@adhdev/daemon-standalone 0.9.82-rc.536 → 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.js CHANGED
@@ -30208,10 +30208,10 @@ var require_dist3 = __commonJS({
30208
30208
  }
30209
30209
  function getDaemonBuildInfo() {
30210
30210
  if (cached2) return cached2;
30211
- const commit = readInjected(true ? "e7001a2fe5b5af35d3710c964727f7e4d735e852" : void 0) ?? "unknown";
30212
- const commitShort = readInjected(true ? "e7001a2f" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
30213
- const version2 = readInjected(true ? "0.9.82-rc.536" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
30214
- const builtAt = readInjected(true ? "2026-07-15T11:40:00.569Z" : void 0);
30211
+ const commit = readInjected(true ? "35ac849eca76162066561b27633e11827fa19a73" : void 0) ?? "unknown";
30212
+ const commitShort = readInjected(true ? "35ac849e" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
30213
+ const version2 = readInjected(true ? "0.9.82-rc.538" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
30214
+ const builtAt = readInjected(true ? "2026-07-15T15:52:37.408Z" : void 0);
30215
30215
  cached2 = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
30216
30216
  return cached2;
30217
30217
  }
@@ -35565,6 +35565,8 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
35565
35565
  MESH_TASK_MODES: () => MESH_TASK_MODES,
35566
35566
  MESH_TASK_PRIORITIES: () => MESH_TASK_PRIORITIES,
35567
35567
  NOT_BEFORE_RELATIVE_THRESHOLD_MS: () => NOT_BEFORE_RELATIVE_THRESHOLD_MS,
35568
+ REDRIVE_RECLAIM_REASONS: () => REDRIVE_RECLAIM_REASONS,
35569
+ REDRIVE_SUPERSEDE_WINDOW_MS: () => REDRIVE_SUPERSEDE_WINDOW_MS,
35568
35570
  __clearDirectDispatchesForTests: () => __clearDirectDispatchesForTests,
35569
35571
  __clearMeshQueueForTests: () => __clearMeshQueueForTests,
35570
35572
  __replaceMeshQueueForTests: () => __replaceMeshQueueForTests,
@@ -35860,10 +35862,21 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
35860
35862
  return true;
35861
35863
  });
35862
35864
  }
35863
- function firstProviderPriority(policy) {
35864
- const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
35865
- if (!Array.isArray(raw)) return void 0;
35866
- return raw.find((type) => typeof type === "string" && type.trim())?.trim();
35865
+ function readNodeProviderTypes(policy) {
35866
+ const record2 = policy && typeof policy === "object" && !Array.isArray(policy) ? policy : {};
35867
+ const seen = /* @__PURE__ */ new Set();
35868
+ const out = [];
35869
+ const push = (type) => {
35870
+ const trimmed = typeof type === "string" ? type.trim() : "";
35871
+ if (!trimmed || seen.has(trimmed)) return;
35872
+ seen.add(trimmed);
35873
+ out.push(trimmed);
35874
+ };
35875
+ for (const slot of normalizeNodeCapabilitySlots(record2.slots)) push(slot.provider);
35876
+ if (Array.isArray(record2.providerPriority)) {
35877
+ for (const type of record2.providerPriority) push(type);
35878
+ }
35879
+ return out;
35867
35880
  }
35868
35881
  function readNodeOverride(node, key2) {
35869
35882
  const overrides = node?.userOverrides;
@@ -35876,7 +35889,8 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
35876
35889
  return typeof value === "string" && value.trim() ? value.trim() : null;
35877
35890
  }
35878
35891
  function buildMeshNodeCapabilityTags(node, providerType) {
35879
- const provider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : firstProviderPriority(node?.policy);
35892
+ const pinnedProvider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : void 0;
35893
+ const providerTags = pinnedProvider ? [pinnedProvider] : readNodeProviderTypes(node?.policy);
35880
35894
  const worktreeBranch = typeof node?.worktreeBranch === "string" && node.worktreeBranch.trim() ? node.worktreeBranch.trim() : null;
35881
35895
  const os322 = readNodeOverride(node, "platform") ?? readNodeReporter(node, "platform") ?? process.platform;
35882
35896
  const arch2 = readNodeOverride(node, "arch") ?? readNodeReporter(node, "arch") ?? process.arch;
@@ -35884,7 +35898,7 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
35884
35898
  ...Array.isArray(node?.capabilities) ? node.capabilities : [],
35885
35899
  `os=${os322}`,
35886
35900
  `arch=${arch2}`,
35887
- ...provider ? [`provider=${provider}`] : [],
35901
+ ...providerTags.map((p) => `provider=${p}`),
35888
35902
  // Worktree nodes automatically expose a "worktree=<branch>" tag so that
35889
35903
  // mesh_enqueue_task with required_tags: ["worktree=<branch>"] routes
35890
35904
  // only to the matching worktree node.
@@ -36400,6 +36414,8 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
36400
36414
  var GIT_STASH_READONLY_SUBCOMMANDS;
36401
36415
  var DEPENDENCY_FAILURE_TERMINALS;
36402
36416
  var MAX_STRANDED_RECLAIMS;
36417
+ var REDRIVE_RECLAIM_REASONS;
36418
+ var REDRIVE_SUPERSEDE_WINDOW_MS;
36403
36419
  var init_mesh_work_queue = __esm2({
36404
36420
  "src/mesh/mesh-work-queue.ts"() {
36405
36421
  "use strict";
@@ -36468,6 +36484,12 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
36468
36484
  GIT_STASH_READONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "show"]);
36469
36485
  DEPENDENCY_FAILURE_TERMINALS = /* @__PURE__ */ new Set(["failed", "cancelled"]);
36470
36486
  MAX_STRANDED_RECLAIMS = 3;
36487
+ REDRIVE_RECLAIM_REASONS = /* @__PURE__ */ new Set([
36488
+ "delivered_no_turn_deadline",
36489
+ "reclaim_after_unknown_grace",
36490
+ "delivered_not_consumed_redrive"
36491
+ ]);
36492
+ REDRIVE_SUPERSEDE_WINDOW_MS = 5 * 6e4;
36471
36493
  }
36472
36494
  });
36473
36495
  function loadDatabaseCtor() {
@@ -46753,7 +46775,8 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
46753
46775
  if (task.targetNodeId && !meshNodeIdMatches(node, task.targetNodeId)) return false;
46754
46776
  if (task.taskMode === "convergence" && node?.isLocalWorktree === true) return false;
46755
46777
  if (task.requiredTags?.length) {
46756
- const priorities = normalizeProviderPriority2(node?.policy);
46778
+ const slotProviders = resolveNodeCapabilitySlots(node).map((s2) => s2.provider).filter(Boolean);
46779
+ const priorities = slotProviders.length ? slotProviders : normalizeProviderPriority2(node?.policy);
46757
46780
  const providerCandidates = priorities.length ? priorities : [void 0];
46758
46781
  return providerCandidates.some(
46759
46782
  (p) => nodeSatisfiesRequiredTags(task.requiredTags, buildMeshNodeCapabilityTags(node, p))
@@ -50373,6 +50396,52 @@ ${cleanBody}`;
50373
50396
  LOG2.warn("MeshQueue", `stopStaleMeshWorker error for ${sessionId}: ${e?.message || e}`);
50374
50397
  }
50375
50398
  }
50399
+ function supersedeRedriveReclaimForLateCompletion(components, meshId, row, completingSessionId, outcome, args) {
50400
+ if (!row.requeueReason || !REDRIVE_RECLAIM_REASONS.has(row.requeueReason)) return false;
50401
+ if (row.status === "completed" || row.status === "failed" || row.status === "cancelled") return false;
50402
+ const requeuedAtMs = Date.parse(row.requeuedAt ?? "");
50403
+ if (!Number.isFinite(requeuedAtMs)) return false;
50404
+ if (Date.now() - requeuedAtMs > REDRIVE_SUPERSEDE_WINDOW_MS) return false;
50405
+ const reDispatchedSessionId = row.assignedSessionId;
50406
+ if (row.status === "assigned" && reDispatchedSessionId && !sessionIdsEquivalent(reDispatchedSessionId, completingSessionId)) {
50407
+ stopStaleMeshWorker(components, {
50408
+ meshId,
50409
+ sessionId: reDispatchedSessionId,
50410
+ nodeId: row.assignedNodeId,
50411
+ providerType: row.assignedProviderType
50412
+ });
50413
+ }
50414
+ endTaskDispatchInFlight(meshId, row.id);
50415
+ updateTaskStatus(meshId, row.id, outcome === "completed" ? "completed" : "failed");
50416
+ if (!findTerminalLedgerEvidenceForTask({ meshId, taskId: row.id })) {
50417
+ try {
50418
+ appendLedgerEntry(meshId, {
50419
+ kind: outcome === "completed" ? "task_completed" : "task_failed",
50420
+ sessionId: completingSessionId,
50421
+ nodeId: readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId) || void 0,
50422
+ providerType: readNonEmptyString2(args.metadataEvent.providerType) || void 0,
50423
+ payload: {
50424
+ taskId: row.id,
50425
+ event: args.event,
50426
+ source: "redrive_late_completion_supersede",
50427
+ reclaimReason: row.requeueReason,
50428
+ reclaimAgeMs: Date.now() - requeuedAtMs,
50429
+ finalSummary: readNonEmptyString2(args.metadataEvent.finalSummary) || void 0
50430
+ }
50431
+ });
50432
+ } catch {
50433
+ }
50434
+ }
50435
+ LOG2.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}` : ""}`);
50436
+ traceMeshEventDrop("redrive_late_completion_supersede", {
50437
+ taskId: row.id,
50438
+ sessionId: completingSessionId,
50439
+ nodeId: row.assignedNodeId ?? args.nodeId,
50440
+ meshId,
50441
+ event: args.event
50442
+ }, `${row.requeueReason} ${Math.round((Date.now() - requeuedAtMs) / 1e3)}s \u2192 ${outcome}`);
50443
+ return true;
50444
+ }
50376
50445
  function injectMeshSystemMessage(components, args) {
50377
50446
  const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
50378
50447
  const eventNodeId = readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId);
@@ -50484,6 +50553,7 @@ ${cleanBody}`;
50484
50553
  }
50485
50554
  });
50486
50555
  }
