@adhdev/daemon-standalone 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.js CHANGED
@@ -30036,10 +30036,10 @@ var require_dist3 = __commonJS({
30036
30036
  }
30037
30037
  function getDaemonBuildInfo() {
30038
30038
  if (cached2) return cached2;
30039
- const commit = readInjected(true ? "5d100a177f412ae29a58048f0a211c3928b06910" : void 0) ?? "unknown";
30040
- const commitShort = readInjected(true ? "5d100a17" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
30041
- const version2 = readInjected(true ? "0.9.82-rc.354" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
30042
- const builtAt = readInjected(true ? "2026-06-22T11:32:34.147Z" : void 0);
30039
+ const commit = readInjected(true ? "91500e054cf2b6258f6041f90c18be03ad05a8ad" : void 0) ?? "unknown";
30040
+ const commitShort = readInjected(true ? "91500e05" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
30041
+ const version2 = readInjected(true ? "0.9.82-rc.356" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
30042
+ const builtAt = readInjected(true ? "2026-06-22T22:47:57.762Z" : void 0);
30043
30043
  cached2 = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
30044
30044
  return cached2;
30045
30045
  }
@@ -34593,9 +34593,12 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
34593
34593
  return [];
34594
34594
  }
34595
34595
  }
34596
- function updateDirectDispatchStatus(meshId, sessionId, status) {
34596
+ function updateDirectDispatchStatus(meshId, sessionId, status, taskId) {
34597
34597
  try {
34598
- MeshRuntimeStore.getInstance().updateDirectDispatchStatus(meshId, sessionId, status);
34598
+ if (!taskId) {
34599
+ LOG2.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)`);
34600
+ }
34601
+ MeshRuntimeStore.getInstance().updateDirectDispatchStatus(meshId, sessionId, status, taskId);
34599
34602
  } catch {
34600
34603
  }
34601
34604
  }
@@ -35440,9 +35443,25 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
35440
35443
  updatedAt: r.updated_at
35441
35444
  }));
35442
35445
  }
35443
- updateDirectDispatchStatus(meshId, sessionId, status) {
35444
- if (!sessionId) return;
35446
+ // CANON-B (dispatch identity): mesh_direct_dispatches is keyed by task_id (PK), but a
35447
+ // single session can host several sequential direct dispatches (re-dispatch / nudge), so
35448
+ // matching a status flip by session_id alone hits EVERY non-terminal row for that session
35449
+ // — flipping a sibling task's row and stranding the one whose event actually fired (the
35450
+ // assigned-stranded watchdog then requeues a task that is really still generating). When
35451
+ // the firing event carries a taskId, target the single PK row; the session_id match is the
35452
+ // legacy fallback only for events that arrive without a taskId.
35453
+ updateDirectDispatchStatus(meshId, sessionId, status, taskId) {
35445
35454
  const now = (/* @__PURE__ */ new Date()).toISOString();
35455
+ if (taskId) {
35456
+ this.db.prepare(`
35457
+ UPDATE mesh_direct_dispatches
35458
+ SET status = @status, updated_at = @updatedAt
35459
+ WHERE mesh_id = @meshId AND task_id = @taskId
35460
+ AND status NOT IN ('completed', 'failed')
35461
+ `).run({ status, meshId, taskId, updatedAt: now });
35462
+ return;
35463
+ }
35464
+ if (!sessionId) return;
35446
35465
  this.db.prepare(`
35447
35466
  UPDATE mesh_direct_dispatches
35448
35467
  SET status = @status, updated_at = @updatedAt
@@ -37169,7 +37188,7 @@ ${rendered}`, "utf-8");
37169
37188
  windowsHide: true
37170
37189
  }).trim();
37171
37190
  if (out) {
37172
- const matches = out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
37191
+ const matches = out.split(/\r?\n/).map((s2) => s2.trim()).filter(Boolean);
37173
37192
  const direct = matches.find((m) => DIRECT_EXEC_EXT.has(path10.extname(m).toLowerCase()));
37174
37193
  return direct || matches[0] || command;
37175
37194
  }
@@ -38521,11 +38540,29 @@ Next step: ${nextStep}`;
38521
38540
  (pending) => pending.event === event.event && readRefineJobId2(pending) === jobId
38522
38541
  );
38523
38542
  }
38543
+ function isWeakCompletionMetadata(metadata) {
38544
+ const evidenceLevel = readNonEmptyString2(metadata.evidenceLevel);
38545
+ if (evidenceLevel === "insufficient" || evidenceLevel === "weak") return true;
38546
+ if (metadata.reviewRecommended === true) return true;
38547
+ const diag = readRecord4(metadata.completionDiagnostic);
38548
+ return diag?.finalAssistantPresent === false || diag?.blockReason === "missing_final_assistant";
38549
+ }
38524
38550
  function buildPendingEventFingerprint(event) {
38525
38551
  const metadata = readRecord4(event.metadataEvent) || {};
38526
38552
  if (event.event === "worktree_bootstrap_complete" || event.event === "worktree_bootstrap_failed") {
38527
38553
  return [event.meshId, event.event, event.nodeId || ""].join("::");
38528
38554
  }
38555
+ if (TERMINAL_COMPLETION_EVENTS.has(event.event)) {
38556
+ const terminalTaskId = readNonEmptyString2(metadata.taskId) || readNonEmptyString2(readRecord4(metadata.payload)?.taskId);
38557
+ if (terminalTaskId) {
38558
+ return [
38559
+ event.meshId,
38560
+ event.event,
38561
+ terminalTaskId,
38562
+ isWeakCompletionMetadata(metadata) ? "weak" : "genuine"
38563
+ ].join("::");
38564
+ }
38565
+ }
38529
38566
  const sessionId = resolveEventSessionId(metadata);
38530
38567
  const providerSessionId = readNonEmptyString2(metadata.providerSessionId);
38531
38568
  const taskId = readNonEmptyString2(metadata.taskId) || readNonEmptyString2(readRecord4(metadata.payload)?.taskId);
@@ -38888,6 +38925,7 @@ Next step: ${nextStep}`;
38888
38925
  var import_path9;
38889
38926
  var import_crypto7;
38890
38927
  var REFINE_TERMINAL_EVENTS;
38928
+ var TERMINAL_COMPLETION_EVENTS;
38891
38929
  var MAX_PENDING_EVENTS_BYTES;
38892
38930
  var MAX_PENDING_EVENTS_KEEP;
38893
38931
  var init_mesh_events_pending = __esm2({
@@ -38902,6 +38940,7 @@ Next step: ${nextStep}`;
38902
38940
  init_mesh_events_utils();
38903
38941
  init_dist();
38904
38942
  REFINE_TERMINAL_EVENTS = /* @__PURE__ */ new Set(["refine:completed", "refine:failed"]);
38943
+ TERMINAL_COMPLETION_EVENTS = /* @__PURE__ */ new Set(["agent:generating_completed", "agent:stopped"]);
38905
38944
  MAX_PENDING_EVENTS_BYTES = 100 * 1024;
38906
38945
  MAX_PENDING_EVENTS_KEEP = 50;
38907
38946
  }
@@ -39230,7 +39269,7 @@ Next step: ${nextStep}`;
39230
39269
  evidence
39231
39270
  }
39232
39271
  });
39233
- updateDirectDispatchStatus(args.meshId, args.sessionId, kind === "task_completed" ? "completed" : "failed");
39272
+ updateDirectDispatchStatus(args.meshId, args.sessionId, kind === "task_completed" ? "completed" : "failed", args.taskId);
39234
39273
  markSessionDeliveriesTerminal(args.meshId, args.sessionId, kind === "task_completed" ? "completed" : "failed");
39235
39274
  setImmediate(() => cleanupTerminalDirectDispatches());
39236
39275
  queuePendingMeshCoordinatorEvent({
@@ -39516,8 +39555,8 @@ Next step: ${nextStep}`;
39516
39555
  if (x instanceof RegExp) return x;
39517
39556
  if (x && typeof x === "object" && typeof x.source === "string") {
39518
39557
  try {
39519
- const s = x;
39520
- return new RegExp(s.source, s.flags || "");
39558
+ const s2 = x;
39559
+ return new RegExp(s2.source, s2.flags || "");
39521
39560
  } catch {
39522
39561
  return null;
39523
39562
  }
@@ -40091,6 +40130,36 @@ Next step: ${nextStep}`;
40091
40130
  UNRESOLVED_FORWARD_MAX_AGE_MS = 30 * 60 * 1e3;
40092
40131
  }
40093
40132
  });
40133
+ function s(v) {
40134
+ return typeof v === "string" && v.trim() ? v.trim() : "";
40135
+ }
40136
+ function meshEventTraceKey(ctx) {
40137
+ const segs = [`task=${s(ctx.taskId) || "-"}`];
40138
+ const eventId = s(ctx.eventId);
40139
+ if (eventId) segs.push(`evt=${eventId}`);
40140
+ segs.push(`sess=${s(ctx.sessionId) || "-"}`);
40141
+ const nodeId = s(ctx.nodeId);
40142
+ if (nodeId) segs.push(`node=${nodeId}`);
40143
+ const meshId = s(ctx.meshId);
40144
+ if (meshId) segs.push(`mesh=${meshId}`);
40145
+ const event = s(ctx.event);
40146
+ if (event) segs.push(`event=${event}`);
40147
+ return segs.join(" ");
40148
+ }
40149
+ function traceMeshEventStage(stage, ctx, detail) {
40150
+ LOG2.info(CAT, `[stage:${stage}] ${meshEventTraceKey(ctx)}${detail ? ` \u2014 ${detail}` : ""}`);
40151
+ }
40152
+ function traceMeshEventDrop(reason, ctx, detail) {
40153
+ LOG2.warn(CAT, `[drop:${reason}] ${meshEventTraceKey(ctx)}${detail ? ` \u2014 ${detail}` : ""}`);
40154
+ }
40155
+ var CAT;
40156
+ var init_mesh_event_trace = __esm2({
40157
+ "src/mesh/mesh-event-trace.ts"() {
40158
+ "use strict";
40159
+ init_logger();
40160
+ CAT = "EvtTrace";
40161
+ }
40162
+ });
40094
40163
  function isPlainObject22(value) {
40095
40164
  return !!value && typeof value === "object" && !Array.isArray(value);
40096
40165
  }
@@ -41884,9 +41953,9 @@ ${cleanBody}`;
41884
41953
  }
41885
41954
  function buildSessionEntries(allStates, cdpManagers, options = {}) {
41886
41955
  const sessions = [];
41887
- const ideStates = allStates.filter((s) => s.category === "ide");
41888
- const cliStates = allStates.filter((s) => s.category === "cli");
41889
- const acpStates = allStates.filter((s) => s.category === "acp");
41956
+ const ideStates = allStates.filter((s2) => s2.category === "ide");
41957
+ const cliStates = allStates.filter((s2) => s2.category === "cli");
41958
+ const acpStates = allStates.filter((s2) => s2.category === "acp");
41890
41959
  for (const state of ideStates) {
41891
41960
  sessions.push(buildWorkspaceSession(state, cdpManagers, options));
41892
41961
  for (const ext of state.extensions) {
@@ -42370,6 +42439,15 @@ ${cleanBody}`;
42370
42439
  meshByWorkspaceCache.set(workspace, { mesh, cachedAt: now });
42371
42440
  return mesh;
42372
42441
  }
42442
+ function recoverMeshIdByNodeId(nodeId) {
42443
+ if (!nodeId) return "";
42444
+ for (const mesh of listMeshes()) {
42445
+ if (Array.isArray(mesh.nodes) && mesh.nodes.some((n) => meshNodeIdMatches(n, nodeId))) {
42446
+ return readNonEmptyString2(mesh.id);
42447
+ }
42448
+ }
42449
+ return "";
42450
+ }
42373
42451
  function __resetIdleAutoFastForwardForTests() {
42374
42452
  idleAutoFastForwardLastAttempt.clear();
42375
42453
  }
@@ -43272,6 +43350,13 @@ ${cleanBody}`;
43272
43350
  function injectMeshSystemMessage(components, args) {
43273
43351
  const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
43274
43352
  const eventNodeId = readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId);
43353
+ const traceCtx = {
43354
+ taskId: args.metadataEvent.taskId,
43355
+ sessionId: eventSessionId,
43356
+ nodeId: eventNodeId,
43357
+ meshId: args.meshId,
43358
+ event: args.event
43359
+ };
43275
43360
  const sourceSession = args.sourceInstanceId ? components.instanceManager.getInstance(args.sourceInstanceId) : void 0;
43276
43361
  const workerCoordinatorDaemonId = readNonEmptyString2(
43277
43362
  sourceSession?.getState()?.settings?.meshCoordinatorDaemonId
@@ -43325,6 +43410,7 @@ ${cleanBody}`;
43325
43410
  }
43326
43411
  }
43327
43412
  LOG2.info("MeshEvents", `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || "(unknown session)"}`);
43413
+ traceMeshEventDrop("intentional_cleanup_stop", traceCtx);
43328
43414
  return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
43329
43415
  }
43330
43416
  if (args.event === "monitor:no_progress") {
@@ -43345,6 +43431,7 @@ ${cleanBody}`;
43345
43431
  }
43346
43432
  if (reconciledCompletion?.source === "no_progress_terminal_ledger_suppression") {
43347
43433
  LOG2.info("MeshEvents", `Suppressed no-progress monitor because terminal ledger evidence already exists for session ${eventSessionId || "(unknown session)"}`);
43434
+ traceMeshEventDrop("no_progress_terminal_ledger_suppression", traceCtx, `terminalKind=${reconciledCompletion.terminalLedgerKind}`);
43348
43435
  return {
43349
43436
  success: true,
43350
43437
  forwarded: 0,
@@ -43356,6 +43443,7 @@ ${cleanBody}`;
43356
43443
  }
43357
43444
  if (isDuplicateRefineTerminalEvent(args.meshId, args.event, args.metadataEvent)) {
43358
43445
  LOG2.info("MeshEvents", `Suppressed duplicate ${args.event} for refine job ${readRefineJobId({ metadataEvent: args.metadataEvent })}`);
43446
+ traceMeshEventDrop("duplicate_refine_terminal", traceCtx);
43359
43447
  return { success: true, forwarded: 0, suppressed: true, duplicateRefineTerminalEvent: true };
43360
43448
  }
43361
43449
  const eventTimestamp = readEventTimestamp(args.metadataEvent.timestamp);
@@ -43370,6 +43458,7 @@ ${cleanBody}`;
43370
43458
  });
43371
43459
  if (duplicateApproval) {
43372
43460
  LOG2.info("MeshEvents", `Suppressed duplicate approval event for mesh ${args.meshId} session ${eventSessionId}`);
43461
+ traceMeshEventDrop("duplicate_approval", traceCtx);
43373
43462
  return { success: true, forwarded: 0, suppressed: true, duplicateApproval: true };
43374
43463
  }
43375
43464
  }
@@ -43389,6 +43478,7 @@ ${cleanBody}`;
43389
43478
  const eventFinalSummary = readNonEmptyString2(args.metadataEvent.finalSummary);
43390
43479
  if (terminalProviderSessionId && terminalProviderSessionId === eventProviderSessionId || terminalFinalSummary && terminalFinalSummary === eventFinalSummary || args.metadataEvent.source === "no_progress_reconciliation") {
43391
43480
  LOG2.info("MeshEvents", `Suppressed duplicate completion with existing terminal ledger evidence for mesh ${args.meshId} session ${eventSessionId}`);
43481
+ traceMeshEventDrop("duplicate_completion_terminal_ledger", traceCtx);
43392
43482
  return { success: true, forwarded: 0, suppressed: true, duplicateCompletion: true, terminalLedgerEvidence: true };
43393
43483
  }
43394
43484
  }
@@ -43407,6 +43497,7 @@ ${cleanBody}`;
43407
43497
  });
43408
43498
  if (duplicateCompletion) {
43409
43499
  LOG2.info("MeshEvents", `Suppressed duplicate completion for mesh ${args.meshId} session ${eventSessionId}`);
43500
+ traceMeshEventDrop("duplicate_completion", traceCtx);
43410
43501
  return { success: true, forwarded: 0, suppressed: true, duplicateCompletion: true };
43411
43502
  }
43412
43503
  }
@@ -43425,6 +43516,7 @@ ${cleanBody}`;
43425
43516
  });
43426
43517
  if (duplicateStopped) {
43427
43518
  LOG2.info("MeshEvents", `Suppressed duplicate stopped event for mesh ${args.meshId} session ${eventSessionId}`);
43519
+ traceMeshEventDrop("duplicate_stopped", traceCtx);
43428
43520
  return { success: true, forwarded: 0, suppressed: true, duplicateStopped: true };
43429
43521
  }
43430
43522
  }
@@ -43436,7 +43528,7 @@ ${cleanBody}`;
43436
43528
  });
