@llblab/pi-actors 0.22.2 → 0.22.4

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/CHANGELOG.md CHANGED
@@ -2,16 +2,35 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.22.4: Actor Isolation and Registry Diagnostics Hotfix
6
+
7
+ - `[Tests]` Added explicit regression coverage that ad hoc recipe files outside the user recipe root remain recipe components rather than automatically exposed tools, reinforcing the location-based tool exposure invariant.
8
+ - `[Registry]` Improved invalid recipe diagnostics for discovery summaries so JSON parse failures, missing templates, and malformed Markdown recipes keep actionable causes, structured severity, and suggested actions instead of collapsing to a generic invalid recipe message.
9
+ - `[Observability]` Keyed run transition observation by state directory instead of display run id so nested child runs or reused run names do not collide in terminal follow-ups and pruning state.
10
+ - `[Async Runs]` Normalized run-message delivery failures after durable inbox append: failures now preserve queued state details such as `queued`, `inbox_id`, and `delivery_error`, while successful FIFO, named-pipe, and mailbox-only deliveries also expose the inbox id.
11
+ - `[Tests]` Added explicit cross-session kill-control regression coverage so run ownership boundaries stay fail-closed for destructive actor controls as well as ordinary messages and inspection.
12
+ - `[Tests]` Added branch and room routing safety regressions for cross-run senders and invalid multicast recipients, including assertions that failed validation does not create branch inbox or room timeline records.
13
+ - `[Actor Rooms]` Hardened branch inbox reads and status rewrites against malformed JSONL lines; valid messages continue to update while corrupted record counts surface through branch mailbox inspection.
14
+ - `[Tests]` Added async lifecycle regression coverage for missing-result terminal status inference, preserving `cancelled`/`killed` over generic `exited`, and tail behavior when only event logs exist.
15
+ - `[Registry]` Allowed `register_tool` to persist object command-template configs with composition flags, aligning it with recipes and `spawn`, while preserving precise validation errors for invalid object templates.
16
+ - `[Registry]` Added deterministic live-reload regressions for invalid user updates blocking lower-priority fallback recipes and valid recovery refreshing the active tool schema without restart.
17
+ - `[Tools]` Preserved target tool failure shape through `message to=tool:<name>` by including the tool name, message type, bounded params preview, and original error on routed failures.
18
+ - `[Actors]` Tightened branch and room routing isolation so session-owned runs reject branch/room messages from a different current Pi session, keeping room state scoped to the owning actor tree.
19
+ - `[Tests]` Added executable protocol-example coverage for public actor-message, room join/leave, mailbox, spawn, and inspect examples so documentation drift fails in CI.
20
+
21
+ ## 0.22.3: Idempotent GitHub Release Workflow Hotfix
22
+
23
+ - `[Release]` Made the tag-triggered GitHub Release workflow idempotent: existing releases are edited with the generated title and notes instead of failing when an operator already created the release for the tag.
24
+ - `[Context]` Added a changelog signal rule to project context and removed release-bookkeeping-only bullets from changelog history so future entries describe meaningful behavior rather than package-version metadata.
25
+
5
26
  ## 0.22.2: Portable Recipe Tool Exposure Hotfix
6
27
 
7
28
  - `[Registry]` Stopped writing redundant exposure metadata during registration and aligned docs/tests around location-based tool exposure so recipes remain portable between user, ad hoc, and packaged roots.
8
29
  - `[Skills]` Generalized the actors-skill tool-registration lenses and added existing recipe surfaces, including skill-local recipes, as first candidates for promotion into durable tools.
9
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata for the hotfix release.
10
30
 
11
31
  ## 0.22.1: Tool Registration Lens Hotfix
12
32
 
13
33
  - `[Skills]` Added tool-registration lenses to the packaged actors skill so agents prefer persistent tools for error-prone workflows, safe preflights around dangerous operations, and context-affordance shortcuts that should be visible in future sessions.
14
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata for the hotfix release.
15
34
 
16
35
  ## 0.22.0: Cross-Platform Runtime Notification Layer
17
36
 
