discoclaw 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.context/README.md +42 -0
- package/.context/architecture.md +58 -0
- package/.context/bot-setup.md +24 -0
- package/.context/dev.md +230 -0
- package/.context/discord.md +144 -0
- package/.context/memory.md +257 -0
- package/.context/ops.md +59 -0
- package/.context/pa-safety.md +47 -0
- package/.context/pa.md +118 -0
- package/.context/project.md +43 -0
- package/.context/runtime.md +253 -0
- package/.context/tasks.md +71 -0
- package/.context/tools.md +75 -0
- package/.env.example +88 -0
- package/.env.example.full +378 -0
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/dist/beads/auto-tag.js +2 -0
- package/dist/beads/auto-tag.test.js +62 -0
- package/dist/beads/bd-cli.js +9 -0
- package/dist/beads/bd-cli.test.js +495 -0
- package/dist/beads/bead-hooks-cli.js +149 -0
- package/dist/beads/bead-sync-cli.js +5 -0
- package/dist/beads/bead-sync-cli.test.js +72 -0
- package/dist/beads/bead-sync-coordinator.js +4 -0
- package/dist/beads/bead-sync-coordinator.test.js +239 -0
- package/dist/beads/bead-sync-watcher.js +2 -0
- package/dist/beads/bead-sync-watcher.test.js +96 -0
- package/dist/beads/bead-sync.js +7 -0
- package/dist/beads/bead-sync.test.js +876 -0
- package/dist/beads/bead-thread-cache.js +8 -0
- package/dist/beads/bead-thread-cache.test.js +91 -0
- package/dist/beads/discord-sync.js +18 -0
- package/dist/beads/discord-sync.test.js +782 -0
- package/dist/beads/find-bead-by-thread.test.js +36 -0
- package/dist/beads/forum-guard.js +2 -0
- package/dist/beads/forum-guard.test.js +204 -0
- package/dist/beads/initialize.js +3 -0
- package/dist/beads/initialize.test.js +304 -0
- package/dist/beads/types.js +10 -0
- package/dist/cli/daemon-installer.js +225 -0
- package/dist/cli/daemon-installer.test.js +289 -0
- package/dist/cli/index.js +42 -0
- package/dist/cli/init-wizard.js +374 -0
- package/dist/cli/init-wizard.test.js +191 -0
- package/dist/config.js +385 -0
- package/dist/config.test.js +589 -0
- package/dist/cron/auto-tag.js +100 -0
- package/dist/cron/auto-tag.test.js +91 -0
- package/dist/cron/cadence.js +74 -0
- package/dist/cron/cadence.test.js +53 -0
- package/dist/cron/cron-sync-coordinator.js +66 -0
- package/dist/cron/cron-sync-coordinator.test.js +118 -0
- package/dist/cron/cron-sync.js +165 -0
- package/dist/cron/cron-sync.test.js +228 -0
- package/dist/cron/cron-tag-map-watcher.js +128 -0
- package/dist/cron/cron-tag-map-watcher.test.js +155 -0
- package/dist/cron/default-timezone.js +23 -0
- package/dist/cron/default-timezone.test.js +30 -0
- package/dist/cron/discord-sync.js +205 -0
- package/dist/cron/discord-sync.test.js +353 -0
- package/dist/cron/executor.js +303 -0
- package/dist/cron/executor.test.js +614 -0
- package/dist/cron/forum-sync.js +347 -0
- package/dist/cron/forum-sync.test.js +539 -0
- package/dist/cron/job-lock.js +164 -0
- package/dist/cron/job-lock.test.js +178 -0
- package/dist/cron/parser.js +68 -0
- package/dist/cron/parser.test.js +115 -0
- package/dist/cron/run-control.js +24 -0
- package/dist/cron/run-control.test.js +27 -0
- package/dist/cron/run-stats.js +265 -0
- package/dist/cron/run-stats.test.js +160 -0
- package/dist/cron/scheduler.js +97 -0
- package/dist/cron/scheduler.test.js +112 -0
- package/dist/cron/tag-map.js +47 -0
- package/dist/cron/tag-map.test.js +64 -0
- package/dist/cron/types.js +1 -0
- package/dist/discoclaw-plan-format.test.js +137 -0
- package/dist/discoclaw-recipe-format.test.js +137 -0
- package/dist/discord/abort-registry.js +70 -0
- package/dist/discord/action-categories.js +36 -0
- package/dist/discord/action-types.js +1 -0
- package/dist/discord/action-utils.js +58 -0
- package/dist/discord/action-utils.test.js +58 -0
- package/dist/discord/actions-beads.js +1 -0
- package/dist/discord/actions-beads.test.js +372 -0
- package/dist/discord/actions-bot-profile.js +107 -0
- package/dist/discord/actions-bot-profile.test.js +138 -0
- package/dist/discord/actions-channels.js +427 -0
- package/dist/discord/actions-channels.test.js +697 -0
- package/dist/discord/actions-config.js +173 -0
- package/dist/discord/actions-config.test.js +322 -0
- package/dist/discord/actions-crons.js +586 -0
- package/dist/discord/actions-crons.test.js +499 -0
- package/dist/discord/actions-defer.js +60 -0
- package/dist/discord/actions-defer.test.js +134 -0
- package/dist/discord/actions-forge.js +134 -0
- package/dist/discord/actions-forge.test.js +206 -0
- package/dist/discord/actions-guild.js +301 -0
- package/dist/discord/actions-guild.test.js +386 -0
- package/dist/discord/actions-memory.js +106 -0
- package/dist/discord/actions-memory.test.js +248 -0
- package/dist/discord/actions-messaging.js +401 -0
- package/dist/discord/actions-messaging.test.js +738 -0
- package/dist/discord/actions-moderation.js +65 -0
- package/dist/discord/actions-moderation.test.js +88 -0
- package/dist/discord/actions-plan.js +445 -0
- package/dist/discord/actions-plan.test.js +610 -0
- package/dist/discord/actions-poll.js +38 -0
- package/dist/discord/actions-poll.test.js +93 -0
- package/dist/discord/actions-tasks.js +3 -0
- package/dist/discord/actions-tasks.test.js +418 -0
- package/dist/discord/actions.js +600 -0
- package/dist/discord/actions.test.js +522 -0
- package/dist/discord/allowed-mentions.js +3 -0
- package/dist/discord/allowed-mentions.test.js +17 -0
- package/dist/discord/allowlist.js +29 -0
- package/dist/discord/allowlist.test.js +24 -0
- package/dist/discord/audit-handler.js +191 -0
- package/dist/discord/audit-handler.test.js +361 -0
- package/dist/discord/bot.js +141 -0
- package/dist/discord/channel-context.js +181 -0
- package/dist/discord/defer-scheduler.js +45 -0
- package/dist/discord/destructive-confirmation.js +128 -0
- package/dist/discord/destructive-confirmation.test.js +49 -0
- package/dist/discord/discord-plan-auto-implement.test.js +18 -0
- package/dist/discord/durable-memory.js +145 -0
- package/dist/discord/durable-memory.test.js +281 -0
- package/dist/discord/durable-write-queue.js +4 -0
- package/dist/discord/file-download.js +308 -0
- package/dist/discord/file-download.test.js +303 -0
- package/dist/discord/forge-audit-verdict.js +140 -0
- package/dist/discord/forge-auto-implement.js +80 -0
- package/dist/discord/forge-auto-implement.test.js +110 -0
- package/dist/discord/forge-commands.js +698 -0
- package/dist/discord/forge-commands.test.js +1606 -0
- package/dist/discord/forge-plan-registry.js +68 -0
- package/dist/discord/forge-plan-registry.test.js +127 -0
- package/dist/discord/forum-count-sync.js +130 -0
- package/dist/discord/forum-count-sync.test.js +200 -0
- package/dist/discord/health-command.js +98 -0
- package/dist/discord/health-command.test.js +195 -0
- package/dist/discord/help-command.js +22 -0
- package/dist/discord/help-command.test.js +49 -0
- package/dist/discord/image-download.js +201 -0
- package/dist/discord/image-download.test.js +499 -0
- package/dist/discord/inflight-replies.js +228 -0
- package/dist/discord/inflight-replies.test.js +295 -0
- package/dist/discord/json-extract.js +110 -0
- package/dist/discord/keyed-queue.js +22 -0
- package/dist/discord/memory-commands.js +85 -0
- package/dist/discord/memory-commands.test.js +159 -0
- package/dist/discord/memory-timing.integration.test.js +159 -0
- package/dist/discord/message-coordinator.js +2347 -0
- package/dist/discord/message-coordinator.onboarding.test.js +183 -0
- package/dist/discord/message-coordinator.plan-run.test.js +264 -0
- package/dist/discord/message-history.js +53 -0
- package/dist/discord/message-history.test.js +95 -0
- package/dist/discord/models-command.js +59 -0
- package/dist/discord/models-command.test.js +150 -0
- package/dist/discord/nickname.test.js +76 -0
- package/dist/discord/onboarding-completion.js +55 -0
- package/dist/discord/onboarding-completion.test.js +176 -0
- package/dist/discord/output-common.js +178 -0
- package/dist/discord/output-common.test.js +198 -0
- package/dist/discord/output-utils.js +156 -0
- package/dist/discord/parse-identity-name.test.js +129 -0
- package/dist/discord/plan-commands.js +612 -0
- package/dist/discord/plan-commands.test.js +1622 -0
- package/dist/discord/plan-manager.js +1491 -0
- package/dist/discord/plan-manager.test.js +2380 -0
- package/dist/discord/plan-parser.js +110 -0
- package/dist/discord/plan-parser.test.js +63 -0
- package/dist/discord/plan-run-phase-start.js +20 -0
- package/dist/discord/plan-run-phase-start.test.js +29 -0
- package/dist/discord/platform-message.js +45 -0
- package/dist/discord/platform-message.test.js +110 -0
- package/dist/discord/prompt-common.js +240 -0
- package/dist/discord/prompt-common.test.js +423 -0
- package/dist/discord/reaction-handler.js +691 -0
- package/dist/discord/reaction-handler.test.js +1574 -0
- package/dist/discord/reaction-prompts.js +118 -0
- package/dist/discord/reaction-prompts.test.js +253 -0
- package/dist/discord/reply-reference.js +66 -0
- package/dist/discord/reply-reference.test.js +125 -0
- package/dist/discord/restart-command.js +143 -0
- package/dist/discord/restart-command.test.js +196 -0
- package/dist/discord/runtime-utils.js +43 -0
- package/dist/discord/runtime-utils.test.js +112 -0
- package/dist/discord/session-key.js +7 -0
- package/dist/discord/session-key.test.js +13 -0
- package/dist/discord/shortterm-memory.js +166 -0
- package/dist/discord/shortterm-memory.test.js +345 -0
- package/dist/discord/shutdown-context.js +122 -0
- package/dist/discord/shutdown-context.test.js +279 -0
- package/dist/discord/startup-profile.test.js +214 -0
- package/dist/discord/status-channel.js +190 -0
- package/dist/discord/status-channel.test.js +282 -0
- package/dist/discord/status-command.js +206 -0
- package/dist/discord/status-command.test.js +341 -0
- package/dist/discord/streaming-progress.js +107 -0
- package/dist/discord/streaming-progress.test.js +93 -0
- package/dist/discord/summarizer.js +89 -0
- package/dist/discord/summarizer.test.js +245 -0
- package/dist/discord/system-bootstrap.js +396 -0
- package/dist/discord/system-bootstrap.test.js +724 -0
- package/dist/discord/thread-context.js +169 -0
- package/dist/discord/thread-context.test.js +386 -0
- package/dist/discord/tool-aware-queue.js +116 -0
- package/dist/discord/tool-aware-queue.test.js +180 -0
- package/dist/discord/update-command.js +127 -0
- package/dist/discord/update-command.test.js +275 -0
- package/dist/discord/user-errors.js +40 -0
- package/dist/discord/user-errors.test.js +31 -0
- package/dist/discord/user-turn-to-durable.js +111 -0
- package/dist/discord/user-turn-to-durable.test.js +273 -0
- package/dist/discord-followup.test.js +677 -0
- package/dist/discord.channel-context.test.js +95 -0
- package/dist/discord.fail-closed.test.js +199 -0
- package/dist/discord.health-command.integration.test.js +140 -0
- package/dist/discord.js +190 -0
- package/dist/discord.prompt-context.test.js +1431 -0
- package/dist/discord.render.test.js +621 -0
- package/dist/discord.status-wiring.test.js +187 -0
- package/dist/engine/claudeCli.js +137 -0
- package/dist/engine/types.js +1 -0
- package/dist/group-queue.js +25 -0
- package/dist/health/credential-check.js +175 -0
- package/dist/health/credential-check.test.js +401 -0
- package/dist/health/startup-healing.js +139 -0
- package/dist/health/startup-healing.test.js +298 -0
- package/dist/identity.js +36 -0
- package/dist/index.js +1378 -0
- package/dist/logging/logger-like.js +1 -0
- package/dist/observability/memory-sampler.js +51 -0
- package/dist/observability/memory-sampler.test.js +93 -0
- package/dist/observability/metrics.js +88 -0
- package/dist/observability/metrics.test.js +42 -0
- package/dist/onboarding/onboarding-flow.js +246 -0
- package/dist/onboarding/onboarding-flow.test.js +238 -0
- package/dist/onboarding/onboarding-writer.js +102 -0
- package/dist/onboarding/onboarding-writer.test.js +143 -0
- package/dist/pidlock.js +187 -0
- package/dist/pidlock.test.js +128 -0
- package/dist/pipeline/engine.js +206 -0
- package/dist/pipeline/engine.test.js +771 -0
- package/dist/root-policy.js +21 -0
- package/dist/root-policy.test.js +55 -0
- package/dist/runtime/claude-code-cli.js +35 -0
- package/dist/runtime/claude-code-cli.test.js +1199 -0
- package/dist/runtime/cli-adapter.js +584 -0
- package/dist/runtime/cli-output-parsers.js +108 -0
- package/dist/runtime/cli-shared.js +96 -0
- package/dist/runtime/cli-shared.test.js +104 -0
- package/dist/runtime/cli-strategy.js +6 -0
- package/dist/runtime/codex-cli.js +16 -0
- package/dist/runtime/codex-cli.test.js +862 -0
- package/dist/runtime/concurrency-limit.js +80 -0
- package/dist/runtime/concurrency-limit.test.js +137 -0
- package/dist/runtime/gemini-cli.js +16 -0
- package/dist/runtime/gemini-cli.test.js +413 -0
- package/dist/runtime/long-running-process.js +415 -0
- package/dist/runtime/long-running-process.test.js +318 -0
- package/dist/runtime/model-smoke-helpers.js +160 -0
- package/dist/runtime/model-smoke.test.js +194 -0
- package/dist/runtime/model-tiers.js +33 -0
- package/dist/runtime/model-tiers.test.js +65 -0
- package/dist/runtime/openai-auth.js +151 -0
- package/dist/runtime/openai-auth.test.js +361 -0
- package/dist/runtime/openai-compat.js +178 -0
- package/dist/runtime/openai-compat.test.js +449 -0
- package/dist/runtime/process-pool.js +93 -0
- package/dist/runtime/process-pool.test.js +148 -0
- package/dist/runtime/registry.js +15 -0
- package/dist/runtime/registry.test.js +47 -0
- package/dist/runtime/session-scanner.js +186 -0
- package/dist/runtime/session-scanner.test.js +257 -0
- package/dist/runtime/strategies/claude-strategy.js +193 -0
- package/dist/runtime/strategies/codex-strategy.js +161 -0
- package/dist/runtime/strategies/gemini-strategy.js +64 -0
- package/dist/runtime/strategies/template-strategy.js +85 -0
- package/dist/runtime/tool-capabilities.js +27 -0
- package/dist/runtime/tool-capabilities.test.js +24 -0
- package/dist/runtime/tool-labels.js +48 -0
- package/dist/runtime/types.js +2 -0
- package/dist/sessionManager.js +47 -0
- package/dist/sessions.js +18 -0
- package/dist/tasks/architecture-contract.js +33 -0
- package/dist/tasks/architecture-contract.test.js +90 -0
- package/dist/tasks/auto-tag.js +50 -0
- package/dist/tasks/auto-tag.test.js +64 -0
- package/dist/tasks/bd-cli.js +164 -0
- package/dist/tasks/bd-cli.test.js +359 -0
- package/dist/tasks/bead-sync.js +1 -0
- package/dist/tasks/context-summary.js +27 -0
- package/dist/tasks/discord-sync.js +3 -0
- package/dist/tasks/discord-sync.test.js +685 -0
- package/dist/tasks/discord-types.js +4 -0
- package/dist/tasks/find-task-by-thread.test.js +36 -0
- package/dist/tasks/forum-guard.js +81 -0
- package/dist/tasks/forum-guard.test.js +192 -0
- package/dist/tasks/initialize.js +77 -0
- package/dist/tasks/initialize.test.js +263 -0
- package/dist/tasks/logger-types.js +1 -0
- package/dist/tasks/metrics-types.js +3 -0
- package/dist/tasks/migrate.js +33 -0
- package/dist/tasks/migrate.test.js +156 -0
- package/dist/tasks/path-defaults.js +67 -0
- package/dist/tasks/path-defaults.test.js +73 -0
- package/dist/tasks/runtime-types.js +1 -0
- package/dist/tasks/service.js +33 -0
- package/dist/tasks/service.test.js +51 -0
- package/dist/tasks/store.js +238 -0
- package/dist/tasks/store.test.js +417 -0
- package/dist/tasks/sync-context.js +1 -0
- package/dist/tasks/sync-contract.js +24 -0
- package/dist/tasks/sync-contract.test.js +25 -0
- package/dist/tasks/sync-coordinator-metrics.js +41 -0
- package/dist/tasks/sync-coordinator-retries.js +71 -0
- package/dist/tasks/sync-coordinator.js +96 -0
- package/dist/tasks/sync-coordinator.test.js +501 -0
- package/dist/tasks/sync-types.js +1 -0
- package/dist/tasks/sync-watcher.js +27 -0
- package/dist/tasks/sync-watcher.test.js +92 -0
- package/dist/tasks/tag-map.js +36 -0
- package/dist/tasks/tag-map.test.js +54 -0
- package/dist/tasks/task-action-contract.js +16 -0
- package/dist/tasks/task-action-contract.test.js +16 -0
- package/dist/tasks/task-action-executor.js +18 -0
- package/dist/tasks/task-action-executor.test.js +420 -0
- package/dist/tasks/task-action-mutation-helpers.js +17 -0
- package/dist/tasks/task-action-mutations.js +151 -0
- package/dist/tasks/task-action-prompt.js +62 -0
- package/dist/tasks/task-action-read-ops.js +73 -0
- package/dist/tasks/task-action-runner-types.js +1 -0
- package/dist/tasks/task-action-thread-sync.js +82 -0
- package/dist/tasks/task-actions.js +3 -0
- package/dist/tasks/task-cli.js +227 -0
- package/dist/tasks/task-context.js +1 -0
- package/dist/tasks/task-lifecycle.js +46 -0
- package/dist/tasks/task-lifecycle.test.js +35 -0
- package/dist/tasks/task-sync-apply-plan.js +95 -0
- package/dist/tasks/task-sync-apply-types.js +12 -0
- package/dist/tasks/task-sync-apply.js +319 -0
- package/dist/tasks/task-sync-cli.js +89 -0
- package/dist/tasks/task-sync-cli.test.js +70 -0
- package/dist/tasks/task-sync-engine.js +88 -0
- package/dist/tasks/task-sync-engine.test.js +934 -0
- package/dist/tasks/task-sync-phase-apply.js +171 -0
- package/dist/tasks/task-sync-pipeline.js +2 -0
- package/dist/tasks/task-sync-pipeline.test.js +265 -0
- package/dist/tasks/task-sync-reconcile-plan.js +182 -0
- package/dist/tasks/task-sync-reconcile.js +144 -0
- package/dist/tasks/task-sync.js +56 -0
- package/dist/tasks/task-sync.test.js +86 -0
- package/dist/tasks/thread-cache.js +42 -0
- package/dist/tasks/thread-cache.test.js +89 -0
- package/dist/tasks/thread-contracts.test.js +711 -0
- package/dist/tasks/thread-forum-ops.js +68 -0
- package/dist/tasks/thread-helpers.js +86 -0
- package/dist/tasks/thread-helpers.test.js +33 -0
- package/dist/tasks/thread-lifecycle-ops.js +144 -0
- package/dist/tasks/thread-ops-shared.js +21 -0
- package/dist/tasks/thread-ops.js +2 -0
- package/dist/tasks/types.js +20 -0
- package/dist/tasks/types.test.js +60 -0
- package/dist/test-setup.js +11 -0
- package/dist/test-setup.test.js +42 -0
- package/dist/transport/types.js +1 -0
- package/dist/validate.js +41 -0
- package/dist/validate.test.js +94 -0
- package/dist/version.js +15 -0
- package/dist/version.test.js +31 -0
- package/dist/webhook/server.js +199 -0
- package/dist/webhook/server.test.js +460 -0
- package/dist/workspace-bootstrap.js +135 -0
- package/dist/workspace-bootstrap.test.js +514 -0
- package/dist/workspace-permissions.js +134 -0
- package/dist/workspace-permissions.test.js +181 -0
- package/package.json +74 -0
- package/scripts/cron/cron-tag-map.json +9 -0
- package/scripts/tasks/tag-map.json +10 -0
- package/systemd/discoclaw.service +19 -0
- package/templates/recipes/integration.discoclaw-recipe.md +171 -0
- package/templates/workspace/AGENTS.md +217 -0
- package/templates/workspace/BOOTSTRAP.md +1 -0
- package/templates/workspace/HEARTBEAT.md +10 -0
- package/templates/workspace/IDENTITY.md +16 -0
- package/templates/workspace/MEMORY.md +24 -0
- package/templates/workspace/SOUL.md +52 -0
- package/templates/workspace/TOOLS.md +304 -0
- package/templates/workspace/USER.md +37 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { ChannelType } from 'discord.js';
|
|
2
|
+
// Record ensures every union member is listed; TS errors if a new type is added to the union but not here.
|
|
3
|
+
const CHANNEL_TYPE_MAP = {
|
|
4
|
+
channelCreate: true, channelEdit: true, channelDelete: true,
|
|
5
|
+
channelList: true, channelInfo: true, categoryCreate: true,
|
|
6
|
+
channelMove: true, threadListArchived: true,
|
|
7
|
+
forumTagCreate: true, forumTagDelete: true, forumTagList: true,
|
|
8
|
+
threadEdit: true,
|
|
9
|
+
};
|
|
10
|
+
export const CHANNEL_ACTION_TYPES = new Set(Object.keys(CHANNEL_TYPE_MAP));
|
|
11
|
+
const CHANNEL_TYPE_ENUM = {
|
|
12
|
+
text: ChannelType.GuildText,
|
|
13
|
+
voice: ChannelType.GuildVoice,
|
|
14
|
+
announcement: ChannelType.GuildAnnouncement,
|
|
15
|
+
stage: ChannelType.GuildStageVoice,
|
|
16
|
+
};
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Executor
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
export async function executeChannelAction(action, ctx) {
|
|
21
|
+
const { guild } = ctx;
|
|
22
|
+
switch (action.type) {
|
|
23
|
+
case 'channelCreate': {
|
|
24
|
+
let parent;
|
|
25
|
+
if (action.parent) {
|
|
26
|
+
const cat = guild.channels.cache.find((ch) => ch.type === ChannelType.GuildCategory &&
|
|
27
|
+
ch.name.toLowerCase() === action.parent.toLowerCase());
|
|
28
|
+
if (cat) {
|
|
29
|
+
parent = cat.id;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return { ok: false, error: `Category "${action.parent}" not found` };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const resolvedType = action.channelType
|
|
36
|
+
? CHANNEL_TYPE_ENUM[action.channelType]
|
|
37
|
+
: ChannelType.GuildText;
|
|
38
|
+
if (resolvedType === undefined) {
|
|
39
|
+
return { ok: false, error: `Invalid channelType: "${action.channelType}"` };
|
|
40
|
+
}
|
|
41
|
+
const created = await guild.channels.create({
|
|
42
|
+
name: action.name,
|
|
43
|
+
type: resolvedType,
|
|
44
|
+
parent,
|
|
45
|
+
topic: action.topic,
|
|
46
|
+
});
|
|
47
|
+
return { ok: true, summary: `Created #${created.name}${parent ? ` under ${action.parent}` : ''}` };
|
|
48
|
+
}
|
|
49
|
+
case 'channelEdit': {
|
|
50
|
+
if (action.name == null && action.topic == null) {
|
|
51
|
+
return { ok: false, error: 'channelEdit requires at least one of name or topic' };
|
|
52
|
+
}
|
|
53
|
+
const channel = guild.channels.cache.get(action.channelId);
|
|
54
|
+
if (!channel)
|
|
55
|
+
return { ok: false, error: `Channel "${action.channelId}" not found` };
|
|
56
|
+
const edits = {};
|
|
57
|
+
if (action.name != null)
|
|
58
|
+
edits.name = action.name;
|
|
59
|
+
if (action.topic != null)
|
|
60
|
+
edits.topic = action.topic;
|
|
61
|
+
await channel.edit(edits);
|
|
62
|
+
const parts = [];
|
|
63
|
+
if (action.name != null)
|
|
64
|
+
parts.push(`name → ${action.name}`);
|
|
65
|
+
if (action.topic != null)
|
|
66
|
+
parts.push(`topic updated`);
|
|
67
|
+
return { ok: true, summary: `Edited #${channel.name}: ${parts.join(', ')}` };
|
|
68
|
+
}
|
|
69
|
+
case 'channelDelete': {
|
|
70
|
+
const channel = guild.channels.cache.get(action.channelId);
|
|
71
|
+
if (!channel)
|
|
72
|
+
return { ok: false, error: `Channel "${action.channelId}" not found` };
|
|
73
|
+
const name = channel.name;
|
|
74
|
+
await channel.delete();
|
|
75
|
+
return { ok: true, summary: `Deleted #${name}` };
|
|
76
|
+
}
|
|
77
|
+
case 'channelList': {
|
|
78
|
+
const grouped = new Map();
|
|
79
|
+
const uncategorized = [];
|
|
80
|
+
for (const ch of guild.channels.cache.values()) {
|
|
81
|
+
if (ch.type === ChannelType.GuildCategory)
|
|
82
|
+
continue;
|
|
83
|
+
const parentName = ch.parent?.name;
|
|
84
|
+
if (parentName) {
|
|
85
|
+
const list = grouped.get(parentName) ?? [];
|
|
86
|
+
list.push(`#${ch.name} (id:${ch.id})`);
|
|
87
|
+
grouped.set(parentName, list);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
uncategorized.push(`#${ch.name} (id:${ch.id})`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const lines = [];
|
|
94
|
+
if (uncategorized.length > 0) {
|
|
95
|
+
lines.push(`(no category): ${uncategorized.join(', ')}`);
|
|
96
|
+
}
|
|
97
|
+
for (const [cat, chs] of grouped) {
|
|
98
|
+
lines.push(`${cat}: ${chs.join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
return { ok: true, summary: lines.length > 0 ? lines.join('\n') : '(no channels)' };
|
|
101
|
+
}
|
|
102
|
+
case 'channelInfo': {
|
|
103
|
+
const channel = guild.channels.cache.get(action.channelId);
|
|
104
|
+
if (!channel)
|
|
105
|
+
return { ok: false, error: `Channel "${action.channelId}" not found` };
|
|
106
|
+
const info = [
|
|
107
|
+
`Name: #${channel.name}`,
|
|
108
|
+
`ID: ${channel.id}`,
|
|
109
|
+
`Type: ${ChannelType[channel.type] ?? channel.type}`,
|
|
110
|
+
];
|
|
111
|
+
if (channel.parent)
|
|
112
|
+
info.push(`Category: ${channel.parent.name}`);
|
|
113
|
+
const gc = channel;
|
|
114
|
+
if (gc.topic)
|
|
115
|
+
info.push(`Topic: ${gc.topic}`);
|
|
116
|
+
if (gc.createdAt)
|
|
117
|
+
info.push(`Created: ${gc.createdAt.toISOString().slice(0, 10)}`);
|
|
118
|
+
return { ok: true, summary: info.join('\n') };
|
|
119
|
+
}
|
|
120
|
+
case 'categoryCreate': {
|
|
121
|
+
const created = await guild.channels.create({
|
|
122
|
+
name: action.name,
|
|
123
|
+
type: ChannelType.GuildCategory,
|
|
124
|
+
position: action.position,
|
|
125
|
+
});
|
|
126
|
+
return { ok: true, summary: `Created category "${created.name}"` };
|
|
127
|
+
}
|
|
128
|
+
case 'channelMove': {
|
|
129
|
+
if (action.parent == null && action.position == null) {
|
|
130
|
+
return { ok: false, error: 'channelMove requires at least one of parent or position' };
|
|
131
|
+
}
|
|
132
|
+
const channel = guild.channels.cache.get(action.channelId);
|
|
133
|
+
if (!channel)
|
|
134
|
+
return { ok: false, error: `Channel "${action.channelId}" not found` };
|
|
135
|
+
const parts = [];
|
|
136
|
+
if (action.parent != null) {
|
|
137
|
+
if (action.parent === '') {
|
|
138
|
+
await channel.setParent(null);
|
|
139
|
+
parts.push('removed from category');
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// Resolve by ID first, then by name (case-insensitive).
|
|
143
|
+
let cat = guild.channels.cache.get(action.parent);
|
|
144
|
+
if (!cat || cat.type !== ChannelType.GuildCategory) {
|
|
145
|
+
cat = guild.channels.cache.find((ch) => ch.type === ChannelType.GuildCategory &&
|
|
146
|
+
ch.name.toLowerCase() === action.parent.toLowerCase());
|
|
147
|
+
}
|
|
148
|
+
if (!cat)
|
|
149
|
+
return { ok: false, error: `Category "${action.parent}" not found` };
|
|
150
|
+
await channel.setParent(cat.id);
|
|
151
|
+
parts.push(`moved to ${cat.name}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (action.position != null) {
|
|
155
|
+
await channel.setPosition(action.position);
|
|
156
|
+
parts.push(`position → ${action.position}`);
|
|
157
|
+
}
|
|
158
|
+
return { ok: true, summary: `Moved #${channel.name}: ${parts.join(', ')}` };
|
|
159
|
+
}
|
|
160
|
+
case 'threadListArchived': {
|
|
161
|
+
const channel = guild.channels.cache.get(action.channelId);
|
|
162
|
+
if (!channel)
|
|
163
|
+
return { ok: false, error: `Channel "${action.channelId}" not found` };
|
|
164
|
+
if (channel.type !== ChannelType.GuildForum && channel.type !== ChannelType.GuildText) {
|
|
165
|
+
return { ok: false, error: `Channel #${channel.name} is not a forum or text channel` };
|
|
166
|
+
}
|
|
167
|
+
const limit = action.limit ?? 50;
|
|
168
|
+
const fetched = await channel.threads.fetchArchived({ limit, fetchAll: true });
|
|
169
|
+
const threads = [...fetched.threads.values()];
|
|
170
|
+
if (threads.length === 0) {
|
|
171
|
+
return { ok: true, summary: `No archived threads in #${channel.name}` };
|
|
172
|
+
}
|
|
173
|
+
const lines = threads.map((t) => `• ${t.name} (id:${t.id})`);
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
summary: `Archived threads in #${channel.name} (${threads.length}):\n${lines.join('\n')}`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
case 'forumTagCreate': {
|
|
180
|
+
const channel = guild.channels.cache.get(action.channelId);
|
|
181
|
+
if (!channel)
|
|
182
|
+
return { ok: false, error: `Channel "${action.channelId}" not found` };
|
|
183
|
+
if (channel.type !== ChannelType.GuildForum) {
|
|
184
|
+
return { ok: false, error: `Channel #${channel.name} is not a forum channel` };
|
|
185
|
+
}
|
|
186
|
+
const forum = channel;
|
|
187
|
+
const existingTags = forum.availableTags ?? [];
|
|
188
|
+
if (existingTags.length >= 20) {
|
|
189
|
+
return { ok: false, error: `Forum #${channel.name} already has 20 tags (Discord maximum)` };
|
|
190
|
+
}
|
|
191
|
+
const newTag = { name: action.name };
|
|
192
|
+
if (action.emoji) {
|
|
193
|
+
newTag.emoji = { id: action.emoji.id ?? null, name: action.emoji.name ?? null };
|
|
194
|
+
}
|
|
195
|
+
const updatedTags = [
|
|
196
|
+
...existingTags.map((t) => ({ id: t.id, name: t.name, moderated: t.moderated, emoji: t.emoji })),
|
|
197
|
+
newTag,
|
|
198
|
+
];
|
|
199
|
+
await forum.edit({ availableTags: updatedTags });
|
|
200
|
+
// Re-fetch to get the created tag's ID.
|
|
201
|
+
const updated = guild.channels.cache.get(action.channelId);
|
|
202
|
+
const createdTag = updated?.availableTags?.find((t) => t.name.toLowerCase() === action.name.toLowerCase());
|
|
203
|
+
const tagId = createdTag?.id ?? 'unknown';
|
|
204
|
+
return { ok: true, summary: `Created forum tag "${action.name}" (id:${tagId}) on #${channel.name}` };
|
|
205
|
+
}
|
|
206
|
+
case 'forumTagDelete': {
|
|
207
|
+
const channel = guild.channels.cache.get(action.channelId);
|
|
208
|
+
if (!channel)
|
|
209
|
+
return { ok: false, error: `Channel "${action.channelId}" not found` };
|
|
210
|
+
if (channel.type !== ChannelType.GuildForum) {
|
|
211
|
+
return { ok: false, error: `Channel #${channel.name} is not a forum channel` };
|
|
212
|
+
}
|
|
213
|
+
const forum = channel;
|
|
214
|
+
const existingTags = forum.availableTags ?? [];
|
|
215
|
+
const tagToDelete = existingTags.find((t) => t.id === action.tagId);
|
|
216
|
+
if (!tagToDelete) {
|
|
217
|
+
return { ok: false, error: `Tag "${action.tagId}" not found on forum #${channel.name}` };
|
|
218
|
+
}
|
|
219
|
+
const filteredTags = existingTags
|
|
220
|
+
.filter((t) => t.id !== action.tagId)
|
|
221
|
+
.map((t) => ({ id: t.id, name: t.name, moderated: t.moderated, emoji: t.emoji }));
|
|
222
|
+
await forum.edit({ availableTags: filteredTags });
|
|
223
|
+
return { ok: true, summary: `Deleted forum tag "${tagToDelete.name}" (id:${action.tagId}) from #${channel.name}` };
|
|
224
|
+
}
|
|
225
|
+
case 'forumTagList': {
|
|
226
|
+
const channel = guild.channels.cache.get(action.channelId);
|
|
227
|
+
if (!channel)
|
|
228
|
+
return { ok: false, error: `Channel "${action.channelId}" not found` };
|
|
229
|
+
if (channel.type !== ChannelType.GuildForum) {
|
|
230
|
+
return { ok: false, error: `Channel #${channel.name} is not a forum channel` };
|
|
231
|
+
}
|
|
232
|
+
const forum = channel;
|
|
233
|
+
const tags = forum.availableTags ?? [];
|
|
234
|
+
if (tags.length === 0) {
|
|
235
|
+
return { ok: true, summary: `No tags on forum #${channel.name}` };
|
|
236
|
+
}
|
|
237
|
+
const lines = tags.map((t) => {
|
|
238
|
+
const emojiStr = t.emoji?.name ? ` ${t.emoji.name}` : t.emoji?.id ? ` (emoji:${t.emoji.id})` : '';
|
|
239
|
+
return `• ${t.name}${emojiStr} (id:${t.id})`;
|
|
240
|
+
});
|
|
241
|
+
return { ok: true, summary: `Tags on #${channel.name} (${tags.length}):\n${lines.join('\n')}` };
|
|
242
|
+
}
|
|
243
|
+
case 'threadEdit': {
|
|
244
|
+
if (action.appliedTags == null && action.name == null) {
|
|
245
|
+
return { ok: false, error: 'threadEdit requires at least one of appliedTags or name' };
|
|
246
|
+
}
|
|
247
|
+
// Cache-first fetch, same pattern as fetchThreadChannel in task sync.
|
|
248
|
+
let thread = null;
|
|
249
|
+
const cached = ctx.client.channels.cache.get(action.threadId);
|
|
250
|
+
if (cached && cached.isThread()) {
|
|
251
|
+
thread = cached;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
try {
|
|
255
|
+
const fetched = await ctx.client.channels.fetch(action.threadId);
|
|
256
|
+
if (fetched && fetched.isThread())
|
|
257
|
+
thread = fetched;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// fall through to forum-channel fallback below
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Fallback: search guild forum channels for the thread.
|
|
264
|
+
// client.channels.fetch() can miss archived forum threads that have
|
|
265
|
+
// been evicted from the gateway cache after archiving.
|
|
266
|
+
// Check archived first (the common case for this fallback), then active.
|
|
267
|
+
if (!thread) {
|
|
268
|
+
for (const ch of guild.channels.cache.values()) {
|
|
269
|
+
if (ch.type !== ChannelType.GuildForum)
|
|
270
|
+
continue;
|
|
271
|
+
try {
|
|
272
|
+
const archived = await ch.threads.fetchArchived({ limit: 100 });
|
|
273
|
+
const archivedMatch = archived.threads.get(action.threadId);
|
|
274
|
+
if (archivedMatch?.isThread()) {
|
|
275
|
+
thread = archivedMatch;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
const active = await ch.threads.fetchActive();
|
|
279
|
+
const activeMatch = active.threads.get(action.threadId);
|
|
280
|
+
if (activeMatch?.isThread()) {
|
|
281
|
+
thread = activeMatch;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// skip forums we can't access
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (!thread)
|
|
291
|
+
return { ok: false, error: `Thread "${action.threadId}" not found` };
|
|
292
|
+
if (thread.guildId !== guild.id) {
|
|
293
|
+
return { ok: false, error: `Thread "${action.threadId}" does not belong to this guild` };
|
|
294
|
+
}
|
|
295
|
+
if (action.appliedTags != null) {
|
|
296
|
+
const parentType = thread.parent?.type;
|
|
297
|
+
if (parentType !== ChannelType.GuildForum) {
|
|
298
|
+
return { ok: false, error: `Thread "${action.threadId}" is not in a forum channel — appliedTags only applies to forum threads` };
|
|
299
|
+
}
|
|
300
|
+
if (action.appliedTags.length > 5) {
|
|
301
|
+
return { ok: false, error: `appliedTags exceeds Discord maximum of 5 (got ${action.appliedTags.length})` };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Unarchive before editing — Discord rejects edits on archived threads.
|
|
305
|
+
const wasArchived = thread.archived === true;
|
|
306
|
+
if (wasArchived) {
|
|
307
|
+
try {
|
|
308
|
+
await thread.setArchived(false);
|
|
309
|
+
}
|
|
310
|
+
catch { /* proceed — edit may still work */ }
|
|
311
|
+
}
|
|
312
|
+
const edits = {};
|
|
313
|
+
if (action.appliedTags != null)
|
|
314
|
+
edits.appliedTags = action.appliedTags;
|
|
315
|
+
if (action.name != null)
|
|
316
|
+
edits.name = action.name;
|
|
317
|
+
await thread.edit(edits);
|
|
318
|
+
// Re-archive if the thread was archived before we touched it.
|
|
319
|
+
let rearchiveFailed = false;
|
|
320
|
+
if (wasArchived) {
|
|
321
|
+
try {
|
|
322
|
+
await thread.setArchived(true);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
rearchiveFailed = true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const parts = [];
|
|
329
|
+
if (action.name != null)
|
|
330
|
+
parts.push(`name → ${action.name}`);
|
|
331
|
+
if (action.appliedTags != null)
|
|
332
|
+
parts.push(`appliedTags → [${action.appliedTags.join(', ')}]`);
|
|
333
|
+
const warning = rearchiveFailed ? ' (warning: failed to re-archive)' : '';
|
|
334
|
+
return { ok: true, summary: `Edited thread "${thread.name}" (id:${thread.id}): ${parts.join(', ')}${warning}` };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
// Prompt section
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
export function channelActionsPromptSection() {
|
|
342
|
+
return `### Channel Management
|
|
343
|
+
|
|
344
|
+
**channelCreate** — Create a channel:
|
|
345
|
+
\`\`\`
|
|
346
|
+
<discord-action>{"type":"channelCreate","name":"channel-name","parent":"Category Name","topic":"Optional topic","channelType":"text"}</discord-action>
|
|
347
|
+
\`\`\`
|
|
348
|
+
- \`name\` (required): Channel name (lowercase, hyphens, no spaces).
|
|
349
|
+
- \`parent\` (optional): Category name to create the channel under.
|
|
350
|
+
- \`topic\` (optional): Channel topic description.
|
|
351
|
+
- \`channelType\` (optional): \`text\` (default), \`voice\`, \`announcement\`, or \`stage\`.
|
|
352
|
+
|
|
353
|
+
**channelEdit** — Edit a channel's name or topic:
|
|
354
|
+
\`\`\`
|
|
355
|
+
<discord-action>{"type":"channelEdit","channelId":"123","name":"new-name","topic":"New topic"}</discord-action>
|
|
356
|
+
\`\`\`
|
|
357
|
+
- \`channelId\` (required): Channel ID.
|
|
358
|
+
- \`name\` (optional): New channel name.
|
|
359
|
+
- \`topic\` (optional): New channel topic.
|
|
360
|
+
|
|
361
|
+
**channelDelete** — Delete a channel (destructive — confirm with user first):
|
|
362
|
+
\`\`\`
|
|
363
|
+
<discord-action>{"type":"channelDelete","channelId":"123"}</discord-action>
|
|
364
|
+
\`\`\`
|
|
365
|
+
|
|
366
|
+
**channelList** — List all channels in the server:
|
|
367
|
+
\`\`\`
|
|
368
|
+
<discord-action>{"type":"channelList"}</discord-action>
|
|
369
|
+
\`\`\`
|
|
370
|
+
|
|
371
|
+
**channelInfo** — Get details about a channel:
|
|
372
|
+
\`\`\`
|
|
373
|
+
<discord-action>{"type":"channelInfo","channelId":"123"}</discord-action>
|
|
374
|
+
\`\`\`
|
|
375
|
+
|
|
376
|
+
**categoryCreate** — Create a channel category:
|
|
377
|
+
\`\`\`
|
|
378
|
+
<discord-action>{"type":"categoryCreate","name":"Category Name"}</discord-action>
|
|
379
|
+
\`\`\`
|
|
380
|
+
|
|
381
|
+
**channelMove** — Move a channel to a category or position:
|
|
382
|
+
\`\`\`
|
|
383
|
+
<discord-action>{"type":"channelMove","channelId":"123","parent":"Category Name","position":0}</discord-action>
|
|
384
|
+
\`\`\`
|
|
385
|
+
- \`channelId\` (required): Channel ID.
|
|
386
|
+
- \`parent\` (optional): Category name or ID. Empty string removes from category.
|
|
387
|
+
- \`position\` (optional): New position (0-based).
|
|
388
|
+
At least one of parent or position is required.
|
|
389
|
+
|
|
390
|
+
**threadListArchived** — List archived threads in a forum or text channel:
|
|
391
|
+
\`\`\`
|
|
392
|
+
<discord-action>{"type":"threadListArchived","channelId":"123","limit":25}</discord-action>
|
|
393
|
+
\`\`\`
|
|
394
|
+
- \`channelId\` (required): The forum or text channel ID.
|
|
395
|
+
- \`limit\` (optional): Max threads to return (default 50).
|
|
396
|
+
|
|
397
|
+
**forumTagCreate** — Create a tag on a forum channel:
|
|
398
|
+
\`\`\`
|
|
399
|
+
<discord-action>{"type":"forumTagCreate","channelId":"123","name":"open","emoji":{"name":"🟢"}}</discord-action>
|
|
400
|
+
\`\`\`
|
|
401
|
+
- \`channelId\` (required): The forum channel ID.
|
|
402
|
+
- \`name\` (required): Tag name.
|
|
403
|
+
- \`emoji\` (optional): Object with \`id\` (custom emoji) or \`name\` (unicode emoji).
|
|
404
|
+
Returns the created tag's ID in the summary.
|
|
405
|
+
|
|
406
|
+
**forumTagDelete** — Delete a tag from a forum channel (destructive — confirm with user first):
|
|
407
|
+
\`\`\`
|
|
408
|
+
<discord-action>{"type":"forumTagDelete","channelId":"123","tagId":"456"}</discord-action>
|
|
409
|
+
\`\`\`
|
|
410
|
+
- \`channelId\` (required): The forum channel ID.
|
|
411
|
+
- \`tagId\` (required): The tag ID to delete.
|
|
412
|
+
|
|
413
|
+
**forumTagList** — List all tags on a forum channel:
|
|
414
|
+
\`\`\`
|
|
415
|
+
<discord-action>{"type":"forumTagList","channelId":"123"}</discord-action>
|
|
416
|
+
\`\`\`
|
|
417
|
+
|
|
418
|
+
**threadEdit** — Edit a forum thread's applied tags and/or name:
|
|
419
|
+
\`\`\`
|
|
420
|
+
<discord-action>{"type":"threadEdit","threadId":"789","appliedTags":["tag-id-1","tag-id-2"],"name":"New thread name"}</discord-action>
|
|
421
|
+
\`\`\`
|
|
422
|
+
- \`threadId\` (required): The thread ID (resolved via cache then fetch).
|
|
423
|
+
- \`appliedTags\` (optional): Array of tag IDs to apply. Max 5. Only valid for threads in forum channels.
|
|
424
|
+
- \`name\` (optional): New thread title.
|
|
425
|
+
At least one of appliedTags or name is required.
|
|
426
|
+
Use \`forumTagList\` to get tag IDs, then pass them here to swap status tags on orphan threads.`;
|
|
427
|
+
}
|