43437
43529
  const leaveDirectDispatchActive = !task && opts?.tentativeIfDirect === true;
43438
43530
  if (!leaveDirectDispatchActive) {
43439
- updateDirectDispatchStatus(args.meshId, sessionId, outcome);
43531
+ updateDirectDispatchStatus(args.meshId, sessionId, outcome, eventTaskId);
43440
43532
  }
43441
43533
  markSessionDeliveriesTerminal(args.meshId, sessionId, outcome);
43442
43534
  setImmediate(() => cleanupTerminalDirectDispatches());
@@ -43449,7 +43541,7 @@ ${cleanBody}`;
43449
43541
  const nodeId = readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId);
43450
43542
  const providerType = readNonEmptyString2(args.metadataEvent.providerType);
43451
43543
  if (sessionId) {
43452
- directDispatchTaskIdForLedger = resolveActiveDirectDispatchTaskId(args.meshId, sessionId);
43544
+ directDispatchTaskIdForLedger = readNonEmptyString2(args.metadataEvent.taskId) || resolveActiveDirectDispatchTaskId(args.meshId, sessionId);
43453
43545
  const isFalseIdle = isFalseIdleCompletion(args.metadataEvent);
43454
43546
  completedTaskForLedger = markSessionTerminal(sessionId, "completed", eventTimestamp, { tentativeIfDirect: isFalseIdle });
43455
43547
  if (nodeId && providerType) {
@@ -43532,7 +43624,8 @@ ${cleanBody}`;
43532
43624
  }
43533
43625
  }
43534
43626
  if (sessionId) {
43535
- updateDirectDispatchStatus(args.meshId, sessionId, "acked");
43627
+ const startedTaskId = readNonEmptyString2(args.metadataEvent.taskId) || void 0;
43628
+ updateDirectDispatchStatus(args.meshId, sessionId, "acked", startedTaskId);
43536
43629
  const activeDeliveries = (() => {
43537
43630
  try {
43538
43631
  return MeshRuntimeStore.getInstance().getActiveSessionDeliveries(args.meshId, sessionId);
@@ -43540,7 +43633,8 @@ ${cleanBody}`;
43540
43633
  return [];
43541
43634
  }
43542
43635
  })();
43543
- for (const d of activeDeliveries) {
43636
+ const deliveriesToAck = startedTaskId ? activeDeliveries.filter((d) => d.taskId === startedTaskId) : activeDeliveries;
43637
+ for (const d of deliveriesToAck) {
43544
43638
  updateSessionDeliveryStatus(d.id, "acked");
43545
43639
  }
43546
43640
  }
@@ -43554,7 +43648,7 @@ ${cleanBody}`;
43554
43648
  }
43555
43649
  }
43556
43650
  if (sessionId) {
43557
- directDispatchTaskIdForLedger = resolveActiveDirectDispatchTaskId(args.meshId, sessionId);
43651
+ directDispatchTaskIdForLedger = readNonEmptyString2(args.metadataEvent.taskId) || resolveActiveDirectDispatchTaskId(args.meshId, sessionId);
43558
43652
  completedTaskForLedger = markSessionTerminal(sessionId, "failed");
43559
43653
  }
43560
43654
  }
@@ -43700,6 +43794,9 @@ ${cleanBody}`;
43700
43794
  };
43701
43795
  if (queuePendingMeshCoordinatorEvent(pendingEvent)) {
43702
43796
  LOG2.info("MeshEvents", `Queued ${args.event} for coordinator (mesh ${args.meshId}${workerCoordinatorDaemonId ? `, coordinator daemon ${workerCoordinatorDaemonId}` : ""}${workerCoordinatorSessionId ? `, coordinator session ${workerCoordinatorSessionId}` : ""})`);
43797
+ traceMeshEventStage("queued", traceCtx, workerCoordinatorDaemonId ? `coordinatorDaemon=${workerCoordinatorDaemonId}` : "broadcast");
43798
+ } else {
43799
+ traceMeshEventDrop("queue_dedup", traceCtx);
43703
43800
  }
43704
43801
  return { success: true, forwarded: 0 };
43705
43802
  }
@@ -43710,8 +43807,23 @@ ${cleanBody}`;
43710
43807
  }
43711
43808
  const nodeId = readNonEmptyString2(payload.nodeId);
43712
43809
  const workspace = readNonEmptyString2(payload.workspace);
43713
- const meshId = readNonEmptyString2(payload.meshId) || (workspace ? readNonEmptyString2(getCachedMeshByWorkspace(workspace)?.id) : "");
43714
- if (!meshId) return { success: false, error: "meshId required" };
43810
+ const meshId = readNonEmptyString2(payload.meshId) || (workspace ? readNonEmptyString2(getCachedMeshByWorkspace(workspace)?.id) : "") || recoverMeshIdByNodeId(nodeId);
43811
+ if (!meshId) {
43812
+ traceMeshEventDrop("meshId_required", {
43813
+ taskId: payload.taskId,
43814
+ sessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId),
43815
+ nodeId,
43816
+ event: eventName
43817
+ }, workspace ? `workspace=${workspace} unresolved` : "no workspace/nodeId");
43818
+ return { success: false, error: "meshId required" };
43819
+ }
43820
+ traceMeshEventStage("received", {
43821
+ taskId: payload.taskId,
43822
+ sessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId),
43823
+ nodeId,
43824
+ meshId,
43825
+ event: eventName
43826
+ });
43715
43827
  const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
43716
43828
  const relayModalMessage = readNonEmptyString2(payload.modalMessage);
43717
43829
  const relayModalButtons = Array.isArray(payload.modalButtons) ? payload.modalButtons.filter((b) => typeof b === "string" && b.trim().length > 0) : null;
@@ -43792,9 +43904,18 @@ ${cleanBody}`;
43792
43904
  workspace: readNonEmptyString2(routing.workspace) || readNonEmptyString2(event.workspace) || void 0
43793
43905
  };
43794
43906
  const persisted = enqueueUnresolvedDelegateForward(coordinatorDaemonId, eventName, payload);
43907
+ const fwdTraceCtx = {
43908
+ taskId: payload.taskId,
43909
+ sessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId),
43910
+ nodeId: readNonEmptyString2(routing.nodeId) || readNonEmptyString2(event.meshNodeId),
43911
+ event: eventName
43912
+ };
43913
+ traceMeshEventStage("outbox_enqueue", fwdTraceCtx, `coordinatorDaemon=${coordinatorDaemonId} meshId=absent`);
43914
+ traceMeshEventStage("forward_send", fwdTraceCtx, "immediate push");
43795
43915
  Promise.resolve(components.dispatchMeshCommand(coordinatorDaemonId, "mesh_forward_event", payload)).then((result) => {
43796
43916
  if (result && result.success === false) {
43797
43917
  LOG2.warn("MeshEvents", `Immediate forward of ${eventName} to coordinator ${coordinatorDaemonId} rejected (${readNonEmptyString2(result.error) || "no reason"}) \u2014 left queued for retry`);
43918
+ traceMeshEventDrop("immediate_forward_rejected", fwdTraceCtx, readNonEmptyString2(result.error) || "no reason");
43798
43919
  return;
43799
43920
  }
43800
43921
  if (persisted) ackUnresolvedDelegateForwardByFingerprint(coordinatorDaemonId, eventName, payload);
@@ -43862,6 +43983,14 @@ ${cleanBody}`;
43862
43983
  if (isUnroutableDelegateRejection(routing) && forwardUnresolvedDelegateEvent(components, routing, event)) {
43863
43984
  return;
43864
43985
  }
43986
+ if (isUnroutableDelegateRejection(routing)) {
43987
+ traceMeshEventDrop("unroutable", {
43988
+ taskId: event.meshActiveTaskId ?? event.taskId,
43989
+ sessionId: routing.sessionId,
43990
+ nodeId: routing.nodeId,
43991
+ event: event.event
43992
+ }, "no coordinator anchor / mesh_unresolved");
43993
+ }
43865
43994
  recordUnroutableDelegateEvent(routing, event.event);
43866
43995
  return;
43867
43996
  }
@@ -43909,6 +44038,7 @@ ${cleanBody}`;
43909
44038
  init_mesh_events_pending();
43910
44039
  init_mesh_routing();
43911
44040
  init_mesh_unresolved_forward_outbox();
44041
+ init_mesh_event_trace();
43912
44042
  init_snapshot();
43913
44043
  init_repo_mesh_types();
43914
44044
  init_dist();
@@ -44018,6 +44148,13 @@ ${cleanBody}`;
44018
44148
  function injectPendingIntoCoordinator(coordinator, pending) {
44019
44149
  if (!coordinator || !pending.coordinatorMessage) return;
44020
44150
  const force = shouldForceInjectMeshEvent(pending.event);
44151
+ traceMeshEventStage("surfaced", {
44152
+ taskId: pending.metadataEvent?.taskId,
44153
+ sessionId: pending.metadataEvent?.targetSessionId ?? pending.targetCoordinatorSessionId,
44154
+ nodeId: pending.nodeId,
44155
+ meshId: pending.meshId,
44156
+ event: pending.event
44157
+ }, force ? "force-inject" : "inject");
44021
44158
  coordinator.onEvent("send_message", {
44022
44159
  input: { text: pending.coordinatorMessage, textFallback: pending.coordinatorMessage },
44023
44160
  ...force ? { force: true } : {}
@@ -44076,6 +44213,13 @@ ${cleanBody}`;
44076
44213
  });
44077
44214
  if (reclaimed) {
44078
44215
  LOG2.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})`);
44216
+ traceMeshEventDrop("assigned_stranded_reclaim", {
44217
+ taskId: row.id,
44218
+ sessionId: row.assignedSessionId,
44219
+ nodeId: row.assignedNodeId,
44220
+ meshId,
44221
+ event: "agent:generating_completed"
44222
+ }, `unconfirmed ${Math.round((nowMs - dispatchedAtMs) / 1e3)}s \u2192 ${reclaimed.status}`);
44079
44223
  }
44080
44224
  }
44081
44225
  }
@@ -44235,6 +44379,13 @@ ${cleanBody}`;
44235
44379
  try {
44236
44380
  queuePendingMeshCoordinatorEvent(pending);
44237
44381
  LOG2.info("MeshReconcile", `Strict route hold: coordinator session ${wantSession} not live on mesh ${meshId} \u2014 re-queued (${pending.event})`);
44382
+ traceMeshEventDrop("strict_route_hold", {
44383
+ taskId: pending.metadataEvent?.taskId,
44384
+ sessionId: pending.metadataEvent?.targetSessionId ?? wantSession,
44385
+ nodeId: pending.nodeId,
44386
+ meshId,
44387
+ event: pending.event
44388
+ }, `coordinatorSession=${wantSession} not live`);
44238
44389
  } catch (e) {
44239
44390
  LOG2.warn("MeshReconcile", `Strict route re-queue failed for ${pending.event} on mesh ${meshId}: ${e?.message || e}`);
44240
44391
  }
@@ -44258,6 +44409,13 @@ ${cleanBody}`;
44258
44409
  }
44259
44410
  });
44260
44411
  LOG2.warn("MeshReconcile", `Strict route expire: coordinator session ${wantSession} never returned for mesh ${meshId} \u2014 recorded to ledger (recoverable), dropped (${pending.event})`);
44412
+ traceMeshEventDrop("strict_route_expired", {
44413
+ taskId: pending.metadataEvent?.taskId,
44414
+ sessionId: pending.metadataEvent?.targetSessionId ?? wantSession,
44415
+ nodeId: pending.nodeId,
44416
+ meshId,
44417
+ event: pending.event
44418
+ }, `coordinatorSession=${wantSession} never returned`);
44261
44419
  } catch (e) {
44262
44420
  LOG2.warn("MeshReconcile", `Failed to ledger-expire strict-unmatched ${pending.event} for mesh ${meshId}: ${e?.message || e}`);
44263
44421
  }
