negotium 0.2.3 → 0.2.5
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/agent-helpers.js +3366 -3048
- package/dist/agent-helpers.js.map +49 -48
- package/dist/background-bash.js +1 -3
- package/dist/background-bash.js.map +4 -4
- package/dist/browser-runtime.js +137 -23
- package/dist/browser-runtime.js.map +11 -10
- package/dist/chunk-qmbcbhyy.js +2407 -0
- package/dist/chunk-qmbcbhyy.js.map +35 -0
- package/dist/hosted-agent.js +56 -39
- package/dist/hosted-agent.js.map +13 -13
- package/dist/main.js +3276 -2808
- package/dist/main.js.map +54 -51
- package/dist/mcp-factories.js +6471 -6148
- package/dist/mcp-factories.js.map +49 -48
- package/dist/prompts.js +1 -3
- package/dist/prompts.js.map +4 -4
- package/dist/query-runtime.js +22 -4
- package/dist/query-runtime.js.map +6 -6
- package/dist/registry.js +169 -213
- package/dist/registry.js.map +8 -9
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/agents/api-topic-agent-switch.ts +6 -12
- package/dist/runtime/src/agents/claude-provider.ts +2 -2
- package/dist/runtime/src/agents/codex-app-server.ts +136 -0
- package/dist/runtime/src/agents/codex-native-multi-agent.ts +1 -1
- package/dist/runtime/src/agents/codex-provider.ts +22 -11
- package/dist/runtime/src/agents/codex-registry.ts +9 -17
- package/dist/runtime/src/agents/contracts.ts +2 -0
- package/dist/runtime/src/agents/execution-host.ts +6 -0
- package/dist/runtime/src/agents/fork.ts +24 -18
- package/dist/runtime/src/agents/idle-archiver.ts +7 -2
- package/dist/runtime/src/agents/index.ts +21 -15
- package/dist/runtime/src/agents/maestro-provider.ts +1 -1
- package/dist/runtime/src/agents/maestro-registry.ts +26 -7
- package/dist/runtime/src/agents/memory-archive-policy.ts +20 -0
- package/dist/runtime/src/agents/rollout/codex.ts +31 -23
- package/dist/runtime/src/agents/self-config-core.ts +2 -4
- package/dist/runtime/src/agents/topic-agent-switch.ts +4 -10
- package/dist/runtime/src/application/submit-runtime-gateway-turn.ts +77 -19
- package/dist/runtime/src/application/switch-topic-model.ts +7 -10
- package/dist/runtime/src/index.ts +4 -0
- package/dist/runtime/src/mcp/factories/session-comm.ts +12 -0
- package/dist/runtime/src/mcp/session-comm/default-host.ts +16 -0
- package/dist/runtime/src/mcp/session-comm/server.ts +25 -0
- package/dist/runtime/src/mcp/wiki-server.ts +19 -12
- package/dist/runtime/src/platform/config.ts +4 -3
- package/dist/runtime/src/platform/log-rotation.ts +45 -0
- package/dist/runtime/src/platform/mcp-config.ts +9 -3
- package/dist/runtime/src/platform/playwright/manager.ts +57 -1
- package/dist/runtime/src/platform/playwright/profile-management.ts +33 -0
- package/dist/runtime/src/platform/playwright/public-runtime.ts +5 -0
- package/dist/runtime/src/platform/playwright/vault-broker.ts +9 -10
- package/dist/runtime/src/query/active-rooms.ts +7 -1
- package/dist/runtime/src/runtime/errors.ts +1 -0
- package/dist/runtime/src/runtime/turn-event-stream.ts +46 -1
- package/dist/runtime/src/runtime/turn-runner.ts +176 -100
- package/dist/runtime/src/runtime/user-turn-envelope.ts +19 -0
- package/dist/runtime/src/storage/browser-profiles.ts +35 -10
- package/dist/runtime/src/storage/runtime-gateway-submissions.ts +12 -3
- package/dist/runtime/src/storage/runtime-turn-requests.ts +68 -7
- package/dist/runtime/src/storage/storage-host.ts +31 -5
- package/dist/runtime/src/storage/vault.ts +30 -2
- package/dist/runtime/src/topics/derive.ts +38 -2
- package/dist/runtime/src/topics/lifecycle.ts +12 -2
- package/dist/runtime/src/types.ts +2 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +22 -4
- package/dist/runtime-helpers.js.map +5 -5
- package/dist/storage.js +22 -2
- package/dist/storage.js.map +5 -5
- package/dist/types/packages/core/src/agents/api-topic-agent-switch.d.ts +1 -1
- package/dist/types/packages/core/src/agents/codex-app-server.d.ts +13 -0
- package/dist/types/packages/core/src/agents/codex-native-multi-agent.d.ts +1 -0
- package/dist/types/packages/core/src/agents/contracts.d.ts +2 -0
- package/dist/types/packages/core/src/agents/execution-host.d.ts +2 -0
- package/dist/types/packages/core/src/agents/fork.d.ts +8 -7
- package/dist/types/packages/core/src/agents/memory-archive-policy.d.ts +10 -0
- package/dist/types/packages/core/src/agents/rollout/codex.d.ts +4 -2
- package/dist/types/packages/core/src/agents/self-config-core.d.ts +1 -1
- package/dist/types/packages/core/src/mcp/factories/session-comm.d.ts +1 -0
- package/dist/types/packages/core/src/platform/config.d.ts +0 -1
- package/dist/types/packages/core/src/platform/mcp-config.d.ts +3 -0
- package/dist/types/packages/core/src/platform/playwright/manager.d.ts +15 -0
- package/dist/types/packages/core/src/platform/playwright/profile-management.d.ts +8 -0
- package/dist/types/packages/core/src/platform/playwright/public-runtime.d.ts +2 -1
- package/dist/types/packages/core/src/query/active-rooms.d.ts +7 -1
- package/dist/types/packages/core/src/runtime/turn-runner.d.ts +42 -1
- package/dist/types/packages/core/src/runtime/user-turn-envelope.d.ts +7 -0
- package/dist/types/packages/core/src/storage/browser-profiles.d.ts +3 -1
- package/dist/types/packages/core/src/storage/runtime-turn-requests.d.ts +17 -0
- package/dist/types/packages/core/src/storage/storage-host.d.ts +1 -0
- package/dist/types/packages/core/src/storage/vault.d.ts +8 -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 +19 -5
- package/dist/vault.js.map +5 -5
- package/package.json +2 -2
- package/dist/chunk-hsqgb2hw.js +0 -1223
- package/dist/chunk-hsqgb2hw.js.map +0 -19
|
@@ -0,0 +1,2407 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
4
|
+
// ../../packages/core/src/agents/rollout/shared.ts
|
|
5
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
6
|
+
import { resolve as resolve2 } from "path";
|
|
7
|
+
|
|
8
|
+
// ../../packages/core/src/platform/config.ts
|
|
9
|
+
import { execFileSync } from "child_process";
|
|
10
|
+
import { randomBytes } from "crypto";
|
|
11
|
+
import {
|
|
12
|
+
accessSync,
|
|
13
|
+
chmodSync,
|
|
14
|
+
constants,
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
writeFileSync
|
|
19
|
+
} from "fs";
|
|
20
|
+
import { createRequire } from "module";
|
|
21
|
+
import { homedir } from "os";
|
|
22
|
+
import { dirname, join, resolve } from "path";
|
|
23
|
+
import { fileURLToPath } from "url";
|
|
24
|
+
|
|
25
|
+
// ../../packages/core/src/platform/config-helpers.ts
|
|
26
|
+
function readEnvText(env, key) {
|
|
27
|
+
const value = env[key]?.trim();
|
|
28
|
+
return value || undefined;
|
|
29
|
+
}
|
|
30
|
+
function parseRuntimePort(value, fallback) {
|
|
31
|
+
if (!value)
|
|
32
|
+
return fallback;
|
|
33
|
+
const port = Number.parseInt(value, 10);
|
|
34
|
+
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : fallback;
|
|
35
|
+
}
|
|
36
|
+
function safeRuntimePathSegment(value, fallback, maxLength = 160) {
|
|
37
|
+
const cleaned = value.trim().replace(/[^A-Za-z0-9._-]/g, "_").replace(/^_+|_+$/g, "").slice(0, maxLength);
|
|
38
|
+
return cleaned || fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ../../packages/core/src/platform/logger.ts
|
|
42
|
+
import pino from "pino";
|
|
43
|
+
function createStdioLogger(options = {}) {
|
|
44
|
+
const development = options.development ?? true;
|
|
45
|
+
return pino({
|
|
46
|
+
level: options.level ?? process.env.LOG_LEVEL ?? "info",
|
|
47
|
+
transport: development ? {
|
|
48
|
+
target: "pino-pretty",
|
|
49
|
+
options: {
|
|
50
|
+
colorize: true,
|
|
51
|
+
translateTime: "SYS:yyyy-mm-dd HH:MM:ss",
|
|
52
|
+
destination: 2
|
|
53
|
+
}
|
|
54
|
+
} : undefined
|
|
55
|
+
}, pino.destination(2));
|
|
56
|
+
}
|
|
57
|
+
var logger = createStdioLogger();
|
|
58
|
+
|
|
59
|
+
// ../../packages/core/src/types.ts
|
|
60
|
+
var SUPPORTED_AGENTS = ["maestro", "claude", "codex"];
|
|
61
|
+
function isAgentKind(value) {
|
|
62
|
+
return typeof value === "string" && SUPPORTED_AGENTS.includes(value);
|
|
63
|
+
}
|
|
64
|
+
var CLAUDE_EFFORT_VALUES = ["low", "medium", "high", "xhigh", "max"];
|
|
65
|
+
var CODEX_EFFORT_VALUES = ["low", "medium", "high", "xhigh", "max"];
|
|
66
|
+
var MAESTRO_EFFORT_VALUES = ["low", "medium", "high", "xhigh", "max"];
|
|
67
|
+
|
|
68
|
+
// ../../packages/core/src/platform/config.ts
|
|
69
|
+
function envText(envKey) {
|
|
70
|
+
return readEnvText(process.env, envKey);
|
|
71
|
+
}
|
|
72
|
+
function resolveAgentEnv(envKey, fallback, legacyEnvKey) {
|
|
73
|
+
const value = envText(envKey) ?? (legacyEnvKey ? envText(legacyEnvKey) : undefined);
|
|
74
|
+
return isAgentKind(value) ? value : fallback;
|
|
75
|
+
}
|
|
76
|
+
var HOME = homedir();
|
|
77
|
+
function resolveProjectRoot() {
|
|
78
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
79
|
+
const packagedRuntime = resolve(moduleDir, "runtime");
|
|
80
|
+
if (existsSync(resolve(packagedRuntime, "src")))
|
|
81
|
+
return packagedRuntime;
|
|
82
|
+
return resolve(moduleDir, "../..");
|
|
83
|
+
}
|
|
84
|
+
var PROJECT_ROOT = resolveProjectRoot();
|
|
85
|
+
function resolveDependencyBin(name) {
|
|
86
|
+
let dir = PROJECT_ROOT;
|
|
87
|
+
while (true) {
|
|
88
|
+
const candidate = resolve(dir, "node_modules", ".bin", name);
|
|
89
|
+
if (existsSync(candidate))
|
|
90
|
+
return candidate;
|
|
91
|
+
const parent = dirname(dir);
|
|
92
|
+
if (parent === dir)
|
|
93
|
+
return candidate;
|
|
94
|
+
dir = parent;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
var STATE_DIR_ENV = envText("NEGOTIUM_STATE_DIR");
|
|
98
|
+
var STATE_DIR = STATE_DIR_ENV ? resolve(STATE_DIR_ENV) : resolve(HOME, ".negotium");
|
|
99
|
+
function resolveLocalStateDir(envKey, stateName) {
|
|
100
|
+
const envValue = envText(envKey);
|
|
101
|
+
if (envValue)
|
|
102
|
+
return resolve(envValue);
|
|
103
|
+
return resolve(STATE_DIR, stateName);
|
|
104
|
+
}
|
|
105
|
+
function parsePortEnv(envValue, fallback) {
|
|
106
|
+
return parseRuntimePort(envValue, fallback);
|
|
107
|
+
}
|
|
108
|
+
var WORKSPACE_DIR = resolveLocalStateDir("NEGOTIUM_WORKSPACE_DIR", "workspace");
|
|
109
|
+
var TOPIC_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "topics");
|
|
110
|
+
var SHARED_WIKI_DIR = resolve(WORKSPACE_DIR, "wiki");
|
|
111
|
+
var CRON_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "cron");
|
|
112
|
+
var BROWSER_DIR = resolveLocalStateDir("NEGOTIUM_BROWSER_DIR", "browser");
|
|
113
|
+
var BROWSER_PROFILES_DIR = resolve(BROWSER_DIR, "profiles");
|
|
114
|
+
var BINARIES_DIR = resolve(STATE_DIR, "binaries");
|
|
115
|
+
var SECRETS_DIR = resolve(STATE_DIR, "secrets");
|
|
116
|
+
var DM_WORKSPACE_DIR = resolve(STATE_DIR, "data", "dm");
|
|
117
|
+
var SESSION_WORKSPACE_DIR = resolve(STATE_DIR, "data", "sessions");
|
|
118
|
+
var CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
|
|
119
|
+
var CLAUDE_EXECUTABLE = CLAUDE_EXECUTABLE_ENV ? resolve(CLAUDE_EXECUTABLE_ENV) : undefined;
|
|
120
|
+
var BROWSER_RS_VERSION = "v0.1.16";
|
|
121
|
+
var BROWSER_RS_MIN_SECURE_VERSION = "0.1.15";
|
|
122
|
+
function versionAtLeast(actualVersion, minimumVersion) {
|
|
123
|
+
const actual = actualVersion.split(".").map(Number);
|
|
124
|
+
const minimum = minimumVersion.split(".").map(Number);
|
|
125
|
+
if (actual.some(Number.isNaN) || minimum.some(Number.isNaN))
|
|
126
|
+
return false;
|
|
127
|
+
for (let index = 0;index < minimum.length; index += 1) {
|
|
128
|
+
if ((actual[index] ?? 0) > (minimum[index] ?? 0))
|
|
129
|
+
return true;
|
|
130
|
+
if ((actual[index] ?? 0) < (minimum[index] ?? 0))
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
function browserRsMeetsMinimumVersion(candidate) {
|
|
136
|
+
try {
|
|
137
|
+
const output = execFileSync(candidate, ["--version"], {
|
|
138
|
+
encoding: "utf8",
|
|
139
|
+
timeout: 2000,
|
|
140
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
141
|
+
}).trim();
|
|
142
|
+
const match = output.match(/^browser-rs (\d+)\.(\d+)\.(\d+)$/);
|
|
143
|
+
if (!match)
|
|
144
|
+
return false;
|
|
145
|
+
return versionAtLeast(match.slice(1).join("."), BROWSER_RS_MIN_SECURE_VERSION);
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function resolveBrowserRsBin(envValue) {
|
|
151
|
+
const override = envValue?.trim();
|
|
152
|
+
if (!override && !versionAtLeast(BROWSER_RS_VERSION.replace(/^v/, ""), BROWSER_RS_MIN_SECURE_VERSION)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const candidate = override ? resolve(override) : resolve(BINARIES_DIR, "browser-rs", BROWSER_RS_VERSION, "browser-rs");
|
|
156
|
+
try {
|
|
157
|
+
accessSync(candidate, constants.X_OK);
|
|
158
|
+
return browserRsMeetsMinimumVersion(candidate) ? candidate : undefined;
|
|
159
|
+
} catch {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
var BROWSER_RS_BIN = resolveBrowserRsBin(envText("NEGOTIUM_BROWSER_RS_BIN"));
|
|
164
|
+
function resolveBrowserMcpBin(envValue) {
|
|
165
|
+
const override = envValue?.trim();
|
|
166
|
+
if (override)
|
|
167
|
+
return resolve(override);
|
|
168
|
+
return BROWSER_RS_BIN ?? resolve(BINARIES_DIR, "browser-rs", BROWSER_RS_VERSION, "browser-rs");
|
|
169
|
+
}
|
|
170
|
+
var PLAYWRIGHT_MCP_BIN = resolveBrowserMcpBin(envText("NEGOTIUM_BROWSER_MCP_BIN"));
|
|
171
|
+
var TSX_BIN = resolveDependencyBin("tsx");
|
|
172
|
+
var TSX_LOADER = createRequire(import.meta.url).resolve("tsx");
|
|
173
|
+
var TSCONFIG_PATH = resolve(PROJECT_ROOT, "tsconfig.json");
|
|
174
|
+
var SESSION_COMM_SERVER = resolve(PROJECT_ROOT, "src/mcp/session-comm/server.ts");
|
|
175
|
+
var TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
|
|
176
|
+
var BROWSER_MCP_SSE_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/browser-sse-proxy-server.ts");
|
|
177
|
+
var CANONICAL_MCP_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/canonical-proxy-server.ts");
|
|
178
|
+
var WIKI_SERVER = resolve(PROJECT_ROOT, "src/mcp/wiki-server.ts");
|
|
179
|
+
var TOKEN_STATS_SERVER = resolve(PROJECT_ROOT, "src/mcp/token-stats-server.ts");
|
|
180
|
+
var COMPACTION_LOG_SERVER = resolve(PROJECT_ROOT, "src/mcp/compaction-log-server.ts");
|
|
181
|
+
var SYSTEM_HEALTH_SERVER = resolve(PROJECT_ROOT, "src/mcp/system-health-server.ts");
|
|
182
|
+
var AGENT_HEALTH_SERVER = resolve(PROJECT_ROOT, "src/mcp/agent-health-server.ts");
|
|
183
|
+
var BACKGROUND_BASH_SERVER = resolve(PROJECT_ROOT, "src/mcp/background-bash-server.ts");
|
|
184
|
+
var VAULT_SERVER = resolve(PROJECT_ROOT, "src/mcp/vault-server.ts");
|
|
185
|
+
var BG_BASH_BASE_PORT = parsePortEnv(process.env.BG_BASH_BASE_PORT, 9700);
|
|
186
|
+
var BG_BASH_MAX_PORT = parsePortEnv(process.env.BG_BASH_MAX_PORT, 9799);
|
|
187
|
+
function safeWorkspaceSegment(value, fallback) {
|
|
188
|
+
return safeRuntimePathSegment(value, fallback);
|
|
189
|
+
}
|
|
190
|
+
function resolveTopicWorkspaceDir(topicId) {
|
|
191
|
+
return join(TOPIC_WORKSPACE_DIR, safeWorkspaceSegment(topicId, "topic"));
|
|
192
|
+
}
|
|
193
|
+
function loadOrCreateLocalSecret(envKey, filename, options = {}) {
|
|
194
|
+
const envValue = envText(envKey);
|
|
195
|
+
const secretFile = resolve(SECRETS_DIR, filename);
|
|
196
|
+
mkdirSync(dirname(secretFile), { recursive: true });
|
|
197
|
+
if (envValue) {
|
|
198
|
+
if (options.persistEnvValue) {
|
|
199
|
+
writeFileSync(secretFile, `${envValue}
|
|
200
|
+
`, { mode: 384 });
|
|
201
|
+
chmodSync(secretFile, 384);
|
|
202
|
+
}
|
|
203
|
+
return envValue;
|
|
204
|
+
}
|
|
205
|
+
if (existsSync(secretFile)) {
|
|
206
|
+
const stored = readFileSync(secretFile, "utf-8").trim();
|
|
207
|
+
if (stored) {
|
|
208
|
+
chmodSync(secretFile, 384);
|
|
209
|
+
return stored;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const secret = randomBytes(32).toString("base64url");
|
|
213
|
+
try {
|
|
214
|
+
writeFileSync(secretFile, `${secret}
|
|
215
|
+
`, { mode: 384, flag: "wx" });
|
|
216
|
+
return secret;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (error.code !== "EEXIST")
|
|
219
|
+
throw error;
|
|
220
|
+
const stored = readFileSync(secretFile, "utf-8").trim();
|
|
221
|
+
if (!stored)
|
|
222
|
+
throw new Error(`Secret file exists but is empty: ${secretFile}`);
|
|
223
|
+
chmodSync(secretFile, 384);
|
|
224
|
+
return stored;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
var RUNTIME_MCP_SECRET = loadOrCreateLocalSecret("RUNTIME_MCP_SECRET", "runtime-mcp-secret");
|
|
228
|
+
var NODE_CONTROL_TOKEN = loadOrCreateLocalSecret("NEGOTIUM_CONTROL_TOKEN", "node-control-token");
|
|
229
|
+
var VAULT_MASTER_KEY = loadOrCreateLocalSecret("NEGOTIUM_VAULT_MASTER_KEY", "vault-master-key", { persistEnvValue: true });
|
|
230
|
+
delete process.env.NEGOTIUM_VAULT_MASTER_KEY;
|
|
231
|
+
var NEGOTIUM_PORT = parseInt(process.env.NEGOTIUM_PORT || "7777", 10);
|
|
232
|
+
var hostname = process.env.HOSTNAME || "127.0.0.1";
|
|
233
|
+
var DATA_DIR = resolveLocalStateDir("NEGOTIUM_DATA_DIR", "data");
|
|
234
|
+
var LOG_DIR = resolveLocalStateDir("NEGOTIUM_LOG_DIR", "logs");
|
|
235
|
+
var UPLOADS_DIR = resolve(DATA_DIR, "uploads");
|
|
236
|
+
var VAULT_DIR = resolve(DATA_DIR, "vault");
|
|
237
|
+
var SESSIONS_DB = process.env.SESSIONS_DB_PATH ? resolve(process.env.SESSIONS_DB_PATH) : resolve(DATA_DIR, "sessions.db");
|
|
238
|
+
var DEBUG_FILE = resolve(DATA_DIR, "debug-users.json");
|
|
239
|
+
var USERS_LOG_DIR = resolve(DATA_DIR, "users");
|
|
240
|
+
var RUN_DIR = resolveLocalStateDir("NEGOTIUM_RUN_DIR", "runtime");
|
|
241
|
+
var PROGRESS_DIR = resolve(RUN_DIR, "progress");
|
|
242
|
+
var DM_CMD_DIR = resolve(RUN_DIR, "dm-commands");
|
|
243
|
+
var DM_RESP_DIR = resolve(RUN_DIR, "dm-responses");
|
|
244
|
+
var SESSION_INBOX_DIR = resolve(RUN_DIR, "session-inbox");
|
|
245
|
+
var SESSION_ASKS_DIR = resolve(RUN_DIR, "session-asks");
|
|
246
|
+
var PLAYWRIGHT_BASE_PORT = parsePortEnv(process.env.PLAYWRIGHT_BASE_PORT, 9100);
|
|
247
|
+
var PLAYWRIGHT_MAX_PORT = parsePortEnv(process.env.PLAYWRIGHT_MAX_PORT, 9499);
|
|
248
|
+
var PLAYWRIGHT_PORTS_DIR = resolve(RUN_DIR, "playwright-ports");
|
|
249
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
250
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
251
|
+
mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
252
|
+
mkdirSync(VAULT_DIR, { recursive: true, mode: 448 });
|
|
253
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
254
|
+
mkdirSync(PROGRESS_DIR, { recursive: true });
|
|
255
|
+
mkdirSync(DM_CMD_DIR, { recursive: true });
|
|
256
|
+
mkdirSync(DM_RESP_DIR, { recursive: true });
|
|
257
|
+
mkdirSync(SESSION_INBOX_DIR, { recursive: true });
|
|
258
|
+
mkdirSync(SESSION_ASKS_DIR, { recursive: true });
|
|
259
|
+
mkdirSync(PLAYWRIGHT_PORTS_DIR, { recursive: true });
|
|
260
|
+
mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
261
|
+
mkdirSync(TOPIC_WORKSPACE_DIR, { recursive: true });
|
|
262
|
+
mkdirSync(SHARED_WIKI_DIR, { recursive: true });
|
|
263
|
+
mkdirSync(CRON_WORKSPACE_DIR, { recursive: true });
|
|
264
|
+
mkdirSync(DM_WORKSPACE_DIR, { recursive: true });
|
|
265
|
+
mkdirSync(SESSION_WORKSPACE_DIR, { recursive: true });
|
|
266
|
+
mkdirSync(BROWSER_DIR, { recursive: true });
|
|
267
|
+
mkdirSync(BROWSER_PROFILES_DIR, { recursive: true });
|
|
268
|
+
mkdirSync(BINARIES_DIR, { recursive: true });
|
|
269
|
+
mkdirSync(SECRETS_DIR, { recursive: true, mode: 448 });
|
|
270
|
+
var ACTIVE_QUERY_STALE_MS = 10 * 60 * 1000;
|
|
271
|
+
var AGENTS_PROMPTS_DIR = resolve(PROJECT_ROOT, "src/prompts/agents");
|
|
272
|
+
var RESOURCES_DIR = resolve(PROJECT_ROOT, "src/resources");
|
|
273
|
+
var MODEL_SONNET = "claude-sonnet-5";
|
|
274
|
+
var MODEL_OPUS = "claude-opus-5";
|
|
275
|
+
var MODEL_FABLE = "claude-fable-5";
|
|
276
|
+
var FALLBACK_AGENT = resolveAgentEnv("FALLBACK_AGENT", "maestro", "DEFAULT_AGENT");
|
|
277
|
+
var SESSION_AGENT = resolveAgentEnv("SESSION_AGENT", FALLBACK_AGENT);
|
|
278
|
+
var GATEWAY_AGENT = resolveAgentEnv("GATEWAY_AGENT", FALLBACK_AGENT);
|
|
279
|
+
var FALLBACK_MODEL = envText("FALLBACK_MODEL") ?? envText("DEFAULT_MODEL");
|
|
280
|
+
function resolveModelEnv(envKey, agentConst) {
|
|
281
|
+
return envText(envKey) ?? (agentConst === FALLBACK_AGENT ? FALLBACK_MODEL : undefined);
|
|
282
|
+
}
|
|
283
|
+
var SESSION_MODEL = resolveModelEnv("SESSION_MODEL", SESSION_AGENT);
|
|
284
|
+
var GATEWAY_MODEL = resolveModelEnv("GATEWAY_MODEL", GATEWAY_AGENT);
|
|
285
|
+
var FFMPEG_BIN = envText("FFMPEG_BIN");
|
|
286
|
+
var FFPROBE_BIN = envText("FFPROBE_BIN");
|
|
287
|
+
var PYTHON_BIN = envText("PYTHON_BIN") ?? "python3";
|
|
288
|
+
var FASTER_WHISPER_WRAPPER = envText("FASTER_WHISPER_WRAPPER") ?? resolve(PROJECT_ROOT, "scripts/faster-whisper-wrapper.py");
|
|
289
|
+
var WHISPER_MODEL = envText("WHISPER_MODEL_FILE") ?? "turbo";
|
|
290
|
+
var TESSERACT_BIN = envText("TESSERACT_BIN") ?? "tesseract";
|
|
291
|
+
var PDFTOTEXT_BIN = envText("PDFTOTEXT_BIN") ?? "pdftotext";
|
|
292
|
+
var _envMaxTellDepth = Number.parseInt(process.env.MAX_TELL_DEPTH ?? "", 10);
|
|
293
|
+
var MAX_TELL_DEPTH = Number.isInteger(_envMaxTellDepth) && _envMaxTellDepth > 0 ? _envMaxTellDepth : 20;
|
|
294
|
+
function codexAuthFilePath() {
|
|
295
|
+
return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ../../packages/core/src/runtime/user-turn-envelope.ts
|
|
299
|
+
function renderUserPromptBatch(prompts) {
|
|
300
|
+
if (prompts.length <= 1)
|
|
301
|
+
return prompts[0] ?? "";
|
|
302
|
+
return [
|
|
303
|
+
"[Consecutive user messages received before an assistant response]",
|
|
304
|
+
"",
|
|
305
|
+
...prompts.map((prompt, index) => `${index + 1}. ${prompt}`)
|
|
306
|
+
].join(`
|
|
307
|
+
`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ../../packages/core/src/agents/rollout/shared.ts
|
|
311
|
+
var trustedWorkspaceRoots = [
|
|
312
|
+
WORKSPACE_DIR,
|
|
313
|
+
DM_WORKSPACE_DIR,
|
|
314
|
+
SESSION_WORKSPACE_DIR,
|
|
315
|
+
TOPIC_WORKSPACE_DIR
|
|
316
|
+
].map((root) => resolve2(root));
|
|
317
|
+
function configureRolloutHost(options) {
|
|
318
|
+
if (!options.workspaceRoots && !options.fixturesDir) {
|
|
319
|
+
throw new Error("configureRolloutHost requires workspaceRoots or fixturesDir");
|
|
320
|
+
}
|
|
321
|
+
if (options.workspaceRoots?.length === 0) {
|
|
322
|
+
throw new Error("configureRolloutHost requires at least one workspace root");
|
|
323
|
+
}
|
|
324
|
+
const previousRoots = trustedWorkspaceRoots;
|
|
325
|
+
const previousFixturesDir = FIXTURES_DIR;
|
|
326
|
+
if (options.workspaceRoots) {
|
|
327
|
+
trustedWorkspaceRoots = options.workspaceRoots.map((root) => resolve2(root));
|
|
328
|
+
}
|
|
329
|
+
if (options.fixturesDir)
|
|
330
|
+
FIXTURES_DIR = resolve2(options.fixturesDir);
|
|
331
|
+
let disposed = false;
|
|
332
|
+
return () => {
|
|
333
|
+
if (disposed)
|
|
334
|
+
return;
|
|
335
|
+
disposed = true;
|
|
336
|
+
trustedWorkspaceRoots = previousRoots;
|
|
337
|
+
FIXTURES_DIR = previousFixturesDir;
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
var FIXTURES_DIR = resolve2(PROJECT_ROOT, "src", "agents", "fixtures");
|
|
341
|
+
function clone(obj) {
|
|
342
|
+
return structuredClone(obj);
|
|
343
|
+
}
|
|
344
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
345
|
+
function assertUuidLike(label, value) {
|
|
346
|
+
if (!UUID_RE.test(value)) {
|
|
347
|
+
throw new Error(`rollout: ${label} is not a UUID-shaped string: ${value}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function assertCwdInWorkspace(cwd) {
|
|
351
|
+
const abs = resolve2(cwd);
|
|
352
|
+
const ok = trustedWorkspaceRoots.some((root) => abs === root || abs.startsWith(`${root}/`));
|
|
353
|
+
if (!ok) {
|
|
354
|
+
throw new Error(`rollout: cwd outside trusted workspace roots: ${cwd} (resolved=${abs})`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function ensureCwdExists(cwd) {
|
|
358
|
+
assertCwdInWorkspace(cwd);
|
|
359
|
+
try {
|
|
360
|
+
mkdirSync2(cwd, { recursive: true });
|
|
361
|
+
} catch (err) {
|
|
362
|
+
logger.warn({ err, cwd }, "rollout: ensureCwdExists failed \u2014 caller should pre-create cwd");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function truncate(text, n) {
|
|
366
|
+
const cps = Array.from(text);
|
|
367
|
+
return cps.length > n ? `${cps.slice(0, n).join("")}\u2026` : text;
|
|
368
|
+
}
|
|
369
|
+
function extractChatPairs(entries, opts = { includeToolAnnotations: true }) {
|
|
370
|
+
const pairs = [];
|
|
371
|
+
let pendingUsers = [];
|
|
372
|
+
let pendingBatchSize = null;
|
|
373
|
+
let pendingBatchNextIndex = 0;
|
|
374
|
+
let pendingAssistantParts = [];
|
|
375
|
+
let toolBuffer = [];
|
|
376
|
+
const flushAssistant = () => {
|
|
377
|
+
if (pendingUsers.length === 0)
|
|
378
|
+
return;
|
|
379
|
+
const tools = opts.includeToolAnnotations && toolBuffer.length > 0 ? `
|
|
380
|
+
|
|
381
|
+
${toolBuffer.join(`
|
|
382
|
+
`)}` : "";
|
|
383
|
+
const assistantText = pendingAssistantParts.join("").trim() + tools;
|
|
384
|
+
if (assistantText.trim()) {
|
|
385
|
+
pairs.push({ userText: renderUserPromptBatch(pendingUsers), assistantText });
|
|
386
|
+
}
|
|
387
|
+
pendingUsers = [];
|
|
388
|
+
pendingBatchSize = null;
|
|
389
|
+
pendingBatchNextIndex = 0;
|
|
390
|
+
pendingAssistantParts = [];
|
|
391
|
+
toolBuffer = [];
|
|
392
|
+
};
|
|
393
|
+
for (const entry of entries) {
|
|
394
|
+
const ev = entry.event;
|
|
395
|
+
switch (ev.type) {
|
|
396
|
+
case "user_message": {
|
|
397
|
+
const userEvent = ev;
|
|
398
|
+
if (pendingAssistantParts.length > 0 || toolBuffer.length > 0) {
|
|
399
|
+
flushAssistant();
|
|
400
|
+
}
|
|
401
|
+
const batchSize = userEvent.consecutiveBatchSize;
|
|
402
|
+
const batchIndex = userEvent.consecutiveBatchIndex;
|
|
403
|
+
const marked = Number.isInteger(batchSize) && Number.isInteger(batchIndex) && (batchSize ?? 0) > 1 && (batchIndex ?? -1) >= 0 && (batchIndex ?? 0) < (batchSize ?? 0);
|
|
404
|
+
if (!marked) {
|
|
405
|
+
if (pendingUsers.length > 0)
|
|
406
|
+
flushAssistant();
|
|
407
|
+
pendingUsers.push(userEvent.content);
|
|
408
|
+
pendingBatchSize = null;
|
|
409
|
+
pendingBatchNextIndex = 0;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
if (batchIndex === 0) {
|
|
413
|
+
if (pendingUsers.length > 0)
|
|
414
|
+
flushAssistant();
|
|
415
|
+
pendingBatchSize = batchSize ?? null;
|
|
416
|
+
pendingBatchNextIndex = 0;
|
|
417
|
+
} else if (!(pendingBatchSize !== null && (batchSize ?? 0) >= pendingBatchSize && pendingBatchNextIndex === batchIndex || pendingBatchSize === null && pendingUsers.length === batchIndex)) {
|
|
418
|
+
flushAssistant();
|
|
419
|
+
}
|
|
420
|
+
pendingUsers.push(userEvent.content);
|
|
421
|
+
pendingBatchSize = batchSize ?? null;
|
|
422
|
+
pendingBatchNextIndex = (batchIndex ?? 0) + 1;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
case "session":
|
|
426
|
+
if (pendingAssistantParts.length > 0 || toolBuffer.length > 0) {
|
|
427
|
+
flushAssistant();
|
|
428
|
+
}
|
|
429
|
+
break;
|
|
430
|
+
case "text": {
|
|
431
|
+
if (pendingUsers.length === 0) {
|
|
432
|
+
pendingUsers = ["(continued)"];
|
|
433
|
+
}
|
|
434
|
+
pendingAssistantParts.push(ev.content);
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
case "result": {
|
|
438
|
+
if (pendingUsers.length === 0) {
|
|
439
|
+
pendingUsers = ["(continued)"];
|
|
440
|
+
}
|
|
441
|
+
pendingAssistantParts = [ev.content];
|
|
442
|
+
flushAssistant();
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
case "text_delta":
|
|
446
|
+
break;
|
|
447
|
+
case "tool_use": {
|
|
448
|
+
const u = ev;
|
|
449
|
+
toolBuffer.push(`<!-- Tool: ${u.name} ${truncate(JSON.stringify(u.input), 200)} -->`);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case "tool_result": {
|
|
453
|
+
const u = ev;
|
|
454
|
+
toolBuffer.push(`<!-- Tool result: ${truncate(u.content, 200)} -->`);
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
case "error": {
|
|
458
|
+
const u = ev;
|
|
459
|
+
toolBuffer.push(`[Error: ${truncate(u.content, 200)}]`);
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
case "tool_progress":
|
|
463
|
+
case "tool_use_summary":
|
|
464
|
+
case "file":
|
|
465
|
+
case "status":
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
flushAssistant();
|
|
470
|
+
return pairs;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ../../packages/core/src/agents/rollout/claude.ts
|
|
474
|
+
import { randomUUID } from "crypto";
|
|
475
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
476
|
+
import { homedir as homedir2 } from "os";
|
|
477
|
+
import { join as join2 } from "path";
|
|
478
|
+
|
|
479
|
+
// ../../packages/core/src/platform/jsonl.ts
|
|
480
|
+
import {
|
|
481
|
+
appendFileSync,
|
|
482
|
+
closeSync,
|
|
483
|
+
fsyncSync,
|
|
484
|
+
mkdirSync as mkdirSync3,
|
|
485
|
+
openSync,
|
|
486
|
+
readFileSync as readFileSync2,
|
|
487
|
+
renameSync,
|
|
488
|
+
statSync,
|
|
489
|
+
unlinkSync as unlinkSync2,
|
|
490
|
+
writeFileSync as writeFileSync2
|
|
491
|
+
} from "fs";
|
|
492
|
+
import { dirname as dirname2 } from "path";
|
|
493
|
+
|
|
494
|
+
// ../../packages/core/src/platform/file-utils.ts
|
|
495
|
+
import { unlinkSync } from "fs";
|
|
496
|
+
function createSafeUnlink(host) {
|
|
497
|
+
return (path, warnLabel) => {
|
|
498
|
+
try {
|
|
499
|
+
host.unlink(path);
|
|
500
|
+
} catch (e) {
|
|
501
|
+
if (e?.code === "ENOENT")
|
|
502
|
+
return;
|
|
503
|
+
if (warnLabel)
|
|
504
|
+
host.warn({ err: e, path }, warnLabel);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
var defaultSafeUnlink = createSafeUnlink({
|
|
509
|
+
unlink: unlinkSync,
|
|
510
|
+
warn: (context, message) => logger.warn(context, message)
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// ../../packages/core/src/platform/jsonl.ts
|
|
514
|
+
function parseJsonlText(raw) {
|
|
515
|
+
return raw.trim().split(`
|
|
516
|
+
`).filter(Boolean).map((line) => JSON.parse(line));
|
|
517
|
+
}
|
|
518
|
+
var LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
519
|
+
function writeJsonlFile(filePath, entries) {
|
|
520
|
+
const dir = dirname2(filePath);
|
|
521
|
+
mkdirSync3(dir, { recursive: true });
|
|
522
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
523
|
+
const payload = `${entries.map((e) => JSON.stringify(e)).join(`
|
|
524
|
+
`)}
|
|
525
|
+
`;
|
|
526
|
+
let fd = null;
|
|
527
|
+
try {
|
|
528
|
+
fd = openSync(tmpPath, "w");
|
|
529
|
+
writeFileSync2(fd, payload);
|
|
530
|
+
fsyncSync(fd);
|
|
531
|
+
closeSync(fd);
|
|
532
|
+
fd = null;
|
|
533
|
+
renameSync(tmpPath, filePath);
|
|
534
|
+
fsyncDirectoryBestEffort(dir);
|
|
535
|
+
} catch (err) {
|
|
536
|
+
if (fd !== null) {
|
|
537
|
+
try {
|
|
538
|
+
closeSync(fd);
|
|
539
|
+
} catch {}
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
unlinkSync2(tmpPath);
|
|
543
|
+
} catch {}
|
|
544
|
+
throw err;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function fsyncDirectoryBestEffort(dir) {
|
|
548
|
+
let fd = null;
|
|
549
|
+
try {
|
|
550
|
+
fd = openSync(dir, "r");
|
|
551
|
+
fsyncSync(fd);
|
|
552
|
+
} catch {} finally {
|
|
553
|
+
if (fd !== null) {
|
|
554
|
+
try {
|
|
555
|
+
closeSync(fd);
|
|
556
|
+
} catch {}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ../../packages/core/src/agents/rollout/claude.ts
|
|
562
|
+
var _attachmentsCache = null;
|
|
563
|
+
function loadClaudeAttachments() {
|
|
564
|
+
if (_attachmentsCache)
|
|
565
|
+
return _attachmentsCache;
|
|
566
|
+
const raw = readFileSync3(join2(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
|
|
567
|
+
const lines = parseJsonlText(raw);
|
|
568
|
+
if (lines.length < 2) {
|
|
569
|
+
throw new Error(`loadClaudeAttachments: expected >=2 entries in claude-attachments.jsonl, got ${lines.length}`);
|
|
570
|
+
}
|
|
571
|
+
_attachmentsCache = {
|
|
572
|
+
deferredToolsDelta: lines[0],
|
|
573
|
+
skillListing: lines[1]
|
|
574
|
+
};
|
|
575
|
+
return _attachmentsCache;
|
|
576
|
+
}
|
|
577
|
+
var CLAUDE_SDK_VERSION = "2.1.126";
|
|
578
|
+
var CLAUDE_DEFAULT_MODEL = "claude-sonnet-5";
|
|
579
|
+
var CLAUDE_DEFAULT_GIT_BRANCH = "HEAD";
|
|
580
|
+
function encodeClaudeCwd(cwd) {
|
|
581
|
+
const realCwd = cwd.startsWith("/tmp/") ? cwd.replace(/^\/tmp\//, "/private/tmp/") : cwd;
|
|
582
|
+
return `-${realCwd.replaceAll(/[^a-zA-Z0-9]/g, "-").replace(/^-/, "")}`;
|
|
583
|
+
}
|
|
584
|
+
function writeClaudeRollout(opts) {
|
|
585
|
+
const sessionId = opts.sessionId ?? randomUUID();
|
|
586
|
+
assertUuidLike("sessionId", sessionId);
|
|
587
|
+
ensureCwdExists(opts.cwd);
|
|
588
|
+
const pairs = opts.pairs ?? extractChatPairs(opts.entries ?? []);
|
|
589
|
+
const cwdReal = opts.cwd.startsWith("/tmp/") ? opts.cwd.replace(/^\/tmp\//, "/private/tmp/") : opts.cwd;
|
|
590
|
+
const lines = [];
|
|
591
|
+
const ts = () => new Date().toISOString();
|
|
592
|
+
let lastUuid = null;
|
|
593
|
+
lines.push({ type: "queue-operation", operation: "enqueue", timestamp: ts(), sessionId });
|
|
594
|
+
lines.push({ type: "queue-operation", operation: "dequeue", timestamp: ts(), sessionId });
|
|
595
|
+
const attachments = loadClaudeAttachments();
|
|
596
|
+
for (const pair of pairs) {
|
|
597
|
+
const userUuid = randomUUID();
|
|
598
|
+
lines.push({
|
|
599
|
+
parentUuid: lastUuid,
|
|
600
|
+
isSidechain: false,
|
|
601
|
+
promptId: randomUUID(),
|
|
602
|
+
type: "user",
|
|
603
|
+
message: { role: "user", content: [{ type: "text", text: pair.userText }] },
|
|
604
|
+
uuid: userUuid,
|
|
605
|
+
timestamp: ts(),
|
|
606
|
+
permissionMode: "bypassPermissions",
|
|
607
|
+
userType: "external",
|
|
608
|
+
entrypoint: "sdk-ts",
|
|
609
|
+
cwd: cwdReal,
|
|
610
|
+
sessionId,
|
|
611
|
+
version: CLAUDE_SDK_VERSION,
|
|
612
|
+
gitBranch: CLAUDE_DEFAULT_GIT_BRANCH
|
|
613
|
+
});
|
|
614
|
+
const att1Uuid = randomUUID();
|
|
615
|
+
const att1 = clone(attachments.deferredToolsDelta);
|
|
616
|
+
att1.parentUuid = userUuid;
|
|
617
|
+
att1.uuid = att1Uuid;
|
|
618
|
+
att1.timestamp = ts();
|
|
619
|
+
att1.sessionId = sessionId;
|
|
620
|
+
att1.cwd = cwdReal;
|
|
621
|
+
lines.push(att1);
|
|
622
|
+
const att2Uuid = randomUUID();
|
|
623
|
+
const att2 = clone(attachments.skillListing);
|
|
624
|
+
att2.parentUuid = att1Uuid;
|
|
625
|
+
att2.uuid = att2Uuid;
|
|
626
|
+
att2.timestamp = ts();
|
|
627
|
+
att2.sessionId = sessionId;
|
|
628
|
+
att2.cwd = cwdReal;
|
|
629
|
+
lines.push(att2);
|
|
630
|
+
const assistantUuid = randomUUID();
|
|
631
|
+
lines.push({
|
|
632
|
+
parentUuid: att2Uuid,
|
|
633
|
+
isSidechain: false,
|
|
634
|
+
message: {
|
|
635
|
+
model: opts.model ?? CLAUDE_DEFAULT_MODEL,
|
|
636
|
+
id: `msg_${randomUUID().replace(/-/g, "").slice(0, 24)}`,
|
|
637
|
+
type: "message",
|
|
638
|
+
role: "assistant",
|
|
639
|
+
content: [{ type: "text", text: pair.assistantText }],
|
|
640
|
+
stop_reason: "end_turn",
|
|
641
|
+
stop_sequence: null,
|
|
642
|
+
stop_details: null,
|
|
643
|
+
usage: {
|
|
644
|
+
input_tokens: 0,
|
|
645
|
+
cache_creation_input_tokens: 0,
|
|
646
|
+
cache_read_input_tokens: 0,
|
|
647
|
+
output_tokens: 0,
|
|
648
|
+
server_tool_use: { web_search_requests: 0, web_fetch_requests: 0 },
|
|
649
|
+
service_tier: "standard",
|
|
650
|
+
cache_creation: { ephemeral_1h_input_tokens: 0, ephemeral_5m_input_tokens: 0 },
|
|
651
|
+
inference_geo: "",
|
|
652
|
+
iterations: [],
|
|
653
|
+
speed: "standard"
|
|
654
|
+
},
|
|
655
|
+
diagnostics: null
|
|
656
|
+
},
|
|
657
|
+
requestId: `req_${randomUUID().replace(/-/g, "").slice(0, 22)}`,
|
|
658
|
+
type: "assistant",
|
|
659
|
+
uuid: assistantUuid,
|
|
660
|
+
timestamp: ts(),
|
|
661
|
+
userType: "external",
|
|
662
|
+
entrypoint: "sdk-ts",
|
|
663
|
+
cwd: cwdReal,
|
|
664
|
+
sessionId,
|
|
665
|
+
version: CLAUDE_SDK_VERSION,
|
|
666
|
+
gitBranch: CLAUDE_DEFAULT_GIT_BRANCH
|
|
667
|
+
});
|
|
668
|
+
lastUuid = assistantUuid;
|
|
669
|
+
}
|
|
670
|
+
const projectsDir = join2(homedir2(), ".claude", "projects", encodeClaudeCwd(opts.cwd));
|
|
671
|
+
const path = join2(projectsDir, `${sessionId}.jsonl`);
|
|
672
|
+
writeJsonlFile(path, lines);
|
|
673
|
+
logger.info({ sessionId, path, pairs: pairs.length }, "writeClaudeRollout: synthetic rollout placed");
|
|
674
|
+
return { sessionId, rolloutPath: path };
|
|
675
|
+
}
|
|
676
|
+
function repairPoisonedRollout(sessionId, cwd) {
|
|
677
|
+
try {
|
|
678
|
+
const path = join2(homedir2(), ".claude", "projects", encodeClaudeCwd(cwd), `${sessionId}.jsonl`);
|
|
679
|
+
if (!existsSync2(path))
|
|
680
|
+
return false;
|
|
681
|
+
const lines = readFileSync3(path, "utf8").split(`
|
|
682
|
+
`).filter((l) => l.trim());
|
|
683
|
+
let poisonedAssistantIdx = -1;
|
|
684
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
685
|
+
const entry = parseClaudeRolloutLine(lines[i]);
|
|
686
|
+
if (entry && isPoisonedAssistantEntry(entry)) {
|
|
687
|
+
poisonedAssistantIdx = i;
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (poisonedAssistantIdx < 0) {
|
|
692
|
+
logger.warn({ sessionId, path }, "repairPoisonedRollout: no poisoned assistant entry found");
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
let turnStartIdx = -1;
|
|
696
|
+
for (let i = poisonedAssistantIdx - 1;i >= 0; i--) {
|
|
697
|
+
const entry = parseClaudeRolloutLine(lines[i]);
|
|
698
|
+
if (entry?.type === "user") {
|
|
699
|
+
turnStartIdx = i;
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (turnStartIdx < 0) {
|
|
704
|
+
logger.warn({ sessionId, path, poisonedAssistantIdx }, "repairPoisonedRollout: poisoned assistant has no preceding user entry");
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
const kept = lines.slice(0, turnStartIdx);
|
|
708
|
+
writeFileSync3(path, kept.length ? `${kept.join(`
|
|
709
|
+
`)}
|
|
710
|
+
` : "");
|
|
711
|
+
logger.warn({
|
|
712
|
+
sessionId,
|
|
713
|
+
path,
|
|
714
|
+
poisonedAssistantIdx,
|
|
715
|
+
turnStartIdx,
|
|
716
|
+
droppedEntries: lines.length - turnStartIdx,
|
|
717
|
+
keptEntries: kept.length
|
|
718
|
+
}, "repairPoisonedRollout: truncated turn removed from rollout JSONL");
|
|
719
|
+
return true;
|
|
720
|
+
} catch (err) {
|
|
721
|
+
logger.warn({ err, sessionId, cwd }, "repairPoisonedRollout: I/O error");
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function parseClaudeRolloutLine(line) {
|
|
726
|
+
try {
|
|
727
|
+
return JSON.parse(line);
|
|
728
|
+
} catch {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function isPoisonedAssistantEntry(entry) {
|
|
733
|
+
if (entry.type !== "assistant")
|
|
734
|
+
return false;
|
|
735
|
+
if (entry.message?.stop_reason !== "max_tokens")
|
|
736
|
+
return false;
|
|
737
|
+
const content = entry.message.content;
|
|
738
|
+
if (!Array.isArray(content))
|
|
739
|
+
return false;
|
|
740
|
+
return content.some((block) => {
|
|
741
|
+
if (!block || typeof block !== "object")
|
|
742
|
+
return false;
|
|
743
|
+
const type = block.type;
|
|
744
|
+
return type === "thinking" || type === "redacted_thinking";
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// ../../packages/core/src/agents/rollout/codex.ts
|
|
749
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
750
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
751
|
+
import { basename, dirname as dirname4, join as join4, resolve as resolve4 } from "path";
|
|
752
|
+
|
|
753
|
+
// ../../packages/core/src/agents/execution-host.ts
|
|
754
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
755
|
+
import { dirname as dirname3 } from "path";
|
|
756
|
+
|
|
757
|
+
// ../../packages/core/src/security/sensitive-path.ts
|
|
758
|
+
import { realpathSync } from "fs";
|
|
759
|
+
import { resolve as resolve3 } from "path";
|
|
760
|
+
var SENSITIVE_PATH_PATTERNS = [
|
|
761
|
+
/\/\.env(\.|$)/i,
|
|
762
|
+
/\/\.ssh\//i,
|
|
763
|
+
/\/\.aws\//i,
|
|
764
|
+
/\/\.gnupg\//i,
|
|
765
|
+
/\/\.netrc$/i,
|
|
766
|
+
/\/\.npmrc$/i,
|
|
767
|
+
/\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)(\.pub)?$/i,
|
|
768
|
+
/\.(pem|key|p12|pfx|cer|crt)$/i,
|
|
769
|
+
/\/Library\/Keychains\//i,
|
|
770
|
+
/\/vault\.db(-wal|-shm|-journal)?$/i,
|
|
771
|
+
/\/vault-master-key$/i,
|
|
772
|
+
/\/runtime-mcp-secret$/i,
|
|
773
|
+
/\/sessions\.db(-wal|-shm|-journal)?$/i
|
|
774
|
+
];
|
|
775
|
+
function isSensitivePath(filePath) {
|
|
776
|
+
const normalized = resolve3(filePath);
|
|
777
|
+
if (SENSITIVE_PATH_PATTERNS.some((p) => p.test(normalized)))
|
|
778
|
+
return true;
|
|
779
|
+
try {
|
|
780
|
+
const real = realpathSync(normalized);
|
|
781
|
+
if (real !== normalized)
|
|
782
|
+
return SENSITIVE_PATH_PATTERNS.some((p) => p.test(real));
|
|
783
|
+
} catch {}
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// ../../packages/core/src/agents/vault-tool-policy.ts
|
|
788
|
+
var SENSITIVE_RUNTIME_NAMES = [
|
|
789
|
+
"vault.db",
|
|
790
|
+
"vault-master-key",
|
|
791
|
+
"runtime-mcp-secret",
|
|
792
|
+
"sessions.db"
|
|
793
|
+
];
|
|
794
|
+
var DIRECT_VAULT_EXECUTION_TOOLS = new Set(["Bash", "WebFetch"]);
|
|
795
|
+
function createVaultToolPolicy(host) {
|
|
796
|
+
function isVaultBrokerTool(toolName) {
|
|
797
|
+
return toolName.includes("vault_run") || toolName.includes("vault_http_request");
|
|
798
|
+
}
|
|
799
|
+
function referencesRuntimeSecretStorage(value) {
|
|
800
|
+
if (typeof value === "string") {
|
|
801
|
+
const lower = value.toLowerCase();
|
|
802
|
+
if (SENSITIVE_RUNTIME_NAMES.some((name) => lower.includes(name)))
|
|
803
|
+
return true;
|
|
804
|
+
return value.startsWith("/") && host.isSensitivePath(value);
|
|
805
|
+
}
|
|
806
|
+
if (Array.isArray(value))
|
|
807
|
+
return value.some(referencesRuntimeSecretStorage);
|
|
808
|
+
if (value && typeof value === "object") {
|
|
809
|
+
return Object.values(value).some(referencesRuntimeSecretStorage);
|
|
810
|
+
}
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
function shouldRedirectVaultTool(userId, toolName, input) {
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
return { isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool };
|
|
817
|
+
}
|
|
818
|
+
var defaultVaultToolPolicy = createVaultToolPolicy({
|
|
819
|
+
isSensitivePath,
|
|
820
|
+
valueReferencesVaultKey: () => false
|
|
821
|
+
});
|
|
822
|
+
var isVaultBrokerTool = defaultVaultToolPolicy.isVaultBrokerTool;
|
|
823
|
+
var referencesRuntimeSecretStorage = defaultVaultToolPolicy.referencesRuntimeSecretStorage;
|
|
824
|
+
var shouldRedirectVaultTool = defaultVaultToolPolicy.shouldRedirectVaultTool;
|
|
825
|
+
|
|
826
|
+
// ../../packages/core/src/mcp/canonical-bridge-config.ts
|
|
827
|
+
var registrations = [];
|
|
828
|
+
var turnLeases = new Map;
|
|
829
|
+
function turnKey(scope) {
|
|
830
|
+
return JSON.stringify([
|
|
831
|
+
scope.userId,
|
|
832
|
+
scope.topicId,
|
|
833
|
+
scope.queryId,
|
|
834
|
+
scope.peerBridge.hubCellId,
|
|
835
|
+
scope.peerBridge.hostTopicId,
|
|
836
|
+
scope.peerBridge.hostQueryId
|
|
837
|
+
]);
|
|
838
|
+
}
|
|
839
|
+
function canonicalMcpBridgeEnv(scope) {
|
|
840
|
+
const lease = registrations.at(-1)?.provider(scope);
|
|
841
|
+
if (!lease)
|
|
842
|
+
return;
|
|
843
|
+
const key = turnKey(scope);
|
|
844
|
+
const leases = turnLeases.get(key) ?? new Set;
|
|
845
|
+
leases.add(lease.revoke);
|
|
846
|
+
turnLeases.set(key, leases);
|
|
847
|
+
return lease.env;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// ../../packages/core/src/mcp/runtime-spec.ts
|
|
851
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
852
|
+
var RUNTIME_MCP_KEY = "runtime";
|
|
853
|
+
var RUNTIME_MCP_BASE_PATH = "/mcp/runtime";
|
|
854
|
+
var TOKEN_TTL_MS = 4 * 60 * 60 * 1000;
|
|
855
|
+
var CLAUDE_MCP_TOOL_TIMEOUT_MS = 600000;
|
|
856
|
+
var runtimePort = NEGOTIUM_PORT;
|
|
857
|
+
function encodeTokenPart(value) {
|
|
858
|
+
return Buffer.from(JSON.stringify(value), "utf-8").toString("base64url");
|
|
859
|
+
}
|
|
860
|
+
function signTokenPayload(payloadPart) {
|
|
861
|
+
return createHmac("sha256", RUNTIME_MCP_SECRET).update(payloadPart).digest("base64url");
|
|
862
|
+
}
|
|
863
|
+
function issueRuntimeMcpToken(ctx) {
|
|
864
|
+
const payloadPart = encodeTokenPart({
|
|
865
|
+
v: 1,
|
|
866
|
+
exp: Date.now() + TOKEN_TTL_MS,
|
|
867
|
+
ctx
|
|
868
|
+
});
|
|
869
|
+
return `${payloadPart}.${signTokenPayload(payloadPart)}`;
|
|
870
|
+
}
|
|
871
|
+
function issueHostedMcpToken(surface, ctx) {
|
|
872
|
+
const payloadPart = encodeTokenPart({
|
|
873
|
+
v: 2,
|
|
874
|
+
exp: Date.now() + TOKEN_TTL_MS,
|
|
875
|
+
aud: surface,
|
|
876
|
+
ctx
|
|
877
|
+
});
|
|
878
|
+
return `${payloadPart}.${signTokenPayload(payloadPart)}`;
|
|
879
|
+
}
|
|
880
|
+
function buildRuntimeMcpSpec(agent, ctx) {
|
|
881
|
+
const token = issueRuntimeMcpToken(ctx);
|
|
882
|
+
const base = `http://127.0.0.1:${runtimePort}${RUNTIME_MCP_BASE_PATH}`;
|
|
883
|
+
const query = `token=${encodeURIComponent(token)}`;
|
|
884
|
+
if (agent === "codex")
|
|
885
|
+
return { url: `${base}/mcp?${query}` };
|
|
886
|
+
return {
|
|
887
|
+
type: "sse",
|
|
888
|
+
url: `${base}/sse?${query}`,
|
|
889
|
+
timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
function buildHostedMcpSpec(agent, surface, ctx) {
|
|
893
|
+
const token = issueHostedMcpToken(surface, ctx);
|
|
894
|
+
const base = `http://127.0.0.1:${runtimePort}${RUNTIME_MCP_BASE_PATH}/${surface}`;
|
|
895
|
+
const query = `token=${encodeURIComponent(token)}`;
|
|
896
|
+
if (agent === "codex")
|
|
897
|
+
return { url: `${base}/mcp?${query}` };
|
|
898
|
+
return {
|
|
899
|
+
type: "sse",
|
|
900
|
+
url: `${base}/sse?${query}`,
|
|
901
|
+
timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// ../../packages/core/src/mcp/session-comm/bridge-ipc-config.ts
|
|
906
|
+
var registrations2 = [];
|
|
907
|
+
function peerSessionBridgeIpcEnv() {
|
|
908
|
+
const active = registrations2.at(-1)?.config;
|
|
909
|
+
if (!active)
|
|
910
|
+
return;
|
|
911
|
+
return {
|
|
912
|
+
NEGOTIUM_PEER_SESSION_BRIDGE_URL: active.url,
|
|
913
|
+
NEGOTIUM_PEER_SESSION_BRIDGE_TOKEN: active.token
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// ../../packages/core/src/platform/background-bash/manager.ts
|
|
918
|
+
import { execFileSync as execFileSync2, spawn } from "child_process";
|
|
919
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
920
|
+
|
|
921
|
+
// ../../packages/core/src/platform/delay.ts
|
|
922
|
+
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
923
|
+
|
|
924
|
+
// ../../packages/core/src/platform/background-bash/context.ts
|
|
925
|
+
import { createHmac as createHmac2 } from "crypto";
|
|
926
|
+
function deriveBgBashContextCapability(runtimeCapability, userId, topic) {
|
|
927
|
+
return createHmac2("sha256", runtimeCapability).update(`${userId}\x00${topic}`).digest("hex");
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// ../../packages/core/src/platform/background-bash/manager.ts
|
|
931
|
+
function makeBgBashKey(_userId, _topic) {
|
|
932
|
+
return "runtime";
|
|
933
|
+
}
|
|
934
|
+
function defaultPortPids(port) {
|
|
935
|
+
try {
|
|
936
|
+
return execFileSync2("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" }).toString().trim().split(`
|
|
937
|
+
`).map((pid) => Number.parseInt(pid, 10)).filter((pid) => !Number.isNaN(pid));
|
|
938
|
+
} catch {
|
|
939
|
+
return [];
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function createBackgroundBashManager(options = {}) {
|
|
943
|
+
const instances = new Map;
|
|
944
|
+
const usedPorts = new Set;
|
|
945
|
+
const spawning = new Map;
|
|
946
|
+
const knownContexts = new Map;
|
|
947
|
+
const runtimeCapability = options.capability ?? randomBytes2(32).toString("hex");
|
|
948
|
+
const runtimeServerId = options.serverId ?? randomBytes2(16).toString("hex");
|
|
949
|
+
const serverFile = options.serverFile ?? BACKGROUND_BASH_SERVER;
|
|
950
|
+
const basePort = options.basePort ?? BG_BASH_BASE_PORT;
|
|
951
|
+
const maxPort = options.maxPort ?? BG_BASH_MAX_PORT;
|
|
952
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
953
|
+
const now = options.now ?? Date.now;
|
|
954
|
+
const wait = options.delay ?? delay;
|
|
955
|
+
const portPids = options.portPids ?? defaultPortPids;
|
|
956
|
+
const spawnImpl = options.spawn ?? ((command, args, spawnOptions) => spawn(command, [...args], spawnOptions));
|
|
957
|
+
function contextKey(userId, topic) {
|
|
958
|
+
return `${userId}\x00${topic}`;
|
|
959
|
+
}
|
|
960
|
+
function contextCapability(userId, topic) {
|
|
961
|
+
return deriveBgBashContextCapability(runtimeCapability, userId, topic);
|
|
962
|
+
}
|
|
963
|
+
async function allocatePort(excludedPorts = new Set) {
|
|
964
|
+
for (let port = basePort;port <= maxPort; port++) {
|
|
965
|
+
if (usedPorts.has(port) || excludedPorts.has(port))
|
|
966
|
+
continue;
|
|
967
|
+
usedPorts.add(port);
|
|
968
|
+
if (portPids(port).length > 0) {
|
|
969
|
+
usedPorts.delete(port);
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
return port;
|
|
973
|
+
}
|
|
974
|
+
throw new Error(`No available ports for background-bash (range ${basePort}-${maxPort}, ${instances.size} active)`);
|
|
975
|
+
}
|
|
976
|
+
async function isHealthy(port) {
|
|
977
|
+
try {
|
|
978
|
+
const response = await fetchImpl(`http://127.0.0.1:${port}/health`, {
|
|
979
|
+
signal: AbortSignal.timeout(2000)
|
|
980
|
+
});
|
|
981
|
+
return response.ok && await response.text() === runtimeServerId;
|
|
982
|
+
} catch {
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
function killRuntime() {
|
|
987
|
+
const key = "runtime";
|
|
988
|
+
const instance = instances.get(key);
|
|
989
|
+
if (!instance)
|
|
990
|
+
return;
|
|
991
|
+
try {
|
|
992
|
+
instance.process.kill("SIGTERM");
|
|
993
|
+
} catch {}
|
|
994
|
+
usedPorts.delete(instance.port);
|
|
995
|
+
instances.delete(key);
|
|
996
|
+
logger.info({ key, port: instance.port }, "background-bash server killed");
|
|
997
|
+
}
|
|
998
|
+
async function spawnServer(key, reservedPort, excludedPorts = new Set) {
|
|
999
|
+
const port = reservedPort ?? await allocatePort(excludedPorts);
|
|
1000
|
+
const process2 = spawnImpl("bun", ["run", serverFile, `--port=${port}`], {
|
|
1001
|
+
stdio: "ignore",
|
|
1002
|
+
detached: false,
|
|
1003
|
+
env: {
|
|
1004
|
+
...options.env ?? globalThis.process.env,
|
|
1005
|
+
NEGOTIUM_BG_BASH_CAPABILITY: runtimeCapability,
|
|
1006
|
+
NEGOTIUM_BG_BASH_SERVER_ID: runtimeServerId
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
process2.once("error", (error) => {
|
|
1010
|
+
logger.error({ err: error, key }, "background-bash server error");
|
|
1011
|
+
if (instances.get(key)?.process === process2) {
|
|
1012
|
+
usedPorts.delete(port);
|
|
1013
|
+
instances.delete(key);
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
process2.once("exit", (code) => {
|
|
1017
|
+
logger.info({ key, code }, "background-bash server exited");
|
|
1018
|
+
if (instances.get(key)?.process === process2) {
|
|
1019
|
+
usedPorts.delete(port);
|
|
1020
|
+
instances.delete(key);
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
const timestamp = now();
|
|
1024
|
+
instances.set(key, { process: process2, port, startedAt: timestamp, lastUsedAt: timestamp });
|
|
1025
|
+
const started = now();
|
|
1026
|
+
while (now() - started < 8000) {
|
|
1027
|
+
if (process2.exitCode !== null) {
|
|
1028
|
+
const nextExcluded = new Set(excludedPorts);
|
|
1029
|
+
nextExcluded.add(port);
|
|
1030
|
+
return spawnServer(key, undefined, nextExcluded);
|
|
1031
|
+
}
|
|
1032
|
+
if (await isHealthy(port))
|
|
1033
|
+
return port;
|
|
1034
|
+
await wait(200);
|
|
1035
|
+
}
|
|
1036
|
+
killRuntime();
|
|
1037
|
+
throw new Error(`background-bash server failed health check after spawn on port ${port}`);
|
|
1038
|
+
}
|
|
1039
|
+
async function ensure(userId, topic) {
|
|
1040
|
+
const key = makeBgBashKey(userId, topic);
|
|
1041
|
+
knownContexts.set(contextKey(userId, topic), { userId, topic });
|
|
1042
|
+
const inProgress = spawning.get(key);
|
|
1043
|
+
if (inProgress)
|
|
1044
|
+
return inProgress;
|
|
1045
|
+
const promise = (async () => {
|
|
1046
|
+
const existing = instances.get(key);
|
|
1047
|
+
if (existing && !existing.process.killed && existing.process.exitCode === null) {
|
|
1048
|
+
if (await isHealthy(existing.port)) {
|
|
1049
|
+
existing.lastUsedAt = now();
|
|
1050
|
+
return existing.port;
|
|
1051
|
+
}
|
|
1052
|
+
killRuntime();
|
|
1053
|
+
} else if (existing) {
|
|
1054
|
+
usedPorts.delete(existing.port);
|
|
1055
|
+
instances.delete(key);
|
|
1056
|
+
}
|
|
1057
|
+
return spawnServer(key);
|
|
1058
|
+
})().finally(() => spawning.delete(key));
|
|
1059
|
+
spawning.set(key, promise);
|
|
1060
|
+
return promise;
|
|
1061
|
+
}
|
|
1062
|
+
function clear(userId, topic) {
|
|
1063
|
+
knownContexts.delete(contextKey(userId, topic));
|
|
1064
|
+
const instance = instances.get("runtime");
|
|
1065
|
+
if (!instance)
|
|
1066
|
+
return;
|
|
1067
|
+
const query = new URLSearchParams({
|
|
1068
|
+
user: userId,
|
|
1069
|
+
topic,
|
|
1070
|
+
capability: contextCapability(userId, topic)
|
|
1071
|
+
});
|
|
1072
|
+
fetchImpl(`http://127.0.0.1:${instance.port}/contexts?${query}`, {
|
|
1073
|
+
method: "DELETE"
|
|
1074
|
+
}).catch(() => {});
|
|
1075
|
+
}
|
|
1076
|
+
function clearUser(userId) {
|
|
1077
|
+
for (const context of [...knownContexts.values()]) {
|
|
1078
|
+
if (context.userId === userId)
|
|
1079
|
+
clear(context.userId, context.topic);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
async function killAll() {
|
|
1083
|
+
const entries = [...instances.values()];
|
|
1084
|
+
for (const instance of entries) {
|
|
1085
|
+
try {
|
|
1086
|
+
instance.process.kill("SIGTERM");
|
|
1087
|
+
} catch {}
|
|
1088
|
+
}
|
|
1089
|
+
instances.clear();
|
|
1090
|
+
usedPorts.clear();
|
|
1091
|
+
knownContexts.clear();
|
|
1092
|
+
const deadline = now() + 3000;
|
|
1093
|
+
await Promise.all(entries.map((instance) => new Promise((resolve4) => {
|
|
1094
|
+
if (instance.process.exitCode !== null || instance.process.killed)
|
|
1095
|
+
return resolve4();
|
|
1096
|
+
instance.process.once("exit", resolve4);
|
|
1097
|
+
instance.process.once("error", resolve4);
|
|
1098
|
+
const timer = setTimeout(resolve4, Math.max(0, deadline - now()));
|
|
1099
|
+
timer.unref?.();
|
|
1100
|
+
})));
|
|
1101
|
+
}
|
|
1102
|
+
return { contextCapability, ensure, clear, clearUser, killAll };
|
|
1103
|
+
}
|
|
1104
|
+
var defaultManager = createBackgroundBashManager();
|
|
1105
|
+
var bgBashContextCapability = defaultManager.contextCapability;
|
|
1106
|
+
var ensureBgBash = defaultManager.ensure;
|
|
1107
|
+
var killBgBash = defaultManager.clear;
|
|
1108
|
+
var killBgBashForUser = defaultManager.clearUser;
|
|
1109
|
+
var killAllBgBash = defaultManager.killAll;
|
|
1110
|
+
|
|
1111
|
+
// ../../packages/core/src/platform/mcp-catalog-policy.ts
|
|
1112
|
+
var COMMON_RUNTIME_MCP_POLICY = {
|
|
1113
|
+
playwright: { scopes: ["dm", "forum", "fork", "cron"], forumRequired: true },
|
|
1114
|
+
runtime: { scopes: ["forum", "manager", "fork", "cron"], forumRequired: true },
|
|
1115
|
+
"token-stats": { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
1116
|
+
task: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
1117
|
+
"session-comm": { scopes: ["forum", "fork", "manager"], forumRequired: true },
|
|
1118
|
+
wiki: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
1119
|
+
skills: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
1120
|
+
"system-health": { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
1121
|
+
"background-bash": { scopes: ["forum"], forumRequired: true },
|
|
1122
|
+
"agent-health": { scopes: ["forum", "manager", "cron"], forumRequired: true },
|
|
1123
|
+
vault: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true }
|
|
1124
|
+
};
|
|
1125
|
+
function classifyForumMcpServers(catalog) {
|
|
1126
|
+
const all = Object.entries(catalog).filter(([, entry]) => entry.scopes.includes("forum")).map(([name]) => name);
|
|
1127
|
+
const required = Object.entries(catalog).filter(([, entry]) => entry.scopes.includes("forum") && entry.forumRequired).map(([name]) => name);
|
|
1128
|
+
const requiredSet = new Set(required);
|
|
1129
|
+
return { all, required, optional: all.filter((name) => !requiredSet.has(name)) };
|
|
1130
|
+
}
|
|
1131
|
+
function commonRuntimeMcpPolicy(name) {
|
|
1132
|
+
return COMMON_RUNTIME_MCP_POLICY[name];
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// ../../packages/core/src/platform/playwright/capability.ts
|
|
1136
|
+
import { createHmac as createHmac3 } from "crypto";
|
|
1137
|
+
function browserOwnerCapability(capability, owner) {
|
|
1138
|
+
return createHmac3("sha256", capability).update(owner).digest("hex");
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// ../../packages/core/src/platform/mcp-config.ts
|
|
1142
|
+
function buildStdioMcpServer(agent, serverFile, serverArgs, env) {
|
|
1143
|
+
if (agent === "codex") {
|
|
1144
|
+
return {
|
|
1145
|
+
command: "node",
|
|
1146
|
+
args: ["--import", TSX_LOADER, serverFile, ...serverArgs],
|
|
1147
|
+
env: { TSX_TSCONFIG_PATH: TSCONFIG_PATH, ...env }
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
return {
|
|
1151
|
+
command: "bun",
|
|
1152
|
+
args: ["run", serverFile, ...serverArgs],
|
|
1153
|
+
...env ? { env } : {}
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
function useHostedBuiltinMcp() {
|
|
1157
|
+
return envText("NEGOTIUM_BUILTIN_MCP_TRANSPORT") !== "stdio";
|
|
1158
|
+
}
|
|
1159
|
+
function buildBuiltinMcpServer(surface, ctx, stdio) {
|
|
1160
|
+
if (!useHostedBuiltinMcp())
|
|
1161
|
+
return stdio();
|
|
1162
|
+
const agent = ctx.agent ?? FALLBACK_AGENT;
|
|
1163
|
+
return buildHostedMcpSpec(agent, surface, {
|
|
1164
|
+
userId: ctx.userId,
|
|
1165
|
+
topicTitle: ctx.session,
|
|
1166
|
+
...ctx.topicId ? { topicId: ctx.topicId } : {},
|
|
1167
|
+
...ctx.queryId ? { queryId: ctx.queryId } : {},
|
|
1168
|
+
...ctx.wikiTopicId ? { wikiTopicId: ctx.wikiTopicId } : {},
|
|
1169
|
+
...ctx.subagentParentTopicId ? { subagentParentTopicId: ctx.subagentParentTopicId } : {},
|
|
1170
|
+
cwd: ctx.cwd ?? (ctx.topicId ? resolveTopicWorkspaceDir(ctx.topicId) : process.cwd()),
|
|
1171
|
+
agent,
|
|
1172
|
+
...ctx.model ? { model: ctx.model } : {},
|
|
1173
|
+
...ctx.depth !== undefined ? { depth: ctx.depth } : {},
|
|
1174
|
+
...ctx.silent !== undefined ? { silent: ctx.silent } : {},
|
|
1175
|
+
...ctx.peerBridge ? { peerBridge: ctx.peerBridge } : {}
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
var _playwrightUnavailableNotifier;
|
|
1179
|
+
var _playwrightUnavailableLastNotifiedAt = new Map;
|
|
1180
|
+
var _PLAYWRIGHT_UNAVAILABLE_COOLDOWN_MS = 5 * 60000;
|
|
1181
|
+
var _playwrightUnavailableThisTurn = new Set;
|
|
1182
|
+
function _playwrightUnavailableKey(userId, topic) {
|
|
1183
|
+
return `${userId}:${topic ?? ""}`;
|
|
1184
|
+
}
|
|
1185
|
+
function _markPlaywrightUnavailable(ctx) {
|
|
1186
|
+
_playwrightUnavailableThisTurn.add(_playwrightUnavailableKey(ctx.userId, ctx.topic));
|
|
1187
|
+
if (!_playwrightUnavailableNotifier)
|
|
1188
|
+
return;
|
|
1189
|
+
const key = _playwrightUnavailableKey(ctx.userId, ctx.topic);
|
|
1190
|
+
const now = Date.now();
|
|
1191
|
+
const last = _playwrightUnavailableLastNotifiedAt.get(key) ?? 0;
|
|
1192
|
+
if (now - last < _PLAYWRIGHT_UNAVAILABLE_COOLDOWN_MS)
|
|
1193
|
+
return;
|
|
1194
|
+
_playwrightUnavailableLastNotifiedAt.set(key, now);
|
|
1195
|
+
try {
|
|
1196
|
+
_playwrightUnavailableNotifier(ctx);
|
|
1197
|
+
} catch (err) {
|
|
1198
|
+
logger.warn({ err }, "playwright unavailable notifier threw");
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
var CODEX_BROWSER_CAPABILITY_ENV = "NEGOTIUM_BROWSER_CAPABILITY";
|
|
1202
|
+
function browserOwnerForContext(ctx) {
|
|
1203
|
+
if (ctx.topicId)
|
|
1204
|
+
return `topic:${ctx.topicId}`;
|
|
1205
|
+
if (ctx.userId && ctx.session)
|
|
1206
|
+
return `user:${ctx.userId}:${ctx.session}`;
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
function playwrightTransport(port, owner, capability, agent) {
|
|
1210
|
+
const ownerCapability = browserOwnerCapability(capability, owner);
|
|
1211
|
+
if (agent === "codex") {
|
|
1212
|
+
const query2 = new URLSearchParams({ owner });
|
|
1213
|
+
return {
|
|
1214
|
+
url: `http://127.0.0.1:${port}/mcp?${query2}`,
|
|
1215
|
+
env_http_headers: { "X-Browser-Capability": CODEX_BROWSER_CAPABILITY_ENV }
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
const query = new URLSearchParams({ owner });
|
|
1219
|
+
if (agent === "maestro") {
|
|
1220
|
+
return buildStdioMcpServer("maestro", BROWSER_MCP_SSE_PROXY_SERVER, [], {
|
|
1221
|
+
NEGOTIUM_BROWSER_SSE_URL: `http://127.0.0.1:${port}/sse?${query}`,
|
|
1222
|
+
NEGOTIUM_BROWSER_OWNER_CAPABILITY: ownerCapability
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
type: "sse",
|
|
1227
|
+
url: `http://127.0.0.1:${port}/sse?${query}`,
|
|
1228
|
+
headers: { "X-Browser-Capability": ownerCapability }
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
function longLivedHttpMcp(agent, port) {
|
|
1232
|
+
return agent === "codex" ? { url: `http://127.0.0.1:${port}/mcp` } : { type: "sse", url: `http://127.0.0.1:${port}/sse` };
|
|
1233
|
+
}
|
|
1234
|
+
function backgroundBashTransport(agent, port, userId, topic) {
|
|
1235
|
+
const capability = bgBashContextCapability(userId, topic);
|
|
1236
|
+
const headers = {
|
|
1237
|
+
"X-Background-Bash-User": userId,
|
|
1238
|
+
"X-Background-Bash-Topic": topic,
|
|
1239
|
+
"X-Background-Bash-Capability": capability
|
|
1240
|
+
};
|
|
1241
|
+
if (agent === "codex")
|
|
1242
|
+
return { url: `http://127.0.0.1:${port}/mcp`, http_headers: headers };
|
|
1243
|
+
if (agent === "maestro") {
|
|
1244
|
+
const query = new URLSearchParams({ user: userId, topic, capability });
|
|
1245
|
+
return { type: "sse", url: `http://127.0.0.1:${port}/sse?${query}` };
|
|
1246
|
+
}
|
|
1247
|
+
return { type: "sse", url: `http://127.0.0.1:${port}/sse`, headers };
|
|
1248
|
+
}
|
|
1249
|
+
var MCP_CATALOG = {
|
|
1250
|
+
playwright: {
|
|
1251
|
+
...commonRuntimeMcpPolicy("playwright"),
|
|
1252
|
+
build({ userId, session, topicId, playwrightPort, playwrightCapability, agent }) {
|
|
1253
|
+
if (playwrightPort && playwrightCapability) {
|
|
1254
|
+
const owner = browserOwnerForContext({ userId, session, topicId });
|
|
1255
|
+
if (!owner)
|
|
1256
|
+
return null;
|
|
1257
|
+
return playwrightTransport(playwrightPort, owner, playwrightCapability, agent);
|
|
1258
|
+
}
|
|
1259
|
+
_markPlaywrightUnavailable({
|
|
1260
|
+
userId,
|
|
1261
|
+
topic: session,
|
|
1262
|
+
agent
|
|
1263
|
+
});
|
|
1264
|
+
return null;
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
[RUNTIME_MCP_KEY]: {
|
|
1268
|
+
...commonRuntimeMcpPolicy("runtime"),
|
|
1269
|
+
build({
|
|
1270
|
+
userId,
|
|
1271
|
+
session,
|
|
1272
|
+
topicId,
|
|
1273
|
+
queryId,
|
|
1274
|
+
agent,
|
|
1275
|
+
cwd,
|
|
1276
|
+
model,
|
|
1277
|
+
currentUserPrompt,
|
|
1278
|
+
autoContinue,
|
|
1279
|
+
visualTools,
|
|
1280
|
+
fileDeliveryTools,
|
|
1281
|
+
peerBridge
|
|
1282
|
+
}) {
|
|
1283
|
+
if (!topicId || !agent)
|
|
1284
|
+
return null;
|
|
1285
|
+
return buildRuntimeMcpSpec(agent, {
|
|
1286
|
+
userId,
|
|
1287
|
+
topicId,
|
|
1288
|
+
topicTitle: session,
|
|
1289
|
+
queryId,
|
|
1290
|
+
cwd: cwd ?? resolveTopicWorkspaceDir(topicId),
|
|
1291
|
+
agent,
|
|
1292
|
+
model,
|
|
1293
|
+
currentUserPrompt,
|
|
1294
|
+
autoContinue,
|
|
1295
|
+
visualTools,
|
|
1296
|
+
fileDeliveryTools,
|
|
1297
|
+
peerBridge
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
},
|
|
1301
|
+
"token-stats": {
|
|
1302
|
+
...commonRuntimeMcpPolicy("token-stats"),
|
|
1303
|
+
build(ctx) {
|
|
1304
|
+
return buildBuiltinMcpServer("token-stats", ctx, () => buildStdioMcpServer(ctx.agent, TOKEN_STATS_SERVER, [`--user-id=${ctx.userId}`]));
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
task: {
|
|
1308
|
+
...commonRuntimeMcpPolicy("task"),
|
|
1309
|
+
build(ctx) {
|
|
1310
|
+
const { userId, session, topicId, queryId, agent, peerBridge } = ctx;
|
|
1311
|
+
if (peerBridge) {
|
|
1312
|
+
if (!topicId || !queryId)
|
|
1313
|
+
return null;
|
|
1314
|
+
const env = canonicalMcpBridgeEnv({
|
|
1315
|
+
surface: "task",
|
|
1316
|
+
userId,
|
|
1317
|
+
topicId,
|
|
1318
|
+
queryId,
|
|
1319
|
+
peerBridge
|
|
1320
|
+
});
|
|
1321
|
+
return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=task"], env) : null;
|
|
1322
|
+
}
|
|
1323
|
+
const args = [`--user-id=${userId}`, `--topic=${session}`];
|
|
1324
|
+
if (topicId)
|
|
1325
|
+
args.push(`--topic-id=${topicId}`);
|
|
1326
|
+
return buildBuiltinMcpServer("task", ctx, () => buildStdioMcpServer(agent, TASK_SERVER, args));
|
|
1327
|
+
}
|
|
1328
|
+
},
|
|
1329
|
+
"session-comm": {
|
|
1330
|
+
...commonRuntimeMcpPolicy("session-comm"),
|
|
1331
|
+
build(ctx) {
|
|
1332
|
+
const {
|
|
1333
|
+
userId,
|
|
1334
|
+
session,
|
|
1335
|
+
topicId,
|
|
1336
|
+
subagentParentTopicId,
|
|
1337
|
+
agent,
|
|
1338
|
+
depth = 0,
|
|
1339
|
+
silent,
|
|
1340
|
+
peerBridge
|
|
1341
|
+
} = ctx;
|
|
1342
|
+
const effectiveAgent = agent ?? FALLBACK_AGENT;
|
|
1343
|
+
const args = [
|
|
1344
|
+
`--user-id=${userId}`,
|
|
1345
|
+
`--topic=${session}`,
|
|
1346
|
+
...topicId ? [`--topic-id=${topicId}`] : [],
|
|
1347
|
+
...subagentParentTopicId ? [`--subagent-parent-topic-id=${subagentParentTopicId}`] : [],
|
|
1348
|
+
`--depth=${depth}`,
|
|
1349
|
+
`--agent=${effectiveAgent}`,
|
|
1350
|
+
...silent ? ["--reply-only=true"] : [],
|
|
1351
|
+
...peerBridge ? [`--peer-host-query-id=${peerBridge.hostQueryId}`] : []
|
|
1352
|
+
];
|
|
1353
|
+
return buildBuiltinMcpServer("session-comm", ctx, () => buildStdioMcpServer(effectiveAgent, SESSION_COMM_SERVER, args, peerBridge ? peerSessionBridgeIpcEnv() : undefined));
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
wiki: {
|
|
1357
|
+
...commonRuntimeMcpPolicy("wiki"),
|
|
1358
|
+
build(ctx) {
|
|
1359
|
+
const { userId, session, topicId, queryId, wikiTopicId, agent, peerBridge } = ctx;
|
|
1360
|
+
if (peerBridge) {
|
|
1361
|
+
if (!topicId || !queryId)
|
|
1362
|
+
return null;
|
|
1363
|
+
const env = canonicalMcpBridgeEnv({
|
|
1364
|
+
surface: "wiki",
|
|
1365
|
+
userId,
|
|
1366
|
+
topicId,
|
|
1367
|
+
queryId,
|
|
1368
|
+
peerBridge
|
|
1369
|
+
});
|
|
1370
|
+
return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=wiki"], env) : null;
|
|
1371
|
+
}
|
|
1372
|
+
const args = [`--user-id=${userId}`];
|
|
1373
|
+
const resolvedWikiTopicId = wikiTopicId ?? topicId ?? (session !== "dm" ? session : undefined);
|
|
1374
|
+
if (resolvedWikiTopicId)
|
|
1375
|
+
args.push(`--topic-id=${resolvedWikiTopicId}`);
|
|
1376
|
+
args.push("--surface=wiki");
|
|
1377
|
+
return buildBuiltinMcpServer("wiki", { ...ctx, wikiTopicId: resolvedWikiTopicId }, () => buildStdioMcpServer(agent, WIKI_SERVER, args));
|
|
1378
|
+
}
|
|
1379
|
+
},
|
|
1380
|
+
skills: {
|
|
1381
|
+
...commonRuntimeMcpPolicy("skills"),
|
|
1382
|
+
build(ctx) {
|
|
1383
|
+
const { userId, topicId, agent } = ctx;
|
|
1384
|
+
const args = [`--user-id=${userId}`, "--surface=skills"];
|
|
1385
|
+
if (topicId)
|
|
1386
|
+
args.push(`--topic-id=${topicId}`);
|
|
1387
|
+
return buildBuiltinMcpServer("skills", ctx, () => buildStdioMcpServer(agent, WIKI_SERVER, args));
|
|
1388
|
+
}
|
|
1389
|
+
},
|
|
1390
|
+
"system-health": {
|
|
1391
|
+
...commonRuntimeMcpPolicy("system-health"),
|
|
1392
|
+
build(ctx) {
|
|
1393
|
+
return buildBuiltinMcpServer("system-health", ctx, () => buildStdioMcpServer(ctx.agent, SYSTEM_HEALTH_SERVER, []));
|
|
1394
|
+
}
|
|
1395
|
+
},
|
|
1396
|
+
"background-bash": {
|
|
1397
|
+
...commonRuntimeMcpPolicy("background-bash"),
|
|
1398
|
+
build({ agent, bgBashPort, userId, topicId }) {
|
|
1399
|
+
if (bgBashPort === undefined || !topicId)
|
|
1400
|
+
return null;
|
|
1401
|
+
return backgroundBashTransport(agent, bgBashPort, userId, topicId);
|
|
1402
|
+
}
|
|
1403
|
+
},
|
|
1404
|
+
"agent-health": {
|
|
1405
|
+
...commonRuntimeMcpPolicy("agent-health"),
|
|
1406
|
+
build(ctx) {
|
|
1407
|
+
const { userId, agent } = ctx;
|
|
1408
|
+
const args = [`--user-id=${userId}`];
|
|
1409
|
+
return buildBuiltinMcpServer("agent-health", ctx, () => buildStdioMcpServer(agent, AGENT_HEALTH_SERVER, args));
|
|
1410
|
+
}
|
|
1411
|
+
},
|
|
1412
|
+
vault: {
|
|
1413
|
+
...commonRuntimeMcpPolicy("vault"),
|
|
1414
|
+
build(ctx) {
|
|
1415
|
+
const { userId, vaultUserId, agent } = ctx;
|
|
1416
|
+
const args = [`--user-id=${vaultUserId ?? userId}`];
|
|
1417
|
+
if (agent !== "codex")
|
|
1418
|
+
args.push("--list-only=true");
|
|
1419
|
+
return buildBuiltinMcpServer("vault", { ...ctx, userId: vaultUserId ?? userId }, () => buildStdioMcpServer(agent, VAULT_SERVER, args));
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
var allForumMcpServerNames = [];
|
|
1424
|
+
var requiredForumMcpServers = [];
|
|
1425
|
+
var REQUIRED_FORUM_MCP_SERVERS = requiredForumMcpServers;
|
|
1426
|
+
var optionalForumMcpServers = [];
|
|
1427
|
+
function refreshForumCatalogViews() {
|
|
1428
|
+
const { all, required, optional } = classifyForumMcpServers(MCP_CATALOG);
|
|
1429
|
+
allForumMcpServerNames.splice(0, allForumMcpServerNames.length, ...all);
|
|
1430
|
+
requiredForumMcpServers.splice(0, requiredForumMcpServers.length, ...required);
|
|
1431
|
+
optionalForumMcpServers.splice(0, optionalForumMcpServers.length, ...optional);
|
|
1432
|
+
}
|
|
1433
|
+
refreshForumCatalogViews();
|
|
1434
|
+
var nodeMcpEntries = [];
|
|
1435
|
+
function buildNodeMcpSpecs(agent, filter) {
|
|
1436
|
+
const out = {};
|
|
1437
|
+
for (const entry of nodeMcpEntries) {
|
|
1438
|
+
if (!filter(entry.key))
|
|
1439
|
+
continue;
|
|
1440
|
+
out[entry.key] = entry.kind === "http" ? longLivedHttpMcp(agent, entry.port) : {
|
|
1441
|
+
command: entry.command,
|
|
1442
|
+
args: entry.args ?? [],
|
|
1443
|
+
...entry.env ? { env: entry.env } : {}
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
return out;
|
|
1447
|
+
}
|
|
1448
|
+
function buildScope(scope, ctx, filter = () => true) {
|
|
1449
|
+
const out = {};
|
|
1450
|
+
for (const [name, entry] of Object.entries(MCP_CATALOG)) {
|
|
1451
|
+
if (!entry.scopes.includes(scope))
|
|
1452
|
+
continue;
|
|
1453
|
+
if (!filter(name))
|
|
1454
|
+
continue;
|
|
1455
|
+
const spec = entry.build(ctx);
|
|
1456
|
+
if (spec === null)
|
|
1457
|
+
continue;
|
|
1458
|
+
out[name] = spec;
|
|
1459
|
+
}
|
|
1460
|
+
if (scope !== "cron") {
|
|
1461
|
+
Object.assign(out, buildNodeMcpSpecs(ctx.agent, filter));
|
|
1462
|
+
}
|
|
1463
|
+
return out;
|
|
1464
|
+
}
|
|
1465
|
+
function getDmMcpServers(opts) {
|
|
1466
|
+
return buildScope("dm", {
|
|
1467
|
+
userId: opts.userId,
|
|
1468
|
+
session: "dm",
|
|
1469
|
+
agent: opts.agent,
|
|
1470
|
+
playwrightPort: opts.playwrightPort,
|
|
1471
|
+
playwrightCapability: opts.playwrightCapability
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
function getManagerMcpServers(opts) {
|
|
1475
|
+
if (!opts.topicId) {
|
|
1476
|
+
throw new Error("getManagerMcpServers: private General topicId is required");
|
|
1477
|
+
}
|
|
1478
|
+
const topicId = opts.topicId;
|
|
1479
|
+
return buildScope("manager", {
|
|
1480
|
+
userId: opts.userId,
|
|
1481
|
+
session: opts.session ?? "General",
|
|
1482
|
+
topicId,
|
|
1483
|
+
queryId: opts.queryId,
|
|
1484
|
+
wikiTopicId: opts.wikiTopicId ?? topicId,
|
|
1485
|
+
agent: opts.agent,
|
|
1486
|
+
cwd: opts.cwd,
|
|
1487
|
+
model: opts.model,
|
|
1488
|
+
currentUserPrompt: opts.currentUserPrompt,
|
|
1489
|
+
playwrightPort: opts.playwrightPort,
|
|
1490
|
+
playwrightCapability: opts.playwrightCapability,
|
|
1491
|
+
autoContinue: opts.autoContinue,
|
|
1492
|
+
visualTools: opts.visualTools,
|
|
1493
|
+
fileDeliveryTools: opts.fileDeliveryTools
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
function getForumMcpServers(opts) {
|
|
1497
|
+
const {
|
|
1498
|
+
userId,
|
|
1499
|
+
vaultUserId,
|
|
1500
|
+
session,
|
|
1501
|
+
topicId,
|
|
1502
|
+
subagentParentTopicId,
|
|
1503
|
+
queryId,
|
|
1504
|
+
wikiTopicId,
|
|
1505
|
+
agent,
|
|
1506
|
+
cwd,
|
|
1507
|
+
model,
|
|
1508
|
+
currentUserPrompt,
|
|
1509
|
+
playwrightPort,
|
|
1510
|
+
playwrightCapability,
|
|
1511
|
+
depth = 0,
|
|
1512
|
+
enabled = null,
|
|
1513
|
+
extra = {},
|
|
1514
|
+
silent = false,
|
|
1515
|
+
bgBashPort,
|
|
1516
|
+
autoContinue,
|
|
1517
|
+
visualTools,
|
|
1518
|
+
fileDeliveryTools,
|
|
1519
|
+
peerBridge
|
|
1520
|
+
} = opts;
|
|
1521
|
+
const filter = (name) => {
|
|
1522
|
+
if (silent && name === "task")
|
|
1523
|
+
return false;
|
|
1524
|
+
if (enabled === null)
|
|
1525
|
+
return true;
|
|
1526
|
+
return enabled.includes(name) || REQUIRED_FORUM_MCP_SERVERS.includes(name);
|
|
1527
|
+
};
|
|
1528
|
+
const base = buildScope("forum", {
|
|
1529
|
+
userId,
|
|
1530
|
+
vaultUserId,
|
|
1531
|
+
session,
|
|
1532
|
+
topicId,
|
|
1533
|
+
subagentParentTopicId,
|
|
1534
|
+
queryId,
|
|
1535
|
+
wikiTopicId,
|
|
1536
|
+
agent,
|
|
1537
|
+
cwd,
|
|
1538
|
+
model,
|
|
1539
|
+
currentUserPrompt,
|
|
1540
|
+
depth,
|
|
1541
|
+
playwrightPort,
|
|
1542
|
+
playwrightCapability,
|
|
1543
|
+
bgBashPort,
|
|
1544
|
+
autoContinue,
|
|
1545
|
+
visualTools,
|
|
1546
|
+
fileDeliveryTools,
|
|
1547
|
+
silent,
|
|
1548
|
+
peerBridge
|
|
1549
|
+
}, filter);
|
|
1550
|
+
return { ...base, ...extra };
|
|
1551
|
+
}
|
|
1552
|
+
function getCronMcpServers(opts) {
|
|
1553
|
+
return buildScope("cron", {
|
|
1554
|
+
userId: opts.userId,
|
|
1555
|
+
session: opts.session,
|
|
1556
|
+
topicId: opts.topicId,
|
|
1557
|
+
queryId: opts.queryId,
|
|
1558
|
+
wikiTopicId: opts.wikiTopicId ?? opts.topicId,
|
|
1559
|
+
agent: opts.agent,
|
|
1560
|
+
cwd: opts.cwd,
|
|
1561
|
+
model: opts.model,
|
|
1562
|
+
currentUserPrompt: opts.currentUserPrompt,
|
|
1563
|
+
playwrightPort: opts.playwrightPort,
|
|
1564
|
+
playwrightCapability: opts.playwrightCapability,
|
|
1565
|
+
autoContinue: false,
|
|
1566
|
+
visualTools: opts.visualTools,
|
|
1567
|
+
fileDeliveryTools: opts.fileDeliveryTools
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
function getMcpServersForQuery(opts) {
|
|
1571
|
+
if (opts.toolPolicy === "none")
|
|
1572
|
+
return {};
|
|
1573
|
+
if (opts.toolPolicy === "compaction-log") {
|
|
1574
|
+
const compactLog = opts.mcpExtra?.compact_log;
|
|
1575
|
+
return compactLog ? { compact_log: compactLog } : {};
|
|
1576
|
+
}
|
|
1577
|
+
if (opts.sessionType === "cron") {
|
|
1578
|
+
if (!opts.topicId)
|
|
1579
|
+
throw new Error("getMcpServersForQuery: cron sessionType requires topicId");
|
|
1580
|
+
return getCronMcpServers({
|
|
1581
|
+
userId: opts.userId || "local",
|
|
1582
|
+
session: opts.session || "cron",
|
|
1583
|
+
topicId: opts.topicId,
|
|
1584
|
+
queryId: opts.queryId,
|
|
1585
|
+
wikiTopicId: opts.wikiTopicId,
|
|
1586
|
+
agent: opts.agent,
|
|
1587
|
+
cwd: opts.cwd,
|
|
1588
|
+
model: opts.model,
|
|
1589
|
+
currentUserPrompt: opts.prompt,
|
|
1590
|
+
playwrightPort: opts.playwrightPort,
|
|
1591
|
+
playwrightCapability: opts.playwrightCapability,
|
|
1592
|
+
visualTools: opts.visualTools,
|
|
1593
|
+
fileDeliveryTools: opts.fileDeliveryTools
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
if (opts.sessionType === "dm" || opts.sessionType === "ephemeral") {
|
|
1597
|
+
return getDmMcpServers({
|
|
1598
|
+
userId: opts.userId || "local",
|
|
1599
|
+
agent: opts.agent,
|
|
1600
|
+
playwrightPort: opts.playwrightPort,
|
|
1601
|
+
playwrightCapability: opts.playwrightCapability
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
if (opts.sessionType === "manager") {
|
|
1605
|
+
return getManagerMcpServers({
|
|
1606
|
+
userId: opts.userId || "local",
|
|
1607
|
+
session: opts.session,
|
|
1608
|
+
topicId: opts.topicId,
|
|
1609
|
+
queryId: opts.queryId,
|
|
1610
|
+
wikiTopicId: opts.wikiTopicId,
|
|
1611
|
+
agent: opts.agent,
|
|
1612
|
+
cwd: opts.cwd,
|
|
1613
|
+
model: opts.model,
|
|
1614
|
+
currentUserPrompt: opts.prompt,
|
|
1615
|
+
playwrightPort: opts.playwrightPort,
|
|
1616
|
+
playwrightCapability: opts.playwrightCapability,
|
|
1617
|
+
autoContinue: opts.autoContinue,
|
|
1618
|
+
visualTools: opts.visualTools,
|
|
1619
|
+
fileDeliveryTools: opts.fileDeliveryTools
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
return getForumMcpServers({
|
|
1623
|
+
userId: opts.userId || "local",
|
|
1624
|
+
vaultUserId: opts.vaultUserId,
|
|
1625
|
+
session: opts.session || "default",
|
|
1626
|
+
topicId: opts.topicId,
|
|
1627
|
+
subagentParentTopicId: opts.subagentParentTopicId,
|
|
1628
|
+
queryId: opts.queryId,
|
|
1629
|
+
wikiTopicId: opts.wikiTopicId,
|
|
1630
|
+
agent: opts.agent,
|
|
1631
|
+
cwd: opts.cwd,
|
|
1632
|
+
model: opts.model,
|
|
1633
|
+
currentUserPrompt: opts.prompt,
|
|
1634
|
+
playwrightPort: opts.playwrightPort,
|
|
1635
|
+
playwrightCapability: opts.playwrightCapability,
|
|
1636
|
+
bgBashPort: opts.bgBashPort,
|
|
1637
|
+
autoContinue: opts.autoContinue,
|
|
1638
|
+
visualTools: opts.visualTools,
|
|
1639
|
+
fileDeliveryTools: opts.fileDeliveryTools,
|
|
1640
|
+
depth: opts.depth,
|
|
1641
|
+
enabled: opts.mcpEnabled,
|
|
1642
|
+
extra: opts.mcpExtra,
|
|
1643
|
+
silent: opts.silent,
|
|
1644
|
+
peerBridge: opts.peerBridge
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// ../../packages/core/src/storage/vault.ts
|
|
1649
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync4 } from "fs";
|
|
1650
|
+
import { join as join3 } from "path";
|
|
1651
|
+
|
|
1652
|
+
// ../../packages/core/src/storage/sqlite.ts
|
|
1653
|
+
var isBun = typeof process.versions.bun === "string";
|
|
1654
|
+
var Database;
|
|
1655
|
+
if (isBun) {
|
|
1656
|
+
({ Database } = await import("bun:sqlite"));
|
|
1657
|
+
} else {
|
|
1658
|
+
const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
|
|
1659
|
+
const { DatabaseSync } = await import(nodeSqliteSpecifier);
|
|
1660
|
+
|
|
1661
|
+
class NodeDatabase {
|
|
1662
|
+
#db;
|
|
1663
|
+
constructor(path, options = {}) {
|
|
1664
|
+
this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
|
|
1665
|
+
}
|
|
1666
|
+
query(sql) {
|
|
1667
|
+
return this.#db.prepare(sql);
|
|
1668
|
+
}
|
|
1669
|
+
prepare(sql) {
|
|
1670
|
+
return this.#db.prepare(sql);
|
|
1671
|
+
}
|
|
1672
|
+
exec(sql) {
|
|
1673
|
+
this.#db.exec(sql);
|
|
1674
|
+
}
|
|
1675
|
+
run(sql, ...params) {
|
|
1676
|
+
return this.#db.prepare(sql).run(...params);
|
|
1677
|
+
}
|
|
1678
|
+
transaction(fn) {
|
|
1679
|
+
const run = (begin) => (...args) => {
|
|
1680
|
+
this.#db.exec(begin);
|
|
1681
|
+
try {
|
|
1682
|
+
const result = fn(...args);
|
|
1683
|
+
this.#db.exec("COMMIT");
|
|
1684
|
+
return result;
|
|
1685
|
+
} catch (err) {
|
|
1686
|
+
this.#db.exec("ROLLBACK");
|
|
1687
|
+
throw err;
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
const tx = run("BEGIN");
|
|
1691
|
+
tx.deferred = run("BEGIN DEFERRED");
|
|
1692
|
+
tx.immediate = run("BEGIN IMMEDIATE");
|
|
1693
|
+
tx.exclusive = run("BEGIN EXCLUSIVE");
|
|
1694
|
+
return tx;
|
|
1695
|
+
}
|
|
1696
|
+
close() {
|
|
1697
|
+
this.#db.close();
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
Database = NodeDatabase;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// ../../packages/core/src/storage/vault-crypto-core.ts
|
|
1704
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes3 } from "crypto";
|
|
1705
|
+
var ENVELOPE_PREFIX = "otium-vault:v1:";
|
|
1706
|
+
var IV_BYTES = 12;
|
|
1707
|
+
var KEY_BYTES = 32;
|
|
1708
|
+
function encryptionKey(masterKey) {
|
|
1709
|
+
return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
|
|
1710
|
+
}
|
|
1711
|
+
function aad(userId, key) {
|
|
1712
|
+
return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
|
|
1713
|
+
}
|
|
1714
|
+
function isEncryptedVaultValue(value) {
|
|
1715
|
+
return value.startsWith(ENVELOPE_PREFIX);
|
|
1716
|
+
}
|
|
1717
|
+
function encryptVaultValueWithKey(userId, key, value, masterKey) {
|
|
1718
|
+
const iv = randomBytes3(IV_BYTES);
|
|
1719
|
+
const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
1720
|
+
cipher.setAAD(aad(userId, key));
|
|
1721
|
+
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
1722
|
+
const tag = cipher.getAuthTag();
|
|
1723
|
+
return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
|
|
1724
|
+
}
|
|
1725
|
+
function decryptVaultValueWithKey(userId, key, storedValue, masterKey) {
|
|
1726
|
+
if (!isEncryptedVaultValue(storedValue)) {
|
|
1727
|
+
return { value: storedValue, legacyPlaintext: true };
|
|
1728
|
+
}
|
|
1729
|
+
const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
|
|
1730
|
+
const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
|
|
1731
|
+
if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
|
|
1732
|
+
throw new Error("Invalid encrypted vault value");
|
|
1733
|
+
}
|
|
1734
|
+
const iv = Buffer.from(ivPart, "base64url");
|
|
1735
|
+
const ciphertext = Buffer.from(ciphertextPart, "base64url");
|
|
1736
|
+
const tag = Buffer.from(tagPart, "base64url");
|
|
1737
|
+
if (iv.length !== IV_BYTES || tag.length !== 16) {
|
|
1738
|
+
throw new Error("Invalid encrypted vault value");
|
|
1739
|
+
}
|
|
1740
|
+
const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
1741
|
+
decipher.setAAD(aad(userId, key));
|
|
1742
|
+
decipher.setAuthTag(tag);
|
|
1743
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
1744
|
+
return { value: plaintext.toString("utf8"), legacyPlaintext: false };
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// ../../packages/core/src/storage/vault-crypto.ts
|
|
1748
|
+
function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
|
|
1749
|
+
return encryptVaultValueWithKey(userId, key, value, masterKey);
|
|
1750
|
+
}
|
|
1751
|
+
function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
|
|
1752
|
+
return decryptVaultValueWithKey(userId, key, storedValue, masterKey);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// ../../packages/core/src/storage/vault.ts
|
|
1756
|
+
var vaultDb;
|
|
1757
|
+
var vaultMasterKey = VAULT_MASTER_KEY;
|
|
1758
|
+
function initializeVaultDatabase(database) {
|
|
1759
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
1760
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
1761
|
+
database.exec(`
|
|
1762
|
+
CREATE TABLE IF NOT EXISTS vault (
|
|
1763
|
+
user_id TEXT NOT NULL,
|
|
1764
|
+
key TEXT NOT NULL,
|
|
1765
|
+
value TEXT NOT NULL,
|
|
1766
|
+
description TEXT NOT NULL DEFAULT '',
|
|
1767
|
+
PRIMARY KEY (user_id, key)
|
|
1768
|
+
)
|
|
1769
|
+
`);
|
|
1770
|
+
{
|
|
1771
|
+
const cols = database.prepare("PRAGMA table_info(vault)").all();
|
|
1772
|
+
const uid = cols.find((c) => c.name === "user_id");
|
|
1773
|
+
if (uid && uid.type.toUpperCase() === "INTEGER") {
|
|
1774
|
+
database.exec("BEGIN");
|
|
1775
|
+
database.exec(`
|
|
1776
|
+
CREATE TABLE vault_migrated (
|
|
1777
|
+
user_id TEXT NOT NULL,
|
|
1778
|
+
key TEXT NOT NULL,
|
|
1779
|
+
value TEXT NOT NULL,
|
|
1780
|
+
description TEXT NOT NULL DEFAULT '',
|
|
1781
|
+
PRIMARY KEY (user_id, key)
|
|
1782
|
+
)
|
|
1783
|
+
`);
|
|
1784
|
+
database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
|
|
1785
|
+
database.exec("DROP TABLE vault");
|
|
1786
|
+
database.exec("ALTER TABLE vault_migrated RENAME TO vault");
|
|
1787
|
+
database.exec("COMMIT");
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
function openVaultDatabase(dataDir) {
|
|
1792
|
+
const vaultDir = join3(dataDir, "vault");
|
|
1793
|
+
const path = join3(vaultDir, "vault.db");
|
|
1794
|
+
mkdirSync4(vaultDir, { recursive: true, mode: 448 });
|
|
1795
|
+
const database = new Database(path, { create: true });
|
|
1796
|
+
chmodSync2(path, 384);
|
|
1797
|
+
initializeVaultDatabase(database);
|
|
1798
|
+
return database;
|
|
1799
|
+
}
|
|
1800
|
+
function activeVaultDatabase() {
|
|
1801
|
+
if (!vaultDb)
|
|
1802
|
+
vaultDb = openVaultDatabase(DATA_DIR);
|
|
1803
|
+
return vaultDb;
|
|
1804
|
+
}
|
|
1805
|
+
var VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
1806
|
+
function normalizeVaultKey(key) {
|
|
1807
|
+
return key.trim().toUpperCase();
|
|
1808
|
+
}
|
|
1809
|
+
function decryptRow(userId, key, storedValue) {
|
|
1810
|
+
const database = activeVaultDatabase();
|
|
1811
|
+
const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
|
|
1812
|
+
if (decoded.legacyPlaintext) {
|
|
1813
|
+
database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
|
|
1814
|
+
}
|
|
1815
|
+
return decoded.value;
|
|
1816
|
+
}
|
|
1817
|
+
function vaultListDecryptableValues(userId) {
|
|
1818
|
+
const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
1819
|
+
const entries = [];
|
|
1820
|
+
for (const row of rows) {
|
|
1821
|
+
try {
|
|
1822
|
+
entries.push({
|
|
1823
|
+
key: row.key,
|
|
1824
|
+
description: row.description,
|
|
1825
|
+
value: decryptRow(userId, row.key, row.value)
|
|
1826
|
+
});
|
|
1827
|
+
} catch {}
|
|
1828
|
+
}
|
|
1829
|
+
return entries;
|
|
1830
|
+
}
|
|
1831
|
+
function vaultGetValue(userId, key) {
|
|
1832
|
+
const normalizedKey = normalizeVaultKey(key);
|
|
1833
|
+
const row = activeVaultDatabase().prepare("SELECT key, value FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizedKey);
|
|
1834
|
+
return row ? decryptRow(userId, row.key, row.value) : undefined;
|
|
1835
|
+
}
|
|
1836
|
+
function vaultSubstituteDetailed(userId, text) {
|
|
1837
|
+
const entries = new Map;
|
|
1838
|
+
const usedKeys = new Set;
|
|
1839
|
+
const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
|
|
1840
|
+
const key = normalizeVaultKey(rawKey);
|
|
1841
|
+
if (!entries.has(key))
|
|
1842
|
+
entries.set(key, vaultGetValue(userId, key));
|
|
1843
|
+
const value = entries.get(key);
|
|
1844
|
+
if (value === undefined)
|
|
1845
|
+
return match;
|
|
1846
|
+
usedKeys.add(key);
|
|
1847
|
+
return value;
|
|
1848
|
+
});
|
|
1849
|
+
return { text: substituted, usedKeys: [...usedKeys] };
|
|
1850
|
+
}
|
|
1851
|
+
function encodedSecretForms(value) {
|
|
1852
|
+
const forms = new Set([
|
|
1853
|
+
value,
|
|
1854
|
+
encodeURIComponent(value),
|
|
1855
|
+
Buffer.from(value, "utf8").toString("base64"),
|
|
1856
|
+
Buffer.from(value, "utf8").toString("base64url"),
|
|
1857
|
+
Buffer.from(value, "utf8").toString("hex")
|
|
1858
|
+
]);
|
|
1859
|
+
forms.delete("");
|
|
1860
|
+
return [...forms].sort((a, b) => b.length - a.length);
|
|
1861
|
+
}
|
|
1862
|
+
function redactVaultSecrets(userId, text) {
|
|
1863
|
+
const candidates = vaultListDecryptableValues(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));
|
|
1864
|
+
if (candidates.length === 0)
|
|
1865
|
+
return text;
|
|
1866
|
+
const candidatesByFirstCharacter = new Map;
|
|
1867
|
+
for (const candidate of candidates) {
|
|
1868
|
+
const first = candidate.form[0];
|
|
1869
|
+
if (!first)
|
|
1870
|
+
continue;
|
|
1871
|
+
const bucket = candidatesByFirstCharacter.get(first) ?? [];
|
|
1872
|
+
bucket.push(candidate);
|
|
1873
|
+
candidatesByFirstCharacter.set(first, bucket);
|
|
1874
|
+
}
|
|
1875
|
+
let redacted = "";
|
|
1876
|
+
let offset = 0;
|
|
1877
|
+
while (offset < text.length) {
|
|
1878
|
+
const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
|
|
1879
|
+
if (!match) {
|
|
1880
|
+
redacted += text[offset];
|
|
1881
|
+
offset += 1;
|
|
1882
|
+
continue;
|
|
1883
|
+
}
|
|
1884
|
+
redacted += `[REDACTED:${match.key}]`;
|
|
1885
|
+
offset += match.form.length;
|
|
1886
|
+
}
|
|
1887
|
+
return redacted;
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// ../../packages/core/src/agents/execution-host.ts
|
|
1891
|
+
var defaultHost = {
|
|
1892
|
+
getMcpServersForQuery,
|
|
1893
|
+
redactVaultSecrets,
|
|
1894
|
+
substituteVaultSecrets: (userId, value) => vaultSubstituteDetailed(userId, value).text,
|
|
1895
|
+
referencesRuntimeSecretStorage,
|
|
1896
|
+
shouldRedirectVaultTool,
|
|
1897
|
+
claudeCodeExecutablePath: () => CLAUDE_EXECUTABLE,
|
|
1898
|
+
codexAuthFilePath
|
|
1899
|
+
};
|
|
1900
|
+
var hostRegistrations = [];
|
|
1901
|
+
var scopedHost = new AsyncLocalStorage;
|
|
1902
|
+
function activeHost() {
|
|
1903
|
+
const scoped = scopedHost.getStore();
|
|
1904
|
+
if (scoped)
|
|
1905
|
+
return scoped;
|
|
1906
|
+
const host = { ...defaultHost };
|
|
1907
|
+
for (const registration of hostRegistrations)
|
|
1908
|
+
Object.assign(host, registration.overrides);
|
|
1909
|
+
return host;
|
|
1910
|
+
}
|
|
1911
|
+
function hostedCodexAuthFilePath() {
|
|
1912
|
+
return activeHost().codexAuthFilePath();
|
|
1913
|
+
}
|
|
1914
|
+
function hostedCodexHomePath() {
|
|
1915
|
+
return dirname3(hostedCodexAuthFilePath());
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// ../../packages/core/src/agents/rollout/codex.ts
|
|
1919
|
+
function codexSessionsDir() {
|
|
1920
|
+
return join4(hostedCodexHomePath(), "sessions");
|
|
1921
|
+
}
|
|
1922
|
+
var _shellCache = null;
|
|
1923
|
+
function loadCodexShell() {
|
|
1924
|
+
if (_shellCache)
|
|
1925
|
+
return _shellCache;
|
|
1926
|
+
const raw = readFileSync4(join4(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
|
|
1927
|
+
const lines = parseJsonlText(raw);
|
|
1928
|
+
if (lines.length < 5) {
|
|
1929
|
+
throw new Error(`loadCodexShell: expected >=5 entries in codex-shell.jsonl, got ${lines.length}`);
|
|
1930
|
+
}
|
|
1931
|
+
_shellCache = {
|
|
1932
|
+
sessionMeta: lines[0],
|
|
1933
|
+
taskStarted: lines[1],
|
|
1934
|
+
developerSetup: lines[2],
|
|
1935
|
+
envContext: lines[3],
|
|
1936
|
+
turnContext: lines[4]
|
|
1937
|
+
};
|
|
1938
|
+
return _shellCache;
|
|
1939
|
+
}
|
|
1940
|
+
function uuidv7() {
|
|
1941
|
+
const ts = Date.now();
|
|
1942
|
+
const tsHex = ts.toString(16).padStart(12, "0");
|
|
1943
|
+
const rand = randomBytes4(10);
|
|
1944
|
+
rand[0] = rand[0] & 15 | 112;
|
|
1945
|
+
rand[2] = rand[2] & 63 | 128;
|
|
1946
|
+
return [
|
|
1947
|
+
tsHex.slice(0, 8),
|
|
1948
|
+
tsHex.slice(8, 12),
|
|
1949
|
+
rand.slice(0, 2).toString("hex"),
|
|
1950
|
+
rand.slice(2, 4).toString("hex"),
|
|
1951
|
+
rand.slice(4, 10).toString("hex")
|
|
1952
|
+
].join("-");
|
|
1953
|
+
}
|
|
1954
|
+
function patchDeveloperSetup(developerSetup) {
|
|
1955
|
+
const payload = developerSetup.payload;
|
|
1956
|
+
if (!payload)
|
|
1957
|
+
return;
|
|
1958
|
+
payload.content = [
|
|
1959
|
+
{
|
|
1960
|
+
type: "input_text",
|
|
1961
|
+
text: [
|
|
1962
|
+
"<permissions instructions>",
|
|
1963
|
+
"Filesystem sandboxing is disabled for this runtime turn (`danger-full-access`).",
|
|
1964
|
+
"Approval policy is `never`.",
|
|
1965
|
+
"</permissions instructions>"
|
|
1966
|
+
].join(`
|
|
1967
|
+
`)
|
|
1968
|
+
}
|
|
1969
|
+
];
|
|
1970
|
+
}
|
|
1971
|
+
function patchEnvContext(envContext, cwd, currentDate, timezone) {
|
|
1972
|
+
const payload = envContext.payload;
|
|
1973
|
+
if (!payload)
|
|
1974
|
+
return;
|
|
1975
|
+
const content = payload.content;
|
|
1976
|
+
if (!Array.isArray(content))
|
|
1977
|
+
return;
|
|
1978
|
+
for (const block of content) {
|
|
1979
|
+
if (block.type === "input_text" && typeof block.text === "string") {
|
|
1980
|
+
block.text = block.text.replace(/<cwd>[^<]*<\/cwd>/, `<cwd>${cwd}</cwd>`).replace(/<current_date>[^<]*<\/current_date>/, `<current_date>${currentDate}</current_date>`).replace(/<timezone>[^<]*<\/timezone>/, `<timezone>${timezone}</timezone>`);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
function codexRolloutPath(threadId, fallback) {
|
|
1985
|
+
const threadCreatedAt = decodeUuidV7Timestamp(threadId);
|
|
1986
|
+
const createdAt = threadCreatedAt === null ? fallback : new Date(threadCreatedAt);
|
|
1987
|
+
const yyyy = String(createdAt.getFullYear());
|
|
1988
|
+
const mm = String(createdAt.getMonth() + 1).padStart(2, "0");
|
|
1989
|
+
const dd = String(createdAt.getDate()).padStart(2, "0");
|
|
1990
|
+
const hh = String(createdAt.getHours()).padStart(2, "0");
|
|
1991
|
+
const min = String(createdAt.getMinutes()).padStart(2, "0");
|
|
1992
|
+
const ss = String(createdAt.getSeconds()).padStart(2, "0");
|
|
1993
|
+
const dir = join4(codexSessionsDir(), yyyy, mm, dd);
|
|
1994
|
+
return join4(dir, `rollout-${yyyy}-${mm}-${dd}T${hh}-${min}-${ss}-${threadId}.jsonl`);
|
|
1995
|
+
}
|
|
1996
|
+
function canonicalFilePath(path) {
|
|
1997
|
+
const absolute = resolve4(path);
|
|
1998
|
+
try {
|
|
1999
|
+
return realpathSync2(absolute);
|
|
2000
|
+
} catch {
|
|
2001
|
+
try {
|
|
2002
|
+
return join4(realpathSync2(dirname4(absolute)), basename(absolute));
|
|
2003
|
+
} catch {
|
|
2004
|
+
return absolute;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
function changedPatchLines(value) {
|
|
2009
|
+
const removed = [];
|
|
2010
|
+
const added = [];
|
|
2011
|
+
const diffPreview = [];
|
|
2012
|
+
let oldLine = 0;
|
|
2013
|
+
let newLine = 0;
|
|
2014
|
+
let sawHunk = false;
|
|
2015
|
+
for (const line of value.split(`
|
|
2016
|
+
`)) {
|
|
2017
|
+
const hunk = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
|
|
2018
|
+
if (hunk) {
|
|
2019
|
+
if (sawHunk)
|
|
2020
|
+
diffPreview.push("\u2026");
|
|
2021
|
+
sawHunk = true;
|
|
2022
|
+
oldLine = Number.parseInt(hunk[1] ?? "0", 10);
|
|
2023
|
+
newLine = Number.parseInt(hunk[2] ?? "0", 10);
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
if (!sawHunk && (line.startsWith("--- ") || line.startsWith("+++ ")))
|
|
2027
|
+
continue;
|
|
2028
|
+
if (line.startsWith("\\ No newline"))
|
|
2029
|
+
continue;
|
|
2030
|
+
if (line.startsWith("-")) {
|
|
2031
|
+
removed.push(line.slice(1));
|
|
2032
|
+
diffPreview.push(`${oldLine} -${line.slice(1)}`);
|
|
2033
|
+
oldLine += 1;
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
if (line.startsWith("+")) {
|
|
2037
|
+
added.push(line.slice(1));
|
|
2038
|
+
diffPreview.push(`${newLine} +${line.slice(1)}`);
|
|
2039
|
+
newLine += 1;
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
if (line.startsWith(" ")) {
|
|
2043
|
+
diffPreview.push(`${newLine} ${line.slice(1)}`);
|
|
2044
|
+
oldLine += 1;
|
|
2045
|
+
newLine += 1;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
return {
|
|
2049
|
+
...removed.length > 0 ? { before: removed.join(`
|
|
2050
|
+
`) } : {},
|
|
2051
|
+
...added.length > 0 ? { after: added.join(`
|
|
2052
|
+
`) } : {},
|
|
2053
|
+
...diffPreview.length > 0 ? { diffPreview: diffPreview.join(`
|
|
2054
|
+
`) } : {}
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
function extractLatestCodexPatchPreview(jsonl, expectedPaths, consumedCallIds = new Set, expectedCallId) {
|
|
2058
|
+
const expected = expectedPaths.map(canonicalFilePath);
|
|
2059
|
+
const lines = jsonl.trimEnd().split(`
|
|
2060
|
+
`);
|
|
2061
|
+
for (let index = lines.length - 1;index >= 0; index -= 1) {
|
|
2062
|
+
try {
|
|
2063
|
+
const entry = JSON.parse(lines[index] ?? "");
|
|
2064
|
+
if (entry.type !== "event_msg" || entry.payload?.type !== "patch_apply_end")
|
|
2065
|
+
continue;
|
|
2066
|
+
const callId = entry.payload.call_id;
|
|
2067
|
+
const rawChanges = entry.payload.changes;
|
|
2068
|
+
if (!callId || consumedCallIds.has(callId) || expectedCallId !== undefined && callId !== expectedCallId || !rawChanges) {
|
|
2069
|
+
continue;
|
|
2070
|
+
}
|
|
2071
|
+
const entries = Object.entries(rawChanges);
|
|
2072
|
+
const matched = expected.map((path) => entries.find(([candidate]) => canonicalFilePath(candidate) === path));
|
|
2073
|
+
const complete = matched.filter((change) => change !== undefined);
|
|
2074
|
+
if (complete.length !== expected.length)
|
|
2075
|
+
continue;
|
|
2076
|
+
return {
|
|
2077
|
+
callId,
|
|
2078
|
+
changes: complete.map(([path, change]) => {
|
|
2079
|
+
if (typeof change.unified_diff === "string") {
|
|
2080
|
+
return { path, ...changedPatchLines(change.unified_diff) };
|
|
2081
|
+
}
|
|
2082
|
+
const content = typeof change.content === "string" ? change.content.replace(/\n$/, "") : undefined;
|
|
2083
|
+
return {
|
|
2084
|
+
path,
|
|
2085
|
+
...change.type === "delete" && content !== undefined ? { before: content } : {},
|
|
2086
|
+
...change.type === "add" && content !== undefined ? { after: content } : {}
|
|
2087
|
+
};
|
|
2088
|
+
})
|
|
2089
|
+
};
|
|
2090
|
+
} catch {}
|
|
2091
|
+
}
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
function extractCodexPatchCallIds(jsonl) {
|
|
2095
|
+
const callIds = [];
|
|
2096
|
+
for (const line of jsonl.trimEnd().split(`
|
|
2097
|
+
`)) {
|
|
2098
|
+
try {
|
|
2099
|
+
const entry = JSON.parse(line);
|
|
2100
|
+
if (entry.type === "event_msg" && entry.payload?.type === "patch_apply_end" && typeof entry.payload.call_id === "string") {
|
|
2101
|
+
callIds.push(entry.payload.call_id);
|
|
2102
|
+
}
|
|
2103
|
+
} catch {}
|
|
2104
|
+
}
|
|
2105
|
+
return callIds;
|
|
2106
|
+
}
|
|
2107
|
+
function readCodexPatchCallIds(threadId) {
|
|
2108
|
+
const path = latestCodexRolloutPath(threadId);
|
|
2109
|
+
if (!path)
|
|
2110
|
+
return [];
|
|
2111
|
+
try {
|
|
2112
|
+
return extractCodexPatchCallIds(readFileSync4(path, "utf8"));
|
|
2113
|
+
} catch (error) {
|
|
2114
|
+
logger.debug({ error, threadId }, "codex patch ids: rollout read failed");
|
|
2115
|
+
return [];
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
function readLatestCodexPatchPreview(threadId, expectedPaths, consumedCallIds = new Set, expectedCallId) {
|
|
2119
|
+
const path = latestCodexRolloutPath(threadId);
|
|
2120
|
+
if (!path)
|
|
2121
|
+
return;
|
|
2122
|
+
try {
|
|
2123
|
+
return extractLatestCodexPatchPreview(readFileSync4(path, "utf8"), expectedPaths, consumedCallIds, expectedCallId);
|
|
2124
|
+
} catch (error) {
|
|
2125
|
+
logger.debug({ error, threadId }, "codex patch preview: rollout read failed");
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
function extractLatestCodexContextUsage(jsonl) {
|
|
2130
|
+
const lines = jsonl.trimEnd().split(`
|
|
2131
|
+
`);
|
|
2132
|
+
for (let index = lines.length - 1;index >= 0; index -= 1) {
|
|
2133
|
+
try {
|
|
2134
|
+
const entry = JSON.parse(lines[index] ?? "");
|
|
2135
|
+
if (entry.type !== "event_msg" || entry.payload?.type !== "token_count")
|
|
2136
|
+
continue;
|
|
2137
|
+
const contextTokens = entry.payload.info?.last_token_usage?.total_tokens;
|
|
2138
|
+
const contextWindow = entry.payload.info?.model_context_window;
|
|
2139
|
+
if (typeof contextTokens === "number" && Number.isFinite(contextTokens) && contextTokens >= 0 && typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0) {
|
|
2140
|
+
return { contextTokens, contextWindow };
|
|
2141
|
+
}
|
|
2142
|
+
} catch {}
|
|
2143
|
+
}
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
function readLatestCodexContextUsage(threadId) {
|
|
2147
|
+
const path = latestCodexRolloutPath(threadId);
|
|
2148
|
+
if (!path)
|
|
2149
|
+
return;
|
|
2150
|
+
try {
|
|
2151
|
+
return extractLatestCodexContextUsage(readFileSync4(path, "utf8"));
|
|
2152
|
+
} catch (error) {
|
|
2153
|
+
logger.debug({ error, threadId }, "codex context usage: rollout read failed");
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
function migrateCodexRolloutNativeMultiAgentMetadata(threadId) {
|
|
2158
|
+
const path = latestCodexRolloutPath(threadId);
|
|
2159
|
+
if (!path)
|
|
2160
|
+
return false;
|
|
2161
|
+
try {
|
|
2162
|
+
const entries = parseJsonlText(readFileSync4(path, "utf8"));
|
|
2163
|
+
let changed = false;
|
|
2164
|
+
for (const entry of entries) {
|
|
2165
|
+
if (entry.type !== "session_meta" && entry.type !== "turn_context")
|
|
2166
|
+
continue;
|
|
2167
|
+
if (!entry.payload)
|
|
2168
|
+
entry.payload = {};
|
|
2169
|
+
if (entry.payload.multi_agent_version !== "disabled") {
|
|
2170
|
+
entry.payload.multi_agent_version = "disabled";
|
|
2171
|
+
changed = true;
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
if (changed) {
|
|
2175
|
+
writeJsonlFile(path, entries);
|
|
2176
|
+
logger.info({ threadId, path }, "codex rollout native multi-agent metadata disabled");
|
|
2177
|
+
}
|
|
2178
|
+
return changed;
|
|
2179
|
+
} catch (error) {
|
|
2180
|
+
logger.error({ error, threadId, path }, "codex rollout native multi-agent migration failed");
|
|
2181
|
+
throw new Error(`Failed to migrate Codex rollout '${threadId}'`, { cause: error });
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
function latestCodexRolloutPath(threadId) {
|
|
2185
|
+
const sessionsDir = codexSessionsDir();
|
|
2186
|
+
const candidates = [];
|
|
2187
|
+
const buckets = candidateDateBuckets(threadId);
|
|
2188
|
+
try {
|
|
2189
|
+
if (buckets) {
|
|
2190
|
+
for (const bucket of buckets) {
|
|
2191
|
+
const dir = join4(sessionsDir, bucket);
|
|
2192
|
+
if (!existsSync3(dir))
|
|
2193
|
+
continue;
|
|
2194
|
+
const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
|
|
2195
|
+
for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
|
|
2196
|
+
candidates.push(join4(dir, rel));
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
if (candidates.length === 0) {
|
|
2201
|
+
const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
|
|
2202
|
+
for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
|
|
2203
|
+
candidates.push(join4(sessionsDir, rel));
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
return candidates.sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs)[0];
|
|
2207
|
+
} catch (error) {
|
|
2208
|
+
logger.debug({ error, threadId }, "codex rollout lookup failed");
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
function writeCodexRollout(opts) {
|
|
2213
|
+
const threadId = opts.threadId ?? uuidv7();
|
|
2214
|
+
assertUuidLike("threadId", threadId);
|
|
2215
|
+
ensureCwdExists(opts.cwd);
|
|
2216
|
+
const pairs = opts.pairs ?? extractChatPairs(opts.entries ?? []);
|
|
2217
|
+
const now = new Date;
|
|
2218
|
+
const tsIso = now.toISOString();
|
|
2219
|
+
const currentDate = [
|
|
2220
|
+
now.getFullYear(),
|
|
2221
|
+
String(now.getMonth() + 1).padStart(2, "0"),
|
|
2222
|
+
String(now.getDate()).padStart(2, "0")
|
|
2223
|
+
].join("-");
|
|
2224
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || process.env.TZ || "UTC";
|
|
2225
|
+
const shell = loadCodexShell();
|
|
2226
|
+
const sessionMeta = clone(shell.sessionMeta);
|
|
2227
|
+
sessionMeta.payload.id = threadId;
|
|
2228
|
+
sessionMeta.payload.timestamp = tsIso;
|
|
2229
|
+
sessionMeta.payload.cwd = opts.cwd;
|
|
2230
|
+
sessionMeta.payload.multi_agent_version = "disabled";
|
|
2231
|
+
sessionMeta.timestamp = tsIso;
|
|
2232
|
+
const taskStarted = clone(shell.taskStarted);
|
|
2233
|
+
taskStarted.timestamp = tsIso;
|
|
2234
|
+
const developerSetup = clone(shell.developerSetup);
|
|
2235
|
+
developerSetup.timestamp = tsIso;
|
|
2236
|
+
patchDeveloperSetup(developerSetup);
|
|
2237
|
+
const envContext = clone(shell.envContext);
|
|
2238
|
+
envContext.timestamp = tsIso;
|
|
2239
|
+
patchEnvContext(envContext, opts.cwd, currentDate, timezone);
|
|
2240
|
+
const turnContext = clone(shell.turnContext);
|
|
2241
|
+
turnContext.timestamp = tsIso;
|
|
2242
|
+
const turnPayload = turnContext.payload;
|
|
2243
|
+
turnPayload.cwd = opts.cwd;
|
|
2244
|
+
turnPayload.current_date = currentDate;
|
|
2245
|
+
turnPayload.timezone = timezone;
|
|
2246
|
+
turnPayload.approval_policy = "never";
|
|
2247
|
+
turnPayload.sandbox_policy = { type: "danger-full-access" };
|
|
2248
|
+
turnPayload.multi_agent_version = "disabled";
|
|
2249
|
+
delete turnPayload.permission_profile;
|
|
2250
|
+
if (opts.model)
|
|
2251
|
+
turnPayload.model = opts.model;
|
|
2252
|
+
const collaborationMode = turnPayload.collaboration_mode;
|
|
2253
|
+
const collaborationSettings = collaborationMode?.settings;
|
|
2254
|
+
if (collaborationSettings) {
|
|
2255
|
+
if (opts.model)
|
|
2256
|
+
collaborationSettings.model = opts.model;
|
|
2257
|
+
if (opts.effort !== undefined)
|
|
2258
|
+
collaborationSettings.reasoning_effort = opts.effort;
|
|
2259
|
+
}
|
|
2260
|
+
const lines = [sessionMeta, taskStarted, developerSetup, envContext, turnContext];
|
|
2261
|
+
for (const pair of pairs) {
|
|
2262
|
+
lines.push({
|
|
2263
|
+
timestamp: tsIso,
|
|
2264
|
+
type: "response_item",
|
|
2265
|
+
payload: {
|
|
2266
|
+
type: "message",
|
|
2267
|
+
role: "user",
|
|
2268
|
+
content: [{ type: "input_text", text: pair.userText }]
|
|
2269
|
+
}
|
|
2270
|
+
});
|
|
2271
|
+
lines.push({
|
|
2272
|
+
timestamp: tsIso,
|
|
2273
|
+
type: "event_msg",
|
|
2274
|
+
payload: {
|
|
2275
|
+
type: "user_message",
|
|
2276
|
+
message: pair.userText,
|
|
2277
|
+
images: [],
|
|
2278
|
+
local_images: [],
|
|
2279
|
+
text_elements: []
|
|
2280
|
+
}
|
|
2281
|
+
});
|
|
2282
|
+
lines.push({
|
|
2283
|
+
timestamp: tsIso,
|
|
2284
|
+
type: "event_msg",
|
|
2285
|
+
payload: {
|
|
2286
|
+
type: "agent_message",
|
|
2287
|
+
message: pair.assistantText,
|
|
2288
|
+
phase: "final_answer",
|
|
2289
|
+
memory_citation: null
|
|
2290
|
+
}
|
|
2291
|
+
});
|
|
2292
|
+
lines.push({
|
|
2293
|
+
timestamp: tsIso,
|
|
2294
|
+
type: "response_item",
|
|
2295
|
+
payload: {
|
|
2296
|
+
type: "message",
|
|
2297
|
+
role: "assistant",
|
|
2298
|
+
content: [{ type: "output_text", text: pair.assistantText }],
|
|
2299
|
+
phase: "final_answer"
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
lines.push({
|
|
2304
|
+
timestamp: tsIso,
|
|
2305
|
+
type: "event_msg",
|
|
2306
|
+
payload: { type: "task_complete" }
|
|
2307
|
+
});
|
|
2308
|
+
const path = codexRolloutPath(threadId, now);
|
|
2309
|
+
if (opts.threadId) {
|
|
2310
|
+
sweepPriorRolloutsForThread(opts.threadId);
|
|
2311
|
+
}
|
|
2312
|
+
writeJsonlFile(path, lines);
|
|
2313
|
+
logger.info({ threadId, path, pairs: pairs.length }, "writeCodexRollout: synthetic rollout placed");
|
|
2314
|
+
return { threadId, rolloutPath: path };
|
|
2315
|
+
}
|
|
2316
|
+
function sweepPriorRolloutsForThread(threadId) {
|
|
2317
|
+
const sessionsDir = codexSessionsDir();
|
|
2318
|
+
const buckets = candidateDateBuckets(threadId);
|
|
2319
|
+
if (!buckets) {
|
|
2320
|
+
sweepPriorRolloutsFullTree(threadId, sessionsDir);
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
for (const bucket of buckets) {
|
|
2324
|
+
const dir = join4(sessionsDir, bucket);
|
|
2325
|
+
if (!existsSync3(dir))
|
|
2326
|
+
continue;
|
|
2327
|
+
try {
|
|
2328
|
+
const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
|
|
2329
|
+
for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
|
|
2330
|
+
const fullPath = join4(dir, rel);
|
|
2331
|
+
try {
|
|
2332
|
+
unlinkSync3(fullPath);
|
|
2333
|
+
} catch (e) {
|
|
2334
|
+
if (e?.code !== "ENOENT") {
|
|
2335
|
+
logger.warn({ err: e, path: fullPath }, "writeCodexRollout: prior-rollout unlink failed (continuing)");
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
} catch (e) {
|
|
2340
|
+
logger.warn({ err: e, threadId, bucket }, "writeCodexRollout: prior-rollout bucket scan failed (continuing)");
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
function sweepPriorRolloutsFullTree(threadId, sessionsDir) {
|
|
2345
|
+
try {
|
|
2346
|
+
const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
|
|
2347
|
+
for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
|
|
2348
|
+
const fullPath = join4(sessionsDir, rel);
|
|
2349
|
+
try {
|
|
2350
|
+
unlinkSync3(fullPath);
|
|
2351
|
+
} catch (e) {
|
|
2352
|
+
if (e?.code !== "ENOENT") {
|
|
2353
|
+
logger.warn({ err: e, path: fullPath }, "writeCodexRollout: prior-rollout unlink failed (continuing)");
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
} catch (e) {
|
|
2358
|
+
logger.warn({ err: e, threadId }, "writeCodexRollout: full-tree prior-rollout sweep failed (continuing)");
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
var MAX_DAY_SPAN = 30;
|
|
2362
|
+
var DAY_MS = 86400000;
|
|
2363
|
+
var LOCAL_DATE_SKEW_DAYS = 1;
|
|
2364
|
+
function candidateDateBuckets(threadId) {
|
|
2365
|
+
const tidMs = decodeUuidV7Timestamp(threadId);
|
|
2366
|
+
if (tidMs === null)
|
|
2367
|
+
return null;
|
|
2368
|
+
const nowMs = Date.now();
|
|
2369
|
+
const tidDay = Math.floor(tidMs / DAY_MS);
|
|
2370
|
+
const todayDay = Math.floor(nowMs / DAY_MS);
|
|
2371
|
+
const minDay = Math.min(tidDay, todayDay);
|
|
2372
|
+
const maxDay = Math.max(tidDay, todayDay);
|
|
2373
|
+
const span = maxDay - minDay;
|
|
2374
|
+
if (span > MAX_DAY_SPAN)
|
|
2375
|
+
return null;
|
|
2376
|
+
const buckets = new Set;
|
|
2377
|
+
for (let d = minDay - LOCAL_DATE_SKEW_DAYS;d <= maxDay + LOCAL_DATE_SKEW_DAYS; d++) {
|
|
2378
|
+
buckets.add(formatDateBucket(new Date(d * DAY_MS)));
|
|
2379
|
+
}
|
|
2380
|
+
return buckets;
|
|
2381
|
+
}
|
|
2382
|
+
function decodeUuidV7Timestamp(threadId) {
|
|
2383
|
+
if (typeof threadId !== "string")
|
|
2384
|
+
return null;
|
|
2385
|
+
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);
|
|
2386
|
+
if (!m)
|
|
2387
|
+
return null;
|
|
2388
|
+
const hex = `${m[1]}${m[2]}`;
|
|
2389
|
+
const ms = Number.parseInt(hex, 16);
|
|
2390
|
+
if (!Number.isFinite(ms))
|
|
2391
|
+
return null;
|
|
2392
|
+
const MIN = 1577836800000;
|
|
2393
|
+
const MAX = 4102444800000;
|
|
2394
|
+
if (ms < MIN || ms > MAX)
|
|
2395
|
+
return null;
|
|
2396
|
+
return ms;
|
|
2397
|
+
}
|
|
2398
|
+
function formatDateBucket(d) {
|
|
2399
|
+
const yyyy = d.getUTCFullYear();
|
|
2400
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
2401
|
+
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
2402
|
+
return `${yyyy}/${mm}/${dd}`;
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
export { __require, logger, CLAUDE_EFFORT_VALUES, CODEX_EFFORT_VALUES, MAESTRO_EFFORT_VALUES, MODEL_SONNET, MODEL_OPUS, MODEL_FABLE, configureRolloutHost, assertUuidLike, ensureCwdExists, extractChatPairs, encodeClaudeCwd, writeClaudeRollout, repairPoisonedRollout, hostedCodexHomePath, extractLatestCodexPatchPreview, extractCodexPatchCallIds, readCodexPatchCallIds, readLatestCodexPatchPreview, extractLatestCodexContextUsage, readLatestCodexContextUsage, migrateCodexRolloutNativeMultiAgentMetadata, latestCodexRolloutPath, writeCodexRollout, decodeUuidV7Timestamp };
|
|
2406
|
+
|
|
2407
|
+
//# debugId=3E746F752A616D9C64756E2164756E21
|