@adhdev/daemon-core 1.0.18-rc.10 → 1.0.18-rc.11

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.
Files changed (33) hide show
  1. package/dist/index.js +690 -35
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +690 -35
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/mesh/mesh-active-work.d.ts +1 -1
  6. package/dist/mesh/mesh-ledger.d.ts +1 -1
  7. package/dist/mesh/mesh-node-identity.d.ts +23 -0
  8. package/dist/mesh/mesh-refine-gates.d.ts +89 -0
  9. package/dist/mesh/mesh-work-queue.d.ts +24 -1
  10. package/dist/providers/cli-provider-instance-types.d.ts +2 -0
  11. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +14 -0
  12. package/dist/providers/types/interactive-prompt.d.ts +18 -0
  13. package/package.json +3 -3
  14. package/src/commands/high-family/mesh-events.ts +13 -2
  15. package/src/commands/med-family/mesh-queue.ts +74 -1
  16. package/src/commands/router-refine.ts +71 -4
  17. package/src/commands/router.ts +8 -0
  18. package/src/mesh/coordinator-prompt.ts +2 -1
  19. package/src/mesh/mesh-active-work.ts +11 -1
  20. package/src/mesh/mesh-event-classify.ts +19 -0
  21. package/src/mesh/mesh-event-forwarding.ts +46 -4
  22. package/src/mesh/mesh-events-utils.ts +42 -0
  23. package/src/mesh/mesh-ledger.ts +5 -0
  24. package/src/mesh/mesh-node-identity.ts +49 -0
  25. package/src/mesh/mesh-queue-assignment.ts +18 -1
  26. package/src/mesh/mesh-reconcile-loop.ts +6 -1
  27. package/src/mesh/mesh-refine-gates.ts +300 -0
  28. package/src/mesh/mesh-work-queue.ts +85 -2
  29. package/src/providers/cli-provider-instance-types.ts +28 -0
  30. package/src/providers/cli-provider-instance.ts +90 -15
  31. package/src/providers/sdk/v1/builders/cli/detect-status.ts +23 -0
  32. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +20 -0
  33. package/src/providers/types/interactive-prompt.ts +77 -0
package/dist/index.mjs CHANGED
@@ -437,10 +437,10 @@ function readInjected(value) {
437
437
  }
438
438
  function getDaemonBuildInfo() {
439
439
  if (cached) return cached;
440
- const commit = readInjected(true ? "36bb91235b40db3d3fb1587850f0a213d040bcef" : void 0) ?? "unknown";
441
- const commitShort = readInjected(true ? "36bb9123" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
442
- const version = readInjected(true ? "1.0.18-rc.10" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
443
- const builtAt = readInjected(true ? "2026-07-22T07:21:15.085Z" : void 0);
440
+ const commit = readInjected(true ? "99f9c20bcf4ceec1e7ab2bfe6c8f641ca88da360" : void 0) ?? "unknown";
441
+ const commitShort = readInjected(true ? "99f9c20b" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
442
+ const version = readInjected(true ? "1.0.18-rc.11" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
443
+ const builtAt = readInjected(true ? "2026-07-22T10:18:44.739Z" : void 0);
444
444
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
445
445
  return cached;
446
446
  }
@@ -2629,6 +2629,51 @@ var init_hash = __esm({
2629
2629
  });
2630
2630
 
2631
2631
  // ../mesh-shared/dist/index.mjs
2632
+ var dist_exports = {};
2633
+ __export(dist_exports, {
2634
+ CANONICAL_MESH_TOOL_COUNT: () => CANONICAL_MESH_TOOL_COUNT,
2635
+ CANONICAL_MESH_TOOL_NAMES: () => CANONICAL_MESH_TOOL_NAMES,
2636
+ DEFAULT_DIFFICULTY_BRAINS: () => DEFAULT_DIFFICULTY_BRAINS,
2637
+ MAGI_RAW_ANSWER_CAP: () => MAGI_RAW_ANSWER_CAP,
2638
+ MESH_TASK_DIFFICULTIES: () => MESH_TASK_DIFFICULTIES,
2639
+ STATUS_PROBE_ARG_KEY: () => STATUS_PROBE_ARG_KEY,
2640
+ argsCarryStatusProbeMarker: () => argsCarryStatusProbeMarker,
2641
+ canonicalDaemonId: () => canonicalDaemonId,
2642
+ daemonIdsEquivalent: () => daemonIdsEquivalent,
2643
+ deriveSlotsFromLegacy: () => deriveSlotsFromLegacy,
2644
+ expandDaemonIdForms: () => expandDaemonIdForms,
2645
+ hasGitStatusEvidence: () => hasGitStatusEvidence,
2646
+ interpolateArgs: () => interpolateArgs,
2647
+ interpolateString: () => interpolateString,
2648
+ isMeshTaskDifficulty: () => isMeshTaskDifficulty,
2649
+ joinRepoPath: () => joinRepoPath,
2650
+ machineCoreFromDaemonId: () => machineCoreFromDaemonId,
2651
+ meshNodeIdMatches: () => meshNodeIdMatches,
2652
+ meshWorkspacesEquivalent: () => meshWorkspacesEquivalent,
2653
+ normalizeBrainSlot: () => normalizeBrainSlot,
2654
+ normalizeDifficultyBrainMap: () => normalizeDifficultyBrainMap,
2655
+ normalizeGitStatus: () => normalizeGitStatus,
2656
+ normalizeMeshNodeId: () => normalizeMeshNodeId,
2657
+ normalizeMeshSessionRecord: () => normalizeMeshSessionRecord,
2658
+ normalizeMeshWorkspaceForCompare: () => normalizeMeshWorkspaceForCompare,
2659
+ normalizeNodeCapabilitySlot: () => normalizeNodeCapabilitySlot,
2660
+ normalizeNodeCapabilitySlots: () => normalizeNodeCapabilitySlots,
2661
+ normalizeThinkingLevel: () => normalizeThinkingLevel,
2662
+ parseJsonRecord: () => parseJsonRecord,
2663
+ pickBestTransitGitStatus: () => pickBestTransitGitStatus,
2664
+ readBoolean: () => readBoolean,
2665
+ readGitSubmodules: () => readGitSubmodules,
2666
+ readNumber: () => readNumber,
2667
+ readRecord: () => readRecord,
2668
+ readString: () => readString2,
2669
+ readStringArray: () => readStringArray2,
2670
+ scoreGitStatusCandidate: () => scoreGitStatusCandidate,
2671
+ scoreGitUpstreamFreshness: () => scoreGitUpstreamFreshness,
2672
+ sessionIdsEquivalent: () => sessionIdsEquivalent,
2673
+ stripStatusProbeMarker: () => stripStatusProbeMarker,
2674
+ summarizeGitShape: () => summarizeGitShape,
2675
+ withStatusProbeMarker: () => withStatusProbeMarker
2676
+ });
2632
2677
  function readRecord(value) {
2633
2678
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2634
2679
  }
@@ -2652,6 +2697,21 @@ function readBoolean(...values) {
2652
2697
  }
2653
2698
  return void 0;
2654
2699
  }
2700
+ function readStringArray2(value) {
2701
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
2702
+ }
2703
+ function parseJsonRecord(value) {
2704
+ if (!value) return {};
2705
+ if (typeof value === "object") return readRecord(value);
2706
+ if (typeof value !== "string") return {};
2707
+ const trimmed = value.trim();
2708
+ if (!trimmed) return {};
2709
+ try {
2710
+ return readRecord(JSON.parse(trimmed));
2711
+ } catch {
2712
+ return {};
2713
+ }
2714
+ }
2655
2715
  function joinRepoPath(root, relativePath) {
2656
2716
  const normalizedRoot = typeof root === "string" ? root.trim().replace(/[\\/]+$/, "") : "";
2657
2717
  const normalizedPath = typeof relativePath === "string" ? relativePath.trim() : "";
@@ -2792,12 +2852,48 @@ function pickBestTransitGitStatus(node, options) {
2792
2852
  }
2793
2853
  return best?.git;
2794
2854
  }
2855
+ function deriveSyntheticSessionId(record) {
2856
+ const parts = [
2857
+ readString2(record.workspace),
2858
+ readString2(record.providerType, record.provider),
2859
+ readString2(record.role),
2860
+ readString2(record.state, record.status),
2861
+ readString2(record.title),
2862
+ readString2(record.createdAt, record.created_at),
2863
+ readString2(record.startedAt, record.started_at)
2864
+ ].filter((part) => Boolean(part));
2865
+ if (parts.length === 0) return void 0;
2866
+ return `synthetic:${parts.join("|")}`;
2867
+ }
2795
2868
  function sessionIdsEquivalent(a, b) {
2796
2869
  const idA = readString2(a);
2797
2870
  const idB = readString2(b);
2798
2871
  if (!idA || !idB) return false;
2799
2872
  return idA === idB;
2800
2873
  }
2874
+ function normalizeMeshSessionRecord(entry) {
2875
+ const record = readRecord(entry);
2876
+ const sessionId = readString2(record.sessionId, record.session_id, record.id) ?? deriveSyntheticSessionId(record);
2877
+ if (!sessionId) return null;
2878
+ return {
2879
+ sessionId,
2880
+ ...readString2(record.providerType, record.provider) ? { providerType: readString2(record.providerType, record.provider) } : {},
2881
+ ...readString2(record.state, record.status) ? { state: readString2(record.state, record.status) } : {},
2882
+ ...readString2(record.chatStatus, record.chat_status) ? { chatStatus: readString2(record.chatStatus, record.chat_status) } : {},
2883
+ ...readString2(record.lifecycle) ? { lifecycle: readString2(record.lifecycle) } : {},
2884
+ ...readString2(record.surfaceKind, record.surface_kind) ? { surfaceKind: readString2(record.surfaceKind, record.surface_kind) } : {},
2885
+ ...readString2(record.recoveryState, record.recovery_state) ? { recoveryState: readString2(record.recoveryState, record.recovery_state) } : {},
2886
+ ...readString2(record.workspace) ? { workspace: readString2(record.workspace) } : {},
2887
+ ...readString2(record.title) ? { title: readString2(record.title) } : {},
2888
+ ...readString2(record.role) ? { role: readString2(record.role) } : {},
2889
+ ...readBoolean(record.isSelfCoordinator, record.is_self_coordinator) !== void 0 ? { isSelfCoordinator: readBoolean(record.isSelfCoordinator, record.is_self_coordinator) } : {},
2890
+ ...readString2(record.statusNote, record.status_note) ? { statusNote: readString2(record.statusNote, record.status_note) } : {},
2891
+ ...readString2(record.createdAt, record.created_at) ? { createdAt: readString2(record.createdAt, record.created_at) } : {},
2892
+ ...readString2(record.startedAt, record.started_at) ? { startedAt: readString2(record.startedAt, record.started_at) } : {},
2893
+ ...readString2(record.lastActivityAt, record.last_activity_at) ? { lastActivityAt: readString2(record.lastActivityAt, record.last_activity_at) } : {},
2894
+ ...readBoolean(record.isCached, record.is_cached) !== void 0 ? { isCached: readBoolean(record.isCached, record.is_cached) } : {}
2895
+ };
2896
+ }
2801
2897
  function normalizeMeshNodeId(node) {
2802
2898
  const record = node && typeof node === "object" ? node : {};
2803
2899
  return readString2(record.id, record.nodeId, record.node_id);
@@ -2980,9 +3076,30 @@ function deriveSlotsFromLegacy(input) {
2980
3076
  };
2981
3077
  });
2982
3078
  }
3079
+ function interpolateArgs(args, context) {
3080
+ const result = {};
3081
+ for (const [k, v] of Object.entries(args)) {
3082
+ result[k] = typeof v === "string" ? interpolateString(v, context) : v;
3083
+ }
3084
+ return result;
3085
+ }
3086
+ function interpolateString(template, ctx) {
3087
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key2) => {
3088
+ const val = ctx[key2];
3089
+ return val !== void 0 ? String(val) : `{{${key2}}}`;
3090
+ });
3091
+ }
2983
3092
  function withStatusProbeMarker(args = {}) {
2984
3093
  return { ...args, [STATUS_PROBE_ARG_KEY]: true };
2985
3094
  }
