@llblab/pi-actors 0.22.5 → 0.24.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 (180) hide show
  1. package/AGENTS.md +125 -59
  2. package/BACKLOG.md +88 -403
  3. package/CHANGELOG.md +39 -0
  4. package/README.md +14 -1
  5. package/dist/fixtures/protocol/actor-message-branch.json +13 -0
  6. package/dist/fixtures/protocol/artifact-manifest.json +9 -0
  7. package/dist/fixtures/protocol/mailbox-contract.json +15 -0
  8. package/dist/fixtures/protocol/recipe-summary.json +16 -0
  9. package/dist/fixtures/protocol/room-message.json +11 -0
  10. package/dist/fixtures/protocol/room-roster.json +11 -0
  11. package/dist/fixtures/protocol/run-inbox-message.json +9 -0
  12. package/dist/fixtures/protocol/run-outbox-event.json +9 -0
  13. package/dist/fixtures/protocol/run-state.json +10 -0
  14. package/dist/index.js +18 -2
  15. package/dist/lib/actor-inspector-tui.d.ts +4 -0
  16. package/dist/lib/actor-inspector-tui.js +58 -44
  17. package/dist/lib/actor-rooms.d.ts +16 -0
  18. package/dist/lib/actor-rooms.js +123 -28
  19. package/dist/lib/actor-worker.d.ts +13 -0
  20. package/dist/lib/actor-worker.js +86 -0
  21. package/dist/lib/async-runner.d.ts +5 -0
  22. package/dist/lib/async-runner.js +134 -0
  23. package/dist/lib/async-runs.d.ts +34 -2
  24. package/dist/lib/async-runs.js +224 -50
  25. package/dist/lib/build-dist.d.ts +5 -0
  26. package/dist/lib/build-dist.js +24 -0
  27. package/dist/lib/command-templates.js +5 -5
  28. package/dist/lib/conformance.d.ts +12 -0
  29. package/dist/lib/conformance.js +28 -0
  30. package/dist/lib/coordinator.d.ts +5 -0
  31. package/dist/lib/coordinator.js +557 -0
  32. package/dist/lib/limits.d.ts +11 -0
  33. package/dist/lib/limits.js +11 -0
  34. package/dist/lib/locker.d.ts +5 -0
  35. package/dist/lib/locker.js +310 -0
  36. package/dist/lib/mailbox-loop.d.ts +41 -0
  37. package/dist/lib/mailbox-loop.js +62 -0
  38. package/dist/lib/observability.d.ts +2 -2
  39. package/dist/lib/observability.js +57 -57
  40. package/dist/lib/output.js +4 -5
  41. package/dist/lib/prompts.d.ts +1 -1
  42. package/dist/lib/prompts.js +1 -1
  43. package/dist/lib/recipe-discovery.js +1 -0
  44. package/dist/lib/recipe-references.d.ts +11 -2
  45. package/dist/lib/recipe-references.js +29 -4
  46. package/dist/lib/recipe-usage.d.ts +2 -1
  47. package/dist/lib/recipe-usage.js +15 -3
  48. package/dist/lib/recipe-utils.d.ts +5 -0
  49. package/dist/lib/recipe-utils.js +385 -0
  50. package/dist/lib/runtime-notifier.js +3 -7
  51. package/dist/lib/runtime.js +12 -2
  52. package/dist/lib/state-readers.d.ts +21 -0
  53. package/dist/lib/state-readers.js +74 -0
  54. package/dist/lib/tools.js +198 -39
  55. package/dist/lib/validate-recipe.d.ts +6 -0
  56. package/dist/lib/validate-recipe.js +104 -0
  57. package/dist/recipes/actor-worker.json +35 -0
  58. package/dist/recipes/coordinator-locker.json +45 -0
  59. package/dist/recipes/lens-swarm.json +66 -0
  60. package/dist/recipes/locker.json +45 -0
  61. package/dist/recipes/music-player.json +38 -0
  62. package/dist/recipes/pipeline-architect-coordinator.json +95 -0
  63. package/dist/recipes/pipeline-artifact-bundle.json +100 -0
  64. package/dist/recipes/pipeline-artifact-report.json +58 -0
  65. package/dist/recipes/pipeline-artifact-write.json +72 -0
  66. package/dist/recipes/pipeline-async-run-ops.json +70 -0
  67. package/dist/recipes/pipeline-checkpoint-continuation.json +67 -0
  68. package/dist/recipes/pipeline-development-tasking.json +81 -0
  69. package/dist/recipes/pipeline-docs-maintenance.json +80 -0
  70. package/dist/recipes/pipeline-media-library.json +59 -0
  71. package/dist/recipes/pipeline-quorum-review.json +79 -0
  72. package/dist/recipes/pipeline-release-readiness.json +110 -0
  73. package/dist/recipes/pipeline-release-summary.json +88 -0
  74. package/dist/recipes/pipeline-repo-health.json +89 -0
  75. package/dist/recipes/pipeline-research-synthesis.json +94 -0
  76. package/dist/recipes/pipeline-review-readiness.json +54 -0
  77. package/dist/recipes/pipeline-room-swarm.json +50 -0
  78. package/dist/recipes/subagent-artifact.json +32 -0
  79. package/dist/recipes/subagent-checkpoint.json +33 -0
  80. package/dist/recipes/subagent-conflict-report.json +32 -0
  81. package/dist/recipes/subagent-contradiction-map.json +33 -0
  82. package/dist/recipes/subagent-critic.json +35 -0
  83. package/dist/recipes/subagent-evidence-map.json +33 -0
  84. package/dist/recipes/subagent-followup.json +33 -0
  85. package/dist/recipes/subagent-judge.json +33 -0
  86. package/dist/recipes/subagent-merge.json +33 -0
  87. package/dist/recipes/subagent-message.json +34 -0
  88. package/dist/recipes/subagent-normalize.json +31 -0
  89. package/dist/recipes/subagent-plan.json +33 -0
  90. package/dist/recipes/subagent-prompt.json +28 -0
  91. package/dist/recipes/subagent-quorum.json +43 -0
  92. package/dist/recipes/subagent-review-coordinator.json +114 -0
  93. package/dist/recipes/subagent-review.json +37 -0
  94. package/dist/recipes/subagent-task-card.json +35 -0
  95. package/dist/recipes/subagent-tools.json +27 -0
  96. package/dist/recipes/subagent-verify.json +34 -0
  97. package/dist/recipes/subagents-prompts.json +51 -0
  98. package/dist/recipes/utility-actor-message.json +23 -0
  99. package/dist/recipes/utility-artifact-manifest.json +16 -0
  100. package/dist/recipes/utility-artifact-write.json +16 -0
  101. package/dist/recipes/utility-changelog-head.json +11 -0
  102. package/dist/recipes/utility-changelog-section.json +13 -0
  103. package/dist/recipes/utility-coordinator-lock-snapshot.json +13 -0
  104. package/dist/recipes/utility-git-log.json +11 -0
  105. package/dist/recipes/utility-git-status.json +9 -0
  106. package/dist/recipes/utility-jsonl-tail.json +10 -0
  107. package/dist/recipes/utility-markdown-index.json +14 -0
  108. package/dist/recipes/utility-package-summary.json +11 -0
  109. package/dist/recipes/utility-playlist-build.json +17 -0
  110. package/dist/recipes/utility-playlist-scan.json +11 -0
  111. package/dist/recipes/utility-run-ops-snapshot.json +17 -0
  112. package/dist/recipes/utility-run-state-files.json +13 -0
  113. package/dist/recipes/utility-run-summary.json +11 -0
  114. package/dist/recipes/utility-skill-summary.json +13 -0
  115. package/dist/recipes/utility-validate-recipe.json +13 -0
  116. package/dist/recipes/utility-validation-wrapper.json +13 -0
  117. package/dist/scripts/actor-worker.mjs +31 -0
  118. package/dist/scripts/async-runner.mjs +31 -0
  119. package/dist/scripts/build-dist.mjs +25 -0
  120. package/dist/scripts/conformance.mjs +33 -0
  121. package/dist/scripts/coordinator.mjs +31 -0
  122. package/dist/scripts/locker.mjs +33 -0
  123. package/dist/scripts/music-player.mjs +964 -0
  124. package/dist/scripts/recipe-utils.mjs +31 -0
  125. package/dist/scripts/validate-recipe.mjs +34 -0
  126. package/dist/skills/actors/SKILL.md +375 -0
  127. package/dist/skills/swarm/SKILL.md +467 -0
  128. package/dist/skills/swarm/references/development-swarm.md +596 -0
  129. package/docs/actor-messages.md +2 -2
  130. package/docs/async-runs.md +13 -1
  131. package/docs/template-recipes.md +2 -2
  132. package/docs/tool-registry.md +0 -1
  133. package/fixtures/protocol/actor-message-branch.json +13 -0
  134. package/fixtures/protocol/artifact-manifest.json +9 -0
  135. package/fixtures/protocol/mailbox-contract.json +15 -0
  136. package/fixtures/protocol/recipe-summary.json +16 -0
  137. package/fixtures/protocol/room-message.json +11 -0
  138. package/fixtures/protocol/room-roster.json +11 -0
  139. package/fixtures/protocol/run-inbox-message.json +9 -0
  140. package/fixtures/protocol/run-outbox-event.json +9 -0
  141. package/fixtures/protocol/run-state.json +10 -0
  142. package/index.ts +21 -0
  143. package/lib/actor-inspector-tui.ts +138 -59
  144. package/lib/actor-rooms.ts +241 -60
  145. package/lib/actor-worker.ts +118 -0
  146. package/lib/async-runner.ts +173 -0
  147. package/lib/async-runs.ts +302 -53
  148. package/lib/build-dist.ts +30 -0
  149. package/lib/command-templates.ts +5 -5
  150. package/lib/conformance.ts +46 -0
  151. package/lib/coordinator.ts +649 -0
  152. package/lib/limits.ts +12 -0
  153. package/lib/locker.ts +340 -0
  154. package/lib/mailbox-loop.ts +148 -0
  155. package/lib/observability.ts +34 -23
  156. package/lib/output.ts +4 -6
  157. package/lib/prompts.ts +1 -1
  158. package/lib/recipe-discovery.ts +1 -0
  159. package/lib/recipe-references.ts +57 -6
  160. package/lib/recipe-usage.ts +31 -4
  161. package/lib/recipe-utils.ts +486 -0
  162. package/lib/runtime-notifier.ts +4 -6
  163. package/lib/runtime.ts +31 -7
  164. package/lib/state-readers.ts +93 -0
  165. package/lib/tools.ts +297 -58
  166. package/lib/validate-recipe.ts +110 -0
  167. package/package.json +11 -2
  168. package/recipes/actor-worker.json +35 -0
  169. package/recipes/pipeline-quorum-review.json +12 -7
  170. package/scripts/actor-worker.mjs +31 -0
  171. package/scripts/async-runner.mjs +11 -195
  172. package/scripts/build-dist.mjs +25 -0
  173. package/scripts/conformance.mjs +33 -0
  174. package/scripts/coordinator.mjs +19 -616
  175. package/scripts/locker.mjs +23 -322
  176. package/scripts/music-player.mjs +21 -2
  177. package/scripts/recipe-utils.mjs +20 -467
  178. package/scripts/validate-recipe.mjs +23 -113
  179. package/skills/actors/SKILL.md +7 -4
  180. package/skills/swarm/SKILL.md +3 -1
