@llblab/pi-actors 0.41.0 → 0.42.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.
Files changed (150) hide show
  1. package/AGENTS.md +13 -8
  2. package/BACKLOG.md +2 -81
  3. package/CHANGELOG.md +22 -0
  4. package/README.md +22 -4
  5. package/dist/index.js +29 -82
  6. package/dist/lib/async-runs.d.ts +25 -5
  7. package/dist/lib/async-runs.js +136 -47
  8. package/dist/lib/automatic-review-runtime.d.ts +18 -0
  9. package/dist/lib/automatic-review-runtime.js +96 -0
  10. package/dist/lib/draft-consolidation-transaction.d.ts +65 -0
  11. package/dist/lib/draft-consolidation-transaction.js +610 -0
  12. package/dist/lib/draft-consolidation.d.ts +35 -0
  13. package/dist/lib/draft-consolidation.js +126 -0
  14. package/dist/lib/draft-review.d.ts +56 -0
  15. package/dist/lib/draft-review.js +254 -0
  16. package/dist/lib/draft-sleep.d.ts +65 -0
  17. package/dist/lib/draft-sleep.js +468 -0
  18. package/dist/lib/file-state.d.ts +8 -1
  19. package/dist/lib/file-state.js +115 -18
  20. package/dist/lib/inspector-actions.d.ts +16 -0
  21. package/dist/lib/inspector-actions.js +57 -0
  22. package/dist/lib/inspector-command.d.ts +7 -0
  23. package/dist/lib/inspector-command.js +37 -0
  24. package/dist/lib/inspector-overlay.d.ts +29 -1
  25. package/dist/lib/inspector-overlay.js +537 -90
  26. package/dist/lib/inspector.d.ts +9 -0
  27. package/dist/lib/inspector.js +52 -1
  28. package/dist/lib/observability.d.ts +39 -3
  29. package/dist/lib/observability.js +162 -34
  30. package/dist/lib/paths.d.ts +7 -0
  31. package/dist/lib/paths.js +28 -0
  32. package/dist/lib/recipes-discovery.js +32 -24
  33. package/dist/lib/recipes-usage.d.ts +18 -6
  34. package/dist/lib/recipes-usage.js +445 -34
  35. package/dist/lib/review-control.d.ts +14 -0
  36. package/dist/lib/review-control.js +111 -0
  37. package/dist/lib/review-diagnostics.d.ts +11 -0
  38. package/dist/lib/review-diagnostics.js +148 -0
  39. package/dist/lib/review-projection.d.ts +14 -0
  40. package/dist/lib/review-projection.js +170 -0
  41. package/dist/lib/run-ui-runtime.d.ts +18 -0
  42. package/dist/lib/run-ui-runtime.js +123 -0
  43. package/dist/lib/runs-artifacts.d.ts +1 -1
  44. package/dist/lib/runs-artifacts.js +1 -1
  45. package/dist/lib/runs-control.d.ts +8 -2
  46. package/dist/lib/runs-control.js +23 -6
  47. package/dist/lib/runs-identity.d.ts +1 -1
  48. package/dist/lib/runs-identity.js +1 -1
  49. package/dist/lib/runs-index.d.ts +11 -2
  50. package/dist/lib/runs-index.js +46 -23
  51. package/dist/lib/runs-mailbox.d.ts +1 -1
  52. package/dist/lib/runs-mailbox.js +1 -1
  53. package/dist/lib/runs-messages.d.ts +1 -1
  54. package/dist/lib/runs-messages.js +1 -1
  55. package/dist/lib/runs-outbox.d.ts +1 -1
  56. package/dist/lib/runs-outbox.js +1 -1
  57. package/dist/lib/runs-ownership.d.ts +1 -1
  58. package/dist/lib/runs-ownership.js +1 -1
  59. package/dist/lib/runs-parent-teardown.d.ts +51 -0
  60. package/dist/lib/runs-parent-teardown.js +172 -0
  61. package/dist/lib/runs-process.d.ts +1 -1
  62. package/dist/lib/runs-process.js +1 -1
  63. package/dist/lib/runs-retention.d.ts +1 -1
  64. package/dist/lib/runs-retention.js +1 -1
  65. package/dist/lib/runs-start.d.ts +5 -3
  66. package/dist/lib/runs-start.js +6 -48
  67. package/dist/lib/runs-status.d.ts +5 -3
  68. package/dist/lib/runs-status.js +4 -6
  69. package/dist/lib/runtime.d.ts +7 -1
  70. package/dist/lib/runtime.js +32 -18
  71. package/dist/lib/tool-review-lineage-transaction.d.ts +27 -0
  72. package/dist/lib/tool-review-lineage-transaction.js +597 -0
  73. package/dist/lib/tool-review-lineage.d.ts +24 -0
  74. package/dist/lib/tool-review-lineage.js +98 -0
  75. package/dist/lib/tool-review-scheduler.d.ts +80 -0
  76. package/dist/lib/tool-review-scheduler.js +494 -0
  77. package/dist/lib/tool-review-transaction.d.ts +50 -0
  78. package/dist/lib/tool-review-transaction.js +362 -0
  79. package/dist/lib/tool-review.d.ts +56 -0
  80. package/dist/lib/tool-review.js +197 -0
  81. package/dist/lib/tools-inspect.js +26 -3
  82. package/dist/lib/tools-local.js +4 -2
  83. package/dist/lib/tools-message.d.ts +1 -0
  84. package/dist/lib/tools-message.js +29 -17
  85. package/dist/lib/tools-response.d.ts +1 -1
  86. package/dist/lib/tools-response.js +5 -7
  87. package/dist/lib/tools-spawn.js +4 -1
  88. package/dist/lib/tools.d.ts +1 -0
  89. package/dist/lib/tools.js +1 -0
  90. package/dist/recipes/draft-review.json +24 -0
  91. package/dist/recipes/tool-review.json +24 -0
  92. package/dist/scripts/release-gates.mjs +165 -0
  93. package/dist/skills/actors/SKILL.md +11 -14
  94. package/dist/skills/swarm/SKILL.md +1 -1
  95. package/docs/actor-inspector.md +32 -18
  96. package/docs/async-runs.md +10 -2
  97. package/docs/recipe-library.md +7 -3
  98. package/docs/template-recipes.md +4 -11
  99. package/docs/tool-registry.md +11 -4
  100. package/index.ts +29 -103
  101. package/lib/async-runs.ts +218 -62
  102. package/lib/automatic-review-runtime.ts +135 -0
  103. package/lib/draft-consolidation-transaction.ts +821 -0
  104. package/lib/draft-consolidation.ts +181 -0
  105. package/lib/draft-review.ts +325 -0
  106. package/lib/draft-sleep.ts +576 -0
  107. package/lib/file-state.ts +143 -19
  108. package/lib/inspector-actions.ts +79 -0
  109. package/lib/inspector-command.ts +54 -0
  110. package/lib/inspector-overlay.ts +675 -105
  111. package/lib/inspector.ts +78 -3
  112. package/lib/observability.ts +219 -40
  113. package/lib/paths.ts +43 -0
  114. package/lib/recipes-discovery.ts +34 -26
  115. package/lib/recipes-usage.ts +569 -40
  116. package/lib/review-control.ts +137 -0
  117. package/lib/review-diagnostics.ts +164 -0
  118. package/lib/review-projection.ts +200 -0
  119. package/lib/run-ui-runtime.ts +153 -0
  120. package/lib/runs-artifacts.ts +1 -1
  121. package/lib/runs-control.ts +49 -5
  122. package/lib/runs-identity.ts +1 -1
  123. package/lib/runs-index.ts +57 -21
  124. package/lib/runs-mailbox.ts +1 -1
  125. package/lib/runs-messages.ts +1 -1
  126. package/lib/runs-outbox.ts +1 -1
  127. package/lib/runs-ownership.ts +1 -1
  128. package/lib/runs-parent-teardown.ts +257 -0
  129. package/lib/runs-process.ts +1 -1
  130. package/lib/runs-retention.ts +1 -1
  131. package/lib/runs-start.ts +12 -68
  132. package/lib/runs-status.ts +12 -8
  133. package/lib/runtime.ts +34 -17
  134. package/lib/tool-review-lineage-transaction.ts +881 -0
  135. package/lib/tool-review-lineage.ts +145 -0
  136. package/lib/tool-review-scheduler.ts +635 -0
  137. package/lib/tool-review-transaction.ts +563 -0
  138. package/lib/tool-review.ts +270 -0
  139. package/lib/tools-inspect.ts +33 -3
  140. package/lib/tools-local.ts +8 -2
  141. package/lib/tools-message.ts +45 -30
  142. package/lib/tools-response.ts +5 -6
  143. package/lib/tools-spawn.ts +8 -1
  144. package/lib/tools.ts +5 -0
  145. package/package.json +3 -2
  146. package/recipes/draft-review.json +24 -0
  147. package/recipes/tool-review.json +24 -0
  148. package/scripts/release-gates.mjs +165 -0
  149. package/skills/actors/SKILL.md +11 -14
  150. package/skills/swarm/SKILL.md +1 -1
