@inetafrica/open-claudia 2.15.0 → 3.0.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.
Files changed (151) hide show
  1. package/.env.example +30 -4
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +87 -66
  4. package/bin/agent.js +44 -18
  5. package/bin/cli.js +30 -28
  6. package/bin/dream.js +5 -11
  7. package/bin/loopback-client.js +29 -5
  8. package/bin/schedule.js +19 -5
  9. package/bin/tool.js +23 -5
  10. package/bot-agent.js +5 -2350
  11. package/bot.js +66 -3
  12. package/channels/telegram/adapter.js +6 -1
  13. package/channels/voice/adapter.js +1 -0
  14. package/channels/voice/manage.js +43 -5
  15. package/config-dir.js +3 -11
  16. package/core/actions.js +149 -40
  17. package/core/approvals.js +136 -29
  18. package/core/auth-flow.js +103 -19
  19. package/core/config-dir.js +19 -0
  20. package/core/config.js +115 -69
  21. package/core/doctor.js +111 -57
  22. package/core/dream.js +219 -62
  23. package/core/enforcer.js +0 -0
  24. package/core/entities.js +2 -1
  25. package/core/fsutil.js +21 -4
  26. package/core/handlers.js +503 -217
  27. package/core/ideas.js +2 -1
  28. package/core/jobs.js +589 -72
  29. package/core/lessons.js +3 -2
  30. package/core/loopback.js +42 -6
  31. package/core/memory-mutation-queue.js +241 -0
  32. package/core/migration-backup.js +1060 -0
  33. package/core/pack-review.js +295 -88
  34. package/core/packs.js +4 -3
  35. package/core/persona.js +2 -1
  36. package/core/process-tree.js +19 -2
  37. package/core/provider-migration.js +39 -0
  38. package/core/provider-status.js +80 -0
  39. package/core/providers/claude-events.js +117 -0
  40. package/core/providers/claude-hook.js +114 -0
  41. package/core/providers/claude.js +296 -0
  42. package/core/providers/codex-events.js +121 -0
  43. package/core/providers/codex-hook.js +280 -0
  44. package/core/providers/codex.js +286 -0
  45. package/core/providers/contract.js +153 -0
  46. package/core/providers/env.js +148 -0
  47. package/core/providers/events.js +170 -0
  48. package/core/providers/hook-command.js +22 -0
  49. package/core/providers/index.js +240 -0
  50. package/core/providers/utility-policy.js +269 -0
  51. package/core/recall/classic.js +2 -2
  52. package/core/recall/discoverer.js +71 -22
  53. package/core/recall/metrics.js +27 -1
  54. package/core/recall/tuning.js +4 -1
  55. package/core/recall/warm-walker.js +151 -108
  56. package/core/recall-filter.js +86 -61
  57. package/core/router.js +79 -7
  58. package/core/run-context.js +185 -0
  59. package/core/runner.js +1414 -1290
  60. package/core/scheduler.js +515 -210
  61. package/core/side-chat.js +346 -0
  62. package/core/state.js +1084 -97
  63. package/core/subagent.js +72 -98
  64. package/core/system-prompt.js +462 -279
  65. package/core/tool-guard.js +73 -39
  66. package/core/tools.js +22 -5
  67. package/core/transcripts.js +30 -1
  68. package/core/usage-log.js +19 -4
  69. package/core/utility-agent.js +654 -0
  70. package/core/web-auth.js +29 -13
  71. package/docs/CHANNEL_DESIGN.md +181 -0
  72. package/docs/MULTI_CHANNEL_PLAN.md +254 -0
  73. package/docs/PROVIDER_MIGRATION.md +67 -0
  74. package/health.js +50 -39
  75. package/package.json +48 -4
  76. package/setup.js +198 -38
  77. package/soul.md +39 -0
  78. package/test-ability-extraction.js +5 -0
  79. package/test-approval-async.js +63 -4
  80. package/test-cursor-removal.js +334 -0
  81. package/test-enforcer.js +70 -27
  82. package/test-fixtures/current-provider-parity.json +132 -0
  83. package/test-fixtures/fake-agent-cli.js +477 -0
  84. package/test-fixtures/migrations/claude-only/jobs.json +3 -0
  85. package/test-fixtures/migrations/claude-only/sessions.json +5 -0
  86. package/test-fixtures/migrations/claude-only/state.json +5 -0
  87. package/test-fixtures/migrations/cursor-selected/crons.json +3 -0
  88. package/test-fixtures/migrations/cursor-selected/jobs.json +3 -0
  89. package/test-fixtures/migrations/cursor-selected/sessions.json +5 -0
  90. package/test-fixtures/migrations/cursor-selected/state.json +6 -0
  91. package/test-fixtures/migrations/dual-provider/jobs.json +3 -0
  92. package/test-fixtures/migrations/dual-provider/sessions.json +7 -0
  93. package/test-fixtures/migrations/dual-provider/state.json +10 -0
  94. package/test-fixtures/migrations/malformed-partial/jobs.json +1 -0
  95. package/test-fixtures/migrations/malformed-partial/sessions.json +1 -0
  96. package/test-fixtures/migrations/malformed-partial/sessions.json.bak +3 -0
  97. package/test-fixtures/migrations/malformed-partial/state.json +1 -0
  98. package/test-fixtures/migrations/malformed-partial/state.json.bak +5 -0
  99. package/test-fixtures/migrations/missing-project/jobs.json +3 -0
  100. package/test-fixtures/migrations/missing-project/sessions.json +3 -0
  101. package/test-fixtures/migrations/missing-project/state.json +4 -0
  102. package/test-fixtures/migrations/multi-user/jobs.json +4 -0
  103. package/test-fixtures/migrations/multi-user/sessions.json +4 -0
  104. package/test-fixtures/migrations/multi-user/state.json +6 -0
  105. package/test-fixtures/provider-event-samples.json +17 -0
  106. package/test-learning-e2e.js +5 -0
  107. package/test-memory-mutation-queue.js +191 -0
  108. package/test-provider-boot-matrix.js +399 -0
  109. package/test-provider-bootstrap.js +158 -0
  110. package/test-provider-capabilities.js +232 -0
  111. package/test-provider-claude.js +206 -0
  112. package/test-provider-codex.js +228 -0
  113. package/test-provider-compaction.js +371 -0
  114. package/test-provider-core-prompt.js +264 -0
  115. package/test-provider-coupling-audit.js +90 -0
  116. package/test-provider-dream.js +312 -0
  117. package/test-provider-enforcer.js +252 -0
  118. package/test-provider-env-isolation.js +150 -0
  119. package/test-provider-events.js +141 -0
  120. package/test-provider-fixture.js +332 -0
  121. package/test-provider-gateway.js +508 -0
  122. package/test-provider-language.js +89 -0
  123. package/test-provider-migration-backup.js +424 -0
  124. package/test-provider-pack-review.js +436 -0
  125. package/test-provider-parity-e2e.js +537 -0
  126. package/test-provider-prompt-parity.js +89 -0
  127. package/test-provider-recall.js +271 -0
  128. package/test-provider-registry.js +251 -0
  129. package/test-provider-resume-parity.js +41 -0
  130. package/test-provider-run-lifecycle.js +349 -0
  131. package/test-provider-runner.js +271 -0
  132. package/test-provider-scheduler.js +689 -0
  133. package/test-provider-session-commands.js +185 -0
  134. package/test-provider-session-history.js +205 -0
  135. package/test-provider-side-chat.js +337 -0
  136. package/test-provider-state-migration.js +316 -0
  137. package/test-provider-stream-decoder.js +69 -0
  138. package/test-provider-subagent.js +228 -0
  139. package/test-provider-tool-hooks.js +360 -0
  140. package/test-recall-discoverer.js +1 -0
  141. package/test-recall-engine.js +3 -3
  142. package/test-recall-evolution.js +18 -0
  143. package/test-runner-watchdog-static.js +16 -8
  144. package/test-single-runtime.js +35 -0
  145. package/test-telegram-poll-recovery.js +56 -0
  146. package/test-tool-guard.js +56 -0
  147. package/test-tools.js +19 -0
  148. package/test-unified-identity.js +3 -1
  149. package/test-usage-accounting.js +92 -20
  150. package/test-utility-provider-policy.js +486 -0
  151. package/web.js +130 -35
