@adhdev/daemon-core 0.9.82-rc.261 → 0.9.82-rc.262
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/commands/router.d.ts +116 -0
- package/dist/index.js +481 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +481 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/commands/router.ts +756 -7
package/dist/index.js
CHANGED
|
@@ -40749,8 +40749,37 @@ async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead)
|
|
|
40749
40749
|
maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
|
|
40750
40750
|
});
|
|
40751
40751
|
const mergeBase = git(["merge-base", baseHead, branchHead]).trim();
|
|
40752
|
-
|
|
40753
|
-
|
|
40752
|
+
let mergedTree = "";
|
|
40753
|
+
let mergeTreeStdout = "";
|
|
40754
|
+
let gitlinkTrivialFastForward;
|
|
40755
|
+
try {
|
|
40756
|
+
mergeTreeStdout = git(["merge-tree", "--write-tree", baseHead, branchHead]);
|
|
40757
|
+
mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || "";
|
|
40758
|
+
} catch (mergeTreeErr) {
|
|
40759
|
+
const output = `${mergeTreeErr?.message || ""}
|
|
40760
|
+
${mergeTreeErr?.stdout || ""}
|
|
40761
|
+
${mergeTreeErr?.stderr || ""}`;
|
|
40762
|
+
const isSubmoduleConflict = /(submodule|160000)/i.test(output) || /Recursive merging with submodules/i.test(output);
|
|
40763
|
+
if (!isSubmoduleConflict) throw mergeTreeErr;
|
|
40764
|
+
const evaluation = evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead);
|
|
40765
|
+
if (!evaluation.trivial) {
|
|
40766
|
+
return {
|
|
40767
|
+
status: "failed",
|
|
40768
|
+
equivalent: false,
|
|
40769
|
+
baseHead,
|
|
40770
|
+
branchHead,
|
|
40771
|
+
mergeBase: mergeBase || void 0,
|
|
40772
|
+
durationMs: Date.now() - startedAt,
|
|
40773
|
+
error: mergeTreeErr?.message || String(mergeTreeErr),
|
|
40774
|
+
stdout: truncateValidationOutput(mergeTreeErr?.stdout),
|
|
40775
|
+
stderr: truncateValidationOutput(mergeTreeErr?.stderr),
|
|
40776
|
+
gitlinkTrivialFastForward: { resolved: false, gitlinks: evaluation.gitlinks, reason: evaluation.reason },
|
|
40777
|
+
actionableHint: buildPatchEquivalenceSubmoduleConflictHint(repoRoot, baseHead, branchHead, output)
|
|
40778
|
+
};
|
|
40779
|
+
}
|
|
40780
|
+
mergedTree = synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, evaluation.gitlinks) || "";
|
|
40781
|
+
gitlinkTrivialFastForward = { resolved: true, gitlinks: evaluation.gitlinks };
|
|
40782
|
+
}
|
|
40754
40783
|
if (!mergeBase || !mergedTree) {
|
|
40755
40784
|
return {
|
|
40756
40785
|
status: "failed",
|
|
@@ -40761,7 +40790,8 @@ async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead)
|
|
|
40761
40790
|
mergedTree: mergedTree || void 0,
|
|
40762
40791
|
durationMs: Date.now() - startedAt,
|
|
40763
40792
|
error: "patch equivalence preflight could not resolve merge-base or synthetic merge tree",
|
|
40764
|
-
stdout: truncateValidationOutput(mergeTreeStdout)
|
|
40793
|
+
stdout: truncateValidationOutput(mergeTreeStdout),
|
|
40794
|
+
gitlinkTrivialFastForward
|
|
40765
40795
|
};
|
|
40766
40796
|
}
|
|
40767
40797
|
const expectedPatchId = await computeGitPatchId(repoRoot, mergeBase, branchHead);
|
|
@@ -40776,7 +40806,8 @@ async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead)
|
|
|
40776
40806
|
mergedTree,
|
|
40777
40807
|
expectedPatchId,
|
|
40778
40808
|
actualPatchId,
|
|
40779
|
-
durationMs: Date.now() - startedAt
|
|
40809
|
+
durationMs: Date.now() - startedAt,
|
|
40810
|
+
gitlinkTrivialFastForward
|
|
40780
40811
|
};
|
|
40781
40812
|
} catch (e) {
|
|
40782
40813
|
return {
|
|
@@ -40799,6 +40830,65 @@ ${e?.stderr || ""}`
|
|
|
40799
40830
|
};
|
|
40800
40831
|
}
|
|
40801
40832
|
}
|
|
40833
|
+
async function runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead) {
|
|
40834
|
+
const startedAt = Date.now();
|
|
40835
|
+
try {
|
|
40836
|
+
const { execFileSync: execFileSync6 } = await import("child_process");
|
|
40837
|
+
const git = (args, opts) => execFileSync6("git", args, {
|
|
40838
|
+
cwd: opts?.cwd || repoRoot,
|
|
40839
|
+
encoding: "utf8",
|
|
40840
|
+
maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
|
|
40841
|
+
});
|
|
40842
|
+
const rawDiff = git(["diff", "--raw", baseHead, branchHead]).trim();
|
|
40843
|
+
if (rawDiff) {
|
|
40844
|
+
const changedPaths = rawDiff.split("\n").map((line) => line.split(" ").slice(1).join(" ").trim()).filter(Boolean).slice(0, 50);
|
|
40845
|
+
return {
|
|
40846
|
+
status: "passed",
|
|
40847
|
+
hasEffectiveDiff: true,
|
|
40848
|
+
baseHead,
|
|
40849
|
+
branchHead,
|
|
40850
|
+
changedPaths,
|
|
40851
|
+
durationMs: Date.now() - startedAt
|
|
40852
|
+
};
|
|
40853
|
+
}
|
|
40854
|
+
const submoduleHints = [];
|
|
40855
|
+
try {
|
|
40856
|
+
const status = git(["submodule", "status"]);
|
|
40857
|
+
for (const line of status.split("\n")) {
|
|
40858
|
+
const trimmed = line.trimEnd();
|
|
40859
|
+
if (!trimmed) continue;
|
|
40860
|
+
if (trimmed.startsWith("+")) {
|
|
40861
|
+
const parts = trimmed.slice(1).trim().split(/\s+/);
|
|
40862
|
+
const path39 = parts[1] || parts[0] || "(unknown)";
|
|
40863
|
+
submoduleHints.push({
|
|
40864
|
+
path: path39,
|
|
40865
|
+
reason: "submodule checked-out commit differs from the committed gitlink (pointer bump not committed on the root branch)"
|
|
40866
|
+
});
|
|
40867
|
+
}
|
|
40868
|
+
}
|
|
40869
|
+
} catch {
|
|
40870
|
+
}
|
|
40871
|
+
return {
|
|
40872
|
+
status: "failed",
|
|
40873
|
+
hasEffectiveDiff: false,
|
|
40874
|
+
baseHead,
|
|
40875
|
+
branchHead,
|
|
40876
|
+
...submoduleHints.length ? { submoduleHints } : {},
|
|
40877
|
+
durationMs: Date.now() - startedAt
|
|
40878
|
+
};
|
|
40879
|
+
} catch (e) {
|
|
40880
|
+
return {
|
|
40881
|
+
status: "skipped",
|
|
40882
|
+
hasEffectiveDiff: true,
|
|
40883
|
+
baseHead,
|
|
40884
|
+
branchHead,
|
|
40885
|
+
durationMs: Date.now() - startedAt,
|
|
40886
|
+
error: e?.message || String(e),
|
|
40887
|
+
stdout: truncateValidationOutput(e?.stdout),
|
|
40888
|
+
stderr: truncateValidationOutput(e?.stderr)
|
|
40889
|
+
};
|
|
40890
|
+
}
|
|
40891
|
+
}
|
|
40802
40892
|
function buildPatchEquivalenceSubmoduleConflictHint(repoRoot, baseHead, branchHead, output) {
|
|
40803
40893
|
if (!/(submodule|160000)/i.test(output) || !/(conflict|failed to merge)/i.test(output)) return void 0;
|
|
40804
40894
|
const conflicts = readChangedGitlinkPaths(repoRoot, baseHead, branchHead).map((path39) => ({
|
|
@@ -40855,6 +40945,135 @@ function readTreeObject(repoRoot, ref, path39) {
|
|
|
40855
40945
|
return void 0;
|
|
40856
40946
|
}
|
|
40857
40947
|
}
|
|
40948
|
+
function resolveGitDir(repoRoot) {
|
|
40949
|
+
const out = (0, import_node_child_process6.execFileSync)("git", ["rev-parse", "--absolute-git-dir"], {
|
|
40950
|
+
cwd: repoRoot,
|
|
40951
|
+
encoding: "utf8",
|
|
40952
|
+
maxBuffer: 1024 * 1024
|
|
40953
|
+
}).trim();
|
|
40954
|
+
return out;
|
|
40955
|
+
}
|
|
40956
|
+
function isSubmoduleFastForward(submoduleRepoPath, baseCommit, branchCommit) {
|
|
40957
|
+
if (!baseCommit || !branchCommit) return false;
|
|
40958
|
+
if (baseCommit === branchCommit) return true;
|
|
40959
|
+
try {
|
|
40960
|
+
if (!fs23.existsSync(submoduleRepoPath)) return false;
|
|
40961
|
+
(0, import_node_child_process6.execFileSync)("git", ["cat-file", "-e", `${baseCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
|
|
40962
|
+
(0, import_node_child_process6.execFileSync)("git", ["cat-file", "-e", `${branchCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
|
|
40963
|
+
(0, import_node_child_process6.execFileSync)("git", ["merge-base", "--is-ancestor", baseCommit, branchCommit], { cwd: submoduleRepoPath, stdio: "ignore" });
|
|
40964
|
+
return true;
|
|
40965
|
+
} catch {
|
|
40966
|
+
return false;
|
|
40967
|
+
}
|
|
40968
|
+
}
|
|
40969
|
+
function readChangedPathKinds(repoRoot, fromRef, toRef) {
|
|
40970
|
+
try {
|
|
40971
|
+
const output = (0, import_node_child_process6.execFileSync)("git", ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
|
|
40972
|
+
cwd: repoRoot,
|
|
40973
|
+
encoding: "utf8",
|
|
40974
|
+
maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
|
|
40975
|
+
});
|
|
40976
|
+
const result = [];
|
|
40977
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40978
|
+
for (const line of output.split("\n")) {
|
|
40979
|
+
if (!line.trim()) continue;
|
|
40980
|
+
const metaAndPath = line.split(" ");
|
|
40981
|
+
const meta = metaAndPath[0] || "";
|
|
40982
|
+
const path39 = metaAndPath[metaAndPath.length - 1]?.trim();
|
|
40983
|
+
if (!path39 || seen.has(path39)) continue;
|
|
40984
|
+
seen.add(path39);
|
|
40985
|
+
const parts = meta.split(/\s+/);
|
|
40986
|
+
const isGitlink = !!(parts[0]?.includes("160000") || parts[1]?.includes("160000"));
|
|
40987
|
+
result.push({ path: path39, isGitlink });
|
|
40988
|
+
}
|
|
40989
|
+
return result;
|
|
40990
|
+
} catch {
|
|
40991
|
+
return [];
|
|
40992
|
+
}
|
|
40993
|
+
}
|
|
40994
|
+
function evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead) {
|
|
40995
|
+
const changedGitlinks = readChangedGitlinkPaths(repoRoot, baseHead, branchHead).map((path39) => {
|
|
40996
|
+
const baseCommit = readTreeObject(repoRoot, baseHead, path39);
|
|
40997
|
+
const branchCommit = readTreeObject(repoRoot, branchHead, path39);
|
|
40998
|
+
const submoduleRepoPath = (0, import_path10.resolve)(repoRoot, path39);
|
|
40999
|
+
const fastForward = !!baseCommit && !!branchCommit && isSubmoduleFastForward(submoduleRepoPath, baseCommit, branchCommit);
|
|
41000
|
+
return { path: path39, baseCommit, branchCommit, fastForward };
|
|
41001
|
+
});
|
|
41002
|
+
if (changedGitlinks.length === 0) {
|
|
41003
|
+
return { trivial: false, reason: "no_changed_gitlinks", gitlinks: changedGitlinks };
|
|
41004
|
+
}
|
|
41005
|
+
const nonFastForward = changedGitlinks.filter((entry) => !entry.fastForward);
|
|
41006
|
+
if (nonFastForward.length > 0) {
|
|
41007
|
+
return {
|
|
41008
|
+
trivial: false,
|
|
41009
|
+
reason: `diverged_gitlinks:${nonFastForward.map((entry) => entry.path).join(",")}`,
|
|
41010
|
+
gitlinks: changedGitlinks
|
|
41011
|
+
};
|
|
41012
|
+
}
|
|
41013
|
+
let mergeBase = "";
|
|
41014
|
+
try {
|
|
41015
|
+
mergeBase = (0, import_node_child_process6.execFileSync)("git", ["merge-base", baseHead, branchHead], {
|
|
41016
|
+
cwd: repoRoot,
|
|
41017
|
+
encoding: "utf8",
|
|
41018
|
+
maxBuffer: 1024 * 1024
|
|
41019
|
+
}).trim();
|
|
41020
|
+
} catch {
|
|
41021
|
+
return { trivial: false, reason: "merge_base_unresolved", gitlinks: changedGitlinks };
|
|
41022
|
+
}
|
|
41023
|
+
if (!mergeBase) {
|
|
41024
|
+
return { trivial: false, reason: "merge_base_unresolved", gitlinks: changedGitlinks };
|
|
41025
|
+
}
|
|
41026
|
+
const baseSideChanges = readChangedPathKinds(repoRoot, mergeBase, baseHead);
|
|
41027
|
+
const branchSideChanges = readChangedPathKinds(repoRoot, mergeBase, branchHead);
|
|
41028
|
+
const baseChangedPaths = new Map(baseSideChanges.map((entry) => [entry.path, entry]));
|
|
41029
|
+
const overlapping = branchSideChanges.filter((entry) => baseChangedPaths.has(entry.path));
|
|
41030
|
+
const nonGitlinkOverlap = overlapping.filter((entry) => {
|
|
41031
|
+
const baseEntry = baseChangedPaths.get(entry.path);
|
|
41032
|
+
return !(entry.isGitlink && baseEntry?.isGitlink);
|
|
41033
|
+
});
|
|
41034
|
+
if (nonGitlinkOverlap.length > 0) {
|
|
41035
|
+
return {
|
|
41036
|
+
trivial: false,
|
|
41037
|
+
reason: `non_gitlink_overlap:${nonGitlinkOverlap.map((entry) => entry.path).join(",")}`,
|
|
41038
|
+
gitlinks: changedGitlinks
|
|
41039
|
+
};
|
|
41040
|
+
}
|
|
41041
|
+
return { trivial: true, gitlinks: changedGitlinks };
|
|
41042
|
+
}
|
|
41043
|
+
function synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, gitlinks) {
|
|
41044
|
+
try {
|
|
41045
|
+
const baseTree = (0, import_node_child_process6.execFileSync)("git", ["rev-parse", `${baseHead}^{tree}`], {
|
|
41046
|
+
cwd: repoRoot,
|
|
41047
|
+
encoding: "utf8",
|
|
41048
|
+
maxBuffer: 1024 * 1024
|
|
41049
|
+
}).trim();
|
|
41050
|
+
if (!baseTree) return void 0;
|
|
41051
|
+
const updates = gitlinks.filter((entry) => entry.branchCommit).map((entry) => `160000 commit ${entry.branchCommit} ${entry.path}`).join("\n");
|
|
41052
|
+
if (!updates) return baseTree;
|
|
41053
|
+
const tmpIndex = (0, import_path10.join)(resolveGitDir(repoRoot), `adhdev-refine-ff-${baseHead.slice(0, 12)}-${branchHead.slice(0, 12)}.index`);
|
|
41054
|
+
const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
|
|
41055
|
+
try {
|
|
41056
|
+
(0, import_node_child_process6.execFileSync)("git", ["read-tree", baseTree], { cwd: repoRoot, env, stdio: "ignore" });
|
|
41057
|
+
(0, import_node_child_process6.execFileSync)("git", ["update-index", "--index-info"], {
|
|
41058
|
+
cwd: repoRoot,
|
|
41059
|
+
env,
|
|
41060
|
+
input: `${updates}
|
|
41061
|
+
`,
|
|
41062
|
+
encoding: "utf8",
|
|
41063
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
41064
|
+
});
|
|
41065
|
+
const newTree = (0, import_node_child_process6.execFileSync)("git", ["write-tree"], { cwd: repoRoot, env, encoding: "utf8" }).trim();
|
|
41066
|
+
return newTree || void 0;
|
|
41067
|
+
} finally {
|
|
41068
|
+
try {
|
|
41069
|
+
fs23.rmSync(tmpIndex, { force: true });
|
|
41070
|
+
} catch {
|
|
41071
|
+
}
|
|
41072
|
+
}
|
|
41073
|
+
} catch {
|
|
41074
|
+
return void 0;
|
|
41075
|
+
}
|
|
41076
|
+
}
|
|
40858
41077
|
async function alignRefinerySubmodulesAfterMerge(repoRoot, previousBaseHead, currentHead, options = {}) {
|
|
40859
41078
|
const startedAt = Date.now();
|
|
40860
41079
|
const changedGitlinkPaths = readChangedGitlinkPaths(repoRoot, previousBaseHead, currentHead).filter((path39) => !(options.submoduleIgnorePaths || []).includes(path39));
|
|
@@ -41522,6 +41741,10 @@ var DaemonCommandRouter = class {
|
|
|
41522
41741
|
runningRefineJobs = /* @__PURE__ */ new Map();
|
|
41523
41742
|
/** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
|
|
41524
41743
|
terminalRefineJobs = /* @__PURE__ */ new Map();
|
|
41744
|
+
/** In-memory async batch Refinery jobs keyed by meshId (one batch convergence per mesh at a time). */
|
|
41745
|
+
runningRefineBatchJobs = /* @__PURE__ */ new Map();
|
|
41746
|
+
/** Terminal async batch Refinery jobs preserve the last batch outcome for late readers. */
|
|
41747
|
+
terminalRefineBatchJobs = /* @__PURE__ */ new Map();
|
|
41525
41748
|
constructor(deps) {
|
|
41526
41749
|
this.deps = deps;
|
|
41527
41750
|
}
|
|
@@ -42751,6 +42974,48 @@ ${tail}` : ""
|
|
|
42751
42974
|
}
|
|
42752
42975
|
};
|
|
42753
42976
|
}
|
|
42977
|
+
const effectiveDiffStarted = Date.now();
|
|
42978
|
+
const effectiveDiff = await runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead);
|
|
42979
|
+
recordMeshRefineStage(refineStages, "effective_diff", effectiveDiff.status, effectiveDiffStarted, {
|
|
42980
|
+
hasEffectiveDiff: effectiveDiff.hasEffectiveDiff,
|
|
42981
|
+
changedPaths: effectiveDiff.changedPaths,
|
|
42982
|
+
submoduleHints: effectiveDiff.submoduleHints,
|
|
42983
|
+
...effectiveDiff.error ? { error: effectiveDiff.error } : {}
|
|
42984
|
+
});
|
|
42985
|
+
if (effectiveDiff.status === "failed" && !effectiveDiff.hasEffectiveDiff) {
|
|
42986
|
+
const hintLines = (effectiveDiff.submoduleHints || []).map((h) => ` - ${h.path}: ${h.reason}`);
|
|
42987
|
+
const message = [
|
|
42988
|
+
`Refinery no-op guard: branch '${branch}' has no effective root-tree diff against '${baseBranch}' (${baseHead.slice(0, 12)}); nothing would merge.`,
|
|
42989
|
+
"This usually means a submodule (e.g. oss) has commits but the root branch never committed the gitlink (pointer) bump, so the merge would be a silent no-op while the real change never reaches main.",
|
|
42990
|
+
hintLines.length ? `Submodules with uncommitted pointer bumps:
|
|
42991
|
+
${hintLines.join("\n")}` : "",
|
|
42992
|
+
`Fix: commit the submodule pointer bump on '${branch}' (git add <submodule-path> && git commit), then re-run refine.`
|
|
42993
|
+
].filter(Boolean).join("\n");
|
|
42994
|
+
return {
|
|
42995
|
+
success: false,
|
|
42996
|
+
code: "no_effective_diff",
|
|
42997
|
+
convergenceStatus: "blocked_review",
|
|
42998
|
+
error: message,
|
|
42999
|
+
branch,
|
|
43000
|
+
into: baseBranch,
|
|
43001
|
+
validationSummary,
|
|
43002
|
+
patchEquivalence,
|
|
43003
|
+
effectiveDiff,
|
|
43004
|
+
refineStages,
|
|
43005
|
+
finalBranchConvergenceState: {
|
|
43006
|
+
branch,
|
|
43007
|
+
baseBranch,
|
|
43008
|
+
merged: false,
|
|
43009
|
+
removed: false,
|
|
43010
|
+
validation: "passed",
|
|
43011
|
+
patchEquivalence: "passed",
|
|
43012
|
+
effectiveDiff: "no_effective_diff",
|
|
43013
|
+
status: "blocked_review",
|
|
43014
|
+
reason: "no_effective_diff",
|
|
43015
|
+
...effectiveDiff.submoduleHints?.length ? { submoduleHints: effectiveDiff.submoduleHints } : {}
|
|
43016
|
+
}
|
|
43017
|
+
};
|
|
43018
|
+
}
|
|
42754
43019
|
let mergeResult;
|
|
42755
43020
|
const mergeStarted = Date.now();
|
|
42756
43021
|
try {
|
|
@@ -43092,6 +43357,17 @@ ${tail}` : ""
|
|
|
43092
43357
|
note: "Dry-run: no validation, rebase, or merge was executed. Re-run with execute=true to converge nodes in this order."
|
|
43093
43358
|
};
|
|
43094
43359
|
}
|
|
43360
|
+
return this.runMeshRefineBatchConvergence(meshId, orderedNodes, ordering, args);
|
|
43361
|
+
}
|
|
43362
|
+
/**
|
|
43363
|
+
* Convergence core shared by the synchronous batch entry and the async batch job.
|
|
43364
|
+
* Refines each node in order: the per-node refine pipeline fetches origin/<base>
|
|
43365
|
+
* fresh, so each merged sibling advances the base before the next node's auto-rebase
|
|
43366
|
+
* + patch-equivalence re-check. A blocked/failed node is isolated; the batch
|
|
43367
|
+
* continues with the remaining nodes. Does NOT touch the per-node merge logic — it
|
|
43368
|
+
* only sequences calls to executeMeshRefineNodeSynchronously and aggregates outcomes.
|
|
43369
|
+
*/
|
|
43370
|
+
async runMeshRefineBatchConvergence(meshId, orderedNodes, ordering, args) {
|
|
43095
43371
|
const results = [];
|
|
43096
43372
|
for (const node of orderedNodes) {
|
|
43097
43373
|
let result;
|
|
@@ -43146,6 +43422,204 @@ ${tail}` : ""
|
|
|
43146
43422
|
}
|
|
43147
43423
|
};
|
|
43148
43424
|
}
|
|
43425
|
+
buildRefineBatchJobKey(meshId) {
|
|
43426
|
+
return `${meshId}::batch`;
|
|
43427
|
+
}
|
|
43428
|
+
buildRefineBatchJobHandle(args) {
|
|
43429
|
+
return {
|
|
43430
|
+
success: true,
|
|
43431
|
+
async: true,
|
|
43432
|
+
batch: true,
|
|
43433
|
+
status: args.status || "accepted",
|
|
43434
|
+
jobId: args.jobId || `refine_batch_${createInteractionId()}`,
|
|
43435
|
+
interactionId: args.interactionId || createInteractionId(),
|
|
43436
|
+
meshId: args.meshId,
|
|
43437
|
+
batchLabel: `batch:${args.nodeIds.length} node${args.nodeIds.length === 1 ? "" : "s"}`,
|
|
43438
|
+
nodeIds: args.nodeIds,
|
|
43439
|
+
nodeCount: args.nodeIds.length,
|
|
43440
|
+
order: args.order,
|
|
43441
|
+
startedAt: args.startedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
43442
|
+
...args.completedAt ? { completedAt: args.completedAt } : {},
|
|
43443
|
+
...args.coordinatorDaemonId ? { targetCoordinatorDaemonId: args.coordinatorDaemonId } : {},
|
|
43444
|
+
eventDelivery: { pendingEvents: true, ledger: true },
|
|
43445
|
+
evidence: {
|
|
43446
|
+
pendingEventsCommand: "get_pending_mesh_events",
|
|
43447
|
+
ledgerCommand: "get_mesh_ledger_slice",
|
|
43448
|
+
taskHistoryKind: args.status === "completed" ? "task_completed" : args.status === "failed" ? "task_failed" : "task_dispatched"
|
|
43449
|
+
}
|
|
43450
|
+
};
|
|
43451
|
+
}
|
|
43452
|
+
/**
|
|
43453
|
+
* Emit a batch Refinery terminal/accepted event through the SAME pending-event +
|
|
43454
|
+
* forward mechanism single-node refine uses (queueRefineJobEvent), so the
|
|
43455
|
+
* coordinator's existing refine:accepted/completed/failed handling and message
|
|
43456
|
+
* renderer apply unchanged. The aggregate per-node results ride along in `result`.
|
|
43457
|
+
*/
|
|
43458
|
+
queueRefineBatchJobEvent(event, handle, result) {
|
|
43459
|
+
const metadataEvent = {
|
|
43460
|
+
source: "refine_mesh_node_async_job",
|
|
43461
|
+
batch: true,
|
|
43462
|
+
jobId: handle.jobId,
|
|
43463
|
+
interactionId: handle.interactionId,
|
|
43464
|
+
meshId: handle.meshId,
|
|
43465
|
+
nodeId: handle.batchLabel,
|
|
43466
|
+
nodeIds: handle.nodeIds,
|
|
43467
|
+
workspace: void 0,
|
|
43468
|
+
status: handle.status,
|
|
43469
|
+
startedAt: handle.startedAt,
|
|
43470
|
+
completedAt: handle.completedAt,
|
|
43471
|
+
order: handle.order,
|
|
43472
|
+
...result ? { result } : {}
|
|
43473
|
+
};
|
|
43474
|
+
const eventPayload = {
|
|
43475
|
+
event,
|
|
43476
|
+
meshId: handle.meshId,
|
|
43477
|
+
nodeLabel: handle.batchLabel,
|
|
43478
|
+
nodeId: handle.batchLabel,
|
|
43479
|
+
metadataEvent,
|
|
43480
|
+
queuedAt: Date.now(),
|
|
43481
|
+
...handle.targetCoordinatorDaemonId ? { targetCoordinatorDaemonId: handle.targetCoordinatorDaemonId } : {}
|
|
43482
|
+
};
|
|
43483
|
+
if (typeof this.deps.instanceManager?.getByCategory === "function") {
|
|
43484
|
+
const forwarded = handleMeshForwardEvent(
|
|
43485
|
+
{ instanceManager: this.deps.instanceManager },
|
|
43486
|
+
{
|
|
43487
|
+
event,
|
|
43488
|
+
meshId: handle.meshId,
|
|
43489
|
+
nodeId: handle.batchLabel,
|
|
43490
|
+
jobId: handle.jobId,
|
|
43491
|
+
interactionId: handle.interactionId,
|
|
43492
|
+
status: handle.status,
|
|
43493
|
+
startedAt: handle.startedAt,
|
|
43494
|
+
completedAt: handle.completedAt,
|
|
43495
|
+
...result ? { result } : {}
|
|
43496
|
+
}
|
|
43497
|
+
);
|
|
43498
|
+
if (forwarded?.success === true) return;
|
|
43499
|
+
LOG.warn("Mesh", `[Refinery] Failed to forward async refine batch event ${event}: ${forwarded?.error || "unknown error"}`);
|
|
43500
|
+
}
|
|
43501
|
+
queuePendingMeshCoordinatorEvent(eventPayload);
|
|
43502
|
+
}
|
|
43503
|
+
async appendRefineBatchJobLedger(kind, handle, result) {
|
|
43504
|
+
try {
|
|
43505
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
43506
|
+
appendLedgerEntry2(handle.meshId, {
|
|
43507
|
+
kind,
|
|
43508
|
+
nodeId: handle.batchLabel,
|
|
43509
|
+
payload: {
|
|
43510
|
+
source: "refine_mesh_node_async_job",
|
|
43511
|
+
refineJob: {
|
|
43512
|
+
batch: true,
|
|
43513
|
+
jobId: handle.jobId,
|
|
43514
|
+
interactionId: handle.interactionId,
|
|
43515
|
+
status: handle.status,
|
|
43516
|
+
meshId: handle.meshId,
|
|
43517
|
+
nodeIds: handle.nodeIds,
|
|
43518
|
+
order: handle.order,
|
|
43519
|
+
targetCoordinatorDaemonId: handle.targetCoordinatorDaemonId,
|
|
43520
|
+
startedAt: handle.startedAt,
|
|
43521
|
+
completedAt: handle.completedAt
|
|
43522
|
+
},
|
|
43523
|
+
async: true,
|
|
43524
|
+
batch: true,
|
|
43525
|
+
...result ? {
|
|
43526
|
+
success: result.success === true,
|
|
43527
|
+
result
|
|
43528
|
+
} : {}
|
|
43529
|
+
}
|
|
43530
|
+
});
|
|
43531
|
+
} catch (e) {
|
|
43532
|
+
LOG.warn("Mesh", `[Refinery] Failed to append async refine batch ledger entry: ${e?.message || e}`);
|
|
43533
|
+
}
|
|
43534
|
+
}
|
|
43535
|
+
async finishMeshRefineBatchJob(handle, orderedNodes, ordering, args) {
|
|
43536
|
+
const key = this.buildRefineBatchJobKey(handle.meshId);
|
|
43537
|
+
let result;
|
|
43538
|
+
try {
|
|
43539
|
+
result = await this.runMeshRefineBatchConvergence(handle.meshId, orderedNodes, ordering, args);
|
|
43540
|
+
} catch (e) {
|
|
43541
|
+
result = { success: false, error: e?.message || String(e), batch: true };
|
|
43542
|
+
}
|
|
43543
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
43544
|
+
const summary = result.summary && typeof result.summary === "object" ? result.summary : void 0;
|
|
43545
|
+
const allConverged = result.allConverged === true;
|
|
43546
|
+
const isTerminalSuccess = result.success === true && allConverged;
|
|
43547
|
+
const nextStep = typeof result.nextStep === "string" && result.nextStep ? result.nextStep : isTerminalSuccess ? "All batched nodes converged onto base. Continue from the updated mesh state." : "Resolve blocked_review / not_mergeable nodes (see per-node code/stage/error in result.results), then re-run mesh_refine_batch for the remaining nodes.";
|
|
43548
|
+
const normalizedResult = {
|
|
43549
|
+
...result,
|
|
43550
|
+
batch: true,
|
|
43551
|
+
nextStep,
|
|
43552
|
+
...summary ? {
|
|
43553
|
+
convergenceStatus: allConverged ? "all_converged" : "partial"
|
|
43554
|
+
} : {}
|
|
43555
|
+
};
|
|
43556
|
+
const terminalHandle = this.buildRefineBatchJobHandle({
|
|
43557
|
+
meshId: handle.meshId,
|
|
43558
|
+
nodeIds: handle.nodeIds,
|
|
43559
|
+
order: handle.order,
|
|
43560
|
+
status: isTerminalSuccess ? "completed" : "failed",
|
|
43561
|
+
startedAt: handle.startedAt,
|
|
43562
|
+
completedAt,
|
|
43563
|
+
jobId: handle.jobId,
|
|
43564
|
+
interactionId: handle.interactionId,
|
|
43565
|
+
coordinatorDaemonId: handle.targetCoordinatorDaemonId
|
|
43566
|
+
});
|
|
43567
|
+
const terminal = { ...terminalHandle, result: normalizedResult };
|
|
43568
|
+
this.terminalRefineBatchJobs.set(key, terminal);
|
|
43569
|
+
this.runningRefineBatchJobs.delete(key);
|
|
43570
|
+
this.invalidateAggregateMeshStatus(handle.meshId);
|
|
43571
|
+
await this.appendRefineBatchJobLedger(isTerminalSuccess ? "task_completed" : "task_failed", terminalHandle, normalizedResult);
|
|
43572
|
+
this.queueRefineBatchJobEvent(isTerminalSuccess ? "refine:completed" : "refine:failed", terminalHandle, normalizedResult);
|
|
43573
|
+
}
|
|
43574
|
+
/**
|
|
43575
|
+
* Async entry for the batch Refinery execute path. Mirrors startMeshRefineJob:
|
|
43576
|
+
* resolves the plan synchronously (so target/ordering errors and the dry-run shape
|
|
43577
|
+
* stay synchronous), then for execute=true registers an in-flight batch job, returns
|
|
43578
|
+
* {async:true, status:'accepted', batch:true, ...plan} immediately, and runs the
|
|
43579
|
+
* convergence loop in the background — emitting the same terminal refine event.
|
|
43580
|
+
* Idempotent: a batch already in flight for this mesh returns the running handle
|
|
43581
|
+
* with duplicate:true rather than spawning a second background job.
|
|
43582
|
+
*/
|
|
43583
|
+
async startMeshRefineBatchJob(meshId, requestedNodeIds, args) {
|
|
43584
|
+
const plan = await this.batchRefineMeshNodes(meshId, requestedNodeIds, { ...args, dryRun: true, execute: false });
|
|
43585
|
+
const planRecord = plan;
|
|
43586
|
+
if (planRecord.success !== true) return plan;
|
|
43587
|
+
if (args?.dryRun === true && args?.execute !== true) return plan;
|
|
43588
|
+
const order = Array.isArray(planRecord.order) ? planRecord.order.filter((v) => typeof v === "string") : [];
|
|
43589
|
+
const nodeIds = order.slice();
|
|
43590
|
+
if (nodeIds.length === 0) {
|
|
43591
|
+
return { ...planRecord, success: true, batch: true, dryRun: false, async: false };
|
|
43592
|
+
}
|
|
43593
|
+
const key = this.buildRefineBatchJobKey(meshId);
|
|
43594
|
+
const running = this.runningRefineBatchJobs.get(key);
|
|
43595
|
+
if (running) return { ...running, duplicate: true };
|
|
43596
|
+
const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
|
|
43597
|
+
const mesh = meshRecord?.mesh;
|
|
43598
|
+
const allNodes = Array.isArray(mesh?.nodes) ? mesh.nodes : [];
|
|
43599
|
+
const orderedNodes = nodeIds.map((id) => allNodes.find((n) => n.id === id || n.nodeId === id)).filter((n) => !!n);
|
|
43600
|
+
if (orderedNodes.length === 0) {
|
|
43601
|
+
return { success: false, error: "Batch nodes no longer resolvable in mesh", batch: true };
|
|
43602
|
+
}
|
|
43603
|
+
const ordering = {
|
|
43604
|
+
order,
|
|
43605
|
+
rationale: planRecord.orderingRationale
|
|
43606
|
+
};
|
|
43607
|
+
const coordinatorDaemonId = typeof args?.coordinatorDaemonId === "string" && args.coordinatorDaemonId.trim() ? args.coordinatorDaemonId.trim() : this.deps.statusInstanceId || void 0;
|
|
43608
|
+
const handle = this.buildRefineBatchJobHandle({ meshId, nodeIds, order, coordinatorDaemonId });
|
|
43609
|
+
this.runningRefineBatchJobs.set(key, handle);
|
|
43610
|
+
await this.appendRefineBatchJobLedger("task_dispatched", handle);
|
|
43611
|
+
this.queueRefineBatchJobEvent("refine:accepted", handle);
|
|
43612
|
+
setImmediate(() => {
|
|
43613
|
+
void this.finishMeshRefineBatchJob(handle, orderedNodes, ordering, args);
|
|
43614
|
+
});
|
|
43615
|
+
return {
|
|
43616
|
+
...handle,
|
|
43617
|
+
order,
|
|
43618
|
+
orderingRationale: planRecord.orderingRationale,
|
|
43619
|
+
plan: planRecord.plan,
|
|
43620
|
+
note: "Batch convergence accepted and running in the background. Completion/failure (with per-node results) will be delivered as a terminal refine event; do not poll repeatedly."
|
|
43621
|
+
};
|
|
43622
|
+
}
|
|
43149
43623
|
async finishMeshRefineJob(handle, args) {
|
|
43150
43624
|
const key = this.buildRefineJobKey(handle.meshId, handle.targetNodeId);
|
|
43151
43625
|
let result;
|
|
@@ -44693,7 +45167,9 @@ ${tail}` : ""
|
|
|
44693
45167
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
44694
45168
|
if (!meshId) return { success: false, error: "meshId required" };
|
|
44695
45169
|
const requestedNodeIds = Array.isArray(args?.nodeIds) ? args.nodeIds.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : void 0;
|
|
44696
|
-
|
|
45170
|
+
const isDryRun = args?.dryRun !== false && args?.execute !== true;
|
|
45171
|
+
if (isDryRun) return this.batchRefineMeshNodes(meshId, requestedNodeIds, args);
|
|
45172
|
+
return this.startMeshRefineBatchJob(meshId, requestedNodeIds, args);
|
|
44697
45173
|
}
|
|
44698
45174
|
case "remove_mesh_node": {
|
|
44699
45175
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|