@@ -0,0 +1,15 @@
1
+ {
2
+ "accepts": [
3
+ "task.assign",
4
+ {
5
+ "type": "control.stop",
6
+ "description": "Request graceful worker shutdown"
7
+ }
8
+ ],
9
+ "emits": [
10
+ "task.claim",
11
+ "task.result",
12
+ "awaiting_assignment",
13
+ "actor.leave"
14
+ ]
15
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "actor-worker",
3
+ "path": "recipes/actor-worker.json",
4
+ "location": "packaged",
5
+ "active": true,
6
+ "args": [
7
+ "run",
8
+ "branch",
9
+ "poll_ms",
10
+ "state_dir"
11
+ ],
12
+ "mailbox": {
13
+ "kind": "branch",
14
+ "accepts": ["task.assign", "control.stop"]
15
+ }
16
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "to": "room:demo",
3
+ "from": "branch:demo/worker",
4
+ "type": "task.result",
5
+ "summary": "Worker completed task",
6
+ "body": {
7
+ "id": "inbox-001",
8
+ "result": "ok"
9
+ },
10
+ "received_at": "2026-05-26T00:00:00.000Z"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "room": "main",
3
+ "members": {
4
+ "branch:demo/worker": {
5
+ "display": "worker",
6
+ "role": "worker",
7
+ "status": "present",
8
+ "last_seen": "2026-05-26T00:00:00.000Z"
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "inbox-001",
3
+ "status": "queued",
4
+ "queued_at": "2026-05-26T00:00:00.000Z",
5
+ "to": "run:demo",
6
+ "from": "coordinator",
7
+ "type": "control.continue",
8
+ "body": "continue"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "event-001",
3
+ "type": "task.result",
4
+ "summary": "Worker completed task",
5
+ "body": {
6
+ "result": "ok"
7
+ },
8
+ "created_at": "2026-05-26T00:00:00.000Z"
9
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "run": "demo",
3
+ "state_dir": "{state_dir}",
4
+ "status": "running",
5
+ "template": "echo demo",
6
+ "values": {
7
+ "run_id": "demo",
8
+ "state_dir": "{state_dir}"
9
+ }
10
+ }
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ export default function toolRegistryExtension(pi) {
39
39
  const runDirWatchers = new Map();
40
40
  const observedRuns = new Map();
41
41
  const observedRunEventLines = new Map();
42
+ const observedRunOutboxEventIds = new Map();
42
43
  const retirementAttempts = new Set();
43
44
  let runStatusFrame = 0;
44
45
  let communicationWidgetVisible = false;
@@ -49,6 +50,7 @@ export default function toolRegistryExtension(pi) {
49
50
  let actorInspectorUnreadOnly = false;
50
51
  let actorInspectorRoomLimitPerRun = 12;
51
52
  let selectedInspectorSequence;
53
+ const actorInspectorReadKeys = new Set();
52
54
  let recipeWatcherFailureNotified = false;
53
55
  const getRunOwnerId = (ctx) => ctx.sessionManager.getSessionId();
54
56
  const retireCandidateRuns = (ctx, summary) => {
@@ -84,6 +86,7 @@ export default function toolRegistryExtension(pi) {
84
86
  branch: actorInspectorBranch,
85
87
  mention: actorInspectorMention,
86
88
  ownerId,
89
+ readKeys: actorInspectorReadKeys,
87
90
  roomLimitPerRun: actorInspectorRoomLimitPerRun,
88
91
  unreadOnly: actorInspectorUnreadOnly,
89
92
  });
@@ -100,7 +103,7 @@ export default function toolRegistryExtension(pi) {
100
103
  }
101
104
  : undefined, { placement: "belowEditor" });
102
105
  const transitions = Observability.detectRunTransitions(observedRuns, summary);
103
- const outboxEvents = Observability.detectRunOutboxEvents(observedRunEventLines, summary);
106
+ const outboxEvents = Observability.detectRunOutboxEvents(observedRunEventLines, summary, observedRunOutboxEventIds);
104
107
  if (!notify)
105
108
  return;
106
109
  retireCandidateRuns(ctx, summary);
@@ -119,7 +122,7 @@ export default function toolRegistryExtension(pi) {
119
122
  details: transition,
120
123
  }, { deliverAs: "followUp", triggerTurn: true });
121
124
  }
122
- Observability.pruneRunObservationState(observedRuns, observedRunEventLines, summary, transitions.map((transition) => transition.stateDir ?? transition.run));
125
+ Observability.pruneRunObservationState(observedRuns, observedRunEventLines, summary, transitions.map((transition) => transition.stateDir ?? transition.run), observedRunOutboxEventIds);
123
126
  for (const event of outboxEvents) {
124
127
  if (!Observability.shouldNotifyRunOutboxEvent(event))
125
128
  continue;
@@ -356,6 +359,19 @@ export default function toolRegistryExtension(pi) {
356
359
  ctx.ui.notify("Usage: /actors-inspect <number>", "warning");
357
360
  return;
358
361
  }
362
+ const previews = ActorInspectorTui.readActorInspectorPreviews(RUN_STATE_ROOT, actorInspectorRows, {
363
+ channels: actorInspectorChannels,
364
+ currentRunOnly: true,
365
+ branch: actorInspectorBranch,
366
+ mention: actorInspectorMention,
367
+ ownerId: getRunOwnerId(ctx),
368
+ readKeys: actorInspectorReadKeys,
369
+ roomLimitPerRun: actorInspectorRoomLimitPerRun,
370
+ unreadOnly: actorInspectorUnreadOnly,
371
+ });
372
+ const preview = previews.find((item) => item.sequence === sequence);
373
+ if (preview)
374
+ actorInspectorReadKeys.add(ActorInspectorTui.inspectorPreviewReadKey(preview));
359
375
  selectedInspectorSequence = sequence;
360
376
  communicationWidgetVisible = true;
361
377
  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,34 +5,18 @@
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";
10
+ import { readJsonFileResilient, readJsonlFileResilient } from "./state-readers.js";
9
11
  function asRecord(value) {
10
12
  return value && typeof value === "object" && !Array.isArray(value)
11
13
  ? value
12
14
  : {};
13
15
  }
14
16
  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
- }
17
+ return readJsonlFileResilient(file).records;
34
18
  }