@@ -1,45 +1,55 @@
1
1
  # Actor Inspector
2
2
 
3
- The actor inspector is a manually opened, read-only TUI navigator for owned actor runs. It keeps communication evidence and persisted subagent execution evidence in one hierarchy without merging their meanings.
3
+ The actor inspector is a manually opened TUI navigator for owned actor runs. Evidence remains read-only; its one explicit lifecycle action can send canonical `control.kill` to the selected running run after confirmation. It keeps recipe/launch identity, communication evidence, and persisted subagent execution evidence in one hierarchy without merging their meanings.
4
4
 
5
5
  ```text
6
6
  owned run
7
+ → recipe
7
8
  → messages | turns
8
9
  → filtered timeline
9
- → bounded detail
10
+ → one bounded detail level
10
11
  ```
11
12
 
12
13
  ## Navigation
13
14
 
14
- `/actors-inspector-toggle` opens one centered overlay and remains the only command to remember. The first run owned by the current Pi session becomes the active run automatically; an empty session still exposes functional tabs and filters.
15
+ `/actors-inspector` opens one centered overlay and remains the only command to remember. The latest run owned by the current Pi session becomes active automatically; an empty session still exposes functional tabs and filters.
15
16
 
16
17
  The overlay exposes an explicit focus hierarchy:
17
18
 
18
19
  ```text
19
- Run Enter opens owned runs, ↓ enters tabs
20
- Tabs ←/→ chooses Messages or Turns, Enter opens filter parameters
21
- Filters ↑/↓ chooses Channel/State or Subagent, Enter opens values to the right
20
+ Run ←/→ chooses the previous/next owned run, Enter opens runs, K asks to Kill a running run, ↓ enters tabs
21
+ Tabs ←/→ chooses Recipe, Messages, or Turns
22
+ Recipe ↑/↓ scroll; PageUp/PageDown jumps by viewport; ↑ at top, Escape, or ← returns to tabs
23
+ Filters Enter on Messages/Turns opens Channel/State or Subagent; Enter opens values
22
24
  Values ↑/↓ hovers, Enter applies, Escape returns one menu level
23
- List ↑/↓ chooses, Enter/→ opens detail
24
- Detail ↑/↓ scroll, Escape/← returns
25
+ List ↑/↓ chooses, PageUp/PageDown jumps by viewport, Enter/→ opens detail, ← returns to tabs
26
+ Detail ↑/↓ scroll, PageUp/PageDown jumps by viewport, Escape/← returns to the list
25
27
  Escape Close (or cancel the active options popup)
26
28
  ```
27
29
 
28
30
  Navigation stays bounded by available actions. `↑` on Run does nothing because no higher control exists. `↓` on Tabs enters the timeline only when it contains rows. Empty timelines therefore never receive focus.
29
31
 
30
- Selection and focus remain separate visual states. Accent-blue text marks the current tab, active filter popup, and applied option. A light neutral background plus `▶` marks every selectable control or timeline row that currently owns keyboard focus. Opening a popup keeps its parent filter blue so the relationship remains visible. The footer uses accent color only for key names and arrows; descriptions remain muted.
32
+ `K` appears only while Run is focused and the selected owned run reports `running`. It opens an in-overlay destructive confirmation; `Y`/Enter confirms and `N`/Escape cancels. Confirmation captures the immutable run generation and routes expected owner/generation through canonical `control.kill`; control compares both while serialized against same-directory restart, so terminal, ownership, or replacement-generation races reject without signaling. Success, cancellation, rejection, and failure remain bounded in the content area; terminal runs expose no Kill hint and reject a stale keypress.
31
33
 
32
- The top Run control aligns vertically with the tab labels, names the selected owned run, and colors its textual lifecycle status semantically. Enter opens available owned runs immediately beneath it, overlaying the tab row rather than leaving a detached gap. The timeline no longer renders run metadata as a data row.
34
+ Selection and focus remain separate visual states. Accent-blue text marks the current tab, active filter popup, and applied option. The Run control uses `← … →` markers plus a light neutral background to show both focus and horizontal cycling; menus and timeline rows retain the single `▶` focus marker, while selected tabs retain brackets. Opening a popup keeps its parent filter blue so the relationship remains visible. The footer uses accent color only for key names and arrows; descriptions remain muted.
33
35
 
34
- Filters live behind their tab rather than occupying a permanent row. Non-default filters remain visible as compact suffixes in the tab label, so hidden state never silently changes the timeline. Enter on Messages opens `Channel: <current>`, `State: <current>`, and `From: <current>`; `From` draws its values from the selected run's roster and limits rows to one actor. Enter on Turns opens `Subagent: <current>`. Enter on a parameter opens its alternative values as a second menu to the right while the parent and current value remain visible. Parent and child share their touching border rather than leaving or doubling a spacer column. Escape returns one level at a time. Moving focus never applies a value.
36
+ The top Run control aligns vertically with the tab labels, names the selected owned run, and colors its textual lifecycle status semantically. ←/→ cycles owned runs directly with wraparound, while Enter opens the complete owned-run list immediately beneath the control. That run list starts one cell farther left than the filter menus so its border aligns with the Run control rather than the tab/filter grid. It still overlays the tab row rather than leaving a detached gap. The timeline no longer renders run metadata as a data row.
35
37
 
