discoclaw 0.1.0 → 0.1.1

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.
Files changed (106) hide show
  1. package/.context/README.md +4 -4
  2. package/.context/architecture.md +0 -1
  3. package/.context/dev.md +127 -12
  4. package/.context/discord.md +20 -10
  5. package/.context/runtime.md +51 -1
  6. package/.context/tasks.md +3 -2
  7. package/.env.example.full +39 -10
  8. package/dist/cron/cron-sync.js +35 -11
  9. package/dist/cron/executor.js +7 -5
  10. package/dist/cron/forum-sync.js +5 -4
  11. package/dist/cron/run-stats.js +5 -3
  12. package/dist/discord/action-utils.js +2 -0
  13. package/dist/discord/actions-bot-profile.js +7 -1
  14. package/dist/discord/actions-config.js +1 -1
  15. package/dist/discord/actions-crons.js +19 -5
  16. package/dist/discord/actions-guild.js +16 -2
  17. package/dist/discord/actions-messaging.js +87 -42
  18. package/dist/discord/actions-messaging.test.js +26 -0
  19. package/dist/discord/actions-plan.js +30 -11
  20. package/dist/discord/actions-poll.js +2 -1
  21. package/dist/discord/actions.js +1 -1
  22. package/dist/discord/deferred-runner.js +195 -0
  23. package/dist/discord/durable-memory.js +14 -8
  24. package/dist/discord/forum-count-sync.js +9 -2
  25. package/dist/discord/inflight-replies.js +20 -4
  26. package/dist/discord/message-coordinator.groupdir.test.js +33 -0
  27. package/dist/discord/message-coordinator.js +78 -35
  28. package/dist/discord/plan-manager.js +4 -1
  29. package/dist/discord/platform-message.js +3 -5
  30. package/dist/discord/reaction-handler.js +21 -8
  31. package/dist/discord/reaction-prompts.js +5 -1
  32. package/dist/discord/restart-command.js +7 -2
  33. package/dist/discord/shortterm-memory.js +27 -16
  34. package/dist/discord/shutdown-context.js +15 -6
  35. package/dist/discord/status-command.js +26 -17
  36. package/dist/discord/streaming-progress.js +7 -1
  37. package/dist/discord/summarizer.js +11 -8
  38. package/dist/discord/system-bootstrap.js +43 -33
  39. package/dist/discord/update-command.js +6 -2
  40. package/dist/discord/user-turn-to-durable.js +13 -7
  41. package/dist/discord.js +7 -1
  42. package/dist/health/startup-healing.js +15 -6
  43. package/dist/index.js +154 -368
  44. package/dist/index.paths.js +25 -0
  45. package/dist/index.paths.test.js +36 -0
  46. package/dist/index.post-connect.js +114 -0
  47. package/dist/index.runtime.js +50 -0
  48. package/dist/runtime/cli-adapter.js +21 -10
  49. package/dist/runtime/cli-adapter.test.js +155 -0
  50. package/dist/runtime/cli-output-parsers.js +17 -7
  51. package/dist/runtime/session-scanner.js +22 -12
  52. package/dist/runtime/strategies/codex-strategy.js +3 -2
  53. package/dist/runtime/strategies/gemini-strategy.js +3 -2
  54. package/dist/runtime/strategies/template-strategy.js +2 -2
  55. package/dist/tasks/forum-guard.js +12 -4
  56. package/dist/tasks/thread-lifecycle-ops.js +37 -8
  57. package/dist/workspace-bootstrap.test.js +3 -3
  58. package/package.json +7 -2
  59. package/templates/workspace/AGENTS.md +4 -5
  60. package/templates/workspace/TOOLS.md +2 -2
  61. package/dist/beads/auto-tag.js +0 -2
  62. package/dist/beads/auto-tag.test.js +0 -62
  63. package/dist/beads/bd-cli.js +0 -9
  64. package/dist/beads/bd-cli.test.js +0 -495
  65. package/dist/beads/bead-hooks-cli.js +0 -149
  66. package/dist/beads/bead-sync-cli.js +0 -5
  67. package/dist/beads/bead-sync-cli.test.js +0 -72
  68. package/dist/beads/bead-sync-coordinator.js +0 -4
  69. package/dist/beads/bead-sync-coordinator.test.js +0 -239
  70. package/dist/beads/bead-sync-watcher.js +0 -2
  71. package/dist/beads/bead-sync-watcher.test.js +0 -96
  72. package/dist/beads/bead-sync.js +0 -7
  73. package/dist/beads/bead-sync.test.js +0 -876
  74. package/dist/beads/bead-thread-cache.js +0 -8
  75. package/dist/beads/bead-thread-cache.test.js +0 -91
  76. package/dist/beads/discord-sync.js +0 -18
  77. package/dist/beads/discord-sync.test.js +0 -782
  78. package/dist/beads/find-bead-by-thread.test.js +0 -36
  79. package/dist/beads/forum-guard.js +0 -2
  80. package/dist/beads/forum-guard.test.js +0 -204
  81. package/dist/beads/initialize.js +0 -3
  82. package/dist/beads/initialize.test.js +0 -304
  83. package/dist/beads/types.js +0 -10
  84. package/dist/discoclaw-plan-format.test.js +0 -137
  85. package/dist/discord/action-types.js +0 -1
  86. package/dist/discord/actions-beads.js +0 -1
  87. package/dist/discord/actions-beads.test.js +0 -372
  88. package/dist/discord/actions-tasks.js +0 -3
  89. package/dist/discord/actions-tasks.test.js +0 -418
  90. package/dist/discord/bot.js +0 -141
  91. package/dist/discord/keyed-queue.js +0 -22
  92. package/dist/discord/plan-run-phase-start.js +0 -20
  93. package/dist/discord/plan-run-phase-start.test.js +0 -29
  94. package/dist/engine/claudeCli.js +0 -137
  95. package/dist/engine/types.js +0 -1
  96. package/dist/sessionManager.js +0 -47
  97. package/dist/tasks/bd-cli.js +0 -164
  98. package/dist/tasks/bd-cli.test.js +0 -359
  99. package/dist/tasks/bead-sync.js +0 -1
  100. package/dist/tasks/discord-sync.js +0 -3
  101. package/dist/tasks/discord-sync.test.js +0 -685
  102. package/dist/tasks/migrate.js +0 -33
  103. package/dist/tasks/migrate.test.js +0 -156
  104. package/dist/tasks/sync-watcher.js +0 -27
  105. package/dist/tasks/sync-watcher.test.js +0 -92
  106. package/dist/tasks/task-sync-apply.js +0 -319
@@ -12,10 +12,10 @@ Core instructions live in `CLAUDE.md` at the repo root.
12
12
  | **Discord behavior + routing** | `discord.md` |
13
13
  | **Discord bot setup (invite + env)** | `bot-setup.md` |
14
14
  | **Development / build / test** | `dev.md` |
15
- | **Runtime adapters (Claude CLI, OpenAI/Gemini later)** | `runtime.md` |
15
+ | **Runtime adapters (Claude CLI, Codex CLI, Gemini CLI, OpenRouter)** | `runtime.md` |
16
16
  | **Ops / systemd service** | `ops.md` |
17
17
  | **Memory system** | `memory.md` |
18
- | **Task tracking / bd CLI** | `tasks.md` |
18
+ | **Task tracking** | `tasks.md` |
19
19
  | **Architecture / system overview** | `architecture.md` |
20
20
  | **Tool capabilities / browser automation** | `tools.md` |
21
21
  | **Forge/plan standing constraints** | `project.md` *(auto-loaded by forge)* |
@@ -31,10 +31,10 @@ Core instructions live in `CLAUDE.md` at the repo root.
31
31
  - **pa-safety.md** — Indirect prompt injection defense, golden rules, red flags
32
32
  - **dev.md** — Commands, env, local dev loops, build/test
