@llblab/pi-actors 0.19.11 → 0.20.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 (54) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +8 -0
  3. package/dist/lib/actor-inspector-tui.d.ts +55 -0
  4. package/dist/lib/actor-inspector-tui.js +559 -0
  5. package/dist/lib/actor-messages.d.ts +25 -0
  6. package/dist/lib/actor-messages.js +122 -0
  7. package/dist/lib/actor-recipe-context.d.ts +14 -0
  8. package/dist/lib/actor-recipe-context.js +79 -0
  9. package/dist/lib/actor-rooms.d.ts +81 -0
  10. package/dist/lib/actor-rooms.js +468 -0
  11. package/dist/lib/async-runs.d.ts +101 -0
  12. package/dist/lib/async-runs.js +612 -0
  13. package/dist/lib/command-templates.d.ts +70 -0
  14. package/dist/lib/command-templates.js +592 -0
  15. package/dist/lib/config.d.ts +34 -0
  16. package/dist/lib/config.js +226 -0
  17. package/dist/lib/execution.d.ts +63 -0
  18. package/dist/lib/execution.js +450 -0
  19. package/dist/lib/file-state.d.ts +6 -0
  20. package/dist/lib/file-state.js +25 -0
  21. package/dist/lib/identity.d.ts +9 -0
  22. package/dist/lib/identity.js +27 -0
  23. package/dist/lib/observability.d.ts +86 -0
  24. package/dist/lib/observability.js +534 -0
  25. package/dist/lib/output.d.ts +25 -0
  26. package/dist/lib/output.js +89 -0
  27. package/dist/lib/paths.d.ts +11 -0
  28. package/dist/lib/paths.js +28 -0
  29. package/dist/lib/prompts.d.ts +23 -0
  30. package/dist/lib/prompts.js +50 -0
  31. package/dist/lib/recipe-discovery.d.ts +50 -0
  32. package/dist/lib/recipe-discovery.js +317 -0
  33. package/dist/lib/recipe-migration.d.ts +21 -0
  34. package/dist/lib/recipe-migration.js +90 -0
  35. package/dist/lib/recipe-references.d.ts +67 -0
  36. package/dist/lib/recipe-references.js +542 -0
  37. package/dist/lib/recipe-usage.d.ts +6 -0
  38. package/dist/lib/recipe-usage.js +57 -0
  39. package/dist/lib/registry.d.ts +47 -0
  40. package/dist/lib/registry.js +222 -0
  41. package/dist/lib/runtime.d.ts +36 -0
  42. package/dist/lib/runtime.js +126 -0
  43. package/dist/lib/schema.d.ts +48 -0
  44. package/dist/lib/schema.js +355 -0
  45. package/dist/lib/temp.d.ts +10 -0
  46. package/dist/lib/temp.js +90 -0
  47. package/dist/lib/tools.d.ts +39 -0
  48. package/dist/lib/tools.js +982 -0
  49. package/lib/async-runs.ts +20 -4
  50. package/package.json +5 -2
  51. package/scripts/async-runner.mjs +8 -12
  52. package/scripts/validate-recipe.mjs +9 -13
  53. package/skills/actors/SKILL.md +1 -1
  54. package/skills/swarm/SKILL.md +1 -1
package/AGENTS.md CHANGED
@@ -43,7 +43,7 @@
43
43
  - `Typed arg authoring`: Typed args support `string`, `path`, `int`, `number`, `bool`, and `enum(...)` plus two equivalent readability styles: metadata-first (`args` + `defaults` + simple `{name}` placeholders) for long command lines, and inline-first (`{name:type=default}` placeholders) for compact one-property templates | Trigger: Changing arg parsing, docs, schema generation, or registry serialization | Action: Preserve both styles, keep explicit `args` type declarations higher priority than inline placeholder types, and make breaking cleanup explicit when removing old arg shapes
44
44
  - `Template recipe graph`: The valid execution chain is `tool → template → recipe → run → template`; file-backed and co-located recipes are storage variants of that chain | Trigger: Adding registry bindings, recipes, docs, or runtime shortcuts | Action: Keep command templates synchronous and portable, use `async: true` as the detached run switch, require every recipe to own `template` directly, and reject cyclic shortcuts such as recipe-owned `tool`
45
45
  - `Layer boundary discipline`: Command-template evolution must be separated from template-recipe configuration and async-run lifecycle configuration | Trigger: Adding syntax, placeholders, imports, async controls, or docs | Action: Put portable execution graph semantics in `docs/command-templates.md`, recipe storage/import/default/reference behavior in `docs/template-recipes.md`, and detached lifecycle/state/IPC behavior in `docs/async-runs.md`; type imported recipes as command-template-shaped recipe definitions, not async-run instances
