@llblab/pi-actors 0.22.5 → 0.23.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 (49) hide show
  1. package/AGENTS.md +1 -0
  2. package/BACKLOG.md +0 -220
  3. package/CHANGELOG.md +18 -1
  4. package/README.md +1 -1
  5. package/dist/index.js +15 -0
  6. package/dist/lib/actor-inspector-tui.d.ts +4 -0
  7. package/dist/lib/actor-inspector-tui.js +53 -12
  8. package/dist/lib/actor-rooms.d.ts +8 -0
  9. package/dist/lib/actor-rooms.js +61 -10
  10. package/dist/lib/async-runs.d.ts +34 -2
  11. package/dist/lib/async-runs.js +215 -22
  12. package/dist/lib/command-templates.js +5 -5
  13. package/dist/lib/limits.d.ts +11 -0
  14. package/dist/lib/limits.js +11 -0
  15. package/dist/lib/observability.js +6 -4
  16. package/dist/lib/output.js +4 -5
  17. package/dist/lib/recipe-discovery.js +1 -0
  18. package/dist/lib/recipe-references.d.ts +11 -2
  19. package/dist/lib/recipe-references.js +4 -2
  20. package/dist/lib/recipe-usage.d.ts +2 -1
  21. package/dist/lib/recipe-usage.js +15 -3
  22. package/dist/lib/runtime.js +12 -2
  23. package/dist/lib/tools.js +197 -38
  24. package/docs/async-runs.md +2 -1
  25. package/docs/template-recipes.md +1 -1
  26. package/docs/tool-registry.md +0 -1
  27. package/index.ts +18 -0
  28. package/lib/actor-inspector-tui.ts +131 -29
  29. package/lib/actor-rooms.ts +155 -44
  30. package/lib/async-runs.ts +290 -31
  31. package/lib/command-templates.ts +5 -5
  32. package/lib/limits.ts +12 -0
  33. package/lib/observability.ts +10 -4
  34. package/lib/output.ts +4 -6
  35. package/lib/recipe-discovery.ts +1 -0
  36. package/lib/recipe-references.ts +20 -4
  37. package/lib/recipe-usage.ts +31 -4
  38. package/lib/runtime.ts +31 -7
  39. package/lib/tools.ts +296 -57
  40. package/package.json +2 -1
  41. package/scripts/async-runner.mjs +6 -0
  42. package/scripts/conformance.mjs +47 -0
  43. package/scripts/coordinator.mjs +13 -0
  44. package/scripts/locker.mjs +13 -0
  45. package/scripts/music-player.mjs +21 -2
  46. package/scripts/recipe-utils.mjs +13 -0
  47. package/scripts/validate-recipe.mjs +13 -0
  48. package/skills/actors/SKILL.md +3 -3
  49. package/skills/swarm/SKILL.md +1 -1
package/AGENTS.md CHANGED
@@ -64,6 +64,7 @@
64
64
  - `npm run check`: Lightweight extension-load sanity check
65
65
  - `npm test`: Focused regression tests for extracted pure domains
66
66
  - `npm run pack:dry`: Verify package contents and npm metadata
67
+ - `npm run conformance`: Compact protocol conformance runner for actor/recipe behavior
67
68
  - `bash ~/.pi/agent/skills/abcd-context/scripts/validate-context.sh`: Validate context split, links, and README/docs reachability
68
69
 
69
70
  ## Pre-Task Preparation
package/BACKLOG.md CHANGED
@@ -84,20 +84,6 @@ No open hotfix items.
84
84
  - `inspect` can show id, correlation, and causation.
85
85
  - No migration is forced.
86
86
 
87
- ### M-03 Mailbox Contract v1
88
-
89
- - Priority: Medium.
90
- - Goal: Extend `mailbox.accepts` and `mailbox.emits` from string arrays to backward-compatible typed contracts.
91
- - Direction:
92
- - Keep string declarations valid.
93
- - Normalize typed entries for inspection.
94
- - Support fields such as body schema, ack requirement, idempotency, response requirement, level, and summary.
95
- - Acceptance:
96
- - String declarations still work.
97
- - `inspect view=mailbox` shows normalized contracts.
98
- - Messages outside accepts produce advisory warnings by default, not hard blocks.
99
- - Docs clarify advisory versus strict mode.
100
-
101
87
  ### M-04 Actor Loop Helper SDK