33
33
  - **discord.md** — Allowlist gating, session keys, threading rules, output constraints
34
- - **runtime.md** — Runtime adapter interface, Claude CLI flags, capability routing
34
+ - **runtime.md** — Runtime adapter interface, multi-runtime CLI flags, capability routing
35
35
  - **ops.md** — systemd service notes, logs, restart workflow
36
36
  - **memory.md** — Memory layers, user-facing examples, config reference, concurrency
37
- - **tasks.md** — Task tracker: data model, bd CLI, hooks, Discord sync, auto-tagging
37
+ - **tasks.md** — Task tracker: data model, store events, Discord sync, auto-tagging
38
38
  - **architecture.md** — System overview, data flow, directory layout, key concepts
39
39
  - **bot-setup.md** — One-time bot creation and invite guide
40
40
  - **tools.md** — Available tools: browser automation (agent-browser), escalation ladder, CDP connect, security guardrails
@@ -25,7 +25,6 @@ Discord message
25
25
  | `src/discord/` | Discord subsystems: actions, allowlist, channel context, memory, output |
26
26
  | `src/runtime/` | Runtime adapters (Claude CLI), concurrency, process pool |
27
27
  | `src/tasks/` | In-process task data model + store + migration helpers |
28
- | `src/beads/` | Retired legacy shim namespace (runtime shims removed in hard-cut) |
29
28
  | `src/cron/` | Cron scheduler, executor, forum sync, run stats |
30
29
  | `src/observability/` | Metrics registry |
31
30
  | `src/sessions.ts` | Session manager (maps session keys to runtime session IDs) |
package/.context/dev.md CHANGED
@@ -32,16 +32,25 @@ Two setup paths:
32
32
  | `DISCORD_TOKEN` | **(required)** | Bot token |
33
33
  | `DISCORD_ALLOW_USER_IDS` | **(required)** | Comma/space-separated Discord user IDs; fail-closed if empty |
34
34
  | `DISCORD_CHANNEL_IDS` | *(empty — all channels)* | Restrict the bot to specific guild channel IDs (DMs still allowed) |
35
+ | `DISCORD_GUILD_ID` | *(empty)* | Optional guild snowflake; required for some guild-scoped features |
35
36
  | `DISCORD_REQUIRE_CHANNEL_CONTEXT` | `1` | Require a per-channel context file before responding |
36
37
  | `DISCORD_AUTO_INDEX_CHANNEL_CONTEXT` | `1` | Auto-create stub context files for new channels |
37
- | `DISCORD_AUTO_JOIN_THREADS` | `0` | Best-effort auto-join threads so the bot can respond inside them |
38
- | `DISCOCLAW_DISCORD_ACTIONS` | `0` | Master switch for Discord server actions |
38
+ | `DISCORD_AUTO_JOIN_THREADS` | `1` | Best-effort auto-join threads so the bot can respond inside them |
39
+ | `DISCOCLAW_DISCORD_ACTIONS` | `1` | Master switch for Discord server actions |
39
40
  | `DISCOCLAW_DISCORD_ACTIONS_CHANNELS` | `1` | Channel management (create/edit/delete/list/info, categoryCreate) |
40
- | `DISCOCLAW_DISCORD_ACTIONS_MESSAGING` | `0` | Messaging (send/edit/delete/read messages, react, threads, pins) |
41
- | `DISCOCLAW_DISCORD_ACTIONS_GUILD` | `0` | Guild info (memberInfo, roleInfo, roleAdd/Remove, events, search) |
41
+ | `DISCOCLAW_DISCORD_ACTIONS_MESSAGING` | `1` | Messaging (send/edit/delete/read messages, react, threads, pins) |
42
+ | `DISCOCLAW_DISCORD_ACTIONS_GUILD` | `1` | Guild info (memberInfo, roleInfo, roleAdd/Remove, events, search) |
42
43
  | `DISCOCLAW_DISCORD_ACTIONS_MODERATION` | `0` | Moderation (timeout, kick, ban) |
43
- | `DISCOCLAW_DISCORD_ACTIONS_POLLS` | `0` | Poll creation |
44
+ | `DISCOCLAW_DISCORD_ACTIONS_POLLS` | `1` | Poll creation |
44
45
  | `DISCOCLAW_DISCORD_ACTIONS_TASKS` | `1` | Task tracking (create/update/close/show/list/sync) |
46
+ | `DISCOCLAW_DISCORD_ACTIONS_CRONS` | `1` | Cron/automation forum actions |
47
+ | `DISCOCLAW_DISCORD_ACTIONS_BOT_PROFILE` | `1` | Bot profile management (display name, avatar, status) |
48
+ | `DISCOCLAW_DISCORD_ACTIONS_FORGE` | `1` | Forge actions (file drafting and implementation) |
49
+ | `DISCOCLAW_DISCORD_ACTIONS_PLAN` | `1` | Plan command actions |
50
+ | `DISCOCLAW_DISCORD_ACTIONS_MEMORY` | `1` | Memory management actions |
51
+ | `DISCOCLAW_DISCORD_ACTIONS_DEFER` | `1` | Deferred/background action execution |
52
+ | `DISCOCLAW_DISCORD_ACTIONS_DEFER_MAX_DELAY_SECONDS` | `1800` | Max wait in seconds before a deferred action times out |
53
+ | `DISCOCLAW_DISCORD_ACTIONS_DEFER_MAX_CONCURRENT` | `5` | Max concurrent deferred actions |
45
54
 
46
55
  ### Claude CLI
47
56
  | Variable | Default | Description |
@@ -49,6 +58,7 @@ Two setup paths:
49
58
  | `CLAUDE_BIN` | `claude` | Path/name of the Claude CLI binary |
50
59
  | `CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS` | `0` | Pass `--dangerously-skip-permissions` to the CLI |
51
60
  | `CLAUDE_OUTPUT_FORMAT` | `text` | `text` or `stream-json` (preferred for smoother streaming) |
61
+ | `CLAUDE_VERBOSE` | `0` | Pass `--verbose` to the CLI; only effective when `CLAUDE_OUTPUT_FORMAT=stream-json` |
52
62
  | `CLAUDE_ECHO_STDIO` | `0` | Forward raw CLI stdout/stderr lines into Discord output |
53
63
  | `CLAUDE_DEBUG_FILE` | *(empty)* | Write Claude CLI debug logs to this file path |
54
64
  | `CLAUDE_STRICT_MCP_CONFIG` | `1` | Pass `--strict-mcp-config` to skip slow MCP plugin init |
@@ -63,14 +73,21 @@ Two setup paths:
63
73
  | `USE_GROUP_DIR_CWD` | `0` | Enable nanoclaw-style group CWD per session |
64
74
  | `LOG_LEVEL` | `info` | Pino log level |
65
75
  | `DISCOCLAW_DEBUG_RUNTIME` | `0` | Dump resolved runtime config at startup (debugging systemd env issues) |
76
+ | `DISCOCLAW_FORCE_BOOTSTRAP` | `0` | Re-create `workspace/BOOTSTRAP.md` from template on next startup (set to `1`; auto-clears after first boot) |
66
77
 
67
78
  ### Runtime Invocation
68
79
  | Variable | Default | Description |
69
80
  |----------|---------|-------------|
81
+ | `PRIMARY_RUNTIME` | `claude` | Runtime engine (`claude`, `openai`, `openrouter`, `gemini`, `codex`) |
70
82
  | `RUNTIME_MODEL` | `capable` | Model tier (`fast`, `capable`) or concrete model name passed to the CLI |
71
83
  | `RUNTIME_TOOLS` | `Bash,Read,Write,Edit,Glob,Grep,WebSearch,WebFetch` | Comma-separated tool list |