package/.env.example CHANGED
@@ -15,11 +15,35 @@ KAZEE_OWNER_USER_ID=
15
15
  KAZEE_DEBUG_EVENTS=
16
16
 
17
17
  WORKSPACE=/path/to/your/workspace
18
- CLAUDE_PATH=/path/to/claude
18
+ # Both provider paths are optional. Leave blank to discover the CLI on PATH.
19
+ CLAUDE_PATH=
20
+ CODEX_PATH=
21
+ # Optional explicit foreground/global default. When blank, Open Claudia picks
22
+ # the first available compatible provider deterministically (Claude, then Codex).
23
+ DEFAULT_PROVIDER=
19
24
  # Default Claude Code model when users haven't selected one with /model.
20
25
  CLAUDE_MODEL=claude-fable-5
21
- CURSOR_PATH=
22
- CODEX_PATH=
26
+ CODEX_MODEL=
27
+ # Background intelligence uses the active provider by default. Per-purpose
28
+ # overrides and fallbacks are opt-in; fallbacks never retarget foreground turns.
29
+ UTILITY_PROVIDER=active
30
+ PROVIDER_FALLBACKS=
31
+ DREAM_PROVIDER=
32
+ MEMORY_PROVIDER=
33
+ RECALL_PROVIDER=
34
+ ENFORCER_PROVIDER=
35
+ SUBAGENT_PROVIDER=
36
+ # Exact per-provider model overrides for utility work (optional).
37
+ DREAM_MODEL_CLAUDE=
38
+ DREAM_MODEL_CODEX=
39
+ PACK_REVIEW_MODEL_CLAUDE=
40
+ PACK_REVIEW_MODEL_CODEX=
41
+ RECALL_MODEL_CLAUDE=
42
+ RECALL_MODEL_CODEX=
43
+ ENFORCER_MODEL_CLAUDE=
44
+ ENFORCER_MODEL_CODEX=
45
+ SUBAGENT_MODEL_CLAUDE=
46
+ SUBAGENT_MODEL_CODEX=
23
47
  AUTO_COMPACT_TOKENS=280000
24
48
  # Inactivity watchdog: kill a heavy turn's child if it emits no output for
25
49
  # this many ms, so a hung tool call can't wedge the bot. Default 600000 (10min).
@@ -32,8 +56,10 @@ USAGE_ALERT_COOLDOWN_MS=1800000
32
56
  MEMORY_RECALL_MAX_CHARS=9000
33
57
  # Default recall engine when a chat hasn't picked one with /engine: classic | discoverer
34
58
  RECALL_ENGINE=classic
35
- # Dream model tier: low (haiku) | medium (sonnet) | high (opus, default). DREAM_MODEL overrides.
59
+ # Dream utility tier is mapped by the selected provider: low | medium | high (default).
36
60
  DREAM_TIER=high
61
+ # Independent external-person guardrail fails closed. Every total provider
62
+ # failure holds the action for owner approval.
37
63
  PROJECT_TRANSCRIPTS=true
38
64
  TRANSCRIPT_MAX_ENTRY_CHARS=12000
39
65
  TRANSCRIPTS_DIR=
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.0.0 — provider parity and Cursor removal
4
+
5
+ - **The runtime is now a provider-agnostic coding-agent harness.** Claude Code and OpenAI Codex implement one registry contract for prompts, immutable run admission, normalized events, native sessions, capability reporting, utility work, and pre-tool safety policy. Setup, web configuration, status, and doctor work with either, both, or neither provider installed; only model turns require a compatible authenticated provider.
6
+ - **Active Cursor Agent runtime support has been removed.** Open Claudia now runs model turns through the registered Claude Code and OpenAI Codex providers only. The command, model/backend buttons, executable discovery, auth/doctor branches, configuration key, active session pointer, and runtime parser/invocation path are gone. Buttons from an older message return a removal notice and direct the user to `/backend`; they never switch to another provider silently.
7
+ - **Cursor data is archived, not rewritten.** The verified provider-migration snapshot retains the byte-for-byte original state, session, job, and legacy cron files. Cursor selections, settings, session pointers/history, and pinned jobs move only into non-selectable/disabled archive records; no native session ID or model is reassigned to Claude or Codex.
8
+ - **Migration activation is proven, not asserted.** Startup snapshots the allowlisted originals, migrates state, sessions, and jobs before the scheduler/router/adapters load, and records each live output's schema, size, path, and SHA-256. A partial, stale, or legacy output cannot satisfy provider-removal readiness; rerunning startup safely completes an interrupted component.
9
+ - **Rollback requires the matching older runtime.** Stop every bot/scheduler process, validate and restore the snapshot described in `docs/PROVIDER_MIGRATION.md`, then restart a pre-removal Open Claudia release that still understands the restored schema. Do not run old and new schedulers against the same restored files.
10
+ - **Authenticated release validation passed locally on 2026-07-10.** Claude Code 2.1.158 and Codex CLI 0.144.0 each passed a fresh turn, resumed turn, read-only plan, harmless tool command, utility JSON output, and prompt-context sentinel through the Open Claudia adapters. Claude-only and Codex-only startup/status probes also passed with the opposite executable absent. These were isolated existing-auth checks only: no login, credential change, publish, deploy, or production action was performed.
11
+ - **Real-CLI validation tightened three compatibility edges.** Claude `structured_output` is now the authoritative terminal value for schema-constrained utility work even when the CLI also streams presentation text. Codex hook trust uses a strict inline `projects` table accepted by the current CLI while user configuration remains ignored, and the supported model tiers no longer select deprecated `o4-mini` for utility work.
12
+ - **Breaking-release recommendation accepted — released as `v3.0.0` (2026-07-10).** Removing an active provider and changing configuration/session schemas are breaking changes; package metadata and the git tag are v3.0.0.
13
+ - **Post-review hardening (2026-07-10).** stdin EPIPE from a fast-exiting provider child no longer crashes the bot — it surfaces as a stderr diagnostic and the exit code decides the run. Crons/wakeups archived by the provider migration are announced once at boot and listed in `open-claudia cron-list` and `/cron`. Healthy provider status is cached for 30 seconds so per-message admission stops respawning CLI probes; disappearance detection stays instant and unhealthy states always re-probe. `/doctor` now runs the real-binary Codex tool-hook enforcement probe behind an explicit opt-in, so boot/health/setup never spawn a model turn. Merged main's Telegram polling recovery fix.
14
+
3
15
  ## v2.15.0