102
88
 
103
89
  - Priority: Medium.
@@ -119,143 +105,6 @@ No open hotfix items.
119
105
  - Concurrent wake and poll paths do not double-process messages.
120
106
  - Helper supports run inbox and branch inbox.
121
107
 
122
- ### M-05 Branch Delivery Unification
123
-
124
- - Priority: Medium.
125
- - Goal: Route direct branch messages and room multicast branch copies through one internal helper.
126
- - Target helper:
127
- - `routeBranchEnvelope(stateDir, runId, message, { source })`.
128
- - Acceptance:
129
- - Direct branch messages and room multicast produce the same durable branch inbox shape.
130
- - Parent run dispatch envelope is consistent.
131
- - Tests cover both paths.
132
-
133
- ### M-06 Attention Semantics v1
134
-
135
- - Priority: Medium.
136
- - Goal: Formalize coordinator attention behavior through semantic metadata rather than transport knobs.
137
- - Direction:
138
- - Support attention metadata such as `requires_response=true` and reason.
139
- - Map response-required messages to follow-up.
140
- - Keep progress/info messages inspectable or notify-only.
141
- - Preserve existing internal `delivery` behavior.
142
- - Acceptance:
143
- - `requires_response=true` becomes coordinator follow-up.
144
- - Progress info stays log or notification.
145
- - Explicit stop and kill controls do not create duplicate terminal follow-up.
146
-
147
- ### M-07 Recipe Doctor
148
-
149
- - Priority: Medium.
150
- - Goal: Add intentional recipe health inspection.
151
- - Surface:
152
- - `inspect target=recipes view=doctor`.
153
- - `inspect target=recipes view=doctor verbose=true`.
154
- - Checks:
155
- - Import graph health.
156
- - Shadowing and blockers.
157
- - Invalid or disabled recipes.
158
- - Risky command templates.
159
- - Absolute path portability.
160
- - OS compatibility.
161
- - Root permissions.
162
- - Mailbox completeness.
163
- - Artifact placeholder resolution.
164
- - Stale or unused usage.
165
- - Acceptance:
166
- - Compact output is grouped by severity.
167
- - Verbose output is structured.
168
- - No automatic cleanup happens.
169
- - Every diagnostic has reason and suggested actions.
170
-
171
- ### M-08 Artifact Manifest v1
172
-
173
- - Priority: Medium.
174
- - Goal: Extend recipe artifacts from string paths to backward-compatible artifact metadata.
175
- - Direction:
176
- - Keep string artifact paths valid.
177
- - Add optional object fields such as path, kind, media type, and required.
178
- - Resolve manifests with existence, size, and optional hash.
179
- - Acceptance:
180
- - String artifacts remain valid.
181
- - Runtime resolves manifest with `exists`, `size`, and optional `sha256`.
182
- - Terminal follow-ups group named artifacts.
183
- - Missing required artifacts are visible in result and inspect output.
184
-
185
- ### M-09 Lifecycle Retention Policy
186
-
187
- - Priority: Medium.
188
- - Goal: Add explicit archive and prune behavior for terminal run state.
189
- - Messages:
190
- - `control.archive`.
191
- - `control.prune`.
192
- - Direction:
193
- - Only terminal runs can be archived or pruned.
194
- - Active runs fail closed.
195
- - Archive moves or compresses state with a tombstone.
196
- - Prune deletes terminal state after ownership checks, optionally preserving artifacts.
197
- - Acceptance:
198
- - Terminal cleanup candidates are inspectable.
199
- - Artifacts can be preserved.
200
- - Active run deletion is impossible.
201
-
202
- ### M-10 Inspector v2 For Unread Mentions And Needs Response
203
-
204
- - Priority: Medium.
205
- - Goal: Strengthen the TUI inspector as the operator membrane for multi-actor sessions.
206
- - Direction:
207
- - Add stable event ids in previews.
208
- - Add unread cursor per session, run, and room.
209
- - Preserve mention filtering.
210
- - Add needs-response marker from attention semantics.
211
- - Distinguish room timeline rows from branch inbox rows.
212
- - Acceptance:
213
- - `/actors-inspector-filter unread` works.
214
- - `/actors-inspector-filter mention <text>` works.
215
- - `/actors-inspect <number>` marks the item read for the current session.
216
- - Unread state stays UI/session metadata, not protocol truth.
217
-
218
- ### M-11 Run State Index
219
-
220
- - Priority: Medium.
221
- - Goal: Add a rebuildable run-state index to reduce recursive scans and improve observability performance.
222
- - Target file:
223
- - `~/.pi/agent/tmp/pi-actors/runs/index.json`.
224
- - Contents:
225
- - State dir.
226
- - Run id.
227
- - Owner id.
228
- - Status.
229
- - Updated time.
230
- - Recipe or tool.
231
- - Acceptance:
232
- - Index accelerates list and summarize.
233
- - Corruption falls back to scan.
234
- - Rebuild helper exists.
235
- - Nested runs are represented without id collision.
236
-
237
- ### M-12 Internal Conformance Runner
238
-
239
- - Priority: Medium.
240
- - Goal: Add a CI-ready internal conformance runner for pi-actors protocol behavior.
241
- - Script:
242
- - `npm run conformance`.
243
- - Suites:
244
- - Recipe discovery.
245
- - Register, update, and delete.
246
- - Spawn lifecycle.
247
- - Message routing.
248
- - Room roster.
249
- - Branch inbox.
250
- - Ownership checks.
251
- - Artifacts.
252
- - Attention semantics.
253
- - Acceptance:
254
- - Runs without Pi UI where possible.
255
- - Outputs compact report.
256
- - Fixtures live in the repository.
257
- - CI can run it.
258
-
259
108
  ### M-13 Packaged Actor Worker Recipe Template
