negotium 0.2.4 → 0.2.6

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 +241 -127
  2. package/dist/agent-helpers.js.map +19 -19
  3. package/dist/background-bash.js +1 -3
  4. package/dist/background-bash.js.map +4 -4
  5. package/dist/browser-runtime.js +1 -3
  6. package/dist/browser-runtime.js.map +4 -4
  7. package/dist/{chunk-1s9ryz8g.js → chunk-qmbcbhyy.js} +20 -12
  8. package/dist/chunk-qmbcbhyy.js.map +35 -0
  9. package/dist/hosted-agent.js +13 -10
  10. package/dist/hosted-agent.js.map +10 -10
  11. package/dist/main.js +607 -438
  12. package/dist/main.js.map +30 -29
  13. package/dist/mcp-factories.js +238 -123
  14. package/dist/mcp-factories.js.map +18 -18
  15. package/dist/prompts.js +1 -3
  16. package/dist/prompts.js.map +4 -4
  17. package/dist/query-runtime.js +1 -3
  18. package/dist/query-runtime.js.map +5 -5
  19. package/dist/registry.js +3 -3
  20. package/dist/registry.js.map +2 -2
  21. package/dist/rollout.js +1 -1
  22. package/dist/runtime/src/agents/api-topic-agent-switch.ts +6 -12
  23. package/dist/runtime/src/agents/claude-provider.ts +2 -2
  24. package/dist/runtime/src/agents/codex-provider.ts +1 -0
  25. package/dist/runtime/src/agents/index.ts +19 -14
  26. package/dist/runtime/src/agents/maestro-provider.ts +1 -1
  27. package/dist/runtime/src/agents/rollout/codex.ts +25 -18
  28. package/dist/runtime/src/agents/self-config-core.ts +2 -4
  29. package/dist/runtime/src/agents/topic-agent-switch.ts +4 -10
  30. package/dist/runtime/src/application/submit-runtime-gateway-turn.ts +91 -19
  31. package/dist/runtime/src/application/switch-topic-model.ts +7 -10
  32. package/dist/runtime/src/index.ts +4 -0
  33. package/dist/runtime/src/mcp/wiki-server.ts +19 -12
  34. package/dist/runtime/src/platform/config.ts +0 -2
  35. package/dist/runtime/src/platform/log-rotation.ts +45 -0
  36. package/dist/runtime/src/platform/mcp-config.ts +9 -3
  37. package/dist/runtime/src/query/active-rooms.ts +7 -1
  38. package/dist/runtime/src/runtime/errors.ts +1 -0
  39. package/dist/runtime/src/runtime/turn-event-stream.ts +46 -1
  40. package/dist/runtime/src/runtime/turn-runner.ts +176 -100
  41. package/dist/runtime/src/runtime/user-turn-envelope.ts +19 -0
  42. package/dist/runtime/src/storage/runtime-gateway-submissions.ts +32 -3
  43. package/dist/runtime/src/storage/runtime-turn-requests.ts +68 -7
  44. package/dist/runtime/src/types.ts +2 -0
  45. package/dist/runtime/src/version.ts +1 -1
  46. package/dist/runtime-helpers.js +1 -3
  47. package/dist/runtime-helpers.js.map +4 -4
  48. package/dist/storage.js.map +1 -1
  49. package/dist/types/packages/core/src/agents/api-topic-agent-switch.d.ts +1 -1
  50. package/dist/types/packages/core/src/agents/self-config-core.d.ts +1 -1
  51. package/dist/types/packages/core/src/platform/config.d.ts +0 -1
  52. package/dist/types/packages/core/src/platform/mcp-config.d.ts +3 -0
  53. package/dist/types/packages/core/src/query/active-rooms.d.ts +7 -1
  54. package/dist/types/packages/core/src/runtime/turn-runner.d.ts +42 -1
  55. package/dist/types/packages/core/src/runtime/user-turn-envelope.d.ts +7 -0
  56. package/dist/types/packages/core/src/storage/runtime-turn-requests.d.ts +17 -0
  57. package/dist/types/packages/core/src/types.d.ts +2 -0
  58. package/dist/types/packages/core/src/version.d.ts +1 -1
  59. package/dist/vault.js +1 -3
  60. package/dist/vault.js.map +4 -4
  61. package/package.json +1 -1
  62. package/dist/chunk-1s9ryz8g.js.map +0 -35
@@ -240,7 +240,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
240
240
  function codexAuthFilePath() {
241
241
  return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
242
242
  }
