@runfusion/fusion 0.78.0-beta.2 → 0.78.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1150 -285
- package/dist/child-process-worker.js +760 -203
- package/dist/client/.vite/manifest.json +253 -253
- package/dist/client/assets/{AgentDetailView-4tEdPTG2.js → AgentDetailView-DAtUBPar.js} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor-DIGxjQFE.js → AgentPermissionPolicyEditor-Cth5Qyvz.js} +1 -1
- package/dist/client/assets/{AgentsView-BXFYQAPe.js → AgentsView-D6odi29-.js} +3 -3
- package/dist/client/assets/{CommandCenter-vknNrxBN.js → CommandCenter-DUlZFMvJ.js} +1 -1
- package/dist/client/assets/{DevServerView-COFb49kW.js → DevServerView-GD1-vwId.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-C-wYRUc5.js → DirectoryPicker-De3Q7_zu.js} +1 -1
- package/dist/client/assets/{DocumentsView-B-uzhK3C.js → DocumentsView-BMYBV8DV.js} +1 -1
- package/dist/client/assets/{EvalsView-BcOgXvPe.js → EvalsView-CYfclysr.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-B0zF29ER.js → ExperimentalAgentOnboardingModal-B1dQuReY.js} +1 -1
- package/dist/client/assets/{GoalsView--Cj2se_t.js → GoalsView-BHxjegdt.js} +1 -1
- package/dist/client/assets/{InsightsView-qMoM8oFe.js → InsightsView-DK0EP68R.js} +1 -1
- package/dist/client/assets/{MemoryView-Ca0Y2fYg.js → MemoryView-O8vfqipX.js} +1 -1
- package/dist/client/assets/{PatchnodeView--E259o36.js → PatchnodeView-WM5ZlnTS.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-DKKLn5YE.js → PiExtensionsManager-BrTit_vk.js} +1 -1
- package/dist/client/assets/{PluginManager-e0Rnv7RI.js → PluginManager-DBUm9Q30.js} +1 -1
- package/dist/client/assets/{PullRequestView-BFe77u-d.js → PullRequestView-DW9EqNT3.js} +1 -1
- package/dist/client/assets/{ReportModal-DvyDEj8D.js → ReportModal-Cv7Lxy1u.js} +1 -1
- package/dist/client/assets/{ResearchView-DROedGZR.js → ResearchView-DDGbBXlh.js} +1 -1
- package/dist/client/assets/{SecretsView-DTrrJEt9.js → SecretsView-C2K-n5_X.js} +1 -1
- package/dist/client/assets/{SettingsModal-CSbSmwxZ.js → SettingsModal-C7npF9nB.js} +3 -3
- package/dist/client/assets/{SettingsModal-DnTX4rMK.js → SettingsModal-Crpg0GmF.js} +1 -1
- package/dist/client/assets/{SettingsTextareaRow-y3gQ4u_G.js → SettingsTextareaRow-B7FIDTtF.js} +1 -1
- package/dist/client/assets/{SetupWizardModal-ClAbtrU0.js → SetupWizardModal-CBWFrde9.js} +2 -2
- package/dist/client/assets/{SkillsView-BpEJE16a.js → SkillsView-B1l4qkYj.js} +1 -1
- package/dist/client/assets/{WorkflowNodeEditor-CSbhpY5K.js → WorkflowNodeEditor-e122mQ0Z.js} +1 -1
- package/dist/client/assets/{agent-import-generation-BT0ZBGQA.js → agent-import-generation-CX_NVRPC.js} +1 -1
- package/dist/client/assets/{agentsOrgChartLayout-C_tkb_Pq.js → agentsOrgChartLayout-BAhAecHd.js} +1 -1
- package/dist/client/assets/{app-C8kudFlj.js → app-BO1iuWOt.js} +1 -1
- package/dist/client/assets/{app-B0hINWeH.js → app-BRFfR55q.js} +1 -1
- package/dist/client/assets/{app-BSxI9vvS.js → app-BW03hpJV.js} +3 -3
- package/dist/client/assets/{app-C8jogXBu.js → app-B_skBURN.js} +1 -1
- package/dist/client/assets/{app-hEhR2PdL.js → app-C0JL4IcI.js} +3 -3
- package/dist/client/assets/{app-BeDYumHl.js → app-C5uZ_9kI.js} +3 -3
- package/dist/client/assets/app-DxFiSzSJ.js +5 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BfPYqDRS.js → architectureDiagram-3BPJPVTR-BOmFDW7M.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-BEcaGTmc.js → blockDiagram-GPEHLZMM-D7CnZ9-e.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-BX1xrinF.js → c4Diagram-AAUBKEIU-CQZw8PeP.js} +1 -1
- package/dist/client/assets/channel-Drj7VQBu.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-OksyoE_c.js → chunk-2J33WTMH-DBS2o2AZ.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-C5ivKrB5.js → chunk-4BX2VUAB-CFKEnTmJ.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-C7vkVq-W.js → chunk-55IACEB6-DpjYRScs.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-c-khhz21.js → chunk-727SXJPM-Db7lQvPG.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DwxYJXXQ.js → chunk-AQP2D5EJ-rSGEvDdj.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-BKhdu4up.js → chunk-FMBD7UC4-Cdi9zNME.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-okVZM2p2.js → chunk-ND2GUHAM-D_3BQZ27.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DDso7rM3.js → chunk-QZHKN3VN-DnVoTnz9.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DGxwjUYU.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DGxwjUYU.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-CNT9ltBk.js → cose-bilkent-S5V4N54A-HQFw9frh.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-C5a30-g5.js → dagre-BM42HDAG-BdHp0sc2.js} +1 -1
- package/dist/client/assets/{dashboard-view-Brqe-SVE.js → dashboard-view-C3vvo4zP.js} +1 -1
- package/dist/client/assets/{dashboard-view-DxpkE7dL.js → dashboard-view-CoxDPI4m.js} +1 -1
- package/dist/client/assets/{dashboard-view-WFOf0Kpl.js → dashboard-view-DaqL1Ufr.js} +1 -1
- package/dist/client/assets/{dashboard-view-DyAQ_agZ.js → dashboard-view-mWhkEiwE.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-DG7EyUNe.js → diagram-2AECGRRQ-BDu9AETT.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-DE7lhEna.js → diagram-5GNKFQAL-2qzqfWTz.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DFtJNqNF.js → diagram-KO2AKTUF-DYa-tplS.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DUAuxZQD.js → diagram-LMA3HP47-ChwcQccK.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-DCKETcWy.js → diagram-OG6HWLK6-DoGKft5E.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-pa0blyAD.js → erDiagram-TEJ5UH35-5dsSgUXk.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-D7Veqs1W.js → flowDiagram-I6XJVG4X-BOHB4RzL.js} +1 -1
- package/dist/client/assets/{folder-open-CojKP0zy.js → folder-open-BQcY8jvq.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CsqBMy2D.js → ganttDiagram-6RSMTGT7-BD3aivUG.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BRrVKZJo.js → gitGraphDiagram-PVQCEYII-HBZEAECF.js} +1 -1
- package/dist/client/assets/{index-Dliz9s5K.css → index-26HUNwwJ.css} +1 -1
- package/dist/client/assets/{index-Cv_ouAgx.js → index-QU8sENvc.js} +298 -298
- package/dist/client/assets/{infoDiagram-5YYISTIA-o2GEmmb2.js → infoDiagram-5YYISTIA-d7hsqM0V.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-k1p6nWtj.js → ishikawaDiagram-YF4QCWOH-k4Dnhc_y.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-B43V37S0.js → journeyDiagram-JHISSGLW-Dh3yqZPQ.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-HRMYyAQB.js → kanban-definition-UN3LZRKU-BYqpe7Ju.js} +1 -1
- package/dist/client/assets/{mermaid.core-6vt3zKxL.js → mermaid.core-D5Ccw5tQ.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-D3VJuvfj.js → mindmap-definition-RKZ34NQL-D64P3Mc4.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-D0RjFhk9.js → pieDiagram-4H26LBE5-CtMfKnlZ.js} +1 -1
- package/dist/client/assets/{puzzle-D3dJEsTf.js → puzzle-D4y4g-nb.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DHaEqU3Q.js → quadrantDiagram-W4KKPZXB-B5gSMr9M.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DnfFCRP0.js → requirementDiagram-4Y6WPE33-DKtBfWDi.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-D6GCuiqV.js → sankeyDiagram-5OEKKPKP-BcWeBcNk.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DaGYYzxS.js → sequenceDiagram-3UESZ5HK-Crp7Mc0M.js} +1 -1
- package/dist/client/assets/{standing-instructions-template-CN8Kp4Hq.js → standing-instructions-template-lD5Vcanq.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-ek4Wb8ai.js → stateDiagram-AJRCARHV-CA5FEPYe.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-EPn8Vq7v.js → stateDiagram-v2-BHNVJYJU-Bo75sO6t.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-CyAwCbnE.js → timeline-definition-PNZ67QCA-Dmr4NeTD.js} +1 -1
- package/dist/client/assets/{upload-BhpNL3xI.js → upload-CzJ1o8Px.js} +1 -1
- package/dist/client/assets/{users-Bka1cIkR.js → users-BMnytPKO.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-Bd8-8r3a.js → vennDiagram-CIIHVFJN-DfbBSNgT.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-nNvWflNF.js → wardley-L42UT6IY-Wxq1L5VU.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Dy7XrvPx.js → wardleyDiagram-YWT4CUSO-D6HnIS8j.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-38VTptR4.js → xychartDiagram-2RQKCTM6-5MfFlu30.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/extension.js +844 -264
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugin-sdk/index.d.ts +39 -11
- package/dist/plugin-sdk/index.js +2 -2
- package/dist/plugins/fusion-plugin-acp-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-claude-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-grok-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-linear-import/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
- package/dist/plugins/fusion-plugin-omp-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-quality/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-quality/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-todos/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-todos/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +1 -1
- package/dist/client/assets/app-DZ5j1n3j.js +0 -5
- package/dist/client/assets/channel-BjDq20V4.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-B114c_Fi.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B114c_Fi.js +0 -1
package/dist/extension.js
CHANGED
|
@@ -1840,10 +1840,11 @@ function toSnapshot(result2) {
|
|
|
1840
1840
|
const { priorAttempts: _drop, ...rest } = result2;
|
|
1841
1841
|
return rest;
|
|
1842
1842
|
}
|
|
1843
|
-
function archiveTerminalWorkflowStepFailures(results, archivedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1844
|
-
|
|
1843
|
+
function archiveTerminalWorkflowStepFailures(results, archivedAt = (/* @__PURE__ */ new Date()).toISOString(), options = {}) {
|
|
1844
|
+
const inScope = (result2) => isTerminalFailure(result2) && (!options.workflowStepIds || options.workflowStepIds.has(result2.workflowStepId));
|
|
1845
|
+
if (!results?.some(inScope)) return results;
|
|
1845
1846
|
return results.map((result2) => {
|
|
1846
|
-
if (!
|
|
1847
|
+
if (!inScope(result2)) return result2;
|
|
1847
1848
|
const snapshot = toSnapshot(result2);
|
|
1848
1849
|
const priorAttempts = [snapshot, ...result2.priorAttempts ?? []].slice(0, MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS);
|
|
1849
1850
|
const {
|
|
@@ -2019,6 +2020,26 @@ function isAuditedOperatorBypass(result2) {
|
|
|
2019
2020
|
actor && !AUTOMATED_BYPASS_ACTORS.has(actor) && result2.bypassedAt?.trim() && result2.bypassReason?.trim()
|
|
2020
2021
|
);
|
|
2021
2022
|
}
|
|
2023
|
+
function resolveCollateralArchivedReviewGate(result2, options) {
|
|
2024
|
+
const archivedFrom = result2.remediationArchivedFromStatus;
|
|
2025
|
+
if ((result2.phase ?? "pre-merge") !== "pre-merge" || result2.status !== "skipped" || result2.remediationArchivedAt == null || result2.bypassedBy !== void 0 || archivedFrom !== "failed" && archivedFrom !== "advisory_failure" || options.remediationGateIds.has(result2.workflowStepId)) {
|
|
2026
|
+
return void 0;
|
|
2027
|
+
}
|
|
2028
|
+
const {
|
|
2029
|
+
remediationArchivedAt: _archivedAt,
|
|
2030
|
+
remediationArchivedFromStatus: _archivedFrom,
|
|
2031
|
+
...rest
|
|
2032
|
+
} = result2;
|
|
2033
|
+
return {
|
|
2034
|
+
restored: {
|
|
2035
|
+
...rest,
|
|
2036
|
+
status: archivedFrom,
|
|
2037
|
+
output: COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC,
|
|
2038
|
+
notes: COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC
|
|
2039
|
+
},
|
|
2040
|
+
reason: COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2022
2043
|
function resolveUnprovenReviewApproval(result2, options) {
|
|
2023
2044
|
if ((result2.phase ?? "pre-merge") !== "pre-merge" || !requiresContentReviewProof(result2.workflowStepId, result2) || options.workspace || result2.status !== "passed" || result2.verdict !== "APPROVE" && result2.verdict !== "APPROVE_WITH_NOTES" || result2.reviewInputFingerprint !== void 0 || result2.bypassedBy !== void 0 || result2.remediationArchivedAt != null) {
|
|
2024
2045
|
return void 0;
|
|
@@ -2050,9 +2071,10 @@ function evaluateStep(workflowStepId, results, task, descriptor) {
|
|
|
2050
2071
|
const isPlanDomain = workflowStepId === PLAN_REVIEW_GROUP_ID || result2.reviewKind === "plan";
|
|
2051
2072
|
const notRunApproves = isWorkflowStepNotRun(result2) && !requiresAuthoredVerdict && !isPlanDomain;
|
|
2052
2073
|
const approved = result2.status === "passed" && (requiresAuthoredVerdict ? approvedVerdict : result2.verdict === void 0 || approvedVerdict) || result2.status === "skipped" && !!result2.bypassedBy || notRunApproves;
|
|
2053
|
-
|
|
2074
|
+
const auditedOperatorWaiver = isAuditedOperatorBypass(result2) && descriptor?.kind !== "workspace";
|
|
2075
|
+
if (!approved || result2.remediationArchivedAt != null && !auditedOperatorWaiver) return { workflowStepId, state: "not-approved" };
|
|
2054
2076
|
if (result2.reviewKind === "plan") return { workflowStepId, state: "approved" };
|
|
2055
|
-
if (
|
|
2077
|
+
if (auditedOperatorWaiver) {
|
|
2056
2078
|
return { workflowStepId, state: "approved" };
|
|
2057
2079
|
}
|
|
2058
2080
|
const bindsContent = requiresExplicitVerdict || result2.reviewInputFingerprint !== void 0;
|
|
@@ -2085,7 +2107,7 @@ function evaluateStep(workflowStepId, results, task, descriptor) {
|
|
|
2085
2107
|
if (stale2.length) return { workflowStepId, state: "stale-content", repositories: stale2 };
|
|
2086
2108
|
return { workflowStepId, state: "approved" };
|
|
2087
2109
|
}
|
|
2088
|
-
var AUTOMATED_BYPASS_ACTORS, UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC;
|
|
2110
|
+
var AUTOMATED_BYPASS_ACTORS, UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC, COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC;
|
|
2089
2111
|
var init_pre_merge_approval = __esm({
|
|
2090
2112
|
"../core/src/merge/pre-merge-approval.ts"() {
|
|
2091
2113
|
"use strict";
|
|
@@ -2094,6 +2116,7 @@ var init_pre_merge_approval = __esm({
|
|
|
2094
2116
|
init_workflow_step_results();
|
|
2095
2117
|
AUTOMATED_BYPASS_ACTORS = /* @__PURE__ */ new Set([FAST_MODE_BYPASS_ACTOR]);
|
|
2096
2118
|
UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC = "Content-binding review approval recorded without reviewInputFingerprint; approval invalidated so the gate can run again.";
|
|
2119
|
+
COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC = "Gate archived as collateral of another gate's remediation and restored to its recoverable failed state by self-healing. No reviewer verdict was fabricated; re-run or bypass this gate to clear the merge door.";
|
|
2097
2120
|
}
|
|
2098
2121
|
});
|
|
2099
2122
|
|
|
@@ -2202,6 +2225,9 @@ function hasNonTerminalSteps(task) {
|
|
|
2202
2225
|
function isPreMergeStepsNotRunBlocker(blocker) {
|
|
2203
2226
|
return blocker === PRE_MERGE_STEPS_NOT_RUN_BLOCKER;
|
|
2204
2227
|
}
|
|
2228
|
+
function isStaleContentApprovalBlocker(blocker) {
|
|
2229
|
+
return typeof blocker === "string" && blocker.trim().endsWith(STALE_CONTENT_APPROVAL_BLOCKER);
|
|
2230
|
+
}
|
|
2205
2231
|
function getTaskMergeBlocker(task, options = {}) {
|
|
2206
2232
|
if (!options.skipColumnIdentityCheck) {
|
|
2207
2233
|
const reviewColumns = options.reviewColumns?.size ? options.reviewColumns : void 0;
|
|
@@ -2223,8 +2249,10 @@ function getTaskMergeBlocker(task, options = {}) {
|
|
|
2223
2249
|
}
|
|
2224
2250
|
const approval = evaluatePreMergeApprovals(task, options).find((candidate) => candidate.state !== "approved");
|
|
2225
2251
|
if (approval?.state === "missing") return PRE_MERGE_STEPS_NOT_RUN_BLOCKER;
|
|
2226
|
-
if (approval?.state === "not-approved")
|
|
2227
|
-
|
|
2252
|
+
if (approval?.state === "not-approved") {
|
|
2253
|
+
return `task has enabled pre-merge workflow steps without a current approval (gate '${approval.workflowStepId}')`;
|
|
2254
|
+
}
|
|
2255
|
+
if (approval?.state === "stale-content") return STALE_CONTENT_APPROVAL_BLOCKER;
|
|
2228
2256
|
if (approval?.state === "unprovable-content") return "task has no provable approval for the content being merged";
|
|
2229
2257
|
if (task.workflowStepResults?.some((result2) => {
|
|
2230
2258
|
const phase = result2.phase || "pre-merge";
|
|
@@ -2241,11 +2269,14 @@ function getTaskMergeBlocker(task, options = {}) {
|
|
|
2241
2269
|
return void 0;
|
|
2242
2270
|
}
|
|
2243
2271
|
function getLatestFailedPreMergeReviewStep(task) {
|
|
2244
|
-
|
|
2272
|
+
const results = task.workflowStepResults ?? [];
|
|
2273
|
+
const recentFirst = (a, b3) => {
|
|
2245
2274
|
const aTs = Date.parse(a.completedAt || a.startedAt || "");
|
|
2246
2275
|
const bTs = Date.parse(b3.completedAt || b3.startedAt || "");
|
|
2247
2276
|
return (Number.isFinite(bTs) ? bTs : 0) - (Number.isFinite(aTs) ? aTs : 0);
|
|
2248
|
-
}
|
|
2277
|
+
};
|
|
2278
|
+
const isPreMerge = (result2) => (result2.phase || "pre-merge") === "pre-merge";
|
|
2279
|
+
return results.filter((result2) => isPreMerge(result2) && result2.status === "failed").sort(recentFirst)[0] ?? results.filter((result2) => isPreMerge(result2) && isArchivedRemediationCarrier(result2) && (result2.remediationArchivedFromStatus === "failed" || result2.remediationArchivedFromStatus === "advisory_failure") && !result2.bypassedBy && !result2.supersededAt).sort(recentFirst)[0];
|
|
2249
2280
|
}
|
|
2250
2281
|
function clearMergeConfirmedTransientStatus(status) {
|
|
2251
2282
|
return status !== void 0 && MERGE_CONFIRMED_TRANSIENT_STATUSES.has(status) ? void 0 : status;
|
|
@@ -2329,13 +2360,14 @@ function findPendingPreMergeStep(task) {
|
|
|
2329
2360
|
(a, b3) => new Date(b3.startedAt ?? 0).getTime() - new Date(a.startedAt ?? 0).getTime()
|
|
2330
2361
|
)[0];
|
|
2331
2362
|
}
|
|
2332
|
-
var FUSION_SIBLING_BRANCH_RE, AWAITING_APPROVAL_PAUSE_REASON, HARD_BLOCKING_TASK_STATUSES, SCHEDULER_TRANSIENT_STATUSES, BLOCKING_TASK_STATUSES, NON_TERMINAL_STEP_STATUSES, NON_TERMINAL_WORKFLOW_STATUSES, PRE_MERGE_STEPS_NOT_RUN_BLOCKER, PreMergeStepsNotRunError, REVIEW_ELIGIBLE_SENTINEL_COLUMN, MERGE_CONFIRMED_TRANSIENT_STATUSES;
|
|
2363
|
+
var FUSION_SIBLING_BRANCH_RE, AWAITING_APPROVAL_PAUSE_REASON, HARD_BLOCKING_TASK_STATUSES, SCHEDULER_TRANSIENT_STATUSES, BLOCKING_TASK_STATUSES, NON_TERMINAL_STEP_STATUSES, NON_TERMINAL_WORKFLOW_STATUSES, PRE_MERGE_STEPS_NOT_RUN_BLOCKER, STALE_CONTENT_APPROVAL_BLOCKER, PreMergeStepsNotRunError, REVIEW_ELIGIBLE_SENTINEL_COLUMN, MERGE_CONFIRMED_TRANSIENT_STATUSES;
|
|
2333
2364
|
var init_task_merge = __esm({
|
|
2334
2365
|
"../core/src/merge/task-merge.ts"() {
|
|
2335
2366
|
"use strict";
|
|
2336
2367
|
init_branch_group_completion();
|
|
2337
2368
|
init_task_helpers();
|
|
2338
2369
|
init_pre_merge_approval();
|
|
2370
|
+
init_workflow_step_results();
|
|
2339
2371
|
FUSION_SIBLING_BRANCH_RE = /^fusion\/fn-/i;
|
|
2340
2372
|
AWAITING_APPROVAL_PAUSE_REASON = "awaiting-approval";
|
|
2341
2373
|
HARD_BLOCKING_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -2379,6 +2411,7 @@ var init_task_merge = __esm({
|
|
|
2379
2411
|
"pending"
|
|
2380
2412
|
]);
|
|
2381
2413
|
PRE_MERGE_STEPS_NOT_RUN_BLOCKER = "task has enabled pre-merge workflow steps that never ran";
|
|
2414
|
+
STALE_CONTENT_APPROVAL_BLOCKER = "task has a pre-merge approval recorded against different content";
|
|
2382
2415
|
PreMergeStepsNotRunError = class extends Error {
|
|
2383
2416
|
code = "pre-merge-steps-not-run";
|
|
2384
2417
|
taskId;
|
|
@@ -6358,8 +6391,9 @@ function renderValidationCause(diagnostics8) {
|
|
|
6358
6391
|
}
|
|
6359
6392
|
function featureValidationRepairEligibility(feature) {
|
|
6360
6393
|
const repairableLoop = feature.loopState === "blocked" || feature.loopState === "needs_fix";
|
|
6394
|
+
const unvalidatedPassedMarker = feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId;
|
|
6361
6395
|
return {
|
|
6362
|
-
clear: repairableLoop || feature.status === "blocked",
|
|
6396
|
+
clear: repairableLoop || feature.status === "blocked" || unvalidatedPassedMarker,
|
|
6363
6397
|
reRun: repairableLoop || feature.status === "blocked" && (feature.loopState === void 0 || feature.loopState === "idle")
|
|
6364
6398
|
};
|
|
6365
6399
|
}
|
|
@@ -6423,7 +6457,8 @@ var init_mission_types = __esm({
|
|
|
6423
6457
|
implementing: [],
|
|
6424
6458
|
validating: [],
|
|
6425
6459
|
needs_fix: ["idle", "implementing"],
|
|
6426
|
-
|
|
6460
|
+
/* FNXC:Missions 2026-09-05-22:07: Explicit repair may return a fabricated unvalidated passed marker to idle (issue #3574). */
|
|
6461
|
+
passed: ["idle"],
|
|
6427
6462
|
blocked: ["idle", "implementing"]
|
|
6428
6463
|
};
|
|
6429
6464
|
VALIDATOR_RUN_STATUSES = ["running", "passed", "failed", "blocked", "error"];
|
|
@@ -13530,7 +13565,7 @@ function isBlockingFinding(finding, threshold) {
|
|
|
13530
13565
|
if (!finding.severity) return true;
|
|
13531
13566
|
return SEVERITY_RANK[finding.severity] >= SEVERITY_RANK[threshold];
|
|
13532
13567
|
}
|
|
13533
|
-
function applyReviewSeverityGate({ verdict, findings, threshold }) {
|
|
13568
|
+
function applyReviewSeverityGate({ verdict, findings, threshold, findingsUnreadable }) {
|
|
13534
13569
|
const all = findings ?? [];
|
|
13535
13570
|
const open7 = all.filter(isActionableReviewFinding);
|
|
13536
13571
|
const resolved = all.filter((finding) => !isActionableReviewFinding(finding));
|
|
@@ -13538,6 +13573,7 @@ function applyReviewSeverityGate({ verdict, findings, threshold }) {
|
|
|
13538
13573
|
const advisory = open7.filter((finding) => !isBlockingFinding(finding, threshold));
|
|
13539
13574
|
if (verdict !== "REVISE") return { verdict, downgraded: false, blocking, advisory, resolved };
|
|
13540
13575
|
if (blocking.length > 0) return { verdict, downgraded: false, blocking, advisory, resolved };
|
|
13576
|
+
if (findingsUnreadable) return { verdict, downgraded: false, blocking, advisory, resolved };
|
|
13541
13577
|
return { verdict: "APPROVE_WITH_NOTES", downgraded: true, blocking, advisory, resolved };
|
|
13542
13578
|
}
|
|
13543
13579
|
function formatFindingsByPriority(findings) {
|
|
@@ -18279,6 +18315,18 @@ function detectWorkflowDrift(ir2, pin) {
|
|
|
18279
18315
|
function defaultCodingWorkflowIr() {
|
|
18280
18316
|
return resolveDefaultWorkflowIr();
|
|
18281
18317
|
}
|
|
18318
|
+
async function prefetchWorkflowSelections(store, taskIds, cache) {
|
|
18319
|
+
const missingIds = [...new Set(taskIds)].filter((id) => !cache.has(id));
|
|
18320
|
+
if (missingIds.length === 0) return { batched: 0, singles: 0 };
|
|
18321
|
+
if (!store.getTaskWorkflowSelectionsAsync) return { batched: 0, singles: missingIds.length };
|
|
18322
|
+
try {
|
|
18323
|
+
const selections = await store.getTaskWorkflowSelectionsAsync(missingIds);
|
|
18324
|
+
for (const id of missingIds) cache.set(id, selections.get(id));
|
|
18325
|
+
return { batched: 1, singles: 0 };
|
|
18326
|
+
} catch {
|
|
18327
|
+
return { batched: 0, singles: missingIds.length };
|
|
18328
|
+
}
|
|
18329
|
+
}
|
|
18282
18330
|
function resolveSeamPromptFromIr(ir2, seam) {
|
|
18283
18331
|
for (const node of ir2.nodes) {
|
|
18284
18332
|
if (node.kind !== "prompt") continue;
|
|
@@ -18497,9 +18545,9 @@ function resolveLifecycleColumns(ir2) {
|
|
|
18497
18545
|
archived: first(LIFECYCLE_ROLE_FLAGS.archived)
|
|
18498
18546
|
};
|
|
18499
18547
|
}
|
|
18500
|
-
async function resolveTaskLifecycleColumns(store, taskId, cache) {
|
|
18548
|
+
async function resolveTaskLifecycleColumns(store, taskId, cache, selectionCache) {
|
|
18501
18549
|
try {
|
|
18502
|
-
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache);
|
|
18550
|
+
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache, selectionCache);
|
|
18503
18551
|
return resolveLifecycleColumns(ir2);
|
|
18504
18552
|
} catch {
|
|
18505
18553
|
return void 0;
|
|
@@ -30495,18 +30543,33 @@ function isValidPluginParserId(id) {
|
|
|
30495
30543
|
const seg = /^[a-z0-9-]+$/;
|
|
30496
30544
|
return seg.test(parts[1]) && seg.test(parts[2]);
|
|
30497
30545
|
}
|
|
30498
|
-
function
|
|
30499
|
-
const steps = [];
|
|
30546
|
+
function matchStepHeadings(content) {
|
|
30500
30547
|
const stepRegex = /^###\s+Step\s+\d+[^:]*:\s*(.+)$/gm;
|
|
30501
|
-
const annotatedRegex = /^###\s+Step\s+\d+\s*\(depends:\s*([^)]*)\)\s*:\s*([^\n]+)$/;
|
|
30502
30548
|
const matches = [];
|
|
30503
30549
|
let match;
|
|
30504
30550
|
while ((match = stepRegex.exec(content)) !== null) {
|
|
30505
30551
|
const heading = /^###\s+Step\s+(\d+)/.exec(match[0]);
|
|
30506
|
-
if (heading)
|
|
30507
|
-
|
|
30508
|
-
|
|
30552
|
+
if (!heading) continue;
|
|
30553
|
+
const index2 = match.index;
|
|
30554
|
+
const newline = content.indexOf("\n", index2);
|
|
30555
|
+
matches.push({
|
|
30556
|
+
headingNumber: Number(heading[1]),
|
|
30557
|
+
index: index2,
|
|
30558
|
+
headingLineEnd: newline === -1 ? content.length : newline,
|
|
30559
|
+
match: match[0]
|
|
30560
|
+
});
|
|
30509
30561
|
}
|
|
30562
|
+
return matches;
|
|
30563
|
+
}
|
|
30564
|
+
function parseStepHeadings(content) {
|
|
30565
|
+
const steps = [];
|
|
30566
|
+
const annotatedRegex = /^###\s+Step\s+\d+\s*\(depends:\s*([^)]*)\)\s*:\s*([^\n]+)$/;
|
|
30567
|
+
const matches = matchStepHeadings(content).map((entry) => ({
|
|
30568
|
+
full: entry.match,
|
|
30569
|
+
name: /^###\s+Step\s+\d+[^:]*:\s*(.+)$/m.exec(entry.match)?.[1] ?? "",
|
|
30570
|
+
headingNumber: entry.headingNumber
|
|
30571
|
+
}));
|
|
30572
|
+
let match;
|
|
30510
30573
|
const offset = resolveAuthoredStepHeadingOffset(matches.map((entry) => entry.headingNumber));
|
|
30511
30574
|
for (const { full, name: legacyName } of matches) {
|
|
30512
30575
|
if (!full.includes("(depends:")) {
|
|
@@ -75103,8 +75166,8 @@ function appendAgentLogEntriesSync(taskDir, entries) {
|
|
|
75103
75166
|
if (entries.length === 0) return [];
|
|
75104
75167
|
const filePath = getAgentLogFilePath(taskDir);
|
|
75105
75168
|
mkdirSync5(dirname14(filePath), { recursive: true });
|
|
75106
|
-
const startingLineNo =
|
|
75107
|
-
const payload = entries.map((entry) => serializeEntry(entry)).join("")
|
|
75169
|
+
const { lineCount: startingLineNo, endsWithNewline } = inspectAgentLogTail(filePath);
|
|
75170
|
+
const payload = `${startingLineNo > 0 && !endsWithNewline ? "\n" : ""}${entries.map((entry) => serializeEntry(entry)).join("")}`;
|
|
75108
75171
|
appendFileSync(filePath, payload, "utf8");
|
|
75109
75172
|
return entries.map((entry, index2) => materializeEntry(entry, startingLineNo + index2 + 1));
|
|
75110
75173
|
}
|
|
@@ -75140,6 +75203,7 @@ function readAllAgentLogEntries(taskDir, options = {}) {
|
|
|
75140
75203
|
}
|
|
75141
75204
|
const lines = content.split("\n");
|
|
75142
75205
|
const entries = [];
|
|
75206
|
+
const skippedLines = [];
|
|
75143
75207
|
for (let index2 = 0; index2 < lines.length; index2 += 1) {
|
|
75144
75208
|
const rawLine = lines[index2];
|
|
75145
75209
|
if (!rawLine) continue;
|
|
@@ -75161,9 +75225,18 @@ function readAllAgentLogEntries(taskDir, options = {}) {
|
|
|
75161
75225
|
}
|
|
75162
75226
|
entries.push(entry);
|
|
75163
75227
|
} catch (error3) {
|
|
75164
|
-
|
|
75228
|
+
skippedLines.push({
|
|
75229
|
+
lineNo,
|
|
75230
|
+
message: error3 instanceof Error ? error3.message : String(error3)
|
|
75231
|
+
});
|
|
75165
75232
|
}
|
|
75166
75233
|
}
|
|
75234
|
+
const firstSkipped = skippedLines[0];
|
|
75235
|
+
if (firstSkipped) {
|
|
75236
|
+
log7.warn(
|
|
75237
|
+
`Skipped ${skippedLines.length} malformed JSONL line(s) in ${filePath}; first line ${firstSkipped.lineNo}: ${firstSkipped.message}`
|
|
75238
|
+
);
|
|
75239
|
+
}
|
|
75167
75240
|
return entries;
|
|
75168
75241
|
}
|
|
75169
75242
|
function normalizeTimingMs(value) {
|
|
@@ -75206,16 +75279,19 @@ function materializeEntry(entry, lineNo) {
|
|
|
75206
75279
|
sourceRef: buildAgentLogSourceRef(entry.taskId, lineNo)
|
|
75207
75280
|
};
|
|
75208
75281
|
}
|
|
75209
|
-
function
|
|
75282
|
+
function inspectAgentLogTail(filePath) {
|
|
75210
75283
|
if (!existsSync16(filePath)) {
|
|
75211
|
-
return 0;
|
|
75284
|
+
return { lineCount: 0, endsWithNewline: true };
|
|
75212
75285
|
}
|
|
75213
75286
|
const content = readFileSync7(filePath, "utf8");
|
|
75214
75287
|
if (content.length === 0) {
|
|
75215
|
-
return 0;
|
|
75288
|
+
return { lineCount: 0, endsWithNewline: true };
|
|
75216
75289
|
}
|
|
75217
75290
|
const lines = content.split("\n");
|
|
75218
|
-
return
|
|
75291
|
+
return {
|
|
75292
|
+
lineCount: lines.at(-1) === "" ? lines.length - 1 : lines.length,
|
|
75293
|
+
endsWithNewline: content.endsWith("\n")
|
|
75294
|
+
};
|
|
75219
75295
|
}
|
|
75220
75296
|
function pruneAgentLogFiles(tasksDir, retentionDays, scanTaskIds) {
|
|
75221
75297
|
if (!Number.isFinite(retentionDays) || retentionDays <= 0 || !existsSync16(tasksDir)) {
|
|
@@ -79005,6 +79081,10 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79005
79081
|
* FNXC:Missions 2026-07-11-12:35:
|
|
79006
79082
|
* A generated fix can also supersede itself once its own validator/loop evidence has passed.
|
|
79007
79083
|
* Reconciliation treats that as terminal evidence so a completed fix does not stay active only because its ancestor previously failed.
|
|
79084
|
+
*
|
|
79085
|
+
* FNXC:Missions 2026-09-05-22:07:
|
|
79086
|
+
* Superseding a generated fix means it is no longer needed, not that it passed validation.
|
|
79087
|
+
* Never stamp unearned validator evidence because that re-arms the reconciler's own trigger (issue #3574).
|
|
79008
79088
|
*/
|
|
79009
79089
|
reconcileSupersededGeneratedFixFeatures(sliceId) {
|
|
79010
79090
|
const features = this.listFeatures(sliceId);
|
|
@@ -79029,7 +79109,42 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79029
79109
|
ancestorPassedMemo.set(feature.id, passed);
|
|
79030
79110
|
return passed;
|
|
79031
79111
|
};
|
|
79032
|
-
const
|
|
79112
|
+
const isFabricatedMarker = (feature) => Boolean(
|
|
79113
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasPassedAncestor(feature)
|
|
79114
|
+
);
|
|
79115
|
+
const repairedFeatureIds = features.filter(isFabricatedMarker).map((feature) => feature.id);
|
|
79116
|
+
const repairedIds = new Set(repairedFeatureIds);
|
|
79117
|
+
const supersededFeatureIds = features.filter((feature) => !repairedIds.has(feature.id)).filter((feature) => feature.generatedFromFeatureId && (featureHasPassed(feature) || hasPassedAncestor(feature))).filter((feature) => feature.status !== "done" || feature.loopState !== "passed" || feature.taskId).map((feature) => feature.id);
|
|
79118
|
+
if (repairedFeatureIds.length > 0) {
|
|
79119
|
+
this.db.transaction(() => {
|
|
79120
|
+
const hasCurrentPassedAncestor = (feature, seen = /* @__PURE__ */ new Set()) => {
|
|
79121
|
+
const sourceFeatureId = feature.generatedFromFeatureId;
|
|
79122
|
+
if (!sourceFeatureId || seen.has(sourceFeatureId)) return false;
|
|
79123
|
+
seen.add(sourceFeatureId);
|
|
79124
|
+
const sourceFeature = this.getFeature(sourceFeatureId);
|
|
79125
|
+
return featureHasPassed(sourceFeature) || (sourceFeature ? hasCurrentPassedAncestor(sourceFeature, seen) : false);
|
|
79126
|
+
};
|
|
79127
|
+
const isCurrentFabricatedMarker = (feature) => Boolean(
|
|
79128
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasCurrentPassedAncestor(feature)
|
|
79129
|
+
);
|
|
79130
|
+
for (const featureId of repairedFeatureIds) {
|
|
79131
|
+
const feature = this.getFeature(featureId);
|
|
79132
|
+
if (!feature || !isCurrentFabricatedMarker(feature)) continue;
|
|
79133
|
+
this.updateFeature(featureId, { status: "defined", taskId: void 0, loopState: "idle", lastValidatorStatus: void 0 });
|
|
79134
|
+
const slice = this.getSlice(feature.sliceId);
|
|
79135
|
+
const milestone = slice ? this.getMilestone(slice.milestoneId) : void 0;
|
|
79136
|
+
if (milestone) {
|
|
79137
|
+
this.logMissionEvent(milestone.missionId, "feature_status_changed", `Feature status changed from ${feature.status} to defined`, {
|
|
79138
|
+
featureId,
|
|
79139
|
+
field: "status",
|
|
79140
|
+
from: feature.status,
|
|
79141
|
+
to: "defined",
|
|
79142
|
+
source: "superseded-fix-marker-repair"
|
|
79143
|
+
});
|
|
79144
|
+
}
|
|
79145
|
+
}
|
|
79146
|
+
});
|
|
79147
|
+
}
|
|
79033
79148
|
if (supersededFeatureIds.length > 0) {
|
|
79034
79149
|
this.db.transaction(() => {
|
|
79035
79150
|
for (const featureId of supersededFeatureIds) {
|
|
@@ -79038,8 +79153,7 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79038
79153
|
this.updateFeature(featureId, {
|
|
79039
79154
|
status: "done",
|
|
79040
79155
|
taskId: void 0,
|
|
79041
|
-
loopState: "passed"
|
|
79042
|
-
lastValidatorStatus: "passed"
|
|
79156
|
+
loopState: "passed"
|
|
79043
79157
|
});
|
|
79044
79158
|
if (feature.taskId) {
|
|
79045
79159
|
this.db.prepare('UPDATE tasks SET missionId = NULL, sliceId = NULL WHERE id = ? AND "deletedAt" IS NULL').run(feature.taskId);
|
|
@@ -79049,7 +79163,9 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79049
79163
|
}
|
|
79050
79164
|
return {
|
|
79051
79165
|
supersededCount: supersededFeatureIds.length,
|
|
79052
|
-
featureIds: supersededFeatureIds
|
|
79166
|
+
featureIds: supersededFeatureIds,
|
|
79167
|
+
repairedCount: repairedFeatureIds.length,
|
|
79168
|
+
repairedFeatureIds
|
|
79053
79169
|
};
|
|
79054
79170
|
}
|
|
79055
79171
|
/**
|
|
@@ -82393,10 +82509,12 @@ var init_async_mission_store = __esm({
|
|
|
82393
82509
|
throw new RepairNotEligibleError(featureId, options.action);
|
|
82394
82510
|
}
|
|
82395
82511
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
82512
|
+
const hasUnvalidatedMarker = options.action === "clear" && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId;
|
|
82513
|
+
const appliesUnvalidatedMarker = hasUnvalidatedMarker && Boolean(feature.generatedFromFeatureId || feature.generatedFromRunId) && !feature.taskId && feature.status === "done";
|
|
82396
82514
|
const priorLoopState = feature.loopState;
|
|
82397
82515
|
const priorStatus = feature.status;
|
|
82398
82516
|
let groundTruthMetadata = {};
|
|
82399
|
-
if (options.action === "clear" && feature.status === "blocked") {
|
|
82517
|
+
if (options.action === "clear" && (feature.status === "blocked" || appliesUnvalidatedMarker)) {
|
|
82400
82518
|
const fence = options.groundTruth;
|
|
82401
82519
|
if (!fence || fence.featureId !== featureId || fence.taskId !== (feature.taskId ?? null)) {
|
|
82402
82520
|
throw new RepairGroundTruthStaleError(featureId);
|
|
@@ -82426,14 +82544,14 @@ var init_async_mission_store = __esm({
|
|
|
82426
82544
|
let updated;
|
|
82427
82545
|
let run;
|
|
82428
82546
|
if (options.action === "clear") {
|
|
82429
|
-
const currentLoop = feature.loopState;
|
|
82430
|
-
const appliesLoop = currentLoop === "blocked" || currentLoop === "needs_fix";
|
|
82547
|
+
const currentLoop = feature.loopState ?? "idle";
|
|
82548
|
+
const appliesLoop = currentLoop === "blocked" || currentLoop === "needs_fix" || appliesUnvalidatedMarker;
|
|
82431
82549
|
const nextLoop = appliesLoop ? options.resolvedLoopState ?? "idle" : currentLoop;
|
|
82432
82550
|
if (appliesLoop && !FEATURE_LOOP_REPAIR_TRANSITIONS[currentLoop].includes(nextLoop)) {
|
|
82433
82551
|
throw new Error(`Invalid validation repair transition from '${currentLoop}' to '${nextLoop}'`);
|
|
82434
82552
|
}
|
|
82435
|
-
const appliesStatus = feature.status === "blocked";
|
|
82436
|
-
const nextStatus = appliesStatus ? options.resolvedStatus : feature.status;
|
|
82553
|
+
const appliesStatus = feature.status === "blocked" || appliesUnvalidatedMarker;
|
|
82554
|
+
const nextStatus = appliesUnvalidatedMarker ? "defined" : appliesStatus ? options.resolvedStatus : feature.status;
|
|
82437
82555
|
if (appliesStatus && (nextStatus !== "in-progress" && nextStatus !== "triaged" && nextStatus !== "defined")) {
|
|
82438
82556
|
throw new Error("Validation repair requires resolvedStatus of in-progress, triaged, or defined");
|
|
82439
82557
|
}
|
|
@@ -82445,13 +82563,13 @@ var init_async_mission_store = __esm({
|
|
|
82445
82563
|
const matchesLane = nextStatus === "in-progress" && fence.taskLiveness === "live" && fence.laneRole === "wip" || nextStatus === "triaged" && fence.taskLiveness === "live" && fence.laneRole === "planner" || nextStatus === "defined" && fence.taskLiveness === "absent" && fence.laneRole === "none";
|
|
82446
82564
|
if (!matchesLane) throw new RepairGroundTruthStaleError(featureId);
|
|
82447
82565
|
}
|
|
82448
|
-
if (!appliesLoop && !appliesStatus) throw new RepairNotEligibleError(featureId, options.action);
|
|
82566
|
+
if (!appliesLoop && !appliesStatus && !hasUnvalidatedMarker) throw new RepairNotEligibleError(featureId, options.action);
|
|
82449
82567
|
updated = {
|
|
82450
82568
|
...feature,
|
|
82451
82569
|
loopState: nextLoop,
|
|
82452
82570
|
status: nextStatus,
|
|
82453
82571
|
implementationAttemptCount: 0,
|
|
82454
|
-
...feature.lastValidatorStatus === "blocked" || feature.lastValidatorStatus === "failed" ? { lastValidatorStatus: void 0 } : {},
|
|
82572
|
+
...feature.lastValidatorStatus === "blocked" || feature.lastValidatorStatus === "failed" || hasUnvalidatedMarker ? { lastValidatorStatus: void 0 } : {},
|
|
82455
82573
|
updatedAt: now
|
|
82456
82574
|
};
|
|
82457
82575
|
await updateFeature(tx, updated);
|
|
@@ -82919,10 +83037,60 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0,
|
|
|
82919
83037
|
const source = byId.get(sourceId);
|
|
82920
83038
|
return passed(source) || (source ? hasPassedAncestor(source, seen) : false);
|
|
82921
83039
|
};
|
|
83040
|
+
const isFabricatedMarker = (feature) => Boolean(
|
|
83041
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasPassedAncestor(feature)
|
|
83042
|
+
);
|
|
83043
|
+
const repairIds = new Set(features.filter(isFabricatedMarker).map((feature) => feature.id));
|
|
82922
83044
|
const ids = [];
|
|
82923
83045
|
for (const feature of features) {
|
|
82924
|
-
if (!feature.generatedFromFeatureId || !(passed(feature) || hasPassedAncestor(feature))) continue;
|
|
82925
|
-
if (feature.status !== "done" || feature.loopState !== "passed" || feature.
|
|
83046
|
+
if (repairIds.has(feature.id) || !feature.generatedFromFeatureId || !(passed(feature) || hasPassedAncestor(feature))) continue;
|
|
83047
|
+
if (feature.status !== "done" || feature.loopState !== "passed" || feature.taskId) ids.push(feature.id);
|
|
83048
|
+
}
|
|
83049
|
+
const repairedFeatureIds = [];
|
|
83050
|
+
if (repairIds.size > 0) {
|
|
83051
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83052
|
+
const repaired = await this.layer.transactionImmediate(async (tx) => {
|
|
83053
|
+
const locked = await tx.select({ id: project_exports.missionFeatures.id }).from(project_exports.missionFeatures).where(inArray(project_exports.missionFeatures.id, [...repairIds])).for("update");
|
|
83054
|
+
const preImages = locked.length > 0 ? await listFeaturesByIds(tx, locked.map((row) => row.id)) : [];
|
|
83055
|
+
const currentById = new Map(preImages.map((feature) => [feature.id, feature]));
|
|
83056
|
+
let missingAncestorIds = [...new Set(preImages.map((feature) => feature.generatedFromFeatureId).filter((id) => Boolean(id) && !currentById.has(id)))];
|
|
83057
|
+
while (missingAncestorIds.length > 0) {
|
|
83058
|
+
const lockedAncestors = await tx.select({ id: project_exports.missionFeatures.id }).from(project_exports.missionFeatures).where(inArray(project_exports.missionFeatures.id, missingAncestorIds)).for("update");
|
|
83059
|
+
const ancestors = lockedAncestors.length > 0 ? await listFeaturesByIds(tx, lockedAncestors.map((row) => row.id)) : [];
|
|
83060
|
+
for (const ancestor of ancestors) currentById.set(ancestor.id, ancestor);
|
|
83061
|
+
missingAncestorIds = [...new Set(ancestors.map((ancestor) => ancestor.generatedFromFeatureId).filter((id) => Boolean(id) && !currentById.has(id)))];
|
|
83062
|
+
}
|
|
83063
|
+
const hasCurrentPassedAncestor = (feature, seen = /* @__PURE__ */ new Set()) => {
|
|
83064
|
+
const sourceId = feature.generatedFromFeatureId;
|
|
83065
|
+
if (!sourceId || seen.has(sourceId)) return false;
|
|
83066
|
+
seen.add(sourceId);
|
|
83067
|
+
const source = currentById.get(sourceId);
|
|
83068
|
+
return passed(source) || (source ? hasCurrentPassedAncestor(source, seen) : false);
|
|
83069
|
+
};
|
|
83070
|
+
const changed = preImages.filter((feature) => Boolean(
|
|
83071
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasCurrentPassedAncestor(feature)
|
|
83072
|
+
));
|
|
83073
|
+
if (changed.length === 0) return { events: [], features: [] };
|
|
83074
|
+
await tx.update(project_exports.missionFeatures).set({
|
|
83075
|
+
status: "defined",
|
|
83076
|
+
taskId: null,
|
|
83077
|
+
loopState: "idle",
|
|
83078
|
+
lastValidatorStatus: null,
|
|
83079
|
+
updatedAt: now
|
|
83080
|
+
}).where(inArray(project_exports.missionFeatures.id, changed.map((feature) => feature.id)));
|
|
83081
|
+
let seq = await getMaxEventSeq(tx);
|
|
83082
|
+
const events = [];
|
|
83083
|
+
for (const feature of changed) {
|
|
83084
|
+
const event = await this.recordFeatureStatusChange(tx, feature, "defined", { type: "system", id: "mission-store", source: "superseded-fix-marker-repair" }, void 0, ++seq);
|
|
83085
|
+
if (event) events.push(event);
|
|
83086
|
+
}
|
|
83087
|
+
return { events, features: changed };
|
|
83088
|
+
});
|
|
83089
|
+
for (const event of repaired.events) this.emit("mission:event", event);
|
|
83090
|
+
for (const feature of repaired.features) {
|
|
83091
|
+
this.emit("feature:updated", { ...feature, status: "defined", taskId: void 0, loopState: "idle", lastValidatorStatus: void 0, updatedAt: now });
|
|
83092
|
+
repairedFeatureIds.push(feature.id);
|
|
83093
|
+
}
|
|
82926
83094
|
}
|
|
82927
83095
|
if (ids.length > 0) {
|
|
82928
83096
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -82930,13 +83098,12 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0,
|
|
|
82930
83098
|
const locked = await tx.select({ id: project_exports.missionFeatures.id }).from(project_exports.missionFeatures).where(inArray(project_exports.missionFeatures.id, ids)).for("update");
|
|
82931
83099
|
const lockedIds = locked.map((row) => row.id);
|
|
82932
83100
|
const preImages = lockedIds.length > 0 ? await listFeaturesByIds(tx, lockedIds) : [];
|
|
82933
|
-
const changed = preImages.filter((feature) => feature.status !== "done" || feature.loopState !== "passed" || feature.
|
|
83101
|
+
const changed = preImages.filter((feature) => feature.status !== "done" || feature.loopState !== "passed" || feature.taskId);
|
|
82934
83102
|
if (changed.length === 0) return { events: [], updatedFeatures: [] };
|
|
82935
83103
|
await tx.update(project_exports.missionFeatures).set({
|
|
82936
83104
|
status: "done",
|
|
82937
83105
|
taskId: null,
|
|
82938
83106
|
loopState: "passed",
|
|
82939
|
-
lastValidatorStatus: "passed",
|
|
82940
83107
|
updatedAt: now
|
|
82941
83108
|
}).where(inArray(project_exports.missionFeatures.id, changed.map((feature) => feature.id)));
|
|
82942
83109
|
let seq = await getMaxEventSeq(tx);
|
|
@@ -82951,13 +83118,14 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0,
|
|
|
82951
83118
|
});
|
|
82952
83119
|
for (const event of events) this.emit("mission:event", event);
|
|
82953
83120
|
for (const feature of updatedFeatures) {
|
|
82954
|
-
const updated = { ...feature, status: "done", taskId: void 0, loopState: "passed",
|
|
83121
|
+
const updated = { ...feature, status: "done", taskId: void 0, loopState: "passed", updatedAt: now };
|
|
82955
83122
|
this.emit("feature:updated", updated);
|
|
82956
83123
|
if (feature.taskId) await clearTaskMissionLinkage(this.db, feature.taskId);
|
|
82957
83124
|
}
|
|
82958
83125
|
if (updatedFeatures.length > 0) await this.recomputeSliceStatus(sliceId);
|
|
82959
83126
|
}
|
|
82960
|
-
|
|
83127
|
+
if (repairedFeatureIds.length > 0) await this.recomputeSliceStatus(sliceId);
|
|
83128
|
+
return { supersededCount: ids.length, featureIds: ids, repairedCount: repairedFeatureIds.length, repairedFeatureIds };
|
|
82961
83129
|
}
|
|
82962
83130
|
async transitionLoopState(featureId, newState) {
|
|
82963
83131
|
const feature = await getFeature(this.db, featureId);
|
|
@@ -88589,19 +88757,81 @@ async function archiveAllDoneImpl(store, options) {
|
|
|
88589
88757
|
for (const column of completeColumns) {
|
|
88590
88758
|
for (const task of await store.listTasks({ slim: true, column })) doneById.set(task.id, task);
|
|
88591
88759
|
}
|
|
88592
|
-
|
|
88593
|
-
|
|
88594
|
-
|
|
88760
|
+
if (doneById.size === 0) return { archived: [], skipped: [] };
|
|
88761
|
+
const archivedColumns = await resolveArchivedLanes(store).catch(() => void 0) ?? new Set(LEGACY_ARCHIVE_LANES);
|
|
88762
|
+
const liveTasks = (await store.listTasks({ slim: true, includeArchived: false })).filter((task) => !task.deletedAt && !archivedColumns.has(task.column));
|
|
88763
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
88764
|
+
for (const task of liveTasks) {
|
|
88765
|
+
if (!task.sourceParentTaskId) continue;
|
|
88766
|
+
const children = childrenByParentId.get(task.sourceParentTaskId) ?? [];
|
|
88767
|
+
children.push(task.id);
|
|
88768
|
+
childrenByParentId.set(task.sourceParentTaskId, children);
|
|
88769
|
+
}
|
|
88770
|
+
for (const children of childrenByParentId.values()) children.sort();
|
|
88771
|
+
const bypassLineageGate = options?.removeLineageReferences === true;
|
|
88772
|
+
const archivableIds = new Set(doneById.keys());
|
|
88773
|
+
const skipped = [];
|
|
88774
|
+
if (!bypassLineageGate) {
|
|
88775
|
+
let firstRemovalWave = true;
|
|
88776
|
+
while (true) {
|
|
88777
|
+
const removals = [...archivableIds].filter(
|
|
88778
|
+
(id) => (childrenByParentId.get(id) ?? []).some((childId) => !archivableIds.has(childId))
|
|
88779
|
+
).sort();
|
|
88780
|
+
if (removals.length === 0) break;
|
|
88781
|
+
for (const id of removals) {
|
|
88782
|
+
const blockers = (childrenByParentId.get(id) ?? []).filter((childId) => !archivableIds.has(childId));
|
|
88783
|
+
skipped.push({
|
|
88784
|
+
id,
|
|
88785
|
+
reason: firstRemovalWave ? "open-lineage-children" : "blocked-by-unarchived-batch-member",
|
|
88786
|
+
blockers,
|
|
88787
|
+
message: firstRemovalWave ? `Live lineage children prevent archiving ${id}.` : `Unarchived batch members prevent archiving ${id}.`
|
|
88788
|
+
});
|
|
88789
|
+
archivableIds.delete(id);
|
|
88790
|
+
}
|
|
88791
|
+
firstRemovalWave = false;
|
|
88792
|
+
}
|
|
88793
|
+
}
|
|
88794
|
+
const archived = [];
|
|
88795
|
+
const remaining = new Set(archivableIds);
|
|
88796
|
+
const archivedIds = /* @__PURE__ */ new Set();
|
|
88797
|
+
const archiveWave = async (ids) => {
|
|
88798
|
+
const settled = await Promise.allSettled(ids.map((id) => store.archiveTask(id, {
|
|
88799
|
+
cleanup: true,
|
|
88800
|
+
removeLineageReferences: options?.removeLineageReferences
|
|
88801
|
+
})));
|
|
88802
|
+
for (const [index2, result2] of settled.entries()) {
|
|
88803
|
+
const id = ids[index2];
|
|
88804
|
+
if (result2.status === "fulfilled") {
|
|
88805
|
+
archived.push(result2.value);
|
|
88806
|
+
archivedIds.add(id);
|
|
88807
|
+
} else {
|
|
88808
|
+
const error3 = result2.reason;
|
|
88809
|
+
const blockers = error3 instanceof TaskHasLineageChildrenError ? error3.childIds : error3 instanceof TaskHasDependentsError ? error3.dependentIds : [];
|
|
88810
|
+
skipped.push({ id, reason: "archive-failed", blockers, message: error3 instanceof Error ? error3.message : String(error3) });
|
|
88811
|
+
}
|
|
88812
|
+
remaining.delete(id);
|
|
88813
|
+
}
|
|
88814
|
+
};
|
|
88815
|
+
while (remaining.size > 0) {
|
|
88816
|
+
const wave = [...remaining].filter(
|
|
88817
|
+
(id) => bypassLineageGate ? (childrenByParentId.get(id) ?? []).filter((childId) => remaining.has(childId)).length === 0 : (childrenByParentId.get(id) ?? []).filter((childId) => archivableIds.has(childId)).every((childId) => archivedIds.has(childId))
|
|
88818
|
+
).sort();
|
|
88819
|
+
if (wave.length > 0) {
|
|
88820
|
+
await archiveWave(wave);
|
|
88821
|
+
continue;
|
|
88822
|
+
}
|
|
88823
|
+
if (bypassLineageGate) {
|
|
88824
|
+
await archiveWave([...remaining].sort());
|
|
88825
|
+
} else {
|
|
88826
|
+
for (const id of [...remaining].sort()) {
|
|
88827
|
+
const blockers = (childrenByParentId.get(id) ?? []).filter((childId) => remaining.has(childId) || !archivedIds.has(childId));
|
|
88828
|
+
skipped.push({ id, reason: "blocked-by-unarchived-batch-member", blockers, message: `Unarchived batch members prevent archiving ${id}.` });
|
|
88829
|
+
remaining.delete(id);
|
|
88830
|
+
}
|
|
88831
|
+
}
|
|
88595
88832
|
}
|
|
88596
|
-
|
|
88597
|
-
|
|
88598
|
-
(task) => store.archiveTask(task.id, {
|
|
88599
|
-
cleanup: true,
|
|
88600
|
-
removeLineageReferences: options?.removeLineageReferences
|
|
88601
|
-
})
|
|
88602
|
-
)
|
|
88603
|
-
);
|
|
88604
|
-
return archivedTasks3;
|
|
88833
|
+
skipped.sort((left, right) => left.id.localeCompare(right.id));
|
|
88834
|
+
return { archived, skipped };
|
|
88605
88835
|
}
|
|
88606
88836
|
async function resolveUnarchiveTargetColumnImpl(store, preArchiveColumn, taskId) {
|
|
88607
88837
|
const lifecycle2 = taskId ? await resolveTaskLifecycleColumns(store, taskId) : void 0;
|
|
@@ -89065,6 +89295,7 @@ var init_task_artifacts_ops = __esm({
|
|
|
89065
89295
|
init_types();
|
|
89066
89296
|
init_store();
|
|
89067
89297
|
init_project_lane_vocabulary();
|
|
89298
|
+
init_errors3();
|
|
89068
89299
|
LEGACY_ARCHIVE_LANES = ["archived"];
|
|
89069
89300
|
}
|
|
89070
89301
|
});
|
|
@@ -106936,18 +107167,18 @@ function hasFreshAgentLogActivitySinceTaskUpdate(store, task, now, reviewColumns
|
|
|
106936
107167
|
}
|
|
106937
107168
|
return Math.max(0, now - latestAgentLogMs) < DEFAULT_STALE_MERGING_MIN_AGE_MS;
|
|
106938
107169
|
}
|
|
106939
|
-
async function resolveHoldColumnForTask(store, taskId, cache) {
|
|
107170
|
+
async function resolveHoldColumnForTask(store, taskId, cache, selectionCache) {
|
|
106940
107171
|
try {
|
|
106941
|
-
const lifecycle2 = resolveLifecycleColumns(await resolveWorkflowIrForTask(store, taskId, cache));
|
|
107172
|
+
const lifecycle2 = resolveLifecycleColumns(await resolveWorkflowIrForTask(store, taskId, cache, selectionCache));
|
|
106942
107173
|
return lifecycle2?.hold ?? "todo";
|
|
106943
107174
|
} catch {
|
|
106944
107175
|
return "todo";
|
|
106945
107176
|
}
|
|
106946
107177
|
}
|
|
106947
|
-
async function resolveReviewColumnsForTask(store, taskId, cache) {
|
|
107178
|
+
async function resolveReviewColumnsForTask(store, taskId, cache, selectionCache) {
|
|
106948
107179
|
const columns = /* @__PURE__ */ new Set(["in-review"]);
|
|
106949
107180
|
try {
|
|
106950
|
-
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache);
|
|
107181
|
+
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache, selectionCache);
|
|
106951
107182
|
if (ir2) for (const id of resolveReviewColumns(ir2)) columns.add(id);
|
|
106952
107183
|
} catch {
|
|
106953
107184
|
}
|
|
@@ -107068,6 +107299,12 @@ async function listTasksImpl(store, options) {
|
|
|
107068
107299
|
const settings = await store.getSettingsFast();
|
|
107069
107300
|
const mergeQueuedTaskIds = await store.getMergeQueuedTaskIdsAsync();
|
|
107070
107301
|
const listPassIrCache = /* @__PURE__ */ new Map();
|
|
107302
|
+
const listPassSelectionCache = options?.selectionCache ?? /* @__PURE__ */ new Map();
|
|
107303
|
+
const listSelectionReads = await prefetchWorkflowSelections(store, filteredRows.map((row) => String(row.id)), listPassSelectionCache);
|
|
107304
|
+
if (options?.selectionReadTally) {
|
|
107305
|
+
options.selectionReadTally.batched += listSelectionReads.batched;
|
|
107306
|
+
options.selectionReadTally.singles += listSelectionReads.singles;
|
|
107307
|
+
}
|
|
107071
107308
|
const staleThresholds = {
|
|
107072
107309
|
inProgressWarningMs: settings.staleInProgressWarningMs,
|
|
107073
107310
|
inProgressCriticalMs: settings.staleInProgressCriticalMs,
|
|
@@ -107078,7 +107315,7 @@ async function listTasksImpl(store, options) {
|
|
|
107078
107315
|
const row = store.pgRowToTaskRow(pgRow);
|
|
107079
107316
|
const task = store.rowToTask(row);
|
|
107080
107317
|
const isMergeQueued = mergeQueuedTaskIds.has(task.id);
|
|
107081
|
-
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, listPassIrCache);
|
|
107318
|
+
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, listPassIrCache, listPassSelectionCache);
|
|
107082
107319
|
const hasFreshAgentLogActivity = hasFreshAgentLogActivitySinceTaskUpdate(store, task, now, reviewColumnsForRow);
|
|
107083
107320
|
const executingTaskIds = hasFreshAgentLogActivity ? /* @__PURE__ */ new Set([task.id]) : void 0;
|
|
107084
107321
|
task.inReviewStall = isMergeQueued ? void 0 : getInReviewStallReason(task, {
|
|
@@ -107110,7 +107347,7 @@ async function listTasksImpl(store, options) {
|
|
|
107110
107347
|
thresholdMs: settings.stalePausedTodoThresholdMs,
|
|
107111
107348
|
// Paused-only (the signal is a no-op otherwise), sharing the list-pass
|
|
107112
107349
|
// IR cache so one workflow is read once per pass, not once per card.
|
|
107113
|
-
holdColumn: task.paused === true ? await resolveHoldColumnForTask(store, task.id, listPassIrCache) : void 0,
|
|
107350
|
+
holdColumn: task.paused === true ? await resolveHoldColumnForTask(store, task.id, listPassIrCache, listPassSelectionCache) : void 0,
|
|
107114
107351
|
engineActiveSinceMs: settings.engineActiveSinceMs,
|
|
107115
107352
|
engineActivationGraceMs: settings.engineActivationGraceMs
|
|
107116
107353
|
});
|
|
@@ -107120,7 +107357,7 @@ async function listTasksImpl(store, options) {
|
|
|
107120
107357
|
thresholds: staleThresholds,
|
|
107121
107358
|
engineActiveSinceMs: settings.engineActiveSinceMs,
|
|
107122
107359
|
engineActivationGraceMs: settings.engineActivationGraceMs,
|
|
107123
|
-
lifecycle: await resolveTaskLifecycleColumns(store, task.id, listPassIrCache)
|
|
107360
|
+
lifecycle: await resolveTaskLifecycleColumns(store, task.id, listPassIrCache, listPassSelectionCache)
|
|
107124
107361
|
});
|
|
107125
107362
|
} catch (err) {
|
|
107126
107363
|
if (!(err instanceof RangeError)) throw err;
|
|
@@ -107199,12 +107436,14 @@ async function listTasksModifiedSinceImpl(store, since, limit, opts) {
|
|
|
107199
107436
|
const lifecycleByTaskId = /* @__PURE__ */ new Map();
|
|
107200
107437
|
{
|
|
107201
107438
|
const irCache = /* @__PURE__ */ new Map();
|
|
107439
|
+
const selectionCache = /* @__PURE__ */ new Map();
|
|
107440
|
+
await prefetchWorkflowSelections(store, pageRows.map((row) => row.id), selectionCache);
|
|
107202
107441
|
for (const pgRow of pageRows) {
|
|
107203
107442
|
const row = store.pgRowToTaskRow(pgRow);
|
|
107204
|
-
reviewColumnsByTaskId.set(row.id, await resolveReviewColumnsForTask(store, row.id, irCache));
|
|
107205
|
-
lifecycleByTaskId.set(row.id, await resolveTaskLifecycleColumns(store, row.id, irCache));
|
|
107443
|
+
reviewColumnsByTaskId.set(row.id, await resolveReviewColumnsForTask(store, row.id, irCache, selectionCache));
|
|
107444
|
+
lifecycleByTaskId.set(row.id, await resolveTaskLifecycleColumns(store, row.id, irCache, selectionCache));
|
|
107206
107445
|
if (store.rowToTask(row).paused !== true) continue;
|
|
107207
|
-
holdColumnByTaskId.set(row.id, await resolveHoldColumnForTask(store, row.id, irCache));
|
|
107446
|
+
holdColumnByTaskId.set(row.id, await resolveHoldColumnForTask(store, row.id, irCache, selectionCache));
|
|
107208
107447
|
}
|
|
107209
107448
|
}
|
|
107210
107449
|
const tasks2 = pageRows.map((pgRow) => {
|
|
@@ -107322,10 +107561,12 @@ async function searchTasksImpl(store, query, options) {
|
|
|
107322
107561
|
const settings = await store.getSettingsFast();
|
|
107323
107562
|
const mergeQueuedTaskIds = await store.getMergeQueuedTaskIdsAsync();
|
|
107324
107563
|
const searchPassIrCache = /* @__PURE__ */ new Map();
|
|
107564
|
+
const searchPassSelectionCache = /* @__PURE__ */ new Map();
|
|
107565
|
+
await prefetchWorkflowSelections(store, pgRows.map((row) => String(row.id)), searchPassSelectionCache);
|
|
107325
107566
|
const tasks2 = await Promise.all(pgRows.map(async (pgRow) => {
|
|
107326
107567
|
const task = store.rowToTask(store.pgRowToTaskRow(pgRow));
|
|
107327
107568
|
const isMergeQueued = mergeQueuedTaskIds.has(task.id);
|
|
107328
|
-
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, searchPassIrCache);
|
|
107569
|
+
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, searchPassIrCache, searchPassSelectionCache);
|
|
107329
107570
|
const hasFreshAgentLogActivity = hasFreshAgentLogActivitySinceTaskUpdate(store, task, now, reviewColumnsForRow);
|
|
107330
107571
|
const executingTaskIds = hasFreshAgentLogActivity ? /* @__PURE__ */ new Set([task.id]) : void 0;
|
|
107331
107572
|
task.inReviewStall = isMergeQueued ? void 0 : getInReviewStallReason(task, {
|
|
@@ -108565,6 +108806,7 @@ var init_store = __esm({
|
|
|
108565
108806
|
init_async_self_healing();
|
|
108566
108807
|
init_task_merge();
|
|
108567
108808
|
init_required_pre_merge_steps();
|
|
108809
|
+
init_pre_merge_approval();
|
|
108568
108810
|
init_logger();
|
|
108569
108811
|
init_project_root_guard();
|
|
108570
108812
|
init_agent_store();
|
|
@@ -110330,29 +110572,35 @@ var init_store = __esm({
|
|
|
110330
110572
|
const results = task.workflowStepResults ?? [];
|
|
110331
110573
|
const failedTarget = getLatestFailedPreMergeReviewStep(task);
|
|
110332
110574
|
const reviewIrForBypass = failedTarget ? void 0 : await resolveWorkflowIrForTask(this, task.id);
|
|
110333
|
-
const
|
|
110334
|
-
|
|
110575
|
+
const requiredForBypass = reviewIrForBypass ? [...resolveRequiredPreMergeStepIds(reviewIrForBypass, task.enabledWorkflowSteps, task)] : [];
|
|
110576
|
+
const absentStepId = requiredForBypass.find((workflowStepId) => !results.some((result2) => result2.workflowStepId === workflowStepId));
|
|
110577
|
+
const unapprovedTarget = !failedTarget && !absentStepId && requiredForBypass.length > 0 ? (() => {
|
|
110578
|
+
const blocked = evaluatePreMergeApprovals(task, { requiredPreMergeStepIds: new Set(requiredForBypass) }).find((approval) => approval.state !== "approved");
|
|
110579
|
+
return blocked ? results.find((result2) => result2.workflowStepId === blocked.workflowStepId) : void 0;
|
|
110580
|
+
})() : void 0;
|
|
110581
|
+
if (!failedTarget && !absentStepId && !unapprovedTarget) {
|
|
110335
110582
|
throw new Error(`Cannot bypass review lane for ${id}: no failed pre-merge review step found`);
|
|
110336
110583
|
}
|
|
110337
|
-
const target = failedTarget ?? {
|
|
110584
|
+
const target = failedTarget ?? unapprovedTarget ?? {
|
|
110338
110585
|
workflowStepId: absentStepId,
|
|
110339
110586
|
workflowStepName: absentStepId,
|
|
110340
110587
|
phase: "pre-merge",
|
|
110341
110588
|
status: "absent"
|
|
110342
110589
|
};
|
|
110343
|
-
const
|
|
110344
|
-
|
|
110590
|
+
const rowTarget = failedTarget ?? unapprovedTarget;
|
|
110591
|
+
const targetIndex = rowTarget ? results.indexOf(rowTarget) : -1;
|
|
110592
|
+
if (rowTarget && targetIndex === -1) {
|
|
110345
110593
|
throw new Error(`Cannot bypass review lane for ${id}: failed step result not found`);
|
|
110346
110594
|
}
|
|
110347
110595
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
110348
|
-
const bypassed =
|
|
110349
|
-
...
|
|
110596
|
+
const bypassed = rowTarget ? {
|
|
110597
|
+
...rowTarget,
|
|
110350
110598
|
status: "skipped",
|
|
110351
110599
|
bypassedBy: actor,
|
|
110352
110600
|
bypassedAt: now,
|
|
110353
110601
|
bypassReason: reason,
|
|
110354
|
-
bypassedFromStatus:
|
|
110355
|
-
bypassedFromVerdict:
|
|
110602
|
+
bypassedFromStatus: rowTarget.remediationArchivedFromStatus ?? rowTarget.status,
|
|
110603
|
+
bypassedFromVerdict: rowTarget.verdict ?? rowTarget.priorAttempts?.[0]?.verdict
|
|
110356
110604
|
} : {
|
|
110357
110605
|
workflowStepId: absentStepId,
|
|
110358
110606
|
workflowStepName: absentStepId,
|
|
@@ -110386,8 +110634,8 @@ var init_store = __esm({
|
|
|
110386
110634
|
metadata: {
|
|
110387
110635
|
workflowStepId: target.workflowStepId,
|
|
110388
110636
|
workflowStepName: target.workflowStepName,
|
|
110389
|
-
bypassedFromStatus:
|
|
110390
|
-
bypassedFromVerdict:
|
|
110637
|
+
bypassedFromStatus: bypassed.bypassedFromStatus,
|
|
110638
|
+
bypassedFromVerdict: bypassed.bypassedFromVerdict ?? null,
|
|
110391
110639
|
reason
|
|
110392
110640
|
}
|
|
110393
110641
|
});
|
|
@@ -110449,9 +110697,8 @@ var init_store = __esm({
|
|
|
110449
110697
|
);
|
|
110450
110698
|
}
|
|
110451
110699
|
if (target.status !== "pending") {
|
|
110452
|
-
|
|
110453
|
-
|
|
110454
|
-
);
|
|
110700
|
+
const archivedFailure = target.remediationArchivedAt != null && (target.remediationArchivedFromStatus === "failed" || target.remediationArchivedFromStatus === "advisory_failure") && !target.bypassedBy && !target.supersededAt;
|
|
110701
|
+
throw new Error(`Cannot resume workflow step for ${id}: only pending steps can be resumed${archivedFailure ? ` (step '${stepId}' is an archived remediation carrier \u2014 use fn_task_bypass_review / POST /tasks/:id/bypass-review to clear it)` : ""}`);
|
|
110455
110702
|
}
|
|
110456
110703
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
110457
110704
|
const resumed = {
|
|
@@ -140959,6 +141206,7 @@ __export(src_exports, {
|
|
|
140959
141206
|
CLOUD_TICKET_QUERY: () => CLOUD_TICKET_QUERY,
|
|
140960
141207
|
CODE_REVIEW_BLOCKING_SEVERITY_SETTING_ID: () => CODE_REVIEW_BLOCKING_SEVERITY_SETTING_ID,
|
|
140961
141208
|
CODE_REVIEW_MAX_REVISIONS_SETTING_ID: () => CODE_REVIEW_MAX_REVISIONS_SETTING_ID,
|
|
141209
|
+
COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC: () => COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC,
|
|
140962
141210
|
COLOR_THEMES: () => COLOR_THEMES,
|
|
140963
141211
|
COLUMNS: () => COLUMNS,
|
|
140964
141212
|
COLUMN_DESCRIPTIONS: () => COLUMN_DESCRIPTIONS,
|
|
@@ -141361,6 +141609,7 @@ __export(src_exports, {
|
|
|
141361
141609
|
SLICE_PLAN_STATES: () => SLICE_PLAN_STATES,
|
|
141362
141610
|
SLICE_STATUSES: () => SLICE_STATUSES,
|
|
141363
141611
|
SPEC_LOCK_PARSER_VERSION: () => SPEC_LOCK_PARSER_VERSION,
|
|
141612
|
+
STALE_CONTENT_APPROVAL_BLOCKER: () => STALE_CONTENT_APPROVAL_BLOCKER,
|
|
141364
141613
|
STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS: () => STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS,
|
|
141365
141614
|
STALLED_REVIEW_INVALID_TRANSITION_PATTERN: () => STALLED_REVIEW_INVALID_TRANSITION_PATTERN,
|
|
141366
141615
|
STALLED_REVIEW_INVALID_TRANSITION_THRESHOLD: () => STALLED_REVIEW_INVALID_TRANSITION_THRESHOLD,
|
|
@@ -142090,6 +142339,7 @@ __export(src_exports, {
|
|
|
142090
142339
|
isSqliteLockError: () => isSqliteLockError,
|
|
142091
142340
|
isSqliteMigrationComplete: () => isSqliteMigrationComplete,
|
|
142092
142341
|
isStaleBlockedByBlocker: () => isStaleBlockedByBlocker,
|
|
142342
|
+
isStaleContentApprovalBlocker: () => isStaleContentApprovalBlocker,
|
|
142093
142343
|
isStoredAuthCredential: () => isStoredAuthCredential,
|
|
142094
142344
|
isStrictDescendantPath: () => isStrictDescendantPath,
|
|
142095
142345
|
isTailscaleCgnatIpv4: () => isTailscaleCgnatIpv4,
|
|
@@ -142169,6 +142419,7 @@ __export(src_exports, {
|
|
|
142169
142419
|
mapRoleToCapability: () => mapRoleToCapability,
|
|
142170
142420
|
markDraftSummarized: () => markDraftSummarized,
|
|
142171
142421
|
markdownHtmlCommentUnits: () => markdownHtmlCommentUnits,
|
|
142422
|
+
matchStepHeadings: () => matchStepHeadings,
|
|
142172
142423
|
matchesFrontendUxPath: () => matchesFrontendUxPath,
|
|
142173
142424
|
matchesPatchnodeQuery: () => matchesPatchnodeQuery,
|
|
142174
142425
|
materializeMcpServerSecrets: () => materializeMcpServerSecrets,
|
|
@@ -142282,6 +142533,7 @@ __export(src_exports, {
|
|
|
142282
142533
|
postMergeOptionalGroupNode: () => postMergeOptionalGroupNode,
|
|
142283
142534
|
postMergeVerificationOptionalGroupNode: () => postMergeVerificationOptionalGroupNode,
|
|
142284
142535
|
postgresSchema: () => schema_exports,
|
|
142536
|
+
prefetchWorkflowSelections: () => prefetchWorkflowSelections,
|
|
142285
142537
|
prepareAgentCompaniesImport: () => prepareAgentCompaniesImport,
|
|
142286
142538
|
pricingAsOf: () => pricingAsOf,
|
|
142287
142539
|
probeGitCliStatus: () => probeGitCliStatus,
|
|
@@ -142393,6 +142645,7 @@ __export(src_exports, {
|
|
|
142393
142645
|
resolveBackupSettingsMigrationConflict: () => resolveBackupSettingsMigrationConflict,
|
|
142394
142646
|
resolveCapacityPoolId: () => resolveCapacityPoolId,
|
|
142395
142647
|
resolveClaudeCodeClientVersion: () => resolveClaudeCodeClientVersion,
|
|
142648
|
+
resolveCollateralArchivedReviewGate: () => resolveCollateralArchivedReviewGate,
|
|
142396
142649
|
resolveColumnAdjacency: () => resolveColumnAdjacency,
|
|
142397
142650
|
resolveColumnAgentBinding: () => resolveColumnAgentBinding,
|
|
142398
142651
|
resolveColumnCapacity: () => resolveColumnCapacity,
|
|
@@ -153521,7 +153774,10 @@ async function runHoldReleaseSweep(store, deps) {
|
|
|
153521
153774
|
const settings = await store.getSettings();
|
|
153522
153775
|
if (expired()) return logPreambleTruncation(0);
|
|
153523
153776
|
counters.tasks += 1;
|
|
153524
|
-
const
|
|
153777
|
+
const selectionReadTally = { batched: 0, singles: 0 };
|
|
153778
|
+
const allTasks = await store.listTasks({ includeArchived: false, selectionCache, selectionReadTally });
|
|
153779
|
+
counters.batchSelections += selectionReadTally.batched;
|
|
153780
|
+
counters.selections += selectionReadTally.singles;
|
|
153525
153781
|
if (expired()) return logPreambleTruncation(allTasks.length);
|
|
153526
153782
|
const effectiveWorkflowIdByTask = /* @__PURE__ */ new Map();
|
|
153527
153783
|
const missingIds = [...new Set(allTasks.map((task) => task.id))].filter((id) => !selectionCache.has(id));
|
|
@@ -179249,7 +179505,7 @@ function extractMarkdownSection(document2, headingName) {
|
|
|
179249
179505
|
return document2.slice(start, end).trim();
|
|
179250
179506
|
}
|
|
179251
179507
|
function compactTaskPromptStepsSection(section) {
|
|
179252
|
-
const stepTitles =
|
|
179508
|
+
const stepTitles = matchStepHeadings(section).map(({ index: index2, headingLineEnd }) => section.slice(index2, headingLineEnd).trim());
|
|
179253
179509
|
if (stepTitles.length === 0) {
|
|
179254
179510
|
return section.trim();
|
|
179255
179511
|
}
|
|
@@ -188605,7 +188861,7 @@ var init_scheduler = __esm({
|
|
|
188605
188861
|
for (const slice of hierarchy.milestones.flatMap((milestone) => milestone.slices).filter((slice2) => slice2.status === "active")) {
|
|
188606
188862
|
try {
|
|
188607
188863
|
const superseded = await missionStore.reconcileSupersededGeneratedFixFeatures(slice.id);
|
|
188608
|
-
fixed += superseded.supersededCount;
|
|
188864
|
+
fixed += superseded.supersededCount + (superseded.repairedCount ?? 0);
|
|
188609
188865
|
if (superseded.supersededCount > 0) {
|
|
188610
188866
|
const refreshed = await missionStore.getSlice(slice.id);
|
|
188611
188867
|
if (refreshed?.status === "complete") {
|
|
@@ -188618,8 +188874,9 @@ var init_scheduler = __esm({
|
|
|
188618
188874
|
const supersededFeatureIds = new Set(superseded.featureIds ?? []);
|
|
188619
188875
|
const autoTriage = mission.autopilotEnabled === true || mission.autoAdvance === true;
|
|
188620
188876
|
for (const feature of features) {
|
|
188621
|
-
if (supersededFeatureIds.has(feature.id) || feature.taskId || !autoTriage || feature.status === "blocked") continue;
|
|
188877
|
+
if (supersededFeatureIds.has(feature.id) || feature.taskId || !autoTriage || feature.status === "blocked" || feature.status === "done") continue;
|
|
188622
188878
|
if (feature.status !== "defined" && this.isGeneratedFixFeature(feature)) {
|
|
188879
|
+
schedulerLog.warn(`Parking stranded generated Fix feature ${feature.id} from ${feature.status}: not triageable without a defined state`);
|
|
188623
188880
|
await missionStore.updateFeature(feature.id, { status: "blocked", loopState: "blocked", taskId: void 0 });
|
|
188624
188881
|
fixed++;
|
|
188625
188882
|
continue;
|
|
@@ -189671,12 +189928,12 @@ function createTaskSearchTool(store) {
|
|
|
189671
189928
|
}
|
|
189672
189929
|
};
|
|
189673
189930
|
}
|
|
189674
|
-
function
|
|
189931
|
+
function createHistoryReadTool(store) {
|
|
189675
189932
|
return {
|
|
189676
|
-
name: "
|
|
189933
|
+
name: "fn_history_read",
|
|
189677
189934
|
label: "Read History",
|
|
189678
189935
|
description: "Read the permanent daily history of completed and reverted task deliveries.",
|
|
189679
|
-
parameters:
|
|
189936
|
+
parameters: historyReadParams,
|
|
189680
189937
|
execute: async (_id, params) => {
|
|
189681
189938
|
const limit = Math.min(50, Math.max(1, Math.floor(params.limit ?? 20)));
|
|
189682
189939
|
const result2 = await store.listPatchnodeEntries({
|
|
@@ -193264,7 +193521,7 @@ ${lines.join("\n")}`
|
|
|
193264
193521
|
}
|
|
193265
193522
|
};
|
|
193266
193523
|
}
|
|
193267
|
-
var TASK_CREATE_PRIORITY_VALUES, missionLineageParams, taskCreateParams, taskLogParams, agentLogTypeParams, agentLogDetailModeParams, taskLogsReadParams, chatTaskLogsReadParams, taskListParams, taskShowParams, taskSearchParams,
|
|
193524
|
+
var TASK_CREATE_PRIORITY_VALUES, missionLineageParams, taskCreateParams, taskLogParams, agentLogTypeParams, agentLogDetailModeParams, taskLogsReadParams, chatTaskLogsReadParams, taskListParams, taskShowParams, taskSearchParams, historyReadParams, installWorktreeDependenciesParams, taskDocumentWriteParams, taskDocumentReadParams, taskPromptWriteParams, taskFileScopeAddParams, chatTaskDocumentWriteParams, chatTaskDocumentReadParams, ARTIFACT_TYPE_VALUES, artifactTypeSchema, artifactRegisterParams, artifactListParams, artifactViewParams, chatArtifactRegisterParams, chatArtifactListParams, workflowListParams, workflowGetParams, workflowSelectParams, taskPromoteParams, taskArchiveParams, taskDeleteParams, taskUnarchiveParams, taskRetryParams, taskPauseParams, taskUnpauseParams, taskDuplicateParams, taskMergeParams, taskAddDepParams, STEP_STATUSES, taskUpdateParams, workflowCreateParams, workflowValidateParams, workflowUpdateParams, workflowDeleteParams, workflowSettingsParams, traitListParams, reflectOnPerformanceParams, readEvaluationsParams, updateIdentityParams, goalListParams, goalShowParams, listAgentsParams, delegateTaskParams, taskAssignParams, getAgentConfigParams, updateAgentConfigParams, createAgentParams, deleteAgentParams, sendMessageParams, readMessagesParams, postRoomMessageParams, askQuestionParams, memorySearchParams, memoryGetParams, webFetchParams, researchRunParams, researchListParams, researchGetParams, researchCancelParams, researchRetryParams, memoryAppendParams, log25, MAX_INSTRUCTIONS_TEXT_LENGTH, MAX_MEMORY_LENGTH, MAX_SOUL_LENGTH, AGENT_MEMORY_ROOT3, AGENT_MEMORY_FILENAME2, AGENT_DREAMS_FILENAME2, agentQmdRefreshState, AGENT_QMD_REFRESH_INTERVAL_MS, DAILY_AGENT_MEMORY_RE2, SEMANTIC_TOOL_READ_MAX_CHARS, AGENT_LOG_READ_DETAIL_PREVIEW_MAX, ARTIFACT_FILE_MAX_BYTES, ARTIFACT_EXTENSION_MIME_TYPES, GOAL_LIST_HARD_LIMIT, GOAL_LIST_SOFT_WARNING_THRESHOLD, GOAL_SNIPPET_MAX_CHARS, missionListParams, missionShowParams, missionCreateParams, missionUpdateParams, missionDeleteParams, missionSetStatusParams, missionReconcileParams, milestoneAddParams, milestoneUpdateParams, milestoneDeleteParams, sliceAddParams, sliceActivateParams, sliceDeleteParams, featureAddParams, featureUpdateParams, featureDeleteParams, featureSetStatusParams, featureLinkTaskParams, featureRepointTaskParams, featureUnlinkTaskParams, featureRepairValidationParams, researchFindingPromoteParams, missionToolResult, optionalText, updateFields, ideationStartParams, ideationDivergeParams, ideationShowParams, ideationConvergeParams, ideationToolResult, formatIdeationCandidate;
|
|
193268
193525
|
var init_agent_tools = __esm({
|
|
193269
193526
|
"../engine/src/agent-tools.ts"() {
|
|
193270
193527
|
"use strict";
|
|
@@ -193355,7 +193612,7 @@ var init_agent_tools = __esm({
|
|
|
193355
193612
|
includeArchived: Type2.Optional(Type2.Boolean({ description: "Include archived tasks (default true)" })),
|
|
193356
193613
|
limit: Type2.Optional(Type2.Number({ minimum: 1, maximum: 50, description: "Max results (default 20, max 50)" }))
|
|
193357
193614
|
});
|
|
193358
|
-
|
|
193615
|
+
historyReadParams = Type2.Object({
|
|
193359
193616
|
query: Type2.Optional(Type2.String({ description: "Search task IDs, titles, and completion summaries" })),
|
|
193360
193617
|
from: Type2.Optional(Type2.String({ description: "First UTC day, YYYY-MM-DD (inclusive)" })),
|
|
193361
193618
|
to: Type2.Optional(Type2.String({ description: "Last UTC day, YYYY-MM-DD (inclusive)" })),
|
|
@@ -196565,6 +196822,55 @@ var init_fallback_model_observer = __esm({
|
|
|
196565
196822
|
}
|
|
196566
196823
|
});
|
|
196567
196824
|
|
|
196825
|
+
// ../engine/src/execution/resolve-activity-run-agent-id.ts
|
|
196826
|
+
async function resolveWorkflowStepRunAgentId(store, candidate, options = {}) {
|
|
196827
|
+
if (!candidate?.trim()) return null;
|
|
196828
|
+
const resolve66 = async () => {
|
|
196829
|
+
if (typeof store?.getAgent === "function") {
|
|
196830
|
+
const agent = await store.getAgent(candidate);
|
|
196831
|
+
if (agent) return agent.id;
|
|
196832
|
+
}
|
|
196833
|
+
if (typeof store?.listAgents !== "function") return null;
|
|
196834
|
+
const matches = (await store.listAgents({ includeEphemeral: true })).filter(
|
|
196835
|
+
(agent) => agent.role === candidate || agent.roles?.includes(candidate)
|
|
196836
|
+
);
|
|
196837
|
+
const builtInOwners = matches.filter(
|
|
196838
|
+
(agent) => isBuiltinWorkflowRoleAgent(agent) && agent.metadata?.workflowRole === candidate
|
|
196839
|
+
);
|
|
196840
|
+
if (builtInOwners.length === 1) return builtInOwners[0].id;
|
|
196841
|
+
return matches.length === 1 ? matches[0].id : null;
|
|
196842
|
+
};
|
|
196843
|
+
let lookup2;
|
|
196844
|
+
try {
|
|
196845
|
+
lookup2 = Promise.resolve(resolve66());
|
|
196846
|
+
} catch {
|
|
196847
|
+
return null;
|
|
196848
|
+
}
|
|
196849
|
+
void lookup2.catch(() => void 0);
|
|
196850
|
+
return await new Promise((complete) => {
|
|
196851
|
+
const timer2 = setTimeout(() => complete(null), options.timeoutMs ?? WORKFLOW_STEP_RUN_ATTRIBUTION_TIMEOUT_MS);
|
|
196852
|
+
timer2.unref?.();
|
|
196853
|
+
void lookup2.then(
|
|
196854
|
+
(agentId) => {
|
|
196855
|
+
clearTimeout(timer2);
|
|
196856
|
+
complete(agentId);
|
|
196857
|
+
},
|
|
196858
|
+
() => {
|
|
196859
|
+
clearTimeout(timer2);
|
|
196860
|
+
complete(null);
|
|
196861
|
+
}
|
|
196862
|
+
);
|
|
196863
|
+
});
|
|
196864
|
+
}
|
|
196865
|
+
var WORKFLOW_STEP_RUN_ATTRIBUTION_TIMEOUT_MS;
|
|
196866
|
+
var init_resolve_activity_run_agent_id = __esm({
|
|
196867
|
+
"../engine/src/execution/resolve-activity-run-agent-id.ts"() {
|
|
196868
|
+
"use strict";
|
|
196869
|
+
init_src2();
|
|
196870
|
+
WORKFLOW_STEP_RUN_ATTRIBUTION_TIMEOUT_MS = 2e3;
|
|
196871
|
+
}
|
|
196872
|
+
});
|
|
196873
|
+
|
|
196568
196874
|
// ../engine/src/execution/step-session-executor.ts
|
|
196569
196875
|
import { exec as exec20 } from "node:child_process";
|
|
196570
196876
|
import { promisify as promisify27 } from "node:util";
|
|
@@ -196573,12 +196879,7 @@ import { rm as rm8 } from "node:fs/promises";
|
|
|
196573
196879
|
function parseStepFileScopes(prompt) {
|
|
196574
196880
|
const result2 = /* @__PURE__ */ new Map();
|
|
196575
196881
|
if (!prompt) return result2;
|
|
196576
|
-
const
|
|
196577
|
-
const splits = [];
|
|
196578
|
-
let match;
|
|
196579
|
-
while ((match = stepRegex.exec(prompt)) !== null) {
|
|
196580
|
-
splits.push({ index: match.index, stepNum: parseInt(match[1], 10) });
|
|
196581
|
-
}
|
|
196882
|
+
const splits = matchStepHeadings(prompt).map(({ index: index2, headingNumber }) => ({ index: index2, stepNum: headingNumber }));
|
|
196582
196883
|
if (splits.length === 0) return result2;
|
|
196583
196884
|
for (let i = 0; i < splits.length; i++) {
|
|
196584
196885
|
const start = splits[i].index;
|
|
@@ -196854,12 +197155,7 @@ function scopePromptToWorktree(prompt, rootDir, worktreePath) {
|
|
|
196854
197155
|
return prompt.replaceAll(`${rootDir}/`, `${worktreePath}/`).replaceAll(`${worktreePath}/.fusion/`, `${rootDir}/.fusion/`);
|
|
196855
197156
|
}
|
|
196856
197157
|
function extractStepSection(prompt, stepIndex) {
|
|
196857
|
-
const
|
|
196858
|
-
const splits = [];
|
|
196859
|
-
let match;
|
|
196860
|
-
while ((match = stepRegex.exec(prompt)) !== null) {
|
|
196861
|
-
splits.push({ index: match.index, stepNum: parseInt(match[1], 10) });
|
|
196862
|
-
}
|
|
197158
|
+
const splits = matchStepHeadings(prompt).map(({ index: index2, headingNumber }) => ({ index: index2, stepNum: headingNumber }));
|
|
196863
197159
|
const offset = resolveAuthoredStepHeadingOffset(splits.map((split) => split.stepNum));
|
|
196864
197160
|
const targetSplit = splits.find((s3) => s3.stepNum === stepIndex + offset);
|
|
196865
197161
|
if (!targetSplit) return "";
|
|
@@ -196869,9 +197165,7 @@ function extractStepSection(prompt, stepIndex) {
|
|
|
196869
197165
|
return prompt.slice(start, end).trim();
|
|
196870
197166
|
}
|
|
196871
197167
|
function countSteps(prompt) {
|
|
196872
|
-
|
|
196873
|
-
const matches = prompt.match(stepRegex);
|
|
196874
|
-
return matches ? matches.length : 0;
|
|
197168
|
+
return matchStepHeadings(prompt).length;
|
|
196875
197169
|
}
|
|
196876
197170
|
function resolveStepSection(taskDetail, prompt, stepIndex, authoredStepCount) {
|
|
196877
197171
|
const authoredSection = extractStepSection(prompt, stepIndex);
|
|
@@ -196966,6 +197260,7 @@ var init_step_session_executor = __esm({
|
|
|
196966
197260
|
init_usage_limit_detector();
|
|
196967
197261
|
init_agent_tools();
|
|
196968
197262
|
init_worktree_backend();
|
|
197263
|
+
init_resolve_activity_run_agent_id();
|
|
196969
197264
|
init_worktree_prune();
|
|
196970
197265
|
init_active_session_registry();
|
|
196971
197266
|
execAsync18 = promisify27(exec20);
|
|
@@ -196998,6 +197293,8 @@ var init_step_session_executor = __esm({
|
|
|
196998
197293
|
credentialInstanceId;
|
|
196999
197294
|
reusablePrimaryRetargetPending = false;
|
|
197000
197295
|
reusablePrimaryAttemptActive = false;
|
|
197296
|
+
workflowStepRunAgentId;
|
|
197297
|
+
warnedUnattributableWorkflowStepRun = false;
|
|
197001
197298
|
registerActiveStepSession(stepIndex, handle2, worktreePath) {
|
|
197002
197299
|
this.activeSessions.set(stepIndex, handle2);
|
|
197003
197300
|
activeSessionRegistry.registerPath(worktreePath, {
|
|
@@ -197342,10 +197639,20 @@ var init_step_session_executor = __esm({
|
|
|
197342
197639
|
modelId: current.modelId
|
|
197343
197640
|
};
|
|
197344
197641
|
}
|
|
197345
|
-
createWorkflowStepActivityRun(stepIndex, startedAt) {
|
|
197642
|
+
async createWorkflowStepActivityRun(stepIndex, startedAt) {
|
|
197346
197643
|
const { taskDetail } = this.options;
|
|
197347
197644
|
const step = taskDetail.steps?.[stepIndex];
|
|
197348
|
-
|
|
197645
|
+
if (typeof this.options.agentStore?.saveRun !== "function") return null;
|
|
197646
|
+
const agentIdCandidate = this.options.effectiveAgentId ?? taskDetail.assignedAgentId ?? "executor";
|
|
197647
|
+
this.workflowStepRunAgentId ??= resolveWorkflowStepRunAgentId(this.options.agentStore, agentIdCandidate);
|
|
197648
|
+
const agentId = await this.workflowStepRunAgentId;
|
|
197649
|
+
if (!agentId) {
|
|
197650
|
+
if (!this.warnedUnattributableWorkflowStepRun) {
|
|
197651
|
+
this.warnedUnattributableWorkflowStepRun = true;
|
|
197652
|
+
stepExecLog.warn(`Skipping unattributable workflow-step activity runs for task ${taskDetail.id} (candidate: ${agentIdCandidate})`);
|
|
197653
|
+
}
|
|
197654
|
+
return null;
|
|
197655
|
+
}
|
|
197349
197656
|
return {
|
|
197350
197657
|
id: `${generateSyntheticRunId("workflow-step", taskDetail.id)}-step-${stepIndex}`,
|
|
197351
197658
|
agentId,
|
|
@@ -197365,6 +197672,7 @@ var init_step_session_executor = __esm({
|
|
|
197365
197672
|
taskTitle: taskDetail.title,
|
|
197366
197673
|
assignedAgentId: taskDetail.assignedAgentId,
|
|
197367
197674
|
effectiveAgentId: this.options.effectiveAgentId,
|
|
197675
|
+
agentIdCandidate,
|
|
197368
197676
|
agentId,
|
|
197369
197677
|
stepIndex,
|
|
197370
197678
|
stepName: step?.name ?? `Step ${stepIndex}`
|
|
@@ -197379,6 +197687,7 @@ var init_step_session_executor = __esm({
|
|
|
197379
197687
|
};
|
|
197380
197688
|
}
|
|
197381
197689
|
async saveWorkflowStepActivityRun(run) {
|
|
197690
|
+
if (!run) return;
|
|
197382
197691
|
const saveRun2 = this.options.agentStore?.saveRun?.bind(this.options.agentStore);
|
|
197383
197692
|
if (!saveRun2) return;
|
|
197384
197693
|
try {
|
|
@@ -197391,6 +197700,7 @@ var init_step_session_executor = __esm({
|
|
|
197391
197700
|
}
|
|
197392
197701
|
}
|
|
197393
197702
|
async completeWorkflowStepActivityRun(run, status, result2) {
|
|
197703
|
+
if (!run) return;
|
|
197394
197704
|
const terminalRun = {
|
|
197395
197705
|
...run,
|
|
197396
197706
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -197450,7 +197760,7 @@ var init_step_session_executor = __esm({
|
|
|
197450
197760
|
if (semaphore) {
|
|
197451
197761
|
await semaphore.acquire();
|
|
197452
197762
|
}
|
|
197453
|
-
const activityRun = this.createWorkflowStepActivityRun(stepIndex, (/* @__PURE__ */ new Date()).toISOString());
|
|
197763
|
+
const activityRun = await this.createWorkflowStepActivityRun(stepIndex, (/* @__PURE__ */ new Date()).toISOString());
|
|
197454
197764
|
await this.saveWorkflowStepActivityRun(activityRun);
|
|
197455
197765
|
const trackingKey = this.makeTrackingKey(stepIndex);
|
|
197456
197766
|
let retries = 0;
|
|
@@ -197999,12 +198309,11 @@ function collectTaskPaths(text3) {
|
|
|
197999
198309
|
}
|
|
198000
198310
|
function collectStepSections(stepsSection) {
|
|
198001
198311
|
const sections2 = [];
|
|
198002
|
-
const headingMatches =
|
|
198312
|
+
const headingMatches = matchStepHeadings(stepsSection);
|
|
198003
198313
|
for (let i = 0; i < headingMatches.length; i += 1) {
|
|
198004
198314
|
const heading = headingMatches[i];
|
|
198005
|
-
const
|
|
198006
|
-
|
|
198007
|
-
sections2.push({ name: `Step ${heading[1]}`, body: stepsSection.slice(start, end).trim() });
|
|
198315
|
+
const end = i + 1 < headingMatches.length ? headingMatches[i + 1].index : stepsSection.length;
|
|
198316
|
+
sections2.push({ name: `Step ${heading.headingNumber}`, body: stepsSection.slice(heading.headingLineEnd, end).trim() });
|
|
198008
198317
|
}
|
|
198009
198318
|
return sections2;
|
|
198010
198319
|
}
|
|
@@ -198096,6 +198405,7 @@ var TASK_PATH_REGEX;
|
|
|
198096
198405
|
var init_task_document_references = __esm({
|
|
198097
198406
|
"../engine/src/spec-validation/task-document-references.ts"() {
|
|
198098
198407
|
"use strict";
|
|
198408
|
+
init_src2();
|
|
198099
198409
|
init_step_session_executor();
|
|
198100
198410
|
TASK_PATH_REGEX = /\.fusion\/tasks\/[A-Z0-9]+(?:-[A-Z0-9]+)*\/[^\s`'"\]]+/g;
|
|
198101
198411
|
}
|
|
@@ -199122,6 +199432,24 @@ var init_configured_command = __esm({
|
|
|
199122
199432
|
}
|
|
199123
199433
|
});
|
|
199124
199434
|
|
|
199435
|
+
// ../engine/src/merge/stale-content-park.ts
|
|
199436
|
+
function classifyStaleContentPark(task) {
|
|
199437
|
+
if (task.paused || task.userPaused || task.deletedAt) return void 0;
|
|
199438
|
+
if (typeof task.error !== "string" || !isStaleContentApprovalBlocker(task.error)) return void 0;
|
|
199439
|
+
if (task.status === "failed") {
|
|
199440
|
+
return task.error.startsWith(AUTO_MERGE_RETRY_REJECTED_PREFIX) ? "retry-rejected" : "raw-blocker";
|
|
199441
|
+
}
|
|
199442
|
+
return task.status == null ? "retry-exhausted" : void 0;
|
|
199443
|
+
}
|
|
199444
|
+
var AUTO_MERGE_RETRY_REJECTED_PREFIX;
|
|
199445
|
+
var init_stale_content_park = __esm({
|
|
199446
|
+
"../engine/src/merge/stale-content-park.ts"() {
|
|
199447
|
+
"use strict";
|
|
199448
|
+
init_src2();
|
|
199449
|
+
AUTO_MERGE_RETRY_REJECTED_PREFIX = "AUTO_MERGE_RETRY_REJECTED:";
|
|
199450
|
+
}
|
|
199451
|
+
});
|
|
199452
|
+
|
|
199125
199453
|
// ../engine/src/execution/lifecycle-move-log.ts
|
|
199126
199454
|
function laneRole(column, lanes) {
|
|
199127
199455
|
if (!lanes) return void 0;
|
|
@@ -222902,6 +223230,17 @@ function isRecoveryRetryDue(task, now) {
|
|
|
222902
223230
|
const retryAt = Date.parse(task.nextRecoveryAt);
|
|
222903
223231
|
return !Number.isFinite(retryAt) || retryAt <= now;
|
|
222904
223232
|
}
|
|
223233
|
+
function formatMergeableReviewRecoverySummary({
|
|
223234
|
+
enqueued,
|
|
223235
|
+
merged,
|
|
223236
|
+
parked
|
|
223237
|
+
}) {
|
|
223238
|
+
const outcomes = [];
|
|
223239
|
+
if (enqueued > 0) outcomes.push(`${enqueued} re-enqueued for merge`);
|
|
223240
|
+
if (merged > 0) outcomes.push(`${merged} merged \u2192 done`);
|
|
223241
|
+
if (parked > 0) outcomes.push(`${parked} parked after enqueue starvation`);
|
|
223242
|
+
return `Mergeable review recovery: ${outcomes.join(", ") || "no tasks recovered"}`;
|
|
223243
|
+
}
|
|
222905
223244
|
async function resolveNoOpFinalizeGateIds2(store, task) {
|
|
222906
223245
|
const selection = store.getTaskWorkflowSelectionAsync ? await store.getTaskWorkflowSelectionAsync(task.id) : store.getTaskWorkflowSelection?.(task.id);
|
|
222907
223246
|
if (!selection) return void 0;
|
|
@@ -222948,6 +223287,7 @@ var init_self_healing = __esm({
|
|
|
222948
223287
|
"use strict";
|
|
222949
223288
|
init_src2();
|
|
222950
223289
|
init_src2();
|
|
223290
|
+
init_stale_content_park();
|
|
222951
223291
|
init_logger3();
|
|
222952
223292
|
init_lifecycle_move_log();
|
|
222953
223293
|
init_lifecycle_move();
|
|
@@ -222968,6 +223308,7 @@ var init_self_healing = __esm({
|
|
|
222968
223308
|
init_post_landing_worktree_cleanup();
|
|
222969
223309
|
init_auto_recovery();
|
|
222970
223310
|
init_active_session_registry();
|
|
223311
|
+
init_planning_liveness();
|
|
222971
223312
|
init_replan_target();
|
|
222972
223313
|
init_planning_handoff_recovery();
|
|
222973
223314
|
init_spec_staleness();
|
|
@@ -223134,6 +223475,9 @@ var init_self_healing = __esm({
|
|
|
223134
223475
|
maintenanceTickCounter = 0;
|
|
223135
223476
|
taskLifecycleRetentionLastPrunedAt = /* @__PURE__ */ new Map();
|
|
223136
223477
|
staleContentRerouteAuditKeys = /* @__PURE__ */ new Set();
|
|
223478
|
+
mergeRecoveryBlockerWarnKeys = /* @__PURE__ */ new Set();
|
|
223479
|
+
staleContentParkRecoveryAttempts = /* @__PURE__ */ new Map();
|
|
223480
|
+
staleContentParkRecoveryBudgetLogged = /* @__PURE__ */ new Set();
|
|
223137
223481
|
unrunPreMergeGateRerouteAuditKeys = /* @__PURE__ */ new Set();
|
|
223138
223482
|
githubCheckStateRetentionLastPrunedAt = /* @__PURE__ */ new Map();
|
|
223139
223483
|
processBootStartedAt = Date.now();
|
|
@@ -223852,6 +224196,13 @@ var init_self_healing = __esm({
|
|
|
223852
224196
|
{ name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults().then(() => void 0) },
|
|
223853
224197
|
{ name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals().then(() => void 0) },
|
|
223854
224198
|
/*
|
|
224199
|
+
FNXC:PreMergeApproval 2026-09-05-22:11:
|
|
224200
|
+
Runs AFTER the orphaned-step sweep, because that sweep is what produces the `failed` row this
|
|
224201
|
+
defect archives, and BEFORE failed-step recovery, so the restored gate is visible to it in the
|
|
224202
|
+
same startup pass instead of one cycle later.
|
|
224203
|
+
*/
|
|
224204
|
+
{ name: "reconcile-collateral-archived-review-gates", fn: () => this.reconcileCollateralArchivedReviewGates().then(() => void 0) },
|
|
224205
|
+
/*
|
|
223855
224206
|
FNXC:WorkspaceContention 2026-08-23-08:00:
|
|
223856
224207
|
`contention-hold` is owned by an in-memory retry timer. After a process restart that
|
|
223857
224208
|
timer is gone, so clear only an unowned wait before ordinary dispatch classification;
|
|
@@ -224732,6 +225083,7 @@ var init_self_healing = __esm({
|
|
|
224732
225083
|
*/
|
|
224733
225084
|
{ name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults() },
|
|
224734
225085
|
{ name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals() },
|
|
225086
|
+
{ name: "reconcile-collateral-archived-review-gates", fn: () => this.reconcileCollateralArchivedReviewGates() },
|
|
224735
225087
|
{ name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
|
|
224736
225088
|
{ name: "recover-missing-worktree-review-failures", fn: () => this.recoverMissingWorktreeReviewFailures() },
|
|
224737
225089
|
{ name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
|
|
@@ -228218,29 +228570,28 @@ var init_self_healing = __esm({
|
|
|
228218
228570
|
/** Repair legacy dependency residue before it can re-enter executor dispatch. */
|
|
228219
228571
|
async reconcileMissingDependencies() {
|
|
228220
228572
|
let repaired = 0;
|
|
228221
|
-
const
|
|
228573
|
+
const archivedColumns = new Set(await Promise.resolve().then(() => resolveProjectColumnsForRoles(this.store, ["archived"])).catch(() => /* @__PURE__ */ new Set()));
|
|
228574
|
+
archivedColumns.add("archived");
|
|
228575
|
+
const tasks2 = await this.store.listTasks({ slim: true, includeArchived: false });
|
|
228222
228576
|
for (const snapshot of tasks2) {
|
|
228577
|
+
if (snapshot.deletedAt || archivedColumns.has(snapshot.column)) continue;
|
|
228223
228578
|
if (!snapshot.dependencies.length || snapshot.userPaused || snapshot.paused || snapshot.autoMerge === false) continue;
|
|
228224
228579
|
const livePaths = activeSessionRegistry.pathsForTask(snapshot.id);
|
|
228225
228580
|
const liveExecution = livePaths.some((path13) => activeSessionRegistry.isPathActive(path13)) || executingTaskLock.has(snapshot.id) || this.options.getExecutingTaskIds?.().has(snapshot.id) === true || this.options.isTaskActive?.(snapshot.id) === true || Boolean(snapshot.checkedOutBy);
|
|
228226
228581
|
if (liveExecution) continue;
|
|
228227
228582
|
let removed = 0;
|
|
228228
228583
|
for (const dependencyId of snapshot.dependencies) {
|
|
228229
|
-
|
|
228230
|
-
|
|
228231
|
-
if (!dependency.deletedAt) continue;
|
|
228232
|
-
} catch (error3) {
|
|
228233
|
-
if (!isTaskNotFoundError2(error3)) throw error3;
|
|
228234
|
-
}
|
|
228235
|
-
const current = await this.store.getTask(snapshot.id).catch((error3) => {
|
|
228236
|
-
if (isTaskNotFoundError2(error3)) return void 0;
|
|
228237
|
-
throw error3;
|
|
228238
|
-
});
|
|
228584
|
+
if (await readLinkedTaskOrUndefined(this.store, dependencyId)) continue;
|
|
228585
|
+
const current = await readLinkedTaskOrUndefined(this.store, snapshot.id);
|
|
228239
228586
|
if (!current || current.userPaused || current.paused || current.autoMerge === false || current.checkedOutBy || activeSessionRegistry.pathsForTask(current.id).some((path13) => activeSessionRegistry.isPathActive(path13)) || executingTaskLock.has(current.id) || this.options.getExecutingTaskIds?.().has(current.id) === true || this.options.isTaskActive?.(current.id) === true || !current.dependencies.includes(dependencyId)) continue;
|
|
228240
228587
|
try {
|
|
228241
228588
|
await this.store.updateTaskDependencies(current.id, { operation: "remove", dependency: dependencyId });
|
|
228242
228589
|
removed += 1;
|
|
228243
228590
|
} catch (error3) {
|
|
228591
|
+
if (isMissingTaskLookupError(error3)) {
|
|
228592
|
+
log34.debug(`reconcileMissingDependencies: skipped unreadable ${current.id} dependency ${dependencyId}`);
|
|
228593
|
+
continue;
|
|
228594
|
+
}
|
|
228244
228595
|
if (!/does not depend on/i.test(error3 instanceof Error ? error3.message : String(error3))) throw error3;
|
|
228245
228596
|
}
|
|
228246
228597
|
}
|
|
@@ -229006,7 +229357,7 @@ var init_self_healing = __esm({
|
|
|
229006
229357
|
const graceMs = 10 * 6e4;
|
|
229007
229358
|
const now = Date.now();
|
|
229008
229359
|
const nowIso3 = new Date(now).toISOString();
|
|
229009
|
-
const live = (taskId) => activeSessionRegistry.pathsForTask(taskId).some((path13) => activeSessionRegistry.isPathActive(path13)) || executingTaskLock.has(taskId) || this.options.isTaskActive?.(taskId) === true;
|
|
229360
|
+
const live = (taskId) => activeSessionRegistry.pathsForTask(taskId).some((path13) => activeSessionRegistry.isPathActive(path13)) || executingTaskLock.has(taskId) || isPlanningLive(taskId) || this.options.isTaskActive?.(taskId) === true;
|
|
229010
229361
|
const due = await this.store.listDueWorkflowWorkItems({
|
|
229011
229362
|
now: nowIso3,
|
|
229012
229363
|
states: [...ACTIVE_WORKFLOW_WORK_ITEM_STATES]
|
|
@@ -229383,6 +229734,109 @@ var init_self_healing = __esm({
|
|
|
229383
229734
|
return 0;
|
|
229384
229735
|
}
|
|
229385
229736
|
}
|
|
229737
|
+
/**
|
|
229738
|
+
* FNXC:PreMergeApproval 2026-09-05-22:11:
|
|
229739
|
+
* FN-295: restore a required pre-merge gate that was archived as COLLATERAL of another gate's
|
|
229740
|
+
* remediation, so the card stops being permanently unmergeable with no owner.
|
|
229741
|
+
*
|
|
229742
|
+
* The wedge needs four ingredients, none of which is individually wrong: a review row left
|
|
229743
|
+
* `pending` by a dead session, the orphaned-step sweep rewriting it to `failed`, the remediation
|
|
229744
|
+
* archiver stamping EVERY terminal failure (not just its own gate), and the merge door treating
|
|
229745
|
+
* `remediationArchivedAt` as an unconditional veto. The result is a row the reseed cannot re-run
|
|
229746
|
+
* (it handles `missing`), the stale-content reroute cannot re-run (it handles `stale-content`),
|
|
229747
|
+
* and the operator bypass cannot select (it selects `status:"failed"`). Measured on FN-295: three
|
|
229748
|
+
* review restarts re-ran Code Review and Documentation to APPROVE and merged nothing.
|
|
229749
|
+
*
|
|
229750
|
+
* This sweep does NOT approve or merge anything: it undoes the collateral archive so the gate is
|
|
229751
|
+
* once again a visible failed step that the ordinary FN-7720 audited bypass — or a genuine re-run
|
|
229752
|
+
* — can clear. It never touches an operator waiver, the gate that owns the remediation wave, a
|
|
229753
|
+
* workspace card, a user-paused card, or a card with a live session, and it re-validates every
|
|
229754
|
+
* condition inside the atomic mutation.
|
|
229755
|
+
*/
|
|
229756
|
+
async reconcileCollateralArchivedReviewGates() {
|
|
229757
|
+
try {
|
|
229758
|
+
const reviewColumns = await resolveProjectColumnsForRoles(this.store, REVIEW_ROLES);
|
|
229759
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
229760
|
+
for (const column of reviewColumns) {
|
|
229761
|
+
for (const task of await this.store.listTasks({ column, slim: true })) candidates.set(task.id, task);
|
|
229762
|
+
}
|
|
229763
|
+
let repairedTasks = 0;
|
|
229764
|
+
const isSessionLive = (taskId) => {
|
|
229765
|
+
const livePaths = activeSessionRegistry.pathsForTask(taskId);
|
|
229766
|
+
return livePaths.some((path13) => activeSessionRegistry.isPathActive(path13)) || executingTaskLock.has(taskId) || this.options.isTaskActive?.(taskId) === true;
|
|
229767
|
+
};
|
|
229768
|
+
const remediationGateIdsFor = (task) => new Set(
|
|
229769
|
+
(task.steps ?? []).map((step) => step.remediation?.gateStepId).filter((gateStepId) => typeof gateStepId === "string" && gateStepId.length > 0)
|
|
229770
|
+
);
|
|
229771
|
+
for (const candidate of candidates.values()) {
|
|
229772
|
+
if (candidate.userPaused === true || candidate.workspaceWorktrees !== void 0 || isSessionLive(candidate.id)) continue;
|
|
229773
|
+
const fresh = await this.store.getTask(candidate.id);
|
|
229774
|
+
if (!fresh || !reviewColumns.has(fresh.column) || fresh.userPaused === true || fresh.workspaceWorktrees !== void 0 || isSessionLive(fresh.id)) continue;
|
|
229775
|
+
if (!(fresh.workflowStepResults ?? []).some((result2) => resolveCollateralArchivedReviewGate(result2, { remediationGateIds: remediationGateIdsFor(fresh) }))) continue;
|
|
229776
|
+
let requiredPreMergeStepIds;
|
|
229777
|
+
try {
|
|
229778
|
+
requiredPreMergeStepIds = (await resolvePreMergeGateForTask(
|
|
229779
|
+
this.store,
|
|
229780
|
+
fresh.id,
|
|
229781
|
+
fresh.enabledWorkflowSteps,
|
|
229782
|
+
fresh
|
|
229783
|
+
)).requiredPreMergeStepIds;
|
|
229784
|
+
} catch {
|
|
229785
|
+
continue;
|
|
229786
|
+
}
|
|
229787
|
+
if (requiredPreMergeStepIds.size === 0) continue;
|
|
229788
|
+
let repair;
|
|
229789
|
+
try {
|
|
229790
|
+
await this.store.updateTaskAtomic(fresh.id, (live) => {
|
|
229791
|
+
repair = void 0;
|
|
229792
|
+
if (!reviewColumns.has(live.column) || live.userPaused === true || live.workspaceWorktrees !== void 0 || isSessionLive(live.id)) return null;
|
|
229793
|
+
const remediationGateIds = remediationGateIdsFor(live);
|
|
229794
|
+
const restoredStepIds = [];
|
|
229795
|
+
const results = (live.workflowStepResults ?? []).map((result2) => {
|
|
229796
|
+
if (!requiredPreMergeStepIds.has(result2.workflowStepId)) return result2;
|
|
229797
|
+
const resolution = resolveCollateralArchivedReviewGate(result2, { remediationGateIds });
|
|
229798
|
+
if (!resolution) return result2;
|
|
229799
|
+
restoredStepIds.push(result2.workflowStepId);
|
|
229800
|
+
return resolution.restored;
|
|
229801
|
+
});
|
|
229802
|
+
if (restoredStepIds.length === 0) return null;
|
|
229803
|
+
repair = { stepIds: restoredStepIds, column: live.column, resultCount: results.length };
|
|
229804
|
+
return { workflowStepResults: results };
|
|
229805
|
+
});
|
|
229806
|
+
if (!repair) continue;
|
|
229807
|
+
repairedTasks += 1;
|
|
229808
|
+
} catch (error3) {
|
|
229809
|
+
log34.warn(`reconcileCollateralArchivedReviewGates: failed for ${fresh.id}: ${error3 instanceof Error ? error3.message : String(error3)}`);
|
|
229810
|
+
continue;
|
|
229811
|
+
}
|
|
229812
|
+
await this.store.logEntry(
|
|
229813
|
+
fresh.id,
|
|
229814
|
+
`[pre-merge] Restored collaterally archived review gate(s): ${repair.stepIds.join(", ")}`,
|
|
229815
|
+
COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC
|
|
229816
|
+
).catch(() => void 0);
|
|
229817
|
+
await emitBoundedRunAudit2(this.store, {
|
|
229818
|
+
taskId: fresh.id,
|
|
229819
|
+
agentId: "self-healing",
|
|
229820
|
+
runId: generateSyntheticRunId("reconcile-collateral-archived-review-gate", fresh.id),
|
|
229821
|
+
domain: "database",
|
|
229822
|
+
mutationType: "task:reconcile-collateral-archived-review-gate",
|
|
229823
|
+
target: fresh.id,
|
|
229824
|
+
metadata: {
|
|
229825
|
+
taskId: fresh.id,
|
|
229826
|
+
column: repair.column,
|
|
229827
|
+
workflowStepId: repair.stepIds[0],
|
|
229828
|
+
restoredCount: repair.stepIds.length,
|
|
229829
|
+
resultCount: repair.resultCount
|
|
229830
|
+
}
|
|
229831
|
+
}, { log: log34 });
|
|
229832
|
+
}
|
|
229833
|
+
if (repairedTasks > 0) log34.log(`Restored collaterally archived review gates on ${repairedTasks} task(s)`);
|
|
229834
|
+
return repairedTasks;
|
|
229835
|
+
} catch (error3) {
|
|
229836
|
+
log34.error(`reconcileCollateralArchivedReviewGates failed: ${error3 instanceof Error ? error3.message : String(error3)}`);
|
|
229837
|
+
return 0;
|
|
229838
|
+
}
|
|
229839
|
+
}
|
|
229386
229840
|
/**
|
|
229387
229841
|
* Backward lifecycle move gated on triple proof (FN-5335).
|
|
229388
229842
|
* When the unproven fallback predicate fails, emits `task:finalize-no-op-review-no-action` and skips lifecycle mutation.
|
|
@@ -229742,25 +230196,14 @@ var init_self_healing = __esm({
|
|
|
229742
230196
|
blocker check lacks a fresh Git descriptor. Capture the same merge content the merge queue uses and
|
|
229743
230197
|
seed the graph back at Code Review, so cards like FN-9234 stop looping through doomed merge retries.
|
|
229744
230198
|
*/
|
|
229745
|
-
async routeStaleSingularApprovalBackToReview(task, reviewColumns,
|
|
229746
|
-
let mergeGate;
|
|
229747
|
-
try {
|
|
229748
|
-
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
229749
|
-
} catch {
|
|
229750
|
-
return false;
|
|
229751
|
-
}
|
|
229752
|
-
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) return false;
|
|
229753
|
-
const mergeContent = await captureMergeContentDescriptor(task, {
|
|
229754
|
-
workspaceRootDir: this.options.rootDir,
|
|
229755
|
-
settings
|
|
229756
|
-
});
|
|
230199
|
+
async routeStaleSingularApprovalBackToReview(task, reviewColumns, mergeGate, mergeContent, parkShape) {
|
|
229757
230200
|
const blocker = getTaskMergeBlocker(task, {
|
|
229758
230201
|
reviewColumns,
|
|
229759
230202
|
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
229760
230203
|
mergeContent
|
|
229761
230204
|
});
|
|
229762
|
-
if (blocker
|
|
229763
|
-
return false;
|
|
230205
|
+
if (!isStaleContentApprovalBlocker(blocker) || mergeContent.kind !== "singular") {
|
|
230206
|
+
return { handled: false, seeded: false, parkCleared: false, mergeRetriesReset: false };
|
|
229764
230207
|
}
|
|
229765
230208
|
const reroute = await rerouteSingularStaleContentToReview(this.store, task, {
|
|
229766
230209
|
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
@@ -229771,11 +230214,25 @@ var init_self_healing = __esm({
|
|
|
229771
230214
|
nodeId: void 0,
|
|
229772
230215
|
workflowStepId: void 0
|
|
229773
230216
|
}));
|
|
230217
|
+
let parkCleared = false;
|
|
230218
|
+
if (reroute.rerouted && parkShape) {
|
|
230219
|
+
await this.store.updateTaskAtomic(task.id, (live) => {
|
|
230220
|
+
if (classifyStaleContentPark(live) !== parkShape || live.paused || live.userPaused || live.deletedAt) {
|
|
230221
|
+
return null;
|
|
230222
|
+
}
|
|
230223
|
+
parkCleared = true;
|
|
230224
|
+
return { status: null, error: null, mergeRetries: 0 };
|
|
230225
|
+
});
|
|
230226
|
+
}
|
|
230227
|
+
const mergeRetriesReset = parkCleared;
|
|
229774
230228
|
if (reroute.rerouted) {
|
|
229775
|
-
await this.store.logEntry(task.id,
|
|
230229
|
+
await this.store.logEntry(task.id, `[pre-merge] Review lane '${reroute.nodeId ?? "code-review"}' approved older content; self-healing re-seeded it against current content.`);
|
|
230230
|
+
if (parkCleared) {
|
|
230231
|
+
await this.store.logEntry(task.id, `[pre-merge] Cleared the stale-content merge park after re-seeding review lane '${reroute.nodeId ?? "code-review"}' against current content.`);
|
|
230232
|
+
}
|
|
229776
230233
|
log34.warn(`Stale content approval for ${task.id} re-seeded at ${reroute.nodeId ?? "code-review"}`);
|
|
229777
230234
|
}
|
|
229778
|
-
const auditKey = `${task.id}:${reroute.reason}:${reroute.nodeId ?? ""}`;
|
|
230235
|
+
const auditKey = `${task.id}:${reroute.reason}:${reroute.nodeId ?? ""}:${parkShape ?? ""}`;
|
|
229779
230236
|
if (!this.staleContentRerouteAuditKeys.has(auditKey)) {
|
|
229780
230237
|
this.staleContentRerouteAuditKeys.add(auditKey);
|
|
229781
230238
|
await emitBoundedRunAudit2(this.store, {
|
|
@@ -229790,21 +230247,23 @@ var init_self_healing = __esm({
|
|
|
229790
230247
|
nodeId: reroute.nodeId,
|
|
229791
230248
|
workflowStepId: reroute.workflowStepId,
|
|
229792
230249
|
reason: reroute.reason,
|
|
229793
|
-
source: "self-healing"
|
|
230250
|
+
source: "self-healing",
|
|
230251
|
+
parkShape,
|
|
230252
|
+
parkCleared,
|
|
230253
|
+
mergeRetriesReset
|
|
229794
230254
|
}
|
|
229795
230255
|
});
|
|
229796
230256
|
}
|
|
229797
|
-
return
|
|
230257
|
+
return {
|
|
230258
|
+
handled: true,
|
|
230259
|
+
seeded: reroute.rerouted,
|
|
230260
|
+
nodeId: reroute.nodeId,
|
|
230261
|
+
parkShape,
|
|
230262
|
+
parkCleared,
|
|
230263
|
+
mergeRetriesReset
|
|
230264
|
+
};
|
|
229798
230265
|
}
|
|
229799
|
-
async routeUnrunPreMergeGateBackToReview(task, reviewColumns,
|
|
229800
|
-
let mergeGate;
|
|
229801
|
-
try {
|
|
229802
|
-
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
229803
|
-
} catch {
|
|
229804
|
-
return false;
|
|
229805
|
-
}
|
|
229806
|
-
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) return false;
|
|
229807
|
-
const mergeContent = await captureMergeContentDescriptor(task, { workspaceRootDir: this.options.rootDir, settings });
|
|
230266
|
+
async routeUnrunPreMergeGateBackToReview(task, reviewColumns, mergeGate, mergeContent) {
|
|
229808
230267
|
if (getTaskMergeBlocker(task, { reviewColumns, requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds, mergeContent }) !== "task has enabled pre-merge workflow steps that never ran") return false;
|
|
229809
230268
|
const reroute = await rerouteUnrunPreMergeGateToReview(this.store, task, { requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds, mergeContent }).catch(() => ({ rerouted: false, reason: "no-unrun-gate", nodeId: void 0, workflowStepId: void 0 }));
|
|
229810
230269
|
if (reroute.rerouted) {
|
|
@@ -229879,11 +230338,62 @@ var init_self_healing = __esm({
|
|
|
229879
230338
|
for (const task of mergeable) {
|
|
229880
230339
|
const reviewColumns = await ownReviewLanesFor(task);
|
|
229881
230340
|
if (!reviewColumns.has(task.column)) continue;
|
|
229882
|
-
|
|
229883
|
-
|
|
229884
|
-
|
|
230341
|
+
let mergeGate;
|
|
230342
|
+
try {
|
|
230343
|
+
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
230344
|
+
} catch {
|
|
230345
|
+
continue;
|
|
230346
|
+
}
|
|
230347
|
+
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) continue;
|
|
230348
|
+
const mergeContent = await captureMergeContentDescriptor(task, { workspaceRootDir: this.options.rootDir, settings });
|
|
230349
|
+
if ((await this.routeStaleSingularApprovalBackToReview(task, reviewColumns, mergeGate, mergeContent)).handled) continue;
|
|
230350
|
+
if (await this.routeUnrunPreMergeGateBackToReview(task, reviewColumns, mergeGate, mergeContent)) continue;
|
|
230351
|
+
const blocker = getTaskMergeBlocker(task, {
|
|
230352
|
+
reviewColumns,
|
|
230353
|
+
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
230354
|
+
mergeContent
|
|
230355
|
+
});
|
|
230356
|
+
if (blocker !== void 0) {
|
|
230357
|
+
const warningKey = `${task.id}:${blocker}`;
|
|
230358
|
+
if (!this.mergeRecoveryBlockerWarnKeys.has(warningKey)) {
|
|
230359
|
+
this.mergeRecoveryBlockerWarnKeys.add(warningKey);
|
|
230360
|
+
log34.warn(`mergeable-review recovery declined ${task.id}: ${blocker}`);
|
|
230361
|
+
}
|
|
230362
|
+
continue;
|
|
230363
|
+
}
|
|
229885
230364
|
laneQualifiedMergeable.push(task);
|
|
229886
230365
|
}
|
|
230366
|
+
const hiddenStaleContentCandidateIds = /* @__PURE__ */ new Set();
|
|
230367
|
+
for (const task of tasks2) {
|
|
230368
|
+
if (mergeable.includes(task)) continue;
|
|
230369
|
+
const parkShape = classifyStaleContentPark(task);
|
|
230370
|
+
if (!parkShape || mergeAdmissionByTaskId.get(task.id) !== true || executingIds.has(task.id) || task.status === "merging" || task.status === "merging-pr") continue;
|
|
230371
|
+
const reviewColumns = await ownReviewLanesFor(task);
|
|
230372
|
+
if (!reviewColumns.has(task.column)) continue;
|
|
230373
|
+
hiddenStaleContentCandidateIds.add(task.id);
|
|
230374
|
+
const attempts = (this.staleContentParkRecoveryAttempts.get(task.id) ?? 0) + 1;
|
|
230375
|
+
this.staleContentParkRecoveryAttempts.set(task.id, attempts);
|
|
230376
|
+
if (attempts === MAX_STARVATION_DROPS && !this.staleContentParkRecoveryBudgetLogged.has(task.id)) {
|
|
230377
|
+
this.staleContentParkRecoveryBudgetLogged.add(task.id);
|
|
230378
|
+
await this.store.logEntry(task.id, `[pre-merge] Stopped stale-content park recovery after ${MAX_STARVATION_DROPS} attempts; operator attention is required.`);
|
|
230379
|
+
}
|
|
230380
|
+
if (attempts > MAX_STARVATION_DROPS) continue;
|
|
230381
|
+
let mergeGate;
|
|
230382
|
+
try {
|
|
230383
|
+
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
230384
|
+
} catch {
|
|
230385
|
+
continue;
|
|
230386
|
+
}
|
|
230387
|
+
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) continue;
|
|
230388
|
+
const mergeContent = await captureMergeContentDescriptor(task, { workspaceRootDir: this.options.rootDir, settings });
|
|
230389
|
+
await this.routeStaleSingularApprovalBackToReview(task, reviewColumns, mergeGate, mergeContent, parkShape);
|
|
230390
|
+
}
|
|
230391
|
+
for (const taskId of this.staleContentParkRecoveryAttempts.keys()) {
|
|
230392
|
+
if (!hiddenStaleContentCandidateIds.has(taskId)) {
|
|
230393
|
+
this.staleContentParkRecoveryAttempts.delete(taskId);
|
|
230394
|
+
this.staleContentParkRecoveryBudgetLogged.delete(taskId);
|
|
230395
|
+
}
|
|
230396
|
+
}
|
|
229887
230397
|
if (unresolvedMergeableCards.length > 0) {
|
|
229888
230398
|
log34.warn(
|
|
229889
230399
|
`mergeable-review recovery: ${unresolvedMergeableCards.length} card(s) measured against the built-in review lane because their own workflow could not be resolved (${unresolvedMergeableCards.slice(0, 5).join(", ")}); a renamed review lane there stays stuck.`
|
|
@@ -229906,6 +230416,9 @@ var init_self_healing = __esm({
|
|
|
229906
230416
|
);
|
|
229907
230417
|
const enqueueMerge = this.options.enqueueMerge;
|
|
229908
230418
|
let recovered = 0;
|
|
230419
|
+
let enqueued = 0;
|
|
230420
|
+
let merged = 0;
|
|
230421
|
+
let parked = 0;
|
|
229909
230422
|
for (const task of unownedMergeable) {
|
|
229910
230423
|
try {
|
|
229911
230424
|
if (enqueueMerge) {
|
|
@@ -229921,19 +230434,22 @@ var init_self_healing = __esm({
|
|
|
229921
230434
|
await this.store.updateTask(task.id, { status: "failed", error: error3 });
|
|
229922
230435
|
await this.store.logEntry(task.id, error3);
|
|
229923
230436
|
this.mergeStarvationDrops.delete(task.id);
|
|
230437
|
+
parked++;
|
|
229924
230438
|
recovered++;
|
|
229925
230439
|
}
|
|
229926
230440
|
continue;
|
|
229927
230441
|
}
|
|
229928
230442
|
this.mergeStarvationDrops.delete(task.id);
|
|
230443
|
+
enqueued++;
|
|
229929
230444
|
} else {
|
|
229930
230445
|
await this.store.mergeTask(task.id);
|
|
230446
|
+
merged++;
|
|
229931
230447
|
}
|
|
229932
230448
|
await this.store.logEntry(
|
|
229933
230449
|
task.id,
|
|
229934
230450
|
enqueueMerge ? "Auto-recovered: eligible in-review task re-enqueued for merge" : "Auto-recovered: eligible in-review task was merged and moved to done"
|
|
229935
230451
|
);
|
|
229936
|
-
log34.log(`
|
|
230452
|
+
log34.log(enqueueMerge ? `Re-enqueued mergeable review task ${task.id} for merge` : `Merged review task ${task.id} and moved it to done`);
|
|
229937
230453
|
recovered++;
|
|
229938
230454
|
} catch (err) {
|
|
229939
230455
|
const errorMessage3 = err instanceof Error ? err.message : String(err);
|
|
@@ -229941,7 +230457,7 @@ var init_self_healing = __esm({
|
|
|
229941
230457
|
}
|
|
229942
230458
|
}
|
|
229943
230459
|
if (recovered > 0) {
|
|
229944
|
-
log34.log(
|
|
230460
|
+
log34.log(formatMergeableReviewRecoverySummary({ enqueued, merged, parked }));
|
|
229945
230461
|
}
|
|
229946
230462
|
return recovered;
|
|
229947
230463
|
} catch (err) {
|
|
@@ -237933,7 +238449,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
237933
238449
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
237934
238450
|
await this.store.logEntry(task.id, retryMessage);
|
|
237935
238451
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
237936
|
-
status: this.
|
|
238452
|
+
status: await this.resolvePlanningRetryHoldStatus(task, written),
|
|
237937
238453
|
error: null,
|
|
237938
238454
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
237939
238455
|
nextRecoveryAt: decision.nextState.nextRecoveryAt
|
|
@@ -237970,9 +238486,13 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
237970
238486
|
const retryMessage = `Generated plan failed deterministic validation (${deterministicSpecFailure}) \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}.`;
|
|
237971
238487
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
237972
238488
|
await this.store.logEntry(task.id, retryMessage);
|
|
237973
|
-
|
|
238489
|
+
await this.store.logEntry(
|
|
238490
|
+
task.id,
|
|
238491
|
+
"AI spec revision requested",
|
|
238492
|
+
`Deterministic plan validation rejected the generated plan: ${deterministicSpecFailure}`
|
|
238493
|
+
).catch(() => void 0);
|
|
237974
238494
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
237975
|
-
status:
|
|
238495
|
+
status: await this.resolvePlanningRetryHoldStatus(task, written),
|
|
237976
238496
|
error: null,
|
|
237977
238497
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
237978
238498
|
nextRecoveryAt: decision.nextState.nextRecoveryAt
|
|
@@ -238142,9 +238662,10 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
238142
238662
|
const retryMessage = `${failureMessage} \u2014 retry ${decision.nextState.recoveryRetryCount}/${MAX_RECOVERY_RETRIES} in ${formatDelay(decision.delayMs)}.`;
|
|
238143
238663
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
238144
238664
|
await this.store.logEntry(task.id, retryMessage).catch(() => void 0);
|
|
238665
|
+
const retryHoldStatus = await this.resolvePlanningRetryHoldStatus(task);
|
|
238145
238666
|
await this.updatePlanningStateIfStillCurrent(task, (live) => ({
|
|
238146
238667
|
...persistMarker(live),
|
|
238147
|
-
status:
|
|
238668
|
+
status: retryHoldStatus,
|
|
238148
238669
|
error: null,
|
|
238149
238670
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
238150
238671
|
nextRecoveryAt: decision.nextState.nextRecoveryAt
|
|
@@ -238174,7 +238695,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
238174
238695
|
planLog.warn(`${task.id}: failed to log transient-error retry entry: ${msg}`);
|
|
238175
238696
|
});
|
|
238176
238697
|
}
|
|
238177
|
-
const restoreStatus = this.
|
|
238698
|
+
const restoreStatus = await this.resolvePlanningRetryHoldStatus(task);
|
|
238178
238699
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
238179
238700
|
status: restoreStatus,
|
|
238180
238701
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
@@ -238226,7 +238747,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
238226
238747
|
const msg = logErr instanceof Error ? logErr.message : String(logErr);
|
|
238227
238748
|
planLog.warn(`${task.id}: failed to log planning-failure retry entry: ${msg}`);
|
|
238228
238749
|
});
|
|
238229
|
-
const restoreStatus = this.
|
|
238750
|
+
const restoreStatus = await this.resolvePlanningRetryHoldStatus(task);
|
|
238230
238751
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
238231
238752
|
status: restoreStatus,
|
|
238232
238753
|
recoveryRetryCount: genericDecision.nextState.recoveryRetryCount,
|
|
@@ -238510,11 +239031,22 @@ ${lines.join("\n")}` }],
|
|
|
238510
239031
|
}
|
|
238511
239032
|
return null;
|
|
238512
239033
|
}
|
|
239034
|
+
/*
|
|
239035
|
+
FNXC:TriagePlanningRetry 2026-09-05-22:06:
|
|
239036
|
+
A retry over a finished-looking PROMPT.md must hold `needs-replan`, matching scheduler validation
|
|
239037
|
+
retries. Clearing status makes the card dispatchable and bypasses finalizeApprovedTask approval.
|
|
239038
|
+
*/
|
|
239039
|
+
async resolvePlanningRetryHoldStatus(task, knownPromptContent) {
|
|
239040
|
+
const restored = this.restoreStatusAfterInterruptedTriageWork(task);
|
|
239041
|
+
if (restored) return restored;
|
|
239042
|
+
const content = knownPromptContent ?? await this.readNonEmptyPromptDraft(task.id, "planning retry hold");
|
|
239043
|
+
return content && !isTaskAwaitingPlanning(task, content) ? "needs-replan" : null;
|
|
239044
|
+
}
|
|
238513
239045
|
async validateGeneratedPrompt(taskId, promptContent) {
|
|
238514
239046
|
if (!promptContent.trim()) {
|
|
238515
239047
|
return "PROMPT.md file not found or empty";
|
|
238516
239048
|
}
|
|
238517
|
-
const headingNumbers =
|
|
239049
|
+
const headingNumbers = matchStepHeadings(promptContent).map((match) => match.headingNumber);
|
|
238518
239050
|
if (headingNumbers.length > 0 && !headingNumbers.every((heading, index2) => heading === index2)) {
|
|
238519
239051
|
const diagnostic = `Step headings must be contiguous 0-based execution indices (observed: ${headingNumbers.join(", ")}). Renumber from Step 0 and update prose cross-references.`;
|
|
238520
239052
|
planLog.warn(`${taskId}: ${diagnostic}`);
|
|
@@ -239124,6 +239656,101 @@ ${lines.join("\n")}` }],
|
|
|
239124
239656
|
}
|
|
239125
239657
|
});
|
|
239126
239658
|
|
|
239659
|
+
// ../engine/src/executor/optional-step-revision.ts
|
|
239660
|
+
function normalizeOptionalStepRevisionKey2(value) {
|
|
239661
|
+
return (value ?? "").trim().toLowerCase();
|
|
239662
|
+
}
|
|
239663
|
+
function optionalStepRevisionKey2(nodeId, stepName) {
|
|
239664
|
+
return normalizeOptionalStepRevisionKey2(nodeId) || normalizeOptionalStepRevisionKey2(stepName) || "pre-merge-optional-step";
|
|
239665
|
+
}
|
|
239666
|
+
function optionalStepRevisionResetOutcome(key) {
|
|
239667
|
+
return `${OPTIONAL_STEP_REVISION_RESET_MARKER} ${key}`;
|
|
239668
|
+
}
|
|
239669
|
+
function resetIndexFor(log42, normalizedKey) {
|
|
239670
|
+
for (let index2 = log42.length - 1; index2 >= 0; index2 -= 1) {
|
|
239671
|
+
const outcome = log42[index2]?.outcome ?? "";
|
|
239672
|
+
const markerIndex = outcome.indexOf(OPTIONAL_STEP_REVISION_RESET_MARKER);
|
|
239673
|
+
if (markerIndex < 0) continue;
|
|
239674
|
+
const markerValue = outcome.slice(markerIndex + OPTIONAL_STEP_REVISION_RESET_MARKER.length).split(/\r?\n/, 1)[0]?.trim();
|
|
239675
|
+
if (normalizeOptionalStepRevisionKey2(markerValue) === normalizedKey) return index2;
|
|
239676
|
+
}
|
|
239677
|
+
return -1;
|
|
239678
|
+
}
|
|
239679
|
+
function countOptionalStepRevisionAttempts2(task, key, stepName) {
|
|
239680
|
+
const normalizedKey = normalizeOptionalStepRevisionKey2(key);
|
|
239681
|
+
const normalizedStepName = normalizeOptionalStepRevisionKey2(stepName);
|
|
239682
|
+
const log42 = task.log ?? [];
|
|
239683
|
+
const since = resetIndexFor(log42, normalizedKey);
|
|
239684
|
+
return (since >= 0 ? log42.slice(since + 1) : log42).filter((entry) => {
|
|
239685
|
+
const action = entry.action ?? "";
|
|
239686
|
+
const outcome = entry.outcome ?? "";
|
|
239687
|
+
if (!/attempt \d+\//.test(action)) return false;
|
|
239688
|
+
const markerIndex = outcome.indexOf(OPTIONAL_STEP_REVISION_KEY_MARKER2);
|
|
239689
|
+
if (markerIndex >= 0) {
|
|
239690
|
+
const markerValue = outcome.slice(markerIndex + OPTIONAL_STEP_REVISION_KEY_MARKER2.length).split(/\r?\n/, 1)[0]?.trim();
|
|
239691
|
+
return normalizeOptionalStepRevisionKey2(markerValue) === normalizedKey;
|
|
239692
|
+
}
|
|
239693
|
+
if (!normalizedStepName) return false;
|
|
239694
|
+
return normalizeOptionalStepRevisionKey2(outcome).includes(`step: ${normalizedStepName}`);
|
|
239695
|
+
}).length;
|
|
239696
|
+
}
|
|
239697
|
+
function optionalStepRevisionLogOutcome2(details, key) {
|
|
239698
|
+
return `${details}
|
|
239699
|
+
${OPTIONAL_STEP_REVISION_KEY_MARKER2} ${key}`;
|
|
239700
|
+
}
|
|
239701
|
+
function buildReviewConvergenceContext(task, options) {
|
|
239702
|
+
const revisionKey = options.revisionKey;
|
|
239703
|
+
const priorAttemptCount = countPlanReviewRevisionAttempts(task.workflowStepResults, { revisionKey });
|
|
239704
|
+
const attempt = priorAttemptCount + 1;
|
|
239705
|
+
if (attempt <= 1) return "";
|
|
239706
|
+
const history = collectPlanReviewFeedbackHistory(task.workflowStepResults, { revisionKey });
|
|
239707
|
+
const lines = [
|
|
239708
|
+
`## Convergence \u2014 ${options.reviewKind === "plan" ? "Plan Review" : "Code Review"} attempt ${attempt}`
|
|
239709
|
+
];
|
|
239710
|
+
if (options.changeSummaryBlock?.trim()) lines.push("", options.changeSummaryBlock.trim());
|
|
239711
|
+
lines.push(
|
|
239712
|
+
"Treat the cumulative prior feedback below as a decision primer. Verify each prior blocker against the current PROMPT.md before looking for new findings.",
|
|
239713
|
+
"- Do not re-raise a resolved or semantically duplicate blocker.",
|
|
239714
|
+
"- A newly blocking finding must identify the revision that introduced it, the prior blocker that genuinely masked it, or why it is independently delivery-blocking for correctness, security, data safety, or executability. Record an earlier reviewer miss explicitly; never demote a critical defect merely because it was missed before."
|
|
239715
|
+
);
|
|
239716
|
+
if (attempt >= 3) {
|
|
239717
|
+
lines.push(
|
|
239718
|
+
"- Severity ratchet (attempt 3+): only delivery-blocking critical defects may return REVISE; important/minor wording or implementation-detail findings are advisory."
|
|
239719
|
+
);
|
|
239720
|
+
}
|
|
239721
|
+
if (history.length > 0) {
|
|
239722
|
+
lines.push("", "### Cumulative prior Plan Review ledger");
|
|
239723
|
+
history.forEach((feedback, index2) => lines.push(`#### PR${index2 + 1}`, feedback));
|
|
239724
|
+
}
|
|
239725
|
+
const own2 = task.workflowStepResults?.find((result2) => result2.workflowStepId === revisionKey);
|
|
239726
|
+
const findings = (own2?.priorAttempts ?? []).flatMap((attempt2) => attempt2.findings ?? []).filter(isOpenWorkflowReviewFinding);
|
|
239727
|
+
if (findings.length > 0) {
|
|
239728
|
+
lines.push("", "### Your prior findings on this gate");
|
|
239729
|
+
findings.forEach((finding) => lines.push(`- ${finding.id} \u2014 [${finding.severity ?? "unclassified"}] ${finding.title}: ${finding.body}${finding.filePath ? ` (${finding.filePath}${finding.line ? `:${finding.line}` : ""})` : ""}`));
|
|
239730
|
+
lines.push("Match prior defects by file path, line region, and described behavior; IDs and titles may change between rounds.");
|
|
239731
|
+
}
|
|
239732
|
+
const disputed = findings.filter((finding) => finding.disputedAt);
|
|
239733
|
+
if (disputed.length > 0) {
|
|
239734
|
+
lines.push("", "### Disputed by the implementer");
|
|
239735
|
+
disputed.forEach((finding) => lines.push(`- ${finding.id}: ${finding.disputeRationale ?? "No rationale recorded."}`));
|
|
239736
|
+
lines.push("You MUST rule on each dispute: accept it by listing its ID in supersededFindingIds, or maintain it with a finding whose rebutsDisputedFindingId names it and whose body answers the rationale. A maintained objection needs that reference even if it has a new finding ID.");
|
|
239737
|
+
}
|
|
239738
|
+
return lines.join("\n");
|
|
239739
|
+
}
|
|
239740
|
+
function buildGraphPlanReviewConvergenceContext(task, revisionKey) {
|
|
239741
|
+
return buildReviewConvergenceContext(task, { revisionKey, reviewKind: "plan" });
|
|
239742
|
+
}
|
|
239743
|
+
var OPTIONAL_STEP_REVISION_KEY_MARKER2, OPTIONAL_STEP_REVISION_RESET_MARKER;
|
|
239744
|
+
var init_optional_step_revision = __esm({
|
|
239745
|
+
"../engine/src/executor/optional-step-revision.ts"() {
|
|
239746
|
+
"use strict";
|
|
239747
|
+
init_src2();
|
|
239748
|
+
init_plan_review_feedback_history();
|
|
239749
|
+
OPTIONAL_STEP_REVISION_KEY_MARKER2 = "Workflow revision key:";
|
|
239750
|
+
OPTIONAL_STEP_REVISION_RESET_MARKER = "Workflow revision ledger reset:";
|
|
239751
|
+
}
|
|
239752
|
+
});
|
|
239753
|
+
|
|
239127
239754
|
// ../engine/src/executor/workflow-gate-activity.ts
|
|
239128
239755
|
function resolveWorkflowGateActivityClaim(routedPrincipalAgentId, assignedAgentId) {
|
|
239129
239756
|
const agentId = routedPrincipalAgentId ?? assignedAgentId ?? "executor";
|
|
@@ -239955,7 +240582,7 @@ function extractPromptSection(promptContent, sectionName) {
|
|
|
239955
240582
|
return promptContent.slice(start, end).trim();
|
|
239956
240583
|
}
|
|
239957
240584
|
function summarizePromptSteps(promptContent) {
|
|
239958
|
-
const stepTitles =
|
|
240585
|
+
const stepTitles = matchStepHeadings(promptContent).map(({ index: index2, headingLineEnd }) => promptContent.slice(index2, headingLineEnd).trim());
|
|
239959
240586
|
if (stepTitles.length === 0) {
|
|
239960
240587
|
return "";
|
|
239961
240588
|
}
|
|
@@ -244726,85 +245353,6 @@ var init_review_inline_fix_recapture = __esm({
|
|
|
244726
245353
|
}
|
|
244727
245354
|
});
|
|
244728
245355
|
|
|
244729
|
-
// ../engine/src/executor/optional-step-revision.ts
|
|
244730
|
-
function normalizeOptionalStepRevisionKey2(value) {
|
|
244731
|
-
return (value ?? "").trim().toLowerCase();
|
|
244732
|
-
}
|
|
244733
|
-
function optionalStepRevisionKey2(nodeId, stepName) {
|
|
244734
|
-
return normalizeOptionalStepRevisionKey2(nodeId) || normalizeOptionalStepRevisionKey2(stepName) || "pre-merge-optional-step";
|
|
244735
|
-
}
|
|
244736
|
-
function countOptionalStepRevisionAttempts2(task, key, stepName) {
|
|
244737
|
-
const normalizedKey = normalizeOptionalStepRevisionKey2(key);
|
|
244738
|
-
const normalizedStepName = normalizeOptionalStepRevisionKey2(stepName);
|
|
244739
|
-
return (task.log ?? []).filter((entry) => {
|
|
244740
|
-
const action = entry.action ?? "";
|
|
244741
|
-
const outcome = entry.outcome ?? "";
|
|
244742
|
-
if (!/attempt \d+\//.test(action)) return false;
|
|
244743
|
-
const markerIndex = outcome.indexOf(OPTIONAL_STEP_REVISION_KEY_MARKER2);
|
|
244744
|
-
if (markerIndex >= 0) {
|
|
244745
|
-
const markerValue = outcome.slice(markerIndex + OPTIONAL_STEP_REVISION_KEY_MARKER2.length).split(/\r?\n/, 1)[0]?.trim();
|
|
244746
|
-
return normalizeOptionalStepRevisionKey2(markerValue) === normalizedKey;
|
|
244747
|
-
}
|
|
244748
|
-
if (!normalizedStepName) return false;
|
|
244749
|
-
return normalizeOptionalStepRevisionKey2(outcome).includes(`step: ${normalizedStepName}`);
|
|
244750
|
-
}).length;
|
|
244751
|
-
}
|
|
244752
|
-
function optionalStepRevisionLogOutcome2(details, key) {
|
|
244753
|
-
return `${details}
|
|
244754
|
-
${OPTIONAL_STEP_REVISION_KEY_MARKER2} ${key}`;
|
|
244755
|
-
}
|
|
244756
|
-
function buildReviewConvergenceContext(task, options) {
|
|
244757
|
-
const revisionKey = options.revisionKey;
|
|
244758
|
-
const priorAttemptCount = countPlanReviewRevisionAttempts(task.workflowStepResults, { revisionKey });
|
|
244759
|
-
const attempt = priorAttemptCount + 1;
|
|
244760
|
-
if (attempt <= 1) return "";
|
|
244761
|
-
const history = collectPlanReviewFeedbackHistory(task.workflowStepResults, { revisionKey });
|
|
244762
|
-
const lines = [
|
|
244763
|
-
`## Convergence \u2014 ${options.reviewKind === "plan" ? "Plan Review" : "Code Review"} attempt ${attempt}`
|
|
244764
|
-
];
|
|
244765
|
-
if (options.changeSummaryBlock?.trim()) lines.push("", options.changeSummaryBlock.trim());
|
|
244766
|
-
lines.push(
|
|
244767
|
-
"Treat the cumulative prior feedback below as a decision primer. Verify each prior blocker against the current PROMPT.md before looking for new findings.",
|
|
244768
|
-
"- Do not re-raise a resolved or semantically duplicate blocker.",
|
|
244769
|
-
"- A newly blocking finding must identify the revision that introduced it, the prior blocker that genuinely masked it, or why it is independently delivery-blocking for correctness, security, data safety, or executability. Record an earlier reviewer miss explicitly; never demote a critical defect merely because it was missed before."
|
|
244770
|
-
);
|
|
244771
|
-
if (attempt >= 3) {
|
|
244772
|
-
lines.push(
|
|
244773
|
-
"- Severity ratchet (attempt 3+): only delivery-blocking critical defects may return REVISE; important/minor wording or implementation-detail findings are advisory."
|
|
244774
|
-
);
|
|
244775
|
-
}
|
|
244776
|
-
if (history.length > 0) {
|
|
244777
|
-
lines.push("", "### Cumulative prior Plan Review ledger");
|
|
244778
|
-
history.forEach((feedback, index2) => lines.push(`#### PR${index2 + 1}`, feedback));
|
|
244779
|
-
}
|
|
244780
|
-
const own2 = task.workflowStepResults?.find((result2) => result2.workflowStepId === revisionKey);
|
|
244781
|
-
const findings = (own2?.priorAttempts ?? []).flatMap((attempt2) => attempt2.findings ?? []).filter(isOpenWorkflowReviewFinding);
|
|
244782
|
-
if (findings.length > 0) {
|
|
244783
|
-
lines.push("", "### Your prior findings on this gate");
|
|
244784
|
-
findings.forEach((finding) => lines.push(`- ${finding.id} \u2014 [${finding.severity ?? "unclassified"}] ${finding.title}: ${finding.body}${finding.filePath ? ` (${finding.filePath}${finding.line ? `:${finding.line}` : ""})` : ""}`));
|
|
244785
|
-
lines.push("Match prior defects by file path, line region, and described behavior; IDs and titles may change between rounds.");
|
|
244786
|
-
}
|
|
244787
|
-
const disputed = findings.filter((finding) => finding.disputedAt);
|
|
244788
|
-
if (disputed.length > 0) {
|
|
244789
|
-
lines.push("", "### Disputed by the implementer");
|
|
244790
|
-
disputed.forEach((finding) => lines.push(`- ${finding.id}: ${finding.disputeRationale ?? "No rationale recorded."}`));
|
|
244791
|
-
lines.push("You MUST rule on each dispute: accept it by listing its ID in supersededFindingIds, or maintain it with a finding whose rebutsDisputedFindingId names it and whose body answers the rationale. A maintained objection needs that reference even if it has a new finding ID.");
|
|
244792
|
-
}
|
|
244793
|
-
return lines.join("\n");
|
|
244794
|
-
}
|
|
244795
|
-
function buildGraphPlanReviewConvergenceContext(task, revisionKey) {
|
|
244796
|
-
return buildReviewConvergenceContext(task, { revisionKey, reviewKind: "plan" });
|
|
244797
|
-
}
|
|
244798
|
-
var OPTIONAL_STEP_REVISION_KEY_MARKER2;
|
|
244799
|
-
var init_optional_step_revision = __esm({
|
|
244800
|
-
"../engine/src/executor/optional-step-revision.ts"() {
|
|
244801
|
-
"use strict";
|
|
244802
|
-
init_src2();
|
|
244803
|
-
init_plan_review_feedback_history();
|
|
244804
|
-
OPTIONAL_STEP_REVISION_KEY_MARKER2 = "Workflow revision key:";
|
|
244805
|
-
}
|
|
244806
|
-
});
|
|
244807
|
-
|
|
244808
245356
|
// ../engine/src/executor/execute-workflow-step.ts
|
|
244809
245357
|
import { exec as exec44 } from "node:child_process";
|
|
244810
245358
|
import { promisify as promisify63 } from "node:util";
|
|
@@ -245570,10 +246118,12 @@ ${JSON.stringify({ verdict: repairedVerdict })}`,
|
|
|
245570
246118
|
session.dispose();
|
|
245571
246119
|
await agentLogger.flush();
|
|
245572
246120
|
if (parsed.verdict) {
|
|
246121
|
+
const findingsUnreadable = verdictRepairResult === "repaired" && (parsed.findings?.length ?? 0) === 0;
|
|
245573
246122
|
const gated = reviewBlockingSeverity ? applyReviewSeverityGate({
|
|
245574
246123
|
verdict: parsed.verdict,
|
|
245575
246124
|
findings: parsed.findings,
|
|
245576
|
-
threshold: reviewBlockingSeverity
|
|
246125
|
+
threshold: reviewBlockingSeverity,
|
|
246126
|
+
findingsUnreadable
|
|
245577
246127
|
}) : void 0;
|
|
245578
246128
|
const effectiveVerdict = gated?.verdict ?? parsed.verdict;
|
|
245579
246129
|
if (gated?.downgraded) {
|
|
@@ -260088,7 +260638,7 @@ async function routeGraphMergeFailureToRetry(deps, live, result2, abortProvenanc
|
|
|
260088
260638
|
const parked = await parkTaskFailed(
|
|
260089
260639
|
deps.store,
|
|
260090
260640
|
live.id,
|
|
260091
|
-
|
|
260641
|
+
`${AUTO_MERGE_RETRY_REJECTED_PREFIX} ${reason}`,
|
|
260092
260642
|
deps.getRunContextFor(live.id),
|
|
260093
260643
|
live.columnMovedAt
|
|
260094
260644
|
);
|
|
@@ -260104,6 +260654,7 @@ var init_route_graph_merge_failure_to_retry = __esm({
|
|
|
260104
260654
|
init_logger3();
|
|
260105
260655
|
init_workflow_merge_nodes();
|
|
260106
260656
|
init_emit_merge_boundary_unproven_audit();
|
|
260657
|
+
init_stale_content_park();
|
|
260107
260658
|
BIT_BACKOFF_MS = [1e3, 2e3, 4e3, 8e3, 16e3, 32e3, 64e3];
|
|
260108
260659
|
}
|
|
260109
260660
|
});
|
|
@@ -264765,9 +265316,13 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
|
|
|
264765
265316
|
*/
|
|
264766
265317
|
credentialInstanceId: detail.credentialInstanceId,
|
|
264767
265318
|
resolveCredentialInstanceRetarget: nextStepInstance,
|
|
264768
|
-
|
|
264769
|
-
|
|
264770
|
-
|
|
265319
|
+
/*
|
|
265320
|
+
FNXC:ColumnAgent 2026-09-04-14:11:
|
|
265321
|
+
The effective step identity is the governing column agent or the authoritative assignee.
|
|
265322
|
+
Both are existence-proved reads; with empty project.boards the column seam is absent, so
|
|
265323
|
+
retaining only it lost attribution for every assigned task.
|
|
265324
|
+
*/
|
|
265325
|
+
effectiveAgentId: stepIdentityAgent?.id,
|
|
264771
265326
|
actionGateContext: deps.buildActionGateContext(task.id, stepIdentityAgent, settings.defaultAgentPermissionPolicy),
|
|
264772
265327
|
permanentAgentGating: deps.buildPermanentAgentGatingContext(task.id, stepIdentityAgent, settings.defaultAgentPermissionPolicy),
|
|
264773
265328
|
// FNXC:McpConfig 2026-06-25-23:03: Per-step workflow sessions are an executor lane, so they inherit the task's resolved MCP set from the effective step identity agent and never re-read or log plaintext secret values.
|
|
@@ -269630,10 +270185,22 @@ var init_terminate_all_children = __esm({
|
|
|
269630
270185
|
});
|
|
269631
270186
|
|
|
269632
270187
|
// ../engine/src/executor/clear-terminal-step-failures-for-retry.ts
|
|
270188
|
+
function latestTerminalPreMergeFailureId(results) {
|
|
270189
|
+
return (results ?? []).filter((result2) => (result2.phase || "pre-merge") === "pre-merge" && (result2.status === "failed" || result2.status === "advisory_failure")).sort((left, right) => {
|
|
270190
|
+
const leftTime = Date.parse(left.completedAt || left.startedAt || "");
|
|
270191
|
+
const rightTime = Date.parse(right.completedAt || right.startedAt || "");
|
|
270192
|
+
return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
|
|
270193
|
+
})[0]?.workflowStepId;
|
|
270194
|
+
}
|
|
269633
270195
|
async function clearTerminalStepFailuresForRetry(deps, taskId, mode) {
|
|
269634
270196
|
const live = await deps.store.getTask(taskId).catch(() => null);
|
|
269635
270197
|
if (!live) return;
|
|
269636
|
-
const
|
|
270198
|
+
const remediatedStepId = mode === "archive" ? latestTerminalPreMergeFailureId(live.workflowStepResults) : void 0;
|
|
270199
|
+
const cleared = mode === "archive" ? archiveTerminalWorkflowStepFailures(
|
|
270200
|
+
live.workflowStepResults,
|
|
270201
|
+
void 0,
|
|
270202
|
+
remediatedStepId ? { workflowStepIds: /* @__PURE__ */ new Set([remediatedStepId]) } : {}
|
|
270203
|
+
) : clearTerminalWorkflowStepFailures(live.workflowStepResults);
|
|
269637
270204
|
if (cleared !== live.workflowStepResults) {
|
|
269638
270205
|
await deps.store.updateTask(taskId, { workflowStepResults: cleared }, deps.getRunContextFor(taskId));
|
|
269639
270206
|
}
|
|
@@ -274240,10 +274807,14 @@ var init_mission_execution_loop = __esm({
|
|
|
274240
274807
|
for (const slice of milestone.slices) {
|
|
274241
274808
|
if (slice.status !== "active") continue;
|
|
274242
274809
|
const supersededFixes = await this.missionStore.reconcileSupersededGeneratedFixFeatures(slice.id);
|
|
274243
|
-
const supersededFeatureIds = new Set(supersededFixes.featureIds);
|
|
274810
|
+
const supersededFeatureIds = new Set(supersededFixes.featureIds ?? []);
|
|
274811
|
+
if ((supersededFixes.repairedCount ?? 0) > 0) {
|
|
274812
|
+
loopLog.warn(`Recovery: restored ${supersededFixes.repairedCount ?? 0} generated Fix Features in slice ${slice.id}: ${(supersededFixes.repairedFeatureIds ?? []).join(", ")}`);
|
|
274813
|
+
recoveredCount += supersededFixes.repairedCount ?? 0;
|
|
274814
|
+
}
|
|
274244
274815
|
if (supersededFixes.supersededCount > 0) {
|
|
274245
274816
|
loopLog.warn(
|
|
274246
|
-
`Recovery: superseded ${supersededFixes.supersededCount} generated Fix Features in slice ${slice.id} because an ancestor feature already passed
|
|
274817
|
+
`Recovery: superseded ${supersededFixes.supersededCount} generated Fix Features in slice ${slice.id} because an ancestor or the Fix feature's own validation already passed`
|
|
274247
274818
|
);
|
|
274248
274819
|
recoveredCount += supersededFixes.supersededCount;
|
|
274249
274820
|
}
|
|
@@ -297253,7 +297824,7 @@ ${detail}`);
|
|
|
297253
297824
|
metadata: { taskId: task.id, nodeId: reroute.nodeId, workflowStepId: reroute.workflowStepId, reason: reroute.reason, source: "merge-gate", missingGateCount: mergeGate.requiredPreMergeStepIds.size }
|
|
297254
297825
|
});
|
|
297255
297826
|
}
|
|
297256
|
-
if (blocker
|
|
297827
|
+
if (isStaleContentApprovalBlocker(blocker) && mergeContent.kind === "singular") {
|
|
297257
297828
|
const reroute = await rerouteSingularStaleContentToReview(store, task, {
|
|
297258
297829
|
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
297259
297830
|
mergeContent
|
|
@@ -297263,9 +297834,10 @@ ${detail}`);
|
|
|
297263
297834
|
nodeId: void 0,
|
|
297264
297835
|
workflowStepId: void 0
|
|
297265
297836
|
}));
|
|
297266
|
-
|
|
297267
|
-
|
|
297268
|
-
|
|
297837
|
+
await store.logEntry(
|
|
297838
|
+
task.id,
|
|
297839
|
+
`[pre-merge] Review lane '${reroute.nodeId ?? "unknown"}' approved older content; re-review is owned by the workflow graph (${reroute.reason}).`
|
|
297840
|
+
);
|
|
297269
297841
|
const auditKey = `${task.id}:${reroute.reason}`;
|
|
297270
297842
|
if (!this.staleContentRerouteAuditKeys.has(auditKey)) {
|
|
297271
297843
|
this.staleContentRerouteAuditKeys.add(auditKey);
|
|
@@ -301631,6 +302203,7 @@ __export(src_exports2, {
|
|
|
301631
302203
|
NotificationService: () => NotificationService,
|
|
301632
302204
|
NtfyNotificationProvider: () => NtfyNotificationProvider,
|
|
301633
302205
|
NtfyNotifier: () => NtfyNotifier,
|
|
302206
|
+
OPTIONAL_STEP_REVISION_RESET_MARKER: () => OPTIONAL_STEP_REVISION_RESET_MARKER,
|
|
301634
302207
|
OVERSEER_ADVISOR_REPLY_CONTRACT: () => OVERSEER_ADVISOR_REPLY_CONTRACT,
|
|
301635
302208
|
OVERSEER_ADVISOR_SYSTEM_PROMPT: () => OVERSEER_ADVISOR_SYSTEM_PROMPT,
|
|
301636
302209
|
OVERSEER_WATCHED_STAGES: () => OVERSEER_WATCHED_STAGES,
|
|
@@ -301810,6 +302383,7 @@ __export(src_exports2, {
|
|
|
301810
302383
|
createFusionModelRegistry: () => createFusionModelRegistry,
|
|
301811
302384
|
createGetAgentConfigTool: () => createGetAgentConfigTool,
|
|
301812
302385
|
createGoalRetrievalTools: () => createGoalRetrievalTools,
|
|
302386
|
+
createHistoryReadTool: () => createHistoryReadTool,
|
|
301813
302387
|
createIdeationTools: () => createIdeationTools,
|
|
301814
302388
|
createInteractiveAiSessionWith: () => createInteractiveAiSessionWith,
|
|
301815
302389
|
createListAgentsTool: () => createListAgentsTool,
|
|
@@ -301819,7 +302393,6 @@ __export(src_exports2, {
|
|
|
301819
302393
|
createNoopLegacySeams: () => createNoopLegacySeams,
|
|
301820
302394
|
createParseStepsHandler: () => createParseStepsHandler,
|
|
301821
302395
|
createParsingOverseerAgent: () => createParsingOverseerAgent,
|
|
301822
|
-
createPatchnodeReadTool: () => createPatchnodeReadTool,
|
|
301823
302396
|
createPrNodeHandlers: () => createPrNodeHandlers,
|
|
301824
302397
|
createPrimitivePromptLikeHandler: () => createPrimitivePromptLikeHandler,
|
|
301825
302398
|
createPrimitiveStepReviewHandler: () => createPrimitiveStepReviewHandler,
|
|
@@ -301991,6 +302564,7 @@ __export(src_exports2, {
|
|
|
301991
302564
|
normalizeAgentLogPaging: () => normalizeAgentLogPaging,
|
|
301992
302565
|
normalizeJiraIssueKey: () => normalizeJiraIssueKey,
|
|
301993
302566
|
notifyAutostashOrphans: () => notifyAutostashOrphans,
|
|
302567
|
+
optionalStepRevisionResetOutcome: () => optionalStepRevisionResetOutcome,
|
|
301994
302568
|
parseAdvisorReplyForAdvice: () => parseAdvisorReplyForAdvice,
|
|
301995
302569
|
parseInteractiveAgentResponse: () => parseAgentResponse,
|
|
301996
302570
|
parsePrEntityMarker: () => parsePrEntityMarker,
|
|
@@ -302180,6 +302754,7 @@ var init_src5 = __esm({
|
|
|
302180
302754
|
init_postgres_migration_notice();
|
|
302181
302755
|
init_concurrency();
|
|
302182
302756
|
init_triage();
|
|
302757
|
+
init_optional_step_revision();
|
|
302183
302758
|
init_executor();
|
|
302184
302759
|
init_workflow_graph_executor();
|
|
302185
302760
|
init_workflow_graph_branches();
|
|
@@ -313293,6 +313868,13 @@ var init_task_planner_chat_metrics = __esm({
|
|
|
313293
313868
|
}
|
|
313294
313869
|
});
|
|
313295
313870
|
|
|
313871
|
+
// ../dashboard/src/chat-conversation-references.ts
|
|
313872
|
+
var init_chat_conversation_references = __esm({
|
|
313873
|
+
"../dashboard/src/chat-conversation-references.ts"() {
|
|
313874
|
+
"use strict";
|
|
313875
|
+
}
|
|
313876
|
+
});
|
|
313877
|
+
|
|
313296
313878
|
// ../dashboard/src/chat.ts
|
|
313297
313879
|
import { EventEmitter as EventEmitter39 } from "node:events";
|
|
313298
313880
|
import { SessionManager as SessionManager3 } from "@earendil-works/pi-coding-agent";
|
|
@@ -313307,6 +313889,7 @@ var init_chat = __esm({
|
|
|
313307
313889
|
init_task_planner_chat_context();
|
|
313308
313890
|
init_task_planner_chat_metrics();
|
|
313309
313891
|
init_sse2();
|
|
313892
|
+
init_chat_conversation_references();
|
|
313310
313893
|
init_src5();
|
|
313311
313894
|
init_src5();
|
|
313312
313895
|
chatLog = createLogger("dashboard-chat");
|
|
@@ -318682,12 +319265,9 @@ var init_dashboard_views = __esm({
|
|
|
318682
319265
|
{ id: "board", label: "Board", labelKey: "nav.board" },
|
|
318683
319266
|
{ id: "list", label: "List", labelKey: "nav.list" },
|
|
318684
319267
|
/*
|
|
318685
|
-
|
|
318686
|
-
|
|
318687
|
-
|
|
318688
|
-
FNXC:PatchnoteNaming 2026-08-30-06:36:
|
|
318689
|
-
Operators now call the delivery history History. The stable `patchnode` identifier and translation key remain unchanged for routes, storage, persisted mobile-nav preferences, and integrations.
|
|
318690
|
-
*/
|
|
319268
|
+
FNXC:HistoryNaming 2026-09-04-09:35:
|
|
319269
|
+
Operators call this destination History, and chat exposes it as `fn_history_read`. The `patchnode` view id, `nav.patchnode` key, `GET /api/patchnode` route, `project.patchnode_entries` table, and persisted mobile-navigation preferences intentionally remain unchanged.
|
|
319270
|
+
*/
|
|
318691
319271
|
{ id: "patchnode", label: "History", labelKey: "nav.patchnode" },
|
|
318692
319272
|
{ id: "graph", label: "Graph" },
|
|
318693
319273
|
{ id: "agents", label: "Agents", labelKey: "nav.agents" },
|