@@ -44269,15 +44427,24 @@ ${cleanBody}`;
44269
44427
  const entries = peekUnresolvedDelegateForwards();
44270
44428
  if (entries.length === 0) return;
44271
44429
  for (const entry of entries) {
44430
+ const entryTraceCtx = {
44431
+ taskId: entry.payload.taskId,
44432
+ sessionId: readNonEmptyString2(entry.payload.targetSessionId) || readNonEmptyString2(entry.payload.sessionId),
44433
+ nodeId: readNonEmptyString2(entry.payload.nodeId),
44434
+ event: readNonEmptyString2(entry.payload.event)
44435
+ };
44272
44436
  let result;
44273
44437
  try {
44438
+ traceMeshEventStage("forward_send", entryTraceCtx, `retry \u2192 ${entry.coordinatorDaemonId}`);
44274
44439
  result = await dispatchMeshCommand(entry.coordinatorDaemonId, "mesh_forward_event", entry.payload);
44275
44440
  } catch (e) {
44276
44441
  LOG2.warn("MeshReconcile", `Retry forward to coordinator ${entry.coordinatorDaemonId} failed: ${e?.message || e} \u2014 left queued`);
44442
+ traceMeshEventDrop("retry_forward_failed", entryTraceCtx, e?.message || String(e));
44277
44443
  continue;
44278
44444
  }
44279
44445
  if (result && result.success === false) {
44280
44446
  LOG2.warn("MeshReconcile", `Retry forward to coordinator ${entry.coordinatorDaemonId} rejected (${readNonEmptyString2(result.error) || "no reason"}) \u2014 left queued`);
44447
+ traceMeshEventDrop("retry_forward_rejected", entryTraceCtx, readNonEmptyString2(result.error) || "no reason");
44281
44448
  continue;
44282
44449
  }
44283
44450
  ackUnresolvedDelegateForward(entry.id);
@@ -44523,6 +44690,7 @@ ${cleanBody}`;
44523
44690
  init_mesh_events_coordinator();
44524
44691
  init_mesh_unresolved_forward_outbox();
44525
44692
  init_mesh_events_utils();
44693
+ init_mesh_event_trace();
44526
44694
  init_dist();
44527
44695
  init_mesh_work_queue();
44528
44696
  init_mesh_ledger();
@@ -45352,8 +45520,8 @@ ${cleanBody}`;
45352
45520
  }
45353
45521
  function isValidSource(x) {
45354
45522
  if (!x || typeof x !== "object") return false;
45355
- const s = x;
45356
- 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";
45523
+ const s2 = x;
45524
+ 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";
45357
45525
  }
45358
45526
  function deriveSourceName(url2) {
45359
45527
  const m = url2.match(/[/:]([^/:]+)\/([^/]+?)(?:\.git)?$/);
@@ -45409,7 +45577,7 @@ ${cleanBody}`;
45409
45577
  }
45410
45578
  function sourcesProviding(category, type) {
45411
45579
  const inventory = inventoryExternalSources();
45412
- return inventory.filter((s) => (s.providers[category] || []).includes(type)).map((s) => s.sourceName);
45580
+ return inventory.filter((s2) => (s2.providers[category] || []).includes(type)).map((s2) => s2.sourceName);
45413
45581
  }
45414
45582
  function resolveActiveSource(category, type, activeFile) {
45415
45583
  const candidates = sourcesProviding(category, type);
@@ -45617,10 +45785,10 @@ ${cleanBody}`;
45617
45785
  const footers = (spec.withFooter ?? []).map((f) => {
45618
45786
  if (f.kind === "regex") {
45619
45787
  const re = compile2(f.pattern, f.flags ?? "i");
45620
- return { test: (s) => re.test(s) };
45788
+ return { test: (s2) => re.test(s2) };
45621
45789
  }
45622
45790
  const needle = f.pattern.toLowerCase();
45623
- return { test: (s) => s.toLowerCase().includes(needle) };
45791
+ return { test: (s2) => s2.toLowerCase().includes(needle) };
45624
45792
  });
45625
45793
  return { prompt, footers };
45626
45794
  }
@@ -46495,7 +46663,7 @@ ${cont}` : cont;
46495
46663
  }
46496
46664
  resolveModal(buttonIndex) {
46497
46665
  const snap = this.transport.getSnapshot();
46498
- const parseApproval = typeof this.transport.runParseApproval === "function" ? (s) => this.transport.runParseApproval(s.recentOutputBuffer.slice(-500)) : (s) => this.runParseApproval(s);
46666
+ const parseApproval = typeof this.transport.runParseApproval === "function" ? (s2) => this.transport.runParseApproval(s2.recentOutputBuffer.slice(-500)) : (s2) => this.runParseApproval(s2);
46499
46667
  let modal = this.activeModal ?? parseApproval(snap);
46500
46668
  if (!modal && this.runner.hasParseSession()) {
46501
46669
  try {
@@ -49181,22 +49349,23 @@ ${lastSnapshot}`;
49181
49349
  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]));
49182
49350
  let idx = -1;
49183
49351
  for (const c of candidates) {
49352
+ let candIdx = -1;
49184
49353
  if (sec.anchor_last) {
49185
49354
  for (let i = total - 1; i >= 0; i--) {
49186
49355
  if (matchesCandidate(c, i)) {
49187
- idx = i;
49356
+ candIdx = i;
49188
49357
  break;
49189
49358
  }
49190
49359
  }
49191
49360
  } else {
49192
49361
  for (let i = 0; i < total; i++) {
49193
49362
  if (matchesCandidate(c, i)) {
49194
- idx = i;
49363
+ candIdx = i;
49195
49364
  break;
49196
49365
  }
49197
49366
  }
49198
49367
  }
49199
- if (idx !== -1) break;
49368
+ if (candIdx !== -1 && (idx === -1 || candIdx < idx)) idx = candIdx;
49200
49369
  }
49201
49370
  if (idx !== -1) {
49202
49371
  from = idx;
@@ -49248,7 +49417,7 @@ ${lastSnapshot}`;
49248
49417
  }
49249
49418
  function sectionText(sections, sectionId, fullScreen) {
49250
49419
  if (!sectionId) return fullScreen;
49251
- const found = sections.find((s) => s.id === sectionId);
49420
+ const found = sections.find((s2) => s2.id === sectionId);
49252
49421
  return found ? found.text : "";
49253
49422
  }
49254
49423
  function isRegexCondition(c) {
@@ -49376,6 +49545,7 @@ ${lastSnapshot}`;
49376
49545
  const idx = Number(m[1]);
49377
49546
  let label = String(m[2] ?? "").trim();
49378
49547
  if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
49548
+ const current = hasCursorMarker(lines[i]);
49379
49549
  let j = i + 1;
49380
49550
  while (j < lines.length) {
49381
49551
  const next = lines[j];
@@ -49387,7 +49557,7 @@ ${lastSnapshot}`;
49387
49557
  }
49388
49558
  if (buttons.some((b) => b.index === idx)) continue;
49389
49559
  const key = keyTemplate.replace(/\{index\}/g, String(idx));
49390
- buttons.push({ index: idx, label, key });
49560
+ buttons.push({ index: idx, label, key, current });
49391
49561
  i = j - 1;
49392
49562
  }
49393
49563
  } else {
@@ -49399,12 +49569,15 @@ ${lastSnapshot}`;
49399
49569
  if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
49400
49570
  if (buttons.some((b) => b.index === idx)) continue;
49401
49571
  const key = keyTemplate.replace(/\{index\}/g, String(idx));
49402
- buttons.push({ index: idx, label, key });
49572
+ buttons.push({ index: idx, label, key, current: hasCursorMarker(m[0]) });
49403
49573
  }
49404
49574
  }
49405
49575
  buttons.sort((a, b) => a.index - b.index);
49406
49576
  return buttons;
49407
49577
  }
49578
+ function hasCursorMarker(text) {
49579
+ return /^\s*[❯›>]/.test(text);
49580
+ }
49408
49581
  var init_evaluator = __esm2({
49409
49582
  "src/providers/spec/evaluator.ts"() {
49410
49583
  "use strict";
@@ -49414,10 +49587,10 @@ ${lastSnapshot}`;
49414
49587
  return !!raw && typeof raw === "object" && raw.$schema === "adhdev:cli/spec@4";
49415
49588
  }
49416
49589
  function initialState(spec) {
49417
- return spec.states.find((s) => s.initial) ?? spec.states[0];
49590
+ return spec.states.find((s2) => s2.initial) ?? spec.states[0];
49418
49591
  }
49419
49592
  function stateById(spec, id) {
49420
- return spec.states.find((s) => s.id === id);
49593
+ return spec.states.find((s2) => s2.id === id);
49421
49594
  }