72
- | `RUNTIME_TIMEOUT_MS` | `600000` | Per-invocation timeout in milliseconds |
84
+ | `RUNTIME_TIMEOUT_MS` | `1800000` | Per-invocation timeout in milliseconds |
85
+ | `RUNTIME_FALLBACK_MODEL` | *(unset)* | Auto-fallback model when primary is overloaded (e.g. `sonnet`) |
86
+ | `RUNTIME_MAX_BUDGET_USD` | *(unset)* | Max USD per CLI process; one-shot = per invocation, multi-turn = per session lifetime |
87
+ | `DISCOCLAW_FAST_MODEL` | `fast` | Default "fast" model tier alias used for summarization, auto-tag, and cron parsing |
73
88
  | `DISCOCLAW_RUNTIME_SESSIONS` | `1` | Persist Claude session IDs across messages |
89
+ | `DISCOCLAW_SESSION_SCANNING` | `1` | Enable session ID scanning for resume detection |
90
+ | `DISCOCLAW_ACTION_FOLLOWUP_DEPTH` | `3` | Max depth for chained action follow-ups |
74
91
  | `DISCOCLAW_MESSAGE_HISTORY_BUDGET` | `3000` | Char budget for recent conversation history in prompts (0 = disabled) |
75
92
  | `DISCOCLAW_SUMMARY_ENABLED` | `1` | Enable rolling conversation summaries |
76
93
  | `DISCOCLAW_SUMMARY_MODEL` | `fast` | Model tier or concrete name for summarization |
@@ -81,18 +98,25 @@ Two setup paths:
81
98
  | `DISCOCLAW_DURABLE_MAX_ITEMS` | `200` | Max durable items per user |
82
99
  | `DISCOCLAW_MEMORY_COMMANDS_ENABLED` | `1` | Enable `!memory` commands (show/remember/forget/reset) |
83
100
  | `DISCOCLAW_STATUS_CHANNEL` | *(empty — disabled)* | Channel name or ID for status messages (bot online/offline, errors) |
84
- | `RUNTIME_FALLBACK_MODEL` | *(unset)* | Auto-fallback model when primary is overloaded (e.g. `sonnet`) |
85
- | `RUNTIME_MAX_BUDGET_USD` | *(unset)* | Max USD per CLI process; one-shot = per invocation, multi-turn = per session lifetime |
86
101
  | `CLAUDE_APPEND_SYSTEM_PROMPT` | *(unset)* | Append to system prompt (max 4000 chars); skips workspace PA file reads when set |
87
- | `DISCOCLAW_CRON_ENABLED` | `1` | Master switch for the cron subsystem (forum-based scheduled tasks) |
88
- | `DISCOCLAW_CRON_FORUM` | **(required when enabled)** | Automations forum channel ID (snowflake) for cron/automation definitions |
89
- | `DISCOCLAW_CRON_MODEL` | `fast` | Model tier or concrete name for parsing cron definitions |
90
102
 
91
103
  ### Browser Automation
92
104
  | Variable | Default | Description |
93
105
  |----------|---------|-------------|
94
106
  | `AGENT_BROWSER_EXECUTABLE_PATH` | *(empty)* | Path to the browser binary for `agent-browser` (e.g. Chromium). If unset, agent-browser uses its bundled default. |
95
107
 
108
+ ### Cron
109
+ | Variable | Default | Description |
110
+ |----------|---------|-------------|
111
+ | `DISCOCLAW_CRON_ENABLED` | `1` | Master switch for the cron subsystem (forum-based scheduled tasks) |
112
+ | `DISCOCLAW_CRON_FORUM` | **(required when enabled)** | Automations forum channel ID (snowflake) for cron/automation definitions |
113
+ | `DISCOCLAW_CRON_MODEL` | `fast` | Model tier or concrete name for parsing cron definitions |
114
+ | `DISCOCLAW_CRON_AUTO_TAG` | `1` | Enable AI auto-tagging of cron threads |
115
+ | `DISCOCLAW_CRON_AUTO_TAG_MODEL` | `fast` | Model tier or concrete name for cron auto-tagging |
116
+ | `DISCOCLAW_CRON_STATS_DIR` | *(empty)* | Override directory for cron run stats (defaults to data dir) |
117
+ | `DISCOCLAW_CRON_TAG_MAP` | *(empty)* | Override path to cron forum tag map JSON |
118
+ | `DEFAULT_TIMEZONE` | *(system timezone)* | Default IANA timezone for cron schedules that don't specify one (e.g. `America/New_York`); falls back to system timezone if unset or invalid |
119
+
96
120
  ### Tasks (Task Tracking)
97
121
  | Variable | Default | Description |
98
122
  |----------|---------|-------------|
@@ -101,12 +125,103 @@ Two setup paths:
101
125
  | `DISCOCLAW_TASKS_CWD` | `<WORKSPACE_CWD>` | Tasks workspace override (legacy import/migration path) |
102
126
  | `DISCOCLAW_TASKS_TAG_MAP` | `data/tasks/tag-map.json` | Path to task forum tag map |
103
127
  | `DISCOCLAW_TASKS_MENTION_USER` | *(empty)* | User ID to @mention in new task threads |
104
- | `DISCOCLAW_TASKS_SIDEBAR` | `0` | When `1` + `MENTION_USER` set, persists @mention in open task starters for sidebar visibility |
128
+ | `DISCOCLAW_TASKS_SIDEBAR` | `1` | When `1` + `MENTION_USER` set, persists @mention in open task starters for sidebar visibility |
105
129
  | `DISCOCLAW_TASKS_AUTO_TAG` | `1` | Enable AI auto-tagging |
106
130
  | `DISCOCLAW_TASKS_AUTO_TAG_MODEL` | `fast` | Model tier or concrete name for auto-tagging |
107
131
  | `DISCOCLAW_TASKS_SYNC_SKIP_PHASE5` | `0` | Disable phase-5 reconciliation during sync |
132
+ | `DISCOCLAW_TASKS_SYNC_FAILURE_RETRY_ENABLED` | `1` | Retry a failed Discord sync on the next mutation |
133
+ | `DISCOCLAW_TASKS_SYNC_FAILURE_RETRY_DELAY_MS` | `30000` | Delay in ms before retrying a failed sync |
134
+ | `DISCOCLAW_TASKS_SYNC_DEFERRED_RETRY_DELAY_MS` | `30000` | Delay in ms for deferred sync retries when Discord is busy |
108
135
  | `DISCOCLAW_TASKS_PREFIX` | `ws` | Prefix for generated task IDs (e.g. `ws-001`) |
109
136
 
