negotium 0.6.13 → 0.6.17

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 (31) hide show
  1. package/dist/agent-helpers.js +760 -142
  2. package/dist/agent-helpers.js.map +13 -12
  3. package/dist/{chunk-sr48tc2c.js → chunk-ax6tqx0s.js} +2 -2
  4. package/dist/{chunk-sr48tc2c.js.map → chunk-ax6tqx0s.js.map} +2 -2
  5. package/dist/hosted-agent.js +64 -3
  6. package/dist/hosted-agent.js.map +4 -4
  7. package/dist/main.js +783 -297
  8. package/dist/main.js.map +18 -17
  9. package/dist/mcp-factories.js +850 -261
  10. package/dist/mcp-factories.js.map +13 -12
  11. package/dist/registry.js +1 -1
  12. package/dist/runtime/cron/background-sessions.ts +5 -2
  13. package/dist/runtime/src/agents/archiver.ts +8 -5
  14. package/dist/runtime/src/agents/claude-provider.ts +88 -1
  15. package/dist/runtime/src/agents/idle-compact.ts +178 -0
  16. package/dist/runtime/src/node-host.ts +8 -1
  17. package/dist/runtime/src/runtime/background-sessions.ts +21 -8
  18. package/dist/runtime/src/runtime/turn-event-stream.ts +54 -1
  19. package/dist/runtime/src/runtime/turn-runner.ts +63 -0
  20. package/dist/runtime/src/topics/lifecycle.ts +2 -0
  21. package/dist/runtime/src/topics/session.ts +60 -3
  22. package/dist/runtime/src/version.ts +1 -1
  23. package/dist/types/packages/core/src/agents/archiver.d.ts +2 -2
  24. package/dist/types/packages/core/src/agents/idle-compact.d.ts +28 -0
  25. package/dist/types/packages/core/src/runtime/background-sessions.d.ts +10 -2
  26. package/dist/types/packages/core/src/runtime/turn-event-stream.d.ts +14 -0
  27. package/dist/types/packages/core/src/runtime/turn-runner.d.ts +2 -0
  28. package/dist/types/packages/core/src/topics/session.d.ts +11 -0
  29. package/dist/types/packages/core/src/version.d.ts +1 -1
  30. package/dist/types/packages/module-cron/src/background-sessions.d.ts +1 -1
  31. package/package.json +1 -1
@@ -3001,7 +3001,7 @@ var init_codex = __esm(async () => {
3001
3001
  });
3002
3002
 
3003
3003
  // ../../packages/core/src/version.ts
3004
- var NEGOTIUM_VERSION = "0.6.13";
3004
+ var NEGOTIUM_VERSION = "0.6.17";
3005
3005
 
3006
3006
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
3007
3007
  import { spawn as spawn2 } from "child_process";
@@ -3759,6 +3759,16 @@ function appendConversationEventStrict(userId, topicName, agent, event) {
3759
3759
  }
3760
3760
  }
3761
3761
  }
3762
+ function appendRawConversationEventStrict(userId, topicName, agent, event) {
3763
+ const path = getConversationPath(userId, topicName);
3764
+ const entry = {
3765
+ ts: new Date().toISOString(),
3766
+ agent,
3767
+ event
3768
+ };
3769
+ mkdirSync9(dirname8(path), { recursive: true });
3770
+ appendJsonlLine(path, JSON.stringify(entry));
3771
+ }
3762
3772
  function readConversationPath(path) {
3763
3773
  const out = [];
3764
3774
  if (!existsSync9(path))
@@ -4027,6 +4037,35 @@ function buildClaudePrompt(opts) {
4027
4037
  parent_tool_use_id: null
4028
4038
  });
4029
4039
  }
4040
+ function watchClaudeProcessExit(signal) {
4041
+ let listener;
4042
+ const exited = new Promise((resolve7) => {
4043
+ listener = resolve7;
4044
+ const listeners = claudeProcessExitListeners.get(signal) ?? new Set;
4045
+ listeners.add(listener);
4046
+ claudeProcessExitListeners.set(signal, listeners);
4047
+ });
4048
+ return {
4049
+ exited,
4050
+ dispose: () => {
4051
+ if (!listener)
4052
+ return;
4053
+ const listeners = claudeProcessExitListeners.get(signal);
4054
+ listeners?.delete(listener);
4055
+ if (listeners?.size === 0)
4056
+ claudeProcessExitListeners.delete(signal);
4057
+ listener = undefined;
4058
+ }
4059
+ };
4060
+ }
4061
+ function notifyClaudeProcessExit(signal, exit) {
4062
+ const listeners = claudeProcessExitListeners.get(signal);
4063
+ if (!listeners)
4064
+ return;
4065
+ claudeProcessExitListeners.delete(signal);
4066
+ for (const listener of listeners)
4067
+ listener(exit);
4068
+ }
4030
4069
  function signalProcessTree(pid, signal) {
4031
4070
  try {
4032
4071
  process.kill(-pid, signal);
@@ -4088,6 +4127,7 @@ function spawnClaudeCodeProcessWithTreeKill(options) {
4088
4127
  clearKillTimer();
4089
4128
  options.signal.removeEventListener("abort", onAbort);
4090
4129
  logger.debug({ pid: child.pid, code, signal }, "Claude Code process exited");
4130
+ notifyClaudeProcessExit(options.signal, { code, signal });
4091
4131
  });
4092
4132
  child.once("error", (err) => {
4093
4133
  exited = true;
@@ -4098,6 +4138,7 @@ function spawnClaudeCodeProcessWithTreeKill(options) {
4098
4138
  command: options.command,
4099
4139
  err: err instanceof Error ? err.message : String(err)
4100
4140
  }, "Claude Code process error event");
4141
+ notifyClaudeProcessExit(options.signal, { code: null, signal: null });
4101
4142
  });
4102
4143
  return {
4103
4144
  stdin: child.stdin,
@@ -4145,6 +4186,20 @@ async function* claudeProvider(opts) {
4145
4186
  delete cleanEnv.CLAUDECODE;
4146
4187
  cleanEnv.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT ??= "300000";
4147
4188
  cleanEnv.CLAUDE_CODE_DISABLE_WORKFLOWS = "1";
4189
+ const sdkAbortController = new AbortController;
4190
+ const onCallerAbort = () => sdkAbortController.abort();
4191
+ if (opts.abortController?.signal.aborted)
4192
+ sdkAbortController.abort();
4193
+ else
4194
+ opts.abortController?.signal.addEventListener("abort", onCallerAbort, { once: true });
4195
+ const processExitWatch = watchClaudeProcessExit(sdkAbortController.signal);
4196
+ let unexpectedProcessExit;
4197
+ processExitWatch.exited.then((exit) => {
4198
+ if (opts.abortController?.signal.aborted)
4199
+ return;
4200
+ unexpectedProcessExit = exit;
4201
+ sdkAbortController.abort();
4202
+ });
4148
4203
  const queryOptions = {
4149
4204
  ...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
4150
4205
  spawnClaudeCodeProcess: spawnClaudeCodeProcessWithTreeKill,
@@ -4155,7 +4210,7 @@ async function* claudeProvider(opts) {
4155
4210
  env: cleanEnv,
4156
4211
  mcpServers: hostedMcpServers(opts),
4157
4212
  ...claudeBuiltInTools(opts) ? { tools: claudeBuiltInTools(opts) } : {},
4158
- abortController: opts.abortController,
4213
+ abortController: sdkAbortController,
4159
4214
  disallowedTools: buildClaudeDisallowedTools(opts.disallowedTools),
4160
4215
  ...opts.model ? { model: opts.model } : {},
4161
4216
  ...opts.maxBudgetUsd ? { maxBudgetUsd: opts.maxBudgetUsd } : {},
@@ -4393,14 +4448,28 @@ async function* claudeProvider(opts) {
4393
4448
  }
4394
4449
  }
4395
4450
  }
4451
+ if (unexpectedProcessExit) {
4452
+ const detail = unexpectedProcessExit.signal ? `signal ${unexpectedProcessExit.signal}` : unexpectedProcessExit.code === null ? "before it could start" : `exit code ${unexpectedProcessExit.code}`;
4453
+ logger.error({ detail }, "claudeProvider: CLI exited before terminal SDK event");
4454
+ yield { type: "error", content: `Claude CLI exited unexpectedly (${detail}).` };
4455
+ }
4396
4456
  } catch (e) {
4457
+ if (unexpectedProcessExit) {
4458
+ const detail = unexpectedProcessExit.signal ? `signal ${unexpectedProcessExit.signal}` : unexpectedProcessExit.code === null ? "before it could start" : `exit code ${unexpectedProcessExit.code}`;
4459
+ logger.error({ err: e, detail }, "claudeProvider: CLI exited before terminal SDK event");
4460
+ yield { type: "error", content: `Claude CLI exited unexpectedly (${detail}).` };
4461
+ return;
4462
+ }
4397
4463
  if (isAbortError(e) || opts.abortController?.signal.aborted)
4398
4464
  return;
4399
4465
  logger.error({ err: e }, "claudeProvider: SDK iteration failed");
4400
4466
  yield { type: "error", content: errMsg(e) };
4467
+ } finally {
4468
+ processExitWatch.dispose();
4469
+ opts.abortController?.signal.removeEventListener("abort", onCallerAbort);
4401
4470
  }
4402
4471
  }