260
109
 
261
110
  - Priority: Medium.
@@ -270,50 +119,6 @@ No open hotfix items.
270
119
  - Recipe demonstrates correct mailbox loop semantics.
271
120
  - It is a recipe-authoring example, not a product feature.
272
121
 
273
- ### M-14 Recipe Usage Integrity Improvements
274
-
275
- - Priority: Medium.
276
- - Goal: Make recipe usage tracking more explainable.
277
- - Direction:
278
- - Add fingerprint diff reason.
279
- - Add reset reason.
280
- - Consider optional last error.
281
- - Split launch counts by tool, spawn, and direct recipe.
282
- - Acceptance:
283
- - `inspect recipes view=summary verbose=true` shows whether usage refers to current recipe content.
284
- - Doctor can flag unused current meaning instead of stale old recipe history.
285
-
286
- ### M-15 Safer Command Warning Policy
287
-
288
- - Priority: Medium.
289
- - Goal: Unify command-template diagnostics by severity and make warnings actionable without startup spam.
290
- - Severity:
291
- - `info`: portability.
292
- - `warning`: broad mutation, shell, or eval.
293
- - `error`: impossible, invalid, or unsafe repeat.
294
- - Direction:
295
- - Treat legitimate `bash` wrappers as expected trusted boundaries when already packaged or explicitly registered.
296
- - Keep routine shell wrapper notes out of startup warning blocks.
297
- - Surface shell/eval/destructive diagnostics through register-time warnings, doctor, and verbose recipe inspection.
298
- - Acceptance:
299
- - Command-template warnings include command label, reason, and suggested mitigation.
300
- - `register_tool` shows relevant warnings.
301
- - Recipe doctor aggregates warning policy.
302
- - Startup no longer emits large warning blocks only because recipes wrap `bash`.
303
-
304
- ### M-16 Output And Log Size Governance
305
-
306
- - Priority: Medium.
307
- - Goal: Keep stdout, stderr, result, outbox, and actor-message previews bounded in agent context.
308
- - Direction:
309
- - Centralize constants for body preview, outbox preview, inspector preview, and tail lines.
310
- - Keep verbose output opt-in.
311
- - Prefer files or artifacts for large bodies.
312
- - Acceptance:
313
- - Actor messages and previews share consistent caps.
314
- - Verbose mode is explicit.
315
- - Large bodies do not flood normal tool output.
316
-
317
122
  ### M-17 Windows And Nix Portability Pass
