@adhdev/daemon-core 0.9.82-rc.566 → 0.9.82-rc.568

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
@@ -423,10 +423,10 @@ function readInjected(value) {
423
423
  }
424
424
  function getDaemonBuildInfo() {
425
425
  if (cached) return cached;
426
- const commit = readInjected(true ? "d0d245e6363e5bdeee369f7e28132ff8ca989184" : void 0) ?? "unknown";
427
- const commitShort = readInjected(true ? "d0d245e6" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
428
- const version = readInjected(true ? "0.9.82-rc.566" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
429
- const builtAt = readInjected(true ? "2026-07-18T07:10:36.457Z" : void 0);
426
+ const commit = readInjected(true ? "39e0687342226d334e36c982bd8988802f43dd3e" : void 0) ?? "unknown";
427
+ const commitShort = readInjected(true ? "39e06873" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
428
+ const version = readInjected(true ? "0.9.82-rc.568" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
429
+ const builtAt = readInjected(true ? "2026-07-18T14:18:06.483Z" : void 0);
430
430
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
431
431
  return cached;
432
432
  }
@@ -14976,12 +14976,16 @@ function buildMeshActiveWork(opts) {
14976
14976
  for (const task of opts.queue || []) {
14977
14977
  if (task.status !== "pending" && task.status !== "assigned") continue;
14978
14978
  const { title, summary: summary2 } = summarizeMessage(task.message || "");
14979
+ const queueNodeId = task.assignedNodeId || task.targetNodeId;
14980
+ const queueSessionId = task.assignedSessionId || task.targetSessionId;
14981
+ const queueLive = task.status === "assigned" ? sessionStatusFromNodes(opts.nodes, queueNodeId ?? void 0, queueSessionId ?? void 0) : {};
14982
+ const queueStatus = queueLive.status === "awaiting_approval" || queueLive.status === "generating" ? queueLive.status : task.status;
14979
14983
  records.push({
14980
14984
  taskId: task.id,
14981
14985
  source: "queue",
14982
- status: task.status,
14983
- nodeId: task.assignedNodeId || task.targetNodeId,
14984
- sessionId: task.assignedSessionId || task.targetSessionId,
14986
+ status: queueStatus,
14987
+ nodeId: queueNodeId,
14988
+ sessionId: queueSessionId,
14985
14989
  taskTitle: title,
14986
14990
  taskSummary: summary2,
14987
14991
  message: task.message,
@@ -20564,12 +20568,15 @@ function evaluateMeshEventSuppression(args, ctx) {
20564
20568
  }
20565
20569
  return null;
20566
20570
  }
20567
- function sourceWorkerAutoApproves(components, sessionId) {
20571
+ function shouldSuppressAutoApprovingWorkerApproval(components, sessionId) {
20568
20572
  if (!sessionId) return false;
20569
20573
  try {
20570
- const state = components.instanceManager?.getInstance?.(sessionId)?.getState?.();
20574
+ const instance = components.instanceManager?.getInstance?.(sessionId);
20575
+ const state = instance?.getState?.();
20571
20576
  const settings = state?.settings || {};
20572
- return settings.autoApprove === true;
20577
+ if (settings.autoApprove !== true) return false;
20578
+ const resolvedLocally = instance?.approvalRecentlyResolvedLocally;
20579
+ return typeof resolvedLocally === "function" ? resolvedLocally.call(instance) === true : false;
20573
20580
  } catch {
20574
20581
  return false;
20575
20582
  }
@@ -20714,8 +20721,8 @@ function injectMeshSystemMessage(components, args) {
20714
20721
  }
20715
20722
  }
20716
20723
  const eventTimestamp = readEventTimestamp(args.metadataEvent.timestamp);
20717
- if (args.event === "agent:waiting_approval" && sourceWorkerAutoApproves(components, eventSessionId)) {
20718
- LOG.info("MeshEvents", `Suppressed agent:waiting_approval for auto-approving worker session ${eventSessionId || "(unknown)"} (mesh ${args.meshId}) \u2014 modal is resolved locally, coordinator not notified`);
20724
+ if (args.event === "agent:waiting_approval" && shouldSuppressAutoApprovingWorkerApproval(components, eventSessionId)) {
20725
+ LOG.info("MeshEvents", `Suppressed agent:waiting_approval for auto-approving worker session ${eventSessionId || "(unknown)"} (mesh ${args.meshId}) \u2014 modal resolved locally within cooldown, coordinator not notified`);
20719
20726
  traceMeshEventDrop("waiting_approval_auto_approving_worker", traceCtx);
20720
20727
  return { success: true, forwarded: 0, suppressed: true, autoApprovingWorkerApproval: true };
20721
20728
  }
@@ -22414,6 +22421,14 @@ function drainAndDeliverApprovalNudges(meshId, drainDaemonIds, localDaemonId, me
22414
22421
  }
22415
22422
  return delivered;
22416
22423
  }
22424
+ function assignedRowLiveStatusIsAwaitingApproval(mesh, nodeId, sessionId) {
22425
+ if (!nodeId || !sessionId) return false;
22426
+ try {
22427
+ return sessionStatusFromNodes(mesh.nodes, nodeId, sessionId).status === "awaiting_approval";
22428
+ } catch {
22429
+ return false;
22430
+ }
22431
+ }
22417
22432
  async function recoverStrandedAssignedDispatches(components, mesh, store) {
22418
22433
  const meshId = mesh.id;
22419
22434
  const assigned = getQueue(meshId, { status: ["assigned"] });
@@ -22445,6 +22460,15 @@ async function recoverStrandedAssignedDispatches(components, mesh, store) {
22445
22460
  } else {
22446
22461
  if (verdict === "IDLE_CONFIRMED") {
22447
22462
  deliveredUnconsumedUnknownStreak.delete(shortStreakKey);
22463
+ } else if (assignedRowLiveStatusIsAwaitingApproval(mesh, row.assignedNodeId, row.assignedSessionId)) {
22464
+ traceMeshEventDrop("short_redrive_deferred_awaiting_approval", {
22465
+ taskId: row.id,
22466
+ sessionId: row.assignedSessionId,
22467
+ nodeId: row.assignedNodeId,
22468
+ meshId,
22469
+ event: "agent:waiting_approval"
22470
+ }, "live_awaiting_approval");
22471
+ continue;
22448
22472
  } else {
22449
22473
  const streak = (deliveredUnconsumedUnknownStreak.get(shortStreakKey) ?? 0) + 1;
22450
22474
  deliveredUnconsumedUnknownStreak.set(shortStreakKey, streak);
@@ -22507,6 +22531,15 @@ async function recoverStrandedAssignedDispatches(components, mesh, store) {
22507
22531
  if (verdict === "IDLE_CONFIRMED") {
22508
22532
  deliveredNoTurnUnknownStreak.delete(streakKey);
22509
22533
  reclaimReason = "delivered_no_turn_deadline";
22534
+ } else if (assignedRowLiveStatusIsAwaitingApproval(mesh, row.assignedNodeId, row.assignedSessionId)) {
22535
+ traceMeshEventDrop("reclaim_deferred_awaiting_approval", {
22536
+ taskId: row.id,
22537
+ sessionId: row.assignedSessionId,
22538
+ nodeId: row.assignedNodeId,
22539
+ meshId,
22540
+ event: "agent:waiting_approval"
22541
+ }, "live_awaiting_approval");
22542
+ continue;
22510
22543
  } else {
22511
22544
  const streak = (deliveredNoTurnUnknownStreak.get(streakKey) ?? 0) + 1;
22512
22545
  deliveredNoTurnUnknownStreak.set(streakKey, streak);
@@ -23074,6 +23107,7 @@ var init_mesh_reconcile_loop = __esm({
23074
23107
  init_mesh_reconcile_config();
23075
23108
  init_mesh_remote_event_pull();
23076
23109
  init_mesh_completion_synthesis();
23110
+ init_mesh_active_work();
23077
23111
  init_mesh_reconcile_v2_backstop();
23078
23112
  init_mesh_reconcile_acked_hold();
23079
23113
  coordinatorModalParkState = /* @__PURE__ */ new Map();
@@ -39008,7 +39042,13 @@ async function handleResolveAction(h, args) {
39008
39042
  LOG.info("Command", `[resolveAction] CLI PTY \u2192 stale_prompt (already resolved within cooldown)`);
39009
39043
  return { success: true, stalePrompt: true, buttonIndex, button: buttons[buttonIndex] ?? button };
39010
39044
  }
39011
- if (typeof adapter.resolveModal === "function") {
39045
+ if (typeof adapter.resolveModalMatched === "function") {
39046
+ const matched = adapter.resolveModalMatched(buttonIndex);
39047
+ if (!matched) {
39048
+ LOG.warn("Command", `[resolveAction] CLI PTY \u2192 no button matched for buttonIndex=${buttonIndex} "${buttons[buttonIndex] ?? "?"}" (modal not resolved)`);
39049
+ return { success: false, error: "Approval button index did not map to a visible modal button", buttonIndex, button: buttons[buttonIndex] ?? button };
39050
+ }
39051
+ } else if (typeof adapter.resolveModal === "function") {
39012
39052
  adapter.resolveModal(buttonIndex);
39013
39053
  } else {
39014
39054
  const keys = "\x1B[B".repeat(Math.max(0, buttonIndex)) + "\r";
@@ -43601,7 +43641,7 @@ var FsmDriver = class {
43601
43641
  this.handleClickControl(cmd.control_id, cmd.payload);
43602
43642
  return;
43603
43643
  case "click_modal_button":
43604
- this.handleClickModalButton(cmd.index);
43644
+ this.clickModalButton(cmd.index);
43605
43645
  return;
43606
43646
  case "attach_image":
43607
43647
  this.handleAttachImage(cmd.blob, cmd.mime);
@@ -44344,11 +44384,24 @@ var FsmDriver = class {
44344
44384
  }
44345
44385
  }
44346
44386
  }
44387
+ /**
44388
+ * BUTTON-INDEX-MISMAP (Fix C.3): public modal-click entry that returns whether a button
44389
+ * matching the requested FSM display index was actually found and its confirm keys were
44390
+ * dispatched. The old private handleClickModalButton silently `return`ed on a miss (no
44391
+ * modal captured, or no button whose `.index` equals the requested display index), so a
44392
+ * mis-mapped index looked identical to a successful press. Callers that need to know
44393
+ * whether the click landed (mesh_approve → resolveModal) can now observe the miss instead
44394
+ * of reporting success into the void. The generic `dispatch('click_modal_button')` path
44395
+ * keeps ignoring the return (fire-and-forget UI clicks).
44396
+ */
44397
+ clickModalButton(index) {
44398
+ return this.handleClickModalButton(index);
44399
+ }
44347
44400
  handleClickModalButton(index) {
44348
44401
  const m = this.currentEval?.modal;
44349
- if (!m) return;
44402
+ if (!m) return false;
44350
44403
  const btn = m.buttons.find((b) => b.index === index);
44351
- if (!btn) return;
44404
+ if (!btn) return false;
44352
44405
  const rule = stateById(this.spec, this.currentStateId)?.extract?.buttons;
44353
44406
  if (rule?.select_mode === "arrow_keys") {
44354
44407
  const from = m.buttons.find((b) => b.current)?.index ?? 1;
@@ -44360,9 +44413,10 @@ var FsmDriver = class {
44360
44413
  const confirm = (rule.key_for_index || "\r").replace(/\{index\}/g, "") || "\r";
44361
44414
  if (nav) this.adapter.send_keys(nav);
44362
44415
  this.submitModalConfirm(confirm);
44363
- return;
44416
+ return true;
44364
44417
  }
44365
44418
  this.submitModalConfirm(btn.key);
44419
+ return true;
44366
44420
  }
44367
44421
  /**
44368
44422
  * Submit a modal-confirm key sequence (the choice key + its trailing CR).
@@ -45794,7 +45848,20 @@ var SpecCliAdapter = class _SpecCliAdapter {
45794
45848
  // modal this frame still stays waiting_approval (no activeModal yet).
45795
45849
  // `kind` carries the semantic modal class through to the auto-approve
45796
45850
  // gate so a /model picker (kind='picker') is never auto-answered.
45797
- activeModal: modal ? { message: modal.title ?? state.label, buttons: modal.buttons.map((b) => b.label), kind: modal.kind ?? null } : null,
45851
+ // BUTTON-INDEX-MISMAP (Fix C.1): keep `buttons` as the label list every
45852
+ // existing consumer (pickApprovalButton, mesh_approve, auto-approve) reads,
45853
+ // but ALSO surface `buttonMeta` carrying each button's real FSM display index
45854
+ // alongside its label. A partial/non-contiguous modal (display indices [1,3,4]
45855
+ // at array positions [0,1,2]) then no longer loses the index → label mapping
45856
+ // once it leaves the adapter: a consumer that has an array position can recover
45857
+ // the true FSM index without re-parsing. resolveModal() below relies on the same
45858
+ // ordered list to translate an array position to the correct FSM index.
45859
+ activeModal: modal ? {
45860
+ message: modal.title ?? state.label,
45861
+ buttons: modal.buttons.map((b) => b.label),
45862
+ buttonMeta: modal.buttons.map((b) => ({ index: b.index, label: b.label })),
45863
+ kind: modal.kind ?? null
45864
+ } : null,
45798
45865
  activeInteractivePrompt: this.activeInteractivePrompt,
45799
45866
  ...sessionFields
45800
45867
  };
@@ -45966,7 +46033,12 @@ var SpecCliAdapter = class _SpecCliAdapter {
45966
46033
  this.driver.dispatch({ kind: "resize", cols, rows });
45967
46034
  }
45968
46035
  resolveModal(buttonIndex) {
45969
- this.driver.dispatch({ kind: "click_modal_button", index: buttonIndex + 1 });
46036
+ this.resolveModalMatched(buttonIndex);
46037
+ }
46038
+ resolveModalMatched(buttonIndex) {
46039
+ const buttons = this.latestModal?.buttons ?? [];
46040
+ const target = buttonIndex >= 0 && buttonIndex < buttons.length ? buttons[buttonIndex].index : buttonIndex + 1;
46041
+ return this.driver.clickModalButton(target);
45970
46042
  }
45971
46043
  async resolveAction(data) {
45972
46044
  const args = data && typeof data === "object" ? data : {};
@@ -47126,6 +47198,15 @@ var CliProviderInstance = class _CliProviderInstance {
47126
47198
  * keystroke until the modal *content* has settled.
47127
47199
  */
47128
47200
  static AUTO_APPROVE_SETTLE_MS = 600;
47201
+ /**
47202
+ * APPROVAL-INBOX-BLINDSPOT (Fix A): how long after a LOCAL auto-approve fire the mesh
47203
+ * event forwarder still treats the modal as "being resolved locally" and suppresses the
47204
+ * coordinator notification. Chosen to comfortably cover the resolveModal → PTY absorb →
47205
+ * status-leaves-approval round trip (incl. the win32 CR-resend loop) while staying short
47206
+ * enough that a modal which auto-approve fired at but did NOT resolve re-surfaces to the
47207
+ * coordinator on the next event. Aligned with the adapter's own approval cooldown scale.
47208
+ */
47209
+ static APPROVAL_LOCAL_RESOLUTION_COOLDOWN_MS = 8e3;
47129
47210
  /**
47130
47211
  * Busy-side hysteresis for the settle gate. A momentary `generating` flip
47131
47212
  * while the SAME approval modal's button block is still on screen (its
@@ -47398,6 +47479,16 @@ var CliProviderInstance = class _CliProviderInstance {
47398
47479
  // signature) while a genuinely closed modal — buttons empty continuously past
47399
47480
  // the continuity window — is still recognised and resets the gate.
47400
47481
  autoApproveLastModalSeenAt = 0;
47482
+ // APPROVAL-INBOX-BLINDSPOT (Fix A): wall-clock of the last time this session actually
47483
+ // FIRED a local auto-approve resolveModal (the settle gate passed → resolveModal
47484
+ // dispatched). The mesh event forwarder keys its agent:waiting_approval suppression on
47485
+ // this + a cooldown so it only drops the coordinator notification when we can positively
47486
+ // confirm the modal was (or is being) resolved LOCALLY. If auto-approve is merely
47487
+ // *configured* on but has NOT recently fired for this modal, the raw waiting_approval is
47488
+ // forwarded so a task_approval_needed ledger row is created and the coordinator/inbox is
47489
+ // told — closing the blind spot where a never-resolving worker approval was silently
47490
+ // dropped just because settings.autoApprove===true.
47491
+ lastAutoApproveFiredAt = 0;
47401
47492
  // AUTOAPPROVE-FLAP-INBOX-MISSING sticky-approval overlay (see APPROVAL_STICKY_FLAP_MS).
47402
47493
  // The wall-clock of the last frame where the RAW adapter reported waiting_approval with
47403
47494
  // a CONCRETE modal (buttons present), the cached modal to re-present across a busy blip,
@@ -47883,6 +47974,33 @@ var CliProviderInstance = class _CliProviderInstance {
47883
47974
  }
47884
47975
  return null;
47885
47976
  }
47977
+ /**
47978
+ * APPROVAL-INBOX-BLINDSPOT (Fix A): true when this session's approval modal was — or is
47979
+ * being — resolved LOCALLY within the recent cooldown. Two independent positive signals:
47980
+ * (1) auto-approve fired its resolveModal within APPROVAL_LOCAL_RESOLUTION_COOLDOWN_MS
47981
+ * (lastAutoApproveFiredAt), or
47982
+ * (2) the underlying adapter reports isApprovalRecentlyResolved() — its own resolve
47983
+ * cooldown, which also covers a dashboard / mesh_approve resolution.
47984
+ * The mesh event forwarder uses this to decide whether an agent:waiting_approval from an
47985
+ * auto-approving worker can be safely SUPPRESSED (a local resolution is in flight) or must
47986
+ * be FORWARDED (auto-approve is configured but has NOT actually resolved this modal, so the
47987
+ * coordinator/inbox must be told). Keying suppression on real resolution — not just the
47988
+ * autoApprove *intent* — is the blind-spot fix: a never-resolving worker approval is no
47989
+ * longer silently dropped.
47990
+ */
47991
+ approvalRecentlyResolvedLocally(now = Date.now()) {
47992
+ if (this.lastAutoApproveFiredAt && now - this.lastAutoApproveFiredAt < _CliProviderInstance.APPROVAL_LOCAL_RESOLUTION_COOLDOWN_MS) {
47993
+ return true;
47994
+ }
47995
+ try {
47996
+ const adapter = this.adapter;
47997
+ if (typeof adapter.isApprovalRecentlyResolved === "function") {
47998
+ return adapter.isApprovalRecentlyResolved() === true;
47999
+ }
48000
+ } catch {
48001
+ }
48002
+ return false;
48003
+ }
47886
48004
  /**
47887
48005
  * NOTIF-HELD-DRAIN: true when this `waiting_approval` is a routine, transient tool-consent
47888
48006
  * of an autonomously-progressing mesh session rather than a genuine human-await modal —
@@ -48240,18 +48358,30 @@ var CliProviderInstance = class _CliProviderInstance {
48240
48358
  * the dashboard tail-repair cache — a display value, not a completion decision.
48241
48359
  */
48242
48360
  lastVisibleAssistantSummary(messages) {
48243
- if (!Array.isArray(messages)) return "";
48361
+ return this.lastVisibleAssistantSummaryDetail(messages).content;
48362
+ }
48363
+ // Like lastVisibleAssistantSummary but also returns the source bubble's own
48364
+ // timestamp (ms), so a cached summary can later be turn-scoped: the display
48365
+ // cache is populated from an UNSCOPED tail read (it must show the answer as
48366
+ // soon as native-history has it), so it can hold a bubble that predates the
48367
+ // current turn. Recording the bubble's timestamp lets the weak-completion
48368
+ // fallback reject a turn-stale cached summary instead of re-leaking the exact
48369
+ // stale bubble the turn-boundary gate already rejected (FALSE-IDLE Defect 1c).
48370
+ lastVisibleAssistantSummaryDetail(messages) {
48371
+ if (!Array.isArray(messages)) return { content: "" };
48244
48372
  for (let i = messages.length - 1; i >= 0; i -= 1) {
48245
48373
  const m = messages[i];
48246
48374
  const role = typeof m?.role === "string" ? m.role : "";
48247
48375
  const kind = typeof m?.kind === "string" ? m.kind : "";
48248
48376
  if (role === "system") continue;
48249
48377
  if (kind === "tool" || kind === "activity") continue;
48250
- if (role === "user" || role === "human") return "";
48251
- if (role === "assistant") return flattenContent(m.content).trim();
48252
- return "";
48378
+ if (role === "user" || role === "human") return { content: "" };
48379
+ if (role === "assistant") {
48380
+ return { content: flattenContent(m.content).trim(), timestampMs: readChatMessageTimestampMs(m) };
48381
+ }
48382
+ return { content: "" };
48253
48383
  }
48254
- return "";
48384
+ return { content: "" };
48255
48385
  }
48256
48386
  /**
48257
48387
  * NOTIF Defect-B: the final assistant summary this instance ALREADY parsed and
@@ -48272,6 +48402,28 @@ var CliProviderInstance = class _CliProviderInstance {
48272
48402
  const content = typeof cached3?.content === "string" ? cached3.content.trim() : "";
48273
48403
  return content;
48274
48404
  }
48405
+ // FALSE-IDLE Defect 1c: turn-scoped view of the cached completion summary.
48406
+ // The cache is populated from an UNSCOPED tail read (lastVisibleAssistant‑
48407
+ // SummaryDetail) so the dashboard can show the answer the instant native-history
48408
+ // has it — which means it can hold a bubble that PREDATES the producing turn.
48409
+ // The weak-completion (missing_final_assistant) emit path falls back to the cache
48410
+ // for finalSummary; without turn-scoping it would re-surface the exact stale
48411
+ // mid-turn bubble the turn-boundary gate already rejected as evidence, freezing
48412
+ // that stale text as the completion's finalSummary. Consult the cache only when
48413
+ // its source bubble is proven in-turn (timestamp at/after turnStartedAt). When no
48414
+ // boundary is known (turnStartedAt falsy) or the cache carries no source timestamp
48415
+ // (legacy writes), behaviour is identical to the unscoped read.
48416
+ cachedInTurnCompletionSummaryContent(turnStartedAt) {
48417
+ const cached3 = this.lastCompletionSummary;
48418
+ const content = typeof cached3?.content === "string" ? cached3.content.trim() : "";
48419
+ if (!content) return "";
48420
+ const hasBoundary = typeof turnStartedAt === "number" && Number.isFinite(turnStartedAt) && turnStartedAt > 0;
48421
+ const ts2 = cached3?.sourceTimestampMs;
48422
+ if (hasBoundary && typeof ts2 === "number" && Number.isFinite(ts2) && ts2 < turnStartedAt) {
48423
+ return "";
48424
+ }
48425
+ return content;
48426
+ }
48275
48427
  completionFinalAssistantEvidence(parsedMessages, turnStartedAt) {
48276
48428
  const turnClosed = !this.hasAdapterPendingResponse();
48277
48429
  if (this.completionHasFinalAssistantMessage(parsedMessages, turnStartedAt)) {
@@ -48285,9 +48437,9 @@ var CliProviderInstance = class _CliProviderInstance {
48285
48437
  if (externalMessages) {
48286
48438
  const injectedTaskGenerating = this.injectedTaskHasStartedGenerating();
48287
48439
  const present = injectedTaskGenerating && turnClosed && this.completionHasFinalAssistantMessage(externalMessages, turnStartedAt);
48288
- const lastVisibleAssistant = this.lastVisibleAssistantSummary(externalMessages);
48289
- if (lastVisibleAssistant) {
48290
- this.lastCompletionSummary = { content: lastVisibleAssistant, receivedAt: Date.now() };
48440
+ const lastVisibleAssistant = this.lastVisibleAssistantSummaryDetail(externalMessages);
48441
+ if (lastVisibleAssistant.content) {
48442
+ this.lastCompletionSummary = { content: lastVisibleAssistant.content, receivedAt: Date.now(), sourceTimestampMs: lastVisibleAssistant.timestampMs };
48291
48443
  }
48292
48444
  return {
48293
48445
  present,
@@ -48311,7 +48463,7 @@ var CliProviderInstance = class _CliProviderInstance {
48311
48463
  const externalMessages = this.readExternalCompletionMessages();
48312
48464
  const externalSummary = externalMessages ? extractFinalSummaryFromMessagesAfter(externalMessages, turnStartedAt) : "";
48313
48465
  if (externalSummary) {
48314
- this.lastCompletionSummary = { content: externalSummary, receivedAt: Date.now() };
48466
+ this.lastCompletionSummary = { content: externalSummary, receivedAt: Date.now(), sourceTimestampMs: typeof turnStartedAt === "number" ? turnStartedAt : void 0 };
48315
48467
  return externalSummary;
48316
48468
  }
48317
48469
  return parsedSummary || void 0;
@@ -48326,7 +48478,7 @@ var CliProviderInstance = class _CliProviderInstance {
48326
48478
  } catch (error) {
48327
48479
  parseError = error?.message || String(error);
48328
48480
  }
48329
- const evidence = this.completionFinalAssistantEvidence(parsed?.messages);
48481
+ const evidence = this.completionFinalAssistantEvidence(parsed?.messages, args.pending.turnStartedAt);
48330
48482
  if (evidence.source === "external-native") {
48331
48483
  this.recordPendingTranscriptProbe(args.pending);
48332
48484
  }
@@ -48335,7 +48487,7 @@ var CliProviderInstance = class _CliProviderInstance {
48335
48487
  const lastVisibleRole = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : null;
48336
48488
  const lastVisibleKind = typeof lastVisible?.kind === "string" ? lastVisible.kind : null;
48337
48489
  const lastVisibleContentLength = lastVisible ? flattenContent(lastVisible.content).trim().length : 0;
48338
- const cachedSummary = evidence.present ? "" : this.cachedCompletionSummaryContent();
48490
+ const cachedSummary = evidence.present ? "" : this.cachedInTurnCompletionSummaryContent(args.pending.turnStartedAt);
48339
48491
  const creditedFromCache = !evidence.present && cachedSummary.length > 0;
48340
48492
  const finalAssistantPresent = evidence.present || creditedFromCache;
48341
48493
  const finalAssistantEvidenceSource = evidence.present ? evidence.source : creditedFromCache ? "cached-summary" : evidence.source;
@@ -48999,7 +49151,7 @@ var CliProviderInstance = class _CliProviderInstance {
48999
49151
  // real answer (lastCompletionSummary). Fall back to the cache so the notification
49000
49152
  // carries the summary that mesh_read_chat.summary already shows — consistent with
49001
49153
  // completionDiagnostic.finalAssistantPresent being credited from the same cache.
49002
- finalSummary: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages, pending.turnStartedAt) || this.cachedCompletionSummaryContent() || (blockReason.startsWith("parsed_status:") ? "" : void 0),
49154
+ finalSummary: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages, pending.turnStartedAt) || this.cachedInTurnCompletionSummaryContent(pending.turnStartedAt) || (blockReason.startsWith("parsed_status:") ? "" : void 0),
49003
49155
  completionDiagnostic
49004
49156
  });
49005
49157
  this.completedDebouncePending = null;
@@ -49233,8 +49385,18 @@ ${buttons.join("\n")}`;
49233
49385
  this.lastAutoApprovalSignature = "";
49234
49386
  }, 5e3);
49235
49387
  this.recordAutoApproval(modal?.message, buttonLabel, now);
49388
+ this.lastAutoApproveFiredAt = now;
49236
49389
  setTimeout(() => {
49237
- this.adapter.resolveModal(buttonIndex);
49390
+ const adapter = this.adapter;
49391
+ if (typeof adapter.resolveModalMatched === "function") {
49392
+ const matched = adapter.resolveModalMatched(buttonIndex);
49393
+ if (!matched) {
49394
+ if (this.lastAutoApproveFiredAt === now) this.lastAutoApproveFiredAt = 0;
49395
+ LOG.warn("CLI", `[${this.type}] auto-approve resolveModal matched no button (index ${buttonIndex}) \u2014 surfacing approval to coordinator`);
49396
+ }
49397
+ } else {
49398
+ adapter.resolveModal?.(buttonIndex);
49399
+ }
49238
49400
  }, 0);
49239
49401
  return autoApproveActive;
49240
49402
  }