50556
+ } else if (strandedRow && supersedeRedriveReclaimForLateCompletion(components, args.meshId, strandedRow, sessionId, outcome, args)) {
50487
50557
  }
50488
50558
  } catch {
50489
50559
  }
@@ -51807,6 +51877,50 @@ ${cleanBody}`;
51807
51877
  LOG2.info("MeshReconcile", `Auto-pruned ${result.prunedCount} orphaned direct dispatch record(s) for mesh ${mesh.id}`);
51808
51878
  }
51809
51879
  }
51880
+ async function pollAssignedTaskTerminalEvidence(components, mesh, row) {
51881
+ const sessionId = readNonEmptyString2(row.assignedSessionId);
51882
+ const nodeId = readNonEmptyString2(row.assignedNodeId);
51883
+ if (!sessionId || !nodeId) return null;
51884
+ const node = (mesh.nodes ?? []).find((n) => n.id === nodeId);
51885
+ const nodeDaemonId = readNonEmptyString2(node?.daemonId);
51886
+ const localDaemonId = readNonEmptyString2(components.statusInstanceId);
51887
+ const isLocalNode = !nodeDaemonId || daemonIdsEquivalent(nodeDaemonId, localDaemonId) || !!components.instanceManager.getInstance(sessionId);
51888
+ const providerType = readNonEmptyString2(row.assignedProviderType);
51889
+ const readArgs = {
51890
+ sessionId,
51891
+ targetSessionId: sessionId,
51892
+ tailLimit: 10,
51893
+ ...node?.workspace ? { workspace: node.workspace } : {},
51894
+ ...providerType ? { agentType: providerType, providerType } : {}
51895
+ };
51896
+ let payload = null;
51897
+ try {
51898
+ if (isLocalNode) {
51899
+ const result = await components.commandHandler?.handle("read_chat", readArgs);
51900
+ if (result && result.success === false) return null;
51901
+ payload = unwrapReadChatPayload(result);
51902
+ } else if (components.dispatchMeshCommand) {
51903
+ const result = await components.dispatchMeshCommand(nodeDaemonId, "read_chat", readArgs);
51904
+ payload = unwrapReadChatPayload(result);
51905
+ if (payload && payload.success === false) return null;
51906
+ } else {
51907
+ return null;
51908
+ }
51909
+ } catch {
51910
+ return null;
51911
+ }
51912
+ if (!payload) return null;
51913
+ if (readChatPayloadStatus(payload) !== "idle") return null;
51914
+ const messages = Array.isArray(payload.messages) ? payload.messages : [];
51915
+ const evidence = extractFinalAssistantSummaryEvidence(messages);
51916
+ if (!evidence.finalSummary) return null;
51917
+ const dispatchedAtMs = Date.parse(readNonEmptyString2(row.dispatchTimestamp));
51918
+ const transcriptAtMs = Date.parse(evidence.transcriptMessageAt ?? "");
51919
+ if (!(Number.isFinite(dispatchedAtMs) && Number.isFinite(transcriptAtMs) && transcriptAtMs >= dispatchedAtMs)) {
51920
+ return null;
51921
+ }
51922
+ return "completed";
51923
+ }
51810
51924
  var init_mesh_completion_synthesis = __esm2({
51811
51925
  "src/mesh/mesh-completion-synthesis.ts"() {
51812
51926
  "use strict";
@@ -52081,7 +52195,8 @@ ${cleanBody}`;
52081
52195
  }
