@alook/daemon 0.1.17 → 0.1.19

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
@@ -17736,6 +17736,7 @@ __export(exports_community_schema, {
17736
17736
  communityServerFolderItem: () => communityServerFolderItem,
17737
17737
  communityServerFolder: () => communityServerFolder,
17738
17738
  communityServer: () => communityServer,
17739
+ communityReadStateRevision: () => communityReadStateRevision,
17739
17740
  communityReadState: () => communityReadState,
17740
17741
  communityReaction: () => communityReaction,
17741
17742
  communityPin: () => communityPin,
@@ -17900,6 +17901,10 @@ var communityReadState = sqliteTable("community_read_state", {
17900
17901
  lastReadMessageId: text("last_read_message_id"),
17901
17902
  lastReadSeq: integer2("last_read_seq").notNull().default(0)
17902
17903
  }, (t) => [index("idx_read_state_user").on(t.userId)]);
17904
+ var communityReadStateRevision = sqliteTable("community_read_state_revision", {
17905
+ userId: text("user_id").primaryKey().references(() => user.id, { onDelete: "cascade" }),
17906
+ revision: integer2("revision").notNull().default(0)
17907
+ });
17903
17908
  var communityReaction = sqliteTable("community_reaction", {
17904
17909
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
17905
17910
  messageId: text("message_id").notNull().references(() => communityMessage.id, { onDelete: "cascade" }),
@@ -18493,6 +18498,24 @@ var communityUnreadBumpSchema = exports_external.strictObject({
18493
18498
  railChannelId: string4.optional(),
18494
18499
  isMention: exports_external.boolean().optional()
18495
18500
  });
18501
+ var readStateEnvelopeFields = {
18502
+ revision: exports_external.number().int().positive(),
18503
+ inboxChanged: exports_external.literal(true)
18504
+ };
18505
+ var communityReadStateAdvancedSchema = exports_external.strictObject({
18506
+ type: exports_external.literal("community:read_state.advanced"),
18507
+ ...readStateEnvelopeFields
18508
+ });
18509
+ var communityInboxChangedSchema = exports_external.strictObject({
18510
+ type: exports_external.literal("community:inbox.changed"),
18511
+ ...readStateEnvelopeFields,
18512
+ reason: exports_external.enum([
18513
+ "read_all",
18514
+ "mention_read_all",
18515
+ "mention_dismiss",
18516
+ "notification_policy"
18517
+ ])
18518
+ });
18496
18519
  var communityPresenceUpdateSchema = exports_external.strictObject({
18497
18520
  type: exports_external.literal("community:presence.update"),
18498
18521
  userId: string4,
@@ -18588,6 +18611,8 @@ var CommunityWsEventDiscriminatedSchema = exports_external.discriminatedUnion("t
18588
18611
  communityInviteCreateSchema,
18589
18612
  communityMentionCreateSchema,
18590
18613
  communityUnreadBumpSchema,
18614
+ communityReadStateAdvancedSchema,
18615
+ communityInboxChangedSchema,
18591
18616
  communityPresenceUpdateSchema,
18592
18617
  communityStatusUpdateSchema,
18593
18618
  communityMachineCreatedSchema,
@@ -18632,6 +18657,8 @@ var WS_EVENTS = {
18632
18657
  INVITE_CREATE: "community:invite.create",
18633
18658
  MENTION_CREATE: "community:mention.create",
18634
18659
  UNREAD_BUMP: "community:unread.bump",
18660
+ READ_STATE_ADVANCED: "community:read_state.advanced",
18661
+ INBOX_CHANGED: "community:inbox.changed",
18635
18662
  PRESENCE_UPDATE: "community:presence.update",
18636
18663
  STATUS_UPDATE: "community:status.update",
18637
18664
  MACHINE_CREATED: "community:machine.created",
@@ -20288,19 +20315,23 @@ class ClaudeEventNormalizer {
20288
20315
  const content = event?.message?.content;
20289
20316
  if (!Array.isArray(content))
20290
20317
  return;
20318
+ const completedText = [];
20291
20319
  for (const block of content) {
20292
20320
  if (block?.type === "thinking") {
20293
- out.push({ kind: "thinking", text: block.thinking ?? "" });
20321
+ out.push({ kind: "assistant_reasoning_completed", text: block.thinking ?? "" });
20294
20322
  } else if (block?.type === "text") {
20295
20323
  const text2 = block.text ?? "";
20296
20324
  if (API_ERROR_RE.test(text2))
20297
20325
  out.push({ kind: "error", message: text2 });
20298
20326
  else
20299
- out.push({ kind: "text", text: text2 });
20327
+ completedText.push(text2);
20300
20328
  } else if (block?.type === "tool_use") {
20301
20329
  out.push({ kind: "tool_call", name: block.name ?? "unknown_tool", input: block.input });
20302
20330
  }
20303
20331
  }
20332
+ if (completedText.length > 0) {
20333
+ out.push({ kind: "assistant_message_completed", text: completedText.join("") });
20334
+ }
20304
20335
  }
20305
20336
  handleUser(event, out) {
20306
20337
  if (event.isReplay === true && typeof event.uuid === "string") {
@@ -20709,14 +20740,17 @@ class CodexEventNormalizer {
20709
20740
  this.turnId = params.turn.id;
20710
20741
  this.terminalTurn = null;
20711
20742
  return [
20712
- { kind: "turn_owner", receipt: this.turnReceipt(params.threadId, params.turn.id) },
20713
- { kind: "thinking", text: "" }
20743
+ {
20744
+ kind: "turn_owner",
20745
+ receipt: this.turnReceipt(params.threadId, params.turn.id),
20746
+ nativeTurnId: params.turn.id
20747
+ }
20714
20748
  ];
20715
20749
  case "item/reasoning/textDelta":
20716
20750
  case "item/reasoning/summaryTextDelta":
20717
- return [{ kind: "thinking", text: params?.delta ?? "" }];
20751
+ return [{ kind: "assistant_reasoning_delta", text: params?.delta ?? "" }];
20718
20752
  case "item/agentMessage/delta":
20719
- return [{ kind: "text", text: params?.delta ?? "" }];
20753
+ return [{ kind: "assistant_message_delta", text: params?.delta ?? "" }];
20720
20754
  case "item/started":
20721
20755
  return this.handleItemStarted(params);
20722
20756
  case "item/completed":
@@ -20744,7 +20778,10 @@ class CodexEventNormalizer {
20744
20778
  }
20745
20779
  return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
20746
20780
  case "error":
20747
- return [{ kind: "error", message: params?.message ?? "Codex error" }];
20781
+ if (params?.willRetry === true) {
20782
+ return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
20783
+ }
20784
+ return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
20748
20785
  case "thread/tokenUsage/updated":
20749
20786
  case "account/rateLimits/updated":
20750
20787
  return mapCodexTelemetry(method, params);
@@ -20837,9 +20874,9 @@ class CodexEventNormalizer {
20837
20874
  case "collabAgentToolCall":
20838
20875
  return [{ kind: "tool_output", name: "collab_tool_call" }];
20839
20876
  case "agentMessage":
20840
- return [{ kind: "text", text: params?.item?.text ?? "" }];
20877
+ return [{ kind: "assistant_message_completed", text: params?.item?.text ?? "" }];
20841
20878
  case "reasoning":
20842
- return [{ kind: "thinking", text: params?.item?.text ?? "" }];
20879
+ return [{ kind: "assistant_reasoning_completed", text: params?.item?.text ?? "" }];
20843
20880
  default:
20844
20881
  return [];
20845
20882
  }
@@ -20872,7 +20909,13 @@ class CodexDriver {
20872
20909
  lifetime: "session",
20873
20910
  transport: { kind: "stdio_rpc", protocol: "codex.app-server.v1" },
20874
20911
  wakeStart: "immediate",
20875
- terminalOwnership: "transport_request"
20912
+ terminalOwnership: "transport_request",
20913
+ turnSilence: {
20914
+ nativeIdleTimeoutMs: 300000,
20915
+ daemonGraceMs: 60000,
20916
+ recoveryGraceMs: 60000,
20917
+ maxRecoveryExtensions: 1
20918
+ }
20876
20919
  };
20877
20920
  eventNormalizer = new CodexEventNormalizer;
20878
20921
  requestId = 0;
@@ -21529,14 +21572,14 @@ class CursorAcpLane {
21529
21572
  case "agent_message_chunk": {
21530
21573
  const content = record2(update.content);
21531
21574
  if (content?.type === "text" && typeof content.text === "string") {
21532
- this.events.emit("runtime_event", { kind: "text", text: content.text });
21575
+ this.events.emit("runtime_event", { kind: "assistant_message_delta", text: content.text });
21533
21576
  }
21534
21577
  return;
21535
21578
  }
21536
21579
  case "agent_thought_chunk": {
21537
21580
  const content = record2(update.content);
21538
21581
  if (content?.type === "text" && typeof content.text === "string") {
21539
- this.events.emit("runtime_event", { kind: "thinking", text: content.text });
21582
+ this.events.emit("runtime_event", { kind: "assistant_reasoning_delta", text: content.text });
21540
21583
  }
21541
21584
  return;
21542
21585
  }
@@ -22341,16 +22384,20 @@ class OpenCodeServiceLane {
22341
22384
  }
22342
22385
  switch (event.type) {
22343
22386
  case "session.next.step.started":
22344
- this.events.emit("runtime_event", { kind: "thinking", text: "" });
22387
+ this.events.emit("runtime_event", {
22388
+ kind: "internal_progress",
22389
+ source: "opencode.service",
22390
+ itemType: "step_started"
22391
+ });
22345
22392
  break;
22346
22393
  case "session.next.text.ended":
22347
22394
  if (typeof data.text === "string" && data.text.length > 0) {
22348
- this.events.emit("runtime_event", { kind: "text", text: data.text });
22395
+ this.events.emit("runtime_event", { kind: "assistant_message_completed", text: data.text });
22349
22396
  }
22350
22397
  break;
22351
22398
  case "session.next.reasoning.ended":
22352
22399
  if (typeof data.text === "string" && data.text.length > 0) {
22353
- this.events.emit("runtime_event", { kind: "thinking", text: data.text });
22400
+ this.events.emit("runtime_event", { kind: "assistant_reasoning_completed", text: data.text });
22354
22401
  }
22355
22402
  break;
22356
22403
  case "session.next.tool.called":
@@ -23101,22 +23148,28 @@ function readPiSdkVersion() {
23101
23148
  }
23102
23149
  function mapPiSdkEvent(event, sessionId, state) {
23103
23150
  if (event?.type === "message_update") {
23104
- const d = event.delta ?? {};
23151
+ const d = event.assistantMessageEvent ?? {};
23105
23152
  switch (d.type) {
23106
23153
  case "thinking_delta":
23107
- return [{ kind: "thinking", text: d.delta ?? "" }];
23154
+ return [{ kind: "assistant_reasoning_delta", text: d.delta ?? "" }];
23108
23155
  case "text_delta":
23109
23156
  state.sawTextDelta = true;
23110
- return [{ kind: "text", text: d.delta ?? "" }];
23111
- case "text_end":
23112
- return state.sawTextDelta ? [] : [{ kind: "text", text: d.content ?? "" }];
23157
+ return [{ kind: "assistant_message_delta", text: d.delta ?? "" }];
23158
+ case "text_end": {
23159
+ state.sawTextDelta = false;
23160
+ return [{ kind: "assistant_message_completed", text: d.content ?? "" }];
23161
+ }
23113
23162
  case "error":
23114
- return [{ kind: "error", message: d.message ?? "Pi error" }];
23163
+ return [{ kind: "error", message: d.error?.errorMessage ?? "Pi error" }];
23115
23164
  default:
23116
23165
  return [];
23117
23166
  }
23118
23167
  }
23119
23168
  switch (event?.type) {
23169
+ case "auto_retry_start":
23170
+ return [{ kind: "runtime_recovery", stage: "retrying", source: "pi_auto_retry" }];
23171
+ case "auto_retry_end":
23172
+ return [{ kind: "runtime_recovery", stage: "recovered", source: "pi_auto_retry" }];
23120
23173
  case "tool_execution_start":
23121
23174
  return [{ kind: "tool_call", name: event.toolName ?? "unknown_tool", input: event.args ?? {} }];
23122
23175
  case "tool_execution_end":
@@ -23281,6 +23334,13 @@ function assertAdapterCompatibility(registrationId, registeredCapabilities, adap
23281
23334
  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
23335
  throw new Error(`Adapter ${registrationId} has an invalid transport declaration`);
23283
23336
  }
23337
+ if (execution.turnSilence !== undefined) {
23338
+ const silence = execution.turnSilence;
23339
+ const safeInteger = (value, minimum) => typeof value === "number" && Number.isSafeInteger(value) && value >= minimum;
23340
+ 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)) {
23341
+ throw new Error(`Adapter ${registrationId} has an invalid turnSilence declaration`);
23342
+ }
23343
+ }
23284
23344
  const capabilities = registeredCapabilities;
23285
23345
  const declaredLifetime = capabilities?.sessionLifetime === "persistent" ? "session" : "turn";
23286
23346
  if (execution.lifetime !== declaredLifetime) {
@@ -23370,6 +23430,12 @@ function capabilitiesFor(backend) {
23370
23430
  return builtinRegistry.get(backend).capabilities;
23371
23431
  }
23372
23432
 
23433
+ // agent-driver/dist/internal/adapter.js
23434
+ var DEFAULT_NATIVE_IDLE_TIMEOUT_MS = 300000;
23435
+ var DEFAULT_DAEMON_GRACE_MS = 60000;
23436
+ var DEFAULT_RECOVERY_GRACE_MS = 60000;
23437
+ var DEFAULT_MAX_RECOVERY_EXTENSIONS = 1;
23438
+
23373
23439
  // agent-driver/dist/controller/event-queue.js
23374
23440
  var MAX_BUFFERED_BYTES = 4194304;
23375
23441
 
@@ -23566,6 +23632,62 @@ function stableErrorCode(value, fallback) {
23566
23632
 
23567
23633
  // agent-driver/dist/controller/logical-session.js
23568
23634
  import { mkdirSync as mkdirSync4 } from "node:fs";
23635
+ var SEMANTIC_ASSEMBLER_MAX_BYTES = 1048576;
23636
+ var WORK_HEARTBEAT_MIN_INTERVAL_MS = 1000;
23637
+ function emptySemanticAssembler() {
23638
+ return { chunks: [], bytes: 0, truncated: false };
23639
+ }
23640
+ function utf8Prefix(text2, maxBytes) {
23641
+ if (maxBytes <= 0 || text2.length === 0)
23642
+ return "";
23643
+ if (Buffer.byteLength(text2, "utf8") <= maxBytes)
23644
+ return text2;
23645
+ let low = 0;
23646
+ let high = text2.length;
23647
+ while (low < high) {
23648
+ const mid = Math.ceil((low + high) / 2);
23649
+ let end2 = mid;
23650
+ const code2 = text2.charCodeAt(end2 - 1);
23651
+ if (code2 >= 55296 && code2 <= 56319)
23652
+ end2 -= 1;
23653
+ if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
23654
+ low = mid;
23655
+ else
23656
+ high = mid - 1;
23657
+ }
23658
+ let end = low;
23659
+ const code = text2.charCodeAt(end - 1);
23660
+ if (code >= 55296 && code <= 56319)
23661
+ end -= 1;
23662
+ while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
23663
+ end -= 1;
23664
+ return text2.slice(0, end);
23665
+ }
23666
+ function appendSemanticFragment(buffer, text2) {
23667
+ if (text2.length === 0 || buffer.truncated)
23668
+ return;
23669
+ const remaining = SEMANTIC_ASSEMBLER_MAX_BYTES - buffer.bytes;
23670
+ const bytes = Buffer.byteLength(text2, "utf8");
23671
+ if (bytes <= remaining) {
23672
+ buffer.chunks.push(text2);
23673
+ buffer.bytes += bytes;
23674
+ return;
23675
+ }
23676
+ const prefix = utf8Prefix(text2, remaining);
23677
+ if (prefix.length > 0) {
23678
+ buffer.chunks.push(prefix);
23679
+ buffer.bytes += Buffer.byteLength(prefix, "utf8");
23680
+ }
23681
+ buffer.truncated = true;
23682
+ }
23683
+ function finishSemanticAssembler(buffer) {
23684
+ return { text: buffer.chunks.join(""), truncated: buffer.truncated };
23685
+ }
23686
+ function boundedSemanticCompletion(text2) {
23687
+ const buffer = emptySemanticAssembler();
23688
+ appendSemanticFragment(buffer, text2);
23689
+ return finishSemanticAssembler(buffer);
23690
+ }
23569
23691
  function driverError(category, code, message2, retryable = false) {
23570
23692
  return { category, code, message: scrubDriverErrorMessage(message2), retryable };
23571
23693
  }
@@ -23639,6 +23761,7 @@ class LogicalAgentSession {
23639
23761
  resumeOutcome;
23640
23762
  eventQueue;
23641
23763
  behavior;
23764
+ turnSilence;
23642
23765
  constructor(backend, config2, launch, adapter, capabilities2, host, prepared, hostReleaseTimeoutMs) {
23643
23766
  this.backend = backend;
23644
23767
  this.config = config2;
@@ -23649,6 +23772,18 @@ class LogicalAgentSession {
23649
23772
  this.hostReleaseTimeoutMs = hostReleaseTimeoutMs;
23650
23773
  this.capabilities = capabilities2;
23651
23774
  this.behavior = this.capabilities;
23775
+ const declaredSilence = adapter.execution.turnSilence;
23776
+ const nativeIdleTimeoutMs = declaredSilence?.nativeIdleTimeoutMs ?? DEFAULT_NATIVE_IDLE_TIMEOUT_MS;
23777
+ const daemonGraceMs = declaredSilence?.daemonGraceMs ?? DEFAULT_DAEMON_GRACE_MS;
23778
+ const recoveryGraceMs = declaredSilence?.recoveryGraceMs ?? DEFAULT_RECOVERY_GRACE_MS;
23779
+ const maxRecoveryExtensions = declaredSilence?.maxRecoveryExtensions ?? DEFAULT_MAX_RECOVERY_EXTENSIONS;
23780
+ this.turnSilence = {
23781
+ nativeIdleTimeoutMs,
23782
+ daemonGraceMs,
23783
+ recoveryGraceMs,
23784
+ maxRecoveryExtensions,
23785
+ normalBudgetMs: nativeIdleTimeoutMs + daemonGraceMs
23786
+ };
23652
23787
  this.resumeOutcome = launch.resumeSessionId ? "pending" : "not_requested";
23653
23788
  this.sessionInstanceId = host.createId();
23654
23789
  this.closed = new Promise((resolve3) => {
@@ -23753,6 +23888,7 @@ class LogicalAgentSession {
23753
23888
  lastEventSequence: this.eventSequence,
23754
23889
  diagnostics: {
23755
23890
  deliveryPhase: this.deliveryPhase(),
23891
+ turnSilence: this.turnSilence,
23756
23892
  metrics: {
23757
23893
  physicalOpenCount: this.metricValue(this.physicalOpenCount),
23758
23894
  turnCount: this.metricValue(this.turnCount),
@@ -23843,7 +23979,14 @@ class LogicalAgentSession {
23843
23979
  const turnId = this.host.createId();
23844
23980
  const commandIds = messages.map((message2) => message2.id);
23845
23981
  const terminalOwner = this.adapter.beginTurn?.();
23846
- this.activeTurn = { turnId, commandIds: [...commandIds], ...terminalOwner ? { terminalOwner } : {} };
23982
+ this.activeTurn = {
23983
+ turnId,
23984
+ commandIds: [...commandIds],
23985
+ ...terminalOwner ? { terminalOwner } : {},
23986
+ pendingMessage: emptySemanticAssembler(),
23987
+ pendingReasoning: emptySemanticAssembler(),
23988
+ lastWorkHeartbeatAt: null
23989
+ };
23847
23990
  this.turnError = undefined;
23848
23991
  this.interruptedTurnId = undefined;
23849
23992
  this.processTurnEnded = false;
@@ -24041,14 +24184,36 @@ class LogicalAgentSession {
24041
24184
  return;
24042
24185
  }
24043
24186
  this.activeTurn.terminalOwner = event.receipt;
24187
+ const nativeTurnId = event.nativeTurnId?.trim();
24188
+ if (nativeTurnId && nativeTurnId.length <= 512 && /^[A-Za-z0-9._:-]+$/.test(nativeTurnId)) {
24189
+ this.emit({
24190
+ type: "backend_turn_started",
24191
+ turnId: this.activeTurn.turnId,
24192
+ backendTurnId: nativeTurnId
24193
+ });
24194
+ }
24044
24195
  return;
24045
- case "thinking":
24046
- if (turnId)
24047
- this.emit({ type: "thinking_delta", turnId, text: event.text });
24196
+ case "assistant_reasoning_delta":
24197
+ case "assistant_message_delta":
24198
+ if (turnId && this.activeTurn?.turnId === turnId && event.text.length > 0) {
24199
+ appendSemanticFragment(event.kind === "assistant_message_delta" ? this.activeTurn.pendingMessage : this.activeTurn.pendingReasoning, event.text);
24200
+ const now = this.host.now();
24201
+ if (this.activeTurn.lastWorkHeartbeatAt === null || now - this.activeTurn.lastWorkHeartbeatAt >= WORK_HEARTBEAT_MIN_INTERVAL_MS) {
24202
+ this.activeTurn.lastWorkHeartbeatAt = now;
24203
+ this.emit({ type: "work_heartbeat", turnId });
24204
+ }
24205
+ }
24048
24206
  return;
24049
- case "text":
24050
- if (turnId)
24051
- this.emit({ type: "text_delta", turnId, text: event.text });
24207
+ case "assistant_reasoning_completed":
24208
+ case "assistant_message_completed":
24209
+ if (turnId && this.activeTurn?.turnId === turnId) {
24210
+ const field = event.kind === "assistant_message_completed" ? "pendingMessage" : "pendingReasoning";
24211
+ this.activeTurn[field] = emptySemanticAssembler();
24212
+ if (event.text.length > 0) {
24213
+ const completed = boundedSemanticCompletion(event.text);
24214
+ this.emit({ type: event.kind, turnId, ...completed });
24215
+ }
24216
+ }
24052
24217
  return;
24053
24218
  case "tool_call":
24054
24219
  this.outstandingToolUses += 1;
@@ -24091,9 +24256,19 @@ class LogicalAgentSession {
24091
24256
  message: scrubDriverErrorMessage(event.message, "Runtime diagnostic")
24092
24257
  });
24093
24258
  return;
24259
+ case "runtime_recovery":
24260
+ this.emit({
24261
+ type: "recovery",
24262
+ turnId,
24263
+ stage: event.stage,
24264
+ source: event.source
24265
+ });
24266
+ return;
24094
24267
  case "runtime_metric":
24095
- if (event.name === "sse_reconnect" && event.increment === 1)
24268
+ if (event.name === "sse_reconnect" && event.increment === 1) {
24096
24269
  this.sseReconnectCount += 1;
24270
+ this.emit({ type: "recovery", turnId, stage: "retrying", source: "transport_reconnect" });
24271
+ }
24097
24272
  return;
24098
24273
  case "telemetry": {
24099
24274
  const details = jsonValue(event.attrs);
@@ -24116,6 +24291,18 @@ class LogicalAgentSession {
24116
24291
  });
24117
24292
  return;
24118
24293
  case "turn_end":
24294
+ if (turnId && this.activeTurn?.turnId === turnId) {
24295
+ const reasoning = finishSemanticAssembler(this.activeTurn.pendingReasoning);
24296
+ const message2 = finishSemanticAssembler(this.activeTurn.pendingMessage);
24297
+ this.activeTurn.pendingReasoning = emptySemanticAssembler();
24298
+ this.activeTurn.pendingMessage = emptySemanticAssembler();
24299
+ if (reasoning.text.length > 0) {
24300
+ this.emit({ type: "assistant_reasoning_completed", turnId, ...reasoning });
24301
+ }
24302
+ if (message2.text.length > 0) {
24303
+ this.emit({ type: "assistant_message_completed", turnId, ...message2 });
24304
+ }
24305
+ }
24119
24306
  this.completeTurn(event.sessionId, physicalOwner, generation, event.turnOwner);
24120
24307
  return;
24121
24308
  }
@@ -24129,7 +24316,7 @@ class LogicalAgentSession {
24129
24316
  reopenClosedLaneForWork(event, physicalOwner, generation) {
24130
24317
  if (this.adapter.execution.lifetime === "turn")
24131
24318
  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";
24319
+ 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
24320
  if (!isRootWork)
24134
24321
  return;
24135
24322
  const tombstone = this.closedLaneTombstone;
@@ -24139,7 +24326,10 @@ class LogicalAgentSession {
24139
24326
  this.activeTurn = {
24140
24327
  turnId: tombstone.localTurnId,
24141
24328
  commandIds: tombstone.commandIds,
24142
- ...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {}
24329
+ ...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {},
24330
+ pendingMessage: emptySemanticAssembler(),
24331
+ pendingReasoning: emptySemanticAssembler(),
24332
+ lastWorkHeartbeatAt: null
24143
24333
  };
24144
24334
  this.state = "working";
24145
24335
  return tombstone.localTurnId;
@@ -26242,11 +26432,26 @@ function isActivelyWorking(agent2) {
26242
26432
  return agent2.status === "running" && (leaseIsWorking(agent2.execution.lease) || agent2.pendingAdmissions.length > 0 || agent2.inbox.length > 0);
26243
26433
  }
26244
26434
  var DEFAULT_STALE_THRESHOLD_MS = 120000;
26245
- var DEFAULT_IDLE_TIMEOUT_MS = 300000;
26435
+ var DEFAULT_TURN_SILENCE_POLICY = {
26436
+ nativeIdleTimeoutMs: 300000,
26437
+ daemonGraceMs: 60000,
26438
+ recoveryGraceMs: 60000,
26439
+ maxRecoveryExtensions: 1,
26440
+ normalBudgetMs: 360000
26441
+ };
26442
+ var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
26443
+ var DEFAULT_IDLE_RESET_TIMEOUT_MS = 6 * 60 * 60 * 1000;
26246
26444
  var DEFAULT_RESET_STUCK_THRESHOLD_MS = 120000;
26247
26445
  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 };
26446
+ 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) {
26447
+ return {
26448
+ agents: {},
26449
+ staleThresholdMs,
26450
+ idleTimeoutMs,
26451
+ idleResetTimeoutMs,
26452
+ resetStuckThresholdMs,
26453
+ stoppingStuckThresholdMs
26454
+ };
26250
26455
  }
26251
26456
  function reduceManager(state, event) {
26252
26457
  switch (event.type) {
@@ -26261,6 +26466,11 @@ function reduceManager(state, event) {
26261
26466
  });
26262
26467
  case "backend_session":
26263
26468
  return mutate(state, event.agentId, (a) => {
26469
+ if (event.stalledBefore) {
26470
+ a.stalledSessionId = event.sessionId;
26471
+ } else if (a.stalledSessionId !== null && a.stalledSessionId !== event.sessionId) {
26472
+ a.stalledSessionId = null;
26473
+ }
26264
26474
  a.sessionId = event.sessionId;
26265
26475
  });
26266
26476
  case "attach_session": {
@@ -26273,7 +26483,17 @@ function reduceManager(state, event) {
26273
26483
  {
26274
26484
  const a = agent2;
26275
26485
  a.execution = { sessionInstanceId: event.sessionInstanceId, lease: { state: "none", lastTerminal: null } };
26486
+ a.turnSilence = event.turnSilence ?? {
26487
+ ...DEFAULT_TURN_SILENCE_POLICY,
26488
+ nativeIdleTimeoutMs: state.staleThresholdMs,
26489
+ daemonGraceMs: 0,
26490
+ normalBudgetMs: state.staleThresholdMs
26491
+ };
26276
26492
  a.lastProgressAt = event.nowMs;
26493
+ a.lastNativeActivityAt = event.nowMs;
26494
+ a.lastNativeActivityKind = null;
26495
+ a.runtimePhase = "admission";
26496
+ a.backendTurnId = null;
26277
26497
  a.idleSince = null;
26278
26498
  syncExecutionProjection(a);
26279
26499
  }
@@ -26329,7 +26549,25 @@ function reduceManager(state, event) {
26329
26549
  return { state, effects: [] };
26330
26550
  return mutate(state, event.agentId, (a) => {
26331
26551
  a.sessionId = null;
26552
+ a.stalledSessionId = null;
26553
+ a.idleSince = null;
26332
26554
  });
26555
+ case "idle_reset_committed": {
26556
+ const existing = state.agents[event.agentId];
26557
+ if (!existing)
26558
+ return { state, effects: [] };
26559
+ const agent2 = clone2(existing);
26560
+ agent2.sessionId = null;
26561
+ agent2.stalledSessionId = null;
26562
+ agent2.idleSince = null;
26563
+ if (agent2.status !== "running")
26564
+ return commit(state, agent2, []);
26565
+ agent2.status = "stopping";
26566
+ agent2.stoppingSince = event.nowMs;
26567
+ return commit(state, agent2, [
26568
+ { type: "stop", agentId: event.agentId, reason: "idle_session_reset" }
26569
+ ]);
26570
+ }
26333
26571
  case "begin_reset":
26334
26572
  if (!state.agents[event.agentId])
26335
26573
  return { state, effects: [] };
@@ -26367,8 +26605,18 @@ function reduceManager(state, event) {
26367
26605
  return;
26368
26606
  const startedCommands = new Set(event.commandIds);
26369
26607
  a.pendingAdmissions = a.pendingAdmissions.filter((entry) => !startedCommands.has(entry.commandId));
26370
- a.execution.lease = { state: "active", identity, lastWorkAt: event.nowMs };
26608
+ a.execution.lease = {
26609
+ state: "active",
26610
+ identity,
26611
+ lastWorkAt: event.nowMs,
26612
+ nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
26613
+ recoveryExtensionsUsed: 0
26614
+ };
26371
26615
  a.lastProgressAt = event.nowMs;
26616
+ a.lastNativeActivityAt = event.nowMs;
26617
+ a.lastNativeActivityKind = "turn_started";
26618
+ a.runtimePhase = "inference";
26619
+ a.backendTurnId = null;
26372
26620
  a.idleSince = null;
26373
26621
  syncExecutionProjection(a);
26374
26622
  });
@@ -26389,7 +26637,12 @@ function reduceManager(state, event) {
26389
26637
  const lease = a.execution.lease;
26390
26638
  const identity = identityOf(event);
26391
26639
  if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
26392
- a.execution.lease = { ...lease, lastWorkAt: event.nowMs };
26640
+ a.execution.lease = {
26641
+ ...lease,
26642
+ lastWorkAt: event.nowMs,
26643
+ nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
26644
+ recoveryExtensionsUsed: 0
26645
+ };
26393
26646
  } else {
26394
26647
  const terminal = lease.state === "none" ? lease.lastTerminal : null;
26395
26648
  if (!terminal || !sameIdentity(terminal.identity, identity))
@@ -26398,6 +26651,8 @@ function reduceManager(state, event) {
26398
26651
  state: "suspect_active",
26399
26652
  identity,
26400
26653
  lastWorkAt: event.nowMs,
26654
+ nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
26655
+ recoveryExtensionsUsed: 0,
26401
26656
  reason: "work_after_terminal"
26402
26657
  };
26403
26658
  }
@@ -26411,7 +26666,7 @@ function reduceManager(state, event) {
26411
26666
  case "turn_tool_finished":
26412
26667
  return onTurnToolLifecycle(state, event, "finished");
26413
26668
  case "turn_completed":
26414
- return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId);
26669
+ return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId, event.endReason);
26415
26670
  case "session_closed":
26416
26671
  if (state.agents[event.agentId]?.execution.sessionInstanceId !== event.sessionInstanceId) {
26417
26672
  return { state, effects: [] };
@@ -26421,7 +26676,6 @@ function reduceManager(state, event) {
26421
26676
  const closing = agent2.pendingAdmissions.filter((entry) => entry.sessionInstanceId === event.sessionInstanceId);
26422
26677
  agent2.pendingAdmissions = agent2.pendingAdmissions.filter((entry) => entry.sessionInstanceId !== event.sessionInstanceId);
26423
26678
  agent2.execution = { sessionInstanceId: null, lease: { state: "detached" } };
26424
- agent2.idleSince = null;
26425
26679
  syncExecutionProjection(agent2);
26426
26680
  return commit(state, agent2, recoveryEffects(agent2, closing));
26427
26681
  }
@@ -26429,8 +26683,57 @@ function reduceManager(state, event) {
26429
26683
  return onExit(state, event.agentId);
26430
26684
  case "tick":
26431
26685
  return onTick(state, event.nowMs);
26432
- case "runtime_signal":
26433
- return { state, effects: [] };
26686
+ case "runtime_signal": {
26687
+ const existing = state.agents[event.agentId];
26688
+ if (!existing || existing.execution.sessionInstanceId !== event.sessionInstanceId)
26689
+ return { state, effects: [] };
26690
+ const lease = existing.execution.lease;
26691
+ const identity = { sessionInstanceId: event.sessionInstanceId, turnId: event.turnId };
26692
+ if (lease.state !== "active" && lease.state !== "suspect_active" || !sameIdentity(lease.identity, identity)) {
26693
+ return { state, effects: [] };
26694
+ }
26695
+ return mutate(state, event.agentId, (a) => {
26696
+ const active = a.execution.lease;
26697
+ if (active.state !== "active" && active.state !== "suspect_active" || !sameIdentity(active.identity, identity))
26698
+ return;
26699
+ if (event.kind === "recovery" && event.recoveryStage !== "recovered") {
26700
+ if (active.recoveryExtensionsUsed < a.turnSilence.maxRecoveryExtensions) {
26701
+ a.execution.lease = {
26702
+ ...active,
26703
+ nativeDeadlineAt: Math.max(active.nativeDeadlineAt, event.nowMs + a.turnSilence.recoveryGraceMs),
26704
+ recoveryExtensionsUsed: active.recoveryExtensionsUsed + 1
26705
+ };
26706
+ }
26707
+ } else {
26708
+ a.execution.lease = {
26709
+ ...active,
26710
+ nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs
26711
+ };
26712
+ }
26713
+ a.lastNativeActivityAt = event.nowMs;
26714
+ a.lastNativeActivityKind = event.kind;
26715
+ a.runtimePhase = event.phase;
26716
+ if (event.backendTurnId)
26717
+ a.backendTurnId = event.backendTurnId;
26718
+ });
26719
+ }
26720
+ case "stall_control_failed":
26721
+ return mutate(state, event.agentId, (a) => {
26722
+ if (event.transition === "clear") {
26723
+ if (a.sessionId === event.sessionId)
26724
+ a.stalledSessionId = event.sessionId;
26725
+ return;
26726
+ }
26727
+ if (a.status !== "stopping")
26728
+ return;
26729
+ const lease = a.execution.lease;
26730
+ if (lease.state !== "active" && lease.state !== "suspect_active")
26731
+ return;
26732
+ a.status = "running";
26733
+ a.stoppingSince = null;
26734
+ a.sessionId = event.sessionId;
26735
+ a.stalledSessionId = event.transition === "fence" ? event.sessionId : null;
26736
+ });
26434
26737
  case "delivery_rejected":
26435
26738
  return mutate(state, event.agentId, (a) => {
26436
26739
  if (!a.inbox.some((message2) => message2.id === event.message.id)) {
@@ -26468,7 +26771,7 @@ function onWake(state, agentId, message2) {
26468
26771
  }
26469
26772
  return commit(state, agent2, []);
26470
26773
  }
26471
- function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
26774
+ function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId, endReason) {
26472
26775
  const existing = state.agents[agentId];
26473
26776
  if (!existing)
26474
26777
  return { state, effects: [] };
@@ -26484,13 +26787,23 @@ function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
26484
26787
  const lastTerminal = { identity, at: nowMs };
26485
26788
  agent2.execution.lease = { state: "none", lastTerminal };
26486
26789
  agent2.lastProgressAt = nowMs;
26790
+ agent2.lastNativeActivityAt = nowMs;
26791
+ agent2.lastNativeActivityKind = "turn_end";
26792
+ agent2.runtimePhase = "terminal";
26793
+ const clearedStallSessionId = endReason === undefined ? agent2.stalledSessionId : null;
26794
+ if (clearedStallSessionId !== null)
26795
+ agent2.stalledSessionId = null;
26487
26796
  syncExecutionProjection(agent2);
26797
+ const clearEffects = clearedStallSessionId === null ? [] : [{ type: "clear_stall_recovery", agentId, sessionId: clearedStallSessionId }];
26488
26798
  if (agent2.inbox.length > 0) {
26489
26799
  const messages = drainInbox(agent2);
26490
- return commit(state, agent2, messages.map((queued) => ({ type: "send", agentId, message: queued, mode: "idle" })));
26800
+ return commit(state, agent2, [
26801
+ ...clearEffects,
26802
+ ...messages.map((queued) => ({ type: "send", agentId, message: queued, mode: "idle" }))
26803
+ ]);
26491
26804
  }
26492
26805
  agent2.idleSince = nowMs;
26493
- return commit(state, agent2, []);
26806
+ return commit(state, agent2, clearEffects);
26494
26807
  }
26495
26808
  function onTurnToolLifecycle(state, event, lifecycle) {
26496
26809
  const existing = state.agents[event.agentId];
@@ -26512,11 +26825,22 @@ function onTurnToolLifecycle(state, event, lifecycle) {
26512
26825
  if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
26513
26826
  const outstandingToolUses = (lease.outstandingToolUses ?? 0) + (lifecycle === "started" ? 1 : -1);
26514
26827
  if (outstandingToolUses > 0) {
26515
- agent2.execution.lease = { ...lease, lastWorkAt: event.nowMs, outstandingToolUses };
26828
+ agent2.execution.lease = {
26829
+ ...lease,
26830
+ lastWorkAt: event.nowMs,
26831
+ nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
26832
+ recoveryExtensionsUsed: 0,
26833
+ outstandingToolUses
26834
+ };
26516
26835
  } else {
26517
26836
  const unblockedLease = { ...lease };
26518
26837
  delete unblockedLease.outstandingToolUses;
26519
- agent2.execution.lease = { ...unblockedLease, lastWorkAt: event.nowMs };
26838
+ agent2.execution.lease = {
26839
+ ...unblockedLease,
26840
+ lastWorkAt: event.nowMs,
26841
+ nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
26842
+ recoveryExtensionsUsed: 0
26843
+ };
26520
26844
  }
26521
26845
  } else {
26522
26846
  const terminal = lease.state === "none" ? lease.lastTerminal : null;
@@ -26526,11 +26850,16 @@ function onTurnToolLifecycle(state, event, lifecycle) {
26526
26850
  state: "suspect_active",
26527
26851
  identity,
26528
26852
  lastWorkAt: event.nowMs,
26853
+ nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
26854
+ recoveryExtensionsUsed: 0,
26529
26855
  outstandingToolUses: 1,
26530
26856
  reason: "work_after_terminal"
26531
26857
  };
26532
26858
  }
26533
26859
  agent2.lastProgressAt = event.nowMs;
26860
+ agent2.lastNativeActivityAt = event.nowMs;
26861
+ agent2.lastNativeActivityKind = lifecycle === "started" ? "tool_call" : "tool_output";
26862
+ agent2.runtimePhase = lifecycle === "started" ? "tool" : "inference";
26534
26863
  agent2.idleSince = null;
26535
26864
  syncExecutionProjection(agent2);
26536
26865
  });
@@ -26543,6 +26872,8 @@ function onExit(state, agentId) {
26543
26872
  const effects = recoveryEffects(agent2, agent2.pendingAdmissions);
26544
26873
  agent2.pendingAdmissions = [];
26545
26874
  agent2.execution = { sessionInstanceId: null, lease: { state: "detached" } };
26875
+ agent2.runtimePhase = "idle";
26876
+ agent2.backendTurnId = null;
26546
26877
  agent2.stoppingSince = null;
26547
26878
  syncExecutionProjection(agent2);
26548
26879
  if (agent2.inbox.length > 0) {
@@ -26562,10 +26893,19 @@ function onTick(state, nowMs) {
26562
26893
  for (const id of Object.keys(agents)) {
26563
26894
  const a = agents[id];
26564
26895
  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;
26896
+ 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
26897
  if (stalled) {
26567
- agents[id] = { ...a, status: "stopping", idleSince: null, stoppingSince: nowMs };
26568
- effects.push({ type: "terminate_stalled", agentId: id });
26898
+ const repeatedSessionStall = a.sessionId !== null && a.stalledSessionId === a.sessionId;
26899
+ const forgetSessionId = repeatedSessionStall ? a.sessionId : undefined;
26900
+ agents[id] = {
26901
+ ...a,
26902
+ status: "stopping",
26903
+ sessionId: repeatedSessionStall ? null : a.sessionId,
26904
+ stalledSessionId: repeatedSessionStall ? null : a.sessionId,
26905
+ idleSince: null,
26906
+ stoppingSince: nowMs
26907
+ };
26908
+ 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
26909
  continue;
26570
26910
  }
26571
26911
  const expiredAdmission = a.status === "running" && a.pendingAdmissions.filter((entry) => !entry.driverAcknowledged && nowMs - entry.admittedAt >= state.staleThresholdMs);
@@ -26597,9 +26937,14 @@ function onTick(state, nowMs) {
26597
26937
  effects.push({ type: "force_exit", agentId: id, reason: "stopping_stuck" });
26598
26938
  continue;
26599
26939
  }
26940
+ 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);
26941
+ if (idleResetEligible && nowMs - a.idleSince >= state.idleResetTimeoutMs) {
26942
+ effects.push({ type: "reset_idle_session", agentId: id, sessionId: a.sessionId });
26943
+ continue;
26944
+ }
26600
26945
  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
26946
  if (idleEligible && a.idleSince !== null && nowMs - a.idleSince >= state.idleTimeoutMs) {
26602
- agents[id] = { ...a, status: "stopping", idleSince: null, stoppingSince: nowMs };
26947
+ agents[id] = { ...a, status: "stopping", stoppingSince: nowMs };
26603
26948
  effects.push({ type: "stop", agentId: id, reason: "idle_timeout" });
26604
26949
  }
26605
26950
  }
@@ -26611,11 +26956,17 @@ function freshAgent(agentId) {
26611
26956
  status: "idle",
26612
26957
  inbox: [],
26613
26958
  sessionId: null,
26959
+ stalledSessionId: null,
26614
26960
  execution: { sessionInstanceId: null, lease: { state: "detached" } },
26615
26961
  pendingAdmissions: [],
26616
26962
  turnId: null,
26617
26963
  turnActive: false,
26618
26964
  lastProgressAt: 0,
26965
+ lastNativeActivityAt: 0,
26966
+ lastNativeActivityKind: null,
26967
+ runtimePhase: "idle",
26968
+ backendTurnId: null,
26969
+ turnSilence: DEFAULT_TURN_SILENCE_POLICY,
26619
26970
  lastDeliverAt: null,
26620
26971
  idleSince: null,
26621
26972
  stoppingSince: null,
@@ -26737,9 +27088,6 @@ function identitySection(config2) {
26737
27088
  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
27089
  }
26739
27090
  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
27091
  return parts.join(`
26744
27092
  `);
26745
27093
  }
@@ -26976,7 +27324,17 @@ function chaosAwarenessSection() {
26976
27324
  ].join(`
26977
27325
  `);
26978
27326
  }
26979
- function workspaceMemorySection() {
27327
+ function workspaceMemorySection(config2) {
27328
+ const roleSection = config2.description ? [
27329
+ "",
27330
+ "### Your bio",
27331
+ "",
27332
+ config2.description,
27333
+ "",
27334
+ "Your bio is the public description of your role that other people and agents see on " + "your Alook profile.",
27335
+ "",
27336
+ `You can change your own role and bio description with \`${CLI} setting profile ` + `--set-bio <text>\`.`
27337
+ ] : [];
26980
27338
  return [
26981
27339
  "## Self-awareness",
26982
27340
  "",
@@ -26985,6 +27343,7 @@ function workspaceMemorySection() {
26985
27343
  "**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
26986
27344
  "",
26987
27345
  "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.",
27346
+ ...roleSection,
26988
27347
  "",
26989
27348
  "### Napping",
26990
27349
  "",
@@ -27026,7 +27385,7 @@ function buildCliSystemPrompt(config2) {
27026
27385
  criticalRulesSection(),
27027
27386
  executionModelSection(),
27028
27387
  chaosAwarenessSection(),
27029
- workspaceMemorySection(),
27388
+ workspaceMemorySection(config2),
27030
27389
  utilsSection()
27031
27390
  ];
27032
27391
  return sections.filter((s) => s && s.length > 0).join(`
@@ -27294,8 +27653,8 @@ class AgentProcessManager {
27294
27653
  resumeSessions = new Map;
27295
27654
  launchIds = new Map;
27296
27655
  liveSessions = new Map;
27297
- thinkingBuffers = new Map;
27298
27656
  activeSpawnState = new Map;
27657
+ publishedAgentActivity = new Map;
27299
27658
  traceProcessNonce = randomUUID5();
27300
27659
  nextSpawnOrdinal = 1;
27301
27660
  nextDaemonTurnOrdinal = 1;
@@ -27309,7 +27668,8 @@ class AgentProcessManager {
27309
27668
  this.opts = {
27310
27669
  tickIntervalMs: 5000,
27311
27670
  staleThresholdMs: 120000,
27312
- idleTimeoutMs: 300000,
27671
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
27672
+ idleResetTimeoutMs: DEFAULT_IDLE_RESET_TIMEOUT_MS,
27313
27673
  resetStuckThresholdMs: 120000,
27314
27674
  stoppingStuckThresholdMs: DEFAULT_STOPPING_STUCK_THRESHOLD_MS,
27315
27675
  handshakeTimeoutMs: 60000,
@@ -27318,7 +27678,7 @@ class AgentProcessManager {
27318
27678
  };
27319
27679
  this.now = opts.now ?? (() => Date.now());
27320
27680
  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);
27681
+ this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs, this.opts.idleResetTimeoutMs);
27322
27682
  }
27323
27683
  register(agentId, launch) {
27324
27684
  if (launch?.runtimeConfig)
@@ -27337,11 +27697,19 @@ class AgentProcessManager {
27337
27697
  const effects = this.dispatch({ type: "wake", agentId, message: normalized, nowMs: this.now() });
27338
27698
  return effects.length > 0;
27339
27699
  }
27340
- forgetSession(agentId, barrierType = "reset_session") {
27700
+ forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
27701
+ if (!this.forgetSessionSources(agentId, barrierType, forgottenSessionId))
27702
+ return false;
27703
+ this.dispatch({ type: "reset_session", agentId });
27704
+ return true;
27705
+ }
27706
+ forgetSessionSources(agentId, barrierType, forgottenSessionId) {
27707
+ const persisted = this.opts.timeline?.forgetSession(agentId, barrierType, forgottenSessionId);
27708
+ if (persisted === false)
27709
+ return false;
27341
27710
  this.resumeSessions.delete(agentId);
27342
27711
  this.liveSessions.delete(agentId);
27343
- this.dispatch({ type: "reset_session", agentId });
27344
- this.opts.timeline?.forgetSession(agentId, barrierType);
27712
+ return true;
27345
27713
  }
27346
27714
  enqueueRewake(agentId, message2) {
27347
27715
  this.dispatch({ type: "rewake_after_reset", agentId, message: message2 });
@@ -27364,9 +27732,14 @@ class AgentProcessManager {
27364
27732
  });
27365
27733
  }
27366
27734
  async restartAgent(agentId, opts) {
27735
+ if (opts.forgetSession && !this.forgetSession(agentId, opts.barrierType ?? "reset_session")) {
27736
+ this.log.error("resume control transition failed; reset aborted", { agentId, barrierType: opts.barrierType });
27737
+ this.emitErrorAudit(agentId, "reset", "resume_control_update_failed", "Reset aborted because resume control could not be persisted");
27738
+ throw new Error("Reset aborted because resume control could not be persisted");
27739
+ }
27367
27740
  this.register(agentId, { runtimeConfig: opts.runtimeConfig, launchId: opts.launchId });
27368
- if (opts.forgetSession)
27369
- this.forgetSession(agentId, opts.barrierType ?? "reset_session");
27741
+ if (!opts.forgetSession)
27742
+ this.opts.timeline?.fenceSession(agentId);
27370
27743
  this.abortCurrentTurn(agentId, opts.abortCause);
27371
27744
  this.markResetting(agentId);
27372
27745
  const status = this.state.agents[agentId]?.status;
@@ -27435,12 +27808,19 @@ class AgentProcessManager {
27435
27808
  snapshot() {
27436
27809
  return this.state;
27437
27810
  }
27811
+ runningAgentCount() {
27812
+ return this.sessions.size;
27813
+ }
27438
27814
  auditContext(agentId) {
27439
27815
  return {
27440
27816
  sessionId: this.liveSessions.get(agentId) ?? null,
27441
27817
  launchId: this.launchIds.get(agentId) ?? null
27442
27818
  };
27443
27819
  }
27820
+ timelineTurnOwner(agentId) {
27821
+ const owner = this.traceOwnerFor(agentId);
27822
+ return owner?.timelineTurnOwner ? { ...owner.timelineTurnOwner } : null;
27823
+ }
27444
27824
  liveSessionReports() {
27445
27825
  return [...this.liveSessions.entries()].map(([agentId, sessionId]) => ({
27446
27826
  agentId,
@@ -27510,6 +27890,10 @@ class AgentProcessManager {
27510
27890
  if (!expectedSpan || owner.activeSpan !== expectedSpan)
27511
27891
  return false;
27512
27892
  owner.activeSpan = null;
27893
+ const timelineTurnOwner = owner.timelineTurnOwner;
27894
+ owner.timelineTurnOwner = null;
27895
+ if (timelineTurnOwner)
27896
+ this.opts.timeline?.finalizeTurn(owner.agentId, timelineTurnOwner);
27513
27897
  const nowMs = this.now();
27514
27898
  const base = {
27515
27899
  recordKind: "turn_span",
@@ -27567,6 +27951,13 @@ class AgentProcessManager {
27567
27951
  inbox: a.inbox.length,
27568
27952
  lastDeliverAt: a.lastDeliverAt,
27569
27953
  lastProgressAt: a.lastProgressAt,
27954
+ lastNativeActivityAt: a.lastNativeActivityAt,
27955
+ lastNativeActivityKind: a.lastNativeActivityKind,
27956
+ runtimePhase: a.runtimePhase,
27957
+ backendTurnId: a.backendTurnId,
27958
+ turnSilenceBudgetMs: a.turnSilence.normalBudgetMs,
27959
+ nativeDeadlineAt: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.nativeDeadlineAt : null,
27960
+ recoveryExtensionsUsed: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.recoveryExtensionsUsed : 0,
27570
27961
  idleSince: a.idleSince,
27571
27962
  resetting: a.resetting,
27572
27963
  resettingSince: a.resettingSince,
@@ -27577,6 +27968,7 @@ class AgentProcessManager {
27577
27968
  timeIso: new Date(nowMs).toISOString(),
27578
27969
  ...activeSpan ? activeSpan : {},
27579
27970
  sinceProgressMs: nowMs - a.lastProgressAt,
27971
+ sinceNativeActivityMs: nowMs - a.lastNativeActivityAt,
27580
27972
  sinceDeliverMs: a.lastDeliverAt === null ? null : nowMs - a.lastDeliverAt,
27581
27973
  sinceStoppingMs: a.stoppingSince === null ? null : nowMs - a.stoppingSince,
27582
27974
  ...event.type === "turn_completed" && event.endReason === "errored" ? {
@@ -27611,11 +28003,18 @@ class AgentProcessManager {
27611
28003
  terminationCause: normalizeTerminationCause(event.terminationCause)
27612
28004
  } : { event: "turn_end", outcome: "clean" });
27613
28005
  }
27614
- if (this.opts.onAgentActivity && event.type !== "spawned" && event.type !== "admission_started" && event.type !== "admission_settled") {
28006
+ if (this.opts.onAgentActivity && event.type !== "admission_started" && event.type !== "admission_settled") {
27615
28007
  const after = this.deriveActivitySnapshot(state);
27616
28008
  for (const [agentId, activity] of Object.entries(after)) {
27617
- if (agentId in before && before[agentId] !== activity) {
28009
+ if (!(agentId in before)) {
28010
+ this.publishedAgentActivity.set(agentId, activity);
28011
+ continue;
28012
+ }
28013
+ const previouslyPublished = this.publishedAgentActivity.get(agentId) ?? before[agentId];
28014
+ const publishable = event.type !== "spawned" || activity === "running";
28015
+ if (publishable && previouslyPublished !== activity) {
27618
28016
  this.opts.onAgentActivity({ agentId, state: activity });
28017
+ this.publishedAgentActivity.set(agentId, activity);
27619
28018
  }
27620
28019
  }
27621
28020
  }
@@ -27780,6 +28179,47 @@ ${this.opts.wakePromptFooter}` : text2;
27780
28179
  case "terminate_stalled": {
27781
28180
  const session2 = this.sessions.get(effect.agentId);
27782
28181
  const spawnState = this.activeSpawnState.get(effect.agentId);
28182
+ const endedSessionId = this.liveSessions.get(effect.agentId) ?? "";
28183
+ if (effect.type === "terminate_stalled" && effect.recordSessionId) {
28184
+ const persisted = this.opts.timeline?.recordSessionStall?.(effect.agentId, effect.recordSessionId);
28185
+ if (persisted === false) {
28186
+ this.dispatch({
28187
+ type: "stall_control_failed",
28188
+ agentId: effect.agentId,
28189
+ sessionId: effect.recordSessionId,
28190
+ transition: "attempt"
28191
+ });
28192
+ this.log.error("stall recovery attempt was not persisted; termination deferred", {
28193
+ agentId: effect.agentId,
28194
+ sessionId: effect.recordSessionId
28195
+ });
28196
+ this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the recovery attempt could not be persisted");
28197
+ break;
28198
+ }
28199
+ }
28200
+ if (effect.type === "terminate_stalled" && effect.forgetSessionId) {
28201
+ const persisted = this.forgetSessionSources(effect.agentId, "stall_recovery", effect.forgetSessionId);
28202
+ if (!persisted) {
28203
+ this.dispatch({
28204
+ type: "stall_control_failed",
28205
+ agentId: effect.agentId,
28206
+ sessionId: effect.forgetSessionId,
28207
+ transition: "fence"
28208
+ });
28209
+ this.log.error("repeated-session fence was not persisted; termination deferred", {
28210
+ agentId: effect.agentId,
28211
+ sessionId: effect.forgetSessionId
28212
+ });
28213
+ this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the exact session fence could not be persisted");
28214
+ break;
28215
+ }
28216
+ if (spawnState)
28217
+ spawnState.discardEvents = true;
28218
+ this.log.warn("repeatedly stalled backend session fenced", {
28219
+ agentId: effect.agentId,
28220
+ sessionId: effect.forgetSessionId
28221
+ });
28222
+ }
27783
28223
  if (effect.type === "terminate_stalled" && spawnState) {
27784
28224
  this.closeTurn(spawnState, spawnState.activeSpan, {
27785
28225
  event: "turn_abort",
@@ -27798,10 +28238,27 @@ ${this.opts.wakePromptFooter}` : text2;
27798
28238
  if (spawnState) {
27799
28239
  spawnState.terminationSemantics = effect.type === "terminate_stalled" ? "killed_stalled" : "idle_stop";
27800
28240
  }
27801
- this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled");
28241
+ this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled", endedSessionId);
27802
28242
  this.opts.onAgentLocallyStopped?.({ agentId: effect.agentId, reason: effect.type });
27803
28243
  break;
27804
28244
  }
28245
+ case "clear_stall_recovery": {
28246
+ const persisted = this.opts.timeline?.clearSessionStall?.(effect.agentId, effect.sessionId);
28247
+ if (persisted === false) {
28248
+ this.dispatch({
28249
+ type: "stall_control_failed",
28250
+ agentId: effect.agentId,
28251
+ sessionId: effect.sessionId,
28252
+ transition: "clear"
28253
+ });
28254
+ this.log.error("stall recovery clear was not persisted; allowance remains consumed", {
28255
+ agentId: effect.agentId,
28256
+ sessionId: effect.sessionId
28257
+ });
28258
+ this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall recovery allowance remains consumed because its clear could not be persisted");
28259
+ }
28260
+ break;
28261
+ }
27805
28262
  case "expire_admission": {
27806
28263
  const owner = this.activeSpawnState.get(effect.agentId);
27807
28264
  if (!owner || owner.sessionInstanceId !== effect.sessionInstanceId)
@@ -27824,6 +28281,26 @@ ${this.opts.wakePromptFooter}` : text2;
27824
28281
  mode: effect.mode
27825
28282
  });
27826
28283
  break;
28284
+ case "reset_idle_session": {
28285
+ const spawnState = this.activeSpawnState.get(effect.agentId);
28286
+ const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId);
28287
+ if (!persisted) {
28288
+ this.log.error("idle session reset barrier was not persisted; reset deferred", {
28289
+ agentId: effect.agentId,
28290
+ sessionId: effect.sessionId
28291
+ });
28292
+ this.emitErrorAudit(effect.agentId, "reset", "resume_control_update_failed", "Idle session reset deferred because resume control could not be persisted");
28293
+ break;
28294
+ }
28295
+ if (spawnState)
28296
+ spawnState.discardEvents = true;
28297
+ this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
28298
+ this.log.info("idle agent session reset", {
28299
+ agentId: effect.agentId,
28300
+ sessionId: effect.sessionId
28301
+ });
28302
+ break;
28303
+ }
27827
28304
  case "force_exit": {
27828
28305
  const session2 = this.sessions.get(effect.agentId);
27829
28306
  const state = this.activeSpawnState.get(effect.agentId);
@@ -27856,8 +28333,8 @@ ${this.opts.wakePromptFooter}` : text2;
27856
28333
  }
27857
28334
  }
27858
28335
  }
27859
- logSessionEnded(agentId, reason) {
27860
- this.log.info("agent session ended", { agentId, sessionId: this.liveSessions.get(agentId) ?? "", reason });
28336
+ logSessionEnded(agentId, reason, sessionId = this.liveSessions.get(agentId) ?? "") {
28337
+ this.log.info("agent session ended", { agentId, sessionId, reason });
27861
28338
  }
27862
28339
  doSpawn(agentId, messages, resumeSessionId) {
27863
28340
  const [first, ...pending] = messages;
@@ -27882,7 +28359,13 @@ ${this.opts.wakePromptFooter}` : text2;
27882
28359
  mode: { kind: "default" }
27883
28360
  };
27884
28361
  const provider = runtimeConfig.runtime;
27885
- const sessionId = resumeSessionId ?? this.resumeSessions.get(agentId) ?? this.opts.timeline?.resumeSessionId(agentId, provider) ?? base.config?.sessionId;
28362
+ const timelineResolution = this.opts.timeline?.resolveResumeSession?.(agentId, provider);
28363
+ const timelineSessionId = timelineResolution?.kind === "session" ? timelineResolution.sessionId : timelineResolution === undefined ? this.opts.timeline?.resumeSessionId(agentId, provider) : null;
28364
+ const candidateSessionId = resumeSessionId ?? this.resumeSessions.get(agentId) ?? timelineSessionId ?? base.config?.sessionId;
28365
+ const blockedByBarrier = timelineResolution?.kind === "barrier" && (timelineResolution.type !== "stall_recovery" || timelineResolution.forgottenSessionId === null || timelineResolution.forgottenSessionId === candidateSessionId);
28366
+ const blockedByExactFence = candidateSessionId !== undefined && timelineResolution?.fencedSessionId === candidateSessionId;
28367
+ const sessionId = blockedByBarrier || blockedByExactFence ? undefined : candidateSessionId;
28368
+ const stalledSessionIdAtLaunch = timelineResolution !== undefined && (timelineResolution.kind === "session" || timelineResolution.kind === "none") && timelineResolution.stalledSessionId === sessionId ? timelineResolution.stalledSessionId : null;
27886
28369
  const description = runtimeConfig.instruction ?? base.config?.description ?? runtimeConfig.agentName;
27887
28370
  const agentName = runtimeConfig.agentName ?? base.config?.agentName;
27888
28371
  const agentHandle = runtimeConfig.agentHandle ?? base.config?.agentHandle;
@@ -27913,12 +28396,15 @@ ${this.opts.wakePromptFooter}` : text2;
27913
28396
  handshakeTimer: null,
27914
28397
  torndown: false,
27915
28398
  superseded: false,
28399
+ discardEvents: false,
28400
+ stalledSessionIdAtLaunch,
27916
28401
  spawnFailureReason: null,
27917
28402
  terminationSemantics: null,
27918
28403
  spawnOrdinal: this.nextSpawnOrdinal++,
27919
28404
  launchIdSnapshot: typeof ctx.launchId === "string" && ctx.launchId.length > 0 ? ctx.launchId : null,
27920
28405
  nextTurnOrdinal: 1,
27921
28406
  activeSpan: null,
28407
+ timelineTurnOwner: null,
27922
28408
  pendingDeliverySpans: new Map
27923
28409
  };
27924
28410
  const previousOwner = this.activeSpawnState.get(agentId);
@@ -27969,7 +28455,6 @@ ${this.opts.wakePromptFooter}` : text2;
27969
28455
  this.emitErrorAudit(agentId, "exit", "abnormal_exit", `Session ended unexpectedly (${detail})`);
27970
28456
  }
27971
28457
  }
27972
- this.flushThinkingAudit(agentId);
27973
28458
  if (state.sessionInstanceId) {
27974
28459
  this.dispatch({ type: "session_closed", agentId, sessionInstanceId: state.sessionInstanceId }, state);
27975
28460
  }
@@ -27994,10 +28479,23 @@ ${this.opts.wakePromptFooter}` : text2;
27994
28479
  const onEvent = (event) => {
27995
28480
  if (state.torndown)
27996
28481
  return;
28482
+ if (state.discardEvents) {
28483
+ this.log.warn("ignored event from discarded backend session owner", { agentId, event: event.type });
28484
+ return;
28485
+ }
27997
28486
  if (event.type === "session_failed" && !state.hasEstablished) {
27998
28487
  reportSpawnFailure(event.error.code || "failed_to_start", { message: event.error.message });
27999
28488
  }
28000
28489
  if (event.type === "session_started") {
28490
+ const persisted = this.opts.timeline?.setSession(agentId, event.backendSessionId, event.sessionInstanceId);
28491
+ if (persisted === false) {
28492
+ state.discardEvents = true;
28493
+ reportSpawnFailure("resume_control_update_failed", {
28494
+ message: "Backend session rejected because resume control could not be persisted"
28495
+ });
28496
+ state.session?.stop({ reason: "shutdown", forceAfterMs: SESSION_STOP_GRACE_MS2 });
28497
+ return;
28498
+ }
28001
28499
  state.hasEstablished = true;
28002
28500
  clearHandshakeTimer();
28003
28501
  this.opts.onRuntimeSessionEstablished?.(driver.id);
@@ -28016,7 +28514,8 @@ ${this.opts.wakePromptFooter}` : text2;
28016
28514
  type: "attach_session",
28017
28515
  agentId,
28018
28516
  sessionInstanceId: session2.sessionInstanceId,
28019
- nowMs: this.now()
28517
+ nowMs: this.now(),
28518
+ turnSilence: session2.snapshot().diagnostics?.turnSilence
28020
28519
  }, state);
28021
28520
  previousOwner?.pendingDeliverySpans.clear();
28022
28521
  (async () => {
@@ -28164,26 +28663,6 @@ ${this.opts.wakePromptFooter}` : text2;
28164
28663
  this.log.debug("audit emit failed (error)", { agentId, err: String(err) });
28165
28664
  }
28166
28665
  }
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
28666
  onAgentEvent(agentId, event, runtimeId, owner) {
28188
28667
  if (event.type === "session_closed")
28189
28668
  return;
@@ -28216,32 +28695,43 @@ ${this.opts.wakePromptFooter}` : text2;
28216
28695
  }
28217
28696
  }
28218
28697
  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);
28698
+ if (event.type === "assistant_reasoning_completed" && event.text.length > 0) {
28699
+ const { text: text2, truncated, chars } = truncateThinking(event.text);
28700
+ try {
28701
+ this.opts.onBotAuditEvent(agentId, {
28702
+ kind: "thinking",
28703
+ payload: { text: text2, truncated: truncated || event.truncated, chars }
28704
+ }, {
28705
+ sessionId: this.liveSessions.get(agentId) ?? null,
28706
+ launchId: this.launchIds.get(agentId) ?? null
28707
+ });
28708
+ } catch (err) {
28709
+ this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
28222
28710
  }
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
- }
28711
+ }
28712
+ if (event.type === "tool_started") {
28713
+ const audit = extractToolAudit(event.name, event.input);
28714
+ if (!audit.suppressed) {
28715
+ const payload = audit.target !== undefined ? { name: audit.name, target: audit.target } : { name: audit.name };
28716
+ try {
28717
+ this.opts.onBotAuditEvent(agentId, { kind: "tool_call", payload }, {
28718
+ sessionId: this.liveSessions.get(agentId) ?? null,
28719
+ launchId: this.launchIds.get(agentId) ?? null
28720
+ });
28721
+ } catch (err) {
28722
+ this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
28237
28723
  }
28238
28724
  }
28239
28725
  }
28240
28726
  }
28241
28727
  if (event.type === "session_started") {
28242
- this.dispatch({ type: "backend_session", agentId, sessionId: event.backendSessionId }, owner);
28728
+ this.dispatch({
28729
+ type: "backend_session",
28730
+ agentId,
28731
+ sessionId: event.backendSessionId,
28732
+ stalledBefore: owner.stalledSessionIdAtLaunch === event.backendSessionId
28733
+ }, owner);
28243
28734
  this.liveSessions.set(agentId, event.backendSessionId);
28244
- this.opts.timeline?.setSession(agentId, event.backendSessionId);
28245
28735
  this.opts.onAgentSession?.({
28246
28736
  agentId,
28247
28737
  sessionId: event.backendSessionId,
@@ -28253,8 +28743,20 @@ ${this.opts.wakePromptFooter}` : text2;
28253
28743
  runtime: runtimeId
28254
28744
  });
28255
28745
  }
28256
- if (event.type === "text_delta" && event.text.length > 0) {
28257
- this.opts.timeline?.appendResponseToLatest(agentId, event.text);
28746
+ if (event.type === "turn_started") {
28747
+ const timelineTurnOwner = {
28748
+ sessionInstanceId: event.sessionInstanceId,
28749
+ rootTurnId: event.turnId,
28750
+ barrierGeneration: this.opts.timeline?.barrierGeneration(agentId) ?? 0
28751
+ };
28752
+ owner.timelineTurnOwner = timelineTurnOwner;
28753
+ this.opts.timeline?.beginTurn(agentId, timelineTurnOwner);
28754
+ }
28755
+ if (event.type === "assistant_message_completed" && event.text.length > 0) {
28756
+ const timelineTurnOwner = owner.timelineTurnOwner;
28757
+ if (timelineTurnOwner && timelineTurnOwner.sessionInstanceId === event.sessionInstanceId && timelineTurnOwner.rootTurnId === event.turnId) {
28758
+ this.opts.timeline?.recordAssistantMessage(agentId, timelineTurnOwner, event.text, event.truncated);
28759
+ }
28258
28760
  }
28259
28761
  if (event.type === "command_queued")
28260
28762
  this.acknowledgePendingDelivery(owner, event.commandId);
@@ -28272,9 +28774,10 @@ ${this.opts.wakePromptFooter}` : text2;
28272
28774
  switch (event.type) {
28273
28775
  case "turn_started":
28274
28776
  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;
28777
+ case "work_heartbeat":
28778
+ case "assistant_reasoning_completed":
28779
+ case "assistant_message_completed":
28780
+ return { type: "turn_work", turnId: event.turnId };
28278
28781
  case "tool_started":
28279
28782
  return { type: "turn_tool_started", turnId: event.turnId };
28280
28783
  case "tool_finished":
@@ -28301,37 +28804,55 @@ ${this.opts.wakePromptFooter}` : text2;
28301
28804
  if (!wasActive && this.state.agents[agentId]?.turnActive && !owner.activeSpan)
28302
28805
  this.openTurn(owner);
28303
28806
  }
28304
- const signalKind = (() => {
28807
+ const nativeSignal = (() => {
28305
28808
  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";
28809
+ case "turn_started":
28810
+ return { kind: "turn_started", phase: "inference", turnId: event.turnId };
28811
+ case "backend_turn_started":
28812
+ return {
28813
+ kind: "backend_turn_started",
28814
+ phase: "inference",
28815
+ turnId: event.turnId,
28816
+ backendTurnId: event.backendTurnId
28817
+ };
28818
+ case "assistant_reasoning_completed":
28819
+ return { kind: "thinking", phase: "inference", turnId: event.turnId };
28820
+ case "assistant_message_completed":
28821
+ return { kind: "text", phase: "inference", turnId: event.turnId };
28822
+ case "work_heartbeat":
28823
+ return { kind: "internal_progress", phase: "inference", turnId: event.turnId };
28312
28824
  case "tool_started":
28313
- return "tool_call";
28825
+ return { kind: "tool_call", phase: "tool", turnId: event.turnId };
28314
28826
  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";
28827
+ return { kind: "tool_output", phase: "inference", turnId: event.turnId };
28828
+ case "compaction_started":
28829
+ case "compaction_finished":
28830
+ case "review_started":
28831
+ case "review_finished":
28832
+ case "internal_progress":
28833
+ return event.turnId ? { kind: "internal_progress", phase: "inference", turnId: event.turnId } : null;
28834
+ case "recovery":
28835
+ return event.turnId ? {
28836
+ kind: "recovery",
28837
+ phase: event.stage === "retrying" ? "recovery" : "inference",
28838
+ recoveryStage: event.stage,
28839
+ turnId: event.turnId
28840
+ } : null;
28321
28841
  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";
28842
+ return { kind: "turn_end", phase: "terminal", turnId: event.turnId };
28330
28843
  default:
28331
- return event.type;
28844
+ return null;
28332
28845
  }
28333
28846
  })();
28334
- this.dispatch({ type: "runtime_signal", agentId, kind: signalKind, nowMs: this.now() }, owner);
28847
+ if (nativeSignal) {
28848
+ this.dispatch({
28849
+ type: "runtime_signal",
28850
+ agentId,
28851
+ sessionInstanceId: event.sessionInstanceId,
28852
+ ...nativeSignal,
28853
+ nowMs: this.now()
28854
+ }, owner);
28855
+ }
28335
28856
  if (event.type === "turn_completed") {
28336
28857
  this.logSessionEnded(agentId, "turn_end");
28337
28858
  const marker = this.nonCleanEndMarker.get(agentId);
@@ -28411,6 +28932,7 @@ Then read @memory.md and your .context_timeline for durable context, and pull `
28411
28932
  class AgentRouter {
28412
28933
  opts;
28413
28934
  running = new Set;
28935
+ nextWakeAdmissionOrdinal = 1;
28414
28936
  runtimes = new Map;
28415
28937
  pendingResend = false;
28416
28938
  scheduleResend;
@@ -28571,7 +29093,7 @@ class AgentRouter {
28571
29093
  this.opts.typingTracker?.add(cmd.agentId, channelScope);
28572
29094
  const text2 = (this.opts.formatUnreadNoticeText ?? defaultFormatUnreadNoticeText)(cmd.unreadNotice);
28573
29095
  const producedEffect = this.opts.manager.deliver(cmd.agentId, {
28574
- id: `${cmd.agentId}:wake:${cmd.unreadNotice.channel}:${cmd.unreadNotice.latestSeq}`,
29096
+ id: `${cmd.agentId}:wake:${cmd.unreadNotice.channel}:${cmd.unreadNotice.latestSeq}:admission:${this.nextWakeAdmissionOrdinal++}`,
28575
29097
  seq: cmd.unreadNotice.latestSeq,
28576
29098
  text: text2
28577
29099
  });
@@ -28726,7 +29248,7 @@ function createTypingScopeTracker() {
28726
29248
  }
28727
29249
  // src/timeline/timeline.ts
28728
29250
  import * as fs9 from "node:fs";
28729
- import { randomBytes as randomBytes4 } from "node:crypto";
29251
+ import { createHash as createHash2, randomBytes as randomBytes4 } from "node:crypto";
28730
29252
  import { basename as basename3, dirname as dirname5, join as join11 } from "node:path";
28731
29253
 
28732
29254
  // src/timeline/filelock.ts
@@ -28793,17 +29315,25 @@ function reclaim(lockPath) {
28793
29315
  var TIMELINE_MAX_BYTES = 1048576;
28794
29316
  var TIMELINE_READ_CHUNK_BYTES = 65536;
28795
29317
  var DATE_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
29318
+ var RESUME_CONTROL_FILENAME = ".resume-control.json";
29319
+ var RESUME_CONTROL_MAX_BYTES = 4096;
29320
+ var EMPTY_RESUME_CONTROL = {
29321
+ version: 1,
29322
+ attemptedSessionId: null,
29323
+ fencedSessionId: null,
29324
+ fullBarrier: null
29325
+ };
28796
29326
  function isBarrier(entry) {
28797
- return entry.system?.type === "reset_session" || entry.system?.type === "nap";
29327
+ return entry.system !== undefined;
28798
29328
  }
28799
29329
  function canonicalTimelineEntry(value) {
28800
29330
  if (!value || typeof value !== "object")
28801
29331
  return null;
28802
29332
  const entry = value;
28803
29333
  if (entry.system) {
28804
- if (entry.system.type !== "reset_session" && entry.system.type !== "nap" || typeof entry.system.time !== "string")
29334
+ 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
29335
  return null;
28806
- return createSystemEntry(entry.system.type, entry.system.time);
29336
+ return createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id);
28807
29337
  }
28808
29338
  if (entry.session_id !== null && typeof entry.session_id !== "string")
28809
29339
  return null;
@@ -28821,13 +29351,60 @@ function canonicalTimelineEntry(value) {
28821
29351
  };
28822
29352
  }
28823
29353
  function timelineLine(entry) {
28824
- const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
29354
+ 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
29355
  const text2 = JSON.stringify(boundedEntry);
28826
29356
  const bytes = Buffer.byteLength(text2, "utf8") + 1;
28827
29357
  if (bytes > TIMELINE_MAX_BYTES)
28828
29358
  return null;
28829
29359
  return { text: text2, bytes, entry: boundedEntry, barrier: isBarrier(boundedEntry) };
28830
29360
  }
29361
+ function timelineRowHash(line) {
29362
+ return createHash2("sha256").update(line.text, "utf8").digest("hex");
29363
+ }
29364
+ function timelineFileGeneration(filePath, lines) {
29365
+ try {
29366
+ const stat = fs9.lstatSync(filePath, { bigint: true });
29367
+ if (!stat.isFile())
29368
+ return null;
29369
+ const digest = createHash2("sha256");
29370
+ digest.update(`${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}
29371
+ `);
29372
+ for (const line of lines)
29373
+ digest.update(line.text, "utf8").update(`
29374
+ `);
29375
+ return digest.digest("hex");
29376
+ } catch {
29377
+ return null;
29378
+ }
29379
+ }
29380
+ function handleFor(filename, generation, lines, rowOrdinal) {
29381
+ return {
29382
+ filename,
29383
+ fileGeneration: generation,
29384
+ rowOrdinal,
29385
+ expectedHash: timelineRowHash(lines[rowOrdinal])
29386
+ };
29387
+ }
29388
+ function refreshTimelineEntryHandle(handle, rewrite) {
29389
+ if (handle.filename !== rewrite.filename)
29390
+ return handle;
29391
+ if (handle.fileGeneration !== rewrite.previousFileGeneration)
29392
+ return null;
29393
+ if (rewrite.previousRowHashes[handle.rowOrdinal] !== handle.expectedHash)
29394
+ return null;
29395
+ const rowOrdinal = rewrite.rowOrdinals[handle.rowOrdinal];
29396
+ if (rowOrdinal === null || rowOrdinal === undefined)
29397
+ return null;
29398
+ const expectedHash = rewrite.rowHashes[rowOrdinal];
29399
+ if (!expectedHash)
29400
+ return null;
29401
+ return {
29402
+ filename: handle.filename,
29403
+ fileGeneration: rewrite.fileGeneration,
29404
+ rowOrdinal,
29405
+ expectedHash
29406
+ };
29407
+ }
28831
29408
  function compactLines(input) {
28832
29409
  let head = 0;
28833
29410
  let bytes = input.reduce((total, line) => total + line.bytes, 0);
@@ -29039,11 +29616,37 @@ function atomicReplaceTimeline(filePath, lines) {
29039
29616
  } catch {}
29040
29617
  }
29041
29618
  }
29042
- function writeRequiredTimeline(filePath, input, required2) {
29619
+ function writeTrackedTimeline(filePath, filename, existing, input, required2, replacement) {
29620
+ const previousFileGeneration = timelineFileGeneration(filePath, existing);
29621
+ const previousRowHashes = existing.map(timelineRowHash);
29043
29622
  const compacted = compactLines(input);
29044
- if (!compacted.includes(required2))
29045
- return false;
29046
- return atomicReplaceTimeline(filePath, compacted);
29623
+ const targetOrdinal = compacted.indexOf(required2);
29624
+ if (targetOrdinal < 0)
29625
+ return { status: "rejected", reason: "evicted" };
29626
+ if (!atomicReplaceTimeline(filePath, compacted))
29627
+ return { status: "rejected", reason: "write" };
29628
+ const fileGeneration = timelineFileGeneration(filePath, compacted);
29629
+ if (!fileGeneration)
29630
+ return { status: "rejected", reason: "write" };
29631
+ const rowOrdinals = existing.map((line, oldOrdinal) => {
29632
+ if (replacement?.oldOrdinal === oldOrdinal)
29633
+ return compacted.indexOf(replacement.line);
29634
+ const nextOrdinal = compacted.indexOf(line);
29635
+ return nextOrdinal < 0 ? null : nextOrdinal;
29636
+ });
29637
+ const rewrite = {
29638
+ filename,
29639
+ previousFileGeneration,
29640
+ fileGeneration,
29641
+ previousRowHashes,
29642
+ rowOrdinals,
29643
+ rowHashes: compacted.map(timelineRowHash)
29644
+ };
29645
+ return {
29646
+ status: "written",
29647
+ rewrite,
29648
+ handle: handleFor(filename, fileGeneration, compacted, targetOrdinal)
29649
+ };
29047
29650
  }
29048
29651
  function filenameForDate(date5) {
29049
29652
  const y = date5.getFullYear();
@@ -29075,114 +29678,181 @@ function readRecentEntries(timelineDir, opts = {}) {
29075
29678
  }
29076
29679
  return entries;
29077
29680
  }
29078
- function appendEntry(timelineDir, entry, now = new Date) {
29681
+ function readResumeControlState(timelineDir) {
29682
+ if (timelineDirectoryState(timelineDir) !== "safe")
29683
+ return { kind: "missing" };
29684
+ const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
29685
+ let source;
29686
+ try {
29687
+ source = fs9.lstatSync(filePath);
29688
+ } catch (error51) {
29689
+ return error51.code === "ENOENT" ? { kind: "missing" } : { kind: "invalid" };
29690
+ }
29691
+ if (!source.isFile() || source.size <= 0 || source.size > RESUME_CONTROL_MAX_BYTES) {
29692
+ return { kind: "invalid" };
29693
+ }
29694
+ let fd = null;
29695
+ try {
29696
+ fd = fs9.openSync(filePath, fs9.constants.O_RDONLY | (fs9.constants.O_NOFOLLOW ?? 0));
29697
+ const stat = fs9.fstatSync(fd);
29698
+ if (!stat.isFile() || stat.size <= 0 || stat.size > RESUME_CONTROL_MAX_BYTES) {
29699
+ return { kind: "invalid" };
29700
+ }
29701
+ const bounded = Buffer.allocUnsafe(stat.size + 1);
29702
+ let bytesRead = 0;
29703
+ while (bytesRead < bounded.length) {
29704
+ const count = fs9.readSync(fd, bounded, bytesRead, bounded.length - bytesRead, bytesRead);
29705
+ if (count <= 0)
29706
+ break;
29707
+ bytesRead += count;
29708
+ }
29709
+ if (bytesRead !== stat.size)
29710
+ return { kind: "invalid" };
29711
+ const raw = bounded.subarray(0, bytesRead).toString("utf8");
29712
+ const value = JSON.parse(raw);
29713
+ const validSessionId = (candidate) => candidate === null || typeof candidate === "string" && candidate.length > 0 && candidate.length <= 512;
29714
+ if (value.version !== 1 || !validSessionId(value.attemptedSessionId) || !validSessionId(value.fencedSessionId) || value.fullBarrier !== null && value.fullBarrier !== "reset_session" && value.fullBarrier !== "nap")
29715
+ return { kind: "invalid" };
29716
+ return {
29717
+ kind: "state",
29718
+ state: {
29719
+ version: 1,
29720
+ attemptedSessionId: value.attemptedSessionId,
29721
+ fencedSessionId: value.fencedSessionId,
29722
+ fullBarrier: value.fullBarrier
29723
+ }
29724
+ };
29725
+ } catch {
29726
+ return { kind: "invalid" };
29727
+ } finally {
29728
+ if (fd !== null) {
29729
+ try {
29730
+ fs9.closeSync(fd);
29731
+ } catch {}
29732
+ }
29733
+ }
29734
+ }
29735
+ function updateResumeControlState(timelineDir, update) {
29079
29736
  if (timelineDirectoryState(timelineDir) !== "safe")
29080
29737
  return false;
29081
- const filename = filenameForDate(now);
29082
- const filePath = join11(timelineDir, filename);
29083
- const lockPath = lockPathFor(timelineDir, filename);
29738
+ const lockPath = lockPathFor(timelineDir, RESUME_CONTROL_FILENAME);
29084
29739
  if (!acquireLock(lockPath))
29085
29740
  return false;
29086
29741
  try {
29087
- const existing = scanTimelineFile(filePath);
29088
- const required2 = timelineLine(entry);
29089
- if (!existing || !required2)
29742
+ const current = readResumeControlState(timelineDir);
29743
+ const base = current.kind === "state" ? current.state : EMPTY_RESUME_CONTROL;
29744
+ const next = update({ ...base });
29745
+ const canonical = {
29746
+ version: 1,
29747
+ attemptedSessionId: next.attemptedSessionId,
29748
+ fencedSessionId: next.fencedSessionId,
29749
+ fullBarrier: next.fullBarrier
29750
+ };
29751
+ const body = JSON.stringify(canonical) + `
29752
+ `;
29753
+ if (Buffer.byteLength(body, "utf8") > RESUME_CONTROL_MAX_BYTES)
29090
29754
  return false;
29091
- return writeRequiredTimeline(filePath, [...existing, required2], required2);
29755
+ const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
29756
+ const tempPath = join11(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes4(12).toString("hex")}.tmp`);
29757
+ let fd = null;
29758
+ try {
29759
+ fd = fs9.openSync(tempPath, "wx", 384);
29760
+ fs9.writeFileSync(fd, body, "utf8");
29761
+ fs9.fsyncSync(fd);
29762
+ fs9.closeSync(fd);
29763
+ fd = null;
29764
+ fs9.renameSync(tempPath, filePath);
29765
+ return true;
29766
+ } finally {
29767
+ if (fd !== null) {
29768
+ try {
29769
+ fs9.closeSync(fd);
29770
+ } catch {}
29771
+ }
29772
+ try {
29773
+ fs9.unlinkSync(tempPath);
29774
+ } catch {}
29775
+ }
29092
29776
  } catch {
29093
29777
  return false;
29094
29778
  } finally {
29095
29779
  releaseLock(lockPath);
29096
29780
  }
29097
29781
  }
29098
- function appendOrMergeEntry(timelineDir, entry, now = new Date) {
29782
+ function appendTrackedEntry(timelineDir, entry, now = new Date) {
29099
29783
  if (timelineDirectoryState(timelineDir) !== "safe")
29100
- return false;
29784
+ return { status: "rejected", reason: "unsafe" };
29101
29785
  const filename = filenameForDate(now);
29102
29786
  const filePath = join11(timelineDir, filename);
29103
29787
  const lockPath = lockPathFor(timelineDir, filename);
29104
- if (!acquireLock(lockPath))
29105
- return false;
29788
+ try {
29789
+ if (!acquireLock(lockPath))
29790
+ return { status: "rejected", reason: "lock" };
29791
+ } catch {
29792
+ return { status: "rejected", reason: "write" };
29793
+ }
29106
29794
  try {
29107
29795
  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
29796
  const required2 = timelineLine(entry);
29797
+ if (!existing)
29798
+ return { status: "rejected", reason: "unsafe" };
29126
29799
  if (!required2)
29127
- return false;
29128
- return writeRequiredTimeline(filePath, [...existing, required2], required2);
29800
+ return { status: "rejected", reason: "oversized" };
29801
+ return writeTrackedTimeline(filePath, filename, existing, [...existing, required2], required2);
29129
29802
  } catch {
29130
- return false;
29803
+ return { status: "rejected", reason: "write" };
29131
29804
  } finally {
29132
29805
  releaseLock(lockPath);
29133
29806
  }
29134
29807
  }
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);
29808
+ function updateTrackedEntry(timelineDir, handle, update) {
29809
+ if (timelineDirectoryState(timelineDir) !== "safe")
29810
+ return { status: "rejected", reason: "unsafe" };
29811
+ if (!DATE_FILENAME_PATTERN.test(handle.filename) || basename3(handle.filename) !== handle.filename) {
29812
+ return { status: "rejected", reason: "unsafe" };
29813
+ }
29814
+ const filePath = join11(timelineDir, handle.filename);
29815
+ const lockPath = lockPathFor(timelineDir, handle.filename);
29816
+ try {
29154
29817
  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
- }
29818
+ return { status: "rejected", reason: "lock" };
29819
+ } catch {
29820
+ return { status: "rejected", reason: "write" };
29821
+ }
29822
+ try {
29823
+ const existing = scanTimelineFile(filePath);
29824
+ if (!existing)
29825
+ return { status: "rejected", reason: "unsafe" };
29826
+ if (existing.length === 0)
29827
+ return { status: "rejected", reason: "missing" };
29828
+ const generation = timelineFileGeneration(filePath, existing);
29829
+ if (!generation || generation !== handle.fileGeneration) {
29830
+ return { status: "rejected", reason: "generation" };
29831
+ }
29832
+ const captured = existing[handle.rowOrdinal];
29833
+ if (!captured)
29834
+ return { status: "rejected", reason: "ordinal" };
29835
+ if (timelineRowHash(captured) !== handle.expectedHash) {
29836
+ return { status: "rejected", reason: "hash" };
29837
+ }
29838
+ if (captured.entry.system)
29839
+ return { status: "rejected", reason: "system" };
29840
+ const nextEntry = update({
29841
+ ...captured.entry,
29842
+ messages: [...captured.entry.messages],
29843
+ agent_responses: [...captured.entry.agent_responses]
29844
+ });
29845
+ const replacement = timelineLine(nextEntry);
29846
+ if (!replacement)
29847
+ return { status: "rejected", reason: "oversized" };
29848
+ const input = [...existing];
29849
+ input[handle.rowOrdinal] = replacement;
29850
+ return writeTrackedTimeline(filePath, handle.filename, existing, input, replacement, { oldOrdinal: handle.rowOrdinal, line: replacement });
29851
+ } catch {
29852
+ return { status: "rejected", reason: "write" };
29853
+ } finally {
29854
+ releaseLock(lockPath);
29184
29855
  }
29185
- return "missing";
29186
29856
  }
29187
29857
  function yieldToEventLoop() {
29188
29858
  return new Promise((resolve4) => setImmediate(resolve4));
@@ -29246,84 +29916,562 @@ function createTimelineEntry(fields) {
29246
29916
  provider: fields.provider ?? null
29247
29917
  };
29248
29918
  }
29249
- function createSystemEntry(type, time3) {
29919
+ function createSystemEntry(type, time3, backendSessionId) {
29250
29920
  return {
29251
29921
  session_id: null,
29252
29922
  messages: [],
29253
29923
  agent_responses: [],
29254
29924
  provider: null,
29255
- system: { type, time: time3 }
29925
+ system: {
29926
+ type,
29927
+ time: time3,
29928
+ ...backendSessionId ? { backend_session_id: backendSessionId } : {}
29929
+ }
29256
29930
  };
29257
29931
  }
29258
- function findResumableSession(rows, provider) {
29932
+ function resolveResumableSession(rows, provider) {
29933
+ let candidateSessionId = null;
29934
+ let recoveryMarkerSeen = false;
29935
+ let stalledSessionId = null;
29936
+ let fencedSessionId = null;
29259
29937
  for (let i = rows.length - 1;i >= 0; i--) {
29260
29938
  const e = rows[i];
29261
- if (e.system?.type === "reset_session" || e.system?.type === "nap")
29262
- return null;
29939
+ if (e.system) {
29940
+ if (e.system.type === "stall_recovery_attempt") {
29941
+ if (!recoveryMarkerSeen) {
29942
+ recoveryMarkerSeen = true;
29943
+ stalledSessionId = e.system.backend_session_id ?? null;
29944
+ }
29945
+ continue;
29946
+ }
29947
+ if (e.system.type === "stall_recovery_clear") {
29948
+ if (!recoveryMarkerSeen)
29949
+ recoveryMarkerSeen = true;
29950
+ continue;
29951
+ }
29952
+ if (e.system.type === "stall_recovery" && fencedSessionId === null) {
29953
+ fencedSessionId = e.system.backend_session_id ?? null;
29954
+ }
29955
+ if (candidateSessionId !== null) {
29956
+ return {
29957
+ kind: "session",
29958
+ sessionId: candidateSessionId,
29959
+ stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
29960
+ fencedSessionId
29961
+ };
29962
+ }
29963
+ return {
29964
+ kind: "barrier",
29965
+ type: e.system.type,
29966
+ forgottenSessionId: e.system.backend_session_id ?? null,
29967
+ fencedSessionId
29968
+ };
29969
+ }
29263
29970
  if (!e.session_id)
29264
29971
  continue;
29265
29972
  if (provider && e.provider !== provider)
29266
29973
  continue;
29267
- return e.session_id;
29974
+ if (candidateSessionId === null) {
29975
+ candidateSessionId = e.session_id;
29976
+ continue;
29977
+ }
29978
+ if (candidateSessionId !== e.session_id)
29979
+ break;
29268
29980
  }
29269
- return null;
29981
+ if (candidateSessionId !== null) {
29982
+ return {
29983
+ kind: "session",
29984
+ sessionId: candidateSessionId,
29985
+ stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
29986
+ fencedSessionId
29987
+ };
29988
+ }
29989
+ return { kind: "none", stalledSessionId, fencedSessionId };
29270
29990
  }
29271
29991
  // src/timeline/recorder.ts
29272
29992
  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);
29993
+ var MAX_AGENT_RESPONSE_BYTES = 65536;
29994
+ var MAX_PENDING_COMMITS_PER_AGENT = 8;
29995
+ var PENDING_COMMIT_TTL_MS = 15 * 60000;
29996
+ var TRUNCATION_MARKER = `
29997
+ … [truncated]`;
29998
+ function turnKey(agentId, owner) {
29999
+ return `${agentId}\x00${owner.sessionInstanceId}\x00${owner.rootTurnId}\x00${owner.barrierGeneration}`;
30000
+ }
30001
+ function sameOwner(left, right) {
30002
+ return left.sessionInstanceId === right.sessionInstanceId && left.rootTurnId === right.rootTurnId && left.barrierGeneration === right.barrierGeneration;
30003
+ }
30004
+ function utf8Prefix2(text2, maxBytes) {
30005
+ if (maxBytes <= 0)
30006
+ return "";
30007
+ if (Buffer.byteLength(text2, "utf8") <= maxBytes)
30008
+ return text2;
30009
+ let low = 0;
30010
+ let high = text2.length;
30011
+ while (low < high) {
30012
+ const mid = Math.ceil((low + high) / 2);
30013
+ let end2 = mid;
30014
+ const code2 = text2.charCodeAt(end2 - 1);
30015
+ if (code2 >= 55296 && code2 <= 56319)
30016
+ end2 -= 1;
30017
+ if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
30018
+ low = mid;
30019
+ else
30020
+ high = mid - 1;
30021
+ }
30022
+ let end = low;
30023
+ const code = text2.charCodeAt(end - 1);
30024
+ if (code >= 55296 && code <= 56319)
30025
+ end -= 1;
30026
+ while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
30027
+ end -= 1;
30028
+ return text2.slice(0, end);
30029
+ }
30030
+ function boundedResponse(text2, alreadyTruncated) {
30031
+ const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
30032
+ const needsTruncation = alreadyTruncated || Buffer.byteLength(text2, "utf8") > MAX_AGENT_RESPONSE_BYTES;
30033
+ if (!needsTruncation)
30034
+ return text2;
30035
+ return utf8Prefix2(text2, MAX_AGENT_RESPONSE_BYTES - markerBytes) + TRUNCATION_MARKER;
30036
+ }
30037
+ function serializedTimelineBytes(entry) {
30038
+ return Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
30039
+ }
30040
+ function fitResponsesToRow(entry, responses) {
30041
+ let fitted = [...entry.agent_responses];
30042
+ for (const response of responses) {
30043
+ fitted = [...fitted, response].slice(-MAX_AGENT_RESPONSES);
30044
+ if (serializedTimelineBytes({ ...entry, agent_responses: fitted }) <= TIMELINE_MAX_BYTES)
30045
+ continue;
30046
+ const truncationBase = response.endsWith(TRUNCATION_MARKER) ? response.slice(0, -TRUNCATION_MARKER.length) : response;
30047
+ let low = 0;
30048
+ let high = truncationBase.length;
30049
+ let replacement = null;
30050
+ while (low <= high) {
30051
+ const mid = Math.floor((low + high) / 2);
30052
+ let end = mid;
30053
+ const code = truncationBase.charCodeAt(end - 1);
30054
+ if (code >= 55296 && code <= 56319)
30055
+ end -= 1;
30056
+ const candidateResponse = truncationBase.slice(0, end) + TRUNCATION_MARKER;
30057
+ const candidate = [...fitted.slice(0, -1), candidateResponse];
30058
+ if (serializedTimelineBytes({ ...entry, agent_responses: candidate }) <= TIMELINE_MAX_BYTES) {
30059
+ replacement = candidateResponse;
30060
+ low = mid + 1;
30061
+ } else {
30062
+ high = mid - 1;
30063
+ }
30064
+ }
30065
+ if (replacement === null)
30066
+ return null;
30067
+ fitted[fitted.length - 1] = replacement;
29277
30068
  }
30069
+ return fitted;
29278
30070
  }
29279
30071
  function createTimelineRecorder(opts) {
29280
30072
  const now = opts.now ?? (() => new Date);
29281
30073
  const dirFor = (agentId) => opts.timelineDirFor(agentId);
29282
30074
  const sessionByAgent = new Map;
30075
+ const resolveForAgent = (agentId, provider) => {
30076
+ const dir = dirFor(agentId);
30077
+ const recent = resolveResumableSession(readRecentEntries(dir, { now: now() }), provider ?? undefined);
30078
+ const control = readResumeControlState(dir);
30079
+ if (control.kind === "missing")
30080
+ return recent;
30081
+ if (control.kind === "invalid") {
30082
+ return {
30083
+ kind: "barrier",
30084
+ type: "reset_session",
30085
+ forgottenSessionId: null,
30086
+ fencedSessionId: null
30087
+ };
30088
+ }
30089
+ const { attemptedSessionId, fencedSessionId, fullBarrier } = control.state;
30090
+ if (fullBarrier !== null) {
30091
+ return {
30092
+ kind: "barrier",
30093
+ type: fullBarrier,
30094
+ forgottenSessionId: null,
30095
+ fencedSessionId
30096
+ };
30097
+ }
30098
+ if (recent.kind === "session") {
30099
+ return {
30100
+ kind: "session",
30101
+ sessionId: recent.sessionId,
30102
+ stalledSessionId: attemptedSessionId === recent.sessionId ? attemptedSessionId : null,
30103
+ fencedSessionId
30104
+ };
30105
+ }
30106
+ if (recent.kind === "barrier") {
30107
+ if (recent.type !== "stall_recovery" || recent.forgottenSessionId === fencedSessionId) {
30108
+ return { ...recent, fencedSessionId };
30109
+ }
30110
+ }
30111
+ return { kind: "none", stalledSessionId: attemptedSessionId, fencedSessionId };
30112
+ };
30113
+ const sessionByEpoch = new Map;
30114
+ const barrierByAgent = new Map;
30115
+ const turnsByAgent = new Map;
30116
+ const activeTurnByAgent = new Map;
30117
+ const epochKey = (agentId, sessionInstanceId) => `${agentId}\x00${sessionInstanceId}`;
30118
+ const currentBarrier = (agentId) => barrierByAgent.get(agentId) ?? 0;
30119
+ const statesFor = (agentId) => {
30120
+ let states = turnsByAgent.get(agentId);
30121
+ if (!states) {
30122
+ states = new Map;
30123
+ turnsByAgent.set(agentId, states);
30124
+ }
30125
+ return states;
30126
+ };
30127
+ const diagnostic = (agentId, code, reason) => {
30128
+ try {
30129
+ opts.onDiagnostic?.({ agentId, code, ...reason ? { reason } : {} });
30130
+ } catch {}
30131
+ };
30132
+ const applyRewrite = (agentId, rewrite) => {
30133
+ for (const state of statesFor(agentId).values()) {
30134
+ if (!state.handle)
30135
+ continue;
30136
+ const refreshed = refreshTimelineEntryHandle(state.handle, rewrite);
30137
+ if (refreshed) {
30138
+ state.handle = refreshed;
30139
+ } else if (state.handle.filename === rewrite.filename) {
30140
+ state.handle = undefined;
30141
+ state.rowFenced = true;
30142
+ diagnostic(agentId, "timeline_handle_fenced", "rewrite_remap");
30143
+ }
30144
+ }
30145
+ };
30146
+ const handleTrackedResult = (agentId, state, result) => {
30147
+ if (result.status === "written") {
30148
+ applyRewrite(agentId, result.rewrite);
30149
+ if (state && result.handle) {
30150
+ state.handle = result.handle;
30151
+ state.rowFenced = false;
30152
+ }
30153
+ return "written";
30154
+ }
30155
+ if (result.reason === "lock" || result.reason === "write")
30156
+ return "retryable";
30157
+ if (state && result.reason !== "oversized")
30158
+ state.rowFenced = true;
30159
+ diagnostic(agentId, "timeline_exact_write_rejected", result.reason);
30160
+ return "terminal";
30161
+ };
30162
+ const tryCommit = (agentId, state) => {
30163
+ if (state.responses.length === 0)
30164
+ return "written";
30165
+ const dir = dirFor(agentId);
30166
+ if (!prepareTimelineDirectory(dir)) {
30167
+ diagnostic(agentId, "timeline_directory_unavailable");
30168
+ return "terminal";
30169
+ }
30170
+ if (state.handle) {
30171
+ const result2 = updateTrackedEntry(dir, state.handle, (entry2) => {
30172
+ const fitted2 = fitResponsesToRow(entry2, state.responses);
30173
+ return { ...entry2, agent_responses: fitted2 ?? [...entry2.agent_responses, ...state.responses] };
30174
+ });
30175
+ return handleTrackedResult(agentId, state, result2);
30176
+ }
30177
+ if (state.rowFenced || state.pendingMode === "handle")
30178
+ return "terminal";
30179
+ if (state.owner.barrierGeneration !== currentBarrier(agentId))
30180
+ return "terminal";
30181
+ const entry = createTimelineEntry({
30182
+ messages: [],
30183
+ sessionId: state.backendSessionId,
30184
+ provider: state.provider
30185
+ });
30186
+ const fitted = fitResponsesToRow(entry, state.responses);
30187
+ if (!fitted) {
30188
+ diagnostic(agentId, "timeline_response_did_not_fit", "oversized");
30189
+ return "terminal";
30190
+ }
30191
+ entry.agent_responses = fitted;
30192
+ const result = appendTrackedEntry(dir, entry, now());
30193
+ return handleTrackedResult(agentId, state, result);
30194
+ };
30195
+ const deleteState = (agentId, key) => {
30196
+ const states = statesFor(agentId);
30197
+ states.delete(key);
30198
+ if (activeTurnByAgent.get(agentId) === key)
30199
+ activeTurnByAgent.delete(agentId);
30200
+ if (states.size === 0)
30201
+ turnsByAgent.delete(agentId);
30202
+ };
30203
+ const retryPending = (agentId) => {
30204
+ const states = turnsByAgent.get(agentId);
30205
+ if (!states)
30206
+ return;
30207
+ const nowMs = now().getTime();
30208
+ for (const [key, state] of [...states]) {
30209
+ if (state.finalized && state.pendingSinceMs === undefined && state.completedAtMs !== undefined && nowMs - state.completedAtMs >= PENDING_COMMIT_TTL_MS) {
30210
+ deleteState(agentId, key);
30211
+ continue;
30212
+ }
30213
+ if (!state.finalized || state.pendingSinceMs === undefined)
30214
+ continue;
30215
+ if (nowMs - state.pendingSinceMs >= PENDING_COMMIT_TTL_MS) {
30216
+ diagnostic(agentId, "timeline_pending_commit_expired");
30217
+ deleteState(agentId, key);
30218
+ continue;
30219
+ }
30220
+ const result = tryCommit(agentId, state);
30221
+ if (result === "written") {
30222
+ state.responses = [];
30223
+ state.pendingSinceMs = undefined;
30224
+ state.pendingMode = undefined;
30225
+ state.completedAtMs = nowMs;
30226
+ } else if (result === "terminal") {
30227
+ deleteState(agentId, key);
30228
+ }
30229
+ }
30230
+ const completed = [...states.entries()].filter(([, state]) => state.finalized && state.pendingSinceMs === undefined).sort((left, right) => (left[1].completedAtMs ?? 0) - (right[1].completedAtMs ?? 0));
30231
+ while (completed.length > MAX_PENDING_COMMITS_PER_AGENT) {
30232
+ const oldest = completed.shift();
30233
+ if (oldest)
30234
+ deleteState(agentId, oldest[0]);
30235
+ }
30236
+ };
30237
+ const retainPending = (agentId, key, state) => {
30238
+ const states = statesFor(agentId);
30239
+ const pending = [...states.values()].filter((candidate) => candidate.finalized && candidate.pendingSinceMs !== undefined);
30240
+ if (pending.length >= MAX_PENDING_COMMITS_PER_AGENT) {
30241
+ diagnostic(agentId, "timeline_pending_commit_overflow");
30242
+ deleteState(agentId, key);
30243
+ return;
30244
+ }
30245
+ state.pendingSinceMs ??= now().getTime();
30246
+ state.pendingMode = state.handle ? "handle" : "fallback";
30247
+ };
30248
+ const appendOwnerless = (agentId, messages) => {
30249
+ if (messages.length === 0)
30250
+ return;
30251
+ const dir = dirFor(agentId);
30252
+ if (!prepareTimelineDirectory(dir))
30253
+ return;
30254
+ const result = appendTrackedEntry(dir, createTimelineEntry({
30255
+ messages,
30256
+ sessionId: sessionByAgent.get(agentId) ?? null,
30257
+ provider: opts.providerFor?.(agentId) ?? null
30258
+ }), now());
30259
+ handleTrackedResult(agentId, null, result);
30260
+ };
29283
30261
  return {
29284
- setSession(agentId, sessionId) {
29285
- sessionByAgent.set(agentId, sessionId);
30262
+ barrierGeneration(agentId) {
30263
+ return currentBarrier(agentId);
30264
+ },
30265
+ beginTurn(agentId, owner) {
30266
+ retryPending(agentId);
30267
+ const states = statesFor(agentId);
30268
+ if (owner.barrierGeneration !== currentBarrier(agentId)) {
30269
+ diagnostic(agentId, "timeline_turn_begin_fenced", "barrier_generation");
30270
+ return;
30271
+ }
30272
+ const key = turnKey(agentId, owner);
30273
+ if (!states.has(key)) {
30274
+ states.set(key, {
30275
+ owner: { ...owner },
30276
+ provider: opts.providerFor?.(agentId) ?? null,
30277
+ backendSessionId: sessionByEpoch.get(epochKey(agentId, owner.sessionInstanceId)) ?? sessionByAgent.get(agentId) ?? null,
30278
+ responses: [],
30279
+ rowFenced: false,
30280
+ finalized: false
30281
+ });
30282
+ }
30283
+ activeTurnByAgent.set(agentId, key);
29286
30284
  },
29287
- appendEntryForAgent(agentId, messages) {
30285
+ recordInboxPull(agentId, owner, messages) {
30286
+ retryPending(agentId);
29288
30287
  if (messages.length === 0)
29289
30288
  return;
30289
+ if (!owner) {
30290
+ appendOwnerless(agentId, messages);
30291
+ return;
30292
+ }
30293
+ const key = turnKey(agentId, owner);
30294
+ const state = turnsByAgent.get(agentId)?.get(key);
30295
+ if (!state || state.rowFenced || !sameOwner(state.owner, owner)) {
30296
+ appendOwnerless(agentId, messages);
30297
+ return;
30298
+ }
29290
30299
  const dir = dirFor(agentId);
29291
30300
  if (!prepareTimelineDirectory(dir))
29292
30301
  return;
29293
- appendOrMergeEntry(dir, createTimelineEntry({
29294
- messages,
29295
- sessionId: sessionByAgent.get(agentId) ?? null,
29296
- provider: opts.providerFor?.(agentId) ?? null
29297
- }), now());
30302
+ const stamp = now();
30303
+ let result;
30304
+ if (state.handle && (state.handle.filename === filenameForDate(stamp) || state.owner.barrierGeneration !== currentBarrier(agentId))) {
30305
+ result = updateTrackedEntry(dir, state.handle, (entry) => ({
30306
+ ...entry,
30307
+ messages: [...entry.messages, ...messages],
30308
+ session_id: state.backendSessionId,
30309
+ provider: state.provider
30310
+ }));
30311
+ } else if (state.owner.barrierGeneration === currentBarrier(agentId)) {
30312
+ result = appendTrackedEntry(dir, createTimelineEntry({
30313
+ messages,
30314
+ sessionId: state.backendSessionId,
30315
+ provider: state.provider
30316
+ }), stamp);
30317
+ } else {
30318
+ appendOwnerless(agentId, messages);
30319
+ return;
30320
+ }
30321
+ handleTrackedResult(agentId, state, result);
29298
30322
  },
29299
- appendResponseToLatest(agentId, text2) {
29300
- const dir = dirFor(agentId);
29301
- if (!prepareTimelineDirectory(dir))
30323
+ recordAssistantMessage(agentId, owner, text2, truncated = false) {
30324
+ retryPending(agentId);
30325
+ const key = turnKey(agentId, owner);
30326
+ const state = turnsByAgent.get(agentId)?.get(key);
30327
+ if (!state || state.finalized || state.owner.barrierGeneration !== currentBarrier(agentId) || !sameOwner(state.owner, owner)) {
30328
+ diagnostic(agentId, "timeline_completed_message_rejected", "stale_owner");
29302
30329
  return;
29303
- const result = updateLatestEntryResult(dir, (entry2) => appendAgentResponse(entry2, text2), { now: now() });
29304
- if (result === "updated" || result === "rejected")
30330
+ }
30331
+ state.responses.push(boundedResponse(text2, truncated));
30332
+ if (state.responses.length > MAX_AGENT_RESPONSES) {
30333
+ state.responses.splice(0, state.responses.length - MAX_AGENT_RESPONSES);
30334
+ }
30335
+ },
30336
+ finalizeTurn(agentId, owner) {
30337
+ retryPending(agentId);
30338
+ const key = turnKey(agentId, owner);
30339
+ const state = turnsByAgent.get(agentId)?.get(key);
30340
+ if (!state || state.finalized || !sameOwner(state.owner, owner))
29305
30341
  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());
30342
+ const fallbackAuthorized = activeTurnByAgent.get(agentId) === key && owner.barrierGeneration === currentBarrier(agentId);
30343
+ state.finalized = true;
30344
+ activeTurnByAgent.delete(agentId);
30345
+ if (state.responses.length === 0) {
30346
+ state.completedAtMs = now().getTime();
30347
+ return;
30348
+ }
30349
+ if (!state.handle && !fallbackAuthorized) {
30350
+ diagnostic(agentId, "timeline_fallback_rejected", "fenced_owner");
30351
+ deleteState(agentId, key);
30352
+ return;
30353
+ }
30354
+ const result = tryCommit(agentId, state);
30355
+ if (result === "written") {
30356
+ state.responses = [];
30357
+ state.completedAtMs = now().getTime();
30358
+ } else if (result === "terminal") {
30359
+ deleteState(agentId, key);
30360
+ } else {
30361
+ retainPending(agentId, key, state);
30362
+ }
30363
+ },
30364
+ fenceSession(agentId) {
30365
+ retryPending(agentId);
30366
+ barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
30367
+ activeTurnByAgent.delete(agentId);
30368
+ const states = turnsByAgent.get(agentId);
30369
+ if (!states)
30370
+ return;
30371
+ for (const [key, state] of [...states]) {
30372
+ if (!state.handle) {
30373
+ diagnostic(agentId, "timeline_fallback_rejected", "session_fence");
30374
+ deleteState(agentId, key);
30375
+ }
30376
+ }
30377
+ },
30378
+ setSession(agentId, sessionId, sessionInstanceId) {
30379
+ retryPending(agentId);
30380
+ const dir = dirFor(agentId);
30381
+ if (!prepareTimelineDirectory(dir))
30382
+ return false;
30383
+ const persisted = updateResumeControlState(dir, (state) => ({
30384
+ ...state,
30385
+ fullBarrier: null,
30386
+ attemptedSessionId: state.attemptedSessionId === sessionId ? state.attemptedSessionId : null
30387
+ }));
30388
+ if (!persisted)
30389
+ return false;
30390
+ sessionByAgent.set(agentId, sessionId);
30391
+ if (sessionInstanceId) {
30392
+ sessionByEpoch.set(epochKey(agentId, sessionInstanceId), sessionId);
30393
+ for (const state of statesFor(agentId).values()) {
30394
+ if (state.owner.sessionInstanceId === sessionInstanceId)
30395
+ state.backendSessionId = sessionId;
30396
+ }
30397
+ }
30398
+ return true;
29313
30399
  },
29314
30400
  resumeSessionId(agentId, provider) {
29315
- const rows = readRecentEntries(dirFor(agentId), { now: now() });
29316
- return findResumableSession(rows, provider ?? undefined);
30401
+ const resolution = resolveForAgent(agentId, provider);
30402
+ return resolution.kind === "session" ? resolution.sessionId : null;
30403
+ },
30404
+ resolveResumeSession(agentId, provider) {
30405
+ return resolveForAgent(agentId, provider);
30406
+ },
30407
+ recordSessionStall(agentId, sessionId) {
30408
+ return appendStallMarker(agentId, "stall_recovery_attempt", sessionId);
30409
+ },
30410
+ clearSessionStall(agentId, sessionId) {
30411
+ return appendStallMarker(agentId, "stall_recovery_clear", sessionId);
29317
30412
  },
29318
- forgetSession(agentId, barrierType = "reset_session") {
30413
+ forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
30414
+ retryPending(agentId);
29319
30415
  const dir = dirFor(agentId);
29320
- sessionByAgent.delete(agentId);
29321
30416
  if (!prepareTimelineDirectory(dir))
29322
- return;
30417
+ return false;
29323
30418
  const stamp = now();
29324
- appendEntry(dir, createSystemEntry(barrierType, stamp.toISOString()), stamp);
30419
+ let persisted = false;
30420
+ if (barrierType === "reset_session" || barrierType === "nap") {
30421
+ persisted = updateResumeControlState(dir, (state) => ({
30422
+ ...state,
30423
+ attemptedSessionId: null,
30424
+ fencedSessionId: null,
30425
+ fullBarrier: barrierType
30426
+ }));
30427
+ } else if (barrierType === "stall_recovery") {
30428
+ persisted = updateResumeControlState(dir, (state) => ({
30429
+ ...state,
30430
+ attemptedSessionId: state.attemptedSessionId === forgottenSessionId ? null : state.attemptedSessionId,
30431
+ fencedSessionId: forgottenSessionId ?? null,
30432
+ fullBarrier: null
30433
+ }));
30434
+ }
30435
+ if (!persisted)
30436
+ return false;
30437
+ sessionByAgent.delete(agentId);
30438
+ for (const key of [...sessionByEpoch.keys()]) {
30439
+ if (key.startsWith(`${agentId}\x00`))
30440
+ sessionByEpoch.delete(key);
30441
+ }
30442
+ barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
30443
+ activeTurnByAgent.delete(agentId);
30444
+ const states = turnsByAgent.get(agentId);
30445
+ if (states) {
30446
+ for (const [key, state] of [...states]) {
30447
+ if (!state.handle) {
30448
+ diagnostic(agentId, "timeline_fallback_rejected", "barrier");
30449
+ deleteState(agentId, key);
30450
+ }
30451
+ }
30452
+ }
30453
+ const result = appendTrackedEntry(dir, createSystemEntry(barrierType, stamp.toISOString(), forgottenSessionId), stamp);
30454
+ handleTrackedResult(agentId, null, result);
30455
+ return true;
29325
30456
  }
29326
30457
  };
30458
+ function appendStallMarker(agentId, type, sessionId) {
30459
+ retryPending(agentId);
30460
+ const dir = dirFor(agentId);
30461
+ if (!prepareTimelineDirectory(dir))
30462
+ return false;
30463
+ const stamp = now();
30464
+ const persisted = updateResumeControlState(dir, (state) => ({
30465
+ ...state,
30466
+ attemptedSessionId: type === "stall_recovery_attempt" ? sessionId : null,
30467
+ fullBarrier: null
30468
+ }));
30469
+ if (!persisted)
30470
+ return false;
30471
+ const result = appendTrackedEntry(dir, createSystemEntry(type, stamp.toISOString(), sessionId), stamp);
30472
+ handleTrackedResult(agentId, null, result);
30473
+ return true;
30474
+ }
29327
30475
  }
29328
30476
  // src/daemon/diagnosticsCommand.ts
29329
30477
  function reportUnavailable(options, reportId) {
@@ -29868,11 +31016,15 @@ async function createDaemon(opts) {
29868
31016
  const typingTracker = createTypingScopeTracker();
29869
31017
  const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
29870
31018
  const proxy = await startCredentialProxy(broker, {
29871
- onInboxPullStart: (agentId) => channelRef?.modelSeenGeneration(agentId),
31019
+ onInboxPullStart: (agentId) => ({
31020
+ modelSeenGeneration: channelRef?.modelSeenGeneration(agentId),
31021
+ owner: managerRef?.timelineTurnOwner(agentId) ?? null
31022
+ }),
29872
31023
  onInboxPullResponse: (agentId, messages, observationToken) => {
29873
- timeline2.appendEntryForAgent(agentId, messages);
29874
- if (typeof observationToken === "number") {
29875
- channelRef?.recordModelSeen(agentId, messages, observationToken);
31024
+ const token = observationToken && typeof observationToken === "object" ? observationToken : null;
31025
+ timeline2.recordInboxPull(agentId, token?.owner ?? null, messages);
31026
+ if (typeof token?.modelSeenGeneration === "number") {
31027
+ channelRef?.recordModelSeen(agentId, messages, token.modelSeenGeneration);
29876
31028
  }
29877
31029
  },
29878
31030
  onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
@@ -29933,6 +31085,7 @@ async function createDaemon(opts) {
29933
31085
  typingTracker.clear(agentId);
29934
31086
  }
29935
31087
  const botsById = new Map;
31088
+ let botCacheReady = false;
29936
31089
  async function listMyBotsHttp() {
29937
31090
  const res = await fetch(`${opts.serverUrl}/api/community/daemon/bots`, {
29938
31091
  method: "GET",
@@ -29943,22 +31096,26 @@ async function createDaemon(opts) {
29943
31096
  const json2 = await res.json();
29944
31097
  return json2.bots ?? [];
29945
31098
  }
31099
+ function replaceBotCache(bots) {
31100
+ botsById.clear();
31101
+ for (const b of bots) {
31102
+ botsById.set(b.id, {
31103
+ name: b.name,
31104
+ discriminator: b.discriminator,
31105
+ description: b.description,
31106
+ ownerName: b.ownerName,
31107
+ ownerDiscriminator: b.ownerDiscriminator
31108
+ });
31109
+ }
31110
+ botCacheReady = true;
31111
+ }
29946
31112
  async function coldStartWarmup() {
29947
31113
  const start = Date.now();
29948
31114
  let attempt = 0;
29949
31115
  while (Date.now() - start < WARMUP_CEILING_MS) {
29950
31116
  try {
29951
31117
  const bots = await listMyBotsHttp();
29952
- botsById.clear();
29953
- for (const b of bots) {
29954
- botsById.set(b.id, {
29955
- name: b.name,
29956
- discriminator: b.discriminator,
29957
- description: b.description,
29958
- ownerName: b.ownerName,
29959
- ownerDiscriminator: b.ownerDiscriminator
29960
- });
29961
- }
31118
+ replaceBotCache(bots);
29962
31119
  log2.info("cold-start bot-cache warmup succeeded", { bots: bots.length, attempt });
29963
31120
  return;
29964
31121
  } catch {
@@ -30179,7 +31336,14 @@ async function createDaemon(opts) {
30179
31336
  const statusPath = opts.statusFilePath;
30180
31337
  const writeStatus = () => {
30181
31338
  const nowMs = Date.now();
30182
- writeStatusFile(statusPath, { writtenAt: nowMs, agents: manager.statusProjection(nowMs) });
31339
+ writeStatusFile(statusPath, {
31340
+ writtenAt: nowMs,
31341
+ agentSummary: {
31342
+ total: botCacheReady ? botsById.size : null,
31343
+ running: manager.runningAgentCount()
31344
+ },
31345
+ agents: manager.statusProjection(nowMs)
31346
+ });
30183
31347
  };
30184
31348
  writeStatus();
30185
31349
  statusTimer = setInterval(writeStatus, STATUS_WRITE_INTERVAL_MS);
@@ -30204,15 +31368,7 @@ async function createDaemon(opts) {
30204
31368
  if (!botsById.has(agentId)) {
30205
31369
  try {
30206
31370
  const bots = await listMyBotsHttp();
30207
- for (const b of bots) {
30208
- botsById.set(b.id, {
30209
- name: b.name,
30210
- discriminator: b.discriminator,
30211
- description: b.description,
30212
- ownerName: b.ownerName,
30213
- ownerDiscriminator: b.ownerDiscriminator
30214
- });
30215
- }
31371
+ replaceBotCache(bots);
30216
31372
  } catch {}
30217
31373
  }
30218
31374
  if (!botsById.has(agentId)) {
@@ -30634,8 +31790,8 @@ function projectDaemonLogRow(value, targetAgentId) {
30634
31790
  fields: projectedFields
30635
31791
  };
30636
31792
  }
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"];
31793
+ 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"];
31794
+ var MANAGER_EFFECTS = ["spawn", "send", "stop", "terminate_stalled", "clear_stall_recovery", "force_exit", "gated_hold"];
30639
31795
  var AGENT_STATUSES = ["idle", "starting", "running", "stopping"];
30640
31796
  var DELIVERY_PHASES = ["idle", "admission_wait", "steering", "next_turn_queued", "compacting", "reviewing", "tool_wait", "working"];
30641
31797
  var RESUME_OUTCOMES = ["not_requested", "pending", "resumed", "reset_required", "failed"];
@@ -30644,6 +31800,8 @@ var TERMINATION_CAUSES = ["runtime_error", "killed_stalled", "other"];
30644
31800
  var SPAWN_FAILURE_REASONS = ["ENOENT", "handshake_timeout", "pre_handshake_exit", "spawn_threw", "other"];
30645
31801
  var TERMINATION_SEMANTICS = ["killed_stalled", "idle_stop", "force_exit", "other"];
30646
31802
  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"];
31803
+ var NATIVE_ACTIVITY_KINDS = ["turn_started", "backend_turn_started", "thinking", "text", "tool_call", "tool_output", "internal_progress", "recovery", "turn_end"];
31804
+ var RUNTIME_PHASES = ["idle", "admission", "inference", "tool", "recovery", "terminal"];
30647
31805
  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
31806
  function enumValue(value, values, bucket = false, maxChars = 64) {
30649
31807
  if (!boundedString(value, maxChars))
@@ -30681,6 +31839,13 @@ function copyString(row, output, key, maxChars, optional2 = false, nullable2 = f
30681
31839
  output[key] = row[key];
30682
31840
  return true;
30683
31841
  }
31842
+ function copyScrubbedString(row, output, key, maxChars, optional2 = false, nullable2 = false) {
31843
+ if (!copyString(row, output, key, maxChars, optional2, nullable2))
31844
+ return false;
31845
+ if (typeof output[key] === "string")
31846
+ output[key] = scrubDiagnosticText(output[key]);
31847
+ return true;
31848
+ }
30684
31849
  function projectTraceBase(row, targetAgentId) {
30685
31850
  if (row.agentId !== targetAgentId || !boundedString(row.agentId, 128) || !canonicalTime(row.timeIso))
30686
31851
  return null;
@@ -30707,11 +31872,29 @@ function projectFsm(row, targetAgentId) {
30707
31872
  Object.assign(output, { event, status, turnActive: row.turnActive });
30708
31873
  if (!copyInteger(row, output, "inbox") || !copyInteger(row, output, "lastDeliverAt", { nullable: true }) || !copyInteger(row, output, "lastProgressAt") || !copyInteger(row, output, "idleSince", { nullable: true }))
30709
31874
  return null;
31875
+ 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 }))
31876
+ return null;
31877
+ if ("lastNativeActivityKind" in row && row.lastNativeActivityKind !== undefined) {
31878
+ if (row.lastNativeActivityKind === null)
31879
+ output.lastNativeActivityKind = null;
31880
+ else {
31881
+ const kind = enumValue(row.lastNativeActivityKind, NATIVE_ACTIVITY_KINDS);
31882
+ if (!kind)
31883
+ return null;
31884
+ output.lastNativeActivityKind = kind;
31885
+ }
31886
+ }
31887
+ if ("runtimePhase" in row && row.runtimePhase !== undefined) {
31888
+ const phase = enumValue(row.runtimePhase, RUNTIME_PHASES);
31889
+ if (!phase)
31890
+ return null;
31891
+ output.runtimePhase = phase;
31892
+ }
30710
31893
  output.resetting = row.resetting;
30711
31894
  if (!copyInteger(row, output, "resettingSince", { nullable: true }) || !copyInteger(row, output, "stoppingSince", { nullable: true }))
30712
31895
  return null;
30713
31896
  output.deliveryPhase = deliveryPhase;
30714
- if (!copyInteger(row, output, "sinceProgressMs") || !copyInteger(row, output, "sinceDeliverMs", { nullable: true }) || !copyInteger(row, output, "sinceStoppingMs", { nullable: true }) || !projectOptionalSpanMetadata(row, output))
31897
+ 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
31898
  return null;
30716
31899
  const metricKeys = [
30717
31900
  "physicalOpenCount",
@@ -30842,7 +32025,7 @@ function projectStatusRow(value, targetAgentId) {
30842
32025
  return output;
30843
32026
  }
30844
32027
  // src/diagnostics/bundle.ts
30845
- import { createHash as createHash2 } from "node:crypto";
32028
+ import { createHash as createHash3 } from "node:crypto";
30846
32029
  import { createWriteStream, unlinkSync as unlinkSync5 } from "node:fs";
30847
32030
  import { Readable, Transform } from "node:stream";
30848
32031
  import { pipeline } from "node:stream/promises";
@@ -30992,7 +32175,7 @@ async function buildDiagnosticBundle(args) {
30992
32175
  const { footer, total } = envelope();
30993
32176
  if (total > maxUncompressed)
30994
32177
  throw new BundleTooLargeError;
30995
- const hash2 = createHash2("sha256");
32178
+ const hash2 = createHash3("sha256");
30996
32179
  let compressedBytes = 0;
30997
32180
  const meter = new Transform({
30998
32181
  transform(chunk2, _encoding, callback) {
@@ -31033,7 +32216,7 @@ async function buildDiagnosticBundle(args) {
31033
32216
  };
31034
32217
  }
31035
32218
  // src/diagnostics/coordinator.ts
31036
- import { createHash as createHash3, randomBytes as randomBytes5 } from "node:crypto";
32219
+ import { createHash as createHash4, randomBytes as randomBytes5 } from "node:crypto";
31037
32220
  import {
31038
32221
  chmodSync as chmodSync2,
31039
32222
  closeSync as closeSync4,
@@ -31189,7 +32372,7 @@ function createDiagnosticReportCoordinator(args) {
31189
32372
  if (!stat.isFile() || stat.isSymbolicLink())
31190
32373
  return null;
31191
32374
  const bytes = readFileSync6(path11);
31192
- return { sizeBytes: bytes.byteLength, sha256: createHash3("sha256").update(bytes).digest("hex") };
32375
+ return { sizeBytes: bytes.byteLength, sha256: createHash4("sha256").update(bytes).digest("hex") };
31193
32376
  } catch {
31194
32377
  return null;
31195
32378
  }
@@ -32501,8 +33684,13 @@ function daemonList(opts) {
32501
33684
  if (alive && statusPath) {
32502
33685
  const s = daemonStatusFromFile(statusPath, now);
32503
33686
  if (s.found) {
32504
- agents = s.agents.length;
32505
- running = s.agents.filter((a) => a.derivedActivity === "running").length;
33687
+ if (s.agentSummary) {
33688
+ agents = s.agentSummary.total;
33689
+ running = s.agentSummary.running;
33690
+ } else {
33691
+ agents = s.agents.length;
33692
+ running = s.agents.filter((a) => a.derivedActivity === "running").length;
33693
+ }
32506
33694
  lastActiveMs = daemonLastActiveMs(s, now);
32507
33695
  }
32508
33696
  }
@@ -32517,7 +33705,24 @@ function daemonList(opts) {
32517
33705
  return results;
32518
33706
  }
32519
33707
  var STATUS_STALE_MS = 20000;
32520
- var MISSING_STATUS = { found: false, ageMs: null, freshness: "missing", writtenAt: null, agents: [] };
33708
+ var MISSING_STATUS = {
33709
+ found: false,
33710
+ ageMs: null,
33711
+ freshness: "missing",
33712
+ writtenAt: null,
33713
+ agentSummary: null,
33714
+ agents: []
33715
+ };
33716
+ function validAgentSummary(value) {
33717
+ if (!value || typeof value !== "object")
33718
+ return null;
33719
+ const summary = value;
33720
+ const validTotal = summary.total === null || Number.isInteger(summary.total) && summary.total >= 0;
33721
+ const validRunning = Number.isInteger(summary.running) && summary.running >= 0;
33722
+ if (!validTotal || !validRunning)
33723
+ return null;
33724
+ return { total: summary.total, running: summary.running };
33725
+ }
32521
33726
  function daemonStatusFromFile(statusPath, nowMs) {
32522
33727
  if (!fs12.existsSync(statusPath))
32523
33728
  return MISSING_STATUS;
@@ -32529,6 +33734,7 @@ function daemonStatusFromFile(statusPath, nowMs) {
32529
33734
  ageMs,
32530
33735
  freshness: ageMs <= STATUS_STALE_MS ? "fresh" : "stale",
32531
33736
  writtenAt: snap.writtenAt,
33737
+ agentSummary: validAgentSummary(snap.agentSummary),
32532
33738
  agents: Array.isArray(snap.agents) ? snap.agents : []
32533
33739
  };
32534
33740
  } catch {