@llblab/pi-actors 0.20.2 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKLOG.md +34 -80
- package/CHANGELOG.md +29 -0
- package/README.md +7 -1
- package/dist/index.js +11 -0
- package/dist/lib/actor-rooms.d.ts +1 -0
- package/dist/lib/actor-rooms.js +33 -1
- package/dist/lib/async-runs.d.ts +35 -1
- package/dist/lib/async-runs.js +318 -36
- package/dist/lib/command-templates.js +8 -1
- package/dist/lib/observability.d.ts +15 -0
- package/dist/lib/observability.js +103 -18
- package/dist/lib/recipe-discovery.js +13 -5
- package/dist/lib/recipe-references.js +137 -11
- package/dist/lib/runtime-notifier.d.ts +48 -0
- package/dist/lib/runtime-notifier.js +137 -0
- package/dist/lib/tools.js +18 -9
- package/docs/README.md +1 -1
- package/docs/actor-messages.md +8 -3
- package/docs/async-runs.md +7 -5
- package/docs/recipe-library.md +25 -8
- package/docs/template-recipes.md +37 -7
- package/docs/tool-registry.md +4 -3
- package/index.ts +14 -0
- package/lib/actor-rooms.ts +46 -1
- package/lib/async-runs.ts +433 -49
- package/lib/command-templates.ts +8 -1
- package/lib/observability.ts +133 -20
- package/lib/recipe-discovery.ts +21 -6
- package/lib/recipe-references.ts +141 -17
- package/lib/runtime-notifier.ts +207 -0
- package/lib/tools.ts +36 -13
- package/package.json +2 -3
- package/recipes/music-player.json +1 -1
- package/recipes/pipeline-room-swarm.json +1 -1
- package/scripts/coordinator.mjs +276 -135
- package/scripts/locker.mjs +87 -28
- package/scripts/music-player.mjs +401 -94
- package/scripts/validate-recipe.mjs +2 -2
- package/skills/actors/SKILL.md +10 -9
- package/skills/swarm/SKILL.md +1 -1
- package/index.js +0 -19
package/BACKLOG.md
CHANGED
|
@@ -2,78 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
## Open Work
|
|
4
4
|
|
|
5
|
-
###
|
|
6
|
-
|
|
7
|
-
- Priority: Medium.
|
|
8
|
-
- Goal: Keep direct branch message queues reliable for long-lived interactive branch runners without unbounded rewrite amplification.
|
|
9
|
-
- Direction:
|
|
10
|
-
- Evaluate current whole-file branch inbox status rewrites under realistic long-lived direct-message workloads.
|
|
11
|
-
- Consider bounded retention, compaction, or append-only transition logs for `queued` / `claimed` / `handled` / `failed` state changes while preserving stable message IDs and exact-once claim semantics.
|
|
12
|
-
- Keep branch-local inbox append/status mutations lock-guarded and preserve inspector visibility for unread/current-branch filters.
|
|
13
|
-
- Exit:
|
|
14
|
-
- A documented decision or implementation explains how branch inboxes scale for persistent runners and proves existing direct-message semantics remain compatible.
|
|
15
|
-
|
|
16
|
-
### Installed Recipe Trust Boundary Hardening
|
|
17
|
-
|
|
18
|
-
- Priority: Medium.
|
|
19
|
-
- Goal: Keep recipe-library growth local-first without letting operator muscle memory become an accidental sandbox bypass.
|
|
20
|
-
- Direction:
|
|
21
|
-
- Review packaged recipes, examples, and docs for destructive or external side effects and ensure they require explicit paths, typed args, narrow helper scripts, and clear operator gates.
|
|
22
|
-
- Keep warnings framed as diagnostics, not a security boundary.
|
|
23
|
-
- Prefer small audited helper scripts over broad shell templates when recipes touch files, processes, networks, or external services.
|
|
24
|
-
- Exit:
|
|
25
|
-
- A trust-boundary review confirms packaged recipes and docs preserve the current local-first/not-sandbox-first contract, with any needed hardening captured in tests or docs.
|
|
26
|
-
|
|
27
|
-
### Direct Branch Message Consumption Semantics
|
|
28
|
-
|
|
29
|
-
- Priority: Medium.
|
|
30
|
-
- Goal: Make it impossible to misunderstand branch inbox delivery as universal active delivery without a consuming coordinator or runner protocol.
|
|
31
|
-
- Direction:
|
|
32
|
-
- Audit README, actor-message docs, async-run docs, actors skill, and recipe guidance for branch inbox wording.
|
|
33
|
-
- Clarify that direct branch messages are queued and become active work only when the relevant coordinator/runner claims, injects, handles, or fails them.
|
|
34
|
-
- Add or update a bounded smoke scenario that demonstrates queued direct messages, claim/handle transitions, and inspector visibility.
|
|
35
|
-
- Exit:
|
|
36
|
-
- Public docs and tests show both halves of direct branch delivery: durable branch-local queueing and explicit worker consumption semantics.
|
|
37
|
-
|
|
38
|
-
### Actor Rooms, Roster, and Cross-Branch Messaging
|
|
5
|
+
### Native Windows Smoke and Runtime Notification Follow-up
|
|
39
6
|
|
|
40
7
|
- Priority: High.
|
|
41
|
-
-
|
|
8
|
+
- Target: Post-0.22.0 validation and hardening.
|
|
9
|
+
- Goal: Validate the cross-platform actor wake/notification layer on native Windows without changing the public actor API or replacing file-backed actor state as the observable source of truth.
|
|
10
|
+
- Decision:
|
|
11
|
+
- Keep files as durable truth: mailbox/state/event/room files remain canonical for `inspect`, observability, crash recovery, and replay.
|
|
12
|
+
- Treat runtime notification as an advisory wake layer, not as the queue itself.
|
|
13
|
+
- Preserve `spawn`, `message`, and `inspect` as the only public actor API; platform transport choices stay internal.
|
|
14
|
+
- Progress:
|
|
15
|
+
- Prepared the cross-platform runtime notification layer for release with a file-backed runtime notifier boundary using `notify(actor)` / `subscribe(actor, onWake)`, persisted `wake.jsonl` records, `fs.watch` subscription, and periodic fallback coverage.
|
|
16
|
+
- Notifier subscriptions can now receive explicit reconciliation callbacks for initial scan, wake-triggered scans, and polling fallback.
|
|
17
|
+
- Run-local `message` delivery now records the durable run inbox entry and advisory wake before attempting the optional live control endpoint; successful endpoint delivery marks the inbox entry `sent`.
|
|
18
|
+
- Run mailbox inspection now shows recent durable inbox entries alongside recipe-declared mailbox metadata.
|
|
19
|
+
- Added run inbox claim/handle/fail helpers for runtime loops, including locked claims so reconciliation callbacks can safely dispatch queued mailbox work once.
|
|
20
|
+
- Added mailbox-only run control endpoints so runtimes can accept `message` through durable inbox/wake state without requiring FIFO or named-pipe delivery.
|
|
21
|
+
- Migrated the packaged music-player control path to queued mailbox commands as the first concrete script using the mailbox-only runtime direction.
|
|
22
|
+
- Added a native Windows `wmp` music-player backend using legacy Windows Media Player COM via `powershell.exe`, with `wmplayer.exe` detection in standard Program Files locations and mailbox-backed controls mapped to WMP play/pause/stop operations.
|
|
23
|
+
- Hardened the music-player mailbox loop to avoid repeated unchanged mailbox reads by combining advisory wake records, `fs.watch`, and inbox signature polling.
|
|
24
|
+
- Improved Unix-like playback support with the macOS-native `afplay` backend, broader audio extension scanning, and process-group signaling for child playback controls.
|
|
25
|
+
- Room timeline appends and branch inbox append/status transitions now emit advisory wake records for the addressed room or branch actor.
|
|
42
26
|
- Direction:
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
27
|
+
- Continue wiring mailbox-only endpoints and notifier reconciliation callbacks into concrete packaged actor scripts where file-backed mailbox dispatch should replace transport-specific control loops.
|
|
28
|
+
- Ensure message delivery writes durable file-backed mailbox/state first, then emits a wake notification.
|
|
29
|
+
- Require actor runtimes to reconcile mailbox state on wake and also on a periodic fallback so missed notifications do not lose work.
|
|
30
|
+
- Provide a universal baseline backend using file-system change notification plus periodic reconcile across Linux, macOS, and Windows.
|
|
31
|
+
- Keep FIFO/named-pipe/socket style endpoints as optional fast wake backends or compatibility paths, not as required durable queues.
|
|
32
|
+
- Document the model as "wake, not queue": notification wakes a live actor; files remain the queue and audit trail.
|
|
33
|
+
- Windows smoke focus:
|
|
34
|
+
- Run installed `@llblab/pi-actors@0.22.0` or newer on native Windows.
|
|
35
|
+
- Verify simple `spawn` / `message` / `inspect` actor communication.
|
|
36
|
+
- Verify small room-swarm/subagent communication, branch/direct messages, mailbox claim/handled transitions, graceful stop/cancel behavior, and opt-in retirement.
|
|
37
|
+
- If smoke passes, update docs/release notes from "adapter support" to "Windows smoke-tested subagent communication" for the next release.
|
|
48
38
|
- Exit:
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- Priority: Medium.
|
|
55
|
-
- Goal: Automatically retire coordinator/helper actors that were launched only to supervise a bounded worker tree once their dependent workers have finished.
|
|
56
|
-
- Direction:
|
|
57
|
-
- Build on the existing `retire_when: "children_terminal"` recipe/run metadata contract and observability retirement-candidate detection for ephemeral supervisors.
|
|
58
|
-
- Treat auto-retirement as opt-in only; never infer it for arbitrary long-lived services, user tools, or persistent backlog implementers.
|
|
59
|
-
- Extend candidate detection beyond current active command/proc-descendant gating to full observed child async-run state rather than log text: the supervisor may retire only when all launched child async runs are terminal and required artifacts/outbox events have been flushed.
|
|
60
|
-
- Prefer graceful stop (`control.stop` / actor message) before process termination; escalate only after a bounded timeout and record the retirement event in run state.
|
|
61
|
-
- Preserve manual `cancel` / `kill` semantics and make retirement visible through `inspect` / ambient observability.
|
|
62
|
-
- Exit:
|
|
63
|
-
- A packaged coordinator recipe can launch worker actors, complete its coordination duties, and shut itself down automatically after the worker tree reaches terminal state.
|
|
64
|
-
- Persistent services and implementer actors remain alive unless their recipe explicitly opts into retirement.
|
|
65
|
-
|
|
66
|
-
### Coordinator Strategy Boundary
|
|
67
|
-
|
|
68
|
-
- Priority: Medium.
|
|
69
|
-
- Goal: Keep the generic coordinator from becoming a second overloaded monolith as room/direct-message workflows mature.
|
|
70
|
-
- Direction:
|
|
71
|
-
- Split only at real pressure points: branch inbox claim/finalize helpers, participant execution, room transcript synthesis, and mode strategies are likely seams, but avoid cosmetic module churn.
|
|
72
|
-
- Preserve the current principle that the locker stays generic/thin and all orchestration policy stays in coordinator strategy code or recipe composition.
|
|
73
|
-
- Prefer reusable helper modules or small scripts only when at least two packaged workflows need the same behavior.
|
|
74
|
-
- Exit:
|
|
75
|
-
- Adding a new coordinator mode or packaged multi-agent workflow does not require editing unrelated mode logic.
|
|
76
|
-
- Existing room-swarm, locker, and direct-branch-message tests still cover the extracted seams.
|
|
39
|
+
- Actor communication works through the cross-platform notifier layer with public API unchanged.
|
|
40
|
+
- Inspect/observability continue to read canonical file state and do not depend on a live notifier process.
|
|
41
|
+
- Missed wake notifications are recovered by mailbox reconciliation.
|
|
42
|
+
- Windows subagent communication smoke is documented with results and any remaining limitations.
|
|
77
43
|
|
|
78
44
|
### Consensus-First Build Recipe
|
|
79
45
|
|
|
@@ -90,18 +56,6 @@
|
|
|
90
56
|
- A packaged recipe can reproduce the interactive-music-instrument workflow shape for another single-artifact task without copying the demo script.
|
|
91
57
|
- Docs and skills point agents to the packaged recipe and explain when to choose it over a free-form room swarm.
|
|
92
58
|
|
|
93
|
-
### Actor OS Scenario Smoke Matrix
|
|
94
|
-
|
|
95
|
-
- Priority: Medium.
|
|
96
|
-
- Goal: Convert the 0.19.x actor-communication hardening into repeatable end-to-end scenario checks instead of relying on ad hoc demos.
|
|
97
|
-
- Direction:
|
|
98
|
-
- Cover one scenario each for shared room coordination, direct branch work delivery, branch inbox claim/handle/fail transitions, inspector navigation, recipe context injection, recipe persistence suggestion, and opt-in retirement candidate detection.
|
|
99
|
-
- Keep scenarios local-first and bounded: fake `pi`/models where possible, no external services, no long sleeps, no broad golden transcripts.
|
|
100
|
-
- Prefer packaged recipes and public `spawn` / `message` / `inspect` calls so the smoke matrix exercises the same surface agents use.
|
|
101
|
-
- Exit:
|
|
102
|
-
- A single validation command or documented test group verifies the actor OS behaviors that made 0.19.x production-useful.
|
|
103
|
-
- The smoke matrix catches regressions in actor communication, recipe memory, and observability without requiring a manual swarm demo.
|
|
104
|
-
|
|
105
59
|
### Persistent Backlog Implementer Workflow
|
|
106
60
|
|
|
107
61
|
- Priority: Medium.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.22.0: Cross-Platform Runtime Notification Layer
|
|
6
|
+
|
|
7
|
+
- `[Runtime]` Started the cross-platform notification layer with a file-backed advisory wake notifier (`wake.jsonl`), explicit initial/wake/poll reconciliation callbacks, periodic reconciliation fallback, and run-message/room-message/branch-inbox wake records. Run messages now persist a canonical inbox record before optional endpoint delivery, can accept mailbox-only control endpoints without FIFO/named-pipe transport, mark delivered endpoint messages `sent`, expose recent run inbox entries through `inspect view=mailbox`, and provide locked run-inbox claim/handle/fail helpers for runtime reconciliation loops. Files remain the canonical mailbox/event state for inspection and crash recovery.
|
|
8
|
+
- `[Docs/Tests]` Documented the "wake, not queue" runtime model, added cross-platform music-player smoke guidance, and added coverage for persisted wake events, missed `fs.watch` recovery through polling, and Windows named-pipe message wakes.
|
|
9
|
+
- `[Packaging]` Removed the root JavaScript entrypoint wrapper from packaged files and pointed extension metadata directly at the compiled `dist/index.js` output. Source checkouts keep `index.ts` as the only root entrypoint while installed packages load compiled JavaScript from `dist`.
|
|
10
|
+
- `[Docs/Prompts]` Removed stale FIFO-queue wording from branch-direct message docs and coordinator prompt injection so queued mailbox work is described consistently with the notification/runtime model. Clarified that worker-backed direct branch messages are runner-owned prompt steering, not coordinator follow-ups, while one-shot prompt children do not consume branch inbox records automatically.
|
|
11
|
+
- `[Recipes]` Migrated the packaged music-player control path from Unix FIFO commands to queued mailbox commands, preserving addressed `message` control while making the script align with mailbox-only runtime endpoints.
|
|
12
|
+
- `[Recipes]` Added a native Windows `wmp` music-player backend that drives legacy Windows Media Player through `powershell.exe`/COM, verifies `wmplayer.exe` in the standard Program Files locations, and includes mailbox-backed play, pause, next, previous, and stop controls.
|
|
13
|
+
- `[Recipes]` Reduced music-player mailbox overhead by using advisory wake records, `fs.watch` where available, and inbox file signatures so the loop avoids repeatedly locking and rereading an unchanged mailbox.
|
|
14
|
+
- `[Recipes]` Improved Unix-like playback by adding the macOS-native `afplay` backend, scanning additional common audio extensions, and running child players in their own process group so controls can signal the playback subtree directly.
|
|
15
|
+
- `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata for the minor release.
|
|
16
|
+
|
|
17
|
+
## 0.21.0: Native Windows Actor Control and Literate Recipes
|
|
18
|
+
|
|
19
|
+
- `[Async Runs]` Added a platform-adapted run-control path: Unix FIFO behavior remains backward-compatible, native Windows can target named-pipe run-control endpoints recorded in run state, and run message receipts still update events and inbox state through the same actor-message path.
|
|
20
|
+
- `[Async Runs]` Added Windows process-tree termination planning for cancel/kill through `taskkill`, while preserving Unix process-group signaling semantics.
|
|
21
|
+
- `[Scripts]` Migrated `locker.mjs` and coordinator locker calls to platform-adapted control metadata: Unix still uses `control.fifo`, while native Windows can use a deterministic named-pipe endpoint with the same message protocol.
|
|
22
|
+
- `[Branch Messages]` Added bounded branch-inbox terminal retention during status transitions, preserving active queued/claimed work while compacting older handled/failed records for long-lived branch runners.
|
|
23
|
+
- `[Recipe Discovery]` Tightened trust-boundary diagnostics so combined short shell/eval flags such as `bash -lc` and nested recipe command-template objects are surfaced, including the packaged validation wrapper's trusted shell boundary.
|
|
24
|
+
- `[Rooms]` Recorded the backend decision to keep the current file-backed room adapter until real workflows need live subscriptions/fanout or shared mutable state, backed by a mixed room/direct-branch workload regression.
|
|
25
|
+
- `[Recipes]` Added Markdown-authored recipe loading for `.md` files with frontmatter metadata and fenced executable recipe/template blocks, with same-id JSON shadowing Markdown in the same priority layer.
|
|
26
|
+
- `[Retirement]` Extended run summaries to discover nested child async-run state dirs, blocks opt-in retirement while nested children are still running, surfaces child/terminal child counts on candidates, and has the session watcher retire ready candidates with one graceful stop attempt plus owned cancellation fallback. Added an integration smoke where an idle supervisor stops after its nested child is terminal while a non-opt-in service remains running.
|
|
27
|
+
- `[Coordinator]` Consolidated direct branch inbox claim/finalize rewrites behind one locked mutation helper and moved room-swarm mode dispatch behind an explicit mode registry. Unknown coordinator modes now fail closed, and `pipeline-room-swarm` exposes the supported mode enum.
|
|
28
|
+
- `[Docs]` Documented the local Actor OS smoke matrix covered by `npm test`, spanning room coordination, direct branch delivery, inbox claim/handle transitions, inspector navigation, recipe context injection, persistence suggestions, and opt-in retirement smoke.
|
|
29
|
+
- `[Docs/Tests]` Documented native Windows support scope and added regression coverage for Windows endpoint metadata, mocked named-pipe sends, Windows process-control planning, unchanged Unix FIFO behavior, locker control metadata, branch inbox compaction, mixed room/direct workloads, Markdown recipe loading/discovery/validation, nested child-run retirement gating, and packaged recipe trust diagnostics.
|
|
30
|
+
- `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata for the minor release.
|
|
31
|
+
|
|
3
32
|
## 0.20.2: Installed Extension Entrypoint Hotfix
|
|
4
33
|
|
|
5
34
|
- `[Packaging]` Added a JavaScript extension entrypoint wrapper and changed package metadata to load `./index.js`, so npm-installed packages import compiled `dist/index.js` instead of asking Node to strip `index.ts` under `node_modules`. Source checkouts still fall back to `index.ts` before a local build exists.
|
package/README.md
CHANGED
|
@@ -169,6 +169,7 @@ The persistent tool surface is file-discovered:
|
|
|
169
169
|
|
|
170
170
|
```text
|
|
171
171
|
~/.pi/agent/recipes/*.json
|
|
172
|
+
~/.pi/agent/recipes/*.md
|
|
172
173
|
```
|
|
173
174
|
|
|
174
175
|
That directory is operator-managed executable memory.
|
|
@@ -178,6 +179,7 @@ Rules:
|
|
|
178
179
|
- User recipes in `~/.pi/agent/recipes/` are tools by location;
|
|
179
180
|
- Recipe filenames define tool ids;
|
|
180
181
|
- User recipes override same-name lower-priority recipes;
|
|
182
|
+
- Same-id JSON recipes shadow Markdown recipes in the same priority layer;
|
|
181
183
|
- Packaged recipes are standard-library components, not automatically installed operator policy;
|
|
182
184
|
- `register_tool` creates, updates, lists, or deletes user recipe files through the normal agent interface.
|
|
183
185
|
|
|
@@ -220,7 +222,7 @@ Templates support:
|
|
|
220
222
|
- Retries, recovery, failure policy, delays, and guarded execution;
|
|
221
223
|
- Async run values such as `{run_id}`, `{state_dir}`, `{actor_address}`, `{default_room}`, and `{communication_file}`.
|
|
222
224
|
|
|
223
|
-
The template owns execution shape. The recipe owns saved metadata, defaults, imports, mailbox, and artifacts. The run actor owns detached lifecycle, state, messages, cancellation, and inspection. File-backed async recipes also provide child `pi -p` actors with a bounded JSONL recipe context bundle by default, including raw entry/import recipe records and a `"you_are_here": true` marker for the recipe node that launched the child. Set `"actor_context": false` or `"off"` in a recipe to suppress that context for minimal prompts.
|
|
225
|
+
The template owns execution shape. The recipe owns saved metadata, defaults, imports, mailbox, and artifacts. JSON is the canonical precise recipe format; Markdown recipes use frontmatter plus fenced `template`/`json recipe` blocks for literate authoring and compile into the same model. The run actor owns detached lifecycle, state, messages, cancellation, and inspection. File-backed async recipes also provide child `pi -p` actors with a bounded JSONL recipe context bundle by default, including raw entry/import recipe records and a `"you_are_here": true` marker for the recipe node that launched the child. Set `"actor_context": false` or `"off"` in a recipe to suppress that context for minimal prompts.
|
|
224
226
|
|
|
225
227
|
## Recipe Library
|
|
226
228
|
|
|
@@ -249,6 +251,10 @@ Use artifacts when outputs should survive context compression.
|
|
|
249
251
|
|
|
250
252
|
Use mailbox declarations when an actor has a stable conversational surface.
|
|
251
253
|
|
|
254
|
+
## Platform Support
|
|
255
|
+
|
|
256
|
+
Core actor state, inspection, foreground tools, and basic async runs are portable Node.js behavior. Run-local messaging and stop/kill use a platform adapter under the same `message` API: Unix-compatible recipes can use their existing local control endpoint, while native Windows recipes can expose a Windows-native endpoint in run state. Some packaged scripts still depend on Unix tools and are WSL/Linux/macOS-only until migrated; their public recipe surface should stay `spawn` / `message` / `inspect` either way.
|
|
257
|
+
|
|
252
258
|
## Safety Boundary
|
|
253
259
|
|
|
254
260
|
`pi-actors` is local-first, not sandbox-first.
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, readdirSync, watch } from "node:fs";
|
|
8
8
|
import * as ActorInspectorTui from "./lib/actor-inspector-tui.js";
|
|
9
|
+
import * as AsyncRuns from "./lib/async-runs.js";
|
|
9
10
|
import * as CommandTemplates from "./lib/command-templates.js";
|
|
10
11
|
import * as Observability from "./lib/observability.js";
|
|
11
12
|
import * as Paths from "./lib/paths.js";
|
|
@@ -38,6 +39,7 @@ export default function toolRegistryExtension(pi) {
|
|
|
38
39
|
const runDirWatchers = new Map();
|
|
39
40
|
const observedRuns = new Map();
|
|
40
41
|
const observedRunEventLines = new Map();
|
|
42
|
+
const retirementAttempts = new Set();
|
|
41
43
|
let runStatusFrame = 0;
|
|
42
44
|
let communicationWidgetVisible = false;
|
|
43
45
|
let actorInspectorRows = 12;
|
|
@@ -49,6 +51,14 @@ export default function toolRegistryExtension(pi) {
|
|
|
49
51
|
let selectedInspectorSequence;
|
|
50
52
|
let recipeWatcherFailureNotified = false;
|
|
51
53
|
const getRunOwnerId = (ctx) => ctx.sessionManager.getSessionId();
|
|
54
|
+
const retireCandidateRuns = (ctx, summary) => {
|
|
55
|
+
void Observability.executeRunRetirements(summary, {
|
|
56
|
+
attempted: retirementAttempts,
|
|
57
|
+
cancelRun: (candidate) => AsyncRuns.cancelRun(candidate.stateDir),
|
|
58
|
+
notify: (message, level) => ctx.ui.notify(message, level),
|
|
59
|
+
sendStop: (candidate) => AsyncRuns.sendRunMessage(candidate.stateDir, "stop"),
|
|
60
|
+
});
|
|
61
|
+
};
|
|
52
62
|
const updateRunUi = (ctx, notify = false) => {
|
|
53
63
|
const ownerId = getRunOwnerId(ctx);
|
|
54
64
|
const summary = Observability.summarizeRuns(undefined, ownerId);
|
|
@@ -93,6 +103,7 @@ export default function toolRegistryExtension(pi) {
|
|
|
93
103
|
const outboxEvents = Observability.detectRunOutboxEvents(observedRunEventLines, summary);
|
|
94
104
|
if (!notify)
|
|
95
105
|
return;
|
|
106
|
+
retireCandidateRuns(ctx, summary);
|
|
96
107
|
for (const transition of transitions) {
|
|
97
108
|
if (!Observability.shouldNotifyRunTransition(transition))
|
|
98
109
|
continue;
|
|
@@ -67,6 +67,7 @@ export declare function readBranchInboxMessages(stateDir: string, run: string, a
|
|
|
67
67
|
queued_at?: string;
|
|
68
68
|
status?: string;
|
|
69
69
|
}>;
|
|
70
|
+
export declare function getBranchInboxTerminalRetainLimit(): number;
|
|
70
71
|
export declare function appendBranchInboxMessage(stateDir: string, run: string, address: string, message: ActorMessage): void;
|
|
71
72
|
export declare function updateBranchInboxMessageStatus(stateDir: string, run: string, address: string, id: string, status: "claimed" | "handled" | "failed", metadata?: Record<string, unknown>): boolean;
|
|
72
73
|
export declare function appendRoomMessage(stateDir: string, room: string, message: ActorMessage): RoomAppendResult;
|
package/dist/lib/actor-rooms.js
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
+
import { notifyRuntimeWake } from "./runtime-notifier.js";
|
|
9
10
|
const STATE_LOCK_MAX_AGE_MS = 5 * 60 * 1000;
|
|
10
11
|
const STATE_LOCK_TIMEOUT_MS = 5000;
|
|
11
12
|
const DEFAULT_ROOM_MAX_MESSAGES = 10000;
|
|
13
|
+
const DEFAULT_BRANCH_INBOX_TERMINAL_RETAINED = 2000;
|
|
12
14
|
const DEFAULT_SNAPSHOT_MIN_INTERVAL_MS = 250;
|
|
13
15
|
function roomDir(stateDir, room) {
|
|
14
16
|
return path.join(stateDir, "rooms", room);
|
|
@@ -97,6 +99,14 @@ function writeJsonFile(file, value) {
|
|
|
97
99
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
98
100
|
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
99
101
|
}
|
|
102
|
+
function notifyActorWake(stateDir, actor, reason, metadata = {}) {
|
|
103
|
+
try {
|
|
104
|
+
notifyRuntimeWake(stateDir, { actor, metadata, reason });
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Runtime wakes are advisory; durable room/inbox state remains canonical.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
100
110
|
function positiveEnvInt(name, fallback) {
|
|
101
111
|
const value = Number(process.env[name] ?? fallback);
|
|
102
112
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
@@ -275,6 +285,18 @@ export function readBranchInboxMessages(stateDir, run, address, limit = 40) {
|
|
|
275
285
|
throw error;
|
|
276
286
|
}
|
|
277
287
|
}
|
|
288
|
+
export function getBranchInboxTerminalRetainLimit() {
|
|
289
|
+
const value = Number(process.env.PI_ACTORS_BRANCH_INBOX_TERMINAL_RETAINED ?? "");
|
|
290
|
+
return Number.isInteger(value) && value >= 0
|
|
291
|
+
? value
|
|
292
|
+
: DEFAULT_BRANCH_INBOX_TERMINAL_RETAINED;
|
|
293
|
+
}
|
|
294
|
+
function compactBranchInboxMessages(messages) {
|
|
295
|
+
const retainTerminal = getBranchInboxTerminalRetainLimit();
|
|
296
|
+
const active = messages.filter((message) => message.status !== "handled" && message.status !== "failed");
|
|
297
|
+
const terminal = messages.filter((message) => message.status === "handled" || message.status === "failed");
|
|
298
|
+
return [...terminal.slice(-retainTerminal), ...active];
|
|
299
|
+
}
|
|
278
300
|
export function appendBranchInboxMessage(stateDir, run, address, message) {
|
|
279
301
|
const branch = branchIdFromAddress(address, run);
|
|
280
302
|
if (!branch)
|
|
@@ -282,6 +304,10 @@ export function appendBranchInboxMessage(stateDir, run, address, message) {
|
|
|
282
304
|
const releaseLock = acquireBranchInboxLock(stateDir, branch);
|
|
283
305
|
try {
|
|
284
306
|
fs.writeFileSync(branchInboxFile(stateDir, branch), `${JSON.stringify({ ...message, id: randomUUID(), queued_at: new Date().toISOString(), status: "queued" })}\n`, { flag: "a" });
|
|
307
|
+
notifyActorWake(stateDir, address, "branch.message", {
|
|
308
|
+
...(message.from ? { from: message.from } : {}),
|
|
309
|
+
type: message.type,
|
|
310
|
+
});
|
|
285
311
|
}
|
|
286
312
|
finally {
|
|
287
313
|
releaseLock();
|
|
@@ -305,7 +331,9 @@ export function updateBranchInboxMessageStatus(stateDir, run, address, id, statu
|
|
|
305
331
|
});
|
|
306
332
|
if (!changed)
|
|
307
333
|
return false;
|
|
308
|
-
|
|
334
|
+
const compacted = compactBranchInboxMessages(updated);
|
|
335
|
+
fs.writeFileSync(file, `${compacted.map((message) => JSON.stringify(message)).join("\n")}\n`);
|
|
336
|
+
notifyActorWake(stateDir, address, "branch.inbox.status", { id, status });
|
|
309
337
|
return true;
|
|
310
338
|
}
|
|
311
339
|
finally {
|
|
@@ -323,6 +351,10 @@ export function appendRoomMessage(stateDir, room, message) {
|
|
|
323
351
|
const run = runFromRoomAddress(message.to);
|
|
324
352
|
if (run) {
|
|
325
353
|
writeCommunicationSnapshot(stateDir, run);
|
|
354
|
+
notifyActorWake(stateDir, message.to, "room.message", {
|
|
355
|
+
...(message.from ? { from: message.from } : {}),
|
|
356
|
+
type: message.type,
|
|
357
|
+
});
|
|
326
358
|
if (message.from && branchIdFromAddress(message.from, run)) {
|
|
327
359
|
writeBranchCommunicationSnapshotDebounced(stateDir, run, message.from);
|
|
328
360
|
}
|
package/dist/lib/async-runs.d.ts
CHANGED
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
import type { CommandTemplateFailureScope, CommandTemplateValue } from "./command-templates.ts";
|
|
7
7
|
import * as RecipeReferences from "./recipe-references.ts";
|
|
8
8
|
export type AsyncRunLaunchSource = "spawn" | "tool";
|
|
9
|
+
export interface AsyncRunControlEndpoint {
|
|
10
|
+
path: string;
|
|
11
|
+
type: "fifo" | "mailbox" | "named-pipe";
|
|
12
|
+
}
|
|
9
13
|
export interface AsyncRunStartParams {
|
|
10
14
|
async?: boolean;
|
|
15
|
+
control?: AsyncRunControlEndpoint;
|
|
11
16
|
file?: string;
|
|
12
17
|
launch_source?: AsyncRunLaunchSource;
|
|
13
18
|
name?: string;
|
|
@@ -72,6 +77,7 @@ export interface AsyncRunMeta {
|
|
|
72
77
|
template: CommandTemplateValue;
|
|
73
78
|
values: Record<string, unknown>;
|
|
74
79
|
artifacts?: Record<string, string>;
|
|
80
|
+
control?: AsyncRunControlEndpoint;
|
|
75
81
|
mailbox?: RecipeReferences.TemplateRecipeMailbox;
|
|
76
82
|
recipe_context_records?: RecipeReferences.TemplateRecipeContextRecord[];
|
|
77
83
|
retire_when?: "children_terminal";
|
|
@@ -82,6 +88,24 @@ export declare function getRunStatus(runOrDir: string): Record<string, unknown>;
|
|
|
82
88
|
export declare function listRuns(stateRoot?: string, statusFilter?: string): Array<Record<string, unknown>>;
|
|
83
89
|
export declare function tailRun(runOrDir: string, lines?: number): string;
|
|
84
90
|
export declare function readRunEvents(runOrDir: string, lines?: number): RunOutboxEvent[];
|
|
91
|
+
export type RunInboxStatus = "queued" | "sent" | "claimed" | "handled" | "failed";
|
|
92
|
+
export type RunInboxMessage = Record<string, unknown> & {
|
|
93
|
+
id?: string;
|
|
94
|
+
status?: RunInboxStatus | string;
|
|
95
|
+
};
|
|
96
|
+
export interface ProcessRunInboxResult {
|
|
97
|
+
claimed: number;
|
|
98
|
+
failed: number;
|
|
99
|
+
handled: number;
|
|
100
|
+
}
|
|
101
|
+
export declare function readRunInboxMessages(runOrDir: string, lines?: number): RunInboxMessage[];
|
|
102
|
+
export declare function updateRunInboxMessageStatus(runOrDir: string, id: string, nextStatus: RunInboxStatus, metadata?: Record<string, unknown>): boolean;
|
|
103
|
+
export declare function claimRunInboxMessage(runOrDir: string, owner?: string, statuses?: string[]): RunInboxMessage | undefined;
|
|
104
|
+
export declare function processRunInboxMessages(runOrDir: string, handler: (message: RunInboxMessage) => Promise<void> | void, options?: {
|
|
105
|
+
limit?: number;
|
|
106
|
+
owner?: string;
|
|
107
|
+
statuses?: string[];
|
|
108
|
+
}): Promise<ProcessRunInboxResult>;
|
|
85
109
|
export declare function appendRunOutboxEvent(runOrDir: string, event: {
|
|
86
110
|
body?: unknown;
|
|
87
111
|
correlation_id?: string;
|
|
@@ -96,6 +120,16 @@ export declare function appendRunOutboxEvent(runOrDir: string, event: {
|
|
|
96
120
|
to?: string;
|
|
97
121
|
type?: string;
|
|
98
122
|
}): Record<string, unknown>;
|
|
99
|
-
export
|
|
123
|
+
export interface SendRunMessageOptions {
|
|
124
|
+
namedPipeSend?: (path: string, payload: string) => Promise<number>;
|
|
125
|
+
platform?: NodeJS.Platform;
|
|
126
|
+
}
|
|
127
|
+
export declare function sendRunMessage(runOrDir: string, message: string, options?: SendRunMessageOptions): Promise<Record<string, unknown>>;
|
|
128
|
+
export interface RunProcessSignalPlan {
|
|
129
|
+
args?: string[];
|
|
130
|
+
command?: string;
|
|
131
|
+
signalTarget: "processGroup" | "process" | "processTree";
|
|
132
|
+
}
|
|
133
|
+
export declare function getRunProcessSignalPlan(pid: number, signal: NodeJS.Signals, runtimePlatform?: NodeJS.Platform): RunProcessSignalPlan;
|
|
100
134
|
export declare function cancelRun(runOrDir: string): Record<string, unknown>;
|
|
101
135
|
export declare function killRun(runOrDir: string): Record<string, unknown>;
|