taskplane 0.6.0 → 0.7.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/README.md +6 -5
- package/dashboard/public/app.js +227 -0
- package/dashboard/public/index.html +19 -0
- package/dashboard/public/style.css +319 -0
- package/dashboard/server.cjs +219 -1
- package/extensions/task-runner.ts +36 -7
- package/extensions/taskplane/config-loader.ts +7 -1
- package/extensions/taskplane/config-schema.ts +19 -2
- package/extensions/taskplane/config.ts +23 -1
- package/extensions/taskplane/engine.ts +913 -46
- package/extensions/taskplane/execution.ts +1 -0
- package/extensions/taskplane/extension.ts +975 -54
- package/extensions/taskplane/formatting.ts +713 -712
- package/extensions/taskplane/index.ts +1 -0
- package/extensions/taskplane/merge.ts +236 -48
- package/extensions/taskplane/messages.ts +10 -0
- package/extensions/taskplane/persistence.ts +183 -3
- package/extensions/taskplane/resume.ts +243 -24
- package/extensions/taskplane/settings-tui.ts +10 -3
- package/extensions/taskplane/supervisor-primer.md +626 -0
- package/extensions/taskplane/supervisor.ts +3659 -0
- package/extensions/taskplane/types.ts +330 -3
- package/package.json +1 -1
|
@@ -18,8 +18,8 @@ export interface OrchestratorConfig {
|
|
|
18
18
|
tmux_prefix: string;
|
|
19
19
|
/** Optional operator identifier. Auto-detected from OS username if empty. */
|
|
20
20
|
operator_id: string;
|
|
21
|
-
/** How completed batches are integrated. manual = user runs /orch-integrate. auto =
|
|
22
|
-
integration: "manual" | "auto";
|
|
21
|
+
/** How completed batches are integrated. manual = user runs /orch-integrate. supervised = supervisor proposes plan, asks confirmation. auto = supervisor executes without asking. */
|
|
22
|
+
integration: "manual" | "supervised" | "auto";
|
|
23
23
|
};
|
|
24
24
|
dependencies: {
|
|
25
25
|
source: "prompt" | "agent";
|
|
@@ -838,6 +838,17 @@ export interface WaveExecutionResult {
|
|
|
838
838
|
finalMonitorState: MonitorState | null;
|
|
839
839
|
/** Allocated lanes used in this wave (preserved for merge and cleanup) */
|
|
840
840
|
allocatedLanes: AllocatedLane[];
|
|
841
|
+
/**
|
|
842
|
+
* Structured allocation error when lane provisioning failed.
|
|
843
|
+
* Null when allocation succeeded or wave failed for other reasons.
|
|
844
|
+
* Used by Tier 0 to detect stale worktree failures and retry.
|
|
845
|
+
* @since TP-039
|
|
846
|
+
*/
|
|
847
|
+
allocationError?: {
|
|
848
|
+
code: AllocationErrorCode;
|
|
849
|
+
message: string;
|
|
850
|
+
details?: string;
|
|
851
|
+
} | null;
|
|
841
852
|
}
|
|
842
853
|
|
|
843
854
|
|
|
@@ -853,7 +864,7 @@ export interface WaveExecutionResult {
|
|
|
853
864
|
* → paused (via /orch-pause)
|
|
854
865
|
* Any active state → idle (via cleanup after completion/failure)
|
|
855
866
|
*/
|
|
856
|
-
export type OrchBatchPhase = "idle" | "planning" | "executing" | "merging" | "paused" | "stopped" | "completed" | "failed";
|
|
867
|
+
export type OrchBatchPhase = "idle" | "launching" | "planning" | "executing" | "merging" | "paused" | "stopped" | "completed" | "failed";
|
|
857
868
|
|
|
858
869
|
/**
|
|
859
870
|
* Runtime state for a batch execution.
|
|
@@ -1244,6 +1255,19 @@ export const MERGE_RESULT_READ_RETRY_DELAY_MS = 1_000;
|
|
|
1244
1255
|
*/
|
|
1245
1256
|
export const MERGE_SPAWN_RETRY_MAX = 2;
|
|
1246
1257
|
|
|
1258
|
+
/**
|
|
1259
|
+
* Maximum retries for merge agent timeout (TP-038).
|
|
1260
|
+
*
|
|
1261
|
+
* When a merge agent times out, the orchestrator retries with 2× the
|
|
1262
|
+
* previous timeout. This allows recovery from transient slowness without
|
|
1263
|
+
* operator intervention.
|
|
1264
|
+
*
|
|
1265
|
+
* Retry 0: original timeout (e.g., 10 min)
|
|
1266
|
+
* Retry 1: 2× original (e.g., 20 min)
|
|
1267
|
+
* Retry 2: 4× original (e.g., 40 min)
|
|
1268
|
+
*/
|
|
1269
|
+
export const MERGE_TIMEOUT_MAX_RETRIES = 2;
|
|
1270
|
+
|
|
1247
1271
|
|
|
1248
1272
|
// ── Merge Retry Policy Matrix (TP-033 Step 2) ───────────────────────
|
|
1249
1273
|
|
|
@@ -1346,6 +1370,283 @@ export const MERGE_FAILURE_CLASSIFICATIONS: readonly MergeFailureClassification[
|
|
|
1346
1370
|
"git_lock_file",
|
|
1347
1371
|
] as const;
|
|
1348
1372
|
|
|
1373
|
+
|
|
1374
|
+
// ── Tier 0 Watchdog Recovery Types (TP-039) ──────────────────────────
|
|
1375
|
+
|
|
1376
|
+
/**
|
|
1377
|
+
* Tier 0 recovery pattern identifiers.
|
|
1378
|
+
*
|
|
1379
|
+
* Each pattern corresponds to a failure class that the engine can
|
|
1380
|
+
* handle automatically without supervisor intervention.
|
|
1381
|
+
*
|
|
1382
|
+
* @since TP-039
|
|
1383
|
+
*/
|
|
1384
|
+
export type Tier0RecoveryPattern =
|
|
1385
|
+
| "worker_crash"
|
|
1386
|
+
| "stale_worktree"
|
|
1387
|
+
| "cleanup_gate";
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* Exit classifications that are eligible for automatic Tier 0 retry.
|
|
1391
|
+
*
|
|
1392
|
+
* These are transient failures where re-running the task has a reasonable
|
|
1393
|
+
* chance of success. Classifications NOT in this set (e.g., user_killed,
|
|
1394
|
+
* stall_timeout, context_overflow) indicate persistent problems that
|
|
1395
|
+
* won't be fixed by retrying.
|
|
1396
|
+
*
|
|
1397
|
+
* @since TP-039
|
|
1398
|
+
*/
|
|
1399
|
+
export const TIER0_RETRYABLE_CLASSIFICATIONS: ReadonlySet<string> = new Set([
|
|
1400
|
+
"api_error",
|
|
1401
|
+
"process_crash",
|
|
1402
|
+
"session_vanished",
|
|
1403
|
+
]);
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* Retry budget for Tier 0 recovery patterns.
|
|
1407
|
+
*
|
|
1408
|
+
* Defines max retries, cooldown between attempts, and backoff
|
|
1409
|
+
* multiplier for each pattern. Values from spec §5.3.
|
|
1410
|
+
*
|
|
1411
|
+
* @since TP-039
|
|
1412
|
+
*/
|
|
1413
|
+
export interface Tier0RetryBudget {
|
|
1414
|
+
/** Maximum number of retry attempts */
|
|
1415
|
+
maxRetries: number;
|
|
1416
|
+
/** Cooldown delay between retries in milliseconds */
|
|
1417
|
+
cooldownMs: number;
|
|
1418
|
+
/** Multiplier applied to cooldown on each subsequent retry */
|
|
1419
|
+
backoffMultiplier: number;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* Centralized retry budgets for Tier 0 recovery patterns.
|
|
1424
|
+
*
|
|
1425
|
+
* These are the defaults from spec §5.3. They are NOT configurable
|
|
1426
|
+
* via user config in Tier 0 — the supervisor (Tier 1) can override
|
|
1427
|
+
* them in future iterations.
|
|
1428
|
+
*
|
|
1429
|
+
* @since TP-039
|
|
1430
|
+
*/
|
|
1431
|
+
export const TIER0_RETRY_BUDGETS: Readonly<Record<Tier0RecoveryPattern, Tier0RetryBudget>> = {
|
|
1432
|
+
worker_crash: {
|
|
1433
|
+
maxRetries: 1,
|
|
1434
|
+
cooldownMs: 5_000,
|
|
1435
|
+
backoffMultiplier: 1.0,
|
|
1436
|
+
},
|
|
1437
|
+
stale_worktree: {
|
|
1438
|
+
maxRetries: 1,
|
|
1439
|
+
cooldownMs: 2_000,
|
|
1440
|
+
backoffMultiplier: 1.0,
|
|
1441
|
+
},
|
|
1442
|
+
cleanup_gate: {
|
|
1443
|
+
maxRetries: 1,
|
|
1444
|
+
cooldownMs: 2_000,
|
|
1445
|
+
backoffMultiplier: 1.0,
|
|
1446
|
+
},
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* All Tier 0 escalation-eligible pattern identifiers.
|
|
1451
|
+
*
|
|
1452
|
+
* Extends `Tier0RecoveryPattern` with `merge_timeout` so that
|
|
1453
|
+
* `EscalationContext` can describe escalations from every exhaustion
|
|
1454
|
+
* path, including the merge retry loop (which uses its own retry
|
|
1455
|
+
* matrix but still triggers Tier 0 escalation on exhaustion).
|
|
1456
|
+
*
|
|
1457
|
+
* @since TP-039
|
|
1458
|
+
*/
|
|
1459
|
+
export type Tier0EscalationPattern = Tier0RecoveryPattern | "merge_timeout";
|
|
1460
|
+
|
|
1461
|
+
/**
|
|
1462
|
+
* Context payload emitted when Tier 0 retries are exhausted and the
|
|
1463
|
+
* engine must escalate to the supervisor (future TP-041).
|
|
1464
|
+
*
|
|
1465
|
+
* This is the structured data that a Tier 1 supervisor agent uses to
|
|
1466
|
+
* decide what to do next. In Tier 0, escalation simply falls through
|
|
1467
|
+
* to the existing pause behaviour.
|
|
1468
|
+
*
|
|
1469
|
+
* @since TP-039
|
|
1470
|
+
*/
|
|
1471
|
+
export interface EscalationContext {
|
|
1472
|
+
/** Which recovery pattern was attempted */
|
|
1473
|
+
pattern: Tier0EscalationPattern;
|
|
1474
|
+
/** Number of retry attempts that were made (1-based) */
|
|
1475
|
+
attempts: number;
|
|
1476
|
+
/** Maximum attempts that were allowed */
|
|
1477
|
+
maxAttempts: number;
|
|
1478
|
+
/** Human-readable last error / failure reason */
|
|
1479
|
+
lastError: string;
|
|
1480
|
+
/** Task IDs affected by this failure */
|
|
1481
|
+
affectedTasks: string[];
|
|
1482
|
+
/** Suggested remediation for an operator or supervisor */
|
|
1483
|
+
suggestion: string;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* Scope key prefix for Tier 0 (non-merge) retry counters.
|
|
1488
|
+
*
|
|
1489
|
+
* Format: `t0:{pattern}:{taskId}:w{waveIndex}`
|
|
1490
|
+
* This namespace prevents collisions with merge retry scope keys
|
|
1491
|
+
* (which use `{taskId}:w{waveIndex}:l{laneNumber}`).
|
|
1492
|
+
*
|
|
1493
|
+
* @since TP-039
|
|
1494
|
+
*/
|
|
1495
|
+
export function tier0ScopeKey(pattern: Tier0RecoveryPattern, taskId: string, waveIndex: number): string {
|
|
1496
|
+
return `t0:${pattern}:${taskId}:w${waveIndex}`;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* Wave-level scope key for Tier 0 patterns that operate at wave granularity
|
|
1501
|
+
* (stale_worktree, cleanup_gate).
|
|
1502
|
+
*
|
|
1503
|
+
* Format: `t0:{pattern}:w{waveIndex}`
|
|
1504
|
+
*
|
|
1505
|
+
* @since TP-039
|
|
1506
|
+
*/
|
|
1507
|
+
export function tier0WaveScopeKey(pattern: Tier0RecoveryPattern, waveIndex: number): string {
|
|
1508
|
+
return `t0:${pattern}:w${waveIndex}`;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
// ── Engine Event Types (TP-040) ──────────────────────────────────────
|
|
1512
|
+
|
|
1513
|
+
/**
|
|
1514
|
+
* Engine lifecycle event types emitted during batch execution.
|
|
1515
|
+
*
|
|
1516
|
+
* These events are the primary coordination mechanism between the
|
|
1517
|
+
* non-blocking engine and external consumers (supervisor agent,
|
|
1518
|
+
* dashboard, command handlers).
|
|
1519
|
+
*
|
|
1520
|
+
* Event semantics (from spec §7.3):
|
|
1521
|
+
* - `wave_start` — Wave execution begins
|
|
1522
|
+
* - `task_complete` — Task .DONE detected (succeeded)
|
|
1523
|
+
* - `task_failed` — Task failed or stalled
|
|
1524
|
+
* - `merge_start` — Wave merge begins
|
|
1525
|
+
* - `merge_success` — Merge and verification pass
|
|
1526
|
+
* - `merge_failed` — Merge or verification fails
|
|
1527
|
+
* - `batch_complete` — All waves done (terminal)
|
|
1528
|
+
* - `batch_paused` — Batch paused (failure or manual)
|
|
1529
|
+
*
|
|
1530
|
+
* Tier 0 recovery events (`tier0_recovery_attempt`, `tier0_recovery_success`,
|
|
1531
|
+
* `tier0_recovery_exhausted`, `tier0_escalation`) continue to use the
|
|
1532
|
+
* existing `Tier0EventType` from persistence.ts and share the same JSONL
|
|
1533
|
+
* file. Engine events extend the same stream with lifecycle context.
|
|
1534
|
+
*
|
|
1535
|
+
* @since TP-040
|
|
1536
|
+
*/
|
|
1537
|
+
export type EngineEventType =
|
|
1538
|
+
| "wave_start"
|
|
1539
|
+
| "task_complete"
|
|
1540
|
+
| "task_failed"
|
|
1541
|
+
| "merge_start"
|
|
1542
|
+
| "merge_success"
|
|
1543
|
+
| "merge_failed"
|
|
1544
|
+
| "batch_complete"
|
|
1545
|
+
| "batch_paused";
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* Structured engine event written to `.pi/supervisor/events.jsonl`.
|
|
1549
|
+
*
|
|
1550
|
+
* Shares the same JSONL file as Tier 0 events, with a consistent
|
|
1551
|
+
* base payload (`timestamp`, `batchId`, `waveIndex`) for uniform
|
|
1552
|
+
* consumption by the supervisor agent.
|
|
1553
|
+
*
|
|
1554
|
+
* Design: follows reviewer suggestion (R001) to use a shared base
|
|
1555
|
+
* payload and extend the existing event-writing infrastructure rather
|
|
1556
|
+
* than introducing a parallel writer.
|
|
1557
|
+
*
|
|
1558
|
+
* @since TP-040
|
|
1559
|
+
*/
|
|
1560
|
+
export interface EngineEvent {
|
|
1561
|
+
/** ISO 8601 timestamp */
|
|
1562
|
+
timestamp: string;
|
|
1563
|
+
/** Engine event type */
|
|
1564
|
+
type: EngineEventType;
|
|
1565
|
+
/** Batch identifier */
|
|
1566
|
+
batchId: string;
|
|
1567
|
+
/** Wave index (0-based, -1 if not wave-scoped) */
|
|
1568
|
+
waveIndex: number;
|
|
1569
|
+
/** Current batch phase at event emission time */
|
|
1570
|
+
phase: OrchBatchPhase;
|
|
1571
|
+
|
|
1572
|
+
// ── Event-specific fields (all optional) ─────────────────────
|
|
1573
|
+
|
|
1574
|
+
/** Task IDs in the wave (for wave_start) */
|
|
1575
|
+
taskIds?: string[];
|
|
1576
|
+
/** Number of lanes used (for wave_start, merge_start) */
|
|
1577
|
+
laneCount?: number;
|
|
1578
|
+
/** Task ID (for task_complete, task_failed) */
|
|
1579
|
+
taskId?: string;
|
|
1580
|
+
/** Task execution duration in milliseconds (for task_complete, task_failed) */
|
|
1581
|
+
durationMs?: number;
|
|
1582
|
+
/** Task outcome summary (for task_complete) */
|
|
1583
|
+
outcome?: string;
|
|
1584
|
+
/** Failure reason (for task_failed, merge_failed, batch_paused) */
|
|
1585
|
+
reason?: string;
|
|
1586
|
+
/** Whether partial progress was preserved (for task_failed) */
|
|
1587
|
+
partialProgress?: boolean;
|
|
1588
|
+
/** Lane number (for merge_failed) */
|
|
1589
|
+
laneNumber?: number;
|
|
1590
|
+
/** Merge error details (for merge_failed) */
|
|
1591
|
+
error?: string;
|
|
1592
|
+
/** Number of merge test verifications (for merge_success) */
|
|
1593
|
+
testCount?: number;
|
|
1594
|
+
/** Wave count for total waves (for merge_success) */
|
|
1595
|
+
totalWaves?: number;
|
|
1596
|
+
|
|
1597
|
+
// ── Batch summary fields (for batch_complete, batch_paused) ──
|
|
1598
|
+
|
|
1599
|
+
/** Total succeeded tasks (for batch_complete) */
|
|
1600
|
+
succeededTasks?: number;
|
|
1601
|
+
/** Total failed tasks (for batch_complete, batch_paused) */
|
|
1602
|
+
failedTasks?: number;
|
|
1603
|
+
/** Total skipped tasks (for batch_complete) */
|
|
1604
|
+
skippedTasks?: number;
|
|
1605
|
+
/** Total blocked tasks (for batch_complete) */
|
|
1606
|
+
blockedTasks?: number;
|
|
1607
|
+
/** Batch duration in milliseconds (for batch_complete) */
|
|
1608
|
+
batchDurationMs?: number;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
/**
|
|
1612
|
+
* Callback type for engine event consumers.
|
|
1613
|
+
*
|
|
1614
|
+
* The command handler (extension.ts) subscribes to this to receive
|
|
1615
|
+
* real-time engine state transitions. In the non-blocking architecture
|
|
1616
|
+
* (Step 2), this is the primary way the caller observes engine progress
|
|
1617
|
+
* instead of awaiting the return value.
|
|
1618
|
+
*
|
|
1619
|
+
* The callback is invoked synchronously in the engine's event loop.
|
|
1620
|
+
* Consumers MUST NOT perform blocking I/O in the callback.
|
|
1621
|
+
*
|
|
1622
|
+
* @since TP-040
|
|
1623
|
+
*/
|
|
1624
|
+
export type EngineEventCallback = (event: EngineEvent) => void;
|
|
1625
|
+
|
|
1626
|
+
/**
|
|
1627
|
+
* Build the base fields for an engine event.
|
|
1628
|
+
*
|
|
1629
|
+
* Ensures consistent field population across all emit sites.
|
|
1630
|
+
* Analogous to `buildTier0EventBase()` for Tier 0 events.
|
|
1631
|
+
*
|
|
1632
|
+
* @since TP-040
|
|
1633
|
+
*/
|
|
1634
|
+
export function buildEngineEventBase(
|
|
1635
|
+
type: EngineEventType,
|
|
1636
|
+
batchId: string,
|
|
1637
|
+
waveIndex: number,
|
|
1638
|
+
phase: OrchBatchPhase,
|
|
1639
|
+
): Pick<EngineEvent, "timestamp" | "type" | "batchId" | "waveIndex" | "phase"> {
|
|
1640
|
+
return {
|
|
1641
|
+
timestamp: new Date().toISOString(),
|
|
1642
|
+
type,
|
|
1643
|
+
batchId,
|
|
1644
|
+
waveIndex,
|
|
1645
|
+
phase,
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
|
|
1349
1650
|
/**
|
|
1350
1651
|
* Decision output from the merge retry policy evaluator.
|
|
1351
1652
|
*
|
|
@@ -1385,11 +1686,23 @@ export type MergeRetryLoopOutcome =
|
|
|
1385
1686
|
/** Retry succeeded — caller should continue normal post-merge flow */
|
|
1386
1687
|
kind: "retry_succeeded";
|
|
1387
1688
|
mergeResult: MergeWaveResult;
|
|
1689
|
+
/** Classification of the failure that was retried */
|
|
1690
|
+
classification: MergeFailureClassification | null;
|
|
1691
|
+
/** Scope key used for retry counter tracking */
|
|
1692
|
+
scopeKey: string;
|
|
1693
|
+
/** Last retry decision (carries attempt/maxAttempts for event emission) */
|
|
1694
|
+
lastDecision: MergeRetryDecision;
|
|
1388
1695
|
}
|
|
1389
1696
|
| {
|
|
1390
1697
|
/** Safe-stop triggered during retry — caller should break the wave loop */
|
|
1391
1698
|
kind: "safe_stop";
|
|
1392
1699
|
mergeResult: MergeWaveResult;
|
|
1700
|
+
/** Classification of the failure that was retried */
|
|
1701
|
+
classification: MergeFailureClassification | null;
|
|
1702
|
+
/** Scope key used for retry counter tracking */
|
|
1703
|
+
scopeKey: string;
|
|
1704
|
+
/** Last retry decision (carries attempt/maxAttempts for event emission) */
|
|
1705
|
+
lastDecision: MergeRetryDecision;
|
|
1393
1706
|
errorMessage: string;
|
|
1394
1707
|
notifyMessage: string;
|
|
1395
1708
|
}
|
|
@@ -1434,6 +1747,13 @@ export interface MergeRetryCallbacks {
|
|
|
1434
1747
|
updateMergeResult: (result: MergeWaveResult) => void;
|
|
1435
1748
|
/** Sleep for cooldown (allows test injection) */
|
|
1436
1749
|
sleep: (ms: number) => void;
|
|
1750
|
+
/**
|
|
1751
|
+
* Optional callback fired when a retry attempt is about to be executed.
|
|
1752
|
+
* Provides the retry decision with classification, attempt count, and cooldown
|
|
1753
|
+
* so callers can emit structured Tier 0 events at the right time.
|
|
1754
|
+
* @since TP-039 R004
|
|
1755
|
+
*/
|
|
1756
|
+
onRetryAttempt?: (decision: MergeRetryDecision) => void;
|
|
1437
1757
|
}
|
|
1438
1758
|
|
|
1439
1759
|
// ── View-Model Types ─────────────────────────────────────────────────
|
|
@@ -2037,6 +2357,13 @@ export interface ResumePoint {
|
|
|
2037
2357
|
reconnectTaskIds: string[];
|
|
2038
2358
|
/** Task IDs with dead sessions but existing worktrees that need re-execution */
|
|
2039
2359
|
reExecuteTaskIds: string[];
|
|
2360
|
+
/**
|
|
2361
|
+
* Wave indexes (0-based) where all tasks are terminal but the merge
|
|
2362
|
+
* is missing or failed. These waves should be retried for merge only
|
|
2363
|
+
* (no task re-execution). Empty when all completed waves have
|
|
2364
|
+
* successful merges. (TP-037, Bug #102)
|
|
2365
|
+
*/
|
|
2366
|
+
mergeRetryWaveIndexes: number[];
|
|
2040
2367
|
}
|
|
2041
2368
|
|
|
2042
2369
|
// ── Abort (TS-009 Step 5) ────────────────────────────────────────────
|