36
- Nested menus overlay rather than replace the timeline. Only rows and columns containing menu borders or values occlude underlying cells. When adjacent menus have different heights, the unused corner remains transparent and preserves the separator, striped background, and timeline data beneath it.
38
+ Filters live behind their tab rather than occupying a permanent row. Non-default filters remain visible as compact parenthesized suffixes in the tab label, so hidden state never silently changes the timeline. Enter on Messages opens `Channel: <current>`, `State: <current>`, and `From: <current>`; `From` draws its values from the selected run's roster and limits rows to one actor. Enter on Turns opens `Subagent: <current>`. Enter on a parameter opens its alternative values as a second menu to the right while the parent and current value remain visible. Parent and child share their touching border rather than leaving or doubling a spacer column. Escape returns one level at a time. Moving focus never applies a value.
37
39
 
38
- The overlay uses most of the available terminal width and height. The bordered header keeps both tabs visible, while the list body shows the selected run and its current status above the evidence rows. Evidence rows retain stable alternating backgrounds based on their absolute timeline position, including while scrolling: even rows keep the dark overlay background, while odd rows use the neutral `customMessageBg` stripe. The footer exposes the active keys. Messages retain attention markers and unread filtering and open into bounded detail without leaving the overlay. The overlay refreshes while visible and distinguishes true empty timelines from filtered-empty results; filtered-empty copy points back to Enter on the active tab without moving focus.
40
+ Nested menus overlay rather than replace the timeline. Only rows and columns containing menu borders or values occlude underlying cells. When adjacent menus have different heights, the unused corner remains transparent and preserves the separator, striped background, and timeline data beneath it. Every run, filter, and nested value menu is viewport-bounded: ↑/↓ moves through the complete option set, the visible window follows focus, and `↑`/`↓` border markers disclose hidden options above or below without growing past the available inspector rows.
41
+
42
+ The overlay uses most of the available terminal width and height and reduces its content/menu viewport on shorter terminals. The bordered header keeps all three tabs visible, while the body shows the selected run and its current status above the active document or evidence rows. Run, Message, and Turn lists place the newest retained item directly below their control; a newly opened Inspector therefore selects the latest owned run, and ↓ moves backward in time toward older entries. Run options, Messages, and Turns all use compact descending `#N` labels, providing one timestamp-free time axis without repeating type words on every row. Evidence rows retain stable alternating backgrounds based on their absolute timeline position, including while scrolling: even rows keep the dark overlay background, while odd rows use the neutral `customMessageBg` stripe. Unused viewport padding stays on the plain overlay background instead of drawing fake striped rows beneath the last item. The footer exposes the active keys. Messages retain attention markers and unread filtering and open into bounded detail without leaving the overlay. The overlay refreshes while visible and distinguishes true empty timelines from filtered-empty results; filtered-empty copy points back to Enter on the active tab without moving focus.
43
+
44
+ ## Recipe Document
45
+
46
+ `Recipe` is the first and initially selected tab. It reads only persisted owned-run evidence from `run.json`: recipe identity and source, the authored recipe context captured at launch, the resolved executable template and runtime values, composition records, model policy, mailbox, artifacts, notification/retirement policy, and bounded read diagnostics. It never follows a mutable external recipe path while the Inspector is open.
47
+
48
+ The document renders as labeled, indented terminal text rather than raw JSON and scrolls as one level. Secret-bearing values receive the same redaction as turn evidence. Recipe context lives here rather than repeating inside every Turn.
39
49
 
40
50
  ## Communication Timeline
41
51
 
42
- The communication timeline reads run-local room, direct, branch-inbox, and coordinator/session message evidence. It preserves channel/sender filters, unread state, attention markers, roster-derived sender options, and bounded body previews. Unread remains filterable but does not consume a row column with a separate dot marker.
52
+ The communication timeline reads run-local room, direct, branch-inbox, and coordinator/session message evidence. Rows display their stable `#N` sequence in newest-first order. It preserves channel/sender filters, unread state, attention markers, roster-derived sender options, and bounded body previews. Unread remains filterable but does not consume a row column with a separate dot marker.
43
53
 
44
54
  Communication evidence describes messages between actors. It does not prove model execution.
45
55
 
@@ -53,19 +63,23 @@ Detached child `pi -p` commands receive isolated session storage under their own
53
63
 
54
64
  The runner records direct command-template session files in `review-evidence.json`. Coordinator-managed room/swarm participants also persist role/phase-scoped directories under the same `sessions/` root; the inspector discovers those owned files even though the coordinator, rather than the command-template runner, launched them. Explicit caller session policy (`--no-session`, `--session`, `--session-id`, `--session-dir`, or `--fork`) remains authoritative and is not replaced. A command may therefore have no inspector-visible session.
55
65
 
56
- The turns timeline follows the latest persisted entry branch in each recorded Pi session and groups:
66
+ The turns timeline follows the latest persisted entry branch in each recorded Pi session and displays numbered turns newest-first. Each list row begins with compact `#N`, then a humanized `Subagent N` derived from the internal `command-NNN` session owner, followed by an optional parenthesized semantic stage such as `(reviewer)`. The internal command id remains available in evidence detail for provenance but no longer acts as the unexplained primary list label. The visible model column shows only the model id, not its provider. Tool activity appears as a compact parenthesized action summary such as `(read)`, `(read, bash)`, or `(3 tools)`; `(error)` appears only when the turn or a tool result failed.
67
+
68
+ Each turn groups:
57
69
 
58
70
  - User input associated with the response;
59
71
  - Assistant text and host-persisted thinking blocks;
60
- - Provider, model, stop reason, usage, and error metadata;
72
+ - Model, stop reason, usage, and error metadata;
61
73
  - Tool calls in assistant source order;
62
74
  - Tool results correlated by `toolCallId`, regardless of completion order.
63
75
 