49422
49595
  function outgoingTransitions(spec, stateId) {
49423
49596
  const matches = spec.transitions.filter((t) => {
@@ -49504,7 +49677,17 @@ ${lastSnapshot}`;
49504
49677
  const result = evaluateCondition(cond, sections, fullScreen, cursor, prevLines, legacyTrace, stateId);
49505
49678
  const kind = isRegex(cond) ? "regex" : "changed";
49506
49679
  const detail = isRegex(cond) ? `${cond.section ?? "*"}~/${cond.matches}/` : `cursor_above=${cond.cursor_above} changed=${cond.changed}`;
49507
- return { kind, result, detail };
49680
+ let matchedText;
49681
+ if (result && isRegex(cond)) {
49682
+ try {
49683
+ const hay = sectionText(sections, cond.section, fullScreen);
49684
+ const re = new RegExp(cond.matches, cond.flags ?? "i");
49685
+ const m = re.exec(hay);
49686
+ if (m && m[0]) matchedText = m[0].replace(/\s+/g, " ").trim().slice(0, 160);
49687
+ } catch {
49688
+ }
49689
+ }
49690
+ return matchedText ? { kind, result, detail, matchedText } : { kind, result, detail };
49508
49691
  }
49509
49692
  return { kind: "all", result: false, detail: "unknown condition" };
49510
49693
  }
@@ -49617,17 +49800,17 @@ ${lastSnapshot}`;
49617
49800
  }
49618
49801
  const ids = /* @__PURE__ */ new Set();
49619
49802
  let initialCount = 0;
49620
- for (const [i, s] of spec.states.entries()) {
49621
- if (!s.id) {
49803
+ for (const [i, s2] of spec.states.entries()) {
49804
+ if (!s2.id) {
49622
49805
  errs.push(`states[${i}].id is required`);
49623
49806
  continue;
49624
49807
  }
49625
- if (ids.has(s.id)) errs.push(`states[${i}].id "${s.id}" is duplicated`);
49626
- ids.add(s.id);
49627
- if (!s.label) errs.push(`states[${i}].label is required`);
49628
- if (s.initial) initialCount += 1;
49629
- if (s.status && !["idle", "generating", "approval"].includes(s.status)) {
49630
- errs.push(`states[${i}].status "${s.status}" must be idle|generating|approval`);
49808
+ if (ids.has(s2.id)) errs.push(`states[${i}].id "${s2.id}" is duplicated`);
49809
+ ids.add(s2.id);
49810
+ if (!s2.label) errs.push(`states[${i}].label is required`);
49811
+ if (s2.initial) initialCount += 1;
49812
+ if (s2.status && !["idle", "generating", "approval"].includes(s2.status)) {
49813
+ errs.push(`states[${i}].status "${s2.status}" must be idle|generating|approval`);
49631
49814
  }
49632
49815
  }
49633
49816
  if (initialCount === 0) errs.push("exactly one state must have initial:true (none found)");
@@ -49643,10 +49826,10 @@ ${lastSnapshot}`;
49643
49826
  else if (!ids.has(t.to)) errs.push(`transitions[${i}].to references unknown state "${t.to}"`);
49644
49827
  if (t.when) errs.push(...validateCondition(t.when, sectionIds, `transitions[${i}].when`));
49645
49828
  }
49646
- for (const [i, s] of spec.states.entries()) {
49647
- const sec = s.extract?.title?.section;
49829
+ for (const [i, s2] of spec.states.entries()) {
49830
+ const sec = s2.extract?.title?.section;
49648
49831
  if (sec && !sectionIds.has(sec)) errs.push(`states[${i}].extract.title.section "${sec}" unknown`);
49649
- const bsec = s.extract?.buttons?.section;
49832
+ const bsec = s2.extract?.buttons?.section;
49650
49833
  if (bsec && !sectionIds.has(bsec)) errs.push(`states[${i}].extract.buttons.section "${bsec}" unknown`);
49651
49834
  }
49652
49835
  return errs;
@@ -62273,10 +62456,10 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62273
62456
  const path422 = require("path");
62274
62457
  const { spawnSync: spawnSync2 } = require("child_process");
62275
62458
  const file2 = ext.loadExternalSources();
62276
- if (file2.sources.some((s) => s.name === requestedName)) {
62459
+ if (file2.sources.some((s2) => s2.name === requestedName)) {
62277
62460
  return { success: false, error: `source name "${requestedName}" is already registered` };
62278
62461
  }
62279
- if (file2.sources.some((s) => s.url === url2 && s.ref === ref)) {
62462
+ if (file2.sources.some((s2) => s2.url === url2 && s2.ref === ref)) {
62280
62463
  return { success: false, error: `source url+ref already registered (use a different name to track another ref)` };
62281
62464
  }
62282
62465
  const sourceDir = path422.join(ext.externalRoot(), requestedName);
@@ -62338,7 +62521,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62338
62521
  const fs322 = require("fs");
62339
62522
  const path422 = require("path");
62340
62523
  const file2 = ext.loadExternalSources();
62341
- const match = file2.sources.find((s) => s.name === name);
62524
+ const match = file2.sources.find((s2) => s2.name === name);
62342
62525
  if (!match) return { success: false, error: `source "${name}" not registered` };
62343
62526
  const sourceDir = path422.join(ext.externalRoot(), name);
62344
62527
  if (fs322.existsSync(sourceDir)) {
@@ -62350,7 +62533,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62350
62533
  }
62351
62534
  ext.saveExternalSources({
62352
62535
  schema: 1,
62353
- sources: file2.sources.filter((s) => s.name !== name)
62536
+ sources: file2.sources.filter((s2) => s2.name !== name)
62354
62537
  });
62355
62538
  const active = ext.loadProvidersActive();
62356
62539
  const filteredActive = {};
@@ -62374,10 +62557,10 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62374
62557
  const file2 = ext.loadExternalSources();
62375
62558
  const inventory = ext.inventoryExternalSources();
62376
62559
  const active = ext.loadProvidersActive();
62377
- const sources = file2.sources.map((s) => {
62378
- const inv = inventory.find((e) => e.sourceName === s.name);
62560
+ const sources = file2.sources.map((s2) => {
62561
+ const inv = inventory.find((e) => e.sourceName === s2.name);
62379
62562
  return {
62380
- ...s,
62563
+ ...s2,
62381
62564
  providers: inv?.providers ?? {}
62382
62565
  };
62383
62566
  });
@@ -62544,6 +62727,21 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62544
62727
  var path21 = __toESM2(require("path"));
62545
62728
  init_terminal_screen();
62546
62729
  var import_session_host_core6 = require_dist();
62730
+ var MAX_PTY_EVENTS = 300;
62731
+ var EVENT_CONTENT_CAP = 240;
62732
+ function escapeControl(text) {
62733
+ return String(text).replace(/[\x00-\x1f\x7f]/g, (ch) => {
62734
+ const code = ch.charCodeAt(0);
62735
+ if (ch === "\r") return "\\r";
62736
+ if (ch === "\n") return "\\n";
62737
+ if (ch === " ") return "\\t";
62738
+ if (code === 27) return "\\x1b";
62739
+ return "\\x" + code.toString(16).padStart(2, "0");
62740
+ });
62741
+ }
62742
+ function capPreview(text) {
62743
+ return text.length > EVENT_CONTENT_CAP ? text.slice(0, EVENT_CONTENT_CAP) + `\u2026(+${text.length - EVENT_CONTENT_CAP})` : text;
62744
+ }
62547
62745
  var TerminalAdapter = class {
62548
62746
  constructor(opts, handlers) {
62549
62747
  this.opts = opts;
@@ -62570,6 +62768,9 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62570
62768
  screenTimer = null;
62571
62769
  tickTimer = null;
62572
62770
  lastScreen = "";
62771
+ /** Debug-only ring buffer of PTY input/output/resize/cursor events. */
62772
+ events = [];
62773
+ lastCursorKey = "";
62573
62774
  start() {
62574
62775
  const env2 = this.opts.envIsComplete ? this.opts.env ?? {} : { ...process.env, ...this.opts.env ?? {} };
62575
62776
  this.pty = this.factory.spawn(this.opts.binary, this.opts.args ?? [], {
@@ -62578,10 +62779,12 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62578
62779
  cols: this.cols,
62579
62780
  rows: this.rows
62580
62781
  });
62782
+ this.recordEvent("spawn", `${this.opts.binary} (${this.cols}x${this.rows})`);
62581
62783
  this.handlers.init?.({ pid: this.pty.pid });
62582
62784
  this.pty.onData((chunk) => this.onChunk(chunk));
62583
62785
  this.pty.onExit((info) => {
62584
62786
  this.stopTimers();
62787
+ this.recordEvent("exit", `exitCode=${typeof info.exitCode === "number" ? info.exitCode : 0}`);
62585
62788
  this.handlers.on_exit?.({ exitCode: typeof info.exitCode === "number" ? info.exitCode : 0 });
62586
62789
  this.pty = null;
62587
62790
  });
@@ -62592,6 +62795,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62592
62795
  resize(cols, rows) {
62593
62796
  this.cols = cols;
62594
62797
  this.rows = rows;
62798
+ this.recordEvent("resize", `${cols}x${rows}`);
62595
62799
  this.pty?.resize(cols, rows);
62596
62800
  this.screen.resize(rows, cols);
62597
62801
  }
@@ -62612,8 +62816,21 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62612
62816
  return { row: pos.row, col: pos.col };
62613
62817
  }
62614
62818
  send_keys(text) {
62819
+ this.recordEvent("input", capPreview(escapeControl(text)), text.length);
62615
62820
  this.pty?.write(text);
62616
62821
  }
62822
+ /** Debug-only: most-recent PTY input/output/resize/cursor events, oldest
62823
+ * first. Pure observation — never consulted by the FSM. */
62824
+ getEventTimeline(limit = MAX_PTY_EVENTS) {
62825
+ const n = Math.max(0, Math.min(limit, this.events.length));
62826
+ return this.events.slice(this.events.length - n);
62827
+ }
62828
+ recordEvent(kind, content, bytes) {
62829
+ const ev = { ts: Date.now(), kind, content };
62830
+ if (typeof bytes === "number") ev.bytes = bytes;
62831
+ this.events.push(ev);
62832
+ if (this.events.length > MAX_PTY_EVENTS) this.events.splice(0, this.events.length - MAX_PTY_EVENTS);
62833
+ }
62617
62834
  kill() {
62618
62835
  this.stopTimers();
62619
62836
  try {
@@ -62624,6 +62841,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62624
62841
  this.screen.dispose();
62625
62842
  }
62626
62843
  onChunk(chunk) {
62844
+ this.recordEvent("output", capPreview(escapeControl(chunk)), chunk.length);
62627
62845
  try {
62628
62846
  this.handlers.on_pty_data?.(chunk);
62629
62847
  } catch {
@@ -62633,6 +62851,12 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62633
62851
  this.screenTimer = setTimeout(() => {
62634
62852
  this.screenTimer = null;
62635
62853
  const snap = this.computeScreen();
62854
+ const cur = this.screen.getCursorPosition();
62855
+ const curKey = `${cur.row},${cur.col}`;
62856
+ if (curKey !== this.lastCursorKey) {
62857
+ this.lastCursorKey = curKey;
62858
+ this.recordEvent("cursor", `(${cur.row},${cur.col})`);
62859
+ }
62636
62860
  if (snap === this.lastScreen) return;
62637
62861
  this.lastScreen = snap;
62638
62862
  try {
@@ -62711,20 +62935,40 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62711
62935
  }
62712
62936
  }
62713
62937
  init_logger();
62714
- function countNewlines(s) {
62938
+ function countNewlines(s2) {
62715
62939
  let n = 0;
62716
- for (let i = 0; i < s.length; i += 1) if (s.charCodeAt(i) === 10) n += 1;
62940
+ for (let i = 0; i < s2.length; i += 1) if (s2.charCodeAt(i) === 10) n += 1;
62717
62941
  return n;
62718
62942
  }
62719
62943
  var SUBMIT_DELAY_FLOOR_MS = 200;
62720
62944
  var WIN32_SUBMIT_RESEND_GAP_MS = 350;
62721
62945
  var WIN32_SUBMIT_MAX_RESENDS = 14;
62946
+ var WIN32_SUBMIT_SETTLE_MS = 500;
62947
+ var WIN32_SUBMIT_MAX_SETTLE_WAIT_MS = 1e4;
62948
+ var WIN32_SUBMIT_SETTLE_POLL_MS = 120;
62949
+ var WIN32_PTY_WRITE_CHUNK_CHARS = 1024;
62950
+ var WIN32_PTY_WRITE_CHUNK_GAP_MS = 8;
62722
62951
  function resolveSubmitDelayMs(specBeforeSubmit, text) {
62723
62952
  const lines = countNewlines(text);
62724
62953
  const linesBonus = Math.min(800, lines * 80);
62725
62954
  const spec = typeof specBeforeSubmit === "number" && specBeforeSubmit > 0 ? specBeforeSubmit : 0;
62726
62955
  return Math.max(spec, SUBMIT_DELAY_FLOOR_MS + linesBonus);
62727
62956
  }
62957
+ function chunkPreservingSurrogates(text, size) {
62958
+ const chunks = [];
62959
+ let offset = 0;
62960
+ while (offset < text.length) {
62961
+ let end = Math.min(text.length, offset + size);
62962
+ if (end < text.length) {
62963
+ const code = text.charCodeAt(end - 1);
62964
+ if (code >= 55296 && code <= 56319) end -= 1;
62965
+ }
62966
+ if (end <= offset) end = Math.min(text.length, offset + size);
62967
+ chunks.push(text.slice(offset, end));
62968
+ offset = end;
62969
+ }
62970
+ return chunks;
62971
+ }
62728
62972
  function guessExt(mime) {
62729
62973
  if (/png/i.test(mime)) return ".png";
62730
62974
  if (/jpe?g/i.test(mime)) return ".jpg";
@@ -62740,7 +62984,10 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62740
62984
  this.buildAdapterOpts(),
62741
62985
  {
62742
62986
  init: () => this.emitInitialState(),
62743
- on_pty_data: (chunk) => this.emit({ kind: "pty_data", chunk }),
62987
+ on_pty_data: (chunk) => {
62988
+ this.lastPtyDataAt = Date.now();
62989
+ this.emit({ kind: "pty_data", chunk });
62990
+ },
62744
62991
  on_screen_changed: () => this.reevaluate(),
62745
62992
  on_exit: ({ exitCode }) => this.handleExit(exitCode)
62746
62993
  }
@@ -62774,6 +63021,16 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62774
63021
  * WIN32_SUBMIT_* and scheduleWin32Submit). Re-arms itself until the FSM
62775
63022
  * leaves idle (submitted) or the resend budget is spent. */
62776
63023
  win32SubmitTimer = null;
63024
+ /** Wall-clock (ms) of the most recent raw PTY output chunk. Advances on every
63025
+ * on_pty_data — including the echo of text written into the composer — so the
63026
+ * win32 submit settle-gate can tell when input has finished landing. */
63027
+ lastPtyDataAt = 0;
63028
+ /** Wall-clock (ms) of the most recent win32 message-body input write. Bridges
63029
+ * the gap between writing a chunk and its echo so the settle-gate does not
63030
+ * declare "quiet" mid-write. */
63031
+ lastWin32WriteAt = 0;
63032
+ /** Pending paced chunk-write timer for a large win32 body (see writeWin32Body). */
63033
+ win32WriteTimer = null;
62777
63034
  currentEval = null;
62778
63035
  stateHistory = [];
62779
63036
  prevStateAt = 0;
@@ -62894,6 +63151,10 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62894
63151
  clearTimeout(this.win32SubmitTimer);
62895
63152
  this.win32SubmitTimer = null;
62896
63153
  }
63154
+ if (this.win32WriteTimer) {
63155
+ clearTimeout(this.win32WriteTimer);
63156
+ this.win32WriteTimer = null;
63157
+ }
62897
63158
  this.specWatcher?.close();
62898
63159
  this.adapter.kill();
62899
63160
  }
@@ -62924,11 +63185,15 @@ ${formatManifestValidationIssues2(validation.issues)}`,
62924
63185
  getFsmSnapshotHistory() {
62925
63186
  return this.fsmSnapshotHistory;
62926
63187
  }
63188
+ /** Debug-only PTY input/output/resize/cursor timeline from the adapter. */
63189
+ getEventTimeline(limit) {
63190
+ return this.adapter.getEventTimeline(limit);
63191
+ }
62927
63192
  getSections() {
62928
63193
  try {
62929
63194
  const screen = this.adapter.snapshot();
62930
63195
  const lines = screen.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
62931
- return resolveSections(this.spec.sections ?? {}, lines).map((s) => ({ id: s.id, text: s.text }));
63196
+ return resolveSections(this.spec.sections ?? {}, lines).map((s2) => ({ id: s2.id, text: s2.text }));
62932
63197
  } catch {
62933
63198
  return null;
62934
63199
  }
@@ -63306,7 +63571,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
63306
63571
  const perChar = sm.delay_ms_per_char ?? 0;
63307
63572
  const beforeSubmit = resolveSubmitDelayMs(sm.delay_ms_before_submit, text);
63308
63573
  if (process.platform === "win32") {
63309
- this.adapter.send_keys(text);
63574
+ this.writeWin32Body(text);
63310
63575
  this.scheduleWin32Submit(sm.submit_key, beforeSubmit);
63311
63576
  return;
63312
63577
  }
@@ -63332,20 +63597,72 @@ ${formatManifestValidationIssues2(validation.issues)}`,
63332
63597
  const st = stateById(this.spec, this.currentStateId);
63333
63598
  return st ? statusForState(st) : "idle";
63334
63599
  }
63600
+ /** Record a win32 body write so the settle-gate counts it as input activity
63601
+ * even before the echo arrives. */
63602
+ markWin32Write() {
63603
+ this.lastWin32WriteAt = Date.now();
63604
+ }
63605
+ /** Most recent win32 input activity — a write we issued OR a PTY output chunk
63606
+ * (echo). The submit settle-gate waits for this to go quiet. */
63607
+ lastWin32InputActivityAt() {
63608
+ return Math.max(this.lastPtyDataAt, this.lastWin32WriteAt);
63609
+ }
63335
63610
  /**
63336
- * win32 verification-based submit. Sends the submit key, waits a gap, and if
63337
- * the FSM is still 'idle' (the prompt did not submit — the CR was absorbed as
63338
- * a multiline-paste newline) resends, up to WIN32_SUBMIT_MAX_RESENDS. The
63339
- * first CR always fires (so a stale/edge status never suppresses the submit);
63340
- * subsequent resends are gated on still being idle, and stop the instant the
63341
- * agent leaves idle (submitted generating / approval). This converges the
63342
- * nondeterministic multiline window without spamming Enter into the next turn.
63611
+ * Write the message body to the PTY for win32, paced into bounded chunks. A
63612
+ * single unbounded ConPTY write can overflow the input pipe and drop leading
63613
+ * bytes; splitting it with a short inter-chunk gap keeps the console input
63614
+ * buffer from overflowing. Small bodies still go out in a single write. Each
63615
+ * chunk advances lastWin32WriteAt so the submit settle-gate keeps waiting until
63616
+ * the final chunk is out and echoed.
63617
+ */
63618
+ writeWin32Body(text) {
63619
+ if (this.win32WriteTimer) {
63620
+ clearTimeout(this.win32WriteTimer);
63621
+ this.win32WriteTimer = null;
63622
+ }
63623
+ if (text.length <= WIN32_PTY_WRITE_CHUNK_CHARS) {
63624
+ this.markWin32Write();
63625
+ this.adapter.send_keys(text);
63626
+ return;
63627
+ }
63628
+ const chunks = chunkPreservingSurrogates(text, WIN32_PTY_WRITE_CHUNK_CHARS);
63629
+ let idx = 0;
63630
+ const writeNext = () => {
63631
+ this.win32WriteTimer = null;
63632
+ if (idx >= chunks.length) return;
63633
+ this.markWin32Write();
63634
+ this.adapter.send_keys(chunks[idx]);
63635
+ idx += 1;
63636
+ if (idx < chunks.length) {
63637
+ this.win32WriteTimer = setTimeout(writeNext, WIN32_PTY_WRITE_CHUNK_GAP_MS);
63638
+ }
63639
+ };
63640
+ writeNext();
63641
+ }
63642
+ /**
63643
+ * win32 submit. Two phases:
63644
+ *
63645
+ * Phase 1 (settle-gate): hold the first CR until the PTY output has been quiet
63646
+ * for WIN32_SUBMIT_SETTLE_MS after the last input write — i.e. the full
63647
+ * (possibly multi-KB / multiline) body has finished arriving in the composer
63648
+ * and echoing. Honors an initial minimum delay and is bounded by
63649
+ * WIN32_SUBMIT_MAX_SETTLE_WAIT_MS so a noisy screen can never hang the submit.
63650
+ * This is what stops a long message from being submitted half-arrived (its
63651
+ * leading lines lost). A short message settles almost immediately.
63652
+ *
63653
+ * Phase 2 (verified resend — unchanged): send the submit key, wait a gap, and
63654
+ * if the FSM is still 'idle' (the CR was absorbed as a multiline-paste
63655
+ * newline) resend, up to WIN32_SUBMIT_MAX_RESENDS. The first CR always fires
63656
+ * (a stale/edge status never suppresses it); resends are gated on still being
63657
+ * idle and stop the instant the agent leaves idle (submitted → generating /
63658
+ * approval). This preserves the win32 lone-CR-swallow handling.
63343
63659
  */
63344
63660
  scheduleWin32Submit(submitKey, initialDelayMs) {
63345
63661
  if (this.win32SubmitTimer) {
63346
63662
  clearTimeout(this.win32SubmitTimer);
63347
63663
  this.win32SubmitTimer = null;
63348
63664
  }
63665
+ const startedAt = Date.now();
63349
63666
  const fire = (attempt) => {
63350
63667
  this.win32SubmitTimer = null;
63351
63668
  this.adapter.send_keys(submitKey);
@@ -63358,8 +63675,20 @@ ${formatManifestValidationIssues2(validation.issues)}`,
63358
63675
  fire(attempt + 1);
63359
63676
  }, WIN32_SUBMIT_RESEND_GAP_MS);
63360
63677
  };
63361
- if (initialDelayMs > 0) this.win32SubmitTimer = setTimeout(() => fire(0), initialDelayMs);
63362
- else fire(0);
63678
+ const waitForSettle = () => {
63679
+ this.win32SubmitTimer = null;
63680
+ const now = Date.now();
63681
+ const quietFor = now - this.lastWin32InputActivityAt();
63682
+ const waited = now - startedAt;
63683
+ if (quietFor >= WIN32_SUBMIT_SETTLE_MS || waited >= WIN32_SUBMIT_MAX_SETTLE_WAIT_MS) {
63684
+ fire(0);
63685
+ return;
63686
+ }
63687
+ const recheckIn = Math.min(WIN32_SUBMIT_SETTLE_MS - quietFor, WIN32_SUBMIT_SETTLE_POLL_MS);
63688
+ this.win32SubmitTimer = setTimeout(waitForSettle, Math.max(recheckIn, 30));
63689
+ };
63690
+ if (initialDelayMs > 0) this.win32SubmitTimer = setTimeout(waitForSettle, initialDelayMs);
63691
+ else waitForSettle();
63363
63692
  }
63364
63693
  handleClickControl(controlId, payload) {
63365
63694
  const ctl = (this.spec.control_bar ?? []).find((c) => c.id === controlId);
@@ -63394,6 +63723,19 @@ ${formatManifestValidationIssues2(validation.issues)}`,
63394
63723
  if (!m) return;
63395
63724
  const btn = m.buttons.find((b) => b.index === index);
63396
63725
  if (!btn) return;
63726
+ const rule = stateById(this.spec, this.currentStateId)?.extract?.buttons;
63727
+ if (rule?.select_mode === "arrow_keys") {
63728
+ const from = m.buttons.find((b) => b.current)?.index ?? 1;
63729
+ const up = rule.cursor_keys?.up ?? "\x1B[A";
63730
+ const down = rule.cursor_keys?.down ?? "\x1B[B";
63731
+ const delta = btn.index - from;
63732
+ const step = delta >= 0 ? down : up;
63733
+ const nav = step.repeat(Math.abs(delta));
63734
+ const confirm = (rule.key_for_index || "\r").replace(/\{index\}/g, "") || "\r";
63735
+ if (nav) this.adapter.send_keys(nav);
63736
+ this.adapter.send_keys(confirm);
63737
+ return;
63738
+ }
63397
63739
  this.adapter.send_keys(btn.key);
63398
63740
  }
63399
63741
  handleAttachImage(blob, mime) {
@@ -63472,7 +63814,8 @@ ${formatManifestValidationIssues2(validation.issues)}`,
63472
63814
  return out;
63473
63815
  }
63474
63816
  function flattenCond(c, out, depth) {
63475
- out.push(`${" ".repeat(depth)}${c.kind} ${c.detail} = ${c.result}${c.remainingMs ? ` (${c.remainingMs}ms left)` : ""}`);
63817
+ const matched = c.matchedText ? ` matched=${JSON.stringify(c.matchedText)}` : "";
63818
+ out.push(`${" ".repeat(depth)}${c.kind} ${c.detail} = ${c.result}${c.remainingMs ? ` (${c.remainingMs}ms left)` : ""}${matched}`);
63476
63819
  for (const child of c.children ?? []) flattenCond(child, out, depth + 1);
63477
63820
  }
63478
63821
  function findStable(c) {
@@ -64188,8 +64531,8 @@ ${formatManifestValidationIssues2(validation.issues)}`,
64188
64531
  }
64189
64532
  return null;
64190
64533
  }
64191
- function oneLine(s, max) {
64192
- const flat = s.replace(/\s+/g, " ").trim();
64534
+ function oneLine(s2, max) {
64535
+ const flat = s2.replace(/\s+/g, " ").trim();
64193
64536
  return flat.length > max ? flat.slice(0, max - 1) + "\u2026" : flat;
64194
64537
  }
64195
64538
  function parseTimestamp(v) {
@@ -64209,10 +64552,10 @@ ${formatManifestValidationIssues2(validation.issues)}`,
64209
64552
  return null;
64210
64553
  }
64211
64554
  function normalizeRole(r) {
64212
- const s = String(r ?? "").toLowerCase();
64213
- if (s === "user" || s === "human" || s === "user_explicit") return "user";
64214
- if (s === "assistant" || s === "ai" || s === "model") return "assistant";
64215
- if (s === "tool" || s === "tool_result" || s === "function") return "assistant";
64555
+ const s2 = String(r ?? "").toLowerCase();
64556
+ if (s2 === "user" || s2 === "human" || s2 === "user_explicit") return "user";
64557
+ if (s2 === "assistant" || s2 === "ai" || s2 === "model") return "assistant";
64558
+ if (s2 === "tool" || s2 === "tool_result" || s2 === "function") return "assistant";
64216
64559
  return "system";
64217
64560
  }
64218
64561
  function stringifyContent(v) {
@@ -64260,18 +64603,18 @@ ${formatManifestValidationIssues2(validation.issues)}`,
64260
64603
  return (record2) => ors.some((ands) => ands.every((t) => evalTerm(t, record2)));
64261
64604
  }
64262
64605
  function parseTerm(src) {
64263
- let s = src.trim();
64606
+ let s2 = src.trim();
64264
64607
  let negate = false;
64265
- if (s.startsWith("!")) {
64608
+ if (s2.startsWith("!")) {
64266
64609
  negate = true;
64267
- s = s.slice(1).trim();
64610
+ s2 = s2.slice(1).trim();
64268
64611
  }
64269
- const fnMatch = s.match(/^(startsWith|endsWith|contains)\s*\(\s*(.+?)\s*,\s*(.+?)\s*\)$/);
64612
+ const fnMatch = s2.match(/^(startsWith|endsWith|contains)\s*\(\s*(.+?)\s*,\s*(.+?)\s*\)$/);
64270
64613
  if (fnMatch) {
64271
64614
  const [, op2, pathExpr, litExpr] = fnMatch;
64272
64615
  return { path: pathExpr, op: op2, lit: parseLiteral(litExpr), negate };
64273
64616
  }
64274
- const opMatch = s.match(/^(.+?)\s*(==|!=|>=|<=|>|<)\s*(.+)$/);
64617
+ const opMatch = s2.match(/^(.+?)\s*(==|!=|>=|<=|>|<)\s*(.+)$/);
64275
64618
  if (!opMatch) return null;
64276
64619
  const [, lhs, op, rhsRaw] = opMatch;
64277
64620
  return { path: lhs.trim(), op, lit: parseLiteral(rhsRaw.trim()), negate };
@@ -64626,9 +64969,12 @@ ${formatManifestValidationIssues2(validation.issues)}`,
64626
64969
  * — not this code — decides how a selection is keyed for each CLI.
64627
64970
  */
64628
64971
  async selectPickerChoice(ctl, action, choiceIndex, choiceLabel) {
64629
- this.driver.dispatch({ kind: "click_control", control_id: ctl.id });
64630
- await this.waitForPickerRendered(action);
64631
- const options = this.extractPickerChoices(action);
64972
+ let options = this.extractPickerChoicesIfRendered(action);
64973
+ if (!options) {
64974
+ this.driver.dispatch({ kind: "click_control", control_id: ctl.id });
64975
+ await this.waitForPickerRendered(action);
64976
+ options = this.extractPickerChoices(action);
64977
+ }
64632
64978
  let index = choiceIndex;
64633
64979
  if ((index == null || !Number.isFinite(index)) && choiceLabel) {
64634
64980
  const needle = choiceLabel.trim().toLowerCase();
@@ -64641,8 +64987,27 @@ ${formatManifestValidationIssues2(validation.issues)}`,
64641
64987
  if (index == null || !Number.isFinite(index)) {
64642
64988
  return { ok: false, error: "choiceIndex or choiceLabel required to select" };
64643
64989
  }
64644
- const keys = (action.submit_key || "{index}\r").replace(/\{index\}/g, String(index));
64645
- this.driver.dispatch({ kind: "pty_write", data: keys });
64990
+ if (action.select_mode === "arrow_keys") {
64991
+ const current = options.find((o) => o.current);
64992
+ if (current == null) {
64993
+ return {
64994
+ ok: false,
64995
+ error: "arrow-nav picker: current cursor row not detected on screen",
64996
+ controlResult: { options: options.map((o) => ({ value: o.label, label: o.label, current: o.current })) }
64997
+ };
64998
+ }
64999
+ const up = action.cursor_keys?.up ?? "\x1B[A";
65000
+ const down = action.cursor_keys?.down ?? "\x1B[B";
65001
+ const delta = index - current.index;
65002
+ const step = delta >= 0 ? down : up;
65003
+ const nav = step.repeat(Math.abs(delta));
65004
+ const confirm = (action.submit_key || "\r").replace(/\{index\}/g, "") || "\r";
65005
+ if (nav) this.driver.dispatch({ kind: "pty_write", data: nav });
65006
+ this.driver.dispatch({ kind: "pty_write", data: confirm });
65007
+ } else {
65008
+ const keys = (action.submit_key || "{index}\r").replace(/\{index\}/g, String(index));
65009
+ this.driver.dispatch({ kind: "pty_write", data: keys });
65010
+ }
64646
65011
  const selected = options.find((o) => o.index === index);
64647
65012
  return {
64648
65013
  ok: true,
@@ -64654,6 +65019,20 @@ ${formatManifestValidationIssues2(validation.issues)}`,
64654
65019
  }
64655
65020
  };
64656
65021
  }
65022
+ /** Parse the picker choices only if the picker already appears rendered on
65023
+ * the live screen (its `wait_for` condition currently matches and at least
65024
+ * one choice parses). Returns the parsed choices when open, else null so
65025
+ * the caller knows it must send the trigger to open it. Used to de-dup the
65026
+ * picker open in {@link selectPickerChoice}. */
65027
+ extractPickerChoicesIfRendered(action) {
65028
+ const wf = action.wait_for;
65029
+ if (wf?.regex) {
65030
+ const re = new RegExp(wf.regex, wf.flags ?? "i");
65031
+ if (!re.test(this.readScreenSectionText(wf.section))) return null;
65032
+ }
65033
+ const options = this.extractPickerChoices(action);
65034
+ return options.length > 0 ? options : null;
65035
+ }
64657
65036
  /** Poll the live screen until the picker's `wait_for` condition matches,
64658
65037
  * up to a short budget. Returns true if it rendered, false on timeout. */
64659
65038
  async waitForPickerRendered(action) {
@@ -64701,7 +65080,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
64701
65080
  try {
64702
65081
  const sections = this.driver.getSections();
64703
65082
  if (sectionId && sections) {
64704
- const hit = sections.find((s) => s.id === sectionId);
65083
+ const hit = sections.find((s2) => s2.id === sectionId);
64705
65084
  if (hit) return hit.text;
64706
65085
  }
64707
65086
  return this.driver.getScreen();
@@ -64716,7 +65095,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
64716
65095
  screen = this.driver.snapshot();
64717
65096
  const driverSections = this.driver.getSections?.();
64718
65097
  if (driverSections) {
64719
- sections = Object.fromEntries(driverSections.map((s) => [s.id, s.text]));
65098
+ sections = Object.fromEntries(driverSections.map((s2) => [s2.id, s2.text]));
64720
65099
  } else {
64721
65100
  sections = this.readCurrentScreenSections(screen);
64722
65101
  }
@@ -64762,6 +65141,10 @@ ${formatManifestValidationIssues2(validation.issues)}`,
64762
65141
  // answers "why did this rule fire" after the fact, unlike the live
64763
65142
  // `fsm` field which only reflects the current instant.
64764
65143
  fsmHistory: this.driver.getFsmSnapshotHistory?.() ?? null,
65144
+ // PTY input/output/resize/cursor event timeline (debug-only) so the
65145
+ // snapshot shows what we typed / what the PTY printed around each
65146
+ // status transition. Null for drivers without the timeline.
65147
+ eventTimeline: this.driver.getEventTimeline?.() ?? null,
64765
65148
  // Extended fields
64766
65149
  name: this.cliName,
64767
65150
  status: this.getStatus().status,
@@ -65126,6 +65509,8 @@ ${formatManifestValidationIssues2(validation.issues)}`,
65126
65509
  // v4 FSM transition snapshot history — the captured pre-transition
65127
65510
  // evaluation table at each transition (null for v3 specs).
65128
65511
  fsmHistory: this.driver.getFsmSnapshotHistory?.() ?? null,
65512
+ // PTY input/output/resize/cursor event timeline (debug-only).
65513
+ eventTimeline: this.driver.getEventTimeline?.() ?? null,
65129
65514
  messages,
65130
65515
  committedMessages: messages
65131
65516
  };
@@ -65166,6 +65551,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
65166
65551
  return new ProviderCliAdapter(provider, workingDir, cliArgs, extraEnv, transportFactory);
65167
65552
  }
65168
65553
  init_logger();
65554
+ init_mesh_event_trace();
65169
65555
  init_control_effects();
65170
65556
  init_approval_utils();
65171
65557
  init_provider_patch_state();
@@ -66209,6 +66595,23 @@ ${formatManifestValidationIssues2(validation.issues)}`,
66209
66595
  if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
66210
66596
  this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
66211
66597
  }
66598
+ // EVTTRACE (observation-only): is this a mesh worker session whose completion
66599
+ // events must route to a coordinator? Used purely to gate trace logging so a
66600
+ // non-mesh CLI session's completions don't add EvtTrace noise. No decision logic.
66601
+ isMeshWorkerSession() {
66602
+ return !!(this.settings.meshNodeFor || this.settings.meshActiveTaskId || this.settings.meshNodeId || this.settings.launchedByCoordinator);
66603
+ }
66604
+ // EVTTRACE correlation context for this session's completion lifecycle. taskId is
66605
+ // the primary grep anchor; instanceId is the session fallback.
66606
+ meshTraceCtx(event = "agent:generating_completed") {
66607
+ return {
66608
+ taskId: this.settings.meshActiveTaskId,
66609
+ sessionId: this.instanceId,
66610
+ nodeId: this.settings.meshNodeId,
66611
+ meshId: this.settings.meshNodeFor,
66612
+ event
66613
+ };
66614
+ }
66212
66615
  flushCompletedDebounceIfFinalized() {
66213
66616
  const pending = this.completedDebouncePending;
66214
66617
  if (!pending) {
@@ -66229,24 +66632,33 @@ ${formatManifestValidationIssues2(validation.issues)}`,
66229
66632
  if (block2) {
66230
66633
  const blockReason = block2.reason;
66231
66634
  const waitedMs = Date.now() - pending.firstObservedAt;
66232
- LOG2.debug("CLI", `[${this.type}] finalization block: reason=${blockReason} terminal=${block2.terminal} waitedMs=${waitedMs} maxWait=${COMPLETED_FINALIZATION_MAX_WAIT_MS}`);
66233
- if (block2.terminal && !block2.allowTimeout || waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
66635
+ const isTranscriptEvidenceGate = block2.allowTimeout === true;
66636
+ LOG2.debug("CLI", `[${this.type}] finalization block: reason=${blockReason} terminal=${block2.terminal} allowTimeout=${isTranscriptEvidenceGate} waitedMs=${waitedMs} maxWait=${COMPLETED_FINALIZATION_MAX_WAIT_MS}`);
66637
+ if (!isTranscriptEvidenceGate && (block2.terminal || waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS)) {
66234
66638
  if (pending.loggedBlockReason !== blockReason) {
66235
66639
  LOG2.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
66640
+ if (this.isMeshWorkerSession()) {
66641
+ traceMeshEventDrop("completion_gate_hold", this.meshTraceCtx(), `${blockReason} waited=${waitedMs}ms`);
66642
+ }
66236
66643
  pending.loggedBlockReason = blockReason;
66237
66644
  }
66238
66645
  this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
66239
66646
  return;
66240
66647
  }
66648
+ const emittedAfterFinalizationTimeout = waitedMs >= COMPLETED_FINALIZATION_MAX_WAIT_MS;
66241
66649
  const completionDiagnostic = this.buildCompletedFinalizationDiagnostic({
66242
66650
  blockReason,
66243
66651
  latestStatus,
66244
66652
  latestVisibleStatus,
66245
66653
  waitedMs,
66246
66654
  pending,
66247
- emittedAfterFinalizationTimeout: true
66655
+ emittedAfterFinalizationTimeout
66248
66656
  });
66249
- LOG2.warn("CLI", `[${this.type}] emitting completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
66657
+ completionDiagnostic.decoupledImmediateEmit = isTranscriptEvidenceGate && !emittedAfterFinalizationTimeout;
66658
+ LOG2.warn("CLI", `[${this.type}] emitting completed event (${isTranscriptEvidenceGate && !emittedAfterFinalizationTimeout ? "CANON-C decoupled-immediate, transcript pending" : `after ${waitedMs}ms`}) without finalized assistant turn (${blockReason})`);
66659
+ if (this.isMeshWorkerSession()) {
66660
+ traceMeshEventStage("fired", this.meshTraceCtx(), `forced after ${waitedMs}ms (${blockReason})`);
66661
+ }
66250
66662
  this.pushEvent({
66251
66663
  event: "agent:generating_completed",
66252
66664
  chatTitle: pending.chatTitle,
@@ -66269,6 +66681,9 @@ ${formatManifestValidationIssues2(validation.issues)}`,
66269
66681
  return;
66270
66682
  }
66271
66683
  LOG2.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
66684
+ if (this.isMeshWorkerSession()) {
66685
+ traceMeshEventStage("fired", this.meshTraceCtx(), `duration=${pending.duration}s`);
66686
+ }
66272
66687
  this.pushEvent({
66273
66688
  event: "agent:generating_completed",
66274
66689
  chatTitle: pending.chatTitle,
@@ -66507,6 +66922,9 @@ ${formatManifestValidationIssues2(validation.issues)}`,
66507
66922
  if (missingEvidence && !hasMeshContext) {
66508
66923
  LOG2.info("CLI", `[${this.type}] short completion suppressed: missing final assistant evidence, no mesh context (source=${shortEvidenceSource})`);
66509
66924
  } else {
66925
+ if (this.isMeshWorkerSession()) {
66926
+ traceMeshEventStage("fired", this.meshTraceCtx(), `short-generating idle (source=${shortEvidenceSource})`);
66927
+ }
66510
66928
  this.pushEvent({
66511
66929
  event: "agent:generating_completed",
66512
66930
  chatTitle,
@@ -66583,6 +67001,9 @@ ${formatManifestValidationIssues2(validation.issues)}`,
66583
67001
  const monitorParsedStatus = parsedStatus;
66584
67002
  for (const me of monitorEvents) {
66585
67003
  if (me.type === "monitor:no_progress" && this.completionHasFinalAssistantMessage(monitorParsedStatus?.messages) && !this.hasAdapterPendingResponse() && !hasNonEmptyCliModalButtons(monitorParsedStatus?.activeModal ?? monitorParsedStatus?.modal)) {
67004
+ if (this.isMeshWorkerSession()) {
67005
+ traceMeshEventStage("fired", this.meshTraceCtx(), "no_progress_monitor_final_summary");
67006
+ }
66586
67007
  this.pushEvent({
66587
67008
  event: "agent:generating_completed",
66588
67009
  chatTitle,
@@ -66623,6 +67044,12 @@ ${formatManifestValidationIssues2(validation.issues)}`,
66623
67044
  workspace: typeof event.workspace === "string" && event.workspace.trim() ? event.workspace : this.workingDir,
66624
67045
  providerSessionId: typeof event.providerSessionId === "string" && event.providerSessionId.trim() ? event.providerSessionId : this.providerSessionId
66625
67046
  };
67047
+ if (this.isMeshWorkerSession() && this.settings.meshActiveTaskId) {
67048
+ const existingTaskId = typeof enrichedEvent.taskId === "string" && enrichedEvent.taskId.trim() ? enrichedEvent.taskId : void 0;
67049
+ if (!existingTaskId) {
67050
+ enrichedEvent.taskId = this.settings.meshActiveTaskId;
67051
+ }
67052
+ }
66626
67053
  if (this.context?.emitProviderEvent) {
66627
67054
  this.context.emitProviderEvent(enrichedEvent);
66628
67055
  } else {
@@ -70422,7 +70849,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
70422
70849
  }
70423
70850
  if (buf.length === 0) return null;
70424
70851
  const strings = extractStringsFromBuffer(buf);
70425
- const meaningful = strings.filter((s) => s.length >= MIN_PRINTABLE_RUN && /\w/.test(s));
70852
+ const meaningful = strings.filter((s2) => s2.length >= MIN_PRINTABLE_RUN && /\w/.test(s2));
70426
70853
  if (meaningful.length === 0) return null;
70427
70854
  const content = meaningful.join("\n");
70428
70855
  const sourceMtimeMs = statMtimeMs3(filePath);
@@ -70628,10 +71055,10 @@ Run 'adhdev doctor' for detailed diagnostics.`
70628
71055
  };
70629
71056
  }
70630
71057
  function normalizeHermesRole(r) {
70631
- const s = String(r ?? "").toLowerCase();
70632
- if (s === "user" || s === "human") return "user";
70633
- if (s === "assistant" || s === "ai" || s === "model") return "assistant";
70634
- if (s === "tool" || s === "tool_result" || s === "function") return "assistant";
71058
+ const s2 = String(r ?? "").toLowerCase();
71059
+ if (s2 === "user" || s2 === "human") return "user";
71060
+ if (s2 === "assistant" || s2 === "ai" || s2 === "model") return "assistant";
71061
+ if (s2 === "tool" || s2 === "tool_result" || s2 === "function") return "assistant";
70635
71062
  return "system";
70636
71063
  }
70637
71064
  function createNativeHistoryDispatcher(reader) {
@@ -70855,10 +71282,10 @@ Run 'adhdev doctor' for detailed diagnostics.`
70855
71282
  }
70856
71283
  }
70857
71284
  function normalizeRole2(r) {
70858
- const s = String(r ?? "").toLowerCase();
70859
- if (s === "user" || s === "human") return "user";
70860
- if (s === "assistant" || s === "ai" || s === "model") return "assistant";
70861
- if (s === "tool" || s === "tool_result" || s === "function") return "assistant";
71285
+ const s2 = String(r ?? "").toLowerCase();
71286
+ if (s2 === "user" || s2 === "human") return "user";
71287
+ if (s2 === "assistant" || s2 === "ai" || s2 === "model") return "assistant";
71288
+ if (s2 === "tool" || s2 === "tool_result" || s2 === "function") return "assistant";
70862
71289
  return "system";
70863
71290
  }
70864
71291
  function registerProviderScriptRootSafely(root) {
@@ -70876,7 +71303,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
70876
71303
  const actionType = ctl?.action?.type;
70877
71304
  if (!id || !actionType) return;
70878
71305
  const label = typeof ctl?.label === "string" && ctl.label.trim() ? ctl.label : id;
70879
- const visibleWhenState = Array.isArray(ctl?.visible_when_state) ? ctl.visible_when_state.filter((s) => typeof s === "string") : void 0;
71306
+ const visibleWhenState = Array.isArray(ctl?.visible_when_state) ? ctl.visible_when_state.filter((s2) => typeof s2 === "string") : void 0;
70880
71307
  if (actionType === "open_picker") {
70881
71308
  out.push({
70882
71309
  id,
@@ -77762,7 +78189,7 @@ ${mergeTreeErr?.stderr || ""}`;
77762
78189
  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.";
77763
78190
  if (!firstFailedCmd) return base;
77764
78191
  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 : "";
77765
- const rawOutput = [firstFailedCmd.stdout, firstFailedCmd.stderr, firstFailedCmd.output].filter((s) => typeof s === "string" && s.length > 0).join("\n");
78192
+ const rawOutput = [firstFailedCmd.stdout, firstFailedCmd.stderr, firstFailedCmd.output].filter((s2) => typeof s2 === "string" && s2.length > 0).join("\n");
77766
78193
  const tail = rawOutput.length > 800 ? rawOutput.slice(-800) : rawOutput;
77767
78194
  return [
77768
78195
  base,
@@ -78513,7 +78940,7 @@ ${hintLines.join("\n")}` : "",
78513
78940
  convergence = "blocked_review";
78514
78941
  }
78515
78942
  const fbcs = result.finalBranchConvergenceState && typeof result.finalBranchConvergenceState === "object" ? result.finalBranchConvergenceState : void 0;
78516
- const stage = Array.isArray(result.refineStages) ? result.refineStages.filter((s) => s.status === "failed").map((s) => s.stage).filter(Boolean).pop() : void 0;
78943
+ const stage = Array.isArray(result.refineStages) ? result.refineStages.filter((s2) => s2.status === "failed").map((s2) => s2.stage).filter(Boolean).pop() : void 0;
78517
78944
  results.push({
78518
78945
  nodeId: node.id,
78519
78946
  workspace: node.workspace,
@@ -79510,7 +79937,7 @@ ${hintLines.join("\n")}` : "",
79510
79937
  return {
79511
79938
  success: true,
79512
79939
  screenLineCount: lines.length,
79513
- sections: resolved.map((s) => ({ id: s.id, fromLine: s.fromLine, toLine: s.toLine, text: s.text }))
79940
+ sections: resolved.map((s2) => ({ id: s2.id, fromLine: s2.fromLine, toLine: s2.toLine, text: s2.text }))
79514
79941
  };
79515
79942
  } catch (e) {
79516
79943
  return { success: false, error: `resolve failed: ${e.message}` };
@@ -80079,7 +80506,7 @@ ${hintLines.join("\n")}` : "",
80079
80506
  if (!meshId) return { success: false, error: "meshId required" };
80080
80507
  try {
80081
80508
  const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2, describeTaskDependencyState: describeTaskDependencyState2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
80082
- const status = Array.isArray(args?.status) ? args.status.map((s) => typeof s === "string" ? s.trim() : "").filter(Boolean) : void 0;
80509
+ const status = Array.isArray(args?.status) ? args.status.map((s2) => typeof s2 === "string" ? s2.trim() : "").filter(Boolean) : void 0;
80083
80510
  const rawQueue = getQueue2(meshId, { status });
80084
80511
  const statusById = new Map(getQueue2(meshId).map((task) => [task.id, task.status]));
80085
80512
  const queue = rawQueue.map((task) => Array.isArray(task.dependsOn) && task.dependsOn.length > 0 ? { ...task, ...describeTaskDependencyState2(task, statusById) } : task);
@@ -80360,7 +80787,7 @@ ${hintLines.join("\n")}` : "",
80360
80787
  nodeDaemonId = typeof node?.daemonId === "string" ? node.daemonId.trim() : void 0;
80361
80788
  }
80362
80789
  const selfDaemonId = this.deps.statusInstanceId;
80363
- const isRemote = nodeDaemonId && selfDaemonId && nodeDaemonId !== selfDaemonId;
80790
+ const isRemote = nodeDaemonId && selfDaemonId && !daemonIdsEquivalent(nodeDaemonId, selfDaemonId);
80364
80791
  if (isRemote && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
80365
80792
  const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "fast_forward_mesh_node", {
80366
80793
  ...typeof args === "object" && args !== null ? args : {},
@@ -80394,7 +80821,7 @@ ${hintLines.join("\n")}` : "",
80394
80821
  nodeDaemonId = typeof node?.daemonId === "string" ? node.daemonId.trim() : void 0;
80395
80822
  }
80396
80823
  const selfDaemonId = this.deps.statusInstanceId;
80397
- const isRemote = nodeDaemonId && selfDaemonId && nodeDaemonId !== selfDaemonId;
80824
+ const isRemote = nodeDaemonId && selfDaemonId && !daemonIdsEquivalent(nodeDaemonId, selfDaemonId);
80398
80825
  if (isRemote && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
80399
80826
  const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "get_mesh_node_logs", {
80400
80827
  ...typeof args === "object" && args !== null ? args : {},
@@ -80442,7 +80869,7 @@ ${hintLines.join("\n")}` : "",
80442
80869
  const forwardNode = meshRecordForForward?.mesh?.nodes?.find((n) => meshNodeIdMatches(n, nodeId));
80443
80870
  const nodeDaemonId = typeof forwardNode?.daemonId === "string" ? forwardNode.daemonId.trim() : void 0;
80444
80871
  const selfDaemonId = this.deps.statusInstanceId;
80445
- const isRemote = nodeDaemonId && selfDaemonId && nodeDaemonId !== selfDaemonId;
80872
+ const isRemote = nodeDaemonId && selfDaemonId && !daemonIdsEquivalent(nodeDaemonId, selfDaemonId);
80446
80873
  if (isRemote && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
80447
80874
  const callerCoordinatorDaemonId = typeof args?.coordinatorDaemonId === "string" && args.coordinatorDaemonId.trim() ? args.coordinatorDaemonId.trim() : void 0;
80448
80875
  const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "refine_mesh_node", {
@@ -80529,7 +80956,7 @@ ${hintLines.join("\n")}` : "",
80529
80956
  let worktreeCleanup;
80530
80957
  if (node?.isLocalWorktree) {
80531
80958
  const nodeDaemonId = typeof node.daemonId === "string" ? node.daemonId.trim() : void 0;
80532
- const isRemoteWorktree = nodeDaemonId && nodeDaemonId !== this.deps.statusInstanceId && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch;
80959
+ const isRemoteWorktree = nodeDaemonId && !daemonIdsEquivalent(nodeDaemonId, this.deps.statusInstanceId) && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch;
80533
80960
  if (isRemoteWorktree) {
80534
80961
  const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "remove_mesh_node", {
80535
80962
  ...typeof args === "object" && args !== null ? args : {},
@@ -80611,7 +81038,7 @@ ${hintLines.join("\n")}` : "",
80611
81038
  const sourceNode = mesh.nodes?.find((n) => meshNodeIdMatches(n, sourceNodeId));
80612
81039
  if (!sourceNode) return { success: false, error: `Source node '${sourceNodeId}' not found in mesh` };
80613
81040
  const sourceDaemonId = typeof sourceNode.daemonId === "string" ? sourceNode.daemonId.trim() : void 0;
80614
- if (sourceDaemonId && sourceDaemonId !== this.deps.statusInstanceId && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
81041
+ if (sourceDaemonId && !daemonIdsEquivalent(sourceDaemonId, this.deps.statusInstanceId) && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
80615
81042
  const forwarded = await this.deps.dispatchMeshCommand(sourceDaemonId, "clone_mesh_node", {
80616
81043
  ...typeof args === "object" && args !== null ? args : {},
80617
81044
  _meshDirectDispatch: true
@@ -80853,7 +81280,7 @@ ${hintLines.join("\n")}` : "",
80853
81280
  if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
80854
81281
  if (!node.isLocalWorktree) return { success: false, error: "Node is not a local worktree node" };
80855
81282
  const nodeDaemonId = typeof node.daemonId === "string" ? node.daemonId.trim() : void 0;
80856
- if (nodeDaemonId && nodeDaemonId !== this.deps.statusInstanceId && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
81283
+ if (nodeDaemonId && !daemonIdsEquivalent(nodeDaemonId, this.deps.statusInstanceId) && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
80857
81284
  const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "retry_mesh_node_bootstrap", {
80858
81285
  ...typeof args === "object" && args !== null ? args : {},
80859
81286
  _meshDirectDispatch: true
@@ -82061,16 +82488,16 @@ ${ptyResult.output.slice(-2e3)}`);
82061
82488
  const now = this.lastStatusSentAt;
82062
82489
  const target = opts?.p2pOnly ? "P2P" : serverConnected ? "P2P+Server" : "P2P";
82063
82490
  const allStates = this.deps.instanceManager.collectAllStates();
82064
- const ideStates = allStates.filter((s) => s.category === "ide");
82065
- const cliStates = allStates.filter((s) => s.category === "cli");
82066
- const acpStates = allStates.filter((s) => s.category === "acp");
82067
- const ideSummary = ideStates.map((s) => {
82068
- const msgs = s.activeChat?.messages?.length || 0;
82069
- const exts = s.extensions.length;
82070
- return `${s.type}(${s.status},${msgs}msg,${exts}ext)`;
82491
+ const ideStates = allStates.filter((s2) => s2.category === "ide");
82492
+ const cliStates = allStates.filter((s2) => s2.category === "cli");
82493
+ const acpStates = allStates.filter((s2) => s2.category === "acp");
82494
+ const ideSummary = ideStates.map((s2) => {
82495
+ const msgs = s2.activeChat?.messages?.length || 0;
82496
+ const exts = s2.extensions.length;
82497
+ return `${s2.type}(${s2.status},${msgs}msg,${exts}ext)`;
82071
82498
  }).join(", ");
82072
- const cliSummary = cliStates.map((s) => `${s.type}(${s.status})`).join(", ");
82073
- const acpSummary = acpStates.map((s) => `${s.type}(${s.status})`).join(", ");
82499
+ const cliSummary = cliStates.map((s2) => `${s2.type}(${s2.status})`).join(", ");
82500
+ const acpSummary = acpStates.map((s2) => `${s2.type}(${s2.status})`).join(", ");
82074
82501
  const logLevel = opts?.p2pOnly ? "debug" : "info";
82075
82502
  const baseSummary = `IDE: ${ideStates.length} [${ideSummary}] CLI: ${cliStates.length} [${cliSummary}] ACP: ${acpStates.length} [${acpSummary}]`;
82076
82503
  const summaryChanged = baseSummary !== this.lastStatusSummary;
@@ -82181,10 +82608,10 @@ ${ptyResult.output.slice(-2e3)}`);
82181
82608
  }
82182
82609
  return false;
82183
82610
  }
82184
- simpleHash(s) {
82611
+ simpleHash(s2) {
82185
82612
  let h = 2166136261;
82186
- for (let i = 0; i < s.length; i++) {
82187
- h ^= s.charCodeAt(i);
82613
+ for (let i = 0; i < s2.length; i++) {
82614
+ h ^= s2.charCodeAt(i);
82188
82615
  h = h * 16777619 >>> 0;
82189
82616
  }
82190
82617
  return h.toString(36);
@@ -83392,7 +83819,7 @@ ${ptyResult.output.slice(-2e3)}`);
83392
83819
  * Per-category status collect
83393
83820
  */
83394
83821
  collectStatesByCategory(category) {
83395
- return this.collectAllStates().filter((s) => s.category === category);
83822
+ return this.collectAllStates().filter((s2) => s2.category === category);
83396
83823
  }
83397
83824
  // ─── Tick engine ─────────────────────────────────
83398
83825
  /**
@@ -85278,9 +85705,9 @@ async (params) => {
85278
85705
  function findCliTarget(ctx, type, instanceId) {
85279
85706
  if (!ctx.instanceManager) return null;
85280
85707
  const cliStates = ctx.instanceManager.collectAllStates().filter(isCliTargetState);
85281
- if (instanceId) return cliStates.find((s) => s.instanceId === instanceId) || null;
85708
+ if (instanceId) return cliStates.find((s2) => s2.instanceId === instanceId) || null;
85282
85709
  if (!type) return cliStates[cliStates.length - 1] || null;
85283
- const matches = cliStates.filter((s) => s.type === type);
85710
+ const matches = cliStates.filter((s2) => s2.type === type);
85284
85711
  return matches[matches.length - 1] || null;
85285
85712
  }
85286
85713
  function getCliTargetBundle(ctx, type, instanceId) {
@@ -85643,20 +86070,20 @@ async (params) => {
85643
86070
  return;
85644
86071
  }
85645
86072
  const allStates = ctx.instanceManager.collectAllStates();
85646
- const cliStates = allStates.filter((s) => s.category === "cli" || s.category === "acp");
85647
- const result = cliStates.map((s) => ({
85648
- instanceId: s.instanceId,
85649
- type: s.type,
85650
- name: s.name,
85651
- category: s.category,
85652
- status: s.status,
85653
- mode: s.mode,
85654
- workspace: s.workspace,
85655
- messageCount: s.activeChat?.messages?.length || 0,
85656
- lastMessage: s.activeChat?.messages?.slice(-1)[0] || null,
85657
- activeModal: s.activeChat?.activeModal || null,
85658
- pendingEvents: s.pendingEvents || [],
85659
- settings: s.settings
86073
+ const cliStates = allStates.filter((s2) => s2.category === "cli" || s2.category === "acp");
86074
+ const result = cliStates.map((s2) => ({
86075
+ instanceId: s2.instanceId,
86076
+ type: s2.type,
86077
+ name: s2.name,
86078
+ category: s2.category,
86079
+ status: s2.status,
86080
+ mode: s2.mode,
86081
+ workspace: s2.workspace,
86082
+ messageCount: s2.activeChat?.messages?.length || 0,
86083
+ lastMessage: s2.activeChat?.messages?.slice(-1)[0] || null,
86084
+ activeModal: s2.activeChat?.activeModal || null,
86085
+ pendingEvents: s2.pendingEvents || [],
86086
+ settings: s2.settings
85660
86087
  }));
85661
86088
  ctx.json(res, 200, { instances: result, count: result.length });
85662
86089
  }
@@ -85745,9 +86172,9 @@ async (params) => {
85745
86172
  }
85746
86173
  if (ctx.instanceManager) {
85747
86174
  const allStates = ctx.instanceManager.collectAllStates();
85748
- const cliStates = allStates.filter((s) => s.category === "cli" || s.category === "acp");
85749
- for (const s of cliStates) {
85750
- ctx.sendCliSSE({ event: "snapshot", providerType: s.type, status: s.status, instanceId: s.instanceId });
86175
+ const cliStates = allStates.filter((s2) => s2.category === "cli" || s2.category === "acp");
86176
+ for (const s2 of cliStates) {
86177
+ ctx.sendCliSSE({ event: "snapshot", providerType: s2.type, status: s2.status, instanceId: s2.instanceId });
85751
86178
  }
85752
86179
  }
85753
86180
  _req.on("close", () => {
@@ -85763,7 +86190,7 @@ async (params) => {
85763
86190
  const target = findCliTarget(ctx, type);
85764
86191
  if (!target) {
85765
86192
  const allStates = ctx.instanceManager.collectAllStates();
85766
- ctx.json(res, 404, { error: `No running instance for: ${type}`, available: allStates.filter((s) => s.category === "cli" || s.category === "acp").map((s) => s.type) });
86193
+ ctx.json(res, 404, { error: `No running instance for: ${type}`, available: allStates.filter((s2) => s2.category === "cli" || s2.category === "acp").map((s2) => s2.type) });
85767
86194
  return;
85768
86195
  }
85769
86196
  const instance = ctx.instanceManager.getInstance(target.instanceId);
@@ -85809,7 +86236,7 @@ async (params) => {
85809
86236
  const allStates = ctx.instanceManager.collectAllStates();
85810
86237
  ctx.json(res, 404, {
85811
86238
  error: `No running instance for: ${type}`,
85812
- available: allStates.filter((s) => s.category === "cli" || s.category === "acp").map((s) => s.type)
86239
+ available: allStates.filter((s2) => s2.category === "cli" || s2.category === "acp").map((s2) => s2.type)
85813
86240
  });
85814
86241
  return;
85815
86242
  }
@@ -86612,7 +87039,7 @@ async (params) => {
86612
87039
  child.write("\x1B[12;1R");
86613
87040
  ctx.log("Terminal CPR request (\\x1b[6n) intercepted in PTY, responding with dummy coordinates [12;1R]");
86614
87041
  }
86615
- checkAutoApproval(data, (s) => child.write(s));
87042
+ checkAutoApproval(data, (s2) => child.write(s2));
86616
87043
  sendAutoImplSSE(ctx, { event: "output", data: { chunk: data, stream: "stdout" } });
86617
87044
  scheduleAutoStopForVerification();
86618
87045
  });
@@ -86625,7 +87052,7 @@ async (params) => {
86625
87052
  stdout += chunk;
86626
87053
  clearAutoStopTimer();
86627
87054
  if (chunk.includes("\x1B[6n")) child.stdin?.write("\x1B[1;1R");
86628
- checkAutoApproval(chunk, (s) => child.stdin?.write(s));
87055
+ checkAutoApproval(chunk, (s2) => child.stdin?.write(s2));
86629
87056
  sendAutoImplSSE(ctx, { event: "output", data: { chunk, stream: "stdout" } });
86630
87057
  scheduleAutoStopForVerification();
86631
87058
  });
@@ -86633,7 +87060,7 @@ async (params) => {
86633
87060
  const chunk = d.toString();
86634
87061
  stderr += chunk;
86635
87062
  clearAutoStopTimer();
86636
- checkAutoApproval(chunk, (s) => child.stdin?.write(s));
87063
+ checkAutoApproval(chunk, (s2) => child.stdin?.write(s2));
86637
87064
  sendAutoImplSSE(ctx, { event: "output", data: { chunk, stream: "stderr" } });
86638
87065
  scheduleAutoStopForVerification();
86639
87066
  });
@@ -87446,59 +87873,59 @@ data: ${JSON.stringify(msg.data)}
87446
87873
  // ─── Route Table ─────────────────────────────────────
87447
87874
  routes = [
87448
87875
  // Static routes
87449
- { method: "GET", pattern: "/api/providers", handler: (q, s) => this.handleListProviders(q, s) },
87450
- { method: "GET", pattern: "/api/providers/source-config", handler: (q, s) => this.handleGetProviderSourceConfig(q, s) },
87451
- { method: "POST", pattern: "/api/providers/source-config", handler: (q, s) => this.handleSetProviderSourceConfig(q, s) },
87452
- { method: "GET", pattern: "/api/providers/versions", handler: (q, s) => this.handleDetectVersions(q, s) },
87453
- { method: "POST", pattern: "/api/providers/reload", handler: (q, s) => this.handleReload(q, s) },
87454
- { method: "POST", pattern: "/api/cdp/evaluate", handler: (q, s) => this.handleCdpEvaluate(q, s) },
87455
- { method: "POST", pattern: "/api/cdp/click", handler: (q, s) => this.handleCdpClick(q, s) },
87456
- { method: "POST", pattern: "/api/cdp/dom/query", handler: (q, s) => this.handleCdpDomQuery(q, s) },
87457
- { method: "POST", pattern: "/api/cdp/dom/inspect", handler: (q, s) => this.handleDomInspect(q, s) },
87458
- { method: "POST", pattern: "/api/cdp/dom/children", handler: (q, s) => this.handleDomChildren(q, s) },
87459
- { method: "POST", pattern: "/api/cdp/dom/analyze", handler: (q, s) => this.handleDomAnalyze(q, s) },
87460
- { method: "POST", pattern: "/api/cdp/dom/find-text", handler: (q, s) => this.handleFindByText(q, s) },
87461
- { method: "POST", pattern: "/api/cdp/dom/find-common", handler: (q, s) => this.handleFindCommon(q, s) },
87462
- { method: "GET", pattern: "/api/cdp/screenshot", handler: (q, s) => this.handleScreenshot(q, s) },
87463
- { method: "GET", pattern: "/api/cdp/targets", handler: (q, s) => this.handleCdpTargets(q, s) },
87464
- { method: "POST", pattern: "/api/scripts/run", handler: (q, s) => this.handleScriptsRun(q, s) },
87465
- { method: "GET", pattern: "/api/status", handler: (q, s) => this.handleStatus(q, s) },
87466
- { method: "POST", pattern: "/api/watch/start", handler: (q, s) => this.handleWatchStart(q, s) },
87467
- { method: "POST", pattern: "/api/watch/stop", handler: (q, s) => this.handleWatchStop(q, s) },
87468
- { method: "GET", pattern: "/api/watch/events", handler: (q, s) => this.handleSSE(q, s) },
87469
- { method: "POST", pattern: "/api/scaffold", handler: (q, s) => this.handleScaffold(q, s) },
87876
+ { method: "GET", pattern: "/api/providers", handler: (q, s2) => this.handleListProviders(q, s2) },
87877
+ { method: "GET", pattern: "/api/providers/source-config", handler: (q, s2) => this.handleGetProviderSourceConfig(q, s2) },
87878
+ { method: "POST", pattern: "/api/providers/source-config", handler: (q, s2) => this.handleSetProviderSourceConfig(q, s2) },
87879
+ { method: "GET", pattern: "/api/providers/versions", handler: (q, s2) => this.handleDetectVersions(q, s2) },
87880
+ { method: "POST", pattern: "/api/providers/reload", handler: (q, s2) => this.handleReload(q, s2) },
87881
+ { method: "POST", pattern: "/api/cdp/evaluate", handler: (q, s2) => this.handleCdpEvaluate(q, s2) },
87882
+ { method: "POST", pattern: "/api/cdp/click", handler: (q, s2) => this.handleCdpClick(q, s2) },
87883
+ { method: "POST", pattern: "/api/cdp/dom/query", handler: (q, s2) => this.handleCdpDomQuery(q, s2) },
87884
+ { method: "POST", pattern: "/api/cdp/dom/inspect", handler: (q, s2) => this.handleDomInspect(q, s2) },
87885
+ { method: "POST", pattern: "/api/cdp/dom/children", handler: (q, s2) => this.handleDomChildren(q, s2) },
87886
+ { method: "POST", pattern: "/api/cdp/dom/analyze", handler: (q, s2) => this.handleDomAnalyze(q, s2) },
87887
+ { method: "POST", pattern: "/api/cdp/dom/find-text", handler: (q, s2) => this.handleFindByText(q, s2) },
87888
+ { method: "POST", pattern: "/api/cdp/dom/find-common", handler: (q, s2) => this.handleFindCommon(q, s2) },
87889
+ { method: "GET", pattern: "/api/cdp/screenshot", handler: (q, s2) => this.handleScreenshot(q, s2) },
87890
+ { method: "GET", pattern: "/api/cdp/targets", handler: (q, s2) => this.handleCdpTargets(q, s2) },
87891
+ { method: "POST", pattern: "/api/scripts/run", handler: (q, s2) => this.handleScriptsRun(q, s2) },
87892
+ { method: "GET", pattern: "/api/status", handler: (q, s2) => this.handleStatus(q, s2) },
87893
+ { method: "POST", pattern: "/api/watch/start", handler: (q, s2) => this.handleWatchStart(q, s2) },
87894
+ { method: "POST", pattern: "/api/watch/stop", handler: (q, s2) => this.handleWatchStop(q, s2) },
87895
+ { method: "GET", pattern: "/api/watch/events", handler: (q, s2) => this.handleSSE(q, s2) },
87896
+ { method: "POST", pattern: "/api/scaffold", handler: (q, s2) => this.handleScaffold(q, s2) },
87470
87897
  // CLI Debug routes
87471
- { method: "GET", pattern: "/api/cli/status", handler: (q, s) => this.handleCliStatus(q, s) },
87472
- { method: "POST", pattern: "/api/cli/launch", handler: (q, s) => this.handleCliLaunch(q, s) },
87473
- { method: "POST", pattern: "/api/cli/send", handler: (q, s) => this.handleCliSend(q, s) },
87474
- { method: "POST", pattern: "/api/cli/exercise", handler: (q, s) => this.handleCliExercise(q, s) },
87475
- { method: "POST", pattern: "/api/cli/fixture/capture", handler: (q, s) => this.handleCliFixtureCapture(q, s) },
87476
- { method: "POST", pattern: "/api/cli/fixture/replay", handler: (q, s) => this.handleCliFixtureReplay(q, s) },
87477
- { method: "POST", pattern: "/api/cli/resolve", handler: (q, s) => this.handleCliResolve(q, s) },
87478
- { method: "POST", pattern: "/api/cli/raw", handler: (q, s) => this.handleCliRaw(q, s) },
87479
- { method: "POST", pattern: "/api/cli/stop", handler: (q, s) => this.handleCliStop(q, s) },
87480
- { method: "GET", pattern: "/api/cli/events", handler: (q, s) => this.handleCliSSE(q, s) },
87481
- { method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s, p) => this.handleCliDebug(p[0], q, s) },
87482
- { method: "GET", pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q, s, p) => this.handleCliTrace(p[0], q, s) },
87483
- { method: "GET", pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q, s, p) => this.handleCliFixtureList(p[0], q, s) },
87898
+ { method: "GET", pattern: "/api/cli/status", handler: (q, s2) => this.handleCliStatus(q, s2) },
87899
+ { method: "POST", pattern: "/api/cli/launch", handler: (q, s2) => this.handleCliLaunch(q, s2) },
87900
+ { method: "POST", pattern: "/api/cli/send", handler: (q, s2) => this.handleCliSend(q, s2) },
87901
+ { method: "POST", pattern: "/api/cli/exercise", handler: (q, s2) => this.handleCliExercise(q, s2) },
87902
+ { method: "POST", pattern: "/api/cli/fixture/capture", handler: (q, s2) => this.handleCliFixtureCapture(q, s2) },
87903
+ { method: "POST", pattern: "/api/cli/fixture/replay", handler: (q, s2) => this.handleCliFixtureReplay(q, s2) },
87904
+ { method: "POST", pattern: "/api/cli/resolve", handler: (q, s2) => this.handleCliResolve(q, s2) },
87905
+ { method: "POST", pattern: "/api/cli/raw", handler: (q, s2) => this.handleCliRaw(q, s2) },
87906
+ { method: "POST", pattern: "/api/cli/stop", handler: (q, s2) => this.handleCliStop(q, s2) },
87907
+ { method: "GET", pattern: "/api/cli/events", handler: (q, s2) => this.handleCliSSE(q, s2) },
87908
+ { method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s2, p) => this.handleCliDebug(p[0], q, s2) },
87909
+ { method: "GET", pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q, s2, p) => this.handleCliTrace(p[0], q, s2) },
87910
+ { method: "GET", pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q, s2, p) => this.handleCliFixtureList(p[0], q, s2) },
87484
87911
  // Dynamic routes (provider :type param)
87485
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s, p) => this.handleRunScript(p[0], q, s) },
87486
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s, p) => this.handleListFiles(p[0], q, s) },
87487
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/file$/, handler: (q, s, p) => this.handleReadFile(p[0], q, s) },
87488
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/file$/, handler: (q, s, p) => this.handleWriteFile(p[0], q, s) },
87489
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/source$/, handler: (q, s, p) => this.handleSource(p[0], q, s) },
87490
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/save$/, handler: (q, s, p) => this.handleSave(p[0], q, s) },
87491
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/typeAndSend$/, handler: (q, s, p) => this.handleTypeAndSend(p[0], q, s) },
87492
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/typeAndSendAt$/, handler: (q, s, p) => this.handleTypeAndSendAt(p[0], q, s) },
87493
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/config$/, handler: (q, s, p) => this.handleProviderConfig(p[0], q, s) },
87494
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/dom-context$/, handler: (q, s, p) => this.handleDomContext(p[0], q, s) },
87495
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/auto-implement$/, handler: (q, s, p) => this.handleAutoImplement(p[0], q, s) },
87496
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/auto-implement\/cancel$/, handler: (q, s, p) => this.handleAutoImplCancel(p[0], q, s) },
87497
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/auto-implement\/status$/, handler: (q, s, p) => this.handleAutoImplSSE(p[0], q, s) },
87498
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/spawn-test$/, handler: (q, s, p) => this.handleSpawnTest(p[0], q, s) },
87499
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/validate$/, handler: (q, s, p) => this.handleValidate(p[0], q, s) },
87500
- { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/acp-chat$/, handler: (q, s, p) => this.handleAcpChat(p[0], q, s) },
87501
- { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/script-hints$/, handler: (q, s, p) => this.handleScriptHints(p[0], q, s) }
87912
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s2, p) => this.handleRunScript(p[0], q, s2) },
87913
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s2, p) => this.handleListFiles(p[0], q, s2) },
87914
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/file$/, handler: (q, s2, p) => this.handleReadFile(p[0], q, s2) },
87915
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/file$/, handler: (q, s2, p) => this.handleWriteFile(p[0], q, s2) },
87916
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/source$/, handler: (q, s2, p) => this.handleSource(p[0], q, s2) },
87917
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/save$/, handler: (q, s2, p) => this.handleSave(p[0], q, s2) },
87918
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/typeAndSend$/, handler: (q, s2, p) => this.handleTypeAndSend(p[0], q, s2) },
87919
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/typeAndSendAt$/, handler: (q, s2, p) => this.handleTypeAndSendAt(p[0], q, s2) },
87920
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/config$/, handler: (q, s2, p) => this.handleProviderConfig(p[0], q, s2) },
87921
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/dom-context$/, handler: (q, s2, p) => this.handleDomContext(p[0], q, s2) },
87922
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/auto-implement$/, handler: (q, s2, p) => this.handleAutoImplement(p[0], q, s2) },
87923
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/auto-implement\/cancel$/, handler: (q, s2, p) => this.handleAutoImplCancel(p[0], q, s2) },
87924
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/auto-implement\/status$/, handler: (q, s2, p) => this.handleAutoImplSSE(p[0], q, s2) },
87925
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/spawn-test$/, handler: (q, s2, p) => this.handleSpawnTest(p[0], q, s2) },
87926
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/validate$/, handler: (q, s2, p) => this.handleValidate(p[0], q, s2) },
87927
+ { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/acp-chat$/, handler: (q, s2, p) => this.handleAcpChat(p[0], q, s2) },
87928
+ { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/script-hints$/, handler: (q, s2, p) => this.handleScriptHints(p[0], q, s2) }
87502
87929
  ];
87503
87930
  matchRoute(method, pathname) {
87504
87931
  for (const route of this.routes) {
@@ -88093,14 +88520,14 @@ data: ${JSON.stringify(msg.data)}
88093
88520
  warnings.push(...validation.warnings);
88094
88521
  if (config2.settings) {
88095
88522
  for (const [key, val] of Object.entries(config2.settings)) {
88096
- const s = val;
88097
- if (!s.type) errors.push(`settings.${key}: missing type`);
88098
- else if (!["boolean", "number", "string", "select"].includes(s.type))
88099
- errors.push(`settings.${key}: invalid type '${s.type}'`);
88100
- if (s.default === void 0) warnings.push(`settings.${key}: no default value`);
88101
- if (s.type === "number" && s.min !== void 0 && s.max !== void 0 && s.min > s.max)
88102
- errors.push(`settings.${key}: min (${s.min}) > max (${s.max})`);
88103
- if (s.type === "select" && (!s.options || !Array.isArray(s.options) || s.options.length === 0))
88523
+ const s2 = val;
88524
+ if (!s2.type) errors.push(`settings.${key}: missing type`);
88525
+ else if (!["boolean", "number", "string", "select"].includes(s2.type))
88526
+ errors.push(`settings.${key}: invalid type '${s2.type}'`);
88527
+ if (s2.default === void 0) warnings.push(`settings.${key}: no default value`);
88528
+ if (s2.type === "number" && s2.min !== void 0 && s2.max !== void 0 && s2.min > s2.max)
88529
+ errors.push(`settings.${key}: min (${s2.min}) > max (${s2.max})`);
88530
+ if (s2.type === "select" && (!s2.options || !Array.isArray(s2.options) || s2.options.length === 0))
88104
88531
  errors.push(`settings.${key}: select type requires options[]`);
88105
88532
  }
88106
88533
  }