@@ -23,7 +42,6 @@
23
42
  - `[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.
24
43
  - `[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.
25
44
  - `[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.
26
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata for the minor release.
27
45
 
28
46
  ## 0.21.0: Native Windows Actor Control and Literate Recipes
29
47
 
@@ -38,7 +56,6 @@
38
56
  - `[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.
39
57
  - `[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.
40
58
  - `[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.
41
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata for the minor release.
42
59
 
43
60
  ## 0.20.2: Installed Extension Entrypoint Hotfix
44
61
 
@@ -46,13 +63,11 @@
46
63
  - `[Build]` Extended the compiled runtime build to emit `dist/index.js` alongside `dist/lib/*.js`, keeping extension entrypoint imports and script runtime imports on the same installed-package path model.
47
64
  - `[Rooms]` Fixed immediate room append results to report the true persisted room message count after long timelines instead of the default 40-message preview length; `appendRoomMessage`, existing-member room joins, and `getRoomStatus()` now share the same line-count helper.
48
65
  - `[Tests]` Added installed-package coverage that imports the extension entrypoint from package metadata without TypeScript stripping, plus room-count regression coverage beyond the default preview limit.
49
- - `[Package]` Bumped package metadata and packaged skill metadata to `0.20.2` for the hotfix release.
50
66
 
51
67
  ## 0.20.1: Installed Packaged Recipe Root Hotfix
52
68
 
53
69
  - `[Recipe Imports]` Fixed installed compiled runtime path resolution so bare user recipe imports can fall back to the packaged standard-library `recipes/` directory instead of looking for a non-existent `dist/recipes` directory.
54
70
  - `[Tests]` Added installed-package validation coverage for a user recipe that imports a packaged recipe by bare name, preserving the documented priority order for user, adjacent, and packaged recipes.
55
- - `[Package]` Bumped package metadata and packaged skill metadata to `0.20.1` for the hotfix release.
56
71
 
57
72
  ## 0.20.0: Compiled Runtime Entrypoints
58
73
 
@@ -60,33 +75,32 @@
60
75
  - `[Async Runs]` Replaced the emergency installed-package copy workaround in `scripts/async-runner.mjs` with dist-first imports. Installed npm packages now execute the async runner against compiled JS without relying on Node native type stripping for `.ts` files under `node_modules`; source checkouts still fall back to TypeScript imports for local development.
61
76
  - `[Scripts]` Updated `scripts/validate-recipe.mjs` to use the same dist-first import path, so packaged recipe validation also runs from compiled JS when installed from npm.
62
77
  - `[Tests]` Updated installed-package smoke coverage to simulate `node_modules/@llblab/pi-actors` with `dist`, execute scripts without `--experimental-strip-types`, and assert the old `.type-strip-lib` workaround is not used.
63
- - `[Package]` Changed the package description to `Local Actor Kernel for Pi`, added `tsconfig.build.json`, included `dist` in the published package, and bumped package/skill metadata to `0.20.0`.
78
+ - `[Package]` Changed the package description to `Local Actor Kernel for Pi`, added `tsconfig.build.json`, and included `dist` in the published package.
64
79
 
65
80
  ## 0.19.11: Installed Async Runner Hotfix
66
81
 
67
82
  - `[Async Runs]` Fixed installed npm package async recipe launches on Node 22 by avoiding direct runtime imports of raw `.ts` files from under `node_modules` in `scripts/async-runner.mjs`. Installed runners now copy the package `lib` sources into the run state before importing them, keeping Node native type stripping outside the blocked `node_modules` path.
68
83
  - `[Scripts]` Applied the same installed-package import guard to `scripts/validate-recipe.mjs`, so the packaged recipe validator works when invoked from an installed `@llblab/pi-actors` package.
69
84
  - `[Tests]` Added installed-package script smoke coverage that copies `lib`/`scripts` under a temporary `node_modules/@llblab/pi-actors` path and verifies both async runner execution and recipe validation avoid `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`.
70
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata to `0.19.11` for the hotfix release.
71
85
 
72
86
  ## 0.19.10: Legacy Branch Message Claim IDs
73
87
 
74
88
  - `[Branch Messages]` Coordinator claim handling now assigns IDs to older/manual queued branch inbox entries that lack `id`, so injected direct messages can still transition to `handled` or `failed` and do not repeat forever.
75
89
  - `[Tests]` Extended direct branch inbox coordinator coverage to include a legacy no-ID message and assert both claimed/handled timestamps are recorded.
76
- - `[Docs/Context]` Updated actor-message docs, durable project context, package metadata, lockfile metadata, and packaged skill metadata to `0.19.10`.
90
+ - `[Docs/Context]` Updated actor-message docs and durable project context for legacy branch message claim IDs.
77
91
 
78
92
  ## 0.19.9: Locked Branch Inbox Mutations
79
93
 
80
94
  - `[Branch Messages]` Added lock-guarded append and status rewrites for branch-local direct-message inbox files so concurrent direct delivery and coordinator claim/handle transitions do not overwrite each other.
81
95
  - `[Coordinator]` Made room-swarm branch prompt execution atomically claim queued direct messages before injection, then mark claimed messages as `handled` or `failed` after the child prompt exits.
82
96
  - `[Tests]` Added concurrent branch inbox append coverage and asserted coordinator direct-message handling records both `claimed_at` and `handled_at`.
83
- - `[Docs/Context]` Updated actor-message docs, project context, backlog safeguards, package metadata, lockfile metadata, and packaged skill metadata to `0.19.9`.
97
+ - `[Docs/Context]` Updated actor-message docs, project context, and backlog safeguards for locked branch inbox mutations.
84
98
 
85
99
  ## 0.19.8: Efficient Room Status Reads
86
100
 
87
101
  - `[Rooms]` Changed room status inspection to count JSONL entries and read only the last timeline record instead of parsing the full room timeline into actor-envelope objects.
88
102
  - `[Inspector]` Preserved the existing `inspect room:<run> view=status` shape while reducing storage/read amplification for large room transcripts.
89
- - `[Docs/Context]` Updated actor-message docs, backlog safeguards, project context, package metadata, lockfile metadata, and skill metadata for `0.19.8`.
103
+ - `[Docs/Context]` Updated actor-message docs, backlog safeguards, and project context for efficient room status reads.
90
104
  - `[Tests]` Added regression coverage that room status preserves message count and last-message metadata across longer timelines.
91
105
 
92
106
  ## 0.19.7: Burst-Safe Roster Writes
@@ -95,7 +109,6 @@
95
109
  - `[Runtime IO]` Added `PI_ACTORS_ROOM_ROSTER_MIN_MS` as the roster-only debounce interval, mirroring the existing communication snapshot debounce approach without changing public `room:<run>` message or inspect semantics.
96
110
  - `[Docs/Context]` Updated actor-message docs, project context, and the remaining rooms backlog scope to preserve the new burst-safe roster invariant during future storage/backend changes.
97
111
  - `[Tests]` Added regression coverage for roster rewrite debounce and immediate semantic roster updates.
98
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata to `0.19.7` for the hotfix release.
99
112
 
100
113
  ## 0.19.6: Conservative Retirement Candidates
101
114
 
@@ -103,7 +116,6 @@
103
116
  - `[Retirement]` Tightened opt-in `retire_when: "children_terminal"` candidate detection so supervisors are not considered retirement-ready while command-template progress or descendant `pi -p` workers are still active.
104
117
  - `[Docs/Context]` Updated async-run docs, project context, and the remaining retirement backlog scope to reflect the conservative candidate baseline and the remaining child async-run/output-flush work.
105
118
  - `[Tests]` Added regression coverage that blocks retirement candidates with descendant subagents.
106
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata to `0.19.6` for the hotfix release.
107
119
 
108
120
  ## 0.19.5: Branch Inbox Inspector Filters
109
121
 
@@ -111,7 +123,6 @@
111
123
  - `[Actor Inspector]` Added `/actors-inspector-filter unread`, `/actors-inspector-filter branch <name>`, and `/actors-inspector-filter current-branch <name>` to focus queued branch inbox work and one branch's room/direct/inbox traffic without exposing full payloads by default.
112
124
  - `[Docs/Skills]` Updated README and the packaged actors skill with the new inspector filters and branch-inbox preview behavior.
113
125
  - `[Backlog]` Closed the high-priority actor communication TUI preview item now that unread/current-branch navigation is implemented with branch read-state semantics.
114
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata to `0.19.5` for the hotfix release.
115
126
 
116
127
  ## 0.19.4: User Recipe Collection Suggestions
117
128
 
@@ -119,7 +130,6 @@
119
130
  - `[Runtime]` Preserved the ask-first boundary and suppression for recipes already in the user recipe root, so pi-actors grows operator muscle memory without silently writing user recipe files.
120
131
  - `[Docs/Prompt]` Updated README, async-run docs, actors skill, onboarding prompt, and project context to frame `~/.pi/agent/recipes` as the everyday per-machine collection of reusable actor recipes/tools.
121
132
  - `[Tests]` Added coverage for successful external recipe suggestions, while keeping user-owned recipe suppression covered.
122
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata to `0.19.4` for the hotfix release.
123
133
 
124
134
  ## 0.19.3: Spawn Recipe Persistence Suggestions
125
135
 
@@ -127,20 +137,17 @@
127
137
  - `[Runtime]` Recorded `launch_source` metadata for actor starts so observability can distinguish direct spawns from registered recipe-tool runs and avoid prompting for actors already backed by user-owned recipes.
128
138
  - `[Docs/Prompt]` Updated onboarding prompt guidance, README, async-run docs, and project context around ask-first recipe persistence after successful transient actors.
129
139
  - `[Tests]` Added regression coverage for successful transient spawn suggestions and suppression when the run already came from a saved user recipe.
130
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata to `0.19.3` for the hotfix release.
131
140
 
132
141
  ## 0.19.2: Actor Recipe Context Bundle
133
142
 
134
143
  - `[Actor Context]` Added a recipe context bundle for file-backed async recipes. The runtime now collects the raw authored entry recipe and resolved imports into deterministic JSONL records with filename-derived `name`, import alias/path metadata, role/depth, and raw recipe JSON so spawned LLM actors can understand the workflow composition behind their prompt.
135
144
  - `[Actor Context]` Annotated command-template leaves with actor recipe context and appends the JSONL bundle to child `pi -p` prompts. The recipe record that launched the current child receives `"you_are_here": true` plus path metadata, enabling actors to give advisory feedback on their own recipe/composition fit; recipes can opt out with `"actor_context": false` / `"off"` when a minimal prompt is required.
136
145
  - `[Tests]` Added coverage for raw recipe context record generation, import identity, `you_are_here` JSONL marking, prompt injection for `pi -p`, execution-time context propagation, async-run persistence, and recipe opt-out behavior.
137
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata to `0.19.2` for the hotfix release.
138
146
 
139
147
  ## 0.19.1: Actor Inspector Hotfix
140
148
 
141
149
  - `[Actor Inspector]` Fixed the live communications roster and row numbering controls after real swarm usage. `/actors-inspector-toggle <rows>` now keeps the room preview cap aligned with the requested row count, current-run sequence numbers are assigned before row limiting so the visible tail keeps its full-log positions, and roster role labels use concise `name/role` text instead of slugifying full role descriptions.
142
150
  - `[Coordinator]` Preserved explicit `--thinking off` forwarding in `scripts/coordinator.mjs` so packaged room-swarm launches keep caller-selected thinking policy instead of silently relying on CLI defaults.
143
- - `[Package]` Bumped package metadata, lockfile metadata, and packaged skill metadata to `0.19.1` for the hotfix release.
144
151
 
145
152
  ## 0.19.0: Modular Coordination And Active Mailboxes
146
153
 
@@ -156,14 +163,12 @@
156
163
  - `[Observability]` Reduced long-session overhead by pruning stale run observation state, caching active-subagent process scans, and expanding ambient run triangles with descendant `pi -p` workers launched by coordinators. Impact: terminal status remains useful during long actor sessions without retaining completed-run bookkeeping, repeatedly scanning `/proc` on every refresh, or showing one coordinator triangle when a visible worker tree is still running.
157
164
  - `[Recipes]` Strengthened the recipe registry as a local capability surface. Recipe loading now rejects oversized files and excessive import depth, reports risky executable shapes and unsafe recipe-root permissions, exposes an integrity manifest, warns when the recipe-root watcher fails, derives recipe identity from filenames, resolves bare import names by recipe-root priority, and makes tool exposure location-derived: user recipe-root files are tools, packaged/ad hoc recipes are components. Impact: recipes are easier to audit, easier to compose from the standard library, harder to misuse accidentally, and no longer depend on redundant `name` / `tool` JSON fields for identity or exposure.
158
165
  - `[Docs/Skills/Context]` Updated the README, actor-message/template-recipe/command-template docs, recipe-library/task-first docs, actors/swarm skills, onboarding prompt, and project context to reflect the hardened runtime, room/inspector controls, filename-derived recipes, location-derived tools, consensus-first build orchestration, shell-placeholder boundaries, and the rule that recurring multi-agent scenarios should grow packaged recipes/pipelines instead of task-local orchestration scripts. `BACKLOG.md` now stays focused on completable future work while durable operating principles live in project context.
159
- - `[Package]` Bumped package and packaged skill metadata to `0.18.0`; promoted the release from a hotfix to a minor release because the scope now includes actor runtime hardening, room/TUI behavior, recipe guardrails, observability cleanup, and agent-facing guidance updates.
160
166
 
161
167
  ## 0.17.1: Inspector Hotfix And Room Swarm Hardening
162
168
 
163
169
  - `[TUI]` Fixed actor inspector line bounding to use `visibleWidth()` for direction/type/summary/body width math, replaced the verbose two-line preview with a hidden-by-default numbered table, made bare `/actors-inspector-toggle` open 12 rows from closed state, removed the verbosity toggle, made `/actors-inspector-toggle <rows>` update the live row count, upgraded `/actors-inspect <number>` to show a separated two-column header plus all preview-object properties as aligned two-space key/value columns, and added a styled wide-character regression. Impact: room previews with wide text no longer crash Pi, actor logs stay dense while preserving message type visibility, operators can tune visible row count without persistent inspector settings, and they can drill into one visible row then toggle back to the table.
164
170
  - `[Recipe Library]` Added `pipeline-room-swarm` backed by `scripts/room-swarm.mjs`: repeated room-aware participants join `room:<run>`, coordinate over multiple room-visible rounds, leave cleanly, and synthesize the room transcript into a Markdown artifact. Roles can be supplied via `roles_path` to avoid raw JSON placeholders, default roles use plain actor names, room rosters preserve display metadata, and `locker=true` composes a local coordinator-locker cell for artifact locks and decision journaling with regression coverage. Direct branch delivery remains available for worker protocols that consume parent-run branch envelopes, but the packaged swarm no longer relies on it for peer coordination. Impact: the DeepSeek room-swarm experiment is now represented as a policy-light packaged scenario while concrete model choice remains caller/operator policy.
165
171
  - `[Docs]` Reconciled `BACKLOG.md` back to future-only open work, removing completed hotfix implementation notes and version-scoped backlog language now captured in this changelog. Refreshed README and project context around the packaged room-swarm/coordinator-locker library surface and actor-inspector TUI ownership.
166
- - `[Package]` Bumped package and packaged skill metadata to `0.17.1` for the hotfix release.
167
172
 
168
173
  ## 0.17.0: Actor Rooms And Inspector
169
174
 
@@ -171,32 +176,27 @@
171
176
  - `[TUI]` Added the hidden-by-default actor inspector widget with `/actors-inspector-toggle` and `/actors-inspector-verbosity-toggle`, compact and verbose layouts, current-run scoping, chronological sequence numbers, owner filtering, JSONL-tolerant preview reads, mobile-width and wide-character-aware truncation, and transparent/dark row striping. Impact: operators can see the current actor conversation at a glance without flooding the prompt or leaking unrelated session previews.
172
177
  - `[Registry]` Added usage metadata and operator-gated cleanup recommendations to recipe registry summaries, removed stale public references to the old tool config filename, removed recipe content exposure markers from repository recipes/docs/fixtures, and fixed the 0.17 registry model around location-derived tool exposure: every recipe in `~/.pi/agent/recipes/*.json` is an agent tool, `register_tool` creates recipe files there under the hood, and packaged/ad hoc recipes outside that root are components. Impact: the sticky agent tool surface is explicit executable muscle memory, maintained like capability state rather than configured through per-recipe exposure flags.
173
178
  - `[Docs]` Updated README, actor-message docs, async-run docs, recipe-library docs, actors skill, backlog, and project context around the room/roster protocol, inspector behavior, release-artifact hygiene, and the persistent-backlog-implementer protocol. The implementer workflow remains future recipe-composition work around reusable cells such as `coordinator-locker`, not bespoke release scripts.
174
- - `[Package]` Bumped package and packaged skill metadata to `0.17.0`; validated with `npm run validate` and context validation. PR #42 tracks the squashed `actor-rooms-017` branch as one release commit against `main` with green checks.
175
179
 
176
180
  ## 0.16.4: Recipe Usage Fingerprints
177
181
 
178
182
  - `[Recipe Usage]` Added content fingerprints to user recipe usage metadata. Impact: when a recipe file is edited and its authored meaning changes, the next launch resets `usage.calls`, records `usage.reset_at`, and starts counting usage for the current recipe content.
179
183
  - `[Docs]` Documented fingerprint-backed usage reset semantics in the template recipe and tool registry docs.
180
- - `[Package]` Bumped package and packaged skill metadata to `0.16.4` for the hotfix release.
181
184
 
182
185
  ## 0.16.3: Recipe Import Path Placeholders
183
186
 
184
187
  - `[Template Recipes]` Added static `{repo}` and `{agent}` expansion for recipe paths, including `imports` and `from` bindings. Impact: recipes can import sibling packaged/user recipes without hard-coded absolute paths while keeping imports load-time deterministic.
185
188
  - `[Docs]` Documented `{repo}` and `{agent}` import path placeholders in the template recipe standard.
186
- - `[Package]` Bumped package and packaged skill metadata to `0.16.3` for the hotfix release.
187
189
 
188
190
  ## 0.16.2: Recipe Registry Diagnostics Hotfix
189
191
 
190
192
  - `[Schema]` Derived recipe tool arguments without expanding runtime-dependent repeat nodes. Impact: valid recipes using repeat expressions such as `{lenses.length}` can be exposed as tools instead of being skipped during startup schema generation.
191
193
  - `[Runtime]` Replaced the dense semicolon warning with grouped recipe registry diagnostics and explicit spacing. Impact: startup diagnostics are easier to scan and do not visually run into adjacent text.
192
194
  - `[Recipes]` Added a packaged `lens-swarm` recipe that composes the review coordinator without concrete model-version defaults. Impact: the standard library includes the general multi-lens review launcher instead of relying only on operator-local copies.
193
- - `[Package]` Bumped package and packaged skill metadata to `0.16.2` for the hotfix release.
194
195
 
195
196
  ## 0.16.1: Recipe Registry Hotfix
196
197
 
197
198
  - `[Runtime]` Prevented invalid user recipe files from aborting extension startup when tool-schema generation fails, surfacing a warning and skipping the offending tool instead. Impact: one bad recipe in `~/.pi/agent/recipes` no longer takes down the pi-actors extension.
198
199
  - `[Recipe Discovery]` Excluded the legacy migration report file from recipe discovery. Impact: legacy migration reports no longer appear as broken recipe/tool candidates after migration.
199
- - `[Package]` Bumped package and packaged skill metadata to `0.16.1` for the hotfix release.
200
200
 
201
201
  ## 0.16.0: File-Discovered Recipe Registry Migration
202
202
 
package/dist/index.js CHANGED
@@ -119,7 +119,7 @@ export default function toolRegistryExtension(pi) {
119
119
  details: transition,
120
120
  }, { deliverAs: "followUp", triggerTurn: true });
121
121
  }
122
- Observability.pruneRunObservationState(observedRuns, observedRunEventLines, summary, transitions.map((transition) => transition.run));
122
+ Observability.pruneRunObservationState(observedRuns, observedRunEventLines, summary, transitions.map((transition) => transition.stateDir ?? transition.run));
123
123
  for (const event of outboxEvents) {
124
124
  if (!Observability.shouldNotifyRunOutboxEvent(event))
125
125
  continue;
@@ -62,11 +62,17 @@ export interface ActorCommunicationSnapshot {
62
62
  updated_at: string;
63
63
  }
64
64
  export declare function readRoomRoster(stateDir: string, room: string): Record<string, RoomMember>;
65
- export declare function readBranchInboxMessages(stateDir: string, run: string, address: string, limit?: number): Array<ActorMessage & {
65
+ export interface BranchInboxRecord extends ActorMessage {
66
66
  id?: string;
67
67
  queued_at?: string;
68
68
  status?: string;
69
- }>;
69
+ }
70
+ export interface BranchInboxReadResult {
71
+ corrupted: number;
72
+ messages: BranchInboxRecord[];
73
+ }
74
+ export declare function readBranchInboxMessages(stateDir: string, run: string, address: string, limit?: number): BranchInboxRecord[];
75
+ export declare function readBranchInboxDiagnostics(stateDir: string, run: string, address: string, limit?: number): BranchInboxReadResult;
70
76
  export declare function getBranchInboxTerminalRetainLimit(): number;
71
77
  export declare function appendBranchInboxMessage(stateDir: string, run: string, address: string, message: ActorMessage): void;
72
78
  export declare function updateBranchInboxMessageStatus(stateDir: string, run: string, address: string, id: string, status: "claimed" | "handled" | "failed", metadata?: Record<string, unknown>): boolean;
@@ -272,12 +272,47 @@ function updateRosterForMessage(stateDir, room, message, receivedAt) {
272
272
  }
273
273
  return roster;
274
274
  }
275
- export function readBranchInboxMessages(stateDir, run, address, limit = 40) {
275
+ function readBranchInboxFile(stateDir, run, address, limit = 40) {
276
+ const branch = branchIdFromAddress(address, run);
277
+ if (!branch)
278
+ throw new Error(`Expected branch:${run}/<branch>; got ${address}`);
279
+ try {
280
+ const lines = readJsonlTailLines(branchInboxFile(stateDir, branch), limit);
281
+ const messages = [];
282
+ let corrupted = 0;
283
+ for (const line of lines) {
284
+ try {
285
+ messages.push(JSON.parse(line));
286
+ }
287
+ catch {
288
+ corrupted += 1;
289
+ }
290
+ }
291
+ return { corrupted, messages };
292
+ }
293
+ catch (error) {
294
+ if (error.code === "ENOENT")
295
+ return { corrupted: 0, messages: [] };
296
+ throw error;
297
+ }
298
+ }
299
+ function readAllBranchInboxLines(stateDir, run, address) {
276
300
  const branch = branchIdFromAddress(address, run);
277
301
  if (!branch)
278
302
  throw new Error(`Expected branch:${run}/<branch>; got ${address}`);
279
303
  try {
280
- return readJsonlTailLines(branchInboxFile(stateDir, branch), limit).map((line) => JSON.parse(line));
304
+ const content = fs.readFileSync(branchInboxFile(stateDir, branch), "utf8");
305
+ return content
306
+ .split("\n")
307
+ .filter((line) => line.length > 0)
308
+ .map((line) => {
309
+ try {
310
+ return { message: JSON.parse(line) };
311
+ }
312
+ catch {
313
+ return { raw: line };
314
+ }
315
+ });
281
316
  }
282
317
  catch (error) {
283
318
  if (error.code === "ENOENT")
@@ -285,6 +320,12 @@ export function readBranchInboxMessages(stateDir, run, address, limit = 40) {
285
320
  throw error;
286
321
  }
287
322
  }
323
+ export function readBranchInboxMessages(stateDir, run, address, limit = 40) {
324
+ return readBranchInboxFile(stateDir, run, address, limit).messages;
325
+ }
326
+ export function readBranchInboxDiagnostics(stateDir, run, address, limit = 40) {
327
+ return readBranchInboxFile(stateDir, run, address, limit);
328
+ }
288
329
  export function getBranchInboxTerminalRetainLimit() {
289
330
  const value = Number(process.env.PI_ACTORS_BRANCH_INBOX_TERMINAL_RETAINED ?? "");
290
331
  return Number.isInteger(value) && value >= 0
@@ -320,19 +361,27 @@ export function updateBranchInboxMessageStatus(stateDir, run, address, id, statu
320
361
  const releaseLock = acquireBranchInboxLock(stateDir, branch);
321
362
  try {
322
363
  const file = branchInboxFile(stateDir, branch);
323
- const messages = readBranchInboxMessages(stateDir, run, address, Number.MAX_SAFE_INTEGER);
364
+ const records = readAllBranchInboxLines(stateDir, run, address);
324
365
  let changed = false;
325
366
  const timestampKey = `${status}_at`;
326
- const updated = messages.map((message) => {
327
- if (message.id !== id)
328
- return message;
367
+ const updated = records.map((record) => {
368
+ if (!("message" in record) || record.message.id !== id)
369
+ return record;
329
370
  changed = true;
330
- return { ...message, ...metadata, [timestampKey]: new Date().toISOString(), status };
371
+ return { message: { ...record.message, ...metadata, [timestampKey]: new Date().toISOString(), status } };
331
372
  });
332
373
  if (!changed)
333
374
  return false;
334
- const compacted = compactBranchInboxMessages(updated);
335
- fs.writeFileSync(file, `${compacted.map((message) => JSON.stringify(message)).join("\n")}\n`);
375
+ const validMessages = updated.flatMap((record) => "message" in record ? [record.message] : []);
376
+ const compactedIds = new Set(compactBranchInboxMessages(validMessages).map((message) => message.id));
377
+ const lines = updated.flatMap((record) => {
378
+ if ("raw" in record)
379
+ return [record.raw];
380
+ if (record.message.id && !compactedIds.has(record.message.id))
381
+ return [];
382
+ return [JSON.stringify(record.message)];
383
+ });
384
+ fs.writeFileSync(file, lines.length ? `${lines.join("\n")}\n` : "");
336
385
  notifyActorWake(stateDir, address, "branch.inbox.status", { id, status });
337
386
  return true;
338
387
  }
@@ -3,8 +3,8 @@
3
3
  * Zones: async runtime, lifecycle, state files
4
4
  * Owns detached run state, observation, log tailing, listing, and cancellation safety
5
5
  */
6
- import { randomUUID } from "node:crypto";
7
6
  import { spawn, spawnSync } from "node:child_process";
7
+ import { randomUUID } from "node:crypto";
8
8
  import { closeSync, constants, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readlinkSync, rmSync, statSync, writeFileSync, writeSync, } from "node:fs";
9
9
  import { createConnection } from "node:net";
10
10
  import { platform } from "node:os";
@@ -22,7 +22,8 @@ const DEFAULT_STATE_ROOT = Paths.getRunStateRoot();
22
22
  const DEFAULT_RECIPE_ROOT = Paths.getRecipeRoot();
23
23
  function packageRoot() {
24
24
  const moduleDir = dirname(fileURLToPath(import.meta.url));
25
- if (basename(moduleDir) === "lib" && basename(dirname(moduleDir)) === "dist") {
25
+ if (basename(moduleDir) === "lib" &&
26
+ basename(dirname(moduleDir)) === "dist") {
26
27
  return dirname(dirname(moduleDir));
27
28
  }
28
29
  return dirname(moduleDir);
@@ -263,7 +264,8 @@ export function startRun(params, cwd) {
263
264
  ? resolveRecipeFile(startParams.file)
264
265
  : undefined;
265
266
  const recipe = startParams.name || getRunIdFromFile(recipeFile);
266
- const includeActorRecipeContext = startParams.actor_context !== false && startParams.actor_context !== "off";
267
+ const includeActorRecipeContext = startParams.actor_context !== false &&
268
+ startParams.actor_context !== "off";
267
269
  const recipeContextRecords = recipeFile && includeActorRecipeContext
268
270
  ? RecipeReferences.buildRecipeContextRecords(recipeFile)
269
271
  : undefined;
@@ -290,7 +292,9 @@ export function startRun(params, cwd) {
290
292
  argv: [process.execPath, ...argv],
291
293
  createdAt: new Date().toISOString(),
292
294
  cwd,
293
- ...(startParams.launch_source ? { launch_source: startParams.launch_source } : {}),
295
+ ...(startParams.launch_source
296
+ ? { launch_source: startParams.launch_source }
297
+ : {}),
294
298
  ...(startParams.ownerId ? { ownerId: startParams.ownerId } : {}),
295
299
  pid: 0,
296
300
  ...(recipe ? { recipe } : {}),
@@ -582,7 +586,9 @@ export function claimRunInboxMessage(runOrDir, owner = "runtime", statuses = ["q
582
586
  ...messages[index],
583
587
  claimed_at: new Date().toISOString(),
584
588
  claimed_by: owner,
585
- id: typeof messages[index].id === "string" ? messages[index].id : randomUUID(),
589
+ id: typeof messages[index].id === "string"
590
+ ? messages[index].id
591
+ : randomUUID(),
586
592
  status: "claimed",
587
593
  };
588
594
  messages[index] = claimed;
@@ -669,9 +675,10 @@ function appendRunInboxMessage(stateDir, message) {
669
675
  let record;
670
676
  try {
671
677
  const parsed = JSON.parse(message);
672
- record = parsed && typeof parsed === "object" && !Array.isArray(parsed)
673
- ? parsed
674
- : { body: parsed, type: "run.message" };
678
+ record =
679
+ parsed && typeof parsed === "object" && !Array.isArray(parsed)
680
+ ? parsed
681
+ : { body: parsed, type: "run.message" };
675
682
  }
676
683
  catch {
677
684
  record = { body: message, type: "run.message" };
@@ -781,6 +788,7 @@ export async function sendRunMessage(runOrDir, message, options = {}) {
781
788
  control: "inbox.jsonl",
782
789
  control_path: endpoint.path,
783
790
  control_type: endpoint.type,
791
+ inbox_id: inboxId,
784
792
  queued: true,
785
793
  run,
786
794
  sent: true,
@@ -800,6 +808,7 @@ export async function sendRunMessage(runOrDir, message, options = {}) {
800
808
  control: "control.fifo",
801
809
  control_path: endpoint.path,
802
810
  control_type: endpoint.type,
811
+ inbox_id: inboxId,
803
812
  run,
804
813
  sent: true,
805
814
  state_dir: stateDir,
@@ -813,13 +822,24 @@ export async function sendRunMessage(runOrDir, message, options = {}) {
813
822
  control: endpoint.path,
814
823
  control_path: endpoint.path,
815
824
  control_type: endpoint.type,
825
+ inbox_id: inboxId,
816
826
  run,
817
827
  sent: true,
818
828
  state_dir: stateDir,
819
829
  };
820
830
  }
821
831
  catch (error) {
822
- throw new Error(`Run control endpoint is not ready: ${endpoint.path}: ${error instanceof Error ? error.message : String(error)}`);
832
+ const deliveryError = error instanceof Error ? error.message : String(error);
833
+ throw Object.assign(new Error(`Run control endpoint is not ready: ${endpoint.path}: ${deliveryError}`), {
834
+ control_path: endpoint.path,
835
+ control_type: endpoint.type,
836
+ delivery_error: deliveryError,
837
+ inbox_id: inboxId,
838
+ queued: true,
839
+ run,
840
+ sent: false,
841
+ state_dir: stateDir,
842
+ });
823
843
  }
824
844
  }
825
845
  export function getRunProcessSignalPlan(pid, signal, runtimePlatform = process.platform) {
@@ -842,7 +862,9 @@ function signalOwnedRunProcess(pid, signal) {
842
862
  if (plan.command && plan.args) {
843
863
  const result = spawnSync(plan.command, plan.args, { encoding: "utf8" });
844
864
  if (result.status !== 0) {
845
- throw new Error(result.stderr?.trim() || result.stdout?.trim() || `${plan.command} failed`);
865
+ throw new Error(result.stderr?.trim() ||
866
+ result.stdout?.trim() ||
867
+ `${plan.command} failed`);
846
868
  }
847
869
  return plan;
848
870
  }
@@ -4,7 +4,7 @@
4
4
  * Owns ambient summaries, terminal events, and run outbox delivery for detached command-template runs
5
5
  */
6
6
  import { existsSync, readdirSync, readFileSync } from "node:fs";
7
- import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
7
+ import { basename, dirname, isAbsolute, join, relative, resolve, } from "node:path";
8
8
  import * as AsyncRuns from "./async-runs.js";
9
9
  import * as Paths from "./paths.js";
10
10
  const TERMINAL = new Set([
@@ -324,14 +324,22 @@ export async function executeRunRetirements(summary, options) {
324
324
  const results = [];
325
325
  for (const candidate of findRunRetirementCandidates(summary)) {
326
326
  if (options.attempted?.has(candidate.stateDir)) {
327
- results.push({ action: "skip", run: candidate.run, stateDir: candidate.stateDir });
327
+ results.push({
328
+ action: "skip",
329
+ run: candidate.run,
330
+ stateDir: candidate.stateDir,
331
+ });
328
332
  continue;
329
333
  }
330
334
  options.attempted?.add(candidate.stateDir);
331
335
  try {
332
336
  await options.sendStop(candidate);
333
337
  options.notify?.(`Retiring actor ${candidate.run} after child runs reached terminal state`, "info");
334
- results.push({ action: "stop", run: candidate.run, stateDir: candidate.stateDir });
338
+ results.push({
339
+ action: "stop",
340
+ run: candidate.run,
341
+ stateDir: candidate.stateDir,
342
+ });
335
343
  continue;
336
344
  }
337
345
  catch (error) {
@@ -343,13 +351,19 @@ export async function executeRunRetirements(summary, options) {
343
351
  : `Actor retirement skipped for ${candidate.run}: ${error instanceof Error ? error.message : String(error)}`, cancelled ? "warning" : "error");
344
352
  results.push({
345
353
  action: cancelled ? "cancel" : "skip",
346
- ...(cancelled ? {} : { error: error instanceof Error ? error.message : String(error) }),
354
+ ...(cancelled
355
+ ? {}
356
+ : {
357
+ error: error instanceof Error ? error.message : String(error),
358
+ }),
347
359
  run: candidate.run,
348
360
  stateDir: candidate.stateDir,
349
361
  });
350
362
  }
351
363
  catch (cancelError) {
352
- const message = cancelError instanceof Error ? cancelError.message : String(cancelError);
364
+ const message = cancelError instanceof Error
365
+ ? cancelError.message
366
+ : String(cancelError);
353
367
  options.notify?.(`Actor retirement failed for ${candidate.run}: ${message}`, "error");
354
368
  results.push({
355
369
  action: "failed",
@@ -362,10 +376,14 @@ export async function executeRunRetirements(summary, options) {
362
376
  }
363
377
  return results;
364
378
  }
379
+ function runObservationKey(run) {
380
+ return run.stateDir ?? run.run;
381
+ }
365
382
  export function detectRunTransitions(previous, summary) {
366
383
  const transitions = [];
367
384
  for (const run of summary.runs) {
368
- const old = previous.get(run.run);
385
+ const key = runObservationKey(run);
386
+ const old = previous.get(key);
369
387
  if (old && old !== run.status && TERMINAL.has(run.status)) {
370
388
  transitions.push({
371
389
  from: old,
@@ -379,7 +397,7 @@ export function detectRunTransitions(previous, summary) {
379
397
  ...(run.tool ? { tool: run.tool } : {}),
380
398
  });
381
399
  }
382
- previous.set(run.run, run.status);
400
+ previous.set(key, run.status);
383
401
  }
384
402
  return transitions;
385
403
  }
@@ -440,11 +458,11 @@ function readOutboxLines(run) {
440
458
  return content ? content.split("\n") : [];
441
459
  }
442
460
  export function pruneRunObservationState(previousStatuses, previousLineCounts, summary, terminalRuns = []) {
443
- const activeRuns = new Set(summary.runs.map((run) => run.run));
461
+ const activeRuns = new Set(summary.runs.map((run) => runObservationKey(run)));
444
462
  const terminalRunSet = new Set(terminalRuns);
445
463
  const terminalLineKeys = new Set(summary.runs
446
- .filter((run) => terminalRunSet.has(run.run))
447
- .map((run) => run.stateDir ?? run.run));
464
+ .filter((run) => terminalRunSet.has(runObservationKey(run)))
465
+ .map((run) => runObservationKey(run)));
448
466
  const activeLineKeys = new Set(summary.runs.map((run) => run.stateDir ?? run.run));
449
467
  for (const run of terminalRunSet)
450
468
  previousStatuses.delete(run);