@adhdev/daemon-core 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.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 ? "e7001a2fe5b5af35d3710c964727f7e4d735e852" : void 0) ?? "unknown";
418
- const commitShort = readInjected(true ? "e7001a2f" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
419
- const version = readInjected(true ? "0.9.82-rc.536" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
420
- const builtAt = readInjected(true ? "2026-07-15T11:34:59.318Z" : 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
  }
@@ -24552,6 +24695,70 @@ function modalMatches(spec, input) {
24552
24695
  if (buttonBlockApprovalCue(spec, text)) return true;
24553
24696
  return false;
24554
24697
  }
24698
+ function lastModalCueLine(spec, screenText) {
24699
+ if (!screenText) return -1;
24700
+ const lines = screenText.split("\n");
24701
+ const question = compile2(spec.questionPattern, spec.questionFlags ?? "i");
24702
+ const variants = (spec.questionVariants ?? []).map((v) => compile2(v.regex, v.flags ?? "i"));
24703
+ const buttonFlags = spec.buttonFlags && spec.buttonFlags.includes("m") ? spec.buttonFlags : `${spec.buttonFlags ?? ""}m`;
24704
+ const buttonRe = compile2(spec.buttonPattern, buttonFlags);
24705
+ let last = -1;
24706
+ for (let i = 0; i < lines.length; i++) {
24707
+ const line = lines[i];
24708
+ question.lastIndex = 0;
24709
+ if (question.test(line)) {
24710
+ last = i;
24711
+ continue;
24712
+ }
24713
+ if (variants.some((re) => {
24714
+ re.lastIndex = 0;
24715
+ return re.test(line);
24716
+ })) {
24717
+ last = i;
24718
+ continue;
24719
+ }
24720
+ buttonRe.lastIndex = 0;
24721
+ if (buttonRe.test(line)) {
24722
+ last = i;
24723
+ continue;
24724
+ }
24725
+ }
24726
+ return last;
24727
+ }
24728
+ function modalSupersededBySettledPrompt(modalSpec, settledSpec, settled, input) {
24729
+ if (!settled || !settledSpec) return false;
24730
+ if (settledSpec.scope === "whole-screen") return false;
24731
+ const screenText = input.screenText ?? "";
24732
+ if (!screenText) return false;
24733
+ const modalLine = lastModalCueLine(modalSpec, screenText);
24734
+ if (modalLine < 0) return false;
24735
+ const lines = screenText.split("\n");
24736
+ const below = lines.slice(modalLine + 1);
24737
+ if (below.length === 0) return false;
24738
+ const belowText = below.join("\n");
24739
+ if (!settled.prompt.test(belowText)) return false;
24740
+ if (settled.footers.length > 0 && !settled.footers.every((f) => f.test(belowText))) return false;
24741
+ const question = compile2(modalSpec.questionPattern, modalSpec.questionFlags ?? "i");
24742
+ const variants = (modalSpec.questionVariants ?? []).map((v) => compile2(v.regex, v.flags ?? "i"));
24743
+ const buttonFlags = modalSpec.buttonFlags && modalSpec.buttonFlags.includes("m") ? modalSpec.buttonFlags : `${modalSpec.buttonFlags ?? ""}m`;
24744
+ const buttonRe = compile2(modalSpec.buttonPattern, buttonFlags);
24745
+ const isModalCueLine = (line) => {
24746
+ question.lastIndex = 0;
24747
+ if (question.test(line)) return true;
24748
+ if (variants.some((re) => {
24749
+ re.lastIndex = 0;
24750
+ return re.test(line);
24751
+ })) return true;
24752
+ buttonRe.lastIndex = 0;
24753
+ return buttonRe.test(line);
24754
+ };
24755
+ const settledPromptLineRe = compile2(settledSpec.regex, (settledSpec.flags ?? "m").includes("m") ? settledSpec.flags ?? "m" : `${settledSpec.flags ?? ""}m`);
24756
+ const isSettledLine = (line) => {
24757
+ settledPromptLineRe.lastIndex = 0;
24758
+ return settledPromptLineRe.test(line);
24759
+ };
24760
+ return below.some((line) => line.trim() !== "" && !isModalCueLine(line) && !isSettledLine(line));
24761
+ }
24555
24762
  function evaluateGroup(group, spec, input, compiled) {
24556
24763
  switch (group) {
24557
24764
  case "spinner": {
@@ -24561,7 +24768,9 @@ function evaluateGroup(group, spec, input, compiled) {
24561
24768
  }
24562
24769
  case "modal": {
24563
24770
  if (!spec.modal) return null;
24564
- return modalMatches(spec.modal, input) ? "waiting_approval" : null;
24771
+ if (!modalMatches(spec.modal, input)) return null;
24772
+ if (modalSupersededBySettledPrompt(spec.modal, spec.settledPrompt, compiled.settled, input)) return null;
24773
+ return "waiting_approval";
24565
24774
  }
24566
24775
  case "settled-prompt": {
24567
24776
  if (!spec.settledPrompt || !compiled.settled) return null;
@@ -24619,6 +24828,21 @@ function compile3(re, flags) {
24619
24828
  }
24620
24829
  function findQuestionLineIndex(spec, lines) {
24621
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
+ }
24622
24846
  for (let i = lines.length - 1; i >= 0; i -= 1) {
24623
24847
  if (primary.test(lines[i])) return { index: i, matchedSource: "primary" };
24624
24848
  }
@@ -25348,6 +25572,7 @@ var init_cli_state_engine = __esm({
25348
25572
  * paint and made the engine type "1" repeatedly into the prompt.
25349
25573
  */
25350
25574
  modalLostAt = 0;
25575
+ modalLostRecheckTimer = null;
25351
25576
  approvalExitTimeout = null;
25352
25577
  // ── Response tracking ────────────────────────────
25353
25578
  responseEpoch = 0;
@@ -25399,6 +25624,10 @@ var init_cli_state_engine = __esm({
25399
25624
  setStatus(status, trigger) {
25400
25625
  const prev = this.currentStatus;
25401
25626
  if (prev === status) return;
25627
+ if (prev === "waiting_approval" && this.modalLostRecheckTimer) {
25628
+ clearTimeout(this.modalLostRecheckTimer);
25629
+ this.modalLostRecheckTimer = null;
25630
+ }
25402
25631
  this.currentStatus = status;
25403
25632
  this.statusHistory.push({ status, at: Date.now(), trigger });
25404
25633
  if (this.statusHistory.length > 50) this.statusHistory.shift();
@@ -25559,6 +25788,10 @@ var init_cli_state_engine = __esm({
25559
25788
  clearTimeout(this.approvalExitTimeout);
25560
25789
  this.approvalExitTimeout = null;
25561
25790
  }
25791
+ if (this.modalLostRecheckTimer) {
25792
+ clearTimeout(this.modalLostRecheckTimer);
25793
+ this.modalLostRecheckTimer = null;
25794
+ }
25562
25795
  if (this.finishRetryTimer) {
25563
25796
  clearTimeout(this.finishRetryTimer);
25564
25797
  this.finishRetryTimer = null;
@@ -25870,7 +26103,7 @@ var init_cli_state_engine = __esm({
25870
26103
  if (!inCooldown || modal) {
25871
26104
  if (!modal) {
25872
26105
  LOG.warn("CLI", `[${this.provider.type}] detectStatus=waiting_approval but parseApproval returned null; ignoring`);
25873
- if (this.currentStatus === "waiting_approval" && this.activeModal) {
26106
+ if (this.currentStatus === "waiting_approval") {
25874
26107
  const lostAt = this.modalLostAt || Date.now();
25875
26108
  if (!this.modalLostAt) this.modalLostAt = lostAt;
25876
26109
  if (Date.now() - lostAt >= this.timeouts.approvalCooldown) {
@@ -25878,6 +26111,8 @@ var init_cli_state_engine = __esm({
25878
26111
  this.modalLostAt = 0;
25879
26112
  this.setStatus("generating", "approval_lost_modal");
25880
26113
  this.callbacks.onStatusChange();
26114
+ } else {
26115
+ this.armModalLostRecheck();
25881
26116
  }
25882
26117
  }
25883
26118
  return;
@@ -26139,6 +26374,25 @@ var init_cli_state_engine = __esm({
26139
26374
  this.recordTrace("idle_finish_cancelled", { trigger: reason });
26140
26375
  }
26141
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
+ }
26142
26396
  armApprovalExitTimeout() {
26143
26397
  if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
26144
26398
  this.approvalExitTimeout = setTimeout(() => {
@@ -26731,6 +26985,7 @@ var init_provider_cli_adapter = __esm({
26731
26985
  const staleSnapshotLooksActive = activeScreenPattern.test(lastSnapshot);
26732
26986
  const currentScreenLooksIdle = /(?:^|\n|\r)\s*[❯›>]\s*(?:Try\s+["“][^\n\r"”]+["”])?\s*(?:\n|\r|$)/.test(screenText) && !activeScreenPattern.test(screenText);
26733
26987
  if (staleSnapshotLooksActive && currentScreenLooksIdle) return screenText;
26988
+ if (this.runDetectStatus(screenText) === "idle") return screenText;
26734
26989
  if (currentSnapshot.length >= lastSnapshot.length) return screenText;
26735
26990
  return `${screenText}
26736
26991
  ${lastSnapshot}`;
@@ -35551,6 +35806,33 @@ var ChatSourceRegistry = class {
35551
35806
  getState(key2) {
35552
35807
  return this.records.get(key2)?.state ?? INITIAL_CHAT_SOURCE_STATE;
35553
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
+ }
35554
35836
  /** Recent transitions, newest last. Empty array when nothing has happened. */
35555
35837
  getTransitions(key2) {
35556
35838
  return this.records.get(key2)?.transitions ?? [];
@@ -35974,7 +36256,52 @@ function decideCliReadChatSource(args) {
35974
36256
  const supportsNative = supportsCliNativeTranscript(args.providerType, args.provider);
35975
36257
  const observation = buildObservationForCli(args, supportsNative);
35976
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");
35977
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
+ }
35978
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) {
35979
36306
  CHAT_SOURCE_REGISTRY.clear(sessionKey);
35980
36307
  decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
@@ -36294,7 +36621,7 @@ function sessionSpawnEnvFromAdapter(h, targetSessionId) {
36294
36621
  function readCliProviderNativeHistory(agentStr, args) {
36295
36622
  const canBindFromLiveSession = !args.historySessionId && typeof args.sessionStartedAtMs === "number" && args.sessionStartedAtMs > 0 && typeof args.workspace === "string" && args.workspace.trim().length > 0;
36296
36623
  const pinnedProviderSessionId = typeof args.pinnedProviderSessionId === "string" ? args.pinnedProviderSessionId.trim() : "";
36297
- const effectiveHistorySessionId = args.historySessionId || (!canBindFromLiveSession ? pinnedProviderSessionId : "");
36624
+ const effectiveHistorySessionId = args.historySessionId || pinnedProviderSessionId || "";
36298
36625
  const workspaceLatestFallback = !effectiveHistorySessionId && !canBindFromLiveSession && !pinnedProviderSessionId && args.allowWorkspaceLatestFallback === true && typeof args.workspace === "string" && args.workspace.trim().length > 0;
36299
36626
  if (!effectiveHistorySessionId && !canBindFromLiveSession && !workspaceLatestFallback) {
36300
36627
  return {