64
- Enter opens the selected turn inside the overlay. Detail adds command/stage identity, session and prompt paths, recipe-context reference, tool arguments/results, truncation state, unmatched result counts, and parse diagnostics. ↑/↓ scroll long detail while the footer keeps return/close keys visible.
76
+ Enter/→ opens the selected turn as one structured, scrollable detail document inside the overlay. A compact `Subagent N` heading with an optional meaningful role leads into meaning-first sections: User, persisted Thinking, Assistant, Tools, Execution, and Diagnostics. A final Provenance section retains session/prompt paths and truncation state without duplicating recipe context from the Recipe tab. Generic internal stages such as `command` and `subagent` stay hidden; technical `command-NNN` provenance remains available through the session and prompt paths without producing a redundant `Command / command-NNN (command)` block. Secondary qualifiers use parentheses rather than centered-dot separators. Long text, paths, and structured values wrap to subsequent terminal rows instead of receiving visual ellipsis; lines that already fit the available inner width remain intact, leading indentation is reserved before wrapping long unbroken paths so it cannot become a whitespace-only row, and every section plus all of its explicit or wrapped continuations keeps one background stripe. Blank-only source lines and trailing line breaks are omitted from both evidence and readable rendering. Section boundaries change the stripe without inserting separator rows, so the next heading follows the previous value immediately. ↑/↓ scrolls the resulting visual-row document while the footer remains visible. Source evidence remains bounded by the persisted session reader, but the detail view no longer truncates that retained evidence to one terminal row per field.
77
+
78
+ The detail view removes a single enclosing `<file name="…">…</file>` prompt transport wrapper and renders structured values as indented key/value text rather than one-line JSON. It has no nested transcript mode: Escape/← returns directly to the Turns list.
65
79
 
66
80
  ## Evidence And Privacy Boundary
67
81
 
68
- The inspector reads file-backed evidence; it does not reconstruct hidden provider reasoning or claim access to data Pi did not persist. When no explicit thinking block exists, detail shows `reasoning unavailable`.
82
+ The inspector reads file-backed evidence; it does not reconstruct hidden provider reasoning or claim access to data Pi did not persist. When no explicit thinking block exists, Execution reports `thinking: not persisted`.
69
83
 
70
84
  Session text, communication bodies, and structured values remain bounded. Common secret-bearing keys, camelCase/private-key credentials, serialized JSON credentials, and inline credential patterns are redacted before rendering. Malformed JSONL lines, missing parents, cycles, missing sessions, and incomplete tool correlation remain diagnostic states rather than inferred data.
71
85
 
@@ -218,7 +218,7 @@ Runtime wake notifications are now modeled separately from durable queues. Messa
218
218
 
219
219
  The launching coordinator should not busy-poll long-running async runs. The extension watches run state directories and queues terminal `done`/`failed`/unhandled `killed`/`exited` transitions back to the owning session through Pi's `followUp` delivery mode with `triggerTurn: true`; a busy coordinator finishes its current work before queued actor results arrive, while an idle coordinator starts a normal turn without a racy manual idle check. Pi's configured `followUpMode` determines whether concurrently queued results arrive together or one at a time. Script-authored `notify`/`followup` actor messages still follow their declared outbox delivery policy. Terminal notifications include recipe-level named `artifacts` when declared. The generic runner also emits compact `command.done` actor messages for completed leaf commands; recipe authors declare that capability in `mailbox.emits` rather than configuring a separate delivery policy. Failures and in-flight parallel branch completions can bubble according to outbox policy, while successful final leaf completions stay diagnostic to avoid flooding long sequential pipelines. Intentional `control.kill` and recipe-local stop commands stay out of coordinator context because the initiating message already returns synchronously or is handled by actor-local policy. If a notification asks for direction, answer with `message` rather than starting a polling loop. Use explicit `inspect` only when a delivered notification requests inspection, a real decision depends on state, or a suspected stuck run needs diagnosis — never merely because a timeout elapsed.
220
220
 
221
- Ambient status indicators may refresh while work is active, but coordinator attention is driven from run-state changes rather than a coordinator agent loop. This lets the coordinator continue other work after `spawn`; the run signals back through lifecycle state, results, and actor messages. An owned terminal run without `terminal-handled.json` remains retry-eligible during same-runtime and extension/session replacement reconciliation; the marker is written only after successful follow-up delivery, and initial reconciliation does not replay historical outbox traffic. This is an at-least-once contract: a process crash after send but before marker persistence can produce a duplicate notification, while a failed send remains durably retryable. The ambient triangle count represents active async work units: each running async run contributes at least one triangle, and a run with multiple active parallel command/subagent branches contributes the reported active branch count. If a coordinator starts one parent run with four active parallel branches, four triangles are shown; if the same coordinator starts five independent single-branch runs, five triangles are shown.
221
+ Ambient status indicators may refresh while work is active, but coordinator attention is driven from run-state changes rather than a coordinator agent loop. This lets the coordinator continue other work after `spawn`; the run signals back through lifecycle state, results, and actor messages. File-system watchers accelerate live discovery, while a bounded ten-second terminal-only reconciliation pass scans owned unhandled terminal state without reading or replaying outbox traffic. Failed root or run-directory watcher attachment, runtime errors, error-driven watcher removal, and successful rearm remain available as bounded runtime diagnostics; normal run-directory deletion stays quiet; reconciliation rearms degraded watchers but does not depend on them. An owned terminal run without `terminal-handled.json` remains retry-eligible during same-runtime and extension/session replacement reconciliation; the marker is written only after successful follow-up delivery, and initial reconciliation does not replay historical outbox traffic. Watch-triggered and periodic delivery share an in-flight guard so one live runtime sends one follow-up when both paths race. This is an at-least-once contract: a process crash after send but before marker persistence can produce a duplicate notification, while a failed send remains durably retryable. The ambient triangle count represents active async work units: each running async run contributes at least one triangle, and a run with multiple active parallel command/subagent branches contributes the reported active branch count. If a coordinator starts one parent run with four active parallel branches, four triangles are shown; if the same coordinator starts five independent single-branch runs, five triangles are shown.
222
222
 
223
223
  ## Run Actor Messages
224
224
 
@@ -246,7 +246,7 @@ Use coordinator/session-bound messages for completion and decision points, not f
246
246
 
247
247
  An async run belongs to the current user, cwd, and launching agent session at start time. Send, cancellation, and force-kill target only the recorded runner pid when command line and cwd still match the recorded owner data. Stale pid reuse must fail closed.
248
248
 
249
- On Unix-like systems, `control.kill` signals the runner process group when available, then falls back to the runner pid. On native Windows, `control.kill` uses Windows process-tree termination through the platform adapter. The runner starts command-template children in the owned process tree, so long-running descendants such as audio players should stop with the run instead of becoming orphaned background processes. A recipe may manage a true detached daemon, but then daemon ownership is recipe-local: the script must persist and verify a pid or service handle, expose status/stop behavior, and bridge `control.kill` to daemon cleanup. The generic runner does not scan for or guess detached services. After the process exits, status reflects the operator action as `killed` instead of a generic `exited`. Programmatic `cancelRun()` remains an internal lifecycle helper for retirement and tests, but it is not a documented actor-message action.
249
+ Immediately before signaling, control revalidates the persisted process identity a second time inside the state-directory lifecycle lock. On Unix-like systems, `control.kill` signals the runner process group when available and falls back to the exact runner pid only when group signaling returns `ESRCH` and one additional identity revalidation still matches; authorization and permission errors fail closed without fallback. On native Windows, `control.kill` uses Windows process-tree termination through the platform adapter. The runner starts command-template children in the owned process tree, so long-running descendants such as audio players should stop with the run instead of becoming orphaned background processes. A recipe may manage a true detached daemon, but then daemon ownership is recipe-local: the script must persist and verify a pid or service handle, expose status/stop behavior, and bridge `control.kill` to daemon cleanup. The generic runner does not scan for or guess detached services. After the process exits, status reflects the operator action as `killed` instead of a generic `exited`. Programmatic `cancelRun()` remains an internal lifecycle helper for retirement and tests, but it is not a documented actor-message action. Node does not expose one portable identity-stable process-group handle across Linux, macOS, and Windows, so a runner can theoretically exit and its PID/PGID can be reused after the final identity read but before the OS signal call. Generation fencing, lifecycle serialization, immediate revalidation, and error-specific fallback minimize this residual platform window; docs and evidence must not claim pidfd/handle-level atomic signaling where the host cannot provide it.
250
250
 