4
16
  - **Production-hardening pass (P0). Two root causes, closed at the source.** A deep-dive audit traced the reliability and safety gaps to two patterns: (1) **overloaded return contracts** where a falsy value meant both "no id" and "failed", so callers guessed — the exact shape behind v2.14.8's Kazee double-send; and (2) **unsafe shared state under concurrency**, amplified because the owner's Telegram + Kazee turns now share ONE in-memory state object *and* load-modify-write the same JSON files under unified identity. This release fixes both, plus the credential-exposure and crash-durability gaps found alongside them. No new deps, no new required env, no schema change.
5
17
  - **One explicit delivery contract, so no caller guesses.** Adapters historically overloaded `send()`'s return: a message id on success, a bare `true` for an idless success (server fast-ack), or a falsy value on failure. `core/io.send()` now collapses every adapter's raw return through `normalizeSendResult` into `{ ok, messageId, editable }` — `messageId` is always a string or null, so `typeof`/edit-target checks are uniform across transports. The runner's final-delivery guard is now `if (!sent.ok)` (an idless success no longer reads as failure → no phantom re-send), and the streaming-preview path keeps the real string id when editable or a truthy sentinel otherwise, so `canEditStatus()` still declines to edit an unaddressable bubble. The loopback approval-prompt send routes through the same normalizer. New `test-delivery-contract.js`.
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # Open Claudia
2
2
 
3
- Your always-on AI coding assistant — Claude Code, Cursor Agent, and OpenAI Codex via Telegram or Kazee Chat.
3
+ Your always-on, provider-agnostic coding-agent harness — Claude Code and OpenAI Codex via Telegram or Kazee Chat.
4
4
 
5
- Send text, voice notes, screenshots, and files from your phone. Your chosen AI agent works on your projects, remembers what it learned, and reports back.
5
+ Send text, voice notes, screenshots, and files from your phone. Open Claudia runs the selected coding-agent provider on your projects, remembers what it learned, and reports back.
6
6
 
7
7
  ## Features
8
8
 
9
- ### Channels & backends
9
+ ### Channels & providers
10
10
  - **Multi-channel** — run the same bot on Telegram, Kazee Chat, or both at once (`CHANNELS=telegram,kazee`). Each channel renders keyboards, files, voice notes, and edits natively
11
- - **Multi-backend** — switch between Claude Code, Cursor Agent, and OpenAI Codex on the fly (`/claude`, `/cursor`, `/codex`); each keeps its own session state
12
- - **Multi-user / team mode** — one bot serves multiple authorized users in parallel, each with their own conversations, settings, model, backend, and usage counters
11
+ - **Multi-provider** — switch between Claude Code and OpenAI Codex on the fly (`/claude`, `/codex`); each keeps its own project-scoped session state
12
+ - **Multi-user / team mode** — one bot serves multiple authorized users in parallel, each with their own conversations, settings, model, provider, and usage counters
13
13
  - **Multi-project sessions** — switch between workspace projects; per-project conversation history auto-resumes
14
14
 
15
15
  ### Memory & long-term context
@@ -44,7 +44,7 @@ Send text, voice notes, screenshots, and files from your phone. Your chosen AI a
44
44
  ### Operations
45
45
  - **Encrypted vault** — store API keys and credentials securely
46
46
  - **Customizable soul** — define your assistant's personality and knowledge
47
- - **Model switching, plan mode, effort levels, budgets** full control from chat
47
+ - **Capability-driven settings** model, effort, read-only mode, budget, and worktree controls appear only when the selected provider supports them
48
48
  - **Token economy** — byte-stable system prompt for maximum Anthropic prompt-cache hits; dynamic state rides each message instead
49
49
  - **Web UI** — optional browser UI for setup and config (`open-claudia start --web`)
50
50
  - **Auto-updates** — checks npm every 5 minutes, upgrade with `/upgrade`
@@ -56,16 +56,16 @@ Send text, voice notes, screenshots, and files from your phone. Your chosen AI a
56
56
 
