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