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.
@@ -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 = fast-forward on completion. */
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) ────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",