251
251
  State is append-only where practical. Final result writes should be atomic. Recipe-local control endpoints and actor-message logs may live in the state dir. pi-actors core owns the generic run-local message adapter and runtime attention policy; command and message vocabularies belong to the recipe/script.
252
252
 
@@ -270,6 +270,14 @@ Rules:
270
270
  - The `runs` state root is preserved by startup cleanup; run lifecycle cleanup must be explicit and run-aware.
271
271
  - State that must survive restarts belongs in the agent root, not in `tmp`.
272
272
 
273
+ ## Parent Session Teardown
274
+
275
+ Async actors may outlive individual agent turns. Every Pi `session_shutdown` reason (`quit`, `reload`, `new`, `resume`, or `fork`) scans persisted run state and attempts teardown for discovered readable `running` runs whose exact `ownerId` matches the retiring coordinator session. Each new run persists immutable `run_instance_id`; teardown carries expected owner/generation into canonical `control.kill`, which compares both while holding the state-directory lifecycle lock shared with restart. Missing ownership or generation fails closed; terminal, ambiguous, changed-generation, and other-session runs remain untouched. Teardown never signals processes directly.
276
+
277
+ Teardown remains idempotent and best-effort across discovered siblings: one signal, process-proof, or evidence-write failure does not block later candidates. A `run.parent_teardown` event is written only while the selected generation still owns that state directory; replacement generations cannot receive stale teardown evidence. Successful kills retain `run.kill`, terminal progress, process-identity fencing, and handled-marker evidence.
278
+
279
+ This boundary intentionally does not run at ordinary `agent_end`. Teardown uses unbounded directory discovery rather than the ordinary index depth cap. Unreadable directories and corrupt run state become explicit failures, and every invocation persists a bounded summary under `<run-root>/teardown/`; shutdown warnings include that path when failures remain. Actors launched by descendant Pi sessions deliberately remain outside the exact-owner contract and rely on their own session shutdown hook. A hard OS/process kill can still prevent either hook; use the persisted summary plus OS-level/manual recovery for an orphan that a replacement session cannot control safely.
280
+
273
281
  ## Ambient Observability
274
282
 
275
283
  Interactive sessions expose compact activity with minimal screen cost:
@@ -13,14 +13,16 @@ Helper scripts that belong to library recipes live in root `scripts/`. The music
13
13
 
14
14
  ## Install Locally
15
15
 
16
- Recipes can be copied into the user recipe root:
16
+ Select only the operator-facing recipe or wrapper you intend to own locally. For example:
17
17
 
18
18
  ```bash
19
19
  mkdir -p ~/.pi/agent/recipes
20
- cp <repo>/recipes/*.json ~/.pi/agent/recipes/
20
+ cp <repo>/recipes/pipeline-review-readiness.json ~/.pi/agent/recipes/
21
21
  ```
22
22
 
23
- Or a registered tool can point directly at a recipe path when that is more convenient.
23
+ Do not bulk-copy `recipes/*.json`. The packaged library also contains internal composition stages, including `draft-review.json` and `tool-review.json`; the automatic-review runtime launches those selectors with fenced inputs and they must not become user-installed callable tools.
24
+
25
+ A registered tool can instead point at one selected recipe path when a durable operator-facing name is useful. Prefer a thin wrapper for public defaults or policy rather than copying the wrapper's internal imports.
24
26
 
25
27
  ## Async Subagent Components
26
28
 
@@ -32,6 +34,8 @@ Core subagent recipes:
32
34
  - `recipes/subagent-preflight.json`: Tiny model/thinking/tool-policy smoke check before expensive fanout; failures surface `ACTOR_PREFLIGHT_FAILED` with stage, selected policy, provider error class, prompt file, and override args.
33
35
  - Packaged reviewer, verifier, merger, judge, and normalizer stages use `accept_output: review_evidence` and require `ACTOR_REVIEW_RESULT` as the exact first non-whitespace output line. Marker prefixes, format acknowledgements, and input requests therefore remain rejected branch diagnostics rather than usable quorum evidence.
34
36
  - `recipes/subagent-review.json`: Evidence-grounded review lens.
37
+ - `recipes/draft-review.json`: Internal no-tools selector for one immutable automatic draft batch. It receives an attached value-free structural projection with batch-local opaque occurrence/content-group identities, counts, risk labels, and usage—not canonical names, draft basenames, raw hashes, recipe bodies, template text, defaults, authored prose, or filesystem paths—then emits one terminal `DRAFT_REVIEW_RESULT` with quota-free promote/discard decisions. The executor derives any promotion from the separate trusted captured source.
38
+ - `recipes/tool-review.json`: Internal no-tools selector for one immutable 36-tool portfolio. It receives the same identity-opaque value-free structural projection and may recommend quota-free keep, unchanged-source rename (`evolve`), unchanged-source demote, or identical-source merge decisions. `replace`, `split`, and returned recipe content fail mechanically; deterministic executors alone read trusted captured recipes and own validated safe-boundary activation.
35
39
  - `recipes/subagent-critic.json`: Assumption and failure-mode critique.
36
40
  - `recipes/subagent-plan.json`: Bounded plan slices and validation gates.
37
41
  - `recipes/subagent-evidence-map.json`: Evidence and confidence map.
@@ -107,20 +107,13 @@ The high-priority user recipe directory is also the default tool set: recipes pl
107
107
 
108
108
  Higher-priority files shadow lower-priority files with the same basename. Within one priority layer, same-id JSON shadows Markdown because JSON is the canonical precise format. A highest-priority invalid recipe is still visible and blocks fallback so operators do not accidentally run packaged behavior when a user override is broken. A highest-priority recipe with `disabled: true` also blocks fallback, is not launchable, and intentionally disables that id. Healthy overrides are silent; failed bare-name launches caused by invalid or disabled shadowing include compact `reason=shadowed_invalid` or `reason=shadowed_disabled` diagnostics with the active path, blocked candidate, and recipe-doctor hint.
109
109
 
110
- ## Usage Metadata
110
+ ## Usage And Lineage Metadata
111
111
 