4403
- var CLAUDE_DEFAULT_DISALLOWED_TOOLS, CLAUDE_NATIVE_AGENT_TOOLS, CLAUDE_IMAGE_MAX_BYTES, CLAUDE_IMAGE_MIME_TYPES, CLAUDE_ABORT_SIGKILL_DELAY_MS = 2500;
4472
+ var CLAUDE_DEFAULT_DISALLOWED_TOOLS, CLAUDE_NATIVE_AGENT_TOOLS, CLAUDE_IMAGE_MAX_BYTES, CLAUDE_IMAGE_MIME_TYPES, CLAUDE_ABORT_SIGKILL_DELAY_MS = 2500, claudeProcessExitListeners;
4404
4473
  var init_claude_provider = __esm(async () => {
4405
4474
  init_claude_registry();
4406
4475
  await init_execution_host();
@@ -4415,6 +4484,7 @@ var init_claude_provider = __esm(async () => {
4415
4484
  "TaskUpdate",
4416
4485
  "TaskList",
4417
4486
  "TaskGet",
4487
+ "Monitor",
4418
4488
  "ScheduleWakeup",
4419
4489
  "CronCreate",
4420
4490
  "CronList",
@@ -4428,6 +4498,7 @@ var init_claude_provider = __esm(async () => {
4428
4498
  "image/gif",
4429
4499
  "image/webp"
4430
4500
  ]);
4501
+ claudeProcessExitListeners = new WeakMap;
4431
4502
  });
4432
4503
 
4433
4504
  // ../../packages/core/src/agents/codex-tree-manager.ts
@@ -8351,7 +8422,7 @@ function createArchiverRuntime(host) {
8351
8422
  }
8352
8423
  }
8353
8424
  };