35
- function previewValue(value, maxLength = 320) {
19
+ function previewValue(value, maxLength = Limits.INSPECTOR_BODY_PREVIEW_CHARS) {
36
20
  if (value === undefined)
37
21
  return undefined;
38
22
  const text = typeof value === "string" ? value : JSON.stringify(value);
@@ -58,6 +42,10 @@ function previewFromMessage(run, message, timestamp, displayNames = {}) {
58
42
  const from = typeof message.from === "string" ? message.from : undefined;
59
43
  const summary = typeof message.summary === "string" ? message.summary : undefined;
60
44
  const body = asRecord(message.body);
45
+ const metadata = asRecord(message.metadata);
46
+ const eventId = typeof message.id === "string" && message.id.trim()
47
+ ? message.id.trim()
48
+ : undefined;
61
49
  const display = from
62
50
  ? typeof body.display === "string" && body.display.trim()
63
51
  ? body.display.trim()
@@ -68,8 +56,10 @@ function previewFromMessage(run, message, timestamp, displayNames = {}) {
68
56
  ? { body_preview: previewValue(message.body) }
69
57
  : {}),
70
58
  channel: channelFor({ to }),
59
+ ...(eventId ? { event_id: eventId } : {}),
71
60
  ...(from ? { from } : {}),
72
61
  ...(display ? { from_display: display } : {}),
62
+ ...(metadata.requires_response === true ? { needs_response: true } : {}),
73
63
  run,
74
64
  ...(summary ? { summary } : {}),
75
65
  timestamp,
@@ -78,12 +68,7 @@ function previewFromMessage(run, message, timestamp, displayNames = {}) {
78
68
  };
79
69
  }
80
70
  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
- }
71
+ return readJsonFileResilient(path.join(stateDir, "rooms", room, "roster.json"), {}).value;
87
72
  }