112
- User-owned recipe launches may accumulate extension-maintained usage metadata in `.usage/<recipe-filename>.json` sidecars:
112
+ User-owned recipe launches update extension-maintained lineage ledgers under `.usage/recipes/<recipe-name>.json` plus a path index. Authored recipes remain untouched. The ledger keeps lifetime and revision-local launch counts, executable fingerprints, former names and paths, bounded revision ancestry, transition events, and review epochs.
113
113
 
114
- ```json
115
- {
116
- "calls": 12,
117
- "last_called": "2026-05-22T10:30:00.000Z"
118
- }
119
- ```
120
-
121
- The extension increments `calls` and updates `last_called` when it starts that concrete recipe, either through a recipe-backed tool call or a direct async recipe-file run. The sidecar also stores a content `fingerprint`; if authored recipe content changes, the next launch resets `calls` before counting the new launch and records `reset_at`. Keeping telemetry outside the recipe prevents usage writes from replacing concurrent operator edits; discovery merges sidecar usage into inspection. Agents should treat these fields as cleanup evidence, not as authored recipe contract. Packaged standard-library recipes do not receive usage metadata.
114
+ Lifetime usage survives rename, promotion, demotion, and executable revision. Revision-local counters restart when executable content changes, making the new fingerprint eligible for portfolio review without erasing prior evidence. Discovery merges current lineage evidence into inspection; packaged standard-library recipes receive no mutable usage ledger. The unreleased format stays intentionally unversioned until a public compatibility boundary exists.
122
115
 
123
- There is intentionally no failure counter in the recipe contract. A failed launch can reflect caller misuse, missing runtime values, or an environmental problem rather than recipe uselessness. Cleanup decisions should be explicit operator work: keep as a tool, move out of the agent recipe root to retain recipe-only memory, merge, delete, or archive.
116
+ There is intentionally no failure counter: a failed launch can reflect caller misuse, missing values, or environment state rather than recipe quality. Usage remains evidence, not an automatic usefulness verdict. Automatic review combines it with contract quality, portability, duplication, safety, and likely future value. `register_tool draft=...` is the preferred fenced single-draft override; a deliberate move/copy from `drafts/` into the recipe root also remains valid, though it may defer an already captured automatic batch.
124
117
 
125
118
  For object form, keep `template` last. Recipe metadata comes first; executable content stays last.
126
119
 
@@ -10,13 +10,15 @@ The registry source is location-discovered recipes, not a live tool-only JSON fi
10
10
 
11
11
  - `~/.pi/agent/recipes/*.json` and `*.md` are the highest-priority user recipe root and the operator-managed tool set.
12
12
  - Recipes in that root are tools by location.
13
- - `~/.pi/agent/recipes/drafts/*.json` stores captured inline-spawn draft recipes, not registered tools. Promote one with `register_tool name=<tool_name> draft=<draft_path>` or by manually moving/copying it up one level into `~/.pi/agent/recipes`. `inspect target=recipes view=summary` reports their count, and verbose output lists their paths, timestamps, fingerprints, validation state, source run when known, descriptions, and template previews for explicit replay or promotion.
13
+ - `~/.pi/agent/recipes/drafts/*.json` stores captured inline-spawn draft recipes, not registered tools. Twelve drafts trigger one silent automatic exact-batch review after the foreground turn and active actors finish; the deterministic executor promotes or discards every reviewed source while preserving newer drafts for the next batch. Promote one earlier with the fenced `register_tool name=<tool_name> draft=<draft_path>` override or a deliberate move/copy into the recipe root. A direct filesystem promotion remains legitimate, but it can shrink or invalidate a captured batch and defer its automatic cleanup; lineage reattaches on the next launch when the move remains unambiguous. No manual batch-consolidation command exists. `inspect target=recipes view=summary` reports draft count, and verbose output lists paths, timestamps, fingerprints, validation state, source run when known, descriptions, and template previews.
14
14
  - Packaged pi-actors recipes are the lower-priority standard library of declarative actor config components, not automatically registered tools.
15
15
  - Ad hoc recipe files outside the user recipe root are components unless explicitly registered/copied into `~/.pi/agent/recipes`.
16
- - Recipe identity is the filename basename; `~/.pi/agent/recipes/docs_review.json` and `docs_review.md` both have id/tool name `docs_review`.
16
+ - The current tool name is the filename basename; `~/.pi/agent/recipes/docs_review.json` and `docs_review.md` both expose `docs_review`. A canonical name-and-priority lineage ledger preserves usage and revision history as draft/active location changes; controlled rename transfers that history to the new name and retains the former name.
17
+ - Set `PI_ACTORS_AUTOMATIC_REVIEW=off` (also accepts `false` or `0`) before starting Pi to disable draft/tool reviewer scheduling and safe-boundary portfolio activation; runtime status exposes the effective policy.
18
+ - Active user recipes receive zero-call lineage without fabricating launches. Once thirty-six non-sensitive current revisions lack a review fingerprint, pi-actors captures the oldest exact portfolio and silently attaches an identity-opaque value-free structural projection with batch-local equality-only content groups to the no-tools `tool-review` actor after a foreground turn and only while no other actor runs. A content revision resets revision-local usage and becomes eligible again while lifetime usage remains continuous. The reviewer may select only `keep`, unchanged-source rename (`evolve`), unchanged-source `demote`, or `merge` for canonically identical captured recipes; it never returns recipe content. `replace`, `split`, and executable contract changes require explicit operator authoring. Completed output becomes an immutable size-bounded approval plan only after exact result, source-hash, target-collision, and lineage-projection validation; approval itself never mutates active recipes. At the next `session_start`, filesystem commit persists `lineage_pending`, journaled lineage records roll forward, `completed` persists, and only then may quarantine be removed before runtime tool discovery.
17
19
  - Same-id JSON shadows Markdown in the same priority layer.
18
20
 
19
- Because the user recipe directory is sticky agent muscle memory, runtime launches update `usage.calls`, `usage.last_called`, and a content `usage.fingerprint` in `.usage/<recipe-filename>.json` sidecars rather than rewriting authored recipe files. If authored recipe content changes, the next launch resets `usage.calls` and records `usage.reset_at` before counting the launch, so usage evidence follows the current recipe meaning without racing operator edits. Discovery and file-watcher refresh merge sidecar usage into inspect summaries. `inspect target=recipes view=summary verbose=true` includes usage metadata and operator-gated cleanup recommendations for invalid, shadowed, disabled, component-only, unused, or overriding recipes. Recommended actions stay explicit: keep as a tool/component, enable, merge, fix, delete, or archive. The extension does not maintain a failure counter and agents should not silently clean tools during unrelated work.
21
+ Because the user recipe directory is sticky agent muscle memory, runtime launches update a stable lineage ledger under `.usage/recipes/<recipe-name>.json` plus a priority-compatible path index rather than rewriting authored recipe files. Launch accounting briefly shares the canonical recipe-root fence used by portfolio activation before taking index/ledger locks; activation therefore cannot quarantine a source between launch authorization and accounting. If activation already changed the loaded source, the stale invocation rejects with a reload-and-retry error instead of executing without usage evidence. `lifetime_calls` and the compatibility `calls` view survive rename, promotion, demotion, and content revision; `revision_calls` restarts only when the executable fingerprint changes. The bounded ledger retains former paths/names, revision ancestry, promotion/demotion events, and review epochs. An unambiguous external rename follows its prior lineage by fingerprint. Because automatic review has not shipped publicly, its inputs, results, admission state, plans, journals, evidence, lineage storage, and snapshots remain unversioned rather than carrying migration branches for discarded internal iterations. Discovery and file-watcher refresh merge ledger usage into inspect summaries. `inspect target=recipes view=summary verbose=true` includes usage metadata and operator-gated cleanup recommendations for invalid, shadowed, disabled, component-only, unused, or overriding recipes. The extension does not maintain a failure counter.
20
22
 