46
- - `Executable script recipes`: Recipe templates may point directly at executable helper scripts, including JavaScript `.mjs` files with shebangs; do not prefix such recipes with `node` unless the script is intentionally not executable | Trigger: Adding or editing script-backed recipes and docs | Action: Keep the script executable bit, call `{repo}/scripts/name.mjs ...` directly, keep the standard library on one maintained wrapper per capability unless a second wrapper has a concrete platform reason, and ensure installed npm script entrypoints do not import `.ts` files from under `node_modules` through Node native type stripping
46
+ - `Executable script recipes`: Recipe templates may point directly at executable helper scripts, including JavaScript `.mjs` files with shebangs; do not prefix such recipes with `node` unless the script is intentionally not executable | Trigger: Adding or editing script-backed recipes and docs | Action: Keep the script executable bit, call `{repo}/scripts/name.mjs ...` directly, keep the standard library on one maintained wrapper per capability unless a second wrapper has a concrete platform reason, and ship compiled `dist/lib/*.js` runtime modules for installed npm script entrypoints and ensure those scripts do not import `.ts` files from under `node_modules` through Node native type stripping
47
47
  - `Registry safety boundaries`: Tool definitions use `template`, not `script`, and built-in/core tool names must not be shadowed | Trigger: Loading/editing persisted config or registration logic | Action: Reject legacy `script` entries explicitly, avoid silent user-config rewrites outside the repo, and keep conflict checks before persistence/runtime registration
48
48
  - `Async run observability`: Ambient triangles count active async work units across the visible run tree: each running async run contributes at least one triangle, reported active parallel command/subagent branches contribute the visible branch count when greater than one, and descendant `pi -p` subagent processes are folded in so coordinator-plus-workers scenarios expand beyond a single coordinator marker. Event-driven terminal/outbox watchers should initiate follow-up for unhandled terminal completion/failure states, failed or in-flight `command.done` branch completions, and coordinator-bound script-authored messages with bounded body previews; actor `message` is the explicit coordinator-to-run command channel paired with these upward events. Do not restore busy-polling loops, sleep-then-status smoke examples, duplicate follow-ups for final successful leaf commands, or duplicate follow-ups for `cancel`, `kill`, or control-stop actions already handled by synchronous tool results. | Trigger: Changing async run UI, notifications, actor-message routing, or smoke-test interpretation | Action: Preserve branch-aware triangles from `progress.activeSubagents`, runtime-inferred branch bubbling for packaged fanout completion, process-tree expansion for coordinator-launched workers, terminal notifications as event-driven behavior, and docs/examples that teach reactive run→coordinator→message loops before sleep-polling patterns.
49
49
  - `Communication direction`: The design target is an organic universal message layer across sync tasks, async runs, branches, tools, and coordinators. Breaking changes are allowed to compress concepts, remove accidental duplication, and make duplex communication symmetric where the domain is symmetric. | Trigger: Designing APIs or recipes that communicate | Action: Prefer a concentrated actor/message protocol (`spawn`, `message`, `inspect`, addressed endpoints, typed message envelopes, mailbox accepts/emits) over exposing FIFO/outbox/status mechanics directly; use one envelope for upward, downward, lateral, parent/branch, and branch/parent messages; absorb runtime async primitives into actor API instead of preserving parallel public concepts.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.20.0: Compiled Runtime Entrypoints
