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
|
@@ -98,9 +98,9 @@ export const SECTIONS: SectionDef[] = [
|
|
|
98
98
|
{ configPath: "orchestrator.orchestrator.worktreeLocation", label: "Worktree Location", control: "toggle", layer: "L1", fieldType: "enum", values: ["sibling", "subdirectory"], description: "Where lane worktree directories are created" },
|
|
99
99
|
{ configPath: "orchestrator.orchestrator.worktreePrefix", label: "Worktree Prefix", control: "input", layer: "L1", fieldType: "string", description: "Prefix for worktree directory names" },
|
|
100
100
|
{ configPath: "orchestrator.orchestrator.batchIdFormat", label: "Batch ID Format", control: "toggle", layer: "L1", fieldType: "enum", values: ["timestamp", "sequential"], description: "Batch ID format for logs/branch naming" },
|
|
101
|
-
// spawn_mode removed from Orchestrator section —
|
|
101
|
+
// spawn_mode removed from Orchestrator section — Runtime V2 is subprocess-only.
|
|
102
102
|
// The user-facing spawn mode setting is under Worker (controls /task behavior).
|
|
103
|
-
{ configPath: "orchestrator.orchestrator.
|
|
103
|
+
{ configPath: "orchestrator.orchestrator.sessionPrefix", label: "Session Prefix", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "sessionPrefix", description: "Prefix for orchestrator session names" },
|
|
104
104
|
{ configPath: "orchestrator.orchestrator.operatorId", label: "Operator ID", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "operatorId", description: "Operator identifier (empty = auto-detect)" },
|
|
105
105
|
{ configPath: "orchestrator.orchestrator.integration", label: "Integration", control: "toggle", layer: "L1", fieldType: "enum", values: ["manual", "supervised", "auto"], description: "How completed batches are integrated. manual = user runs /orch-integrate. supervised = supervisor proposes plan, asks confirmation. auto = supervisor executes without asking." },
|
|
106
106
|
],
|
|
@@ -162,7 +162,7 @@ export const SECTIONS: SectionDef[] = [
|
|
|
162
162
|
{ configPath: "taskRunner.worker.model", label: "Worker Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "workerModel", description: "Worker model (empty = inherit session)" },
|
|
163
163
|
{ configPath: "taskRunner.worker.tools", label: "Worker Tools", control: "input", layer: "L1", fieldType: "string", description: "Worker tool allowlist" },
|
|
164
164
|
{ configPath: "taskRunner.worker.thinking", label: "Worker Thinking", control: "input", layer: "L1", fieldType: "string", description: "Worker thinking mode" },
|
|
165
|
-
{ configPath: "taskRunner.worker.spawnMode", label: "Spawn Mode", control: "toggle", layer: "L1", fieldType: "enum", values: ["subprocess",
|
|
165
|
+
{ configPath: "taskRunner.worker.spawnMode", label: "Spawn Mode", control: "toggle", layer: "L1", fieldType: "enum", values: ["subprocess"], optional: true, description: "How /task spawns workers and reviewers. Runtime V2 supports subprocess only." },
|
|
166
166
|
],
|
|
167
167
|
},
|
|
168
168
|
{
|
|
@@ -625,7 +625,7 @@ export function detectFieldSource(
|
|
|
625
625
|
} else if (field.fieldType === "enum") {
|
|
626
626
|
// Enum rule: must be a valid enum value from the field's values array.
|
|
627
627
|
// Matches extractAllowlistedPreferences which checks exact enum membership
|
|
628
|
-
// (e.g., raw.spawnMode === "
|
|
628
|
+
// (e.g., raw.spawnMode === "subprocess").
|
|
629
629
|
if (prefVal !== undefined && field.values && field.values.includes(String(prefVal))) return "user";
|
|
630
630
|
} else if (field.fieldType === "number") {
|
|
631
631
|
// Number rule: must be typeof number and finite → (user)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration-only helpers for legacy TMUX-shaped persisted lane fields.
|
|
3
|
+
*
|
|
4
|
+
* Runtime V2 no longer accepts TMUX config/runtime contracts. The only
|
|
5
|
+
* compatibility retained here is one-release state ingress normalization for
|
|
6
|
+
* `lanes[].tmuxSessionName` → `lanes[].laneSessionId`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface LaneSessionAliasTarget {
|
|
10
|
+
laneSessionId?: unknown;
|
|
11
|
+
tmuxSessionName?: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Read canonical + legacy lane session fields from a lane-like record.
|
|
16
|
+
*/
|
|
17
|
+
export function readLaneSessionAliases(target: LaneSessionAliasTarget): {
|
|
18
|
+
laneSessionId: unknown;
|
|
19
|
+
tmuxSessionName: unknown;
|
|
20
|
+
} {
|
|
21
|
+
return {
|
|
22
|
+
laneSessionId: target.laneSessionId,
|
|
23
|
+
tmuxSessionName: target.tmuxSessionName,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Normalize tmuxSessionName -> laneSessionId in place and remove legacy key.
|
|
29
|
+
*/
|
|
30
|
+
export function normalizeLaneSessionAlias(target: LaneSessionAliasTarget): void {
|
|
31
|
+
if (typeof target.laneSessionId !== "string" && typeof target.tmuxSessionName === "string") {
|
|
32
|
+
target.laneSessionId = target.tmuxSessionName;
|
|
33
|
+
}
|
|
34
|
+
if ("tmuxSessionName" in target) {
|
|
35
|
+
delete target.tmuxSessionName;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -14,8 +14,8 @@ export interface OrchestratorConfig {
|
|
|
14
14
|
worktree_location: "sibling" | "subdirectory";
|
|
15
15
|
worktree_prefix: string;
|
|
16
16
|
batch_id_format: "timestamp" | "sequential";
|
|
17
|
-
spawn_mode: "
|
|
18
|
-
|
|
17
|
+
spawn_mode: "subprocess";
|
|
18
|
+
sessionPrefix: string;
|
|
19
19
|
/** Optional operator identifier. Auto-detected from OS username if empty. */
|
|
20
20
|
operator_id: string;
|
|
21
21
|
/** How completed batches are integrated. manual = user runs /orch-integrate. supervised = supervisor proposes plan, asks confirmation. auto = supervisor executes without asking. */
|
|
@@ -260,7 +260,7 @@ export const DEFAULT_ORCHESTRATOR_CONFIG: OrchestratorConfig = {
|
|
|
260
260
|
worktree_prefix: "taskplane-wt",
|
|
261
261
|
batch_id_format: "timestamp",
|
|
262
262
|
spawn_mode: "subprocess",
|
|
263
|
-
|
|
263
|
+
sessionPrefix: "orch",
|
|
264
264
|
operator_id: "",
|
|
265
265
|
integration: "manual",
|
|
266
266
|
},
|
|
@@ -608,7 +608,7 @@ export interface AllocatedTask {
|
|
|
608
608
|
/**
|
|
609
609
|
* A fully-allocated lane ready for execution.
|
|
610
610
|
*
|
|
611
|
-
* Contains everything Steps 2-3 need to
|
|
611
|
+
* Contains everything Steps 2-3 need to run lane sessions,
|
|
612
612
|
* monitor progress, and identify the lane. This is the contract
|
|
613
613
|
* between Step 1 (allocation) and Step 2 (execution).
|
|
614
614
|
*/
|
|
@@ -617,8 +617,8 @@ export interface AllocatedLane {
|
|
|
617
617
|
laneNumber: number;
|
|
618
618
|
/** Lane identifier for display and logging (e.g., "lane-1") */
|
|
619
619
|
laneId: string;
|
|
620
|
-
/**
|
|
621
|
-
|
|
620
|
+
/** Lane session identifier (e.g., "orch-lane-1") — used by Step 2 */
|
|
621
|
+
laneSessionId: string;
|
|
622
622
|
/** Absolute path to the lane's worktree directory */
|
|
623
623
|
worktreePath: string;
|
|
624
624
|
/** Git branch name checked out in the worktree */
|
|
@@ -690,7 +690,7 @@ export interface LaneTaskOutcome {
|
|
|
690
690
|
endTime: number | null;
|
|
691
691
|
/** Human-readable reason for the outcome */
|
|
692
692
|
exitReason: string;
|
|
693
|
-
/**
|
|
693
|
+
/** Lane session name used for this task (e.g., "orch-lane-1") */
|
|
694
694
|
sessionName: string;
|
|
695
695
|
/** Whether .DONE file was found */
|
|
696
696
|
doneFileFound: boolean;
|
|
@@ -758,7 +758,7 @@ export interface LaneExecutionResult {
|
|
|
758
758
|
// ── Execution Constants ──────────────────────────────────────────────
|
|
759
759
|
|
|
760
760
|
/**
|
|
761
|
-
* Grace period (ms) after
|
|
761
|
+
* Grace period (ms) after a lane session exits before declaring failure.
|
|
762
762
|
* Allows time for .DONE file to be flushed to disk on slow filesystems.
|
|
763
763
|
*/
|
|
764
764
|
export const DONE_GRACE_MS = 5_000;
|
|
@@ -769,7 +769,7 @@ export const DONE_GRACE_MS = 5_000;
|
|
|
769
769
|
export const EXECUTION_POLL_INTERVAL_MS = 2_000;
|
|
770
770
|
|
|
771
771
|
/**
|
|
772
|
-
* Maximum retries for
|
|
772
|
+
* Maximum retries for legacy lane-session spawn failures.
|
|
773
773
|
* Only transient failures (session name collision) are retried.
|
|
774
774
|
*/
|
|
775
775
|
export const SESSION_SPAWN_RETRY_MAX = 2;
|
|
@@ -779,12 +779,12 @@ export const SESSION_SPAWN_RETRY_MAX = 2;
|
|
|
779
779
|
/**
|
|
780
780
|
* Error codes for lane execution failures.
|
|
781
781
|
*
|
|
782
|
-
* - EXEC_SPAWN_FAILED:
|
|
782
|
+
* - EXEC_SPAWN_FAILED: Lane session could not be created after retries
|
|
783
783
|
* - EXEC_TASK_FAILED: task completed without .DONE (non-zero exit)
|
|
784
784
|
* - EXEC_TASK_STALLED: STATUS.md unchanged for stall_timeout (handled by Step 3)
|
|
785
785
|
* - EXEC_TASK_STAGE_FAILED: git add failed for task files
|
|
786
786
|
* - EXEC_TASK_COMMIT_FAILED: git commit failed for staged task files
|
|
787
|
-
* - EXEC_TMUX_NOT_AVAILABLE: tmux binary not found
|
|
787
|
+
* - EXEC_TMUX_NOT_AVAILABLE: Legacy `tmux` binary not found (compat path)
|
|
788
788
|
* - EXEC_WORKTREE_MISSING: lane worktree path doesn't exist
|
|
789
789
|
*/
|
|
790
790
|
export type ExecutionErrorCode =
|
|
@@ -819,7 +819,7 @@ export class ExecutionError extends Error {
|
|
|
819
819
|
*
|
|
820
820
|
* Produced by `resolveTaskMonitorState()` from combining:
|
|
821
821
|
* - .DONE file presence
|
|
822
|
-
* -
|
|
822
|
+
* - Lane-session liveness
|
|
823
823
|
* - STATUS.md parse results
|
|
824
824
|
* - STATUS.md mtime for stall detection
|
|
825
825
|
*/
|
|
@@ -838,7 +838,7 @@ export interface TaskMonitorSnapshot {
|
|
|
838
838
|
totalChecked: number;
|
|
839
839
|
/** Total checkbox count across all steps */
|
|
840
840
|
totalItems: number;
|
|
841
|
-
/** Whether the
|
|
841
|
+
/** Whether the lane session is alive */
|
|
842
842
|
sessionAlive: boolean;
|
|
843
843
|
/** Whether the .DONE file was found */
|
|
844
844
|
doneFileFound: boolean;
|
|
@@ -864,9 +864,9 @@ export interface LaneMonitorSnapshot {
|
|
|
864
864
|
laneId: string;
|
|
865
865
|
/** Lane number (1-indexed) */
|
|
866
866
|
laneNumber: number;
|
|
867
|
-
/**
|
|
867
|
+
/** Lane session name (e.g., "orch-lane-1") */
|
|
868
868
|
sessionName: string;
|
|
869
|
-
/** Whether the
|
|
869
|
+
/** Whether the lane session is alive right now */
|
|
870
870
|
sessionAlive: boolean;
|
|
871
871
|
/** Current task being executed (null if lane is idle/complete) */
|
|
872
872
|
currentTaskId: string | null;
|
|
@@ -943,7 +943,7 @@ export interface MtimeTracker {
|
|
|
943
943
|
* - monitorLanes() runs as sibling async loop, can kill stalled sessions
|
|
944
944
|
* - executeWave() coordinates both and applies policy
|
|
945
945
|
* - Monitor's stall-kill does NOT conflict with executeLane() because
|
|
946
|
-
* executeLane() polls
|
|
946
|
+
* executeLane() polls session liveness and will see the killed session
|
|
947
947
|
*/
|
|
948
948
|
|
|
949
949
|
/**
|
|
@@ -1089,7 +1089,7 @@ export interface OrchBatchRuntimeState {
|
|
|
1089
1089
|
* Session registry entry for /orch-sessions command.
|
|
1090
1090
|
*/
|
|
1091
1091
|
export interface OrchestratorSessionEntry {
|
|
1092
|
-
/**
|
|
1092
|
+
/** Lane session name (e.g., "orch-lane-1") */
|
|
1093
1093
|
sessionName: string;
|
|
1094
1094
|
/** Lane ID (e.g., "lane-1") */
|
|
1095
1095
|
laneId: string;
|
|
@@ -1339,9 +1339,9 @@ export interface TransactionRecord {
|
|
|
1339
1339
|
/**
|
|
1340
1340
|
* Error codes for merge operations.
|
|
1341
1341
|
*
|
|
1342
|
-
* - MERGE_SPAWN_FAILED: Could not create
|
|
1342
|
+
* - MERGE_SPAWN_FAILED: Could not create merge-agent session
|
|
1343
1343
|
* - MERGE_TIMEOUT: Merge agent did not produce result within timeout
|
|
1344
|
-
* - MERGE_SESSION_DIED:
|
|
1344
|
+
* - MERGE_SESSION_DIED: Merge-agent session exited without writing result
|
|
1345
1345
|
* - MERGE_RESULT_INVALID: Result file exists but contains invalid JSON
|
|
1346
1346
|
* - MERGE_RESULT_MISSING_FIELDS: Result JSON missing required fields
|
|
1347
1347
|
* - MERGE_UNKNOWN_STATUS: Result has an unrecognized status value
|
|
@@ -1384,7 +1384,7 @@ export const MERGE_TIMEOUT_MS = 90 * 60 * 1000;
|
|
|
1384
1384
|
export const MERGE_POLL_INTERVAL_MS = 2_000;
|
|
1385
1385
|
|
|
1386
1386
|
/**
|
|
1387
|
-
* Grace period after
|
|
1387
|
+
* Grace period after a merge-agent session exits before declaring failure (ms).
|
|
1388
1388
|
* Allows for slow disk flush of the result file.
|
|
1389
1389
|
*/
|
|
1390
1390
|
export const MERGE_RESULT_GRACE_MS = 3_000;
|
|
@@ -1401,7 +1401,7 @@ export const MERGE_RESULT_READ_RETRIES = 3;
|
|
|
1401
1401
|
export const MERGE_RESULT_READ_RETRY_DELAY_MS = 1_000;
|
|
1402
1402
|
|
|
1403
1403
|
/**
|
|
1404
|
-
* Maximum retries for
|
|
1404
|
+
* Maximum retries for merge-agent session spawn.
|
|
1405
1405
|
*/
|
|
1406
1406
|
export const MERGE_SPAWN_RETRY_MAX = 2;
|
|
1407
1407
|
|
|
@@ -1442,7 +1442,7 @@ export const MERGE_HEALTH_WARNING_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes
|
|
|
1442
1442
|
export const MERGE_HEALTH_STUCK_THRESHOLD_MS = 20 * 60 * 1000; // 20 minutes
|
|
1443
1443
|
|
|
1444
1444
|
/**
|
|
1445
|
-
* Number of lines to capture from
|
|
1445
|
+
* Number of lines to capture from recent merge output snapshots
|
|
1446
1446
|
* for activity detection via snapshot comparison.
|
|
1447
1447
|
* @since TP-056
|
|
1448
1448
|
*/
|
|
@@ -1529,7 +1529,7 @@ export interface MergeSessionSnapshot {
|
|
|
1529
1529
|
* @since TP-056
|
|
1530
1530
|
*/
|
|
1531
1531
|
export interface MergeSessionHealthState {
|
|
1532
|
-
/**
|
|
1532
|
+
/** Merge session name */
|
|
1533
1533
|
sessionName: string;
|
|
1534
1534
|
/** Lane number this session belongs to */
|
|
1535
1535
|
laneNumber: number;
|
|
@@ -1899,7 +1899,7 @@ export interface EngineEvent {
|
|
|
1899
1899
|
|
|
1900
1900
|
// ── Merge health monitoring fields (TP-056) ──────────────────
|
|
1901
1901
|
|
|
1902
|
-
/**
|
|
1902
|
+
/** Merge session name (for merge_health_* events) */
|
|
1903
1903
|
sessionName?: string;
|
|
1904
1904
|
/** Merge health status classification (for merge_health_* events) */
|
|
1905
1905
|
healthStatus?: MergeHealthStatus;
|
|
@@ -2223,7 +2223,7 @@ export interface OrchDashboardViewModel {
|
|
|
2223
2223
|
elapsed: string; // e.g., "2m 14s"
|
|
2224
2224
|
summary: OrchSummaryCounts;
|
|
2225
2225
|
laneCards: OrchLaneCardData[];
|
|
2226
|
-
attachHint: string; // e.g., "
|
|
2226
|
+
attachHint: string; // e.g., "Attach via the current runtime session tool"
|
|
2227
2227
|
errors: string[];
|
|
2228
2228
|
failurePolicy: string | null; // e.g., "stop-wave" if stopped by policy
|
|
2229
2229
|
}
|
|
@@ -2457,7 +2457,7 @@ export interface PersistedTaskRecord {
|
|
|
2457
2457
|
taskId: string;
|
|
2458
2458
|
/** Lane number the task was assigned to (1-indexed) */
|
|
2459
2459
|
laneNumber: number;
|
|
2460
|
-
/**
|
|
2460
|
+
/** Lane session name used (e.g., "orch-lane-1") */
|
|
2461
2461
|
sessionName: string;
|
|
2462
2462
|
/** Current task status */
|
|
2463
2463
|
status: LaneTaskStatus;
|
|
@@ -2574,7 +2574,7 @@ export interface PersistedSegmentRecord {
|
|
|
2574
2574
|
status: PersistedSegmentStatus;
|
|
2575
2575
|
/** Lane ID the segment executed on (e.g., "lane-1"), empty if not yet assigned */
|
|
2576
2576
|
laneId: string;
|
|
2577
|
-
/**
|
|
2577
|
+
/** Lane session name used for this segment */
|
|
2578
2578
|
sessionName: string;
|
|
2579
2579
|
/** Absolute path to the worktree used for this segment */
|
|
2580
2580
|
worktreePath: string;
|
|
@@ -2627,8 +2627,8 @@ export interface PersistedLaneRecord {
|
|
|
2627
2627
|
laneNumber: number;
|
|
2628
2628
|
/** Lane identifier (e.g., "lane-1") */
|
|
2629
2629
|
laneId: string;
|
|
2630
|
-
/**
|
|
2631
|
-
|
|
2630
|
+
/** Lane session identifier (e.g., "orch-lane-1") */
|
|
2631
|
+
laneSessionId: string;
|
|
2632
2632
|
/** Absolute path to the lane's worktree directory */
|
|
2633
2633
|
worktreePath: string;
|
|
2634
2634
|
/** Git branch name checked out in the worktree */
|
|
@@ -2809,7 +2809,7 @@ export interface PersistedBatchState {
|
|
|
2809
2809
|
* - RESUME_INVALID_STATE: State file exists but cannot be parsed/validated
|
|
2810
2810
|
* - RESUME_SCHEMA_MISMATCH: State file has incompatible schema version
|
|
2811
2811
|
* - RESUME_PHASE_NOT_RESUMABLE: Persisted phase does not allow resume
|
|
2812
|
-
* - RESUME_TMUX_UNAVAILABLE:
|
|
2812
|
+
* - RESUME_TMUX_UNAVAILABLE: Legacy session backend is unavailable for reconnection
|
|
2813
2813
|
* - RESUME_EXECUTION_FAILED: Resume reconciliation succeeded but execution failed
|
|
2814
2814
|
*/
|
|
2815
2815
|
export type ResumeErrorCode =
|
|
@@ -2834,7 +2834,7 @@ export class ResumeError extends Error {
|
|
|
2834
2834
|
/**
|
|
2835
2835
|
* Result of reconciling a single task's persisted state against live signals.
|
|
2836
2836
|
*
|
|
2837
|
-
* Combines persisted status,
|
|
2837
|
+
* Combines persisted status, lane-session liveness, and .DONE file presence
|
|
2838
2838
|
* into a deterministic action for the resume engine.
|
|
2839
2839
|
*
|
|
2840
2840
|
* Reconciliation precedence (highest → lowest):
|
|
@@ -2850,7 +2850,7 @@ export interface ReconciledTaskState {
|
|
|
2850
2850
|
persistedStatus: LaneTaskStatus;
|
|
2851
2851
|
/** Reconciled live status after checking signals */
|
|
2852
2852
|
liveStatus: LaneTaskStatus;
|
|
2853
|
-
/** Whether the
|
|
2853
|
+
/** Whether the lane session is alive right now */
|
|
2854
2854
|
sessionAlive: boolean;
|
|
2855
2855
|
/** Whether the .DONE file was found */
|
|
2856
2856
|
doneFileFound: boolean;
|
|
@@ -2913,9 +2913,9 @@ export type AbortMode = "graceful" | "hard";
|
|
|
2913
2913
|
/**
|
|
2914
2914
|
* Error codes for abort operations.
|
|
2915
2915
|
*
|
|
2916
|
-
* - ABORT_TMUX_LIST_FAILED: Could not list
|
|
2916
|
+
* - ABORT_TMUX_LIST_FAILED: Could not list legacy session records
|
|
2917
2917
|
* - ABORT_WRAPUP_WRITE_FAILED: Failed to write wrap-up signal file(s)
|
|
2918
|
-
* - ABORT_KILL_FAILED: Failed to kill one or more
|
|
2918
|
+
* - ABORT_KILL_FAILED: Failed to kill one or more lane sessions
|
|
2919
2919
|
* - ABORT_STATE_DELETE_FAILED: Failed to delete batch-state.json
|
|
2920
2920
|
*/
|
|
2921
2921
|
export type AbortErrorCode =
|
|
@@ -2928,7 +2928,7 @@ export type AbortErrorCode =
|
|
|
2928
2928
|
* Per-lane result from an abort operation.
|
|
2929
2929
|
*/
|
|
2930
2930
|
export interface AbortLaneResult {
|
|
2931
|
-
/**
|
|
2931
|
+
/** Lane session name */
|
|
2932
2932
|
sessionName: string;
|
|
2933
2933
|
/** Lane ID (e.g., "lane-1") or "unknown" */
|
|
2934
2934
|
laneId: string;
|
|
@@ -2983,7 +2983,7 @@ export type AbortActionStep =
|
|
|
2983
2983
|
* Target session with enrichment from persisted state.
|
|
2984
2984
|
*/
|
|
2985
2985
|
export interface AbortTargetSession {
|
|
2986
|
-
/**
|
|
2986
|
+
/** Lane session name */
|
|
2987
2987
|
sessionName: string;
|
|
2988
2988
|
/** Lane ID from persisted state or "unknown" */
|
|
2989
2989
|
laneId: string;
|
|
@@ -3495,12 +3495,12 @@ export interface WriteMailboxMessageOpts {
|
|
|
3495
3495
|
|
|
3496
3496
|
// ── Runtime V2 Contracts (TP-102) ────────────────────────────────────
|
|
3497
3497
|
//
|
|
3498
|
-
// These types define the foundational contracts for
|
|
3498
|
+
// These types define the foundational contracts for backend-neutral Runtime V2
|
|
3499
3499
|
// architecture. They are additive — existing runtime paths continue to work
|
|
3500
3500
|
// while Runtime V2 is incrementally adopted.
|
|
3501
3501
|
//
|
|
3502
3502
|
// Design principles:
|
|
3503
|
-
// 1. Agent identity is a stable runtime ID, not a
|
|
3503
|
+
// 1. Agent identity is a stable runtime ID, not a legacy session name.
|
|
3504
3504
|
// 2. Packet-path authority is explicit, never inferred from cwd.
|
|
3505
3505
|
// 3. Process ownership uses a registry, not terminal session discovery.
|
|
3506
3506
|
// 4. Normalized events flow directly from child to parent.
|
|
@@ -3546,13 +3546,13 @@ export const TERMINAL_AGENT_STATUSES: ReadonlySet<RuntimeAgentStatus> = new Set(
|
|
|
3546
3546
|
/**
|
|
3547
3547
|
* Stable agent identity for Runtime V2.
|
|
3548
3548
|
*
|
|
3549
|
-
* This replaces
|
|
3549
|
+
* This replaces legacy session names as the canonical identifier for a
|
|
3550
3550
|
* spawned agent process. The string format is deliberately compatible
|
|
3551
3551
|
* with existing naming conventions (e.g., "orch-henrylach-lane-1-worker")
|
|
3552
3552
|
* to minimize churn in supervisor tools, dashboard, and mailbox addressing.
|
|
3553
3553
|
*
|
|
3554
3554
|
* The key semantic change: this is a **runtime process ID**, not a terminal
|
|
3555
|
-
* session
|
|
3555
|
+
* session label. Code must not assume terminal-session probes apply to RuntimeAgentId.
|
|
3556
3556
|
*
|
|
3557
3557
|
* @since TP-102
|
|
3558
3558
|
*/
|
|
@@ -3645,7 +3645,7 @@ export interface ExecutionUnit {
|
|
|
3645
3645
|
* the agent is considered visible. Updated on status transitions and
|
|
3646
3646
|
* cleaned up on batch completion.
|
|
3647
3647
|
*
|
|
3648
|
-
* Replaces
|
|
3648
|
+
* Replaces legacy session discovery as the source of truth for agent
|
|
3649
3649
|
* liveness, identity, and attribution.
|
|
3650
3650
|
*
|
|
3651
3651
|
* File location: `.pi/runtime/{batchId}/agents/{agentId}/manifest.json`
|
|
@@ -3684,7 +3684,7 @@ export interface RuntimeAgentManifest {
|
|
|
3684
3684
|
*
|
|
3685
3685
|
* Contains all active and recently-exited agents for one batch.
|
|
3686
3686
|
* The authoritative source of truth for which agents exist, replacing
|
|
3687
|
-
*
|
|
3687
|
+
* legacy session discovery.
|
|
3688
3688
|
*
|
|
3689
3689
|
* File location: `.pi/runtime/{batchId}/registry.json`
|
|
3690
3690
|
*
|
|
@@ -3918,7 +3918,7 @@ export function runtimeRegistryPath(stateRoot: string, batchId: string): string
|
|
|
3918
3918
|
*
|
|
3919
3919
|
* Produces IDs compatible with the existing naming convention
|
|
3920
3920
|
* (e.g., "orch-henrylach-lane-1-worker") while semantically
|
|
3921
|
-
* decoupling them from
|
|
3921
|
+
* decoupling them from legacy session names.
|
|
3922
3922
|
*
|
|
3923
3923
|
* @param prefix - Operator/batch prefix (e.g., "orch-henrylach")
|
|
3924
3924
|
* @param laneNumber - Lane number (null for merge agents)
|
|
@@ -488,7 +488,7 @@ export function generateLaneId(laneLocalNumber: number, repoId?: string): string
|
|
|
488
488
|
}
|
|
489
489
|
|
|
490
490
|
/**
|
|
491
|
-
* Generate a
|
|
491
|
+
* Generate a lane session identifier for a lane.
|
|
492
492
|
*
|
|
493
493
|
* Includes the operator identifier (`opId`) for collision resistance
|
|
494
494
|
* across concurrent operators on the same machine.
|
|
@@ -496,20 +496,20 @@ export function generateLaneId(laneLocalNumber: number, repoId?: string): string
|
|
|
496
496
|
* - Repo mode: `"{prefix}-{opId}-lane-{N}"` — operator-scoped
|
|
497
497
|
* - Workspace mode: `"{prefix}-{opId}-{repoId}-lane-{N}"` — operator + repo scoped
|
|
498
498
|
*
|
|
499
|
-
*
|
|
499
|
+
* Session identifiers must not contain periods or colons. Both `opId`
|
|
500
500
|
* and `repoId` are assumed to be sanitized identifiers (alphanumeric
|
|
501
501
|
* + hyphens only).
|
|
502
502
|
*
|
|
503
|
-
* @param
|
|
503
|
+
* @param sessionPrefix - Session prefix from config (e.g., "orch")
|
|
504
504
|
* @param laneLocalNumber - Lane number within the repo group (1-indexed)
|
|
505
505
|
* @param opId - Operator identifier (sanitized, e.g., "henrylach")
|
|
506
506
|
* @param repoId - Repo identifier (undefined in repo mode)
|
|
507
507
|
*/
|
|
508
|
-
export function
|
|
508
|
+
export function generateLaneSessionId(sessionPrefix: string, laneLocalNumber: number, opId: string, repoId?: string): string {
|
|
509
509
|
if (repoId) {
|
|
510
|
-
return `${
|
|
510
|
+
return `${sessionPrefix}-${opId}-${repoId}-lane-${laneLocalNumber}`;
|
|
511
511
|
}
|
|
512
|
-
return `${
|
|
512
|
+
return `${sessionPrefix}-${opId}-lane-${laneLocalNumber}`;
|
|
513
513
|
}
|
|
514
514
|
|
|
515
515
|
|
|
@@ -1069,8 +1069,9 @@ export function validateAllocationInputs(
|
|
|
1069
1069
|
* newly-created lanes in this call are rolled back.
|
|
1070
1070
|
*
|
|
1071
1071
|
* 4. **Build AllocatedLane[]** — each lane gets repo-aware `laneId` and
|
|
1072
|
-
* `
|
|
1073
|
-
* In repo mode: `"lane-1"`,
|
|
1072
|
+
* `laneSessionId`. In workspace mode: `"api/lane-1"`, `"orch-api-lane-1"`.
|
|
1073
|
+
* In repo mode: `"lane-1"`,
|
|
1074
|
+
* `"orch-lane-1"` (unchanged).
|
|
1074
1075
|
*
|
|
1075
1076
|
* **Determinism guarantee:** Given the same `waveTasks`, `pending`, and `config`,
|
|
1076
1077
|
* this function always produces the same lane assignments and task ordering.
|
|
@@ -1279,7 +1280,7 @@ export function allocateLanes(
|
|
|
1279
1280
|
}
|
|
1280
1281
|
|
|
1281
1282
|
// ── Stage 4: Build AllocatedLane[] from assignments + worktrees ─
|
|
1282
|
-
const
|
|
1283
|
+
const sessionPrefix = config.orchestrator.sessionPrefix || "orch";
|
|
1283
1284
|
const opId = resolveOperatorId(config);
|
|
1284
1285
|
const strategy = config.assignment.strategy as AllocatedLane["strategy"];
|
|
1285
1286
|
const sizeWeights = config.assignment.size_weights;
|
|
@@ -1328,10 +1329,11 @@ export function allocateLanes(
|
|
|
1328
1329
|
0,
|
|
1329
1330
|
);
|
|
1330
1331
|
|
|
1332
|
+
const laneSessionId = generateLaneSessionId(sessionPrefix, entry.localLane, opId, entry.repoId);
|
|
1331
1333
|
allocatedLanes.push({
|
|
1332
1334
|
laneNumber: entry.globalLane,
|
|
1333
1335
|
laneId: generateLaneId(entry.localLane, entry.repoId),
|
|
1334
|
-
|
|
1336
|
+
laneSessionId,
|
|
1335
1337
|
worktreePath: wt.path,
|
|
1336
1338
|
branch: wt.branch,
|
|
1337
1339
|
tasks: allocatedTasks,
|
|
@@ -1654,13 +1654,11 @@ export function meetsMinVersion(actual: [number, number], minimum: [number, numb
|
|
|
1654
1654
|
* - git worktree support
|
|
1655
1655
|
* - pi availability
|
|
1656
1656
|
*
|
|
1657
|
-
*
|
|
1658
|
-
* -
|
|
1659
|
-
* - tmux functional (can create/destroy sessions)
|
|
1657
|
+
* Compatibility checks:
|
|
1658
|
+
* - Runtime backend mode visibility (subprocess-only)
|
|
1660
1659
|
*/
|
|
1661
1660
|
export function runPreflight(config: OrchestratorConfig, repoRoot?: string): PreflightResult {
|
|
1662
1661
|
const checks: PreflightCheck[] = [];
|
|
1663
|
-
const tmuxRequired = config.orchestrator.spawn_mode === "tmux";
|
|
1664
1662
|
|
|
1665
1663
|
// ── Git version ──────────────────────────────────────────────
|
|
1666
1664
|
const gitResult = execCheck("git --version");
|
|
@@ -1706,68 +1704,12 @@ export function runPreflight(config: OrchestratorConfig, repoRoot?: string): Pre
|
|
|
1706
1704
|
: "Workspace root is not a git repo. Check workspace config repo paths.",
|
|
1707
1705
|
});
|
|
1708
1706
|
|
|
1709
|
-
// ──
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
checks.push({
|
|
1716
|
-
name: "tmux",
|
|
1717
|
-
status: "pass",
|
|
1718
|
-
message: `TMUX ${versionStr} available`,
|
|
1719
|
-
});
|
|
1720
|
-
} else {
|
|
1721
|
-
checks.push({
|
|
1722
|
-
name: "tmux",
|
|
1723
|
-
status: tmuxRequired ? "fail" : "warn",
|
|
1724
|
-
message: `TMUX ${versionStr} found, but 2.6+ required`,
|
|
1725
|
-
hint: "Upgrade TMUX: https://github.com/tmux/tmux/wiki/Installing",
|
|
1726
|
-
});
|
|
1727
|
-
}
|
|
1728
|
-
} else {
|
|
1729
|
-
checks.push({
|
|
1730
|
-
name: "tmux",
|
|
1731
|
-
status: tmuxRequired ? "fail" : "warn",
|
|
1732
|
-
message: "TMUX not found",
|
|
1733
|
-
hint: tmuxRequired
|
|
1734
|
-
? "Install TMUX (required for tmux spawn_mode):\n" +
|
|
1735
|
-
" Linux: sudo apt install tmux\n" +
|
|
1736
|
-
" macOS: brew install tmux\n" +
|
|
1737
|
-
" Windows (MSYS2): pacman -S tmux\n" +
|
|
1738
|
-
" Or set spawn_mode: subprocess in .pi/task-orchestrator.yaml"
|
|
1739
|
-
: "TMUX not required for subprocess spawn_mode. Install for drill-down observability:\n" +
|
|
1740
|
-
" Linux: sudo apt install tmux | macOS: brew install tmux",
|
|
1741
|
-
});
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
// ── TMUX functional (only if tmux was found) ─────────────────
|
|
1745
|
-
if (tmuxResult.ok) {
|
|
1746
|
-
const testSession = "orch-preflight-test";
|
|
1747
|
-
const tmuxFunctional = execCheck(`tmux new-session -d -s ${testSession} "exit 0"`);
|
|
1748
|
-
if (tmuxFunctional.ok) {
|
|
1749
|
-
// Clean up test session
|
|
1750
|
-
execCheck(`tmux kill-session -t ${testSession}`);
|
|
1751
|
-
checks.push({
|
|
1752
|
-
name: "tmux-functional",
|
|
1753
|
-
status: "pass",
|
|
1754
|
-
message: "TMUX can create sessions",
|
|
1755
|
-
});
|
|
1756
|
-
} else {
|
|
1757
|
-
checks.push({
|
|
1758
|
-
name: "tmux-functional",
|
|
1759
|
-
status: tmuxRequired ? "fail" : "warn",
|
|
1760
|
-
message: "TMUX installed but cannot create sessions",
|
|
1761
|
-
hint: "Check TMUX server status. Try: tmux new-session -d -s test 'echo ok'",
|
|
1762
|
-
});
|
|
1763
|
-
}
|
|
1764
|
-
} else {
|
|
1765
|
-
checks.push({
|
|
1766
|
-
name: "tmux-functional",
|
|
1767
|
-
status: tmuxRequired ? "fail" : "warn",
|
|
1768
|
-
message: "Skipped — TMUX not installed",
|
|
1769
|
-
});
|
|
1770
|
-
}
|
|
1707
|
+
// ── Runtime backend contract (Runtime V2) ─────────────────────
|
|
1708
|
+
checks.push({
|
|
1709
|
+
name: "runtime-backend",
|
|
1710
|
+
status: "pass",
|
|
1711
|
+
message: `Runtime V2 subprocess backend active (configured spawn_mode: ${config.orchestrator.spawn_mode})`,
|
|
1712
|
+
});
|
|
1771
1713
|
|
|
1772
1714
|
// ── Pi availability ──────────────────────────────────────────
|
|
1773
1715
|
const piResult = execCheck("pi --version");
|
package/package.json
CHANGED
|
@@ -27,12 +27,11 @@ orchestrator:
|
|
|
27
27
|
# Batch ID format used in branch names and logs.
|
|
28
28
|
batch_id_format: "timestamp"
|
|
29
29
|
|
|
30
|
-
#
|
|
31
|
-
# "subprocess" = headless execution without tmux dependency
|
|
30
|
+
# Runtime V2 execution backend (subprocess-only)
|
|
32
31
|
spawn_mode: "subprocess"
|
|
33
32
|
|
|
34
|
-
# Prefix for
|
|
35
|
-
|
|
33
|
+
# Prefix for orchestrator session names.
|
|
34
|
+
session_prefix: "orch"
|
|
36
35
|
|
|
37
36
|
# Optional operator identifier for team-scale collision resistance.
|
|
38
37
|
# Auto-detected from OS username if empty. Set explicitly in CI or
|