@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.
- package/dist/index.js +690 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +690 -35
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-active-work.d.ts +1 -1
- package/dist/mesh/mesh-ledger.d.ts +1 -1
- package/dist/mesh/mesh-node-identity.d.ts +23 -0
- package/dist/mesh/mesh-refine-gates.d.ts +89 -0
- package/dist/mesh/mesh-work-queue.d.ts +24 -1
- package/dist/providers/cli-provider-instance-types.d.ts +2 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +14 -0
- package/dist/providers/types/interactive-prompt.d.ts +18 -0
- package/package.json +3 -3
- package/src/commands/high-family/mesh-events.ts +13 -2
- package/src/commands/med-family/mesh-queue.ts +74 -1
- package/src/commands/router-refine.ts +71 -4
- package/src/commands/router.ts +8 -0
- package/src/mesh/coordinator-prompt.ts +2 -1
- package/src/mesh/mesh-active-work.ts +11 -1
- package/src/mesh/mesh-event-classify.ts +19 -0
- package/src/mesh/mesh-event-forwarding.ts +46 -4
- package/src/mesh/mesh-events-utils.ts +42 -0
- package/src/mesh/mesh-ledger.ts +5 -0
- package/src/mesh/mesh-node-identity.ts +49 -0
- package/src/mesh/mesh-queue-assignment.ts +18 -1
- package/src/mesh/mesh-reconcile-loop.ts +6 -1
- package/src/mesh/mesh-refine-gates.ts +300 -0
- package/src/mesh/mesh-work-queue.ts +85 -2
- package/src/providers/cli-provider-instance-types.ts +28 -0
- package/src/providers/cli-provider-instance.ts +90 -15
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +23 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +20 -0
- 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 ? "
|
|
441
|
-
const commitShort = readInjected(true ? "
|
|
442
|
-
const version = readInjected(true ? "1.0.18-rc.
|
|
443
|
-
const builtAt = readInjected(true ? "2026-07-
|
|
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
|
|
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:
|
|
9170
|
+
changedFiles: readStringArray3(raw.changedFiles),
|
|
8993
9171
|
validationResults: normalizeValidationResults(raw.validationResults),
|
|
8994
9172
|
...gitStatus ? { gitStatus } : {},
|
|
8995
9173
|
processArtifacts: normalizeProcessArtifacts(raw.processArtifacts),
|
|
8996
|
-
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
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
48800
|
-
// state (
|
|
48801
|
-
//
|
|
48802
|
-
//
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
63747
|
-
|
|
63748
|
-
|
|
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) {
|