318
123
 
319
124
  - Priority: Medium.
@@ -341,18 +146,6 @@ No open hotfix items.
341
146
  - Corrupt paths are reported clearly.
342
147
  - Canonical state is not silently rewritten without explicit action.
343
148
 
344
- ### M-19 Recipe Import Graph Inspector
345
-
346
- - Priority: Medium.
347
- - Goal: Add focused inspection for recipe import graphs.
348
- - Surface:
349
- - `inspect target=recipes view=imports`.
350
- - `inspect target=recipes view=imports verbose=true`.
351
- - Acceptance:
352
- - Shows import graph, aliases, resolved paths, and shadowed imports.
353
- - Shows cyclic and depth diagnostics.
354
- - Helps debug packaged and user recipe composition.
355
-
356
149
  ### M-20 Spawn Preflight Mode
357
150
 
358
151
  - Priority: Medium.
@@ -393,19 +186,6 @@ No open hotfix items.
393
186
  - Examples cover branch, room, run, session, and tool addresses.
394
187
  - No new public address kinds are added.
395
188
 
396
- ### M-23 Room Compaction Metadata Improvements
397
-
398
- - Priority: Medium.
399
- - Goal: Record richer room compaction metadata.
400
- - Direction:
401
- - Track dropped count.
402
- - Track first and last kept timestamps.
403
- - Track configured max.
404
- - Acceptance:
405
- - `inspect room:<run> view=status verbose=true` shows compaction info.
406
- - Compaction never corrupts JSONL.
407
- - Tests cover low `PI_ACTORS_ROOM_MAX_MESSAGES`.
408
-
409
189
  ### M-24 Coordinator Follow-Up Deduplication
410
190
 
411
191
  - Priority: Medium.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,23 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.23.0: Actor Manifests, Inspection, and Runtime Hygiene
4
+
5
+ - `[Tools]` Unified branch-envelope routing for direct branch messages and selected-recipient room multicast so both paths persist the same branch-local inbox shape before dispatching through the parent run mailbox.
6
+ - `[Async Runs]` Added attention semantics for coordinator-bound actor messages: `metadata.requires_response=true` now produces a follow-up while ordinary coordinator progress messages default to notification-level delivery.
7
+ - `[Registry]` Added `inspect target=recipes view=doctor` as an intentional recipe health surface with compact severity/action counts and structured verbose diagnostics.
8
+ - `[Async Runs]` Added artifact manifest resolution for string and object artifact declarations, including `exists`, `size`, `sha256`, and missing required artifact visibility in artifact inspection.
9
+ - `[Async Runs]` Added explicit terminal run retention controls via `control.archive` and `control.prune`, with active-run fail-closed behavior and optional artifact preservation during prune.
10
+ - `[Inspector]` Added stable event ids, needs-response markers, and session-local read markers to actor inspector previews and selected-item details.
11
+ - `[Registry]` Suppressed routine trusted `bash` wrapper diagnostics from startup warning notifications while keeping them available through recipe diagnostics surfaces.
12
+ - `[Async Runs]` Added a rebuildable run-state index for run listing and observability discovery, with corrupt-index fallback to recursive scan and nested-run-safe state directory entries.
13
+ - `[Testing]` Added `npm run conformance` for a compact CI-ready protocol conformance runner covering recipes, registry, spawn lifecycle, messaging, rooms, branch inboxes, ownership, artifacts, and attention semantics.
14
+ - `[Mailbox]` Added backward-compatible typed mailbox contracts with normalized inspection and advisory warnings for undeclared run message types.
15
+ - `[Output]` Centralized inspect, preview, and tool-output size limits so bounded output governance is shared across tools, room previews, and the actor inspector.
16
+ - `[Registry]` Added explicit mitigation guidance to command-template trust-boundary warnings for shells, eval modes, and broad filesystem mutation.
17
+ - `[Scripts]` Added top-of-file descriptions to packaged helper scripts so their purpose, boundaries, and policy ownership are clear when opened directly.
18
+ - `[Rooms]` Added room compaction metadata with dropped count, configured maximum, and first/last kept timestamps exposed through room status inspection.
19
+ - `[Recipes]` Improved usage telemetry with launch-kind counters (`tool`, `spawn`, `direct`) and explicit reset reasons when recipe content fingerprints change.
20
+ - `[Inspect]` Added `inspect target=recipes view=imports` to summarize recipe import aliases and source references for debugging recipe composition.
4
21
 