52082
52196
  return delivered;
52083
52197
  }
52084
- function recoverStrandedAssignedDispatches(components, meshId, store) {
52198
+ async function recoverStrandedAssignedDispatches(components, mesh, store) {
52199
+ const meshId = mesh.id;
52085
52200
  const assigned = getQueue(meshId, { status: ["assigned"] });
52086
52201
  if (!assigned.length) return;
52087
52202
  const nowMs = Date.now();
@@ -52188,6 +52303,36 @@ ${cleanBody}`;
52188
52303
  }
52189
52304
  reclaimReason = "reclaim_after_unknown_grace";
52190
52305
  }
52306
+ const terminalEvidence = await pollAssignedTaskTerminalEvidence(components, mesh, row);
52307
+ if (terminalEvidence) {
52308
+ deliveredNoTurnUnknownStreak.delete(streakKey);
52309
+ updateTaskStatus(meshId, row.id, terminalEvidence);
52310
+ if (!findTerminalLedgerEvidenceForTask({ meshId, taskId: row.id })) {
52311
+ try {
52312
+ appendLedgerEntry(meshId, {
52313
+ kind: terminalEvidence === "completed" ? "task_completed" : "task_failed",
52314
+ nodeId: row.assignedNodeId,
52315
+ sessionId: row.assignedSessionId,
52316
+ providerType: row.assignedProviderType,
52317
+ payload: {
52318
+ taskId: row.id,
52319
+ event: "agent:generating_completed",
52320
+ source: "redrive_deadline_transcript_evidence"
52321
+ }
52322
+ });
52323
+ } catch {
52324
+ }
52325
+ }
52326
+ LOG2.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`);
52327
+ traceMeshEventDrop("redrive_deadline_transcript_completed", {
52328
+ taskId: row.id,
52329
+ sessionId: row.assignedSessionId,
52330
+ nodeId: row.assignedNodeId,
52331
+ meshId,
52332
+ event: "agent:generating_completed"
52333
+ }, `${reclaimReason} \u2192 transcript ${terminalEvidence}`);
52334
+ continue;
52335
+ }
52191
52336
  const reclaimedLost = reclaimStrandedAssignedTask(meshId, row.id, {
52192
52337
  reason: reclaimReason,
52193
52338
  ageMs: nowMs - dispatchedAtMs
@@ -52314,7 +52459,7 @@ ${cleanBody}`;
52314
52459
  const selfIds = resolveCoordinatorSelfIds(mesh, drainDaemonIds);
52315
52460
  if (!daemonHostsMesh(mesh, selfIds)) continue;
52316
52461
  try {
52317
- recoverStrandedAssignedDispatches(components, mesh.id, store);
52462
+ await recoverStrandedAssignedDispatches(components, mesh, store);
52318
52463
  } catch (e) {
52319
52464
  LOG2.warn("MeshReconcile", `Assigned-stranded watchdog failed for mesh ${mesh.id}: ${e?.message || e}`);
52320
52465
  }
@@ -54488,6 +54633,70 @@ ${cleanBody}`;
54488
54633
  if (buttonBlockApprovalCue(spec, text)) return true;
54489
54634
  return false;
54490
54635
  }
54636
+ function lastModalCueLine(spec, screenText) {
54637
+ if (!screenText) return -1;
54638
+ const lines = screenText.split("\n");
54639
+ const question = compile2(spec.questionPattern, spec.questionFlags ?? "i");
54640
+ const variants = (spec.questionVariants ?? []).map((v) => compile2(v.regex, v.flags ?? "i"));
54641
+ const buttonFlags = spec.buttonFlags && spec.buttonFlags.includes("m") ? spec.buttonFlags : `${spec.buttonFlags ?? ""}m`;
54642
+ const buttonRe = compile2(spec.buttonPattern, buttonFlags);
54643
+ let last = -1;
54644
+ for (let i = 0; i < lines.length; i++) {
54645
+ const line = lines[i];
54646
+ question.lastIndex = 0;
54647
+ if (question.test(line)) {
54648
+ last = i;
54649
+ continue;
54650
+ }
54651
+ if (variants.some((re) => {
54652
+ re.lastIndex = 0;
54653
+ return re.test(line);
54654
+ })) {
54655
+ last = i;
54656
+ continue;
54657
+ }
54658
+ buttonRe.lastIndex = 0;
54659
+ if (buttonRe.test(line)) {
54660
+ last = i;
54661
+ continue;
54662
+ }
54663
+ }
54664
+ return last;
54665
+ }
54666
+ function modalSupersededBySettledPrompt(modalSpec, settledSpec, settled, input) {
54667
+ if (!settled || !settledSpec) return false;
54668
+ if (settledSpec.scope === "whole-screen") return false;
54669
+ const screenText = input.screenText ?? "";
54670
+ if (!screenText) return false;
54671
+ const modalLine = lastModalCueLine(modalSpec, screenText);
54672
+ if (modalLine < 0) return false;
54673
+ const lines = screenText.split("\n");
54674
+ const below = lines.slice(modalLine + 1);
54675
+ if (below.length === 0) return false;
54676
+ const belowText = below.join("\n");
54677
+ if (!settled.prompt.test(belowText)) return false;
54678
+ if (settled.footers.length > 0 && !settled.footers.every((f) => f.test(belowText))) return false;
54679
+ const question = compile2(modalSpec.questionPattern, modalSpec.questionFlags ?? "i");
54680
+ const variants = (modalSpec.questionVariants ?? []).map((v) => compile2(v.regex, v.flags ?? "i"));
54681
+ const buttonFlags = modalSpec.buttonFlags && modalSpec.buttonFlags.includes("m") ? modalSpec.buttonFlags : `${modalSpec.buttonFlags ?? ""}m`;
54682
+ const buttonRe = compile2(modalSpec.buttonPattern, buttonFlags);
54683
+ const isModalCueLine = (line) => {
54684
+ question.lastIndex = 0;
54685
+ if (question.test(line)) return true;
54686
+ if (variants.some((re) => {
54687
+ re.lastIndex = 0;
54688
+ return re.test(line);
54689
+ })) return true;
54690
+ buttonRe.lastIndex = 0;
54691
+ return buttonRe.test(line);
54692
+ };
54693
+ const settledPromptLineRe = compile2(settledSpec.regex, (settledSpec.flags ?? "m").includes("m") ? settledSpec.flags ?? "m" : `${settledSpec.flags ?? ""}m`);
54694
+ const isSettledLine = (line) => {
54695
+ settledPromptLineRe.lastIndex = 0;
54696
+ return settledPromptLineRe.test(line);
54697
+ };
54698
+ return below.some((line) => line.trim() !== "" && !isModalCueLine(line) && !isSettledLine(line));
54699
+ }
54491
54700
  function evaluateGroup(group, spec, input, compiled) {
54492
54701
  switch (group) {
54493
54702
  case "spinner": {
@@ -54497,7 +54706,9 @@ ${cleanBody}`;
54497
54706
  }
54498
54707
  case "modal": {
54499
54708
  if (!spec.modal) return null;
54500
- return modalMatches(spec.modal, input) ? "waiting_approval" : null;
54709
+ if (!modalMatches(spec.modal, input)) return null;
54710
+ if (modalSupersededBySettledPrompt(spec.modal, spec.settledPrompt, compiled.settled, input)) return null;
54711
+ return "waiting_approval";
54501
54712
  }
54502
54713
  case "settled-prompt": {
54503
54714
  if (!spec.settledPrompt || !compiled.settled) return null;
@@ -54553,6 +54764,21 @@ ${cleanBody}`;
54553
54764
  }
54554
54765
  function findQuestionLineIndex(spec, lines) {
54555
54766
  const primary = compile3(spec.questionPattern, spec.questionFlags ?? "i");
54767
+ const buttonFlags = spec.buttonFlags && spec.buttonFlags.includes("m") ? spec.buttonFlags : `${spec.buttonFlags ?? ""}m`;
54768
+ const buttonRe = compile3(spec.buttonPattern, buttonFlags);
54769
+ const isButtonLine = (line) => {
54770
+ buttonRe.lastIndex = 0;
54771
+ return buttonRe.test(line);
54772
+ };
54773
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
54774
+ if (primary.test(lines[i]) && !isButtonLine(lines[i])) return { index: i, matchedSource: "primary" };
54775
+ }
54776
+ for (const variant of spec.questionVariants ?? []) {
54777
+ const re = compile3(variant.regex, variant.flags ?? "i");
54778
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
54779
+ if (re.test(lines[i]) && !isButtonLine(lines[i])) return { index: i, matchedSource: variant.label ?? "variant" };
54780
+ }
54781
+ }
54556
54782
  for (let i = lines.length - 1; i >= 0; i -= 1) {
54557
54783
  if (primary.test(lines[i])) return { index: i, matchedSource: "primary" };
54558
54784
  }
@@ -55286,6 +55512,7 @@ ${cont}` : cont;
55286
55512
  * paint and made the engine type "1" repeatedly into the prompt.
55287
55513
  */
55288
55514
  modalLostAt = 0;
55515
+ modalLostRecheckTimer = null;
55289
55516
  approvalExitTimeout = null;
55290
55517
  // ── Response tracking ────────────────────────────
55291
55518
  responseEpoch = 0;
@@ -55337,6 +55564,10 @@ ${cont}` : cont;
55337
55564
  setStatus(status, trigger) {
55338
55565
  const prev = this.currentStatus;
55339
55566
  if (prev === status) return;
55567
+ if (prev === "waiting_approval" && this.modalLostRecheckTimer) {
55568
+ clearTimeout(this.modalLostRecheckTimer);
55569
+ this.modalLostRecheckTimer = null;
55570
+ }
55340
55571
  this.currentStatus = status;
55341
55572
  this.statusHistory.push({ status, at: Date.now(), trigger });
55342
55573
  if (this.statusHistory.length > 50) this.statusHistory.shift();
@@ -55497,6 +55728,10 @@ ${cont}` : cont;
55497
55728
  clearTimeout(this.approvalExitTimeout);
55498
55729
  this.approvalExitTimeout = null;
55499
55730
  }
55731
+ if (this.modalLostRecheckTimer) {
55732
+ clearTimeout(this.modalLostRecheckTimer);
55733
+ this.modalLostRecheckTimer = null;
55734
+ }
55500
55735
  if (this.finishRetryTimer) {
55501
55736
  clearTimeout(this.finishRetryTimer);
55502
55737
  this.finishRetryTimer = null;
@@ -55808,7 +56043,7 @@ ${cont}` : cont;
55808
56043
  if (!inCooldown || modal) {
55809
56044
  if (!modal) {
55810
56045
  LOG2.warn("CLI", `[${this.provider.type}] detectStatus=waiting_approval but parseApproval returned null; ignoring`);
55811
- if (this.currentStatus === "waiting_approval" && this.activeModal) {
56046
+ if (this.currentStatus === "waiting_approval") {
55812
56047
  const lostAt = this.modalLostAt || Date.now();
55813
56048
  if (!this.modalLostAt) this.modalLostAt = lostAt;
55814
56049
  if (Date.now() - lostAt >= this.timeouts.approvalCooldown) {
@@ -55816,6 +56051,8 @@ ${cont}` : cont;
55816
56051
  this.modalLostAt = 0;
55817
56052
  this.setStatus("generating", "approval_lost_modal");
55818
56053
  this.callbacks.onStatusChange();
56054
+ } else {
56055
+ this.armModalLostRecheck();
55819
56056
  }
55820
56057
  }
55821
56058
  return;
@@ -56077,6 +56314,25 @@ ${cont}` : cont;
56077
56314
  this.recordTrace("idle_finish_cancelled", { trigger: reason });
56078
56315
  }
56079
56316
  // ─── Helpers ────────────────────────────────────────────────────────────
56317
+ /**
56318
+ * Schedule one more settled evaluation while pinned to `waiting_approval`
56319
+ * with no actionable modal. The settled FSM normally only re-runs on new
56320
+ * PTY output; a provider whose modal cue lingers in a form detectStatus
56321
+ * still matches (e.g. kimi's questionPattern hitting the user echo) but
56322
+ * whose PTY has gone quiet would never get another evaluation, latching
56323
+ * `waiting_approval` forever. This timer guarantees the modal-lost recovery
56324
+ * in `applyWaitingApproval` is reached even against a silent PTY. It is a
56325
+ * no-op once the FSM leaves `waiting_approval` (the re-evaluation itself
56326
+ * takes the recovery branch and clears the state).
56327
+ */
56328
+ armModalLostRecheck() {
56329
+ if (this.modalLostRecheckTimer) return;
56330
+ this.modalLostRecheckTimer = setTimeout(() => {
56331
+ this.modalLostRecheckTimer = null;
56332
+ if (this.currentStatus !== "waiting_approval") return;
56333
+ this.evaluateSettled(this.transport.getSnapshot());
56334
+ }, this.timeouts.approvalCooldown);
56335
+ }
56080
56336
  armApprovalExitTimeout() {
56081
56337
  if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
56082
56338
  this.approvalExitTimeout = setTimeout(() => {
@@ -56669,6 +56925,7 @@ ${cont}` : cont;
56669
56925
  const staleSnapshotLooksActive = activeScreenPattern.test(lastSnapshot);
56670
56926
  const currentScreenLooksIdle = /(?:^|\n|\r)\s*[❯›>]\s*(?:Try\s+["“][^\n\r"”]+["”])?\s*(?:\n|\r|$)/.test(screenText) && !activeScreenPattern.test(screenText);
56671
56927
  if (staleSnapshotLooksActive && currentScreenLooksIdle) return screenText;
56928
+ if (this.runDetectStatus(screenText) === "idle") return screenText;
56672
56929
  if (currentSnapshot.length >= lastSnapshot.length) return screenText;
56673
56930
  return `${screenText}
56674
56931
  ${lastSnapshot}`;
@@ -65824,6 +66081,33 @@ ${effect.notification.body || ""}`.trim();
65824
66081
  getState(key2) {
65825
66082
  return this.records.get(key2)?.state ?? INITIAL_CHAT_SOURCE_STATE;
65826
66083
  }
66084
+ /**
66085
+ * Opaque snapshot of a session's FULL record (state + lock + transitions),
66086
+ * for callers that need to speculatively `observe()` and then roll back if
66087
+ * the resulting decision is undesirable (STICKY-NATIVE hold: a trusted
66088
+ * exact-identity session must not flip to PTY on a transient native gap).
66089
+ * Returns undefined when the session has no record yet. The snapshot is a
66090
+ * shallow copy — transitions array is copied so a later append does not
66091
+ * mutate it.
66092
+ */
66093
+ snapshotRecord(key2) {
66094
+ const rec = this.records.get(key2);
66095
+ if (!rec) return void 0;
66096
+ return { state: rec.state, lockedSince: rec.lockedSince, transitions: [...rec.transitions] };
66097
+ }
66098
+ /** Restore a previously snapshotted record, undoing a speculative observe.
66099
+ * Passing undefined clears the key (it had no record when snapshotted). */
66100
+ restoreRecord(key2, snapshot) {
66101
+ if (!snapshot) {
66102
+ this.records.delete(key2);
66103
+ return;
66104
+ }
66105
+ this.records.set(key2, {
66106
+ state: snapshot.state,
66107
+ lockedSince: snapshot.lockedSince,
66108
+ transitions: [...snapshot.transitions]
66109
+ });
66110
+ }
65827
66111
  /** Recent transitions, newest last. Empty array when nothing has happened. */
65828
66112
  getTransitions(key2) {
65829
66113
  return this.records.get(key2)?.transitions ?? [];
@@ -66245,7 +66529,52 @@ ${effect.notification.body || ""}`.trim();
66245
66529
  const supportsNative = supportsCliNativeTranscript(args.providerType, args.provider);
66246
66530
  const observation = buildObservationForCli(args, supportsNative);
66247
66531
  const sessionKey = chatSourceSessionKey(args.providerType, args.sessionId);
66532
+ const priorSnapshot = CHAT_SOURCE_REGISTRY.snapshotRecord(sessionKey);
66533
+ const priorState = priorSnapshot?.state ?? CHAT_SOURCE_REGISTRY.getState(sessionKey);
66534
+ const eligibleForStickyHold = args.trustedExactNativeIdentity === true && (priorState.name === "NativeLocked" || priorState.name === "Recovering");
66248
66535
  let decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
66536
+ if (eligibleForStickyHold && decision.selected === "pty-parser") {
66537
+ CHAT_SOURCE_REGISTRY.restoreRecord(sessionKey, priorSnapshot);
66538
+ const heldNativeMessages = observation.kind === "native_present" ? extractNativeMessagesFromResult(args.providerType, args.nativeHistoryResult) : [];
66539
+ const messageSource2 = buildCliMessageSourceProvenance({
66540
+ selected: "native-history",
66541
+ provider: args.providerType,
66542
+ nativeHandle: typeof args.nativeHistoryResult?.providerSessionId === "string" ? args.nativeHistoryResult.providerSessionId : void 0,
66543
+ sessionWorkspace: args.sessionWorkspace,
66544
+ intendedWorkspace: args.intendedWorkspace,
66545
+ transcriptWorkspace: void 0,
66546
+ fallbackReason: "native_history_transient_gap_held",
66547
+ nativeSource: "provider-native",
66548
+ sourcePath: typeof args.nativeHistoryResult?.sourcePath === "string" ? args.nativeHistoryResult.sourcePath : void 0,
66549
+ sourceMtimeMs: typeof args.nativeHistoryResult?.sourceMtimeMs === "number" ? args.nativeHistoryResult.sourceMtimeMs : void 0,
66550
+ nativeHistoryCoverage: void 0,
66551
+ partialReason: void 0,
66552
+ unavailableReason: observation.kind === "native_unavailable" ? observation.reason : void 0,
66553
+ nativeMessages: heldNativeMessages,
66554
+ ptyMessages: args.ptyMessages,
66555
+ returnedMessages: heldNativeMessages,
66556
+ safeMapping: args.safeMapping,
66557
+ freshEnough: true,
66558
+ ptyStatusApprovalOnly: true
66559
+ });
66560
+ return {
66561
+ decision: {
66562
+ selected: "native-history",
66563
+ nextState: priorState,
66564
+ transition: {
66565
+ fromState: priorState.name,
66566
+ toState: priorState.name,
66567
+ event: "NoOp",
66568
+ cause: decision.transition.cause,
66569
+ at: Date.now()
66570
+ },
66571
+ lockState: { locked: priorState.name === "NativeLocked" }
66572
+ },
66573
+ messageSource: messageSource2,
66574
+ nativeMessages: heldNativeMessages,
66575
+ nativeSelected: true
66576
+ };
66577
+ }
66249
66578
  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) {
66250
66579
  CHAT_SOURCE_REGISTRY.clear(sessionKey);
66251
66580
  decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
@@ -66565,7 +66894,7 @@ ${effect.notification.body || ""}`.trim();
66565
66894
  function readCliProviderNativeHistory(agentStr, args) {
66566
66895
  const canBindFromLiveSession = !args.historySessionId && typeof args.sessionStartedAtMs === "number" && args.sessionStartedAtMs > 0 && typeof args.workspace === "string" && args.workspace.trim().length > 0;
66567
66896
  const pinnedProviderSessionId = typeof args.pinnedProviderSessionId === "string" ? args.pinnedProviderSessionId.trim() : "";
66568
- const effectiveHistorySessionId = args.historySessionId || (!canBindFromLiveSession ? pinnedProviderSessionId : "");
66897
+ const effectiveHistorySessionId = args.historySessionId || pinnedProviderSessionId || "";
66569
66898
  const workspaceLatestFallback = !effectiveHistorySessionId && !canBindFromLiveSession && !pinnedProviderSessionId && args.allowWorkspaceLatestFallback === true && typeof args.workspace === "string" && args.workspace.trim().length > 0;
66570
66899
  if (!effectiveHistorySessionId && !canBindFromLiveSession && !workspaceLatestFallback) {
66571
66900
  return {