21
23
  `register_tool` is the preferred agent-facing mutation API. It creates, updates, and deletes recipe files in `~/.pi/agent/recipes`; agents do not need to edit the files directly for normal registration. Extension-authored register, update, delete, draft-promotion, and usage-metadata mutations hold a cross-process lock keyed by filesystem-canonical recipe identity across the complete check/read/write/runtime-update window. Existing targets or the nearest existing parent are resolved through `realpath`, so real and symlink aliases serialize while unrelated recipes remain independent; stale locks are reclaimed only after their owner is proven dead. Direct file edits are still valid for operators and advanced agents. Runtime behavior is reactive: file creation, deletion, or edits in the user recipe root trigger validation and tool-set refresh, with invalid recipes surfaced as diagnostics rather than silently ignored. If the recipe root does not exist at session start, an advisory parent watcher detects its creation and switches to the normal root watcher; deletion or rename rearms the parent watcher without polling.
22
24
 
@@ -27,10 +29,15 @@ inspect target=tool:pi-actors view=status
27
29
  inspect target=tool:pi-actors view=triage
28
30
  inspect target=recipes view=status
29
31
  inspect target=recipes view=doctor
32
+ inspect target=recipes view=reviews
30
33
  inspect target=recipes view=summary verbose=true
31
34
  ```
32
35
 
33
- `tool:pi-actors` is a reserved runtime-status actor. `view=status` reports the loaded package version, package root, source/dist mode, entrypoint path, recipe roots, and git commit when available. Use it after reloads to confirm which extension code is actually live. `view=triage` adds a compact attention surface for active runs, other-session runs, invalid or blocking recipes, exposed tool recipes with non-lifecycle risk labels, drafts, stale claims, failed runs, attention messages, and next inspect actions without repairing anything. Packaged components and recipes whose only label is `risk.long_running` stay in recipe doctor/summary evidence rather than triage attention.
36
+ `inspect target=recipes view=reviews` returns bounded read-only evidence for automatic draft/tool review phases, decision counts, garbage collection, lineage revisions, demotions, rollback provenance, retained revision snapshots, and bounded `failed_stage`/`last_error`/`next_action` fields. It never starts a review or generates a follow-up turn. Automatic reviewers receive only an attached value-free projection—counts, risk labels, bounded usage, and command-graph shape without recipe bodies, template/default values, authored prose, or filesystem paths—and no general filesystem or mutation tools. Internal snapshot rollback writes one CAS-authenticated journal before changing either recipe or lineage state; interruption after either write rolls forward on the next identical rollback request instead of returning a permanently split recipe/ledger state.
37
+
38
+ Explicit recovery stays inside the existing actor-message surface: send `review.retry` or `review.reset` to `tool:pi-actors` with `body={"scope":"draft"}` or `body={"scope":"tool"}`. Retry resets bounded launch/processing counters and reuses the immutable batch. If a draft transaction journal already exists, retry preserves the original reviewer run and resumes that authenticated journal plan; even changed reviewer stdout cannot redirect committed recipe or lineage targets. When a tool transaction already committed, retry preserves approval/transaction evidence and returns to the safe activation/lineage boundary rather than launching another reviewer. Reset removes only disposable failed/completed admission state and rejects tool cycles that still carry recovery evidence.
39
+
40
+ `tool:pi-actors` is a reserved runtime-status/control actor. `view=status` reports the loaded package version, package root, source/dist mode, entrypoint path, recipe roots, automatic-review policy, and git commit when available. Use it after reloads to confirm which extension code is actually live. `view=triage` adds a compact attention surface for active runs, other-session runs, invalid or blocking recipes, exposed tool recipes with non-lifecycle risk labels, drafts, stale claims, failed runs, attention messages, and next inspect actions without repairing anything. Packaged components and recipes whose only label is `risk.long_running` stay in recipe doctor/summary evidence rather than triage attention.
34
41
 
35
42
  The recipe summary reports active, shadowed, invalid, disabled, and diagnostic entries so operators can answer why a tool is present, hidden, broken, or disabled. The doctor view keeps the same registry evidence but promotes an advisory action surface: compact output includes the highest-priority `top` remediation, risk-label counts, and ordered actions for invalid/blocking, disabled, risky shell-boundary, and shadowed recipes. Verbose inspection keeps per-recipe `risk_labels`, the structured `risk_summary`, `remediations`, `top_action`, diagnostic details, and blocked lower-priority fallback paths when a broken or disabled higher-priority recipe masks a fallback. Risk labels are deterministic review aids, not execution blockers or sandbox claims.
36
43
 
package/index.ts CHANGED
@@ -5,80 +5,31 @@
5
5
  * Wraps command templates as callable pi tools, stores durable user tools as recipe files, and exposes actor orchestration across reloads and sessions.
6
6
  */
7
7
 
8
- import * as AsyncRuns from "./lib/async-runs.ts";
8
+ import * as AutomaticReviewRuntime from "./lib/automatic-review-runtime.ts";
9
9
  import * as CommandTemplates from "./lib/command-templates.ts";
10
- import * as InspectorOverlay from "./lib/inspector-overlay.ts";
11
- import * as Observability from "./lib/observability.ts";
10
+ import * as InspectorCommand from "./lib/inspector-command.ts";
12
11
  import * as Paths from "./lib/paths.ts";
13
12
  import * as Pi from "./lib/pi.ts";
14
13
  import * as Prompts from "./lib/prompts.ts";
14
+ import * as RunUiRuntime from "./lib/run-ui-runtime.ts";
15
15
  import * as Runtime from "./lib/runtime.ts";
16
16
  import * as Temp from "./lib/temp.ts";
17
17
  import * as Tools from "./lib/tools.ts";
18
18
  import * as ToolsResponse from "./lib/tools-response.ts";
19
19
 
20
20
  export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
21
- let runsAnimationInterval: NodeJS.Timeout | undefined;
22
- let runsNotifyTimeout: NodeJS.Timeout | undefined;
23
21
  let activeRunContext: Pi.ExtensionContext | undefined;
24
- const runUi = Observability.createRunUiObservationState();
25
- const retirementAttempts = new Set<string>();
26
22
  const getRunOwnerId = Pi.getSessionId;
27
- const retireCandidateRuns = (
28
- ctx: Pi.ExtensionContext,
29
- summary: Observability.RunSummary,
30
- ): void => {
31
- void Observability.executeRunRetirements(summary, {
32
- attempted: retirementAttempts,
33
- cancelRun: (candidate) => AsyncRuns.cancelRun(candidate.stateDir),
34
- notify: (message, level) => ctx.ui.notify(message, level),
35
- sendStop: (candidate) =>
36
- AsyncRuns.sendRunMessage(candidate.stateDir, "stop"),
37
- });
38
- };
39
- const updateRunUi = (
40
- ctx: Pi.ExtensionContext,
41
- notify = false,
42
- terminalOnly = false,
43
- ): void => {
44
- const ownerId = getRunOwnerId(ctx);
45
- const snapshot = Observability.readRunUiSnapshot(runUi, ownerId);
46
- ctx.ui.setStatus(
47
- "zz-pi-actors-runs",
48
- snapshot.status ? ctx.ui.theme.fg("dim", snapshot.status) : undefined,
49
- );
50
- if (!notify) return;
51
- const notificationSink = Pi.createNotificationSink(pi, ctx);
52
- retireCandidateRuns(ctx, snapshot.summary);
53
- Observability.deliverRunTransitionNotifications(
54
- snapshot.transitions,
55
- notificationSink,
56
- );
57
- Observability.pruneRunUiObservationState(runUi, snapshot);
58
- if (!terminalOnly) {
59
- Observability.deliverRunOutboxNotifications(
60
- snapshot.outboxEvents,
61
- notificationSink,
62
- );
63
- }
64
- };
65
- const closeRunWatchers = (): void => {
66
- runWatcher.close();
67
- if (runsNotifyTimeout) clearTimeout(runsNotifyTimeout);
68
- runsNotifyTimeout = undefined;
69
- };
70
- const scheduleRunEventUpdate = (ctx: Pi.ExtensionContext): void => {
71
- if (runsNotifyTimeout) clearTimeout(runsNotifyTimeout);
72
- runsNotifyTimeout = setTimeout(() => {
73
- runWatcher.refresh();
74
- updateRunUi(ctx, true);
75
- }, 50);
76
- runsNotifyTimeout.unref?.();
77
- };
78
- const runWatcher = Observability.createRunStateWatcher({
79
- stateRoot: Paths.EXTENSION_RUNTIME_PATHS.runStateRoot,
80
- onChange: () =>
81
- activeRunContext && scheduleRunEventUpdate(activeRunContext),
23
+ const automaticReview = AutomaticReviewRuntime.createAutomaticReviewRuntime({
24
+ getActiveContext: () => activeRunContext,
25
+ getRunOwnerId,
26
+ getThinkingLevel: () => pi.getThinkingLevel(),
27
+ });
28
+ const runUiRuntime = RunUiRuntime.createRunUiRuntime({
29
+ getActiveContext: () => activeRunContext,
30
+ getRunOwnerId,
31
+ onRunEvent: automaticReview.schedule,
32
+ pi,
82
33
  });
83
34
  const actorToolDefinitions = new Map<string, Tools.ActorToolDefinition>();
84
35
  const withCurrentThinkingContext = <T extends Tools.ActorToolDefinition>(
@@ -127,50 +78,26 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
127
78
  // Clear the pre-overlay widget after hot reloads from older pi-actors builds.
128
79
  ctx.ui.setWidget("zz-pi-actors-comms", undefined);
129
80
  activeRunContext = ctx;
81
+ runUiRuntime.close();
82
+ automaticReview.close();
83
+ recipeReload.close();
130
84
  await Temp.prepareExtensionTempDir(Paths.EXTENSION_RUNTIME_PATHS.tempDir);
85
+ if (activeRunContext !== ctx) return;
86
+ automaticReview.start(ctx);
131
87
  runtime.loadTools(ctx);
132
- updateRunUi(ctx, true, true);
133
- closeRunWatchers();
134
- recipeReload.close();
135
- runWatcher.refresh();
88
+ runUiRuntime.start(ctx);
136
89
  recipeReload.watch(ctx);
137
- if (runsAnimationInterval) clearInterval(runsAnimationInterval);
138
- runsAnimationInterval = setInterval(() => updateRunUi(ctx, false), 1000);
139
- runsAnimationInterval.unref?.();
140
90
  });
141
- pi.on("session_shutdown", async () => {
142
- if (runsAnimationInterval) clearInterval(runsAnimationInterval);
143
- runsAnimationInterval = undefined;
91
+ pi.on("agent_end", async (_event, ctx) => {
92
+ if (activeRunContext === ctx) automaticReview.schedule();
93
+ });
94
+ pi.on("session_shutdown", async (event, ctx) => {
144
95
  activeRunContext = undefined;
145
- closeRunWatchers();
96
+ automaticReview.close();
146
97
  recipeReload.close();
98
+ runUiRuntime.shutdown(event.reason, ctx);
147
99
  });
148
- pi.registerCommand("actors-inspector-toggle", {
149
- description: "Toggle the keyboard-driven actor inspector overlay",
150
- handler: async (_args, ctx) => {
151
- ctx.ui.setWidget("zz-pi-actors-comms", undefined);
152
- await ctx.ui.custom<void>(
153
- (tui, theme, _keybindings, done) =>
154
- new InspectorOverlay.ActorInspectorOverlay({
155
- done,
156
- ownerId: getRunOwnerId(ctx),
157
- stateRoot: Paths.EXTENSION_RUNTIME_PATHS.runStateRoot,
158
- theme,
159
- tui,
160
- }),
161
- {
162
- overlay: true,
163
- overlayOptions: {
164
- anchor: "center",
165
- width: "94%",
166
- minWidth: 72,
167
- maxHeight: "94%",
168
- margin: 1,
169
- },
170
- },
171
- );
172
- },
173
- });
100
+ InspectorCommand.registerActorInspectorCommand(pi, getRunOwnerId);
174
101
  pi.on("before_agent_start", async (event) => ({
175
102
  systemPrompt: `${event.systemPrompt}\n\n${Prompts.ONBOARDING_SYSTEM_PROMPT}`,
176
103
  }));
@@ -180,11 +107,10 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
180
107
  configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
181
108
  getActiveTools: () => pi.getActiveTools(),
182
109
  getRuntimeTool: (name) =>
183
- Tools.resolveActiveRuntimeTool(
184
- name,
185
- runtime.getTools(),
186
- (activeName) => actorToolDefinitions.get(activeName),
110
+ Tools.resolveActiveRuntimeTool(name, runtime.getTools(), (activeName) =>
111
+ actorToolDefinitions.get(activeName),
187
112
  ),
113
+ handleRuntimeMessage: automaticReview.handleMessage,
188
114
  registryRuntime: runtime,
189
115
  setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
190
116
  }).map(withCurrentThinkingContext),