57
57
  - [Node.js](https://nodejs.org/) 18+ (22.5+ recommended — its built-in SQLite powers transcript/pack/entity search; on older Node those features quietly disable themselves)
58
58
  - A Telegram bot token (from [@BotFather](https://t.me/BotFather)) and/or a Kazee Chat bot
59
- - At least one authenticated CLI backend on the host machine (see below)
59
+ - At least one authenticated coding-agent provider on the host machine for model turns (setup, doctor, status, and web configuration also work with none installed)
60
60
  - (Optional) [whisper.cpp](https://github.com/ggerganov/whisper.cpp) + ffmpeg for voice notes
61
61
 
62
62
  ## Quick Start
63
63
 
64
- ### 1. Install and authenticate the CLI backends
64
+ ### 1. Install and authenticate coding-agent providers
65
65
 
66
- You need **at least one** of these authenticated on the machine where Open Claudia will run.
66
+ For model-bearing turns, authenticate **at least one** of these on the machine where Open Claudia will run.
67
67
 
68
- **Claude Code** (required):
68
+ **Claude Code** (optional — at least one provider is required for model turns):
69
69
 
70
70
  ```bash
71
71
  npm install -g @anthropic-ai/claude-code
@@ -73,16 +73,7 @@ claude # Opens browser to log in
73
73
  claude --version # Verify it works
74
74
  ```
75
75
 
76
- **Cursor Agent** (optional — enables `/cursor` backend):
77
-
78
- ```bash
79
- # Install from Cursor IDE: Settings > General > Agent CLI
80
- # Or download from https://docs.cursor.com/agent
81
- agent login # Opens browser to authenticate
82
- agent status # Verify: should show your email and plan
83
- ```
84
-
85
- **OpenAI Codex** (optional — enables `/codex` backend):
76
+ **OpenAI Codex** (optional — at least one provider is required for model turns):
86
77
 
87
78
  ```bash
88
79
  npm install -g @openai/codex
@@ -92,7 +83,7 @@ codex login # Opens browser to authenticate
92
83
  codex --version # Verify it works
93
84
  ```
94
85
 
95
- Docker images include the Codex CLI. Direct npm installs still need optional backend CLIs installed on the host.
86
+ Docker images include the Codex CLI. Direct npm installs still need optional provider CLIs installed on the host.
96
87
 
97
88
  > **Important**: Claude Code can use macOS Keychain when you log in interactively, but a launchd/background bot may not be able to read that Keychain session. Open Claudia supports `CLAUDE_CODE_OAUTH_TOKEN` for non-interactive Claude runs. Prefer `/setup_token` then `/use_oauth_token` if chat shows Claude auth/keychain errors.
98
89
 
@@ -110,8 +101,8 @@ open-claudia setup
110
101
 
111
102
  The setup wizard will:
112
103
 
113
- 1. Detect Claude CLI, Cursor Agent CLI, ffmpeg, and whisper on your system
114
- 2. Verify Claude is authenticated
104
+ 1. Detect the Claude and Codex CLIs, plus ffmpeg and whisper, on your system
105
+ 2. Report provider authentication status
115
106
  3. Ask for your Telegram bot token and verify it
116
107
  4. Generate a verification code — send it to your bot to prove your identity
117
108
  5. Set your workspace path (default: `~/.open-claudia/Workspace`)
@@ -142,16 +133,15 @@ If installed as a background service, the bot starts automatically on login and
142
133
 
143
134
  ## Chat Commands
144
135
 
145
- ### Backend switching
136
+ ### Provider switching
146
137
 
147
138
  | Command | Description |
148
139
  |---------|-------------|
149
- | `/claude` | Switch to Claude Code backend |
150
- | `/cursor` | Switch to Cursor Agent backend |
151
- | `/codex` | Switch to OpenAI Codex backend |
152
- | `/backend` | Show current backend with picker |
140
+ | `/claude` | Switch to the Claude Code provider |
141
+ | `/codex` | Switch to the OpenAI Codex provider |
142
+ | `/backend` | Show the current provider with picker |
153
143
 
154
- Each backend keeps its own persistent session. Switching doesn't lose your place — you can go back and forth freely.
144
+ Each provider keeps its own persistent project session. Switching doesn't lose your place — you can go back and forth freely.
155
145
 
156
146
  ### Session management
157
147
 
@@ -171,14 +161,14 @@ When you select a project, the last conversation is automatically resumed. Tap "
171
161
 
172
162
  | Command | Description |
173
163
  |---------|-------------|
174
- | `/model [<model>]` | Switch model (opus / sonnet / haiku for Claude; any model flag for Cursor/Codex) |
175
- | `/effort [low\|medium\|high\|max]` | Set effort level |
176
- | `/budget [$N]` | Set max spend for next task Claude only |
177
- | `/plan` | Toggle plan mode`--permission-mode plan` (Claude) / `--mode plan` (Cursor) |
178
- | `/ask` | Toggle ask mode — read-only Q&A, no edits (Cursor Agent only) |
179
- | `/worktree` | Toggle isolated git branch |
164
+ | `/model [<model>]` | Switch model for the selected provider |
165
+ | `/effort [<value>]` | Set a provider-supported effort level |
166
+ | `/budget [$N]` | Set max spend for the next task when supported (Claude Code) |
167
+ | `/plan` | Toggle read-only planningClaude permission mode / Codex read-only sandbox |
168
+ | `/ask` | Toggle read-only Q&A through the selected provider's read-only mode |
169
+ | `/worktree` | Toggle a provider-managed isolated git branch when supported (Claude Code) |
180
170
  | `/mode` | Switch between direct and agent bot modes |
181
- | `/status` | Show current session, backend, recall engine, and settings |
171
+ | `/status` | Show current session, provider, capabilities, recall engine, and settings |
182
172
  | `/usage` | Token usage and cost for this session |
183
173
  | `/doctor` / `/requirements` | Check Node, CLI binaries/versions/auth, voice stack, and writable paths |
184
174
 
@@ -259,7 +249,7 @@ Available only when the bot runs as an AgentSpace-provisioned pod (the broker cr
259
249
 
260
250
  ## Memory & Long-Term Context
261
251
 
262
- Open Claudia layers three memory systems on top of the backend's native sessions:
252
+ Open Claudia layers three memory systems on top of provider-native sessions:
263
253
 
264
254
  **Context packs** (`~/.open-claudia/packs/<dir>/PACK.md`) are living per-topic documents with four sections: *Stance* (how to think about the topic — your preferences and hard rules), *Procedure* (verified how-to steps), *State* (where work stands now), and *Journal* (a dated one-line log of past sessions). Incoming messages are matched against packs (FTS5, field-weighted so a stray word can't drag a pack in) and hits are injected into the agent's context — mention a project anywhere and the assistant picks up its train of thought, decisions, and history without you re-explaining. After every substantial turn, a background reviewer on a cheap model updates the relevant pack (or creates one for a genuinely new topic). Every change is announced in chat.
265
255
 
@@ -267,7 +257,7 @@ Open Claudia layers three memory systems on top of the backend's native sessions
267
257
 
268
258
  **Recall engines** — how packs and entities get matched and surfaced is pluggable per chat via `/engine` (or the `RECALL_ENGINE` env default). **classic** (the default) is keyword FTS plus a relevance judge with headline injection — stable and unchanged. **discoverer** (opt-in) adds a typed-edge graph over the same corpus (`parent`/`governed-by`/`related` edges with weights in `recall-graph.db`) and runs: a pre-gate that skips recall on trivial turns → FTS seeding → spreading activation across the graph (1–2 hops — auto-pulls cross-cutting concerns the query never named) → a walker that reads each candidate and returns the genuinely-relevant set with one-line why-bullets (fail-open to keyword seeds, so it never recalls worse than classic). Edges form structurally from pack `parent` frontmatter and `[[links]]`, and strengthen via Hebbian co-use when the agent opens packs together (📖); weights decay over time. Inspect with `open-claudia recall-stats` and `open-claudia recall graph [--sync]`, or flip on `/recall` to watch — per turn — which packs/entities surfaced and why, right in the chat. Switch back any time with `/engine classic`.
269
259
 
270
- **Dream consolidation** — while the per-turn reviewer takes quick notes, *dream* is the slow overnight pass (default 4am, on a high-tier model — opus by default, set `DREAM_TIER` or `DREAM_MODEL`): it merges packs that drifted into the same topic, builds parent/sub pack trees with umbrella summaries, tightens descriptions and tags so the router matches with less noise, dedupes entities, cross-links notes, and tends the recall graph (structural sync, weight decay, orphan prune). The pass is *evidence-grounded*: per-turn recall telemetry (what surfaced, what got kept, what the agent actually opened) feeds the prompt, so archive/merge calls rest on usage numbers rather than vibes. Most nights run as cheap *deltas* — full pack bodies only for what changed since the last dream plus graph neighbours and pre-computed merge candidates; a full-corpus sweep runs every `DREAM_FULL_SWEEP_DAYS` (30). Deterministic phases run even if the model call fails: journal backfill dedupe, weakening of graph nodes that keep arriving but never get kept, co-rescue edge reinforcement, episodic index tending, and a memory-health report (rescue rate, noise, latency, spend) in the morning chat summary. Dream may also tune one bounded recall knob per night (`recall-tuning.json`) — every change is checked against the next window's health and auto-rolled-back if rescue drops or noise rises; env pins always win. Anything merged away is backed up under `~/.open-claudia/backup/dream-<stamp>/` first, and every dream that changes something reports in chat. Configure with `DREAM_CRON` / `DREAM_MODEL`, disable with `DREAM=off`.
260
+ **Dream consolidation** — while the per-turn reviewer takes quick notes, *dream* is the slow overnight pass (default 4am, through the configured provider's high model tier): it merges packs that drifted into the same topic, builds parent/sub pack trees with umbrella summaries, tightens descriptions and tags so the router matches with less noise, dedupes entities, cross-links notes, and tends the recall graph (structural sync, weight decay, orphan prune). The pass is *evidence-grounded*: per-turn recall telemetry (what surfaced, what got kept, what the agent actually opened) feeds the prompt, so archive/merge calls rest on usage numbers rather than vibes. Most nights run as cheap *deltas* — full pack bodies only for what changed since the last dream plus graph neighbours and pre-computed merge candidates; a full-corpus sweep runs every `DREAM_FULL_SWEEP_DAYS` (30). Deterministic phases run even if the model call fails: journal backfill dedupe, weakening of graph nodes that keep arriving but never get kept, co-rescue edge reinforcement, episodic index tending, and a memory-health report (rescue rate, noise, latency, spend) in the morning chat summary. Dream may also tune one bounded recall knob per night (`recall-tuning.json`) — every change is checked against the next window's health and auto-rolled-back if rescue drops or noise rises; env pins always win. Anything merged away is backed up under `~/.open-claudia/backup/dream-<stamp>/` first, and each report and morning summary records the provider/model used. Configure with `DREAM_CRON`, `DREAM_TIER`, `DREAM_PROVIDER`, or a provider-specific `DREAM_MODEL_CLAUDE` / `DREAM_MODEL_CODEX`; disable with `DREAM=off`.
271
261
 
272
262
  **Personality** — your `soul.md` holds identity and hard rules; `~/.open-claudia/persona.md` holds the voice on top — tone, quirks, emoji habits. It feeds into the system prompt and the dream pass may evolve it gently (bounded, backed up, announced). Edit it directly any time.
273
263
 
@@ -332,22 +322,41 @@ open-claudia send-to --person "<name>" "<message>"
332
322
  open-claudia recent --person "<name>" [--limit 20] # read recent activity from another chat
333
323
  ```
334
324
 
335
- ## Backend Comparison
325
+ Replies and write/destructive actions for external people pass through an independent, read-only relationship guard. The guard can use Claude or Codex (`ENFORCER_PROVIDER`) and fails closed: malformed output, timeout, or total provider failure holds the action for owner approval. A fallback is used only when explicitly listed in `PROVIDER_FALLBACKS`; audit records identify the provider/model that judged the action without storing the mandate or proposed content.
326
+
327
+ ## Provider Comparison
328
+
329
+ | | Claude Code | OpenAI Codex |
330
+ |---|---|---|
331
+ | Binary | `claude` | `codex` |
332
+ | Session flag | `--resume <id>` | `exec resume <id>` |
333
+ | Auth | `claude auth` | `codex login` |
334
+ | Plan mode | Yes (`--permission-mode plan`) | Yes (read-only sandbox) |
335
+ | Budget control | Yes (`--max-budget-usd`) | No |
336
+ | Effort levels | Native (`low` through `max`) | Native (`minimal` through `xhigh`) |
337
+ | Worktree | Yes (`--worktree`) | No |
338
+ | Model switching | Yes | Yes (`--model`) |
339
+ | Partial text streaming | Yes | No; normalized progress and final events only |
340
+
341
+ Both providers output structured events which Open Claudia normalizes for progress, usage, tools, sessions, and terminal results.
342
+
343
+ Unsupported optional controls return a provider-specific explanation and do not change saved settings. Mandatory safety controls are different: Open Claudia configures each provider's native pre-tool hook, and refuses unrestricted execution if that policy cannot initialize.
344
+
345
+ ## Utility provider policy
336
346
 
337
- | | Claude Code | Cursor Agent | OpenAI Codex |
338
- |---|---|---|---|
339
- | Binary | `claude` | `agent` | `codex` |
340
- | Session flag | `--resume <id>` | `--resume <id>` | `exec resume <id>` |
341
- | Auth | `claude auth` | `agent login` | `codex login` |
342
- | Plan mode | Yes (`--permission-mode plan`) | Yes (`--mode plan`) | Yes (`--sandbox read-only`) |
343
- | Ask mode | No | Yes (`--mode ask`) | No |
344
- | Budget control | Yes (`--max-budget-usd`) | No | No |
345
- | Effort levels | Yes | No | Via config.toml |
346
- | Worktree | Yes (`--worktree`) | Yes (`--worktree`) | No |
347
- | Model switching | Yes | Yes | Yes (`--model`) |
348
- | Dangerously skip permissions | Yes | Yes (`--trust`) | Yes (`--dangerously-bypass-approvals-and-sandbox`) |
347
+ Background intelligence—sub-agents, recall, memory review, dream/introspection, and the external-person enforcer—uses one provider-selection policy. A per-purpose override is checked first, then `UTILITY_PROVIDER` (default `active`), the active foreground provider when a chat exists, and `DEFAULT_PROVIDER` for global jobs. Providers translate the neutral `low`, `medium`, and `high` tiers to their own models; exact per-provider model overrides remain available.
349
348
 
350
- All three backends output structured JSON which Open Claudia parses for real-time progress updates (Claude/Cursor: `stream-json`; Codex: JSONL events).
349
+ Foreground turns never silently fall back to a different provider. Utility work uses `PROVIDER_FALLBACKS` only when the operator explicitly supplies an ordered list, starts a fresh provider session, and records the provider/model used. The relationship enforcer may try that configured list, but total failure always fails closed and asks the owner to decide.
350
+
351
+ ## Provider session semantics
352
+
353
+ Conversation identity is the tuple of canonical user, project, provider, and native session ID. Claude Code and Codex histories and active pointers stay separate: switching providers restores only that provider's project session and never passes one provider's native session ID to another. Session-history entries are provider-tagged; ambiguous legacy records remain visible but non-selectable.
354
+
355
+ Model, effort, budget, permission mode, and worktree settings are stored per provider. `/new` clears only the selected project/provider pointer, while `/end` closes the project selection without deleting history. Compaction and scheduled jobs capture an immutable provider/session tuple; an explicitly configured scheduled fallback starts fresh from a provider-neutral archived brief.
356
+
357
+ ## Provider migration and rollback
358
+
359
+ Upgrades from a release with the removed third provider snapshot `state.json`, `sessions.json`, `jobs.json`, legacy cron sources, and their relevant backups before activating provider-aware schemas. Removed-provider selections, histories, and jobs become non-selectable or disabled archives; they are never reassigned to Claude Code or Codex. See [Provider migration snapshots and rollback](docs/PROVIDER_MIGRATION.md) before upgrading or restoring an older runtime.
351
360
 
352
361
  ## Sending Files
353
362
 
@@ -379,16 +388,19 @@ Voice notes are transcribed locally — nothing sent to external services. On ma
379
388
 
380
389
  ```
381
390
  Phone (Telegram / Kazee) --> Bot (Node.js) --> Claude Code CLI --> Your codebase
382
- --> Cursor Agent CLI -->
383
391
  --> OpenAI Codex CLI -->
384
392
  <-- <-- <--
385
393
  ```
386
394
 
387
- The bot spawns the active backend CLI in headless mode (`--print` / `-p`) for each message, streaming `stream-json`/JSONL output back to the chat. It maintains native backend context via `--resume` and passes a system prompt that gives the agent awareness of the chat environment, configuration files, memory systems, and the ability to send files/images directly.
395
+ The bot spawns the selected provider CLI in headless mode for each message, normalizing its JSONL output back to the chat. It maintains provider-native context through the provider adapter and passes the same Open Claudia prompt/context contract to either CLI.
388
396
 
389
397
  The appended system prompt is byte-stable within a session to maximize Anthropic prompt-cache hits; per-turn state (vault status, pending tasks, matched packs/entities) rides the user message instead, where it is always uncached anyway.
390
398
 
391
- Open Claudia does not summarize one backend into another during switches; instead it records a redacted project transcript outside the repo and injects a small pointer telling fresh/switched sessions to search it only if needed.
399
+ Open Claudia does not summarize one provider's native session into another during switches; instead it records a redacted project transcript outside the repo and injects a small pointer telling fresh/switched sessions to search it only if needed.
400
+
401
+ ### Provider credential boundary
402
+
403
+ Native Claude and Codex child processes receive allowlisted process variables plus explicitly configured `AGENT_ENV_PASSTHROUGH` entries. Open Claudia removes control-plane, keyring, and all provider credential keys before adding back only the selected provider's credentials and auth/config root. This is environment isolation, not OS containment: a native same-UID process retains whatever filesystem access that operating-system user has, including access to readable CLI auth stores and configuration files.
392
404
 
393
405
  ## Configuration Files
394
406
 
@@ -408,7 +420,7 @@ All stored in `~/.open-claudia/`:
408
420
  | `packs/` | Context packs (living topic documents) + FTS index |
409
421
  | `entities/` | Entity notes (people/places/projects) + FTS index |
410
422
  | `sessions.json` | Per-project conversation history |
411
- | `state.json` | Current session state including active backend (survives restarts) |
423
+ | `state.json` | Current provider/project state and provider-scoped settings (survives restarts) |
412
424
  | `transcripts/` | Redacted project-scoped JSONL transcripts + FTS index |
413
425
  | `briefs/` | Archived full compaction briefs |
414
426
  | `audit.log` | Relay/intro/auth audit trail |
@@ -426,10 +438,13 @@ All stored in `~/.open-claudia/`:
426
438
  | `CHANNELS` | No | Channels to start: `telegram`, `kazee`, or `telegram,kazee` (default `telegram`) |
427
439
  | `KAZEE_URL` / `KAZEE_BOT_TOKEN` / `KAZEE_BOT_USER_ID` / `KAZEE_OWNER_USER_ID` | Kazee only | Kazee Chat connection |
428
440
  | `WORKSPACE` | Yes | Path to your projects directory |
429
- | `CLAUDE_PATH` | Yes | Path to Claude Code CLI binary |
430
- | `CURSOR_PATH` / `CODEX_PATH` | No | Paths to optional backend CLIs (auto-detected if in PATH) |
441
+ | `CLAUDE_PATH` / `CODEX_PATH` | No | Optional paths to provider CLIs (auto-detected if in PATH) |
442
+ | `DEFAULT_PROVIDER` | No | Foreground default (`claude` or `codex`); blank deterministically chooses the first compatible provider |
443
+ | `UTILITY_PROVIDER` | No | Utility default (`active`, `claude`, or `codex`); defaults to the active foreground provider |
444
+ | `MEMORY_PROVIDER` / `RECALL_PROVIDER` / `SUBAGENT_PROVIDER` | No | Optional per-purpose utility-provider overrides |
431
445
  | `CLAUDE_CODE_OAUTH_TOKEN` | No | OAuth token for non-interactive Claude runs (set via `/use_oauth_token`) |
432
- | `CLAUDE_MODEL` | No | Default Claude model |
446
+ | `AGENT_ENV_PASSTHROUGH` | No | Comma-separated project environment keys to pass to provider children; reserved provider/control/keyring keys are always filtered and re-added only by the selected provider |
447
+ | `CLAUDE_MODEL` / `CODEX_MODEL` | No | Optional default model for the matching provider |
433
448
  | `AUTO_COMPACT_TOKENS` | No | Auto-compact threshold in tokens (also settable via `/compactwindow`) |
434
449
  | `USAGE_ALERT_CONTEXT_TOKENS` | No | Alert when one completed turn's context tokens exceed this ceiling (default `120000`, `off` disables) |
435
450
  | `USAGE_ALERT_RATE_MULTIPLIER` | No | Alert when the latest context-token rate exceeds the recent baseline by this multiple (default `1.75`, `off` disables) |
@@ -450,9 +465,13 @@ All stored in `~/.open-claudia/`:
450
465
  | `PACK_MATCH_THRESHOLD` / `ENTITY_MATCH_THRESHOLD` | No | Router match score thresholds (default `2`) |
451
466
  | `DREAM` | No | `off` disables the nightly memory consolidation pass (default on) |
452
467
  | `DREAM_CRON` | No | Schedule for the dream pass (default `0 4 * * *`) |
453
- | `DREAM_MODEL` | No | Explicit model override for the dream pass (otherwise picked from `DREAM_TIER`) |
454
- | `DREAM_TIER` | No | Model tier for the dream pass: `low` (haiku) / `medium` (sonnet) / `high` (opus, default) |
468
+ | `DREAM_PROVIDER` | No | Provider for dream and introspection (`claude` or `codex`); otherwise scheduled work resolves through `DEFAULT_PROVIDER` |
469
+ | `DREAM_MODEL_CLAUDE` / `DREAM_MODEL_CODEX` | No | Exact per-provider dream model override; legacy `DREAM_MODEL` remains a Claude-only compatibility alias |
470
+ | `DREAM_TIER` | No | Provider-owned model tier for the dream pass: `low`, `medium`, or `high` (default) |
455
471
  | `DREAM_FULL_SWEEP_DAYS` | No | Days between full-corpus dream sweeps; other nights are cheap deltas over recently-touched packs (default `30`) |
472
+ | `ENFORCER_PROVIDER` | No | Provider for the independent external-person guard (`claude` or `codex`) |
473
+ | `ENFORCER_MODEL_CLAUDE` / `ENFORCER_MODEL_CODEX` | No | Exact per-provider guard model override; legacy `ENFORCER_MODEL` remains a Claude-only compatibility alias |
474
+ | `PROVIDER_FALLBACKS` | No | Explicit ordered utility fallback list (for example `codex`); the enforcer never invents an unconfigured fallback |
456
475
  | `PERSONA_FILE` | No | Override the persona file location |
457
476
  | `WEB_UI` / `WEB_PORT` / `WEB_PASSWORD` | No | Web UI toggle, port, and password |
458
477
  | `WHISPER_CLI` / `WHISPER_MODEL` | No | whisper.cpp binary and model for voice notes |
@@ -467,13 +486,13 @@ All stored in `~/.open-claudia/`:
467
486
  Set up during `open-claudia setup`, or manually:
468
487
 
469
488
  ```bash
470
- # The setup wizard creates ~/Library/LaunchAgents/com.open-claudia.plist
489
+ # The setup wizard retains the legacy-compatible service id com.claude-telegram-bot
471
490
  # To manage:
472
- launchctl load ~/Library/LaunchAgents/com.open-claudia.plist
473
- launchctl unload ~/Library/LaunchAgents/com.open-claudia.plist
491
+ launchctl load ~/Library/LaunchAgents/com.claude-telegram-bot.plist
492
+ launchctl unload ~/Library/LaunchAgents/com.claude-telegram-bot.plist
474
493
  ```
475
494
 
476
- **Important**: If `agent` is installed in a non-standard location (e.g. `~/.local/bin`), make sure that path is included in the launchd plist's `PATH` environment variable. Otherwise the bot won't detect it at startup.
495
+ **Important**: If a provider CLI is installed in a non-standard location, make sure that path is included in the launchd plist's `PATH` environment variable or configure its explicit path.
477
496
 
478
497
  ### Linux (systemd)
479
498
 
@@ -485,11 +504,13 @@ sudo systemctl start claude-telegram-bot
485
504
  sudo systemctl status claude-telegram-bot
486
505
  ```
487
506
 
507
+ The systemd unit name is retained as a legacy service identifier so upgrades manage the existing unit instead of installing a duplicate; it does not select the coding-agent provider.
508
+
488
509
  ## Auto-Updates
489
510
 
490
511
  The bot checks npm for new versions every 5 minutes. When an update is available, you get a chat notification. Send `/upgrade` to update — the bot installs the new version, restarts, runs a doctor check, and notifies you it's back.
491
512
 
492
- For direct npm installs, `/upgrade` updates Open Claudia itself and does not install optional backend CLIs such as Codex or Cursor Agent. Container deployments should roll out a new Docker image when bundled CLI tools change.
513
+ For direct npm installs, `/upgrade` updates Open Claudia itself and does not install optional provider CLIs such as Codex. Container deployments should roll out a new Docker image when bundled CLI tools change.
493
514
 
494
515
  ## Cron Jobs
495
516
 
package/bin/agent.js CHANGED
@@ -1,14 +1,14 @@
1
- // open-claudia agent "<prompt>" [--role "<role>"] [--json] [--cwd <dir>]
2
- // Spawn a fresh, throwaway Claude sub-agent for focused research.
1
+ // open-claudia agent "<prompt>" [--role "<role>"] [--provider active|claude|codex] [--json] [--cwd <dir>]
2
+ // Spawn a fresh provider-isolated sub-agent for focused research.
3
3
  // No session resume; the parent agent captures stdout and decides what
4
4
  // to do with the result.
5
5
 
6
6
  const path = require("path");
7
7
 
8
8
  const HELP = `
9
- Spawn a fresh Claude sub-agent. Output goes to stdout.
9
+ Spawn a fresh Open Claudia sub-agent. Output goes to stdout.
10
10
 
11
- open-claudia agent "<prompt>" [--role "<role>"] [--cwd <dir>] [--json] [--timeout <s>]
11
+ open-claudia agent "<prompt>" [--role "<role>"] [--provider active|claude|codex] [--cwd <dir>] [--json] [--timeout <s>]
12
12
 
13
13
  The sub-agent has read-only research tools (Read, Glob, Grep, Bash for read-only
14
14
  commands) but no access to your chat session or the send-* CLIs.
@@ -18,6 +18,7 @@ function takeFlag(args, name) {
18
18
  const idx = args.indexOf(`--${name}`);
19
19
  if (idx < 0) return null;
20
20
  const v = args[idx + 1];
21
+ if (v == null || String(v).startsWith("--")) throw new Error(`--${name} requires a value`);
21
22
  args.splice(idx, 2);
22
23
  return v;
23
24
  }
@@ -29,32 +30,57 @@ function hasFlag(args, name) {
29
30
  return true;
30
31
  }
31
32
 
32
- async function run(args) {
33
- if (args.length === 0 || args[0] === "--help" || args[0] === "help") {
34
- console.log(HELP); process.exit(args[0] === "--help" || args[0] === "help" ? 0 : 2);
35
- }
36
- const rest = args.slice();
33
+ function parseAgentArgs(input) {
34
+ const rest = input.slice();
37
35
  const role = takeFlag(rest, "role");
36
+ const provider = takeFlag(rest, "provider");
37
+ if (provider !== null && !new Set(["active", "claude", "codex"]).has(provider)) {
38
+ throw new Error("--provider must be active, claude, or codex");
39
+ }
38
40
  const cwd = takeFlag(rest, "cwd") || process.cwd();
39
41
  const timeoutS = takeFlag(rest, "timeout");
40
42
  const json = hasFlag(rest, "json");
43
+ if (rest.some((value) => value.startsWith("--"))) {
44
+ throw new Error(`Unknown agent option: ${rest.find((value) => value.startsWith("--"))}`);
45
+ }
41
46
  const prompt = rest.join(" ").trim();
42
- if (!prompt) { console.error("Missing prompt."); process.exit(2); }
47
+ if (!prompt) throw new Error("Missing prompt.");
48
+ let timeoutMs;
49
+ if (timeoutS != null) {
50
+ const seconds = Number(timeoutS);
51
+ if (!Number.isFinite(seconds) || seconds <= 0) throw new Error("--timeout must be a positive number of seconds");
52
+ timeoutMs = seconds * 1000;
53
+ }
54
+ return { prompt, role, provider, cwd, timeoutMs, json };
55
+ }
56
+
57
+ async function run(args) {
58
+ if (args.length === 0 || args[0] === "--help" || args[0] === "help") {
59
+ console.log(HELP); process.exit(args[0] === "--help" || args[0] === "help" ? 0 : 2);
60
+ }
61
+ let parsed;
62
+ try { parsed = parseAgentArgs(args); }
63
+ catch (error) { console.error(error.message); process.exit(2); return; }
43
64
 
44
- const { spawnSubagent } = require(path.join(__dirname, "..", "core", "subagent"));
65
+ const { spawnUtilityAgent } = require(path.join(__dirname, "..", "core", "utility-agent"));
66
+ const { buildSubagentSystemPrompt } = require(path.join(__dirname, "..", "core", "subagent"));
45
67
  const channelId = process.env.OC_CHANNEL_ID || null;
46
68
  try {
47
- const res = await spawnSubagent(prompt, {
48
- cwd,
49
- role,
69
+ const request = {
70
+ purpose: "subagent",
71
+ tier: "medium",
72
+ cwd: parsed.cwd,
73
+ systemPrompt: buildSubagentSystemPrompt(parsed.role),
74
+ readOnly: true,
50
75
  channelId,
51
- json,
52
- timeoutMs: timeoutS ? Number(timeoutS) * 1000 : undefined,
53
- });
76
+ timeoutMs: parsed.timeoutMs,
77
+ };
78
+ if (parsed.provider !== null) request.provider = parsed.provider;
79
+ const res = await spawnUtilityAgent(parsed.prompt, request);
54
80
  if (res.text) process.stdout.write(res.text + (res.text.endsWith("\n") ? "" : "\n"));
55
81
  if (res.truncated) process.stderr.write("(output truncated to 64KB)\n");
56
82
  process.exit(0);
57
83
  } catch (e) { console.error(`agent failed: ${e.message}`); process.exit(1); }
58
84
  }
59
85
 
60
- module.exports = { run, HELP };
86
+ module.exports = { run, HELP, parseAgentArgs };
package/bin/cli.js CHANGED
@@ -11,22 +11,17 @@ const botDir = path.join(__dirname, "..");
11
11
  const configDir = require(path.join(botDir, "config-dir"));
12
12
  const health = require(path.join(botDir, "health"));
13
13
 
14
- function getBotFile() {
15
- const modeFile = path.join(configDir, ".bot-mode");
16
- try {
17
- const mode = fs.readFileSync(modeFile, "utf-8").trim();
18
- if (mode === "agent") return "bot-agent.js";
19
- } catch (e) {}
20
- return "bot.js";
14
+ function getBotMode() {
15
+ return require(path.join(botDir, "core", "side-chat")).readRuntimeMode(configDir);
21
16
  }
22
17
 
23
- function startBot(botFile, args) {
18
+ function startBot(args) {
24
19
  // Start web UI alongside bot if WEB_UI=true or --web flag
25
20
  if (process.env.WEB_UI === "true" || args.includes("--web")) {
26
21
  const { startWebServer } = require(path.join(botDir, "web.js"));
27
22
  startWebServer();
28
23
  }
29
- require(path.join(botDir, botFile));
24
+ require(path.join(botDir, "bot.js"));
30
25
  }
31
26
 
32
27
  function sendViaLoopback(kind, restArgs) {
@@ -52,7 +47,7 @@ function sendViaLoopback(kind, restArgs) {
52
47
  if (info) { sendUrl = info.url; sendToken = info.token; }
53
48
  }
54
49
  if (!sendUrl || !sendToken || !channelId || !adapterId) {
55
- console.error("No active chat context. This command only works from inside a running Claude task spawned by the bot.");
50
+ console.error("No active chat context. This command only works from inside an Open Claudia agent task spawned by the bot.");
56
51
  process.exit(2);
57
52
  }
58
53
 
@@ -132,13 +127,17 @@ function findBotProcesses() {
132
127
 
133
128
  switch (command) {
134
129
  case "setup":
135
- require(path.join(botDir, "setup.js"));
130
+ require(path.join(botDir, "setup.js")).main().catch((error) => {
131
+ console.error(error.stack || error.message);
132
+ process.exitCode = 1;
133
+ });
136
134
  break;
137
135
 
138
- // PreToolUse hook target (tool-first deny-gate, 5b). Claude pipes the tool
139
- // payload on stdin; exit 2 + stderr = deny (message fed back to the model),
140
- // exit 0 = allow. Every failure mode fails OPEN — a broken gate must never
141
- // brick the agent's shell.
136
+ // Provider-neutral PreToolUse target. Claude and Codex both pipe the Bash
137
+ // payload on stdin; exit 2 + stderr denies the command and exit 0 allows it.
138
+ // Transport initialization is mandatory in the provider adapters. Payload
139
+ // compatibility remains fail-open so an unknown future event shape cannot
140
+ // brick every shell command.
142
141
  case "deny-gate-hook": {
143
142
  let code = 0;
144
143
  try {
@@ -172,7 +171,7 @@ switch (command) {
172
171
 
173
172
  case "start": {
174
173
  const skipHealthCheck = args.includes("--skip-health") || args.includes("--force");
175
- const botFile = getBotFile();
174
+ const botMode = getBotMode();
176
175
 
177
176
  // Run health checks before starting (unless skipped)
178
177
  if (!skipHealthCheck) {
@@ -187,15 +186,15 @@ switch (command) {
187
186
  console.log("Warnings:");
188
187
  for (const w of results.warnings) console.log(` - ${w}`);
189
188
  }
190
- console.log(`Starting Open Claudia (${botFile === "bot-agent.js" ? "agent" : "direct"} mode)...`);
191
- startBot(botFile, args);
189
+ console.log(`Starting Open Claudia (${botMode} mode, modular runtime)...`);
190
+ startBot(args);
192
191
  }).catch((err) => {
193
192
  console.error("Health check failed:", err.message);
194
193
  process.exit(1);
195
194
  });
196
195
  } else {
197
- console.log(`Starting Open Claudia (${botFile === "bot-agent.js" ? "agent" : "direct"} mode)...`);
198
- startBot(botFile, args);
196
+ console.log(`Starting Open Claudia (${botMode} mode, modular runtime)...`);
197
+ startBot(args);
199
198
  }
200
199
  break;
201
200
  }
@@ -215,9 +214,9 @@ switch (command) {
215
214
  const { startWebServer: startWeb, isConfigured } = require(path.join(botDir, "web.js"));
216
215
  startWeb();
217
216
  if (isConfigured()) {
218
- const botFile = getBotFile();
219
- console.log(`Bot config found. Starting bot (${botFile === "bot-agent.js" ? "agent" : "direct"} mode)...`);
220
- require(path.join(botDir, botFile));
217
+ const botMode = getBotMode();
218
+ console.log(`Bot config found. Starting bot (${botMode} mode, modular runtime)...`);
219
+ require(path.join(botDir, "bot.js"));
221
220
  } else {
222
221
  console.log("No config found. Complete setup in the web UI.");
223
222
  }
@@ -232,7 +231,10 @@ switch (command) {
232
231
  }
233
232
  // Legacy: open-claudia auth (no subcommand) → setup.js auth mode
234
233
  process.argv = [process.argv[0], process.argv[1], "--auth"];
235
- require(path.join(botDir, "setup.js"));
234
+ require(path.join(botDir, "setup.js")).main().catch((error) => {
235
+ console.error(error.stack || error.message);
236
+ process.exitCode = 1;
237
+ });
236
238
  break;
237
239
  }
238
240
 
@@ -419,12 +421,12 @@ Memory tools:
419
421
  open-claudia dream [--dry-run] Run the memory consolidation pass now
420
422
 
421
423
  Background work (only inside an active bot-spawned task):
422
- open-claudia schedule-wakeup <when> "<prompt>" One-shot future wake-up; resumes session
423
- open-claudia cron-add "<sched>" "<prompt>" Recurring cron job
424
+ open-claudia schedule-wakeup <when> "<prompt>" One-shot; resumes the captured selected provider session
425
+ open-claudia cron-add "<sched>" "<prompt>" Recurring job pinned to the selected provider session
424
426
  open-claudia cron-list List all wakeups + crons on this channel
425
427
  open-claudia cron-remove <id> Remove a scheduled job
426
428
  open-claudia task add|list|start|done|remove Per-channel todo (survives restarts)
427
- open-claudia agent "<prompt>" [--role X] Spawn a throwaway sub-agent for research
429
+ open-claudia agent "<prompt>" [--role X] [--provider active|claude|codex] Spawn a provider-isolated sub-agent
428
430
 
429
431
  Cluster control (self-management of this bot's own deployment via the broker):
430
432
  open-claudia cluster status|logs|restart|start|stop|scale <0|1>|sync
@@ -438,7 +440,7 @@ Multi-user / cross-channel:
438
440
 
439
441
  Start options:
440
442
  --web Also start the web UI
441
- --quick Skip slow health checks (Claude auth, Telegram API)
443
+ --quick Skip slow provider auth and channel API checks
442
444
  --skip-health Bypass all health checks (not recommended)
443
445
 
444
446
  Health options: