negotium 0.1.12 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canonical-mcp-bridge.js +2 -3
- package/dist/canonical-mcp-bridge.js.map +1 -1
- package/dist/chunk-a9y2rwxf.js.map +1 -1
- package/dist/{chunk-j0kjscb7.js → chunk-s2gez3wg.js} +20 -2
- package/dist/{chunk-j0kjscb7.js.map → chunk-s2gez3wg.js.map} +2 -2
- package/dist/cron.js +49 -25
- package/dist/cron.js.map +15 -14
- package/dist/hosted-agent.js +3012 -17
- package/dist/hosted-agent.js.map +30 -3
- package/dist/main.js +31323 -36
- package/dist/main.js.map +198 -3
- package/dist/prompts.js +11 -8
- package/dist/prompts.js.map +4 -4
- package/dist/runtime/src/application/submit-user-message.ts +3 -0
- package/dist/runtime/src/index.ts +7 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +3 -0
- package/dist/runtime/src/mcp/session-comm/server.ts +1 -1
- package/dist/runtime/src/platform/mcp-config.ts +13 -0
- package/dist/runtime/src/prompts/builders.ts +20 -6
- package/dist/runtime/src/query/active-rooms.ts +2 -0
- package/dist/runtime/src/runtime/delivery-ack.ts +112 -0
- package/dist/runtime/src/runtime/errors.ts +12 -12
- package/dist/runtime/src/runtime/turn-runner.ts +12 -0
- package/dist/runtime/src/storage/runtime-turn-requests.ts +1 -0
- package/dist/runtime/src/types/api.ts +2 -0
- package/dist/runtime/src/types.ts +2 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js.map +1 -1
- package/dist/storage.js.map +1 -1
- package/dist/types/packages/core/src/mcp/runtime-spec.d.ts +2 -0
- package/dist/types/packages/core/src/platform/mcp-config.d.ts +4 -0
- package/dist/types/packages/core/src/prompts/builders.d.ts +2 -0
- package/dist/types/packages/core/src/types/api.d.ts +2 -0
- package/dist/types/packages/core/src/types.d.ts +2 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-00q25k7p.js +0 -2387
- package/dist/chunk-00q25k7p.js.map +0 -26
- package/dist/chunk-03kv7mvj.js +0 -2152
- package/dist/chunk-03kv7mvj.js.map +0 -15
- package/dist/chunk-05pm51dh.js +0 -589
- package/dist/chunk-05pm51dh.js.map +0 -11
- package/dist/chunk-0eh16bk1.js +0 -2936
- package/dist/chunk-0eh16bk1.js.map +0 -20
- package/dist/chunk-0yfqyv88.js +0 -108
- package/dist/chunk-0yfqyv88.js.map +0 -10
- package/dist/chunk-1k7t7bnx.js +0 -97
- package/dist/chunk-1k7t7bnx.js.map +0 -10
- package/dist/chunk-3gkfr9vb.js +0 -431
- package/dist/chunk-3gkfr9vb.js.map +0 -11
- package/dist/chunk-5akd1gbj.js +0 -281
- package/dist/chunk-5akd1gbj.js.map +0 -10
- package/dist/chunk-7k19pcg8.js +0 -577
- package/dist/chunk-7k19pcg8.js.map +0 -14
- package/dist/chunk-81tbfbmt.js +0 -10
- package/dist/chunk-81tbfbmt.js.map +0 -10
- package/dist/chunk-82qhv1mq.js +0 -512
- package/dist/chunk-82qhv1mq.js.map +0 -13
- package/dist/chunk-8dy7e2a9.js +0 -103
- package/dist/chunk-8dy7e2a9.js.map +0 -10
- package/dist/chunk-8f2sqmrj.js +0 -1712
- package/dist/chunk-8f2sqmrj.js.map +0 -18
- package/dist/chunk-9hjeemfz.js +0 -372
- package/dist/chunk-9hjeemfz.js.map +0 -10
- package/dist/chunk-a4aa2ccv.js +0 -141
- package/dist/chunk-a4aa2ccv.js.map +0 -10
- package/dist/chunk-bsg215q3.js +0 -3536
- package/dist/chunk-bsg215q3.js.map +0 -20
- package/dist/chunk-bxnfsc7w.js +0 -58
- package/dist/chunk-bxnfsc7w.js.map +0 -10
- package/dist/chunk-c500xe9z.js +0 -33
- package/dist/chunk-c500xe9z.js.map +0 -10
- package/dist/chunk-cngrf7ab.js +0 -149
- package/dist/chunk-cngrf7ab.js.map +0 -10
- package/dist/chunk-d7tprf06.js +0 -53
- package/dist/chunk-d7tprf06.js.map +0 -10
- package/dist/chunk-ddnrmkrv.js +0 -61
- package/dist/chunk-ddnrmkrv.js.map +0 -10
- package/dist/chunk-edb07sjp.js +0 -567
- package/dist/chunk-edb07sjp.js.map +0 -14
- package/dist/chunk-ejcm6zx8.js +0 -591
- package/dist/chunk-ejcm6zx8.js.map +0 -11
- package/dist/chunk-ezs352qs.js +0 -237
- package/dist/chunk-ezs352qs.js.map +0 -11
- package/dist/chunk-f5xgprg1.js +0 -59
- package/dist/chunk-f5xgprg1.js.map +0 -10
- package/dist/chunk-fbtb3v0s.js +0 -1339
- package/dist/chunk-fbtb3v0s.js.map +0 -16
- package/dist/chunk-ffefbd3r.js +0 -17
- package/dist/chunk-ffefbd3r.js.map +0 -10
- package/dist/chunk-gaknyw04.js +0 -708
- package/dist/chunk-gaknyw04.js.map +0 -14
- package/dist/chunk-gxmny6tq.js +0 -1637
- package/dist/chunk-gxmny6tq.js.map +0 -15
- package/dist/chunk-h7bj5zwb.js +0 -3069
- package/dist/chunk-h7bj5zwb.js.map +0 -32
- package/dist/chunk-hp4g7wx7.js +0 -46
- package/dist/chunk-hp4g7wx7.js.map +0 -10
- package/dist/chunk-hwy3n31w.js +0 -21
- package/dist/chunk-hwy3n31w.js.map +0 -9
- package/dist/chunk-jzz91jwh.js +0 -70
- package/dist/chunk-jzz91jwh.js.map +0 -10
- package/dist/chunk-k778y16p.js +0 -153
- package/dist/chunk-k778y16p.js.map +0 -11
- package/dist/chunk-mh5mexsf.js +0 -147
- package/dist/chunk-mh5mexsf.js.map +0 -11
- package/dist/chunk-mnwaext5.js +0 -318
- package/dist/chunk-mnwaext5.js.map +0 -10
- package/dist/chunk-nkgs40ch.js +0 -23
- package/dist/chunk-nkgs40ch.js.map +0 -10
- package/dist/chunk-pz57vwsm.js +0 -250
- package/dist/chunk-pz57vwsm.js.map +0 -11
- package/dist/chunk-r6kdp7gn.js +0 -252
- package/dist/chunk-r6kdp7gn.js.map +0 -10
- package/dist/chunk-rkvrkcgx.js +0 -117
- package/dist/chunk-rkvrkcgx.js.map +0 -10
- package/dist/chunk-s74rdyqe.js +0 -117
- package/dist/chunk-s74rdyqe.js.map +0 -12
- package/dist/chunk-sa874j5c.js +0 -9
- package/dist/chunk-sa874j5c.js.map +0 -10
- package/dist/chunk-sffqdkvq.js +0 -122
- package/dist/chunk-sffqdkvq.js.map +0 -10
- package/dist/chunk-tcs9qjps.js +0 -64
- package/dist/chunk-tcs9qjps.js.map +0 -10
- package/dist/chunk-wx83ep8e.js +0 -131
- package/dist/chunk-wx83ep8e.js.map +0 -10
- package/dist/chunk-xag86ng3.js +0 -122
- package/dist/chunk-xag86ng3.js.map +0 -10
- package/dist/chunk-xfdkep71.js +0 -371
- package/dist/chunk-xfdkep71.js.map +0 -11
- package/dist/chunk-xhm1rfpt.js +0 -2907
- package/dist/chunk-xhm1rfpt.js.map +0 -24
- package/dist/chunk-xpcps28p.js +0 -2138
- package/dist/chunk-xpcps28p.js.map +0 -29
- package/dist/chunk-yb4rbev9.js +0 -214
- package/dist/chunk-yb4rbev9.js.map +0 -10
package/dist/hosted-agent.js
CHANGED
|
@@ -1,22 +1,3017 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
maestroProvider,
|
|
7
|
-
resolveAgentExecutionHost,
|
|
8
|
-
transformHostedQueryOptions,
|
|
9
|
-
withAgentExecutionHost
|
|
10
|
-
} from "./chunk-00q25k7p.js";
|
|
11
|
-
import"./chunk-gaknyw04.js";
|
|
12
|
-
import"./chunk-mh5mexsf.js";
|
|
13
|
-
import"./chunk-ezs352qs.js";
|
|
14
|
-
import {
|
|
3
|
+
__require,
|
|
4
|
+
__toESM,
|
|
5
|
+
canonicalMcpBridgeEnv,
|
|
15
6
|
revokeCanonicalMcpBridgeTurn
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import"
|
|
7
|
+
} from "./chunk-s2gez3wg.js";
|
|
8
|
+
|
|
9
|
+
// ../../packages/core/src/agents/claude-provider.ts
|
|
10
|
+
import { spawn } from "child_process";
|
|
11
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
12
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
13
|
+
|
|
14
|
+
// ../../packages/core/src/agents/claude-registry.ts
|
|
15
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync, renameSync as renameSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
16
|
+
import { homedir as homedir3 } from "os";
|
|
17
|
+
import { join as join4 } from "path";
|
|
18
|
+
|
|
19
|
+
// ../../packages/core/src/agents/rollout/claude.ts
|
|
20
|
+
import { randomUUID } from "crypto";
|
|
21
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
22
|
+
import { homedir as homedir2 } from "os";
|
|
23
|
+
import { join as join3 } from "path";
|
|
24
|
+
|
|
25
|
+
// ../../packages/core/src/agents/rollout/shared.ts
|
|
26
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
27
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
28
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
29
|
+
|
|
30
|
+
// ../../packages/core/src/platform/config.ts
|
|
31
|
+
import { randomBytes } from "crypto";
|
|
32
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
33
|
+
import { homedir } from "os";
|
|
34
|
+
import { dirname, join, resolve } from "path";
|
|
35
|
+
import { fileURLToPath } from "url";
|
|
36
|
+
|
|
37
|
+
// ../../packages/core/src/platform/logger.ts
|
|
38
|
+
import pino from "pino";
|
|
39
|
+
var logger = pino({
|
|
40
|
+
level: process.env.LOG_LEVEL || "info",
|
|
41
|
+
transport: {
|
|
42
|
+
target: "pino-pretty",
|
|
43
|
+
options: {
|
|
44
|
+
colorize: true,
|
|
45
|
+
translateTime: "SYS:yyyy-mm-dd HH:MM:ss",
|
|
46
|
+
destination: 2
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}, pino.destination(2));
|
|
50
|
+
|
|
51
|
+
// ../../packages/core/src/types.ts
|
|
52
|
+
var SUPPORTED_AGENTS = ["maestro", "claude", "codex"];
|
|
53
|
+
function isAgentKind(value) {
|
|
54
|
+
return typeof value === "string" && SUPPORTED_AGENTS.includes(value);
|
|
55
|
+
}
|
|
56
|
+
var CLAUDE_EFFORT_VALUES = ["low", "medium", "high", "xhigh", "max"];
|
|
57
|
+
|
|
58
|
+
// ../../packages/core/src/platform/config.ts
|
|
59
|
+
function envText(envKey) {
|
|
60
|
+
const value = process.env[envKey]?.trim();
|
|
61
|
+
return value ? value : undefined;
|
|
62
|
+
}
|
|
63
|
+
function resolveAgentEnv(envKey, fallback, legacyEnvKey) {
|
|
64
|
+
const value = envText(envKey) ?? (legacyEnvKey ? envText(legacyEnvKey) : undefined);
|
|
65
|
+
return isAgentKind(value) ? value : fallback;
|
|
66
|
+
}
|
|
67
|
+
var HOME = homedir();
|
|
68
|
+
function resolveProjectRoot() {
|
|
69
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
70
|
+
const packagedRuntime = resolve(moduleDir, "runtime");
|
|
71
|
+
if (existsSync(resolve(packagedRuntime, "src")))
|
|
72
|
+
return packagedRuntime;
|
|
73
|
+
return resolve(moduleDir, "../..");
|
|
74
|
+
}
|
|
75
|
+
var PROJECT_ROOT = resolveProjectRoot();
|
|
76
|
+
function resolveDependencyBin(name) {
|
|
77
|
+
let dir = PROJECT_ROOT;
|
|
78
|
+
while (true) {
|
|
79
|
+
const candidate = resolve(dir, "node_modules", ".bin", name);
|
|
80
|
+
if (existsSync(candidate))
|
|
81
|
+
return candidate;
|
|
82
|
+
const parent = dirname(dir);
|
|
83
|
+
if (parent === dir)
|
|
84
|
+
return candidate;
|
|
85
|
+
dir = parent;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
var STATE_DIR_ENV = envText("NEGOTIUM_STATE_DIR");
|
|
89
|
+
var STATE_DIR = STATE_DIR_ENV ? resolve(STATE_DIR_ENV) : resolve(HOME, ".negotium");
|
|
90
|
+
function resolveLocalStateDir(envKey, stateName) {
|
|
91
|
+
const envValue = envText(envKey);
|
|
92
|
+
if (envValue)
|
|
93
|
+
return resolve(envValue);
|
|
94
|
+
return resolve(STATE_DIR, stateName);
|
|
95
|
+
}
|
|
96
|
+
function parsePortEnv(envValue, fallback) {
|
|
97
|
+
if (!envValue)
|
|
98
|
+
return fallback;
|
|
99
|
+
const port = Number.parseInt(envValue, 10);
|
|
100
|
+
return Number.isInteger(port) && port > 0 ? port : fallback;
|
|
101
|
+
}
|
|
102
|
+
var WORKSPACE_DIR = resolveLocalStateDir("NEGOTIUM_WORKSPACE_DIR", "workspace");
|
|
103
|
+
var TOPIC_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "topics");
|
|
104
|
+
var SHARED_WIKI_DIR = resolve(WORKSPACE_DIR, "wiki");
|
|
105
|
+
var CONTEXTS_DIR = resolve(WORKSPACE_DIR, "contexts");
|
|
106
|
+
var BROWSER_PROFILES_DIR = resolve(WORKSPACE_DIR, "browser-profiles");
|
|
107
|
+
var DM_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "dm");
|
|
108
|
+
var SESSION_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "sessions");
|
|
109
|
+
var CLAUDE_EXECUTABLE = resolve(HOME, ".local/bin/claude");
|
|
110
|
+
function resolveBrowserMcpBin(envValue) {
|
|
111
|
+
const override = envValue?.trim();
|
|
112
|
+
if (override) {
|
|
113
|
+
if (!/(^|\/)(mcp-patchright|mcp-patchright-http\.mjs)$/.test(override)) {
|
|
114
|
+
throw new Error("NEGOTIUM_BROWSER_MCP_BIN must point to the authenticated mcp-patchright wrapper.");
|
|
115
|
+
}
|
|
116
|
+
return override;
|
|
117
|
+
}
|
|
118
|
+
return PATCHRIGHT_MCP_BIN;
|
|
119
|
+
}
|
|
120
|
+
var PATCHRIGHT_MCP_BIN = resolve(PROJECT_ROOT, "scripts/mcp-patchright-http.mjs");
|
|
121
|
+
var PLAYWRIGHT_MCP_BIN = resolveBrowserMcpBin(envText("NEGOTIUM_BROWSER_MCP_BIN"));
|
|
122
|
+
var TSX_BIN = resolveDependencyBin("tsx");
|
|
123
|
+
var TSCONFIG_PATH = resolve(PROJECT_ROOT, "tsconfig.json");
|
|
124
|
+
var SESSION_COMM_SERVER = resolve(PROJECT_ROOT, "src/mcp/session-comm/server.ts");
|
|
125
|
+
var TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
|
|
126
|
+
var CANONICAL_MCP_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/canonical-proxy-server.ts");
|
|
127
|
+
var WIKI_SERVER = resolve(PROJECT_ROOT, "src/mcp/wiki-server.ts");
|
|
128
|
+
var PADDLEOCR_SERVER = resolve(PROJECT_ROOT, "src/mcp/paddleocr-server.ts");
|
|
129
|
+
var TOKEN_STATS_SERVER = resolve(PROJECT_ROOT, "src/mcp/token-stats-server.ts");
|
|
130
|
+
var SYSTEM_HEALTH_SERVER = resolve(PROJECT_ROOT, "src/mcp/system-health-server.ts");
|
|
131
|
+
var AGENT_HEALTH_SERVER = resolve(PROJECT_ROOT, "src/mcp/agent-health-server.ts");
|
|
132
|
+
var BACKGROUND_BASH_SERVER = resolve(PROJECT_ROOT, "src/mcp/background-bash-server.ts");
|
|
133
|
+
var VAULT_SERVER = resolve(PROJECT_ROOT, "src/mcp/vault-server.ts");
|
|
134
|
+
var BG_BASH_BASE_PORT = parsePortEnv(process.env.BG_BASH_BASE_PORT, 9700);
|
|
135
|
+
var BG_BASH_MAX_PORT = parsePortEnv(process.env.BG_BASH_MAX_PORT, 9799);
|
|
136
|
+
function safeWorkspaceSegment(value, fallback) {
|
|
137
|
+
const cleaned = value.trim().replace(/[^A-Za-z0-9._-]/g, "_").replace(/^_+|_+$/g, "").slice(0, 160);
|
|
138
|
+
return cleaned || fallback;
|
|
139
|
+
}
|
|
140
|
+
function resolveTopicWorkspaceDir(topicId) {
|
|
141
|
+
return join(TOPIC_WORKSPACE_DIR, safeWorkspaceSegment(topicId, "topic"));
|
|
142
|
+
}
|
|
143
|
+
function loadOrCreateLocalSecret(envKey, filename, options = {}) {
|
|
144
|
+
const envValue = envText(envKey);
|
|
145
|
+
const secretFile = resolve(STATE_DIR, filename);
|
|
146
|
+
mkdirSync(dirname(secretFile), { recursive: true });
|
|
147
|
+
if (envValue) {
|
|
148
|
+
if (options.persistEnvValue) {
|
|
149
|
+
writeFileSync(secretFile, `${envValue}
|
|
150
|
+
`, { mode: 384 });
|
|
151
|
+
chmodSync(secretFile, 384);
|
|
152
|
+
}
|
|
153
|
+
return envValue;
|
|
154
|
+
}
|
|
155
|
+
if (existsSync(secretFile)) {
|
|
156
|
+
const stored = readFileSync(secretFile, "utf-8").trim();
|
|
157
|
+
if (stored) {
|
|
158
|
+
chmodSync(secretFile, 384);
|
|
159
|
+
return stored;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const secret = randomBytes(32).toString("base64url");
|
|
163
|
+
try {
|
|
164
|
+
writeFileSync(secretFile, `${secret}
|
|
165
|
+
`, { mode: 384, flag: "wx" });
|
|
166
|
+
return secret;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (error.code !== "EEXIST")
|
|
169
|
+
throw error;
|
|
170
|
+
const stored = readFileSync(secretFile, "utf-8").trim();
|
|
171
|
+
if (!stored)
|
|
172
|
+
throw new Error(`Secret file exists but is empty: ${secretFile}`);
|
|
173
|
+
chmodSync(secretFile, 384);
|
|
174
|
+
return stored;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
var RUNTIME_MCP_SECRET = loadOrCreateLocalSecret("RUNTIME_MCP_SECRET", "runtime-mcp-secret");
|
|
178
|
+
var NODE_CONTROL_TOKEN = loadOrCreateLocalSecret("NEGOTIUM_CONTROL_TOKEN", "node-control-token");
|
|
179
|
+
var VAULT_MASTER_KEY = loadOrCreateLocalSecret("NEGOTIUM_VAULT_MASTER_KEY", "vault-master-key", { persistEnvValue: true });
|
|
180
|
+
delete process.env.NEGOTIUM_VAULT_MASTER_KEY;
|
|
181
|
+
var NEGOTIUM_PORT = parseInt(process.env.NEGOTIUM_PORT || "7777", 10);
|
|
182
|
+
var hostname = process.env.HOSTNAME || "127.0.0.1";
|
|
183
|
+
var DATA_DIR = resolveLocalStateDir("NEGOTIUM_DATA_DIR", "data");
|
|
184
|
+
var LOG_DIR = resolveLocalStateDir("NEGOTIUM_LOG_DIR", "logs");
|
|
185
|
+
var SESSIONS_DB = process.env.SESSIONS_DB_PATH ? resolve(process.env.SESSIONS_DB_PATH) : resolve(DATA_DIR, "sessions.db");
|
|
186
|
+
var DEBUG_FILE = resolve(DATA_DIR, "debug-users.json");
|
|
187
|
+
var USERS_LOG_DIR = resolve(DATA_DIR, "users");
|
|
188
|
+
var RUN_DIR = resolveLocalStateDir("NEGOTIUM_RUN_DIR", "run");
|
|
189
|
+
var PROGRESS_DIR = resolve(RUN_DIR, "progress");
|
|
190
|
+
var DM_CMD_DIR = resolve(RUN_DIR, "dm-commands");
|
|
191
|
+
var DM_RESP_DIR = resolve(RUN_DIR, "dm-responses");
|
|
192
|
+
var SESSION_INBOX_DIR = resolve(RUN_DIR, "session-inbox");
|
|
193
|
+
var SESSION_ASKS_DIR = resolve(RUN_DIR, "session-asks");
|
|
194
|
+
var PLAYWRIGHT_BASE_PORT = parsePortEnv(process.env.PLAYWRIGHT_BASE_PORT, 9100);
|
|
195
|
+
var PLAYWRIGHT_MAX_PORT = parsePortEnv(process.env.PLAYWRIGHT_MAX_PORT, 9499);
|
|
196
|
+
var PLAYWRIGHT_PORTS_DIR = resolve(RUN_DIR, "playwright-ports");
|
|
197
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
198
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
199
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
200
|
+
mkdirSync(PROGRESS_DIR, { recursive: true });
|
|
201
|
+
mkdirSync(DM_CMD_DIR, { recursive: true });
|
|
202
|
+
mkdirSync(DM_RESP_DIR, { recursive: true });
|
|
203
|
+
mkdirSync(SESSION_INBOX_DIR, { recursive: true });
|
|
204
|
+
mkdirSync(SESSION_ASKS_DIR, { recursive: true });
|
|
205
|
+
mkdirSync(PLAYWRIGHT_PORTS_DIR, { recursive: true });
|
|
206
|
+
mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
207
|
+
mkdirSync(TOPIC_WORKSPACE_DIR, { recursive: true });
|
|
208
|
+
mkdirSync(SHARED_WIKI_DIR, { recursive: true });
|
|
209
|
+
mkdirSync(CONTEXTS_DIR, { recursive: true });
|
|
210
|
+
mkdirSync(BROWSER_PROFILES_DIR, { recursive: true });
|
|
211
|
+
mkdirSync(DM_WORKSPACE_DIR, { recursive: true });
|
|
212
|
+
mkdirSync(SESSION_WORKSPACE_DIR, { recursive: true });
|
|
213
|
+
var ACTIVE_QUERY_STALE_MS = 10 * 60 * 1000;
|
|
214
|
+
var AGENTS_PROMPTS_DIR = resolve(PROJECT_ROOT, "src/prompts/agents");
|
|
215
|
+
var RESOURCES_DIR = resolve(PROJECT_ROOT, "src/resources");
|
|
216
|
+
var FILE_TAG_REGEX = /\[FILE:(\/[^\]]+)\]/gi;
|
|
217
|
+
var MODEL_SONNET = "claude-sonnet-5";
|
|
218
|
+
var MODEL_OPUS = "claude-opus-4-8";
|
|
219
|
+
var MODEL_FABLE = "claude-fable-5";
|
|
220
|
+
var FALLBACK_AGENT = resolveAgentEnv("FALLBACK_AGENT", "maestro", "DEFAULT_AGENT");
|
|
221
|
+
var SESSION_AGENT = resolveAgentEnv("SESSION_AGENT", FALLBACK_AGENT);
|
|
222
|
+
var GATEWAY_AGENT = resolveAgentEnv("GATEWAY_AGENT", FALLBACK_AGENT);
|
|
223
|
+
var FALLBACK_MODEL = envText("FALLBACK_MODEL") ?? envText("DEFAULT_MODEL");
|
|
224
|
+
function resolveModelEnv(envKey, agentConst) {
|
|
225
|
+
return envText(envKey) ?? (agentConst === FALLBACK_AGENT ? FALLBACK_MODEL : undefined);
|
|
226
|
+
}
|
|
227
|
+
var SESSION_MODEL = resolveModelEnv("SESSION_MODEL", SESSION_AGENT);
|
|
228
|
+
var GATEWAY_MODEL = resolveModelEnv("GATEWAY_MODEL", GATEWAY_AGENT);
|
|
229
|
+
var FFMPEG_BIN = envText("FFMPEG_BIN");
|
|
230
|
+
var FFPROBE_BIN = envText("FFPROBE_BIN");
|
|
231
|
+
var PYTHON_BIN = envText("PYTHON_BIN") ?? "python3";
|
|
232
|
+
var FASTER_WHISPER_WRAPPER = envText("FASTER_WHISPER_WRAPPER") ?? resolve(PROJECT_ROOT, "scripts/faster-whisper-wrapper.py");
|
|
233
|
+
var WHISPER_MODEL = envText("WHISPER_MODEL_FILE") ?? "turbo";
|
|
234
|
+
var TESSERACT_BIN = envText("TESSERACT_BIN") ?? "tesseract";
|
|
235
|
+
var PDFTOTEXT_BIN = envText("PDFTOTEXT_BIN") ?? "pdftotext";
|
|
236
|
+
var _envMaxTellDepth = Number.parseInt(process.env.MAX_TELL_DEPTH ?? "", 10);
|
|
237
|
+
var MAX_TELL_DEPTH = Number.isInteger(_envMaxTellDepth) && _envMaxTellDepth > 0 ? _envMaxTellDepth : 20;
|
|
238
|
+
function codexAuthFilePath() {
|
|
239
|
+
return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(homedir(), ".codex", "auth.json");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ../../packages/core/src/agents/rollout/shared.ts
|
|
243
|
+
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
244
|
+
var trustedWorkspaceRoots = [WORKSPACE_DIR, DM_WORKSPACE_DIR, TOPIC_WORKSPACE_DIR].map((root) => resolve2(root));
|
|
245
|
+
var FIXTURES_DIR = join2(__dirname2, "..", "fixtures");
|
|
246
|
+
function clone(obj) {
|
|
247
|
+
return structuredClone(obj);
|
|
248
|
+
}
|
|
249
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
250
|
+
function assertUuidLike(label, value) {
|
|
251
|
+
if (!UUID_RE.test(value)) {
|
|
252
|
+
throw new Error(`rollout: ${label} is not a UUID-shaped string: ${value}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function assertCwdInWorkspace(cwd) {
|
|
256
|
+
const abs = resolve2(cwd);
|
|
257
|
+
const ok = trustedWorkspaceRoots.some((root) => abs === root || abs.startsWith(`${root}/`));
|
|
258
|
+
if (!ok) {
|
|
259
|
+
throw new Error(`rollout: cwd outside trusted workspace roots: ${cwd} (resolved=${abs})`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function ensureCwdExists(cwd) {
|
|
263
|
+
assertCwdInWorkspace(cwd);
|
|
264
|
+
try {
|
|
265
|
+
mkdirSync2(cwd, { recursive: true });
|
|
266
|
+
} catch (err) {
|
|
267
|
+
logger.warn({ err, cwd }, "rollout: ensureCwdExists failed \u2014 caller should pre-create cwd");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function truncate(text, n) {
|
|
271
|
+
const cps = Array.from(text);
|
|
272
|
+
return cps.length > n ? `${cps.slice(0, n).join("")}\u2026` : text;
|
|
273
|
+
}
|
|
274
|
+
function extractChatPairs(entries, opts = { includeToolAnnotations: true }) {
|
|
275
|
+
const pairs = [];
|
|
276
|
+
let pendingUser = null;
|
|
277
|
+
let pendingAssistantParts = [];
|
|
278
|
+
let toolBuffer = [];
|
|
279
|
+
const flushAssistant = () => {
|
|
280
|
+
if (pendingUser === null)
|
|
281
|
+
return;
|
|
282
|
+
const tools = opts.includeToolAnnotations && toolBuffer.length > 0 ? `
|
|
283
|
+
|
|
284
|
+
${toolBuffer.join(`
|
|
285
|
+
`)}` : "";
|
|
286
|
+
const assistantText = pendingAssistantParts.join("").trim() + tools;
|
|
287
|
+
if (assistantText.trim()) {
|
|
288
|
+
pairs.push({ userText: pendingUser, assistantText });
|
|
289
|
+
}
|
|
290
|
+
pendingUser = null;
|
|
291
|
+
pendingAssistantParts = [];
|
|
292
|
+
toolBuffer = [];
|
|
293
|
+
};
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
const ev = entry.event;
|
|
296
|
+
switch (ev.type) {
|
|
297
|
+
case "user_message": {
|
|
298
|
+
flushAssistant();
|
|
299
|
+
pendingUser = ev.content;
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case "session":
|
|
303
|
+
if (pendingAssistantParts.length > 0 || toolBuffer.length > 0) {
|
|
304
|
+
flushAssistant();
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
case "text": {
|
|
308
|
+
if (pendingUser === null) {
|
|
309
|
+
pendingUser = "(continued)";
|
|
310
|
+
}
|
|
311
|
+
pendingAssistantParts.push(ev.content);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
case "result": {
|
|
315
|
+
if (pendingUser === null) {
|
|
316
|
+
pendingUser = "(continued)";
|
|
317
|
+
}
|
|
318
|
+
pendingAssistantParts = [ev.content];
|
|
319
|
+
flushAssistant();
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case "text_delta":
|
|
323
|
+
break;
|
|
324
|
+
case "tool_use": {
|
|
325
|
+
const u = ev;
|
|
326
|
+
toolBuffer.push(`<!-- Tool: ${u.name} ${truncate(JSON.stringify(u.input), 200)} -->`);
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
case "tool_result": {
|
|
330
|
+
const u = ev;
|
|
331
|
+
toolBuffer.push(`<!-- Tool result: ${truncate(u.content, 200)} -->`);
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case "error": {
|
|
335
|
+
const u = ev;
|
|
336
|
+
toolBuffer.push(`[Error: ${truncate(u.content, 200)}]`);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
case "tool_progress":
|
|
340
|
+
case "tool_use_summary":
|
|
341
|
+
case "file":
|
|
342
|
+
case "status":
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
flushAssistant();
|
|
347
|
+
return pairs;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ../../packages/core/src/platform/jsonl.ts
|
|
351
|
+
import {
|
|
352
|
+
appendFileSync,
|
|
353
|
+
closeSync,
|
|
354
|
+
fsyncSync,
|
|
355
|
+
mkdirSync as mkdirSync3,
|
|
356
|
+
openSync,
|
|
357
|
+
readFileSync as readFileSync2,
|
|
358
|
+
renameSync,
|
|
359
|
+
statSync,
|
|
360
|
+
unlinkSync,
|
|
361
|
+
writeFileSync as writeFileSync2
|
|
362
|
+
} from "fs";
|
|
363
|
+
import { dirname as dirname3 } from "path";
|
|
364
|
+
function parseJsonlText(raw) {
|
|
365
|
+
return raw.trim().split(`
|
|
366
|
+
`).filter(Boolean).map((line) => JSON.parse(line));
|
|
367
|
+
}
|
|
368
|
+
var LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
369
|
+
function writeJsonlFile(filePath, entries) {
|
|
370
|
+
const dir = dirname3(filePath);
|
|
371
|
+
mkdirSync3(dir, { recursive: true });
|
|
372
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
373
|
+
const payload = `${entries.map((e) => JSON.stringify(e)).join(`
|
|
374
|
+
`)}
|
|
375
|
+
`;
|
|
376
|
+
let fd = null;
|
|
377
|
+
try {
|
|
378
|
+
fd = openSync(tmpPath, "w");
|
|
379
|
+
writeFileSync2(fd, payload);
|
|
380
|
+
fsyncSync(fd);
|
|
381
|
+
closeSync(fd);
|
|
382
|
+
fd = null;
|
|
383
|
+
renameSync(tmpPath, filePath);
|
|
384
|
+
fsyncDirectoryBestEffort(dir);
|
|
385
|
+
} catch (err) {
|
|
386
|
+
if (fd !== null) {
|
|
387
|
+
try {
|
|
388
|
+
closeSync(fd);
|
|
389
|
+
} catch {}
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
unlinkSync(tmpPath);
|
|
393
|
+
} catch {}
|
|
394
|
+
throw err;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function fsyncDirectoryBestEffort(dir) {
|
|
398
|
+
let fd = null;
|
|
399
|
+
try {
|
|
400
|
+
fd = openSync(dir, "r");
|
|
401
|
+
fsyncSync(fd);
|
|
402
|
+
} catch {} finally {
|
|
403
|
+
if (fd !== null) {
|
|
404
|
+
try {
|
|
405
|
+
closeSync(fd);
|
|
406
|
+
} catch {}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ../../packages/core/src/agents/rollout/claude.ts
|
|
412
|
+
var _attachmentsCache = null;
|
|
413
|
+
function loadClaudeAttachments() {
|
|
414
|
+
if (_attachmentsCache)
|
|
415
|
+
return _attachmentsCache;
|
|
416
|
+
const raw = readFileSync3(join3(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
|
|
417
|
+
const lines = parseJsonlText(raw);
|
|
418
|
+
if (lines.length < 2) {
|
|
419
|
+
throw new Error(`loadClaudeAttachments: expected >=2 entries in claude-attachments.jsonl, got ${lines.length}`);
|
|
420
|
+
}
|
|
421
|
+
_attachmentsCache = {
|
|
422
|
+
deferredToolsDelta: lines[0],
|
|
423
|
+
skillListing: lines[1]
|
|
424
|
+
};
|
|
425
|
+
return _attachmentsCache;
|
|
426
|
+
}
|
|
427
|
+
var CLAUDE_SDK_VERSION = "2.1.126";
|
|
428
|
+
var CLAUDE_DEFAULT_MODEL = "claude-sonnet-5";
|
|
429
|
+
var CLAUDE_DEFAULT_GIT_BRANCH = "HEAD";
|
|
430
|
+
function encodeClaudeCwd(cwd) {
|
|
431
|
+
const realCwd = cwd.startsWith("/tmp/") ? cwd.replace(/^\/tmp\//, "/private/tmp/") : cwd;
|
|
432
|
+
return `-${realCwd.replaceAll(/[^a-zA-Z0-9]/g, "-").replace(/^-/, "")}`;
|
|
433
|
+
}
|
|
434
|
+
function writeClaudeRollout(opts) {
|
|
435
|
+
const sessionId = opts.sessionId ?? randomUUID();
|
|
436
|
+
assertUuidLike("sessionId", sessionId);
|
|
437
|
+
ensureCwdExists(opts.cwd);
|
|
438
|
+
const pairs = opts.pairs ?? extractChatPairs(opts.entries ?? []);
|
|
439
|
+
const cwdReal = opts.cwd.startsWith("/tmp/") ? opts.cwd.replace(/^\/tmp\//, "/private/tmp/") : opts.cwd;
|
|
440
|
+
const lines = [];
|
|
441
|
+
const ts = () => new Date().toISOString();
|
|
442
|
+
let lastUuid = null;
|
|
443
|
+
lines.push({ type: "queue-operation", operation: "enqueue", timestamp: ts(), sessionId });
|
|
444
|
+
lines.push({ type: "queue-operation", operation: "dequeue", timestamp: ts(), sessionId });
|
|
445
|
+
const attachments = loadClaudeAttachments();
|
|
446
|
+
for (const pair of pairs) {
|
|
447
|
+
const userUuid = randomUUID();
|
|
448
|
+
lines.push({
|
|
449
|
+
parentUuid: lastUuid,
|
|
450
|
+
isSidechain: false,
|
|
451
|
+
promptId: randomUUID(),
|
|
452
|
+
type: "user",
|
|
453
|
+
message: { role: "user", content: [{ type: "text", text: pair.userText }] },
|
|
454
|
+
uuid: userUuid,
|
|
455
|
+
timestamp: ts(),
|
|
456
|
+
permissionMode: "bypassPermissions",
|
|
457
|
+
userType: "external",
|
|
458
|
+
entrypoint: "sdk-ts",
|
|
459
|
+
cwd: cwdReal,
|
|
460
|
+
sessionId,
|
|
461
|
+
version: CLAUDE_SDK_VERSION,
|
|
462
|
+
gitBranch: CLAUDE_DEFAULT_GIT_BRANCH
|
|
463
|
+
});
|
|
464
|
+
const att1Uuid = randomUUID();
|
|
465
|
+
const att1 = clone(attachments.deferredToolsDelta);
|
|
466
|
+
att1.parentUuid = userUuid;
|
|
467
|
+
att1.uuid = att1Uuid;
|
|
468
|
+
att1.timestamp = ts();
|
|
469
|
+
att1.sessionId = sessionId;
|
|
470
|
+
att1.cwd = cwdReal;
|
|
471
|
+
lines.push(att1);
|
|
472
|
+
const att2Uuid = randomUUID();
|
|
473
|
+
const att2 = clone(attachments.skillListing);
|
|
474
|
+
att2.parentUuid = att1Uuid;
|
|
475
|
+
att2.uuid = att2Uuid;
|
|
476
|
+
att2.timestamp = ts();
|
|
477
|
+
att2.sessionId = sessionId;
|
|
478
|
+
att2.cwd = cwdReal;
|
|
479
|
+
lines.push(att2);
|
|
480
|
+
const assistantUuid = randomUUID();
|
|
481
|
+
lines.push({
|
|
482
|
+
parentUuid: att2Uuid,
|
|
483
|
+
isSidechain: false,
|
|
484
|
+
message: {
|
|
485
|
+
model: opts.model ?? CLAUDE_DEFAULT_MODEL,
|
|
486
|
+
id: `msg_${randomUUID().replace(/-/g, "").slice(0, 24)}`,
|
|
487
|
+
type: "message",
|
|
488
|
+
role: "assistant",
|
|
489
|
+
content: [{ type: "text", text: pair.assistantText }],
|
|
490
|
+
stop_reason: "end_turn",
|
|
491
|
+
stop_sequence: null,
|
|
492
|
+
stop_details: null,
|
|
493
|
+
usage: {
|
|
494
|
+
input_tokens: 0,
|
|
495
|
+
cache_creation_input_tokens: 0,
|
|
496
|
+
cache_read_input_tokens: 0,
|
|
497
|
+
output_tokens: 0,
|
|
498
|
+
server_tool_use: { web_search_requests: 0, web_fetch_requests: 0 },
|
|
499
|
+
service_tier: "standard",
|
|
500
|
+
cache_creation: { ephemeral_1h_input_tokens: 0, ephemeral_5m_input_tokens: 0 },
|
|
501
|
+
inference_geo: "",
|
|
502
|
+
iterations: [],
|
|
503
|
+
speed: "standard"
|
|
504
|
+
},
|
|
505
|
+
diagnostics: null
|
|
506
|
+
},
|
|
507
|
+
requestId: `req_${randomUUID().replace(/-/g, "").slice(0, 22)}`,
|
|
508
|
+
type: "assistant",
|
|
509
|
+
uuid: assistantUuid,
|
|
510
|
+
timestamp: ts(),
|
|
511
|
+
userType: "external",
|
|
512
|
+
entrypoint: "sdk-ts",
|
|
513
|
+
cwd: cwdReal,
|
|
514
|
+
sessionId,
|
|
515
|
+
version: CLAUDE_SDK_VERSION,
|
|
516
|
+
gitBranch: CLAUDE_DEFAULT_GIT_BRANCH
|
|
517
|
+
});
|
|
518
|
+
lastUuid = assistantUuid;
|
|
519
|
+
}
|
|
520
|
+
const projectsDir = join3(homedir2(), ".claude", "projects", encodeClaudeCwd(opts.cwd));
|
|
521
|
+
const path = join3(projectsDir, `${sessionId}.jsonl`);
|
|
522
|
+
writeJsonlFile(path, lines);
|
|
523
|
+
logger.info({ sessionId, path, pairs: pairs.length }, "writeClaudeRollout: synthetic rollout placed");
|
|
524
|
+
return { sessionId, rolloutPath: path };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ../../packages/core/src/agents/claude-registry.ts
|
|
528
|
+
var ALIAS_MAP = {
|
|
529
|
+
sonnet: MODEL_SONNET,
|
|
530
|
+
opus: MODEL_OPUS,
|
|
531
|
+
fable: MODEL_FABLE
|
|
532
|
+
};
|
|
533
|
+
var VALID_ALIASES = new Set(Object.keys(ALIAS_MAP));
|
|
534
|
+
var VALID_EFFORTS = new Set(CLAUDE_EFFORT_VALUES);
|
|
535
|
+
var claudeRegistry = {
|
|
536
|
+
kind: "claude",
|
|
537
|
+
defaultModel: "sonnet",
|
|
538
|
+
defaultEffort: "high",
|
|
539
|
+
expandModelAlias(s) {
|
|
540
|
+
return ALIAS_MAP[s] ?? s;
|
|
541
|
+
},
|
|
542
|
+
validateModel(s) {
|
|
543
|
+
return VALID_ALIASES.has(s);
|
|
544
|
+
},
|
|
545
|
+
validEfforts: CLAUDE_EFFORT_VALUES,
|
|
546
|
+
validateEffort(s) {
|
|
547
|
+
return VALID_EFFORTS.has(s);
|
|
548
|
+
},
|
|
549
|
+
footerLabel(model, effort) {
|
|
550
|
+
return effort ? `${model} \xB7 ${effort}` : model;
|
|
551
|
+
},
|
|
552
|
+
writeRollout(opts) {
|
|
553
|
+
const { sessionId, rolloutPath } = writeClaudeRollout({
|
|
554
|
+
cwd: opts.cwd,
|
|
555
|
+
entries: opts.entries,
|
|
556
|
+
model: claudeRegistry.expandModelAlias(opts.model ?? claudeRegistry.defaultModel),
|
|
557
|
+
...opts.reuseSessionId ? { sessionId: opts.reuseSessionId } : {}
|
|
558
|
+
});
|
|
559
|
+
return { sessionId, rolloutPath };
|
|
560
|
+
},
|
|
561
|
+
async forkSession({ parentSessionId, cwd, title }) {
|
|
562
|
+
const { forkSession } = await import("@anthropic-ai/claude-agent-sdk");
|
|
563
|
+
const result = await forkSession(parentSessionId, {
|
|
564
|
+
...title ? { title } : {}
|
|
565
|
+
});
|
|
566
|
+
const projectsRoot = join4(homedir3(), ".claude", "projects");
|
|
567
|
+
const destDir = join4(projectsRoot, encodeClaudeCwd(cwd));
|
|
568
|
+
const destPath = join4(destDir, `${result.sessionId}.jsonl`);
|
|
569
|
+
if (!existsSync3(destPath)) {
|
|
570
|
+
const sourcePath = readdirSync(projectsRoot).map((d) => join4(projectsRoot, d, `${result.sessionId}.jsonl`)).find((p) => existsSync3(p));
|
|
571
|
+
if (!sourcePath) {
|
|
572
|
+
throw new Error(`claude forkSession: fork rollout ${result.sessionId}.jsonl not found under ${projectsRoot}`);
|
|
573
|
+
}
|
|
574
|
+
mkdirSync4(destDir, { recursive: true });
|
|
575
|
+
renameSync2(sourcePath, destPath);
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
forkId: result.sessionId,
|
|
579
|
+
rolloutPath: destPath
|
|
580
|
+
};
|
|
581
|
+
},
|
|
582
|
+
async cleanupRollouts({ cwd, sessionIds }) {
|
|
583
|
+
const projectsDir = join4(homedir3(), ".claude", "projects", encodeClaudeCwd(cwd));
|
|
584
|
+
const failures = [];
|
|
585
|
+
for (const sid of sessionIds) {
|
|
586
|
+
const path = join4(projectsDir, `${sid}.jsonl`);
|
|
587
|
+
try {
|
|
588
|
+
unlinkSync2(path);
|
|
589
|
+
} catch (e) {
|
|
590
|
+
if (e?.code !== "ENOENT") {
|
|
591
|
+
logger.warn({ err: e, path }, "claude cleanupRollouts: unlink failed");
|
|
592
|
+
failures.push(e);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (failures.length > 0) {
|
|
597
|
+
throw new AggregateError(failures, "claude cleanupRollouts failed");
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
// ../../packages/core/src/agents/deep-map.ts
|
|
603
|
+
function deepMapStrings(value, fn) {
|
|
604
|
+
if (typeof value === "string")
|
|
605
|
+
return fn(value);
|
|
606
|
+
if (Array.isArray(value))
|
|
607
|
+
return value.map((v) => deepMapStrings(v, fn));
|
|
608
|
+
if (value && typeof value === "object") {
|
|
609
|
+
const out = {};
|
|
610
|
+
for (const [k, v] of Object.entries(value)) {
|
|
611
|
+
out[k] = deepMapStrings(v, fn);
|
|
612
|
+
}
|
|
613
|
+
return out;
|
|
614
|
+
}
|
|
615
|
+
return value;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ../../packages/core/src/agents/execution-host.ts
|
|
619
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
620
|
+
|
|
621
|
+
// ../../packages/core/src/security/sensitive-path.ts
|
|
622
|
+
import { realpathSync } from "fs";
|
|
623
|
+
import { resolve as resolve3 } from "path";
|
|
624
|
+
var SENSITIVE_PATH_PATTERNS = [
|
|
625
|
+
/\/\.env(\.|$)/i,
|
|
626
|
+
/\/\.ssh\//i,
|
|
627
|
+
/\/\.aws\//i,
|
|
628
|
+
/\/\.gnupg\//i,
|
|
629
|
+
/\/\.netrc$/i,
|
|
630
|
+
/\/\.npmrc$/i,
|
|
631
|
+
/\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)(\.pub)?$/i,
|
|
632
|
+
/\.(pem|key|p12|pfx|cer|crt)$/i,
|
|
633
|
+
/\/Library\/Keychains\//i,
|
|
634
|
+
/\/vault\.db(-wal|-shm|-journal)?$/i,
|
|
635
|
+
/\/vault-master-key$/i,
|
|
636
|
+
/\/runtime-mcp-secret$/i,
|
|
637
|
+
/\/sessions\.db(-wal|-shm|-journal)?$/i
|
|
638
|
+
];
|
|
639
|
+
function isSensitivePath(filePath) {
|
|
640
|
+
const normalized = resolve3(filePath);
|
|
641
|
+
if (SENSITIVE_PATH_PATTERNS.some((p) => p.test(normalized)))
|
|
642
|
+
return true;
|
|
643
|
+
try {
|
|
644
|
+
const real = realpathSync(normalized);
|
|
645
|
+
if (real !== normalized)
|
|
646
|
+
return SENSITIVE_PATH_PATTERNS.some((p) => p.test(real));
|
|
647
|
+
} catch {}
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// ../../packages/core/src/storage/vault.ts
|
|
652
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync5 } from "fs";
|
|
653
|
+
import { join as join5 } from "path";
|
|
654
|
+
|
|
655
|
+
// ../../packages/core/src/storage/sqlite.ts
|
|
656
|
+
var isBun = typeof process.versions.bun === "string";
|
|
657
|
+
var Database;
|
|
658
|
+
if (isBun) {
|
|
659
|
+
({ Database } = await import("bun:sqlite"));
|
|
660
|
+
} else {
|
|
661
|
+
const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
|
|
662
|
+
const { DatabaseSync } = await import(nodeSqliteSpecifier);
|
|
663
|
+
|
|
664
|
+
class NodeDatabase {
|
|
665
|
+
#db;
|
|
666
|
+
constructor(path, options = {}) {
|
|
667
|
+
this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
|
|
668
|
+
}
|
|
669
|
+
query(sql) {
|
|
670
|
+
return this.#db.prepare(sql);
|
|
671
|
+
}
|
|
672
|
+
prepare(sql) {
|
|
673
|
+
return this.#db.prepare(sql);
|
|
674
|
+
}
|
|
675
|
+
exec(sql) {
|
|
676
|
+
this.#db.exec(sql);
|
|
677
|
+
}
|
|
678
|
+
run(sql, ...params) {
|
|
679
|
+
return this.#db.prepare(sql).run(...params);
|
|
680
|
+
}
|
|
681
|
+
transaction(fn) {
|
|
682
|
+
const run = (begin) => (...args) => {
|
|
683
|
+
this.#db.exec(begin);
|
|
684
|
+
try {
|
|
685
|
+
const result = fn(...args);
|
|
686
|
+
this.#db.exec("COMMIT");
|
|
687
|
+
return result;
|
|
688
|
+
} catch (err) {
|
|
689
|
+
this.#db.exec("ROLLBACK");
|
|
690
|
+
throw err;
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
const tx = run("BEGIN");
|
|
694
|
+
tx.deferred = run("BEGIN DEFERRED");
|
|
695
|
+
tx.immediate = run("BEGIN IMMEDIATE");
|
|
696
|
+
tx.exclusive = run("BEGIN EXCLUSIVE");
|
|
697
|
+
return tx;
|
|
698
|
+
}
|
|
699
|
+
close() {
|
|
700
|
+
this.#db.close();
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
Database = NodeDatabase;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// ../../packages/core/src/storage/vault-crypto.ts
|
|
707
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes2 } from "crypto";
|
|
708
|
+
var ENVELOPE_PREFIX = "otium-vault:v1:";
|
|
709
|
+
var IV_BYTES = 12;
|
|
710
|
+
var KEY_BYTES = 32;
|
|
711
|
+
function encryptionKey(masterKey = VAULT_MASTER_KEY) {
|
|
712
|
+
return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
|
|
713
|
+
}
|
|
714
|
+
function aad(userId, key) {
|
|
715
|
+
return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
|
|
716
|
+
}
|
|
717
|
+
function isEncryptedVaultValue(value) {
|
|
718
|
+
return value.startsWith(ENVELOPE_PREFIX);
|
|
719
|
+
}
|
|
720
|
+
function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
|
|
721
|
+
const iv = randomBytes2(IV_BYTES);
|
|
722
|
+
const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
723
|
+
cipher.setAAD(aad(userId, key));
|
|
724
|
+
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
725
|
+
const tag = cipher.getAuthTag();
|
|
726
|
+
return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
|
|
727
|
+
}
|
|
728
|
+
function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
|
|
729
|
+
if (!isEncryptedVaultValue(storedValue)) {
|
|
730
|
+
return { value: storedValue, legacyPlaintext: true };
|
|
731
|
+
}
|
|
732
|
+
const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
|
|
733
|
+
const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
|
|
734
|
+
if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
|
|
735
|
+
throw new Error("Invalid encrypted vault value");
|
|
736
|
+
}
|
|
737
|
+
const iv = Buffer.from(ivPart, "base64url");
|
|
738
|
+
const ciphertext = Buffer.from(ciphertextPart, "base64url");
|
|
739
|
+
const tag = Buffer.from(tagPart, "base64url");
|
|
740
|
+
if (iv.length !== IV_BYTES || tag.length !== 16) {
|
|
741
|
+
throw new Error("Invalid encrypted vault value");
|
|
742
|
+
}
|
|
743
|
+
const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
744
|
+
decipher.setAAD(aad(userId, key));
|
|
745
|
+
decipher.setAuthTag(tag);
|
|
746
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
747
|
+
return { value: plaintext.toString("utf8"), legacyPlaintext: false };
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// ../../packages/core/src/storage/vault.ts
|
|
751
|
+
var vaultDb;
|
|
752
|
+
var vaultMasterKey = VAULT_MASTER_KEY;
|
|
753
|
+
function initializeVaultDatabase(database) {
|
|
754
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
755
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
756
|
+
database.exec(`
|
|
757
|
+
CREATE TABLE IF NOT EXISTS vault (
|
|
758
|
+
user_id TEXT NOT NULL,
|
|
759
|
+
key TEXT NOT NULL,
|
|
760
|
+
value TEXT NOT NULL,
|
|
761
|
+
description TEXT NOT NULL DEFAULT '',
|
|
762
|
+
PRIMARY KEY (user_id, key)
|
|
763
|
+
)
|
|
764
|
+
`);
|
|
765
|
+
{
|
|
766
|
+
const cols = database.prepare("PRAGMA table_info(vault)").all();
|
|
767
|
+
const uid = cols.find((c) => c.name === "user_id");
|
|
768
|
+
if (uid && uid.type.toUpperCase() === "INTEGER") {
|
|
769
|
+
database.exec("BEGIN");
|
|
770
|
+
database.exec(`
|
|
771
|
+
CREATE TABLE vault_migrated (
|
|
772
|
+
user_id TEXT NOT NULL,
|
|
773
|
+
key TEXT NOT NULL,
|
|
774
|
+
value TEXT NOT NULL,
|
|
775
|
+
description TEXT NOT NULL DEFAULT '',
|
|
776
|
+
PRIMARY KEY (user_id, key)
|
|
777
|
+
)
|
|
778
|
+
`);
|
|
779
|
+
database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
|
|
780
|
+
database.exec("DROP TABLE vault");
|
|
781
|
+
database.exec("ALTER TABLE vault_migrated RENAME TO vault");
|
|
782
|
+
database.exec("COMMIT");
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function openVaultDatabase(dataDir) {
|
|
787
|
+
const path = join5(dataDir, "vault.db");
|
|
788
|
+
mkdirSync5(dataDir, { recursive: true });
|
|
789
|
+
const database = new Database(path, { create: true });
|
|
790
|
+
chmodSync2(path, 384);
|
|
791
|
+
initializeVaultDatabase(database);
|
|
792
|
+
return database;
|
|
793
|
+
}
|
|
794
|
+
function activeVaultDatabase() {
|
|
795
|
+
if (!vaultDb)
|
|
796
|
+
vaultDb = openVaultDatabase(DATA_DIR);
|
|
797
|
+
return vaultDb;
|
|
798
|
+
}
|
|
799
|
+
var VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
800
|
+
function normalizeVaultKey(key) {
|
|
801
|
+
return key.trim().toUpperCase();
|
|
802
|
+
}
|
|
803
|
+
function decryptRow(userId, key, storedValue) {
|
|
804
|
+
const database = activeVaultDatabase();
|
|
805
|
+
const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
|
|
806
|
+
if (decoded.legacyPlaintext) {
|
|
807
|
+
database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
|
|
808
|
+
}
|
|
809
|
+
return decoded.value;
|
|
810
|
+
}
|
|
811
|
+
function vaultListWithValues(userId) {
|
|
812
|
+
const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
813
|
+
return rows.map((row) => ({
|
|
814
|
+
key: row.key,
|
|
815
|
+
description: row.description,
|
|
816
|
+
value: decryptRow(userId, row.key, row.value)
|
|
817
|
+
}));
|
|
818
|
+
}
|
|
819
|
+
function vaultList(userId) {
|
|
820
|
+
return activeVaultDatabase().prepare("SELECT key, description FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
821
|
+
}
|
|
822
|
+
function valueReferencesVaultKey(userId, value) {
|
|
823
|
+
const keys = new Set(vaultList(userId).map((entry) => entry.key));
|
|
824
|
+
const visit = (candidate) => {
|
|
825
|
+
if (typeof candidate === "string") {
|
|
826
|
+
for (const match of candidate.matchAll(/\{\{([^}]+)\}\}/g)) {
|
|
827
|
+
if (keys.has(normalizeVaultKey(match[1] ?? "")))
|
|
828
|
+
return true;
|
|
829
|
+
}
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
if (Array.isArray(candidate))
|
|
833
|
+
return candidate.some(visit);
|
|
834
|
+
if (candidate && typeof candidate === "object") {
|
|
835
|
+
return Object.values(candidate).some(visit);
|
|
836
|
+
}
|
|
837
|
+
return false;
|
|
838
|
+
};
|
|
839
|
+
return visit(value);
|
|
840
|
+
}
|
|
841
|
+
function encodedSecretForms(value) {
|
|
842
|
+
const forms = new Set([
|
|
843
|
+
value,
|
|
844
|
+
encodeURIComponent(value),
|
|
845
|
+
Buffer.from(value, "utf8").toString("base64"),
|
|
846
|
+
Buffer.from(value, "utf8").toString("base64url"),
|
|
847
|
+
Buffer.from(value, "utf8").toString("hex")
|
|
848
|
+
]);
|
|
849
|
+
forms.delete("");
|
|
850
|
+
return [...forms].sort((a, b) => b.length - a.length);
|
|
851
|
+
}
|
|
852
|
+
function redactVaultSecrets(userId, text) {
|
|
853
|
+
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));
|
|
854
|
+
if (candidates.length === 0)
|
|
855
|
+
return text;
|
|
856
|
+
const candidatesByFirstCharacter = new Map;
|
|
857
|
+
for (const candidate of candidates) {
|
|
858
|
+
const first = candidate.form[0];
|
|
859
|
+
if (!first)
|
|
860
|
+
continue;
|
|
861
|
+
const bucket = candidatesByFirstCharacter.get(first) ?? [];
|
|
862
|
+
bucket.push(candidate);
|
|
863
|
+
candidatesByFirstCharacter.set(first, bucket);
|
|
864
|
+
}
|
|
865
|
+
let redacted = "";
|
|
866
|
+
let offset = 0;
|
|
867
|
+
while (offset < text.length) {
|
|
868
|
+
const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
|
|
869
|
+
if (!match) {
|
|
870
|
+
redacted += text[offset];
|
|
871
|
+
offset += 1;
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
redacted += `[REDACTED:${match.key}]`;
|
|
875
|
+
offset += match.form.length;
|
|
876
|
+
}
|
|
877
|
+
return redacted;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// ../../packages/core/src/agents/vault-tool-policy.ts
|
|
881
|
+
var SENSITIVE_RUNTIME_NAMES = [
|
|
882
|
+
"vault.db",
|
|
883
|
+
"vault-master-key",
|
|
884
|
+
"runtime-mcp-secret",
|
|
885
|
+
"sessions.db"
|
|
886
|
+
];
|
|
887
|
+
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.";
|
|
888
|
+
function isVaultBrokerTool(toolName) {
|
|
889
|
+
return toolName.includes("vault_run") || toolName.includes("vault_http_request");
|
|
890
|
+
}
|
|
891
|
+
function referencesRuntimeSecretStorage(value) {
|
|
892
|
+
if (typeof value === "string") {
|
|
893
|
+
const lower = value.toLowerCase();
|
|
894
|
+
if (SENSITIVE_RUNTIME_NAMES.some((name) => lower.includes(name)))
|
|
895
|
+
return true;
|
|
896
|
+
return value.startsWith("/") && isSensitivePath(value);
|
|
897
|
+
}
|
|
898
|
+
if (Array.isArray(value))
|
|
899
|
+
return value.some(referencesRuntimeSecretStorage);
|
|
900
|
+
if (value && typeof value === "object") {
|
|
901
|
+
return Object.values(value).some(referencesRuntimeSecretStorage);
|
|
902
|
+
}
|
|
903
|
+
return false;
|
|
904
|
+
}
|
|
905
|
+
function shouldRedirectVaultTool(userId, toolName, input) {
|
|
906
|
+
return !isVaultBrokerTool(toolName) && valueReferencesVaultKey(userId, input);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// ../../packages/core/src/platform/mcp-config.ts
|
|
910
|
+
import { createHmac as createHmac2 } from "crypto";
|
|
911
|
+
|
|
912
|
+
// ../../packages/core/src/mcp/runtime-spec.ts
|
|
913
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
914
|
+
var RUNTIME_MCP_KEY = "runtime";
|
|
915
|
+
var RUNTIME_MCP_BASE_PATH = "/mcp/runtime";
|
|
916
|
+
var TOKEN_TTL_MS = 4 * 60 * 60 * 1000;
|
|
917
|
+
var CLAUDE_MCP_TOOL_TIMEOUT_MS = 600000;
|
|
918
|
+
var runtimePort = NEGOTIUM_PORT;
|
|
919
|
+
function encodeTokenPart(value) {
|
|
920
|
+
return Buffer.from(JSON.stringify(value), "utf-8").toString("base64url");
|
|
921
|
+
}
|
|
922
|
+
function signTokenPayload(payloadPart) {
|
|
923
|
+
return createHmac("sha256", RUNTIME_MCP_SECRET).update(payloadPart).digest("base64url");
|
|
924
|
+
}
|
|
925
|
+
function issueRuntimeMcpToken(ctx) {
|
|
926
|
+
const payloadPart = encodeTokenPart({
|
|
927
|
+
v: 1,
|
|
928
|
+
exp: Date.now() + TOKEN_TTL_MS,
|
|
929
|
+
ctx
|
|
930
|
+
});
|
|
931
|
+
return `${payloadPart}.${signTokenPayload(payloadPart)}`;
|
|
932
|
+
}
|
|
933
|
+
function buildRuntimeMcpSpec(agent, ctx) {
|
|
934
|
+
const token = issueRuntimeMcpToken(ctx);
|
|
935
|
+
const base = `http://127.0.0.1:${runtimePort}${RUNTIME_MCP_BASE_PATH}`;
|
|
936
|
+
const query = `token=${encodeURIComponent(token)}`;
|
|
937
|
+
if (agent === "codex")
|
|
938
|
+
return { url: `${base}/mcp?${query}` };
|
|
939
|
+
return {
|
|
940
|
+
type: "sse",
|
|
941
|
+
url: `${base}/sse?${query}`,
|
|
942
|
+
timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// ../../packages/core/src/mcp/session-comm/bridge-ipc-config.ts
|
|
947
|
+
var registrations = [];
|
|
948
|
+
function peerSessionBridgeIpcEnv() {
|
|
949
|
+
const active = registrations.at(-1)?.config;
|
|
950
|
+
if (!active)
|
|
951
|
+
return;
|
|
952
|
+
return {
|
|
953
|
+
NEGOTIUM_PEER_SESSION_BRIDGE_URL: active.url,
|
|
954
|
+
NEGOTIUM_PEER_SESSION_BRIDGE_TOKEN: active.token
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// ../../packages/core/src/platform/mcp-config.ts
|
|
959
|
+
function buildStdioMcpServer(agent, serverFile, serverArgs, env) {
|
|
960
|
+
if (agent === "codex") {
|
|
961
|
+
return {
|
|
962
|
+
command: "node",
|
|
963
|
+
args: [TSX_BIN, serverFile, ...serverArgs],
|
|
964
|
+
env: { TSX_TSCONFIG_PATH: TSCONFIG_PATH, ...env }
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
command: "bun",
|
|
969
|
+
args: ["run", serverFile, ...serverArgs],
|
|
970
|
+
...env ? { env } : {}
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
var _playwrightUnavailableNotifier;
|
|
974
|
+
var _playwrightUnavailableLastNotifiedAt = new Map;
|
|
975
|
+
var _PLAYWRIGHT_UNAVAILABLE_COOLDOWN_MS = 5 * 60000;
|
|
976
|
+
var _playwrightUnavailableThisTurn = new Set;
|
|
977
|
+
function _playwrightUnavailableKey(userId, topic) {
|
|
978
|
+
return `${userId}:${topic ?? ""}`;
|
|
979
|
+
}
|
|
980
|
+
function _markPlaywrightUnavailable(ctx) {
|
|
981
|
+
_playwrightUnavailableThisTurn.add(_playwrightUnavailableKey(ctx.userId, ctx.topic));
|
|
982
|
+
if (!_playwrightUnavailableNotifier)
|
|
983
|
+
return;
|
|
984
|
+
const key = _playwrightUnavailableKey(ctx.userId, ctx.topic);
|
|
985
|
+
const now = Date.now();
|
|
986
|
+
const last = _playwrightUnavailableLastNotifiedAt.get(key) ?? 0;
|
|
987
|
+
if (now - last < _PLAYWRIGHT_UNAVAILABLE_COOLDOWN_MS)
|
|
988
|
+
return;
|
|
989
|
+
_playwrightUnavailableLastNotifiedAt.set(key, now);
|
|
990
|
+
try {
|
|
991
|
+
_playwrightUnavailableNotifier(ctx);
|
|
992
|
+
} catch (err) {
|
|
993
|
+
logger.warn({ err }, "playwright unavailable notifier threw");
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
var CODEX_BROWSER_CAPABILITY_ENV = "NEGOTIUM_BROWSER_CAPABILITY";
|
|
997
|
+
function browserOwnerCapability(capability, owner) {
|
|
998
|
+
return createHmac2("sha256", capability).update(owner).digest("hex");
|
|
999
|
+
}
|
|
1000
|
+
function browserOwnerForContext(ctx) {
|
|
1001
|
+
if (ctx.topicId)
|
|
1002
|
+
return `topic:${ctx.topicId}`;
|
|
1003
|
+
if (ctx.userId && ctx.session)
|
|
1004
|
+
return `user:${ctx.userId}:${ctx.session}`;
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
function playwrightTransport(port, owner, capability, agent) {
|
|
1008
|
+
const ownerCapability = browserOwnerCapability(capability, owner);
|
|
1009
|
+
if (agent === "codex") {
|
|
1010
|
+
return {
|
|
1011
|
+
url: `http://127.0.0.1:${port}/mcp`,
|
|
1012
|
+
http_headers: { "X-Browser-Owner": owner },
|
|
1013
|
+
env_http_headers: { "X-Browser-Capability": CODEX_BROWSER_CAPABILITY_ENV }
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
if (agent === "maestro") {
|
|
1017
|
+
const query = new URLSearchParams({ owner, capability: ownerCapability });
|
|
1018
|
+
return {
|
|
1019
|
+
type: "sse",
|
|
1020
|
+
url: `http://127.0.0.1:${port}/sse?${query}`
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
type: "sse",
|
|
1025
|
+
url: `http://127.0.0.1:${port}/sse`,
|
|
1026
|
+
headers: { "X-Browser-Owner": owner, "X-Browser-Capability": ownerCapability }
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
function longLivedHttpMcp(agent, port) {
|
|
1030
|
+
return agent === "codex" ? { url: `http://127.0.0.1:${port}/mcp` } : { type: "sse", url: `http://127.0.0.1:${port}/sse` };
|
|
1031
|
+
}
|
|
1032
|
+
var MCP_CATALOG = {
|
|
1033
|
+
playwright: {
|
|
1034
|
+
scopes: ["dm", "forum", "fork", "cron"],
|
|
1035
|
+
build({ userId, session, topicId, playwrightPort, playwrightCapability, agent }) {
|
|
1036
|
+
if (playwrightPort && playwrightCapability) {
|
|
1037
|
+
const owner = browserOwnerForContext({ userId, session, topicId });
|
|
1038
|
+
if (!owner)
|
|
1039
|
+
return null;
|
|
1040
|
+
return playwrightTransport(playwrightPort, owner, playwrightCapability, agent);
|
|
1041
|
+
}
|
|
1042
|
+
_markPlaywrightUnavailable({
|
|
1043
|
+
userId,
|
|
1044
|
+
topic: session,
|
|
1045
|
+
agent
|
|
1046
|
+
});
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
[RUNTIME_MCP_KEY]: {
|
|
1051
|
+
scopes: ["forum", "manager", "fork", "cron"],
|
|
1052
|
+
forumRequired: true,
|
|
1053
|
+
build({
|
|
1054
|
+
userId,
|
|
1055
|
+
session,
|
|
1056
|
+
topicId,
|
|
1057
|
+
queryId,
|
|
1058
|
+
agent,
|
|
1059
|
+
cwd,
|
|
1060
|
+
model,
|
|
1061
|
+
currentUserPrompt,
|
|
1062
|
+
autoContinue,
|
|
1063
|
+
visualTools,
|
|
1064
|
+
fileDeliveryTools,
|
|
1065
|
+
peerBridge
|
|
1066
|
+
}) {
|
|
1067
|
+
if (!topicId || !agent)
|
|
1068
|
+
return null;
|
|
1069
|
+
return buildRuntimeMcpSpec(agent, {
|
|
1070
|
+
userId,
|
|
1071
|
+
topicId,
|
|
1072
|
+
topicTitle: session,
|
|
1073
|
+
queryId,
|
|
1074
|
+
cwd: cwd ?? resolveTopicWorkspaceDir(topicId),
|
|
1075
|
+
agent,
|
|
1076
|
+
model,
|
|
1077
|
+
currentUserPrompt,
|
|
1078
|
+
autoContinue,
|
|
1079
|
+
visualTools,
|
|
1080
|
+
fileDeliveryTools,
|
|
1081
|
+
peerBridge
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
},
|
|
1085
|
+
paddleocr: {
|
|
1086
|
+
scopes: ["dm", "forum", "fork", "cron"],
|
|
1087
|
+
build({ agent }) {
|
|
1088
|
+
return buildStdioMcpServer(agent, PADDLEOCR_SERVER, []);
|
|
1089
|
+
}
|
|
1090
|
+
},
|
|
1091
|
+
"token-stats": {
|
|
1092
|
+
scopes: ["dm", "forum", "manager", "cron"],
|
|
1093
|
+
forumRequired: true,
|
|
1094
|
+
build({ userId, agent }) {
|
|
1095
|
+
return buildStdioMcpServer(agent, TOKEN_STATS_SERVER, [`--user-id=${userId}`]);
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
task: {
|
|
1099
|
+
scopes: ["dm", "forum", "manager", "cron"],
|
|
1100
|
+
forumRequired: true,
|
|
1101
|
+
build({ userId, session, topicId, queryId, agent, peerBridge }) {
|
|
1102
|
+
if (peerBridge) {
|
|
1103
|
+
if (!topicId || !queryId)
|
|
1104
|
+
return null;
|
|
1105
|
+
const env = canonicalMcpBridgeEnv({
|
|
1106
|
+
surface: "task",
|
|
1107
|
+
userId,
|
|
1108
|
+
topicId,
|
|
1109
|
+
queryId,
|
|
1110
|
+
peerBridge
|
|
1111
|
+
});
|
|
1112
|
+
return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=task"], env) : null;
|
|
1113
|
+
}
|
|
1114
|
+
const args = [`--user-id=${userId}`, `--topic=${session}`];
|
|
1115
|
+
if (topicId)
|
|
1116
|
+
args.push(`--topic-id=${topicId}`);
|
|
1117
|
+
return buildStdioMcpServer(agent, TASK_SERVER, args);
|
|
1118
|
+
}
|
|
1119
|
+
},
|
|
1120
|
+
"session-comm": {
|
|
1121
|
+
scopes: ["forum", "fork", "manager"],
|
|
1122
|
+
forumRequired: true,
|
|
1123
|
+
build({ userId, session, topicId, agent, depth = 0, silent, peerBridge }) {
|
|
1124
|
+
const effectiveAgent = agent ?? FALLBACK_AGENT;
|
|
1125
|
+
const args = [
|
|
1126
|
+
`--user-id=${userId}`,
|
|
1127
|
+
`--topic=${session}`,
|
|
1128
|
+
...topicId ? [`--topic-id=${topicId}`] : [],
|
|
1129
|
+
`--depth=${depth}`,
|
|
1130
|
+
`--agent=${effectiveAgent}`,
|
|
1131
|
+
...silent ? ["--reply-only=true"] : [],
|
|
1132
|
+
...peerBridge ? [`--peer-host-query-id=${peerBridge.hostQueryId}`] : []
|
|
1133
|
+
];
|
|
1134
|
+
return buildStdioMcpServer(effectiveAgent, SESSION_COMM_SERVER, args, peerBridge ? peerSessionBridgeIpcEnv() : undefined);
|
|
1135
|
+
}
|
|
1136
|
+
},
|
|
1137
|
+
wiki: {
|
|
1138
|
+
scopes: ["dm", "forum", "manager", "cron"],
|
|
1139
|
+
forumRequired: true,
|
|
1140
|
+
build({ userId, session, topicId, queryId, wikiTopicId, agent, peerBridge }) {
|
|
1141
|
+
if (peerBridge) {
|
|
1142
|
+
if (!topicId || !queryId)
|
|
1143
|
+
return null;
|
|
1144
|
+
const env = canonicalMcpBridgeEnv({
|
|
1145
|
+
surface: "wiki",
|
|
1146
|
+
userId,
|
|
1147
|
+
topicId,
|
|
1148
|
+
queryId,
|
|
1149
|
+
peerBridge
|
|
1150
|
+
});
|
|
1151
|
+
return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=wiki"], env) : null;
|
|
1152
|
+
}
|
|
1153
|
+
const args = [`--user-id=${userId}`];
|
|
1154
|
+
const resolvedWikiTopicId = wikiTopicId ?? topicId ?? (session !== "dm" ? session : undefined);
|
|
1155
|
+
if (resolvedWikiTopicId)
|
|
1156
|
+
args.push(`--topic-id=${resolvedWikiTopicId}`);
|
|
1157
|
+
args.push("--surface=wiki");
|
|
1158
|
+
return buildStdioMcpServer(agent, WIKI_SERVER, args);
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
skills: {
|
|
1162
|
+
scopes: ["dm", "forum", "manager", "cron"],
|
|
1163
|
+
forumRequired: true,
|
|
1164
|
+
build({ userId, topicId, agent }) {
|
|
1165
|
+
const args = [`--user-id=${userId}`, "--surface=skills"];
|
|
1166
|
+
if (topicId)
|
|
1167
|
+
args.push(`--topic-id=${topicId}`);
|
|
1168
|
+
return buildStdioMcpServer(agent, WIKI_SERVER, args);
|
|
1169
|
+
}
|
|
1170
|
+
},
|
|
1171
|
+
"system-health": {
|
|
1172
|
+
scopes: ["dm", "forum", "manager", "cron"],
|
|
1173
|
+
forumRequired: true,
|
|
1174
|
+
build({ agent }) {
|
|
1175
|
+
return buildStdioMcpServer(agent, SYSTEM_HEALTH_SERVER, []);
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
"background-bash": {
|
|
1179
|
+
scopes: ["forum"],
|
|
1180
|
+
build({ agent, bgBashPort }) {
|
|
1181
|
+
if (bgBashPort === undefined)
|
|
1182
|
+
return null;
|
|
1183
|
+
return longLivedHttpMcp(agent, bgBashPort);
|
|
1184
|
+
}
|
|
1185
|
+
},
|
|
1186
|
+
"agent-health": {
|
|
1187
|
+
scopes: ["forum", "manager", "cron"],
|
|
1188
|
+
forumRequired: true,
|
|
1189
|
+
build({ userId, agent }) {
|
|
1190
|
+
const args = [`--user-id=${userId}`];
|
|
1191
|
+
return buildStdioMcpServer(agent, AGENT_HEALTH_SERVER, args);
|
|
1192
|
+
}
|
|
1193
|
+
},
|
|
1194
|
+
vault: {
|
|
1195
|
+
scopes: ["dm", "forum", "manager", "cron"],
|
|
1196
|
+
forumRequired: true,
|
|
1197
|
+
build({ userId, agent }) {
|
|
1198
|
+
const args = [`--user-id=${userId}`];
|
|
1199
|
+
if (agent === "codex")
|
|
1200
|
+
args.push("--http-only=true");
|
|
1201
|
+
return buildStdioMcpServer(agent, VAULT_SERVER, args);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
function namesInScope(scope) {
|
|
1206
|
+
return Object.entries(MCP_CATALOG).filter(([, e]) => e.scopes.includes(scope)).map(([name]) => name);
|
|
1207
|
+
}
|
|
1208
|
+
var allForumMcpServerNames = [];
|
|
1209
|
+
var requiredForumMcpServers = [];
|
|
1210
|
+
var REQUIRED_FORUM_MCP_SERVERS = requiredForumMcpServers;
|
|
1211
|
+
var optionalForumMcpServers = [];
|
|
1212
|
+
function refreshForumCatalogViews() {
|
|
1213
|
+
const all = namesInScope("forum");
|
|
1214
|
+
const required = Object.entries(MCP_CATALOG).filter(([, entry]) => entry.scopes.includes("forum") && entry.forumRequired).map(([name]) => name);
|
|
1215
|
+
allForumMcpServerNames.splice(0, allForumMcpServerNames.length, ...all);
|
|
1216
|
+
requiredForumMcpServers.splice(0, requiredForumMcpServers.length, ...required);
|
|
1217
|
+
optionalForumMcpServers.splice(0, optionalForumMcpServers.length, ...all.filter((name) => !required.includes(name)));
|
|
1218
|
+
}
|
|
1219
|
+
refreshForumCatalogViews();
|
|
1220
|
+
var nodeMcpEntries = [];
|
|
1221
|
+
function buildNodeMcpSpecs(agent, filter) {
|
|
1222
|
+
const out = {};
|
|
1223
|
+
for (const entry of nodeMcpEntries) {
|
|
1224
|
+
if (!filter(entry.key))
|
|
1225
|
+
continue;
|
|
1226
|
+
out[entry.key] = entry.kind === "http" ? longLivedHttpMcp(agent, entry.port) : {
|
|
1227
|
+
command: entry.command,
|
|
1228
|
+
args: entry.args ?? [],
|
|
1229
|
+
...entry.env ? { env: entry.env } : {}
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
return out;
|
|
1233
|
+
}
|
|
1234
|
+
function buildScope(scope, ctx, filter = () => true) {
|
|
1235
|
+
const out = {};
|
|
1236
|
+
for (const [name, entry] of Object.entries(MCP_CATALOG)) {
|
|
1237
|
+
if (!entry.scopes.includes(scope))
|
|
1238
|
+
continue;
|
|
1239
|
+
if (!filter(name))
|
|
1240
|
+
continue;
|
|
1241
|
+
const spec = entry.build(ctx);
|
|
1242
|
+
if (spec === null)
|
|
1243
|
+
continue;
|
|
1244
|
+
out[name] = spec;
|
|
1245
|
+
}
|
|
1246
|
+
if (scope !== "cron") {
|
|
1247
|
+
Object.assign(out, buildNodeMcpSpecs(ctx.agent, filter));
|
|
1248
|
+
}
|
|
1249
|
+
return out;
|
|
1250
|
+
}
|
|
1251
|
+
function getDmMcpServers(opts) {
|
|
1252
|
+
return buildScope("dm", {
|
|
1253
|
+
userId: opts.userId,
|
|
1254
|
+
session: "dm",
|
|
1255
|
+
agent: opts.agent,
|
|
1256
|
+
playwrightPort: opts.playwrightPort,
|
|
1257
|
+
playwrightCapability: opts.playwrightCapability
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
function getManagerMcpServers(opts) {
|
|
1261
|
+
if (!opts.topicId) {
|
|
1262
|
+
throw new Error("getManagerMcpServers: private General topicId is required");
|
|
1263
|
+
}
|
|
1264
|
+
const topicId = opts.topicId;
|
|
1265
|
+
return buildScope("manager", {
|
|
1266
|
+
userId: opts.userId,
|
|
1267
|
+
session: opts.session ?? "General",
|
|
1268
|
+
topicId,
|
|
1269
|
+
queryId: opts.queryId,
|
|
1270
|
+
wikiTopicId: opts.wikiTopicId ?? topicId,
|
|
1271
|
+
agent: opts.agent,
|
|
1272
|
+
cwd: opts.cwd,
|
|
1273
|
+
model: opts.model,
|
|
1274
|
+
currentUserPrompt: opts.currentUserPrompt,
|
|
1275
|
+
playwrightPort: opts.playwrightPort,
|
|
1276
|
+
playwrightCapability: opts.playwrightCapability,
|
|
1277
|
+
autoContinue: opts.autoContinue,
|
|
1278
|
+
visualTools: opts.visualTools,
|
|
1279
|
+
fileDeliveryTools: opts.fileDeliveryTools
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
function getForumMcpServers(opts) {
|
|
1283
|
+
const {
|
|
1284
|
+
userId,
|
|
1285
|
+
session,
|
|
1286
|
+
topicId,
|
|
1287
|
+
queryId,
|
|
1288
|
+
wikiTopicId,
|
|
1289
|
+
agent,
|
|
1290
|
+
cwd,
|
|
1291
|
+
model,
|
|
1292
|
+
currentUserPrompt,
|
|
1293
|
+
playwrightPort,
|
|
1294
|
+
playwrightCapability,
|
|
1295
|
+
depth = 0,
|
|
1296
|
+
enabled = null,
|
|
1297
|
+
extra = {},
|
|
1298
|
+
silent = false,
|
|
1299
|
+
bgBashPort,
|
|
1300
|
+
autoContinue,
|
|
1301
|
+
visualTools,
|
|
1302
|
+
fileDeliveryTools,
|
|
1303
|
+
peerBridge
|
|
1304
|
+
} = opts;
|
|
1305
|
+
const filter = (name) => {
|
|
1306
|
+
if (silent && name === "task")
|
|
1307
|
+
return false;
|
|
1308
|
+
if (enabled === null)
|
|
1309
|
+
return true;
|
|
1310
|
+
return enabled.includes(name) || REQUIRED_FORUM_MCP_SERVERS.includes(name);
|
|
1311
|
+
};
|
|
1312
|
+
const base = buildScope("forum", {
|
|
1313
|
+
userId,
|
|
1314
|
+
session,
|
|
1315
|
+
topicId,
|
|
1316
|
+
queryId,
|
|
1317
|
+
wikiTopicId,
|
|
1318
|
+
agent,
|
|
1319
|
+
cwd,
|
|
1320
|
+
model,
|
|
1321
|
+
currentUserPrompt,
|
|
1322
|
+
depth,
|
|
1323
|
+
playwrightPort,
|
|
1324
|
+
playwrightCapability,
|
|
1325
|
+
bgBashPort,
|
|
1326
|
+
autoContinue,
|
|
1327
|
+
visualTools,
|
|
1328
|
+
fileDeliveryTools,
|
|
1329
|
+
silent,
|
|
1330
|
+
peerBridge
|
|
1331
|
+
}, filter);
|
|
1332
|
+
return { ...base, ...extra };
|
|
1333
|
+
}
|
|
1334
|
+
function getCronMcpServers(opts) {
|
|
1335
|
+
return buildScope("cron", {
|
|
1336
|
+
userId: opts.userId,
|
|
1337
|
+
session: opts.session,
|
|
1338
|
+
topicId: opts.topicId,
|
|
1339
|
+
queryId: opts.queryId,
|
|
1340
|
+
wikiTopicId: opts.wikiTopicId ?? opts.topicId,
|
|
1341
|
+
agent: opts.agent,
|
|
1342
|
+
cwd: opts.cwd,
|
|
1343
|
+
model: opts.model,
|
|
1344
|
+
currentUserPrompt: opts.currentUserPrompt,
|
|
1345
|
+
playwrightPort: opts.playwrightPort,
|
|
1346
|
+
playwrightCapability: opts.playwrightCapability,
|
|
1347
|
+
autoContinue: false,
|
|
1348
|
+
visualTools: opts.visualTools,
|
|
1349
|
+
fileDeliveryTools: opts.fileDeliveryTools
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
function getMcpServersForQuery(opts) {
|
|
1353
|
+
if (opts.sessionType === "cron") {
|
|
1354
|
+
if (!opts.topicId)
|
|
1355
|
+
throw new Error("getMcpServersForQuery: cron sessionType requires topicId");
|
|
1356
|
+
return getCronMcpServers({
|
|
1357
|
+
userId: opts.userId || "default",
|
|
1358
|
+
session: opts.session || "cron",
|
|
1359
|
+
topicId: opts.topicId,
|
|
1360
|
+
queryId: opts.queryId,
|
|
1361
|
+
wikiTopicId: opts.wikiTopicId,
|
|
1362
|
+
agent: opts.agent,
|
|
1363
|
+
cwd: opts.cwd,
|
|
1364
|
+
model: opts.model,
|
|
1365
|
+
currentUserPrompt: opts.prompt,
|
|
1366
|
+
playwrightPort: opts.playwrightPort,
|
|
1367
|
+
playwrightCapability: opts.playwrightCapability,
|
|
1368
|
+
visualTools: opts.visualTools,
|
|
1369
|
+
fileDeliveryTools: opts.fileDeliveryTools
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
if (opts.sessionType === "dm" || opts.sessionType === "ephemeral") {
|
|
1373
|
+
return getDmMcpServers({
|
|
1374
|
+
userId: opts.userId || "default",
|
|
1375
|
+
agent: opts.agent,
|
|
1376
|
+
playwrightPort: opts.playwrightPort,
|
|
1377
|
+
playwrightCapability: opts.playwrightCapability
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
if (opts.sessionType === "manager") {
|
|
1381
|
+
return getManagerMcpServers({
|
|
1382
|
+
userId: opts.userId || "default",
|
|
1383
|
+
session: opts.session,
|
|
1384
|
+
topicId: opts.topicId,
|
|
1385
|
+
queryId: opts.queryId,
|
|
1386
|
+
wikiTopicId: opts.wikiTopicId,
|
|
1387
|
+
agent: opts.agent,
|
|
1388
|
+
cwd: opts.cwd,
|
|
1389
|
+
model: opts.model,
|
|
1390
|
+
currentUserPrompt: opts.prompt,
|
|
1391
|
+
playwrightPort: opts.playwrightPort,
|
|
1392
|
+
playwrightCapability: opts.playwrightCapability,
|
|
1393
|
+
autoContinue: opts.autoContinue,
|
|
1394
|
+
visualTools: opts.visualTools,
|
|
1395
|
+
fileDeliveryTools: opts.fileDeliveryTools
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
return getForumMcpServers({
|
|
1399
|
+
userId: opts.userId || "default",
|
|
1400
|
+
session: opts.session || "default",
|
|
1401
|
+
topicId: opts.topicId,
|
|
1402
|
+
queryId: opts.queryId,
|
|
1403
|
+
wikiTopicId: opts.wikiTopicId,
|
|
1404
|
+
agent: opts.agent,
|
|
1405
|
+
cwd: opts.cwd,
|
|
1406
|
+
model: opts.model,
|
|
1407
|
+
currentUserPrompt: opts.prompt,
|
|
1408
|
+
playwrightPort: opts.playwrightPort,
|
|
1409
|
+
playwrightCapability: opts.playwrightCapability,
|
|
1410
|
+
bgBashPort: opts.bgBashPort,
|
|
1411
|
+
autoContinue: opts.autoContinue,
|
|
1412
|
+
visualTools: opts.visualTools,
|
|
1413
|
+
fileDeliveryTools: opts.fileDeliveryTools,
|
|
1414
|
+
depth: opts.depth,
|
|
1415
|
+
enabled: opts.mcpEnabled,
|
|
1416
|
+
extra: opts.mcpExtra,
|
|
1417
|
+
silent: opts.silent,
|
|
1418
|
+
peerBridge: opts.peerBridge
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// ../../packages/core/src/agents/execution-host.ts
|
|
1423
|
+
var defaultHost = {
|
|
1424
|
+
getMcpServersForQuery,
|
|
1425
|
+
redactVaultSecrets,
|
|
1426
|
+
referencesRuntimeSecretStorage,
|
|
1427
|
+
shouldRedirectVaultTool,
|
|
1428
|
+
claudeCodeExecutablePath: () => CLAUDE_EXECUTABLE,
|
|
1429
|
+
codexAuthFilePath
|
|
1430
|
+
};
|
|
1431
|
+
var hostRegistrations = [];
|
|
1432
|
+
var scopedHost = new AsyncLocalStorage;
|
|
1433
|
+
function activeHost() {
|
|
1434
|
+
const scoped = scopedHost.getStore();
|
|
1435
|
+
if (scoped)
|
|
1436
|
+
return scoped;
|
|
1437
|
+
const host = { ...defaultHost };
|
|
1438
|
+
for (const registration of hostRegistrations)
|
|
1439
|
+
Object.assign(host, registration.overrides);
|
|
1440
|
+
return host;
|
|
1441
|
+
}
|
|
1442
|
+
function resolveAgentExecutionHost(overrides = {}) {
|
|
1443
|
+
return { ...activeHost(), ...overrides };
|
|
1444
|
+
}
|
|
1445
|
+
function withAgentExecutionHost(host, operation) {
|
|
1446
|
+
return scopedHost.run(host, operation);
|
|
1447
|
+
}
|
|
1448
|
+
function configureAgentExecutionHost(overrides) {
|
|
1449
|
+
const registration = { id: Symbol("agent-execution-host"), overrides };
|
|
1450
|
+
hostRegistrations.push(registration);
|
|
1451
|
+
let disposed = false;
|
|
1452
|
+
return () => {
|
|
1453
|
+
if (disposed)
|
|
1454
|
+
return;
|
|
1455
|
+
disposed = true;
|
|
1456
|
+
const index = hostRegistrations.findIndex((entry) => entry.id === registration.id);
|
|
1457
|
+
if (index >= 0)
|
|
1458
|
+
hostRegistrations.splice(index, 1);
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
function hostedMcpServers(opts) {
|
|
1462
|
+
return activeHost().getMcpServersForQuery(opts);
|
|
1463
|
+
}
|
|
1464
|
+
function redactHostedSecrets(userId, value) {
|
|
1465
|
+
return activeHost().redactVaultSecrets(userId, value);
|
|
1466
|
+
}
|
|
1467
|
+
function referencesHostedSecretStorage(value) {
|
|
1468
|
+
return activeHost().referencesRuntimeSecretStorage(value);
|
|
1469
|
+
}
|
|
1470
|
+
function shouldRedirectHostedVaultTool(userId, toolName, input) {
|
|
1471
|
+
return activeHost().shouldRedirectVaultTool(userId, toolName, input);
|
|
1472
|
+
}
|
|
1473
|
+
function transformHostedQueryOptions(opts) {
|
|
1474
|
+
return activeHost().transformQueryOptions?.(opts) ?? opts;
|
|
1475
|
+
}
|
|
1476
|
+
function hostedClaudeCodeExecutablePath() {
|
|
1477
|
+
return activeHost().claudeCodeExecutablePath();
|
|
1478
|
+
}
|
|
1479
|
+
function hostedCodexAuthFilePath() {
|
|
1480
|
+
return activeHost().codexAuthFilePath();
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// ../../packages/core/src/media/file-events.ts
|
|
1484
|
+
function* extractFileEvents(text, source) {
|
|
1485
|
+
const tagRegex = new RegExp(FILE_TAG_REGEX.source, "gi");
|
|
1486
|
+
let match = tagRegex.exec(text);
|
|
1487
|
+
while (match !== null) {
|
|
1488
|
+
yield { type: "file", path: match[1], source, origin: "tag" };
|
|
1489
|
+
match = tagRegex.exec(text);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// ../../packages/core/src/platform/error.ts
|
|
1494
|
+
function errMsg(e, fallback) {
|
|
1495
|
+
if (e instanceof Error)
|
|
1496
|
+
return e.message;
|
|
1497
|
+
return fallback ?? String(e);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// ../../packages/core/src/agents/claude-provider.ts
|
|
1501
|
+
var CLAUDE_DEFAULT_DISALLOWED_TOOLS = [
|
|
1502
|
+
"AskUserQuestion",
|
|
1503
|
+
"Workflow",
|
|
1504
|
+
"TodoWrite",
|
|
1505
|
+
"TaskCreate",
|
|
1506
|
+
"TaskUpdate",
|
|
1507
|
+
"TaskList",
|
|
1508
|
+
"TaskGet",
|
|
1509
|
+
"Task",
|
|
1510
|
+
"Agent",
|
|
1511
|
+
"TaskOutput",
|
|
1512
|
+
"TaskStop"
|
|
1513
|
+
];
|
|
1514
|
+
function buildClaudeDisallowedTools(extra = undefined) {
|
|
1515
|
+
return [...new Set([...CLAUDE_DEFAULT_DISALLOWED_TOOLS, ...extra ?? []])];
|
|
1516
|
+
}
|
|
1517
|
+
function toClaudeEffort(e) {
|
|
1518
|
+
if (!e || !claudeRegistry.validateEffort(e))
|
|
1519
|
+
return;
|
|
1520
|
+
return e;
|
|
1521
|
+
}
|
|
1522
|
+
var CLAUDE_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
1523
|
+
var CLAUDE_IMAGE_MIME_TYPES = new Set([
|
|
1524
|
+
"image/jpeg",
|
|
1525
|
+
"image/png",
|
|
1526
|
+
"image/gif",
|
|
1527
|
+
"image/webp"
|
|
1528
|
+
]);
|
|
1529
|
+
function isClaudeImageAttachment(attachment) {
|
|
1530
|
+
return attachment.type === "image" && CLAUDE_IMAGE_MIME_TYPES.has(attachment.mimeType);
|
|
1531
|
+
}
|
|
1532
|
+
async function* singleUserMessage(message) {
|
|
1533
|
+
yield message;
|
|
1534
|
+
}
|
|
1535
|
+
function buildClaudePrompt(opts) {
|
|
1536
|
+
const attachments = opts.attachments ?? [];
|
|
1537
|
+
if (attachments.length === 0)
|
|
1538
|
+
return opts.prompt;
|
|
1539
|
+
const imageBlocks = [];
|
|
1540
|
+
for (const attachment of attachments) {
|
|
1541
|
+
if (!isClaudeImageAttachment(attachment))
|
|
1542
|
+
continue;
|
|
1543
|
+
if (attachment.sizeBytes > CLAUDE_IMAGE_MAX_BYTES) {
|
|
1544
|
+
logger.info({
|
|
1545
|
+
path: attachment.path,
|
|
1546
|
+
mimeType: attachment.mimeType,
|
|
1547
|
+
sizeBytes: attachment.sizeBytes,
|
|
1548
|
+
maxBytes: CLAUDE_IMAGE_MAX_BYTES
|
|
1549
|
+
}, "claudeProvider: skipping oversized image attachment content block");
|
|
1550
|
+
continue;
|
|
1551
|
+
}
|
|
1552
|
+
try {
|
|
1553
|
+
imageBlocks.push({
|
|
1554
|
+
type: "image",
|
|
1555
|
+
source: {
|
|
1556
|
+
type: "base64",
|
|
1557
|
+
media_type: attachment.mimeType,
|
|
1558
|
+
data: readFileSync4(attachment.path).toString("base64")
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
} catch (err) {
|
|
1562
|
+
logger.warn({
|
|
1563
|
+
path: attachment.path,
|
|
1564
|
+
mimeType: attachment.mimeType,
|
|
1565
|
+
err
|
|
1566
|
+
}, "claudeProvider: failed to read image attachment for content block");
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
if (imageBlocks.length === 0)
|
|
1570
|
+
return opts.prompt;
|
|
1571
|
+
const content = [{ type: "text", text: opts.prompt }, ...imageBlocks];
|
|
1572
|
+
return singleUserMessage({
|
|
1573
|
+
type: "user",
|
|
1574
|
+
message: {
|
|
1575
|
+
role: "user",
|
|
1576
|
+
content
|
|
1577
|
+
},
|
|
1578
|
+
parent_tool_use_id: null
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
var CLAUDE_ABORT_SIGKILL_DELAY_MS = 2500;
|
|
1582
|
+
function signalProcessTree(pid, signal) {
|
|
1583
|
+
try {
|
|
1584
|
+
process.kill(-pid, signal);
|
|
1585
|
+
return true;
|
|
1586
|
+
} catch (groupErr) {
|
|
1587
|
+
try {
|
|
1588
|
+
process.kill(pid, signal);
|
|
1589
|
+
return true;
|
|
1590
|
+
} catch (pidErr) {
|
|
1591
|
+
logger.debug({ groupErr, pidErr, pid, signal }, "Failed to signal Claude process tree");
|
|
1592
|
+
return false;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
function spawnClaudeCodeProcessWithTreeKill(options) {
|
|
1597
|
+
const child = spawn(options.command, options.args, {
|
|
1598
|
+
cwd: options.cwd,
|
|
1599
|
+
detached: true,
|
|
1600
|
+
env: options.env,
|
|
1601
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
1602
|
+
});
|
|
1603
|
+
if (!child.stdin || !child.stdout) {
|
|
1604
|
+
child.kill("SIGKILL");
|
|
1605
|
+
throw new Error("Failed to spawn Claude Code process with piped stdin/stdout");
|
|
1606
|
+
}
|
|
1607
|
+
let exited = false;
|
|
1608
|
+
let killTimer;
|
|
1609
|
+
const clearKillTimer = () => {
|
|
1610
|
+
if (killTimer)
|
|
1611
|
+
clearTimeout(killTimer);
|
|
1612
|
+
killTimer = undefined;
|
|
1613
|
+
};
|
|
1614
|
+
const kill = (signal) => {
|
|
1615
|
+
if (!child.pid)
|
|
1616
|
+
return child.kill(signal);
|
|
1617
|
+
const ok = signalProcessTree(child.pid, signal);
|
|
1618
|
+
if (signal !== "SIGKILL") {
|
|
1619
|
+
clearKillTimer();
|
|
1620
|
+
killTimer = setTimeout(() => {
|
|
1621
|
+
if (!exited && child.pid) {
|
|
1622
|
+
logger.warn({ pid: child.pid }, "Claude process tree still alive after SIGTERM; SIGKILL");
|
|
1623
|
+
signalProcessTree(child.pid, "SIGKILL");
|
|
1624
|
+
}
|
|
1625
|
+
}, CLAUDE_ABORT_SIGKILL_DELAY_MS);
|
|
1626
|
+
killTimer.unref?.();
|
|
1627
|
+
}
|
|
1628
|
+
return ok;
|
|
1629
|
+
};
|
|
1630
|
+
const onAbort = () => {
|
|
1631
|
+
logger.warn({ pid: child.pid, command: options.command }, "Aborting Claude Code process tree");
|
|
1632
|
+
kill("SIGTERM");
|
|
1633
|
+
};
|
|
1634
|
+
if (options.signal.aborted)
|
|
1635
|
+
onAbort();
|
|
1636
|
+
else
|
|
1637
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
1638
|
+
child.once("exit", (code, signal) => {
|
|
1639
|
+
exited = true;
|
|
1640
|
+
clearKillTimer();
|
|
1641
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
1642
|
+
logger.debug({ pid: child.pid, code, signal }, "Claude Code process exited");
|
|
1643
|
+
});
|
|
1644
|
+
child.once("error", (err) => {
|
|
1645
|
+
exited = true;
|
|
1646
|
+
clearKillTimer();
|
|
1647
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
1648
|
+
logger.error({
|
|
1649
|
+
pid: child.pid,
|
|
1650
|
+
command: options.command,
|
|
1651
|
+
err: err instanceof Error ? err.message : String(err)
|
|
1652
|
+
}, "Claude Code process error event");
|
|
1653
|
+
});
|
|
1654
|
+
return {
|
|
1655
|
+
stdin: child.stdin,
|
|
1656
|
+
stdout: child.stdout,
|
|
1657
|
+
get killed() {
|
|
1658
|
+
return child.killed || exited;
|
|
1659
|
+
},
|
|
1660
|
+
get exitCode() {
|
|
1661
|
+
return child.exitCode;
|
|
1662
|
+
},
|
|
1663
|
+
kill,
|
|
1664
|
+
on(event, listener) {
|
|
1665
|
+
child.on(event, listener);
|
|
1666
|
+
},
|
|
1667
|
+
once(event, listener) {
|
|
1668
|
+
child.once(event, listener);
|
|
1669
|
+
},
|
|
1670
|
+
off(event, listener) {
|
|
1671
|
+
child.off(event, listener);
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
function isAbortError(err) {
|
|
1676
|
+
if (!err || typeof err !== "object")
|
|
1677
|
+
return false;
|
|
1678
|
+
const e = err;
|
|
1679
|
+
if (e.name === "AbortError")
|
|
1680
|
+
return true;
|
|
1681
|
+
if (e.code === 20 || e.code === "ABORT_ERR")
|
|
1682
|
+
return true;
|
|
1683
|
+
return false;
|
|
1684
|
+
}
|
|
1685
|
+
async function* claudeProvider(opts) {
|
|
1686
|
+
const claudeExecutable = hostedClaudeCodeExecutablePath();
|
|
1687
|
+
if (claudeExecutable && !existsSync4(claudeExecutable)) {
|
|
1688
|
+
yield {
|
|
1689
|
+
type: "error",
|
|
1690
|
+
content: `Claude CLI not found at ${claudeExecutable}. Install Claude Code or configure the executable path.`
|
|
1691
|
+
};
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
const cleanEnv = { ...process.env };
|
|
1695
|
+
delete cleanEnv.NEGOTIUM_PEER_SESSION_BRIDGE_URL;
|
|
1696
|
+
delete cleanEnv.NEGOTIUM_PEER_SESSION_BRIDGE_TOKEN;
|
|
1697
|
+
delete cleanEnv.CLAUDECODE;
|
|
1698
|
+
cleanEnv.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT ??= process.env.PADDLEOCR_TIMEOUT_MS ?? "300000";
|
|
1699
|
+
cleanEnv.CLAUDE_CODE_DISABLE_WORKFLOWS = "1";
|
|
1700
|
+
const queryOptions = {
|
|
1701
|
+
...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
|
|
1702
|
+
spawnClaudeCodeProcess: spawnClaudeCodeProcessWithTreeKill,
|
|
1703
|
+
cwd: opts.cwd,
|
|
1704
|
+
permissionMode: "bypassPermissions",
|
|
1705
|
+
allowDangerouslySkipPermissions: true,
|
|
1706
|
+
includePartialMessages: true,
|
|
1707
|
+
env: cleanEnv,
|
|
1708
|
+
mcpServers: hostedMcpServers(opts),
|
|
1709
|
+
abortController: opts.abortController,
|
|
1710
|
+
disallowedTools: buildClaudeDisallowedTools(opts.disallowedTools),
|
|
1711
|
+
...opts.model ? { model: opts.model } : {},
|
|
1712
|
+
...opts.maxBudgetUsd ? { maxBudgetUsd: opts.maxBudgetUsd } : {},
|
|
1713
|
+
...opts.agents ? {
|
|
1714
|
+
agents: Object.fromEntries(Object.entries(opts.agents).map(([name, def]) => {
|
|
1715
|
+
const { effort, ...rest } = def;
|
|
1716
|
+
const narrowed = typeof effort === "string" ? toClaudeEffort(effort) : effort;
|
|
1717
|
+
return [name, narrowed !== undefined ? { ...rest, effort: narrowed } : rest];
|
|
1718
|
+
}))
|
|
1719
|
+
} : {},
|
|
1720
|
+
...toClaudeEffort(opts.effort) ? { effort: toClaudeEffort(opts.effort) } : {},
|
|
1721
|
+
hooks: {
|
|
1722
|
+
PreToolUse: [
|
|
1723
|
+
{
|
|
1724
|
+
hooks: [
|
|
1725
|
+
async (input) => {
|
|
1726
|
+
const { tool_name, tool_input } = input;
|
|
1727
|
+
if (referencesHostedSecretStorage(tool_input)) {
|
|
1728
|
+
return {
|
|
1729
|
+
hookSpecificOutput: {
|
|
1730
|
+
hookEventName: "PreToolUse",
|
|
1731
|
+
permissionDecision: "deny",
|
|
1732
|
+
permissionDecisionReason: "Runtime secret storage access is not permitted"
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
if (tool_name === "Bash" && tool_input?.run_in_background === true) {
|
|
1737
|
+
return {
|
|
1738
|
+
hookSpecificOutput: {
|
|
1739
|
+
hookEventName: "PreToolUse",
|
|
1740
|
+
permissionDecision: "deny",
|
|
1741
|
+
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."
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
const userId = opts.userId ?? "";
|
|
1746
|
+
if (!shouldRedirectHostedVaultTool(userId, tool_name, tool_input)) {
|
|
1747
|
+
return { continue: true };
|
|
1748
|
+
}
|
|
1749
|
+
return {
|
|
1750
|
+
hookSpecificOutput: {
|
|
1751
|
+
hookEventName: "PreToolUse",
|
|
1752
|
+
permissionDecision: "deny",
|
|
1753
|
+
permissionDecisionReason: VAULT_BROKER_REDIRECT_ERROR
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
]
|
|
1758
|
+
}
|
|
1759
|
+
],
|
|
1760
|
+
PostToolUse: [
|
|
1761
|
+
{
|
|
1762
|
+
hooks: [
|
|
1763
|
+
async (input) => {
|
|
1764
|
+
const { tool_response } = input;
|
|
1765
|
+
const userId = opts.userId ?? "";
|
|
1766
|
+
const redacted = deepMapStrings(tool_response, (value) => redactHostedSecrets(userId, value));
|
|
1767
|
+
if (JSON.stringify(redacted) === JSON.stringify(tool_response)) {
|
|
1768
|
+
return { continue: true };
|
|
1769
|
+
}
|
|
1770
|
+
return {
|
|
1771
|
+
hookSpecificOutput: {
|
|
1772
|
+
hookEventName: "PostToolUse",
|
|
1773
|
+
updatedToolOutput: redacted
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
]
|
|
1778
|
+
}
|
|
1779
|
+
]
|
|
1780
|
+
},
|
|
1781
|
+
settingSources: ["project"],
|
|
1782
|
+
settings: {
|
|
1783
|
+
disableWorkflows: true,
|
|
1784
|
+
workflowKeywordTriggerEnabled: false
|
|
1785
|
+
},
|
|
1786
|
+
systemPrompt: opts.systemPrompt
|
|
1787
|
+
};
|
|
1788
|
+
if (opts.sessionId) {
|
|
1789
|
+
queryOptions.resume = opts.sessionId;
|
|
1790
|
+
}
|
|
1791
|
+
const THINKING_BEAT_MS = 5000;
|
|
1792
|
+
let thinkingStart = null;
|
|
1793
|
+
let lastThinkingBeat = 0;
|
|
1794
|
+
let lastContextTokens;
|
|
1795
|
+
let lastContextModel;
|
|
1796
|
+
const thinkingBeat = () => {
|
|
1797
|
+
const now = Date.now();
|
|
1798
|
+
if (thinkingStart === null) {
|
|
1799
|
+
thinkingStart = now;
|
|
1800
|
+
lastThinkingBeat = now;
|
|
1801
|
+
return { type: "tool_progress", toolName: "thinking", elapsed: 0 };
|
|
1802
|
+
}
|
|
1803
|
+
if (now - lastThinkingBeat < THINKING_BEAT_MS)
|
|
1804
|
+
return null;
|
|
1805
|
+
lastThinkingBeat = now;
|
|
1806
|
+
return { type: "tool_progress", toolName: "thinking", elapsed: (now - thinkingStart) / 1000 };
|
|
1807
|
+
};
|
|
1808
|
+
try {
|
|
1809
|
+
const prompt = buildClaudePrompt(opts);
|
|
1810
|
+
for await (const message of query({
|
|
1811
|
+
prompt,
|
|
1812
|
+
options: queryOptions
|
|
1813
|
+
})) {
|
|
1814
|
+
if (message.type === "system" && message.subtype === "thinking_tokens") {
|
|
1815
|
+
const beat = thinkingBeat();
|
|
1816
|
+
if (beat)
|
|
1817
|
+
yield beat;
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
if (message.type === "stream_event") {
|
|
1821
|
+
const streamMsg = message;
|
|
1822
|
+
const evt = streamMsg.event;
|
|
1823
|
+
if (!evt)
|
|
1824
|
+
continue;
|
|
1825
|
+
if (evt.type === "content_block_delta") {
|
|
1826
|
+
const delta = evt;
|
|
1827
|
+
if (delta.delta.type === "thinking_delta") {
|
|
1828
|
+
const beat = thinkingBeat();
|
|
1829
|
+
if (beat)
|
|
1830
|
+
yield beat;
|
|
1831
|
+
} else if (delta.delta.type === "text_delta" && delta.delta.text) {
|
|
1832
|
+
thinkingStart = null;
|
|
1833
|
+
} else {
|
|
1834
|
+
thinkingStart = null;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
thinkingStart = null;
|
|
1840
|
+
if (message.type === "tool_progress") {
|
|
1841
|
+
const m = message;
|
|
1842
|
+
yield {
|
|
1843
|
+
type: "tool_progress",
|
|
1844
|
+
toolName: m.tool_name,
|
|
1845
|
+
elapsed: m.elapsed_time_seconds
|
|
1846
|
+
};
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
if (message.type === "tool_use_summary") {
|
|
1850
|
+
const m = message;
|
|
1851
|
+
yield { type: "tool_use_summary", summary: m.summary };
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
if (message.type === "system") {
|
|
1855
|
+
const m = message;
|
|
1856
|
+
if (m.subtype === "init") {
|
|
1857
|
+
yield { type: "session", sessionId: m.session_id };
|
|
1858
|
+
} else if (m.subtype === "task_started") {
|
|
1859
|
+
const t = m;
|
|
1860
|
+
if (t.subagent_type && !t.skip_transcript) {
|
|
1861
|
+
const desc = t.description ? ` ${t.description.slice(0, 60)}` : "";
|
|
1862
|
+
yield { type: "status", content: `\u25B6 [${t.subagent_type}]${desc}` };
|
|
1863
|
+
}
|
|
1864
|
+
} else {
|
|
1865
|
+
logger.debug({ subtype: m.subtype, msg: m }, "claudeProvider: system message (unhandled subtype)");
|
|
1866
|
+
}
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
if (message.type === "result") {
|
|
1870
|
+
const m = message;
|
|
1871
|
+
if (m.subtype === "success") {
|
|
1872
|
+
const contextWindow = (lastContextModel ? m.modelUsage?.[lastContextModel]?.contextWindow : undefined) ?? Object.values(m.modelUsage ?? {})[0]?.contextWindow;
|
|
1873
|
+
yield {
|
|
1874
|
+
type: "result",
|
|
1875
|
+
content: m.result,
|
|
1876
|
+
stopReason: m.stop_reason ?? "end_turn",
|
|
1877
|
+
usage: m.usage ? {
|
|
1878
|
+
inputTokens: m.usage.input_tokens,
|
|
1879
|
+
outputTokens: m.usage.output_tokens,
|
|
1880
|
+
cacheCreationInputTokens: m.usage.cache_creation_input_tokens ?? undefined,
|
|
1881
|
+
cacheReadInputTokens: m.usage.cache_read_input_tokens ?? undefined,
|
|
1882
|
+
costUsd: m.total_cost_usd,
|
|
1883
|
+
...lastContextTokens !== undefined && contextWindow ? { contextTokens: lastContextTokens, contextWindow } : {}
|
|
1884
|
+
} : undefined
|
|
1885
|
+
};
|
|
1886
|
+
yield* extractFileEvents(m.result, "result");
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
const budgetExceeded = m.subtype === "error_max_budget_usd";
|
|
1890
|
+
const errorMsg = budgetExceeded ? "The job reached its cost limit" : m.errors?.join("; ") || "Unknown error";
|
|
1891
|
+
yield {
|
|
1892
|
+
type: "error",
|
|
1893
|
+
content: errorMsg,
|
|
1894
|
+
...budgetExceeded ? { code: "budget_exceeded" } : {},
|
|
1895
|
+
...m.usage ? {
|
|
1896
|
+
usage: {
|
|
1897
|
+
inputTokens: m.usage.input_tokens,
|
|
1898
|
+
outputTokens: m.usage.output_tokens,
|
|
1899
|
+
cacheCreationInputTokens: m.usage.cache_creation_input_tokens ?? undefined,
|
|
1900
|
+
cacheReadInputTokens: m.usage.cache_read_input_tokens ?? undefined,
|
|
1901
|
+
costUsd: m.total_cost_usd
|
|
1902
|
+
}
|
|
1903
|
+
} : {}
|
|
1904
|
+
};
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
if (message.type === "assistant") {
|
|
1908
|
+
const m = message;
|
|
1909
|
+
const content = m.message?.content ?? [];
|
|
1910
|
+
const usage = m.message?.usage;
|
|
1911
|
+
if (usage) {
|
|
1912
|
+
lastContextTokens = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.output_tokens ?? 0);
|
|
1913
|
+
lastContextModel = m.message.model;
|
|
1914
|
+
}
|
|
1915
|
+
for (const block of content) {
|
|
1916
|
+
if (block.type === "text") {
|
|
1917
|
+
const textBlock = block;
|
|
1918
|
+
yield { type: "text", content: textBlock.text };
|
|
1919
|
+
yield* extractFileEvents(textBlock.text, "text");
|
|
1920
|
+
} else if (block.type === "tool_use") {
|
|
1921
|
+
const tb = block;
|
|
1922
|
+
yield {
|
|
1923
|
+
type: "tool_use",
|
|
1924
|
+
name: tb.name,
|
|
1925
|
+
input: tb.input || {},
|
|
1926
|
+
toolUseId: tb.id
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
continue;
|
|
1931
|
+
}
|
|
1932
|
+
if (message.type === "user") {
|
|
1933
|
+
const um = message;
|
|
1934
|
+
if ("isReplay" in um && um.isReplay)
|
|
1935
|
+
continue;
|
|
1936
|
+
const content = um.message?.content;
|
|
1937
|
+
if (!Array.isArray(content))
|
|
1938
|
+
continue;
|
|
1939
|
+
for (const block of content) {
|
|
1940
|
+
if (block.type !== "tool_result")
|
|
1941
|
+
continue;
|
|
1942
|
+
const trBlock = block;
|
|
1943
|
+
const trContent = typeof trBlock.content === "string" ? trBlock.content.slice(0, 200) : "";
|
|
1944
|
+
yield {
|
|
1945
|
+
type: "tool_result",
|
|
1946
|
+
toolUseId: trBlock.tool_use_id || "",
|
|
1947
|
+
content: trContent
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
} catch (e) {
|
|
1953
|
+
if (isAbortError(e) || opts.abortController?.signal.aborted)
|
|
1954
|
+
return;
|
|
1955
|
+
logger.error({ err: e }, "claudeProvider: SDK iteration failed");
|
|
1956
|
+
yield { type: "error", content: errMsg(e) };
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// ../../packages/core/src/agents/codex-provider.ts
|
|
1961
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1962
|
+
import { Codex } from "@openai/codex-sdk";
|
|
1963
|
+
|
|
1964
|
+
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
1965
|
+
import { spawn as spawn2 } from "child_process";
|
|
1966
|
+
import {
|
|
1967
|
+
chmodSync as chmodSync3,
|
|
1968
|
+
existsSync as existsSync5,
|
|
1969
|
+
readFileSync as readFileSync5,
|
|
1970
|
+
renameSync as renameSync3,
|
|
1971
|
+
unlinkSync as unlinkSync3,
|
|
1972
|
+
writeFileSync as writeFileSync4
|
|
1973
|
+
} from "fs";
|
|
1974
|
+
import { createRequire } from "module";
|
|
1975
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
1976
|
+
|
|
1977
|
+
// ../../packages/core/src/version.ts
|
|
1978
|
+
var NEGOTIUM_VERSION = "0.1.13";
|
|
1979
|
+
|
|
1980
|
+
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
1981
|
+
var NEGOTIUM_MODEL_CATALOG = "negotium-model-catalog.json";
|
|
1982
|
+
var moduleRequire = createRequire(import.meta.url);
|
|
1983
|
+
function codexCliScriptPath() {
|
|
1984
|
+
const sdkPackagePath = moduleRequire.resolve("@openai/codex-sdk/package.json");
|
|
1985
|
+
const sdkRequire = createRequire(sdkPackagePath);
|
|
1986
|
+
return join6(dirname4(sdkRequire.resolve("@openai/codex/package.json")), "bin", "codex.js");
|
|
1987
|
+
}
|
|
1988
|
+
async function bootstrapCodexModelCache(codexHome, cachePath) {
|
|
1989
|
+
const child = spawn2(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
|
|
1990
|
+
env: { ...process.env, CODEX_HOME: codexHome },
|
|
1991
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1992
|
+
});
|
|
1993
|
+
await new Promise((resolve4, reject) => {
|
|
1994
|
+
let settled = false;
|
|
1995
|
+
let stdoutBuffer = "";
|
|
1996
|
+
let stderr = "";
|
|
1997
|
+
const timer = setTimeout(() => finish(new Error("timed out while refreshing the Codex model catalog")), 15000);
|
|
1998
|
+
const finish = (error) => {
|
|
1999
|
+
if (settled)
|
|
2000
|
+
return;
|
|
2001
|
+
settled = true;
|
|
2002
|
+
clearTimeout(timer);
|
|
2003
|
+
try {
|
|
2004
|
+
child.stdin.end();
|
|
2005
|
+
child.kill();
|
|
2006
|
+
} catch {}
|
|
2007
|
+
if (error)
|
|
2008
|
+
reject(error);
|
|
2009
|
+
else if (!existsSync5(cachePath))
|
|
2010
|
+
reject(new Error("Codex did not create its model cache"));
|
|
2011
|
+
else
|
|
2012
|
+
resolve4();
|
|
2013
|
+
};
|
|
2014
|
+
const send = (message) => {
|
|
2015
|
+
child.stdin.write(`${JSON.stringify(message)}
|
|
2016
|
+
`);
|
|
2017
|
+
};
|
|
2018
|
+
child.stderr.on("data", (chunk) => {
|
|
2019
|
+
if (stderr.length < 4096)
|
|
2020
|
+
stderr += String(chunk);
|
|
2021
|
+
});
|
|
2022
|
+
child.on("error", (error) => finish(error));
|
|
2023
|
+
child.on("exit", (code, signal) => {
|
|
2024
|
+
if (!settled) {
|
|
2025
|
+
finish(new Error(`Codex model catalog refresh exited with ${signal ? `signal ${signal}` : `code ${code ?? 1}`}${stderr.trim() ? `: ${stderr.trim()}` : ""}`));
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
child.stdout.on("data", (chunk) => {
|
|
2029
|
+
stdoutBuffer += String(chunk);
|
|
2030
|
+
for (;; ) {
|
|
2031
|
+
const newline = stdoutBuffer.indexOf(`
|
|
2032
|
+
`);
|
|
2033
|
+
if (newline < 0)
|
|
2034
|
+
break;
|
|
2035
|
+
const line = stdoutBuffer.slice(0, newline);
|
|
2036
|
+
stdoutBuffer = stdoutBuffer.slice(newline + 1);
|
|
2037
|
+
let message;
|
|
2038
|
+
try {
|
|
2039
|
+
message = JSON.parse(line);
|
|
2040
|
+
} catch {
|
|
2041
|
+
continue;
|
|
2042
|
+
}
|
|
2043
|
+
if (message.id === 1) {
|
|
2044
|
+
if (message.error) {
|
|
2045
|
+
finish(new Error(message.error.message || "Codex initialization failed"));
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
send({ method: "initialized" });
|
|
2049
|
+
send({ id: 2, method: "model/list", params: { includeHidden: true } });
|
|
2050
|
+
} else if (message.id === 2) {
|
|
2051
|
+
if (message.error) {
|
|
2052
|
+
finish(new Error(message.error.message || "Codex model listing failed"));
|
|
2053
|
+
} else {
|
|
2054
|
+
finish();
|
|
2055
|
+
}
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2060
|
+
send({
|
|
2061
|
+
id: 1,
|
|
2062
|
+
method: "initialize",
|
|
2063
|
+
params: {
|
|
2064
|
+
clientInfo: { name: "negotium", version: NEGOTIUM_VERSION },
|
|
2065
|
+
capabilities: { experimentalApi: true }
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexModelCache) {
|
|
2071
|
+
const codexHome = dirname4(authFilePath);
|
|
2072
|
+
const cachePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE ?? join6(codexHome, "models_cache.json");
|
|
2073
|
+
const hardenedCatalogPath = join6(codexHome, NEGOTIUM_MODEL_CATALOG);
|
|
2074
|
+
if (existsSync5(cachePath) || existsSync5(hardenedCatalogPath))
|
|
2075
|
+
return;
|
|
2076
|
+
if (process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE) {
|
|
2077
|
+
throw new Error(`Configured Codex model cache does not exist: ${cachePath}`);
|
|
2078
|
+
}
|
|
2079
|
+
await bootstrap(codexHome, cachePath);
|
|
2080
|
+
}
|
|
2081
|
+
function writeCodexCatalogWithNativeMultiAgentDisabled(authFilePath) {
|
|
2082
|
+
const codexHome = dirname4(authFilePath);
|
|
2083
|
+
const sourcePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE ?? (existsSync5(join6(codexHome, "models_cache.json")) ? join6(codexHome, "models_cache.json") : join6(codexHome, NEGOTIUM_MODEL_CATALOG));
|
|
2084
|
+
const outputPath = join6(codexHome, NEGOTIUM_MODEL_CATALOG);
|
|
2085
|
+
const parsed = JSON.parse(readFileSync5(sourcePath, "utf8"));
|
|
2086
|
+
if (!Array.isArray(parsed.models) || parsed.models.length === 0) {
|
|
2087
|
+
throw new Error(`Codex model cache has no models: ${sourcePath}`);
|
|
2088
|
+
}
|
|
2089
|
+
const models = parsed.models.map((model, index) => {
|
|
2090
|
+
if (!model || typeof model !== "object" || Array.isArray(model)) {
|
|
2091
|
+
throw new Error(`Codex model cache entry ${index} is invalid: ${sourcePath}`);
|
|
2092
|
+
}
|
|
2093
|
+
return { ...model, multi_agent_version: "disabled" };
|
|
2094
|
+
});
|
|
2095
|
+
const contents = `${JSON.stringify({ models }, null, 2)}
|
|
2096
|
+
`;
|
|
2097
|
+
if (existsSync5(outputPath) && readFileSync5(outputPath, "utf8") === contents) {
|
|
2098
|
+
return outputPath;
|
|
2099
|
+
}
|
|
2100
|
+
const tempPath = `${outputPath}.${process.pid}.${Date.now()}.tmp`;
|
|
2101
|
+
try {
|
|
2102
|
+
writeFileSync4(tempPath, contents, { encoding: "utf8", mode: 384 });
|
|
2103
|
+
renameSync3(tempPath, outputPath);
|
|
2104
|
+
chmodSync3(outputPath, 384);
|
|
2105
|
+
} finally {
|
|
2106
|
+
try {
|
|
2107
|
+
unlinkSync3(tempPath);
|
|
2108
|
+
} catch {}
|
|
2109
|
+
}
|
|
2110
|
+
return outputPath;
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
// ../../packages/core/src/agents/codex-tree-kill.ts
|
|
2114
|
+
import { execFileSync } from "child_process";
|
|
2115
|
+
var SIGKILL_DELAY_MS = 5000;
|
|
2116
|
+
var CODEX_PROC_NAME_MATCH = /codex/i;
|
|
2117
|
+
var TREE_DEPTH_CAP = 16;
|
|
2118
|
+
function getDirectChildren(parentPid) {
|
|
2119
|
+
try {
|
|
2120
|
+
const out = execFileSync("pgrep", ["-P", String(parentPid)], {
|
|
2121
|
+
encoding: "utf8",
|
|
2122
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2123
|
+
}).trim();
|
|
2124
|
+
if (!out)
|
|
2125
|
+
return [];
|
|
2126
|
+
return out.split(`
|
|
2127
|
+
`).map((s) => Number(s)).filter((n) => Number.isFinite(n) && n > 0);
|
|
2128
|
+
} catch {
|
|
2129
|
+
return [];
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
function getProcName(pid) {
|
|
2133
|
+
try {
|
|
2134
|
+
return execFileSync("ps", ["-p", String(pid), "-o", "comm="], {
|
|
2135
|
+
encoding: "utf8",
|
|
2136
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2137
|
+
}).trim();
|
|
2138
|
+
} catch {
|
|
2139
|
+
return "";
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
function getProcLstart(pid) {
|
|
2143
|
+
try {
|
|
2144
|
+
return execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
2145
|
+
encoding: "utf8",
|
|
2146
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2147
|
+
}).trim();
|
|
2148
|
+
} catch {
|
|
2149
|
+
return "";
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
function collectTree(rootPid, out = new Set, depth = 0) {
|
|
2153
|
+
if (out.has(rootPid))
|
|
2154
|
+
return out;
|
|
2155
|
+
out.add(rootPid);
|
|
2156
|
+
if (depth >= TREE_DEPTH_CAP) {
|
|
2157
|
+
logger.warn({ rootPid, depth, cap: TREE_DEPTH_CAP }, "codex-tree-kill: tree depth cap reached, descendants beyond this point not collected");
|
|
2158
|
+
return out;
|
|
2159
|
+
}
|
|
2160
|
+
for (const child of getDirectChildren(rootPid)) {
|
|
2161
|
+
collectTree(child, out, depth + 1);
|
|
2162
|
+
}
|
|
2163
|
+
return out;
|
|
2164
|
+
}
|
|
2165
|
+
function snapshotCodexChildren() {
|
|
2166
|
+
const out = new Map;
|
|
2167
|
+
for (const pid of getDirectChildren(process.pid)) {
|
|
2168
|
+
const name = getProcName(pid);
|
|
2169
|
+
if (!name || !CODEX_PROC_NAME_MATCH.test(name))
|
|
2170
|
+
continue;
|
|
2171
|
+
const lstart = getProcLstart(pid);
|
|
2172
|
+
out.set(pid, lstart);
|
|
2173
|
+
}
|
|
2174
|
+
return out;
|
|
2175
|
+
}
|
|
2176
|
+
var globalOwnedCodexPids = new Set;
|
|
2177
|
+
function registerOwnedCodexPids(pids) {
|
|
2178
|
+
for (const p of pids)
|
|
2179
|
+
globalOwnedCodexPids.add(p);
|
|
2180
|
+
}
|
|
2181
|
+
function unregisterOwnedCodexPids(pids) {
|
|
2182
|
+
for (const p of pids)
|
|
2183
|
+
globalOwnedCodexPids.delete(p);
|
|
2184
|
+
}
|
|
2185
|
+
function findNewCodexChildren(baseline) {
|
|
2186
|
+
const current = snapshotCodexChildren();
|
|
2187
|
+
const novel = [];
|
|
2188
|
+
for (const [pid, lstart] of current) {
|
|
2189
|
+
const baselineLstart = baseline.get(pid);
|
|
2190
|
+
if (baselineLstart !== undefined && baselineLstart === lstart)
|
|
2191
|
+
continue;
|
|
2192
|
+
if (globalOwnedCodexPids.has(pid))
|
|
2193
|
+
continue;
|
|
2194
|
+
novel.push(pid);
|
|
2195
|
+
}
|
|
2196
|
+
return novel;
|
|
2197
|
+
}
|
|
2198
|
+
var codexSpawnChain = Promise.resolve();
|
|
2199
|
+
async function acquireCodexSpawnLock() {
|
|
2200
|
+
const prev = codexSpawnChain;
|
|
2201
|
+
let release;
|
|
2202
|
+
let released = false;
|
|
2203
|
+
codexSpawnChain = new Promise((r) => {
|
|
2204
|
+
release = () => {
|
|
2205
|
+
if (released)
|
|
2206
|
+
return;
|
|
2207
|
+
released = true;
|
|
2208
|
+
r();
|
|
2209
|
+
};
|
|
2210
|
+
});
|
|
2211
|
+
await prev;
|
|
2212
|
+
return release;
|
|
2213
|
+
}
|
|
2214
|
+
function isAlive(pid) {
|
|
2215
|
+
try {
|
|
2216
|
+
process.kill(pid, 0);
|
|
2217
|
+
return true;
|
|
2218
|
+
} catch {
|
|
2219
|
+
return false;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
function isSameProcess(pid, lstart) {
|
|
2223
|
+
return lstart !== "" && isAlive(pid) && getProcLstart(pid) === lstart;
|
|
2224
|
+
}
|
|
2225
|
+
function signalCodexTrees(rootPids) {
|
|
2226
|
+
if (rootPids.length === 0)
|
|
2227
|
+
return null;
|
|
2228
|
+
const targets = new Map;
|
|
2229
|
+
for (const root of rootPids) {
|
|
2230
|
+
for (const pid of collectTree(root)) {
|
|
2231
|
+
if (!targets.has(pid))
|
|
2232
|
+
targets.set(pid, getProcLstart(pid));
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
logger.warn({ rootPids, totalPids: targets.size }, "codex-tree-kill: SIGTERM process tree");
|
|
2236
|
+
for (const pid of targets.keys()) {
|
|
2237
|
+
try {
|
|
2238
|
+
process.kill(pid, "SIGTERM");
|
|
2239
|
+
} catch {}
|
|
2240
|
+
}
|
|
2241
|
+
return { rootPids, targets };
|
|
2242
|
+
}
|
|
2243
|
+
function killCodexStragglers({ rootPids, targets }) {
|
|
2244
|
+
const aliveTargets = [];
|
|
2245
|
+
let droppedAsPossibleReuse = 0;
|
|
2246
|
+
for (const [pid, lstart] of targets) {
|
|
2247
|
+
if (isSameProcess(pid, lstart)) {
|
|
2248
|
+
aliveTargets.push(pid);
|
|
2249
|
+
} else if (isAlive(pid)) {
|
|
2250
|
+
droppedAsPossibleReuse++;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
if (aliveTargets.length === 0)
|
|
2254
|
+
return;
|
|
2255
|
+
const reachable = new Set(aliveTargets);
|
|
2256
|
+
for (const root of rootPids) {
|
|
2257
|
+
const rootLstart = targets.get(root);
|
|
2258
|
+
if (rootLstart && isSameProcess(root, rootLstart)) {
|
|
2259
|
+
for (const pid of collectTree(root))
|
|
2260
|
+
reachable.add(pid);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
const stragglers = [];
|
|
2264
|
+
for (const pid of reachable) {
|
|
2265
|
+
const lstart = targets.get(pid);
|
|
2266
|
+
if (lstart && isSameProcess(pid, lstart))
|
|
2267
|
+
stragglers.push(pid);
|
|
2268
|
+
else if (isAlive(pid))
|
|
2269
|
+
droppedAsPossibleReuse++;
|
|
2270
|
+
}
|
|
2271
|
+
logger.warn({
|
|
2272
|
+
aliveRoots: rootPids.filter((pid) => {
|
|
2273
|
+
const lstart = targets.get(pid);
|
|
2274
|
+
return lstart ? isSameProcess(pid, lstart) : false;
|
|
2275
|
+
}),
|
|
2276
|
+
totalStragglers: stragglers.length,
|
|
2277
|
+
droppedAsPossibleReuse
|
|
2278
|
+
}, "codex-tree-kill: SIGKILL fallback after SIGTERM timeout");
|
|
2279
|
+
for (const pid of stragglers) {
|
|
2280
|
+
try {
|
|
2281
|
+
process.kill(pid, "SIGKILL");
|
|
2282
|
+
} catch {}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
function killCodexTrees(rootPids) {
|
|
2286
|
+
const plan = signalCodexTrees(rootPids);
|
|
2287
|
+
if (!plan)
|
|
2288
|
+
return;
|
|
2289
|
+
const killTimer = setTimeout(() => {
|
|
2290
|
+
killCodexStragglers(plan);
|
|
2291
|
+
}, SIGKILL_DELAY_MS);
|
|
2292
|
+
killTimer.unref?.();
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
// ../../packages/core/src/agents/rollout/codex.ts
|
|
2296
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2297
|
+
import { homedir as homedir4 } from "os";
|
|
2298
|
+
import { join as join7 } from "path";
|
|
2299
|
+
function codexSessionsDir() {
|
|
2300
|
+
return join7(process.env.CODEX_HOME || join7(homedir4(), ".codex"), "sessions");
|
|
2301
|
+
}
|
|
2302
|
+
function extractLatestCodexContextUsage(jsonl) {
|
|
2303
|
+
const lines = jsonl.trimEnd().split(`
|
|
2304
|
+
`);
|
|
2305
|
+
for (let index = lines.length - 1;index >= 0; index -= 1) {
|
|
2306
|
+
try {
|
|
2307
|
+
const entry = JSON.parse(lines[index] ?? "");
|
|
2308
|
+
if (entry.type !== "event_msg" || entry.payload?.type !== "token_count")
|
|
2309
|
+
continue;
|
|
2310
|
+
const contextTokens = entry.payload.info?.last_token_usage?.total_tokens;
|
|
2311
|
+
const contextWindow = entry.payload.info?.model_context_window;
|
|
2312
|
+
if (typeof contextTokens === "number" && Number.isFinite(contextTokens) && contextTokens >= 0 && typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0) {
|
|
2313
|
+
return { contextTokens, contextWindow };
|
|
2314
|
+
}
|
|
2315
|
+
} catch {}
|
|
2316
|
+
}
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
function readLatestCodexContextUsage(threadId) {
|
|
2320
|
+
const sessionsDir = codexSessionsDir();
|
|
2321
|
+
const candidates = [];
|
|
2322
|
+
const buckets = candidateDateBuckets(threadId);
|
|
2323
|
+
try {
|
|
2324
|
+
if (buckets) {
|
|
2325
|
+
for (const bucket of buckets) {
|
|
2326
|
+
const dir = join7(sessionsDir, bucket);
|
|
2327
|
+
if (!existsSync6(dir))
|
|
2328
|
+
continue;
|
|
2329
|
+
const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
|
|
2330
|
+
for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
|
|
2331
|
+
candidates.push(join7(dir, rel));
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
} else {
|
|
2335
|
+
const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
|
|
2336
|
+
for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
|
|
2337
|
+
candidates.push(join7(sessionsDir, rel));
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
const path = candidates.sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs)[0];
|
|
2341
|
+
return path ? extractLatestCodexContextUsage(readFileSync6(path, "utf8")) : undefined;
|
|
2342
|
+
} catch (error) {
|
|
2343
|
+
logger.debug({ error, threadId }, "codex context usage: rollout read failed");
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
function migrateCodexRolloutNativeMultiAgentMetadata(threadId) {
|
|
2348
|
+
const path = latestCodexRolloutPath(threadId);
|
|
2349
|
+
if (!path)
|
|
2350
|
+
return false;
|
|
2351
|
+
try {
|
|
2352
|
+
const entries = parseJsonlText(readFileSync6(path, "utf8"));
|
|
2353
|
+
let changed = false;
|
|
2354
|
+
for (const entry of entries) {
|
|
2355
|
+
if (entry.type !== "session_meta" && entry.type !== "turn_context")
|
|
2356
|
+
continue;
|
|
2357
|
+
if (!entry.payload)
|
|
2358
|
+
entry.payload = {};
|
|
2359
|
+
if (entry.payload.multi_agent_version !== "disabled") {
|
|
2360
|
+
entry.payload.multi_agent_version = "disabled";
|
|
2361
|
+
changed = true;
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
if (changed) {
|
|
2365
|
+
writeJsonlFile(path, entries);
|
|
2366
|
+
logger.info({ threadId, path }, "codex rollout native multi-agent metadata disabled");
|
|
2367
|
+
}
|
|
2368
|
+
return changed;
|
|
2369
|
+
} catch (error) {
|
|
2370
|
+
logger.error({ error, threadId, path }, "codex rollout native multi-agent migration failed");
|
|
2371
|
+
throw new Error(`Failed to migrate Codex rollout '${threadId}'`, { cause: error });
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
function latestCodexRolloutPath(threadId) {
|
|
2375
|
+
const sessionsDir = codexSessionsDir();
|
|
2376
|
+
const candidates = [];
|
|
2377
|
+
const buckets = candidateDateBuckets(threadId);
|
|
2378
|
+
try {
|
|
2379
|
+
if (buckets) {
|
|
2380
|
+
for (const bucket of buckets) {
|
|
2381
|
+
const dir = join7(sessionsDir, bucket);
|
|
2382
|
+
if (!existsSync6(dir))
|
|
2383
|
+
continue;
|
|
2384
|
+
const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
|
|
2385
|
+
for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
|
|
2386
|
+
candidates.push(join7(dir, rel));
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
if (candidates.length === 0) {
|
|
2391
|
+
const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
|
|
2392
|
+
for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
|
|
2393
|
+
candidates.push(join7(sessionsDir, rel));
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
return candidates.sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs)[0];
|
|
2397
|
+
} catch (error) {
|
|
2398
|
+
logger.debug({ error, threadId }, "codex rollout lookup failed");
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
var MAX_DAY_SPAN = 30;
|
|
2403
|
+
var DAY_MS = 86400000;
|
|
2404
|
+
var LOCAL_DATE_SKEW_DAYS = 1;
|
|
2405
|
+
function candidateDateBuckets(threadId) {
|
|
2406
|
+
const tidMs = decodeUuidV7Timestamp(threadId);
|
|
2407
|
+
if (tidMs === null)
|
|
2408
|
+
return null;
|
|
2409
|
+
const nowMs = Date.now();
|
|
2410
|
+
const tidDay = Math.floor(tidMs / DAY_MS);
|
|
2411
|
+
const todayDay = Math.floor(nowMs / DAY_MS);
|
|
2412
|
+
const minDay = Math.min(tidDay, todayDay);
|
|
2413
|
+
const maxDay = Math.max(tidDay, todayDay);
|
|
2414
|
+
const span = maxDay - minDay;
|
|
2415
|
+
if (span > MAX_DAY_SPAN)
|
|
2416
|
+
return null;
|
|
2417
|
+
const buckets = new Set;
|
|
2418
|
+
for (let d = minDay - LOCAL_DATE_SKEW_DAYS;d <= maxDay + LOCAL_DATE_SKEW_DAYS; d++) {
|
|
2419
|
+
buckets.add(formatDateBucket(new Date(d * DAY_MS)));
|
|
2420
|
+
}
|
|
2421
|
+
return buckets;
|
|
2422
|
+
}
|
|
2423
|
+
function decodeUuidV7Timestamp(threadId) {
|
|
2424
|
+
if (typeof threadId !== "string")
|
|
2425
|
+
return null;
|
|
2426
|
+
const m = /^([0-9a-fA-F]{8})-([0-9a-fA-F]{4})-7[0-9a-fA-F]{3}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.exec(threadId);
|
|
2427
|
+
if (!m)
|
|
2428
|
+
return null;
|
|
2429
|
+
const hex = `${m[1]}${m[2]}`;
|
|
2430
|
+
const ms = Number.parseInt(hex, 16);
|
|
2431
|
+
if (!Number.isFinite(ms))
|
|
2432
|
+
return null;
|
|
2433
|
+
const MIN = 1577836800000;
|
|
2434
|
+
const MAX = 4102444800000;
|
|
2435
|
+
if (ms < MIN || ms > MAX)
|
|
2436
|
+
return null;
|
|
2437
|
+
return ms;
|
|
2438
|
+
}
|
|
2439
|
+
function formatDateBucket(d) {
|
|
2440
|
+
const yyyy = d.getUTCFullYear();
|
|
2441
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
2442
|
+
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
2443
|
+
return `${yyyy}/${mm}/${dd}`;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// ../../packages/core/src/agents/codex-provider.ts
|
|
2447
|
+
function mapEffort(effort) {
|
|
2448
|
+
if (!effort)
|
|
2449
|
+
return;
|
|
2450
|
+
if (effort === "max" || effort === "minimal") {
|
|
2451
|
+
throw new Error(`codexProvider received effort='${effort}', which codex does not support \u2014 storage validation was bypassed`);
|
|
2452
|
+
}
|
|
2453
|
+
return effort;
|
|
2454
|
+
}
|
|
2455
|
+
var CODEX_MCP_SERVER_NAME_OVERRIDES = {
|
|
2456
|
+
playwright: "otium_playwright"
|
|
2457
|
+
};
|
|
2458
|
+
function codexMcpServerName(name) {
|
|
2459
|
+
return CODEX_MCP_SERVER_NAME_OVERRIDES[name] ?? name;
|
|
2460
|
+
}
|
|
2461
|
+
function parsePositiveInt(value, fallback) {
|
|
2462
|
+
if (!value)
|
|
2463
|
+
return fallback;
|
|
2464
|
+
const parsed = Number.parseInt(value, 10);
|
|
2465
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
2466
|
+
}
|
|
2467
|
+
function withCodexMcpServerOverrides(name, server) {
|
|
2468
|
+
if (name === "paddleocr") {
|
|
2469
|
+
return {
|
|
2470
|
+
...server,
|
|
2471
|
+
startup_timeout_sec: parsePositiveInt(process.env.PADDLEOCR_MCP_STARTUP_TIMEOUT_SEC, 30),
|
|
2472
|
+
tool_timeout_sec: parsePositiveInt(process.env.PADDLEOCR_MCP_TOOL_TIMEOUT_SEC, 300)
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
return {
|
|
2476
|
+
startup_timeout_sec: parsePositiveInt(process.env.CODEX_MCP_STARTUP_TIMEOUT_SEC, 30),
|
|
2477
|
+
...server
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
function toCodexMcpServers(claudeShape) {
|
|
2481
|
+
const disabledBrowserStdio = () => ({
|
|
2482
|
+
command: process.execPath,
|
|
2483
|
+
args: ["-e", "process.exit(0)"],
|
|
2484
|
+
enabled: false
|
|
2485
|
+
});
|
|
2486
|
+
const out = {
|
|
2487
|
+
playwright: disabledBrowserStdio(),
|
|
2488
|
+
"browser-rs": disabledBrowserStdio(),
|
|
2489
|
+
patchright: disabledBrowserStdio()
|
|
2490
|
+
};
|
|
2491
|
+
for (const [name, srv] of Object.entries(claudeShape)) {
|
|
2492
|
+
if (!srv || typeof srv !== "object")
|
|
2493
|
+
continue;
|
|
2494
|
+
const s = srv;
|
|
2495
|
+
const codexName = codexMcpServerName(name);
|
|
2496
|
+
if (typeof s.command === "string") {
|
|
2497
|
+
out[codexName] = withCodexMcpServerOverrides(name, {
|
|
2498
|
+
command: s.command,
|
|
2499
|
+
...Array.isArray(s.args) ? { args: s.args } : {},
|
|
2500
|
+
...s.env && typeof s.env === "object" ? { env: s.env } : {}
|
|
2501
|
+
});
|
|
2502
|
+
continue;
|
|
2503
|
+
}
|
|
2504
|
+
if (typeof s.url === "string" && s.type !== "sse") {
|
|
2505
|
+
out[codexName] = withCodexMcpServerOverrides(name, {
|
|
2506
|
+
url: s.url,
|
|
2507
|
+
...s.http_headers && typeof s.http_headers === "object" ? { http_headers: s.http_headers } : {},
|
|
2508
|
+
...s.env_http_headers && typeof s.env_http_headers === "object" ? { env_http_headers: s.env_http_headers } : {}
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return out;
|
|
2513
|
+
}
|
|
2514
|
+
function isMissingCodexRolloutError(err) {
|
|
2515
|
+
const msg = errMsg(err);
|
|
2516
|
+
return /no rollout found|thread\/resume failed/i.test(msg);
|
|
2517
|
+
}
|
|
2518
|
+
function summarizeMcpToolCallResult(item) {
|
|
2519
|
+
if (item.error)
|
|
2520
|
+
return item.error.message.slice(0, 200);
|
|
2521
|
+
if (!item.result)
|
|
2522
|
+
return "";
|
|
2523
|
+
const blocks = Array.isArray(item.result.content) ? item.result.content : [];
|
|
2524
|
+
const text = blocks.map((b) => {
|
|
2525
|
+
if (b && typeof b === "object" && "type" in b && b.type === "text" && "text" in b) {
|
|
2526
|
+
return String(b.text ?? "");
|
|
2527
|
+
}
|
|
2528
|
+
return "";
|
|
2529
|
+
}).filter(Boolean).join(`
|
|
2530
|
+
`);
|
|
2531
|
+
if (text)
|
|
2532
|
+
return text.slice(0, 200);
|
|
2533
|
+
return JSON.stringify(item.result.structured_content ?? "").slice(0, 200);
|
|
2534
|
+
}
|
|
2535
|
+
function fileChangeEvents(item) {
|
|
2536
|
+
return item.changes.flatMap((change, index) => {
|
|
2537
|
+
const name = change.kind === "add" ? "Write" : change.kind === "delete" ? "Delete" : "Edit";
|
|
2538
|
+
const toolUseId = `${item.id}:${index}`;
|
|
2539
|
+
const success = item.status === "completed";
|
|
2540
|
+
return [
|
|
2541
|
+
{
|
|
2542
|
+
type: "tool_use",
|
|
2543
|
+
name,
|
|
2544
|
+
input: { file_path: change.path, change_kind: change.kind },
|
|
2545
|
+
toolUseId
|
|
2546
|
+
},
|
|
2547
|
+
{
|
|
2548
|
+
type: "tool_result",
|
|
2549
|
+
toolUseId,
|
|
2550
|
+
content: success ? `${change.kind} applied: ${change.path}` : `${change.kind} failed: ${change.path}`
|
|
2551
|
+
}
|
|
2552
|
+
];
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
function promptForThread(opts, includeSystemPrompt) {
|
|
2556
|
+
return includeSystemPrompt && opts.systemPrompt ? `[System Instructions]
|
|
2557
|
+
${opts.systemPrompt}
|
|
2558
|
+
|
|
2559
|
+
${opts.prompt}` : opts.prompt;
|
|
2560
|
+
}
|
|
2561
|
+
async function closeIterator(iter) {
|
|
2562
|
+
try {
|
|
2563
|
+
await iter.return?.();
|
|
2564
|
+
} catch (err) {
|
|
2565
|
+
logger.warn({ err }, "codexProvider: failed to close codex event iterator");
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
var CODEX_STARTUP_TIMEOUT_MS = 90000;
|
|
2569
|
+
async function startStreamedWithTracking(thread, prompt, abortSignal, trackedPids) {
|
|
2570
|
+
const release = await acquireCodexSpawnLock();
|
|
2571
|
+
try {
|
|
2572
|
+
const baseline = snapshotCodexChildren();
|
|
2573
|
+
const runResult = await thread.runStreamed(prompt, {
|
|
2574
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
2575
|
+
});
|
|
2576
|
+
const iter = runResult.events[Symbol.asyncIterator]();
|
|
2577
|
+
let first;
|
|
2578
|
+
let startupTimer;
|
|
2579
|
+
try {
|
|
2580
|
+
const startupTimeout = new Promise((_, reject) => {
|
|
2581
|
+
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);
|
|
2582
|
+
});
|
|
2583
|
+
const nextP = iter.next();
|
|
2584
|
+
nextP.catch(() => {});
|
|
2585
|
+
first = await Promise.race([nextP, startupTimeout]);
|
|
2586
|
+
} catch (err) {
|
|
2587
|
+
const novel2 = findNewCodexChildren(baseline);
|
|
2588
|
+
if (novel2.length > 0) {
|
|
2589
|
+
killCodexTrees(novel2);
|
|
2590
|
+
}
|
|
2591
|
+
await closeIterator(iter);
|
|
2592
|
+
throw err;
|
|
2593
|
+
} finally {
|
|
2594
|
+
if (startupTimer)
|
|
2595
|
+
clearTimeout(startupTimer);
|
|
2596
|
+
}
|
|
2597
|
+
const novel = findNewCodexChildren(baseline);
|
|
2598
|
+
trackedPids.pids = novel;
|
|
2599
|
+
registerOwnedCodexPids(novel);
|
|
2600
|
+
return { iter, firstEvent: first.value, done: !!first.done };
|
|
2601
|
+
} finally {
|
|
2602
|
+
release();
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
function prependFirstEvent(iter, firstEvent, alreadyDone) {
|
|
2606
|
+
return {
|
|
2607
|
+
[Symbol.asyncIterator]() {
|
|
2608
|
+
let emittedFirst = false;
|
|
2609
|
+
let exhausted = alreadyDone;
|
|
2610
|
+
return {
|
|
2611
|
+
async next() {
|
|
2612
|
+
if (!emittedFirst) {
|
|
2613
|
+
emittedFirst = true;
|
|
2614
|
+
if (alreadyDone)
|
|
2615
|
+
return { value: undefined, done: true };
|
|
2616
|
+
return { value: firstEvent, done: false };
|
|
2617
|
+
}
|
|
2618
|
+
if (exhausted)
|
|
2619
|
+
return { value: undefined, done: true };
|
|
2620
|
+
const r = await iter.next();
|
|
2621
|
+
if (r.done)
|
|
2622
|
+
exhausted = true;
|
|
2623
|
+
return r;
|
|
2624
|
+
},
|
|
2625
|
+
async return(value) {
|
|
2626
|
+
exhausted = true;
|
|
2627
|
+
if (iter.return)
|
|
2628
|
+
return iter.return(value);
|
|
2629
|
+
return { value, done: true };
|
|
2630
|
+
}
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
async function* codexProvider(opts) {
|
|
2636
|
+
const codexAuthPath = hostedCodexAuthFilePath();
|
|
2637
|
+
if (!existsSync7(codexAuthPath)) {
|
|
2638
|
+
yield {
|
|
2639
|
+
type: "error",
|
|
2640
|
+
content: `Codex auth file not found at ${codexAuthPath}. Run \`codex login\` to authenticate.`
|
|
2641
|
+
};
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
let codexModelCatalogPath;
|
|
2645
|
+
try {
|
|
2646
|
+
await ensureCodexModelCache(codexAuthPath);
|
|
2647
|
+
codexModelCatalogPath = writeCodexCatalogWithNativeMultiAgentDisabled(codexAuthPath);
|
|
2648
|
+
if (opts.sessionId)
|
|
2649
|
+
migrateCodexRolloutNativeMultiAgentMetadata(opts.sessionId);
|
|
2650
|
+
} catch (err) {
|
|
2651
|
+
yield {
|
|
2652
|
+
type: "error",
|
|
2653
|
+
content: `Failed to enforce the Codex native multi-agent policy: ${errMsg(err)}`
|
|
2654
|
+
};
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
const codexMcpServers = toCodexMcpServers(hostedMcpServers(opts));
|
|
2658
|
+
const browserOwner = browserOwnerForContext(opts);
|
|
2659
|
+
const scopedBrowserCapability = opts.playwrightCapability && browserOwner ? browserOwnerCapability(opts.playwrightCapability, browserOwner) : undefined;
|
|
2660
|
+
logger.info({
|
|
2661
|
+
model: opts.model ?? "(sdk default)",
|
|
2662
|
+
effort: opts.effort ?? "(off)",
|
|
2663
|
+
cwd: opts.cwd,
|
|
2664
|
+
cwdExists: existsSync7(opts.cwd),
|
|
2665
|
+
resume: Boolean(opts.sessionId),
|
|
2666
|
+
mcpServerCount: Object.keys(codexMcpServers).length
|
|
2667
|
+
}, "codexProvider: starting turn");
|
|
2668
|
+
const codex = new Codex({
|
|
2669
|
+
...scopedBrowserCapability ? {
|
|
2670
|
+
env: {
|
|
2671
|
+
...Object.fromEntries(Object.entries(process.env).filter((entry) => typeof entry[1] === "string")),
|
|
2672
|
+
[CODEX_BROWSER_CAPABILITY_ENV]: scopedBrowserCapability
|
|
2673
|
+
}
|
|
2674
|
+
} : {},
|
|
2675
|
+
config: {
|
|
2676
|
+
features: { multi_agent: false, multi_agent_v2: false, enable_fanout: false },
|
|
2677
|
+
model_catalog_json: codexModelCatalogPath,
|
|
2678
|
+
mcp_servers: codexMcpServers
|
|
2679
|
+
}
|
|
2680
|
+
});
|
|
2681
|
+
const threadOptions = {
|
|
2682
|
+
workingDirectory: opts.cwd,
|
|
2683
|
+
skipGitRepoCheck: true,
|
|
2684
|
+
sandboxMode: "danger-full-access",
|
|
2685
|
+
approvalPolicy: "never",
|
|
2686
|
+
...opts.model ? { model: opts.model } : {},
|
|
2687
|
+
...opts.effort ? { modelReasoningEffort: mapEffort(opts.effort) } : {}
|
|
2688
|
+
};
|
|
2689
|
+
let currentSessionId = opts.sessionId;
|
|
2690
|
+
let thread = currentSessionId ? codex.resumeThread(currentSessionId, threadOptions) : codex.startThread(threadOptions);
|
|
2691
|
+
let prompt = promptForThread(opts, true);
|
|
2692
|
+
let agentTextSoFar = "";
|
|
2693
|
+
let currentAgentMessageId;
|
|
2694
|
+
let finalText = "";
|
|
2695
|
+
let stopReason = "end_turn";
|
|
2696
|
+
const trackedPids = { pids: [] };
|
|
2697
|
+
const abortSignal = opts.abortController?.signal;
|
|
2698
|
+
const onAbortKill = () => {
|
|
2699
|
+
if (trackedPids.pids.length > 0) {
|
|
2700
|
+
killCodexTrees(trackedPids.pids);
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
abortSignal?.addEventListener("abort", onAbortKill, { once: true });
|
|
2704
|
+
let heartbeatMs = 8 * 60 * 1000;
|
|
2705
|
+
let heartbeatAttempts = 0;
|
|
2706
|
+
const HEARTBEAT_MAX_MS = 16 * 60 * 1000;
|
|
2707
|
+
const HEARTBEAT_MAX_ATTEMPTS = 2;
|
|
2708
|
+
const HEARTBEAT_CONTINUATION = "\uACC4\uC18D \uC9C4\uD589\uD574\uC918.";
|
|
2709
|
+
try {
|
|
2710
|
+
while (true) {
|
|
2711
|
+
if (abortSignal?.aborted)
|
|
2712
|
+
return;
|
|
2713
|
+
const attemptAbort = new AbortController;
|
|
2714
|
+
const propagateOuter = () => attemptAbort.abort();
|
|
2715
|
+
abortSignal?.addEventListener("abort", propagateOuter, { once: true });
|
|
2716
|
+
trackedPids.pids = [];
|
|
2717
|
+
let heartbeatTriggered = false;
|
|
2718
|
+
let attemptCompleted = false;
|
|
2719
|
+
let heartbeatTimer = null;
|
|
2720
|
+
const scheduleHeartbeat = () => {
|
|
2721
|
+
if (heartbeatTimer !== null)
|
|
2722
|
+
clearTimeout(heartbeatTimer);
|
|
2723
|
+
if (abortSignal?.aborted || attemptAbort.signal.aborted)
|
|
2724
|
+
return;
|
|
2725
|
+
heartbeatTimer = setTimeout(() => {
|
|
2726
|
+
heartbeatTriggered = true;
|
|
2727
|
+
if (trackedPids.pids.length > 0)
|
|
2728
|
+
killCodexTrees(trackedPids.pids);
|
|
2729
|
+
attemptAbort.abort();
|
|
2730
|
+
}, heartbeatMs);
|
|
2731
|
+
};
|
|
2732
|
+
try {
|
|
2733
|
+
scheduleHeartbeat();
|
|
2734
|
+
let startResult;
|
|
2735
|
+
try {
|
|
2736
|
+
startResult = await startStreamedWithTracking(thread, prompt, attemptAbort.signal, trackedPids);
|
|
2737
|
+
} catch (err) {
|
|
2738
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
2739
|
+
throw err;
|
|
2740
|
+
if (!currentSessionId || !isMissingCodexRolloutError(err))
|
|
2741
|
+
throw err;
|
|
2742
|
+
logger.warn({ staleSessionId: currentSessionId, err: errMsg(err) }, "codexProvider: stale/missing rollout, restarting fresh thread");
|
|
2743
|
+
thread = codex.startThread(threadOptions);
|
|
2744
|
+
prompt = promptForThread(opts, true);
|
|
2745
|
+
currentSessionId = undefined;
|
|
2746
|
+
startResult = await startStreamedWithTracking(thread, prompt, attemptAbort.signal, trackedPids);
|
|
2747
|
+
}
|
|
2748
|
+
if (attemptAbort.signal.aborted) {
|
|
2749
|
+
killCodexTrees(trackedPids.pids);
|
|
2750
|
+
await closeIterator(startResult.iter);
|
|
2751
|
+
throw Object.assign(new Error("heartbeat abort"), { name: "AbortError" });
|
|
2752
|
+
}
|
|
2753
|
+
const eventStream = prependFirstEvent(startResult.iter, startResult.firstEvent, startResult.done);
|
|
2754
|
+
for await (const rawEvent of eventStream) {
|
|
2755
|
+
scheduleHeartbeat();
|
|
2756
|
+
const event = rawEvent;
|
|
2757
|
+
switch (event.type) {
|
|
2758
|
+
case "thread.started": {
|
|
2759
|
+
if (event.thread_id) {
|
|
2760
|
+
currentSessionId = event.thread_id;
|
|
2761
|
+
yield { type: "session", sessionId: event.thread_id };
|
|
2762
|
+
}
|
|
2763
|
+
break;
|
|
2764
|
+
}
|
|
2765
|
+
case "item.started": {
|
|
2766
|
+
const item = event.item;
|
|
2767
|
+
if (!item)
|
|
2768
|
+
break;
|
|
2769
|
+
if (item.type === "command_execution") {
|
|
2770
|
+
yield {
|
|
2771
|
+
type: "tool_use",
|
|
2772
|
+
name: "Bash",
|
|
2773
|
+
input: { command: String(item.command ?? "") },
|
|
2774
|
+
toolUseId: String(item.id ?? "")
|
|
2775
|
+
};
|
|
2776
|
+
} else if (item.type === "mcp_tool_call") {
|
|
2777
|
+
yield {
|
|
2778
|
+
type: "tool_use",
|
|
2779
|
+
name: String(item.tool ?? "unknown"),
|
|
2780
|
+
input: item.arguments && typeof item.arguments === "object" ? item.arguments : {},
|
|
2781
|
+
toolUseId: String(item.id ?? "")
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
break;
|
|
2785
|
+
}
|
|
2786
|
+
case "item.updated": {
|
|
2787
|
+
const item = event.item;
|
|
2788
|
+
if (!item)
|
|
2789
|
+
break;
|
|
2790
|
+
if (item.type === "agent_message") {
|
|
2791
|
+
const messageId = item.id != null ? String(item.id) : undefined;
|
|
2792
|
+
if (messageId !== currentAgentMessageId) {
|
|
2793
|
+
currentAgentMessageId = messageId;
|
|
2794
|
+
agentTextSoFar = "";
|
|
2795
|
+
}
|
|
2796
|
+
const text = String(item.text ?? "");
|
|
2797
|
+
const newChars = text.slice(agentTextSoFar.length);
|
|
2798
|
+
if (newChars) {
|
|
2799
|
+
yield { type: "text_delta", content: newChars };
|
|
2800
|
+
agentTextSoFar = text;
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
break;
|
|
2804
|
+
}
|
|
2805
|
+
case "item.completed": {
|
|
2806
|
+
const item = event.item;
|
|
2807
|
+
if (!item)
|
|
2808
|
+
break;
|
|
2809
|
+
if (item.type === "agent_message") {
|
|
2810
|
+
finalText = String(item.text ?? "");
|
|
2811
|
+
agentTextSoFar = finalText;
|
|
2812
|
+
yield { type: "text", content: finalText };
|
|
2813
|
+
yield* extractFileEvents(finalText, "text");
|
|
2814
|
+
} else if (item.type === "reasoning") {
|
|
2815
|
+
const reasoning = String(item.text ?? "").trim();
|
|
2816
|
+
if (reasoning)
|
|
2817
|
+
yield { type: "reasoning", content: reasoning };
|
|
2818
|
+
} else if (item.type === "mcp_tool_call") {
|
|
2819
|
+
yield {
|
|
2820
|
+
type: "tool_result",
|
|
2821
|
+
toolUseId: String(item.id ?? ""),
|
|
2822
|
+
content: summarizeMcpToolCallResult(item)
|
|
2823
|
+
};
|
|
2824
|
+
} else if (item.type === "command_execution") {
|
|
2825
|
+
yield {
|
|
2826
|
+
type: "tool_result",
|
|
2827
|
+
toolUseId: String(item.id ?? ""),
|
|
2828
|
+
content: String(item.aggregated_output ?? "").slice(0, 200)
|
|
2829
|
+
};
|
|
2830
|
+
} else if (item.type === "file_change") {
|
|
2831
|
+
for (const fileEvent of fileChangeEvents(item)) {
|
|
2832
|
+
yield fileEvent;
|
|
2833
|
+
}
|
|
2834
|
+
} else if (item.type === "error") {
|
|
2835
|
+
yield { type: "error", content: String(item.message ?? "") };
|
|
2836
|
+
}
|
|
2837
|
+
break;
|
|
2838
|
+
}
|
|
2839
|
+
case "turn.completed": {
|
|
2840
|
+
const usage = event.usage;
|
|
2841
|
+
if (!usage)
|
|
2842
|
+
break;
|
|
2843
|
+
attemptCompleted = true;
|
|
2844
|
+
const contextUsage = currentSessionId ? readLatestCodexContextUsage(currentSessionId) : undefined;
|
|
2845
|
+
yield {
|
|
2846
|
+
type: "result",
|
|
2847
|
+
content: finalText,
|
|
2848
|
+
stopReason,
|
|
2849
|
+
usage: {
|
|
2850
|
+
inputTokens: usage.input_tokens,
|
|
2851
|
+
outputTokens: usage.output_tokens,
|
|
2852
|
+
cacheReadInputTokens: usage.cached_input_tokens,
|
|
2853
|
+
...contextUsage
|
|
2854
|
+
}
|
|
2855
|
+
};
|
|
2856
|
+
if (finalText)
|
|
2857
|
+
yield* extractFileEvents(finalText, "result");
|
|
2858
|
+
break;
|
|
2859
|
+
}
|
|
2860
|
+
case "turn.failed": {
|
|
2861
|
+
stopReason = "error";
|
|
2862
|
+
yield {
|
|
2863
|
+
type: "error",
|
|
2864
|
+
content: event.error?.message || "Codex turn failed"
|
|
2865
|
+
};
|
|
2866
|
+
break;
|
|
2867
|
+
}
|
|
2868
|
+
default:
|
|
2869
|
+
break;
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
} catch (err) {
|
|
2873
|
+
if (abortSignal?.aborted)
|
|
2874
|
+
return;
|
|
2875
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
2876
|
+
if (!heartbeatTriggered)
|
|
2877
|
+
return;
|
|
2878
|
+
} else {
|
|
2879
|
+
logger.error({ err }, "codexProvider: attempt failed");
|
|
2880
|
+
yield { type: "error", content: errMsg(err) };
|
|
2881
|
+
break;
|
|
2882
|
+
}
|
|
2883
|
+
} finally {
|
|
2884
|
+
if (heartbeatTimer !== null)
|
|
2885
|
+
clearTimeout(heartbeatTimer);
|
|
2886
|
+
abortSignal?.removeEventListener("abort", propagateOuter);
|
|
2887
|
+
if (trackedPids.pids.length > 0)
|
|
2888
|
+
unregisterOwnedCodexPids(trackedPids.pids);
|
|
2889
|
+
}
|
|
2890
|
+
if (attemptCompleted || abortSignal?.aborted)
|
|
2891
|
+
break;
|
|
2892
|
+
if (!heartbeatTriggered)
|
|
2893
|
+
break;
|
|
2894
|
+
heartbeatAttempts++;
|
|
2895
|
+
if (heartbeatAttempts > HEARTBEAT_MAX_ATTEMPTS) {
|
|
2896
|
+
logger.warn({ attempts: heartbeatAttempts }, "codexProvider: max heartbeat attempts reached");
|
|
2897
|
+
yield {
|
|
2898
|
+
type: "error",
|
|
2899
|
+
content: `Codex\uAC00 ${HEARTBEAT_MAX_ATTEMPTS}\uD68C \uC7AC\uC2DC\uB3C4 \uD6C4\uC5D0\uB3C4 \uC751\uB2F5\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`
|
|
2900
|
+
};
|
|
2901
|
+
break;
|
|
2902
|
+
}
|
|
2903
|
+
heartbeatMs = Math.min(heartbeatMs * 2, HEARTBEAT_MAX_MS);
|
|
2904
|
+
logger.info({ attempt: heartbeatAttempts, nextIntervalMs: heartbeatMs }, "codexProvider: heartbeat fired, resuming thread with continuation prompt");
|
|
2905
|
+
agentTextSoFar = "";
|
|
2906
|
+
finalText = "";
|
|
2907
|
+
thread = currentSessionId ? codex.resumeThread(currentSessionId, threadOptions) : codex.startThread(threadOptions);
|
|
2908
|
+
prompt = currentSessionId ? HEARTBEAT_CONTINUATION : promptForThread(opts, true);
|
|
2909
|
+
}
|
|
2910
|
+
} catch (err) {
|
|
2911
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
2912
|
+
return;
|
|
2913
|
+
if (abortSignal?.aborted)
|
|
2914
|
+
return;
|
|
2915
|
+
logger.error({ err }, "codexProvider: setup failed");
|
|
2916
|
+
yield { type: "error", content: errMsg(err) };
|
|
2917
|
+
} finally {
|
|
2918
|
+
abortSignal?.removeEventListener("abort", onAbortKill);
|
|
2919
|
+
if (trackedPids.pids.length > 0)
|
|
2920
|
+
unregisterOwnedCodexPids(trackedPids.pids);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
// ../../packages/core/src/platform/maestro-bootstrap-env.ts
|
|
2925
|
+
process.env.MAESTRO_SDK_SILENT_BOOTSTRAP ??= "1";
|
|
2926
|
+
|
|
2927
|
+
// ../../packages/core/src/agents/maestro-provider.ts
|
|
2928
|
+
import { maestroProvider as sdkMaestroProvider, setMcpResolver } from "maestro-agent-sdk";
|
|
2929
|
+
var MAESTRO_DEFAULT_MAX_TOKENS = 32768;
|
|
2930
|
+
var PROVIDER_ASK_USER_TOOL = "AskUserQuestion";
|
|
2931
|
+
var PROVIDER_SUBAGENT_TOOL = "Agent";
|
|
2932
|
+
var MAESTRO_NATIVE_TASK_TOOLS = [
|
|
2933
|
+
"TaskCreate",
|
|
2934
|
+
"TaskUpdate",
|
|
2935
|
+
"TaskList",
|
|
2936
|
+
"TaskGet",
|
|
2937
|
+
"TaskOutput",
|
|
2938
|
+
"TaskStop"
|
|
2939
|
+
];
|
|
2940
|
+
var MAESTRO_PROVIDER_OWNED_TOOL_SET = new Set([
|
|
2941
|
+
PROVIDER_ASK_USER_TOOL,
|
|
2942
|
+
PROVIDER_SUBAGENT_TOOL,
|
|
2943
|
+
...MAESTRO_NATIVE_TASK_TOOLS
|
|
2944
|
+
]);
|
|
2945
|
+
var DEFAULT_MAESTRO_DISALLOWED_TOOLS = [
|
|
2946
|
+
PROVIDER_ASK_USER_TOOL,
|
|
2947
|
+
PROVIDER_SUBAGENT_TOOL,
|
|
2948
|
+
...MAESTRO_NATIVE_TASK_TOOLS
|
|
2949
|
+
];
|
|
2950
|
+
function buildMaestroDisallowedTools(callerDisallowedTools = []) {
|
|
2951
|
+
return [...new Set([...DEFAULT_MAESTRO_DISALLOWED_TOOLS, ...callerDisallowedTools])];
|
|
2952
|
+
}
|
|
2953
|
+
function buildVaultHook(userId) {
|
|
2954
|
+
return {
|
|
2955
|
+
name: "vault-guard",
|
|
2956
|
+
pre({ toolName, input }) {
|
|
2957
|
+
if (referencesHostedSecretStorage(input)) {
|
|
2958
|
+
return { decision: "block", error: "Runtime secret storage access is not permitted" };
|
|
2959
|
+
}
|
|
2960
|
+
if (shouldRedirectHostedVaultTool(userId, toolName, input)) {
|
|
2961
|
+
return { decision: "block", error: VAULT_BROKER_REDIRECT_ERROR };
|
|
2962
|
+
}
|
|
2963
|
+
return { decision: "allow" };
|
|
2964
|
+
},
|
|
2965
|
+
post({ output }) {
|
|
2966
|
+
const redacted = redactHostedSecrets(userId, output);
|
|
2967
|
+
return redacted === output ? {} : { output: redacted };
|
|
2968
|
+
}
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
function buildMaestroToolHooks(userId) {
|
|
2972
|
+
return [buildVaultHook(userId), buildProviderOwnedToolBlockHook()];
|
|
2973
|
+
}
|
|
2974
|
+
function providerOwnedToolRedirect(toolName) {
|
|
2975
|
+
if (toolName === PROVIDER_ASK_USER_TOOL) {
|
|
2976
|
+
return "Use the runtime ask_user_question MCP tool instead.";
|
|
2977
|
+
}
|
|
2978
|
+
if (toolName === PROVIDER_SUBAGENT_TOOL) {
|
|
2979
|
+
return "Use the runtime spawn_subagent MCP tool instead.";
|
|
2980
|
+
}
|
|
2981
|
+
return "Use the shared task MCP tools instead " + "(mcp__task__task_create / task_update / task_list / task_get / task_delete).";
|
|
2982
|
+
}
|
|
2983
|
+
function buildProviderOwnedToolBlockHook() {
|
|
2984
|
+
return {
|
|
2985
|
+
name: "provider-owned-tool-redirect",
|
|
2986
|
+
pre({ toolName }) {
|
|
2987
|
+
if (!MAESTRO_PROVIDER_OWNED_TOOL_SET.has(toolName))
|
|
2988
|
+
return { decision: "allow" };
|
|
2989
|
+
return {
|
|
2990
|
+
decision: "block",
|
|
2991
|
+
error: `${toolName} is disabled in this environment. ${providerOwnedToolRedirect(toolName)}`
|
|
2992
|
+
};
|
|
2993
|
+
}
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
function maestroProvider(opts) {
|
|
2997
|
+
if (opts.agent !== undefined && opts.agent !== "maestro") {
|
|
2998
|
+
throw new Error(`maestroProvider: unexpected agent "${opts.agent}", expected "maestro"`);
|
|
2999
|
+
}
|
|
3000
|
+
const userId = opts.userId ?? "";
|
|
3001
|
+
setMcpResolver(hostedMcpServers);
|
|
3002
|
+
const callerDisallowedTools = opts.disallowedTools ?? [];
|
|
3003
|
+
const callerToolHooks = opts.toolHooks ?? [];
|
|
3004
|
+
const sdkOpts = {
|
|
3005
|
+
enableBackgroundBash: false,
|
|
3006
|
+
maxTokens: MAESTRO_DEFAULT_MAX_TOKENS,
|
|
3007
|
+
enableToolSearch: true,
|
|
3008
|
+
...opts,
|
|
3009
|
+
agent: "maestro",
|
|
3010
|
+
disallowedTools: buildMaestroDisallowedTools(callerDisallowedTools),
|
|
3011
|
+
toolHooks: [...buildMaestroToolHooks(userId), ...callerToolHooks]
|
|
3012
|
+
};
|
|
3013
|
+
return sdkMaestroProvider(sdkOpts);
|
|
3014
|
+
}
|
|
20
3015
|
|
|
21
3016
|
// ../../packages/core/src/agents/hosted-agent.ts
|
|
22
3017
|
async function* runHostedAgent(input, host) {
|
|
@@ -61,4 +3056,4 @@ export {
|
|
|
61
3056
|
configureAgentExecutionHost
|
|
62
3057
|
};
|
|
63
3058
|
|
|
64
|
-
//# debugId=
|
|
3059
|
+
//# debugId=F7C9A771B460541764756E2164756E21
|