negotium 0.1.11 → 0.1.13

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 (165) hide show
  1. package/README.md +11 -2
  2. package/dist/canonical-mcp-bridge.js +2 -3
  3. package/dist/canonical-mcp-bridge.js.map +1 -1
  4. package/dist/{chunk-gz12qcf0.js → chunk-a9y2rwxf.js} +2 -2
  5. package/dist/{chunk-gz12qcf0.js.map → chunk-a9y2rwxf.js.map} +3 -3
  6. package/dist/{chunk-j0kjscb7.js → chunk-s2gez3wg.js} +20 -2
  7. package/dist/{chunk-j0kjscb7.js.map → chunk-s2gez3wg.js.map} +2 -2
  8. package/dist/cron.js +292 -256
  9. package/dist/cron.js.map +23 -22
  10. package/dist/hosted-agent.js +3012 -17
  11. package/dist/hosted-agent.js.map +30 -3
  12. package/dist/main.js +31323 -36
  13. package/dist/main.js.map +198 -3
  14. package/dist/prompts.js +12 -9
  15. package/dist/prompts.js.map +5 -5
  16. package/dist/registry.js +10 -6
  17. package/dist/registry.js.map +3 -3
  18. package/dist/rollout.js +1 -1
  19. package/dist/runtime/cron/store.ts +24 -1
  20. package/dist/runtime/src/application/submit-user-message.ts +3 -0
  21. package/dist/runtime/src/index.ts +7 -0
  22. package/dist/runtime/src/mcp/runtime-spec.ts +3 -0
  23. package/dist/runtime/src/mcp/session-comm/server.ts +1 -1
  24. package/dist/runtime/src/platform/config.ts +2 -2
  25. package/dist/runtime/src/platform/mcp-config.ts +13 -0
  26. package/dist/runtime/src/prompts/builders.ts +20 -6
  27. package/dist/runtime/src/query/active-rooms.ts +2 -0
  28. package/dist/runtime/src/runtime/delivery-ack.ts +112 -0
  29. package/dist/runtime/src/runtime/errors.ts +12 -12
  30. package/dist/runtime/src/runtime/turn-runner.ts +12 -0
  31. package/dist/runtime/src/storage/api-messages.ts +7 -5
  32. package/dist/runtime/src/storage/api-topics.ts +5 -2
  33. package/dist/runtime/src/storage/forum-db.ts +4 -1
  34. package/dist/runtime/src/storage/runtime-turn-requests.ts +1 -0
  35. package/dist/runtime/src/storage/session-asks.ts +13 -5
  36. package/dist/runtime/src/storage/storage-contract.ts +54 -0
  37. package/dist/runtime/src/storage/storage-host.ts +89 -36
  38. package/dist/runtime/src/storage/storage-public-host.ts +16 -0
  39. package/dist/runtime/src/storage/storage-public.ts +21 -5
  40. package/dist/runtime/src/storage/token-stats.ts +1 -1
  41. package/dist/runtime/src/types/api.ts +2 -0
  42. package/dist/runtime/src/types.ts +2 -0
  43. package/dist/runtime/src/version.ts +1 -1
  44. package/dist/runtime-helpers.js +29 -18
  45. package/dist/runtime-helpers.js.map +8 -7
  46. package/dist/storage.js +260 -202
  47. package/dist/storage.js.map +11 -9
  48. package/dist/types/packages/core/src/mcp/runtime-spec.d.ts +2 -0
  49. package/dist/types/packages/core/src/platform/mcp-config.d.ts +4 -0
  50. package/dist/types/packages/core/src/prompts/builders.d.ts +2 -0
  51. package/dist/types/packages/core/src/storage/api-messages.d.ts +6 -6
  52. package/dist/types/packages/core/src/storage/api-topics.d.ts +20 -2
  53. package/dist/types/packages/core/src/storage/forum-db.d.ts +2 -1
  54. package/dist/types/packages/core/src/storage/session-asks.d.ts +3 -2
  55. package/dist/types/packages/core/src/storage/storage-contract.d.ts +46 -0
  56. package/dist/types/packages/core/src/storage/storage-host.d.ts +12 -19
  57. package/dist/types/packages/core/src/storage/storage-public-host.d.ts +7 -0
  58. package/dist/types/packages/core/src/storage/storage-public.d.ts +4 -2
  59. package/dist/types/packages/core/src/types/api.d.ts +2 -0
  60. package/dist/types/packages/core/src/types.d.ts +2 -0
  61. package/dist/types/packages/core/src/version.d.ts +1 -1
  62. package/dist/types/packages/module-cron/src/store.d.ts +22 -2
  63. package/dist/vault.js +2 -2
  64. package/dist/vault.js.map +3 -3
  65. package/package.json +1 -1
  66. package/dist/chunk-01xcc856.js +0 -149
  67. package/dist/chunk-01xcc856.js.map +0 -10
  68. package/dist/chunk-1a6kv9vt.js +0 -118
  69. package/dist/chunk-1a6kv9vt.js.map +0 -10
  70. package/dist/chunk-1b62d730.js +0 -117
  71. package/dist/chunk-1b62d730.js.map +0 -10
  72. package/dist/chunk-1k7t7bnx.js +0 -97
  73. package/dist/chunk-1k7t7bnx.js.map +0 -10
  74. package/dist/chunk-28apmz4r.js +0 -431
  75. package/dist/chunk-28apmz4r.js.map +0 -11
  76. package/dist/chunk-2fvydfkx.js +0 -141
  77. package/dist/chunk-2fvydfkx.js.map +0 -10
  78. package/dist/chunk-41ygvg5k.js +0 -2387
  79. package/dist/chunk-41ygvg5k.js.map +0 -26
  80. package/dist/chunk-4q966ttx.js +0 -371
  81. package/dist/chunk-4q966ttx.js.map +0 -11
  82. package/dist/chunk-51rcsavw.js +0 -1712
  83. package/dist/chunk-51rcsavw.js.map +0 -18
  84. package/dist/chunk-59d0shyj.js +0 -103
  85. package/dist/chunk-59d0shyj.js.map +0 -10
  86. package/dist/chunk-5fzgvn79.js +0 -3536
  87. package/dist/chunk-5fzgvn79.js.map +0 -20
  88. package/dist/chunk-61px0717.js +0 -153
  89. package/dist/chunk-61px0717.js.map +0 -11
  90. package/dist/chunk-66y0hr8c.js +0 -2136
  91. package/dist/chunk-66y0hr8c.js.map +0 -29
  92. package/dist/chunk-69k45zg8.js +0 -250
  93. package/dist/chunk-69k45zg8.js.map +0 -11
  94. package/dist/chunk-6ah5farg.js +0 -130
  95. package/dist/chunk-6ah5farg.js.map +0 -10
  96. package/dist/chunk-6d4fmea5.js +0 -117
  97. package/dist/chunk-6d4fmea5.js.map +0 -12
  98. package/dist/chunk-8cahx7xc.js +0 -46
  99. package/dist/chunk-8cahx7xc.js.map +0 -10
  100. package/dist/chunk-8f20fk4n.js +0 -252
  101. package/dist/chunk-8f20fk4n.js.map +0 -10
  102. package/dist/chunk-91ckrvk8.js +0 -318
  103. package/dist/chunk-91ckrvk8.js.map +0 -10
  104. package/dist/chunk-93krf6ng.js +0 -3069
  105. package/dist/chunk-93krf6ng.js.map +0 -32
  106. package/dist/chunk-9qx1t77a.js +0 -214
  107. package/dist/chunk-9qx1t77a.js.map +0 -10
  108. package/dist/chunk-beqhdr9w.js +0 -589
  109. package/dist/chunk-beqhdr9w.js.map +0 -11
  110. package/dist/chunk-bftftsgq.js +0 -1637
  111. package/dist/chunk-bftftsgq.js.map +0 -15
  112. package/dist/chunk-c500xe9z.js +0 -33
  113. package/dist/chunk-c500xe9z.js.map +0 -10
  114. package/dist/chunk-cjsnkvq2.js +0 -23
  115. package/dist/chunk-cjsnkvq2.js.map +0 -10
  116. package/dist/chunk-cxa7yghg.js +0 -577
  117. package/dist/chunk-cxa7yghg.js.map +0 -14
  118. package/dist/chunk-edb07sjp.js +0 -567
  119. package/dist/chunk-edb07sjp.js.map +0 -14
  120. package/dist/chunk-f361dwba.js +0 -589
  121. package/dist/chunk-f361dwba.js.map +0 -11
  122. package/dist/chunk-f5xgprg1.js +0 -59
  123. package/dist/chunk-f5xgprg1.js.map +0 -10
  124. package/dist/chunk-ffefbd3r.js +0 -17
  125. package/dist/chunk-ffefbd3r.js.map +0 -10
  126. package/dist/chunk-fswhzdf5.js +0 -64
  127. package/dist/chunk-fswhzdf5.js.map +0 -10
  128. package/dist/chunk-g144wdrs.js +0 -2907
  129. package/dist/chunk-g144wdrs.js.map +0 -24
  130. package/dist/chunk-h980hb2n.js +0 -512
  131. package/dist/chunk-h980hb2n.js.map +0 -13
  132. package/dist/chunk-h9vbhhgr.js +0 -1337
  133. package/dist/chunk-h9vbhhgr.js.map +0 -16
  134. package/dist/chunk-hwy3n31w.js +0 -21
  135. package/dist/chunk-hwy3n31w.js.map +0 -9
  136. package/dist/chunk-jtrk0khr.js +0 -108
  137. package/dist/chunk-jtrk0khr.js.map +0 -10
  138. package/dist/chunk-jzz91jwh.js +0 -70
  139. package/dist/chunk-jzz91jwh.js.map +0 -10
  140. package/dist/chunk-m9e3tbr8.js +0 -708
  141. package/dist/chunk-m9e3tbr8.js.map +0 -14
  142. package/dist/chunk-mh5mexsf.js +0 -147
  143. package/dist/chunk-mh5mexsf.js.map +0 -11
  144. package/dist/chunk-npd8n03v.js +0 -2152
  145. package/dist/chunk-npd8n03v.js.map +0 -15
  146. package/dist/chunk-pg1azh6n.js +0 -2
  147. package/dist/chunk-pg1azh6n.js.map +0 -9
  148. package/dist/chunk-qwy57177.js +0 -237
  149. package/dist/chunk-qwy57177.js.map +0 -11
  150. package/dist/chunk-r31q21h5.js +0 -53
  151. package/dist/chunk-r31q21h5.js.map +0 -10
  152. package/dist/chunk-rbvctv1n.js +0 -366
  153. package/dist/chunk-rbvctv1n.js.map +0 -10
  154. package/dist/chunk-sa874j5c.js +0 -9
  155. package/dist/chunk-sa874j5c.js.map +0 -10
  156. package/dist/chunk-sctcs5zb.js +0 -281
  157. package/dist/chunk-sctcs5zb.js.map +0 -10
  158. package/dist/chunk-tsstrcfc.js +0 -122
  159. package/dist/chunk-tsstrcfc.js.map +0 -10
  160. package/dist/chunk-wk2qf31s.js +0 -58
  161. package/dist/chunk-wk2qf31s.js.map +0 -10
  162. package/dist/chunk-wx7ag4dd.js +0 -2936
  163. package/dist/chunk-wx7ag4dd.js.map +0 -20
  164. package/dist/chunk-znx1fpfd.js +0 -61
  165. package/dist/chunk-znx1fpfd.js.map +0 -10
