taskplane 0.23.16 → 0.24.0
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/bin/taskplane.mjs +8 -41
- package/dashboard/public/app.js +24 -24
- package/dashboard/server.cjs +29 -7
- package/extensions/task-runner.ts +7 -3
- package/extensions/taskplane/abort.ts +93 -81
- package/extensions/taskplane/agent-host.ts +4 -5
- package/extensions/taskplane/config-loader.ts +86 -11
- package/extensions/taskplane/config-schema.ts +13 -13
- package/extensions/taskplane/diagnostic-reports.ts +1 -1
- package/extensions/taskplane/diagnostics.ts +3 -3
- package/extensions/taskplane/engine.ts +37 -16
- package/extensions/taskplane/execution.ts +86 -1000
- package/extensions/taskplane/extension.ts +60 -189
- package/extensions/taskplane/formatting.ts +5 -5
- package/extensions/taskplane/merge.ts +63 -371
- package/extensions/taskplane/messages.ts +1 -1
- package/extensions/taskplane/naming.ts +4 -4
- package/extensions/taskplane/persistence.ts +53 -26
- package/extensions/taskplane/process-registry.ts +2 -2
- package/extensions/taskplane/resume.ts +65 -130
- package/extensions/taskplane/sessions.ts +57 -92
- package/extensions/taskplane/settings-tui.ts +4 -4
- package/extensions/taskplane/tmux-compat.ts +37 -0
- package/extensions/taskplane/types.ts +43 -43
- package/extensions/taskplane/waves.ts +12 -10
- package/extensions/taskplane/worktree.ts +8 -66
- package/package.json +1 -1
- package/templates/config/task-orchestrator.yaml +3 -4
|
@@ -56,11 +56,13 @@ import type {
|
|
|
56
56
|
* - CONFIG_JSON_MALFORMED: File exists but is not valid JSON
|
|
57
57
|
* - CONFIG_VERSION_UNSUPPORTED: configVersion is not supported by this version
|
|
58
58
|
* - CONFIG_VERSION_MISSING: configVersion field is missing from JSON
|
|
59
|
+
* - CONFIG_LEGACY_FIELD: removed TMUX-era field/value detected; migration required
|
|
59
60
|
*/
|
|
60
61
|
export type ConfigLoadErrorCode =
|
|
61
62
|
| "CONFIG_JSON_MALFORMED"
|
|
62
63
|
| "CONFIG_VERSION_UNSUPPORTED"
|
|
63
|
-
| "CONFIG_VERSION_MISSING"
|
|
64
|
+
| "CONFIG_VERSION_MISSING"
|
|
65
|
+
| "CONFIG_LEGACY_FIELD";
|
|
64
66
|
|
|
65
67
|
export class ConfigLoadError extends Error {
|
|
66
68
|
code: ConfigLoadErrorCode;
|
|
@@ -110,6 +112,61 @@ function deepMerge<T extends Record<string, any>>(target: T, source: Record<stri
|
|
|
110
112
|
return target;
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
function hasOwn(obj: unknown, key: string): boolean {
|
|
116
|
+
return !!obj && typeof obj === "object" && Object.prototype.hasOwnProperty.call(obj, key);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function throwLegacyFieldError(fieldPath: string, source: string, fixHint: string): never {
|
|
120
|
+
throw new ConfigLoadError(
|
|
121
|
+
"CONFIG_LEGACY_FIELD",
|
|
122
|
+
`[taskplane] ${source}: "${fieldPath}" is no longer supported under Runtime V2. ${fixHint}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function assertNoLegacyTmuxProjectConfig(config: TaskplaneConfig, source: string): void {
|
|
127
|
+
const orchestratorCore = config.orchestrator?.orchestrator as Record<string, unknown> | undefined;
|
|
128
|
+
if (hasOwn(orchestratorCore, "tmuxPrefix")) {
|
|
129
|
+
throwLegacyFieldError(
|
|
130
|
+
"orchestrator.orchestrator.tmuxPrefix",
|
|
131
|
+
source,
|
|
132
|
+
"Use \"orchestrator.orchestrator.sessionPrefix\" instead.",
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (orchestratorCore?.spawnMode === "tmux") {
|
|
136
|
+
throwLegacyFieldError(
|
|
137
|
+
"orchestrator.orchestrator.spawnMode",
|
|
138
|
+
source,
|
|
139
|
+
"Use \"subprocess\" instead.",
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const workerConfig = config.taskRunner?.worker as Record<string, unknown> | undefined;
|
|
144
|
+
if (workerConfig?.spawnMode === "tmux") {
|
|
145
|
+
throwLegacyFieldError(
|
|
146
|
+
"taskRunner.worker.spawnMode",
|
|
147
|
+
source,
|
|
148
|
+
"Use \"subprocess\" or remove the field to inherit defaults.",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function assertNoLegacyTmuxUserPreferences(raw: Record<string, any>, prefsPath: string): void {
|
|
154
|
+
if (hasOwn(raw, "tmuxPrefix")) {
|
|
155
|
+
throwLegacyFieldError(
|
|
156
|
+
"tmuxPrefix",
|
|
157
|
+
`user preferences (${prefsPath})`,
|
|
158
|
+
"Rename it to \"sessionPrefix\".",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (raw.spawnMode === "tmux") {
|
|
162
|
+
throwLegacyFieldError(
|
|
163
|
+
"spawnMode",
|
|
164
|
+
`user preferences (${prefsPath})`,
|
|
165
|
+
"Set it to \"subprocess\".",
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
113
170
|
|
|
114
171
|
// ── YAML snake_case → camelCase Mapping ──────────────────────────────
|
|
115
172
|
|
|
@@ -600,19 +657,25 @@ export function loadUserPreferences(): UserPreferences {
|
|
|
600
657
|
}
|
|
601
658
|
|
|
602
659
|
// Extract only allowlisted fields — unknown keys are ignored
|
|
603
|
-
return extractAllowlistedPreferences(parsed);
|
|
660
|
+
return extractAllowlistedPreferences(parsed, prefsPath);
|
|
604
661
|
}
|
|
605
662
|
|
|
606
663
|
/**
|
|
607
664
|
* Extract only recognized/allowlisted fields from a raw parsed object.
|
|
608
665
|
* Unknown keys are silently dropped — this is the Layer 2 boundary guardrail.
|
|
609
666
|
*/
|
|
610
|
-
function extractAllowlistedPreferences(raw: Record<string, any
|
|
667
|
+
function extractAllowlistedPreferences(raw: Record<string, any>, prefsPath: string): UserPreferences {
|
|
668
|
+
assertNoLegacyTmuxUserPreferences(raw, prefsPath);
|
|
669
|
+
|
|
611
670
|
const prefs: UserPreferences = {};
|
|
612
671
|
|
|
613
672
|
if (typeof raw.operatorId === "string") prefs.operatorId = raw.operatorId;
|
|
614
|
-
if (typeof raw.
|
|
615
|
-
|
|
673
|
+
if (typeof raw.sessionPrefix === "string") {
|
|
674
|
+
prefs.sessionPrefix = raw.sessionPrefix;
|
|
675
|
+
}
|
|
676
|
+
if (raw.spawnMode === "subprocess") {
|
|
677
|
+
prefs.spawnMode = "subprocess";
|
|
678
|
+
}
|
|
616
679
|
if (typeof raw.workerModel === "string") prefs.workerModel = raw.workerModel;
|
|
617
680
|
if (typeof raw.reviewerModel === "string") prefs.reviewerModel = raw.reviewerModel;
|
|
618
681
|
if (typeof raw.mergeModel === "string") prefs.mergeModel = raw.mergeModel;
|
|
@@ -638,7 +701,7 @@ function extractAllowlistedPreferences(raw: Record<string, any>): UserPreference
|
|
|
638
701
|
*
|
|
639
702
|
* Mapping table:
|
|
640
703
|
* prefs.operatorId → config.orchestrator.orchestrator.operatorId
|
|
641
|
-
* prefs.
|
|
704
|
+
* prefs.sessionPrefix → config.orchestrator.orchestrator.sessionPrefix
|
|
642
705
|
* prefs.spawnMode → config.orchestrator.orchestrator.spawnMode
|
|
643
706
|
* prefs.workerModel → config.taskRunner.worker.model
|
|
644
707
|
* prefs.reviewerModel → config.taskRunner.reviewer.model
|
|
@@ -653,7 +716,7 @@ export function applyUserPreferences(config: TaskplaneConfig, prefs: UserPrefere
|
|
|
653
716
|
};
|
|
654
717
|
|
|
655
718
|
applyStr(prefs.operatorId, (v) => { config.orchestrator.orchestrator.operatorId = v; });
|
|
656
|
-
applyStr(prefs.
|
|
719
|
+
applyStr(prefs.sessionPrefix, (v) => { config.orchestrator.orchestrator.sessionPrefix = v; });
|
|
657
720
|
applyStr(prefs.workerModel, (v) => { config.taskRunner.worker.model = v; });
|
|
658
721
|
applyStr(prefs.reviewerModel, (v) => { config.taskRunner.reviewer.model = v; });
|
|
659
722
|
applyStr(prefs.mergeModel, (v) => { config.orchestrator.merge.model = v; });
|
|
@@ -661,6 +724,13 @@ export function applyUserPreferences(config: TaskplaneConfig, prefs: UserPrefere
|
|
|
661
724
|
|
|
662
725
|
// spawnMode: enum — apply if defined (not a string-empty check)
|
|
663
726
|
if (prefs.spawnMode !== undefined) {
|
|
727
|
+
if (prefs.spawnMode === "tmux") {
|
|
728
|
+
throwLegacyFieldError(
|
|
729
|
+
"spawnMode",
|
|
730
|
+
"user preferences (runtime)",
|
|
731
|
+
"Set it to \"subprocess\".",
|
|
732
|
+
);
|
|
733
|
+
}
|
|
664
734
|
config.orchestrator.orchestrator.spawnMode = prefs.spawnMode;
|
|
665
735
|
}
|
|
666
736
|
|
|
@@ -670,7 +740,6 @@ export function applyUserPreferences(config: TaskplaneConfig, prefs: UserPrefere
|
|
|
670
740
|
return config;
|
|
671
741
|
}
|
|
672
742
|
|
|
673
|
-
|
|
674
743
|
// ── Unified Loader ───────────────────────────────────────────────────
|
|
675
744
|
|
|
676
745
|
/**
|
|
@@ -784,9 +853,12 @@ export function loadProjectConfig(cwd: string, pointerConfigRoot?: string): Task
|
|
|
784
853
|
};
|
|
785
854
|
}
|
|
786
855
|
|
|
856
|
+
assertNoLegacyTmuxProjectConfig(config, `project config (${configRoot})`);
|
|
857
|
+
|
|
787
858
|
// Layer 2: User preferences (allowlisted fields only)
|
|
788
859
|
const prefs = loadUserPreferences();
|
|
789
860
|
applyUserPreferences(config, prefs);
|
|
861
|
+
assertNoLegacyTmuxProjectConfig(config, `project config (${configRoot}) after preferences merge`);
|
|
790
862
|
|
|
791
863
|
return config;
|
|
792
864
|
}
|
|
@@ -810,6 +882,7 @@ export function loadLayer1Config(cwd: string, pointerConfigRoot?: string): Taskp
|
|
|
810
882
|
// Try JSON first
|
|
811
883
|
const jsonConfig = loadJsonConfig(configRoot);
|
|
812
884
|
if (jsonConfig !== null) {
|
|
885
|
+
assertNoLegacyTmuxProjectConfig(jsonConfig, `project config (${configRoot})`);
|
|
813
886
|
return jsonConfig;
|
|
814
887
|
}
|
|
815
888
|
|
|
@@ -817,12 +890,14 @@ export function loadLayer1Config(cwd: string, pointerConfigRoot?: string): Taskp
|
|
|
817
890
|
const taskRunner = loadTaskRunnerYaml(configRoot);
|
|
818
891
|
const orchestrator = loadOrchestratorYaml(configRoot);
|
|
819
892
|
const workspace = loadWorkspaceYaml(configRoot);
|
|
820
|
-
|
|
893
|
+
const config: TaskplaneConfig = {
|
|
821
894
|
configVersion: CONFIG_VERSION,
|
|
822
895
|
taskRunner,
|
|
823
896
|
orchestrator,
|
|
824
897
|
...(workspace ? { workspace } : {}),
|
|
825
898
|
};
|
|
899
|
+
assertNoLegacyTmuxProjectConfig(config, `project config (${configRoot})`);
|
|
900
|
+
return config;
|
|
826
901
|
}
|
|
827
902
|
|
|
828
903
|
|
|
@@ -847,7 +922,7 @@ export function toOrchestratorConfig(config: TaskplaneConfig): import("./types.t
|
|
|
847
922
|
worktree_prefix: o.orchestrator.worktreePrefix,
|
|
848
923
|
batch_id_format: o.orchestrator.batchIdFormat,
|
|
849
924
|
spawn_mode: o.orchestrator.spawnMode,
|
|
850
|
-
|
|
925
|
+
sessionPrefix: o.orchestrator.sessionPrefix,
|
|
851
926
|
operator_id: o.orchestrator.operatorId,
|
|
852
927
|
integration: o.orchestrator.integration,
|
|
853
928
|
},
|
|
@@ -942,7 +1017,7 @@ export function toTaskConfig(config: TaskplaneConfig): {
|
|
|
942
1017
|
standards: { docs: string[]; rules: string[] };
|
|
943
1018
|
standards_overrides: Record<string, { docs?: string[]; rules?: string[] }>;
|
|
944
1019
|
task_areas: Record<string, { path: string; [key: string]: any }>;
|
|
945
|
-
worker: { model: string; tools: string; thinking: string; spawn_mode?: "subprocess"
|
|
1020
|
+
worker: { model: string; tools: string; thinking: string; spawn_mode?: "subprocess" };
|
|
946
1021
|
reviewer: { model: string; tools: string; thinking: string };
|
|
947
1022
|
context: {
|
|
948
1023
|
worker_context_window: number;
|
|
@@ -109,8 +109,8 @@ export interface WorkerConfig {
|
|
|
109
109
|
tools: string;
|
|
110
110
|
/** Thinking mode setting passed to worker agent */
|
|
111
111
|
thinking: string;
|
|
112
|
-
/** Optional spawn mode override for task-runner */
|
|
113
|
-
spawnMode?: "subprocess"
|
|
112
|
+
/** Optional spawn mode override for task-runner (Runtime V2 subprocess-only). */
|
|
113
|
+
spawnMode?: "subprocess";
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/** Reviewer agent configuration */
|
|
@@ -139,7 +139,7 @@ export interface ContextConfig {
|
|
|
139
139
|
maxReviewCycles: number;
|
|
140
140
|
/** Max no-progress iterations before marking failure */
|
|
141
141
|
noProgressLimit: number;
|
|
142
|
-
/** Optional per-worker wall-clock cap (minutes, used in
|
|
142
|
+
/** Optional per-worker wall-clock cap (minutes, used in orchestrated flows) */
|
|
143
143
|
maxWorkerMinutes?: number;
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -260,10 +260,10 @@ export interface OrchestratorCoreConfig {
|
|
|
260
260
|
worktreePrefix: string;
|
|
261
261
|
/** Batch ID format used in logs/branch naming */
|
|
262
262
|
batchIdFormat: "timestamp" | "sequential";
|
|
263
|
-
/** How lane sessions are spawned */
|
|
264
|
-
spawnMode: "
|
|
265
|
-
/** Prefix for orchestrator
|
|
266
|
-
|
|
263
|
+
/** How lane sessions are spawned (Runtime V2 subprocess-only). */
|
|
264
|
+
spawnMode: "subprocess";
|
|
265
|
+
/** Prefix for orchestrator session naming */
|
|
266
|
+
sessionPrefix: string;
|
|
267
267
|
/** Operator identifier. Auto-detected from OS username if empty */
|
|
268
268
|
operatorId: string;
|
|
269
269
|
/** How completed batches are integrated. manual = user runs /orch-integrate. supervised = supervisor proposes plan, asks confirmation. auto = supervisor executes without asking. */
|
|
@@ -498,7 +498,7 @@ export interface TaskplaneConfig {
|
|
|
498
498
|
* | Preference field | Config path | Type |
|
|
499
499
|
* |--------------------|--------------------------------------|---------|
|
|
500
500
|
* | operatorId | orchestrator.orchestrator.operatorId | string |
|
|
501
|
-
* |
|
|
501
|
+
* | sessionPrefix | orchestrator.orchestrator.sessionPrefix | string |
|
|
502
502
|
* | spawnMode | orchestrator.orchestrator.spawnMode | string |
|
|
503
503
|
* | workerModel | taskRunner.worker.model | string |
|
|
504
504
|
* | reviewerModel | taskRunner.reviewer.model | string |
|
|
@@ -509,10 +509,10 @@ export interface TaskplaneConfig {
|
|
|
509
509
|
export interface UserPreferences {
|
|
510
510
|
/** Operator identifier (overrides orchestrator.orchestrator.operatorId) */
|
|
511
511
|
operatorId?: string;
|
|
512
|
-
/**
|
|
513
|
-
|
|
514
|
-
/** Spawn mode override (overrides orchestrator.orchestrator.spawnMode) */
|
|
515
|
-
spawnMode?: "
|
|
512
|
+
/** Orchestrator session prefix (overrides orchestrator.orchestrator.sessionPrefix) */
|
|
513
|
+
sessionPrefix?: string;
|
|
514
|
+
/** Spawn mode override (overrides orchestrator.orchestrator.spawnMode). */
|
|
515
|
+
spawnMode?: "subprocess";
|
|
516
516
|
/** Worker model override (overrides taskRunner.worker.model) */
|
|
517
517
|
workerModel?: string;
|
|
518
518
|
/** Reviewer model override (overrides taskRunner.reviewer.model) */
|
|
@@ -582,7 +582,7 @@ export const DEFAULT_ORCHESTRATOR_SECTION: OrchestratorSection = {
|
|
|
582
582
|
worktreePrefix: "taskplane-wt",
|
|
583
583
|
batchIdFormat: "timestamp",
|
|
584
584
|
spawnMode: "subprocess",
|
|
585
|
-
|
|
585
|
+
sessionPrefix: "orch",
|
|
586
586
|
operatorId: "",
|
|
587
587
|
integration: "manual",
|
|
588
588
|
},
|
|
@@ -411,7 +411,7 @@ export function assembleDiagnosticInput(
|
|
|
411
411
|
const record: PersistedTaskRecord = {
|
|
412
412
|
taskId,
|
|
413
413
|
laneNumber: lane?.laneNumber ?? 0,
|
|
414
|
-
sessionName: outcome?.sessionName || lane?.
|
|
414
|
+
sessionName: outcome?.sessionName || lane?.laneSessionId || "",
|
|
415
415
|
status: outcome?.status ?? "pending",
|
|
416
416
|
taskFolder: "",
|
|
417
417
|
startedAt: outcome?.startTime ?? null,
|
|
@@ -49,9 +49,9 @@ export interface SessionTokenCounts {
|
|
|
49
49
|
* | `context_overflow` | Hit context window limit (compactions + high ctx %) |
|
|
50
50
|
* | `wall_clock_timeout` | Killed by task-runner's max_worker_minutes timer |
|
|
51
51
|
* | `process_crash` | Non-zero exit code with no API error indicators |
|
|
52
|
-
* | `session_vanished` |
|
|
52
|
+
* | `session_vanished` | Session disappeared without exit summary |
|
|
53
53
|
* | `stall_timeout` | No STATUS.md progress for stall_timeout minutes |
|
|
54
|
-
* | `user_killed` | User manually killed the session (e.g.,
|
|
54
|
+
* | `user_killed` | User manually killed the session (e.g., forced process kill) |
|
|
55
55
|
* | `unknown` | Could not determine cause |
|
|
56
56
|
*/
|
|
57
57
|
export type ExitClassification =
|
|
@@ -157,7 +157,7 @@ export interface ExitSummary {
|
|
|
157
157
|
* - `timerKilled`: true if task-runner's max_worker_minutes timer killed the session
|
|
158
158
|
* - `contextKilled`: true if the task-runner explicitly killed the session due to context limit
|
|
159
159
|
* - `stallDetected`: true if monitoring detected no STATUS.md progress
|
|
160
|
-
* - `userKilled`: true if user manually killed the session (e.g., /orch-abort,
|
|
160
|
+
* - `userKilled`: true if user manually killed the session (e.g., /orch-abort, forced process kill)
|
|
161
161
|
* - `contextPct`: estimated context utilization % (0-100), null if unknown
|
|
162
162
|
*
|
|
163
163
|
* Design: single structured input object (not positional args) for
|
|
@@ -6,19 +6,19 @@ import { existsSync, readdirSync, readFileSync, unlinkSync } from "fs";
|
|
|
6
6
|
import { join, resolve } from "path";
|
|
7
7
|
|
|
8
8
|
import { formatDiscoveryResults, runDiscovery } from "./discovery.ts";
|
|
9
|
-
import { computeTransitiveDependents, execLog,
|
|
9
|
+
import { computeTransitiveDependents, execLog, executeLaneV2, executeWave, killV2LaneAgents } from "./execution.ts";
|
|
10
10
|
import type { RuntimeBackend } from "./execution.ts";
|
|
11
11
|
import type { MonitorUpdateCallback } from "./execution.ts";
|
|
12
12
|
// classifyExit no longer called directly — Tier 0 uses exitDiagnostic.classification
|
|
13
13
|
// from the diagnostic-reports pipeline (populated by assembleDiagnosticInput).
|
|
14
14
|
import { getCurrentBranch, runGit } from "./git.ts";
|
|
15
|
-
import { mergeWaveByRepo, MergeHealthMonitor } from "./merge.ts";
|
|
15
|
+
import { killAllMergeAgentsV2, mergeWaveByRepo, MergeHealthMonitor } from "./merge.ts";
|
|
16
16
|
import { applyMergeRetryLoop, computeCleanupGatePolicy, computeMergeFailurePolicy, extractFailedRepoId, formatRepoMergeSummary, ORCH_MESSAGES } from "./messages.ts";
|
|
17
17
|
import type { CleanupGateRepoFailure } from "./messages.ts";
|
|
18
18
|
import { assembleDiagnosticInput, emitDiagnosticReports } from "./diagnostic-reports.ts";
|
|
19
19
|
import { resolveOperatorId } from "./naming.ts";
|
|
20
20
|
import { applyPartialProgressToOutcomes, buildTier0EventBase, deleteBatchState, emitEngineEvent, emitTier0Event, loadBatchHistory, loadBatchState, persistRuntimeState, saveBatchHistory, seedPendingOutcomesForAllocatedLanes, syncTaskOutcomesFromMonitor, upsertTaskOutcome } from "./persistence.ts";
|
|
21
|
-
import {
|
|
21
|
+
import { readRegistrySnapshot, isTerminalStatus, isProcessAlive as registryIsProcessAlive } from "./process-registry.ts";
|
|
22
22
|
import { buildBatchProgressSnapshot, buildEngineEventBase, defaultResilienceState, FATAL_DISCOVERY_CODES, generateBatchId, TIER0_RETRYABLE_CLASSIFICATIONS, TIER0_RETRY_BUDGETS, tier0ScopeKey, tier0WaveScopeKey } from "./types.ts";
|
|
23
23
|
import type { AllocatedLane, AllocatedTask, BatchHistorySummary, BatchTaskSummary, BatchWaveSummary, DiscoveryResult, EngineEventCallback, EscalationContext, LaneExecutionResult, LaneTaskOutcome, MergeWaveResult, OrchBatchPhase, OrchBatchRuntimeState, OrchestratorConfig, SupervisorAlert, SupervisorAlertCallback, TaskRunnerConfig, Tier0EscalationPattern, Tier0RecoveryPattern, TokenCounts, WaveExecutionResult, WorkspaceConfig } from "./types.ts";
|
|
24
24
|
import { buildDependencyGraph, computeWaves, resolveBaseBranch, resolveRepoRoot, validateGraph } from "./waves.ts";
|
|
@@ -278,8 +278,7 @@ async function attemptWorkerCrashRetry(
|
|
|
278
278
|
// may be paused due to stop-wave policy, but Tier 0 retry should
|
|
279
279
|
// attempt recovery before the stop decision takes effect (R002-4).
|
|
280
280
|
const retryPauseSignal = { paused: false };
|
|
281
|
-
const
|
|
282
|
-
const retryResult = await retryExecutor(
|
|
281
|
+
const retryResult = await executeLaneV2(
|
|
283
282
|
retryLane,
|
|
284
283
|
orchConfig,
|
|
285
284
|
repoRoot,
|
|
@@ -546,8 +545,7 @@ async function attemptModelFallbackRetry(
|
|
|
546
545
|
// the task-runner to use the session model instead of configured model.
|
|
547
546
|
// TP-089: Also include ORCH_BATCH_ID so mailbox steering works for retries.
|
|
548
547
|
const modelFallbackEnv = { TASKPLANE_MODEL_FALLBACK: "1", ORCH_BATCH_ID: batchState.batchId };
|
|
549
|
-
const
|
|
550
|
-
const retryResult = await retryExecutor(
|
|
548
|
+
const retryResult = await executeLaneV2(
|
|
551
549
|
retryLane,
|
|
552
550
|
orchConfig,
|
|
553
551
|
repoRoot,
|
|
@@ -2475,16 +2473,39 @@ export async function executeOrchBatch(
|
|
|
2475
2473
|
if (preserveWorktreesForResume) {
|
|
2476
2474
|
execLog("batch", batchState.batchId, "skipping final cleanup to preserve worktrees/branches for resume");
|
|
2477
2475
|
} else {
|
|
2478
|
-
// Kill
|
|
2479
|
-
// On Windows,
|
|
2480
|
-
// directory
|
|
2481
|
-
const
|
|
2482
|
-
const
|
|
2483
|
-
if (
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2476
|
+
// Kill lingering Runtime V2 agents BEFORE removing worktrees.
|
|
2477
|
+
// On Windows, lingering processes with cwd inside the worktree can lock
|
|
2478
|
+
// the directory and cause `git worktree remove` to fail.
|
|
2479
|
+
const lingeringLaneSessions = new Set<string>();
|
|
2480
|
+
const registry = readRegistrySnapshot(stateRoot, batchState.batchId);
|
|
2481
|
+
if (registry) {
|
|
2482
|
+
for (const manifest of Object.values(registry.agents)) {
|
|
2483
|
+
if (manifest.role !== "worker" && manifest.role !== "reviewer") continue;
|
|
2484
|
+
if (isTerminalStatus(manifest.status) || !registryIsProcessAlive(manifest.pid)) continue;
|
|
2485
|
+
lingeringLaneSessions.add(manifest.agentId.replace(/-(worker|reviewer)$/, ""));
|
|
2487
2486
|
}
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
let performedAgentCleanup = false;
|
|
2490
|
+
if (lingeringLaneSessions.size > 0) {
|
|
2491
|
+
execLog("batch", batchState.batchId, `killing ${lingeringLaneSessions.size} lingering lane agent session(s) before cleanup`);
|
|
2492
|
+
for (const sessionName of lingeringLaneSessions) {
|
|
2493
|
+
killV2LaneAgents(sessionName, {
|
|
2494
|
+
stateRoot,
|
|
2495
|
+
batchId: batchState.batchId,
|
|
2496
|
+
logContext: "batch",
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
performedAgentCleanup = true;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
const killedMergeAgents = killAllMergeAgentsV2();
|
|
2503
|
+
if (killedMergeAgents > 0) {
|
|
2504
|
+
execLog("batch", batchState.batchId, `killed ${killedMergeAgents} lingering merge agent(s) before cleanup`);
|
|
2505
|
+
performedAgentCleanup = true;
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
if (performedAgentCleanup) {
|
|
2488
2509
|
sleepSync(1000); // Give OS time to release file locks
|
|
2489
2510
|
}
|
|
2490
2511
|
|