@adhdev/daemon-core 0.9.82-rc.354 → 0.9.82-rc.356

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
@@ -311,10 +311,10 @@ function readInjected(value) {
311
311
  }
312
312
  function getDaemonBuildInfo() {
313
313
  if (cached) return cached;
314
- const commit = readInjected(true ? "5d100a177f412ae29a58048f0a211c3928b06910" : void 0) ?? "unknown";
315
- const commitShort = readInjected(true ? "5d100a17" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
316
- const version = readInjected(true ? "0.9.82-rc.354" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
317
- const builtAt = readInjected(true ? "2026-06-22T11:32:05.389Z" : void 0);
314
+ const commit = readInjected(true ? "91500e054cf2b6258f6041f90c18be03ad05a8ad" : void 0) ?? "unknown";
315
+ const commitShort = readInjected(true ? "91500e05" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
316
+ const version = readInjected(true ? "0.9.82-rc.356" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
317
+ const builtAt = readInjected(true ? "2026-06-22T22:47:29.082Z" : void 0);
318
318
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
319
319
  return cached;
320
320
  }
@@ -4822,9 +4822,12 @@ function getActiveDirectDispatches(meshId) {
4822
4822
  return [];
4823
4823
  }
4824
4824
  }
4825
- function updateDirectDispatchStatus(meshId, sessionId, status) {
4825
+ function updateDirectDispatchStatus(meshId, sessionId, status, taskId) {
4826
4826
  try {
4827
- MeshRuntimeStore.getInstance().updateDirectDispatchStatus(meshId, sessionId, status);
4827
+ if (!taskId) {
4828
+ LOG.warn("MeshQueue", `updateDirectDispatchStatus(${status}) for mesh ${meshId} session ${sessionId} has no taskId \u2014 falling back to session_id match (may flip a sibling dispatch row)`);
4829
+ }
4830
+ MeshRuntimeStore.getInstance().updateDirectDispatchStatus(meshId, sessionId, status, taskId);
4828
4831
  } catch {
4829
4832
  }
4830
4833
  }
@@ -5656,9 +5659,25 @@ var init_mesh_runtime_store = __esm({
5656
5659
  updatedAt: r.updated_at
5657
5660
  }));
5658
5661
  }
5659
- updateDirectDispatchStatus(meshId, sessionId, status) {
5660
- if (!sessionId) return;
5662
+ // CANON-B (dispatch identity): mesh_direct_dispatches is keyed by task_id (PK), but a
5663
+ // single session can host several sequential direct dispatches (re-dispatch / nudge), so
5664
+ // matching a status flip by session_id alone hits EVERY non-terminal row for that session
5665
+ // — flipping a sibling task's row and stranding the one whose event actually fired (the
5666
+ // assigned-stranded watchdog then requeues a task that is really still generating). When
5667
+ // the firing event carries a taskId, target the single PK row; the session_id match is the
5668
+ // legacy fallback only for events that arrive without a taskId.
5669
+ updateDirectDispatchStatus(meshId, sessionId, status, taskId) {
5661
5670
  const now = (/* @__PURE__ */ new Date()).toISOString();
5671
+ if (taskId) {
5672
+ this.db.prepare(`
5673
+ UPDATE mesh_direct_dispatches
5674
+ SET status = @status, updated_at = @updatedAt
5675
+ WHERE mesh_id = @meshId AND task_id = @taskId
5676
+ AND status NOT IN ('completed', 'failed')
5677
+ `).run({ status, meshId, taskId, updatedAt: now });
5678
+ return;
5679
+ }
5680
+ if (!sessionId) return;
5662
5681
  this.db.prepare(`
5663
5682
  UPDATE mesh_direct_dispatches
5664
5683
  SET status = @status, updated_at = @updatedAt
@@ -7387,7 +7406,7 @@ function resolveWin32Executable(command) {
7387
7406
  windowsHide: true
7388
7407
  }).trim();
7389
7408
  if (out) {
7390
- const matches = out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
7409
+ const matches = out.split(/\r?\n/).map((s2) => s2.trim()).filter(Boolean);
7391
7410
  const direct = matches.find((m) => DIRECT_EXEC_EXT.has(path10.extname(m).toLowerCase()));
7392
7411
  return direct || matches[0] || command;
7393
7412
  }
@@ -8739,11 +8758,29 @@ function hasPendingRefineTerminalEventDuplicate(event) {
8739
8758
  (pending) => pending.event === event.event && readRefineJobId2(pending) === jobId
8740
8759
  );
8741
8760
  }
8761
+ function isWeakCompletionMetadata(metadata) {
8762
+ const evidenceLevel = readNonEmptyString2(metadata.evidenceLevel);
8763
+ if (evidenceLevel === "insufficient" || evidenceLevel === "weak") return true;
8764
+ if (metadata.reviewRecommended === true) return true;
8765
+ const diag = readRecord4(metadata.completionDiagnostic);
8766
+ return diag?.finalAssistantPresent === false || diag?.blockReason === "missing_final_assistant";
8767
+ }
8742
8768
  function buildPendingEventFingerprint(event) {
8743
8769
  const metadata = readRecord4(event.metadataEvent) || {};
8744
8770
  if (event.event === "worktree_bootstrap_complete" || event.event === "worktree_bootstrap_failed") {
8745
8771
  return [event.meshId, event.event, event.nodeId || ""].join("::");
8746
8772
  }
8773
+ if (TERMINAL_COMPLETION_EVENTS.has(event.event)) {
8774
+ const terminalTaskId = readNonEmptyString2(metadata.taskId) || readNonEmptyString2(readRecord4(metadata.payload)?.taskId);
8775
+ if (terminalTaskId) {
8776
+ return [
8777
+ event.meshId,
8778
+ event.event,
8779
+ terminalTaskId,
8780
+ isWeakCompletionMetadata(metadata) ? "weak" : "genuine"
8781
+ ].join("::");
8782
+ }
8783
+ }
8747
8784
  const sessionId = resolveEventSessionId(metadata);
8748
8785
  const providerSessionId = readNonEmptyString2(metadata.providerSessionId);
8749
8786
  const taskId = readNonEmptyString2(metadata.taskId) || readNonEmptyString2(readRecord4(metadata.payload)?.taskId);
@@ -9102,7 +9139,7 @@ function clearPendingMeshCoordinatorEvents(meshId, coordinatorDaemonId) {
9102
9139
  }
9103
9140
  }
9104
9141
  }
9105
- var REFINE_TERMINAL_EVENTS, MAX_PENDING_EVENTS_BYTES, MAX_PENDING_EVENTS_KEEP;
9142
+ var REFINE_TERMINAL_EVENTS, TERMINAL_COMPLETION_EVENTS, MAX_PENDING_EVENTS_BYTES, MAX_PENDING_EVENTS_KEEP;
9106
9143
  var init_mesh_events_pending = __esm({
9107
9144
  "src/mesh/mesh-events-pending.ts"() {
9108
9145
  "use strict";
@@ -9112,6 +9149,7 @@ var init_mesh_events_pending = __esm({
9112
9149
  init_mesh_events_utils();
9113
9150
  init_dist();
9114
9151
  REFINE_TERMINAL_EVENTS = /* @__PURE__ */ new Set(["refine:completed", "refine:failed"]);
9152
+ TERMINAL_COMPLETION_EVENTS = /* @__PURE__ */ new Set(["agent:generating_completed", "agent:stopped"]);
9115
9153
  MAX_PENDING_EVENTS_BYTES = 100 * 1024;
9116
9154
  MAX_PENDING_EVENTS_KEEP = 50;
9117
9155
  }
@@ -9441,7 +9479,7 @@ function reconcileDirectDispatchCompletionFromTranscript(args) {
9441
9479
  evidence
9442
9480
  }
9443
9481
  });
9444
- updateDirectDispatchStatus(args.meshId, args.sessionId, kind === "task_completed" ? "completed" : "failed");
9482
+ updateDirectDispatchStatus(args.meshId, args.sessionId, kind === "task_completed" ? "completed" : "failed", args.taskId);
9445
9483
  markSessionDeliveriesTerminal(args.meshId, args.sessionId, kind === "task_completed" ? "completed" : "failed");
9446
9484
  setImmediate(() => cleanupTerminalDirectDispatches());
9447
9485
  queuePendingMeshCoordinatorEvent({
@@ -9736,8 +9774,8 @@ function parsePatternEntry(x) {
9736
9774
  if (x instanceof RegExp) return x;
9737
9775
  if (x && typeof x === "object" && typeof x.source === "string") {
9738
9776
  try {
9739
- const s = x;
9740
- return new RegExp(s.source, s.flags || "");
9777
+ const s2 = x;
9778
+ return new RegExp(s2.source, s2.flags || "");
9741
9779
  } catch {
9742
9780
  return null;
9743
9781
  }
@@ -10305,6 +10343,38 @@ var init_mesh_unresolved_forward_outbox = __esm({
10305
10343
  }
10306
10344
  });
10307
10345
 
10346
+ // src/mesh/mesh-event-trace.ts
10347
+ function s(v) {
10348
+ return typeof v === "string" && v.trim() ? v.trim() : "";
10349
+ }
10350
+ function meshEventTraceKey(ctx) {
10351
+ const segs = [`task=${s(ctx.taskId) || "-"}`];
10352
+ const eventId = s(ctx.eventId);
10353
+ if (eventId) segs.push(`evt=${eventId}`);
10354
+ segs.push(`sess=${s(ctx.sessionId) || "-"}`);
10355
+ const nodeId = s(ctx.nodeId);
10356
+ if (nodeId) segs.push(`node=${nodeId}`);
10357
+ const meshId = s(ctx.meshId);
10358
+ if (meshId) segs.push(`mesh=${meshId}`);
10359
+ const event = s(ctx.event);
10360
+ if (event) segs.push(`event=${event}`);
10361
+ return segs.join(" ");
10362
+ }
10363
+ function traceMeshEventStage(stage, ctx, detail) {
10364
+ LOG.info(CAT, `[stage:${stage}] ${meshEventTraceKey(ctx)}${detail ? ` \u2014 ${detail}` : ""}`);
10365
+ }
10366
+ function traceMeshEventDrop(reason, ctx, detail) {
10367
+ LOG.warn(CAT, `[drop:${reason}] ${meshEventTraceKey(ctx)}${detail ? ` \u2014 ${detail}` : ""}`);
10368
+ }
10369
+ var CAT;
10370
+ var init_mesh_event_trace = __esm({
10371
+ "src/mesh/mesh-event-trace.ts"() {
10372
+ "use strict";
10373
+ init_logger();
10374
+ CAT = "EvtTrace";
10375
+ }
10376
+ });
10377
+
10308
10378
  // src/config/state-store.ts
10309
10379
  import { existsSync as existsSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
10310
10380
  import { join as join18 } from "path";
@@ -12090,9 +12160,9 @@ function buildAcpSession(state, options) {
12090
12160
  }
12091
12161
  function buildSessionEntries(allStates, cdpManagers, options = {}) {
12092
12162
  const sessions = [];
12093
- const ideStates = allStates.filter((s) => s.category === "ide");
12094
- const cliStates = allStates.filter((s) => s.category === "cli");
12095
- const acpStates = allStates.filter((s) => s.category === "acp");
12163
+ const ideStates = allStates.filter((s2) => s2.category === "ide");
12164
+ const cliStates = allStates.filter((s2) => s2.category === "cli");
12165
+ const acpStates = allStates.filter((s2) => s2.category === "acp");
12096
12166
  for (const state of ideStates) {
12097
12167
  sessions.push(buildWorkspaceSession(state, cdpManagers, options));
12098
12168
  for (const ext of state.extensions) {
@@ -12577,6 +12647,15 @@ function getCachedMeshByWorkspace(workspace) {
12577
12647
  meshByWorkspaceCache.set(workspace, { mesh, cachedAt: now });
12578
12648
  return mesh;
12579
12649
  }
12650
+ function recoverMeshIdByNodeId(nodeId) {
12651
+ if (!nodeId) return "";
12652
+ for (const mesh of listMeshes()) {
12653
+ if (Array.isArray(mesh.nodes) && mesh.nodes.some((n) => meshNodeIdMatches(n, nodeId))) {
12654
+ return readNonEmptyString2(mesh.id);
12655
+ }
12656
+ }
12657
+ return "";
12658
+ }
12580
12659
  function __resetIdleAutoFastForwardForTests() {
12581
12660
  idleAutoFastForwardLastAttempt.clear();
12582
12661
  }
@@ -13479,6 +13558,13 @@ function shouldForceInjectMeshEvent(eventName) {
13479
13558
  function injectMeshSystemMessage(components, args) {
13480
13559
  const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
13481
13560
  const eventNodeId = readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId);
13561
+ const traceCtx = {
13562
+ taskId: args.metadataEvent.taskId,
13563
+ sessionId: eventSessionId,
13564
+ nodeId: eventNodeId,
13565
+ meshId: args.meshId,
13566
+ event: args.event
13567
+ };
13482
13568
  const sourceSession = args.sourceInstanceId ? components.instanceManager.getInstance(args.sourceInstanceId) : void 0;
13483
13569
  const workerCoordinatorDaemonId = readNonEmptyString2(
13484
13570
  sourceSession?.getState()?.settings?.meshCoordinatorDaemonId
@@ -13532,6 +13618,7 @@ function injectMeshSystemMessage(components, args) {
13532
13618
  }
13533
13619
  }
13534
13620
  LOG.info("MeshEvents", `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || "(unknown session)"}`);
13621
+ traceMeshEventDrop("intentional_cleanup_stop", traceCtx);
13535
13622
  return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
13536
13623
  }
13537
13624
  if (args.event === "monitor:no_progress") {
@@ -13552,6 +13639,7 @@ function injectMeshSystemMessage(components, args) {
13552
13639
  }
13553
13640
  if (reconciledCompletion?.source === "no_progress_terminal_ledger_suppression") {
13554
13641
  LOG.info("MeshEvents", `Suppressed no-progress monitor because terminal ledger evidence already exists for session ${eventSessionId || "(unknown session)"}`);
13642
+ traceMeshEventDrop("no_progress_terminal_ledger_suppression", traceCtx, `terminalKind=${reconciledCompletion.terminalLedgerKind}`);
13555
13643
  return {
13556
13644
  success: true,
13557
13645
  forwarded: 0,
@@ -13563,6 +13651,7 @@ function injectMeshSystemMessage(components, args) {
13563
13651
  }
13564
13652
  if (isDuplicateRefineTerminalEvent(args.meshId, args.event, args.metadataEvent)) {
13565
13653
  LOG.info("MeshEvents", `Suppressed duplicate ${args.event} for refine job ${readRefineJobId({ metadataEvent: args.metadataEvent })}`);
13654
+ traceMeshEventDrop("duplicate_refine_terminal", traceCtx);
13566
13655
  return { success: true, forwarded: 0, suppressed: true, duplicateRefineTerminalEvent: true };
13567
13656
  }
13568
13657
  const eventTimestamp = readEventTimestamp(args.metadataEvent.timestamp);
@@ -13577,6 +13666,7 @@ function injectMeshSystemMessage(components, args) {
13577
13666
  });
13578
13667
  if (duplicateApproval) {
13579
13668
  LOG.info("MeshEvents", `Suppressed duplicate approval event for mesh ${args.meshId} session ${eventSessionId}`);
13669
+ traceMeshEventDrop("duplicate_approval", traceCtx);
13580
13670
  return { success: true, forwarded: 0, suppressed: true, duplicateApproval: true };
13581
13671
  }
13582
13672
  }
@@ -13596,6 +13686,7 @@ function injectMeshSystemMessage(components, args) {
13596
13686
  const eventFinalSummary = readNonEmptyString2(args.metadataEvent.finalSummary);
13597
13687
  if (terminalProviderSessionId && terminalProviderSessionId === eventProviderSessionId || terminalFinalSummary && terminalFinalSummary === eventFinalSummary || args.metadataEvent.source === "no_progress_reconciliation") {
13598
13688
  LOG.info("MeshEvents", `Suppressed duplicate completion with existing terminal ledger evidence for mesh ${args.meshId} session ${eventSessionId}`);
13689
+ traceMeshEventDrop("duplicate_completion_terminal_ledger", traceCtx);
13599
13690
  return { success: true, forwarded: 0, suppressed: true, duplicateCompletion: true, terminalLedgerEvidence: true };
13600
13691
  }
13601
13692
  }
@@ -13614,6 +13705,7 @@ function injectMeshSystemMessage(components, args) {
13614
13705
  });
13615
13706
  if (duplicateCompletion) {
13616
13707
  LOG.info("MeshEvents", `Suppressed duplicate completion for mesh ${args.meshId} session ${eventSessionId}`);
13708
+ traceMeshEventDrop("duplicate_completion", traceCtx);
13617
13709
  return { success: true, forwarded: 0, suppressed: true, duplicateCompletion: true };
13618
13710
  }
13619
13711
  }
@@ -13632,6 +13724,7 @@ function injectMeshSystemMessage(components, args) {
13632
13724
  });
13633
13725
  if (duplicateStopped) {
13634
13726
  LOG.info("MeshEvents", `Suppressed duplicate stopped event for mesh ${args.meshId} session ${eventSessionId}`);
13727
+ traceMeshEventDrop("duplicate_stopped", traceCtx);
13635
13728
  return { success: true, forwarded: 0, suppressed: true, duplicateStopped: true };
13636
13729
  }
13637
13730
  }
@@ -13643,7 +13736,7 @@ function injectMeshSystemMessage(components, args) {
13643
13736
  });
13644
13737
  const leaveDirectDispatchActive = !task && opts?.tentativeIfDirect === true;
13645
13738
  if (!leaveDirectDispatchActive) {
13646
- updateDirectDispatchStatus(args.meshId, sessionId, outcome);
13739
+ updateDirectDispatchStatus(args.meshId, sessionId, outcome, eventTaskId);
13647
13740
  }
13648
13741
  markSessionDeliveriesTerminal(args.meshId, sessionId, outcome);
13649
13742
  setImmediate(() => cleanupTerminalDirectDispatches());
@@ -13656,7 +13749,7 @@ function injectMeshSystemMessage(components, args) {
13656
13749
  const nodeId = readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId);
13657
13750
  const providerType = readNonEmptyString2(args.metadataEvent.providerType);
13658
13751
  if (sessionId) {
13659
- directDispatchTaskIdForLedger = resolveActiveDirectDispatchTaskId(args.meshId, sessionId);
13752
+ directDispatchTaskIdForLedger = readNonEmptyString2(args.metadataEvent.taskId) || resolveActiveDirectDispatchTaskId(args.meshId, sessionId);
13660
13753
  const isFalseIdle = isFalseIdleCompletion(args.metadataEvent);
13661
13754
  completedTaskForLedger = markSessionTerminal(sessionId, "completed", eventTimestamp, { tentativeIfDirect: isFalseIdle });
13662
13755
  if (nodeId && providerType) {
@@ -13739,7 +13832,8 @@ function injectMeshSystemMessage(components, args) {
13739
13832
  }
13740
13833
  }
13741
13834
  if (sessionId) {
13742
- updateDirectDispatchStatus(args.meshId, sessionId, "acked");
13835
+ const startedTaskId = readNonEmptyString2(args.metadataEvent.taskId) || void 0;
13836
+ updateDirectDispatchStatus(args.meshId, sessionId, "acked", startedTaskId);
13743
13837
  const activeDeliveries = (() => {
13744
13838
  try {
13745
13839
  return MeshRuntimeStore.getInstance().getActiveSessionDeliveries(args.meshId, sessionId);
@@ -13747,7 +13841,8 @@ function injectMeshSystemMessage(components, args) {
13747
13841
  return [];
13748
13842
  }
13749
13843
  })();
13750
- for (const d of activeDeliveries) {
13844
+ const deliveriesToAck = startedTaskId ? activeDeliveries.filter((d) => d.taskId === startedTaskId) : activeDeliveries;
13845
+ for (const d of deliveriesToAck) {
13751
13846
  updateSessionDeliveryStatus(d.id, "acked");
13752
13847
  }
13753
13848
  }
@@ -13761,7 +13856,7 @@ function injectMeshSystemMessage(components, args) {
13761
13856
  }
13762
13857
  }
13763
13858
  if (sessionId) {
13764
- directDispatchTaskIdForLedger = resolveActiveDirectDispatchTaskId(args.meshId, sessionId);
13859
+ directDispatchTaskIdForLedger = readNonEmptyString2(args.metadataEvent.taskId) || resolveActiveDirectDispatchTaskId(args.meshId, sessionId);
13765
13860
  completedTaskForLedger = markSessionTerminal(sessionId, "failed");
13766
13861
  }
13767
13862
  }
@@ -13907,6 +14002,9 @@ function injectMeshSystemMessage(components, args) {
13907
14002
  };
13908
14003
  if (queuePendingMeshCoordinatorEvent(pendingEvent)) {
13909
14004
  LOG.info("MeshEvents", `Queued ${args.event} for coordinator (mesh ${args.meshId}${workerCoordinatorDaemonId ? `, coordinator daemon ${workerCoordinatorDaemonId}` : ""}${workerCoordinatorSessionId ? `, coordinator session ${workerCoordinatorSessionId}` : ""})`);
14005
+ traceMeshEventStage("queued", traceCtx, workerCoordinatorDaemonId ? `coordinatorDaemon=${workerCoordinatorDaemonId}` : "broadcast");
14006
+ } else {
14007
+ traceMeshEventDrop("queue_dedup", traceCtx);
13910
14008
  }
13911
14009
  return { success: true, forwarded: 0 };
13912
14010
  }
@@ -13917,8 +14015,23 @@ function handleMeshForwardEvent(components, payload) {
13917
14015
  }
13918
14016
  const nodeId = readNonEmptyString2(payload.nodeId);
13919
14017
  const workspace = readNonEmptyString2(payload.workspace);
13920
- const meshId = readNonEmptyString2(payload.meshId) || (workspace ? readNonEmptyString2(getCachedMeshByWorkspace(workspace)?.id) : "");
13921
- if (!meshId) return { success: false, error: "meshId required" };
14018
+ const meshId = readNonEmptyString2(payload.meshId) || (workspace ? readNonEmptyString2(getCachedMeshByWorkspace(workspace)?.id) : "") || recoverMeshIdByNodeId(nodeId);
14019
+ if (!meshId) {
14020
+ traceMeshEventDrop("meshId_required", {
14021
+ taskId: payload.taskId,
14022
+ sessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId),
14023
+ nodeId,
14024
+ event: eventName
14025
+ }, workspace ? `workspace=${workspace} unresolved` : "no workspace/nodeId");
14026
+ return { success: false, error: "meshId required" };
14027
+ }
14028
+ traceMeshEventStage("received", {
14029
+ taskId: payload.taskId,
14030
+ sessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId),
14031
+ nodeId,
14032
+ meshId,
14033
+ event: eventName
14034
+ });
13922
14035
  const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
13923
14036
  const relayModalMessage = readNonEmptyString2(payload.modalMessage);
13924
14037
  const relayModalButtons = Array.isArray(payload.modalButtons) ? payload.modalButtons.filter((b) => typeof b === "string" && b.trim().length > 0) : null;
@@ -13999,9 +14112,18 @@ function forwardUnresolvedDelegateEvent(components, routing, event) {
13999
14112
  workspace: readNonEmptyString2(routing.workspace) || readNonEmptyString2(event.workspace) || void 0
14000
14113
  };
14001
14114
  const persisted = enqueueUnresolvedDelegateForward(coordinatorDaemonId, eventName, payload);
14115
+ const fwdTraceCtx = {
14116
+ taskId: payload.taskId,
14117
+ sessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId),
14118
+ nodeId: readNonEmptyString2(routing.nodeId) || readNonEmptyString2(event.meshNodeId),
14119
+ event: eventName
14120
+ };
14121
+ traceMeshEventStage("outbox_enqueue", fwdTraceCtx, `coordinatorDaemon=${coordinatorDaemonId} meshId=absent`);
14122
+ traceMeshEventStage("forward_send", fwdTraceCtx, "immediate push");
14002
14123
  Promise.resolve(components.dispatchMeshCommand(coordinatorDaemonId, "mesh_forward_event", payload)).then((result) => {
14003
14124
  if (result && result.success === false) {
14004
14125
  LOG.warn("MeshEvents", `Immediate forward of ${eventName} to coordinator ${coordinatorDaemonId} rejected (${readNonEmptyString2(result.error) || "no reason"}) \u2014 left queued for retry`);
14126
+ traceMeshEventDrop("immediate_forward_rejected", fwdTraceCtx, readNonEmptyString2(result.error) || "no reason");
14005
14127
  return;
14006
14128
  }
14007
14129
  if (persisted) ackUnresolvedDelegateForwardByFingerprint(coordinatorDaemonId, eventName, payload);
@@ -14069,6 +14191,14 @@ function setupMeshEventForwarding(components) {
14069
14191
  if (isUnroutableDelegateRejection(routing) && forwardUnresolvedDelegateEvent(components, routing, event)) {
14070
14192
  return;
14071
14193
  }
14194
+ if (isUnroutableDelegateRejection(routing)) {
14195
+ traceMeshEventDrop("unroutable", {
14196
+ taskId: event.meshActiveTaskId ?? event.taskId,
14197
+ sessionId: routing.sessionId,
14198
+ nodeId: routing.nodeId,
14199
+ event: event.event
14200
+ }, "no coordinator anchor / mesh_unresolved");
14201
+ }
14072
14202
  recordUnroutableDelegateEvent(routing, event.event);
14073
14203
  return;
14074
14204
  }
@@ -14098,6 +14228,7 @@ var init_mesh_events_coordinator = __esm({
14098
14228
  init_mesh_events_pending();
14099
14229
  init_mesh_routing();
14100
14230
  init_mesh_unresolved_forward_outbox();
14231
+ init_mesh_event_trace();
14101
14232
  init_snapshot();
14102
14233
  init_repo_mesh_types();
14103
14234
  init_dist();
@@ -14209,6 +14340,13 @@ function findLiveCoordinators(components) {
14209
14340
  function injectPendingIntoCoordinator(coordinator, pending) {
14210
14341
  if (!coordinator || !pending.coordinatorMessage) return;
14211
14342
  const force = shouldForceInjectMeshEvent(pending.event);
14343
+ traceMeshEventStage("surfaced", {
14344
+ taskId: pending.metadataEvent?.taskId,
14345
+ sessionId: pending.metadataEvent?.targetSessionId ?? pending.targetCoordinatorSessionId,
14346
+ nodeId: pending.nodeId,
14347
+ meshId: pending.meshId,
14348
+ event: pending.event
14349
+ }, force ? "force-inject" : "inject");
14212
14350
  coordinator.onEvent("send_message", {
14213
14351
  input: { text: pending.coordinatorMessage, textFallback: pending.coordinatorMessage },
14214
14352
  ...force ? { force: true } : {}
@@ -14267,6 +14405,13 @@ function recoverStrandedAssignedDispatches(meshId, store) {
14267
14405
  });
14268
14406
  if (reclaimed) {
14269
14407
  LOG.warn("MeshReconcile", `Reclaimed stranded assigned task ${row.id} on mesh ${meshId} (node=${row.assignedNodeId ?? "?"} session=${row.assignedSessionId ?? "?"}, dispatched ${Math.round((nowMs - dispatchedAtMs) / 1e3)}s ago, never confirmed delivered \u2192 ${reclaimed.status})`);
14408
+ traceMeshEventDrop("assigned_stranded_reclaim", {
14409
+ taskId: row.id,
14410
+ sessionId: row.assignedSessionId,
14411
+ nodeId: row.assignedNodeId,
14412
+ meshId,
14413
+ event: "agent:generating_completed"
14414
+ }, `unconfirmed ${Math.round((nowMs - dispatchedAtMs) / 1e3)}s \u2192 ${reclaimed.status}`);
14270
14415
  }
14271
14416
  }
14272
14417
  }
@@ -14426,6 +14571,13 @@ function holdOrExpireStrictUnmatchedEvent(pending, wantSession, meshId) {
14426
14571
  try {
14427
14572
  queuePendingMeshCoordinatorEvent(pending);
14428
14573
  LOG.info("MeshReconcile", `Strict route hold: coordinator session ${wantSession} not live on mesh ${meshId} \u2014 re-queued (${pending.event})`);
14574
+ traceMeshEventDrop("strict_route_hold", {
14575
+ taskId: pending.metadataEvent?.taskId,
14576
+ sessionId: pending.metadataEvent?.targetSessionId ?? wantSession,
14577
+ nodeId: pending.nodeId,
14578
+ meshId,
14579
+ event: pending.event
14580
+ }, `coordinatorSession=${wantSession} not live`);
14429
14581
  } catch (e) {
14430
14582
  LOG.warn("MeshReconcile", `Strict route re-queue failed for ${pending.event} on mesh ${meshId}: ${e?.message || e}`);
14431
14583
  }
@@ -14449,6 +14601,13 @@ function holdOrExpireStrictUnmatchedEvent(pending, wantSession, meshId) {
14449
14601
  }
14450
14602
  });
14451
14603
  LOG.warn("MeshReconcile", `Strict route expire: coordinator session ${wantSession} never returned for mesh ${meshId} \u2014 recorded to ledger (recoverable), dropped (${pending.event})`);
14604
+ traceMeshEventDrop("strict_route_expired", {
14605
+ taskId: pending.metadataEvent?.taskId,
14606
+ sessionId: pending.metadataEvent?.targetSessionId ?? wantSession,
14607
+ nodeId: pending.nodeId,
14608
+ meshId,
14609
+ event: pending.event
14610
+ }, `coordinatorSession=${wantSession} never returned`);
14452
14611
  } catch (e) {
14453
14612
  LOG.warn("MeshReconcile", `Failed to ledger-expire strict-unmatched ${pending.event} for mesh ${meshId}: ${e?.message || e}`);
14454
14613
  }
@@ -14460,15 +14619,24 @@ async function retryUnresolvedDelegateForwards(components) {
14460
14619
  const entries = peekUnresolvedDelegateForwards();
14461
14620
  if (entries.length === 0) return;
14462
14621
  for (const entry of entries) {
14622
+ const entryTraceCtx = {
14623
+ taskId: entry.payload.taskId,
14624
+ sessionId: readNonEmptyString2(entry.payload.targetSessionId) || readNonEmptyString2(entry.payload.sessionId),
14625
+ nodeId: readNonEmptyString2(entry.payload.nodeId),
14626
+ event: readNonEmptyString2(entry.payload.event)
14627
+ };
14463
14628
  let result;
14464
14629
  try {
14630
+ traceMeshEventStage("forward_send", entryTraceCtx, `retry \u2192 ${entry.coordinatorDaemonId}`);
14465
14631
  result = await dispatchMeshCommand(entry.coordinatorDaemonId, "mesh_forward_event", entry.payload);
14466
14632
  } catch (e) {
14467
14633
  LOG.warn("MeshReconcile", `Retry forward to coordinator ${entry.coordinatorDaemonId} failed: ${e?.message || e} \u2014 left queued`);
14634
+ traceMeshEventDrop("retry_forward_failed", entryTraceCtx, e?.message || String(e));
14468
14635
  continue;
14469
14636
  }
14470
14637
  if (result && result.success === false) {
14471
14638
  LOG.warn("MeshReconcile", `Retry forward to coordinator ${entry.coordinatorDaemonId} rejected (${readNonEmptyString2(result.error) || "no reason"}) \u2014 left queued`);
14639
+ traceMeshEventDrop("retry_forward_rejected", entryTraceCtx, readNonEmptyString2(result.error) || "no reason");
14472
14640
  continue;
14473
14641
  }
14474
14642
  ackUnresolvedDelegateForward(entry.id);
@@ -14710,6 +14878,7 @@ var init_mesh_reconcile_loop = __esm({
14710
14878
  init_mesh_events_coordinator();
14711
14879
  init_mesh_unresolved_forward_outbox();
14712
14880
  init_mesh_events_utils();
14881
+ init_mesh_event_trace();
14713
14882
  init_dist();
14714
14883
  init_mesh_work_queue();
14715
14884
  init_mesh_ledger();
@@ -15548,8 +15717,8 @@ function saveProvidersActive(file) {
15548
15717
  }
15549
15718
  function isValidSource(x) {
15550
15719
  if (!x || typeof x !== "object") return false;
15551
- const s = x;
15552
- return typeof s.name === "string" && s.name.length > 0 && typeof s.url === "string" && s.url.length > 0 && typeof s.ref === "string" && s.ref.length > 0 && typeof s.addedAt === "string";
15720
+ const s2 = x;
15721
+ return typeof s2.name === "string" && s2.name.length > 0 && typeof s2.url === "string" && s2.url.length > 0 && typeof s2.ref === "string" && s2.ref.length > 0 && typeof s2.addedAt === "string";
15553
15722
  }
15554
15723
  function deriveSourceName(url) {
15555
15724
  const m = url.match(/[/:]([^/:]+)\/([^/]+?)(?:\.git)?$/);
@@ -15605,7 +15774,7 @@ function inventoryExternalSources() {
15605
15774
  }
15606
15775
  function sourcesProviding(category, type) {
15607
15776
  const inventory = inventoryExternalSources();
15608
- return inventory.filter((s) => (s.providers[category] || []).includes(type)).map((s) => s.sourceName);
15777
+ return inventory.filter((s2) => (s2.providers[category] || []).includes(type)).map((s2) => s2.sourceName);
15609
15778
  }
15610
15779
  function resolveActiveSource(category, type, activeFile) {
15611
15780
  const candidates = sourcesProviding(category, type);
@@ -15809,10 +15978,10 @@ function compileSettledPromptMatchers(spec) {
15809
15978
  const footers = (spec.withFooter ?? []).map((f) => {
15810
15979
  if (f.kind === "regex") {
15811
15980
  const re = compile2(f.pattern, f.flags ?? "i");
15812
- return { test: (s) => re.test(s) };
15981
+ return { test: (s2) => re.test(s2) };
15813
15982
  }
15814
15983
  const needle = f.pattern.toLowerCase();
15815
- return { test: (s) => s.toLowerCase().includes(needle) };
15984
+ return { test: (s2) => s2.toLowerCase().includes(needle) };
15816
15985
  });
15817
15986
  return { prompt, footers };
15818
15987
  }
@@ -16686,7 +16855,7 @@ var init_cli_state_engine = __esm({
16686
16855
  }
16687
16856
  resolveModal(buttonIndex) {
16688
16857
  const snap = this.transport.getSnapshot();
16689
- const parseApproval = typeof this.transport.runParseApproval === "function" ? (s) => this.transport.runParseApproval(s.recentOutputBuffer.slice(-500)) : (s) => this.runParseApproval(s);
16858
+ const parseApproval = typeof this.transport.runParseApproval === "function" ? (s2) => this.transport.runParseApproval(s2.recentOutputBuffer.slice(-500)) : (s2) => this.runParseApproval(s2);
16690
16859
  let modal = this.activeModal ?? parseApproval(snap);
16691
16860
  if (!modal && this.runner.hasParseSession()) {
16692
16861
  try {
@@ -19374,22 +19543,23 @@ function resolveSections(sectionsObj, lines) {
19374
19543
  const matchesCandidate = (c, i) => c.re.test(lines[i]) && (c.prevRe === null || i > 0 && c.prevRe.test(lines[i - 1])) && (c.nextRe === null || i < total - 1 && c.nextRe.test(lines[i + 1]));
19375
19544
  let idx = -1;
19376
19545
  for (const c of candidates) {
19546
+ let candIdx = -1;
19377
19547
  if (sec.anchor_last) {
19378
19548
  for (let i = total - 1; i >= 0; i--) {
19379
19549
  if (matchesCandidate(c, i)) {
19380
- idx = i;
19550
+ candIdx = i;
19381
19551
  break;
19382
19552
  }
19383
19553
  }
19384
19554
  } else {
19385
19555
  for (let i = 0; i < total; i++) {
19386
19556
  if (matchesCandidate(c, i)) {
19387
- idx = i;
19557
+ candIdx = i;
19388
19558
  break;
19389
19559
  }
19390
19560
  }
19391
19561
  }
19392
- if (idx !== -1) break;
19562
+ if (candIdx !== -1 && (idx === -1 || candIdx < idx)) idx = candIdx;
19393
19563
  }
19394
19564
  if (idx !== -1) {
19395
19565
  from = idx;
@@ -19441,7 +19611,7 @@ function resolveSections(sectionsObj, lines) {
19441
19611
  }
19442
19612
  function sectionText(sections, sectionId, fullScreen) {
19443
19613
  if (!sectionId) return fullScreen;
19444
- const found = sections.find((s) => s.id === sectionId);
19614
+ const found = sections.find((s2) => s2.id === sectionId);
19445
19615
  return found ? found.text : "";
19446
19616
  }
19447
19617
  function isRegexCondition(c) {
@@ -19569,6 +19739,7 @@ function extractButtonsFromRule(rule, hay) {
19569
19739
  const idx = Number(m[1]);
19570
19740
  let label = String(m[2] ?? "").trim();
19571
19741
  if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
19742
+ const current = hasCursorMarker(lines[i]);
19572
19743
  let j = i + 1;
19573
19744
  while (j < lines.length) {
19574
19745
  const next = lines[j];
@@ -19580,7 +19751,7 @@ function extractButtonsFromRule(rule, hay) {
19580
19751
  }
19581
19752
  if (buttons.some((b) => b.index === idx)) continue;
19582
19753
  const key = keyTemplate.replace(/\{index\}/g, String(idx));
19583
- buttons.push({ index: idx, label, key });
19754
+ buttons.push({ index: idx, label, key, current });
19584
19755
  i = j - 1;
19585
19756
  }
19586
19757
  } else {
@@ -19592,12 +19763,15 @@ function extractButtonsFromRule(rule, hay) {
19592
19763
  if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
19593
19764
  if (buttons.some((b) => b.index === idx)) continue;
19594
19765
  const key = keyTemplate.replace(/\{index\}/g, String(idx));
19595
- buttons.push({ index: idx, label, key });
19766
+ buttons.push({ index: idx, label, key, current: hasCursorMarker(m[0]) });
19596
19767
  }
19597
19768
  }
19598
19769
  buttons.sort((a, b) => a.index - b.index);
19599
19770
  return buttons;
19600
19771
  }
19772
+ function hasCursorMarker(text) {
19773
+ return /^\s*[❯›>]/.test(text);
19774
+ }
19601
19775
  var init_evaluator = __esm({
19602
19776
  "src/providers/spec/evaluator.ts"() {
19603
19777
  "use strict";
@@ -19609,10 +19783,10 @@ function isV4Spec(raw) {
19609
19783
  return !!raw && typeof raw === "object" && raw.$schema === "adhdev:cli/spec@4";
19610
19784
  }
19611
19785
  function initialState(spec) {
19612
- return spec.states.find((s) => s.initial) ?? spec.states[0];
19786
+ return spec.states.find((s2) => s2.initial) ?? spec.states[0];
19613
19787
  }
19614
19788
  function stateById(spec, id) {
19615
- return spec.states.find((s) => s.id === id);
19789
+ return spec.states.find((s2) => s2.id === id);
19616
19790
  }
19617
19791
  function outgoingTransitions(spec, stateId) {
19618
19792
  const matches = spec.transitions.filter((t) => {
@@ -19701,7 +19875,17 @@ function evalCond(cond, sections, fullScreen, cursor, prevLines, clock, legacyTr
19701
19875
  const result = evaluateCondition(cond, sections, fullScreen, cursor, prevLines, legacyTrace, stateId);
19702
19876
  const kind = isRegex(cond) ? "regex" : "changed";
19703
19877
  const detail = isRegex(cond) ? `${cond.section ?? "*"}~/${cond.matches}/` : `cursor_above=${cond.cursor_above} changed=${cond.changed}`;
19704
- return { kind, result, detail };
19878
+ let matchedText;
19879
+ if (result && isRegex(cond)) {
19880
+ try {
19881
+ const hay = sectionText(sections, cond.section, fullScreen);
19882
+ const re = new RegExp(cond.matches, cond.flags ?? "i");
19883
+ const m = re.exec(hay);
19884
+ if (m && m[0]) matchedText = m[0].replace(/\s+/g, " ").trim().slice(0, 160);
19885
+ } catch {
19886
+ }
19887
+ }
19888
+ return matchedText ? { kind, result, detail, matchedText } : { kind, result, detail };
19705
19889
  }
19706
19890
  return { kind: "all", result: false, detail: "unknown condition" };
19707
19891
  }
@@ -19817,17 +20001,17 @@ function validateFsmSpec(raw) {
19817
20001
  }
19818
20002
  const ids = /* @__PURE__ */ new Set();
19819
20003
  let initialCount = 0;
19820
- for (const [i, s] of spec.states.entries()) {
19821
- if (!s.id) {
20004
+ for (const [i, s2] of spec.states.entries()) {
20005
+ if (!s2.id) {
19822
20006
  errs.push(`states[${i}].id is required`);
19823
20007
  continue;
19824
20008
  }
19825
- if (ids.has(s.id)) errs.push(`states[${i}].id "${s.id}" is duplicated`);
19826
- ids.add(s.id);
19827
- if (!s.label) errs.push(`states[${i}].label is required`);
19828
- if (s.initial) initialCount += 1;
19829
- if (s.status && !["idle", "generating", "approval"].includes(s.status)) {
19830
- errs.push(`states[${i}].status "${s.status}" must be idle|generating|approval`);
20009
+ if (ids.has(s2.id)) errs.push(`states[${i}].id "${s2.id}" is duplicated`);
20010
+ ids.add(s2.id);
20011
+ if (!s2.label) errs.push(`states[${i}].label is required`);
20012
+ if (s2.initial) initialCount += 1;
20013
+ if (s2.status && !["idle", "generating", "approval"].includes(s2.status)) {
20014
+ errs.push(`states[${i}].status "${s2.status}" must be idle|generating|approval`);
19831
20015
  }
19832
20016
  }
19833
20017
  if (initialCount === 0) errs.push("exactly one state must have initial:true (none found)");
@@ -19843,10 +20027,10 @@ function validateFsmSpec(raw) {
19843
20027
  else if (!ids.has(t.to)) errs.push(`transitions[${i}].to references unknown state "${t.to}"`);
19844
20028
  if (t.when) errs.push(...validateCondition(t.when, sectionIds, `transitions[${i}].when`));
19845
20029
  }
19846
- for (const [i, s] of spec.states.entries()) {
19847
- const sec = s.extract?.title?.section;
20030
+ for (const [i, s2] of spec.states.entries()) {
20031
+ const sec = s2.extract?.title?.section;
19848
20032
  if (sec && !sectionIds.has(sec)) errs.push(`states[${i}].extract.title.section "${sec}" unknown`);
19849
- const bsec = s.extract?.buttons?.section;
20033
+ const bsec = s2.extract?.buttons?.section;
19850
20034
  if (bsec && !sectionIds.has(bsec)) errs.push(`states[${i}].extract.buttons.section "${bsec}" unknown`);
19851
20035
  }
19852
20036
  return errs;
@@ -32218,10 +32402,10 @@ ${formatManifestValidationIssues2(validation.issues)}`,
32218
32402
  const path42 = __require("path");
32219
32403
  const { spawnSync: spawnSync2 } = __require("child_process");
32220
32404
  const file = ext.loadExternalSources();
32221
- if (file.sources.some((s) => s.name === requestedName)) {
32405
+ if (file.sources.some((s2) => s2.name === requestedName)) {
32222
32406
  return { success: false, error: `source name "${requestedName}" is already registered` };
32223
32407
  }
32224
- if (file.sources.some((s) => s.url === url && s.ref === ref)) {
32408
+ if (file.sources.some((s2) => s2.url === url && s2.ref === ref)) {
32225
32409
  return { success: false, error: `source url+ref already registered (use a different name to track another ref)` };
32226
32410
  }
32227
32411
  const sourceDir = path42.join(ext.externalRoot(), requestedName);
@@ -32283,7 +32467,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
32283
32467
  const fs32 = __require("fs");
32284
32468
  const path42 = __require("path");
32285
32469
  const file = ext.loadExternalSources();
32286
- const match = file.sources.find((s) => s.name === name);
32470
+ const match = file.sources.find((s2) => s2.name === name);
32287
32471
  if (!match) return { success: false, error: `source "${name}" not registered` };
32288
32472
  const sourceDir = path42.join(ext.externalRoot(), name);
32289
32473
  if (fs32.existsSync(sourceDir)) {
@@ -32295,7 +32479,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
32295
32479
  }
32296
32480
  ext.saveExternalSources({
32297
32481
  schema: 1,
32298
- sources: file.sources.filter((s) => s.name !== name)
32482
+ sources: file.sources.filter((s2) => s2.name !== name)
32299
32483
  });
32300
32484
  const active = ext.loadProvidersActive();
32301
32485
  const filteredActive = {};
@@ -32319,10 +32503,10 @@ ${formatManifestValidationIssues2(validation.issues)}`,
32319
32503
  const file = ext.loadExternalSources();
32320
32504
  const inventory = ext.inventoryExternalSources();
32321
32505
  const active = ext.loadProvidersActive();
32322
- const sources = file.sources.map((s) => {
32323
- const inv = inventory.find((e) => e.sourceName === s.name);
32506
+ const sources = file.sources.map((s2) => {
32507
+ const inv = inventory.find((e) => e.sourceName === s2.name);
32324
32508
  return {
32325
- ...s,
32509
+ ...s2,
32326
32510
  providers: inv?.providers ?? {}
32327
32511
  };
32328
32512
  });
@@ -32499,6 +32683,21 @@ import * as path21 from "path";
32499
32683
  // src/providers/spec/adapter.ts
32500
32684
  init_terminal_screen();
32501
32685
  import { DEFAULT_SESSION_HOST_COLS as DEFAULT_SESSION_HOST_COLS5, DEFAULT_SESSION_HOST_ROWS as DEFAULT_SESSION_HOST_ROWS5 } from "@adhdev/session-host-core";
32686
+ var MAX_PTY_EVENTS = 300;
32687
+ var EVENT_CONTENT_CAP = 240;
32688
+ function escapeControl(text) {
32689
+ return String(text).replace(/[\x00-\x1f\x7f]/g, (ch) => {
32690
+ const code = ch.charCodeAt(0);
32691
+ if (ch === "\r") return "\\r";
32692
+ if (ch === "\n") return "\\n";
32693
+ if (ch === " ") return "\\t";
32694
+ if (code === 27) return "\\x1b";
32695
+ return "\\x" + code.toString(16).padStart(2, "0");
32696
+ });
32697
+ }
32698
+ function capPreview(text) {
32699
+ return text.length > EVENT_CONTENT_CAP ? text.slice(0, EVENT_CONTENT_CAP) + `\u2026(+${text.length - EVENT_CONTENT_CAP})` : text;
32700
+ }
32502
32701
  var TerminalAdapter = class {
32503
32702
  constructor(opts, handlers) {
32504
32703
  this.opts = opts;
@@ -32525,6 +32724,9 @@ var TerminalAdapter = class {
32525
32724
  screenTimer = null;
32526
32725
  tickTimer = null;
32527
32726
  lastScreen = "";
32727
+ /** Debug-only ring buffer of PTY input/output/resize/cursor events. */
32728
+ events = [];
32729
+ lastCursorKey = "";
32528
32730
  start() {
32529
32731
  const env = this.opts.envIsComplete ? this.opts.env ?? {} : { ...process.env, ...this.opts.env ?? {} };
32530
32732
  this.pty = this.factory.spawn(this.opts.binary, this.opts.args ?? [], {
@@ -32533,10 +32735,12 @@ var TerminalAdapter = class {
32533
32735
  cols: this.cols,
32534
32736
  rows: this.rows
32535
32737
  });
32738
+ this.recordEvent("spawn", `${this.opts.binary} (${this.cols}x${this.rows})`);
32536
32739
  this.handlers.init?.({ pid: this.pty.pid });
32537
32740
  this.pty.onData((chunk) => this.onChunk(chunk));
32538
32741
  this.pty.onExit((info) => {
32539
32742
  this.stopTimers();
32743
+ this.recordEvent("exit", `exitCode=${typeof info.exitCode === "number" ? info.exitCode : 0}`);
32540
32744
  this.handlers.on_exit?.({ exitCode: typeof info.exitCode === "number" ? info.exitCode : 0 });
32541
32745
  this.pty = null;
32542
32746
  });
@@ -32547,6 +32751,7 @@ var TerminalAdapter = class {
32547
32751
  resize(cols, rows) {
32548
32752
  this.cols = cols;
32549
32753
  this.rows = rows;
32754
+ this.recordEvent("resize", `${cols}x${rows}`);
32550
32755
  this.pty?.resize(cols, rows);
32551
32756
  this.screen.resize(rows, cols);
32552
32757
  }
@@ -32567,8 +32772,21 @@ var TerminalAdapter = class {
32567
32772
  return { row: pos.row, col: pos.col };
32568
32773
  }
32569
32774
  send_keys(text) {
32775
+ this.recordEvent("input", capPreview(escapeControl(text)), text.length);
32570
32776
  this.pty?.write(text);
32571
32777
  }
32778
+ /** Debug-only: most-recent PTY input/output/resize/cursor events, oldest
32779
+ * first. Pure observation — never consulted by the FSM. */
32780
+ getEventTimeline(limit = MAX_PTY_EVENTS) {
32781
+ const n = Math.max(0, Math.min(limit, this.events.length));
32782
+ return this.events.slice(this.events.length - n);
32783
+ }
32784
+ recordEvent(kind, content, bytes) {
32785
+ const ev = { ts: Date.now(), kind, content };
32786
+ if (typeof bytes === "number") ev.bytes = bytes;
32787
+ this.events.push(ev);
32788
+ if (this.events.length > MAX_PTY_EVENTS) this.events.splice(0, this.events.length - MAX_PTY_EVENTS);
32789
+ }
32572
32790
  kill() {
32573
32791
  this.stopTimers();
32574
32792
  try {
@@ -32579,6 +32797,7 @@ var TerminalAdapter = class {
32579
32797
  this.screen.dispose();
32580
32798
  }
32581
32799
  onChunk(chunk) {
32800
+ this.recordEvent("output", capPreview(escapeControl(chunk)), chunk.length);
32582
32801
  try {
32583
32802
  this.handlers.on_pty_data?.(chunk);
32584
32803
  } catch {
@@ -32588,6 +32807,12 @@ var TerminalAdapter = class {
32588
32807
  this.screenTimer = setTimeout(() => {
32589
32808
  this.screenTimer = null;
32590
32809
  const snap = this.computeScreen();
32810
+ const cur = this.screen.getCursorPosition();
32811
+ const curKey = `${cur.row},${cur.col}`;
32812
+ if (curKey !== this.lastCursorKey) {
32813
+ this.lastCursorKey = curKey;
32814
+ this.recordEvent("cursor", `(${cur.row},${cur.col})`);
32815
+ }
32591
32816
  if (snap === this.lastScreen) return;
32592
32817
  this.lastScreen = snap;
32593
32818
  try {
@@ -32672,20 +32897,40 @@ function applyPreLaunchTrust(trust, workingDir) {
32672
32897
 
32673
32898
  // src/providers/spec/fsm-driver.ts
32674
32899
  init_logger();
32675
- function countNewlines(s) {
32900
+ function countNewlines(s2) {
32676
32901
  let n = 0;
32677
- for (let i = 0; i < s.length; i += 1) if (s.charCodeAt(i) === 10) n += 1;
32902
+ for (let i = 0; i < s2.length; i += 1) if (s2.charCodeAt(i) === 10) n += 1;
32678
32903
  return n;
32679
32904
  }
32680
32905
  var SUBMIT_DELAY_FLOOR_MS = 200;
32681
32906
  var WIN32_SUBMIT_RESEND_GAP_MS = 350;
32682
32907
  var WIN32_SUBMIT_MAX_RESENDS = 14;
32908
+ var WIN32_SUBMIT_SETTLE_MS = 500;
32909
+ var WIN32_SUBMIT_MAX_SETTLE_WAIT_MS = 1e4;
32910
+ var WIN32_SUBMIT_SETTLE_POLL_MS = 120;
32911
+ var WIN32_PTY_WRITE_CHUNK_CHARS = 1024;
32912
+ var WIN32_PTY_WRITE_CHUNK_GAP_MS = 8;
32683
32913
  function resolveSubmitDelayMs(specBeforeSubmit, text) {
32684
32914
  const lines = countNewlines(text);
32685
32915
  const linesBonus = Math.min(800, lines * 80);
32686
32916
  const spec = typeof specBeforeSubmit === "number" && specBeforeSubmit > 0 ? specBeforeSubmit : 0;
32687
32917
  return Math.max(spec, SUBMIT_DELAY_FLOOR_MS + linesBonus);
32688
32918
  }
32919
+ function chunkPreservingSurrogates(text, size) {
32920
+ const chunks = [];
32921
+ let offset = 0;
32922
+ while (offset < text.length) {
32923
+ let end = Math.min(text.length, offset + size);
32924
+ if (end < text.length) {
32925
+ const code = text.charCodeAt(end - 1);
32926
+ if (code >= 55296 && code <= 56319) end -= 1;
32927
+ }
32928
+ if (end <= offset) end = Math.min(text.length, offset + size);
32929
+ chunks.push(text.slice(offset, end));
32930
+ offset = end;
32931
+ }
32932
+ return chunks;
32933
+ }
32689
32934
  function guessExt(mime) {
32690
32935
  if (/png/i.test(mime)) return ".png";
32691
32936
  if (/jpe?g/i.test(mime)) return ".jpg";
@@ -32701,7 +32946,10 @@ var FsmDriver = class {
32701
32946
  this.buildAdapterOpts(),
32702
32947
  {
32703
32948
  init: () => this.emitInitialState(),
32704
- on_pty_data: (chunk) => this.emit({ kind: "pty_data", chunk }),
32949
+ on_pty_data: (chunk) => {
32950
+ this.lastPtyDataAt = Date.now();
32951
+ this.emit({ kind: "pty_data", chunk });
32952
+ },
32705
32953
  on_screen_changed: () => this.reevaluate(),
32706
32954
  on_exit: ({ exitCode }) => this.handleExit(exitCode)
32707
32955
  }
@@ -32735,6 +32983,16 @@ var FsmDriver = class {
32735
32983
  * WIN32_SUBMIT_* and scheduleWin32Submit). Re-arms itself until the FSM
32736
32984
  * leaves idle (submitted) or the resend budget is spent. */
32737
32985
  win32SubmitTimer = null;
32986
+ /** Wall-clock (ms) of the most recent raw PTY output chunk. Advances on every
32987
+ * on_pty_data — including the echo of text written into the composer — so the
32988
+ * win32 submit settle-gate can tell when input has finished landing. */
32989
+ lastPtyDataAt = 0;
32990
+ /** Wall-clock (ms) of the most recent win32 message-body input write. Bridges
32991
+ * the gap between writing a chunk and its echo so the settle-gate does not
32992
+ * declare "quiet" mid-write. */
32993
+ lastWin32WriteAt = 0;
32994
+ /** Pending paced chunk-write timer for a large win32 body (see writeWin32Body). */
32995
+ win32WriteTimer = null;
32738
32996
  currentEval = null;
32739
32997
  stateHistory = [];
32740
32998
  prevStateAt = 0;
@@ -32855,6 +33113,10 @@ var FsmDriver = class {
32855
33113
  clearTimeout(this.win32SubmitTimer);
32856
33114
  this.win32SubmitTimer = null;
32857
33115
  }
33116
+ if (this.win32WriteTimer) {
33117
+ clearTimeout(this.win32WriteTimer);
33118
+ this.win32WriteTimer = null;
33119
+ }
32858
33120
  this.specWatcher?.close();
32859
33121
  this.adapter.kill();
32860
33122
  }
@@ -32885,11 +33147,15 @@ var FsmDriver = class {
32885
33147
  getFsmSnapshotHistory() {
32886
33148
  return this.fsmSnapshotHistory;
32887
33149
  }
33150
+ /** Debug-only PTY input/output/resize/cursor timeline from the adapter. */
33151
+ getEventTimeline(limit) {
33152
+ return this.adapter.getEventTimeline(limit);
33153
+ }
32888
33154
  getSections() {
32889
33155
  try {
32890
33156
  const screen = this.adapter.snapshot();
32891
33157
  const lines = screen.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
32892
- return resolveSections(this.spec.sections ?? {}, lines).map((s) => ({ id: s.id, text: s.text }));
33158
+ return resolveSections(this.spec.sections ?? {}, lines).map((s2) => ({ id: s2.id, text: s2.text }));
32893
33159
  } catch {
32894
33160
  return null;
32895
33161
  }
@@ -33267,7 +33533,7 @@ var FsmDriver = class {
33267
33533
  const perChar = sm.delay_ms_per_char ?? 0;
33268
33534
  const beforeSubmit = resolveSubmitDelayMs(sm.delay_ms_before_submit, text);
33269
33535
  if (process.platform === "win32") {
33270
- this.adapter.send_keys(text);
33536
+ this.writeWin32Body(text);
33271
33537
  this.scheduleWin32Submit(sm.submit_key, beforeSubmit);
33272
33538
  return;
33273
33539
  }
@@ -33293,20 +33559,72 @@ var FsmDriver = class {
33293
33559
  const st = stateById(this.spec, this.currentStateId);
33294
33560
  return st ? statusForState(st) : "idle";
33295
33561
  }
33562
+ /** Record a win32 body write so the settle-gate counts it as input activity
33563
+ * even before the echo arrives. */
33564
+ markWin32Write() {
33565
+ this.lastWin32WriteAt = Date.now();
33566
+ }
33567
+ /** Most recent win32 input activity — a write we issued OR a PTY output chunk
33568
+ * (echo). The submit settle-gate waits for this to go quiet. */
33569
+ lastWin32InputActivityAt() {
33570
+ return Math.max(this.lastPtyDataAt, this.lastWin32WriteAt);
33571
+ }
33296
33572
  /**
33297
- * win32 verification-based submit. Sends the submit key, waits a gap, and if
33298
- * the FSM is still 'idle' (the prompt did not submit — the CR was absorbed as
33299
- * a multiline-paste newline) resends, up to WIN32_SUBMIT_MAX_RESENDS. The
33300
- * first CR always fires (so a stale/edge status never suppresses the submit);
33301
- * subsequent resends are gated on still being idle, and stop the instant the
33302
- * agent leaves idle (submitted generating / approval). This converges the
33303
- * nondeterministic multiline window without spamming Enter into the next turn.
33573
+ * Write the message body to the PTY for win32, paced into bounded chunks. A
33574
+ * single unbounded ConPTY write can overflow the input pipe and drop leading
33575
+ * bytes; splitting it with a short inter-chunk gap keeps the console input
33576
+ * buffer from overflowing. Small bodies still go out in a single write. Each
33577
+ * chunk advances lastWin32WriteAt so the submit settle-gate keeps waiting until
33578
+ * the final chunk is out and echoed.
33579
+ */
33580
+ writeWin32Body(text) {
33581
+ if (this.win32WriteTimer) {
33582
+ clearTimeout(this.win32WriteTimer);
33583
+ this.win32WriteTimer = null;
33584
+ }
33585
+ if (text.length <= WIN32_PTY_WRITE_CHUNK_CHARS) {
33586
+ this.markWin32Write();
33587
+ this.adapter.send_keys(text);
33588
+ return;
33589
+ }
33590
+ const chunks = chunkPreservingSurrogates(text, WIN32_PTY_WRITE_CHUNK_CHARS);
33591
+ let idx = 0;
33592
+ const writeNext = () => {
33593
+ this.win32WriteTimer = null;
33594
+ if (idx >= chunks.length) return;
33595
+ this.markWin32Write();
33596
+ this.adapter.send_keys(chunks[idx]);
33597
+ idx += 1;
33598
+ if (idx < chunks.length) {
33599
+ this.win32WriteTimer = setTimeout(writeNext, WIN32_PTY_WRITE_CHUNK_GAP_MS);
33600
+ }
33601
+ };
33602
+ writeNext();
33603
+ }
33604
+ /**
33605
+ * win32 submit. Two phases:
33606
+ *
33607
+ * Phase 1 (settle-gate): hold the first CR until the PTY output has been quiet
33608
+ * for WIN32_SUBMIT_SETTLE_MS after the last input write — i.e. the full
33609
+ * (possibly multi-KB / multiline) body has finished arriving in the composer
33610
+ * and echoing. Honors an initial minimum delay and is bounded by
33611
+ * WIN32_SUBMIT_MAX_SETTLE_WAIT_MS so a noisy screen can never hang the submit.
33612
+ * This is what stops a long message from being submitted half-arrived (its
33613
+ * leading lines lost). A short message settles almost immediately.
33614
+ *
33615
+ * Phase 2 (verified resend — unchanged): send the submit key, wait a gap, and
33616
+ * if the FSM is still 'idle' (the CR was absorbed as a multiline-paste
33617
+ * newline) resend, up to WIN32_SUBMIT_MAX_RESENDS. The first CR always fires
33618
+ * (a stale/edge status never suppresses it); resends are gated on still being
33619
+ * idle and stop the instant the agent leaves idle (submitted → generating /
33620
+ * approval). This preserves the win32 lone-CR-swallow handling.
33304
33621
  */
33305
33622
  scheduleWin32Submit(submitKey, initialDelayMs) {
33306
33623
  if (this.win32SubmitTimer) {
33307
33624
  clearTimeout(this.win32SubmitTimer);
33308
33625
  this.win32SubmitTimer = null;
33309
33626
  }
33627
+ const startedAt = Date.now();
33310
33628
  const fire = (attempt) => {
33311
33629
  this.win32SubmitTimer = null;
33312
33630
  this.adapter.send_keys(submitKey);
@@ -33319,8 +33637,20 @@ var FsmDriver = class {
33319
33637
  fire(attempt + 1);
33320
33638
  }, WIN32_SUBMIT_RESEND_GAP_MS);
33321
33639
  };
33322
- if (initialDelayMs > 0) this.win32SubmitTimer = setTimeout(() => fire(0), initialDelayMs);
33323
- else fire(0);
33640
+ const waitForSettle = () => {
33641
+ this.win32SubmitTimer = null;
33642
+ const now = Date.now();
33643
+ const quietFor = now - this.lastWin32InputActivityAt();
33644
+ const waited = now - startedAt;
33645
+ if (quietFor >= WIN32_SUBMIT_SETTLE_MS || waited >= WIN32_SUBMIT_MAX_SETTLE_WAIT_MS) {
33646
+ fire(0);
33647
+ return;
33648
+ }
33649
+ const recheckIn = Math.min(WIN32_SUBMIT_SETTLE_MS - quietFor, WIN32_SUBMIT_SETTLE_POLL_MS);
33650
+ this.win32SubmitTimer = setTimeout(waitForSettle, Math.max(recheckIn, 30));
33651
+ };
33652
+ if (initialDelayMs > 0) this.win32SubmitTimer = setTimeout(waitForSettle, initialDelayMs);
33653
+ else waitForSettle();
33324
33654
  }
33325
33655
  handleClickControl(controlId, payload) {
33326
33656
  const ctl = (this.spec.control_bar ?? []).find((c) => c.id === controlId);
@@ -33355,6 +33685,19 @@ var FsmDriver = class {
33355
33685
  if (!m) return;
33356
33686
  const btn = m.buttons.find((b) => b.index === index);
33357
33687
  if (!btn) return;
33688
+ const rule = stateById(this.spec, this.currentStateId)?.extract?.buttons;
33689
+ if (rule?.select_mode === "arrow_keys") {
33690
+ const from = m.buttons.find((b) => b.current)?.index ?? 1;
33691
+ const up = rule.cursor_keys?.up ?? "\x1B[A";
33692
+ const down = rule.cursor_keys?.down ?? "\x1B[B";
33693
+ const delta = btn.index - from;
33694
+ const step = delta >= 0 ? down : up;
33695
+ const nav = step.repeat(Math.abs(delta));
33696
+ const confirm = (rule.key_for_index || "\r").replace(/\{index\}/g, "") || "\r";
33697
+ if (nav) this.adapter.send_keys(nav);
33698
+ this.adapter.send_keys(confirm);
33699
+ return;
33700
+ }
33358
33701
  this.adapter.send_keys(btn.key);
33359
33702
  }
33360
33703
  handleAttachImage(blob, mime) {
@@ -33433,7 +33776,8 @@ function summarizeTransition(t) {
33433
33776
  return out;
33434
33777
  }
33435
33778
  function flattenCond(c, out, depth) {
33436
- out.push(`${" ".repeat(depth)}${c.kind} ${c.detail} = ${c.result}${c.remainingMs ? ` (${c.remainingMs}ms left)` : ""}`);
33779
+ const matched = c.matchedText ? ` matched=${JSON.stringify(c.matchedText)}` : "";
33780
+ out.push(`${" ".repeat(depth)}${c.kind} ${c.detail} = ${c.result}${c.remainingMs ? ` (${c.remainingMs}ms left)` : ""}${matched}`);
33437
33781
  for (const child of c.children ?? []) flattenCond(child, out, depth + 1);
33438
33782
  }
33439
33783
  function findStable(c) {
@@ -34151,8 +34495,8 @@ function projectToolBlock(block2, role, tmap) {
34151
34495
  }
34152
34496
  return null;
34153
34497
  }
34154
- function oneLine(s, max) {
34155
- const flat = s.replace(/\s+/g, " ").trim();
34498
+ function oneLine(s2, max) {
34499
+ const flat = s2.replace(/\s+/g, " ").trim();
34156
34500
  return flat.length > max ? flat.slice(0, max - 1) + "\u2026" : flat;
34157
34501
  }
34158
34502
  function parseTimestamp(v) {
@@ -34172,10 +34516,10 @@ function parseTimestamp(v) {
34172
34516
  return null;
34173
34517
  }
34174
34518
  function normalizeRole(r) {
34175
- const s = String(r ?? "").toLowerCase();
34176
- if (s === "user" || s === "human" || s === "user_explicit") return "user";
34177
- if (s === "assistant" || s === "ai" || s === "model") return "assistant";
34178
- if (s === "tool" || s === "tool_result" || s === "function") return "assistant";
34519
+ const s2 = String(r ?? "").toLowerCase();
34520
+ if (s2 === "user" || s2 === "human" || s2 === "user_explicit") return "user";
34521
+ if (s2 === "assistant" || s2 === "ai" || s2 === "model") return "assistant";
34522
+ if (s2 === "tool" || s2 === "tool_result" || s2 === "function") return "assistant";
34179
34523
  return "system";
34180
34524
  }
34181
34525
  function stringifyContent(v) {
@@ -34223,18 +34567,18 @@ function compileWhere(src) {
34223
34567
  return (record) => ors.some((ands) => ands.every((t) => evalTerm(t, record)));
34224
34568
  }
34225
34569
  function parseTerm(src) {
34226
- let s = src.trim();
34570
+ let s2 = src.trim();
34227
34571
  let negate = false;
34228
- if (s.startsWith("!")) {
34572
+ if (s2.startsWith("!")) {
34229
34573
  negate = true;
34230
- s = s.slice(1).trim();
34574
+ s2 = s2.slice(1).trim();
34231
34575
  }
34232
- const fnMatch = s.match(/^(startsWith|endsWith|contains)\s*\(\s*(.+?)\s*,\s*(.+?)\s*\)$/);
34576
+ const fnMatch = s2.match(/^(startsWith|endsWith|contains)\s*\(\s*(.+?)\s*,\s*(.+?)\s*\)$/);
34233
34577
  if (fnMatch) {
34234
34578
  const [, op2, pathExpr, litExpr] = fnMatch;
34235
34579
  return { path: pathExpr, op: op2, lit: parseLiteral(litExpr), negate };
34236
34580
  }
34237
- const opMatch = s.match(/^(.+?)\s*(==|!=|>=|<=|>|<)\s*(.+)$/);
34581
+ const opMatch = s2.match(/^(.+?)\s*(==|!=|>=|<=|>|<)\s*(.+)$/);
34238
34582
  if (!opMatch) return null;
34239
34583
  const [, lhs, op, rhsRaw] = opMatch;
34240
34584
  return { path: lhs.trim(), op, lit: parseLiteral(rhsRaw.trim()), negate };
@@ -34591,9 +34935,12 @@ var SpecCliAdapter = class _SpecCliAdapter {
34591
34935
  * — not this code — decides how a selection is keyed for each CLI.
34592
34936
  */
34593
34937
  async selectPickerChoice(ctl, action, choiceIndex, choiceLabel) {
34594
- this.driver.dispatch({ kind: "click_control", control_id: ctl.id });
34595
- await this.waitForPickerRendered(action);
34596
- const options = this.extractPickerChoices(action);
34938
+ let options = this.extractPickerChoicesIfRendered(action);
34939
+ if (!options) {
34940
+ this.driver.dispatch({ kind: "click_control", control_id: ctl.id });
34941
+ await this.waitForPickerRendered(action);
34942
+ options = this.extractPickerChoices(action);
34943
+ }
34597
34944
  let index = choiceIndex;
34598
34945
  if ((index == null || !Number.isFinite(index)) && choiceLabel) {
34599
34946
  const needle = choiceLabel.trim().toLowerCase();
@@ -34606,8 +34953,27 @@ var SpecCliAdapter = class _SpecCliAdapter {
34606
34953
  if (index == null || !Number.isFinite(index)) {
34607
34954
  return { ok: false, error: "choiceIndex or choiceLabel required to select" };
34608
34955
  }
34609
- const keys = (action.submit_key || "{index}\r").replace(/\{index\}/g, String(index));
34610
- this.driver.dispatch({ kind: "pty_write", data: keys });
34956
+ if (action.select_mode === "arrow_keys") {
34957
+ const current = options.find((o) => o.current);
34958
+ if (current == null) {
34959
+ return {
34960
+ ok: false,
34961
+ error: "arrow-nav picker: current cursor row not detected on screen",
34962
+ controlResult: { options: options.map((o) => ({ value: o.label, label: o.label, current: o.current })) }
34963
+ };
34964
+ }
34965
+ const up = action.cursor_keys?.up ?? "\x1B[A";
34966
+ const down = action.cursor_keys?.down ?? "\x1B[B";
34967
+ const delta = index - current.index;
34968
+ const step = delta >= 0 ? down : up;
34969
+ const nav = step.repeat(Math.abs(delta));
34970
+ const confirm = (action.submit_key || "\r").replace(/\{index\}/g, "") || "\r";
34971
+ if (nav) this.driver.dispatch({ kind: "pty_write", data: nav });
34972
+ this.driver.dispatch({ kind: "pty_write", data: confirm });
34973
+ } else {
34974
+ const keys = (action.submit_key || "{index}\r").replace(/\{index\}/g, String(index));
34975
+ this.driver.dispatch({ kind: "pty_write", data: keys });
34976
+ }
34611
34977
  const selected = options.find((o) => o.index === index);
34612
34978
  return {
34613
34979
  ok: true,
@@ -34619,6 +34985,20 @@ var SpecCliAdapter = class _SpecCliAdapter {
34619
34985
  }
34620
34986
  };
34621
34987
  }
34988
+ /** Parse the picker choices only if the picker already appears rendered on
34989
+ * the live screen (its `wait_for` condition currently matches and at least
34990
+ * one choice parses). Returns the parsed choices when open, else null so
34991
+ * the caller knows it must send the trigger to open it. Used to de-dup the
34992
+ * picker open in {@link selectPickerChoice}. */
34993
+ extractPickerChoicesIfRendered(action) {
34994
+ const wf = action.wait_for;
34995
+ if (wf?.regex) {
34996
+ const re = new RegExp(wf.regex, wf.flags ?? "i");
34997
+ if (!re.test(this.readScreenSectionText(wf.section))) return null;
34998
+ }
34999
+ const options = this.extractPickerChoices(action);
35000
+ return options.length > 0 ? options : null;
35001
+ }
34622
35002
  /** Poll the live screen until the picker's `wait_for` condition matches,
34623
35003
  * up to a short budget. Returns true if it rendered, false on timeout. */
34624
35004
  async waitForPickerRendered(action) {
@@ -34666,7 +35046,7 @@ var SpecCliAdapter = class _SpecCliAdapter {
34666
35046
  try {
34667
35047
  const sections = this.driver.getSections();
34668
35048
  if (sectionId && sections) {
34669
- const hit = sections.find((s) => s.id === sectionId);
35049
+ const hit = sections.find((s2) => s2.id === sectionId);
34670
35050
  if (hit) return hit.text;
34671
35051
  }
34672
35052
  return this.driver.getScreen();
@@ -34681,7 +35061,7 @@ var SpecCliAdapter = class _SpecCliAdapter {
34681
35061
  screen = this.driver.snapshot();
34682
35062
  const driverSections = this.driver.getSections?.();
34683
35063
  if (driverSections) {
34684
- sections = Object.fromEntries(driverSections.map((s) => [s.id, s.text]));
35064
+ sections = Object.fromEntries(driverSections.map((s2) => [s2.id, s2.text]));
34685
35065
  } else {
34686
35066
  sections = this.readCurrentScreenSections(screen);
34687
35067
  }
@@ -34727,6 +35107,10 @@ var SpecCliAdapter = class _SpecCliAdapter {
34727
35107
  // answers "why did this rule fire" after the fact, unlike the live
34728
35108
  // `fsm` field which only reflects the current instant.
34729
35109
  fsmHistory: this.driver.getFsmSnapshotHistory?.() ?? null,
35110
+ // PTY input/output/resize/cursor event timeline (debug-only) so the
35111
+ // snapshot shows what we typed / what the PTY printed around each
35112
+ // status transition. Null for drivers without the timeline.
35113
+ eventTimeline: this.driver.getEventTimeline?.() ?? null,
34730
35114
  // Extended fields
34731
35115
  name: this.cliName,
34732
35116
  status: this.getStatus().status,
@@ -35091,6 +35475,8 @@ var SpecCliAdapter = class _SpecCliAdapter {
35091
35475
  // v4 FSM transition snapshot history — the captured pre-transition
35092
35476
  // evaluation table at each transition (null for v3 specs).
35093
35477
  fsmHistory: this.driver.getFsmSnapshotHistory?.() ?? null,
35478
+ // PTY input/output/resize/cursor event timeline (debug-only).
35479
+ eventTimeline: this.driver.getEventTimeline?.() ?? null,
35094
35480
  messages,
35095
35481
  committedMessages: messages
35096
35482
  };
@@ -35135,6 +35521,7 @@ function createCliAdapter(provider, workingDir, cliArgs, extraEnv, transportFact
35135
35521
 
35136
35522
  // src/providers/cli-provider-instance.ts
35137
35523
  init_logger();
35524
+ init_mesh_event_trace();
35138
35525
  init_control_effects();
35139
35526
  init_approval_utils();
35140
35527
  init_provider_patch_state();
@@ -36186,6 +36573,23 @@ var CliProviderInstance = class _CliProviderInstance {
36186
36573
  if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
36187
36574
  this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
36188
36575
  }
36576
+ // EVTTRACE (observation-only): is this a mesh worker session whose completion
36577
+ // events must route to a coordinator? Used purely to gate trace logging so a
36578
+ // non-mesh CLI session's completions don't add EvtTrace noise. No decision logic.
36579
+ isMeshWorkerSession() {
36580
+ return !!(this.settings.meshNodeFor || this.settings.meshActiveTaskId || this.settings.meshNodeId || this.settings.launchedByCoordinator);
36581
+ }
36582
+ // EVTTRACE correlation context for this session's completion lifecycle. taskId is
36583
+ // the primary grep anchor; instanceId is the session fallback.
36584
+ meshTraceCtx(event = "agent:generating_completed") {
36585
+ return {
36586
+ taskId: this.settings.meshActiveTaskId,
36587
+ sessionId: this.instanceId,
36588
+ nodeId: this.settings.meshNodeId,
36589
+ meshId: this.settings.meshNodeFor,
36590
+ event
36591
+ };
36592
+ }
36189
36593
  flushCompletedDebounceIfFinalized() {
36190
36594
  const pending = this.completedDebouncePending;
36191
36595
  if (!pending) {
@@ -36206,24 +36610,33 @@ var CliProviderInstance = class _CliProviderInstance {
36206
36610
  if (block2) {
36207
36611
  const blockReason = block2.reason;
36208
36612
  const waitedMs = Date.now() - pending.firstObservedAt;
36209
- LOG.debug("CLI", `[${this.type}] finalization block: reason=${blockReason} terminal=${block2.terminal} waitedMs=${waitedMs} maxWait=${COMPLETED_FINALIZATION_MAX_WAIT_MS}`);
36210
- if (block2.terminal && !block2.allowTimeout || waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
36613
+ const isTranscriptEvidenceGate = block2.allowTimeout === true;
36614
+ LOG.debug("CLI", `[${this.type}] finalization block: reason=${blockReason} terminal=${block2.terminal} allowTimeout=${isTranscriptEvidenceGate} waitedMs=${waitedMs} maxWait=${COMPLETED_FINALIZATION_MAX_WAIT_MS}`);
36615
+ if (!isTranscriptEvidenceGate && (block2.terminal || waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS)) {
36211
36616
  if (pending.loggedBlockReason !== blockReason) {
36212
36617
  LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
36618
+ if (this.isMeshWorkerSession()) {
36619
+ traceMeshEventDrop("completion_gate_hold", this.meshTraceCtx(), `${blockReason} waited=${waitedMs}ms`);
36620
+ }
36213
36621
  pending.loggedBlockReason = blockReason;
36214
36622
  }
36215
36623
  this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
36216
36624
  return;
36217
36625
  }
36626
+ const emittedAfterFinalizationTimeout = waitedMs >= COMPLETED_FINALIZATION_MAX_WAIT_MS;
36218
36627
  const completionDiagnostic = this.buildCompletedFinalizationDiagnostic({
36219
36628
  blockReason,
36220
36629
  latestStatus,
36221
36630
  latestVisibleStatus,
36222
36631
  waitedMs,
36223
36632
  pending,
36224
- emittedAfterFinalizationTimeout: true
36633
+ emittedAfterFinalizationTimeout
36225
36634
  });
36226
- LOG.warn("CLI", `[${this.type}] emitting completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
36635
+ completionDiagnostic.decoupledImmediateEmit = isTranscriptEvidenceGate && !emittedAfterFinalizationTimeout;
36636
+ LOG.warn("CLI", `[${this.type}] emitting completed event (${isTranscriptEvidenceGate && !emittedAfterFinalizationTimeout ? "CANON-C decoupled-immediate, transcript pending" : `after ${waitedMs}ms`}) without finalized assistant turn (${blockReason})`);
36637
+ if (this.isMeshWorkerSession()) {
36638
+ traceMeshEventStage("fired", this.meshTraceCtx(), `forced after ${waitedMs}ms (${blockReason})`);
36639
+ }
36227
36640
  this.pushEvent({
36228
36641
  event: "agent:generating_completed",
36229
36642
  chatTitle: pending.chatTitle,
@@ -36246,6 +36659,9 @@ var CliProviderInstance = class _CliProviderInstance {
36246
36659
  return;
36247
36660
  }
36248
36661
  LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
36662
+ if (this.isMeshWorkerSession()) {
36663
+ traceMeshEventStage("fired", this.meshTraceCtx(), `duration=${pending.duration}s`);
36664
+ }
36249
36665
  this.pushEvent({
36250
36666
  event: "agent:generating_completed",
36251
36667
  chatTitle: pending.chatTitle,
@@ -36484,6 +36900,9 @@ var CliProviderInstance = class _CliProviderInstance {
36484
36900
  if (missingEvidence && !hasMeshContext) {
36485
36901
  LOG.info("CLI", `[${this.type}] short completion suppressed: missing final assistant evidence, no mesh context (source=${shortEvidenceSource})`);
36486
36902
  } else {
36903
+ if (this.isMeshWorkerSession()) {
36904
+ traceMeshEventStage("fired", this.meshTraceCtx(), `short-generating idle (source=${shortEvidenceSource})`);
36905
+ }
36487
36906
  this.pushEvent({
36488
36907
  event: "agent:generating_completed",
36489
36908
  chatTitle,
@@ -36560,6 +36979,9 @@ var CliProviderInstance = class _CliProviderInstance {
36560
36979
  const monitorParsedStatus = parsedStatus;
36561
36980
  for (const me of monitorEvents) {
36562
36981
  if (me.type === "monitor:no_progress" && this.completionHasFinalAssistantMessage(monitorParsedStatus?.messages) && !this.hasAdapterPendingResponse() && !hasNonEmptyCliModalButtons(monitorParsedStatus?.activeModal ?? monitorParsedStatus?.modal)) {
36982
+ if (this.isMeshWorkerSession()) {
36983
+ traceMeshEventStage("fired", this.meshTraceCtx(), "no_progress_monitor_final_summary");
36984
+ }
36563
36985
  this.pushEvent({
36564
36986
  event: "agent:generating_completed",
36565
36987
  chatTitle,
@@ -36600,6 +37022,12 @@ var CliProviderInstance = class _CliProviderInstance {
36600
37022
  workspace: typeof event.workspace === "string" && event.workspace.trim() ? event.workspace : this.workingDir,
36601
37023
  providerSessionId: typeof event.providerSessionId === "string" && event.providerSessionId.trim() ? event.providerSessionId : this.providerSessionId
36602
37024
  };
37025
+ if (this.isMeshWorkerSession() && this.settings.meshActiveTaskId) {
37026
+ const existingTaskId = typeof enrichedEvent.taskId === "string" && enrichedEvent.taskId.trim() ? enrichedEvent.taskId : void 0;
37027
+ if (!existingTaskId) {
37028
+ enrichedEvent.taskId = this.settings.meshActiveTaskId;
37029
+ }
37030
+ }
36603
37031
  if (this.context?.emitProviderEvent) {
36604
37032
  this.context.emitProviderEvent(enrichedEvent);
36605
37033
  } else {
@@ -40428,7 +40856,7 @@ function parsePbFile(filePath, sessionId) {
40428
40856
  }
40429
40857
  if (buf.length === 0) return null;
40430
40858
  const strings = extractStringsFromBuffer(buf);
40431
- const meaningful = strings.filter((s) => s.length >= MIN_PRINTABLE_RUN && /\w/.test(s));
40859
+ const meaningful = strings.filter((s2) => s2.length >= MIN_PRINTABLE_RUN && /\w/.test(s2));
40432
40860
  if (meaningful.length === 0) return null;
40433
40861
  const content = meaningful.join("\n");
40434
40862
  const sourceMtimeMs = statMtimeMs3(filePath);
@@ -40636,10 +41064,10 @@ function readSession4(sessionPath) {
40636
41064
  };
40637
41065
  }
40638
41066
  function normalizeHermesRole(r) {
40639
- const s = String(r ?? "").toLowerCase();
40640
- if (s === "user" || s === "human") return "user";
40641
- if (s === "assistant" || s === "ai" || s === "model") return "assistant";
40642
- if (s === "tool" || s === "tool_result" || s === "function") return "assistant";
41067
+ const s2 = String(r ?? "").toLowerCase();
41068
+ if (s2 === "user" || s2 === "human") return "user";
41069
+ if (s2 === "assistant" || s2 === "ai" || s2 === "model") return "assistant";
41070
+ if (s2 === "tool" || s2 === "tool_result" || s2 === "function") return "assistant";
40643
41071
  return "system";
40644
41072
  }
40645
41073
 
@@ -40865,10 +41293,10 @@ function safeMtime(p) {
40865
41293
  }
40866
41294
  }
40867
41295
  function normalizeRole2(r) {
40868
- const s = String(r ?? "").toLowerCase();
40869
- if (s === "user" || s === "human") return "user";
40870
- if (s === "assistant" || s === "ai" || s === "model") return "assistant";
40871
- if (s === "tool" || s === "tool_result" || s === "function") return "assistant";
41296
+ const s2 = String(r ?? "").toLowerCase();
41297
+ if (s2 === "user" || s2 === "human") return "user";
41298
+ if (s2 === "assistant" || s2 === "ai" || s2 === "model") return "assistant";
41299
+ if (s2 === "tool" || s2 === "tool_result" || s2 === "function") return "assistant";
40872
41300
  return "system";
40873
41301
  }
40874
41302
 
@@ -40888,7 +41316,7 @@ function synthesizeControlsFromControlBar(specControls) {
40888
41316
  const actionType = ctl?.action?.type;
40889
41317
  if (!id || !actionType) return;
40890
41318
  const label = typeof ctl?.label === "string" && ctl.label.trim() ? ctl.label : id;
40891
- const visibleWhenState = Array.isArray(ctl?.visible_when_state) ? ctl.visible_when_state.filter((s) => typeof s === "string") : void 0;
41319
+ const visibleWhenState = Array.isArray(ctl?.visible_when_state) ? ctl.visible_when_state.filter((s2) => typeof s2 === "string") : void 0;
40892
41320
  if (actionType === "open_picker") {
40893
41321
  out.push({
40894
41322
  id,
@@ -47804,7 +48232,7 @@ var DaemonCommandRouter = class {
47804
48232
  const base = validationSummary.failureCode === "missing_dependencies" ? "Refinery validation dependencies are missing; merge/refine was not attempted. Configure validation.bootstrapCommands if Refinery should bootstrap dependencies before validation." : validationSummary.failureCode === "dependency_bootstrap_failed" ? "Refinery dependency/bootstrap command failed; merge/refine was not attempted." : validationSummary.failureCode === "spawn_resolution_failed" ? validationSummary.spawnResolutionError || "Refinery validation command could not be spawned (executable not found); merge/refine was not attempted." : "Refinery validation gate failed; merge/refine was not attempted.";
47805
48233
  if (!firstFailedCmd) return base;
47806
48234
  const cmdName = typeof firstFailedCmd.displayCommand === "string" ? firstFailedCmd.displayCommand : typeof firstFailedCmd.command === "string" ? [firstFailedCmd.command, ...Array.isArray(firstFailedCmd.args) ? firstFailedCmd.args : []].join(" ").trim() : typeof firstFailedCmd.cmd === "string" ? firstFailedCmd.cmd : "";
47807
- const rawOutput = [firstFailedCmd.stdout, firstFailedCmd.stderr, firstFailedCmd.output].filter((s) => typeof s === "string" && s.length > 0).join("\n");
48235
+ const rawOutput = [firstFailedCmd.stdout, firstFailedCmd.stderr, firstFailedCmd.output].filter((s2) => typeof s2 === "string" && s2.length > 0).join("\n");
47808
48236
  const tail = rawOutput.length > 800 ? rawOutput.slice(-800) : rawOutput;
47809
48237
  return [
47810
48238
  base,
@@ -48555,7 +48983,7 @@ ${hintLines.join("\n")}` : "",
48555
48983
  convergence = "blocked_review";
48556
48984
  }
48557
48985
  const fbcs = result.finalBranchConvergenceState && typeof result.finalBranchConvergenceState === "object" ? result.finalBranchConvergenceState : void 0;
48558
- const stage = Array.isArray(result.refineStages) ? result.refineStages.filter((s) => s.status === "failed").map((s) => s.stage).filter(Boolean).pop() : void 0;
48986
+ const stage = Array.isArray(result.refineStages) ? result.refineStages.filter((s2) => s2.status === "failed").map((s2) => s2.stage).filter(Boolean).pop() : void 0;
48559
48987
  results.push({
48560
48988
  nodeId: node.id,
48561
48989
  workspace: node.workspace,
@@ -49552,7 +49980,7 @@ ${hintLines.join("\n")}` : "",
49552
49980
  return {
49553
49981
  success: true,
49554
49982
  screenLineCount: lines.length,
49555
- sections: resolved.map((s) => ({ id: s.id, fromLine: s.fromLine, toLine: s.toLine, text: s.text }))
49983
+ sections: resolved.map((s2) => ({ id: s2.id, fromLine: s2.fromLine, toLine: s2.toLine, text: s2.text }))
49556
49984
  };
49557
49985
  } catch (e) {
49558
49986
  return { success: false, error: `resolve failed: ${e.message}` };
@@ -50121,7 +50549,7 @@ ${hintLines.join("\n")}` : "",
50121
50549
  if (!meshId) return { success: false, error: "meshId required" };
50122
50550
  try {
50123
50551
  const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2, describeTaskDependencyState: describeTaskDependencyState2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
50124
- const status = Array.isArray(args?.status) ? args.status.map((s) => typeof s === "string" ? s.trim() : "").filter(Boolean) : void 0;
50552
+ const status = Array.isArray(args?.status) ? args.status.map((s2) => typeof s2 === "string" ? s2.trim() : "").filter(Boolean) : void 0;
50125
50553
  const rawQueue = getQueue2(meshId, { status });
50126
50554
  const statusById = new Map(getQueue2(meshId).map((task) => [task.id, task.status]));
50127
50555
  const queue = rawQueue.map((task) => Array.isArray(task.dependsOn) && task.dependsOn.length > 0 ? { ...task, ...describeTaskDependencyState2(task, statusById) } : task);
@@ -50402,7 +50830,7 @@ ${hintLines.join("\n")}` : "",
50402
50830
  nodeDaemonId = typeof node?.daemonId === "string" ? node.daemonId.trim() : void 0;
50403
50831
  }
50404
50832
  const selfDaemonId = this.deps.statusInstanceId;
50405
- const isRemote = nodeDaemonId && selfDaemonId && nodeDaemonId !== selfDaemonId;
50833
+ const isRemote = nodeDaemonId && selfDaemonId && !daemonIdsEquivalent(nodeDaemonId, selfDaemonId);
50406
50834
  if (isRemote && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
50407
50835
  const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "fast_forward_mesh_node", {
50408
50836
  ...typeof args === "object" && args !== null ? args : {},
@@ -50436,7 +50864,7 @@ ${hintLines.join("\n")}` : "",
50436
50864
  nodeDaemonId = typeof node?.daemonId === "string" ? node.daemonId.trim() : void 0;
50437
50865
  }
50438
50866
  const selfDaemonId = this.deps.statusInstanceId;
50439
- const isRemote = nodeDaemonId && selfDaemonId && nodeDaemonId !== selfDaemonId;
50867
+ const isRemote = nodeDaemonId && selfDaemonId && !daemonIdsEquivalent(nodeDaemonId, selfDaemonId);
50440
50868
  if (isRemote && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
50441
50869
  const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "get_mesh_node_logs", {
50442
50870
  ...typeof args === "object" && args !== null ? args : {},
@@ -50484,7 +50912,7 @@ ${hintLines.join("\n")}` : "",
50484
50912
  const forwardNode = meshRecordForForward?.mesh?.nodes?.find((n) => meshNodeIdMatches(n, nodeId));
50485
50913
  const nodeDaemonId = typeof forwardNode?.daemonId === "string" ? forwardNode.daemonId.trim() : void 0;
50486
50914
  const selfDaemonId = this.deps.statusInstanceId;
50487
- const isRemote = nodeDaemonId && selfDaemonId && nodeDaemonId !== selfDaemonId;
50915
+ const isRemote = nodeDaemonId && selfDaemonId && !daemonIdsEquivalent(nodeDaemonId, selfDaemonId);
50488
50916
  if (isRemote && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
50489
50917
  const callerCoordinatorDaemonId = typeof args?.coordinatorDaemonId === "string" && args.coordinatorDaemonId.trim() ? args.coordinatorDaemonId.trim() : void 0;
50490
50918
  const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "refine_mesh_node", {
@@ -50571,7 +50999,7 @@ ${hintLines.join("\n")}` : "",
50571
50999
  let worktreeCleanup;
50572
51000
  if (node?.isLocalWorktree) {
50573
51001
  const nodeDaemonId = typeof node.daemonId === "string" ? node.daemonId.trim() : void 0;
50574
- const isRemoteWorktree = nodeDaemonId && nodeDaemonId !== this.deps.statusInstanceId && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch;
51002
+ const isRemoteWorktree = nodeDaemonId && !daemonIdsEquivalent(nodeDaemonId, this.deps.statusInstanceId) && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch;
50575
51003
  if (isRemoteWorktree) {
50576
51004
  const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "remove_mesh_node", {
50577
51005
  ...typeof args === "object" && args !== null ? args : {},
@@ -50653,7 +51081,7 @@ ${hintLines.join("\n")}` : "",
50653
51081
  const sourceNode = mesh.nodes?.find((n) => meshNodeIdMatches(n, sourceNodeId));
50654
51082
  if (!sourceNode) return { success: false, error: `Source node '${sourceNodeId}' not found in mesh` };
50655
51083
  const sourceDaemonId = typeof sourceNode.daemonId === "string" ? sourceNode.daemonId.trim() : void 0;
50656
- if (sourceDaemonId && sourceDaemonId !== this.deps.statusInstanceId && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
51084
+ if (sourceDaemonId && !daemonIdsEquivalent(sourceDaemonId, this.deps.statusInstanceId) && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
50657
51085
  const forwarded = await this.deps.dispatchMeshCommand(sourceDaemonId, "clone_mesh_node", {
50658
51086
  ...typeof args === "object" && args !== null ? args : {},
50659
51087
  _meshDirectDispatch: true
@@ -50895,7 +51323,7 @@ ${hintLines.join("\n")}` : "",
50895
51323
  if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
50896
51324
  if (!node.isLocalWorktree) return { success: false, error: "Node is not a local worktree node" };
50897
51325
  const nodeDaemonId = typeof node.daemonId === "string" ? node.daemonId.trim() : void 0;
50898
- if (nodeDaemonId && nodeDaemonId !== this.deps.statusInstanceId && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
51326
+ if (nodeDaemonId && !daemonIdsEquivalent(nodeDaemonId, this.deps.statusInstanceId) && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
50899
51327
  const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "retry_mesh_node_bootstrap", {
50900
51328
  ...typeof args === "object" && args !== null ? args : {},
50901
51329
  _meshDirectDispatch: true
@@ -52105,16 +52533,16 @@ var DaemonStatusReporter = class {
52105
52533
  const now = this.lastStatusSentAt;
52106
52534
  const target = opts?.p2pOnly ? "P2P" : serverConnected ? "P2P+Server" : "P2P";
52107
52535
  const allStates = this.deps.instanceManager.collectAllStates();
52108
- const ideStates = allStates.filter((s) => s.category === "ide");
52109
- const cliStates = allStates.filter((s) => s.category === "cli");
52110
- const acpStates = allStates.filter((s) => s.category === "acp");
52111
- const ideSummary = ideStates.map((s) => {
52112
- const msgs = s.activeChat?.messages?.length || 0;
52113
- const exts = s.extensions.length;
52114
- return `${s.type}(${s.status},${msgs}msg,${exts}ext)`;
52536
+ const ideStates = allStates.filter((s2) => s2.category === "ide");
52537
+ const cliStates = allStates.filter((s2) => s2.category === "cli");
52538
+ const acpStates = allStates.filter((s2) => s2.category === "acp");
52539
+ const ideSummary = ideStates.map((s2) => {
52540
+ const msgs = s2.activeChat?.messages?.length || 0;
52541
+ const exts = s2.extensions.length;
52542
+ return `${s2.type}(${s2.status},${msgs}msg,${exts}ext)`;
52115
52543
  }).join(", ");
52116
- const cliSummary = cliStates.map((s) => `${s.type}(${s.status})`).join(", ");
52117
- const acpSummary = acpStates.map((s) => `${s.type}(${s.status})`).join(", ");
52544
+ const cliSummary = cliStates.map((s2) => `${s2.type}(${s2.status})`).join(", ");
52545
+ const acpSummary = acpStates.map((s2) => `${s2.type}(${s2.status})`).join(", ");
52118
52546
  const logLevel = opts?.p2pOnly ? "debug" : "info";
52119
52547
  const baseSummary = `IDE: ${ideStates.length} [${ideSummary}] CLI: ${cliStates.length} [${cliSummary}] ACP: ${acpStates.length} [${acpSummary}]`;
52120
52548
  const summaryChanged = baseSummary !== this.lastStatusSummary;
@@ -52225,10 +52653,10 @@ var DaemonStatusReporter = class {
52225
52653
  }
52226
52654
  return false;
52227
52655
  }
52228
- simpleHash(s) {
52656
+ simpleHash(s2) {
52229
52657
  let h = 2166136261;
52230
- for (let i = 0; i < s.length; i++) {
52231
- h ^= s.charCodeAt(i);
52658
+ for (let i = 0; i < s2.length; i++) {
52659
+ h ^= s2.charCodeAt(i);
52232
52660
  h = h * 16777619 >>> 0;
52233
52661
  }
52234
52662
  return h.toString(36);
@@ -53454,7 +53882,7 @@ var ProviderInstanceManager = class {
53454
53882
  * Per-category status collect
53455
53883
  */
53456
53884
  collectStatesByCategory(category) {
53457
- return this.collectAllStates().filter((s) => s.category === category);
53885
+ return this.collectAllStates().filter((s2) => s2.category === category);
53458
53886
  }
53459
53887
  // ─── Tick engine ─────────────────────────────────
53460
53888
  /**
@@ -55354,9 +55782,9 @@ function getCliProviderResolutionMeta(ctx, type, adapter) {
55354
55782
  function findCliTarget(ctx, type, instanceId) {
55355
55783
  if (!ctx.instanceManager) return null;
55356
55784
  const cliStates = ctx.instanceManager.collectAllStates().filter(isCliTargetState);
55357
- if (instanceId) return cliStates.find((s) => s.instanceId === instanceId) || null;
55785
+ if (instanceId) return cliStates.find((s2) => s2.instanceId === instanceId) || null;
55358
55786
  if (!type) return cliStates[cliStates.length - 1] || null;
55359
- const matches = cliStates.filter((s) => s.type === type);
55787
+ const matches = cliStates.filter((s2) => s2.type === type);
55360
55788
  return matches[matches.length - 1] || null;
55361
55789
  }
55362
55790
  function getCliTargetBundle(ctx, type, instanceId) {
@@ -55719,20 +56147,20 @@ async function handleCliStatus(ctx, _req, res) {
55719
56147
  return;
55720
56148
  }
55721
56149
  const allStates = ctx.instanceManager.collectAllStates();
55722
- const cliStates = allStates.filter((s) => s.category === "cli" || s.category === "acp");
55723
- const result = cliStates.map((s) => ({
55724
- instanceId: s.instanceId,
55725
- type: s.type,
55726
- name: s.name,
55727
- category: s.category,
55728
- status: s.status,
55729
- mode: s.mode,
55730
- workspace: s.workspace,
55731
- messageCount: s.activeChat?.messages?.length || 0,
55732
- lastMessage: s.activeChat?.messages?.slice(-1)[0] || null,
55733
- activeModal: s.activeChat?.activeModal || null,
55734
- pendingEvents: s.pendingEvents || [],
55735
- settings: s.settings
56150
+ const cliStates = allStates.filter((s2) => s2.category === "cli" || s2.category === "acp");
56151
+ const result = cliStates.map((s2) => ({
56152
+ instanceId: s2.instanceId,
56153
+ type: s2.type,
56154
+ name: s2.name,
56155
+ category: s2.category,
56156
+ status: s2.status,
56157
+ mode: s2.mode,
56158
+ workspace: s2.workspace,
56159
+ messageCount: s2.activeChat?.messages?.length || 0,
56160
+ lastMessage: s2.activeChat?.messages?.slice(-1)[0] || null,
56161
+ activeModal: s2.activeChat?.activeModal || null,
56162
+ pendingEvents: s2.pendingEvents || [],
56163
+ settings: s2.settings
55736
56164
  }));
55737
56165
  ctx.json(res, 200, { instances: result, count: result.length });
55738
56166
  }
@@ -55821,9 +56249,9 @@ function handleCliSSE(ctx, cliSSEClients, _req, res) {
55821
56249
  }
55822
56250
  if (ctx.instanceManager) {
55823
56251
  const allStates = ctx.instanceManager.collectAllStates();
55824
- const cliStates = allStates.filter((s) => s.category === "cli" || s.category === "acp");
55825
- for (const s of cliStates) {
55826
- ctx.sendCliSSE({ event: "snapshot", providerType: s.type, status: s.status, instanceId: s.instanceId });
56252
+ const cliStates = allStates.filter((s2) => s2.category === "cli" || s2.category === "acp");
56253
+ for (const s2 of cliStates) {
56254
+ ctx.sendCliSSE({ event: "snapshot", providerType: s2.type, status: s2.status, instanceId: s2.instanceId });
55827
56255
  }
55828
56256
  }
55829
56257
  _req.on("close", () => {
@@ -55839,7 +56267,7 @@ async function handleCliDebug(ctx, type, _req, res) {
55839
56267
  const target = findCliTarget(ctx, type);
55840
56268
  if (!target) {
55841
56269
  const allStates = ctx.instanceManager.collectAllStates();
55842
- ctx.json(res, 404, { error: `No running instance for: ${type}`, available: allStates.filter((s) => s.category === "cli" || s.category === "acp").map((s) => s.type) });
56270
+ ctx.json(res, 404, { error: `No running instance for: ${type}`, available: allStates.filter((s2) => s2.category === "cli" || s2.category === "acp").map((s2) => s2.type) });
55843
56271
  return;
55844
56272
  }
55845
56273
  const instance = ctx.instanceManager.getInstance(target.instanceId);
@@ -55885,7 +56313,7 @@ async function handleCliTrace(ctx, type, req, res) {
55885
56313
  const allStates = ctx.instanceManager.collectAllStates();
55886
56314
  ctx.json(res, 404, {
55887
56315
  error: `No running instance for: ${type}`,
55888
- available: allStates.filter((s) => s.category === "cli" || s.category === "acp").map((s) => s.type)
56316
+ available: allStates.filter((s2) => s2.category === "cli" || s2.category === "acp").map((s2) => s2.type)
55889
56317
  });
55890
56318
  return;
55891
56319
  }
@@ -56690,7 +57118,7 @@ async function handleAutoImplement(ctx, type, req, res) {
56690
57118
  child.write("\x1B[12;1R");
56691
57119
  ctx.log("Terminal CPR request (\\x1b[6n) intercepted in PTY, responding with dummy coordinates [12;1R]");
56692
57120
  }
56693
- checkAutoApproval(data, (s) => child.write(s));
57121
+ checkAutoApproval(data, (s2) => child.write(s2));
56694
57122
  sendAutoImplSSE(ctx, { event: "output", data: { chunk: data, stream: "stdout" } });
56695
57123
  scheduleAutoStopForVerification();
56696
57124
  });
@@ -56703,7 +57131,7 @@ async function handleAutoImplement(ctx, type, req, res) {
56703
57131
  stdout += chunk;
56704
57132
  clearAutoStopTimer();
56705
57133
  if (chunk.includes("\x1B[6n")) child.stdin?.write("\x1B[1;1R");
56706
- checkAutoApproval(chunk, (s) => child.stdin?.write(s));
57134
+ checkAutoApproval(chunk, (s2) => child.stdin?.write(s2));
56707
57135
  sendAutoImplSSE(ctx, { event: "output", data: { chunk, stream: "stdout" } });
56708
57136
  scheduleAutoStopForVerification();
56709
57137
  });
@@ -56711,7 +57139,7 @@ async function handleAutoImplement(ctx, type, req, res) {
56711
57139
  const chunk = d.toString();
56712
57140
  stderr += chunk;
56713
57141
  clearAutoStopTimer();
56714
- checkAutoApproval(chunk, (s) => child.stdin?.write(s));
57142
+ checkAutoApproval(chunk, (s2) => child.stdin?.write(s2));
56715
57143
  sendAutoImplSSE(ctx, { event: "output", data: { chunk, stream: "stderr" } });
56716
57144
  scheduleAutoStopForVerification();
56717
57145
  });
@@ -57526,59 +57954,59 @@ var DevServer = class _DevServer {
57526
57954
  // ─── Route Table ─────────────────────────────────────
57527
57955
  routes = [
57528
57956
  // Static routes
57529
- { method: "GET", pattern: "/api/providers", handler: (q, s) => this.handleListProviders(q, s) },
57530
- { method: "GET", pattern: "/api/providers/source-config", handler: (q, s) => this.handleGetProviderSourceConfig(q, s) },
57531
- { method: "POST", pattern: "/api/providers/source-config", handler: (q, s) => this.handleSetProviderSourceConfig(q, s) },
57532
- { method: "GET", pattern: "/api/providers/versions", handler: (q, s) => this.handleDetectVersions(q, s) },
57533
- { method: "POST", pattern: "/api/providers/reload", handler: (q, s) => this.handleReload(q, s) },
57534
- { method: "POST", pattern: "/api/cdp/evaluate", handler: (q, s) => this.handleCdpEvaluate(q, s) },
57535
- { method: "POST", pattern: "/api/cdp/click", handler: (q, s) => this.handleCdpClick(q, s) },
57536
- { method: "POST", pattern: "/api/cdp/dom/query", handler: (q, s) => this.handleCdpDomQuery(q, s) },
57537
- { method: "POST", pattern: "/api/cdp/dom/inspect", handler: (q, s) => this.handleDomInspect(q, s) },
57538
- { method: "POST", pattern: "/api/cdp/dom/children", handler: (q, s) => this.handleDomChildren(q, s) },
57539
- { method: "POST", pattern: "/api/cdp/dom/analyze", handler: (q, s) => this.handleDomAnalyze(q, s) },
57540
- { method: "POST", pattern: "/api/cdp/dom/find-text", handler: (q, s) => this.handleFindByText(q, s) },
57541
- { method: "POST", pattern: "/api/cdp/dom/find-common", handler: (q, s) => this.handleFindCommon(q, s) },
57542
- { method: "GET", pattern: "/api/cdp/screenshot", handler: (q, s) => this.handleScreenshot(q, s) },
57543
- { method: "GET", pattern: "/api/cdp/targets", handler: (q, s) => this.handleCdpTargets(q, s) },
57544
- { method: "POST", pattern: "/api/scripts/run", handler: (q, s) => this.handleScriptsRun(q, s) },
57545
- { method: "GET", pattern: "/api/status", handler: (q, s) => this.handleStatus(q, s) },
57546
- { method: "POST", pattern: "/api/watch/start", handler: (q, s) => this.handleWatchStart(q, s) },
57547
- { method: "POST", pattern: "/api/watch/stop", handler: (q, s) => this.handleWatchStop(q, s) },
57548
- { method: "GET", pattern: "/api/watch/events", handler: (q, s) => this.handleSSE(q, s) },
57549
- { method: "POST", pattern: "/api/scaffold", handler: (q, s) => this.handleScaffold(q, s) },
57957
+ { method: "GET", pattern: "/api/providers", handler: (q, s2) => this.handleListProviders(q, s2) },
57958
+ { method: "GET", pattern: "/api/providers/source-config", handler: (q, s2) => this.handleGetProviderSourceConfig(q, s2) },
57959
+ { method: "POST", pattern: "/api/providers/source-config", handler: (q, s2) => this.handleSetProviderSourceConfig(q, s2) },
57960
+ { method: "GET", pattern: "/api/providers/versions", handler: (q, s2) => this.handleDetectVersions(q, s2) },
57961
+ { method: "POST", pattern: "/api/providers/reload", handler: (q, s2) => this.handleReload(q, s2) },
57962
+ { method: "POST", pattern: "/api/cdp/evaluate", handler: (q, s2) => this.handleCdpEvaluate(q, s2) },
57963
+ { method: "POST", pattern: "/api/cdp/click", handler: (q, s2) => this.handleCdpClick(q, s2) },
57964
+ { method: "POST", pattern: "/api/cdp/dom/query", handler: (q, s2) => this.handleCdpDomQuery(q, s2) },
57965
+ { method: "POST", pattern: "/api/cdp/dom/inspect", handler: (q, s2) => this.handleDomInspect(q, s2) },
57966
+ { method: "POST", pattern: "/api/cdp/dom/children", handler: (q, s2) => this.handleDomChildren(q, s2) },
57967
+ { method: "POST", pattern: "/api/cdp/dom/analyze", handler: (q, s2) => this.handleDomAnalyze(q, s2) },
57968
+ { method: "POST", pattern: "/api/cdp/dom/find-text", handler: (q, s2) => this.handleFindByText(q, s2) },
57969
+ { method: "POST", pattern: "/api/cdp/dom/find-common", handler: (q, s2) => this.handleFindCommon(q, s2) },
57970
+ { method: "GET", pattern: "/api/cdp/screenshot", handler: (q, s2) => this.handleScreenshot(q, s2) },
57971
+ { method: "GET", pattern: "/api/cdp/targets", handler: (q, s2) => this.handleCdpTargets(q, s2) },
57972
+ { method: "POST", pattern: "/api/scripts/run", handler: (q, s2) => this.handleScriptsRun(q, s2) },
57973
+ { method: "GET", pattern: "/api/status", handler: (q, s2) => this.handleStatus(q, s2) },
57974
+ { method: "POST", pattern: "/api/watch/start", handler: (q, s2) => this.handleWatchStart(q, s2) },
57975
+ { method: "POST", pattern: "/api/watch/stop", handler: (q, s2) => this.handleWatchStop(q, s2) },
57976
+ { method: "GET", pattern: "/api/watch/events", handler: (q, s2) => this.handleSSE(q, s2) },
57977
+ { method: "POST", pattern: "/api/scaffold", handler: (q, s2) => this.handleScaffold(q, s2) },
57550
57978
  // CLI Debug routes
57551
- { method: "GET", pattern: "/api/cli/status", handler: (q, s) => this.handleCliStatus(q, s) },
57552
- { method: "POST", pattern: "/api/cli/launch", handler: (q, s) => this.handleCliLaunch(q, s) },
57553
- { method: "POST", pattern: "/api/cli/send", handler: (q, s) => this.handleCliSend(q, s) },
57554
- { method: "POST", pattern: "/api/cli/exercise", handler: (q, s) => this.handleCliExercise(q, s) },
57555
- { method: "POST", pattern: "/api/cli/fixture/capture", handler: (q, s) => this.handleCliFixtureCapture(q, s) },
57556
- { method: "POST", pattern: "/api/cli/fixture/replay", handler: (q, s) => this.handleCliFixtureReplay(q, s) },
57557
- { method: "POST", pattern: "/api/cli/resolve", handler: (q, s) => this.handleCliResolve(q, s) },
57558
- { method: "POST", pattern: "/api/cli/raw", handler: (q, s) => this.handleCliRaw(q, s) },
57559
- { method: "POST", pattern: "/api/cli/stop", handler: (q, s) => this.handleCliStop(q, s) },
57560
- { method: "GET", pattern: "/api/cli/events", handler: (q, s) => this.handleCliSSE(q, s) },
57561
- { method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s, p) => this.handleCliDebug(p[0], q, s) },
57562
- { method: "GET", pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q, s, p) => this.handleCliTrace(p[0], q, s) },
57563
- { method: "GET", pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q, s, p) => this.handleCliFixtureList(p[0], q, s) },
57979
+ { method: "GET", pattern: "/api/cli/status", handler: (q, s2) => this.handleCliStatus(q, s2) },
57980
+ { method: "POST", pattern: "/api/cli/launch", handler: (q, s2) => this.handleCliLaunch(q, s2) },
57981
+ { method: "POST", pattern: "/api/cli/send", handler: (q, s2) => this.handleCliSend(q, s2) },
57982
+ { method: "POST", pattern: "/api/cli/exercise", handler: (q, s2) => this.handleCliExercise(q, s2) },
57983
+ { method: "POST", pattern: "/api/cli/fixture/capture", handler: (q, s2) => this.handleCliFixtureCapture(q, s2) },
57984
+ { method: "POST", pattern: "/api/cli/fixture/replay", handler: (q, s2) => this.handleCliFixtureReplay(q, s2) },
57985
+ { method: "POST", pattern: "/api/cli/resolve", handler: (q, s2) => this.handleCliResolve(q, s2) },
57986
+ { method: "POST", pattern: "/api/cli/raw", handler: (q, s2) => this.handleCliRaw(q, s2) },
57987
+ { method: "POST", pattern: "/api/cli/stop", handler: (q, s2) => this.handleCliStop(q, s2) },
57988
+ { method: "GET", pattern: "/api/cli/events", handler: (q, s2) => this.handleCliSSE(q, s2) },
57989
+ { method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s2, p) => this.handleCliDebug(p[0], q, s2) },
57990
+ { method: "GET", pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q, s2, p) => this.handleCliTrace(p[0], q, s2) },
57991
+ { method: "GET", pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q, s2, p) => this.handleCliFixtureList(p[0], q, s2) },
57564
57992
  // Dynamic routes (provider :type param)
57565
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s, p) => this.handleRunScript(p[0], q, s) },
57566
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s, p) => this.handleListFiles(p[0], q, s) },
57567
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/file$/, handler: (q, s, p) => this.handleReadFile(p[0], q, s) },
57568
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/file$/, handler: (q, s, p) => this.handleWriteFile(p[0], q, s) },
57569
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/source$/, handler: (q, s, p) => this.handleSource(p[0], q, s) },
57570
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/save$/, handler: (q, s, p) => this.handleSave(p[0], q, s) },
57571
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/typeAndSend$/, handler: (q, s, p) => this.handleTypeAndSend(p[0], q, s) },
57572
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/typeAndSendAt$/, handler: (q, s, p) => this.handleTypeAndSendAt(p[0], q, s) },
57573
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/config$/, handler: (q, s, p) => this.handleProviderConfig(p[0], q, s) },
57574
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/dom-context$/, handler: (q, s, p) => this.handleDomContext(p[0], q, s) },
57575
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/auto-implement$/, handler: (q, s, p) => this.handleAutoImplement(p[0], q, s) },
57576
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/auto-implement\/cancel$/, handler: (q, s, p) => this.handleAutoImplCancel(p[0], q, s) },
57577
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/auto-implement\/status$/, handler: (q, s, p) => this.handleAutoImplSSE(p[0], q, s) },
57578
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/spawn-test$/, handler: (q, s, p) => this.handleSpawnTest(p[0], q, s) },
57579
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/validate$/, handler: (q, s, p) => this.handleValidate(p[0], q, s) },
57580
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/acp-chat$/, handler: (q, s, p) => this.handleAcpChat(p[0], q, s) },
57581
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/script-hints$/, handler: (q, s, p) => this.handleScriptHints(p[0], q, s) }
57993
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s2, p) => this.handleRunScript(p[0], q, s2) },
57994
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s2, p) => this.handleListFiles(p[0], q, s2) },
57995
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/file$/, handler: (q, s2, p) => this.handleReadFile(p[0], q, s2) },
57996
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/file$/, handler: (q, s2, p) => this.handleWriteFile(p[0], q, s2) },
57997
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/source$/, handler: (q, s2, p) => this.handleSource(p[0], q, s2) },
57998
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/save$/, handler: (q, s2, p) => this.handleSave(p[0], q, s2) },
57999
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/typeAndSend$/, handler: (q, s2, p) => this.handleTypeAndSend(p[0], q, s2) },
58000
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/typeAndSendAt$/, handler: (q, s2, p) => this.handleTypeAndSendAt(p[0], q, s2) },
58001
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/config$/, handler: (q, s2, p) => this.handleProviderConfig(p[0], q, s2) },
58002
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/dom-context$/, handler: (q, s2, p) => this.handleDomContext(p[0], q, s2) },
58003
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/auto-implement$/, handler: (q, s2, p) => this.handleAutoImplement(p[0], q, s2) },
58004
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/auto-implement\/cancel$/, handler: (q, s2, p) => this.handleAutoImplCancel(p[0], q, s2) },
58005
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/auto-implement\/status$/, handler: (q, s2, p) => this.handleAutoImplSSE(p[0], q, s2) },
58006
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/spawn-test$/, handler: (q, s2, p) => this.handleSpawnTest(p[0], q, s2) },
58007
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/validate$/, handler: (q, s2, p) => this.handleValidate(p[0], q, s2) },
58008
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/acp-chat$/, handler: (q, s2, p) => this.handleAcpChat(p[0], q, s2) },
58009
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/script-hints$/, handler: (q, s2, p) => this.handleScriptHints(p[0], q, s2) }
57582
58010
  ];
57583
58011
  matchRoute(method, pathname) {
57584
58012
  for (const route of this.routes) {
@@ -58173,14 +58601,14 @@ var DevServer = class _DevServer {
58173
58601
  warnings.push(...validation.warnings);
58174
58602
  if (config.settings) {
58175
58603
  for (const [key, val] of Object.entries(config.settings)) {
58176
- const s = val;
58177
- if (!s.type) errors.push(`settings.${key}: missing type`);
58178
- else if (!["boolean", "number", "string", "select"].includes(s.type))
58179
- errors.push(`settings.${key}: invalid type '${s.type}'`);
58180
- if (s.default === void 0) warnings.push(`settings.${key}: no default value`);
58181
- if (s.type === "number" && s.min !== void 0 && s.max !== void 0 && s.min > s.max)
58182
- errors.push(`settings.${key}: min (${s.min}) > max (${s.max})`);
58183
- if (s.type === "select" && (!s.options || !Array.isArray(s.options) || s.options.length === 0))
58604
+ const s2 = val;
58605
+ if (!s2.type) errors.push(`settings.${key}: missing type`);
58606
+ else if (!["boolean", "number", "string", "select"].includes(s2.type))
58607
+ errors.push(`settings.${key}: invalid type '${s2.type}'`);
58608
+ if (s2.default === void 0) warnings.push(`settings.${key}: no default value`);
58609
+ if (s2.type === "number" && s2.min !== void 0 && s2.max !== void 0 && s2.min > s2.max)
58610
+ errors.push(`settings.${key}: min (${s2.min}) > max (${s2.max})`);
58611
+ if (s2.type === "select" && (!s2.options || !Array.isArray(s2.options) || s2.options.length === 0))
58184
58612
  errors.push(`settings.${key}: select type requires options[]`);
58185
58613
  }
58186
58614
  }