negotium 0.3.13 → 0.4.0

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 (39) hide show
  1. package/README.md +2 -6
  2. package/dist/agent-helpers.js +328 -185
  3. package/dist/agent-helpers.js.map +9 -8
  4. package/dist/{chunk-0ynjwr50.js → chunk-zq2tcq4k.js} +12 -29
  5. package/dist/{chunk-0ynjwr50.js.map → chunk-zq2tcq4k.js.map} +4 -4
  6. package/dist/hosted-agent.js +207 -64
  7. package/dist/hosted-agent.js.map +8 -7
  8. package/dist/main.js +753 -886
  9. package/dist/main.js.map +13 -13
  10. package/dist/mcp-factories.js +300 -468
  11. package/dist/mcp-factories.js.map +9 -10
  12. package/dist/registry.js +3 -3
  13. package/dist/registry.js.map +2 -2
  14. package/dist/rollout.js +1 -1
  15. package/dist/runtime/src/agents/codex-provider.ts +33 -8
  16. package/dist/runtime/src/agents/codex-vault-hook-bridge.ts +192 -0
  17. package/dist/runtime/src/agents/codex-vault-hook.mjs +48 -0
  18. package/dist/runtime/src/agents/execution-host.ts +1 -14
  19. package/dist/runtime/src/agents/public-helpers.ts +0 -9
  20. package/dist/runtime/src/agents/vault-tool-policy.ts +9 -51
  21. package/dist/runtime/src/mcp/factories/index.ts +1 -8
  22. package/dist/runtime/src/mcp/factories/vault.ts +6 -104
  23. package/dist/runtime/src/mcp/vault-server.ts +4 -20
  24. package/dist/runtime/src/prompts/sessions/_shared-tools.md +1 -1
  25. package/dist/runtime/src/version.ts +1 -1
  26. package/dist/types/packages/core/src/agents/codex-vault-hook-bridge.d.ts +27 -0
  27. package/dist/types/packages/core/src/agents/execution-host.d.ts +0 -2
  28. package/dist/types/packages/core/src/agents/public-helpers.d.ts +0 -1
  29. package/dist/types/packages/core/src/agents/vault-tool-policy.d.ts +1 -14
  30. package/dist/types/packages/core/src/mcp/factories/index.d.ts +1 -3
  31. package/dist/types/packages/core/src/mcp/factories/vault.d.ts +4 -13
  32. package/dist/types/packages/core/src/version.d.ts +1 -1
  33. package/package.json +1 -1
  34. package/dist/runtime/src/mcp/factories/vault-host.ts +0 -8
  35. package/dist/runtime/src/mcp/vault-http.ts +0 -235
  36. package/dist/runtime/src/mcp/vault-run.ts +0 -154
  37. package/dist/types/packages/core/src/mcp/factories/vault-host.d.ts +0 -7
  38. package/dist/types/packages/core/src/mcp/vault-http.d.ts +0 -23
  39. package/dist/types/packages/core/src/mcp/vault-run.d.ts +0 -19
@@ -1017,10 +1017,10 @@ var init_claude_registry = __esm(() => {
1017
1017
  });
1018
1018
 
1019
1019
  // ../../packages/core/src/version.ts
1020
- var NEGOTIUM_VERSION = "0.3.13";
1020
+ var NEGOTIUM_VERSION = "0.4.0";
1021
1021
 
1022
1022
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
1023
- import { spawn as spawn2 } from "child_process";
1023
+ import { spawn } from "child_process";
1024
1024
  import { randomUUID as randomUUID3 } from "crypto";
