@adhdev/daemon-core 0.9.82-rc.537 → 0.9.82-rc.538

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/index.mjs CHANGED
@@ -414,10 +414,10 @@ function readInjected(value) {
414
414
  }
415
415
  function getDaemonBuildInfo() {
416
416
  if (cached) return cached;
417
- const commit = readInjected(true ? "b47ad45cf2b2ad404d911162a9cadd7db2e274a6" : void 0) ?? "unknown";
418
- const commitShort = readInjected(true ? "b47ad45c" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
419
- const version = readInjected(true ? "0.9.82-rc.537" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
420
- const builtAt = readInjected(true ? "2026-07-15T13:12:55.213Z" : void 0);
417
+ const commit = readInjected(true ? "35ac849eca76162066561b27633e11827fa19a73" : void 0) ?? "unknown";
418
+ const commitShort = readInjected(true ? "35ac849e" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
419
+ const version = readInjected(true ? "0.9.82-rc.538" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
420
+ const builtAt = readInjected(true ? "2026-07-15T15:51:55.785Z" : void 0);
421
421
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
422
422
  return cached;
423
423
  }
@@ -5705,6 +5705,8 @@ __export(mesh_work_queue_exports, {
5705
5705
  MESH_TASK_MODES: () => MESH_TASK_MODES,
5706
5706
  MESH_TASK_PRIORITIES: () => MESH_TASK_PRIORITIES,
5707
5707
  NOT_BEFORE_RELATIVE_THRESHOLD_MS: () => NOT_BEFORE_RELATIVE_THRESHOLD_MS,
5708
+ REDRIVE_RECLAIM_REASONS: () => REDRIVE_RECLAIM_REASONS,
5709
+ REDRIVE_SUPERSEDE_WINDOW_MS: () => REDRIVE_SUPERSEDE_WINDOW_MS,
5708
5710
  __clearDirectDispatchesForTests: () => __clearDirectDispatchesForTests,
5709
5711
  __clearMeshQueueForTests: () => __clearMeshQueueForTests,
5710
5712
  __replaceMeshQueueForTests: () => __replaceMeshQueueForTests,
@@ -6001,10 +6003,21 @@ function normalizeMeshCapabilityTags(value) {
6001
6003
  return true;
6002
6004
  });
6003
6005
  }
6004
- function firstProviderPriority(policy) {
6005
- const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
6006
- if (!Array.isArray(raw)) return void 0;
6007
- return raw.find((type) => typeof type === "string" && type.trim())?.trim();
6006
+ function readNodeProviderTypes(policy) {
6007
+ const record = policy && typeof policy === "object" && !Array.isArray(policy) ? policy : {};
6008
+ const seen = /* @__PURE__ */ new Set();
6009
+ const out = [];
6010
+ const push = (type) => {
6011
+ const trimmed = typeof type === "string" ? type.trim() : "";
6012
+ if (!trimmed || seen.has(trimmed)) return;
6013
+ seen.add(trimmed);
6014
+ out.push(trimmed);
6015
+ };
6016
+ for (const slot of normalizeNodeCapabilitySlots(record.slots)) push(slot.provider);
6017
+ if (Array.isArray(record.providerPriority)) {
6018
+ for (const type of record.providerPriority) push(type);
6019
+ }
6020
+ return out;
6008
6021
  }
6009
6022
  function readNodeOverride(node, key2) {
6010
6023
  const overrides = node?.userOverrides;
@@ -6017,7 +6030,8 @@ function readNodeReporter(node, key2) {
6017
6030
  return typeof value === "string" && value.trim() ? value.trim() : null;
6018
6031
  }
6019
6032
  function buildMeshNodeCapabilityTags(node, providerType) {
6020
- const provider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : firstProviderPriority(node?.policy);
6033
+ const pinnedProvider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : void 0;
6034
+ const providerTags = pinnedProvider ? [pinnedProvider] : readNodeProviderTypes(node?.policy);
6021
6035
  const worktreeBranch = typeof node?.worktreeBranch === "string" && node.worktreeBranch.trim() ? node.worktreeBranch.trim() : null;
6022
6036
  const os32 = readNodeOverride(node, "platform") ?? readNodeReporter(node, "platform") ?? process.platform;
6023
6037
  const arch2 = readNodeOverride(node, "arch") ?? readNodeReporter(node, "arch") ?? process.arch;
@@ -6025,7 +6039,7 @@ function buildMeshNodeCapabilityTags(node, providerType) {
6025
6039
  ...Array.isArray(node?.capabilities) ? node.capabilities : [],
6026
6040
  `os=${os32}`,
6027
6041
  `arch=${arch2}`,
6028
- ...provider ? [`provider=${provider}`] : [],
6042
+ ...providerTags.map((p) => `provider=${p}`),
6029
6043
  // Worktree nodes automatically expose a "worktree=<branch>" tag so that
6030
6044
  // mesh_enqueue_task with required_tags: ["worktree=<branch>"] routes
6031
6045
  // only to the matching worktree node.
@@ -6528,7 +6542,7 @@ function recordMeshToolCall(opts) {
6528
6542
  return { rateLimitExceeded: false, callsInWindow: 0, advisory: null };
6529
6543
  }
6530
6544
  }
6531
- var ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES, MESH_TASK_MODES, MESH_TASK_PRIORITIES, NOT_BEFORE_RELATIVE_THRESHOLD_MS, LIVE_DEBUG_READONLY_FORBIDDEN, NEGATION_CUES, NEGATION_WINDOW_TOKENS, GIT_MUTATION_SUBCOMMANDS, GIT_STASH_READONLY_SUBCOMMANDS, DEPENDENCY_FAILURE_TERMINALS, MAX_STRANDED_RECLAIMS;
6545
+ var ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES, MESH_TASK_MODES, MESH_TASK_PRIORITIES, NOT_BEFORE_RELATIVE_THRESHOLD_MS, LIVE_DEBUG_READONLY_FORBIDDEN, NEGATION_CUES, NEGATION_WINDOW_TOKENS, GIT_MUTATION_SUBCOMMANDS, GIT_STASH_READONLY_SUBCOMMANDS, DEPENDENCY_FAILURE_TERMINALS, MAX_STRANDED_RECLAIMS, REDRIVE_RECLAIM_REASONS, REDRIVE_SUPERSEDE_WINDOW_MS;
6532
6546
  var init_mesh_work_queue = __esm({
6533
6547
  "src/mesh/mesh-work-queue.ts"() {
6534
6548
  "use strict";
@@ -6596,6 +6610,12 @@ var init_mesh_work_queue = __esm({
6596
6610
  GIT_STASH_READONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "show"]);
6597
6611
  DEPENDENCY_FAILURE_TERMINALS = /* @__PURE__ */ new Set(["failed", "cancelled"]);
6598
6612
  MAX_STRANDED_RECLAIMS = 3;
6613
+ REDRIVE_RECLAIM_REASONS = /* @__PURE__ */ new Set([
6614
+ "delivered_no_turn_deadline",
6615
+ "reclaim_after_unknown_grace",
6616
+ "delivered_not_consumed_redrive"
6617
+ ]);
6618
+ REDRIVE_SUPERSEDE_WINDOW_MS = 5 * 6e4;
6599
6619
  }
6600
6620
  });
6601
6621
 
@@ -16844,7 +16864,8 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
16844
16864
  if (task.targetNodeId && !meshNodeIdMatches(node, task.targetNodeId)) return false;
16845
16865
  if (task.taskMode === "convergence" && node?.isLocalWorktree === true) return false;
16846
16866
  if (task.requiredTags?.length) {
16847
- const priorities = normalizeProviderPriority2(node?.policy);
16867
+ const slotProviders = resolveNodeCapabilitySlots(node).map((s2) => s2.provider).filter(Boolean);
16868
+ const priorities = slotProviders.length ? slotProviders : normalizeProviderPriority2(node?.policy);
16848
16869
  const providerCandidates = priorities.length ? priorities : [void 0];
16849
16870
  return providerCandidates.some(
16850
16871
  (p) => nodeSatisfiesRequiredTags(task.requiredTags, buildMeshNodeCapabilityTags(node, p))
@@ -20432,6 +20453,52 @@ function stopStaleMeshWorker(components, args) {
20432
20453
  LOG.warn("MeshQueue", `stopStaleMeshWorker error for ${sessionId}: ${e?.message || e}`);
20433
20454
  }
20434
20455
  }
20456
+ function supersedeRedriveReclaimForLateCompletion(components, meshId, row, completingSessionId, outcome, args) {
20457
+ if (!row.requeueReason || !REDRIVE_RECLAIM_REASONS.has(row.requeueReason)) return false;
20458
+ if (row.status === "completed" || row.status === "failed" || row.status === "cancelled") return false;
20459
+ const requeuedAtMs = Date.parse(row.requeuedAt ?? "");
20460
+ if (!Number.isFinite(requeuedAtMs)) return false;
20461
+ if (Date.now() - requeuedAtMs > REDRIVE_SUPERSEDE_WINDOW_MS) return false;
20462
+ const reDispatchedSessionId = row.assignedSessionId;
20463
+ if (row.status === "assigned" && reDispatchedSessionId && !sessionIdsEquivalent(reDispatchedSessionId, completingSessionId)) {
20464
+ stopStaleMeshWorker(components, {
20465
+ meshId,
20466
+ sessionId: reDispatchedSessionId,
20467
+ nodeId: row.assignedNodeId,
20468
+ providerType: row.assignedProviderType
20469
+ });
20470
+ }
20471
+ endTaskDispatchInFlight(meshId, row.id);
20472
+ updateTaskStatus(meshId, row.id, outcome === "completed" ? "completed" : "failed");
20473
+ if (!findTerminalLedgerEvidenceForTask({ meshId, taskId: row.id })) {
20474
+ try {
20475
+ appendLedgerEntry(meshId, {
20476
+ kind: outcome === "completed" ? "task_completed" : "task_failed",
20477
+ sessionId: completingSessionId,
20478
+ nodeId: readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId) || void 0,
20479
+ providerType: readNonEmptyString2(args.metadataEvent.providerType) || void 0,
20480
+ payload: {
20481
+ taskId: row.id,
20482
+ event: args.event,
20483
+ source: "redrive_late_completion_supersede",
20484
+ reclaimReason: row.requeueReason,
20485
+ reclaimAgeMs: Date.now() - requeuedAtMs,
20486
+ finalSummary: readNonEmptyString2(args.metadataEvent.finalSummary) || void 0
20487
+ }
20488
+ });
20489
+ } catch {
20490
+ }
20491
+ }
20492
+ LOG.warn("MeshQueue", `Late completion superseded re-drive for task ${row.id} on mesh ${meshId} (reclaimed '${row.requeueReason}' ${Math.round((Date.now() - requeuedAtMs) / 1e3)}s ago; completing session ${completingSessionId}) \u2192 flipped ${outcome}${row.status === "assigned" && reDispatchedSessionId && !sessionIdsEquivalent(reDispatchedSessionId, completingSessionId) ? `, stopped duplicate re-dispatch on ${reDispatchedSessionId}` : ""}`);
20493
+ traceMeshEventDrop("redrive_late_completion_supersede", {
20494
+ taskId: row.id,
20495
+ sessionId: completingSessionId,
20496
+ nodeId: row.assignedNodeId ?? args.nodeId,
20497
+ meshId,
20498
+ event: args.event
20499
+ }, `${row.requeueReason} ${Math.round((Date.now() - requeuedAtMs) / 1e3)}s \u2192 ${outcome}`);
20500
+ return true;
20501
+ }
20435
20502
  function injectMeshSystemMessage(components, args) {
20436
20503
  const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
20437
20504
  const eventNodeId = readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId);
@@ -20543,6 +20610,7 @@ function injectMeshSystemMessage(components, args) {
20543
20610
  }
20544
20611
  });
20545
20612
  }
20613
+ } else if (strandedRow && supersedeRedriveReclaimForLateCompletion(components, args.meshId, strandedRow, sessionId, outcome, args)) {
20546
20614
  }
20547
20615
  } catch {
20548
20616
  }
@@ -21869,6 +21937,50 @@ async function autoPruneStaleDirectDispatches(components, mesh, selfIds, localDa
21869
21937
  LOG.info("MeshReconcile", `Auto-pruned ${result.prunedCount} orphaned direct dispatch record(s) for mesh ${mesh.id}`);
21870
21938
  }
21871
21939
  }
21940
+ async function pollAssignedTaskTerminalEvidence(components, mesh, row) {
21941
+ const sessionId = readNonEmptyString2(row.assignedSessionId);
21942
+ const nodeId = readNonEmptyString2(row.assignedNodeId);
21943
+ if (!sessionId || !nodeId) return null;
21944
+ const node = (mesh.nodes ?? []).find((n) => n.id === nodeId);
21945
+ const nodeDaemonId = readNonEmptyString2(node?.daemonId);
21946
+ const localDaemonId = readNonEmptyString2(components.statusInstanceId);
21947
+ const isLocalNode = !nodeDaemonId || daemonIdsEquivalent(nodeDaemonId, localDaemonId) || !!components.instanceManager.getInstance(sessionId);
21948
+ const providerType = readNonEmptyString2(row.assignedProviderType);
21949
+ const readArgs = {
21950
+ sessionId,
21951
+ targetSessionId: sessionId,
21952
+ tailLimit: 10,
21953
+ ...node?.workspace ? { workspace: node.workspace } : {},
21954
+ ...providerType ? { agentType: providerType, providerType } : {}
21955
+ };
21956
+ let payload = null;
21957
+ try {
21958
+ if (isLocalNode) {
21959
+ const result = await components.commandHandler?.handle("read_chat", readArgs);
21960
+ if (result && result.success === false) return null;
21961
+ payload = unwrapReadChatPayload(result);
21962
+ } else if (components.dispatchMeshCommand) {
21963
+ const result = await components.dispatchMeshCommand(nodeDaemonId, "read_chat", readArgs);
21964
+ payload = unwrapReadChatPayload(result);
21965
+ if (payload && payload.success === false) return null;
21966
+ } else {
21967
+ return null;
21968
+ }
21969
+ } catch {
21970
+ return null;
21971
+ }
21972
+ if (!payload) return null;
21973
+ if (readChatPayloadStatus(payload) !== "idle") return null;
21974
+ const messages = Array.isArray(payload.messages) ? payload.messages : [];
21975
+ const evidence = extractFinalAssistantSummaryEvidence(messages);
21976
+ if (!evidence.finalSummary) return null;
21977
+ const dispatchedAtMs = Date.parse(readNonEmptyString2(row.dispatchTimestamp));
21978
+ const transcriptAtMs = Date.parse(evidence.transcriptMessageAt ?? "");
21979
+ if (!(Number.isFinite(dispatchedAtMs) && Number.isFinite(transcriptAtMs) && transcriptAtMs >= dispatchedAtMs)) {
21980
+ return null;
21981
+ }
21982
+ return "completed";
21983
+ }
21872
21984
  var init_mesh_completion_synthesis = __esm({
21873
21985
  "src/mesh/mesh-completion-synthesis.ts"() {
21874
21986
  "use strict";
@@ -22145,7 +22257,8 @@ function drainAndDeliverApprovalNudges(meshId, drainDaemonIds, localDaemonId, me
22145
22257
  }
22146
22258
  return delivered;
22147
22259
  }
22148
- function recoverStrandedAssignedDispatches(components, meshId, store) {
22260
+ async function recoverStrandedAssignedDispatches(components, mesh, store) {
22261
+ const meshId = mesh.id;
22149
22262
  const assigned = getQueue(meshId, { status: ["assigned"] });
22150
22263
  if (!assigned.length) return;
22151
22264
  const nowMs = Date.now();
@@ -22252,6 +22365,36 @@ function recoverStrandedAssignedDispatches(components, meshId, store) {
22252
22365
  }
22253
22366
  reclaimReason = "reclaim_after_unknown_grace";
22254
22367
  }
22368
+ const terminalEvidence = await pollAssignedTaskTerminalEvidence(components, mesh, row);
22369
+ if (terminalEvidence) {
22370
+ deliveredNoTurnUnknownStreak.delete(streakKey);
22371
+ updateTaskStatus(meshId, row.id, terminalEvidence);
22372
+ if (!findTerminalLedgerEvidenceForTask({ meshId, taskId: row.id })) {
22373
+ try {
22374
+ appendLedgerEntry(meshId, {
22375
+ kind: terminalEvidence === "completed" ? "task_completed" : "task_failed",
22376
+ nodeId: row.assignedNodeId,
22377
+ sessionId: row.assignedSessionId,
22378
+ providerType: row.assignedProviderType,
22379
+ payload: {
22380
+ taskId: row.id,
22381
+ event: "agent:generating_completed",
22382
+ source: "redrive_deadline_transcript_evidence"
22383
+ }
22384
+ });
22385
+ } catch {
22386
+ }
22387
+ }
22388
+ LOG.warn("MeshReconcile", `Skipped delivered-no-turn re-drive for task ${row.id} on mesh ${meshId} (node=${row.assignedNodeId ?? "?"} session=${row.assignedSessionId ?? "?"}): worker transcript is idle with a final assistant message after dispatch \u2014 the completion event was lost/late, task is ${terminalEvidence}, NOT re-driving`);
22389
+ traceMeshEventDrop("redrive_deadline_transcript_completed", {
22390
+ taskId: row.id,
22391
+ sessionId: row.assignedSessionId,
22392
+ nodeId: row.assignedNodeId,
22393
+ meshId,
22394
+ event: "agent:generating_completed"
22395
+ }, `${reclaimReason} \u2192 transcript ${terminalEvidence}`);
22396
+ continue;
22397
+ }
22255
22398
  const reclaimedLost = reclaimStrandedAssignedTask(meshId, row.id, {
22256
22399
  reason: reclaimReason,
22257
22400
  ageMs: nowMs - dispatchedAtMs
@@ -22378,7 +22521,7 @@ async function runMeshReconcileTick(components) {
22378
22521
  const selfIds = resolveCoordinatorSelfIds(mesh, drainDaemonIds);
22379
22522
  if (!daemonHostsMesh(mesh, selfIds)) continue;
22380
22523
  try {
22381
- recoverStrandedAssignedDispatches(components, mesh.id, store);
22524
+ await recoverStrandedAssignedDispatches(components, mesh, store);
22382
22525
  } catch (e) {
22383
22526
  LOG.warn("MeshReconcile", `Assigned-stranded watchdog failed for mesh ${mesh.id}: ${e?.message || e}`);
22384
22527
  }
@@ -24685,6 +24828,21 @@ function compile3(re, flags) {
24685
24828
  }
24686
24829
  function findQuestionLineIndex(spec, lines) {
24687
24830
  const primary = compile3(spec.questionPattern, spec.questionFlags ?? "i");
24831
+ const buttonFlags = spec.buttonFlags && spec.buttonFlags.includes("m") ? spec.buttonFlags : `${spec.buttonFlags ?? ""}m`;
24832
+ const buttonRe = compile3(spec.buttonPattern, buttonFlags);
24833
+ const isButtonLine = (line) => {
24834
+ buttonRe.lastIndex = 0;
24835
+ return buttonRe.test(line);
24836
+ };
24837
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
24838
+ if (primary.test(lines[i]) && !isButtonLine(lines[i])) return { index: i, matchedSource: "primary" };
24839
+ }
24840
+ for (const variant of spec.questionVariants ?? []) {
24841
+ const re = compile3(variant.regex, variant.flags ?? "i");
24842
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
24843
+ if (re.test(lines[i]) && !isButtonLine(lines[i])) return { index: i, matchedSource: variant.label ?? "variant" };
24844
+ }
24845
+ }
24688
24846
  for (let i = lines.length - 1; i >= 0; i -= 1) {
24689
24847
  if (primary.test(lines[i])) return { index: i, matchedSource: "primary" };
24690
24848
  }
@@ -25414,6 +25572,7 @@ var init_cli_state_engine = __esm({
25414
25572
  * paint and made the engine type "1" repeatedly into the prompt.
25415
25573
  */
25416
25574
  modalLostAt = 0;
25575
+ modalLostRecheckTimer = null;
25417
25576
  approvalExitTimeout = null;
25418
25577
  // ── Response tracking ────────────────────────────
25419
25578
  responseEpoch = 0;
@@ -25465,6 +25624,10 @@ var init_cli_state_engine = __esm({
25465
25624
  setStatus(status, trigger) {
25466
25625
  const prev = this.currentStatus;
25467
25626
  if (prev === status) return;
25627
+ if (prev === "waiting_approval" && this.modalLostRecheckTimer) {
25628
+ clearTimeout(this.modalLostRecheckTimer);
25629
+ this.modalLostRecheckTimer = null;
25630
+ }
25468
25631
  this.currentStatus = status;
25469
25632
  this.statusHistory.push({ status, at: Date.now(), trigger });
25470
25633
  if (this.statusHistory.length > 50) this.statusHistory.shift();
@@ -25625,6 +25788,10 @@ var init_cli_state_engine = __esm({
25625
25788
  clearTimeout(this.approvalExitTimeout);
25626
25789
  this.approvalExitTimeout = null;
25627
25790
  }
25791
+ if (this.modalLostRecheckTimer) {
25792
+ clearTimeout(this.modalLostRecheckTimer);
25793
+ this.modalLostRecheckTimer = null;
25794
+ }
25628
25795
  if (this.finishRetryTimer) {
25629
25796
  clearTimeout(this.finishRetryTimer);
25630
25797
  this.finishRetryTimer = null;
@@ -25936,7 +26103,7 @@ var init_cli_state_engine = __esm({
25936
26103
  if (!inCooldown || modal) {
25937
26104
  if (!modal) {
25938
26105
  LOG.warn("CLI", `[${this.provider.type}] detectStatus=waiting_approval but parseApproval returned null; ignoring`);
25939
- if (this.currentStatus === "waiting_approval" && this.activeModal) {
26106
+ if (this.currentStatus === "waiting_approval") {
25940
26107
  const lostAt = this.modalLostAt || Date.now();
25941
26108
  if (!this.modalLostAt) this.modalLostAt = lostAt;
25942
26109
  if (Date.now() - lostAt >= this.timeouts.approvalCooldown) {
@@ -25944,6 +26111,8 @@ var init_cli_state_engine = __esm({
25944
26111
  this.modalLostAt = 0;
25945
26112
  this.setStatus("generating", "approval_lost_modal");
25946
26113
  this.callbacks.onStatusChange();
26114
+ } else {
26115
+ this.armModalLostRecheck();
25947
26116
  }
25948
26117
  }
25949
26118
  return;
@@ -26205,6 +26374,25 @@ var init_cli_state_engine = __esm({
26205
26374
  this.recordTrace("idle_finish_cancelled", { trigger: reason });
26206
26375
  }
26207
26376
  // ─── Helpers ────────────────────────────────────────────────────────────
26377
+ /**
26378
+ * Schedule one more settled evaluation while pinned to `waiting_approval`
26379
+ * with no actionable modal. The settled FSM normally only re-runs on new
26380
+ * PTY output; a provider whose modal cue lingers in a form detectStatus
26381
+ * still matches (e.g. kimi's questionPattern hitting the user echo) but
26382
+ * whose PTY has gone quiet would never get another evaluation, latching
26383
+ * `waiting_approval` forever. This timer guarantees the modal-lost recovery
26384
+ * in `applyWaitingApproval` is reached even against a silent PTY. It is a
26385
+ * no-op once the FSM leaves `waiting_approval` (the re-evaluation itself
26386
+ * takes the recovery branch and clears the state).
26387
+ */
26388
+ armModalLostRecheck() {
26389
+ if (this.modalLostRecheckTimer) return;
26390
+ this.modalLostRecheckTimer = setTimeout(() => {
26391
+ this.modalLostRecheckTimer = null;
26392
+ if (this.currentStatus !== "waiting_approval") return;
26393
+ this.evaluateSettled(this.transport.getSnapshot());
26394
+ }, this.timeouts.approvalCooldown);
26395
+ }
26208
26396
  armApprovalExitTimeout() {
26209
26397
  if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
26210
26398
  this.approvalExitTimeout = setTimeout(() => {
@@ -35618,6 +35806,33 @@ var ChatSourceRegistry = class {
35618
35806
  getState(key2) {
35619
35807
  return this.records.get(key2)?.state ?? INITIAL_CHAT_SOURCE_STATE;
35620
35808
  }
35809
+ /**
35810
+ * Opaque snapshot of a session's FULL record (state + lock + transitions),
35811
+ * for callers that need to speculatively `observe()` and then roll back if
35812
+ * the resulting decision is undesirable (STICKY-NATIVE hold: a trusted
35813
+ * exact-identity session must not flip to PTY on a transient native gap).
35814
+ * Returns undefined when the session has no record yet. The snapshot is a
35815
+ * shallow copy — transitions array is copied so a later append does not
35816
+ * mutate it.
35817
+ */
35818
+ snapshotRecord(key2) {
35819
+ const rec = this.records.get(key2);
35820
+ if (!rec) return void 0;
35821
+ return { state: rec.state, lockedSince: rec.lockedSince, transitions: [...rec.transitions] };
35822
+ }
35823
+ /** Restore a previously snapshotted record, undoing a speculative observe.
35824
+ * Passing undefined clears the key (it had no record when snapshotted). */
35825
+ restoreRecord(key2, snapshot) {
35826
+ if (!snapshot) {
35827
+ this.records.delete(key2);
35828
+ return;
35829
+ }
35830
+ this.records.set(key2, {
35831
+ state: snapshot.state,
35832
+ lockedSince: snapshot.lockedSince,
35833
+ transitions: [...snapshot.transitions]
35834
+ });
35835
+ }
35621
35836
  /** Recent transitions, newest last. Empty array when nothing has happened. */
35622
35837
  getTransitions(key2) {
35623
35838
  return this.records.get(key2)?.transitions ?? [];
@@ -36041,7 +36256,52 @@ function decideCliReadChatSource(args) {
36041
36256
  const supportsNative = supportsCliNativeTranscript(args.providerType, args.provider);
36042
36257
  const observation = buildObservationForCli(args, supportsNative);
36043
36258
  const sessionKey = chatSourceSessionKey(args.providerType, args.sessionId);
36259
+ const priorSnapshot = CHAT_SOURCE_REGISTRY.snapshotRecord(sessionKey);
36260
+ const priorState = priorSnapshot?.state ?? CHAT_SOURCE_REGISTRY.getState(sessionKey);
36261
+ const eligibleForStickyHold = args.trustedExactNativeIdentity === true && (priorState.name === "NativeLocked" || priorState.name === "Recovering");
36044
36262
  let decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
36263
+ if (eligibleForStickyHold && decision.selected === "pty-parser") {
36264
+ CHAT_SOURCE_REGISTRY.restoreRecord(sessionKey, priorSnapshot);
36265
+ const heldNativeMessages = observation.kind === "native_present" ? extractNativeMessagesFromResult(args.providerType, args.nativeHistoryResult) : [];
36266
+ const messageSource2 = buildCliMessageSourceProvenance({
36267
+ selected: "native-history",
36268
+ provider: args.providerType,
36269
+ nativeHandle: typeof args.nativeHistoryResult?.providerSessionId === "string" ? args.nativeHistoryResult.providerSessionId : void 0,
36270
+ sessionWorkspace: args.sessionWorkspace,
36271
+ intendedWorkspace: args.intendedWorkspace,
36272
+ transcriptWorkspace: void 0,
36273
+ fallbackReason: "native_history_transient_gap_held",
36274
+ nativeSource: "provider-native",
36275
+ sourcePath: typeof args.nativeHistoryResult?.sourcePath === "string" ? args.nativeHistoryResult.sourcePath : void 0,
36276
+ sourceMtimeMs: typeof args.nativeHistoryResult?.sourceMtimeMs === "number" ? args.nativeHistoryResult.sourceMtimeMs : void 0,
36277
+ nativeHistoryCoverage: void 0,
36278
+ partialReason: void 0,
36279
+ unavailableReason: observation.kind === "native_unavailable" ? observation.reason : void 0,
36280
+ nativeMessages: heldNativeMessages,
36281
+ ptyMessages: args.ptyMessages,
36282
+ returnedMessages: heldNativeMessages,
36283
+ safeMapping: args.safeMapping,
36284
+ freshEnough: true,
36285
+ ptyStatusApprovalOnly: true
36286
+ });
36287
+ return {
36288
+ decision: {
36289
+ selected: "native-history",
36290
+ nextState: priorState,
36291
+ transition: {
36292
+ fromState: priorState.name,
36293
+ toState: priorState.name,
36294
+ event: "NoOp",
36295
+ cause: decision.transition.cause,
36296
+ at: Date.now()
36297
+ },
36298
+ lockState: { locked: priorState.name === "NativeLocked" }
36299
+ },
36300
+ messageSource: messageSource2,
36301
+ nativeMessages: heldNativeMessages,
36302
+ nativeSelected: true
36303
+ };
36304
+ }
36045
36305
  if (decision.selected === "pty-parser" && args.trustedExactNativeIdentity === true && args.safeMapping && args.ptyMessages.length === 0 && observation.kind === "native_present" && observation.coverage !== "partial" && observation.messages.length > 0) {
36046
36306
  CHAT_SOURCE_REGISTRY.clear(sessionKey);
36047
36307
  decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
@@ -36361,7 +36621,7 @@ function sessionSpawnEnvFromAdapter(h, targetSessionId) {
36361
36621
  function readCliProviderNativeHistory(agentStr, args) {
36362
36622
  const canBindFromLiveSession = !args.historySessionId && typeof args.sessionStartedAtMs === "number" && args.sessionStartedAtMs > 0 && typeof args.workspace === "string" && args.workspace.trim().length > 0;
36363
36623
  const pinnedProviderSessionId = typeof args.pinnedProviderSessionId === "string" ? args.pinnedProviderSessionId.trim() : "";
36364
- const effectiveHistorySessionId = args.historySessionId || (!canBindFromLiveSession ? pinnedProviderSessionId : "");
36624
+ const effectiveHistorySessionId = args.historySessionId || pinnedProviderSessionId || "";
36365
36625
  const workspaceLatestFallback = !effectiveHistorySessionId && !canBindFromLiveSession && !pinnedProviderSessionId && args.allowWorkspaceLatestFallback === true && typeof args.workspace === "string" && args.workspace.trim().length > 0;
36366
36626
  if (!effectiveHistorySessionId && !canBindFromLiveSession && !workspaceLatestFallback) {
36367
36627
  return {