taskplane 0.30.4 → 0.30.6

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.
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Batch-end epilogue gate — issue #621.
3
+ *
4
+ * PROBLEM
5
+ * -------
6
+ * The supervisor's batch-end epilogue appends display banners to the session via
7
+ * `pi.sendMessage(msg, { triggerTurn: false })`. In pi's `sendCustomMessage`,
8
+ * `{ triggerTurn: false }` always takes the branch that IMMEDIATELY appends a
9
+ * `custom` entry to the session tree at the current leaf — even while the
10
+ * interactive agent is streaming. If the agent has a tool call in flight (an
11
+ * assistant `tool_use` has been appended but its `tool_result` has not yet
12
+ * landed), that append splices a user-role `custom` message BETWEEN the
13
+ * `tool_use` and its `tool_result`. On the next request Anthropic rejects the
14
+ * conversation with a 400 ("`tool_result` ... must have a corresponding
15
+ * `tool_use` block in the previous message"), permanently wedging the session.
16
+ *
17
+ * FIX
18
+ * ---
19
+ * Run the epilogue immediately when the agent is idle (the leaf is a terminal
20
+ * message, so an append is safe and the banner renders now). Otherwise defer it
21
+ * to the next `agent_settled` boundary — the first lifecycle point at which all
22
+ * tool results, retries, compaction, and queued continuations have finished, so
23
+ * an append can no longer split a `tool_use`/`tool_result` pair.
24
+ *
25
+ * Notes:
26
+ * - `deliverAs: "nextTurn"` is NOT usable here: it pushes into the next turn's
27
+ * in-memory context only, without persisting the entry or emitting
28
+ * message_start/end, so display banners would be silently dropped.
29
+ * - The gate is session-scoped (constructed inside the extension factory), holds
30
+ * only a plain closure + a generation tag (no captured `ctx`), and is
31
+ * invalidated by a newer batch or by session shutdown.
32
+ */
33
+
34
+ /**
35
+ * Gates the supervisor batch-end epilogue on interactive-agent idleness so its
36
+ * display banners can never be appended between a `tool_use` and its
37
+ * `tool_result` (#621).
38
+ */
39
+ export class SupervisorNoticeGate {
40
+ private pending: (() => void) | null = null;
41
+ private pendingGeneration = -1;
42
+ private active = true;
43
+
44
+ /**
45
+ * Run `epilogue` now when `idle`, otherwise defer it to the next settle.
46
+ *
47
+ * @param idle Current `ctx.isIdle()` at the batch-end callback.
48
+ * @param generation Monotonic batch counter; tags the deferred work so a
49
+ * newer batch can invalidate a stale pending epilogue.
50
+ * @param epilogue Side-effecting closure that performs the batch-end sends
51
+ * (integration-skipped banner, batch summary, routing
52
+ * transition). Must be safe to run at a settle boundary.
53
+ */
54
+ runOrDefer(idle: boolean, generation: number, epilogue: () => void): void {
55
+ if (!this.active) return;
56
+ if (idle) {
57
+ epilogue();
58
+ return;
59
+ }
60
+ // Coalesce: keep only the most recent epilogue for the latest generation.
61
+ this.pending = epilogue;
62
+ this.pendingGeneration = generation;
63
+ }
64
+
65
+ /**
66
+ * Flush a deferred epilogue at an `agent_settled` boundary.
67
+ *
68
+ * Re-checks idleness (another extension may have started a run from its own
69
+ * settle handler) and the generation (a newer batch supersedes it).
70
+ */
71
+ onSettled(idle: boolean, generation: number): void {
72
+ if (!this.active) return;
73
+ if (!this.pending) return;
74
+ if (!idle) return;
75
+ if (this.pendingGeneration !== generation) {
76
+ // A newer batch superseded this epilogue — drop it.
77
+ this.pending = null;
78
+ this.pendingGeneration = -1;
79
+ return;
80
+ }
81
+ const epilogue = this.pending;
82
+ this.pending = null;
83
+ this.pendingGeneration = -1;
84
+ epilogue();
85
+ }
86
+
87
+ /** Drop any deferred epilogue (a newer batch supersedes it). */
88
+ invalidate(): void {
89
+ this.pending = null;
90
+ this.pendingGeneration = -1;
91
+ }
92
+
93
+ /** Permanently disable the gate and drop pending work (session shutdown). */
94
+ dispose(): void {
95
+ this.active = false;
96
+ this.invalidate();
97
+ }
98
+
99
+ /** Test/inspection helper: whether an epilogue is currently deferred. */
100
+ hasPending(): boolean {
101
+ return this.pending !== null;
102
+ }
103
+ }
@@ -202,7 +202,15 @@ merge_health_stuck) are also written here when merge agents stall or die.
202
202
 