1025
1025
  import {
1026
1026
  chmodSync as chmodSync3,
@@ -1088,7 +1088,7 @@ function bundledCodexModelCachePath(authFilePath) {
1088
1088
  return join5(dirname4(authFilePath), NEGOTIUM_MODEL_CACHE);
1089
1089
  }
1090
1090
  async function bootstrapCodexModelCache(codexHome, cachePath) {
1091
- const child = spawn2(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
1091
+ const child = spawn(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
1092
1092
  env: { ...process.env, CODEX_HOME: codexHome },
1093
1093
  stdio: ["pipe", "pipe", "pipe"]
1094
1094
  });
@@ -1287,30 +1287,21 @@ function shouldSubstituteVaultToolInput(toolName) {
1287
1287
  const leaf = leafToolName(toolName);
1288
1288
  return leaf.startsWith("browser_") || DIRECT_VAULT_EXECUTION_TOOLS.has(leaf);
1289
1289
  }
1290
- function createVaultToolPolicy(host) {
1291
- function isVaultBrokerTool(toolName) {
1292
- return toolName.includes("vault_run") || toolName.includes("vault_http_request");
1293
- }
1294
- function referencesRuntimeSecretStorage(value2) {
1295
- if (typeof value2 === "string") {
1296
- const lower = value2.toLowerCase();
1297
- if (SENSITIVE_RUNTIME_NAMES.some((name) => lower.includes(name)))
1298
- return true;
1299
- return value2.startsWith("/") && host.isSensitivePath(value2);
1300
- }
1301
- if (Array.isArray(value2))
1302
- return value2.some(referencesRuntimeSecretStorage);
1303
- if (value2 && typeof value2 === "object") {
1304
- return Object.values(value2).some(referencesRuntimeSecretStorage);
1305
- }
1306
- return false;
1290
+ function referencesRuntimeSecretStorage(value2) {
1291
+ if (typeof value2 === "string") {
1292
+ const lower = value2.toLowerCase();
1293
+ if (SENSITIVE_RUNTIME_NAMES.some((name) => lower.includes(name)))
1294
+ return true;
1295
+ return value2.startsWith("/") && isSensitivePath(value2);
1307
1296
  }
1308
- function shouldRedirectVaultTool(userId, toolName, input) {
1309
- return false;
1297
+ if (Array.isArray(value2))
1298
+ return value2.some(referencesRuntimeSecretStorage);
1299
+ if (value2 && typeof value2 === "object") {
1300
+ return Object.values(value2).some(referencesRuntimeSecretStorage);
1310
1301
  }
1311
- return { isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool };
1302
+ return false;
1312
1303
  }
1313
- var SENSITIVE_RUNTIME_NAMES, DIRECT_VAULT_EXECUTION_TOOLS, defaultVaultToolPolicy, isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool;
1304
+ var SENSITIVE_RUNTIME_NAMES, DIRECT_VAULT_EXECUTION_TOOLS;
1314
1305
  var init_vault_tool_policy = __esm(() => {
1315
1306
  init_sensitive_path();
1316
1307
  SENSITIVE_RUNTIME_NAMES = [
@@ -1320,13 +1311,6 @@ var init_vault_tool_policy = __esm(() => {
1320
1311
  "sessions.db"
1321
1312
  ];
1322
1313
  DIRECT_VAULT_EXECUTION_TOOLS = new Set(["Bash", "WebFetch"]);
1323
- defaultVaultToolPolicy = createVaultToolPolicy({
1324
- isSensitivePath,
1325
- valueReferencesVaultKey: () => false
1326
- });
1327
- isVaultBrokerTool = defaultVaultToolPolicy.isVaultBrokerTool;
1328
- referencesRuntimeSecretStorage = defaultVaultToolPolicy.referencesRuntimeSecretStorage;
1329
- shouldRedirectVaultTool = defaultVaultToolPolicy.shouldRedirectVaultTool;
1330
1314
  });
1331
1315
 
1332
1316
  // ../../packages/core/src/mcp/canonical-bridge-config.ts
@@ -1489,7 +1473,7 @@ function deriveBgBashContextCapability(runtimeCapability, userId, topic) {
1489
1473
  var init_context = () => {};
1490
1474
 
1491
1475
  // ../../packages/core/src/platform/background-bash/manager.ts
1492
- import { execFileSync as execFileSync2, spawn as spawn3 } from "child_process";
1476
+ import { execFileSync as execFileSync2, spawn as spawn2 } from "child_process";
1493
1477
  import { randomBytes as randomBytes2 } from "crypto";
1494
1478
  function makeBgBashKey(_userId, _topic) {
1495
1479
  return "runtime";
@@ -1516,7 +1500,7 @@ function createBackgroundBashManager(options = {}) {
1516
1500
  const now = options.now ?? Date.now;
1517
1501
  const wait = options.delay ?? delay;
1518
1502
  const portPids = options.portPids ?? defaultPortPids;
1519
- const spawnImpl = options.spawn ?? ((command, args, spawnOptions) => spawn3(command, [...args], spawnOptions));
1503
+ const spawnImpl = options.spawn ?? ((command, args, spawnOptions) => spawn2(command, [...args], spawnOptions));
1520
1504
  function contextKey(userId, topic) {
1521
1505
  return `${userId}\x00${topic}`;
1522
1506
  }
@@ -2688,7 +2672,6 @@ var init_execution_host = __esm(async () => {
2688
2672
  redactVaultSecrets,
2689
2673
  substituteVaultSecrets: (userId, value2) => vaultSubstituteDetailed(userId, value2).text,
2690
2674
  referencesRuntimeSecretStorage,
2691
- shouldRedirectVaultTool,
2692
2675
  claudeCodeExecutablePath: () => CLAUDE_EXECUTABLE,
2693
2676
  codexAuthFilePath
2694
2677
  };
@@ -3231,7 +3214,7 @@ var init_codex = __esm(async () => {
3231
3214
  });
3232
3215
 
3233
3216
  // ../../packages/core/src/agents/codex-app-server.ts
3234
- import { spawn as spawn4 } from "child_process";
3217
+ import { spawn as spawn3 } from "child_process";
3235
3218
  function createCodexAppServerForker(host) {
3236
3219
  return async (parentThreadId) => {
3237
3220
  const child = host.spawnServer();
@@ -3335,7 +3318,7 @@ var init_codex_app_server = __esm(async () => {
3335
3318
  await init_codex();
3336
3319
  forkCodexThread = createCodexAppServerForker({
3337
3320
  spawnServer() {
3338
- return spawn4(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
3321
+ return spawn3(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
3339
3322
  env: { ...process.env, CODEX_HOME: hostedCodexHomePath() },
3340
3323
  stdio: ["pipe", "pipe", "pipe"]
3341
3324
  });
@@ -6265,7 +6248,7 @@ var init_browser_profiles = __esm(async () => {
6265
6248
  });
6266
6249
 
6267
6250
  // ../../packages/core/src/platform/playwright/manager.ts
6268
- import { execFileSync as execFileSync4, spawn as spawn6 } from "child_process";
6251
+ import { execFileSync as execFileSync4, spawn as spawn5 } from "child_process";
6269
6252
  import { createHash as createHash3, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual3 } from "crypto";
6270
6253
  import {
6271
6254
  cpSync,
@@ -6625,7 +6608,7 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
6625
6608
  }
6626
6609
  let proc;
6627
6610
  try {
6628
- proc = spawn6(spawnSpec.command, spawnSpec.args, {
6611
+ proc = spawn5(spawnSpec.command, spawnSpec.args, {
6629
6612
  stdio: ["ignore", "ignore", "pipe"],
6630
6613
  detached: false,
6631
6614
  env: childEnv
@@ -7939,7 +7922,7 @@ __export(exports_claude_provider, {
7939
7922
  claudeBuiltInTools: () => claudeBuiltInTools,
7940
7923
  buildClaudeDisallowedTools: () => buildClaudeDisallowedTools
7941
7924
  });
7942
- import { spawn as spawn7 } from "child_process";
7925
+ import { spawn as spawn6 } from "child_process";
7943
7926
  import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
7944
7927
  import { query } from "@anthropic-ai/claude-agent-sdk";
7945
7928
  function claudeBuiltInTools(opts) {
@@ -8031,7 +8014,7 @@ function signalProcessTree2(pid, signal) {
8031
8014
  }
8032
8015
  }
8033
8016
  function spawnClaudeCodeProcessWithTreeKill(options) {
8034
- const child = spawn7(options.command, options.args, {
8017
+ const child = spawn6(options.command, options.args, {
8035
8018
  cwd: options.cwd,
8036
8019
  detached: true,
8037
8020
  env: options.env,
@@ -8640,9 +8623,163 @@ var init_codex_tree_kill = __esm(() => {
8640
8623
  killOwnedCodexTreesForShutdown = manager.killOwnedTreesForShutdown;
8641
8624
  });
8642
8625
 
8626
+ // ../../packages/core/src/agents/codex-vault-hook-bridge.ts
8627
+ import { randomBytes as randomBytes7 } from "crypto";
8628
+ import { existsSync as existsSync17 } from "fs";
8629
+ import { chmod, mkdtemp, rm, writeFile } from "fs/promises";
8630
+ import { createServer as createServer3 } from "net";
8631
+ import { tmpdir as tmpdir3 } from "os";
8632
+ import { dirname as dirname12, join as join17, resolve as resolve11 } from "path";
8633
+ import { fileURLToPath as fileURLToPath2 } from "url";
8634
+ function evaluateCodexVaultPreToolUse(input, userId, operations) {
8635
+ if (operations.referencesSensitiveStorage(input.tool_input)) {
8636
+ return {
8637
+ hookSpecificOutput: {
8638
+ hookEventName: "PreToolUse",
8639
+ permissionDecision: "deny",
8640
+ permissionDecisionReason: SENSITIVE_STORAGE_DENIAL
8641
+ }
8642
+ };
8643
+ }
8644
+ if (!shouldSubstituteVaultToolInput(input.tool_name))
8645
+ return {};
8646
+ const updatedInput = deepMapStrings2(input.tool_input, (value2) => operations.substitute(userId, value2));
8647
+ if (JSON.stringify(updatedInput) === JSON.stringify(input.tool_input))
8648
+ return {};
8649
+ return {
8650
+ hookSpecificOutput: {
8651
+ hookEventName: "PreToolUse",
8652
+ permissionDecision: "allow",
8653
+ updatedInput
8654
+ }
8655
+ };
8656
+ }
8657
+ function shellQuote(value2) {
8658
+ return `'${value2.replaceAll("'", `'"'"'`)}'`;
8659
+ }
8660
+ function hookClientPath() {
8661
+ const moduleDir = dirname12(fileURLToPath2(import.meta.url));
8662
+ const adjacent = resolve11(moduleDir, "codex-vault-hook.mjs");
8663
+ if (existsSync17(adjacent))
8664
+ return adjacent;
8665
+ const packaged = resolve11(moduleDir, "runtime/src/agents/codex-vault-hook.mjs");
8666
+ if (existsSync17(packaged))
8667
+ return packaged;
8668
+ throw new Error("Codex Vault hook client is missing from this installation");
8669
+ }
8670
+ function privateCodexWrapper(codexScript) {
8671
+ return [
8672
+ "#!/bin/sh",
8673
+ 'if [ "$1" = "exec" ]; then',
8674
+ " shift",
8675
+ ` exec ${shellQuote(process.execPath)} ${shellQuote(codexScript)} exec --dangerously-bypass-hook-trust "$@"`,
8676
+ "fi",
8677
+ `exec ${shellQuote(process.execPath)} ${shellQuote(codexScript)} "$@"`,
8678
+ ""
8679
+ ].join(`
8680
+ `);
8681
+ }
8682
+ async function createCodexVaultHookBridge(userId) {
8683
+ const root = await mkdtemp(join17(tmpdir3(), "negotium-codex-vault-"));
8684
+ await chmod(root, 448);
8685
+ const socketPath = join17(root, "hook.sock");
8686
+ const wrapperPath = join17(root, "codex-with-hooks");
8687
+ const token = randomBytes7(32).toString("hex");
8688
+ const connections = new Set;
8689
+ const server = createServer3((socket) => {
8690
+ connections.add(socket);
8691
+ socket.once("close", () => connections.delete(socket));
8692
+ socket.setEncoding("utf8");
8693
+ let request = "";
8694
+ let handled = false;
8695
+ const handleRequest = () => {
8696
+ if (handled)
8697
+ return;
8698
+ handled = true;
8699
+ try {
8700
+ const parsed = JSON.parse(request.trimEnd());
8701
+ if (parsed.token !== token)
8702
+ throw new Error("invalid hook capability");
8703
+ if (!parsed.input || typeof parsed.input !== "object" || typeof parsed.input.tool_name !== "string") {
8704
+ throw new Error("invalid PreToolUse payload");
8705
+ }
8706
+ const output = evaluateCodexVaultPreToolUse(parsed.input, userId, {
8707
+ referencesSensitiveStorage: referencesHostedSecretStorage,
8708
+ substitute: substituteHostedSecrets
8709
+ });
8710
+ socket.end(JSON.stringify({ ok: true, output }));
8711
+ } catch (error) {
8712
+ const message = error instanceof Error ? error.message : String(error);
8713
+ socket.end(JSON.stringify({ ok: false, error: message }));
8714
+ }
8715
+ };
8716
+ socket.on("data", (chunk) => {
8717
+ request += chunk;
8718
+ if (Buffer.byteLength(request) > MAX_HOOK_REQUEST_BYTES)
8719
+ socket.destroy();
8720
+ else if (request.endsWith(`
8721
+ `))
8722
+ handleRequest();
8723
+ });
8724
+ socket.on("end", handleRequest);
8725
+ });
8726
+ try {
8727
+ await new Promise((resolveListen, reject) => {
8728
+ server.once("error", reject);
8729
+ server.listen(socketPath, () => {
8730
+ server.off("error", reject);
8731
+ resolveListen();
8732
+ });
8733
+ });
8734
+ await chmod(socketPath, 384);
8735
+ await writeFile(wrapperPath, privateCodexWrapper(codexCliScriptPath()), { mode: 448 });
8736
+ const command = [
8737
+ shellQuote(process.execPath),
8738
+ shellQuote(hookClientPath()),
8739
+ shellQuote(socketPath),
8740
+ shellQuote(token)
8741
+ ].join(" ");
8742
+ return {
8743
+ codexPathOverride: wrapperPath,
8744
+ hooks: {
8745
+ PreToolUse: [
8746
+ {
8747
+ matcher: "*",
8748
+ hooks: [
8749
+ {
8750
+ type: "command",
8751
+ command,
8752
+ timeout: 30,
8753
+ statusMessage: "Resolving Vault placeholders"
8754
+ }
8755
+ ]
8756
+ }
8757
+ ]
8758
+ },
8759
+ async close() {
8760
+ for (const socket of connections)
8761
+ socket.destroy();
8762
+ await new Promise((resolveClose) => server.close(() => resolveClose()));
8763
+ await rm(root, { recursive: true, force: true });
8764
+ }
8765
+ };
8766
+ } catch (error) {
8767
+ server.close();
8768
+ await rm(root, { recursive: true, force: true });
8769
+ throw error;
8770
+ }
8771
+ }
8772
+ var MAX_HOOK_REQUEST_BYTES, SENSITIVE_STORAGE_DENIAL = "Runtime secret storage access is not permitted";
8773
+ var init_codex_vault_hook_bridge = __esm(async () => {
8774
+ init_codex_native_multi_agent();
8775
+ await init_execution_host();
8776
+ init_vault_tool_policy();
8777
+ MAX_HOOK_REQUEST_BYTES = 1024 * 1024;
8778
+ });
8779
+
8643
8780
  // ../../packages/core/src/agents/tool-format.ts
8644
8781
  import { readFileSync as readFileSync15, statSync as statSync5 } from "fs";
8645
- import { isAbsolute as isAbsolute2, resolve as resolve11 } from "path";
8782
+ import { isAbsolute as isAbsolute2, resolve as resolve12 } from "path";
8646
8783
  import { diffLines as computeLineDiff } from "diff";
8647
8784
  function summarizeDisplayText(value2) {
8648
8785
  const normalized = value2.replace(/\s+/g, " ").trim();
@@ -8966,7 +9103,7 @@ function sourceStartLine(input, before, after, cwd) {
8966
9103
  const rawPath = input.file_path ?? input.path;
8967
9104
  if (typeof rawPath !== "string" || !rawPath.trim())
8968
9105
  return 1;
8969
- const path = isAbsolute2(rawPath) ? rawPath : resolve11(cwd ?? process.cwd(), rawPath);
9106
+ const path = isAbsolute2(rawPath) ? rawPath : resolve12(cwd ?? process.cwd(), rawPath);
8970
9107
  try {
8971
9108
  if (statSync5(path).size > TOOL_DIFF_SOURCE_MAX_BYTES)
8972
9109
  return 1;
@@ -9164,9 +9301,9 @@ __export(exports_codex_provider, {
9164
9301
  codexProvider: () => codexProvider
9165
9302
  });
9166
9303
  import { execFileSync as execFileSync6 } from "child_process";
9167
- import { existsSync as existsSync17, readFileSync as readFileSync16, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
9304
+ import { existsSync as existsSync18, readFileSync as readFileSync16, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
9168
9305
  import { homedir as homedir6 } from "os";
9169
- import { dirname as dirname12, isAbsolute as isAbsolute3, join as join17, relative as relative2, resolve as resolve12 } from "path";
9306
+ import { dirname as dirname13, isAbsolute as isAbsolute3, join as join18, relative as relative2, resolve as resolve13 } from "path";
9170
9307
  import { Codex } from "@openai/codex-sdk";
9171
9308
  function sameCodexUsage(usage, total) {
9172
9309
  return usage.input_tokens === total.inputTokens && usage.output_tokens === total.outputTokens && (usage.cached_input_tokens ?? 0) === total.cachedInputTokens && (usage.cache_write_input_tokens ?? 0) === total.cacheWriteInputTokens;
@@ -9194,8 +9331,8 @@ function codexMcpServerName(name) {
9194
9331
  return CODEX_MCP_SERVER_NAME_OVERRIDES[name] ?? name;
9195
9332
  }
9196
9333
  function globalCodexMcpServerNames(authFilePath) {
9197
- const configPath = join17(dirname12(authFilePath), "config.toml");
9198
- if (!existsSync17(configPath))
9334
+ const configPath = join18(dirname13(authFilePath), "config.toml");
9335
+ if (!existsSync18(configPath))
9199
9336
  return [];
9200
9337
  try {
9201
9338
  const names = new Set;
@@ -9290,13 +9427,13 @@ function summarizeMcpToolCallResult(item) {
9290
9427
  }
9291
9428
  function canonicalPath(path) {
9292
9429
  try {
9293
- return realpathSync4(resolve12(path));
9430
+ return realpathSync4(resolve13(path));
9294
9431
  } catch {
9295
- return resolve12(path);
9432
+ return resolve13(path);
9296
9433
  }
9297
9434
  }
9298
9435
  function textFile(path, maxBytes = CODEX_DIFF_FILE_LIMIT) {
9299
- if (!existsSync17(path))
9436
+ if (!existsSync18(path))
9300
9437
  return null;
9301
9438
  try {
9302
9439
  const byteLimit = Math.min(CODEX_DIFF_FILE_LIMIT, Math.max(0, maxBytes));
@@ -9336,7 +9473,7 @@ class CodexFilePreviewTracker {
9336
9473
  const code = record.slice(0, 2);
9337
9474
  const path = record.slice(3);
9338
9475
  const remainingBytes = CODEX_DIFF_BASELINE_BYTE_LIMIT - loadedBytes;
9339
- const content = loadedFiles < CODEX_DIFF_BASELINE_FILE_LIMIT && remainingBytes > 0 ? textFile(resolve12(this.#root, path), remainingBytes) : undefined;
9476
+ const content = loadedFiles < CODEX_DIFF_BASELINE_FILE_LIMIT && remainingBytes > 0 ? textFile(resolve13(this.#root, path), remainingBytes) : undefined;
9340
9477
  this.#baseline.set(path, content);
9341
9478
  if (typeof content === "string") {
9342
9479
  loadedFiles += 1;
@@ -9349,7 +9486,7 @@ class CodexFilePreviewTracker {
9349
9486
  preview(path, succeeded) {
9350
9487
  if (!this.#root || !this.#baselineAvailable || !succeeded)
9351
9488
  return {};
9352
- const absolute = canonicalPath(isAbsolute3(path) ? path : resolve12(this.#cwd, path));
9489
+ const absolute = canonicalPath(isAbsolute3(path) ? path : resolve13(this.#cwd, path));
9353
9490
  const relativePath = relative2(this.#root, absolute);
9354
9491
  if (!relativePath || relativePath.startsWith("..") || isAbsolute3(relativePath))
9355
9492
  return {};
@@ -9384,7 +9521,7 @@ class CodexFilePreviewTracker {
9384
9521
  }
9385
9522
  }
9386
9523
  async function fileChangeEvents(item, previews, cwd, threadId, consumedPatchCallIds) {
9387
- const expectedPaths = item.changes.map((change) => isAbsolute3(change.path) ? change.path : resolve12(cwd, change.path));
9524
+ const expectedPaths = item.changes.map((change) => isAbsolute3(change.path) ? change.path : resolve13(cwd, change.path));
9388
9525
  let nativePreview;
9389
9526
  if (threadId && item.status === "completed") {
9390
9527
  await new Promise((resolveDelay) => setTimeout(resolveDelay, 20));
@@ -9508,7 +9645,7 @@ async function* codexProvider(opts) {
9508
9645
  const filePreviews = new CodexFilePreviewTracker(opts.cwd);
9509
9646
  const consumedPatchCallIds = new Set(opts.sessionId ? readCodexPatchCallIds(opts.sessionId) : []);
9510
9647
  const codexAuthPath = hostedCodexAuthFilePath();
9511
- if (!existsSync17(codexAuthPath)) {
9648
+ if (!existsSync18(codexAuthPath)) {
9512
9649
  yield {
9513
9650
  type: "error",
9514
9651
  content: `Codex auth file not found at ${codexAuthPath}. Run \`codex login\` to authenticate.`
@@ -9547,21 +9684,30 @@ async function* codexProvider(opts) {
9547
9684
  model: opts.model ?? "(sdk default)",
9548
9685
  effort: opts.effort ?? "(off)",
9549
9686
  cwd: opts.cwd,
9550
- cwdExists: existsSync17(opts.cwd),
9687
+ cwdExists: existsSync18(opts.cwd),
9551
9688
  resume: Boolean(opts.sessionId),
9552
9689
  mcpServerCount: Object.keys(codexMcpServers).length
9553
9690
  }, "codexProvider: starting turn");
9554
9691
  const hostedCodexHome = hostedCodexHomePath();
9555
- const inheritedCodexHome = process.env.CODEX_HOME || join17(homedir6(), ".codex");
9556
- const codexEnvironment = scopedBrowserCapability || resolve12(hostedCodexHome) !== resolve12(inheritedCodexHome) ? {
9692
+ const inheritedCodexHome = process.env.CODEX_HOME || join18(homedir6(), ".codex");
9693
+ const codexEnvironment = scopedBrowserCapability || resolve13(hostedCodexHome) !== resolve13(inheritedCodexHome) ? {
9557
9694
  ...Object.fromEntries(Object.entries(process.env).filter((entry) => typeof entry[1] === "string")),
9558
9695
  CODEX_HOME: hostedCodexHome,
9559
9696
  ...scopedBrowserCapability ? { [CODEX_BROWSER_CAPABILITY_ENV]: scopedBrowserCapability } : {}
9560
9697
  } : undefined;
9698
+ let vaultHook;
9699
+ try {
9700
+ vaultHook = await createCodexVaultHookBridge(opts.vaultUserId ?? opts.userId ?? "");
9701
+ } catch (err) {
9702
+ yield { type: "error", content: `Failed to initialize Codex Vault hooks: ${errMsg(err)}` };
9703
+ return;
9704
+ }
9561
9705
  const codex = new Codex({
9706
+ codexPathOverride: vaultHook.codexPathOverride,
9562
9707
  ...codexEnvironment ? { env: codexEnvironment } : {},
9563
9708
  config: {
9564
- features: { multi_agent: false, multi_agent_v2: false, enable_fanout: false },
9709
+ features: { hooks: true, multi_agent: false, multi_agent_v2: false, enable_fanout: false },
9710
+ hooks: vaultHook.hooks,
9565
9711
  model_catalog_json: codexModelCatalogPath,
9566
9712
  mcp_servers: codexMcpServers,
9567
9713
  ...opts.toolPolicy ? { sandbox_permissions: [] } : {}
@@ -9665,7 +9811,7 @@ async function* codexProvider(opts) {
9665
9811
  if (!item)
9666
9812
  break;
9667
9813
  if (item.type === "command_execution") {
9668
- const command = String(item.command ?? "");
9814
+ const command = redactHostedSecrets(opts.vaultUserId ?? opts.userId ?? "", String(item.command ?? ""));
9669
9815
  yield {
9670
9816
  type: "tool_use",
9671
9817
  name: "Bash",
@@ -9673,10 +9819,11 @@ async function* codexProvider(opts) {
9673
9819
  toolUseId: String(item.id ?? "")
9674
9820
  };
9675
9821
  } else if (item.type === "mcp_tool_call") {
9822
+ const input = item.arguments && typeof item.arguments === "object" ? item.arguments : {};
9676
9823
  yield {
9677
9824
  type: "tool_use",
9678
9825
  name: String(item.tool ?? "unknown"),
9679
- input: item.arguments && typeof item.arguments === "object" ? item.arguments : {},
9826
+ input: deepMapStrings2(input, (value2) => redactHostedSecrets(opts.vaultUserId ?? opts.userId ?? "", value2)),
9680
9827
  toolUseId: String(item.id ?? "")
9681
9828
  };
9682
9829
  }
@@ -9719,7 +9866,7 @@ async function* codexProvider(opts) {
9719
9866
  yield {
9720
9867
  type: "tool_result",
9721
9868
  toolUseId: String(item.id ?? ""),
9722
- content: summarizeMcpToolCallResult(item),
9869
+ content: redactHostedSecrets(opts.vaultUserId ?? opts.userId ?? "", summarizeMcpToolCallResult(item)),
9723
9870
  ...isError ? { isError: true } : {}
9724
9871
  };
9725
9872
  } else if (item.type === "command_execution") {
@@ -9727,7 +9874,7 @@ async function* codexProvider(opts) {
9727
9874
  yield {
9728
9875
  type: "tool_result",
9729
9876
  toolUseId: String(item.id ?? ""),
9730
- content: String(item.aggregated_output ?? "").slice(0, 200),
9877
+ content: redactHostedSecrets(opts.vaultUserId ?? opts.userId ?? "", String(item.aggregated_output ?? "").slice(0, 200)),
9731
9878
  ...isError ? { isError: true } : {}
9732
9879
  };
9733
9880
  } else if (item.type === "file_change") {
@@ -9824,12 +9971,14 @@ async function* codexProvider(opts) {
9824
9971
  abortSignal?.removeEventListener("abort", onAbortKill);
9825
9972
  if (trackedPids.pids.length > 0)
9826
9973
  unregisterOwnedCodexPids(trackedPids.pids);
9974
+ await vaultHook.close();
9827
9975
  }
9828
9976
  }
9829
9977
  var CODEX_MCP_SERVER_NAME_OVERRIDES, CODEX_DIFF_FILE_LIMIT, CODEX_DIFF_BASELINE_FILE_LIMIT = 200, CODEX_DIFF_BASELINE_BYTE_LIMIT, CODEX_STARTUP_TIMEOUT_MS = 90000;
9830
9978
  var init_codex_provider = __esm(async () => {
9831
9979
  init_codex_native_multi_agent();
9832
9980
  init_codex_tree_kill();
9981
+ await init_codex_vault_hook_bridge();
9833
9982
  await init_execution_host();
9834
9983
  await init_codex();
9835
9984
  init_tool_format();
@@ -9857,7 +10006,7 @@ __export(exports_maestro_provider, {
9857
10006
  buildMaestroToolHooks: () => buildMaestroToolHooks,
9858
10007
  buildMaestroDisallowedTools: () => buildMaestroDisallowedTools
9859
10008
  });
9860
- import { resolve as resolve13 } from "path";
10009
+ import { resolve as resolve14 } from "path";
9861
10010
  import { maestroProvider as sdkMaestroProvider, setMcpResolver } from "maestro-agent-sdk";
9862
10011
  function buildMaestroDisallowedTools(callerDisallowedTools = [], toolPolicy) {
9863
10012
  return [
@@ -9991,13 +10140,13 @@ var init_maestro_provider = __esm(async () => {
9991
10140
  "ToolSearch",
9992
10141
  ...DEFAULT_MAESTRO_DISALLOWED_TOOLS
9993
10142
  ];
9994
- MAESTRO_TOOL_OUTPUT_DIR = resolve13(RUN_DIR, "maestro-tool-outputs");
10143
+ MAESTRO_TOOL_OUTPUT_DIR = resolve14(RUN_DIR, "maestro-tool-outputs");
9995
10144
  });
9996
10145
 
9997
10146
  // ../../packages/core/src/agents/index.ts
9998
- import { existsSync as existsSync18 } from "fs";
10147
+ import { existsSync as existsSync19 } from "fs";
9999
10148
  import { homedir as homedir7 } from "os";
10000
- import { join as join18 } from "path";
10149
+ import { join as join19 } from "path";
10001
10150
  async function* dispatchAgent(opts) {
10002
10151
  switch (opts.agent) {
10003
10152
  case "claude": {
@@ -10031,11 +10180,11 @@ async function resolveSessionFileMissing(agent, sessionId, cwd) {
10031
10180
  switch (agent) {
10032
10181
  case "claude": {
10033
10182
  const encodedCwd = encodeClaudeCwd(cwd);
10034
- const path = join18(homedir7(), ".claude", "projects", encodedCwd, `${sessionId}.jsonl`);
10035
- return !existsSync18(path);
10183
+ const path = join19(homedir7(), ".claude", "projects", encodedCwd, `${sessionId}.jsonl`);
10184
+ return !existsSync19(path);
10036
10185
  }
10037
10186
  case "codex": {
10038
- const sessionsDir = join18(hostedCodexHomePath(), "sessions");
10187
+ const sessionsDir = join19(hostedCodexHomePath(), "sessions");
10039
10188
  const glob = new Bun.Glob(`**/rollout-*-${sessionId}.jsonl`);
10040
10189
  for await (const _rel of glob.scan({ cwd: sessionsDir, onlyFiles: true })) {
10041
10190
  return false;
@@ -10044,7 +10193,7 @@ async function resolveSessionFileMissing(agent, sessionId, cwd) {
10044
10193
  }
10045
10194
  case "maestro": {
10046
10195
  const { hasActiveMaestroSession, maestroSessionPath: maestroSessionPath2 } = await import("maestro-agent-sdk");
10047
- return !existsSync18(maestroSessionPath2(sessionId)) && !hasActiveMaestroSession(sessionId);
10196
+ return !existsSync19(maestroSessionPath2(sessionId)) && !hasActiveMaestroSession(sessionId);
10048
10197
  }
10049
10198
  default: {
10050
10199
  const _exhaustive = agent;
@@ -10116,9 +10265,9 @@ var init_agents = __esm(async () => {
10116
10265
 
10117
10266
  // ../../packages/core/src/prompts/builders.ts
10118
10267
  import { readFileSync as readFileSync17 } from "fs";
10119
- import { resolve as resolve14 } from "path";
10268
+ import { resolve as resolve15 } from "path";
10120
10269
  function loadPrompt(filename, dir = SESSIONS_DIR) {
10121
- const raw = readFileSync17(resolve14(dir, filename), "utf-8");
10270
+ const raw = readFileSync17(resolve15(dir, filename), "utf-8");
10122
10271
  return raw.replace(/\{\{RESOURCES_DIR\}\}/g, RESOURCES_DIR);
10123
10272
  }
10124
10273
  function replaceVars(template, vars) {
@@ -10167,7 +10316,7 @@ function visualDesignGuide() {
10167
10316
  return _visualDesignGuide;
10168
10317
  }
10169
10318
  function loadAgentPrompt(filename) {
10170
- const raw = readFileSync17(resolve14(AGENTS_PROMPTS_DIR, filename), "utf-8");
10319
+ const raw = readFileSync17(resolve15(AGENTS_PROMPTS_DIR, filename), "utf-8");
10171
10320
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
10172
10321
  if (!match)
10173
10322
  throw new Error(`Agent prompt ${filename} is missing frontmatter`);
@@ -10482,8 +10631,8 @@ var init_builders = __esm(() => {
10482
10631
  init_model_catalog();
10483
10632
  init_config();
10484
10633
  init_logger();
10485
- PROMPTS_DIR = resolve14(PROJECT_ROOT, "src/prompts");
10486
- SESSIONS_DIR = resolve14(PROMPTS_DIR, "sessions");
10634
+ PROMPTS_DIR = resolve15(PROJECT_ROOT, "src/prompts");
10635
+ SESSIONS_DIR = resolve15(PROMPTS_DIR, "sessions");
10487
10636
  defaultPromptBuilders = createPromptBuilders();
10488
10637
  buildTopicSystemPrompt = defaultPromptBuilders.buildTopicSystemPrompt;
10489
10638
  buildChannelSystemPrompt = defaultPromptBuilders.buildChannelSystemPrompt;
@@ -10559,9 +10708,9 @@ var init_api_topic_brief = __esm(async () => {
10559
10708
  });
10560
10709
 
10561
10710
  // ../../packages/core/src/storage/wiki.ts
10562
- import { basename as basename3, dirname as dirname13, join as join19 } from "path";
10711
+ import { basename as basename3, dirname as dirname14, join as join20 } from "path";
10563
10712
  function getSharedWikiDir(workspaceDir = resolveStorageWorkspaceDir()) {
10564
- return workspaceDir === resolveStorageWorkspaceDir() ? resolveStorageSharedWikiDir() : join19(workspaceDir, "wiki");
10713
+ return workspaceDir === resolveStorageWorkspaceDir() ? resolveStorageSharedWikiDir() : join20(workspaceDir, "wiki");
10565
10714
  }
10566
10715
  var init_wiki = __esm(async () => {
10567
10716
  await init_storage_host();
@@ -10569,8 +10718,8 @@ var init_wiki = __esm(async () => {
10569
10718
 
10570
10719
  // ../../packages/core/src/agents/archiver.ts
10571
10720
  import { randomUUID as randomUUID9 } from "crypto";
10572
- import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync18, statSync as statSync7 } from "fs";
10573
- import { join as join20 } from "path";
10721
+ import { existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync18, statSync as statSync7 } from "fs";
10722
+ import { join as join21 } from "path";
10574
10723
  function resolveMemoryLanguage() {
10575
10724
  const override = process.env.NEGOTIUM_MEMORY_LANG?.trim();
10576
10725
  return override && override.length > 0 ? override : resolveOutputLanguage();
@@ -10821,10 +10970,10 @@ function distillOneLine(summaryMd) {
10821
10970
  return "";
10822
10971
  }
10823
10972
  function findSummaryFile(storage, topicTitle, date, sinceMs, topicId) {
10824
- const dir = join20(storage.getWikiDir(), "summaries");
10973
+ const dir = join21(storage.getWikiDir(), "summaries");
10825
10974
  if (!storage.fileExists(dir))
10826
10975
  return null;
10827
- const predicted = join20(dir, wikiSummaryFilename(date, topicTitle, topicId));
10976
+ const predicted = join21(dir, wikiSummaryFilename(date, topicTitle, topicId));
10828
10977
  if (storage.fileExists(predicted) && storage.fileModifiedAt(predicted) >= sinceMs)
10829
10978
  return predicted;
10830
10979
  let best = null;
@@ -10832,7 +10981,7 @@ function findSummaryFile(storage, topicTitle, date, sinceMs, topicId) {
10832
10981
  if (!f.startsWith(`${date}-`) || !isTopicSummaryFile(f, topicId ?? "", topicTitle)) {
10833
10982
  continue;
10834
10983
  }
10835
- const p = join20(dir, f);
10984
+ const p = join21(dir, f);
10836
10985
  try {
10837
10986
  const m = storage.fileModifiedAt(p);
10838
10987
  if (m >= sinceMs && (!best || m > best.mtime))
@@ -10913,7 +11062,7 @@ var init_archiver = __esm(async () => {
10913
11062
  defaultArchiverRuntime = createArchiverRuntime({
10914
11063
  storage: {
10915
11064
  getWikiDir: getSharedWikiDir,
10916
- fileExists: existsSync19,
11065
+ fileExists: existsSync20,
10917
11066
  listDirectory: readdirSync5,
10918
11067
  readTextFile: (path) => readFileSync18(path, "utf-8"),
10919
11068
  fileSize: (path) => statSync7(path).size,
@@ -11036,13 +11185,13 @@ function formatTopicArchiveTranscriptRecord(row, topicTitle, index) {
11036
11185
 
11037
11186
  // ../../packages/core/src/storage/topic-archive.ts
11038
11187
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
11039
- import { join as join21 } from "path";
11188
+ import { join as join22 } from "path";
11040
11189
  function archiveTopicMessages(topicId, topicTitle, options = {}) {
11041
11190
  const rows = options.afterRowid !== undefined ? getMessagesForTopicAfterRowid(topicId, options.afterRowid) : getAllMessagesForTopic(topicId);
11042
11191
  if (rows.length === 0)
11043
11192
  return null;
11044
11193
  const safeTopic = sanitizeTopicName(topicTitle, true);
11045
- const archiveDir = join21(getSharedWikiDir(), "archive");
11194
+ const archiveDir = join22(getSharedWikiDir(), "archive");
11046
11195
  mkdirSync13(archiveDir, { recursive: true });
11047
11196
  const date = new Date().toISOString().slice(0, 10);
11048
11197
  const reasonSuffix = options.reason && options.reason !== "delete" ? `_${options.reason}` : "";
@@ -11055,7 +11204,7 @@ function archiveTopicMessages(topicId, topicTitle, options = {}) {
11055
11204
  let path;
11056
11205
  while (true) {
11057
11206
  filename = `${safeTopic}_${date}${reasonSuffix}${counter === 1 ? "" : `_${counter}`}.jsonl`;
11058
- path = join21(archiveDir, filename);
11207
+ path = join22(archiveDir, filename);
11059
11208
  try {
11060
11209
  writeFileSync11(path, body, { flag: "wx" });
11061
11210
  break;
@@ -11100,7 +11249,7 @@ function archiveConversationEvents(topicId, topicTitle, userId, options = {}) {
11100
11249
  const entries = readRawConversation(userId, topicTitle);
11101
11250
  if (entries.length === 0)
11102
11251
  return null;
11103
- const archiveDir = join21(getSharedWikiDir(), "archive");
11252
+ const archiveDir = join22(getSharedWikiDir(), "archive");
11104
11253
  mkdirSync13(archiveDir, { recursive: true });
11105
11254
  const safeTopic = sanitizeTopicName(topicTitle, true);
11106
11255
  const date = new Date().toISOString().slice(0, 10);
@@ -11128,7 +11277,7 @@ function archiveConversationEvents(topicId, topicTitle, userId, options = {}) {
11128
11277
  let counter = 1;
11129
11278
  while (true) {
11130
11279
  const suffix = counter === 1 ? "" : `_${counter}`;
11131
- const path = join21(archiveDir, `${safeTopic}_${date}${reasonSuffix}_events${suffix}.jsonl`);
11280
+ const path = join22(archiveDir, `${safeTopic}_${date}${reasonSuffix}_events${suffix}.jsonl`);
11132
11281
  try {
11133
11282
  writeFileSync11(path, body, { flag: "wx" });
11134
11283
  logger.info({ topicId, topicTitle, archive: path, eventCount: entries.length }, "archiveConversationEvents: archived raw conversation events");
@@ -11447,7 +11596,7 @@ var init_idle_archiver = __esm(async () => {
11447
11596
 
11448
11597
  // ../../packages/core/src/agents/topic-cleanup.ts
11449
11598
  import { mkdirSync as mkdirSync14, renameSync as renameSync8, unlinkSync as unlinkSync13, writeFileSync as writeFileSync12 } from "fs";
11450
- import { dirname as dirname14 } from "path";
11599
+ import { dirname as dirname15 } from "path";
11451
11600
  function collectSessionIdsByAgent(entries, extraSessions = []) {
11452
11601
  const out = new Map;
11453
11602
  for (const e of entries) {
@@ -11513,7 +11662,7 @@ function createTopicLogMaintenance(host) {
11513
11662
  const path = runtimeHost.activeConversationPath(opts.userId, opts.topicName);
11514
11663
  const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
11515
11664
  try {
11516
- mkdirSync14(dirname14(path), { recursive: true });
11665
+ mkdirSync14(dirname15(path), { recursive: true });
11517
11666
  writeFileSync12(tempPath, retained.length > 0 ? `${retained.map((entry) => JSON.stringify(entry)).join(`
11518
11667
  `)}
11519
11668
  ` : "", { flag: "wx" });
@@ -11676,8 +11825,8 @@ var init_usage_alert = __esm(() => {
11676
11825
  // ../../packages/core/src/topics/session.ts
11677
11826
  import { randomUUID as randomUUID10 } from "crypto";
11678
11827
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync4, writeFileSync as writeFileSync13 } from "fs";
11679
- import { tmpdir as tmpdir3 } from "os";
11680
- import { join as join22 } from "path";
11828
+ import { tmpdir as tmpdir4 } from "os";
11829
+ import { join as join23 } from "path";
11681
11830
  function previousCompactedSummary(entries) {
11682
11831
  for (let index = entries.length - 2;index >= 0; index -= 1) {
11683
11832
  const request = entries[index]?.event;
@@ -11807,7 +11956,7 @@ function formatCompactElapsed(startedAt) {
11807
11956
  async function summarizeTopicContext(request) {
11808
11957
  const startedAt = Date.now();
11809
11958
  const sessionIds = [];
11810
- const compactCwd = mkdtempSync2(join22(tmpdir3(), "negotium-compact-"));
11959
+ const compactCwd = mkdtempSync2(join23(tmpdir4(), "negotium-compact-"));
11811
11960
  const abortController = new AbortController;
11812
11961
  const relayAbort = () => abortController.abort(request.signal?.reason);
11813
11962
  if (request.signal?.aborted)
@@ -11834,7 +11983,7 @@ async function summarizeTopicContext(request) {
11834
11983
  let error = "";
11835
11984
  let toolViolation = false;
11836
11985
  let compactionLogCalls = 0;
11837
- const compactionLogPath = join22(compactCwd, "conversation.log");
11986
+ const compactionLogPath = join23(compactCwd, "conversation.log");
11838
11987
  try {
11839
11988
  const compactionMcp = useCompactionLog ? {
11840
11989
  compact_log: {
@@ -12453,14 +12602,14 @@ var init_derive = __esm(async () => {
12453
12602
  });
12454
12603
 
12455
12604
  // ../../packages/core/src/query/session-inbox-path.ts
12456
- import { join as join23 } from "path";
12605
+ import { join as join24 } from "path";
12457
12606
  function sessionInboxPath(userId, topicId) {
12458
12607
  const key = Buffer.from(topicId, "utf8").toString("base64url");
12459
- return join23(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${JSONL_SUFFIX}`);
12608
+ return join24(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${JSONL_SUFFIX}`);
12460
12609
  }
12461
12610
  function scheduledSessionInboxPath(userId, topicId) {
12462
12611
  const key = Buffer.from(topicId, "utf8").toString("base64url");
12463
- return join23(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${SCHEDULE_SUFFIX}`);
12612
+ return join24(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${SCHEDULE_SUFFIX}`);
12464
12613
  }
12465
12614
  var TOPIC_ID_FILE_PREFIX = "topic-id-", JSONL_SUFFIX = ".jsonl", SCHEDULE_SUFFIX = ".schedule";
12466
12615
  var init_session_inbox_path = __esm(() => {
@@ -12469,13 +12618,13 @@ var init_session_inbox_path = __esm(() => {
12469
12618
 
12470
12619
  // ../../packages/core/src/query/session-inbox-cleanup.ts
12471
12620
  import { unlinkSync as unlinkSync15 } from "fs";
12472
- import { basename as basename4, join as join24 } from "path";
12621
+ import { basename as basename4, join as join25 } from "path";
12473
12622
  function cleanupSessionInboxFiles(userId, topicId, legacyTopicTitle) {
12474
12623
  const live = sessionInboxPath(userId, topicId);
12475
12624
  const scheduled = scheduledSessionInboxPath(userId, topicId);
12476
12625
  const candidates = new Set([live, `${live}.processing`, scheduled, `${scheduled}.processing`]);
12477
12626
  if (legacyTopicTitle && legacyTopicTitle !== "." && legacyTopicTitle !== ".." && basename4(legacyTopicTitle) === legacyTopicTitle) {
12478
- const legacyBase = join24(SESSION_INBOX_DIR, userId, legacyTopicTitle);
12627
+ const legacyBase = join25(SESSION_INBOX_DIR, userId, legacyTopicTitle);
12479
12628
  for (const suffix of [".jsonl", ".jsonl.processing", ".schedule", ".schedule.processing"]) {
12480
12629
  candidates.add(`${legacyBase}${suffix}`);
12481
12630
  }
@@ -12501,16 +12650,16 @@ var init_session_inbox_cleanup = __esm(() => {
12501
12650
 
12502
12651
  // ../../packages/core/src/query/state.ts
12503
12652
  import { mkdirSync as mkdirSync16, renameSync as renameSync9, unlinkSync as unlinkSync16, writeFileSync as writeFileSync14 } from "fs";
12504
- import { basename as basename5, join as join25 } from "path";
12653
+ import { basename as basename5, join as join26 } from "path";
12505
12654
  function createQueryStateStore(options) {
12506
12655
  const sanitize = options.sanitizeTopicId ?? sanitizeId;
12507
- const queryStateDirPath = (userId) => join25(options.usersLogDir, String(userId), "active-queries");
12508
- const queryStateFile = (userId, topicId) => join25(queryStateDirPath(userId), `${sanitize(topicId)}.json`);
12656
+ const queryStateDirPath = (userId) => join26(options.usersLogDir, String(userId), "active-queries");
12657
+ const queryStateFile = (userId, topicId) => join26(queryStateDirPath(userId), `${sanitize(topicId)}.json`);
12509
12658
  const legacyQueryStateFile = (userId, topicName) => {
12510
12659
  if (!topicName || topicName === "." || topicName === ".." || basename5(topicName) !== topicName) {
12511
12660
  return null;
12512
12661
  }
12513
- return join25(queryStateDirPath(userId), `${topicName}.json`);
12662
+ return join26(queryStateDirPath(userId), `${topicName}.json`);
12514
12663
  };
12515
12664
  return {
12516
12665
  write(userId, topicId, topicName, task) {
@@ -12671,30 +12820,30 @@ import {
12671
12820
  unlinkSync as unlinkSync17,
12672
12821
  writeFileSync as writeFileSync15
12673
12822
  } from "fs";
12674
- import { dirname as dirname15, join as join26 } from "path";
12823
+ import { dirname as dirname16, join as join27 } from "path";
12675
12824
  function pendingAskDir(userId) {
12676
12825
  const rawUserId = String(userId);
12677
12826
  const safeUserId = /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash5("sha256").update(rawUserId).digest("hex")}`;
12678
- return join26(resolveStorageSessionAsksDir(), safeUserId);
12827
+ return join27(resolveStorageSessionAsksDir(), safeUserId);
12679
12828
  }
12680
12829
  function encodeAskKey(key) {
12681
12830
  return JSON.stringify([key.from, key.to]);
12682
12831
  }
12683
12832
  function pendingAskPath(key) {
12684
12833
  const digest = createHash5("sha256").update(encodeAskKey(key)).digest("hex");
12685
- return join26(pendingAskDir(key.userId), `${ASK_FILENAME_PREFIX}${digest}.pending`);
12834
+ return join27(pendingAskDir(key.userId), `${ASK_FILENAME_PREFIX}${digest}.pending`);
12686
12835
  }
12687
12836
  function v2PendingAskPath(key) {
12688
12837
  const encoded = Buffer.from(encodeAskKey(key), "utf8").toString("base64url");
12689
- return join26(pendingAskDir(key.userId), `${V2_ASK_FILENAME_PREFIX}${encoded}.pending`);
12838
+ return join27(pendingAskDir(key.userId), `${V2_ASK_FILENAME_PREFIX}${encoded}.pending`);
12690
12839
  }
12691
12840
  function legacyPendingAskPath(key) {
12692
12841
  if (key.from.includes("/") || key.from.includes("\\") || key.to.includes("/") || key.to.includes("\\") || key.from.includes("\x00") || key.to.includes("\x00")) {
12693
12842
  return null;
12694
12843
  }
12695
12844
  const dir = pendingAskDir(key.userId);
12696
- const candidate = join26(dir, `${key.from}___${key.to}.pending`);
12697
- return dirname15(candidate) === dir ? candidate : null;
12845
+ const candidate = join27(dir, `${key.from}___${key.to}.pending`);
12846
+ return dirname16(candidate) === dir ? candidate : null;
12698
12847
  }
12699
12848
  function parsePendingAskFilename(fileName) {
12700
12849
  if (!fileName.endsWith(".pending"))
@@ -12934,7 +13083,7 @@ function listPendingAsksForCaller(args) {
12934
13083
  const parsed = isV3 ? { from: args.from, to: "" } : parsePendingAskFilename(fileName);
12935
13084
  if (!parsed)
12936
13085
  continue;
12937
- const path = join26(dir, fileName);
13086
+ const path = join27(dir, fileName);
12938
13087
  const record = readPendingAskFile(path, {
12939
13088
  userId: args.userId,
12940
13089
  from: parsed.from,
@@ -12976,7 +13125,7 @@ function deletePendingAsksForTopic(args) {
12976
13125
  }
12977
13126
  let deleted = 0;
12978
13127
  for (const fileName of files) {
12979
- const path = join26(dir, fileName);
13128
+ const path = join27(dir, fileName);
12980
13129
  const parsed = parsePendingAskFilename(fileName);
12981
13130
  const record = readPendingAskFile(path, {
12982
13131
  userId: args.userId,
@@ -13414,7 +13563,7 @@ var init_self_schedules = __esm(async () => {
13414
13563
  // ../../packages/core/src/storage/token-stats.ts
13415
13564
  import { createHash as createHash6 } from "crypto";
13416
13565
  import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
13417
- import { join as join27 } from "path";
13566
+ import { join as join28 } from "path";
13418
13567
  function emptyBucket() {
13419
13568
  return {
13420
13569
  inputTokens: 0,
@@ -13433,7 +13582,7 @@ function queriesPath(userId) {
13433
13582
  const fileId = tokenStatsFileId(userId);
13434
13583
  const logDir = resolveStorageLogDir();
13435
13584
  mkdirSync18(logDir, { recursive: true });
13436
- return join27(logDir, `token-queries-${fileId}.jsonl`);
13585
+ return join28(logDir, `token-queries-${fileId}.jsonl`);
13437
13586
  }
13438
13587
  function loadRecords(userId) {
13439
13588
  try {
@@ -13835,7 +13984,7 @@ var init_lifecycle = __esm(async () => {
13835
13984
  // ../../packages/core/src/runtime/attachments.ts
13836
13985
  import { randomUUID as randomUUID13 } from "crypto";
13837
13986
  import { copyFileSync as copyFileSync2, mkdirSync as mkdirSync19, writeFileSync as writeFileSync17 } from "fs";
13838
- import { basename as basename6, join as join28 } from "path";
13987
+ import { basename as basename6, join as join29 } from "path";
13839
13988
  function workspaceCwdFor(topicId) {
13840
13989
  return resolveTopicWorkspaceDir(topicId);
13841
13990
  }
@@ -13848,7 +13997,7 @@ function materializePromptAttachments(topicId, queryId, attachmentIds) {
13848
13997
  if (!attachmentIds?.length)
13849
13998
  return [];
13850
13999
  const out = [];
13851
- const destDir = join28(workspaceCwdFor(topicId), "attachments", queryId);
14000
+ const destDir = join29(workspaceCwdFor(topicId), "attachments", queryId);
13852
14001
  for (const rawId of attachmentIds) {
13853
14002
  if (typeof rawId !== "string")
13854
14003
  continue;
@@ -13865,7 +14014,7 @@ function materializePromptAttachments(topicId, queryId, attachmentIds) {
13865
14014
  mkdirSync19(destDir, { recursive: true });
13866
14015
  const index = String(out.length + 1).padStart(2, "0");
13867
14016
  const safeName = safeAttachmentFilename(attachment.filename, fileId);
13868
- const destPath = join28(destDir, `${index}-${fileId.slice(0, 8)}-${safeName}`);
14017
+ const destPath = join29(destDir, `${index}-${fileId.slice(0, 8)}-${safeName}`);
13869
14018
  copyFileSync2(sourcePath, destPath);
13870
14019
  out.push({
13871
14020
  id: attachment.id,
@@ -13895,10 +14044,10 @@ function promptWithAttachments(prompt, attachments) {
13895
14044
  return composeAttachmentPrompt(prompt, attachments.map(({ filename, path }) => attachmentPromptLine(filename, path)));
13896
14045
  }
13897
14046
  function ingestAttachment(args) {
13898
- const destDir = join28(UPLOADS_DIR, args.topicId);
14047
+ const destDir = join29(UPLOADS_DIR, args.topicId);
13899
14048
  mkdirSync19(destDir, { recursive: true });
13900
14049
  const safeName = safeAttachmentFilename(args.filename, "upload");
13901
- const destPath = join28(destDir, `${Date.now()}-${randomUUID13().slice(0, 8)}-${safeName}`);
14050
+ const destPath = join29(destDir, `${Date.now()}-${randomUUID13().slice(0, 8)}-${safeName}`);
13902
14051
  if (args.sourcePath !== undefined) {
13903
14052
  copyFileSync2(args.sourcePath, destPath);
13904
14053
  } else if (args.bytes !== undefined) {
@@ -14073,12 +14222,12 @@ var init_errors = __esm(() => {
14073
14222
 
14074
14223
  // ../../packages/core/src/runtime/event-heartbeat.ts
14075
14224
  function nextOrHeartbeat(pending, intervalMs) {
14076
- return new Promise((resolve15, reject) => {
14077
- const timer = setTimeout(() => resolve15({ kind: "heartbeat" }), intervalMs);
14225
+ return new Promise((resolve16, reject) => {
14226
+ const timer = setTimeout(() => resolve16({ kind: "heartbeat" }), intervalMs);
14078
14227
  timer.unref?.();
14079
14228
  pending.then((result) => {
14080
14229
  clearTimeout(timer);
14081
- resolve15({ kind: "event", result });
14230
+ resolve16({ kind: "event", result });
14082
14231
  }, (error) => {
14083
14232
  clearTimeout(timer);
14084
14233
  reject(error);
@@ -14616,8 +14765,8 @@ function createAskUserRuntime(host) {
14616
14765
  return errorResult(`Error: failed to persist ask_user_question: ${error instanceof Error ? error.message : String(error)}`);
14617
14766
  }
14618
14767
  let resolveAnswer;
14619
- const promise = new Promise((resolve15) => {
14620
- resolveAnswer = resolve15;
14768
+ const promise = new Promise((resolve16) => {
14769
+ resolveAnswer = resolve16;
14621
14770
  });
14622
14771
  pendingAsks2.set(message.id, {
14623
14772
  topicId: ctx.topicId,
@@ -14945,7 +15094,7 @@ var init_visual_html = __esm(() => {
14945
15094
 
14946
15095
  // ../../packages/core/src/runtime/visuals.ts
14947
15096
  import { realpathSync as realpathSync5 } from "fs";
14948
- import { isAbsolute as isAbsolute4, resolve as resolve15 } from "path";
15097
+ import { isAbsolute as isAbsolute4, resolve as resolve16 } from "path";
14949
15098
  function activeVisualHtmlForPrompt(html) {
14950
15099
  if (html.length <= ACTIVE_VISUAL_PROMPT_MAX_CHARS) {
14951
15100
  return { html, omittedChars: 0 };
@@ -14996,8 +15145,8 @@ function topicAllowsVisualFileId(topicId, fileId) {
14996
15145
  return topicHasAttachmentFileId(topicId, fileId) || topicHasVisualFileId(topicId, fileId);
14997
15146
  }
14998
15147
  function isPathInside(baseDir, filePath) {
14999
- const base = resolve15(baseDir);
15000
- const normalized = resolve15(filePath);
15148
+ const base = resolve16(baseDir);
15149
+ const normalized = resolve16(filePath);
15001
15150
  try {
15002
15151
  const realBase = realpathSync5(base);
15003
15152
  const real = realpathSync5(normalized);
@@ -15059,7 +15208,7 @@ function resolveVisualMediaInput(topicId, input) {
15059
15208
  }
15060
15209
  const rawPath = input.file_path.trim();
15061
15210
  const cwd = workspaceCwdFor(topicId);
15062
- const candidate = isAbsolute4(rawPath) ? rawPath : resolve15(cwd, rawPath);
15211
+ const candidate = isAbsolute4(rawPath) ? rawPath : resolve16(cwd, rawPath);
15063
15212
  if (!isPathInside(cwd, candidate)) {
15064
15213
  return { error: "file_path must be inside the topic workspace" };
15065
15214
  }
@@ -15097,7 +15246,7 @@ var init_visuals = __esm(async () => {
15097
15246
  // ../../packages/core/src/runtime/turn-event-stream.ts
15098
15247
  import { randomUUID as randomUUID15 } from "crypto";
15099
15248
  import { realpathSync as realpathSync6, statSync as statSync9 } from "fs";
15100
- import { isAbsolute as isAbsolute5, resolve as resolve16 } from "path";
15249
+ import { isAbsolute as isAbsolute5, resolve as resolve17 } from "path";
15101
15250
  function sessionEventMatchesCurrentExecution(topicId, queryId, agent, model) {
15102
15251
  if (getRoomQuery(topicId)?.queryId !== queryId)
15103
15252
  return false;
@@ -15450,7 +15599,7 @@ ${JSON.stringify(event.input ?? {})}`);
15450
15599
  case "file":
15451
15600
  if (!silent && peerBridge) {
15452
15601
  const cwd = workspaceCwdFor(topicId);
15453
- const path = isAbsolute5(event.path) ? event.path : resolve16(cwd, event.path);
15602
+ const path = isAbsolute5(event.path) ? event.path : resolve17(cwd, event.path);
15454
15603
  if (!isPathInside(cwd, path)) {
15455
15604
  logger.warn({ topicId, path }, "peer output file is outside the topic workspace");
15456
15605
  break;
@@ -15732,15 +15881,15 @@ var init_turn_session = __esm(async () => {
15732
15881
  });
15733
15882
 
15734
15883
  // ../../packages/core/src/storage/app-settings.ts
15735
- import { existsSync as existsSync20, mkdirSync as mkdirSync20, readFileSync as readFileSync20, writeFileSync as writeFileSync18 } from "fs";
15736
- import { dirname as dirname16, join as join29 } from "path";
15884
+ import { existsSync as existsSync21, mkdirSync as mkdirSync20, readFileSync as readFileSync20, writeFileSync as writeFileSync18 } from "fs";
15885
+ import { dirname as dirname17, join as join30 } from "path";
15737
15886
  function settingsFile() {
15738
- return join29(resolveStorageDataDir(), "otium-settings.json");
15887
+ return join30(resolveStorageDataDir(), "otium-settings.json");
15739
15888
  }
15740
15889
  function getGlobalAiName() {
15741
15890
  const path = settingsFile();
15742
15891
  try {
15743
- if (existsSync20(path)) {
15892
+ if (existsSync21(path)) {
15744
15893
  const data = JSON.parse(readFileSync20(path, "utf8"));
15745
15894
  if (typeof data.aiName === "string" && data.aiName.trim()) {
15746
15895
  return data.aiName.trim();
@@ -15829,8 +15978,8 @@ __export(exports_turn_runner, {
15829
15978
  AGENT_DISPLAY_NAME: () => AGENT_DISPLAY_NAME
15830
15979
  });
15831
15980
  import { randomUUID as randomUUID16 } from "crypto";
15832
- import { existsSync as existsSync21, mkdirSync as mkdirSync21, readdirSync as readdirSync7, statSync as statSync10 } from "fs";
15833
- import { join as join30 } from "path";
15981
+ import { existsSync as existsSync22, mkdirSync as mkdirSync21, readdirSync as readdirSync7, statSync as statSync10 } from "fs";
15982
+ import { join as join31 } from "path";
15834
15983
  function withDefaultPlaywright(configuredMcp, isManager) {
15835
15984
  if (isManager)
15836
15985
  return configuredMcp;
@@ -15885,8 +16034,8 @@ function appendAskReplyMessage(topicId, text2, agentType) {
15885
16034
  return message;
15886
16035
  }
15887
16036
  function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, matchesTitle, preferExact = true) {
15888
- const preferred = join30(directory, preferredFilename);
15889
- if (preferExact && existsSync21(preferred))
16037
+ const preferred = join31(directory, preferredFilename);
16038
+ if (preferExact && existsSync22(preferred))
15890
16039
  return preferred;
15891
16040
  let newestLegacyId = null;
15892
16041
  let newestTitle = null;
@@ -15896,7 +16045,7 @@ function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, ma
15896
16045
  const legacyIdMatch = !titleMatch && matchesLegacyId(filename);
15897
16046
  if (!titleMatch && !legacyIdMatch)
15898
16047
  continue;
15899
- const path = join30(directory, filename);
16048
+ const path = join31(directory, filename);
15900
16049
  const mtimeMs = statSync10(path).mtimeMs;
15901
16050
  if (titleMatch) {
15902
16051
  if (!newestTitle || mtimeMs > newestTitle.mtimeMs) {
@@ -15910,10 +16059,10 @@ function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, ma
15910
16059
  return newestTitle?.path ?? newestLegacyId?.path ?? preferred;
15911
16060
  }
15912
16061
  function resolveWikiMemoryMirror(wikiDir, topicId, topicTitle) {
15913
- const briefFile = resolveWikiMirrorPath(join30(wikiDir, "topic"), `${wikiBriefStorageKey(topicTitle, topicId)}.md`, (filename) => isTopicBriefFile(filename, topicId), (filename) => isTopicBriefFile(filename, topicId, topicTitle));
15914
- const hasBriefFile = existsSync21(briefFile) && statSync10(briefFile).isFile();
15915
- const latestSummaryCandidate = resolveWikiMirrorPath(join30(wikiDir, "summaries"), `__missing__-${wikiSummaryFilename("0000-00-00", topicTitle, topicId)}`, (filename) => isTopicSummaryFile(filename, topicId), (filename) => isTopicSummaryFile(filename, topicId, topicTitle), false);
15916
- const latestSummaryFile = existsSync21(latestSummaryCandidate) && statSync10(latestSummaryCandidate).isFile() ? latestSummaryCandidate : null;
16062
+ const briefFile = resolveWikiMirrorPath(join31(wikiDir, "topic"), `${wikiBriefStorageKey(topicTitle, topicId)}.md`, (filename) => isTopicBriefFile(filename, topicId), (filename) => isTopicBriefFile(filename, topicId, topicTitle));
16063
+ const hasBriefFile = existsSync22(briefFile) && statSync10(briefFile).isFile();
16064
+ const latestSummaryCandidate = resolveWikiMirrorPath(join31(wikiDir, "summaries"), `__missing__-${wikiSummaryFilename("0000-00-00", topicTitle, topicId)}`, (filename) => isTopicSummaryFile(filename, topicId), (filename) => isTopicSummaryFile(filename, topicId, topicTitle), false);
16065
+ const latestSummaryFile = existsSync22(latestSummaryCandidate) && statSync10(latestSummaryCandidate).isFile() ? latestSummaryCandidate : null;
15917
16066
  return { briefFile, hasBriefFile, latestSummaryFile };
15918
16067
  }
15919
16068
  async function streamAgentEvents(topicId, topicTitle, queryId, events, control, agentType, model, effort, userId, retryableSessionExpired = true, onSessionId, execution) {
@@ -17109,9 +17258,9 @@ __export(exports_auth_check, {
17109
17258
  checkAgentAuth: () => checkAgentAuth
17110
17259
  });
17111
17260
  import { execFileSync as execFileSync7 } from "child_process";
17112
- import { existsSync as existsSync22 } from "fs";
17261
+ import { existsSync as existsSync23 } from "fs";
17113
17262
  import { homedir as homedir8, platform } from "os";
17114
- import { join as join31 } from "path";
17263
+ import { join as join32 } from "path";
17115
17264
  function hasMaestroCredential(host, key, userId) {
17116
17265
  if (userId && host.getVaultValue(userId, key)?.trim())
17117
17266
  return true;
@@ -17131,7 +17280,7 @@ function checkAgentAuth(agent, host = defaultAgentAuthHost, userId) {
17131
17280
  case "claude": {
17132
17281
  if (host.environment.ANTHROPIC_API_KEY)
17133
17282
  return { ok: true };
17134
- const path = join31(host.homeDirectory(), ".claude", ".credentials.json");
17283
+ const path = join32(host.homeDirectory(), ".claude", ".credentials.json");
17135
17284
  if (host.operatingSystem() === "darwin") {
17136
17285
  if (host.hasMacOsCredential("Claude Code-credentials") || host.exists(path)) {
17137
17286
  return { ok: true };
@@ -17182,7 +17331,7 @@ var init_auth_check = __esm(async () => {
17182
17331
  await init_vault();
17183
17332
  defaultAgentAuthHost = {
17184
17333
  codexAuthFilePath,
17185
- exists: existsSync22,
17334
+ exists: existsSync23,
17186
17335
  environment: process.env,
17187
17336
  homeDirectory: homedir8,
17188
17337
  operatingSystem: platform,
@@ -18133,280 +18282,6 @@ Available: ${available.join(", ") || "none"}`
18133
18282
  }
18134
18283
  return Object.freeze({ listTargets, getTopics, validateTarget });
18135
18284
  }
18136
- // ../../packages/core/src/mcp/vault-http.ts
18137
- var SAFE_RESPONSE_HEADERS = new Set([
18138
- "content-type",
18139
- "content-length",
18140
- "location",
18141
- "retry-after",
18142
- "x-ratelimit-limit",
18143
- "x-ratelimit-remaining",
18144
- "x-ratelimit-reset"
18145
- ]);
18146
- var FORBIDDEN_REQUEST_HEADERS = new Set([
18147
- "connection",
18148
- "content-length",
18149
- "host",
18150
- "proxy-authorization",
18151
- "transfer-encoding"
18152
- ]);
18153
- function substituteObject(userId, values, host) {
18154
- const used = new Set;
18155
- const substituted = {};
18156
- for (const [key, value2] of Object.entries(values)) {
18157
- if (FORBIDDEN_REQUEST_HEADERS.has(key.toLowerCase())) {
18158
- throw new Error(`Header "${key}" is not allowed`);
18159
- }
18160
- const result = host.substitute(userId, value2);
18161
- for (const usedKey of result.usedKeys)
18162
- used.add(usedKey);
18163
- substituted[key] = result.text;
18164
- }
18165
- return { values: substituted, usedKeys: [...used] };
18166
- }
18167
- function safeResponseHeaders(userId, headers, host) {
18168
- const output = {};
18169
- for (const [key, value2] of headers.entries()) {
18170
- if (!SAFE_RESPONSE_HEADERS.has(key.toLowerCase()))
18171
- continue;
18172
- output[key] = host.redact(userId, value2);
18173
- }
18174
- return output;
18175
- }
18176
- async function readBoundedBody(response, maxBytes) {
18177
- if (!response.body)
18178
- return { bytes: new Uint8Array, truncated: false };
18179
- const reader = response.body.getReader();
18180
- const chunks = [];
18181
- let keptBytes = 0;
18182
- let truncated = false;
18183
- try {
18184
- while (true) {
18185
- const next = await reader.read();
18186
- if (next.done)
18187
- break;
18188
- const remaining = maxBytes - keptBytes;
18189
- if (remaining <= 0) {
18190
- truncated = true;
18191
- await reader.cancel();
18192
- break;
18193
- }
18194
- const visible = next.value.subarray(0, remaining);
18195
- chunks.push(visible);
18196
- keptBytes += visible.byteLength;
18197
- if (visible.byteLength < next.value.byteLength) {
18198
- truncated = true;
18199
- await reader.cancel();
18200
- break;
18201
- }
18202
- }
18203
- } finally {
18204
- reader.releaseLock();
18205
- }
18206
- const bytes = new Uint8Array(keptBytes);
18207
- let offset = 0;
18208
- for (const chunk of chunks) {
18209
- bytes.set(chunk, offset);
18210
- offset += chunk.byteLength;
18211
- }
18212
- return { bytes, truncated };
18213
- }
18214
- async function executeVaultHttpRequest(userId, request, host, fetchImpl = fetch) {
18215
- let parsedUrl;
18216
- try {
18217
- parsedUrl = new URL(request.url);
18218
- } catch {
18219
- return { ok: false, error: "url must be an absolute HTTPS URL" };
18220
- }
18221
- if (parsedUrl.protocol !== "https:") {
18222
- return {
18223
- ok: false,
18224
- error: "Vault credentials may only be sent over HTTPS"
18225
- };
18226
- }
18227
- if (parsedUrl.username || parsedUrl.password || /\{\{[^}]+\}\}/.test(request.url)) {
18228
- return {
18229
- ok: false,
18230
- error: "Keep Vault placeholders out of URLs; put credentials in headers or body"
18231
- };
18232
- }
18233
- let headers;
18234
- let headerKeys;
18235
- try {
18236
- const substituted = substituteObject(userId, request.headers ?? {}, host);
18237
- headers = substituted.values;
18238
- headerKeys = substituted.usedKeys;
18239
- } catch (error) {
18240
- return {
18241
- ok: false,
18242
- error: error instanceof Error ? error.message : String(error)
18243
- };
18244
- }
18245
- const bodyResult = request.body === undefined ? { text: undefined, usedKeys: [] } : host.substitute(userId, request.body);
18246
- const usedKeys = [...new Set([...headerKeys, ...bodyResult.usedKeys])].sort();
18247
- if (usedKeys.length === 0) {
18248
- return {
18249
- ok: false,
18250
- error: "No valid Vault placeholder was found in headers or body"
18251
- };
18252
- }
18253
- if (request.method === "GET" && request.body !== undefined) {
18254
- return {
18255
- ok: false,
18256
- error: "GET requests cannot include a credential-bearing body"
18257
- };
18258
- }
18259
- const controller = new AbortController;
18260
- const timeoutMs = Math.min(Math.max(request.timeoutMs ?? 30000, 1000), 120000);
18261
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
18262
- const startedAt = Date.now();
18263
- try {
18264
- const response = await fetchImpl(parsedUrl, {
18265
- method: request.method,
18266
- headers,
18267
- body: request.method === "GET" ? undefined : bodyResult.text,
18268
- redirect: "manual",
18269
- signal: controller.signal
18270
- });
18271
- const maxBytes = Math.min(Math.max(request.maxResponseBytes ?? 256 * 1024, 1024), 1024 * 1024);
18272
- const { bytes, truncated } = await readBoundedBody(response, maxBytes);
18273
- const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
18274
- const textual = contentType.startsWith("text/") || contentType.includes("json") || contentType.includes("xml") || contentType.includes("javascript") || contentType === "";
18275
- const body = textual ? host.redact(userId, new TextDecoder().decode(bytes)) : `[binary response omitted: ${bytes.byteLength} bytes, ${contentType || "unknown content type"}]`;
18276
- host.log?.("info", {
18277
- userId,
18278
- vaultKeys: usedKeys,
18279
- host: parsedUrl.hostname,
18280
- method: request.method,
18281
- status: response.status,
18282
- durationMs: Date.now() - startedAt
18283
- }, "vault credential used");
18284
- return {
18285
- ok: response.ok,
18286
- status: response.status,
18287
- statusText: response.statusText,
18288
- headers: safeResponseHeaders(userId, response.headers, host),
18289
- body,
18290
- truncated
18291
- };
18292
- } catch (error) {
18293
- const raw = error instanceof Error ? error.message : String(error);
18294
- host.log?.("warn", {
18295
- userId,
18296
- vaultKeys: usedKeys,
18297
- host: parsedUrl.hostname,
18298
- method: request.method,
18299
- durationMs: Date.now() - startedAt
18300
- }, "vault credential request failed");
18301
- return { ok: false, error: host.redact(userId, raw) };
18302
- } finally {
18303
- clearTimeout(timeout);
18304
- }
18305
- }
18306
- // ../../packages/core/src/mcp/vault-run.ts
18307
- import { spawn } from "child_process";
18308
- function appendBounded(chunks, chunk, state, maxBytes) {
18309
- if (state.bytes >= maxBytes) {
18310
- state.truncated = true;
18311
- return;
18312
- }
18313
- const remaining = maxBytes - state.bytes;
18314
- const visible = chunk.subarray(0, remaining);
18315
- chunks.push(visible);
18316
- state.bytes += visible.byteLength;
18317
- if (visible.byteLength < chunk.byteLength)
18318
- state.truncated = true;
18319
- }
18320
- async function executeVaultRun(userId, request, host) {
18321
- const substitution = host.substitute(userId, request.command);
18322
- if (substitution.usedKeys.length === 0) {
18323
- return {
18324
- ok: false,
18325
- exitCode: null,
18326
- signal: null,
18327
- stdout: "",
18328
- stderr: "",
18329
- truncated: false,
18330
- usedKeys: [],
18331
- error: "No valid Vault placeholder was found in command"
18332
- };
18333
- }
18334
- const timeoutMs = Math.min(Math.max(request.timeoutMs ?? 120000, 1000), 600000);
18335
- const maxOutputBytes = Math.min(Math.max(request.maxOutputBytes ?? 512 * 1024, 1024), 2 * 1024 * 1024);
18336
- const stdoutChunks = [];
18337
- const stderrChunks = [];
18338
- const stdoutState = { bytes: 0, truncated: false };
18339
- const stderrState = { bytes: 0, truncated: false };
18340
- const startedAt = Date.now();
18341
- return await new Promise((resolve) => {
18342
- const child = spawn(process.env.SHELL || "/bin/sh", ["-s"], {
18343
- cwd: request.cwd,
18344
- detached: true,
18345
- env: process.env,
18346
- stdio: ["pipe", "pipe", "pipe"]
18347
- });
18348
- let settled = false;
18349
- let timedOut = false;
18350
- const signalTree = (signal) => {
18351
- if (!child.pid)
18352
- return;
18353
- try {
18354
- process.kill(-child.pid, signal);
18355
- } catch {
18356
- try {
18357
- child.kill(signal);
18358
- } catch {}
18359
- }
18360
- };
18361
- const finish = (result) => {
18362
- if (settled)
18363
- return;
18364
- settled = true;
18365
- clearTimeout(timeout);
18366
- host.log?.("info", {
18367
- userId,
18368
- vaultKeys: substitution.usedKeys,
18369
- exitCode: result.exitCode,
18370
- signal: result.signal,
18371
- durationMs: Date.now() - startedAt,
18372
- timedOut
18373
- }, "vault credential command used");
18374
- resolve({ ...result, usedKeys: substitution.usedKeys });
18375
- };
18376
- child.stdout.on("data", (chunk) => appendBounded(stdoutChunks, chunk, stdoutState, maxOutputBytes));
18377
- child.stderr.on("data", (chunk) => appendBounded(stderrChunks, chunk, stderrState, maxOutputBytes));
18378
- child.once("error", (error) => {
18379
- finish({
18380
- ok: false,
18381
- exitCode: null,
18382
- signal: null,
18383
- stdout: "",
18384
- stderr: "",
18385
- truncated: false,
18386
- error: host.redact(userId, error.message)
18387
- });
18388
- });
18389
- child.once("close", (exitCode, signal) => {
18390
- signalTree("SIGTERM");
18391
- const stdout = host.redact(userId, Buffer.concat(stdoutChunks).toString("utf8"));
18392
- const stderr = host.redact(userId, Buffer.concat(stderrChunks).toString("utf8"));
18393
- finish({
18394
- ok: !timedOut && exitCode === 0,
18395
- exitCode,
18396
- signal,
18397
- stdout,
18398
- stderr,
18399
- truncated: stdoutState.truncated || stderrState.truncated,
18400
- ...timedOut ? { error: `Command timed out after ${timeoutMs}ms` } : {}
18401
- });
18402
- });
18403
- const timeout = setTimeout(() => {
18404
- timedOut = true;
18405
- signalTree("SIGKILL");
18406
- }, timeoutMs);
18407
- child.stdin.end(substitution.text);
18408
- });
18409
- }
18410
18285
  // ../../packages/core/src/mcp/wiki-server.ts
18411
18286
  import { AsyncLocalStorage } from "async_hooks";
18412
18287
  import { randomUUID } from "crypto";
@@ -20334,7 +20209,7 @@ init_maestro_registry();
20334
20209
  init_config();
20335
20210
  init_jsonl();
20336
20211
  init_mcp_helpers();
20337
- import { spawn as spawn5 } from "child_process";
20212
+ import { spawn as spawn4 } from "child_process";
20338
20213
  import { existsSync as existsSync10, readdirSync as readdirSync3 } from "fs";
20339
20214
  import { join as join11 } from "path";
20340
20215
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -20353,7 +20228,7 @@ function signalProcessTree(child, signal) {
20353
20228
  }
20354
20229
  function spawnCapture(cmd, args, timeoutMs, signal, env) {
20355
20230
  return new Promise((resolve8, reject) => {
20356
- const child = spawn5(cmd, args, {
20231
+ const child = spawn4(cmd, args, {
20357
20232
  env: env ?? process.env,
20358
20233
  stdio: ["ignore", "pipe", "pipe"],
20359
20234
  detached: process.platform !== "win32"
@@ -21450,14 +21325,11 @@ function createTokenStatsMcpServer(context, host = defaultTokenStatsMcpHost) {
21450
21325
  return server;
21451
21326
  }
21452
21327
  // ../../packages/core/src/mcp/factories/vault.ts
21453
- import { McpServer as McpServer8 } from "@modelcontextprotocol/sdk/server/mcp.js";
21454
- import { z as z9 } from "zod";
21455
21328
  init_mcp_helpers();
21456
- function createVaultMcpServer(context, host, executors = {}) {
21329
+ import { McpServer as McpServer8 } from "@modelcontextprotocol/sdk/server/mcp.js";
21330
+ function createVaultMcpServer(context, host) {
21457
21331
  const server = new McpServer8({ name: "vault", version: "1.0.0" });
21458
- const run = executors.run ?? executeVaultRun;
21459
- const http = executors.http ?? executeVaultHttpRequest;
21460
- server.tool("vault_list", context.httpOnly ? "List the user's Vault keys and descriptions without exposing values. Use vault_http_request for HTTPS APIs that need a credential." : "List the user's Vault keys and descriptions without exposing values. Use vault_http_request for APIs and vault_run for shell/CLI work that needs a credential.", {}, () => {
21332
+ server.tool("vault_list", "List the user's Vault keys and descriptions without exposing values. Use {{KEY}} placeholders directly in supported transient tool inputs.", {}, () => {
21461
21333
  if (!context.userId)
21462
21334
  return mcpOk("(vault unavailable: no user context)");
21463
21335
  const entries = host.list(context.userId);
@@ -21468,51 +21340,11 @@ function createVaultMcpServer(context, host, executors = {}) {
21468
21340
  ${lines.join(`
21469
21341
  `)}`);
21470
21342
  });
21471
- if (!context.listOnly && !context.httpOnly) {
21472
- server.tool("vault_run", "Run a shell command containing {{KEY}} references inside Otium's credential broker. Expanded command input never reaches the model/provider, and stdout/stderr are redacted before return. Prefer vault_http_request for HTTP APIs.", {
21473
- command: z9.string().min(1).max(64 * 1024),
21474
- timeout_ms: z9.number().int().min(1000).max(600000).optional(),
21475
- max_output_bytes: z9.number().int().min(1024).max(2 * 1024 * 1024).optional()
21476
- }, async ({ command, timeout_ms, max_output_bytes }) => {
21477
- if (!context.userId)
21478
- return mcpError("Vault unavailable: no user context");
21479
- const result = await run(context.userId, {
21480
- command,
21481
- timeoutMs: timeout_ms,
21482
- maxOutputBytes: max_output_bytes,
21483
- cwd: context.cwd
21484
- }, host);
21485
- return result.error && result.exitCode === null ? mcpError(result.error) : mcpOk(JSON.stringify(result, null, 2));
21486
- });
21487
- }
21488
- if (!context.listOnly)
21489
- server.tool("vault_http_request", "Make an HTTPS request with {{KEY}} references resolved inside Otium. Put secrets in headers or body, never in the URL. The expanded request is not returned; the response is redacted before the model sees it.", {
21490
- method: z9.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
21491
- url: z9.string().url().describe("Absolute HTTPS URL without Vault placeholders"),
21492
- headers: z9.record(z9.string(), z9.string()).optional(),
21493
- body: z9.string().optional(),
21494
- timeout_ms: z9.number().int().min(1000).max(120000).optional(),
21495
- max_response_bytes: z9.number().int().min(1024).max(1024 * 1024).optional()
21496
- }, async ({ method, url, headers, body, timeout_ms, max_response_bytes }) => {
21497
- if (!context.userId)
21498
- return mcpError("Vault unavailable: no user context");
21499
- const result = await http(context.userId, {
21500
- method,
21501
- url,
21502
- headers,
21503
- body,
21504
- timeoutMs: timeout_ms,
21505
- maxResponseBytes: max_response_bytes
21506
- }, host);
21507
- return result.error ? mcpError(result.error) : mcpOk(JSON.stringify(result, null, 2));
21508
- });
21509
21343
  return server;
21510
21344
  }
21511
21345
  export {
21512
21346
  protectMcpStdio,
21513
21347
  parseSessionCommContext,
21514
- executeVaultRun,
21515
- executeVaultHttpRequest,
21516
21348
  defaultTokenStatsMcpHost,
21517
21349
  defaultTaskMcpHost,
21518
21350
  defaultSystemHealthMcpHost,
@@ -21529,4 +21361,4 @@ export {
21529
21361
  createAgentHealthMcpServer
21530
21362
  };
21531
21363
 
21532
- //# debugId=2A5A21A70EF2539564756E2164756E21
21364
+ //# debugId=3C9958DCA4DDE01364756E2164756E21