3095
+ function argsCarryStatusProbeMarker(args) {
3096
+ return !!args && typeof args === "object" && args[STATUS_PROBE_ARG_KEY] === true;
3097
+ }
3098
+ function stripStatusProbeMarker(args) {
3099
+ if (!argsCarryStatusProbeMarker(args)) return args;
3100
+ const { [STATUS_PROBE_ARG_KEY]: _drop, ...rest } = args;
3101
+ return rest;
3102
+ }
2986
3103
  var DAEMON_ID_PREFIXES, MAGI_RAW_ANSWER_CAP, MESH_TASK_DIFFICULTIES, DEFAULT_DIFFICULTY_BRAINS, CANONICAL_MESH_TOOL_NAMES, CANONICAL_MESH_TOOL_COUNT, STATUS_PROBE_ARG_KEY;
2987
3104
  var init_dist = __esm({
2988
3105
  "../mesh-shared/dist/index.mjs"() {
@@ -3014,6 +3131,7 @@ var init_dist = __esm({
3014
3131
  "mesh_restart_daemon",
3015
3132
  "mesh_checkpoint",
3016
3133
  "mesh_approve",
3134
+ "mesh_answer_question",
3017
3135
  "mesh_list_pending_approvals",
3018
3136
  "mesh_clone_node",
3019
3137
  "mesh_remove_node",
@@ -4451,6 +4569,42 @@ ${surfaced}`;
4451
4569
  if (args.event === "agent:waiting_approval") {
4452
4570
  return `[System] ${args.nodeLabel} is waiting for approval to proceed${metadata}. You may use mesh_read_chat and mesh_approve to handle it.`;
4453
4571
  }
4572
+ if (args.event === "agent:waiting_choice") {
4573
+ const prompt = args.metadataEvent.interactivePrompt;
4574
+ const promptId = readNonEmptyString(args.metadataEvent.promptId) || prompt && readNonEmptyString(prompt.promptId);
4575
+ const lines = [
4576
+ `[System] ${args.nodeLabel} is asking a question and is waiting for your answer${metadata}.`
4577
+ ];
4578
+ const questions = Array.isArray(prompt?.questions) ? prompt.questions : [];
4579
+ if (questions.length > 0) {
4580
+ for (const q of questions) {
4581
+ const header = readNonEmptyString(q.header);
4582
+ const question = readNonEmptyString(q.question);
4583
+ const multiSelect = q.multiSelect === true;
4584
+ if (question) {
4585
+ lines.push(`
4586
+ **${header ? `${header}: ` : ""}${question}**${multiSelect ? " (select one or more)" : ""}`);
4587
+ }
4588
+ const options = Array.isArray(q.options) ? q.options : [];
4589
+ options.forEach((opt, i) => {
4590
+ const record = opt && typeof opt === "object" ? opt : {};
4591
+ const label = readNonEmptyString(record.label);
4592
+ if (!label) return;
4593
+ const description = readNonEmptyString(record.description);
4594
+ lines.push(` ${i + 1}. ${label}${description ? ` \u2014 ${description}` : ""}`);
4595
+ });
4596
+ }
4597
+ } else {
4598
+ const modalMessage = readNonEmptyString(args.metadataEvent.modalMessage);
4599
+ if (modalMessage) lines.push(`
4600
+ ${modalMessage}`);
4601
+ }
4602
+ lines.push(
4603
+ `
4604
+ Answer with mesh_answer_question(node_id, session_id${promptId ? `, promptId: "${promptId}"` : ""}, answers). Do NOT use mesh_approve \u2014 that only resolves yes/no consent modals, not a question. Use mesh_read_chat once if you need the full context first.`
4605
+ );
4606
+ return lines.join("\n");
4607
+ }
4454
4608
  if (args.event === "agent:stopped") {
4455
4609
  const rc = args.recoveryContext;
4456
4610
  if (rc && rc.consecutiveNodeFailures > 0) {
@@ -5952,6 +6106,7 @@ __export(mesh_work_queue_exports, {
5952
6106
  requeueTask: () => requeueTask,
5953
6107
  resolveConvergeRequiredTags: () => resolveConvergeRequiredTags,
5954
6108
  resolveNotBefore: () => resolveNotBefore,
6109
+ takeCancelledTaskAssignment: () => takeCancelledTaskAssignment,
5955
6110
  taskDependenciesSatisfied: () => taskDependenciesSatisfied,
5956
6111
  updateDirectDispatchStatus: () => updateDirectDispatchStatus,
5957
6112
  updateSessionTaskStatus: () => updateSessionTaskStatus,
@@ -6512,6 +6667,10 @@ function updateTaskStatus(meshId, taskId, status, opts) {
6512
6667
  const result = withQueueLock(meshId, () => {
6513
6668
  const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
6514
6669
  if (!entry) return null;
6670
+ if (!opts?.force && TERMINAL_TASK_STATUSES.has(entry.status) && !TERMINAL_TASK_STATUSES.has(status)) {
6671
+ LOG.debug("MeshQueue", `Refusing updateTaskStatus(${taskId} \u2192 ${status}) on mesh ${meshId}: row is terminal (${entry.status}). A late writer (e.g. dispatch-failure requeue) must not resurrect a cancelled/completed/failed task. Pass force to override.`);
6672
+ return { entry, cascaded: [] };
6673
+ }
6515
6674
  entry.status = status;
6516
6675
  MeshRuntimeStore.getInstance().updateQueueEntry(entry);
6517
6676
  if (status !== "assigned") endTaskDispatchInFlight(meshId, taskId);
@@ -6537,17 +6696,34 @@ function cancelTask(meshId, taskId, opts) {
6537
6696
  const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
6538
6697
  if (!entry) return null;
6539
6698
  const now = (/* @__PURE__ */ new Date()).toISOString();
6699
+ const priorAssignment = entry.assignedSessionId ? {
6700
+ sessionId: entry.assignedSessionId,
6701
+ nodeId: entry.assignedNodeId,
6702
+ providerType: entry.assignedProviderType
6703
+ } : void 0;
6540
6704
  entry.status = "cancelled";
6541
6705
  entry.cancelledAt = now;
6542
6706
  if (opts?.reason) entry.cancelReason = opts.reason;
6707
+ delete entry.assignedNodeId;
6708
+ delete entry.assignedSessionId;
6709
+ delete entry.assignedProviderType;
6710
+ delete entry.dispatchTimestamp;
6711
+ entry.dispatchNonce = (entry.dispatchNonce || 0) + 1;
6543
6712
  MeshRuntimeStore.getInstance().updateQueueEntry(entry);
6544
6713
  endTaskDispatchInFlight(meshId, taskId);
6545
6714
  const cascaded = propagateDependencyFailure(meshId, taskId);
6546
- return { entry, cascaded };
6715
+ return { entry, cascaded, priorAssignment };
6547
6716
  });
6548
6717
  if (result) scheduleMissionCloseCandidateCheck(meshId, [result.entry, ...result.cascaded]);
6718
+ if (result?.priorAssignment) lastCancelledTaskAssignment.set(`${meshId}::${taskId}`, result.priorAssignment);
6549
6719
  return result ? result.entry : null;
6550
6720
  }
6721
+ function takeCancelledTaskAssignment(meshId, taskId) {
6722
+ const key2 = `${meshId}::${taskId}`;
6723
+ const value = lastCancelledTaskAssignment.get(key2);
6724
+ if (value) lastCancelledTaskAssignment.delete(key2);
6725
+ return value;
6726
+ }
6551
6727
  function requeueTask(meshId, taskId, opts) {
6552
6728
  requireMeshHostQueueOwner(opts);
6553
6729
  const result = withQueueLock(meshId, () => {
@@ -6762,7 +6938,7 @@ function recordMeshToolCall(opts) {
6762
6938
  return { rateLimitExceeded: false, callsInWindow: 0, advisory: null };
6763
6939
  }
6764
6940
  }
6765
- var ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES, MESH_TASK_MODES, MESH_TASK_PRIORITIES, NOT_BEFORE_RELATIVE_THRESHOLD_MS, LIVE_DEBUG_READONLY_FORBIDDEN, NEGATION_CUES, NEGATION_WINDOW_TOKENS, GIT_MUTATION_SUBCOMMANDS, GIT_STASH_READONLY_SUBCOMMANDS, DEPENDENCY_FAILURE_TERMINALS, MAX_STRANDED_RECLAIMS, REDRIVE_RECLAIM_REASONS, REDRIVE_SUPERSEDE_WINDOW_MS;
6941
+ var ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES, MESH_TASK_MODES, MESH_TASK_PRIORITIES, NOT_BEFORE_RELATIVE_THRESHOLD_MS, LIVE_DEBUG_READONLY_FORBIDDEN, NEGATION_CUES, NEGATION_WINDOW_TOKENS, GIT_MUTATION_SUBCOMMANDS, GIT_STASH_READONLY_SUBCOMMANDS, DEPENDENCY_FAILURE_TERMINALS, TERMINAL_TASK_STATUSES, lastCancelledTaskAssignment, MAX_STRANDED_RECLAIMS, REDRIVE_RECLAIM_REASONS, REDRIVE_SUPERSEDE_WINDOW_MS;
6766
6942
  var init_mesh_work_queue = __esm({
6767
6943
  "src/mesh/mesh-work-queue.ts"() {
6768
6944
  "use strict";
@@ -6829,6 +7005,8 @@ var init_mesh_work_queue = __esm({
6829
7005
  ]);
6830
7006
  GIT_STASH_READONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "show"]);
6831
7007
  DEPENDENCY_FAILURE_TERMINALS = /* @__PURE__ */ new Set(["failed", "cancelled"]);
7008
+ TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
7009
+ lastCancelledTaskAssignment = /* @__PURE__ */ new Map();
6832
7010
  MAX_STRANDED_RECLAIMS = 3;
6833
7011
  REDRIVE_RECLAIM_REASONS = /* @__PURE__ */ new Set([
6834
7012
  "delivered_no_turn_deadline",
@@ -8929,7 +9107,7 @@ function compactLedger(meshId) {
8929
9107
  function readNonEmptyString2(value) {
8930
9108
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
8931
9109
  }
8932
- function readStringArray2(value) {
9110
+ function readStringArray3(value) {
8933
9111
  if (!Array.isArray(value)) return [];
8934
9112
  return value.map((item) => readNonEmptyString2(item)).filter(Boolean);
8935
9113
  }
@@ -8989,11 +9167,11 @@ function normalizeMeshWorkerResult(input, source = "explicit_metadata") {
8989
9167
  return {
8990
9168
  status,
8991
9169
  ...readNonEmptyString2(raw.classification) ? { classification: readNonEmptyString2(raw.classification) } : {},
8992
- changedFiles: readStringArray2(raw.changedFiles),
9170
+ changedFiles: readStringArray3(raw.changedFiles),
8993
9171
  validationResults: normalizeValidationResults(raw.validationResults),
8994
9172
  ...gitStatus ? { gitStatus } : {},
8995
9173
  processArtifacts: normalizeProcessArtifacts(raw.processArtifacts),
8996
- errors: readStringArray2(raw.errors),
9174
+ errors: readStringArray3(raw.errors),
8997
9175
  ...readNonEmptyString2(raw.nextAction) ? { nextAction: readNonEmptyString2(raw.nextAction) } : {},
8998
9176
  requiresUserAction: raw.requiresUserAction === true,
8999
9177
  source
@@ -10138,7 +10316,8 @@ var init_coordinator_prompt = __esm({
10138
10316
  | \`mesh_fast_forward_node\` | Safely dry-run or explicitly execute an obvious clean fast-forward without launching an agent session |
10139
10317
  | \`mesh_restart_daemon\` | Update a node's daemon to the latest published version on its channel and restart it (the dashboard "preview update" path, as a mesh command) |
10140
10318
  | \`mesh_checkpoint\` | Create a git checkpoint on a node |
10141
- | \`mesh_approve\` | Approve/reject a pending agent action |
10319
+ | \`mesh_approve\` | Approve/reject a pending agent action (a yes/no tool-consent modal) |
10320
+ | \`mesh_answer_question\` | Answer a delegated session's multi-choice QUESTION (AskUserQuestion / status awaiting_choice). NOT an approval \u2014 it offers labelled options (possibly multi-select or freeform). Pass the promptId from the agent:waiting_choice event + one answer per question (select by option label or 1-based index). Use this, never mesh_approve, for a question |
10142
10321
  | \`mesh_list_pending_approvals\` | List every session across the mesh awaiting an approval decision (the approval inbox) \u2014 read-only; enumerate all blocked sessions at once, then drive a mesh_approve for each |
10143
10322
  | \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
10144
10323
  | \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
@@ -10376,7 +10555,7 @@ function readString5(value) {
10376
10555
  function readRecord4(value) {
10377
10556
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
10378
10557
  }
10379
- function readStringArray3(value, max) {
10558
+ function readStringArray4(value, max) {
10380
10559
  if (!Array.isArray(value)) return { values: [], truncated: false };
10381
10560
  const values = value.filter((item) => typeof item === "string" && item.trim().length > 0);
10382
10561
  return { values: values.slice(0, max), truncated: values.length > max };
@@ -10406,14 +10585,14 @@ function isMergeCandidate(convergence) {
10406
10585
  function readWorkerArtifact(value) {
10407
10586
  const worker = readRecord4(value);
10408
10587
  if (!worker) return null;
10409
- const changed = readStringArray3(worker.changedFiles, MAX_CHANGED_FILES);
10588
+ const changed = readStringArray4(worker.changedFiles, MAX_CHANGED_FILES);
10410
10589
  return {
10411
10590
  status: readString5(worker.status) ?? "unknown",
10412
10591
  ...readString5(worker.classification) ? { classification: readString5(worker.classification) } : {},
10413
10592
  changedFiles: changed.values,
10414
10593
  ...changed.truncated ? { changedFilesTruncated: true } : {},
10415
10594
  validationResults: Array.isArray(worker.validationResults) ? worker.validationResults.map((item) => readRecord4(item)).filter((item) => item !== null) : [],
10416
- errors: readStringArray3(worker.errors, 20).values,
10595
+ errors: readStringArray4(worker.errors, 20).values,
10417
10596
  requiresUserAction: worker.requiresUserAction === true
10418
10597
  };
10419
10598
  }
@@ -13551,6 +13730,61 @@ var init_runtime_defaults = __esm({
13551
13730
  });
13552
13731
 
13553
13732
  // src/mesh/mesh-node-identity.ts
13733
+ var mesh_node_identity_exports = {};
13734
+ __export(mesh_node_identity_exports, {
13735
+ MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS: () => MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS,
13736
+ MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS: () => MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS,
13737
+ MESH_DIRECT_PROBE_REUSE_MS: () => MESH_DIRECT_PROBE_REUSE_MS,
13738
+ MESH_DIRECT_PROBE_TIMEOUT_MS: () => MESH_DIRECT_PROBE_TIMEOUT_MS,
13739
+ MESH_NODE_LIVE_TRUTH_MARKER: () => MESH_NODE_LIVE_TRUTH_MARKER,
13740
+ MeshGitProbeCache: () => MeshGitProbeCache,
13741
+ applyCachedInlineMeshNodeStatus: () => applyCachedInlineMeshNodeStatus,
13742
+ applyInlineMeshBranchConvergence: () => applyInlineMeshBranchConvergence,
13743
+ awaitWithWarmupDeadline: () => awaitWithWarmupDeadline,
13744
+ buildHistoricalMeshSessions: () => buildHistoricalMeshSessions,
13745
+ buildInlineMeshTransitGitStatus: () => buildInlineMeshTransitGitStatus,
13746
+ buildLivePeerGitConnection: () => buildLivePeerGitConnection,
13747
+ buildMeshNodeDataFreshness: () => buildMeshNodeDataFreshness,
13748
+ buildMeshNodeDisplayLabel: () => buildMeshNodeDisplayLabel,
13749
+ buildMeshNodeMachineIdentity: () => buildMeshNodeMachineIdentity,
13750
+ buildMeshNodeProbeFreshness: () => buildMeshNodeProbeFreshness,
13751
+ collectLiveMeshSessionRecords: () => collectLiveMeshSessionRecords,
13752
+ collectMeshNodeHostedSessionIds: () => collectMeshNodeHostedSessionIds,
13753
+ deriveMeshNodeHealthFromGit: () => deriveMeshNodeHealthFromGit,
13754
+ finalizeMeshNodeStatus: () => finalizeMeshNodeStatus,
13755
+ foldMeshNodeIdentityToCanonical: () => foldMeshNodeIdentityToCanonical,
13756
+ hydrateInlineMeshDirectTruth: () => hydrateInlineMeshDirectTruth,
13757
+ inlineMeshCarriesTransientNodeTruth: () => inlineMeshCarriesTransientNodeTruth,
13758
+ isDeadLocalWorktreeNode: () => isDeadLocalWorktreeNode,
13759
+ isMeshNodeFreshEnoughToLaunch: () => isMeshNodeFreshEnoughToLaunch,
13760
+ isMeshNodeHealthLaunchable: () => isMeshNodeHealthLaunchable,
13761
+ logRepoMeshStatusDebug: () => logRepoMeshStatusDebug,
13762
+ normalizeInlineMeshNodeIdentity: () => normalizeInlineMeshNodeIdentity,
13763
+ normalizeProviderRoles: () => normalizeProviderRoles,
13764
+ persistNodeReporterPlatform: () => persistNodeReporterPlatform,
13765
+ probeRemoteMeshGitStatusWithRetry: () => probeRemoteMeshGitStatusWithRetry,
13766
+ readBooleanValue: () => readBooleanValue,
13767
+ readCachedInlineMeshActiveSessionDetails: () => readCachedInlineMeshActiveSessionDetails,
13768
+ readCachedInlineMeshActiveSessions: () => readCachedInlineMeshActiveSessions,
13769
+ readInlineMeshNodeId: () => readInlineMeshNodeId,
13770
+ readLiveMeshNodeWorkspace: () => readLiveMeshNodeWorkspace,
13771
+ readMeshNodeDaemonId: () => readMeshNodeDaemonId,
13772
+ readMeshNodeHostname: () => readMeshNodeHostname,
13773
+ readMeshNodeMachineId: () => readMeshNodeMachineId,
13774
+ readObjectRecord: () => readObjectRecord,
13775
+ readProviderPriorityFromPolicy: () => readProviderPriorityFromPolicy,
13776
+ readStringValue: () => readStringValue,
13777
+ reconcileInlineMeshCache: () => reconcileInlineMeshCache,
13778
+ recordInlineMeshDirectGitTruth: () => recordInlineMeshDirectGitTruth,
13779
+ resolveEffectiveMeshNodeHealth: () => resolveEffectiveMeshNodeHealth,
13780
+ resolveMeshNodeAttribution: () => resolveMeshNodeAttribution,
13781
+ resolveProviderTypeFromPriority: () => resolveProviderTypeFromPriority,
13782
+ sanitizeInlineMesh: () => sanitizeInlineMesh,
13783
+ shouldRefreshStalePendingAggregate: () => shouldRefreshStalePendingAggregate,
13784
+ summarizeInlineMeshBranchConvergence: () => summarizeInlineMeshBranchConvergence,
13785
+ summarizeMeshSessionRecord: () => summarizeMeshSessionRecord,
13786
+ summarizeRepoMeshStatusDebug: () => summarizeRepoMeshStatusDebug
13787
+ });
13554
13788
  import * as fs5 from "fs";
13555
13789
  function readProviderPriorityFromPolicy(policy) {
13556
13790
  const record = policy && typeof policy === "object" && !Array.isArray(policy) ? policy : {};
@@ -14115,6 +14349,23 @@ function isMeshNodeHealthLaunchable(node) {
14115
14349
  const health = resolveEffectiveMeshNodeHealth(node);
14116
14350
  return health === "online" || health === "unknown";
14117
14351
  }
14352
+ function resolveEffectiveNodeGit(node) {
14353
+ const git = readObjectRecord(node?.git);
14354
+ if (Object.keys(git).length > 0) return git;
14355
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
14356
+ return readObjectRecord(cachedStatus.git);
14357
+ }
14358
+ function isMeshNodeFreshEnoughToLaunch(node, opts) {
14359
+ const git = resolveEffectiveNodeGit(node);
14360
+ if (Object.keys(git).length === 0) return true;
14361
+ if (readBooleanValue(git.isGitRepo) === false) return true;
14362
+ const submoduleDrift = getGitSubmoduleDriftState(git);
14363
+ if (submoduleDrift.outOfSync) return false;
14364
+ const behind = readNumberValue(git.behind);
14365
+ if (behind === void 0) return true;
14366
+ const maxBehind = Number.isFinite(opts?.maxBehind) && opts?.maxBehind >= 0 ? Math.floor(opts.maxBehind) : 0;
14367
+ return behind <= maxBehind;
14368
+ }
14118
14369
  function readMeshNodeLabel(status, node) {
14119
14370
  return readStringValue(status.nodeId, normalizeMeshNodeId(node)) ?? "unknown";
14120
14371
  }
@@ -15010,6 +15261,7 @@ function sessionStatusFromNodes(nodes, nodeId, sessionId) {
15010
15261
  if (!session) return { staleReason: "direct task session is not present in live session records" };
15011
15262
  if (typeof session === "string") return {};
15012
15263
  const raw = `${readString6(session.status) || ""} ${readString6(session.lifecycle) || ""} ${readString6(session.state) || ""} ${readString6(session.activeChat?.status) || ""}`.toLowerCase();
15264
+ if (raw.includes("waiting_choice") || raw.includes("choice")) return { status: "awaiting_choice" };
15013
15265
  if (raw.includes("approval")) return { status: "awaiting_approval" };
15014
15266
  if (raw.includes("generating") || raw.includes("running") || raw.includes("busy")) return { status: "generating" };
15015
15267
  if (raw.includes("failed") || raw.includes("stopped") || raw.includes("terminated") || raw.includes("exited")) return { status: "failed" };
@@ -15035,6 +15287,7 @@ function terminalMatchesDispatch(terminal, dispatch, taskId) {
15035
15287
  }
15036
15288
  function statusFromTerminal(entry) {
15037
15289
  if (entry.kind === "task_approval_needed") return "awaiting_approval";
15290
+ if (entry.kind === "task_question_pending") return "awaiting_choice";
15038
15291
  if (entry.kind === "task_completed") return "idle";
15039
15292
  return "failed";
15040
15293
  }
@@ -15113,7 +15366,8 @@ function buildMeshActiveWorkSummary(activeWork) {
15113
15366
  generating: 0,
15114
15367
  idle: 0,
15115
15368
  failed: 0,
15116
- awaiting_approval: 0
15369
+ awaiting_approval: 0,
15370
+ awaiting_choice: 0
15117
15371
  };
15118
15372
  const sourceCounts = { queue: 0, direct: 0 };
15119
15373
  for (const item of activeWork) {
@@ -15148,7 +15402,7 @@ function buildMeshActiveWork(opts) {
15148
15402
  const queueNodeId = task.assignedNodeId || task.targetNodeId;
15149
15403
  const queueSessionId = task.assignedSessionId || task.targetSessionId;
15150
15404
  const queueLive = task.status === "assigned" ? sessionStatusFromNodes(opts.nodes, queueNodeId ?? void 0, queueSessionId ?? void 0) : {};
15151
- const queueStatus = queueLive.status === "awaiting_approval" || queueLive.status === "generating" ? queueLive.status : task.status;
15405
+ const queueStatus = queueLive.status === "awaiting_approval" || queueLive.status === "awaiting_choice" || queueLive.status === "generating" ? queueLive.status : task.status;
15152
15406
  records.push({
15153
15407
  taskId: task.id,
15154
15408
  source: "queue",
@@ -16144,6 +16398,11 @@ var init_mesh_event_classify = __esm({
16144
16398
  "agent:generating_started",
16145
16399
  "agent:generating_completed",
16146
16400
  "agent:waiting_approval",
16401
+ // A worker parked on an AskUserQuestion multi-choice prompt. DISTINCT from
16402
+ // agent:waiting_approval (a yes/no tool-consent modal): a question is answered
16403
+ // with mesh_answer_question, never mesh_approve (mission f1d25e11). Carries the
16404
+ // full InteractivePrompt payload so the coordinator can render + answer.
16405
+ "agent:waiting_choice",
16147
16406
  "agent:stopped",
16148
16407
  "agent:ready",
16149
16408
  "monitor:no_progress",
@@ -16161,6 +16420,7 @@ var init_mesh_event_classify = __esm({
16161
16420
  EVENT_TO_LEDGER_KIND = {
16162
16421
  "agent:generating_completed": "task_completed",
16163
16422
  "agent:waiting_approval": "task_approval_needed",
16423
+ "agent:waiting_choice": "task_question_pending",
16164
16424
  "agent:stopped": "task_failed",
16165
16425
  "monitor:no_progress": "task_stalled"
16166
16426
  };
@@ -16168,13 +16428,26 @@ var init_mesh_event_classify = __esm({
16168
16428
  "agent:generating_completed",
16169
16429
  "agent:stopped",
16170
16430
  "agent:waiting_approval",
16431
+ // A worker's question (waiting_choice) is real-time and the coordinator may be
16432
+ // generating when it arrives; force-inject it like approval so the busy coordinator
16433
+ // learns it must answer (mesh_answer_question) rather than the nudge sitting in the
16434
+ // adapter's outbound queue until the coordinator idles on its own (mission f1d25e11).
16435
+ "agent:waiting_choice",
16171
16436
  "refine:completed",
16172
16437
  "refine:failed",
16173
16438
  "worktree_bootstrap_complete",
16174
16439
  "worktree_bootstrap_failed"
16175
16440
  ]);
16176
16441
  MESH_APPROVAL_EVENTS = /* @__PURE__ */ new Set([
16177
- "agent:waiting_approval"
16442
+ "agent:waiting_approval",
16443
+ // agent:waiting_choice (a worker's AskUserQuestion) shares the approval class's
16444
+ // real-time-nudge / level-backed contract: its authoritative state is recorded at
16445
+ // LEVEL the moment it is processed (task_question_pending → mesh_status
16446
+ // awaiting_choice), so the pending event is only a NUDGE and can be delivered to a
16447
+ // busy coordinator's inbox (and dropped) without data loss — the level state
16448
+ // re-derives it. It is therefore exempt from the idle-edge hold completions require,
16449
+ // exactly like an approval nudge (mission f1d25e11).
16450
+ "agent:waiting_choice"
16178
16451
  ]);
16179
16452
  }
16180
16453
  });
@@ -17164,6 +17437,7 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
17164
17437
  }
17165
17438
  }
17166
17439
  if (!pending.length) return false;
17440
+ const freshnessGate = { maxBehind: resolveAutoFastForwardPolicy(mesh).maxBehind };
17167
17441
  const maxParallelTasks = resolveMaxParallelTasks(mesh?.policy?.maxParallelTasks);
17168
17442
  const maxReadonlyParallelTasks = resolveMaxReadonlyParallelTasks(maxParallelTasks);
17169
17443
  for (const task of pending) {
@@ -17278,6 +17552,10 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
17278
17552
  markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
17279
17553
  continue;
17280
17554
  }
17555
+ if (!isMeshNodeFreshEnoughToLaunch(node, freshnessGate)) {
17556
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_stale_behind_upstream", nodeId });
17557
+ continue;
17558
+ }
17281
17559
  const launchTarget = resolveAutoLaunchTarget(components, node);
17282
17560
  if (launchTarget.mode === "skip") {
17283
17561
  markAutoLaunch(meshId, task.id, { status: "skipped", reason: launchTarget.reason || "auto_launch_unavailable", nodeId });
@@ -20978,10 +21256,19 @@ function injectMeshSystemMessage(components, args) {
20978
21256
  }
20979
21257
  function markSessionTerminal(sessionId, outcome, occurredAtMs, opts) {
20980
21258
  const eventTaskId = readNonEmptyString(args.metadataEvent.taskId) || void 0;
20981
- const task = updateSessionTaskStatus(args.meshId, sessionId, outcome, {
21259
+ const completionDiagnostic = args.metadataEvent.completionDiagnostic && typeof args.metadataEvent.completionDiagnostic === "object" ? args.metadataEvent.completionDiagnostic : void 0;
21260
+ const emittedAfterFinalizationTimeout = completionDiagnostic?.emittedAfterFinalizationTimeout === true;
21261
+ const weakCompleted = outcome === "completed" && isWeakCompletionEvidence(args.metadataEvent) && !emittedAfterFinalizationTimeout;
21262
+ const task = weakCompleted ? updateSessionTaskStatus(args.meshId, sessionId, "assigned", {
21263
+ occurredAt: occurredAtMs != null ? new Date(occurredAtMs).toISOString() : void 0,
21264
+ taskId: eventTaskId
21265
+ }) : updateSessionTaskStatus(args.meshId, sessionId, outcome, {
20982
21266
  occurredAt: occurredAtMs != null ? new Date(occurredAtMs).toISOString() : void 0,
20983
21267
  taskId: eventTaskId
20984
21268
  });
21269
+ if (weakCompleted && task) {
21270
+ LOG.info("MeshQueue", `Weak completion (${readNonEmptyString(args.metadataEvent.evidenceLevel) || "missing_final_assistant"}) kept queue task ${task.id} tentative (session ${sessionId}); reconcile owns the genuine terminal`);
21271
+ }
20985
21272
  const leaveDirectDispatchActive = !task && opts?.tentativeIfDirect === true || !eventTaskId && !sessionHasActiveAssignment(args.meshId, sessionId);
20986
21273
  if (!leaveDirectDispatchActive) {
20987
21274
  updateDirectDispatchStatus(args.meshId, sessionId, outcome, eventTaskId);
@@ -21438,6 +21725,14 @@ function buildRelayMetadataEvent(payload) {
21438
21725
  retryOfJobId: readNonEmptyString(payload.retryOfJobId),
21439
21726
  ...relayModalMessage ? { modalMessage: relayModalMessage } : {},
21440
21727
  ...relayModalButtons && relayModalButtons.length > 0 ? { modalButtons: relayModalButtons } : {},
21728
+ // agent:waiting_choice (mission f1d25e11): carry the FULL structured question
21729
+ // payload across the machine boundary so a REMOTE worker's AskUserQuestion reaches
21730
+ // the coordinator with every question + option intact — the coordinator renders
21731
+ // these and answers with mesh_answer_question. The local in-process forward path
21732
+ // preserves the whole event for free; this mirrors the fields for the remote relay.
21733
+ ...payload.interactivePrompt && typeof payload.interactivePrompt === "object" && !Array.isArray(payload.interactivePrompt) ? { interactivePrompt: payload.interactivePrompt } : {},
21734
+ ...readNonEmptyString(payload.promptId) ? { promptId: readNonEmptyString(payload.promptId) } : {},
21735
+ ...payload.multiSelect === true ? { multiSelect: true } : {},
21441
21736
  ...payload.result && typeof payload.result === "object" && !Array.isArray(payload.result) ? { result: payload.result } : {},
21442
21737
  ...payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? { completionDiagnostic: payload.completionDiagnostic } : {},
21443
21738
  ...payload.workerResult && typeof payload.workerResult === "object" && !Array.isArray(payload.workerResult) ? { workerResult: payload.workerResult } : {},
@@ -22863,7 +23158,8 @@ function drainAndDeliverApprovalNudges(meshId, drainDaemonIds, localDaemonId, me
22863
23158
  function assignedRowLiveStatusIsAwaitingApproval(mesh, nodeId, sessionId) {
22864
23159
  if (!nodeId || !sessionId) return false;
22865
23160
  try {
22866
- return sessionStatusFromNodes(mesh.nodes, nodeId, sessionId).status === "awaiting_approval";
23161
+ const liveStatus = sessionStatusFromNodes(mesh.nodes, nodeId, sessionId).status;
23162
+ return liveStatus === "awaiting_approval" || liveStatus === "awaiting_choice";
22867
23163
  } catch {
22868
23164
  return false;
22869
23165
  }
@@ -25343,8 +25639,15 @@ function buttonBlockApprovalCue(spec, text) {
25343
25639
  if (pickApprovalButton(labels).index < 0) return false;
25344
25640
  return hasNegativeApprovalOption(labels);
25345
25641
  }
25642
+ function isAskUserQuestionPickerSignature(text) {
25643
+ if (!text) return false;
25644
+ const hasSelectFooter = /Enter to select/i.test(text) && /Esc to cancel/i.test(text);
25645
+ if (!hasSelectFooter) return false;
25646
+ return /Type something\.?|Chat about this/i.test(text);
25647
+ }
25346
25648
  function modalMatches(spec, input) {
25347
25649
  const text = input.screenText ?? "";
25650
+ if (isAskUserQuestionPickerSignature(text)) return false;
25348
25651
  const question = compile2(spec.questionPattern, spec.questionFlags ?? "i");
25349
25652
  if (question.test(text)) return true;
25350
25653
  for (const variant of spec.questionVariants ?? []) {
@@ -25486,6 +25789,12 @@ function compile3(re, flags) {
25486
25789
  throw new Error(`Invalid regex /${re}/${flags ?? ""}: ${e.message}`);
25487
25790
  }
25488
25791
  }
25792
+ function isAskUserQuestionPickerSignature2(text) {
25793
+ if (!text) return false;
25794
+ const hasSelectFooter = /Enter to select/i.test(text) && /Esc to cancel/i.test(text);
25795
+ if (!hasSelectFooter) return false;
25796
+ return /Type something\.?|Chat about this/i.test(text);
25797
+ }
25489
25798
  function findQuestionLineIndex(spec, lines) {
25490
25799
  const primary = compile3(spec.questionPattern, spec.questionFlags ?? "i");
25491
25800
  const buttonFlags = spec.buttonFlags && spec.buttonFlags.includes("m") ? spec.buttonFlags : `${spec.buttonFlags ?? ""}m`;
@@ -25610,6 +25919,7 @@ function buildParseApprovalFromTui(spec, visibleRegion) {
25610
25919
  return function parseApproval(input) {
25611
25920
  const rawText = input.screenText ?? input.buffer ?? "";
25612
25921
  if (!rawText) return null;
25922
+ if (isAskUserQuestionPickerSignature2(rawText)) return null;
25613
25923
  const text = visibleRegion ? applyVisibleRegion(visibleRegion, rawText) : rawText;
25614
25924
  const lines = text.split("\n");
25615
25925
  const question = findQuestionLineIndex(spec, lines);
@@ -29984,6 +30294,57 @@ function normalizeInteractivePromptResponse(raw) {
29984
30294
  }
29985
30295
  return { promptId, answers };
29986
30296
  }
30297
+ function resolveInteractivePromptResponse(prompt, raw) {
30298
+ if (!raw || typeof raw !== "object") throw new Error("Interactive prompt response must be an object");
30299
+ const record = raw;
30300
+ const promptId = readString(record.promptId);
30301
+ if (!promptId) throw new Error("promptId must be a non-empty string");
30302
+ if (promptId !== prompt.promptId) throw new Error("Interactive prompt response does not match active prompt");
30303
+ if (record.answers && typeof record.answers === "object" && !Array.isArray(record.answers)) {
30304
+ return normalizeInteractivePromptResponse(record);
30305
+ }
30306
+ if (!Array.isArray(record.answers)) throw new Error("answers must be an array or a questionId-keyed object");
30307
+ const resolveOneLabel = (question, sel) => {
30308
+ if (typeof sel === "number" && Number.isFinite(sel)) {
30309
+ const idx = Math.trunc(sel) - 1;
30310
+ const option = question.options[idx];
30311
+ if (!option) throw new Error(`Option index ${sel} out of range for ${question.questionId}`);
30312
+ return option.label;
30313
+ }
30314
+ const label = readString(sel);
30315
+ if (!label) throw new Error(`Empty selection for ${question.questionId}`);
30316
+ const exact = question.options.find((o) => o.label === label);
30317
+ if (exact) return exact.label;
30318
+ const asIndex = Number(label);
30319
+ if (Number.isInteger(asIndex)) {
30320
+ const option = question.options[asIndex - 1];
30321
+ if (option) return option.label;
30322
+ }
30323
+ throw new Error(`Unknown option for ${question.questionId}: ${label}`);
30324
+ };
30325
+ const answers = {};
30326
+ const entries = record.answers;
30327
+ entries.forEach((entryRaw, index) => {
30328
+ if (!entryRaw || typeof entryRaw !== "object" || Array.isArray(entryRaw)) return;
30329
+ const entry = entryRaw;
30330
+ const questionId = readString(entry.questionId);
30331
+ const question = (questionId ? prompt.questions.find((q) => q.questionId === questionId) : void 0) ?? prompt.questions[index];
30332
+ if (!question) throw new Error(`No matching question for answer entry ${index}`);
30333
+ const freeformText = readString(entry.freeform) ?? readString(entry.freeformText);
30334
+ const selectedLabels = [];
30335
+ const select = entry.select;
30336
+ if (Array.isArray(select)) {
30337
+ for (const sel of select) selectedLabels.push(resolveOneLabel(question, sel));
30338
+ } else if (select !== void 0 && select !== null) {
30339
+ selectedLabels.push(resolveOneLabel(question, select));
30340
+ }
30341
+ answers[question.questionId] = {
30342
+ selectedLabels,
30343
+ ...freeformText ? { freeformText } : {}
30344
+ };
30345
+ });
30346
+ return { promptId, answers };
30347
+ }
29987
30348
  function buildClaudeInteractiveToolResult(response) {
29988
30349
  return JSON.stringify({
29989
30350
  type: "user",
@@ -48376,6 +48737,7 @@ async function waitForCliAdapterReady(adapter, options) {
48376
48737
  var STATUS_HYDRATION_TAIL_LIMIT = 200;
48377
48738
  var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
48378
48739
  var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
48740
+ var CANON_C_MISSING_ASSISTANT_MIN_ELAPSED_MS = 2e4;
48379
48741
  var NATIVE_HISTORY_MESH_IDLE_SETTLE_MS = 4e3;
48380
48742
  var PTY_PARSED_FINAL_ASSISTANT_QUIET_DWELL_MS = 1200;
48381
48743
  var ANTIGRAVITY_HOLD_QUIET_DWELL_MS = 3e3;
@@ -48796,10 +49158,12 @@ var CliProviderInstance = class _CliProviderInstance {
48796
49158
  // `waiting_choice` overlay in getState(); the raw adapter status stays idle/generating,
48797
49159
  // so detectStatusTransition()'s status-keyed arms never fire and no push-worthy
48798
49160
  // agent:* event is emitted — the owner misses the "ACTION REQUIRED" prompt when the
48799
- // app is backgrounded. We emit one agent:waiting_approval on ENTRY into the prompt
48800
- // state (reusing the existing server push path, no cloud change) and dedupe on this
48801
- // key so repeated status ticks with the same prompt do not re-fire. '' means no
48802
- // prompt is currently active (cleared when the prompt is answered/gone).
49161
+ // app is backgrounded. We emit one agent:waiting_choice on ENTRY into the prompt
49162
+ // state (its own coordinator event, NOT the approval channel a multi-choice
49163
+ // question is answered with mesh_answer_question, never mesh_approve) carrying the
49164
+ // FULL InteractivePrompt payload, and dedupe on this key so repeated status ticks
49165
+ // with the same prompt do not re-fire. '' means no prompt is currently active
49166
+ // (cleared when the prompt is answered/gone).
48803
49167
  lastInteractivePromptEventKey = "";
48804
49168
  autoApproveBusy = false;
48805
49169
  autoApproveBusyTimer = null;
@@ -49444,7 +49808,7 @@ var CliProviderInstance = class _CliProviderInstance {
49444
49808
  }
49445
49809
  } else if (event === "interactive_prompt_response" && data) {
49446
49810
  try {
49447
- const response = normalizeInteractivePromptResponse(data);
49811
+ const response = this.activeInteractivePrompt && this.activeInteractivePrompt.promptId === data?.promptId && Array.isArray(data?.answers) ? resolveInteractivePromptResponse(this.activeInteractivePrompt, data) : normalizeInteractivePromptResponse(data);
49448
49812
  if (this.activeInteractivePrompt?.promptId === response.promptId) {
49449
49813
  this.activeInteractivePrompt = null;
49450
49814
  }
@@ -49983,7 +50347,12 @@ var CliProviderInstance = class _CliProviderInstance {
49983
50347
  return {
49984
50348
  reason: "missing_final_assistant",
49985
50349
  terminal: this.provider.requiresFinalAssistantBeforeIdle === true,
49986
- allowTimeout: allowMissingAssistantTimeout
50350
+ allowTimeout: allowMissingAssistantTimeout,
50351
+ // PTY-parsed provider (codex-cli): no external transcript trails the idle
50352
+ // transition, so the CANON-C decoupled emit must observe the min-elapsed floor
50353
+ // rather than fire at the first-poll waitedMs. (claude-cli's external-native
50354
+ // branch above is exempt — its transcript legitimately lands moments later.)
50355
+ noExternalTranscriptSource: true
49987
50356
  };
49988
50357
  }
49989
50358
  }
@@ -50481,6 +50850,24 @@ var CliProviderInstance = class _CliProviderInstance {
50481
50850
  this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
50482
50851
  return;
50483
50852
  }
50853
+ if (isTranscriptEvidenceGate && blockReason === "missing_final_assistant" && block2.noExternalTranscriptSource === true && waitedMs < CANON_C_MISSING_ASSISTANT_MIN_ELAPSED_MS) {
50854
+ if (pending.loggedBlockReason !== "canon_c_min_elapsed_floor") {
50855
+ LOG.info("CLI", `[${this.type}] holding CANON-C decoupled emit until min-elapsed floor (waitedMs=${waitedMs} floor=${CANON_C_MISSING_ASSISTANT_MIN_ELAPSED_MS}); no final assistant yet (${blockReason})`);
50856
+ if (this.isMeshWorkerSession()) {
50857
+ traceMeshEventDrop("completion_gate_hold", this.meshTraceCtx(), `canon_c_min_elapsed_floor waited=${waitedMs}ms`);
50858
+ }
50859
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("hold", {
50860
+ blockReason,
50861
+ latestVisibleStatus,
50862
+ terminal: block2.terminal === true,
50863
+ canonCMinElapsedFloor: true,
50864
+ waitedMs
50865
+ });
50866
+ pending.loggedBlockReason = "canon_c_min_elapsed_floor";
50867
+ }
50868
+ this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
50869
+ return;
50870
+ }
50484
50871
  if (this.type === "antigravity-cli" && block2.holdForTranscript === true && waitedMs < ANTIGRAVITY_HOLD_HARD_CAP_MS && this.antigravityHoldPtyStillActive()) {
50485
50872
  if (pending.loggedBlockReason !== "antigravity_hold_pty_active") {
50486
50873
  LOG.info("CLI", `[${this.type}] 30s cap reached but PTY still generating; holding antigravity completion past cap (waitedMs=${waitedMs} hardCap=${ANTIGRAVITY_HOLD_HARD_CAP_MS}) (${blockReason})`);
@@ -51180,9 +51567,16 @@ ${buttons.join("\n")}`;
51180
51567
  const modalMessage = firstQuestion ? firstQuestion.header ? `${firstQuestion.header}: ${firstQuestion.question}` : firstQuestion.question : void 0;
51181
51568
  const modalButtons = firstQuestion?.options?.map((option) => option.label);
51182
51569
  this.pushEvent({
51183
- event: "agent:waiting_approval",
51570
+ event: "agent:waiting_choice",
51184
51571
  chatTitle,
51185
51572
  timestamp: now,
51573
+ // Structured, authoritative payload — the coordinator renders these and
51574
+ // answers via mesh_answer_question. Carried whole (all questions, all
51575
+ // options) so multi-question / multi-select prompts round-trip fully.
51576
+ interactivePrompt,
51577
+ promptId: interactivePrompt.promptId,
51578
+ multiSelect: firstQuestion?.multiSelect === true,
51579
+ // Push-notification-friendly projection (server push path reads these).
51186
51580
  modalMessage,
51187
51581
  modalButtons
51188
51582
  });
@@ -60099,6 +60493,50 @@ var meshHostPairingHandlers = {
60099
60493
  };
60100
60494
 
60101
60495
  // src/commands/med-family/mesh-queue.ts
60496
+ init_logger();
60497
+ async function stopCancelledTaskWorker(ctx, meshId, prior) {
60498
+ const { sessionId, nodeId, providerType } = prior;
60499
+ const stopArgs = {
60500
+ targetSessionId: sessionId,
60501
+ ...providerType ? { cliType: providerType } : {},
60502
+ mode: "hard",
60503
+ reason: "mesh_task_cancelled"
60504
+ };
60505
+ try {
60506
+ const cliManager = ctx.deps.cliManager;
60507
+ const isLocal = cliManager?.adapters?.has?.(sessionId) === true;
60508
+ if (isLocal) {
60509
+ if (!stopArgs.cliType) {
60510
+ const localType = cliManager?.adapters?.get?.(sessionId)?.cliType;
60511
+ if (localType) stopArgs.cliType = localType;
60512
+ }
60513
+ await Promise.resolve(cliManager?.handleCliCommand?.("stop_cli", stopArgs)).catch((e) => LOG.warn("MeshQueue", `Local stop of cancelled worker ${sessionId} failed: ${e?.message || e}`));
60514
+ return;
60515
+ }
60516
+ const dispatch = ctx.deps.dispatchMeshCommand;
60517
+ let daemonId;
60518
+ if (nodeId) {
60519
+ try {
60520
+ const [{ getMesh: getMesh2 }, { meshNodeIdMatches: meshNodeIdMatches2 }, { readMeshNodeDaemonId: readMeshNodeDaemonId2 }] = await Promise.all([
60521
+ Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports)),
60522
+ Promise.resolve().then(() => (init_dist(), dist_exports)),
60523
+ Promise.resolve().then(() => (init_mesh_node_identity(), mesh_node_identity_exports))
60524
+ ]);
60525
+ const mesh = getMesh2(meshId);
60526
+ const node = mesh?.nodes?.find((n) => meshNodeIdMatches2(n, nodeId));
60527
+ daemonId = node ? readMeshNodeDaemonId2(node) || void 0 : void 0;
60528
+ } catch {
60529
+ }
60530
+ }
60531
+ if (daemonId && dispatch) {
60532
+ await Promise.resolve(dispatch(daemonId, "stop_cli", stopArgs)).catch((e) => LOG.warn("MeshQueue", `Remote stop of cancelled worker ${sessionId} on daemon ${daemonId} failed: ${e?.message || e}`));
60533
+ } else {
60534
+ LOG.warn("MeshQueue", `Cannot stop cancelled worker ${sessionId}: no local adapter and no resolvable remote daemon id (node ${nodeId ?? "?"}). Row is already terminal; if the worker completes it strand-fails harmlessly.`);
60535
+ }
60536
+ } catch (e) {
60537
+ LOG.warn("MeshQueue", `stopCancelledTaskWorker error for ${sessionId}: ${e?.message || e}`);
60538
+ }
60539
+ }
60102
60540
  var meshQueueHandlers = {
60103
60541
  get_mesh_queue: async (_ctx, args) => {
60104
60542
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
@@ -60132,10 +60570,14 @@ var meshQueueHandlers = {
60132
60570
  const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "queue cancellation");
60133
60571
  if (ownerFailure) return ownerFailure;
60134
60572
  try {
60135
- const { cancelTask: cancelTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
60573
+ const { cancelTask: cancelTask2, takeCancelledTaskAssignment: takeCancelledTaskAssignment2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
60136
60574
  const reason = typeof args?.reason === "string" ? args.reason : void 0;
60137
60575
  const task = cancelTask2(meshId, taskId, { reason });
60138
60576
  if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
60577
+ const prior = takeCancelledTaskAssignment2(meshId, taskId);
60578
+ if (prior?.sessionId) {
60579
+ void stopCancelledTaskWorker(ctx, meshId, prior);
60580
+ }
60139
60581
  return { success: true, task };
60140
60582
  } catch (e) {
60141
60583
  return { success: false, error: e.message };
@@ -60614,10 +61056,12 @@ var meshEventsHandlers = {
60614
61056
  interactive_prompt_response: async (ctx, args) => {
60615
61057
  const sessionId = typeof args?.targetSessionId === "string" && args.targetSessionId.trim() ? args.targetSessionId.trim() : typeof args?.sessionId === "string" && args.sessionId.trim() ? args.sessionId.trim() : "";
60616
61058
  if (!sessionId) return { success: false, error: "targetSessionId required" };
60617
- const response = normalizeInteractivePromptResponse(args?.response ?? args);
61059
+ const rawResponse = args?.response ?? args;
60618
61060
  const instance = ctx.deps.instanceManager.getInstance(sessionId);
60619
61061
  if (!instance) return { success: false, error: `No running instance for session ${sessionId}` };
60620
- ctx.deps.instanceManager.sendEvent(sessionId, "interactive_prompt_response", response);
61062
+ const isFriendlyArrayForm = rawResponse && typeof rawResponse === "object" && Array.isArray(rawResponse.answers);
61063
+ const payload = isFriendlyArrayForm ? rawResponse : normalizeInteractivePromptResponse(rawResponse);
61064
+ ctx.deps.instanceManager.sendEvent(sessionId, "interactive_prompt_response", payload);
60621
61065
  return { success: true };
60622
61066
  }
60623
61067
  };
@@ -62682,6 +63126,171 @@ function collectFastForwardGitlinkPaths(repoRoot, baseHead, branchHead) {
62682
63126
  return isSubmoduleFastForward(pathResolve2(repoRoot, path46), baseCommit, branchCommit);
62683
63127
  });
62684
63128
  }
63129
+ function isSubmoduleDivergedSibling(submoduleRepoPath, baseCommit, branchCommit) {
63130
+ if (!baseCommit || !branchCommit || baseCommit === branchCommit) return false;
63131
+ try {
63132
+ if (!fs34.existsSync(submoduleRepoPath)) return false;
63133
+ execFileSync9(GIT2, ["cat-file", "-e", `${baseCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
63134
+ execFileSync9(GIT2, ["cat-file", "-e", `${branchCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
63135
+ } catch {
63136
+ return false;
63137
+ }
63138
+ try {
63139
+ execFileSync9(GIT2, ["merge-base", "--is-ancestor", baseCommit, branchCommit], { cwd: submoduleRepoPath, stdio: "ignore" });
63140
+ return false;
63141
+ } catch {
63142
+ }
63143
+ try {
63144
+ execFileSync9(GIT2, ["merge-base", "--is-ancestor", branchCommit, baseCommit], { cwd: submoduleRepoPath, stdio: "ignore" });
63145
+ return false;
63146
+ } catch {
63147
+ }
63148
+ try {
63149
+ const mb = execFileSync9(GIT2, ["merge-base", baseCommit, branchCommit], { cwd: submoduleRepoPath, encoding: "utf8" }).trim();
63150
+ return !!mb;
63151
+ } catch {
63152
+ return false;
63153
+ }
63154
+ }
63155
+ function ensureSubmoduleCommitLocal(submoduleRepoPath, baseSubmoduleRepoPath, commit) {
63156
+ if (!commit) return;
63157
+ try {
63158
+ execFileSync9(GIT2, ["cat-file", "-e", `${commit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
63159
+ return;
63160
+ } catch {
63161
+ }
63162
+ try {
63163
+ if (!fs34.existsSync(submoduleRepoPath) || !fs34.existsSync(baseSubmoduleRepoPath)) return;
63164
+ execFileSync9(GIT2, ["-c", "protocol.file.allow=always", "fetch", "-q", baseSubmoduleRepoPath, "+refs/heads/*:refs/adhdev-refine-base/*"], {
63165
+ cwd: submoduleRepoPath,
63166
+ stdio: ["ignore", "ignore", "pipe"]
63167
+ });
63168
+ } catch {
63169
+ }
63170
+ }
63171
+ function convergeDivergedSubmoduleGitlinks(worktreeRoot, baseRepoRoot, baseHead, branchHead) {
63172
+ const changed = readChangedGitlinkPaths(worktreeRoot, baseHead, branchHead);
63173
+ if (changed.length === 0) {
63174
+ return { converged: false, reason: "no_changed_gitlinks", resolutions: [], gitlinks: [] };
63175
+ }
63176
+ const gitlinks = [];
63177
+ const resolutions = [];
63178
+ let sawDiverged = false;
63179
+ for (const path46 of changed) {
63180
+ const baseCommit = readTreeObject(baseRepoRoot, baseHead, path46);
63181
+ const branchCommit = readTreeObject(worktreeRoot, branchHead, path46);
63182
+ const submoduleRepoPath = pathResolve2(worktreeRoot, path46);
63183
+ if (baseCommit) {
63184
+ ensureSubmoduleCommitLocal(submoduleRepoPath, pathResolve2(baseRepoRoot, path46), baseCommit);
63185
+ }
63186
+ if (!baseCommit || !branchCommit || !isSubmoduleDivergedSibling(submoduleRepoPath, baseCommit, branchCommit)) {
63187
+ gitlinks.push({ path: path46, baseCommit, branchCommit, action: "skipped_not_diverged" });
63188
+ continue;
63189
+ }
63190
+ sawDiverged = true;
63191
+ let rebasedCommit;
63192
+ try {
63193
+ execFileSync9(GIT2, ["checkout", "-q", "--detach", branchCommit], { cwd: submoduleRepoPath, stdio: ["ignore", "ignore", "pipe"] });
63194
+ execFileSync9(GIT2, ["rebase", baseCommit], { cwd: submoduleRepoPath, stdio: ["ignore", "pipe", "pipe"] });
63195
+ rebasedCommit = execFileSync9(GIT2, ["rev-parse", "HEAD"], { cwd: submoduleRepoPath, encoding: "utf8" }).trim();
63196
+ } catch {
63197
+ try {
63198
+ execFileSync9(GIT2, ["rebase", "--abort"], { cwd: submoduleRepoPath, stdio: "ignore" });
63199
+ } catch {
63200
+ }
63201
+ try {
63202
+ execFileSync9(GIT2, ["checkout", "-q", "--detach", branchCommit], { cwd: submoduleRepoPath, stdio: "ignore" });
63203
+ } catch {
63204
+ }
63205
+ gitlinks.push({ path: path46, baseCommit, branchCommit, action: "rebase_conflict" });
63206
+ return { converged: false, reason: "rebase_conflict", resolutions: [], gitlinks };
63207
+ }
63208
+ gitlinks.push({ path: path46, baseCommit, branchCommit, rebasedCommit, action: "rebased" });
63209
+ resolutions.push({ path: path46, baseCommit, branchCommit, rebasedCommit });
63210
+ }
63211
+ if (!sawDiverged) {
63212
+ return { converged: false, reason: "not_diverged", resolutions: [], gitlinks };
63213
+ }
63214
+ return { converged: resolutions.length > 0, resolutions, gitlinks };
63215
+ }
63216
+ function rootRebaseResolvingGitlinks(worktreeRoot, baseHead, resolutions) {
63217
+ const resolveByPath = new Map(resolutions.map((r) => [r.path, r.rebasedCommit]));
63218
+ const runRebase = (args) => {
63219
+ try {
63220
+ execFileSync9(GIT2, args, {
63221
+ cwd: worktreeRoot,
63222
+ stdio: ["ignore", "pipe", "pipe"],
63223
+ // A rebase editor prompt would hang; keep it non-interactive.
63224
+ env: { ...process.env, GIT_EDITOR: "true", GIT_SEQUENCE_EDITOR: "true" }
63225
+ });
63226
+ return { ok: true };
63227
+ } catch {
63228
+ return { ok: false };
63229
+ }
63230
+ };
63231
+ const unmergedPaths = () => {
63232
+ try {
63233
+ return execFileSync9(GIT2, ["diff", "--name-only", "--diff-filter=U"], { cwd: worktreeRoot, encoding: "utf8" }).split("\n").map((s2) => s2.trim()).filter(Boolean);
63234
+ } catch {
63235
+ return [];
63236
+ }
63237
+ };
63238
+ const abort = (reason, conflictPaths) => {
63239
+ try {
63240
+ execFileSync9(GIT2, ["rebase", "--abort"], { cwd: worktreeRoot, stdio: "ignore" });
63241
+ } catch {
63242
+ }
63243
+ return { ok: false, reason, conflictPaths };
63244
+ };
63245
+ let progress = runRebase(["rebase", baseHead]);
63246
+ let guard = 0;
63247
+ while (!progress.ok) {
63248
+ if (guard++ > 100) return abort("rebase_error");
63249
+ const conflicts = unmergedPaths();
63250
+ if (conflicts.length === 0) {
63251
+ return abort("rebase_error");
63252
+ }
63253
+ const unresolvable = conflicts.filter((p) => !resolveByPath.has(p));
63254
+ if (unresolvable.length > 0) {
63255
+ const unresolvableGitlink = unresolvable.some((p) => {
63256
+ try {
63257
+ const staged = execFileSync9(GIT2, ["ls-files", "--stage", "--", p], { cwd: worktreeRoot, encoding: "utf8" });
63258
+ return /^160000\s/m.test(staged);
63259
+ } catch {
63260
+ return false;
63261
+ }
63262
+ });
63263
+ const allGitlink = unresolvable.every((p) => {
63264
+ try {
63265
+ const staged = execFileSync9(GIT2, ["ls-files", "--stage", "--", p], { cwd: worktreeRoot, encoding: "utf8" });
63266
+ return /^160000\s/m.test(staged);
63267
+ } catch {
63268
+ return false;
63269
+ }
63270
+ });
63271
+ return abort(allGitlink && unresolvableGitlink ? "unexpected_gitlink" : "non_gitlink_conflict", conflicts);
63272
+ }
63273
+ for (const p of conflicts) {
63274
+ const commit = resolveByPath.get(p);
63275
+ try {
63276
+ execFileSync9(GIT2, ["checkout", "-q", "--detach", commit], { cwd: pathResolve2(worktreeRoot, p), stdio: "ignore" });
63277
+ } catch {
63278
+ }
63279
+ try {
63280
+ execFileSync9(GIT2, ["add", p], { cwd: worktreeRoot, stdio: "ignore" });
63281
+ } catch {
63282
+ return abort("rebase_error", conflicts);
63283
+ }
63284
+ }
63285
+ progress = runRebase(["rebase", "--continue"]);
63286
+ }
63287
+ let branchHead;
63288
+ try {
63289
+ branchHead = execFileSync9(GIT2, ["rev-parse", "HEAD"], { cwd: worktreeRoot, encoding: "utf8" }).trim();
63290
+ } catch {
63291
+ }
63292
+ return { ok: true, branchHead };
63293
+ }
62685
63294
  function evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead) {
62686
63295
  const changedGitlinks = readChangedGitlinkPaths(repoRoot, baseHead, branchHead).map((path46) => {
62687
63296
  const baseCommit = readTreeObject(repoRoot, baseHead, path46);
@@ -63713,6 +64322,7 @@ async function computeBranchBaseDivergence(execFileAsync4, cwd, baseHead, branch
63713
64322
  async function refineSyncBaseStage(self, ctx) {
63714
64323
  const { repoRoot, baseHead, node, branch, baseBranch, refineStages, execFileAsync: execFileAsync4 } = ctx;
63715
64324
  let branchHead = ctx.branchHead;
64325
+ let gitlinkResolutions = [];
63716
64326
  const syncStarted = Date.now();
63717
64327
  const divergence = await computeBranchBaseDivergence(execFileAsync4, node.workspace, baseHead, branchHead);
63718
64328
  if (divergence.behind === 0) {
@@ -63728,24 +64338,61 @@ async function refineSyncBaseStage(self, ctx) {
63728
64338
  const preRebasePe = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
63729
64339
  const alreadyMerged = !preRebasePe.actualPatchId && !!preRebasePe.expectedPatchId;
63730
64340
  const submoduleConflict = preRebasePe.actionableHint?.kind === "submodule_conflict";
63731
- if (alreadyMerged || submoduleConflict) {
64341
+ if (alreadyMerged) {
63732
64342
  recordMeshRefineStage(refineStages, "sync_base", "passed", syncStarted, {
63733
64343
  ahead: divergence.ahead,
63734
64344
  behind: divergence.behind,
63735
64345
  rebased: false,
63736
- reason: alreadyMerged ? "already_merged_via_other_path_skip_rebase" : "submodule_conflict_defer_to_patch_equivalence"
64346
+ reason: "already_merged_via_other_path_skip_rebase"
63737
64347
  });
63738
64348
  return { kind: "continue", ctx };
63739
64349
  }
64350
+ if (submoduleConflict) {
64351
+ const converge = convergeDivergedSubmoduleGitlinks(node.workspace, repoRoot, baseHead, branchHead);
64352
+ if (converge.converged) {
64353
+ gitlinkResolutions = converge.resolutions;
64354
+ recordMeshRefineStage(refineStages, "submodule_gitlink_converge", "passed", syncStarted, {
64355
+ reason: "submodule_diverged_auto_rebased",
64356
+ gitlinks: converge.gitlinks
64357
+ });
64358
+ LOG.info("Mesh", `[Refinery] Auto-converged diverged submodule gitlink(s) onto base for node ${node.id}: ` + converge.resolutions.map((r) => `${r.path}\u2192${r.rebasedCommit.slice(0, 12)}`).join(", "));
64359
+ } else {
64360
+ recordMeshRefineStage(refineStages, "submodule_gitlink_converge", "skipped", syncStarted, {
64361
+ reason: "submodule_conflict_defer_to_patch_equivalence",
64362
+ convergeReason: converge.reason,
64363
+ gitlinks: converge.gitlinks
64364
+ });
64365
+ recordMeshRefineStage(refineStages, "sync_base", "passed", syncStarted, {
64366
+ ahead: divergence.ahead,
64367
+ behind: divergence.behind,
64368
+ rebased: false,
64369
+ reason: "submodule_conflict_defer_to_patch_equivalence"
64370
+ });
64371
+ return { kind: "continue", ctx };
64372
+ }
64373
+ }
63740
64374
  } catch {
63741
64375
  }
63742
64376
  const rebaseStarted = Date.now();
63743
64377
  try {
63744
- execFileSync10("git", ["rebase", baseHead], { cwd: node.workspace, stdio: ["ignore", "pipe", "pipe"] });
64378
+ if (gitlinkResolutions.length > 0) {
64379
+ const gitlinkRebase = rootRebaseResolvingGitlinks(node.workspace, baseHead, gitlinkResolutions);
64380
+ if (!gitlinkRebase.ok) {
64381
+ const err = new Error(`gitlink-aware rebase aborted: ${gitlinkRebase.reason || "unknown"}`);
64382
+ err.gitlinkRebaseReason = gitlinkRebase.reason;
64383
+ err.gitlinkRebaseConflicts = gitlinkRebase.conflictPaths;
64384
+ err.alreadyAborted = true;
64385
+ throw err;
64386
+ }
64387
+ } else {
64388
+ execFileSync10("git", ["rebase", baseHead], { cwd: node.workspace, stdio: ["ignore", "pipe", "pipe"] });
64389
+ }
63745
64390
  } catch (rebaseErr) {
63746
- try {
63747
- execFileSync10("git", ["rebase", "--abort"], { cwd: node.workspace, stdio: "ignore" });
63748
- } catch {
64391
+ if (!rebaseErr?.alreadyAborted) {
64392
+ try {
64393
+ execFileSync10("git", ["rebase", "--abort"], { cwd: node.workspace, stdio: "ignore" });
64394
+ } catch {
64395
+ }
63749
64396
  }
63750
64397
  let submoduleHintPatchEquivalence;
63751
64398
  try {
@@ -66064,7 +66711,15 @@ var MESH_FORWARDABLE_SESSION_COMMANDS = /* @__PURE__ */ new Set([
66064
66711
  // returns 'Session not found' (same class as mission 6938892f). Unlike read_terminal this
66065
66712
  // MUTATES the worker PTY, so forwarding to the real owner (not a wrong local session) is
66066
66713
  // doubly important. The daemon re-enforces the destructive-key confirm gate after the forward.
66067
- "send_keys"
66714
+ "send_keys",
66715
+ // interactive_prompt_response (mesh_answer_question, mission f1d25e11): the coordinator
66716
+ // answers a REMOTE worker's AskUserQuestion (waiting_choice). The answer must reach the
66717
+ // OWNING worker session's live instance — its activeInteractivePrompt (the authoritative
66718
+ // prompt the labels/indexes resolve against) and its adapter.setInteractivePromptResponse
66719
+ // live only there. Without forwarding, the coordinator's local high-family handler returns
66720
+ // 'No running instance for session …' and the question is never answered — the exact
66721
+ // remote-worker forwarding gap of mission 6938892f, now closed for questions too.
66722
+ "interactive_prompt_response"
66068
66723
  ]);
66069
66724
  function normalizeCommandSource(source) {
66070
66725
  switch (source) {