@runfusion/fusion 0.74.0-beta.1 → 0.74.0-beta.3
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/bin.js +1246 -680
- package/dist/child-process-worker.js +810 -253
- package/dist/client/.vite/manifest.json +268 -268
- package/dist/client/assets/{AgentDetailView-Bopzxiqb.js → AgentDetailView-D0J_GDct.js} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor-i5N7rUlL.js → AgentPermissionPolicyEditor-BBAwprvO.js} +1 -1
- package/dist/client/assets/{AgentsView-M3GoJDHH.js → AgentsView-DTY5DIav.js} +3 -3
- package/dist/client/assets/{ChatView-CqiyA9Kt.js → ChatView-CRoW2tRJ.js} +1 -1
- package/dist/client/assets/{CommandCenter-CAvMGKhl.js → CommandCenter-BieyKvKb.js} +1 -1
- package/dist/client/assets/{DevServerView-wqM6h3nV.js → DevServerView-CLR1LmhK.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-C8OyXCE-.js → DirectoryPicker-B-olNAvT.js} +1 -1
- package/dist/client/assets/{DocumentsView-CjNMZQi8.js → DocumentsView-CG_-pnXU.js} +1 -1
- package/dist/client/assets/{EvalsView-BVlbuB5t.js → EvalsView-Xp0zCUlr.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-CIwCg8sq.js → ExperimentalAgentOnboardingModal-z8m5V5yv.js} +1 -1
- package/dist/client/assets/{GoalsView-Cnw2igil.js → GoalsView-Bd-4g949.js} +1 -1
- package/dist/client/assets/{InsightsView-D7GmD-Kx.js → InsightsView-tCgg66wJ.js} +1 -1
- package/dist/client/assets/{MemoryView-BlqkfIE5.js → MemoryView-Dqgsd_0E.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-EC94zLMq.js → PiExtensionsManager-GzGoWgb5.js} +1 -1
- package/dist/client/assets/{PluginManager-pYyCD3uh.js → PluginManager-BWdqEqr9.js} +1 -1
- package/dist/client/assets/{PullRequestView-BAb0vVhu.js → PullRequestView-CQq1G1kY.js} +1 -1
- package/dist/client/assets/{ReportModal-COmReivA.js → ReportModal-C-3rBFhw.js} +1 -1
- package/dist/client/assets/{ResearchView-DLMo4rFw.js → ResearchView-CS1HwDg_.js} +1 -1
- package/dist/client/assets/{SecretsView-w_O8NLfw.js → SecretsView-C6RP9Rkw.js} +1 -1
- package/dist/client/assets/{SessionTerminal-CSPi-qWE.js → SessionTerminal-C9nCSefk.js} +1 -1
- package/dist/client/assets/SettingsModal-C1oEklYy.css +1 -0
- package/dist/client/assets/{SettingsModal-wilrGXvE.js → SettingsModal-D3DCvCfP.js} +1 -1
- package/dist/client/assets/{SettingsModal-C9bVvYQq.js → SettingsModal-D4mjT8hw.js} +4 -4
- package/dist/client/assets/{SettingsTextareaRow-Bd7_SxVv.js → SettingsTextareaRow-zBVXQbcC.js} +1 -1
- package/dist/client/assets/{SetupWizardModal-sPs6Lw3a.js → SetupWizardModal-C9r1dd-h.js} +2 -2
- package/dist/client/assets/{SkillsView-B9PvYS8t.js → SkillsView-DFyUBWd8.js} +1 -1
- package/dist/client/assets/{TodoView-Df0SD_JT.js → TodoView-BC8f952m.js} +1 -1
- package/dist/client/assets/{WorkflowNodeEditor-B3kFD-S0.js → WorkflowNodeEditor-UTX757WH.js} +1 -1
- package/dist/client/assets/{agent-import-generation-OXfU1-D1.js → agent-import-generation-DN0q1xEo.js} +1 -1
- package/dist/client/assets/{app-DJIiVHdA.js → app-BP57l_5h.js} +1 -1
- package/dist/client/assets/{app-hQw_x2KJ.js → app-D4frL0Ka.js} +1 -1
- package/dist/client/assets/{app-DaikOPcV.js → app-niWDkeV8.js} +3 -3
- package/dist/client/assets/{app-Dn1u7zJ9.js → app-sLOwmHAE.js} +1 -1
- package/dist/client/assets/{app-DMZ_RusZ.js → app-vRWwM-aD.js} +1 -1
- package/dist/client/assets/{app-DxTbBEMH.js → app-zPyey_fg.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-B_8zk4ag.js → architectureDiagram-3BPJPVTR-D-nqJ95-.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-CpDKBJkO.js → blockDiagram-GPEHLZMM--OlkXlyS.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-D3wtfGzI.js → c4Diagram-AAUBKEIU-CH7PaWgs.js} +1 -1
- package/dist/client/assets/channel-FJNMzlkv.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-DS9sHIMv.js → chunk-2J33WTMH-CpHDwqg2.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-B-xW6cfi.js → chunk-4BX2VUAB-BdW4tdnc.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-Ddxf-sPU.js → chunk-55IACEB6-b3tBFiuH.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-Bv_Z1w57.js → chunk-727SXJPM-DifknEtv.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DYHYN43E.js → chunk-AQP2D5EJ-DHIZNZ4Z.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-wuuFvvBk.js → chunk-FMBD7UC4-BLRLYW2y.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-DfVRkVZo.js → chunk-ND2GUHAM-Dh8f06Ld.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-D0t6ryuh.js → chunk-QZHKN3VN-CpzUiemt.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BOenhjVP.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BOenhjVP.js +1 -0
- package/dist/client/assets/common-13IRj_PO.js +1 -0
- package/dist/client/assets/common-BBynATth.js +1 -0
- package/dist/client/assets/common-BG-vw0tL.js +1 -0
- package/dist/client/assets/common-DSo46rnH.js +1 -0
- package/dist/client/assets/common-DbOCE-0i.js +1 -0
- package/dist/client/assets/common-DnXF4Jmj.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-DVlKqNFJ.js → cose-bilkent-S5V4N54A-CE2RJ_k0.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-DTtiGxz-.js → dagre-BM42HDAG-CCs_xlYw.js} +1 -1
- package/dist/client/assets/{dashboard-view-CLjYqvE9.js → dashboard-view-D5qv61fP.js} +1 -1
- package/dist/client/assets/{dashboard-view-D-nDgSmP.js → dashboard-view-Eg7Jyvnm.js} +1 -1
- package/dist/client/assets/{dashboard-view-DH99T6VJ.js → dashboard-view-Z6H-k0uR.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-Tv-YxCWk.js → diagram-2AECGRRQ-CF60Q-de.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-DlAAPkGu.js → diagram-5GNKFQAL-DDcGMlua.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DisatTBB.js → diagram-KO2AKTUF-YgvJ4DQf.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-CDrUxjzW.js → diagram-LMA3HP47-Bi-j9nGw.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-BQ34a4Ej.js → diagram-OG6HWLK6-DEmMqnwc.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-s-YHwAEU.js → erDiagram-TEJ5UH35-DwmZwhiW.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-EpXzmG2d.js → flowDiagram-I6XJVG4X-B5ITa_HJ.js} +1 -1
- package/dist/client/assets/{folder-open-DN86xUly.js → folder-open-BVxA7YtM.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-Dk2WaoX8.js → ganttDiagram-6RSMTGT7-BgAPwh9w.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CjchnKW0.js → gitGraphDiagram-PVQCEYII-DTtGzZFj.js} +1 -1
- package/dist/client/assets/{index-aGZ3DHQw.js → index-idkERMsE.js} +289 -289
- package/dist/client/assets/{infoDiagram-5YYISTIA-CiU-twoj.js → infoDiagram-5YYISTIA-CkSnTje1.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BvylyUKK.js → ishikawaDiagram-YF4QCWOH-jn5VNpMh.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-Ca6z84KB.js → journeyDiagram-JHISSGLW-BpaqoXaI.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-DXbw2-s-.js → kanban-definition-UN3LZRKU-CpfjJoBN.js} +1 -1
- package/dist/client/assets/{mermaid.core-DkS33D12.js → mermaid.core-DZNGPQzF.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-BgXuWPDD.js → mindmap-definition-RKZ34NQL-BdbX1EJa.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-CnOzTN6U.js → pieDiagram-4H26LBE5-Xn0322tJ.js} +1 -1
- package/dist/client/assets/{puzzle-Dxl1kcLF.js → puzzle-CPx1RP2g.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DI3s1u95.js → quadrantDiagram-W4KKPZXB-OOHqqw5y.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-Ck97mo-W.js → requirementDiagram-4Y6WPE33-Dvj6zvaB.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Z3w8NJac.js → sankeyDiagram-5OEKKPKP-DEkEqthK.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DLGtWgfy.js → sequenceDiagram-3UESZ5HK-BPc9ncPj.js} +1 -1
- package/dist/client/assets/{shield-alert-BCGxgBpM.js → shield-alert-baoKRyU6.js} +1 -1
- package/dist/client/assets/{standing-instructions-template-CUE6ItG6.js → standing-instructions-template-CkqsGC1L.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-C6Zl2EJW.js → stateDiagram-AJRCARHV-DywhjHIx.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-BA77mDAW.js → stateDiagram-v2-BHNVJYJU-D7t7x4uT.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-D9Cp15Hm.js → timeline-definition-PNZ67QCA-BH3CKgrg.js} +1 -1
- package/dist/client/assets/{upload-2jCicylS.js → upload-DtIvOGwM.js} +1 -1
- package/dist/client/assets/{users-C1TTGAfX.js → users-DcxsxPlj.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-BwFSh6Tr.js → vennDiagram-CIIHVFJN-CCDllBgJ.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-CQknAo24.js → wardley-L42UT6IY-C43hFLXh.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Dwc8f8bd.js → wardleyDiagram-YWT4CUSO-__f9rI8w.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-CPkeHv8g.js → xychartDiagram-2RQKCTM6-DY-fpQ_x.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/client/version.json +1 -1
- package/dist/extension.js +950 -393
- package/package.json +1 -1
- package/skill/fusion/references/engine-tools.md +1 -1
- package/dist/client/assets/SettingsModal-CMLHZBhX.css +0 -1
- package/dist/client/assets/channel-BLzjKIVz.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DkwPy8u9.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DkwPy8u9.js +0 -1
- package/dist/client/assets/common-BDp2y6CF.js +0 -1
- package/dist/client/assets/common-BnEo5fLN.js +0 -1
- package/dist/client/assets/common-Bzr_Y8h4.js +0 -1
- package/dist/client/assets/common-CvwyFzfv.js +0 -1
- package/dist/client/assets/common-CztCXRkz.js +0 -1
- package/dist/client/assets/common-DPe2mbb7.js +0 -1
package/dist/extension.js
CHANGED
|
@@ -5525,7 +5525,7 @@ var init_sql = __esm({
|
|
|
5525
5525
|
return new SQL([new StringChunk(str)]);
|
|
5526
5526
|
}
|
|
5527
5527
|
sql2.raw = raw;
|
|
5528
|
-
function
|
|
5528
|
+
function join109(chunks, separator) {
|
|
5529
5529
|
const result2 = [];
|
|
5530
5530
|
for (const [i, chunk] of chunks.entries()) {
|
|
5531
5531
|
if (i > 0 && separator !== void 0) {
|
|
@@ -5535,7 +5535,7 @@ var init_sql = __esm({
|
|
|
5535
5535
|
}
|
|
5536
5536
|
return new SQL(result2);
|
|
5537
5537
|
}
|
|
5538
|
-
sql2.join =
|
|
5538
|
+
sql2.join = join109;
|
|
5539
5539
|
function identifier(value) {
|
|
5540
5540
|
return new Name(value);
|
|
5541
5541
|
}
|
|
@@ -10183,7 +10183,7 @@ var init_select2 = __esm({
|
|
|
10183
10183
|
const baseTableName = this.tableName;
|
|
10184
10184
|
const tableName = getTableLikeName(table);
|
|
10185
10185
|
for (const item of extractUsedTable(table)) this.usedTables.add(item);
|
|
10186
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
10186
|
+
if (typeof tableName === "string" && this.config.joins?.some((join109) => join109.alias === tableName)) {
|
|
10187
10187
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
10188
10188
|
}
|
|
10189
10189
|
if (!this.isPartialSelect) {
|
|
@@ -11718,7 +11718,7 @@ var init_update = __esm({
|
|
|
11718
11718
|
createJoin(joinType) {
|
|
11719
11719
|
return (table, on) => {
|
|
11720
11720
|
const tableName = getTableLikeName(table);
|
|
11721
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
11721
|
+
if (typeof tableName === "string" && this.config.joins.some((join109) => join109.alias === tableName)) {
|
|
11722
11722
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
11723
11723
|
}
|
|
11724
11724
|
if (typeof on === "function") {
|
|
@@ -11814,10 +11814,10 @@ var init_update = __esm({
|
|
|
11814
11814
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
11815
11815
|
fields[tableName] = fromFields;
|
|
11816
11816
|
}
|
|
11817
|
-
for (const
|
|
11818
|
-
const tableName2 = getTableLikeName(
|
|
11819
|
-
if (typeof tableName2 === "string" && !is(
|
|
11820
|
-
const fromFields = this.getTableLikeFields(
|
|
11817
|
+
for (const join109 of this.config.joins) {
|
|
11818
|
+
const tableName2 = getTableLikeName(join109.table);
|
|
11819
|
+
if (typeof tableName2 === "string" && !is(join109.table, SQL)) {
|
|
11820
|
+
const fromFields = this.getTableLikeFields(join109.table);
|
|
11821
11821
|
fields[tableName2] = fromFields;
|
|
11822
11822
|
}
|
|
11823
11823
|
}
|
|
@@ -20421,26 +20421,26 @@ function validateParallelism(nodes2, outgoing, nodesById) {
|
|
|
20421
20421
|
}
|
|
20422
20422
|
const joinsReached = /* @__PURE__ */ new Set();
|
|
20423
20423
|
for (const edge of branchEdges) {
|
|
20424
|
-
const
|
|
20425
|
-
if (!
|
|
20424
|
+
const join110 = walkBranchToJoin(edge.to, split.id, outgoing, nodesById);
|
|
20425
|
+
if (!join110) {
|
|
20426
20426
|
throw new WorkflowIrError(`split '${split.id}' has a branch with no reachable matching join`);
|
|
20427
20427
|
}
|
|
20428
|
-
joinsReached.add(
|
|
20428
|
+
joinsReached.add(join110);
|
|
20429
20429
|
}
|
|
20430
20430
|
if (joinsReached.size !== 1) {
|
|
20431
20431
|
throw new WorkflowIrError(`split '${split.id}' branches converge on more than one join`);
|
|
20432
20432
|
}
|
|
20433
20433
|
const joinId = [...joinsReached][0];
|
|
20434
|
-
const
|
|
20435
|
-
const mode =
|
|
20434
|
+
const join109 = nodesById.get(joinId);
|
|
20435
|
+
const mode = join109.config?.mode;
|
|
20436
20436
|
if (mode && typeof mode === "object" && "quorum" in mode) {
|
|
20437
20437
|
const n = mode.quorum;
|
|
20438
20438
|
if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
|
|
20439
|
-
throw new WorkflowIrError(`join '${
|
|
20439
|
+
throw new WorkflowIrError(`join '${join109.id}' quorum must be a positive integer`);
|
|
20440
20440
|
}
|
|
20441
20441
|
if (n > branchEdges.length) {
|
|
20442
20442
|
throw new WorkflowIrError(
|
|
20443
|
-
`join '${
|
|
20443
|
+
`join '${join109.id}' quorum(${n}) exceeds the split's ${branchEdges.length} branches`
|
|
20444
20444
|
);
|
|
20445
20445
|
}
|
|
20446
20446
|
}
|
|
@@ -23996,14 +23996,14 @@ async function detectWorkspaceRepos(dir, runner = runGitCommand, timeout = DEFAU
|
|
|
23996
23996
|
return [];
|
|
23997
23997
|
}
|
|
23998
23998
|
const { stat: stat13 } = await import("node:fs/promises");
|
|
23999
|
-
const { join:
|
|
23999
|
+
const { join: join109 } = await import("node:path");
|
|
24000
24000
|
const found = [];
|
|
24001
24001
|
const EXCLUDED_ENTRIES = /* @__PURE__ */ new Set(["node_modules", ".fusion", ".git", ".pi"]);
|
|
24002
24002
|
for (const entry of entries) {
|
|
24003
24003
|
if (EXCLUDED_ENTRIES.has(entry)) continue;
|
|
24004
|
-
const childDir =
|
|
24004
|
+
const childDir = join109(dir, entry);
|
|
24005
24005
|
try {
|
|
24006
|
-
const s3 = await stat13(
|
|
24006
|
+
const s3 = await stat13(join109(childDir, ".git"));
|
|
24007
24007
|
if (!s3.isDirectory() && !s3.isFile()) continue;
|
|
24008
24008
|
} catch {
|
|
24009
24009
|
continue;
|
|
@@ -24016,9 +24016,9 @@ async function detectWorkspaceRepos(dir, runner = runGitCommand, timeout = DEFAU
|
|
|
24016
24016
|
}
|
|
24017
24017
|
async function isWorkspaceModeExplicitlyDisabled(projectPath) {
|
|
24018
24018
|
try {
|
|
24019
|
-
const { readFile:
|
|
24020
|
-
const { join:
|
|
24021
|
-
const raw = await
|
|
24019
|
+
const { readFile: readFile47 } = await import("node:fs/promises");
|
|
24020
|
+
const { join: join109 } = await import("node:path");
|
|
24021
|
+
const raw = await readFile47(join109(projectPath, ".fusion", "config.json"), "utf-8");
|
|
24022
24022
|
const config3 = JSON.parse(raw);
|
|
24023
24023
|
return config3.settings?.workspaceMode === false;
|
|
24024
24024
|
} catch {
|
|
@@ -24026,12 +24026,12 @@ async function isWorkspaceModeExplicitlyDisabled(projectPath) {
|
|
|
24026
24026
|
}
|
|
24027
24027
|
}
|
|
24028
24028
|
async function setWorkspaceModeInConfig(projectPath, value) {
|
|
24029
|
-
const { readFile:
|
|
24030
|
-
const { join:
|
|
24031
|
-
const configPath =
|
|
24029
|
+
const { readFile: readFile47, writeFile: writeFile35, mkdir: mkdir32 } = await import("node:fs/promises");
|
|
24030
|
+
const { join: join109 } = await import("node:path");
|
|
24031
|
+
const configPath = join109(projectPath, ".fusion", "config.json");
|
|
24032
24032
|
let config3 = {};
|
|
24033
24033
|
try {
|
|
24034
|
-
config3 = JSON.parse(await
|
|
24034
|
+
config3 = JSON.parse(await readFile47(configPath, "utf-8"));
|
|
24035
24035
|
} catch (err) {
|
|
24036
24036
|
if (err?.code !== "ENOENT") throw err;
|
|
24037
24037
|
}
|
|
@@ -24040,7 +24040,7 @@ async function setWorkspaceModeInConfig(projectPath, value) {
|
|
|
24040
24040
|
}
|
|
24041
24041
|
const settings = config3.settings;
|
|
24042
24042
|
settings.workspaceMode = value;
|
|
24043
|
-
await mkdir32(
|
|
24043
|
+
await mkdir32(join109(projectPath, ".fusion"), { recursive: true });
|
|
24044
24044
|
await writeFile35(configPath, JSON.stringify(config3, null, 2), "utf-8");
|
|
24045
24045
|
}
|
|
24046
24046
|
function isInRootRelativePath(entry, pathMod) {
|
|
@@ -24053,12 +24053,12 @@ function isInRootRelativePath(entry, pathMod) {
|
|
|
24053
24053
|
return true;
|
|
24054
24054
|
}
|
|
24055
24055
|
async function loadWorkspaceConfig(rootDir) {
|
|
24056
|
-
const { readFile:
|
|
24056
|
+
const { readFile: readFile47 } = await import("node:fs/promises");
|
|
24057
24057
|
const pathMod = await import("node:path");
|
|
24058
|
-
const { join:
|
|
24059
|
-
const configPath =
|
|
24058
|
+
const { join: join109 } = pathMod;
|
|
24059
|
+
const configPath = join109(rootDir, ".fusion", WORKSPACE_CONFIG_FILENAME);
|
|
24060
24060
|
try {
|
|
24061
|
-
const raw = await
|
|
24061
|
+
const raw = await readFile47(configPath, "utf-8");
|
|
24062
24062
|
const parsed = JSON.parse(raw);
|
|
24063
24063
|
if (parsed !== null && typeof parsed === "object" && "repos" in parsed && Array.isArray(parsed.repos) && parsed.repos.every((r) => typeof r === "string")) {
|
|
24064
24064
|
const rawRepos = parsed.repos;
|
|
@@ -24072,11 +24072,11 @@ async function loadWorkspaceConfig(rootDir) {
|
|
|
24072
24072
|
}
|
|
24073
24073
|
async function saveWorkspaceConfig(rootDir, config3) {
|
|
24074
24074
|
const { mkdir: mkdir32, writeFile: writeFile35 } = await import("node:fs/promises");
|
|
24075
|
-
const { join:
|
|
24076
|
-
const fusionDir =
|
|
24075
|
+
const { join: join109 } = await import("node:path");
|
|
24076
|
+
const fusionDir = join109(rootDir, ".fusion");
|
|
24077
24077
|
await mkdir32(fusionDir, { recursive: true });
|
|
24078
24078
|
await writeFile35(
|
|
24079
|
-
|
|
24079
|
+
join109(fusionDir, WORKSPACE_CONFIG_FILENAME),
|
|
24080
24080
|
JSON.stringify(config3, null, 2),
|
|
24081
24081
|
"utf-8"
|
|
24082
24082
|
);
|
|
@@ -38905,11 +38905,11 @@ async function rekeyFallbackProjectPartition(db, fallbackProjectId, registeredPr
|
|
|
38905
38905
|
const parentKey = `${fk.parentSchema}.${fk.parentTable}`;
|
|
38906
38906
|
if (!targetKeys.has(parentKey)) continue;
|
|
38907
38907
|
const childTarget = targetKeys.has(`${fk.childSchema}.${fk.childTable}`);
|
|
38908
|
-
const
|
|
38908
|
+
const join109 = fk.childColumns.map((column, index2) => `child.${quoteIdentifier(column)} IS NOT DISTINCT FROM parent.${quoteIdentifier(fk.parentColumns[index2])}`).join(" AND ");
|
|
38909
38909
|
const registeredChildScope = childTarget ? ` AND child.${quoteIdentifier("project_id")} = ${quoteLiteral(registeredProjectId)}` : "";
|
|
38910
38910
|
const unreplaced = await tx.execute(sql.raw(`SELECT EXISTS (
|
|
38911
38911
|
SELECT 1 FROM ${relationName({ schema: fk.childSchema, table: fk.childTable })} child
|
|
38912
|
-
JOIN ${relationName({ schema: fk.parentSchema, table: fk.parentTable })} parent ON ${
|
|
38912
|
+
JOIN ${relationName({ schema: fk.parentSchema, table: fk.parentTable })} parent ON ${join109}
|
|
38913
38913
|
JOIN fusion_rekey_conflict_candidates parent_candidate
|
|
38914
38914
|
ON parent_candidate.schema_name = ${quoteLiteral(fk.parentSchema)}
|
|
38915
38915
|
AND parent_candidate.table_name = ${quoteLiteral(fk.parentTable)}
|
|
@@ -49746,8 +49746,8 @@ var require_CronFileParser = __commonJS({
|
|
|
49746
49746
|
* @throws If file cannot be read
|
|
49747
49747
|
*/
|
|
49748
49748
|
static async parseFile(filePath) {
|
|
49749
|
-
const { readFile:
|
|
49750
|
-
const data = await
|
|
49749
|
+
const { readFile: readFile47 } = await Promise.resolve().then(() => __importStar(__require("fs/promises")));
|
|
49750
|
+
const data = await readFile47(filePath, "utf8");
|
|
49751
49751
|
return _CronFileParser.#parseContent(data);
|
|
49752
49752
|
}
|
|
49753
49753
|
/**
|
|
@@ -132671,8 +132671,8 @@ var require_graceful_fs = __commonJS({
|
|
|
132671
132671
|
fs10.createReadStream = createReadStream;
|
|
132672
132672
|
fs10.createWriteStream = createWriteStream;
|
|
132673
132673
|
var fs$readFile = fs10.readFile;
|
|
132674
|
-
fs10.readFile =
|
|
132675
|
-
function
|
|
132674
|
+
fs10.readFile = readFile47;
|
|
132675
|
+
function readFile47(path10, options, cb) {
|
|
132676
132676
|
if (typeof options === "function")
|
|
132677
132677
|
cb = options, options = null;
|
|
132678
132678
|
return go$readFile(path10, options, cb);
|
|
@@ -135388,10 +135388,10 @@ async function runHoldReleaseSweep(store, deps) {
|
|
|
135388
135388
|
}
|
|
135389
135389
|
return result2;
|
|
135390
135390
|
}
|
|
135391
|
-
async function issueRelease(store, deps, task, target, ir2) {
|
|
135391
|
+
async function issueRelease(store, deps, task, target, ir2, options = {}) {
|
|
135392
135392
|
const targetColumn = findColumn2(ir2, target);
|
|
135393
135393
|
const targetIsProcessing = targetColumn ? resolveColumnFlags(targetColumn).countsTowardWip === true : false;
|
|
135394
|
-
if (targetIsProcessing && await isUnplannedForExecution(store, task, ir2)) {
|
|
135394
|
+
if (targetIsProcessing && !options.allowUnplanned && await isUnplannedForExecution(store, task, ir2)) {
|
|
135395
135395
|
schedulerLog.log(`Hold release for ${task.id} blocked \u2014 card is unplanned and cannot enter processing column ${target}`);
|
|
135396
135396
|
return false;
|
|
135397
135397
|
}
|
|
@@ -135433,7 +135433,7 @@ async function issueRelease(store, deps, task, target, ir2) {
|
|
|
135433
135433
|
return false;
|
|
135434
135434
|
}
|
|
135435
135435
|
}
|
|
135436
|
-
async function promoteHeldTask(store, taskId, deps = {}) {
|
|
135436
|
+
async function promoteHeldTask(store, taskId, deps = {}, options = {}) {
|
|
135437
135437
|
const task = await store.getTask(taskId);
|
|
135438
135438
|
if (!task) return { released: false, rejection: "task-not-found" };
|
|
135439
135439
|
const ir2 = await resolveWorkflowIrForTask(store, taskId);
|
|
@@ -135444,17 +135444,50 @@ async function promoteHeldTask(store, taskId, deps = {}) {
|
|
|
135444
135444
|
if (!target) return { released: false, rejection: "no-release-target" };
|
|
135445
135445
|
const targetColumn = findColumn2(ir2, target);
|
|
135446
135446
|
const targetIsProcessing = targetColumn ? resolveColumnFlags(targetColumn).countsTowardWip === true : false;
|
|
135447
|
-
|
|
135447
|
+
const unplanned = targetIsProcessing && await isUnplannedForExecution(store, task, ir2);
|
|
135448
|
+
if (unplanned && options.force !== true) {
|
|
135448
135449
|
return { released: false, rejection: "unplanned-for-execution", toColumn: target };
|
|
135449
135450
|
}
|
|
135451
|
+
let promoted = task;
|
|
135452
|
+
if (unplanned) {
|
|
135453
|
+
if (task.status === "needs-replan" || task.status === "plan-review-unavailable") {
|
|
135454
|
+
try {
|
|
135455
|
+
await store.updateTask(task.id, { status: null });
|
|
135456
|
+
promoted = { ...task, status: void 0 };
|
|
135457
|
+
} catch (error3) {
|
|
135458
|
+
schedulerLog.warn(
|
|
135459
|
+
`Force-promote for ${task.id} could not clear replan status: ${error3 instanceof Error ? error3.message : String(error3)}`
|
|
135460
|
+
);
|
|
135461
|
+
}
|
|
135462
|
+
}
|
|
135463
|
+
if (typeof store.recordRunAuditEvent === "function") {
|
|
135464
|
+
try {
|
|
135465
|
+
await store.recordRunAuditEvent({
|
|
135466
|
+
domain: "database",
|
|
135467
|
+
mutationType: "task:promote-forced-unplanned",
|
|
135468
|
+
target: task.id,
|
|
135469
|
+
taskId: task.id,
|
|
135470
|
+
agentId: "system",
|
|
135471
|
+
runId: `promote-force-${task.id}`,
|
|
135472
|
+
metadata: { fromColumn: task.column, toColumn: target, priorStatus: task.status ?? null }
|
|
135473
|
+
});
|
|
135474
|
+
} catch {
|
|
135475
|
+
}
|
|
135476
|
+
}
|
|
135477
|
+
schedulerLog.log(`Force-promote for ${task.id} bypassing unplanned gate into ${target} (operator override)`);
|
|
135478
|
+
}
|
|
135450
135479
|
const released = await issueRelease(
|
|
135451
135480
|
store,
|
|
135452
135481
|
{ now: () => Date.now(), reserveSlot: deps.reserveSlot, allocateWorktree: deps.allocateWorktree },
|
|
135453
|
-
|
|
135482
|
+
promoted,
|
|
135454
135483
|
target,
|
|
135455
|
-
ir2
|
|
135484
|
+
ir2,
|
|
135485
|
+
{ allowUnplanned: unplanned }
|
|
135456
135486
|
);
|
|
135457
|
-
|
|
135487
|
+
if (!released) {
|
|
135488
|
+
return { released: false, rejection: "capacity-exhausted-or-no-slot" };
|
|
135489
|
+
}
|
|
135490
|
+
return { released: true, toColumn: target, forcedUnplanned: unplanned || void 0 };
|
|
135458
135491
|
}
|
|
135459
135492
|
async function releaseHeldTaskByEvent(store, taskId, eventTag, deps = {}) {
|
|
135460
135493
|
const task = await store.getTask(taskId);
|
|
@@ -138568,6 +138601,12 @@ var init_usage_limit_detector = __esm({
|
|
|
138568
138601
|
});
|
|
138569
138602
|
|
|
138570
138603
|
// ../engine/src/transient-error-patterns.ts
|
|
138604
|
+
function isSessionContentionError(errorMessage2) {
|
|
138605
|
+
if (!errorMessage2 || typeof errorMessage2 !== "string") {
|
|
138606
|
+
return false;
|
|
138607
|
+
}
|
|
138608
|
+
return SESSION_CONTENTION_PATTERNS.some((pattern) => pattern.test(errorMessage2));
|
|
138609
|
+
}
|
|
138571
138610
|
function isTransientError(errorMessage2) {
|
|
138572
138611
|
if (!errorMessage2 || typeof errorMessage2 !== "string") {
|
|
138573
138612
|
return false;
|
|
@@ -138575,6 +138614,9 @@ function isTransientError(errorMessage2) {
|
|
|
138575
138614
|
if (isTransientAuthCredentialError(errorMessage2)) {
|
|
138576
138615
|
return true;
|
|
138577
138616
|
}
|
|
138617
|
+
if (isSessionContentionError(errorMessage2)) {
|
|
138618
|
+
return true;
|
|
138619
|
+
}
|
|
138578
138620
|
return TRANSIENT_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage2));
|
|
138579
138621
|
}
|
|
138580
138622
|
function isTransientAuthCredentialError(errorMessage2) {
|
|
@@ -138586,7 +138628,7 @@ function isTransientAuthCredentialError(errorMessage2) {
|
|
|
138586
138628
|
}
|
|
138587
138629
|
return TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN.test(errorMessage2);
|
|
138588
138630
|
}
|
|
138589
|
-
var TRANSIENT_ERROR_PATTERNS, TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN, OPERATOR_ACTIONABLE_AUTH_EXCLUSION_PATTERN;
|
|
138631
|
+
var TRANSIENT_ERROR_PATTERNS, SESSION_CONTENTION_PATTERNS, TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN, OPERATOR_ACTIONABLE_AUTH_EXCLUSION_PATTERN;
|
|
138590
138632
|
var init_transient_error_patterns = __esm({
|
|
138591
138633
|
"../engine/src/transient-error-patterns.ts"() {
|
|
138592
138634
|
"use strict";
|
|
@@ -138647,6 +138689,14 @@ var init_transient_error_patterns = __esm({
|
|
|
138647
138689
|
/\bACP failed to start\b/i,
|
|
138648
138690
|
/\bACP session has no live connection\b/i
|
|
138649
138691
|
];
|
|
138692
|
+
SESSION_CONTENTION_PATTERNS = [
|
|
138693
|
+
// ActiveSessionPathHeldByForeignTaskError (active-session-registry.ts)
|
|
138694
|
+
/active-session path\s+\S+\s+is held by task\s+\S+/i,
|
|
138695
|
+
// WorkspaceRepoAcquireBusyError (worktree-acquisition.ts)
|
|
138696
|
+
/workspace sub-repo\s+\S+\s+acquisition is in progress for task\s+\S+/i,
|
|
138697
|
+
// WorkspaceRepoLandBusyError (merger-ai.ts)
|
|
138698
|
+
/workspace sub-repo\s+\S+\s+land is in progress for task\s+\S+/i
|
|
138699
|
+
];
|
|
138650
138700
|
TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN = /"type":\s*"authentication_error"|invalid authentication credentials|token[_\s]?expired/i;
|
|
138651
138701
|
OPERATOR_ACTIONABLE_AUTH_EXCLUSION_PATTERN = /oauth token does not meet scope|insufficient[_\s-]?scope|invalid[_\s-]?scope|invalid (?:api[_\s-]?key|x-api-key)|missing\s+(?:\S+\s+)?(?:api[_\s-]?)?key|revoked|suspend(?:ed)?|disabled|deactivated|subscription|account (?:is )?(?:locked|closed|inactive)|access denied/i;
|
|
138652
138702
|
}
|
|
@@ -138655,6 +138705,9 @@ var init_transient_error_patterns = __esm({
|
|
|
138655
138705
|
// ../engine/src/transient-error-detector.ts
|
|
138656
138706
|
function isNonPlanDefectPlanReviewFailure(input) {
|
|
138657
138707
|
if (input.verdict === "REVISE") return false;
|
|
138708
|
+
if (input.failureValue?.trim().toLowerCase() === "session-contention-hold" || (input.errorMessage ? isSessionContentionError(input.errorMessage) : false)) {
|
|
138709
|
+
return false;
|
|
138710
|
+
}
|
|
138658
138711
|
const failureValue = input.failureValue?.trim().toLowerCase();
|
|
138659
138712
|
if (failureValue === "exception" || failureValue === "aborted") return true;
|
|
138660
138713
|
const errorMessage2 = input.errorMessage?.trim();
|
|
@@ -161607,6 +161660,86 @@ var init_research_step_runner = __esm({
|
|
|
161607
161660
|
}
|
|
161608
161661
|
});
|
|
161609
161662
|
|
|
161663
|
+
// ../engine/src/plan-artifact-writeback.ts
|
|
161664
|
+
import { readFile as readFile29 } from "node:fs/promises";
|
|
161665
|
+
import { join as join61 } from "node:path";
|
|
161666
|
+
function relativePromptPath(taskId) {
|
|
161667
|
+
return `.fusion/tasks/${taskId}/PROMPT.md`;
|
|
161668
|
+
}
|
|
161669
|
+
async function readIfPresent(path10) {
|
|
161670
|
+
try {
|
|
161671
|
+
return await readFile29(path10, "utf-8");
|
|
161672
|
+
} catch {
|
|
161673
|
+
return null;
|
|
161674
|
+
}
|
|
161675
|
+
}
|
|
161676
|
+
async function reconcileWorktreePlanArtifact(options) {
|
|
161677
|
+
const { store, taskId, rootDir, planningCwd, logger: logger3 } = options;
|
|
161678
|
+
const relPath = relativePromptPath(taskId);
|
|
161679
|
+
const rootContent = await readIfPresent(join61(rootDir, relPath));
|
|
161680
|
+
if (planningCwd === rootDir) {
|
|
161681
|
+
return { outcome: "not-worktree", content: rootContent ?? void 0 };
|
|
161682
|
+
}
|
|
161683
|
+
const worktreeContent = await readIfPresent(join61(planningCwd, relPath));
|
|
161684
|
+
if (worktreeContent === null) {
|
|
161685
|
+
return { outcome: "no-worktree-artifact", content: rootContent ?? void 0 };
|
|
161686
|
+
}
|
|
161687
|
+
if (worktreeContent.trim().length === 0) {
|
|
161688
|
+
return { outcome: "worktree-artifact-empty", content: rootContent ?? void 0 };
|
|
161689
|
+
}
|
|
161690
|
+
if (rootContent === worktreeContent) {
|
|
161691
|
+
return { outcome: "already-authoritative", content: rootContent };
|
|
161692
|
+
}
|
|
161693
|
+
try {
|
|
161694
|
+
await store.updateTask(taskId, { prompt: worktreeContent });
|
|
161695
|
+
logger3?.log?.(
|
|
161696
|
+
`${taskId}: recovered a worktree-local PROMPT.md into the project .fusion folder (${planningCwd})`
|
|
161697
|
+
);
|
|
161698
|
+
return { outcome: "recovered", content: worktreeContent };
|
|
161699
|
+
} catch (error3) {
|
|
161700
|
+
const message = error3 instanceof Error ? error3.message : String(error3);
|
|
161701
|
+
logger3?.warn?.(
|
|
161702
|
+
`${taskId}: failed to copy the worktree-local PROMPT.md back into the project .fusion folder: ${message}`
|
|
161703
|
+
);
|
|
161704
|
+
return { outcome: "recovery-failed", content: rootContent ?? void 0 };
|
|
161705
|
+
}
|
|
161706
|
+
}
|
|
161707
|
+
async function mirrorPlanToProjectDb(store, taskId, content, options = {}) {
|
|
161708
|
+
if (content.trim().length === 0) return false;
|
|
161709
|
+
if (typeof store.upsertTaskDocument !== "function") return false;
|
|
161710
|
+
try {
|
|
161711
|
+
if (typeof store.getTaskDocument === "function") {
|
|
161712
|
+
const existing = await store.getTaskDocument(taskId, PLAN_DOCUMENT_KEY);
|
|
161713
|
+
if (typeof existing?.content === "string" && existing.content === content) return false;
|
|
161714
|
+
}
|
|
161715
|
+
await store.upsertTaskDocument(taskId, {
|
|
161716
|
+
key: PLAN_DOCUMENT_KEY,
|
|
161717
|
+
content,
|
|
161718
|
+
author: options.author ?? "agent"
|
|
161719
|
+
});
|
|
161720
|
+
return true;
|
|
161721
|
+
} catch (error3) {
|
|
161722
|
+
const message = error3 instanceof Error ? error3.message : String(error3);
|
|
161723
|
+
options.logger?.warn?.(`${taskId}: failed to mirror the plan into the project database: ${message}`);
|
|
161724
|
+
return false;
|
|
161725
|
+
}
|
|
161726
|
+
}
|
|
161727
|
+
async function persistPlanArtifact(options) {
|
|
161728
|
+
const result2 = await reconcileWorktreePlanArtifact(options);
|
|
161729
|
+
const mirrored = result2.content ? await mirrorPlanToProjectDb(options.store, options.taskId, result2.content, {
|
|
161730
|
+
author: options.author,
|
|
161731
|
+
logger: options.logger
|
|
161732
|
+
}) : false;
|
|
161733
|
+
return { ...result2, mirrored };
|
|
161734
|
+
}
|
|
161735
|
+
var PLAN_DOCUMENT_KEY;
|
|
161736
|
+
var init_plan_artifact_writeback = __esm({
|
|
161737
|
+
"../engine/src/plan-artifact-writeback.ts"() {
|
|
161738
|
+
"use strict";
|
|
161739
|
+
PLAN_DOCUMENT_KEY = "plan";
|
|
161740
|
+
}
|
|
161741
|
+
});
|
|
161742
|
+
|
|
161610
161743
|
// ../engine/src/auto-recovery-handlers/message-delivery.ts
|
|
161611
161744
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
161612
161745
|
var baseLog, MessageDeliveryAutoRecoveryHandler;
|
|
@@ -163109,6 +163242,23 @@ var init_worktrunk_installer = __esm({
|
|
|
163109
163242
|
});
|
|
163110
163243
|
|
|
163111
163244
|
// ../engine/src/active-session-registry.ts
|
|
163245
|
+
function acquireActiveSessionPath(registry3, path10, registration, options = {}) {
|
|
163246
|
+
const existing = registry3.lookupByPath(path10);
|
|
163247
|
+
if (!existing || existing.taskId === registration.taskId) {
|
|
163248
|
+
registry3.registerPath(path10, registration);
|
|
163249
|
+
return { action: "registered" };
|
|
163250
|
+
}
|
|
163251
|
+
const now = options.now?.() ?? Date.now();
|
|
163252
|
+
const ageMs = now - existing.registeredAt;
|
|
163253
|
+
const minIdleMs = options.minIdleMs ?? DEFAULT_SELF_OWNED_MIN_IDLE_MS;
|
|
163254
|
+
const holderIsLive = options.holderLiveProbe?.(existing.taskId, path10) ?? true;
|
|
163255
|
+
if (holderIsLive || ageMs < minIdleMs) {
|
|
163256
|
+
return { action: "contended", holderTaskId: existing.taskId, holderKind: existing.kind, ageMs };
|
|
163257
|
+
}
|
|
163258
|
+
registry3.unregisterPath(path10);
|
|
163259
|
+
registry3.registerPath(path10, registration);
|
|
163260
|
+
return { action: "reclaimed-stale-foreign", holderTaskId: existing.taskId, ageMs };
|
|
163261
|
+
}
|
|
163112
163262
|
function reconcileSelfOwnedActiveSessionForRemoval(registry3, worktreePath, requestingTaskId, liveBindingProbe, options = {}) {
|
|
163113
163263
|
const record2 = registry3.lookupByPath(worktreePath);
|
|
163114
163264
|
if (!record2) {
|
|
@@ -163599,7 +163749,7 @@ var init_worktree_prune = __esm({
|
|
|
163599
163749
|
|
|
163600
163750
|
// ../engine/src/worktree-stale-lock.ts
|
|
163601
163751
|
import { exec as exec8 } from "node:child_process";
|
|
163602
|
-
import { readFile as
|
|
163752
|
+
import { readFile as readFile31, stat as stat8, unlink as unlink9 } from "node:fs/promises";
|
|
163603
163753
|
import { dirname as dirname23, resolve as resolve26 } from "node:path";
|
|
163604
163754
|
import { promisify as promisify12 } from "node:util";
|
|
163605
163755
|
function parseIndexLockPath(stderr) {
|
|
@@ -163624,7 +163774,7 @@ async function resolveOwningWorktreePath(input) {
|
|
|
163624
163774
|
if (!worktreeName) return void 0;
|
|
163625
163775
|
const gitdirFile = resolve26(dirname23(input.lockPath), "gitdir");
|
|
163626
163776
|
try {
|
|
163627
|
-
const gitdirRef = (await
|
|
163777
|
+
const gitdirRef = (await readFile31(gitdirFile, "utf-8")).trim();
|
|
163628
163778
|
if (gitdirRef) {
|
|
163629
163779
|
const resolvedGitdirRef = resolve26(dirname23(gitdirFile), gitdirRef);
|
|
163630
163780
|
if (resolvedGitdirRef.endsWith("/.git") || resolvedGitdirRef.endsWith("\\.git")) {
|
|
@@ -165003,7 +165153,7 @@ import { exec as exec11, execFile as execFile9 } from "node:child_process";
|
|
|
165003
165153
|
import { promisify as promisify15 } from "node:util";
|
|
165004
165154
|
import { existsSync as existsSync47, lstatSync as lstatSync2, readdirSync as readdirSync7, readFileSync as readFileSync16, rmSync as rmSync5, realpathSync as realpathSync6 } from "node:fs";
|
|
165005
165155
|
import { mkdir as mkdir24 } from "node:fs/promises";
|
|
165006
|
-
import { basename as basename15, dirname as dirname24, join as
|
|
165156
|
+
import { basename as basename15, dirname as dirname24, join as join62, relative as relative8, resolve as resolve29, isAbsolute as isAbsolute9 } from "node:path";
|
|
165007
165157
|
async function getWorktrunkBinary(settings) {
|
|
165008
165158
|
const cacheKey = `${process.env.HOME ?? ""}::${settings.binaryPath ?? ""}`;
|
|
165009
165159
|
const cached2 = _worktrunkBinaryCache.get(cacheKey);
|
|
@@ -165154,7 +165304,7 @@ async function isRegisteredGitWorktree2(rootDir, worktreePath) {
|
|
|
165154
165304
|
return (await getRegisteredWorktreePaths(rootDir)).has(canonicalizePath(worktreePath));
|
|
165155
165305
|
}
|
|
165156
165306
|
function hasRequiredWorktreeFiles(worktreePath) {
|
|
165157
|
-
return existsSync47(
|
|
165307
|
+
return existsSync47(join62(worktreePath, ".git"));
|
|
165158
165308
|
}
|
|
165159
165309
|
async function isInsideGitWorkTree2(worktreePath) {
|
|
165160
165310
|
try {
|
|
@@ -165287,7 +165437,7 @@ async function scanIdleWorktrees(rootDir, store, settings) {
|
|
|
165287
165437
|
let dirs;
|
|
165288
165438
|
try {
|
|
165289
165439
|
const entries = readdirSync7(worktreesDir, { withFileTypes: true });
|
|
165290
|
-
dirs = entries.filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) =>
|
|
165440
|
+
dirs = entries.filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) => join62(worktreesDir, e.name));
|
|
165291
165441
|
} catch (err) {
|
|
165292
165442
|
const errorMessage2 = err instanceof Error ? err.message : String(err);
|
|
165293
165443
|
worktreePoolLog.warn(`Failed to read .worktrees/ directory: ${errorMessage2}`);
|
|
@@ -165319,7 +165469,7 @@ async function cleanupOrphanedWorktrees(rootDir, store, settings) {
|
|
|
165319
165469
|
let dirs = [];
|
|
165320
165470
|
if (existsSync47(worktreesDir)) {
|
|
165321
165471
|
try {
|
|
165322
|
-
dirs = readdirSync7(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) =>
|
|
165472
|
+
dirs = readdirSync7(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) => join62(worktreesDir, e.name));
|
|
165323
165473
|
} catch (err) {
|
|
165324
165474
|
const errorMessage2 = err instanceof Error ? err.message : String(err);
|
|
165325
165475
|
worktreePoolLog.warn(`Failed to read .worktrees/ directory for cleanup: ${errorMessage2}`);
|
|
@@ -165397,11 +165547,11 @@ async function reapOrphanWorktrees(projectRoot, settings) {
|
|
|
165397
165547
|
entries = readdirSync7(worktreesDir, { withFileTypes: true }).filter((e) => {
|
|
165398
165548
|
if (!e.isDirectory() || isAiMergeContainerDir(e.name)) return false;
|
|
165399
165549
|
try {
|
|
165400
|
-
return lstatSync2(
|
|
165550
|
+
return lstatSync2(join62(worktreesDir, e.name)).isDirectory() && !lstatSync2(join62(worktreesDir, e.name)).isSymbolicLink();
|
|
165401
165551
|
} catch {
|
|
165402
165552
|
return false;
|
|
165403
165553
|
}
|
|
165404
|
-
}).map((e) => ({ name: e.name, fullPath:
|
|
165554
|
+
}).map((e) => ({ name: e.name, fullPath: join62(worktreesDir, e.name) }));
|
|
165405
165555
|
} catch (err) {
|
|
165406
165556
|
const msg = err instanceof Error ? err.message : String(err);
|
|
165407
165557
|
worktreePoolLog.warn(`reapOrphanWorktrees: failed to read .worktrees/ \u2014 ${msg}`);
|
|
@@ -165420,7 +165570,7 @@ async function reapOrphanWorktrees(projectRoot, settings) {
|
|
|
165420
165570
|
if (registered2.has(resolvedFull)) {
|
|
165421
165571
|
continue;
|
|
165422
165572
|
}
|
|
165423
|
-
const dotGit =
|
|
165573
|
+
const dotGit = join62(resolvedFull, ".git");
|
|
165424
165574
|
if (existsSync47(dotGit)) {
|
|
165425
165575
|
if (!dotGitPointerIsDangling(dotGit)) {
|
|
165426
165576
|
worktreePoolLog.log(`reapOrphanWorktrees: skipping ${name2} (has .git entry but not in registered list \u2014 may be partially registered)`);
|
|
@@ -165786,27 +165936,27 @@ var init_worktree_pool = __esm({
|
|
|
165786
165936
|
|
|
165787
165937
|
// ../engine/src/worktree-paths.ts
|
|
165788
165938
|
import { homedir as homedir10 } from "node:os";
|
|
165789
|
-
import { basename as basename16, isAbsolute as isAbsolute10, join as
|
|
165939
|
+
import { basename as basename16, isAbsolute as isAbsolute10, join as join63, relative as relative9, resolve as resolve30 } from "node:path";
|
|
165790
165940
|
function isAiMergeContainerDir(name2) {
|
|
165791
165941
|
return name2 === AI_MERGE_DIRNAME;
|
|
165792
165942
|
}
|
|
165793
165943
|
function resolveAiMergeRootPath(rootDir, settings) {
|
|
165794
|
-
return
|
|
165944
|
+
return join63(resolveWorktreesDir(rootDir, settings), AI_MERGE_DIRNAME);
|
|
165795
165945
|
}
|
|
165796
165946
|
function resolveLegacyAiMergeRootPath(rootDir) {
|
|
165797
|
-
return
|
|
165947
|
+
return join63(rootDir, ".fusion", "ai-merge");
|
|
165798
165948
|
}
|
|
165799
165949
|
function resolveWorktreesDir(rootDir, settings) {
|
|
165800
165950
|
const configured = settings?.worktreesDir;
|
|
165801
165951
|
if (!configured) {
|
|
165802
|
-
return
|
|
165952
|
+
return join63(rootDir, ".worktrees");
|
|
165803
165953
|
}
|
|
165804
165954
|
const expandedHome = configured.replace(/^~(?=$|[\\/])/, homedir10());
|
|
165805
165955
|
const expandedRepo = expandedHome.replaceAll("{repo}", basename16(rootDir));
|
|
165806
165956
|
return resolve30(rootDir, expandedRepo);
|
|
165807
165957
|
}
|
|
165808
165958
|
function resolveTaskWorktreePath(rootDir, settings, worktreeName) {
|
|
165809
|
-
return
|
|
165959
|
+
return join63(resolveWorktreesDir(rootDir, settings), worktreeName);
|
|
165810
165960
|
}
|
|
165811
165961
|
async function resolveTaskWorktreePathForBackend(rootDir, worktreeName, settings, backend, branch) {
|
|
165812
165962
|
if (backend.kind === "worktrunk" && backend.resolveWorktreePath) {
|
|
@@ -166920,9 +167070,9 @@ function assertInRootRepoRelPath(repoRelPath, sep17, isAbsolute27, normalize8) {
|
|
|
166920
167070
|
async function acquireWorkspaceRepoWorktree(opts) {
|
|
166921
167071
|
const { repoRelPath, workspaceRootDir, task, store, settings, logger: logger3, secretsStore, audit, runContext, runConfiguredCommand: runConfiguredCommand2, taskEnv } = opts;
|
|
166922
167072
|
const registry3 = opts.registry ?? activeSessionRegistry;
|
|
166923
|
-
const { join:
|
|
167073
|
+
const { join: join109, isAbsolute: isAbsolute27, normalize: normalize8, sep: sep17 } = await import("node:path");
|
|
166924
167074
|
assertInRootRepoRelPath(repoRelPath, sep17, isAbsolute27, normalize8);
|
|
166925
|
-
const repoAbsPath =
|
|
167075
|
+
const repoAbsPath = join109(workspaceRootDir, repoRelPath);
|
|
166926
167076
|
const existing = task.workspaceWorktrees?.[repoRelPath];
|
|
166927
167077
|
if (existing) {
|
|
166928
167078
|
let live = existsSync49(existing.worktreePath);
|
|
@@ -168646,7 +168796,7 @@ import { execFile as execFile11 } from "node:child_process";
|
|
|
168646
168796
|
import { createRequire as createRequire6 } from "node:module";
|
|
168647
168797
|
import { mkdtemp, rm as rm7, writeFile as writeFile24 } from "node:fs/promises";
|
|
168648
168798
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
168649
|
-
import { join as
|
|
168799
|
+
import { join as join64 } from "node:path";
|
|
168650
168800
|
function getTransformSync() {
|
|
168651
168801
|
if (!cachedTransformSync) {
|
|
168652
168802
|
const req = createRequire6(import.meta.url);
|
|
@@ -168733,9 +168883,9 @@ function readStdin() {
|
|
|
168733
168883
|
async function runCodeNode(opts) {
|
|
168734
168884
|
const timeoutMs = resolveCodeNodeTimeout(opts.timeoutMs);
|
|
168735
168885
|
const compiled = await compileCodeNodeSource(opts.source);
|
|
168736
|
-
const dir = await mkdtemp(
|
|
168737
|
-
const userModuleFile =
|
|
168738
|
-
const harnessFile =
|
|
168886
|
+
const dir = await mkdtemp(join64(tmpdir4(), "fusion-code-node-"));
|
|
168887
|
+
const userModuleFile = join64(dir, "user.mjs");
|
|
168888
|
+
const harnessFile = join64(dir, "harness.mjs");
|
|
168739
168889
|
try {
|
|
168740
168890
|
await writeFile24(userModuleFile, compiled, "utf8");
|
|
168741
168891
|
await writeFile24(harnessFile, buildChildHarness(userModuleFile), "utf8");
|
|
@@ -168963,11 +169113,11 @@ var init_code_node_runner = __esm({
|
|
|
168963
169113
|
});
|
|
168964
169114
|
|
|
168965
169115
|
// ../engine/src/agent-tools.ts
|
|
168966
|
-
import { appendFile as appendFile3, mkdir as mkdir26, readFile as
|
|
169116
|
+
import { appendFile as appendFile3, mkdir as mkdir26, readFile as readFile32, readdir as readdir9, realpath, stat as stat9, writeFile as writeFile25 } from "node:fs/promises";
|
|
168967
169117
|
import { existsSync as existsSync50 } from "node:fs";
|
|
168968
169118
|
import { createHash as createHash16, randomUUID as randomUUID45 } from "node:crypto";
|
|
168969
169119
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
168970
|
-
import { extname as extname3, isAbsolute as isAbsolute12, join as
|
|
169120
|
+
import { extname as extname3, isAbsolute as isAbsolute12, join as join65, relative as relative11, resolve as resolve32, sep as sep9 } from "node:path";
|
|
168971
169121
|
import { Type as Type2 } from "@earendil-works/pi-ai";
|
|
168972
169122
|
function sanitizeAgentMemoryId(agentId) {
|
|
168973
169123
|
return agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
@@ -168979,16 +169129,16 @@ function agentDreamsDisplayPath(agentId) {
|
|
|
168979
169129
|
return `${AGENT_MEMORY_ROOT3}/${sanitizeAgentMemoryId(agentId)}/${AGENT_DREAMS_FILENAME2}`;
|
|
168980
169130
|
}
|
|
168981
169131
|
function agentMemoryDirectory(rootDir, agentId) {
|
|
168982
|
-
return
|
|
169132
|
+
return join65(rootDir, AGENT_MEMORY_ROOT3, sanitizeAgentMemoryId(agentId));
|
|
168983
169133
|
}
|
|
168984
169134
|
function agentMemoryFilePath(rootDir, agentId) {
|
|
168985
|
-
return
|
|
169135
|
+
return join65(agentMemoryDirectory(rootDir, agentId), AGENT_MEMORY_FILENAME2);
|
|
168986
169136
|
}
|
|
168987
169137
|
function agentDreamsFilePath(rootDir, agentId) {
|
|
168988
|
-
return
|
|
169138
|
+
return join65(agentMemoryDirectory(rootDir, agentId), AGENT_DREAMS_FILENAME2);
|
|
168989
169139
|
}
|
|
168990
169140
|
function agentDailyFilePath(rootDir, agentId, date5 = /* @__PURE__ */ new Date()) {
|
|
168991
|
-
return
|
|
169141
|
+
return join65(agentMemoryDirectory(rootDir, agentId), `${date5.toISOString().slice(0, 10)}.md`);
|
|
168992
169142
|
}
|
|
168993
169143
|
async function readAgentMemoryWorkspaceLongTerm(rootDir, agentId) {
|
|
168994
169144
|
const safeRoot = typeof rootDir === "string" ? rootDir.trim() : "";
|
|
@@ -169002,7 +169152,7 @@ async function readAgentMemoryWorkspaceLongTerm(rootDir, agentId) {
|
|
|
169002
169152
|
if (!fileStat.isFile()) {
|
|
169003
169153
|
return "";
|
|
169004
169154
|
}
|
|
169005
|
-
const content = await
|
|
169155
|
+
const content = await readFile32(filePath, "utf-8");
|
|
169006
169156
|
return typeof content === "string" ? content.trim() : "";
|
|
169007
169157
|
} catch {
|
|
169008
169158
|
return "";
|
|
@@ -169081,7 +169231,7 @@ async function listAgentMemoryFiles2(rootDir, agentMemory) {
|
|
|
169081
169231
|
}
|
|
169082
169232
|
for (const entry of entries) {
|
|
169083
169233
|
if (!DAILY_AGENT_MEMORY_RE2.test(entry)) continue;
|
|
169084
|
-
const absPath =
|
|
169234
|
+
const absPath = join65(dir, entry);
|
|
169085
169235
|
const fileStat = await stat9(absPath);
|
|
169086
169236
|
if (fileStat.isFile()) {
|
|
169087
169237
|
files.push({
|
|
@@ -169104,7 +169254,7 @@ async function searchAgentMemoryFile(rootDir, agentMemory, query, limit) {
|
|
|
169104
169254
|
}
|
|
169105
169255
|
const results = [];
|
|
169106
169256
|
for (const file of await listAgentMemoryFiles2(rootDir, agentMemory)) {
|
|
169107
|
-
const content = await
|
|
169257
|
+
const content = await readFile32(file.absPath, "utf-8");
|
|
169108
169258
|
const lines = content.split("\n");
|
|
169109
169259
|
for (let index2 = 0; index2 < lines.length; index2 += 8) {
|
|
169110
169260
|
const chunk = lines.slice(index2, index2 + 12).join("\n").trim();
|
|
@@ -169250,7 +169400,7 @@ function resolveAgentMemoryPath(rootDir, agentId, path10) {
|
|
|
169250
169400
|
return null;
|
|
169251
169401
|
}
|
|
169252
169402
|
return {
|
|
169253
|
-
absPath:
|
|
169403
|
+
absPath: join65(agentMemoryDirectory(rootDir, agentId), filename),
|
|
169254
169404
|
displayPath: `${prefix}${filename}`
|
|
169255
169405
|
};
|
|
169256
169406
|
}
|
|
@@ -169260,7 +169410,7 @@ async function getAgentMemoryWindow(rootDir, agentMemory, path10, startLine = 1,
|
|
|
169260
169410
|
return null;
|
|
169261
169411
|
}
|
|
169262
169412
|
await syncAgentMemoryFile(rootDir, agentMemory);
|
|
169263
|
-
const content = await
|
|
169413
|
+
const content = await readFile32(resolved.absPath, "utf-8");
|
|
169264
169414
|
const lines = content.split("\n");
|
|
169265
169415
|
const start = Math.max(1, Math.floor(startLine));
|
|
169266
169416
|
const count2 = Math.max(1, Math.min(Math.floor(lineCount), 200));
|
|
@@ -169919,6 +170069,9 @@ function createTaskPromptWriteTool(store, taskId, runContext) {
|
|
|
169919
170069
|
if (persisted?.prompt !== params.content) {
|
|
169920
170070
|
throw new Error("authoritative PROMPT.md read-back did not match the requested content; persistence could not be verified");
|
|
169921
170071
|
}
|
|
170072
|
+
await mirrorPlanToProjectDb(store, taskId, params.content, {
|
|
170073
|
+
author: runContext?.agentId ?? "agent"
|
|
170074
|
+
});
|
|
169922
170075
|
return {
|
|
169923
170076
|
content: [{ type: "text", text: `Updated PROMPT.md for ${taskId}.` }],
|
|
169924
170077
|
details: {}
|
|
@@ -170207,7 +170360,7 @@ async function readArtifactFileFromPath(params, baseDir) {
|
|
|
170207
170360
|
if (!mimeType) {
|
|
170208
170361
|
throw new Error(`Could not infer a MIME type from ${resolvedPath}; pass mimeType explicitly.`);
|
|
170209
170362
|
}
|
|
170210
|
-
const data = await
|
|
170363
|
+
const data = await readFile32(canonicalPath);
|
|
170211
170364
|
if (params.type === "image") {
|
|
170212
170365
|
if (!mimeType.startsWith("image/")) {
|
|
170213
170366
|
throw new Error(`image artifacts require an image/* mimeType, got ${mimeType}.`);
|
|
@@ -170550,25 +170703,33 @@ function createTaskPromoteTool(store, currentTaskId) {
|
|
|
170550
170703
|
return {
|
|
170551
170704
|
name: "fn_task_promote",
|
|
170552
170705
|
label: "Promote Held Task",
|
|
170553
|
-
description: "Manually promote a held task out of its hold column, releasing it regardless of the hold's release kind (the explicit operator action a 'manual' hold waits for). Defaults to the current task. Returns the destination column, or a rejection reason when the task is not held or the destination is full.",
|
|
170706
|
+
description: "Manually promote a held task out of its hold column, releasing it regardless of the hold's release kind (the explicit operator action a 'manual' hold waits for). Defaults to the current task. Returns the destination column, or a rejection reason when the task is not held or the destination is full. Pass force:true to start execution even when planning or plan review is still outstanding (that waives the plan gate and cancels the pending replan; capacity still applies).",
|
|
170554
170707
|
parameters: taskPromoteParams,
|
|
170555
170708
|
execute: async (_id, params) => {
|
|
170556
170709
|
const taskId = params.task_id?.trim() || currentTaskId;
|
|
170710
|
+
const force = params.force === true;
|
|
170557
170711
|
try {
|
|
170558
|
-
const outcome = await promoteHeldTask(store, taskId);
|
|
170712
|
+
const outcome = await promoteHeldTask(store, taskId, {}, { force });
|
|
170559
170713
|
if (outcome.released) {
|
|
170714
|
+
const forcedNote = outcome.forcedUnplanned ? " Forced past the outstanding planning/plan review \u2014 the pending replan was cancelled." : "";
|
|
170560
170715
|
return {
|
|
170561
170716
|
content: [{
|
|
170562
170717
|
type: "text",
|
|
170563
|
-
text: `Promoted ${taskId} to column '${outcome.toColumn}'
|
|
170718
|
+
text: `Promoted ${taskId} to column '${outcome.toColumn}'.${forcedNote}`
|
|
170564
170719
|
}],
|
|
170565
|
-
details: {
|
|
170720
|
+
details: {
|
|
170721
|
+
taskId,
|
|
170722
|
+
released: true,
|
|
170723
|
+
toColumn: outcome.toColumn,
|
|
170724
|
+
forcedUnplanned: outcome.forcedUnplanned === true
|
|
170725
|
+
}
|
|
170566
170726
|
};
|
|
170567
170727
|
}
|
|
170728
|
+
const forceHint = outcome.rejection === "unplanned-for-execution" ? " Pass force:true to start execution anyway." : "";
|
|
170568
170729
|
return {
|
|
170569
170730
|
content: [{
|
|
170570
170731
|
type: "text",
|
|
170571
|
-
text: `ERROR: Could not promote ${taskId}: ${outcome.rejection ?? "unknown"}
|
|
170732
|
+
text: `ERROR: Could not promote ${taskId}: ${outcome.rejection ?? "unknown"}.${forceHint}`
|
|
170572
170733
|
}],
|
|
170573
170734
|
details: { taskId, released: false, rejection: outcome.rejection },
|
|
170574
170735
|
isError: true
|
|
@@ -172792,6 +172953,7 @@ var init_agent_tools = __esm({
|
|
|
172792
172953
|
init_provider_registry();
|
|
172793
172954
|
init_research_step_runner();
|
|
172794
172955
|
init_logger3();
|
|
172956
|
+
init_plan_artifact_writeback();
|
|
172795
172957
|
init_web_fetch();
|
|
172796
172958
|
init_agent_action_gate();
|
|
172797
172959
|
init_message_delivery();
|
|
@@ -172971,6 +173133,19 @@ var init_agent_tools = __esm({
|
|
|
172971
173133
|
taskPromoteParams = Type2.Object({
|
|
172972
173134
|
task_id: Type2.Optional(
|
|
172973
173135
|
Type2.String({ description: "Held task to promote. Defaults to the current task." })
|
|
173136
|
+
),
|
|
173137
|
+
/*
|
|
173138
|
+
FNXC:WorkflowScheduling 2026-07-25-05:40:
|
|
173139
|
+
Agent-native parity with the dashboard's force-promote override: an agent that
|
|
173140
|
+
has read the card and judged the pending replan / Plan Review not worth waiting
|
|
173141
|
+
for can start execution anyway. Opt-in per call and never defaulted on — the
|
|
173142
|
+
automatic surfaces (hold-release sweep, webhook release) still cannot force, so
|
|
173143
|
+
FN-7648 holds for everything that is not an explicit promote request.
|
|
173144
|
+
*/
|
|
173145
|
+
force: Type2.Optional(
|
|
173146
|
+
Type2.Boolean({
|
|
173147
|
+
description: "Start execution even when the task is still waiting on planning or plan review (rejection 'unplanned-for-execution'). Waives ONLY that gate \u2014 hold membership and downstream capacity still apply \u2014 and cancels the pending replan. Default false."
|
|
173148
|
+
})
|
|
172974
173149
|
)
|
|
172975
173150
|
});
|
|
172976
173151
|
workflowCreateParams = Type2.Object({
|
|
@@ -173488,7 +173663,7 @@ function hasAdvancedPastPlanning(task) {
|
|
|
173488
173663
|
if (task.column === "in-progress" || task.column === "in-review" || task.column === "done" || task.column === "archived") {
|
|
173489
173664
|
return true;
|
|
173490
173665
|
}
|
|
173491
|
-
if (task.
|
|
173666
|
+
if (task.firstExecutionAt != null || task.executionStartedAt != null) {
|
|
173492
173667
|
return true;
|
|
173493
173668
|
}
|
|
173494
173669
|
if (task.column === "triage") {
|
|
@@ -177279,7 +177454,7 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
177279
177454
|
|
|
177280
177455
|
// ../engine/src/spec-validation/task-document-references.ts
|
|
177281
177456
|
import { access as access7 } from "node:fs/promises";
|
|
177282
|
-
import { join as
|
|
177457
|
+
import { join as join66 } from "node:path";
|
|
177283
177458
|
function normalizePathToken(token) {
|
|
177284
177459
|
return token.replace(/^[`([{"']+/, "").replace(/[`),.;:!?\]}'"]+$/, "");
|
|
177285
177460
|
}
|
|
@@ -177367,7 +177542,7 @@ async function detectDanglingTaskDocReferences(promptContent, opts) {
|
|
|
177367
177542
|
}
|
|
177368
177543
|
const dangling = [];
|
|
177369
177544
|
for (const [path10, sections] of references.entries()) {
|
|
177370
|
-
const absPath =
|
|
177545
|
+
const absPath = join66(opts.rootDir, path10);
|
|
177371
177546
|
if (await existsImpl(absPath)) continue;
|
|
177372
177547
|
dangling.push({ path: path10, sections: Array.from(sections).sort() });
|
|
177373
177548
|
}
|
|
@@ -177628,8 +177803,8 @@ var init_session_skill_context = __esm({
|
|
|
177628
177803
|
});
|
|
177629
177804
|
|
|
177630
177805
|
// ../engine/src/agent-memory-index.ts
|
|
177631
|
-
import { readFile as
|
|
177632
|
-
import { join as
|
|
177806
|
+
import { readFile as readFile33 } from "node:fs/promises";
|
|
177807
|
+
import { join as join67 } from "node:path";
|
|
177633
177808
|
function clampUtf8(input, maxBytes) {
|
|
177634
177809
|
if (Buffer.byteLength(input, "utf8") <= maxBytes) return input;
|
|
177635
177810
|
let out = "";
|
|
@@ -177661,7 +177836,7 @@ function parseHeadings(content) {
|
|
|
177661
177836
|
}
|
|
177662
177837
|
async function buildFileSection(sectionHeader, displayPath, fsPath) {
|
|
177663
177838
|
try {
|
|
177664
|
-
const content = await
|
|
177839
|
+
const content = await readFile33(fsPath, "utf-8");
|
|
177665
177840
|
const headings = parseHeadings(content);
|
|
177666
177841
|
const lines = [sectionHeader, `- ${displayPath}`];
|
|
177667
177842
|
for (const entry of headings) {
|
|
@@ -177677,9 +177852,9 @@ async function buildFileSection(sectionHeader, displayPath, fsPath) {
|
|
|
177677
177852
|
async function buildMemoryIndex({ rootDir, agentId }) {
|
|
177678
177853
|
const safeAgentId = agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
177679
177854
|
const agentDisplayPath = `.fusion/agent-memory/${safeAgentId}/MEMORY.md`;
|
|
177680
|
-
const agentFsPath =
|
|
177855
|
+
const agentFsPath = join67(rootDir, ".fusion", "agent-memory", safeAgentId, "MEMORY.md");
|
|
177681
177856
|
const projectDisplayPath = ".fusion/memory/MEMORY.md";
|
|
177682
|
-
const projectFsPath =
|
|
177857
|
+
const projectFsPath = join67(rootDir, ".fusion", "memory", "MEMORY.md");
|
|
177683
177858
|
const [agentSection, projectSection] = await Promise.all([
|
|
177684
177859
|
buildFileSection("## Agent Memory Index (use fn_memory_search / fn_memory_get to read)", agentDisplayPath, agentFsPath),
|
|
177685
177860
|
buildFileSection("## Project Memory Index", projectDisplayPath, projectFsPath)
|
|
@@ -177698,7 +177873,7 @@ var init_agent_memory_index = __esm({
|
|
|
177698
177873
|
});
|
|
177699
177874
|
|
|
177700
177875
|
// ../engine/src/agent-instructions.ts
|
|
177701
|
-
import { readFile as
|
|
177876
|
+
import { readFile as readFile34, writeFile as writeFile26, mkdir as mkdir27, access as access8 } from "node:fs/promises";
|
|
177702
177877
|
import { constants as fsConstants3 } from "node:fs";
|
|
177703
177878
|
import { isAbsolute as isAbsolute13, resolve as resolve33, relative as relative12, normalize as normalize7, sep as sep10, dirname as dirname28 } from "node:path";
|
|
177704
177879
|
function trimAndClamp(value, maxLength, label, agentId) {
|
|
@@ -177760,7 +177935,7 @@ async function resolveAgentHeartbeatProcedure(agent, rootDir) {
|
|
|
177760
177935
|
return null;
|
|
177761
177936
|
}
|
|
177762
177937
|
try {
|
|
177763
|
-
const content = await
|
|
177938
|
+
const content = await readFile34(filePath, "utf-8");
|
|
177764
177939
|
const normalized = trimAndClamp(
|
|
177765
177940
|
content,
|
|
177766
177941
|
MAX_INSTRUCTIONS_TEXT_LENGTH2,
|
|
@@ -177906,7 +178081,7 @@ async function resolveAgentInstructions(agent, rootDir, ratingSummary, inclusion
|
|
|
177906
178081
|
const filePath = resolveValidatedInstructionsPath(agent.instructionsPath, rootDir, agent.id);
|
|
177907
178082
|
if (filePath) {
|
|
177908
178083
|
try {
|
|
177909
|
-
const content = await
|
|
178084
|
+
const content = await readFile34(filePath, "utf-8");
|
|
177910
178085
|
const normalizedContent = trimAndClamp(
|
|
177911
178086
|
content,
|
|
177912
178087
|
MAX_INSTRUCTIONS_TEXT_LENGTH2,
|
|
@@ -185913,8 +186088,8 @@ var init_mission_symbol_admission = __esm({
|
|
|
185913
186088
|
|
|
185914
186089
|
// ../engine/src/scheduler.ts
|
|
185915
186090
|
import { existsSync as existsSync54 } from "node:fs";
|
|
185916
|
-
import { readFile as
|
|
185917
|
-
import { join as
|
|
186091
|
+
import { readFile as readFile35 } from "node:fs/promises";
|
|
186092
|
+
import { join as join68 } from "node:path";
|
|
185918
186093
|
function logTaskRouting(taskId, node) {
|
|
185919
186094
|
const message = `Task ${taskId} routed to node=${node.nodeId ?? "local"} (source=${node.source})`;
|
|
185920
186095
|
if (node.nodeId === void 0) {
|
|
@@ -186547,16 +186722,16 @@ var init_scheduler = __esm({
|
|
|
186547
186722
|
if (typeof this.store.getTasksDir !== "function") {
|
|
186548
186723
|
return { valid: true };
|
|
186549
186724
|
}
|
|
186550
|
-
const taskDir =
|
|
186725
|
+
const taskDir = join68(this.store.getTasksDir(), id);
|
|
186551
186726
|
if (!existsSync54(taskDir)) {
|
|
186552
186727
|
return { valid: false, reason: "missing directory" };
|
|
186553
186728
|
}
|
|
186554
|
-
const promptPath =
|
|
186729
|
+
const promptPath = join68(taskDir, "PROMPT.md");
|
|
186555
186730
|
if (!existsSync54(promptPath)) {
|
|
186556
186731
|
return { valid: false, reason: "missing or empty PROMPT.md" };
|
|
186557
186732
|
}
|
|
186558
186733
|
try {
|
|
186559
|
-
const content = await
|
|
186734
|
+
const content = await readFile35(promptPath, "utf-8");
|
|
186560
186735
|
if (!content || content.trim().length === 0) {
|
|
186561
186736
|
return { valid: false, reason: "missing or empty PROMPT.md" };
|
|
186562
186737
|
}
|
|
@@ -186926,7 +187101,7 @@ var init_scheduler = __esm({
|
|
|
186926
187101
|
todo = (await Promise.all(
|
|
186927
187102
|
todo.map(async (t) => {
|
|
186928
187103
|
try {
|
|
186929
|
-
const content = await
|
|
187104
|
+
const content = await readFile35(getPromptPath(this.store.getTasksDir(), t.id), "utf-8");
|
|
186930
187105
|
if (isUnplannedSeedPrompt(content, t.id, t.title, t.description)) return null;
|
|
186931
187106
|
} catch {
|
|
186932
187107
|
}
|
|
@@ -190493,10 +190668,10 @@ var init_verification_utils = __esm({
|
|
|
190493
190668
|
import { exec as exec23 } from "node:child_process";
|
|
190494
190669
|
import { createHash as createHash18 } from "node:crypto";
|
|
190495
190670
|
import { existsSync as existsSync55, readFileSync as readFileSync18, writeFileSync as writeFileSync7 } from "node:fs";
|
|
190496
|
-
import { join as
|
|
190671
|
+
import { join as join69 } from "node:path";
|
|
190497
190672
|
import { promisify as promisify32 } from "node:util";
|
|
190498
190673
|
function hasInstallState(rootDir) {
|
|
190499
|
-
return existsSync55(
|
|
190674
|
+
return existsSync55(join69(rootDir, "node_modules")) || existsSync55(join69(rootDir, ".pnp.cjs"));
|
|
190500
190675
|
}
|
|
190501
190676
|
function getConfiguredWorktreeInitCommand(settings) {
|
|
190502
190677
|
const trimmed = settings?.worktreeInitCommand?.trim();
|
|
@@ -190505,17 +190680,17 @@ function getConfiguredWorktreeInitCommand(settings) {
|
|
|
190505
190680
|
function getDependencySyncCommand(rootDir, settings) {
|
|
190506
190681
|
const configuredCommand = getConfiguredWorktreeInitCommand(settings);
|
|
190507
190682
|
if (configuredCommand) return configuredCommand;
|
|
190508
|
-
if (existsSync55(
|
|
190509
|
-
if (existsSync55(
|
|
190510
|
-
if (existsSync55(
|
|
190511
|
-
if (existsSync55(
|
|
190683
|
+
if (existsSync55(join69(rootDir, "pnpm-lock.yaml"))) return "pnpm install --frozen-lockfile";
|
|
190684
|
+
if (existsSync55(join69(rootDir, "package-lock.json"))) return "npm install";
|
|
190685
|
+
if (existsSync55(join69(rootDir, "yarn.lock"))) return "yarn install --frozen-lockfile";
|
|
190686
|
+
if (existsSync55(join69(rootDir, "bun.lock")) || existsSync55(join69(rootDir, "bun.lockb"))) {
|
|
190512
190687
|
return "bun install --frozen-lockfile";
|
|
190513
190688
|
}
|
|
190514
190689
|
return null;
|
|
190515
190690
|
}
|
|
190516
190691
|
function computeLockfileHash(rootDir) {
|
|
190517
190692
|
for (const name2 of LOCKFILE_CANDIDATES) {
|
|
190518
|
-
const p2 =
|
|
190693
|
+
const p2 = join69(rootDir, name2);
|
|
190519
190694
|
if (existsSync55(p2)) {
|
|
190520
190695
|
try {
|
|
190521
190696
|
return createHash18("sha256").update(readFileSync18(p2)).digest("hex");
|
|
@@ -190528,7 +190703,7 @@ function computeLockfileHash(rootDir) {
|
|
|
190528
190703
|
}
|
|
190529
190704
|
function readInstallMarker(rootDir) {
|
|
190530
190705
|
try {
|
|
190531
|
-
const value = readFileSync18(
|
|
190706
|
+
const value = readFileSync18(join69(rootDir, INSTALL_MARKER_RELPATH), "utf-8").trim();
|
|
190532
190707
|
return value || null;
|
|
190533
190708
|
} catch {
|
|
190534
190709
|
return null;
|
|
@@ -190536,7 +190711,7 @@ function readInstallMarker(rootDir) {
|
|
|
190536
190711
|
}
|
|
190537
190712
|
function writeInstallMarker(rootDir, hash) {
|
|
190538
190713
|
try {
|
|
190539
|
-
writeFileSync7(
|
|
190714
|
+
writeFileSync7(join69(rootDir, INSTALL_MARKER_RELPATH), hash);
|
|
190540
190715
|
} catch {
|
|
190541
190716
|
}
|
|
190542
190717
|
}
|
|
@@ -190627,7 +190802,7 @@ var init_merge_dependency_sync = __esm({
|
|
|
190627
190802
|
"../engine/src/merge-dependency-sync.ts"() {
|
|
190628
190803
|
"use strict";
|
|
190629
190804
|
execAsync21 = promisify32(exec23);
|
|
190630
|
-
INSTALL_MARKER_RELPATH =
|
|
190805
|
+
INSTALL_MARKER_RELPATH = join69("node_modules", ".fusion-install-marker");
|
|
190631
190806
|
LOCKFILE_CANDIDATES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb", "bun.lock"];
|
|
190632
190807
|
INSTALL_TIMEOUT_MS = 3e5;
|
|
190633
190808
|
}
|
|
@@ -191004,7 +191179,7 @@ var init_merger_glob = __esm({
|
|
|
191004
191179
|
|
|
191005
191180
|
// ../engine/src/merger-workspace-test-commands.ts
|
|
191006
191181
|
import { existsSync as existsSync56, readdirSync as readdirSync9, readFileSync as readFileSync19 } from "node:fs";
|
|
191007
|
-
import { basename as basename18, dirname as dirname31, join as
|
|
191182
|
+
import { basename as basename18, dirname as dirname31, join as join70 } from "node:path";
|
|
191008
191183
|
import { execSync as execSync4 } from "node:child_process";
|
|
191009
191184
|
function quoteArg2(value) {
|
|
191010
191185
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
@@ -191035,14 +191210,14 @@ function resolveWorkspacePackageRoots(rootDir, globs) {
|
|
|
191035
191210
|
for (const glob of globs) {
|
|
191036
191211
|
const starIdx = glob.indexOf("*");
|
|
191037
191212
|
if (starIdx === -1) {
|
|
191038
|
-
const candidate =
|
|
191039
|
-
if (existsSync56(
|
|
191213
|
+
const candidate = join70(rootDir, glob);
|
|
191214
|
+
if (existsSync56(join70(candidate, "package.json"))) {
|
|
191040
191215
|
roots.push(glob);
|
|
191041
191216
|
}
|
|
191042
191217
|
continue;
|
|
191043
191218
|
}
|
|
191044
191219
|
const prefix = glob.slice(0, starIdx);
|
|
191045
|
-
const parentDir =
|
|
191220
|
+
const parentDir = join70(rootDir, prefix.replace(/\/$/, ""));
|
|
191046
191221
|
let entries;
|
|
191047
191222
|
try {
|
|
191048
191223
|
entries = readdirSync9(parentDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -191051,8 +191226,8 @@ function resolveWorkspacePackageRoots(rootDir, globs) {
|
|
|
191051
191226
|
}
|
|
191052
191227
|
for (const entry of entries) {
|
|
191053
191228
|
const relPath = `${prefix.replace(/\/$/, "")}/${entry}`;
|
|
191054
|
-
const absPath =
|
|
191055
|
-
if (existsSync56(
|
|
191229
|
+
const absPath = join70(rootDir, relPath);
|
|
191230
|
+
if (existsSync56(join70(absPath, "package.json"))) {
|
|
191056
191231
|
roots.push(relPath);
|
|
191057
191232
|
}
|
|
191058
191233
|
}
|
|
@@ -191075,7 +191250,7 @@ function mapChangedFilesToPackageNames(changedFiles, packageRoots, rootDir) {
|
|
|
191075
191250
|
}
|
|
191076
191251
|
if (bestRoot !== null) {
|
|
191077
191252
|
try {
|
|
191078
|
-
const pkgJsonPath =
|
|
191253
|
+
const pkgJsonPath = join70(rootDir, bestRoot, "package.json");
|
|
191079
191254
|
const raw = readFileSync19(pkgJsonPath, "utf-8");
|
|
191080
191255
|
const parsed = JSON.parse(raw);
|
|
191081
191256
|
if (parsed.name) {
|
|
@@ -191092,7 +191267,7 @@ function packageNamesForFiles(rootDir, files) {
|
|
|
191092
191267
|
if (files.length === 0) return [];
|
|
191093
191268
|
let workspaceContent;
|
|
191094
191269
|
try {
|
|
191095
|
-
workspaceContent = readFileSync19(
|
|
191270
|
+
workspaceContent = readFileSync19(join70(rootDir, "pnpm-workspace.yaml"), "utf-8");
|
|
191096
191271
|
} catch {
|
|
191097
191272
|
return [];
|
|
191098
191273
|
}
|
|
@@ -191103,7 +191278,7 @@ function packageNamesForFiles(rootDir, files) {
|
|
|
191103
191278
|
return mapChangedFilesToPackageNames(files, packageRoots, rootDir);
|
|
191104
191279
|
}
|
|
191105
191280
|
function deriveScopedPnpmTestCommand(rootDir, baseBranch, branch) {
|
|
191106
|
-
const workspacePath =
|
|
191281
|
+
const workspacePath = join70(rootDir, "pnpm-workspace.yaml");
|
|
191107
191282
|
let workspaceContent;
|
|
191108
191283
|
try {
|
|
191109
191284
|
workspaceContent = readFileSync19(workspacePath, "utf-8");
|
|
@@ -191141,7 +191316,7 @@ function deriveScopedPnpmTestCommand(rootDir, baseBranch, branch) {
|
|
|
191141
191316
|
function deriveFileScopedPnpmTestCommand(rootDir, baseBranch, branch) {
|
|
191142
191317
|
let workspaceContent;
|
|
191143
191318
|
try {
|
|
191144
|
-
workspaceContent = readFileSync19(
|
|
191319
|
+
workspaceContent = readFileSync19(join70(rootDir, "pnpm-workspace.yaml"), "utf-8");
|
|
191145
191320
|
} catch {
|
|
191146
191321
|
return null;
|
|
191147
191322
|
}
|
|
@@ -191183,7 +191358,7 @@ function deriveFileScopedPnpmTestCommand(rootDir, baseBranch, branch) {
|
|
|
191183
191358
|
];
|
|
191184
191359
|
for (const candidate of candidates) {
|
|
191185
191360
|
const normalized = candidate.startsWith("./") ? candidate.slice(2) : candidate;
|
|
191186
|
-
if (existsSync56(
|
|
191361
|
+
if (existsSync56(join70(rootDir, normalized))) {
|
|
191187
191362
|
resolvedTests.add(normalized);
|
|
191188
191363
|
}
|
|
191189
191364
|
}
|
|
@@ -191211,7 +191386,7 @@ function deriveFileScopedPnpmTestCommand(rootDir, baseBranch, branch) {
|
|
|
191211
191386
|
let name2 = bestRoot;
|
|
191212
191387
|
try {
|
|
191213
191388
|
const parsed = JSON.parse(
|
|
191214
|
-
readFileSync19(
|
|
191389
|
+
readFileSync19(join70(rootDir, bestRoot, "package.json"), "utf-8")
|
|
191215
191390
|
);
|
|
191216
191391
|
if (parsed.name) name2 = parsed.name;
|
|
191217
191392
|
} catch {
|
|
@@ -191255,8 +191430,8 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
|
|
|
191255
191430
|
};
|
|
191256
191431
|
}
|
|
191257
191432
|
const buildSource = explicitBuildCommand?.trim() ? "explicit" : void 0;
|
|
191258
|
-
if (existsSync56(
|
|
191259
|
-
if (existsSync56(
|
|
191433
|
+
if (existsSync56(join70(rootDir, "pnpm-lock.yaml"))) {
|
|
191434
|
+
if (existsSync56(join70(rootDir, "pnpm-workspace.yaml"))) {
|
|
191260
191435
|
if (baseBranch?.trim() && branch?.trim()) {
|
|
191261
191436
|
try {
|
|
191262
191437
|
const scoped = deriveScopedPnpmTestCommand(rootDir, baseBranch.trim(), branch.trim());
|
|
@@ -191275,13 +191450,13 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
|
|
|
191275
191450
|
}
|
|
191276
191451
|
return { command: "pnpm test", testSource: "inferred", buildSource };
|
|
191277
191452
|
}
|
|
191278
|
-
if (existsSync56(
|
|
191453
|
+
if (existsSync56(join70(rootDir, "yarn.lock"))) {
|
|
191279
191454
|
return { command: "yarn test", testSource: "inferred", buildSource };
|
|
191280
191455
|
}
|
|
191281
|
-
if (existsSync56(
|
|
191456
|
+
if (existsSync56(join70(rootDir, "bun.lock")) || existsSync56(join70(rootDir, "bun.lockb"))) {
|
|
191282
191457
|
return { command: "bun test", testSource: "inferred", buildSource };
|
|
191283
191458
|
}
|
|
191284
|
-
if (existsSync56(
|
|
191459
|
+
if (existsSync56(join70(rootDir, "package-lock.json"))) {
|
|
191285
191460
|
return { command: "npm test", testSource: "inferred", buildSource };
|
|
191286
191461
|
}
|
|
191287
191462
|
return null;
|
|
@@ -193558,7 +193733,7 @@ var init_merger_integration_worktree = __esm({
|
|
|
193558
193733
|
import { execFile as execFile23, spawn as spawn7 } from "node:child_process";
|
|
193559
193734
|
import { promisify as promisify43 } from "node:util";
|
|
193560
193735
|
import { copyFileSync, existsSync as existsSync57, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync2, readFileSync as readFileSync20, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
193561
|
-
import { dirname as dirname32, join as
|
|
193736
|
+
import { dirname as dirname32, join as join71 } from "node:path";
|
|
193562
193737
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
193563
193738
|
async function runGit4(args, cwd, timeoutMs) {
|
|
193564
193739
|
const result2 = await execFileAsync16("git", ["-c", "core.quotePath=false", ...args], {
|
|
@@ -193644,9 +193819,9 @@ async function syncWorktreeToHead(input) {
|
|
|
193644
193819
|
if (mode === "ff-only") {
|
|
193645
193820
|
return { kind: "skipped-dirty", fromSha: previousSha, reason: "ff-only-mode-requires-clean-tree", dirtyFiles, untrackedFiles };
|
|
193646
193821
|
}
|
|
193647
|
-
const stageDir = mkdtempSync2(
|
|
193648
|
-
const patchPath =
|
|
193649
|
-
const untrackedDir =
|
|
193822
|
+
const stageDir = mkdtempSync2(join71(tmpdir6(), "fusion-worktree-sync-"));
|
|
193823
|
+
const patchPath = join71(stageDir, "edits.patch");
|
|
193824
|
+
const untrackedDir = join71(stageDir, "untracked");
|
|
193650
193825
|
let preserveStageDir = false;
|
|
193651
193826
|
try {
|
|
193652
193827
|
mkdirSync9(untrackedDir, { recursive: true });
|
|
@@ -193666,8 +193841,8 @@ async function syncWorktreeToHead(input) {
|
|
|
193666
193841
|
}
|
|
193667
193842
|
}
|
|
193668
193843
|
for (const rel of untrackedFiles) {
|
|
193669
|
-
const src =
|
|
193670
|
-
const dst =
|
|
193844
|
+
const src = join71(worktreePath, rel);
|
|
193845
|
+
const dst = join71(untrackedDir, rel);
|
|
193671
193846
|
try {
|
|
193672
193847
|
mkdirSync9(dirname32(dst), { recursive: true });
|
|
193673
193848
|
copyFileSync(src, dst);
|
|
@@ -193730,13 +193905,13 @@ async function syncWorktreeToHead(input) {
|
|
|
193730
193905
|
const restored = [];
|
|
193731
193906
|
const untrackedSkippedAsTracked = [];
|
|
193732
193907
|
for (const rel of untrackedFiles) {
|
|
193733
|
-
const src =
|
|
193908
|
+
const src = join71(untrackedDir, rel);
|
|
193734
193909
|
if (!existsSync57(src)) continue;
|
|
193735
193910
|
if (trackedAtHead.has(rel)) {
|
|
193736
193911
|
untrackedSkippedAsTracked.push(rel);
|
|
193737
193912
|
continue;
|
|
193738
193913
|
}
|
|
193739
|
-
const dst =
|
|
193914
|
+
const dst = join71(worktreePath, rel);
|
|
193740
193915
|
try {
|
|
193741
193916
|
mkdirSync9(dirname32(dst), { recursive: true });
|
|
193742
193917
|
const data = readFileSync20(src);
|
|
@@ -193805,8 +193980,8 @@ var init_worktree_ref_sync = __esm({
|
|
|
193805
193980
|
|
|
193806
193981
|
// ../engine/src/merger-scope-auto-widen.ts
|
|
193807
193982
|
import { exec as exec30 } from "node:child_process";
|
|
193808
|
-
import { readFile as
|
|
193809
|
-
import { join as
|
|
193983
|
+
import { readFile as readFile36, writeFile as writeFile27 } from "node:fs/promises";
|
|
193984
|
+
import { join as join72 } from "node:path";
|
|
193810
193985
|
import { promisify as promisify44 } from "node:util";
|
|
193811
193986
|
function quoteArg4(value) {
|
|
193812
193987
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -193945,8 +194120,8 @@ function parseScopeEntries(section) {
|
|
|
193945
194120
|
async function appendAutoWidenedScopeToPrompt(params) {
|
|
193946
194121
|
const { store, taskId, files } = params;
|
|
193947
194122
|
if (files.length === 0) return [];
|
|
193948
|
-
const promptPath =
|
|
193949
|
-
const prompt = await
|
|
194123
|
+
const promptPath = join72(store.getTaskDir(taskId), "PROMPT.md");
|
|
194124
|
+
const prompt = await readFile36(promptPath, "utf-8");
|
|
193950
194125
|
const { before, section, after } = splitPromptSections(prompt);
|
|
193951
194126
|
const existing = parseScopeEntries(section);
|
|
193952
194127
|
const toAdd = files.filter((file) => !existing.has(file));
|
|
@@ -194102,7 +194277,7 @@ import * as childProcess4 from "node:child_process";
|
|
|
194102
194277
|
import { promisify as promisify45 } from "node:util";
|
|
194103
194278
|
import { existsSync as existsSync58, readFileSync as readFileSync21, writeFileSync as writeFileSync9, unlinkSync as unlinkSync3, renameSync as renameSync5 } from "node:fs";
|
|
194104
194279
|
import { createHash as createHash20 } from "node:crypto";
|
|
194105
|
-
import { join as
|
|
194280
|
+
import { join as join73, resolve as resolve36 } from "node:path";
|
|
194106
194281
|
import { hostname as hostname2 } from "node:os";
|
|
194107
194282
|
import { Type as Type4 } from "typebox";
|
|
194108
194283
|
function mergerCommitEnv() {
|
|
@@ -194415,7 +194590,7 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
|
|
|
194415
194590
|
const deterministicVerificationMessage = "Running deterministic merge verification" + (hasTestCommand ? ` (test${testSourceDisplayLabel}: ${normalizedTestCommand})` : "") + (hasBuildCommand ? ` (build${buildSource === "inferred" ? " [inferred]" : ""}: ${normalizedBuildCommand})` : "");
|
|
194416
194591
|
await store.logEntry(taskId, deterministicVerificationMessage);
|
|
194417
194592
|
await store.appendAgentLog(taskId, deterministicVerificationMessage, "status", void 0, "merger");
|
|
194418
|
-
const bootstrapScriptPath =
|
|
194593
|
+
const bootstrapScriptPath = join73(rootDir, "scripts/ensure-test-artifacts.mjs");
|
|
194419
194594
|
if (hasTestCommand || hasBuildCommand) {
|
|
194420
194595
|
if (!existsSync58(bootstrapScriptPath)) {
|
|
194421
194596
|
const bootstrapMissingMessage = `${taskId}: [verification:bootstrap] script missing at scripts/ensure-test-artifacts.mjs \u2014 skipping preamble`;
|
|
@@ -194914,7 +195089,7 @@ async function stashTreesEqual(rootDir, aSha, bSha) {
|
|
|
194914
195089
|
}
|
|
194915
195090
|
function writeActiveMergerStatus(rootDir, taskId) {
|
|
194916
195091
|
try {
|
|
194917
|
-
const statusPath =
|
|
195092
|
+
const statusPath = join73(rootDir, ".git", ACTIVE_MERGER_STATUS_FILENAME);
|
|
194918
195093
|
const payload = {
|
|
194919
195094
|
taskId,
|
|
194920
195095
|
pid: process.pid,
|
|
@@ -194943,7 +195118,7 @@ function clearActiveMergerStatus(statusPath, taskId) {
|
|
|
194943
195118
|
}
|
|
194944
195119
|
function readActiveMergerStatus(rootDir) {
|
|
194945
195120
|
try {
|
|
194946
|
-
const statusPath =
|
|
195121
|
+
const statusPath = join73(rootDir, ".git", ACTIVE_MERGER_STATUS_FILENAME);
|
|
194947
195122
|
if (!existsSync58(statusPath)) return null;
|
|
194948
195123
|
const raw = readFileSync21(statusPath, "utf-8");
|
|
194949
195124
|
const parsed = JSON.parse(raw);
|
|
@@ -195516,7 +195691,7 @@ async function findFilesWithConflictMarkers(rootDir, files) {
|
|
|
195516
195691
|
const stillConflicted = [];
|
|
195517
195692
|
for (const file of files) {
|
|
195518
195693
|
try {
|
|
195519
|
-
const fullPath =
|
|
195694
|
+
const fullPath = join73(rootDir, file);
|
|
195520
195695
|
if (!existsSync58(fullPath)) continue;
|
|
195521
195696
|
const { stdout } = await execAsync29(
|
|
195522
195697
|
`git grep -l -e "^<<<<<<< " -e "^=======$" -e "^>>>>>>> " --no-index -- ${quoteArg3(fullPath)}`,
|
|
@@ -195543,7 +195718,7 @@ async function tryRecoverHardFailApply(params) {
|
|
|
195543
195718
|
if (!patchText.trim()) {
|
|
195544
195719
|
mergerLog.warn(`${taskId}: autostash ${sha.slice(0, 7)} produced empty patch; cannot 3-way recover`);
|
|
195545
195720
|
} else {
|
|
195546
|
-
const patchPath =
|
|
195721
|
+
const patchPath = join73(rootDir, ".git", `fusion-autostash-${sha.slice(0, 7)}.patch`);
|
|
195547
195722
|
writeFileSync9(patchPath, patchText, "utf-8");
|
|
195548
195723
|
try {
|
|
195549
195724
|
await execAsync29(`git apply --3way --whitespace=nowarn ${quoteArg3(patchPath)}`, { cwd: rootDir });
|
|
@@ -197031,13 +197206,13 @@ async function isCherryPickInProgress(rootDir) {
|
|
|
197031
197206
|
cwd: rootDir,
|
|
197032
197207
|
encoding: "utf-8"
|
|
197033
197208
|
});
|
|
197034
|
-
const cherryPickHeadPath =
|
|
197209
|
+
const cherryPickHeadPath = join73(rootDir, cherryPickHeadPathOut.trim());
|
|
197035
197210
|
if (existsSync58(cherryPickHeadPath)) return true;
|
|
197036
197211
|
const { stdout: sequencerPathOut } = await execAsync29("git rev-parse --git-path sequencer", {
|
|
197037
197212
|
cwd: rootDir,
|
|
197038
197213
|
encoding: "utf-8"
|
|
197039
197214
|
});
|
|
197040
|
-
const sequencerPath =
|
|
197215
|
+
const sequencerPath = join73(rootDir, sequencerPathOut.trim());
|
|
197041
197216
|
return existsSync58(sequencerPath);
|
|
197042
197217
|
} catch {
|
|
197043
197218
|
return false;
|
|
@@ -202080,7 +202255,7 @@ import { promisify as promisify46 } from "node:util";
|
|
|
202080
202255
|
import { setImmediate as setImmediateCb2 } from "node:timers";
|
|
202081
202256
|
import { existsSync as existsSync59, mkdirSync as mkdirSync10, readdirSync as readdirSync10, readFileSync as readFileSync22, realpathSync as realpathSync7, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync10 } from "node:fs";
|
|
202082
202257
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
202083
|
-
import { isAbsolute as isAbsolute14, join as
|
|
202258
|
+
import { isAbsolute as isAbsolute14, join as join74, relative as relative13, resolve as resolve37 } from "node:path";
|
|
202084
202259
|
function resolveRepoLocalAiMergeRoot(rootDir, settings) {
|
|
202085
202260
|
return resolveAiMergeRootPath(rootDir, settings);
|
|
202086
202261
|
}
|
|
@@ -202113,9 +202288,9 @@ async function preserveWorktreeChanges(repoDir, worktreePath, taskId) {
|
|
|
202113
202288
|
return null;
|
|
202114
202289
|
}
|
|
202115
202290
|
const diff = (await execAsync30("git diff HEAD --binary", { cwd: worktreePath, encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 })).stdout;
|
|
202116
|
-
const recoveryDir =
|
|
202291
|
+
const recoveryDir = join74(repoDir, ".fusion", "recovery");
|
|
202117
202292
|
mkdirSync10(recoveryDir, { recursive: true });
|
|
202118
|
-
const patchPath =
|
|
202293
|
+
const patchPath = join74(recoveryDir, `${taskId.toLowerCase()}-${formatRecoveryTimestamp()}.patch`);
|
|
202119
202294
|
writeFileSync10(patchPath, diff, "utf-8");
|
|
202120
202295
|
return patchPath;
|
|
202121
202296
|
} catch (error3) {
|
|
@@ -202252,7 +202427,7 @@ function isNoTaskDoneFailure2(task) {
|
|
|
202252
202427
|
function hasStepProgress2(task) {
|
|
202253
202428
|
return task.steps.some((step) => step.status !== "pending");
|
|
202254
202429
|
}
|
|
202255
|
-
var log28, worktreeMetadataReconcileLog, execAsync30, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER, MAX_NO_PROGRESS_RESUME_ATTEMPTS, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, LEAKED_WORKTREE_SLOT_GRACE_MS, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, RECONCILE_SCOPE_OVERRIDE_MERGE_ACTIVE_STATUS_SET, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, SelfHealingManager;
|
|
202430
|
+
var log28, worktreeMetadataReconcileLog, execAsync30, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER, MAX_NO_PROGRESS_RESUME_ATTEMPTS, PRE_EXECUTION_WORKTREE_MAX_IDLE_MS, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, LEAKED_WORKTREE_SLOT_GRACE_MS, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, RECONCILE_SCOPE_OVERRIDE_MERGE_ACTIVE_STATUS_SET, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, SelfHealingManager;
|
|
202256
202431
|
var init_self_healing = __esm({
|
|
202257
202432
|
"../engine/src/self-healing.ts"() {
|
|
202258
202433
|
"use strict";
|
|
@@ -202308,6 +202483,7 @@ var init_self_healing = __esm({
|
|
|
202308
202483
|
DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
202309
202484
|
PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER = 3;
|
|
202310
202485
|
MAX_NO_PROGRESS_RESUME_ATTEMPTS = 2;
|
|
202486
|
+
PRE_EXECUTION_WORKTREE_MAX_IDLE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
202311
202487
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
202312
202488
|
STARVED_REFINEMENT_RECOVERY_GRACE_MS = 10 * 6e4;
|
|
202313
202489
|
STARVED_PEER_PROGRESS_THRESHOLD = 3;
|
|
@@ -203547,6 +203723,35 @@ var init_self_healing = __esm({
|
|
|
203547
203723
|
}
|
|
203548
203724
|
return false;
|
|
203549
203725
|
}
|
|
203726
|
+
/**
|
|
203727
|
+
* FNXC:WorkflowRecovery 2026-07-25-09:40:
|
|
203728
|
+
* Single shared decision for "is this branch tip's foreign `Fusion-Task-Id`/`Fusion-Task-Lineage` trailer real
|
|
203729
|
+
* evidence of cross-task contamination, or just an inherited start point?"
|
|
203730
|
+
*
|
|
203731
|
+
* A task branch cut from the integration branch points at whatever landed last. When the task has not committed
|
|
203732
|
+
* anything of its own (planning aborted, spec revise, operator move back to `todo`), the branch tip IS that
|
|
203733
|
+
* previous task's commit — a foreign trailer with zero foreign intent. FN-1406 hit exactly this: `fusion/fn-1406`
|
|
203734
|
+
* was cut from `main` at FN-1401's landed commit, planning ended without a commit, and the reclaim sweep logged
|
|
203735
|
+
* `already-merged rejected ... owner=FN-1401 reason=foreign-task-tip` every pass instead of clearing the stale
|
|
203736
|
+
* worktree/branch metadata.
|
|
203737
|
+
*
|
|
203738
|
+
* Merge-base-to-tip diff proof separates the two: no unique diff means the branch carries no task content, so the
|
|
203739
|
+
* foreign trailer is inherited and the caller may treat the row as stale metadata. The one exception is when the
|
|
203740
|
+
* base ALREADY has an explicit commit for this task — then the branch should have been sitting on the task's own
|
|
203741
|
+
* landed commit, and a foreign tip is genuine misbinding worth rejecting.
|
|
203742
|
+
*
|
|
203743
|
+
* Callers: already-merged recovery (`branchTipForeignOwnership`), branch-misbound recovery
|
|
203744
|
+
* (`isBranchTipMisboundToTask`), and the self-owned-branch reclaim sweep's `tip-already-merged` arm. Keeping the
|
|
203745
|
+
* three on one helper is the point — the reclaim arm drifted without the diff proof and that was the bug.
|
|
203746
|
+
*/
|
|
203747
|
+
async foreignTipRejection(input) {
|
|
203748
|
+
const { taskId, lineageId, branchTip, baseBranch, ownership } = input;
|
|
203749
|
+
if (ownership.rejectionReason !== "foreign-task" && ownership.rejectionReason !== "foreign-lineage") return null;
|
|
203750
|
+
const hasNoUniqueDiff = await this.branchHasNoUniqueDiff(branchTip, baseBranch).catch(() => false);
|
|
203751
|
+
const baseAlreadyHasCurrentTask = hasNoUniqueDiff ? await this.baseHasExplicitTaskOwnership(taskId, lineageId, baseBranch).catch(() => false) : false;
|
|
203752
|
+
if (hasNoUniqueDiff && !baseAlreadyHasCurrentTask) return null;
|
|
203753
|
+
return ownership.rejectionReason === "foreign-task" ? { reason: "foreign-task-tip", owner: ownership.ownerTaskId } : { reason: "foreign-lineage-tip", owner: ownership.ownerLineageId };
|
|
203754
|
+
}
|
|
203550
203755
|
async rejectForeignAlreadyMergedCandidate(input) {
|
|
203551
203756
|
const { task, candidateSha, candidateOwner, taskBranch, baseBranch, reason, phase } = input;
|
|
203552
203757
|
await this.store.logEntry(
|
|
@@ -203588,20 +203793,14 @@ var init_self_healing = __esm({
|
|
|
203588
203793
|
}
|
|
203589
203794
|
const sha = stdout.trim();
|
|
203590
203795
|
if (!sha) return null;
|
|
203591
|
-
const hasNoUniqueDiff = await this.branchHasNoUniqueDiff(sha, baseBranch).catch(() => false);
|
|
203592
203796
|
let ownership;
|
|
203593
203797
|
try {
|
|
203594
203798
|
ownership = await this.readCommitTaskOwnership(sha, taskId, lineageId);
|
|
203595
203799
|
} catch {
|
|
203596
203800
|
return { sha, reason: "ownership-unverifiable" };
|
|
203597
203801
|
}
|
|
203598
|
-
const
|
|
203599
|
-
if (
|
|
203600
|
-
return { sha, owner: ownership.ownerTaskId, reason: "foreign-task-tip" };
|
|
203601
|
-
}
|
|
203602
|
-
if ((!hasNoUniqueDiff || baseAlreadyHasCurrentTask) && ownership.rejectionReason === "foreign-lineage") {
|
|
203603
|
-
return { sha, owner: ownership.ownerLineageId, reason: "foreign-lineage-tip" };
|
|
203604
|
-
}
|
|
203802
|
+
const rejection = await this.foreignTipRejection({ taskId, lineageId, branchTip: sha, baseBranch, ownership });
|
|
203803
|
+
if (rejection) return { sha, owner: rejection.owner, reason: rejection.reason };
|
|
203605
203804
|
return null;
|
|
203606
203805
|
}
|
|
203607
203806
|
async resolveSelfHealingMergeTarget(task, settings, phase) {
|
|
@@ -203948,6 +204147,8 @@ var init_self_healing = __esm({
|
|
|
203948
204147
|
{ name: "reconcile-workspace-partial-lands", fn: () => this.reconcileWorkspacePartialLands() },
|
|
203949
204148
|
{ name: "reclaim-phantom-workspace-land-leases", fn: () => this.reclaimPhantomWorkspaceLandLeases() },
|
|
203950
204149
|
{ name: "reconcile-orphaned-workspace-worktrees", fn: () => this.reconcileOrphanedWorkspaceWorktrees() },
|
|
204150
|
+
// FNXC:PlanningEvacuation 2026-07-25-23:00: reclaim worktrees acquired at planning time by cards that never executed.
|
|
204151
|
+
{ name: "reconcile-pre-execution-worktrees", fn: () => this.reconcilePreExecutionWorktrees() },
|
|
203951
204152
|
{ name: "recover-merged-review", fn: () => this.recoverMergedReviewTasks() },
|
|
203952
204153
|
{ name: "recover-already-merged-review", fn: () => this.recoverAlreadyMergedReviewTasks() },
|
|
203953
204154
|
{ name: "recover-post-done-noncontinuable-wedge", fn: () => this.recoverPostDoneNonContinuableWedge() },
|
|
@@ -204815,14 +205016,21 @@ var init_self_healing = __esm({
|
|
|
204815
205016
|
if (!ownership) {
|
|
204816
205017
|
continue;
|
|
204817
205018
|
}
|
|
204818
|
-
|
|
205019
|
+
const foreignRejection = await this.foreignTipRejection({
|
|
205020
|
+
taskId: task.id,
|
|
205021
|
+
lineageId: task.lineageId,
|
|
205022
|
+
branchTip: inspection.tipSha,
|
|
205023
|
+
baseBranch: inspection.integrationRef,
|
|
205024
|
+
ownership
|
|
205025
|
+
});
|
|
205026
|
+
if (foreignRejection) {
|
|
204819
205027
|
await this.rejectForeignAlreadyMergedCandidate({
|
|
204820
205028
|
task,
|
|
204821
205029
|
candidateSha: inspection.tipSha,
|
|
204822
|
-
candidateOwner:
|
|
205030
|
+
candidateOwner: foreignRejection.owner,
|
|
204823
205031
|
taskBranch: branchName,
|
|
204824
205032
|
baseBranch: inspection.integrationRef,
|
|
204825
|
-
reason:
|
|
205033
|
+
reason: foreignRejection.reason,
|
|
204826
205034
|
phase: "tip-already-merged"
|
|
204827
205035
|
});
|
|
204828
205036
|
continue;
|
|
@@ -208762,7 +208970,7 @@ var init_self_healing = __esm({
|
|
|
208762
208970
|
const unrecoverableRepos = [];
|
|
208763
208971
|
for (const repoRel of repoKeys) {
|
|
208764
208972
|
const entry = workspaceWorktrees[repoRel];
|
|
208765
|
-
const repoRootDir =
|
|
208973
|
+
const repoRootDir = join74(this.options.rootDir, repoRel);
|
|
208766
208974
|
let integrationBranch;
|
|
208767
208975
|
try {
|
|
208768
208976
|
integrationBranch = await resolveIntegrationBranch(
|
|
@@ -208974,6 +209182,58 @@ var init_self_healing = __esm({
|
|
|
208974
209182
|
return 0;
|
|
208975
209183
|
}
|
|
208976
209184
|
}
|
|
209185
|
+
/*
|
|
209186
|
+
FNXC:PlanningEvacuation 2026-07-25-23:00 (pre-execution worktree sweep):
|
|
209187
|
+
Planning acquires the task's own worktree, so cards that never reach execution would accumulate
|
|
209188
|
+
worktrees on disk: withdrawn to an intake column, archived from a planner lane, or simply parked.
|
|
209189
|
+
This sweep reclaims them.
|
|
209190
|
+
|
|
209191
|
+
Candidates are addressed from task ROWS (never a directory walk — AGENTS.md forbids unbounded temp
|
|
209192
|
+
scans): tasks holding `worktree` that sit in a non-executing column and carry no execution
|
|
209193
|
+
timestamp. `todo` is deliberately EXCLUDED: a planned card waiting for a WIP slot legitimately
|
|
209194
|
+
keeps its worktree, and churning it would just force a re-acquire minutes later. Every real safety
|
|
209195
|
+
decision (live session, clean branch, execution evidence) belongs to
|
|
209196
|
+
`TaskExecutor.releasePreExecutionWorktree`, so this sweep cannot diverge from the event-driven path
|
|
209197
|
+
that runs on the move itself.
|
|
209198
|
+
*/
|
|
209199
|
+
async reconcilePreExecutionWorktrees() {
|
|
209200
|
+
try {
|
|
209201
|
+
const release2 = this.options.releasePreExecutionWorktree;
|
|
209202
|
+
if (!release2) return 0;
|
|
209203
|
+
const settings = await this.store.getSettings();
|
|
209204
|
+
if (settings.globalPause || settings.enginePaused) return 0;
|
|
209205
|
+
const now = Date.now();
|
|
209206
|
+
const parked = await this.store.listTasks({ slim: true });
|
|
209207
|
+
const candidates = parked.filter((task) => {
|
|
209208
|
+
if (!task.worktree || task.deletedAt) return false;
|
|
209209
|
+
if (task.firstExecutionAt || task.executionStartedAt) return false;
|
|
209210
|
+
if (task.column === "todo" || task.column === "in-progress" || task.column === "in-review" || task.column === "done") return false;
|
|
209211
|
+
if (task.paused || task.userPaused) return false;
|
|
209212
|
+
if (task.status != null) return false;
|
|
209213
|
+
if (task.blockedBy || task.overlapBlockedBy || task.nextRecoveryAt) return false;
|
|
209214
|
+
const lastTouchedMs = Math.max(
|
|
209215
|
+
Date.parse(task.columnMovedAt ?? "") || 0,
|
|
209216
|
+
Date.parse(task.updatedAt ?? "") || 0
|
|
209217
|
+
);
|
|
209218
|
+
if (!lastTouchedMs) return false;
|
|
209219
|
+
return now - lastTouchedMs >= PRE_EXECUTION_WORKTREE_MAX_IDLE_MS;
|
|
209220
|
+
});
|
|
209221
|
+
if (candidates.length === 0) return 0;
|
|
209222
|
+
let released = 0;
|
|
209223
|
+
for (const task of candidates) {
|
|
209224
|
+
try {
|
|
209225
|
+
if (await release2(task.id, `parked pre-execution in '${task.column}'`)) released++;
|
|
209226
|
+
} catch (err) {
|
|
209227
|
+
log28.warn(`reconcilePreExecutionWorktrees: release failed for ${task.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
209228
|
+
}
|
|
209229
|
+
}
|
|
209230
|
+
if (released > 0) log28.log(`reconcilePreExecutionWorktrees: released ${released} pre-execution worktree(s)`);
|
|
209231
|
+
return released;
|
|
209232
|
+
} catch (err) {
|
|
209233
|
+
log28.error(`reconcilePreExecutionWorktrees sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
209234
|
+
return 0;
|
|
209235
|
+
}
|
|
209236
|
+
}
|
|
208977
209237
|
/*
|
|
208978
209238
|
FNXC:Workspace 2026-06-22-09:30 (Phase D U1, KTD4 — per-repo worktree cleanup from STORED paths):
|
|
208979
209239
|
For done/dead workspace tasks, remove each recorded per-repo worktree. The paths are ADDRESSABLE
|
|
@@ -209004,7 +209264,7 @@ var init_self_healing = __esm({
|
|
|
209004
209264
|
if ((this.orphanWorktreeRemovalFailures.get(worktreePath) ?? 0) >= MAX_STARVATION_DROPS) {
|
|
209005
209265
|
continue;
|
|
209006
209266
|
}
|
|
209007
|
-
const repoRootDir =
|
|
209267
|
+
const repoRootDir = join74(this.options.rootDir, repoRel);
|
|
209008
209268
|
let success = false;
|
|
209009
209269
|
let reason = "removed";
|
|
209010
209270
|
try {
|
|
@@ -209925,7 +210185,7 @@ var init_self_healing = __esm({
|
|
|
209925
210185
|
if (testWorkerRoot) {
|
|
209926
210186
|
try {
|
|
209927
210187
|
for (const entry of readdirSync10(testWorkerRoot)) {
|
|
209928
|
-
if (entry.startsWith("redir-")) roots.push(
|
|
210188
|
+
if (entry.startsWith("redir-")) roots.push(join74(testWorkerRoot, entry));
|
|
209929
210189
|
}
|
|
209930
210190
|
} catch {
|
|
209931
210191
|
}
|
|
@@ -209939,7 +210199,7 @@ var init_self_healing = __esm({
|
|
|
209939
210199
|
} catch {
|
|
209940
210200
|
continue;
|
|
209941
210201
|
}
|
|
209942
|
-
for (const entry of entries) paths2.push(
|
|
210202
|
+
for (const entry of entries) paths2.push(join74(root, entry));
|
|
209943
210203
|
}
|
|
209944
210204
|
return paths2;
|
|
209945
210205
|
}
|
|
@@ -210175,14 +210435,10 @@ var init_self_healing = __esm({
|
|
|
210175
210435
|
maxBuffer: 1024 * 1024
|
|
210176
210436
|
});
|
|
210177
210437
|
const branchTip = tipOut.trim();
|
|
210178
|
-
const hasNoUniqueDiff = await this.branchHasNoUniqueDiff(branchTip, baseBranch).catch(() => false);
|
|
210179
210438
|
const ownership = await this.readCommitTaskOwnership(branchTip, taskId, lineageId);
|
|
210180
|
-
const
|
|
210181
|
-
if (
|
|
210182
|
-
return { misbound: false, branchTip, landed: null, rejection
|
|
210183
|
-
}
|
|
210184
|
-
if ((!hasNoUniqueDiff || baseAlreadyHasCurrentTask) && ownership.rejectionReason === "foreign-lineage") {
|
|
210185
|
-
return { misbound: false, branchTip, landed: null, rejection: { reason: "foreign-lineage-tip", owner: ownership.ownerLineageId } };
|
|
210439
|
+
const rejection = await this.foreignTipRejection({ taskId, lineageId, branchTip, baseBranch, ownership });
|
|
210440
|
+
if (rejection) {
|
|
210441
|
+
return { misbound: false, branchTip, landed: null, rejection };
|
|
210186
210442
|
}
|
|
210187
210443
|
const hasTaskId = ownership.ownerTaskId === taskId;
|
|
210188
210444
|
const hasLineage = lineageId ? ownership.ownerLineageId === lineageId : false;
|
|
@@ -211844,7 +212100,7 @@ var init_self_healing = __esm({
|
|
|
211844
212100
|
let processedMarkers = 0;
|
|
211845
212101
|
for (const task of candidates) {
|
|
211846
212102
|
try {
|
|
211847
|
-
const promptPath =
|
|
212103
|
+
const promptPath = join74(this.options.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
211848
212104
|
if (!existsSync59(promptPath)) {
|
|
211849
212105
|
continue;
|
|
211850
212106
|
}
|
|
@@ -212231,7 +212487,7 @@ var init_self_healing = __esm({
|
|
|
212231
212487
|
if (!existsSync59(worktreesDir)) return 0;
|
|
212232
212488
|
let dirs;
|
|
212233
212489
|
try {
|
|
212234
|
-
dirs = readdirSync10(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) =>
|
|
212490
|
+
dirs = readdirSync10(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) => join74(worktreesDir, e.name));
|
|
212235
212491
|
} catch (err) {
|
|
212236
212492
|
log28.warn(`Failed to read .worktrees/ for unregistered orphan reap: ${err instanceof Error ? err.message : String(err)}`);
|
|
212237
212493
|
return 0;
|
|
@@ -212301,7 +212557,7 @@ var init_self_healing = __esm({
|
|
|
212301
212557
|
}
|
|
212302
212558
|
if (entries.length === 0) continue;
|
|
212303
212559
|
for (const entry of entries) {
|
|
212304
|
-
const path10 =
|
|
212560
|
+
const path10 = join74(tempRoot, entry);
|
|
212305
212561
|
let canonicalPath = path10;
|
|
212306
212562
|
let cleanupReason = "stale";
|
|
212307
212563
|
try {
|
|
@@ -212539,8 +212795,8 @@ var init_self_healing = __esm({
|
|
|
212539
212795
|
// ../engine/src/triage.ts
|
|
212540
212796
|
import { Type as Type5 } from "@earendil-works/pi-ai";
|
|
212541
212797
|
import { exec as exec33 } from "node:child_process";
|
|
212542
|
-
import { readFile as
|
|
212543
|
-
import { join as
|
|
212798
|
+
import { readFile as readFile37, writeFile as writeFile28, rm as rm9 } from "node:fs/promises";
|
|
212799
|
+
import { join as join75 } from "node:path";
|
|
212544
212800
|
import { promisify as promisify47 } from "node:util";
|
|
212545
212801
|
function inlineTaskListFallback(lines, opts = {}) {
|
|
212546
212802
|
const maxChars = Math.max(1, Math.floor(opts.maxChars ?? MAX_TASK_LIST_TEXT_CHARS));
|
|
@@ -212591,10 +212847,10 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
212591
212847
|
if (!attachments || attachments.length === 0) {
|
|
212592
212848
|
return { attachmentContents, imageContents };
|
|
212593
212849
|
}
|
|
212594
|
-
const { readFile:
|
|
212595
|
-
const { join:
|
|
212850
|
+
const { readFile: readFile47 } = await import("node:fs/promises");
|
|
212851
|
+
const { join: join109 } = await import("node:path");
|
|
212596
212852
|
for (const att of attachments) {
|
|
212597
|
-
const filePath =
|
|
212853
|
+
const filePath = join109(
|
|
212598
212854
|
rootDir,
|
|
212599
212855
|
".fusion",
|
|
212600
212856
|
"tasks",
|
|
@@ -212604,7 +212860,7 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
212604
212860
|
);
|
|
212605
212861
|
try {
|
|
212606
212862
|
if (IMAGE_MIME_TYPES.has(att.mimeType)) {
|
|
212607
|
-
const data = await
|
|
212863
|
+
const data = await readFile47(filePath);
|
|
212608
212864
|
const detectedMimeType = detectImageMimeFromBytes(data);
|
|
212609
212865
|
const imageMimeType = detectedMimeType ?? att.mimeType;
|
|
212610
212866
|
if (detectedMimeType && detectedMimeType !== att.mimeType) {
|
|
@@ -212621,7 +212877,7 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
212621
212877
|
text: null
|
|
212622
212878
|
});
|
|
212623
212879
|
} else {
|
|
212624
|
-
const data = await
|
|
212880
|
+
const data = await readFile47(filePath, "utf-8");
|
|
212625
212881
|
const text2 = data.length > TEXT_INLINE_LIMIT ? data.slice(0, TEXT_INLINE_LIMIT) + "\n... (truncated at 50KB)" : data;
|
|
212626
212882
|
attachmentContents.push({
|
|
212627
212883
|
originalName: att.originalName,
|
|
@@ -212862,6 +213118,7 @@ var init_triage = __esm({
|
|
|
212862
213118
|
init_prompt_layers();
|
|
212863
213119
|
init_fallback_model_observer();
|
|
212864
213120
|
init_logger3();
|
|
213121
|
+
init_plan_artifact_writeback();
|
|
212865
213122
|
init_mcp_resolution();
|
|
212866
213123
|
init_usage_limit_detector();
|
|
212867
213124
|
init_transient_error_detector();
|
|
@@ -212972,6 +213229,33 @@ var init_triage = __esm({
|
|
|
212972
213229
|
if (this.processing.has(task.id) || this.hasLivePlanningWork(task.id)) return;
|
|
212973
213230
|
this.requestImmediatePoll();
|
|
212974
213231
|
};
|
|
213232
|
+
this.taskEvacuatedFromPlanningHandler = (task) => {
|
|
213233
|
+
if (!task?.id) return;
|
|
213234
|
+
if (typeof task.column !== "string") return;
|
|
213235
|
+
if (task.column === "todo" || task.column === "triage" || task.column === "in-progress") return;
|
|
213236
|
+
if (this.activeSubagentSessions.has(task.id)) {
|
|
213237
|
+
this.disposeSubagentsForTask(task.id, `task moved to ${task.column}`);
|
|
213238
|
+
}
|
|
213239
|
+
const session = this.activeSessions.get(task.id);
|
|
213240
|
+
if (!session) return;
|
|
213241
|
+
planLog.log(`task moved out of planning to '${task.column}' \u2014 terminating triage session for ${task.id}`);
|
|
213242
|
+
this.pauseAborted.add(task.id);
|
|
213243
|
+
this.options.stuckTaskDetector?.untrackTask(task.id);
|
|
213244
|
+
const sessionWithAbort = session;
|
|
213245
|
+
if (typeof sessionWithAbort.abort === "function") {
|
|
213246
|
+
void sessionWithAbort.abort().catch((err) => {
|
|
213247
|
+
planLog.warn(`Failed to abort triage session for ${task.id}: ${err}`);
|
|
213248
|
+
});
|
|
213249
|
+
}
|
|
213250
|
+
this.recordTriageSessionTokenUsageSoon(task.id, session, { agentId: task.assignedAgentId ?? "triage" });
|
|
213251
|
+
session.dispose();
|
|
213252
|
+
this.activeSessions.delete(task.id);
|
|
213253
|
+
if (task.status === "planning") {
|
|
213254
|
+
void Promise.resolve(this.store.updateTask(task.id, { status: null })).catch((err) => {
|
|
213255
|
+
planLog.warn(`${task.id}: failed to clear planning status after evacuation: ${err instanceof Error ? err.message : String(err)}`);
|
|
213256
|
+
});
|
|
213257
|
+
}
|
|
213258
|
+
};
|
|
212975
213259
|
}
|
|
212976
213260
|
running = false;
|
|
212977
213261
|
polling = false;
|
|
@@ -213030,6 +213314,8 @@ var init_triage = __esm({
|
|
|
213030
213314
|
taskPausedHandler;
|
|
213031
213315
|
/** FNXC:CodingIdeasWorkflow 2026-07-25-11:20: store-event wake for planning-eligible columns. */
|
|
213032
213316
|
taskColumnWakeHandler;
|
|
213317
|
+
/** FNXC:PlanningEvacuation 2026-07-25-23:00: stops planning when a card leaves the planner lanes. */
|
|
213318
|
+
taskEvacuatedFromPlanningHandler;
|
|
213033
213319
|
_approvalRequestStore;
|
|
213034
213320
|
/**
|
|
213035
213321
|
* @param store — Task store instance (also used to listen for `settings:updated` events)
|
|
@@ -213138,6 +213424,9 @@ var init_triage = __esm({
|
|
|
213138
213424
|
this.store.on("task:updated", this.taskColumnWakeHandler);
|
|
213139
213425
|
this.store.on("task:created", this.taskColumnWakeHandler);
|
|
213140
213426
|
}
|
|
213427
|
+
if (this.taskEvacuatedFromPlanningHandler && typeof this.store.on === "function") {
|
|
213428
|
+
this.store.on("task:updated", this.taskEvacuatedFromPlanningHandler);
|
|
213429
|
+
}
|
|
213141
213430
|
this.clearStaleSpecifyingStatuses().catch((err) => {
|
|
213142
213431
|
planLog.error("Failed to clear stale planning statuses:", err);
|
|
213143
213432
|
});
|
|
@@ -213185,6 +213474,9 @@ var init_triage = __esm({
|
|
|
213185
213474
|
this.store.off("task:updated", this.taskColumnWakeHandler);
|
|
213186
213475
|
this.store.off("task:created", this.taskColumnWakeHandler);
|
|
213187
213476
|
}
|
|
213477
|
+
if (this.taskEvacuatedFromPlanningHandler && typeof this.store.off === "function") {
|
|
213478
|
+
this.store.off("task:updated", this.taskEvacuatedFromPlanningHandler);
|
|
213479
|
+
}
|
|
213188
213480
|
this.abortAndDisposeActiveSessions("engine stop");
|
|
213189
213481
|
planLog.log("Processor stopped");
|
|
213190
213482
|
}
|
|
@@ -213383,8 +213675,8 @@ var init_triage = __esm({
|
|
|
213383
213675
|
}
|
|
213384
213676
|
const settings = await mergeEffectiveSettings(this.store, task, await this.store.getSettings());
|
|
213385
213677
|
const approvalRequired = resolvePlanApprovalRequired(settings);
|
|
213386
|
-
const promptPath =
|
|
213387
|
-
const written = await
|
|
213678
|
+
const promptPath = join75(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
213679
|
+
const written = await readFile37(promptPath, "utf-8").catch((err) => {
|
|
213388
213680
|
const msg = err instanceof Error ? err.message : String(err);
|
|
213389
213681
|
planLog.warn(`${task.id}: failed to read PROMPT.md during planning recovery (${promptPath}): ${msg}`);
|
|
213390
213682
|
return "";
|
|
@@ -213422,8 +213714,8 @@ var init_triage = __esm({
|
|
|
213422
213714
|
return true;
|
|
213423
213715
|
}
|
|
213424
213716
|
async readNonEmptyPromptDraft(taskId, context) {
|
|
213425
|
-
const promptPath =
|
|
213426
|
-
const written = await
|
|
213717
|
+
const promptPath = join75(this.rootDir, ".fusion", "tasks", taskId, "PROMPT.md");
|
|
213718
|
+
const written = await readFile37(promptPath, "utf-8").catch((err) => {
|
|
213427
213719
|
const msg = err instanceof Error ? err.message : String(err);
|
|
213428
213720
|
planLog.warn(`${taskId}: failed to read PROMPT.md during ${context} (${promptPath}): ${msg}`);
|
|
213429
213721
|
return "";
|
|
@@ -213587,8 +213879,8 @@ var init_triage = __esm({
|
|
|
213587
213879
|
continue;
|
|
213588
213880
|
}
|
|
213589
213881
|
try {
|
|
213590
|
-
const promptPath =
|
|
213591
|
-
const content = await
|
|
213882
|
+
const promptPath = join75(this.rootDir, ".fusion", "tasks", todoTask.id, "PROMPT.md");
|
|
213883
|
+
const content = await readFile37(promptPath, "utf-8");
|
|
213592
213884
|
if (isUnplannedSeedPrompt(content, todoTask.id, todoTask.title, todoTask.description)) {
|
|
213593
213885
|
eligibleTodoTasks.push(todoTask);
|
|
213594
213886
|
}
|
|
@@ -213799,7 +214091,7 @@ var init_triage = __esm({
|
|
|
213799
214091
|
const detail = await this.store.getTask(task.id);
|
|
213800
214092
|
const currentTask = detail ?? task;
|
|
213801
214093
|
const settings = await mergeEffectiveSettings(this.store, currentTask, await this.store.getSettings());
|
|
213802
|
-
const promptPath =
|
|
214094
|
+
const promptPath = relativePromptPath(task.id);
|
|
213803
214095
|
const isFast = task.executionMode === "fast";
|
|
213804
214096
|
const leanPlanning = settings.leanPlanning === true || isFast;
|
|
213805
214097
|
const agentWork = async () => {
|
|
@@ -213976,11 +214268,15 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
213976
214268
|
planningModel.modelId,
|
|
213977
214269
|
assignedAgent?.runtimeConfig
|
|
213978
214270
|
) : { provider: void 0, modelId: void 0 };
|
|
214271
|
+
const planningCwd = await this.options.acquirePlanningWorktree?.(task.id).catch(() => null) || this.rootDir;
|
|
214272
|
+
if (planningCwd !== this.rootDir) {
|
|
214273
|
+
await this.store.logEntry(task.id, `Planning session running in task worktree ${planningCwd}`).catch(() => void 0);
|
|
214274
|
+
}
|
|
213979
214275
|
const { session } = await createResolvedAgentSession({
|
|
213980
214276
|
sessionPurpose: "triage",
|
|
213981
214277
|
runtimeHint: triageRuntimeHint,
|
|
213982
214278
|
pluginRunner: this.options.pluginRunner,
|
|
213983
|
-
cwd:
|
|
214279
|
+
cwd: planningCwd,
|
|
213984
214280
|
systemPrompt: triageSystemPromptFinal,
|
|
213985
214281
|
systemPromptLayers: triageLayers,
|
|
213986
214282
|
tools: "coding",
|
|
@@ -214131,8 +214427,22 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
214131
214427
|
}
|
|
214132
214428
|
return;
|
|
214133
214429
|
}
|
|
214134
|
-
const
|
|
214135
|
-
|
|
214430
|
+
const planPersistence = await persistPlanArtifact({
|
|
214431
|
+
store: this.store,
|
|
214432
|
+
taskId: task.id,
|
|
214433
|
+
rootDir: this.rootDir,
|
|
214434
|
+
planningCwd,
|
|
214435
|
+
author: "triage",
|
|
214436
|
+
logger: { log: (m2) => planLog.log(m2), warn: (m2) => planLog.warn(m2) }
|
|
214437
|
+
});
|
|
214438
|
+
if (planPersistence.outcome === "recovered") {
|
|
214439
|
+
await this.store.logEntry(
|
|
214440
|
+
task.id,
|
|
214441
|
+
"Recovered the plan written inside the task worktree into the project .fusion folder"
|
|
214442
|
+
).catch(() => void 0);
|
|
214443
|
+
}
|
|
214444
|
+
const written = await readFile37(
|
|
214445
|
+
join75(this.rootDir, promptPath),
|
|
214136
214446
|
"utf-8"
|
|
214137
214447
|
).catch((err) => {
|
|
214138
214448
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -214749,7 +215059,7 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
214749
215059
|
if (isNearDuplicateCanonicalInactive(canonicalTask ?? void 0)) {
|
|
214750
215060
|
if (canClearInactiveMarker) {
|
|
214751
215061
|
if (!await this.runIfStillPlanningUnderTaskLock(task, async () => {
|
|
214752
|
-
await rm9(
|
|
215062
|
+
await rm9(join75(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), { force: true });
|
|
214753
215063
|
})) return;
|
|
214754
215064
|
await this.updatePlanningStateIfStillCurrent(task, { paused: false, pausedReason: null, status: null });
|
|
214755
215065
|
}
|
|
@@ -214760,7 +215070,7 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
214760
215070
|
if (resolution === "prompt" && keepAcknowledged) {
|
|
214761
215071
|
if (canClearInactiveMarker) {
|
|
214762
215072
|
if (!await this.runIfStillPlanningUnderTaskLock(task, async () => {
|
|
214763
|
-
await rm9(
|
|
215073
|
+
await rm9(join75(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), { force: true });
|
|
214764
215074
|
})) return;
|
|
214765
215075
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
214766
215076
|
paused: false,
|
|
@@ -214801,7 +215111,7 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
214801
215111
|
return;
|
|
214802
215112
|
}
|
|
214803
215113
|
if (!await this.runIfStillPlanningUnderTaskLock(task, async () => {
|
|
214804
|
-
await rm9(
|
|
215114
|
+
await rm9(join75(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), { force: true });
|
|
214805
215115
|
})) return;
|
|
214806
215116
|
if (!await this.updatePlanningStateIfStillCurrent(task, {
|
|
214807
215117
|
paused: false,
|
|
@@ -214866,7 +215176,7 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
214866
215176
|
let nextPrompt = applyOriginalDescription(written, originalDescription);
|
|
214867
215177
|
nextPrompt = applyFrontendUxCriteria(nextPrompt, parsedFileScope);
|
|
214868
215178
|
if (nextPrompt !== written) {
|
|
214869
|
-
const promptPath =
|
|
215179
|
+
const promptPath = join75(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
214870
215180
|
try {
|
|
214871
215181
|
if (!await this.runIfStillPlanningUnderTaskLock(task, async () => {
|
|
214872
215182
|
await writeFile28(promptPath, nextPrompt, "utf-8");
|
|
@@ -214878,6 +215188,10 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
214878
215188
|
}
|
|
214879
215189
|
}
|
|
214880
215190
|
}
|
|
215191
|
+
await mirrorPlanToProjectDb(this.store, task.id, written, {
|
|
215192
|
+
author: "triage",
|
|
215193
|
+
logger: { warn: (m2) => planLog.warn(m2) }
|
|
215194
|
+
});
|
|
214881
215195
|
let taskIntentSignature = {
|
|
214882
215196
|
routePaths: [],
|
|
214883
215197
|
filePaths: [],
|
|
@@ -215124,10 +215438,10 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
215124
215438
|
});
|
|
215125
215439
|
|
|
215126
215440
|
// ../engine/src/review-artifacts/feature-video.ts
|
|
215127
|
-
import { access as access9, mkdtemp as mkdtemp2, readFile as
|
|
215441
|
+
import { access as access9, mkdtemp as mkdtemp2, readFile as readFile38, rm as rm10 } from "node:fs/promises";
|
|
215128
215442
|
import { constants as constants2 } from "node:fs";
|
|
215129
215443
|
import { tmpdir as tmpdir8 } from "node:os";
|
|
215130
|
-
import { join as
|
|
215444
|
+
import { join as join76 } from "node:path";
|
|
215131
215445
|
function shouldGenerateReviewArtifacts(task, settings) {
|
|
215132
215446
|
return isReviewArtifactGenerationEligible(settings, task.prompt);
|
|
215133
215447
|
}
|
|
@@ -215153,7 +215467,7 @@ async function generateFeatureVideo(options) {
|
|
|
215153
215467
|
let browser;
|
|
215154
215468
|
let context;
|
|
215155
215469
|
try {
|
|
215156
|
-
recordingDir = await mkdtemp2(
|
|
215470
|
+
recordingDir = await mkdtemp2(join76(tmpdir8(), "fusion-feature-video-"));
|
|
215157
215471
|
try {
|
|
215158
215472
|
browser = await client.launch({ executablePath, headless: true });
|
|
215159
215473
|
} catch {
|
|
@@ -215171,7 +215485,7 @@ async function generateFeatureVideo(options) {
|
|
|
215171
215485
|
await (options.sleep ?? defaultSleep)(Math.min(Math.max(options.durationMs ?? DEFAULT_DURATION_MS, 0), MAX_DURATION_MS));
|
|
215172
215486
|
await context.close();
|
|
215173
215487
|
context = void 0;
|
|
215174
|
-
const data = await
|
|
215488
|
+
const data = await readFile38(await video.path());
|
|
215175
215489
|
if (data.byteLength > (options.maxBytes ?? DEFAULT_MAX_BYTES2)) {
|
|
215176
215490
|
return { status: "failed", reason: "size-cap-exceeded" };
|
|
215177
215491
|
}
|
|
@@ -215320,16 +215634,16 @@ async function persistBranchState(persistence, state) {
|
|
|
215320
215634
|
);
|
|
215321
215635
|
}
|
|
215322
215636
|
}
|
|
215323
|
-
function resolveJoinConfig(
|
|
215324
|
-
const rawMode =
|
|
215637
|
+
function resolveJoinConfig(join109) {
|
|
215638
|
+
const rawMode = join109.config?.mode;
|
|
215325
215639
|
let mode = "all";
|
|
215326
215640
|
if (rawMode === "all" || rawMode === "any") mode = rawMode;
|
|
215327
215641
|
else if (rawMode && typeof rawMode === "object" && "quorum" in rawMode) {
|
|
215328
215642
|
const n = rawMode.quorum;
|
|
215329
215643
|
if (typeof n === "number" && Number.isInteger(n) && n > 0) mode = { quorum: n };
|
|
215330
|
-
else throw new WorkflowIrError(`join '${
|
|
215644
|
+
else throw new WorkflowIrError(`join '${join109.id}' quorum must be a positive integer`);
|
|
215331
215645
|
}
|
|
215332
|
-
const rawFail =
|
|
215646
|
+
const rawFail = join109.config?.onBranchFailure;
|
|
215333
215647
|
const onBranchFailure = rawFail === "collect" ? "collect" : "fail-fast";
|
|
215334
215648
|
return { mode, onBranchFailure };
|
|
215335
215649
|
}
|
|
@@ -215345,9 +215659,9 @@ async function runSplitJoin(split, env) {
|
|
|
215345
215659
|
if (branchEdges.length === 0) {
|
|
215346
215660
|
throw new WorkflowIrError(`split '${split.id}' has no traversable branches`);
|
|
215347
215661
|
}
|
|
215348
|
-
const
|
|
215349
|
-
if (!
|
|
215350
|
-
const joinConfig = resolveJoinConfig(env.nodeMap.get(
|
|
215662
|
+
const join109 = findMatchingJoin(branchEdges[0].to, env);
|
|
215663
|
+
if (!join109) throw new WorkflowIrError(`split '${split.id}' has no reachable matching join`);
|
|
215664
|
+
const joinConfig = resolveJoinConfig(env.nodeMap.get(join109));
|
|
215351
215665
|
const controller = new AbortController();
|
|
215352
215666
|
const branchCount = branchEdges.length;
|
|
215353
215667
|
const required2 = requiredCompletions(joinConfig.mode, branchCount);
|
|
@@ -215383,7 +215697,7 @@ async function runSplitJoin(split, env) {
|
|
|
215383
215697
|
};
|
|
215384
215698
|
const branchPromises = branchEdges.map((edge) => {
|
|
215385
215699
|
const branchId = edge.to;
|
|
215386
|
-
return walkBranch(branchId,
|
|
215700
|
+
return walkBranch(branchId, join109, env, controller.signal, visitedNodeIds).then((result2) => {
|
|
215387
215701
|
branchOutcomes.push({ branchId, outcome: result2.outcome, nodeId: result2.lastNodeId });
|
|
215388
215702
|
if (result2.outcome === "success") succeeded += 1;
|
|
215389
215703
|
else failed += 1;
|
|
@@ -215401,7 +215715,7 @@ async function runSplitJoin(split, env) {
|
|
|
215401
215715
|
});
|
|
215402
215716
|
const outcome = await joinReached;
|
|
215403
215717
|
await Promise.allSettled(branchPromises);
|
|
215404
|
-
return { joinNodeId:
|
|
215718
|
+
return { joinNodeId: join109, outcome, branchOutcomes, visitedNodeIds };
|
|
215405
215719
|
}
|
|
215406
215720
|
async function walkBranch(startNodeId, joinId, env, signal, visitedNodeIds) {
|
|
215407
215721
|
let currentId = startNodeId;
|
|
@@ -216441,12 +216755,13 @@ function recoverPassedPlanReviewFromLatestLog(task) {
|
|
|
216441
216755
|
completedAt: latest.timestamp
|
|
216442
216756
|
};
|
|
216443
216757
|
}
|
|
216444
|
-
var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE, PLAN_REVIEW_LEASE_HELD_VALUE, WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY, WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY, WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
|
|
216758
|
+
var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE, PLAN_REVIEW_LEASE_HELD_VALUE, SESSION_CONTENTION_HOLD_VALUE, WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY, WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY, WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
|
|
216445
216759
|
var init_workflow_graph_executor = __esm({
|
|
216446
216760
|
"../engine/src/workflow-graph-executor.ts"() {
|
|
216447
216761
|
"use strict";
|
|
216448
216762
|
init_src2();
|
|
216449
216763
|
init_transient_error_detector();
|
|
216764
|
+
init_transient_error_patterns();
|
|
216450
216765
|
init_required_workflow_artifacts();
|
|
216451
216766
|
init_workflow_node_handlers();
|
|
216452
216767
|
init_workflow_graph_branches();
|
|
@@ -216455,6 +216770,7 @@ var init_workflow_graph_executor = __esm({
|
|
|
216455
216770
|
init_workflow_node_execution_needs();
|
|
216456
216771
|
PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE = "plan-review-provider-failure-hold";
|
|
216457
216772
|
PLAN_REVIEW_LEASE_HELD_VALUE = "plan-review-lease-held";
|
|
216773
|
+
SESSION_CONTENTION_HOLD_VALUE = "session-contention-hold";
|
|
216458
216774
|
WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY = "workflow:interruptedNodeId";
|
|
216459
216775
|
WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY = "workflow:interruptedNodeAbortKind";
|
|
216460
216776
|
WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY = "workflow:optionalGroupActive";
|
|
@@ -216774,6 +217090,15 @@ var init_workflow_graph_executor = __esm({
|
|
|
216774
217090
|
};
|
|
216775
217091
|
context[`node:${node.id}:outcome`] = result3.outcome;
|
|
216776
217092
|
if (result3.value !== void 0) context[`node:${node.id}:value`] = result3.value;
|
|
217093
|
+
const sessionContentionFailure = stepStatus === "failed" && (verdictRaw === SESSION_CONTENTION_HOLD_VALUE || isSessionContentionError(stepOutput ?? stepNotes ?? ""));
|
|
217094
|
+
if (sessionContentionFailure) {
|
|
217095
|
+
this.deps.logTaskEntry?.(
|
|
217096
|
+
`${logPrefix} ${groupName} is waiting on another task to release its session path \u2014 holding in place`
|
|
217097
|
+
);
|
|
217098
|
+
context[`node:${node.id}:outcome`] = "failure";
|
|
217099
|
+
context[`node:${node.id}:value`] = SESSION_CONTENTION_HOLD_VALUE;
|
|
217100
|
+
return { outcome: "failure", value: SESSION_CONTENTION_HOLD_VALUE };
|
|
217101
|
+
}
|
|
216777
217102
|
const nonPlanDefectPlanReviewFailure = node.id === PLAN_REVIEW_GROUP_ID && stepStatus === "failed" && (isRequiredArtifactReadFailedValue(verdictRaw) || isNonPlanDefectPlanReviewFailure({
|
|
216778
217103
|
verdict,
|
|
216779
217104
|
errorMessage: stepOutput ?? stepNotes,
|
|
@@ -217157,6 +217482,7 @@ var init_workflow_graph_executor = __esm({
|
|
|
217157
217482
|
} catch (error3) {
|
|
217158
217483
|
if (signal?.aborted) return this.withEnginePauseAbortContext(node, { outcome: "failure", value: "aborted" });
|
|
217159
217484
|
lastError = error3;
|
|
217485
|
+
if (isSessionContentionError(error3 instanceof Error ? error3.message : String(error3))) break;
|
|
217160
217486
|
}
|
|
217161
217487
|
}
|
|
217162
217488
|
if (signal?.aborted) {
|
|
@@ -217175,11 +217501,13 @@ var init_workflow_graph_executor = __esm({
|
|
|
217175
217501
|
}
|
|
217176
217502
|
return degraded;
|
|
217177
217503
|
}
|
|
217504
|
+
const lastErrorText = lastError instanceof Error ? lastError.message : String(lastError);
|
|
217178
217505
|
const failureResult = {
|
|
217179
217506
|
outcome: "failure",
|
|
217180
|
-
|
|
217507
|
+
// FNXC:SessionContention 2026-07-25-21:30: contention is a retryable hold, not an exception.
|
|
217508
|
+
value: isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
|
|
217181
217509
|
contextPatch: {
|
|
217182
|
-
[`node:${node.id}:error`]:
|
|
217510
|
+
[`node:${node.id}:error`]: lastErrorText
|
|
217183
217511
|
}
|
|
217184
217512
|
};
|
|
217185
217513
|
if (recordProgress && this.shouldRecordNodeProgress(node)) {
|
|
@@ -218803,7 +219131,7 @@ var init_workspace_paths = __esm({
|
|
|
218803
219131
|
|
|
218804
219132
|
// ../engine/src/cli-agent/hook-scripts.ts
|
|
218805
219133
|
import { mkdir as mkdir28, rm as rm11, writeFile as writeFile29 } from "node:fs/promises";
|
|
218806
|
-
import { join as
|
|
219134
|
+
import { join as join77 } from "node:path";
|
|
218807
219135
|
function shellSingleQuote(value) {
|
|
218808
219136
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
218809
219137
|
}
|
|
@@ -218875,8 +219203,8 @@ exit 0
|
|
|
218875
219203
|
async function writeSessionHookScripts(opts) {
|
|
218876
219204
|
const { sessionId, token, endpointUrl, dir } = opts;
|
|
218877
219205
|
await mkdir28(dir, { recursive: true, mode: 448 });
|
|
218878
|
-
const hookScriptPath =
|
|
218879
|
-
const notifyScriptPath =
|
|
219206
|
+
const hookScriptPath = join77(dir, HOOK_SCRIPT_NAMES.hook);
|
|
219207
|
+
const notifyScriptPath = join77(dir, HOOK_SCRIPT_NAMES.notify);
|
|
218880
219208
|
await writeFile29(hookScriptPath, buildHookScriptContent({ sessionId, token, endpointUrl }), {
|
|
218881
219209
|
mode: 448
|
|
218882
219210
|
});
|
|
@@ -219035,7 +219363,7 @@ var init_autonomy = __esm({
|
|
|
219035
219363
|
// ../engine/src/cli-agent/task-session.ts
|
|
219036
219364
|
import { mkdtemp as mkdtemp3, rm as rm12 } from "node:fs/promises";
|
|
219037
219365
|
import { tmpdir as tmpdir9 } from "node:os";
|
|
219038
|
-
import { join as
|
|
219366
|
+
import { join as join78 } from "node:path";
|
|
219039
219367
|
function launchCliTaskSession(opts) {
|
|
219040
219368
|
return CliTaskSession.launch(opts);
|
|
219041
219369
|
}
|
|
@@ -219101,9 +219429,9 @@ var init_task_session = __esm({
|
|
|
219101
219429
|
isApproved: opts.isAutonomyApproved ?? (() => false)
|
|
219102
219430
|
});
|
|
219103
219431
|
const root = opts.hookDirRoot ?? tmpdir9();
|
|
219104
|
-
const hookDir = await mkdtemp3(
|
|
219105
|
-
const hookScriptPath =
|
|
219106
|
-
const settingsPath =
|
|
219432
|
+
const hookDir = await mkdtemp3(join78(root, "fusion-cli-hooks-"));
|
|
219433
|
+
const hookScriptPath = join78(hookDir, HOOK_SCRIPT_NAMES.hook);
|
|
219434
|
+
const settingsPath = join78(hookDir, "settings.json");
|
|
219107
219435
|
const cliAgentSettings = opts.config.cliAgentSettings ?? null;
|
|
219108
219436
|
const operatorEnvAdditions = (cliAgentSettings?.envAdditions ?? []).filter(
|
|
219109
219437
|
(k2) => !/^FUSION_/i.test(k2)
|
|
@@ -219345,7 +219673,7 @@ var init_task_session = __esm({
|
|
|
219345
219673
|
// ../engine/src/pty-native.ts
|
|
219346
219674
|
import * as fs7 from "node:fs";
|
|
219347
219675
|
import { createRequire as createRequire7 } from "node:module";
|
|
219348
|
-
import { join as
|
|
219676
|
+
import { join as join79, dirname as dirname33 } from "node:path";
|
|
219349
219677
|
function getNativePrebuildName() {
|
|
219350
219678
|
const platform4 = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "win32" : "unknown";
|
|
219351
219679
|
const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : "unknown";
|
|
@@ -219355,12 +219683,12 @@ function findInstalledNodePtyNativeDir() {
|
|
|
219355
219683
|
try {
|
|
219356
219684
|
const packageJsonPath = require4.resolve("node-pty/package.json");
|
|
219357
219685
|
const pkgRoot = dirname33(packageJsonPath);
|
|
219358
|
-
const releaseDir =
|
|
219359
|
-
if (fs7.existsSync(
|
|
219686
|
+
const releaseDir = join79(pkgRoot, "build", "Release");
|
|
219687
|
+
if (fs7.existsSync(join79(releaseDir, "pty.node"))) {
|
|
219360
219688
|
return releaseDir;
|
|
219361
219689
|
}
|
|
219362
|
-
const prebuildDir =
|
|
219363
|
-
if (fs7.existsSync(
|
|
219690
|
+
const prebuildDir = join79(pkgRoot, "prebuilds", getNativePrebuildName());
|
|
219691
|
+
if (fs7.existsSync(join79(prebuildDir, "pty.node"))) {
|
|
219364
219692
|
return prebuildDir;
|
|
219365
219693
|
}
|
|
219366
219694
|
return null;
|
|
@@ -219371,14 +219699,14 @@ function findInstalledNodePtyNativeDir() {
|
|
|
219371
219699
|
function findStagedNativeDir() {
|
|
219372
219700
|
const prebuildName = getNativePrebuildName();
|
|
219373
219701
|
if (process.env.FUSION_RUNTIME_DIR) {
|
|
219374
|
-
const envPath =
|
|
219375
|
-
if (fs7.existsSync(
|
|
219702
|
+
const envPath = join79(process.env.FUSION_RUNTIME_DIR, prebuildName);
|
|
219703
|
+
if (fs7.existsSync(join79(envPath, "pty.node"))) {
|
|
219376
219704
|
return envPath;
|
|
219377
219705
|
}
|
|
219378
219706
|
}
|
|
219379
219707
|
const execDir = dirname33(process.execPath);
|
|
219380
|
-
const nextToBinary =
|
|
219381
|
-
if (fs7.existsSync(
|
|
219708
|
+
const nextToBinary = join79(execDir, "runtime", prebuildName);
|
|
219709
|
+
if (fs7.existsSync(join79(nextToBinary, "pty.node"))) {
|
|
219382
219710
|
return nextToBinary;
|
|
219383
219711
|
}
|
|
219384
219712
|
return null;
|
|
@@ -219401,8 +219729,8 @@ function ensureNodePtyNativePermissions() {
|
|
|
219401
219729
|
candidateDirs.add(installedNativeDir);
|
|
219402
219730
|
}
|
|
219403
219731
|
for (const nativeDir of candidateDirs) {
|
|
219404
|
-
const helperPath =
|
|
219405
|
-
const nativeModulePath =
|
|
219732
|
+
const helperPath = join79(nativeDir, "spawn-helper");
|
|
219733
|
+
const nativeModulePath = join79(nativeDir, "pty.node");
|
|
219406
219734
|
try {
|
|
219407
219735
|
fs7.chmodSync(helperPath, 493);
|
|
219408
219736
|
} catch {
|
|
@@ -219432,7 +219760,7 @@ async function loadPtyModule() {
|
|
|
219432
219760
|
process.env.NODE_PTY_SPAWN_HELPER_DIR = nativeDir;
|
|
219433
219761
|
}
|
|
219434
219762
|
process.env.FUSION_NATIVE_ASSETS_PATH = nativeDir;
|
|
219435
|
-
const nativePath =
|
|
219763
|
+
const nativePath = join79(nativeDir, "pty.node");
|
|
219436
219764
|
if (fs7.existsSync(nativePath)) {
|
|
219437
219765
|
try {
|
|
219438
219766
|
const nativeModule = { exports: {} };
|
|
@@ -220971,7 +221299,7 @@ var init_file_scope2 = __esm({
|
|
|
220971
221299
|
|
|
220972
221300
|
// ../engine/src/run-verification-tool.ts
|
|
220973
221301
|
import { existsSync as existsSync64, readdirSync as readdirSync11, readFileSync as readFileSync23 } from "node:fs";
|
|
220974
|
-
import { isAbsolute as isAbsolute16, join as
|
|
221302
|
+
import { isAbsolute as isAbsolute16, join as join80, relative as relative14 } from "node:path";
|
|
220975
221303
|
import { Type as Type6 } from "@earendil-works/pi-ai";
|
|
220976
221304
|
function shellSplit(input) {
|
|
220977
221305
|
const tokens = [];
|
|
@@ -221086,7 +221414,7 @@ function findWorkspacePackageDir(rootDir, packageName) {
|
|
|
221086
221414
|
for (const candidate of candidates) {
|
|
221087
221415
|
if (!candidate) continue;
|
|
221088
221416
|
try {
|
|
221089
|
-
const pkg = JSON.parse(readFileSync23(
|
|
221417
|
+
const pkg = JSON.parse(readFileSync23(join80(rootDir, candidate, "package.json"), "utf8"));
|
|
221090
221418
|
if (pkg.name === packageName) {
|
|
221091
221419
|
packageDirCache.set(cacheKey, candidate);
|
|
221092
221420
|
return candidate;
|
|
@@ -221101,7 +221429,7 @@ function findWorkspacePackageDir(rootDir, packageName) {
|
|
|
221101
221429
|
while (queue.length > 0) {
|
|
221102
221430
|
const current = queue.shift();
|
|
221103
221431
|
if (!current) break;
|
|
221104
|
-
const abs =
|
|
221432
|
+
const abs = join80(rootDir, current.dir);
|
|
221105
221433
|
let entries;
|
|
221106
221434
|
try {
|
|
221107
221435
|
entries = readdirSync11(abs, { withFileTypes: true });
|
|
@@ -221111,9 +221439,9 @@ function findWorkspacePackageDir(rootDir, packageName) {
|
|
|
221111
221439
|
for (const entry of entries) {
|
|
221112
221440
|
if (!entry.isDirectory()) continue;
|
|
221113
221441
|
if (entry.name === "node_modules" || entry.name === "dist") continue;
|
|
221114
|
-
const child =
|
|
221442
|
+
const child = join80(current.dir, entry.name);
|
|
221115
221443
|
try {
|
|
221116
|
-
const pkg = JSON.parse(readFileSync23(
|
|
221444
|
+
const pkg = JSON.parse(readFileSync23(join80(rootDir, child, "package.json"), "utf8"));
|
|
221117
221445
|
if (pkg.name === packageName) {
|
|
221118
221446
|
packageDirCache.set(cacheKey, child);
|
|
221119
221447
|
return child;
|
|
@@ -221133,7 +221461,7 @@ function toPackageRelativeFilter(token, rootDir, packageDir) {
|
|
|
221133
221461
|
return normalized.slice(normalizedPackageDir.length + 1);
|
|
221134
221462
|
}
|
|
221135
221463
|
if (isAbsolute16(token)) {
|
|
221136
|
-
const rel = relative14(
|
|
221464
|
+
const rel = relative14(join80(rootDir, packageDir), token).replace(/\\/g, "/");
|
|
221137
221465
|
if (!rel.startsWith("../") && rel !== "..") return rel;
|
|
221138
221466
|
}
|
|
221139
221467
|
return token;
|
|
@@ -221390,7 +221718,7 @@ function createRunVerificationTool(opts) {
|
|
|
221390
221718
|
if (params.cwd && isAbsolute16(params.cwd)) {
|
|
221391
221719
|
resolvedCwd = params.cwd;
|
|
221392
221720
|
} else if (params.cwd) {
|
|
221393
|
-
resolvedCwd =
|
|
221721
|
+
resolvedCwd = join80(worktreePath, params.cwd);
|
|
221394
221722
|
} else {
|
|
221395
221723
|
resolvedCwd = worktreePath;
|
|
221396
221724
|
}
|
|
@@ -221423,7 +221751,7 @@ function createRunVerificationTool(opts) {
|
|
|
221423
221751
|
warnings.push(...normalized.warnings);
|
|
221424
221752
|
}
|
|
221425
221753
|
if (effectiveCommand.trimStart().startsWith("pnpm --filter")) {
|
|
221426
|
-
const modulesYaml =
|
|
221754
|
+
const modulesYaml = join80(rootDir, "node_modules", ".modules.yaml");
|
|
221427
221755
|
if (!existsSync64(modulesYaml)) {
|
|
221428
221756
|
const installCmd = "pnpm install --prefer-offline";
|
|
221429
221757
|
const msg = `node_modules/.modules.yaml not found in workspace root \u2014 auto-prepending \`${installCmd}\` before running the command.`;
|
|
@@ -221768,9 +222096,9 @@ var init_pseudo_pause = __esm({
|
|
|
221768
222096
|
import { exec as exec38, execFile as execFile26, execSync as execSync7 } from "node:child_process";
|
|
221769
222097
|
import { promisify as promisify53 } from "node:util";
|
|
221770
222098
|
import { setImmediate as setImmediateCb3 } from "node:timers";
|
|
221771
|
-
import { basename as basename19, delimiter as delimiter2, isAbsolute as isAbsolute18, join as
|
|
222099
|
+
import { basename as basename19, delimiter as delimiter2, isAbsolute as isAbsolute18, join as join81, relative as relative16, resolve as resolvePath3 } from "node:path";
|
|
221772
222100
|
import { existsSync as existsSync65, lstatSync as lstatSync3, realpathSync as realpathSync9 } from "node:fs";
|
|
221773
|
-
import { readFile as
|
|
222101
|
+
import { readFile as readFile39, rm as rm13, writeFile as writeFile30 } from "node:fs/promises";
|
|
221774
222102
|
import { Type as Type7 } from "@earendil-works/pi-ai";
|
|
221775
222103
|
import { SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
|
|
221776
222104
|
function mergeAdditionalSkillPaths2(...pathGroups) {
|
|
@@ -221784,7 +222112,7 @@ function isWorkflowStepSkillDiscoverable(skillName, additionalSkillPaths, ceSkil
|
|
|
221784
222112
|
return false;
|
|
221785
222113
|
}
|
|
221786
222114
|
return (additionalSkillPaths ?? []).some(
|
|
221787
|
-
(skillPath) => basename19(skillPath) === bareSkillName && existsSync65(
|
|
222115
|
+
(skillPath) => basename19(skillPath) === bareSkillName && existsSync65(join81(skillPath, "SKILL.md")) || existsSync65(join81(skillPath, bareSkillName, "SKILL.md"))
|
|
221788
222116
|
);
|
|
221789
222117
|
}
|
|
221790
222118
|
function getPromptSection(prompt, heading) {
|
|
@@ -222093,7 +222421,7 @@ function normalizeWorktreePath(pathValue) {
|
|
|
222093
222421
|
}
|
|
222094
222422
|
async function extractPersistedSessionWorktreePath(sessionFile, rootDir, settings) {
|
|
222095
222423
|
try {
|
|
222096
|
-
const content = await
|
|
222424
|
+
const content = await readFile39(sessionFile, "utf-8");
|
|
222097
222425
|
const matches = content.match(buildSessionWorktreePathRegex(rootDir, settings)) ?? [];
|
|
222098
222426
|
if (matches.length === 0) return null;
|
|
222099
222427
|
const normalizedCounts = /* @__PURE__ */ new Map();
|
|
@@ -222507,7 +222835,7 @@ function preservePreExecutionWorkflowStepResults(task) {
|
|
|
222507
222835
|
}
|
|
222508
222836
|
];
|
|
222509
222837
|
}
|
|
222510
|
-
var execAsync35, execFileAsync19, WORKFLOW_THINKING_LEVEL_SET, yieldEventLoop3, tokenCacheMetricsLog, OPTIONAL_STEP_REVISION_KEY_MARKER2, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, MAX_TRANSIENT_GRAPH_RESUME_RETRIES, TRANSIENT_GRAPH_RESUME_RETRY_BACKOFF_MS, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, LOOP_COMPACTION_TIMEOUT_MS, TASK_DONE_REFUSAL_SUFFIX, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, USER_QUESTION_TOOL_NAMES, FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
222838
|
+
var execAsync35, execFileAsync19, WORKFLOW_THINKING_LEVEL_SET, yieldEventLoop3, tokenCacheMetricsLog, OPTIONAL_STEP_REVISION_KEY_MARKER2, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, MAX_TRANSIENT_GRAPH_RESUME_RETRIES, TRANSIENT_GRAPH_RESUME_RETRY_BACKOFF_MS, MAX_SESSION_CONTENTION_HOLD_RETRIES, SESSION_CONTENTION_HOLD_BACKOFF_MS, SESSION_CONTENTION_HOLD_MAX_BACKOFF_MS, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, LOOP_COMPACTION_TIMEOUT_MS, TASK_DONE_REFUSAL_SUFFIX, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, USER_QUESTION_TOOL_NAMES, FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
222511
222839
|
var init_executor = __esm({
|
|
222512
222840
|
"../engine/src/executor.ts"() {
|
|
222513
222841
|
"use strict";
|
|
@@ -222620,6 +222948,9 @@ var init_executor = __esm({
|
|
|
222620
222948
|
MAX_TASK_DONE_REQUEUE_RETRIES = 3;
|
|
222621
222949
|
MAX_TRANSIENT_GRAPH_RESUME_RETRIES = 2;
|
|
222622
222950
|
TRANSIENT_GRAPH_RESUME_RETRY_BACKOFF_MS = process.env.VITEST || process.env.NODE_ENV === "test" ? 0 : 1e3;
|
|
222951
|
+
MAX_SESSION_CONTENTION_HOLD_RETRIES = 10;
|
|
222952
|
+
SESSION_CONTENTION_HOLD_BACKOFF_MS = process.env.VITEST || process.env.NODE_ENV === "test" ? 0 : 5e3;
|
|
222953
|
+
SESSION_CONTENTION_HOLD_MAX_BACKOFF_MS = 6e4;
|
|
222623
222954
|
COMPLETED_TASK_WATCHDOG_MS = 6e4;
|
|
222624
222955
|
WORKFLOW_RERUN_WATCHDOG_MS = 15e3;
|
|
222625
222956
|
LOOP_COMPACTION_TIMEOUT_MS = 6e4;
|
|
@@ -222980,6 +223311,15 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
222980
223311
|
}
|
|
222981
223312
|
})
|
|
222982
223313
|
);
|
|
223314
|
+
} else if ((from === "todo" || from === "triage") && to2 !== "in-progress" && to2 !== "in-review" && to2 !== "done") {
|
|
223315
|
+
this.trackTaskDisposal(
|
|
223316
|
+
task.id,
|
|
223317
|
+
this.awaitAbortInFlightTaskWork(task.id, `task moved out of planning to ${to2}`, {
|
|
223318
|
+
userCanceled: source === "user"
|
|
223319
|
+
}).then(async () => {
|
|
223320
|
+
await this.releasePreExecutionWorktree(task.id, `moved to ${to2}`);
|
|
223321
|
+
})
|
|
223322
|
+
);
|
|
222983
223323
|
} else if (from === "in-progress") {
|
|
222984
223324
|
if (this.workflowLifecycleMovesInFlight.has(task.id) && this.graphRouting.has(task.id)) {
|
|
222985
223325
|
executorLog.log(
|
|
@@ -223569,15 +223909,63 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
223569
223909
|
getActiveWorktreePaths() consumers that cd into a path are unaffected; only the registry key changes.
|
|
223570
223910
|
Non-workspace tasks (unique worktree path != rootDir) are returned unchanged.
|
|
223571
223911
|
*/
|
|
223912
|
+
/*
|
|
223913
|
+
FNXC:PlanReviewWorktree 2026-07-25-20:40 (concurrent root-rooted step sessions — single-repo collision):
|
|
223914
|
+
The task-scoped key must apply to the shared repo root in EVERY project mode, not only workspace mode.
|
|
223915
|
+
Read-only graph nodes that need no worktree (Plan Review is the canonical one — it reviews the
|
|
223916
|
+
store-injected PROMPT.md, see FNXC:PlanReviewSpecInjection) run rooted at `this.rootDir`, and a todo
|
|
223917
|
+
task has no worktree of its own. With the bare root as the registry key, two tasks reaching Plan Review
|
|
223918
|
+
at the same time collided: the second failed with "active-session path <root> is held by task <other>;
|
|
223919
|
+
task <self> may not overwrite it", which surfaced as a Plan Review provider failure, burned the
|
|
223920
|
+
in-place retry budget against a hold that retrying can never clear, and left the task parked
|
|
223921
|
+
(reported: FN-1398 holding /home/ubuntu/dev/freemap-svelte while FN-1403 planned).
|
|
223922
|
+
Path-exclusivity on the shared root is not what keeps these sessions correct: write-capable nodes are
|
|
223923
|
+
refused at the root outright (no-worktree-for-write-node above), real per-sub-repo exclusivity is the
|
|
223924
|
+
workspace-repo-acquire lease, and every isPathActive consumer guards removable WORKTREE paths — the
|
|
223925
|
+
root is never one. Liveness still works because the synthetic key stays in the registry under the task.
|
|
223926
|
+
*/
|
|
223572
223927
|
sessionRegistryPath(taskId, worktreePath) {
|
|
223573
|
-
if (
|
|
223928
|
+
if (worktreePath === this.rootDir) {
|
|
223574
223929
|
return `${worktreePath}#session:${taskId}`;
|
|
223575
223930
|
}
|
|
223576
223931
|
return worktreePath;
|
|
223577
223932
|
}
|
|
223933
|
+
/*
|
|
223934
|
+
FNXC:SessionContention 2026-07-25-21:30 (contention prevention at the registration seam):
|
|
223935
|
+
Every executor session registration goes through `acquireActiveSessionPath` instead of the raw
|
|
223936
|
+
`registerPath`, so a LEAKED entry owned by a task with no live session surface in this process is
|
|
223937
|
+
RECLAIMED rather than throwing at the newcomer. That closes the second contention class (a dead
|
|
223938
|
+
holder can never release, so waiting on it is waiting forever). A genuinely live holder still throws
|
|
223939
|
+
the typed error — that case is real serialization, and callers classify it as a retryable contention
|
|
223940
|
+
hold (SESSION_CONTENTION_HOLD_VALUE), never as a provider/model failure.
|
|
223941
|
+
The probe reports LIVE on any uncertainty: an unknown holder with a fresh entry is treated as live by
|
|
223942
|
+
the staleness floor, so the reclaim only ever fires on proven-dead, aged entries.
|
|
223943
|
+
*/
|
|
223944
|
+
acquireSessionRegistryPath(taskId, registryPath, kind, ownerKey) {
|
|
223945
|
+
const outcome = acquireActiveSessionPath(activeSessionRegistry, registryPath, { taskId, kind, ownerKey }, {
|
|
223946
|
+
holderLiveProbe: (holderTaskId) => this.hasLiveTaskSessionSurface(holderTaskId) || executingTaskLock.has(holderTaskId)
|
|
223947
|
+
});
|
|
223948
|
+
if (outcome.action === "contended") {
|
|
223949
|
+
throw new ActiveSessionPathHeldByForeignTaskError(registryPath, outcome.holderTaskId, taskId);
|
|
223950
|
+
}
|
|
223951
|
+
if (outcome.action === "reclaimed-stale-foreign") {
|
|
223952
|
+
executorLog.warn(
|
|
223953
|
+
`${taskId}: reclaimed a stale active-session entry on ${registryPath} from dead task ${outcome.holderTaskId} (idle ${outcome.ageMs}ms)`
|
|
223954
|
+
);
|
|
223955
|
+
void this.store.recordRunAuditEvent?.({
|
|
223956
|
+
taskId,
|
|
223957
|
+
agentId: "executor",
|
|
223958
|
+
runId: generateSyntheticRunId("session-path-reclaim", taskId),
|
|
223959
|
+
domain: "database",
|
|
223960
|
+
mutationType: "session:reclaim-stale-foreign-path",
|
|
223961
|
+
target: taskId,
|
|
223962
|
+
metadata: { taskId, holderTaskId: outcome.holderTaskId, kind, ageMs: outcome.ageMs }
|
|
223963
|
+
})?.catch?.(() => void 0);
|
|
223964
|
+
}
|
|
223965
|
+
}
|
|
223578
223966
|
setActiveSession(taskId, sessionState, worktreePath) {
|
|
223579
223967
|
this.activeSessions.set(taskId, sessionState);
|
|
223580
|
-
|
|
223968
|
+
this.acquireSessionRegistryPath(taskId, this.sessionRegistryPath(taskId, worktreePath), "executor", taskId);
|
|
223581
223969
|
}
|
|
223582
223970
|
markGraphExecuteSelfRequeued(taskId) {
|
|
223583
223971
|
if (this.graphRouting.has(taskId)) {
|
|
@@ -223595,7 +223983,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
223595
223983
|
setActiveStepExecutor(taskId, stepExecutor, worktreePath, seenSteeringIds = /* @__PURE__ */ new Set()) {
|
|
223596
223984
|
this.activeStepExecutors.set(taskId, stepExecutor);
|
|
223597
223985
|
this.activeStepExecutorSeenSteeringIds.set(taskId, seenSteeringIds);
|
|
223598
|
-
|
|
223986
|
+
this.acquireSessionRegistryPath(taskId, this.sessionRegistryPath(taskId, worktreePath), "step-session", `${taskId}#step-session`);
|
|
223599
223987
|
}
|
|
223600
223988
|
deleteActiveStepExecutor(taskId, worktreePath) {
|
|
223601
223989
|
this.activeStepExecutors.delete(taskId);
|
|
@@ -223609,7 +223997,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
223609
223997
|
setActiveWorkflowStepSession(taskId, session, worktreePath, seenSteeringIds = /* @__PURE__ */ new Set()) {
|
|
223610
223998
|
this.activeWorkflowStepSessions.set(taskId, session);
|
|
223611
223999
|
this.activeWorkflowStepSessionSeenSteeringIds.set(taskId, seenSteeringIds);
|
|
223612
|
-
|
|
224000
|
+
this.acquireSessionRegistryPath(taskId, this.sessionRegistryPath(taskId, worktreePath), "workflow-step", `${taskId}#workflow-step`);
|
|
223613
224001
|
}
|
|
223614
224002
|
deleteActiveWorkflowStepSession(taskId, worktreePath) {
|
|
223615
224003
|
this.activeWorkflowStepSessions.delete(taskId);
|
|
@@ -225481,7 +225869,7 @@ ${info.feedback}`, revisionKey),
|
|
|
225481
225869
|
const authoritativeAgentLayer = this.store.getAsyncLayer();
|
|
225482
225870
|
if (!this.authoritativeAssignedAgentStore || authoritativeAgentLayer && !this.authoritativeAssignedAgentStore.backendMode) {
|
|
225483
225871
|
this.authoritativeAssignedAgentStore = new AgentStore({
|
|
225484
|
-
rootDir:
|
|
225872
|
+
rootDir: join81(this.rootDir, ".fusion"),
|
|
225485
225873
|
taskStore: this.store,
|
|
225486
225874
|
...authoritativeAgentLayer ? { asyncLayer: authoritativeAgentLayer } : {}
|
|
225487
225875
|
});
|
|
@@ -227928,6 +228316,87 @@ ${info.feedback}`, revisionKey),
|
|
|
227928
228316
|
this.unregisterConfiguredCommandController(task.id, commandAbortController);
|
|
227929
228317
|
}
|
|
227930
228318
|
}
|
|
228319
|
+
/*
|
|
228320
|
+
FNXC:NodeWorktreeIsolation 2026-07-25-22:10 (planning acquires the task worktree):
|
|
228321
|
+
Public seam for the planning/triage lane. Specification runs a CODING-tool session; pointing it at
|
|
228322
|
+
the shared main checkout meant every planning agent had write tools in the operator's tree and every
|
|
228323
|
+
concurrent planner shared one path. Acquire the task's own worktree up front and let the whole
|
|
228324
|
+
lifecycle — planning, Plan Review, implementation, code review — reuse that single worktree.
|
|
228325
|
+
Returns null (caller falls back to the root, unchanged behavior) when the project is a workspace, or
|
|
228326
|
+
when acquisition fails: planning must never be blocked by a worktree problem.
|
|
228327
|
+
*/
|
|
228328
|
+
/*
|
|
228329
|
+
FNXC:PlanningEvacuation 2026-07-25-23:00 (pre-execution worktree release):
|
|
228330
|
+
Planning now acquires a worktree, so a card that never reaches execution — withdrawn to Ideas,
|
|
228331
|
+
archived from a planner lane, or parked pre-execution — would otherwise hold one forever. Release
|
|
228332
|
+
it. Safety conditions, all required:
|
|
228333
|
+
- the task never executed (`firstExecutionAt`/`executionStartedAt` unset): execution evidence means
|
|
228334
|
+
the worktree may hold real work, and only the normal merge/archive lifecycle may remove it;
|
|
228335
|
+
- no live session registered on the path (the same isPathActive guard the other sweeps use);
|
|
228336
|
+
- the branch carries no commits beyond its base — planning writes its spec to the task store, not
|
|
228337
|
+
the worktree, so a clean branch means there is genuinely nothing to lose.
|
|
228338
|
+
Metadata (`worktree`/`branch`) is cleared with it, so a later promotion re-acquires cleanly.
|
|
228339
|
+
Fail-soft throughout: a cleanup problem must never block the lifecycle move that triggered it.
|
|
228340
|
+
*/
|
|
228341
|
+
async releasePreExecutionWorktree(taskId, reason) {
|
|
228342
|
+
try {
|
|
228343
|
+
const live = await this.store.getTask(taskId);
|
|
228344
|
+
if (!live?.worktree) return false;
|
|
228345
|
+
if (live.firstExecutionAt || live.executionStartedAt) return false;
|
|
228346
|
+
if (activeSessionRegistry.isPathActive(live.worktree) || activeSessionRegistry.isPathActive(resolvePath3(live.worktree))) return false;
|
|
228347
|
+
if (this.hasLiveTaskSessionSurface(taskId) || executingTaskLock.has(taskId)) return false;
|
|
228348
|
+
if (existsSync65(live.worktree)) {
|
|
228349
|
+
if (await this.preExecutionWorktreeHasWork(live.worktree)) {
|
|
228350
|
+
executorLog.log(`${taskId}: keeping pre-execution worktree ${live.worktree} \u2014 it carries commits or uncommitted changes`);
|
|
228351
|
+
return false;
|
|
228352
|
+
}
|
|
228353
|
+
const settings = await this.store.getSettings();
|
|
228354
|
+
await removeWorktree({
|
|
228355
|
+
rootDir: this.rootDir,
|
|
228356
|
+
worktreePath: live.worktree,
|
|
228357
|
+
settings,
|
|
228358
|
+
taskId,
|
|
228359
|
+
reason: RemovalReason.SelfHealingReclaim
|
|
228360
|
+
});
|
|
228361
|
+
}
|
|
228362
|
+
this.activeWorktrees.get(taskId)?.delete(live.worktree);
|
|
228363
|
+
await this.store.updateTask(taskId, { worktree: null, branch: null, baseCommitSha: null, sessionFile: null }, this.getRunContextFor(taskId));
|
|
228364
|
+
await this.store.logEntry(taskId, `Released the pre-execution worktree (${reason}) \u2014 it will be re-acquired when planning or execution resumes`, void 0, this.getRunContextFor(taskId)).catch(() => void 0);
|
|
228365
|
+
executorLog.log(`${taskId}: released pre-execution worktree ${live.worktree} (${reason})`);
|
|
228366
|
+
return true;
|
|
228367
|
+
} catch (error3) {
|
|
228368
|
+
executorLog.warn(`${taskId}: could not release the pre-execution worktree: ${formatError(error3).message}`);
|
|
228369
|
+
return false;
|
|
228370
|
+
}
|
|
228371
|
+
}
|
|
228372
|
+
/** True when a pre-execution worktree holds commits past its base or any uncommitted change. */
|
|
228373
|
+
async preExecutionWorktreeHasWork(worktreePath) {
|
|
228374
|
+
try {
|
|
228375
|
+
const { stdout: dirty } = await execFileAsync19("git", ["status", "--porcelain"], { cwd: worktreePath, timeout: 3e4 });
|
|
228376
|
+
if (dirty.trim()) return true;
|
|
228377
|
+
const { stdout: ahead } = await execFileAsync19("git", ["log", "--oneline", "@{upstream}..HEAD"], { cwd: worktreePath, timeout: 3e4 }).catch(async () => await execFileAsync19("git", ["log", "--oneline", "-1", "HEAD", "--not", "--remotes", "--branches=main", "--branches=master"], { cwd: worktreePath, timeout: 3e4 }));
|
|
228378
|
+
return Boolean(ahead.trim());
|
|
228379
|
+
} catch {
|
|
228380
|
+
return true;
|
|
228381
|
+
}
|
|
228382
|
+
}
|
|
228383
|
+
async ensureTaskWorktreeForPlanning(taskId) {
|
|
228384
|
+
try {
|
|
228385
|
+
if (this.workspaceConfig === void 0) {
|
|
228386
|
+
this.workspaceConfig = await loadWorkspaceConfig(this.rootDir);
|
|
228387
|
+
}
|
|
228388
|
+
if (this.workspaceConfig && (this.workspaceConfig.repos.length ?? 0) > 0) return null;
|
|
228389
|
+
const live = await this.store.getTask(taskId);
|
|
228390
|
+
if (live.worktree && existsSync65(live.worktree)) return live.worktree;
|
|
228391
|
+
const settings = await this.store.getSettings();
|
|
228392
|
+
const acquisitionTask = live.worktree ? { ...live, worktree: void 0, sessionFile: void 0 } : live;
|
|
228393
|
+
const acquired = await this.ensureGraphCustomNodeWorktree(acquisitionTask, settings, "planning");
|
|
228394
|
+
return acquired.worktree || null;
|
|
228395
|
+
} catch (error3) {
|
|
228396
|
+
executorLog.warn(`${taskId}: could not acquire a planning worktree \u2014 planning falls back to the repo root: ${formatError(error3)}`);
|
|
228397
|
+
return null;
|
|
228398
|
+
}
|
|
228399
|
+
}
|
|
227931
228400
|
async prepareGraphNodeExecution(node, nodeTask, settings, requirement) {
|
|
227932
228401
|
if (!requirement.requiresWorktree) return;
|
|
227933
228402
|
const live = await this.store.getTask(nodeTask.id);
|
|
@@ -228054,22 +228523,29 @@ ${info.feedback}`, revisionKey),
|
|
|
228054
228523
|
optionalGroupId,
|
|
228055
228524
|
reviewerInlineFixes: settings.reviewerInlineFixes
|
|
228056
228525
|
});
|
|
228057
|
-
|
|
228058
|
-
if (writeCapable && !executionTarget.worktree && !this.workspaceConfig) {
|
|
228059
|
-
return { outcome: "failure", value: "no-worktree-for-write-node" };
|
|
228060
|
-
}
|
|
228526
|
+
let executionTarget = writeCapable ? await this.store.getTask(live.id) : live;
|
|
228061
228527
|
const nodeDisplayName = typeof cfg.name === "string" && cfg.name.trim() ? cfg.name.trim() : node.id;
|
|
228062
228528
|
const isPlanReviewNode = node.id === "plan-review-step" || nodeDisplayName === "Plan Review" || optionalGroupId === "plan-review";
|
|
228063
|
-
|
|
228064
|
-
|
|
228065
|
-
|
|
228066
|
-
|
|
228067
|
-
|
|
228068
|
-
|
|
228069
|
-
|
|
228070
|
-
|
|
228071
|
-
|
|
228529
|
+
if (!this.workspaceConfig) {
|
|
228530
|
+
const recordedWorktreeMissing = Boolean(executionTarget.worktree) && !existsSync65(executionTarget.worktree);
|
|
228531
|
+
const shouldAcquire = !executionTarget.worktree || recordedWorktreeMissing && isPlanReviewNode;
|
|
228532
|
+
if (shouldAcquire) {
|
|
228533
|
+
if (recordedWorktreeMissing) {
|
|
228534
|
+
await this.store.logEntry(
|
|
228535
|
+
live.id,
|
|
228536
|
+
`Plan Review worktree ${executionTarget.worktree} is missing on disk \u2014 re-acquiring a task worktree instead of running in the shared checkout`,
|
|
228537
|
+
void 0,
|
|
228538
|
+
this.getRunContextFor(live.id)
|
|
228539
|
+
);
|
|
228540
|
+
}
|
|
228541
|
+
const acquisitionTask = recordedWorktreeMissing ? { ...executionTarget, worktree: void 0, sessionFile: void 0 } : executionTarget;
|
|
228542
|
+
executionTarget = await this.ensureGraphCustomNodeWorktree(acquisitionTask, settings, node.id);
|
|
228543
|
+
}
|
|
228544
|
+
}
|
|
228545
|
+
if (writeCapable && !executionTarget.worktree && !this.workspaceConfig) {
|
|
228546
|
+
return { outcome: "failure", value: "no-worktree-for-write-node" };
|
|
228072
228547
|
}
|
|
228548
|
+
const worktreePath = executionTarget.worktree || this.rootDir;
|
|
228073
228549
|
let prompt = typeof cfg.prompt === "string" ? cfg.prompt : "";
|
|
228074
228550
|
let modelProvider = typeof cfg.modelProvider === "string" && cfg.modelProvider.trim() ? cfg.modelProvider : void 0;
|
|
228075
228551
|
let modelId = typeof cfg.modelId === "string" && cfg.modelId.trim() ? cfg.modelId : void 0;
|
|
@@ -228366,6 +228842,77 @@ ${prompt}`;
|
|
|
228366
228842
|
const latestAction = live.log.at(-1)?.action;
|
|
228367
228843
|
return latestAction === "Resumed after engine restart" || latestAction === "Resuming execution after unpause";
|
|
228368
228844
|
}
|
|
228845
|
+
/*
|
|
228846
|
+
FNXC:SessionContention 2026-07-25-21:30:
|
|
228847
|
+
Two ways in, because contention must never slip through to a park:
|
|
228848
|
+
- the typed failure value the graph now publishes (SESSION_CONTENTION_HOLD_VALUE), and
|
|
228849
|
+
- a message-shape fallback over the run's `:error` context patches, so contention arriving from a
|
|
228850
|
+
path that has not been taught the typed value is still recognized.
|
|
228851
|
+
*/
|
|
228852
|
+
graphFailureErrorTexts(result2) {
|
|
228853
|
+
if (!result2.context) return [];
|
|
228854
|
+
const texts = [];
|
|
228855
|
+
for (const [key, value] of Object.entries(result2.context)) {
|
|
228856
|
+
if (key.endsWith(":error") && typeof value === "string" && value.trim()) texts.push(value);
|
|
228857
|
+
}
|
|
228858
|
+
return texts;
|
|
228859
|
+
}
|
|
228860
|
+
isSessionContentionGraphFailure(result2) {
|
|
228861
|
+
if (this.graphFailureValue(result2) === SESSION_CONTENTION_HOLD_VALUE) return true;
|
|
228862
|
+
return this.graphFailureErrorTexts(result2).some((text2) => isSessionContentionError(text2));
|
|
228863
|
+
}
|
|
228864
|
+
/*
|
|
228865
|
+
FNXC:SessionContention 2026-07-25-21:30 (self-recovering wait — the task is never parked):
|
|
228866
|
+
Retry the graph in place on an exponential backoff while the holder finishes. The counter is
|
|
228867
|
+
IN-MEMORY on purpose: it needs no schema change, and an engine restart resetting it is the desired
|
|
228868
|
+
behavior (a restart also drops the in-process registry, so the contention is gone anyway).
|
|
228869
|
+
When the ladder is exhausted the task is left cleanly dispatchable — status/error cleared, progress
|
|
228870
|
+
untouched — so ordinary scheduling picks it up later with a fresh budget. There is no terminal branch
|
|
228871
|
+
here by design: lease contention always ends (the holder finishes, or self-healing sweeps it), so
|
|
228872
|
+
parking the task would only require a human to press Retry on a condition that fixed itself.
|
|
228873
|
+
*/
|
|
228874
|
+
sessionContentionHoldAttempts = /* @__PURE__ */ new Map();
|
|
228875
|
+
clearSessionContentionHold(taskId) {
|
|
228876
|
+
this.sessionContentionHoldAttempts.delete(taskId);
|
|
228877
|
+
}
|
|
228878
|
+
async holdForSessionContention(task, live, result2) {
|
|
228879
|
+
const detail = this.graphFailureErrorTexts(result2).find((text2) => isSessionContentionError(text2));
|
|
228880
|
+
const priorAttempts = this.sessionContentionHoldAttempts.get(task.id) ?? 0;
|
|
228881
|
+
const attempt = priorAttempts + 1;
|
|
228882
|
+
if (attempt > MAX_SESSION_CONTENTION_HOLD_RETRIES) {
|
|
228883
|
+
this.clearSessionContentionHold(task.id);
|
|
228884
|
+
const message2 = `Still waiting on another task to release a shared session path after ${MAX_SESSION_CONTENTION_HOLD_RETRIES} attempts \u2014 leaving the task queued for normal re-dispatch (not a failure)${detail ? `: ${detail}` : ""}`;
|
|
228885
|
+
executorLog.warn(`${task.id}: ${message2}`);
|
|
228886
|
+
await this.store.logEntry(task.id, message2, void 0, this.getRunContextFor(task.id));
|
|
228887
|
+
if (live.status != null || live.error != null) {
|
|
228888
|
+
await this.store.updateTask(task.id, { status: null, error: null }, this.getRunContextFor(task.id));
|
|
228889
|
+
}
|
|
228890
|
+
return;
|
|
228891
|
+
}
|
|
228892
|
+
this.sessionContentionHoldAttempts.set(task.id, attempt);
|
|
228893
|
+
const message = `Waiting on another task to release a shared session path \u2014 retrying in place (${attempt}/${MAX_SESSION_CONTENTION_HOLD_RETRIES})${detail ? `: ${detail}` : ""}`;
|
|
228894
|
+
executorLog.warn(`${task.id}: ${message}`);
|
|
228895
|
+
await this.store.logEntry(task.id, message, void 0, this.getRunContextFor(task.id));
|
|
228896
|
+
if (live.status != null || live.error != null) {
|
|
228897
|
+
await this.store.updateTask(task.id, { status: null, error: null }, this.getRunContextFor(task.id));
|
|
228898
|
+
}
|
|
228899
|
+
const delayMs = SESSION_CONTENTION_HOLD_BACKOFF_MS === 0 ? 0 : Math.min(SESSION_CONTENTION_HOLD_MAX_BACKOFF_MS, SESSION_CONTENTION_HOLD_BACKOFF_MS * 2 ** (attempt - 1));
|
|
228900
|
+
const scheduleRetry = () => {
|
|
228901
|
+
void (async () => {
|
|
228902
|
+
try {
|
|
228903
|
+
const resume = await this.store.getTask(task.id);
|
|
228904
|
+
if (!resume || resume.deletedAt || resume.paused || resume.userPaused) {
|
|
228905
|
+
this.clearSessionContentionHold(task.id);
|
|
228906
|
+
return;
|
|
228907
|
+
}
|
|
228908
|
+
await this.execute(resume);
|
|
228909
|
+
} catch (err) {
|
|
228910
|
+
executorLog.error(`Failed session-contention retry for ${task.id}:`, err);
|
|
228911
|
+
}
|
|
228912
|
+
})();
|
|
228913
|
+
};
|
|
228914
|
+
setTimeout(scheduleRetry, delayMs).unref?.();
|
|
228915
|
+
}
|
|
228369
228916
|
graphFailureValue(result2) {
|
|
228370
228917
|
const failedNode = result2.visitedNodeIds[result2.visitedNodeIds.length - 1];
|
|
228371
228918
|
if (!failedNode || !result2.context) return void 0;
|
|
@@ -228957,6 +229504,11 @@ Graph value: ${failureValue}` : ""}`, budget.key),
|
|
|
228957
229504
|
await this.persistTokenUsage(task.id);
|
|
228958
229505
|
return;
|
|
228959
229506
|
}
|
|
229507
|
+
if (this.isSessionContentionGraphFailure(result2)) {
|
|
229508
|
+
await this.holdForSessionContention(task, live, result2);
|
|
229509
|
+
await this.persistTokenUsage(task.id);
|
|
229510
|
+
return;
|
|
229511
|
+
}
|
|
228960
229512
|
if (await this.routeUnusableWorktreeGraphFailureToRecovery(task, live, result2)) {
|
|
228961
229513
|
await this.persistTokenUsage(task.id);
|
|
228962
229514
|
return;
|
|
@@ -229777,7 +230329,7 @@ Graph value: ${failureValue}` : ""}`, budget.key),
|
|
|
229777
230329
|
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
229778
230330
|
const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
|
|
229779
230331
|
if (!isActiveTask) {
|
|
229780
|
-
const tasksDir =
|
|
230332
|
+
const tasksDir = join81(this.store.getFusionDir(), "tasks");
|
|
229781
230333
|
const promptPath = getPromptPath(tasksDir, task.id);
|
|
229782
230334
|
const staleness = await evaluateSpecStaleness({ settings, promptPath, task });
|
|
229783
230335
|
if (staleness.isStale) {
|
|
@@ -233638,10 +234190,10 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
233638
234190
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
233639
234191
|
*/
|
|
233640
234192
|
async injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, retry) {
|
|
233641
|
-
const promptPath =
|
|
234193
|
+
const promptPath = join81(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
233642
234194
|
let content;
|
|
233643
234195
|
try {
|
|
233644
|
-
content = await
|
|
234196
|
+
content = await readFile39(promptPath, "utf-8");
|
|
233645
234197
|
} catch {
|
|
233646
234198
|
executorLog.warn(`${task.id}: PROMPT.md not found at ${promptPath}, skipping workflow failure injection`);
|
|
233647
234199
|
return;
|
|
@@ -240180,7 +240732,7 @@ import { execFile as execFile27 } from "node:child_process";
|
|
|
240180
240732
|
import { appendFileSync as appendFileSync2, existsSync as existsSync66, mkdirSync as mkdirSync11, readdirSync as readdirSync12, readFileSync as readFileSync24, realpathSync as realpathSync10, rmSync as rmSync9, statSync as statSync13 } from "node:fs";
|
|
240181
240733
|
import { rm as rm14 } from "node:fs/promises";
|
|
240182
240734
|
import { tmpdir as tmpdir10 } from "node:os";
|
|
240183
|
-
import { isAbsolute as isAbsolute19, join as
|
|
240735
|
+
import { isAbsolute as isAbsolute19, join as join82, relative as relative17 } from "node:path";
|
|
240184
240736
|
import { promisify as promisify56 } from "node:util";
|
|
240185
240737
|
async function git4(args, cwd, opts = {}) {
|
|
240186
240738
|
const { stdout } = await execFileAsync20("git", args, {
|
|
@@ -240211,7 +240763,7 @@ function isBenignAbsentWorktreeError(err) {
|
|
|
240211
240763
|
return /is not a working tree|No such file or directory|spawn\s+.*\bENOENT\b/i.test(description);
|
|
240212
240764
|
}
|
|
240213
240765
|
function ensureAiMergeRootIgnored(projectRootDir, settings) {
|
|
240214
|
-
const excludePath =
|
|
240766
|
+
const excludePath = join82(projectRootDir, ".git", "info", "exclude");
|
|
240215
240767
|
if (!existsSync66(excludePath)) return;
|
|
240216
240768
|
try {
|
|
240217
240769
|
const current = readFileSync24(excludePath, "utf-8");
|
|
@@ -240243,7 +240795,7 @@ function getAiMergeTempSearchRoots(projectRootDir, settings) {
|
|
|
240243
240795
|
if (testWorkerRoot) {
|
|
240244
240796
|
try {
|
|
240245
240797
|
for (const entry of readdirSync12(testWorkerRoot)) {
|
|
240246
|
-
if (entry.startsWith("redir-")) roots.push(
|
|
240798
|
+
if (entry.startsWith("redir-")) roots.push(join82(testWorkerRoot, entry));
|
|
240247
240799
|
}
|
|
240248
240800
|
} catch {
|
|
240249
240801
|
}
|
|
@@ -240266,7 +240818,7 @@ async function pruneExistingAiMergeWorktrees(taskId, projectRootDir, audit, log3
|
|
|
240266
240818
|
continue;
|
|
240267
240819
|
}
|
|
240268
240820
|
for (const entry of entries) {
|
|
240269
|
-
const candidatePath =
|
|
240821
|
+
const candidatePath = join82(tempRoot, entry);
|
|
240270
240822
|
let canonicalPath = candidatePath;
|
|
240271
240823
|
try {
|
|
240272
240824
|
canonicalPath = realpathSync10(candidatePath);
|
|
@@ -240689,7 +241241,7 @@ import { promisify as promisify57 } from "node:util";
|
|
|
240689
241241
|
import { realpathSync as realpathSync11, readdirSync as readdirSync13 } from "node:fs";
|
|
240690
241242
|
import { mkdtemp as mkdtemp4 } from "node:fs/promises";
|
|
240691
241243
|
import { tmpdir as tmpdir11 } from "node:os";
|
|
240692
|
-
import { join as
|
|
241244
|
+
import { join as join83 } from "node:path";
|
|
240693
241245
|
async function git5(args, cwd, opts = {}) {
|
|
240694
241246
|
const { stdout } = await execFileAsync21("git", args, {
|
|
240695
241247
|
cwd,
|
|
@@ -240761,7 +241313,7 @@ function listAiMergeWorktreeCandidates(taskId, projectRootDir, settings) {
|
|
|
240761
241313
|
if (testWorkerRoot) {
|
|
240762
241314
|
try {
|
|
240763
241315
|
for (const entry of readdirSync13(testWorkerRoot)) {
|
|
240764
|
-
if (entry.startsWith("redir-")) roots.push(
|
|
241316
|
+
if (entry.startsWith("redir-")) roots.push(join83(testWorkerRoot, entry));
|
|
240765
241317
|
}
|
|
240766
241318
|
} catch {
|
|
240767
241319
|
}
|
|
@@ -240774,7 +241326,7 @@ function listAiMergeWorktreeCandidates(taskId, projectRootDir, settings) {
|
|
|
240774
241326
|
} catch {
|
|
240775
241327
|
continue;
|
|
240776
241328
|
}
|
|
240777
|
-
for (const entry of entries) candidates.push(
|
|
241329
|
+
for (const entry of entries) candidates.push(join83(root, entry));
|
|
240778
241330
|
}
|
|
240779
241331
|
return candidates;
|
|
240780
241332
|
}
|
|
@@ -241108,7 +241660,7 @@ async function landOneRepo(repoRootDir, branch, integrationBranch, ctx) {
|
|
|
241108
241660
|
registeredMergePaths.add(pathToRegister);
|
|
241109
241661
|
};
|
|
241110
241662
|
try {
|
|
241111
|
-
mergeRoot = await mkdtemp4(
|
|
241663
|
+
mergeRoot = await mkdtemp4(join83(resolveAiMergeRoot(repoRootDir, settings), `fusion-ai-merge-${taskId.toLowerCase()}-`));
|
|
241112
241664
|
registerMergeRoot(mergeRoot);
|
|
241113
241665
|
await git5(["worktree", "add", "--detach", mergeRoot, tipSha], repoRootDir);
|
|
241114
241666
|
worktreeAdded = true;
|
|
@@ -241604,7 +242156,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
241604
242156
|
for (const repoRel of repoKeys) {
|
|
241605
242157
|
throwIfAborted2(options.signal, taskId);
|
|
241606
242158
|
const entry = workspaceWorktrees[repoRel];
|
|
241607
|
-
const repoRootDir =
|
|
242159
|
+
const repoRootDir = join83(workspaceRootDir, repoRel);
|
|
241608
242160
|
let integrationBranch;
|
|
241609
242161
|
try {
|
|
241610
242162
|
integrationBranch = await resolveIntegrationBranch(
|
|
@@ -241921,7 +242473,7 @@ async function pushAfterMergeToRemote(input) {
|
|
|
241921
242473
|
let worktreeAdded = false;
|
|
241922
242474
|
const registeredPaths = /* @__PURE__ */ new Set();
|
|
241923
242475
|
try {
|
|
241924
|
-
pushRoot = await mkdtemp4(
|
|
242476
|
+
pushRoot = await mkdtemp4(join83(resolveAiMergeRoot(projectRootDir, settings), `fusion-ai-merge-push-${taskId.toLowerCase()}-`));
|
|
241925
242477
|
for (const p2 of [pushRoot]) {
|
|
241926
242478
|
activeSessionRegistry.registerPath(p2, { taskId, kind: "ai-merge", ownerKey: `ai-merge-push:${taskId}` });
|
|
241927
242479
|
registeredPaths.add(p2);
|
|
@@ -242228,7 +242780,7 @@ var init_merger_ai = __esm({
|
|
|
242228
242780
|
|
|
242229
242781
|
// ../engine/src/task-revert.ts
|
|
242230
242782
|
import { exec as exec41 } from "node:child_process";
|
|
242231
|
-
import { join as
|
|
242783
|
+
import { join as join84 } from "node:path";
|
|
242232
242784
|
import { promisify as promisify58 } from "node:util";
|
|
242233
242785
|
function quoteShellArg7(value) {
|
|
242234
242786
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -242678,7 +243230,7 @@ async function resolveWorkspaceTaskRevertCommits(task, opts) {
|
|
|
242678
243230
|
const result2 = {};
|
|
242679
243231
|
for (const repoRel of repoKeys) {
|
|
242680
243232
|
const entry = workspaceWorktrees[repoRel];
|
|
242681
|
-
const repoRootDir =
|
|
243233
|
+
const repoRootDir = join84(opts.workspaceRootDir, repoRel);
|
|
242682
243234
|
const landedSha = workspaceLandedShas[repoRel] ?? entry?.landedSha;
|
|
242683
243235
|
if (landedSha && !entry?.baseCommitSha) {
|
|
242684
243236
|
result2[repoRel] = { commits: [landedSha], source: "squash" };
|
|
@@ -242745,7 +243297,7 @@ async function revertWorkspaceTask(opts) {
|
|
|
242745
243297
|
});
|
|
242746
243298
|
const contexts = [];
|
|
242747
243299
|
for (const repoRel of repoKeys) {
|
|
242748
|
-
const repoRootDir =
|
|
243300
|
+
const repoRootDir = join84(workspaceRootDir, repoRel);
|
|
242749
243301
|
let integrationBranch;
|
|
242750
243302
|
try {
|
|
242751
243303
|
integrationBranch = await resolveIntegrationBranch(repoRootDir, { ...opts.settings, integrationBranch: void 0, baseBranch: void 0 });
|
|
@@ -242870,7 +243422,7 @@ async function prepareWorkspaceRevertPrBranches(opts) {
|
|
|
242870
243422
|
});
|
|
242871
243423
|
const contexts = [];
|
|
242872
243424
|
for (const repoRel of repoKeys) {
|
|
242873
|
-
const repoRootDir =
|
|
243425
|
+
const repoRootDir = join84(workspaceRootDir, repoRel);
|
|
242874
243426
|
let integrationBranch;
|
|
242875
243427
|
try {
|
|
242876
243428
|
integrationBranch = await resolveIntegrationBranch(repoRootDir, { ...opts.settings, integrationBranch: void 0, baseBranch: void 0 });
|
|
@@ -243211,7 +243763,7 @@ async function readOptionalTextFile(filePath) {
|
|
|
243211
243763
|
}
|
|
243212
243764
|
async function discoverMcpServers(opts) {
|
|
243213
243765
|
const homeDir = opts.homeDir ?? process.env.HOME ?? process.env.USERPROFILE ?? osHomedir();
|
|
243214
|
-
const
|
|
243766
|
+
const readFile47 = opts.readFile ?? readOptionalTextFile;
|
|
243215
243767
|
const sources = getMcpDiscoverySources({
|
|
243216
243768
|
homeDir,
|
|
243217
243769
|
platform: osPlatform(),
|
|
@@ -243222,7 +243774,7 @@ async function discoverMcpServers(opts) {
|
|
|
243222
243774
|
for (const source of sources) {
|
|
243223
243775
|
let contents;
|
|
243224
243776
|
try {
|
|
243225
|
-
contents = await
|
|
243777
|
+
contents = await readFile47(source.path);
|
|
243226
243778
|
} catch (error3) {
|
|
243227
243779
|
errors.push(`${source.label}: ${error3 instanceof Error ? error3.message : String(error3)}`);
|
|
243228
243780
|
continue;
|
|
@@ -243772,7 +244324,7 @@ var agent_reflection_exports = {};
|
|
|
243772
244324
|
__export(agent_reflection_exports, {
|
|
243773
244325
|
AgentReflectionService: () => AgentReflectionService
|
|
243774
244326
|
});
|
|
243775
|
-
import { readFile as
|
|
244327
|
+
import { readFile as readFile40 } from "node:fs/promises";
|
|
243776
244328
|
import { isAbsolute as isAbsolute20, resolve as resolve38 } from "node:path";
|
|
243777
244329
|
var reflectionLog, REFLECTION_SYSTEM_PROMPT, DEFAULT_OUTCOME_LIMIT, AgentReflectionService;
|
|
243778
244330
|
var init_agent_reflection = __esm({
|
|
@@ -244331,7 +244883,7 @@ Rules:
|
|
|
244331
244883
|
if (agent.instructionsPath?.trim()) {
|
|
244332
244884
|
const resolvedPath = isAbsolute20(agent.instructionsPath) ? agent.instructionsPath : resolve38(this.rootDir, agent.instructionsPath);
|
|
244333
244885
|
try {
|
|
244334
|
-
const content = await
|
|
244886
|
+
const content = await readFile40(resolvedPath, "utf-8");
|
|
244335
244887
|
if (content.trim()) {
|
|
244336
244888
|
pieces.push(content.trim());
|
|
244337
244889
|
}
|
|
@@ -246439,7 +246991,7 @@ The note must be specific (what/where/why). Prefer citing File Scope, PROMPT, a
|
|
|
246439
246991
|
|
|
246440
246992
|
// ../engine/src/overseer-watchdog.ts
|
|
246441
246993
|
import { existsSync as existsSync67, readFileSync as readFileSync25 } from "node:fs";
|
|
246442
|
-
import { dirname as dirname34, join as
|
|
246994
|
+
import { dirname as dirname34, join as join85, relative as relative18, resolve as resolve39, sep as sep11 } from "node:path";
|
|
246443
246995
|
import { homedir as homedir11 } from "node:os";
|
|
246444
246996
|
function defaultReadText(path10) {
|
|
246445
246997
|
try {
|
|
@@ -246468,7 +247020,7 @@ function discoverOverseerWatchdogFiles(options) {
|
|
|
246468
247020
|
};
|
|
246469
247021
|
if (agentDir) {
|
|
246470
247022
|
for (const name2 of WATCHDOG_FILENAMES) {
|
|
246471
|
-
tryAdd(
|
|
247023
|
+
tryAdd(join85(agentDir, name2), "user", 999);
|
|
246472
247024
|
}
|
|
246473
247025
|
}
|
|
246474
247026
|
let current = cwd;
|
|
@@ -246476,9 +247028,9 @@ function discoverOverseerWatchdogFiles(options) {
|
|
|
246476
247028
|
for (let hop = 0; hop < 64; hop++) {
|
|
246477
247029
|
const depth = relative18(cwd, current) === "" ? 0 : relative18(cwd, current).split(sep11).filter(Boolean).length;
|
|
246478
247030
|
for (const name2 of WATCHDOG_FILENAMES) {
|
|
246479
|
-
tryAdd(
|
|
246480
|
-
tryAdd(
|
|
246481
|
-
tryAdd(
|
|
247031
|
+
tryAdd(join85(current, name2), "project", depth);
|
|
247032
|
+
tryAdd(join85(current, ".fusion", name2), "project", depth);
|
|
247033
|
+
tryAdd(join85(current, ".omp", name2), "project", depth);
|
|
246482
247034
|
}
|
|
246483
247035
|
if (current === stopAt) break;
|
|
246484
247036
|
const parent = dirname34(current);
|
|
@@ -253709,6 +254261,9 @@ var init_in_process_runtime = __esm({
|
|
|
253709
254261
|
usageLimitPauser: this.usageLimitPauser,
|
|
253710
254262
|
agentStore: this.agentStore,
|
|
253711
254263
|
pluginRunner: this.pluginRunner,
|
|
254264
|
+
// FNXC:NodeWorktreeIsolation 2026-07-25-22:10: planning acquires (or reuses) the task's own
|
|
254265
|
+
// worktree through the executor's acquisition path, so no lane runs in the shared checkout.
|
|
254266
|
+
acquirePlanningWorktree: (taskId) => this.executor.ensureTaskWorktreeForPlanning(taskId),
|
|
253712
254267
|
onSpecifyStart: (t) => {
|
|
253713
254268
|
this.recordActivity();
|
|
253714
254269
|
runtimeLog.log(`Specifying ${t.id}...`);
|
|
@@ -253797,6 +254352,8 @@ var init_in_process_runtime = __esm({
|
|
|
253797
254352
|
getExecutingTaskIds: () => this.executor?.getExecutingTaskIds() ?? /* @__PURE__ */ new Set(),
|
|
253798
254353
|
clearPhantomExecutorBinding: (taskId, options) => this.executor?.clearPhantomExecutorBinding(taskId, options),
|
|
253799
254354
|
listWorktreeHolders: () => this.executor?.listWorktreeHolders() ?? [],
|
|
254355
|
+
// FNXC:PlanningEvacuation 2026-07-25-23:00: the executor owns the release safety conditions.
|
|
254356
|
+
releasePreExecutionWorktree: (taskId, reason) => this.executor?.releasePreExecutionWorktree(taskId, reason) ?? Promise.resolve(false),
|
|
253800
254357
|
recoverApprovedTriageTask: (task) => this.triageProcessor?.recoverApprovedTask(task) ?? Promise.resolve(false),
|
|
253801
254358
|
getPlanningTaskIds: () => this.triageProcessor?.getPlanningTaskIds() ?? /* @__PURE__ */ new Set(),
|
|
253802
254359
|
// FNXC:TaskTiming 2026-08-01-12:00: orphan planning recovery must defer
|
|
@@ -254776,7 +255333,7 @@ __export(child_process_runtime_exports, {
|
|
|
254776
255333
|
import { EventEmitter as EventEmitter28 } from "node:events";
|
|
254777
255334
|
import { fork } from "node:child_process";
|
|
254778
255335
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
254779
|
-
import { dirname as dirname35, join as
|
|
255336
|
+
import { dirname as dirname35, join as join86 } from "node:path";
|
|
254780
255337
|
var HealthMonitor, ChildProcessRuntime;
|
|
254781
255338
|
var init_child_process_runtime = __esm({
|
|
254782
255339
|
"../engine/src/runtimes/child-process-runtime.ts"() {
|
|
@@ -254938,7 +255495,7 @@ var init_child_process_runtime = __esm({
|
|
|
254938
255495
|
const isCompiled = !import.meta.url.endsWith(".ts");
|
|
254939
255496
|
const currentDir = dirname35(fileURLToPath4(import.meta.url));
|
|
254940
255497
|
const workerFile = isCompiled ? "child-process-worker.js" : "child-process-worker.ts";
|
|
254941
|
-
return
|
|
255498
|
+
return join86(currentDir, workerFile);
|
|
254942
255499
|
}
|
|
254943
255500
|
/**
|
|
254944
255501
|
* Set up event forwarding from IPC host to runtime listeners.
|
|
@@ -261205,7 +261762,7 @@ import { mkdirSync as mkdirSync12 } from "node:fs";
|
|
|
261205
261762
|
import { access as access10, unlink as unlink10, writeFile as writeFile32 } from "node:fs/promises";
|
|
261206
261763
|
import net2 from "node:net";
|
|
261207
261764
|
import { platform as platform3, tmpdir as tmpdir12 } from "node:os";
|
|
261208
|
-
import { join as
|
|
261765
|
+
import { join as join87 } from "node:path";
|
|
261209
261766
|
import { createHash as createHash24 } from "node:crypto";
|
|
261210
261767
|
function projectHash(projectId) {
|
|
261211
261768
|
return createHash24("sha1").update(projectId).digest("hex").slice(0, 16);
|
|
@@ -261215,10 +261772,10 @@ function computeEngineSocketPath(projectId) {
|
|
|
261215
261772
|
if (platform3() === "win32") {
|
|
261216
261773
|
return `\\\\.\\pipe\\fusion-engine-${hash}`;
|
|
261217
261774
|
}
|
|
261218
|
-
return
|
|
261775
|
+
return join87(tmpdir12(), `fusion-engine-${hash}.sock`);
|
|
261219
261776
|
}
|
|
261220
261777
|
function computeEngineLockFilePath(workingDir) {
|
|
261221
|
-
return
|
|
261778
|
+
return join87(workingDir, ".fusion", "engine.lock");
|
|
261222
261779
|
}
|
|
261223
261780
|
async function ensureLockTargetExists(lockPath) {
|
|
261224
261781
|
try {
|
|
@@ -261228,7 +261785,7 @@ async function ensureLockTargetExists(lockPath) {
|
|
|
261228
261785
|
}
|
|
261229
261786
|
}
|
|
261230
261787
|
async function acquireLockfile(workingDir, onCompromised) {
|
|
261231
|
-
const dir =
|
|
261788
|
+
const dir = join87(workingDir, ".fusion");
|
|
261232
261789
|
mkdirSync12(dir, { recursive: true });
|
|
261233
261790
|
const path10 = computeEngineLockFilePath(workingDir);
|
|
261234
261791
|
await ensureLockTargetExists(path10);
|
|
@@ -266904,7 +267461,7 @@ var init_issue_image_attachments = __esm({
|
|
|
266904
267461
|
import { exec as exec45 } from "node:child_process";
|
|
266905
267462
|
import { mkdtemp as mkdtemp6, rm as rm15 } from "node:fs/promises";
|
|
266906
267463
|
import { tmpdir as tmpdir13 } from "node:os";
|
|
266907
|
-
import { join as
|
|
267464
|
+
import { join as join88 } from "node:path";
|
|
266908
267465
|
import { promisify as promisify65 } from "node:util";
|
|
266909
267466
|
function resolveCommentAuthor(input) {
|
|
266910
267467
|
const login = input.login || "unknown";
|
|
@@ -268073,8 +268630,8 @@ var init_github = __esm({
|
|
|
268073
268630
|
{ timeout: 3e4, maxBuffer: 10 * 1024 * 1024 }
|
|
268074
268631
|
);
|
|
268075
268632
|
const mergeBase = mergeBaseStdout.trim();
|
|
268076
|
-
const indexDir = await mkdtemp6(
|
|
268077
|
-
const indexPath =
|
|
268633
|
+
const indexDir = await mkdtemp6(join88(tmpdir13(), "fn-pr-conflict-"));
|
|
268634
|
+
const indexPath = join88(indexDir, "index");
|
|
268078
268635
|
const gitEnv = { ...process.env, GIT_INDEX_FILE: indexPath };
|
|
268079
268636
|
try {
|
|
268080
268637
|
await execAsync40(`git -C ${quoteGitArg(opts.repoRoot)} read-tree -m ${quoteGitArg(mergeBase)} ${quoteGitArg(baseRef)} ${quoteGitArg(headRef)}`, {
|
|
@@ -271661,8 +272218,8 @@ var init_knowledge_index_refresh = __esm({
|
|
|
271661
272218
|
// ../dashboard/src/pr-metadata-generator.ts
|
|
271662
272219
|
import { promisify as promisify66 } from "node:util";
|
|
271663
272220
|
import { exec as execCb } from "node:child_process";
|
|
271664
|
-
import { access as access11, readFile as
|
|
271665
|
-
import { join as
|
|
272221
|
+
import { access as access11, readFile as readFile41 } from "node:fs/promises";
|
|
272222
|
+
import { join as join89 } from "node:path";
|
|
271666
272223
|
function buildFallbackPrMetadata(task) {
|
|
271667
272224
|
return {
|
|
271668
272225
|
title: task.title ?? task.id,
|
|
@@ -271862,14 +272419,14 @@ async function generatePrMetadata(input) {
|
|
|
271862
272419
|
]), combinedSignal);
|
|
271863
272420
|
let promptContent = "";
|
|
271864
272421
|
try {
|
|
271865
|
-
const promptPath =
|
|
271866
|
-
promptContent = (await raceWithAbort(
|
|
272422
|
+
const promptPath = join89(repoRoot, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
272423
|
+
promptContent = (await raceWithAbort(readFile41(promptPath, "utf8"), combinedSignal)).trim();
|
|
271867
272424
|
} catch {
|
|
271868
272425
|
promptContent = "";
|
|
271869
272426
|
}
|
|
271870
|
-
const templatePath =
|
|
272427
|
+
const templatePath = join89(repoRoot, ".github", "pull_request_template.md");
|
|
271871
272428
|
const templateExists = await raceWithAbort(access11(templatePath).then(() => true).catch(() => false), combinedSignal);
|
|
271872
|
-
const template = templateExists ? await raceWithAbort(
|
|
272429
|
+
const template = templateExists ? await raceWithAbort(readFile41(templatePath, "utf8"), combinedSignal) : "";
|
|
271873
272430
|
const model = resolveTitleSummarizerSettingsModel(settings);
|
|
271874
272431
|
const systemPrompt = [PR_METADATA_SYSTEM_PROMPT];
|
|
271875
272432
|
const titleGuidance = settings.prTitlePromptInstructions?.trim();
|
|
@@ -279522,12 +280079,12 @@ import {
|
|
|
279522
280079
|
unlinkSync as unlinkSync4
|
|
279523
280080
|
} from "node:fs";
|
|
279524
280081
|
import { homedir as homedir12 } from "node:os";
|
|
279525
|
-
import { basename as basename21, dirname as dirname36, join as
|
|
280082
|
+
import { basename as basename21, dirname as dirname36, join as join90, resolve as resolve40 } from "node:path";
|
|
279526
280083
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
279527
280084
|
function resolveHermesHome(profile) {
|
|
279528
|
-
const base = process.env.HERMES_HOME ??
|
|
280085
|
+
const base = process.env.HERMES_HOME ?? join90(homedir12(), ".hermes");
|
|
279529
280086
|
if (!profile || profile === "default") return base;
|
|
279530
|
-
return
|
|
280087
|
+
return join90(base, "profiles", profile);
|
|
279531
280088
|
}
|
|
279532
280089
|
function getFusionSkillSourceCandidates(moduleUrl = import.meta.url) {
|
|
279533
280090
|
const here = fileURLToPath5(moduleUrl);
|
|
@@ -279542,13 +280099,13 @@ function getFusionSkillSourceCandidates(moduleUrl = import.meta.url) {
|
|
|
279542
280099
|
function resolveBundledFusionSkillSource() {
|
|
279543
280100
|
const candidates = getFusionSkillSourceCandidates();
|
|
279544
280101
|
for (const candidate of candidates) {
|
|
279545
|
-
if (existsSync69(
|
|
280102
|
+
if (existsSync69(join90(candidate, "SKILL.md"))) return candidate;
|
|
279546
280103
|
}
|
|
279547
280104
|
return null;
|
|
279548
280105
|
}
|
|
279549
280106
|
function installFusionSkillIntoHermesHome(options = {}) {
|
|
279550
280107
|
const sourceDir = options.sourceDir ?? resolveBundledFusionSkillSource();
|
|
279551
|
-
const targetDir =
|
|
280108
|
+
const targetDir = join90(resolveHermesHome(options.profile), "skills", FUSION_SKILL_NAME);
|
|
279552
280109
|
if (!sourceDir) {
|
|
279553
280110
|
return {
|
|
279554
280111
|
outcome: "warning",
|
|
@@ -279637,7 +280194,7 @@ function isBrokenSymlink(path10) {
|
|
|
279637
280194
|
}
|
|
279638
280195
|
}
|
|
279639
280196
|
function looksLikePriorFusionInstall(path10) {
|
|
279640
|
-
const skillMd =
|
|
280197
|
+
const skillMd = join90(path10, "SKILL.md");
|
|
279641
280198
|
if (!existsSync69(skillMd)) return false;
|
|
279642
280199
|
try {
|
|
279643
280200
|
const body = readFileSync26(skillMd, "utf-8");
|
|
@@ -279649,7 +280206,7 @@ function looksLikePriorFusionInstall(path10) {
|
|
|
279649
280206
|
function looksLikeFusionSkillTarget(path10) {
|
|
279650
280207
|
if (!path10) return false;
|
|
279651
280208
|
if (basename21(path10).toLowerCase() === FUSION_SKILL_NAME) return true;
|
|
279652
|
-
return existsSync69(
|
|
280209
|
+
return existsSync69(join90(path10, "SKILL.md"));
|
|
279653
280210
|
}
|
|
279654
280211
|
var FUSION_SKILL_NAME;
|
|
279655
280212
|
var init_fusion_skill_install = __esm({
|
|
@@ -279830,7 +280387,7 @@ var init_src8 = __esm({
|
|
|
279830
280387
|
// ../../plugins/fusion-plugin-openclaw-runtime/src/pi-module.ts
|
|
279831
280388
|
import { spawn as spawn13 } from "node:child_process";
|
|
279832
280389
|
import { randomUUID as randomUUID50 } from "node:crypto";
|
|
279833
|
-
import { readFile as
|
|
280390
|
+
import { readFile as readFile43 } from "node:fs/promises";
|
|
279834
280391
|
function asString2(v2) {
|
|
279835
280392
|
return typeof v2 === "string" && v2.trim() !== "" ? v2.trim() : void 0;
|
|
279836
280393
|
}
|
|
@@ -279901,7 +280458,7 @@ function createCliSession(opts) {
|
|
|
279901
280458
|
};
|
|
279902
280459
|
}
|
|
279903
280460
|
async function configureOpenClawMcpServer(opts) {
|
|
279904
|
-
const serverValue = await
|
|
280461
|
+
const serverValue = await readFile43(opts.serverConfigPath, "utf-8");
|
|
279905
280462
|
await new Promise((resolve53, reject) => {
|
|
279906
280463
|
const child = spawn13(opts.binaryPath, ["--no-color", "--profile", opts.profile, "mcp", "set", opts.serverName, serverValue], {
|
|
279907
280464
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -280045,7 +280602,7 @@ var init_pi_module = __esm({
|
|
|
280045
280602
|
// ../../plugins/fusion-plugin-openclaw-runtime/src/mcp-config.ts
|
|
280046
280603
|
import { writeFileSync as writeFileSync13 } from "node:fs";
|
|
280047
280604
|
import { tmpdir as tmpdir14 } from "node:os";
|
|
280048
|
-
import { join as
|
|
280605
|
+
import { join as join91 } from "node:path";
|
|
280049
280606
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
280050
280607
|
import { dirname as dirname37 } from "node:path";
|
|
280051
280608
|
function toolsToMcpToolDefs(tools) {
|
|
@@ -280058,16 +280615,16 @@ function toolsToMcpToolDefs(tools) {
|
|
|
280058
280615
|
}
|
|
280059
280616
|
function writeOpenClawMcpBridgeFiles(toolDefs, cacheKey) {
|
|
280060
280617
|
const suffix = cacheKey ? `${process.pid}-${cacheKey}` : `${process.pid}`;
|
|
280061
|
-
const schemaPath =
|
|
280618
|
+
const schemaPath = join91(tmpdir14(), `openclaw-runtime-mcp-schemas-${suffix}.json`);
|
|
280062
280619
|
writeFileSync13(schemaPath, JSON.stringify(toolDefs));
|
|
280063
280620
|
const __filename = fileURLToPath6(import.meta.url);
|
|
280064
280621
|
const __dirname3 = dirname37(__filename);
|
|
280065
|
-
const serverPath =
|
|
280622
|
+
const serverPath = join91(__dirname3, "mcp-schema-server.cjs");
|
|
280066
280623
|
const serverConfig = {
|
|
280067
280624
|
command: "node",
|
|
280068
280625
|
args: [serverPath, schemaPath]
|
|
280069
280626
|
};
|
|
280070
|
-
const serverConfigPath =
|
|
280627
|
+
const serverConfigPath = join91(tmpdir14(), `openclaw-runtime-mcp-server-${suffix}.json`);
|
|
280071
280628
|
writeFileSync13(serverConfigPath, JSON.stringify(serverConfig));
|
|
280072
280629
|
return {
|
|
280073
280630
|
schemaPath,
|
|
@@ -280835,9 +281392,9 @@ var init_cli_spawn3 = __esm({
|
|
|
280835
281392
|
});
|
|
280836
281393
|
|
|
280837
281394
|
// ../../plugins/fusion-plugin-grok-runtime/src/probe.ts
|
|
280838
|
-
import { readFile as
|
|
281395
|
+
import { readFile as readFile44 } from "node:fs/promises";
|
|
280839
281396
|
import { homedir as homedir13 } from "node:os";
|
|
280840
|
-
import { join as
|
|
281397
|
+
import { join as join92 } from "node:path";
|
|
280841
281398
|
function buildCandidates2(binaryPath) {
|
|
280842
281399
|
const configuredBinaryPath = binaryPath?.trim() || void 0;
|
|
280843
281400
|
const ordered = configuredBinaryPath ? [configuredBinaryPath, ...CANDIDATES3] : [...CANDIDATES3];
|
|
@@ -280854,10 +281411,10 @@ async function probeGrokApiKeyPresence() {
|
|
|
280854
281411
|
if (typeof envKey === "string" && envKey.trim().length > 0) {
|
|
280855
281412
|
return { detected: true };
|
|
280856
281413
|
}
|
|
280857
|
-
const settingsPath =
|
|
281414
|
+
const settingsPath = join92(homedir13(), ".grok", "user-settings.json");
|
|
280858
281415
|
let raw;
|
|
280859
281416
|
try {
|
|
280860
|
-
raw = await
|
|
281417
|
+
raw = await readFile44(settingsPath, "utf-8");
|
|
280861
281418
|
} catch {
|
|
280862
281419
|
return { detected: false, reason: "No Grok API key detected by Fusion (GROK_API_KEY unset, ~/.grok/user-settings.json not found); the CLI will use its own credentials." };
|
|
280863
281420
|
}
|
|
@@ -281006,7 +281563,7 @@ var init_provider2 = __esm({
|
|
|
281006
281563
|
|
|
281007
281564
|
// ../../plugins/fusion-plugin-grok-runtime/src/acp/cli-spawn.ts
|
|
281008
281565
|
import { existsSync as existsSync70 } from "node:fs";
|
|
281009
|
-
import { dirname as dirname38, isAbsolute as isAbsolute22, join as
|
|
281566
|
+
import { dirname as dirname38, isAbsolute as isAbsolute22, join as join93, resolve as resolve41 } from "node:path";
|
|
281010
281567
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
281011
281568
|
function asTrimmedString(value) {
|
|
281012
281569
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
@@ -281024,7 +281581,7 @@ function pluginRootDir() {
|
|
|
281024
281581
|
}
|
|
281025
281582
|
function bundledClaudeBridgeBinPath(pluginRoot = pluginRootDir()) {
|
|
281026
281583
|
const extension = process.platform === "win32" ? ".cmd" : "";
|
|
281027
|
-
return
|
|
281584
|
+
return join93(pluginRoot, "node_modules", ".bin", `${CLAUDE_CODE_CLI_ACP_BINARY}${extension}`);
|
|
281028
281585
|
}
|
|
281029
281586
|
function resolveBundledClaudeBridgeBinary(options = {}) {
|
|
281030
281587
|
const root = options.pluginRoot ?? pluginRootDir();
|
|
@@ -282387,10 +282944,10 @@ import {
|
|
|
282387
282944
|
} from "node:fs";
|
|
282388
282945
|
import { createRequire as createRequire8 } from "node:module";
|
|
282389
282946
|
import { tmpdir as tmpdir15 } from "node:os";
|
|
282390
|
-
import { basename as basename23, dirname as dirname40, join as
|
|
282947
|
+
import { basename as basename23, dirname as dirname40, join as join95, resolve as resolve43 } from "node:path";
|
|
282391
282948
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
282392
282949
|
function isSkillDir(dir) {
|
|
282393
|
-
return existsSync71(
|
|
282950
|
+
return existsSync71(join95(dir, "SKILL.md"));
|
|
282394
282951
|
}
|
|
282395
282952
|
function pushUnique(out, candidate) {
|
|
282396
282953
|
if (!candidate) return;
|
|
@@ -282398,8 +282955,8 @@ function pushUnique(out, candidate) {
|
|
|
282398
282955
|
if (!out.includes(resolved)) out.push(resolved);
|
|
282399
282956
|
}
|
|
282400
282957
|
function pushSkillLayoutsAtRoot(out, root) {
|
|
282401
|
-
pushUnique(out,
|
|
282402
|
-
pushUnique(out,
|
|
282958
|
+
pushUnique(out, join95(root, "skill", FUSION_SKILL_NAME2));
|
|
282959
|
+
pushUnique(out, join95(root, "packages", "cli", "skill", FUSION_SKILL_NAME2));
|
|
282403
282960
|
}
|
|
282404
282961
|
function walkAncestorSkillCandidates(out, startDir, maxParents = 8) {
|
|
282405
282962
|
let dir = startDir;
|
|
@@ -282414,7 +282971,7 @@ function pushPackageRequireCandidates(out, from) {
|
|
|
282414
282971
|
try {
|
|
282415
282972
|
const require5 = createRequire8(from);
|
|
282416
282973
|
const pkgJson = require5.resolve("@runfusion/fusion/package.json");
|
|
282417
|
-
pushUnique(out,
|
|
282974
|
+
pushUnique(out, join95(dirname40(pkgJson), "skill", FUSION_SKILL_NAME2));
|
|
282418
282975
|
} catch {
|
|
282419
282976
|
}
|
|
282420
282977
|
}
|
|
@@ -282482,21 +283039,21 @@ function collectSkillsFromRoot(root, out) {
|
|
|
282482
283039
|
return;
|
|
282483
283040
|
}
|
|
282484
283041
|
for (const entry of entries) {
|
|
282485
|
-
const child =
|
|
283042
|
+
const child = join95(root, entry);
|
|
282486
283043
|
if (isSkillDir(child)) {
|
|
282487
283044
|
out.set(entry, child);
|
|
282488
283045
|
}
|
|
282489
283046
|
}
|
|
282490
283047
|
}
|
|
282491
283048
|
function stageGrokSessionSkills(options = {}) {
|
|
282492
|
-
const pluginDir = mkdtempSync3(
|
|
282493
|
-
const skillsDir =
|
|
283049
|
+
const pluginDir = mkdtempSync3(join95(tmpdir15(), "fusion-grok-plugin-"));
|
|
283050
|
+
const skillsDir = join95(pluginDir, "skills");
|
|
282494
283051
|
mkdirSync14(skillsDir, { recursive: true });
|
|
282495
283052
|
const installed = /* @__PURE__ */ new Map();
|
|
282496
283053
|
const includeFusion = options.includeFusionSkill !== false;
|
|
282497
283054
|
if (includeFusion) {
|
|
282498
283055
|
const fusionSource = resolveBundledFusionSkillSource2();
|
|
282499
|
-
if (fusionSource && installSkillDir(fusionSource,
|
|
283056
|
+
if (fusionSource && installSkillDir(fusionSource, join95(skillsDir, FUSION_SKILL_NAME2))) {
|
|
282500
283057
|
installed.set(FUSION_SKILL_NAME2, fusionSource);
|
|
282501
283058
|
}
|
|
282502
283059
|
}
|
|
@@ -282506,15 +283063,15 @@ function stageGrokSessionSkills(options = {}) {
|
|
|
282506
283063
|
}
|
|
282507
283064
|
for (const [name2, source] of installed) {
|
|
282508
283065
|
if (name2 === FUSION_SKILL_NAME2 && includeFusion) continue;
|
|
282509
|
-
installSkillDir(source,
|
|
283066
|
+
installSkillDir(source, join95(skillsDir, name2));
|
|
282510
283067
|
}
|
|
282511
283068
|
for (const [name2, source] of installed) {
|
|
282512
|
-
if (!existsSync71(
|
|
282513
|
-
installSkillDir(source,
|
|
283069
|
+
if (!existsSync71(join95(skillsDir, name2))) {
|
|
283070
|
+
installSkillDir(source, join95(skillsDir, name2));
|
|
282514
283071
|
}
|
|
282515
283072
|
}
|
|
282516
283073
|
writeFileSync14(
|
|
282517
|
-
|
|
283074
|
+
join95(pluginDir, "plugin.json"),
|
|
282518
283075
|
JSON.stringify(
|
|
282519
283076
|
{
|
|
282520
283077
|
name: "fusion-session-skills",
|
|
@@ -282580,7 +283137,7 @@ var init_skill_loader = __esm({
|
|
|
282580
283137
|
import { createServer as createServer2 } from "node:http";
|
|
282581
283138
|
import { existsSync as existsSync72, writeFileSync as writeFileSync15 } from "node:fs";
|
|
282582
283139
|
import { tmpdir as tmpdir16 } from "node:os";
|
|
282583
|
-
import { dirname as dirname41, join as
|
|
283140
|
+
import { dirname as dirname41, join as join96 } from "node:path";
|
|
282584
283141
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
282585
283142
|
import { randomUUID as randomUUID52 } from "node:crypto";
|
|
282586
283143
|
function toolsToMcpToolDefs2(tools) {
|
|
@@ -282592,7 +283149,7 @@ function toolsToMcpToolDefs2(tools) {
|
|
|
282592
283149
|
}));
|
|
282593
283150
|
}
|
|
282594
283151
|
function fusionToolsMcpServerPath() {
|
|
282595
|
-
return
|
|
283152
|
+
return join96(dirname41(fileURLToPath9(import.meta.url)), "mcp-schema-server.cjs");
|
|
282596
283153
|
}
|
|
282597
283154
|
function missingMcpSchemaServerError(serverPath) {
|
|
282598
283155
|
const error3 = new Error(`Fusion MCP schema server is missing: ${serverPath}`);
|
|
@@ -282633,7 +283190,7 @@ async function startFusionToolBridge(tools) {
|
|
|
282633
283190
|
byName.set(tool.name, tool);
|
|
282634
283191
|
}
|
|
282635
283192
|
}
|
|
282636
|
-
const schemaPath =
|
|
283193
|
+
const schemaPath = join96(tmpdir16(), `fusion-grok-mcp-schemas-${process.pid}-${randomUUID52()}.json`);
|
|
282637
283194
|
writeFileSync15(schemaPath, JSON.stringify(defs));
|
|
282638
283195
|
const server = createServer2(async (req, res) => {
|
|
282639
283196
|
if (req.method !== "POST" || req.url !== "/tool-call") {
|
|
@@ -283066,7 +283623,7 @@ var init_src11 = __esm({
|
|
|
283066
283623
|
|
|
283067
283624
|
// ../../plugins/fusion-plugin-claude-runtime/src/acp/cli-spawn.ts
|
|
283068
283625
|
import { existsSync as existsSync73 } from "node:fs";
|
|
283069
|
-
import { dirname as dirname42, isAbsolute as isAbsolute23, join as
|
|
283626
|
+
import { dirname as dirname42, isAbsolute as isAbsolute23, join as join97, resolve as resolve44 } from "node:path";
|
|
283070
283627
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
283071
283628
|
function asTrimmedString2(value) {
|
|
283072
283629
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
@@ -283084,7 +283641,7 @@ function pluginRootDir2() {
|
|
|
283084
283641
|
}
|
|
283085
283642
|
function bundledClaudeBridgeBinPath2(pluginRoot = pluginRootDir2()) {
|
|
283086
283643
|
const extension = process.platform === "win32" ? ".cmd" : "";
|
|
283087
|
-
return
|
|
283644
|
+
return join97(pluginRoot, "node_modules", ".bin", `${CLAUDE_CODE_CLI_ACP_BINARY2}${extension}`);
|
|
283088
283645
|
}
|
|
283089
283646
|
function resolveBundledClaudeBridgeBinary2(options = {}) {
|
|
283090
283647
|
const root = options.pluginRoot ?? pluginRootDir2();
|
|
@@ -284324,14 +284881,14 @@ var init_acp2 = __esm({
|
|
|
284324
284881
|
// ../../plugins/fusion-plugin-claude-runtime/src/cli-spawn.ts
|
|
284325
284882
|
import { spawn as spawn19 } from "node:child_process";
|
|
284326
284883
|
import { existsSync as existsSync74 } from "node:fs";
|
|
284327
|
-
import { basename as basename25, dirname as dirname44, isAbsolute as isAbsolute24, join as
|
|
284884
|
+
import { basename as basename25, dirname as dirname44, isAbsolute as isAbsolute24, join as join99, resolve as resolve46 } from "node:path";
|
|
284328
284885
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
284329
284886
|
function pluginRootDir3() {
|
|
284330
284887
|
const moduleDir = dirname44(fileURLToPath11(import.meta.url));
|
|
284331
284888
|
return ["src", "dist"].includes(basename25(moduleDir)) ? resolve46(moduleDir, "..") : moduleDir;
|
|
284332
284889
|
}
|
|
284333
284890
|
function bundledClaudeBridgeBinPath3(pluginRoot = pluginRootDir3()) {
|
|
284334
|
-
return
|
|
284891
|
+
return join99(pluginRoot, "bridge", `${CLAUDE_CODE_CLI_ACP_BINARY3}${process.platform === "win32" ? ".cmd" : ""}`);
|
|
284335
284892
|
}
|
|
284336
284893
|
function resolveBundledClaudeBridgeBinary3(options = {}) {
|
|
284337
284894
|
const candidate = bundledClaudeBridgeBinPath3(options.pluginRoot ?? pluginRootDir3());
|
|
@@ -284508,10 +285065,10 @@ import {
|
|
|
284508
285065
|
} from "node:fs";
|
|
284509
285066
|
import { createRequire as createRequire9 } from "node:module";
|
|
284510
285067
|
import { tmpdir as tmpdir17 } from "node:os";
|
|
284511
|
-
import { basename as basename26, dirname as dirname45, join as
|
|
285068
|
+
import { basename as basename26, dirname as dirname45, join as join100, resolve as resolve47 } from "node:path";
|
|
284512
285069
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
284513
285070
|
function isSkillDir2(dir) {
|
|
284514
|
-
return existsSync75(
|
|
285071
|
+
return existsSync75(join100(dir, "SKILL.md"));
|
|
284515
285072
|
}
|
|
284516
285073
|
function pushUnique2(out, candidate) {
|
|
284517
285074
|
if (!candidate) return;
|
|
@@ -284519,8 +285076,8 @@ function pushUnique2(out, candidate) {
|
|
|
284519
285076
|
if (!out.includes(resolved)) out.push(resolved);
|
|
284520
285077
|
}
|
|
284521
285078
|
function pushSkillLayoutsAtRoot2(out, root) {
|
|
284522
|
-
pushUnique2(out,
|
|
284523
|
-
pushUnique2(out,
|
|
285079
|
+
pushUnique2(out, join100(root, "skill", FUSION_SKILL_NAME3));
|
|
285080
|
+
pushUnique2(out, join100(root, "packages", "cli", "skill", FUSION_SKILL_NAME3));
|
|
284524
285081
|
}
|
|
284525
285082
|
function walkAncestorSkillCandidates2(out, startDir, maxParents = 8) {
|
|
284526
285083
|
let dir = startDir;
|
|
@@ -284535,7 +285092,7 @@ function pushPackageRequireCandidates2(out, from) {
|
|
|
284535
285092
|
try {
|
|
284536
285093
|
const require5 = createRequire9(from);
|
|
284537
285094
|
const pkgJson = require5.resolve("@runfusion/fusion/package.json");
|
|
284538
|
-
pushUnique2(out,
|
|
285095
|
+
pushUnique2(out, join100(dirname45(pkgJson), "skill", FUSION_SKILL_NAME3));
|
|
284539
285096
|
} catch {
|
|
284540
285097
|
}
|
|
284541
285098
|
}
|
|
@@ -284603,21 +285160,21 @@ function collectSkillsFromRoot2(root, out) {
|
|
|
284603
285160
|
return;
|
|
284604
285161
|
}
|
|
284605
285162
|
for (const entry of entries) {
|
|
284606
|
-
const child =
|
|
285163
|
+
const child = join100(root, entry);
|
|
284607
285164
|
if (isSkillDir2(child)) {
|
|
284608
285165
|
out.set(entry, child);
|
|
284609
285166
|
}
|
|
284610
285167
|
}
|
|
284611
285168
|
}
|
|
284612
285169
|
function stageClaudeSessionSkills(options = {}) {
|
|
284613
|
-
const pluginDir = mkdtempSync4(
|
|
284614
|
-
const skillsDir =
|
|
285170
|
+
const pluginDir = mkdtempSync4(join100(tmpdir17(), "fusion-claude-plugin-"));
|
|
285171
|
+
const skillsDir = join100(pluginDir, "skills");
|
|
284615
285172
|
mkdirSync15(skillsDir, { recursive: true });
|
|
284616
285173
|
const installed = /* @__PURE__ */ new Map();
|
|
284617
285174
|
const includeFusion = options.includeFusionSkill !== false;
|
|
284618
285175
|
if (includeFusion) {
|
|
284619
285176
|
const fusionSource = resolveBundledFusionSkillSource3();
|
|
284620
|
-
if (fusionSource && installSkillDir2(fusionSource,
|
|
285177
|
+
if (fusionSource && installSkillDir2(fusionSource, join100(skillsDir, FUSION_SKILL_NAME3))) {
|
|
284621
285178
|
installed.set(FUSION_SKILL_NAME3, fusionSource);
|
|
284622
285179
|
}
|
|
284623
285180
|
}
|
|
@@ -284627,15 +285184,15 @@ function stageClaudeSessionSkills(options = {}) {
|
|
|
284627
285184
|
}
|
|
284628
285185
|
for (const [name2, source] of installed) {
|
|
284629
285186
|
if (name2 === FUSION_SKILL_NAME3 && includeFusion) continue;
|
|
284630
|
-
installSkillDir2(source,
|
|
285187
|
+
installSkillDir2(source, join100(skillsDir, name2));
|
|
284631
285188
|
}
|
|
284632
285189
|
for (const [name2, source] of installed) {
|
|
284633
|
-
if (!existsSync75(
|
|
284634
|
-
installSkillDir2(source,
|
|
285190
|
+
if (!existsSync75(join100(skillsDir, name2))) {
|
|
285191
|
+
installSkillDir2(source, join100(skillsDir, name2));
|
|
284635
285192
|
}
|
|
284636
285193
|
}
|
|
284637
285194
|
writeFileSync16(
|
|
284638
|
-
|
|
285195
|
+
join100(pluginDir, "plugin.json"),
|
|
284639
285196
|
JSON.stringify(
|
|
284640
285197
|
{
|
|
284641
285198
|
name: "fusion-session-skills",
|
|
@@ -284701,7 +285258,7 @@ var init_skill_loader2 = __esm({
|
|
|
284701
285258
|
import { createServer as createServer3 } from "node:http";
|
|
284702
285259
|
import { existsSync as existsSync76, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "node:fs";
|
|
284703
285260
|
import { tmpdir as tmpdir18 } from "node:os";
|
|
284704
|
-
import { dirname as dirname46, join as
|
|
285261
|
+
import { dirname as dirname46, join as join101 } from "node:path";
|
|
284705
285262
|
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
284706
285263
|
import { randomBytes as randomBytes7, randomUUID as randomUUID53 } from "node:crypto";
|
|
284707
285264
|
function toolsToMcpToolDefs3(tools) {
|
|
@@ -284713,7 +285270,7 @@ function toolsToMcpToolDefs3(tools) {
|
|
|
284713
285270
|
}));
|
|
284714
285271
|
}
|
|
284715
285272
|
function fusionToolsMcpServerPath2() {
|
|
284716
|
-
return
|
|
285273
|
+
return join101(dirname46(fileURLToPath13(import.meta.url)), "mcp-schema-server.cjs");
|
|
284717
285274
|
}
|
|
284718
285275
|
function missingMcpSchemaServerError2(serverPath) {
|
|
284719
285276
|
const error3 = new Error(`Fusion MCP schema server is missing: ${serverPath}`);
|
|
@@ -284754,7 +285311,7 @@ async function startFusionToolBridge2(tools, options = {}) {
|
|
|
284754
285311
|
byName.set(tool.name, tool);
|
|
284755
285312
|
}
|
|
284756
285313
|
}
|
|
284757
|
-
const schemaPath =
|
|
285314
|
+
const schemaPath = join101(tmpdir18(), `fusion-claude-mcp-schemas-${process.pid}-${randomUUID53()}.json`);
|
|
284758
285315
|
writeFileSync17(schemaPath, JSON.stringify(defs));
|
|
284759
285316
|
const capabilityToken = randomBytes7(32).toString("base64url");
|
|
284760
285317
|
const server = createServer3(async (req, res) => {
|
|
@@ -285558,7 +286115,7 @@ var init_provider6 = __esm({
|
|
|
285558
286115
|
|
|
285559
286116
|
// ../../plugins/fusion-plugin-omp-runtime/src/acp/cli-spawn.ts
|
|
285560
286117
|
import { existsSync as existsSync77 } from "node:fs";
|
|
285561
|
-
import { dirname as dirname47, isAbsolute as isAbsolute25, join as
|
|
286118
|
+
import { dirname as dirname47, isAbsolute as isAbsolute25, join as join102, resolve as resolve48 } from "node:path";
|
|
285562
286119
|
import { fileURLToPath as fileURLToPath14 } from "node:url";
|
|
285563
286120
|
function asTrimmedString3(value) {
|
|
285564
286121
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
@@ -285576,7 +286133,7 @@ function pluginRootDir4() {
|
|
|
285576
286133
|
}
|
|
285577
286134
|
function bundledClaudeBridgeBinPath4(pluginRoot = pluginRootDir4()) {
|
|
285578
286135
|
const extension = process.platform === "win32" ? ".cmd" : "";
|
|
285579
|
-
return
|
|
286136
|
+
return join102(pluginRoot, "node_modules", ".bin", `${CLAUDE_CODE_CLI_ACP_BINARY4}${extension}`);
|
|
285580
286137
|
}
|
|
285581
286138
|
function resolveBundledClaudeBridgeBinary4(options = {}) {
|
|
285582
286139
|
const root = options.pluginRoot ?? pluginRootDir4();
|
|
@@ -286894,7 +287451,7 @@ var init_mcp_forwarding3 = __esm({
|
|
|
286894
287451
|
import { createServer as createServer4 } from "node:http";
|
|
286895
287452
|
import { existsSync as existsSync78, unlinkSync as unlinkSync5, writeFileSync as writeFileSync18 } from "node:fs";
|
|
286896
287453
|
import { tmpdir as tmpdir19 } from "node:os";
|
|
286897
|
-
import { dirname as dirname49, join as
|
|
287454
|
+
import { dirname as dirname49, join as join104 } from "node:path";
|
|
286898
287455
|
import { fileURLToPath as fileURLToPath15 } from "node:url";
|
|
286899
287456
|
import { randomUUID as randomUUID54 } from "node:crypto";
|
|
286900
287457
|
function toolsToMcpToolDefs4(tools) {
|
|
@@ -286911,12 +287468,12 @@ function fusionToolsMcpServerPath3() {
|
|
|
286911
287468
|
const here = dirname49(fileURLToPath15(import.meta.url));
|
|
286912
287469
|
const candidates = [
|
|
286913
287470
|
// Packaged CLI / same-dir as bundled.js or tool-bridge source.
|
|
286914
|
-
|
|
287471
|
+
join104(here, "mcp-schema-server.cjs"),
|
|
286915
287472
|
// Source layout when running from dist/ that still has ../src assets.
|
|
286916
|
-
|
|
286917
|
-
|
|
287473
|
+
join104(here, "src", "mcp-schema-server.cjs"),
|
|
287474
|
+
join104(here, "..", "src", "mcp-schema-server.cjs"),
|
|
286918
287475
|
// Monorepo plugin root relative to src/ or dist/plugins/.../bundled.js.
|
|
286919
|
-
|
|
287476
|
+
join104(here, "..", "mcp-schema-server.cjs")
|
|
286920
287477
|
];
|
|
286921
287478
|
for (const candidate of candidates) {
|
|
286922
287479
|
if (existsSync78(candidate)) return candidate;
|
|
@@ -286953,7 +287510,7 @@ async function startFusionToolBridge3(tools) {
|
|
|
286953
287510
|
byName.set(tool.name, tool);
|
|
286954
287511
|
}
|
|
286955
287512
|
}
|
|
286956
|
-
const schemaPath =
|
|
287513
|
+
const schemaPath = join104(tmpdir19(), `fusion-omp-mcp-schemas-${process.pid}-${randomUUID54()}.json`);
|
|
286957
287514
|
writeFileSync18(schemaPath, JSON.stringify(defs));
|
|
286958
287515
|
const server = createServer4(async (req, res) => {
|
|
286959
287516
|
if (req.method !== "POST" || req.url !== "/tool-call") {
|
|
@@ -288675,8 +289232,8 @@ var init_types5 = __esm({
|
|
|
288675
289232
|
|
|
288676
289233
|
// ../dashboard/src/stt/model-manager.ts
|
|
288677
289234
|
import { createHash as createHash25, randomUUID as randomUUID56 } from "node:crypto";
|
|
288678
|
-
import { mkdir as mkdir31, open as open4, readFile as
|
|
288679
|
-
import { join as
|
|
289235
|
+
import { mkdir as mkdir31, open as open4, readFile as readFile45, readdir as readdir11, rename as rename10, rm as rm18, stat as stat12, writeFile as writeFile34 } from "node:fs/promises";
|
|
289236
|
+
import { join as join105, relative as relative19, resolve as resolve50, sep as sep15 } from "node:path";
|
|
288680
289237
|
function parseSafeTarListing(listing) {
|
|
288681
289238
|
for (const line2 of listing.split("\n").filter(Boolean)) {
|
|
288682
289239
|
const type = line2[0];
|
|
@@ -288691,8 +289248,8 @@ function parseSafeTarListing(listing) {
|
|
|
288691
289248
|
function createVoiceModelManager(options = {}) {
|
|
288692
289249
|
const asset = options.asset ?? PARAKEET_V3_ASSET;
|
|
288693
289250
|
let configuredCacheDir = options.cacheDir;
|
|
288694
|
-
const cacheDir = () => configuredCacheDir ??=
|
|
288695
|
-
const finalDir = () =>
|
|
289251
|
+
const cacheDir = () => configuredCacheDir ??= join105(resolveGlobalDir(), "models", "parakeet-v3");
|
|
289252
|
+
const finalDir = () => join105(cacheDir(), "model");
|
|
288696
289253
|
let state = { status: "not-installed" };
|
|
288697
289254
|
let generation = 0;
|
|
288698
289255
|
let cleanupBarrier = Promise.resolve();
|
|
@@ -288755,8 +289312,8 @@ function createVoiceModelManager(options = {}) {
|
|
|
288755
289312
|
if (!current(g2, signal)) return state;
|
|
288756
289313
|
await mkdir31(cacheDir(), { recursive: true });
|
|
288757
289314
|
if (!current(g2, signal)) return state;
|
|
288758
|
-
partial2 =
|
|
288759
|
-
staging =
|
|
289315
|
+
partial2 = join105(cacheDir(), `.partial-${g2}-${randomUUID56()}`);
|
|
289316
|
+
staging = join105(cacheDir(), `.staging-${g2}-${randomUUID56()}`);
|
|
288760
289317
|
const file = await open4(partial2, "wx");
|
|
288761
289318
|
try {
|
|
288762
289319
|
const response = await (options.fetch ?? globalThis.fetch)(asset.url, { signal });
|
|
@@ -288801,10 +289358,10 @@ function createVoiceModelManager(options = {}) {
|
|
|
288801
289358
|
}
|
|
288802
289359
|
}
|
|
288803
289360
|
if (!current(g2, signal)) return state;
|
|
288804
|
-
await writeFile34(
|
|
289361
|
+
await writeFile34(join105(staging, "manifest.json"), JSON.stringify({ filename: asset.filename, sha256: asset.sha256, installedAt: (/* @__PURE__ */ new Date()).toISOString(), expectedFiles: asset.expectedFiles }));
|
|
288805
289362
|
await options.beforePromote?.(signal);
|
|
288806
289363
|
if (!current(g2, signal)) return state;
|
|
288807
|
-
const backup =
|
|
289364
|
+
const backup = join105(cacheDir(), `.backup-${g2}-${randomUUID56()}`);
|
|
288808
289365
|
let movedPrevious = false;
|
|
288809
289366
|
try {
|
|
288810
289367
|
try {
|
|
@@ -288884,9 +289441,9 @@ function createVoiceModelManager(options = {}) {
|
|
|
288884
289441
|
publish(absent);
|
|
288885
289442
|
return absent;
|
|
288886
289443
|
}
|
|
288887
|
-
const manifest = JSON.parse(await
|
|
289444
|
+
const manifest = JSON.parse(await readFile45(join105(finalDir(), "manifest.json"), "utf8"));
|
|
288888
289445
|
if (manifest.sha256 !== asset.sha256) throw new Error("stale manifest digest");
|
|
288889
|
-
for (const expected of asset.expectedFiles) if (!await existsFile(
|
|
289446
|
+
for (const expected of asset.expectedFiles) if (!await existsFile(join105(finalDir(), expected))) throw new Error("missing expected model file");
|
|
288890
289447
|
const installed = { status: "installed", checksumVerified: true, installedPath: finalDir() };
|
|
288891
289448
|
publish(installed);
|
|
288892
289449
|
return installed;
|
|
@@ -288898,7 +289455,7 @@ function createVoiceModelManager(options = {}) {
|
|
|
288898
289455
|
try {
|
|
288899
289456
|
for (const entry of await readdir11(cacheDir())) {
|
|
288900
289457
|
const own = /^\.(partial|staging|backup)-(\d+)-/.exec(entry);
|
|
288901
|
-
if (own && Number(own[2]) !== active?.generation) await rm18(
|
|
289458
|
+
if (own && Number(own[2]) !== active?.generation) await rm18(join105(cacheDir(), entry), { recursive: true, force: true });
|
|
288902
289459
|
}
|
|
288903
289460
|
} catch {
|
|
288904
289461
|
}
|
|
@@ -288920,7 +289477,7 @@ function createVoiceModelManager(options = {}) {
|
|
|
288920
289477
|
try {
|
|
288921
289478
|
for (const entry of await readdir11(cacheDir())) {
|
|
288922
289479
|
const match = /^\.(partial|staging|backup)-(\d+)-/.exec(entry);
|
|
288923
|
-
if (match && Number(match[2]) <= g2) await rm18(
|
|
289480
|
+
if (match && Number(match[2]) <= g2) await rm18(join105(cacheDir(), entry), { recursive: true, force: true });
|
|
288924
289481
|
}
|
|
288925
289482
|
} catch {
|
|
288926
289483
|
}
|
|
@@ -289706,7 +290263,7 @@ var init_provider_health_monitor = __esm({
|
|
|
289706
290263
|
|
|
289707
290264
|
// ../dashboard/src/server.ts
|
|
289708
290265
|
import express3 from "express";
|
|
289709
|
-
import { join as
|
|
290266
|
+
import { join as join106, dirname as dirname50 } from "node:path";
|
|
289710
290267
|
import { fileURLToPath as fileURLToPath16 } from "node:url";
|
|
289711
290268
|
import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
289712
290269
|
function clearAiSessionCleanupInterval() {
|
|
@@ -290410,7 +290967,7 @@ __export(task_exports, {
|
|
|
290410
290967
|
});
|
|
290411
290968
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
290412
290969
|
import { watchFile, unwatchFile, statSync as statSync14, existsSync as existsSync80, readFileSync as readFileSync27 } from "node:fs";
|
|
290413
|
-
import { basename as basename29, join as
|
|
290970
|
+
import { basename as basename29, join as join107 } from "node:path";
|
|
290414
290971
|
function columnLabel(column) {
|
|
290415
290972
|
return COLUMN_LABELS[column] ?? column;
|
|
290416
290973
|
}
|
|
@@ -290768,7 +291325,7 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName,
|
|
|
290768
291325
|
}
|
|
290769
291326
|
console.log(` Path: .fusion/tasks/${resolvedTask.id}/`);
|
|
290770
291327
|
if (attachFiles && attachFiles.length > 0) {
|
|
290771
|
-
const { readFile:
|
|
291328
|
+
const { readFile: readFile47 } = await import("node:fs/promises");
|
|
290772
291329
|
const { basename: basename31, extname: extname5, resolve: resolve53 } = await import("node:path");
|
|
290773
291330
|
for (const filePath of attachFiles) {
|
|
290774
291331
|
const resolvedPath = resolve53(filePath);
|
|
@@ -290781,7 +291338,7 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName,
|
|
|
290781
291338
|
}
|
|
290782
291339
|
let content;
|
|
290783
291340
|
try {
|
|
290784
|
-
content = await
|
|
291341
|
+
content = await readFile47(resolvedPath);
|
|
290785
291342
|
} catch {
|
|
290786
291343
|
console.error(` \u2717 Cannot read file: ${filePath}`);
|
|
290787
291344
|
continue;
|
|
@@ -290952,7 +291509,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
290952
291509
|
printEntries(filteredEntries);
|
|
290953
291510
|
if (options.follow) {
|
|
290954
291511
|
const projectPath = context.projectPath ?? process.cwd();
|
|
290955
|
-
const logPath =
|
|
291512
|
+
const logPath = join107(projectPath, ".fusion", "tasks", id, "agent.log");
|
|
290956
291513
|
if (!existsSync80(logPath)) {
|
|
290957
291514
|
console.log(`
|
|
290958
291515
|
Waiting for log file to be created...`);
|
|
@@ -291157,7 +291714,7 @@ async function runTaskMerge(id, projectName) {
|
|
|
291157
291714
|
}
|
|
291158
291715
|
}
|
|
291159
291716
|
async function runTaskAttach(id, filePath, projectName) {
|
|
291160
|
-
const { readFile:
|
|
291717
|
+
const { readFile: readFile47 } = await import("node:fs/promises");
|
|
291161
291718
|
const { basename: basename31, extname: extname5 } = await import("node:path");
|
|
291162
291719
|
const { resolve: resolve53 } = await import("node:path");
|
|
291163
291720
|
const resolvedPath = resolve53(filePath);
|
|
@@ -291171,7 +291728,7 @@ async function runTaskAttach(id, filePath, projectName) {
|
|
|
291171
291728
|
}
|
|
291172
291729
|
let content;
|
|
291173
291730
|
try {
|
|
291174
|
-
content = await
|
|
291731
|
+
content = await readFile47(resolvedPath);
|
|
291175
291732
|
} catch {
|
|
291176
291733
|
console.error(`Cannot read file: ${filePath}`);
|
|
291177
291734
|
process.exit(1);
|
|
@@ -292336,8 +292893,8 @@ init_src15();
|
|
|
292336
292893
|
import { randomUUID as randomUUID58 } from "node:crypto";
|
|
292337
292894
|
import { Type as Type9 } from "typebox";
|
|
292338
292895
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
292339
|
-
import { resolve as resolve52, relative as relative20, isAbsolute as isAbsolute26, sep as sep16, basename as basename30, extname as extname4, join as
|
|
292340
|
-
import { readFile as
|
|
292896
|
+
import { resolve as resolve52, relative as relative20, isAbsolute as isAbsolute26, sep as sep16, basename as basename30, extname as extname4, join as join108 } from "node:path";
|
|
292897
|
+
import { readFile as readFile46 } from "node:fs/promises";
|
|
292341
292898
|
import { existsSync as existsSync81 } from "node:fs";
|
|
292342
292899
|
import { spawn as spawn23 } from "node:child_process";
|
|
292343
292900
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -292418,12 +292975,12 @@ function resolveProjectRoot2(cwd) {
|
|
|
292418
292975
|
warnedMissingProjectRootResolver = true;
|
|
292419
292976
|
console.warn("[fusion-extension] @fusion/core.getProjectRootFromWorktree is unavailable; using filesystem fallback resolution.");
|
|
292420
292977
|
}
|
|
292421
|
-
if (worktreeProjectRoot && existsSync81(
|
|
292978
|
+
if (worktreeProjectRoot && existsSync81(join108(worktreeProjectRoot, ".fusion"))) {
|
|
292422
292979
|
return worktreeProjectRoot;
|
|
292423
292980
|
}
|
|
292424
292981
|
let current = resolve52(cwd);
|
|
292425
292982
|
while (true) {
|
|
292426
|
-
if (existsSync81(
|
|
292983
|
+
if (existsSync81(join108(current, ".fusion"))) {
|
|
292427
292984
|
return current;
|
|
292428
292985
|
}
|
|
292429
292986
|
const parent = resolve52(current, "..");
|
|
@@ -292685,7 +293242,7 @@ async function closeCachedStores() {
|
|
|
292685
293242
|
}
|
|
292686
293243
|
}
|
|
292687
293244
|
function getFusionDir(cwd) {
|
|
292688
|
-
return
|
|
293245
|
+
return join108(resolveProjectRoot2(cwd), ".fusion");
|
|
292689
293246
|
}
|
|
292690
293247
|
function requireProjectLayer(store, consumer) {
|
|
292691
293248
|
const layer = store.getAsyncLayer();
|
|
@@ -293467,7 +294024,7 @@ ${renderAgentLogEntries(entries)}` : `${header}
|
|
|
293467
294024
|
}
|
|
293468
294025
|
let content;
|
|
293469
294026
|
try {
|
|
293470
|
-
content = await
|
|
294027
|
+
content = await readFile46(filePath);
|
|
293471
294028
|
} catch {
|
|
293472
294029
|
throw new Error(`Cannot read file: ${params.path}`);
|
|
293473
294030
|
}
|