137
+ ### Short-Term Memory
138
+ | Variable | Default | Description |
139
+ |----------|---------|-------------|
140
+ | `DISCOCLAW_SHORTTERM_MEMORY_ENABLED` | `1` | Enable ephemeral short-term memory (cross-session recency context) |
141
+ | `DISCOCLAW_SHORTTERM_MAX_ENTRIES` | `20` | Max entries kept in the short-term memory store |
142
+ | `DISCOCLAW_SHORTTERM_MAX_AGE_HOURS` | `6` | Max age in hours before a short-term entry expires |
143
+ | `DISCOCLAW_SHORTTERM_INJECT_MAX_CHARS` | `1000` | Max chars for short-term memory injected into prompts |
144
+ | `DISCOCLAW_SHORTTERM_DATA_DIR` | *(empty)* | Override directory for short-term memory storage |
145
+ | `DISCOCLAW_SUMMARY_TO_DURABLE_ENABLED` | `1` | Promote significant summary content into durable memory |
146
+ | `DISCOCLAW_SUMMARY_DATA_DIR` | *(empty)* | Override directory for rolling summary storage |
147
+ | `DISCOCLAW_DURABLE_DATA_DIR` | *(empty)* | Override directory for durable memory storage |
148
+
149
+ ### Reaction Handler
150
+ | Variable | Default | Description |
151
+ |----------|---------|-------------|
152
+ | `DISCOCLAW_REACTION_HANDLER` | `1` | Enable reaction-add event handler |
153
+ | `DISCOCLAW_REACTION_REMOVE_HANDLER` | `0` | Enable reaction-remove event handler |
154
+ | `DISCOCLAW_REACTION_MAX_AGE_HOURS` | `24` | Max age in hours of a message that will trigger a reaction event |
155
+
156
+ ### Bot Identity
157
+ | Variable | Default | Description |
158
+ |----------|---------|-------------|
159
+ | `DISCOCLAW_BOT_NAME` | *(empty)* | Override the bot's display name in Discord |
160
+ | `DISCOCLAW_BOT_STATUS` | *(empty — Discord default)* | Presence status: `online`, `idle`, `dnd`, or `invisible` |
161
+ | `DISCOCLAW_BOT_ACTIVITY` | *(empty)* | Activity text shown in Discord presence (e.g. `with fire`) |
162
+ | `DISCOCLAW_BOT_ACTIVITY_TYPE` | `Playing` | Activity verb: `Playing`, `Listening`, `Watching`, `Competing`, or `Custom` |
163
+ | `DISCOCLAW_BOT_AVATAR` | *(empty)* | Absolute file path or URL for the bot's avatar image |
164
+
165
+ ### Health/Status
166
+ | Variable | Default | Description |
167
+ |----------|---------|-------------|
168
+ | `DISCOCLAW_HEALTH_COMMANDS_ENABLED` | `1` | Enable `!health` command (uptime, memory, runtime status) |
169
+ | `DISCOCLAW_HEALTH_VERBOSE_ALLOWLIST` | *(empty — falls back to `DISCORD_ALLOW_USER_IDS`)* | Space/comma-separated user IDs that can request verbose health output |
170
+
171
+ ### Plan & Forge
172
+ | Variable | Default | Description |
173
+ |----------|---------|-------------|
174
+ | `DISCOCLAW_PLAN_COMMANDS_ENABLED` | `1` | Enable plan commands (`!plan`, `!plan phase`, etc.) |
175
+ | `PLAN_PHASES_ENABLED` | `1` | Enable phase-by-phase plan execution |
176
+ | `PLAN_PHASE_MAX_CONTEXT_FILES` | `5` | Max `.context/` files injected per plan phase |
177
+ | `PLAN_PHASE_TIMEOUT_MS` | `1800000` | Per-phase timeout in milliseconds |
178
+ | `PLAN_PHASE_AUDIT_FIX_MAX` | `3` | Max audit-fix attempts per phase before giving up |
179
+ | `DISCOCLAW_FORGE_COMMANDS_ENABLED` | `1` | Enable forge commands (`!forge`) |
180
+ | `FORGE_MAX_AUDIT_ROUNDS` | `5` | Max audit rounds before forge accepts the draft |
181
+ | `FORGE_DRAFTER_MODEL` | *(empty — uses `RUNTIME_MODEL`)* | Override model for the forge drafter step |
182
+ | `FORGE_AUDITOR_MODEL` | *(empty — uses `RUNTIME_MODEL`)* | Override model for the forge auditor step |
183
+ | `FORGE_TIMEOUT_MS` | `1800000` | Per-forge-session timeout in milliseconds |
184
+ | `FORGE_PROGRESS_THROTTLE_MS` | `3000` | Min ms between forge progress Discord updates |
185
+ | `FORGE_AUTO_IMPLEMENT` | `1` | Automatically implement the approved forge plan without a separate confirm step |
186
+ | `FORGE_DRAFTER_RUNTIME` | *(empty — uses `PRIMARY_RUNTIME`)* | Runtime adapter for the forge drafter (e.g. `openai`, `claude`) |
187
+ | `FORGE_AUDITOR_RUNTIME` | *(empty — uses `PRIMARY_RUNTIME`)* | Runtime adapter for the forge auditor |
188
+
189
+ ### Multi-Provider Adapters
190
+ | Variable | Default | Description |
191
+ |----------|---------|-------------|
192
+ | `OPENAI_API_KEY` | *(empty)* | OpenAI API key; required when `PRIMARY_RUNTIME=openai` or `FORGE_*_RUNTIME=openai` |
193
+ | `OPENAI_BASE_URL` | *(empty — OpenAI default)* | OpenAI-compatible API base URL override (e.g. for local models) |
194
+ | `OPENAI_MODEL` | `gpt-4o` | Default model for the OpenAI adapter |
195
+ | `OPENROUTER_API_KEY` | *(empty)* | OpenRouter API key; required when `PRIMARY_RUNTIME=openrouter` |
196
+ | `OPENROUTER_BASE_URL` | *(empty — OpenRouter default)* | OpenRouter API base URL override |
197
+ | `OPENROUTER_MODEL` | `anthropic/claude-sonnet-4` | Default model for the OpenRouter adapter |
198
+ | `GEMINI_BIN` | `gemini` | Path/name of the Gemini CLI binary |
199
+ | `GEMINI_MODEL` | `gemini-2.5-pro` | Default model for the Gemini adapter |
200
+ | `CODEX_BIN` | `codex` | Path/name of the Codex CLI binary |
201
+ | `CODEX_MODEL` | `gpt-5.3-codex` | Default model for the Codex adapter |
202
+ | `CODEX_DANGEROUSLY_BYPASS_APPROVALS_AND_SANDBOX` | `0` | Pass the bypass-approvals flag to Codex (use with caution) |
203
+ | `CODEX_DISABLE_SESSIONS` | `0` | Disable session persistence for the Codex adapter |
204
+
205
+ ### Webhooks
206
+ | Variable | Default | Description |
207
+ |----------|---------|-------------|
208
+ | `DISCOCLAW_WEBHOOK_ENABLED` | `0` | Enable the inbound webhook HTTP server |
209
+ | `DISCOCLAW_WEBHOOK_PORT` | `9400` | Port for the webhook HTTP server |
210
+ | `DISCOCLAW_WEBHOOK_CONFIG` | *(empty)* | Path to webhook route config JSON |
211
+
212
+ ### Streaming & Multi-Turn
213
+ | Variable | Default | Description |
214
+ |----------|---------|-------------|
215
+ | `DISCOCLAW_MULTI_TURN` | `1` | Enable multi-turn (persistent subprocess) mode |
216
+ | `DISCOCLAW_MULTI_TURN_HANG_TIMEOUT_MS` | `60000` | Timeout in ms to detect a hung multi-turn process |
217
+ | `DISCOCLAW_MULTI_TURN_IDLE_TIMEOUT_MS` | `300000` | Idle timeout in ms before a multi-turn process is recycled |
218
+ | `DISCOCLAW_MULTI_TURN_MAX_PROCESSES` | `5` | Max concurrent multi-turn processes |
219
+ | `DISCOCLAW_TOOL_AWARE_STREAMING` | `1` | Parse tool-use events during streaming for better progress reporting |
220
+ | `DISCOCLAW_STREAM_STALL_TIMEOUT_MS` | `300000` | Timeout in ms before a stalled stream is aborted |
221
+ | `DISCOCLAW_PROGRESS_STALL_TIMEOUT_MS` | `300000` | Timeout in ms before a stalled progress indicator is shown |
222
+ | `DISCOCLAW_STREAM_STALL_WARNING_MS` | `150000` | Warn in Discord after this many ms of stream inactivity |
223
+ | `DISCOCLAW_MAX_CONCURRENT_INVOCATIONS` | `0` | Max concurrent runtime invocations (0 = unlimited) |
224
+
110
225
  ## Debugging
111
226
 
112
227
  ### Where logs go
@@ -28,7 +28,7 @@ Strict mode:
28
28
  - `DISCORD_AUTO_INDEX_CHANNEL_CONTEXT=1` (default): when a message arrives for a new channel, DiscoClaw appends it to `discord/DISCORD.md` and creates a blank stub file.
29
29
 
30
30
  Thread auto-join:
