Merge branch 'pr-730-merge'

This commit is contained in:
Shadow
2026-01-11 10:16:49 -06:00
4 changed files with 241 additions and 1 deletions

View File

@@ -11,6 +11,7 @@
- Docs: add gog calendar event color IDs from `gog calendar colors`. (#715) — thanks @mjrussell.
- Cron/CLI: trim model overrides on cron edits and document main-session guidance. (#711) — thanks @mjrussell.
- Skills: bundle `skill-creator` to guide creating and packaging skills.
- Discord: expose channel/category management actions in the message tool. (#730) — thanks @NicholasSpisak
### Fixes
- Doctor: surface plugin diagnostics in the report.

View File

@@ -1,6 +1,6 @@
---
name: discord
description: Use when you need to control Discord from Clawdbot via the discord tool: send messages, react, post or upload stickers, upload emojis, run polls, manage threads/pins/search, fetch permissions or member/role/channel info, or handle moderation actions in Discord DMs or channels.
description: Use when you need to control Discord from Clawdbot via the discord tool: send messages, react, post or upload stickers, upload emojis, run polls, manage threads/pins/search, create/edit/delete channels and categories, fetch permissions or member/role/channel info, or handle moderation actions in Discord DMs or channels.
---
# Discord Actions
@@ -135,6 +135,7 @@ Use `discord.actions.*` to disable action groups:
- `emojiUploads`, `stickerUploads`
- `memberInfo`, `roleInfo`, `channelInfo`, `voiceStatus`, `events`
- `roles` (role add/remove, default `false`)
- `channels` (channel/category create/edit/delete/move, default `false`)
- `moderation` (timeout/kick/ban, default `false`)
### Read recent messages
@@ -314,6 +315,90 @@ Use `discord.actions.*` to disable action groups:
}
```
### Channel management (disabled by default)
Create, edit, delete, and move channels and categories. Enable via `discord.actions.channels: true`.
**Create a text channel:**
```json
{
"action": "channelCreate",
"guildId": "999",
"name": "general-chat",
"type": 0,
"parentId": "888",
"topic": "General discussion"
}
```
- `type`: Discord channel type integer (0 = text, 2 = voice, 4 = category; other values supported)
- `parentId`: category ID to nest under (optional)
- `topic`, `position`, `nsfw`: optional
**Create a category:**
```json
{
"action": "categoryCreate",
"guildId": "999",
"name": "Projects"
}
```
**Edit a channel:**
```json
{
"action": "channelEdit",
"channelId": "123",
"name": "new-name",
"topic": "Updated topic"
}
```
- Supports `name`, `topic`, `position`, `parentId` (null to remove from category), `nsfw`, `rateLimitPerUser`
**Move a channel:**
```json
{
"action": "channelMove",
"guildId": "999",
"channelId": "123",
"parentId": "888",
"position": 2
}
```
- `parentId`: target category (null to move to top level)
**Delete a channel:**
```json
{
"action": "channelDelete",
"channelId": "123"
}
```
**Edit/delete a category:**
```json
{
"action": "categoryEdit",
"categoryId": "888",
"name": "Renamed Category"
}
```
```json
{
"action": "categoryDelete",
"categoryId": "888"
}
```
### Voice status
```json

View File

@@ -55,6 +55,13 @@ const AllMessageActions = [
"role-remove",
"channel-info",
"channel-list",
"channel-create",
"channel-edit",
"channel-delete",
"channel-move",
"category-create",
"category-edit",
"category-delete",
"voice-status",
"event-list",
"event-create",
@@ -130,6 +137,14 @@ const MessageToolCommonSchema = {
gatewayUrl: Type.Optional(Type.String()),
gatewayToken: Type.Optional(Type.String()),
timeoutMs: Type.Optional(Type.Number()),
name: Type.Optional(Type.String()),
type: Type.Optional(Type.Number()),
parentId: Type.Optional(Type.Union([Type.String(), Type.Null()])),
topic: Type.Optional(Type.String()),
position: Type.Optional(Type.Number()),
nsfw: Type.Optional(Type.Boolean()),
rateLimitPerUser: Type.Optional(Type.Number()),
categoryId: Type.Optional(Type.String()),
};
function buildMessageToolSchemaFromActions(

View File

@@ -57,6 +57,15 @@ export const discordMessageActions: ProviderMessageActionAdapter = {
actions.add("channel-info");
actions.add("channel-list");
}
if (gate("channels", false)) {
actions.add("channel-create");
actions.add("channel-edit");
actions.add("channel-delete");
actions.add("channel-move");
actions.add("category-create");
actions.add("category-edit");
actions.add("category-delete");
}
if (gate("voiceStatus")) actions.add("voice-status");
if (gate("events")) {
actions.add("event-list");
@@ -449,6 +458,136 @@ export const discordMessageActions: ProviderMessageActionAdapter = {
return await handleDiscordAction({ action: "channelList", guildId }, cfg);
}
if (action === "channel-create") {
const guildId = readStringParam(params, "guildId", { required: true });
const name = readStringParam(params, "name", { required: true });
const type = readNumberParam(params, "type", { integer: true });
const parentId =
params.parentId === null
? null
: readStringParam(params, "parentId");
const topic = readStringParam(params, "topic");
const position = readNumberParam(params, "position", { integer: true });
const nsfw = typeof params.nsfw === "boolean" ? params.nsfw : undefined;
return await handleDiscordAction(
{
action: "channelCreate",
guildId,
name,
type: type ?? undefined,
parentId: parentId ?? undefined,
topic: topic ?? undefined,
position: position ?? undefined,
nsfw,
},
cfg,
);
}
if (action === "channel-edit") {
const channelId = readStringParam(params, "channelId", {
required: true,
});
const name = readStringParam(params, "name");
const topic = readStringParam(params, "topic");
const position = readNumberParam(params, "position", { integer: true });
const parentId =
params.parentId === null
? null
: readStringParam(params, "parentId");
const nsfw = typeof params.nsfw === "boolean" ? params.nsfw : undefined;
const rateLimitPerUser = readNumberParam(params, "rateLimitPerUser", {
integer: true,
});
return await handleDiscordAction(
{
action: "channelEdit",
channelId,
name: name ?? undefined,
topic: topic ?? undefined,
position: position ?? undefined,
parentId: parentId === undefined ? undefined : parentId,
nsfw,
rateLimitPerUser: rateLimitPerUser ?? undefined,
},
cfg,
);
}
if (action === "channel-delete") {
const channelId = readStringParam(params, "channelId", {
required: true,
});
return await handleDiscordAction(
{ action: "channelDelete", channelId },
cfg,
);
}
if (action === "channel-move") {
const guildId = readStringParam(params, "guildId", { required: true });
const channelId = readStringParam(params, "channelId", {
required: true,
});
const parentId =
params.parentId === null
? null
: readStringParam(params, "parentId");
const position = readNumberParam(params, "position", { integer: true });
return await handleDiscordAction(
{
action: "channelMove",
guildId,
channelId,
parentId: parentId === undefined ? undefined : parentId,
position: position ?? undefined,
},
cfg,
);
}
if (action === "category-create") {
const guildId = readStringParam(params, "guildId", { required: true });
const name = readStringParam(params, "name", { required: true });
const position = readNumberParam(params, "position", { integer: true });
return await handleDiscordAction(
{
action: "categoryCreate",
guildId,
name,
position: position ?? undefined,
},
cfg,
);
}
if (action === "category-edit") {
const categoryId = readStringParam(params, "categoryId", {
required: true,
});
const name = readStringParam(params, "name");
const position = readNumberParam(params, "position", { integer: true });
return await handleDiscordAction(
{
action: "categoryEdit",
categoryId,
name: name ?? undefined,
position: position ?? undefined,
},
cfg,
);
}
if (action === "category-delete") {
const categoryId = readStringParam(params, "categoryId", {
required: true,
});
return await handleDiscordAction(
{ action: "categoryDelete", categoryId },
cfg,
);
}
if (action === "voice-status") {
const guildId = readStringParam(params, "guildId", {
required: true,