4
+
5
+ - `[Packaging]` Added a build step that emits compiled `dist/lib/*.js` and declaration files from the TypeScript runtime modules, with relative `.ts` imports rewritten to `.js` for installed package execution.
6
+ - `[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.
7
+ - `[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.
8
+ - `[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.
9
+ - `[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`.
10
+
3
11
  ## 0.19.11: Installed Async Runner Hotfix
4
12
 
5
13
  - `[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.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Actor inspector TUI previews.
3
+ * Zones: terminal actor inspection, room/direct message previews, no-dependency UI formatting
4
+ */
5
+ export interface ActorInspectorPreview {
6
+ body_preview?: string;
7
+ branch?: string;
8
+ channel: "broadcast" | "direct" | "room";
9
+ from?: string;
10
+ from_display?: string;
11
+ inbox_status?: string;
12
+ message_id?: string;
13
+ run: string;
14
+ sequence?: number;
15
+ summary?: string;
16
+ stripe?: boolean;
17
+ timestamp: string;
18
+ to: string;
19
+ type: string;
20
+ }
21
+ export interface ActorInspectorWidgetStyle {
22
+ actor?: (text: string) => string;
23
+ muted?: (text: string) => string;
24
+ preview?: (text: string) => string;
25
+ stripe?: (text: string) => string;
26
+ stripeAlt?: (text: string) => string;
27
+ target?: (text: string) => string;
28
+ type?: (text: string) => string;
29
+ }
30
+ export interface ActorInspectorRenderOptions {
31
+ }
32
+ export interface ActorInspectorItemViewOptions {
33
+ sequence: number;
34
+ }
35
+ export interface ActorInspectorRosterMember {
36
+ address: string;
37
+ display?: string;
38
+ role?: string;
39
+ status?: string;
40
+ }
41
+ export interface ActorInspectorPreviewReadOptions {
42
+ ownerId?: string;
43
+ branch?: string;
44
+ currentRunOnly?: boolean;
45
+ channels?: ActorInspectorPreview["channel"][];
46
+ mention?: string;
47
+ roomLimitPerRun?: number;
48
+ unreadOnly?: boolean;
49
+ }
50
+ export declare function readActorInspectorPreviews(stateRoot?: string, limit?: number, options?: ActorInspectorPreviewReadOptions): ActorInspectorPreview[];
51
+ export declare function readActorInspectorRoster(stateRoot: string | undefined, run: string, room?: string): ActorInspectorRosterMember[];
52
+ export declare function renderInspectorRosterLine(members: ActorInspectorRosterMember[], width?: number, styles?: ActorInspectorWidgetStyle): string | undefined;
53
+ export declare function renderInspectorRosterPanel(members: ActorInspectorRosterMember[], width?: number, styles?: ActorInspectorWidgetStyle): string[] | undefined;
54
+ export declare function renderInspectorItemView(previews: ActorInspectorPreview[], width: number | undefined, styles: ActorInspectorWidgetStyle | undefined, options: ActorInspectorItemViewOptions): string[] | undefined;
55
+ export declare function renderInspectorWidget(previews: ActorInspectorPreview[], width?: number, styles?: ActorInspectorWidgetStyle, options?: ActorInspectorRenderOptions): string[] | undefined;
@@ -0,0 +1,559 @@
1
+ /**
2
+ * Actor inspector TUI previews.
3
+ * Zones: terminal actor inspection, room/direct message previews, no-dependency UI formatting
4
+ */
5
+ import * as fs from "node:fs";
6
+ import * as path from "node:path";
7
+ import { visibleWidth } from "@earendil-works/pi-tui";
8
+ import * as Paths from "./paths.js";
9
+ function asRecord(value) {
10
+ return value && typeof value === "object" && !Array.isArray(value)
11
+ ? value
12
+ : {};
13
+ }
14
+ function readJsonLines(file) {
15
+ try {
16
+ return fs
17
+ .readFileSync(file, "utf8")
18
+ .split("\n")
19
+ .filter(Boolean)
20
+ .flatMap((line) => {
21
+ try {
22
+ return [JSON.parse(line)];
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ });
28
+ }
29
+ catch (error) {
30
+ if (error.code === "ENOENT")
31
+ return [];
32
+ return [];
33
+ }
34
+ }
35
+ function previewValue(value, maxLength = 320) {
36
+ if (value === undefined)
37
+ return undefined;
38
+ const text = typeof value === "string" ? value : JSON.stringify(value);
39
+ const compact = text.replaceAll(/\s+/g, " ").trim();
40
+ if (!compact)
41
+ return undefined;
42
+ return compact.length > maxLength
43
+ ? `${compact.slice(0, Math.max(0, maxLength - 1))}…`
44
+ : compact;
45
+ }
46
+ function channelFor(message) {
47
+ if (message.to.startsWith("room:"))
48
+ return "room";
49
+ if (message.to === "coordinator" || message.to.startsWith("session:"))
50
+ return "broadcast";
51
+ return "direct";
52
+ }
53
+ function previewFromMessage(run, message, timestamp, displayNames = {}) {
54
+ const to = typeof message.to === "string" ? message.to : undefined;
55
+ const type = typeof message.type === "string" ? message.type : undefined;
56
+ if (!to || !type)
57
+ return undefined;
58
+ const from = typeof message.from === "string" ? message.from : undefined;
59
+ const summary = typeof message.summary === "string" ? message.summary : undefined;
60
+ const body = asRecord(message.body);
61
+ const display = from
62
+ ? typeof body.display === "string" && body.display.trim()
63
+ ? body.display.trim()
64
+ : displayNames[from]
65
+ : undefined;
66
+ return {
67
+ ...(previewValue(message.body)
68
+ ? { body_preview: previewValue(message.body) }
69
+ : {}),
70
+ channel: channelFor({ to }),
71
+ ...(from ? { from } : {}),
72
+ ...(display ? { from_display: display } : {}),
73
+ run,
74
+ ...(summary ? { summary } : {}),
75
+ timestamp,
76
+ to,
77
+ type,
78
+ };
79
+ }
80
+ function readRoomRosterRecords(stateDir, room) {
81
+ try {
82
+ return JSON.parse(fs.readFileSync(path.join(stateDir, "rooms", room, "roster.json"), "utf8"));
83
+ }
84
+ catch {
85
+ return {};
86
+ }
87
+ }
88
+ function memberDisplay(_address, member) {
89
+ const display = typeof member.display === "string" ? member.display.trim() : "";
90
+ return display || undefined;
91
+ }
92
+ function readRoomDisplayNames(stateDir, room) {
93
+ const roster = readRoomRosterRecords(stateDir, room);
94
+ return Object.fromEntries(Object.entries(roster).flatMap(([address, member]) => {
95
+ const display = memberDisplay(address, member);
96
+ return display ? [[address, display]] : [];
97
+ }));
98
+ }
99
+ function readRoomPreviews(run, stateDir) {
100
+ const roomsDir = path.join(stateDir, "rooms");
101
+ try {
102
+ const previews = fs
103
+ .readdirSync(roomsDir, { withFileTypes: true })
104
+ .filter((entry) => entry.isDirectory())
105
+ .flatMap((entry) => {
106
+ const displayNames = readRoomDisplayNames(stateDir, entry.name);
107
+ return readJsonLines(path.join(roomsDir, entry.name, "messages.jsonl"))
108
+ .map((message) => previewFromMessage(run, message, String(message.received_at ?? message.timestamp ?? ""), displayNames))
109
+ .filter((preview) => Boolean(preview));
110
+ });
111
+ return previews;
112
+ }
113
+ catch (error) {
114
+ if (error.code === "ENOENT")
115
+ return [];
116
+ return [];
117
+ }
118
+ }
119
+ function readInboxPreviews(run, stateDir) {
120
+ return readJsonLines(path.join(stateDir, "inbox.jsonl"))
121
+ .map((message) => previewFromMessage(run, message, String(message.received_at ?? message.timestamp ?? "")))
122
+ .filter((preview) => Boolean(preview));
123
+ }
124
+ function readBranchInboxPreviews(run, stateDir) {
125
+ const branchesDir = path.join(stateDir, "branches");
126
+ try {
127
+ return fs
128
+ .readdirSync(branchesDir, { withFileTypes: true })
129
+ .filter((entry) => entry.isDirectory())
130
+ .flatMap((entry) => readJsonLines(path.join(branchesDir, entry.name, "inbox.jsonl"))
131
+ .map((message) => {
132
+ const preview = previewFromMessage(run, message, String(message.queued_at ?? message.received_at ?? message.timestamp ?? ""));
133
+ if (!preview)
134
+ return undefined;
135
+ return {
136
+ ...preview,
137
+ branch: entry.name,
138
+ ...(typeof message.id === "string" ? { message_id: message.id } : {}),
139
+ ...(typeof message.status === "string" ? { inbox_status: message.status } : {}),
140
+ };
141
+ })
142
+ .filter((preview) => Boolean(preview)));
143
+ }
144
+ catch (error) {
145
+ if (error.code === "ENOENT")
146
+ return [];
147
+ return [];
148
+ }
149
+ }
150
+ function readOutboxPreviews(run, stateDir) {
151
+ return readJsonLines(path.join(stateDir, "outbox.jsonl"))
152
+ .map((event) => {
153
+ const message = asRecord(event.message ?? event);
154
+ return previewFromMessage(run, message, String(event.timestamp ?? event.created_at ?? event.emitted_at ?? ""));
155
+ })
156
+ .filter((preview) => Boolean(preview));
157
+ }
158
+ function getRunOwnerId(stateDir) {
159
+ try {
160
+ const meta = JSON.parse(fs.readFileSync(path.join(stateDir, "run.json"), "utf8"));
161
+ return typeof meta.ownerId === "string" ? meta.ownerId : undefined;
162
+ }
163
+ catch {
164
+ return undefined;
165
+ }
166
+ }
167
+ function matchesOwner(stateDir, ownerId) {
168
+ return ownerId === undefined || getRunOwnerId(stateDir) === ownerId;
169
+ }
170
+ function isUnreadPreview(preview) {
171
+ return preview.inbox_status === "queued" || preview.inbox_status === undefined && preview.branch !== undefined;
172
+ }
173
+ function matchesBranchFilter(preview, branch) {
174
+ const name = branch?.trim();
175
+ if (!name)
176
+ return true;
177
+ if (preview.branch !== undefined)
178
+ return preview.branch === name;
179
+ const address = `branch:${preview.run}/${name}`;
180
+ return preview.from === address || preview.to === address;
181
+ }
182
+ function matchesPreviewFilter(preview, options) {
183
+ if (options.channels?.length && !options.channels.includes(preview.channel)) {
184
+ return false;
185
+ }
186
+ if (options.unreadOnly && !isUnreadPreview(preview))
187
+ return false;
188
+ if (!matchesBranchFilter(preview, options.branch))
189
+ return false;
190
+ const mention = options.mention?.trim().toLowerCase();
191
+ if (!mention)
192
+ return true;
193
+ return [
194
+ preview.branch,
195
+ preview.from,
196
+ preview.from_display,
197
+ preview.inbox_status,
198
+ preview.to,
199
+ preview.type,
200
+ preview.summary,
201
+ preview.body_preview,
202
+ ].some((value) => value?.toLowerCase().includes(mention));
203
+ }
204
+ function limitRoomPreviewsPerRun(previews, limitPerRun) {
205
+ if (!Number.isFinite(limitPerRun))
206
+ return previews;
207
+ const limit = Math.max(0, Number(limitPerRun));
208
+ const remainingByRun = new Map();
209
+ const keep = new Set();
210
+ for (let index = previews.length - 1; index >= 0; index -= 1) {
211
+ const preview = previews[index];
212
+ if (preview.channel !== "room") {
213
+ keep.add(preview);
214
+ continue;
215
+ }
216
+ const remaining = remainingByRun.get(preview.run) ?? limit;
217
+ if (remaining <= 0) {
218
+ remainingByRun.set(preview.run, 0);
219
+ continue;
220
+ }
221
+ keep.add(preview);
222
+ remainingByRun.set(preview.run, remaining - 1);
223
+ }
224
+ return previews.filter((preview) => keep.has(preview));
225
+ }
226
+ export function readActorInspectorPreviews(stateRoot = Paths.getRunStateRoot(), limit = 8, options = {}) {
227
+ try {
228
+ const previews = fs
229
+ .readdirSync(stateRoot, { withFileTypes: true })
230
+ .filter((entry) => entry.isDirectory())
231
+ .flatMap((entry) => {
232
+ const stateDir = path.join(stateRoot, entry.name);
233
+ if (!matchesOwner(stateDir, options.ownerId))
234
+ return [];
235
+ return [
236
+ ...readRoomPreviews(entry.name, stateDir),
237
+ ...readInboxPreviews(entry.name, stateDir),
238
+ ...readBranchInboxPreviews(entry.name, stateDir),
239
+ ...readOutboxPreviews(entry.name, stateDir),
240
+ ];
241
+ })
242
+ .filter((preview) => preview.timestamp)
243
+ .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
244
+ const currentRun = options.currentRunOnly
245
+ ? previews.at(-1)?.run
246
+ : undefined;
247
+ const sequenced = previews
248
+ .filter((preview) => !currentRun || preview.run === currentRun)
249
+ .filter((preview) => matchesPreviewFilter(preview, options))
250
+ .map((preview, index) => ({
251
+ ...preview,
252
+ sequence: index + 1,
253
+ stripe: index % 2 === 1,
254
+ }));
255
+ return limitRoomPreviewsPerRun(sequenced, options.roomLimitPerRun).slice(-Math.max(1, limit));
256
+ }
257
+ catch (error) {
258
+ if (error.code === "ENOENT")
259
+ return [];
260
+ return [];
261
+ }
262
+ }
263
+ function shorten(value, maxLength, options = { preserveSpaces: true }) {
264
+ if (!value)
265
+ return "-";
266
+ const compact = options.preserveSpaces === false
267
+ ? value.replaceAll(/\s+/g, "_")
268
+ : value.replaceAll(/\s+/g, " ").trim();
269
+ if (maxLength <= 1)
270
+ return compact.slice(0, Math.max(0, maxLength));
271
+ return compact.length > maxLength
272
+ ? `${compact.slice(0, Math.max(0, maxLength - 1))}…`
273
+ : compact;
274
+ }
275
+ function actorName(address) {
276
+ if (!address)
277
+ return "unknown";
278
+ const branch = /^branch:[^/]+\/(.+)$/.exec(address);
279
+ if (branch)
280
+ return branch[1] || address;
281
+ const run = /^run:(.+)$/.exec(address);
282
+ if (run)
283
+ return run[1] || address;
284
+ return address;
285
+ }
286
+ function roomName(address) {
287
+ const room = /^room:([^/]+)(?:\/(main))?$/.exec(address);
288
+ return room ? room[1] : undefined;
289
+ }
290
+ function routeActorText(preview) {
291
+ return preview.from_display || actorName(preview.from);
292
+ }
293
+ function routeText(preview) {
294
+ const actor = routeActorText(preview);
295
+ if (preview.channel === "room")
296
+ return `${actor} # all`;
297
+ if (preview.channel === "broadcast")
298
+ return `${actor} ⇢ ${preview.to}`;
299
+ return `${actor} → ${actorName(preview.to)}`;
300
+ }
301
+ function style(styleFn, text) {
302
+ return styleFn ? styleFn(text) : text;
303
+ }
304
+ function previewText(preview) {
305
+ return preview.summary || preview.body_preview || "-";
306
+ }
307
+ function propertyValue(value) {
308
+ if (value === undefined)
309
+ return "";
310
+ if (typeof value === "string")
311
+ return value;
312
+ if (typeof value === "number" || typeof value === "boolean")
313
+ return String(value);
314
+ return JSON.stringify(value);
315
+ }
316
+ function displayWidth(value) {
317
+ return visibleWidth(value);
318
+ }
319
+ const lineSegmenter = new Intl.Segmenter();
320
+ function boundedLine(value, width) {
321
+ if (width <= 0)
322
+ return "";
323
+ if (visibleWidth(value) <= width)
324
+ return value;
325
+ const ellipsis = "…";
326
+ const ellipsisWidth = visibleWidth(ellipsis);
327
+ if (width <= ellipsisWidth)
328
+ return ellipsis.slice(0, width);
329
+ let output = "";
330
+ let used = 0;
331
+ const maxTextWidth = width - ellipsisWidth;
332
+ for (const { segment } of lineSegmenter.segment(value)) {
333
+ const segmentWidth = visibleWidth(segment);
334
+ if (used + segmentWidth > maxTextWidth)
335
+ break;
336
+ output += segment;
337
+ used += segmentWidth;
338
+ }
339
+ return `${output}${ellipsis}`;
340
+ }
341
+ function padLine(plain, rendered, width, styles) {
342
+ const boundedPlain = boundedLine(plain, width);
343
+ const visible = boundedPlain === plain ? rendered : style(styles.preview, boundedPlain);
344
+ const padding = Math.max(0, width - visibleWidth(boundedPlain));
345
+ return `${visible}${" ".repeat(padding)}`;
346
+ }
347
+ function renderCompactInspectorEntry(preview, width, sequenceWidth, routeWidth, typeWidth, styles, stripe) {
348
+ const separator = " ";
349
+ const prefix = " ";
350
+ const suffix = " ";
351
+ const contentWidth = Math.max(8, width - prefix.length - suffix.length);
352
+ const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
353
+ const sequencePrefix = `${sequence}${separator}`;
354
+ const route = routeText(preview);
355
+ const routePadding = " ".repeat(Math.max(0, routeWidth - displayWidth(route)));
356
+ const typePadding = " ".repeat(Math.max(0, typeWidth - displayWidth(preview.type)));
357
+ const headline = previewText(preview);
358
+ const lead = `${sequencePrefix}${route}${routePadding}${separator}${preview.type}${typePadding}${separator}`;
359
+ const visibleHeadline = boundedLine(headline, Math.max(0, contentWidth - displayWidth(lead)));
360
+ const plain = `${lead}${visibleHeadline}`;
361
+ const rendered = [
362
+ style(styles.muted, sequencePrefix),
363
+ style(styles.target, route),
364
+ routePadding,
365
+ separator,
366
+ style(styles.type, preview.type),
367
+ typePadding,
368
+ separator,
369
+ style(styles.preview, visibleHeadline),
370
+ ].join("");
371
+ const line = `${prefix}${padLine(plain, rendered, contentWidth, styles)}${suffix}`;
372
+ if (stripe && styles.stripe)
373
+ return [styles.stripe(line)];
374
+ if (!stripe && styles.stripeAlt)
375
+ return [styles.stripeAlt(line)];
376
+ return [line];
377
+ }
378
+ function renderInspectorEntry(preview, width, sequenceWidth, routeWidth, typeWidth, summaryWidth, styles, stripe) {
379
+ const separator = " ";
380
+ const prefix = " ";
381
+ const suffix = " ";
382
+ const contentWidth = Math.max(8, width - prefix.length - suffix.length);
383
+ const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
384
+ const sequencePrefix = `${sequence}${separator}`;
385
+ const route = routeText(preview);
386
+ const type = preview.type;
387
+ const summary = preview.summary?.trim() ?? "";
388
+ const body = preview.body_preview?.trim() || (!summary ? previewText(preview) : "-");
389
+ const visibleRoute = boundedLine(route, routeWidth);
390
+ const visibleType = boundedLine(type, typeWidth);
391
+ const boundedRoutePadding = " ".repeat(Math.max(0, routeWidth - displayWidth(visibleRoute)));
392
+ const boundedTypePadding = " ".repeat(Math.max(0, typeWidth - displayWidth(visibleType)));
393
+ const leadParts = [
394
+ style(styles.muted, sequencePrefix),
395
+ style(styles.target, visibleRoute),
396
+ boundedRoutePadding,
397
+ separator,
398
+ style(styles.type, visibleType),
399
+ boundedTypePadding,
400
+ separator,
401
+ ];
402
+ let lead = `${sequencePrefix}${visibleRoute}${boundedRoutePadding}${separator}${visibleType}${boundedTypePadding}${separator}`;
403
+ if (summary) {
404
+ const visibleSummary = boundedLine(summary, summaryWidth);
405
+ const summaryPadding = " ".repeat(Math.max(0, summaryWidth - displayWidth(visibleSummary)));
406
+ lead += `${visibleSummary}${summaryPadding}${separator}`;
407
+ leadParts.push(style(styles.preview, visibleSummary), summaryPadding, separator);
408
+ }
409
+ const visibleBody = boundedLine(body, Math.max(0, contentWidth - displayWidth(lead)));
410
+ const plain = `${lead}${visibleBody}`;
411
+ const rendered = `${leadParts.join("")}${style(styles.preview, visibleBody)}`;
412
+ const line = `${prefix}${padLine(plain, rendered, contentWidth, styles)}${suffix}`;
413
+ if (stripe && styles.stripe)
414
+ return [styles.stripe(line)];
415
+ if (!stripe && styles.stripeAlt)
416
+ return [styles.stripeAlt(line)];
417
+ return [line];
418
+ }
419
+ export function readActorInspectorRoster(stateRoot = Paths.getRunStateRoot(), run, room = "main") {
420
+ const stateDir = path.join(stateRoot, run);
421
+ const roster = readRoomRosterRecords(stateDir, room);
422
+ return Object.entries(roster).map(([address, member]) => ({
423
+ address,
424
+ ...(memberDisplay(address, member)
425
+ ? { display: memberDisplay(address, member) }
426
+ : {}),
427
+ ...(typeof member.role === "string" ? { role: member.role } : {}),
428
+ ...(typeof member.status === "string" ? { status: member.status } : {}),
429
+ }));
430
+ }
431
+ function rosterRoleText(role) {
432
+ const roleLabel = role?.split(";")[0] ?? "";
433
+ const cleaned = roleLabel.replaceAll(/\s*\([^)]*\)\s*$/g, "").trim().toLowerCase();
434
+ if (!cleaned || cleaned === "actor")
435
+ return undefined;
436
+ return cleaned.replaceAll(/\s+/g, "-");
437
+ }
438
+ function rosterMemberText(member) {
439
+ const name = member.display || actorName(member.address);
440
+ const role = rosterRoleText(member.role);
441
+ if (role === "run")
442
+ return `run/${name}`;
443
+ return role ? `${name}/${role}` : name;
444
+ }
445
+ function isRosterMemberActive(member) {
446
+ const status = member.status?.trim().toLowerCase();
447
+ return !status || status === "present" || status === "active" || status === "running";
448
+ }
449
+ export function renderInspectorRosterLine(members, width = 80, styles = {}) {
450
+ return renderInspectorRosterPanel(members, width, styles)?.[0];
451
+ }
452
+ export function renderInspectorRosterPanel(members, width = 80, styles = {}) {
453
+ if (members.length === 0)
454
+ return undefined;
455
+ const safeWidth = Math.max(1, width);
456
+ const innerWidth = Math.max(1, safeWidth - 2);
457
+ const prefix = `roster ${members.length}: `;
458
+ const tokens = members.map((member) => ({
459
+ active: isRosterMemberActive(member),
460
+ text: rosterMemberText(member),
461
+ }));
462
+ const lines = [];
463
+ let plain = prefix;
464
+ let rendered = style(styles.muted, prefix);
465
+ const flush = () => {
466
+ const visible = boundedLine(plain, innerWidth);
467
+ const line = visible === plain ? rendered : style(styles.muted, visible);
468
+ lines.push(` ${line}${" ".repeat(Math.max(0, innerWidth - displayWidth(visible)))} `);
469
+ };
470
+ for (const token of tokens) {
471
+ const separator = plain === prefix ? "" : ", ";
472
+ const nextPlain = `${plain}${separator}${token.text}`;
473
+ const renderedToken = style(token.active ? styles.target : styles.muted, token.text);
474
+ if (plain !== prefix && displayWidth(nextPlain) > innerWidth) {
475
+ flush();
476
+ plain = ` ${token.text}`;
477
+ rendered = `${style(styles.muted, " ")}${renderedToken}`;
478
+ continue;
479
+ }
480
+ plain = nextPlain;
481
+ rendered = `${rendered}${style(styles.muted, separator)}${renderedToken}`;
482
+ }
483
+ flush();
484
+ return lines;
485
+ }
486
+ export function renderInspectorItemView(previews, width = 80, styles = {}, options) {
487
+ const preview = previews.find((item) => item.sequence === options.sequence);
488
+ if (!preview)
489
+ return undefined;
490
+ const safeWidth = Math.max(1, width);
491
+ const orderedKeys = [
492
+ "channel",
493
+ "run",
494
+ "from",
495
+ "from_display",
496
+ "to",
497
+ "type",
498
+ "summary",
499
+ "body_preview",
500
+ "timestamp",
501
+ "stripe",
502
+ ];
503
+ const entries = orderedKeys
504
+ .filter((key) => preview[key] !== undefined)
505
+ .map((key) => [key, propertyValue(preview[key])]);
506
+ const keyWidth = Math.max(1, ...entries.map(([key]) => displayWidth(key)));
507
+ const sequenceText = String(preview.sequence ?? options.sequence);
508
+ const sequencePadding = " ".repeat(Math.max(0, keyWidth - displayWidth(sequenceText)));
509
+ const headerSeparator = " ";
510
+ const route = routeText(preview);
511
+ const visibleRoute = boundedLine(route, Math.max(0, safeWidth - keyWidth - headerSeparator.length));
512
+ const headerPlain = `${sequenceText}${sequencePadding}${headerSeparator}${visibleRoute}`;
513
+ const header = `${style(styles.muted, sequenceText)}${sequencePadding}${headerSeparator}${style(styles.target, visibleRoute)}`;
514
+ const headerPadding = Math.max(0, safeWidth - visibleWidth(headerPlain));
515
+ const lines = [`${header}${" ".repeat(headerPadding)}`, ""];
516
+ for (const [key, value] of entries) {
517
+ const keyPadding = " ".repeat(Math.max(0, keyWidth - displayWidth(key)));
518
+ const separator = " ";
519
+ const valueWidth = Math.max(0, safeWidth - keyWidth - separator.length);
520
+ const visibleValue = boundedLine(value, valueWidth);
521
+ const plain = `${key}${keyPadding}${separator}${visibleValue}`;
522
+ const rendered = `${style(styles.muted, key)}${keyPadding}${separator}${style(styles.preview, visibleValue)}`;
523
+ const padding = Math.max(0, safeWidth - visibleWidth(plain));
524
+ lines.push(`${rendered}${" ".repeat(padding)}`);
525
+ }
526
+ return lines;
527
+ }
528
+ export function renderInspectorWidget(previews, width = 80, styles = {}, options = {}) {
529
+ if (previews.length === 0)
530
+ return undefined;
531
+ const safeWidth = Math.max(1, width);
532
+ void options;
533
+ const visible = previews.map((preview, index) => ({
534
+ preview: { ...preview, sequence: preview.sequence ?? index + 1 },
535
+ stripe: preview.stripe ?? index % 2 === 1,
536
+ }));
537
+ const sequenceWidth = Math.max(1, ...visible.map(({ preview }) => String(preview.sequence ?? 0).length));
538
+ const lines = [];
539
+ const separatorWidth = 2;
540
+ const sequencePrefixWidth = sequenceWidth + separatorWidth;
541
+ const fixedSeparatorsWidth = separatorWidth * 3;
542
+ const availableForColumns = Math.max(0, safeWidth - 1 - sequencePrefixWidth - fixedSeparatorsWidth);
543
+ const naturalRouteWidth = Math.max(...visible.map(({ preview }) => displayWidth(routeText(preview))));
544
+ const naturalTypeWidth = Math.max(...visible.map(({ preview }) => displayWidth(preview.type)));
545
+ const routeWidth = Math.min(naturalRouteWidth, Math.max(4, Math.floor(availableForColumns * 0.35)));
546
+ const typeWidth = Math.min(naturalTypeWidth, Math.max(4, Math.floor(availableForColumns * 0.25)));
547
+ const messageWidth = Math.max(0, availableForColumns - routeWidth - typeWidth);
548
+ const summaryWidths = visible
549
+ .map(({ preview }) => preview.summary?.trim())
550
+ .filter((summary) => Boolean(summary))
551
+ .map((summary) => displayWidth(summary));
552
+ const summaryWidth = summaryWidths.length
553
+ ? Math.min(Math.max(...summaryWidths), Math.max(1, Math.floor(messageWidth * 0.5)))
554
+ : 0;
555
+ for (const { preview, stripe } of visible) {
556
+ lines.push(...renderInspectorEntry(preview, safeWidth, sequenceWidth, routeWidth, typeWidth, summaryWidth, styles, stripe));
557
+ }
558
+ return lines;
559
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Actor message protocol helpers.
3
+ * Zones: 0.10 draft communication protocol, addressed messages, mailbox metadata
4
+ * Owns pure validation/normalization for the semantic message envelope; transport routing stays in adapters.
5
+ */
6
+ export type ActorAddressKind = "branch" | "coordinator" | "room" | "run" | "session" | "tool";
7
+ export interface ActorAddress {
8
+ kind: ActorAddressKind;
9
+ value?: string;
10
+ branch?: string;
11
+ room?: string;
12
+ }
13
+ export interface ActorMessage {
14
+ to: string;
15
+ type: string;
16
+ body?: unknown;
17
+ correlation_id?: string;
18
+ from?: string;
19
+ metadata?: Record<string, unknown>;
20
+ reply_to?: string;
21
+ summary?: string;
22
+ }
23
+ export declare function parseActorAddress(address: string): ActorAddress;
24
+ export declare function formatActorAddress(address: ActorAddress): string;
25
+ export declare function normalizeActorMessage(input: unknown): ActorMessage;