negotium 0.1.28 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/agent-helpers.js +972 -506
  2. package/dist/agent-helpers.js.map +18 -17
  3. package/dist/background-bash.js +60 -5
  4. package/dist/background-bash.js.map +4 -4
  5. package/dist/{chunk-7azk83mw.js → chunk-en2s6xnh.js} +60 -5
  6. package/dist/chunk-en2s6xnh.js.map +18 -0
  7. package/dist/cron.js +3797 -3224
  8. package/dist/cron.js.map +24 -24
  9. package/dist/hosted-agent.js +527 -71
  10. package/dist/hosted-agent.js.map +13 -12
  11. package/dist/main.js +2759 -1665
  12. package/dist/main.js.map +30 -30
  13. package/dist/mcp-factories.js +58 -3
  14. package/dist/mcp-factories.js.map +4 -4
  15. package/dist/prompts.js +58 -3
  16. package/dist/prompts.js.map +4 -4
  17. package/dist/registry.js +1 -1
  18. package/dist/rollout.js +1 -1
  19. package/dist/runtime/scripts/browser-passkey-policy.mjs +15 -4
  20. package/dist/runtime/scripts/browser-vault-transform.mjs +5 -1
  21. package/dist/runtime/scripts/mcp-patchright-http.mjs +241 -26
  22. package/dist/runtime/src/agents/api-topic-agent-switch.ts +1 -1
  23. package/dist/runtime/src/agents/auth-check.ts +36 -10
  24. package/dist/runtime/src/agents/claude-provider.ts +6 -11
  25. package/dist/runtime/src/agents/codex-native-multi-agent.ts +159 -42
  26. package/dist/runtime/src/agents/codex-provider.ts +198 -8
  27. package/dist/runtime/src/agents/maestro-provider.ts +26 -0
  28. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +1 -1
  29. package/dist/runtime/src/agents/rollout/codex.ts +198 -2
  30. package/dist/runtime/src/agents/rollout/index.ts +6 -0
  31. package/dist/runtime/src/agents/self-config-core.ts +1 -1
  32. package/dist/runtime/src/agents/tool-format.ts +228 -15
  33. package/dist/runtime/src/agents/topic-agent-switch.ts +1 -1
  34. package/dist/runtime/src/application/switch-topic-model.ts +1 -1
  35. package/dist/runtime/src/bus.ts +21 -3
  36. package/dist/runtime/src/mcp/browser-sse-proxy-server.ts +48 -0
  37. package/dist/runtime/src/platform/config.ts +80 -4
  38. package/dist/runtime/src/platform/mcp-config.ts +13 -9
  39. package/dist/runtime/src/platform/playwright/manager.ts +24 -14
  40. package/dist/runtime/src/runtime/turn-runner.ts +11 -5
  41. package/dist/runtime/src/types/api.ts +2 -0
  42. package/dist/runtime/src/types.ts +2 -0
  43. package/dist/runtime/src/version.ts +1 -1
  44. package/dist/runtime-helpers.js +58 -3
  45. package/dist/runtime-helpers.js.map +4 -4
  46. package/dist/storage.js.map +1 -1
  47. package/dist/types/packages/core/src/agents/auth-check.d.ts +3 -2
  48. package/dist/types/packages/core/src/agents/codex-native-multi-agent.d.ts +6 -4
  49. package/dist/types/packages/core/src/agents/maestro-provider.d.ts +13 -0
  50. package/dist/types/packages/core/src/agents/rollout/codex.d.ts +24 -0
  51. package/dist/types/packages/core/src/agents/rollout/index.d.ts +2 -2
  52. package/dist/types/packages/core/src/agents/tool-format.d.ts +16 -9
  53. package/dist/types/packages/core/src/bus.d.ts +2 -2
  54. package/dist/types/packages/core/src/platform/config.d.ts +13 -1
  55. package/dist/types/packages/core/src/types/api.d.ts +2 -0
  56. package/dist/types/packages/core/src/types.d.ts +2 -0
  57. package/dist/types/packages/core/src/version.d.ts +1 -1
  58. package/dist/vault.js +58 -3
  59. package/dist/vault.js.map +4 -4
  60. package/install-browser-rs.mjs +77 -0
  61. package/package.json +6 -3
  62. package/dist/chunk-7azk83mw.js.map +0 -18
@@ -18,14 +18,23 @@ var __toESM = (mod, isNodeMode, target) => {
18
18
  var __require = import.meta.require;
19
19
 
20
20
  // ../../packages/core/src/agents/auth-check.ts
21
- import { execFileSync } from "child_process";
21
+ import { execFileSync as execFileSync2 } from "child_process";
22
22
  import { existsSync as existsSync2 } from "fs";
23
23
  import { homedir as homedir2, platform } from "os";
24
- import { join as join2 } from "path";
24
+ import { join as join3 } from "path";
25
25
 
26
26
  // ../../packages/core/src/platform/config.ts
27
+ import { execFileSync } from "child_process";
27
28
  import { randomBytes } from "crypto";
28
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
29
+ import {
30
+ accessSync,
31
+ chmodSync,
32
+ constants,
33
+ existsSync,
34
+ mkdirSync,
35
+ readFileSync,
36
+ writeFileSync
37
+ } from "fs";
29
38
  import { homedir } from "os";
30
39
  import { dirname, join, resolve } from "path";
31
40
  import { fileURLToPath } from "url";
@@ -119,7 +128,8 @@ var CONTEXTS_DIR = resolve(WORKSPACE_DIR, "contexts");
119
128
  var BROWSER_PROFILES_DIR = resolve(WORKSPACE_DIR, "browser-profiles");
120
129
  var DM_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "dm");
121
130
  var SESSION_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "sessions");
122
- var CLAUDE_EXECUTABLE = resolve(HOME, ".local/bin/claude");
131
+ var CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
132
+ var CLAUDE_EXECUTABLE = CLAUDE_EXECUTABLE_ENV ? resolve(CLAUDE_EXECUTABLE_ENV) : undefined;
123
133
  function resolveBrowserMcpBin(envValue) {
124
134
  const override = envValue?.trim();
125
135
  if (override) {
@@ -132,10 +142,55 @@ function resolveBrowserMcpBin(envValue) {
132
142
  }
133
143
  var PATCHRIGHT_MCP_BIN = resolve(PROJECT_ROOT, "scripts/mcp-patchright-http.mjs");
134
144
  var PLAYWRIGHT_MCP_BIN = resolveBrowserMcpBin(envText("NEGOTIUM_BROWSER_MCP_BIN"));
145
+ var BROWSER_RS_VERSION = "v0.1.12";
146
+ var BROWSER_RS_MIN_SECURE_VERSION = "0.1.12";
147
+ function versionAtLeast(actualVersion, minimumVersion) {
148
+ const actual = actualVersion.split(".").map(Number);
149
+ const minimum = minimumVersion.split(".").map(Number);
150
+ if (actual.some(Number.isNaN) || minimum.some(Number.isNaN))
151
+ return false;
152
+ for (let index = 0;index < minimum.length; index += 1) {
153
+ if ((actual[index] ?? 0) > (minimum[index] ?? 0))
154
+ return true;
155
+ if ((actual[index] ?? 0) < (minimum[index] ?? 0))
156
+ return false;
157
+ }
158
+ return true;
159
+ }
160
+ function browserRsMeetsMinimumVersion(candidate) {
161
+ try {
162
+ const output = execFileSync(candidate, ["--version"], {
163
+ encoding: "utf8",
164
+ timeout: 2000,
165
+ stdio: ["ignore", "pipe", "ignore"]
166
+ }).trim();
167
+ const match = output.match(/^browser-rs (\d+)\.(\d+)\.(\d+)$/);
168
+ if (!match)
169
+ return false;
170
+ return versionAtLeast(match.slice(1).join("."), BROWSER_RS_MIN_SECURE_VERSION);
171
+ } catch {
172
+ return false;
173
+ }
174
+ }
175
+ function resolveBrowserRsBin(envValue) {
176
+ const override = envValue?.trim();
177
+ if (!override && !versionAtLeast(BROWSER_RS_VERSION.replace(/^v/, ""), BROWSER_RS_MIN_SECURE_VERSION)) {
178
+ return;
179
+ }
180
+ const candidate = override ? resolve(override) : resolve(STATE_DIR, "bin", "browser-rs", BROWSER_RS_VERSION, "browser-rs");
181
+ try {
182
+ accessSync(candidate, constants.X_OK);
183
+ return browserRsMeetsMinimumVersion(candidate) ? candidate : undefined;
184
+ } catch {
185
+ return;
186
+ }
187
+ }
188
+ var BROWSER_RS_BIN = resolveBrowserRsBin(envText("NEGOTIUM_BROWSER_RS_BIN"));
135
189
  var TSX_BIN = resolveDependencyBin("tsx");
136
190
  var TSCONFIG_PATH = resolve(PROJECT_ROOT, "tsconfig.json");
137
191
  var SESSION_COMM_SERVER = resolve(PROJECT_ROOT, "src/mcp/session-comm/server.ts");
138
192
  var TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
193
+ var BROWSER_MCP_SSE_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/browser-sse-proxy-server.ts");
139
194
  var CANONICAL_MCP_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/canonical-proxy-server.ts");
140
195
  var WIKI_SERVER = resolve(PROJECT_ROOT, "src/mcp/wiki-server.ts");
141
196
  var TOKEN_STATS_SERVER = resolve(PROJECT_ROOT, "src/mcp/token-stats-server.ts");
@@ -253,6 +308,231 @@ function codexAuthFilePath() {
253
308
  return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(homedir(), ".codex", "auth.json");
254
309
  }
255
310
 
311
+ // ../../packages/core/src/storage/vault.ts
312
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync2 } from "fs";
313
+ import { join as join2 } from "path";
314
+
315
+ // ../../packages/core/src/storage/sqlite.ts
316
+ var isBun = typeof process.versions.bun === "string";
317
+ var Database;
318
+ if (isBun) {
319
+ ({ Database } = await import("bun:sqlite"));
320
+ } else {
321
+ const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
322
+ const { DatabaseSync } = await import(nodeSqliteSpecifier);
323
+
324
+ class NodeDatabase {
325
+ #db;
326
+ constructor(path, options = {}) {
327
+ this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
328
+ }
329
+ query(sql) {
330
+ return this.#db.prepare(sql);
331
+ }
332
+ prepare(sql) {
333
+ return this.#db.prepare(sql);
334
+ }
335
+ exec(sql) {
336
+ this.#db.exec(sql);
337
+ }
338
+ run(sql, ...params) {
339
+ return this.#db.prepare(sql).run(...params);
340
+ }
341
+ transaction(fn) {
342
+ const run = (begin) => (...args) => {
343
+ this.#db.exec(begin);
344
+ try {
345
+ const result = fn(...args);
346
+ this.#db.exec("COMMIT");
347
+ return result;
348
+ } catch (err) {
349
+ this.#db.exec("ROLLBACK");
350
+ throw err;
351
+ }
352
+ };
353
+ const tx = run("BEGIN");
354
+ tx.deferred = run("BEGIN DEFERRED");
355
+ tx.immediate = run("BEGIN IMMEDIATE");
356
+ tx.exclusive = run("BEGIN EXCLUSIVE");
357
+ return tx;
358
+ }
359
+ close() {
360
+ this.#db.close();
361
+ }
362
+ }
363
+ Database = NodeDatabase;
364
+ }
365
+
366
+ // ../../packages/core/src/storage/vault-crypto.ts
367
+ import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes2 } from "crypto";
368
+ var ENVELOPE_PREFIX = "otium-vault:v1:";
369
+ var IV_BYTES = 12;
370
+ var KEY_BYTES = 32;
371
+ function encryptionKey(masterKey = VAULT_MASTER_KEY) {
372
+ return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
373
+ }
374
+ function aad(userId, key) {
375
+ return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
376
+ }
377
+ function isEncryptedVaultValue(value) {
378
+ return value.startsWith(ENVELOPE_PREFIX);
379
+ }
380
+ function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
381
+ const iv = randomBytes2(IV_BYTES);
382
+ const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
383
+ cipher.setAAD(aad(userId, key));
384
+ const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
385
+ const tag = cipher.getAuthTag();
386
+ return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
387
+ }
388
+ function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
389
+ if (!isEncryptedVaultValue(storedValue)) {
390
+ return { value: storedValue, legacyPlaintext: true };
391
+ }
392
+ const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
393
+ const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
394
+ if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
395
+ throw new Error("Invalid encrypted vault value");
396
+ }
397
+ const iv = Buffer.from(ivPart, "base64url");
398
+ const ciphertext = Buffer.from(ciphertextPart, "base64url");
399
+ const tag = Buffer.from(tagPart, "base64url");
400
+ if (iv.length !== IV_BYTES || tag.length !== 16) {
401
+ throw new Error("Invalid encrypted vault value");
402
+ }
403
+ const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
404
+ decipher.setAAD(aad(userId, key));
405
+ decipher.setAuthTag(tag);
406
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
407
+ return { value: plaintext.toString("utf8"), legacyPlaintext: false };
408
+ }
409
+
410
+ // ../../packages/core/src/storage/vault.ts
411
+ var vaultDb;
412
+ var vaultMasterKey = VAULT_MASTER_KEY;
413
+ function initializeVaultDatabase(database) {
414
+ database.exec("PRAGMA journal_mode = WAL");
415
+ database.exec("PRAGMA busy_timeout = 5000");
416
+ database.exec(`
417
+ CREATE TABLE IF NOT EXISTS vault (
418
+ user_id TEXT NOT NULL,
419
+ key TEXT NOT NULL,
420
+ value TEXT NOT NULL,
421
+ description TEXT NOT NULL DEFAULT '',
422
+ PRIMARY KEY (user_id, key)
423
+ )
424
+ `);
425
+ {
426
+ const cols = database.prepare("PRAGMA table_info(vault)").all();
427
+ const uid = cols.find((c) => c.name === "user_id");
428
+ if (uid && uid.type.toUpperCase() === "INTEGER") {
429
+ database.exec("BEGIN");
430
+ database.exec(`
431
+ CREATE TABLE vault_migrated (
432
+ user_id TEXT NOT NULL,
433
+ key TEXT NOT NULL,
434
+ value TEXT NOT NULL,
435
+ description TEXT NOT NULL DEFAULT '',
436
+ PRIMARY KEY (user_id, key)
437
+ )
438
+ `);
439
+ database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
440
+ database.exec("DROP TABLE vault");
441
+ database.exec("ALTER TABLE vault_migrated RENAME TO vault");
442
+ database.exec("COMMIT");
443
+ }
444
+ }
445
+ }
446
+ function openVaultDatabase(dataDir) {
447
+ const path = join2(dataDir, "vault.db");
448
+ mkdirSync2(dataDir, { recursive: true });
449
+ const database = new Database(path, { create: true });
450
+ chmodSync2(path, 384);
451
+ initializeVaultDatabase(database);
452
+ return database;
453
+ }
454
+ function activeVaultDatabase() {
455
+ if (!vaultDb)
456
+ vaultDb = openVaultDatabase(DATA_DIR);
457
+ return vaultDb;
458
+ }
459
+ var VAULT_VALUE_MAX_BYTES = 64 * 1024;
460
+ function normalizeVaultKey(key) {
461
+ return key.trim().toUpperCase();
462
+ }
463
+ function decryptRow(userId, key, storedValue) {
464
+ const database = activeVaultDatabase();
465
+ const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
466
+ if (decoded.legacyPlaintext) {
467
+ database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
468
+ }
469
+ return decoded.value;
470
+ }
471
+ function vaultListWithValues(userId) {
472
+ const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
473
+ return rows.map((row) => ({
474
+ key: row.key,
475
+ description: row.description,
476
+ value: decryptRow(userId, row.key, row.value)
477
+ }));
478
+ }
479
+ function vaultGetValue(userId, key) {
480
+ const normalizedKey = normalizeVaultKey(key);
481
+ const row = activeVaultDatabase().prepare("SELECT key, value FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizedKey);
482
+ return row ? decryptRow(userId, row.key, row.value) : undefined;
483
+ }
484
+ function vaultSubstituteDetailed(userId, text) {
485
+ const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
486
+ const usedKeys = new Set;
487
+ const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
488
+ const key = normalizeVaultKey(rawKey);
489
+ const value = entries.get(key);
490
+ if (value === undefined)
491
+ return match;
492
+ usedKeys.add(key);
493
+ return value;
494
+ });
495
+ return { text: substituted, usedKeys: [...usedKeys] };
496
+ }
497
+ function encodedSecretForms(value) {
498
+ const forms = new Set([
499
+ value,
500
+ encodeURIComponent(value),
501
+ Buffer.from(value, "utf8").toString("base64"),
502
+ Buffer.from(value, "utf8").toString("base64url"),
503
+ Buffer.from(value, "utf8").toString("hex")
504
+ ]);
505
+ forms.delete("");
506
+ return [...forms].sort((a, b) => b.length - a.length);
507
+ }
508
+ function redactVaultSecrets(userId, text) {
509
+ const candidates = vaultListWithValues(userId).flatMap((entry) => encodedSecretForms(entry.value).map((form) => ({ form, key: entry.key }))).sort((a, b) => b.form.length - a.form.length || a.key.localeCompare(b.key));
510
+ if (candidates.length === 0)
511
+ return text;
512
+ const candidatesByFirstCharacter = new Map;
513
+ for (const candidate of candidates) {
514
+ const first = candidate.form[0];
515
+ if (!first)
516
+ continue;
517
+ const bucket = candidatesByFirstCharacter.get(first) ?? [];
518
+ bucket.push(candidate);
519
+ candidatesByFirstCharacter.set(first, bucket);
520
+ }
521
+ let redacted = "";
522
+ let offset = 0;
523
+ while (offset < text.length) {
524
+ const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
525
+ if (!match) {
526
+ redacted += text[offset];
527
+ offset += 1;
528
+ continue;
529
+ }
530
+ redacted += `[REDACTED:${match.key}]`;
531
+ offset += match.form.length;
532
+ }
533
+ return redacted;
534
+ }
535
+
256
536
  // ../../packages/core/src/agents/auth-check.ts
