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,600 @@
|
|
|
1
|
+
import { CHANNEL_ACTION_TYPES, executeChannelAction, channelActionsPromptSection } from './actions-channels.js';
|
|
2
|
+
import { MESSAGING_ACTION_TYPES, executeMessagingAction, messagingActionsPromptSection } from './actions-messaging.js';
|
|
3
|
+
import { GUILD_ACTION_TYPES, executeGuildAction, guildActionsPromptSection } from './actions-guild.js';
|
|
4
|
+
import { MODERATION_ACTION_TYPES, executeModerationAction, moderationActionsPromptSection } from './actions-moderation.js';
|
|
5
|
+
import { POLL_ACTION_TYPES, executePollAction, pollActionsPromptSection } from './actions-poll.js';
|
|
6
|
+
import { executeTaskAction, TASK_ACTION_TYPES, isTaskActionRequest, taskActionsPromptSection, } from '../tasks/task-actions.js';
|
|
7
|
+
import { CRON_ACTION_TYPES, executeCronAction, cronActionsPromptSection } from './actions-crons.js';
|
|
8
|
+
import { BOT_PROFILE_ACTION_TYPES, executeBotProfileAction, botProfileActionsPromptSection } from './actions-bot-profile.js';
|
|
9
|
+
import { FORGE_ACTION_TYPES, executeForgeAction, forgeActionsPromptSection } from './actions-forge.js';
|
|
10
|
+
import { PLAN_ACTION_TYPES, executePlanAction, planActionsPromptSection } from './actions-plan.js';
|
|
11
|
+
import { MEMORY_ACTION_TYPES, executeMemoryAction, memoryActionsPromptSection } from './actions-memory.js';
|
|
12
|
+
import { DEFER_ACTION_TYPES, executeDeferAction } from './actions-defer.js';
|
|
13
|
+
import { CONFIG_ACTION_TYPES, executeConfigAction, configActionsPromptSection } from './actions-config.js';
|
|
14
|
+
import { executeReactionPromptAction as executeReactionPrompt, REACTION_PROMPT_ACTION_TYPES, reactionPromptSection } from './reaction-prompts.js';
|
|
15
|
+
import { describeDestructiveConfirmationRequirement } from './destructive-confirmation.js';
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Valid types (union of all sub-module type sets)
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
function buildValidTypes(flags) {
|
|
20
|
+
const types = new Set();
|
|
21
|
+
if (flags.channels)
|
|
22
|
+
for (const t of CHANNEL_ACTION_TYPES)
|
|
23
|
+
types.add(t);
|
|
24
|
+
if (flags.messaging)
|
|
25
|
+
for (const t of MESSAGING_ACTION_TYPES)
|
|
26
|
+
types.add(t);
|
|
27
|
+
if (flags.messaging)
|
|
28
|
+
for (const t of REACTION_PROMPT_ACTION_TYPES)
|
|
29
|
+
types.add(t);
|
|
30
|
+
if (flags.guild)
|
|
31
|
+
for (const t of GUILD_ACTION_TYPES)
|
|
32
|
+
types.add(t);
|
|
33
|
+
if (flags.moderation)
|
|
34
|
+
for (const t of MODERATION_ACTION_TYPES)
|
|
35
|
+
types.add(t);
|
|
36
|
+
if (flags.polls)
|
|
37
|
+
for (const t of POLL_ACTION_TYPES)
|
|
38
|
+
types.add(t);
|
|
39
|
+
if (flags.tasks)
|
|
40
|
+
for (const t of TASK_ACTION_TYPES)
|
|
41
|
+
types.add(t);
|
|
42
|
+
if (flags.crons)
|
|
43
|
+
for (const t of CRON_ACTION_TYPES)
|
|
44
|
+
types.add(t);
|
|
45
|
+
if (flags.botProfile)
|
|
46
|
+
for (const t of BOT_PROFILE_ACTION_TYPES)
|
|
47
|
+
types.add(t);
|
|
48
|
+
if (flags.forge)
|
|
49
|
+
for (const t of FORGE_ACTION_TYPES)
|
|
50
|
+
types.add(t);
|
|
51
|
+
if (flags.plan)
|
|
52
|
+
for (const t of PLAN_ACTION_TYPES)
|
|
53
|
+
types.add(t);
|
|
54
|
+
if (flags.memory)
|
|
55
|
+
for (const t of MEMORY_ACTION_TYPES)
|
|
56
|
+
types.add(t);
|
|
57
|
+
if (flags.defer)
|
|
58
|
+
for (const t of DEFER_ACTION_TYPES)
|
|
59
|
+
types.add(t);
|
|
60
|
+
if (flags.config)
|
|
61
|
+
for (const t of CONFIG_ACTION_TYPES)
|
|
62
|
+
types.add(t);
|
|
63
|
+
return types;
|
|
64
|
+
}
|
|
65
|
+
function rewriteLegacyPlanCloseToTaskClose(parsed, flags) {
|
|
66
|
+
if (parsed.type !== 'planClose')
|
|
67
|
+
return null;
|
|
68
|
+
if (flags.plan || !flags.tasks)
|
|
69
|
+
return null;
|
|
70
|
+
if (typeof parsed.planId !== 'string')
|
|
71
|
+
return null;
|
|
72
|
+
const id = parsed.planId.trim();
|
|
73
|
+
if (!id)
|
|
74
|
+
return null;
|
|
75
|
+
// Keep true plan IDs untouched; only recover task-like IDs.
|
|
76
|
+
if (/^plan-\d+$/i.test(id))
|
|
77
|
+
return null;
|
|
78
|
+
return { type: 'taskClose', taskId: id };
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Parser
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
const ACTION_RE = /<discord-action>([\s\S]*?)<\/discord-action>/g;
|
|
84
|
+
const ACTION_OPEN = '<discord-action>';
|
|
85
|
+
const ACTION_CLOSE = '</discord-action>';
|
|
86
|
+
// Trailing XML closing tags left by garbled AI output (e.g. </parameter>\n</invoke>).
|
|
87
|
+
const TRAILING_XML_RE = /^(?:\s*<\/[a-z-]+>)+/;
|
|
88
|
+
function mergeRanges(ranges) {
|
|
89
|
+
if (ranges.length <= 1)
|
|
90
|
+
return ranges;
|
|
91
|
+
const sorted = [...ranges].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
92
|
+
const merged = [sorted[0]];
|
|
93
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
94
|
+
const prev = merged[merged.length - 1];
|
|
95
|
+
const cur = sorted[i];
|
|
96
|
+
if (cur.start <= prev.end) {
|
|
97
|
+
prev.end = Math.max(prev.end, cur.end);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
merged.push({ start: cur.start, end: cur.end });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return merged;
|
|
104
|
+
}
|
|
105
|
+
function isIndexInRanges(index, ranges) {
|
|
106
|
+
for (const range of ranges) {
|
|
107
|
+
if (index < range.start)
|
|
108
|
+
return false;
|
|
109
|
+
if (index < range.end)
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
function computeMarkdownCodeRanges(text) {
|
|
115
|
+
const ranges = [];
|
|
116
|
+
// 1) Fenced code blocks (``` and ~~~)
|
|
117
|
+
let inFence = false;
|
|
118
|
+
let fenceChar = '';
|
|
119
|
+
let fenceLen = 0;
|
|
120
|
+
let fenceStart = 0;
|
|
121
|
+
let lineStart = 0;
|
|
122
|
+
while (lineStart <= text.length) {
|
|
123
|
+
const nl = text.indexOf('\n', lineStart);
|
|
124
|
+
const hasNl = nl !== -1;
|
|
125
|
+
const lineEnd = hasNl ? nl : text.length;
|
|
126
|
+
const lineEndWithNl = hasNl ? nl + 1 : text.length;
|
|
127
|
+
const line = text.slice(lineStart, lineEnd);
|
|
128
|
+
if (!inFence) {
|
|
129
|
+
const open = line.match(/^[ \t]*(`{3,}|~{3,})/);
|
|
130
|
+
if (open) {
|
|
131
|
+
inFence = true;
|
|
132
|
+
fenceChar = open[1][0];
|
|
133
|
+
fenceLen = open[1].length;
|
|
134
|
+
fenceStart = lineStart;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const closeRe = new RegExp(`^[ \\t]*\\${fenceChar}{${fenceLen},}[ \\t]*$`);
|
|
139
|
+
if (closeRe.test(line)) {
|
|
140
|
+
ranges.push({ start: fenceStart, end: lineEndWithNl });
|
|
141
|
+
inFence = false;
|
|
142
|
+
fenceChar = '';
|
|
143
|
+
fenceLen = 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!hasNl)
|
|
147
|
+
break;
|
|
148
|
+
lineStart = lineEndWithNl;
|
|
149
|
+
}
|
|
150
|
+
if (inFence) {
|
|
151
|
+
ranges.push({ start: fenceStart, end: text.length });
|
|
152
|
+
}
|
|
153
|
+
// 2) Indented code blocks outside fenced blocks.
|
|
154
|
+
const mergedFence = mergeRanges(ranges);
|
|
155
|
+
let segStart = 0;
|
|
156
|
+
for (const fence of mergedFence) {
|
|
157
|
+
if (segStart < fence.start) {
|
|
158
|
+
collectIndentedCodeRanges(text, segStart, fence.start, ranges);
|
|
159
|
+
}
|
|
160
|
+
segStart = fence.end;
|
|
161
|
+
}
|
|
162
|
+
if (segStart < text.length) {
|
|
163
|
+
collectIndentedCodeRanges(text, segStart, text.length, ranges);
|
|
164
|
+
}
|
|
165
|
+
// 3) Inline code spans (`...`) outside fenced/indented code blocks.
|
|
166
|
+
const mergedBlock = mergeRanges(ranges);
|
|
167
|
+
segStart = 0;
|
|
168
|
+
for (const block of mergedBlock) {
|
|
169
|
+
if (segStart < block.start) {
|
|
170
|
+
collectInlineCodeRanges(text, segStart, block.start, ranges);
|
|
171
|
+
}
|
|
172
|
+
segStart = block.end;
|
|
173
|
+
}
|
|
174
|
+
if (segStart < text.length) {
|
|
175
|
+
collectInlineCodeRanges(text, segStart, text.length, ranges);
|
|
176
|
+
}
|
|
177
|
+
return mergeRanges(ranges);
|
|
178
|
+
}
|
|
179
|
+
function collectIndentedCodeRanges(text, start, end, out) {
|
|
180
|
+
let lineStart = start;
|
|
181
|
+
let blockStart = -1;
|
|
182
|
+
let blockEnd = -1;
|
|
183
|
+
while (lineStart <= end) {
|
|
184
|
+
const nl = text.indexOf('\n', lineStart);
|
|
185
|
+
const hasNl = nl !== -1 && nl < end;
|
|
186
|
+
const lineEnd = hasNl ? nl : end;
|
|
187
|
+
const lineEndWithNl = hasNl ? nl + 1 : end;
|
|
188
|
+
const line = text.slice(lineStart, lineEnd);
|
|
189
|
+
const isBlank = /^[ \t]*$/.test(line);
|
|
190
|
+
const isIndented = /^(?: {4,}|\t)/.test(line);
|
|
191
|
+
if (blockStart === -1) {
|
|
192
|
+
if (isIndented && !isBlank) {
|
|
193
|
+
blockStart = lineStart;
|
|
194
|
+
blockEnd = lineEndWithNl;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (isIndented || isBlank) {
|
|
198
|
+
if (isIndented)
|
|
199
|
+
blockEnd = lineEndWithNl;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
out.push({ start: blockStart, end: blockEnd });
|
|
203
|
+
blockStart = -1;
|
|
204
|
+
blockEnd = -1;
|
|
205
|
+
}
|
|
206
|
+
if (!hasNl)
|
|
207
|
+
break;
|
|
208
|
+
lineStart = lineEndWithNl;
|
|
209
|
+
}
|
|
210
|
+
if (blockStart !== -1) {
|
|
211
|
+
out.push({ start: blockStart, end: blockEnd });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function collectInlineCodeRanges(text, start, end, out) {
|
|
215
|
+
let i = start;
|
|
216
|
+
let inInline = false;
|
|
217
|
+
let inlineTicks = 0;
|
|
218
|
+
let inlineStart = -1;
|
|
219
|
+
while (i < end) {
|
|
220
|
+
if (text[i] !== '`') {
|
|
221
|
+
i++;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
let ticks = 1;
|
|
225
|
+
while (i + ticks < end && text[i + ticks] === '`')
|
|
226
|
+
ticks++;
|
|
227
|
+
if (!inInline) {
|
|
228
|
+
inInline = true;
|
|
229
|
+
inlineTicks = ticks;
|
|
230
|
+
inlineStart = i;
|
|
231
|
+
}
|
|
232
|
+
else if (ticks === inlineTicks) {
|
|
233
|
+
out.push({ start: inlineStart, end: i + ticks });
|
|
234
|
+
inInline = false;
|
|
235
|
+
inlineTicks = 0;
|
|
236
|
+
inlineStart = -1;
|
|
237
|
+
}
|
|
238
|
+
i += ticks;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function findNextActionOpenOutsideCode(text, from, codeRanges) {
|
|
242
|
+
let idx = text.indexOf(ACTION_OPEN, from);
|
|
243
|
+
while (idx !== -1) {
|
|
244
|
+
if (!isIndexInRanges(idx, codeRanges))
|
|
245
|
+
return idx;
|
|
246
|
+
idx = text.indexOf(ACTION_OPEN, idx + ACTION_OPEN.length);
|
|
247
|
+
}
|
|
248
|
+
return -1;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Extract a JSON object starting at `text[start]` (which must be '{') by
|
|
252
|
+
* counting brace depth, respecting string literals. Returns the substring
|
|
253
|
+
* including the outer braces, or null if braces never balance.
|
|
254
|
+
*/
|
|
255
|
+
function extractJsonObject(text, start) {
|
|
256
|
+
let depth = 0;
|
|
257
|
+
let inString = false;
|
|
258
|
+
let escaped = false;
|
|
259
|
+
for (let i = start; i < text.length; i++) {
|
|
260
|
+
const ch = text[i];
|
|
261
|
+
if (escaped) {
|
|
262
|
+
escaped = false;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (ch === '\\' && inString) {
|
|
266
|
+
escaped = true;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (ch === '"') {
|
|
270
|
+
inString = !inString;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (inString)
|
|
274
|
+
continue;
|
|
275
|
+
if (ch === '{')
|
|
276
|
+
depth++;
|
|
277
|
+
else if (ch === '}') {
|
|
278
|
+
depth--;
|
|
279
|
+
if (depth === 0)
|
|
280
|
+
return text.slice(start, i + 1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Primary scanner for `<discord-action>` blocks.
|
|
287
|
+
* Handles well-formed blocks and malformed variants where the closing tag is
|
|
288
|
+
* wrong/missing (e.g. `</parameter>\n</invoke>`), using brace counting for the
|
|
289
|
+
* JSON payload and then consuming trailing XML-like tags.
|
|
290
|
+
*/
|
|
291
|
+
function collectParsedAction(parsed, flags, validTypes, actions, strippedUnrecognizedTypes) {
|
|
292
|
+
if (!parsed || typeof parsed !== 'object')
|
|
293
|
+
return;
|
|
294
|
+
const rewritten = rewriteLegacyPlanCloseToTaskClose(parsed, flags);
|
|
295
|
+
if (rewritten) {
|
|
296
|
+
actions.push(rewritten);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (typeof parsed.type !== 'string')
|
|
300
|
+
return;
|
|
301
|
+
const type = parsed.type;
|
|
302
|
+
if (validTypes.has(type)) {
|
|
303
|
+
actions.push(parsed);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
strippedUnrecognizedTypes.push(type);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function parseActionJson(jsonStr, flags, validTypes, actions, strippedUnrecognizedTypes) {
|
|
310
|
+
try {
|
|
311
|
+
const parsed = JSON.parse(jsonStr);
|
|
312
|
+
collectParsedAction(parsed, flags, validTypes, actions, strippedUnrecognizedTypes);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Malformed JSON — skip silently.
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function stripActionsWithScanner(text, flags, validTypes, actions, strippedUnrecognizedTypes, codeRanges) {
|
|
319
|
+
let result = '';
|
|
320
|
+
let cursor = 0;
|
|
321
|
+
while (cursor < text.length) {
|
|
322
|
+
const idx = findNextActionOpenOutsideCode(text, cursor, codeRanges);
|
|
323
|
+
if (idx === -1) {
|
|
324
|
+
result += text.slice(cursor);
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
// Copy text before the marker.
|
|
328
|
+
result += text.slice(cursor, idx);
|
|
329
|
+
// Find the opening brace after the marker.
|
|
330
|
+
let afterMarker = idx + ACTION_OPEN.length;
|
|
331
|
+
// Skip whitespace between marker and brace.
|
|
332
|
+
while (afterMarker < text.length && /\s/.test(text[afterMarker]))
|
|
333
|
+
afterMarker++;
|
|
334
|
+
if (afterMarker >= text.length || text[afterMarker] !== '{') {
|
|
335
|
+
// No JSON object follows — keep the marker text as-is and move on.
|
|
336
|
+
result += ACTION_OPEN;
|
|
337
|
+
cursor = idx + ACTION_OPEN.length;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const jsonStr = extractJsonObject(text, afterMarker);
|
|
341
|
+
if (!jsonStr) {
|
|
342
|
+
// Unbalanced braces — strip this line, then consume any trailing XML closing tags.
|
|
343
|
+
const nl = text.indexOf('\n', afterMarker);
|
|
344
|
+
cursor = nl === -1 ? text.length : nl;
|
|
345
|
+
const trailing = text.slice(cursor).match(TRAILING_XML_RE);
|
|
346
|
+
if (trailing)
|
|
347
|
+
cursor += trailing[0].length;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
parseActionJson(jsonStr, flags, validTypes, actions, strippedUnrecognizedTypes);
|
|
351
|
+
// Advance past the JSON object and consume any trailing XML closing tags.
|
|
352
|
+
cursor = afterMarker + jsonStr.length;
|
|
353
|
+
const remaining = text.slice(cursor);
|
|
354
|
+
if (remaining.startsWith(ACTION_CLOSE)) {
|
|
355
|
+
cursor += ACTION_CLOSE.length;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const trailing = remaining.match(TRAILING_XML_RE);
|
|
359
|
+
if (trailing)
|
|
360
|
+
cursor += trailing[0].length;
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
function parseWithRegexFallback(text, flags, validTypes, codeRanges) {
|
|
365
|
+
const actions = [];
|
|
366
|
+
const strippedUnrecognizedTypes = [];
|
|
367
|
+
const cleaned = text.replace(ACTION_RE, (match, json, offset) => {
|
|
368
|
+
if (isIndexInRanges(offset, codeRanges))
|
|
369
|
+
return match;
|
|
370
|
+
parseActionJson(json.trim(), flags, validTypes, actions, strippedUnrecognizedTypes);
|
|
371
|
+
return '';
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
cleanText: cleaned.replace(/\n{3,}/g, '\n\n').trim(),
|
|
375
|
+
actions,
|
|
376
|
+
strippedUnrecognizedTypes,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
export function parseDiscordActions(text, flags) {
|
|
380
|
+
const validTypes = buildValidTypes(flags);
|
|
381
|
+
const actions = [];
|
|
382
|
+
const strippedUnrecognizedTypes = [];
|
|
383
|
+
const codeRanges = computeMarkdownCodeRanges(text);
|
|
384
|
+
const cleaned = stripActionsWithScanner(text, flags, validTypes, actions, strippedUnrecognizedTypes, codeRanges);
|
|
385
|
+
const scanned = {
|
|
386
|
+
cleanText: cleaned.replace(/\n{3,}/g, '\n\n').trim(),
|
|
387
|
+
actions,
|
|
388
|
+
strippedUnrecognizedTypes,
|
|
389
|
+
};
|
|
390
|
+
// Compatibility fallback: if scanner leaves markers behind or extracts nothing,
|
|
391
|
+
// run the legacy regex parser and prefer it when it captures more actions.
|
|
392
|
+
const hasActionOutsideCode = findNextActionOpenOutsideCode(text, 0, codeRanges) !== -1;
|
|
393
|
+
if (hasActionOutsideCode) {
|
|
394
|
+
const markerLeft = scanned.cleanText.includes(ACTION_OPEN) || scanned.cleanText.includes(ACTION_CLOSE);
|
|
395
|
+
if (markerLeft || scanned.actions.length === 0) {
|
|
396
|
+
const legacy = parseWithRegexFallback(text, flags, validTypes, codeRanges);
|
|
397
|
+
if (legacy.actions.length > scanned.actions.length)
|
|
398
|
+
return legacy;
|
|
399
|
+
if (markerLeft && legacy.actions.length === scanned.actions.length && legacy.cleanText.length < scanned.cleanText.length) {
|
|
400
|
+
return legacy;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return scanned;
|
|
405
|
+
}
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
// Executor (dispatcher)
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
export async function executeDiscordActions(actions, ctx, log, subs) {
|
|
410
|
+
const effectiveSubs = subs ?? {};
|
|
411
|
+
const results = [];
|
|
412
|
+
for (const action of actions) {
|
|
413
|
+
try {
|
|
414
|
+
let result;
|
|
415
|
+
const destructiveCheck = describeDestructiveConfirmationRequirement(action, ctx.confirmation);
|
|
416
|
+
if (!destructiveCheck.allow) {
|
|
417
|
+
result = { ok: false, error: destructiveCheck.error };
|
|
418
|
+
results.push(result);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (CHANNEL_ACTION_TYPES.has(action.type)) {
|
|
422
|
+
result = await executeChannelAction(action, ctx);
|
|
423
|
+
}
|
|
424
|
+
else if (MESSAGING_ACTION_TYPES.has(action.type)) {
|
|
425
|
+
result = await executeMessagingAction(action, ctx);
|
|
426
|
+
}
|
|
427
|
+
else if (REACTION_PROMPT_ACTION_TYPES.has(action.type)) {
|
|
428
|
+
result = await executeReactionPrompt(action, ctx);
|
|
429
|
+
}
|
|
430
|
+
else if (GUILD_ACTION_TYPES.has(action.type)) {
|
|
431
|
+
result = await executeGuildAction(action, ctx);
|
|
432
|
+
}
|
|
433
|
+
else if (MODERATION_ACTION_TYPES.has(action.type)) {
|
|
434
|
+
result = await executeModerationAction(action, ctx);
|
|
435
|
+
}
|
|
436
|
+
else if (POLL_ACTION_TYPES.has(action.type)) {
|
|
437
|
+
result = await executePollAction(action, ctx);
|
|
438
|
+
}
|
|
439
|
+
else if (isTaskActionRequest(action)) {
|
|
440
|
+
const taskCtx = effectiveSubs.taskCtx;
|
|
441
|
+
if (!taskCtx) {
|
|
442
|
+
result = { ok: false, error: 'Tasks subsystem not configured' };
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
result = await executeTaskAction(action, ctx, taskCtx);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else if (CRON_ACTION_TYPES.has(action.type)) {
|
|
449
|
+
if (!effectiveSubs.cronCtx) {
|
|
450
|
+
result = { ok: false, error: 'Cron subsystem not configured' };
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
result = await executeCronAction(action, ctx, effectiveSubs.cronCtx);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else if (BOT_PROFILE_ACTION_TYPES.has(action.type)) {
|
|
457
|
+
result = await executeBotProfileAction(action, ctx);
|
|
458
|
+
}
|
|
459
|
+
else if (FORGE_ACTION_TYPES.has(action.type)) {
|
|
460
|
+
if (!effectiveSubs.forgeCtx) {
|
|
461
|
+
result = { ok: false, error: 'Forge subsystem not configured' };
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
result = await executeForgeAction(action, ctx, effectiveSubs.forgeCtx);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
else if (PLAN_ACTION_TYPES.has(action.type)) {
|
|
468
|
+
if (!effectiveSubs.planCtx) {
|
|
469
|
+
result = { ok: false, error: 'Plan subsystem not configured' };
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
result = await executePlanAction(action, ctx, effectiveSubs.planCtx);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else if (MEMORY_ACTION_TYPES.has(action.type)) {
|
|
476
|
+
if (!effectiveSubs.memoryCtx) {
|
|
477
|
+
result = { ok: false, error: 'Memory subsystem not configured' };
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
result = await executeMemoryAction(action, ctx, effectiveSubs.memoryCtx);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
else if (DEFER_ACTION_TYPES.has(action.type)) {
|
|
484
|
+
result = await executeDeferAction(action, ctx);
|
|
485
|
+
}
|
|
486
|
+
else if (CONFIG_ACTION_TYPES.has(action.type)) {
|
|
487
|
+
if (!effectiveSubs.configCtx) {
|
|
488
|
+
result = { ok: false, error: 'Config subsystem not configured' };
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
result = executeConfigAction(action, effectiveSubs.configCtx);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
result = { ok: false, error: `Unknown action type: ${action.type ?? 'unknown'}` };
|
|
496
|
+
}
|
|
497
|
+
results.push(result);
|
|
498
|
+
if (result.ok) {
|
|
499
|
+
log?.info({ action: action.type, summary: result.summary }, `discord:action ${action.type}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
catch (err) {
|
|
503
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
504
|
+
results.push({ ok: false, error: msg });
|
|
505
|
+
log?.error({ err, action }, 'discord:action failed');
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return results;
|
|
509
|
+
}
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
// Result-line helpers
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
/**
|
|
514
|
+
* Build result lines for display in Discord (posted message).
|
|
515
|
+
* Suppresses successful sendMessage results since the sent message
|
|
516
|
+
* is its own confirmation.
|
|
517
|
+
*/
|
|
518
|
+
export function buildDisplayResultLines(actions, results) {
|
|
519
|
+
return results
|
|
520
|
+
.map((r, i) => {
|
|
521
|
+
if (r.ok && (actions[i]?.type === 'sendMessage' || actions[i]?.type === 'sendFile'))
|
|
522
|
+
return null;
|
|
523
|
+
return r.ok ? `Done: ${r.summary}` : `Failed: ${r.error}`;
|
|
524
|
+
})
|
|
525
|
+
.filter((line) => line !== null);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Build result lines for follow-up prompts (AI sees all results).
|
|
529
|
+
*/
|
|
530
|
+
export function buildAllResultLines(results) {
|
|
531
|
+
return results.map((r) => r.ok ? `Done: ${r.summary}` : `Failed: ${r.error}`);
|
|
532
|
+
}
|
|
533
|
+
// ---------------------------------------------------------------------------
|
|
534
|
+
// Prompt section
|
|
535
|
+
// ---------------------------------------------------------------------------
|
|
536
|
+
export function discordActionsPromptSection(flags, botDisplayName) {
|
|
537
|
+
const displayName = botDisplayName ?? 'Discoclaw';
|
|
538
|
+
const sections = [];
|
|
539
|
+
sections.push(`## Discord Actions
|
|
540
|
+
|
|
541
|
+
Setting DISCOCLAW_DISCORD_ACTIONS=1 publishes this standard guidance (even if only a subset of sub-categories are available). You can perform Discord server actions by including structured action blocks in your response.`);
|
|
542
|
+
if (flags.messaging) {
|
|
543
|
+
sections.push(messagingActionsPromptSection());
|
|
544
|
+
sections.push(reactionPromptSection());
|
|
545
|
+
}
|
|
546
|
+
if (flags.channels) {
|
|
547
|
+
sections.push(channelActionsPromptSection());
|
|
548
|
+
}
|
|
549
|
+
if (flags.guild) {
|
|
550
|
+
sections.push(guildActionsPromptSection());
|
|
551
|
+
}
|
|
552
|
+
if (flags.moderation) {
|
|
553
|
+
sections.push(moderationActionsPromptSection());
|
|
554
|
+
}
|
|
555
|
+
if (flags.polls) {
|
|
556
|
+
sections.push(pollActionsPromptSection());
|
|
557
|
+
}
|
|
558
|
+
if (flags.tasks) {
|
|
559
|
+
sections.push(taskActionsPromptSection());
|
|
560
|
+
}
|
|
561
|
+
if (flags.crons) {
|
|
562
|
+
sections.push(cronActionsPromptSection());
|
|
563
|
+
}
|
|
564
|
+
if (flags.botProfile) {
|
|
565
|
+
sections.push(botProfileActionsPromptSection());
|
|
566
|
+
}
|
|
567
|
+
if (flags.forge) {
|
|
568
|
+
sections.push(forgeActionsPromptSection());
|
|
569
|
+
}
|
|
570
|
+
if (flags.plan) {
|
|
571
|
+
sections.push(planActionsPromptSection());
|
|
572
|
+
}
|
|
573
|
+
if (flags.memory) {
|
|
574
|
+
sections.push(memoryActionsPromptSection());
|
|
575
|
+
}
|
|
576
|
+
if (flags.config) {
|
|
577
|
+
sections.push(configActionsPromptSection());
|
|
578
|
+
}
|
|
579
|
+
sections.push(`### Rules
|
|
580
|
+
- Only the action types listed above are supported.
|
|
581
|
+
- Never emit an action with empty, placeholder, or missing values for required parameters. If you don't have the value (e.g., no messageId for react), skip the action entirely.
|
|
582
|
+
- Confirm with the user before performing destructive actions (delete, kick, ban, timeout).
|
|
583
|
+
- Action blocks are removed from the displayed message; results are appended automatically.
|
|
584
|
+
- Results from information-gathering actions (channelList, channelInfo, threadListArchived, forumTagList, readMessages, fetchMessage, listPins, memberInfo, roleInfo, searchMessages, eventList, taskList, taskShow, cronList, cronShow, planList, planShow, memoryShow, modelShow) are automatically sent back to you for further analysis. You can emit a query action and continue reasoning in the follow-up.
|
|
585
|
+
- Include all needed actions in a single response when possible (e.g., a channelList and multiple channelDelete blocks together).
|
|
586
|
+
|
|
587
|
+
### Permissions
|
|
588
|
+
These actions require the bot to have appropriate permissions in this Discord server (e.g. Manage Channels, Manage Roles, Moderate Members). These are server-level role permissions, not Discord Developer Portal settings.
|
|
589
|
+
|
|
590
|
+
If an action fails with a "Missing Permissions" or "Missing Access" error, tell the user:
|
|
591
|
+
1. Open **Server Settings → Roles**.
|
|
592
|
+
2. Find the ${displayName} bot's role (usually named after the bot).
|
|
593
|
+
3. Enable the required permission under the role's permissions.
|
|
594
|
+
4. The bot may need to be re-invited with the "moderator" permission profile if the role wasn't granted at invite time.`);
|
|
595
|
+
if (flags.defer) {
|
|
596
|
+
sections.push(`### Deferred self-invocation
|
|
597
|
+
Use a <discord-action>{"type":"defer","channel":"general","delaySeconds":600,"prompt":"Check on the forge run"}</discord-action> block to schedule a follow-up run inside the requested channel without another user prompt. You must specify the channel by name or ID; delaySeconds is how long to wait (capped by DISCOCLAW_DISCORD_ACTIONS_DEFER_MAX_DELAY_SECONDS) and prompt becomes the user message when the deferred invocation runs. The scheduler enforces DISCOCLAW_DISCORD_ACTIONS_DEFER_MAX_CONCURRENT pending jobs, respects the same channel permissions as this response, automatically posts the follow-up output, and forces \`defer\` off during that run so no chains can form. If a guard rail rejects the request (too long, too many active defers, missing permissions, or the channel becomes invalid) the action fails with an explanatory message.`);
|
|
598
|
+
}
|
|
599
|
+
return sections.join('\n\n');
|
|
600
|
+
}
|