@@ -1,2387 +0,0 @@
1
- import {
2
- claudeRegistry,
3
- migrateCodexRolloutNativeMultiAgentMetadata,
4
- readLatestCodexContextUsage
5
- } from "./chunk-m9e3tbr8.js";
6
- import {
7
- AGENT_HEALTH_SERVER,
8
- CANONICAL_MCP_PROXY_SERVER,
9
- CLAUDE_EXECUTABLE,
10
- DATA_DIR,
11
- FALLBACK_AGENT,
12
- FILE_TAG_REGEX,
13
- NEGOTIUM_PORT,
14
- PADDLEOCR_SERVER,
15
- RUNTIME_MCP_SECRET,
16
- SESSION_COMM_SERVER,
17
- SYSTEM_HEALTH_SERVER,
18
- TASK_SERVER,
19
- TOKEN_STATS_SERVER,
20
- TSCONFIG_PATH,
21
- TSX_BIN,
22
- VAULT_MASTER_KEY,
23
- VAULT_SERVER,
24
- WIKI_SERVER,
25
- codexAuthFilePath,
26
- isAgentKind,
27
- resolveTopicWorkspaceDir
28
- } from "./chunk-qwy57177.js";
29
- import {
30
- canonicalMcpBridgeEnv
31
- } from "./chunk-j0kjscb7.js";
32
- import {
33
- logger
34
- } from "./chunk-ffefbd3r.js";
35
- import {
36
- Database
37
- } from "./chunk-f5xgprg1.js";
38
-
39
- // ../../packages/core/src/agents/codex-tree-kill.ts
40
- import { execFileSync } from "child_process";
41
- var SIGKILL_DELAY_MS = 5000;
42
- var CODEX_PROC_NAME_MATCH = /codex/i;
43
- var TREE_DEPTH_CAP = 16;
44
- function getDirectChildren(parentPid) {
45
- try {
46
- const out = execFileSync("pgrep", ["-P", String(parentPid)], {
47
- encoding: "utf8",
48
- stdio: ["ignore", "pipe", "ignore"]
49
- }).trim();
50
- if (!out)
51
- return [];
52
- return out.split(`
53
- `).map((s) => Number(s)).filter((n) => Number.isFinite(n) && n > 0);
54
- } catch {
55
- return [];
56
- }
57
- }
58
- function getProcName(pid) {
59
- try {
60
- return execFileSync("ps", ["-p", String(pid), "-o", "comm="], {
61
- encoding: "utf8",
62
- stdio: ["ignore", "pipe", "ignore"]
63
- }).trim();
64
- } catch {
65
- return "";
66
- }
67
- }
68
- function getProcLstart(pid) {
69
- try {
70
- return execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
71
- encoding: "utf8",
72
- stdio: ["ignore", "pipe", "ignore"]
73
- }).trim();
74
- } catch {
75
- return "";
76
- }
77
- }
78
- function collectTree(rootPid, out = new Set, depth = 0) {
79
- if (out.has(rootPid))
80
- return out;
81
- out.add(rootPid);
82
- if (depth >= TREE_DEPTH_CAP) {
83
- logger.warn({ rootPid, depth, cap: TREE_DEPTH_CAP }, "codex-tree-kill: tree depth cap reached, descendants beyond this point not collected");
84
- return out;
85
- }
86
- for (const child of getDirectChildren(rootPid)) {
87
- collectTree(child, out, depth + 1);
88
- }
89
- return out;
90
- }
91
- function snapshotCodexChildren() {
92
- const out = new Map;
93
- for (const pid of getDirectChildren(process.pid)) {
94
- const name = getProcName(pid);
95
- if (!name || !CODEX_PROC_NAME_MATCH.test(name))
96
- continue;
97
- const lstart = getProcLstart(pid);
98
- out.set(pid, lstart);
99
- }
100
- return out;
101
- }
102
- var globalOwnedCodexPids = new Set;
103
- function registerOwnedCodexPids(pids) {
104
- for (const p of pids)
105
- globalOwnedCodexPids.add(p);
106
- }
107
- function unregisterOwnedCodexPids(pids) {
108
- for (const p of pids)
109
- globalOwnedCodexPids.delete(p);
110
- }
111
- function findNewCodexChildren(baseline) {
112
- const current = snapshotCodexChildren();
113
- const novel = [];
114
- for (const [pid, lstart] of current) {
115
- const baselineLstart = baseline.get(pid);
116
- if (baselineLstart !== undefined && baselineLstart === lstart)
117
- continue;
118
- if (globalOwnedCodexPids.has(pid))
119
- continue;
120
- novel.push(pid);
121
- }
122
- return novel;
123
- }
124
- var codexSpawnChain = Promise.resolve();
125
- async function acquireCodexSpawnLock() {
126
- const prev = codexSpawnChain;
127
- let release;
128
- let released = false;
129
- codexSpawnChain = new Promise((r) => {
130
- release = () => {
131
- if (released)
132
- return;
133
- released = true;
134
- r();
135
- };
136
- });
137
- await prev;
138
- return release;
139
- }
140
- function isAlive(pid) {
141
- try {
142
- process.kill(pid, 0);
143
- return true;
144
- } catch {
145
- return false;
146
- }
147
- }
148
- function isSameProcess(pid, lstart) {
149
- return lstart !== "" && isAlive(pid) && getProcLstart(pid) === lstart;
150
- }
151
- function signalCodexTrees(rootPids) {
152
- if (rootPids.length === 0)
153
- return null;
154
- const targets = new Map;
155
- for (const root of rootPids) {
156
- for (const pid of collectTree(root)) {
157
- if (!targets.has(pid))
158
- targets.set(pid, getProcLstart(pid));
159
- }
160
- }
161
- logger.warn({ rootPids, totalPids: targets.size }, "codex-tree-kill: SIGTERM process tree");
162
- for (const pid of targets.keys()) {
163
- try {
164
- process.kill(pid, "SIGTERM");
165
- } catch {}
166
- }
167
- return { rootPids, targets };
168
- }
169
- function hasSameProcessSurvivors(targets) {
170
- for (const [pid, lstart] of targets) {
171
- if (isSameProcess(pid, lstart))
172
- return true;
173
- }
174
- return false;
175
- }
176
- function killCodexStragglers({ rootPids, targets }) {
177
- const aliveTargets = [];
178
- let droppedAsPossibleReuse = 0;
179
- for (const [pid, lstart] of targets) {
180
- if (isSameProcess(pid, lstart)) {
181
- aliveTargets.push(pid);
182
- } else if (isAlive(pid)) {
183
- droppedAsPossibleReuse++;
184
- }
185
- }
186
- if (aliveTargets.length === 0)
187
- return;
188
- const reachable = new Set(aliveTargets);
189
- for (const root of rootPids) {
190
- const rootLstart = targets.get(root);
191
- if (rootLstart && isSameProcess(root, rootLstart)) {
192
- for (const pid of collectTree(root))
193
- reachable.add(pid);
194
- }
195
- }
196
- const stragglers = [];
197
- for (const pid of reachable) {
198
- const lstart = targets.get(pid);
199
- if (lstart && isSameProcess(pid, lstart))
200
- stragglers.push(pid);
201
- else if (isAlive(pid))
202
- droppedAsPossibleReuse++;
203
- }
204
- logger.warn({
205
- aliveRoots: rootPids.filter((pid) => {
206
- const lstart = targets.get(pid);
207
- return lstart ? isSameProcess(pid, lstart) : false;
208
- }),
209
- totalStragglers: stragglers.length,
210
- droppedAsPossibleReuse
211
- }, "codex-tree-kill: SIGKILL fallback after SIGTERM timeout");
212
- for (const pid of stragglers) {
213
- try {
214
- process.kill(pid, "SIGKILL");
215
- } catch {}
216
- }
217
- }
218
- function killCodexTrees(rootPids) {
219
- const plan = signalCodexTrees(rootPids);
220
- if (!plan)
221
- return;
222
- const killTimer = setTimeout(() => {
223
- killCodexStragglers(plan);
224
- }, SIGKILL_DELAY_MS);
225
- killTimer.unref?.();
226
- }
227
- async function killOwnedCodexTreesForShutdown(graceMs = 3000) {
228
- const roots = new Set(globalOwnedCodexPids);
229
- for (const pid of snapshotCodexChildren().keys())
230
- roots.add(pid);
231
- const plan = signalCodexTrees([...roots]);
232
- if (!plan)
233
- return;
234
- const deadline = Date.now() + Math.max(0, graceMs);
235
- while (Date.now() < deadline && hasSameProcessSurvivors(plan.targets)) {
236
- await new Promise((resolve) => setTimeout(resolve, Math.min(100, Math.max(1, deadline - Date.now()))));
237
- }
238
- killCodexStragglers(plan);
239
- }
240
-
241
- // ../../packages/core/src/security/sensitive-path.ts
242
- import { realpathSync } from "fs";
243
- import { resolve } from "path";
244
- var SENSITIVE_PATH_PATTERNS = [
245
- /\/\.env(\.|$)/i,
246
- /\/\.ssh\//i,
247
- /\/\.aws\//i,
248
- /\/\.gnupg\//i,
249
- /\/\.netrc$/i,
250
- /\/\.npmrc$/i,
251
- /\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)(\.pub)?$/i,
252
- /\.(pem|key|p12|pfx|cer|crt)$/i,
253
- /\/Library\/Keychains\//i,
254
- /\/vault\.db(-wal|-shm|-journal)?$/i,
255
- /\/vault-master-key$/i,
256
- /\/runtime-mcp-secret$/i,
257
- /\/sessions\.db(-wal|-shm|-journal)?$/i
258
- ];
259
- function isSensitivePath(filePath) {
260
- const normalized = resolve(filePath);
261
- if (SENSITIVE_PATH_PATTERNS.some((p) => p.test(normalized)))
262
- return true;
263
- try {
264
- const real = realpathSync(normalized);
265
- if (real !== normalized)
266
- return SENSITIVE_PATH_PATTERNS.some((p) => p.test(real));
267
- } catch {}
268
- return false;
269
- }
270
-
271
- // ../../packages/core/src/storage/vault.ts
272
- import { chmodSync, mkdirSync } from "fs";
273
- import { join } from "path";
274
-
275
- // ../../packages/core/src/storage/vault-crypto.ts
276
- import { createCipheriv, createDecipheriv, createHash, randomBytes } from "crypto";
277
- var ENVELOPE_PREFIX = "otium-vault:v1:";
278
- var IV_BYTES = 12;
279
- var KEY_BYTES = 32;
280
- function encryptionKey(masterKey = VAULT_MASTER_KEY) {
281
- return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
282
- }
283
- function aad(userId, key) {
284
- return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
285
- }
286
- function isEncryptedVaultValue(value) {
287
- return value.startsWith(ENVELOPE_PREFIX);
288
- }
289
- function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
290
- const iv = randomBytes(IV_BYTES);
291
- const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
292
- cipher.setAAD(aad(userId, key));
293
- const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
294
- const tag = cipher.getAuthTag();
295
- return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
296
- }
297
- function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
298
- if (!isEncryptedVaultValue(storedValue)) {
299
- return { value: storedValue, legacyPlaintext: true };
300
- }
301
- const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
302
- const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
303
- if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
304
- throw new Error("Invalid encrypted vault value");
305
- }
306
- const iv = Buffer.from(ivPart, "base64url");
307
- const ciphertext = Buffer.from(ciphertextPart, "base64url");
308
- const tag = Buffer.from(tagPart, "base64url");
309
- if (iv.length !== IV_BYTES || tag.length !== 16) {
310
- throw new Error("Invalid encrypted vault value");
311
- }
312
- const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
313
- decipher.setAAD(aad(userId, key));
314
- decipher.setAuthTag(tag);
315
- const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
316
- return { value: plaintext.toString("utf8"), legacyPlaintext: false };
317
- }
318
-
319
- // ../../packages/core/src/storage/vault.ts
320
- var vaultDb;
321
- var vaultMasterKey = VAULT_MASTER_KEY;
322
- function initializeVaultDatabase(database) {
323
- database.exec("PRAGMA journal_mode = WAL");
324
- database.exec("PRAGMA busy_timeout = 5000");
325
- database.exec(`
326
- CREATE TABLE IF NOT EXISTS vault (
327
- user_id TEXT NOT NULL,
328
- key TEXT NOT NULL,
329
- value TEXT NOT NULL,
330
- description TEXT NOT NULL DEFAULT '',
331
- PRIMARY KEY (user_id, key)
332
- )
333
- `);
334
- {
335
- const cols = database.prepare("PRAGMA table_info(vault)").all();
336
- const uid = cols.find((c) => c.name === "user_id");
337
- if (uid && uid.type.toUpperCase() === "INTEGER") {
338
- database.exec("BEGIN");
339
- database.exec(`
340
- CREATE TABLE vault_migrated (
341
- user_id TEXT NOT NULL,
342
- key TEXT NOT NULL,
343
- value TEXT NOT NULL,
344
- description TEXT NOT NULL DEFAULT '',
345
- PRIMARY KEY (user_id, key)
346
- )
347
- `);
348
- database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
349
- database.exec("DROP TABLE vault");
350
- database.exec("ALTER TABLE vault_migrated RENAME TO vault");
351
- database.exec("COMMIT");
352
- }
353
- }
354
- }
355
- function openVaultDatabase(dataDir) {
356
- const path = join(dataDir, "vault.db");
357
- mkdirSync(dataDir, { recursive: true });
358
- const database = new Database(path, { create: true });
359
- chmodSync(path, 384);
360
- initializeVaultDatabase(database);
361
- return database;
362
- }
363
- function activeVaultDatabase() {
364
- if (!vaultDb)
365
- vaultDb = openVaultDatabase(DATA_DIR);
366
- return vaultDb;
367
- }
368
- var VAULT_KEY_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
369
- var VAULT_VALUE_MIN_BYTES = 4;
370
- var VAULT_VALUE_MAX_BYTES = 64 * 1024;
371
- var VAULT_DESCRIPTION_MAX_LENGTH = 500;
372
- function normalizeVaultKey(key) {
373
- return key.trim().toUpperCase();
374
- }
375
- function validateVaultKey(key) {
376
- return VAULT_KEY_PATTERN.test(normalizeVaultKey(key));
377
- }
378
- function decryptRow(userId, key, storedValue) {
379
- const database = activeVaultDatabase();
380
- const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
381
- if (decoded.legacyPlaintext) {
382
- database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
383
- }
384
- return decoded.value;
385
- }
386
- function vaultListWithValues(userId) {
387
- const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
388
- return rows.map((row) => ({
389
- key: row.key,
390
- description: row.description,
391
- value: decryptRow(userId, row.key, row.value)
392
- }));
393
- }
394
- function vaultSet(userId, key, value, description = "") {
395
- const normalizedKey = normalizeVaultKey(key);
396
- activeVaultDatabase().prepare(`INSERT INTO vault (user_id, key, value, description) VALUES (?, ?, ?, ?)
397
- ON CONFLICT (user_id, key) DO UPDATE
398
- SET value = excluded.value, description = excluded.description`).run(userId, normalizedKey, encryptVaultValue(userId, normalizedKey, value, vaultMasterKey), description);
399
- }
400
- function vaultHasKey(userId, key) {
401
- const row = activeVaultDatabase().prepare("SELECT 1 FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizeVaultKey(key));
402
- return row != null;
403
- }
404
- function vaultDel(userId, key) {
405
- const result = activeVaultDatabase().prepare("DELETE FROM vault WHERE user_id = ? AND key = ?").run(userId, normalizeVaultKey(key));
406
- return result.changes > 0;
407
- }
408
- function vaultList(userId) {
409
- return activeVaultDatabase().prepare("SELECT key, description FROM vault WHERE user_id = ? ORDER BY key").all(userId);
410
- }
411
- function valueReferencesVaultKey(userId, value) {
412
- const keys = new Set(vaultList(userId).map((entry) => entry.key));
413
- const visit = (candidate) => {
414
- if (typeof candidate === "string") {
415
- for (const match of candidate.matchAll(/\{\{([^}]+)\}\}/g)) {
416
- if (keys.has(normalizeVaultKey(match[1] ?? "")))
417
- return true;
418
- }
419
- return false;
420
- }
421
- if (Array.isArray(candidate))
422
- return candidate.some(visit);
423
- if (candidate && typeof candidate === "object") {
424
- return Object.values(candidate).some(visit);
425
- }
426
- return false;
427
- };
428
- return visit(value);
429
- }
430
- function encodedSecretForms(value) {
431
- const forms = new Set([
432
- value,
433
- encodeURIComponent(value),
434
- Buffer.from(value, "utf8").toString("base64"),
435
- Buffer.from(value, "utf8").toString("base64url"),
436
- Buffer.from(value, "utf8").toString("hex")
437
- ]);
438
- forms.delete("");
439
- return [...forms].sort((a, b) => b.length - a.length);
440
- }
441
- function redactVaultSecrets(userId, text) {
442
- const candidates = vaultListWithValues(userId).flatMap((entry) => encodedSecretForms(entry.value).map((form) => ({ form, key: entry.key }))).sort((a, b) => b.form.length - a.form.length || a.key.localeCompare(b.key));
443
- if (candidates.length === 0)
444
- return text;
445
- const candidatesByFirstCharacter = new Map;
446
- for (const candidate of candidates) {
447
- const first = candidate.form[0];
448
- if (!first)
449
- continue;
450
- const bucket = candidatesByFirstCharacter.get(first) ?? [];
451
- bucket.push(candidate);
452
- candidatesByFirstCharacter.set(first, bucket);
453
- }
454
- let redacted = "";
455
- let offset = 0;
456
- while (offset < text.length) {
457
- const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
458
- if (!match) {
459
- redacted += text[offset];
460
- offset += 1;
461
- continue;
462
- }
463
- redacted += `[REDACTED:${match.key}]`;
464
- offset += match.form.length;
465
- }
466
- return redacted;
467
- }
468
-
469
- // ../../packages/core/src/mcp/runtime-spec.ts
470
- import { createHmac, timingSafeEqual } from "crypto";
471
- var RUNTIME_MCP_KEY = "runtime";
472
- var RUNTIME_MCP_BASE_PATH = "/mcp/runtime";
473
- var TOKEN_TTL_MS = 4 * 60 * 60 * 1000;
474
- var CLAUDE_MCP_TOOL_TIMEOUT_MS = 600000;
475
- var runtimePort = NEGOTIUM_PORT;
476
- function setRuntimeMcpPort(port) {
477
- runtimePort = port;
478
- }
479
- function getRuntimeMcpPort() {
480
- return runtimePort;
481
- }
482
- function encodeTokenPart(value) {
483
- return Buffer.from(JSON.stringify(value), "utf-8").toString("base64url");
484
- }
485
- function decodeTokenPart(part) {
486
- return JSON.parse(Buffer.from(part, "base64url").toString("utf-8"));
487
- }
488
- function signTokenPayload(payloadPart) {
489
- return createHmac("sha256", RUNTIME_MCP_SECRET).update(payloadPart).digest("base64url");
490
- }
491
- function safeEqual(a, b) {
492
- const aa = Buffer.from(a);
493
- const bb = Buffer.from(b);
494
- return aa.length === bb.length && timingSafeEqual(aa, bb);
495
- }
496
- function isRuntimeMcpContext(value) {
497
- if (!value || typeof value !== "object" || Array.isArray(value))
498
- return false;
499
- const ctx = value;
500
- return typeof ctx.userId === "string" && typeof ctx.topicId === "string" && typeof ctx.topicTitle === "string" && typeof ctx.cwd === "string" && typeof ctx.agent === "string" && isAgentKind(ctx.agent) && (ctx.queryId === undefined || typeof ctx.queryId === "string") && (ctx.model === undefined || typeof ctx.model === "string") && (ctx.currentUserPrompt === undefined || typeof ctx.currentUserPrompt === "string") && (ctx.autoContinue === undefined || typeof ctx.autoContinue === "boolean") && (ctx.visualTools === undefined || typeof ctx.visualTools === "boolean") && (ctx.peerBridge === undefined || typeof ctx.peerBridge.hubCellId === "string" && typeof ctx.peerBridge.hostTopicId === "string" && typeof ctx.peerBridge.hostQueryId === "string" && typeof ctx.peerBridge.canSpawnSubagents === "boolean");
501
- }
502
- function issueRuntimeMcpToken(ctx) {
503
- const payloadPart = encodeTokenPart({
504
- v: 1,
505
- exp: Date.now() + TOKEN_TTL_MS,
506
- ctx
507
- });
508
- return `${payloadPart}.${signTokenPayload(payloadPart)}`;
509
- }
510
- function resolveRuntimeMcpToken(token) {
511
- if (!token)
512
- return null;
513
- const [payloadPart, signature, extra] = token.split(".");
514
- if (!payloadPart || !signature || extra !== undefined)
515
- return null;
516
- if (!safeEqual(signature, signTokenPayload(payloadPart)))
517
- return null;
518
- try {
519
- const payload = decodeTokenPart(payloadPart);
520
- if (payload.v !== 1 || typeof payload.exp !== "number" || payload.exp <= Date.now())
521
- return null;
522
- if (!isRuntimeMcpContext(payload.ctx))
523
- return null;
524
- return payload.ctx;
525
- } catch {
526
- return null;
527
- }
528
- }
529
- function buildRuntimeMcpSpec(agent, ctx) {
530
- const token = issueRuntimeMcpToken(ctx);
531
- const base = `http://127.0.0.1:${runtimePort}${RUNTIME_MCP_BASE_PATH}`;
532
- const query = `token=${encodeURIComponent(token)}`;
533
- if (agent === "codex")
534
- return { url: `${base}/mcp?${query}` };
535
- return {
536
- type: "sse",
537
- url: `${base}/sse?${query}`,
538
- timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS
539
- };
540
- }
541
-
542
- // ../../packages/core/src/platform/mcp-config.ts
543
- import { createHmac as createHmac2 } from "crypto";
544
-
545
- // ../../packages/core/src/mcp/session-comm/bridge-ipc-config.ts
546
- var registrations = [];
547
- function registerPeerSessionBridgeIpcConfig(config) {
548
- const registration = { id: Symbol("peer-session-bridge-ipc"), config: { ...config } };
549
- registrations.push(registration);
550
- let disposed = false;
551
- return () => {
552
- if (disposed)
553
- return;
554
- disposed = true;
555
- const index = registrations.findIndex((entry) => entry.id === registration.id);
556
- if (index >= 0)
557
- registrations.splice(index, 1);
558
- };
559
- }
560
- function peerSessionBridgeIpcEnv() {
561
- const active = registrations.at(-1)?.config;
562
- if (!active)
563
- return;
564
- return {
565
- NEGOTIUM_PEER_SESSION_BRIDGE_URL: active.url,
566
- NEGOTIUM_PEER_SESSION_BRIDGE_TOKEN: active.token
567
- };
568
- }
569
-
570
- // ../../packages/core/src/platform/mcp-config.ts
571
- function buildStdioMcpServer(agent, serverFile, serverArgs, env) {
572
- if (agent === "codex") {
573
- return {
574
- command: "node",
575
- args: [TSX_BIN, serverFile, ...serverArgs],
576
- env: { TSX_TSCONFIG_PATH: TSCONFIG_PATH, ...env }
577
- };
578
- }
579
- return {
580
- command: "bun",
581
- args: ["run", serverFile, ...serverArgs],
582
- ...env ? { env } : {}
583
- };
584
- }
585
- var _playwrightUnavailableNotifier;
586
- var _playwrightUnavailableLastNotifiedAt = new Map;
587
- var _PLAYWRIGHT_UNAVAILABLE_COOLDOWN_MS = 5 * 60000;
588
- var _playwrightUnavailableThisTurn = new Set;
589
- function _playwrightUnavailableKey(userId, topic) {
590
- return `${userId}:${topic ?? ""}`;
591
- }
592
- function setPlaywrightUnavailableNotifier(cb) {
593
- _playwrightUnavailableNotifier = cb;
594
- }
595
- function markPlaywrightUnavailable(ctx) {
596
- _markPlaywrightUnavailable(ctx);
597
- }
598
- function _markPlaywrightUnavailable(ctx) {
599
- _playwrightUnavailableThisTurn.add(_playwrightUnavailableKey(ctx.userId, ctx.topic));
600
- if (!_playwrightUnavailableNotifier)
601
- return;
602
- const key = _playwrightUnavailableKey(ctx.userId, ctx.topic);
603
- const now = Date.now();
604
- const last = _playwrightUnavailableLastNotifiedAt.get(key) ?? 0;
605
- if (now - last < _PLAYWRIGHT_UNAVAILABLE_COOLDOWN_MS)
606
- return;
607
- _playwrightUnavailableLastNotifiedAt.set(key, now);
608
- try {
609
- _playwrightUnavailableNotifier(ctx);
610
- } catch (err) {
611
- logger.warn({ err }, "playwright unavailable notifier threw");
612
- }
613
- }
614
- function consumePlaywrightUnavailable(userId, topic) {
615
- const key = _playwrightUnavailableKey(userId, topic);
616
- if (!_playwrightUnavailableThisTurn.has(key))
617
- return false;
618
- _playwrightUnavailableThisTurn.delete(key);
619
- return true;
620
- }
621
- var CODEX_BROWSER_CAPABILITY_ENV = "NEGOTIUM_BROWSER_CAPABILITY";
622
- function browserOwnerCapability(capability, owner) {
623
- return createHmac2("sha256", capability).update(owner).digest("hex");
624
- }
625
- function browserOwnerForContext(ctx) {
626
- if (ctx.topicId)
627
- return `topic:${ctx.topicId}`;
628
- if (ctx.userId && ctx.session)
629
- return `user:${ctx.userId}:${ctx.session}`;
630
- return;
631
- }
632
- function playwrightTransport(port, owner, capability, agent) {
633
- const ownerCapability = browserOwnerCapability(capability, owner);
634
- if (agent === "codex") {
635
- return {
636
- url: `http://127.0.0.1:${port}/mcp`,
637
- http_headers: { "X-Browser-Owner": owner },
638
- env_http_headers: { "X-Browser-Capability": CODEX_BROWSER_CAPABILITY_ENV }
639
- };
640
- }
641
- if (agent === "maestro") {
642
- const query = new URLSearchParams({ owner, capability: ownerCapability });
643
- return {
644
- type: "sse",
645
- url: `http://127.0.0.1:${port}/sse?${query}`
646
- };
647
- }
648
- return {
649
- type: "sse",
650
- url: `http://127.0.0.1:${port}/sse`,
651
- headers: { "X-Browser-Owner": owner, "X-Browser-Capability": ownerCapability }
652
- };
653
- }
654
- function longLivedHttpMcp(agent, port) {
655
- return agent === "codex" ? { url: `http://127.0.0.1:${port}/mcp` } : { type: "sse", url: `http://127.0.0.1:${port}/sse` };
656
- }
657
- var MCP_CATALOG = {
658
- playwright: {
659
- scopes: ["dm", "forum", "fork", "cron"],
660
- build({ userId, session, topicId, playwrightPort, playwrightCapability, agent }) {
661
- if (playwrightPort && playwrightCapability) {
662
- const owner = browserOwnerForContext({ userId, session, topicId });
663
- if (!owner)
664
- return null;
665
- return playwrightTransport(playwrightPort, owner, playwrightCapability, agent);
666
- }
667
- _markPlaywrightUnavailable({
668
- userId,
669
- topic: session,
670
- agent
671
- });
672
- return null;
673
- }
674
- },
675
- [RUNTIME_MCP_KEY]: {
676
- scopes: ["forum", "manager", "fork", "cron"],
677
- forumRequired: true,
678
- build({
679
- userId,
680
- session,
681
- topicId,
682
- queryId,
683
- agent,
684
- cwd,
685
- model,
686
- currentUserPrompt,
687
- autoContinue,
688
- visualTools,
689
- peerBridge
690
- }) {
691
- if (!topicId || !agent)
692
- return null;
693
- return buildRuntimeMcpSpec(agent, {
694
- userId,
695
- topicId,
696
- topicTitle: session,
697
- queryId,
698
- cwd: cwd ?? resolveTopicWorkspaceDir(topicId),
699
- agent,
700
- model,
701
- currentUserPrompt,
702
- autoContinue,
703
- visualTools,
704
- peerBridge
705
- });
706
- }
707
- },
708
- paddleocr: {
709
- scopes: ["dm", "forum", "fork", "cron"],
710
- build({ agent }) {
711
- return buildStdioMcpServer(agent, PADDLEOCR_SERVER, []);
712
- }
713
- },
714
- "token-stats": {
715
- scopes: ["dm", "forum", "manager", "cron"],
716
- forumRequired: true,
717
- build({ userId, agent }) {
718
- return buildStdioMcpServer(agent, TOKEN_STATS_SERVER, [`--user-id=${userId}`]);
719
- }
720
- },
721
- task: {
722
- scopes: ["dm", "forum", "manager", "cron"],
723
- forumRequired: true,
724
- build({ userId, session, topicId, queryId, agent, peerBridge }) {
725
- if (peerBridge) {
726
- if (!topicId || !queryId)
727
- return null;
728
- const env = canonicalMcpBridgeEnv({
729
- surface: "task",
730
- userId,
731
- topicId,
732
- queryId,
733
- peerBridge
734
- });
735
- return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=task"], env) : null;
736
- }
737
- const args = [`--user-id=${userId}`, `--topic=${session}`];
738
- if (topicId)
739
- args.push(`--topic-id=${topicId}`);
740
- return buildStdioMcpServer(agent, TASK_SERVER, args);
741
- }
742
- },
743
- "session-comm": {
744
- scopes: ["forum", "fork", "manager"],
745
- forumRequired: true,
746
- build({ userId, session, topicId, agent, depth = 0, silent, peerBridge }) {
747
- const effectiveAgent = agent ?? FALLBACK_AGENT;
748
- const args = [
749
- `--user-id=${userId}`,
750
- `--topic=${session}`,
751
- ...topicId ? [`--topic-id=${topicId}`] : [],
752
- `--depth=${depth}`,
753
- `--agent=${effectiveAgent}`,
754
- ...silent ? ["--reply-only=true"] : [],
755
- ...peerBridge ? [`--peer-host-query-id=${peerBridge.hostQueryId}`] : []
756
- ];
757
- return buildStdioMcpServer(effectiveAgent, SESSION_COMM_SERVER, args, peerBridge ? peerSessionBridgeIpcEnv() : undefined);
758
- }
759
- },
760
- wiki: {
761
- scopes: ["dm", "forum", "manager", "cron"],
762
- forumRequired: true,
763
- build({ userId, session, topicId, queryId, wikiTopicId, agent, peerBridge }) {
764
- if (peerBridge) {
765
- if (!topicId || !queryId)
766
- return null;
767
- const env = canonicalMcpBridgeEnv({
768
- surface: "wiki",
769
- userId,
770
- topicId,
771
- queryId,
772
- peerBridge
773
- });
774
- return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=wiki"], env) : null;
775
- }
776
- const args = [`--user-id=${userId}`];
777
- const resolvedWikiTopicId = wikiTopicId ?? topicId ?? (session !== "dm" ? session : undefined);
778
- if (resolvedWikiTopicId)
779
- args.push(`--topic-id=${resolvedWikiTopicId}`);
780
- args.push("--surface=wiki");
781
- return buildStdioMcpServer(agent, WIKI_SERVER, args);
782
- }
783
- },
784
- skills: {
785
- scopes: ["dm", "forum", "manager", "cron"],
786
- forumRequired: true,
787
- build({ userId, topicId, agent }) {
788
- const args = [`--user-id=${userId}`, "--surface=skills"];
789
- if (topicId)
790
- args.push(`--topic-id=${topicId}`);
791
- return buildStdioMcpServer(agent, WIKI_SERVER, args);
792
- }
793
- },
794
- "system-health": {
795
- scopes: ["dm", "forum", "manager", "cron"],
796
- forumRequired: true,
797
- build({ agent }) {
798
- return buildStdioMcpServer(agent, SYSTEM_HEALTH_SERVER, []);
799
- }
800
- },
801
- "background-bash": {
802
- scopes: ["forum"],
803
- build({ agent, bgBashPort }) {
804
- if (bgBashPort === undefined)
805
- return null;
806
- return longLivedHttpMcp(agent, bgBashPort);
807
- }
808
- },
809
- "agent-health": {
810
- scopes: ["forum", "manager", "cron"],
811
- forumRequired: true,
812
- build({ userId, agent }) {
813
- const args = [`--user-id=${userId}`];
814
- return buildStdioMcpServer(agent, AGENT_HEALTH_SERVER, args);
815
- }
816
- },
817
- vault: {
818
- scopes: ["dm", "forum", "manager", "cron"],
819
- forumRequired: true,
820
- build({ userId, agent }) {
821
- const args = [`--user-id=${userId}`];
822
- if (agent === "codex")
823
- args.push("--http-only=true");
824
- return buildStdioMcpServer(agent, VAULT_SERVER, args);
825
- }
826
- }
827
- };
828
- function namesInScope(scope) {
829
- return Object.entries(MCP_CATALOG).filter(([, e]) => e.scopes.includes(scope)).map(([name]) => name);
830
- }
831
- var allForumMcpServerNames = [];
832
- var requiredForumMcpServers = [];
833
- var REQUIRED_FORUM_MCP_SERVERS = requiredForumMcpServers;
834
- var optionalForumMcpServers = [];
835
- var OPTIONAL_FORUM_MCP_SERVERS = optionalForumMcpServers;
836
- function refreshForumCatalogViews() {
837
- const all = namesInScope("forum");
838
- const required = Object.entries(MCP_CATALOG).filter(([, entry]) => entry.scopes.includes("forum") && entry.forumRequired).map(([name]) => name);
839
- allForumMcpServerNames.splice(0, allForumMcpServerNames.length, ...all);
840
- requiredForumMcpServers.splice(0, requiredForumMcpServers.length, ...required);
841
- optionalForumMcpServers.splice(0, optionalForumMcpServers.length, ...all.filter((name) => !required.includes(name)));
842
- }
843
- refreshForumCatalogViews();
844
- function registerRuntimeMcpServer(name, entry) {
845
- const key = name.trim();
846
- if (!key || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(key)) {
847
- throw new Error(`invalid runtime MCP server name: ${name}`);
848
- }
849
- if (MCP_CATALOG[key])
850
- throw new Error(`runtime MCP server already registered: ${key}`);
851
- MCP_CATALOG[key] = entry;
852
- refreshForumCatalogViews();
853
- return () => {
854
- if (MCP_CATALOG[key] !== entry)
855
- return;
856
- delete MCP_CATALOG[key];
857
- refreshForumCatalogViews();
858
- };
859
- }
860
- var nodeMcpEntries = [];
861
- function setNodeMcpServers(entries) {
862
- nodeMcpEntries = entries.filter((entry) => !(entry.key in MCP_CATALOG));
863
- const dropped = entries.length - nodeMcpEntries.length;
864
- if (dropped > 0) {
865
- logger.warn({ dropped }, "setNodeMcpServers: entries shadowing built-in catalog keys ignored");
866
- }
867
- }
868
- function getNodeMcpServers() {
869
- return nodeMcpEntries;
870
- }
871
- function buildNodeMcpSpecs(agent, filter) {
872
- const out = {};
873
- for (const entry of nodeMcpEntries) {
874
- if (!filter(entry.key))
875
- continue;
876
- out[entry.key] = entry.kind === "http" ? longLivedHttpMcp(agent, entry.port) : {
877
- command: entry.command,
878
- args: entry.args ?? [],
879
- ...entry.env ? { env: entry.env } : {}
880
- };
881
- }
882
- return out;
883
- }
884
- function buildScope(scope, ctx, filter = () => true) {
885
- const out = {};
886
- for (const [name, entry] of Object.entries(MCP_CATALOG)) {
887
- if (!entry.scopes.includes(scope))
888
- continue;
889
- if (!filter(name))
890
- continue;
891
- const spec = entry.build(ctx);
892
- if (spec === null)
893
- continue;
894
- out[name] = spec;
895
- }
896
- if (scope !== "cron") {
897
- Object.assign(out, buildNodeMcpSpecs(ctx.agent, filter));
898
- }
899
- return out;
900
- }
901
- function getDmMcpServers(opts) {
902
- return buildScope("dm", {
903
- userId: opts.userId,
904
- session: "dm",
905
- agent: opts.agent,
906
- playwrightPort: opts.playwrightPort,
907
- playwrightCapability: opts.playwrightCapability
908
- });
909
- }
910
- function getManagerMcpServers(opts) {
911
- if (!opts.topicId) {
912
- throw new Error("getManagerMcpServers: private General topicId is required");
913
- }
914
- const topicId = opts.topicId;
915
- return buildScope("manager", {
916
- userId: opts.userId,
917
- session: opts.session ?? "General",
918
- topicId,
919
- queryId: opts.queryId,
920
- wikiTopicId: opts.wikiTopicId ?? topicId,
921
- agent: opts.agent,
922
- cwd: opts.cwd,
923
- model: opts.model,
924
- currentUserPrompt: opts.currentUserPrompt,
925
- playwrightPort: opts.playwrightPort,
926
- playwrightCapability: opts.playwrightCapability,
927
- autoContinue: opts.autoContinue,
928
- visualTools: opts.visualTools
929
- });
930
- }
931
- function getForumMcpServers(opts) {
932
- const {
933
- userId,
934
- session,
935
- topicId,
936
- queryId,
937
- wikiTopicId,
938
- agent,
939
- cwd,
940
- model,
941
- currentUserPrompt,
942
- playwrightPort,
943
- playwrightCapability,
944
- depth = 0,
945
- enabled = null,
946
- extra = {},
947
- silent = false,
948
- bgBashPort,
949
- autoContinue,
950
- visualTools,
951
- peerBridge
952
- } = opts;
953
- const filter = (name) => {
954
- if (silent && name === "task")
955
- return false;
956
- if (enabled === null)
957
- return true;
958
- return enabled.includes(name) || REQUIRED_FORUM_MCP_SERVERS.includes(name);
959
- };
960
- const base = buildScope("forum", {
961
- userId,
962
- session,
963
- topicId,
964
- queryId,
965
- wikiTopicId,
966
- agent,
967
- cwd,
968
- model,
969
- currentUserPrompt,
970
- depth,
971
- playwrightPort,
972
- playwrightCapability,
973
- bgBashPort,
974
- autoContinue,
975
- visualTools,
976
- silent,
977
- peerBridge
978
- }, filter);
979
- return { ...base, ...extra };
980
- }
981
- function getCronMcpServers(opts) {
982
- return buildScope("cron", {
983
- userId: opts.userId,
984
- session: opts.session,
985
- topicId: opts.topicId,
986
- queryId: opts.queryId,
987
- wikiTopicId: opts.wikiTopicId ?? opts.topicId,
988
- agent: opts.agent,
989
- cwd: opts.cwd,
990
- model: opts.model,
991
- currentUserPrompt: opts.currentUserPrompt,
992
- playwrightPort: opts.playwrightPort,
993
- playwrightCapability: opts.playwrightCapability,
994
- autoContinue: false,
995
- visualTools: opts.visualTools
996
- });
997
- }
998
- function getMcpServersForQuery(opts) {
999
- if (opts.sessionType === "cron") {
1000
- if (!opts.topicId)
1001
- throw new Error("getMcpServersForQuery: cron sessionType requires topicId");
1002
- return getCronMcpServers({
1003
- userId: opts.userId || "default",
1004
- session: opts.session || "cron",
1005
- topicId: opts.topicId,
1006
- queryId: opts.queryId,
1007
- wikiTopicId: opts.wikiTopicId,
1008
- agent: opts.agent,
1009
- cwd: opts.cwd,
1010
- model: opts.model,
1011
- currentUserPrompt: opts.prompt,
1012
- playwrightPort: opts.playwrightPort,
1013
- playwrightCapability: opts.playwrightCapability,
1014
- visualTools: opts.visualTools
1015
- });
1016
- }
1017
- if (opts.sessionType === "dm" || opts.sessionType === "ephemeral") {
1018
- return getDmMcpServers({
1019
- userId: opts.userId || "default",
1020
- agent: opts.agent,
1021
- playwrightPort: opts.playwrightPort,
1022
- playwrightCapability: opts.playwrightCapability
1023
- });
1024
- }
1025
- if (opts.sessionType === "manager") {
1026
- return getManagerMcpServers({
1027
- userId: opts.userId || "default",
1028
- session: opts.session,
1029
- topicId: opts.topicId,
1030
- queryId: opts.queryId,
1031
- wikiTopicId: opts.wikiTopicId,
1032
- agent: opts.agent,
1033
- cwd: opts.cwd,
1034
- model: opts.model,
1035
- currentUserPrompt: opts.prompt,
1036
- playwrightPort: opts.playwrightPort,
1037
- playwrightCapability: opts.playwrightCapability,
1038
- autoContinue: opts.autoContinue,
1039
- visualTools: opts.visualTools
1040
- });
1041
- }
1042
- return getForumMcpServers({
1043
- userId: opts.userId || "default",
1044
- session: opts.session || "default",
1045
- topicId: opts.topicId,
1046
- queryId: opts.queryId,
1047
- wikiTopicId: opts.wikiTopicId,
1048
- agent: opts.agent,
1049
- cwd: opts.cwd,
1050
- model: opts.model,
1051
- currentUserPrompt: opts.prompt,
1052
- playwrightPort: opts.playwrightPort,
1053
- playwrightCapability: opts.playwrightCapability,
1054
- bgBashPort: opts.bgBashPort,
1055
- autoContinue: opts.autoContinue,
1056
- visualTools: opts.visualTools,
1057
- depth: opts.depth,
1058
- enabled: opts.mcpEnabled,
1059
- extra: opts.mcpExtra,
1060
- silent: opts.silent,
1061
- peerBridge: opts.peerBridge
1062
- });
1063
- }
1064
-
1065
- // ../../packages/core/src/agents/execution-host.ts
1066
- import { AsyncLocalStorage } from "async_hooks";
1067
-
1068
- // ../../packages/core/src/agents/vault-tool-policy.ts
1069
- var SENSITIVE_RUNTIME_NAMES = [
1070
- "vault.db",
1071
- "vault-master-key",
1072
- "runtime-mcp-secret",
1073
- "sessions.db"
1074
- ];
1075
- var VAULT_BROKER_REDIRECT_ERROR = "Vault placeholders must be executed through mcp__vault__vault_run (shell/CLI) or mcp__vault__vault_http_request (HTTP) so secret values cannot enter the model transcript.";
1076
- function isVaultBrokerTool(toolName) {
1077
- return toolName.includes("vault_run") || toolName.includes("vault_http_request");
1078
- }
1079
- function referencesRuntimeSecretStorage(value) {
1080
- if (typeof value === "string") {
1081
- const lower = value.toLowerCase();
1082
- if (SENSITIVE_RUNTIME_NAMES.some((name) => lower.includes(name)))
1083
- return true;
1084
- return value.startsWith("/") && isSensitivePath(value);
1085
- }
1086
- if (Array.isArray(value))
1087
- return value.some(referencesRuntimeSecretStorage);
1088
- if (value && typeof value === "object") {
1089
- return Object.values(value).some(referencesRuntimeSecretStorage);
1090
- }
1091
- return false;
1092
- }
1093
- function shouldRedirectVaultTool(userId, toolName, input) {
1094
- return !isVaultBrokerTool(toolName) && valueReferencesVaultKey(userId, input);
1095
- }
1096
-
1097
- // ../../packages/core/src/agents/execution-host.ts
1098
- var defaultHost = {
1099
- getMcpServersForQuery,
1100
- redactVaultSecrets,
1101
- referencesRuntimeSecretStorage,
1102
- shouldRedirectVaultTool,
1103
- claudeCodeExecutablePath: () => CLAUDE_EXECUTABLE,
1104
- codexAuthFilePath
1105
- };
1106
- var hostRegistrations = [];
1107
- var scopedHost = new AsyncLocalStorage;
1108
- function activeHost() {
1109
- const scoped = scopedHost.getStore();
1110
- if (scoped)
1111
- return scoped;
1112
- const host = { ...defaultHost };
1113
- for (const registration of hostRegistrations)
1114
- Object.assign(host, registration.overrides);
1115
- return host;
1116
- }
1117
- function resolveAgentExecutionHost(overrides = {}) {
1118
- return { ...activeHost(), ...overrides };
1119
- }
1120
- function withAgentExecutionHost(host, operation) {
1121
- return scopedHost.run(host, operation);
1122
- }
1123
- function configureAgentExecutionHost(overrides) {
1124
- const registration = { id: Symbol("agent-execution-host"), overrides };
1125
- hostRegistrations.push(registration);
1126
- let disposed = false;
1127
- return () => {
1128
- if (disposed)
1129
- return;
1130
- disposed = true;
1131
- const index = hostRegistrations.findIndex((entry) => entry.id === registration.id);
1132
- if (index >= 0)
1133
- hostRegistrations.splice(index, 1);
1134
- };
1135
- }
1136
- function hostedMcpServers(opts) {
1137
- return activeHost().getMcpServersForQuery(opts);
1138
- }
1139
- function redactHostedSecrets(userId, value) {
1140
- return activeHost().redactVaultSecrets(userId, value);
1141
- }
1142
- function referencesHostedSecretStorage(value) {
1143
- return activeHost().referencesRuntimeSecretStorage(value);
1144
- }
1145
- function shouldRedirectHostedVaultTool(userId, toolName, input) {
1146
- return activeHost().shouldRedirectVaultTool(userId, toolName, input);
1147
- }
1148
- function transformHostedQueryOptions(opts) {
1149
- return activeHost().transformQueryOptions?.(opts) ?? opts;
1150
- }
1151
- function hostedClaudeCodeExecutablePath() {
1152
- return activeHost().claudeCodeExecutablePath();
1153
- }
1154
- function hostedCodexAuthFilePath() {
1155
- return activeHost().codexAuthFilePath();
1156
- }
1157
-
1158
- // ../../packages/core/src/media/file-events.ts
1159
- function* extractFileEvents(text, source) {
1160
- const tagRegex = new RegExp(FILE_TAG_REGEX.source, "gi");
1161
- let match = tagRegex.exec(text);
1162
- while (match !== null) {
1163
- yield { type: "file", path: match[1], source, origin: "tag" };
1164
- match = tagRegex.exec(text);
1165
- }
1166
- }
1167
- function extractFileTagPaths(text) {
1168
- const seen = new Set;
1169
- for (const event of extractFileEvents(text, "tag")) {
1170
- if (event.type === "file")
1171
- seen.add(event.path);
1172
- }
1173
- return [...seen];
1174
- }
1175
- function stripFileTags(text) {
1176
- return text.replace(new RegExp(FILE_TAG_REGEX.source, "gi"), "").trim();
1177
- }
1178
-
1179
- // ../../packages/core/src/platform/error.ts
1180
- function errMsg(e, fallback) {
1181
- if (e instanceof Error)
1182
- return e.message;
1183
- return fallback ?? String(e);
1184
- }
1185
-
1186
- // ../../packages/core/src/version.ts
1187
- var NEGOTIUM_VERSION = "0.1.11";
1188
-
1189
- // ../../packages/core/src/agents/claude-provider.ts
1190
- import { spawn } from "child_process";
1191
- import { existsSync, readFileSync } from "fs";
1192
- import { query } from "@anthropic-ai/claude-agent-sdk";
1193
-
1194
- // ../../packages/core/src/agents/deep-map.ts
1195
- function deepMapStrings(value, fn) {
1196
- if (typeof value === "string")
1197
- return fn(value);
1198
- if (Array.isArray(value))
1199
- return value.map((v) => deepMapStrings(v, fn));
1200
- if (value && typeof value === "object") {
1201
- const out = {};
1202
- for (const [k, v] of Object.entries(value)) {
1203
- out[k] = deepMapStrings(v, fn);
1204
- }
1205
- return out;
1206
- }
1207
- return value;
1208
- }
1209
-
1210
- // ../../packages/core/src/agents/claude-provider.ts
1211
- var CLAUDE_DEFAULT_DISALLOWED_TOOLS = [
1212
- "AskUserQuestion",
1213
- "Workflow",
1214
- "TodoWrite",
1215
- "TaskCreate",
1216
- "TaskUpdate",
1217
- "TaskList",
1218
- "TaskGet",
1219
- "Task",
1220
- "Agent",
1221
- "TaskOutput",
1222
- "TaskStop"
1223
- ];
1224
- function buildClaudeDisallowedTools(extra = undefined) {
1225
- return [...new Set([...CLAUDE_DEFAULT_DISALLOWED_TOOLS, ...extra ?? []])];
1226
- }
1227
- function toClaudeEffort(e) {
1228
- if (!e || !claudeRegistry.validateEffort(e))
1229
- return;
1230
- return e;
1231
- }
1232
- var CLAUDE_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
1233
- var CLAUDE_IMAGE_MIME_TYPES = new Set([
1234
- "image/jpeg",
1235
- "image/png",
1236
- "image/gif",
1237
- "image/webp"
1238
- ]);
1239
- function isClaudeImageAttachment(attachment) {
1240
- return attachment.type === "image" && CLAUDE_IMAGE_MIME_TYPES.has(attachment.mimeType);
1241
- }
1242
- async function* singleUserMessage(message) {
1243
- yield message;
1244
- }
1245
- function buildClaudePrompt(opts) {
1246
- const attachments = opts.attachments ?? [];
1247
- if (attachments.length === 0)
1248
- return opts.prompt;
1249
- const imageBlocks = [];
1250
- for (const attachment of attachments) {
1251
- if (!isClaudeImageAttachment(attachment))
1252
- continue;
1253
- if (attachment.sizeBytes > CLAUDE_IMAGE_MAX_BYTES) {
1254
- logger.info({
1255
- path: attachment.path,
1256
- mimeType: attachment.mimeType,
1257
- sizeBytes: attachment.sizeBytes,
1258
- maxBytes: CLAUDE_IMAGE_MAX_BYTES
1259
- }, "claudeProvider: skipping oversized image attachment content block");
1260
- continue;
1261
- }
1262
- try {
1263
- imageBlocks.push({
1264
- type: "image",
1265
- source: {
1266
- type: "base64",
1267
- media_type: attachment.mimeType,
1268
- data: readFileSync(attachment.path).toString("base64")
1269
- }
1270
- });
1271
- } catch (err) {
1272
- logger.warn({
1273
- path: attachment.path,
1274
- mimeType: attachment.mimeType,
1275
- err
1276
- }, "claudeProvider: failed to read image attachment for content block");
1277
- }
1278
- }
1279
- if (imageBlocks.length === 0)
1280
- return opts.prompt;
1281
- const content = [{ type: "text", text: opts.prompt }, ...imageBlocks];
1282
- return singleUserMessage({
1283
- type: "user",
1284
- message: {
1285
- role: "user",
1286
- content
1287
- },
1288
- parent_tool_use_id: null
1289
- });
1290
- }
1291
- var CLAUDE_ABORT_SIGKILL_DELAY_MS = 2500;
1292
- function signalProcessTree(pid, signal) {
1293
- try {
1294
- process.kill(-pid, signal);
1295
- return true;
1296
- } catch (groupErr) {
1297
- try {
1298
- process.kill(pid, signal);
1299
- return true;
1300
- } catch (pidErr) {
1301
- logger.debug({ groupErr, pidErr, pid, signal }, "Failed to signal Claude process tree");
1302
- return false;
1303
- }
1304
- }
1305
- }
1306
- function spawnClaudeCodeProcessWithTreeKill(options) {
1307
- const child = spawn(options.command, options.args, {
1308
- cwd: options.cwd,
1309
- detached: true,
1310
- env: options.env,
1311
- stdio: ["pipe", "pipe", "inherit"]
1312
- });
1313
- if (!child.stdin || !child.stdout) {
1314
- child.kill("SIGKILL");
1315
- throw new Error("Failed to spawn Claude Code process with piped stdin/stdout");
1316
- }
1317
- let exited = false;
1318
- let killTimer;
1319
- const clearKillTimer = () => {
1320
- if (killTimer)
1321
- clearTimeout(killTimer);
1322
- killTimer = undefined;
1323
- };
1324
- const kill = (signal) => {
1325
- if (!child.pid)
1326
- return child.kill(signal);
1327
- const ok = signalProcessTree(child.pid, signal);
1328
- if (signal !== "SIGKILL") {
1329
- clearKillTimer();
1330
- killTimer = setTimeout(() => {
1331
- if (!exited && child.pid) {
1332
- logger.warn({ pid: child.pid }, "Claude process tree still alive after SIGTERM; SIGKILL");
1333
- signalProcessTree(child.pid, "SIGKILL");
1334
- }
1335
- }, CLAUDE_ABORT_SIGKILL_DELAY_MS);
1336
- killTimer.unref?.();
1337
- }
1338
- return ok;
1339
- };
1340
- const onAbort = () => {
1341
- logger.warn({ pid: child.pid, command: options.command }, "Aborting Claude Code process tree");
1342
- kill("SIGTERM");
1343
- };
1344
- if (options.signal.aborted)
1345
- onAbort();
1346
- else
1347
- options.signal.addEventListener("abort", onAbort, { once: true });
1348
- child.once("exit", (code, signal) => {
1349
- exited = true;
1350
- clearKillTimer();
1351
- options.signal.removeEventListener("abort", onAbort);
1352
- logger.debug({ pid: child.pid, code, signal }, "Claude Code process exited");
1353
- });
1354
- child.once("error", (err) => {
1355
- exited = true;
1356
- clearKillTimer();
1357
- options.signal.removeEventListener("abort", onAbort);
1358
- logger.error({
1359
- pid: child.pid,
1360
- command: options.command,
1361
- err: err instanceof Error ? err.message : String(err)
1362
- }, "Claude Code process error event");
1363
- });
1364
- return {
1365
- stdin: child.stdin,
1366
- stdout: child.stdout,
1367
- get killed() {
1368
- return child.killed || exited;
1369
- },
1370
- get exitCode() {
1371
- return child.exitCode;
1372
- },
1373
- kill,
1374
- on(event, listener) {
1375
- child.on(event, listener);
1376
- },
1377
- once(event, listener) {
1378
- child.once(event, listener);
1379
- },
1380
- off(event, listener) {
1381
- child.off(event, listener);
1382
- }
1383
- };
1384
- }
1385
- function isAbortError(err) {
1386
- if (!err || typeof err !== "object")
1387
- return false;
1388
- const e = err;
1389
- if (e.name === "AbortError")
1390
- return true;
1391
- if (e.code === 20 || e.code === "ABORT_ERR")
1392
- return true;
1393
- return false;
1394
- }
1395
- async function* claudeProvider(opts) {
1396
- const claudeExecutable = hostedClaudeCodeExecutablePath();
1397
- if (claudeExecutable && !existsSync(claudeExecutable)) {
1398
- yield {
1399
- type: "error",
1400
- content: `Claude CLI not found at ${claudeExecutable}. Install Claude Code or configure the executable path.`
1401
- };
1402
- return;
1403
- }
1404
- const cleanEnv = { ...process.env };
1405
- delete cleanEnv.NEGOTIUM_PEER_SESSION_BRIDGE_URL;
1406
- delete cleanEnv.NEGOTIUM_PEER_SESSION_BRIDGE_TOKEN;
1407
- delete cleanEnv.CLAUDECODE;
1408
- cleanEnv.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT ??= process.env.PADDLEOCR_TIMEOUT_MS ?? "300000";
1409
- cleanEnv.CLAUDE_CODE_DISABLE_WORKFLOWS = "1";
1410
- const queryOptions = {
1411
- ...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
1412
- spawnClaudeCodeProcess: spawnClaudeCodeProcessWithTreeKill,
1413
- cwd: opts.cwd,
1414
- permissionMode: "bypassPermissions",
1415
- allowDangerouslySkipPermissions: true,
1416
- includePartialMessages: true,
1417
- env: cleanEnv,
1418
- mcpServers: hostedMcpServers(opts),
1419
- abortController: opts.abortController,
1420
- disallowedTools: buildClaudeDisallowedTools(opts.disallowedTools),
1421
- ...opts.model ? { model: opts.model } : {},
1422
- ...opts.maxBudgetUsd ? { maxBudgetUsd: opts.maxBudgetUsd } : {},
1423
- ...opts.agents ? {
1424
- agents: Object.fromEntries(Object.entries(opts.agents).map(([name, def]) => {
1425
- const { effort, ...rest } = def;
1426
- const narrowed = typeof effort === "string" ? toClaudeEffort(effort) : effort;
1427
- return [name, narrowed !== undefined ? { ...rest, effort: narrowed } : rest];
1428
- }))
1429
- } : {},
1430
- ...toClaudeEffort(opts.effort) ? { effort: toClaudeEffort(opts.effort) } : {},
1431
- hooks: {
1432
- PreToolUse: [
1433
- {
1434
- hooks: [
1435
- async (input) => {
1436
- const { tool_name, tool_input } = input;
1437
- if (referencesHostedSecretStorage(tool_input)) {
1438
- return {
1439
- hookSpecificOutput: {
1440
- hookEventName: "PreToolUse",
1441
- permissionDecision: "deny",
1442
- permissionDecisionReason: "Runtime secret storage access is not permitted"
1443
- }
1444
- };
1445
- }
1446
- if (tool_name === "Bash" && tool_input?.run_in_background === true) {
1447
- return {
1448
- hookSpecificOutput: {
1449
- hookEventName: "PreToolUse",
1450
- permissionDecision: "deny",
1451
- permissionDecisionReason: "run_in_background is not supported in headless mode \u2014 the bash process dies when the agent turn ends. Use mcp__background-bash__background_bash_run instead: it survives across turns and automatically injects the result into the session when done."
1452
- }
1453
- };
1454
- }
1455
- const userId = opts.userId ?? "";
1456
- if (!shouldRedirectHostedVaultTool(userId, tool_name, tool_input)) {
1457
- return { continue: true };
1458
- }
1459
- return {
1460
- hookSpecificOutput: {
1461
- hookEventName: "PreToolUse",
1462
- permissionDecision: "deny",
1463
- permissionDecisionReason: VAULT_BROKER_REDIRECT_ERROR
1464
- }
1465
- };
1466
- }
1467
- ]
1468
- }
1469
- ],
1470
- PostToolUse: [
1471
- {
1472
- hooks: [
1473
- async (input) => {
1474
- const { tool_response } = input;
1475
- const userId = opts.userId ?? "";
1476
- const redacted = deepMapStrings(tool_response, (value) => redactHostedSecrets(userId, value));
1477
- if (JSON.stringify(redacted) === JSON.stringify(tool_response)) {
1478
- return { continue: true };
1479
- }
1480
- return {
1481
- hookSpecificOutput: {
1482
- hookEventName: "PostToolUse",
1483
- updatedToolOutput: redacted
1484
- }
1485
- };
1486
- }
1487
- ]
1488
- }
1489
- ]
1490
- },
1491
- settingSources: ["project"],
1492
- settings: {
1493
- disableWorkflows: true,
1494
- workflowKeywordTriggerEnabled: false
1495
- },
1496
- systemPrompt: opts.systemPrompt
1497
- };
1498
- if (opts.sessionId) {
1499
- queryOptions.resume = opts.sessionId;
1500
- }
1501
- const THINKING_BEAT_MS = 5000;
1502
- let thinkingStart = null;
1503
- let lastThinkingBeat = 0;
1504
- let lastContextTokens;
1505
- let lastContextModel;
1506
- const thinkingBeat = () => {
1507
- const now = Date.now();
1508
- if (thinkingStart === null) {
1509
- thinkingStart = now;
1510
- lastThinkingBeat = now;
1511
- return { type: "tool_progress", toolName: "thinking", elapsed: 0 };
1512
- }
1513
- if (now - lastThinkingBeat < THINKING_BEAT_MS)
1514
- return null;
1515
- lastThinkingBeat = now;
1516
- return { type: "tool_progress", toolName: "thinking", elapsed: (now - thinkingStart) / 1000 };
1517
- };
1518
- try {
1519
- const prompt = buildClaudePrompt(opts);
1520
- for await (const message of query({
1521
- prompt,
1522
- options: queryOptions
1523
- })) {
1524
- if (message.type === "system" && message.subtype === "thinking_tokens") {
1525
- const beat = thinkingBeat();
1526
- if (beat)
1527
- yield beat;
1528
- continue;
1529
- }
1530
- if (message.type === "stream_event") {
1531
- const streamMsg = message;
1532
- const evt = streamMsg.event;
1533
- if (!evt)
1534
- continue;
1535
- if (evt.type === "content_block_delta") {
1536
- const delta = evt;
1537
- if (delta.delta.type === "thinking_delta") {
1538
- const beat = thinkingBeat();
1539
- if (beat)
1540
- yield beat;
1541
- } else if (delta.delta.type === "text_delta" && delta.delta.text) {
1542
- thinkingStart = null;
1543
- } else {
1544
- thinkingStart = null;
1545
- }
1546
- }
1547
- continue;
1548
- }
1549
- thinkingStart = null;
1550
- if (message.type === "tool_progress") {
1551
- const m = message;
1552
- yield {
1553
- type: "tool_progress",
1554
- toolName: m.tool_name,
1555
- elapsed: m.elapsed_time_seconds
1556
- };
1557
- continue;
1558
- }
1559
- if (message.type === "tool_use_summary") {
1560
- const m = message;
1561
- yield { type: "tool_use_summary", summary: m.summary };
1562
- continue;
1563
- }
1564
- if (message.type === "system") {
1565
- const m = message;
1566
- if (m.subtype === "init") {
1567
- yield { type: "session", sessionId: m.session_id };
1568
- } else if (m.subtype === "task_started") {
1569
- const t = m;
1570
- if (t.subagent_type && !t.skip_transcript) {
1571
- const desc = t.description ? ` ${t.description.slice(0, 60)}` : "";
1572
- yield { type: "status", content: `\u25B6 [${t.subagent_type}]${desc}` };
1573
- }
1574
- } else {
1575
- logger.debug({ subtype: m.subtype, msg: m }, "claudeProvider: system message (unhandled subtype)");
1576
- }
1577
- continue;
1578
- }
1579
- if (message.type === "result") {
1580
- const m = message;
1581
- if (m.subtype === "success") {
1582
- const contextWindow = (lastContextModel ? m.modelUsage?.[lastContextModel]?.contextWindow : undefined) ?? Object.values(m.modelUsage ?? {})[0]?.contextWindow;
1583
- yield {
1584
- type: "result",
1585
- content: m.result,
1586
- stopReason: m.stop_reason ?? "end_turn",
1587
- usage: m.usage ? {
1588
- inputTokens: m.usage.input_tokens,
1589
- outputTokens: m.usage.output_tokens,
1590
- cacheCreationInputTokens: m.usage.cache_creation_input_tokens ?? undefined,
1591
- cacheReadInputTokens: m.usage.cache_read_input_tokens ?? undefined,
1592
- costUsd: m.total_cost_usd,
1593
- ...lastContextTokens !== undefined && contextWindow ? { contextTokens: lastContextTokens, contextWindow } : {}
1594
- } : undefined
1595
- };
1596
- yield* extractFileEvents(m.result, "result");
1597
- return;
1598
- }
1599
- const budgetExceeded = m.subtype === "error_max_budget_usd";
1600
- const errorMsg = budgetExceeded ? "The job reached its cost limit" : m.errors?.join("; ") || "Unknown error";
1601
- yield {
1602
- type: "error",
1603
- content: errorMsg,
1604
- ...budgetExceeded ? { code: "budget_exceeded" } : {},
1605
- ...m.usage ? {
1606
- usage: {
1607
- inputTokens: m.usage.input_tokens,
1608
- outputTokens: m.usage.output_tokens,
1609
- cacheCreationInputTokens: m.usage.cache_creation_input_tokens ?? undefined,
1610
- cacheReadInputTokens: m.usage.cache_read_input_tokens ?? undefined,
1611
- costUsd: m.total_cost_usd
1612
- }
1613
- } : {}
1614
- };
1615
- return;
1616
- }
1617
- if (message.type === "assistant") {
1618
- const m = message;
1619
- const content = m.message?.content ?? [];
1620
- const usage = m.message?.usage;
1621
- if (usage) {
1622
- lastContextTokens = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.output_tokens ?? 0);
1623
- lastContextModel = m.message.model;
1624
- }
1625
- for (const block of content) {
1626
- if (block.type === "text") {
1627
- const textBlock = block;
1628
- yield { type: "text", content: textBlock.text };
1629
- yield* extractFileEvents(textBlock.text, "text");
1630
- } else if (block.type === "tool_use") {
1631
- const tb = block;
1632
- yield {
1633
- type: "tool_use",
1634
- name: tb.name,
1635
- input: tb.input || {},
1636
- toolUseId: tb.id
1637
- };
1638
- }
1639
- }
1640
- continue;
1641
- }
1642
- if (message.type === "user") {
1643
- const um = message;
1644
- if ("isReplay" in um && um.isReplay)
1645
- continue;
1646
- const content = um.message?.content;
1647
- if (!Array.isArray(content))
1648
- continue;
1649
- for (const block of content) {
1650
- if (block.type !== "tool_result")
1651
- continue;
1652
- const trBlock = block;
1653
- const trContent = typeof trBlock.content === "string" ? trBlock.content.slice(0, 200) : "";
1654
- yield {
1655
- type: "tool_result",
1656
- toolUseId: trBlock.tool_use_id || "",
1657
- content: trContent
1658
- };
1659
- }
1660
- }
1661
- }
1662
- } catch (e) {
1663
- if (isAbortError(e) || opts.abortController?.signal.aborted)
1664
- return;
1665
- logger.error({ err: e }, "claudeProvider: SDK iteration failed");
1666
- yield { type: "error", content: errMsg(e) };
1667
- }
1668
- }
1669
-
1670
- // ../../packages/core/src/agents/codex-provider.ts
1671
- import { existsSync as existsSync3 } from "fs";
1672
- import { Codex } from "@openai/codex-sdk";
1673
-
1674
- // ../../packages/core/src/agents/codex-native-multi-agent.ts
1675
- import { spawn as spawn2 } from "child_process";
1676
- import {
1677
- chmodSync as chmodSync2,
1678
- existsSync as existsSync2,
1679
- readFileSync as readFileSync2,
1680
- renameSync,
1681
- unlinkSync,
1682
- writeFileSync
1683
- } from "fs";
1684
- import { createRequire } from "module";
1685
- import { dirname, join as join2 } from "path";
1686
- var NEGOTIUM_MODEL_CATALOG = "negotium-model-catalog.json";
1687
- var moduleRequire = createRequire(import.meta.url);
1688
- function codexCliScriptPath() {
1689
- const sdkPackagePath = moduleRequire.resolve("@openai/codex-sdk/package.json");
1690
- const sdkRequire = createRequire(sdkPackagePath);
1691
- return join2(dirname(sdkRequire.resolve("@openai/codex/package.json")), "bin", "codex.js");
1692
- }
1693
- async function bootstrapCodexModelCache(codexHome, cachePath) {
1694
- const child = spawn2(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
1695
- env: { ...process.env, CODEX_HOME: codexHome },
1696
- stdio: ["pipe", "pipe", "pipe"]
1697
- });
1698
- await new Promise((resolve2, reject) => {
1699
- let settled = false;
1700
- let stdoutBuffer = "";
1701
- let stderr = "";
1702
- const timer = setTimeout(() => finish(new Error("timed out while refreshing the Codex model catalog")), 15000);
1703
- const finish = (error) => {
1704
- if (settled)
1705
- return;
1706
- settled = true;
1707
- clearTimeout(timer);
1708
- try {
1709
- child.stdin.end();
1710
- child.kill();
1711
- } catch {}
1712
- if (error)
1713
- reject(error);
1714
- else if (!existsSync2(cachePath))
1715
- reject(new Error("Codex did not create its model cache"));
1716
- else
1717
- resolve2();
1718
- };
1719
- const send = (message) => {
1720
- child.stdin.write(`${JSON.stringify(message)}
1721
- `);
1722
- };
1723
- child.stderr.on("data", (chunk) => {
1724
- if (stderr.length < 4096)
1725
- stderr += String(chunk);
1726
- });
1727
- child.on("error", (error) => finish(error));
1728
- child.on("exit", (code, signal) => {
1729
- if (!settled) {
1730
- finish(new Error(`Codex model catalog refresh exited with ${signal ? `signal ${signal}` : `code ${code ?? 1}`}${stderr.trim() ? `: ${stderr.trim()}` : ""}`));
1731
- }
1732
- });
1733
- child.stdout.on("data", (chunk) => {
1734
- stdoutBuffer += String(chunk);
1735
- for (;; ) {
1736
- const newline = stdoutBuffer.indexOf(`
1737
- `);
1738
- if (newline < 0)
1739
- break;
1740
- const line = stdoutBuffer.slice(0, newline);
1741
- stdoutBuffer = stdoutBuffer.slice(newline + 1);
1742
- let message;
1743
- try {
1744
- message = JSON.parse(line);
1745
- } catch {
1746
- continue;
1747
- }
1748
- if (message.id === 1) {
1749
- if (message.error) {
1750
- finish(new Error(message.error.message || "Codex initialization failed"));
1751
- return;
1752
- }
1753
- send({ method: "initialized" });
1754
- send({ id: 2, method: "model/list", params: { includeHidden: true } });
1755
- } else if (message.id === 2) {
1756
- if (message.error) {
1757
- finish(new Error(message.error.message || "Codex model listing failed"));
1758
- } else {
1759
- finish();
1760
- }
1761
- return;
1762
- }
1763
- }
1764
- });
1765
- send({
1766
- id: 1,
1767
- method: "initialize",
1768
- params: {
1769
- clientInfo: { name: "negotium", version: NEGOTIUM_VERSION },
1770
- capabilities: { experimentalApi: true }
1771
- }
1772
- });
1773
- });
1774
- }
1775
- async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexModelCache) {
1776
- const codexHome = dirname(authFilePath);
1777
- const cachePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE ?? join2(codexHome, "models_cache.json");
1778
- const hardenedCatalogPath = join2(codexHome, NEGOTIUM_MODEL_CATALOG);
1779
- if (existsSync2(cachePath) || existsSync2(hardenedCatalogPath))
1780
- return;
1781
- if (process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE) {
1782
- throw new Error(`Configured Codex model cache does not exist: ${cachePath}`);
1783
- }
1784
- await bootstrap(codexHome, cachePath);
1785
- }
1786
- function writeCodexCatalogWithNativeMultiAgentDisabled(authFilePath) {
1787
- const codexHome = dirname(authFilePath);
1788
- const sourcePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE ?? (existsSync2(join2(codexHome, "models_cache.json")) ? join2(codexHome, "models_cache.json") : join2(codexHome, NEGOTIUM_MODEL_CATALOG));
1789
- const outputPath = join2(codexHome, NEGOTIUM_MODEL_CATALOG);
1790
- const parsed = JSON.parse(readFileSync2(sourcePath, "utf8"));
1791
- if (!Array.isArray(parsed.models) || parsed.models.length === 0) {
1792
- throw new Error(`Codex model cache has no models: ${sourcePath}`);
1793
- }
1794
- const models = parsed.models.map((model, index) => {
1795
- if (!model || typeof model !== "object" || Array.isArray(model)) {
1796
- throw new Error(`Codex model cache entry ${index} is invalid: ${sourcePath}`);
1797
- }
1798
- return { ...model, multi_agent_version: "disabled" };
1799
- });
1800
- const contents = `${JSON.stringify({ models }, null, 2)}
1801
- `;
1802
- if (existsSync2(outputPath) && readFileSync2(outputPath, "utf8") === contents) {
1803
- return outputPath;
1804
- }
1805
- const tempPath = `${outputPath}.${process.pid}.${Date.now()}.tmp`;
1806
- try {
1807
- writeFileSync(tempPath, contents, { encoding: "utf8", mode: 384 });
1808
- renameSync(tempPath, outputPath);
1809
- chmodSync2(outputPath, 384);
1810
- } finally {
1811
- try {
1812
- unlinkSync(tempPath);
1813
- } catch {}
1814
- }
1815
- return outputPath;
1816
- }
1817
-
1818
- // ../../packages/core/src/agents/codex-provider.ts
1819
- function mapEffort(effort) {
1820
- if (!effort)
1821
- return;
1822
- if (effort === "max" || effort === "minimal") {
1823
- throw new Error(`codexProvider received effort='${effort}', which codex does not support \u2014 storage validation was bypassed`);
1824
- }
1825
- return effort;
1826
- }
1827
- var CODEX_MCP_SERVER_NAME_OVERRIDES = {
1828
- playwright: "otium_playwright"
1829
- };
1830
- function codexMcpServerName(name) {
1831
- return CODEX_MCP_SERVER_NAME_OVERRIDES[name] ?? name;
1832
- }
1833
- function parsePositiveInt(value, fallback) {
1834
- if (!value)
1835
- return fallback;
1836
- const parsed = Number.parseInt(value, 10);
1837
- return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
1838
- }
1839
- function withCodexMcpServerOverrides(name, server) {
1840
- if (name === "paddleocr") {
1841
- return {
1842
- ...server,
1843
- startup_timeout_sec: parsePositiveInt(process.env.PADDLEOCR_MCP_STARTUP_TIMEOUT_SEC, 30),
1844
- tool_timeout_sec: parsePositiveInt(process.env.PADDLEOCR_MCP_TOOL_TIMEOUT_SEC, 300)
1845
- };
1846
- }
1847
- return {
1848
- startup_timeout_sec: parsePositiveInt(process.env.CODEX_MCP_STARTUP_TIMEOUT_SEC, 30),
1849
- ...server
1850
- };
1851
- }
1852
- function toCodexMcpServers(claudeShape) {
1853
- const disabledBrowserStdio = () => ({
1854
- command: process.execPath,
1855
- args: ["-e", "process.exit(0)"],
1856
- enabled: false
1857
- });
1858
- const out = {
1859
- playwright: disabledBrowserStdio(),
1860
- "browser-rs": disabledBrowserStdio(),
1861
- patchright: disabledBrowserStdio()
1862
- };
1863
- for (const [name, srv] of Object.entries(claudeShape)) {
1864
- if (!srv || typeof srv !== "object")
1865
- continue;
1866
- const s = srv;
1867
- const codexName = codexMcpServerName(name);
1868
- if (typeof s.command === "string") {
1869
- out[codexName] = withCodexMcpServerOverrides(name, {
1870
- command: s.command,
1871
- ...Array.isArray(s.args) ? { args: s.args } : {},
1872
- ...s.env && typeof s.env === "object" ? { env: s.env } : {}
1873
- });
1874
- continue;
1875
- }
1876
- if (typeof s.url === "string" && s.type !== "sse") {
1877
- out[codexName] = withCodexMcpServerOverrides(name, {
1878
- url: s.url,
1879
- ...s.http_headers && typeof s.http_headers === "object" ? { http_headers: s.http_headers } : {},
1880
- ...s.env_http_headers && typeof s.env_http_headers === "object" ? { env_http_headers: s.env_http_headers } : {}
1881
- });
1882
- }
1883
- }
1884
- return out;
1885
- }
1886
- function isMissingCodexRolloutError(err) {
1887
- const msg = errMsg(err);
1888
- return /no rollout found|thread\/resume failed/i.test(msg);
1889
- }
1890
- function summarizeMcpToolCallResult(item) {
1891
- if (item.error)
1892
- return item.error.message.slice(0, 200);
1893
- if (!item.result)
1894
- return "";
1895
- const blocks = Array.isArray(item.result.content) ? item.result.content : [];
1896
- const text = blocks.map((b) => {
1897
- if (b && typeof b === "object" && "type" in b && b.type === "text" && "text" in b) {
1898
- return String(b.text ?? "");
1899
- }
1900
- return "";
1901
- }).filter(Boolean).join(`
1902
- `);
1903
- if (text)
1904
- return text.slice(0, 200);
1905
- return JSON.stringify(item.result.structured_content ?? "").slice(0, 200);
1906
- }
1907
- function fileChangeEvents(item) {
1908
- return item.changes.flatMap((change, index) => {
1909
- const name = change.kind === "add" ? "Write" : change.kind === "delete" ? "Delete" : "Edit";
1910
- const toolUseId = `${item.id}:${index}`;
1911
- const success = item.status === "completed";
1912
- return [
1913
- {
1914
- type: "tool_use",
1915
- name,
1916
- input: { file_path: change.path, change_kind: change.kind },
1917
- toolUseId
1918
- },
1919
- {
1920
- type: "tool_result",
1921
- toolUseId,
1922
- content: success ? `${change.kind} applied: ${change.path}` : `${change.kind} failed: ${change.path}`
1923
- }
1924
- ];
1925
- });
1926
- }
1927
- function promptForThread(opts, includeSystemPrompt) {
1928
- return includeSystemPrompt && opts.systemPrompt ? `[System Instructions]
1929
- ${opts.systemPrompt}
1930
-
1931
- ${opts.prompt}` : opts.prompt;
1932
- }
1933
- async function closeIterator(iter) {
1934
- try {
1935
- await iter.return?.();
1936
- } catch (err) {
1937
- logger.warn({ err }, "codexProvider: failed to close codex event iterator");
1938
- }
1939
- }
1940
- var CODEX_STARTUP_TIMEOUT_MS = 90000;
1941
- async function startStreamedWithTracking(thread, prompt, abortSignal, trackedPids) {
1942
- const release = await acquireCodexSpawnLock();
1943
- try {
1944
- const baseline = snapshotCodexChildren();
1945
- const runResult = await thread.runStreamed(prompt, {
1946
- ...abortSignal ? { signal: abortSignal } : {}
1947
- });
1948
- const iter = runResult.events[Symbol.asyncIterator]();
1949
- let first;
1950
- let startupTimer;
1951
- try {
1952
- const startupTimeout = new Promise((_, reject) => {
1953
- startupTimer = setTimeout(() => reject(new Error(`Codex\uAC00 ${CODEX_STARTUP_TIMEOUT_MS / 1000}\uCD08 \uB0B4 \uC751\uB2F5\uC744 \uC2DC\uC791\uD558\uC9C0 \uC54A\uC544 \uC911\uB2E8\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.`)), CODEX_STARTUP_TIMEOUT_MS);
1954
- });
1955
- const nextP = iter.next();
1956
- nextP.catch(() => {});
1957
- first = await Promise.race([nextP, startupTimeout]);
1958
- } catch (err) {
1959
- const novel2 = findNewCodexChildren(baseline);
1960
- if (novel2.length > 0) {
1961
- killCodexTrees(novel2);
1962
- }
1963
- await closeIterator(iter);
1964
- throw err;
1965
- } finally {
1966
- if (startupTimer)
1967
- clearTimeout(startupTimer);
1968
- }
1969
- const novel = findNewCodexChildren(baseline);
1970
- trackedPids.pids = novel;
1971
- registerOwnedCodexPids(novel);
1972
- return { iter, firstEvent: first.value, done: !!first.done };
1973
- } finally {
1974
- release();
1975
- }
1976
- }
1977
- function prependFirstEvent(iter, firstEvent, alreadyDone) {
1978
- return {
1979
- [Symbol.asyncIterator]() {
1980
- let emittedFirst = false;
1981
- let exhausted = alreadyDone;
1982
- return {
1983
- async next() {
1984
- if (!emittedFirst) {
1985
- emittedFirst = true;
1986
- if (alreadyDone)
1987
- return { value: undefined, done: true };
1988
- return { value: firstEvent, done: false };
1989
- }
1990
- if (exhausted)
1991
- return { value: undefined, done: true };
1992
- const r = await iter.next();
1993
- if (r.done)
1994
- exhausted = true;
1995
- return r;
1996
- },
1997
- async return(value) {
1998
- exhausted = true;
1999
- if (iter.return)
2000
- return iter.return(value);
2001
- return { value, done: true };
2002
- }
2003
- };
2004
- }
2005
- };
2006
- }
2007
- async function* codexProvider(opts) {
2008
- const codexAuthPath = hostedCodexAuthFilePath();
2009
- if (!existsSync3(codexAuthPath)) {
2010
- yield {
2011
- type: "error",
2012
- content: `Codex auth file not found at ${codexAuthPath}. Run \`codex login\` to authenticate.`
2013
- };
2014
- return;
2015
- }
2016
- let codexModelCatalogPath;
2017
- try {
2018
- await ensureCodexModelCache(codexAuthPath);
2019
- codexModelCatalogPath = writeCodexCatalogWithNativeMultiAgentDisabled(codexAuthPath);
2020
- if (opts.sessionId)
2021
- migrateCodexRolloutNativeMultiAgentMetadata(opts.sessionId);
2022
- } catch (err) {
2023
- yield {
2024
- type: "error",
2025
- content: `Failed to enforce the Codex native multi-agent policy: ${errMsg(err)}`
2026
- };
2027
- return;
2028
- }
2029
- const codexMcpServers = toCodexMcpServers(hostedMcpServers(opts));
2030
- const browserOwner = browserOwnerForContext(opts);
2031
- const scopedBrowserCapability = opts.playwrightCapability && browserOwner ? browserOwnerCapability(opts.playwrightCapability, browserOwner) : undefined;
2032
- logger.info({
2033
- model: opts.model ?? "(sdk default)",
2034
- effort: opts.effort ?? "(off)",
2035
- cwd: opts.cwd,
2036
- cwdExists: existsSync3(opts.cwd),
2037
- resume: Boolean(opts.sessionId),
2038
- mcpServerCount: Object.keys(codexMcpServers).length
2039
- }, "codexProvider: starting turn");
2040
- const codex = new Codex({
2041
- ...scopedBrowserCapability ? {
2042
- env: {
2043
- ...Object.fromEntries(Object.entries(process.env).filter((entry) => typeof entry[1] === "string")),
2044
- [CODEX_BROWSER_CAPABILITY_ENV]: scopedBrowserCapability
2045
- }
2046
- } : {},
2047
- config: {
2048
- features: { multi_agent: false, multi_agent_v2: false, enable_fanout: false },
2049
- model_catalog_json: codexModelCatalogPath,
2050
- mcp_servers: codexMcpServers
2051
- }
2052
- });
2053
- const threadOptions = {
2054
- workingDirectory: opts.cwd,
2055
- skipGitRepoCheck: true,
2056
- sandboxMode: "danger-full-access",
2057
- approvalPolicy: "never",
2058
- ...opts.model ? { model: opts.model } : {},
2059
- ...opts.effort ? { modelReasoningEffort: mapEffort(opts.effort) } : {}
2060
- };
2061
- let currentSessionId = opts.sessionId;
2062
- let thread = currentSessionId ? codex.resumeThread(currentSessionId, threadOptions) : codex.startThread(threadOptions);
2063
- let prompt = promptForThread(opts, true);
2064
- let agentTextSoFar = "";
2065
- let currentAgentMessageId;
2066
- let finalText = "";
2067
- let stopReason = "end_turn";
2068
- const trackedPids = { pids: [] };
2069
- const abortSignal = opts.abortController?.signal;
2070
- const onAbortKill = () => {
2071
- if (trackedPids.pids.length > 0) {
2072
- killCodexTrees(trackedPids.pids);
2073
- }
2074
- };
2075
- abortSignal?.addEventListener("abort", onAbortKill, { once: true });
2076
- let heartbeatMs = 8 * 60 * 1000;
2077
- let heartbeatAttempts = 0;
2078
- const HEARTBEAT_MAX_MS = 16 * 60 * 1000;
2079
- const HEARTBEAT_MAX_ATTEMPTS = 2;
2080
- const HEARTBEAT_CONTINUATION = "\uACC4\uC18D \uC9C4\uD589\uD574\uC918.";
2081
- try {
2082
- while (true) {
2083
- if (abortSignal?.aborted)
2084
- return;
2085
- const attemptAbort = new AbortController;
2086
- const propagateOuter = () => attemptAbort.abort();
2087
- abortSignal?.addEventListener("abort", propagateOuter, { once: true });
2088
- trackedPids.pids = [];
2089
- let heartbeatTriggered = false;
2090
- let attemptCompleted = false;
2091
- let heartbeatTimer = null;
2092
- const scheduleHeartbeat = () => {
2093
- if (heartbeatTimer !== null)
2094
- clearTimeout(heartbeatTimer);
2095
- if (abortSignal?.aborted || attemptAbort.signal.aborted)
2096
- return;
2097
- heartbeatTimer = setTimeout(() => {
2098
- heartbeatTriggered = true;
2099
- if (trackedPids.pids.length > 0)
2100
- killCodexTrees(trackedPids.pids);
2101
- attemptAbort.abort();
2102
- }, heartbeatMs);
2103
- };
2104
- try {
2105
- scheduleHeartbeat();
2106
- let startResult;
2107
- try {
2108
- startResult = await startStreamedWithTracking(thread, prompt, attemptAbort.signal, trackedPids);
2109
- } catch (err) {
2110
- if (err instanceof Error && err.name === "AbortError")
2111
- throw err;
2112
- if (!currentSessionId || !isMissingCodexRolloutError(err))
2113
- throw err;
2114
- logger.warn({ staleSessionId: currentSessionId, err: errMsg(err) }, "codexProvider: stale/missing rollout, restarting fresh thread");
2115
- thread = codex.startThread(threadOptions);
2116
- prompt = promptForThread(opts, true);
2117
- currentSessionId = undefined;
2118
- startResult = await startStreamedWithTracking(thread, prompt, attemptAbort.signal, trackedPids);
2119
- }
2120
- if (attemptAbort.signal.aborted) {
2121
- killCodexTrees(trackedPids.pids);
2122
- await closeIterator(startResult.iter);
2123
- throw Object.assign(new Error("heartbeat abort"), { name: "AbortError" });
2124
- }
2125
- const eventStream = prependFirstEvent(startResult.iter, startResult.firstEvent, startResult.done);
2126
- for await (const rawEvent of eventStream) {
2127
- scheduleHeartbeat();
2128
- const event = rawEvent;
2129
- switch (event.type) {
2130
- case "thread.started": {
2131
- if (event.thread_id) {
2132
- currentSessionId = event.thread_id;
2133
- yield { type: "session", sessionId: event.thread_id };
2134
- }
2135
- break;
2136
- }
2137
- case "item.started": {
2138
- const item = event.item;
2139
- if (!item)
2140
- break;
2141
- if (item.type === "command_execution") {
2142
- yield {
2143
- type: "tool_use",
2144
- name: "Bash",
2145
- input: { command: String(item.command ?? "") },
2146
- toolUseId: String(item.id ?? "")
2147
- };
2148
- } else if (item.type === "mcp_tool_call") {
2149
- yield {
2150
- type: "tool_use",
2151
- name: String(item.tool ?? "unknown"),
2152
- input: item.arguments && typeof item.arguments === "object" ? item.arguments : {},
2153
- toolUseId: String(item.id ?? "")
2154
- };
2155
- }
2156
- break;
2157
- }
2158
- case "item.updated": {
2159
- const item = event.item;
2160
- if (!item)
2161
- break;
2162
- if (item.type === "agent_message") {
2163
- const messageId = item.id != null ? String(item.id) : undefined;
2164
- if (messageId !== currentAgentMessageId) {
2165
- currentAgentMessageId = messageId;
2166
- agentTextSoFar = "";
2167
- }
2168
- const text = String(item.text ?? "");
2169
- const newChars = text.slice(agentTextSoFar.length);
2170
- if (newChars) {
2171
- yield { type: "text_delta", content: newChars };
2172
- agentTextSoFar = text;
2173
- }
2174
- }
2175
- break;
2176
- }
2177
- case "item.completed": {
2178
- const item = event.item;
2179
- if (!item)
2180
- break;
2181
- if (item.type === "agent_message") {
2182
- finalText = String(item.text ?? "");
2183
- agentTextSoFar = finalText;
2184
- yield { type: "text", content: finalText };
2185
- yield* extractFileEvents(finalText, "text");
2186
- } else if (item.type === "reasoning") {
2187
- const reasoning = String(item.text ?? "").trim();
2188
- if (reasoning)
2189
- yield { type: "reasoning", content: reasoning };
2190
- } else if (item.type === "mcp_tool_call") {
2191
- yield {
2192
- type: "tool_result",
2193
- toolUseId: String(item.id ?? ""),
2194
- content: summarizeMcpToolCallResult(item)
2195
- };
2196
- } else if (item.type === "command_execution") {
2197
- yield {
2198
- type: "tool_result",
2199
- toolUseId: String(item.id ?? ""),
2200
- content: String(item.aggregated_output ?? "").slice(0, 200)
2201
- };
2202
- } else if (item.type === "file_change") {
2203
- for (const fileEvent of fileChangeEvents(item)) {
2204
- yield fileEvent;
2205
- }
2206
- } else if (item.type === "error") {
2207
- yield { type: "error", content: String(item.message ?? "") };
2208
- }
2209
- break;
2210
- }
2211
- case "turn.completed": {
2212
- const usage = event.usage;
2213
- if (!usage)
2214
- break;
2215
- attemptCompleted = true;
2216
- const contextUsage = currentSessionId ? readLatestCodexContextUsage(currentSessionId) : undefined;
2217
- yield {
2218
- type: "result",
2219
- content: finalText,
2220
- stopReason,
2221
- usage: {
2222
- inputTokens: usage.input_tokens,
2223
- outputTokens: usage.output_tokens,
2224
- cacheReadInputTokens: usage.cached_input_tokens,
2225
- ...contextUsage
2226
- }
2227
- };
2228
- if (finalText)
2229
- yield* extractFileEvents(finalText, "result");
2230
- break;
2231
- }
2232
- case "turn.failed": {
2233
- stopReason = "error";
2234
- yield {
2235
- type: "error",
2236
- content: event.error?.message || "Codex turn failed"
2237
- };
2238
- break;
2239
- }
2240
- default:
2241
- break;
2242
- }
2243
- }
2244
- } catch (err) {
2245
- if (abortSignal?.aborted)
2246
- return;
2247
- if (err instanceof Error && err.name === "AbortError") {
2248
- if (!heartbeatTriggered)
2249
- return;
2250
- } else {
2251
- logger.error({ err }, "codexProvider: attempt failed");
2252
- yield { type: "error", content: errMsg(err) };
2253
- break;
2254
- }
2255
- } finally {
2256
- if (heartbeatTimer !== null)
2257
- clearTimeout(heartbeatTimer);
2258
- abortSignal?.removeEventListener("abort", propagateOuter);
2259
- if (trackedPids.pids.length > 0)
2260
- unregisterOwnedCodexPids(trackedPids.pids);
2261
- }
2262
- if (attemptCompleted || abortSignal?.aborted)
2263
- break;
2264
- if (!heartbeatTriggered)
2265
- break;
2266
- heartbeatAttempts++;
2267
- if (heartbeatAttempts > HEARTBEAT_MAX_ATTEMPTS) {
2268
- logger.warn({ attempts: heartbeatAttempts }, "codexProvider: max heartbeat attempts reached");
2269
- yield {
2270
- type: "error",
2271
- content: `Codex\uAC00 ${HEARTBEAT_MAX_ATTEMPTS}\uD68C \uC7AC\uC2DC\uB3C4 \uD6C4\uC5D0\uB3C4 \uC751\uB2F5\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`
2272
- };
2273
- break;
2274
- }
2275
- heartbeatMs = Math.min(heartbeatMs * 2, HEARTBEAT_MAX_MS);
2276
- logger.info({ attempt: heartbeatAttempts, nextIntervalMs: heartbeatMs }, "codexProvider: heartbeat fired, resuming thread with continuation prompt");
2277
- agentTextSoFar = "";
2278
- finalText = "";
2279
- thread = currentSessionId ? codex.resumeThread(currentSessionId, threadOptions) : codex.startThread(threadOptions);
2280
- prompt = currentSessionId ? HEARTBEAT_CONTINUATION : promptForThread(opts, true);
2281
- }
2282
- } catch (err) {
2283
- if (err instanceof Error && err.name === "AbortError")
2284
- return;
2285
- if (abortSignal?.aborted)
2286
- return;
2287
- logger.error({ err }, "codexProvider: setup failed");
2288
- yield { type: "error", content: errMsg(err) };
2289
- } finally {
2290
- abortSignal?.removeEventListener("abort", onAbortKill);
2291
- if (trackedPids.pids.length > 0)
2292
- unregisterOwnedCodexPids(trackedPids.pids);
2293
- }
2294
- }
2295
-
2296
- // ../../packages/core/src/agents/maestro-provider.ts
2297
- import { maestroProvider as sdkMaestroProvider, setMcpResolver } from "maestro-agent-sdk";
2298
- var MAESTRO_DEFAULT_MAX_TOKENS = 32768;
2299
- var PROVIDER_ASK_USER_TOOL = "AskUserQuestion";
2300
- var PROVIDER_SUBAGENT_TOOL = "Agent";
2301
- var MAESTRO_NATIVE_TASK_TOOLS = [
2302
- "TaskCreate",
2303
- "TaskUpdate",
2304
- "TaskList",
2305
- "TaskGet",
2306
- "TaskOutput",
2307
- "TaskStop"
2308
- ];
2309
- var MAESTRO_PROVIDER_OWNED_TOOL_SET = new Set([
2310
- PROVIDER_ASK_USER_TOOL,
2311
- PROVIDER_SUBAGENT_TOOL,
2312
- ...MAESTRO_NATIVE_TASK_TOOLS
2313
- ]);
2314
- var DEFAULT_MAESTRO_DISALLOWED_TOOLS = [
2315
- PROVIDER_ASK_USER_TOOL,
2316
- PROVIDER_SUBAGENT_TOOL,
2317
- ...MAESTRO_NATIVE_TASK_TOOLS
2318
- ];
2319
- function buildMaestroDisallowedTools(callerDisallowedTools = []) {
2320
- return [...new Set([...DEFAULT_MAESTRO_DISALLOWED_TOOLS, ...callerDisallowedTools])];
2321
- }
2322
- function buildVaultHook(userId) {
2323
- return {
2324
- name: "vault-guard",
2325
- pre({ toolName, input }) {
2326
- if (referencesHostedSecretStorage(input)) {
2327
- return { decision: "block", error: "Runtime secret storage access is not permitted" };
2328
- }
2329
- if (shouldRedirectHostedVaultTool(userId, toolName, input)) {
2330
- return { decision: "block", error: VAULT_BROKER_REDIRECT_ERROR };
2331
- }
2332
- return { decision: "allow" };
2333
- },
2334
- post({ output }) {
2335
- const redacted = redactHostedSecrets(userId, output);
2336
- return redacted === output ? {} : { output: redacted };
2337
- }
2338
- };
2339
- }
2340
- function buildMaestroToolHooks(userId) {
2341
- return [buildVaultHook(userId), buildProviderOwnedToolBlockHook()];
2342
- }
2343
- function providerOwnedToolRedirect(toolName) {
2344
- if (toolName === PROVIDER_ASK_USER_TOOL) {
2345
- return "Use the runtime ask_user_question MCP tool instead.";
2346
- }
2347
- if (toolName === PROVIDER_SUBAGENT_TOOL) {
2348
- return "Use the runtime spawn_subagent MCP tool instead.";
2349
- }
2350
- return "Use the shared task MCP tools instead " + "(mcp__task__task_create / task_update / task_list / task_get / task_delete).";
2351
- }
2352
- function buildProviderOwnedToolBlockHook() {
2353
- return {
2354
- name: "provider-owned-tool-redirect",
2355
- pre({ toolName }) {
2356
- if (!MAESTRO_PROVIDER_OWNED_TOOL_SET.has(toolName))
2357
- return { decision: "allow" };
2358
- return {
2359
- decision: "block",
2360
- error: `${toolName} is disabled in this environment. ${providerOwnedToolRedirect(toolName)}`
2361
- };
2362
- }
2363
- };
2364
- }
2365
- function maestroProvider(opts) {
2366
- if (opts.agent !== undefined && opts.agent !== "maestro") {
2367
- throw new Error(`maestroProvider: unexpected agent "${opts.agent}", expected "maestro"`);
2368
- }
2369
- const userId = opts.userId ?? "";
2370
- setMcpResolver(hostedMcpServers);
2371
- const callerDisallowedTools = opts.disallowedTools ?? [];
2372
- const callerToolHooks = opts.toolHooks ?? [];
2373
- const sdkOpts = {
2374
- enableBackgroundBash: false,
2375
- maxTokens: MAESTRO_DEFAULT_MAX_TOKENS,
2376
- enableToolSearch: true,
2377
- ...opts,
2378
- agent: "maestro",
2379
- disallowedTools: buildMaestroDisallowedTools(callerDisallowedTools),
2380
- toolHooks: [...buildMaestroToolHooks(userId), ...callerToolHooks]
2381
- };
2382
- return sdkMaestroProvider(sdkOpts);
2383
- }
2384
-
2385
- export { killOwnedCodexTreesForShutdown, isSensitivePath, VAULT_VALUE_MIN_BYTES, VAULT_VALUE_MAX_BYTES, VAULT_DESCRIPTION_MAX_LENGTH, normalizeVaultKey, validateVaultKey, vaultListWithValues, vaultSet, vaultHasKey, vaultDel, vaultList, RUNTIME_MCP_KEY, RUNTIME_MCP_BASE_PATH, setRuntimeMcpPort, getRuntimeMcpPort, issueRuntimeMcpToken, resolveRuntimeMcpToken, buildRuntimeMcpSpec, registerPeerSessionBridgeIpcConfig, buildStdioMcpServer, setPlaywrightUnavailableNotifier, markPlaywrightUnavailable, consumePlaywrightUnavailable, OPTIONAL_FORUM_MCP_SERVERS, registerRuntimeMcpServer, setNodeMcpServers, getNodeMcpServers, getMcpServersForQuery, resolveAgentExecutionHost, withAgentExecutionHost, configureAgentExecutionHost, transformHostedQueryOptions, extractFileEvents, extractFileTagPaths, stripFileTags, errMsg, claudeProvider, NEGOTIUM_VERSION, codexProvider, maestroProvider };
2386
-
2387
- //# debugId=BE2FC3DA2C6F5E2D64756E2164756E21