@adhdev/daemon-core 0.9.82-rc.355 → 0.9.82-rc.357

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.
@@ -12,6 +12,15 @@ export interface CliAdapterStatus {
12
12
  activeModal?: {
13
13
  message: string;
14
14
  buttons: string[];
15
+ /**
16
+ * Semantic modal class, when the adapter knows it (spec/FSM path):
17
+ * 'approval' = tool/command/trust consent (auto-approve may fire);
18
+ * 'picker' = a selection menu the user opened (/model, /mode — must NOT
19
+ * be auto-answered); 'confirm' = a yes/no left to the user. Absent/null
20
+ * for adapters that don't classify modals — the auto-approve gate then
21
+ * falls back to its structural heuristic.
22
+ */
23
+ kind?: 'approval' | 'picker' | 'confirm' | null;
15
24
  } | null;
16
25
  activeInteractivePrompt?: InteractivePrompt | null;
17
26
  providerSessionId?: string;
package/dist/index.js CHANGED
@@ -316,10 +316,10 @@ function readInjected(value) {
316
316
  }
317
317
  function getDaemonBuildInfo() {
318
318
  if (cached) return cached;
319
- const commit = readInjected(true ? "a45106605e2ae1c10c0bc6cbe48c2cac4e862ded" : void 0) ?? "unknown";
320
- const commitShort = readInjected(true ? "a4510660" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
321
- const version = readInjected(true ? "0.9.82-rc.355" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
322
- const builtAt = readInjected(true ? "2026-06-22T15:21:19.981Z" : void 0);
319
+ const commit = readInjected(true ? "37b9d1d9e10336707dbc0861a96d5bcc55cc015e" : void 0) ?? "unknown";
320
+ const commitShort = readInjected(true ? "37b9d1d9" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
321
+ const version = readInjected(true ? "0.9.82-rc.357" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
322
+ const builtAt = readInjected(true ? "2026-06-23T00:25:21.599Z" : void 0);
323
323
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
324
324
  return cached;
325
325
  }
@@ -12673,8 +12673,22 @@ function sweepExpiredRemoteIdleSessions() {
12673
12673
  }
12674
12674
  function getMeshWithCache(components, meshId) {
12675
12675
  const localMesh = getMesh(meshId);
12676
- if (localMesh) return localMesh;
12677
- return components.router?.getCachedInlineMesh(meshId);
12676
+ const cachedMesh = components.router?.getCachedInlineMesh(meshId);
12677
+ if (!localMesh) return cachedMesh;
12678
+ if (!cachedMesh) return localMesh;
12679
+ return mergeInlineCacheOnlyNodes(localMesh, cachedMesh);
12680
+ }
12681
+ function mergeInlineCacheOnlyNodes(localMesh, cachedMesh) {
12682
+ const localNodes = Array.isArray(localMesh?.nodes) ? localMesh.nodes : [];
12683
+ const cachedNodes = Array.isArray(cachedMesh?.nodes) ? cachedMesh.nodes : [];
12684
+ if (!cachedNodes.length) return localMesh;
12685
+ const cacheOnly = cachedNodes.filter((cachedNode) => {
12686
+ const cachedId = readMeshNodeId(cachedNode);
12687
+ if (!cachedId) return false;
12688
+ return !localNodes.some((localNode) => meshNodeIdMatches(localNode, cachedId));
12689
+ });
12690
+ if (!cacheOnly.length) return localMesh;
12691
+ return { ...localMesh, nodes: [...localNodes, ...cacheOnly] };
12678
12692
  }
12679
12693
  function isIntentionalCleanupStopMetadata(event) {
12680
12694
  return event.intentional === true || event.intentionalStop === true || event.operatorCleanup === true || event.reason === "operator_cleanup" || event.stopReason === "operator_cleanup" || event.cleanupReason === "operator_cleanup" || event.source === "mesh_cleanup_sessions" || event.source === "mesh_remove_node";
@@ -14011,6 +14025,76 @@ function injectMeshSystemMessage(components, args) {
14011
14025
  }
14012
14026
  return { success: true, forwarded: 0 };
14013
14027
  }
14028
+ function buildRelayMetadataEvent(payload) {
14029
+ const relayModalMessage = readNonEmptyString2(payload.modalMessage);
14030
+ const relayModalButtons = Array.isArray(payload.modalButtons) ? payload.modalButtons.filter((b) => typeof b === "string" && b.trim().length > 0) : null;
14031
+ return {
14032
+ // Preserve the dispatch task id across the machine boundary. The `received` trace
14033
+ // stage reads payload.taskId; without mirroring it here the rebuilt metadataEvent
14034
+ // loses it, so injectMeshSystemMessage's traceCtx.taskId and the
14035
+ // updateDirectDispatchStatus(eventTaskId) call go undefined — the EvtTrace
14036
+ // queued/surfaced stages show task=- and the direct-dispatch ledger falls back to a
14037
+ // session_id match (which can flip a sibling row). The local in-process forward path
14038
+ // keeps event.taskId/meshActiveTaskId for free; this mirrors it for the remote relay.
14039
+ // Same taskId/meshActiveTaskId ordering the local unroutable trace uses.
14040
+ taskId: readNonEmptyString2(payload.taskId) || readNonEmptyString2(payload.meshActiveTaskId),
14041
+ targetSessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId) || readNonEmptyString2(payload.instanceId),
14042
+ providerType: readNonEmptyString2(payload.providerType),
14043
+ providerSessionId: readNonEmptyString2(payload.providerSessionId),
14044
+ // Preserve the originating coordinator SESSION id across the machine boundary so
14045
+ // the completion routes back to the exact coordinator session (multi-coordinator).
14046
+ // buildForwardPayloadFromPending spreads the worker event's metadata, so the id
14047
+ // arrives as payload.meshCoordinatorSessionId; the top-level targetCoordinatorSessionId
14048
+ // is also accepted as a fallback. injectMeshSystemMessage re-derives the routing
14049
+ // anchors from this. Absent → daemon-level fallback (version-skew safe).
14050
+ meshCoordinatorSessionId: readNonEmptyString2(payload.meshCoordinatorSessionId) || readNonEmptyString2(payload.targetCoordinatorSessionId),
14051
+ // Carry the session identity fields the worker provider event emits so the
14052
+ // coordinator's mirror (updateMeshOwnedSession) gets a real workspace/title/
14053
+ // settings. Without these the remote-relay hop reconstructs metadataEvent with
14054
+ // an empty workspace, and the dashboard flaps to the generic
14055
+ // "Terminal (Mesh Node)" title (and degrades the provider label) between live
14056
+ // events and the periodic get_status_metadata snapshot. The local in-process
14057
+ // forward path (onMeshCoordinatorEventForwarded) already preserves these; this
14058
+ // mirrors them for the remote-only relay path.
14059
+ workspace: readNonEmptyString2(payload.workspace) || readNonEmptyString2(payload.workspaceName),
14060
+ workspaceName: readNonEmptyString2(payload.workspaceName) || readNonEmptyString2(payload.workspace),
14061
+ sessionTitle: readNonEmptyString2(payload.sessionTitle),
14062
+ sessionStatus: readNonEmptyString2(payload.sessionStatus),
14063
+ sessionChatStatus: readNonEmptyString2(payload.sessionChatStatus),
14064
+ providerName: readNonEmptyString2(payload.providerName),
14065
+ ...payload.sessionSettings && typeof payload.sessionSettings === "object" && !Array.isArray(payload.sessionSettings) ? { sessionSettings: payload.sessionSettings } : {},
14066
+ finalSummary: readNonEmptyString2(payload.finalSummary) || readNonEmptyString2(payload.summary),
14067
+ // T2: carry the worker's status-snapshot last-message preview across the machine
14068
+ // boundary so a summary-less completion still surfaces the assistant reply in the
14069
+ // coordinator's inbox mirror. resolveMeshSurfacedSessionPreview reads these
14070
+ // (assistant-role only) when finalSummary is absent.
14071
+ lastMessagePreview: readNonEmptyString2(payload.lastMessagePreview),
14072
+ lastMessageRole: readNonEmptyString2(payload.lastMessageRole),
14073
+ ...payload.lastMessageAt !== void 0 ? { lastMessageAt: payload.lastMessageAt } : {},
14074
+ jobId: readNonEmptyString2(payload.jobId),
14075
+ interactionId: readNonEmptyString2(payload.interactionId),
14076
+ status: readNonEmptyString2(payload.status),
14077
+ targetDaemonId: readNonEmptyString2(payload.targetDaemonId),
14078
+ startedAt: readNonEmptyString2(payload.startedAt),
14079
+ completedAt: readNonEmptyString2(payload.completedAt),
14080
+ retryOfJobId: readNonEmptyString2(payload.retryOfJobId),
14081
+ ...relayModalMessage ? { modalMessage: relayModalMessage } : {},
14082
+ ...relayModalButtons && relayModalButtons.length > 0 ? { modalButtons: relayModalButtons } : {},
14083
+ ...payload.result && typeof payload.result === "object" && !Array.isArray(payload.result) ? { result: payload.result } : {},
14084
+ ...payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? { completionDiagnostic: payload.completionDiagnostic } : {},
14085
+ ...payload.workerResult && typeof payload.workerResult === "object" && !Array.isArray(payload.workerResult) ? { workerResult: payload.workerResult } : {},
14086
+ ...payload.meshWorkerResult && typeof payload.meshWorkerResult === "object" && !Array.isArray(payload.meshWorkerResult) ? { meshWorkerResult: payload.meshWorkerResult } : {},
14087
+ ...payload.structuredResult && typeof payload.structuredResult === "object" && !Array.isArray(payload.structuredResult) ? { structuredResult: payload.structuredResult } : {},
14088
+ ...payload.timestamp !== void 0 ? { timestamp: payload.timestamp } : {},
14089
+ intentional: payload.intentional === true,
14090
+ intentionalStop: payload.intentionalStop === true,
14091
+ operatorCleanup: payload.operatorCleanup === true,
14092
+ reason: readNonEmptyString2(payload.reason),
14093
+ stopReason: readNonEmptyString2(payload.stopReason),
14094
+ cleanupReason: readNonEmptyString2(payload.cleanupReason),
14095
+ source: readNonEmptyString2(payload.source)
14096
+ };
14097
+ }
14014
14098
  function handleMeshForwardEvent(components, payload) {
14015
14099
  const eventName = readNonEmptyString2(payload.event);
14016
14100
  if (!isMeshCoordinatorEvent(eventName)) {
@@ -14036,70 +14120,12 @@ function handleMeshForwardEvent(components, payload) {
14036
14120
  event: eventName
14037
14121
  });
14038
14122
  const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
14039
- const relayModalMessage = readNonEmptyString2(payload.modalMessage);
14040
- const relayModalButtons = Array.isArray(payload.modalButtons) ? payload.modalButtons.filter((b) => typeof b === "string" && b.trim().length > 0) : null;
14041
14123
  return injectMeshSystemMessage(components, {
14042
14124
  meshId,
14043
14125
  nodeId,
14044
14126
  nodeLabel,
14045
14127
  event: eventName,
14046
- metadataEvent: {
14047
- targetSessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId) || readNonEmptyString2(payload.instanceId),
14048
- providerType: readNonEmptyString2(payload.providerType),
14049
- providerSessionId: readNonEmptyString2(payload.providerSessionId),
14050
- // Preserve the originating coordinator SESSION id across the machine boundary so
14051
- // the completion routes back to the exact coordinator session (multi-coordinator).
14052
- // buildForwardPayloadFromPending spreads the worker event's metadata, so the id
14053
- // arrives as payload.meshCoordinatorSessionId; the top-level targetCoordinatorSessionId
14054
- // is also accepted as a fallback. injectMeshSystemMessage re-derives the routing
14055
- // anchors from this. Absent → daemon-level fallback (version-skew safe).
14056
- meshCoordinatorSessionId: readNonEmptyString2(payload.meshCoordinatorSessionId) || readNonEmptyString2(payload.targetCoordinatorSessionId),
14057
- // Carry the session identity fields the worker provider event emits so the
14058
- // coordinator's mirror (updateMeshOwnedSession) gets a real workspace/title/
14059
- // settings. Without these the remote-relay hop reconstructs metadataEvent with
14060
- // an empty workspace, and the dashboard flaps to the generic
14061
- // "Terminal (Mesh Node)" title (and degrades the provider label) between live
14062
- // events and the periodic get_status_metadata snapshot. The local in-process
14063
- // forward path (onMeshCoordinatorEventForwarded) already preserves these; this
14064
- // mirrors them for the remote-only relay path.
14065
- workspace: readNonEmptyString2(payload.workspace) || readNonEmptyString2(payload.workspaceName),
14066
- workspaceName: readNonEmptyString2(payload.workspaceName) || readNonEmptyString2(payload.workspace),
14067
- sessionTitle: readNonEmptyString2(payload.sessionTitle),
14068
- sessionStatus: readNonEmptyString2(payload.sessionStatus),
14069
- sessionChatStatus: readNonEmptyString2(payload.sessionChatStatus),
14070
- providerName: readNonEmptyString2(payload.providerName),
14071
- ...payload.sessionSettings && typeof payload.sessionSettings === "object" && !Array.isArray(payload.sessionSettings) ? { sessionSettings: payload.sessionSettings } : {},
14072
- finalSummary: readNonEmptyString2(payload.finalSummary) || readNonEmptyString2(payload.summary),
14073
- // T2: carry the worker's status-snapshot last-message preview across the machine
14074
- // boundary so a summary-less completion still surfaces the assistant reply in the
14075
- // coordinator's inbox mirror. resolveMeshSurfacedSessionPreview reads these
14076
- // (assistant-role only) when finalSummary is absent.
14077
- lastMessagePreview: readNonEmptyString2(payload.lastMessagePreview),
14078
- lastMessageRole: readNonEmptyString2(payload.lastMessageRole),
14079
- ...payload.lastMessageAt !== void 0 ? { lastMessageAt: payload.lastMessageAt } : {},
14080
- jobId: readNonEmptyString2(payload.jobId),
14081
- interactionId: readNonEmptyString2(payload.interactionId),
14082
- status: readNonEmptyString2(payload.status),
14083
- targetDaemonId: readNonEmptyString2(payload.targetDaemonId),
14084
- startedAt: readNonEmptyString2(payload.startedAt),
14085
- completedAt: readNonEmptyString2(payload.completedAt),
14086
- retryOfJobId: readNonEmptyString2(payload.retryOfJobId),
14087
- ...relayModalMessage ? { modalMessage: relayModalMessage } : {},
14088
- ...relayModalButtons && relayModalButtons.length > 0 ? { modalButtons: relayModalButtons } : {},
14089
- ...payload.result && typeof payload.result === "object" && !Array.isArray(payload.result) ? { result: payload.result } : {},
14090
- ...payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? { completionDiagnostic: payload.completionDiagnostic } : {},
14091
- ...payload.workerResult && typeof payload.workerResult === "object" && !Array.isArray(payload.workerResult) ? { workerResult: payload.workerResult } : {},
14092
- ...payload.meshWorkerResult && typeof payload.meshWorkerResult === "object" && !Array.isArray(payload.meshWorkerResult) ? { meshWorkerResult: payload.meshWorkerResult } : {},
14093
- ...payload.structuredResult && typeof payload.structuredResult === "object" && !Array.isArray(payload.structuredResult) ? { structuredResult: payload.structuredResult } : {},
14094
- ...payload.timestamp !== void 0 ? { timestamp: payload.timestamp } : {},
14095
- intentional: payload.intentional === true,
14096
- intentionalStop: payload.intentionalStop === true,
14097
- operatorCleanup: payload.operatorCleanup === true,
14098
- reason: readNonEmptyString2(payload.reason),
14099
- stopReason: readNonEmptyString2(payload.stopReason),
14100
- cleanupReason: readNonEmptyString2(payload.cleanupReason),
14101
- source: readNonEmptyString2(payload.source)
14102
- }
14128
+ metadataEvent: buildRelayMetadataEvent(payload)
14103
14129
  });
14104
14130
  }
14105
14131
  function forwardUnresolvedDelegateEvent(components, routing, event) {
@@ -19744,6 +19770,7 @@ function extractButtonsFromRule(rule, hay) {
19744
19770
  const idx = Number(m[1]);
19745
19771
  let label = String(m[2] ?? "").trim();
19746
19772
  if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
19773
+ const current = hasCursorMarker(lines[i]);
19747
19774
  let j = i + 1;
19748
19775
  while (j < lines.length) {
19749
19776
  const next = lines[j];
@@ -19755,7 +19782,7 @@ function extractButtonsFromRule(rule, hay) {
19755
19782
  }
19756
19783
  if (buttons.some((b) => b.index === idx)) continue;
19757
19784
  const key = keyTemplate.replace(/\{index\}/g, String(idx));
19758
- buttons.push({ index: idx, label, key });
19785
+ buttons.push({ index: idx, label, key, current });
19759
19786
  i = j - 1;
19760
19787
  }
19761
19788
  } else {
@@ -19767,12 +19794,15 @@ function extractButtonsFromRule(rule, hay) {
19767
19794
  if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
19768
19795
  if (buttons.some((b) => b.index === idx)) continue;
19769
19796
  const key = keyTemplate.replace(/\{index\}/g, String(idx));
19770
- buttons.push({ index: idx, label, key });
19797
+ buttons.push({ index: idx, label, key, current: hasCursorMarker(m[0]) });
19771
19798
  }
19772
19799
  }
19773
19800
  buttons.sort((a, b) => a.index - b.index);
19774
19801
  return buttons;
19775
19802
  }
19803
+ function hasCursorMarker(text) {
19804
+ return /^\s*[❯›>]/.test(text);
19805
+ }
19776
19806
  var init_evaluator = __esm({
19777
19807
  "src/providers/spec/evaluator.ts"() {
19778
19808
  "use strict";
@@ -19803,6 +19833,11 @@ function statusForState(state) {
19803
19833
  if (state.id === "busy" || state.id === "generating") return "generating";
19804
19834
  return "idle";
19805
19835
  }
19836
+ function modalKindForState(state) {
19837
+ if (state.modal_kind) return state.modal_kind;
19838
+ if (state.modal) return "approval";
19839
+ return null;
19840
+ }
19806
19841
  var init_fsm_types = __esm({
19807
19842
  "src/providers/spec/fsm-types.ts"() {
19808
19843
  "use strict";
@@ -33694,7 +33729,12 @@ var FsmDriver = class {
33694
33729
  this.emit({
33695
33730
  kind: "state_changed",
33696
33731
  state: next.state,
33697
- modal: next.modal ? { title: next.modal.title, buttons: next.modal.buttons.map((b) => ({ index: b.index, label: b.label })) } : null,
33732
+ // kind is the SEMANTIC modal class (approval vs picker/confirm)
33733
+ // derived from the FSM state, NOT from the parsed buttons — the
33734
+ // status field already collapsed it to 'approval' so the modal is
33735
+ // surfaced. The auto-approve worker needs the distinction back to
33736
+ // avoid answering a /model picker on the user's behalf.
33737
+ modal: next.modal ? { title: next.modal.title, buttons: next.modal.buttons.map((b) => ({ index: b.index, label: b.label })), kind: modalKindForState(state) } : null,
33698
33738
  controls: next.controls.map((c) => ({ id: c.id, label: c.label, action_type: c.actionType }))
33699
33739
  });
33700
33740
  this.fireNotifications(state.id, title);
@@ -34049,6 +34089,19 @@ var FsmDriver = class {
34049
34089
  if (!m) return;
34050
34090
  const btn = m.buttons.find((b) => b.index === index);
34051
34091
  if (!btn) return;
34092
+ const rule = stateById(this.spec, this.currentStateId)?.extract?.buttons;
34093
+ if (rule?.select_mode === "arrow_keys") {
34094
+ const from = m.buttons.find((b) => b.current)?.index ?? 1;
34095
+ const up = rule.cursor_keys?.up ?? "\x1B[A";
34096
+ const down = rule.cursor_keys?.down ?? "\x1B[B";
34097
+ const delta = btn.index - from;
34098
+ const step = delta >= 0 ? down : up;
34099
+ const nav = step.repeat(Math.abs(delta));
34100
+ const confirm = (rule.key_for_index || "\r").replace(/\{index\}/g, "") || "\r";
34101
+ if (nav) this.adapter.send_keys(nav);
34102
+ this.adapter.send_keys(confirm);
34103
+ return;
34104
+ }
34052
34105
  this.adapter.send_keys(btn.key);
34053
34106
  }
34054
34107
  handleAttachImage(blob, mime) {
@@ -35101,7 +35154,9 @@ var SpecCliAdapter = class _SpecCliAdapter {
35101
35154
  messages: [],
35102
35155
  // Surface buttons when we have them; an approval state with no parsed
35103
35156
  // modal this frame still stays waiting_approval (no activeModal yet).
35104
- activeModal: modal ? { message: modal.title ?? state.label, buttons: modal.buttons.map((b) => b.label) } : null,
35157
+ // `kind` carries the semantic modal class through to the auto-approve
35158
+ // gate so a /model picker (kind='picker') is never auto-answered.
35159
+ activeModal: modal ? { message: modal.title ?? state.label, buttons: modal.buttons.map((b) => b.label), kind: modal.kind ?? null } : null,
35105
35160
  activeInteractivePrompt: this.activeInteractivePrompt,
35106
35161
  ...sessionFields
35107
35162
  };
@@ -35286,9 +35341,12 @@ var SpecCliAdapter = class _SpecCliAdapter {
35286
35341
  * — not this code — decides how a selection is keyed for each CLI.
35287
35342
  */
35288
35343
  async selectPickerChoice(ctl, action, choiceIndex, choiceLabel) {
35289
- this.driver.dispatch({ kind: "click_control", control_id: ctl.id });
35290
- await this.waitForPickerRendered(action);
35291
- const options = this.extractPickerChoices(action);
35344
+ let options = this.extractPickerChoicesIfRendered(action);
35345
+ if (!options) {
35346
+ this.driver.dispatch({ kind: "click_control", control_id: ctl.id });
35347
+ await this.waitForPickerRendered(action);
35348
+ options = this.extractPickerChoices(action);
35349
+ }
35292
35350
  let index = choiceIndex;
35293
35351
  if ((index == null || !Number.isFinite(index)) && choiceLabel) {
35294
35352
  const needle = choiceLabel.trim().toLowerCase();
@@ -35301,8 +35359,27 @@ var SpecCliAdapter = class _SpecCliAdapter {
35301
35359
  if (index == null || !Number.isFinite(index)) {
35302
35360
  return { ok: false, error: "choiceIndex or choiceLabel required to select" };
35303
35361
  }
35304
- const keys = (action.submit_key || "{index}\r").replace(/\{index\}/g, String(index));
35305
- this.driver.dispatch({ kind: "pty_write", data: keys });
35362
+ if (action.select_mode === "arrow_keys") {
35363
+ const current = options.find((o) => o.current);
35364
+ if (current == null) {
35365
+ return {
35366
+ ok: false,
35367
+ error: "arrow-nav picker: current cursor row not detected on screen",
35368
+ controlResult: { options: options.map((o) => ({ value: o.label, label: o.label, current: o.current })) }
35369
+ };
35370
+ }
35371
+ const up = action.cursor_keys?.up ?? "\x1B[A";
35372
+ const down = action.cursor_keys?.down ?? "\x1B[B";
35373
+ const delta = index - current.index;
35374
+ const step = delta >= 0 ? down : up;
35375
+ const nav = step.repeat(Math.abs(delta));
35376
+ const confirm = (action.submit_key || "\r").replace(/\{index\}/g, "") || "\r";
35377
+ if (nav) this.driver.dispatch({ kind: "pty_write", data: nav });
35378
+ this.driver.dispatch({ kind: "pty_write", data: confirm });
35379
+ } else {
35380
+ const keys = (action.submit_key || "{index}\r").replace(/\{index\}/g, String(index));
35381
+ this.driver.dispatch({ kind: "pty_write", data: keys });
35382
+ }
35306
35383
  const selected = options.find((o) => o.index === index);
35307
35384
  return {
35308
35385
  ok: true,
@@ -35314,6 +35391,20 @@ var SpecCliAdapter = class _SpecCliAdapter {
35314
35391
  }
35315
35392
  };
35316
35393
  }
35394
+ /** Parse the picker choices only if the picker already appears rendered on
35395
+ * the live screen (its `wait_for` condition currently matches and at least
35396
+ * one choice parses). Returns the parsed choices when open, else null so
35397
+ * the caller knows it must send the trigger to open it. Used to de-dup the
35398
+ * picker open in {@link selectPickerChoice}. */
35399
+ extractPickerChoicesIfRendered(action) {
35400
+ const wf = action.wait_for;
35401
+ if (wf?.regex) {
35402
+ const re = new RegExp(wf.regex, wf.flags ?? "i");
35403
+ if (!re.test(this.readScreenSectionText(wf.section))) return null;
35404
+ }
35405
+ const options = this.extractPickerChoices(action);
35406
+ return options.length > 0 ? options : null;
35407
+ }
35317
35408
  /** Poll the live screen until the picker's `wait_for` condition matches,
35318
35409
  * up to a short budget. Returns true if it rendered, false on timeout. */
35319
35410
  async waitForPickerRendered(action) {
@@ -37032,8 +37123,12 @@ var CliProviderInstance = class _CliProviderInstance {
37032
37123
  if (!modal || buttons.length === 0) {
37033
37124
  return autoApproveActive;
37034
37125
  }
37035
- const { index: buttonIndex, label: buttonLabel } = pickAutoApprovalButton(buttons);
37036
- if (buttonIndex < 0) {
37126
+ const modalKind = typeof modal?.kind === "string" ? modal.kind : "approval";
37127
+ if (modalKind !== "approval") {
37128
+ return autoApproveActive;
37129
+ }
37130
+ const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(buttons, this.provider);
37131
+ if (buttonIndex < 0 || !hasNegativeApprovalOption(buttons)) {
37037
37132
  return autoApproveActive;
37038
37133
  }
37039
37134
  const modalSignature = [
@@ -37337,6 +37432,12 @@ var CliProviderInstance = class _CliProviderInstance {
37337
37432
  workspace: typeof event.workspace === "string" && event.workspace.trim() ? event.workspace : this.workingDir,
37338
37433
  providerSessionId: typeof event.providerSessionId === "string" && event.providerSessionId.trim() ? event.providerSessionId : this.providerSessionId
37339
37434
  };
37435
+ if (this.isMeshWorkerSession() && this.settings.meshActiveTaskId) {
37436
+ const existingTaskId = typeof enrichedEvent.taskId === "string" && enrichedEvent.taskId.trim() ? enrichedEvent.taskId : void 0;
37437
+ if (!existingTaskId) {
37438
+ enrichedEvent.taskId = this.settings.meshActiveTaskId;
37439
+ }
37440
+ }
37340
37441
  if (this.context?.emitProviderEvent) {
37341
37442
  this.context.emitProviderEvent(enrichedEvent);
37342
37443
  } else {