88
73
  function memberDisplay(_address, member) {
89
74
  const display = typeof member.display === "string" ? member.display.trim() : "";
@@ -129,14 +114,21 @@ function readBranchInboxPreviews(run, stateDir) {
129
114
  .filter((entry) => entry.isDirectory())
130
115
  .flatMap((entry) => readJsonLines(path.join(branchesDir, entry.name, "inbox.jsonl"))
131
116
  .map((message) => {
132
- const preview = previewFromMessage(run, message, String(message.queued_at ?? message.received_at ?? message.timestamp ?? ""));
117
+ const preview = previewFromMessage(run, message, String(message.queued_at ??
118
+ message.received_at ??
119
+ message.timestamp ??
120
+ ""));
133
121
  if (!preview)
134
122
  return undefined;
135
123
  return {
136
124
  ...preview,
137
125
  branch: entry.name,
138
- ...(typeof message.id === "string" ? { message_id: message.id } : {}),
139
- ...(typeof message.status === "string" ? { inbox_status: message.status } : {}),
126
+ ...(typeof message.id === "string"
127
+ ? { message_id: message.id }
128
+ : {}),
129
+ ...(typeof message.status === "string"
130
+ ? { inbox_status: message.status }
131
+ : {}),
140
132
  };
141
133
  })
142
134
  .filter((preview) => Boolean(preview)));
@@ -156,19 +148,28 @@ function readOutboxPreviews(run, stateDir) {
156
148
  .filter((preview) => Boolean(preview));
157
149
  }
158
150
  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
- }
151
+ const meta = readJsonFileResilient(path.join(stateDir, "run.json"), undefined).value;
152
+ return typeof meta?.ownerId === "string" ? meta.ownerId : undefined;
166
153
  }