8354
- const listSessions = (userId) => {
8425
+ const listSessions = (userId, allUsers = false) => {
8355
8426
  const now = host.config.now().getTime();
8356
8427
  for (const [id, session] of activeSessions) {
8357
8428
  if (session.expiresAt !== undefined && session.expiresAt <= now) {
@@ -8361,7 +8432,7 @@ function createArchiverRuntime(host) {
8361
8432
  activeSessions.delete(id);
8362
8433
  }
8363
8434
  }
8364
- return [...activeSessions.values()].filter((session) => session.userId === userId).map(({ userId: _userId, expiresAt: _expiresAt, expiryTimer: _expiryTimer, ...session }) => ({
8435
+ return [...activeSessions.values()].filter((session) => allUsers || session.userId === userId).map(({ userId: _userId, expiresAt: _expiresAt, expiryTimer: _expiryTimer, ...session }) => ({
8365
8436
  ...session,
8366
8437
  steps: [...session.steps]
8367
8438
  }));
@@ -12052,8 +12123,270 @@ RULES:
12052
12123
  - Do NOT invent file paths or facts not present in the transcript.
12053
12124
  - Keep the entire summary under 1500 words.`, CHARS_PER_TEXT_TOKEN = 3.5, CHARS_PER_CJK_TOKEN = 0.9;
12054
12125
 
12126
+ // ../../packages/core/src/storage/token-stats.ts
12127
+ import { createHash as createHash5 } from "crypto";
12128
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "fs";
12129
+ import { join as join21 } from "path";
12130
+ function emptyBucket() {
12131
+ return {
12132
+ inputTokens: 0,
12133
+ outputTokens: 0,
12134
+ cacheCreationInputTokens: 0,
12135
+ cacheReadInputTokens: 0,
12136
+ queries: 0,
12137
+ estimatedCostUsd: 0
12138
+ };
12139
+ }
12140
+ function tokenStatsFileId(userId) {
12141
+ const rawUserId = String(userId);
12142
+ return /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash5("sha256").update(rawUserId).digest("hex")}`;
12143
+ }
12144
+ function queriesPath(userId) {
12145
+ const fileId = tokenStatsFileId(userId);
12146
+ const logDir = resolveStorageLogDir();
12147
+ mkdirSync12(logDir, { recursive: true });
12148
+ return join21(logDir, `token-queries-${fileId}.jsonl`);
12149
+ }
12150
+ function loadRecords(userId) {
12151
+ try {
12152
+ return readJsonlLines(queriesPath(userId)).flatMap((line) => {
12153
+ try {
12154
+ return [JSON.parse(line)];
12155
+ } catch {
12156
+ return [];
12157
+ }
12158
+ });
12159
+ } catch {
12160
+ return [];
12161
+ }
12162
+ }
12163
+ function estimateUsageCost(agent, model, usage) {
12164
+ const prices = TOKEN_PRICES[`${agent}:${model}`];
12165
+ if (!prices)
12166
+ return 0;
12167
+ return (usage.inputTokens * prices.input + usage.outputTokens * prices.output + (agent === "claude" ? usage.cacheCreationInputTokens * (prices.cacheWrite ?? prices.input) : 0) + usage.cacheReadInputTokens * prices.cacheRead) / 1e6;
12168
+ }
12169
+ function isQueryRecord(value) {
12170
+ if (!value || typeof value !== "object")
12171
+ return false;
12172
+ const record = value;
12173
+ return record.schemaVersion === 2 && typeof record.timestamp === "string" && typeof record.session === "string" && typeof record.topicId === "string" && typeof record.agent === "string" && typeof record.model === "string" && typeof record.inputTokens === "number" && typeof record.outputTokens === "number" && typeof record.cacheCreationInputTokens === "number" && typeof record.cacheReadInputTokens === "number" && typeof record.estimatedCostUsd === "number";
12174
+ }
12175
+ function recordUsage(userId, session, usage, context) {
12176
+ const cacheReadInputTokens = usage.cacheReadInputTokens ?? 0;
12177
+ const inputTokens = context.agent === "claude" ? usage.inputTokens : Math.max(0, usage.inputTokens - cacheReadInputTokens);
12178
+ const normalized = {
12179
+ inputTokens,
12180
+ outputTokens: usage.outputTokens,
12181
+ cacheCreationInputTokens: usage.cacheCreationInputTokens ?? 0,
12182
+ cacheReadInputTokens
12183
+ };
12184
+ const record = {
12185
+ schemaVersion: 2,
12186
+ timestamp: new Date().toISOString(),
12187
+ session,
12188
+ topicId: context.topicId,
12189
+ ...context.providerSessionId ? { providerSessionId: context.providerSessionId } : {},
12190
+ agent: context.agent,
12191
+ model: context.model,
12192
+ ...normalized,
12193
+ ...usage.contextTokens !== undefined ? { contextTokens: usage.contextTokens } : {},
12194
+ ...usage.contextWindow !== undefined ? { contextWindow: usage.contextWindow } : {},
12195
+ estimatedCostUsd: usage.costUsd ?? estimateUsageCost(context.agent, context.model, normalized)
12196
+ };
12197
+ try {
12198
+ appendJsonlEntry(queriesPath(userId), record);
12199
+ } catch (e) {
12200
+ logger.warn({ err: e, userId }, "token-stats: Failed to record");
12201
+ }
12202
+ }
12203
+ function deleteTopicStats(userId, topicId) {
12204
+ const path = queriesPath(userId);
12205
+ try {
12206
+ const kept = readJsonlLines(path).filter((line) => {
12207
+ try {
12208
+ const record = JSON.parse(line);
12209
+ return record.topicId !== topicId;
12210
+ } catch {
12211
+ return true;
12212
+ }
12213
+ });
12214
+ writeFileSync10(path, kept.length > 0 ? `${kept.join(`
12215
+ `)}
12216
+ ` : "", "utf-8");
12217
+ } catch (e) {
12218
+ if (e.code === "ENOENT")
12219
+ return;
12220
+ logger.warn({ err: e, userId, topicId }, "token-stats: Failed to delete topic stats");
12221
+ }
12222
+ }
12223
+ function getTopicStats(userId, topicId, activeProviderSessionId = getTopicSessionId(topicId) ?? undefined) {
12224
+ const total = emptyBucket();
12225
+ let currentSession;
12226
+ for (const raw of loadRecords(userId)) {
12227
+ if (!isQueryRecord(raw) || raw.topicId !== topicId)
12228
+ continue;
12229
+ total.inputTokens += raw.inputTokens;
12230
+ total.outputTokens += raw.outputTokens;
12231
+ total.cacheCreationInputTokens += raw.cacheCreationInputTokens;
12232
+ total.cacheReadInputTokens += raw.cacheReadInputTokens;
12233
+ total.queries += 1;
12234
+ total.estimatedCostUsd += raw.estimatedCostUsd;
12235
+ if (raw.contextTokens !== undefined && raw.contextWindow !== undefined && raw.contextWindow > 0 && activeProviderSessionId !== undefined && raw.providerSessionId === activeProviderSessionId && (!currentSession || raw.timestamp > currentSession.timestamp)) {
12236
+ currentSession = {
12237
+ timestamp: raw.timestamp,
12238
+ topicId: raw.topicId,
12239
+ topicTitle: raw.session,
12240
+ ...raw.providerSessionId ? { providerSessionId: raw.providerSessionId } : {},
12241
+ agent: raw.agent,
12242
+ model: raw.model,
12243
+ contextTokens: raw.contextTokens,
12244
+ contextWindow: raw.contextWindow
12245
+ };
12246
+ }
12247
+ }
12248
+ return { topicId, ...total, ...currentSession ? { currentSession } : {} };
12249
+ }
12250
+ var TOKEN_PRICES;
12251
+ var init_token_stats = __esm(async () => {
12252
+ init_jsonl();
12253
+ init_logger();
12254
+ await init_api_topics();
12255
+ await init_storage_host();
12256
+ TOKEN_PRICES = {
12257
+ "codex:gpt-5.6-sol": { input: 5, cacheRead: 0.5, output: 30 },
12258
+ "codex:gpt-5.6-terra": { input: 2.5, cacheRead: 0.25, output: 15 },
12259
+ "codex:gpt-5.6-luna": { input: 1, cacheRead: 0.1, output: 6 },
12260
+ "claude:fable": { input: 10, cacheWrite: 12.5, cacheRead: 1, output: 50 },
12261
+ "claude:opus": { input: 5, cacheWrite: 6.25, cacheRead: 0.5, output: 25 },
12262
+ "claude:sonnet": { input: 2, cacheWrite: 2.5, cacheRead: 0.2, output: 10 },
12263
+ "maestro:kimi-k3": { input: 3, cacheRead: 0.3, output: 15 },
12264
+ "maestro:kimi-k2.7-code": { input: 0.95, cacheRead: 0.19, output: 4 },
12265
+ "maestro:deepseek-pro": { input: 0.435, cacheRead: 0.003625, output: 0.87 },
12266
+ "maestro:deepseek-flash": { input: 0.14, cacheRead: 0.0028, output: 0.28 }
12267
+ };
12268
+ });
12269
+
12270
+ // ../../packages/core/src/agents/idle-compact.ts
12271
+ function cancelIdleCompactForTopic(topicId) {
12272
+ const timer = timers2.get(topicId);
12273
+ if (!timer)
12274
+ return false;
12275
+ clearTimeout(timer);
12276
+ timers2.delete(topicId);
12277
+ return true;
12278
+ }
12279
+ function envFlagEnabled2(name, fallback) {
12280
+ const raw = process.env[name]?.trim().toLowerCase();
12281
+ if (!raw)
12282
+ return fallback;
12283
+ return !["0", "false", "off", "no"].includes(raw);
12284
+ }
12285
+ function envPositiveInt2(name, fallback) {
12286
+ const value = Number.parseInt(process.env[name] ?? "", 10);
12287
+ return Number.isFinite(value) && value > 0 ? value : fallback;
12288
+ }
12289
+ function idleCompactDelayMs() {
12290
+ return envPositiveInt2("NEGOTIUM_IDLE_COMPACT_DELAY_MS", DEFAULT_IDLE_DELAY_MS2);
12291
+ }
12292
+ function idleCompactMinContextPercent() {
12293
+ return envPositiveInt2("NEGOTIUM_IDLE_COMPACT_MIN_CONTEXT_PERCENT", DEFAULT_MIN_CONTEXT_PERCENT);
12294
+ }
12295
+ function idleCompactEnabled() {
12296
+ return envFlagEnabled2("NEGOTIUM_IDLE_COMPACT_ENABLED", true);
12297
+ }
12298
+ function scheduleIdleCompactForTopic(topicId, userId) {
12299
+ if (!idleCompactEnabled())
12300
+ return "disabled";
12301
+ const topic = getTopic(topicId);
12302
+ if (!topic)
12303
+ return "topic-not-found";
12304
+ if (!topic.agent)
12305
+ return "not-ai-invited";
12306
+ if (topic.aiMode === "mention")
12307
+ return "mention-only-channel";
12308
+ const existing = timers2.get(topicId);
12309
+ if (existing)
12310
+ clearTimeout(existing);
12311
+ const timer = setTimeout(() => {
12312
+ timers2.delete(topicId);
12313
+ runIdleCompactForTopic(topicId, userId);
12314
+ }, idleCompactDelayMs());
12315
+ timer.unref?.();
12316
+ timers2.set(topicId, timer);
12317
+ return "scheduled";
12318
+ }
12319
+ async function runIdleCompactForTopic(topicId, userId, options = {}) {
12320
+ if (!idleCompactEnabled())
12321
+ return "disabled";
12322
+ const busy = options.isBusy ? options.isBusy(topicId) : Boolean(getRoomQuery(topicId) || getRuntimeTurnLease(topicId));
12323
+ if (busy) {
12324
+ if (options.onBusy)
12325
+ options.onBusy(topicId, userId);
12326
+ else
12327
+ scheduleIdleCompactForTopic(topicId, userId);
12328
+ return "busy";
12329
+ }
12330
+ const topic = getTopic(topicId);
12331
+ if (!topic)
12332
+ return "topic-not-found";
12333
+ if (!topic.agent)
12334
+ return "not-ai-invited";
12335
+ if (topic.aiMode === "mention")
12336
+ return "mention-only-channel";
12337
+ const owner = topic.participants.find((participant) => participant.role === "owner")?.userId;
12338
+ if (!owner)
12339
+ return "no-owner";
12340
+ const stats = (options.getStats ?? getTopicStats)(owner, topicId);
12341
+ const currentSession = stats.currentSession;
12342
+ if (!currentSession || currentSession.contextWindow <= 0) {
12343
+ logger.debug({ topicId }, "idle-compact: no provider-reported context usage yet, skipping");
12344
+ return "below-threshold";
12345
+ }
12346
+ const percent = currentSession.contextTokens / currentSession.contextWindow * 100;
12347
+ const minPercent = options.minContextPercent ?? idleCompactMinContextPercent();
12348
+ if (percent < minPercent) {
12349
+ logger.debug({ topicId, percent: Math.round(percent), minPercent }, "idle-compact: skipped below context-usage threshold");
12350
+ return "below-threshold";
12351
+ }
12352
+ const compact = options.compact ?? (async (id, actorId, reason) => {
12353
+ const { compactTopicSession } = await init_session().then(() => exports_session);
12354
+ return compactTopicSession(id, actorId, reason, { preemptive: false });
12355
+ });
12356
+ try {
12357
+ const result = await compact(topicId, owner, "idle-compact");
12358
+ if (result.busy) {
12359
+ logger.debug({ topicId, percent: Math.round(percent) }, "idle-compact: topic became busy, rescheduling");
12360
+ if (options.onBusy)
12361
+ options.onBusy(topicId, owner);
12362
+ else
12363
+ scheduleIdleCompactForTopic(topicId, owner);
12364
+ return "busy";
12365
+ }
12366
+ if (result.isError) {
12367
+ logger.warn({ topicId, percent: Math.round(percent), text: result.text }, "idle-compact: failed");
12368
+ return "failed";
12369
+ }
12370
+ logger.info({ topicId, percent: Math.round(percent) }, "idle-compact: compacted an idle topic's context");
12371
+ return "compacted";
12372
+ } catch (error) {
12373
+ logger.warn({ err: error, topicId, percent: Math.round(percent) }, "idle-compact: unexpected failure while compacting an idle topic");
12374
+ return "failed";
12375
+ }
12376
+ }
12377
+ var DEFAULT_IDLE_DELAY_MS2, DEFAULT_MIN_CONTEXT_PERCENT = 50, timers2;
12378
+ var init_idle_compact = __esm(async () => {
12379
+ init_logger();
12380
+ await init_active_rooms();
12381
+ await init_api_topics();
12382
+ await init_runtime_leases();
12383
+ await init_token_stats();
12384
+ DEFAULT_IDLE_DELAY_MS2 = 6 * 60 * 60 * 1000;
12385
+ timers2 = new Map;
12386
+ });
12387
+
12055
12388
  // ../../packages/core/src/agents/topic-cleanup.ts
12056
- import { mkdirSync as mkdirSync12, renameSync as renameSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync10 } from "fs";
12389
+ import { mkdirSync as mkdirSync13, renameSync as renameSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync11 } from "fs";
12057
12390
  import { dirname as dirname13 } from "path";
12058
12391
  function collectSessionIdsByAgent(entries, extraSessions = []) {
12059
12392
  const out = new Map;
@@ -12120,8 +12453,8 @@ function createTopicLogMaintenance(host) {
12120
12453
  const path = runtimeHost.activeConversationPath(opts.userId, opts.topicName);
12121
12454
  const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
12122
12455
  try {
12123
- mkdirSync12(dirname13(path), { recursive: true });
12124
- writeFileSync10(tempPath, retained.length > 0 ? `${retained.map((entry) => JSON.stringify(entry)).join(`
12456
+ mkdirSync13(dirname13(path), { recursive: true });
12457
+ writeFileSync11(tempPath, retained.length > 0 ? `${retained.map((entry) => JSON.stringify(entry)).join(`
12125
12458
  `)}
12126
12459
  ` : "", { flag: "wx" });
12127
12460
  renameSync6(tempPath, path);
@@ -12572,10 +12905,149 @@ var init_runtime_turn_requests = __esm(async () => {
12572
12905
  });
12573
12906
 
12574
12907
  // ../../packages/core/src/topics/session.ts
12908
+ var exports_session = {};
12909
+ __export(exports_session, {
12910
+ shouldUseCompactionLog: () => shouldUseCompactionLog,
12911
+ shouldCompactForkEntries: () => shouldCompactForkEntries,
12912
+ restartTopicSession: () => restartTopicSession,
12913
+ createCompactedRolloutEntries: () => createCompactedRolloutEntries,
12914
+ compactTopicSession: () => compactTopicSession,
12915
+ AUTO_FORK_COMPACTION_TOKENS: () => AUTO_FORK_COMPACTION_TOKENS
12916
+ });
12575
12917
  import { randomUUID as randomUUID11 } from "crypto";
12576
- import { mkdtempSync as mkdtempSync2, rmSync as rmSync4, writeFileSync as writeFileSync11 } from "fs";
12918
+ import { mkdtempSync as mkdtempSync2, rmSync as rmSync4, writeFileSync as writeFileSync12 } from "fs";
12577
12919
  import { tmpdir as tmpdir4 } from "os";
12578
- import { join as join21 } from "path";
12920
+ import { join as join22 } from "path";
12921
+ async function waitForMemoryArchive(settled, timeoutMs) {
12922
+ let timer;
12923
+ try {
12924
+ return await Promise.race([
12925
+ settled.then(() => true),
12926
+ new Promise((resolve16) => {
12927
+ timer = setTimeout(() => resolve16(false), timeoutMs);
12928
+ timer.unref?.();
12929
+ })
12930
+ ]);
12931
+ } finally {
12932
+ if (timer)
12933
+ clearTimeout(timer);
12934
+ }
12935
+ }
12936
+ async function fenceTopicWork(topicId, maintenance) {
12937
+ for (const queryId of cancelRuntimeUserTurnRequestsBeforeEpoch(topicId, maintenance.epoch)) {
12938
+ WsHub.get().broadcastAborted(topicId, queryId, "stopped");
12939
+ }
12940
+ interSessionQueue.drop(topicId);
12941
+ const abortedLocal = abortRoom(topicId);
12942
+ const abortedRemote = requestRuntimeTurnAbort(topicId, "external");
12943
+ if (abortedLocal || abortedRemote || getRuntimeTurnLease(topicId)) {
12944
+ const deadline = Date.now() + RESET_TURN_WAIT_MS;
12945
+ while ((getRoomQuery(topicId) || getRuntimeTurnLease(topicId)) && Date.now() < deadline) {
12946
+ await delay(50);
12947
+ }
12948
+ if (getRoomQuery(topicId) || getRuntimeTurnLease(topicId)) {
12949
+ return "The active turn did not stop in time. Try again.";
12950
+ }
12951
+ }
12952
+ return maintenance.isOwned() ? null : "Topic maintenance ownership was lost. Try again.";
12953
+ }
12954
+ function hasTopicWorkInFlight(topicId) {
12955
+ return Boolean(getRoomQuery(topicId) || getRuntimeTurnLease(topicId) || getRuntimeUserTurnRequest(topicId));
12956
+ }
12957
+ function topicIsQuiescedForNonPreemptiveWork(topicId) {
12958
+ return !hasTopicWorkInFlight(topicId);
12959
+ }
12960
+ async function restartTopicSession(topicId, userId, reason = "topic-session-restart", options = {}) {
12961
+ const topic = getTopic(topicId);
12962
+ if (!topic)
12963
+ return { text: "Topic not found.", isError: true };
12964
+ if (isLegacySharedGeneral(topic.id)) {
12965
+ return { text: "The legacy shared General session cannot be reset.", isError: true };
12966
+ }
12967
+ const owner = topic.participants.some((participant) => participant.userId === userId && participant.role === "owner");
12968
+ if (!owner)
12969
+ return { text: "Only the topic owner can reset the session.", isError: true };
12970
+ const maintenance = beginRuntimeTopicMaintenance(topicId);
12971
+ if (!maintenance)
12972
+ return { text: "Topic maintenance is already in progress.", isError: true };
12973
+ try {
12974
+ const fenceError = await fenceTopicWork(topicId, maintenance);
12975
+ if (fenceError)
12976
+ return { text: fenceError, isError: true };
12977
+ cancelIdleArchiveForTopic(topicId);
12978
+ cancelIdleCompactForTopic(topicId);
12979
+ const rawArchivePaths = [];
12980
+ try {
12981
+ for (const participantUserId of new Set([
12982
+ userId,
12983
+ ...topic.participants.map((participant) => participant.userId)
12984
+ ])) {
12985
+ const archived = archiveConversationEvents(topicId, topic.title, participantUserId, {
12986
+ reason: "reset"
12987
+ });
12988
+ if (archived)
12989
+ rawArchivePaths.push(archived.path);
12990
+ }
12991
+ } catch (error) {
12992
+ return {
12993
+ text: `Session reset could not archive the raw conversation: ${error instanceof Error ? error.message : String(error)}`,
12994
+ isError: true
12995
+ };
12996
+ }
12997
+ let settleMemoryArchive;
12998
+ const memoryArchiveSettled = new Promise((resolve16) => {
12999
+ settleMemoryArchive = resolve16;
13000
+ });
13001
+ const archiveStatus = (options.archiveMemory ?? archiveActiveTopicForMemory)(topicId, options.memoryUserId ?? userId, {
13002
+ reason: "reset",
13003
+ minMessages: 1,
13004
+ minExchanges: MIN_MEMORY_ARCHIVE_EXCHANGES,
13005
+ allowMentionOnly: true,
13006
+ skipBusyCheck: true,
13007
+ rawArchivePaths,
13008
+ onSettled: () => settleMemoryArchive?.()
13009
+ });
13010
+ if (archiveStatus === "archived") {
13011
+ const archiveFinished = await waitForMemoryArchive(memoryArchiveSettled, options.memoryArchiveWaitMs ?? RESET_MEMORY_ARCHIVE_WAIT_MS);
13012
+ if (!archiveFinished) {
13013
+ return {
13014
+ text: "Memory archiving did not finish in time. The session was not reset.",
13015
+ isError: true
13016
+ };
13017
+ }
13018
+ }
13019
+ if (!maintenance.isOwned()) {
13020
+ return { text: "Topic maintenance ownership was lost. Try again.", isError: true };
13021
+ }
13022
+ const sessionId = getTopicSessionId(topicId);
13023
+ const purgeLogs = options.purgeLogs ?? purgeTopicLogs;
13024
+ const participantUserIds = Array.from(new Set([userId, ...topic.participants.map((participant) => participant.userId)]));
13025
+ for (const [index, participantUserId] of participantUserIds.entries()) {
13026
+ let purged = false;
13027
+ try {
13028
+ purged = await purgeLogs({
13029
+ userId: participantUserId,
13030
+ topicName: topic.title,
13031
+ cwd: resolveTopicWorkspaceDir(topicId),
13032
+ extraSessions: index === 0 && topic.agent && sessionId ? [{ agent: topic.agent, sessionId }] : []
13033
+ });
13034
+ } catch (error) {
13035
+ logger.warn({ err: error, topicId, userId: participantUserId }, "restartTopicSession: participant context cleanup failed");
13036
+ }
13037
+ if (!purged) {
13038
+ return {
13039
+ text: "Session reset could not remove all provider context. The current session was kept.",
13040
+ isError: true
13041
+ };
13042
+ }
13043
+ }
13044
+ clearTopicSessionId(topicId, reason);
13045
+ clearQueryUsageAlert(userId, topicId);
13046
+ return { text: `Session reset for "${topic.title}". The next message starts fresh.` };
13047
+ } finally {
13048
+ maintenance.finish();
13049
+ }
13050
+ }
12579
13051
  function previousCompactedSummary(entries) {
12580
13052
  for (let index = entries.length - 2;index >= 0; index -= 1) {
12581
13053
  const request = entries[index]?.event;
@@ -12705,7 +13177,7 @@ function formatCompactElapsed(startedAt) {
12705
13177
  async function summarizeTopicContext(request) {
12706
13178
  const startedAt = Date.now();
12707
13179
  const sessionIds = [];
12708
- const compactCwd = mkdtempSync2(join21(tmpdir4(), "negotium-compact-"));
13180
+ const compactCwd = mkdtempSync2(join22(tmpdir4(), "negotium-compact-"));
12709
13181
  const abortController = new AbortController;
12710
13182
  const relayAbort = () => abortController.abort(request.signal?.reason);
12711
13183
  if (request.signal?.aborted)
@@ -12732,7 +13204,7 @@ async function summarizeTopicContext(request) {
12732
13204
  let error = "";
12733
13205
  let toolViolation = false;
12734
13206
  let compactionLogCalls = 0;
12735
- const compactionLogPath = join21(compactCwd, "conversation.log");
13207
+ const compactionLogPath = join22(compactCwd, "conversation.log");
12736
13208
  try {
12737
13209
  const compactionMcp = useCompactionLog ? {
12738
13210
  compact_log: {
@@ -12746,7 +13218,7 @@ async function summarizeTopicContext(request) {
12746
13218
  }
12747
13219
  } : undefined;
12748
13220
  if (useCompactionLog)
12749
- writeFileSync11(compactionLogPath, request.source, { mode: 384 });
13221
+ writeFileSync12(compactionLogPath, request.source, { mode: 384 });
12750
13222
  for await (const event of runAgent({
12751
13223
  agent: request.agent,
12752
13224
  prompt: useCompactionLog ? [
@@ -12949,9 +13421,165 @@ async function createCompactedRolloutEntries(request, summarize = summarizeTopic
12949
13421
  throw new Error("Context compaction returned an empty summary.");
12950
13422
  return compactEntries(request.agent, summary.slice(0, COMPACTION_OUTPUT_CHARS));
12951
13423
  }
12952
- var RESET_MEMORY_ARCHIVE_WAIT_MS, COMPACTION_INLINE_CHARS = 1e5, COMPACTION_SOURCE_CHARS, COMPACTION_MEMORY_CHARS = 80000, COMPACTION_OUTPUT_CHARS = 30000, COMPACTION_TIMEOUT_MS, COMPACTION_LOG_TIMEOUT_MS, COMPACTION_LOG_MAX_CALLS = 12, COMPACTION_LOG_MAX_TOTAL_BYTES, COMPACTION_LOG_MAX_CHUNK_BYTES, COMPACT_CONTEXT_MARKER = "[Negotium compacted context]", AUTO_FORK_COMPACTION_TOKENS = 28000;
13424
+ async function cleanupNewRollout(agent, cwd, sessionId) {
13425
+ try {
13426
+ await getRegistryOperations(agent).cleanupRollouts({ cwd, sessionIds: [sessionId] });
13427
+ } catch (error) {
13428
+ logger.warn({ err: error, agent, sessionId }, "compact: replacement rollout cleanup failed");
13429
+ }
13430
+ }
13431
+ async function compactTopicSession(topicId, userId, reason = "topic-session-compact", options = {}) {
13432
+ const topic = getTopic(topicId);
13433
+ if (!topic)
13434
+ return { text: "Topic not found.", isError: true };
13435
+ const owner = topic.participants.some((participant) => participant.userId === userId && participant.role === "owner");
13436
+ if (!owner)
13437
+ return { text: "Only the topic owner can compact the session.", isError: true };
13438
+ const preemptive = options.preemptive ?? true;
13439
+ if (!preemptive && hasTopicWorkInFlight(topicId)) {
13440
+ return { text: "A turn is active or queued; compaction skipped.", isError: true, busy: true };
13441
+ }
13442
+ const maintenance = beginRuntimeTopicMaintenance(topicId);
13443
+ if (!maintenance)
13444
+ return { text: "Topic maintenance is already in progress.", isError: true };
13445
+ try {
13446
+ if (preemptive) {
13447
+ const fenceError = await fenceTopicWork(topicId, maintenance);
13448
+ if (fenceError)
13449
+ return { text: fenceError, isError: true };
13450
+ } else if (!topicIsQuiescedForNonPreemptiveWork(topicId)) {
13451
+ return {
13452
+ text: "A turn is active or queued; compaction skipped.",
13453
+ isError: true,
13454
+ busy: true
13455
+ };
13456
+ } else if (!maintenance.isOwned()) {
13457
+ return { text: "Topic maintenance ownership was lost. Try again.", isError: true };
13458
+ }
13459
+ cancelIdleCompactForTopic(topicId);
13460
+ const agent = topic.agent ?? "maestro";
13461
+ const registry = getRegistry(agent);
13462
+ const config = getApiTopicConfig(topicId);
13463
+ const model = resolveModelForAgent(agent, config?.model ?? topic.defaultModel, registry);
13464
+ const requestedEffort = config?.effort ?? topic.defaultEffort;
13465
+ const effort = requestedEffort && registry.validateEffort(requestedEffort) ? requestedEffort : registry.defaultEffort;
13466
+ const compactionExecution = resolveCompactionExecution(agent, registry);
13467
+ const cwd = resolveTopicWorkspaceDir(topicId);
13468
+ const oldEntries = readConversation(userId, topic.title);
13469
+ let compactEntries2;
13470
+ try {
13471
+ compactEntries2 = await createCompactedRolloutEntries({
13472
+ topicId,
13473
+ topicTitle: topic.title,
13474
+ userId,
13475
+ entries: oldEntries,
13476
+ agent,
13477
+ model,
13478
+ ...effort ? { effort } : {},
13479
+ summaryModel: compactionExecution.model,
13480
+ ...compactionExecution.effort ? { summaryEffort: compactionExecution.effort } : {},
13481
+ cwd
13482
+ }, options.summarize);
13483
+ } catch (error) {
13484
+ return {
13485
+ text: `Context compaction failed: ${error instanceof Error ? error.message : String(error)}`,
13486
+ isError: true
13487
+ };
13488
+ }
13489
+ if (!maintenance.isOwned()) {
13490
+ return { text: "Topic maintenance ownership was lost. Try again.", isError: true };
13491
+ }
13492
+ const now = new Date().toISOString();
13493
+ let replacement;
13494
+ try {
13495
+ replacement = getRegistryOperations(agent).writeRollout({
13496
+ cwd,
13497
+ entries: compactEntries2,
13498
+ model,
13499
+ ...effort ? { effort } : {}
13500
+ });
13501
+ } catch (error) {
13502
+ return {
13503
+ text: `Context compaction failed to create a replacement session: ${error instanceof Error ? error.message : String(error)}`,
13504
+ isError: true
13505
+ };
13506
+ }
13507
+ const replacementSessionEntry = {
13508
+ ts: now,
13509
+ agent,
13510
+ event: { type: "session", sessionId: replacement.sessionId }
13511
+ };
13512
+ if (!maintenance.isOwned()) {
13513
+ await cleanupNewRollout(agent, cwd, replacement.sessionId);
13514
+ return { text: "Topic maintenance ownership was lost. Try again.", isError: true };
13515
+ }
13516
+ const previousSessionId = getTopicSessionId(topicId);
13517
+ const priorSessionEntries = oldEntries.filter((entry) => entry.event.type === "session");
13518
+ if (previousSessionId && topic.agent && !priorSessionEntries.some((entry) => entry.agent === topic.agent && entry.event.type === "session" && entry.event.sessionId === previousSessionId)) {
13519
+ priorSessionEntries.push({
13520
+ ts: now,
13521
+ agent: topic.agent,
13522
+ event: { type: "session", sessionId: previousSessionId }
13523
+ });
13524
+ }
13525
+ try {
13526
+ replaceConversationStrict(userId, topic.title, [
13527
+ ...compactEntries2,
13528
+ ...priorSessionEntries,
13529
+ replacementSessionEntry
13530
+ ]);
13531
+ setTopicSessionId(topicId, replacement.sessionId, { reason, agent });
13532
+ appendRawConversationEventStrict(userId, topic.title, agent, replacementSessionEntry.event);
13533
+ } catch (error) {
13534
+ try {
13535
+ replaceConversationStrict(userId, topic.title, oldEntries);
13536
+ if (previousSessionId) {
13537
+ setTopicSessionId(topicId, previousSessionId, {
13538
+ reason: `${reason}-rollback`,
13539
+ agent
13540
+ });
13541
+ } else {
13542
+ clearTopicSessionId(topicId, `${reason}-rollback`);
13543
+ }
13544
+ } catch (rollbackError) {
13545
+ logger.error({ err: rollbackError, topicId, replacementSessionId: replacement.sessionId }, "compact: failed to restore prior session after commit error");
13546
+ }
13547
+ await cleanupNewRollout(agent, cwd, replacement.sessionId);
13548
+ return {
13549
+ text: `Context compaction could not commit the replacement session: ${error instanceof Error ? error.message : String(error)}`,
13550
+ isError: true
13551
+ };
13552
+ }
13553
+ const oldRolloutsRemoved = await (options.cleanupOldRollouts ?? cleanupTopicRolloutsFromEntries)({
13554
+ userId,
13555
+ topicName: topic.title,
13556
+ cwd,
13557
+ extraSessions: previousSessionId && topic.agent ? [{ agent: topic.agent, sessionId: previousSessionId }] : []
13558
+ }, oldEntries);
13559
+ if (!oldRolloutsRemoved) {
13560
+ logger.warn({ topicId, previousSessionId, replacementSessionId: replacement.sessionId }, "compact: replacement committed; old rollout cleanup deferred");
13561
+ } else {
13562
+ try {
13563
+ replaceConversationStrict(userId, topic.title, [
13564
+ ...compactEntries2,
13565
+ replacementSessionEntry
13566
+ ]);
13567
+ } catch (manifestError) {
13568
+ logger.warn({ err: manifestError, topicId, replacementSessionId: replacement.sessionId }, "compact: old rollout cleanup succeeded but pending manifest compaction failed");
13569
+ }
13570
+ }
13571
+ clearQueryUsageAlert(userId, topicId);
13572
+ return {
13573
+ text: `Compacted context for "${topic.title}". Visible conversation history was preserved.`
13574
+ };
13575
+ } finally {
13576
+ maintenance.finish();
13577
+ }
13578
+ }
13579
+ var RESET_TURN_WAIT_MS = 5000, RESET_MEMORY_ARCHIVE_WAIT_MS, COMPACTION_INLINE_CHARS = 1e5, COMPACTION_SOURCE_CHARS, COMPACTION_MEMORY_CHARS = 80000, COMPACTION_OUTPUT_CHARS = 30000, COMPACTION_TIMEOUT_MS, COMPACTION_LOG_TIMEOUT_MS, COMPACTION_LOG_MAX_CALLS = 12, COMPACTION_LOG_MAX_TOTAL_BYTES, COMPACTION_LOG_MAX_CHUNK_BYTES, COMPACT_CONTEXT_MARKER = "[Negotium compacted context]", AUTO_FORK_COMPACTION_TOKENS = 28000;
12953
13580
  var init_session = __esm(async () => {
12954
13581
  await init_idle_archiver();
13582
+ await init_idle_compact();
12955
13583
  await init_agents();
12956
13584
  init_model_catalog();
12957
13585
  await init_registry();
@@ -12994,8 +13622,8 @@ __export(exports_derive, {
12994
13622
  TopicForkCompactionError: () => TopicForkCompactionError,
12995
13623
  TopicDeriveBusyError: () => TopicDeriveBusyError
12996
13624
  });
12997
- import { createHash as createHash5, randomUUID as randomUUID12 } from "crypto";
12998
- import { mkdirSync as mkdirSync13, rmSync as rmSync5, unlinkSync as unlinkSync13 } from "fs";
13625
+ import { createHash as createHash6, randomUUID as randomUUID12 } from "crypto";
13626
+ import { mkdirSync as mkdirSync14, rmSync as rmSync5, unlinkSync as unlinkSync13 } from "fs";
12999
13627
  function getTopics(opts = {}) {
13000
13628
  return listTopics(opts).filter((topic) => !isLegacySharedGeneral(topic.id));
13001
13629
  }
@@ -13060,7 +13688,7 @@ function captureForkSnapshot(sourceTopicId, userId, topicTitle) {
13060
13688
  maxRowid: messageRows.at(-1)?.rowid ?? 0
13061
13689
  },
13062
13690
  active: isTopicRunning(sourceTopicId),
13063
- canonicalDigest: createHash5("sha256").update(entries.map((entry) => JSON.stringify(entry)).join(`
13691
+ canonicalDigest: createHash6("sha256").update(entries.map((entry) => JSON.stringify(entry)).join(`
13064
13692
  `)).digest("hex")
13065
13693
  };
13066
13694
  }
@@ -13128,7 +13756,7 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
13128
13756
  try {
13129
13757
  if (agent) {
13130
13758
  const cwd = derivedWorkspace;
13131
- mkdirSync13(cwd, { recursive: true });
13759
+ mkdirSync14(cwd, { recursive: true });
13132
13760
  const registry = getRegistry(agent);
13133
13761
  const requestedRolloutModel = subagentModel ?? (subagent?.agent ? undefined : sourceConfig?.model) ?? derived.defaultModel;
13134
13762
  const rolloutModel = resolveModelForAgent(agent, requestedRolloutModel, registry);
@@ -13351,14 +13979,14 @@ var init_derive = __esm(async () => {
13351
13979
  });
13352
13980
 
13353
13981
  // ../../packages/core/src/query/session-inbox-path.ts
13354
- import { join as join22 } from "path";
13982
+ import { join as join23 } from "path";
13355
13983
  function sessionInboxPath(userId, topicId) {
13356
13984
  const key = Buffer.from(topicId, "utf8").toString("base64url");
13357
- return join22(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${JSONL_SUFFIX}`);
13985
+ return join23(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${JSONL_SUFFIX}`);
13358
13986
  }
13359
13987
  function scheduledSessionInboxPath(userId, topicId) {
13360
13988
  const key = Buffer.from(topicId, "utf8").toString("base64url");
13361
- return join22(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${SCHEDULE_SUFFIX}`);
13989
+ return join23(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${SCHEDULE_SUFFIX}`);
13362
13990
  }
13363
13991
  var TOPIC_ID_FILE_PREFIX = "topic-id-", JSONL_SUFFIX = ".jsonl", SCHEDULE_SUFFIX = ".schedule";
13364
13992
  var init_session_inbox_path = __esm(() => {
@@ -13366,12 +13994,12 @@ var init_session_inbox_path = __esm(() => {
13366
13994
  });
13367
13995
 
13368
13996
  // ../../packages/core/src/storage/session-inbox-signal.ts
13369
- import { join as join23 } from "path";
13997
+ import { join as join24 } from "path";
13370
13998
  var SESSION_INBOX_WAKE_FILE, listeners;
13371
13999
  var init_session_inbox_signal = __esm(() => {
13372
14000
  init_config();
13373
14001
  init_logger();
13374
- SESSION_INBOX_WAKE_FILE = join23(SESSION_INBOX_DIR, ".wake");
14002
+ SESSION_INBOX_WAKE_FILE = join24(SESSION_INBOX_DIR, ".wake");
13375
14003
  listeners = new Set;
13376
14004
  });
13377
14005
 
@@ -13407,13 +14035,13 @@ var init_session_inbox = __esm(async () => {
13407
14035
 
13408
14036
  // ../../packages/core/src/query/session-inbox-cleanup.ts
13409
14037
  import { unlinkSync as unlinkSync14 } from "fs";
13410
- import { basename as basename3, join as join24 } from "path";
14038
+ import { basename as basename3, join as join25 } from "path";
13411
14039
  function cleanupSessionInboxFiles(userId, topicId, legacyTopicTitle) {
13412
14040
  const live = sessionInboxPath(userId, topicId);
13413
14041
  const scheduled = scheduledSessionInboxPath(userId, topicId);
13414
14042
  const candidates = new Set([live, `${live}.processing`, scheduled, `${scheduled}.processing`]);
13415
14043
  if (legacyTopicTitle && legacyTopicTitle !== "." && legacyTopicTitle !== ".." && basename3(legacyTopicTitle) === legacyTopicTitle) {
13416
- const legacyBase = join24(SESSION_INBOX_DIR, userId, legacyTopicTitle);
14044
+ const legacyBase = join25(SESSION_INBOX_DIR, userId, legacyTopicTitle);
13417
14045
  for (const suffix of [".jsonl", ".jsonl.processing", ".schedule", ".schedule.processing"]) {
13418
14046
  candidates.add(`${legacyBase}${suffix}`);
13419
14047
  }
@@ -13439,28 +14067,28 @@ var init_session_inbox_cleanup = __esm(async () => {
13439
14067
  });
13440
14068
 
13441
14069
  // ../../packages/core/src/query/state.ts
13442
- import { mkdirSync as mkdirSync14, renameSync as renameSync7, unlinkSync as unlinkSync15, writeFileSync as writeFileSync12 } from "fs";
13443
- import { basename as basename4, join as join25 } from "path";
14070
+ import { mkdirSync as mkdirSync15, renameSync as renameSync7, unlinkSync as unlinkSync15, writeFileSync as writeFileSync13 } from "fs";
14071
+ import { basename as basename4, join as join26 } from "path";
13444
14072
  function createQueryStateStore(options) {
13445
14073
  const sanitize = options.sanitizeTopicId ?? sanitizeId;
13446
- const queryStateDirPath = (userId) => join25(options.usersLogDir, String(userId), "active-queries");
13447
- const queryStateFile = (userId, topicId) => join25(queryStateDirPath(userId), `${sanitize(topicId)}.json`);
14074
+ const queryStateDirPath = (userId) => join26(options.usersLogDir, String(userId), "active-queries");
14075
+ const queryStateFile = (userId, topicId) => join26(queryStateDirPath(userId), `${sanitize(topicId)}.json`);
13448
14076
  const legacyQueryStateFile = (userId, topicName) => {
13449
14077
  if (!topicName || topicName === "." || topicName === ".." || basename4(topicName) !== topicName) {
13450
14078
  return null;
13451
14079
  }
13452
- return join25(queryStateDirPath(userId), `${topicName}.json`);
14080
+ return join26(queryStateDirPath(userId), `${topicName}.json`);
13453
14081
  };
13454
14082
  return {
13455
14083
  write(userId, topicId, topicName, task) {
13456
14084
  const dir = queryStateDirPath(userId);
13457
- mkdirSync14(dir, { recursive: true });
14085
+ mkdirSync15(dir, { recursive: true });
13458
14086
  const state = { topicId, topicName, since: new Date().toISOString() };
13459
14087
  if (task)
13460
14088
  state.task = [...task.replace(/\n+/g, " ").trim()].slice(0, 100).join("");
13461
14089
  const target = queryStateFile(userId, topicId);
13462
14090
  const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
13463
- writeFileSync12(tmp, JSON.stringify(state));
14091
+ writeFileSync13(tmp, JSON.stringify(state));
13464
14092
  renameSync7(tmp, target);
13465
14093
  },
13466
14094
  clear(userId, topicId, legacyTopicName) {
@@ -13599,40 +14227,40 @@ __export(exports_session_asks, {
13599
14227
  clearPendingAsk: () => clearPendingAsk,
13600
14228
  PENDING_ASK_TTL_MS: () => PENDING_ASK_TTL_MS
13601
14229
  });
13602
- import { createHash as createHash6 } from "crypto";
14230
+ import { createHash as createHash7 } from "crypto";
13603
14231
  import {
13604
14232
  closeSync as closeSync2,
13605
- mkdirSync as mkdirSync15,
14233
+ mkdirSync as mkdirSync16,
13606
14234
  openSync as openSync2,
13607
14235
  readdirSync as readdirSync4,
13608
14236
  readFileSync as readFileSync16,
13609
14237
  statSync as statSync8,
13610
14238
  unlinkSync as unlinkSync16,
13611
- writeFileSync as writeFileSync13
14239
+ writeFileSync as writeFileSync14
13612
14240
  } from "fs";
13613
- import { dirname as dirname14, join as join26 } from "path";
14241
+ import { dirname as dirname14, join as join27 } from "path";
13614
14242
  function pendingAskDir(userId) {
13615
14243
  const rawUserId = String(userId);
13616
- const safeUserId = /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash6("sha256").update(rawUserId).digest("hex")}`;
13617
- return join26(resolveStorageSessionAsksDir(), safeUserId);
14244
+ const safeUserId = /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash7("sha256").update(rawUserId).digest("hex")}`;
14245
+ return join27(resolveStorageSessionAsksDir(), safeUserId);
13618
14246
  }
13619
14247
  function encodeAskKey(key) {
13620
14248
  return JSON.stringify([key.from, key.to]);
13621
14249
  }
13622
14250
  function pendingAskPath(key) {
13623
- const digest = createHash6("sha256").update(encodeAskKey(key)).digest("hex");
13624
- return join26(pendingAskDir(key.userId), `${ASK_FILENAME_PREFIX}${digest}.pending`);
14251
+ const digest = createHash7("sha256").update(encodeAskKey(key)).digest("hex");
14252
+ return join27(pendingAskDir(key.userId), `${ASK_FILENAME_PREFIX}${digest}.pending`);
13625
14253
  }
13626
14254
  function v2PendingAskPath(key) {
13627
14255
  const encoded = Buffer.from(encodeAskKey(key), "utf8").toString("base64url");
13628
- return join26(pendingAskDir(key.userId), `${V2_ASK_FILENAME_PREFIX}${encoded}.pending`);
14256
+ return join27(pendingAskDir(key.userId), `${V2_ASK_FILENAME_PREFIX}${encoded}.pending`);
13629
14257
  }
13630
14258
  function legacyPendingAskPath(key) {
13631
14259
  if (key.from.includes("/") || key.from.includes("\\") || key.to.includes("/") || key.to.includes("\\") || key.from.includes("\x00") || key.to.includes("\x00")) {
13632
14260
  return null;
13633
14261
  }
13634
14262
  const dir = pendingAskDir(key.userId);
13635
- const candidate = join26(dir, `${key.from}___${key.to}.pending`);
14263
+ const candidate = join27(dir, `${key.from}___${key.to}.pending`);
13636
14264
  return dirname14(candidate) === dir ? candidate : null;
13637
14265
  }
13638
14266
  function parsePendingAskFilename(fileName) {
@@ -13705,17 +14333,17 @@ function isStale(record, path) {
13705
14333
  }
13706
14334
  function writePendingAsk(record) {
13707
14335
  const path = pendingAskPath(record);
13708
- mkdirSync15(pendingAskDir(record.userId), { recursive: true });
13709
- writeFileSync13(path, `${JSON.stringify(record)}
14336
+ mkdirSync16(pendingAskDir(record.userId), { recursive: true });
14337
+ writeFileSync14(path, `${JSON.stringify(record)}
13710
14338
  `);
13711
14339
  }
13712
14340
  function writePendingAskIfAbsent(record) {
13713
14341
  const path = pendingAskPath(record);
13714
- mkdirSync15(pendingAskDir(record.userId), { recursive: true });
14342
+ mkdirSync16(pendingAskDir(record.userId), { recursive: true });
13715
14343
  let fd = null;
13716
14344
  try {
13717
14345
  fd = openSync2(path, "wx");
13718
- writeFileSync13(fd, `${JSON.stringify(record)}
14346
+ writeFileSync14(fd, `${JSON.stringify(record)}
13719
14347
  `);
13720
14348
  return true;
13721
14349
  } catch (error) {
@@ -13775,12 +14403,12 @@ function createPendingAsk(args) {
13775
14403
  createdAt: now,
13776
14404
  updatedAt: now
13777
14405
  };
13778
- mkdirSync15(pendingAskDir(args.userId), { recursive: true });
14406
+ mkdirSync16(pendingAskDir(args.userId), { recursive: true });
13779
14407
  for (let attempt = 0;attempt < 2; attempt++) {
13780
14408
  let fd = null;
13781
14409
  try {
13782
14410
  fd = openSync2(path, "wx");
13783
- writeFileSync13(fd, `${JSON.stringify(record)}
14411
+ writeFileSync14(fd, `${JSON.stringify(record)}
13784
14412
  `);
13785
14413
  return { ok: true, record };
13786
14414
  } catch (err2) {
@@ -13873,7 +14501,7 @@ function listPendingAsksForCaller(args) {
13873
14501
  const parsed = isV3 ? { from: args.from, to: "" } : parsePendingAskFilename(fileName);
13874
14502
  if (!parsed)
13875
14503
  continue;
13876
- const path = join26(dir, fileName);
14504
+ const path = join27(dir, fileName);
13877
14505
  const record = readPendingAskFile(path, {
13878
14506
  userId: args.userId,
13879
14507
  from: parsed.from,
@@ -13915,7 +14543,7 @@ function deletePendingAsksForTopic(args) {
13915
14543
  }
13916
14544
  let deleted = 0;
13917
14545
  for (const fileName of files) {
13918
- const path = join26(dir, fileName);
14546
+ const path = join27(dir, fileName);
13919
14547
  const parsed = parsePendingAskFilename(fileName);
13920
14548
  const record = readPendingAskFile(path, {
13921
14549
  userId: args.userId,
@@ -14312,94 +14940,6 @@ body{font-family:system-ui,-apple-system,"Segoe UI",sans-serif;background:var(--
14312
14940
  </style>`;
14313
14941
  });
14314
14942
 
14315
- // ../../packages/core/src/storage/token-stats.ts
14316
- import { createHash as createHash7 } from "crypto";
14317
- import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "fs";
14318
- import { join as join27 } from "path";
14319
- function tokenStatsFileId(userId) {
14320
- const rawUserId = String(userId);
14321
- return /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash7("sha256").update(rawUserId).digest("hex")}`;
14322
- }
14323
- function queriesPath(userId) {
14324
- const fileId = tokenStatsFileId(userId);
14325
- const logDir = resolveStorageLogDir();
14326
- mkdirSync16(logDir, { recursive: true });
14327
- return join27(logDir, `token-queries-${fileId}.jsonl`);
14328
- }
14329
- function estimateUsageCost(agent, model, usage) {
14330
- const prices = TOKEN_PRICES[`${agent}:${model}`];
14331
- if (!prices)
14332
- return 0;
14333
- return (usage.inputTokens * prices.input + usage.outputTokens * prices.output + (agent === "claude" ? usage.cacheCreationInputTokens * (prices.cacheWrite ?? prices.input) : 0) + usage.cacheReadInputTokens * prices.cacheRead) / 1e6;
14334
- }
14335
- function recordUsage(userId, session, usage, context) {
14336
- const cacheReadInputTokens = usage.cacheReadInputTokens ?? 0;
14337
- const inputTokens = context.agent === "claude" ? usage.inputTokens : Math.max(0, usage.inputTokens - cacheReadInputTokens);
14338
- const normalized = {
14339
- inputTokens,
14340
- outputTokens: usage.outputTokens,
14341
- cacheCreationInputTokens: usage.cacheCreationInputTokens ?? 0,
14342
- cacheReadInputTokens
14343
- };
14344
- const record = {
14345
- schemaVersion: 2,
14346
- timestamp: new Date().toISOString(),
14347
- session,
14348
- topicId: context.topicId,
14349
- ...context.providerSessionId ? { providerSessionId: context.providerSessionId } : {},
14350
- agent: context.agent,
14351
- model: context.model,
14352
- ...normalized,
14353
- ...usage.contextTokens !== undefined ? { contextTokens: usage.contextTokens } : {},
14354
- ...usage.contextWindow !== undefined ? { contextWindow: usage.contextWindow } : {},
14355
- estimatedCostUsd: usage.costUsd ?? estimateUsageCost(context.agent, context.model, normalized)
14356
- };
14357
- try {
14358
- appendJsonlEntry(queriesPath(userId), record);
14359
- } catch (e) {
14360
- logger.warn({ err: e, userId }, "token-stats: Failed to record");
14361
- }
14362
- }
14363
- function deleteTopicStats(userId, topicId) {
14364
- const path = queriesPath(userId);
14365
- try {
14366
- const kept = readJsonlLines(path).filter((line) => {
14367
- try {
14368
- const record = JSON.parse(line);
14369
- return record.topicId !== topicId;
14370
- } catch {
14371
- return true;
14372
- }
14373
- });
14374
- writeFileSync14(path, kept.length > 0 ? `${kept.join(`
14375
- `)}
14376
- ` : "", "utf-8");
14377
- } catch (e) {
14378
- if (e.code === "ENOENT")
14379
- return;
14380
- logger.warn({ err: e, userId, topicId }, "token-stats: Failed to delete topic stats");
14381
- }
14382
- }
14383
- var TOKEN_PRICES;
14384
- var init_token_stats = __esm(async () => {
14385
- init_jsonl();
14386
- init_logger();
14387
- await init_api_topics();
14388
- await init_storage_host();
14389
- TOKEN_PRICES = {
14390
- "codex:gpt-5.6-sol": { input: 5, cacheRead: 0.5, output: 30 },
14391
- "codex:gpt-5.6-terra": { input: 2.5, cacheRead: 0.25, output: 15 },
14392
- "codex:gpt-5.6-luna": { input: 1, cacheRead: 0.1, output: 6 },
14393
- "claude:fable": { input: 10, cacheWrite: 12.5, cacheRead: 1, output: 50 },
14394
- "claude:opus": { input: 5, cacheWrite: 6.25, cacheRead: 0.5, output: 25 },
14395
- "claude:sonnet": { input: 2, cacheWrite: 2.5, cacheRead: 0.2, output: 10 },
14396
- "maestro:kimi-k3": { input: 3, cacheRead: 0.3, output: 15 },
14397
- "maestro:kimi-k2.7-code": { input: 0.95, cacheRead: 0.19, output: 4 },
14398
- "maestro:deepseek-pro": { input: 0.435, cacheRead: 0.003625, output: 0.87 },
14399
- "maestro:deepseek-flash": { input: 0.14, cacheRead: 0.0028, output: 0.28 }
14400
- };
14401
- });
14402
-
14403
14943
  // ../../packages/core/src/storage/topic-tool-capabilities.ts
14404
14944
  function getTopicToolCapabilities(topicId) {
14405
14945
  const row = db.query(`SELECT visual_tools, file_delivery_tools
@@ -14438,6 +14978,7 @@ import { rmSync as rmSync6 } from "fs";
14438
14978
  async function abortAndWaitForTopic(topicId) {
14439
14979
  interSessionQueue.drop(topicId);
14440
14980
  cancelIdleArchiveForTopic(topicId);
14981
+ cancelIdleCompactForTopic(topicId);
14441
14982
  const aborted = abortRoom(topicId);
14442
14983
  if (!aborted)
14443
14984
  return true;
@@ -14610,6 +15151,7 @@ var DELETE_TURN_WAIT_MS = 5000, TopicArchiveRequiredError, TopicTurnStillActiveE
14610
15151
  var init_lifecycle = __esm(async () => {
14611
15152
  await init_archiver();
14612
15153
  await init_idle_archiver();
15154
+ await init_idle_compact();
14613
15155
  await init_spawn_subagent();
14614
15156
  await init_topic_cleanup();
14615
15157
  await init_bus();
@@ -15405,6 +15947,7 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
15405
15947
  const peerBridge = execution?.peerBridge ?? control.injectParams?.peerBridge;
15406
15948
  let errorOccurred = false;
15407
15949
  let terminalEmitted = false;
15950
+ const streamStartedAt = Date.now();
15408
15951
  let lastEventType = null;
15409
15952
  let pendingSawDelta = false;
15410
15953
  let accumulatedText = "";
@@ -15773,6 +16316,10 @@ ${JSON.stringify(event.input ?? {})}`);
15773
16316
  case "result":
15774
16317
  if (event.usage)
15775
16318
  recordEventUsage(event.usage);
16319
+ if (!accumulatedText.trim() && event.content.trim()) {
16320
+ accumulatedText = event.content;
16321
+ pendingText = event.content;
16322
+ }
15776
16323
  {
15777
16324
  const usage = event.usage ? {
15778
16325
  input: event.usage.inputTokens,
@@ -15789,8 +16336,18 @@ ${JSON.stringify(event.input ?? {})}`);
15789
16336
  }
15790
16337
  }
15791
16338
  }
16339
+ if (!accumulatedText.trim()) {
16340
+ const error = "Provider completed without an assistant response";
16341
+ terminalEmitted = true;
16342
+ outcome = silent ? { kind: "provider-error", error } : { kind: "empty-response", error };
16343
+ logger.warn({ topicId, queryId, agentType, model, silent }, "ai: provider completed without assistant text");
16344
+ if (silent)
16345
+ deliverAskError(queryId, topicTitle, error);
16346
+ return outcome;
16347
+ }
15792
16348
  if (!silent) {
15793
16349
  scheduleIdleArchiveForTopic(topicId, execution?.actorUserId ?? userId);
16350
+ scheduleIdleCompactForTopic(topicId, execution?.actorUserId ?? userId);
15794
16351
  hub.broadcastDone(topicId, queryId, event.usage ? {
15795
16352
  input: event.usage.inputTokens,
15796
16353
  output: event.usage.outputTokens,
@@ -15946,7 +16503,7 @@ ${JSON.stringify(event.input ?? {})}`);
15946
16503
  hadToolActivity: syntheticToolCounter > 0 || providerToolIds.size > 0
15947
16504
  }, "ai: provider stream ended without result, error, or abort");
15948
16505
  }
15949
- const discardIncompleteSegments = outcome.kind === "session-expired" || outcome.kind === "provider-error" || !terminalEmitted && !abortController.signal.aborted;
16506
+ const discardIncompleteSegments = outcome.kind === "session-expired" || outcome.kind === "provider-error" || outcome.kind === "empty-response" || !terminalEmitted && !abortController.signal.aborted;
15950
16507
  if (discardIncompleteSegments)
15951
16508
  discardVisibleAssistantMessages();
15952
16509
  const stillCurrent = getRoomQuery(roomId)?.queryId === queryId;
@@ -15958,22 +16515,32 @@ ${JSON.stringify(event.input ?? {})}`);
15958
16515
  hub.broadcastTyping(topicId, "");
15959
16516
  const forkHandle = control.injectParams?.forkHandle;
15960
16517
  const forkRequestId = control.injectParams?.requestId;
15961
- if (forkHandle && outcome.kind !== "session-expired" && (!forkRequestId || !interSessionQueue.hasRequest(topicId, forkRequestId))) {
16518
+ if (forkHandle && outcome.kind !== "session-expired" && outcome.kind !== "empty-response" && (!forkRequestId || !interSessionQueue.hasRequest(topicId, forkRequestId))) {
15962
16519
  cleanupAgentFork(forkHandle);
15963
16520
  }
15964
16521
  const queuedUserTurn = roomId === topicId ? getRuntimeUserTurnRequest(topicId) : null;
15965
16522
  const hasReplacementUserTurn = queuedUserTurn !== null && queuedUserTurn.requestId !== queryId;
15966
- if (roomId === topicId && outcome.kind !== "session-expired" && !getRoomQuery(topicId) && !hasReplacementUserTurn) {
16523
+ if (roomId === topicId && outcome.kind !== "session-expired" && outcome.kind !== "empty-response" && !getRoomQuery(topicId) && !hasReplacementUserTurn) {
15967
16524
  const next = takeDeferredInject(topicId);
15968
16525
  if (next)
15969
16526
  redispatchInject(next);
15970
16527
  }
16528
+ logger.info({
16529
+ topicId,
16530
+ queryId,
16531
+ outcomeKind: outcome.kind,
16532
+ assistantMessageCount: visibleMessageIds.length,
16533
+ assistantTextChars: accumulatedText.length,
16534
+ durationMs: Date.now() - streamStartedAt,
16535
+ lastEventType
16536
+ }, "ai: turn settled");
15971
16537
  }
15972
16538
  return outcome;
15973
16539
  }
15974
16540
  var init_turn_event_stream = __esm(async () => {
15975
16541
  await init_fork();
15976
16542
  await init_idle_archiver();
16543
+ await init_idle_compact();
15977
16544
  await init_ask_user();
15978
16545
  await init_spawn_subagent();
15979
16546
  init_model_catalog();
@@ -16656,6 +17223,7 @@ function startAiTurn(params) {
16656
17223
  const peerBridge = params.peerBridge;
16657
17224
  const askReplySources = params.askReplySources;
16658
17225
  const sessionRetried = params._sessionRetried === true;
17226
+ const emptyResponseRetried = params._emptyResponseRetried === true;
16659
17227
  const queryId = params._queryId ?? randomUUID16();
16660
17228
  const roomId = turnConcurrency === "isolated" ? isolatedTurnRoomId(topicId, queryId) : topicId;
16661
17229
  const currentRuntimeEpoch = getRuntimeTopicEpoch(topic.id);
@@ -17244,6 +17812,56 @@ function startAiTurn(params) {
17244
17812
  return;
17245
17813
  }
17246
17814
  }
17815
+ if (outcome.kind === "empty-response") {
17816
+ if (emptyResponseRetried) {
17817
+ outcome = { kind: "provider-error", error: outcome.error };
17818
+ } else {
17819
+ if (!silent)
17820
+ WsHub.get().broadcastAborted(topicId, queryId, "stopped");
17821
+ logger.info({ topicId, prevQueryId: queryId, agent: agentKind }, "ai: retrying query after empty provider response");
17822
+ startAiTurn({
17823
+ topic,
17824
+ userId,
17825
+ vaultUserId,
17826
+ prompt,
17827
+ _userMessages: userMessages,
17828
+ _conversationPrompts: conversationPrompts,
17829
+ _loggedUserMessageCount: loggedUserMessageCount,
17830
+ _durableRequestIds: durableRequestIds,
17831
+ attachments: attachments2,
17832
+ allowAutoContinue,
17833
+ origin,
17834
+ onDispatched,
17835
+ requestId,
17836
+ depth,
17837
+ silent,
17838
+ contextId,
17839
+ agentOverride,
17840
+ modelOverride,
17841
+ effortOverride,
17842
+ sessionId,
17843
+ sessionScope,
17844
+ turnConcurrency,
17845
+ forkHandle,
17846
+ prepareSession,
17847
+ cwd,
17848
+ sessionName,
17849
+ sessionType,
17850
+ visualTools,
17851
+ fileDeliveryTools,
17852
+ onSessionId,
17853
+ onSessionReset,
17854
+ bridgeSessionFromHistory,
17855
+ onSettled,
17856
+ peerBridge,
17857
+ askReplySources,
17858
+ _runtimeEpoch: runtimeEpoch,
17859
+ _sessionRetried: sessionRetried,
17860
+ _emptyResponseRetried: true
17861
+ });
17862
+ return;
17863
+ }
17864
+ }
17247
17865
  if (outcome.kind === "budget-capped") {
17248
17866
  const error = "The job reached its cost limit";
17249
17867
  if (!silent) {
@@ -19339,4 +19957,4 @@ export {
19339
19957
  DEFAULT_SELF_CONFIG_PRODUCT
19340
19958
  };
19341
19959
 
19342
- //# debugId=8B9AB8887B02E08264756E2164756E21
19960
+ //# debugId=823FBA6C6CCA73FD64756E2164756E21