@alook/daemon 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -20288,19 +20288,23 @@ class ClaudeEventNormalizer {
20288
20288
  const content = event?.message?.content;
20289
20289
  if (!Array.isArray(content))
20290
20290
  return;
20291
+ const completedText = [];
20291
20292
  for (const block of content) {
20292
20293
  if (block?.type === "thinking") {
20293
- out.push({ kind: "thinking", text: block.thinking ?? "" });
20294
+ out.push({ kind: "assistant_reasoning_completed", text: block.thinking ?? "" });
20294
20295
  } else if (block?.type === "text") {
20295
20296
  const text2 = block.text ?? "";
20296
20297
  if (API_ERROR_RE.test(text2))
20297
20298
  out.push({ kind: "error", message: text2 });
20298
20299
  else
20299
- out.push({ kind: "text", text: text2 });
20300
+ completedText.push(text2);
20300
20301
  } else if (block?.type === "tool_use") {
20301
20302
  out.push({ kind: "tool_call", name: block.name ?? "unknown_tool", input: block.input });
20302
20303
  }
20303
20304
  }
20305
+ if (completedText.length > 0) {
20306
+ out.push({ kind: "assistant_message_completed", text: completedText.join("") });
20307
+ }
20304
20308
  }
20305
20309
  handleUser(event, out) {
20306
20310
  if (event.isReplay === true && typeof event.uuid === "string") {
@@ -20709,14 +20713,17 @@ class CodexEventNormalizer {
20709
20713
  this.turnId = params.turn.id;
20710
20714
  this.terminalTurn = null;
20711
20715
  return [
20712
- { kind: "turn_owner", receipt: this.turnReceipt(params.threadId, params.turn.id) },
20713
- { kind: "thinking", text: "" }
20716
+ {
20717
+ kind: "turn_owner",
20718
+ receipt: this.turnReceipt(params.threadId, params.turn.id),
20719
+ nativeTurnId: params.turn.id
20720
+ }
20714
20721
  ];
20715
20722
  case "item/reasoning/textDelta":
20716
20723
  case "item/reasoning/summaryTextDelta":
20717
- return [{ kind: "thinking", text: params?.delta ?? "" }];
20724
+ return [{ kind: "assistant_reasoning_delta", text: params?.delta ?? "" }];
20718
20725
  case "item/agentMessage/delta":
20719
- return [{ kind: "text", text: params?.delta ?? "" }];
20726
+ return [{ kind: "assistant_message_delta", text: params?.delta ?? "" }];
20720
20727
  case "item/started":
20721
20728
  return this.handleItemStarted(params);
20722
20729
  case "item/completed":
@@ -20744,7 +20751,10 @@ class CodexEventNormalizer {
20744
20751
  }
20745
20752
  return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
20746
20753
  case "error":
20747
- return [{ kind: "error", message: params?.message ?? "Codex error" }];
20754
+ if (params?.willRetry === true) {
20755
+ return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
20756
+ }
20757
+ return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
20748
20758
  case "thread/tokenUsage/updated":
20749
20759
  case "account/rateLimits/updated":
20750
20760
  return mapCodexTelemetry(method, params);
@@ -20837,9 +20847,9 @@ class CodexEventNormalizer {
20837
20847
  case "collabAgentToolCall":
20838
20848
  return [{ kind: "tool_output", name: "collab_tool_call" }];
20839
20849
  case "agentMessage":
20840
- return [{ kind: "text", text: params?.item?.text ?? "" }];
20850
+ return [{ kind: "assistant_message_completed", text: params?.item?.text ?? "" }];
20841
20851
  case "reasoning":
20842
- return [{ kind: "thinking", text: params?.item?.text ?? "" }];
20852
+ return [{ kind: "assistant_reasoning_completed", text: params?.item?.text ?? "" }];
20843
20853
  default:
20844
20854
  return [];
20845
20855
  }
@@ -20872,7 +20882,13 @@ class CodexDriver {
20872
20882
  lifetime: "session",
20873
20883
  transport: { kind: "stdio_rpc", protocol: "codex.app-server.v1" },
20874
20884
  wakeStart: "immediate",
20875
- terminalOwnership: "transport_request"
20885
+ terminalOwnership: "transport_request",
20886
+ turnSilence: {
20887
+ nativeIdleTimeoutMs: 300000,
20888
+ daemonGraceMs: 60000,
20889
+ recoveryGraceMs: 60000,
20890
+ maxRecoveryExtensions: 1
20891
+ }
20876
20892
  };
20877
20893
  eventNormalizer = new CodexEventNormalizer;
20878
20894
  requestId = 0;
@@ -21529,14 +21545,14 @@ class CursorAcpLane {
21529
21545
  case "agent_message_chunk": {
21530
21546
  const content = record2(update.content);
21531
21547
  if (content?.type === "text" && typeof content.text === "string") {
21532
- this.events.emit("runtime_event", { kind: "text", text: content.text });
21548
+ this.events.emit("runtime_event", { kind: "assistant_message_delta", text: content.text });
21533
21549
  }
21534
21550
  return;
21535
21551
  }
21536
21552
  case "agent_thought_chunk": {
21537
21553
  const content = record2(update.content);
21538
21554
  if (content?.type === "text" && typeof content.text === "string") {
21539
- this.events.emit("runtime_event", { kind: "thinking", text: content.text });
21555
+ this.events.emit("runtime_event", { kind: "assistant_reasoning_delta", text: content.text });
21540
21556
  }
21541
21557
  return;
21542
21558
  }
@@ -22341,16 +22357,20 @@ class OpenCodeServiceLane {
22341
22357
  }
22342
22358
  switch (event.type) {
22343
22359
  case "session.next.step.started":
22344
- this.events.emit("runtime_event", { kind: "thinking", text: "" });
22360
+ this.events.emit("runtime_event", {
22361
+ kind: "internal_progress",
22362
+ source: "opencode.service",
22363
+ itemType: "step_started"
22364
+ });
22345
22365
  break;
22346
22366
  case "session.next.text.ended":
22347
22367
  if (typeof data.text === "string" && data.text.length > 0) {
22348
- this.events.emit("runtime_event", { kind: "text", text: data.text });
22368
+ this.events.emit("runtime_event", { kind: "assistant_message_completed", text: data.text });
22349
22369
  }
22350
22370
  break;
22351
22371
  case "session.next.reasoning.ended":
22352
22372
  if (typeof data.text === "string" && data.text.length > 0) {
22353
- this.events.emit("runtime_event", { kind: "thinking", text: data.text });
22373
+ this.events.emit("runtime_event", { kind: "assistant_reasoning_completed", text: data.text });
22354
22374
  }
22355
22375
  break;
22356
22376
  case "session.next.tool.called":
@@ -23101,22 +23121,28 @@ function readPiSdkVersion() {
23101
23121
  }
23102
23122
  function mapPiSdkEvent(event, sessionId, state) {
23103
23123
  if (event?.type === "message_update") {
23104
- const d = event.delta ?? {};
23124
+ const d = event.assistantMessageEvent ?? {};
23105
23125
  switch (d.type) {
23106
23126
  case "thinking_delta":
23107
- return [{ kind: "thinking", text: d.delta ?? "" }];
23127
+ return [{ kind: "assistant_reasoning_delta", text: d.delta ?? "" }];
23108
23128
  case "text_delta":
23109
23129
  state.sawTextDelta = true;
23110
- return [{ kind: "text", text: d.delta ?? "" }];
23111
- case "text_end":
23112
- return state.sawTextDelta ? [] : [{ kind: "text", text: d.content ?? "" }];
23130
+ return [{ kind: "assistant_message_delta", text: d.delta ?? "" }];
23131
+ case "text_end": {
23132
+ state.sawTextDelta = false;
23133
+ return [{ kind: "assistant_message_completed", text: d.content ?? "" }];
23134
+ }
23113
23135
  case "error":
23114
- return [{ kind: "error", message: d.message ?? "Pi error" }];
23136
+ return [{ kind: "error", message: d.error?.errorMessage ?? "Pi error" }];
23115
23137
  default:
23116
23138
  return [];
23117
23139
  }
23118
23140
  }
23119
23141
  switch (event?.type) {
23142
+ case "auto_retry_start":
23143
+ return [{ kind: "runtime_recovery", stage: "retrying", source: "pi_auto_retry" }];
23144
+ case "auto_retry_end":
23145
+ return [{ kind: "runtime_recovery", stage: "recovered", source: "pi_auto_retry" }];
23120
23146
  case "tool_execution_start":
23121
23147
  return [{ kind: "tool_call", name: event.toolName ?? "unknown_tool", input: event.args ?? {} }];
23122
23148
  case "tool_execution_end":
@@ -23281,6 +23307,13 @@ function assertAdapterCompatibility(registrationId, registeredCapabilities, adap
23281
23307
  if (!transport || typeof transport.kind !== "string" || transport.kind.trim().length === 0 || typeof transport.protocol !== "string" || transport.protocol.trim().length === 0 || transport.metadata !== undefined && (!transport.metadata || typeof transport.metadata !== "object" || Array.isArray(transport.metadata))) {
23282
23308
  throw new Error(`Adapter ${registrationId} has an invalid transport declaration`);
23283
23309
  }
23310
+ if (execution.turnSilence !== undefined) {
23311
+ const silence = execution.turnSilence;
23312
+ const safeInteger = (value, minimum) => typeof value === "number" && Number.isSafeInteger(value) && value >= minimum;
23313
+ if (!silence || typeof silence !== "object" || Array.isArray(silence) || !safeInteger(silence.nativeIdleTimeoutMs, 1) || !safeInteger(silence.daemonGraceMs, 0) || !safeInteger(silence.recoveryGraceMs, 0) || !safeInteger(silence.maxRecoveryExtensions, 0) || !Number.isSafeInteger(silence.nativeIdleTimeoutMs + silence.daemonGraceMs)) {
23314
+ throw new Error(`Adapter ${registrationId} has an invalid turnSilence declaration`);
23315
+ }
23316
+ }
23284
23317
  const capabilities = registeredCapabilities;
23285
23318
  const declaredLifetime = capabilities?.sessionLifetime === "persistent" ? "session" : "turn";
23286
23319
  if (execution.lifetime !== declaredLifetime) {
@@ -23370,6 +23403,12 @@ function capabilitiesFor(backend) {
23370
23403
  return builtinRegistry.get(backend).capabilities;
23371
23404
  }
23372
23405
 
23406
+ // agent-driver/dist/internal/adapter.js
23407
+ var DEFAULT_NATIVE_IDLE_TIMEOUT_MS = 300000;
23408
+ var DEFAULT_DAEMON_GRACE_MS = 60000;
23409
+ var DEFAULT_RECOVERY_GRACE_MS = 60000;
23410
+ var DEFAULT_MAX_RECOVERY_EXTENSIONS = 1;
23411
+
23373
23412
  // agent-driver/dist/controller/event-queue.js
23374
23413
  var MAX_BUFFERED_BYTES = 4194304;
23375
23414
 
@@ -23566,6 +23605,62 @@ function stableErrorCode(value, fallback) {
23566
23605
 
23567
23606
  // agent-driver/dist/controller/logical-session.js
23568
23607
  import { mkdirSync as mkdirSync4 } from "node:fs";
23608
+ var SEMANTIC_ASSEMBLER_MAX_BYTES = 1048576;
23609
+ var WORK_HEARTBEAT_MIN_INTERVAL_MS = 1000;
23610
+ function emptySemanticAssembler() {
23611
+ return { chunks: [], bytes: 0, truncated: false };
23612
+ }
23613
+ function utf8Prefix(text2, maxBytes) {
23614
+ if (maxBytes <= 0 || text2.length === 0)
23615
+ return "";
23616
+ if (Buffer.byteLength(text2, "utf8") <= maxBytes)
23617
+ return text2;
23618
+ let low = 0;
23619
+ let high = text2.length;
23620
+ while (low < high) {
23621
+ const mid = Math.ceil((low + high) / 2);
23622
+ let end2 = mid;
23623
+ const code2 = text2.charCodeAt(end2 - 1);
23624
+ if (code2 >= 55296 && code2 <= 56319)
23625
+ end2 -= 1;
23626
+ if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
23627
+ low = mid;
23628
+ else
23629
+ high = mid - 1;
23630
+ }
23631
+ let end = low;
23632
+ const code = text2.charCodeAt(end - 1);
23633
+ if (code >= 55296 && code <= 56319)
23634
+ end -= 1;
23635
+ while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
23636
+ end -= 1;
23637
+ return text2.slice(0, end);
23638
+ }
23639
+ function appendSemanticFragment(buffer, text2) {
23640
+ if (text2.length === 0 || buffer.truncated)
23641
+ return;
23642
+ const remaining = SEMANTIC_ASSEMBLER_MAX_BYTES - buffer.bytes;
23643
+ const bytes = Buffer.byteLength(text2, "utf8");
23644
+ if (bytes <= remaining) {
23645
+ buffer.chunks.push(text2);
23646
+ buffer.bytes += bytes;
23647
+ return;
23648
+ }
23649
+ const prefix = utf8Prefix(text2, remaining);
23650
+ if (prefix.length > 0) {
23651
+ buffer.chunks.push(prefix);
23652
+ buffer.bytes += Buffer.byteLength(prefix, "utf8");
23653
+ }
23654
+ buffer.truncated = true;
23655
+ }
23656
+ function finishSemanticAssembler(buffer) {
23657
+ return { text: buffer.chunks.join(""), truncated: buffer.truncated };
23658
+ }
23659
+ function boundedSemanticCompletion(text2) {
23660
+ const buffer = emptySemanticAssembler();
23661
+ appendSemanticFragment(buffer, text2);
23662
+ return finishSemanticAssembler(buffer);
23663
+ }
23569
23664
  function driverError(category, code, message2, retryable = false) {
23570
23665
  return { category, code, message: scrubDriverErrorMessage(message2), retryable };
23571
23666
  }
@@ -23639,6 +23734,7 @@ class LogicalAgentSession {
23639
23734
  resumeOutcome;
23640
23735
  eventQueue;
23641
23736
  behavior;
23737
+ turnSilence;
23642
23738
  constructor(backend, config2, launch, adapter, capabilities2, host, prepared, hostReleaseTimeoutMs) {
23643
23739
  this.backend = backend;
23644
23740
  this.config = config2;
@@ -23649,6 +23745,18 @@ class LogicalAgentSession {
23649
23745
  this.hostReleaseTimeoutMs = hostReleaseTimeoutMs;
23650
23746
  this.capabilities = capabilities2;
23651
23747
  this.behavior = this.capabilities;
23748
+ const declaredSilence = adapter.execution.turnSilence;
23749
+ const nativeIdleTimeoutMs = declaredSilence?.nativeIdleTimeoutMs ?? DEFAULT_NATIVE_IDLE_TIMEOUT_MS;
23750
+ const daemonGraceMs = declaredSilence?.daemonGraceMs ?? DEFAULT_DAEMON_GRACE_MS;
23751
+ const recoveryGraceMs = declaredSilence?.recoveryGraceMs ?? DEFAULT_RECOVERY_GRACE_MS;
23752
+ const maxRecoveryExtensions = declaredSilence?.maxRecoveryExtensions ?? DEFAULT_MAX_RECOVERY_EXTENSIONS;
23753
+ this.turnSilence = {
23754
+ nativeIdleTimeoutMs,
23755
+ daemonGraceMs,
23756
+ recoveryGraceMs,
23757
+ maxRecoveryExtensions,
23758
+ normalBudgetMs: nativeIdleTimeoutMs + daemonGraceMs
23759
+ };
23652
23760
  this.resumeOutcome = launch.resumeSessionId ? "pending" : "not_requested";
23653
23761
  this.sessionInstanceId = host.createId();
23654
23762
  this.closed = new Promise((resolve3) => {
@@ -23753,6 +23861,7 @@ class LogicalAgentSession {
23753
23861
  lastEventSequence: this.eventSequence,
23754
23862
  diagnostics: {
23755
23863
  deliveryPhase: this.deliveryPhase(),
23864
+ turnSilence: this.turnSilence,
23756
23865
  metrics: {
23757
23866
  physicalOpenCount: this.metricValue(this.physicalOpenCount),
23758
23867
  turnCount: this.metricValue(this.turnCount),
@@ -23843,7 +23952,14 @@ class LogicalAgentSession {
23843
23952
  const turnId = this.host.createId();
23844
23953
  const commandIds = messages.map((message2) => message2.id);
23845
23954
  const terminalOwner = this.adapter.beginTurn?.();
23846
- this.activeTurn = { turnId, commandIds: [...commandIds], ...terminalOwner ? { terminalOwner } : {} };
23955
+ this.activeTurn = {
23956
+ turnId,
23957
+ commandIds: [...commandIds],
23958
+ ...terminalOwner ? { terminalOwner } : {},
23959
+ pendingMessage: emptySemanticAssembler(),
23960
+ pendingReasoning: emptySemanticAssembler(),
23961
+ lastWorkHeartbeatAt: null
23962
+ };
23847
23963
  this.turnError = undefined;
23848
23964
  this.interruptedTurnId = undefined;
23849
23965
  this.processTurnEnded = false;
@@ -24041,14 +24157,36 @@ class LogicalAgentSession {
24041
24157
  return;
24042
24158
  }
24043
24159
  this.activeTurn.terminalOwner = event.receipt;
24160
+ const nativeTurnId = event.nativeTurnId?.trim();
24161
+ if (nativeTurnId && nativeTurnId.length <= 512 && /^[A-Za-z0-9._:-]+$/.test(nativeTurnId)) {
24162
+ this.emit({
24163
+ type: "backend_turn_started",
24164
+ turnId: this.activeTurn.turnId,
24165
+ backendTurnId: nativeTurnId
24166
+ });
24167
+ }
24044
24168
  return;
24045
- case "thinking":
24046
- if (turnId)
24047
- this.emit({ type: "thinking_delta", turnId, text: event.text });
24169
+ case "assistant_reasoning_delta":
24170
+ case "assistant_message_delta":
24171
+ if (turnId && this.activeTurn?.turnId === turnId && event.text.length > 0) {
24172
+ appendSemanticFragment(event.kind === "assistant_message_delta" ? this.activeTurn.pendingMessage : this.activeTurn.pendingReasoning, event.text);
24173
+ const now = this.host.now();
24174
+ if (this.activeTurn.lastWorkHeartbeatAt === null || now - this.activeTurn.lastWorkHeartbeatAt >= WORK_HEARTBEAT_MIN_INTERVAL_MS) {
24175
+ this.activeTurn.lastWorkHeartbeatAt = now;
24176
+ this.emit({ type: "work_heartbeat", turnId });
24177
+ }
24178
+ }
24048
24179
  return;
24049
- case "text":
24050
- if (turnId)
24051
- this.emit({ type: "text_delta", turnId, text: event.text });
24180
+ case "assistant_reasoning_completed":
24181
+ case "assistant_message_completed":
24182
+ if (turnId && this.activeTurn?.turnId === turnId) {
24183
+ const field = event.kind === "assistant_message_completed" ? "pendingMessage" : "pendingReasoning";
24184
+ this.activeTurn[field] = emptySemanticAssembler();
24185
+ if (event.text.length > 0) {
24186
+ const completed = boundedSemanticCompletion(event.text);
24187
+ this.emit({ type: event.kind, turnId, ...completed });
24188
+ }
24189
+ }
24052
24190
  return;
24053
24191
  case "tool_call":
24054
24192
  this.outstandingToolUses += 1;
@@ -24091,9 +24229,19 @@ class LogicalAgentSession {
24091
24229
  message: scrubDriverErrorMessage(event.message, "Runtime diagnostic")
24092
24230
  });
24093
24231
  return;
24232
+ case "runtime_recovery":
24233
+ this.emit({
24234
+ type: "recovery",
24235
+ turnId,
24236
+ stage: event.stage,
24237
+ source: event.source
24238
+ });
24239
+ return;
24094
24240
  case "runtime_metric":
24095
- if (event.name === "sse_reconnect" && event.increment === 1)
24241
+ if (event.name === "sse_reconnect" && event.increment === 1) {
24096
24242
  this.sseReconnectCount += 1;
24243
+ this.emit({ type: "recovery", turnId, stage: "retrying", source: "transport_reconnect" });
24244
+ }
24097
24245
  return;
24098
24246
  case "telemetry": {
24099
24247
  const details = jsonValue(event.attrs);
@@ -24116,6 +24264,18 @@ class LogicalAgentSession {
24116
24264
  });
24117
24265
  return;
24118
24266
  case "turn_end":
24267
+ if (turnId && this.activeTurn?.turnId === turnId) {
24268
+ const reasoning = finishSemanticAssembler(this.activeTurn.pendingReasoning);
24269
+ const message2 = finishSemanticAssembler(this.activeTurn.pendingMessage);
24270
+ this.activeTurn.pendingReasoning = emptySemanticAssembler();
24271
+ this.activeTurn.pendingMessage = emptySemanticAssembler();
24272
+ if (reasoning.text.length > 0) {
24273
+ this.emit({ type: "assistant_reasoning_completed", turnId, ...reasoning });
24274
+ }
24275
+ if (message2.text.length > 0) {
24276
+ this.emit({ type: "assistant_message_completed", turnId, ...message2 });
24277
+ }
24278
+ }
24119
24279
  this.completeTurn(event.sessionId, physicalOwner, generation, event.turnOwner);
24120
24280
  return;
24121
24281
  }
@@ -24129,7 +24289,7 @@ class LogicalAgentSession {
24129
24289
  reopenClosedLaneForWork(event, physicalOwner, generation) {
24130
24290
  if (this.adapter.execution.lifetime === "turn")
24131
24291
  return;
24132
- const isRootWork = event.kind === "thinking" || event.kind === "text" || event.kind === "tool_call" || event.kind === "tool_output" || event.kind === "compaction_started" || event.kind === "compaction_finished" || event.kind === "review_started" || event.kind === "review_finished" || event.kind === "internal_progress";
24292
+ const isRootWork = event.kind === "tool_call" || event.kind === "tool_output" || event.kind === "compaction_started" || event.kind === "compaction_finished" || event.kind === "review_started" || event.kind === "review_finished" || event.kind === "internal_progress";
24133
24293
  if (!isRootWork)
24134
24294
  return;
24135
24295
  const tombstone = this.closedLaneTombstone;
@@ -24139,7 +24299,10 @@ class LogicalAgentSession {
24139
24299
  this.activeTurn = {
24140
24300
  turnId: tombstone.localTurnId,
24141
24301
  commandIds: tombstone.commandIds,
24142
- ...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {}
24302
+ ...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {},
24303
+ pendingMessage: emptySemanticAssembler(),
24304
+ pendingReasoning: emptySemanticAssembler(),
24305
+ lastWorkHeartbeatAt: null
24143
24306
  };
24144
24307
  this.state = "working";
24145
24308
  return tombstone.localTurnId;
@@ -26242,11 +26405,26 @@ function isActivelyWorking(agent2) {
26242
26405
  return agent2.status === "running" && (leaseIsWorking(agent2.execution.lease) || agent2.pendingAdmissions.length > 0 || agent2.inbox.length > 0);
26243
26406
  }
26244
26407
  var DEFAULT_STALE_THRESHOLD_MS = 120000;
26245
- var DEFAULT_IDLE_TIMEOUT_MS = 300000;
26408
+ var DEFAULT_TURN_SILENCE_POLICY = {
26409
+ nativeIdleTimeoutMs: 300000,
26410
+ daemonGraceMs: 60000,
26411
+ recoveryGraceMs: 60000,
26412
+ maxRecoveryExtensions: 1,
26413
+ normalBudgetMs: 360000
26414
+ };
26415
+ var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
26416
+ var DEFAULT_IDLE_RESET_TIMEOUT_MS = 6 * 60 * 60 * 1000;
26246
26417
  var DEFAULT_RESET_STUCK_THRESHOLD_MS = 120000;
26247
26418
  var DEFAULT_STOPPING_STUCK_THRESHOLD_MS = 30000;
26248
- function createInitialManagerState(staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, resetStuckThresholdMs = DEFAULT_RESET_STUCK_THRESHOLD_MS, stoppingStuckThresholdMs = DEFAULT_STOPPING_STUCK_THRESHOLD_MS) {
26249
- return { agents: {}, staleThresholdMs, idleTimeoutMs, resetStuckThresholdMs, stoppingStuckThresholdMs };
26419
+ function createInitialManagerState(staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, resetStuckThresholdMs = DEFAULT_RESET_STUCK_THRESHOLD_MS, stoppingStuckThresholdMs = DEFAULT_STOPPING_STUCK_THRESHOLD_MS, idleResetTimeoutMs = DEFAULT_IDLE_RESET_TIMEOUT_MS) {
26420
+ return {
26421
+ agents: {},
26422
+ staleThresholdMs,
26423
+ idleTimeoutMs,
26424
+ idleResetTimeoutMs,
26425
+ resetStuckThresholdMs,
26426
+ stoppingStuckThresholdMs
26427
+ };
26250
26428
  }
26251
26429
  function reduceManager(state, event) {
26252
26430
  switch (event.type) {
@@ -26261,6 +26439,11 @@ function reduceManager(state, event) {
26261
26439
  });
26262
26440
  case "backend_session":
26263
26441
  return mutate(state, event.agentId, (a) => {
26442
+ if (event.stalledBefore) {
26443
+ a.stalledSessionId = event.sessionId;
26444
+ } else if (a.stalledSessionId !== null && a.stalledSessionId !== event.sessionId) {
26445
+ a.stalledSessionId = null;
26446
+ }
26264
26447
  a.sessionId = event.sessionId;
26265
26448
  });
26266
26449
  case "attach_session": {
@@ -26273,7 +26456,17 @@ function reduceManager(state, event) {
26273
26456
  {
26274
26457
  const a = agent2;
26275
26458
  a.execution = { sessionInstanceId: event.sessionInstanceId, lease: { state: "none", lastTerminal: null } };
26459
+ a.turnSilence = event.turnSilence ?? {
26460
+ ...DEFAULT_TURN_SILENCE_POLICY,
26461
+ nativeIdleTimeoutMs: state.staleThresholdMs,
26462
+ daemonGraceMs: 0,
26463
+ normalBudgetMs: state.staleThresholdMs
26464
+ };
26276
26465
  a.lastProgressAt = event.nowMs;
26466
+ a.lastNativeActivityAt = event.nowMs;
26467
+ a.lastNativeActivityKind = null;
26468
+ a.runtimePhase = "admission";
26469
+ a.backendTurnId = null;
26277
26470
  a.idleSince = null;
26278
26471
  syncExecutionProjection(a);
26279
26472
  }
@@ -26329,7 +26522,25 @@ function reduceManager(state, event) {
26329
26522
  return { state, effects: [] };
26330
26523
  return mutate(state, event.agentId, (a) => {
26331
26524
  a.sessionId = null;
26525
+ a.stalledSessionId = null;
26526
+ a.idleSince = null;
26332
26527
  });
26528
+ case "idle_reset_committed": {
26529
+ const existing = state.agents[event.agentId];
26530
+ if (!existing)
26531
+ return { state, effects: [] };
26532
+ const agent2 = clone2(existing);
26533
+ agent2.sessionId = null;
26534
+ agent2.stalledSessionId = null;
26535
+ agent2.idleSince = null;
26536
+ if (agent2.status !== "running")
26537
+ return commit(state, agent2, []);
26538
+ agent2.status = "stopping";
26539
+ agent2.stoppingSince = event.nowMs;
26540
+ return commit(state, agent2, [
26541
+ { type: "stop", agentId: event.agentId, reason: "idle_session_reset" }
26542
+ ]);
26543
+ }
26333
26544
  case "begin_reset":
26334
26545
  if (!state.agents[event.agentId])
26335
26546
  return { state, effects: [] };
@@ -26367,8 +26578,18 @@ function reduceManager(state, event) {
26367
26578
  return;
26368
26579
  const startedCommands = new Set(event.commandIds);
26369
26580
  a.pendingAdmissions = a.pendingAdmissions.filter((entry) => !startedCommands.has(entry.commandId));
26370
- a.execution.lease = { state: "active", identity, lastWorkAt: event.nowMs };
26581
+ a.execution.lease = {
26582
+ state: "active",
26583
+ identity,
26584
+ lastWorkAt: event.nowMs,
26585
+ nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
26586
+ recoveryExtensionsUsed: 0
26587
+ };
26371
26588
  a.lastProgressAt = event.nowMs;
26589
+ a.lastNativeActivityAt = event.nowMs;
26590
+ a.lastNativeActivityKind = "turn_started";
26591
+ a.runtimePhase = "inference";
26592
+ a.backendTurnId = null;
26372
26593
  a.idleSince = null;
26373
26594
  syncExecutionProjection(a);
26374
26595
  });
@@ -26389,7 +26610,12 @@ function reduceManager(state, event) {
26389
26610
  const lease = a.execution.lease;
26390
26611
  const identity = identityOf(event);
26391
26612
  if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
26392
- a.execution.lease = { ...lease, lastWorkAt: event.nowMs };
26613
+ a.execution.lease = {
26614
+ ...lease,
26615
+ lastWorkAt: event.nowMs,
26616
+ nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
26617
+ recoveryExtensionsUsed: 0
26618
+ };
26393
26619
  } else {
26394
26620
  const terminal = lease.state === "none" ? lease.lastTerminal : null;
26395
26621
  if (!terminal || !sameIdentity(terminal.identity, identity))
@@ -26398,6 +26624,8 @@ function reduceManager(state, event) {
26398
26624
  state: "suspect_active",
26399
26625
  identity,
26400
26626
  lastWorkAt: event.nowMs,
26627
+ nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
26628
+ recoveryExtensionsUsed: 0,
26401
26629
  reason: "work_after_terminal"
26402
26630
  };
26403
26631
  }
@@ -26411,7 +26639,7 @@ function reduceManager(state, event) {
26411
26639
  case "turn_tool_finished":
26412
26640
  return onTurnToolLifecycle(state, event, "finished");
26413
26641
  case "turn_completed":
26414
- return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId);
26642
+ return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId, event.endReason);
26415
26643
  case "session_closed":
26416
26644
  if (state.agents[event.agentId]?.execution.sessionInstanceId !== event.sessionInstanceId) {
26417
26645
  return { state, effects: [] };
@@ -26421,7 +26649,6 @@ function reduceManager(state, event) {
26421
26649
  const closing = agent2.pendingAdmissions.filter((entry) => entry.sessionInstanceId === event.sessionInstanceId);
26422
26650
  agent2.pendingAdmissions = agent2.pendingAdmissions.filter((entry) => entry.sessionInstanceId !== event.sessionInstanceId);
26423
26651
  agent2.execution = { sessionInstanceId: null, lease: { state: "detached" } };
26424
- agent2.idleSince = null;
26425
26652
  syncExecutionProjection(agent2);
26426
26653
  return commit(state, agent2, recoveryEffects(agent2, closing));
26427
26654
  }
@@ -26429,8 +26656,57 @@ function reduceManager(state, event) {
26429
26656
  return onExit(state, event.agentId);
26430
26657
  case "tick":
26431
26658
  return onTick(state, event.nowMs);
26432
- case "runtime_signal":
26433
- return { state, effects: [] };
26659
+ case "runtime_signal": {
26660
+ const existing = state.agents[event.agentId];
26661
+ if (!existing || existing.execution.sessionInstanceId !== event.sessionInstanceId)
26662
+ return { state, effects: [] };
26663
+ const lease = existing.execution.lease;
26664
+ const identity = { sessionInstanceId: event.sessionInstanceId, turnId: event.turnId };
26665
+ if (lease.state !== "active" && lease.state !== "suspect_active" || !sameIdentity(lease.identity, identity)) {
26666
+ return { state, effects: [] };
26667
+ }
26668
+ return mutate(state, event.agentId, (a) => {
26669
+ const active = a.execution.lease;
26670
+ if (active.state !== "active" && active.state !== "suspect_active" || !sameIdentity(active.identity, identity))
26671
+ return;
26672
+ if (event.kind === "recovery" && event.recoveryStage !== "recovered") {
26673
+ if (active.recoveryExtensionsUsed < a.turnSilence.maxRecoveryExtensions) {
26674
+ a.execution.lease = {
26675
+ ...active,
26676
+ nativeDeadlineAt: Math.max(active.nativeDeadlineAt, event.nowMs + a.turnSilence.recoveryGraceMs),
26677
+ recoveryExtensionsUsed: active.recoveryExtensionsUsed + 1
26678
+ };
26679
+ }
26680
+ } else {
26681
+ a.execution.lease = {
26682
+ ...active,
26683
+ nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs
26684
+ };
26685
+ }
26686
+ a.lastNativeActivityAt = event.nowMs;
26687
+ a.lastNativeActivityKind = event.kind;
26688
+ a.runtimePhase = event.phase;
26689
+ if (event.backendTurnId)
26690
+ a.backendTurnId = event.backendTurnId;
26691
+ });
26692
+ }
26693
+ case "stall_control_failed":
26694
+ return mutate(state, event.agentId, (a) => {
26695
+ if (event.transition === "clear") {
26696
+ if (a.sessionId === event.sessionId)
26697
+ a.stalledSessionId = event.sessionId;
26698
+ return;
26699
+ }
26700
+ if (a.status !== "stopping")
26701
+ return;
26702
+ const lease = a.execution.lease;
26703
+ if (lease.state !== "active" && lease.state !== "suspect_active")
26704
+ return;
26705
+ a.status = "running";
26706
+ a.stoppingSince = null;
26707
+ a.sessionId = event.sessionId;
26708
+ a.stalledSessionId = event.transition === "fence" ? event.sessionId : null;
26709
+ });
26434
26710
  case "delivery_rejected":
26435
26711
  return mutate(state, event.agentId, (a) => {
26436
26712
  if (!a.inbox.some((message2) => message2.id === event.message.id)) {
@@ -26468,7 +26744,7 @@ function onWake(state, agentId, message2) {
26468
26744
  }
26469
26745
  return commit(state, agent2, []);
26470
26746
  }
26471
- function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
26747
+ function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId, endReason) {
26472
26748
  const existing = state.agents[agentId];
26473
26749
  if (!existing)
26474
26750
  return { state, effects: [] };
@@ -26484,13 +26760,23 @@ function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
26484
26760
  const lastTerminal = { identity, at: nowMs };
26485
26761
  agent2.execution.lease = { state: "none", lastTerminal };
26486
26762
  agent2.lastProgressAt = nowMs;
26763
+ agent2.lastNativeActivityAt = nowMs;
26764
+ agent2.lastNativeActivityKind = "turn_end";
26765
+ agent2.runtimePhase = "terminal";
26766
+ const clearedStallSessionId = endReason === undefined ? agent2.stalledSessionId : null;
26767
+ if (clearedStallSessionId !== null)
26768
+ agent2.stalledSessionId = null;
26487
26769
  syncExecutionProjection(agent2);
26770
+ const clearEffects = clearedStallSessionId === null ? [] : [{ type: "clear_stall_recovery", agentId, sessionId: clearedStallSessionId }];
26488
26771
  if (agent2.inbox.length > 0) {
26489
26772
  const messages = drainInbox(agent2);
26490
- return commit(state, agent2, messages.map((queued) => ({ type: "send", agentId, message: queued, mode: "idle" })));
26773
+ return commit(state, agent2, [
26774
+ ...clearEffects,
26775
+ ...messages.map((queued) => ({ type: "send", agentId, message: queued, mode: "idle" }))
26776
+ ]);
26491
26777
  }
26492
26778
  agent2.idleSince = nowMs;
26493
- return commit(state, agent2, []);
26779
+ return commit(state, agent2, clearEffects);
26494
26780
  }
26495
26781
  function onTurnToolLifecycle(state, event, lifecycle) {
26496
26782
  const existing = state.agents[event.agentId];
@@ -26512,11 +26798,22 @@ function onTurnToolLifecycle(state, event, lifecycle) {
26512
26798
  if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
26513
26799
  const outstandingToolUses = (lease.outstandingToolUses ?? 0) + (lifecycle === "started" ? 1 : -1);
26514
26800
  if (outstandingToolUses > 0) {
26515
- agent2.execution.lease = { ...lease, lastWorkAt: event.nowMs, outstandingToolUses };
26801
+ agent2.execution.lease = {
26802
+ ...lease,
26803
+ lastWorkAt: event.nowMs,
26804
+ nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
26805
+ recoveryExtensionsUsed: 0,
26806
+ outstandingToolUses
26807
+ };
26516
26808
  } else {
26517
26809
  const unblockedLease = { ...lease };
26518
26810
  delete unblockedLease.outstandingToolUses;
26519
- agent2.execution.lease = { ...unblockedLease, lastWorkAt: event.nowMs };
26811
+ agent2.execution.lease = {
26812
+ ...unblockedLease,
26813
+ lastWorkAt: event.nowMs,
26814
+ nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
26815
+ recoveryExtensionsUsed: 0
26816
+ };
26520
26817
  }
26521
26818
  } else {
26522
26819
  const terminal = lease.state === "none" ? lease.lastTerminal : null;
@@ -26526,11 +26823,16 @@ function onTurnToolLifecycle(state, event, lifecycle) {
26526
26823
  state: "suspect_active",
26527
26824
  identity,
26528
26825
  lastWorkAt: event.nowMs,
26826
+ nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
26827
+ recoveryExtensionsUsed: 0,
26529
26828
  outstandingToolUses: 1,
26530
26829
  reason: "work_after_terminal"
26531
26830
  };
26532
26831
  }
26533
26832
  agent2.lastProgressAt = event.nowMs;
26833
+ agent2.lastNativeActivityAt = event.nowMs;
26834
+ agent2.lastNativeActivityKind = lifecycle === "started" ? "tool_call" : "tool_output";
26835
+ agent2.runtimePhase = lifecycle === "started" ? "tool" : "inference";
26534
26836
  agent2.idleSince = null;
26535
26837
  syncExecutionProjection(agent2);
26536
26838
  });
@@ -26543,6 +26845,8 @@ function onExit(state, agentId) {
26543
26845
  const effects = recoveryEffects(agent2, agent2.pendingAdmissions);
26544
26846
  agent2.pendingAdmissions = [];
26545
26847
  agent2.execution = { sessionInstanceId: null, lease: { state: "detached" } };
26848
+ agent2.runtimePhase = "idle";
26849
+ agent2.backendTurnId = null;
26546
26850
  agent2.stoppingSince = null;
26547
26851
  syncExecutionProjection(agent2);
26548
26852
  if (agent2.inbox.length > 0) {
@@ -26562,10 +26866,19 @@ function onTick(state, nowMs) {
26562
26866
  for (const id of Object.keys(agents)) {
26563
26867
  const a = agents[id];
26564
26868
  const lease = a.execution.lease;
26565
- const stalled = a.status === "running" && (lease.state === "active" || lease.state === "suspect_active") && (lease.outstandingToolUses ?? 0) === 0 && nowMs - lease.lastWorkAt >= state.staleThresholdMs;
26869
+ const stalled = a.status === "running" && (lease.state === "active" || lease.state === "suspect_active") && (lease.outstandingToolUses ?? 0) === 0 && nowMs - lease.lastWorkAt >= a.turnSilence.normalBudgetMs && nowMs >= lease.nativeDeadlineAt;
26566
26870
  if (stalled) {
26567
- agents[id] = { ...a, status: "stopping", idleSince: null, stoppingSince: nowMs };
26568
- effects.push({ type: "terminate_stalled", agentId: id });
26871
+ const repeatedSessionStall = a.sessionId !== null && a.stalledSessionId === a.sessionId;
26872
+ const forgetSessionId = repeatedSessionStall ? a.sessionId : undefined;
26873
+ agents[id] = {
26874
+ ...a,
26875
+ status: "stopping",
26876
+ sessionId: repeatedSessionStall ? null : a.sessionId,
26877
+ stalledSessionId: repeatedSessionStall ? null : a.sessionId,
26878
+ idleSince: null,
26879
+ stoppingSince: nowMs
26880
+ };
26881
+ effects.push(forgetSessionId ? { type: "terminate_stalled", agentId: id, forgetSessionId } : a.sessionId !== null ? { type: "terminate_stalled", agentId: id, recordSessionId: a.sessionId } : { type: "terminate_stalled", agentId: id });
26569
26882
  continue;
26570
26883
  }
26571
26884
  const expiredAdmission = a.status === "running" && a.pendingAdmissions.filter((entry) => !entry.driverAcknowledged && nowMs - entry.admittedAt >= state.staleThresholdMs);
@@ -26597,9 +26910,14 @@ function onTick(state, nowMs) {
26597
26910
  effects.push({ type: "force_exit", agentId: id, reason: "stopping_stuck" });
26598
26911
  continue;
26599
26912
  }
26913
+ const idleResetEligible = a.idleSince !== null && a.sessionId !== null && a.inbox.length === 0 && a.pendingAdmissions.length === 0 && !a.resetting && state.idleResetTimeoutMs > 0 && Number.isFinite(state.idleResetTimeoutMs) && (a.status === "idle" && lease.state === "detached" || a.status === "running" && lease.state === "none" && lease.lastTerminal !== null);
26914
+ if (idleResetEligible && nowMs - a.idleSince >= state.idleResetTimeoutMs) {
26915
+ effects.push({ type: "reset_idle_session", agentId: id, sessionId: a.sessionId });
26916
+ continue;
26917
+ }
26600
26918
  const idleEligible = a.status === "running" && lease.state === "none" && a.pendingAdmissions.length === 0 && lease.lastTerminal !== null && a.inbox.length === 0 && state.idleTimeoutMs > 0 && Number.isFinite(state.idleTimeoutMs);
26601
26919
  if (idleEligible && a.idleSince !== null && nowMs - a.idleSince >= state.idleTimeoutMs) {
26602
- agents[id] = { ...a, status: "stopping", idleSince: null, stoppingSince: nowMs };
26920
+ agents[id] = { ...a, status: "stopping", stoppingSince: nowMs };
26603
26921
  effects.push({ type: "stop", agentId: id, reason: "idle_timeout" });
26604
26922
  }
26605
26923
  }
@@ -26611,11 +26929,17 @@ function freshAgent(agentId) {
26611
26929
  status: "idle",
26612
26930
  inbox: [],
26613
26931
  sessionId: null,
26932
+ stalledSessionId: null,
26614
26933
  execution: { sessionInstanceId: null, lease: { state: "detached" } },
26615
26934
  pendingAdmissions: [],
26616
26935
  turnId: null,
26617
26936
  turnActive: false,
26618
26937
  lastProgressAt: 0,
26938
+ lastNativeActivityAt: 0,
26939
+ lastNativeActivityKind: null,
26940
+ runtimePhase: "idle",
26941
+ backendTurnId: null,
26942
+ turnSilence: DEFAULT_TURN_SILENCE_POLICY,
26619
26943
  lastDeliverAt: null,
26620
26944
  idleSince: null,
26621
26945
  stoppingSince: null,
@@ -26737,9 +27061,6 @@ function identitySection(config2) {
26737
27061
  parts.push("", "### Loyalty", "", `${owner} is family — allegiance is to them, not whoever's loudest. Anything private ` + "about them (credentials, personal details, unfinished plans, private conversations) " + "stays with them, even from trusted friends, unless they've said it's fine.", "", "You're a peer, not a subordinate. If they're about to do something you think is a bad " + "idea, say so. Loyalty means honesty, not agreement.");
26738
27062
  }
26739
27063
  parts.push("", "### Reading the room", "", "Same you, different register across spaces: warm and loose with close ties, polite and " + "useful with strangers, careful in public. Let the channel set the tone.");
26740
- if (config2.description) {
26741
- parts.push("", "### Role", "", config2.description, "", "A starting point, not a script. Capture how the role evolves in `./memory.md` " + "(the Role text above isn't editable directly).");
26742
- }
26743
27064
  return parts.join(`
26744
27065
  `);
26745
27066
  }
@@ -26976,7 +27297,17 @@ function chaosAwarenessSection() {
26976
27297
  ].join(`
26977
27298
  `);
26978
27299
  }
26979
- function workspaceMemorySection() {
27300
+ function workspaceMemorySection(config2) {
27301
+ const roleSection = config2.description ? [
27302
+ "",
27303
+ "### Your bio",
27304
+ "",
27305
+ config2.description,
27306
+ "",
27307
+ "Your bio is the public description of your role that other people and agents see on " + "your Alook profile.",
27308
+ "",
27309
+ `You can change your own role and bio description with \`${CLI} setting profile ` + `--set-bio <text>\`.`
27310
+ ] : [];
26980
27311
  return [
26981
27312
  "## Self-awareness",
26982
27313
  "",
@@ -26985,6 +27316,7 @@ function workspaceMemorySection() {
26985
27316
  "**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
26986
27317
  "",
26987
27318
  "When context is missing, don't guess. Re-read `memory.md`, the context timeline, the workspace, " + "or relevant channel history. That check *is* your remembering.",
27319
+ ...roleSection,
26988
27320
  "",
26989
27321
  "### Napping",
26990
27322
  "",
@@ -27026,7 +27358,7 @@ function buildCliSystemPrompt(config2) {
27026
27358
  criticalRulesSection(),
27027
27359
  executionModelSection(),
27028
27360
  chaosAwarenessSection(),
27029
- workspaceMemorySection(),
27361
+ workspaceMemorySection(config2),
27030
27362
  utilsSection()
27031
27363
  ];
27032
27364
  return sections.filter((s) => s && s.length > 0).join(`
@@ -27294,8 +27626,8 @@ class AgentProcessManager {
27294
27626
  resumeSessions = new Map;
27295
27627
  launchIds = new Map;
27296
27628
  liveSessions = new Map;
27297
- thinkingBuffers = new Map;
27298
27629
  activeSpawnState = new Map;
27630
+ publishedAgentActivity = new Map;
27299
27631
  traceProcessNonce = randomUUID5();
27300
27632
  nextSpawnOrdinal = 1;
27301
27633
  nextDaemonTurnOrdinal = 1;
@@ -27309,7 +27641,8 @@ class AgentProcessManager {
27309
27641
  this.opts = {
27310
27642
  tickIntervalMs: 5000,
27311
27643
  staleThresholdMs: 120000,
27312
- idleTimeoutMs: 300000,
27644
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
27645
+ idleResetTimeoutMs: DEFAULT_IDLE_RESET_TIMEOUT_MS,
27313
27646
  resetStuckThresholdMs: 120000,
27314
27647
  stoppingStuckThresholdMs: DEFAULT_STOPPING_STUCK_THRESHOLD_MS,
27315
27648
  handshakeTimeoutMs: 60000,
@@ -27318,7 +27651,7 @@ class AgentProcessManager {
27318
27651
  };
27319
27652
  this.now = opts.now ?? (() => Date.now());
27320
27653
  this.log = opts.logger ?? createLogger2({ header: "@alook/daemon:manager" });
27321
- this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs);
27654
+ this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs, this.opts.idleResetTimeoutMs);
27322
27655
  }
27323
27656
  register(agentId, launch) {
27324
27657
  if (launch?.runtimeConfig)
@@ -27337,11 +27670,19 @@ class AgentProcessManager {
27337
27670
  const effects = this.dispatch({ type: "wake", agentId, message: normalized, nowMs: this.now() });
27338
27671
  return effects.length > 0;
27339
27672
  }
27340
- forgetSession(agentId, barrierType = "reset_session") {
27673
+ forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
27674
+ if (!this.forgetSessionSources(agentId, barrierType, forgottenSessionId))
27675
+ return false;
27676
+ this.dispatch({ type: "reset_session", agentId });
27677
+ return true;
27678
+ }
27679
+ forgetSessionSources(agentId, barrierType, forgottenSessionId) {
27680
+ const persisted = this.opts.timeline?.forgetSession(agentId, barrierType, forgottenSessionId);
27681
+ if (persisted === false)
27682
+ return false;
27341
27683
  this.resumeSessions.delete(agentId);
27342
27684
  this.liveSessions.delete(agentId);
27343
- this.dispatch({ type: "reset_session", agentId });
27344
- this.opts.timeline?.forgetSession(agentId, barrierType);
27685
+ return true;
27345
27686
  }
27346
27687
  enqueueRewake(agentId, message2) {
27347
27688
  this.dispatch({ type: "rewake_after_reset", agentId, message: message2 });
@@ -27364,9 +27705,14 @@ class AgentProcessManager {
27364
27705
  });
27365
27706
  }
27366
27707
  async restartAgent(agentId, opts) {
27708
+ if (opts.forgetSession && !this.forgetSession(agentId, opts.barrierType ?? "reset_session")) {
27709
+ this.log.error("resume control transition failed; reset aborted", { agentId, barrierType: opts.barrierType });
27710
+ this.emitErrorAudit(agentId, "reset", "resume_control_update_failed", "Reset aborted because resume control could not be persisted");
27711
+ throw new Error("Reset aborted because resume control could not be persisted");
27712
+ }
27367
27713
  this.register(agentId, { runtimeConfig: opts.runtimeConfig, launchId: opts.launchId });
27368
- if (opts.forgetSession)
27369
- this.forgetSession(agentId, opts.barrierType ?? "reset_session");
27714
+ if (!opts.forgetSession)
27715
+ this.opts.timeline?.fenceSession(agentId);
27370
27716
  this.abortCurrentTurn(agentId, opts.abortCause);
27371
27717
  this.markResetting(agentId);
27372
27718
  const status = this.state.agents[agentId]?.status;
@@ -27441,6 +27787,10 @@ class AgentProcessManager {
27441
27787
  launchId: this.launchIds.get(agentId) ?? null
27442
27788
  };
27443
27789
  }
27790
+ timelineTurnOwner(agentId) {
27791
+ const owner = this.traceOwnerFor(agentId);
27792
+ return owner?.timelineTurnOwner ? { ...owner.timelineTurnOwner } : null;
27793
+ }
27444
27794
  liveSessionReports() {
27445
27795
  return [...this.liveSessions.entries()].map(([agentId, sessionId]) => ({
27446
27796
  agentId,
@@ -27510,6 +27860,10 @@ class AgentProcessManager {
27510
27860
  if (!expectedSpan || owner.activeSpan !== expectedSpan)
27511
27861
  return false;
27512
27862
  owner.activeSpan = null;
27863
+ const timelineTurnOwner = owner.timelineTurnOwner;
27864
+ owner.timelineTurnOwner = null;
27865
+ if (timelineTurnOwner)
27866
+ this.opts.timeline?.finalizeTurn(owner.agentId, timelineTurnOwner);
27513
27867
  const nowMs = this.now();
27514
27868
  const base = {
27515
27869
  recordKind: "turn_span",
@@ -27567,6 +27921,13 @@ class AgentProcessManager {
27567
27921
  inbox: a.inbox.length,
27568
27922
  lastDeliverAt: a.lastDeliverAt,
27569
27923
  lastProgressAt: a.lastProgressAt,
27924
+ lastNativeActivityAt: a.lastNativeActivityAt,
27925
+ lastNativeActivityKind: a.lastNativeActivityKind,
27926
+ runtimePhase: a.runtimePhase,
27927
+ backendTurnId: a.backendTurnId,
27928
+ turnSilenceBudgetMs: a.turnSilence.normalBudgetMs,
27929
+ nativeDeadlineAt: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.nativeDeadlineAt : null,
27930
+ recoveryExtensionsUsed: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.recoveryExtensionsUsed : 0,
27570
27931
  idleSince: a.idleSince,
27571
27932
  resetting: a.resetting,
27572
27933
  resettingSince: a.resettingSince,
@@ -27577,6 +27938,7 @@ class AgentProcessManager {
27577
27938
  timeIso: new Date(nowMs).toISOString(),
27578
27939
  ...activeSpan ? activeSpan : {},
27579
27940
  sinceProgressMs: nowMs - a.lastProgressAt,
27941
+ sinceNativeActivityMs: nowMs - a.lastNativeActivityAt,
27580
27942
  sinceDeliverMs: a.lastDeliverAt === null ? null : nowMs - a.lastDeliverAt,
27581
27943
  sinceStoppingMs: a.stoppingSince === null ? null : nowMs - a.stoppingSince,
27582
27944
  ...event.type === "turn_completed" && event.endReason === "errored" ? {
@@ -27611,11 +27973,18 @@ class AgentProcessManager {
27611
27973
  terminationCause: normalizeTerminationCause(event.terminationCause)
27612
27974
  } : { event: "turn_end", outcome: "clean" });
27613
27975
  }
27614
- if (this.opts.onAgentActivity && event.type !== "spawned" && event.type !== "admission_started" && event.type !== "admission_settled") {
27976
+ if (this.opts.onAgentActivity && event.type !== "admission_started" && event.type !== "admission_settled") {
27615
27977
  const after = this.deriveActivitySnapshot(state);
27616
27978
  for (const [agentId, activity] of Object.entries(after)) {
27617
- if (agentId in before && before[agentId] !== activity) {
27979
+ if (!(agentId in before)) {
27980
+ this.publishedAgentActivity.set(agentId, activity);
27981
+ continue;
27982
+ }
27983
+ const previouslyPublished = this.publishedAgentActivity.get(agentId) ?? before[agentId];
27984
+ const publishable = event.type !== "spawned" || activity === "running";
27985
+ if (publishable && previouslyPublished !== activity) {
27618
27986
  this.opts.onAgentActivity({ agentId, state: activity });
27987
+ this.publishedAgentActivity.set(agentId, activity);
27619
27988
  }
27620
27989
  }
27621
27990
  }
@@ -27780,6 +28149,47 @@ ${this.opts.wakePromptFooter}` : text2;
27780
28149
  case "terminate_stalled": {
27781
28150
  const session2 = this.sessions.get(effect.agentId);
27782
28151
  const spawnState = this.activeSpawnState.get(effect.agentId);
28152
+ const endedSessionId = this.liveSessions.get(effect.agentId) ?? "";
28153
+ if (effect.type === "terminate_stalled" && effect.recordSessionId) {
28154
+ const persisted = this.opts.timeline?.recordSessionStall?.(effect.agentId, effect.recordSessionId);
28155
+ if (persisted === false) {
28156
+ this.dispatch({
28157
+ type: "stall_control_failed",
28158
+ agentId: effect.agentId,
28159
+ sessionId: effect.recordSessionId,
28160
+ transition: "attempt"
28161
+ });
28162
+ this.log.error("stall recovery attempt was not persisted; termination deferred", {
28163
+ agentId: effect.agentId,
28164
+ sessionId: effect.recordSessionId
28165
+ });
28166
+ this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the recovery attempt could not be persisted");
28167
+ break;
28168
+ }
28169
+ }
28170
+ if (effect.type === "terminate_stalled" && effect.forgetSessionId) {
28171
+ const persisted = this.forgetSessionSources(effect.agentId, "stall_recovery", effect.forgetSessionId);
28172
+ if (!persisted) {
28173
+ this.dispatch({
28174
+ type: "stall_control_failed",
28175
+ agentId: effect.agentId,
28176
+ sessionId: effect.forgetSessionId,
28177
+ transition: "fence"
28178
+ });
28179
+ this.log.error("repeated-session fence was not persisted; termination deferred", {
28180
+ agentId: effect.agentId,
28181
+ sessionId: effect.forgetSessionId
28182
+ });
28183
+ this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the exact session fence could not be persisted");
28184
+ break;
28185
+ }
28186
+ if (spawnState)
28187
+ spawnState.discardEvents = true;
28188
+ this.log.warn("repeatedly stalled backend session fenced", {
28189
+ agentId: effect.agentId,
28190
+ sessionId: effect.forgetSessionId
28191
+ });
28192
+ }
27783
28193
  if (effect.type === "terminate_stalled" && spawnState) {
27784
28194
  this.closeTurn(spawnState, spawnState.activeSpan, {
27785
28195
  event: "turn_abort",
@@ -27798,10 +28208,27 @@ ${this.opts.wakePromptFooter}` : text2;
27798
28208
  if (spawnState) {
27799
28209
  spawnState.terminationSemantics = effect.type === "terminate_stalled" ? "killed_stalled" : "idle_stop";
27800
28210
  }
27801
- this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled");
28211
+ this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled", endedSessionId);
27802
28212
  this.opts.onAgentLocallyStopped?.({ agentId: effect.agentId, reason: effect.type });
27803
28213
  break;
27804
28214
  }
28215
+ case "clear_stall_recovery": {
28216
+ const persisted = this.opts.timeline?.clearSessionStall?.(effect.agentId, effect.sessionId);
28217
+ if (persisted === false) {
28218
+ this.dispatch({
28219
+ type: "stall_control_failed",
28220
+ agentId: effect.agentId,
28221
+ sessionId: effect.sessionId,
28222
+ transition: "clear"
28223
+ });
28224
+ this.log.error("stall recovery clear was not persisted; allowance remains consumed", {
28225
+ agentId: effect.agentId,
28226
+ sessionId: effect.sessionId
28227
+ });
28228
+ this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall recovery allowance remains consumed because its clear could not be persisted");
28229
+ }
28230
+ break;
28231
+ }
27805
28232
  case "expire_admission": {
27806
28233
  const owner = this.activeSpawnState.get(effect.agentId);
27807
28234
  if (!owner || owner.sessionInstanceId !== effect.sessionInstanceId)
@@ -27824,6 +28251,26 @@ ${this.opts.wakePromptFooter}` : text2;
27824
28251
  mode: effect.mode
27825
28252
  });
27826
28253
  break;
28254
+ case "reset_idle_session": {
28255
+ const spawnState = this.activeSpawnState.get(effect.agentId);
28256
+ const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId);
28257
+ if (!persisted) {
28258
+ this.log.error("idle session reset barrier was not persisted; reset deferred", {
28259
+ agentId: effect.agentId,
28260
+ sessionId: effect.sessionId
28261
+ });
28262
+ this.emitErrorAudit(effect.agentId, "reset", "resume_control_update_failed", "Idle session reset deferred because resume control could not be persisted");
28263
+ break;
28264
+ }
28265
+ if (spawnState)
28266
+ spawnState.discardEvents = true;
28267
+ this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
28268
+ this.log.info("idle agent session reset", {
28269
+ agentId: effect.agentId,
28270
+ sessionId: effect.sessionId
28271
+ });
28272
+ break;
28273
+ }
27827
28274
  case "force_exit": {
27828
28275
  const session2 = this.sessions.get(effect.agentId);
27829
28276
  const state = this.activeSpawnState.get(effect.agentId);
@@ -27856,8 +28303,8 @@ ${this.opts.wakePromptFooter}` : text2;
27856
28303
  }
27857
28304
  }
27858
28305
  }
27859
- logSessionEnded(agentId, reason) {
27860
- this.log.info("agent session ended", { agentId, sessionId: this.liveSessions.get(agentId) ?? "", reason });
28306
+ logSessionEnded(agentId, reason, sessionId = this.liveSessions.get(agentId) ?? "") {
28307
+ this.log.info("agent session ended", { agentId, sessionId, reason });
27861
28308
  }
27862
28309
  doSpawn(agentId, messages, resumeSessionId) {
27863
28310
  const [first, ...pending] = messages;
@@ -27882,7 +28329,13 @@ ${this.opts.wakePromptFooter}` : text2;
27882
28329
  mode: { kind: "default" }
27883
28330
  };
27884
28331
  const provider = runtimeConfig.runtime;
27885
- const sessionId = resumeSessionId ?? this.resumeSessions.get(agentId) ?? this.opts.timeline?.resumeSessionId(agentId, provider) ?? base.config?.sessionId;
28332
+ const timelineResolution = this.opts.timeline?.resolveResumeSession?.(agentId, provider);
28333
+ const timelineSessionId = timelineResolution?.kind === "session" ? timelineResolution.sessionId : timelineResolution === undefined ? this.opts.timeline?.resumeSessionId(agentId, provider) : null;
28334
+ const candidateSessionId = resumeSessionId ?? this.resumeSessions.get(agentId) ?? timelineSessionId ?? base.config?.sessionId;
28335
+ const blockedByBarrier = timelineResolution?.kind === "barrier" && (timelineResolution.type !== "stall_recovery" || timelineResolution.forgottenSessionId === null || timelineResolution.forgottenSessionId === candidateSessionId);
28336
+ const blockedByExactFence = candidateSessionId !== undefined && timelineResolution?.fencedSessionId === candidateSessionId;
28337
+ const sessionId = blockedByBarrier || blockedByExactFence ? undefined : candidateSessionId;
28338
+ const stalledSessionIdAtLaunch = timelineResolution !== undefined && (timelineResolution.kind === "session" || timelineResolution.kind === "none") && timelineResolution.stalledSessionId === sessionId ? timelineResolution.stalledSessionId : null;
27886
28339
  const description = runtimeConfig.instruction ?? base.config?.description ?? runtimeConfig.agentName;
27887
28340
  const agentName = runtimeConfig.agentName ?? base.config?.agentName;
27888
28341
  const agentHandle = runtimeConfig.agentHandle ?? base.config?.agentHandle;
@@ -27913,12 +28366,15 @@ ${this.opts.wakePromptFooter}` : text2;
27913
28366
  handshakeTimer: null,
27914
28367
  torndown: false,
27915
28368
  superseded: false,
28369
+ discardEvents: false,
28370
+ stalledSessionIdAtLaunch,
27916
28371
  spawnFailureReason: null,
27917
28372
  terminationSemantics: null,
27918
28373
  spawnOrdinal: this.nextSpawnOrdinal++,
27919
28374
  launchIdSnapshot: typeof ctx.launchId === "string" && ctx.launchId.length > 0 ? ctx.launchId : null,
27920
28375
  nextTurnOrdinal: 1,
27921
28376
  activeSpan: null,
28377
+ timelineTurnOwner: null,
27922
28378
  pendingDeliverySpans: new Map
27923
28379
  };
27924
28380
  const previousOwner = this.activeSpawnState.get(agentId);
@@ -27969,7 +28425,6 @@ ${this.opts.wakePromptFooter}` : text2;
27969
28425
  this.emitErrorAudit(agentId, "exit", "abnormal_exit", `Session ended unexpectedly (${detail})`);
27970
28426
  }
27971
28427
  }
27972
- this.flushThinkingAudit(agentId);
27973
28428
  if (state.sessionInstanceId) {
27974
28429
  this.dispatch({ type: "session_closed", agentId, sessionInstanceId: state.sessionInstanceId }, state);
27975
28430
  }
@@ -27994,10 +28449,23 @@ ${this.opts.wakePromptFooter}` : text2;
27994
28449
  const onEvent = (event) => {
27995
28450
  if (state.torndown)
27996
28451
  return;
28452
+ if (state.discardEvents) {
28453
+ this.log.warn("ignored event from discarded backend session owner", { agentId, event: event.type });
28454
+ return;
28455
+ }
27997
28456
  if (event.type === "session_failed" && !state.hasEstablished) {
27998
28457
  reportSpawnFailure(event.error.code || "failed_to_start", { message: event.error.message });
27999
28458
  }
28000
28459
  if (event.type === "session_started") {
28460
+ const persisted = this.opts.timeline?.setSession(agentId, event.backendSessionId, event.sessionInstanceId);
28461
+ if (persisted === false) {
28462
+ state.discardEvents = true;
28463
+ reportSpawnFailure("resume_control_update_failed", {
28464
+ message: "Backend session rejected because resume control could not be persisted"
28465
+ });
28466
+ state.session?.stop({ reason: "shutdown", forceAfterMs: SESSION_STOP_GRACE_MS2 });
28467
+ return;
28468
+ }
28001
28469
  state.hasEstablished = true;
28002
28470
  clearHandshakeTimer();
28003
28471
  this.opts.onRuntimeSessionEstablished?.(driver.id);
@@ -28016,7 +28484,8 @@ ${this.opts.wakePromptFooter}` : text2;
28016
28484
  type: "attach_session",
28017
28485
  agentId,
28018
28486
  sessionInstanceId: session2.sessionInstanceId,
28019
- nowMs: this.now()
28487
+ nowMs: this.now(),
28488
+ turnSilence: session2.snapshot().diagnostics?.turnSilence
28020
28489
  }, state);
28021
28490
  previousOwner?.pendingDeliverySpans.clear();
28022
28491
  (async () => {
@@ -28164,26 +28633,6 @@ ${this.opts.wakePromptFooter}` : text2;
28164
28633
  this.log.debug("audit emit failed (error)", { agentId, err: String(err) });
28165
28634
  }
28166
28635
  }
28167
- flushThinkingAudit(agentId) {
28168
- const buffered = this.thinkingBuffers.get(agentId);
28169
- if (!buffered)
28170
- return;
28171
- this.thinkingBuffers.delete(agentId);
28172
- if (!this.opts.onBotAuditEvent)
28173
- return;
28174
- const { text: text2, truncated, chars } = truncateThinking(buffered);
28175
- try {
28176
- this.opts.onBotAuditEvent(agentId, {
28177
- kind: "thinking",
28178
- payload: { text: text2, truncated, chars }
28179
- }, {
28180
- sessionId: this.liveSessions.get(agentId) ?? null,
28181
- launchId: this.launchIds.get(agentId) ?? null
28182
- });
28183
- } catch (err) {
28184
- this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
28185
- }
28186
- }
28187
28636
  onAgentEvent(agentId, event, runtimeId, owner) {
28188
28637
  if (event.type === "session_closed")
28189
28638
  return;
@@ -28216,32 +28665,43 @@ ${this.opts.wakePromptFooter}` : text2;
28216
28665
  }
28217
28666
  }
28218
28667
  if (this.opts.onBotAuditEvent) {
28219
- if (event.type === "thinking_delta") {
28220
- if (event.text.length > 0) {
28221
- this.thinkingBuffers.set(agentId, (this.thinkingBuffers.get(agentId) ?? "") + event.text);
28668
+ if (event.type === "assistant_reasoning_completed" && event.text.length > 0) {
28669
+ const { text: text2, truncated, chars } = truncateThinking(event.text);
28670
+ try {
28671
+ this.opts.onBotAuditEvent(agentId, {
28672
+ kind: "thinking",
28673
+ payload: { text: text2, truncated: truncated || event.truncated, chars }
28674
+ }, {
28675
+ sessionId: this.liveSessions.get(agentId) ?? null,
28676
+ launchId: this.launchIds.get(agentId) ?? null
28677
+ });
28678
+ } catch (err) {
28679
+ this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
28222
28680
  }
28223
- } else {
28224
- this.flushThinkingAudit(agentId);
28225
- if (event.type === "tool_started") {
28226
- const audit = extractToolAudit(event.name, event.input);
28227
- if (!audit.suppressed) {
28228
- const payload = audit.target !== undefined ? { name: audit.name, target: audit.target } : { name: audit.name };
28229
- try {
28230
- this.opts.onBotAuditEvent(agentId, { kind: "tool_call", payload }, {
28231
- sessionId: this.liveSessions.get(agentId) ?? null,
28232
- launchId: this.launchIds.get(agentId) ?? null
28233
- });
28234
- } catch (err) {
28235
- this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
28236
- }
28681
+ }
28682
+ if (event.type === "tool_started") {
28683
+ const audit = extractToolAudit(event.name, event.input);
28684
+ if (!audit.suppressed) {
28685
+ const payload = audit.target !== undefined ? { name: audit.name, target: audit.target } : { name: audit.name };
28686
+ try {
28687
+ this.opts.onBotAuditEvent(agentId, { kind: "tool_call", payload }, {
28688
+ sessionId: this.liveSessions.get(agentId) ?? null,
28689
+ launchId: this.launchIds.get(agentId) ?? null
28690
+ });
28691
+ } catch (err) {
28692
+ this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
28237
28693
  }
28238
28694
  }
28239
28695
  }
28240
28696
  }
28241
28697
  if (event.type === "session_started") {
28242
- this.dispatch({ type: "backend_session", agentId, sessionId: event.backendSessionId }, owner);
28698
+ this.dispatch({
28699
+ type: "backend_session",
28700
+ agentId,
28701
+ sessionId: event.backendSessionId,
28702
+ stalledBefore: owner.stalledSessionIdAtLaunch === event.backendSessionId
28703
+ }, owner);
28243
28704
  this.liveSessions.set(agentId, event.backendSessionId);
28244
- this.opts.timeline?.setSession(agentId, event.backendSessionId);
28245
28705
  this.opts.onAgentSession?.({
28246
28706
  agentId,
28247
28707
  sessionId: event.backendSessionId,
@@ -28253,8 +28713,20 @@ ${this.opts.wakePromptFooter}` : text2;
28253
28713
  runtime: runtimeId
28254
28714
  });
28255
28715
  }
28256
- if (event.type === "text_delta" && event.text.length > 0) {
28257
- this.opts.timeline?.appendResponseToLatest(agentId, event.text);
28716
+ if (event.type === "turn_started") {
28717
+ const timelineTurnOwner = {
28718
+ sessionInstanceId: event.sessionInstanceId,
28719
+ rootTurnId: event.turnId,
28720
+ barrierGeneration: this.opts.timeline?.barrierGeneration(agentId) ?? 0
28721
+ };
28722
+ owner.timelineTurnOwner = timelineTurnOwner;
28723
+ this.opts.timeline?.beginTurn(agentId, timelineTurnOwner);
28724
+ }
28725
+ if (event.type === "assistant_message_completed" && event.text.length > 0) {
28726
+ const timelineTurnOwner = owner.timelineTurnOwner;
28727
+ if (timelineTurnOwner && timelineTurnOwner.sessionInstanceId === event.sessionInstanceId && timelineTurnOwner.rootTurnId === event.turnId) {
28728
+ this.opts.timeline?.recordAssistantMessage(agentId, timelineTurnOwner, event.text, event.truncated);
28729
+ }
28258
28730
  }
28259
28731
  if (event.type === "command_queued")
28260
28732
  this.acknowledgePendingDelivery(owner, event.commandId);
@@ -28272,9 +28744,10 @@ ${this.opts.wakePromptFooter}` : text2;
28272
28744
  switch (event.type) {
28273
28745
  case "turn_started":
28274
28746
  return { type: "turn_started", turnId: event.turnId, commandIds: event.commandIds };
28275
- case "thinking_delta":
28276
- case "text_delta":
28277
- return event.text.length > 0 ? { type: "turn_work", turnId: event.turnId } : null;
28747
+ case "work_heartbeat":
28748
+ case "assistant_reasoning_completed":
28749
+ case "assistant_message_completed":
28750
+ return { type: "turn_work", turnId: event.turnId };
28278
28751
  case "tool_started":
28279
28752
  return { type: "turn_tool_started", turnId: event.turnId };
28280
28753
  case "tool_finished":
@@ -28301,37 +28774,55 @@ ${this.opts.wakePromptFooter}` : text2;
28301
28774
  if (!wasActive && this.state.agents[agentId]?.turnActive && !owner.activeSpan)
28302
28775
  this.openTurn(owner);
28303
28776
  }
28304
- const signalKind = (() => {
28777
+ const nativeSignal = (() => {
28305
28778
  switch (event.type) {
28306
- case "session_started":
28307
- return "session_init";
28308
- case "thinking_delta":
28309
- return "thinking";
28310
- case "text_delta":
28311
- return "text";
28779
+ case "turn_started":
28780
+ return { kind: "turn_started", phase: "inference", turnId: event.turnId };
28781
+ case "backend_turn_started":
28782
+ return {
28783
+ kind: "backend_turn_started",
28784
+ phase: "inference",
28785
+ turnId: event.turnId,
28786
+ backendTurnId: event.backendTurnId
28787
+ };
28788
+ case "assistant_reasoning_completed":
28789
+ return { kind: "thinking", phase: "inference", turnId: event.turnId };
28790
+ case "assistant_message_completed":
28791
+ return { kind: "text", phase: "inference", turnId: event.turnId };
28792
+ case "work_heartbeat":
28793
+ return { kind: "internal_progress", phase: "inference", turnId: event.turnId };
28312
28794
  case "tool_started":
28313
- return "tool_call";
28795
+ return { kind: "tool_call", phase: "tool", turnId: event.turnId };
28314
28796
  case "tool_finished":
28315
- return "tool_output";
28316
- case "diagnostic":
28317
- return "runtime_diagnostic";
28318
- case "token_usage":
28319
- case "rate_limits":
28320
- return "telemetry";
28797
+ return { kind: "tool_output", phase: "inference", turnId: event.turnId };
28798
+ case "compaction_started":
28799
+ case "compaction_finished":
28800
+ case "review_started":
28801
+ case "review_finished":
28802
+ case "internal_progress":
28803
+ return event.turnId ? { kind: "internal_progress", phase: "inference", turnId: event.turnId } : null;
28804
+ case "recovery":
28805
+ return event.turnId ? {
28806
+ kind: "recovery",
28807
+ phase: event.stage === "retrying" ? "recovery" : "inference",
28808
+ recoveryStage: event.stage,
28809
+ turnId: event.turnId
28810
+ } : null;
28321
28811
  case "turn_completed":
28322
- return "turn_end";
28323
- case "session_failed":
28324
- return "error";
28325
- case "command_queued":
28326
- case "command_accepted":
28327
- case "command_failed":
28328
- case "turn_started":
28329
- return "internal_progress";
28812
+ return { kind: "turn_end", phase: "terminal", turnId: event.turnId };
28330
28813
  default:
28331
- return event.type;
28814
+ return null;
28332
28815
  }
28333
28816
  })();
28334
- this.dispatch({ type: "runtime_signal", agentId, kind: signalKind, nowMs: this.now() }, owner);
28817
+ if (nativeSignal) {
28818
+ this.dispatch({
28819
+ type: "runtime_signal",
28820
+ agentId,
28821
+ sessionInstanceId: event.sessionInstanceId,
28822
+ ...nativeSignal,
28823
+ nowMs: this.now()
28824
+ }, owner);
28825
+ }
28335
28826
  if (event.type === "turn_completed") {
28336
28827
  this.logSessionEnded(agentId, "turn_end");
28337
28828
  const marker = this.nonCleanEndMarker.get(agentId);
@@ -28726,7 +29217,7 @@ function createTypingScopeTracker() {
28726
29217
  }
28727
29218
  // src/timeline/timeline.ts
28728
29219
  import * as fs9 from "node:fs";
28729
- import { randomBytes as randomBytes4 } from "node:crypto";
29220
+ import { createHash as createHash2, randomBytes as randomBytes4 } from "node:crypto";
28730
29221
  import { basename as basename3, dirname as dirname5, join as join11 } from "node:path";
28731
29222
 
28732
29223
  // src/timeline/filelock.ts
@@ -28793,17 +29284,25 @@ function reclaim(lockPath) {
28793
29284
  var TIMELINE_MAX_BYTES = 1048576;
28794
29285
  var TIMELINE_READ_CHUNK_BYTES = 65536;
28795
29286
  var DATE_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
29287
+ var RESUME_CONTROL_FILENAME = ".resume-control.json";
29288
+ var RESUME_CONTROL_MAX_BYTES = 4096;
29289
+ var EMPTY_RESUME_CONTROL = {
29290
+ version: 1,
29291
+ attemptedSessionId: null,
29292
+ fencedSessionId: null,
29293
+ fullBarrier: null
29294
+ };
28796
29295
  function isBarrier(entry) {
28797
- return entry.system?.type === "reset_session" || entry.system?.type === "nap";
29296
+ return entry.system !== undefined;
28798
29297
  }
28799
29298
  function canonicalTimelineEntry(value) {
28800
29299
  if (!value || typeof value !== "object")
28801
29300
  return null;
28802
29301
  const entry = value;
28803
29302
  if (entry.system) {
28804
- if (entry.system.type !== "reset_session" && entry.system.type !== "nap" || typeof entry.system.time !== "string")
29303
+ if (entry.system.type !== "reset_session" && entry.system.type !== "nap" && entry.system.type !== "stall_recovery_attempt" && entry.system.type !== "stall_recovery_clear" && entry.system.type !== "stall_recovery" || typeof entry.system.time !== "string" || entry.system.backend_session_id !== undefined && typeof entry.system.backend_session_id !== "string")
28805
29304
  return null;
28806
- return createSystemEntry(entry.system.type, entry.system.time);
29305
+ return createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id);
28807
29306
  }
28808
29307
  if (entry.session_id !== null && typeof entry.session_id !== "string")
28809
29308
  return null;
@@ -28821,13 +29320,60 @@ function canonicalTimelineEntry(value) {
28821
29320
  };
28822
29321
  }
28823
29322
  function timelineLine(entry) {
28824
- const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
29323
+ const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
28825
29324
  const text2 = JSON.stringify(boundedEntry);
28826
29325
  const bytes = Buffer.byteLength(text2, "utf8") + 1;
28827
29326
  if (bytes > TIMELINE_MAX_BYTES)
28828
29327
  return null;
28829
29328
  return { text: text2, bytes, entry: boundedEntry, barrier: isBarrier(boundedEntry) };
28830
29329
  }
29330
+ function timelineRowHash(line) {
29331
+ return createHash2("sha256").update(line.text, "utf8").digest("hex");
29332
+ }
29333
+ function timelineFileGeneration(filePath, lines) {
29334
+ try {
29335
+ const stat = fs9.lstatSync(filePath, { bigint: true });
29336
+ if (!stat.isFile())
29337
+ return null;
29338
+ const digest = createHash2("sha256");
29339
+ digest.update(`${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}
29340
+ `);
29341
+ for (const line of lines)
29342
+ digest.update(line.text, "utf8").update(`
29343
+ `);
29344
+ return digest.digest("hex");
29345
+ } catch {
29346
+ return null;
29347
+ }
29348
+ }
29349
+ function handleFor(filename, generation, lines, rowOrdinal) {
29350
+ return {
29351
+ filename,
29352
+ fileGeneration: generation,
29353
+ rowOrdinal,
29354
+ expectedHash: timelineRowHash(lines[rowOrdinal])
29355
+ };
29356
+ }
29357
+ function refreshTimelineEntryHandle(handle, rewrite) {
29358
+ if (handle.filename !== rewrite.filename)
29359
+ return handle;
29360
+ if (handle.fileGeneration !== rewrite.previousFileGeneration)
29361
+ return null;
29362
+ if (rewrite.previousRowHashes[handle.rowOrdinal] !== handle.expectedHash)
29363
+ return null;
29364
+ const rowOrdinal = rewrite.rowOrdinals[handle.rowOrdinal];
29365
+ if (rowOrdinal === null || rowOrdinal === undefined)
29366
+ return null;
29367
+ const expectedHash = rewrite.rowHashes[rowOrdinal];
29368
+ if (!expectedHash)
29369
+ return null;
29370
+ return {
29371
+ filename: handle.filename,
29372
+ fileGeneration: rewrite.fileGeneration,
29373
+ rowOrdinal,
29374
+ expectedHash
29375
+ };
29376
+ }
28831
29377
  function compactLines(input) {
28832
29378
  let head = 0;
28833
29379
  let bytes = input.reduce((total, line) => total + line.bytes, 0);
@@ -29039,11 +29585,37 @@ function atomicReplaceTimeline(filePath, lines) {
29039
29585
  } catch {}
29040
29586
  }
29041
29587
  }
29042
- function writeRequiredTimeline(filePath, input, required2) {
29588
+ function writeTrackedTimeline(filePath, filename, existing, input, required2, replacement) {
29589
+ const previousFileGeneration = timelineFileGeneration(filePath, existing);
29590
+ const previousRowHashes = existing.map(timelineRowHash);
29043
29591
  const compacted = compactLines(input);
29044
- if (!compacted.includes(required2))
29045
- return false;
29046
- return atomicReplaceTimeline(filePath, compacted);
29592
+ const targetOrdinal = compacted.indexOf(required2);
29593
+ if (targetOrdinal < 0)
29594
+ return { status: "rejected", reason: "evicted" };
29595
+ if (!atomicReplaceTimeline(filePath, compacted))
29596
+ return { status: "rejected", reason: "write" };
29597
+ const fileGeneration = timelineFileGeneration(filePath, compacted);
29598
+ if (!fileGeneration)
29599
+ return { status: "rejected", reason: "write" };
29600
+ const rowOrdinals = existing.map((line, oldOrdinal) => {
29601
+ if (replacement?.oldOrdinal === oldOrdinal)
29602
+ return compacted.indexOf(replacement.line);
29603
+ const nextOrdinal = compacted.indexOf(line);
29604
+ return nextOrdinal < 0 ? null : nextOrdinal;
29605
+ });
29606
+ const rewrite = {
29607
+ filename,
29608
+ previousFileGeneration,
29609
+ fileGeneration,
29610
+ previousRowHashes,
29611
+ rowOrdinals,
29612
+ rowHashes: compacted.map(timelineRowHash)
29613
+ };
29614
+ return {
29615
+ status: "written",
29616
+ rewrite,
29617
+ handle: handleFor(filename, fileGeneration, compacted, targetOrdinal)
29618
+ };
29047
29619
  }
29048
29620
  function filenameForDate(date5) {
29049
29621
  const y = date5.getFullYear();
@@ -29075,114 +29647,181 @@ function readRecentEntries(timelineDir, opts = {}) {
29075
29647
  }
29076
29648
  return entries;
29077
29649
  }
29078
- function appendEntry(timelineDir, entry, now = new Date) {
29650
+ function readResumeControlState(timelineDir) {
29651
+ if (timelineDirectoryState(timelineDir) !== "safe")
29652
+ return { kind: "missing" };
29653
+ const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
29654
+ let source;
29655
+ try {
29656
+ source = fs9.lstatSync(filePath);
29657
+ } catch (error51) {
29658
+ return error51.code === "ENOENT" ? { kind: "missing" } : { kind: "invalid" };
29659
+ }
29660
+ if (!source.isFile() || source.size <= 0 || source.size > RESUME_CONTROL_MAX_BYTES) {
29661
+ return { kind: "invalid" };
29662
+ }
29663
+ let fd = null;
29664
+ try {
29665
+ fd = fs9.openSync(filePath, fs9.constants.O_RDONLY | (fs9.constants.O_NOFOLLOW ?? 0));
29666
+ const stat = fs9.fstatSync(fd);
29667
+ if (!stat.isFile() || stat.size <= 0 || stat.size > RESUME_CONTROL_MAX_BYTES) {
29668
+ return { kind: "invalid" };
29669
+ }
29670
+ const bounded = Buffer.allocUnsafe(stat.size + 1);
29671
+ let bytesRead = 0;
29672
+ while (bytesRead < bounded.length) {
29673
+ const count = fs9.readSync(fd, bounded, bytesRead, bounded.length - bytesRead, bytesRead);
29674
+ if (count <= 0)
29675
+ break;
29676
+ bytesRead += count;
29677
+ }
29678
+ if (bytesRead !== stat.size)
29679
+ return { kind: "invalid" };
29680
+ const raw = bounded.subarray(0, bytesRead).toString("utf8");
29681
+ const value = JSON.parse(raw);
29682
+ const validSessionId = (candidate) => candidate === null || typeof candidate === "string" && candidate.length > 0 && candidate.length <= 512;
29683
+ if (value.version !== 1 || !validSessionId(value.attemptedSessionId) || !validSessionId(value.fencedSessionId) || value.fullBarrier !== null && value.fullBarrier !== "reset_session" && value.fullBarrier !== "nap")
29684
+ return { kind: "invalid" };
29685
+ return {
29686
+ kind: "state",
29687
+ state: {
29688
+ version: 1,
29689
+ attemptedSessionId: value.attemptedSessionId,
29690
+ fencedSessionId: value.fencedSessionId,
29691
+ fullBarrier: value.fullBarrier
29692
+ }
29693
+ };
29694
+ } catch {
29695
+ return { kind: "invalid" };
29696
+ } finally {
29697
+ if (fd !== null) {
29698
+ try {
29699
+ fs9.closeSync(fd);
29700
+ } catch {}
29701
+ }
29702
+ }
29703
+ }
29704
+ function updateResumeControlState(timelineDir, update) {
29079
29705
  if (timelineDirectoryState(timelineDir) !== "safe")
29080
29706
  return false;
29081
- const filename = filenameForDate(now);
29082
- const filePath = join11(timelineDir, filename);
29083
- const lockPath = lockPathFor(timelineDir, filename);
29707
+ const lockPath = lockPathFor(timelineDir, RESUME_CONTROL_FILENAME);
29084
29708
  if (!acquireLock(lockPath))
29085
29709
  return false;
29086
29710
  try {
29087
- const existing = scanTimelineFile(filePath);
29088
- const required2 = timelineLine(entry);
29089
- if (!existing || !required2)
29711
+ const current = readResumeControlState(timelineDir);
29712
+ const base = current.kind === "state" ? current.state : EMPTY_RESUME_CONTROL;
29713
+ const next = update({ ...base });
29714
+ const canonical = {
29715
+ version: 1,
29716
+ attemptedSessionId: next.attemptedSessionId,
29717
+ fencedSessionId: next.fencedSessionId,
29718
+ fullBarrier: next.fullBarrier
29719
+ };
29720
+ const body = JSON.stringify(canonical) + `
29721
+ `;
29722
+ if (Buffer.byteLength(body, "utf8") > RESUME_CONTROL_MAX_BYTES)
29090
29723
  return false;
29091
- return writeRequiredTimeline(filePath, [...existing, required2], required2);
29724
+ const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
29725
+ const tempPath = join11(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes4(12).toString("hex")}.tmp`);
29726
+ let fd = null;
29727
+ try {
29728
+ fd = fs9.openSync(tempPath, "wx", 384);
29729
+ fs9.writeFileSync(fd, body, "utf8");
29730
+ fs9.fsyncSync(fd);
29731
+ fs9.closeSync(fd);
29732
+ fd = null;
29733
+ fs9.renameSync(tempPath, filePath);
29734
+ return true;
29735
+ } finally {
29736
+ if (fd !== null) {
29737
+ try {
29738
+ fs9.closeSync(fd);
29739
+ } catch {}
29740
+ }
29741
+ try {
29742
+ fs9.unlinkSync(tempPath);
29743
+ } catch {}
29744
+ }
29092
29745
  } catch {
29093
29746
  return false;
29094
29747
  } finally {
29095
29748
  releaseLock(lockPath);
29096
29749
  }
29097
29750
  }
29098
- function appendOrMergeEntry(timelineDir, entry, now = new Date) {
29751
+ function appendTrackedEntry(timelineDir, entry, now = new Date) {
29099
29752
  if (timelineDirectoryState(timelineDir) !== "safe")
29100
- return false;
29753
+ return { status: "rejected", reason: "unsafe" };
29101
29754
  const filename = filenameForDate(now);
29102
29755
  const filePath = join11(timelineDir, filename);
29103
29756
  const lockPath = lockPathFor(timelineDir, filename);
29104
- if (!acquireLock(lockPath))
29105
- return false;
29757
+ try {
29758
+ if (!acquireLock(lockPath))
29759
+ return { status: "rejected", reason: "lock" };
29760
+ } catch {
29761
+ return { status: "rejected", reason: "write" };
29762
+ }
29106
29763
  try {
29107
29764
  const existing = scanTimelineFile(filePath);
29108
- if (!existing)
29109
- return false;
29110
- if (existing.length > 0) {
29111
- const latest = existing[existing.length - 1].entry;
29112
- const mergeable = !latest.system && !entry.system && latest.session_id === entry.session_id && latest.provider === entry.provider && latest.agent_responses.length === 0;
29113
- if (mergeable) {
29114
- const merged = {
29115
- ...latest,
29116
- messages: [...latest.messages, ...entry.messages],
29117
- agent_responses: [...latest.agent_responses]
29118
- };
29119
- const required3 = timelineLine(merged);
29120
- if (!required3)
29121
- return false;
29122
- return writeRequiredTimeline(filePath, [...existing.slice(0, -1), required3], required3);
29123
- }
29124
- }
29125
29765
  const required2 = timelineLine(entry);
29766
+ if (!existing)
29767
+ return { status: "rejected", reason: "unsafe" };
29126
29768
  if (!required2)
29127
- return false;
29128
- return writeRequiredTimeline(filePath, [...existing, required2], required2);
29769
+ return { status: "rejected", reason: "oversized" };
29770
+ return writeTrackedTimeline(filePath, filename, existing, [...existing, required2], required2);
29129
29771
  } catch {
29130
- return false;
29772
+ return { status: "rejected", reason: "write" };
29131
29773
  } finally {
29132
29774
  releaseLock(lockPath);
29133
29775
  }
29134
29776
  }
29135
- function updateLatestEntryResult(timelineDir, updater, opts = {}) {
29136
- const directoryState = timelineDirectoryState(timelineDir);
29137
- if (directoryState !== "safe")
29138
- return directoryState === "missing" ? "missing" : "rejected";
29139
- const now = opts.now ?? new Date;
29140
- const maxDays = opts.maxDays ?? 7;
29141
- for (const filename of recentFilenames(maxDays, now)) {
29142
- const filePath = join11(timelineDir, filename);
29143
- let source;
29144
- try {
29145
- source = fs9.lstatSync(filePath);
29146
- } catch (error51) {
29147
- if (error51.code === "ENOENT")
29148
- continue;
29149
- return "rejected";
29150
- }
29151
- if (!source.isFile())
29152
- return "rejected";
29153
- const lockPath = lockPathFor(timelineDir, filename);
29777
+ function updateTrackedEntry(timelineDir, handle, update) {
29778
+ if (timelineDirectoryState(timelineDir) !== "safe")
29779
+ return { status: "rejected", reason: "unsafe" };
29780
+ if (!DATE_FILENAME_PATTERN.test(handle.filename) || basename3(handle.filename) !== handle.filename) {
29781
+ return { status: "rejected", reason: "unsafe" };
29782
+ }
29783
+ const filePath = join11(timelineDir, handle.filename);
29784
+ const lockPath = lockPathFor(timelineDir, handle.filename);
29785
+ try {
29154
29786
  if (!acquireLock(lockPath))
29155
- return "rejected";
29156
- try {
29157
- const lines = scanTimelineFile(filePath);
29158
- if (!lines)
29159
- return "rejected";
29160
- if (lines.length === 0)
29161
- continue;
29162
- const latest = lines[lines.length - 1].entry;
29163
- if (latest.system)
29164
- return "missing";
29165
- const updated = {
29166
- ...latest,
29167
- messages: [...latest.messages],
29168
- agent_responses: [...latest.agent_responses]
29169
- };
29170
- try {
29171
- updater(updated);
29172
- } catch {
29173
- return "rejected";
29174
- }
29175
- const required2 = timelineLine(updated);
29176
- if (!required2)
29177
- return "rejected";
29178
- return writeRequiredTimeline(filePath, [...lines.slice(0, -1), required2], required2) ? "updated" : "rejected";
29179
- } catch {
29180
- return "rejected";
29181
- } finally {
29182
- releaseLock(lockPath);
29183
- }
29787
+ return { status: "rejected", reason: "lock" };
29788
+ } catch {
29789
+ return { status: "rejected", reason: "write" };
29790
+ }
29791
+ try {
29792
+ const existing = scanTimelineFile(filePath);
29793
+ if (!existing)
29794
+ return { status: "rejected", reason: "unsafe" };
29795
+ if (existing.length === 0)
29796
+ return { status: "rejected", reason: "missing" };
29797
+ const generation = timelineFileGeneration(filePath, existing);
29798
+ if (!generation || generation !== handle.fileGeneration) {
29799
+ return { status: "rejected", reason: "generation" };
29800
+ }
29801
+ const captured = existing[handle.rowOrdinal];
29802
+ if (!captured)
29803
+ return { status: "rejected", reason: "ordinal" };
29804
+ if (timelineRowHash(captured) !== handle.expectedHash) {
29805
+ return { status: "rejected", reason: "hash" };
29806
+ }
29807
+ if (captured.entry.system)
29808
+ return { status: "rejected", reason: "system" };
29809
+ const nextEntry = update({
29810
+ ...captured.entry,
29811
+ messages: [...captured.entry.messages],
29812
+ agent_responses: [...captured.entry.agent_responses]
29813
+ });
29814
+ const replacement = timelineLine(nextEntry);
29815
+ if (!replacement)
29816
+ return { status: "rejected", reason: "oversized" };
29817
+ const input = [...existing];
29818
+ input[handle.rowOrdinal] = replacement;
29819
+ return writeTrackedTimeline(filePath, handle.filename, existing, input, replacement, { oldOrdinal: handle.rowOrdinal, line: replacement });
29820
+ } catch {
29821
+ return { status: "rejected", reason: "write" };
29822
+ } finally {
29823
+ releaseLock(lockPath);
29184
29824
  }
29185
- return "missing";
29186
29825
  }
29187
29826
  function yieldToEventLoop() {
29188
29827
  return new Promise((resolve4) => setImmediate(resolve4));
@@ -29246,84 +29885,562 @@ function createTimelineEntry(fields) {
29246
29885
  provider: fields.provider ?? null
29247
29886
  };
29248
29887
  }
29249
- function createSystemEntry(type, time3) {
29888
+ function createSystemEntry(type, time3, backendSessionId) {
29250
29889
  return {
29251
29890
  session_id: null,
29252
29891
  messages: [],
29253
29892
  agent_responses: [],
29254
29893
  provider: null,
29255
- system: { type, time: time3 }
29894
+ system: {
29895
+ type,
29896
+ time: time3,
29897
+ ...backendSessionId ? { backend_session_id: backendSessionId } : {}
29898
+ }
29256
29899
  };
29257
29900
  }
29258
- function findResumableSession(rows, provider) {
29901
+ function resolveResumableSession(rows, provider) {
29902
+ let candidateSessionId = null;
29903
+ let recoveryMarkerSeen = false;
29904
+ let stalledSessionId = null;
29905
+ let fencedSessionId = null;
29259
29906
  for (let i = rows.length - 1;i >= 0; i--) {
29260
29907
  const e = rows[i];
29261
- if (e.system?.type === "reset_session" || e.system?.type === "nap")
29262
- return null;
29908
+ if (e.system) {
29909
+ if (e.system.type === "stall_recovery_attempt") {
29910
+ if (!recoveryMarkerSeen) {
29911
+ recoveryMarkerSeen = true;
29912
+ stalledSessionId = e.system.backend_session_id ?? null;
29913
+ }
29914
+ continue;
29915
+ }
29916
+ if (e.system.type === "stall_recovery_clear") {
29917
+ if (!recoveryMarkerSeen)
29918
+ recoveryMarkerSeen = true;
29919
+ continue;
29920
+ }
29921
+ if (e.system.type === "stall_recovery" && fencedSessionId === null) {
29922
+ fencedSessionId = e.system.backend_session_id ?? null;
29923
+ }
29924
+ if (candidateSessionId !== null) {
29925
+ return {
29926
+ kind: "session",
29927
+ sessionId: candidateSessionId,
29928
+ stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
29929
+ fencedSessionId
29930
+ };
29931
+ }
29932
+ return {
29933
+ kind: "barrier",
29934
+ type: e.system.type,
29935
+ forgottenSessionId: e.system.backend_session_id ?? null,
29936
+ fencedSessionId
29937
+ };
29938
+ }
29263
29939
  if (!e.session_id)
29264
29940
  continue;
29265
29941
  if (provider && e.provider !== provider)
29266
29942
  continue;
29267
- return e.session_id;
29943
+ if (candidateSessionId === null) {
29944
+ candidateSessionId = e.session_id;
29945
+ continue;
29946
+ }
29947
+ if (candidateSessionId !== e.session_id)
29948
+ break;
29268
29949
  }
29269
- return null;
29950
+ if (candidateSessionId !== null) {
29951
+ return {
29952
+ kind: "session",
29953
+ sessionId: candidateSessionId,
29954
+ stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
29955
+ fencedSessionId
29956
+ };
29957
+ }
29958
+ return { kind: "none", stalledSessionId, fencedSessionId };
29270
29959
  }
29271
29960
  // src/timeline/recorder.ts
29272
29961
  var MAX_AGENT_RESPONSES = 5;
29273
- function appendAgentResponse(entry, text2) {
29274
- entry.agent_responses.push(text2);
29275
- if (entry.agent_responses.length > MAX_AGENT_RESPONSES) {
29276
- entry.agent_responses.splice(0, entry.agent_responses.length - MAX_AGENT_RESPONSES);
29962
+ var MAX_AGENT_RESPONSE_BYTES = 65536;
29963
+ var MAX_PENDING_COMMITS_PER_AGENT = 8;
29964
+ var PENDING_COMMIT_TTL_MS = 15 * 60000;
29965
+ var TRUNCATION_MARKER = `
29966
+ … [truncated]`;
29967
+ function turnKey(agentId, owner) {
29968
+ return `${agentId}\x00${owner.sessionInstanceId}\x00${owner.rootTurnId}\x00${owner.barrierGeneration}`;
29969
+ }
29970
+ function sameOwner(left, right) {
29971
+ return left.sessionInstanceId === right.sessionInstanceId && left.rootTurnId === right.rootTurnId && left.barrierGeneration === right.barrierGeneration;
29972
+ }
29973
+ function utf8Prefix2(text2, maxBytes) {
29974
+ if (maxBytes <= 0)
29975
+ return "";
29976
+ if (Buffer.byteLength(text2, "utf8") <= maxBytes)
29977
+ return text2;
29978
+ let low = 0;
29979
+ let high = text2.length;
29980
+ while (low < high) {
29981
+ const mid = Math.ceil((low + high) / 2);
29982
+ let end2 = mid;
29983
+ const code2 = text2.charCodeAt(end2 - 1);
29984
+ if (code2 >= 55296 && code2 <= 56319)
29985
+ end2 -= 1;
29986
+ if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
29987
+ low = mid;
29988
+ else
29989
+ high = mid - 1;
29277
29990
  }
29991
+ let end = low;
29992
+ const code = text2.charCodeAt(end - 1);
29993
+ if (code >= 55296 && code <= 56319)
29994
+ end -= 1;
29995
+ while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
29996
+ end -= 1;
29997
+ return text2.slice(0, end);
29998
+ }
29999
+ function boundedResponse(text2, alreadyTruncated) {
30000
+ const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
30001
+ const needsTruncation = alreadyTruncated || Buffer.byteLength(text2, "utf8") > MAX_AGENT_RESPONSE_BYTES;
30002
+ if (!needsTruncation)
30003
+ return text2;
30004
+ return utf8Prefix2(text2, MAX_AGENT_RESPONSE_BYTES - markerBytes) + TRUNCATION_MARKER;
30005
+ }
30006
+ function serializedTimelineBytes(entry) {
30007
+ return Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
30008
+ }
30009
+ function fitResponsesToRow(entry, responses) {
30010
+ let fitted = [...entry.agent_responses];
30011
+ for (const response of responses) {
30012
+ fitted = [...fitted, response].slice(-MAX_AGENT_RESPONSES);
30013
+ if (serializedTimelineBytes({ ...entry, agent_responses: fitted }) <= TIMELINE_MAX_BYTES)
30014
+ continue;
30015
+ const truncationBase = response.endsWith(TRUNCATION_MARKER) ? response.slice(0, -TRUNCATION_MARKER.length) : response;
30016
+ let low = 0;
30017
+ let high = truncationBase.length;
30018
+ let replacement = null;
30019
+ while (low <= high) {
30020
+ const mid = Math.floor((low + high) / 2);
30021
+ let end = mid;
30022
+ const code = truncationBase.charCodeAt(end - 1);
30023
+ if (code >= 55296 && code <= 56319)
30024
+ end -= 1;
30025
+ const candidateResponse = truncationBase.slice(0, end) + TRUNCATION_MARKER;
30026
+ const candidate = [...fitted.slice(0, -1), candidateResponse];
30027
+ if (serializedTimelineBytes({ ...entry, agent_responses: candidate }) <= TIMELINE_MAX_BYTES) {
30028
+ replacement = candidateResponse;
30029
+ low = mid + 1;
30030
+ } else {
30031
+ high = mid - 1;
30032
+ }
30033
+ }
30034
+ if (replacement === null)
30035
+ return null;
30036
+ fitted[fitted.length - 1] = replacement;
30037
+ }
30038
+ return fitted;
29278
30039
  }
29279
30040
  function createTimelineRecorder(opts) {
29280
30041
  const now = opts.now ?? (() => new Date);
29281
30042
  const dirFor = (agentId) => opts.timelineDirFor(agentId);
29282
30043
  const sessionByAgent = new Map;
30044
+ const resolveForAgent = (agentId, provider) => {
30045
+ const dir = dirFor(agentId);
30046
+ const recent = resolveResumableSession(readRecentEntries(dir, { now: now() }), provider ?? undefined);
30047
+ const control = readResumeControlState(dir);
30048
+ if (control.kind === "missing")
30049
+ return recent;
30050
+ if (control.kind === "invalid") {
30051
+ return {
30052
+ kind: "barrier",
30053
+ type: "reset_session",
30054
+ forgottenSessionId: null,
30055
+ fencedSessionId: null
30056
+ };
30057
+ }
30058
+ const { attemptedSessionId, fencedSessionId, fullBarrier } = control.state;
30059
+ if (fullBarrier !== null) {
30060
+ return {
30061
+ kind: "barrier",
30062
+ type: fullBarrier,
30063
+ forgottenSessionId: null,
30064
+ fencedSessionId
30065
+ };
30066
+ }
30067
+ if (recent.kind === "session") {
30068
+ return {
30069
+ kind: "session",
30070
+ sessionId: recent.sessionId,
30071
+ stalledSessionId: attemptedSessionId === recent.sessionId ? attemptedSessionId : null,
30072
+ fencedSessionId
30073
+ };
30074
+ }
30075
+ if (recent.kind === "barrier") {
30076
+ if (recent.type !== "stall_recovery" || recent.forgottenSessionId === fencedSessionId) {
30077
+ return { ...recent, fencedSessionId };
30078
+ }
30079
+ }
30080
+ return { kind: "none", stalledSessionId: attemptedSessionId, fencedSessionId };
30081
+ };
30082
+ const sessionByEpoch = new Map;
30083
+ const barrierByAgent = new Map;
30084
+ const turnsByAgent = new Map;
30085
+ const activeTurnByAgent = new Map;
30086
+ const epochKey = (agentId, sessionInstanceId) => `${agentId}\x00${sessionInstanceId}`;
30087
+ const currentBarrier = (agentId) => barrierByAgent.get(agentId) ?? 0;
30088
+ const statesFor = (agentId) => {
30089
+ let states = turnsByAgent.get(agentId);
30090
+ if (!states) {
30091
+ states = new Map;
30092
+ turnsByAgent.set(agentId, states);
30093
+ }
30094
+ return states;
30095
+ };
30096
+ const diagnostic = (agentId, code, reason) => {
30097
+ try {
30098
+ opts.onDiagnostic?.({ agentId, code, ...reason ? { reason } : {} });
30099
+ } catch {}
30100
+ };
30101
+ const applyRewrite = (agentId, rewrite) => {
30102
+ for (const state of statesFor(agentId).values()) {
30103
+ if (!state.handle)
30104
+ continue;
30105
+ const refreshed = refreshTimelineEntryHandle(state.handle, rewrite);
30106
+ if (refreshed) {
30107
+ state.handle = refreshed;
30108
+ } else if (state.handle.filename === rewrite.filename) {
30109
+ state.handle = undefined;
30110
+ state.rowFenced = true;
30111
+ diagnostic(agentId, "timeline_handle_fenced", "rewrite_remap");
30112
+ }
30113
+ }
30114
+ };
30115
+ const handleTrackedResult = (agentId, state, result) => {
30116
+ if (result.status === "written") {
30117
+ applyRewrite(agentId, result.rewrite);
30118
+ if (state && result.handle) {
30119
+ state.handle = result.handle;
30120
+ state.rowFenced = false;
30121
+ }
30122
+ return "written";
30123
+ }
30124
+ if (result.reason === "lock" || result.reason === "write")
30125
+ return "retryable";
30126
+ if (state && result.reason !== "oversized")
30127
+ state.rowFenced = true;
30128
+ diagnostic(agentId, "timeline_exact_write_rejected", result.reason);
30129
+ return "terminal";
30130
+ };
30131
+ const tryCommit = (agentId, state) => {
30132
+ if (state.responses.length === 0)
30133
+ return "written";
30134
+ const dir = dirFor(agentId);
30135
+ if (!prepareTimelineDirectory(dir)) {
30136
+ diagnostic(agentId, "timeline_directory_unavailable");
30137
+ return "terminal";
30138
+ }
30139
+ if (state.handle) {
30140
+ const result2 = updateTrackedEntry(dir, state.handle, (entry2) => {
30141
+ const fitted2 = fitResponsesToRow(entry2, state.responses);
30142
+ return { ...entry2, agent_responses: fitted2 ?? [...entry2.agent_responses, ...state.responses] };
30143
+ });
30144
+ return handleTrackedResult(agentId, state, result2);
30145
+ }
30146
+ if (state.rowFenced || state.pendingMode === "handle")
30147
+ return "terminal";
30148
+ if (state.owner.barrierGeneration !== currentBarrier(agentId))
30149
+ return "terminal";
30150
+ const entry = createTimelineEntry({
30151
+ messages: [],
30152
+ sessionId: state.backendSessionId,
30153
+ provider: state.provider
30154
+ });
30155
+ const fitted = fitResponsesToRow(entry, state.responses);
30156
+ if (!fitted) {
30157
+ diagnostic(agentId, "timeline_response_did_not_fit", "oversized");
30158
+ return "terminal";
30159
+ }
30160
+ entry.agent_responses = fitted;
30161
+ const result = appendTrackedEntry(dir, entry, now());
30162
+ return handleTrackedResult(agentId, state, result);
30163
+ };
30164
+ const deleteState = (agentId, key) => {
30165
+ const states = statesFor(agentId);
30166
+ states.delete(key);
30167
+ if (activeTurnByAgent.get(agentId) === key)
30168
+ activeTurnByAgent.delete(agentId);
30169
+ if (states.size === 0)
30170
+ turnsByAgent.delete(agentId);
30171
+ };
30172
+ const retryPending = (agentId) => {
30173
+ const states = turnsByAgent.get(agentId);
30174
+ if (!states)
30175
+ return;
30176
+ const nowMs = now().getTime();
30177
+ for (const [key, state] of [...states]) {
30178
+ if (state.finalized && state.pendingSinceMs === undefined && state.completedAtMs !== undefined && nowMs - state.completedAtMs >= PENDING_COMMIT_TTL_MS) {
30179
+ deleteState(agentId, key);
30180
+ continue;
30181
+ }
30182
+ if (!state.finalized || state.pendingSinceMs === undefined)
30183
+ continue;
30184
+ if (nowMs - state.pendingSinceMs >= PENDING_COMMIT_TTL_MS) {
30185
+ diagnostic(agentId, "timeline_pending_commit_expired");
30186
+ deleteState(agentId, key);
30187
+ continue;
30188
+ }
30189
+ const result = tryCommit(agentId, state);
30190
+ if (result === "written") {
30191
+ state.responses = [];
30192
+ state.pendingSinceMs = undefined;
30193
+ state.pendingMode = undefined;
30194
+ state.completedAtMs = nowMs;
30195
+ } else if (result === "terminal") {
30196
+ deleteState(agentId, key);
30197
+ }
30198
+ }
30199
+ const completed = [...states.entries()].filter(([, state]) => state.finalized && state.pendingSinceMs === undefined).sort((left, right) => (left[1].completedAtMs ?? 0) - (right[1].completedAtMs ?? 0));
30200
+ while (completed.length > MAX_PENDING_COMMITS_PER_AGENT) {
30201
+ const oldest = completed.shift();
30202
+ if (oldest)
30203
+ deleteState(agentId, oldest[0]);
30204
+ }
30205
+ };
30206
+ const retainPending = (agentId, key, state) => {
30207
+ const states = statesFor(agentId);
30208
+ const pending = [...states.values()].filter((candidate) => candidate.finalized && candidate.pendingSinceMs !== undefined);
30209
+ if (pending.length >= MAX_PENDING_COMMITS_PER_AGENT) {
30210
+ diagnostic(agentId, "timeline_pending_commit_overflow");
30211
+ deleteState(agentId, key);
30212
+ return;
30213
+ }
30214
+ state.pendingSinceMs ??= now().getTime();
30215
+ state.pendingMode = state.handle ? "handle" : "fallback";
30216
+ };
30217
+ const appendOwnerless = (agentId, messages) => {
30218
+ if (messages.length === 0)
30219
+ return;
30220
+ const dir = dirFor(agentId);
30221
+ if (!prepareTimelineDirectory(dir))
30222
+ return;
30223
+ const result = appendTrackedEntry(dir, createTimelineEntry({
30224
+ messages,
30225
+ sessionId: sessionByAgent.get(agentId) ?? null,
30226
+ provider: opts.providerFor?.(agentId) ?? null
30227
+ }), now());
30228
+ handleTrackedResult(agentId, null, result);
30229
+ };
29283
30230
  return {
29284
- setSession(agentId, sessionId) {
29285
- sessionByAgent.set(agentId, sessionId);
30231
+ barrierGeneration(agentId) {
30232
+ return currentBarrier(agentId);
29286
30233
  },
29287
- appendEntryForAgent(agentId, messages) {
30234
+ beginTurn(agentId, owner) {
30235
+ retryPending(agentId);
30236
+ const states = statesFor(agentId);
30237
+ if (owner.barrierGeneration !== currentBarrier(agentId)) {
30238
+ diagnostic(agentId, "timeline_turn_begin_fenced", "barrier_generation");
30239
+ return;
30240
+ }
30241
+ const key = turnKey(agentId, owner);
30242
+ if (!states.has(key)) {
30243
+ states.set(key, {
30244
+ owner: { ...owner },
30245
+ provider: opts.providerFor?.(agentId) ?? null,
30246
+ backendSessionId: sessionByEpoch.get(epochKey(agentId, owner.sessionInstanceId)) ?? sessionByAgent.get(agentId) ?? null,
30247
+ responses: [],
30248
+ rowFenced: false,
30249
+ finalized: false
30250
+ });
30251
+ }
30252
+ activeTurnByAgent.set(agentId, key);
30253
+ },
30254
+ recordInboxPull(agentId, owner, messages) {
30255
+ retryPending(agentId);
29288
30256
  if (messages.length === 0)
29289
30257
  return;
30258
+ if (!owner) {
30259
+ appendOwnerless(agentId, messages);
30260
+ return;
30261
+ }
30262
+ const key = turnKey(agentId, owner);
30263
+ const state = turnsByAgent.get(agentId)?.get(key);
30264
+ if (!state || state.rowFenced || !sameOwner(state.owner, owner)) {
30265
+ appendOwnerless(agentId, messages);
30266
+ return;
30267
+ }
29290
30268
  const dir = dirFor(agentId);
29291
30269
  if (!prepareTimelineDirectory(dir))
29292
30270
  return;
29293
- appendOrMergeEntry(dir, createTimelineEntry({
29294
- messages,
29295
- sessionId: sessionByAgent.get(agentId) ?? null,
29296
- provider: opts.providerFor?.(agentId) ?? null
29297
- }), now());
30271
+ const stamp = now();
30272
+ let result;
30273
+ if (state.handle && (state.handle.filename === filenameForDate(stamp) || state.owner.barrierGeneration !== currentBarrier(agentId))) {
30274
+ result = updateTrackedEntry(dir, state.handle, (entry) => ({
30275
+ ...entry,
30276
+ messages: [...entry.messages, ...messages],
30277
+ session_id: state.backendSessionId,
30278
+ provider: state.provider
30279
+ }));
30280
+ } else if (state.owner.barrierGeneration === currentBarrier(agentId)) {
30281
+ result = appendTrackedEntry(dir, createTimelineEntry({
30282
+ messages,
30283
+ sessionId: state.backendSessionId,
30284
+ provider: state.provider
30285
+ }), stamp);
30286
+ } else {
30287
+ appendOwnerless(agentId, messages);
30288
+ return;
30289
+ }
30290
+ handleTrackedResult(agentId, state, result);
30291
+ },
30292
+ recordAssistantMessage(agentId, owner, text2, truncated = false) {
30293
+ retryPending(agentId);
30294
+ const key = turnKey(agentId, owner);
30295
+ const state = turnsByAgent.get(agentId)?.get(key);
30296
+ if (!state || state.finalized || state.owner.barrierGeneration !== currentBarrier(agentId) || !sameOwner(state.owner, owner)) {
30297
+ diagnostic(agentId, "timeline_completed_message_rejected", "stale_owner");
30298
+ return;
30299
+ }
30300
+ state.responses.push(boundedResponse(text2, truncated));
30301
+ if (state.responses.length > MAX_AGENT_RESPONSES) {
30302
+ state.responses.splice(0, state.responses.length - MAX_AGENT_RESPONSES);
30303
+ }
29298
30304
  },
29299
- appendResponseToLatest(agentId, text2) {
29300
- const dir = dirFor(agentId);
29301
- if (!prepareTimelineDirectory(dir))
30305
+ finalizeTurn(agentId, owner) {
30306
+ retryPending(agentId);
30307
+ const key = turnKey(agentId, owner);
30308
+ const state = turnsByAgent.get(agentId)?.get(key);
30309
+ if (!state || state.finalized || !sameOwner(state.owner, owner))
29302
30310
  return;
29303
- const result = updateLatestEntryResult(dir, (entry2) => appendAgentResponse(entry2, text2), { now: now() });
29304
- if (result === "updated" || result === "rejected")
30311
+ const fallbackAuthorized = activeTurnByAgent.get(agentId) === key && owner.barrierGeneration === currentBarrier(agentId);
30312
+ state.finalized = true;
30313
+ activeTurnByAgent.delete(agentId);
30314
+ if (state.responses.length === 0) {
30315
+ state.completedAtMs = now().getTime();
29305
30316
  return;
29306
- const entry = createTimelineEntry({
29307
- messages: [],
29308
- sessionId: sessionByAgent.get(agentId) ?? null,
29309
- provider: opts.providerFor?.(agentId) ?? null
29310
- });
29311
- appendAgentResponse(entry, text2);
29312
- appendEntry(dir, entry, now());
30317
+ }
30318
+ if (!state.handle && !fallbackAuthorized) {
30319
+ diagnostic(agentId, "timeline_fallback_rejected", "fenced_owner");
30320
+ deleteState(agentId, key);
30321
+ return;
30322
+ }
30323
+ const result = tryCommit(agentId, state);
30324
+ if (result === "written") {
30325
+ state.responses = [];
30326
+ state.completedAtMs = now().getTime();
30327
+ } else if (result === "terminal") {
30328
+ deleteState(agentId, key);
30329
+ } else {
30330
+ retainPending(agentId, key, state);
30331
+ }
30332
+ },
30333
+ fenceSession(agentId) {
30334
+ retryPending(agentId);
30335
+ barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
30336
+ activeTurnByAgent.delete(agentId);
30337
+ const states = turnsByAgent.get(agentId);
30338
+ if (!states)
30339
+ return;
30340
+ for (const [key, state] of [...states]) {
30341
+ if (!state.handle) {
30342
+ diagnostic(agentId, "timeline_fallback_rejected", "session_fence");
30343
+ deleteState(agentId, key);
30344
+ }
30345
+ }
30346
+ },
30347
+ setSession(agentId, sessionId, sessionInstanceId) {
30348
+ retryPending(agentId);
30349
+ const dir = dirFor(agentId);
30350
+ if (!prepareTimelineDirectory(dir))
30351
+ return false;
30352
+ const persisted = updateResumeControlState(dir, (state) => ({
30353
+ ...state,
30354
+ fullBarrier: null,
30355
+ attemptedSessionId: state.attemptedSessionId === sessionId ? state.attemptedSessionId : null
30356
+ }));
30357
+ if (!persisted)
30358
+ return false;
30359
+ sessionByAgent.set(agentId, sessionId);
30360
+ if (sessionInstanceId) {
30361
+ sessionByEpoch.set(epochKey(agentId, sessionInstanceId), sessionId);
30362
+ for (const state of statesFor(agentId).values()) {
30363
+ if (state.owner.sessionInstanceId === sessionInstanceId)
30364
+ state.backendSessionId = sessionId;
30365
+ }
30366
+ }
30367
+ return true;
29313
30368
  },
29314
30369
  resumeSessionId(agentId, provider) {
29315
- const rows = readRecentEntries(dirFor(agentId), { now: now() });
29316
- return findResumableSession(rows, provider ?? undefined);
30370
+ const resolution = resolveForAgent(agentId, provider);
30371
+ return resolution.kind === "session" ? resolution.sessionId : null;
30372
+ },
30373
+ resolveResumeSession(agentId, provider) {
30374
+ return resolveForAgent(agentId, provider);
30375
+ },
30376
+ recordSessionStall(agentId, sessionId) {
30377
+ return appendStallMarker(agentId, "stall_recovery_attempt", sessionId);
29317
30378
  },
29318
- forgetSession(agentId, barrierType = "reset_session") {
30379
+ clearSessionStall(agentId, sessionId) {
30380
+ return appendStallMarker(agentId, "stall_recovery_clear", sessionId);
30381
+ },
30382
+ forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
30383
+ retryPending(agentId);
29319
30384
  const dir = dirFor(agentId);
29320
- sessionByAgent.delete(agentId);
29321
30385
  if (!prepareTimelineDirectory(dir))
29322
- return;
30386
+ return false;
29323
30387
  const stamp = now();
29324
- appendEntry(dir, createSystemEntry(barrierType, stamp.toISOString()), stamp);
30388
+ let persisted = false;
30389
+ if (barrierType === "reset_session" || barrierType === "nap") {
30390
+ persisted = updateResumeControlState(dir, (state) => ({
30391
+ ...state,
30392
+ attemptedSessionId: null,
30393
+ fencedSessionId: null,
30394
+ fullBarrier: barrierType
30395
+ }));
30396
+ } else if (barrierType === "stall_recovery") {
30397
+ persisted = updateResumeControlState(dir, (state) => ({
30398
+ ...state,
30399
+ attemptedSessionId: state.attemptedSessionId === forgottenSessionId ? null : state.attemptedSessionId,
30400
+ fencedSessionId: forgottenSessionId ?? null,
30401
+ fullBarrier: null
30402
+ }));
30403
+ }
30404
+ if (!persisted)
30405
+ return false;
30406
+ sessionByAgent.delete(agentId);
30407
+ for (const key of [...sessionByEpoch.keys()]) {
30408
+ if (key.startsWith(`${agentId}\x00`))
30409
+ sessionByEpoch.delete(key);
30410
+ }
30411
+ barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
30412
+ activeTurnByAgent.delete(agentId);
30413
+ const states = turnsByAgent.get(agentId);
30414
+ if (states) {
30415
+ for (const [key, state] of [...states]) {
30416
+ if (!state.handle) {
30417
+ diagnostic(agentId, "timeline_fallback_rejected", "barrier");
30418
+ deleteState(agentId, key);
30419
+ }
30420
+ }
30421
+ }
30422
+ const result = appendTrackedEntry(dir, createSystemEntry(barrierType, stamp.toISOString(), forgottenSessionId), stamp);
30423
+ handleTrackedResult(agentId, null, result);
30424
+ return true;
29325
30425
  }
29326
30426
  };
30427
+ function appendStallMarker(agentId, type, sessionId) {
30428
+ retryPending(agentId);
30429
+ const dir = dirFor(agentId);
30430
+ if (!prepareTimelineDirectory(dir))
30431
+ return false;
30432
+ const stamp = now();
30433
+ const persisted = updateResumeControlState(dir, (state) => ({
30434
+ ...state,
30435
+ attemptedSessionId: type === "stall_recovery_attempt" ? sessionId : null,
30436
+ fullBarrier: null
30437
+ }));
30438
+ if (!persisted)
30439
+ return false;
30440
+ const result = appendTrackedEntry(dir, createSystemEntry(type, stamp.toISOString(), sessionId), stamp);
30441
+ handleTrackedResult(agentId, null, result);
30442
+ return true;
30443
+ }
29327
30444
  }
29328
30445
  // src/daemon/diagnosticsCommand.ts
29329
30446
  function reportUnavailable(options, reportId) {
@@ -29868,11 +30985,15 @@ async function createDaemon(opts) {
29868
30985
  const typingTracker = createTypingScopeTracker();
29869
30986
  const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
29870
30987
  const proxy = await startCredentialProxy(broker, {
29871
- onInboxPullStart: (agentId) => channelRef?.modelSeenGeneration(agentId),
30988
+ onInboxPullStart: (agentId) => ({
30989
+ modelSeenGeneration: channelRef?.modelSeenGeneration(agentId),
30990
+ owner: managerRef?.timelineTurnOwner(agentId) ?? null
30991
+ }),
29872
30992
  onInboxPullResponse: (agentId, messages, observationToken) => {
29873
- timeline2.appendEntryForAgent(agentId, messages);
29874
- if (typeof observationToken === "number") {
29875
- channelRef?.recordModelSeen(agentId, messages, observationToken);
30993
+ const token = observationToken && typeof observationToken === "object" ? observationToken : null;
30994
+ timeline2.recordInboxPull(agentId, token?.owner ?? null, messages);
30995
+ if (typeof token?.modelSeenGeneration === "number") {
30996
+ channelRef?.recordModelSeen(agentId, messages, token.modelSeenGeneration);
29876
30997
  }
29877
30998
  },
29878
30999
  onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
@@ -30634,8 +31755,8 @@ function projectDaemonLogRow(value, targetAgentId) {
30634
31755
  fields: projectedFields
30635
31756
  };
30636
31757
  }
30637
- var MANAGER_EVENTS = ["register", "wake", "spawned", "session", "root_work", "turn_end", "exit", "tick", "reset_session", "begin_reset", "rewake_after_reset", "runtime_signal", "admission_started", "admission_settled"];
30638
- var MANAGER_EFFECTS = ["spawn", "send", "stop", "terminate_stalled", "force_exit", "gated_hold"];
31758
+ var MANAGER_EVENTS = ["register", "wake", "spawned", "session", "root_work", "turn_end", "exit", "tick", "reset_session", "begin_reset", "rewake_after_reset", "runtime_signal", "stall_control_failed", "admission_started", "admission_settled"];
31759
+ var MANAGER_EFFECTS = ["spawn", "send", "stop", "terminate_stalled", "clear_stall_recovery", "force_exit", "gated_hold"];
30639
31760
  var AGENT_STATUSES = ["idle", "starting", "running", "stopping"];
30640
31761
  var DELIVERY_PHASES = ["idle", "admission_wait", "steering", "next_turn_queued", "compacting", "reviewing", "tool_wait", "working"];
30641
31762
  var RESUME_OUTCOMES = ["not_requested", "pending", "resumed", "reset_required", "failed"];
@@ -30644,6 +31765,8 @@ var TERMINATION_CAUSES = ["runtime_error", "killed_stalled", "other"];
30644
31765
  var SPAWN_FAILURE_REASONS = ["ENOENT", "handshake_timeout", "pre_handshake_exit", "spawn_threw", "other"];
30645
31766
  var TERMINATION_SEMANTICS = ["killed_stalled", "idle_stop", "force_exit", "other"];
30646
31767
  var ABORT_CAUSES = ["start_threw", "start_rejected", "send_threw", "spawn_failure", "handshake_timeout", "reset", "nap", "model_switch", "requested_stop", "shutdown", "physical_exit", "terminate_stalled", "force_exit", "other"];
31768
+ var NATIVE_ACTIVITY_KINDS = ["turn_started", "backend_turn_started", "thinking", "text", "tool_call", "tool_output", "internal_progress", "recovery", "turn_end"];
31769
+ var RUNTIME_PHASES = ["idle", "admission", "inference", "tool", "recovery", "terminal"];
30647
31770
  var EXIT_SIGNALS = new Set(["SIGABRT", "SIGALRM", "SIGBREAK", "SIGBUS", "SIGCHLD", "SIGCONT", "SIGFPE", "SIGHUP", "SIGILL", "SIGINFO", "SIGINT", "SIGIO", "SIGIOT", "SIGKILL", "SIGLOST", "SIGPIPE", "SIGPOLL", "SIGPROF", "SIGPWR", "SIGQUIT", "SIGSEGV", "SIGSTKFLT", "SIGSTOP", "SIGSYS", "SIGTERM", "SIGTRAP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGUSR1", "SIGUSR2", "SIGVTALRM", "SIGWINCH", "SIGXCPU", "SIGXFSZ", "other"]);
30648
31771
  function enumValue(value, values, bucket = false, maxChars = 64) {
30649
31772
  if (!boundedString(value, maxChars))
@@ -30681,6 +31804,13 @@ function copyString(row, output, key, maxChars, optional2 = false, nullable2 = f
30681
31804
  output[key] = row[key];
30682
31805
  return true;
30683
31806
  }
31807
+ function copyScrubbedString(row, output, key, maxChars, optional2 = false, nullable2 = false) {
31808
+ if (!copyString(row, output, key, maxChars, optional2, nullable2))
31809
+ return false;
31810
+ if (typeof output[key] === "string")
31811
+ output[key] = scrubDiagnosticText(output[key]);
31812
+ return true;
31813
+ }
30684
31814
  function projectTraceBase(row, targetAgentId) {
30685
31815
  if (row.agentId !== targetAgentId || !boundedString(row.agentId, 128) || !canonicalTime(row.timeIso))
30686
31816
  return null;
@@ -30707,11 +31837,29 @@ function projectFsm(row, targetAgentId) {
30707
31837
  Object.assign(output, { event, status, turnActive: row.turnActive });
30708
31838
  if (!copyInteger(row, output, "inbox") || !copyInteger(row, output, "lastDeliverAt", { nullable: true }) || !copyInteger(row, output, "lastProgressAt") || !copyInteger(row, output, "idleSince", { nullable: true }))
30709
31839
  return null;
31840
+ if (!copyInteger(row, output, "lastNativeActivityAt", { optional: true }) || !copyScrubbedString(row, output, "backendTurnId", 512, true, true) || !copyInteger(row, output, "turnSilenceBudgetMs", { optional: true }) || !copyInteger(row, output, "nativeDeadlineAt", { optional: true, nullable: true }) || !copyInteger(row, output, "recoveryExtensionsUsed", { optional: true }))
31841
+ return null;
31842
+ if ("lastNativeActivityKind" in row && row.lastNativeActivityKind !== undefined) {
31843
+ if (row.lastNativeActivityKind === null)
31844
+ output.lastNativeActivityKind = null;
31845
+ else {
31846
+ const kind = enumValue(row.lastNativeActivityKind, NATIVE_ACTIVITY_KINDS);
31847
+ if (!kind)
31848
+ return null;
31849
+ output.lastNativeActivityKind = kind;
31850
+ }
31851
+ }
31852
+ if ("runtimePhase" in row && row.runtimePhase !== undefined) {
31853
+ const phase = enumValue(row.runtimePhase, RUNTIME_PHASES);
31854
+ if (!phase)
31855
+ return null;
31856
+ output.runtimePhase = phase;
31857
+ }
30710
31858
  output.resetting = row.resetting;
30711
31859
  if (!copyInteger(row, output, "resettingSince", { nullable: true }) || !copyInteger(row, output, "stoppingSince", { nullable: true }))
30712
31860
  return null;
30713
31861
  output.deliveryPhase = deliveryPhase;
30714
- if (!copyInteger(row, output, "sinceProgressMs") || !copyInteger(row, output, "sinceDeliverMs", { nullable: true }) || !copyInteger(row, output, "sinceStoppingMs", { nullable: true }) || !projectOptionalSpanMetadata(row, output))
31862
+ if (!copyInteger(row, output, "sinceProgressMs") || !copyInteger(row, output, "sinceNativeActivityMs", { optional: true }) || !copyInteger(row, output, "sinceDeliverMs", { nullable: true }) || !copyInteger(row, output, "sinceStoppingMs", { nullable: true }) || !projectOptionalSpanMetadata(row, output))
30715
31863
  return null;
30716
31864
  const metricKeys = [
30717
31865
  "physicalOpenCount",
@@ -30842,7 +31990,7 @@ function projectStatusRow(value, targetAgentId) {
30842
31990
  return output;
30843
31991
  }
30844
31992
  // src/diagnostics/bundle.ts
30845
- import { createHash as createHash2 } from "node:crypto";
31993
+ import { createHash as createHash3 } from "node:crypto";
30846
31994
  import { createWriteStream, unlinkSync as unlinkSync5 } from "node:fs";
30847
31995
  import { Readable, Transform } from "node:stream";
30848
31996
  import { pipeline } from "node:stream/promises";
@@ -30992,7 +32140,7 @@ async function buildDiagnosticBundle(args) {
30992
32140
  const { footer, total } = envelope();
30993
32141
  if (total > maxUncompressed)
30994
32142
  throw new BundleTooLargeError;
30995
- const hash2 = createHash2("sha256");
32143
+ const hash2 = createHash3("sha256");
30996
32144
  let compressedBytes = 0;
30997
32145
  const meter = new Transform({
30998
32146
  transform(chunk2, _encoding, callback) {
@@ -31033,7 +32181,7 @@ async function buildDiagnosticBundle(args) {
31033
32181
  };
31034
32182
  }
31035
32183
  // src/diagnostics/coordinator.ts
31036
- import { createHash as createHash3, randomBytes as randomBytes5 } from "node:crypto";
32184
+ import { createHash as createHash4, randomBytes as randomBytes5 } from "node:crypto";
31037
32185
  import {
31038
32186
  chmodSync as chmodSync2,
31039
32187
  closeSync as closeSync4,
@@ -31189,7 +32337,7 @@ function createDiagnosticReportCoordinator(args) {
31189
32337
  if (!stat.isFile() || stat.isSymbolicLink())
31190
32338
  return null;
31191
32339
  const bytes = readFileSync6(path11);
31192
- return { sizeBytes: bytes.byteLength, sha256: createHash3("sha256").update(bytes).digest("hex") };
32340
+ return { sizeBytes: bytes.byteLength, sha256: createHash4("sha256").update(bytes).digest("hex") };
31193
32341
  } catch {
31194
32342
  return null;
31195
32343
  }