negotium 0.1.7 → 0.1.8

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 (256) hide show
  1. package/README.md +2 -2
  2. package/dist/chunk-0ay6cjdy.js +1331 -0
  3. package/dist/chunk-0ay6cjdy.js.map +16 -0
  4. package/dist/chunk-1k7t7bnx.js +97 -0
  5. package/dist/chunk-1k7t7bnx.js.map +10 -0
  6. package/dist/chunk-5ahkew9c.js +806 -0
  7. package/dist/chunk-5ahkew9c.js.map +17 -0
  8. package/dist/chunk-6cpn7ht2.js +287 -0
  9. package/dist/chunk-6cpn7ht2.js.map +13 -0
  10. package/dist/chunk-6kctn20e.js +3529 -0
  11. package/dist/chunk-6kctn20e.js.map +20 -0
  12. package/dist/chunk-7xqj7aex.js +77 -0
  13. package/dist/chunk-7xqj7aex.js.map +11 -0
  14. package/dist/chunk-9p0ww3tk.js +41 -0
  15. package/dist/chunk-9p0ww3tk.js.map +10 -0
  16. package/dist/chunk-a92a7nrk.js +252 -0
  17. package/dist/chunk-a92a7nrk.js.map +10 -0
  18. package/dist/chunk-c500xe9z.js +33 -0
  19. package/dist/chunk-c500xe9z.js.map +10 -0
  20. package/dist/chunk-cds1qxsf.js +1644 -0
  21. package/dist/chunk-cds1qxsf.js.map +17 -0
  22. package/dist/chunk-e4407g8x.js +274 -0
  23. package/dist/chunk-e4407g8x.js.map +10 -0
  24. package/dist/chunk-e5pbggsh.js +363 -0
  25. package/dist/chunk-e5pbggsh.js.map +11 -0
  26. package/dist/chunk-edb07sjp.js +567 -0
  27. package/dist/chunk-edb07sjp.js.map +14 -0
  28. package/dist/chunk-exd0v74a.js +3012 -0
  29. package/dist/chunk-exd0v74a.js.map +32 -0
  30. package/dist/chunk-fgrme9tr.js +4398 -0
  31. package/dist/chunk-fgrme9tr.js.map +47 -0
  32. package/dist/chunk-fka9eqe1.js +581 -0
  33. package/dist/chunk-fka9eqe1.js.map +11 -0
  34. package/dist/chunk-g82jygez.js +108 -0
  35. package/dist/chunk-g82jygez.js.map +10 -0
  36. package/dist/chunk-ghs2j1fr.js +312 -0
  37. package/dist/chunk-ghs2j1fr.js.map +10 -0
  38. package/dist/chunk-gmy11efb.js +125 -0
  39. package/dist/chunk-gmy11efb.js.map +10 -0
  40. package/dist/chunk-gxma4c70.js +110 -0
  41. package/dist/chunk-gxma4c70.js.map +10 -0
  42. package/dist/chunk-hkdqhm34.js +61 -0
  43. package/dist/chunk-hkdqhm34.js.map +10 -0
  44. package/dist/chunk-hpa0cpzh.js +134 -0
  45. package/dist/chunk-hpa0cpzh.js.map +10 -0
  46. package/dist/chunk-hwy3n31w.js +21 -0
  47. package/dist/chunk-hwy3n31w.js.map +9 -0
  48. package/dist/chunk-j2behpy7.js +214 -0
  49. package/dist/chunk-j2behpy7.js.map +10 -0
  50. package/dist/chunk-jc0at0tc.js +102 -0
  51. package/dist/chunk-jc0at0tc.js.map +10 -0
  52. package/dist/chunk-jkqq5ef6.js +243 -0
  53. package/dist/chunk-jkqq5ef6.js.map +11 -0
  54. package/dist/chunk-jzkw2f01.js +51 -0
  55. package/dist/chunk-jzkw2f01.js.map +10 -0
  56. package/dist/chunk-jzz91jwh.js +70 -0
  57. package/dist/chunk-jzz91jwh.js.map +10 -0
  58. package/dist/chunk-k9z47mab.js +247 -0
  59. package/dist/chunk-k9z47mab.js.map +12 -0
  60. package/dist/chunk-n17yxpa8.js +23 -0
  61. package/dist/chunk-n17yxpa8.js.map +10 -0
  62. package/dist/chunk-naa09ags.js +2143 -0
  63. package/dist/chunk-naa09ags.js.map +15 -0
  64. package/dist/chunk-ns3ke88n.js +142 -0
  65. package/dist/chunk-ns3ke88n.js.map +10 -0
  66. package/dist/chunk-p3cr2ejg.js +1626 -0
  67. package/dist/chunk-p3cr2ejg.js.map +15 -0
  68. package/dist/chunk-q5bcpf23.js +2925 -0
  69. package/dist/chunk-q5bcpf23.js.map +20 -0
  70. package/dist/chunk-rafc55bs.js +424 -0
  71. package/dist/chunk-rafc55bs.js.map +11 -0
  72. package/dist/chunk-rbrxjvbj.js +2830 -0
  73. package/dist/chunk-rbrxjvbj.js.map +23 -0
  74. package/dist/chunk-sa874j5c.js +9 -0
  75. package/dist/chunk-sa874j5c.js.map +10 -0
  76. package/dist/chunk-sgsz2fpq.js +289 -0
  77. package/dist/chunk-sgsz2fpq.js.map +10 -0
  78. package/dist/chunk-sm6fh5c5.js +569 -0
  79. package/dist/chunk-sm6fh5c5.js.map +14 -0
  80. package/dist/chunk-t9zcg8f1.js +499 -0
  81. package/dist/chunk-t9zcg8f1.js.map +13 -0
  82. package/dist/chunk-vp2db42d.js +121 -0
  83. package/dist/chunk-vp2db42d.js.map +10 -0
  84. package/dist/chunk-w13jjf4a.js +46 -0
  85. package/dist/chunk-w13jjf4a.js.map +10 -0
  86. package/dist/chunk-x1arr7ze.js +63 -0
  87. package/dist/chunk-x1arr7ze.js.map +10 -0
  88. package/dist/chunk-xfq68rch.js +583 -0
  89. package/dist/chunk-xfq68rch.js.map +11 -0
  90. package/dist/main.js +132 -20
  91. package/dist/main.js.map +4 -4
  92. package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
  93. package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
  94. package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
  95. package/dist/runtime/src/agents/archiver.ts +396 -0
  96. package/dist/runtime/src/agents/auth-check.ts +64 -0
  97. package/dist/runtime/src/agents/claude-provider.ts +737 -0
  98. package/dist/runtime/src/agents/claude-registry.ts +115 -0
  99. package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
  100. package/dist/runtime/src/agents/codex-provider.ts +785 -0
  101. package/dist/runtime/src/agents/codex-registry.ts +115 -0
  102. package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
  103. package/dist/runtime/src/agents/contracts.ts +69 -0
  104. package/dist/runtime/src/agents/deep-map.ts +13 -0
  105. package/dist/runtime/src/agents/execution-host.ts +91 -0
  106. package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
  107. package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
  108. package/dist/runtime/src/agents/fork.ts +65 -0
  109. package/dist/runtime/src/agents/hosted-agent.ts +48 -0
  110. package/dist/runtime/src/agents/idle-archiver.ts +128 -0
  111. package/dist/runtime/src/agents/index.ts +205 -0
  112. package/dist/runtime/src/agents/maestro-provider.ts +206 -0
  113. package/dist/runtime/src/agents/maestro-registry.ts +31 -0
  114. package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
  115. package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
  116. package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
  117. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
  118. package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
  119. package/dist/runtime/src/agents/model-catalog.ts +151 -0
  120. package/dist/runtime/src/agents/registry.ts +24 -0
  121. package/dist/runtime/src/agents/rollout/claude.ts +331 -0
  122. package/dist/runtime/src/agents/rollout/codex.ts +569 -0
  123. package/dist/runtime/src/agents/rollout/shared.ts +205 -0
  124. package/dist/runtime/src/agents/self-config-core.ts +415 -0
  125. package/dist/runtime/src/agents/task-events.ts +44 -0
  126. package/dist/runtime/src/agents/tool-format.ts +352 -0
  127. package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
  128. package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
  129. package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
  130. package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
  131. package/dist/runtime/src/application/submit-user-message.ts +47 -0
  132. package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
  133. package/dist/runtime/src/application/switch-topic-model.ts +79 -0
  134. package/dist/runtime/src/application/topic-service.ts +139 -0
  135. package/dist/runtime/src/application/vault-command.ts +170 -0
  136. package/dist/runtime/src/bus.ts +319 -0
  137. package/dist/runtime/src/index.ts +333 -0
  138. package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
  139. package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
  140. package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
  141. package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
  142. package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
  143. package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
  144. package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
  145. package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
  146. package/dist/runtime/src/mcp/runtime-spec.ts +134 -0
  147. package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
  148. package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
  149. package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
  150. package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
  151. package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
  152. package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
  153. package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
  154. package/dist/runtime/src/mcp/system-health-server.ts +130 -0
  155. package/dist/runtime/src/mcp/task-server.ts +173 -0
  156. package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
  157. package/dist/runtime/src/mcp/vault-http.ts +219 -0
  158. package/dist/runtime/src/mcp/vault-run.ts +153 -0
  159. package/dist/runtime/src/mcp/vault-server.ts +104 -0
  160. package/dist/runtime/src/mcp/wiki-server.ts +755 -0
  161. package/dist/runtime/src/media/file-events.ts +33 -0
  162. package/dist/runtime/src/media/text-extractor.ts +312 -0
  163. package/dist/runtime/src/media/video.ts +92 -0
  164. package/dist/runtime/src/outbox/file-ops.ts +210 -0
  165. package/dist/runtime/src/outbox/index.ts +8 -0
  166. package/dist/runtime/src/outbox/utils.ts +51 -0
  167. package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
  168. package/dist/runtime/src/platform/config.ts +371 -0
  169. package/dist/runtime/src/platform/constants.ts +18 -0
  170. package/dist/runtime/src/platform/debug-users.ts +14 -0
  171. package/dist/runtime/src/platform/delay.ts +1 -0
  172. package/dist/runtime/src/platform/error.ts +9 -0
  173. package/dist/runtime/src/platform/file-utils.ts +16 -0
  174. package/dist/runtime/src/platform/jsonl.ts +202 -0
  175. package/dist/runtime/src/platform/lifecycle.ts +174 -0
  176. package/dist/runtime/src/platform/logger.ts +31 -0
  177. package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
  178. package/dist/runtime/src/platform/mcp-config.ts +865 -0
  179. package/dist/runtime/src/platform/modules.ts +145 -0
  180. package/dist/runtime/src/platform/node-plugins.ts +46 -0
  181. package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
  182. package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
  183. package/dist/runtime/src/prompts/builders.ts +357 -0
  184. package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
  185. package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
  186. package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
  187. package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
  188. package/dist/runtime/src/query/active-rooms.ts +532 -0
  189. package/dist/runtime/src/query/control.ts +313 -0
  190. package/dist/runtime/src/query/index.ts +24 -0
  191. package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
  192. package/dist/runtime/src/query/session-inbox-path.ts +42 -0
  193. package/dist/runtime/src/query/state.ts +94 -0
  194. package/dist/runtime/src/query/types.ts +83 -0
  195. package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
  196. package/dist/runtime/src/runtime/attachments.ts +151 -0
  197. package/dist/runtime/src/runtime/background-sessions.ts +83 -0
  198. package/dist/runtime/src/runtime/channel-context.ts +107 -0
  199. package/dist/runtime/src/runtime/errors.ts +69 -0
  200. package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
  201. package/dist/runtime/src/runtime/file-hooks.ts +81 -0
  202. package/dist/runtime/src/runtime/footer.ts +23 -0
  203. package/dist/runtime/src/runtime/inbox.ts +946 -0
  204. package/dist/runtime/src/runtime/tasks.ts +56 -0
  205. package/dist/runtime/src/runtime/topic-config.ts +29 -0
  206. package/dist/runtime/src/runtime/turn-runner.ts +2286 -0
  207. package/dist/runtime/src/runtime/usage-alert.ts +84 -0
  208. package/dist/runtime/src/runtime/visual-html.ts +109 -0
  209. package/dist/runtime/src/runtime/visual-store.ts +430 -0
  210. package/dist/runtime/src/runtime/visuals.ts +171 -0
  211. package/dist/runtime/src/security/sanitize.ts +27 -0
  212. package/dist/runtime/src/security/sensitive-path.ts +34 -0
  213. package/dist/runtime/src/storage/activity-log.ts +152 -0
  214. package/dist/runtime/src/storage/api-messages.ts +617 -0
  215. package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
  216. package/dist/runtime/src/storage/api-topic-config.ts +192 -0
  217. package/dist/runtime/src/storage/api-topics.ts +844 -0
  218. package/dist/runtime/src/storage/app-settings.ts +44 -0
  219. package/dist/runtime/src/storage/browser-profiles.ts +133 -0
  220. package/dist/runtime/src/storage/conversations.ts +268 -0
  221. package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
  222. package/dist/runtime/src/storage/forum/index.ts +4 -0
  223. package/dist/runtime/src/storage/forum/repository.ts +283 -0
  224. package/dist/runtime/src/storage/forum/schema.ts +300 -0
  225. package/dist/runtime/src/storage/forum-db.ts +23 -0
  226. package/dist/runtime/src/storage/runtime-events.ts +130 -0
  227. package/dist/runtime/src/storage/runtime-leases.ts +189 -0
  228. package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
  229. package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
  230. package/dist/runtime/src/storage/runtime-turn-requests.ts +300 -0
  231. package/dist/runtime/src/storage/self-schedules.ts +298 -0
  232. package/dist/runtime/src/storage/session-asks.ts +347 -0
  233. package/dist/runtime/src/storage/sqlite.ts +129 -0
  234. package/dist/runtime/src/storage/tasks.ts +179 -0
  235. package/dist/runtime/src/storage/token-stats.ts +144 -0
  236. package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
  237. package/dist/runtime/src/storage/topic-archive.ts +74 -0
  238. package/dist/runtime/src/storage/topic-settings.ts +67 -0
  239. package/dist/runtime/src/storage/topic-transcript.ts +98 -0
  240. package/dist/runtime/src/storage/vault-crypto.ts +65 -0
  241. package/dist/runtime/src/storage/vault.ts +225 -0
  242. package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
  243. package/dist/runtime/src/storage/wiki.ts +85 -0
  244. package/dist/runtime/src/topics/create.ts +123 -0
  245. package/dist/runtime/src/topics/derive.ts +398 -0
  246. package/dist/runtime/src/topics/lifecycle.ts +311 -0
  247. package/dist/runtime/src/topics/links.ts +7 -0
  248. package/dist/runtime/src/topics/personal-general.ts +65 -0
  249. package/dist/runtime/src/topics/session.ts +381 -0
  250. package/dist/runtime/src/types/api.ts +497 -0
  251. package/dist/runtime/src/types.ts +255 -0
  252. package/dist/runtime/src/version.ts +1 -0
  253. package/dist/runtime/tsconfig.json +13 -0
  254. package/package.json +16 -3
  255. package/dist/main.d.ts +0 -3
  256. package/dist/main.d.ts.map +0 -1
