@runfusion/fusion 0.77.0-beta.5 → 0.77.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +433 -102
- package/dist/child-process-worker.js +352 -81
- package/dist/client/.vite/manifest.json +261 -261
- package/dist/client/assets/{AgentDetailView-CSK_Rnlk.js → AgentDetailView-oyY72uSo.js} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor-kG44Adcm.js → AgentPermissionPolicyEditor-C3Qo9p1M.js} +1 -1
- package/dist/client/assets/{AgentsView-ClKwKWnU.js → AgentsView-M2MhTIGW.js} +3 -3
- package/dist/client/assets/{ChatView-DoOqgkPX.js → ChatView-zKrUSqiQ.js} +1 -1
- package/dist/client/assets/{CommandCenter-CJxvPbgn.js → CommandCenter-CpcAt6jt.js} +27 -27
- package/dist/client/assets/{DevServerView-C4nyLONX.js → DevServerView-CjTneoG7.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-BIvXWIB5.js → DirectoryPicker-BY6V5HLV.js} +1 -1
- package/dist/client/assets/{DocumentsView-DH_aZysM.js → DocumentsView-RDK56-qg.js} +1 -1
- package/dist/client/assets/{EvalsView-klRK-GJQ.js → EvalsView-DKK4ceUW.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-CgBmukIm.js → ExperimentalAgentOnboardingModal-DiBPZVwC.js} +1 -1
- package/dist/client/assets/{GoalsView-ysh3X2x6.js → GoalsView-DPj59B3l.js} +1 -1
- package/dist/client/assets/{InsightsView-BbMjCu7J.js → InsightsView-QLJNkKJp.js} +1 -1
- package/dist/client/assets/{MemoryView-B3IgA8Bx.js → MemoryView-CDCS-1ig.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-CZlIw7C3.js → PiExtensionsManager-DktXXrmF.js} +1 -1
- package/dist/client/assets/{PluginManager-vy0YZ2Nx.js → PluginManager-CQ3iEFMq.js} +1 -1
- package/dist/client/assets/{PullRequestView-DhhsJV8I.js → PullRequestView-CkObT6e3.js} +1 -1
- package/dist/client/assets/{ReportModal-CQMn4i1y.js → ReportModal-B5LTdDgi.js} +1 -1
- package/dist/client/assets/{ResearchView-CVwfs4l3.js → ResearchView-CryFrV8l.js} +1 -1
- package/dist/client/assets/{SecretsView-CrJfHle9.js → SecretsView-9YcNW_fo.js} +1 -1
- package/dist/client/assets/{SessionTerminal-BRI8W1-G.js → SessionTerminal-BWsA9puD.js} +1 -1
- package/dist/client/assets/{SettingsModal-h1A_aFqD.js → SettingsModal-CL6aiXiI.js} +4 -4
- package/dist/client/assets/{SettingsModal-B6U5Umru.js → SettingsModal-llkoaJev.js} +1 -1
- package/dist/client/assets/{SettingsTextareaRow-O7-u9uMC.js → SettingsTextareaRow-B_Legu-A.js} +1 -1
- package/dist/client/assets/{SetupWizardModal-nXfJp8cA.js → SetupWizardModal-DLA4UAGp.js} +2 -2
- package/dist/client/assets/{SkillsView-CoWAjvWU.js → SkillsView-CSeauwIw.js} +1 -1
- package/dist/client/assets/{WorkflowNodeEditor-BFMM3RF_.js → WorkflowNodeEditor-CGgMiiWy.js} +1 -1
- package/dist/client/assets/{agent-import-generation-BqDmtTur.js → agent-import-generation-CttglSmZ.js} +1 -1
- package/dist/client/assets/{agentsOrgChartLayout-C4cLqlV6.js → agentsOrgChartLayout-f-9mwRbD.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-CdxcwdRe.js → architectureDiagram-3BPJPVTR-DA_Hm_di.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-CfQrRfHR.js → blockDiagram-GPEHLZMM-JF7K8CcV.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-Cx7-2SOm.js → c4Diagram-AAUBKEIU-DN-wklCP.js} +1 -1
- package/dist/client/assets/channel-CwFfFSI9.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-D8jvgP3M.js → chunk-2J33WTMH-ChtjU8Cu.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-B5tBx_s_.js → chunk-4BX2VUAB-dJL0n3nn.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-C-F-HQ5Z.js → chunk-55IACEB6-Bfg-QB9A.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-BP8Ln-8C.js → chunk-727SXJPM-BHPPwYUS.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DMnTmRmn.js → chunk-AQP2D5EJ-DIIpPUQN.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-D_YdfnNc.js → chunk-FMBD7UC4-D230R32b.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-CULXyuXq.js → chunk-ND2GUHAM-0Lhp5CRv.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CT1BWEJL.js → chunk-QZHKN3VN-BxbHBfKJ.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-C8dZsxxX.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C8dZsxxX.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-M-3DXa6e.js → cose-bilkent-S5V4N54A-3vEwhfYz.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-Bsiqvroz.js → dagre-BM42HDAG-Br44UsdY.js} +1 -1
- package/dist/client/assets/{dashboard-view-Bo_gFjdO.js → dashboard-view-B0OM3KYC.js} +1 -1
- package/dist/client/assets/{dashboard-view-CnFFG3cW.js → dashboard-view-BldxxYfz.js} +1 -1
- package/dist/client/assets/{dashboard-view-DAHn20ph.js → dashboard-view-CcPcELPx.js} +1 -1
- package/dist/client/assets/{dashboard-view-BEBh_Ei5.js → dashboard-view-_EglxMDl.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-CZiHoXo5.js → diagram-2AECGRRQ-CLNxbHUP.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-BoJVz4Or.js → diagram-5GNKFQAL-D3cNAeQH.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DivGQuAL.js → diagram-KO2AKTUF-DkX7h6to.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DkMK7OJp.js → diagram-LMA3HP47-6iQIiYUr.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-DFFvRcpW.js → diagram-OG6HWLK6-Caje3229.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35--217vrom.js → erDiagram-TEJ5UH35-BkU-1HB1.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-CQgWO3xf.js → flowDiagram-I6XJVG4X-BRx3V6RE.js} +1 -1
- package/dist/client/assets/{folder-open-DR6cRcNR.js → folder-open-BaSOBfk2.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-BjDMUrS3.js → ganttDiagram-6RSMTGT7-D9f1kH3t.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-gKUcDnln.js → gitGraphDiagram-PVQCEYII-CQwmZcRd.js} +1 -1
- package/dist/client/assets/{index-oxnX59eO.js → index-B8PcG8AJ.js} +298 -298
- package/dist/client/assets/{infoDiagram-5YYISTIA-BA91T8S5.js → infoDiagram-5YYISTIA-DYWBkGjG.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-0BY5xe-c.js → ishikawaDiagram-YF4QCWOH-Df0Nzvh4.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-B_OwenbQ.js → journeyDiagram-JHISSGLW-bSvrZSyZ.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-GLabUTKp.js → kanban-definition-UN3LZRKU-yHJ101M8.js} +1 -1
- package/dist/client/assets/{mermaid.core-BXo4rxYI.js → mermaid.core-Bc_0VLJw.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-DZl6LPoE.js → mindmap-definition-RKZ34NQL-DWRnMguM.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DtHq-4py.js → pieDiagram-4H26LBE5-Ds-DEOqr.js} +1 -1
- package/dist/client/assets/{puzzle-OVm-I1L4.js → puzzle-CdYF81IT.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BdjXhd5d.js → quadrantDiagram-W4KKPZXB-Bz-j2fkf.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-BoFVjELN.js → requirementDiagram-4Y6WPE33-BaQ_PaZ-.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-B0qyJh-P.js → sankeyDiagram-5OEKKPKP-pUCz8V4-.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-ByY62Eqp.js → sequenceDiagram-3UESZ5HK-CFwUMspe.js} +1 -1
- package/dist/client/assets/{shield-alert-CDy69fl7.js → shield-alert-4qeU3_Rm.js} +1 -1
- package/dist/client/assets/{standing-instructions-template-BsnnRlwm.js → standing-instructions-template-Crocrob7.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-CwJ-_Phi.js → stateDiagram-AJRCARHV-CHZwo-vd.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-Cfvhq8mV.js → stateDiagram-v2-BHNVJYJU-BTvKaIZR.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-Co8VfGN9.js → timeline-definition-PNZ67QCA-Cn3XIoz2.js} +1 -1
- package/dist/client/assets/{upload-CkqDbEeE.js → upload--GqImz6h.js} +1 -1
- package/dist/client/assets/{useDiscoveredSkillsCache-BHUasRiZ.js → useDiscoveredSkillsCache-BEHgTFai.js} +1 -1
- package/dist/client/assets/{users-B8DkpbXD.js → users-B0h1q4x6.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-XSJteQrL.js → vennDiagram-CIIHVFJN-CS0FNVU_.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-Btc0FRfF.js → wardley-L42UT6IY-DdYkfF5d.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO--IcDNmrf.js → wardleyDiagram-YWT4CUSO-B_rFmPCh.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-CZW93G6X.js → xychartDiagram-2RQKCTM6-CCoaCgmc.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/extension.js +381 -87
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugin-sdk/index.d.ts +9 -0
- package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
- package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-quality/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-todos/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-B4WN1jC_.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CKE8RSOv.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CKE8RSOv.js +0 -1
|
@@ -231,10 +231,16 @@ function resolveEntryPointBranchAssignment(input) {
|
|
|
231
231
|
};
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
|
-
var isValidTaskBranchName;
|
|
234
|
+
var BranchWriteProvenanceError, isValidTaskBranchName;
|
|
235
235
|
var init_branch_assignment = __esm({
|
|
236
236
|
"../core/src/branch/branch-assignment.ts"() {
|
|
237
237
|
"use strict";
|
|
238
|
+
BranchWriteProvenanceError = class extends Error {
|
|
239
|
+
constructor() {
|
|
240
|
+
super("branchWriteOrigin is required when branch is provided");
|
|
241
|
+
this.name = "BranchWriteProvenanceError";
|
|
242
|
+
}
|
|
243
|
+
};
|
|
238
244
|
isValidTaskBranchName = isValidBranchGroupBranchName;
|
|
239
245
|
}
|
|
240
246
|
});
|
|
@@ -4612,8 +4618,8 @@ var init_research_settings = __esm({
|
|
|
4612
4618
|
function resolveResearchFindingId(finding) {
|
|
4613
4619
|
const explicitId = typeof finding.id === "string" ? finding.id.trim() : "";
|
|
4614
4620
|
if (explicitId) return explicitId;
|
|
4615
|
-
const
|
|
4616
|
-
const canonical = [
|
|
4621
|
+
const normalize10 = (value) => value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
4622
|
+
const canonical = [normalize10(finding.heading ?? ""), normalize10(finding.content ?? ""), ...[...finding.sources ?? []].map(normalize10).filter(Boolean).sort()].join("\n");
|
|
4617
4623
|
let hash3 = 2166136261;
|
|
4618
4624
|
for (let index2 = 0; index2 < canonical.length; index2 += 1) {
|
|
4619
4625
|
hash3 ^= canonical.charCodeAt(index2);
|
|
@@ -36067,17 +36073,17 @@ var init_agent_store = __esm({
|
|
|
36067
36073
|
* @returns Matching agent when unambiguous; otherwise null
|
|
36068
36074
|
*/
|
|
36069
36075
|
async resolveAgent(shortname) {
|
|
36070
|
-
const
|
|
36076
|
+
const normalize10 = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
36071
36077
|
const all = await this.listAgents();
|
|
36072
36078
|
const exact = all.find((agent) => agent.id === shortname);
|
|
36073
36079
|
if (exact) {
|
|
36074
36080
|
return exact;
|
|
36075
36081
|
}
|
|
36076
|
-
const normalizedTarget =
|
|
36082
|
+
const normalizedTarget = normalize10(shortname);
|
|
36077
36083
|
if (!normalizedTarget) {
|
|
36078
36084
|
return null;
|
|
36079
36085
|
}
|
|
36080
|
-
const matches = all.filter((agent) =>
|
|
36086
|
+
const matches = all.filter((agent) => normalize10(agent.name) === normalizedTarget);
|
|
36081
36087
|
return matches.length === 1 ? matches[0] : null;
|
|
36082
36088
|
}
|
|
36083
36089
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -45319,11 +45325,11 @@ function withWindowsNativeBinPath(environment, nativeRoot, platform3 = process.p
|
|
|
45319
45325
|
if (platform3 !== "win32") return environment;
|
|
45320
45326
|
const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
45321
45327
|
const bin = nativeRoot.replace(/[\\/]+$/, "") + "\\bin";
|
|
45322
|
-
const
|
|
45328
|
+
const normalize10 = (value) => value.replace(/\//g, "\\").toLowerCase();
|
|
45323
45329
|
const inherited = environment[pathKey] ?? "";
|
|
45324
45330
|
const segments = inherited.split(";").filter(Boolean);
|
|
45325
|
-
const normalizedBin =
|
|
45326
|
-
const deduplicated = segments.filter((segment) =>
|
|
45331
|
+
const normalizedBin = normalize10(bin);
|
|
45332
|
+
const deduplicated = segments.filter((segment) => normalize10(segment) !== normalizedBin);
|
|
45327
45333
|
return { ...environment, [pathKey]: [bin, ...deduplicated].join(";") };
|
|
45328
45334
|
}
|
|
45329
45335
|
function readPostgresPid(dataDir) {
|
|
@@ -67272,14 +67278,14 @@ function resolveGlobalDir(dir) {
|
|
|
67272
67278
|
const isFusionDirInsideRepo = basename8(dir) === ".fusion" && existsSync14(join19(dirname11(dir), ".git"));
|
|
67273
67279
|
if (isFusionDirInsideRepo) {
|
|
67274
67280
|
const homeGlobalDir = resolveGlobalDirForHome(getHomeDir2());
|
|
67275
|
-
const
|
|
67281
|
+
const normalize10 = (p2) => {
|
|
67276
67282
|
try {
|
|
67277
67283
|
return realpathSync3.native(p2);
|
|
67278
67284
|
} catch {
|
|
67279
67285
|
return resolve13(p2);
|
|
67280
67286
|
}
|
|
67281
67287
|
};
|
|
67282
|
-
if (
|
|
67288
|
+
if (normalize10(dir) !== normalize10(homeGlobalDir)) {
|
|
67283
67289
|
throw new Error(
|
|
67284
67290
|
`resolveGlobalDir(): refusing project-local '.fusion' directory '${dir}' for the central/global store. This would create a stray per-project central database seeded with default global settings and silently reset them. Pass the resolved global dir (or omit the argument so it defaults to ~/.fusion); see TaskStore.getGlobalSettingsDir(). If this really is your intended global dir (e.g. a version-controlled dotfiles repo), set FUSION_ALLOW_PROJECT_LOCAL_GLOBAL_DIR=true to override.`
|
|
67285
67291
|
);
|
|
@@ -88006,15 +88012,15 @@ async function appendRecall(layer, input) {
|
|
|
88006
88012
|
async function mergeRecallGraphNodeIds(layer, recordId, addGraphNodeIds) {
|
|
88007
88013
|
const pid = projectId(layer);
|
|
88008
88014
|
const table = project_exports.memoryRecallRecords;
|
|
88009
|
-
const
|
|
88015
|
+
const normalize10 = (ids) => [...new Set(ids.map((id) => id.trim()).filter(Boolean))].sort();
|
|
88010
88016
|
return layer.transactionImmediate(async (tx) => {
|
|
88011
88017
|
await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${recallGraphNodeIdsLockKey(pid, recordId)}))`);
|
|
88012
88018
|
const rows = await tx.select().from(table).where(and(eq(table.projectId, pid), eq(table.id, recordId))).limit(1);
|
|
88013
88019
|
await recallGraphNodeIdsTestHooks?.afterRowRead?.();
|
|
88014
88020
|
const row = rows[0];
|
|
88015
88021
|
if (!row) return { status: "missing" };
|
|
88016
|
-
const stored =
|
|
88017
|
-
const next =
|
|
88022
|
+
const stored = normalize10(row.graphNodeIds);
|
|
88023
|
+
const next = normalize10([...stored, ...addGraphNodeIds]);
|
|
88018
88024
|
if (stored.length === next.length && stored.every((value, index2) => value === next[index2])) return { status: "unchanged" };
|
|
88019
88025
|
await tx.update(table).set({ graphNodeIds: next, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and(eq(table.projectId, pid), eq(table.id, recordId)));
|
|
88020
88026
|
return { status: "updated" };
|
|
@@ -92417,7 +92423,7 @@ import { randomUUID as randomUUID31 } from "node:crypto";
|
|
|
92417
92423
|
function normalizeCreateBranchProvenance(input) {
|
|
92418
92424
|
if (input.branch === void 0) return input;
|
|
92419
92425
|
if (input.branchWriteOrigin !== "operator" && input.branchWriteOrigin !== "engine") {
|
|
92420
|
-
throw new
|
|
92426
|
+
throw new BranchWriteProvenanceError();
|
|
92421
92427
|
}
|
|
92422
92428
|
validateTaskBranchName(input.branch);
|
|
92423
92429
|
if (input.branchWriteOrigin === "operator") {
|
|
@@ -94634,7 +94640,7 @@ async function updateTaskRepositoryScopeImpl(store, id, requestedScope) {
|
|
|
94634
94640
|
...replacement,
|
|
94635
94641
|
repositories: nextRepositories,
|
|
94636
94642
|
revision: Math.max((current.repositoryScope?.revision ?? 0) + 1, replacement.revision ?? 0),
|
|
94637
|
-
...scopeChanged ? { reviewEvidence: void 0 } : {}
|
|
94643
|
+
...scopeChanged ? { reviewEvidence: void 0, reviewRemediation: void 0 } : {}
|
|
94638
94644
|
};
|
|
94639
94645
|
const [updatedRow] = await tx.update(project_exports.tasks).set({
|
|
94640
94646
|
repositoryScope: normalized2 ?? null,
|
|
@@ -94650,6 +94656,35 @@ async function updateTaskRepositoryScopeImpl(store, id, requestedScope) {
|
|
|
94650
94656
|
return outcome;
|
|
94651
94657
|
}));
|
|
94652
94658
|
}
|
|
94659
|
+
async function updateWorkspaceReviewStateImpl(store, id, expectedScopeRevision, reviewRemediation) {
|
|
94660
|
+
return store.withTaskLock(id, async () => {
|
|
94661
|
+
const layer = store.asyncLayer;
|
|
94662
|
+
const outcome = await layer.transactionImmediate(async (tx) => {
|
|
94663
|
+
await acquireTaskAdvisoryXactLock(tx, layer.projectId, id);
|
|
94664
|
+
const row = await readTaskRowInTransaction(tx, id, { includeDeleted: true }, layer.projectId);
|
|
94665
|
+
if (!row) throw new TaskNotFoundError(id);
|
|
94666
|
+
if (row.deletedAt) throw new TaskDeletedError(id, row.deletedAt);
|
|
94667
|
+
const current = store.rowToTask(store.pgRowToTaskRow(row));
|
|
94668
|
+
if (current.repositoryScope?.revision !== expectedScopeRevision) return { task: current, updated: false };
|
|
94669
|
+
const repositoryScope = {
|
|
94670
|
+
...current.repositoryScope,
|
|
94671
|
+
...reviewRemediation ? { reviewRemediation } : { reviewRemediation: void 0 }
|
|
94672
|
+
};
|
|
94673
|
+
const [updatedRow] = await tx.update(project_exports.tasks).set({
|
|
94674
|
+
repositoryScope,
|
|
94675
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
94676
|
+
}).where(and(eq(project_exports.tasks.id, id), taskProjectScope(layer))).returning();
|
|
94677
|
+
if (!updatedRow) throw new TaskNotFoundError(id);
|
|
94678
|
+
return { task: store.rowToTask(store.pgRowToTaskRow(updatedRow)), updated: true };
|
|
94679
|
+
});
|
|
94680
|
+
if (outcome.updated) {
|
|
94681
|
+
await store.writeTaskJsonFile(store.taskDir(id), outcome.task);
|
|
94682
|
+
if (store.isWatching) store.taskCache.set(id, { ...outcome.task });
|
|
94683
|
+
store.emitTaskLifecycleEventSafely("task:updated", [outcome.task]);
|
|
94684
|
+
}
|
|
94685
|
+
return outcome;
|
|
94686
|
+
});
|
|
94687
|
+
}
|
|
94653
94688
|
async function resolveTaskWedgeNotificationEpisodeImpl(store, id, episodeId) {
|
|
94654
94689
|
const layer = store.asyncLayer;
|
|
94655
94690
|
const row = await resolveActiveTaskWedgeEpisodeRow(layer, id, episodeId, (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -103453,7 +103488,7 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
|
|
|
103453
103488
|
if (updates.recommendations !== void 0) assertValidRecommendations(updates.recommendations);
|
|
103454
103489
|
if (updates.branch !== void 0) {
|
|
103455
103490
|
if (updates.branchWriteOrigin !== "operator" && updates.branchWriteOrigin !== "engine") {
|
|
103456
|
-
throw new
|
|
103491
|
+
throw new BranchWriteProvenanceError();
|
|
103457
103492
|
}
|
|
103458
103493
|
if (updates.branch !== null) validateTaskBranchName(updates.branch);
|
|
103459
103494
|
}
|
|
@@ -103542,7 +103577,7 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
|
|
|
103542
103577
|
task.repositoryScope = void 0;
|
|
103543
103578
|
} else if (updates.repositoryScope !== void 0) {
|
|
103544
103579
|
const scopeRevisionChanged = task.repositoryScope?.revision !== updates.repositoryScope.revision;
|
|
103545
|
-
task.repositoryScope = scopeRevisionChanged ? { ...updates.repositoryScope, reviewEvidence: void 0 } : updates.repositoryScope;
|
|
103580
|
+
task.repositoryScope = scopeRevisionChanged ? { ...updates.repositoryScope, reviewEvidence: void 0, reviewRemediation: void 0 } : updates.repositoryScope;
|
|
103546
103581
|
if (scopeRevisionChanged) {
|
|
103547
103582
|
task.workflowStepResults = invalidateSupersededRepositoryScopeReviews(
|
|
103548
103583
|
task.workflowStepResults,
|
|
@@ -106042,6 +106077,9 @@ var init_store = __esm({
|
|
|
106042
106077
|
async mutateTaskRepositoryScope(id, mutation) {
|
|
106043
106078
|
return updateTaskRepositoryScopeImpl(this, id, mutation);
|
|
106044
106079
|
}
|
|
106080
|
+
async updateWorkspaceReviewState(id, expectedScopeRevision, reviewRemediation) {
|
|
106081
|
+
return updateWorkspaceReviewStateImpl(this, id, expectedScopeRevision, reviewRemediation);
|
|
106082
|
+
}
|
|
106045
106083
|
async resolveTaskWedgeNotificationEpisode(id, episodeId) {
|
|
106046
106084
|
return resolveTaskWedgeNotificationEpisodeImpl(this, id, episodeId);
|
|
106047
106085
|
}
|
|
@@ -136049,6 +136087,7 @@ __export(src_exports, {
|
|
|
136049
136087
|
BUNDLED_PLUGIN_IDS: () => BUNDLED_PLUGIN_IDS,
|
|
136050
136088
|
BackupManager: () => BackupManager,
|
|
136051
136089
|
BackwardCompat: () => BackwardCompat,
|
|
136090
|
+
BranchWriteProvenanceError: () => BranchWriteProvenanceError,
|
|
136052
136091
|
CENTRAL_BACKUP_SCHEMAS: () => CENTRAL_BACKUP_SCHEMAS,
|
|
136053
136092
|
CLAUDE_SONNET_5_MODEL_ID: () => CLAUDE_SONNET_5_MODEL_ID,
|
|
136054
136093
|
CLEAN_COMPLETION_MARKERS: () => CLEAN_COMPLETION_MARKERS,
|
|
@@ -163898,8 +163937,8 @@ var require_resolve = __commonJS({
|
|
|
163898
163937
|
}
|
|
163899
163938
|
return count2;
|
|
163900
163939
|
}
|
|
163901
|
-
function getFullPath(resolver, id = "",
|
|
163902
|
-
if (
|
|
163940
|
+
function getFullPath(resolver, id = "", normalize10) {
|
|
163941
|
+
if (normalize10 !== false)
|
|
163903
163942
|
id = normalizeId2(id);
|
|
163904
163943
|
const p2 = resolver.parse(id);
|
|
163905
163944
|
return _getFullPath(resolver, p2);
|
|
@@ -165239,7 +165278,7 @@ var require_fast_uri = __commonJS({
|
|
|
165239
165278
|
"use strict";
|
|
165240
165279
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
|
|
165241
165280
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
165242
|
-
function
|
|
165281
|
+
function normalize10(uri, options) {
|
|
165243
165282
|
if (typeof uri === "string") {
|
|
165244
165283
|
uri = /** @type {T} */
|
|
165245
165284
|
serialize(parse4(uri, options), options);
|
|
@@ -165475,7 +165514,7 @@ var require_fast_uri = __commonJS({
|
|
|
165475
165514
|
}
|
|
165476
165515
|
var fastUri = {
|
|
165477
165516
|
SCHEMES,
|
|
165478
|
-
normalize:
|
|
165517
|
+
normalize: normalize10,
|
|
165479
165518
|
resolve: resolve50,
|
|
165480
165519
|
resolveComponent,
|
|
165481
165520
|
equal,
|
|
@@ -177689,6 +177728,39 @@ var init_worktrunk_failure_handler = __esm({
|
|
|
177689
177728
|
}
|
|
177690
177729
|
});
|
|
177691
177730
|
|
|
177731
|
+
// ../engine/src/executor/workspace-review-remediation.ts
|
|
177732
|
+
function normalize8(value) {
|
|
177733
|
+
return (value ?? "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
177734
|
+
}
|
|
177735
|
+
function resolveWorkspaceReviewRemediationRepository(task, declaredRepositories) {
|
|
177736
|
+
const scope = task.repositoryScope;
|
|
177737
|
+
const remediation = scope?.reviewRemediation;
|
|
177738
|
+
if (!remediation) return void 0;
|
|
177739
|
+
if (scope?.state !== "confirmed" || scope.revision !== remediation.scopeRevision) {
|
|
177740
|
+
throw new Error(`Workspace Code Review remediation target is stale for ${task.id}`);
|
|
177741
|
+
}
|
|
177742
|
+
if (!declaredRepositories.includes(remediation.repository)) {
|
|
177743
|
+
throw new Error(`Workspace Code Review remediation repository ${remediation.repository} is not declared for ${task.id}`);
|
|
177744
|
+
}
|
|
177745
|
+
return remediation.repository;
|
|
177746
|
+
}
|
|
177747
|
+
function deriveWorkspaceReviewRemediation(result) {
|
|
177748
|
+
if (typeof result.repositoryScopeRevision !== "number") return void 0;
|
|
177749
|
+
const blocking = (result.repositoryReviewOutcomes ?? []).filter((outcome) => outcome.status === "REVIEWED" && (outcome.verdict === "REVISE" || outcome.verdict === "RETHINK")).sort((left, right) => left.repository.localeCompare(right.repository))[0];
|
|
177750
|
+
if (!blocking) return void 0;
|
|
177751
|
+
const findings = (blocking.findings ?? []).map((finding) => `${finding.id}:${normalize8(finding.title)}:${normalize8(finding.body)}`).sort().join("|");
|
|
177752
|
+
return {
|
|
177753
|
+
scopeRevision: result.repositoryScopeRevision,
|
|
177754
|
+
repository: blocking.repository,
|
|
177755
|
+
inputSignature: `${result.workflowStepId}\0${blocking.repository}\0${blocking.fingerprint ?? ""}\0${blocking.verdict}\0${findings}`
|
|
177756
|
+
};
|
|
177757
|
+
}
|
|
177758
|
+
var init_workspace_review_remediation = __esm({
|
|
177759
|
+
"../engine/src/executor/workspace-review-remediation.ts"() {
|
|
177760
|
+
"use strict";
|
|
177761
|
+
}
|
|
177762
|
+
});
|
|
177763
|
+
|
|
177692
177764
|
// ../engine/src/worktree/worktree-copy-files.ts
|
|
177693
177765
|
import { constants as fsConstants3 } from "node:fs";
|
|
177694
177766
|
import { access as access7, copyFile as copyFile2, lstat as lstat2, mkdir as mkdir26 } from "node:fs/promises";
|
|
@@ -178075,14 +178147,15 @@ function invalidateWorkspaceConfigCache(owner) {
|
|
|
178075
178147
|
workspaceConfigEpochs.set(owner, (workspaceConfigEpochs.get(owner) ?? 0) + 1);
|
|
178076
178148
|
}
|
|
178077
178149
|
async function normalizeWorkspaceTaskRouting(store, taskId) {
|
|
178078
|
-
const
|
|
178079
|
-
if (typeof
|
|
178150
|
+
const normalize10 = store.normalizeWorkspaceTaskWorktreeMetadata;
|
|
178151
|
+
if (typeof normalize10 === "function") return normalize10.call(store, taskId);
|
|
178080
178152
|
let task = await store.getTask(taskId);
|
|
178081
178153
|
if (task.worktree || task.branch || task.executionStartBranch || task.baseCommitSha) {
|
|
178082
178154
|
if (typeof store.updateTask !== "function") return task;
|
|
178083
178155
|
await store.updateTask(taskId, {
|
|
178084
178156
|
worktree: null,
|
|
178085
178157
|
branch: null,
|
|
178158
|
+
branchWriteOrigin: "engine",
|
|
178086
178159
|
executionStartBranch: null,
|
|
178087
178160
|
baseCommitSha: null,
|
|
178088
178161
|
sessionFile: null
|
|
@@ -178284,11 +178357,15 @@ async function ensureWorkspaceGroupOwnership(workspaceContext, settings) {
|
|
|
178284
178357
|
async function acquireTaskWorktree(opts) {
|
|
178285
178358
|
const { task, rootDir, store, settings, pool, logger: logger3, audit, runContext, createWorktree: createWorktree2, runConfiguredCommand: runConfiguredCommand2, runInitCommand, taskEnv, secretsStore, workspaceContext } = opts;
|
|
178286
178359
|
const persistWorktreeAssignment = async (patch) => {
|
|
178360
|
+
const provenancePatch = patch.branch === void 0 ? patch : {
|
|
178361
|
+
...patch,
|
|
178362
|
+
branchWriteOrigin: patch.branchWriteOrigin ?? (classifyTaskBranchOrigin(task, patch.branch ?? void 0) === "operator-supplied" ? "operator" : "engine")
|
|
178363
|
+
};
|
|
178287
178364
|
if (!opts.suppressSingularWorktreePersist) {
|
|
178288
|
-
await store.updateTask(task.id,
|
|
178365
|
+
await store.updateTask(task.id, provenancePatch);
|
|
178289
178366
|
return;
|
|
178290
178367
|
}
|
|
178291
|
-
const { worktree: _worktree, branch: _branch, ...nonSingularPatch } =
|
|
178368
|
+
const { worktree: _worktree, branch: _branch, branchWriteOrigin: _origin, ...nonSingularPatch } = provenancePatch;
|
|
178292
178369
|
if (Object.keys(nonSingularPatch).length > 0) {
|
|
178293
178370
|
await store.updateTask(task.id, nonSingularPatch);
|
|
178294
178371
|
}
|
|
@@ -178525,7 +178602,22 @@ async function acquireTaskWorktree(opts) {
|
|
|
178525
178602
|
}
|
|
178526
178603
|
worktreePath = created2.path;
|
|
178527
178604
|
branch = created2.branch;
|
|
178528
|
-
|
|
178605
|
+
try {
|
|
178606
|
+
await persistWorktreeAssignment({ worktree: created2.path, branch: created2.branch });
|
|
178607
|
+
} catch (error2) {
|
|
178608
|
+
try {
|
|
178609
|
+
await removeWorktree({
|
|
178610
|
+
worktreePath: created2.path,
|
|
178611
|
+
rootDir,
|
|
178612
|
+
settings,
|
|
178613
|
+
taskId: task.id,
|
|
178614
|
+
reason: RemovalReason.ExecutorDispose
|
|
178615
|
+
});
|
|
178616
|
+
} catch (cleanupError) {
|
|
178617
|
+
logger3?.warn(`${task.id}: failed to compensate unpersisted worktree ${created2.path}: ${formatError(cleanupError)}`);
|
|
178618
|
+
}
|
|
178619
|
+
throw error2;
|
|
178620
|
+
}
|
|
178529
178621
|
await audit?.git({ type: "worktree:create", target: created2.path, metadata: { branch: created2.branch, source: logOrigin === "return-guard" ? "acquire-return-guard" : void 0 } });
|
|
178530
178622
|
await audit?.git({ type: "branch:create", target: created2.branch });
|
|
178531
178623
|
if (created2.branch !== branchName) {
|
|
@@ -178767,6 +178859,17 @@ async function acquireTaskWorktree(opts) {
|
|
|
178767
178859
|
if (pinned) {
|
|
178768
178860
|
return acquirePinnedWorktree();
|
|
178769
178861
|
}
|
|
178862
|
+
if (!workspaceContext && !task.worktree && !(pool && settings.recycleWorktrees)) {
|
|
178863
|
+
const registeredMatches = (await getRegisteredWorktreeBranches(rootDir)).filter((entry) => entry.branch === branchName);
|
|
178864
|
+
if (registeredMatches.length === 1) {
|
|
178865
|
+
const registeredPath = registeredMatches[0].worktreePath;
|
|
178866
|
+
const classification = await classifyTaskWorktree(rootDir, registeredPath);
|
|
178867
|
+
if (classification.ok) {
|
|
178868
|
+
await persistWorktreeAssignment({ worktree: registeredPath, branch: branchName });
|
|
178869
|
+
return reuseWarmWorktree(registeredPath, branchName, "existing");
|
|
178870
|
+
}
|
|
178871
|
+
}
|
|
178872
|
+
}
|
|
178770
178873
|
if (task.worktree && isResume) {
|
|
178771
178874
|
if (logger3?.debug) logger3.debug(`Reusing existing worktree: ${worktreePath}`);
|
|
178772
178875
|
const cleanup = await removeDesktopBuildArtifacts(worktreePath, logger3);
|
|
@@ -178802,6 +178905,7 @@ async function acquireTaskWorktree(opts) {
|
|
|
178802
178905
|
}
|
|
178803
178906
|
}
|
|
178804
178907
|
if (pooled) {
|
|
178908
|
+
let poolAssignmentPersistenceStarted = false;
|
|
178805
178909
|
try {
|
|
178806
178910
|
const preparedRaw = await pool.prepareForTask(pooled, branchName, freshStartPoint, {
|
|
178807
178911
|
allowSiblingBranchRename,
|
|
@@ -178859,7 +178963,9 @@ async function acquireTaskWorktree(opts) {
|
|
|
178859
178963
|
});
|
|
178860
178964
|
acquiredFromPool = true;
|
|
178861
178965
|
logger3?.log(`Acquired worktree from pool: ${worktreePath}`);
|
|
178966
|
+
poolAssignmentPersistenceStarted = true;
|
|
178862
178967
|
await persistWorktreeAssignment({ worktree: worktreePath, branch });
|
|
178968
|
+
poolAssignmentPersistenceStarted = false;
|
|
178863
178969
|
await audit?.git({ type: "worktree:reuse", target: worktreePath, metadata: { branch, reclaimed: prepared.reclaimed } });
|
|
178864
178970
|
if (prepared.reclaimed) {
|
|
178865
178971
|
await store.logEntry(task.id, `Acquired reclaimed worktree from pool: ${worktreePath} (${prepared.strandedCommitCount ?? 0} commits preserved)`, void 0, runContext);
|
|
@@ -178913,6 +179019,10 @@ async function acquireTaskWorktree(opts) {
|
|
|
178913
179019
|
});
|
|
178914
179020
|
}
|
|
178915
179021
|
} catch (poolErr) {
|
|
179022
|
+
if (poolAssignmentPersistenceStarted) {
|
|
179023
|
+
pool.release(pooled, task.id);
|
|
179024
|
+
throw poolErr;
|
|
179025
|
+
}
|
|
178916
179026
|
if (poolErr instanceof WorktreeBaseRefreshError) {
|
|
178917
179027
|
await persistWorktreeAssignment({ worktree: null, branch: null, branchWriteOrigin: "engine", sessionFile: null });
|
|
178918
179028
|
pool.release(pooled, task.id);
|
|
@@ -179224,7 +179334,11 @@ async function acquireWorkspaceTaskWorktrees(opts) {
|
|
|
179224
179334
|
current = await opts.store.getTask(opts.task.id);
|
|
179225
179335
|
}
|
|
179226
179336
|
const entries = current.workspaceWorktrees ?? {};
|
|
179227
|
-
const
|
|
179337
|
+
const remediationRepository = resolveWorkspaceReviewRemediationRepository(current, repoRelPaths);
|
|
179338
|
+
if (opts.remediationRepository !== remediationRepository) {
|
|
179339
|
+
throw new Error(`Workspace Code Review remediation target changed during acquisition for ${opts.task.id}`);
|
|
179340
|
+
}
|
|
179341
|
+
const coordinatorWorktreePath = remediationRepository ? entries[remediationRepository]?.worktreePath : repoRelPaths.map((repoRelPath) => entries[repoRelPath]?.worktreePath).find((path6) => typeof path6 === "string" && path6.length > 0);
|
|
179228
179342
|
if (!coordinatorWorktreePath) {
|
|
179229
179343
|
throw new Error(`Workspace task ${opts.task.id} did not acquire a declared repository worktree`);
|
|
179230
179344
|
}
|
|
@@ -179245,6 +179359,7 @@ var init_worktree_acquisition = __esm({
|
|
|
179245
179359
|
init_worktree_backend();
|
|
179246
179360
|
init_worktrunk_installer();
|
|
179247
179361
|
init_worktrunk_failure_handler();
|
|
179362
|
+
init_workspace_review_remediation();
|
|
179248
179363
|
init_secrets_env_writer();
|
|
179249
179364
|
init_worktree_desktop_artifacts();
|
|
179250
179365
|
init_worktree_hooks();
|
|
@@ -185943,7 +186058,7 @@ var init_agent_memory_index = __esm({
|
|
|
185943
186058
|
// ../engine/src/agents/agent-instructions.ts
|
|
185944
186059
|
import { readFile as readFile42, writeFile as writeFile28, mkdir as mkdir29, access as access8 } from "node:fs/promises";
|
|
185945
186060
|
import { constants as fsConstants4 } from "node:fs";
|
|
185946
|
-
import { isAbsolute as isAbsolute17, resolve as resolve40, relative as relative16, normalize as
|
|
186061
|
+
import { isAbsolute as isAbsolute17, resolve as resolve40, relative as relative16, normalize as normalize9, sep as sep12, dirname as dirname30 } from "node:path";
|
|
185947
186062
|
function trimAndClamp(value, maxLength, label, agentId) {
|
|
185948
186063
|
const trimmed = value.trim();
|
|
185949
186064
|
if (!trimmed) {
|
|
@@ -185977,7 +186092,7 @@ function resolveValidatedMarkdownPath(rawPath, rootDir, agentId, fieldLabel) {
|
|
|
185977
186092
|
log27.warn(`${fieldLabel} must be project-relative for agent ${agentId}: ${trimmed}`);
|
|
185978
186093
|
return null;
|
|
185979
186094
|
}
|
|
185980
|
-
const normalized2 =
|
|
186095
|
+
const normalized2 = normalize9(trimmed);
|
|
185981
186096
|
if (isPathTraversal2(normalized2)) {
|
|
185982
186097
|
log27.warn(`${fieldLabel} traversal is not allowed for agent ${agentId}: ${trimmed}`);
|
|
185983
186098
|
return null;
|
|
@@ -199373,7 +199488,8 @@ async function tryEarlyEmptyOwnDiffFinalize(input) {
|
|
|
199373
199488
|
try {
|
|
199374
199489
|
await store.updateTask(taskId, {
|
|
199375
199490
|
worktree: worktreeRemoved ? null : task.worktree,
|
|
199376
|
-
branch: branchDeleted ? null : task.branch
|
|
199491
|
+
branch: branchDeleted ? null : task.branch,
|
|
199492
|
+
...branchDeleted ? { branchWriteOrigin: "engine" } : {}
|
|
199377
199493
|
});
|
|
199378
199494
|
if (worktreeRemoved) task.worktree = void 0;
|
|
199379
199495
|
if (branchDeleted) task.branch = void 0;
|
|
@@ -199733,7 +199849,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
199733
199849
|
rootDir,
|
|
199734
199850
|
integrationBranch: mergeTarget.branch
|
|
199735
199851
|
});
|
|
199736
|
-
await store.updateTask(taskId, { worktree: reusableMatch.path, branch: reusableMatch.branch });
|
|
199852
|
+
await store.updateTask(taskId, { worktree: reusableMatch.path, branch: reusableMatch.branch, branchWriteOrigin: "engine" });
|
|
199737
199853
|
await emitReuseHandoffAuditEvent(
|
|
199738
199854
|
"merge:reuse-fallback-reused-existing-registration",
|
|
199739
199855
|
{
|
|
@@ -205486,8 +205602,10 @@ function workflowNodeRequiresWorktree(node, { optionalGroupId, reviewerInlineFix
|
|
|
205486
205602
|
}
|
|
205487
205603
|
|
|
205488
205604
|
// ../engine/src/workflows/workflow-graph-executor.ts
|
|
205605
|
+
init_src2();
|
|
205489
205606
|
init_worktree_acquisition();
|
|
205490
205607
|
var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE = "plan-review-provider-failure-hold";
|
|
205608
|
+
var BRANCH_WRITE_PROVENANCE_FAILURE_VALUE = "branch-write-provenance-failure";
|
|
205491
205609
|
var PLAN_REVIEW_LEASE_HELD_VALUE = "plan-review-lease-held";
|
|
205492
205610
|
function enclosingContainerInstanceId(parent, nodeId) {
|
|
205493
205611
|
if (!parent || parent === nodeId) return void 0;
|
|
@@ -206514,7 +206632,7 @@ var WorkflowGraphExecutor = class {
|
|
|
206514
206632
|
const failureResult = {
|
|
206515
206633
|
outcome: "failure",
|
|
206516
206634
|
// FNXC:SessionContention 2026-07-25-21:30: contention is a retryable hold, not an exception.
|
|
206517
|
-
value: isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
|
|
206635
|
+
value: lastError instanceof BranchWriteProvenanceError ? BRANCH_WRITE_PROVENANCE_FAILURE_VALUE : isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
|
|
206518
206636
|
contextPatch: {
|
|
206519
206637
|
[`node:${node.id}:error`]: lastErrorText
|
|
206520
206638
|
}
|
|
@@ -213855,8 +213973,8 @@ async function autoRecoverWorktreeSessionStartFailure(store, task, opts) {
|
|
|
213855
213973
|
}
|
|
213856
213974
|
const workspaceTask = configuredWorkspace || isWorkspaceTask(task);
|
|
213857
213975
|
if (workspaceTask) {
|
|
213858
|
-
const
|
|
213859
|
-
if (typeof
|
|
213976
|
+
const normalize10 = store.normalizeWorkspaceTaskWorktreeMetadata;
|
|
213977
|
+
if (typeof normalize10 === "function") task = await normalize10.call(store, task.id);
|
|
213860
213978
|
}
|
|
213861
213979
|
const resetRetryBudget = opts.resetRetryBudgetOnStaleMetadataClear === true;
|
|
213862
213980
|
const staleMetadataClearRecoveryRetryCount = opts.staleMetadataClearRecoveryRetryCount;
|
|
@@ -213911,7 +214029,13 @@ async function autoRecoverWorktreeSessionStartFailure(store, task, opts) {
|
|
|
213911
214029
|
worktreeSessionRetryCount: nextCount,
|
|
213912
214030
|
...nextStaleMetadataClearRecoveryCount === void 0 ? {} : { recoveryRetryCount: nextStaleMetadataClearRecoveryCount },
|
|
213913
214031
|
worktree: clearWorktreeMetadata ? null : staleWorktree,
|
|
213914
|
-
|
|
214032
|
+
/*
|
|
214033
|
+
* FNXC:BranchNaming 2026-08-21-09:36:
|
|
214034
|
+
* Session recovery often changes only worktree/session metadata. Do not reassert an unchanged
|
|
214035
|
+
* branch with engine provenance: that would erase a matching operator override and later make
|
|
214036
|
+
* its branch eligible for engine cleanup. A real canonical clear remains an explicit engine write.
|
|
214037
|
+
*/
|
|
214038
|
+
...(task.branch ?? null) === nextBranch ? {} : { branch: nextBranch, branchWriteOrigin: "engine" },
|
|
213915
214039
|
sessionFile: null
|
|
213916
214040
|
});
|
|
213917
214041
|
await opts.auditor?.database({
|
|
@@ -218009,7 +218133,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
218009
218133
|
} catch {
|
|
218010
218134
|
}
|
|
218011
218135
|
}
|
|
218012
|
-
const patch = preservedWorktree ? { branch: selected.branch } : { branch: selected.branch, worktree: null };
|
|
218136
|
+
const patch = preservedWorktree ? { branch: selected.branch, branchWriteOrigin: "engine" } : { branch: selected.branch, branchWriteOrigin: "engine", worktree: null };
|
|
218013
218137
|
if (!task.baseCommitSha) {
|
|
218014
218138
|
const derivedBaseCommit = (await execAsync18(
|
|
218015
218139
|
`git merge-base ${shellQuote2(integrationBase)} ${shellQuote2(selected.branch)}`,
|
|
@@ -221686,6 +221810,12 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
221686
221810
|
await this.emitWorkspacePartialLandNoAction(task, "merge-pending", liveness.livePaths);
|
|
221687
221811
|
continue;
|
|
221688
221812
|
}
|
|
221813
|
+
const latestTask = await this.store.getTask(task.id).catch(() => null);
|
|
221814
|
+
const blockerTask = latestTask?.status === "failed" && latestTask.error?.startsWith("Workspace partial land:") ? { ...latestTask, status: null, error: void 0 } : latestTask;
|
|
221815
|
+
if (!blockerTask || getTaskMergeBlocker(blockerTask, { skipColumnIdentityCheck: true }) !== void 0) {
|
|
221816
|
+
await this.emitWorkspacePartialLandNoAction(task, "merge-blocked", []);
|
|
221817
|
+
continue;
|
|
221818
|
+
}
|
|
221689
221819
|
const workspaceWorktrees = task.workspaceWorktrees ?? {};
|
|
221690
221820
|
const explicitScope = task.repositoryScope?.state === "confirmed" ? task.repositoryScope.repositories : void 0;
|
|
221691
221821
|
if (!explicitScope) {
|
|
@@ -221693,8 +221823,12 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
221693
221823
|
continue;
|
|
221694
221824
|
}
|
|
221695
221825
|
const repoKeys = Object.keys(workspaceWorktrees).filter(
|
|
221696
|
-
(repoRel) => explicitScope.includes(repoRel) && (task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`))
|
|
221826
|
+
(repoRel) => explicitScope.includes(repoRel) && ((task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`)) || Boolean(workspaceWorktrees[repoRel]?.landedSha))
|
|
221697
221827
|
);
|
|
221828
|
+
if (repoKeys.length === 0) {
|
|
221829
|
+
await this.emitWorkspacePartialLandNoAction(task, "empty-obligations", []);
|
|
221830
|
+
continue;
|
|
221831
|
+
}
|
|
221698
221832
|
const landedRepos = [];
|
|
221699
221833
|
const unlandedRepos = [];
|
|
221700
221834
|
const unrecoverableRepos = [];
|
|
@@ -221751,13 +221885,12 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
221751
221885
|
phase: "reconcile-workspace-partial-land"
|
|
221752
221886
|
});
|
|
221753
221887
|
if (evidenceUnavailableRepos.length > 0) {
|
|
221754
|
-
const defers = (
|
|
221755
|
-
this.
|
|
221888
|
+
const defers = (task.mergeTransientRetryCount ?? 0) + 1;
|
|
221889
|
+
await this.store.updateTask(task.id, { mergeTransientRetryCount: defers });
|
|
221756
221890
|
if (defers >= MAX_STARVATION_DROPS) {
|
|
221757
221891
|
const error2 = `Workspace partial-land evidence unavailable: branch state could not be read after ${MAX_STARVATION_DROPS} sweeps for sub-repo(s) ${evidenceUnavailableRepos.join(", ")} \u2014 manual intervention required.`;
|
|
221758
221892
|
await this.store.updateTask(task.id, { status: "failed", error: error2 });
|
|
221759
221893
|
await this.store.logEntry(task.id, error2);
|
|
221760
|
-
this.workspacePartialLandEvidenceDefers.delete(task.id);
|
|
221761
221894
|
await auditor.database({
|
|
221762
221895
|
type: "task:reconcile-workspace-partial-land",
|
|
221763
221896
|
target: task.id,
|
|
@@ -221770,7 +221903,6 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
221770
221903
|
}
|
|
221771
221904
|
continue;
|
|
221772
221905
|
}
|
|
221773
|
-
this.workspacePartialLandEvidenceDefers.delete(task.id);
|
|
221774
221906
|
if (unrecoverableRepos.length > 0) {
|
|
221775
221907
|
const missingBranches = unrecoverableRepos.map((repoRel) => workspaceWorktrees[repoRel]?.branch ?? resolveTaskWorkingBranch(task)).join(", ");
|
|
221776
221908
|
const error2 = `Workspace partial-land unrecoverable: sub-repo(s) ${unrecoverableRepos.join(", ")} have no branch (${missingBranches}) and no landedSha \u2014 manual intervention required.`;
|
|
@@ -221798,7 +221930,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
221798
221930
|
landedRepos,
|
|
221799
221931
|
unlandedRepos: [],
|
|
221800
221932
|
reason: "all-landed-not-finalized",
|
|
221801
|
-
successLog: "
|
|
221933
|
+
successLog: "Workspace merge recovery scheduled: all sub-repositories have proven landing evidence; awaiting finalize-once result"
|
|
221802
221934
|
});
|
|
221803
221935
|
recovered++;
|
|
221804
221936
|
continue;
|
|
@@ -221807,7 +221939,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
221807
221939
|
landedRepos,
|
|
221808
221940
|
unlandedRepos,
|
|
221809
221941
|
reason: landedRepos.length > 0 ? "partial-land" : "zero-land",
|
|
221810
|
-
successLog: `
|
|
221942
|
+
successLog: `Workspace merge recovery scheduled: ${landedRepos.length} landed, ${unlandedRepos.length} pending`
|
|
221811
221943
|
});
|
|
221812
221944
|
recovered++;
|
|
221813
221945
|
} catch (err) {
|
|
@@ -221872,7 +222004,8 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
221872
222004
|
}).catch(() => void 0);
|
|
221873
222005
|
return false;
|
|
221874
222006
|
}
|
|
221875
|
-
const drops = (
|
|
222007
|
+
const drops = (task.mergeTransientRetryCount ?? 0) + 1;
|
|
222008
|
+
await this.store.updateTask(task.id, { mergeTransientRetryCount: drops });
|
|
221876
222009
|
this.workspacePartialLandDrops.set(task.id, drops);
|
|
221877
222010
|
log34.warn(`reconcileWorkspacePartialLands: enqueue dropped for ${task.id} (${drops}/${MAX_STARVATION_DROPS}); merge queue rejected re-enqueue`);
|
|
221878
222011
|
if (drops >= MAX_STARVATION_DROPS) {
|
|
@@ -230129,6 +230262,7 @@ async function handleBranchConflict(deps, task, error2) {
|
|
|
230129
230262
|
status: "failed",
|
|
230130
230263
|
error: conflictMessage,
|
|
230131
230264
|
branch: error2.branchName,
|
|
230265
|
+
branchWriteOrigin: "engine",
|
|
230132
230266
|
worktree: error2.conflictingWorktreePath,
|
|
230133
230267
|
paused: true,
|
|
230134
230268
|
pausedReason: "branch-conflict-unrecoverable"
|
|
@@ -230171,8 +230305,8 @@ async function recoverMissingWorktreeSessionStartFailure(deps, task, worktreePat
|
|
|
230171
230305
|
}
|
|
230172
230306
|
let recoveryTask = task;
|
|
230173
230307
|
if (workspaceTask) {
|
|
230174
|
-
const
|
|
230175
|
-
if (typeof
|
|
230308
|
+
const normalize10 = deps.store.normalizeWorkspaceTaskWorktreeMetadata;
|
|
230309
|
+
if (typeof normalize10 === "function") recoveryTask = await normalize10.call(deps.store, task.id);
|
|
230176
230310
|
}
|
|
230177
230311
|
if (!workspaceTask && isInsideWorktreesDir(deps.rootDir, staleWorktreePath)) {
|
|
230178
230312
|
try {
|
|
@@ -230564,8 +230698,21 @@ async function detectWorkspaceMainCheckoutWork(deps, task, repos, declaredScope)
|
|
|
230564
230698
|
const { stdout } = await execAsync40("git log -n 200 --format=%H%x1f%ct%x1f%B%x1e HEAD", { ...probeOptions, cwd: checkout });
|
|
230565
230699
|
const attributed = new RegExp(`(?:${task.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}|Fusion-Task-Id:\\s*${task.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "i");
|
|
230566
230700
|
for (const commit of parseCommits(stdout)) {
|
|
230567
|
-
const
|
|
230568
|
-
|
|
230701
|
+
const entry = workspaceWorktrees[repo];
|
|
230702
|
+
const recordedLanding = entry?.landedSha === commit.sha || task.mergeDetails?.workspaceLandedShas?.[repo] === commit.sha || task.mergeDetails?.commitSha === commit.sha;
|
|
230703
|
+
let reachableFromBaseline = false;
|
|
230704
|
+
if (entry?.baseCommitSha) {
|
|
230705
|
+
try {
|
|
230706
|
+
await execAsync40(`git merge-base --is-ancestor ${commit.sha} ${entry.baseCommitSha}`, { ...probeOptions, cwd: checkout });
|
|
230707
|
+
reachableFromBaseline = true;
|
|
230708
|
+
} catch {
|
|
230709
|
+
}
|
|
230710
|
+
}
|
|
230711
|
+
if (attributed.test(commit.body) && !recordedLanding && !reachableFromBaseline && anchor !== null && commit.committedAt >= anchor) {
|
|
230712
|
+
violations.push({ repo, files: [], commits: [commit.sha], evidence: "task-attributed-commit" });
|
|
230713
|
+
} else if (!recordedLanding && !reachableFromBaseline && anchor !== null && commit.committedAt >= anchor) {
|
|
230714
|
+
warnings.push({ repo, files: [], commits: [commit.sha], reason: "pre-existing-dirt" });
|
|
230715
|
+
}
|
|
230569
230716
|
}
|
|
230570
230717
|
} catch {
|
|
230571
230718
|
warnings.push({ repo, files: [], commits: [], reason: "commit-scan-unavailable" });
|
|
@@ -230632,7 +230779,7 @@ async function verifyWorktreeInvariants(deps, task, worktreePathOverride, allowR
|
|
|
230632
230779
|
reason: "main_checkout_edit",
|
|
230633
230780
|
repo: firstMainCheckoutViolation.repo,
|
|
230634
230781
|
observed: `${firstMainCheckoutViolation.evidence}: ${observed}`,
|
|
230635
|
-
expected: `move task work into the acquired fusion/${task.id} worktree for ${firstMainCheckoutViolation.repo} (acquire it first if needed), restore its main checkout, then
|
|
230782
|
+
expected: `move task work into the acquired fusion/${task.id} worktree for ${firstMainCheckoutViolation.repo} (acquire it first if needed), restore its main checkout, then ask an operator to retry fn_task_done`
|
|
230636
230783
|
};
|
|
230637
230784
|
}
|
|
230638
230785
|
const zeroAcquire = classifyWorkspaceZeroAcquire(task, {
|
|
@@ -232005,7 +232152,7 @@ ${scopeGuard}
|
|
|
232005
232152
|
}
|
|
232006
232153
|
|
|
232007
232154
|
// ../engine/src/executor/send-task-back-for-fix.ts
|
|
232008
|
-
async function sendTaskBackForFix(deps, task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false, retryPresentation, findings) {
|
|
232155
|
+
async function sendTaskBackForFix(deps, task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false, retryPresentation, findings, persistWorktreePath) {
|
|
232009
232156
|
const taskId = task.id;
|
|
232010
232157
|
deps.clearCompletedTaskWatchdog(taskId);
|
|
232011
232158
|
const { task: authoritativeRemediationTask, route: externalExecutionRoute } = await resolveAuthoritativeExternalExecutionRoute(deps.store, task);
|
|
@@ -232048,7 +232195,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
232048
232195
|
remediationWorktreePath,
|
|
232049
232196
|
`${taskId}: sent back to in-progress for remediation`,
|
|
232050
232197
|
preserveResumeState,
|
|
232051
|
-
!externalExecutionRoute.configured
|
|
232198
|
+
persistWorktreePath ?? !externalExecutionRoute.configured
|
|
232052
232199
|
);
|
|
232053
232200
|
}
|
|
232054
232201
|
|
|
@@ -236837,6 +236984,7 @@ init_logger();
|
|
|
236837
236984
|
init_run_audit();
|
|
236838
236985
|
init_worktree_acquisition();
|
|
236839
236986
|
init_workspace_config_resolver();
|
|
236987
|
+
init_workspace_review_remediation();
|
|
236840
236988
|
async function ensureGraphCustomNodeWorktree(deps, task, settings, nodeId, refreshStaleBase = false) {
|
|
236841
236989
|
const workspaceConfig = await resolveWorkspaceConfigOnce(deps);
|
|
236842
236990
|
const syntheticRunId = generateSyntheticRunId("workflow-node-worktree", task.id);
|
|
@@ -236856,6 +237004,7 @@ async function ensureGraphCustomNodeWorktree(deps, task, settings, nodeId, refre
|
|
|
236856
237004
|
deps.getRunContextFor(task.id)
|
|
236857
237005
|
);
|
|
236858
237006
|
if (workspaceConfig) {
|
|
237007
|
+
const remediationRepository = resolveWorkspaceReviewRemediationRepository(task, workspaceConfig.repos);
|
|
236859
237008
|
const workspace = await acquireWorkspaceTaskWorktrees({
|
|
236860
237009
|
workspaceConfig,
|
|
236861
237010
|
workspaceRootDir: deps.rootDir,
|
|
@@ -236880,7 +237029,8 @@ async function ensureGraphCustomNodeWorktree(deps, task, settings, nodeId, refre
|
|
|
236880
237029
|
return result;
|
|
236881
237030
|
}),
|
|
236882
237031
|
taskEnv: process.env,
|
|
236883
|
-
addActiveWorktree: deps.addActiveWorktree
|
|
237032
|
+
addActiveWorktree: deps.addActiveWorktree,
|
|
237033
|
+
remediationRepository
|
|
236884
237034
|
});
|
|
236885
237035
|
deps.onStart?.(workspace.task, workspace.coordinatorWorktreePath);
|
|
236886
237036
|
executorLog.debug(`${task.id}: workflow node '${nodeId}' using workspace coordinator ${workspace.coordinatorWorktreePath}`);
|
|
@@ -237410,6 +237560,7 @@ init_effective_settings();
|
|
|
237410
237560
|
init_replan_target();
|
|
237411
237561
|
init_transient_error_detector();
|
|
237412
237562
|
init_logger();
|
|
237563
|
+
init_workspace_review_remediation();
|
|
237413
237564
|
function normalizeConvergenceText(value) {
|
|
237414
237565
|
return (value ?? "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
237415
237566
|
}
|
|
@@ -237570,7 +237721,22 @@ ${info.feedback}`,
|
|
|
237570
237721
|
return false;
|
|
237571
237722
|
}
|
|
237572
237723
|
const revisionKey = optionalStepRevisionKey2(info.nodeId, info.stepName);
|
|
237573
|
-
|
|
237724
|
+
const reviewResult = (liveTask.workflowStepResults ?? []).find(
|
|
237725
|
+
(result) => (result.workflowStepId === info.nodeId || result.workflowStepName === info.stepName) && result.verdict === "REVISE"
|
|
237726
|
+
);
|
|
237727
|
+
const derivedRemediation = reviewResult ? deriveWorkspaceReviewRemediation(reviewResult) : void 0;
|
|
237728
|
+
const remediation = derivedRemediation && liveTask.workspaceWorktrees ? derivedRemediation : void 0;
|
|
237729
|
+
const priorRemediation = liveTask.repositoryScope?.reviewRemediation;
|
|
237730
|
+
const hasDurableRepeatedWorkspaceReview = remediation !== void 0 && priorRemediation?.scopeRevision === remediation.scopeRevision && priorRemediation.repository === remediation.repository && priorRemediation.inputSignature === remediation.inputSignature;
|
|
237731
|
+
const updateWorkspaceReviewState = deps.store.updateWorkspaceReviewState;
|
|
237732
|
+
if (remediation && !hasDurableRepeatedWorkspaceReview && updateWorkspaceReviewState) {
|
|
237733
|
+
const persisted = await updateWorkspaceReviewState.call(deps.store, taskId, remediation.scopeRevision, remediation);
|
|
237734
|
+
if (!persisted.updated) {
|
|
237735
|
+
executorLog.warn(`${taskId}: workspace Code Review remediation was superseded by a repository scope change.`);
|
|
237736
|
+
return false;
|
|
237737
|
+
}
|
|
237738
|
+
}
|
|
237739
|
+
if (hasDurableRepeatedWorkspaceReview || (!remediation || !updateWorkspaceReviewState) && hasRepeatedUnchangedCodeReview(liveTask, info)) {
|
|
237574
237740
|
const runContext = deps.getRunContextFor(taskId);
|
|
237575
237741
|
await deps.store.logEntry(
|
|
237576
237742
|
taskId,
|
|
@@ -237618,9 +237784,18 @@ Feedback:
|
|
|
237618
237784
|
${info.feedback}`, revisionKey),
|
|
237619
237785
|
deps.getRunContextFor(taskId)
|
|
237620
237786
|
);
|
|
237621
|
-
|
|
237787
|
+
const remediationWorktreePath = remediation ? liveTask.workspaceWorktrees?.[remediation.repository]?.worktreePath : liveTask.worktree;
|
|
237788
|
+
if (remediation && !remediationWorktreePath) {
|
|
237789
|
+
await deps.store.updateTask(taskId, {
|
|
237790
|
+
status: "awaiting-approval",
|
|
237791
|
+
awaitingApprovalReason: "code-review-non-convergence",
|
|
237792
|
+
error: `Workspace Code Review remediation target ${remediation.repository} has no acquired worktree.`
|
|
237793
|
+
});
|
|
237794
|
+
return false;
|
|
237795
|
+
}
|
|
237796
|
+
const sendArgs = [
|
|
237622
237797
|
liveTask,
|
|
237623
|
-
|
|
237798
|
+
remediationWorktreePath ?? "",
|
|
237624
237799
|
info.feedback,
|
|
237625
237800
|
info.stepName,
|
|
237626
237801
|
`Pre-merge optional workflow step "${info.stepName}" requested revision`,
|
|
@@ -237628,7 +237803,12 @@ ${info.feedback}`, revisionKey),
|
|
|
237628
237803
|
false,
|
|
237629
237804
|
{ attempt: nextCount, max: budget.unbounded ? void 0 : budget.max },
|
|
237630
237805
|
info.findings
|
|
237631
|
-
|
|
237806
|
+
];
|
|
237807
|
+
if (remediation) {
|
|
237808
|
+
await deps.sendTaskBackForFix(...sendArgs, false);
|
|
237809
|
+
} else {
|
|
237810
|
+
await deps.sendTaskBackForFix(...sendArgs);
|
|
237811
|
+
}
|
|
237632
237812
|
return true;
|
|
237633
237813
|
}
|
|
237634
237814
|
|
|
@@ -238498,6 +238678,14 @@ function createTaskDoneTool(deps, taskId, worktreePath, promptContent, codeRevie
|
|
|
238498
238678
|
const refusalMessage = `fn_task_done refused: ${invariantCheck.reason} \u2014 observed=${invariantCheck.observed}, expected=${invariantCheck.expected}`;
|
|
238499
238679
|
await store.logEntry(taskId, refusalMessage, void 0, deps.getRunContextFor(task.id));
|
|
238500
238680
|
executorLog.error(`${taskId}: fn_task_done refused (${invariantCheck.reason}) \u2014 observed=${invariantCheck.observed}, expected=${invariantCheck.expected}`);
|
|
238681
|
+
if (invariantCheck.reason === "main_checkout_edit") {
|
|
238682
|
+
await store.updateTask(taskId, { status: "failed", error: refusalMessage });
|
|
238683
|
+
await store.logEntry(taskId, `${refusalMessage} \u2014 operator cleanup and Retry are required; executor retry budget preserved`, void 0, deps.getRunContextFor(task.id));
|
|
238684
|
+
return {
|
|
238685
|
+
content: [{ type: "text", text: refusalMessage }],
|
|
238686
|
+
details: { error: refusalMessage }
|
|
238687
|
+
};
|
|
238688
|
+
}
|
|
238501
238689
|
const priorRequeues = task.taskDoneRetryCount ?? 0;
|
|
238502
238690
|
const nextRequeueCount = priorRequeues + 1;
|
|
238503
238691
|
if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
|
|
@@ -240161,7 +240349,9 @@ Repository scope (task-level; review this plan once): ${detail.repositoryScope?.
|
|
|
240161
240349
|
return {
|
|
240162
240350
|
repositoryScope: {
|
|
240163
240351
|
...currentScope,
|
|
240164
|
-
reviewEvidence: Object.fromEntries(Object.entries(review.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }]))
|
|
240352
|
+
reviewEvidence: Object.fromEntries(Object.entries(review.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }])),
|
|
240353
|
+
// FNXC:WorkspaceFinalization 2026-08-21-09:50: An APPROVE for this fenced scope ends its REVISE episode; retaining its target would misroute later acquisition.
|
|
240354
|
+
...currentScope.reviewRemediation?.scopeRevision === review.repositoryScopeRevision ? { reviewRemediation: void 0 } : {}
|
|
240165
240355
|
}
|
|
240166
240356
|
};
|
|
240167
240357
|
});
|
|
@@ -241438,6 +241628,7 @@ async function executeWorkflowGraph(deps, task, opts) {
|
|
|
241438
241628
|
// ../engine/src/executor/run-graph-custom-node.ts
|
|
241439
241629
|
init_src2();
|
|
241440
241630
|
init_logger();
|
|
241631
|
+
init_workspace_review_remediation();
|
|
241441
241632
|
import { existsSync as existsSync74 } from "node:fs";
|
|
241442
241633
|
var WORKFLOW_THINKING_LEVEL_SET2 = new Set(THINKING_LEVELS);
|
|
241443
241634
|
async function runGraphCustomNode(deps, node, nodeTask, settings, columnBinding, graphContext, outputLanguage) {
|
|
@@ -241517,7 +241708,8 @@ async function runGraphCustomNode(deps, node, nodeTask, settings, columnBinding,
|
|
|
241517
241708
|
executionTarget = await deps.ensureGraphCustomNodeWorktree(acquisitionTask, settings, node.id);
|
|
241518
241709
|
}
|
|
241519
241710
|
}
|
|
241520
|
-
const
|
|
241711
|
+
const remediationRepository = workspaceConfig ? resolveWorkspaceReviewRemediationRepository(executionTarget, workspaceConfig.repos) : void 0;
|
|
241712
|
+
const workspaceCoordinator = workspaceConfig ? remediationRepository ? executionTarget.workspaceWorktrees?.[remediationRepository]?.worktreePath : Object.keys(executionTarget.workspaceWorktrees ?? {}).filter((repoRelPath) => workspaceConfig.repos.includes(repoRelPath)).sort().map((repoRelPath) => executionTarget.workspaceWorktrees?.[repoRelPath]?.worktreePath).find((path6) => typeof path6 === "string" && path6.length > 0) : void 0;
|
|
241521
241713
|
if (!executionTarget.worktree && !workspaceCoordinator) {
|
|
241522
241714
|
return { outcome: "failure", value: "no-worktree-for-write-node" };
|
|
241523
241715
|
}
|
|
@@ -241677,7 +241869,9 @@ ${prompt}`;
|
|
|
241677
241869
|
return {
|
|
241678
241870
|
repositoryScope: {
|
|
241679
241871
|
...currentScope,
|
|
241680
|
-
reviewEvidence: Object.fromEntries(Object.entries(aggregate.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }]))
|
|
241872
|
+
reviewEvidence: Object.fromEntries(Object.entries(aggregate.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }])),
|
|
241873
|
+
// FNXC:WorkspaceFinalization 2026-08-21-09:50: A current-scope APPROVE clears the durable REVISE coordinator before graph completion can schedule another run.
|
|
241874
|
+
...currentScope.reviewRemediation?.scopeRevision === aggregate.repositoryScopeRevision ? { reviewRemediation: void 0 } : {}
|
|
241681
241875
|
}
|
|
241682
241876
|
};
|
|
241683
241877
|
});
|
|
@@ -241824,6 +242018,13 @@ async function handleGraphFailure(deps, task, result) {
|
|
|
241824
242018
|
await deps.persistTokenUsage(task.id);
|
|
241825
242019
|
return;
|
|
241826
242020
|
}
|
|
242021
|
+
if (graphFailureValue(result) === BRANCH_WRITE_PROVENANCE_FAILURE_VALUE) {
|
|
242022
|
+
const diagnostic = graphFailureErrorTexts(result).find((message2) => message2.includes("branchWriteOrigin is required when branch is provided")) ?? "branchWriteOrigin is required when branch is provided";
|
|
242023
|
+
await deps.store.logEntry(task.id, diagnostic, void 0, deps.getRunContextFor(task.id));
|
|
242024
|
+
await deps.store.updateTask(task.id, { status: "failed", error: diagnostic }, deps.getRunContextFor(task.id));
|
|
242025
|
+
await deps.persistTokenUsage(task.id);
|
|
242026
|
+
return;
|
|
242027
|
+
}
|
|
241827
242028
|
if (isSessionContentionGraphFailure(result)) {
|
|
241828
242029
|
await deps.holdForSessionContention(task, live, result);
|
|
241829
242030
|
await deps.persistTokenUsage(task.id);
|
|
@@ -245639,6 +245840,7 @@ init_planner_lane_resolution();
|
|
|
245639
245840
|
init_effective_settings();
|
|
245640
245841
|
init_run_audit();
|
|
245641
245842
|
init_worktree_acquisition();
|
|
245843
|
+
init_workspace_review_remediation();
|
|
245642
245844
|
init_worktree_paths();
|
|
245643
245845
|
init_worktree_pool();
|
|
245644
245846
|
var execAsync52 = promisify74(exec55);
|
|
@@ -245817,6 +246019,7 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
|
|
|
245817
246019
|
deps.registerConfiguredCommandController(task.id, taskCommandAbortController);
|
|
245818
246020
|
let workspaceCoordinatorWorktree;
|
|
245819
246021
|
if (hasWorkspaceRepos && deps.workspaceConfig) {
|
|
246022
|
+
const remediationRepository = resolveWorkspaceReviewRemediationRepository(task, deps.workspaceConfig.repos);
|
|
245820
246023
|
const workspace = await acquireWorkspaceTaskWorktrees({
|
|
245821
246024
|
workspaceConfig: deps.workspaceConfig,
|
|
245822
246025
|
workspaceRootDir: deps.rootDir,
|
|
@@ -245841,7 +246044,8 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
|
|
|
245841
246044
|
return result;
|
|
245842
246045
|
}),
|
|
245843
246046
|
taskEnv,
|
|
245844
|
-
addActiveWorktree: deps.addActiveWorktree
|
|
246047
|
+
addActiveWorktree: deps.addActiveWorktree,
|
|
246048
|
+
remediationRepository
|
|
245845
246049
|
});
|
|
245846
246050
|
task = workspace.task;
|
|
245847
246051
|
workspaceCoordinatorWorktree = workspace.coordinatorWorktreePath;
|
|
@@ -269259,6 +269463,35 @@ init_merge_dependency_sync();
|
|
|
269259
269463
|
init_active_session_registry();
|
|
269260
269464
|
init_mcp_resolution();
|
|
269261
269465
|
|
|
269466
|
+
// ../engine/src/merge/workspace-merge-readiness.ts
|
|
269467
|
+
function resolveWorkspaceMergeReadiness(task, freshModifiedRepositories, netZeroBranchRepositories) {
|
|
269468
|
+
const scope = task.repositoryScope;
|
|
269469
|
+
if (scope?.state !== "confirmed") {
|
|
269470
|
+
return { kind: "blocked", reason: `Workspace repository scope is unresolved for ${task.id}; operator confirmation is required before landing` };
|
|
269471
|
+
}
|
|
269472
|
+
const entries = task.workspaceWorktrees ?? {};
|
|
269473
|
+
const declaredRepositories = scope.repositories.map((repository) => repository.trim());
|
|
269474
|
+
if (declaredRepositories.some((repository) => !repository) || new Set(declaredRepositories).size !== declaredRepositories.length) {
|
|
269475
|
+
return { kind: "blocked", reason: `Workspace repository scope has duplicate or invalid declarations for ${task.id}; operator repair is required` };
|
|
269476
|
+
}
|
|
269477
|
+
const worktreePaths = Object.values(entries).map((entry) => entry.worktreePath?.trim()).filter((path6) => Boolean(path6));
|
|
269478
|
+
if (new Set(worktreePaths).size !== worktreePaths.length) {
|
|
269479
|
+
return { kind: "blocked", reason: `Workspace merge has duplicate worktree paths for ${task.id}; operator repair is required` };
|
|
269480
|
+
}
|
|
269481
|
+
const repositories = declaredRepositories.filter((repo) => Boolean(entries[repo])).filter((repo) => freshModifiedRepositories.has(repo) || netZeroBranchRepositories.has(repo) || Boolean(entries[repo]?.landedSha)).sort();
|
|
269482
|
+
const preservedFiles = [...new Set(task.modifiedFiles ?? [])].sort();
|
|
269483
|
+
if (repositories.length > 0) return { kind: "ready", repositories, preservedFiles };
|
|
269484
|
+
if (task.noCommitsExpected === true && scope.repositories.length === 0) {
|
|
269485
|
+
return { kind: "no-op", repositories: [], preservedFiles: [] };
|
|
269486
|
+
}
|
|
269487
|
+
const declared = [...declaredRepositories].sort();
|
|
269488
|
+
const knownEntries = declared.filter((repo) => entries[repo] !== void 0);
|
|
269489
|
+
if (knownEntries.length === 0) {
|
|
269490
|
+
return { kind: "blocked", reason: `Workspace merge has no acquired repositories matching confirmed scope for ${task.id}; operator repair is required` };
|
|
269491
|
+
}
|
|
269492
|
+
return { kind: "blocked", reason: `Workspace merge has no evidenced landing obligations for ${task.id}; operator repair is required` };
|
|
269493
|
+
}
|
|
269494
|
+
|
|
269262
269495
|
// ../engine/src/merge/workspace-fence-ref.ts
|
|
269263
269496
|
import { execFile as execFile33 } from "node:child_process";
|
|
269264
269497
|
import { createHash as createHash35 } from "node:crypto";
|
|
@@ -271000,6 +271233,14 @@ var WorkspaceRepoLandBusyError = class extends Error {
|
|
|
271000
271233
|
}
|
|
271001
271234
|
retryable = true;
|
|
271002
271235
|
};
|
|
271236
|
+
var WorkspaceMergeTechnicalError = class extends Error {
|
|
271237
|
+
constructor(kind, message) {
|
|
271238
|
+
super(message);
|
|
271239
|
+
this.kind = kind;
|
|
271240
|
+
this.name = "WorkspaceMergeTechnicalError";
|
|
271241
|
+
}
|
|
271242
|
+
retryable = true;
|
|
271243
|
+
};
|
|
271003
271244
|
var WorkspacePartialLandError = class extends Error {
|
|
271004
271245
|
constructor(landedCount, failedRepos, message) {
|
|
271005
271246
|
super(message);
|
|
@@ -271061,8 +271302,21 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
271061
271302
|
const taskTitle = task.title?.trim() ? task.title.split("\n")[0] : void 0;
|
|
271062
271303
|
const liveMergeBoundaryTask = await store.getTask(taskId).catch(() => void 0);
|
|
271063
271304
|
const mergeBoundaryTask = liveMergeBoundaryTask?.workspaceWorktrees ? liveMergeBoundaryTask : task;
|
|
271305
|
+
const reviewColumns = /* @__PURE__ */ new Set(["in-review"]);
|
|
271306
|
+
const workflowIr = await resolveWorkflowIrForTask(store, taskId).catch(() => void 0);
|
|
271307
|
+
if (workflowIr) for (const column of resolveReviewColumns(workflowIr)) reviewColumns.add(column);
|
|
271308
|
+
const completeColumns = /* @__PURE__ */ new Set(["done"]);
|
|
271309
|
+
if (workflowIr) for (const column of resolveTerminalColumns(workflowIr)) completeColumns.add(column);
|
|
271310
|
+
if (!completeColumns.has(mergeBoundaryTask.column)) {
|
|
271311
|
+
const mergeBlocker = getTaskMergeBlocker(mergeBoundaryTask, {
|
|
271312
|
+
manual: options.manual === true,
|
|
271313
|
+
reviewColumns
|
|
271314
|
+
});
|
|
271315
|
+
if (mergeBlocker) throw new WorkspaceFinalizeBlockedError(taskId, mergeBlocker);
|
|
271316
|
+
}
|
|
271064
271317
|
const confirmedScope = mergeBoundaryTask.repositoryScope?.state === "confirmed" ? new Set(mergeBoundaryTask.repositoryScope.repositories) : void 0;
|
|
271065
271318
|
const mergeBoundaryModifiedFiles = [];
|
|
271319
|
+
const mergeBoundaryModifiedRepositories = /* @__PURE__ */ new Set();
|
|
271066
271320
|
const mergeBoundaryFingerprints = {};
|
|
271067
271321
|
const modifiedOutOfScopeRepositories = /* @__PURE__ */ new Set();
|
|
271068
271322
|
const netZeroBranchRepositories = /* @__PURE__ */ new Set();
|
|
@@ -271079,6 +271333,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
271079
271333
|
if (files.length > 0 && !confirmedScope?.has(repoRel)) {
|
|
271080
271334
|
modifiedOutOfScopeRepositories.add(repoRel);
|
|
271081
271335
|
} else if (confirmedScope?.has(repoRel)) {
|
|
271336
|
+
if (files.length > 0) mergeBoundaryModifiedRepositories.add(repoRel);
|
|
271082
271337
|
mergeBoundaryModifiedFiles.push(...files.map((file) => `${repoRel}/${file}`));
|
|
271083
271338
|
if (files.length === 0) {
|
|
271084
271339
|
const { stdout: aheadCount } = await execFileAsync29("git", ["rev-list", "--count", `HEAD..${entry.branch}`], { cwd: entry.worktreePath, encoding: "utf8" });
|
|
@@ -271094,19 +271349,26 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
271094
271349
|
}
|
|
271095
271350
|
const normalizedMergeBoundaryFiles = [...new Set(mergeBoundaryModifiedFiles)].sort();
|
|
271096
271351
|
const persistedReviewFiles = [...new Set(mergeBoundaryTask.modifiedFiles ?? [])].sort();
|
|
271097
|
-
const approvedReviewEvidence = mergeBoundaryTask.repositoryScope?.reviewEvidence
|
|
271098
|
-
const
|
|
271099
|
-
|
|
271352
|
+
const approvedReviewEvidence = mergeBoundaryTask.repositoryScope?.reviewEvidence;
|
|
271353
|
+
const requiresRepositoryReviewEvidence = approvedReviewEvidence !== void 0 || (mergeBoundaryTask.enabledWorkflowSteps ?? []).some((step) => /review/i.test(step));
|
|
271354
|
+
const missingOrStaleReviewApproval = requiresRepositoryReviewEvidence && Object.entries(mergeBoundaryFingerprints).some(([repoRel, fingerprint]) => approvedReviewEvidence?.[repoRel]?.fingerprint !== fingerprint);
|
|
271355
|
+
if (requiresRepositoryReviewEvidence && normalizedMergeBoundaryFiles.some((file) => !persistedReviewFiles.includes(file)) || missingOrStaleReviewApproval) {
|
|
271100
271356
|
throw new Error(`Workspace merge evidence changed after review for ${taskId}; return the task to Code Review before landing`);
|
|
271101
271357
|
}
|
|
271102
|
-
|
|
271103
|
-
|
|
271104
|
-
|
|
271105
|
-
|
|
271106
|
-
|
|
271107
|
-
|
|
271358
|
+
const readiness = resolveWorkspaceMergeReadiness(
|
|
271359
|
+
mergeBoundaryTask,
|
|
271360
|
+
mergeBoundaryModifiedRepositories,
|
|
271361
|
+
netZeroBranchRepositories
|
|
271362
|
+
);
|
|
271363
|
+
if (readiness.kind === "blocked") throw new Error(readiness.reason);
|
|
271364
|
+
if (readiness.kind === "no-op") {
|
|
271365
|
+
throw new Error(`Workspace task ${taskId} has explicit no-commits policy and cannot enter repository landing`);
|
|
271108
271366
|
}
|
|
271109
|
-
const
|
|
271367
|
+
const retainedModifiedFiles = [.../* @__PURE__ */ new Set([...normalizedMergeBoundaryFiles, ...readiness.preservedFiles])].sort();
|
|
271368
|
+
await store.updateTask(taskId, { modifiedFiles: retainedModifiedFiles });
|
|
271369
|
+
task = { ...mergeBoundaryTask, modifiedFiles: retainedModifiedFiles };
|
|
271370
|
+
const workspaceWorktrees = task.workspaceWorktrees ?? {};
|
|
271371
|
+
const repoKeys = readiness.repositories;
|
|
271110
271372
|
const repos = [];
|
|
271111
271373
|
let allLanded = true;
|
|
271112
271374
|
await setStatus("merging");
|
|
@@ -271135,7 +271397,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
271135
271397
|
};
|
|
271136
271398
|
} catch (error2) {
|
|
271137
271399
|
if (error2 instanceof WorkspaceFenceRefError) {
|
|
271138
|
-
throw new
|
|
271400
|
+
throw new WorkspaceMergeTechnicalError("dispatch-fence-publication", `Workspace merge dispatch fence publication failed for ${taskId}: ${error2.message}`);
|
|
271139
271401
|
}
|
|
271140
271402
|
throw error2;
|
|
271141
271403
|
}
|
|
@@ -271224,7 +271486,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
271224
271486
|
} catch (error2) {
|
|
271225
271487
|
if (durableLandLease) await store.releaseWorkspaceLease(durableLandLease).catch(() => void 0);
|
|
271226
271488
|
if (error2 instanceof WorkspaceRepoLandBusyError) throw error2;
|
|
271227
|
-
throw new
|
|
271489
|
+
throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease unavailable for ${repoRel}: ${getErrorMessage4(error2)}`);
|
|
271228
271490
|
}
|
|
271229
271491
|
try {
|
|
271230
271492
|
activeSessionRegistry.registerPath(repoRootDir, {
|
|
@@ -271261,7 +271523,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
271261
271523
|
renewalTimer?.unref?.();
|
|
271262
271524
|
const landSignal = options.signal ? AbortSignal.any([options.signal, leaseAbort.signal]) : leaseAbort.signal;
|
|
271263
271525
|
const assertLeaseLive = () => {
|
|
271264
|
-
if (leaseLost || leaseAbort.signal.aborted) throw new
|
|
271526
|
+
if (leaseLost || leaseAbort.signal.aborted) throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease renewal was lost for ${repoRel}`);
|
|
271265
271527
|
};
|
|
271266
271528
|
try {
|
|
271267
271529
|
const landResult = await landOneRepo(repoRootDir, entry.branch, integrationBranch, {
|
|
@@ -271346,11 +271608,11 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
|
|
|
271346
271608
|
repos.push({ repo: repoRel, repoRootDir, integrationBranch, branch: entry.branch, status: "empty" });
|
|
271347
271609
|
}
|
|
271348
271610
|
} catch (err) {
|
|
271349
|
-
if (leaseLost) throw new
|
|
271611
|
+
if (leaseLost) throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease renewal was lost for ${repoRel}`);
|
|
271350
271612
|
if (isMergeAbortedError(err)) throw err;
|
|
271351
271613
|
if (err instanceof WorkspacePartialLandError) throw err;
|
|
271352
271614
|
if (err instanceof WorkspaceFenceRefError) {
|
|
271353
|
-
throw new
|
|
271615
|
+
throw new WorkspaceMergeTechnicalError("repository-fence-publication", `Workspace repository fence publication failed for ${repoRel}: ${err.message}`);
|
|
271354
271616
|
}
|
|
271355
271617
|
const message = getErrorMessage4(err);
|
|
271356
271618
|
await log42(`AI merge (workspace): sub-repo ${repoRel} land failed: ${message}`);
|
|
@@ -276162,6 +276424,15 @@ ${detail}`);
|
|
|
276162
276424
|
}
|
|
276163
276425
|
continue;
|
|
276164
276426
|
}
|
|
276427
|
+
if (err instanceof WorkspaceMergeTechnicalError) {
|
|
276428
|
+
await store.logEntry(taskId, `Workspace merge technical failure (${err.kind}): ${err.message}`, "WorkspaceMergeTechnicalFailure").catch(() => void 0);
|
|
276429
|
+
if (hasManualResolver) {
|
|
276430
|
+
this.rejectMergeResolvers(taskId, err);
|
|
276431
|
+
} else if (!await this.maybeRetryTransientMerge(store, taskId, await store.getTask(taskId).catch(() => null), err.message, true)) {
|
|
276432
|
+
await store.updateTask(taskId, { status: "failed", error: err.message }).catch(() => void 0);
|
|
276433
|
+
}
|
|
276434
|
+
continue;
|
|
276435
|
+
}
|
|
276165
276436
|
const isWorkspaceBusyError = err instanceof WorkspaceRepoLandBusyError || err instanceof WorkspaceMergeDispatchBusyError || err instanceof WorkspaceMergeDispatchSupersededError;
|
|
276166
276437
|
if (isWorkspaceBusyError && hasManualResolver) {
|
|
276167
276438
|
await store.logEntry(taskId, `Workspace sub-repo land busy (contention): ${errorMsg}`, "WorkspaceRepoLandBusy").catch(() => void 0);
|
|
@@ -276169,18 +276440,18 @@ ${detail}`);
|
|
|
276169
276440
|
continue;
|
|
276170
276441
|
}
|
|
276171
276442
|
if (isWorkspaceBusyError && !hasManualResolver) {
|
|
276172
|
-
const
|
|
276443
|
+
const liveTask = await store.getTask(taskId).catch(() => null);
|
|
276444
|
+
const busyCount = liveTask?.mergeTransientRetryCount ?? 0;
|
|
276173
276445
|
await store.logEntry(taskId, `Workspace sub-repo land busy (contention): ${errorMsg}`, "WorkspaceRepoLandBusy").catch(() => void 0);
|
|
276174
276446
|
if (busyCount < _ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES) {
|
|
276175
|
-
|
|
276447
|
+
const nextCount = busyCount + 1;
|
|
276176
276448
|
const delayMs = Math.min(5e3 * Math.pow(2, busyCount), 6e4);
|
|
276177
|
-
await store.updateTask(taskId, { status: null }).catch(() => void 0);
|
|
276449
|
+
await store.updateTask(taskId, { status: null, mergeTransientRetryCount: nextCount }).catch(() => void 0);
|
|
276178
276450
|
runtimeLog.log(
|
|
276179
|
-
`Workspace land busy re-enqueue ${
|
|
276451
|
+
`Workspace land busy re-enqueue ${nextCount}/${_ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES} for ${taskId} in ${delayMs / 1e3}s (durable transient retry)`
|
|
276180
276452
|
);
|
|
276181
276453
|
this.scheduleWorkspaceBusyReenqueue(taskId, delayMs);
|
|
276182
276454
|
} else {
|
|
276183
|
-
this.workspaceBusyReenqueues.delete(taskId);
|
|
276184
276455
|
await store.updateTask(taskId, { status: "failed", error: errorMsg }).catch(() => void 0);
|
|
276185
276456
|
runtimeLog.error(
|
|
276186
276457
|
`Auto-merge: ${taskId} workspace land busy ${_ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES} times \u2014 parked as failed (sustained sub-repo lease contention)`
|