31
- - `DISCORD_AUTO_JOIN_THREADS=0` (default): best-effort auto-join threads so the bot can respond inside them. Private threads still require adding the bot manually.
31
+ - `DISCORD_AUTO_JOIN_THREADS=1` (default): best-effort auto-join threads so the bot can respond inside them. Private threads still require adding the bot manually.
32
32
 
33
33
  ## Conversation History & Memory
34
34
  When `DISCOCLAW_MESSAGE_HISTORY_BUDGET` > 0 (default: `3000`), DiscoClaw fetches recent messages from the Discord channel and includes them in the prompt as conversation context. This allows Claude to maintain conversational context across messages without relying on CLI session persistence. The char budget caps the total history size; messages are selected newest-first so the most relevant context is preserved. Bot responses are truncated when necessary to fit the budget.
@@ -73,19 +73,30 @@ Each action category has its own flag (only active when the master switch is `1`
73
73
 
74
74
  | Flag | Default | Actions |
75
75
  |------|---------|---------|
76
- | `DISCOCLAW_DISCORD_ACTIONS_CHANNELS` | `1` | channelCreate, channelEdit, channelDelete, channelList, channelInfo, categoryCreate, threadListArchived |
77
- | `DISCOCLAW_DISCORD_ACTIONS_MESSAGING` | `0` | sendMessage, react, readMessages, fetchMessage, editMessage, deleteMessage, threadCreate, pinMessage, unpinMessage, listPins |
78
- | `DISCOCLAW_DISCORD_ACTIONS_GUILD` | `0` | memberInfo, roleInfo, roleAdd, roleRemove, searchMessages, eventList, eventCreate |
76
+ | `DISCOCLAW_DISCORD_ACTIONS_CHANNELS` | `1` | channelCreate, channelEdit, channelDelete, channelList, channelInfo, categoryCreate, channelMove, threadListArchived, forumTagCreate, forumTagDelete, forumTagList, threadEdit |
77
+ | `DISCOCLAW_DISCORD_ACTIONS_MESSAGING` | `1` | sendMessage, sendFile, react, unreact, readMessages, fetchMessage, editMessage, deleteMessage, bulkDelete, crosspost, threadCreate, pinMessage, unpinMessage, listPins, reactionPrompt |
78
+ | `DISCOCLAW_DISCORD_ACTIONS_GUILD` | `1` | memberInfo, roleInfo, roleAdd, roleRemove, searchMessages, eventList, eventCreate, eventEdit, eventDelete |
79
79
  | `DISCOCLAW_DISCORD_ACTIONS_MODERATION` | `0` | timeout, kick, ban |
80
- | `DISCOCLAW_DISCORD_ACTIONS_POLLS` | `0` | poll |
81
- | `DISCOCLAW_DISCORD_ACTIONS_TASKS` | `1` | taskCreate, taskUpdate, taskClose, taskShow, taskList, taskSync |
82
-
83
- Auto-follow-up: When query actions (channelList, channelInfo, threadListArchived, readMessages, fetchMessage, listPins, memberInfo, roleInfo, searchMessages, eventList, taskList, taskShow) succeed, DiscoClaw automatically re-invokes Claude with the results. This allows Claude to reason about query results without requiring the user to send a follow-up message. Controlled by `DISCOCLAW_ACTION_FOLLOWUP_DEPTH` (default `3`, `0` disables). Mutation-only responses do not trigger follow-ups. Trivially short follow-up responses (<50 chars with no actions) are suppressed.
80
+ | `DISCOCLAW_DISCORD_ACTIONS_POLLS` | `1` | poll |
81
+ | `DISCOCLAW_DISCORD_ACTIONS_TASKS` | `1` | taskCreate, taskUpdate, taskClose, taskShow, taskList, taskSync, tagMapReload |
82
+ | `DISCOCLAW_DISCORD_ACTIONS_CRONS` | `1` | cronCreate, cronUpdate, cronList, cronShow, cronPause, cronResume, cronDelete, cronTrigger, cronSync, cronTagMapReload |
83
+ | `DISCOCLAW_DISCORD_ACTIONS_BOT_PROFILE` | `1` | botSetStatus, botSetActivity, botSetNickname |
84
+ | `DISCOCLAW_DISCORD_ACTIONS_FORGE` | `1` | forgeCreate, forgeResume, forgeStatus, forgeCancel |
85
+ | `DISCOCLAW_DISCORD_ACTIONS_PLAN` | `1` | planList, planShow, planApprove, planClose, planCreate, planRun |
86
+ | `DISCOCLAW_DISCORD_ACTIONS_MEMORY` | `1` | memoryRemember, memoryForget, memoryShow |
87
+ | `DISCOCLAW_DISCORD_ACTIONS_DEFER` | `1` | defer |
88
+ | _(config — always on)_ | — | modelSet, modelShow |
89
+
90
+ Notes:
91
+ - `reactionPrompt` is gated by the MESSAGING flag — it is registered via `REACTION_PROMPT_ACTION_TYPES` only when `flags.messaging` is true (`src/discord/actions.ts:113`).
92
+ - Config actions (`modelSet`, `modelShow`) have no separate env flag. They are always enabled when the master switch is on, hardcoded in `src/index.ts`.
93
+
94
+ Auto-follow-up: When query actions (channelList, channelInfo, threadListArchived, forumTagList, readMessages, fetchMessage, listPins, memberInfo, roleInfo, searchMessages, eventList, taskList, taskShow, cronList, cronShow, planList, planShow, memoryShow, modelShow, forgeStatus) succeed, DiscoClaw automatically re-invokes Claude with the results. This allows Claude to reason about query results without requiring the user to send a follow-up message. Controlled by `DISCOCLAW_ACTION_FOLLOWUP_DEPTH` (default `3`, `0` disables). Mutation-only responses do not trigger follow-ups. Trivially short follow-up responses (<50 chars with no actions) are suppressed.
84
95
 
85
96
  Requirements:
86
97
  - The bot needs appropriate permissions in the server (Manage Channels, Manage Roles, Moderate Members, etc.) depending on the actions used. These are server-level role permissions, not Developer Portal settings.
87
98
  - Only works in guild channels (not DMs).
88
- - Master switch defaults to off (`0`). Only allowlisted users can trigger actions.
99
+ - Master switch defaults to on (`1`). Only allowlisted users can trigger actions.
89
100
  - Destructive actions (delete, kick, ban, timeout) prompt Claude to confirm with the user first.
90
101
  - If actions fail with "Missing Permissions", the bot's role lacks the required permission.
91
102
 
@@ -126,7 +137,6 @@ DiscoClaw includes a task tracker backed by in-process `TaskStore` data and sync
126
137
  - Data model/store: `src/tasks/types.ts`, `src/tasks/store.ts`
127
138
  - Discord task action path: `src/tasks/task-action-executor.ts` (dispatch), `src/tasks/task-action-mutations.ts` (create/update/close), `src/tasks/task-action-thread-sync.ts` (thread lifecycle helpers), `src/tasks/task-action-mutation-helpers.ts` (shared mutation helpers), `src/tasks/task-action-read-ops.ts` (show/list/sync/reload), `src/tasks/task-action-contract.ts` (request types)
128
139
  - Canonical sync modules: `src/tasks/task-sync-engine.ts`, `src/tasks/task-sync-pipeline.ts` (facade), `src/tasks/task-sync-apply-plan.ts`, `src/tasks/task-sync-reconcile-plan.ts`, `src/tasks/task-sync-apply-types.ts`, `src/tasks/task-sync-phase-apply.ts`, `src/tasks/task-sync-reconcile.ts`, `src/tasks/sync-coordinator.ts`, `src/tasks/sync-coordinator-metrics.ts`, `src/tasks/sync-coordinator-retries.ts`, `src/tasks/thread-helpers.ts`, `src/tasks/thread-forum-ops.ts`, `src/tasks/thread-lifecycle-ops.ts`, `src/tasks/thread-ops.ts` (facade), `src/tasks/tag-map.ts`
129
- - Runtime shim status: `src/beads/*` compatibility modules removed in hard-cut phase
130
140
 
131
141
  Auto-sync is event-driven from the in-process store and runs a startup reconciliation pass.
132
142
  Auto-triggered syncs are silent; explicit `taskSync` can post status output.
@@ -7,6 +7,7 @@
7
7
  Sources to check:
8
8
  - **Anthropic:** https://platform.claude.com/docs/en/about-claude/models/overview
9
9
  - **OpenAI/Codex:** https://developers.openai.com/codex/models/
10
+ - **Google Gemini:** https://ai.google.dev/gemini-api/docs/models
10
11
  - **Claude Code CLI shorthand:** `sonnet`, `opus`, `haiku` resolve to the latest version of each model family
11
12
 
12
13
  Current model IDs (as of 2026-02-17):
@@ -18,6 +19,10 @@ Current model IDs (as of 2026-02-17):
18
19
  | OpenAI | GPT-5.3-Codex | `gpt-5.3-codex` |
19
20
  | OpenAI | GPT-5.3-Codex-Spark | `gpt-5.3-codex-spark` |
20
21
  | OpenAI | GPT-5-Codex-Mini | `gpt-5-codex-mini` |
22
+ | Google | Gemini 2.5 Pro | `gemini-2.5-pro` |
23
+ | Google | Gemini 2.5 Flash | `gemini-2.5-flash` |
24
+
25
+ **OpenRouter model IDs** use provider-namespaced format: `anthropic/claude-sonnet-4`, `openai/gpt-4o`, etc. Always check the OpenRouter model list for current IDs — do not guess.
21
26
 
22
27
  ## Runtime Adapter Interface
23
28
  - The orchestrator consumes a provider-agnostic event stream (`EngineEvent`) from any adapter.
@@ -41,6 +46,7 @@ The factory provides: subprocess tracking, process pool, stall detection, sessio
41
46
  |----------|------|------------|-------|
42
47
  | Claude Code | `strategies/claude-strategy.ts` | process-pool | Default JSONL parsing, image support |
43
48
  | Codex CLI | `strategies/codex-strategy.ts` | session-resume | Custom JSONL (thread.started, item.completed), error sanitization; reasoning items surface in the Discord preview during streaming but are excluded from the final reply |
49
+ | Gemini CLI | `strategies/gemini-strategy.ts` | none (Phase 1) | Text-only output mode; no sessions; stdin fallback for large prompts |
44
50
  | Template | `strategies/template-strategy.ts` | — | Commented starting point for new models |
45
51
 
46
52
  Thin wrappers (`claude-code-cli.ts`, `codex-cli.ts`) map legacy opts and re-export for backward compatibility. Shared utilities live in `cli-shared.ts` and `cli-output-parsers.ts`. Strategy types are in `cli-strategy.ts`.
@@ -70,6 +76,49 @@ Shutdown: `killAllSubprocesses()` from `cli-adapter.ts` kills all tracked subpro
70
76
  - `CLAUDE_OUTPUT_FORMAT=stream-json` (preferred; DiscoClaw parses JSONL and streams text)
71
77
  - `CLAUDE_OUTPUT_FORMAT=text` (fallback if your local CLI doesn't support stream-json)
72
78
 
79
+ ## Gemini CLI Runtime
80
+
81
+ - Adapter: `src/runtime/gemini-cli.ts` (thin wrapper around `cli-adapter.ts` + `strategies/gemini-strategy.ts`)
82
+ - Invocation shape:
83
+ ```
84
+ gemini --model <id> -- <prompt>
85
+ ```
86
+ For large prompts that exceed arg length limits, the prompt is passed via stdin instead.
87
+ - Auth: the Gemini CLI binary handles its own authentication — either OAuth (interactive login) or `GEMINI_API_KEY` env var. DiscoClaw does not manage credentials directly.
88
+ - Env vars:
89
+ | Var | Default | Purpose |
90
+ |-----|---------|---------|
91
+ | `GEMINI_BIN` | `gemini` | Path to the Gemini CLI binary |
92
+ | `GEMINI_MODEL` | `gemini-2.5-pro` | Default model ID |
93
+ - Model tier mapping:
94
+ | Tier | Model |
95
+ |------|-------|
96
+ | `fast` | `gemini-2.5-flash` |
97
+ | `capable` | `gemini-2.5-pro` |
98
+ - Capabilities (Phase 1):
99
+ - `streaming_text` only
100
+ - No sessions / multi-turn (each invocation is independent)
101
+ - No JSONL streaming — output is plain text
102
+ - No tool execution, no fs tools
103
+ - No image input/output support
104
+
105
+ ## OpenRouter Adapter
106
+
107
+ - Implementation: reuses `src/runtime/openai-compat.ts` with `id: 'openrouter'` — no separate adapter file needed.
108
+ - Conditional registration: only registered in the runtime registry when `OPENROUTER_API_KEY` is set.
109
+ - Env vars:
110
+ | Var | Default | Purpose |
111
+ |-----|---------|---------|
112
+ | `OPENROUTER_API_KEY` | *(required)* | API key; also gates registration |
113
+ | `OPENROUTER_BASE_URL` | `https://openrouter.ai/api/v1` | OpenRouter API base URL |
114
+ | `OPENROUTER_MODEL` | `anthropic/claude-sonnet-4` | Default model (provider-namespaced) |
115
+ - Model naming: OpenRouter uses provider-namespaced IDs — e.g. `anthropic/claude-sonnet-4`, `openai/gpt-4o`, `google/gemini-2.5-pro`. Never use bare model names.
116
+ - Capabilities:
117
+ - `streaming_text` only
118
+ - No tool execution
119
+ - No sessions / multi-turn
120
+ - Health system: credential presence checked via `checkOpenRouterKey` — returns `skip` if key is missing, `fail` if key is present but invalid/expired.
121
+
73
122
  ## Tool Surface
74
123
  - Default tools: `Bash, Read, Write, Edit, Glob, Grep, WebSearch, WebFetch` (8 tools).
75
124
  - `Glob` + `Grep` are purpose-built for file search — faster than `find`/`grep` via Bash.
@@ -77,7 +126,8 @@ Shutdown: `killAllSubprocesses()` from `cli-adapter.ts` kills all tracked subpro
77
126
  - Non-Claude adapters use a **capability gate** (`tools_fs`) to determine tool access:
78
127
  - Codex CLI adapter: declares `tools_fs` — receives read-only tools (Read, Glob, Grep) in auditor role.
79
128
  - OpenAI HTTP adapter: text-only (`streaming_text` only) — no tool execution.
80
- - Future adapters (Gemini, etc.): declare capabilities as appropriate.
129
+ - Gemini CLI adapter: text-only (`streaming_text` only) no tool execution, no fs tools (Phase 1).
130
+ - OpenRouter adapter: text-only (`streaming_text` only) — no tool execution.
81
131
 
82
132
  ## Per-Workspace Permissions
83
133
  - `workspace/PERMISSIONS.json` controls the tool surface per workspace.
package/.context/tasks.md CHANGED
@@ -44,8 +44,6 @@ Canonical runtime sync implementation lives in `src/tasks/*`:
44
44
  - `src/tasks/thread-cache.ts`
45
45
  - `src/tasks/forum-guard.ts`
46
46
 
47
- Legacy runtime compatibility shims under `src/beads/` have been removed.
48
-
49
47
  Primary action trigger is `taskSync` via `src/tasks/task-action-executor.ts`.
50
48
 
51
49
  ## Auto-Tagging
@@ -69,3 +67,6 @@ Primary names:
69
67
  - `DISCOCLAW_TASKS_AUTO_TAG_MODEL`
70
68
  - `DISCOCLAW_TASKS_SYNC_SKIP_PHASE5`
71
69
  - `DISCOCLAW_TASKS_PREFIX`
70
+ - `DISCOCLAW_TASKS_SYNC_FAILURE_RETRY_ENABLED`
71
+ - `DISCOCLAW_TASKS_SYNC_FAILURE_RETRY_DELAY_MS`
72
+ - `DISCOCLAW_TASKS_SYNC_DEFERRED_RETRY_DELAY_MS`
package/.env.example.full CHANGED
@@ -83,11 +83,14 @@ DISCOCLAW_CRON_FORUM=
83
83
  #DISCOCLAW_DISCORD_ACTIONS_CRONS=1
84
84
  # Persistent stats directory (run counts, last run time, status).
85
85
  #DISCOCLAW_CRON_STATS_DIR=
86
- # AI auto-tagging for cron purpose + model tier (off until tags bootstrapped).
87
- #DISCOCLAW_CRON_AUTO_TAG=0
86
+ # AI auto-tagging for cron purpose + model tier.
87
+ #DISCOCLAW_CRON_AUTO_TAG=1
88
88
  #DISCOCLAW_CRON_AUTO_TAG_MODEL=fast
89
89
  # Runtime tag-map file (seeded from scripts/cron/cron-tag-map.json on first boot).
90
90
  #DISCOCLAW_CRON_TAG_MAP=
91
+ # Default timezone for cron schedules that don't specify one (IANA format, e.g. America/New_York).
92
+ # Falls back to the system timezone if unset or invalid.
93
+ #DEFAULT_TIMEZONE=
91
94
  # Set to 0 to disable cron entirely.
92
95
  #DISCOCLAW_CRON_ENABLED=1
93
96
 
@@ -127,11 +130,11 @@ DISCORD_GUILD_ID=
127
130
  #DISCOCLAW_DISCORD_ACTIONS=1
128
131
  # Per-category flags (only active when master switch is 1):
129
132
  #DISCOCLAW_DISCORD_ACTIONS_CHANNELS=1
130
- #DISCOCLAW_DISCORD_ACTIONS_MESSAGING=0
131
- #DISCOCLAW_DISCORD_ACTIONS_GUILD=0
133
+ #DISCOCLAW_DISCORD_ACTIONS_MESSAGING=1
134
+ #DISCOCLAW_DISCORD_ACTIONS_GUILD=1
132
135
  # Intentionally off — moderation actions require explicit opt-in.
133
136
  #DISCOCLAW_DISCORD_ACTIONS_MODERATION=0
134
- #DISCOCLAW_DISCORD_ACTIONS_POLLS=0
137
+ #DISCOCLAW_DISCORD_ACTIONS_POLLS=1
135
138
  #DISCOCLAW_DISCORD_ACTIONS_TASKS=1
136
139
  # Allow the AI to self-initiate forge runs (draft + audit loops) via action blocks.
137
140
  # Requires DISCOCLAW_FORGE_COMMANDS_ENABLED=1. Only one forge at a time. Default: 1.
@@ -143,7 +146,7 @@ DISCORD_GUILD_ID=
143
146
  # Requires DISCOCLAW_DURABLE_MEMORY_ENABLED=1. Default: 1.
144
147
  #DISCOCLAW_DISCORD_ACTIONS_MEMORY=1
145
148
  # Allow the AI to change status, activity, and nickname at runtime.
146
- #DISCOCLAW_DISCORD_ACTIONS_BOT_PROFILE=0
149
+ #DISCOCLAW_DISCORD_ACTIONS_BOT_PROFILE=1
147
150
 
148
151
  # Allow the AI to defer another invocation (e.g., "check on the forge run in 10 minutes").
149
152
  # The scheduler enforces DISCOCLAW_DISCORD_ACTIONS=1, respects the requesting message's channel
@@ -171,7 +174,7 @@ DISCOCLAW_DISCORD_ACTIONS_DEFER=1
171
174
  #DISCORD_AUTO_INDEX_CHANNEL_CONTEXT=1
172
175
  # Auto-join threads so the bot can respond inside them.
173
176
  # Note: private threads still require manually adding the bot.
174
- #DISCORD_AUTO_JOIN_THREADS=0
177
+ #DISCORD_AUTO_JOIN_THREADS=1
175
178
 
176
179
  # ----------------------------------------------------------
177
180
  # Reaction handler (trigger AI via emoji reactions)
@@ -195,17 +198,23 @@ DISCOCLAW_DISCORD_ACTIONS_DEFER=1
195
198
  #DISCOCLAW_SUMMARY_MODEL=fast
196
199
  #DISCOCLAW_SUMMARY_MAX_CHARS=2000
197
200
  #DISCOCLAW_SUMMARY_EVERY_N_TURNS=5
201
+ # Override storage directory for rolling summaries.
202
+ #DISCOCLAW_SUMMARY_DATA_DIR=
198
203
  # Durable per-user facts/preferences (manual via !memory commands).
199
204
  #DISCOCLAW_DURABLE_MEMORY_ENABLED=1
200
205
  #DISCOCLAW_DURABLE_INJECT_MAX_CHARS=2000
201
206
  #DISCOCLAW_DURABLE_MAX_ITEMS=200
202
207
  #DISCOCLAW_MEMORY_COMMANDS_ENABLED=1
208
+ # Override storage directory for durable memory.
209
+ #DISCOCLAW_DURABLE_DATA_DIR=
203
210
  # Cross-channel short-term memory (per-guild, per-user ring buffer).
204
211
  # Records recent exchanges across public channels for cross-channel awareness.
205
212
  #DISCOCLAW_SHORTTERM_MEMORY_ENABLED=0
206
213
  #DISCOCLAW_SHORTTERM_MAX_ENTRIES=20
207
214
  #DISCOCLAW_SHORTTERM_MAX_AGE_HOURS=6
208
215
  #DISCOCLAW_SHORTTERM_INJECT_MAX_CHARS=1000
216
+ # Override storage directory for short-term memory.
217
+ #DISCOCLAW_SHORTTERM_DATA_DIR=
209
218
  # Auto-extract notable facts from user messages into durable memory.
210
219
  # Runs after rolling summary generation. Default on.
211
220
  #DISCOCLAW_SUMMARY_TO_DURABLE_ENABLED=1
@@ -244,6 +253,8 @@ DISCOCLAW_DISCORD_ACTIONS_DEFER=1
244
253
  #DISCOCLAW_DATA_DIR=
245
254
  # Content directory override.
246
255
  #DISCOCLAW_CONTENT_DIR=
256
+ # Force re-create workspace/BOOTSTRAP.md from the template on next startup (set to 1; auto-clears after first boot).
257
+ #DISCOCLAW_FORCE_BOOTSTRAP=0
247
258
  # Enable per-session runtime isolation.
248
259
  #DISCOCLAW_RUNTIME_SESSIONS=1
249
260
  # Path to claude CLI binary (if not on $PATH).
@@ -281,12 +292,27 @@ DISCOCLAW_DISCORD_ACTIONS_DEFER=1
281
292
  #DC_RESTART_CMD=
282
293
  # Global cap on parallel runtime invocations across all Discord sessions (0 = unlimited).
283
294
  #DISCOCLAW_MAX_CONCURRENT_INVOCATIONS=3
295
+ # Max depth for chained action follow-ups (e.g. defer → action → response). 0 = disabled.
296
+ #DISCOCLAW_ACTION_FOLLOWUP_DEPTH=3
284
297
  # Timeout for runtime invocations (ms).
285
298
  #RUNTIME_TIMEOUT_MS=1800000
299
+ # Multi-turn mode: persistent subprocess per session, keeping session context across messages (default: 1).
300
+ #DISCOCLAW_MULTI_TURN=1
301
+ # Timeout (ms) before a multi-turn process is considered hung and restarted.
302
+ #DISCOCLAW_MULTI_TURN_HANG_TIMEOUT_MS=60000
303
+ # Idle timeout (ms) before an inactive multi-turn process is recycled.
304
+ #DISCOCLAW_MULTI_TURN_IDLE_TIMEOUT_MS=300000
305
+ # Maximum concurrent multi-turn processes (per bot instance).
306
+ #DISCOCLAW_MULTI_TURN_MAX_PROCESSES=5
307
+ # Enable session-ID scanning to detect and resume existing Claude sessions.
308
+ #DISCOCLAW_SESSION_SCANNING=1
309
+ # Parse tool-use events during streaming for better progress reporting and stall suppression.
310
+ #DISCOCLAW_TOOL_AWARE_STREAMING=1
286
311
  # Stream stall detection: kill one-shot process if no stdout/stderr for this long (ms). 0 = disabled.
287
312
  #DISCOCLAW_STREAM_STALL_TIMEOUT_MS=120000
313
+ # Progress stall timeout: alert after this many ms with no progress event (ms). 0 = disabled.
314
+ #DISCOCLAW_PROGRESS_STALL_TIMEOUT_MS=300000
288
315
  # Stream stall warning: show user-visible warning in Discord after this many ms of no events. 0 = disabled.
289
- # Enable DISCOCLAW_SESSION_SCANNING=1 for tool-aware stall suppression (warnings suppressed during tool execution).
290
316
  #DISCOCLAW_STREAM_STALL_WARNING_MS=60000
291
317
 
292
318
  # ----------------------------------------------------------
@@ -302,7 +328,7 @@ DISCOCLAW_DISCORD_ACTIONS_DEFER=1
302
328
  # Per-phase execution timeout (ms). Default: 1800000 (30 min).
303
329
  #PLAN_PHASE_TIMEOUT_MS=1800000
304
330
  # Max audit-fix attempts per phase before marking failed.
305
- #PLAN_PHASE_AUDIT_FIX_MAX=2
331
+ #PLAN_PHASE_AUDIT_FIX_MAX=3
306
332
  # Max draft-audit-revise loops before CAP_REACHED.
307
333
  #FORGE_MAX_AUDIT_ROUNDS=5
308
334
  # Model overrides for forge roles (fall back to RUNTIME_MODEL).
@@ -317,8 +343,11 @@ DISCOCLAW_DISCORD_ACTIONS_DEFER=1
317
343
  #FORGE_AUTO_IMPLEMENT=1
318
344
 
319
345
  # ----------------------------------------------------------
320
- # Multi-provider auditor
346
+ # Multi-provider runtime overrides
321
347
  # ----------------------------------------------------------
348
+ # Route the forge drafter to a non-Claude runtime.
349
+ # Valid values: "codex" (Codex CLI), "openai" (OpenAI-compatible HTTP API), "openrouter" (OpenRouter).
350
+ #FORGE_DRAFTER_RUNTIME=
322
351
  # Route the forge auditor to a non-Claude runtime.
323
352
  # Valid values: "codex" (Codex CLI), "openai" (OpenAI-compatible HTTP API), "openrouter" (OpenRouter).
324
353
  #FORGE_AUDITOR_RUNTIME=codex
@@ -32,6 +32,27 @@ export async function runCronSync(opts) {
32
32
  let namesUpdated = 0;
33
33
  let statusMessagesUpdated = 0;
34
34
  let orphansDetected = 0;
35
+ const asEditableCronThread = (value) => {
36
+ if (!value || typeof value !== 'object')
37
+ return null;
38
+ const t = value;
39
+ if (typeof t.id !== 'string' ||
40
+ typeof t.parentId !== 'string' ||
41
+ typeof t.name !== 'string' ||
42
+ typeof t.edit !== 'function' ||
43
+ typeof t.setName !== 'function') {
44
+ return null;
45
+ }
46
+ const appliedTags = Array.isArray(t.appliedTags) ? t.appliedTags.filter((id) => typeof id === 'string') : undefined;
47
+ return {
48
+ id: t.id,
49
+ parentId: t.parentId,
50
+ name: t.name,
51
+ appliedTags,
52
+ edit: t.edit,
53
+ setName: t.setName,
54
+ };
55
+ };
35
56
  // Get all active threads in the forum.
36
57
  let threads = new Map();
37
58
  try {
@@ -79,7 +100,8 @@ export async function runCronSync(opts) {
79
100
  }
80
101
  // Apply tags to Discord thread.
81
102
  const thread = threads.get(fullJob.threadId);
82
- if (thread) {
103
+ const editableThread = asEditableCronThread(thread);
104
+ if (editableThread) {
83
105
  const desiredPurposeTags = updates.purposeTags ?? record.purposeTags;
84
106
  const desiredCadence = updates.cadence ?? record.cadence;
85
107
  const allTags = [
@@ -91,15 +113,13 @@ export async function runCronSync(opts) {
91
113
  .map((t) => tagMap[t])
92
114
  .filter((id) => Boolean(id));
93
115
  const uniqueTagIds = [...new Set(desiredTagIds)].slice(0, 5);
94
- const currentTagIds = Array.isArray(thread.appliedTags)
95
- ? thread.appliedTags.filter((id) => typeof id === 'string')
96
- : [];
116
+ const currentTagIds = editableThread.appliedTags ?? [];
97
117
  const desiredSet = new Set(uniqueTagIds);
98
118
  const tagsOutOfSync = currentTagIds.length !== uniqueTagIds.length
99
119
  || currentTagIds.some((id) => !desiredSet.has(id));
100
120
  if (tagsOutOfSync) {
101
121
  try {
102
- await thread.edit({ appliedTags: uniqueTagIds });
122
+ await editableThread.edit({ appliedTags: uniqueTagIds });
103
123
  tagsApplied++;
104
124
  }
105
125
  catch (err) {
@@ -122,11 +142,12 @@ export async function runCronSync(opts) {
122
142
  const cadence = record?.cadence ?? null;
123
143
  const expectedName = buildCronThreadName(fullJob.name, cadence);
124
144
  const thread = threads.get(fullJob.threadId);
125
- if (thread && thread.name !== expectedName) {
145
+ const editableThread = asEditableCronThread(thread);
146
+ if (editableThread && editableThread.name !== expectedName) {
126
147
  try {
127
- await thread.setName(expectedName);
148
+ await editableThread.setName(expectedName);
128
149
  namesUpdated++;
129
- log?.info({ threadId: fullJob.threadId, oldName: thread.name, newName: expectedName }, 'cron-sync:phase2 name updated');
150
+ log?.info({ threadId: fullJob.threadId, oldName: editableThread.name, newName: expectedName }, 'cron-sync:phase2 name updated');
130
151
  }
131
152
  catch (err) {
132
153
  log?.warn({ err, threadId: fullJob.threadId }, 'cron-sync:phase2 name update failed');
@@ -153,11 +174,14 @@ export async function runCronSync(opts) {
153
174
  }
154
175
  // Phase 4: Orphan detection (non-destructive, log only).
155
176
  for (const thread of threads.values()) {
156
- if (thread.parentId !== forumId)
177
+ const editableThread = asEditableCronThread(thread);
178
+ if (!editableThread)
179
+ continue;
180
+ if (editableThread.parentId !== forumId)
157
181
  continue;
158
- if (!jobThreadIds.has(thread.id)) {
182
+ if (!jobThreadIds.has(editableThread.id)) {
159
183
  orphansDetected++;
160
- log?.warn({ threadId: thread.id, name: thread.name }, 'cron-sync:phase4 orphan thread (no registered job)');
184
+ log?.warn({ threadId: editableThread.id, name: editableThread.name }, 'cron-sync:phase4 orphan thread (no registered job)');
161
185
  }
162
186
  }
163
187
  log?.info({ tagsApplied, namesUpdated, statusMessagesUpdated, orphansDetected }, 'cron-sync: complete');