@@ -0,0 +1,151 @@
1
+ import { resolveDefaultModel } from "#platform/config";
2
+ import type { AgentKind } from "#types";
3
+
4
+ /**
5
+ * Models/prefixes that belong exclusively to one agent backend. When the
6
+ * resolved agent differs from a model's owner, the model is cross-agent stale
7
+ * and must be dropped in favor of the new agent's default — otherwise the value
8
+ * is passed straight into a provider that can't run it and crashes the turn
9
+ * (bug B: `/codex` leaves the topic's "sonnet" behind → Codex 400
10
+ * "The 'sonnet' model is not supported when using Codex").
11
+ *
12
+ * Codex's own `validateModel` accepts ANY non-empty string (OpenAI ships new
13
+ * IDs frequently), so it can't reject "sonnet" on its own — this ownership
14
+ * owner check is the cross-agent guard the per-registry validator can't provide.
15
+ * Unknown/new model IDs are intentionally absent here so they still pass
16
+ * through to whichever agent is active.
17
+ */
18
+ export const MODEL_OWNER: Record<string, AgentKind> = {
19
+ // Current aliases plus retired aliases retained only for stale-model detection.
20
+ sonnet: "claude",
21
+ opus: "claude",
22
+ haiku: "claude",
23
+ fable: "claude",
24
+ "gpt-5.6-luna": "codex",
25
+ "gpt-5.6-terra": "codex",
26
+ "gpt-5.6-sol": "codex",
27
+ "gpt-5.5": "codex",
28
+ deepseek: "maestro",
29
+ "deepseek-pro": "maestro",
30
+ "deepseek-flash": "maestro",
31
+ };
32
+
33
+ export interface SelectableModel {
34
+ /** Canonical token accepted by user-facing `/model` commands. */
35
+ model: string;
36
+ /** Runtime owner kept internal so channel UIs only need to show `model`. */
37
+ agent: AgentKind;
38
+ /** Short comparison copy shown alongside the model in picker UIs. */
39
+ description: string;
40
+ }
41
+
42
+ /**
43
+ * Canonical model picker shared by every channel. Keep this deliberately
44
+ * finite even though the Codex backend accepts arbitrary future model ids:
45
+ * user-facing completion should only promise models we intentionally support.
46
+ */
47
+ export const SELECTABLE_MODELS: readonly SelectableModel[] = [
48
+ {
49
+ model: "gpt-5.6-sol",
50
+ agent: "codex",
51
+ description: "Latest frontier agentic coding model.",
52
+ },
53
+ {
54
+ model: "gpt-5.6-terra",
55
+ agent: "codex",
56
+ description: "Balanced agentic coding model for everyday work.",
57
+ },
58
+ {
59
+ model: "gpt-5.6-luna",
60
+ agent: "codex",
61
+ description: "Fast and affordable agentic coding model.",
62
+ },
63
+ {
64
+ model: "gpt-5.5",
65
+ agent: "codex",
66
+ description: "Frontier model for complex coding, research, and real-world work.",
67
+ },
68
+ {
69
+ model: "fable",
70
+ agent: "claude",
71
+ description:
72
+ "Fable 5 · Most capable for your hardest and longest-running tasks · $10/$50 per Mtok",
73
+ },
74
+ {
75
+ model: "opus",
76
+ agent: "claude",
77
+ description: "Opus 4.8 with 1M context · Best for everyday, complex tasks · $5/$25 per Mtok",
78
+ },
79
+ {
80
+ model: "sonnet",
81
+ agent: "claude",
82
+ description: "Sonnet 5 · Efficient for routine tasks · $2/$10 per Mtok · promo through Aug 31",
83
+ },
84
+ {
85
+ model: "deepseek-pro",
86
+ agent: "maestro",
87
+ description: "Sonnet-level performance at lower cost · Best for everyday tasks over coding",
88
+ },
89
+ {
90
+ model: "deepseek-flash",
91
+ agent: "maestro",
92
+ description: "DeepSeek V4 Flash · Fast and low-cost for lightweight everyday tasks",
93
+ },
94
+ ];
95
+
96
+ export function selectableModel(value: string): SelectableModel | undefined {
97
+ const normalized = value.trim().toLowerCase();
98
+ return SELECTABLE_MODELS.find((candidate) => candidate.model === normalized);
99
+ }
100
+
101
+ export function modelOwner(model: string): AgentKind | undefined {
102
+ if (model.startsWith("claude-")) return "claude";
103
+ if (model.startsWith("deepseek-")) return "maestro";
104
+ if (model.startsWith("gpt-")) return "codex";
105
+ return MODEL_OWNER[model];
106
+ }
107
+
108
+ /**
109
+ * Resolve the model to run for `agent`, given the requested value from the
110
+ * priority chain (per-message slash > topic-config override > topic default).
111
+ * Drops a model owned by a different agent, then falls back to the agent's
112
+ * registry default if the value is empty/invalid for this agent.
113
+ */
114
+ export function resolveModelForAgent(
115
+ agent: AgentKind,
116
+ requested: string | undefined,
117
+ registry: { validateModel(s: string): boolean; defaultModel: string },
118
+ ): string {
119
+ const defaultModel = resolveDefaultModel(agent, registry.defaultModel);
120
+ if (!requested) return defaultModel;
121
+ const owner = modelOwner(requested);
122
+ if (owner && owner !== agent) return defaultModel; // cross-agent stale
123
+ return registry.validateModel(requested) ? requested : defaultModel;
124
+ }
125
+
126
+ /**
127
+ * Circular fallback order per agent. Each entry lists candidates to try in
128
+ * priority order when the current agent errors out. The actual switch is
129
+ * guarded by `checkAgentAuth` — only candidates whose backend is reachable
130
+ * (API key present / auth file exists) will be selected.
131
+ */
132
+ export const FALLBACK_ORDER: Record<AgentKind, { agent: AgentKind; model: string }[]> = {
133
+ claude: [
134
+ { agent: "maestro", model: "deepseek-pro" },
135
+ { agent: "codex", model: "gpt-5.6-luna" },
136
+ ],
137
+ codex: [
138
+ { agent: "maestro", model: "deepseek-pro" },
139
+ { agent: "claude", model: "sonnet" },
140
+ ],
141
+ maestro: [
142
+ { agent: "codex", model: "gpt-5.6-luna" },
143
+ { agent: "claude", model: "sonnet" },
144
+ ],
145
+ };
146
+
147
+ export const AGENT_DISPLAY_NAME: Record<AgentKind, string> = {
148
+ claude: "Claude",
149
+ codex: "Codex",
150
+ maestro: "Maestro",
151
+ };
@@ -0,0 +1,24 @@
1
+ import { claudeRegistry } from "#agents/claude-registry";
2
+ import { codexRegistry } from "#agents/codex-registry";
3
+ import type { AgentRegistry } from "#agents/contracts";
4
+ import { maestroRegistry } from "#agents/maestro-registry";
5
+ import type { AgentKind } from "#types";
6
+
7
+ export type {
8
+ AgentRegistry,
9
+ CleanupRolloutsOptions,
10
+ ForkRegistryOptions,
11
+ ForkRegistryResult,
12
+ WriteRolloutOptions,
13
+ WriteRolloutResult,
14
+ } from "#agents/contracts";
15
+
16
+ const REGISTRIES: Record<AgentKind, AgentRegistry> = {
17
+ claude: claudeRegistry,
18
+ codex: codexRegistry,
19
+ maestro: maestroRegistry,
20
+ };
21
+
22
+ export function getRegistry(agent: AgentKind): AgentRegistry {
23
+ return REGISTRIES[agent];
24
+ }
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Claude rollout encoder.
3
+ *
4
+ * Materializes a synthetic JSONL at `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`
5
+ * so a subsequent `claude.query({ resume: sessionId })` continues the
6
+ * cross-agent conversation as if it had always been native. Verified by
7
+ * sandbox PoCs (test-7c/test-7d): the SDK accepts these without checksum
8
+ * gating, requiring only `user`/`assistant` messages and a small per-turn
9
+ * attachment chain captured into `agents/fixtures/claude-attachments.jsonl`.
10
+ *
11
+ * Tool history is folded into assistant text as `[Tool: ...]` annotations
12
+ * (see shared.ts:extractChatPairs) — synthetic tool_use IDs across SDKs are
13
+ * too fragile to reconstruct structurally.
14
+ */
15
+
16
+ import { randomUUID } from "node:crypto";
17
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+ import {
21
+ assertUuidLike,
22
+ type ChatPair,
23
+ clone,
24
+ ensureCwdExists,
25
+ extractChatPairs,
26
+ FIXTURES_DIR,
27
+ } from "#agents/rollout/shared";
28
+ import { parseJsonlText, writeJsonlFile } from "#platform/jsonl";
29
+ import { logger } from "#platform/logger";
30
+ import type { ConversationEntry } from "#storage/conversations";
31
+
32
+ interface ClaudeAttachments {
33
+ deferredToolsDelta: Record<string, unknown>;
34
+ skillListing: Record<string, unknown>;
35
+ }
36
+
37
+ let _attachmentsCache: ClaudeAttachments | null = null;
38
+ function loadClaudeAttachments(): ClaudeAttachments {
39
+ if (_attachmentsCache) return _attachmentsCache;
40
+ const raw = readFileSync(join(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
41
+ const lines = parseJsonlText<Record<string, unknown>>(raw);
42
+ if (lines.length < 2) {
43
+ throw new Error(
44
+ `loadClaudeAttachments: expected >=2 entries in claude-attachments.jsonl, got ${lines.length}`,
45
+ );
46
+ }
47
+ _attachmentsCache = {
48
+ deferredToolsDelta: lines[0],
49
+ skillListing: lines[1],
50
+ };
51
+ return _attachmentsCache;
52
+ }
53
+
54
+ /**
55
+ * Version strings stamped onto synthetic rollouts so they look indistinguishable
56
+ * from a live SDK session. These do not affect resume behavior — the SDKs do
57
+ * not gate on them — but keeping them current avoids confusion in observability
58
+ * tooling that scans rollout files.
59
+ */
60
+ const CLAUDE_SDK_VERSION = "2.1.126";
61
+ const CLAUDE_DEFAULT_MODEL = "claude-sonnet-5";
62
+ const CLAUDE_DEFAULT_GIT_BRANCH = "HEAD";
63
+
64
+ /**
65
+ * Encode a directory path the way Claude SDK expects in
66
+ * `~/.claude/projects/<encoded-cwd>/`. macOS resolves `/tmp` to
67
+ * `/private/tmp`, mirroring what the SDK records.
68
+ *
69
+ * The SDK normalizes ANY non-alphanumeric character to `-` (verified
70
+ * empirically on disk: `/Users/maestrobot/.../user_6407801418` →
71
+ * `-Users-...-user-6407801418`, i.e. both `/` AND `_` collapse to `-`).
72
+ * A previous implementation only replaced `/` and left `_` intact, so
73
+ * synthetic rollouts landed at `…-user_6407801418/` while the SDK looked
74
+ * at `…-user-6407801418/` — every `set_agent` switch surfaced as "No
75
+ * conversation found with session ID" on the next message.
76
+ */
77
+ export function encodeClaudeCwd(cwd: string): string {
78
+ const realCwd = cwd.startsWith("/tmp/") ? cwd.replace(/^\/tmp\//, "/private/tmp/") : cwd;
79
+ return `-${realCwd.replaceAll(/[^a-zA-Z0-9]/g, "-").replace(/^-/, "")}`;
80
+ }
81
+
82
+ export interface ClaudeRolloutOptions {
83
+ /** Working directory the resumed Claude session will report. */
84
+ cwd: string;
85
+ /** Optional override; default = freshly generated UUIDv4. */
86
+ sessionId?: string;
87
+ /**
88
+ * Pairs to encode. If omitted, derived from `entries` via extractChatPairs.
89
+ * Pass explicit pairs when the caller has already shaped the dialogue.
90
+ */
91
+ pairs?: ChatPair[];
92
+ /** When `pairs` is omitted, the source UnifiedEvent log to digest. */
93
+ entries?: ConversationEntry[];
94
+ /** Full provider model id stamped on synthesized assistant history. */
95
+ model?: string;
96
+ }
97
+
98
+ export interface ClaudeRolloutResult {
99
+ sessionId: string;
100
+ rolloutPath: string;
101
+ }
102
+
103
+ /**
104
+ * Materialize a Claude rollout JSONL with the provided dialogue and place it
105
+ * at `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl` so a subsequent
106
+ * `claude.query({ resume: sessionId })` will continue the conversation.
107
+ */
108
+ export function writeClaudeRollout(opts: ClaudeRolloutOptions): ClaudeRolloutResult {
109
+ const sessionId = opts.sessionId ?? randomUUID();
110
+ assertUuidLike("sessionId", sessionId);
111
+ ensureCwdExists(opts.cwd);
112
+ const pairs = opts.pairs ?? extractChatPairs(opts.entries ?? []);
113
+ const cwdReal = opts.cwd.startsWith("/tmp/")
114
+ ? opts.cwd.replace(/^\/tmp\//, "/private/tmp/")
115
+ : opts.cwd;
116
+
117
+ const lines: unknown[] = [];
118
+ const ts = () => new Date().toISOString();
119
+ let lastUuid: string | null = null;
120
+
121
+ // Queue-operation noise that the live SDK records around each turn. Not
122
+ // strictly required for resume but mirrors what the SDK itself writes,
123
+ // keeping the synthetic file indistinguishable.
124
+ lines.push({ type: "queue-operation", operation: "enqueue", timestamp: ts(), sessionId });
125
+ lines.push({ type: "queue-operation", operation: "dequeue", timestamp: ts(), sessionId });
126
+
127
+ // Per-turn attachment chain captured from a real Claude rollout. claude SDK
128
+ // emits `deferred_tools_delta` followed by `skill_listing` immediately after
129
+ // every user message, building a parentUuid chain user → att1 → att2 →
130
+ // assistant. PoC-7d preserved this shape; replaying it keeps the resumed
131
+ // session indistinguishable from a native one.
132
+ const attachments = loadClaudeAttachments();
133
+
134
+ for (const pair of pairs) {
135
+ const userUuid = randomUUID();
136
+ lines.push({
137
+ parentUuid: lastUuid,
138
+ isSidechain: false,
139
+ promptId: randomUUID(),
140
+ type: "user",
141
+ message: { role: "user", content: [{ type: "text", text: pair.userText }] },
142
+ uuid: userUuid,
143
+ timestamp: ts(),
144
+ permissionMode: "bypassPermissions",
145
+ userType: "external",
146
+ entrypoint: "sdk-ts",
147
+ cwd: cwdReal,
148
+ sessionId,
149
+ version: CLAUDE_SDK_VERSION,
150
+ gitBranch: CLAUDE_DEFAULT_GIT_BRANCH,
151
+ });
152
+
153
+ // Attachment 1: deferred_tools_delta — chain from user.
154
+ const att1Uuid = randomUUID();
155
+ const att1 = clone(attachments.deferredToolsDelta);
156
+ (att1 as Record<string, unknown>).parentUuid = userUuid;
157
+ (att1 as Record<string, unknown>).uuid = att1Uuid;
158
+ (att1 as Record<string, unknown>).timestamp = ts();
159
+ (att1 as Record<string, unknown>).sessionId = sessionId;
160
+ (att1 as Record<string, unknown>).cwd = cwdReal;
161
+ lines.push(att1);
162
+
163
+ // Attachment 2: skill_listing — chain from att1.
164
+ const att2Uuid = randomUUID();
165
+ const att2 = clone(attachments.skillListing);
166
+ (att2 as Record<string, unknown>).parentUuid = att1Uuid;
167
+ (att2 as Record<string, unknown>).uuid = att2Uuid;
168
+ (att2 as Record<string, unknown>).timestamp = ts();
169
+ (att2 as Record<string, unknown>).sessionId = sessionId;
170
+ (att2 as Record<string, unknown>).cwd = cwdReal;
171
+ lines.push(att2);
172
+
173
+ const assistantUuid = randomUUID();
174
+ lines.push({
175
+ parentUuid: att2Uuid,
176
+ isSidechain: false,
177
+ message: {
178
+ model: opts.model ?? CLAUDE_DEFAULT_MODEL,
179
+ id: `msg_${randomUUID().replace(/-/g, "").slice(0, 24)}`,
180
+ type: "message",
181
+ role: "assistant",
182
+ content: [{ type: "text", text: pair.assistantText }],
183
+ stop_reason: "end_turn",
184
+ stop_sequence: null,
185
+ stop_details: null,
186
+ usage: {
187
+ input_tokens: 0,
188
+ cache_creation_input_tokens: 0,
189
+ cache_read_input_tokens: 0,
190
+ output_tokens: 0,
191
+ server_tool_use: { web_search_requests: 0, web_fetch_requests: 0 },
192
+ service_tier: "standard",
193
+ cache_creation: { ephemeral_1h_input_tokens: 0, ephemeral_5m_input_tokens: 0 },
194
+ inference_geo: "",
195
+ iterations: [],
196
+ speed: "standard",
197
+ },
198
+ diagnostics: null,
199
+ },
200
+ requestId: `req_${randomUUID().replace(/-/g, "").slice(0, 22)}`,
201
+ type: "assistant",
202
+ uuid: assistantUuid,
203
+ timestamp: ts(),
204
+ userType: "external",
205
+ entrypoint: "sdk-ts",
206
+ cwd: cwdReal,
207
+ sessionId,
208
+ version: CLAUDE_SDK_VERSION,
209
+ gitBranch: CLAUDE_DEFAULT_GIT_BRANCH,
210
+ });
211
+ lastUuid = assistantUuid;
212
+ }
213
+
214
+ const projectsDir = join(homedir(), ".claude", "projects", encodeClaudeCwd(opts.cwd));
215
+ const path = join(projectsDir, `${sessionId}.jsonl`);
216
+ writeJsonlFile(path, lines);
217
+ logger.info(
218
+ { sessionId, path, pairs: pairs.length },
219
+ "writeClaudeRollout: synthetic rollout placed",
220
+ );
221
+ return { sessionId, rolloutPath: path };
222
+ }
223
+
224
+ /**
225
+ * Remove the latest poisoned thinking turn and everything after it from the
226
+ * session JSONL.
227
+ *
228
+ * When a turn ends with stop_reason="max_tokens" while extended thinking is
229
+ * active, the Claude CLI writes the truncated thinking block verbatim to the
230
+ * rollout file. The Anthropic API rejects any subsequent resume that replays
231
+ * this incomplete block with 400 "thinking/redacted_thinking blocks cannot be
232
+ * modified". Dropping the entire poisoned turn (user entry + attachments +
233
+ * truncated assistant) leaves the JSONL in a clean state so the next
234
+ * claude.query({ resume }) can proceed without a 400.
235
+ *
236
+ * Returns true when the JSONL was found and repaired, false otherwise
237
+ * (file missing, no poisoned assistant turn found, I/O error). Error-path
238
+ * callers fall back to clearing the session ID when false is returned.
239
+ */
240
+ export function repairPoisonedRollout(sessionId: string, cwd: string): boolean {
241
+ try {
242
+ const path = join(homedir(), ".claude", "projects", encodeClaudeCwd(cwd), `${sessionId}.jsonl`);
243
+ if (!existsSync(path)) return false;
244
+
245
+ const lines = readFileSync(path, "utf8")
246
+ .split("\n")
247
+ .filter((l) => l.trim());
248
+
249
+ // Walk backwards to find the latest assistant turn that actually contains
250
+ // the poisoned shape: max_tokens plus a thinking/redacted_thinking block.
251
+ // A resume failure can append a fresh user entry after the poisoned turn;
252
+ // trimming the last user would remove only the retry and leave the bad
253
+ // assistant in place.
254
+ let poisonedAssistantIdx = -1;
255
+ for (let i = lines.length - 1; i >= 0; i--) {
256
+ const entry = parseClaudeRolloutLine(lines[i]);
257
+ if (entry && isPoisonedAssistantEntry(entry)) {
258
+ poisonedAssistantIdx = i;
259
+ break;
260
+ }
261
+ }
262
+
263
+ if (poisonedAssistantIdx < 0) {
264
+ logger.warn({ sessionId, path }, "repairPoisonedRollout: no poisoned assistant entry found");
265
+ return false;
266
+ }
267
+
268
+ let turnStartIdx = -1;
269
+ for (let i = poisonedAssistantIdx - 1; i >= 0; i--) {
270
+ const entry = parseClaudeRolloutLine(lines[i]);
271
+ if (entry?.type === "user") {
272
+ turnStartIdx = i;
273
+ break;
274
+ }
275
+ }
276
+
277
+ if (turnStartIdx < 0) {
278
+ logger.warn(
279
+ { sessionId, path, poisonedAssistantIdx },
280
+ "repairPoisonedRollout: poisoned assistant has no preceding user entry",
281
+ );
282
+ return false;
283
+ }
284
+
285
+ const kept = lines.slice(0, turnStartIdx);
286
+ writeFileSync(path, kept.length ? `${kept.join("\n")}\n` : "");
287
+ logger.warn(
288
+ {
289
+ sessionId,
290
+ path,
291
+ poisonedAssistantIdx,
292
+ turnStartIdx,
293
+ droppedEntries: lines.length - turnStartIdx,
294
+ keptEntries: kept.length,
295
+ },
296
+ "repairPoisonedRollout: truncated turn removed from rollout JSONL",
297
+ );
298
+ return true;
299
+ } catch (err) {
300
+ logger.warn({ err, sessionId, cwd }, "repairPoisonedRollout: I/O error");
301
+ return false;
302
+ }
303
+ }
304
+
305
+ type ClaudeRolloutEntry = {
306
+ type?: string;
307
+ message?: {
308
+ stop_reason?: string | null;
309
+ content?: unknown;
310
+ };
311
+ };
312
+
313
+ function parseClaudeRolloutLine(line: string): ClaudeRolloutEntry | null {
314
+ try {
315
+ return JSON.parse(line) as ClaudeRolloutEntry;
316
+ } catch {
317
+ return null;
318
+ }
319
+ }
320
+
321
+ function isPoisonedAssistantEntry(entry: ClaudeRolloutEntry): boolean {
322
+ if (entry.type !== "assistant") return false;
323
+ if (entry.message?.stop_reason !== "max_tokens") return false;
324
+ const content = entry.message.content;
325
+ if (!Array.isArray(content)) return false;
326
+ return content.some((block) => {
327
+ if (!block || typeof block !== "object") return false;
328
+ const type = (block as { type?: unknown }).type;
329
+ return type === "thinking" || type === "redacted_thinking";
330
+ });
331
+ }