@runfusion/fusion 0.78.0-beta.5 → 0.78.0-beta.6
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 +3574 -1910
- package/dist/child-process-worker.js +1440 -628
- package/dist/client/.vite/manifest.json +257 -257
- package/dist/client/assets/{AgentDetailView-CdoJDm2q.js → AgentDetailView-F_LCM2Fh.js} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor-vlsZ36BL.js → AgentPermissionPolicyEditor-Da7PHXMp.js} +1 -1
- package/dist/client/assets/{AgentsView-9EwgX1OK.js → AgentsView-nIFjsi9d.js} +3 -3
- package/dist/client/assets/{CommandCenter--BVLmh3G.js → CommandCenter-BDtrZ6JK.js} +1 -1
- package/dist/client/assets/{DevServerView-d1CVa_YB.js → DevServerView-CXE7Swoe.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-_uNfzkVu.js → DirectoryPicker-CYI--LmS.js} +1 -1
- package/dist/client/assets/{DocumentsView-SHFZKBkK.js → DocumentsView-BUwtzdpV.js} +1 -1
- package/dist/client/assets/{EvalsView-CQMcojzl.js → EvalsView-0qtYh3sd.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-BhwVl29h.js → ExperimentalAgentOnboardingModal-D3YNBI3S.js} +1 -1
- package/dist/client/assets/{GoalsView-Ju8PyHt8.js → GoalsView-C8M2Ufj6.js} +1 -1
- package/dist/client/assets/{InsightsView-BHoI4E2e.js → InsightsView-ByhApqco.js} +1 -1
- package/dist/client/assets/{MemoryView-Co-efjDQ.js → MemoryView-BG3vxXrX.js} +1 -1
- package/dist/client/assets/{PatchnodeView-CBJpVpFG.js → PatchnodeView-D4gg6xMq.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-DDk1a_Qy.js → PiExtensionsManager-CaadPnc-.js} +1 -1
- package/dist/client/assets/{PluginManager-DtGflOpe.js → PluginManager-BC9_Ajdd.js} +1 -1
- package/dist/client/assets/{PullRequestView-DwHock6V.js → PullRequestView-BfouYDOl.js} +1 -1
- package/dist/client/assets/{ReportModal-CJaxJ6ua.js → ReportModal-BDsen5Oh.js} +1 -1
- package/dist/client/assets/{ResearchView-ZD-2JxsH.js → ResearchView-vmH_K4of.js} +1 -1
- package/dist/client/assets/{SecretsView-Cz99j53t.js → SecretsView-BinJNcAF.js} +1 -1
- package/dist/client/assets/{SettingsModal-dmitxbA_.js → SettingsModal-Bffo4m2D.js} +4 -4
- package/dist/client/assets/{SettingsModal-BfiZ2-GY.js → SettingsModal-DdnLlKn8.js} +1 -1
- package/dist/client/assets/{SettingsTextareaRow-XyVN0ZEP.js → SettingsTextareaRow-CV1xXRhq.js} +1 -1
- package/dist/client/assets/{SetupWizardModal-mVUAy77v.js → SetupWizardModal-B2O0HOZR.js} +2 -2
- package/dist/client/assets/{SkillsView-DYL1ZeGi.js → SkillsView-CPE3Jkt7.js} +1 -1
- package/dist/client/assets/{WorkflowNodeEditor-E9sIzyMz.js → WorkflowNodeEditor-CTVfvX3A.js} +1 -1
- package/dist/client/assets/{agent-import-generation-DwUlR4qa.js → agent-import-generation-Bu2uCPd0.js} +1 -1
- package/dist/client/assets/{agentsOrgChartLayout-B5F8JGin.js → agentsOrgChartLayout-BGOBewpH.js} +1 -1
- package/dist/client/assets/app-AzX-7oMC.js +11 -0
- package/dist/client/assets/app-B5vKhbbZ.js +11 -0
- package/dist/client/assets/app-BZHD4Tdt.js +5 -0
- package/dist/client/assets/app-CWzmFjgT.js +11 -0
- package/dist/client/assets/app-C_yEgSaj.js +11 -0
- package/dist/client/assets/app-Crvy006P.js +11 -0
- package/dist/client/assets/{app-Dsv4VHMo.js → app-UI8thBW4.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BFirFbC8.js → architectureDiagram-3BPJPVTR-BDzTDKvR.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-CK-1Yktz.js → blockDiagram-GPEHLZMM-BbnxXgkY.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-C7uG_4cR.js → c4Diagram-AAUBKEIU-B_upVmlM.js} +1 -1
- package/dist/client/assets/channel-2BQ2i0n6.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-BhPS_gnq.js → chunk-2J33WTMH-oH3mFmAA.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-N_uAN5vN.js → chunk-4BX2VUAB-vezh3jfu.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-CNYK0ezP.js → chunk-55IACEB6-BmGhY313.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-C9EkXgMB.js → chunk-727SXJPM-ChDetrFZ.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-CYcOZI0r.js → chunk-AQP2D5EJ-BY329LM6.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-D-oVdpAz.js → chunk-FMBD7UC4-zuiiPcMa.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-79zqWauw.js → chunk-ND2GUHAM-CqF_-gMe.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-T2WIzSUZ.js → chunk-QZHKN3VN-DtETgl8P.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CmoQsyhJ.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CmoQsyhJ.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-DYcEyEdj.js → cose-bilkent-S5V4N54A-CB7l3IMd.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-CG6eBqe1.js → dagre-BM42HDAG-B-yrcude.js} +1 -1
- package/dist/client/assets/{dashboard-view-Cthbyplu.js → dashboard-view-BHUOC-dp.js} +1 -1
- package/dist/client/assets/{dashboard-view-CpfQSqFD.js → dashboard-view-C2e-lWkw.js} +1 -1
- package/dist/client/assets/{dashboard-view-C3jacppL.js → dashboard-view-DAkh7C8T.js} +1 -1
- package/dist/client/assets/{dashboard-view-BV97_KM2.js → dashboard-view-lR50LvCR.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-CA-oSX6v.js → diagram-2AECGRRQ-cV9rcK7D.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-7zlBlYUe.js → diagram-5GNKFQAL-COswZy2u.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-kFIrOk6O.js → diagram-KO2AKTUF-CRYtHA-0.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-10NYAStZ.js → diagram-LMA3HP47-BSc2gwi-.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-YUx0Utvh.js → diagram-OG6HWLK6-CkTvh_OG.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-B9jMu7Zv.js → erDiagram-TEJ5UH35-IZxjOfAZ.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-C540twrl.js → flowDiagram-I6XJVG4X-DP1Krzf1.js} +1 -1
- package/dist/client/assets/{folder-open-Da5UOz-O.js → folder-open-3vwTBlrn.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-BRTG1iq_.js → ganttDiagram-6RSMTGT7-DTrMtkeI.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DGzLquoM.js → gitGraphDiagram-PVQCEYII-CZzumF1O.js} +1 -1
- package/dist/client/assets/index-BG6hrVhL.css +1 -0
- package/dist/client/assets/{index-B8Ei6RK_.js → index-jWzr0HgU.js} +343 -332
- package/dist/client/assets/{infoDiagram-5YYISTIA-DxDL2NPL.js → infoDiagram-5YYISTIA-DPToV-eq.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CugUocbL.js → ishikawaDiagram-YF4QCWOH-V9akKrsQ.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-4J1DbTfn.js → journeyDiagram-JHISSGLW-CMZmXLJq.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-3wWdkkqE.js → kanban-definition-UN3LZRKU-DT2oKh0N.js} +1 -1
- package/dist/client/assets/{mermaid.core-CdJo4vtY.js → mermaid.core-C9xSBL9K.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-3ggmNH1R.js → mindmap-definition-RKZ34NQL-BKyCGR7J.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-Br6qnYUo.js → pieDiagram-4H26LBE5-BcyGtqQ4.js} +1 -1
- package/dist/client/assets/{puzzle-COtNelRH.js → puzzle-acZM2rkn.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-q7eumSaG.js → quadrantDiagram-W4KKPZXB-C3FhvUs0.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-CFQyk2vj.js → requirementDiagram-4Y6WPE33-gcTHwql9.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BPRBSEnx.js → sankeyDiagram-5OEKKPKP-CvYBofJY.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-C3oaAZUP.js → sequenceDiagram-3UESZ5HK-D9v9NJKo.js} +1 -1
- package/dist/client/assets/{standing-instructions-template-_wMqEkKb.js → standing-instructions-template-B_qbkt6Q.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-Gf3SuYgE.js → stateDiagram-AJRCARHV-Dq3-ZEwg.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-Cuc4jpXw.js → stateDiagram-v2-BHNVJYJU-Dt4J-On6.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-D5sPA5Fn.js → timeline-definition-PNZ67QCA-BRP1zY5x.js} +1 -1
- package/dist/client/assets/{upload--LqDmM0k.js → upload-Bx4Vz2lr.js} +1 -1
- package/dist/client/assets/{users-C-TzkWjB.js → users-CCfOZK-x.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-BzsN4FbO.js → vennDiagram-CIIHVFJN-BqtShvFk.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-BJIIgfK4.js → wardley-L42UT6IY-D04-M_2p.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DFwxRQ3d.js → wardleyDiagram-YWT4CUSO-BmkvFHY9.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-Djbd80bq.js → xychartDiagram-2RQKCTM6-C1N68DRK.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/index.ts +19 -6
- package/dist/droid-cli/package.json +1 -1
- package/dist/droid-cli/src/__tests__/index.test.ts +10 -11
- package/dist/extension.js +2504 -1047
- package/dist/pi-claude-cli/index.ts +25 -18
- package/dist/pi-claude-cli/package.json +5 -5
- package/dist/pi-claude-cli/src/__tests__/acp-dispatch.test.ts +17 -0
- package/dist/pi-claude-cli/src/event-bridge.ts +2 -1
- package/dist/plugin-sdk/index.d.ts +23 -1
- package/dist/plugin-sdk/index.js +17 -3
- package/dist/plugins/fusion-plugin-acp-runtime/bundled.js +25 -11
- package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-antigravity-runtime/bundled.js +20674 -0
- package/dist/plugins/fusion-plugin-antigravity-runtime/manifest.json +6 -0
- package/dist/plugins/fusion-plugin-antigravity-runtime/mcp-schema-server.cjs +155 -0
- package/dist/plugins/fusion-plugin-antigravity-runtime/package.json +11 -0
- package/dist/plugins/fusion-plugin-claude-runtime/bundled.js +25 -11
- package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +44 -12
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-grok-runtime/bundled.js +25 -11
- package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-linear-import/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
- package/dist/plugins/fusion-plugin-omp-runtime/bundled.js +25 -11
- package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-quality/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-quality/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-todos/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-todos/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/bundled.js +17 -3
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +3 -3
- package/skill/fusion/references/engine-tools.md +1 -1
- package/dist/client/assets/app-BHtkLolA.js +0 -9
- package/dist/client/assets/app-BNkvP9Zj.js +0 -9
- package/dist/client/assets/app-BaaOD_8W.js +0 -9
- package/dist/client/assets/app-CTDJyVYb.js +0 -9
- package/dist/client/assets/app-CYBEnq88.js +0 -9
- package/dist/client/assets/app-D_qEfmZH.js +0 -3
- package/dist/client/assets/channel-BcOi2QPy.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-Do_hlHfK.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Do_hlHfK.js +0 -1
- package/dist/client/assets/index-D-BMoPtn.css +0 -1
|
@@ -1308,7 +1308,43 @@ var init_documents_artifacts = __esm({
|
|
|
1308
1308
|
}
|
|
1309
1309
|
});
|
|
1310
1310
|
|
|
1311
|
+
// ../core/src/secrets/redact-secrets.ts
|
|
1312
|
+
function redactSecrets(text2) {
|
|
1313
|
+
return text2.replace(/(authorization\s*[:=]\s*)(bearer\s+)?[^\s,;"']+/gi, "$1$2[REDACTED]").replace(/\b(bearer)\s+[A-Za-z0-9._\-+/=]+/gi, "$1 [REDACTED]").replace(
|
|
1314
|
+
/\b((?:api[_-]?key|key|token|secret|password|passwd|pwd|access[_-]?token|refresh[_-]?token|client[_-]?secret)\s*[:=]\s*)("?)[^\s,;"']+\2/gi,
|
|
1315
|
+
"$1$2[REDACTED]$2"
|
|
1316
|
+
).replace(/\b(sk-|ghp_|gho_|github_pat_|xox[abpr]-|AKIA)[A-Za-z0-9_-]{8,}/g, "[REDACTED]").replace(/\b[A-Za-z0-9+/]{40,}={0,2}\b/g, "[REDACTED]").replace(/\b[0-9a-fA-F]{32,}\b/g, "[REDACTED]");
|
|
1317
|
+
}
|
|
1318
|
+
var init_redact_secrets = __esm({
|
|
1319
|
+
"../core/src/secrets/redact-secrets.ts"() {
|
|
1320
|
+
"use strict";
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1311
1324
|
// ../core/src/tasks/task-external-block.ts
|
|
1325
|
+
function fallbackReport(block) {
|
|
1326
|
+
const condition = `${block.origin} limitation (${block.code || "UNCLASSIFIED"})`;
|
|
1327
|
+
return {
|
|
1328
|
+
verifiedCondition: `Fusion verified an external ${condition}.`,
|
|
1329
|
+
stopReason: "The task is safely paused to preserve its current execution state.",
|
|
1330
|
+
unimplementedWork: "Remaining implementation work was not performed.",
|
|
1331
|
+
unblockCondition: `Resolve the ${condition} and retry the task.`
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
function sanitizeReportField(value, fallback) {
|
|
1335
|
+
if (typeof value !== "string") return fallback;
|
|
1336
|
+
const sanitized = redactSecrets(value.replace(/[\u0000-\u001F\u007F]/g, " ").replace(/\s+/g, " ").trim()).slice(0, REPORT_FIELD_MAX_LENGTH).trim();
|
|
1337
|
+
return sanitized.length > 0 && !UNSAFE_REPORT_CONTENT.test(sanitized) ? sanitized : fallback;
|
|
1338
|
+
}
|
|
1339
|
+
function buildTaskExternalBlockReport(block, report2) {
|
|
1340
|
+
const fallback = fallbackReport(block);
|
|
1341
|
+
return {
|
|
1342
|
+
verifiedCondition: sanitizeReportField(report2?.verifiedCondition, fallback.verifiedCondition),
|
|
1343
|
+
stopReason: sanitizeReportField(report2?.stopReason, fallback.stopReason),
|
|
1344
|
+
unimplementedWork: sanitizeReportField(report2?.unimplementedWork, fallback.unimplementedWork),
|
|
1345
|
+
unblockCondition: sanitizeReportField(report2?.unblockCondition, fallback.unblockCondition)
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1312
1348
|
function isTaskExternallyBlocked(task) {
|
|
1313
1349
|
return task.status === EXTERNAL_BLOCK_STATUS && task.externalBlock !== void 0;
|
|
1314
1350
|
}
|
|
@@ -1337,12 +1373,15 @@ function buildTaskExternalBlockClearPatch() {
|
|
|
1337
1373
|
externalBlock: null
|
|
1338
1374
|
};
|
|
1339
1375
|
}
|
|
1340
|
-
var EXTERNAL_BLOCK_STATUS, EXTERNAL_BLOCK_PAUSE_REASON;
|
|
1376
|
+
var EXTERNAL_BLOCK_STATUS, EXTERNAL_BLOCK_PAUSE_REASON, REPORT_FIELD_MAX_LENGTH, UNSAFE_REPORT_CONTENT;
|
|
1341
1377
|
var init_task_external_block = __esm({
|
|
1342
1378
|
"../core/src/tasks/task-external-block.ts"() {
|
|
1343
1379
|
"use strict";
|
|
1380
|
+
init_redact_secrets();
|
|
1344
1381
|
EXTERNAL_BLOCK_STATUS = "blocked";
|
|
1345
1382
|
EXTERNAL_BLOCK_PAUSE_REASON = "external-block";
|
|
1383
|
+
REPORT_FIELD_MAX_LENGTH = 320;
|
|
1384
|
+
UNSAFE_REPORT_CONTENT = /\b(?:system prompt|tool (?:output|log)|stack trace|\bat \S+ \([^)]*:\d+:\d+\))/i;
|
|
1346
1385
|
}
|
|
1347
1386
|
});
|
|
1348
1387
|
|
|
@@ -1570,7 +1609,7 @@ var init_builtin_plan_review_group = __esm({
|
|
|
1570
1609
|
5. **Risk callouts** \u2014 migrations, data-loss paths, external integrations, secrets, or plugin/runtime dependencies that need explicit handling.
|
|
1571
1610
|
|
|
1572
1611
|
## Environment feasibility
|
|
1573
|
-
When the supplied \`## Environment Capabilities\` block
|
|
1612
|
+
When the supplied \`## Environment Capabilities\` block confirms a runtime unavailable without an applicable confirmed wrapper, REVISE only if a blocking acceptance/completion criterion requires it and the plan provides no runnable substitute. A documented or configured command using a confirmed wrapper remains binding even when its inner executable is absent from the host PATH. APPROVE a correctly scoped non-blocking \`## Environment Constraints\` substitute (use APPROVE_WITH_NOTES only for a genuine P2 note); unlisted commands are UNKNOWN, never absent.
|
|
1574
1613
|
|
|
1575
1614
|
${PLAN_REVIEW_COMPLETENESS_POLICY}
|
|
1576
1615
|
|
|
@@ -3004,6 +3043,9 @@ var init_settings_schema = __esm({
|
|
|
3004
3043
|
Grok CLI binary overrides are global operator settings because executable locations are machine-local. Blank/undefined preserves PATH auto-detection through grok.
|
|
3005
3044
|
*/
|
|
3006
3045
|
grokCliBinaryPath: void 0,
|
|
3046
|
+
// FNXC:AntigravityProvider 2026-09-20-18:32: Default disabled and preserve PATH auto-detection until an operator enables a verified agy binary.
|
|
3047
|
+
useAntigravityCli: void 0,
|
|
3048
|
+
antigravityCliBinaryPath: void 0,
|
|
3007
3049
|
/*
|
|
3008
3050
|
FNXC:OmpAcp 2026-07-13-22:50:
|
|
3009
3051
|
Oh My Pi (omp) CLI enable + binary override are global operator settings (machine-local), mirroring Grok/Cursor.
|
|
@@ -6190,19 +6232,6 @@ var init_task_priority = __esm({
|
|
|
6190
6232
|
}
|
|
6191
6233
|
});
|
|
6192
6234
|
|
|
6193
|
-
// ../core/src/secrets/redact-secrets.ts
|
|
6194
|
-
function redactSecrets(text2) {
|
|
6195
|
-
return text2.replace(/(authorization\s*[:=]\s*)(bearer\s+)?[^\s,;"']+/gi, "$1$2[REDACTED]").replace(/\b(bearer)\s+[A-Za-z0-9._\-+/=]+/gi, "$1 [REDACTED]").replace(
|
|
6196
|
-
/\b((?:api[_-]?key|key|token|secret|password|passwd|pwd|access[_-]?token|refresh[_-]?token|client[_-]?secret)\s*[:=]\s*)("?)[^\s,;"']+\2/gi,
|
|
6197
|
-
"$1$2[REDACTED]$2"
|
|
6198
|
-
).replace(/\b(sk-|ghp_|gho_|github_pat_|xox[abpr]-|AKIA)[A-Za-z0-9_-]{8,}/g, "[REDACTED]").replace(/\b[A-Za-z0-9+/]{40,}={0,2}\b/g, "[REDACTED]").replace(/\b[0-9a-fA-F]{32,}\b/g, "[REDACTED]");
|
|
6199
|
-
}
|
|
6200
|
-
var init_redact_secrets = __esm({
|
|
6201
|
-
"../core/src/secrets/redact-secrets.ts"() {
|
|
6202
|
-
"use strict";
|
|
6203
|
-
}
|
|
6204
|
-
});
|
|
6205
|
-
|
|
6206
6235
|
// ../core/src/missions/mission-blockers.ts
|
|
6207
6236
|
function normalizeMissionBlockerReason(raw) {
|
|
6208
6237
|
if (raw && KNOWN_REASONS.has(raw)) return { reason: raw };
|
|
@@ -8382,7 +8411,7 @@ var init_sql = __esm({
|
|
|
8382
8411
|
return new SQL([new StringChunk(str)]);
|
|
8383
8412
|
}
|
|
8384
8413
|
sql2.raw = raw;
|
|
8385
|
-
function
|
|
8414
|
+
function join104(chunks, separator) {
|
|
8386
8415
|
const result = [];
|
|
8387
8416
|
for (const [i, chunk] of chunks.entries()) {
|
|
8388
8417
|
if (i > 0 && separator !== void 0) {
|
|
@@ -8392,7 +8421,7 @@ var init_sql = __esm({
|
|
|
8392
8421
|
}
|
|
8393
8422
|
return new SQL(result);
|
|
8394
8423
|
}
|
|
8395
|
-
sql2.join =
|
|
8424
|
+
sql2.join = join104;
|
|
8396
8425
|
function identifier(value) {
|
|
8397
8426
|
return new Name(value);
|
|
8398
8427
|
}
|
|
@@ -12178,26 +12207,26 @@ function validateParallelism(nodes2, outgoing, nodesById) {
|
|
|
12178
12207
|
}
|
|
12179
12208
|
const joinsReached = /* @__PURE__ */ new Set();
|
|
12180
12209
|
for (const edge of branchEdges) {
|
|
12181
|
-
const
|
|
12182
|
-
if (!
|
|
12210
|
+
const join105 = walkBranchToJoin(edge.to, split.id, outgoing, nodesById);
|
|
12211
|
+
if (!join105) {
|
|
12183
12212
|
throw new WorkflowIrError(`split '${split.id}' has a branch with no reachable matching join`);
|
|
12184
12213
|
}
|
|
12185
|
-
joinsReached.add(
|
|
12214
|
+
joinsReached.add(join105);
|
|
12186
12215
|
}
|
|
12187
12216
|
if (joinsReached.size !== 1) {
|
|
12188
12217
|
throw new WorkflowIrError(`split '${split.id}' branches converge on more than one join`);
|
|
12189
12218
|
}
|
|
12190
12219
|
const joinId = [...joinsReached][0];
|
|
12191
|
-
const
|
|
12192
|
-
const mode =
|
|
12220
|
+
const join104 = nodesById.get(joinId);
|
|
12221
|
+
const mode = join104.config?.mode;
|
|
12193
12222
|
if (mode && typeof mode === "object" && "quorum" in mode) {
|
|
12194
12223
|
const n = mode.quorum;
|
|
12195
12224
|
if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
|
|
12196
|
-
throw new WorkflowIrError(`join '${
|
|
12225
|
+
throw new WorkflowIrError(`join '${join104.id}' quorum must be a positive integer`);
|
|
12197
12226
|
}
|
|
12198
12227
|
if (n > branchEdges.length) {
|
|
12199
12228
|
throw new WorkflowIrError(
|
|
12200
|
-
`join '${
|
|
12229
|
+
`join '${join104.id}' quorum(${n}) exceeds the split's ${branchEdges.length} branches`
|
|
12201
12230
|
);
|
|
12202
12231
|
}
|
|
12203
12232
|
}
|
|
@@ -14748,7 +14777,7 @@ The quality of your spec directly determines execution quality, review churn, an
|
|
|
14748
14777
|
- Access to the project's files so you can understand context
|
|
14749
14778
|
|
|
14750
14779
|
## Environment feasibility
|
|
14751
|
-
When an \`## Environment Capabilities\` section is supplied,
|
|
14780
|
+
When an \`## Environment Capabilities\` section is supplied, require a runnable substitute only for a runtime confirmed unavailable without an applicable confirmed wrapper. A documented or configured command using a confirmed wrapper remains binding even when its inner executable is absent from the host PATH. Commands not listed are UNKNOWN, never absent; never state that a plan is blocked because a runtime is missing.
|
|
14752
14781
|
|
|
14753
14782
|
## What you produce
|
|
14754
14783
|
Write a complete PROMPT.md specification to the given path using the write tool.
|
|
@@ -15958,6 +15987,9 @@ var init_builtin_code_review_group = __esm({
|
|
|
15958
15987
|
CODE_REVIEW_DESCRIPTION = "Diff-review the task's changes for correctness bugs, regressions, and intent mismatches that tests miss";
|
|
15959
15988
|
CODE_REVIEW_PROMPT = `You are a senior code reviewer. Review the task's diff for the correctness value automated tests do NOT catch.
|
|
15960
15989
|
|
|
15990
|
+
## Review stage
|
|
15991
|
+
This is a pre-merge review. Evidence that can exist only after landing (for example a push-to-main CI run) belongs to an enabled, blocking post-merge verification gate. Verify that the workflow preserves that requirement before allowing merge; if the gate or its evidence contract is missing, request that concrete configuration fix. Do not request the future run itself as a pre-merge implementation fix, or claim its evidence already exists. Pre-merge correctness and verification requirements still apply.
|
|
15992
|
+
|
|
15961
15993
|
## Step 1: Read the change
|
|
15962
15994
|
1. Read the full diff against the base branch: \`git diff <base>...HEAD\` (or \`git diff <base>\`). Determine the base from the task context / merge target.
|
|
15963
15995
|
2. Read the changed files in full where the diff is non-trivial, so you see the surrounding code paths the change touches \u2014 not just the hunks.
|
|
@@ -16043,9 +16075,17 @@ var init_builtin_post_merge_group = __esm({
|
|
|
16043
16075
|
2. Check the final merged diff and task summary for obvious mismatches, missing verification evidence, or integration-only regressions.
|
|
16044
16076
|
3. If configured test/build commands are available in the task context, inspect their latest result or explain why no post-merge command was applicable.
|
|
16045
16077
|
|
|
16078
|
+
## Required post-landing Full Suite evidence
|
|
16079
|
+
This enabled gate requires post-landing Full Suite evidence. Do NOT approve until its delivery record names all of the following:
|
|
16080
|
+
1. The landed SHA and the first Full Suite push-to-main run at or after that SHA, including the run ID and run SHA.
|
|
16081
|
+
2. A conclusion for every Test shard: 1/4, 2/4, 3/4, and 4/4.
|
|
16082
|
+
3. All four timing artifacts: test-timings-shard-1, test-timings-shard-2, test-timings-shard-3, and test-timings-shard-4.
|
|
16083
|
+
|
|
16084
|
+
Pre-landing, unrelated-main, or partial evidence does not satisfy this contract. If the required run or any required evidence is unavailable, return REVISE and state that final completion remains blocked pending the post-landing evidence. Record verified evidence in the task delivery record before approving.
|
|
16085
|
+
|
|
16046
16086
|
## Output Requirements
|
|
16047
16087
|
- APPROVE: post-merge verification is acceptable.
|
|
16048
|
-
- APPROVE_WITH_NOTES: completion may proceed with non-blocking notes.
|
|
16088
|
+
- APPROVE_WITH_NOTES: completion may proceed with non-blocking notes only when every post-landing evidence item above is recorded.
|
|
16049
16089
|
- REVISE: completion should be blocked; include the concrete post-merge issue and the needed follow-up.
|
|
16050
16090
|
- \`notes\` MUST contain one to three non-empty sentences naming what was checked and why the verdict was reached. An empty \`notes\` string is a protocol violation.
|
|
16051
16091
|
- Final output: output exactly one trailing JSON object on the final line (no markdown fences, no surrounding prose):
|
|
@@ -20333,7 +20373,7 @@ var init_select2 = __esm({
|
|
|
20333
20373
|
const baseTableName = this.tableName;
|
|
20334
20374
|
const tableName = getTableLikeName(table);
|
|
20335
20375
|
for (const item of extractUsedTable(table)) this.usedTables.add(item);
|
|
20336
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
20376
|
+
if (typeof tableName === "string" && this.config.joins?.some((join104) => join104.alias === tableName)) {
|
|
20337
20377
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
20338
20378
|
}
|
|
20339
20379
|
if (!this.isPartialSelect) {
|
|
@@ -21868,7 +21908,7 @@ var init_update = __esm({
|
|
|
21868
21908
|
createJoin(joinType) {
|
|
21869
21909
|
return (table, on) => {
|
|
21870
21910
|
const tableName = getTableLikeName(table);
|
|
21871
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
21911
|
+
if (typeof tableName === "string" && this.config.joins.some((join104) => join104.alias === tableName)) {
|
|
21872
21912
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
21873
21913
|
}
|
|
21874
21914
|
if (typeof on === "function") {
|
|
@@ -21964,10 +22004,10 @@ var init_update = __esm({
|
|
|
21964
22004
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
21965
22005
|
fields[tableName] = fromFields;
|
|
21966
22006
|
}
|
|
21967
|
-
for (const
|
|
21968
|
-
const tableName2 = getTableLikeName(
|
|
21969
|
-
if (typeof tableName2 === "string" && !is(
|
|
21970
|
-
const fromFields = this.getTableLikeFields(
|
|
22007
|
+
for (const join104 of this.config.joins) {
|
|
22008
|
+
const tableName2 = getTableLikeName(join104.table);
|
|
22009
|
+
if (typeof tableName2 === "string" && !is(join104.table, SQL)) {
|
|
22010
|
+
const fromFields = this.getTableLikeFields(join104.table);
|
|
21971
22011
|
fields[tableName2] = fromFields;
|
|
21972
22012
|
}
|
|
21973
22013
|
}
|
|
@@ -26280,8 +26320,10 @@ var init_schema2 = __esm({
|
|
|
26280
26320
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
26281
26321
|
function createAsyncDataLayer(connections, options) {
|
|
26282
26322
|
const db = connections.runtime;
|
|
26323
|
+
const healthDb = connections.health;
|
|
26283
26324
|
return {
|
|
26284
26325
|
db,
|
|
26326
|
+
healthDb,
|
|
26285
26327
|
projectId: options?.projectId,
|
|
26286
26328
|
backend: connections.backend,
|
|
26287
26329
|
async transaction(fn, options2) {
|
|
@@ -30427,13 +30469,13 @@ async function detectWorkspaceRepos(dir, runner = runGitCommand, timeout = DEFAU
|
|
|
30427
30469
|
return [];
|
|
30428
30470
|
}
|
|
30429
30471
|
const { stat: stat14 } = await import("node:fs/promises");
|
|
30430
|
-
const { join:
|
|
30472
|
+
const { join: join104 } = await import("node:path");
|
|
30431
30473
|
const found = [];
|
|
30432
30474
|
for (const entry of entries) {
|
|
30433
30475
|
if (EXCLUDED_WORKSPACE_ENTRIES.has(entry)) continue;
|
|
30434
|
-
const childDir =
|
|
30476
|
+
const childDir = join104(dir, entry);
|
|
30435
30477
|
try {
|
|
30436
|
-
const s3 = await stat14(
|
|
30478
|
+
const s3 = await stat14(join104(childDir, ".git"));
|
|
30437
30479
|
if (!s3.isDirectory() && !s3.isFile()) continue;
|
|
30438
30480
|
} catch {
|
|
30439
30481
|
continue;
|
|
@@ -30446,9 +30488,9 @@ async function detectWorkspaceRepos(dir, runner = runGitCommand, timeout = DEFAU
|
|
|
30446
30488
|
}
|
|
30447
30489
|
async function readConfiguredIntegrationBranch(projectPath) {
|
|
30448
30490
|
try {
|
|
30449
|
-
const { readFile:
|
|
30450
|
-
const { join:
|
|
30451
|
-
const raw = await
|
|
30491
|
+
const { readFile: readFile55 } = await import("node:fs/promises");
|
|
30492
|
+
const { join: join104 } = await import("node:path");
|
|
30493
|
+
const raw = await readFile55(join104(projectPath, ".fusion", "config.json"), "utf-8");
|
|
30452
30494
|
const config3 = JSON.parse(raw);
|
|
30453
30495
|
for (const value of [config3.settings?.integrationBranch, config3.settings?.baseBranch]) {
|
|
30454
30496
|
if (typeof value !== "string") continue;
|
|
@@ -30461,9 +30503,9 @@ async function readConfiguredIntegrationBranch(projectPath) {
|
|
|
30461
30503
|
}
|
|
30462
30504
|
async function isWorkspaceModeExplicitlyDisabled(projectPath) {
|
|
30463
30505
|
try {
|
|
30464
|
-
const { readFile:
|
|
30465
|
-
const { join:
|
|
30466
|
-
const raw = await
|
|
30506
|
+
const { readFile: readFile55 } = await import("node:fs/promises");
|
|
30507
|
+
const { join: join104 } = await import("node:path");
|
|
30508
|
+
const raw = await readFile55(join104(projectPath, ".fusion", "config.json"), "utf-8");
|
|
30467
30509
|
const config3 = JSON.parse(raw);
|
|
30468
30510
|
return config3.settings?.workspaceMode === false;
|
|
30469
30511
|
} catch {
|
|
@@ -30471,12 +30513,12 @@ async function isWorkspaceModeExplicitlyDisabled(projectPath) {
|
|
|
30471
30513
|
}
|
|
30472
30514
|
}
|
|
30473
30515
|
async function setWorkspaceModeInConfig(projectPath, value) {
|
|
30474
|
-
const { readFile:
|
|
30475
|
-
const { join:
|
|
30476
|
-
const configPath =
|
|
30516
|
+
const { readFile: readFile55, writeFile: writeFile31, mkdir: mkdir32 } = await import("node:fs/promises");
|
|
30517
|
+
const { join: join104 } = await import("node:path");
|
|
30518
|
+
const configPath = join104(projectPath, ".fusion", "config.json");
|
|
30477
30519
|
let config3 = {};
|
|
30478
30520
|
try {
|
|
30479
|
-
config3 = JSON.parse(await
|
|
30521
|
+
config3 = JSON.parse(await readFile55(configPath, "utf-8"));
|
|
30480
30522
|
} catch (err) {
|
|
30481
30523
|
if (err?.code !== "ENOENT") throw err;
|
|
30482
30524
|
}
|
|
@@ -30485,7 +30527,7 @@ async function setWorkspaceModeInConfig(projectPath, value) {
|
|
|
30485
30527
|
}
|
|
30486
30528
|
const settings = config3.settings;
|
|
30487
30529
|
settings.workspaceMode = value;
|
|
30488
|
-
await mkdir32(
|
|
30530
|
+
await mkdir32(join104(projectPath, ".fusion"), { recursive: true });
|
|
30489
30531
|
await writeFile31(configPath, JSON.stringify(config3, null, 2), "utf-8");
|
|
30490
30532
|
}
|
|
30491
30533
|
function isInRootRelativePath(entry, pathMod) {
|
|
@@ -30498,12 +30540,12 @@ function isInRootRelativePath(entry, pathMod) {
|
|
|
30498
30540
|
return true;
|
|
30499
30541
|
}
|
|
30500
30542
|
async function loadWorkspaceConfig(rootDir) {
|
|
30501
|
-
const { readFile:
|
|
30543
|
+
const { readFile: readFile55 } = await import("node:fs/promises");
|
|
30502
30544
|
const pathMod = await import("node:path");
|
|
30503
|
-
const { join:
|
|
30504
|
-
const configPath =
|
|
30545
|
+
const { join: join104 } = pathMod;
|
|
30546
|
+
const configPath = join104(rootDir, ".fusion", WORKSPACE_CONFIG_FILENAME);
|
|
30505
30547
|
try {
|
|
30506
|
-
const raw = await
|
|
30548
|
+
const raw = await readFile55(configPath, "utf-8");
|
|
30507
30549
|
const parsed = JSON.parse(raw);
|
|
30508
30550
|
if (parsed !== null && typeof parsed === "object" && "repos" in parsed && Array.isArray(parsed.repos) && parsed.repos.every((r) => typeof r === "string")) {
|
|
30509
30551
|
const rawRepos = parsed.repos;
|
|
@@ -30554,20 +30596,20 @@ async function addWorkspaceRepo(rootDir, repoRelPath, options = {}) {
|
|
|
30554
30596
|
}
|
|
30555
30597
|
async function saveWorkspaceConfig(rootDir, config3) {
|
|
30556
30598
|
const { mkdir: mkdir32, writeFile: writeFile31 } = await import("node:fs/promises");
|
|
30557
|
-
const { join:
|
|
30558
|
-
const fusionDir =
|
|
30599
|
+
const { join: join104 } = await import("node:path");
|
|
30600
|
+
const fusionDir = join104(rootDir, ".fusion");
|
|
30559
30601
|
await mkdir32(fusionDir, { recursive: true });
|
|
30560
30602
|
await writeFile31(
|
|
30561
|
-
|
|
30603
|
+
join104(fusionDir, WORKSPACE_CONFIG_FILENAME),
|
|
30562
30604
|
JSON.stringify(config3, null, 2),
|
|
30563
30605
|
"utf-8"
|
|
30564
30606
|
);
|
|
30565
30607
|
}
|
|
30566
30608
|
async function removeWorkspaceConfig(rootDir) {
|
|
30567
30609
|
const { unlink: unlink13 } = await import("node:fs/promises");
|
|
30568
|
-
const { join:
|
|
30610
|
+
const { join: join104 } = await import("node:path");
|
|
30569
30611
|
try {
|
|
30570
|
-
await unlink13(
|
|
30612
|
+
await unlink13(join104(rootDir, ".fusion", WORKSPACE_CONFIG_FILENAME));
|
|
30571
30613
|
return true;
|
|
30572
30614
|
} catch (error2) {
|
|
30573
30615
|
if (error2?.code === "ENOENT") return false;
|
|
@@ -33642,6 +33684,9 @@ async function replaceActiveTaskWorkflowContinuation(layer, input) {
|
|
|
33642
33684
|
eq(project_exports.workflowWorkItems.kind, "task"),
|
|
33643
33685
|
inArray(project_exports.workflowWorkItems.state, ACTIVE_TASK_CONTINUATION_STATES)
|
|
33644
33686
|
));
|
|
33687
|
+
if (input.onlyIfNoActiveTaskContinuation && activeRows.length > 0) {
|
|
33688
|
+
throw new ActiveTaskContinuationError(input.taskId);
|
|
33689
|
+
}
|
|
33645
33690
|
for (const row of activeRows) {
|
|
33646
33691
|
if (row.runId === input.runId && row.nodeId === input.nodeId && row.kind === input.kind) continue;
|
|
33647
33692
|
await transitionWorkflowWorkItem(layer, row.id, "succeeded", { leaseOwner: null, leaseExpiresAt: null, lastError: null }, tx);
|
|
@@ -33667,6 +33712,24 @@ async function replaceActiveTaskWorkflowContinuation(layer, input) {
|
|
|
33667
33712
|
}
|
|
33668
33713
|
async function seedWorkspaceCodeReviewContinuationIfIdle(layer, input) {
|
|
33669
33714
|
return layer.transactionImmediate(async (tx) => withTaskWorkflowSerialization(tx, layer.projectId, input.taskId, async () => {
|
|
33715
|
+
if (input.expectedWorkflowSelection !== void 0) {
|
|
33716
|
+
const rows = await tx.select({
|
|
33717
|
+
workflowId: project_exports.taskWorkflowSelection.workflowId,
|
|
33718
|
+
stepIds: project_exports.taskWorkflowSelection.stepIds
|
|
33719
|
+
}).from(project_exports.taskWorkflowSelection).where(and(
|
|
33720
|
+
projectScopeFor(project_exports.taskWorkflowSelection.projectId, layer.projectId),
|
|
33721
|
+
eq(project_exports.taskWorkflowSelection.taskId, input.taskId)
|
|
33722
|
+
)).limit(1);
|
|
33723
|
+
const row = rows[0];
|
|
33724
|
+
const current = row ? {
|
|
33725
|
+
workflowId: row.workflowId,
|
|
33726
|
+
stepIds: Array.isArray(row.stepIds) ? row.stepIds.filter((id) => typeof id === "string") : []
|
|
33727
|
+
} : null;
|
|
33728
|
+
const expected = input.expectedWorkflowSelection;
|
|
33729
|
+
if (current?.workflowId !== expected?.workflowId || current?.stepIds.length !== expected?.stepIds.length || current?.stepIds.some((id, index2) => id !== expected?.stepIds[index2])) {
|
|
33730
|
+
return { seeded: false, reason: "workflow-selection-changed" };
|
|
33731
|
+
}
|
|
33732
|
+
}
|
|
33670
33733
|
const active2 = await tx.select({ id: project_exports.workflowWorkItems.id }).from(project_exports.workflowWorkItems).where(and(
|
|
33671
33734
|
projectScopeFor(project_exports.workflowWorkItems.projectId, layer.projectId),
|
|
33672
33735
|
eq(project_exports.workflowWorkItems.taskId, input.taskId),
|
|
@@ -33825,7 +33888,7 @@ async function getCompletionHandoffMarker(db, taskId) {
|
|
|
33825
33888
|
async function clearCompletionHandoffMarker(db, taskId) {
|
|
33826
33889
|
await db.delete(project_exports.completionHandoffMarkers).where(eq(project_exports.completionHandoffMarkers.taskId, taskId));
|
|
33827
33890
|
}
|
|
33828
|
-
var TERMINAL_WORKFLOW_WORK_ITEM_STATES, ACTIVE_TASK_CONTINUATION_STATES;
|
|
33891
|
+
var TERMINAL_WORKFLOW_WORK_ITEM_STATES, ACTIVE_TASK_CONTINUATION_STATES, ActiveTaskContinuationError;
|
|
33829
33892
|
var init_async_workflow_workitems = __esm({
|
|
33830
33893
|
"../core/src/task-store/async/async-workflow-workitems.ts"() {
|
|
33831
33894
|
"use strict";
|
|
@@ -33840,6 +33903,12 @@ var init_async_workflow_workitems = __esm({
|
|
|
33840
33903
|
"cancelled"
|
|
33841
33904
|
]);
|
|
33842
33905
|
ACTIVE_TASK_CONTINUATION_STATES = [...ACTIVE_WORKFLOW_WORK_ITEM_STATES];
|
|
33906
|
+
ActiveTaskContinuationError = class extends Error {
|
|
33907
|
+
constructor(taskId) {
|
|
33908
|
+
super(`active task continuation exists for ${taskId}`);
|
|
33909
|
+
this.name = "ActiveTaskContinuationError";
|
|
33910
|
+
}
|
|
33911
|
+
};
|
|
33843
33912
|
}
|
|
33844
33913
|
});
|
|
33845
33914
|
|
|
@@ -44684,10 +44753,10 @@ async function createConnectionSet(env = process.env, options = {}) {
|
|
|
44684
44753
|
"External backend resolved but runtimeUrl is null. This is an internal error."
|
|
44685
44754
|
);
|
|
44686
44755
|
}
|
|
44687
|
-
return createConnectionSetFromUrl(backend, options);
|
|
44756
|
+
return createConnectionSetFromUrl(backend, { ...options, env });
|
|
44688
44757
|
}
|
|
44689
44758
|
async function createConnectionSetFromUrl(backend, options = {}) {
|
|
44690
|
-
const poolMax = options
|
|
44759
|
+
const poolMax = resolvePoolMax(options);
|
|
44691
44760
|
const connectTimeout = options.connectTimeoutSeconds ?? DEFAULT_CONNECT_TIMEOUT_SECONDS;
|
|
44692
44761
|
const idleTimeout = options.idleTimeoutSeconds ?? DEFAULT_IDLE_TIMEOUT_SECONDS;
|
|
44693
44762
|
const onWarning = options.onWarning ?? ((msg) => log3.warn(msg));
|
|
@@ -44722,6 +44791,16 @@ async function createConnectionSetFromUrl(backend, options = {}) {
|
|
|
44722
44791
|
}
|
|
44723
44792
|
});
|
|
44724
44793
|
const runtimeDb = drizzle(runtimeSql);
|
|
44794
|
+
const healthSql = src_default(runtimeUrl, {
|
|
44795
|
+
max: 1,
|
|
44796
|
+
connect_timeout: connectTimeout,
|
|
44797
|
+
idle_timeout: idleTimeout,
|
|
44798
|
+
prepare: runtimePrepare,
|
|
44799
|
+
connection: runtimeConnectionParameters,
|
|
44800
|
+
onnotice: () => {
|
|
44801
|
+
}
|
|
44802
|
+
});
|
|
44803
|
+
const healthDb = drizzle(healthSql);
|
|
44725
44804
|
const migrationUrl = backend.migrationUrl ?? runtimeUrl;
|
|
44726
44805
|
const migrationSql = src_default(migrationUrl, {
|
|
44727
44806
|
max: 1,
|
|
@@ -44733,23 +44812,40 @@ async function createConnectionSetFromUrl(backend, options = {}) {
|
|
|
44733
44812
|
}
|
|
44734
44813
|
});
|
|
44735
44814
|
const migrationDb = drizzle(migrationSql);
|
|
44815
|
+
let closePromise;
|
|
44736
44816
|
const connections = {
|
|
44737
44817
|
runtime: runtimeDb,
|
|
44738
44818
|
migration: migrationDb,
|
|
44819
|
+
health: healthDb,
|
|
44739
44820
|
backend,
|
|
44740
|
-
|
|
44741
|
-
|
|
44742
|
-
|
|
44743
|
-
|
|
44744
|
-
|
|
44745
|
-
|
|
44821
|
+
close() {
|
|
44822
|
+
closePromise ??= (async () => {
|
|
44823
|
+
await Promise.allSettled([
|
|
44824
|
+
migrationSql.end({ timeout: 5 }),
|
|
44825
|
+
healthSql.end({ timeout: 5 }),
|
|
44826
|
+
runtimeSql.end({ timeout: 5 })
|
|
44827
|
+
]);
|
|
44828
|
+
})();
|
|
44829
|
+
return closePromise;
|
|
44746
44830
|
},
|
|
44747
44831
|
async ping() {
|
|
44748
|
-
await
|
|
44832
|
+
await healthSql`SELECT 1`;
|
|
44749
44833
|
}
|
|
44750
44834
|
};
|
|
44751
44835
|
return connections;
|
|
44752
44836
|
}
|
|
44837
|
+
function resolvePoolMax(options = {}) {
|
|
44838
|
+
if (options.poolMax !== void 0) return options.poolMax;
|
|
44839
|
+
const raw = options.env?.[FUSION_PG_POOL_MAX_ENV] ?? process.env[FUSION_PG_POOL_MAX_ENV];
|
|
44840
|
+
if (raw === void 0) return DEFAULT_POOL_MAX;
|
|
44841
|
+
const trimmed = raw.trim();
|
|
44842
|
+
const value = Number(trimmed);
|
|
44843
|
+
if (trimmed !== "" && Number.isInteger(value) && value >= MIN_POOL_MAX && value <= MAX_POOL_MAX) return value;
|
|
44844
|
+
(options.onWarning ?? ((msg) => log3.warn(msg)))(
|
|
44845
|
+
`${FUSION_PG_POOL_MAX_ENV} must be an integer from ${MIN_POOL_MAX} through ${MAX_POOL_MAX}; using default ${DEFAULT_POOL_MAX}`
|
|
44846
|
+
);
|
|
44847
|
+
return DEFAULT_POOL_MAX;
|
|
44848
|
+
}
|
|
44753
44849
|
async function verifyConnection(url2, timeoutSeconds = DEFAULT_CONNECT_TIMEOUT_SECONDS) {
|
|
44754
44850
|
const sql2 = src_default(url2, {
|
|
44755
44851
|
max: 1,
|
|
@@ -44768,7 +44864,7 @@ async function verifyConnection(url2, timeoutSeconds = DEFAULT_CONNECT_TIMEOUT_S
|
|
|
44768
44864
|
});
|
|
44769
44865
|
}
|
|
44770
44866
|
}
|
|
44771
|
-
var log3, DEFAULT_POOL_MAX, DEFAULT_CONNECT_TIMEOUT_SECONDS, DEFAULT_IDLE_TIMEOUT_SECONDS, DatabaseConnectionError;
|
|
44867
|
+
var log3, DEFAULT_POOL_MAX, FUSION_PG_POOL_MAX_ENV, MIN_POOL_MAX, MAX_POOL_MAX, DEFAULT_CONNECT_TIMEOUT_SECONDS, DEFAULT_IDLE_TIMEOUT_SECONDS, DatabaseConnectionError;
|
|
44772
44868
|
var init_connection2 = __esm({
|
|
44773
44869
|
"../core/src/postgres/connection.ts"() {
|
|
44774
44870
|
"use strict";
|
|
@@ -44779,6 +44875,9 @@ var init_connection2 = __esm({
|
|
|
44779
44875
|
init_credential_redact();
|
|
44780
44876
|
log3 = createLogger2("postgres-connection");
|
|
44781
44877
|
DEFAULT_POOL_MAX = 3;
|
|
44878
|
+
FUSION_PG_POOL_MAX_ENV = "FUSION_PG_POOL_MAX";
|
|
44879
|
+
MIN_POOL_MAX = 1;
|
|
44880
|
+
MAX_POOL_MAX = 500;
|
|
44782
44881
|
DEFAULT_CONNECT_TIMEOUT_SECONDS = 10;
|
|
44783
44882
|
DEFAULT_IDLE_TIMEOUT_SECONDS = 20;
|
|
44784
44883
|
DatabaseConnectionError = class extends Error {
|
|
@@ -46000,7 +46099,7 @@ async function checkPostgresHealth(layer) {
|
|
|
46000
46099
|
return errors;
|
|
46001
46100
|
}
|
|
46002
46101
|
try {
|
|
46003
|
-
const db = layer.
|
|
46102
|
+
const db = layer.healthDb;
|
|
46004
46103
|
const rows = await db.execute(
|
|
46005
46104
|
sql.raw(`
|
|
46006
46105
|
SELECT datallowconn, now() - pg_postmaster_start_time() AS uptime
|
|
@@ -46254,15 +46353,17 @@ function buildReport(checkedAt, anomalies) {
|
|
|
46254
46353
|
anomalies
|
|
46255
46354
|
};
|
|
46256
46355
|
}
|
|
46257
|
-
async function detectTaskIdIntegrityAnomaliesAsync(db) {
|
|
46356
|
+
async function detectTaskIdIntegrityAnomaliesAsync(db, options) {
|
|
46258
46357
|
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
46358
|
+
const projectId2 = options?.projectId?.trim() || void 0;
|
|
46359
|
+
const projectFilter = projectId2 ? sql` WHERE project_id = ${projectId2}` : sql``;
|
|
46259
46360
|
try {
|
|
46260
46361
|
const anomalies = [];
|
|
46261
46362
|
const activeRows = await db.execute(
|
|
46262
|
-
sql
|
|
46363
|
+
sql`SELECT id FROM ${sql.raw(`${PROJECT_SCHEMA}.tasks`)}${projectFilter}`
|
|
46263
46364
|
);
|
|
46264
46365
|
const archivedRows = await db.execute(
|
|
46265
|
-
sql
|
|
46366
|
+
sql`SELECT id FROM ${sql.raw(`${PROJECT_SCHEMA}.archived_tasks`)}${projectFilter}`
|
|
46266
46367
|
);
|
|
46267
46368
|
const activeIds = activeRows.map((r) => String(r.id ?? ""));
|
|
46268
46369
|
const archivedIds = archivedRows.map((r) => String(r.id ?? ""));
|
|
@@ -46313,7 +46414,7 @@ async function detectTaskIdIntegrityAnomaliesAsync(db) {
|
|
|
46313
46414
|
}
|
|
46314
46415
|
}
|
|
46315
46416
|
const stateRows = await db.execute(
|
|
46316
|
-
sql
|
|
46417
|
+
sql`SELECT prefix, next_sequence FROM ${sql.raw(`${PROJECT_SCHEMA}.distributed_task_id_state`)}${projectFilter}`
|
|
46317
46418
|
);
|
|
46318
46419
|
for (const stateRow of stateRows) {
|
|
46319
46420
|
const prefix = String(stateRow.prefix).trim().toUpperCase();
|
|
@@ -46685,6 +46786,11 @@ async function migrateSqliteToPostgresOnSession(migrationDb, sources, options) {
|
|
|
46685
46786
|
emitMigrationProgress(options, { phase: "copy-started", tableCount });
|
|
46686
46787
|
let tableIndex = 0;
|
|
46687
46788
|
for (const { source, plan } of plannedSources) {
|
|
46789
|
+
const sourceHasPreexistingTargetAhead = await hasPreexistingTargetAhead(
|
|
46790
|
+
migrationDb,
|
|
46791
|
+
source,
|
|
46792
|
+
plan
|
|
46793
|
+
);
|
|
46688
46794
|
for (const tablePlan of plan) {
|
|
46689
46795
|
tableIndex += 1;
|
|
46690
46796
|
const progressBase = {
|
|
@@ -46699,6 +46805,7 @@ async function migrateSqliteToPostgresOnSession(migrationDb, sources, options) {
|
|
|
46699
46805
|
source,
|
|
46700
46806
|
tablePlan,
|
|
46701
46807
|
dryRun,
|
|
46808
|
+
sourceHasPreexistingTargetAhead,
|
|
46702
46809
|
{
|
|
46703
46810
|
onCopyProgress: (processedRows, sourceRows) => emitMigrationProgress(options, {
|
|
46704
46811
|
phase: "table-progress",
|
|
@@ -46834,6 +46941,7 @@ async function buildMigrationPlan(db, source, projectId2) {
|
|
|
46834
46941
|
pgTable: table,
|
|
46835
46942
|
columns: [],
|
|
46836
46943
|
unmappedSourceColumns: [],
|
|
46944
|
+
ignoredRetiredSourceColumns: [],
|
|
46837
46945
|
allowedSkipReason: "redirected to central plugin registry and project state"
|
|
46838
46946
|
});
|
|
46839
46947
|
continue;
|
|
@@ -46850,12 +46958,19 @@ async function buildMigrationPlan(db, source, projectId2) {
|
|
|
46850
46958
|
columns: [],
|
|
46851
46959
|
partitionProjectId: projectId2,
|
|
46852
46960
|
unmappedSourceColumns: [],
|
|
46961
|
+
ignoredRetiredSourceColumns: [],
|
|
46853
46962
|
legacyPreservation: { sourceSchemaSql: readSqliteTableSchema(sqlite, table) }
|
|
46854
46963
|
});
|
|
46855
46964
|
continue;
|
|
46856
46965
|
}
|
|
46857
46966
|
const pgTable2 = toSnakeCase2(table);
|
|
46858
|
-
const {
|
|
46967
|
+
const {
|
|
46968
|
+
columns: resolvedColumns,
|
|
46969
|
+
targetColumnNames,
|
|
46970
|
+
unmappedSourceColumns,
|
|
46971
|
+
ignoredRetiredSourceColumns
|
|
46972
|
+
} = resolveColumnMapping(
|
|
46973
|
+
source.pgSchema,
|
|
46859
46974
|
pgTable2,
|
|
46860
46975
|
table,
|
|
46861
46976
|
sqlite,
|
|
@@ -46871,7 +46986,8 @@ async function buildMigrationPlan(db, source, projectId2) {
|
|
|
46871
46986
|
columns: cols,
|
|
46872
46987
|
partitionProjectId,
|
|
46873
46988
|
unmappedSourceColumns,
|
|
46874
|
-
|
|
46989
|
+
ignoredRetiredSourceColumns,
|
|
46990
|
+
allowedSkipReason: retiredSqliteTableSkipReason(source.pgSchema, table) ?? disposableSqliteTableReason(ftsVirtualTables, table)
|
|
46875
46991
|
});
|
|
46876
46992
|
continue;
|
|
46877
46993
|
}
|
|
@@ -46881,7 +46997,8 @@ async function buildMigrationPlan(db, source, projectId2) {
|
|
|
46881
46997
|
pgTable: pgTable2,
|
|
46882
46998
|
columns: cols,
|
|
46883
46999
|
partitionProjectId,
|
|
46884
|
-
unmappedSourceColumns
|
|
47000
|
+
unmappedSourceColumns,
|
|
47001
|
+
ignoredRetiredSourceColumns
|
|
46885
47002
|
});
|
|
46886
47003
|
}
|
|
46887
47004
|
return plans;
|
|
@@ -46889,6 +47006,79 @@ async function buildMigrationPlan(db, source, projectId2) {
|
|
|
46889
47006
|
sqlite.close();
|
|
46890
47007
|
}
|
|
46891
47008
|
}
|
|
47009
|
+
async function hasPreexistingTargetAhead(db, source, plans) {
|
|
47010
|
+
const sqlite = openSqlite(source.sqlitePath);
|
|
47011
|
+
try {
|
|
47012
|
+
for (const plan of plans) {
|
|
47013
|
+
if (plan.columns.length === 0 || plan.unmappedSourceColumns.length > 0) continue;
|
|
47014
|
+
const row = sqlite.prepare(`SELECT COUNT(*) AS n FROM ${quoteIdent(plan.table)}`).get();
|
|
47015
|
+
const sourceRows = Number(row.n);
|
|
47016
|
+
if (sourceRows === 0) continue;
|
|
47017
|
+
const targetRows = await countTargetRows(db, plan.pgSchema, plan.pgTable, plan.partitionProjectId);
|
|
47018
|
+
if (targetRows > sourceRows) return true;
|
|
47019
|
+
}
|
|
47020
|
+
return false;
|
|
47021
|
+
} finally {
|
|
47022
|
+
sqlite.close();
|
|
47023
|
+
}
|
|
47024
|
+
}
|
|
47025
|
+
function staleDuplicateSkipReason(targetRows) {
|
|
47026
|
+
return `stale SQLite duplicate; PostgreSQL already has ${targetRows} scoped rows`;
|
|
47027
|
+
}
|
|
47028
|
+
async function sourceRowsArePrimaryKeyConflictCovered(db, sqlite, plan, insertableCols) {
|
|
47029
|
+
const primaryKeyRows = await db.execute(sql`
|
|
47030
|
+
SELECT key_columns.column_name
|
|
47031
|
+
FROM information_schema.table_constraints AS constraints
|
|
47032
|
+
JOIN information_schema.key_column_usage AS key_columns
|
|
47033
|
+
ON key_columns.constraint_catalog = constraints.constraint_catalog
|
|
47034
|
+
AND key_columns.constraint_schema = constraints.constraint_schema
|
|
47035
|
+
AND key_columns.constraint_name = constraints.constraint_name
|
|
47036
|
+
WHERE constraints.constraint_type = 'PRIMARY KEY'
|
|
47037
|
+
AND constraints.table_schema = ${plan.pgSchema}
|
|
47038
|
+
AND constraints.table_name = ${plan.pgTable}
|
|
47039
|
+
ORDER BY key_columns.ordinal_position
|
|
47040
|
+
`);
|
|
47041
|
+
if (primaryKeyRows.length === 0) return false;
|
|
47042
|
+
const columnByPgName = new Map(insertableCols.map((column) => [column.pgName, column]));
|
|
47043
|
+
const primaryKeyColumns = primaryKeyRows.map(({ column_name }) => {
|
|
47044
|
+
if (column_name === "project_id" && plan.partitionProjectId !== void 0) {
|
|
47045
|
+
return { pgName: column_name, valueFor: () => plan.partitionProjectId };
|
|
47046
|
+
}
|
|
47047
|
+
const column = columnByPgName.get(column_name);
|
|
47048
|
+
if (!column) return void 0;
|
|
47049
|
+
return {
|
|
47050
|
+
pgName: column_name,
|
|
47051
|
+
valueFor: (row) => convertValue(
|
|
47052
|
+
row[column.sqliteName],
|
|
47053
|
+
column.type,
|
|
47054
|
+
column.nullJsonbFallback,
|
|
47055
|
+
column.preserveEmptyJsonbString
|
|
47056
|
+
)
|
|
47057
|
+
};
|
|
47058
|
+
});
|
|
47059
|
+
if (primaryKeyColumns.some((column) => column === void 0)) return false;
|
|
47060
|
+
const selectableCols = [...new Set(
|
|
47061
|
+
primaryKeyColumns.flatMap((column) => {
|
|
47062
|
+
if (!column || column.pgName === "project_id" && plan.partitionProjectId !== void 0) return [];
|
|
47063
|
+
return [quoteIdent(columnByPgName.get(column.pgName).sqliteName)];
|
|
47064
|
+
})
|
|
47065
|
+
)].join(", ") || "1";
|
|
47066
|
+
const rows = sqlite.prepare(`SELECT ${selectableCols} FROM ${quoteIdent(plan.table)}`).all();
|
|
47067
|
+
for (const row of rows) {
|
|
47068
|
+
const predicates = primaryKeyColumns.map((column) => {
|
|
47069
|
+
const resolved = column;
|
|
47070
|
+
return sql`${sql.raw(quoteIdent(resolved.pgName))} IS NOT DISTINCT FROM ${resolved.valueFor(row)}`;
|
|
47071
|
+
});
|
|
47072
|
+
const existing = await db.execute(sql`
|
|
47073
|
+
SELECT 1
|
|
47074
|
+
FROM ${sql.raw(quoteIdent(plan.pgSchema))}.${sql.raw(quoteIdent(plan.pgTable))}
|
|
47075
|
+
WHERE ${sql.join(predicates, sql` AND `)}
|
|
47076
|
+
LIMIT 1
|
|
47077
|
+
`);
|
|
47078
|
+
if (existing.length === 0) return false;
|
|
47079
|
+
}
|
|
47080
|
+
return true;
|
|
47081
|
+
}
|
|
46892
47082
|
function readSqliteTableSchema(db, table) {
|
|
46893
47083
|
const row = db.prepare(
|
|
46894
47084
|
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`
|
|
@@ -47052,15 +47242,21 @@ async function loadTargetColumnMetadata(db, pgSchema2) {
|
|
|
47052
47242
|
}
|
|
47053
47243
|
return byTable;
|
|
47054
47244
|
}
|
|
47055
|
-
function resolveColumnMapping(pgTable2, table, sqlite, targetColumnsByTable) {
|
|
47245
|
+
function resolveColumnMapping(sourceSchema, pgTable2, table, sqlite, targetColumnsByTable) {
|
|
47056
47246
|
const pgCols = targetColumnsByTable.get(pgTable2) ?? [];
|
|
47057
47247
|
if (pgCols.length === 0) {
|
|
47058
|
-
return {
|
|
47248
|
+
return {
|
|
47249
|
+
columns: [],
|
|
47250
|
+
targetColumnNames: /* @__PURE__ */ new Set(),
|
|
47251
|
+
unmappedSourceColumns: [],
|
|
47252
|
+
ignoredRetiredSourceColumns: []
|
|
47253
|
+
};
|
|
47059
47254
|
}
|
|
47060
47255
|
const pgByName = new Map(pgCols.map((c) => [c.column_name, c]));
|
|
47061
47256
|
const sqliteCols = sqlite.prepare(`PRAGMA table_info(${quoteIdent(table)})`).all();
|
|
47062
47257
|
const mapping = [];
|
|
47063
47258
|
const unmappedSourceColumns = [];
|
|
47259
|
+
const ignoredRetiredSourceColumns = [];
|
|
47064
47260
|
for (const sc of sqliteCols) {
|
|
47065
47261
|
let pgName = toSnakeCase2(sc.name);
|
|
47066
47262
|
if (pgName === "project_id" && pgByName.has("owner_project_id")) {
|
|
@@ -47068,7 +47264,11 @@ function resolveColumnMapping(pgTable2, table, sqlite, targetColumnsByTable) {
|
|
|
47068
47264
|
}
|
|
47069
47265
|
const pgCol = pgByName.get(pgName);
|
|
47070
47266
|
if (!pgCol) {
|
|
47071
|
-
|
|
47267
|
+
if (retiredSqliteColumnReason(sourceSchema, table, sc.name)) {
|
|
47268
|
+
ignoredRetiredSourceColumns.push(sc.name);
|
|
47269
|
+
} else {
|
|
47270
|
+
unmappedSourceColumns.push(sc.name);
|
|
47271
|
+
}
|
|
47072
47272
|
continue;
|
|
47073
47273
|
}
|
|
47074
47274
|
const type = classifyColumnType(pgCol);
|
|
@@ -47096,9 +47296,20 @@ function resolveColumnMapping(pgTable2, table, sqlite, targetColumnsByTable) {
|
|
|
47096
47296
|
return {
|
|
47097
47297
|
columns: mapping,
|
|
47098
47298
|
targetColumnNames: new Set(pgByName.keys()),
|
|
47099
|
-
unmappedSourceColumns
|
|
47299
|
+
unmappedSourceColumns,
|
|
47300
|
+
ignoredRetiredSourceColumns
|
|
47100
47301
|
};
|
|
47101
47302
|
}
|
|
47303
|
+
function retiredSqliteColumnReason(schema, table, column) {
|
|
47304
|
+
return schema === PROJECT_SCHEMA && table === "tasks" && column === "breakIntoSubtasks" ? "retired task-splitting field" : void 0;
|
|
47305
|
+
}
|
|
47306
|
+
function retiredSqliteTableSkipReason(schema, table) {
|
|
47307
|
+
return schema === CENTRAL_SCHEMA && table === "globalConcurrency" ? "retired global concurrency table; PostgreSQL target was removed" : void 0;
|
|
47308
|
+
}
|
|
47309
|
+
function retiredColumnSkipReason(plan) {
|
|
47310
|
+
if (plan.ignoredRetiredSourceColumns.length === 0) return void 0;
|
|
47311
|
+
return `ignored retired SQLite column(s): ${plan.ignoredRetiredSourceColumns.join(", ")}`;
|
|
47312
|
+
}
|
|
47102
47313
|
function classifyColumnType(pgCol) {
|
|
47103
47314
|
if (Number(pgCol.is_generated) === 1) {
|
|
47104
47315
|
return "generated";
|
|
@@ -47278,7 +47489,7 @@ async function migrateLegacyPreservationTable(db, source, plan, dryRun, progress
|
|
|
47278
47489
|
sqlite.close();
|
|
47279
47490
|
}
|
|
47280
47491
|
}
|
|
47281
|
-
async function migrateTable(db, source, plan, dryRun, progress = {}) {
|
|
47492
|
+
async function migrateTable(db, source, plan, dryRun, sourceHasPreexistingTargetAhead, progress = {}) {
|
|
47282
47493
|
if (plan.legacyPreservation) {
|
|
47283
47494
|
return migrateLegacyPreservationTable(db, source, plan, dryRun, progress);
|
|
47284
47495
|
}
|
|
@@ -47342,15 +47553,17 @@ async function migrateTable(db, source, plan, dryRun, progress = {}) {
|
|
|
47342
47553
|
const countRow = sqlite.prepare(`SELECT COUNT(*) AS n FROM ${quoteIdent(plan.table)}`).get();
|
|
47343
47554
|
sourceRows = Number(countRow.n);
|
|
47344
47555
|
if (dryRun || sourceRows === 0) {
|
|
47556
|
+
const targetRows2 = await countTargetRows(db, plan.pgSchema, plan.pgTable, plan.partitionProjectId);
|
|
47557
|
+
const staleDuplicate2 = dryRun && sourceRows > 0 && sourceHasPreexistingTargetAhead && targetRows2 >= sourceRows && await sourceRowsArePrimaryKeyConflictCovered(db, sqlite, plan, insertableCols);
|
|
47345
47558
|
return {
|
|
47346
47559
|
schema: plan.pgSchema,
|
|
47347
47560
|
table: plan.pgTable,
|
|
47348
47561
|
sourceRows,
|
|
47349
47562
|
insertedRows: 0,
|
|
47350
|
-
targetRows:
|
|
47351
|
-
verified:
|
|
47352
|
-
skipped:
|
|
47353
|
-
skipReason: dryRun ? "dry-run" : "no source rows"
|
|
47563
|
+
targetRows: targetRows2,
|
|
47564
|
+
verified: staleDuplicate2 ? true : !dryRun,
|
|
47565
|
+
skipped: staleDuplicate2 || dryRun,
|
|
47566
|
+
skipReason: staleDuplicate2 ? staleDuplicateSkipReason(targetRows2) : retiredColumnSkipReason(plan) ?? (dryRun ? "dry-run" : "no source rows")
|
|
47354
47567
|
};
|
|
47355
47568
|
}
|
|
47356
47569
|
const stmt = sqlite.prepare(`SELECT ${selectableCols} FROM ${quoteIdent(plan.table)}`);
|
|
@@ -47420,7 +47633,13 @@ async function migrateTable(db, source, plan, dryRun, progress = {}) {
|
|
|
47420
47633
|
`Row-count mismatch for ${plan.pgSchema}.${plan.pgTable}: source=${sourceRows}, target=${targetRows}`
|
|
47421
47634
|
);
|
|
47422
47635
|
}
|
|
47423
|
-
const
|
|
47636
|
+
const staleDuplicate = insertedRows === 0 && sourceHasPreexistingTargetAhead && targetRows >= sourceRows;
|
|
47637
|
+
if (staleDuplicate) {
|
|
47638
|
+
log4.log(
|
|
47639
|
+
`Skipping stale SQLite duplicate for ${plan.pgSchema}.${plan.pgTable}: all ${sourceRows} source rows conflicted while PostgreSQL already has ${targetRows} scoped rows.`
|
|
47640
|
+
);
|
|
47641
|
+
}
|
|
47642
|
+
const verified = staleDuplicate || rowCountOk && contentOk;
|
|
47424
47643
|
return {
|
|
47425
47644
|
schema: plan.pgSchema,
|
|
47426
47645
|
table: plan.pgTable,
|
|
@@ -47428,7 +47647,8 @@ async function migrateTable(db, source, plan, dryRun, progress = {}) {
|
|
|
47428
47647
|
insertedRows,
|
|
47429
47648
|
targetRows,
|
|
47430
47649
|
verified,
|
|
47431
|
-
skipped:
|
|
47650
|
+
skipped: staleDuplicate,
|
|
47651
|
+
skipReason: staleDuplicate ? staleDuplicateSkipReason(targetRows) : retiredColumnSkipReason(plan)
|
|
47432
47652
|
};
|
|
47433
47653
|
} finally {
|
|
47434
47654
|
sqlite.close();
|
|
@@ -47813,11 +48033,11 @@ async function rekeyFallbackProjectPartition(db, fallbackProjectId, registeredPr
|
|
|
47813
48033
|
const parentKey = `${fk.parentSchema}.${fk.parentTable}`;
|
|
47814
48034
|
if (!targetKeys.has(parentKey)) continue;
|
|
47815
48035
|
const childTarget = targetKeys.has(`${fk.childSchema}.${fk.childTable}`);
|
|
47816
|
-
const
|
|
48036
|
+
const join104 = fk.childColumns.map((column, index2) => `child.${quoteIdentifier(column)} IS NOT DISTINCT FROM parent.${quoteIdentifier(fk.parentColumns[index2])}`).join(" AND ");
|
|
47817
48037
|
const registeredChildScope = childTarget ? ` AND child.${quoteIdentifier("project_id")} = ${quoteLiteral(registeredProjectId)}` : "";
|
|
47818
48038
|
const unreplaced = await tx.execute(sql.raw(`SELECT EXISTS (
|
|
47819
48039
|
SELECT 1 FROM ${relationName({ schema: fk.childSchema, table: fk.childTable })} child
|
|
47820
|
-
JOIN ${relationName({ schema: fk.parentSchema, table: fk.parentTable })} parent ON ${
|
|
48040
|
+
JOIN ${relationName({ schema: fk.parentSchema, table: fk.parentTable })} parent ON ${join104}
|
|
47821
48041
|
JOIN fusion_rekey_conflict_candidates parent_candidate
|
|
47822
48042
|
ON parent_candidate.schema_name = ${quoteLiteral(fk.parentSchema)}
|
|
47823
48043
|
AND parent_candidate.table_name = ${quoteLiteral(fk.parentTable)}
|
|
@@ -58813,8 +59033,8 @@ var require_CronFileParser = __commonJS({
|
|
|
58813
59033
|
* @throws If file cannot be read
|
|
58814
59034
|
*/
|
|
58815
59035
|
static async parseFile(filePath) {
|
|
58816
|
-
const { readFile:
|
|
58817
|
-
const data = await
|
|
59036
|
+
const { readFile: readFile55 } = await Promise.resolve().then(() => __importStar(__require("fs/promises")));
|
|
59037
|
+
const data = await readFile55(filePath, "utf8");
|
|
58818
59038
|
return _CronFileParser.#parseContent(data);
|
|
58819
59039
|
}
|
|
58820
59040
|
/**
|
|
@@ -62244,6 +62464,7 @@ async function bootSchemaBackendOnce(options, bypassProjectIsolation = false) {
|
|
|
62244
62464
|
bypassProjectIsolation
|
|
62245
62465
|
}) : await createConnectionSetFromUrl(resolvedBackend, {
|
|
62246
62466
|
poolMax: options.poolMax,
|
|
62467
|
+
env,
|
|
62247
62468
|
bypassProjectIsolation
|
|
62248
62469
|
});
|
|
62249
62470
|
await applySchemaBaseline(connections.migration);
|
|
@@ -62598,6 +62819,7 @@ async function createTaskStoreForBackend(options) {
|
|
|
62598
62819
|
await connections.close();
|
|
62599
62820
|
connections = await createConnectionSetFromUrl(resolvedBackend, {
|
|
62600
62821
|
poolMax: options.poolMax,
|
|
62822
|
+
env,
|
|
62601
62823
|
projectId: resolvedProjectId,
|
|
62602
62824
|
useRuntimeRole: runtimeRoleRows[0]?.usable === true
|
|
62603
62825
|
});
|
|
@@ -62646,6 +62868,7 @@ async function createTaskStoreForBackend(options) {
|
|
|
62646
62868
|
bypassProjectIsolation: true
|
|
62647
62869
|
}) : await createConnectionSetFromUrl(resolvedBackend, {
|
|
62648
62870
|
poolMax: 1,
|
|
62871
|
+
env,
|
|
62649
62872
|
bypassProjectIsolation: true
|
|
62650
62873
|
});
|
|
62651
62874
|
try {
|
|
@@ -71734,6 +71957,8 @@ function archiveEntryToTask(entry, slim = false) {
|
|
|
71734
71957
|
attachments: slim ? void 0 : entry.attachments,
|
|
71735
71958
|
comments: entry.comments,
|
|
71736
71959
|
review: slim ? void 0 : entry.review,
|
|
71960
|
+
// FNXC:ArchivedRecommendations 2026-09-20-17:23: archive reads retain actionable terminal recommendation state.
|
|
71961
|
+
recommendations: entry.recommendations,
|
|
71737
71962
|
log: slim ? [] : entry.log ?? [],
|
|
71738
71963
|
timedExecutionMs: slim ? computeTimedExecutionMs(entry.log) : void 0,
|
|
71739
71964
|
createdAt: entry.createdAt,
|
|
@@ -95698,9 +95923,9 @@ async function deleteArchivedTaskEntry(db, id, projectId2) {
|
|
|
95698
95923
|
async function archiveParentTaskWithLineageGate(layer, taskId, entry, options = {}) {
|
|
95699
95924
|
const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
95700
95925
|
return layer.transactionImmediate(async (tx) => {
|
|
95926
|
+
await acquireTaskAdvisoryXactLock(tx, layer.projectId, taskId);
|
|
95927
|
+
const live = await readTaskRowInTransaction(tx, taskId, { includeDeleted: true }, layer.projectId);
|
|
95701
95928
|
if (options.livenessWipLanes) {
|
|
95702
|
-
await acquireTaskAdvisoryXactLock(tx, layer.projectId, taskId);
|
|
95703
|
-
const live = await readTaskRowInTransaction(tx, taskId, void 0, layer.projectId);
|
|
95704
95929
|
const verdict = decideArchiveLiveness({ column: String(live?.column ?? ""), status: live?.status, wipLanes: options.livenessWipLanes });
|
|
95705
95930
|
if (verdict.live) return { archived: false, liveVerdict: verdict };
|
|
95706
95931
|
}
|
|
@@ -95712,7 +95937,8 @@ async function archiveParentTaskWithLineageGate(layer, taskId, entry, options =
|
|
|
95712
95937
|
if (options.removeLineageReferences && options.revalidateAgainst !== void 0) assertLineageCandidatesUnchanged(liveChildIds, options.revalidateAgainst);
|
|
95713
95938
|
const lineageOutcome = options.removeLineageReferences ? await removeLineageReferences(tx, taskId, options.revalidateAgainst ?? liveChildIds, now, layer.projectId, options.promptByChildId, options.evidenceTargetVersionForTest) : { clearedChildIds: [], evidenceVersionByChild: /* @__PURE__ */ new Map(), evidenceUnavailableChildIds: [], evidenceInsertAttempts: 0 };
|
|
95714
95939
|
await options.beforeArchive?.(tx);
|
|
95715
|
-
|
|
95940
|
+
const lockedEntry = Array.isArray(live?.recommendations) ? { ...entry, recommendations: live.recommendations } : entry;
|
|
95941
|
+
await upsertArchivedTaskEntry(tx, lockedEntry, layer.projectId);
|
|
95716
95942
|
await softDeleteTaskRowInTransaction(tx, taskId, now, false, layer.projectId);
|
|
95717
95943
|
return options.revalidateAgainst === void 0 ? { archived: true } : { archived: true, lineageOutcome };
|
|
95718
95944
|
});
|
|
@@ -99376,10 +99602,11 @@ async function linkTaskRecommendationImpl(store, id, recommendationId, createdTa
|
|
|
99376
99602
|
await acquireTaskAdvisoryXactLock(tx, layer.projectId, id);
|
|
99377
99603
|
const row = await readTaskRowInTransaction(tx, id, { includeDeleted: true }, layer.projectId);
|
|
99378
99604
|
if (!row) throw new TaskNotFoundError(id);
|
|
99379
|
-
|
|
99380
|
-
|
|
99381
|
-
|
|
99382
|
-
|
|
99605
|
+
const archivedEntry = row.deletedAt ? await findArchivedTaskEntry(tx, id, layer.projectId) : void 0;
|
|
99606
|
+
if (row.deletedAt && !archivedEntry) throw new TaskDeletedError(id, row.deletedAt);
|
|
99607
|
+
const current = archivedEntry ? store.archiveEntryToTask(archivedEntry, false) : store.rowToTask(store.pgRowToTaskRow(row));
|
|
99608
|
+
if (!archivedEntry && completeColumns && !completeColumns.has(current.column)) {
|
|
99609
|
+
throw new Error("Recommendations are available only on completed or archived tasks");
|
|
99383
99610
|
}
|
|
99384
99611
|
const index2 = current.recommendations?.findIndex((item) => item.id === recommendationId) ?? -1;
|
|
99385
99612
|
if (index2 < 0) throw new Error("Recommendation no longer exists");
|
|
@@ -99387,26 +99614,39 @@ async function linkTaskRecommendationImpl(store, id, recommendationId, createdTa
|
|
|
99387
99614
|
if (recommendation.createdTaskId && recommendation.createdTaskId !== createdTaskId) {
|
|
99388
99615
|
throw new Error("Recommendation is already linked to another task");
|
|
99389
99616
|
}
|
|
99390
|
-
if (recommendation.createdTaskId === createdTaskId) return current;
|
|
99617
|
+
if (recommendation.createdTaskId === createdTaskId) return { task: current, archived: Boolean(archivedEntry) };
|
|
99391
99618
|
const recommendations = current.recommendations.map(
|
|
99392
99619
|
(item) => item.id === recommendationId ? { ...item, createdTaskId } : item
|
|
99393
99620
|
);
|
|
99394
99621
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
99622
|
+
if (archivedEntry) {
|
|
99623
|
+
const updatedEntry = { ...archivedEntry, recommendations, updatedAt };
|
|
99624
|
+
await upsertArchivedTaskEntry(tx, updatedEntry, layer.projectId);
|
|
99625
|
+
await tx.update(project_exports.tasks).set({ recommendations, updatedAt }).where(and(
|
|
99626
|
+
eq(project_exports.tasks.id, id),
|
|
99627
|
+
taskProjectScope(layer),
|
|
99628
|
+
isNotNull(project_exports.tasks.deletedAt)
|
|
99629
|
+
));
|
|
99630
|
+
return { task: store.archiveEntryToTask(updatedEntry, false), archived: true };
|
|
99631
|
+
}
|
|
99395
99632
|
const [updatedRow] = await tx.update(project_exports.tasks).set({ recommendations, updatedAt }).where(and(
|
|
99396
99633
|
eq(project_exports.tasks.id, id),
|
|
99397
99634
|
taskProjectScope(layer),
|
|
99635
|
+
isNull(project_exports.tasks.deletedAt),
|
|
99398
99636
|
...completeColumns ? [inArray(project_exports.tasks.column, [...completeColumns])] : []
|
|
99399
99637
|
)).returning();
|
|
99400
99638
|
if (!updatedRow) {
|
|
99401
|
-
if (completeColumns) throw new Error("Recommendations are available only on completed tasks");
|
|
99639
|
+
if (completeColumns) throw new Error("Recommendations are available only on completed or archived tasks");
|
|
99402
99640
|
throw new TaskNotFoundError(id);
|
|
99403
99641
|
}
|
|
99404
|
-
return store.rowToTask(store.pgRowToTaskRow(updatedRow));
|
|
99642
|
+
return { task: store.rowToTask(store.pgRowToTaskRow(updatedRow)), archived: false };
|
|
99405
99643
|
});
|
|
99406
|
-
|
|
99407
|
-
|
|
99408
|
-
|
|
99409
|
-
|
|
99644
|
+
if (!updated.archived) {
|
|
99645
|
+
await store.writeTaskJsonFile(store.taskDir(id), updated.task);
|
|
99646
|
+
if (store.isWatching) store.taskCache.set(id, { ...updated.task });
|
|
99647
|
+
}
|
|
99648
|
+
store.emitTaskLifecycleEventSafely("task:updated", [updated.task]);
|
|
99649
|
+
return updated.task;
|
|
99410
99650
|
});
|
|
99411
99651
|
}
|
|
99412
99652
|
async function normalizeWorkspaceTaskWorktreeMetadataImpl(store, id) {
|
|
@@ -102980,6 +103220,12 @@ async function taskToArchiveEntryImpl(store, task, archivedAt) {
|
|
|
102980
103220
|
comments: task.comments,
|
|
102981
103221
|
review: task.review,
|
|
102982
103222
|
reviewState: task.reviewState,
|
|
103223
|
+
/*
|
|
103224
|
+
FNXC:ArchivedRecommendations 2026-09-20-17:23:
|
|
103225
|
+
The cold archive is the authoritative terminal snapshot. Preserve recommendation records,
|
|
103226
|
+
including createdTaskId, so archival cannot erase an accepted follow-up link.
|
|
103227
|
+
*/
|
|
103228
|
+
recommendations: task.recommendations,
|
|
102983
103229
|
prompt,
|
|
102984
103230
|
...agentLogFields,
|
|
102985
103231
|
log: [{ timestamp: archivedAt, action: "Task archived" }],
|
|
@@ -108747,27 +108993,36 @@ async function listTaskRecommendationsImpl(store, options) {
|
|
|
108747
108993
|
const limit = typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.min(200, Math.max(1, Math.trunc(rawLimit))) : 50;
|
|
108748
108994
|
const offset = typeof rawOffset === "number" && Number.isFinite(rawOffset) ? Math.max(0, Math.trunc(rawOffset)) : 0;
|
|
108749
108995
|
const columns = [...completeColumns];
|
|
108750
|
-
|
|
108751
|
-
const filter = and(
|
|
108996
|
+
const liveFilter = columns.length === 0 ? void 0 : and(
|
|
108752
108997
|
taskProjectScope(layer),
|
|
108753
108998
|
isNull(project_exports.tasks.deletedAt),
|
|
108754
108999
|
inArray(project_exports.tasks.column, columns),
|
|
108755
109000
|
isNotNull(project_exports.tasks.recommendations),
|
|
108756
109001
|
sql`jsonb_array_length(${project_exports.tasks.recommendations}) > 0`
|
|
108757
109002
|
);
|
|
108758
|
-
const [
|
|
108759
|
-
layer.db.select({
|
|
108760
|
-
layer.db
|
|
109003
|
+
const [liveRows, archivedEntries] = await Promise.all([
|
|
109004
|
+
liveFilter ? layer.db.select({ id: project_exports.tasks.id, title: project_exports.tasks.title, column: project_exports.tasks.column, updatedAt: project_exports.tasks.updatedAt, recommendations: project_exports.tasks.recommendations }).from(project_exports.tasks).where(liveFilter).orderBy(desc(project_exports.tasks.updatedAt), desc(project_exports.tasks.id)) : Promise.resolve([]),
|
|
109005
|
+
listArchivedTasks(layer.db, layer.projectId)
|
|
108761
109006
|
]);
|
|
109007
|
+
const rows = [
|
|
109008
|
+
...liveRows.map((row) => ({
|
|
109009
|
+
id: row.id,
|
|
109010
|
+
title: row.title ?? void 0,
|
|
109011
|
+
column: row.column,
|
|
109012
|
+
updatedAt: row.updatedAt,
|
|
109013
|
+
recommendations: Array.isArray(row.recommendations) ? row.recommendations : []
|
|
109014
|
+
})),
|
|
109015
|
+
...archivedEntries.filter((entry) => Array.isArray(entry.recommendations) && entry.recommendations.length > 0).map((entry) => ({ id: entry.id, title: entry.title, column: "archived", updatedAt: entry.updatedAt, recommendations: entry.recommendations }))
|
|
109016
|
+
].sort((a, b3) => b3.updatedAt.localeCompare(a.updatedAt) || b3.id.localeCompare(a.id));
|
|
109017
|
+
const page = rows.slice(offset, offset + limit);
|
|
108762
109018
|
const items = [];
|
|
108763
|
-
for (const row of
|
|
108764
|
-
if (!Array.isArray(row.recommendations) || row.recommendations.length === 0) continue;
|
|
109019
|
+
for (const row of page) {
|
|
108765
109020
|
for (const recommendation of row.recommendations) {
|
|
108766
|
-
items.push({ taskId: row.id, taskTitle: row.title
|
|
109021
|
+
items.push({ taskId: row.id, taskTitle: row.title, taskColumn: row.column, updatedAt: row.updatedAt, recommendation });
|
|
108767
109022
|
}
|
|
108768
109023
|
}
|
|
108769
|
-
const totalRowCount =
|
|
108770
|
-
const returnedRowCount =
|
|
109024
|
+
const totalRowCount = rows.length;
|
|
109025
|
+
const returnedRowCount = page.length;
|
|
108771
109026
|
return { items, rowOffset: offset, rowLimit: limit, returnedRowCount, totalRowCount, hasMore: offset + returnedRowCount < totalRowCount };
|
|
108772
109027
|
}
|
|
108773
109028
|
var LEGACY_REVIEW_LANES5, LEGACY_ARCHIVE_LANES2;
|
|
@@ -120354,6 +120609,7 @@ var init_bundled_plugin_install = __esm({
|
|
|
120354
120609
|
"fusion-plugin-openclaw-runtime",
|
|
120355
120610
|
"fusion-plugin-paperclip-runtime",
|
|
120356
120611
|
"fusion-plugin-cursor-runtime",
|
|
120612
|
+
"fusion-plugin-antigravity-runtime",
|
|
120357
120613
|
"fusion-plugin-grok-runtime",
|
|
120358
120614
|
"fusion-plugin-claude-runtime",
|
|
120359
120615
|
// FNXC:OmpAcp 2026-07-11-23:35: Oh My Pi ACP runtime available as a staged/bundled install target.
|
|
@@ -143053,6 +143309,7 @@ __export(src_exports, {
|
|
|
143053
143309
|
buildTaskDeleteNoticeIdempotencyKey: () => buildTaskDeleteNoticeIdempotencyKey,
|
|
143054
143310
|
buildTaskExternalBlockClearPatch: () => buildTaskExternalBlockClearPatch,
|
|
143055
143311
|
buildTaskExternalBlockPatch: () => buildTaskExternalBlockPatch,
|
|
143312
|
+
buildTaskExternalBlockReport: () => buildTaskExternalBlockReport,
|
|
143056
143313
|
buildTaskLineageTrailer: () => buildTaskLineageTrailer,
|
|
143057
143314
|
buildTaskLogReadOnlyMessage: () => buildTaskLogReadOnlyMessage,
|
|
143058
143315
|
buildTaskNotFoundMessage: () => buildTaskNotFoundMessage,
|
|
@@ -144595,8 +144852,8 @@ var init_src2 = __esm({
|
|
|
144595
144852
|
});
|
|
144596
144853
|
|
|
144597
144854
|
// ../engine/src/execution/lifecycle-move.ts
|
|
144598
|
-
async function moveTaskToContainedBackwardTarget(store, taskId, reason, options,
|
|
144599
|
-
const column =
|
|
144855
|
+
async function moveTaskToContainedBackwardTarget(store, taskId, reason, options, _liveColumn) {
|
|
144856
|
+
const column = (await store.getTask(taskId)).column;
|
|
144600
144857
|
const revisionReasons = /* @__PURE__ */ new Set([
|
|
144601
144858
|
"plan-review-revise-replan",
|
|
144602
144859
|
"code-review-revise-remediation",
|
|
@@ -154573,8 +154830,8 @@ var require_graceful_fs = __commonJS({
|
|
|
154573
154830
|
fs11.createReadStream = createReadStream;
|
|
154574
154831
|
fs11.createWriteStream = createWriteStream;
|
|
154575
154832
|
var fs$readFile = fs11.readFile;
|
|
154576
|
-
fs11.readFile =
|
|
154577
|
-
function
|
|
154833
|
+
fs11.readFile = readFile55;
|
|
154834
|
+
function readFile55(path6, options, cb) {
|
|
154578
154835
|
if (typeof options === "function")
|
|
154579
154836
|
cb = options, options = null;
|
|
154580
154837
|
return go$readFile(path6, options, cb);
|
|
@@ -155736,6 +155993,27 @@ function createFusionCredentialStore(authStorage, resolvedCredentialInstance) {
|
|
|
155736
155993
|
const normalizedProviderId = providerId === ANTHROPIC_PROVIDER_ID2 ? ANTHROPIC_PROVIDER_ID2 : providerId;
|
|
155737
155994
|
return resolvedCredentialInstance?.providerId === normalizedProviderId ? resolvedCredentialInstance : void 0;
|
|
155738
155995
|
};
|
|
155996
|
+
const readCredential = (providerId) => {
|
|
155997
|
+
const scopedRef = scopedRefFor(providerId);
|
|
155998
|
+
return scopedRef ? authStorage.getInstance(scopedRef) : authStorage.get(providerId);
|
|
155999
|
+
};
|
|
156000
|
+
const modifyCredential = async (providerId, fn) => {
|
|
156001
|
+
const scopedRef = scopedRefFor(providerId);
|
|
156002
|
+
if (!scopedRef) {
|
|
156003
|
+
return authStorage.modify(
|
|
156004
|
+
providerId,
|
|
156005
|
+
async (current) => fn(current)
|
|
156006
|
+
);
|
|
156007
|
+
}
|
|
156008
|
+
const next = await fn(authStorage.getInstance(scopedRef));
|
|
156009
|
+
if (next) await authStorage.setInstance(scopedRef, next);
|
|
156010
|
+
return next;
|
|
156011
|
+
};
|
|
156012
|
+
const deleteCredential = async (providerId) => {
|
|
156013
|
+
const scopedRef = scopedRefFor(providerId);
|
|
156014
|
+
if (scopedRef) await authStorage.removeInstance(scopedRef);
|
|
156015
|
+
else await authStorage.remove(providerId);
|
|
156016
|
+
};
|
|
155739
156017
|
return {
|
|
155740
156018
|
/*
|
|
155741
156019
|
FNXC:ProviderAuth 2026-07-17-06:30:
|
|
@@ -155747,23 +156025,36 @@ function createFusionCredentialStore(authStorage, resolvedCredentialInstance) {
|
|
|
155747
156025
|
const token = await authStorage.getApiKey(ANTHROPIC_PROVIDER_ID2, scopedRef);
|
|
155748
156026
|
return token ? { type: "api_key", key: token } : void 0;
|
|
155749
156027
|
}
|
|
155750
|
-
|
|
156028
|
+
if (providerId === META_PROVIDER_ID) {
|
|
156029
|
+
const rawCredential = readCredential(META_PROVIDER_ID);
|
|
156030
|
+
if (rawCredential?.type === "api_key") return rawCredential;
|
|
156031
|
+
const subscriptionCredential = readCredential(META_SUBSCRIPTION_PROVIDER_ID);
|
|
156032
|
+
return subscriptionCredential?.type === "oauth" ? subscriptionCredential : rawCredential;
|
|
156033
|
+
}
|
|
156034
|
+
return readCredential(providerId);
|
|
155751
156035
|
},
|
|
155752
156036
|
list: async () => authStorage.list().flatMap((providerId) => {
|
|
155753
156037
|
const credential = scopedRefFor(providerId) ? authStorage.getInstance(scopedRefFor(providerId)) : authStorage.get(providerId);
|
|
155754
156038
|
return credential?.type === "api_key" || credential?.type === "oauth" ? [{ providerId, type: credential.type }] : [];
|
|
155755
156039
|
}),
|
|
155756
156040
|
modify: async (providerId, fn) => {
|
|
155757
|
-
|
|
155758
|
-
|
|
155759
|
-
|
|
155760
|
-
|
|
155761
|
-
|
|
156041
|
+
if (providerId === META_PROVIDER_ID) {
|
|
156042
|
+
const rawCredential = readCredential(META_PROVIDER_ID);
|
|
156043
|
+
if (rawCredential?.type !== "api_key" && readCredential(META_SUBSCRIPTION_PROVIDER_ID)?.type === "oauth") {
|
|
156044
|
+
return modifyCredential(META_SUBSCRIPTION_PROVIDER_ID, fn);
|
|
156045
|
+
}
|
|
156046
|
+
}
|
|
156047
|
+
return modifyCredential(providerId, fn);
|
|
155762
156048
|
},
|
|
155763
156049
|
delete: async (providerId) => {
|
|
155764
|
-
|
|
155765
|
-
|
|
155766
|
-
|
|
156050
|
+
if (providerId === META_PROVIDER_ID) {
|
|
156051
|
+
const rawCredential = readCredential(META_PROVIDER_ID);
|
|
156052
|
+
if (rawCredential?.type !== "api_key" && readCredential(META_SUBSCRIPTION_PROVIDER_ID)?.type === "oauth") {
|
|
156053
|
+
await deleteCredential(META_SUBSCRIPTION_PROVIDER_ID);
|
|
156054
|
+
return;
|
|
156055
|
+
}
|
|
156056
|
+
}
|
|
156057
|
+
await deleteCredential(providerId);
|
|
155767
156058
|
}
|
|
155768
156059
|
};
|
|
155769
156060
|
}
|
|
@@ -156369,7 +156660,7 @@ function createFusionAuthStorage() {
|
|
|
156369
156660
|
}
|
|
156370
156661
|
});
|
|
156371
156662
|
}
|
|
156372
|
-
var import_proper_lockfile, AUTH_LOCK_OPTIONS, authWriteQueues, FusionFileAuthStorage, CredentialInstanceResolutionError, OAUTH_REFRESH_BUFFER_MS, ANTHROPIC_PROVIDER_ID2, OAUTH_REFRESH_FAILURE_COOLDOWN_MS;
|
|
156663
|
+
var import_proper_lockfile, AUTH_LOCK_OPTIONS, authWriteQueues, FusionFileAuthStorage, CredentialInstanceResolutionError, OAUTH_REFRESH_BUFFER_MS, ANTHROPIC_PROVIDER_ID2, META_PROVIDER_ID, META_SUBSCRIPTION_PROVIDER_ID, OAUTH_REFRESH_FAILURE_COOLDOWN_MS;
|
|
156373
156664
|
var init_auth_storage = __esm({
|
|
156374
156665
|
"../engine/src/auth/auth-storage.ts"() {
|
|
156375
156666
|
"use strict";
|
|
@@ -156670,8 +156961,9 @@ var init_auth_storage = __esm({
|
|
|
156670
156961
|
return { result: existing, changed: false };
|
|
156671
156962
|
});
|
|
156672
156963
|
}
|
|
156964
|
+
// FNXC:ProviderAuth 2026-09-20-16:20: Pi 0.86.1 registers Meta's Muse OAuth login; preserve the raw runtime id for ModelRuntime.login.
|
|
156673
156965
|
getOAuthProviders() {
|
|
156674
|
-
return [{ id: "anthropic", name: "Anthropic" }, { id: "openai-codex", name: "OpenAI Codex" }, { id: "github-copilot", name: "GitHub Copilot" }];
|
|
156966
|
+
return [{ id: "anthropic", name: "Anthropic" }, { id: "openai-codex", name: "OpenAI Codex" }, { id: "github-copilot", name: "GitHub Copilot" }, { id: "meta", name: "Meta (Muse)" }];
|
|
156675
156967
|
}
|
|
156676
156968
|
setModelRuntime(modelRuntime) {
|
|
156677
156969
|
this.modelRuntime = modelRuntime;
|
|
@@ -156713,6 +157005,8 @@ var init_auth_storage = __esm({
|
|
|
156713
157005
|
};
|
|
156714
157006
|
OAUTH_REFRESH_BUFFER_MS = 5 * 6e4;
|
|
156715
157007
|
ANTHROPIC_PROVIDER_ID2 = "anthropic";
|
|
157008
|
+
META_PROVIDER_ID = "meta";
|
|
157009
|
+
META_SUBSCRIPTION_PROVIDER_ID = "meta-subscription";
|
|
156716
157010
|
OAUTH_REFRESH_FAILURE_COOLDOWN_MS = 3e4;
|
|
156717
157011
|
}
|
|
156718
157012
|
});
|
|
@@ -157540,7 +157834,7 @@ function derivePreviousText(partial2, contentIndex, delta, kind) {
|
|
|
157540
157834
|
const content = partial2?.content;
|
|
157541
157835
|
const block = Array.isArray(content) && Number.isInteger(contentIndex) && contentIndex >= 0 ? content[contentIndex] : void 0;
|
|
157542
157836
|
const accumulated = getContentText(block, kind);
|
|
157543
|
-
return accumulated && delta && accumulated.endsWith(delta) ? accumulated.slice(0, Math.max(0, accumulated.length - delta.length)) :
|
|
157837
|
+
return accumulated && delta && accumulated.endsWith(delta) ? accumulated.slice(0, Math.max(0, accumulated.length - delta.length)) : "";
|
|
157544
157838
|
}
|
|
157545
157839
|
function identityFor(partial2, contentIndex) {
|
|
157546
157840
|
const objectPartial = partial2 !== null && typeof partial2 === "object" ? partial2 : void 0;
|
|
@@ -157591,17 +157885,10 @@ function indexOf(value) {
|
|
|
157591
157885
|
if (value === void 0) return 0;
|
|
157592
157886
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
157593
157887
|
}
|
|
157594
|
-
function blockText(partial2, index2, kind) {
|
|
157595
|
-
const content = record(partial2)?.content;
|
|
157596
|
-
if (!Array.isArray(content) || index2 === void 0) return "";
|
|
157597
|
-
const block = record(content[index2]);
|
|
157598
|
-
if (block?.type !== kind) return "";
|
|
157599
|
-
const value = block[kind === "text" ? "text" : "thinking"];
|
|
157600
|
-
return typeof value === "string" ? value : "";
|
|
157601
|
-
}
|
|
157602
157888
|
function createAssistantStreamCapture(sinks) {
|
|
157603
157889
|
const normalizer = createStreamingDeltaNormalizer();
|
|
157604
157890
|
const emitted = { text: /* @__PURE__ */ new Map(), thinking: /* @__PURE__ */ new Map() };
|
|
157891
|
+
const seen = { text: /* @__PURE__ */ new Map(), thinking: /* @__PURE__ */ new Map() };
|
|
157605
157892
|
let lastPartial;
|
|
157606
157893
|
let lastTextPartial;
|
|
157607
157894
|
let lastTextIndex;
|
|
@@ -157609,6 +157896,8 @@ function createAssistantStreamCapture(sinks) {
|
|
|
157609
157896
|
const reset2 = () => {
|
|
157610
157897
|
emitted.text.clear();
|
|
157611
157898
|
emitted.thinking.clear();
|
|
157899
|
+
seen.text.clear();
|
|
157900
|
+
seen.thinking.clear();
|
|
157612
157901
|
normalizer.noteBoundary("text");
|
|
157613
157902
|
normalizer.noteBoundary("thinking");
|
|
157614
157903
|
lastPartial = void 0;
|
|
@@ -157664,12 +157953,17 @@ function createAssistantStreamCapture(sinks) {
|
|
|
157664
157953
|
if (!kind) return;
|
|
157665
157954
|
if (type === `${kind}_delta`) {
|
|
157666
157955
|
if (typeof update.delta !== "string" || index2 === void 0) return;
|
|
157667
|
-
const
|
|
157668
|
-
|
|
157669
|
-
const
|
|
157670
|
-
|
|
157956
|
+
const seenBefore = seen[kind].get(index2) ?? 0;
|
|
157957
|
+
const seenAfter = seenBefore + update.delta.length;
|
|
157958
|
+
const emittedBefore = emitted[kind].get(index2) ?? 0;
|
|
157959
|
+
const sourceSegment = update.delta.slice(Math.max(0, emittedBefore - seenBefore));
|
|
157960
|
+
if (sourceSegment) {
|
|
157961
|
+
const delta = normalizer.normalize(partial2, index2, sourceSegment, kind);
|
|
157962
|
+
emit(kind, delta, partial2, index2);
|
|
157963
|
+
}
|
|
157964
|
+
seen[kind].set(index2, seenAfter);
|
|
157965
|
+
emitted[kind].set(index2, Math.max(emittedBefore, seenAfter));
|
|
157671
157966
|
} else if (type === `${kind}_start`) {
|
|
157672
|
-
flush(kind, blockText(partial2, index2, kind), partial2, index2);
|
|
157673
157967
|
} else if (type === `${kind}_end`) {
|
|
157674
157968
|
flush(kind, typeof update.content === "string" ? update.content : "", partial2, index2);
|
|
157675
157969
|
}
|
|
@@ -158218,6 +158512,9 @@ function buildMissingClaudeRuntimeError() {
|
|
|
158218
158512
|
function buildMissingCursorRuntimeError() {
|
|
158219
158513
|
return unavailable("Cursor CLI", "Install and enable the Cursor runtime plugin (fusion-plugin-cursor-runtime), install `cursor-agent`, and authenticate with `cursor-agent login`.");
|
|
158220
158514
|
}
|
|
158515
|
+
function buildMissingAntigravityRuntimeError() {
|
|
158516
|
+
return unavailable("Google Antigravity CLI", "Install and enable the Antigravity runtime plugin (fusion-plugin-antigravity-runtime), install `agy`, and complete its vendor login flow.");
|
|
158517
|
+
}
|
|
158221
158518
|
function getCliProviderRouting(providerId) {
|
|
158222
158519
|
return CLI_PROVIDER_ROUTING_CENSUS.find((entry) => entry.providerId === providerId);
|
|
158223
158520
|
}
|
|
@@ -158302,7 +158599,9 @@ var init_cli_provider_routing = __esm({
|
|
|
158302
158599
|
{ providerId: "grok-cli", classification: "runtime-routed", runtimeId: "grok", autoDerive: "fail-fast", guardNotApplicable: "pinned-pi-fallback", onExplicitHint: "defer-to-resolve-runtime", fallbackPolicy: "defer-to-runtime", missingRuntimeError: buildMissingGrokRuntimeError, rationale: "Visible-key, fallback-only, and explicit-hint paths intentionally preserve the shipped direct xAI/pi fallback." },
|
|
158303
158600
|
{ providerId: "hermes", classification: "runtime-routed", runtimeId: "hermes", autoDerive: "fail-fast", guardNotApplicable: "pinned-pi-fallback", onExplicitHint: "assert-available", fallbackPolicy: "drop-with-warning", missingRuntimeError: buildMissingHermesRuntimeError, rationale: "Fallback-only Hermes cannot be resolved by a healthy primary pi runtime." },
|
|
158304
158601
|
{ providerId: "claude-cli", classification: "runtime-routed", runtimeId: "claude", autoDerive: "fail-fast", guardNotApplicable: "pinned-pi-fallback", onExplicitHint: "assert-available", fallbackPolicy: "drop-with-warning", missingRuntimeError: buildMissingClaudeRuntimeError, rationale: "Fallback-only Claude CLI cannot be resolved by a healthy primary pi runtime." },
|
|
158305
|
-
{ providerId: "cursor-cli", classification: "runtime-routed", runtimeId: "cursor", autoDerive: "fail-fast", guardNotApplicable: "pinned-pi-fallback", onExplicitHint: "assert-available", fallbackPolicy: "defer-cross-runtime", missingRuntimeError: buildMissingCursorRuntimeError, rationale: "Cursor fallback is withheld from a healthy foreign runtime and armed for a single prompt-time cross-runtime swap." }
|
|
158602
|
+
{ providerId: "cursor-cli", classification: "runtime-routed", runtimeId: "cursor", autoDerive: "fail-fast", guardNotApplicable: "pinned-pi-fallback", onExplicitHint: "assert-available", fallbackPolicy: "defer-cross-runtime", missingRuntimeError: buildMissingCursorRuntimeError, rationale: "Cursor fallback is withheld from a healthy foreign runtime and armed for a single prompt-time cross-runtime swap." },
|
|
158603
|
+
// FNXC:AntigravityProvider 2026-09-20-18:32: Agy has no proven safe cross-runtime context transfer, so fallback-only selection must retain the healthy primary runtime.
|
|
158604
|
+
{ providerId: "antigravity-cli", classification: "runtime-routed", runtimeId: "antigravity", autoDerive: "fail-fast", guardNotApplicable: "pinned-pi-fallback", onExplicitHint: "assert-available", fallbackPolicy: "drop-with-warning", missingRuntimeError: buildMissingAntigravityRuntimeError, rationale: "Non-ACP agy fallback is dropped rather than silently replacing the active primary." }
|
|
158306
158605
|
];
|
|
158307
158606
|
}
|
|
158308
158607
|
});
|
|
@@ -187682,8 +187981,8 @@ async function runWorkflowMergeAttemptNode(deps, ctx, task) {
|
|
|
187682
187981
|
};
|
|
187683
187982
|
}
|
|
187684
187983
|
function classifyMergePrimitiveResult(data, value, primitiveOutcome) {
|
|
187685
|
-
if (value === MERGE_BOUNDARY_UNPROVEN_VALUE) {
|
|
187686
|
-
return { outcome: "failure", value
|
|
187984
|
+
if (value === MERGE_BOUNDARY_RECOVERY_VALUE || value === MERGE_BOUNDARY_UNPROVEN_VALUE) {
|
|
187985
|
+
return { outcome: "failure", value };
|
|
187687
187986
|
}
|
|
187688
187987
|
if (data?.status === "merged") {
|
|
187689
187988
|
return { outcome: "success", value: data.noOp ? "already-landed" : "merged" };
|
|
@@ -187727,11 +188026,12 @@ function classifyMergeFailure(reason) {
|
|
|
187727
188026
|
}
|
|
187728
188027
|
return { outcome: "failure", value: "merge-failed" };
|
|
187729
188028
|
}
|
|
187730
|
-
var MERGE_BOUNDARY_UNPROVEN_VALUE, PRESERVED_MERGE_FAILURE_REASONS;
|
|
188029
|
+
var MERGE_BOUNDARY_UNPROVEN_VALUE, MERGE_BOUNDARY_RECOVERY_VALUE, PRESERVED_MERGE_FAILURE_REASONS;
|
|
187731
188030
|
var init_workflow_merge_nodes = __esm({
|
|
187732
188031
|
"../engine/src/workflows/workflow-merge-nodes.ts"() {
|
|
187733
188032
|
"use strict";
|
|
187734
188033
|
MERGE_BOUNDARY_UNPROVEN_VALUE = "merge-boundary-unproven";
|
|
188034
|
+
MERGE_BOUNDARY_RECOVERY_VALUE = "merge-boundary-evidence-recovery";
|
|
187735
188035
|
PRESERVED_MERGE_FAILURE_REASONS = /* @__PURE__ */ new Set(["implementation-incomplete", "merge-unavailable", "workspace-review-required"]);
|
|
187736
188036
|
}
|
|
187737
188037
|
});
|
|
@@ -194881,9 +195181,9 @@ async function isRememberedWorkspaceWorktreeLive(repoRootDir, worktreePath) {
|
|
|
194881
195181
|
async function acquireWorkspaceRepoWorktree(opts) {
|
|
194882
195182
|
const { repoRelPath, workspaceRootDir, task, store, settings, logger: logger3, secretsStore, audit, runContext, runConfiguredCommand: runConfiguredCommand2, ensureDependencyReadiness, taskEnv, validateTaskBeforeCreate } = opts;
|
|
194883
195183
|
const registry3 = opts.registry ?? activeSessionRegistry;
|
|
194884
|
-
const { join:
|
|
195184
|
+
const { join: join104 } = await import("node:path");
|
|
194885
195185
|
assertInRootRepoRelPath(repoRelPath);
|
|
194886
|
-
const repoAbsPath =
|
|
195186
|
+
const repoAbsPath = join104(workspaceRootDir, repoRelPath);
|
|
194887
195187
|
let durableAcquireLease;
|
|
194888
195188
|
let acquireLeaseLost = false;
|
|
194889
195189
|
let acquireLeaseRenewalInFlight;
|
|
@@ -196589,6 +196889,15 @@ function shouldWithholdWedgeAlertForAutoRecovery(task, options) {
|
|
|
196589
196889
|
}
|
|
196590
196890
|
function describeTaskWedge(task) {
|
|
196591
196891
|
if (isTaskProgressing(task)) return null;
|
|
196892
|
+
if (task.status === "blocked" && task.paused === true && task.pausedReason === "external-block" && task.userPaused !== true && task.autoMerge !== false && task.externalBlock) {
|
|
196893
|
+
const report2 = buildTaskExternalBlockReport(task.externalBlock, task.externalBlock.report);
|
|
196894
|
+
return {
|
|
196895
|
+
reasonKey: `external-block:${task.externalBlock.origin}:${task.externalBlock.code}`,
|
|
196896
|
+
reason: report2.stopReason,
|
|
196897
|
+
action: report2.unblockCondition,
|
|
196898
|
+
externalBlockReport: report2
|
|
196899
|
+
};
|
|
196900
|
+
}
|
|
196592
196901
|
const error2 = task.error ?? "";
|
|
196593
196902
|
const hasPauseProof = task.paused === true || task.status === "paused";
|
|
196594
196903
|
if (hasPauseProof && task.pausedReason === "completed-blocked") {
|
|
@@ -196713,7 +197022,56 @@ var init_task_wedge_notification = __esm({
|
|
|
196713
197022
|
}
|
|
196714
197023
|
});
|
|
196715
197024
|
|
|
197025
|
+
// ../engine/src/notification/mailbox-delivery.ts
|
|
197026
|
+
async function deliverMailboxMessageOnce(store, input, idempotencyKey, timeoutMs = MAILBOX_DELIVERY_TIMEOUT_MS) {
|
|
197027
|
+
if (!store?.sendMessageOnce) return "unavailable";
|
|
197028
|
+
let timer2;
|
|
197029
|
+
try {
|
|
197030
|
+
const delivery = Promise.resolve().then(() => store.sendMessageOnce(input, idempotencyKey));
|
|
197031
|
+
void delivery.catch(() => void 0);
|
|
197032
|
+
const timedOut = new Promise((resolve54) => {
|
|
197033
|
+
timer2 = setTimeout(() => resolve54("timeout"), Math.max(1, timeoutMs));
|
|
197034
|
+
timer2.unref?.();
|
|
197035
|
+
});
|
|
197036
|
+
const outcome = await Promise.race([delivery.then(() => "delivered", () => "unavailable"), timedOut]);
|
|
197037
|
+
return outcome === "delivered" ? "delivered" : "unavailable";
|
|
197038
|
+
} catch {
|
|
197039
|
+
return "unavailable";
|
|
197040
|
+
} finally {
|
|
197041
|
+
if (timer2) clearTimeout(timer2);
|
|
197042
|
+
}
|
|
197043
|
+
}
|
|
197044
|
+
var MAILBOX_DELIVERY_TIMEOUT_MS;
|
|
197045
|
+
var init_mailbox_delivery = __esm({
|
|
197046
|
+
"../engine/src/notification/mailbox-delivery.ts"() {
|
|
197047
|
+
"use strict";
|
|
197048
|
+
MAILBOX_DELIVERY_TIMEOUT_MS = 5e3;
|
|
197049
|
+
}
|
|
197050
|
+
});
|
|
197051
|
+
|
|
196716
197052
|
// ../engine/src/notification/notification-service.ts
|
|
197053
|
+
function formatWedgeMailboxContent(task, descriptor, link) {
|
|
197054
|
+
if (descriptor.externalBlockReport) {
|
|
197055
|
+
const report2 = descriptor.externalBlockReport;
|
|
197056
|
+
return [
|
|
197057
|
+
`**${formatTaskIdentifier(task)} is blocked and needs operator action**`,
|
|
197058
|
+
"",
|
|
197059
|
+
`**Verified:** ${report2.verifiedCondition}`,
|
|
197060
|
+
`**Why work stopped:** ${report2.stopReason}`,
|
|
197061
|
+
`**Not implemented:** ${report2.unimplementedWork}`,
|
|
197062
|
+
`**Unblock:** ${report2.unblockCondition}`,
|
|
197063
|
+
...link ? ["", `[Open ${task.id}](${link})`] : []
|
|
197064
|
+
].join("\n");
|
|
197065
|
+
}
|
|
197066
|
+
return [
|
|
197067
|
+
`**${formatTaskIdentifier(task)} needs operator action**`,
|
|
197068
|
+
"",
|
|
197069
|
+
descriptor.reason,
|
|
197070
|
+
...descriptor.gate ? [`Gate: \`${descriptor.gate}\`.`] : [],
|
|
197071
|
+
`Recommended action: ${descriptor.action}`,
|
|
197072
|
+
...link ? ["", `[Open ${task.id}](${link})`] : []
|
|
197073
|
+
].join("\n");
|
|
197074
|
+
}
|
|
196717
197075
|
var NotificationService;
|
|
196718
197076
|
var init_notification_service = __esm({
|
|
196719
197077
|
"../engine/src/notification/notification-service.ts"() {
|
|
@@ -196724,6 +197082,7 @@ var init_notification_service = __esm({
|
|
|
196724
197082
|
init_ntfy_provider();
|
|
196725
197083
|
init_webhook_provider();
|
|
196726
197084
|
init_task_wedge_notification();
|
|
197085
|
+
init_mailbox_delivery();
|
|
196727
197086
|
NotificationService = class {
|
|
196728
197087
|
constructor(store, options = {}) {
|
|
196729
197088
|
this.store = store;
|
|
@@ -197171,14 +197530,7 @@ var init_notification_service = __esm({
|
|
|
197171
197530
|
if (!episode) return "suppressed";
|
|
197172
197531
|
}
|
|
197173
197532
|
const link = buildNtfyClickUrl({ dashboardHost: this.dashboardHost, projectId: this.options.projectId, taskId: task.id });
|
|
197174
|
-
const content =
|
|
197175
|
-
`**${formatTaskIdentifier(task)} needs operator action**`,
|
|
197176
|
-
"",
|
|
197177
|
-
descriptor.reason,
|
|
197178
|
-
...descriptor.gate ? [`Gate: \`${descriptor.gate}\`.`] : [],
|
|
197179
|
-
`Recommended action: ${descriptor.action}`,
|
|
197180
|
-
...link ? ["", `[Open ${task.id}](${link})`] : []
|
|
197181
|
-
].join("\n");
|
|
197533
|
+
const content = formatWedgeMailboxContent(task, descriptor, link);
|
|
197182
197534
|
const payload = {
|
|
197183
197535
|
taskId: task.id,
|
|
197184
197536
|
taskTitle: task.title,
|
|
@@ -197188,19 +197540,15 @@ var init_notification_service = __esm({
|
|
|
197188
197540
|
metadata: { wedgeReason: descriptor.reasonKey, reason: descriptor.reason, action: descriptor.action, ...descriptor.gate ? { gate: descriptor.gate } : {}, notificationDedupeKey: `task-wedge:${episode}` }
|
|
197189
197541
|
};
|
|
197190
197542
|
void this.dispatch("task-wedged", payload);
|
|
197191
|
-
|
|
197192
|
-
|
|
197193
|
-
|
|
197194
|
-
|
|
197195
|
-
|
|
197196
|
-
|
|
197197
|
-
|
|
197198
|
-
|
|
197199
|
-
|
|
197200
|
-
}, `task-wedge:${episode}`);
|
|
197201
|
-
} catch (error2) {
|
|
197202
|
-
schedulerLog.log(`[notify] ${task.id} wedge mailbox message failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
197203
|
-
}
|
|
197543
|
+
await deliverMailboxMessageOnce(this.options.messageStore, {
|
|
197544
|
+
fromId: "system",
|
|
197545
|
+
fromType: "system",
|
|
197546
|
+
toId: DASHBOARD_USER_ID,
|
|
197547
|
+
toType: "user",
|
|
197548
|
+
type: "system",
|
|
197549
|
+
content,
|
|
197550
|
+
metadata: { taskId: task.id, kind: "task-wedge", wedgeReason: descriptor.reasonKey, ...descriptor.gate ? { gate: descriptor.gate } : {} }
|
|
197551
|
+
}, `task-wedge:${episode}`);
|
|
197204
197552
|
if (isAutoRecoveryEscalationDispatch && classification.action === "notify") {
|
|
197205
197553
|
try {
|
|
197206
197554
|
await this.store.markTerminalFailureAutoRecoveryEscalationDelivered?.(task.id, {
|
|
@@ -197338,14 +197686,8 @@ var init_notification_service = __esm({
|
|
|
197338
197686
|
}
|
|
197339
197687
|
const payload = this.createTaskPayload(task, "task-wedged", { wedgeReason: descriptor.reasonKey, reason: descriptor.reason, action: descriptor.action, ...descriptor.gate ? { gate: descriptor.gate } : {}, notificationDedupeKey: `task-wedge:${claim.episodeId}` });
|
|
197340
197688
|
void this.dispatch("task-wedged", payload);
|
|
197341
|
-
const content =
|
|
197342
|
-
|
|
197343
|
-
${descriptor.reason}
|
|
197344
|
-
Recommended action: ${descriptor.action}`;
|
|
197345
|
-
try {
|
|
197346
|
-
await this.options.messageStore?.sendMessageOnce?.({ fromId: "system", fromType: "system", toId: DASHBOARD_USER_ID, toType: "user", type: "system", content, metadata: { taskId, kind: "task-wedge", wedgeReason: descriptor.reasonKey } }, `task-wedge:${claim.episodeId}`);
|
|
197347
|
-
} catch {
|
|
197348
|
-
}
|
|
197689
|
+
const content = formatWedgeMailboxContent(task, descriptor, buildNtfyClickUrl({ dashboardHost: this.dashboardHost, projectId: this.options.projectId, taskId }));
|
|
197690
|
+
await deliverMailboxMessageOnce(this.options.messageStore, { fromId: "system", fromType: "system", toId: DASHBOARD_USER_ID, toType: "user", type: "system", content, metadata: { taskId, kind: "task-wedge", wedgeReason: descriptor.reasonKey } }, `task-wedge:${claim.episodeId}`);
|
|
197349
197691
|
return { outcome: "delivered", reasonKey: descriptor.reasonKey };
|
|
197350
197692
|
} catch (error2) {
|
|
197351
197693
|
schedulerLog.debug(`[notify] ${taskId} pending wedge completion failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
@@ -211450,7 +211792,7 @@ var agent_reflection_exports = {};
|
|
|
211450
211792
|
__export(agent_reflection_exports, {
|
|
211451
211793
|
AgentReflectionService: () => AgentReflectionService
|
|
211452
211794
|
});
|
|
211453
|
-
import { readFile as
|
|
211795
|
+
import { readFile as readFile54 } from "node:fs/promises";
|
|
211454
211796
|
import { isAbsolute as isAbsolute26, resolve as resolve52 } from "node:path";
|
|
211455
211797
|
var reflectionLog, REFLECTION_SYSTEM_PROMPT, DEFAULT_OUTCOME_LIMIT, AgentReflectionService;
|
|
211456
211798
|
var init_agent_reflection = __esm({
|
|
@@ -212038,7 +212380,7 @@ Rules:
|
|
|
212038
212380
|
if (agent.instructionsPath?.trim()) {
|
|
212039
212381
|
const resolvedPath = isAbsolute26(agent.instructionsPath) ? agent.instructionsPath : resolve52(this.rootDir, agent.instructionsPath);
|
|
212040
212382
|
try {
|
|
212041
|
-
const content = await
|
|
212383
|
+
const content = await readFile54(resolvedPath, "utf-8");
|
|
212042
212384
|
if (content.trim()) {
|
|
212043
212385
|
pieces.push(content.trim());
|
|
212044
212386
|
}
|
|
@@ -213025,16 +213367,16 @@ async function persistBranchState(persistence, state) {
|
|
|
213025
213367
|
);
|
|
213026
213368
|
}
|
|
213027
213369
|
}
|
|
213028
|
-
function resolveJoinConfig(
|
|
213029
|
-
const rawMode =
|
|
213370
|
+
function resolveJoinConfig(join104) {
|
|
213371
|
+
const rawMode = join104.config?.mode;
|
|
213030
213372
|
let mode = "all";
|
|
213031
213373
|
if (rawMode === "all" || rawMode === "any") mode = rawMode;
|
|
213032
213374
|
else if (rawMode && typeof rawMode === "object" && "quorum" in rawMode) {
|
|
213033
213375
|
const n = rawMode.quorum;
|
|
213034
213376
|
if (typeof n === "number" && Number.isInteger(n) && n > 0) mode = { quorum: n };
|
|
213035
|
-
else throw new WorkflowIrError(`join '${
|
|
213377
|
+
else throw new WorkflowIrError(`join '${join104.id}' quorum must be a positive integer`);
|
|
213036
213378
|
}
|
|
213037
|
-
const rawFail =
|
|
213379
|
+
const rawFail = join104.config?.onBranchFailure;
|
|
213038
213380
|
const onBranchFailure = rawFail === "collect" ? "collect" : "fail-fast";
|
|
213039
213381
|
return { mode, onBranchFailure };
|
|
213040
213382
|
}
|
|
@@ -213050,9 +213392,9 @@ async function runSplitJoin(split, env) {
|
|
|
213050
213392
|
if (branchEdges.length === 0) {
|
|
213051
213393
|
throw new WorkflowIrError(`split '${split.id}' has no traversable branches`);
|
|
213052
213394
|
}
|
|
213053
|
-
const
|
|
213054
|
-
if (!
|
|
213055
|
-
const joinConfig = resolveJoinConfig(env.nodeMap.get(
|
|
213395
|
+
const join104 = findMatchingJoin(branchEdges[0].to, env);
|
|
213396
|
+
if (!join104) throw new WorkflowIrError(`split '${split.id}' has no reachable matching join`);
|
|
213397
|
+
const joinConfig = resolveJoinConfig(env.nodeMap.get(join104));
|
|
213056
213398
|
const controller = new AbortController();
|
|
213057
213399
|
const branchCount = branchEdges.length;
|
|
213058
213400
|
const required2 = requiredCompletions(joinConfig.mode, branchCount);
|
|
@@ -213088,7 +213430,7 @@ async function runSplitJoin(split, env) {
|
|
|
213088
213430
|
};
|
|
213089
213431
|
const branchPromises = branchEdges.map((edge) => {
|
|
213090
213432
|
const branchId = edge.to;
|
|
213091
|
-
return walkBranch(branchId,
|
|
213433
|
+
return walkBranch(branchId, join104, env, controller.signal, visitedNodeIds).then((result) => {
|
|
213092
213434
|
branchOutcomes.push({ branchId, outcome: result.outcome, nodeId: result.lastNodeId });
|
|
213093
213435
|
if (result.outcome === "success") succeeded += 1;
|
|
213094
213436
|
else failed += 1;
|
|
@@ -213106,7 +213448,7 @@ async function runSplitJoin(split, env) {
|
|
|
213106
213448
|
});
|
|
213107
213449
|
const outcome = await joinReached;
|
|
213108
213450
|
await Promise.allSettled(branchPromises);
|
|
213109
|
-
return { joinNodeId:
|
|
213451
|
+
return { joinNodeId: join104, outcome, branchOutcomes, visitedNodeIds };
|
|
213110
213452
|
}
|
|
213111
213453
|
async function walkBranch(startNodeId, joinId, env, signal, visitedNodeIds) {
|
|
213112
213454
|
let currentId = startNodeId;
|
|
@@ -222074,8 +222416,11 @@ async function rerouteSingularStaleContentToReview(store, task, options) {
|
|
|
222074
222416
|
|
|
222075
222417
|
// ../engine/src/merge/pre-merge-gate-reseed.ts
|
|
222076
222418
|
init_src2();
|
|
222419
|
+
function isRecoverableUnrunGatePark(task) {
|
|
222420
|
+
return task.status === "failed" && !task.userPaused && !task.deletedAt && task.autoMerge !== false && (!task.paused || task.pausedReason === IN_REVIEW_STALL_DEADLOCK_PAUSE_REASON) && typeof task.error === "string" && (task.error.endsWith(PRE_MERGE_STEPS_NOT_RUN_BLOCKER) || !task.workflowIrPin && task.error.startsWith("Workflow drift park:") && task.error.includes("Stale IR pin cleared"));
|
|
222421
|
+
}
|
|
222077
222422
|
async function rerouteUnrunPreMergeGateToReview(store, task, options) {
|
|
222078
|
-
const { mergeContent, requiredPreMergeStepIds } = options;
|
|
222423
|
+
const { mergeContent, requiredPreMergeStepIds, expectedWorkflowSelection } = options;
|
|
222079
222424
|
if (mergeContent.kind !== "singular" || task.workspaceWorktrees !== void 0) return { rerouted: false, reason: "not-singular" };
|
|
222080
222425
|
if (task.paused || task.userPaused || task.deletedAt || task.autoMerge === false) return { rerouted: false, reason: "operator-held" };
|
|
222081
222426
|
if (requiredPreMergeStepIds.size === 0) return { rerouted: false, reason: "no-unrun-gate" };
|
|
@@ -222095,9 +222440,16 @@ async function rerouteUnrunPreMergeGateToReview(store, task, options) {
|
|
|
222095
222440
|
continuationSequence: items.length,
|
|
222096
222441
|
sourceColumn: task.column,
|
|
222097
222442
|
targetColumn: node.column ?? task.column,
|
|
222098
|
-
irHash: computeWorkflowIrPin(ir2, node.id).irHash
|
|
222443
|
+
irHash: computeWorkflowIrPin(ir2, node.id).irHash,
|
|
222444
|
+
expectedWorkflowSelection
|
|
222099
222445
|
});
|
|
222100
|
-
|
|
222446
|
+
if (result.seeded) return { rerouted: true, reason: "seeded", nodeId: node.id, workflowStepId: node.id };
|
|
222447
|
+
return {
|
|
222448
|
+
rerouted: false,
|
|
222449
|
+
reason: result.reason === "workflow-selection-changed" ? "workflow-selection-changed" : "active-continuation",
|
|
222450
|
+
nodeId: node.id,
|
|
222451
|
+
workflowStepId: node.id
|
|
222452
|
+
};
|
|
222101
222453
|
}
|
|
222102
222454
|
|
|
222103
222455
|
// ../engine/src/healing/auto-recovery.ts
|
|
@@ -222571,6 +222923,469 @@ async function terminalizeEmptyReviewContent(deps, taskId, fence) {
|
|
|
222571
222923
|
return true;
|
|
222572
222924
|
}
|
|
222573
222925
|
|
|
222926
|
+
// ../engine/src/executor/evaluate-workflow-merge-boundary.ts
|
|
222927
|
+
init_src2();
|
|
222928
|
+
async function evaluateWorkflowMergeBoundary(deps, task, runId) {
|
|
222929
|
+
const relevant = (task.workflowStepResults ?? []).filter(
|
|
222930
|
+
(result) => result.source === "node" && (result.phase ?? "pre-merge") === "pre-merge"
|
|
222931
|
+
);
|
|
222932
|
+
const hasRelevantNodeResult = relevant.length > 0;
|
|
222933
|
+
const nonTerminalResult = relevant.find((result) => result.status !== "passed" && result.status !== "skipped");
|
|
222934
|
+
const allResultsTerminal = nonTerminalResult === void 0;
|
|
222935
|
+
let ir2;
|
|
222936
|
+
try {
|
|
222937
|
+
ir2 = await resolveWorkflowIrForTask(deps.store, task.id);
|
|
222938
|
+
} catch {
|
|
222939
|
+
}
|
|
222940
|
+
if (!ir2) return { resolved: false, hasRelevantNodeResult, allResultsTerminal, coverageComplete: true, hasLiveStepImplementationProof: false, hasForeachStepExecute: false, missingInstanceIds: [], nonTerminalResult, complete: false };
|
|
222941
|
+
let persistedInstances = [];
|
|
222942
|
+
try {
|
|
222943
|
+
persistedInstances = await deps.loadMergeBoundaryInstances(task.id, runId);
|
|
222944
|
+
} catch {
|
|
222945
|
+
}
|
|
222946
|
+
const coverage = evaluateForeachMergeProof({ ir: ir2, steps: task.steps, workflowStepResults: task.workflowStepResults, persistedInstances });
|
|
222947
|
+
const coverageComplete = coverage.missingInstanceIds.length === 0;
|
|
222948
|
+
const hasLiveStepImplementationProof = coverage.expectedInstanceIds.length > 0 && coverage.expectedInstanceIds.every((id) => coverage.liveStepSatisfiedInstanceIds.includes(id));
|
|
222949
|
+
const complete = allResultsTerminal && coverageComplete && (hasRelevantNodeResult || hasLiveStepImplementationProof);
|
|
222950
|
+
return { resolved: true, hasRelevantNodeResult, allResultsTerminal, coverageComplete, hasLiveStepImplementationProof, hasForeachStepExecute: coverage.hasForeachStepExecute, missingInstanceIds: coverage.missingInstanceIds, nonTerminalResult, complete };
|
|
222951
|
+
}
|
|
222952
|
+
async function getWorkflowMergeImplementationProofFailure(deps, task) {
|
|
222953
|
+
const taint = evaluateSkipBypassTaint(task);
|
|
222954
|
+
if (taint.blocked) return "implementation did not run: steps were skipped after a bulk-step-completion refusal without an accepted fn_task_done";
|
|
222955
|
+
if (task.noCommitsExpected === true) return void 0;
|
|
222956
|
+
let ir2;
|
|
222957
|
+
try {
|
|
222958
|
+
ir2 = await resolveWorkflowIrForTask(deps.store, task.id);
|
|
222959
|
+
} catch {
|
|
222960
|
+
ir2 = void 0;
|
|
222961
|
+
}
|
|
222962
|
+
if (!ir2) return void 0;
|
|
222963
|
+
const usesParsedSteps = ir2.nodes.some((node) => node.kind === "parse-steps");
|
|
222964
|
+
const usesExecuteSeam = ir2.nodes.some((node) => node.kind === "prompt" && node.config?.seam === "execute");
|
|
222965
|
+
if (!usesParsedSteps && !usesExecuteSeam) return void 0;
|
|
222966
|
+
const steps = Array.isArray(task.steps) ? task.steps : [];
|
|
222967
|
+
const hasTerminalParsedSteps = steps.length > 0 && steps.every((step) => step.status === "done" || step.status === "skipped");
|
|
222968
|
+
const hasModifiedFiles = (task.modifiedFiles?.length ?? 0) > 0;
|
|
222969
|
+
const proof = await deps.evaluateWorkflowMergeBoundary(task);
|
|
222970
|
+
const hasGraphNativeImplementationProof = proof.hasRelevantNodeResult && proof.allResultsTerminal && proof.coverageComplete;
|
|
222971
|
+
if (usesParsedSteps) {
|
|
222972
|
+
if (hasTerminalParsedSteps || hasGraphNativeImplementationProof) return void 0;
|
|
222973
|
+
return proof.hasForeachStepExecute && !proof.coverageComplete ? `implementation did not run: foreach step instances are incomplete (missing ${proof.missingInstanceIds.join(", ")})` : "implementation did not run: parsed coding steps are missing or incomplete";
|
|
222974
|
+
}
|
|
222975
|
+
if (usesExecuteSeam) return hasTerminalParsedSteps || hasModifiedFiles || hasGraphNativeImplementationProof ? void 0 : "implementation did not run: execute seam has no completion proof";
|
|
222976
|
+
return void 0;
|
|
222977
|
+
}
|
|
222978
|
+
|
|
222979
|
+
// ../engine/src/executor/workflow-merge-boundary-helpers.ts
|
|
222980
|
+
init_src2();
|
|
222981
|
+
async function resolveMergeBoundaryColumn(deps, taskId, nodeId) {
|
|
222982
|
+
try {
|
|
222983
|
+
const ir2 = await resolveWorkflowIrForTask(deps.store, taskId);
|
|
222984
|
+
const named = ir2.nodes.find((n) => n.id === nodeId);
|
|
222985
|
+
if (named && MERGE_REGION_KINDS.has(named.kind) && named.column) return named.column;
|
|
222986
|
+
const mergeNode = ir2.nodes.find((n) => MERGE_REGION_KINDS.has(n.kind) && n.column);
|
|
222987
|
+
if (mergeNode?.column) return mergeNode.column;
|
|
222988
|
+
return "in-review";
|
|
222989
|
+
} catch {
|
|
222990
|
+
return "in-review";
|
|
222991
|
+
}
|
|
222992
|
+
}
|
|
222993
|
+
async function loadMergeBoundaryInstances(deps, taskId, runId) {
|
|
222994
|
+
if (!runId) return [];
|
|
222995
|
+
const store = deps.store;
|
|
222996
|
+
try {
|
|
222997
|
+
return await store.loadWorkflowRunStepInstancesAsync?.(taskId, runId) ?? store.loadWorkflowRunStepInstances?.(taskId, runId) ?? [];
|
|
222998
|
+
} catch {
|
|
222999
|
+
return [];
|
|
223000
|
+
}
|
|
223001
|
+
}
|
|
223002
|
+
function shouldCompleteChecklistAtWorkflowMerge(task, proof) {
|
|
223003
|
+
if (!Array.isArray(task.steps) || task.steps.length === 0) return false;
|
|
223004
|
+
if (task.steps.every((step) => step.status === "done" || step.status === "skipped")) return false;
|
|
223005
|
+
if (proof) return proof.complete;
|
|
223006
|
+
const graphNodeResults = (task.workflowStepResults ?? []).filter((result) => result.source === "node" && (result.phase ?? "pre-merge") === "pre-merge");
|
|
223007
|
+
return graphNodeResults.length > 0 && graphNodeResults.every((result) => result.status === "passed" || result.status === "skipped");
|
|
223008
|
+
}
|
|
223009
|
+
|
|
223010
|
+
// ../engine/src/executor/route-graph-failure-to-execution-resume.ts
|
|
223011
|
+
init_src2();
|
|
223012
|
+
|
|
223013
|
+
// ../engine/src/execution-block-classifier.ts
|
|
223014
|
+
init_usage_limit_detector();
|
|
223015
|
+
init_transient_error_detector();
|
|
223016
|
+
var HOST_EXTERNAL_CODES = ["ENOSPC", "EDQUOT", "EROFS", "ENOMEM", "EMFILE", "ENFILE"];
|
|
223017
|
+
var NETWORK_EXTERNAL_CODES = ["ECONNREFUSED", "ECONNRESET", "ETIMEDOUT", "EAI_AGAIN", "ENOTFOUND"];
|
|
223018
|
+
function classifyExternalObstacle(message) {
|
|
223019
|
+
const normalized3 = message.trim();
|
|
223020
|
+
if (!normalized3) return void 0;
|
|
223021
|
+
if (/Forbidden lifecycle path|rejected by the task lifecycle projection|TransitionRejectionError/i.test(normalized3)) {
|
|
223022
|
+
return void 0;
|
|
223023
|
+
}
|
|
223024
|
+
for (const code of HOST_EXTERNAL_CODES) {
|
|
223025
|
+
if (new RegExp(`\\b${code}\\b`, "i").test(normalized3)) return { origin: "host-environment", code };
|
|
223026
|
+
}
|
|
223027
|
+
for (const code of NETWORK_EXTERNAL_CODES) {
|
|
223028
|
+
if (new RegExp(`\\b${code}\\b`, "i").test(normalized3)) return { origin: "network", code };
|
|
223029
|
+
}
|
|
223030
|
+
if (/socket hang up/i.test(normalized3)) return { origin: "network", code: "SOCKET_HANG_UP" };
|
|
223031
|
+
if (isTransientAuthCredentialError(normalized3)) return void 0;
|
|
223032
|
+
if (isUsageLimitError(normalized3)) return { origin: "model-provider", code: "USAGE_LIMIT" };
|
|
223033
|
+
if (isOperatorActionableAgentError(normalized3)) return { origin: "credentials", code: "CREDENTIALS" };
|
|
223034
|
+
return void 0;
|
|
223035
|
+
}
|
|
223036
|
+
function detectRepairableObstacleHint(message) {
|
|
223037
|
+
const normalized3 = message.trim();
|
|
223038
|
+
if (!normalized3) return void 0;
|
|
223039
|
+
const missingSignal = /\b(?:command not found|not installed|is not recognized as an internal or external command)\b|\bneither\b.{0,80}\binstalled\b/i;
|
|
223040
|
+
const missingFileSignal = /(?:\b(?:spawn|exec(?:ute)?|binary|executable|interpreter|command)\b.{0,80}\b(?:ENOENT|no such file or directory)\b|\bENOENT\b.{0,80}\b(?:binary|executable|interpreter|command)\b)/i;
|
|
223041
|
+
if (!missingSignal.test(normalized3) && !missingFileSignal.test(normalized3)) return void 0;
|
|
223042
|
+
return /\b(?:service|daemon)\b/i.test(normalized3) ? "missing-service" : "missing-tooling";
|
|
223043
|
+
}
|
|
223044
|
+
var TASK_ID_RE = /^[A-Z][A-Z0-9]*-\d+$/i;
|
|
223045
|
+
var LEGACY_PR_REF_RE = /^(?:pr[:#-]|#|PR-)(\d+)$/i;
|
|
223046
|
+
function partitionBlockedByRefs(blockedBy) {
|
|
223047
|
+
const taskIds = [];
|
|
223048
|
+
for (const raw of blockedBy) {
|
|
223049
|
+
const id = raw.trim();
|
|
223050
|
+
if (!id) continue;
|
|
223051
|
+
if (LEGACY_PR_REF_RE.test(id)) continue;
|
|
223052
|
+
if (TASK_ID_RE.test(id)) {
|
|
223053
|
+
const m2 = id.toUpperCase().match(/^([A-Z][A-Z0-9]*)-(\d+)$/);
|
|
223054
|
+
taskIds.push(m2 ? `${m2[1]}-${m2[2]}` : id.toUpperCase());
|
|
223055
|
+
}
|
|
223056
|
+
}
|
|
223057
|
+
return { taskIds: [...new Set(taskIds)] };
|
|
223058
|
+
}
|
|
223059
|
+
function classifyBlockedExit(_reason, blockedBy = []) {
|
|
223060
|
+
const { taskIds } = partitionBlockedByRefs(blockedBy);
|
|
223061
|
+
if (taskIds.length > 0) {
|
|
223062
|
+
return {
|
|
223063
|
+
class: "external",
|
|
223064
|
+
thrashSignature: `tasks:${taskIds.slice().sort().join(",")}`
|
|
223065
|
+
};
|
|
223066
|
+
}
|
|
223067
|
+
return {
|
|
223068
|
+
class: "plan-defect",
|
|
223069
|
+
thrashSignature: "plan-defect"
|
|
223070
|
+
};
|
|
223071
|
+
}
|
|
223072
|
+
var BLOCKED_THRASH_LIMIT = 3;
|
|
223073
|
+
var BLOCKED_THRASH_WINDOW_MS = 60 * 60 * 1e3;
|
|
223074
|
+
function countBlockedThrashHits(log42, signature, nowMs = Date.now(), windowMs = BLOCKED_THRASH_WINDOW_MS) {
|
|
223075
|
+
if (!log42?.length) return 0;
|
|
223076
|
+
if (signature === "plan-defect") return 0;
|
|
223077
|
+
const cutoff = nowMs - windowMs;
|
|
223078
|
+
let count2 = 0;
|
|
223079
|
+
for (const entry of log42) {
|
|
223080
|
+
const ts4 = entry.timestamp ? Date.parse(entry.timestamp) : Number.NaN;
|
|
223081
|
+
if (Number.isFinite(ts4) && ts4 < cutoff) continue;
|
|
223082
|
+
const action = entry.action ?? "";
|
|
223083
|
+
if (!action.startsWith("BLOCKED:") && !action.includes("DUPLICATE_REPLAN_EXHAUSTED") && !action.includes("BLOCKED_THRASH")) {
|
|
223084
|
+
if (!action.includes("parked failed (honest blocked exit") && !action.includes("durable external block")) {
|
|
223085
|
+
continue;
|
|
223086
|
+
}
|
|
223087
|
+
}
|
|
223088
|
+
for (const part of signature.split("|")) {
|
|
223089
|
+
if (part.startsWith("tasks:") && part.slice(6).split(",").some((id) => id && action.includes(id))) {
|
|
223090
|
+
count2 += 1;
|
|
223091
|
+
break;
|
|
223092
|
+
}
|
|
223093
|
+
}
|
|
223094
|
+
}
|
|
223095
|
+
return count2;
|
|
223096
|
+
}
|
|
223097
|
+
function isDurableBlockedTask(task) {
|
|
223098
|
+
const meta2 = task.sourceMetadata;
|
|
223099
|
+
if (!meta2 || typeof meta2 !== "object") return false;
|
|
223100
|
+
if (meta2.blockedClass !== "external") return false;
|
|
223101
|
+
return task.status === "failed" || task.status === "needs-replan";
|
|
223102
|
+
}
|
|
223103
|
+
function buildExternalBlockMetadataPatch(classification, thrashCount) {
|
|
223104
|
+
return {
|
|
223105
|
+
blockedClass: classification.class,
|
|
223106
|
+
blockedThrashSignature: classification.thrashSignature,
|
|
223107
|
+
blockedThrashCount: thrashCount,
|
|
223108
|
+
externalBlockers: []
|
|
223109
|
+
};
|
|
223110
|
+
}
|
|
223111
|
+
|
|
223112
|
+
// ../engine/src/executor/route-graph-failure-to-execution-resume.ts
|
|
223113
|
+
init_logger();
|
|
223114
|
+
init_lifecycle_columns();
|
|
223115
|
+
|
|
223116
|
+
// ../engine/src/executor/workflow-step-satisfaction.ts
|
|
223117
|
+
function hasNonTerminalWorkflowSteps(task) {
|
|
223118
|
+
return task.steps.length > 0 && task.steps.some((step) => step.status !== "done" && step.status !== "skipped");
|
|
223119
|
+
}
|
|
223120
|
+
function workflowStepResultPassed(task, workflowStepId) {
|
|
223121
|
+
const results = task?.workflowStepResults ?? [];
|
|
223122
|
+
return results.some(
|
|
223123
|
+
(result) => result.workflowStepId === workflowStepId && result.phase === "pre-merge" && result.status === "passed"
|
|
223124
|
+
);
|
|
223125
|
+
}
|
|
223126
|
+
function areEnabledPreMergeWorkflowStepsSatisfied(task) {
|
|
223127
|
+
const preMergeGateIds = /* @__PURE__ */ new Set(["plan-review", "browser-verification", "code-review"]);
|
|
223128
|
+
const enabled = task?.enabledWorkflowSteps;
|
|
223129
|
+
const enabledPreMerge = Array.isArray(enabled) && enabled.length > 0 ? enabled.filter((id) => preMergeGateIds.has(id)) : ["plan-review", "code-review"];
|
|
223130
|
+
if (enabledPreMerge.length === 0) return false;
|
|
223131
|
+
if (Array.isArray(enabled) && enabledPreMerge.length !== enabled.length) return false;
|
|
223132
|
+
return enabledPreMerge.every((id) => workflowStepResultPassed(task, id));
|
|
223133
|
+
}
|
|
223134
|
+
function preservePreExecutionWorkflowStepResults(task) {
|
|
223135
|
+
const preserved = (task.workflowStepResults ?? []).filter((result) => result.workflowStepId === "plan-review");
|
|
223136
|
+
if (preserved.length > 0) return preserved;
|
|
223137
|
+
let latest;
|
|
223138
|
+
for (const entry of task.log ?? []) {
|
|
223139
|
+
if (entry.action === "[pre-merge] Workflow step completed: Plan Review") {
|
|
223140
|
+
latest = { timestamp: entry.timestamp, outcome: entry.outcome, status: "passed" };
|
|
223141
|
+
} else if (entry.action === "[pre-merge] Workflow step failed: Plan Review") {
|
|
223142
|
+
latest = { timestamp: entry.timestamp, outcome: entry.outcome, status: "failed" };
|
|
223143
|
+
}
|
|
223144
|
+
}
|
|
223145
|
+
if (latest?.status !== "passed") return [];
|
|
223146
|
+
return [
|
|
223147
|
+
{
|
|
223148
|
+
workflowStepId: "plan-review",
|
|
223149
|
+
workflowStepName: "Plan Review",
|
|
223150
|
+
phase: "pre-merge",
|
|
223151
|
+
status: "passed",
|
|
223152
|
+
verdict: "APPROVE",
|
|
223153
|
+
...latest.outcome ? { output: latest.outcome, notes: latest.outcome } : {},
|
|
223154
|
+
...latest.timestamp ? { startedAt: latest.timestamp, completedAt: latest.timestamp } : {}
|
|
223155
|
+
}
|
|
223156
|
+
];
|
|
223157
|
+
}
|
|
223158
|
+
|
|
223159
|
+
// ../engine/src/executor/route-graph-failure-to-execution-resume.ts
|
|
223160
|
+
init_workflow_merge_nodes();
|
|
223161
|
+
function isExecutableNode(node) {
|
|
223162
|
+
const seam = node.config?.seam;
|
|
223163
|
+
return node.kind === "foreach" || seam === "execute" || seam === "step-execute";
|
|
223164
|
+
}
|
|
223165
|
+
function isTerminalWorkItem(item) {
|
|
223166
|
+
return item.state === "succeeded" || item.state === "failed" || item.state === "cancelled" || item.state === "exhausted" || item.state === "manual-required";
|
|
223167
|
+
}
|
|
223168
|
+
function findBoundaryRecoveryOwners(task, nodes2, evidence, workItems) {
|
|
223169
|
+
if (!evidence || !["no-node-result", "non-terminal-node-result", "missing-foreach-instances"].includes(evidence.code)) return void 0;
|
|
223170
|
+
if (evidence.code === "non-terminal-node-result") {
|
|
223171
|
+
const id = evidence.nonTerminalNodeId;
|
|
223172
|
+
const result = task.workflowStepResults?.find((entry) => entry.workflowStepId === id && entry.source === "node");
|
|
223173
|
+
return id && result && result.status !== "passed" && result.status !== "failed" && result.status !== "skipped" && nodes2.some((node) => node.id === id && isExecutableNode(node)) ? [id] : void 0;
|
|
223174
|
+
}
|
|
223175
|
+
if (evidence.code === "missing-foreach-instances") {
|
|
223176
|
+
const ids = evidence.missingInstanceIds;
|
|
223177
|
+
if (ids.length === 0) return void 0;
|
|
223178
|
+
const foreachOwners = nodes2.filter((node) => node.kind === "foreach");
|
|
223179
|
+
const owners2 = ids.map(
|
|
223180
|
+
(id) => (
|
|
223181
|
+
// Prefer the longest prefix so nested/custom node names cannot misclassify an instance.
|
|
223182
|
+
foreachOwners.filter((node) => id.startsWith(`${node.id}#`)).sort((left, right) => right.id.length - left.id.length)[0]?.id
|
|
223183
|
+
)
|
|
223184
|
+
);
|
|
223185
|
+
if (owners2.some((owner) => owner === void 0)) return void 0;
|
|
223186
|
+
return foreachOwners.filter((node) => owners2.includes(node.id)).map((node) => node.id);
|
|
223187
|
+
}
|
|
223188
|
+
if (!hasNonTerminalWorkflowSteps(task)) return void 0;
|
|
223189
|
+
const terminalNodes = new Set(workItems.filter(isTerminalWorkItem).map((item) => item.nodeId));
|
|
223190
|
+
const candidates = nodes2.filter((node) => isExecutableNode(node) && !terminalNodes.has(node.id));
|
|
223191
|
+
return candidates.length === 1 && candidates[0] ? [candidates[0].id] : void 0;
|
|
223192
|
+
}
|
|
223193
|
+
var BOUNDARY_RECOVERY_CLAIM_STATUS = "merge-boundary-evidence-recovery";
|
|
223194
|
+
var BOUNDARY_RECOVERY_HELD_STATUS = "merge-boundary-evidence-recovery-held";
|
|
223195
|
+
var BOUNDARY_RECOVERY_CONCURRENT = /* @__PURE__ */ Symbol("merge-boundary-recovery-concurrent");
|
|
223196
|
+
var MAX_BOUNDARY_RECOVERY_ATTEMPTS = 4;
|
|
223197
|
+
var BOUNDARY_RECOVERY_BACKOFF_MS = [3e4, 6e4, 12e4, 3e5];
|
|
223198
|
+
function boundaryRecoverySignature(task, failedNode, evidence) {
|
|
223199
|
+
return JSON.stringify({
|
|
223200
|
+
failedNode,
|
|
223201
|
+
evidence,
|
|
223202
|
+
steps: task.steps.map((step, index2) => [index2, step.name, step.status]),
|
|
223203
|
+
nodeResults: (task.workflowStepResults ?? []).filter((result) => result.source === "node").map((result) => [result.workflowStepId, result.status]).sort(([left], [right]) => String(left).localeCompare(String(right)))
|
|
223204
|
+
});
|
|
223205
|
+
}
|
|
223206
|
+
async function recoverMergeBoundaryEvidenceGap(deps, live, failedNode, evidence, wipColumn, autoMergeSettings, liveSharedBranchMember = false) {
|
|
223207
|
+
let claimed = false;
|
|
223208
|
+
let deferred = false;
|
|
223209
|
+
let held = false;
|
|
223210
|
+
const now = Date.now();
|
|
223211
|
+
const signature = boundaryRecoverySignature(live, failedNode, evidence);
|
|
223212
|
+
await deps.store.updateTaskAtomic(live.id, (current) => {
|
|
223213
|
+
if (!current || current.deletedAt || current.paused || current.userPaused || autoMergeSettings && !liveSharedBranchMember && (current.autoMerge === false || !allowsAutoMergeProcessing(current, autoMergeSettings)) || current.column !== live.column || current.status !== live.status || current.error !== live.error || live.columnMovedAt !== void 0 && current.columnMovedAt !== live.columnMovedAt) return null;
|
|
223214
|
+
const prior = current.mergeDetails?.mergeBoundaryRecovery;
|
|
223215
|
+
if (prior?.signature === signature) {
|
|
223216
|
+
const nextCheckAt2 = Date.parse(prior.nextCheckAt ?? "");
|
|
223217
|
+
if (Number.isFinite(nextCheckAt2) && nextCheckAt2 > now) {
|
|
223218
|
+
deferred = true;
|
|
223219
|
+
return null;
|
|
223220
|
+
}
|
|
223221
|
+
if (prior.attempt >= MAX_BOUNDARY_RECOVERY_ATTEMPTS) {
|
|
223222
|
+
held = true;
|
|
223223
|
+
return {
|
|
223224
|
+
status: BOUNDARY_RECOVERY_HELD_STATUS,
|
|
223225
|
+
error: "Workflow merge evidence recovery is held after unchanged proof gaps; waiting for durable implementation progress",
|
|
223226
|
+
mergeDetails: {
|
|
223227
|
+
...current.mergeDetails,
|
|
223228
|
+
mergeBoundaryRecovery: { ...prior, nextCheckAt: null, heldAt: new Date(now).toISOString() }
|
|
223229
|
+
}
|
|
223230
|
+
};
|
|
223231
|
+
}
|
|
223232
|
+
}
|
|
223233
|
+
const attempt = prior?.signature === signature ? prior.attempt + 1 : 1;
|
|
223234
|
+
const nextCheckAt = new Date(now + BOUNDARY_RECOVERY_BACKOFF_MS[attempt - 1]).toISOString();
|
|
223235
|
+
claimed = true;
|
|
223236
|
+
return {
|
|
223237
|
+
status: BOUNDARY_RECOVERY_CLAIM_STATUS,
|
|
223238
|
+
mergeDetails: {
|
|
223239
|
+
...current.mergeDetails,
|
|
223240
|
+
mergeBoundaryRecovery: { signature, attempt, nextCheckAt }
|
|
223241
|
+
}
|
|
223242
|
+
};
|
|
223243
|
+
}, deps.getRunContextFor(live.id));
|
|
223244
|
+
if (deferred) {
|
|
223245
|
+
await deps.store.logEntry(live.id, "Workflow merge evidence recovery is waiting for its bounded recheck window before retrying unchanged proof", void 0, deps.getRunContextFor(live.id));
|
|
223246
|
+
return "deferred";
|
|
223247
|
+
}
|
|
223248
|
+
if (held) {
|
|
223249
|
+
await deps.store.logEntry(live.id, "Workflow merge evidence recovery is held because the same proof gap exhausted its bounded retry budget", void 0, deps.getRunContextFor(live.id));
|
|
223250
|
+
return "held";
|
|
223251
|
+
}
|
|
223252
|
+
if (!claimed) return "declined";
|
|
223253
|
+
const claimedTask = typeof deps.store.getTask === "function" ? await deps.store.getTask(live.id) : live;
|
|
223254
|
+
if (!claimedTask || claimedTask.column !== live.column || claimedTask !== live && claimedTask.status !== BOUNDARY_RECOVERY_CLAIM_STATUS || autoMergeSettings && !liveSharedBranchMember && (claimedTask.autoMerge === false || !allowsAutoMergeProcessing(claimedTask, autoMergeSettings))) {
|
|
223255
|
+
await releaseBoundaryRecoveryClaim(deps.store, live, deps.getRunContextFor(live.id));
|
|
223256
|
+
return "declined";
|
|
223257
|
+
}
|
|
223258
|
+
let owner;
|
|
223259
|
+
try {
|
|
223260
|
+
owner = await seedBoundaryRecoveryContinuation(deps.store, live, failedNode, evidence);
|
|
223261
|
+
} catch (error2) {
|
|
223262
|
+
await releaseBoundaryRecoveryClaim(deps.store, live, deps.getRunContextFor(live.id));
|
|
223263
|
+
throw error2;
|
|
223264
|
+
}
|
|
223265
|
+
if (!owner) {
|
|
223266
|
+
await releaseBoundaryRecoveryClaim(deps.store, live, deps.getRunContextFor(live.id));
|
|
223267
|
+
return "declined";
|
|
223268
|
+
}
|
|
223269
|
+
if (owner === BOUNDARY_RECOVERY_CONCURRENT) {
|
|
223270
|
+
await releaseBoundaryRecoveryClaim(deps.store, live, deps.getRunContextFor(live.id));
|
|
223271
|
+
return "concurrent";
|
|
223272
|
+
}
|
|
223273
|
+
const remediation = `Workflow merge evidence recovery: '${owner}' is being resumed after missing proof at '${failedNode}'; returning to '${wipColumn}' before merge proof is rechecked`;
|
|
223274
|
+
executorLog.warn(`${live.id}: ${remediation}`);
|
|
223275
|
+
await deps.store.logEntry(live.id, remediation, void 0, deps.getRunContextFor(live.id));
|
|
223276
|
+
const moveOptions = {
|
|
223277
|
+
moveSource: "engine",
|
|
223278
|
+
preserveProgress: true,
|
|
223279
|
+
preserveWorktree: true,
|
|
223280
|
+
workflowMoveSource: "workflow-graph",
|
|
223281
|
+
workflowMoveMetadata: { reason: "merge-boundary-evidence-recovery", nodeId: failedNode }
|
|
223282
|
+
};
|
|
223283
|
+
const conditionalMoveStore = deps.store;
|
|
223284
|
+
const moved = typeof conditionalMoveStore.moveTaskIf === "function" ? (await conditionalMoveStore.moveTaskIf(
|
|
223285
|
+
live.id,
|
|
223286
|
+
wipColumn,
|
|
223287
|
+
(current) => !current.deletedAt && !current.paused && !current.userPaused && (!autoMergeSettings || liveSharedBranchMember || current.autoMerge !== false && allowsAutoMergeProcessing(current, autoMergeSettings)) && current.column === live.column && current.status === BOUNDARY_RECOVERY_CLAIM_STATUS,
|
|
223288
|
+
moveOptions
|
|
223289
|
+
)).moved : (await deps.store.moveTask(live.id, wipColumn, moveOptions), true);
|
|
223290
|
+
if (!moved) {
|
|
223291
|
+
await releaseBoundaryRecoveryClaim(deps.store, live, deps.getRunContextFor(live.id));
|
|
223292
|
+
return "raced";
|
|
223293
|
+
}
|
|
223294
|
+
let completed = false;
|
|
223295
|
+
await deps.store.updateTaskAtomic(live.id, (current) => {
|
|
223296
|
+
if (!current || current.deletedAt || current.paused || current.userPaused || current.column !== wipColumn || current.status !== BOUNDARY_RECOVERY_CLAIM_STATUS) return null;
|
|
223297
|
+
completed = true;
|
|
223298
|
+
return { status: null, error: null };
|
|
223299
|
+
}, deps.getRunContextFor(live.id));
|
|
223300
|
+
return completed ? "recovered" : "declined";
|
|
223301
|
+
}
|
|
223302
|
+
async function releaseBoundaryRecoveryClaim(store, live, runContext) {
|
|
223303
|
+
await store.updateTaskAtomic(live.id, (current) => {
|
|
223304
|
+
if (!current || current.deletedAt || current.paused || current.userPaused || current.column !== live.column || current.status !== BOUNDARY_RECOVERY_CLAIM_STATUS) return null;
|
|
223305
|
+
return { status: live.status };
|
|
223306
|
+
}, runContext);
|
|
223307
|
+
}
|
|
223308
|
+
async function seedBoundaryRecoveryContinuation(store, task, failedNode, evidence) {
|
|
223309
|
+
const ir2 = await resolveWorkflowIrForTask(store, task.id).catch(() => void 0);
|
|
223310
|
+
const workItems = typeof store.listWorkflowWorkItemsForTask === "function" ? await store.listWorkflowWorkItemsForTask(task.id, { kinds: ["task"] }).catch(() => []) : [];
|
|
223311
|
+
const owners2 = ir2 ? findBoundaryRecoveryOwners(task, ir2.nodes, evidence, workItems) : void 0;
|
|
223312
|
+
const owner = owners2?.[0];
|
|
223313
|
+
if (!owner || typeof store.replaceActiveTaskWorkflowContinuation !== "function") return void 0;
|
|
223314
|
+
const seeded = await Promise.resolve(store.replaceActiveTaskWorkflowContinuation({
|
|
223315
|
+
taskId: task.id,
|
|
223316
|
+
runId: `${task.id}:merge-boundary-recovery:${owner}`,
|
|
223317
|
+
nodeId: owner,
|
|
223318
|
+
kind: "task",
|
|
223319
|
+
state: "runnable",
|
|
223320
|
+
blockedReason: `merge-boundary-evidence-recovery:${failedNode}`,
|
|
223321
|
+
lastError: null,
|
|
223322
|
+
leaseOwner: null,
|
|
223323
|
+
leaseExpiresAt: null,
|
|
223324
|
+
onlyIfNoActiveTaskContinuation: true
|
|
223325
|
+
})).catch((error2) => {
|
|
223326
|
+
if (error2 instanceof Error && error2.name === "ActiveTaskContinuationError") return null;
|
|
223327
|
+
throw error2;
|
|
223328
|
+
});
|
|
223329
|
+
return seeded === null ? BOUNDARY_RECOVERY_CONCURRENT : owner;
|
|
223330
|
+
}
|
|
223331
|
+
async function routeGraphFailureToExecutionResume(deps, live, failedNode, failureValue, resumeLanesMemo, boundaryEvidence) {
|
|
223332
|
+
if (live.deletedAt) return false;
|
|
223333
|
+
if (isTaskExternallyBlocked(live)) {
|
|
223334
|
+
executorLog.log(`${live.id}: graph failure resume skipped \u2014 external Blocked freeze honored`);
|
|
223335
|
+
return false;
|
|
223336
|
+
}
|
|
223337
|
+
if (live.paused || live.userPaused === true) return false;
|
|
223338
|
+
if ((await resolveTerminalColumnsFor(deps.store, live.id)).includes(live.column)) return false;
|
|
223339
|
+
if (isDurableBlockedTask(live)) {
|
|
223340
|
+
executorLog.log(
|
|
223341
|
+
`${live.id}: graph failure resume skipped \u2014 durable BLOCKED park honored (task-dependency block)`
|
|
223342
|
+
);
|
|
223343
|
+
return false;
|
|
223344
|
+
}
|
|
223345
|
+
if (failedNode === COMPLETION_SUMMARY_NODE_ID) return false;
|
|
223346
|
+
const incompleteSteps = hasNonTerminalWorkflowSteps(live);
|
|
223347
|
+
if (!incompleteSteps && (failureValue === "remediation-not-scheduled" || failureValue === "missing-remediation-context") && await deps.isRemediationGraphNode(live.id, failedNode)) return false;
|
|
223348
|
+
const implementationIncompleteMergeFailure = isMergeGraphFailure(failedNode) && failureValue === "implementation-incomplete";
|
|
223349
|
+
const boundaryEvidenceRecovery = isMergeGraphFailure(failedNode) && failureValue === MERGE_BOUNDARY_RECOVERY_VALUE;
|
|
223350
|
+
if (implementationIncompleteMergeFailure && !incompleteSteps) return false;
|
|
223351
|
+
const resumeRouterLanes = await deps.resolveResumeLanes(live.id, resumeLanesMemo);
|
|
223352
|
+
if (!resumeRouterLanes.wipDeclared) return false;
|
|
223353
|
+
const mayResumeInPlace = live.column === resumeRouterLanes.wip && implementationIncompleteMergeFailure;
|
|
223354
|
+
const mayRepairBoundary = boundaryEvidenceRecovery && live.column === resumeRouterLanes.review;
|
|
223355
|
+
if (!mayResumeInPlace && !mayRepairBoundary) {
|
|
223356
|
+
const message2 = `Workflow graph failed at node '${failedNode}'${failureValue ? ` (${failureValue})` : ""} \u2014 automatic recovery cannot move '${live.column}' backward; card remains in place`;
|
|
223357
|
+
executorLog.warn(`${live.id}: ${message2}`);
|
|
223358
|
+
await deps.store.logEntry(live.id, message2, void 0, deps.getRunContextFor(live.id));
|
|
223359
|
+
return false;
|
|
223360
|
+
}
|
|
223361
|
+
if (mayRepairBoundary) {
|
|
223362
|
+
const settings = await deps.store.getSettings?.().catch(() => void 0) ?? { autoMerge: true };
|
|
223363
|
+
if (settings.globalPause === true || settings.enginePaused === true) return false;
|
|
223364
|
+
const liveSharedBranchMember = await deps.isLiveSharedBranchGroupMember?.(live) ?? false;
|
|
223365
|
+
if ((live.autoMerge === false || !allowsAutoMergeProcessing(live, settings)) && !liveSharedBranchMember) return false;
|
|
223366
|
+
const outcome = await recoverMergeBoundaryEvidenceGap(
|
|
223367
|
+
deps,
|
|
223368
|
+
live,
|
|
223369
|
+
failedNode,
|
|
223370
|
+
boundaryEvidence,
|
|
223371
|
+
resumeRouterLanes.wip,
|
|
223372
|
+
settings,
|
|
223373
|
+
liveSharedBranchMember
|
|
223374
|
+
);
|
|
223375
|
+
if (outcome === "declined") return false;
|
|
223376
|
+
if (outcome === "concurrent" || outcome === "raced" || outcome === "deferred" || outcome === "held") return true;
|
|
223377
|
+
}
|
|
223378
|
+
const message = mayRepairBoundary ? `Workflow merge evidence recovery scheduled implementation resume in '${resumeRouterLanes.wip}'` : `Workflow graph failed at node '${failedNode}'${failureValue ? ` (${failureValue})` : ""} with incomplete work \u2014 resuming in place in '${live.column}'`;
|
|
223379
|
+
executorLog.warn(`${live.id}: ${message}`);
|
|
223380
|
+
await deps.store.logEntry(live.id, message, void 0, deps.getRunContextFor(live.id));
|
|
223381
|
+
if (!mayRepairBoundary) {
|
|
223382
|
+
await deps.store.updateTask(live.id, { status: null, error: null }, deps.getRunContextFor(live.id));
|
|
223383
|
+
}
|
|
223384
|
+
await deps.clearTerminalStepFailuresForRetry(live.id, "archive");
|
|
223385
|
+
await deps.persistTokenUsage(live.id);
|
|
223386
|
+
return true;
|
|
223387
|
+
}
|
|
223388
|
+
|
|
222574
223389
|
// ../engine/src/agent-browser-lifecycle.ts
|
|
222575
223390
|
import { execFile as execFile19 } from "node:child_process";
|
|
222576
223391
|
import { lstatSync as lstatSync3, mkdirSync as mkdirSync9, readdirSync as readdirSync10, readFileSync as readFileSync23, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync13, writeFileSync as writeFileSync9 } from "node:fs";
|
|
@@ -224754,6 +225569,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
224754
225569
|
// stall-deadlock ride this sweep exists to prevent.
|
|
224755
225570
|
{ name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults().then(() => void 0) },
|
|
224756
225571
|
{ name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals().then(() => void 0) },
|
|
225572
|
+
{ name: "reconcile-merge-boundary-evidence-gaps", fn: () => this.reconcileMergeBoundaryEvidenceGaps().then(() => void 0) },
|
|
224757
225573
|
/*
|
|
224758
225574
|
FNXC:WorkspaceContention 2026-08-23-08:00:
|
|
224759
225575
|
`contention-hold` is owned by an in-memory retry timer. After a process restart that
|
|
@@ -224776,6 +225592,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
224776
225592
|
{ name: "completed-tasks", fn: () => this.recoverCompletedTasks().then(() => void 0) },
|
|
224777
225593
|
{ name: "recover-stranded-completed-todo", fn: () => this.recoverStrandedCompletedTodoTasks().then(() => void 0) },
|
|
224778
225594
|
{ name: "recover-advanced-triage", fn: () => this.recoverAdvancedTriageTasks().then(() => void 0) },
|
|
225595
|
+
{ name: "recover-mergeable-review", fn: () => this.recoverMergeableReviewTasks().then(() => void 0) },
|
|
224779
225596
|
{ name: "failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps().then(() => void 0) },
|
|
224780
225597
|
{ name: "missing-worktree-review-failures", fn: () => this.recoverMissingWorktreeReviewFailures().then(() => void 0) },
|
|
224781
225598
|
/*
|
|
@@ -225649,6 +226466,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
225649
226466
|
*/
|
|
225650
226467
|
{ name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults() },
|
|
225651
226468
|
{ name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals() },
|
|
226469
|
+
{ name: "reconcile-merge-boundary-evidence-gaps", fn: () => this.reconcileMergeBoundaryEvidenceGaps() },
|
|
225652
226470
|
{ name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
|
|
225653
226471
|
{ name: "recover-missing-worktree-review-failures", fn: () => this.recoverMissingWorktreeReviewFailures() },
|
|
225654
226472
|
{ name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
|
|
@@ -230160,6 +230978,63 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
230160
230978
|
would discard that valid result. Re-check lane, user pause, workspace ownership, and session liveness
|
|
230161
230979
|
in the callback, then log and audit only a repair whose atomic mutation actually applied.
|
|
230162
230980
|
*/
|
|
230981
|
+
/*
|
|
230982
|
+
FNXC:WorkflowMergeRecovery 2026-09-20-19:38:
|
|
230983
|
+
A historic merge-boundary-unproven park can outlive the executor process that
|
|
230984
|
+
created it. Recover only durable failed evidence with unfinished checklist
|
|
230985
|
+
work, then use the ordinary named merge-fix remediation transition; no sweep
|
|
230986
|
+
may invent node proof, clear an external blocker, or disturb a live session.
|
|
230987
|
+
*/
|
|
230988
|
+
async reconcileMergeBoundaryEvidenceGaps() {
|
|
230989
|
+
const settings = await this.store.getSettings().catch(() => void 0);
|
|
230990
|
+
if (!settings || settings.globalPause || settings.enginePaused) return 0;
|
|
230991
|
+
const reviewColumns = await resolveProjectColumnsForRoles(this.store, REVIEW_ROLES);
|
|
230992
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
230993
|
+
for (const column of reviewColumns) {
|
|
230994
|
+
for (const task of await this.store.listTasks({ column, slim: true })) candidates.set(task.id, task);
|
|
230995
|
+
}
|
|
230996
|
+
const isSessionLive = (taskId) => activeSessionRegistry.pathsForTask(taskId).some((path6) => activeSessionRegistry.isPathActive(path6)) || executingTaskLock.has(taskId) || this.options.isTaskActive?.(taskId) === true;
|
|
230997
|
+
let recovered = 0;
|
|
230998
|
+
for (const candidate of candidates.values()) {
|
|
230999
|
+
if (!candidate.error?.includes("merge-boundary-unproven")) continue;
|
|
231000
|
+
const fresh = await this.store.getTask(candidate.id);
|
|
231001
|
+
if (!fresh || fresh.deletedAt || fresh.userPaused || fresh.paused || fresh.workspaceWorktrees !== void 0 || isTaskExternallyBlocked(fresh) || isSessionLive(fresh.id) || !allowsAutoMergeProcessing(fresh, settings)) continue;
|
|
231002
|
+
const ir2 = await resolveWorkflowIrForTask(this.store, fresh.id).catch(() => void 0);
|
|
231003
|
+
const column = ir2?.columns.find((entry) => entry.id === fresh.column);
|
|
231004
|
+
const wipColumn = ir2?.columns.find((entry) => resolveColumnFlags(entry).countsTowardWip === true)?.id;
|
|
231005
|
+
if (!column || !wipColumn || !isReviewColumnRole(resolveColumnFlags(column), fresh.column)) continue;
|
|
231006
|
+
const proof = await evaluateWorkflowMergeBoundary(
|
|
231007
|
+
{ store: this.store, loadMergeBoundaryInstances: (taskId, runId) => loadMergeBoundaryInstances({ store: this.store }, taskId, runId) },
|
|
231008
|
+
fresh
|
|
231009
|
+
).catch(() => void 0);
|
|
231010
|
+
if (!proof?.hasForeachStepExecute || proof.complete || fresh.noCommitsExpected === true && !hasNonTerminalSteps(fresh)) continue;
|
|
231011
|
+
const evidence = !proof.hasRelevantNodeResult ? { code: "no-node-result", missingInstanceIds: [] } : !proof.allResultsTerminal ? { code: "non-terminal-node-result", nonTerminalNodeId: proof.nonTerminalResult?.workflowStepId, missingInstanceIds: [] } : { code: "missing-foreach-instances", missingInstanceIds: proof.missingInstanceIds };
|
|
231012
|
+
const outcome = await recoverMergeBoundaryEvidenceGap(
|
|
231013
|
+
{ store: this.store, getRunContextFor: () => void 0 },
|
|
231014
|
+
fresh,
|
|
231015
|
+
"merge-boundary",
|
|
231016
|
+
evidence,
|
|
231017
|
+
wipColumn,
|
|
231018
|
+
settings
|
|
231019
|
+
);
|
|
231020
|
+
if (outcome !== "recovered") continue;
|
|
231021
|
+
recovered += 1;
|
|
231022
|
+
await this.store.logEntry(
|
|
231023
|
+
fresh.id,
|
|
231024
|
+
"Workflow merge evidence recovery resumed the proven implementation owner; merge proof will be checked after durable progress"
|
|
231025
|
+
).catch(() => void 0);
|
|
231026
|
+
await emitBoundedRunAudit2(this.store, {
|
|
231027
|
+
taskId: fresh.id,
|
|
231028
|
+
agentId: "self-healing",
|
|
231029
|
+
runId: generateSyntheticRunId("reconcile-merge-boundary-evidence-gap", fresh.id),
|
|
231030
|
+
domain: "database",
|
|
231031
|
+
mutationType: "task:merge-boundary-evidence-recovered",
|
|
231032
|
+
target: fresh.id,
|
|
231033
|
+
metadata: { taskId: fresh.id, outcome: "resumed" }
|
|
231034
|
+
}, { log: log34 });
|
|
231035
|
+
}
|
|
231036
|
+
return recovered;
|
|
231037
|
+
}
|
|
230163
231038
|
async reconcileUnprovenReviewApprovals() {
|
|
230164
231039
|
try {
|
|
230165
231040
|
const reviewColumns = await resolveProjectColumnsForRoles(this.store, REVIEW_ROLES);
|
|
@@ -230662,9 +231537,25 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
230662
231537
|
mergeRetriesReset
|
|
230663
231538
|
};
|
|
230664
231539
|
}
|
|
230665
|
-
async routeUnrunPreMergeGateBackToReview(task, reviewColumns, mergeGate, mergeContent) {
|
|
231540
|
+
async routeUnrunPreMergeGateBackToReview(task, reviewColumns, mergeGate, mergeContent, expectedWorkflowSelection, failedPark) {
|
|
230666
231541
|
if (getTaskMergeBlocker(task, { reviewColumns, requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds, mergeContent }) !== "task has enabled pre-merge workflow steps that never ran") return false;
|
|
230667
|
-
const reroute = await rerouteUnrunPreMergeGateToReview(this.store, task, {
|
|
231542
|
+
const reroute = await rerouteUnrunPreMergeGateToReview(this.store, task, {
|
|
231543
|
+
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
231544
|
+
mergeContent,
|
|
231545
|
+
expectedWorkflowSelection
|
|
231546
|
+
}).catch(() => ({ rerouted: false, reason: "no-unrun-gate", nodeId: void 0, workflowStepId: void 0 }));
|
|
231547
|
+
if (reroute.reason === "workflow-selection-changed" && failedPark) {
|
|
231548
|
+
await this.store.updateTaskAtomic(task.id, (live) => live.status === null && live.error === null ? {
|
|
231549
|
+
status: failedPark.status,
|
|
231550
|
+
error: failedPark.error,
|
|
231551
|
+
paused: failedPark.paused,
|
|
231552
|
+
pausedReason: failedPark.pausedReason,
|
|
231553
|
+
mergeRetries: failedPark.mergeRetries,
|
|
231554
|
+
workflowIrPin: failedPark.workflowIrPin,
|
|
231555
|
+
workflowIrPinNodeId: failedPark.workflowIrPinNodeId,
|
|
231556
|
+
workflowIrPinColumnId: failedPark.workflowIrPinColumnId
|
|
231557
|
+
} : null);
|
|
231558
|
+
}
|
|
230668
231559
|
if (reroute.rerouted) {
|
|
230669
231560
|
await this.store.logEntry(task.id, "[pre-merge] Self-healing re-seeded the workflow graph at an enabled pre-merge gate that never ran.");
|
|
230670
231561
|
log34.warn(`Unrun pre-merge gate for ${task.id} re-seeded at ${reroute.nodeId ?? "unknown"}`);
|
|
@@ -230718,8 +231609,68 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
230718
231609
|
await this.canRecoverMergeRequest(task, settings)
|
|
230719
231610
|
]));
|
|
230720
231611
|
const mergeAdmissionByTaskId = new Map(mergeAdmission);
|
|
231612
|
+
const recoveredUnrunGateIds = /* @__PURE__ */ new Set();
|
|
231613
|
+
for (const task of tasks2) {
|
|
231614
|
+
if (!isRecoverableUnrunGatePark(task) || !mergeAdmissionByTaskId.get(task.id) || executingIds.has(task.id)) continue;
|
|
231615
|
+
try {
|
|
231616
|
+
const reviewColumns = await ownReviewLanesFor(task);
|
|
231617
|
+
if (!reviewColumns.has(task.column)) continue;
|
|
231618
|
+
const gate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
231619
|
+
if (gate.provenance === "default" && !gate.selectionAbsent) continue;
|
|
231620
|
+
const content = await captureMergeContentDescriptor(task, { workspaceRootDir: this.options.rootDir, settings });
|
|
231621
|
+
if (content.kind !== "singular" || task.workspaceWorktrees !== void 0) continue;
|
|
231622
|
+
const recoverable = { ...task, status: void 0, paused: false };
|
|
231623
|
+
if (getTaskMergeBlocker(recoverable, { reviewColumns, requiredPreMergeStepIds: gate.requiredPreMergeStepIds, mergeContent: content }) !== PRE_MERGE_STEPS_NOT_RUN_BLOCKER) continue;
|
|
231624
|
+
const items = await this.store.listWorkflowWorkItemsForTask(task.id);
|
|
231625
|
+
if (items.some((item) => ACTIVE_WORKFLOW_WORK_ITEM_STATES.includes(item.state))) continue;
|
|
231626
|
+
const snapshot = { updatedAt: task.updatedAt, error: task.error, column: task.column };
|
|
231627
|
+
const failedPark = { ...task };
|
|
231628
|
+
let released = false;
|
|
231629
|
+
await this.store.updateTaskAtomic(task.id, (live2) => {
|
|
231630
|
+
if (!isRecoverableUnrunGatePark(live2) || live2.updatedAt !== snapshot.updatedAt || live2.error !== snapshot.error || live2.column !== snapshot.column || !allowsAutoMergeProcessing(live2, settings)) return null;
|
|
231631
|
+
released = true;
|
|
231632
|
+
return {
|
|
231633
|
+
status: null,
|
|
231634
|
+
error: null,
|
|
231635
|
+
paused: false,
|
|
231636
|
+
pausedReason: null,
|
|
231637
|
+
mergeRetries: 0,
|
|
231638
|
+
workflowIrPin: null,
|
|
231639
|
+
workflowIrPinNodeId: null,
|
|
231640
|
+
workflowIrPinColumnId: null
|
|
231641
|
+
};
|
|
231642
|
+
});
|
|
231643
|
+
if (!released) continue;
|
|
231644
|
+
const live = await this.store.getTask(task.id);
|
|
231645
|
+
if (!live) continue;
|
|
231646
|
+
const selection = this.store.getTaskWorkflowSelectionAsync ? await this.store.getTaskWorkflowSelectionAsync(live.id) : this.store.getTaskWorkflowSelection(live.id);
|
|
231647
|
+
const expectedWorkflowSelection = selection ? { workflowId: selection.workflowId, stepIds: [...selection.stepIds] } : null;
|
|
231648
|
+
const currentGate = await resolvePreMergeGateForTask(this.store, live.id, live.enabledWorkflowSteps, live);
|
|
231649
|
+
if (currentGate.provenance === "default" && !currentGate.selectionAbsent) continue;
|
|
231650
|
+
const currentContent = await captureMergeContentDescriptor(live, { workspaceRootDir: this.options.rootDir, settings });
|
|
231651
|
+
if (currentContent.kind !== "singular" || live.workspaceWorktrees !== void 0) continue;
|
|
231652
|
+
const currentRecoverable = { ...live, status: void 0, paused: false };
|
|
231653
|
+
if (getTaskMergeBlocker(currentRecoverable, {
|
|
231654
|
+
reviewColumns: currentGate.reviewColumns,
|
|
231655
|
+
requiredPreMergeStepIds: currentGate.requiredPreMergeStepIds,
|
|
231656
|
+
mergeContent: currentContent
|
|
231657
|
+
}) !== PRE_MERGE_STEPS_NOT_RUN_BLOCKER) continue;
|
|
231658
|
+
if (await this.routeUnrunPreMergeGateBackToReview(
|
|
231659
|
+
live,
|
|
231660
|
+
currentGate.reviewColumns,
|
|
231661
|
+
currentGate,
|
|
231662
|
+
currentContent,
|
|
231663
|
+
expectedWorkflowSelection,
|
|
231664
|
+
failedPark
|
|
231665
|
+
)) {
|
|
231666
|
+
recoveredUnrunGateIds.add(task.id);
|
|
231667
|
+
}
|
|
231668
|
+
} catch (error2) {
|
|
231669
|
+
log34.warn(`Unrun pre-merge gate recovery deferred for ${task.id}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
231670
|
+
}
|
|
231671
|
+
}
|
|
230721
231672
|
const mergeable = tasks2.filter(
|
|
230722
|
-
(t) => mergeAdmissionByTaskId.get(t.id) === true && !t.paused && !executingIds.has(t.id) && t.status !== "failed" && // Exclude transient merge statuses. Active merges should be left alone;
|
|
231673
|
+
(t) => !recoveredUnrunGateIds.has(t.id) && mergeAdmissionByTaskId.get(t.id) === true && !t.paused && !executingIds.has(t.id) && t.status !== "failed" && // Exclude transient merge statuses. Active merges should be left alone;
|
|
230723
231674
|
// stale ones are handled by recoverStaleMergingStatus().
|
|
230724
231675
|
t.status !== "merging" && t.status !== "merging-pr" && // FNXC:Workspace 2026-06-22-09:30 (Phase D U1, KTD1 — admit workspace tasks):
|
|
230725
231676
|
// A workspace task has task.worktree===null (its worktrees live per-repo in
|
|
@@ -236596,49 +237547,6 @@ function isGenericAbortProvenance(provenance) {
|
|
|
236596
237547
|
return provenance === "hard-cancel" || provenance === "engine-abort";
|
|
236597
237548
|
}
|
|
236598
237549
|
|
|
236599
|
-
// ../engine/src/executor/workflow-step-satisfaction.ts
|
|
236600
|
-
function hasNonTerminalWorkflowSteps(task) {
|
|
236601
|
-
return task.steps.length > 0 && task.steps.some((step) => step.status !== "done" && step.status !== "skipped");
|
|
236602
|
-
}
|
|
236603
|
-
function workflowStepResultPassed(task, workflowStepId) {
|
|
236604
|
-
const results = task?.workflowStepResults ?? [];
|
|
236605
|
-
return results.some(
|
|
236606
|
-
(result) => result.workflowStepId === workflowStepId && result.phase === "pre-merge" && result.status === "passed"
|
|
236607
|
-
);
|
|
236608
|
-
}
|
|
236609
|
-
function areEnabledPreMergeWorkflowStepsSatisfied(task) {
|
|
236610
|
-
const preMergeGateIds = /* @__PURE__ */ new Set(["plan-review", "browser-verification", "code-review"]);
|
|
236611
|
-
const enabled = task?.enabledWorkflowSteps;
|
|
236612
|
-
const enabledPreMerge = Array.isArray(enabled) && enabled.length > 0 ? enabled.filter((id) => preMergeGateIds.has(id)) : ["plan-review", "code-review"];
|
|
236613
|
-
if (enabledPreMerge.length === 0) return false;
|
|
236614
|
-
if (Array.isArray(enabled) && enabledPreMerge.length !== enabled.length) return false;
|
|
236615
|
-
return enabledPreMerge.every((id) => workflowStepResultPassed(task, id));
|
|
236616
|
-
}
|
|
236617
|
-
function preservePreExecutionWorkflowStepResults(task) {
|
|
236618
|
-
const preserved = (task.workflowStepResults ?? []).filter((result) => result.workflowStepId === "plan-review");
|
|
236619
|
-
if (preserved.length > 0) return preserved;
|
|
236620
|
-
let latest;
|
|
236621
|
-
for (const entry of task.log ?? []) {
|
|
236622
|
-
if (entry.action === "[pre-merge] Workflow step completed: Plan Review") {
|
|
236623
|
-
latest = { timestamp: entry.timestamp, outcome: entry.outcome, status: "passed" };
|
|
236624
|
-
} else if (entry.action === "[pre-merge] Workflow step failed: Plan Review") {
|
|
236625
|
-
latest = { timestamp: entry.timestamp, outcome: entry.outcome, status: "failed" };
|
|
236626
|
-
}
|
|
236627
|
-
}
|
|
236628
|
-
if (latest?.status !== "passed") return [];
|
|
236629
|
-
return [
|
|
236630
|
-
{
|
|
236631
|
-
workflowStepId: "plan-review",
|
|
236632
|
-
workflowStepName: "Plan Review",
|
|
236633
|
-
phase: "pre-merge",
|
|
236634
|
-
status: "passed",
|
|
236635
|
-
verdict: "APPROVE",
|
|
236636
|
-
...latest.outcome ? { output: latest.outcome, notes: latest.outcome } : {},
|
|
236637
|
-
...latest.timestamp ? { startedAt: latest.timestamp, completedAt: latest.timestamp } : {}
|
|
236638
|
-
}
|
|
236639
|
-
];
|
|
236640
|
-
}
|
|
236641
|
-
|
|
236642
237550
|
// ../engine/src/executor/graph-resume-predicates.ts
|
|
236643
237551
|
function isTransientResumeAfterRestartGraphFailure(live, result) {
|
|
236644
237552
|
if ((result.reason ?? "").trim().length > 0) return false;
|
|
@@ -238742,6 +239650,8 @@ init_session_skill_context();
|
|
|
238742
239650
|
|
|
238743
239651
|
// ../engine/src/environment/environment-capabilities.ts
|
|
238744
239652
|
import { exec as exec39 } from "node:child_process";
|
|
239653
|
+
import { readFile as readFile47 } from "node:fs/promises";
|
|
239654
|
+
import { join as join86 } from "node:path";
|
|
238745
239655
|
import { promisify as promisify57 } from "node:util";
|
|
238746
239656
|
var execAsync36 = promisify57(exec39);
|
|
238747
239657
|
var BASELINE_ENVIRONMENT_CAPABILITY_COMMANDS = Object.freeze([
|
|
@@ -238764,17 +239674,23 @@ var BASELINE_ENVIRONMENT_CAPABILITY_COMMANDS = Object.freeze([
|
|
|
238764
239674
|
"dotnet",
|
|
238765
239675
|
"make",
|
|
238766
239676
|
"docker",
|
|
238767
|
-
"git"
|
|
239677
|
+
"git",
|
|
239678
|
+
"nix",
|
|
239679
|
+
"direnv",
|
|
239680
|
+
"devbox",
|
|
239681
|
+
"nvm",
|
|
239682
|
+
"fnm",
|
|
239683
|
+
"asdf",
|
|
239684
|
+
"mise"
|
|
238768
239685
|
]);
|
|
238769
239686
|
var DEFAULT_PROBE_TIMEOUT_MS = 5e3;
|
|
239687
|
+
var PROBE_CACHE_TTL_MS = 6e4;
|
|
238770
239688
|
var PROBE_MAX_BUFFER_BYTES = 64 * 1024;
|
|
238771
239689
|
var SAFE_COMMAND_NAME = /^[A-Za-z0-9._+/-]+$/;
|
|
238772
239690
|
var ENV_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
238773
239691
|
var probeCache = /* @__PURE__ */ new Map();
|
|
238774
239692
|
function unquoteToken(token) {
|
|
238775
|
-
if (token.length >= 2 && (token.startsWith('"') && token.endsWith('"') || token.startsWith("'") && token.endsWith("'")))
|
|
238776
|
-
return token.slice(1, -1);
|
|
238777
|
-
}
|
|
239693
|
+
if (token.length >= 2 && (token.startsWith('"') && token.endsWith('"') || token.startsWith("'") && token.endsWith("'"))) return token.slice(1, -1);
|
|
238778
239694
|
return token;
|
|
238779
239695
|
}
|
|
238780
239696
|
function extractCommandBinaries(command) {
|
|
@@ -238790,70 +239706,126 @@ function extractCommandBinaries(command) {
|
|
|
238790
239706
|
}
|
|
238791
239707
|
return [...new Set(binaries)];
|
|
238792
239708
|
}
|
|
238793
|
-
function
|
|
239709
|
+
function commandTokens(command) {
|
|
239710
|
+
if (/[;&|`$<>()[\]{}\\]/.test(command)) return null;
|
|
239711
|
+
const tokens2 = command.trim().split(/\s+/).map(unquoteToken).filter(Boolean);
|
|
239712
|
+
let index2 = tokens2[0] === "env" ? 1 : 0;
|
|
239713
|
+
while (index2 < tokens2.length && ENV_ASSIGNMENT.test(tokens2[index2])) index2 += 1;
|
|
239714
|
+
const result = tokens2.slice(index2);
|
|
239715
|
+
return result.length > 0 && result.every((token) => SAFE_COMMAND_NAME.test(token)) ? result : null;
|
|
239716
|
+
}
|
|
239717
|
+
function parseRecognizedWrapper(command) {
|
|
239718
|
+
if (!command) return null;
|
|
239719
|
+
const tokens2 = commandTokens(command);
|
|
239720
|
+
if (!tokens2) return null;
|
|
239721
|
+
const wrapper = tokens2[0];
|
|
239722
|
+
let prefix = null;
|
|
239723
|
+
let inner;
|
|
239724
|
+
if (wrapper === "nix" && tokens2[1] === "develop" && tokens2[2] === "--command") {
|
|
239725
|
+
prefix = ["nix", "develop", "--command"];
|
|
239726
|
+
inner = tokens2[3];
|
|
239727
|
+
} else if (wrapper === "devbox" && tokens2[1] === "run") {
|
|
239728
|
+
prefix = ["devbox", "run"];
|
|
239729
|
+
inner = tokens2[2];
|
|
239730
|
+
} else if ((wrapper === "nvm" || wrapper === "fnm") && tokens2[1] === "exec") {
|
|
239731
|
+
prefix = tokens2.slice(0, 3);
|
|
239732
|
+
inner = tokens2[3];
|
|
239733
|
+
} else if ((wrapper === "asdf" || wrapper === "mise") && tokens2[1] === "exec") {
|
|
239734
|
+
prefix = tokens2.slice(0, 2);
|
|
239735
|
+
inner = tokens2[2];
|
|
239736
|
+
} else if (wrapper === "direnv" && tokens2[1] === "exec" && tokens2[2]) {
|
|
239737
|
+
prefix = tokens2.slice(0, 3);
|
|
239738
|
+
inner = tokens2[3];
|
|
239739
|
+
}
|
|
239740
|
+
if (!prefix || !inner || !SAFE_COMMAND_NAME.test(inner)) return null;
|
|
239741
|
+
return { prefix, display: prefix.join(" "), innerCandidates: [inner] };
|
|
239742
|
+
}
|
|
239743
|
+
function collectCandidates(extraCommands, wrappers) {
|
|
238794
239744
|
return [.../* @__PURE__ */ new Set([
|
|
238795
239745
|
...BASELINE_ENVIRONMENT_CAPABILITY_COMMANDS,
|
|
238796
|
-
...extraCommands.map((name2) => name2.trim()).filter((name2) => SAFE_COMMAND_NAME.test(name2))
|
|
239746
|
+
...extraCommands.map((name2) => name2.trim()).filter((name2) => SAFE_COMMAND_NAME.test(name2)),
|
|
239747
|
+
...wrappers.flatMap((wrapper) => wrapper.innerCandidates)
|
|
238797
239748
|
])].sort((a, b3) => a.localeCompare(b3));
|
|
238798
239749
|
}
|
|
238799
239750
|
function quoteShellToken(token) {
|
|
238800
239751
|
return `'${token.replaceAll("'", "'\\''")}'`;
|
|
238801
239752
|
}
|
|
238802
239753
|
function buildProbeCommand(candidates) {
|
|
238803
|
-
|
|
238804
|
-
|
|
239754
|
+
return `for name in ${candidates.map(quoteShellToken).join(" ")}; do if command -v "$name" >/dev/null 2>&1; then printf '%s=1\\n' "$name"; else printf '%s=0\\n' "$name"; fi; done`;
|
|
239755
|
+
}
|
|
239756
|
+
function buildWrapperProbeCommand(wrapper, candidates) {
|
|
239757
|
+
return [...wrapper.prefix.map(quoteShellToken), quoteShellToken("/bin/bash"), quoteShellToken("-c"), quoteShellToken(buildProbeCommand(candidates))].join(" ");
|
|
238805
239758
|
}
|
|
238806
239759
|
function parseProbeOutput(stdout, candidates) {
|
|
238807
239760
|
const parsed = /* @__PURE__ */ new Map();
|
|
238808
239761
|
for (const line2 of stdout.split("\n").map((value) => value.trim()).filter(Boolean)) {
|
|
238809
239762
|
const match = /^(.+)=(0|1)$/.exec(line2);
|
|
238810
|
-
if (!match || !candidates.includes(match[1]) || parsed.has(match[1])) {
|
|
238811
|
-
return { capabilities: [], degraded: true };
|
|
238812
|
-
}
|
|
239763
|
+
if (!match || !candidates.includes(match[1]) || parsed.has(match[1])) return { capabilities: [], degraded: true };
|
|
238813
239764
|
parsed.set(match[1], match[2] === "1");
|
|
238814
239765
|
}
|
|
238815
239766
|
if (parsed.size !== candidates.length) return { capabilities: [], degraded: true };
|
|
238816
|
-
return {
|
|
238817
|
-
capabilities: candidates.map((name2) => ({ name: name2, available: parsed.get(name2) })),
|
|
238818
|
-
degraded: false
|
|
238819
|
-
};
|
|
239767
|
+
return { capabilities: candidates.map((name2) => ({ name: name2, available: parsed.get(name2) })), degraded: false };
|
|
238820
239768
|
}
|
|
238821
|
-
async function
|
|
239769
|
+
async function runProbe2(command, candidates, timeoutMs) {
|
|
238822
239770
|
try {
|
|
238823
|
-
const { stdout } = await execAsync36(
|
|
238824
|
-
timeout: timeoutMs,
|
|
238825
|
-
maxBuffer: PROBE_MAX_BUFFER_BYTES,
|
|
238826
|
-
encoding: "utf-8",
|
|
238827
|
-
shell: "/bin/bash"
|
|
238828
|
-
});
|
|
239771
|
+
const { stdout } = await execAsync36(command, { timeout: timeoutMs, maxBuffer: PROBE_MAX_BUFFER_BYTES, encoding: "utf-8", shell: "/bin/bash" });
|
|
238829
239772
|
return parseProbeOutput(stdout ?? "", candidates);
|
|
238830
239773
|
} catch {
|
|
238831
239774
|
return { capabilities: [], degraded: true };
|
|
238832
239775
|
}
|
|
238833
239776
|
}
|
|
238834
|
-
function
|
|
238835
|
-
|
|
238836
|
-
|
|
238837
|
-
|
|
238838
|
-
|
|
238839
|
-
|
|
238840
|
-
|
|
238841
|
-
}
|
|
239777
|
+
async function rootNixWrapper(rootDir) {
|
|
239778
|
+
if (!rootDir) return { wrapper: null, fingerprint: "no-root" };
|
|
239779
|
+
try {
|
|
239780
|
+
const content = await readFile47(join86(rootDir, "flake.nix"), "utf8");
|
|
239781
|
+
return { wrapper: { prefix: ["nix", "develop", "--command"], display: "nix develop --command", innerCandidates: [] }, fingerprint: `flake:${content.length}:${content.slice(0, 256)}` };
|
|
239782
|
+
} catch {
|
|
239783
|
+
return { wrapper: null, fingerprint: "no-flake" };
|
|
239784
|
+
}
|
|
239785
|
+
}
|
|
239786
|
+
async function probeUncached(candidates, wrappers, timeoutMs) {
|
|
239787
|
+
const host = await runProbe2(buildProbeCommand(candidates), candidates, timeoutMs);
|
|
239788
|
+
if (host.degraded) return host;
|
|
239789
|
+
const merged = new Map(host.capabilities.map((capability) => [capability.name, capability]));
|
|
239790
|
+
for (const wrapper of wrappers) {
|
|
239791
|
+
if (!merged.get(wrapper.prefix[0])?.available) continue;
|
|
239792
|
+
const wrapped = await runProbe2(buildWrapperProbeCommand(wrapper, candidates), candidates, timeoutMs);
|
|
239793
|
+
if (wrapped.degraded) continue;
|
|
239794
|
+
for (const capability of wrapped.capabilities) {
|
|
239795
|
+
if (capability.available && !merged.get(capability.name)?.available) merged.set(capability.name, { ...capability, via: wrapper.display });
|
|
239796
|
+
}
|
|
239797
|
+
}
|
|
239798
|
+
return { capabilities: candidates.map((candidate) => merged.get(candidate)), degraded: false };
|
|
239799
|
+
}
|
|
239800
|
+
async function probeEnvironmentCapabilities(options = {}) {
|
|
239801
|
+
const marker = await rootNixWrapper(options.rootDir);
|
|
239802
|
+
const configured = [parseRecognizedWrapper(options.testCommand), parseRecognizedWrapper(options.buildCommand)].filter((value) => value !== null);
|
|
239803
|
+
const wrappers = [...new Map([...configured, ...marker.wrapper ? [marker.wrapper] : []].map((wrapper) => [wrapper.display, wrapper])).values()];
|
|
239804
|
+
const candidates = collectCandidates(options.extraCommands ?? [], wrappers);
|
|
239805
|
+
const normalizedCommands = [options.testCommand?.trim() ?? "", options.buildCommand?.trim() ?? ""].join("\0");
|
|
239806
|
+
const cacheKey = [options.projectId ?? options.rootDir ?? "host", normalizedCommands, marker.fingerprint, candidates.join("\0")].join("");
|
|
239807
|
+
const now = options.now?.() ?? Date.now();
|
|
239808
|
+
const cached2 = probeCache.get(cacheKey);
|
|
239809
|
+
if (cached2 && now - cached2.createdAt < PROBE_CACHE_TTL_MS) return cached2.promise;
|
|
239810
|
+
const promise = probeUncached(candidates, wrappers, options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS);
|
|
239811
|
+
probeCache.set(cacheKey, { createdAt: now, promise });
|
|
238842
239812
|
return promise;
|
|
238843
239813
|
}
|
|
238844
239814
|
function formatEnvironmentCapabilitiesSection(probe) {
|
|
238845
239815
|
if (probe.degraded || probe.capabilities.length === 0) return "";
|
|
238846
|
-
const available2 = probe.capabilities.filter((capability) => capability.available).map(({ name: name2 }) => name2);
|
|
239816
|
+
const available2 = probe.capabilities.filter((capability) => capability.available && !capability.via).map(({ name: name2 }) => name2);
|
|
239817
|
+
const wrapperAvailable = probe.capabilities.filter((capability) => capability.available && capability.via).map(({ name: name2, via }) => `${name2} via ${via}`);
|
|
238847
239818
|
const unavailable2 = probe.capabilities.filter((capability) => !capability.available).map(({ name: name2 }) => name2);
|
|
238848
239819
|
return [
|
|
238849
239820
|
"## Environment Capabilities",
|
|
238850
239821
|
"",
|
|
238851
|
-
`
|
|
238852
|
-
`
|
|
239822
|
+
`Host-available commands: ${available2.length > 0 ? available2.join(", ") : "none"}`,
|
|
239823
|
+
`Available via confirmed project wrapper: ${wrapperAvailable.length > 0 ? wrapperAvailable.join(", ") : "none"}`,
|
|
239824
|
+
`Confirmed-unavailable commands: ${unavailable2.length > 0 ? unavailable2.join(", ") : "none"}`,
|
|
238853
239825
|
"",
|
|
238854
|
-
"-
|
|
238855
|
-
"-
|
|
238856
|
-
"-
|
|
239826
|
+
"- Only the commands listed here were probed. Any unlisted command is UNKNOWN, not absent.",
|
|
239827
|
+
"- A documented or configured command using a confirmed wrapper remains runnable and binding even if its inner executable is absent from the host PATH.",
|
|
239828
|
+
"- Only a confirmed-unavailable runtime without an applicable confirmed wrapper needs a runnable substitute; record an ideal impossible check under `## Environment Constraints` as explicitly non-blocking."
|
|
238857
239829
|
].join("\n");
|
|
238858
239830
|
}
|
|
238859
239831
|
|
|
@@ -240454,16 +241426,16 @@ init_effective_settings();
|
|
|
240454
241426
|
// ../engine/src/executor/workflow-step-failure-injection.ts
|
|
240455
241427
|
init_src2();
|
|
240456
241428
|
init_logger();
|
|
240457
|
-
import { join as
|
|
240458
|
-
import { readFile as
|
|
241429
|
+
import { join as join87 } from "node:path";
|
|
241430
|
+
import { readFile as readFile48, writeFile as writeFile27 } from "node:fs/promises";
|
|
240459
241431
|
var ADVISORY_SECTION_HEADER = "## Review Advisory Notes";
|
|
240460
241432
|
async function injectReviewAdvisoryNotes(store, task, stepName, findings) {
|
|
240461
241433
|
findings = findings.filter(isOpenWorkflowReviewFinding);
|
|
240462
241434
|
if (findings.length === 0) return;
|
|
240463
|
-
const promptPath =
|
|
241435
|
+
const promptPath = join87(store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
240464
241436
|
let content;
|
|
240465
241437
|
try {
|
|
240466
|
-
content = await
|
|
241438
|
+
content = await readFile48(promptPath, "utf-8");
|
|
240467
241439
|
} catch {
|
|
240468
241440
|
executorLog.warn(`${task.id}: PROMPT.md not found at ${promptPath}, skipping review advisory injection`);
|
|
240469
241441
|
return;
|
|
@@ -240487,10 +241459,10 @@ ${section}`;
|
|
|
240487
241459
|
}
|
|
240488
241460
|
}
|
|
240489
241461
|
async function injectWorkflowStepFailureInstructions(store, task, failureFeedback, stepName, retry, findings) {
|
|
240490
|
-
const promptPath =
|
|
241462
|
+
const promptPath = join87(store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
240491
241463
|
let content;
|
|
240492
241464
|
try {
|
|
240493
|
-
content = await
|
|
241465
|
+
content = await readFile48(promptPath, "utf-8");
|
|
240494
241466
|
} catch {
|
|
240495
241467
|
executorLog.warn(`${task.id}: PROMPT.md not found at ${promptPath}, skipping workflow failure injection`);
|
|
240496
241468
|
return;
|
|
@@ -240568,7 +241540,7 @@ ${scopeGuard}
|
|
|
240568
241540
|
}
|
|
240569
241541
|
|
|
240570
241542
|
// ../engine/src/executor/skill-path-helpers.ts
|
|
240571
|
-
import { basename as basename23, join as
|
|
241543
|
+
import { basename as basename23, join as join88 } from "node:path";
|
|
240572
241544
|
import { existsSync as existsSync68 } from "node:fs";
|
|
240573
241545
|
function mergeAdditionalSkillPaths2(...pathGroups) {
|
|
240574
241546
|
const merged = Array.from(new Set(pathGroups.flatMap((paths2) => paths2 ?? [])));
|
|
@@ -240581,7 +241553,7 @@ function isWorkflowStepSkillDiscoverable(skillName, additionalSkillPaths, ceSkil
|
|
|
240581
241553
|
return false;
|
|
240582
241554
|
}
|
|
240583
241555
|
return (additionalSkillPaths ?? []).some(
|
|
240584
|
-
(skillPath) => basename23(skillPath) === bareSkillName && existsSync68(
|
|
241556
|
+
(skillPath) => basename23(skillPath) === bareSkillName && existsSync68(join88(skillPath, "SKILL.md")) || existsSync68(join88(skillPath, bareSkillName, "SKILL.md"))
|
|
240585
241557
|
);
|
|
240586
241558
|
}
|
|
240587
241559
|
|
|
@@ -240860,7 +241832,11 @@ async function executeWorkflowStep(deps, task, workflowStep, worktreePath, setti
|
|
|
240860
241832
|
extraCommands: [
|
|
240861
241833
|
...extractCommandBinaries(settings.testCommand),
|
|
240862
241834
|
...extractCommandBinaries(settings.buildCommand)
|
|
240863
|
-
]
|
|
241835
|
+
],
|
|
241836
|
+
testCommand: settings.testCommand,
|
|
241837
|
+
buildCommand: settings.buildCommand,
|
|
241838
|
+
rootDir: deps.rootDir,
|
|
241839
|
+
projectId: deps.store.getProjectId?.() ?? deps.rootDir
|
|
240864
241840
|
}).catch(() => ({ capabilities: [], degraded: true })) : void 0;
|
|
240865
241841
|
const planReviewEnvironmentCapabilitiesBlock = planReviewEnvironmentCapabilities ? formatEnvironmentCapabilitiesSection(planReviewEnvironmentCapabilities) : "";
|
|
240866
241842
|
if (isReviewTypeWorkflowStep && !workflowReviewSpecText.trim()) {
|
|
@@ -241717,7 +242693,7 @@ If you have just finished a step's work, immediately call \`fn_task_update\` to
|
|
|
241717
242693
|
|
|
241718
242694
|
The user is not watching this conversation in real-time. They will read the final result. Asking permission wastes a full retry cycle and may orphan committed work.
|
|
241719
242695
|
|
|
241720
|
-
**Cannot proceed \u2014 the honest blocked exit.** If the work genuinely cannot be finished because of a host-resource, network, model-provider, or credential failure, or because a prerequisite Fusion task is incomplete, call \`fn_task_done(outcome="blocked", reason="<concrete blocker + what would unblock it>", blockedBy=["FN-XXXX"])\`. Missing tooling, optional services, and unrunnable commands are not blocked exits: resolve them, substitute a runnable check, or complete achievable work and record the deferred verification. A classified external block freezes in place; task dependencies park durable failed WITHOUT auto-replan and requeue when those tasks complete. Blockers must be Fusion board tasks \u2014 do NOT treat open GitHub PRs touching the same files as blockers; other PRs are not claims on your file scope. Do NOT skip remaining steps to fake completion.
|
|
242696
|
+
**Cannot proceed \u2014 the honest blocked exit.** If the work genuinely cannot be finished because of a host-resource, network, model-provider, or credential failure, or because a prerequisite Fusion task is incomplete, call \`fn_task_done(outcome="blocked", reason="<concrete blocker + what would unblock it>", blockedBy=["FN-XXXX"])\`. Missing tooling, optional services, and unrunnable commands are not blocked exits: resolve them, substitute a runnable check, or complete achievable work and record the deferred verification. A classified external block freezes in place; task dependencies park durable failed WITHOUT auto-replan and requeue when those tasks complete. For an accepted external block, include \`blockedReport\` with concise verified condition, stop reason, unimplemented work, and precise unblock condition; never include prompts, tool logs, stack traces, or credentials. Blockers must be Fusion board tasks \u2014 do NOT treat open GitHub PRs touching the same files as blockers; other PRs are not claims on your file scope. Do NOT skip remaining steps to fake completion.
|
|
241721
242697
|
This is THE correct action when you are stuck \u2014 do NOT instead mark the remaining steps \`skipped\` and call \`fn_task_done\` to make the task look finished. Skipping steps to escape a blocker launders a failure into \`done\` and is never the right move. (\`skipped\` remains valid only for the stale-premise path below, when the requested work is already present on HEAD.) Never write the blocker as plain prose.
|
|
241722
242698
|
|
|
241723
242699
|
## How to work
|
|
@@ -242147,7 +243123,7 @@ Use \`fn_task_create\` for truly separate follow-up work, including unrelated/pr
|
|
|
242147
243123
|
Commit at step boundaries: \`git commit -m "feat(${task.id}): complete Step N \u2014 <short summary>"${sourceIssueRef ? ` -m "Ref: ${sourceIssueRef}"` : ""}${authorArg}\`
|
|
242148
243124
|
The \`<short summary>\` is required \u2014 replace it with a concrete 5\u201310 word description of what the step changed.
|
|
242149
243125
|
When all steps are complete: call \`fn_task_done()\`
|
|
242150
|
-
For \`fn_task_done(outcome="blocked")\`, reserve \`obstacle:"outside-worktree"\` for host-resource, network, model-provider, and credential failures. An unavailable command, interpreter, optional service, or unrunnable test is never a blocked exit: resolve it with project tooling or an available equivalent, substitute a runnable automated check, or\u2014when it does not prevent the task objective\u2014complete the achievable work and record the deferred verification.
|
|
243126
|
+
For \`fn_task_done(outcome="blocked")\`, reserve \`obstacle:"outside-worktree"\` for host-resource, network, model-provider, and credential failures. An unavailable command, interpreter, optional service, or unrunnable test is never a blocked exit: resolve it with project tooling or an available equivalent, substitute a runnable automated check, or\u2014when it does not prevent the task objective\u2014complete the achievable work and record the deferred verification. Treat an \`## Environment Constraints\` substitute as sanctioned only when the accompanying capability block confirms its absence premise. Re-evaluate a stale host-only downgrade when the documented command uses a detected or configured confirmed wrapper; that wrapper command remains binding. Set \`obstacle:"inside-worktree"\` for code, tests, lint, types, plan, merge, or review failures so AI self-repair remains active.
|
|
242151
243127
|
|
|
242152
243128
|
If a build command is configured, run that exact command in this worktree before calling \`fn_task_done()\`.
|
|
242153
243129
|
Treat a non-zero exit code as a blocking failure. Do not claim success without a real passing run.
|
|
@@ -243112,7 +244088,7 @@ import { existsSync as existsSync74 } from "node:fs";
|
|
|
243112
244088
|
|
|
243113
244089
|
// ../engine/src/executor/session-worktree-paths.ts
|
|
243114
244090
|
init_worktree_paths();
|
|
243115
|
-
import { readFile as
|
|
244091
|
+
import { readFile as readFile49 } from "node:fs/promises";
|
|
243116
244092
|
import { realpathSync as realpathSync12 } from "node:fs";
|
|
243117
244093
|
import { resolve as resolvePath6 } from "node:path";
|
|
243118
244094
|
function canonicalizePath2(path6) {
|
|
@@ -243137,7 +244113,7 @@ function normalizeWorktreePath(pathValue) {
|
|
|
243137
244113
|
}
|
|
243138
244114
|
async function extractPersistedSessionWorktreePath(sessionFile, rootDir, settings) {
|
|
243139
244115
|
try {
|
|
243140
|
-
const content = await
|
|
244116
|
+
const content = await readFile49(sessionFile, "utf-8");
|
|
243141
244117
|
const matches = content.match(buildSessionWorktreePathRegex(rootDir, settings)) ?? [];
|
|
243142
244118
|
if (matches.length === 0) return null;
|
|
243143
244119
|
const normalizedCounts = /* @__PURE__ */ new Map();
|
|
@@ -244644,9 +245620,6 @@ async function handleImplicitTaskDoneRefusal(deps, task, refusal) {
|
|
|
244644
245620
|
...taintUpdate,
|
|
244645
245621
|
paused: false,
|
|
244646
245622
|
pausedByAgentId: null,
|
|
244647
|
-
worktree: null,
|
|
244648
|
-
branch: null,
|
|
244649
|
-
branchWriteOrigin: "engine",
|
|
244650
245623
|
sessionFile: null
|
|
244651
245624
|
});
|
|
244652
245625
|
await deps.store.logEntry(
|
|
@@ -244664,9 +245637,6 @@ async function handleImplicitTaskDoneRefusal(deps, task, refusal) {
|
|
|
244664
245637
|
...taintUpdate,
|
|
244665
245638
|
paused: false,
|
|
244666
245639
|
pausedByAgentId: null,
|
|
244667
|
-
worktree: null,
|
|
244668
|
-
branch: null,
|
|
244669
|
-
branchWriteOrigin: "engine",
|
|
244670
245640
|
sessionFile: null
|
|
244671
245641
|
});
|
|
244672
245642
|
await deps.store.logEntry(task.id, `${refusal.message} \u2014 execution failed because implicit fn_task_done was refused`, void 0, deps.getRunContextFor(task.id));
|
|
@@ -245622,7 +246592,15 @@ async function ensureWorkflowMergeBoundaryTask(deps, task, metadata) {
|
|
|
245622
246592
|
await deps.store.logEntry(live.id, `Workflow merge boundary blocked: ${blocked.reason}`, void 0, deps.getRunContextFor(live.id));
|
|
245623
246593
|
return {
|
|
245624
246594
|
task: live,
|
|
245625
|
-
blocked: {
|
|
246595
|
+
blocked: {
|
|
246596
|
+
...blocked,
|
|
246597
|
+
missingInstanceCount: mergeProof.missingInstanceIds.length,
|
|
246598
|
+
evidence: {
|
|
246599
|
+
code: blocked.code,
|
|
246600
|
+
nonTerminalNodeId: mergeProof.nonTerminalResult?.workflowStepId,
|
|
246601
|
+
missingInstanceIds: mergeProof.missingInstanceIds
|
|
246602
|
+
}
|
|
246603
|
+
}
|
|
245626
246604
|
};
|
|
245627
246605
|
}
|
|
245628
246606
|
if (deps.shouldCompleteChecklistAtWorkflowMerge(live, mergeProof)) {
|
|
@@ -245944,6 +246922,7 @@ async function performWorkflowRerunBounce(deps, taskId, worktreePath, preserveRe
|
|
|
245944
246922
|
|
|
245945
246923
|
// ../engine/src/executor/unpause-resume.ts
|
|
245946
246924
|
init_logger();
|
|
246925
|
+
init_scheduler();
|
|
245947
246926
|
async function dispatchUnpauseResume(deps, task) {
|
|
245948
246927
|
if (task.status === "failed") {
|
|
245949
246928
|
return false;
|
|
@@ -245959,6 +246938,13 @@ async function dispatchUnpauseResume(deps, task) {
|
|
|
245959
246938
|
executorLog.debug(`Skipping unpause resume for ${task.id} \u2014 ${pauseLabel} active`);
|
|
245960
246939
|
return false;
|
|
245961
246940
|
}
|
|
246941
|
+
if (task.dependencies?.length) {
|
|
246942
|
+
const tasks2 = await deps.store.listTasks({ includeArchived: false, slim: true });
|
|
246943
|
+
const liveTask = tasks2.find((candidate) => candidate.id === task.id) ?? task;
|
|
246944
|
+
if (getUnmetSchedulingDependencies(liveTask, tasks2).length > 0) {
|
|
246945
|
+
return false;
|
|
246946
|
+
}
|
|
246947
|
+
}
|
|
245962
246948
|
if (deps.executing.has(task.id) || deps.recoveringCompleted.has(task.id) || deps.activeSessions.has(task.id) || deps.activeStepExecutors.has(task.id) || deps.activeWorkflowStepSessions.has(task.id) || deps.graphRouting.has(task.id)) {
|
|
245963
246949
|
return false;
|
|
245964
246950
|
}
|
|
@@ -248338,8 +249324,8 @@ async function resolveSeamColumnAgent(deps, task, detail) {
|
|
|
248338
249324
|
init_src2();
|
|
248339
249325
|
init_logger();
|
|
248340
249326
|
import { setImmediate as setImmediateCb3 } from "node:timers";
|
|
248341
|
-
import { readFile as
|
|
248342
|
-
import { join as
|
|
249327
|
+
import { readFile as readFile50, rm as rm13 } from "node:fs/promises";
|
|
249328
|
+
import { join as join89 } from "node:path";
|
|
248343
249329
|
|
|
248344
249330
|
// ../engine/src/executor/resume-orphan-delay.ts
|
|
248345
249331
|
function getResumeOrphanDelayMs() {
|
|
@@ -248385,12 +249371,12 @@ async function resumeOrphaned(deps) {
|
|
|
248385
249371
|
for (const task of inProgress) {
|
|
248386
249372
|
if (yieldNext) await yieldEventLoop3();
|
|
248387
249373
|
yieldNext = true;
|
|
248388
|
-
const tasksDir = typeof deps.store.getTasksDir === "function" ? deps.store.getTasksDir() : deps.rootDir ?
|
|
248389
|
-
const intentPath = tasksDir ?
|
|
249374
|
+
const tasksDir = typeof deps.store.getTasksDir === "function" ? deps.store.getTasksDir() : deps.rootDir ? join89(deps.rootDir, ".fusion", "tasks") : void 0;
|
|
249375
|
+
const intentPath = tasksDir ? join89(tasksDir, task.id, "deferred-terminal-park.json") : void 0;
|
|
248390
249376
|
if (intentPath) {
|
|
248391
249377
|
let raw;
|
|
248392
249378
|
try {
|
|
248393
|
-
raw = await
|
|
249379
|
+
raw = await readFile50(intentPath, "utf-8");
|
|
248394
249380
|
} catch (error2) {
|
|
248395
249381
|
if (error2?.code !== "ENOENT") {
|
|
248396
249382
|
executorLog.warn(`${task.id}: deferred terminal-park intent could not be read (${error2 instanceof Error ? error2.message : String(error2)}) \u2014 retaining intent and skipping orphan recovery`);
|
|
@@ -250398,11 +251384,11 @@ init_logger();
|
|
|
250398
251384
|
// ../engine/src/cli-agent/task-session.ts
|
|
250399
251385
|
import { mkdtemp as mkdtemp3, rm as rm15 } from "node:fs/promises";
|
|
250400
251386
|
import { tmpdir as tmpdir10 } from "node:os";
|
|
250401
|
-
import { join as
|
|
251387
|
+
import { join as join91 } from "node:path";
|
|
250402
251388
|
|
|
250403
251389
|
// ../engine/src/cli-agent/hook-scripts.ts
|
|
250404
251390
|
import { mkdir as mkdir31, rm as rm14, writeFile as writeFile28 } from "node:fs/promises";
|
|
250405
|
-
import { join as
|
|
251391
|
+
import { join as join90 } from "node:path";
|
|
250406
251392
|
var HOOK_SCRIPT_NAMES = {
|
|
250407
251393
|
/** Main hook script: POSTs stdin payload (with the hook event name) to the route. */
|
|
250408
251394
|
hook: "fusion-hook.sh",
|
|
@@ -250484,8 +251470,8 @@ exit 0
|
|
|
250484
251470
|
async function writeSessionHookScripts(opts) {
|
|
250485
251471
|
const { sessionId, token, endpointUrl, dir } = opts;
|
|
250486
251472
|
await mkdir31(dir, { recursive: true, mode: 448 });
|
|
250487
|
-
const hookScriptPath =
|
|
250488
|
-
const notifyScriptPath =
|
|
251473
|
+
const hookScriptPath = join90(dir, HOOK_SCRIPT_NAMES.hook);
|
|
251474
|
+
const notifyScriptPath = join90(dir, HOOK_SCRIPT_NAMES.notify);
|
|
250489
251475
|
await writeFile28(hookScriptPath, buildHookScriptContent({ sessionId, token, endpointUrl }), {
|
|
250490
251476
|
mode: 448
|
|
250491
251477
|
});
|
|
@@ -250661,9 +251647,9 @@ var CliTaskSession = class _CliTaskSession {
|
|
|
250661
251647
|
isApproved: opts.isAutonomyApproved ?? (() => false)
|
|
250662
251648
|
});
|
|
250663
251649
|
const root = opts.hookDirRoot ?? tmpdir10();
|
|
250664
|
-
const hookDir = await mkdtemp3(
|
|
250665
|
-
const hookScriptPath =
|
|
250666
|
-
const settingsPath =
|
|
251650
|
+
const hookDir = await mkdtemp3(join91(root, "fusion-cli-hooks-"));
|
|
251651
|
+
const hookScriptPath = join91(hookDir, HOOK_SCRIPT_NAMES.hook);
|
|
251652
|
+
const settingsPath = join91(hookDir, "settings.json");
|
|
250667
251653
|
const cliAgentSettings = opts.config.cliAgentSettings ?? null;
|
|
250668
251654
|
const operatorEnvAdditions = (cliAgentSettings?.envAdditions ?? []).filter(
|
|
250669
251655
|
(k2) => !/^FUSION_/i.test(k2)
|
|
@@ -250917,7 +251903,7 @@ function killLiveTaskSessions(taskId, manager, store) {
|
|
|
250917
251903
|
init_logger();
|
|
250918
251904
|
import * as fs8 from "node:fs";
|
|
250919
251905
|
import { createRequire as createRequire7 } from "node:module";
|
|
250920
|
-
import { join as
|
|
251906
|
+
import { join as join92, dirname as dirname43 } from "node:path";
|
|
250921
251907
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
250922
251908
|
var log35 = createLogger("terminal");
|
|
250923
251909
|
var ptyModule = null;
|
|
@@ -250949,16 +251935,16 @@ function findInstalledNodePtyNativeDir() {
|
|
|
250949
251935
|
try {
|
|
250950
251936
|
const umbrellaEntry = require4.resolve("node-pty");
|
|
250951
251937
|
const umbrellaRoot = dirname43(umbrellaEntry);
|
|
250952
|
-
const releaseDir =
|
|
250953
|
-
if (fs8.existsSync(
|
|
251938
|
+
const releaseDir = join92(umbrellaRoot, "build", "Release");
|
|
251939
|
+
if (fs8.existsSync(join92(releaseDir, "pty.node"))) return releaseDir;
|
|
250954
251940
|
const packageName = nodePtyPlatformPackageName();
|
|
250955
251941
|
const nativeEntry = nodePtyRequiredNativeEntry();
|
|
250956
251942
|
if (!packageName || !nativeEntry) return null;
|
|
250957
251943
|
const platformRequire = createRequire7(umbrellaEntry);
|
|
250958
251944
|
const platformEntry = platformRequire.resolve(packageName);
|
|
250959
251945
|
const platformRoot = dirname43(dirname43(platformEntry));
|
|
250960
|
-
const prebuildDir =
|
|
250961
|
-
return fs8.existsSync(
|
|
251946
|
+
const prebuildDir = join92(platformRoot, "prebuilds", getNativePrebuildName());
|
|
251947
|
+
return fs8.existsSync(join92(prebuildDir, nativeEntry)) ? prebuildDir : null;
|
|
250962
251948
|
} catch {
|
|
250963
251949
|
return null;
|
|
250964
251950
|
}
|
|
@@ -250968,14 +251954,14 @@ function findStagedNativeDir(platform3 = process.platform, arch = process.arch)
|
|
|
250968
251954
|
const nativeEntry = nodePtyRequiredNativeEntry(platform3);
|
|
250969
251955
|
if (!nativeEntry) return null;
|
|
250970
251956
|
if (process.env.FUSION_RUNTIME_DIR) {
|
|
250971
|
-
const envPath =
|
|
250972
|
-
if (fs8.existsSync(
|
|
251957
|
+
const envPath = join92(process.env.FUSION_RUNTIME_DIR, prebuildName);
|
|
251958
|
+
if (fs8.existsSync(join92(envPath, nativeEntry))) {
|
|
250973
251959
|
return envPath;
|
|
250974
251960
|
}
|
|
250975
251961
|
}
|
|
250976
251962
|
const execDir = dirname43(process.execPath);
|
|
250977
|
-
const nextToBinary =
|
|
250978
|
-
if (fs8.existsSync(
|
|
251963
|
+
const nextToBinary = join92(execDir, "runtime", prebuildName);
|
|
251964
|
+
if (fs8.existsSync(join92(nextToBinary, nativeEntry))) {
|
|
250979
251965
|
return nextToBinary;
|
|
250980
251966
|
}
|
|
250981
251967
|
return null;
|
|
@@ -250998,8 +251984,8 @@ function ensureNodePtyNativePermissions() {
|
|
|
250998
251984
|
candidateDirs.add(installedNativeDir);
|
|
250999
251985
|
}
|
|
251000
251986
|
for (const nativeDir of candidateDirs) {
|
|
251001
|
-
const helperPath =
|
|
251002
|
-
const nativeModulePath =
|
|
251987
|
+
const helperPath = join92(nativeDir, "spawn-helper");
|
|
251988
|
+
const nativeModulePath = join92(nativeDir, "pty.node");
|
|
251003
251989
|
try {
|
|
251004
251990
|
fs8.chmodSync(helperPath, 493);
|
|
251005
251991
|
} catch {
|
|
@@ -251028,7 +252014,7 @@ async function loadPtyModule() {
|
|
|
251028
252014
|
process.env.NODE_PTY_SPAWN_HELPER_DIR = nativeDir;
|
|
251029
252015
|
}
|
|
251030
252016
|
process.env.FUSION_NATIVE_ASSETS_PATH = nativeDir;
|
|
251031
|
-
const stagedUmbrellaEntry =
|
|
252017
|
+
const stagedUmbrellaEntry = join92(nativeDir, "node-pty-umbrella", "index.js");
|
|
251032
252018
|
if (fs8.existsSync(stagedUmbrellaEntry)) {
|
|
251033
252019
|
try {
|
|
251034
252020
|
const stagedImport = await import(pathToFileURL2(stagedUmbrellaEntry).href);
|
|
@@ -252557,7 +253543,7 @@ async function runGraphTaskStep(deps, task, stepIndex, instanceId, governingNode
|
|
|
252557
253543
|
// ../engine/src/executor/get-authoritative-assigned-agent.ts
|
|
252558
253544
|
init_src2();
|
|
252559
253545
|
init_logger();
|
|
252560
|
-
import { join as
|
|
253546
|
+
import { join as join93 } from "node:path";
|
|
252561
253547
|
async function getAuthoritativeAssignedAgent(deps, assignedAgentId) {
|
|
252562
253548
|
const normalizedId = assignedAgentId?.trim();
|
|
252563
253549
|
if (!normalizedId) return null;
|
|
@@ -252568,7 +253554,7 @@ async function getAuthoritativeAssignedAgent(deps, assignedAgentId) {
|
|
|
252568
253554
|
let authoritativeStore = deps.getAuthoritativeAssignedAgentStore();
|
|
252569
253555
|
if (!authoritativeStore || authoritativeAgentLayer && !authoritativeStore.backendMode) {
|
|
252570
253556
|
authoritativeStore = new AgentStore({
|
|
252571
|
-
rootDir:
|
|
253557
|
+
rootDir: join93(deps.rootDir, ".fusion"),
|
|
252572
253558
|
taskStore: deps.store,
|
|
252573
253559
|
...authoritativeAgentLayer ? { asyncLayer: authoritativeAgentLayer } : {}
|
|
252574
253560
|
});
|
|
@@ -254057,107 +255043,6 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
254057
255043
|
// ../engine/src/executor/create-task-done-tool.ts
|
|
254058
255044
|
init_src2();
|
|
254059
255045
|
import { Type as Type8 } from "@earendil-works/pi-ai";
|
|
254060
|
-
|
|
254061
|
-
// ../engine/src/execution-block-classifier.ts
|
|
254062
|
-
init_usage_limit_detector();
|
|
254063
|
-
init_transient_error_detector();
|
|
254064
|
-
var HOST_EXTERNAL_CODES = ["ENOSPC", "EDQUOT", "EROFS", "ENOMEM", "EMFILE", "ENFILE"];
|
|
254065
|
-
var NETWORK_EXTERNAL_CODES = ["ECONNREFUSED", "ECONNRESET", "ETIMEDOUT", "EAI_AGAIN", "ENOTFOUND"];
|
|
254066
|
-
function classifyExternalObstacle(message) {
|
|
254067
|
-
const normalized3 = message.trim();
|
|
254068
|
-
if (!normalized3) return void 0;
|
|
254069
|
-
if (/Forbidden lifecycle path|rejected by the task lifecycle projection|TransitionRejectionError/i.test(normalized3)) {
|
|
254070
|
-
return void 0;
|
|
254071
|
-
}
|
|
254072
|
-
for (const code of HOST_EXTERNAL_CODES) {
|
|
254073
|
-
if (new RegExp(`\\b${code}\\b`, "i").test(normalized3)) return { origin: "host-environment", code };
|
|
254074
|
-
}
|
|
254075
|
-
for (const code of NETWORK_EXTERNAL_CODES) {
|
|
254076
|
-
if (new RegExp(`\\b${code}\\b`, "i").test(normalized3)) return { origin: "network", code };
|
|
254077
|
-
}
|
|
254078
|
-
if (/socket hang up/i.test(normalized3)) return { origin: "network", code: "SOCKET_HANG_UP" };
|
|
254079
|
-
if (isTransientAuthCredentialError(normalized3)) return void 0;
|
|
254080
|
-
if (isUsageLimitError(normalized3)) return { origin: "model-provider", code: "USAGE_LIMIT" };
|
|
254081
|
-
if (isOperatorActionableAgentError(normalized3)) return { origin: "credentials", code: "CREDENTIALS" };
|
|
254082
|
-
return void 0;
|
|
254083
|
-
}
|
|
254084
|
-
function detectRepairableObstacleHint(message) {
|
|
254085
|
-
const normalized3 = message.trim();
|
|
254086
|
-
if (!normalized3) return void 0;
|
|
254087
|
-
const missingSignal = /\b(?:command not found|not installed|is not recognized as an internal or external command)\b|\bneither\b.{0,80}\binstalled\b/i;
|
|
254088
|
-
const missingFileSignal = /(?:\b(?:spawn|exec(?:ute)?|binary|executable|interpreter|command)\b.{0,80}\b(?:ENOENT|no such file or directory)\b|\bENOENT\b.{0,80}\b(?:binary|executable|interpreter|command)\b)/i;
|
|
254089
|
-
if (!missingSignal.test(normalized3) && !missingFileSignal.test(normalized3)) return void 0;
|
|
254090
|
-
return /\b(?:service|daemon)\b/i.test(normalized3) ? "missing-service" : "missing-tooling";
|
|
254091
|
-
}
|
|
254092
|
-
var TASK_ID_RE = /^[A-Z][A-Z0-9]*-\d+$/i;
|
|
254093
|
-
var LEGACY_PR_REF_RE = /^(?:pr[:#-]|#|PR-)(\d+)$/i;
|
|
254094
|
-
function partitionBlockedByRefs(blockedBy) {
|
|
254095
|
-
const taskIds = [];
|
|
254096
|
-
for (const raw of blockedBy) {
|
|
254097
|
-
const id = raw.trim();
|
|
254098
|
-
if (!id) continue;
|
|
254099
|
-
if (LEGACY_PR_REF_RE.test(id)) continue;
|
|
254100
|
-
if (TASK_ID_RE.test(id)) {
|
|
254101
|
-
const m2 = id.toUpperCase().match(/^([A-Z][A-Z0-9]*)-(\d+)$/);
|
|
254102
|
-
taskIds.push(m2 ? `${m2[1]}-${m2[2]}` : id.toUpperCase());
|
|
254103
|
-
}
|
|
254104
|
-
}
|
|
254105
|
-
return { taskIds: [...new Set(taskIds)] };
|
|
254106
|
-
}
|
|
254107
|
-
function classifyBlockedExit(_reason, blockedBy = []) {
|
|
254108
|
-
const { taskIds } = partitionBlockedByRefs(blockedBy);
|
|
254109
|
-
if (taskIds.length > 0) {
|
|
254110
|
-
return {
|
|
254111
|
-
class: "external",
|
|
254112
|
-
thrashSignature: `tasks:${taskIds.slice().sort().join(",")}`
|
|
254113
|
-
};
|
|
254114
|
-
}
|
|
254115
|
-
return {
|
|
254116
|
-
class: "plan-defect",
|
|
254117
|
-
thrashSignature: "plan-defect"
|
|
254118
|
-
};
|
|
254119
|
-
}
|
|
254120
|
-
var BLOCKED_THRASH_LIMIT = 3;
|
|
254121
|
-
var BLOCKED_THRASH_WINDOW_MS = 60 * 60 * 1e3;
|
|
254122
|
-
function countBlockedThrashHits(log42, signature, nowMs = Date.now(), windowMs = BLOCKED_THRASH_WINDOW_MS) {
|
|
254123
|
-
if (!log42?.length) return 0;
|
|
254124
|
-
if (signature === "plan-defect") return 0;
|
|
254125
|
-
const cutoff = nowMs - windowMs;
|
|
254126
|
-
let count2 = 0;
|
|
254127
|
-
for (const entry of log42) {
|
|
254128
|
-
const ts4 = entry.timestamp ? Date.parse(entry.timestamp) : Number.NaN;
|
|
254129
|
-
if (Number.isFinite(ts4) && ts4 < cutoff) continue;
|
|
254130
|
-
const action = entry.action ?? "";
|
|
254131
|
-
if (!action.startsWith("BLOCKED:") && !action.includes("DUPLICATE_REPLAN_EXHAUSTED") && !action.includes("BLOCKED_THRASH")) {
|
|
254132
|
-
if (!action.includes("parked failed (honest blocked exit") && !action.includes("durable external block")) {
|
|
254133
|
-
continue;
|
|
254134
|
-
}
|
|
254135
|
-
}
|
|
254136
|
-
for (const part of signature.split("|")) {
|
|
254137
|
-
if (part.startsWith("tasks:") && part.slice(6).split(",").some((id) => id && action.includes(id))) {
|
|
254138
|
-
count2 += 1;
|
|
254139
|
-
break;
|
|
254140
|
-
}
|
|
254141
|
-
}
|
|
254142
|
-
}
|
|
254143
|
-
return count2;
|
|
254144
|
-
}
|
|
254145
|
-
function isDurableBlockedTask(task) {
|
|
254146
|
-
const meta2 = task.sourceMetadata;
|
|
254147
|
-
if (!meta2 || typeof meta2 !== "object") return false;
|
|
254148
|
-
if (meta2.blockedClass !== "external") return false;
|
|
254149
|
-
return task.status === "failed" || task.status === "needs-replan";
|
|
254150
|
-
}
|
|
254151
|
-
function buildExternalBlockMetadataPatch(classification, thrashCount) {
|
|
254152
|
-
return {
|
|
254153
|
-
blockedClass: classification.class,
|
|
254154
|
-
blockedThrashSignature: classification.thrashSignature,
|
|
254155
|
-
blockedThrashCount: thrashCount,
|
|
254156
|
-
externalBlockers: []
|
|
254157
|
-
};
|
|
254158
|
-
}
|
|
254159
|
-
|
|
254160
|
-
// ../engine/src/executor/create-task-done-tool.ts
|
|
254161
255046
|
init_effective_settings();
|
|
254162
255047
|
init_run_audit();
|
|
254163
255048
|
init_logger();
|
|
@@ -254231,7 +255116,13 @@ function createTaskDoneTool(deps, taskId, worktreePath, promptContent, codeRevie
|
|
|
254231
255116
|
)),
|
|
254232
255117
|
reason: Type8.Optional(Type8.String({
|
|
254233
255118
|
description: 'Required when outcome="blocked": concrete explanation of what is blocking the work and what is needed to unblock it.'
|
|
254234
|
-
}))
|
|
255119
|
+
})),
|
|
255120
|
+
blockedReport: Type8.Optional(Type8.Object({
|
|
255121
|
+
verifiedCondition: Type8.Optional(Type8.String()),
|
|
255122
|
+
stopReason: Type8.Optional(Type8.String()),
|
|
255123
|
+
unimplementedWork: Type8.Optional(Type8.String()),
|
|
255124
|
+
unblockCondition: Type8.Optional(Type8.String())
|
|
255125
|
+
}, { description: "Optional safe operator report for an accepted outside-worktree block. State what was verified, why work stopped, what remains undone, and the precise unblock condition. Never include prompts, logs, stack traces, or credentials." }))
|
|
254235
255126
|
}),
|
|
254236
255127
|
execute: async (_id, params) => {
|
|
254237
255128
|
if (params.outcome === "blocked") {
|
|
@@ -254282,6 +255173,7 @@ function createTaskDoneTool(deps, taskId, worktreePath, promptContent, codeRevie
|
|
|
254282
255173
|
const externalBlock = {
|
|
254283
255174
|
...classifiedObstacle,
|
|
254284
255175
|
message: reason,
|
|
255176
|
+
report: buildTaskExternalBlockReport(classifiedObstacle, params.blockedReport),
|
|
254285
255177
|
source: "agent-declaration",
|
|
254286
255178
|
blockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
254287
255179
|
resume: {
|
|
@@ -254484,9 +255376,6 @@ function createTaskDoneTool(deps, taskId, worktreePath, promptContent, codeRevie
|
|
|
254484
255376
|
...taintUpdate,
|
|
254485
255377
|
paused: false,
|
|
254486
255378
|
pausedByAgentId: null,
|
|
254487
|
-
worktree: null,
|
|
254488
|
-
branch: null,
|
|
254489
|
-
branchWriteOrigin: "engine",
|
|
254490
255379
|
sessionFile: null
|
|
254491
255380
|
});
|
|
254492
255381
|
await store.logEntry(
|
|
@@ -254504,9 +255393,6 @@ function createTaskDoneTool(deps, taskId, worktreePath, promptContent, codeRevie
|
|
|
254504
255393
|
...taintUpdate,
|
|
254505
255394
|
paused: false,
|
|
254506
255395
|
pausedByAgentId: null,
|
|
254507
|
-
worktree: null,
|
|
254508
|
-
branch: null,
|
|
254509
|
-
branchWriteOrigin: "engine",
|
|
254510
255396
|
sessionFile: null
|
|
254511
255397
|
});
|
|
254512
255398
|
await store.logEntry(taskId, `${refusalMessage} \u2014 fn_task_done refusal retry budget exhausted`, void 0, deps.getRunContextFor(task.id));
|
|
@@ -254839,46 +255725,6 @@ async function isReentrantPausedAbortedInFlightNode(deps, live, result, abortPro
|
|
|
254839
255725
|
return live.column === resumeLanes.hold || live.column === resumeLanes.review || live.column === resumeLanes.wip;
|
|
254840
255726
|
}
|
|
254841
255727
|
|
|
254842
|
-
// ../engine/src/executor/route-graph-failure-to-execution-resume.ts
|
|
254843
|
-
init_src2();
|
|
254844
|
-
init_logger();
|
|
254845
|
-
init_lifecycle_columns();
|
|
254846
|
-
async function routeGraphFailureToExecutionResume(deps, live, failedNode, failureValue, resumeLanesMemo) {
|
|
254847
|
-
if (live.deletedAt) return false;
|
|
254848
|
-
if (isTaskExternallyBlocked(live)) {
|
|
254849
|
-
executorLog.log(`${live.id}: graph failure resume skipped \u2014 external Blocked freeze honored`);
|
|
254850
|
-
return false;
|
|
254851
|
-
}
|
|
254852
|
-
if (live.paused || live.userPaused === true) return false;
|
|
254853
|
-
if ((await resolveTerminalColumnsFor(deps.store, live.id)).includes(live.column)) return false;
|
|
254854
|
-
if (isDurableBlockedTask(live)) {
|
|
254855
|
-
executorLog.log(
|
|
254856
|
-
`${live.id}: graph failure resume skipped \u2014 durable BLOCKED park honored (task-dependency block)`
|
|
254857
|
-
);
|
|
254858
|
-
return false;
|
|
254859
|
-
}
|
|
254860
|
-
if (failedNode === COMPLETION_SUMMARY_NODE_ID) return false;
|
|
254861
|
-
const incompleteSteps = hasNonTerminalWorkflowSteps(live);
|
|
254862
|
-
if (!incompleteSteps && (failureValue === "remediation-not-scheduled" || failureValue === "missing-remediation-context") && await deps.isRemediationGraphNode(live.id, failedNode)) return false;
|
|
254863
|
-
const implementationIncompleteMergeFailure = isMergeGraphFailure(failedNode) && failureValue === "implementation-incomplete";
|
|
254864
|
-
if (implementationIncompleteMergeFailure && !incompleteSteps) return false;
|
|
254865
|
-
const resumeRouterLanes = await deps.resolveResumeLanes(live.id, resumeLanesMemo);
|
|
254866
|
-
if (!resumeRouterLanes.wipDeclared) return false;
|
|
254867
|
-
if (live.column !== resumeRouterLanes.wip || !implementationIncompleteMergeFailure) {
|
|
254868
|
-
const message2 = `Workflow graph failed at node '${failedNode}'${failureValue ? ` (${failureValue})` : ""} \u2014 automatic recovery cannot move '${live.column}' backward; card remains in place`;
|
|
254869
|
-
executorLog.warn(`${live.id}: ${message2}`);
|
|
254870
|
-
await deps.store.logEntry(live.id, message2, void 0, deps.getRunContextFor(live.id));
|
|
254871
|
-
return false;
|
|
254872
|
-
}
|
|
254873
|
-
const message = `Workflow graph failed at node '${failedNode}'${failureValue ? ` (${failureValue})` : ""} with incomplete work \u2014 resuming in place in '${live.column}'`;
|
|
254874
|
-
executorLog.warn(`${live.id}: ${message}`);
|
|
254875
|
-
await deps.store.logEntry(live.id, message, void 0, deps.getRunContextFor(live.id));
|
|
254876
|
-
await deps.store.updateTask(live.id, { status: null, error: null }, deps.getRunContextFor(live.id));
|
|
254877
|
-
await deps.clearTerminalStepFailuresForRetry(live.id, "archive");
|
|
254878
|
-
await deps.persistTokenUsage(live.id);
|
|
254879
|
-
return true;
|
|
254880
|
-
}
|
|
254881
|
-
|
|
254882
255728
|
// ../engine/src/executor/reenter-paused-aborted-workflow-node.ts
|
|
254883
255729
|
init_run_audit();
|
|
254884
255730
|
init_logger();
|
|
@@ -255141,6 +255987,7 @@ async function handleStaleInReviewParsePauseAbortReplay(deps, live, result, abor
|
|
|
255141
255987
|
}
|
|
255142
255988
|
|
|
255143
255989
|
// ../engine/src/executor/route-graph-merge-failure-to-retry.ts
|
|
255990
|
+
init_src2();
|
|
255144
255991
|
init_logger();
|
|
255145
255992
|
init_workflow_merge_nodes();
|
|
255146
255993
|
|
|
@@ -255233,11 +256080,23 @@ async function routeGraphMergeFailureToRetry(deps, live, result, abortProvenance
|
|
|
255233
256080
|
throw firstBoundaryError;
|
|
255234
256081
|
}
|
|
255235
256082
|
if (mergeBoundary.blocked) {
|
|
255236
|
-
const { reason: reason2, code, missingInstanceCount } = mergeBoundary.blocked;
|
|
256083
|
+
const { reason: reason2, code, missingInstanceCount, evidence } = mergeBoundary.blocked;
|
|
256084
|
+
if (await deps.routeGraphFailureToExecutionResume?.(live, failedNode, MERGE_BOUNDARY_RECOVERY_VALUE, void 0, evidence)) {
|
|
256085
|
+
await deps.store.logEntry(live.id, `Workflow merge boundary retry scheduled evidence recovery: ${reason2}`, void 0, deps.getRunContextFor(live.id));
|
|
256086
|
+
await persistTokenUsageBestEffort(deps.persistTokenUsage, live.id);
|
|
256087
|
+
return true;
|
|
256088
|
+
}
|
|
256089
|
+
const settings = typeof deps.store.getSettings === "function" ? await deps.store.getSettings().catch(() => void 0) : void 0;
|
|
256090
|
+
const current = typeof deps.store.getTask === "function" ? await deps.store.getTask(live.id).catch(() => void 0) : live;
|
|
256091
|
+
if (!current || current.deletedAt || current.paused || current.userPaused || current.autoMerge === false || settings && !allowsAutoMergeProcessing(current, settings)) {
|
|
256092
|
+
await deps.store.logEntry(live.id, `Workflow merge boundary retry is held for human control: ${reason2}`, void 0, deps.getRunContextFor(live.id));
|
|
256093
|
+
await persistTokenUsageBestEffort(deps.persistTokenUsage, live.id);
|
|
256094
|
+
return true;
|
|
256095
|
+
}
|
|
255237
256096
|
await deps.store.logEntry(live.id, `Workflow merge boundary retry parked task: ${reason2}`, void 0, deps.getRunContextFor(live.id));
|
|
255238
256097
|
let parked2 = false;
|
|
255239
|
-
await deps.store.updateTaskAtomic(live.id, (
|
|
255240
|
-
if (!
|
|
256098
|
+
await deps.store.updateTaskAtomic(live.id, (current2) => {
|
|
256099
|
+
if (!current2 || current2.deletedAt || current2.status != null || current2.paused || current2.userPaused || current2.autoMerge === false || settings && !allowsAutoMergeProcessing(current2, settings) || typeof live.columnMovedAt === "string" && typeof current2.columnMovedAt === "string" && current2.columnMovedAt !== live.columnMovedAt) return null;
|
|
255241
256100
|
parked2 = true;
|
|
255242
256101
|
return {
|
|
255243
256102
|
status: "failed",
|
|
@@ -255298,6 +256157,10 @@ async function routeGraphMergeFailureToRetry(deps, live, result, abortProvenance
|
|
|
255298
256157
|
return true;
|
|
255299
256158
|
}
|
|
255300
256159
|
const reason = mergeRequestRejection instanceof Error ? mergeRequestRejection.message : String(mergeRequestRejection);
|
|
256160
|
+
if (reason.endsWith(PRE_MERGE_STEPS_NOT_RUN_BLOCKER)) {
|
|
256161
|
+
await persistTokenUsageBestEffort(deps.persistTokenUsage, live.id);
|
|
256162
|
+
return true;
|
|
256163
|
+
}
|
|
255301
256164
|
try {
|
|
255302
256165
|
await deps.store.logEntry(
|
|
255303
256166
|
live.id,
|
|
@@ -255321,7 +256184,8 @@ async function routeGraphMergeFailureToRetry(deps, live, result, abortProvenance
|
|
|
255321
256184
|
// ../engine/src/executor/route-implementation-incomplete-merge-graph-failure.ts
|
|
255322
256185
|
init_logger();
|
|
255323
256186
|
init_lifecycle_columns();
|
|
255324
|
-
|
|
256187
|
+
init_workflow_merge_nodes();
|
|
256188
|
+
async function routeImplementationIncompleteMergeGraphFailure(deps, live, failedNode, failureValue = "implementation-incomplete", boundaryEvidence) {
|
|
255325
256189
|
deps.clearPausedAborted(live.id);
|
|
255326
256190
|
let resumeLive = live;
|
|
255327
256191
|
if (live.paused === true && live.userPaused !== true) {
|
|
@@ -255331,7 +256195,7 @@ async function routeImplementationIncompleteMergeGraphFailure(deps, live, failed
|
|
|
255331
256195
|
}, deps.getRunContextFor(live.id));
|
|
255332
256196
|
resumeLive = { ...live, paused: false, pausedReason: void 0 };
|
|
255333
256197
|
}
|
|
255334
|
-
if (hasNonTerminalWorkflowSteps(resumeLive) && await deps.routeGraphFailureToExecutionResume(resumeLive, failedNode,
|
|
256198
|
+
if ((hasNonTerminalWorkflowSteps(resumeLive) || failureValue === MERGE_BOUNDARY_RECOVERY_VALUE) && await deps.routeGraphFailureToExecutionResume(resumeLive, failedNode, failureValue, void 0, boundaryEvidence)) {
|
|
255335
256199
|
return true;
|
|
255336
256200
|
}
|
|
255337
256201
|
deps.activeWorktrees.delete(live.id);
|
|
@@ -255839,7 +256703,15 @@ function createAuthoritativeWorkflowPrimitivesFromExecutor(deps, settings, outpu
|
|
|
255839
256703
|
runId: ctx.run.runId
|
|
255840
256704
|
});
|
|
255841
256705
|
if (mergeBoundary.blocked) {
|
|
255842
|
-
return {
|
|
256706
|
+
return {
|
|
256707
|
+
outcome: "failure",
|
|
256708
|
+
value: MERGE_BOUNDARY_RECOVERY_VALUE,
|
|
256709
|
+
contextPatch: {
|
|
256710
|
+
"workflow:merge-boundary-recovery-code": mergeBoundary.blocked.evidence.code,
|
|
256711
|
+
"workflow:merge-boundary-non-terminal-node-id": mergeBoundary.blocked.evidence.nonTerminalNodeId,
|
|
256712
|
+
"workflow:merge-boundary-missing-instance-ids": mergeBoundary.blocked.evidence.missingInstanceIds
|
|
256713
|
+
}
|
|
256714
|
+
};
|
|
255843
256715
|
}
|
|
255844
256716
|
const mergeTask = mergeBoundary.task;
|
|
255845
256717
|
const missingImplementationProof = await deps.getWorkflowMergeImplementationProofFailure(mergeTask);
|
|
@@ -256397,7 +257269,7 @@ Repository scope (task-level; review this plan once): ${detail.repositoryScope?.
|
|
|
256397
257269
|
init_src2();
|
|
256398
257270
|
init_logger();
|
|
256399
257271
|
import { existsSync as existsSync81 } from "node:fs";
|
|
256400
|
-
import { join as
|
|
257272
|
+
import { join as join94 } from "node:path";
|
|
256401
257273
|
|
|
256402
257274
|
// ../engine/src/executor/workflow-step-recommendations.ts
|
|
256403
257275
|
init_src2();
|
|
@@ -256485,7 +257357,7 @@ function resolveGraphNodeSessionBoundary(input) {
|
|
|
256485
257357
|
if (!input.isWorkspace || input.legacyWorkspaceLayout) return void 0;
|
|
256486
257358
|
const repoRoots = (input.confirmedRepositories ?? []).map((repoRelPath) => ({
|
|
256487
257359
|
repoRelPath,
|
|
256488
|
-
repoRootDir:
|
|
257360
|
+
repoRootDir: join94(input.rootDir, repoRelPath)
|
|
256489
257361
|
}));
|
|
256490
257362
|
if (repoRoots.length === 0) return void 0;
|
|
256491
257363
|
return {
|
|
@@ -256928,7 +257800,7 @@ ${prompt}`;
|
|
|
256928
257800
|
const legacyWorkspaceLayout = isLegacyWorkspaceWorktreeLayout(workspaceReviewTarget, workspaceTaskDir2);
|
|
256929
257801
|
const scopedRepoRoots = workspaceReviewTarget.repositoryScope.repositories.map((repoRelPath) => ({
|
|
256930
257802
|
repoRelPath,
|
|
256931
|
-
repoRootDir:
|
|
257803
|
+
repoRootDir: join94(deps.rootDir, repoRelPath)
|
|
256932
257804
|
}));
|
|
256933
257805
|
let aggregate = await reviewWorkspacePerRepo(workspaceReviewTarget, async (repoWorktreePath) => {
|
|
256934
257806
|
const repoRelPath = workspaceConfig.repos.find(
|
|
@@ -256937,7 +257809,7 @@ ${prompt}`;
|
|
|
256937
257809
|
const reviewBoundary = legacyWorkspaceLayout ? {
|
|
256938
257810
|
kind: "task-worktree",
|
|
256939
257811
|
writableRoot: repoWorktreePath,
|
|
256940
|
-
projectRoot: repoRelPath ?
|
|
257812
|
+
projectRoot: repoRelPath ? join94(deps.rootDir, repoRelPath) : deps.rootDir
|
|
256941
257813
|
} : {
|
|
256942
257814
|
kind: "workspace-task-dir",
|
|
256943
257815
|
writableRoot: workspaceTaskDir2,
|
|
@@ -257117,8 +257989,8 @@ ${prompt}`;
|
|
|
257117
257989
|
|
|
257118
257990
|
// ../engine/src/executor/handle-graph-failure.ts
|
|
257119
257991
|
init_src2();
|
|
257120
|
-
import { join as
|
|
257121
|
-
import { readFile as
|
|
257992
|
+
import { join as join95 } from "node:path";
|
|
257993
|
+
import { readFile as readFile51, rm as rm16, writeFile as writeFile29 } from "node:fs/promises";
|
|
257122
257994
|
init_spec_staleness();
|
|
257123
257995
|
init_logger();
|
|
257124
257996
|
init_run_audit();
|
|
@@ -257492,10 +258364,18 @@ async function handleGraphFailure(deps, task, result) {
|
|
|
257492
258364
|
const failedNode = result.visitedNodeIds[result.visitedNodeIds.length - 1];
|
|
257493
258365
|
const mergeGraphFailure = isMergeGraphFailure(failedNode);
|
|
257494
258366
|
const failureValue = graphFailureValue(result);
|
|
258367
|
+
const recoveryCode = result.context?.["workflow:merge-boundary-recovery-code"];
|
|
258368
|
+
const recoveryMissingIds = result.context?.["workflow:merge-boundary-missing-instance-ids"];
|
|
258369
|
+
const recoveryNonTerminalNodeId = result.context?.["workflow:merge-boundary-non-terminal-node-id"];
|
|
258370
|
+
const boundaryEvidence = failureValue === MERGE_BOUNDARY_RECOVERY_VALUE && (recoveryCode === "no-node-result" || recoveryCode === "non-terminal-node-result" || recoveryCode === "missing-foreach-instances") && Array.isArray(recoveryMissingIds) && recoveryMissingIds.every((id) => typeof id === "string") ? {
|
|
258371
|
+
code: recoveryCode,
|
|
258372
|
+
missingInstanceIds: recoveryMissingIds,
|
|
258373
|
+
nonTerminalNodeId: typeof recoveryNonTerminalNodeId === "string" ? recoveryNonTerminalNodeId : void 0
|
|
258374
|
+
} : void 0;
|
|
257495
258375
|
if (!live.paused && !live.userPaused && !live.deletedAt && typeof failedNode === "string" && (failedNode === "parse" || failedNode.endsWith(":parse") || failedNode.includes("parse-steps") || failureValue === "parse-error" || failureValue === "missing-implementation-steps")) {
|
|
257496
258376
|
try {
|
|
257497
|
-
const tasksDir = typeof deps.store.getTasksDir === "function" ? deps.store.getTasksDir() :
|
|
257498
|
-
const promptContent = await
|
|
258377
|
+
const tasksDir = typeof deps.store.getTasksDir === "function" ? deps.store.getTasksDir() : join95(deps.rootDir, ".fusion", "tasks");
|
|
258378
|
+
const promptContent = await readFile51(getPromptPath(tasksDir, live.id), "utf-8").catch(() => "");
|
|
257499
258379
|
const redirectReason = nonExecutableDuplicateRedirectReason(promptContent, live.title);
|
|
257500
258380
|
if (redirectReason) {
|
|
257501
258381
|
const duplicateResolution = resolveExplicitDuplicateMarker(promptContent, live.title);
|
|
@@ -257599,8 +258479,8 @@ async function handleGraphFailure(deps, task, result) {
|
|
|
257599
258479
|
await deps.persistTokenUsage(task.id);
|
|
257600
258480
|
return;
|
|
257601
258481
|
}
|
|
257602
|
-
if (mergeGraphFailure && failureValue === "implementation-incomplete") {
|
|
257603
|
-
if (await deps.routeImplementationIncompleteMergeGraphFailure(live, failedNode ?? "unknown")) {
|
|
258482
|
+
if (mergeGraphFailure && (failureValue === "implementation-incomplete" || failureValue === MERGE_BOUNDARY_RECOVERY_VALUE)) {
|
|
258483
|
+
if (await deps.routeImplementationIncompleteMergeGraphFailure(live, failedNode ?? "unknown", failureValue, boundaryEvidence)) {
|
|
257604
258484
|
return;
|
|
257605
258485
|
}
|
|
257606
258486
|
}
|
|
@@ -257862,8 +258742,8 @@ async function handleGraphFailure(deps, task, result) {
|
|
|
257862
258742
|
if (!parked) {
|
|
257863
258743
|
const capturedRunId = deps.getRunContextFor(task.id)?.runId;
|
|
257864
258744
|
const capturedColumnMovedAt = live.columnMovedAt;
|
|
257865
|
-
const tasksDir = typeof deps.store.getTasksDir === "function" ? deps.store.getTasksDir() :
|
|
257866
|
-
const deferredParkIntentPath =
|
|
258745
|
+
const tasksDir = typeof deps.store.getTasksDir === "function" ? deps.store.getTasksDir() : join95(deps.rootDir, ".fusion", "tasks");
|
|
258746
|
+
const deferredParkIntentPath = join95(tasksDir, task.id, "deferred-terminal-park.json");
|
|
257867
258747
|
const intentWrite = writeFile29(deferredParkIntentPath, JSON.stringify({ message, writtenAt: (/* @__PURE__ */ new Date()).toISOString(), columnMovedAt: capturedColumnMovedAt }), "utf-8").catch((intentError) => {
|
|
257868
258748
|
executorLog.warn(`${task.id}: failed to persist deferred terminal-park intent: ${intentError instanceof Error ? intentError.message : String(intentError)}`);
|
|
257869
258749
|
});
|
|
@@ -258129,7 +259009,7 @@ init_src2();
|
|
|
258129
259009
|
import { exec as exec56 } from "node:child_process";
|
|
258130
259010
|
import { promisify as promisify78 } from "node:util";
|
|
258131
259011
|
import { existsSync as existsSync83 } from "node:fs";
|
|
258132
|
-
import { join as
|
|
259012
|
+
import { join as join97 } from "node:path";
|
|
258133
259013
|
import { SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
|
|
258134
259014
|
init_agent_tools();
|
|
258135
259015
|
init_agent_instructions();
|
|
@@ -258175,7 +259055,7 @@ init_src2();
|
|
|
258175
259055
|
init_logger();
|
|
258176
259056
|
init_verification_concurrency();
|
|
258177
259057
|
import { existsSync as existsSync82, readdirSync as readdirSync13, readFileSync as readFileSync28 } from "node:fs";
|
|
258178
|
-
import { isAbsolute as isAbsolute24, join as
|
|
259058
|
+
import { isAbsolute as isAbsolute24, join as join96, relative as relative22 } from "node:path";
|
|
258179
259059
|
import { Type as Type10 } from "@earendil-works/pi-ai";
|
|
258180
259060
|
var MAX_OUTPUT_BYTES = 200 * 1024;
|
|
258181
259061
|
var VERIFICATION_FAILURE_SUMMARY_MAX_CHARS = 8e3;
|
|
@@ -258303,7 +259183,7 @@ function findWorkspacePackageDir(rootDir, packageName) {
|
|
|
258303
259183
|
for (const candidate of candidates) {
|
|
258304
259184
|
if (!candidate) continue;
|
|
258305
259185
|
try {
|
|
258306
|
-
const pkg = JSON.parse(readFileSync28(
|
|
259186
|
+
const pkg = JSON.parse(readFileSync28(join96(rootDir, candidate, "package.json"), "utf8"));
|
|
258307
259187
|
if (pkg.name === packageName) {
|
|
258308
259188
|
packageDirCache.set(cacheKey, candidate);
|
|
258309
259189
|
return candidate;
|
|
@@ -258318,7 +259198,7 @@ function findWorkspacePackageDir(rootDir, packageName) {
|
|
|
258318
259198
|
while (queue.length > 0) {
|
|
258319
259199
|
const current = queue.shift();
|
|
258320
259200
|
if (!current) break;
|
|
258321
|
-
const abs =
|
|
259201
|
+
const abs = join96(rootDir, current.dir);
|
|
258322
259202
|
let entries;
|
|
258323
259203
|
try {
|
|
258324
259204
|
entries = readdirSync13(abs, { withFileTypes: true });
|
|
@@ -258328,9 +259208,9 @@ function findWorkspacePackageDir(rootDir, packageName) {
|
|
|
258328
259208
|
for (const entry of entries) {
|
|
258329
259209
|
if (!entry.isDirectory()) continue;
|
|
258330
259210
|
if (entry.name === "node_modules" || entry.name === "dist") continue;
|
|
258331
|
-
const child =
|
|
259211
|
+
const child = join96(current.dir, entry.name);
|
|
258332
259212
|
try {
|
|
258333
|
-
const pkg = JSON.parse(readFileSync28(
|
|
259213
|
+
const pkg = JSON.parse(readFileSync28(join96(rootDir, child, "package.json"), "utf8"));
|
|
258334
259214
|
if (pkg.name === packageName) {
|
|
258335
259215
|
packageDirCache.set(cacheKey, child);
|
|
258336
259216
|
return child;
|
|
@@ -258350,7 +259230,7 @@ function toPackageRelativeFilter(token, rootDir, packageDir) {
|
|
|
258350
259230
|
return normalized3.slice(normalizedPackageDir.length + 1);
|
|
258351
259231
|
}
|
|
258352
259232
|
if (isAbsolute24(token)) {
|
|
258353
|
-
const rel = relative22(
|
|
259233
|
+
const rel = relative22(join96(rootDir, packageDir), token).replace(/\\/g, "/");
|
|
258354
259234
|
if (!rel.startsWith("../") && rel !== "..") return rel;
|
|
258355
259235
|
}
|
|
258356
259236
|
return token;
|
|
@@ -258804,7 +259684,7 @@ function createRunVerificationTool(opts) {
|
|
|
258804
259684
|
if (params.cwd && isAbsolute24(params.cwd)) {
|
|
258805
259685
|
resolvedCwd = params.cwd;
|
|
258806
259686
|
} else if (params.cwd) {
|
|
258807
|
-
resolvedCwd =
|
|
259687
|
+
resolvedCwd = join96(selectedRepo?.worktreePath ?? worktreePath, params.cwd);
|
|
258808
259688
|
} else {
|
|
258809
259689
|
resolvedCwd = selectedRepo?.worktreePath ?? worktreePath;
|
|
258810
259690
|
}
|
|
@@ -258838,7 +259718,7 @@ function createRunVerificationTool(opts) {
|
|
|
258838
259718
|
warnings.push(...normalized3.warnings);
|
|
258839
259719
|
}
|
|
258840
259720
|
if (effectiveCommand.trimStart().startsWith("pnpm --filter")) {
|
|
258841
|
-
const modulesYaml =
|
|
259721
|
+
const modulesYaml = join96(verificationRootDir, "node_modules", ".modules.yaml");
|
|
258842
259722
|
if (!existsSync82(modulesYaml)) {
|
|
258843
259723
|
const installCmd = "pnpm install --prefer-offline";
|
|
258844
259724
|
const msg = `node_modules/.modules.yaml not found in workspace root \u2014 auto-prepending \`${installCmd}\` before running the command.`;
|
|
@@ -259160,6 +260040,7 @@ async function parkExternalSessionObstacle(deps, taskId, errorMessage3) {
|
|
|
259160
260040
|
const externalBlock = {
|
|
259161
260041
|
...obstacle,
|
|
259162
260042
|
message: errorMessage3,
|
|
260043
|
+
report: buildTaskExternalBlockReport(obstacle),
|
|
259163
260044
|
source: "session-failure",
|
|
259164
260045
|
blockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
259165
260046
|
resume: {
|
|
@@ -259287,7 +260168,7 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
|
|
|
259287
260168
|
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
259288
260169
|
const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
|
|
259289
260170
|
if (!isActiveTask) {
|
|
259290
|
-
const tasksDir =
|
|
260171
|
+
const tasksDir = join97(deps.store.getFusionDir(), "tasks");
|
|
259291
260172
|
const promptPath = getPromptPath(tasksDir, task.id);
|
|
259292
260173
|
const staleness = await evaluateSpecStaleness({
|
|
259293
260174
|
settings,
|
|
@@ -259479,14 +260360,14 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
|
|
|
259479
260360
|
return {
|
|
259480
260361
|
kind: "task-worktree",
|
|
259481
260362
|
writableRoot: worktreePath,
|
|
259482
|
-
projectRoot: repoRelPath ?
|
|
260363
|
+
projectRoot: repoRelPath ? join97(deps.rootDir, repoRelPath) : deps.rootDir
|
|
259483
260364
|
};
|
|
259484
260365
|
}
|
|
259485
260366
|
return {
|
|
259486
260367
|
kind: "workspace-task-dir",
|
|
259487
260368
|
writableRoot: worktreePath,
|
|
259488
260369
|
projectRoot: deps.rootDir,
|
|
259489
|
-
repoRoots: task.repositoryScope?.state === "confirmed" ? task.repositoryScope.repositories.map((repoRelPath) => ({ repoRelPath, repoRootDir:
|
|
260370
|
+
repoRoots: task.repositoryScope?.state === "confirmed" ? task.repositoryScope.repositories.map((repoRelPath) => ({ repoRelPath, repoRootDir: join97(deps.rootDir, repoRelPath) })) : []
|
|
259490
260371
|
};
|
|
259491
260372
|
})() : {
|
|
259492
260373
|
kind: "task-worktree",
|
|
@@ -262133,59 +263014,6 @@ async function terminateChildAgent(deps, childId) {
|
|
|
262133
263014
|
deps.setTotalSpawnedCount(Math.max(0, deps.totalSpawnedCount - 1));
|
|
262134
263015
|
}
|
|
262135
263016
|
|
|
262136
|
-
// ../engine/src/executor/evaluate-workflow-merge-boundary.ts
|
|
262137
|
-
init_src2();
|
|
262138
|
-
async function evaluateWorkflowMergeBoundary(deps, task, runId) {
|
|
262139
|
-
const relevant = (task.workflowStepResults ?? []).filter(
|
|
262140
|
-
(result) => result.source === "node" && (result.phase ?? "pre-merge") === "pre-merge"
|
|
262141
|
-
);
|
|
262142
|
-
const hasRelevantNodeResult = relevant.length > 0;
|
|
262143
|
-
const nonTerminalResult = relevant.find((result) => result.status !== "passed" && result.status !== "skipped");
|
|
262144
|
-
const allResultsTerminal = nonTerminalResult === void 0;
|
|
262145
|
-
let ir2;
|
|
262146
|
-
try {
|
|
262147
|
-
ir2 = await resolveWorkflowIrForTask(deps.store, task.id);
|
|
262148
|
-
} catch {
|
|
262149
|
-
}
|
|
262150
|
-
if (!ir2) return { resolved: false, hasRelevantNodeResult, allResultsTerminal, coverageComplete: true, hasLiveStepImplementationProof: false, hasForeachStepExecute: false, missingInstanceIds: [], nonTerminalResult, complete: false };
|
|
262151
|
-
let persistedInstances = [];
|
|
262152
|
-
try {
|
|
262153
|
-
persistedInstances = await deps.loadMergeBoundaryInstances(task.id, runId);
|
|
262154
|
-
} catch {
|
|
262155
|
-
}
|
|
262156
|
-
const coverage = evaluateForeachMergeProof({ ir: ir2, steps: task.steps, workflowStepResults: task.workflowStepResults, persistedInstances });
|
|
262157
|
-
const coverageComplete = coverage.missingInstanceIds.length === 0;
|
|
262158
|
-
const hasLiveStepImplementationProof = coverage.expectedInstanceIds.length > 0 && coverage.expectedInstanceIds.every((id) => coverage.liveStepSatisfiedInstanceIds.includes(id));
|
|
262159
|
-
const complete = allResultsTerminal && coverageComplete && (hasRelevantNodeResult || hasLiveStepImplementationProof);
|
|
262160
|
-
return { resolved: true, hasRelevantNodeResult, allResultsTerminal, coverageComplete, hasLiveStepImplementationProof, hasForeachStepExecute: coverage.hasForeachStepExecute, missingInstanceIds: coverage.missingInstanceIds, nonTerminalResult, complete };
|
|
262161
|
-
}
|
|
262162
|
-
async function getWorkflowMergeImplementationProofFailure(deps, task) {
|
|
262163
|
-
const taint = evaluateSkipBypassTaint(task);
|
|
262164
|
-
if (taint.blocked) return "implementation did not run: steps were skipped after a bulk-step-completion refusal without an accepted fn_task_done";
|
|
262165
|
-
if (task.noCommitsExpected === true) return void 0;
|
|
262166
|
-
let ir2;
|
|
262167
|
-
try {
|
|
262168
|
-
ir2 = await resolveWorkflowIrForTask(deps.store, task.id);
|
|
262169
|
-
} catch {
|
|
262170
|
-
ir2 = void 0;
|
|
262171
|
-
}
|
|
262172
|
-
if (!ir2) return void 0;
|
|
262173
|
-
const usesParsedSteps = ir2.nodes.some((node) => node.kind === "parse-steps");
|
|
262174
|
-
const usesExecuteSeam = ir2.nodes.some((node) => node.kind === "prompt" && node.config?.seam === "execute");
|
|
262175
|
-
if (!usesParsedSteps && !usesExecuteSeam) return void 0;
|
|
262176
|
-
const steps = Array.isArray(task.steps) ? task.steps : [];
|
|
262177
|
-
const hasTerminalParsedSteps = steps.length > 0 && steps.every((step) => step.status === "done" || step.status === "skipped");
|
|
262178
|
-
const hasModifiedFiles = (task.modifiedFiles?.length ?? 0) > 0;
|
|
262179
|
-
const proof = await deps.evaluateWorkflowMergeBoundary(task);
|
|
262180
|
-
const hasGraphNativeImplementationProof = proof.hasRelevantNodeResult && proof.allResultsTerminal && proof.coverageComplete;
|
|
262181
|
-
if (usesParsedSteps) {
|
|
262182
|
-
if (hasTerminalParsedSteps || hasGraphNativeImplementationProof) return void 0;
|
|
262183
|
-
return proof.hasForeachStepExecute && !proof.coverageComplete ? `implementation did not run: foreach step instances are incomplete (missing ${proof.missingInstanceIds.join(", ")})` : "implementation did not run: parsed coding steps are missing or incomplete";
|
|
262184
|
-
}
|
|
262185
|
-
if (usesExecuteSeam) return hasTerminalParsedSteps || hasModifiedFiles || hasGraphNativeImplementationProof ? void 0 : "implementation did not run: execute seam has no completion proof";
|
|
262186
|
-
return void 0;
|
|
262187
|
-
}
|
|
262188
|
-
|
|
262189
263017
|
// ../engine/src/executor/renew-task-lease.ts
|
|
262190
263018
|
async function renewTaskLease(deps, taskId, agentId, leaseEpoch, nodeId, runId) {
|
|
262191
263019
|
const renewedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -262242,37 +263070,6 @@ async function getExecutionPauseLabel(deps) {
|
|
|
262242
263070
|
return null;
|
|
262243
263071
|
}
|
|
262244
263072
|
|
|
262245
|
-
// ../engine/src/executor/workflow-merge-boundary-helpers.ts
|
|
262246
|
-
init_src2();
|
|
262247
|
-
async function resolveMergeBoundaryColumn(deps, taskId, nodeId) {
|
|
262248
|
-
try {
|
|
262249
|
-
const ir2 = await resolveWorkflowIrForTask(deps.store, taskId);
|
|
262250
|
-
const named = ir2.nodes.find((n) => n.id === nodeId);
|
|
262251
|
-
if (named && MERGE_REGION_KINDS.has(named.kind) && named.column) return named.column;
|
|
262252
|
-
const mergeNode = ir2.nodes.find((n) => MERGE_REGION_KINDS.has(n.kind) && n.column);
|
|
262253
|
-
if (mergeNode?.column) return mergeNode.column;
|
|
262254
|
-
return "in-review";
|
|
262255
|
-
} catch {
|
|
262256
|
-
return "in-review";
|
|
262257
|
-
}
|
|
262258
|
-
}
|
|
262259
|
-
async function loadMergeBoundaryInstances(deps, taskId, runId) {
|
|
262260
|
-
if (!runId) return [];
|
|
262261
|
-
const store = deps.store;
|
|
262262
|
-
try {
|
|
262263
|
-
return await store.loadWorkflowRunStepInstancesAsync?.(taskId, runId) ?? store.loadWorkflowRunStepInstances?.(taskId, runId) ?? [];
|
|
262264
|
-
} catch {
|
|
262265
|
-
return [];
|
|
262266
|
-
}
|
|
262267
|
-
}
|
|
262268
|
-
function shouldCompleteChecklistAtWorkflowMerge(task, proof) {
|
|
262269
|
-
if (!Array.isArray(task.steps) || task.steps.length === 0) return false;
|
|
262270
|
-
if (task.steps.every((step) => step.status === "done" || step.status === "skipped")) return false;
|
|
262271
|
-
if (proof) return proof.complete;
|
|
262272
|
-
const graphNodeResults = (task.workflowStepResults ?? []).filter((result) => result.source === "node" && (result.phase ?? "pre-merge") === "pre-merge");
|
|
262273
|
-
return graphNodeResults.length > 0 && graphNodeResults.every((result) => result.status === "passed" || result.status === "skipped");
|
|
262274
|
-
}
|
|
262275
|
-
|
|
262276
263073
|
// ../engine/src/executor/mark-paused-aborted.ts
|
|
262277
263074
|
function markPausedAborted(deps, taskId, provenance = "hard-cancel", source = "unspecified", options = {}) {
|
|
262278
263075
|
const previousProvenance = deps.pausedAbortProvenance.get(taskId);
|
|
@@ -263397,6 +264194,7 @@ function buildRouteGraphFailureToExecutionResumeDeps(host) {
|
|
|
263397
264194
|
...facadeMethods(host, [
|
|
263398
264195
|
"getRunContextFor",
|
|
263399
264196
|
"resolveResumeLanes",
|
|
264197
|
+
"isLiveSharedBranchGroupMember",
|
|
263400
264198
|
"clearTerminalStepFailuresForRetry",
|
|
263401
264199
|
"persistTokenUsage",
|
|
263402
264200
|
// FNXC:WorkflowRemediation 2026-08-09-21:41: FN-8910 completed-review park for refused remediation.
|
|
@@ -263533,7 +264331,7 @@ function buildRouteGraphMergeFailureToRetryDeps(host) {
|
|
|
263533
264331
|
return {
|
|
263534
264332
|
...buildStoreRunContextDeps(host),
|
|
263535
264333
|
mergeRequester: host.mergeRequester,
|
|
263536
|
-
...facadeMethods(host, ["ensureWorkflowMergeBoundaryTask", "persistTokenUsage"])
|
|
264334
|
+
...facadeMethods(host, ["ensureWorkflowMergeBoundaryTask", "persistTokenUsage", "routeGraphFailureToExecutionResume"])
|
|
263537
264335
|
};
|
|
263538
264336
|
}
|
|
263539
264337
|
function buildRouteImplementationIncompleteMergeGraphFailureDeps(host) {
|
|
@@ -264251,10 +265049,10 @@ init_logger();
|
|
|
264251
265049
|
|
|
264252
265050
|
// ../engine/src/review-artifacts/feature-video.ts
|
|
264253
265051
|
init_src2();
|
|
264254
|
-
import { access as access10, mkdtemp as mkdtemp4, readFile as
|
|
265052
|
+
import { access as access10, mkdtemp as mkdtemp4, readFile as readFile52, rm as rm17 } from "node:fs/promises";
|
|
264255
265053
|
import { constants as constants2 } from "node:fs";
|
|
264256
265054
|
import { tmpdir as tmpdir11 } from "node:os";
|
|
264257
|
-
import { join as
|
|
265055
|
+
import { join as join98 } from "node:path";
|
|
264258
265056
|
var SCENARIO_DOCUMENT_KEY = "review-artifact-scenario";
|
|
264259
265057
|
var MAX_DURATION_MS = 15e3;
|
|
264260
265058
|
var DEFAULT_DURATION_MS = 3e3;
|
|
@@ -264285,7 +265083,7 @@ async function generateFeatureVideo(options) {
|
|
|
264285
265083
|
let browser;
|
|
264286
265084
|
let context;
|
|
264287
265085
|
try {
|
|
264288
|
-
recordingDir = await mkdtemp4(
|
|
265086
|
+
recordingDir = await mkdtemp4(join98(tmpdir11(), "fusion-feature-video-"));
|
|
264289
265087
|
try {
|
|
264290
265088
|
browser = await client.launch({ executablePath, headless: true });
|
|
264291
265089
|
} catch {
|
|
@@ -264303,7 +265101,7 @@ async function generateFeatureVideo(options) {
|
|
|
264303
265101
|
await (options.sleep ?? defaultSleep)(Math.min(Math.max(options.durationMs ?? DEFAULT_DURATION_MS, 0), MAX_DURATION_MS));
|
|
264304
265102
|
await context.close();
|
|
264305
265103
|
context = void 0;
|
|
264306
|
-
const data = await
|
|
265104
|
+
const data = await readFile52(await video.path());
|
|
264307
265105
|
if (data.byteLength > (options.maxBytes ?? DEFAULT_MAX_BYTES2)) {
|
|
264308
265106
|
return { status: "failed", reason: "size-cap-exceeded" };
|
|
264309
265107
|
}
|
|
@@ -274535,7 +275333,7 @@ init_effective_settings();
|
|
|
274535
275333
|
// ../engine/src/spec-validation/task-document-references.ts
|
|
274536
275334
|
init_src2();
|
|
274537
275335
|
import { access as access11 } from "node:fs/promises";
|
|
274538
|
-
import { join as
|
|
275336
|
+
import { join as join99 } from "node:path";
|
|
274539
275337
|
var TASK_PATH_REGEX = /\.fusion\/tasks\/[A-Z0-9]+(?:-[A-Z0-9]+)*\/[^\s`'"\]]+/g;
|
|
274540
275338
|
function normalizePathToken(token) {
|
|
274541
275339
|
return token.replace(/^[`([{"']+/, "").replace(/[`),.;:!?\]}'"]+$/, "");
|
|
@@ -274623,7 +275421,7 @@ async function detectDanglingTaskDocReferences(promptContent, opts) {
|
|
|
274623
275421
|
}
|
|
274624
275422
|
const dangling = [];
|
|
274625
275423
|
for (const [path6, sections2] of references.entries()) {
|
|
274626
|
-
const absPath =
|
|
275424
|
+
const absPath = join99(opts.rootDir, path6);
|
|
274627
275425
|
if (await existsImpl(absPath)) continue;
|
|
274628
275426
|
dangling.push({ path: path6, sections: Array.from(sections2).sort() });
|
|
274629
275427
|
}
|
|
@@ -274721,8 +275519,8 @@ init_recovery_policy();
|
|
|
274721
275519
|
init_agent_tools();
|
|
274722
275520
|
import { execFile as execFile36 } from "node:child_process";
|
|
274723
275521
|
import { existsSync as existsSync87 } from "node:fs";
|
|
274724
|
-
import { readFile as
|
|
274725
|
-
import { join as
|
|
275522
|
+
import { readFile as readFile53, writeFile as writeFile30, rm as rm18 } from "node:fs/promises";
|
|
275523
|
+
import { join as join100 } from "node:path";
|
|
274726
275524
|
import { promisify as promisify83 } from "node:util";
|
|
274727
275525
|
|
|
274728
275526
|
// ../engine/src/triage-domain/triage-preflight.ts
|
|
@@ -275734,8 +276532,8 @@ var TriageProcessor = class _TriageProcessor {
|
|
|
275734
276532
|
}
|
|
275735
276533
|
const settings = await mergeEffectiveSettings(this.store, task, await this.store.getSettings());
|
|
275736
276534
|
const approvalRequired = resolvePlanApprovalRequired(settings);
|
|
275737
|
-
const promptPath =
|
|
275738
|
-
const written = await
|
|
276535
|
+
const promptPath = join100(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
276536
|
+
const written = await readFile53(promptPath, "utf-8").catch((err) => {
|
|
275739
276537
|
const msg = err instanceof Error ? err.message : String(err);
|
|
275740
276538
|
planLog.warn(`${task.id}: failed to read PROMPT.md during planning recovery (${promptPath}): ${msg}`);
|
|
275741
276539
|
return "";
|
|
@@ -275773,8 +276571,8 @@ var TriageProcessor = class _TriageProcessor {
|
|
|
275773
276571
|
return report2.outcome !== "withheld";
|
|
275774
276572
|
}
|
|
275775
276573
|
async readNonEmptyPromptDraft(taskId, context) {
|
|
275776
|
-
const promptPath =
|
|
275777
|
-
const written = await
|
|
276574
|
+
const promptPath = join100(this.rootDir, ".fusion", "tasks", taskId, "PROMPT.md");
|
|
276575
|
+
const written = await readFile53(promptPath, "utf-8").catch((err) => {
|
|
275778
276576
|
const msg = err instanceof Error ? err.message : String(err);
|
|
275779
276577
|
const code = err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
275780
276578
|
if (code === "ENOENT") {
|
|
@@ -276012,8 +276810,8 @@ var TriageProcessor = class _TriageProcessor {
|
|
|
276012
276810
|
continue;
|
|
276013
276811
|
}
|
|
276014
276812
|
try {
|
|
276015
|
-
const promptPath =
|
|
276016
|
-
const content = await
|
|
276813
|
+
const promptPath = join100(this.rootDir, ".fusion", "tasks", todoTask.id, "PROMPT.md");
|
|
276814
|
+
const content = await readFile53(promptPath, "utf-8");
|
|
276017
276815
|
if (isTaskAwaitingPlanning(todoTask, content)) {
|
|
276018
276816
|
eligibleTodoTasks.push(todoTask);
|
|
276019
276817
|
}
|
|
@@ -276396,8 +277194,8 @@ var TriageProcessor = class _TriageProcessor {
|
|
|
276396
277194
|
try {
|
|
276397
277195
|
const liveTask = await this.store.getTask(task.id).catch(() => null);
|
|
276398
277196
|
if (!liveTask || liveTask.paused === true || liveTask.userPaused === true) return false;
|
|
276399
|
-
const promptPath =
|
|
276400
|
-
const written = await
|
|
277197
|
+
const promptPath = join100(this.rootDir, ".fusion", "tasks", liveTask.id, "PROMPT.md");
|
|
277198
|
+
const written = await readFile53(promptPath, "utf-8").catch(() => "");
|
|
276401
277199
|
const duplicateResolution = resolveExplicitDuplicateMarker(written, liveTask.title);
|
|
276402
277200
|
if (!duplicateResolution.marker && !duplicateResolution.conflict) return false;
|
|
276403
277201
|
const settings = await mergeEffectiveSettings(this.store, liveTask, await this.store.getSettings());
|
|
@@ -276799,8 +277597,8 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
276799
277597
|
pluginContributions: triagePluginContributions
|
|
276800
277598
|
});
|
|
276801
277599
|
const triageSystemPromptFinal = collapsePromptLayers(triageLayers);
|
|
276802
|
-
const authoritativePromptPath =
|
|
276803
|
-
const planningAttemptBaseline = existsSync87(authoritativePromptPath) ? await
|
|
277600
|
+
const authoritativePromptPath = join100(this.rootDir, promptPath);
|
|
277601
|
+
const planningAttemptBaseline = existsSync87(authoritativePromptPath) ? await readFile53(authoritativePromptPath, "utf-8").catch(() => void 0) : void 0;
|
|
276804
277602
|
const planningAttempt = {
|
|
276805
277603
|
id: `${task.id}:${++this.planningAttemptSequence}`,
|
|
276806
277604
|
baseline: planningAttemptBaseline,
|
|
@@ -276929,7 +277727,11 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
276929
277727
|
extraCommands: [
|
|
276930
277728
|
...extractCommandBinaries(settings?.testCommand),
|
|
276931
277729
|
...extractCommandBinaries(settings?.buildCommand)
|
|
276932
|
-
]
|
|
277730
|
+
],
|
|
277731
|
+
testCommand: settings?.testCommand,
|
|
277732
|
+
buildCommand: settings?.buildCommand,
|
|
277733
|
+
rootDir: this.rootDir,
|
|
277734
|
+
projectId: this.store.getProjectId?.() ?? this.rootDir
|
|
276933
277735
|
}).catch(() => ({ capabilities: [], degraded: true }));
|
|
276934
277736
|
const agentPrompt = buildSpecificationPrompt(
|
|
276935
277737
|
detail,
|
|
@@ -277040,8 +277842,8 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
277040
277842
|
"Recovered the plan written inside the task worktree into the project .fusion folder"
|
|
277041
277843
|
).catch(() => void 0);
|
|
277042
277844
|
}
|
|
277043
|
-
let written = await
|
|
277044
|
-
|
|
277845
|
+
let written = await readFile53(
|
|
277846
|
+
join100(this.rootDir, promptPath),
|
|
277045
277847
|
"utf-8"
|
|
277046
277848
|
).catch((err) => {
|
|
277047
277849
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -277921,7 +278723,7 @@ ${lines.join("\n")}` }],
|
|
|
277921
278723
|
async clearDuplicateMarkerForReplan(task, canonicalId, feedback, options) {
|
|
277922
278724
|
if (!await this.runIfStillPlanningUnderTaskLock(task, async () => {
|
|
277923
278725
|
if (options?.source !== "title") {
|
|
277924
|
-
await rm18(
|
|
278726
|
+
await rm18(join100(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), { force: true });
|
|
277925
278727
|
}
|
|
277926
278728
|
if (resolveExplicitDuplicateMarker(null, task.title).marker?.canonicalId === canonicalId) {
|
|
277927
278729
|
await this.store.updateTask(task.id, { title: `Duplicate redirect cleared: ${canonicalId}` });
|
|
@@ -278089,7 +278891,7 @@ ${lines.join("\n")}` }],
|
|
|
278089
278891
|
let nextPrompt = applyOriginalDescription(written, originalDescription);
|
|
278090
278892
|
nextPrompt = applyFrontendUxCriteria(nextPrompt, parsedFileScope);
|
|
278091
278893
|
if (nextPrompt !== written) {
|
|
278092
|
-
const promptPath =
|
|
278894
|
+
const promptPath = join100(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
278093
278895
|
try {
|
|
278094
278896
|
if (!await this.runIfStillPlanningUnderTaskLock(task, async () => {
|
|
278095
278897
|
await writeFile30(promptPath, nextPrompt, "utf-8");
|
|
@@ -278424,10 +279226,10 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
278424
279226
|
if (!attachments || attachments.length === 0) {
|
|
278425
279227
|
return { attachmentContents, imageContents };
|
|
278426
279228
|
}
|
|
278427
|
-
const { readFile:
|
|
278428
|
-
const { join:
|
|
279229
|
+
const { readFile: readFile55 } = await import("node:fs/promises");
|
|
279230
|
+
const { join: join104 } = await import("node:path");
|
|
278429
279231
|
for (const att of attachments) {
|
|
278430
|
-
const filePath =
|
|
279232
|
+
const filePath = join104(
|
|
278431
279233
|
rootDir,
|
|
278432
279234
|
".fusion",
|
|
278433
279235
|
"tasks",
|
|
@@ -278437,7 +279239,7 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
278437
279239
|
);
|
|
278438
279240
|
try {
|
|
278439
279241
|
if (IMAGE_MIME_TYPES.has(att.mimeType)) {
|
|
278440
|
-
const data = await
|
|
279242
|
+
const data = await readFile55(filePath);
|
|
278441
279243
|
const detectedMimeType = detectImageMimeFromBytes(data);
|
|
278442
279244
|
const imageMimeType = detectedMimeType ?? att.mimeType;
|
|
278443
279245
|
if (detectedMimeType && detectedMimeType !== att.mimeType) {
|
|
@@ -278454,7 +279256,7 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
278454
279256
|
text: null
|
|
278455
279257
|
});
|
|
278456
279258
|
} else {
|
|
278457
|
-
const data = await
|
|
279259
|
+
const data = await readFile55(filePath, "utf-8");
|
|
278458
279260
|
const text2 = data.length > TEXT_INLINE_LIMIT ? data.slice(0, TEXT_INLINE_LIMIT) + "\n... (truncated at 50KB)" : data;
|
|
278459
279261
|
attachmentContents.push({
|
|
278460
279262
|
originalName: att.originalName,
|
|
@@ -282249,7 +283051,7 @@ function extractAdvisorAssistantText(session) {
|
|
|
282249
283051
|
// ../engine/src/overseer/overseer-watchdog.ts
|
|
282250
283052
|
init_logger();
|
|
282251
283053
|
import { existsSync as existsSync88, readFileSync as readFileSync29 } from "node:fs";
|
|
282252
|
-
import { dirname as dirname44, join as
|
|
283054
|
+
import { dirname as dirname44, join as join101, relative as relative23, resolve as resolve53, sep as sep15 } from "node:path";
|
|
282253
283055
|
import { homedir as homedir13 } from "node:os";
|
|
282254
283056
|
var log39 = createLogger("overseer-watchdog");
|
|
282255
283057
|
var WATCHDOG_FILENAMES = ["OVERSEER.md", "WATCHDOG.md"];
|
|
@@ -282280,7 +283082,7 @@ function discoverOverseerWatchdogFiles(options) {
|
|
|
282280
283082
|
};
|
|
282281
283083
|
if (agentDir) {
|
|
282282
283084
|
for (const name2 of WATCHDOG_FILENAMES) {
|
|
282283
|
-
tryAdd(
|
|
283085
|
+
tryAdd(join101(agentDir, name2), "user", 999);
|
|
282284
283086
|
}
|
|
282285
283087
|
}
|
|
282286
283088
|
let current = cwd;
|
|
@@ -282288,9 +283090,9 @@ function discoverOverseerWatchdogFiles(options) {
|
|
|
282288
283090
|
for (let hop = 0; hop < 64; hop++) {
|
|
282289
283091
|
const depth = relative23(cwd, current) === "" ? 0 : relative23(cwd, current).split(sep15).filter(Boolean).length;
|
|
282290
283092
|
for (const name2 of WATCHDOG_FILENAMES) {
|
|
282291
|
-
tryAdd(
|
|
282292
|
-
tryAdd(
|
|
282293
|
-
tryAdd(
|
|
283093
|
+
tryAdd(join101(current, name2), "project", depth);
|
|
283094
|
+
tryAdd(join101(current, ".fusion", name2), "project", depth);
|
|
283095
|
+
tryAdd(join101(current, ".omp", name2), "project", depth);
|
|
282294
283096
|
}
|
|
282295
283097
|
if (current === stopAt) break;
|
|
282296
283098
|
const parent = dirname44(current);
|
|
@@ -283728,7 +284530,7 @@ import { promisify as promisify88 } from "node:util";
|
|
|
283728
284530
|
import { realpathSync as realpathSync15, readdirSync as readdirSync15 } from "node:fs";
|
|
283729
284531
|
import { mkdtemp as mkdtemp5 } from "node:fs/promises";
|
|
283730
284532
|
import { tmpdir as tmpdir13 } from "node:os";
|
|
283731
|
-
import { join as
|
|
284533
|
+
import { join as join103 } from "node:path";
|
|
283732
284534
|
init_workspace_base_branch();
|
|
283733
284535
|
init_merger_ref_update_advance();
|
|
283734
284536
|
|
|
@@ -284199,7 +285001,7 @@ import { execFile as execFile40 } from "node:child_process";
|
|
|
284199
285001
|
import { appendFileSync as appendFileSync2, existsSync as existsSync90, mkdirSync as mkdirSync13, readdirSync as readdirSync14, readFileSync as readFileSync31, realpathSync as realpathSync14, rmSync as rmSync9, statSync as statSync18 } from "node:fs";
|
|
284200
285002
|
import { rm as rm19 } from "node:fs/promises";
|
|
284201
285003
|
import { tmpdir as tmpdir12 } from "node:os";
|
|
284202
|
-
import { isAbsolute as isAbsolute27, join as
|
|
285004
|
+
import { isAbsolute as isAbsolute27, join as join102, relative as relative24 } from "node:path";
|
|
284203
285005
|
import { promisify as promisify87 } from "node:util";
|
|
284204
285006
|
init_worktree_paths();
|
|
284205
285007
|
init_worktree_removal_retry();
|
|
@@ -284230,7 +285032,7 @@ function isBenignAbsentWorktreeError(err) {
|
|
|
284230
285032
|
return isBenignAbsentRemovalError(err);
|
|
284231
285033
|
}
|
|
284232
285034
|
function ensureAiMergeRootIgnored(projectRootDir, settings) {
|
|
284233
|
-
const excludePath =
|
|
285035
|
+
const excludePath = join102(projectRootDir, ".git", "info", "exclude");
|
|
284234
285036
|
if (!existsSync90(excludePath)) return;
|
|
284235
285037
|
try {
|
|
284236
285038
|
const current = readFileSync31(excludePath, "utf-8");
|
|
@@ -284255,7 +285057,7 @@ function getAiMergeTempSearchRoots(projectRootDir, settings) {
|
|
|
284255
285057
|
if (testWorkerRoot) {
|
|
284256
285058
|
try {
|
|
284257
285059
|
for (const entry of readdirSync14(testWorkerRoot)) {
|
|
284258
|
-
if (entry.startsWith("redir-")) roots.push(
|
|
285060
|
+
if (entry.startsWith("redir-")) roots.push(join102(testWorkerRoot, entry));
|
|
284259
285061
|
}
|
|
284260
285062
|
} catch {
|
|
284261
285063
|
}
|
|
@@ -284278,7 +285080,7 @@ async function pruneExistingAiMergeWorktrees(taskId, projectRootDir, audit, log4
|
|
|
284278
285080
|
continue;
|
|
284279
285081
|
}
|
|
284280
285082
|
for (const entry of entries) {
|
|
284281
|
-
const candidatePath =
|
|
285083
|
+
const candidatePath = join102(tempRoot, entry);
|
|
284282
285084
|
let canonicalPath = candidatePath;
|
|
284283
285085
|
try {
|
|
284284
285086
|
canonicalPath = realpathSync14(candidatePath);
|
|
@@ -284815,7 +285617,7 @@ function listAiMergeWorktreeCandidates(taskId, projectRootDir, settings) {
|
|
|
284815
285617
|
if (testWorkerRoot) {
|
|
284816
285618
|
try {
|
|
284817
285619
|
for (const entry of readdirSync15(testWorkerRoot)) {
|
|
284818
|
-
if (entry.startsWith("redir-")) roots.push(
|
|
285620
|
+
if (entry.startsWith("redir-")) roots.push(join103(testWorkerRoot, entry));
|
|
284819
285621
|
}
|
|
284820
285622
|
} catch {
|
|
284821
285623
|
}
|
|
@@ -284828,7 +285630,7 @@ function listAiMergeWorktreeCandidates(taskId, projectRootDir, settings) {
|
|
|
284828
285630
|
} catch {
|
|
284829
285631
|
continue;
|
|
284830
285632
|
}
|
|
284831
|
-
for (const entry of entries) candidates.push(
|
|
285633
|
+
for (const entry of entries) candidates.push(join103(root, entry));
|
|
284832
285634
|
}
|
|
284833
285635
|
return candidates;
|
|
284834
285636
|
}
|
|
@@ -285265,7 +286067,7 @@ async function landOneRepo(repoRootDir, branch, integrationBranch, ctx) {
|
|
|
285265
286067
|
registeredMergePaths.add(pathToRegister);
|
|
285266
286068
|
};
|
|
285267
286069
|
try {
|
|
285268
|
-
mergeRoot = await mkdtemp5(
|
|
286070
|
+
mergeRoot = await mkdtemp5(join103(resolveAiMergeRoot(repoRootDir, settings), `fusion-ai-merge-${taskId.toLowerCase()}-`));
|
|
285269
286071
|
registerMergeRoot(mergeRoot);
|
|
285270
286072
|
await git9(["worktree", "add", "--detach", mergeRoot, tipSha], repoRootDir);
|
|
285271
286073
|
worktreeAdded = true;
|
|
@@ -286227,7 +287029,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
286227
287029
|
for (const repoRel of repoKeys) {
|
|
286228
287030
|
const entry = workspaceWorktrees[repoRel];
|
|
286229
287031
|
if (!entry) throw new WorkspaceMergeTechnicalError("workspace-entry", `Workspace repository entry is missing for ${repoRel}`);
|
|
286230
|
-
const repoRootDir =
|
|
287032
|
+
const repoRootDir = join103(workspaceRootDir, repoRel);
|
|
286231
287033
|
try {
|
|
286232
287034
|
const baseResolution = await resolveWorkspaceRepoBaseBranch({
|
|
286233
287035
|
mode: "recorded",
|
|
@@ -286285,7 +287087,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
286285
287087
|
handle: options.workspaceDispatchFence,
|
|
286286
287088
|
claimOutcome: "reentrant",
|
|
286287
287089
|
remote: target.remote,
|
|
286288
|
-
cwd:
|
|
287090
|
+
cwd: join103(workspaceRootDir, repoRel),
|
|
286289
287091
|
fenceRefName: mergeDispatchFenceRef(taskId)
|
|
286290
287092
|
});
|
|
286291
287093
|
options.workspaceDispatchFence = ensuredDispatchFence;
|
|
@@ -286316,7 +287118,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
286316
287118
|
for (const repoRel of repoKeys) {
|
|
286317
287119
|
throwIfAborted3(options.signal, taskId);
|
|
286318
287120
|
const entry = workspaceWorktrees[repoRel];
|
|
286319
|
-
const repoRootDir =
|
|
287121
|
+
const repoRootDir = join103(workspaceRootDir, repoRel);
|
|
286320
287122
|
const workspaceTarget = workspaceTargets.get(repoRel);
|
|
286321
287123
|
if (!workspaceTarget) throw new WorkspaceMergeTechnicalError("integration-target", `Workspace integration plan is missing for ${repoRel}`);
|
|
286322
287124
|
const integrationBranch = workspaceTarget.integrationBranch;
|
|
@@ -286976,7 +287778,7 @@ async function pushAfterMergeToRemote(input) {
|
|
|
286976
287778
|
let worktreeAdded = false;
|
|
286977
287779
|
const registeredPaths = /* @__PURE__ */ new Set();
|
|
286978
287780
|
try {
|
|
286979
|
-
pushRoot = await mkdtemp5(
|
|
287781
|
+
pushRoot = await mkdtemp5(join103(resolveAiMergeRoot(projectRootDir, settings), `fusion-ai-merge-push-${taskId.toLowerCase()}-`));
|
|
286980
287782
|
for (const p2 of [pushRoot]) {
|
|
286981
287783
|
activeSessionRegistry.registerPath(p2, { taskId, kind: "ai-merge", ownerKey: `ai-merge-push:${taskId}` });
|
|
286982
287784
|
registeredPaths.add(p2);
|
|
@@ -290338,10 +291140,20 @@ ${detail}`);
|
|
|
290338
291140
|
return false;
|
|
290339
291141
|
}
|
|
290340
291142
|
this.plannerLiveRetrySkipLogDedup.delete(`${task.id}::${decision.watchedStage ?? "executor"}`);
|
|
290341
|
-
await moveTaskToContainedBackwardTarget(store, task.id, "self-healing-stranded-recovery", {
|
|
291143
|
+
const recovery = await moveTaskToContainedBackwardTarget(store, task.id, "self-healing-stranded-recovery", {
|
|
290342
291144
|
preserveProgress: true,
|
|
290343
291145
|
moveSource: "engine"
|
|
290344
291146
|
}, task.column);
|
|
291147
|
+
if (!recovery.moved) {
|
|
291148
|
+
if (!("reason" in recovery) || recovery.reason !== "in-place-recovery" || task.status !== "failed" || (await this.resolveTaskColumnFlags(store, task, /* @__PURE__ */ new Map()))?.countsTowardWip !== true) return false;
|
|
291149
|
+
let resumed = false;
|
|
291150
|
+
await store.updateTaskAtomic(task.id, (current) => {
|
|
291151
|
+
if (current.column !== task.column || current.status !== "failed" || current.error !== task.error || current.updatedAt !== task.updatedAt || current.paused || current.userPaused || current.deletedAt || executor?.isTaskLiveForOverseerRetry?.(task.id) === true) return null;
|
|
291152
|
+
resumed = true;
|
|
291153
|
+
return { status: "queued", error: null, sessionFile: null };
|
|
291154
|
+
});
|
|
291155
|
+
if (!resumed) return false;
|
|
291156
|
+
}
|
|
290345
291157
|
await this.emitOverseerInterventionSafe(
|
|
290346
291158
|
() => emitOverseerRetry({
|
|
290347
291159
|
store,
|