5
22
  ## 0.22.5: CI Stability Hotfix
6
23
 
package/README.md CHANGED
@@ -161,7 +161,7 @@ The terminal actor inspector is hidden by default. When opened without an explic
161
161
  /actors-inspect 3
162
162
  ```
163
163
 
164
- The table is compact and optimistic by default: bounded route/type/summary/body previews, capped noisy room rows, branch-local inbox previews, and an inline roster summary in the form `name/role` that wraps only when needed. Active roster members use the target color; members that sent `actor.leave` remain visible as inactive/muted participants from the current run. Use `unread` to focus queued branch inbox work and `branch <name>` / `current-branch <name>` to focus one branch's room/direct/inbox traffic. `/actors-inspect <number>` opens the selected row as a full-message view; toggle again to return to the table or close it. Actor display names come from room `actor.join` roster metadata or branch addresses, keeping debugger output plain and name-driven.
164
+ The table is compact and optimistic by default: bounded route/type/summary/body previews, capped noisy room rows, branch-local inbox previews, stable event ids in selected-message details, and an inline roster summary in the form `name/role` that wraps only when needed. Active roster members use the target color; members that sent `actor.leave` remain visible as inactive/muted participants from the current run. Use `unread` to focus queued branch inbox work and `branch <name>` / `current-branch <name>` to focus one branch's room/direct/inbox traffic. Rows with `metadata.requires_response=true` show a `!` attention marker. `/actors-inspect <number>` opens the selected row as a full-message view and marks it read for the current session filter; toggle again to return to the table or close it. Actor display names come from room `actor.join` roster metadata or branch addresses, keeping debugger output plain and name-driven.
165
165
 
166
166
  ## Registry Model
167
167
 
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ export default function toolRegistryExtension(pi) {
49
49
  let actorInspectorUnreadOnly = false;
50
50
  let actorInspectorRoomLimitPerRun = 12;
51
51
  let selectedInspectorSequence;
52
+ const actorInspectorReadKeys = new Set();
52
53
  let recipeWatcherFailureNotified = false;
53
54
  const getRunOwnerId = (ctx) => ctx.sessionManager.getSessionId();
54
55
  const retireCandidateRuns = (ctx, summary) => {
@@ -84,6 +85,7 @@ export default function toolRegistryExtension(pi) {
84
85
  branch: actorInspectorBranch,
85
86
  mention: actorInspectorMention,
86
87
  ownerId,
88
+ readKeys: actorInspectorReadKeys,
87
89
  roomLimitPerRun: actorInspectorRoomLimitPerRun,
88
90
  unreadOnly: actorInspectorUnreadOnly,
89
91
  });
@@ -356,6 +358,19 @@ export default function toolRegistryExtension(pi) {
356
358
  ctx.ui.notify("Usage: /actors-inspect <number>", "warning");
357
359
  return;
358
360
  }
361
+ const previews = ActorInspectorTui.readActorInspectorPreviews(RUN_STATE_ROOT, actorInspectorRows, {
362
+ channels: actorInspectorChannels,
363
+ currentRunOnly: true,
364
+ branch: actorInspectorBranch,
365
+ mention: actorInspectorMention,
366
+ ownerId: getRunOwnerId(ctx),
367
+ readKeys: actorInspectorReadKeys,
368
+ roomLimitPerRun: actorInspectorRoomLimitPerRun,
369
+ unreadOnly: actorInspectorUnreadOnly,
370
+ });
371
+ const preview = previews.find((item) => item.sequence === sequence);
372
+ if (preview)
373
+ actorInspectorReadKeys.add(ActorInspectorTui.inspectorPreviewReadKey(preview));
359
374
  selectedInspectorSequence = sequence;
360
375
  communicationWidgetVisible = true;
361
376
  updateRunUi(ctx);
@@ -6,10 +6,12 @@ export interface ActorInspectorPreview {
6
6
  body_preview?: string;
7
7
  branch?: string;
8
8
  channel: "broadcast" | "direct" | "room";
9
+ event_id?: string;
9
10
  from?: string;
10
11
  from_display?: string;
11
12
  inbox_status?: string;
12
13
  message_id?: string;
14
+ needs_response?: boolean;
13
15
  run: string;
14
16
  sequence?: number;
15
17
  summary?: string;
@@ -44,9 +46,11 @@ export interface ActorInspectorPreviewReadOptions {
44
46
  currentRunOnly?: boolean;
45
47
  channels?: ActorInspectorPreview["channel"][];
46
48
  mention?: string;
49
+ readKeys?: Iterable<string>;
47
50
  roomLimitPerRun?: number;
48
51
  unreadOnly?: boolean;
49
52
  }
53
+ export declare function inspectorPreviewReadKey(preview: ActorInspectorPreview): string;
50
54
  export declare function readActorInspectorPreviews(stateRoot?: string, limit?: number, options?: ActorInspectorPreviewReadOptions): ActorInspectorPreview[];
51
55
  export declare function readActorInspectorRoster(stateRoot: string | undefined, run: string, room?: string): ActorInspectorRosterMember[];
52
56
  export declare function renderInspectorRosterLine(members: ActorInspectorRosterMember[], width?: number, styles?: ActorInspectorWidgetStyle): string | undefined;
@@ -5,6 +5,7 @@
5
5
  import * as fs from "node:fs";
6
6
  import * as path from "node:path";
7
7
  import { visibleWidth } from "@earendil-works/pi-tui";
8
+ import * as Limits from "./limits.js";
8
9
  import * as Paths from "./paths.js";
9
10
  function asRecord(value) {
10
11
  return value && typeof value === "object" && !Array.isArray(value)
@@ -32,7 +33,7 @@ function readJsonLines(file) {
32
33
  return [];
33
34
  }
34
35
  }
35
- function previewValue(value, maxLength = 320) {
36
+ function previewValue(value, maxLength = Limits.INSPECTOR_BODY_PREVIEW_CHARS) {
36
37
  if (value === undefined)
37
38
  return undefined;
38
39
  const text = typeof value === "string" ? value : JSON.stringify(value);
@@ -58,6 +59,10 @@ function previewFromMessage(run, message, timestamp, displayNames = {}) {
58
59
  const from = typeof message.from === "string" ? message.from : undefined;
59
60
  const summary = typeof message.summary === "string" ? message.summary : undefined;
60
61
  const body = asRecord(message.body);
62
+ const metadata = asRecord(message.metadata);
63
+ const eventId = typeof message.id === "string" && message.id.trim()
64
+ ? message.id.trim()
65
+ : undefined;
61
66
  const display = from
62
67
  ? typeof body.display === "string" && body.display.trim()
63
68
  ? body.display.trim()
@@ -68,8 +73,10 @@ function previewFromMessage(run, message, timestamp, displayNames = {}) {
68
73
  ? { body_preview: previewValue(message.body) }
69
74
  : {}),
70
75
  channel: channelFor({ to }),
76
+ ...(eventId ? { event_id: eventId } : {}),
71
77
  ...(from ? { from } : {}),
72
78
  ...(display ? { from_display: display } : {}),
79
+ ...(metadata.requires_response === true ? { needs_response: true } : {}),
73
80
  run,
74
81
  ...(summary ? { summary } : {}),
75
82
  timestamp,
@@ -129,14 +136,21 @@ function readBranchInboxPreviews(run, stateDir) {
129
136
  .filter((entry) => entry.isDirectory())
130
137
  .flatMap((entry) => readJsonLines(path.join(branchesDir, entry.name, "inbox.jsonl"))
131
138
  .map((message) => {
132
- const preview = previewFromMessage(run, message, String(message.queued_at ?? message.received_at ?? message.timestamp ?? ""));
139
+ const preview = previewFromMessage(run, message, String(message.queued_at ??
140
+ message.received_at ??
141
+ message.timestamp ??
142
+ ""));
133
143
  if (!preview)
134
144
  return undefined;
135
145
  return {
136
146
  ...preview,
137
147
  branch: entry.name,
138
- ...(typeof message.id === "string" ? { message_id: message.id } : {}),
139
- ...(typeof message.status === "string" ? { inbox_status: message.status } : {}),
148
+ ...(typeof message.id === "string"
149
+ ? { message_id: message.id }
150
+ : {}),
151
+ ...(typeof message.status === "string"
152
+ ? { inbox_status: message.status }
153
+ : {}),
140
154
  };
141
155
  })
142
156
  .filter((preview) => Boolean(preview)));
@@ -167,8 +181,22 @@ function getRunOwnerId(stateDir) {
167
181
  function matchesOwner(stateDir, ownerId) {
168
182
  return ownerId === undefined || getRunOwnerId(stateDir) === ownerId;
169
183
  }
170
- function isUnreadPreview(preview) {
171
- return preview.inbox_status === "queued" || preview.inbox_status === undefined && preview.branch !== undefined;
184
+ export function inspectorPreviewReadKey(preview) {
185
+ return [
186
+ preview.channel,
187
+ preview.run,
188
+ preview.branch ?? "",
189
+ preview.event_id ?? preview.message_id ?? preview.timestamp,
190
+ preview.from ?? "",
191
+ preview.to,
192
+ preview.type,
193
+ ].join("|");
194
+ }
195
+ function isUnreadPreview(preview, readKeys) {
196
+ if (readKeys?.has(inspectorPreviewReadKey(preview)))
197
+ return false;
198
+ return (preview.inbox_status === "queued" ||
199
+ (preview.inbox_status === undefined && preview.branch !== undefined));
172
200
  }
173
201
  function matchesBranchFilter(preview, branch) {
174
202
  const name = branch?.trim();
@@ -183,7 +211,8 @@ function matchesPreviewFilter(preview, options) {
183
211
  if (options.channels?.length && !options.channels.includes(preview.channel)) {
184
212
  return false;
185
213
  }
186
- if (options.unreadOnly && !isUnreadPreview(preview))
214
+ const readKeys = options.readKeys ? new Set(options.readKeys) : undefined;
215
+ if (options.unreadOnly && !isUnreadPreview(preview, readKeys))
187
216
  return false;
188
217
  if (!matchesBranchFilter(preview, options.branch))
189
218
  return false;
@@ -298,6 +327,9 @@ function routeText(preview) {
298
327
  return `${actor} ⇢ ${preview.to}`;
299
328
  return `${actor} → ${actorName(preview.to)}`;
300
329
  }
330
+ function attentionMarker(preview) {
331
+ return preview.needs_response ? "! " : "";
332
+ }
301
333
  function style(styleFn, text) {
302
334
  return styleFn ? styleFn(text) : text;
303
335
  }
@@ -351,7 +383,7 @@ function renderCompactInspectorEntry(preview, width, sequenceWidth, routeWidth,
351
383
  const contentWidth = Math.max(8, width - prefix.length - suffix.length);
352
384
  const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
353
385
  const sequencePrefix = `${sequence}${separator}`;
354
- const route = routeText(preview);
386
+ const route = `${attentionMarker(preview)}${routeText(preview)}`;
355
387
  const routePadding = " ".repeat(Math.max(0, routeWidth - displayWidth(route)));
356
388
  const typePadding = " ".repeat(Math.max(0, typeWidth - displayWidth(preview.type)));
357
389
  const headline = previewText(preview);
@@ -382,7 +414,7 @@ function renderInspectorEntry(preview, width, sequenceWidth, routeWidth, typeWid
382
414
  const contentWidth = Math.max(8, width - prefix.length - suffix.length);
383
415
  const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
384
416
  const sequencePrefix = `${sequence}${separator}`;
385
- const route = routeText(preview);
417
+ const route = `${attentionMarker(preview)}${routeText(preview)}`;
386
418
  const type = preview.type;
387
419
  const summary = preview.summary?.trim() ?? "";
388
420
  const body = preview.body_preview?.trim() || (!summary ? previewText(preview) : "-");
@@ -430,7 +462,10 @@ export function readActorInspectorRoster(stateRoot = Paths.getRunStateRoot(), ru
430
462
  }
431
463
  function rosterRoleText(role) {
432
464
  const roleLabel = role?.split(";")[0] ?? "";
433
- const cleaned = roleLabel.replaceAll(/\s*\([^)]*\)\s*$/g, "").trim().toLowerCase();
465
+ const cleaned = roleLabel
466
+ .replaceAll(/\s*\([^)]*\)\s*$/g, "")
467
+ .trim()
468
+ .toLowerCase();
434
469
  if (!cleaned || cleaned === "actor")
435
470
  return undefined;
436
471
  return cleaned.replaceAll(/\s+/g, "-");
@@ -444,7 +479,10 @@ function rosterMemberText(member) {
444
479
  }
445
480
  function isRosterMemberActive(member) {
446
481
  const status = member.status?.trim().toLowerCase();
447
- return !status || status === "present" || status === "active" || status === "running";
482
+ return (!status ||
483
+ status === "present" ||
484
+ status === "active" ||
485
+ status === "running");
448
486
  }
449
487
  export function renderInspectorRosterLine(members, width = 80, styles = {}) {
450
488
  return renderInspectorRosterPanel(members, width, styles)?.[0];
@@ -491,12 +529,15 @@ export function renderInspectorItemView(previews, width = 80, styles = {}, optio
491
529
  const orderedKeys = [
492
530
  "channel",
493
531
  "run",
532
+ "event_id",
494
533
  "from",
495
534
  "from_display",
496
535
  "to",
497
536
  "type",
498
537
  "summary",
499
538
  "body_preview",
539
+ "message_id",
540
+ "needs_response",
500
541
  "timestamp",
501
542
  "stripe",
502
543
  ];
@@ -540,7 +581,7 @@ export function renderInspectorWidget(previews, width = 80, styles = {}, options
540
581
  const sequencePrefixWidth = sequenceWidth + separatorWidth;
541
582
  const fixedSeparatorsWidth = separatorWidth * 3;
542
583
  const availableForColumns = Math.max(0, safeWidth - 1 - sequencePrefixWidth - fixedSeparatorsWidth);
543
- const naturalRouteWidth = Math.max(...visible.map(({ preview }) => displayWidth(routeText(preview))));
584
+ const naturalRouteWidth = Math.max(...visible.map(({ preview }) => displayWidth(`${attentionMarker(preview)}${routeText(preview)}`)));
544
585
  const naturalTypeWidth = Math.max(...visible.map(({ preview }) => displayWidth(preview.type)));
545
586
  const routeWidth = Math.min(naturalRouteWidth, Math.max(4, Math.floor(availableForColumns * 0.35)));
546
587
  const typeWidth = Math.min(naturalTypeWidth, Math.max(4, Math.floor(availableForColumns * 0.25)));
@@ -32,10 +32,18 @@ export interface RoomMessagePreview {
32
32
  to: string;
33
33
  type: string;
34
34
  }
35
+ export interface RoomCompactionInfo {
36
+ compacted_at: string;
37
+ dropped_count: number;
38
+ first_kept_at?: string;
39
+ last_kept_at?: string;
40
+ max_messages: number;
41
+ }
35
42
  export interface RoomStatus {
36
43
  room: string;
37
44
  message_count: number;
38
45
  roster_count: number;
46
+ compaction?: RoomCompactionInfo;
39
47
  last_message_at?: string;
40
48
  last_message_from?: string;
41
49
  last_message_summary?: string;