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
package/dist/config.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { parseAllowChannelIds, parseAllowUserIds } from './discord/allowlist.js';
|
|
2
|
+
export const KNOWN_TOOLS = new Set(['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'WebSearch', 'WebFetch']);
|
|
3
|
+
export const DEFAULT_DISCORD_ACTIONS_DEFER_MAX_DELAY_SECONDS = 1800;
|
|
4
|
+
export const DEFAULT_DISCORD_ACTIONS_DEFER_MAX_CONCURRENT = 5;
|
|
5
|
+
function parseBoolean(env, name, defaultValue) {
|
|
6
|
+
const raw = env[name];
|
|
7
|
+
if (raw == null || raw.trim() === '')
|
|
8
|
+
return defaultValue;
|
|
9
|
+
const normalized = raw.trim().toLowerCase();
|
|
10
|
+
if (normalized === '1' || normalized === 'true')
|
|
11
|
+
return true;
|
|
12
|
+
if (normalized === '0' || normalized === 'false')
|
|
13
|
+
return false;
|
|
14
|
+
throw new Error(`${name} must be "0"/"1" or "true"/"false", got "${raw}"`);
|
|
15
|
+
}
|
|
16
|
+
function parseNonNegativeNumber(env, name, defaultValue) {
|
|
17
|
+
const raw = env[name];
|
|
18
|
+
if (raw == null || raw.trim() === '')
|
|
19
|
+
return defaultValue;
|
|
20
|
+
const n = Number(raw);
|
|
21
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
22
|
+
throw new Error(`${name} must be a non-negative number, got "${raw}"`);
|
|
23
|
+
}
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
26
|
+
function parsePositiveNumber(env, name, defaultValue) {
|
|
27
|
+
const raw = env[name];
|
|
28
|
+
if (raw == null || raw.trim() === '')
|
|
29
|
+
return defaultValue;
|
|
30
|
+
const n = Number(raw);
|
|
31
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
32
|
+
throw new Error(`${name} must be a positive number, got "${raw}"`);
|
|
33
|
+
}
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
36
|
+
const DEFAULT_THIRTY_MINUTES_MS = 1_800_000;
|
|
37
|
+
function parseNonNegativeInt(env, name, defaultValue) {
|
|
38
|
+
const n = parseNonNegativeNumber(env, name, defaultValue);
|
|
39
|
+
if (!Number.isInteger(n)) {
|
|
40
|
+
throw new Error(`${name} must be an integer, got "${n}"`);
|
|
41
|
+
}
|
|
42
|
+
return n;
|
|
43
|
+
}
|
|
44
|
+
function parsePositiveInt(env, name, defaultValue) {
|
|
45
|
+
const n = parsePositiveNumber(env, name, defaultValue);
|
|
46
|
+
if (!Number.isInteger(n)) {
|
|
47
|
+
throw new Error(`${name} must be an integer, got "${n}"`);
|
|
48
|
+
}
|
|
49
|
+
return n;
|
|
50
|
+
}
|
|
51
|
+
function parseTrimmedString(env, name) {
|
|
52
|
+
const raw = env[name];
|
|
53
|
+
if (raw == null)
|
|
54
|
+
return undefined;
|
|
55
|
+
const trimmed = raw.trim();
|
|
56
|
+
return trimmed || undefined;
|
|
57
|
+
}
|
|
58
|
+
function parseRuntimeName(env, name) {
|
|
59
|
+
const raw = parseTrimmedString(env, name);
|
|
60
|
+
if (!raw)
|
|
61
|
+
return undefined;
|
|
62
|
+
const normalized = raw.toLowerCase();
|
|
63
|
+
if (normalized === 'claude_code')
|
|
64
|
+
return 'claude';
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
function parseEnum(env, name, validValues, defaultValue) {
|
|
68
|
+
const raw = env[name];
|
|
69
|
+
if (raw == null || raw.trim() === '')
|
|
70
|
+
return defaultValue;
|
|
71
|
+
const normalized = raw.trim().toLowerCase();
|
|
72
|
+
const match = validValues.find((v) => v.toLowerCase() === normalized);
|
|
73
|
+
if (!match) {
|
|
74
|
+
throw new Error(`${name} must be one of ${validValues.join('|')}, got "${raw}"`);
|
|
75
|
+
}
|
|
76
|
+
return match;
|
|
77
|
+
}
|
|
78
|
+
function parseAvatarPath(env, name) {
|
|
79
|
+
const val = parseTrimmedString(env, name);
|
|
80
|
+
if (val && !val.startsWith('http://') && !val.startsWith('https://') && !val.startsWith('/')) {
|
|
81
|
+
throw new Error(`${name} must be an absolute file path or URL`);
|
|
82
|
+
}
|
|
83
|
+
return val;
|
|
84
|
+
}
|
|
85
|
+
function parseRuntimeTools(env, warnings) {
|
|
86
|
+
const raw = parseTrimmedString(env, 'RUNTIME_TOOLS');
|
|
87
|
+
if (!raw)
|
|
88
|
+
return ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'WebSearch', 'WebFetch'];
|
|
89
|
+
const tools = raw
|
|
90
|
+
.split(/[,\s]+/g)
|
|
91
|
+
.map((t) => t.trim())
|
|
92
|
+
.filter(Boolean);
|
|
93
|
+
if (tools.length === 0) {
|
|
94
|
+
throw new Error('RUNTIME_TOOLS was set but no tools were parsed');
|
|
95
|
+
}
|
|
96
|
+
const unknown = tools.filter((t) => !KNOWN_TOOLS.has(t));
|
|
97
|
+
if (unknown.length > 0) {
|
|
98
|
+
warnings.push(`RUNTIME_TOOLS includes unknown tools (${unknown.join(', ')}). ` +
|
|
99
|
+
'Passing through as configured for runtime compatibility.');
|
|
100
|
+
}
|
|
101
|
+
return tools;
|
|
102
|
+
}
|
|
103
|
+
export function parseConfig(env) {
|
|
104
|
+
const warnings = [];
|
|
105
|
+
const infos = [];
|
|
106
|
+
const token = parseTrimmedString(env, 'DISCORD_TOKEN');
|
|
107
|
+
if (!token) {
|
|
108
|
+
throw new Error('Missing DISCORD_TOKEN');
|
|
109
|
+
}
|
|
110
|
+
const allowUserIdsRaw = env.DISCORD_ALLOW_USER_IDS;
|
|
111
|
+
const allowUserIds = parseAllowUserIds(allowUserIdsRaw);
|
|
112
|
+
if ((allowUserIdsRaw ?? '').trim().length > 0 && allowUserIds.size === 0) {
|
|
113
|
+
warnings.push('DISCORD_ALLOW_USER_IDS was set but no valid IDs were parsed: bot will respond to nobody (fail closed)');
|
|
114
|
+
}
|
|
115
|
+
else if (allowUserIds.size === 0) {
|
|
116
|
+
warnings.push('DISCORD_ALLOW_USER_IDS is empty: bot will respond to nobody (fail closed)');
|
|
117
|
+
}
|
|
118
|
+
const allowChannelIdsRaw = env.DISCORD_CHANNEL_IDS;
|
|
119
|
+
const restrictChannelIds = (allowChannelIdsRaw ?? '').trim().length > 0;
|
|
120
|
+
const allowChannelIds = parseAllowChannelIds(allowChannelIdsRaw);
|
|
121
|
+
if (restrictChannelIds && allowChannelIds.size === 0) {
|
|
122
|
+
warnings.push('DISCORD_CHANNEL_IDS was set but no valid IDs were parsed: bot will respond to no guild channels (fail closed)');
|
|
123
|
+
}
|
|
124
|
+
const outputFormatRaw = parseTrimmedString(env, 'CLAUDE_OUTPUT_FORMAT');
|
|
125
|
+
if (outputFormatRaw && outputFormatRaw !== 'text' && outputFormatRaw !== 'stream-json') {
|
|
126
|
+
throw new Error(`CLAUDE_OUTPUT_FORMAT must be "text" or "stream-json", got "${outputFormatRaw}"`);
|
|
127
|
+
}
|
|
128
|
+
const outputFormat = outputFormatRaw === 'stream-json' ? 'stream-json' : 'text';
|
|
129
|
+
const rawVerbose = parseBoolean(env, 'CLAUDE_VERBOSE', false);
|
|
130
|
+
const effectiveVerbose = rawVerbose && outputFormat !== 'text';
|
|
131
|
+
if (rawVerbose && !effectiveVerbose) {
|
|
132
|
+
warnings.push('CLAUDE_VERBOSE=1 ignored: incompatible with CLAUDE_OUTPUT_FORMAT=text (verbose metadata would corrupt response text). ' +
|
|
133
|
+
'Set CLAUDE_OUTPUT_FORMAT=stream-json to use verbose mode.');
|
|
134
|
+
}
|
|
135
|
+
const healthVerboseAllowlistRaw = env.DISCOCLAW_HEALTH_VERBOSE_ALLOWLIST;
|
|
136
|
+
const healthVerboseAllowlist = parseAllowUserIds(healthVerboseAllowlistRaw);
|
|
137
|
+
if ((healthVerboseAllowlistRaw ?? '').trim().length > 0 && healthVerboseAllowlist.size === 0) {
|
|
138
|
+
warnings.push('DISCOCLAW_HEALTH_VERBOSE_ALLOWLIST was set but no valid IDs were parsed; verbose health falls back to allowlisted users');
|
|
139
|
+
}
|
|
140
|
+
const discordActionsEnabled = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS', true);
|
|
141
|
+
const discordActionsChannels = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_CHANNELS', true);
|
|
142
|
+
const discordActionsMessaging = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_MESSAGING', true);
|
|
143
|
+
const discordActionsGuild = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_GUILD', true);
|
|
144
|
+
const discordActionsModeration = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_MODERATION', false);
|
|
145
|
+
const discordActionsPolls = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_POLLS', true);
|
|
146
|
+
const discordActionsTasks = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_TASKS', true);
|
|
147
|
+
const discordActionsCrons = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_CRONS', true);
|
|
148
|
+
const discordActionsBotProfile = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_BOT_PROFILE', true);
|
|
149
|
+
const discordActionsForge = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_FORGE', true);
|
|
150
|
+
const discordActionsPlan = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_PLAN', true);
|
|
151
|
+
const discordActionsMemory = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_MEMORY', true);
|
|
152
|
+
const discordActionsDefer = parseBoolean(env, 'DISCOCLAW_DISCORD_ACTIONS_DEFER', true);
|
|
153
|
+
const deferMaxDelaySeconds = parsePositiveNumber(env, 'DISCOCLAW_DISCORD_ACTIONS_DEFER_MAX_DELAY_SECONDS', DEFAULT_DISCORD_ACTIONS_DEFER_MAX_DELAY_SECONDS);
|
|
154
|
+
const deferMaxConcurrent = parsePositiveInt(env, 'DISCOCLAW_DISCORD_ACTIONS_DEFER_MAX_CONCURRENT', DEFAULT_DISCORD_ACTIONS_DEFER_MAX_CONCURRENT);
|
|
155
|
+
if (!discordActionsEnabled) {
|
|
156
|
+
const enabledCategories = [
|
|
157
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_CHANNELS', enabled: discordActionsChannels },
|
|
158
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_MESSAGING', enabled: discordActionsMessaging },
|
|
159
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_GUILD', enabled: discordActionsGuild },
|
|
160
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_MODERATION', enabled: discordActionsModeration },
|
|
161
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_POLLS', enabled: discordActionsPolls },
|
|
162
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_TASKS', enabled: discordActionsTasks },
|
|
163
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_CRONS', enabled: discordActionsCrons },
|
|
164
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_BOT_PROFILE', enabled: discordActionsBotProfile },
|
|
165
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_FORGE', enabled: discordActionsForge },
|
|
166
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_PLAN', enabled: discordActionsPlan },
|
|
167
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_MEMORY', enabled: discordActionsMemory },
|
|
168
|
+
{ name: 'DISCOCLAW_DISCORD_ACTIONS_DEFER', enabled: discordActionsDefer },
|
|
169
|
+
]
|
|
170
|
+
.filter((entry) => (env[entry.name] ?? '').trim().length > 0 && entry.enabled)
|
|
171
|
+
.map((entry) => entry.name);
|
|
172
|
+
if (enabledCategories.length > 0) {
|
|
173
|
+
infos.push(`DISCOCLAW_DISCORD_ACTIONS=0; category flags are ignored: ${enabledCategories.join(', ')}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const cronEnabled = parseBoolean(env, 'DISCOCLAW_CRON_ENABLED', true);
|
|
177
|
+
let cronForum = parseTrimmedString(env, 'DISCOCLAW_CRON_FORUM');
|
|
178
|
+
if (cronForum && !/^\d{8,}$/.test(cronForum)) {
|
|
179
|
+
warnings.push('DISCOCLAW_CRON_FORUM is not a valid snowflake; ignoring (system bootstrap will auto-create)');
|
|
180
|
+
cronForum = undefined;
|
|
181
|
+
}
|
|
182
|
+
const webhookEnabled = parseBoolean(env, 'DISCOCLAW_WEBHOOK_ENABLED', false);
|
|
183
|
+
const webhookPort = parsePositiveInt(env, 'DISCOCLAW_WEBHOOK_PORT', 9400);
|
|
184
|
+
const webhookConfigPath = parseTrimmedString(env, 'DISCOCLAW_WEBHOOK_CONFIG');
|
|
185
|
+
const tasksEnabled = parseBoolean(env, 'DISCOCLAW_TASKS_ENABLED', true);
|
|
186
|
+
let tasksForum = parseTrimmedString(env, 'DISCOCLAW_TASKS_FORUM');
|
|
187
|
+
if (tasksForum && !/^\d{8,}$/.test(tasksForum)) {
|
|
188
|
+
warnings.push('DISCOCLAW_TASKS_FORUM is not a valid snowflake; ignoring (system bootstrap will auto-create)');
|
|
189
|
+
tasksForum = undefined;
|
|
190
|
+
}
|
|
191
|
+
const primaryRuntime = parseRuntimeName(env, 'PRIMARY_RUNTIME') ?? 'claude';
|
|
192
|
+
const forgeDrafterRuntime = parseRuntimeName(env, 'FORGE_DRAFTER_RUNTIME');
|
|
193
|
+
const forgeAuditorRuntime = parseRuntimeName(env, 'FORGE_AUDITOR_RUNTIME');
|
|
194
|
+
const openaiApiKey = parseTrimmedString(env, 'OPENAI_API_KEY');
|
|
195
|
+
const openaiBaseUrl = parseTrimmedString(env, 'OPENAI_BASE_URL');
|
|
196
|
+
const openaiModel = parseTrimmedString(env, 'OPENAI_MODEL') ?? 'gpt-4o';
|
|
197
|
+
if (primaryRuntime === 'openai' && !openaiApiKey) {
|
|
198
|
+
warnings.push('PRIMARY_RUNTIME=openai but OPENAI_API_KEY is not set; startup will fail unless another runtime is selected.');
|
|
199
|
+
}
|
|
200
|
+
if (forgeDrafterRuntime === 'openai' && !openaiApiKey) {
|
|
201
|
+
warnings.push('FORGE_DRAFTER_RUNTIME=openai but OPENAI_API_KEY is not set; drafter will fall back to the primary runtime.');
|
|
202
|
+
}
|
|
203
|
+
if (forgeAuditorRuntime === 'openai' && !openaiApiKey) {
|
|
204
|
+
warnings.push('FORGE_AUDITOR_RUNTIME=openai but OPENAI_API_KEY is not set; auditor will fall back to the primary runtime.');
|
|
205
|
+
}
|
|
206
|
+
const openrouterApiKey = parseTrimmedString(env, 'OPENROUTER_API_KEY');
|
|
207
|
+
const openrouterBaseUrl = parseTrimmedString(env, 'OPENROUTER_BASE_URL');
|
|
208
|
+
const openrouterModel = parseTrimmedString(env, 'OPENROUTER_MODEL') ?? 'anthropic/claude-sonnet-4';
|
|
209
|
+
if (primaryRuntime === 'openrouter' && !openrouterApiKey) {
|
|
210
|
+
warnings.push('PRIMARY_RUNTIME=openrouter but OPENROUTER_API_KEY is not set; startup will fail unless another runtime is selected.');
|
|
211
|
+
}
|
|
212
|
+
if (forgeDrafterRuntime === 'openrouter' && !openrouterApiKey) {
|
|
213
|
+
warnings.push('FORGE_DRAFTER_RUNTIME=openrouter but OPENROUTER_API_KEY is not set; drafter will fall back to the primary runtime.');
|
|
214
|
+
}
|
|
215
|
+
if (forgeAuditorRuntime === 'openrouter' && !openrouterApiKey) {
|
|
216
|
+
warnings.push('FORGE_AUDITOR_RUNTIME=openrouter but OPENROUTER_API_KEY is not set; auditor will fall back to the primary runtime.');
|
|
217
|
+
}
|
|
218
|
+
const fastModel = parseTrimmedString(env, 'DISCOCLAW_FAST_MODEL') ?? 'fast';
|
|
219
|
+
const tasksCwdOverride = parseTrimmedString(env, 'DISCOCLAW_TASKS_CWD');
|
|
220
|
+
const tasksTagMapPathOverride = parseTrimmedString(env, 'DISCOCLAW_TASKS_TAG_MAP');
|
|
221
|
+
const tasksMentionUser = parseTrimmedString(env, 'DISCOCLAW_TASKS_MENTION_USER');
|
|
222
|
+
const tasksSidebar = parseBoolean(env, 'DISCOCLAW_TASKS_SIDEBAR', true);
|
|
223
|
+
const tasksAutoTag = parseBoolean(env, 'DISCOCLAW_TASKS_AUTO_TAG', true);
|
|
224
|
+
const tasksAutoTagModel = parseTrimmedString(env, 'DISCOCLAW_TASKS_AUTO_TAG_MODEL') ?? fastModel;
|
|
225
|
+
const tasksSyncSkipPhase5 = parseBoolean(env, 'DISCOCLAW_TASKS_SYNC_SKIP_PHASE5', false);
|
|
226
|
+
const tasksSyncFailureRetryEnabled = parseBoolean(env, 'DISCOCLAW_TASKS_SYNC_FAILURE_RETRY_ENABLED', true);
|
|
227
|
+
const tasksSyncFailureRetryDelayMs = parsePositiveInt(env, 'DISCOCLAW_TASKS_SYNC_FAILURE_RETRY_DELAY_MS', 30_000);
|
|
228
|
+
const tasksSyncDeferredRetryDelayMs = parsePositiveInt(env, 'DISCOCLAW_TASKS_SYNC_DEFERRED_RETRY_DELAY_MS', 30_000);
|
|
229
|
+
const tasksPrefix = parseTrimmedString(env, 'DISCOCLAW_TASKS_PREFIX') ?? 'ws';
|
|
230
|
+
return {
|
|
231
|
+
config: {
|
|
232
|
+
token,
|
|
233
|
+
allowUserIds,
|
|
234
|
+
allowChannelIds,
|
|
235
|
+
restrictChannelIds,
|
|
236
|
+
primaryRuntime,
|
|
237
|
+
runtimeModel: parseTrimmedString(env, 'RUNTIME_MODEL') ?? 'capable',
|
|
238
|
+
runtimeTools: parseRuntimeTools(env, warnings),
|
|
239
|
+
runtimeTimeoutMs: parsePositiveNumber(env, 'RUNTIME_TIMEOUT_MS', DEFAULT_THIRTY_MINUTES_MS),
|
|
240
|
+
runtimeFallbackModel: parseTrimmedString(env, 'RUNTIME_FALLBACK_MODEL'),
|
|
241
|
+
runtimeMaxBudgetUsd: (() => {
|
|
242
|
+
const raw = parseTrimmedString(env, 'RUNTIME_MAX_BUDGET_USD');
|
|
243
|
+
if (raw == null)
|
|
244
|
+
return undefined;
|
|
245
|
+
const n = Number(raw);
|
|
246
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
247
|
+
throw new Error(`RUNTIME_MAX_BUDGET_USD must be a positive number, got "${raw}"`);
|
|
248
|
+
}
|
|
249
|
+
return n;
|
|
250
|
+
})(),
|
|
251
|
+
appendSystemPrompt: (() => {
|
|
252
|
+
const raw = parseTrimmedString(env, 'CLAUDE_APPEND_SYSTEM_PROMPT');
|
|
253
|
+
if (raw == null)
|
|
254
|
+
return undefined;
|
|
255
|
+
if (raw.length > 4000) {
|
|
256
|
+
throw new Error(`CLAUDE_APPEND_SYSTEM_PROMPT exceeds 4000 char limit (got ${raw.length})`);
|
|
257
|
+
}
|
|
258
|
+
return raw;
|
|
259
|
+
})(),
|
|
260
|
+
dataDir: parseTrimmedString(env, 'DISCOCLAW_DATA_DIR'),
|
|
261
|
+
contentDirOverride: parseTrimmedString(env, 'DISCOCLAW_CONTENT_DIR'),
|
|
262
|
+
requireChannelContext: parseBoolean(env, 'DISCORD_REQUIRE_CHANNEL_CONTEXT', true),
|
|
263
|
+
autoIndexChannelContext: parseBoolean(env, 'DISCORD_AUTO_INDEX_CHANNEL_CONTEXT', true),
|
|
264
|
+
autoJoinThreads: parseBoolean(env, 'DISCORD_AUTO_JOIN_THREADS', true),
|
|
265
|
+
useRuntimeSessions: parseBoolean(env, 'DISCOCLAW_RUNTIME_SESSIONS', true),
|
|
266
|
+
discordActionsEnabled,
|
|
267
|
+
discordActionsChannels,
|
|
268
|
+
discordActionsMessaging,
|
|
269
|
+
discordActionsGuild,
|
|
270
|
+
discordActionsModeration,
|
|
271
|
+
discordActionsPolls,
|
|
272
|
+
discordActionsTasks,
|
|
273
|
+
discordActionsCrons,
|
|
274
|
+
discordActionsBotProfile,
|
|
275
|
+
discordActionsForge,
|
|
276
|
+
discordActionsPlan,
|
|
277
|
+
discordActionsMemory,
|
|
278
|
+
discordActionsDefer,
|
|
279
|
+
deferMaxDelaySeconds,
|
|
280
|
+
deferMaxConcurrent,
|
|
281
|
+
messageHistoryBudget: parseNonNegativeInt(env, 'DISCOCLAW_MESSAGE_HISTORY_BUDGET', 3000),
|
|
282
|
+
summaryEnabled: parseBoolean(env, 'DISCOCLAW_SUMMARY_ENABLED', true),
|
|
283
|
+
summaryModel: parseTrimmedString(env, 'DISCOCLAW_SUMMARY_MODEL') ?? fastModel,
|
|
284
|
+
summaryMaxChars: parseNonNegativeInt(env, 'DISCOCLAW_SUMMARY_MAX_CHARS', 2000),
|
|
285
|
+
summaryEveryNTurns: parsePositiveInt(env, 'DISCOCLAW_SUMMARY_EVERY_N_TURNS', 5),
|
|
286
|
+
summaryDataDirOverride: parseTrimmedString(env, 'DISCOCLAW_SUMMARY_DATA_DIR'),
|
|
287
|
+
durableMemoryEnabled: parseBoolean(env, 'DISCOCLAW_DURABLE_MEMORY_ENABLED', true),
|
|
288
|
+
durableDataDirOverride: parseTrimmedString(env, 'DISCOCLAW_DURABLE_DATA_DIR'),
|
|
289
|
+
durableInjectMaxChars: parsePositiveInt(env, 'DISCOCLAW_DURABLE_INJECT_MAX_CHARS', 2000),
|
|
290
|
+
durableMaxItems: parsePositiveInt(env, 'DISCOCLAW_DURABLE_MAX_ITEMS', 200),
|
|
291
|
+
memoryCommandsEnabled: parseBoolean(env, 'DISCOCLAW_MEMORY_COMMANDS_ENABLED', true),
|
|
292
|
+
planCommandsEnabled: parseBoolean(env, 'DISCOCLAW_PLAN_COMMANDS_ENABLED', true),
|
|
293
|
+
planPhasesEnabled: parseBoolean(env, 'PLAN_PHASES_ENABLED', true),
|
|
294
|
+
planPhaseMaxContextFiles: parsePositiveInt(env, 'PLAN_PHASE_MAX_CONTEXT_FILES', 5),
|
|
295
|
+
planPhaseTimeoutMs: parsePositiveNumber(env, 'PLAN_PHASE_TIMEOUT_MS', DEFAULT_THIRTY_MINUTES_MS),
|
|
296
|
+
planPhaseMaxAuditFixAttempts: parseNonNegativeInt(env, 'PLAN_PHASE_AUDIT_FIX_MAX', 3),
|
|
297
|
+
forgeCommandsEnabled: parseBoolean(env, 'DISCOCLAW_FORGE_COMMANDS_ENABLED', true),
|
|
298
|
+
forgeMaxAuditRounds: parsePositiveInt(env, 'FORGE_MAX_AUDIT_ROUNDS', 5),
|
|
299
|
+
forgeDrafterModel: parseTrimmedString(env, 'FORGE_DRAFTER_MODEL'),
|
|
300
|
+
forgeAuditorModel: parseTrimmedString(env, 'FORGE_AUDITOR_MODEL'),
|
|
301
|
+
forgeTimeoutMs: parsePositiveNumber(env, 'FORGE_TIMEOUT_MS', DEFAULT_THIRTY_MINUTES_MS),
|
|
302
|
+
forgeProgressThrottleMs: parseNonNegativeInt(env, 'FORGE_PROGRESS_THROTTLE_MS', 3000),
|
|
303
|
+
forgeAutoImplement: parseBoolean(env, 'FORGE_AUTO_IMPLEMENT', true),
|
|
304
|
+
openaiApiKey,
|
|
305
|
+
openaiBaseUrl,
|
|
306
|
+
openaiModel,
|
|
307
|
+
forgeDrafterRuntime,
|
|
308
|
+
forgeAuditorRuntime,
|
|
309
|
+
openrouterApiKey,
|
|
310
|
+
openrouterBaseUrl,
|
|
311
|
+
openrouterModel,
|
|
312
|
+
geminiBin: parseTrimmedString(env, 'GEMINI_BIN') ?? 'gemini',
|
|
313
|
+
geminiModel: parseTrimmedString(env, 'GEMINI_MODEL') ?? 'gemini-2.5-pro',
|
|
314
|
+
codexBin: parseTrimmedString(env, 'CODEX_BIN') ?? 'codex',
|
|
315
|
+
codexModel: parseTrimmedString(env, 'CODEX_MODEL') ?? 'gpt-5.3-codex',
|
|
316
|
+
codexDangerouslyBypassApprovalsAndSandbox: parseBoolean(env, 'CODEX_DANGEROUSLY_BYPASS_APPROVALS_AND_SANDBOX', false),
|
|
317
|
+
codexDisableSessions: parseBoolean(env, 'CODEX_DISABLE_SESSIONS', false),
|
|
318
|
+
summaryToDurableEnabled: parseBoolean(env, 'DISCOCLAW_SUMMARY_TO_DURABLE_ENABLED', true),
|
|
319
|
+
shortTermMemoryEnabled: parseBoolean(env, 'DISCOCLAW_SHORTTERM_MEMORY_ENABLED', true),
|
|
320
|
+
shortTermMaxEntries: parsePositiveInt(env, 'DISCOCLAW_SHORTTERM_MAX_ENTRIES', 20),
|
|
321
|
+
shortTermMaxAgeHours: parsePositiveNumber(env, 'DISCOCLAW_SHORTTERM_MAX_AGE_HOURS', 6),
|
|
322
|
+
shortTermInjectMaxChars: parsePositiveInt(env, 'DISCOCLAW_SHORTTERM_INJECT_MAX_CHARS', 1000),
|
|
323
|
+
shortTermDataDirOverride: parseTrimmedString(env, 'DISCOCLAW_SHORTTERM_DATA_DIR'),
|
|
324
|
+
actionFollowupDepth: parseNonNegativeInt(env, 'DISCOCLAW_ACTION_FOLLOWUP_DEPTH', 3),
|
|
325
|
+
reactionHandlerEnabled: parseBoolean(env, 'DISCOCLAW_REACTION_HANDLER', true),
|
|
326
|
+
reactionRemoveHandlerEnabled: parseBoolean(env, 'DISCOCLAW_REACTION_REMOVE_HANDLER', false),
|
|
327
|
+
reactionMaxAgeHours: parseNonNegativeNumber(env, 'DISCOCLAW_REACTION_MAX_AGE_HOURS', 24),
|
|
328
|
+
statusChannel: parseTrimmedString(env, 'DISCOCLAW_STATUS_CHANNEL'),
|
|
329
|
+
guildId: parseTrimmedString(env, 'DISCORD_GUILD_ID'),
|
|
330
|
+
cronEnabled,
|
|
331
|
+
cronForum,
|
|
332
|
+
cronModel: parseTrimmedString(env, 'DISCOCLAW_CRON_MODEL') ?? fastModel,
|
|
333
|
+
cronAutoTag: parseBoolean(env, 'DISCOCLAW_CRON_AUTO_TAG', true),
|
|
334
|
+
cronAutoTagModel: parseTrimmedString(env, 'DISCOCLAW_CRON_AUTO_TAG_MODEL') ?? fastModel,
|
|
335
|
+
cronStatsDirOverride: parseTrimmedString(env, 'DISCOCLAW_CRON_STATS_DIR'),
|
|
336
|
+
cronTagMapPathOverride: parseTrimmedString(env, 'DISCOCLAW_CRON_TAG_MAP'),
|
|
337
|
+
workspaceCwdOverride: parseTrimmedString(env, 'WORKSPACE_CWD'),
|
|
338
|
+
groupsDirOverride: parseTrimmedString(env, 'GROUPS_DIR'),
|
|
339
|
+
useGroupDirCwd: parseBoolean(env, 'USE_GROUP_DIR_CWD', false),
|
|
340
|
+
webhookEnabled,
|
|
341
|
+
webhookPort,
|
|
342
|
+
webhookConfigPath,
|
|
343
|
+
tasksEnabled,
|
|
344
|
+
tasksCwdOverride,
|
|
345
|
+
tasksForum,
|
|
346
|
+
tasksTagMapPathOverride,
|
|
347
|
+
tasksMentionUser,
|
|
348
|
+
tasksSidebar,
|
|
349
|
+
tasksAutoTag,
|
|
350
|
+
tasksAutoTagModel,
|
|
351
|
+
tasksSyncSkipPhase5,
|
|
352
|
+
tasksSyncFailureRetryEnabled,
|
|
353
|
+
tasksSyncFailureRetryDelayMs,
|
|
354
|
+
tasksSyncDeferredRetryDelayMs,
|
|
355
|
+
tasksPrefix,
|
|
356
|
+
claudeBin: parseTrimmedString(env, 'CLAUDE_BIN') ?? 'claude',
|
|
357
|
+
dangerouslySkipPermissions: parseBoolean(env, 'CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS', false),
|
|
358
|
+
outputFormat,
|
|
359
|
+
echoStdio: parseBoolean(env, 'CLAUDE_ECHO_STDIO', false),
|
|
360
|
+
verbose: effectiveVerbose,
|
|
361
|
+
claudeDebugFile: parseTrimmedString(env, 'CLAUDE_DEBUG_FILE'),
|
|
362
|
+
strictMcpConfig: parseBoolean(env, 'CLAUDE_STRICT_MCP_CONFIG', true),
|
|
363
|
+
sessionScanning: parseBoolean(env, 'DISCOCLAW_SESSION_SCANNING', true),
|
|
364
|
+
toolAwareStreaming: parseBoolean(env, 'DISCOCLAW_TOOL_AWARE_STREAMING', true),
|
|
365
|
+
multiTurn: parseBoolean(env, 'DISCOCLAW_MULTI_TURN', true),
|
|
366
|
+
multiTurnHangTimeoutMs: parsePositiveInt(env, 'DISCOCLAW_MULTI_TURN_HANG_TIMEOUT_MS', 60000),
|
|
367
|
+
multiTurnIdleTimeoutMs: parsePositiveInt(env, 'DISCOCLAW_MULTI_TURN_IDLE_TIMEOUT_MS', 300000),
|
|
368
|
+
multiTurnMaxProcesses: parsePositiveInt(env, 'DISCOCLAW_MULTI_TURN_MAX_PROCESSES', 5),
|
|
369
|
+
streamStallTimeoutMs: parseNonNegativeInt(env, 'DISCOCLAW_STREAM_STALL_TIMEOUT_MS', 300000),
|
|
370
|
+
progressStallTimeoutMs: parseNonNegativeInt(env, 'DISCOCLAW_PROGRESS_STALL_TIMEOUT_MS', 300000),
|
|
371
|
+
streamStallWarningMs: parseNonNegativeInt(env, 'DISCOCLAW_STREAM_STALL_WARNING_MS', 150000),
|
|
372
|
+
maxConcurrentInvocations: parseNonNegativeInt(env, 'DISCOCLAW_MAX_CONCURRENT_INVOCATIONS', 0),
|
|
373
|
+
debugRuntime: parseBoolean(env, 'DISCOCLAW_DEBUG_RUNTIME', false),
|
|
374
|
+
healthCommandsEnabled: parseBoolean(env, 'DISCOCLAW_HEALTH_COMMANDS_ENABLED', true),
|
|
375
|
+
healthVerboseAllowlist,
|
|
376
|
+
botDisplayName: parseTrimmedString(env, 'DISCOCLAW_BOT_NAME'),
|
|
377
|
+
botStatus: parseEnum(env, 'DISCOCLAW_BOT_STATUS', ['online', 'idle', 'dnd', 'invisible']),
|
|
378
|
+
botActivity: parseTrimmedString(env, 'DISCOCLAW_BOT_ACTIVITY'),
|
|
379
|
+
botActivityType: parseEnum(env, 'DISCOCLAW_BOT_ACTIVITY_TYPE', ['Playing', 'Listening', 'Watching', 'Competing', 'Custom'], 'Playing'),
|
|
380
|
+
botAvatar: parseAvatarPath(env, 'DISCOCLAW_BOT_AVATAR'),
|
|
381
|
+
},
|
|
382
|
+
warnings,
|
|
383
|
+
infos,
|
|
384
|
+
};
|
|
385
|
+
}
|