257
537
  var defaultAgentAuthHost = {
258
538
  codexAuthFilePath,
@@ -262,14 +542,20 @@ var defaultAgentAuthHost = {
262
542
  operatingSystem: platform,
263
543
  hasMacOsCredential(service) {
264
544
  try {
265
- execFileSync("security", ["find-generic-password", "-s", service], { stdio: "ignore" });
545
+ execFileSync2("security", ["find-generic-password", "-s", service], { stdio: "ignore" });
266
546
  return true;
267
547
  } catch {
268
548
  return false;
269
549
  }
270
- }
550
+ },
551
+ getVaultValue: vaultGetValue
271
552
  };
272
- function checkAgentAuth(agent, host = defaultAgentAuthHost) {
553
+ function hasMaestroCredential(host, key, userId) {
554
+ if (userId && host.getVaultValue(userId, key)?.trim())
555
+ return true;
556
+ return Boolean(host.environment[key]?.trim());
557
+ }
558
+ function checkAgentAuth(agent, host = defaultAgentAuthHost, userId) {
273
559
  switch (agent) {
274
560
  case "codex": {
275
561
  const path = host.codexAuthFilePath();
@@ -292,7 +578,7 @@ function checkAgentAuth(agent, host = defaultAgentAuthHost) {
292
578
  error: "claude is not logged in (no macOS keychain entry 'Claude Code-credentials'). Run `claude` and complete login first"
293
579
  };
294
580
  }
295
- const path = join2(host.homeDirectory(), ".claude", ".credentials.json");
581
+ const path = join3(host.homeDirectory(), ".claude", ".credentials.json");
296
582
  if (host.exists(path))
297
583
  return { ok: true };
298
584
  return {
@@ -301,35 +587,35 @@ function checkAgentAuth(agent, host = defaultAgentAuthHost) {
301
587
  };
302
588
  }
303
589
  case "maestro": {
304
- if (host.environment.DEEPSEEK_API_KEY || host.environment.MOONSHOT_API_KEY) {
590
+ if (hasMaestroCredential(host, "DEEPSEEK_API_KEY", userId) || hasMaestroCredential(host, "MOONSHOT_API_KEY", userId)) {
305
591
  return { ok: true };
306
592
  }
307
593
  return {
308
594
  ok: false,
309
- error: "maestro is not authenticated (neither DEEPSEEK_API_KEY nor MOONSHOT_API_KEY env var is set)"
595
+ error: "maestro is not authenticated (set DEEPSEEK_API_KEY or MOONSHOT_API_KEY via /vault set, or as an env var)"
310
596
  };
311
597
  }
312
598
  }
313
599
  }
314
- function checkAgentModelAuth(agent, model, host = defaultAgentAuthHost) {
600
+ function checkAgentModelAuth(agent, model, host = defaultAgentAuthHost, userId) {
315
601
  if (agent !== "maestro")
316
- return checkAgentAuth(agent, host);
602
+ return checkAgentAuth(agent, host, userId);
317
603
  if (model.startsWith("kimi")) {
318
- return host.environment.MOONSHOT_API_KEY ? { ok: true } : {
604
+ return hasMaestroCredential(host, "MOONSHOT_API_KEY", userId) ? { ok: true } : {
319
605
  ok: false,
320
- error: `maestro is not authenticated for model '${model}' (MOONSHOT_API_KEY env var not set)`
606
+ error: `maestro is not authenticated for model '${model}' (set MOONSHOT_API_KEY via /vault set, or as an env var)`
321
607
  };
322
608
  }
323
609
  if (model.startsWith("deepseek")) {
324
- return host.environment.DEEPSEEK_API_KEY ? { ok: true } : {
610
+ return hasMaestroCredential(host, "DEEPSEEK_API_KEY", userId) ? { ok: true } : {
325
611
  ok: false,
326
- error: `maestro is not authenticated for model '${model}' (DEEPSEEK_API_KEY env var not set)`
612
+ error: `maestro is not authenticated for model '${model}' (set DEEPSEEK_API_KEY via /vault set, or as an env var)`
327
613
  };
328
614
  }
329
- return checkAgentAuth(agent, host);
615
+ return checkAgentAuth(agent, host, userId);
330
616
  }
331
617
  // ../../packages/core/src/agents/codex-tree-kill.ts
332
- import { execFileSync as execFileSync2 } from "child_process";
618
+ import { execFileSync as execFileSync3 } from "child_process";
333
619
 
334
620
  // ../../packages/core/src/agents/codex-tree-manager.ts
335
621
  function createCodexTreeManager(host, options = {}) {
@@ -515,7 +801,7 @@ function createCodexTreeManager(host, options = {}) {
515
801
  // ../../packages/core/src/agents/codex-tree-kill.ts
516
802
  function execText(command, args) {
517
803
  try {
518
- return execFileSync2(command, args, {
804
+ return execFileSync3(command, args, {
519
805
  encoding: "utf8",
520
806
  stdio: ["ignore", "pipe", "ignore"]
521
807
  }).trim();
@@ -550,23 +836,23 @@ var killOwnedCodexTreesForShutdown = manager.killOwnedTreesForShutdown;
550
836
  import { existsSync as existsSync7, unlinkSync as unlinkSync7 } from "fs";
551
837
 
552
838
  // ../../packages/core/src/agents/claude-registry.ts
553
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
839
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
554
840
  import { homedir as homedir4 } from "os";
555
- import { join as join5 } from "path";
841
+ import { join as join6 } from "path";
556
842
 
557
843
  // ../../packages/core/src/agents/rollout/claude.ts
558
844
  import { randomUUID } from "crypto";
559
845
  import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
560
846
  import { homedir as homedir3 } from "os";
561
- import { join as join4 } from "path";
847
+ import { join as join5 } from "path";
562
848
 
563
849
  // ../../packages/core/src/agents/rollout/shared.ts
564
- import { mkdirSync as mkdirSync2 } from "fs";
565
- import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
850
+ import { mkdirSync as mkdirSync3 } from "fs";
851
+ import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
566
852
  import { fileURLToPath as fileURLToPath2 } from "url";
567
853
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
568
854
  var trustedWorkspaceRoots = [WORKSPACE_DIR, DM_WORKSPACE_DIR, TOPIC_WORKSPACE_DIR].map((root) => resolve2(root));
569
- var FIXTURES_DIR = join3(__dirname2, "..", "fixtures");
855
+ var FIXTURES_DIR = join4(__dirname2, "..", "fixtures");
570
856
  function clone(obj) {
571
857
  return structuredClone(obj);
572
858
  }
@@ -586,7 +872,7 @@ function assertCwdInWorkspace(cwd) {
586
872
  function ensureCwdExists(cwd) {
587
873
  assertCwdInWorkspace(cwd);
588
874
  try {
589
- mkdirSync2(cwd, { recursive: true });
875
+ mkdirSync3(cwd, { recursive: true });
590
876
  } catch (err) {
591
877
  logger.warn({ err, cwd }, "rollout: ensureCwdExists failed \u2014 caller should pre-create cwd");
592
878
  }
@@ -676,7 +962,7 @@ import {
676
962
  appendFileSync,
677
963
  closeSync,
678
964
  fsyncSync,
679
- mkdirSync as mkdirSync3,
965
+ mkdirSync as mkdirSync4,
680
966
  openSync,
681
967
  readFileSync as readFileSync2,
682
968
  renameSync,
@@ -739,7 +1025,7 @@ function isStaleLock(lockPath) {
739
1025
  }
740
1026
  }
741
1027
  function appendJsonlLine(filePath, line) {
742
- mkdirSync3(dirname3(filePath), { recursive: true });
1028
+ mkdirSync4(dirname3(filePath), { recursive: true });
743
1029
  const lockPath = `${filePath}${LOCK_SUFFIX}`;
744
1030
  const payload = line.endsWith(`
745
1031
  `) ? line : `${line}
@@ -772,7 +1058,7 @@ function appendJsonlLine(filePath, line) {
772
1058
  }
773
1059
  function writeJsonlFile(filePath, entries) {
774
1060
  const dir = dirname3(filePath);
775
- mkdirSync3(dir, { recursive: true });
1061
+ mkdirSync4(dir, { recursive: true });
776
1062
  const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
777
1063
  const payload = `${entries.map((e) => JSON.stringify(e)).join(`
778
1064
  `)}
@@ -817,7 +1103,7 @@ var _attachmentsCache = null;
817
1103
  function loadClaudeAttachments() {
818
1104
  if (_attachmentsCache)
819
1105
  return _attachmentsCache;
820
- const raw = readFileSync3(join4(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
1106
+ const raw = readFileSync3(join5(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
821
1107
  const lines = parseJsonlText(raw);
822
1108
  if (lines.length < 2) {
823
1109
  throw new Error(`loadClaudeAttachments: expected >=2 entries in claude-attachments.jsonl, got ${lines.length}`);
@@ -921,8 +1207,8 @@ function writeClaudeRollout(opts) {
921
1207
  });
922
1208
  lastUuid = assistantUuid;
923
1209
  }
924
- const projectsDir = join4(homedir3(), ".claude", "projects", encodeClaudeCwd(opts.cwd));
925
- const path = join4(projectsDir, `${sessionId}.jsonl`);
1210
+ const projectsDir = join5(homedir3(), ".claude", "projects", encodeClaudeCwd(opts.cwd));
1211
+ const path = join5(projectsDir, `${sessionId}.jsonl`);
926
1212
  writeJsonlFile(path, lines);
927
1213
  logger.info({ sessionId, path, pairs: pairs.length }, "writeClaudeRollout: synthetic rollout placed");
928
1214
  return { sessionId, rolloutPath: path };
@@ -967,15 +1253,15 @@ var claudeRegistry = {
967
1253
  const result = await forkSession(parentSessionId, {
968
1254
  ...title ? { title } : {}
969
1255
  });
970
- const projectsRoot = join5(homedir4(), ".claude", "projects");
971
- const destDir = join5(projectsRoot, encodeClaudeCwd(cwd));
972
- const destPath = join5(destDir, `${result.sessionId}.jsonl`);
1256
+ const projectsRoot = join6(homedir4(), ".claude", "projects");
1257
+ const destDir = join6(projectsRoot, encodeClaudeCwd(cwd));
1258
+ const destPath = join6(destDir, `${result.sessionId}.jsonl`);
973
1259
  if (!existsSync4(destPath)) {
974
- const sourcePath = readdirSync(projectsRoot).map((d) => join5(projectsRoot, d, `${result.sessionId}.jsonl`)).find((p) => existsSync4(p));
1260
+ const sourcePath = readdirSync(projectsRoot).map((d) => join6(projectsRoot, d, `${result.sessionId}.jsonl`)).find((p) => existsSync4(p));
975
1261
  if (!sourcePath) {
976
1262
  throw new Error(`claude forkSession: fork rollout ${result.sessionId}.jsonl not found under ${projectsRoot}`);
977
1263
  }
978
- mkdirSync4(destDir, { recursive: true });
1264
+ mkdirSync5(destDir, { recursive: true });
979
1265
  renameSync2(sourcePath, destPath);
980
1266
  }
981
1267
  return {
@@ -984,10 +1270,10 @@ var claudeRegistry = {
984
1270
  };
985
1271
  },
986
1272
  async cleanupRollouts({ cwd, sessionIds }) {
987
- const projectsDir = join5(homedir4(), ".claude", "projects", encodeClaudeCwd(cwd));
1273
+ const projectsDir = join6(homedir4(), ".claude", "projects", encodeClaudeCwd(cwd));
988
1274
  const failures = [];
989
1275
  for (const sid of sessionIds) {
990
- const path = join5(projectsDir, `${sid}.jsonl`);
1276
+ const path = join6(projectsDir, `${sid}.jsonl`);
991
1277
  try {
992
1278
  unlinkSync3(path);
993
1279
  } catch (e) {
@@ -1006,21 +1292,21 @@ var claudeRegistry = {
1006
1292
  // ../../packages/core/src/agents/codex-registry.ts
1007
1293
  import { unlinkSync as unlinkSync6 } from "fs";
1008
1294
  import { homedir as homedir7 } from "os";
1009
- import { join as join9 } from "path";
1295
+ import { join as join10 } from "path";
1010
1296
 
1011
1297
  // ../../packages/core/src/agents/rollout/codex.ts
1012
- import { randomBytes as randomBytes2 } from "crypto";
1013
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2, unlinkSync as unlinkSync4 } from "fs";
1298
+ import { randomBytes as randomBytes3 } from "crypto";
1299
+ import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync, statSync as statSync2, unlinkSync as unlinkSync4 } from "fs";
1014
1300
  import { homedir as homedir5 } from "os";
1015
- import { join as join6 } from "path";
1301
+ import { basename, dirname as dirname4, join as join7, resolve as resolve3 } from "path";
1016
1302
  function codexSessionsDir() {
1017
- return join6(process.env.CODEX_HOME || join6(homedir5(), ".codex"), "sessions");
1303
+ return join7(process.env.CODEX_HOME || join7(homedir5(), ".codex"), "sessions");
1018
1304
  }
1019
1305
  var _shellCache = null;
1020
1306
  function loadCodexShell() {
1021
1307
  if (_shellCache)
1022
1308
  return _shellCache;
1023
- const raw = readFileSync4(join6(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
1309
+ const raw = readFileSync4(join7(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
1024
1310
  const lines = parseJsonlText(raw);
1025
1311
  if (lines.length < 5) {
1026
1312
  throw new Error(`loadCodexShell: expected >=5 entries in codex-shell.jsonl, got ${lines.length}`);
@@ -1037,7 +1323,7 @@ function loadCodexShell() {
1037
1323
  function uuidv7() {
1038
1324
  const ts = Date.now();
1039
1325
  const tsHex = ts.toString(16).padStart(12, "0");
1040
- const rand = randomBytes2(10);
1326
+ const rand = randomBytes3(10);
1041
1327
  rand[0] = rand[0] & 15 | 112;
1042
1328
  rand[2] = rand[2] & 63 | 128;
1043
1329
  return [
@@ -1078,6 +1364,139 @@ function patchEnvContext(envContext, cwd, currentDate, timezone) {
1078
1364
  }
1079
1365
  }
1080
1366
  }
1367
+ function canonicalFilePath(path) {
1368
+ const absolute = resolve3(path);
1369
+ try {
1370
+ return realpathSync(absolute);
1371
+ } catch {
1372
+ try {
1373
+ return join7(realpathSync(dirname4(absolute)), basename(absolute));
1374
+ } catch {
1375
+ return absolute;
1376
+ }
1377
+ }
1378
+ }
1379
+ function changedPatchLines(value) {
1380
+ const removed = [];
1381
+ const added = [];
1382
+ const diffPreview = [];
1383
+ let oldLine = 0;
1384
+ let newLine = 0;
1385
+ let sawHunk = false;
1386
+ for (const line of value.split(`
1387
+ `)) {
1388
+ const hunk = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
1389
+ if (hunk) {
1390
+ if (sawHunk)
1391
+ diffPreview.push("\u2026");
1392
+ sawHunk = true;
1393
+ oldLine = Number.parseInt(hunk[1] ?? "0", 10);
1394
+ newLine = Number.parseInt(hunk[2] ?? "0", 10);
1395
+ continue;
1396
+ }
1397
+ if (!sawHunk && (line.startsWith("--- ") || line.startsWith("+++ ")))
1398
+ continue;
1399
+ if (line.startsWith("\\ No newline"))
1400
+ continue;
1401
+ if (line.startsWith("-")) {
1402
+ removed.push(line.slice(1));
1403
+ diffPreview.push(`${oldLine} -${line.slice(1)}`);
1404
+ oldLine += 1;
1405
+ continue;
1406
+ }
1407
+ if (line.startsWith("+")) {
1408
+ added.push(line.slice(1));
1409
+ diffPreview.push(`${newLine} +${line.slice(1)}`);
1410
+ newLine += 1;
1411
+ continue;
1412
+ }
1413
+ if (line.startsWith(" ")) {
1414
+ diffPreview.push(`${newLine} ${line.slice(1)}`);
1415
+ oldLine += 1;
1416
+ newLine += 1;
1417
+ }
1418
+ }
1419
+ return {
1420
+ ...removed.length > 0 ? { before: removed.join(`
1421
+ `) } : {},
1422
+ ...added.length > 0 ? { after: added.join(`
1423
+ `) } : {},
1424
+ ...diffPreview.length > 0 ? { diffPreview: diffPreview.join(`
1425
+ `) } : {}
1426
+ };
1427
+ }
1428
+ function extractLatestCodexPatchPreview(jsonl, expectedPaths, consumedCallIds = new Set, expectedCallId) {
1429
+ const expected = expectedPaths.map(canonicalFilePath);
1430
+ const lines = jsonl.trimEnd().split(`
1431
+ `);
1432
+ for (let index = lines.length - 1;index >= 0; index -= 1) {
1433
+ try {
1434
+ const entry = JSON.parse(lines[index] ?? "");
1435
+ if (entry.type !== "event_msg" || entry.payload?.type !== "patch_apply_end")
1436
+ continue;
1437
+ const callId = entry.payload.call_id;
1438
+ const rawChanges = entry.payload.changes;
1439
+ if (!callId || consumedCallIds.has(callId) || expectedCallId !== undefined && callId !== expectedCallId || !rawChanges) {
1440
+ continue;
1441
+ }
1442
+ const entries = Object.entries(rawChanges);
1443
+ const matched = expected.map((path) => entries.find(([candidate]) => canonicalFilePath(candidate) === path));
1444
+ const complete = matched.filter((change) => change !== undefined);
1445
+ if (complete.length !== expected.length)
1446
+ continue;
1447
+ return {
1448
+ callId,
1449
+ changes: complete.map(([path, change]) => {
1450
+ if (typeof change.unified_diff === "string") {
1451
+ return { path, ...changedPatchLines(change.unified_diff) };
1452
+ }
1453
+ const content = typeof change.content === "string" ? change.content.replace(/\n$/, "") : undefined;
1454
+ return {
1455
+ path,
1456
+ ...change.type === "delete" && content !== undefined ? { before: content } : {},
1457
+ ...change.type === "add" && content !== undefined ? { after: content } : {}
1458
+ };
1459
+ })
1460
+ };
1461
+ } catch {}
1462
+ }
1463
+ return;
1464
+ }
1465
+ function extractCodexPatchCallIds(jsonl) {
1466
+ const callIds = [];
1467
+ for (const line of jsonl.trimEnd().split(`
1468
+ `)) {
1469
+ try {
1470
+ const entry = JSON.parse(line);
1471
+ if (entry.type === "event_msg" && entry.payload?.type === "patch_apply_end" && typeof entry.payload.call_id === "string") {
1472
+ callIds.push(entry.payload.call_id);
1473
+ }
1474
+ } catch {}
1475
+ }
1476
+ return callIds;
1477
+ }
1478
+ function readCodexPatchCallIds(threadId) {
1479
+ const path = latestCodexRolloutPath(threadId);
1480
+ if (!path)
1481
+ return [];
1482
+ try {
1483
+ return extractCodexPatchCallIds(readFileSync4(path, "utf8"));
1484
+ } catch (error) {
1485
+ logger.debug({ error, threadId }, "codex patch ids: rollout read failed");
1486
+ return [];
1487
+ }
1488
+ }
1489
+ function readLatestCodexPatchPreview(threadId, expectedPaths, consumedCallIds = new Set, expectedCallId) {
1490
+ const path = latestCodexRolloutPath(threadId);
1491
+ if (!path)
1492
+ return;
1493
+ try {
1494
+ return extractLatestCodexPatchPreview(readFileSync4(path, "utf8"), expectedPaths, consumedCallIds, expectedCallId);
1495
+ } catch (error) {
1496
+ logger.debug({ error, threadId }, "codex patch preview: rollout read failed");
1497
+ return;
1498
+ }
1499
+ }
1081
1500
  function extractLatestCodexContextUsage(jsonl) {
1082
1501
  const lines = jsonl.trimEnd().split(`
1083
1502
  `);
@@ -1102,18 +1521,18 @@ function readLatestCodexContextUsage(threadId) {
1102
1521
  try {
1103
1522
  if (buckets) {
1104
1523
  for (const bucket of buckets) {
1105
- const dir = join6(sessionsDir, bucket);
1524
+ const dir = join7(sessionsDir, bucket);
1106
1525
  if (!existsSync5(dir))
1107
1526
  continue;
1108
1527
  const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
1109
1528
  for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
1110
- candidates.push(join6(dir, rel));
1529
+ candidates.push(join7(dir, rel));
1111
1530
  }
1112
1531
  }
1113
1532
  } else {
1114
1533
  const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
1115
1534
  for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
1116
- candidates.push(join6(sessionsDir, rel));
1535
+ candidates.push(join7(sessionsDir, rel));
1117
1536
  }
1118
1537
  }
1119
1538
  const path = candidates.sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs)[0];
@@ -1157,19 +1576,19 @@ function latestCodexRolloutPath(threadId) {
1157
1576
  try {
1158
1577
  if (buckets) {
1159
1578
  for (const bucket of buckets) {
1160
- const dir = join6(sessionsDir, bucket);
1579
+ const dir = join7(sessionsDir, bucket);
1161
1580
  if (!existsSync5(dir))
1162
1581
  continue;
1163
1582
  const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
1164
1583
  for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
1165
- candidates.push(join6(dir, rel));
1584
+ candidates.push(join7(dir, rel));
1166
1585
  }
1167
1586
  }
1168
1587
  }
1169
1588
  if (candidates.length === 0) {
1170
1589
  const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
1171
1590
  for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
1172
- candidates.push(join6(sessionsDir, rel));
1591
+ candidates.push(join7(sessionsDir, rel));
1173
1592
  }
1174
1593
  }
1175
1594
  return candidates.sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs)[0];
@@ -1278,8 +1697,8 @@ function writeCodexRollout(opts) {
1278
1697
  const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
1279
1698
  const dd = String(now.getUTCDate()).padStart(2, "0");
1280
1699
  const tsStr = tsIso.replace(/[:.]/g, "-").slice(0, 19);
1281
- const dir = join6(codexSessionsDir(), String(yyyy), mm, dd);
1282
- const path = join6(dir, `rollout-${tsStr}-${threadId}.jsonl`);
1700
+ const dir = join7(codexSessionsDir(), String(yyyy), mm, dd);
1701
+ const path = join7(dir, `rollout-${tsStr}-${threadId}.jsonl`);
1283
1702
  if (opts.threadId) {
1284
1703
  sweepPriorRolloutsForThread(opts.threadId);
1285
1704
  }
@@ -1295,13 +1714,13 @@ function sweepPriorRolloutsForThread(threadId) {
1295
1714
  return;
1296
1715
  }
1297
1716
  for (const bucket of buckets) {
1298
- const dir = join6(sessionsDir, bucket);
1717
+ const dir = join7(sessionsDir, bucket);
1299
1718
  if (!existsSync5(dir))
1300
1719
  continue;
1301
1720
  try {
1302
1721
  const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
1303
1722
  for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
1304
- const fullPath = join6(dir, rel);
1723
+ const fullPath = join7(dir, rel);
1305
1724
  try {
1306
1725
  unlinkSync4(fullPath);
1307
1726
  } catch (e) {
@@ -1319,7 +1738,7 @@ function sweepPriorRolloutsFullTree(threadId, sessionsDir) {
1319
1738
  try {
1320
1739
  const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
1321
1740
  for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
1322
- const fullPath = join6(sessionsDir, rel);
1741
+ const fullPath = join7(sessionsDir, rel);
1323
1742
  try {
1324
1743
  unlinkSync4(fullPath);
1325
1744
  } catch (e) {
@@ -1379,13 +1798,13 @@ function formatDateBucket(d) {
1379
1798
  // ../../packages/core/src/storage/conversations.ts
1380
1799
  import {
1381
1800
  existsSync as existsSync6,
1382
- mkdirSync as mkdirSync6,
1801
+ mkdirSync as mkdirSync7,
1383
1802
  readFileSync as readFileSync5,
1384
1803
  renameSync as renameSync3,
1385
1804
  unlinkSync as unlinkSync5,
1386
1805
  writeFileSync as writeFileSync4
1387
1806
  } from "fs";
1388
- import { dirname as dirname5, join as join8 } from "path";
1807
+ import { dirname as dirname6, join as join9 } from "path";
1389
1808
 
1390
1809
  // ../../packages/core/src/security/sanitize.ts
1391
1810
  function sanitizeTopicName(name, lowercase = false) {
@@ -1400,62 +1819,9 @@ function sanitizeFileName(name) {
1400
1819
  }
1401
1820
 
1402
1821
  // ../../packages/core/src/storage/storage-host.ts
1403
- import { mkdirSync as mkdirSync5 } from "fs";
1822
+ import { mkdirSync as mkdirSync6 } from "fs";
1404
1823
  import { homedir as homedir6 } from "os";
1405
- import { dirname as dirname4, join as join7, resolve as resolve3 } from "path";
1406
-
1407
- // ../../packages/core/src/storage/sqlite.ts
1408
- var isBun = typeof process.versions.bun === "string";
1409
- var Database;
1410
- if (isBun) {
1411
- ({ Database } = await import("bun:sqlite"));
1412
- } else {
1413
- const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
1414
- const { DatabaseSync } = await import(nodeSqliteSpecifier);
1415
-
1416
- class NodeDatabase {
1417
- #db;
1418
- constructor(path, options = {}) {
1419
- this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
1420
- }
1421
- query(sql) {
1422
- return this.#db.prepare(sql);
1423
- }
1424
- prepare(sql) {
1425
- return this.#db.prepare(sql);
1426
- }
1427
- exec(sql) {
1428
- this.#db.exec(sql);
1429
- }
1430
- run(sql, ...params) {
1431
- return this.#db.prepare(sql).run(...params);
1432
- }
1433
- transaction(fn) {
1434
- const run = (begin) => (...args) => {
1435
- this.#db.exec(begin);
1436
- try {
1437
- const result = fn(...args);
1438
- this.#db.exec("COMMIT");
1439
- return result;
1440
- } catch (err) {
1441
- this.#db.exec("ROLLBACK");
1442
- throw err;
1443
- }
1444
- };
1445
- const tx = run("BEGIN");
1446
- tx.deferred = run("BEGIN DEFERRED");
1447
- tx.immediate = run("BEGIN IMMEDIATE");
1448
- tx.exclusive = run("BEGIN EXCLUSIVE");
1449
- return tx;
1450
- }
1451
- close() {
1452
- this.#db.close();
1453
- }
1454
- }
1455
- Database = NodeDatabase;
1456
- }
1457
-
1458
- // ../../packages/core/src/storage/storage-host.ts
1824
+ import { dirname as dirname5, join as join8, resolve as resolve4 } from "path";
1459
1825
  var configuredHost = {};
1460
1826
  var fallbackDatabase = null;
1461
1827
  var fallbackDatabasePath = null;
@@ -1464,19 +1830,19 @@ var initializedSchemas = new WeakMap;
1464
1830
  var initializingDatabases = new WeakSet;
1465
1831
  function envPath(name, fallback) {
1466
1832
  const value = process.env[name]?.trim();
1467
- return resolve3(value || fallback);
1833
+ return resolve4(value || fallback);
1468
1834
  }
1469
1835
  function defaultStateDir() {
1470
- return envPath("NEGOTIUM_STATE_DIR", join7(homedir6(), ".negotium"));
1836
+ return envPath("NEGOTIUM_STATE_DIR", join8(homedir6(), ".negotium"));
1471
1837
  }
1472
1838
  function defaultDataDir() {
1473
- return envPath("NEGOTIUM_DATA_DIR", join7(defaultStateDir(), "data"));
1839
+ return envPath("NEGOTIUM_DATA_DIR", join8(defaultStateDir(), "data"));
1474
1840
  }
1475
1841
  function defaultWorkspaceDir() {
1476
- return envPath("NEGOTIUM_WORKSPACE_DIR", join7(defaultStateDir(), "workspace"));
1842
+ return envPath("NEGOTIUM_WORKSPACE_DIR", join8(defaultStateDir(), "workspace"));
1477
1843
  }
1478
1844
  function defaultSessionsDatabasePath() {
1479
- return envPath("SESSIONS_DB_PATH", join7(resolveStorageDataDir(), "sessions.db"));
1845
+ return envPath("SESSIONS_DB_PATH", join8(resolveStorageDataDir(), "sessions.db"));
1480
1846
  }
1481
1847
  function initializeDatabase(database) {
1482
1848
  database.exec("PRAGMA journal_mode = WAL");
@@ -1493,7 +1859,7 @@ function defaultDatabase() {
1493
1859
  return fallbackDatabase;
1494
1860
  if (fallbackDatabase)
1495
1861
  fallbackDatabase.close();
1496
- mkdirSync5(dirname4(path), { recursive: true });
1862
+ mkdirSync6(dirname5(path), { recursive: true });
1497
1863
  fallbackDatabase = new Database(path, { create: true });
1498
1864
  fallbackDatabasePath = path;
1499
1865
  initializeDatabase(fallbackDatabase);
@@ -1509,7 +1875,7 @@ function resolveStorageWorkspaceDir() {
1509
1875
  return configuredHost.workspaceDir ?? defaultWorkspaceDir();
1510
1876
  }
1511
1877
  function resolveStorageSharedWikiDir() {
1512
- return configuredHost.sharedWikiDir ?? join7(resolveStorageWorkspaceDir(), "wiki");
1878
+ return configuredHost.sharedWikiDir ?? join8(resolveStorageWorkspaceDir(), "wiki");
1513
1879
  }
1514
1880
  function registerStorageSchemaInitializer(initialize, priority = 100) {
1515
1881
  schemaInitializers.push({ initialize, priority });
@@ -1563,14 +1929,14 @@ function safeUserIdComponent(userId) {
1563
1929
  return str;
1564
1930
  }
1565
1931
  function conversationDir(userId) {
1566
- return join8(resolveStorageDataDir(), "conversations", safeUserIdComponent(userId));
1932
+ return join9(resolveStorageDataDir(), "conversations", safeUserIdComponent(userId));
1567
1933
  }
1568
1934
  function topicFilename(topicName) {
1569
1935
  const t = sanitizeTopicName(topicName, true);
1570
1936
  return `${t}.jsonl`;
1571
1937
  }
1572
1938
  function getConversationPath(userId, topicName) {
1573
- return join8(conversationDir(userId), topicFilename(topicName));
1939
+ return join9(conversationDir(userId), topicFilename(topicName));
1574
1940
  }
1575
1941
  function appendConversationEvent(userId, topicName, agent, event) {
1576
1942
  try {
@@ -1586,7 +1952,7 @@ function appendConversationEventStrict(userId, topicName, agent, event) {
1586
1952
  agent,
1587
1953
  event
1588
1954
  };
1589
- mkdirSync6(dirname5(path), { recursive: true });
1955
+ mkdirSync7(dirname6(path), { recursive: true });
1590
1956
  appendJsonlLine(path, JSON.stringify(entry));
1591
1957
  }
1592
1958
  function readConversation(userId, topicName) {
@@ -1655,13 +2021,13 @@ var codexRegistry = {
1655
2021
  async cleanupRollouts({ sessionIds }) {
1656
2022
  if (sessionIds.length === 0)
1657
2023
  return;
1658
- const sessionsDir = join9(process.env.CODEX_HOME || join9(homedir7(), ".codex"), "sessions");
2024
+ const sessionsDir = join10(process.env.CODEX_HOME || join10(homedir7(), ".codex"), "sessions");
1659
2025
  const failures = [];
1660
2026
  for (const tid of sessionIds) {
1661
2027
  try {
1662
2028
  const glob = new Bun.Glob(`**/rollout-*-${tid}.jsonl`);
1663
2029
  for await (const rel of glob.scan({ cwd: sessionsDir, onlyFiles: true })) {
1664
- const path = join9(sessionsDir, rel);
2030
+ const path = join10(sessionsDir, rel);
1665
2031
  try {
1666
2032
  unlinkSync6(path);
1667
2033
  } catch (e) {
@@ -1747,8 +2113,8 @@ function cleanupAgentFork(handle) {
1747
2113
  defaultForkHelpers.cleanupAgentFork(handle);
1748
2114
  }
1749
2115
  // ../../packages/core/src/agents/archiver.ts
1750
- import { randomUUID as randomUUID4 } from "crypto";
1751
- import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
2116
+ import { randomUUID as randomUUID5 } from "crypto";
2117
+ import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync11, statSync as statSync5 } from "fs";
1752
2118
  import { join as join15 } from "path";
1753
2119
 
1754
2120
  // ../../packages/core/src/agents/index.ts
@@ -1781,8 +2147,8 @@ function deepMapStrings(value, fn) {
1781
2147
  import { AsyncLocalStorage } from "async_hooks";
1782
2148
 
1783
2149
  // ../../packages/core/src/security/sensitive-path.ts
1784
- import { realpathSync } from "fs";
1785
- import { resolve as resolve4 } from "path";
2150
+ import { realpathSync as realpathSync2 } from "fs";
2151
+ import { resolve as resolve5 } from "path";
1786
2152
  var SENSITIVE_PATH_PATTERNS = [
1787
2153
  /\/\.env(\.|$)/i,
1788
2154
  /\/\.ssh\//i,
@@ -1799,11 +2165,11 @@ var SENSITIVE_PATH_PATTERNS = [
1799
2165
  /\/sessions\.db(-wal|-shm|-journal)?$/i
1800
2166
  ];
1801
2167
  function isSensitivePath(filePath) {
1802
- const normalized = resolve4(filePath);
2168
+ const normalized = resolve5(filePath);
1803
2169
  if (SENSITIVE_PATH_PATTERNS.some((p) => p.test(normalized)))
1804
2170
  return true;
1805
2171
  try {
1806
- const real = realpathSync(normalized);
2172
+ const real = realpathSync2(normalized);
1807
2173
  if (real !== normalized)
1808
2174
  return SENSITIVE_PATH_PATTERNS.some((p) => p.test(real));
1809
2175
  } catch {}
@@ -1932,8 +2298,8 @@ function peerSessionBridgeIpcEnv() {
1932
2298
  }
1933
2299
 
1934
2300
  // ../../packages/core/src/platform/background-bash/manager.ts
1935
- import { execFileSync as execFileSync3, spawn } from "child_process";
1936
- import { randomBytes as randomBytes3 } from "crypto";
2301
+ import { execFileSync as execFileSync4, spawn } from "child_process";
2302
+ import { randomBytes as randomBytes4 } from "crypto";
1937
2303
 
1938
2304
  // ../../packages/core/src/platform/delay.ts
1939
2305
  var delay = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -1950,7 +2316,7 @@ function makeBgBashKey(_userId, _topic) {
1950
2316
  }
1951
2317
  function defaultPortPids(port) {
1952
2318
  try {
1953
- return execFileSync3("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" }).toString().trim().split(`
2319
+ return execFileSync4("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" }).toString().trim().split(`
1954
2320
  `).map((pid) => Number.parseInt(pid, 10)).filter((pid) => !Number.isNaN(pid));
1955
2321
  } catch {
1956
2322
  return [];
@@ -1961,8 +2327,8 @@ function createBackgroundBashManager(options = {}) {
1961
2327
  const usedPorts = new Set;
1962
2328
  const spawning = new Map;
1963
2329
  const knownContexts = new Map;
1964
- const runtimeCapability = options.capability ?? randomBytes3(32).toString("hex");
1965
- const runtimeServerId = options.serverId ?? randomBytes3(16).toString("hex");
2330
+ const runtimeCapability = options.capability ?? randomBytes4(32).toString("hex");
2331
+ const runtimeServerId = options.serverId ?? randomBytes4(16).toString("hex");
1966
2332
  const serverFile = options.serverFile ?? BACKGROUND_BASH_SERVER;
1967
2333
  const basePort = options.basePort ?? BG_BASH_BASE_PORT;
1968
2334
  const maxPort = options.maxPort ?? BG_BASH_MAX_PORT;
@@ -2107,12 +2473,12 @@ function createBackgroundBashManager(options = {}) {
2107
2473
  usedPorts.clear();
2108
2474
  knownContexts.clear();
2109
2475
  const deadline = now() + 3000;
2110
- await Promise.all(entries.map((instance) => new Promise((resolve5) => {
2476
+ await Promise.all(entries.map((instance) => new Promise((resolve6) => {
2111
2477
  if (instance.process.exitCode !== null || instance.process.killed)
2112
- return resolve5();
2113
- instance.process.once("exit", resolve5);
2114
- instance.process.once("error", resolve5);
2115
- const timer = setTimeout(resolve5, Math.max(0, deadline - now()));
2478
+ return resolve6();
2479
+ instance.process.once("exit", resolve6);
2480
+ instance.process.once("error", resolve6);
2481
+ const timer = setTimeout(resolve6, Math.max(0, deadline - now()));
2116
2482
  timer.unref?.();
2117
2483
  })));
2118
2484
  }
@@ -2201,23 +2567,23 @@ function browserOwnerForContext(ctx) {
2201
2567
  function playwrightTransport(port, owner, capability, agent) {
2202
2568
  const ownerCapability = browserOwnerCapability(capability, owner);
2203
2569
  if (agent === "codex") {
2570
+ const query2 = new URLSearchParams({ owner });
2204
2571
  return {
2205
- url: `http://127.0.0.1:${port}/mcp`,
2206
- http_headers: { "X-Browser-Owner": owner },
2572
+ url: `http://127.0.0.1:${port}/mcp?${query2}`,
2207
2573
  env_http_headers: { "X-Browser-Capability": CODEX_BROWSER_CAPABILITY_ENV }
2208
2574
  };
2209
2575
  }
2576
+ const query = new URLSearchParams({ owner });
2210
2577
  if (agent === "maestro") {
2211
- const query = new URLSearchParams({ owner, capability: ownerCapability });
2212
- return {
2213
- type: "sse",
2214
- url: `http://127.0.0.1:${port}/sse?${query}`
2215
- };
2578
+ return buildStdioMcpServer("maestro", BROWSER_MCP_SSE_PROXY_SERVER, [], {
2579
+ NEGOTIUM_BROWSER_SSE_URL: `http://127.0.0.1:${port}/sse?${query}`,
2580
+ NEGOTIUM_BROWSER_OWNER_CAPABILITY: ownerCapability
2581
+ });
2216
2582
  }
2217
2583
  return {
2218
2584
  type: "sse",
2219
- url: `http://127.0.0.1:${port}/sse`,
2220
- headers: { "X-Browser-Owner": owner, "X-Browser-Capability": ownerCapability }
2585
+ url: `http://127.0.0.1:${port}/sse?${query}`,
2586
+ headers: { "X-Browser-Capability": ownerCapability }
2221
2587
  };
2222
2588
  }
2223
2589
  function longLivedHttpMcp(agent, port) {
@@ -2495,288 +2861,119 @@ function getForumMcpServers(opts) {
2495
2861
  } = opts;
2496
2862
  const filter = (name) => {
2497
2863
  if (silent && name === "task")
2498
- return false;
2499
- if (enabled === null)
2500
- return true;
2501
- return enabled.includes(name) || REQUIRED_FORUM_MCP_SERVERS.includes(name);
2502
- };
2503
- const base = buildScope("forum", {
2504
- userId,
2505
- session,
2506
- topicId,
2507
- queryId,
2508
- wikiTopicId,
2509
- agent,
2510
- cwd,
2511
- model,
2512
- currentUserPrompt,
2513
- depth,
2514
- playwrightPort,
2515
- playwrightCapability,
2516
- bgBashPort,
2517
- autoContinue,
2518
- visualTools,
2519
- fileDeliveryTools,
2520
- silent,
2521
- peerBridge
2522
- }, filter);
2523
- return { ...base, ...extra };
2524
- }
2525
- function getCronMcpServers(opts) {
2526
- return buildScope("cron", {
2527
- userId: opts.userId,
2528
- session: opts.session,
2529
- topicId: opts.topicId,
2530
- queryId: opts.queryId,
2531
- wikiTopicId: opts.wikiTopicId ?? opts.topicId,
2532
- agent: opts.agent,
2533
- cwd: opts.cwd,
2534
- model: opts.model,
2535
- currentUserPrompt: opts.currentUserPrompt,
2536
- playwrightPort: opts.playwrightPort,
2537
- playwrightCapability: opts.playwrightCapability,
2538
- autoContinue: false,
2539
- visualTools: opts.visualTools,
2540
- fileDeliveryTools: opts.fileDeliveryTools
2541
- });
2542
- }
2543
- function getMcpServersForQuery(opts) {
2544
- if (opts.sessionType === "cron") {
2545
- if (!opts.topicId)
2546
- throw new Error("getMcpServersForQuery: cron sessionType requires topicId");
2547
- return getCronMcpServers({
2548
- userId: opts.userId || "default",
2549
- session: opts.session || "cron",
2550
- topicId: opts.topicId,
2551
- queryId: opts.queryId,
2552
- wikiTopicId: opts.wikiTopicId,
2553
- agent: opts.agent,
2554
- cwd: opts.cwd,
2555
- model: opts.model,
2556
- currentUserPrompt: opts.prompt,
2557
- playwrightPort: opts.playwrightPort,
2558
- playwrightCapability: opts.playwrightCapability,
2559
- visualTools: opts.visualTools,
2560
- fileDeliveryTools: opts.fileDeliveryTools
2561
- });
2562
- }
2563
- if (opts.sessionType === "dm" || opts.sessionType === "ephemeral") {
2564
- return getDmMcpServers({
2565
- userId: opts.userId || "default",
2566
- agent: opts.agent,
2567
- playwrightPort: opts.playwrightPort,
2568
- playwrightCapability: opts.playwrightCapability
2569
- });
2570
- }
2571
- if (opts.sessionType === "manager") {
2572
- return getManagerMcpServers({
2573
- userId: opts.userId || "default",
2574
- session: opts.session,
2575
- topicId: opts.topicId,
2576
- queryId: opts.queryId,
2577
- wikiTopicId: opts.wikiTopicId,
2578
- agent: opts.agent,
2579
- cwd: opts.cwd,
2580
- model: opts.model,
2581
- currentUserPrompt: opts.prompt,
2582
- playwrightPort: opts.playwrightPort,
2583
- playwrightCapability: opts.playwrightCapability,
2584
- autoContinue: opts.autoContinue,
2585
- visualTools: opts.visualTools,
2586
- fileDeliveryTools: opts.fileDeliveryTools
2587
- });
2588
- }
2589
- return getForumMcpServers({
2590
- userId: opts.userId || "default",
2591
- session: opts.session || "default",
2592
- topicId: opts.topicId,
2593
- queryId: opts.queryId,
2594
- wikiTopicId: opts.wikiTopicId,
2595
- agent: opts.agent,
2596
- cwd: opts.cwd,
2597
- model: opts.model,
2598
- currentUserPrompt: opts.prompt,
2599
- playwrightPort: opts.playwrightPort,
2600
- playwrightCapability: opts.playwrightCapability,
2601
- bgBashPort: opts.bgBashPort,
2602
- autoContinue: opts.autoContinue,
2603
- visualTools: opts.visualTools,
2604
- fileDeliveryTools: opts.fileDeliveryTools,
2605
- depth: opts.depth,
2606
- enabled: opts.mcpEnabled,
2607
- extra: opts.mcpExtra,
2608
- silent: opts.silent,
2609
- peerBridge: opts.peerBridge
2610
- });
2611
- }
2612
-
2613
- // ../../packages/core/src/storage/vault.ts
2614
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync7 } from "fs";
2615
- import { join as join10 } from "path";
2616
-
2617
- // ../../packages/core/src/storage/vault-crypto.ts
2618
- import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes4 } from "crypto";
2619
- var ENVELOPE_PREFIX = "otium-vault:v1:";
2620
- var IV_BYTES = 12;
2621
- var KEY_BYTES = 32;
2622
- function encryptionKey(masterKey = VAULT_MASTER_KEY) {
2623
- return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
2624
- }
2625
- function aad(userId, key) {
2626
- return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
2627
- }
2628
- function isEncryptedVaultValue(value) {
2629
- return value.startsWith(ENVELOPE_PREFIX);
2630
- }
2631
- function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
2632
- const iv = randomBytes4(IV_BYTES);
2633
- const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
2634
- cipher.setAAD(aad(userId, key));
2635
- const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
2636
- const tag = cipher.getAuthTag();
2637
- return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
2638
- }
2639
- function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
2640
- if (!isEncryptedVaultValue(storedValue)) {
2641
- return { value: storedValue, legacyPlaintext: true };
2642
- }
2643
- const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
2644
- const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
2645
- if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
2646
- throw new Error("Invalid encrypted vault value");
2647
- }
2648
- const iv = Buffer.from(ivPart, "base64url");
2649
- const ciphertext = Buffer.from(ciphertextPart, "base64url");
2650
- const tag = Buffer.from(tagPart, "base64url");
2651
- if (iv.length !== IV_BYTES || tag.length !== 16) {
2652
- throw new Error("Invalid encrypted vault value");
2653
- }
2654
- const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
2655
- decipher.setAAD(aad(userId, key));
2656
- decipher.setAuthTag(tag);
2657
- const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
2658
- return { value: plaintext.toString("utf8"), legacyPlaintext: false };
2659
- }
2660
-
2661
- // ../../packages/core/src/storage/vault.ts
2662
- var vaultDb;
2663
- var vaultMasterKey = VAULT_MASTER_KEY;
2664
- function initializeVaultDatabase(database) {
2665
- database.exec("PRAGMA journal_mode = WAL");
2666
- database.exec("PRAGMA busy_timeout = 5000");
2667
- database.exec(`
2668
- CREATE TABLE IF NOT EXISTS vault (
2669
- user_id TEXT NOT NULL,
2670
- key TEXT NOT NULL,
2671
- value TEXT NOT NULL,
2672
- description TEXT NOT NULL DEFAULT '',
2673
- PRIMARY KEY (user_id, key)
2674
- )
2675
- `);
2676
- {
2677
- const cols = database.prepare("PRAGMA table_info(vault)").all();
2678
- const uid = cols.find((c) => c.name === "user_id");
2679
- if (uid && uid.type.toUpperCase() === "INTEGER") {
2680
- database.exec("BEGIN");
2681
- database.exec(`
2682
- CREATE TABLE vault_migrated (
2683
- user_id TEXT NOT NULL,
2684
- key TEXT NOT NULL,
2685
- value TEXT NOT NULL,
2686
- description TEXT NOT NULL DEFAULT '',
2687
- PRIMARY KEY (user_id, key)
2688
- )
2689
- `);
2690
- database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
2691
- database.exec("DROP TABLE vault");
2692
- database.exec("ALTER TABLE vault_migrated RENAME TO vault");
2693
- database.exec("COMMIT");
2694
- }
2695
- }
2696
- }
2697
- function openVaultDatabase(dataDir) {
2698
- const path = join10(dataDir, "vault.db");
2699
- mkdirSync7(dataDir, { recursive: true });
2700
- const database = new Database(path, { create: true });
2701
- chmodSync2(path, 384);
2702
- initializeVaultDatabase(database);
2703
- return database;
2704
- }
2705
- function activeVaultDatabase() {
2706
- if (!vaultDb)
2707
- vaultDb = openVaultDatabase(DATA_DIR);
2708
- return vaultDb;
2709
- }
2710
- var VAULT_VALUE_MAX_BYTES = 64 * 1024;
2711
- function normalizeVaultKey(key) {
2712
- return key.trim().toUpperCase();
2713
- }
2714
- function decryptRow(userId, key, storedValue) {
2715
- const database = activeVaultDatabase();
2716
- const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
2717
- if (decoded.legacyPlaintext) {
2718
- database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
2719
- }
2720
- return decoded.value;
2721
- }
2722
- function vaultListWithValues(userId) {
2723
- const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
2724
- return rows.map((row) => ({
2725
- key: row.key,
2726
- description: row.description,
2727
- value: decryptRow(userId, row.key, row.value)
2728
- }));
2864
+ return false;
2865
+ if (enabled === null)
2866
+ return true;
2867
+ return enabled.includes(name) || REQUIRED_FORUM_MCP_SERVERS.includes(name);
2868
+ };
2869
+ const base = buildScope("forum", {
2870
+ userId,
2871
+ session,
2872
+ topicId,
2873
+ queryId,
2874
+ wikiTopicId,
2875
+ agent,
2876
+ cwd,
2877
+ model,
2878
+ currentUserPrompt,
2879
+ depth,
2880
+ playwrightPort,
2881
+ playwrightCapability,
2882
+ bgBashPort,
2883
+ autoContinue,
2884
+ visualTools,
2885
+ fileDeliveryTools,
2886
+ silent,
2887
+ peerBridge
2888
+ }, filter);
2889
+ return { ...base, ...extra };
2729
2890
  }
2730
- function vaultSubstituteDetailed(userId, text) {
2731
- const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
2732
- const usedKeys = new Set;
2733
- const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
2734
- const key = normalizeVaultKey(rawKey);
2735
- const value = entries.get(key);
2736
- if (value === undefined)
2737
- return match;
2738
- usedKeys.add(key);
2739
- return value;
2891
+ function getCronMcpServers(opts) {
2892
+ return buildScope("cron", {
2893
+ userId: opts.userId,
2894
+ session: opts.session,
2895
+ topicId: opts.topicId,
2896
+ queryId: opts.queryId,
2897
+ wikiTopicId: opts.wikiTopicId ?? opts.topicId,
2898
+ agent: opts.agent,
2899
+ cwd: opts.cwd,
2900
+ model: opts.model,
2901
+ currentUserPrompt: opts.currentUserPrompt,
2902
+ playwrightPort: opts.playwrightPort,
2903
+ playwrightCapability: opts.playwrightCapability,
2904
+ autoContinue: false,
2905
+ visualTools: opts.visualTools,
2906
+ fileDeliveryTools: opts.fileDeliveryTools
2740
2907
  });
2741
- return { text: substituted, usedKeys: [...usedKeys] };
2742
- }
2743
- function encodedSecretForms(value) {
2744
- const forms = new Set([
2745
- value,
2746
- encodeURIComponent(value),
2747
- Buffer.from(value, "utf8").toString("base64"),
2748
- Buffer.from(value, "utf8").toString("base64url"),
2749
- Buffer.from(value, "utf8").toString("hex")
2750
- ]);
2751
- forms.delete("");
2752
- return [...forms].sort((a, b) => b.length - a.length);
2753
2908
  }
2754
- function redactVaultSecrets(userId, text) {
2755
- const candidates = vaultListWithValues(userId).flatMap((entry) => encodedSecretForms(entry.value).map((form) => ({ form, key: entry.key }))).sort((a, b) => b.form.length - a.form.length || a.key.localeCompare(b.key));
2756
- if (candidates.length === 0)
2757
- return text;
2758
- const candidatesByFirstCharacter = new Map;
2759
- for (const candidate of candidates) {
2760
- const first = candidate.form[0];
2761
- if (!first)
2762
- continue;
2763
- const bucket = candidatesByFirstCharacter.get(first) ?? [];
2764
- bucket.push(candidate);
2765
- candidatesByFirstCharacter.set(first, bucket);
2909
+ function getMcpServersForQuery(opts) {
2910
+ if (opts.sessionType === "cron") {
2911
+ if (!opts.topicId)
2912
+ throw new Error("getMcpServersForQuery: cron sessionType requires topicId");
2913
+ return getCronMcpServers({
2914
+ userId: opts.userId || "default",
2915
+ session: opts.session || "cron",
2916
+ topicId: opts.topicId,
2917
+ queryId: opts.queryId,
2918
+ wikiTopicId: opts.wikiTopicId,
2919
+ agent: opts.agent,
2920
+ cwd: opts.cwd,
2921
+ model: opts.model,
2922
+ currentUserPrompt: opts.prompt,
2923
+ playwrightPort: opts.playwrightPort,
2924
+ playwrightCapability: opts.playwrightCapability,
2925
+ visualTools: opts.visualTools,
2926
+ fileDeliveryTools: opts.fileDeliveryTools
2927
+ });
2766
2928
  }
2767
- let redacted = "";
2768
- let offset = 0;
2769
- while (offset < text.length) {
2770
- const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
2771
- if (!match) {
2772
- redacted += text[offset];
2773
- offset += 1;
2774
- continue;
2775
- }
2776
- redacted += `[REDACTED:${match.key}]`;
2777
- offset += match.form.length;
2929
+ if (opts.sessionType === "dm" || opts.sessionType === "ephemeral") {
2930
+ return getDmMcpServers({
2931
+ userId: opts.userId || "default",
2932
+ agent: opts.agent,
2933
+ playwrightPort: opts.playwrightPort,
2934
+ playwrightCapability: opts.playwrightCapability
2935
+ });
2778
2936
  }
2779
- return redacted;
2937
+ if (opts.sessionType === "manager") {
2938
+ return getManagerMcpServers({
2939
+ userId: opts.userId || "default",
2940
+ session: opts.session,
2941
+ topicId: opts.topicId,
2942
+ queryId: opts.queryId,
2943
+ wikiTopicId: opts.wikiTopicId,
2944
+ agent: opts.agent,
2945
+ cwd: opts.cwd,
2946
+ model: opts.model,
2947
+ currentUserPrompt: opts.prompt,
2948
+ playwrightPort: opts.playwrightPort,
2949
+ playwrightCapability: opts.playwrightCapability,
2950
+ autoContinue: opts.autoContinue,
2951
+ visualTools: opts.visualTools,
2952
+ fileDeliveryTools: opts.fileDeliveryTools
2953
+ });
2954
+ }
2955
+ return getForumMcpServers({
2956
+ userId: opts.userId || "default",
2957
+ session: opts.session || "default",
2958
+ topicId: opts.topicId,
2959
+ queryId: opts.queryId,
2960
+ wikiTopicId: opts.wikiTopicId,
2961
+ agent: opts.agent,
2962
+ cwd: opts.cwd,
2963
+ model: opts.model,
2964
+ currentUserPrompt: opts.prompt,
2965
+ playwrightPort: opts.playwrightPort,
2966
+ playwrightCapability: opts.playwrightCapability,
2967
+ bgBashPort: opts.bgBashPort,
2968
+ autoContinue: opts.autoContinue,
2969
+ visualTools: opts.visualTools,
2970
+ fileDeliveryTools: opts.fileDeliveryTools,
2971
+ depth: opts.depth,
2972
+ enabled: opts.mcpEnabled,
2973
+ extra: opts.mcpExtra,
2974
+ silent: opts.silent,
2975
+ peerBridge: opts.peerBridge
2976
+ });
2780
2977
  }
2781
2978
 
2782
2979
  // ../../packages/core/src/agents/execution-host.ts
@@ -3218,12 +3415,6 @@ async function* claudeProvider(opts) {
3218
3415
  const m = message;
3219
3416
  if (m.subtype === "init") {
3220
3417
  yield { type: "session", sessionId: m.session_id };
3221
- } else if (m.subtype === "task_started") {
3222
- const t = m;
3223
- if (t.subagent_type && !t.skip_transcript) {
3224
- const desc = t.description ? ` ${t.description.slice(0, 60)}` : "";
3225
- yield { type: "status", content: `\u25B6 [${t.subagent_type}]${desc}` };
3226
- }
3227
3418
  } else {
3228
3419
  logger.debug({ subtype: m.subtype, msg: m }, "claudeProvider: system message (unhandled subtype)");
3229
3420
  }
@@ -3307,7 +3498,8 @@ async function* claudeProvider(opts) {
3307
3498
  yield {
3308
3499
  type: "tool_result",
3309
3500
  toolUseId: trBlock.tool_use_id || "",
3310
- content: trContent
3501
+ content: trContent,
3502
+ ...trBlock.is_error ? { isError: true } : {}
3311
3503
  };
3312
3504
  }
3313
3505
  }
@@ -3321,39 +3513,98 @@ async function* claudeProvider(opts) {
3321
3513
  }
3322
3514
 
3323
3515
  // ../../packages/core/src/agents/codex-provider.ts
3324
- import { existsSync as existsSync10 } from "fs";
3516
+ import { execFileSync as execFileSync5 } from "child_process";
3517
+ import { existsSync as existsSync10, readFileSync as readFileSync8, realpathSync as realpathSync3, statSync as statSync3 } from "fs";
3518
+ import { isAbsolute, relative, resolve as resolve6 } from "path";
3325
3519
  import { Codex } from "@openai/codex-sdk";
3326
3520
 
3327
3521
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
3328
3522
  import { spawn as spawn3 } from "child_process";
3523
+ import { randomUUID as randomUUID2 } from "crypto";
3329
3524
  import {
3330
3525
  chmodSync as chmodSync3,
3526
+ copyFileSync,
3331
3527
  existsSync as existsSync9,
3528
+ mkdtempSync,
3332
3529
  readFileSync as readFileSync7,
3333
3530
  renameSync as renameSync4,
3531
+ rmSync,
3334
3532
  unlinkSync as unlinkSync8,
3335
3533
  writeFileSync as writeFileSync5
3336
3534
  } from "fs";
3337
3535
  import { createRequire } from "module";
3338
- import { dirname as dirname6, join as join11 } from "path";
3536
+ import { tmpdir } from "os";
3537
+ import { dirname as dirname7, join as join11 } from "path";
3339
3538
 
3340
3539
  // ../../packages/core/src/version.ts
3341
- var NEGOTIUM_VERSION = "0.1.28";
3540
+ var NEGOTIUM_VERSION = "0.1.30";
3342
3541
 
3343
3542
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
3344
- var NEGOTIUM_MODEL_CATALOG = "negotium-model-catalog.json";
3345
3543
  var moduleRequire = createRequire(import.meta.url);
3544
+ var codexSdkPackagePath = moduleRequire.resolve("@openai/codex-sdk/package.json");
3545
+ var codexSdkRequire = createRequire(codexSdkPackagePath);
3546
+ var bundledCodexPackagePath = codexSdkRequire.resolve("@openai/codex/package.json");
3547
+ function readPackageVersion(packageJsonPath) {
3548
+ const parsed = JSON.parse(readFileSync7(packageJsonPath, "utf8"));
3549
+ if (typeof parsed.version !== "string" || !parsed.version.trim()) {
3550
+ throw new Error(`Codex package has no valid version: ${packageJsonPath}`);
3551
+ }
3552
+ return parsed.version;
3553
+ }
3554
+ var BUNDLED_CODEX_VERSION = readPackageVersion(bundledCodexPackagePath);
3555
+ var SAFE_BUNDLED_CODEX_VERSION = BUNDLED_CODEX_VERSION.replace(/[^a-zA-Z0-9._-]/g, "_");
3556
+ var NEGOTIUM_MODEL_CACHE = `negotium-models-cache-${SAFE_BUNDLED_CODEX_VERSION}.json`;
3557
+ var NEGOTIUM_MODEL_CATALOG = `negotium-model-catalog-${SAFE_BUNDLED_CODEX_VERSION}.json`;
3346
3558
  function codexCliScriptPath() {
3347
- const sdkPackagePath = moduleRequire.resolve("@openai/codex-sdk/package.json");
3348
- const sdkRequire = createRequire(sdkPackagePath);
3349
- return join11(dirname6(sdkRequire.resolve("@openai/codex/package.json")), "bin", "codex.js");
3559
+ return join11(dirname7(bundledCodexPackagePath), "bin", "codex.js");
3560
+ }
3561
+ function parseCodexModelCache(contents, sourcePath) {
3562
+ let parsed;
3563
+ try {
3564
+ parsed = JSON.parse(contents);
3565
+ } catch (error) {
3566
+ throw new Error(`Codex model cache is invalid JSON: ${sourcePath}`, { cause: error });
3567
+ }
3568
+ if (!Array.isArray(parsed.models) || parsed.models.length === 0) {
3569
+ throw new Error(`Codex model cache has no models: ${sourcePath}`);
3570
+ }
3571
+ return parsed;
3572
+ }
3573
+ function readCodexModelCache(cachePath) {
3574
+ const contents = readFileSync7(cachePath, "utf8");
3575
+ return { contents, parsed: parseCodexModelCache(contents, cachePath) };
3576
+ }
3577
+ function readCompatibleCodexModelCache(cachePath) {
3578
+ const cache = readCodexModelCache(cachePath);
3579
+ if (cache.parsed.client_version !== BUNDLED_CODEX_VERSION) {
3580
+ const found = typeof cache.parsed.client_version === "string" ? cache.parsed.client_version : "missing or invalid";
3581
+ throw new Error(`Codex model cache version ${found} does not match Negotium's bundled Codex ${BUNDLED_CODEX_VERSION}: ${cachePath}`);
3582
+ }
3583
+ return cache;
3584
+ }
3585
+ function writePrivateFileAtomic(path, contents) {
3586
+ if (existsSync9(path) && readFileSync7(path, "utf8") === contents)
3587
+ return;
3588
+ const tempPath = `${path}.${process.pid}.${randomUUID2()}.tmp`;
3589
+ try {
3590
+ writeFileSync5(tempPath, contents, { encoding: "utf8", mode: 384 });
3591
+ renameSync4(tempPath, path);
3592
+ chmodSync3(path, 384);
3593
+ } finally {
3594
+ try {
3595
+ unlinkSync8(tempPath);
3596
+ } catch {}
3597
+ }
3598
+ }
3599
+ function bundledCodexModelCachePath(authFilePath) {
3600
+ return join11(dirname7(authFilePath), NEGOTIUM_MODEL_CACHE);
3350
3601
  }
3351
3602
  async function bootstrapCodexModelCache(codexHome, cachePath) {
3352
3603
  const child = spawn3(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
3353
3604
  env: { ...process.env, CODEX_HOME: codexHome },
3354
3605
  stdio: ["pipe", "pipe", "pipe"]
3355
3606
  });
3356
- await new Promise((resolve5, reject) => {
3607
+ await new Promise((resolve6, reject) => {
3357
3608
  let settled = false;
3358
3609
  let stdoutBuffer = "";
3359
3610
  let stderr = "";
@@ -3372,7 +3623,7 @@ async function bootstrapCodexModelCache(codexHome, cachePath) {
3372
3623
  else if (!existsSync9(cachePath))
3373
3624
  reject(new Error("Codex did not create its model cache"));
3374
3625
  else
3375
- resolve5();
3626
+ resolve6();
3376
3627
  };
3377
3628
  const send = (message) => {
3378
3629
  child.stdin.write(`${JSON.stringify(message)}
@@ -3430,25 +3681,59 @@ async function bootstrapCodexModelCache(codexHome, cachePath) {
3430
3681
  });
3431
3682
  });
3432
3683
  }
3684
+ async function bootstrapIsolatedCodexModelCache(authFilePath, bootstrap) {
3685
+ const sourceHome = dirname7(authFilePath);
3686
+ const isolatedHome = mkdtempSync(join11(tmpdir(), "negotium-codex-models-"));
3687
+ const isolatedCachePath = join11(isolatedHome, "models_cache.json");
3688
+ try {
3689
+ const isolatedAuthPath = join11(isolatedHome, "auth.json");
3690
+ copyFileSync(authFilePath, isolatedAuthPath);
3691
+ chmodSync3(isolatedAuthPath, 384);
3692
+ const sourceConfigPath = join11(sourceHome, "config.toml");
3693
+ if (existsSync9(sourceConfigPath)) {
3694
+ const isolatedConfigPath = join11(isolatedHome, "config.toml");
3695
+ copyFileSync(sourceConfigPath, isolatedConfigPath);
3696
+ chmodSync3(isolatedConfigPath, 384);
3697
+ }
3698
+ await bootstrap(isolatedHome, isolatedCachePath);
3699
+ return readCompatibleCodexModelCache(isolatedCachePath).contents;
3700
+ } finally {
3701
+ rmSync(isolatedHome, { recursive: true, force: true });
3702
+ }
3703
+ }
3433
3704
  async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexModelCache) {
3434
- const codexHome = dirname6(authFilePath);
3435
- const cachePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE ?? join11(codexHome, "models_cache.json");
3436
- const hardenedCatalogPath = join11(codexHome, NEGOTIUM_MODEL_CATALOG);
3437
- if (existsSync9(cachePath) || existsSync9(hardenedCatalogPath))
3438
- return;
3439
- if (process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE) {
3440
- throw new Error(`Configured Codex model cache does not exist: ${cachePath}`);
3705
+ const codexHome = dirname7(authFilePath);
3706
+ const configuredCachePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE;
3707
+ if (configuredCachePath) {
3708
+ if (!existsSync9(configuredCachePath)) {
3709
+ throw new Error(`Configured Codex model cache does not exist: ${configuredCachePath}`);
3710
+ }
3711
+ readCompatibleCodexModelCache(configuredCachePath);
3712
+ return configuredCachePath;
3713
+ }
3714
+ const bundledCachePath = bundledCodexModelCachePath(authFilePath);
3715
+ const sharedCachePath = join11(codexHome, "models_cache.json");
3716
+ if (existsSync9(sharedCachePath)) {
3717
+ try {
3718
+ const shared = readCompatibleCodexModelCache(sharedCachePath);
3719
+ writePrivateFileAtomic(bundledCachePath, shared.contents);
3720
+ return bundledCachePath;
3721
+ } catch {}
3722
+ }
3723
+ if (existsSync9(bundledCachePath)) {
3724
+ try {
3725
+ readCompatibleCodexModelCache(bundledCachePath);
3726
+ return bundledCachePath;
3727
+ } catch {}
3441
3728
  }
3442
- await bootstrap(codexHome, cachePath);
3729
+ const refreshedContents = await bootstrapIsolatedCodexModelCache(authFilePath, bootstrap);
3730
+ writePrivateFileAtomic(bundledCachePath, refreshedContents);
3731
+ return bundledCachePath;
3443
3732
  }
3444
- function writeCodexCatalogWithNativeMultiAgentDisabled(authFilePath) {
3445
- const codexHome = dirname6(authFilePath);
3446
- const sourcePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE ?? (existsSync9(join11(codexHome, "models_cache.json")) ? join11(codexHome, "models_cache.json") : join11(codexHome, NEGOTIUM_MODEL_CATALOG));
3733
+ function writeCodexCatalogWithNativeMultiAgentDisabled(authFilePath, sourcePath) {
3734
+ const codexHome = dirname7(authFilePath);
3447
3735
  const outputPath = join11(codexHome, NEGOTIUM_MODEL_CATALOG);
3448
- const parsed = JSON.parse(readFileSync7(sourcePath, "utf8"));
3449
- if (!Array.isArray(parsed.models) || parsed.models.length === 0) {
3450
- throw new Error(`Codex model cache has no models: ${sourcePath}`);
3451
- }
3736
+ const parsed = readCodexModelCache(sourcePath).parsed;
3452
3737
  const models = parsed.models.map((model, index) => {
3453
3738
  if (!model || typeof model !== "object" || Array.isArray(model)) {
3454
3739
  throw new Error(`Codex model cache entry ${index} is invalid: ${sourcePath}`);
@@ -3457,22 +3742,56 @@ function writeCodexCatalogWithNativeMultiAgentDisabled(authFilePath) {
3457
3742
  });
3458
3743
  const contents = `${JSON.stringify({ models }, null, 2)}
3459
3744
  `;
3460
- if (existsSync9(outputPath) && readFileSync7(outputPath, "utf8") === contents) {
3461
- return outputPath;
3462
- }
3463
- const tempPath = `${outputPath}.${process.pid}.${Date.now()}.tmp`;
3464
- try {
3465
- writeFileSync5(tempPath, contents, { encoding: "utf8", mode: 384 });
3466
- renameSync4(tempPath, outputPath);
3467
- chmodSync3(outputPath, 384);
3468
- } finally {
3469
- try {
3470
- unlinkSync8(tempPath);
3471
- } catch {}
3472
- }
3745
+ writePrivateFileAtomic(outputPath, contents);
3473
3746
  return outputPath;
3474
3747
  }
3475
3748
 
3749
+ // ../../packages/core/src/agents/tool-format.ts
3750
+ import { diffLines as computeLineDiff } from "diff";
3751
+ function changedDiffLines(value) {
3752
+ const lines = value.split(`
3753
+ `);
3754
+ if (lines.at(-1) === "")
3755
+ lines.pop();
3756
+ return lines;
3757
+ }
3758
+ function buildNumberedDiffSummary(before, after, startLine = 1) {
3759
+ const removed = [];
3760
+ const added = [];
3761
+ const preview = [];
3762
+ let oldLine = startLine;
3763
+ let newLine = startLine;
3764
+ for (const part of computeLineDiff(before, after)) {
3765
+ const lines = changedDiffLines(part.value);
3766
+ if (part.removed) {
3767
+ for (const line of lines) {
3768
+ removed.push(line);
3769
+ preview.push(`${oldLine} -${line}`);
3770
+ oldLine += 1;
3771
+ }
3772
+ continue;
3773
+ }
3774
+ if (part.added) {
3775
+ for (const line of lines) {
3776
+ added.push(line);
3777
+ preview.push(`${newLine} +${line}`);
3778
+ newLine += 1;
3779
+ }
3780
+ continue;
3781
+ }
3782
+ oldLine += lines.length;
3783
+ newLine += lines.length;
3784
+ }
3785
+ return {
3786
+ ...removed.length > 0 ? { before: removed.join(`
3787
+ `) } : {},
3788
+ ...added.length > 0 ? { after: added.join(`
3789
+ `) } : {},
3790
+ ...preview.length > 0 ? { diffPreview: preview.join(`
3791
+ `) } : {}
3792
+ };
3793
+ }
3794
+
3476
3795
  // ../../packages/core/src/agents/codex-provider.ts
3477
3796
  function mapEffort(effort) {
3478
3797
  if (!effort)
@@ -3555,22 +3874,142 @@ function summarizeMcpToolCallResult(item) {
3555
3874
  return text.slice(0, 200);
3556
3875
  return JSON.stringify(item.result.structured_content ?? "").slice(0, 200);
3557
3876
  }
3558
- function fileChangeEvents(item) {
3877
+ var CODEX_DIFF_FILE_LIMIT = 512 * 1024;
3878
+ var CODEX_DIFF_BASELINE_FILE_LIMIT = 200;
3879
+ var CODEX_DIFF_BASELINE_BYTE_LIMIT = 16 * 1024 * 1024;
3880
+ function canonicalPath(path) {
3881
+ try {
3882
+ return realpathSync3(resolve6(path));
3883
+ } catch {
3884
+ return resolve6(path);
3885
+ }
3886
+ }
3887
+ function textFile(path, maxBytes = CODEX_DIFF_FILE_LIMIT) {
3888
+ if (!existsSync10(path))
3889
+ return null;
3890
+ try {
3891
+ const byteLimit = Math.min(CODEX_DIFF_FILE_LIMIT, Math.max(0, maxBytes));
3892
+ if (statSync3(path).size > byteLimit)
3893
+ return;
3894
+ const content = readFileSync8(path);
3895
+ if (content.byteLength > byteLimit || content.includes(0))
3896
+ return;
3897
+ return content.toString("utf8");
3898
+ } catch {
3899
+ return;
3900
+ }
3901
+ }
3902
+
3903
+ class CodexFilePreviewTracker {
3904
+ #cwd;
3905
+ #root;
3906
+ #baseline = new Map;
3907
+ #baselineAvailable = false;
3908
+ constructor(cwd) {
3909
+ this.#cwd = canonicalPath(cwd);
3910
+ const root = this.#git(["rev-parse", "--show-toplevel"], this.#cwd)?.trim();
3911
+ this.#root = root ? canonicalPath(root) : null;
3912
+ if (!this.#root)
3913
+ return;
3914
+ const status = this.#git(["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
3915
+ if (status === undefined)
3916
+ return;
3917
+ this.#baselineAvailable = true;
3918
+ let loadedFiles = 0;
3919
+ let loadedBytes = 0;
3920
+ const records = status.split("\x00");
3921
+ for (let index = 0;index < records.length; index += 1) {
3922
+ const record = records[index];
3923
+ if (!record || record.length < 4)
3924
+ continue;
3925
+ const code = record.slice(0, 2);
3926
+ const path = record.slice(3);
3927
+ const remainingBytes = CODEX_DIFF_BASELINE_BYTE_LIMIT - loadedBytes;
3928
+ const content = loadedFiles < CODEX_DIFF_BASELINE_FILE_LIMIT && remainingBytes > 0 ? textFile(resolve6(this.#root, path), remainingBytes) : undefined;
3929
+ this.#baseline.set(path, content);
3930
+ if (typeof content === "string") {
3931
+ loadedFiles += 1;
3932
+ loadedBytes += Buffer.byteLength(content);
3933
+ }
3934
+ if (/[RC]/.test(code))
3935
+ index += 1;
3936
+ }
3937
+ }
3938
+ preview(path, succeeded) {
3939
+ if (!this.#root || !this.#baselineAvailable || !succeeded)
3940
+ return {};
3941
+ const absolute = canonicalPath(isAbsolute(path) ? path : resolve6(this.#cwd, path));
3942
+ const relativePath = relative(this.#root, absolute);
3943
+ if (!relativePath || relativePath.startsWith("..") || isAbsolute(relativePath))
3944
+ return {};
3945
+ const before = this.#baseline.has(relativePath) ? this.#baseline.get(relativePath) : this.#headFile(relativePath);
3946
+ const after = textFile(absolute);
3947
+ this.#baseline.set(relativePath, after);
3948
+ if (before === undefined || after === undefined)
3949
+ return {};
3950
+ const diff = buildNumberedDiffSummary(before ?? "", after ?? "");
3951
+ return {
3952
+ ...diff.before !== undefined ? { before: diff.before } : {},
3953
+ ...diff.after !== undefined ? { after: diff.after } : {},
3954
+ ...diff.diffPreview !== undefined ? { diff_preview: diff.diffPreview } : {}
3955
+ };
3956
+ }
3957
+ #headFile(path) {
3958
+ const content = this.#git(["show", `HEAD:${path}`]);
3959
+ if (content === undefined)
3960
+ return null;
3961
+ return Buffer.byteLength(content) > CODEX_DIFF_FILE_LIMIT || content.includes("\x00") ? undefined : content;
3962
+ }
3963
+ #git(args, cwd = this.#root ?? undefined) {
3964
+ try {
3965
+ return execFileSync5("git", ["-C", cwd ?? ".", ...args], {
3966
+ encoding: "utf8",
3967
+ maxBuffer: CODEX_DIFF_FILE_LIMIT * 4,
3968
+ stdio: ["ignore", "pipe", "ignore"]
3969
+ });
3970
+ } catch {
3971
+ return;
3972
+ }
3973
+ }
3974
+ }
3975
+ async function fileChangeEvents(item, previews, cwd, threadId, consumedPatchCallIds) {
3976
+ const expectedPaths = item.changes.map((change) => isAbsolute(change.path) ? change.path : resolve6(cwd, change.path));
3977
+ let nativePreview;
3978
+ if (threadId && item.status === "completed") {
3979
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, 20));
3980
+ nativePreview = readLatestCodexPatchPreview(threadId, expectedPaths, consumedPatchCallIds);
3981
+ }
3982
+ for (const delayMs of [20, 40, 80, 120]) {
3983
+ if (nativePreview || !threadId || item.status !== "completed")
3984
+ break;
3985
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, delayMs));
3986
+ nativePreview = readLatestCodexPatchPreview(threadId, expectedPaths, consumedPatchCallIds);
3987
+ }
3988
+ if (nativePreview)
3989
+ consumedPatchCallIds.add(nativePreview.callId);
3559
3990
  return item.changes.flatMap((change, index) => {
3560
3991
  const name = change.kind === "add" ? "Write" : change.kind === "delete" ? "Delete" : "Edit";
3561
3992
  const toolUseId = `${item.id}:${index}`;
3562
3993
  const success = item.status === "completed";
3994
+ const nativeChange = nativePreview?.changes[index];
3995
+ const fallbackPreview = previews.preview(change.path, success);
3996
+ const preview = success && nativeChange ? {
3997
+ ...nativeChange.before !== undefined ? { before: nativeChange.before } : {},
3998
+ ...nativeChange.after !== undefined ? { after: nativeChange.after } : {},
3999
+ ...nativeChange.diffPreview !== undefined ? { diff_preview: nativeChange.diffPreview } : {}
4000
+ } : fallbackPreview;
3563
4001
  return [
3564
4002
  {
3565
4003
  type: "tool_use",
3566
4004
  name,
3567
- input: { file_path: change.path, change_kind: change.kind },
4005
+ input: { file_path: change.path, change_kind: change.kind, ...preview },
3568
4006
  toolUseId
3569
4007
  },
3570
4008
  {
3571
4009
  type: "tool_result",
3572
4010
  toolUseId,
3573
- content: success ? `${change.kind} applied: ${change.path}` : `${change.kind} failed: ${change.path}`
4011
+ content: success ? `${change.kind} applied: ${change.path}` : `${change.kind} failed: ${change.path}`,
4012
+ ...success ? {} : { isError: true }
3574
4013
  }
3575
4014
  ];
3576
4015
  });
@@ -3656,6 +4095,8 @@ function prependFirstEvent(iter, firstEvent, alreadyDone) {
3656
4095
  };
3657
4096
  }
3658
4097
  async function* codexProvider(opts) {
4098
+ const filePreviews = new CodexFilePreviewTracker(opts.cwd);
4099
+ const consumedPatchCallIds = new Set(opts.sessionId ? readCodexPatchCallIds(opts.sessionId) : []);
3659
4100
  const codexAuthPath = hostedCodexAuthFilePath();
3660
4101
  if (!existsSync10(codexAuthPath)) {
3661
4102
  yield {
@@ -3666,8 +4107,8 @@ async function* codexProvider(opts) {
3666
4107
  }
3667
4108
  let codexModelCatalogPath;
3668
4109
  try {
3669
- await ensureCodexModelCache(codexAuthPath);
3670
- codexModelCatalogPath = writeCodexCatalogWithNativeMultiAgentDisabled(codexAuthPath);
4110
+ const codexModelCachePath = await ensureCodexModelCache(codexAuthPath);
4111
+ codexModelCatalogPath = writeCodexCatalogWithNativeMultiAgentDisabled(codexAuthPath, codexModelCachePath);
3671
4112
  if (opts.sessionId)
3672
4113
  migrateCodexRolloutNativeMultiAgentMetadata(opts.sessionId);
3673
4114
  } catch (err) {
@@ -3790,10 +4231,11 @@ async function* codexProvider(opts) {
3790
4231
  if (!item)
3791
4232
  break;
3792
4233
  if (item.type === "command_execution") {
4234
+ const command = String(item.command ?? "");
3793
4235
  yield {
3794
4236
  type: "tool_use",
3795
4237
  name: "Bash",
3796
- input: { command: String(item.command ?? "") },
4238
+ input: { command },
3797
4239
  toolUseId: String(item.id ?? "")
3798
4240
  };
3799
4241
  } else if (item.type === "mcp_tool_call") {
@@ -3839,19 +4281,24 @@ async function* codexProvider(opts) {
3839
4281
  if (reasoning)
3840
4282
  yield { type: "reasoning", content: reasoning };
3841
4283
  } else if (item.type === "mcp_tool_call") {
4284
+ const isError = item.status === "failed" || Boolean(item.error);
3842
4285
  yield {
3843
4286
  type: "tool_result",
3844
4287
  toolUseId: String(item.id ?? ""),
3845
- content: summarizeMcpToolCallResult(item)
4288
+ content: summarizeMcpToolCallResult(item),
4289
+ ...isError ? { isError: true } : {}
3846
4290
  };
3847
4291
  } else if (item.type === "command_execution") {
4292
+ const isError = item.status === "failed" || typeof item.exit_code === "number" && item.exit_code !== 0;
3848
4293
  yield {
3849
4294
  type: "tool_result",
3850
4295
  toolUseId: String(item.id ?? ""),
3851
- content: String(item.aggregated_output ?? "").slice(0, 200)
4296
+ content: String(item.aggregated_output ?? "").slice(0, 200),
4297
+ ...isError ? { isError: true } : {}
3852
4298
  };
3853
4299
  } else if (item.type === "file_change") {
3854
- for (const fileEvent of fileChangeEvents(item)) {
4300
+ const fileEvents = await fileChangeEvents(item, filePreviews, opts.cwd, currentSessionId, consumedPatchCallIds);
4301
+ for (const fileEvent of fileEvents) {
3855
4302
  yield fileEvent;
3856
4303
  }
3857
4304
  } else if (item.type === "error") {
@@ -4000,6 +4447,18 @@ function providerOwnedToolRedirect(toolName) {
4000
4447
  }
4001
4448
  return "Use the shared task MCP tools instead " + "(mcp__task__task_create / task_update / task_list / task_get / task_delete).";
4002
4449
  }
4450
+ function resolveMaestroApiKeyOverrides(userId) {
4451
+ if (!userId)
4452
+ return;
4453
+ const deepseek = vaultGetValue(userId, "DEEPSEEK_API_KEY")?.trim();
4454
+ const moonshot = vaultGetValue(userId, "MOONSHOT_API_KEY")?.trim();
4455
+ if (!deepseek && !moonshot)
4456
+ return;
4457
+ return {
4458
+ ...deepseek ? { deepseek } : {},
4459
+ ...moonshot ? { moonshot } : {}
4460
+ };
4461
+ }
4003
4462
  function buildProviderOwnedToolBlockHook() {
4004
4463
  return {
4005
4464
  name: "provider-owned-tool-redirect",
@@ -4026,6 +4485,7 @@ function maestroProvider(opts) {
4026
4485
  maxTokens: MAESTRO_DEFAULT_MAX_TOKENS,
4027
4486
  enableToolSearch: true,
4028
4487
  ...opts,
4488
+ apiKeyOverrides: resolveMaestroApiKeyOverrides(userId),
4029
4489
  agent: "maestro",
4030
4490
  disallowedTools: buildMaestroDisallowedTools(callerDisallowedTools),
4031
4491
  toolHooks: [...buildMaestroToolHooks(userId), ...callerToolHooks]
@@ -4034,8 +4494,8 @@ function maestroProvider(opts) {
4034
4494
  }
4035
4495
 
4036
4496
  // ../../packages/core/src/storage/tasks.ts
4037
- import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync8, renameSync as renameSync5, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
4038
- import { dirname as dirname7, join as join12 } from "path";
4497
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, renameSync as renameSync5, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
4498
+ import { dirname as dirname8, join as join12 } from "path";
4039
4499
  function safeUserIdComponent2(userId) {
4040
4500
  const str = String(userId);
4041
4501
  if (!str || /[/\\]|\.\./.test(str)) {
@@ -4061,7 +4521,7 @@ function readTasks(userId, scopeKey) {
4061
4521
  if (!existsSync11(path))
4062
4522
  return [];
4063
4523
  try {
4064
- const parsed = JSON.parse(readFileSync8(path, "utf-8"));
4524
+ const parsed = JSON.parse(readFileSync9(path, "utf-8"));
4065
4525
  return Array.isArray(parsed?.tasks) ? parsed.tasks : [];
4066
4526
  } catch (e) {
4067
4527
  logger.warn({ err: e, path }, "tasks: failed to read task store");
@@ -4070,7 +4530,7 @@ function readTasks(userId, scopeKey) {
4070
4530
  }
4071
4531
  function taskFileMtimeNs(userId, scopeKey) {
4072
4532
  try {
4073
- return statSync3(getTaskFilePath(userId, scopeKey), { bigint: true }).mtimeNs;
4533
+ return statSync4(getTaskFilePath(userId, scopeKey), { bigint: true }).mtimeNs;
4074
4534
  } catch {
4075
4535
  return null;
4076
4536
  }
@@ -4207,7 +4667,7 @@ async function* runAgent(opts) {
4207
4667
  }
4208
4668
 
4209
4669
  // ../../packages/core/src/bus.ts
4210
- import { randomUUID as randomUUID2 } from "crypto";
4670
+ import { randomUUID as randomUUID3 } from "crypto";
4211
4671
 
4212
4672
  // ../../packages/core/src/storage/forum-db.ts
4213
4673
  var db = internalStorageDatabase;
@@ -4288,7 +4748,7 @@ class SqliteRuntimeBus {
4288
4748
  pollTimer = null;
4289
4749
  polling = false;
4290
4750
  constructor(options = {}) {
4291
- this.sourceId = options.sourceId ?? `${process.pid}-${randomUUID2()}`;
4751
+ this.sourceId = options.sourceId ?? `${process.pid}-${randomUUID3()}`;
4292
4752
  this.pollIntervalMs = Math.max(25, options.pollIntervalMs ?? 100);
4293
4753
  this.cursor = latestRuntimeEventSeq();
4294
4754
  }
@@ -4391,9 +4851,15 @@ class SqliteRuntimeBus {
4391
4851
  toolUseId: id
4392
4852
  });
4393
4853
  }
4394
- broadcastToolOutput(topicId, queryId, toolUseId, content) {
4854
+ broadcastToolOutput(topicId, queryId, toolUseId, content, isError) {
4395
4855
  const id = toolUseId.trim() || `${queryId}:tool`;
4396
- this.broadcastAiStatus(topicId, { kind: "tool_output", queryId, toolUseId: id, content });
4856
+ this.broadcastAiStatus(topicId, {
4857
+ kind: "tool_output",
4858
+ queryId,
4859
+ toolUseId: id,
4860
+ content,
4861
+ ...isError ? { isError: true } : {}
4862
+ });
4397
4863
  }
4398
4864
  broadcastToolStatus(topicId, queryId, kind, content, meta) {
4399
4865
  this.broadcastAiStatus(topicId, {
@@ -4435,8 +4901,8 @@ var WsHub = {
4435
4901
  };
4436
4902
 
4437
4903
  // ../../packages/core/src/prompts/builders.ts
4438
- import { readFileSync as readFileSync9 } from "fs";
4439
- import { resolve as resolve5 } from "path";
4904
+ import { readFileSync as readFileSync10 } from "fs";
4905
+ import { resolve as resolve7 } from "path";
4440
4906
 
4441
4907
  // ../../packages/core/src/agents/model-catalog.ts
4442
4908
  var CODEX_PRO_20X_COST = "ChatGPT Pro 20x subscription: $200/month";
@@ -4537,10 +5003,10 @@ var SELECTABLE_MODELS = [
4537
5003
  ];
4538
5004
 
4539
5005
  // ../../packages/core/src/prompts/builders.ts
4540
- var PROMPTS_DIR = resolve5(PROJECT_ROOT, "src/prompts");
4541
- var SESSIONS_DIR = resolve5(PROMPTS_DIR, "sessions");
5006
+ var PROMPTS_DIR = resolve7(PROJECT_ROOT, "src/prompts");
5007
+ var SESSIONS_DIR = resolve7(PROMPTS_DIR, "sessions");
4542
5008
  function loadAgentPrompt(filename) {
4543
- const raw = readFileSync9(resolve5(AGENTS_PROMPTS_DIR, filename), "utf-8");
5009
+ const raw = readFileSync10(resolve7(AGENTS_PROMPTS_DIR, filename), "utf-8");
4544
5010
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
4545
5011
  if (!match)
4546
5012
  throw new Error(`Agent prompt ${filename} is missing frontmatter`);
@@ -4721,7 +5187,7 @@ function setTopicBrief(topicId, fields) {
4721
5187
  }
4722
5188
 
4723
5189
  // ../../packages/core/src/storage/wiki.ts
4724
- import { basename, dirname as dirname8, join as join14 } from "path";
5190
+ import { basename as basename2, dirname as dirname9, join as join14 } from "path";
4725
5191
  function getSharedWikiDir(workspaceDir = resolveStorageWorkspaceDir()) {
4726
5192
  return workspaceDir === resolveStorageWorkspaceDir() ? resolveStorageSharedWikiDir() : join14(workspaceDir, "wiki");
4727
5193
  }
@@ -4741,7 +5207,7 @@ function wikiSummaryFilename(date, rawTopic, topicId) {
4741
5207
  }
4742
5208
 
4743
5209
  // ../../packages/core/src/topics/personal-general.ts
4744
- import { randomUUID as randomUUID3 } from "crypto";
5210
+ import { randomUUID as randomUUID4 } from "crypto";
4745
5211
 
4746
5212
  // ../../packages/core/src/platform/constants.ts
4747
5213
  var FROM_AUTO_CONTINUE = "auto-continue";
@@ -5250,7 +5716,7 @@ function ensurePersonalGeneral(userId) {
5250
5716
  const now = new Date().toISOString();
5251
5717
  const registry = getRegistry("maestro");
5252
5718
  const topic = {
5253
- id: randomUUID3(),
5719
+ id: randomUUID4(),
5254
5720
  title: "General",
5255
5721
  description: PERSONAL_GENERAL_DESCRIPTION,
5256
5722
  kind: "manager",
@@ -5336,7 +5802,7 @@ function runArchiverTurn(params) {
5336
5802
  mcpEnabled: ["wiki"],
5337
5803
  silent: true
5338
5804
  });
5339
- const activeSessionId = `memory:${randomUUID4()}`;
5805
+ const activeSessionId = `memory:${randomUUID5()}`;
5340
5806
  activeArchiverSessions.set(activeSessionId, {
5341
5807
  id: activeSessionId,
5342
5808
  kind: "memory",
@@ -5445,7 +5911,7 @@ function findSummaryFile(topicTitle, date, sinceMs, topicId) {
5445
5911
  continue;
5446
5912
  const p = join15(dir, f);
5447
5913
  try {
5448
- const m = statSync4(p).mtimeMs;
5914
+ const m = statSync5(p).mtimeMs;
5449
5915
  if (m >= sinceMs && (!best || m > best.mtime))
5450
5916
  best = { path: p, mtime: m };
5451
5917
  } catch {}
@@ -5457,7 +5923,7 @@ function finalizeGeneralMemory(userId, topicTitle, messageCount, startMs, ok, to
5457
5923
  const date = new Date().toISOString().slice(0, 10);
5458
5924
  try {
5459
5925
  const summaryPath = ok ? findSummaryFile(topicTitle, date, startMs, topicId) : null;
5460
- const summaryMd = summaryPath ? readFileSync10(summaryPath, "utf-8") : "";
5926
+ const summaryMd = summaryPath ? readFileSync11(summaryPath, "utf-8") : "";
5461
5927
  const oneLine = summaryMd && distillOneLine(summaryMd) || `${messageCount}\uAC1C \uBA54\uC2DC\uC9C0 \uC544\uCE74\uC774\uBE0C`;
5462
5928
  const prev = getTopicBrief(generalTopicId);
5463
5929
  const prevEntries = (prev?.briefMd ?? "").split(`
@@ -5492,7 +5958,7 @@ ${rolled.join(`
5492
5958
  usage: generalReply.usage
5493
5959
  } : { authorId: "system" };
5494
5960
  const msg = {
5495
- id: randomUUID4(),
5961
+ id: randomUUID5(),
5496
5962
  topicId: generalTopicId,
5497
5963
  text,
5498
5964
  ...replyMeta,
@@ -5526,7 +5992,7 @@ function countMemoryArchiveExchanges(rows) {
5526
5992
  }
5527
5993
 
5528
5994
  // ../../packages/core/src/storage/runtime-leases.ts
5529
- import { randomUUID as randomUUID5 } from "crypto";
5995
+ import { randomUUID as randomUUID6 } from "crypto";
5530
5996
 
5531
5997
  // ../../packages/core/src/storage/runtime-topic-state.ts
5532
5998
  var TOPIC_MAINTENANCE_STALE_MS = 30000;
@@ -5541,7 +6007,7 @@ db.exec(`
5541
6007
  `);
5542
6008
 
5543
6009
  // ../../packages/core/src/storage/runtime-leases.ts
5544
- var RUNTIME_INSTANCE_ID = `${process.pid}-${randomUUID5()}`;
6010
+ var RUNTIME_INSTANCE_ID = `${process.pid}-${randomUUID6()}`;
5545
6011
  var TURN_LEASE_STALE_MS = 1e4;
5546
6012
  db.exec(`
5547
6013
  CREATE TABLE IF NOT EXISTS runtime_turn_leases (
@@ -6020,7 +6486,7 @@ function archiveTopicMessages(topicId, topicTitle, options = {}) {
6020
6486
  }
6021
6487
 
6022
6488
  // ../../packages/core/src/storage/topic-archive-state.ts
6023
- import { rmSync } from "fs";
6489
+ import { rmSync as rmSync2 } from "fs";
6024
6490
  registerStorageSchemaInitializer((database) => {
6025
6491
  database.exec(`
6026
6492
  CREATE TABLE IF NOT EXISTS api_topic_archive_state (
@@ -6121,7 +6587,7 @@ function claimTopicArchiveJob(topicId, create, now = Date.now()) {
6121
6587
  })();
6122
6588
  if (result.kind === "claimed")
6123
6589
  return result;
6124
- rmSync(candidate.archivePath, { force: true });
6590
+ rmSync2(candidate.archivePath, { force: true });
6125
6591
  if (result.kind === "busy")
6126
6592
  return result;
6127
6593
  }
@@ -6417,4 +6883,4 @@ export {
6417
6883
  MIN_MEMORY_ARCHIVE_EXCHANGES
6418
6884
  };
6419
6885
 
6420
- //# debugId=34EC4A567FFF49C164756E2164756E21
6886
+ //# debugId=CC442F5FF6F506FC64756E2164756E21