167
154
  function matchesOwner(stateDir, ownerId) {
168
155
  return ownerId === undefined || getRunOwnerId(stateDir) === ownerId;
169
156
  }
170
- function isUnreadPreview(preview) {
171
- return preview.inbox_status === "queued" || preview.inbox_status === undefined && preview.branch !== undefined;
157
+ export function inspectorPreviewReadKey(preview) {
158
+ return [
159
+ preview.channel,
160
+ preview.run,
161
+ preview.branch ?? "",
162
+ preview.event_id ?? preview.message_id ?? preview.timestamp,
163
+ preview.from ?? "",
164
+ preview.to,
165
+ preview.type,
166
+ ].join("|");
167
+ }
168
+ function isUnreadPreview(preview, readKeys) {
169
+ if (readKeys?.has(inspectorPreviewReadKey(preview)))
170
+ return false;
171
+ return (preview.inbox_status === "queued" ||
172
+ (preview.inbox_status === undefined && preview.branch !== undefined));
172
173
  }
173
174
  function matchesBranchFilter(preview, branch) {
174
175
  const name = branch?.trim();
@@ -183,7 +184,8 @@ function matchesPreviewFilter(preview, options) {
183
184
  if (options.channels?.length && !options.channels.includes(preview.channel)) {
184
185
  return false;
185
186
  }
186
- if (options.unreadOnly && !isUnreadPreview(preview))
187
+ const readKeys = options.readKeys ? new Set(options.readKeys) : undefined;
188
+ if (options.unreadOnly && !isUnreadPreview(preview, readKeys))
187
189
  return false;
188
190
  if (!matchesBranchFilter(preview, options.branch))
189
191
  return false;
@@ -298,6 +300,9 @@ function routeText(preview) {
298
300
  return `${actor} ⇢ ${preview.to}`;
299
301
  return `${actor} → ${actorName(preview.to)}`;
300
302
  }
303
+ function attentionMarker(preview) {
304
+ return preview.needs_response ? "! " : "";
305
+ }
301
306
  function style(styleFn, text) {
302
307
  return styleFn ? styleFn(text) : text;
303
308
  }
@@ -351,7 +356,7 @@ function renderCompactInspectorEntry(preview, width, sequenceWidth, routeWidth,
351
356
  const contentWidth = Math.max(8, width - prefix.length - suffix.length);
352
357
  const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
353
358
  const sequencePrefix = `${sequence}${separator}`;
354
- const route = routeText(preview);
359
+ const route = `${attentionMarker(preview)}${routeText(preview)}`;
355
360
  const routePadding = " ".repeat(Math.max(0, routeWidth - displayWidth(route)));
356
361
  const typePadding = " ".repeat(Math.max(0, typeWidth - displayWidth(preview.type)));
357
362
  const headline = previewText(preview);
@@ -382,7 +387,7 @@ function renderInspectorEntry(preview, width, sequenceWidth, routeWidth, typeWid
382
387
  const contentWidth = Math.max(8, width - prefix.length - suffix.length);
383
388
  const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
384
389
  const sequencePrefix = `${sequence}${separator}`;
385
- const route = routeText(preview);
390
+ const route = `${attentionMarker(preview)}${routeText(preview)}`;
386
391
  const type = preview.type;
387
392
  const summary = preview.summary?.trim() ?? "";
388
393
  const body = preview.body_preview?.trim() || (!summary ? previewText(preview) : "-");
@@ -430,7 +435,10 @@ export function readActorInspectorRoster(stateRoot = Paths.getRunStateRoot(), ru
430
435
  }
431
436
  function rosterRoleText(role) {
432
437
  const roleLabel = role?.split(";")[0] ?? "";
433
- const cleaned = roleLabel.replaceAll(/\s*\([^)]*\)\s*$/g, "").trim().toLowerCase();
438
+ const cleaned = roleLabel
439
+ .replaceAll(/\s*\([^)]*\)\s*$/g, "")
440
+ .trim()
441
+ .toLowerCase();
434
442
  if (!cleaned || cleaned === "actor")
435
443
  return undefined;
436
444
  return cleaned.replaceAll(/\s+/g, "-");
@@ -444,7 +452,10 @@ function rosterMemberText(member) {
444
452
  }
445
453
  function isRosterMemberActive(member) {
446
454
  const status = member.status?.trim().toLowerCase();
447
- return !status || status === "present" || status === "active" || status === "running";
455
+ return (!status ||
456
+ status === "present" ||
457
+ status === "active" ||
458
+ status === "running");
448
459
  }
449
460
  export function renderInspectorRosterLine(members, width = 80, styles = {}) {
450
461
  return renderInspectorRosterPanel(members, width, styles)?.[0];
@@ -491,12 +502,15 @@ export function renderInspectorItemView(previews, width = 80, styles = {}, optio
491
502
  const orderedKeys = [
492
503
  "channel",
493
504
  "run",
505
+ "event_id",
494
506
  "from",
495
507
  "from_display",
496
508
  "to",
497
509
  "type",
498
510
  "summary",
499
511
  "body_preview",
512
+ "message_id",
513
+ "needs_response",
500
514
  "timestamp",
501
515
  "stripe",
502
516
  ];
@@ -540,7 +554,7 @@ export function renderInspectorWidget(previews, width = 80, styles = {}, options
540
554
  const sequencePrefixWidth = sequenceWidth + separatorWidth;
541
555
  const fixedSeparatorsWidth = separatorWidth * 3;
542
556
  const availableForColumns = Math.max(0, safeWidth - 1 - sequencePrefixWidth - fixedSeparatorsWidth);
543
- const naturalRouteWidth = Math.max(...visible.map(({ preview }) => displayWidth(routeText(preview))));
557
+ const naturalRouteWidth = Math.max(...visible.map(({ preview }) => displayWidth(`${attentionMarker(preview)}${routeText(preview)}`)));
544
558
  const naturalTypeWidth = Math.max(...visible.map(({ preview }) => displayWidth(preview.type)));
545
559
  const routeWidth = Math.min(naturalRouteWidth, Math.max(4, Math.floor(availableForColumns * 0.35)));
546
560
  const typeWidth = Math.min(naturalTypeWidth, Math.max(4, Math.floor(availableForColumns * 0.25)));
@@ -32,10 +32,20 @@ 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;
47
+ diagnostics?: string[];
48
+ diagnostics_count?: number;
39
49
  last_message_at?: string;
40
50
  last_message_from?: string;
41
51
  last_message_summary?: string;
@@ -63,6 +73,11 @@ export interface ActorCommunicationSnapshot {
63
73
  }
64
74
  export declare function readRoomRoster(stateDir: string, room: string): Record<string, RoomMember>;
65
75
  export interface BranchInboxRecord extends ActorMessage {
76
+ claimed_at?: string;
77
+ claimed_by?: string;
78
+ error?: string;
79
+ failed_at?: string;
80
+ handled_at?: string;
66
81
  id?: string;
67
82
  queued_at?: string;
68
83
  status?: string;
@@ -75,6 +90,7 @@ export declare function readBranchInboxMessages(stateDir: string, run: string, a
75
90
  export declare function readBranchInboxDiagnostics(stateDir: string, run: string, address: string, limit?: number): BranchInboxReadResult;
76
91
  export declare function getBranchInboxTerminalRetainLimit(): number;
77
92
  export declare function appendBranchInboxMessage(stateDir: string, run: string, address: string, message: ActorMessage): void;
93
+ export declare function claimBranchInboxMessage(stateDir: string, run: string, address: string, owner?: string, statuses?: string[]): BranchInboxRecord | undefined;
78
94
  export declare function updateBranchInboxMessageStatus(stateDir: string, run: string, address: string, id: string, status: "claimed" | "handled" | "failed", metadata?: Record<string, unknown>): boolean;
79
95
  export declare function appendRoomMessage(stateDir: string, room: string, message: ActorMessage): RoomAppendResult;
80
96
  export declare function readRoomMessages(stateDir: string, room: string, limit?: number): RoomTimelineEntry[];