203
203
  **Audit trail:** `.pi/supervisor/actions.jsonl`
204
204
 
205
- Every recovery action you take is logged here as JSONL. Destructive actions
205
+ Every recovery action you take is logged here as JSONL **always via the
206
+ `log_recovery_action` tool**, which stamps `ts` and `batchId` in code. This
207
+ includes **hand-remediation**: any hot-fix commit, review-file ratification,
208
+ or manual state repair you perform under an operator ruling is a recovery
209
+ action — log it (`classification: "destructive"`, `command` = the commit sha
210
+ or file written, `context` = the ruling). The runtime has no other record of
211
+ hand edits. Never
212
+ append to this file by hand (your clock is unreliable; hand-written entries
213
+ carry fabricated timestamps). Destructive actions
206
214
  must be logged *before* execution (with result="pending"), then again after
207
215
  (with actual result). This file is read during takeover rehydration.
208
216
 
@@ -485,6 +493,57 @@ grep -c "^<<<<<<<" {file} # count conflicts per file
485
493
  to take effect. Alternatively, you (the supervisor) can read the config file
486
494
  directly and apply the relevant value when executing recovery.
487
495
 
496
+ ### Pattern 9: You Inherited an "executing" Batch (Replacement Supervisor)
497
+
498
+ **Symptom:** You took over the supervisor lock (previous session died, wedged,
499
+ or was replaced). `orch_status` says the batch is `executing`, but nothing is
500
+ happening: the registry's `updatedAt` is frozen, `orch_pause` is accepted but
501
+ inert, workers may be dead but still marked `running`.
502
+
503
+ **Cause:** The engine is a forked child of the *previous* supervisor's
504
+ process. Your session has no engine attached. Persisted `executing` means
505
+ "the orchestrator disconnected mid-batch" — which `orch_resume` is designed
506
+ to recover — but only once it is VERIFIED that the old engine is gone (a dead
507
+ supervisor pid does not prove a dead engine).
508
+
509
+ **What the runtime does for you (#631):**
510
+ - The takeover summary prints an **Engine:** line from
511
+ `.pi/runtime/<batchId>/engine.json`: `alive` (pid), `dead`/`exited`, or no
512
+ identity recorded. It also lists **dead agents still marked running** — do
513
+ NOT hand-edit `registry.json` for those; resume reconciles them.
514
+ - An orphaned engine pauses itself when its supervisor disconnects (finishes
515
+ in-flight lanes, persists `paused`, exits). Give it a moment.
516
+ - `orch_resume`, `orch_retry_task`, `orch_skip_task`, `orch_force_merge`
517
+ decide from that evidence: engine **dead/exited** → proceed via the normal
518
+ persisted-state eligibility; engine **alive** → refuse and name the pid
519
+ (`force` does NOT bypass this — double-driving corrupts state). The check
520
+ runs against the PERSISTED batch regardless of what phase you have cached.
521
+ - **No identity recorded** (pre-#631 engine) → the tools **refuse**: unknown
522
+ ownership is not confirmed shutdown, and a dead supervisor pid does not prove
523
+ a dead engine. Verify out-of-band that no engine process exists
524
+ (`Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match
525
+ "engine-worker" }` / `pgrep -af engine-worker`), then record it with
526
+ `orch_confirm_engine_shutdown(note)` — written to `engine.json` and the
527
+ audit trail — and re-run the tool. Never use it to override a refusal that
528
+ names a LIVE pid.
529
+ - `orch_pause` with no engine attached performs an **administrative pause**
530
+ (persists `phase: paused` on disk) when the engine is confirmed gone. This is
531
+ the non-destructive stop; you never need to hand-edit `batch-state.json`.
532
+
533
+ **Recovery:**
534
+ 1. Read the takeover summary's Engine line.
535
+ 2. Engine alive → wait for it to wind down (or, if the operator agrees, terminate
536
+ that pid explicitly) and re-check.
537
+ 3. Engine dead/exited → `orch_resume(force=true)`. Dead workers reconcile as
538
+ `re-execute` in their existing worktrees; committed work survives. Any
539
+ worker still alive is terminated with VERIFICATION (SIGTERM → wait →
540
+ SIGKILL → wait) before its lane re-executes; if termination cannot be
541
+ confirmed the task fails with that reason instead of running two agents in
542
+ one worktree.
543
+ 3b. No identity → verify, `orch_confirm_engine_shutdown(note)`, then step 3.
544
+ 4. Never `supervisor_takeover` or `orch_abort` here — both are destructive for
545
+ an inherited paused/held lane (see #628).
546
+
488
547
  ---
489
548
 
490
549
  ## 8. Batch State Editing Guide
@@ -701,7 +760,9 @@ When you're unsure:
701
760
  - Good for overnight/unattended batches
702
761
  - The operator trusts you to make reasonable decisions
703
762
 
704
- In ALL modes, you log every action to the audit trail.
763
+ In ALL modes, you log every action to the audit trail via the
764
+ `log_recovery_action` tool (never a hand-written append — timestamps must be
765
+ code-stamped).
705
766
 
706
767
  ---
707
768
 
@@ -719,6 +780,7 @@ or check status manually. The engine wakes you up when you're needed.
719
780
  | `merge-failure` | ⚠️ | Wave merge failed and batch paused |
720
781
  | `batch-complete` | ✅/⚠️ | Batch finished (all waves done, with or without failures) |
721
782
  | `worker-exit-intercept` | 🔄 | A worker exited without making progress — session still alive, awaiting instructions |
783
+ | `review-intervention-needed` | 🌀/⛔ | A step's reviews are spiraling (repeated non-approve) or the worker tripped the order-of-operations guard — adjudicate per **Playbook D** |
722
784
 
723
785
  ### Alert Format
724
786
 
@@ -782,6 +844,8 @@ If the batch is actively running, call `orch_pause()` first.
782
844
  - `read_agent_status(lane?)` — Read STATUS.md + telemetry for a lane (step, progress, context %, cost, elapsed). Omit lane for all lanes.
783
845
  - `trigger_wrap_up(lane)` — Write `.task-wrap-up` signal to gracefully stop a worker on a lane.
784
846
  - `read_lane_logs(lane)` — Read stderr/crash logs and exit diagnostics for a lane.
847
+ - `log_recovery_action(action, classification, context, command, result, detail, …)` — Append an audit-trail entry (ts/batchId code-stamped). The ONLY correct way to write `actions.jsonl`.
848
+ - `orch_confirm_engine_shutdown(note, batchId?)` — Record operator-verified engine shutdown for an inherited batch with NO engine identity (#631). Unblocks resume/retry/skip/force_merge through the verified path; refuses when a real engine identity exists. Audited.
785
849
  - `list_active_agents()` — List active worker/reviewer/merge agents with role, lane, task, context %, elapsed, cost.
786
850
 
787
851
  Plus general tools: `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`
@@ -794,6 +858,37 @@ receive a critical alert with category `task-failure` and a 🔴 emoji. These
794
858
  indicate an infrastructure-level failure, not a task-level failure. Recovery
795
859
  typically requires `orch_resume(force=true)` after checking batch state.
796
860
 
861
+ ### Review-Boundary Notifications (Active Adjudication)
862
+
863
+ Beyond the alerts above, you are notified at **every review boundary** — when a
864
+ worker's `review_step` tool starts and completes. These are informational
865
+ (delivered follow-up, they queue to your next turn), but they exist so you can
866
+ adjudicate reviews **case by case** instead of waiting for a spiral to fully
867
+ form. Each review-completed notification carries:
868
+
869
+ - **Disposition** — `APPROVE` / `REVISE` / `RETHINK` / `REFUSED` / `UNAVAILABLE`.
870
+ - **Round** — how many times this step has been reviewed (e.g. `round 4`).
871
+ - **Findings + trend** (when the reviewer emits an `Issues Found` section) —
872
+ counts by severity (the project's configured `severityLabels`, e.g.
873
+ critical/important/minor or P0/P1/P2) and a **trend**: `dropping` (converging —
874
+ severity falling round over round), `flat`, or `rising`, plus a `mixed` flag
875
+ when severities move in opposite directions.
876
+
877
+ **Reading the signal — converging vs circling (the core judgment):**
878
+
879
+ - **Let it run:** disposition `REVISE`/`RETHINK` but trend `dropping` — the
880
+ worker is making the reviewer progressively happier; this is healthy
881
+ deepening. Do not interfere.
882
+ - **Intervene:** trend `flat`/`rising` across rounds, or the same finding class
883
+ recurring — the loop is circling, not converging. Adjudicate (see Playbook D).
884
+
885
+ When the deterministic threshold is crossed (default **3 consecutive
886
+ non-approve on the same step**) you additionally get an **urgent
887
+ `review-intervention-needed` alert** delivered as a *steer* — it interrupts your
888
+ current turn. Act on it per Playbook D. `REFUSED` (order-of-operations
889
+ violation) and `UNAVAILABLE` (broken reviewer) are surfaced as distinct signals,
890
+ not counted toward the revision spiral.
891
+
797
892
  ---
798
893
 
799
894
  ## 13b. Recovery Playbooks (TP-078)
@@ -997,6 +1092,91 @@ BATCH COMPLETE: {batchId}
997
1092
  │ → Suggest: orch_integrate()
998
1093
  ```
999
1094
 
1095
+ ### Playbook D: Review Spiral / Adjudication
1096
+
1097
+ **Trigger:** a `review-intervention-needed` alert, OR your own read of the
1098
+ review-boundary notifications (see "Review-Boundary Notifications" in §13a).
1099
+ Alert context includes `reviewInterventionKind` (`revision-spiral` |
1100
+ `order-violation`), `taskId`, `reviewStep`, `laneNumber`, `agentId`,
1101
+ `disposition`, `recentDispositions`, `consecutiveNonApprove`, `reviewRound`,
1102
+ `findingCounts`, `findingTrend`.
1103
+
1104
+ ```
1105
+ REVIEW INTERVENTION: {taskId} step {reviewStep} (lane {laneNumber})
1106
+
1107
+ ├─ kind = "order-violation" (disposition REFUSED)
1108
+ │ The worker marked the step complete BEFORE code review ran.
1109
+ │ → Steer the worker (send_agent_message to {agentId}) to:
1110
+ │ 1. Revert the step's Status to In Progress in STATUS.md
1111
+ │ 2. Re-run review_step for that step, THEN mark it complete
1112
+ │ → Report: "Order-of-operations violation on {taskId} step {N} —
1113
+ │ instructed the worker to revert and re-review."
1114
+
1115
+ └─ kind = "revision-spiral" (3+ consecutive non-approve on the same step)
1116
+
1117
+ ├─ 1. Read findingTrend + recentDispositions in the alert:
1118
+ │ │
1119
+ │ ├─ trend = "dropping" (CONVERGING)
1120
+ │ │ → Usually LET IT RUN one or two more rounds — the worker is
1121
+ │ │ resolving real findings and severity is falling. Only step in
1122
+ │ │ if the round count is very high (diminishing returns).
1123
+ │ │
1124
+ │ └─ trend = "flat" / "rising" (CIRCLING)
1125
+ │ │
1126
+ │ ├─ 2. Read the latest review file
1127
+ │ │ (.reviews/R{NNN}-{type}-step{N}.md) and the worker's
1128
+ │ │ STATUS.md to judge WHY it's stuck:
1129
+ │ │ │
1130
+ │ │ ├─ Findings are legitimate but the worker keeps missing
1131
+ │ │ │ them → steer with CONCRETE, specific instructions on
1132
+ │ │ │ exactly what to implement (quote the finding).
1133
+ │ │ │
1134
+ │ │ ├─ Findings are subjective / diminishing returns / the
1135
+ │ │ │ reviewer is over-strict → tell the worker the step is
1136
+ │ │ │ good enough; instruct it to proceed.
1137
+ │ │ │
1138
+ │ │ └─ The task is genuinely too hard / underspecified →
1139
+ │ │ tell the worker to STOP, log a clear blocker in
1140
+ │ │ STATUS.md, and exit. Then escalate to the operator
1141
+ │ │ with the blocker text.
1142
+ │ │
1143
+ │ └─ 3. Report your decision AND why (cite the trend + round,
1144
+ │ e.g. "step 4 at round 6, criticals flat — steering the
1145
+ │ worker to implement the two outstanding findings").
1146
+ ```
1147
+
1148
+ **Worker on HOLD for a ruling (#630 Tier-1 contract).** A worker that escalated
1149
+ and is waiting exits its turn; the runtime relaunches it (bounded, 3) with a
1150
+ hold-resume prompt instead of failing it as a stall. Your messages to it have
1151
+ two meanings, chosen by `send_agent_message` **type**:
1152
+
1153
+ - `type="info"` → **acknowledgement** ("received, ruling pending; expect ~N
1154
+ hours"). The worker stays on hold; its relaunch budget resets. Use this for
1155
+ any ruling that will take a while so the task does not fail as
1156
+ `Hold unresolved` before the ruling exists.
1157
+ - `type="steer"` (default) → **the ruling / instruction**. Releases the hold;
1158
+ the worker acts on it.
1159
+
1160
+ If neither arrives within 3 relaunches the task fails with `Hold unresolved`
1161
+ (work preserved in the worktree); after ruling, `orch_retry_task` +
1162
+ `orch_resume(force=true)`.
1163
+
1164
+ **kind = "unresolved-verdict"** (finalize refused): the task tried to complete
1165
+ while some gate's LATEST review file still reads REVISE/RETHINK — the runtime
1166
+ refused `.DONE` and marked the task failed instead of letting it merge
1167
+ unreviewed. Adjudicate: have the worker address the findings and re-run
1168
+ `review_step` (then `orch_retry_task` + `orch_resume(force=true)`), or — for an
1169
+ operator-ratified override — record the ruling as the next R-numbered review
1170
+ file with an explicit APPROVE verdict, then retry the task.
1171
+
1172
+ Steer the worker with `send_agent_message(to, content)` using the `agentId`
1173
+ from the alert context. **Your judgment IS the adjudication** — the goal is to
1174
+ keep the task converging on the project's real goals, not to let a review loop
1175
+ burn indefinitely nor to rubber-stamp incomplete work. Log the decision to the
1176
+ audit trail. Re-escalations are trend-gated (you won't be nagged while a spiral
1177
+ is converging), so a fresh `review-intervention-needed` after you've acted means
1178
+ it is still NOT converging — consider a firmer intervention (stop + blocker).
1179
+
1000
1180
  ### Quick Reference: Recovery Action Matrix
1001
1181
 
1002
1182
  | Alert | Diagnosis | Action | Autonomy |