@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/bin.js
CHANGED
|
@@ -1956,10 +1956,11 @@ function toSnapshot(result2) {
|
|
|
1956
1956
|
const { priorAttempts: _drop, ...rest } = result2;
|
|
1957
1957
|
return rest;
|
|
1958
1958
|
}
|
|
1959
|
-
function archiveTerminalWorkflowStepFailures(results, archivedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1960
|
-
|
|
1959
|
+
function archiveTerminalWorkflowStepFailures(results, archivedAt = (/* @__PURE__ */ new Date()).toISOString(), options = {}) {
|
|
1960
|
+
const inScope = (result2) => isTerminalFailure(result2) && (!options.workflowStepIds || options.workflowStepIds.has(result2.workflowStepId));
|
|
1961
|
+
if (!results?.some(inScope)) return results;
|
|
1961
1962
|
return results.map((result2) => {
|
|
1962
|
-
if (!
|
|
1963
|
+
if (!inScope(result2)) return result2;
|
|
1963
1964
|
const snapshot = toSnapshot(result2);
|
|
1964
1965
|
const priorAttempts = [snapshot, ...result2.priorAttempts ?? []].slice(0, MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS);
|
|
1965
1966
|
const {
|
|
@@ -2135,6 +2136,26 @@ function isAuditedOperatorBypass(result2) {
|
|
|
2135
2136
|
actor && !AUTOMATED_BYPASS_ACTORS.has(actor) && result2.bypassedAt?.trim() && result2.bypassReason?.trim()
|
|
2136
2137
|
);
|
|
2137
2138
|
}
|
|
2139
|
+
function resolveCollateralArchivedReviewGate(result2, options) {
|
|
2140
|
+
const archivedFrom = result2.remediationArchivedFromStatus;
|
|
2141
|
+
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)) {
|
|
2142
|
+
return void 0;
|
|
2143
|
+
}
|
|
2144
|
+
const {
|
|
2145
|
+
remediationArchivedAt: _archivedAt,
|
|
2146
|
+
remediationArchivedFromStatus: _archivedFrom,
|
|
2147
|
+
...rest
|
|
2148
|
+
} = result2;
|
|
2149
|
+
return {
|
|
2150
|
+
restored: {
|
|
2151
|
+
...rest,
|
|
2152
|
+
status: archivedFrom,
|
|
2153
|
+
output: COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC,
|
|
2154
|
+
notes: COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC
|
|
2155
|
+
},
|
|
2156
|
+
reason: COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2138
2159
|
function resolveUnprovenReviewApproval(result2, options) {
|
|
2139
2160
|
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) {
|
|
2140
2161
|
return void 0;
|
|
@@ -2166,9 +2187,10 @@ function evaluateStep(workflowStepId, results, task, descriptor) {
|
|
|
2166
2187
|
const isPlanDomain = workflowStepId === PLAN_REVIEW_GROUP_ID || result2.reviewKind === "plan";
|
|
2167
2188
|
const notRunApproves = isWorkflowStepNotRun(result2) && !requiresAuthoredVerdict && !isPlanDomain;
|
|
2168
2189
|
const approved = result2.status === "passed" && (requiresAuthoredVerdict ? approvedVerdict : result2.verdict === void 0 || approvedVerdict) || result2.status === "skipped" && !!result2.bypassedBy || notRunApproves;
|
|
2169
|
-
|
|
2190
|
+
const auditedOperatorWaiver = isAuditedOperatorBypass(result2) && descriptor?.kind !== "workspace";
|
|
2191
|
+
if (!approved || result2.remediationArchivedAt != null && !auditedOperatorWaiver) return { workflowStepId, state: "not-approved" };
|
|
2170
2192
|
if (result2.reviewKind === "plan") return { workflowStepId, state: "approved" };
|
|
2171
|
-
if (
|
|
2193
|
+
if (auditedOperatorWaiver) {
|
|
2172
2194
|
return { workflowStepId, state: "approved" };
|
|
2173
2195
|
}
|
|
2174
2196
|
const bindsContent = requiresExplicitVerdict || result2.reviewInputFingerprint !== void 0;
|
|
@@ -2201,7 +2223,7 @@ function evaluateStep(workflowStepId, results, task, descriptor) {
|
|
|
2201
2223
|
if (stale2.length) return { workflowStepId, state: "stale-content", repositories: stale2 };
|
|
2202
2224
|
return { workflowStepId, state: "approved" };
|
|
2203
2225
|
}
|
|
2204
|
-
var AUTOMATED_BYPASS_ACTORS, UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC;
|
|
2226
|
+
var AUTOMATED_BYPASS_ACTORS, UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC, COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC;
|
|
2205
2227
|
var init_pre_merge_approval = __esm({
|
|
2206
2228
|
"../core/src/merge/pre-merge-approval.ts"() {
|
|
2207
2229
|
"use strict";
|
|
@@ -2210,6 +2232,7 @@ var init_pre_merge_approval = __esm({
|
|
|
2210
2232
|
init_workflow_step_results();
|
|
2211
2233
|
AUTOMATED_BYPASS_ACTORS = /* @__PURE__ */ new Set([FAST_MODE_BYPASS_ACTOR]);
|
|
2212
2234
|
UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC = "Content-binding review approval recorded without reviewInputFingerprint; approval invalidated so the gate can run again.";
|
|
2235
|
+
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.";
|
|
2213
2236
|
}
|
|
2214
2237
|
});
|
|
2215
2238
|
|
|
@@ -2318,6 +2341,9 @@ function hasNonTerminalSteps(task) {
|
|
|
2318
2341
|
function isPreMergeStepsNotRunBlocker(blocker) {
|
|
2319
2342
|
return blocker === PRE_MERGE_STEPS_NOT_RUN_BLOCKER;
|
|
2320
2343
|
}
|
|
2344
|
+
function isStaleContentApprovalBlocker(blocker) {
|
|
2345
|
+
return typeof blocker === "string" && blocker.trim().endsWith(STALE_CONTENT_APPROVAL_BLOCKER);
|
|
2346
|
+
}
|
|
2321
2347
|
function getTaskMergeBlocker(task, options = {}) {
|
|
2322
2348
|
if (!options.skipColumnIdentityCheck) {
|
|
2323
2349
|
const reviewColumns = options.reviewColumns?.size ? options.reviewColumns : void 0;
|
|
@@ -2339,8 +2365,10 @@ function getTaskMergeBlocker(task, options = {}) {
|
|
|
2339
2365
|
}
|
|
2340
2366
|
const approval = evaluatePreMergeApprovals(task, options).find((candidate) => candidate.state !== "approved");
|
|
2341
2367
|
if (approval?.state === "missing") return PRE_MERGE_STEPS_NOT_RUN_BLOCKER;
|
|
2342
|
-
if (approval?.state === "not-approved")
|
|
2343
|
-
|
|
2368
|
+
if (approval?.state === "not-approved") {
|
|
2369
|
+
return `task has enabled pre-merge workflow steps without a current approval (gate '${approval.workflowStepId}')`;
|
|
2370
|
+
}
|
|
2371
|
+
if (approval?.state === "stale-content") return STALE_CONTENT_APPROVAL_BLOCKER;
|
|
2344
2372
|
if (approval?.state === "unprovable-content") return "task has no provable approval for the content being merged";
|
|
2345
2373
|
if (task.workflowStepResults?.some((result2) => {
|
|
2346
2374
|
const phase = result2.phase || "pre-merge";
|
|
@@ -2357,11 +2385,14 @@ function getTaskMergeBlocker(task, options = {}) {
|
|
|
2357
2385
|
return void 0;
|
|
2358
2386
|
}
|
|
2359
2387
|
function getLatestFailedPreMergeReviewStep(task) {
|
|
2360
|
-
|
|
2388
|
+
const results = task.workflowStepResults ?? [];
|
|
2389
|
+
const recentFirst = (a, b3) => {
|
|
2361
2390
|
const aTs = Date.parse(a.completedAt || a.startedAt || "");
|
|
2362
2391
|
const bTs = Date.parse(b3.completedAt || b3.startedAt || "");
|
|
2363
2392
|
return (Number.isFinite(bTs) ? bTs : 0) - (Number.isFinite(aTs) ? aTs : 0);
|
|
2364
|
-
}
|
|
2393
|
+
};
|
|
2394
|
+
const isPreMerge = (result2) => (result2.phase || "pre-merge") === "pre-merge";
|
|
2395
|
+
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];
|
|
2365
2396
|
}
|
|
2366
2397
|
function clearMergeConfirmedTransientStatus(status) {
|
|
2367
2398
|
return status !== void 0 && MERGE_CONFIRMED_TRANSIENT_STATUSES.has(status) ? void 0 : status;
|
|
@@ -2445,13 +2476,14 @@ function findPendingPreMergeStep(task) {
|
|
|
2445
2476
|
(a, b3) => new Date(b3.startedAt ?? 0).getTime() - new Date(a.startedAt ?? 0).getTime()
|
|
2446
2477
|
)[0];
|
|
2447
2478
|
}
|
|
2448
|
-
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;
|
|
2479
|
+
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;
|
|
2449
2480
|
var init_task_merge = __esm({
|
|
2450
2481
|
"../core/src/merge/task-merge.ts"() {
|
|
2451
2482
|
"use strict";
|
|
2452
2483
|
init_branch_group_completion();
|
|
2453
2484
|
init_task_helpers();
|
|
2454
2485
|
init_pre_merge_approval();
|
|
2486
|
+
init_workflow_step_results();
|
|
2455
2487
|
FUSION_SIBLING_BRANCH_RE = /^fusion\/fn-/i;
|
|
2456
2488
|
AWAITING_APPROVAL_PAUSE_REASON = "awaiting-approval";
|
|
2457
2489
|
HARD_BLOCKING_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -2495,6 +2527,7 @@ var init_task_merge = __esm({
|
|
|
2495
2527
|
"pending"
|
|
2496
2528
|
]);
|
|
2497
2529
|
PRE_MERGE_STEPS_NOT_RUN_BLOCKER = "task has enabled pre-merge workflow steps that never ran";
|
|
2530
|
+
STALE_CONTENT_APPROVAL_BLOCKER = "task has a pre-merge approval recorded against different content";
|
|
2498
2531
|
PreMergeStepsNotRunError = class extends Error {
|
|
2499
2532
|
code = "pre-merge-steps-not-run";
|
|
2500
2533
|
taskId;
|
|
@@ -6474,8 +6507,9 @@ function renderValidationCause(diagnostics8) {
|
|
|
6474
6507
|
}
|
|
6475
6508
|
function featureValidationRepairEligibility(feature) {
|
|
6476
6509
|
const repairableLoop = feature.loopState === "blocked" || feature.loopState === "needs_fix";
|
|
6510
|
+
const unvalidatedPassedMarker = feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId;
|
|
6477
6511
|
return {
|
|
6478
|
-
clear: repairableLoop || feature.status === "blocked",
|
|
6512
|
+
clear: repairableLoop || feature.status === "blocked" || unvalidatedPassedMarker,
|
|
6479
6513
|
reRun: repairableLoop || feature.status === "blocked" && (feature.loopState === void 0 || feature.loopState === "idle")
|
|
6480
6514
|
};
|
|
6481
6515
|
}
|
|
@@ -6539,7 +6573,8 @@ var init_mission_types = __esm({
|
|
|
6539
6573
|
implementing: [],
|
|
6540
6574
|
validating: [],
|
|
6541
6575
|
needs_fix: ["idle", "implementing"],
|
|
6542
|
-
|
|
6576
|
+
/* FNXC:Missions 2026-09-05-22:07: Explicit repair may return a fabricated unvalidated passed marker to idle (issue #3574). */
|
|
6577
|
+
passed: ["idle"],
|
|
6543
6578
|
blocked: ["idle", "implementing"]
|
|
6544
6579
|
};
|
|
6545
6580
|
VALIDATOR_RUN_STATUSES = ["running", "passed", "failed", "blocked", "error"];
|
|
@@ -13646,7 +13681,7 @@ function isBlockingFinding(finding, threshold) {
|
|
|
13646
13681
|
if (!finding.severity) return true;
|
|
13647
13682
|
return SEVERITY_RANK[finding.severity] >= SEVERITY_RANK[threshold];
|
|
13648
13683
|
}
|
|
13649
|
-
function applyReviewSeverityGate({ verdict, findings, threshold }) {
|
|
13684
|
+
function applyReviewSeverityGate({ verdict, findings, threshold, findingsUnreadable }) {
|
|
13650
13685
|
const all = findings ?? [];
|
|
13651
13686
|
const open7 = all.filter(isActionableReviewFinding);
|
|
13652
13687
|
const resolved = all.filter((finding) => !isActionableReviewFinding(finding));
|
|
@@ -13654,6 +13689,7 @@ function applyReviewSeverityGate({ verdict, findings, threshold }) {
|
|
|
13654
13689
|
const advisory = open7.filter((finding) => !isBlockingFinding(finding, threshold));
|
|
13655
13690
|
if (verdict !== "REVISE") return { verdict, downgraded: false, blocking, advisory, resolved };
|
|
13656
13691
|
if (blocking.length > 0) return { verdict, downgraded: false, blocking, advisory, resolved };
|
|
13692
|
+
if (findingsUnreadable) return { verdict, downgraded: false, blocking, advisory, resolved };
|
|
13657
13693
|
return { verdict: "APPROVE_WITH_NOTES", downgraded: true, blocking, advisory, resolved };
|
|
13658
13694
|
}
|
|
13659
13695
|
function formatFindingsByPriority(findings) {
|
|
@@ -18395,6 +18431,18 @@ function detectWorkflowDrift(ir2, pin) {
|
|
|
18395
18431
|
function defaultCodingWorkflowIr() {
|
|
18396
18432
|
return resolveDefaultWorkflowIr();
|
|
18397
18433
|
}
|
|
18434
|
+
async function prefetchWorkflowSelections(store, taskIds, cache8) {
|
|
18435
|
+
const missingIds = [...new Set(taskIds)].filter((id) => !cache8.has(id));
|
|
18436
|
+
if (missingIds.length === 0) return { batched: 0, singles: 0 };
|
|
18437
|
+
if (!store.getTaskWorkflowSelectionsAsync) return { batched: 0, singles: missingIds.length };
|
|
18438
|
+
try {
|
|
18439
|
+
const selections = await store.getTaskWorkflowSelectionsAsync(missingIds);
|
|
18440
|
+
for (const id of missingIds) cache8.set(id, selections.get(id));
|
|
18441
|
+
return { batched: 1, singles: 0 };
|
|
18442
|
+
} catch {
|
|
18443
|
+
return { batched: 0, singles: missingIds.length };
|
|
18444
|
+
}
|
|
18445
|
+
}
|
|
18398
18446
|
function resolveSeamPromptFromIr(ir2, seam) {
|
|
18399
18447
|
for (const node of ir2.nodes) {
|
|
18400
18448
|
if (node.kind !== "prompt") continue;
|
|
@@ -18613,9 +18661,9 @@ function resolveLifecycleColumns(ir2) {
|
|
|
18613
18661
|
archived: first(LIFECYCLE_ROLE_FLAGS.archived)
|
|
18614
18662
|
};
|
|
18615
18663
|
}
|
|
18616
|
-
async function resolveTaskLifecycleColumns(store, taskId, cache8) {
|
|
18664
|
+
async function resolveTaskLifecycleColumns(store, taskId, cache8, selectionCache) {
|
|
18617
18665
|
try {
|
|
18618
|
-
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache8);
|
|
18666
|
+
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache8, selectionCache);
|
|
18619
18667
|
return resolveLifecycleColumns(ir2);
|
|
18620
18668
|
} catch {
|
|
18621
18669
|
return void 0;
|
|
@@ -30611,18 +30659,33 @@ function isValidPluginParserId(id) {
|
|
|
30611
30659
|
const seg = /^[a-z0-9-]+$/;
|
|
30612
30660
|
return seg.test(parts[1]) && seg.test(parts[2]);
|
|
30613
30661
|
}
|
|
30614
|
-
function
|
|
30615
|
-
const steps = [];
|
|
30662
|
+
function matchStepHeadings(content) {
|
|
30616
30663
|
const stepRegex = /^###\s+Step\s+\d+[^:]*:\s*(.+)$/gm;
|
|
30617
|
-
const annotatedRegex = /^###\s+Step\s+\d+\s*\(depends:\s*([^)]*)\)\s*:\s*([^\n]+)$/;
|
|
30618
30664
|
const matches = [];
|
|
30619
30665
|
let match2;
|
|
30620
30666
|
while ((match2 = stepRegex.exec(content)) !== null) {
|
|
30621
30667
|
const heading = /^###\s+Step\s+(\d+)/.exec(match2[0]);
|
|
30622
|
-
if (heading)
|
|
30623
|
-
|
|
30624
|
-
|
|
30668
|
+
if (!heading) continue;
|
|
30669
|
+
const index3 = match2.index;
|
|
30670
|
+
const newline = content.indexOf("\n", index3);
|
|
30671
|
+
matches.push({
|
|
30672
|
+
headingNumber: Number(heading[1]),
|
|
30673
|
+
index: index3,
|
|
30674
|
+
headingLineEnd: newline === -1 ? content.length : newline,
|
|
30675
|
+
match: match2[0]
|
|
30676
|
+
});
|
|
30625
30677
|
}
|
|
30678
|
+
return matches;
|
|
30679
|
+
}
|
|
30680
|
+
function parseStepHeadings(content) {
|
|
30681
|
+
const steps = [];
|
|
30682
|
+
const annotatedRegex = /^###\s+Step\s+\d+\s*\(depends:\s*([^)]*)\)\s*:\s*([^\n]+)$/;
|
|
30683
|
+
const matches = matchStepHeadings(content).map((entry) => ({
|
|
30684
|
+
full: entry.match,
|
|
30685
|
+
name: /^###\s+Step\s+\d+[^:]*:\s*(.+)$/m.exec(entry.match)?.[1] ?? "",
|
|
30686
|
+
headingNumber: entry.headingNumber
|
|
30687
|
+
}));
|
|
30688
|
+
let match2;
|
|
30626
30689
|
const offset = resolveAuthoredStepHeadingOffset(matches.map((entry) => entry.headingNumber));
|
|
30627
30690
|
for (const { full, name: legacyName } of matches) {
|
|
30628
30691
|
if (!full.includes("(depends:")) {
|
|
@@ -61919,12 +61982,12 @@ async function listGlobalConfigurationRevisions(layer, configKind, configTarget,
|
|
|
61919
61982
|
});
|
|
61920
61983
|
}
|
|
61921
61984
|
async function listConfigurationRevisionsPage(handle2, params) {
|
|
61922
|
-
const
|
|
61985
|
+
const clampInteger3 = (value2, fallback2, minimum, maximum = Number.MAX_SAFE_INTEGER) => {
|
|
61923
61986
|
const integer3 = typeof value2 === "number" && Number.isFinite(value2) ? Math.trunc(value2) : fallback2;
|
|
61924
61987
|
return Math.min(maximum, Math.max(minimum, integer3));
|
|
61925
61988
|
};
|
|
61926
|
-
const limit2 =
|
|
61927
|
-
const offset =
|
|
61989
|
+
const limit2 = clampInteger3(params.limit, 100, 1, 500);
|
|
61990
|
+
const offset = clampInteger3(params.offset, 0, 0);
|
|
61928
61991
|
const rows = await handle2.select().from(schema_exports.project.configurationRevisions).where(and(
|
|
61929
61992
|
eq(schema_exports.project.configurationRevisions.projectId, params.projectId),
|
|
61930
61993
|
eq(schema_exports.project.configurationRevisions.configKind, params.configKind),
|
|
@@ -75219,8 +75282,8 @@ function appendAgentLogEntriesSync(taskDir, entries2) {
|
|
|
75219
75282
|
if (entries2.length === 0) return [];
|
|
75220
75283
|
const filePath = getAgentLogFilePath(taskDir);
|
|
75221
75284
|
mkdirSync5(dirname15(filePath), { recursive: true });
|
|
75222
|
-
const startingLineNo =
|
|
75223
|
-
const payload = entries2.map((entry) => serializeEntry(entry)).join("")
|
|
75285
|
+
const { lineCount: startingLineNo, endsWithNewline } = inspectAgentLogTail(filePath);
|
|
75286
|
+
const payload = `${startingLineNo > 0 && !endsWithNewline ? "\n" : ""}${entries2.map((entry) => serializeEntry(entry)).join("")}`;
|
|
75224
75287
|
appendFileSync(filePath, payload, "utf8");
|
|
75225
75288
|
return entries2.map((entry, index3) => materializeEntry(entry, startingLineNo + index3 + 1));
|
|
75226
75289
|
}
|
|
@@ -75256,6 +75319,7 @@ function readAllAgentLogEntries(taskDir, options = {}) {
|
|
|
75256
75319
|
}
|
|
75257
75320
|
const lines = content.split("\n");
|
|
75258
75321
|
const entries2 = [];
|
|
75322
|
+
const skippedLines = [];
|
|
75259
75323
|
for (let index3 = 0; index3 < lines.length; index3 += 1) {
|
|
75260
75324
|
const rawLine = lines[index3];
|
|
75261
75325
|
if (!rawLine) continue;
|
|
@@ -75277,9 +75341,18 @@ function readAllAgentLogEntries(taskDir, options = {}) {
|
|
|
75277
75341
|
}
|
|
75278
75342
|
entries2.push(entry);
|
|
75279
75343
|
} catch (error3) {
|
|
75280
|
-
|
|
75344
|
+
skippedLines.push({
|
|
75345
|
+
lineNo,
|
|
75346
|
+
message: error3 instanceof Error ? error3.message : String(error3)
|
|
75347
|
+
});
|
|
75281
75348
|
}
|
|
75282
75349
|
}
|
|
75350
|
+
const firstSkipped = skippedLines[0];
|
|
75351
|
+
if (firstSkipped) {
|
|
75352
|
+
log7.warn(
|
|
75353
|
+
`Skipped ${skippedLines.length} malformed JSONL line(s) in ${filePath}; first line ${firstSkipped.lineNo}: ${firstSkipped.message}`
|
|
75354
|
+
);
|
|
75355
|
+
}
|
|
75283
75356
|
return entries2;
|
|
75284
75357
|
}
|
|
75285
75358
|
function normalizeTimingMs(value2) {
|
|
@@ -75322,16 +75395,19 @@ function materializeEntry(entry, lineNo) {
|
|
|
75322
75395
|
sourceRef: buildAgentLogSourceRef(entry.taskId, lineNo)
|
|
75323
75396
|
};
|
|
75324
75397
|
}
|
|
75325
|
-
function
|
|
75398
|
+
function inspectAgentLogTail(filePath) {
|
|
75326
75399
|
if (!existsSync17(filePath)) {
|
|
75327
|
-
return 0;
|
|
75400
|
+
return { lineCount: 0, endsWithNewline: true };
|
|
75328
75401
|
}
|
|
75329
75402
|
const content = readFileSync8(filePath, "utf8");
|
|
75330
75403
|
if (content.length === 0) {
|
|
75331
|
-
return 0;
|
|
75404
|
+
return { lineCount: 0, endsWithNewline: true };
|
|
75332
75405
|
}
|
|
75333
75406
|
const lines = content.split("\n");
|
|
75334
|
-
return
|
|
75407
|
+
return {
|
|
75408
|
+
lineCount: lines.at(-1) === "" ? lines.length - 1 : lines.length,
|
|
75409
|
+
endsWithNewline: content.endsWith("\n")
|
|
75410
|
+
};
|
|
75335
75411
|
}
|
|
75336
75412
|
function pruneAgentLogFiles(tasksDir, retentionDays, scanTaskIds) {
|
|
75337
75413
|
if (!Number.isFinite(retentionDays) || retentionDays <= 0 || !existsSync17(tasksDir)) {
|
|
@@ -79121,6 +79197,10 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79121
79197
|
* FNXC:Missions 2026-07-11-12:35:
|
|
79122
79198
|
* A generated fix can also supersede itself once its own validator/loop evidence has passed.
|
|
79123
79199
|
* Reconciliation treats that as terminal evidence so a completed fix does not stay active only because its ancestor previously failed.
|
|
79200
|
+
*
|
|
79201
|
+
* FNXC:Missions 2026-09-05-22:07:
|
|
79202
|
+
* Superseding a generated fix means it is no longer needed, not that it passed validation.
|
|
79203
|
+
* Never stamp unearned validator evidence because that re-arms the reconciler's own trigger (issue #3574).
|
|
79124
79204
|
*/
|
|
79125
79205
|
reconcileSupersededGeneratedFixFeatures(sliceId) {
|
|
79126
79206
|
const features = this.listFeatures(sliceId);
|
|
@@ -79145,7 +79225,42 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79145
79225
|
ancestorPassedMemo.set(feature.id, passed);
|
|
79146
79226
|
return passed;
|
|
79147
79227
|
};
|
|
79148
|
-
const
|
|
79228
|
+
const isFabricatedMarker = (feature) => Boolean(
|
|
79229
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasPassedAncestor(feature)
|
|
79230
|
+
);
|
|
79231
|
+
const repairedFeatureIds = features.filter(isFabricatedMarker).map((feature) => feature.id);
|
|
79232
|
+
const repairedIds = new Set(repairedFeatureIds);
|
|
79233
|
+
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);
|
|
79234
|
+
if (repairedFeatureIds.length > 0) {
|
|
79235
|
+
this.db.transaction(() => {
|
|
79236
|
+
const hasCurrentPassedAncestor = (feature, seen = /* @__PURE__ */ new Set()) => {
|
|
79237
|
+
const sourceFeatureId = feature.generatedFromFeatureId;
|
|
79238
|
+
if (!sourceFeatureId || seen.has(sourceFeatureId)) return false;
|
|
79239
|
+
seen.add(sourceFeatureId);
|
|
79240
|
+
const sourceFeature = this.getFeature(sourceFeatureId);
|
|
79241
|
+
return featureHasPassed(sourceFeature) || (sourceFeature ? hasCurrentPassedAncestor(sourceFeature, seen) : false);
|
|
79242
|
+
};
|
|
79243
|
+
const isCurrentFabricatedMarker = (feature) => Boolean(
|
|
79244
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasCurrentPassedAncestor(feature)
|
|
79245
|
+
);
|
|
79246
|
+
for (const featureId of repairedFeatureIds) {
|
|
79247
|
+
const feature = this.getFeature(featureId);
|
|
79248
|
+
if (!feature || !isCurrentFabricatedMarker(feature)) continue;
|
|
79249
|
+
this.updateFeature(featureId, { status: "defined", taskId: void 0, loopState: "idle", lastValidatorStatus: void 0 });
|
|
79250
|
+
const slice = this.getSlice(feature.sliceId);
|
|
79251
|
+
const milestone = slice ? this.getMilestone(slice.milestoneId) : void 0;
|
|
79252
|
+
if (milestone) {
|
|
79253
|
+
this.logMissionEvent(milestone.missionId, "feature_status_changed", `Feature status changed from ${feature.status} to defined`, {
|
|
79254
|
+
featureId,
|
|
79255
|
+
field: "status",
|
|
79256
|
+
from: feature.status,
|
|
79257
|
+
to: "defined",
|
|
79258
|
+
source: "superseded-fix-marker-repair"
|
|
79259
|
+
});
|
|
79260
|
+
}
|
|
79261
|
+
}
|
|
79262
|
+
});
|
|
79263
|
+
}
|
|
79149
79264
|
if (supersededFeatureIds.length > 0) {
|
|
79150
79265
|
this.db.transaction(() => {
|
|
79151
79266
|
for (const featureId of supersededFeatureIds) {
|
|
@@ -79154,8 +79269,7 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79154
79269
|
this.updateFeature(featureId, {
|
|
79155
79270
|
status: "done",
|
|
79156
79271
|
taskId: void 0,
|
|
79157
|
-
loopState: "passed"
|
|
79158
|
-
lastValidatorStatus: "passed"
|
|
79272
|
+
loopState: "passed"
|
|
79159
79273
|
});
|
|
79160
79274
|
if (feature.taskId) {
|
|
79161
79275
|
this.db.prepare('UPDATE tasks SET missionId = NULL, sliceId = NULL WHERE id = ? AND "deletedAt" IS NULL').run(feature.taskId);
|
|
@@ -79165,7 +79279,9 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79165
79279
|
}
|
|
79166
79280
|
return {
|
|
79167
79281
|
supersededCount: supersededFeatureIds.length,
|
|
79168
|
-
featureIds: supersededFeatureIds
|
|
79282
|
+
featureIds: supersededFeatureIds,
|
|
79283
|
+
repairedCount: repairedFeatureIds.length,
|
|
79284
|
+
repairedFeatureIds
|
|
79169
79285
|
};
|
|
79170
79286
|
}
|
|
79171
79287
|
/**
|
|
@@ -82509,10 +82625,12 @@ var init_async_mission_store = __esm({
|
|
|
82509
82625
|
throw new RepairNotEligibleError(featureId, options.action);
|
|
82510
82626
|
}
|
|
82511
82627
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
82628
|
+
const hasUnvalidatedMarker = options.action === "clear" && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId;
|
|
82629
|
+
const appliesUnvalidatedMarker = hasUnvalidatedMarker && Boolean(feature.generatedFromFeatureId || feature.generatedFromRunId) && !feature.taskId && feature.status === "done";
|
|
82512
82630
|
const priorLoopState = feature.loopState;
|
|
82513
82631
|
const priorStatus = feature.status;
|
|
82514
82632
|
let groundTruthMetadata = {};
|
|
82515
|
-
if (options.action === "clear" && feature.status === "blocked") {
|
|
82633
|
+
if (options.action === "clear" && (feature.status === "blocked" || appliesUnvalidatedMarker)) {
|
|
82516
82634
|
const fence = options.groundTruth;
|
|
82517
82635
|
if (!fence || fence.featureId !== featureId || fence.taskId !== (feature.taskId ?? null)) {
|
|
82518
82636
|
throw new RepairGroundTruthStaleError(featureId);
|
|
@@ -82542,14 +82660,14 @@ var init_async_mission_store = __esm({
|
|
|
82542
82660
|
let updated;
|
|
82543
82661
|
let run2;
|
|
82544
82662
|
if (options.action === "clear") {
|
|
82545
|
-
const currentLoop = feature.loopState;
|
|
82546
|
-
const appliesLoop = currentLoop === "blocked" || currentLoop === "needs_fix";
|
|
82663
|
+
const currentLoop = feature.loopState ?? "idle";
|
|
82664
|
+
const appliesLoop = currentLoop === "blocked" || currentLoop === "needs_fix" || appliesUnvalidatedMarker;
|
|
82547
82665
|
const nextLoop = appliesLoop ? options.resolvedLoopState ?? "idle" : currentLoop;
|
|
82548
82666
|
if (appliesLoop && !FEATURE_LOOP_REPAIR_TRANSITIONS[currentLoop].includes(nextLoop)) {
|
|
82549
82667
|
throw new Error(`Invalid validation repair transition from '${currentLoop}' to '${nextLoop}'`);
|
|
82550
82668
|
}
|
|
82551
|
-
const appliesStatus = feature.status === "blocked";
|
|
82552
|
-
const nextStatus = appliesStatus ? options.resolvedStatus : feature.status;
|
|
82669
|
+
const appliesStatus = feature.status === "blocked" || appliesUnvalidatedMarker;
|
|
82670
|
+
const nextStatus = appliesUnvalidatedMarker ? "defined" : appliesStatus ? options.resolvedStatus : feature.status;
|
|
82553
82671
|
if (appliesStatus && (nextStatus !== "in-progress" && nextStatus !== "triaged" && nextStatus !== "defined")) {
|
|
82554
82672
|
throw new Error("Validation repair requires resolvedStatus of in-progress, triaged, or defined");
|
|
82555
82673
|
}
|
|
@@ -82561,13 +82679,13 @@ var init_async_mission_store = __esm({
|
|
|
82561
82679
|
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";
|
|
82562
82680
|
if (!matchesLane) throw new RepairGroundTruthStaleError(featureId);
|
|
82563
82681
|
}
|
|
82564
|
-
if (!appliesLoop && !appliesStatus) throw new RepairNotEligibleError(featureId, options.action);
|
|
82682
|
+
if (!appliesLoop && !appliesStatus && !hasUnvalidatedMarker) throw new RepairNotEligibleError(featureId, options.action);
|
|
82565
82683
|
updated = {
|
|
82566
82684
|
...feature,
|
|
82567
82685
|
loopState: nextLoop,
|
|
82568
82686
|
status: nextStatus,
|
|
82569
82687
|
implementationAttemptCount: 0,
|
|
82570
|
-
...feature.lastValidatorStatus === "blocked" || feature.lastValidatorStatus === "failed" ? { lastValidatorStatus: void 0 } : {},
|
|
82688
|
+
...feature.lastValidatorStatus === "blocked" || feature.lastValidatorStatus === "failed" || hasUnvalidatedMarker ? { lastValidatorStatus: void 0 } : {},
|
|
82571
82689
|
updatedAt: now
|
|
82572
82690
|
};
|
|
82573
82691
|
await updateFeature(tx, updated);
|
|
@@ -83035,10 +83153,60 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0,
|
|
|
83035
83153
|
const source = byId.get(sourceId);
|
|
83036
83154
|
return passed(source) || (source ? hasPassedAncestor(source, seen) : false);
|
|
83037
83155
|
};
|
|
83156
|
+
const isFabricatedMarker = (feature) => Boolean(
|
|
83157
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasPassedAncestor(feature)
|
|
83158
|
+
);
|
|
83159
|
+
const repairIds = new Set(features.filter(isFabricatedMarker).map((feature) => feature.id));
|
|
83038
83160
|
const ids = [];
|
|
83039
83161
|
for (const feature of features) {
|
|
83040
|
-
if (!feature.generatedFromFeatureId || !(passed(feature) || hasPassedAncestor(feature))) continue;
|
|
83041
|
-
if (feature.status !== "done" || feature.loopState !== "passed" || feature.
|
|
83162
|
+
if (repairIds.has(feature.id) || !feature.generatedFromFeatureId || !(passed(feature) || hasPassedAncestor(feature))) continue;
|
|
83163
|
+
if (feature.status !== "done" || feature.loopState !== "passed" || feature.taskId) ids.push(feature.id);
|
|
83164
|
+
}
|
|
83165
|
+
const repairedFeatureIds = [];
|
|
83166
|
+
if (repairIds.size > 0) {
|
|
83167
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83168
|
+
const repaired = await this.layer.transactionImmediate(async (tx) => {
|
|
83169
|
+
const locked = await tx.select({ id: project_exports.missionFeatures.id }).from(project_exports.missionFeatures).where(inArray(project_exports.missionFeatures.id, [...repairIds])).for("update");
|
|
83170
|
+
const preImages = locked.length > 0 ? await listFeaturesByIds(tx, locked.map((row) => row.id)) : [];
|
|
83171
|
+
const currentById = new Map(preImages.map((feature) => [feature.id, feature]));
|
|
83172
|
+
let missingAncestorIds = [...new Set(preImages.map((feature) => feature.generatedFromFeatureId).filter((id) => Boolean(id) && !currentById.has(id)))];
|
|
83173
|
+
while (missingAncestorIds.length > 0) {
|
|
83174
|
+
const lockedAncestors = await tx.select({ id: project_exports.missionFeatures.id }).from(project_exports.missionFeatures).where(inArray(project_exports.missionFeatures.id, missingAncestorIds)).for("update");
|
|
83175
|
+
const ancestors = lockedAncestors.length > 0 ? await listFeaturesByIds(tx, lockedAncestors.map((row) => row.id)) : [];
|
|
83176
|
+
for (const ancestor of ancestors) currentById.set(ancestor.id, ancestor);
|
|
83177
|
+
missingAncestorIds = [...new Set(ancestors.map((ancestor) => ancestor.generatedFromFeatureId).filter((id) => Boolean(id) && !currentById.has(id)))];
|
|
83178
|
+
}
|
|
83179
|
+
const hasCurrentPassedAncestor = (feature, seen = /* @__PURE__ */ new Set()) => {
|
|
83180
|
+
const sourceId = feature.generatedFromFeatureId;
|
|
83181
|
+
if (!sourceId || seen.has(sourceId)) return false;
|
|
83182
|
+
seen.add(sourceId);
|
|
83183
|
+
const source = currentById.get(sourceId);
|
|
83184
|
+
return passed(source) || (source ? hasCurrentPassedAncestor(source, seen) : false);
|
|
83185
|
+
};
|
|
83186
|
+
const changed = preImages.filter((feature) => Boolean(
|
|
83187
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasCurrentPassedAncestor(feature)
|
|
83188
|
+
));
|
|
83189
|
+
if (changed.length === 0) return { events: [], features: [] };
|
|
83190
|
+
await tx.update(project_exports.missionFeatures).set({
|
|
83191
|
+
status: "defined",
|
|
83192
|
+
taskId: null,
|
|
83193
|
+
loopState: "idle",
|
|
83194
|
+
lastValidatorStatus: null,
|
|
83195
|
+
updatedAt: now
|
|
83196
|
+
}).where(inArray(project_exports.missionFeatures.id, changed.map((feature) => feature.id)));
|
|
83197
|
+
let seq = await getMaxEventSeq(tx);
|
|
83198
|
+
const events = [];
|
|
83199
|
+
for (const feature of changed) {
|
|
83200
|
+
const event = await this.recordFeatureStatusChange(tx, feature, "defined", { type: "system", id: "mission-store", source: "superseded-fix-marker-repair" }, void 0, ++seq);
|
|
83201
|
+
if (event) events.push(event);
|
|
83202
|
+
}
|
|
83203
|
+
return { events, features: changed };
|
|
83204
|
+
});
|
|
83205
|
+
for (const event of repaired.events) this.emit("mission:event", event);
|
|
83206
|
+
for (const feature of repaired.features) {
|
|
83207
|
+
this.emit("feature:updated", { ...feature, status: "defined", taskId: void 0, loopState: "idle", lastValidatorStatus: void 0, updatedAt: now });
|
|
83208
|
+
repairedFeatureIds.push(feature.id);
|
|
83209
|
+
}
|
|
83042
83210
|
}
|
|
83043
83211
|
if (ids.length > 0) {
|
|
83044
83212
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -83046,13 +83214,12 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0,
|
|
|
83046
83214
|
const locked = await tx.select({ id: project_exports.missionFeatures.id }).from(project_exports.missionFeatures).where(inArray(project_exports.missionFeatures.id, ids)).for("update");
|
|
83047
83215
|
const lockedIds = locked.map((row) => row.id);
|
|
83048
83216
|
const preImages = lockedIds.length > 0 ? await listFeaturesByIds(tx, lockedIds) : [];
|
|
83049
|
-
const changed = preImages.filter((feature) => feature.status !== "done" || feature.loopState !== "passed" || feature.
|
|
83217
|
+
const changed = preImages.filter((feature) => feature.status !== "done" || feature.loopState !== "passed" || feature.taskId);
|
|
83050
83218
|
if (changed.length === 0) return { events: [], updatedFeatures: [] };
|
|
83051
83219
|
await tx.update(project_exports.missionFeatures).set({
|
|
83052
83220
|
status: "done",
|
|
83053
83221
|
taskId: null,
|
|
83054
83222
|
loopState: "passed",
|
|
83055
|
-
lastValidatorStatus: "passed",
|
|
83056
83223
|
updatedAt: now
|
|
83057
83224
|
}).where(inArray(project_exports.missionFeatures.id, changed.map((feature) => feature.id)));
|
|
83058
83225
|
let seq = await getMaxEventSeq(tx);
|
|
@@ -83067,13 +83234,14 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0,
|
|
|
83067
83234
|
});
|
|
83068
83235
|
for (const event of events) this.emit("mission:event", event);
|
|
83069
83236
|
for (const feature of updatedFeatures) {
|
|
83070
|
-
const updated = { ...feature, status: "done", taskId: void 0, loopState: "passed",
|
|
83237
|
+
const updated = { ...feature, status: "done", taskId: void 0, loopState: "passed", updatedAt: now };
|
|
83071
83238
|
this.emit("feature:updated", updated);
|
|
83072
83239
|
if (feature.taskId) await clearTaskMissionLinkage(this.db, feature.taskId);
|
|
83073
83240
|
}
|
|
83074
83241
|
if (updatedFeatures.length > 0) await this.recomputeSliceStatus(sliceId);
|
|
83075
83242
|
}
|
|
83076
|
-
|
|
83243
|
+
if (repairedFeatureIds.length > 0) await this.recomputeSliceStatus(sliceId);
|
|
83244
|
+
return { supersededCount: ids.length, featureIds: ids, repairedCount: repairedFeatureIds.length, repairedFeatureIds };
|
|
83077
83245
|
}
|
|
83078
83246
|
async transitionLoopState(featureId, newState) {
|
|
83079
83247
|
const feature = await getFeature(this.db, featureId);
|
|
@@ -88705,19 +88873,81 @@ async function archiveAllDoneImpl(store, options) {
|
|
|
88705
88873
|
for (const column of completeColumns) {
|
|
88706
88874
|
for (const task of await store.listTasks({ slim: true, column })) doneById.set(task.id, task);
|
|
88707
88875
|
}
|
|
88708
|
-
|
|
88709
|
-
|
|
88710
|
-
|
|
88876
|
+
if (doneById.size === 0) return { archived: [], skipped: [] };
|
|
88877
|
+
const archivedColumns = await resolveArchivedLanes(store).catch(() => void 0) ?? new Set(LEGACY_ARCHIVE_LANES);
|
|
88878
|
+
const liveTasks = (await store.listTasks({ slim: true, includeArchived: false })).filter((task) => !task.deletedAt && !archivedColumns.has(task.column));
|
|
88879
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
88880
|
+
for (const task of liveTasks) {
|
|
88881
|
+
if (!task.sourceParentTaskId) continue;
|
|
88882
|
+
const children = childrenByParentId.get(task.sourceParentTaskId) ?? [];
|
|
88883
|
+
children.push(task.id);
|
|
88884
|
+
childrenByParentId.set(task.sourceParentTaskId, children);
|
|
88885
|
+
}
|
|
88886
|
+
for (const children of childrenByParentId.values()) children.sort();
|
|
88887
|
+
const bypassLineageGate = options?.removeLineageReferences === true;
|
|
88888
|
+
const archivableIds = new Set(doneById.keys());
|
|
88889
|
+
const skipped = [];
|
|
88890
|
+
if (!bypassLineageGate) {
|
|
88891
|
+
let firstRemovalWave = true;
|
|
88892
|
+
while (true) {
|
|
88893
|
+
const removals = [...archivableIds].filter(
|
|
88894
|
+
(id) => (childrenByParentId.get(id) ?? []).some((childId) => !archivableIds.has(childId))
|
|
88895
|
+
).sort();
|
|
88896
|
+
if (removals.length === 0) break;
|
|
88897
|
+
for (const id of removals) {
|
|
88898
|
+
const blockers = (childrenByParentId.get(id) ?? []).filter((childId) => !archivableIds.has(childId));
|
|
88899
|
+
skipped.push({
|
|
88900
|
+
id,
|
|
88901
|
+
reason: firstRemovalWave ? "open-lineage-children" : "blocked-by-unarchived-batch-member",
|
|
88902
|
+
blockers,
|
|
88903
|
+
message: firstRemovalWave ? `Live lineage children prevent archiving ${id}.` : `Unarchived batch members prevent archiving ${id}.`
|
|
88904
|
+
});
|
|
88905
|
+
archivableIds.delete(id);
|
|
88906
|
+
}
|
|
88907
|
+
firstRemovalWave = false;
|
|
88908
|
+
}
|
|
88909
|
+
}
|
|
88910
|
+
const archived = [];
|
|
88911
|
+
const remaining = new Set(archivableIds);
|
|
88912
|
+
const archivedIds = /* @__PURE__ */ new Set();
|
|
88913
|
+
const archiveWave = async (ids) => {
|
|
88914
|
+
const settled = await Promise.allSettled(ids.map((id) => store.archiveTask(id, {
|
|
88915
|
+
cleanup: true,
|
|
88916
|
+
removeLineageReferences: options?.removeLineageReferences
|
|
88917
|
+
})));
|
|
88918
|
+
for (const [index3, result2] of settled.entries()) {
|
|
88919
|
+
const id = ids[index3];
|
|
88920
|
+
if (result2.status === "fulfilled") {
|
|
88921
|
+
archived.push(result2.value);
|
|
88922
|
+
archivedIds.add(id);
|
|
88923
|
+
} else {
|
|
88924
|
+
const error3 = result2.reason;
|
|
88925
|
+
const blockers = error3 instanceof TaskHasLineageChildrenError ? error3.childIds : error3 instanceof TaskHasDependentsError ? error3.dependentIds : [];
|
|
88926
|
+
skipped.push({ id, reason: "archive-failed", blockers, message: error3 instanceof Error ? error3.message : String(error3) });
|
|
88927
|
+
}
|
|
88928
|
+
remaining.delete(id);
|
|
88929
|
+
}
|
|
88930
|
+
};
|
|
88931
|
+
while (remaining.size > 0) {
|
|
88932
|
+
const wave = [...remaining].filter(
|
|
88933
|
+
(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))
|
|
88934
|
+
).sort();
|
|
88935
|
+
if (wave.length > 0) {
|
|
88936
|
+
await archiveWave(wave);
|
|
88937
|
+
continue;
|
|
88938
|
+
}
|
|
88939
|
+
if (bypassLineageGate) {
|
|
88940
|
+
await archiveWave([...remaining].sort());
|
|
88941
|
+
} else {
|
|
88942
|
+
for (const id of [...remaining].sort()) {
|
|
88943
|
+
const blockers = (childrenByParentId.get(id) ?? []).filter((childId) => remaining.has(childId) || !archivedIds.has(childId));
|
|
88944
|
+
skipped.push({ id, reason: "blocked-by-unarchived-batch-member", blockers, message: `Unarchived batch members prevent archiving ${id}.` });
|
|
88945
|
+
remaining.delete(id);
|
|
88946
|
+
}
|
|
88947
|
+
}
|
|
88711
88948
|
}
|
|
88712
|
-
|
|
88713
|
-
|
|
88714
|
-
(task) => store.archiveTask(task.id, {
|
|
88715
|
-
cleanup: true,
|
|
88716
|
-
removeLineageReferences: options?.removeLineageReferences
|
|
88717
|
-
})
|
|
88718
|
-
)
|
|
88719
|
-
);
|
|
88720
|
-
return archivedTasks3;
|
|
88949
|
+
skipped.sort((left, right) => left.id.localeCompare(right.id));
|
|
88950
|
+
return { archived, skipped };
|
|
88721
88951
|
}
|
|
88722
88952
|
async function resolveUnarchiveTargetColumnImpl(store, preArchiveColumn, taskId) {
|
|
88723
88953
|
const lifecycle2 = taskId ? await resolveTaskLifecycleColumns(store, taskId) : void 0;
|
|
@@ -89181,6 +89411,7 @@ var init_task_artifacts_ops = __esm({
|
|
|
89181
89411
|
init_types();
|
|
89182
89412
|
init_store();
|
|
89183
89413
|
init_project_lane_vocabulary();
|
|
89414
|
+
init_errors3();
|
|
89184
89415
|
LEGACY_ARCHIVE_LANES = ["archived"];
|
|
89185
89416
|
}
|
|
89186
89417
|
});
|
|
@@ -107052,18 +107283,18 @@ function hasFreshAgentLogActivitySinceTaskUpdate(store, task, now, reviewColumns
|
|
|
107052
107283
|
}
|
|
107053
107284
|
return Math.max(0, now - latestAgentLogMs) < DEFAULT_STALE_MERGING_MIN_AGE_MS;
|
|
107054
107285
|
}
|
|
107055
|
-
async function resolveHoldColumnForTask(store, taskId, cache8) {
|
|
107286
|
+
async function resolveHoldColumnForTask(store, taskId, cache8, selectionCache) {
|
|
107056
107287
|
try {
|
|
107057
|
-
const lifecycle2 = resolveLifecycleColumns(await resolveWorkflowIrForTask(store, taskId, cache8));
|
|
107288
|
+
const lifecycle2 = resolveLifecycleColumns(await resolveWorkflowIrForTask(store, taskId, cache8, selectionCache));
|
|
107058
107289
|
return lifecycle2?.hold ?? "todo";
|
|
107059
107290
|
} catch {
|
|
107060
107291
|
return "todo";
|
|
107061
107292
|
}
|
|
107062
107293
|
}
|
|
107063
|
-
async function resolveReviewColumnsForTask(store, taskId, cache8) {
|
|
107294
|
+
async function resolveReviewColumnsForTask(store, taskId, cache8, selectionCache) {
|
|
107064
107295
|
const columns = /* @__PURE__ */ new Set(["in-review"]);
|
|
107065
107296
|
try {
|
|
107066
|
-
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache8);
|
|
107297
|
+
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache8, selectionCache);
|
|
107067
107298
|
if (ir2) for (const id of resolveReviewColumns(ir2)) columns.add(id);
|
|
107068
107299
|
} catch {
|
|
107069
107300
|
}
|
|
@@ -107184,6 +107415,12 @@ async function listTasksImpl(store, options) {
|
|
|
107184
107415
|
const settings = await store.getSettingsFast();
|
|
107185
107416
|
const mergeQueuedTaskIds = await store.getMergeQueuedTaskIdsAsync();
|
|
107186
107417
|
const listPassIrCache = /* @__PURE__ */ new Map();
|
|
107418
|
+
const listPassSelectionCache = options?.selectionCache ?? /* @__PURE__ */ new Map();
|
|
107419
|
+
const listSelectionReads = await prefetchWorkflowSelections(store, filteredRows.map((row) => String(row.id)), listPassSelectionCache);
|
|
107420
|
+
if (options?.selectionReadTally) {
|
|
107421
|
+
options.selectionReadTally.batched += listSelectionReads.batched;
|
|
107422
|
+
options.selectionReadTally.singles += listSelectionReads.singles;
|
|
107423
|
+
}
|
|
107187
107424
|
const staleThresholds = {
|
|
107188
107425
|
inProgressWarningMs: settings.staleInProgressWarningMs,
|
|
107189
107426
|
inProgressCriticalMs: settings.staleInProgressCriticalMs,
|
|
@@ -107194,7 +107431,7 @@ async function listTasksImpl(store, options) {
|
|
|
107194
107431
|
const row = store.pgRowToTaskRow(pgRow);
|
|
107195
107432
|
const task = store.rowToTask(row);
|
|
107196
107433
|
const isMergeQueued = mergeQueuedTaskIds.has(task.id);
|
|
107197
|
-
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, listPassIrCache);
|
|
107434
|
+
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, listPassIrCache, listPassSelectionCache);
|
|
107198
107435
|
const hasFreshAgentLogActivity = hasFreshAgentLogActivitySinceTaskUpdate(store, task, now, reviewColumnsForRow);
|
|
107199
107436
|
const executingTaskIds = hasFreshAgentLogActivity ? /* @__PURE__ */ new Set([task.id]) : void 0;
|
|
107200
107437
|
task.inReviewStall = isMergeQueued ? void 0 : getInReviewStallReason(task, {
|
|
@@ -107226,7 +107463,7 @@ async function listTasksImpl(store, options) {
|
|
|
107226
107463
|
thresholdMs: settings.stalePausedTodoThresholdMs,
|
|
107227
107464
|
// Paused-only (the signal is a no-op otherwise), sharing the list-pass
|
|
107228
107465
|
// IR cache so one workflow is read once per pass, not once per card.
|
|
107229
|
-
holdColumn: task.paused === true ? await resolveHoldColumnForTask(store, task.id, listPassIrCache) : void 0,
|
|
107466
|
+
holdColumn: task.paused === true ? await resolveHoldColumnForTask(store, task.id, listPassIrCache, listPassSelectionCache) : void 0,
|
|
107230
107467
|
engineActiveSinceMs: settings.engineActiveSinceMs,
|
|
107231
107468
|
engineActivationGraceMs: settings.engineActivationGraceMs
|
|
107232
107469
|
});
|
|
@@ -107236,7 +107473,7 @@ async function listTasksImpl(store, options) {
|
|
|
107236
107473
|
thresholds: staleThresholds,
|
|
107237
107474
|
engineActiveSinceMs: settings.engineActiveSinceMs,
|
|
107238
107475
|
engineActivationGraceMs: settings.engineActivationGraceMs,
|
|
107239
|
-
lifecycle: await resolveTaskLifecycleColumns(store, task.id, listPassIrCache)
|
|
107476
|
+
lifecycle: await resolveTaskLifecycleColumns(store, task.id, listPassIrCache, listPassSelectionCache)
|
|
107240
107477
|
});
|
|
107241
107478
|
} catch (err) {
|
|
107242
107479
|
if (!(err instanceof RangeError)) throw err;
|
|
@@ -107315,12 +107552,14 @@ async function listTasksModifiedSinceImpl(store, since, limit2, opts) {
|
|
|
107315
107552
|
const lifecycleByTaskId = /* @__PURE__ */ new Map();
|
|
107316
107553
|
{
|
|
107317
107554
|
const irCache = /* @__PURE__ */ new Map();
|
|
107555
|
+
const selectionCache = /* @__PURE__ */ new Map();
|
|
107556
|
+
await prefetchWorkflowSelections(store, pageRows.map((row) => row.id), selectionCache);
|
|
107318
107557
|
for (const pgRow of pageRows) {
|
|
107319
107558
|
const row = store.pgRowToTaskRow(pgRow);
|
|
107320
|
-
reviewColumnsByTaskId.set(row.id, await resolveReviewColumnsForTask(store, row.id, irCache));
|
|
107321
|
-
lifecycleByTaskId.set(row.id, await resolveTaskLifecycleColumns(store, row.id, irCache));
|
|
107559
|
+
reviewColumnsByTaskId.set(row.id, await resolveReviewColumnsForTask(store, row.id, irCache, selectionCache));
|
|
107560
|
+
lifecycleByTaskId.set(row.id, await resolveTaskLifecycleColumns(store, row.id, irCache, selectionCache));
|
|
107322
107561
|
if (store.rowToTask(row).paused !== true) continue;
|
|
107323
|
-
holdColumnByTaskId.set(row.id, await resolveHoldColumnForTask(store, row.id, irCache));
|
|
107562
|
+
holdColumnByTaskId.set(row.id, await resolveHoldColumnForTask(store, row.id, irCache, selectionCache));
|
|
107324
107563
|
}
|
|
107325
107564
|
}
|
|
107326
107565
|
const tasks2 = pageRows.map((pgRow) => {
|
|
@@ -107438,10 +107677,12 @@ async function searchTasksImpl(store, query, options) {
|
|
|
107438
107677
|
const settings = await store.getSettingsFast();
|
|
107439
107678
|
const mergeQueuedTaskIds = await store.getMergeQueuedTaskIdsAsync();
|
|
107440
107679
|
const searchPassIrCache = /* @__PURE__ */ new Map();
|
|
107680
|
+
const searchPassSelectionCache = /* @__PURE__ */ new Map();
|
|
107681
|
+
await prefetchWorkflowSelections(store, pgRows.map((row) => String(row.id)), searchPassSelectionCache);
|
|
107441
107682
|
const tasks2 = await Promise.all(pgRows.map(async (pgRow) => {
|
|
107442
107683
|
const task = store.rowToTask(store.pgRowToTaskRow(pgRow));
|
|
107443
107684
|
const isMergeQueued = mergeQueuedTaskIds.has(task.id);
|
|
107444
|
-
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, searchPassIrCache);
|
|
107685
|
+
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, searchPassIrCache, searchPassSelectionCache);
|
|
107445
107686
|
const hasFreshAgentLogActivity = hasFreshAgentLogActivitySinceTaskUpdate(store, task, now, reviewColumnsForRow);
|
|
107446
107687
|
const executingTaskIds = hasFreshAgentLogActivity ? /* @__PURE__ */ new Set([task.id]) : void 0;
|
|
107447
107688
|
task.inReviewStall = isMergeQueued ? void 0 : getInReviewStallReason(task, {
|
|
@@ -108681,6 +108922,7 @@ var init_store = __esm({
|
|
|
108681
108922
|
init_async_self_healing();
|
|
108682
108923
|
init_task_merge();
|
|
108683
108924
|
init_required_pre_merge_steps();
|
|
108925
|
+
init_pre_merge_approval();
|
|
108684
108926
|
init_logger();
|
|
108685
108927
|
init_project_root_guard();
|
|
108686
108928
|
init_agent_store();
|
|
@@ -110446,29 +110688,35 @@ var init_store = __esm({
|
|
|
110446
110688
|
const results = task.workflowStepResults ?? [];
|
|
110447
110689
|
const failedTarget = getLatestFailedPreMergeReviewStep(task);
|
|
110448
110690
|
const reviewIrForBypass = failedTarget ? void 0 : await resolveWorkflowIrForTask(this, task.id);
|
|
110449
|
-
const
|
|
110450
|
-
|
|
110691
|
+
const requiredForBypass = reviewIrForBypass ? [...resolveRequiredPreMergeStepIds(reviewIrForBypass, task.enabledWorkflowSteps, task)] : [];
|
|
110692
|
+
const absentStepId = requiredForBypass.find((workflowStepId) => !results.some((result2) => result2.workflowStepId === workflowStepId));
|
|
110693
|
+
const unapprovedTarget = !failedTarget && !absentStepId && requiredForBypass.length > 0 ? (() => {
|
|
110694
|
+
const blocked = evaluatePreMergeApprovals(task, { requiredPreMergeStepIds: new Set(requiredForBypass) }).find((approval) => approval.state !== "approved");
|
|
110695
|
+
return blocked ? results.find((result2) => result2.workflowStepId === blocked.workflowStepId) : void 0;
|
|
110696
|
+
})() : void 0;
|
|
110697
|
+
if (!failedTarget && !absentStepId && !unapprovedTarget) {
|
|
110451
110698
|
throw new Error(`Cannot bypass review lane for ${id}: no failed pre-merge review step found`);
|
|
110452
110699
|
}
|
|
110453
|
-
const target = failedTarget ?? {
|
|
110700
|
+
const target = failedTarget ?? unapprovedTarget ?? {
|
|
110454
110701
|
workflowStepId: absentStepId,
|
|
110455
110702
|
workflowStepName: absentStepId,
|
|
110456
110703
|
phase: "pre-merge",
|
|
110457
110704
|
status: "absent"
|
|
110458
110705
|
};
|
|
110459
|
-
const
|
|
110460
|
-
|
|
110706
|
+
const rowTarget = failedTarget ?? unapprovedTarget;
|
|
110707
|
+
const targetIndex = rowTarget ? results.indexOf(rowTarget) : -1;
|
|
110708
|
+
if (rowTarget && targetIndex === -1) {
|
|
110461
110709
|
throw new Error(`Cannot bypass review lane for ${id}: failed step result not found`);
|
|
110462
110710
|
}
|
|
110463
110711
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
110464
|
-
const bypassed =
|
|
110465
|
-
...
|
|
110712
|
+
const bypassed = rowTarget ? {
|
|
110713
|
+
...rowTarget,
|
|
110466
110714
|
status: "skipped",
|
|
110467
110715
|
bypassedBy: actor,
|
|
110468
110716
|
bypassedAt: now,
|
|
110469
110717
|
bypassReason: reason,
|
|
110470
|
-
bypassedFromStatus:
|
|
110471
|
-
bypassedFromVerdict:
|
|
110718
|
+
bypassedFromStatus: rowTarget.remediationArchivedFromStatus ?? rowTarget.status,
|
|
110719
|
+
bypassedFromVerdict: rowTarget.verdict ?? rowTarget.priorAttempts?.[0]?.verdict
|
|
110472
110720
|
} : {
|
|
110473
110721
|
workflowStepId: absentStepId,
|
|
110474
110722
|
workflowStepName: absentStepId,
|
|
@@ -110502,8 +110750,8 @@ var init_store = __esm({
|
|
|
110502
110750
|
metadata: {
|
|
110503
110751
|
workflowStepId: target.workflowStepId,
|
|
110504
110752
|
workflowStepName: target.workflowStepName,
|
|
110505
|
-
bypassedFromStatus:
|
|
110506
|
-
bypassedFromVerdict:
|
|
110753
|
+
bypassedFromStatus: bypassed.bypassedFromStatus,
|
|
110754
|
+
bypassedFromVerdict: bypassed.bypassedFromVerdict ?? null,
|
|
110507
110755
|
reason
|
|
110508
110756
|
}
|
|
110509
110757
|
});
|
|
@@ -110565,9 +110813,8 @@ var init_store = __esm({
|
|
|
110565
110813
|
);
|
|
110566
110814
|
}
|
|
110567
110815
|
if (target.status !== "pending") {
|
|
110568
|
-
|
|
110569
|
-
|
|
110570
|
-
);
|
|
110816
|
+
const archivedFailure = target.remediationArchivedAt != null && (target.remediationArchivedFromStatus === "failed" || target.remediationArchivedFromStatus === "advisory_failure") && !target.bypassedBy && !target.supersededAt;
|
|
110817
|
+
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)` : ""}`);
|
|
110571
110818
|
}
|
|
110572
110819
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
110573
110820
|
const resumed = {
|
|
@@ -141075,6 +141322,7 @@ __export(src_exports, {
|
|
|
141075
141322
|
CLOUD_TICKET_QUERY: () => CLOUD_TICKET_QUERY,
|
|
141076
141323
|
CODE_REVIEW_BLOCKING_SEVERITY_SETTING_ID: () => CODE_REVIEW_BLOCKING_SEVERITY_SETTING_ID,
|
|
141077
141324
|
CODE_REVIEW_MAX_REVISIONS_SETTING_ID: () => CODE_REVIEW_MAX_REVISIONS_SETTING_ID,
|
|
141325
|
+
COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC: () => COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC,
|
|
141078
141326
|
COLOR_THEMES: () => COLOR_THEMES,
|
|
141079
141327
|
COLUMNS: () => COLUMNS,
|
|
141080
141328
|
COLUMN_DESCRIPTIONS: () => COLUMN_DESCRIPTIONS,
|
|
@@ -141477,6 +141725,7 @@ __export(src_exports, {
|
|
|
141477
141725
|
SLICE_PLAN_STATES: () => SLICE_PLAN_STATES,
|
|
141478
141726
|
SLICE_STATUSES: () => SLICE_STATUSES,
|
|
141479
141727
|
SPEC_LOCK_PARSER_VERSION: () => SPEC_LOCK_PARSER_VERSION,
|
|
141728
|
+
STALE_CONTENT_APPROVAL_BLOCKER: () => STALE_CONTENT_APPROVAL_BLOCKER,
|
|
141480
141729
|
STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS: () => STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS,
|
|
141481
141730
|
STALLED_REVIEW_INVALID_TRANSITION_PATTERN: () => STALLED_REVIEW_INVALID_TRANSITION_PATTERN,
|
|
141482
141731
|
STALLED_REVIEW_INVALID_TRANSITION_THRESHOLD: () => STALLED_REVIEW_INVALID_TRANSITION_THRESHOLD,
|
|
@@ -142206,6 +142455,7 @@ __export(src_exports, {
|
|
|
142206
142455
|
isSqliteLockError: () => isSqliteLockError,
|
|
142207
142456
|
isSqliteMigrationComplete: () => isSqliteMigrationComplete,
|
|
142208
142457
|
isStaleBlockedByBlocker: () => isStaleBlockedByBlocker,
|
|
142458
|
+
isStaleContentApprovalBlocker: () => isStaleContentApprovalBlocker,
|
|
142209
142459
|
isStoredAuthCredential: () => isStoredAuthCredential,
|
|
142210
142460
|
isStrictDescendantPath: () => isStrictDescendantPath,
|
|
142211
142461
|
isTailscaleCgnatIpv4: () => isTailscaleCgnatIpv4,
|
|
@@ -142285,6 +142535,7 @@ __export(src_exports, {
|
|
|
142285
142535
|
mapRoleToCapability: () => mapRoleToCapability,
|
|
142286
142536
|
markDraftSummarized: () => markDraftSummarized,
|
|
142287
142537
|
markdownHtmlCommentUnits: () => markdownHtmlCommentUnits,
|
|
142538
|
+
matchStepHeadings: () => matchStepHeadings,
|
|
142288
142539
|
matchesFrontendUxPath: () => matchesFrontendUxPath,
|
|
142289
142540
|
matchesPatchnodeQuery: () => matchesPatchnodeQuery,
|
|
142290
142541
|
materializeMcpServerSecrets: () => materializeMcpServerSecrets,
|
|
@@ -142398,6 +142649,7 @@ __export(src_exports, {
|
|
|
142398
142649
|
postMergeOptionalGroupNode: () => postMergeOptionalGroupNode,
|
|
142399
142650
|
postMergeVerificationOptionalGroupNode: () => postMergeVerificationOptionalGroupNode,
|
|
142400
142651
|
postgresSchema: () => schema_exports,
|
|
142652
|
+
prefetchWorkflowSelections: () => prefetchWorkflowSelections,
|
|
142401
142653
|
prepareAgentCompaniesImport: () => prepareAgentCompaniesImport,
|
|
142402
142654
|
pricingAsOf: () => pricingAsOf,
|
|
142403
142655
|
probeGitCliStatus: () => probeGitCliStatus,
|
|
@@ -142509,6 +142761,7 @@ __export(src_exports, {
|
|
|
142509
142761
|
resolveBackupSettingsMigrationConflict: () => resolveBackupSettingsMigrationConflict,
|
|
142510
142762
|
resolveCapacityPoolId: () => resolveCapacityPoolId,
|
|
142511
142763
|
resolveClaudeCodeClientVersion: () => resolveClaudeCodeClientVersion,
|
|
142764
|
+
resolveCollateralArchivedReviewGate: () => resolveCollateralArchivedReviewGate,
|
|
142512
142765
|
resolveColumnAdjacency: () => resolveColumnAdjacency,
|
|
142513
142766
|
resolveColumnAgentBinding: () => resolveColumnAgentBinding,
|
|
142514
142767
|
resolveColumnCapacity: () => resolveColumnCapacity,
|
|
@@ -154389,7 +154642,10 @@ async function runHoldReleaseSweep(store, deps) {
|
|
|
154389
154642
|
const settings = await store.getSettings();
|
|
154390
154643
|
if (expired()) return logPreambleTruncation(0);
|
|
154391
154644
|
counters.tasks += 1;
|
|
154392
|
-
const
|
|
154645
|
+
const selectionReadTally = { batched: 0, singles: 0 };
|
|
154646
|
+
const allTasks = await store.listTasks({ includeArchived: false, selectionCache, selectionReadTally });
|
|
154647
|
+
counters.batchSelections += selectionReadTally.batched;
|
|
154648
|
+
counters.selections += selectionReadTally.singles;
|
|
154393
154649
|
if (expired()) return logPreambleTruncation(allTasks.length);
|
|
154394
154650
|
const effectiveWorkflowIdByTask = /* @__PURE__ */ new Map();
|
|
154395
154651
|
const missingIds = [...new Set(allTasks.map((task) => task.id))].filter((id) => !selectionCache.has(id));
|
|
@@ -180117,7 +180373,7 @@ function extractMarkdownSection(document2, headingName) {
|
|
|
180117
180373
|
return document2.slice(start, end).trim();
|
|
180118
180374
|
}
|
|
180119
180375
|
function compactTaskPromptStepsSection(section) {
|
|
180120
|
-
const stepTitles =
|
|
180376
|
+
const stepTitles = matchStepHeadings(section).map(({ index: index3, headingLineEnd }) => section.slice(index3, headingLineEnd).trim());
|
|
180121
180377
|
if (stepTitles.length === 0) {
|
|
180122
180378
|
return section.trim();
|
|
180123
180379
|
}
|
|
@@ -189473,7 +189729,7 @@ var init_scheduler = __esm({
|
|
|
189473
189729
|
for (const slice of hierarchy.milestones.flatMap((milestone) => milestone.slices).filter((slice2) => slice2.status === "active")) {
|
|
189474
189730
|
try {
|
|
189475
189731
|
const superseded = await missionStore.reconcileSupersededGeneratedFixFeatures(slice.id);
|
|
189476
|
-
fixed += superseded.supersededCount;
|
|
189732
|
+
fixed += superseded.supersededCount + (superseded.repairedCount ?? 0);
|
|
189477
189733
|
if (superseded.supersededCount > 0) {
|
|
189478
189734
|
const refreshed = await missionStore.getSlice(slice.id);
|
|
189479
189735
|
if (refreshed?.status === "complete") {
|
|
@@ -189486,8 +189742,9 @@ var init_scheduler = __esm({
|
|
|
189486
189742
|
const supersededFeatureIds = new Set(superseded.featureIds ?? []);
|
|
189487
189743
|
const autoTriage = mission.autopilotEnabled === true || mission.autoAdvance === true;
|
|
189488
189744
|
for (const feature of features) {
|
|
189489
|
-
if (supersededFeatureIds.has(feature.id) || feature.taskId || !autoTriage || feature.status === "blocked") continue;
|
|
189745
|
+
if (supersededFeatureIds.has(feature.id) || feature.taskId || !autoTriage || feature.status === "blocked" || feature.status === "done") continue;
|
|
189490
189746
|
if (feature.status !== "defined" && this.isGeneratedFixFeature(feature)) {
|
|
189747
|
+
schedulerLog.warn(`Parking stranded generated Fix feature ${feature.id} from ${feature.status}: not triageable without a defined state`);
|
|
189491
189748
|
await missionStore.updateFeature(feature.id, { status: "blocked", loopState: "blocked", taskId: void 0 });
|
|
189492
189749
|
fixed++;
|
|
189493
189750
|
continue;
|
|
@@ -190539,12 +190796,12 @@ function createTaskSearchTool(store) {
|
|
|
190539
190796
|
}
|
|
190540
190797
|
};
|
|
190541
190798
|
}
|
|
190542
|
-
function
|
|
190799
|
+
function createHistoryReadTool(store) {
|
|
190543
190800
|
return {
|
|
190544
|
-
name: "
|
|
190801
|
+
name: "fn_history_read",
|
|
190545
190802
|
label: "Read History",
|
|
190546
190803
|
description: "Read the permanent daily history of completed and reverted task deliveries.",
|
|
190547
|
-
parameters:
|
|
190804
|
+
parameters: historyReadParams,
|
|
190548
190805
|
execute: async (_id, params) => {
|
|
190549
190806
|
const limit2 = Math.min(50, Math.max(1, Math.floor(params.limit ?? 20)));
|
|
190550
190807
|
const result2 = await store.listPatchnodeEntries({
|
|
@@ -190804,7 +191061,7 @@ function createTaskDocumentReadTool(store, taskId) {
|
|
|
190804
191061
|
};
|
|
190805
191062
|
}
|
|
190806
191063
|
function createInstallWorktreeDependenciesTool(store, taskId, options) {
|
|
190807
|
-
const
|
|
191064
|
+
const errorResult2 = (message) => ({
|
|
190808
191065
|
content: [{ type: "text", text: `ERROR: ${message}` }],
|
|
190809
191066
|
details: {},
|
|
190810
191067
|
isError: true
|
|
@@ -190817,33 +191074,33 @@ function createInstallWorktreeDependenciesTool(store, taskId, options) {
|
|
|
190817
191074
|
parameters: installWorktreeDependenciesParams,
|
|
190818
191075
|
execute: async (_id, params) => {
|
|
190819
191076
|
const task = await store.getTask(taskId);
|
|
190820
|
-
if (!task) return
|
|
191077
|
+
if (!task) return errorResult2(`Task ${taskId} no longer exists.`);
|
|
190821
191078
|
const workspaceRepos = (await loadWorkspaceConfig(options.rootDir))?.repos ?? [];
|
|
190822
191079
|
const requestedRepository = params.repository?.trim();
|
|
190823
191080
|
let worktreePath;
|
|
190824
191081
|
let repositoryLabel;
|
|
190825
191082
|
if (workspaceRepos.length > 0) {
|
|
190826
191083
|
if (workspaceRepos.length > 1 && !requestedRepository) {
|
|
190827
|
-
return
|
|
191084
|
+
return errorResult2("repository is required for a workspace with multiple configured repositories.");
|
|
190828
191085
|
}
|
|
190829
191086
|
if (workspaceRepos.length === 1 && requestedRepository && requestedRepository !== workspaceRepos[0]) {
|
|
190830
|
-
return
|
|
191087
|
+
return errorResult2(`Unknown workspace repository: ${requestedRepository}`);
|
|
190831
191088
|
}
|
|
190832
191089
|
const repository = requestedRepository ?? workspaceRepos[0];
|
|
190833
|
-
if (!workspaceRepos.includes(repository)) return
|
|
191090
|
+
if (!workspaceRepos.includes(repository)) return errorResult2(`Unknown workspace repository: ${repository}`);
|
|
190834
191091
|
worktreePath = task.workspaceWorktrees?.[repository]?.worktreePath;
|
|
190835
191092
|
repositoryLabel = repository;
|
|
190836
191093
|
} else {
|
|
190837
|
-
if (requestedRepository) return
|
|
191094
|
+
if (requestedRepository) return errorResult2("repository is only valid for a configured workspace task.");
|
|
190838
191095
|
worktreePath = task.worktree;
|
|
190839
191096
|
}
|
|
190840
191097
|
if (!worktreePath || !existsSync56(worktreePath)) {
|
|
190841
|
-
return
|
|
191098
|
+
return errorResult2(`No acquired task worktree is available${repositoryLabel ? ` for ${repositoryLabel}` : ""}.`);
|
|
190842
191099
|
}
|
|
190843
191100
|
const settings = await options.getSettings();
|
|
190844
191101
|
if (params.action === "none") {
|
|
190845
191102
|
const reason = params.reason?.trim();
|
|
190846
|
-
if (!reason) return
|
|
191103
|
+
if (!reason) return errorResult2("reason is required for action=none.");
|
|
190847
191104
|
const readiness2 = recordPlannerDependencyResolution({
|
|
190848
191105
|
worktreePath,
|
|
190849
191106
|
action: "none",
|
|
@@ -190862,7 +191119,7 @@ function createInstallWorktreeDependenciesTool(store, taskId, options) {
|
|
|
190862
191119
|
};
|
|
190863
191120
|
}
|
|
190864
191121
|
const command2 = params.command?.trim();
|
|
190865
|
-
if (!command2) return
|
|
191122
|
+
if (!command2) return errorResult2("command is required for action=install.");
|
|
190866
191123
|
let result2;
|
|
190867
191124
|
try {
|
|
190868
191125
|
result2 = await options.runConfiguredCommand(
|
|
@@ -194132,7 +194389,7 @@ ${lines.join("\n")}`
|
|
|
194132
194389
|
}
|
|
194133
194390
|
};
|
|
194134
194391
|
}
|
|
194135
|
-
var TASK_CREATE_PRIORITY_VALUES, missionLineageParams, taskCreateParams, taskLogParams, agentLogTypeParams, agentLogDetailModeParams, taskLogsReadParams, chatTaskLogsReadParams, taskListParams, taskShowParams, taskSearchParams,
|
|
194392
|
+
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;
|
|
194136
194393
|
var init_agent_tools = __esm({
|
|
194137
194394
|
"../engine/src/agent-tools.ts"() {
|
|
194138
194395
|
"use strict";
|
|
@@ -194223,7 +194480,7 @@ var init_agent_tools = __esm({
|
|
|
194223
194480
|
includeArchived: Type2.Optional(Type2.Boolean({ description: "Include archived tasks (default true)" })),
|
|
194224
194481
|
limit: Type2.Optional(Type2.Number({ minimum: 1, maximum: 50, description: "Max results (default 20, max 50)" }))
|
|
194225
194482
|
});
|
|
194226
|
-
|
|
194483
|
+
historyReadParams = Type2.Object({
|
|
194227
194484
|
query: Type2.Optional(Type2.String({ description: "Search task IDs, titles, and completion summaries" })),
|
|
194228
194485
|
from: Type2.Optional(Type2.String({ description: "First UTC day, YYYY-MM-DD (inclusive)" })),
|
|
194229
194486
|
to: Type2.Optional(Type2.String({ description: "Last UTC day, YYYY-MM-DD (inclusive)" })),
|
|
@@ -197433,6 +197690,55 @@ var init_fallback_model_observer = __esm({
|
|
|
197433
197690
|
}
|
|
197434
197691
|
});
|
|
197435
197692
|
|
|
197693
|
+
// ../engine/src/execution/resolve-activity-run-agent-id.ts
|
|
197694
|
+
async function resolveWorkflowStepRunAgentId(store, candidate, options = {}) {
|
|
197695
|
+
if (!candidate?.trim()) return null;
|
|
197696
|
+
const resolve120 = async () => {
|
|
197697
|
+
if (typeof store?.getAgent === "function") {
|
|
197698
|
+
const agent = await store.getAgent(candidate);
|
|
197699
|
+
if (agent) return agent.id;
|
|
197700
|
+
}
|
|
197701
|
+
if (typeof store?.listAgents !== "function") return null;
|
|
197702
|
+
const matches = (await store.listAgents({ includeEphemeral: true })).filter(
|
|
197703
|
+
(agent) => agent.role === candidate || agent.roles?.includes(candidate)
|
|
197704
|
+
);
|
|
197705
|
+
const builtInOwners = matches.filter(
|
|
197706
|
+
(agent) => isBuiltinWorkflowRoleAgent(agent) && agent.metadata?.workflowRole === candidate
|
|
197707
|
+
);
|
|
197708
|
+
if (builtInOwners.length === 1) return builtInOwners[0].id;
|
|
197709
|
+
return matches.length === 1 ? matches[0].id : null;
|
|
197710
|
+
};
|
|
197711
|
+
let lookup2;
|
|
197712
|
+
try {
|
|
197713
|
+
lookup2 = Promise.resolve(resolve120());
|
|
197714
|
+
} catch {
|
|
197715
|
+
return null;
|
|
197716
|
+
}
|
|
197717
|
+
void lookup2.catch(() => void 0);
|
|
197718
|
+
return await new Promise((complete) => {
|
|
197719
|
+
const timer2 = setTimeout(() => complete(null), options.timeoutMs ?? WORKFLOW_STEP_RUN_ATTRIBUTION_TIMEOUT_MS);
|
|
197720
|
+
timer2.unref?.();
|
|
197721
|
+
void lookup2.then(
|
|
197722
|
+
(agentId) => {
|
|
197723
|
+
clearTimeout(timer2);
|
|
197724
|
+
complete(agentId);
|
|
197725
|
+
},
|
|
197726
|
+
() => {
|
|
197727
|
+
clearTimeout(timer2);
|
|
197728
|
+
complete(null);
|
|
197729
|
+
}
|
|
197730
|
+
);
|
|
197731
|
+
});
|
|
197732
|
+
}
|
|
197733
|
+
var WORKFLOW_STEP_RUN_ATTRIBUTION_TIMEOUT_MS;
|
|
197734
|
+
var init_resolve_activity_run_agent_id = __esm({
|
|
197735
|
+
"../engine/src/execution/resolve-activity-run-agent-id.ts"() {
|
|
197736
|
+
"use strict";
|
|
197737
|
+
init_src2();
|
|
197738
|
+
WORKFLOW_STEP_RUN_ATTRIBUTION_TIMEOUT_MS = 2e3;
|
|
197739
|
+
}
|
|
197740
|
+
});
|
|
197741
|
+
|
|
197436
197742
|
// ../engine/src/execution/step-session-executor.ts
|
|
197437
197743
|
import { exec as exec20 } from "node:child_process";
|
|
197438
197744
|
import { promisify as promisify27 } from "node:util";
|
|
@@ -197441,12 +197747,7 @@ import { rm as rm8 } from "node:fs/promises";
|
|
|
197441
197747
|
function parseStepFileScopes(prompt) {
|
|
197442
197748
|
const result2 = /* @__PURE__ */ new Map();
|
|
197443
197749
|
if (!prompt) return result2;
|
|
197444
|
-
const
|
|
197445
|
-
const splits = [];
|
|
197446
|
-
let match2;
|
|
197447
|
-
while ((match2 = stepRegex.exec(prompt)) !== null) {
|
|
197448
|
-
splits.push({ index: match2.index, stepNum: parseInt(match2[1], 10) });
|
|
197449
|
-
}
|
|
197750
|
+
const splits = matchStepHeadings(prompt).map(({ index: index3, headingNumber }) => ({ index: index3, stepNum: headingNumber }));
|
|
197450
197751
|
if (splits.length === 0) return result2;
|
|
197451
197752
|
for (let i = 0; i < splits.length; i++) {
|
|
197452
197753
|
const start = splits[i].index;
|
|
@@ -197722,12 +198023,7 @@ function scopePromptToWorktree(prompt, rootDir, worktreePath) {
|
|
|
197722
198023
|
return prompt.replaceAll(`${rootDir}/`, `${worktreePath}/`).replaceAll(`${worktreePath}/.fusion/`, `${rootDir}/.fusion/`);
|
|
197723
198024
|
}
|
|
197724
198025
|
function extractStepSection(prompt, stepIndex) {
|
|
197725
|
-
const
|
|
197726
|
-
const splits = [];
|
|
197727
|
-
let match2;
|
|
197728
|
-
while ((match2 = stepRegex.exec(prompt)) !== null) {
|
|
197729
|
-
splits.push({ index: match2.index, stepNum: parseInt(match2[1], 10) });
|
|
197730
|
-
}
|
|
198026
|
+
const splits = matchStepHeadings(prompt).map(({ index: index3, headingNumber }) => ({ index: index3, stepNum: headingNumber }));
|
|
197731
198027
|
const offset = resolveAuthoredStepHeadingOffset(splits.map((split) => split.stepNum));
|
|
197732
198028
|
const targetSplit = splits.find((s3) => s3.stepNum === stepIndex + offset);
|
|
197733
198029
|
if (!targetSplit) return "";
|
|
@@ -197737,9 +198033,7 @@ function extractStepSection(prompt, stepIndex) {
|
|
|
197737
198033
|
return prompt.slice(start, end).trim();
|
|
197738
198034
|
}
|
|
197739
198035
|
function countSteps(prompt) {
|
|
197740
|
-
|
|
197741
|
-
const matches = prompt.match(stepRegex);
|
|
197742
|
-
return matches ? matches.length : 0;
|
|
198036
|
+
return matchStepHeadings(prompt).length;
|
|
197743
198037
|
}
|
|
197744
198038
|
function resolveStepSection(taskDetail, prompt, stepIndex, authoredStepCount) {
|
|
197745
198039
|
const authoredSection = extractStepSection(prompt, stepIndex);
|
|
@@ -197834,6 +198128,7 @@ var init_step_session_executor = __esm({
|
|
|
197834
198128
|
init_usage_limit_detector();
|
|
197835
198129
|
init_agent_tools();
|
|
197836
198130
|
init_worktree_backend();
|
|
198131
|
+
init_resolve_activity_run_agent_id();
|
|
197837
198132
|
init_worktree_prune();
|
|
197838
198133
|
init_active_session_registry();
|
|
197839
198134
|
execAsync18 = promisify27(exec20);
|
|
@@ -197866,6 +198161,8 @@ var init_step_session_executor = __esm({
|
|
|
197866
198161
|
credentialInstanceId;
|
|
197867
198162
|
reusablePrimaryRetargetPending = false;
|
|
197868
198163
|
reusablePrimaryAttemptActive = false;
|
|
198164
|
+
workflowStepRunAgentId;
|
|
198165
|
+
warnedUnattributableWorkflowStepRun = false;
|
|
197869
198166
|
registerActiveStepSession(stepIndex, handle2, worktreePath) {
|
|
197870
198167
|
this.activeSessions.set(stepIndex, handle2);
|
|
197871
198168
|
activeSessionRegistry.registerPath(worktreePath, {
|
|
@@ -198210,10 +198507,20 @@ var init_step_session_executor = __esm({
|
|
|
198210
198507
|
modelId: current.modelId
|
|
198211
198508
|
};
|
|
198212
198509
|
}
|
|
198213
|
-
createWorkflowStepActivityRun(stepIndex, startedAt) {
|
|
198510
|
+
async createWorkflowStepActivityRun(stepIndex, startedAt) {
|
|
198214
198511
|
const { taskDetail } = this.options;
|
|
198215
198512
|
const step = taskDetail.steps?.[stepIndex];
|
|
198216
|
-
|
|
198513
|
+
if (typeof this.options.agentStore?.saveRun !== "function") return null;
|
|
198514
|
+
const agentIdCandidate = this.options.effectiveAgentId ?? taskDetail.assignedAgentId ?? "executor";
|
|
198515
|
+
this.workflowStepRunAgentId ??= resolveWorkflowStepRunAgentId(this.options.agentStore, agentIdCandidate);
|
|
198516
|
+
const agentId = await this.workflowStepRunAgentId;
|
|
198517
|
+
if (!agentId) {
|
|
198518
|
+
if (!this.warnedUnattributableWorkflowStepRun) {
|
|
198519
|
+
this.warnedUnattributableWorkflowStepRun = true;
|
|
198520
|
+
stepExecLog.warn(`Skipping unattributable workflow-step activity runs for task ${taskDetail.id} (candidate: ${agentIdCandidate})`);
|
|
198521
|
+
}
|
|
198522
|
+
return null;
|
|
198523
|
+
}
|
|
198217
198524
|
return {
|
|
198218
198525
|
id: `${generateSyntheticRunId("workflow-step", taskDetail.id)}-step-${stepIndex}`,
|
|
198219
198526
|
agentId,
|
|
@@ -198233,6 +198540,7 @@ var init_step_session_executor = __esm({
|
|
|
198233
198540
|
taskTitle: taskDetail.title,
|
|
198234
198541
|
assignedAgentId: taskDetail.assignedAgentId,
|
|
198235
198542
|
effectiveAgentId: this.options.effectiveAgentId,
|
|
198543
|
+
agentIdCandidate,
|
|
198236
198544
|
agentId,
|
|
198237
198545
|
stepIndex,
|
|
198238
198546
|
stepName: step?.name ?? `Step ${stepIndex}`
|
|
@@ -198247,6 +198555,7 @@ var init_step_session_executor = __esm({
|
|
|
198247
198555
|
};
|
|
198248
198556
|
}
|
|
198249
198557
|
async saveWorkflowStepActivityRun(run2) {
|
|
198558
|
+
if (!run2) return;
|
|
198250
198559
|
const saveRun2 = this.options.agentStore?.saveRun?.bind(this.options.agentStore);
|
|
198251
198560
|
if (!saveRun2) return;
|
|
198252
198561
|
try {
|
|
@@ -198259,6 +198568,7 @@ var init_step_session_executor = __esm({
|
|
|
198259
198568
|
}
|
|
198260
198569
|
}
|
|
198261
198570
|
async completeWorkflowStepActivityRun(run2, status, result2) {
|
|
198571
|
+
if (!run2) return;
|
|
198262
198572
|
const terminalRun = {
|
|
198263
198573
|
...run2,
|
|
198264
198574
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -198318,7 +198628,7 @@ var init_step_session_executor = __esm({
|
|
|
198318
198628
|
if (semaphore) {
|
|
198319
198629
|
await semaphore.acquire();
|
|
198320
198630
|
}
|
|
198321
|
-
const activityRun = this.createWorkflowStepActivityRun(stepIndex, (/* @__PURE__ */ new Date()).toISOString());
|
|
198631
|
+
const activityRun = await this.createWorkflowStepActivityRun(stepIndex, (/* @__PURE__ */ new Date()).toISOString());
|
|
198322
198632
|
await this.saveWorkflowStepActivityRun(activityRun);
|
|
198323
198633
|
const trackingKey = this.makeTrackingKey(stepIndex);
|
|
198324
198634
|
let retries = 0;
|
|
@@ -198867,12 +199177,11 @@ function collectTaskPaths(text4) {
|
|
|
198867
199177
|
}
|
|
198868
199178
|
function collectStepSections(stepsSection) {
|
|
198869
199179
|
const sections2 = [];
|
|
198870
|
-
const headingMatches =
|
|
199180
|
+
const headingMatches = matchStepHeadings(stepsSection);
|
|
198871
199181
|
for (let i = 0; i < headingMatches.length; i += 1) {
|
|
198872
199182
|
const heading = headingMatches[i];
|
|
198873
|
-
const
|
|
198874
|
-
|
|
198875
|
-
sections2.push({ name: `Step ${heading[1]}`, body: stepsSection.slice(start, end).trim() });
|
|
199183
|
+
const end = i + 1 < headingMatches.length ? headingMatches[i + 1].index : stepsSection.length;
|
|
199184
|
+
sections2.push({ name: `Step ${heading.headingNumber}`, body: stepsSection.slice(heading.headingLineEnd, end).trim() });
|
|
198876
199185
|
}
|
|
198877
199186
|
return sections2;
|
|
198878
199187
|
}
|
|
@@ -198964,6 +199273,7 @@ var TASK_PATH_REGEX;
|
|
|
198964
199273
|
var init_task_document_references = __esm({
|
|
198965
199274
|
"../engine/src/spec-validation/task-document-references.ts"() {
|
|
198966
199275
|
"use strict";
|
|
199276
|
+
init_src2();
|
|
198967
199277
|
init_step_session_executor();
|
|
198968
199278
|
TASK_PATH_REGEX = /\.fusion\/tasks\/[A-Z0-9]+(?:-[A-Z0-9]+)*\/[^\s`'"\]]+/g;
|
|
198969
199279
|
}
|
|
@@ -199990,6 +200300,24 @@ var init_configured_command = __esm({
|
|
|
199990
200300
|
}
|
|
199991
200301
|
});
|
|
199992
200302
|
|
|
200303
|
+
// ../engine/src/merge/stale-content-park.ts
|
|
200304
|
+
function classifyStaleContentPark(task) {
|
|
200305
|
+
if (task.paused || task.userPaused || task.deletedAt) return void 0;
|
|
200306
|
+
if (typeof task.error !== "string" || !isStaleContentApprovalBlocker(task.error)) return void 0;
|
|
200307
|
+
if (task.status === "failed") {
|
|
200308
|
+
return task.error.startsWith(AUTO_MERGE_RETRY_REJECTED_PREFIX) ? "retry-rejected" : "raw-blocker";
|
|
200309
|
+
}
|
|
200310
|
+
return task.status == null ? "retry-exhausted" : void 0;
|
|
200311
|
+
}
|
|
200312
|
+
var AUTO_MERGE_RETRY_REJECTED_PREFIX;
|
|
200313
|
+
var init_stale_content_park = __esm({
|
|
200314
|
+
"../engine/src/merge/stale-content-park.ts"() {
|
|
200315
|
+
"use strict";
|
|
200316
|
+
init_src2();
|
|
200317
|
+
AUTO_MERGE_RETRY_REJECTED_PREFIX = "AUTO_MERGE_RETRY_REJECTED:";
|
|
200318
|
+
}
|
|
200319
|
+
});
|
|
200320
|
+
|
|
199993
200321
|
// ../engine/src/execution/lifecycle-move-log.ts
|
|
199994
200322
|
function laneRole(column, lanes) {
|
|
199995
200323
|
if (!lanes) return void 0;
|
|
@@ -223770,6 +224098,17 @@ function isRecoveryRetryDue(task, now) {
|
|
|
223770
224098
|
const retryAt = Date.parse(task.nextRecoveryAt);
|
|
223771
224099
|
return !Number.isFinite(retryAt) || retryAt <= now;
|
|
223772
224100
|
}
|
|
224101
|
+
function formatMergeableReviewRecoverySummary({
|
|
224102
|
+
enqueued,
|
|
224103
|
+
merged,
|
|
224104
|
+
parked
|
|
224105
|
+
}) {
|
|
224106
|
+
const outcomes = [];
|
|
224107
|
+
if (enqueued > 0) outcomes.push(`${enqueued} re-enqueued for merge`);
|
|
224108
|
+
if (merged > 0) outcomes.push(`${merged} merged \u2192 done`);
|
|
224109
|
+
if (parked > 0) outcomes.push(`${parked} parked after enqueue starvation`);
|
|
224110
|
+
return `Mergeable review recovery: ${outcomes.join(", ") || "no tasks recovered"}`;
|
|
224111
|
+
}
|
|
223773
224112
|
async function resolveNoOpFinalizeGateIds2(store, task) {
|
|
223774
224113
|
const selection = store.getTaskWorkflowSelectionAsync ? await store.getTaskWorkflowSelectionAsync(task.id) : store.getTaskWorkflowSelection?.(task.id);
|
|
223775
224114
|
if (!selection) return void 0;
|
|
@@ -223816,6 +224155,7 @@ var init_self_healing = __esm({
|
|
|
223816
224155
|
"use strict";
|
|
223817
224156
|
init_src2();
|
|
223818
224157
|
init_src2();
|
|
224158
|
+
init_stale_content_park();
|
|
223819
224159
|
init_logger3();
|
|
223820
224160
|
init_lifecycle_move_log();
|
|
223821
224161
|
init_lifecycle_move();
|
|
@@ -223836,6 +224176,7 @@ var init_self_healing = __esm({
|
|
|
223836
224176
|
init_post_landing_worktree_cleanup();
|
|
223837
224177
|
init_auto_recovery();
|
|
223838
224178
|
init_active_session_registry();
|
|
224179
|
+
init_planning_liveness();
|
|
223839
224180
|
init_replan_target();
|
|
223840
224181
|
init_planning_handoff_recovery();
|
|
223841
224182
|
init_spec_staleness();
|
|
@@ -224002,6 +224343,9 @@ var init_self_healing = __esm({
|
|
|
224002
224343
|
maintenanceTickCounter = 0;
|
|
224003
224344
|
taskLifecycleRetentionLastPrunedAt = /* @__PURE__ */ new Map();
|
|
224004
224345
|
staleContentRerouteAuditKeys = /* @__PURE__ */ new Set();
|
|
224346
|
+
mergeRecoveryBlockerWarnKeys = /* @__PURE__ */ new Set();
|
|
224347
|
+
staleContentParkRecoveryAttempts = /* @__PURE__ */ new Map();
|
|
224348
|
+
staleContentParkRecoveryBudgetLogged = /* @__PURE__ */ new Set();
|
|
224005
224349
|
unrunPreMergeGateRerouteAuditKeys = /* @__PURE__ */ new Set();
|
|
224006
224350
|
githubCheckStateRetentionLastPrunedAt = /* @__PURE__ */ new Map();
|
|
224007
224351
|
processBootStartedAt = Date.now();
|
|
@@ -224720,6 +225064,13 @@ var init_self_healing = __esm({
|
|
|
224720
225064
|
{ name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults().then(() => void 0) },
|
|
224721
225065
|
{ name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals().then(() => void 0) },
|
|
224722
225066
|
/*
|
|
225067
|
+
FNXC:PreMergeApproval 2026-09-05-22:11:
|
|
225068
|
+
Runs AFTER the orphaned-step sweep, because that sweep is what produces the `failed` row this
|
|
225069
|
+
defect archives, and BEFORE failed-step recovery, so the restored gate is visible to it in the
|
|
225070
|
+
same startup pass instead of one cycle later.
|
|
225071
|
+
*/
|
|
225072
|
+
{ name: "reconcile-collateral-archived-review-gates", fn: () => this.reconcileCollateralArchivedReviewGates().then(() => void 0) },
|
|
225073
|
+
/*
|
|
224723
225074
|
FNXC:WorkspaceContention 2026-08-23-08:00:
|
|
224724
225075
|
`contention-hold` is owned by an in-memory retry timer. After a process restart that
|
|
224725
225076
|
timer is gone, so clear only an unowned wait before ordinary dispatch classification;
|
|
@@ -225600,6 +225951,7 @@ var init_self_healing = __esm({
|
|
|
225600
225951
|
*/
|
|
225601
225952
|
{ name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults() },
|
|
225602
225953
|
{ name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals() },
|
|
225954
|
+
{ name: "reconcile-collateral-archived-review-gates", fn: () => this.reconcileCollateralArchivedReviewGates() },
|
|
225603
225955
|
{ name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
|
|
225604
225956
|
{ name: "recover-missing-worktree-review-failures", fn: () => this.recoverMissingWorktreeReviewFailures() },
|
|
225605
225957
|
{ name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
|
|
@@ -229086,29 +229438,28 @@ var init_self_healing = __esm({
|
|
|
229086
229438
|
/** Repair legacy dependency residue before it can re-enter executor dispatch. */
|
|
229087
229439
|
async reconcileMissingDependencies() {
|
|
229088
229440
|
let repaired = 0;
|
|
229089
|
-
const
|
|
229441
|
+
const archivedColumns = new Set(await Promise.resolve().then(() => resolveProjectColumnsForRoles(this.store, ["archived"])).catch(() => /* @__PURE__ */ new Set()));
|
|
229442
|
+
archivedColumns.add("archived");
|
|
229443
|
+
const tasks2 = await this.store.listTasks({ slim: true, includeArchived: false });
|
|
229090
229444
|
for (const snapshot of tasks2) {
|
|
229445
|
+
if (snapshot.deletedAt || archivedColumns.has(snapshot.column)) continue;
|
|
229091
229446
|
if (!snapshot.dependencies.length || snapshot.userPaused || snapshot.paused || snapshot.autoMerge === false) continue;
|
|
229092
229447
|
const livePaths = activeSessionRegistry.pathsForTask(snapshot.id);
|
|
229093
229448
|
const liveExecution = livePaths.some((path18) => activeSessionRegistry.isPathActive(path18)) || executingTaskLock.has(snapshot.id) || this.options.getExecutingTaskIds?.().has(snapshot.id) === true || this.options.isTaskActive?.(snapshot.id) === true || Boolean(snapshot.checkedOutBy);
|
|
229094
229449
|
if (liveExecution) continue;
|
|
229095
229450
|
let removed = 0;
|
|
229096
229451
|
for (const dependencyId of snapshot.dependencies) {
|
|
229097
|
-
|
|
229098
|
-
|
|
229099
|
-
if (!dependency.deletedAt) continue;
|
|
229100
|
-
} catch (error3) {
|
|
229101
|
-
if (!isTaskNotFoundError2(error3)) throw error3;
|
|
229102
|
-
}
|
|
229103
|
-
const current = await this.store.getTask(snapshot.id).catch((error3) => {
|
|
229104
|
-
if (isTaskNotFoundError2(error3)) return void 0;
|
|
229105
|
-
throw error3;
|
|
229106
|
-
});
|
|
229452
|
+
if (await readLinkedTaskOrUndefined(this.store, dependencyId)) continue;
|
|
229453
|
+
const current = await readLinkedTaskOrUndefined(this.store, snapshot.id);
|
|
229107
229454
|
if (!current || current.userPaused || current.paused || current.autoMerge === false || current.checkedOutBy || activeSessionRegistry.pathsForTask(current.id).some((path18) => activeSessionRegistry.isPathActive(path18)) || executingTaskLock.has(current.id) || this.options.getExecutingTaskIds?.().has(current.id) === true || this.options.isTaskActive?.(current.id) === true || !current.dependencies.includes(dependencyId)) continue;
|
|
229108
229455
|
try {
|
|
229109
229456
|
await this.store.updateTaskDependencies(current.id, { operation: "remove", dependency: dependencyId });
|
|
229110
229457
|
removed += 1;
|
|
229111
229458
|
} catch (error3) {
|
|
229459
|
+
if (isMissingTaskLookupError(error3)) {
|
|
229460
|
+
log34.debug(`reconcileMissingDependencies: skipped unreadable ${current.id} dependency ${dependencyId}`);
|
|
229461
|
+
continue;
|
|
229462
|
+
}
|
|
229112
229463
|
if (!/does not depend on/i.test(error3 instanceof Error ? error3.message : String(error3))) throw error3;
|
|
229113
229464
|
}
|
|
229114
229465
|
}
|
|
@@ -229874,7 +230225,7 @@ var init_self_healing = __esm({
|
|
|
229874
230225
|
const graceMs = 10 * 6e4;
|
|
229875
230226
|
const now = Date.now();
|
|
229876
230227
|
const nowIso3 = new Date(now).toISOString();
|
|
229877
|
-
const live = (taskId) => activeSessionRegistry.pathsForTask(taskId).some((path18) => activeSessionRegistry.isPathActive(path18)) || executingTaskLock.has(taskId) || this.options.isTaskActive?.(taskId) === true;
|
|
230228
|
+
const live = (taskId) => activeSessionRegistry.pathsForTask(taskId).some((path18) => activeSessionRegistry.isPathActive(path18)) || executingTaskLock.has(taskId) || isPlanningLive(taskId) || this.options.isTaskActive?.(taskId) === true;
|
|
229878
230229
|
const due = await this.store.listDueWorkflowWorkItems({
|
|
229879
230230
|
now: nowIso3,
|
|
229880
230231
|
states: [...ACTIVE_WORKFLOW_WORK_ITEM_STATES]
|
|
@@ -230251,6 +230602,109 @@ var init_self_healing = __esm({
|
|
|
230251
230602
|
return 0;
|
|
230252
230603
|
}
|
|
230253
230604
|
}
|
|
230605
|
+
/**
|
|
230606
|
+
* FNXC:PreMergeApproval 2026-09-05-22:11:
|
|
230607
|
+
* FN-295: restore a required pre-merge gate that was archived as COLLATERAL of another gate's
|
|
230608
|
+
* remediation, so the card stops being permanently unmergeable with no owner.
|
|
230609
|
+
*
|
|
230610
|
+
* The wedge needs four ingredients, none of which is individually wrong: a review row left
|
|
230611
|
+
* `pending` by a dead session, the orphaned-step sweep rewriting it to `failed`, the remediation
|
|
230612
|
+
* archiver stamping EVERY terminal failure (not just its own gate), and the merge door treating
|
|
230613
|
+
* `remediationArchivedAt` as an unconditional veto. The result is a row the reseed cannot re-run
|
|
230614
|
+
* (it handles `missing`), the stale-content reroute cannot re-run (it handles `stale-content`),
|
|
230615
|
+
* and the operator bypass cannot select (it selects `status:"failed"`). Measured on FN-295: three
|
|
230616
|
+
* review restarts re-ran Code Review and Documentation to APPROVE and merged nothing.
|
|
230617
|
+
*
|
|
230618
|
+
* This sweep does NOT approve or merge anything: it undoes the collateral archive so the gate is
|
|
230619
|
+
* once again a visible failed step that the ordinary FN-7720 audited bypass — or a genuine re-run
|
|
230620
|
+
* — can clear. It never touches an operator waiver, the gate that owns the remediation wave, a
|
|
230621
|
+
* workspace card, a user-paused card, or a card with a live session, and it re-validates every
|
|
230622
|
+
* condition inside the atomic mutation.
|
|
230623
|
+
*/
|
|
230624
|
+
async reconcileCollateralArchivedReviewGates() {
|
|
230625
|
+
try {
|
|
230626
|
+
const reviewColumns = await resolveProjectColumnsForRoles(this.store, REVIEW_ROLES);
|
|
230627
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
230628
|
+
for (const column of reviewColumns) {
|
|
230629
|
+
for (const task of await this.store.listTasks({ column, slim: true })) candidates.set(task.id, task);
|
|
230630
|
+
}
|
|
230631
|
+
let repairedTasks = 0;
|
|
230632
|
+
const isSessionLive = (taskId) => {
|
|
230633
|
+
const livePaths = activeSessionRegistry.pathsForTask(taskId);
|
|
230634
|
+
return livePaths.some((path18) => activeSessionRegistry.isPathActive(path18)) || executingTaskLock.has(taskId) || this.options.isTaskActive?.(taskId) === true;
|
|
230635
|
+
};
|
|
230636
|
+
const remediationGateIdsFor = (task) => new Set(
|
|
230637
|
+
(task.steps ?? []).map((step) => step.remediation?.gateStepId).filter((gateStepId) => typeof gateStepId === "string" && gateStepId.length > 0)
|
|
230638
|
+
);
|
|
230639
|
+
for (const candidate of candidates.values()) {
|
|
230640
|
+
if (candidate.userPaused === true || candidate.workspaceWorktrees !== void 0 || isSessionLive(candidate.id)) continue;
|
|
230641
|
+
const fresh = await this.store.getTask(candidate.id);
|
|
230642
|
+
if (!fresh || !reviewColumns.has(fresh.column) || fresh.userPaused === true || fresh.workspaceWorktrees !== void 0 || isSessionLive(fresh.id)) continue;
|
|
230643
|
+
if (!(fresh.workflowStepResults ?? []).some((result2) => resolveCollateralArchivedReviewGate(result2, { remediationGateIds: remediationGateIdsFor(fresh) }))) continue;
|
|
230644
|
+
let requiredPreMergeStepIds;
|
|
230645
|
+
try {
|
|
230646
|
+
requiredPreMergeStepIds = (await resolvePreMergeGateForTask(
|
|
230647
|
+
this.store,
|
|
230648
|
+
fresh.id,
|
|
230649
|
+
fresh.enabledWorkflowSteps,
|
|
230650
|
+
fresh
|
|
230651
|
+
)).requiredPreMergeStepIds;
|
|
230652
|
+
} catch {
|
|
230653
|
+
continue;
|
|
230654
|
+
}
|
|
230655
|
+
if (requiredPreMergeStepIds.size === 0) continue;
|
|
230656
|
+
let repair;
|
|
230657
|
+
try {
|
|
230658
|
+
await this.store.updateTaskAtomic(fresh.id, (live) => {
|
|
230659
|
+
repair = void 0;
|
|
230660
|
+
if (!reviewColumns.has(live.column) || live.userPaused === true || live.workspaceWorktrees !== void 0 || isSessionLive(live.id)) return null;
|
|
230661
|
+
const remediationGateIds = remediationGateIdsFor(live);
|
|
230662
|
+
const restoredStepIds = [];
|
|
230663
|
+
const results = (live.workflowStepResults ?? []).map((result2) => {
|
|
230664
|
+
if (!requiredPreMergeStepIds.has(result2.workflowStepId)) return result2;
|
|
230665
|
+
const resolution = resolveCollateralArchivedReviewGate(result2, { remediationGateIds });
|
|
230666
|
+
if (!resolution) return result2;
|
|
230667
|
+
restoredStepIds.push(result2.workflowStepId);
|
|
230668
|
+
return resolution.restored;
|
|
230669
|
+
});
|
|
230670
|
+
if (restoredStepIds.length === 0) return null;
|
|
230671
|
+
repair = { stepIds: restoredStepIds, column: live.column, resultCount: results.length };
|
|
230672
|
+
return { workflowStepResults: results };
|
|
230673
|
+
});
|
|
230674
|
+
if (!repair) continue;
|
|
230675
|
+
repairedTasks += 1;
|
|
230676
|
+
} catch (error3) {
|
|
230677
|
+
log34.warn(`reconcileCollateralArchivedReviewGates: failed for ${fresh.id}: ${error3 instanceof Error ? error3.message : String(error3)}`);
|
|
230678
|
+
continue;
|
|
230679
|
+
}
|
|
230680
|
+
await this.store.logEntry(
|
|
230681
|
+
fresh.id,
|
|
230682
|
+
`[pre-merge] Restored collaterally archived review gate(s): ${repair.stepIds.join(", ")}`,
|
|
230683
|
+
COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC
|
|
230684
|
+
).catch(() => void 0);
|
|
230685
|
+
await emitBoundedRunAudit2(this.store, {
|
|
230686
|
+
taskId: fresh.id,
|
|
230687
|
+
agentId: "self-healing",
|
|
230688
|
+
runId: generateSyntheticRunId("reconcile-collateral-archived-review-gate", fresh.id),
|
|
230689
|
+
domain: "database",
|
|
230690
|
+
mutationType: "task:reconcile-collateral-archived-review-gate",
|
|
230691
|
+
target: fresh.id,
|
|
230692
|
+
metadata: {
|
|
230693
|
+
taskId: fresh.id,
|
|
230694
|
+
column: repair.column,
|
|
230695
|
+
workflowStepId: repair.stepIds[0],
|
|
230696
|
+
restoredCount: repair.stepIds.length,
|
|
230697
|
+
resultCount: repair.resultCount
|
|
230698
|
+
}
|
|
230699
|
+
}, { log: log34 });
|
|
230700
|
+
}
|
|
230701
|
+
if (repairedTasks > 0) log34.log(`Restored collaterally archived review gates on ${repairedTasks} task(s)`);
|
|
230702
|
+
return repairedTasks;
|
|
230703
|
+
} catch (error3) {
|
|
230704
|
+
log34.error(`reconcileCollateralArchivedReviewGates failed: ${error3 instanceof Error ? error3.message : String(error3)}`);
|
|
230705
|
+
return 0;
|
|
230706
|
+
}
|
|
230707
|
+
}
|
|
230254
230708
|
/**
|
|
230255
230709
|
* Backward lifecycle move gated on triple proof (FN-5335).
|
|
230256
230710
|
* When the unproven fallback predicate fails, emits `task:finalize-no-op-review-no-action` and skips lifecycle mutation.
|
|
@@ -230610,25 +231064,14 @@ var init_self_healing = __esm({
|
|
|
230610
231064
|
blocker check lacks a fresh Git descriptor. Capture the same merge content the merge queue uses and
|
|
230611
231065
|
seed the graph back at Code Review, so cards like FN-9234 stop looping through doomed merge retries.
|
|
230612
231066
|
*/
|
|
230613
|
-
async routeStaleSingularApprovalBackToReview(task, reviewColumns,
|
|
230614
|
-
let mergeGate;
|
|
230615
|
-
try {
|
|
230616
|
-
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
230617
|
-
} catch {
|
|
230618
|
-
return false;
|
|
230619
|
-
}
|
|
230620
|
-
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) return false;
|
|
230621
|
-
const mergeContent = await captureMergeContentDescriptor(task, {
|
|
230622
|
-
workspaceRootDir: this.options.rootDir,
|
|
230623
|
-
settings
|
|
230624
|
-
});
|
|
231067
|
+
async routeStaleSingularApprovalBackToReview(task, reviewColumns, mergeGate, mergeContent, parkShape) {
|
|
230625
231068
|
const blocker = getTaskMergeBlocker(task, {
|
|
230626
231069
|
reviewColumns,
|
|
230627
231070
|
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
230628
231071
|
mergeContent
|
|
230629
231072
|
});
|
|
230630
|
-
if (blocker
|
|
230631
|
-
return false;
|
|
231073
|
+
if (!isStaleContentApprovalBlocker(blocker) || mergeContent.kind !== "singular") {
|
|
231074
|
+
return { handled: false, seeded: false, parkCleared: false, mergeRetriesReset: false };
|
|
230632
231075
|
}
|
|
230633
231076
|
const reroute = await rerouteSingularStaleContentToReview(this.store, task, {
|
|
230634
231077
|
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
@@ -230639,11 +231082,25 @@ var init_self_healing = __esm({
|
|
|
230639
231082
|
nodeId: void 0,
|
|
230640
231083
|
workflowStepId: void 0
|
|
230641
231084
|
}));
|
|
231085
|
+
let parkCleared = false;
|
|
231086
|
+
if (reroute.rerouted && parkShape) {
|
|
231087
|
+
await this.store.updateTaskAtomic(task.id, (live) => {
|
|
231088
|
+
if (classifyStaleContentPark(live) !== parkShape || live.paused || live.userPaused || live.deletedAt) {
|
|
231089
|
+
return null;
|
|
231090
|
+
}
|
|
231091
|
+
parkCleared = true;
|
|
231092
|
+
return { status: null, error: null, mergeRetries: 0 };
|
|
231093
|
+
});
|
|
231094
|
+
}
|
|
231095
|
+
const mergeRetriesReset = parkCleared;
|
|
230642
231096
|
if (reroute.rerouted) {
|
|
230643
|
-
await this.store.logEntry(task.id,
|
|
231097
|
+
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.`);
|
|
231098
|
+
if (parkCleared) {
|
|
231099
|
+
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.`);
|
|
231100
|
+
}
|
|
230644
231101
|
log34.warn(`Stale content approval for ${task.id} re-seeded at ${reroute.nodeId ?? "code-review"}`);
|
|
230645
231102
|
}
|
|
230646
|
-
const auditKey = `${task.id}:${reroute.reason}:${reroute.nodeId ?? ""}`;
|
|
231103
|
+
const auditKey = `${task.id}:${reroute.reason}:${reroute.nodeId ?? ""}:${parkShape ?? ""}`;
|
|
230647
231104
|
if (!this.staleContentRerouteAuditKeys.has(auditKey)) {
|
|
230648
231105
|
this.staleContentRerouteAuditKeys.add(auditKey);
|
|
230649
231106
|
await emitBoundedRunAudit2(this.store, {
|
|
@@ -230658,21 +231115,23 @@ var init_self_healing = __esm({
|
|
|
230658
231115
|
nodeId: reroute.nodeId,
|
|
230659
231116
|
workflowStepId: reroute.workflowStepId,
|
|
230660
231117
|
reason: reroute.reason,
|
|
230661
|
-
source: "self-healing"
|
|
231118
|
+
source: "self-healing",
|
|
231119
|
+
parkShape,
|
|
231120
|
+
parkCleared,
|
|
231121
|
+
mergeRetriesReset
|
|
230662
231122
|
}
|
|
230663
231123
|
});
|
|
230664
231124
|
}
|
|
230665
|
-
return
|
|
231125
|
+
return {
|
|
231126
|
+
handled: true,
|
|
231127
|
+
seeded: reroute.rerouted,
|
|
231128
|
+
nodeId: reroute.nodeId,
|
|
231129
|
+
parkShape,
|
|
231130
|
+
parkCleared,
|
|
231131
|
+
mergeRetriesReset
|
|
231132
|
+
};
|
|
230666
231133
|
}
|
|
230667
|
-
async routeUnrunPreMergeGateBackToReview(task, reviewColumns,
|
|
230668
|
-
let mergeGate;
|
|
230669
|
-
try {
|
|
230670
|
-
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
230671
|
-
} catch {
|
|
230672
|
-
return false;
|
|
230673
|
-
}
|
|
230674
|
-
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) return false;
|
|
230675
|
-
const mergeContent = await captureMergeContentDescriptor(task, { workspaceRootDir: this.options.rootDir, settings });
|
|
231134
|
+
async routeUnrunPreMergeGateBackToReview(task, reviewColumns, mergeGate, mergeContent) {
|
|
230676
231135
|
if (getTaskMergeBlocker(task, { reviewColumns, requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds, mergeContent }) !== "task has enabled pre-merge workflow steps that never ran") return false;
|
|
230677
231136
|
const reroute = await rerouteUnrunPreMergeGateToReview(this.store, task, { requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds, mergeContent }).catch(() => ({ rerouted: false, reason: "no-unrun-gate", nodeId: void 0, workflowStepId: void 0 }));
|
|
230678
231137
|
if (reroute.rerouted) {
|
|
@@ -230747,11 +231206,62 @@ var init_self_healing = __esm({
|
|
|
230747
231206
|
for (const task of mergeable) {
|
|
230748
231207
|
const reviewColumns = await ownReviewLanesFor(task);
|
|
230749
231208
|
if (!reviewColumns.has(task.column)) continue;
|
|
230750
|
-
|
|
230751
|
-
|
|
230752
|
-
|
|
231209
|
+
let mergeGate;
|
|
231210
|
+
try {
|
|
231211
|
+
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
231212
|
+
} catch {
|
|
231213
|
+
continue;
|
|
231214
|
+
}
|
|
231215
|
+
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) continue;
|
|
231216
|
+
const mergeContent = await captureMergeContentDescriptor(task, { workspaceRootDir: this.options.rootDir, settings });
|
|
231217
|
+
if ((await this.routeStaleSingularApprovalBackToReview(task, reviewColumns, mergeGate, mergeContent)).handled) continue;
|
|
231218
|
+
if (await this.routeUnrunPreMergeGateBackToReview(task, reviewColumns, mergeGate, mergeContent)) continue;
|
|
231219
|
+
const blocker = getTaskMergeBlocker(task, {
|
|
231220
|
+
reviewColumns,
|
|
231221
|
+
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
231222
|
+
mergeContent
|
|
231223
|
+
});
|
|
231224
|
+
if (blocker !== void 0) {
|
|
231225
|
+
const warningKey = `${task.id}:${blocker}`;
|
|
231226
|
+
if (!this.mergeRecoveryBlockerWarnKeys.has(warningKey)) {
|
|
231227
|
+
this.mergeRecoveryBlockerWarnKeys.add(warningKey);
|
|
231228
|
+
log34.warn(`mergeable-review recovery declined ${task.id}: ${blocker}`);
|
|
231229
|
+
}
|
|
231230
|
+
continue;
|
|
231231
|
+
}
|
|
230753
231232
|
laneQualifiedMergeable.push(task);
|
|
230754
231233
|
}
|
|
231234
|
+
const hiddenStaleContentCandidateIds = /* @__PURE__ */ new Set();
|
|
231235
|
+
for (const task of tasks2) {
|
|
231236
|
+
if (mergeable.includes(task)) continue;
|
|
231237
|
+
const parkShape = classifyStaleContentPark(task);
|
|
231238
|
+
if (!parkShape || mergeAdmissionByTaskId.get(task.id) !== true || executingIds.has(task.id) || task.status === "merging" || task.status === "merging-pr") continue;
|
|
231239
|
+
const reviewColumns = await ownReviewLanesFor(task);
|
|
231240
|
+
if (!reviewColumns.has(task.column)) continue;
|
|
231241
|
+
hiddenStaleContentCandidateIds.add(task.id);
|
|
231242
|
+
const attempts = (this.staleContentParkRecoveryAttempts.get(task.id) ?? 0) + 1;
|
|
231243
|
+
this.staleContentParkRecoveryAttempts.set(task.id, attempts);
|
|
231244
|
+
if (attempts === MAX_STARVATION_DROPS && !this.staleContentParkRecoveryBudgetLogged.has(task.id)) {
|
|
231245
|
+
this.staleContentParkRecoveryBudgetLogged.add(task.id);
|
|
231246
|
+
await this.store.logEntry(task.id, `[pre-merge] Stopped stale-content park recovery after ${MAX_STARVATION_DROPS} attempts; operator attention is required.`);
|
|
231247
|
+
}
|
|
231248
|
+
if (attempts > MAX_STARVATION_DROPS) continue;
|
|
231249
|
+
let mergeGate;
|
|
231250
|
+
try {
|
|
231251
|
+
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
231252
|
+
} catch {
|
|
231253
|
+
continue;
|
|
231254
|
+
}
|
|
231255
|
+
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) continue;
|
|
231256
|
+
const mergeContent = await captureMergeContentDescriptor(task, { workspaceRootDir: this.options.rootDir, settings });
|
|
231257
|
+
await this.routeStaleSingularApprovalBackToReview(task, reviewColumns, mergeGate, mergeContent, parkShape);
|
|
231258
|
+
}
|
|
231259
|
+
for (const taskId of this.staleContentParkRecoveryAttempts.keys()) {
|
|
231260
|
+
if (!hiddenStaleContentCandidateIds.has(taskId)) {
|
|
231261
|
+
this.staleContentParkRecoveryAttempts.delete(taskId);
|
|
231262
|
+
this.staleContentParkRecoveryBudgetLogged.delete(taskId);
|
|
231263
|
+
}
|
|
231264
|
+
}
|
|
230755
231265
|
if (unresolvedMergeableCards.length > 0) {
|
|
230756
231266
|
log34.warn(
|
|
230757
231267
|
`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.`
|
|
@@ -230774,6 +231284,9 @@ var init_self_healing = __esm({
|
|
|
230774
231284
|
);
|
|
230775
231285
|
const enqueueMerge = this.options.enqueueMerge;
|
|
230776
231286
|
let recovered = 0;
|
|
231287
|
+
let enqueued = 0;
|
|
231288
|
+
let merged = 0;
|
|
231289
|
+
let parked = 0;
|
|
230777
231290
|
for (const task of unownedMergeable) {
|
|
230778
231291
|
try {
|
|
230779
231292
|
if (enqueueMerge) {
|
|
@@ -230789,19 +231302,22 @@ var init_self_healing = __esm({
|
|
|
230789
231302
|
await this.store.updateTask(task.id, { status: "failed", error: error3 });
|
|
230790
231303
|
await this.store.logEntry(task.id, error3);
|
|
230791
231304
|
this.mergeStarvationDrops.delete(task.id);
|
|
231305
|
+
parked++;
|
|
230792
231306
|
recovered++;
|
|
230793
231307
|
}
|
|
230794
231308
|
continue;
|
|
230795
231309
|
}
|
|
230796
231310
|
this.mergeStarvationDrops.delete(task.id);
|
|
231311
|
+
enqueued++;
|
|
230797
231312
|
} else {
|
|
230798
231313
|
await this.store.mergeTask(task.id);
|
|
231314
|
+
merged++;
|
|
230799
231315
|
}
|
|
230800
231316
|
await this.store.logEntry(
|
|
230801
231317
|
task.id,
|
|
230802
231318
|
enqueueMerge ? "Auto-recovered: eligible in-review task re-enqueued for merge" : "Auto-recovered: eligible in-review task was merged and moved to done"
|
|
230803
231319
|
);
|
|
230804
|
-
log34.log(`
|
|
231320
|
+
log34.log(enqueueMerge ? `Re-enqueued mergeable review task ${task.id} for merge` : `Merged review task ${task.id} and moved it to done`);
|
|
230805
231321
|
recovered++;
|
|
230806
231322
|
} catch (err) {
|
|
230807
231323
|
const errorMessage6 = err instanceof Error ? err.message : String(err);
|
|
@@ -230809,7 +231325,7 @@ var init_self_healing = __esm({
|
|
|
230809
231325
|
}
|
|
230810
231326
|
}
|
|
230811
231327
|
if (recovered > 0) {
|
|
230812
|
-
log34.log(
|
|
231328
|
+
log34.log(formatMergeableReviewRecoverySummary({ enqueued, merged, parked }));
|
|
230813
231329
|
}
|
|
230814
231330
|
return recovered;
|
|
230815
231331
|
} catch (err) {
|
|
@@ -238801,7 +239317,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
238801
239317
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
238802
239318
|
await this.store.logEntry(task.id, retryMessage);
|
|
238803
239319
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
238804
|
-
status: this.
|
|
239320
|
+
status: await this.resolvePlanningRetryHoldStatus(task, written),
|
|
238805
239321
|
error: null,
|
|
238806
239322
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
238807
239323
|
nextRecoveryAt: decision.nextState.nextRecoveryAt
|
|
@@ -238838,9 +239354,13 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
238838
239354
|
const retryMessage = `Generated plan failed deterministic validation (${deterministicSpecFailure}) \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}.`;
|
|
238839
239355
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
238840
239356
|
await this.store.logEntry(task.id, retryMessage);
|
|
238841
|
-
|
|
239357
|
+
await this.store.logEntry(
|
|
239358
|
+
task.id,
|
|
239359
|
+
"AI spec revision requested",
|
|
239360
|
+
`Deterministic plan validation rejected the generated plan: ${deterministicSpecFailure}`
|
|
239361
|
+
).catch(() => void 0);
|
|
238842
239362
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
238843
|
-
status:
|
|
239363
|
+
status: await this.resolvePlanningRetryHoldStatus(task, written),
|
|
238844
239364
|
error: null,
|
|
238845
239365
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
238846
239366
|
nextRecoveryAt: decision.nextState.nextRecoveryAt
|
|
@@ -239010,9 +239530,10 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
239010
239530
|
const retryMessage = `${failureMessage} \u2014 retry ${decision.nextState.recoveryRetryCount}/${MAX_RECOVERY_RETRIES} in ${formatDelay(decision.delayMs)}.`;
|
|
239011
239531
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
239012
239532
|
await this.store.logEntry(task.id, retryMessage).catch(() => void 0);
|
|
239533
|
+
const retryHoldStatus = await this.resolvePlanningRetryHoldStatus(task);
|
|
239013
239534
|
await this.updatePlanningStateIfStillCurrent(task, (live) => ({
|
|
239014
239535
|
...persistMarker(live),
|
|
239015
|
-
status:
|
|
239536
|
+
status: retryHoldStatus,
|
|
239016
239537
|
error: null,
|
|
239017
239538
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
239018
239539
|
nextRecoveryAt: decision.nextState.nextRecoveryAt
|
|
@@ -239042,7 +239563,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
239042
239563
|
planLog.warn(`${task.id}: failed to log transient-error retry entry: ${msg}`);
|
|
239043
239564
|
});
|
|
239044
239565
|
}
|
|
239045
|
-
const restoreStatus = this.
|
|
239566
|
+
const restoreStatus = await this.resolvePlanningRetryHoldStatus(task);
|
|
239046
239567
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
239047
239568
|
status: restoreStatus,
|
|
239048
239569
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
@@ -239094,7 +239615,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
239094
239615
|
const msg = logErr instanceof Error ? logErr.message : String(logErr);
|
|
239095
239616
|
planLog.warn(`${task.id}: failed to log planning-failure retry entry: ${msg}`);
|
|
239096
239617
|
});
|
|
239097
|
-
const restoreStatus = this.
|
|
239618
|
+
const restoreStatus = await this.resolvePlanningRetryHoldStatus(task);
|
|
239098
239619
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
239099
239620
|
status: restoreStatus,
|
|
239100
239621
|
recoveryRetryCount: genericDecision.nextState.recoveryRetryCount,
|
|
@@ -239378,11 +239899,22 @@ ${lines.join("\n")}` }],
|
|
|
239378
239899
|
}
|
|
239379
239900
|
return null;
|
|
239380
239901
|
}
|
|
239902
|
+
/*
|
|
239903
|
+
FNXC:TriagePlanningRetry 2026-09-05-22:06:
|
|
239904
|
+
A retry over a finished-looking PROMPT.md must hold `needs-replan`, matching scheduler validation
|
|
239905
|
+
retries. Clearing status makes the card dispatchable and bypasses finalizeApprovedTask approval.
|
|
239906
|
+
*/
|
|
239907
|
+
async resolvePlanningRetryHoldStatus(task, knownPromptContent) {
|
|
239908
|
+
const restored = this.restoreStatusAfterInterruptedTriageWork(task);
|
|
239909
|
+
if (restored) return restored;
|
|
239910
|
+
const content = knownPromptContent ?? await this.readNonEmptyPromptDraft(task.id, "planning retry hold");
|
|
239911
|
+
return content && !isTaskAwaitingPlanning(task, content) ? "needs-replan" : null;
|
|
239912
|
+
}
|
|
239381
239913
|
async validateGeneratedPrompt(taskId, promptContent) {
|
|
239382
239914
|
if (!promptContent.trim()) {
|
|
239383
239915
|
return "PROMPT.md file not found or empty";
|
|
239384
239916
|
}
|
|
239385
|
-
const headingNumbers =
|
|
239917
|
+
const headingNumbers = matchStepHeadings(promptContent).map((match2) => match2.headingNumber);
|
|
239386
239918
|
if (headingNumbers.length > 0 && !headingNumbers.every((heading, index3) => heading === index3)) {
|
|
239387
239919
|
const diagnostic = `Step headings must be contiguous 0-based execution indices (observed: ${headingNumbers.join(", ")}). Renumber from Step 0 and update prose cross-references.`;
|
|
239388
239920
|
planLog.warn(`${taskId}: ${diagnostic}`);
|
|
@@ -239992,6 +240524,101 @@ ${lines.join("\n")}` }],
|
|
|
239992
240524
|
}
|
|
239993
240525
|
});
|
|
239994
240526
|
|
|
240527
|
+
// ../engine/src/executor/optional-step-revision.ts
|
|
240528
|
+
function normalizeOptionalStepRevisionKey2(value2) {
|
|
240529
|
+
return (value2 ?? "").trim().toLowerCase();
|
|
240530
|
+
}
|
|
240531
|
+
function optionalStepRevisionKey2(nodeId, stepName) {
|
|
240532
|
+
return normalizeOptionalStepRevisionKey2(nodeId) || normalizeOptionalStepRevisionKey2(stepName) || "pre-merge-optional-step";
|
|
240533
|
+
}
|
|
240534
|
+
function optionalStepRevisionResetOutcome(key) {
|
|
240535
|
+
return `${OPTIONAL_STEP_REVISION_RESET_MARKER} ${key}`;
|
|
240536
|
+
}
|
|
240537
|
+
function resetIndexFor(log44, normalizedKey) {
|
|
240538
|
+
for (let index3 = log44.length - 1; index3 >= 0; index3 -= 1) {
|
|
240539
|
+
const outcome = log44[index3]?.outcome ?? "";
|
|
240540
|
+
const markerIndex = outcome.indexOf(OPTIONAL_STEP_REVISION_RESET_MARKER);
|
|
240541
|
+
if (markerIndex < 0) continue;
|
|
240542
|
+
const markerValue = outcome.slice(markerIndex + OPTIONAL_STEP_REVISION_RESET_MARKER.length).split(/\r?\n/, 1)[0]?.trim();
|
|
240543
|
+
if (normalizeOptionalStepRevisionKey2(markerValue) === normalizedKey) return index3;
|
|
240544
|
+
}
|
|
240545
|
+
return -1;
|
|
240546
|
+
}
|
|
240547
|
+
function countOptionalStepRevisionAttempts2(task, key, stepName) {
|
|
240548
|
+
const normalizedKey = normalizeOptionalStepRevisionKey2(key);
|
|
240549
|
+
const normalizedStepName = normalizeOptionalStepRevisionKey2(stepName);
|
|
240550
|
+
const log44 = task.log ?? [];
|
|
240551
|
+
const since = resetIndexFor(log44, normalizedKey);
|
|
240552
|
+
return (since >= 0 ? log44.slice(since + 1) : log44).filter((entry) => {
|
|
240553
|
+
const action = entry.action ?? "";
|
|
240554
|
+
const outcome = entry.outcome ?? "";
|
|
240555
|
+
if (!/attempt \d+\//.test(action)) return false;
|
|
240556
|
+
const markerIndex = outcome.indexOf(OPTIONAL_STEP_REVISION_KEY_MARKER2);
|
|
240557
|
+
if (markerIndex >= 0) {
|
|
240558
|
+
const markerValue = outcome.slice(markerIndex + OPTIONAL_STEP_REVISION_KEY_MARKER2.length).split(/\r?\n/, 1)[0]?.trim();
|
|
240559
|
+
return normalizeOptionalStepRevisionKey2(markerValue) === normalizedKey;
|
|
240560
|
+
}
|
|
240561
|
+
if (!normalizedStepName) return false;
|
|
240562
|
+
return normalizeOptionalStepRevisionKey2(outcome).includes(`step: ${normalizedStepName}`);
|
|
240563
|
+
}).length;
|
|
240564
|
+
}
|
|
240565
|
+
function optionalStepRevisionLogOutcome2(details, key) {
|
|
240566
|
+
return `${details}
|
|
240567
|
+
${OPTIONAL_STEP_REVISION_KEY_MARKER2} ${key}`;
|
|
240568
|
+
}
|
|
240569
|
+
function buildReviewConvergenceContext(task, options) {
|
|
240570
|
+
const revisionKey = options.revisionKey;
|
|
240571
|
+
const priorAttemptCount = countPlanReviewRevisionAttempts(task.workflowStepResults, { revisionKey });
|
|
240572
|
+
const attempt = priorAttemptCount + 1;
|
|
240573
|
+
if (attempt <= 1) return "";
|
|
240574
|
+
const history = collectPlanReviewFeedbackHistory(task.workflowStepResults, { revisionKey });
|
|
240575
|
+
const lines = [
|
|
240576
|
+
`## Convergence \u2014 ${options.reviewKind === "plan" ? "Plan Review" : "Code Review"} attempt ${attempt}`
|
|
240577
|
+
];
|
|
240578
|
+
if (options.changeSummaryBlock?.trim()) lines.push("", options.changeSummaryBlock.trim());
|
|
240579
|
+
lines.push(
|
|
240580
|
+
"Treat the cumulative prior feedback below as a decision primer. Verify each prior blocker against the current PROMPT.md before looking for new findings.",
|
|
240581
|
+
"- Do not re-raise a resolved or semantically duplicate blocker.",
|
|
240582
|
+
"- 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."
|
|
240583
|
+
);
|
|
240584
|
+
if (attempt >= 3) {
|
|
240585
|
+
lines.push(
|
|
240586
|
+
"- Severity ratchet (attempt 3+): only delivery-blocking critical defects may return REVISE; important/minor wording or implementation-detail findings are advisory."
|
|
240587
|
+
);
|
|
240588
|
+
}
|
|
240589
|
+
if (history.length > 0) {
|
|
240590
|
+
lines.push("", "### Cumulative prior Plan Review ledger");
|
|
240591
|
+
history.forEach((feedback, index3) => lines.push(`#### PR${index3 + 1}`, feedback));
|
|
240592
|
+
}
|
|
240593
|
+
const own2 = task.workflowStepResults?.find((result2) => result2.workflowStepId === revisionKey);
|
|
240594
|
+
const findings = (own2?.priorAttempts ?? []).flatMap((attempt2) => attempt2.findings ?? []).filter(isOpenWorkflowReviewFinding);
|
|
240595
|
+
if (findings.length > 0) {
|
|
240596
|
+
lines.push("", "### Your prior findings on this gate");
|
|
240597
|
+
findings.forEach((finding) => lines.push(`- ${finding.id} \u2014 [${finding.severity ?? "unclassified"}] ${finding.title}: ${finding.body}${finding.filePath ? ` (${finding.filePath}${finding.line ? `:${finding.line}` : ""})` : ""}`));
|
|
240598
|
+
lines.push("Match prior defects by file path, line region, and described behavior; IDs and titles may change between rounds.");
|
|
240599
|
+
}
|
|
240600
|
+
const disputed = findings.filter((finding) => finding.disputedAt);
|
|
240601
|
+
if (disputed.length > 0) {
|
|
240602
|
+
lines.push("", "### Disputed by the implementer");
|
|
240603
|
+
disputed.forEach((finding) => lines.push(`- ${finding.id}: ${finding.disputeRationale ?? "No rationale recorded."}`));
|
|
240604
|
+
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.");
|
|
240605
|
+
}
|
|
240606
|
+
return lines.join("\n");
|
|
240607
|
+
}
|
|
240608
|
+
function buildGraphPlanReviewConvergenceContext(task, revisionKey) {
|
|
240609
|
+
return buildReviewConvergenceContext(task, { revisionKey, reviewKind: "plan" });
|
|
240610
|
+
}
|
|
240611
|
+
var OPTIONAL_STEP_REVISION_KEY_MARKER2, OPTIONAL_STEP_REVISION_RESET_MARKER;
|
|
240612
|
+
var init_optional_step_revision = __esm({
|
|
240613
|
+
"../engine/src/executor/optional-step-revision.ts"() {
|
|
240614
|
+
"use strict";
|
|
240615
|
+
init_src2();
|
|
240616
|
+
init_plan_review_feedback_history();
|
|
240617
|
+
OPTIONAL_STEP_REVISION_KEY_MARKER2 = "Workflow revision key:";
|
|
240618
|
+
OPTIONAL_STEP_REVISION_RESET_MARKER = "Workflow revision ledger reset:";
|
|
240619
|
+
}
|
|
240620
|
+
});
|
|
240621
|
+
|
|
239995
240622
|
// ../engine/src/executor/workflow-gate-activity.ts
|
|
239996
240623
|
function resolveWorkflowGateActivityClaim(routedPrincipalAgentId, assignedAgentId) {
|
|
239997
240624
|
const agentId = routedPrincipalAgentId ?? assignedAgentId ?? "executor";
|
|
@@ -240823,7 +241450,7 @@ function extractPromptSection(promptContent, sectionName) {
|
|
|
240823
241450
|
return promptContent.slice(start, end).trim();
|
|
240824
241451
|
}
|
|
240825
241452
|
function summarizePromptSteps(promptContent) {
|
|
240826
|
-
const stepTitles =
|
|
241453
|
+
const stepTitles = matchStepHeadings(promptContent).map(({ index: index3, headingLineEnd }) => promptContent.slice(index3, headingLineEnd).trim());
|
|
240827
241454
|
if (stepTitles.length === 0) {
|
|
240828
241455
|
return "";
|
|
240829
241456
|
}
|
|
@@ -245594,85 +246221,6 @@ var init_review_inline_fix_recapture = __esm({
|
|
|
245594
246221
|
}
|
|
245595
246222
|
});
|
|
245596
246223
|
|
|
245597
|
-
// ../engine/src/executor/optional-step-revision.ts
|
|
245598
|
-
function normalizeOptionalStepRevisionKey2(value2) {
|
|
245599
|
-
return (value2 ?? "").trim().toLowerCase();
|
|
245600
|
-
}
|
|
245601
|
-
function optionalStepRevisionKey2(nodeId, stepName) {
|
|
245602
|
-
return normalizeOptionalStepRevisionKey2(nodeId) || normalizeOptionalStepRevisionKey2(stepName) || "pre-merge-optional-step";
|
|
245603
|
-
}
|
|
245604
|
-
function countOptionalStepRevisionAttempts2(task, key, stepName) {
|
|
245605
|
-
const normalizedKey = normalizeOptionalStepRevisionKey2(key);
|
|
245606
|
-
const normalizedStepName = normalizeOptionalStepRevisionKey2(stepName);
|
|
245607
|
-
return (task.log ?? []).filter((entry) => {
|
|
245608
|
-
const action = entry.action ?? "";
|
|
245609
|
-
const outcome = entry.outcome ?? "";
|
|
245610
|
-
if (!/attempt \d+\//.test(action)) return false;
|
|
245611
|
-
const markerIndex = outcome.indexOf(OPTIONAL_STEP_REVISION_KEY_MARKER2);
|
|
245612
|
-
if (markerIndex >= 0) {
|
|
245613
|
-
const markerValue = outcome.slice(markerIndex + OPTIONAL_STEP_REVISION_KEY_MARKER2.length).split(/\r?\n/, 1)[0]?.trim();
|
|
245614
|
-
return normalizeOptionalStepRevisionKey2(markerValue) === normalizedKey;
|
|
245615
|
-
}
|
|
245616
|
-
if (!normalizedStepName) return false;
|
|
245617
|
-
return normalizeOptionalStepRevisionKey2(outcome).includes(`step: ${normalizedStepName}`);
|
|
245618
|
-
}).length;
|
|
245619
|
-
}
|
|
245620
|
-
function optionalStepRevisionLogOutcome2(details, key) {
|
|
245621
|
-
return `${details}
|
|
245622
|
-
${OPTIONAL_STEP_REVISION_KEY_MARKER2} ${key}`;
|
|
245623
|
-
}
|
|
245624
|
-
function buildReviewConvergenceContext(task, options) {
|
|
245625
|
-
const revisionKey = options.revisionKey;
|
|
245626
|
-
const priorAttemptCount = countPlanReviewRevisionAttempts(task.workflowStepResults, { revisionKey });
|
|
245627
|
-
const attempt = priorAttemptCount + 1;
|
|
245628
|
-
if (attempt <= 1) return "";
|
|
245629
|
-
const history = collectPlanReviewFeedbackHistory(task.workflowStepResults, { revisionKey });
|
|
245630
|
-
const lines = [
|
|
245631
|
-
`## Convergence \u2014 ${options.reviewKind === "plan" ? "Plan Review" : "Code Review"} attempt ${attempt}`
|
|
245632
|
-
];
|
|
245633
|
-
if (options.changeSummaryBlock?.trim()) lines.push("", options.changeSummaryBlock.trim());
|
|
245634
|
-
lines.push(
|
|
245635
|
-
"Treat the cumulative prior feedback below as a decision primer. Verify each prior blocker against the current PROMPT.md before looking for new findings.",
|
|
245636
|
-
"- Do not re-raise a resolved or semantically duplicate blocker.",
|
|
245637
|
-
"- 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."
|
|
245638
|
-
);
|
|
245639
|
-
if (attempt >= 3) {
|
|
245640
|
-
lines.push(
|
|
245641
|
-
"- Severity ratchet (attempt 3+): only delivery-blocking critical defects may return REVISE; important/minor wording or implementation-detail findings are advisory."
|
|
245642
|
-
);
|
|
245643
|
-
}
|
|
245644
|
-
if (history.length > 0) {
|
|
245645
|
-
lines.push("", "### Cumulative prior Plan Review ledger");
|
|
245646
|
-
history.forEach((feedback, index3) => lines.push(`#### PR${index3 + 1}`, feedback));
|
|
245647
|
-
}
|
|
245648
|
-
const own2 = task.workflowStepResults?.find((result2) => result2.workflowStepId === revisionKey);
|
|
245649
|
-
const findings = (own2?.priorAttempts ?? []).flatMap((attempt2) => attempt2.findings ?? []).filter(isOpenWorkflowReviewFinding);
|
|
245650
|
-
if (findings.length > 0) {
|
|
245651
|
-
lines.push("", "### Your prior findings on this gate");
|
|
245652
|
-
findings.forEach((finding) => lines.push(`- ${finding.id} \u2014 [${finding.severity ?? "unclassified"}] ${finding.title}: ${finding.body}${finding.filePath ? ` (${finding.filePath}${finding.line ? `:${finding.line}` : ""})` : ""}`));
|
|
245653
|
-
lines.push("Match prior defects by file path, line region, and described behavior; IDs and titles may change between rounds.");
|
|
245654
|
-
}
|
|
245655
|
-
const disputed = findings.filter((finding) => finding.disputedAt);
|
|
245656
|
-
if (disputed.length > 0) {
|
|
245657
|
-
lines.push("", "### Disputed by the implementer");
|
|
245658
|
-
disputed.forEach((finding) => lines.push(`- ${finding.id}: ${finding.disputeRationale ?? "No rationale recorded."}`));
|
|
245659
|
-
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.");
|
|
245660
|
-
}
|
|
245661
|
-
return lines.join("\n");
|
|
245662
|
-
}
|
|
245663
|
-
function buildGraphPlanReviewConvergenceContext(task, revisionKey) {
|
|
245664
|
-
return buildReviewConvergenceContext(task, { revisionKey, reviewKind: "plan" });
|
|
245665
|
-
}
|
|
245666
|
-
var OPTIONAL_STEP_REVISION_KEY_MARKER2;
|
|
245667
|
-
var init_optional_step_revision = __esm({
|
|
245668
|
-
"../engine/src/executor/optional-step-revision.ts"() {
|
|
245669
|
-
"use strict";
|
|
245670
|
-
init_src2();
|
|
245671
|
-
init_plan_review_feedback_history();
|
|
245672
|
-
OPTIONAL_STEP_REVISION_KEY_MARKER2 = "Workflow revision key:";
|
|
245673
|
-
}
|
|
245674
|
-
});
|
|
245675
|
-
|
|
245676
246224
|
// ../engine/src/executor/execute-workflow-step.ts
|
|
245677
246225
|
import { exec as exec44 } from "node:child_process";
|
|
245678
246226
|
import { promisify as promisify63 } from "node:util";
|
|
@@ -246438,10 +246986,12 @@ ${JSON.stringify({ verdict: repairedVerdict })}`,
|
|
|
246438
246986
|
session.dispose();
|
|
246439
246987
|
await agentLogger.flush();
|
|
246440
246988
|
if (parsed.verdict) {
|
|
246989
|
+
const findingsUnreadable = verdictRepairResult === "repaired" && (parsed.findings?.length ?? 0) === 0;
|
|
246441
246990
|
const gated = reviewBlockingSeverity ? applyReviewSeverityGate({
|
|
246442
246991
|
verdict: parsed.verdict,
|
|
246443
246992
|
findings: parsed.findings,
|
|
246444
|
-
threshold: reviewBlockingSeverity
|
|
246993
|
+
threshold: reviewBlockingSeverity,
|
|
246994
|
+
findingsUnreadable
|
|
246445
246995
|
}) : void 0;
|
|
246446
246996
|
const effectiveVerdict = gated?.verdict ?? parsed.verdict;
|
|
246447
246997
|
if (gated?.downgraded) {
|
|
@@ -260956,7 +261506,7 @@ async function routeGraphMergeFailureToRetry(deps, live, result2, abortProvenanc
|
|
|
260956
261506
|
const parked = await parkTaskFailed(
|
|
260957
261507
|
deps.store,
|
|
260958
261508
|
live.id,
|
|
260959
|
-
|
|
261509
|
+
`${AUTO_MERGE_RETRY_REJECTED_PREFIX} ${reason}`,
|
|
260960
261510
|
deps.getRunContextFor(live.id),
|
|
260961
261511
|
live.columnMovedAt
|
|
260962
261512
|
);
|
|
@@ -260972,6 +261522,7 @@ var init_route_graph_merge_failure_to_retry = __esm({
|
|
|
260972
261522
|
init_logger3();
|
|
260973
261523
|
init_workflow_merge_nodes();
|
|
260974
261524
|
init_emit_merge_boundary_unproven_audit();
|
|
261525
|
+
init_stale_content_park();
|
|
260975
261526
|
BIT_BACKOFF_MS = [1e3, 2e3, 4e3, 8e3, 16e3, 32e3, 64e3];
|
|
260976
261527
|
}
|
|
260977
261528
|
});
|
|
@@ -265633,9 +266184,13 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
|
|
|
265633
266184
|
*/
|
|
265634
266185
|
credentialInstanceId: detail.credentialInstanceId,
|
|
265635
266186
|
resolveCredentialInstanceRetarget: nextStepInstance,
|
|
265636
|
-
|
|
265637
|
-
|
|
265638
|
-
|
|
266187
|
+
/*
|
|
266188
|
+
FNXC:ColumnAgent 2026-09-04-14:11:
|
|
266189
|
+
The effective step identity is the governing column agent or the authoritative assignee.
|
|
266190
|
+
Both are existence-proved reads; with empty project.boards the column seam is absent, so
|
|
266191
|
+
retaining only it lost attribution for every assigned task.
|
|
266192
|
+
*/
|
|
266193
|
+
effectiveAgentId: stepIdentityAgent?.id,
|
|
265639
266194
|
actionGateContext: deps.buildActionGateContext(task.id, stepIdentityAgent, settings.defaultAgentPermissionPolicy),
|
|
265640
266195
|
permanentAgentGating: deps.buildPermanentAgentGatingContext(task.id, stepIdentityAgent, settings.defaultAgentPermissionPolicy),
|
|
265641
266196
|
// 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.
|
|
@@ -270498,10 +271053,22 @@ var init_terminate_all_children = __esm({
|
|
|
270498
271053
|
});
|
|
270499
271054
|
|
|
270500
271055
|
// ../engine/src/executor/clear-terminal-step-failures-for-retry.ts
|
|
271056
|
+
function latestTerminalPreMergeFailureId(results) {
|
|
271057
|
+
return (results ?? []).filter((result2) => (result2.phase || "pre-merge") === "pre-merge" && (result2.status === "failed" || result2.status === "advisory_failure")).sort((left, right) => {
|
|
271058
|
+
const leftTime = Date.parse(left.completedAt || left.startedAt || "");
|
|
271059
|
+
const rightTime = Date.parse(right.completedAt || right.startedAt || "");
|
|
271060
|
+
return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
|
|
271061
|
+
})[0]?.workflowStepId;
|
|
271062
|
+
}
|
|
270501
271063
|
async function clearTerminalStepFailuresForRetry(deps, taskId, mode) {
|
|
270502
271064
|
const live = await deps.store.getTask(taskId).catch(() => null);
|
|
270503
271065
|
if (!live) return;
|
|
270504
|
-
const
|
|
271066
|
+
const remediatedStepId = mode === "archive" ? latestTerminalPreMergeFailureId(live.workflowStepResults) : void 0;
|
|
271067
|
+
const cleared = mode === "archive" ? archiveTerminalWorkflowStepFailures(
|
|
271068
|
+
live.workflowStepResults,
|
|
271069
|
+
void 0,
|
|
271070
|
+
remediatedStepId ? { workflowStepIds: /* @__PURE__ */ new Set([remediatedStepId]) } : {}
|
|
271071
|
+
) : clearTerminalWorkflowStepFailures(live.workflowStepResults);
|
|
270505
271072
|
if (cleared !== live.workflowStepResults) {
|
|
270506
271073
|
await deps.store.updateTask(taskId, { workflowStepResults: cleared }, deps.getRunContextFor(taskId));
|
|
270507
271074
|
}
|
|
@@ -275108,10 +275675,14 @@ var init_mission_execution_loop = __esm({
|
|
|
275108
275675
|
for (const slice of milestone.slices) {
|
|
275109
275676
|
if (slice.status !== "active") continue;
|
|
275110
275677
|
const supersededFixes = await this.missionStore.reconcileSupersededGeneratedFixFeatures(slice.id);
|
|
275111
|
-
const supersededFeatureIds = new Set(supersededFixes.featureIds);
|
|
275678
|
+
const supersededFeatureIds = new Set(supersededFixes.featureIds ?? []);
|
|
275679
|
+
if ((supersededFixes.repairedCount ?? 0) > 0) {
|
|
275680
|
+
loopLog.warn(`Recovery: restored ${supersededFixes.repairedCount ?? 0} generated Fix Features in slice ${slice.id}: ${(supersededFixes.repairedFeatureIds ?? []).join(", ")}`);
|
|
275681
|
+
recoveredCount += supersededFixes.repairedCount ?? 0;
|
|
275682
|
+
}
|
|
275112
275683
|
if (supersededFixes.supersededCount > 0) {
|
|
275113
275684
|
loopLog.warn(
|
|
275114
|
-
`Recovery: superseded ${supersededFixes.supersededCount} generated Fix Features in slice ${slice.id} because an ancestor feature already passed
|
|
275685
|
+
`Recovery: superseded ${supersededFixes.supersededCount} generated Fix Features in slice ${slice.id} because an ancestor or the Fix feature's own validation already passed`
|
|
275115
275686
|
);
|
|
275116
275687
|
recoveredCount += supersededFixes.supersededCount;
|
|
275117
275688
|
}
|
|
@@ -298121,7 +298692,7 @@ ${detail}`);
|
|
|
298121
298692
|
metadata: { taskId: task.id, nodeId: reroute.nodeId, workflowStepId: reroute.workflowStepId, reason: reroute.reason, source: "merge-gate", missingGateCount: mergeGate.requiredPreMergeStepIds.size }
|
|
298122
298693
|
});
|
|
298123
298694
|
}
|
|
298124
|
-
if (blocker
|
|
298695
|
+
if (isStaleContentApprovalBlocker(blocker) && mergeContent.kind === "singular") {
|
|
298125
298696
|
const reroute = await rerouteSingularStaleContentToReview(store, task, {
|
|
298126
298697
|
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
298127
298698
|
mergeContent
|
|
@@ -298131,9 +298702,10 @@ ${detail}`);
|
|
|
298131
298702
|
nodeId: void 0,
|
|
298132
298703
|
workflowStepId: void 0
|
|
298133
298704
|
}));
|
|
298134
|
-
|
|
298135
|
-
|
|
298136
|
-
|
|
298705
|
+
await store.logEntry(
|
|
298706
|
+
task.id,
|
|
298707
|
+
`[pre-merge] Review lane '${reroute.nodeId ?? "unknown"}' approved older content; re-review is owned by the workflow graph (${reroute.reason}).`
|
|
298708
|
+
);
|
|
298137
298709
|
const auditKey = `${task.id}:${reroute.reason}`;
|
|
298138
298710
|
if (!this.staleContentRerouteAuditKeys.has(auditKey)) {
|
|
298139
298711
|
this.staleContentRerouteAuditKeys.add(auditKey);
|
|
@@ -302499,6 +303071,7 @@ __export(src_exports2, {
|
|
|
302499
303071
|
NotificationService: () => NotificationService,
|
|
302500
303072
|
NtfyNotificationProvider: () => NtfyNotificationProvider,
|
|
302501
303073
|
NtfyNotifier: () => NtfyNotifier,
|
|
303074
|
+
OPTIONAL_STEP_REVISION_RESET_MARKER: () => OPTIONAL_STEP_REVISION_RESET_MARKER,
|
|
302502
303075
|
OVERSEER_ADVISOR_REPLY_CONTRACT: () => OVERSEER_ADVISOR_REPLY_CONTRACT,
|
|
302503
303076
|
OVERSEER_ADVISOR_SYSTEM_PROMPT: () => OVERSEER_ADVISOR_SYSTEM_PROMPT,
|
|
302504
303077
|
OVERSEER_WATCHED_STAGES: () => OVERSEER_WATCHED_STAGES,
|
|
@@ -302678,6 +303251,7 @@ __export(src_exports2, {
|
|
|
302678
303251
|
createFusionModelRegistry: () => createFusionModelRegistry,
|
|
302679
303252
|
createGetAgentConfigTool: () => createGetAgentConfigTool,
|
|
302680
303253
|
createGoalRetrievalTools: () => createGoalRetrievalTools,
|
|
303254
|
+
createHistoryReadTool: () => createHistoryReadTool,
|
|
302681
303255
|
createIdeationTools: () => createIdeationTools,
|
|
302682
303256
|
createInteractiveAiSessionWith: () => createInteractiveAiSessionWith,
|
|
302683
303257
|
createListAgentsTool: () => createListAgentsTool,
|
|
@@ -302687,7 +303261,6 @@ __export(src_exports2, {
|
|
|
302687
303261
|
createNoopLegacySeams: () => createNoopLegacySeams,
|
|
302688
303262
|
createParseStepsHandler: () => createParseStepsHandler,
|
|
302689
303263
|
createParsingOverseerAgent: () => createParsingOverseerAgent,
|
|
302690
|
-
createPatchnodeReadTool: () => createPatchnodeReadTool,
|
|
302691
303264
|
createPrNodeHandlers: () => createPrNodeHandlers,
|
|
302692
303265
|
createPrimitivePromptLikeHandler: () => createPrimitivePromptLikeHandler,
|
|
302693
303266
|
createPrimitiveStepReviewHandler: () => createPrimitiveStepReviewHandler,
|
|
@@ -302859,6 +303432,7 @@ __export(src_exports2, {
|
|
|
302859
303432
|
normalizeAgentLogPaging: () => normalizeAgentLogPaging,
|
|
302860
303433
|
normalizeJiraIssueKey: () => normalizeJiraIssueKey,
|
|
302861
303434
|
notifyAutostashOrphans: () => notifyAutostashOrphans,
|
|
303435
|
+
optionalStepRevisionResetOutcome: () => optionalStepRevisionResetOutcome,
|
|
302862
303436
|
parseAdvisorReplyForAdvice: () => parseAdvisorReplyForAdvice,
|
|
302863
303437
|
parseInteractiveAgentResponse: () => parseAgentResponse,
|
|
302864
303438
|
parsePrEntityMarker: () => parsePrEntityMarker,
|
|
@@ -303048,6 +303622,7 @@ var init_src5 = __esm({
|
|
|
303048
303622
|
init_postgres_migration_notice();
|
|
303049
303623
|
init_concurrency();
|
|
303050
303624
|
init_triage();
|
|
303625
|
+
init_optional_step_revision();
|
|
303051
303626
|
init_executor();
|
|
303052
303627
|
init_workflow_graph_executor();
|
|
303053
303628
|
init_workflow_graph_branches();
|
|
@@ -326943,6 +327518,13 @@ async function restartTaskStage(deps) {
|
|
|
326943
327518
|
fenced = false;
|
|
326944
327519
|
const preservedWorkspaceRepositoryRecords = Object.keys(task.workspaceWorktrees ?? {}).length;
|
|
326945
327520
|
await store.logEntry(taskId, `Retry requested from dashboard (${plan.scope} restart in ${task.column}, discarded ${plan.discardedWorkflowStepIds.length} workflow step result(s), preserved ${preservedWorkspaceRepositoryRecords} workspace repository record(s), re-entering at ${plan.entryNodeId})`);
|
|
327521
|
+
for (const workflowStepId of plan.discardedWorkflowStepIds) {
|
|
327522
|
+
await store.logEntry(
|
|
327523
|
+
taskId,
|
|
327524
|
+
`Revision budget reset for '${workflowStepId}' by operator retry`,
|
|
327525
|
+
optionalStepRevisionResetOutcome(workflowStepId)
|
|
327526
|
+
).catch(() => void 0);
|
|
327527
|
+
}
|
|
326946
327528
|
const updated = await store.getTask(taskId);
|
|
326947
327529
|
if (!updated) throw notFound(`Task ${taskId} not found after retry`);
|
|
326948
327530
|
return updated;
|
|
@@ -330057,8 +330639,9 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
|
|
|
330057
330639
|
);
|
|
330058
330640
|
}
|
|
330059
330641
|
const { store: scopedStore } = await getProjectContext2(req);
|
|
330060
|
-
const
|
|
330061
|
-
|
|
330642
|
+
const result2 = await scopedStore.archiveAllDone();
|
|
330643
|
+
const { archived, skipped } = Array.isArray(result2) ? { archived: result2, skipped: [] } : result2;
|
|
330644
|
+
res.json({ archived, skipped });
|
|
330062
330645
|
} catch (err) {
|
|
330063
330646
|
if (err instanceof ApiError) {
|
|
330064
330647
|
throw err;
|
|
@@ -335169,6 +335752,261 @@ var init_task_planner_chat_metrics = __esm({
|
|
|
335169
335752
|
}
|
|
335170
335753
|
});
|
|
335171
335754
|
|
|
335755
|
+
// ../dashboard/src/chat-conversation-references.ts
|
|
335756
|
+
function textResult(text4, details = {}) {
|
|
335757
|
+
return { content: [{ type: "text", text: text4 }], details };
|
|
335758
|
+
}
|
|
335759
|
+
function errorResult(text4 = UNAVAILABLE_TOOL_MESSAGE) {
|
|
335760
|
+
return { content: [{ type: "text", text: text4 }], isError: true, details: {} };
|
|
335761
|
+
}
|
|
335762
|
+
function clampInteger(value2, fallback2, minimum, maximum) {
|
|
335763
|
+
if (typeof value2 !== "number" || !Number.isFinite(value2)) return fallback2;
|
|
335764
|
+
return Math.min(maximum, Math.max(minimum, Math.trunc(value2)));
|
|
335765
|
+
}
|
|
335766
|
+
function truncateText2(value2, maximum) {
|
|
335767
|
+
if (value2.length <= maximum) return { text: value2, truncated: false };
|
|
335768
|
+
if (maximum <= 1) return { text: "\u2026".slice(0, maximum), truncated: true };
|
|
335769
|
+
return { text: `${value2.slice(0, maximum - 1)}\u2026`, truncated: true };
|
|
335770
|
+
}
|
|
335771
|
+
function boundToolResponse(value2, hint) {
|
|
335772
|
+
if (value2.length <= CONVERSATION_TOOL_RESPONSE_MAX_CHARS) return value2;
|
|
335773
|
+
const prefixLength = CONVERSATION_TOOL_RESPONSE_MAX_CHARS - hint.length - 2;
|
|
335774
|
+
return `${value2.slice(0, prefixLength)}
|
|
335775
|
+
|
|
335776
|
+
${hint}`;
|
|
335777
|
+
}
|
|
335778
|
+
function wrapReferenceBlock(id, body) {
|
|
335779
|
+
return `[Referenced Conversation: ${id}]
|
|
335780
|
+
${body}
|
|
335781
|
+
[/Referenced Conversation: ${id}]`;
|
|
335782
|
+
}
|
|
335783
|
+
function formatModel(session) {
|
|
335784
|
+
if (session.modelProvider && session.modelId) return `${session.modelProvider}/${session.modelId}`;
|
|
335785
|
+
return session.modelId ?? session.modelProvider ?? "default";
|
|
335786
|
+
}
|
|
335787
|
+
function formatSessionHeader(session, currentSessionId) {
|
|
335788
|
+
return [
|
|
335789
|
+
`Conversation: ${session.id}${session.id === currentSessionId ? " (current conversation)" : ""}`,
|
|
335790
|
+
`Title: ${session.title ?? "Untitled"}`,
|
|
335791
|
+
`Status: ${session.status}`,
|
|
335792
|
+
`Agent: ${session.agentId}`,
|
|
335793
|
+
`Model: ${formatModel(session)}`,
|
|
335794
|
+
`Created: ${session.createdAt}`,
|
|
335795
|
+
`Updated: ${session.updatedAt}`
|
|
335796
|
+
].join("\n");
|
|
335797
|
+
}
|
|
335798
|
+
function formatMessage(message, maximum) {
|
|
335799
|
+
const content = truncateText2(message.content, maximum);
|
|
335800
|
+
return {
|
|
335801
|
+
text: `[${message.role} @ ${message.createdAt}] ${content.text}`,
|
|
335802
|
+
truncated: content.truncated
|
|
335803
|
+
};
|
|
335804
|
+
}
|
|
335805
|
+
async function resolveProjectSession(chatStore, conversationId, projectId2) {
|
|
335806
|
+
try {
|
|
335807
|
+
const session = await chatStore.getSession(conversationId);
|
|
335808
|
+
return session?.projectId === projectId2 ? session : void 0;
|
|
335809
|
+
} catch {
|
|
335810
|
+
return void 0;
|
|
335811
|
+
}
|
|
335812
|
+
}
|
|
335813
|
+
function parseConversationReferences(content) {
|
|
335814
|
+
const references = [];
|
|
335815
|
+
const seen = /* @__PURE__ */ new Set();
|
|
335816
|
+
const pattern = /(?:^|[\s,.;:!?"'()[\]{}])#(chat-[a-zA-Z0-9]{8})(?![./a-zA-Z0-9-])/g;
|
|
335817
|
+
let match2;
|
|
335818
|
+
while ((match2 = pattern.exec(content)) !== null) {
|
|
335819
|
+
const id = match2[1];
|
|
335820
|
+
if (seen.has(id)) continue;
|
|
335821
|
+
seen.add(id);
|
|
335822
|
+
references.push(id);
|
|
335823
|
+
if (references.length >= MAX_REFERENCED_CONVERSATIONS) break;
|
|
335824
|
+
}
|
|
335825
|
+
return references;
|
|
335826
|
+
}
|
|
335827
|
+
async function buildConversationReferenceContext(input) {
|
|
335828
|
+
const references = parseConversationReferences(input.content);
|
|
335829
|
+
if (references.length === 0) return "";
|
|
335830
|
+
const blocks = await Promise.all(references.map(async (id) => {
|
|
335831
|
+
try {
|
|
335832
|
+
const session = await input.chatStore.getSession(id);
|
|
335833
|
+
if (!session || session.projectId !== input.currentProjectId) {
|
|
335834
|
+
return wrapReferenceBlock(id, UNAVAILABLE_CONTEXT_MESSAGE);
|
|
335835
|
+
}
|
|
335836
|
+
if (session.id === input.currentSessionId) {
|
|
335837
|
+
return wrapReferenceBlock(
|
|
335838
|
+
id,
|
|
335839
|
+
"Current conversation: this ID refers to the current conversation; no preview was added."
|
|
335840
|
+
);
|
|
335841
|
+
}
|
|
335842
|
+
const messages3 = await input.chatStore.getMessages(id, {
|
|
335843
|
+
limit: CONVERSATION_PREVIEW_MESSAGE_COUNT,
|
|
335844
|
+
order: "desc"
|
|
335845
|
+
});
|
|
335846
|
+
const preview2 = messages3.length > 0 ? messages3.map((message) => formatMessage(message, CONVERSATION_PREVIEW_MESSAGE_CHARS).text).join("\n") : "(no messages)";
|
|
335847
|
+
return wrapReferenceBlock(
|
|
335848
|
+
id,
|
|
335849
|
+
[
|
|
335850
|
+
formatSessionHeader(session, input.currentSessionId),
|
|
335851
|
+
"Recent messages (newest first):",
|
|
335852
|
+
preview2,
|
|
335853
|
+
"Use fn_chat_conversation_read or fn_chat_conversation_search for more detail."
|
|
335854
|
+
].join("\n")
|
|
335855
|
+
);
|
|
335856
|
+
} catch {
|
|
335857
|
+
return wrapReferenceBlock(id, UNAVAILABLE_CONTEXT_MESSAGE);
|
|
335858
|
+
}
|
|
335859
|
+
}));
|
|
335860
|
+
return blocks.join("\n\n");
|
|
335861
|
+
}
|
|
335862
|
+
function createConversationReadTool(chatStore, options) {
|
|
335863
|
+
return {
|
|
335864
|
+
name: "fn_chat_conversation_read",
|
|
335865
|
+
label: "Read Chat Conversation",
|
|
335866
|
+
description: "Read a bounded page of messages from a conversation in the current Direct chat project. Requires an explicit conversation_id.",
|
|
335867
|
+
parameters: {
|
|
335868
|
+
type: "object",
|
|
335869
|
+
properties: {
|
|
335870
|
+
conversation_id: { type: "string" },
|
|
335871
|
+
limit: { type: "number", minimum: 1 },
|
|
335872
|
+
offset: { type: "number", minimum: 0 },
|
|
335873
|
+
order: { type: "string", enum: ["asc", "desc"] }
|
|
335874
|
+
},
|
|
335875
|
+
required: ["conversation_id"],
|
|
335876
|
+
additionalProperties: false
|
|
335877
|
+
},
|
|
335878
|
+
execute: async (_toolCallId, raw) => {
|
|
335879
|
+
const conversationId = typeof raw.conversation_id === "string" ? raw.conversation_id.trim() : "";
|
|
335880
|
+
if (!conversationId) return errorResult();
|
|
335881
|
+
const session = await resolveProjectSession(chatStore, conversationId, options.projectId);
|
|
335882
|
+
if (!session) return errorResult();
|
|
335883
|
+
const limit2 = clampInteger(raw.limit, CONVERSATION_READ_DEFAULT_LIMIT, 1, CONVERSATION_READ_MAX_LIMIT);
|
|
335884
|
+
const offset = clampInteger(raw.offset, 0, 0, Number.MAX_SAFE_INTEGER);
|
|
335885
|
+
const order = raw.order === "asc" ? "asc" : "desc";
|
|
335886
|
+
try {
|
|
335887
|
+
const messages3 = await chatStore.getMessages(conversationId, { limit: limit2, offset, order });
|
|
335888
|
+
let messageTruncated = false;
|
|
335889
|
+
const rows = messages3.map((message) => {
|
|
335890
|
+
const formatted = formatMessage(message, CONVERSATION_MESSAGE_MAX_CHARS);
|
|
335891
|
+
messageTruncated ||= formatted.truncated;
|
|
335892
|
+
return formatted.text;
|
|
335893
|
+
});
|
|
335894
|
+
const body = [
|
|
335895
|
+
formatSessionHeader(session, options.currentSessionId),
|
|
335896
|
+
`Messages (${order}, limit ${limit2}, offset ${offset}):`,
|
|
335897
|
+
rows.length > 0 ? rows.join("\n") : "(no messages)",
|
|
335898
|
+
...messageTruncated ? [MESSAGE_TRUNCATION_HINT] : []
|
|
335899
|
+
].join("\n");
|
|
335900
|
+
return textResult(boundToolResponse(body, READ_GLOBAL_TRUNCATION_HINT), {
|
|
335901
|
+
conversationId,
|
|
335902
|
+
count: messages3.length,
|
|
335903
|
+
limit: limit2,
|
|
335904
|
+
offset,
|
|
335905
|
+
order
|
|
335906
|
+
});
|
|
335907
|
+
} catch {
|
|
335908
|
+
return errorResult(TOOL_READ_ERROR_MESSAGE);
|
|
335909
|
+
}
|
|
335910
|
+
}
|
|
335911
|
+
};
|
|
335912
|
+
}
|
|
335913
|
+
function createSearchExcerpt(content, matchIndex, queryLength) {
|
|
335914
|
+
if (content.length <= CONVERSATION_SEARCH_EXCERPT_CHARS) return content;
|
|
335915
|
+
const bodyLength = CONVERSATION_SEARCH_EXCERPT_CHARS - 2;
|
|
335916
|
+
const contextBudget = Math.max(0, bodyLength - Math.min(queryLength, bodyLength));
|
|
335917
|
+
let start = Math.max(0, matchIndex - Math.floor(contextBudget / 2));
|
|
335918
|
+
start = Math.min(start, content.length - bodyLength);
|
|
335919
|
+
const end = Math.min(content.length, start + bodyLength);
|
|
335920
|
+
return `${start > 0 ? "\u2026" : ""}${content.slice(start, end)}${end < content.length ? "\u2026" : ""}`;
|
|
335921
|
+
}
|
|
335922
|
+
function createConversationSearchTool(chatStore, options) {
|
|
335923
|
+
return {
|
|
335924
|
+
name: "fn_chat_conversation_search",
|
|
335925
|
+
label: "Search Chat Conversation",
|
|
335926
|
+
description: "Search recent messages in one conversation in the current Direct chat project using a literal case-insensitive query.",
|
|
335927
|
+
parameters: {
|
|
335928
|
+
type: "object",
|
|
335929
|
+
properties: {
|
|
335930
|
+
conversation_id: { type: "string" },
|
|
335931
|
+
query: { type: "string", minLength: 1 },
|
|
335932
|
+
limit: { type: "number", minimum: 1 }
|
|
335933
|
+
},
|
|
335934
|
+
required: ["conversation_id", "query"],
|
|
335935
|
+
additionalProperties: false
|
|
335936
|
+
},
|
|
335937
|
+
execute: async (_toolCallId, raw) => {
|
|
335938
|
+
const conversationId = typeof raw.conversation_id === "string" ? raw.conversation_id.trim() : "";
|
|
335939
|
+
const query = typeof raw.query === "string" ? raw.query.trim() : "";
|
|
335940
|
+
if (!conversationId) return errorResult();
|
|
335941
|
+
if (!query) return errorResult("ERROR: query must be a non-empty string.");
|
|
335942
|
+
const session = await resolveProjectSession(chatStore, conversationId, options.projectId);
|
|
335943
|
+
if (!session) return errorResult();
|
|
335944
|
+
const limit2 = clampInteger(raw.limit, CONVERSATION_SEARCH_DEFAULT_LIMIT, 1, CONVERSATION_SEARCH_MAX_MATCHES);
|
|
335945
|
+
try {
|
|
335946
|
+
const messages3 = await chatStore.getMessages(conversationId, {
|
|
335947
|
+
limit: CONVERSATION_SEARCH_SCAN_LIMIT,
|
|
335948
|
+
order: "desc"
|
|
335949
|
+
});
|
|
335950
|
+
const normalizedQuery = query.toLocaleLowerCase();
|
|
335951
|
+
const matches = [];
|
|
335952
|
+
for (const message of messages3) {
|
|
335953
|
+
const matchIndex = message.content.toLocaleLowerCase().indexOf(normalizedQuery);
|
|
335954
|
+
if (matchIndex < 0) continue;
|
|
335955
|
+
matches.push(
|
|
335956
|
+
`[${message.role} @ ${message.createdAt}] ${createSearchExcerpt(message.content, matchIndex, query.length)}`
|
|
335957
|
+
);
|
|
335958
|
+
if (matches.length >= limit2) break;
|
|
335959
|
+
}
|
|
335960
|
+
const saturated = messages3.length >= CONVERSATION_SEARCH_SCAN_LIMIT;
|
|
335961
|
+
const body = [
|
|
335962
|
+
formatSessionHeader(session, options.currentSessionId),
|
|
335963
|
+
`Search query: ${query}`,
|
|
335964
|
+
matches.length > 0 ? matches.join("\n") : "(no matches)",
|
|
335965
|
+
...saturated ? [`[Search window saturated at the ${CONVERSATION_SEARCH_SCAN_LIMIT} most recent messages; older matches may exist.]`] : []
|
|
335966
|
+
].join("\n");
|
|
335967
|
+
return textResult(boundToolResponse(body, SEARCH_GLOBAL_TRUNCATION_HINT), {
|
|
335968
|
+
conversationId,
|
|
335969
|
+
matchCount: matches.length,
|
|
335970
|
+
limit: limit2,
|
|
335971
|
+
scannedCount: messages3.length,
|
|
335972
|
+
saturated
|
|
335973
|
+
});
|
|
335974
|
+
} catch {
|
|
335975
|
+
return errorResult(TOOL_READ_ERROR_MESSAGE);
|
|
335976
|
+
}
|
|
335977
|
+
}
|
|
335978
|
+
};
|
|
335979
|
+
}
|
|
335980
|
+
function createChatConversationTools(chatStore, options) {
|
|
335981
|
+
return [
|
|
335982
|
+
createConversationReadTool(chatStore, options),
|
|
335983
|
+
createConversationSearchTool(chatStore, options)
|
|
335984
|
+
];
|
|
335985
|
+
}
|
|
335986
|
+
var MAX_REFERENCED_CONVERSATIONS, CONVERSATION_PREVIEW_MESSAGE_COUNT, CONVERSATION_PREVIEW_MESSAGE_CHARS, CONVERSATION_READ_DEFAULT_LIMIT, CONVERSATION_READ_MAX_LIMIT, CONVERSATION_SEARCH_DEFAULT_LIMIT, CONVERSATION_SEARCH_MAX_MATCHES, CONVERSATION_SEARCH_SCAN_LIMIT, CONVERSATION_SEARCH_EXCERPT_CHARS, CONVERSATION_MESSAGE_MAX_CHARS, CONVERSATION_TOOL_RESPONSE_MAX_CHARS, UNAVAILABLE_CONTEXT_MESSAGE, UNAVAILABLE_TOOL_MESSAGE, TOOL_READ_ERROR_MESSAGE, READ_GLOBAL_TRUNCATION_HINT, SEARCH_GLOBAL_TRUNCATION_HINT, MESSAGE_TRUNCATION_HINT;
|
|
335987
|
+
var init_chat_conversation_references = __esm({
|
|
335988
|
+
"../dashboard/src/chat-conversation-references.ts"() {
|
|
335989
|
+
"use strict";
|
|
335990
|
+
MAX_REFERENCED_CONVERSATIONS = 3;
|
|
335991
|
+
CONVERSATION_PREVIEW_MESSAGE_COUNT = 5;
|
|
335992
|
+
CONVERSATION_PREVIEW_MESSAGE_CHARS = 300;
|
|
335993
|
+
CONVERSATION_READ_DEFAULT_LIMIT = 30;
|
|
335994
|
+
CONVERSATION_READ_MAX_LIMIT = 100;
|
|
335995
|
+
CONVERSATION_SEARCH_DEFAULT_LIMIT = 10;
|
|
335996
|
+
CONVERSATION_SEARCH_MAX_MATCHES = 25;
|
|
335997
|
+
CONVERSATION_SEARCH_SCAN_LIMIT = 400;
|
|
335998
|
+
CONVERSATION_SEARCH_EXCERPT_CHARS = 200;
|
|
335999
|
+
CONVERSATION_MESSAGE_MAX_CHARS = 1500;
|
|
336000
|
+
CONVERSATION_TOOL_RESPONSE_MAX_CHARS = 12e3;
|
|
336001
|
+
UNAVAILABLE_CONTEXT_MESSAGE = "Unavailable: no conversation with this ID exists in this project.";
|
|
336002
|
+
UNAVAILABLE_TOOL_MESSAGE = "ERROR: no conversation with this ID exists in this project.";
|
|
336003
|
+
TOOL_READ_ERROR_MESSAGE = "ERROR: conversation messages could not be read.";
|
|
336004
|
+
READ_GLOBAL_TRUNCATION_HINT = "[Output truncated. Retry with a smaller limit or a larger offset.]";
|
|
336005
|
+
SEARCH_GLOBAL_TRUNCATION_HINT = "[Output truncated. Retry with a smaller limit or a narrower query.]";
|
|
336006
|
+
MESSAGE_TRUNCATION_HINT = "[Some messages were truncated. Retry with a smaller limit or a different offset.]";
|
|
336007
|
+
}
|
|
336008
|
+
});
|
|
336009
|
+
|
|
335172
336010
|
// ../dashboard/src/chat.ts
|
|
335173
336011
|
var chat_exports = {};
|
|
335174
336012
|
__export(chat_exports, {
|
|
@@ -335487,15 +336325,32 @@ function createTaskVerificationTools(taskStore, actionGateContext) {
|
|
|
335487
336325
|
];
|
|
335488
336326
|
}
|
|
335489
336327
|
async function createChatFusionToolset(options) {
|
|
335490
|
-
const {
|
|
336328
|
+
const {
|
|
336329
|
+
taskStore,
|
|
336330
|
+
agentStore,
|
|
336331
|
+
rootDir,
|
|
336332
|
+
agentId,
|
|
336333
|
+
missionMutationGated = false,
|
|
336334
|
+
actionGateContext,
|
|
336335
|
+
focus,
|
|
336336
|
+
chatStore,
|
|
336337
|
+
currentChatSessionId,
|
|
336338
|
+
currentProjectId
|
|
336339
|
+
} = options;
|
|
335491
336340
|
const tools = [];
|
|
336341
|
+
if (chatStore && currentChatSessionId) {
|
|
336342
|
+
tools.push(...createChatConversationTools(chatStore, {
|
|
336343
|
+
currentSessionId: currentChatSessionId,
|
|
336344
|
+
projectId: currentProjectId ?? null
|
|
336345
|
+
}));
|
|
336346
|
+
}
|
|
335492
336347
|
if (taskStore) {
|
|
335493
336348
|
const settings = await taskStore.getSettings?.();
|
|
335494
336349
|
tools.push(
|
|
335495
336350
|
createTaskListTool(taskStore),
|
|
335496
336351
|
createTaskShowTool(taskStore),
|
|
335497
336352
|
createTaskSearchTool(taskStore),
|
|
335498
|
-
|
|
336353
|
+
createHistoryReadTool(taskStore),
|
|
335499
336354
|
...createTaskVerificationTools(taskStore, options.actionGateContext),
|
|
335500
336355
|
createTaskCreateTool(taskStore, { sourceType: "api" }, { rootDir })
|
|
335501
336356
|
);
|
|
@@ -335921,6 +336776,7 @@ var init_chat = __esm({
|
|
|
335921
336776
|
init_task_planner_chat_context();
|
|
335922
336777
|
init_task_planner_chat_metrics();
|
|
335923
336778
|
init_sse2();
|
|
336779
|
+
init_chat_conversation_references();
|
|
335924
336780
|
init_src5();
|
|
335925
336781
|
init_src5();
|
|
335926
336782
|
createFnAgent9 = createFnAgent2;
|
|
@@ -337231,7 +338087,16 @@ ${taskContext}`;
|
|
|
337231
338087
|
${mentionContext}`;
|
|
337232
338088
|
}
|
|
337233
338089
|
}
|
|
337234
|
-
const
|
|
338090
|
+
const fileResolvedContent = await resolveFileReferences(parsedSkillCommands.strippedContent, this.rootDir);
|
|
338091
|
+
const conversationReferenceContext = await buildConversationReferenceContext({
|
|
338092
|
+
chatStore: this.chatStore,
|
|
338093
|
+
content: parsedSkillCommands.strippedContent,
|
|
338094
|
+
currentSessionId: sessionId,
|
|
338095
|
+
currentProjectId: session.projectId ?? null
|
|
338096
|
+
});
|
|
338097
|
+
const resolvedContent = conversationReferenceContext ? `${fileResolvedContent}
|
|
338098
|
+
|
|
338099
|
+
${conversationReferenceContext}` : fileResolvedContent;
|
|
337235
338100
|
const attachmentSummary = attachments && attachments.length > 0 ? `[User attached: ${attachments.map((attachment) => `${attachment.originalName} (${attachment.mimeType}, ${formatAttachmentSize(attachment.size)})`).join(", ")}]` : "";
|
|
337236
338101
|
const { attachmentContents, imageContents } = await readChatAttachmentContents(
|
|
337237
338102
|
this.rootDir,
|
|
@@ -337303,7 +338168,10 @@ ${mentionContext}`;
|
|
|
337303
338168
|
toolset applies it only while experimentalFeatures.chatFocus is enabled. Otherwise the
|
|
337304
338169
|
value is inert and both direct and room chat recall remain whole-project.
|
|
337305
338170
|
*/
|
|
337306
|
-
focus: session?.memoryFocus ?? void 0
|
|
338171
|
+
focus: session?.memoryFocus ?? void 0,
|
|
338172
|
+
chatStore: this.chatStore,
|
|
338173
|
+
currentChatSessionId: sessionId,
|
|
338174
|
+
currentProjectId: session?.projectId ?? null
|
|
337307
338175
|
});
|
|
337308
338176
|
const customTools = dedupeChatTools([
|
|
337309
338177
|
createAskQuestionTool(),
|
|
@@ -375222,12 +376090,9 @@ var init_dashboard_views = __esm({
|
|
|
375222
376090
|
{ id: "board", label: "Board", labelKey: "nav.board" },
|
|
375223
376091
|
{ id: "list", label: "List", labelKey: "nav.list" },
|
|
375224
376092
|
/*
|
|
375225
|
-
|
|
375226
|
-
|
|
375227
|
-
|
|
375228
|
-
FNXC:PatchnoteNaming 2026-08-30-06:36:
|
|
375229
|
-
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.
|
|
375230
|
-
*/
|
|
376093
|
+
FNXC:HistoryNaming 2026-09-04-09:35:
|
|
376094
|
+
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.
|
|
376095
|
+
*/
|
|
375231
376096
|
{ id: "patchnode", label: "History", labelKey: "nav.patchnode" },
|
|
375232
376097
|
{ id: "graph", label: "Graph" },
|
|
375233
376098
|
{ id: "agents", label: "Agents", labelKey: "nav.agents" },
|
|
@@ -397985,12 +398850,12 @@ async function rebuildProjectKnowledgeGraph(store, options = {}) {
|
|
|
397985
398850
|
rebuilds.delete(graphDir);
|
|
397986
398851
|
}
|
|
397987
398852
|
}
|
|
397988
|
-
function
|
|
398853
|
+
function clampInteger2(value2, fallback2, minimum, maximum) {
|
|
397989
398854
|
return typeof value2 === "number" && Number.isFinite(value2) ? Math.min(maximum, Math.max(minimum, Math.trunc(value2))) : fallback2;
|
|
397990
398855
|
}
|
|
397991
398856
|
function findBoundedShortestPath(graph, fromId, toId, options = {}) {
|
|
397992
|
-
const maxHops =
|
|
397993
|
-
const maxExpansions =
|
|
398857
|
+
const maxHops = clampInteger2(options.maxHops, KNOWLEDGE_GRAPH_PATH_DEFAULT_HOPS, 1, KNOWLEDGE_GRAPH_PATH_MAX_HOPS);
|
|
398858
|
+
const maxExpansions = clampInteger2(options.maxExpansions, KNOWLEDGE_GRAPH_PATH_MAX_EXPANSIONS, 1, KNOWLEDGE_GRAPH_PATH_MAX_EXPANSIONS);
|
|
397994
398859
|
const nodes2 = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
397995
398860
|
if (!nodes2.has(fromId) || !nodes2.has(toId)) throw new KnowledgeGraphError("Unknown graph node");
|
|
397996
398861
|
if (fromId === toId) return { outcome: "found", path: { nodes: [nodes2.get(fromId)], edges: [] }, hops: 0, maxHops, expansions: 0, truncated: false };
|