243
- var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, CONTEXTS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.16", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, BACKGROUND_BASH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
243
+ var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.16", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, BACKGROUND_BASH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
244
244
  var init_config = __esm(() => {
245
245
  init_config_helpers();
246
246
  init_logger();
@@ -257,7 +257,6 @@ var init_config = __esm(() => {
257
257
  BROWSER_PROFILES_DIR = resolve(BROWSER_DIR, "profiles");
258
258
  BINARIES_DIR = resolve(STATE_DIR, "binaries");
259
259
  SECRETS_DIR = resolve(STATE_DIR, "secrets");
260
- CONTEXTS_DIR = resolve(STATE_DIR, "data", "contexts");
261
260
  DM_WORKSPACE_DIR = resolve(STATE_DIR, "data", "dm");
262
261
  SESSION_WORKSPACE_DIR = resolve(STATE_DIR, "data", "sessions");
263
262
  CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
@@ -317,7 +316,6 @@ var init_config = __esm(() => {
317
316
  mkdirSync(TOPIC_WORKSPACE_DIR, { recursive: true });
318
317
  mkdirSync(SHARED_WIKI_DIR, { recursive: true });
319
318
  mkdirSync(CRON_WORKSPACE_DIR, { recursive: true });
320
- mkdirSync(CONTEXTS_DIR, { recursive: true });
321
319
  mkdirSync(DM_WORKSPACE_DIR, { recursive: true });
322
320
  mkdirSync(SESSION_WORKSPACE_DIR, { recursive: true });
323
321
  mkdirSync(BROWSER_DIR, { recursive: true });
@@ -402,6 +400,14 @@ function renderUserPromptBatch(prompts) {
402
400
  ].join(`
403
401
  `);
404
402
  }
403
+ function renderUserTurnPrompt(message) {
404
+ const rawLabel = message.actorLabel?.trim() || message.actorUserId?.trim();
405
+ const label = rawLabel?.replace(/[\r\n\t]+/g, " ").replace(/\[/g, "(").replace(/\]/g, ")").replace(/\s{2,}/g, " ");
406
+ return label ? `[@${label}]: ${message.prompt}` : message.prompt;
407
+ }
408
+ function renderUserTurnBatch(messages) {
409
+ return renderUserPromptBatch(messages.map(renderUserTurnPrompt));
410
+ }
405
411
 
406
412
  // ../../packages/core/src/agents/rollout/shared.ts
407
413
  import { mkdirSync as mkdirSync3 } from "fs";
@@ -934,7 +940,7 @@ var init_claude_registry = __esm(() => {
934
940
  });
935
941
 
936
942
  // ../../packages/core/src/version.ts
937
- var NEGOTIUM_VERSION = "0.2.4";
943
+ var NEGOTIUM_VERSION = "0.2.6";
938
944
 
939
945
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
940
946
  import { spawn as spawn2 } from "child_process";
@@ -1761,6 +1767,7 @@ function getManagerMcpServers(opts) {
1761
1767
  function getForumMcpServers(opts) {
1762
1768
  const {
1763
1769
  userId,
1770
+ vaultUserId,
1764
1771
  session,
1765
1772
  topicId,
1766
1773
  subagentParentTopicId,
@@ -1791,6 +1798,7 @@ function getForumMcpServers(opts) {
1791
1798
  };
1792
1799
  const base = buildScope("forum", {
1793
1800
  userId,
1801
+ vaultUserId,
1794
1802
  session,
1795
1803
  topicId,
1796
1804
  subagentParentTopicId,
@@ -1884,6 +1892,7 @@ function getMcpServersForQuery(opts) {
1884
1892
  }
1885
1893
  return getForumMcpServers({
1886
1894
  userId: opts.userId || "local",
1895
+ vaultUserId: opts.vaultUserId,
1887
1896
  session: opts.session || "default",
1888
1897
  topicId: opts.topicId,
1889
1898
  subagentParentTopicId: opts.subagentParentTopicId,
@@ -2085,11 +2094,11 @@ var init_mcp_config = __esm(() => {
2085
2094
  vault: {
2086
2095
  ...commonRuntimeMcpPolicy("vault"),
2087
2096
  build(ctx) {
2088
- const { userId, agent } = ctx;
2089
- const args = [`--user-id=${userId}`];
2097
+ const { userId, vaultUserId, agent } = ctx;
2098
+ const args = [`--user-id=${vaultUserId ?? userId}`];
2090
2099
  if (agent !== "codex")
2091
2100
  args.push("--list-only=true");
2092
- return buildBuiltinMcpServer("vault", ctx, () => buildStdioMcpServer(agent, VAULT_SERVER, args));
2101
+ return buildBuiltinMcpServer("vault", { ...ctx, userId: vaultUserId ?? userId }, () => buildStdioMcpServer(agent, VAULT_SERVER, args));
2093
2102
  }
2094
2103
  }
2095
2104
  };
@@ -2477,6 +2486,18 @@ function patchEnvContext(envContext, cwd, currentDate, timezone) {
2477
2486
  }
2478
2487
  }
2479
2488
  }
2489
+ function codexRolloutPath(threadId, fallback) {
2490
+ const threadCreatedAt = decodeUuidV7Timestamp(threadId);
2491
+ const createdAt = threadCreatedAt === null ? fallback : new Date(threadCreatedAt);
2492
+ const yyyy = String(createdAt.getFullYear());
2493
+ const mm = String(createdAt.getMonth() + 1).padStart(2, "0");
2494
+ const dd = String(createdAt.getDate()).padStart(2, "0");
2495
+ const hh = String(createdAt.getHours()).padStart(2, "0");
2496
+ const min = String(createdAt.getMinutes()).padStart(2, "0");
2497
+ const ss = String(createdAt.getSeconds()).padStart(2, "0");
2498
+ const dir = join7(codexSessionsDir(), yyyy, mm, dd);
2499
+ return join7(dir, `rollout-${yyyy}-${mm}-${dd}T${hh}-${min}-${ss}-${threadId}.jsonl`);
2500
+ }
2480
2501
  function canonicalFilePath(path) {
2481
2502
  const absolute = resolve5(path);
2482
2503
  try {
@@ -2789,12 +2810,7 @@ function writeCodexRollout(opts) {
2789
2810
  type: "event_msg",
2790
2811
  payload: { type: "task_complete" }
2791
2812
  });
2792
- const yyyy = now.getUTCFullYear();
2793
- const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
2794
- const dd = String(now.getUTCDate()).padStart(2, "0");
2795
- const tsStr = tsIso.replace(/[:.]/g, "-").slice(0, 19);
2796
- const dir = join7(codexSessionsDir(), String(yyyy), mm, dd);
2797
- const path = join7(dir, `rollout-${tsStr}-${threadId}.jsonl`);
2813
+ const path = codexRolloutPath(threadId, now);
2798
2814
  if (opts.threadId) {
2799
2815
  sweepPriorRolloutsForThread(opts.threadId);
2800
2816
  }
@@ -4711,7 +4727,7 @@ function rowToRequest(row) {
4711
4727
  if (row.user_messages_json) {
4712
4728
  try {
4713
4729
  const parsed = JSON.parse(row.user_messages_json);
4714
- if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((item) => item && typeof item === "object" && typeof item.prompt === "string" && (item.attachments === undefined || Array.isArray(item.attachments) && item.attachments?.every((attachment) => typeof attachment === "string")))) {
4730
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((item) => item && typeof item === "object" && typeof item.prompt === "string" && (item.attachments === undefined || Array.isArray(item.attachments) && item.attachments?.every((attachment) => typeof attachment === "string")) && (item.actorUserId === undefined || typeof item.actorUserId === "string") && (item.actorLabel === undefined || typeof item.actorLabel === "string"))) {
4715
4731
  userMessages = parsed;
4716
4732
  }
4717
4733
  } catch {
@@ -4763,19 +4779,24 @@ function mergeRuntimeUserTurnRequest(input) {
4763
4779
  return db.transaction(() => {
4764
4780
  const rows = db.query("SELECT * FROM runtime_user_turn_requests WHERE topic_id = ? ORDER BY created_at ASC, rowid ASC").all(input.topicId);
4765
4781
  const previous = rows.map(rowToRequest);
4782
+ const omittedRequestIds = new Set([
4783
+ ...input.omitRequestIds ?? [],
4784
+ ...previous.filter((request) => Boolean(request.execution?.providerSessionId)).map((request) => request.requestId)
4785
+ ]);
4786
+ const carried = previous.filter((request) => !omittedRequestIds.has(request.requestId));
4766
4787
  const alreadyIncludedRequestIds = new Set(input.alreadyIncludedRequestIds ?? []);
4767
- const alreadyIncludedMessages = previous.filter((request) => alreadyIncludedRequestIds.has(request.requestId)).flatMap((request) => request.userMessages);
4788
+ const alreadyIncludedMessages = carried.filter((request) => alreadyIncludedRequestIds.has(request.requestId)).flatMap((request) => request.userMessages);
4768
4789
  const includedPrefixMatches = alreadyIncludedMessages.every((message, index) => {
4769
4790
  const candidate = input.userMessages[index];
4770
- return candidate?.prompt === message.prompt && JSON.stringify(candidate.attachments ?? []) === JSON.stringify(message.attachments ?? []);
4791
+ return candidate?.prompt === message.prompt && candidate.actorUserId === message.actorUserId && candidate.actorLabel === message.actorLabel && JSON.stringify(candidate.attachments ?? []) === JSON.stringify(message.attachments ?? []);
4771
4792
  });
4772
4793
  const alreadyIncludedMessageCount = includedPrefixMatches ? alreadyIncludedMessages.length : 0;
4773
4794
  const userMessages = [
4774
- ...previous.flatMap((request) => request.userMessages),
4795
+ ...carried.flatMap((request) => request.userMessages),
4775
4796
  ...input.userMessages.slice(alreadyIncludedMessageCount)
4776
4797
  ];
4777
4798
  const incomingLoggedCount = Math.min(Math.max(0, input.execution.loggedUserMessageCount ?? 0), input.userMessages.length);
4778
- const loggedUserMessageCount = previous.reduce((count, request) => count + loggedMessageCount(request), 0) + Math.max(0, incomingLoggedCount - alreadyIncludedMessageCount);
4799
+ const loggedUserMessageCount = carried.reduce((count, request) => count + loggedMessageCount(request), 0) + Math.max(0, incomingLoggedCount - alreadyIncludedMessageCount);
4779
4800
  const execution = {
4780
4801
  ...input.execution,
4781
4802
  loggedUserMessageCount,
@@ -4787,7 +4808,7 @@ function mergeRuntimeUserTurnRequest(input) {
4787
4808
  ],
4788
4809
  conversationPrompts: userMessages.slice(loggedUserMessageCount).map((message) => message.prompt)
4789
4810
  };
4790
- const sessionBase = previous.find((request) => request.execution?.sessionIdSpecified)?.execution;
4811
+ const sessionBase = carried.find((request) => request.execution?.sessionIdSpecified)?.execution;
4791
4812
  if (sessionBase?.sessionIdSpecified) {
4792
4813
  execution.sessionId = sessionBase.sessionId;
4793
4814
  execution.sessionIdSpecified = true;
@@ -4798,13 +4819,29 @@ function mergeRuntimeUserTurnRequest(input) {
4798
4819
  (request_id, topic_id, user_id, prompt, user_messages_json, attachments_json,
4799
4820
  allow_auto_continue, execution_json, topic_epoch, created_at,
4800
4821
  status, claimed_by, claimed_at, running_query_id)
4801
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NULL, NULL, NULL)`).run(input.requestId, input.topicId, input.userId, renderUserPromptBatch(userMessages.map((message) => message.prompt)), JSON.stringify(userMessages), attachments?.length ? JSON.stringify(attachments) : null, input.allowAutoContinue ? 1 : 0, JSON.stringify(execution), input.topicEpoch, now);
4822
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NULL, NULL, NULL)`).run(input.requestId, input.topicId, input.userId, renderUserTurnBatch(userMessages), JSON.stringify(userMessages), attachments?.length ? JSON.stringify(attachments) : null, input.allowAutoContinue ? 1 : 0, JSON.stringify(execution), input.topicEpoch, now);
4802
4823
  return {
4803
4824
  requestId: input.requestId,
4804
4825
  supersededRequestIds: previous.map((request) => request.requestId)
4805
4826
  };
4806
4827
  }).immediate();
4807
4828
  }
4829
+ function markRuntimeUserTurnProviderSessionObserved(topicId, requestId, sessionId) {
4830
+ return db.transaction(() => {
4831
+ const row = db.query("SELECT * FROM runtime_user_turn_requests WHERE topic_id = ? AND request_id = ?").get(topicId, requestId);
4832
+ if (!row)
4833
+ return false;
4834
+ const request = rowToRequest(row);
4835
+ const execution = {
4836
+ ...request.execution,
4837
+ providerSessionId: sessionId
4838
+ };
4839
+ const result = db.query(`UPDATE runtime_user_turn_requests
4840
+ SET execution_json = ?
4841
+ WHERE topic_id = ? AND request_id = ?`).run(JSON.stringify(execution), topicId, requestId);
4842
+ return result.changes === 1;
4843
+ }).immediate();
4844
+ }
4808
4845
  function markRuntimeUserTurnMessagesLogged(topicId, requestId, ownerId, loggedUserMessages) {
4809
4846
  if (loggedUserMessages.length === 0)
4810
4847
  return false;
@@ -4812,7 +4849,7 @@ function markRuntimeUserTurnMessagesLogged(topicId, requestId, ownerId, loggedUs
4812
4849
  const requests = db.query("SELECT * FROM runtime_user_turn_requests WHERE topic_id = ? ORDER BY created_at ASC, rowid ASC").all(topicId).map(rowToRequest);
4813
4850
  const hasLoggedPrefix = (request2) => loggedUserMessages.length <= request2.userMessages.length && loggedUserMessages.every((message, index) => {
4814
4851
  const candidate = request2.userMessages[index];
4815
- return candidate?.prompt === message.prompt && JSON.stringify(candidate.attachments ?? []) === JSON.stringify(message.attachments ?? []);
4852
+ return candidate?.prompt === message.prompt && candidate.actorUserId === message.actorUserId && candidate.actorLabel === message.actorLabel && JSON.stringify(candidate.attachments ?? []) === JSON.stringify(message.attachments ?? []);
4816
4853
  });
4817
4854
  const request = requests.find((candidate) => candidate.requestId === requestId && candidate.claimedBy === ownerId && hasLoggedPrefix(candidate)) ?? requests.find((candidate) => candidate.execution?.supersededRequestIds?.includes(requestId) && hasLoggedPrefix(candidate));
4818
4855
  if (!request)
@@ -7851,7 +7888,7 @@ async function* claudeProvider(opts) {
7851
7888
  }
7852
7889
  };
7853
7890
  }
7854
- const userId = opts.userId ?? "";
7891
+ const userId = opts.vaultUserId ?? opts.userId ?? "";
7855
7892
  const withVault = substituteClaudeToolInput(userId, tool_name, tool_input);
7856
7893
  if (JSON.stringify(withVault) === JSON.stringify(tool_input)) {
7857
7894
  return { continue: true };
@@ -7871,7 +7908,7 @@ async function* claudeProvider(opts) {
7871
7908
  hooks: [
7872
7909
  async (input) => {
7873
7910
  const { tool_response } = input;
7874
- const userId = opts.userId ?? "";
7911
+ const userId = opts.vaultUserId ?? opts.userId ?? "";
7875
7912
  const redacted = deepMapStrings2(tool_response, (value2) => redactHostedSecrets(userId, value2));
7876
7913
  if (JSON.stringify(redacted) === JSON.stringify(tool_response)) {
7877
7914
  return { continue: true };
@@ -9285,6 +9322,8 @@ async function* codexProvider(opts) {
9285
9322
  throw err;
9286
9323
  if (!currentSessionId || !isMissingCodexRolloutError(err))
9287
9324
  throw err;
9325
+ if (opts.userId && opts.session)
9326
+ throw err;
9288
9327
  logger.warn({ staleSessionId: currentSessionId, err: errMsg(err) }, "codexProvider: stale/missing rollout, restarting fresh thread");
9289
9328
  thread = codex.startThread(threadOptions);
9290
9329
  prompt = promptForThread(opts, true);
@@ -9584,7 +9623,7 @@ function maestroProvider(opts) {
9584
9623
  if (opts.agent !== undefined && opts.agent !== "maestro") {
9585
9624
  throw new Error(`maestroProvider: unexpected agent "${opts.agent}", expected "maestro"`);
9586
9625
  }
9587
- const userId = opts.userId ?? "";
9626
+ const userId = opts.vaultUserId ?? opts.userId ?? "";
9588
9627
  setMcpResolver(hostedMcpServers);
9589
9628
  const callerDisallowedTools = opts.disallowedTools ?? [];
9590
9629
  const callerToolHooks = opts.toolHooks ?? [];
@@ -9703,16 +9742,16 @@ async function resolveSessionFileMissing(agent, sessionId, cwd) {
9703
9742
  }
9704
9743
  async function maybeRebuildSession(opts) {
9705
9744
  if (!opts.sessionId)
9706
- return true;
9745
+ return "ready";
9707
9746
  try {
9708
9747
  const missing = await resolveSessionFileMissing(opts.agent, opts.sessionId, opts.cwd);
9709
9748
  if (!missing)
9710
- return true;
9749
+ return "ready";
9711
9750
  logger.info({ agent: opts.agent, sessionId: opts.sessionId, topic: opts.session }, "session file missing \u2014 rebuilding from unified log");
9712
9751
  const entries = readConversation(opts.userId, opts.session);
9713
9752
  if (entries.length === 0) {
9714
9753
  logger.info({ agent: opts.agent, sessionId: opts.sessionId }, "no unified log entries to rebuild \u2014 starting fresh session");
9715
- return false;
9754
+ return "fresh";
9716
9755
  }
9717
9756
  const operations = getRegistryOperations(opts.agent);
9718
9757
  const result = operations.writeRollout({
@@ -9723,19 +9762,25 @@ async function maybeRebuildSession(opts) {
9723
9762
  ...opts.effort ? { effort: opts.effort } : {}
9724
9763
  });
9725
9764
  logger.info({ agent: opts.agent, sessionId: result.sessionId, rolloutPath: result.rolloutPath }, "session rebuilt from unified log");
9726
- return true;
9765
+ return "ready";
9727
9766
  } catch (err) {
9728
- logger.warn({ err, agent: opts.agent, sessionId: opts.sessionId }, "session rebuild failed \u2014 starting fresh session");
9729
- return false;
9767
+ logger.warn({ err, agent: opts.agent, sessionId: opts.sessionId }, "session rebuild failed \u2014 preserving resume key");
9768
+ return "failed";
9730
9769
  }
9731
9770
  }
9732
9771
  async function* runAgent(opts) {
9733
9772
  const recording = shouldRecordTurn(opts);
9734
9773
  const dispatchOpts = { ...opts };
9735
9774
  if (hasSessionRepairContext(dispatchOpts)) {
9736
- const ok = await maybeRebuildSession(dispatchOpts);
9737
- if (!ok) {
9775
+ const repair = await maybeRebuildSession(dispatchOpts);
9776
+ if (repair === "fresh") {
9738
9777
  dispatchOpts.sessionId = undefined;
9778
+ } else if (repair === "failed") {
9779
+ yield {
9780
+ type: "error",
9781
+ content: "Provider session could not be rebuilt from conversation history; the existing session was preserved for retry."
9782
+ };
9783
+ return;
9739
9784
  }
9740
9785
  }
9741
9786
  const taskScope = resolveTaskEventScope(dispatchOpts);
@@ -13522,7 +13567,7 @@ function classifyAgentError(err, agent) {
13522
13567
  return `${name} error: ${snippet}`;
13523
13568
  }
13524
13569
  function isSessionExpiredError(message) {
13525
- return message.includes(SESSION_EXPIRED_MSG) || /session was recorded with model .+ but is resuming with/i.test(message) || /session.*not found|session.*expired|unknown conversation/i.test(message);
13570
+ return message.includes(SESSION_EXPIRED_MSG) || /session was recorded with model .+ but is resuming with/i.test(message) || /failed to resolve rollout path .+file does not exist/i.test(message) || /session.*not found|session.*expired|unknown conversation/i.test(message);
13526
13571
  }
13527
13572
  var SESSION_EXPIRED_MSG = "No conversation found with session ID";
13528
13573
  var init_errors = __esm(() => {
@@ -14715,6 +14760,14 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
14715
14760
  for await (const event of events) {
14716
14761
  if (abortController.signal.aborted)
14717
14762
  break;
14763
+ if (!control.providerTurnContentObserved && (event.type === "text_delta" || event.type === "text" || event.type === "reasoning" || event.type === "tool_use" || event.type === "tool_result" || event.type === "file" || event.type === "result")) {
14764
+ control.providerTurnContentObserved = true;
14765
+ if (control.sessionId) {
14766
+ for (const requestId of control.durableRequestIds ?? []) {
14767
+ markRuntimeUserTurnProviderSessionObserved(topicId, requestId, control.sessionId);
14768
+ }
14769
+ }
14770
+ }
14718
14771
  switch (event.type) {
14719
14772
  case "text_delta": {
14720
14773
  pendingSawDelta = true;
@@ -15021,6 +15074,13 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
15021
15074
  }
15022
15075
  break;
15023
15076
  case "session":
15077
+ control.sessionId = event.sessionId;
15078
+ control.providerSessionObserved = true;
15079
+ if (control.providerTurnContentObserved) {
15080
+ for (const requestId of control.durableRequestIds ?? []) {
15081
+ markRuntimeUserTurnProviderSessionObserved(topicId, requestId, event.sessionId);
15082
+ }
15083
+ }
15024
15084
  if (!silent && onSessionId && getRoomQuery(topicId)?.queryId === queryId) {
15025
15085
  try {
15026
15086
  if (control.injectParams)
@@ -15095,7 +15155,9 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
15095
15155
  if (forkHandle && outcome.kind !== "session-expired" && (!forkRequestId || !interSessionQueue.hasRequest(topicId, forkRequestId))) {
15096
15156
  cleanupAgentFork(forkHandle);
15097
15157
  }
15098
- if (roomId === topicId && outcome.kind !== "session-expired" && !getRoomQuery(topicId)) {
15158
+ const queuedUserTurn = roomId === topicId ? getRuntimeUserTurnRequest(topicId) : null;
15159
+ const hasReplacementUserTurn = queuedUserTurn !== null && queuedUserTurn.requestId !== queryId;
15160
+ if (roomId === topicId && outcome.kind !== "session-expired" && !getRoomQuery(topicId) && !hasReplacementUserTurn) {
15099
15161
  const next = takeDeferredInject(topicId);
15100
15162
  if (next)
15101
15163
  redispatchInject(next);
@@ -15127,6 +15189,7 @@ var init_turn_event_stream = __esm(async () => {
15127
15189
  init_visuals(),
15128
15190
  init_api_messages(),
15129
15191
  init_api_topics(),
15192
+ init_runtime_turn_requests(),
15130
15193
  init_token_stats()
15131
15194
  ]);
15132
15195
  });
@@ -15207,6 +15270,7 @@ __export(exports_turn_runner, {
15207
15270
  wasLocallyRequeuedAfterUserPreemption: () => wasLocallyRequeuedAfterUserPreemption,
15208
15271
  userConversationPromptsToRecord: () => userConversationPromptsToRecord,
15209
15272
  upsertTaskPanelMessage: () => upsertTaskPanelMessage,
15273
+ tryReconstructTopicRollout: () => tryReconstructTopicRollout,
15210
15274
  triggerTopicAiTurn: () => triggerTopicAiTurn,
15211
15275
  topicAllowsVisualFileId: () => topicAllowsVisualFileId,
15212
15276
  taskPanelMessageId: () => taskPanelMessageId,
@@ -15222,9 +15286,11 @@ __export(exports_turn_runner, {
15222
15286
  resolveVisualMediaInput: () => resolveVisualMediaInput,
15223
15287
  resolveTopicTurnSession: () => resolveTopicTurnSession,
15224
15288
  resolveTopicTurnExecution: () => resolveTopicTurnExecution,
15289
+ resolveSessionRetry: () => resolveSessionRetry,
15225
15290
  resolveModelForAgent: () => resolveModelForAgent,
15226
15291
  resolveInitialTurnSessionId: () => resolveInitialTurnSessionId,
15227
15292
  resolveCompactionExecution: () => resolveCompactionExecution,
15293
+ renderUserTurnBatch: () => renderUserTurnBatch,
15228
15294
  renderUserPromptBatch: () => renderUserPromptBatch,
15229
15295
  renderTaskPanel: () => renderTaskPanel,
15230
15296
  promptWithAttachments: () => promptWithAttachments,
@@ -15379,6 +15445,7 @@ function redispatchInject(inject) {
15379
15445
  aiMention: topic.aiMention
15380
15446
  },
15381
15447
  userId: inject.userId,
15448
+ vaultUserId: inject.vaultUserId,
15382
15449
  prompt: inject.prompt,
15383
15450
  allowAutoContinue: false,
15384
15451
  origin: inject.origin,
@@ -15504,12 +15571,10 @@ async function deliverAskError(queryId, sourceLabel, error) {
15504
15571
  }
15505
15572
  function tryReconstructTopicRollout(opts) {
15506
15573
  const { topicId, topicTitle, userId, agent, sessionId, cwd, model, effort } = opts;
15507
- if (!sessionId && !opts.allowFreshSession)
15508
- return null;
15509
15574
  try {
15510
15575
  const entries = readConversation(userId, topicTitle);
15511
15576
  if (entries.length === 0)
15512
- return null;
15577
+ return { kind: "no-history" };
15513
15578
  const result = getRegistryOperations(agent).writeRollout({
15514
15579
  cwd,
15515
15580
  entries,
@@ -15524,15 +15589,18 @@ function tryReconstructTopicRollout(opts) {
15524
15589
  rolloutPath: result.rolloutPath,
15525
15590
  entries: entries.length
15526
15591
  }, sessionId ? "ai: session expired \u2014 rollout reconstructed from unified log" : "ai: provider session bridged from shared conversation log");
15527
- return result.sessionId;
15592
+ return { kind: "rebuilt", sessionId: result.sessionId };
15528
15593
  } catch (err) {
15529
15594
  logger.warn({ err, topicId, agent, sessionId }, "ai: session reconstruct failed");
15530
- return null;
15595
+ return { kind: "failed", error: stringifyError(err) };
15531
15596
  }
15532
15597
  }
15533
- function resolveSessionRetryId(opts) {
15598
+ function resolveSessionRetry(opts) {
15534
15599
  const reconstructed = tryReconstructTopicRollout(opts);
15535
- if (reconstructed)
15600
+ if (reconstructed.kind === "rebuilt") {
15601
+ return { kind: "retry", sessionId: reconstructed.sessionId };
15602
+ }
15603
+ if (reconstructed.kind === "failed")
15536
15604
  return reconstructed;
15537
15605
  if (opts.onSessionReset)
15538
15606
  opts.onSessionReset();
@@ -15540,7 +15608,7 @@ function resolveSessionRetryId(opts) {
15540
15608
  clearTopicSessionId(opts.topicId, "session-expired");
15541
15609
  }
15542
15610
  logger.info({ topicId: opts.topicId, agent: opts.agent, hadSessionId: Boolean(opts.sessionId) }, "ai: session expired \u2014 retrying with fresh session");
15543
- return null;
15611
+ return { kind: "retry", sessionId: null };
15544
15612
  }
15545
15613
  function mergeSupersedingUserTurn(running, incoming) {
15546
15614
  const userMessages = [
@@ -15548,7 +15616,7 @@ function mergeSupersedingUserTurn(running, incoming) {
15548
15616
  ...incoming.userMessages ?? [legacyUserTurnEnvelope(incoming.prompt, incoming.attachments)]
15549
15617
  ];
15550
15618
  return {
15551
- prompt: renderUserPromptBatch(userMessages.map((message) => message.prompt)),
15619
+ prompt: renderUserTurnBatch(userMessages),
15552
15620
  userMessages,
15553
15621
  attachments: flattenUserTurnAttachments(userMessages),
15554
15622
  sessionId: running.sessionId
@@ -15575,11 +15643,12 @@ function serializableUserTurnExecution(params) {
15575
15643
  bridgeSessionFromHistory: params.bridgeSessionFromHistory,
15576
15644
  peerBridge: params.peerBridge,
15577
15645
  from: params.from,
15646
+ vaultUserId: params.vaultUserId,
15578
15647
  conversationPrompts: params._conversationPrompts ?? params._userMessages?.map((message) => message.prompt) ?? [params.prompt],
15579
15648
  loggedUserMessageCount: params._loggedUserMessageCount
15580
15649
  };
15581
15650
  }
15582
- function waitToStartRemoteUserTurn(params, queryId) {
15651
+ function waitToStartRemoteUserTurn(params, queryId, options) {
15583
15652
  const execution = serializableUserTurnExecution(params);
15584
15653
  const incomingUserMessages = params._userMessages ?? [
15585
15654
  legacyUserTurnEnvelope(params.prompt, params.attachments)
@@ -15592,7 +15661,8 @@ function waitToStartRemoteUserTurn(params, queryId) {
15592
15661
  requestId: queryId,
15593
15662
  execution,
15594
15663
  topicEpoch: execution.runtimeEpoch ?? getRuntimeTopicEpoch(params.topic.id),
15595
- alreadyIncludedRequestIds: params._durableRequestIds
15664
+ alreadyIncludedRequestIds: params._durableRequestIds,
15665
+ omitRequestIds: options?.omitRequestIds
15596
15666
  });
15597
15667
  for (const supersededRequestId of merged.supersededRequestIds) {
15598
15668
  if (supersededRequestId !== merged.requestId) {
@@ -15643,6 +15713,7 @@ async function drainOneDurableUserTurn() {
15643
15713
  const queryId = startAiTurn({
15644
15714
  topic,
15645
15715
  userId: request.userId,
15716
+ vaultUserId: execution?.vaultUserId,
15646
15717
  prompt: request.prompt,
15647
15718
  _userMessages: request.userMessages,
15648
15719
  _conversationPrompts: execution?.conversationPrompts ?? [request.prompt],
@@ -15713,12 +15784,13 @@ function startAiTurn(params) {
15713
15784
  }
15714
15785
  const topic = storedTopic;
15715
15786
  const { userId, allowAutoContinue, onDispatched } = params;
15787
+ const vaultUserId = params.vaultUserId ?? userId;
15716
15788
  const origin = params.origin ?? "user";
15717
15789
  const conversationPrompts = params._conversationPrompts ?? [params.prompt];
15718
15790
  let loggedUserMessageCount = params._loggedUserMessageCount;
15719
15791
  let durableRequestIds = params._durableRequestIds ?? [];
15720
15792
  let userMessages = isUserOrigin(origin) ? params._userMessages ?? [legacyUserTurnEnvelope(params.prompt, params.attachments)] : undefined;
15721
- let prompt = userMessages ? renderUserPromptBatch(userMessages.map((message) => message.prompt)) : params.prompt;
15793
+ let prompt = userMessages ? renderUserTurnBatch(userMessages) : params.prompt;
15722
15794
  let attachments2 = userMessages ? flattenUserTurnAttachments(userMessages) : params.attachments;
15723
15795
  const execution = resolveTopicTurnExecution(topic, params);
15724
15796
  const sessionResolution = resolveTopicTurnSession(topic, params.sessionId, {
@@ -15783,6 +15855,7 @@ function startAiTurn(params) {
15783
15855
  topicId,
15784
15856
  runtimeEpoch,
15785
15857
  userId,
15858
+ vaultUserId,
15786
15859
  prompt,
15787
15860
  origin,
15788
15861
  sourceNode,
@@ -15867,16 +15940,22 @@ function startAiTurn(params) {
15867
15940
  }
15868
15941
  if (decision.action === "abort-replace") {
15869
15942
  const running = decision.running;
15943
+ let omitRequestIds;
15870
15944
  if (isUserOrigin(running.origin)) {
15871
- const merged = mergeSupersedingUserTurn(running, { prompt, userMessages, attachments: attachments2 });
15872
- userMessages = merged.userMessages;
15873
- prompt = merged.prompt;
15874
- attachments2 = merged.attachments;
15875
- sessionId = merged.sessionId;
15876
- loggedUserMessageCount = running.userMessages?.length ?? 1;
15877
- durableRequestIds = [
15878
- ...new Set([...running.durableRequestIds ?? [], ...durableRequestIds])
15879
- ];
15945
+ if (running.providerTurnContentObserved && running.sessionId) {
15946
+ sessionId = running.sessionId;
15947
+ omitRequestIds = running.durableRequestIds;
15948
+ } else {
15949
+ const merged = mergeSupersedingUserTurn(running, { prompt, userMessages, attachments: attachments2 });
15950
+ userMessages = merged.userMessages;
15951
+ prompt = merged.prompt;
15952
+ attachments2 = merged.attachments;
15953
+ sessionId = merged.sessionId;
15954
+ loggedUserMessageCount = running.userMessages?.length ?? 1;
15955
+ durableRequestIds = [
15956
+ ...new Set([...running.durableRequestIds ?? [], ...durableRequestIds])
15957
+ ];
15958
+ }
15880
15959
  }
15881
15960
  if (running.injectParams) {
15882
15961
  const runningInject = running.injectParams;
@@ -15897,6 +15976,20 @@ function startAiTurn(params) {
15897
15976
  running.abortReason = "internal" /* Internal */;
15898
15977
  running.abortController.abort();
15899
15978
  logger.info({ topicId, supersededQueryId: running.queryId, supersededOrigin: running.origin }, "ai: new user message superseded running turn");
15979
+ const queuedQueryId = waitToStartRemoteUserTurn({
15980
+ ...params,
15981
+ topic,
15982
+ prompt,
15983
+ attachments: attachments2,
15984
+ sessionId,
15985
+ _userMessages: userMessages,
15986
+ _conversationPrompts: conversationPrompts,
15987
+ _loggedUserMessageCount: loggedUserMessageCount,
15988
+ _durableRequestIds: durableRequestIds,
15989
+ _runtimeEpoch: runtimeEpoch
15990
+ }, queryId, { omitRequestIds });
15991
+ announceQueuedUserTurn({ ...params, topic }, queuedQueryId);
15992
+ return queuedQueryId;
15900
15993
  }
15901
15994
  const abortController = new AbortController;
15902
15995
  const control = {
@@ -15915,6 +16008,7 @@ function startAiTurn(params) {
15915
16008
  topicId,
15916
16009
  runtimeEpoch,
15917
16010
  userId,
16011
+ vaultUserId,
15918
16012
  prompt,
15919
16013
  origin,
15920
16014
  requestId,
@@ -15988,8 +16082,9 @@ function startAiTurn(params) {
15988
16082
  const resolvedEffort = execution.effort;
15989
16083
  const workspaceCwd = cwd ?? workspaceCwdFor(topicId);
15990
16084
  mkdirSync20(workspaceCwd, { recursive: true });
16085
+ let historyBridgeError;
15991
16086
  if (bridgeSessionFromHistory && !sessionId) {
15992
- const bridgedSessionId = tryReconstructTopicRollout({
16087
+ const bridge = tryReconstructTopicRollout({
15993
16088
  topicId,
15994
16089
  topicTitle: sessionName,
15995
16090
  userId,
@@ -15997,25 +16092,32 @@ function startAiTurn(params) {
15997
16092
  sessionId: null,
15998
16093
  cwd: workspaceCwd,
15999
16094
  model: resolvedModel,
16000
- ...resolvedEffort ? { effort: resolvedEffort } : {},
16001
- allowFreshSession: true
16095
+ ...resolvedEffort ? { effort: resolvedEffort } : {}
16002
16096
  });
16003
- if (bridgedSessionId) {
16004
- sessionId = bridgedSessionId;
16005
- control.sessionId = bridgedSessionId;
16097
+ if (bridge.kind === "rebuilt") {
16098
+ sessionId = bridge.sessionId;
16099
+ control.sessionId = bridge.sessionId;
16006
16100
  if (control.injectParams)
16007
- control.injectParams.sessionId = bridgedSessionId;
16101
+ control.injectParams.sessionId = bridge.sessionId;
16008
16102
  try {
16009
- onSessionId?.(bridgedSessionId);
16103
+ onSessionId?.(bridge.sessionId);
16010
16104
  } catch (err) {
16011
16105
  logger.warn({ err, topicId, queryId, agent: agentKind }, "ai: isolated session owner rejected bridged provider session id");
16012
16106
  }
16107
+ } else if (bridge.kind === "failed") {
16108
+ historyBridgeError = "Conversation history could not be encoded into a provider session; no fresh session was started.";
16013
16109
  }
16014
16110
  }
16015
16111
  const messageAttachmentGroups = userMessages?.map((message, index) => materializePromptAttachments(topicId, `${queryId}-${index}`, message.attachments));
16016
16112
  const promptAttachments = messageAttachmentGroups?.flat() ?? materializePromptAttachments(topicId, queryId, attachments2);
16017
- const promptWithFiles = userMessages ? renderUserPromptBatch(userMessages.map((message, index) => promptWithAttachments(message.prompt, messageAttachmentGroups?.[index] ?? []))) : promptWithAttachments(prompt, promptAttachments);
16018
- const renderedUserPrompts = userMessages ? userMessages.map((message, index) => promptWithAttachments(message.prompt, messageAttachmentGroups?.[index] ?? [])) : undefined;
16113
+ const promptWithFiles = userMessages ? renderUserTurnBatch(userMessages.map((message, index) => ({
16114
+ ...message,
16115
+ prompt: promptWithAttachments(message.prompt, messageAttachmentGroups?.[index] ?? [])
16116
+ }))) : promptWithAttachments(prompt, promptAttachments);
16117
+ const renderedUserPrompts = userMessages ? userMessages.map((message, index) => renderUserTurnPrompt({
16118
+ ...message,
16119
+ prompt: promptWithAttachments(message.prompt, messageAttachmentGroups?.[index] ?? [])
16120
+ })) : undefined;
16019
16121
  const agentPrompt = topic.aiMode === "mention" && isUserOrigin(origin) && !silent ? buildMentionOnlyChannelPrompt({
16020
16122
  topicId,
16021
16123
  userId,
@@ -16131,6 +16233,10 @@ function startAiTurn(params) {
16131
16233
  const turnMcpEnabled = browserProfileOwner ? enabledMcp : enabledMcp.filter((name) => name !== "playwright");
16132
16234
  const wantsBgBash = !isManager && enabledMcp.includes("background-bash");
16133
16235
  async function* runWithPlaywright() {
16236
+ if (historyBridgeError) {
16237
+ yield { type: "error", content: historyBridgeError };
16238
+ return;
16239
+ }
16134
16240
  if (prepareSession && !sessionId) {
16135
16241
  if (abortController.signal.aborted)
16136
16242
  return;
@@ -16203,6 +16309,7 @@ ${playwrightNote}`;
16203
16309
  systemPrompt: effectiveSystemPrompt,
16204
16310
  sessionId,
16205
16311
  userId,
16312
+ vaultUserId,
16206
16313
  session: sessionName,
16207
16314
  sessionType: sessionType ?? (isManager ? "manager" : "forum"),
16208
16315
  abortController,
@@ -16236,11 +16343,10 @@ ${playwrightNote}`;
16236
16343
  WsHub.get().broadcastTyping(topicId, "ai");
16237
16344
  WsHub.get().broadcastAiActive(topicId, queryId);
16238
16345
  }
16239
- streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, { silent, peerBridge, sourceNode }).then(async (outcome) => {
16346
+ streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, { silent, peerBridge, sourceNode }).then(async (streamOutcome) => {
16347
+ let outcome = streamOutcome;
16240
16348
  if (outcome.kind === "session-expired") {
16241
- if (!silent)
16242
- WsHub.get().broadcastAborted(topicId, queryId, "stopped");
16243
- const retrySessionId = resolveSessionRetryId({
16349
+ const retry = resolveSessionRetry({
16244
16350
  topicId,
16245
16351
  topicTitle: sessionName,
16246
16352
  userId,
@@ -16253,55 +16359,66 @@ ${playwrightNote}`;
16253
16359
  externalSessionOwner: Boolean(onSessionId),
16254
16360
  onSessionReset
16255
16361
  });
16256
- logger.info({
16257
- topicId,
16258
- prevQueryId: queryId,
16259
- agent: agentKind,
16260
- retrySessionId: retrySessionId ? retrySessionId.slice(0, 8) : null
16261
- }, "ai: retrying query after session expiry");
16262
- if (!retrySessionId && prepareSession && forkHandle) {
16263
- cleanupAgentFork(forkHandle);
16264
- forkHandle = undefined;
16362
+ if (retry.kind === "failed") {
16363
+ outcome = {
16364
+ kind: "provider-error",
16365
+ error: "Provider session expired and conversation history could not be rebuilt; the existing session was preserved for retry."
16366
+ };
16367
+ } else {
16368
+ if (!silent)
16369
+ WsHub.get().broadcastAborted(topicId, queryId, "stopped");
16370
+ const retrySessionId = retry.sessionId;
16371
+ logger.info({
16372
+ topicId,
16373
+ prevQueryId: queryId,
16374
+ agent: agentKind,
16375
+ retrySessionId: retrySessionId ? retrySessionId.slice(0, 8) : null
16376
+ }, "ai: retrying query after session expiry");
16377
+ if (!retrySessionId && prepareSession && forkHandle) {
16378
+ cleanupAgentFork(forkHandle);
16379
+ forkHandle = undefined;
16380
+ }
16381
+ startAiTurn({
16382
+ topic,
16383
+ userId,
16384
+ vaultUserId,
16385
+ prompt,
16386
+ _userMessages: userMessages,
16387
+ _conversationPrompts: conversationPrompts,
16388
+ _loggedUserMessageCount: loggedUserMessageCount,
16389
+ _durableRequestIds: durableRequestIds,
16390
+ attachments: attachments2,
16391
+ allowAutoContinue,
16392
+ origin,
16393
+ onDispatched,
16394
+ requestId,
16395
+ depth,
16396
+ silent,
16397
+ contextId,
16398
+ agentOverride,
16399
+ modelOverride,
16400
+ effortOverride,
16401
+ sessionId: retrySessionId,
16402
+ sessionScope,
16403
+ turnConcurrency,
16404
+ forkHandle,
16405
+ prepareSession,
16406
+ cwd,
16407
+ sessionName,
16408
+ sessionType,
16409
+ visualTools,
16410
+ fileDeliveryTools,
16411
+ onSessionId,
16412
+ onSessionReset,
16413
+ bridgeSessionFromHistory,
16414
+ onSettled,
16415
+ peerBridge,
16416
+ askReplySources,
16417
+ _runtimeEpoch: runtimeEpoch,
16418
+ _sessionRetried: true
16419
+ });
16420
+ return;
16265
16421
  }
16266
- startAiTurn({
16267
- topic,
16268
- userId,
16269
- prompt,
16270
- _userMessages: userMessages,
16271
- _conversationPrompts: conversationPrompts,
16272
- _loggedUserMessageCount: loggedUserMessageCount,
16273
- _durableRequestIds: durableRequestIds,
16274
- attachments: attachments2,
16275
- allowAutoContinue,
16276
- origin,
16277
- onDispatched,
16278
- requestId,
16279
- depth,
16280
- silent,
16281
- contextId,
16282
- agentOverride,
16283
- modelOverride,
16284
- effortOverride,
16285
- sessionId: retrySessionId,
16286
- sessionScope,
16287
- turnConcurrency,
16288
- forkHandle,
16289
- prepareSession,
16290
- cwd,
16291
- sessionName,
16292
- sessionType,
16293
- visualTools,
16294
- fileDeliveryTools,
16295
- onSessionId,
16296
- onSessionReset,
16297
- bridgeSessionFromHistory,
16298
- onSettled,
16299
- peerBridge,
16300
- askReplySources,
16301
- _runtimeEpoch: runtimeEpoch,
16302
- _sessionRetried: true
16303
- });
16304
- return;
16305
16422
  }
16306
16423
  if (outcome.kind === "provider-error") {
16307
16424
  if (!silent) {
@@ -18080,19 +18197,17 @@ function wikiLastConversation(args) {
18080
18197
  }
18081
18198
  const name = slugify(topicNameFrom(rawTopic));
18082
18199
  const archiveDir = resolve2(runtime().archiveDir, name);
18083
- ensureDir(archiveDir);
18084
18200
  try {
18085
- let files = readdirSync(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
18201
+ let files = existsSync2(archiveDir) ? readdirSync(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
18086
18202
  if (files.length === 0) {
18087
- ensureDir(runtime().archiveDir);
18088
- files = readdirSync(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse();
18203
+ files = existsSync2(runtime().archiveDir) ? readdirSync(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
18089
18204
  }
18090
18205
  if (files.length === 0) {
18091
18206
  return {
18092
18207
  content: [{ type: "text", text: `No archive found for topic: ${rawTopic}` }]
18093
18208
  };
18094
18209
  }
18095
- const actualPath = files[0].includes("/") ? files[0] : resolve2(readdirSync(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
18210
+ const actualPath = files[0].includes("/") ? files[0] : resolve2(existsSync2(archiveDir) && readdirSync(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
18096
18211
  try {
18097
18212
  const raw = readFileSync2(actualPath, "utf-8");
18098
18213
  const lines = raw.trim().split(`
@@ -19538,4 +19653,4 @@ export {
19538
19653
  createAgentHealthMcpServer
19539
19654
  };
19540
19655
 
19541
- //# debugId=558B090AEC9CCF7B64756E2164756E21
19656
+ //# debugId=09A6616F9F0036C964756E2164756E21