@runfusion/fusion 0.74.0-beta.1 → 0.74.0-beta.2
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 +399 -25
- package/dist/child-process-worker.js +396 -22
- package/dist/client/.vite/manifest.json +256 -256
- package/dist/client/assets/{AgentDetailView-Bopzxiqb.js → AgentDetailView-DH4Cn5iG.js} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor-i5N7rUlL.js → AgentPermissionPolicyEditor-BycC7HhP.js} +1 -1
- package/dist/client/assets/{AgentsView-M3GoJDHH.js → AgentsView-Dkij8Sm8.js} +3 -3
- package/dist/client/assets/{ChatView-CqiyA9Kt.js → ChatView-DAsZY1We.js} +1 -1
- package/dist/client/assets/{CommandCenter-CAvMGKhl.js → CommandCenter-jTzBo-pg.js} +1 -1
- package/dist/client/assets/{DevServerView-wqM6h3nV.js → DevServerView-CxlacYJp.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-C8OyXCE-.js → DirectoryPicker-DsCk3Nsw.js} +1 -1
- package/dist/client/assets/{DocumentsView-CjNMZQi8.js → DocumentsView-8EI6KNBY.js} +1 -1
- package/dist/client/assets/{EvalsView-BVlbuB5t.js → EvalsView-kYnaIk11.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-CIwCg8sq.js → ExperimentalAgentOnboardingModal-ce6DROKD.js} +1 -1
- package/dist/client/assets/{GoalsView-Cnw2igil.js → GoalsView-BVlTtw-u.js} +1 -1
- package/dist/client/assets/{InsightsView-D7GmD-Kx.js → InsightsView-BKQeROkb.js} +1 -1
- package/dist/client/assets/{MemoryView-BlqkfIE5.js → MemoryView-DCzTnxsV.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-EC94zLMq.js → PiExtensionsManager-C94HwDz5.js} +1 -1
- package/dist/client/assets/{PluginManager-pYyCD3uh.js → PluginManager-C3DegklW.js} +1 -1
- package/dist/client/assets/{PullRequestView-BAb0vVhu.js → PullRequestView-CrE89UL7.js} +1 -1
- package/dist/client/assets/{ReportModal-COmReivA.js → ReportModal-BD1ZUDdV.js} +1 -1
- package/dist/client/assets/{ResearchView-DLMo4rFw.js → ResearchView-eMiRw1EM.js} +1 -1
- package/dist/client/assets/{SecretsView-w_O8NLfw.js → SecretsView-6qK8Idb7.js} +1 -1
- package/dist/client/assets/{SessionTerminal-CSPi-qWE.js → SessionTerminal-DtFLyUaK.js} +1 -1
- package/dist/client/assets/SettingsModal-BTfZlghk.css +1 -0
- package/dist/client/assets/{SettingsModal-wilrGXvE.js → SettingsModal-BkOSlfyr.js} +1 -1
- package/dist/client/assets/{SettingsModal-C9bVvYQq.js → SettingsModal-DSq-_qwo.js} +4 -4
- package/dist/client/assets/{SettingsTextareaRow-Bd7_SxVv.js → SettingsTextareaRow-Btyx5kaR.js} +1 -1
- package/dist/client/assets/{SetupWizardModal-sPs6Lw3a.js → SetupWizardModal-Blhx-dRt.js} +2 -2
- package/dist/client/assets/{SkillsView-B9PvYS8t.js → SkillsView-DGE5kYc3.js} +1 -1
- package/dist/client/assets/{TodoView-Df0SD_JT.js → TodoView-YSGI7ATd.js} +1 -1
- package/dist/client/assets/{WorkflowNodeEditor-B3kFD-S0.js → WorkflowNodeEditor-CZiEThu2.js} +1 -1
- package/dist/client/assets/{agent-import-generation-OXfU1-D1.js → agent-import-generation-DQeNZMAm.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-B_8zk4ag.js → architectureDiagram-3BPJPVTR-DhaJNKa4.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-CpDKBJkO.js → blockDiagram-GPEHLZMM-pKpg7gJU.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-D3wtfGzI.js → c4Diagram-AAUBKEIU-DVPXKJnr.js} +1 -1
- package/dist/client/assets/channel-xFBfK0sX.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-DS9sHIMv.js → chunk-2J33WTMH-CHt90T5z.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-B-xW6cfi.js → chunk-4BX2VUAB-DsWFYy0a.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-Ddxf-sPU.js → chunk-55IACEB6-D8XiXEo8.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-Bv_Z1w57.js → chunk-727SXJPM-DlUYBn_Y.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DYHYN43E.js → chunk-AQP2D5EJ-BSdPOr9q.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-wuuFvvBk.js → chunk-FMBD7UC4-B9IAeBld.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-DfVRkVZo.js → chunk-ND2GUHAM-CmxnYQrR.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-D0t6ryuh.js → chunk-QZHKN3VN-Dxp6Po_j.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BFBVD968.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BFBVD968.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-DVlKqNFJ.js → cose-bilkent-S5V4N54A-BNl3bhpS.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-DTtiGxz-.js → dagre-BM42HDAG-DFs7WCcz.js} +1 -1
- package/dist/client/assets/{dashboard-view-DH99T6VJ.js → dashboard-view-DnoTcnIo.js} +1 -1
- package/dist/client/assets/{dashboard-view-D-nDgSmP.js → dashboard-view-ZGxLEL9r.js} +1 -1
- package/dist/client/assets/{dashboard-view-CLjYqvE9.js → dashboard-view-vc8kgLMs.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-Tv-YxCWk.js → diagram-2AECGRRQ-C2PVozvu.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-DlAAPkGu.js → diagram-5GNKFQAL-DFJ5b9H8.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DisatTBB.js → diagram-KO2AKTUF-D335CW-c.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-CDrUxjzW.js → diagram-LMA3HP47-DSfhroAW.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-BQ34a4Ej.js → diagram-OG6HWLK6-8iGveM-Z.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-s-YHwAEU.js → erDiagram-TEJ5UH35-Xc56MEEi.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-EpXzmG2d.js → flowDiagram-I6XJVG4X-DjCU9WbD.js} +1 -1
- package/dist/client/assets/{folder-open-DN86xUly.js → folder-open-BRrB44UX.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-Dk2WaoX8.js → ganttDiagram-6RSMTGT7-CXQE1bea.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CjchnKW0.js → gitGraphDiagram-PVQCEYII--mrSABLC.js} +1 -1
- package/dist/client/assets/{index-aGZ3DHQw.js → index-C2xsZoNG.js} +8 -8
- package/dist/client/assets/{infoDiagram-5YYISTIA-CiU-twoj.js → infoDiagram-5YYISTIA-CTS_nljP.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BvylyUKK.js → ishikawaDiagram-YF4QCWOH-CpV5x3gk.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-Ca6z84KB.js → journeyDiagram-JHISSGLW-BK2QCMnt.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-DXbw2-s-.js → kanban-definition-UN3LZRKU-B4srpspY.js} +1 -1
- package/dist/client/assets/{mermaid.core-DkS33D12.js → mermaid.core-B8DVAt21.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-BgXuWPDD.js → mindmap-definition-RKZ34NQL-CsmmiPpA.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-CnOzTN6U.js → pieDiagram-4H26LBE5-BahLDqf4.js} +1 -1
- package/dist/client/assets/{puzzle-Dxl1kcLF.js → puzzle-BFPQsuIa.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DI3s1u95.js → quadrantDiagram-W4KKPZXB-Bo3u0wTU.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-Ck97mo-W.js → requirementDiagram-4Y6WPE33-DYkYsz7d.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Z3w8NJac.js → sankeyDiagram-5OEKKPKP-BCbh0BSY.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DLGtWgfy.js → sequenceDiagram-3UESZ5HK-C2AP9GlE.js} +1 -1
- package/dist/client/assets/{shield-alert-BCGxgBpM.js → shield-alert-tlZOi02c.js} +1 -1
- package/dist/client/assets/{standing-instructions-template-CUE6ItG6.js → standing-instructions-template-C0Rzv9Mr.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-C6Zl2EJW.js → stateDiagram-AJRCARHV-CARkH0ug.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-BA77mDAW.js → stateDiagram-v2-BHNVJYJU-C2He043y.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-D9Cp15Hm.js → timeline-definition-PNZ67QCA-MvbYneEM.js} +1 -1
- package/dist/client/assets/{upload-2jCicylS.js → upload-DWK0F17Z.js} +1 -1
- package/dist/client/assets/{users-C1TTGAfX.js → users-1AkDfM14.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-BwFSh6Tr.js → vennDiagram-CIIHVFJN-DOjzzLqc.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-CQknAo24.js → wardley-L42UT6IY-Di70vhu2.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Dwc8f8bd.js → wardleyDiagram-YWT4CUSO-nK6ARhDv.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-CPkeHv8g.js → xychartDiagram-2RQKCTM6-C7_y76B7.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/client/version.json +1 -1
- package/dist/extension.js +399 -25
- package/package.json +1 -1
- package/dist/client/assets/SettingsModal-CMLHZBhX.css +0 -1
- package/dist/client/assets/channel-BLzjKIVz.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DkwPy8u9.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DkwPy8u9.js +0 -1
package/dist/bin.js
CHANGED
|
@@ -139330,6 +139330,12 @@ var init_usage_limit_detector = __esm({
|
|
|
139330
139330
|
});
|
|
139331
139331
|
|
|
139332
139332
|
// ../engine/src/transient-error-patterns.ts
|
|
139333
|
+
function isSessionContentionError(errorMessage4) {
|
|
139334
|
+
if (!errorMessage4 || typeof errorMessage4 !== "string") {
|
|
139335
|
+
return false;
|
|
139336
|
+
}
|
|
139337
|
+
return SESSION_CONTENTION_PATTERNS.some((pattern) => pattern.test(errorMessage4));
|
|
139338
|
+
}
|
|
139333
139339
|
function isTransientError(errorMessage4) {
|
|
139334
139340
|
if (!errorMessage4 || typeof errorMessage4 !== "string") {
|
|
139335
139341
|
return false;
|
|
@@ -139337,6 +139343,9 @@ function isTransientError(errorMessage4) {
|
|
|
139337
139343
|
if (isTransientAuthCredentialError(errorMessage4)) {
|
|
139338
139344
|
return true;
|
|
139339
139345
|
}
|
|
139346
|
+
if (isSessionContentionError(errorMessage4)) {
|
|
139347
|
+
return true;
|
|
139348
|
+
}
|
|
139340
139349
|
return TRANSIENT_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage4));
|
|
139341
139350
|
}
|
|
139342
139351
|
function isTransientAuthCredentialError(errorMessage4) {
|
|
@@ -139348,7 +139357,7 @@ function isTransientAuthCredentialError(errorMessage4) {
|
|
|
139348
139357
|
}
|
|
139349
139358
|
return TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN.test(errorMessage4);
|
|
139350
139359
|
}
|
|
139351
|
-
var TRANSIENT_ERROR_PATTERNS, TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN, OPERATOR_ACTIONABLE_AUTH_EXCLUSION_PATTERN;
|
|
139360
|
+
var TRANSIENT_ERROR_PATTERNS, SESSION_CONTENTION_PATTERNS, TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN, OPERATOR_ACTIONABLE_AUTH_EXCLUSION_PATTERN;
|
|
139352
139361
|
var init_transient_error_patterns = __esm({
|
|
139353
139362
|
"../engine/src/transient-error-patterns.ts"() {
|
|
139354
139363
|
"use strict";
|
|
@@ -139409,6 +139418,14 @@ var init_transient_error_patterns = __esm({
|
|
|
139409
139418
|
/\bACP failed to start\b/i,
|
|
139410
139419
|
/\bACP session has no live connection\b/i
|
|
139411
139420
|
];
|
|
139421
|
+
SESSION_CONTENTION_PATTERNS = [
|
|
139422
|
+
// ActiveSessionPathHeldByForeignTaskError (active-session-registry.ts)
|
|
139423
|
+
/active-session path\s+\S+\s+is held by task\s+\S+/i,
|
|
139424
|
+
// WorkspaceRepoAcquireBusyError (worktree-acquisition.ts)
|
|
139425
|
+
/workspace sub-repo\s+\S+\s+acquisition is in progress for task\s+\S+/i,
|
|
139426
|
+
// WorkspaceRepoLandBusyError (merger-ai.ts)
|
|
139427
|
+
/workspace sub-repo\s+\S+\s+land is in progress for task\s+\S+/i
|
|
139428
|
+
];
|
|
139412
139429
|
TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN = /"type":\s*"authentication_error"|invalid authentication credentials|token[_\s]?expired/i;
|
|
139413
139430
|
OPERATOR_ACTIONABLE_AUTH_EXCLUSION_PATTERN = /oauth token does not meet scope|insufficient[_\s-]?scope|invalid[_\s-]?scope|invalid (?:api[_\s-]?key|x-api-key)|missing\s+(?:\S+\s+)?(?:api[_\s-]?)?key|revoked|suspend(?:ed)?|disabled|deactivated|subscription|account (?:is )?(?:locked|closed|inactive)|access denied/i;
|
|
139414
139431
|
}
|
|
@@ -139417,6 +139434,9 @@ var init_transient_error_patterns = __esm({
|
|
|
139417
139434
|
// ../engine/src/transient-error-detector.ts
|
|
139418
139435
|
function isNonPlanDefectPlanReviewFailure(input) {
|
|
139419
139436
|
if (input.verdict === "REVISE") return false;
|
|
139437
|
+
if (input.failureValue?.trim().toLowerCase() === "session-contention-hold" || (input.errorMessage ? isSessionContentionError(input.errorMessage) : false)) {
|
|
139438
|
+
return false;
|
|
139439
|
+
}
|
|
139420
139440
|
const failureValue = input.failureValue?.trim().toLowerCase();
|
|
139421
139441
|
if (failureValue === "exception" || failureValue === "aborted") return true;
|
|
139422
139442
|
const errorMessage4 = input.errorMessage?.trim();
|
|
@@ -163871,6 +163891,23 @@ var init_worktrunk_installer = __esm({
|
|
|
163871
163891
|
});
|
|
163872
163892
|
|
|
163873
163893
|
// ../engine/src/active-session-registry.ts
|
|
163894
|
+
function acquireActiveSessionPath(registry3, path14, registration, options = {}) {
|
|
163895
|
+
const existing = registry3.lookupByPath(path14);
|
|
163896
|
+
if (!existing || existing.taskId === registration.taskId) {
|
|
163897
|
+
registry3.registerPath(path14, registration);
|
|
163898
|
+
return { action: "registered" };
|
|
163899
|
+
}
|
|
163900
|
+
const now = options.now?.() ?? Date.now();
|
|
163901
|
+
const ageMs = now - existing.registeredAt;
|
|
163902
|
+
const minIdleMs = options.minIdleMs ?? DEFAULT_SELF_OWNED_MIN_IDLE_MS;
|
|
163903
|
+
const holderIsLive = options.holderLiveProbe?.(existing.taskId, path14) ?? true;
|
|
163904
|
+
if (holderIsLive || ageMs < minIdleMs) {
|
|
163905
|
+
return { action: "contended", holderTaskId: existing.taskId, holderKind: existing.kind, ageMs };
|
|
163906
|
+
}
|
|
163907
|
+
registry3.unregisterPath(path14);
|
|
163908
|
+
registry3.registerPath(path14, registration);
|
|
163909
|
+
return { action: "reclaimed-stale-foreign", holderTaskId: existing.taskId, ageMs };
|
|
163910
|
+
}
|
|
163874
163911
|
function reconcileSelfOwnedActiveSessionForRemoval(registry3, worktreePath, requestingTaskId, liveBindingProbe, options = {}) {
|
|
163875
163912
|
const record2 = registry3.lookupByPath(worktreePath);
|
|
163876
163913
|
if (!record2) {
|
|
@@ -174250,7 +174287,7 @@ function hasAdvancedPastPlanning(task) {
|
|
|
174250
174287
|
if (task.column === "in-progress" || task.column === "in-review" || task.column === "done" || task.column === "archived") {
|
|
174251
174288
|
return true;
|
|
174252
174289
|
}
|
|
174253
|
-
if (task.
|
|
174290
|
+
if (task.firstExecutionAt != null || task.executionStartedAt != null) {
|
|
174254
174291
|
return true;
|
|
174255
174292
|
}
|
|
174256
174293
|
if (task.column === "triage") {
|
|
@@ -203014,7 +203051,7 @@ function isNoTaskDoneFailure2(task) {
|
|
|
203014
203051
|
function hasStepProgress2(task) {
|
|
203015
203052
|
return task.steps.some((step) => step.status !== "pending");
|
|
203016
203053
|
}
|
|
203017
|
-
var log28, worktreeMetadataReconcileLog, execAsync30, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER, MAX_NO_PROGRESS_RESUME_ATTEMPTS, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, LEAKED_WORKTREE_SLOT_GRACE_MS, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, RECONCILE_SCOPE_OVERRIDE_MERGE_ACTIVE_STATUS_SET, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, SelfHealingManager;
|
|
203054
|
+
var log28, worktreeMetadataReconcileLog, execAsync30, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER, MAX_NO_PROGRESS_RESUME_ATTEMPTS, PRE_EXECUTION_WORKTREE_MAX_IDLE_MS, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, LEAKED_WORKTREE_SLOT_GRACE_MS, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, RECONCILE_SCOPE_OVERRIDE_MERGE_ACTIVE_STATUS_SET, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, SelfHealingManager;
|
|
203018
203055
|
var init_self_healing = __esm({
|
|
203019
203056
|
"../engine/src/self-healing.ts"() {
|
|
203020
203057
|
"use strict";
|
|
@@ -203070,6 +203107,7 @@ var init_self_healing = __esm({
|
|
|
203070
203107
|
DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
203071
203108
|
PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER = 3;
|
|
203072
203109
|
MAX_NO_PROGRESS_RESUME_ATTEMPTS = 2;
|
|
203110
|
+
PRE_EXECUTION_WORKTREE_MAX_IDLE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
203073
203111
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
203074
203112
|
STARVED_REFINEMENT_RECOVERY_GRACE_MS = 10 * 6e4;
|
|
203075
203113
|
STARVED_PEER_PROGRESS_THRESHOLD = 3;
|
|
@@ -204710,6 +204748,8 @@ var init_self_healing = __esm({
|
|
|
204710
204748
|
{ name: "reconcile-workspace-partial-lands", fn: () => this.reconcileWorkspacePartialLands() },
|
|
204711
204749
|
{ name: "reclaim-phantom-workspace-land-leases", fn: () => this.reclaimPhantomWorkspaceLandLeases() },
|
|
204712
204750
|
{ name: "reconcile-orphaned-workspace-worktrees", fn: () => this.reconcileOrphanedWorkspaceWorktrees() },
|
|
204751
|
+
// FNXC:PlanningEvacuation 2026-07-25-23:00: reclaim worktrees acquired at planning time by cards that never executed.
|
|
204752
|
+
{ name: "reconcile-pre-execution-worktrees", fn: () => this.reconcilePreExecutionWorktrees() },
|
|
204713
204753
|
{ name: "recover-merged-review", fn: () => this.recoverMergedReviewTasks() },
|
|
204714
204754
|
{ name: "recover-already-merged-review", fn: () => this.recoverAlreadyMergedReviewTasks() },
|
|
204715
204755
|
{ name: "recover-post-done-noncontinuable-wedge", fn: () => this.recoverPostDoneNonContinuableWedge() },
|
|
@@ -209736,6 +209776,58 @@ var init_self_healing = __esm({
|
|
|
209736
209776
|
return 0;
|
|
209737
209777
|
}
|
|
209738
209778
|
}
|
|
209779
|
+
/*
|
|
209780
|
+
FNXC:PlanningEvacuation 2026-07-25-23:00 (pre-execution worktree sweep):
|
|
209781
|
+
Planning acquires the task's own worktree, so cards that never reach execution would accumulate
|
|
209782
|
+
worktrees on disk: withdrawn to an intake column, archived from a planner lane, or simply parked.
|
|
209783
|
+
This sweep reclaims them.
|
|
209784
|
+
|
|
209785
|
+
Candidates are addressed from task ROWS (never a directory walk — AGENTS.md forbids unbounded temp
|
|
209786
|
+
scans): tasks holding `worktree` that sit in a non-executing column and carry no execution
|
|
209787
|
+
timestamp. `todo` is deliberately EXCLUDED: a planned card waiting for a WIP slot legitimately
|
|
209788
|
+
keeps its worktree, and churning it would just force a re-acquire minutes later. Every real safety
|
|
209789
|
+
decision (live session, clean branch, execution evidence) belongs to
|
|
209790
|
+
`TaskExecutor.releasePreExecutionWorktree`, so this sweep cannot diverge from the event-driven path
|
|
209791
|
+
that runs on the move itself.
|
|
209792
|
+
*/
|
|
209793
|
+
async reconcilePreExecutionWorktrees() {
|
|
209794
|
+
try {
|
|
209795
|
+
const release2 = this.options.releasePreExecutionWorktree;
|
|
209796
|
+
if (!release2) return 0;
|
|
209797
|
+
const settings = await this.store.getSettings();
|
|
209798
|
+
if (settings.globalPause || settings.enginePaused) return 0;
|
|
209799
|
+
const now = Date.now();
|
|
209800
|
+
const parked = await this.store.listTasks({ slim: true });
|
|
209801
|
+
const candidates = parked.filter((task) => {
|
|
209802
|
+
if (!task.worktree || task.deletedAt) return false;
|
|
209803
|
+
if (task.firstExecutionAt || task.executionStartedAt) return false;
|
|
209804
|
+
if (task.column === "todo" || task.column === "in-progress" || task.column === "in-review" || task.column === "done") return false;
|
|
209805
|
+
if (task.paused || task.userPaused) return false;
|
|
209806
|
+
if (task.status != null) return false;
|
|
209807
|
+
if (task.blockedBy || task.overlapBlockedBy || task.nextRecoveryAt) return false;
|
|
209808
|
+
const lastTouchedMs = Math.max(
|
|
209809
|
+
Date.parse(task.columnMovedAt ?? "") || 0,
|
|
209810
|
+
Date.parse(task.updatedAt ?? "") || 0
|
|
209811
|
+
);
|
|
209812
|
+
if (!lastTouchedMs) return false;
|
|
209813
|
+
return now - lastTouchedMs >= PRE_EXECUTION_WORKTREE_MAX_IDLE_MS;
|
|
209814
|
+
});
|
|
209815
|
+
if (candidates.length === 0) return 0;
|
|
209816
|
+
let released = 0;
|
|
209817
|
+
for (const task of candidates) {
|
|
209818
|
+
try {
|
|
209819
|
+
if (await release2(task.id, `parked pre-execution in '${task.column}'`)) released++;
|
|
209820
|
+
} catch (err) {
|
|
209821
|
+
log28.warn(`reconcilePreExecutionWorktrees: release failed for ${task.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
209822
|
+
}
|
|
209823
|
+
}
|
|
209824
|
+
if (released > 0) log28.log(`reconcilePreExecutionWorktrees: released ${released} pre-execution worktree(s)`);
|
|
209825
|
+
return released;
|
|
209826
|
+
} catch (err) {
|
|
209827
|
+
log28.error(`reconcilePreExecutionWorktrees sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
209828
|
+
return 0;
|
|
209829
|
+
}
|
|
209830
|
+
}
|
|
209739
209831
|
/*
|
|
209740
209832
|
FNXC:Workspace 2026-06-22-09:30 (Phase D U1, KTD4 — per-repo worktree cleanup from STORED paths):
|
|
209741
209833
|
For done/dead workspace tasks, remove each recorded per-repo worktree. The paths are ADDRESSABLE
|
|
@@ -213734,6 +213826,33 @@ var init_triage = __esm({
|
|
|
213734
213826
|
if (this.processing.has(task.id) || this.hasLivePlanningWork(task.id)) return;
|
|
213735
213827
|
this.requestImmediatePoll();
|
|
213736
213828
|
};
|
|
213829
|
+
this.taskEvacuatedFromPlanningHandler = (task) => {
|
|
213830
|
+
if (!task?.id) return;
|
|
213831
|
+
if (typeof task.column !== "string") return;
|
|
213832
|
+
if (task.column === "todo" || task.column === "triage" || task.column === "in-progress") return;
|
|
213833
|
+
if (this.activeSubagentSessions.has(task.id)) {
|
|
213834
|
+
this.disposeSubagentsForTask(task.id, `task moved to ${task.column}`);
|
|
213835
|
+
}
|
|
213836
|
+
const session = this.activeSessions.get(task.id);
|
|
213837
|
+
if (!session) return;
|
|
213838
|
+
planLog.log(`task moved out of planning to '${task.column}' \u2014 terminating triage session for ${task.id}`);
|
|
213839
|
+
this.pauseAborted.add(task.id);
|
|
213840
|
+
this.options.stuckTaskDetector?.untrackTask(task.id);
|
|
213841
|
+
const sessionWithAbort = session;
|
|
213842
|
+
if (typeof sessionWithAbort.abort === "function") {
|
|
213843
|
+
void sessionWithAbort.abort().catch((err) => {
|
|
213844
|
+
planLog.warn(`Failed to abort triage session for ${task.id}: ${err}`);
|
|
213845
|
+
});
|
|
213846
|
+
}
|
|
213847
|
+
this.recordTriageSessionTokenUsageSoon(task.id, session, { agentId: task.assignedAgentId ?? "triage" });
|
|
213848
|
+
session.dispose();
|
|
213849
|
+
this.activeSessions.delete(task.id);
|
|
213850
|
+
if (task.status === "planning") {
|
|
213851
|
+
void Promise.resolve(this.store.updateTask(task.id, { status: null })).catch((err) => {
|
|
213852
|
+
planLog.warn(`${task.id}: failed to clear planning status after evacuation: ${err instanceof Error ? err.message : String(err)}`);
|
|
213853
|
+
});
|
|
213854
|
+
}
|
|
213855
|
+
};
|
|
213737
213856
|
}
|
|
213738
213857
|
running = false;
|
|
213739
213858
|
polling = false;
|
|
@@ -213792,6 +213911,8 @@ var init_triage = __esm({
|
|
|
213792
213911
|
taskPausedHandler;
|
|
213793
213912
|
/** FNXC:CodingIdeasWorkflow 2026-07-25-11:20: store-event wake for planning-eligible columns. */
|
|
213794
213913
|
taskColumnWakeHandler;
|
|
213914
|
+
/** FNXC:PlanningEvacuation 2026-07-25-23:00: stops planning when a card leaves the planner lanes. */
|
|
213915
|
+
taskEvacuatedFromPlanningHandler;
|
|
213795
213916
|
_approvalRequestStore;
|
|
213796
213917
|
/**
|
|
213797
213918
|
* @param store — Task store instance (also used to listen for `settings:updated` events)
|
|
@@ -213900,6 +214021,9 @@ var init_triage = __esm({
|
|
|
213900
214021
|
this.store.on("task:updated", this.taskColumnWakeHandler);
|
|
213901
214022
|
this.store.on("task:created", this.taskColumnWakeHandler);
|
|
213902
214023
|
}
|
|
214024
|
+
if (this.taskEvacuatedFromPlanningHandler && typeof this.store.on === "function") {
|
|
214025
|
+
this.store.on("task:updated", this.taskEvacuatedFromPlanningHandler);
|
|
214026
|
+
}
|
|
213903
214027
|
this.clearStaleSpecifyingStatuses().catch((err) => {
|
|
213904
214028
|
planLog.error("Failed to clear stale planning statuses:", err);
|
|
213905
214029
|
});
|
|
@@ -213947,6 +214071,9 @@ var init_triage = __esm({
|
|
|
213947
214071
|
this.store.off("task:updated", this.taskColumnWakeHandler);
|
|
213948
214072
|
this.store.off("task:created", this.taskColumnWakeHandler);
|
|
213949
214073
|
}
|
|
214074
|
+
if (this.taskEvacuatedFromPlanningHandler && typeof this.store.off === "function") {
|
|
214075
|
+
this.store.off("task:updated", this.taskEvacuatedFromPlanningHandler);
|
|
214076
|
+
}
|
|
213950
214077
|
this.abortAndDisposeActiveSessions("engine stop");
|
|
213951
214078
|
planLog.log("Processor stopped");
|
|
213952
214079
|
}
|
|
@@ -214738,11 +214865,15 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
214738
214865
|
planningModel.modelId,
|
|
214739
214866
|
assignedAgent?.runtimeConfig
|
|
214740
214867
|
) : { provider: void 0, modelId: void 0 };
|
|
214868
|
+
const planningCwd = await this.options.acquirePlanningWorktree?.(task.id).catch(() => null) || this.rootDir;
|
|
214869
|
+
if (planningCwd !== this.rootDir) {
|
|
214870
|
+
await this.store.logEntry(task.id, `Planning session running in task worktree ${planningCwd}`).catch(() => void 0);
|
|
214871
|
+
}
|
|
214741
214872
|
const { session } = await createResolvedAgentSession({
|
|
214742
214873
|
sessionPurpose: "triage",
|
|
214743
214874
|
runtimeHint: triageRuntimeHint,
|
|
214744
214875
|
pluginRunner: this.options.pluginRunner,
|
|
214745
|
-
cwd:
|
|
214876
|
+
cwd: planningCwd,
|
|
214746
214877
|
systemPrompt: triageSystemPromptFinal,
|
|
214747
214878
|
systemPromptLayers: triageLayers,
|
|
214748
214879
|
tools: "coding",
|
|
@@ -217203,12 +217334,13 @@ function recoverPassedPlanReviewFromLatestLog(task) {
|
|
|
217203
217334
|
completedAt: latest.timestamp
|
|
217204
217335
|
};
|
|
217205
217336
|
}
|
|
217206
|
-
var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE, PLAN_REVIEW_LEASE_HELD_VALUE, WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY, WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY, WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
|
|
217337
|
+
var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE, PLAN_REVIEW_LEASE_HELD_VALUE, SESSION_CONTENTION_HOLD_VALUE, WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY, WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY, WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
|
|
217207
217338
|
var init_workflow_graph_executor = __esm({
|
|
217208
217339
|
"../engine/src/workflow-graph-executor.ts"() {
|
|
217209
217340
|
"use strict";
|
|
217210
217341
|
init_src2();
|
|
217211
217342
|
init_transient_error_detector();
|
|
217343
|
+
init_transient_error_patterns();
|
|
217212
217344
|
init_required_workflow_artifacts();
|
|
217213
217345
|
init_workflow_node_handlers();
|
|
217214
217346
|
init_workflow_graph_branches();
|
|
@@ -217217,6 +217349,7 @@ var init_workflow_graph_executor = __esm({
|
|
|
217217
217349
|
init_workflow_node_execution_needs();
|
|
217218
217350
|
PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE = "plan-review-provider-failure-hold";
|
|
217219
217351
|
PLAN_REVIEW_LEASE_HELD_VALUE = "plan-review-lease-held";
|
|
217352
|
+
SESSION_CONTENTION_HOLD_VALUE = "session-contention-hold";
|
|
217220
217353
|
WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY = "workflow:interruptedNodeId";
|
|
217221
217354
|
WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY = "workflow:interruptedNodeAbortKind";
|
|
217222
217355
|
WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY = "workflow:optionalGroupActive";
|
|
@@ -217536,6 +217669,15 @@ var init_workflow_graph_executor = __esm({
|
|
|
217536
217669
|
};
|
|
217537
217670
|
context[`node:${node.id}:outcome`] = result3.outcome;
|
|
217538
217671
|
if (result3.value !== void 0) context[`node:${node.id}:value`] = result3.value;
|
|
217672
|
+
const sessionContentionFailure = stepStatus === "failed" && (verdictRaw === SESSION_CONTENTION_HOLD_VALUE || isSessionContentionError(stepOutput ?? stepNotes ?? ""));
|
|
217673
|
+
if (sessionContentionFailure) {
|
|
217674
|
+
this.deps.logTaskEntry?.(
|
|
217675
|
+
`${logPrefix} ${groupName} is waiting on another task to release its session path \u2014 holding in place`
|
|
217676
|
+
);
|
|
217677
|
+
context[`node:${node.id}:outcome`] = "failure";
|
|
217678
|
+
context[`node:${node.id}:value`] = SESSION_CONTENTION_HOLD_VALUE;
|
|
217679
|
+
return { outcome: "failure", value: SESSION_CONTENTION_HOLD_VALUE };
|
|
217680
|
+
}
|
|
217539
217681
|
const nonPlanDefectPlanReviewFailure = node.id === PLAN_REVIEW_GROUP_ID && stepStatus === "failed" && (isRequiredArtifactReadFailedValue(verdictRaw) || isNonPlanDefectPlanReviewFailure({
|
|
217540
217682
|
verdict,
|
|
217541
217683
|
errorMessage: stepOutput ?? stepNotes,
|
|
@@ -217919,6 +218061,7 @@ var init_workflow_graph_executor = __esm({
|
|
|
217919
218061
|
} catch (error3) {
|
|
217920
218062
|
if (signal?.aborted) return this.withEnginePauseAbortContext(node, { outcome: "failure", value: "aborted" });
|
|
217921
218063
|
lastError = error3;
|
|
218064
|
+
if (isSessionContentionError(error3 instanceof Error ? error3.message : String(error3))) break;
|
|
217922
218065
|
}
|
|
217923
218066
|
}
|
|
217924
218067
|
if (signal?.aborted) {
|
|
@@ -217937,11 +218080,13 @@ var init_workflow_graph_executor = __esm({
|
|
|
217937
218080
|
}
|
|
217938
218081
|
return degraded;
|
|
217939
218082
|
}
|
|
218083
|
+
const lastErrorText = lastError instanceof Error ? lastError.message : String(lastError);
|
|
217940
218084
|
const failureResult = {
|
|
217941
218085
|
outcome: "failure",
|
|
217942
|
-
|
|
218086
|
+
// FNXC:SessionContention 2026-07-25-21:30: contention is a retryable hold, not an exception.
|
|
218087
|
+
value: isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
|
|
217943
218088
|
contextPatch: {
|
|
217944
|
-
[`node:${node.id}:error`]:
|
|
218089
|
+
[`node:${node.id}:error`]: lastErrorText
|
|
217945
218090
|
}
|
|
217946
218091
|
};
|
|
217947
218092
|
if (recordProgress && this.shouldRecordNodeProgress(node)) {
|
|
@@ -223269,7 +223414,7 @@ function preservePreExecutionWorkflowStepResults(task) {
|
|
|
223269
223414
|
}
|
|
223270
223415
|
];
|
|
223271
223416
|
}
|
|
223272
|
-
var execAsync35, execFileAsync19, WORKFLOW_THINKING_LEVEL_SET, yieldEventLoop3, tokenCacheMetricsLog, OPTIONAL_STEP_REVISION_KEY_MARKER2, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, MAX_TRANSIENT_GRAPH_RESUME_RETRIES, TRANSIENT_GRAPH_RESUME_RETRY_BACKOFF_MS, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, LOOP_COMPACTION_TIMEOUT_MS, TASK_DONE_REFUSAL_SUFFIX, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, USER_QUESTION_TOOL_NAMES, FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
223417
|
+
var execAsync35, execFileAsync19, WORKFLOW_THINKING_LEVEL_SET, yieldEventLoop3, tokenCacheMetricsLog, OPTIONAL_STEP_REVISION_KEY_MARKER2, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, MAX_TRANSIENT_GRAPH_RESUME_RETRIES, TRANSIENT_GRAPH_RESUME_RETRY_BACKOFF_MS, MAX_SESSION_CONTENTION_HOLD_RETRIES, SESSION_CONTENTION_HOLD_BACKOFF_MS, SESSION_CONTENTION_HOLD_MAX_BACKOFF_MS, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, LOOP_COMPACTION_TIMEOUT_MS, TASK_DONE_REFUSAL_SUFFIX, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, USER_QUESTION_TOOL_NAMES, FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
223273
223418
|
var init_executor = __esm({
|
|
223274
223419
|
"../engine/src/executor.ts"() {
|
|
223275
223420
|
"use strict";
|
|
@@ -223382,6 +223527,9 @@ var init_executor = __esm({
|
|
|
223382
223527
|
MAX_TASK_DONE_REQUEUE_RETRIES = 3;
|
|
223383
223528
|
MAX_TRANSIENT_GRAPH_RESUME_RETRIES = 2;
|
|
223384
223529
|
TRANSIENT_GRAPH_RESUME_RETRY_BACKOFF_MS = process.env.VITEST || process.env.NODE_ENV === "test" ? 0 : 1e3;
|
|
223530
|
+
MAX_SESSION_CONTENTION_HOLD_RETRIES = 10;
|
|
223531
|
+
SESSION_CONTENTION_HOLD_BACKOFF_MS = process.env.VITEST || process.env.NODE_ENV === "test" ? 0 : 5e3;
|
|
223532
|
+
SESSION_CONTENTION_HOLD_MAX_BACKOFF_MS = 6e4;
|
|
223385
223533
|
COMPLETED_TASK_WATCHDOG_MS = 6e4;
|
|
223386
223534
|
WORKFLOW_RERUN_WATCHDOG_MS = 15e3;
|
|
223387
223535
|
LOOP_COMPACTION_TIMEOUT_MS = 6e4;
|
|
@@ -223742,6 +223890,15 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
223742
223890
|
}
|
|
223743
223891
|
})
|
|
223744
223892
|
);
|
|
223893
|
+
} else if ((from === "todo" || from === "triage") && to2 !== "in-progress" && to2 !== "in-review" && to2 !== "done") {
|
|
223894
|
+
this.trackTaskDisposal(
|
|
223895
|
+
task.id,
|
|
223896
|
+
this.awaitAbortInFlightTaskWork(task.id, `task moved out of planning to ${to2}`, {
|
|
223897
|
+
userCanceled: source === "user"
|
|
223898
|
+
}).then(async () => {
|
|
223899
|
+
await this.releasePreExecutionWorktree(task.id, `moved to ${to2}`);
|
|
223900
|
+
})
|
|
223901
|
+
);
|
|
223745
223902
|
} else if (from === "in-progress") {
|
|
223746
223903
|
if (this.workflowLifecycleMovesInFlight.has(task.id) && this.graphRouting.has(task.id)) {
|
|
223747
223904
|
executorLog.log(
|
|
@@ -224331,15 +224488,63 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
224331
224488
|
getActiveWorktreePaths() consumers that cd into a path are unaffected; only the registry key changes.
|
|
224332
224489
|
Non-workspace tasks (unique worktree path != rootDir) are returned unchanged.
|
|
224333
224490
|
*/
|
|
224491
|
+
/*
|
|
224492
|
+
FNXC:PlanReviewWorktree 2026-07-25-20:40 (concurrent root-rooted step sessions — single-repo collision):
|
|
224493
|
+
The task-scoped key must apply to the shared repo root in EVERY project mode, not only workspace mode.
|
|
224494
|
+
Read-only graph nodes that need no worktree (Plan Review is the canonical one — it reviews the
|
|
224495
|
+
store-injected PROMPT.md, see FNXC:PlanReviewSpecInjection) run rooted at `this.rootDir`, and a todo
|
|
224496
|
+
task has no worktree of its own. With the bare root as the registry key, two tasks reaching Plan Review
|
|
224497
|
+
at the same time collided: the second failed with "active-session path <root> is held by task <other>;
|
|
224498
|
+
task <self> may not overwrite it", which surfaced as a Plan Review provider failure, burned the
|
|
224499
|
+
in-place retry budget against a hold that retrying can never clear, and left the task parked
|
|
224500
|
+
(reported: FN-1398 holding /home/ubuntu/dev/freemap-svelte while FN-1403 planned).
|
|
224501
|
+
Path-exclusivity on the shared root is not what keeps these sessions correct: write-capable nodes are
|
|
224502
|
+
refused at the root outright (no-worktree-for-write-node above), real per-sub-repo exclusivity is the
|
|
224503
|
+
workspace-repo-acquire lease, and every isPathActive consumer guards removable WORKTREE paths — the
|
|
224504
|
+
root is never one. Liveness still works because the synthetic key stays in the registry under the task.
|
|
224505
|
+
*/
|
|
224334
224506
|
sessionRegistryPath(taskId, worktreePath) {
|
|
224335
|
-
if (
|
|
224507
|
+
if (worktreePath === this.rootDir) {
|
|
224336
224508
|
return `${worktreePath}#session:${taskId}`;
|
|
224337
224509
|
}
|
|
224338
224510
|
return worktreePath;
|
|
224339
224511
|
}
|
|
224512
|
+
/*
|
|
224513
|
+
FNXC:SessionContention 2026-07-25-21:30 (contention prevention at the registration seam):
|
|
224514
|
+
Every executor session registration goes through `acquireActiveSessionPath` instead of the raw
|
|
224515
|
+
`registerPath`, so a LEAKED entry owned by a task with no live session surface in this process is
|
|
224516
|
+
RECLAIMED rather than throwing at the newcomer. That closes the second contention class (a dead
|
|
224517
|
+
holder can never release, so waiting on it is waiting forever). A genuinely live holder still throws
|
|
224518
|
+
the typed error — that case is real serialization, and callers classify it as a retryable contention
|
|
224519
|
+
hold (SESSION_CONTENTION_HOLD_VALUE), never as a provider/model failure.
|
|
224520
|
+
The probe reports LIVE on any uncertainty: an unknown holder with a fresh entry is treated as live by
|
|
224521
|
+
the staleness floor, so the reclaim only ever fires on proven-dead, aged entries.
|
|
224522
|
+
*/
|
|
224523
|
+
acquireSessionRegistryPath(taskId, registryPath, kind, ownerKey) {
|
|
224524
|
+
const outcome = acquireActiveSessionPath(activeSessionRegistry, registryPath, { taskId, kind, ownerKey }, {
|
|
224525
|
+
holderLiveProbe: (holderTaskId) => this.hasLiveTaskSessionSurface(holderTaskId) || executingTaskLock.has(holderTaskId)
|
|
224526
|
+
});
|
|
224527
|
+
if (outcome.action === "contended") {
|
|
224528
|
+
throw new ActiveSessionPathHeldByForeignTaskError(registryPath, outcome.holderTaskId, taskId);
|
|
224529
|
+
}
|
|
224530
|
+
if (outcome.action === "reclaimed-stale-foreign") {
|
|
224531
|
+
executorLog.warn(
|
|
224532
|
+
`${taskId}: reclaimed a stale active-session entry on ${registryPath} from dead task ${outcome.holderTaskId} (idle ${outcome.ageMs}ms)`
|
|
224533
|
+
);
|
|
224534
|
+
void this.store.recordRunAuditEvent?.({
|
|
224535
|
+
taskId,
|
|
224536
|
+
agentId: "executor",
|
|
224537
|
+
runId: generateSyntheticRunId("session-path-reclaim", taskId),
|
|
224538
|
+
domain: "database",
|
|
224539
|
+
mutationType: "session:reclaim-stale-foreign-path",
|
|
224540
|
+
target: taskId,
|
|
224541
|
+
metadata: { taskId, holderTaskId: outcome.holderTaskId, kind, ageMs: outcome.ageMs }
|
|
224542
|
+
})?.catch?.(() => void 0);
|
|
224543
|
+
}
|
|
224544
|
+
}
|
|
224340
224545
|
setActiveSession(taskId, sessionState, worktreePath) {
|
|
224341
224546
|
this.activeSessions.set(taskId, sessionState);
|
|
224342
|
-
|
|
224547
|
+
this.acquireSessionRegistryPath(taskId, this.sessionRegistryPath(taskId, worktreePath), "executor", taskId);
|
|
224343
224548
|
}
|
|
224344
224549
|
markGraphExecuteSelfRequeued(taskId) {
|
|
224345
224550
|
if (this.graphRouting.has(taskId)) {
|
|
@@ -224357,7 +224562,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
224357
224562
|
setActiveStepExecutor(taskId, stepExecutor, worktreePath, seenSteeringIds = /* @__PURE__ */ new Set()) {
|
|
224358
224563
|
this.activeStepExecutors.set(taskId, stepExecutor);
|
|
224359
224564
|
this.activeStepExecutorSeenSteeringIds.set(taskId, seenSteeringIds);
|
|
224360
|
-
|
|
224565
|
+
this.acquireSessionRegistryPath(taskId, this.sessionRegistryPath(taskId, worktreePath), "step-session", `${taskId}#step-session`);
|
|
224361
224566
|
}
|
|
224362
224567
|
deleteActiveStepExecutor(taskId, worktreePath) {
|
|
224363
224568
|
this.activeStepExecutors.delete(taskId);
|
|
@@ -224371,7 +224576,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
224371
224576
|
setActiveWorkflowStepSession(taskId, session, worktreePath, seenSteeringIds = /* @__PURE__ */ new Set()) {
|
|
224372
224577
|
this.activeWorkflowStepSessions.set(taskId, session);
|
|
224373
224578
|
this.activeWorkflowStepSessionSeenSteeringIds.set(taskId, seenSteeringIds);
|
|
224374
|
-
|
|
224579
|
+
this.acquireSessionRegistryPath(taskId, this.sessionRegistryPath(taskId, worktreePath), "workflow-step", `${taskId}#workflow-step`);
|
|
224375
224580
|
}
|
|
224376
224581
|
deleteActiveWorkflowStepSession(taskId, worktreePath) {
|
|
224377
224582
|
this.activeWorkflowStepSessions.delete(taskId);
|
|
@@ -228690,6 +228895,87 @@ ${info.feedback}`, revisionKey),
|
|
|
228690
228895
|
this.unregisterConfiguredCommandController(task.id, commandAbortController);
|
|
228691
228896
|
}
|
|
228692
228897
|
}
|
|
228898
|
+
/*
|
|
228899
|
+
FNXC:NodeWorktreeIsolation 2026-07-25-22:10 (planning acquires the task worktree):
|
|
228900
|
+
Public seam for the planning/triage lane. Specification runs a CODING-tool session; pointing it at
|
|
228901
|
+
the shared main checkout meant every planning agent had write tools in the operator's tree and every
|
|
228902
|
+
concurrent planner shared one path. Acquire the task's own worktree up front and let the whole
|
|
228903
|
+
lifecycle — planning, Plan Review, implementation, code review — reuse that single worktree.
|
|
228904
|
+
Returns null (caller falls back to the root, unchanged behavior) when the project is a workspace, or
|
|
228905
|
+
when acquisition fails: planning must never be blocked by a worktree problem.
|
|
228906
|
+
*/
|
|
228907
|
+
/*
|
|
228908
|
+
FNXC:PlanningEvacuation 2026-07-25-23:00 (pre-execution worktree release):
|
|
228909
|
+
Planning now acquires a worktree, so a card that never reaches execution — withdrawn to Ideas,
|
|
228910
|
+
archived from a planner lane, or parked pre-execution — would otherwise hold one forever. Release
|
|
228911
|
+
it. Safety conditions, all required:
|
|
228912
|
+
- the task never executed (`firstExecutionAt`/`executionStartedAt` unset): execution evidence means
|
|
228913
|
+
the worktree may hold real work, and only the normal merge/archive lifecycle may remove it;
|
|
228914
|
+
- no live session registered on the path (the same isPathActive guard the other sweeps use);
|
|
228915
|
+
- the branch carries no commits beyond its base — planning writes its spec to the task store, not
|
|
228916
|
+
the worktree, so a clean branch means there is genuinely nothing to lose.
|
|
228917
|
+
Metadata (`worktree`/`branch`) is cleared with it, so a later promotion re-acquires cleanly.
|
|
228918
|
+
Fail-soft throughout: a cleanup problem must never block the lifecycle move that triggered it.
|
|
228919
|
+
*/
|
|
228920
|
+
async releasePreExecutionWorktree(taskId, reason) {
|
|
228921
|
+
try {
|
|
228922
|
+
const live = await this.store.getTask(taskId);
|
|
228923
|
+
if (!live?.worktree) return false;
|
|
228924
|
+
if (live.firstExecutionAt || live.executionStartedAt) return false;
|
|
228925
|
+
if (activeSessionRegistry.isPathActive(live.worktree) || activeSessionRegistry.isPathActive(resolvePath3(live.worktree))) return false;
|
|
228926
|
+
if (this.hasLiveTaskSessionSurface(taskId) || executingTaskLock.has(taskId)) return false;
|
|
228927
|
+
if (existsSync65(live.worktree)) {
|
|
228928
|
+
if (await this.preExecutionWorktreeHasWork(live.worktree)) {
|
|
228929
|
+
executorLog.log(`${taskId}: keeping pre-execution worktree ${live.worktree} \u2014 it carries commits or uncommitted changes`);
|
|
228930
|
+
return false;
|
|
228931
|
+
}
|
|
228932
|
+
const settings = await this.store.getSettings();
|
|
228933
|
+
await removeWorktree({
|
|
228934
|
+
rootDir: this.rootDir,
|
|
228935
|
+
worktreePath: live.worktree,
|
|
228936
|
+
settings,
|
|
228937
|
+
taskId,
|
|
228938
|
+
reason: RemovalReason.SelfHealingReclaim
|
|
228939
|
+
});
|
|
228940
|
+
}
|
|
228941
|
+
this.activeWorktrees.get(taskId)?.delete(live.worktree);
|
|
228942
|
+
await this.store.updateTask(taskId, { worktree: null, branch: null, baseCommitSha: null, sessionFile: null }, this.getRunContextFor(taskId));
|
|
228943
|
+
await this.store.logEntry(taskId, `Released the pre-execution worktree (${reason}) \u2014 it will be re-acquired when planning or execution resumes`, void 0, this.getRunContextFor(taskId)).catch(() => void 0);
|
|
228944
|
+
executorLog.log(`${taskId}: released pre-execution worktree ${live.worktree} (${reason})`);
|
|
228945
|
+
return true;
|
|
228946
|
+
} catch (error3) {
|
|
228947
|
+
executorLog.warn(`${taskId}: could not release the pre-execution worktree: ${formatError(error3).message}`);
|
|
228948
|
+
return false;
|
|
228949
|
+
}
|
|
228950
|
+
}
|
|
228951
|
+
/** True when a pre-execution worktree holds commits past its base or any uncommitted change. */
|
|
228952
|
+
async preExecutionWorktreeHasWork(worktreePath) {
|
|
228953
|
+
try {
|
|
228954
|
+
const { stdout: dirty } = await execFileAsync19("git", ["status", "--porcelain"], { cwd: worktreePath, timeout: 3e4 });
|
|
228955
|
+
if (dirty.trim()) return true;
|
|
228956
|
+
const { stdout: ahead } = await execFileAsync19("git", ["log", "--oneline", "@{upstream}..HEAD"], { cwd: worktreePath, timeout: 3e4 }).catch(async () => await execFileAsync19("git", ["log", "--oneline", "-1", "HEAD", "--not", "--remotes", "--branches=main", "--branches=master"], { cwd: worktreePath, timeout: 3e4 }));
|
|
228957
|
+
return Boolean(ahead.trim());
|
|
228958
|
+
} catch {
|
|
228959
|
+
return true;
|
|
228960
|
+
}
|
|
228961
|
+
}
|
|
228962
|
+
async ensureTaskWorktreeForPlanning(taskId) {
|
|
228963
|
+
try {
|
|
228964
|
+
if (this.workspaceConfig === void 0) {
|
|
228965
|
+
this.workspaceConfig = await loadWorkspaceConfig(this.rootDir);
|
|
228966
|
+
}
|
|
228967
|
+
if (this.workspaceConfig && (this.workspaceConfig.repos.length ?? 0) > 0) return null;
|
|
228968
|
+
const live = await this.store.getTask(taskId);
|
|
228969
|
+
if (live.worktree && existsSync65(live.worktree)) return live.worktree;
|
|
228970
|
+
const settings = await this.store.getSettings();
|
|
228971
|
+
const acquisitionTask = live.worktree ? { ...live, worktree: void 0, sessionFile: void 0 } : live;
|
|
228972
|
+
const acquired = await this.ensureGraphCustomNodeWorktree(acquisitionTask, settings, "planning");
|
|
228973
|
+
return acquired.worktree || null;
|
|
228974
|
+
} catch (error3) {
|
|
228975
|
+
executorLog.warn(`${taskId}: could not acquire a planning worktree \u2014 planning falls back to the repo root: ${formatError(error3)}`);
|
|
228976
|
+
return null;
|
|
228977
|
+
}
|
|
228978
|
+
}
|
|
228693
228979
|
async prepareGraphNodeExecution(node, nodeTask, settings, requirement) {
|
|
228694
228980
|
if (!requirement.requiresWorktree) return;
|
|
228695
228981
|
const live = await this.store.getTask(nodeTask.id);
|
|
@@ -228816,22 +229102,29 @@ ${info.feedback}`, revisionKey),
|
|
|
228816
229102
|
optionalGroupId,
|
|
228817
229103
|
reviewerInlineFixes: settings.reviewerInlineFixes
|
|
228818
229104
|
});
|
|
228819
|
-
|
|
228820
|
-
if (writeCapable && !executionTarget.worktree && !this.workspaceConfig) {
|
|
228821
|
-
return { outcome: "failure", value: "no-worktree-for-write-node" };
|
|
228822
|
-
}
|
|
229105
|
+
let executionTarget = writeCapable ? await this.store.getTask(live.id) : live;
|
|
228823
229106
|
const nodeDisplayName = typeof cfg.name === "string" && cfg.name.trim() ? cfg.name.trim() : node.id;
|
|
228824
229107
|
const isPlanReviewNode = node.id === "plan-review-step" || nodeDisplayName === "Plan Review" || optionalGroupId === "plan-review";
|
|
228825
|
-
|
|
228826
|
-
|
|
228827
|
-
|
|
228828
|
-
|
|
228829
|
-
|
|
228830
|
-
|
|
228831
|
-
|
|
228832
|
-
|
|
228833
|
-
|
|
229108
|
+
if (!this.workspaceConfig) {
|
|
229109
|
+
const recordedWorktreeMissing = Boolean(executionTarget.worktree) && !existsSync65(executionTarget.worktree);
|
|
229110
|
+
const shouldAcquire = !executionTarget.worktree || recordedWorktreeMissing && isPlanReviewNode;
|
|
229111
|
+
if (shouldAcquire) {
|
|
229112
|
+
if (recordedWorktreeMissing) {
|
|
229113
|
+
await this.store.logEntry(
|
|
229114
|
+
live.id,
|
|
229115
|
+
`Plan Review worktree ${executionTarget.worktree} is missing on disk \u2014 re-acquiring a task worktree instead of running in the shared checkout`,
|
|
229116
|
+
void 0,
|
|
229117
|
+
this.getRunContextFor(live.id)
|
|
229118
|
+
);
|
|
229119
|
+
}
|
|
229120
|
+
const acquisitionTask = recordedWorktreeMissing ? { ...executionTarget, worktree: void 0, sessionFile: void 0 } : executionTarget;
|
|
229121
|
+
executionTarget = await this.ensureGraphCustomNodeWorktree(acquisitionTask, settings, node.id);
|
|
229122
|
+
}
|
|
228834
229123
|
}
|
|
229124
|
+
if (writeCapable && !executionTarget.worktree && !this.workspaceConfig) {
|
|
229125
|
+
return { outcome: "failure", value: "no-worktree-for-write-node" };
|
|
229126
|
+
}
|
|
229127
|
+
const worktreePath = executionTarget.worktree || this.rootDir;
|
|
228835
229128
|
let prompt = typeof cfg.prompt === "string" ? cfg.prompt : "";
|
|
228836
229129
|
let modelProvider = typeof cfg.modelProvider === "string" && cfg.modelProvider.trim() ? cfg.modelProvider : void 0;
|
|
228837
229130
|
let modelId = typeof cfg.modelId === "string" && cfg.modelId.trim() ? cfg.modelId : void 0;
|
|
@@ -229128,6 +229421,77 @@ ${prompt}`;
|
|
|
229128
229421
|
const latestAction = live.log.at(-1)?.action;
|
|
229129
229422
|
return latestAction === "Resumed after engine restart" || latestAction === "Resuming execution after unpause";
|
|
229130
229423
|
}
|
|
229424
|
+
/*
|
|
229425
|
+
FNXC:SessionContention 2026-07-25-21:30:
|
|
229426
|
+
Two ways in, because contention must never slip through to a park:
|
|
229427
|
+
- the typed failure value the graph now publishes (SESSION_CONTENTION_HOLD_VALUE), and
|
|
229428
|
+
- a message-shape fallback over the run's `:error` context patches, so contention arriving from a
|
|
229429
|
+
path that has not been taught the typed value is still recognized.
|
|
229430
|
+
*/
|
|
229431
|
+
graphFailureErrorTexts(result2) {
|
|
229432
|
+
if (!result2.context) return [];
|
|
229433
|
+
const texts = [];
|
|
229434
|
+
for (const [key, value] of Object.entries(result2.context)) {
|
|
229435
|
+
if (key.endsWith(":error") && typeof value === "string" && value.trim()) texts.push(value);
|
|
229436
|
+
}
|
|
229437
|
+
return texts;
|
|
229438
|
+
}
|
|
229439
|
+
isSessionContentionGraphFailure(result2) {
|
|
229440
|
+
if (this.graphFailureValue(result2) === SESSION_CONTENTION_HOLD_VALUE) return true;
|
|
229441
|
+
return this.graphFailureErrorTexts(result2).some((text3) => isSessionContentionError(text3));
|
|
229442
|
+
}
|
|
229443
|
+
/*
|
|
229444
|
+
FNXC:SessionContention 2026-07-25-21:30 (self-recovering wait — the task is never parked):
|
|
229445
|
+
Retry the graph in place on an exponential backoff while the holder finishes. The counter is
|
|
229446
|
+
IN-MEMORY on purpose: it needs no schema change, and an engine restart resetting it is the desired
|
|
229447
|
+
behavior (a restart also drops the in-process registry, so the contention is gone anyway).
|
|
229448
|
+
When the ladder is exhausted the task is left cleanly dispatchable — status/error cleared, progress
|
|
229449
|
+
untouched — so ordinary scheduling picks it up later with a fresh budget. There is no terminal branch
|
|
229450
|
+
here by design: lease contention always ends (the holder finishes, or self-healing sweeps it), so
|
|
229451
|
+
parking the task would only require a human to press Retry on a condition that fixed itself.
|
|
229452
|
+
*/
|
|
229453
|
+
sessionContentionHoldAttempts = /* @__PURE__ */ new Map();
|
|
229454
|
+
clearSessionContentionHold(taskId) {
|
|
229455
|
+
this.sessionContentionHoldAttempts.delete(taskId);
|
|
229456
|
+
}
|
|
229457
|
+
async holdForSessionContention(task, live, result2) {
|
|
229458
|
+
const detail = this.graphFailureErrorTexts(result2).find((text3) => isSessionContentionError(text3));
|
|
229459
|
+
const priorAttempts = this.sessionContentionHoldAttempts.get(task.id) ?? 0;
|
|
229460
|
+
const attempt = priorAttempts + 1;
|
|
229461
|
+
if (attempt > MAX_SESSION_CONTENTION_HOLD_RETRIES) {
|
|
229462
|
+
this.clearSessionContentionHold(task.id);
|
|
229463
|
+
const message2 = `Still waiting on another task to release a shared session path after ${MAX_SESSION_CONTENTION_HOLD_RETRIES} attempts \u2014 leaving the task queued for normal re-dispatch (not a failure)${detail ? `: ${detail}` : ""}`;
|
|
229464
|
+
executorLog.warn(`${task.id}: ${message2}`);
|
|
229465
|
+
await this.store.logEntry(task.id, message2, void 0, this.getRunContextFor(task.id));
|
|
229466
|
+
if (live.status != null || live.error != null) {
|
|
229467
|
+
await this.store.updateTask(task.id, { status: null, error: null }, this.getRunContextFor(task.id));
|
|
229468
|
+
}
|
|
229469
|
+
return;
|
|
229470
|
+
}
|
|
229471
|
+
this.sessionContentionHoldAttempts.set(task.id, attempt);
|
|
229472
|
+
const message = `Waiting on another task to release a shared session path \u2014 retrying in place (${attempt}/${MAX_SESSION_CONTENTION_HOLD_RETRIES})${detail ? `: ${detail}` : ""}`;
|
|
229473
|
+
executorLog.warn(`${task.id}: ${message}`);
|
|
229474
|
+
await this.store.logEntry(task.id, message, void 0, this.getRunContextFor(task.id));
|
|
229475
|
+
if (live.status != null || live.error != null) {
|
|
229476
|
+
await this.store.updateTask(task.id, { status: null, error: null }, this.getRunContextFor(task.id));
|
|
229477
|
+
}
|
|
229478
|
+
const delayMs = SESSION_CONTENTION_HOLD_BACKOFF_MS === 0 ? 0 : Math.min(SESSION_CONTENTION_HOLD_MAX_BACKOFF_MS, SESSION_CONTENTION_HOLD_BACKOFF_MS * 2 ** (attempt - 1));
|
|
229479
|
+
const scheduleRetry = () => {
|
|
229480
|
+
void (async () => {
|
|
229481
|
+
try {
|
|
229482
|
+
const resume = await this.store.getTask(task.id);
|
|
229483
|
+
if (!resume || resume.deletedAt || resume.paused || resume.userPaused) {
|
|
229484
|
+
this.clearSessionContentionHold(task.id);
|
|
229485
|
+
return;
|
|
229486
|
+
}
|
|
229487
|
+
await this.execute(resume);
|
|
229488
|
+
} catch (err) {
|
|
229489
|
+
executorLog.error(`Failed session-contention retry for ${task.id}:`, err);
|
|
229490
|
+
}
|
|
229491
|
+
})();
|
|
229492
|
+
};
|
|
229493
|
+
setTimeout(scheduleRetry, delayMs).unref?.();
|
|
229494
|
+
}
|
|
229131
229495
|
graphFailureValue(result2) {
|
|
229132
229496
|
const failedNode = result2.visitedNodeIds[result2.visitedNodeIds.length - 1];
|
|
229133
229497
|
if (!failedNode || !result2.context) return void 0;
|
|
@@ -229719,6 +230083,11 @@ Graph value: ${failureValue}` : ""}`, budget.key),
|
|
|
229719
230083
|
await this.persistTokenUsage(task.id);
|
|
229720
230084
|
return;
|
|
229721
230085
|
}
|
|
230086
|
+
if (this.isSessionContentionGraphFailure(result2)) {
|
|
230087
|
+
await this.holdForSessionContention(task, live, result2);
|
|
230088
|
+
await this.persistTokenUsage(task.id);
|
|
230089
|
+
return;
|
|
230090
|
+
}
|
|
229722
230091
|
if (await this.routeUnusableWorktreeGraphFailureToRecovery(task, live, result2)) {
|
|
229723
230092
|
await this.persistTokenUsage(task.id);
|
|
229724
230093
|
return;
|
|
@@ -254471,6 +254840,9 @@ var init_in_process_runtime = __esm({
|
|
|
254471
254840
|
usageLimitPauser: this.usageLimitPauser,
|
|
254472
254841
|
agentStore: this.agentStore,
|
|
254473
254842
|
pluginRunner: this.pluginRunner,
|
|
254843
|
+
// FNXC:NodeWorktreeIsolation 2026-07-25-22:10: planning acquires (or reuses) the task's own
|
|
254844
|
+
// worktree through the executor's acquisition path, so no lane runs in the shared checkout.
|
|
254845
|
+
acquirePlanningWorktree: (taskId) => this.executor.ensureTaskWorktreeForPlanning(taskId),
|
|
254474
254846
|
onSpecifyStart: (t) => {
|
|
254475
254847
|
this.recordActivity();
|
|
254476
254848
|
runtimeLog.log(`Specifying ${t.id}...`);
|
|
@@ -254559,6 +254931,8 @@ var init_in_process_runtime = __esm({
|
|
|
254559
254931
|
getExecutingTaskIds: () => this.executor?.getExecutingTaskIds() ?? /* @__PURE__ */ new Set(),
|
|
254560
254932
|
clearPhantomExecutorBinding: (taskId, options) => this.executor?.clearPhantomExecutorBinding(taskId, options),
|
|
254561
254933
|
listWorktreeHolders: () => this.executor?.listWorktreeHolders() ?? [],
|
|
254934
|
+
// FNXC:PlanningEvacuation 2026-07-25-23:00: the executor owns the release safety conditions.
|
|
254935
|
+
releasePreExecutionWorktree: (taskId, reason) => this.executor?.releasePreExecutionWorktree(taskId, reason) ?? Promise.resolve(false),
|
|
254562
254936
|
recoverApprovedTriageTask: (task) => this.triageProcessor?.recoverApprovedTask(task) ?? Promise.resolve(false),
|
|
254563
254937
|
getPlanningTaskIds: () => this.triageProcessor?.getPlanningTaskIds() ?? /* @__PURE__ */ new Set(),
|
|
254564
254938
|
// FNXC:TaskTiming 2026-08-01-12:00: orphan planning recovery must defer
|