@llblab/pi-actors 0.33.0 → 0.34.1

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 (155) hide show
  1. package/AGENTS.md +22 -11
  2. package/BACKLOG.md +3 -34
  3. package/CHANGELOG.md +14 -0
  4. package/dist/index.js +10 -10
  5. package/dist/lib/async-runs.d.ts +23 -75
  6. package/dist/lib/async-runs.js +49 -801
  7. package/dist/lib/command-templates.d.ts +3 -3
  8. package/dist/lib/command-templates.js +3 -3
  9. package/dist/lib/config.d.ts +2 -2
  10. package/dist/lib/config.js +5 -5
  11. package/dist/lib/{output.d.ts → execution-output.d.ts} +3 -3
  12. package/dist/lib/{output.js → execution-output.js} +3 -3
  13. package/dist/lib/execution.js +1 -1
  14. package/dist/lib/{actor-inspector-tui.d.ts → inspector.d.ts} +3 -2
  15. package/dist/lib/{actor-inspector-tui.js → inspector.js} +3 -2
  16. package/dist/lib/mailbox-loop.d.ts +3 -3
  17. package/dist/lib/mailbox-loop.js +3 -3
  18. package/dist/lib/{actor-messages.d.ts → messages.d.ts} +3 -3
  19. package/dist/lib/{actor-messages.js → messages.js} +3 -3
  20. package/dist/lib/{actor-recipe-context.d.ts → recipes-context.d.ts} +3 -2
  21. package/dist/lib/{actor-recipe-context.js → recipes-context.js} +2 -1
  22. package/dist/lib/{recipe-discovery.d.ts → recipes-discovery.d.ts} +8 -8
  23. package/dist/lib/{recipe-discovery.js → recipes-discovery.js} +6 -6
  24. package/dist/lib/registry.d.ts +1 -1
  25. package/dist/lib/registry.js +22 -22
  26. package/dist/lib/{actor-rooms.d.ts → rooms.d.ts} +4 -4
  27. package/dist/lib/{actor-rooms.js → rooms.js} +3 -3
  28. package/dist/lib/runs-artifacts.d.ts +21 -0
  29. package/dist/lib/runs-artifacts.js +68 -0
  30. package/dist/lib/runs-control.d.ts +13 -0
  31. package/dist/lib/runs-control.js +59 -0
  32. package/dist/lib/runs-identity.d.ts +5 -0
  33. package/dist/lib/runs-identity.js +11 -0
  34. package/dist/lib/runs-index.d.ts +18 -0
  35. package/dist/lib/runs-index.js +119 -0
  36. package/dist/lib/runs-mailbox.d.ts +25 -0
  37. package/dist/lib/runs-mailbox.js +146 -0
  38. package/dist/lib/runs-messages.d.ts +15 -0
  39. package/dist/lib/runs-messages.js +179 -0
  40. package/dist/lib/runs-outbox.d.ts +41 -0
  41. package/dist/lib/runs-outbox.js +87 -0
  42. package/dist/lib/runs-process.d.ts +7 -0
  43. package/dist/lib/runs-process.js +36 -0
  44. package/dist/lib/runs-retention.d.ts +8 -0
  45. package/dist/lib/runs-retention.js +56 -0
  46. package/dist/lib/runs-start.d.ts +9 -0
  47. package/dist/lib/runs-start.js +67 -0
  48. package/dist/lib/runs-status.d.ts +8 -0
  49. package/dist/lib/runs-status.js +60 -0
  50. package/dist/lib/runtime.d.ts +2 -2
  51. package/dist/lib/runtime.js +5 -5
  52. package/dist/lib/schema.d.ts +9 -0
  53. package/dist/lib/schema.js +40 -0
  54. package/dist/lib/tools-access.d.ts +19 -0
  55. package/dist/lib/tools-access.js +44 -0
  56. package/dist/lib/tools-inspect.d.ts +11 -0
  57. package/dist/lib/tools-inspect.js +534 -0
  58. package/dist/lib/tools-local.d.ts +14 -0
  59. package/dist/lib/tools-local.js +156 -0
  60. package/dist/lib/tools-mailbox.d.ts +8 -0
  61. package/dist/lib/tools-mailbox.js +48 -0
  62. package/dist/lib/tools-message.d.ts +9 -0
  63. package/dist/lib/tools-message.js +428 -0
  64. package/dist/lib/tools-register.d.ts +18 -0
  65. package/dist/lib/tools-register.js +40 -0
  66. package/dist/lib/tools-response.d.ts +21 -0
  67. package/dist/lib/tools-response.js +219 -0
  68. package/dist/lib/tools-spawn.d.ts +12 -0
  69. package/dist/lib/tools-spawn.js +159 -0
  70. package/dist/lib/tools.d.ts +8 -39
  71. package/dist/lib/tools.js +11 -1490
  72. package/dist/recipes/utility-coordinator-lock-snapshot.json +1 -1
  73. package/dist/scripts/actor-worker.mjs +190 -7
  74. package/dist/scripts/async-runner.mjs +171 -8
  75. package/dist/scripts/build-dist.mjs +1 -1
  76. package/dist/scripts/conformance.mjs +3 -3
  77. package/dist/scripts/coordinator.mjs +733 -11
  78. package/dist/scripts/locker.mjs +345 -16
  79. package/dist/scripts/recipe-utils.mjs +478 -14
  80. package/dist/scripts/validate-recipe.mjs +110 -10
  81. package/dist/skills/actors/SKILL.md +1 -1
  82. package/dist/skills/swarm/SKILL.md +1 -1
  83. package/docs/recipe-library.md +1 -1
  84. package/docs/template-recipes.md +1 -1
  85. package/index.ts +11 -18
  86. package/lib/async-runs.ts +124 -1016
  87. package/lib/command-templates.ts +3 -3
  88. package/lib/config.ts +7 -7
  89. package/lib/{output.ts → execution-output.ts} +3 -3
  90. package/lib/execution.ts +5 -1
  91. package/lib/{actor-inspector-tui.ts → inspector.ts} +4 -3
  92. package/lib/mailbox-loop.ts +3 -3
  93. package/lib/{actor-messages.ts → messages.ts} +3 -3
  94. package/lib/{actor-recipe-context.ts → recipes-context.ts} +3 -2
  95. package/lib/{recipe-discovery.ts → recipes-discovery.ts} +19 -19
  96. package/lib/{recipe-usage.ts → recipes-usage.ts} +2 -2
  97. package/lib/registry.ts +37 -24
  98. package/lib/{actor-rooms.ts → rooms.ts} +4 -4
  99. package/lib/runs-artifacts.ts +96 -0
  100. package/lib/runs-control.ts +85 -0
  101. package/lib/runs-identity.ts +14 -0
  102. package/lib/runs-index.ts +156 -0
  103. package/lib/runs-mailbox.ts +208 -0
  104. package/lib/runs-messages.ts +252 -0
  105. package/lib/runs-outbox.ts +144 -0
  106. package/lib/runs-process.ts +51 -0
  107. package/lib/runs-retention.ts +77 -0
  108. package/lib/runs-start.ts +98 -0
  109. package/lib/runs-status.ts +78 -0
  110. package/lib/runtime.ts +6 -6
  111. package/lib/schema.ts +55 -0
  112. package/lib/tools-access.ts +68 -0
  113. package/lib/tools-inspect.ts +735 -0
  114. package/lib/tools-local.ts +234 -0
  115. package/lib/tools-mailbox.ts +56 -0
  116. package/lib/tools-message.ts +569 -0
  117. package/lib/tools-register.ts +64 -0
  118. package/lib/tools-response.ts +267 -0
  119. package/lib/tools-spawn.ts +240 -0
  120. package/lib/tools.ts +20 -2040
  121. package/package.json +1 -1
  122. package/recipes/utility-coordinator-lock-snapshot.json +1 -1
  123. package/scripts/actor-worker.mjs +190 -7
  124. package/scripts/async-runner.mjs +171 -8
  125. package/scripts/build-dist.mjs +1 -1
  126. package/scripts/conformance.mjs +3 -3
  127. package/scripts/coordinator.mjs +733 -11
  128. package/scripts/locker.mjs +345 -16
  129. package/scripts/recipe-utils.mjs +478 -14
  130. package/scripts/validate-recipe.mjs +110 -10
  131. package/skills/actors/SKILL.md +1 -1
  132. package/skills/swarm/SKILL.md +1 -1
  133. package/dist/lib/actor-worker.d.ts +0 -16
  134. package/dist/lib/actor-worker.js +0 -134
  135. package/dist/lib/async-runner.d.ts +0 -5
  136. package/dist/lib/async-runner.js +0 -134
  137. package/dist/lib/coordinator.d.ts +0 -5
  138. package/dist/lib/coordinator.js +0 -660
  139. package/dist/lib/locker.d.ts +0 -5
  140. package/dist/lib/locker.js +0 -310
  141. package/dist/lib/recipe-utils.d.ts +0 -5
  142. package/dist/lib/recipe-utils.js +0 -385
  143. package/dist/lib/validate-recipe.d.ts +0 -6
  144. package/dist/lib/validate-recipe.js +0 -104
  145. package/lib/actor-worker.ts +0 -174
  146. package/lib/async-runner.ts +0 -173
  147. package/lib/coordinator.ts +0 -742
  148. package/lib/locker.ts +0 -340
  149. package/lib/recipe-utils.ts +0 -486
  150. package/lib/validate-recipe.ts +0 -110
  151. /package/dist/lib/{recipe-references.d.ts → recipes-references.d.ts} +0 -0
  152. /package/dist/lib/{recipe-references.js → recipes-references.js} +0 -0
  153. /package/dist/lib/{recipe-usage.d.ts → recipes-usage.d.ts} +0 -0
  154. /package/dist/lib/{recipe-usage.js → recipes-usage.js} +0 -0
  155. /package/lib/{recipe-references.ts → recipes-references.ts} +0 -0
@@ -0,0 +1,735 @@
1
+ /**
2
+ * Public inspect tool behavior
3
+ * Zones: actor observation, recipe registry inspection, room/session/tool/run views
4
+ * Owns the public inspect execution path and compact observation responses
5
+ */
6
+
7
+ import { execFileSync } from "node:child_process";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { dirname, join } from "node:path";
10
+
11
+ import * as AsyncRuns from "./async-runs.ts";
12
+ import * as Limits from "./limits.ts";
13
+ import * as Messages from "./messages.ts";
14
+ import * as Paths from "./paths.ts";
15
+ import * as RecipesDiscovery from "./recipes-discovery.ts";
16
+ import * as Rooms from "./rooms.ts";
17
+ import * as Schema from "./schema.ts";
18
+ import * as ToolsAccess from "./tools-access.ts";
19
+ import * as ToolsMailbox from "./tools-mailbox.ts";
20
+ import * as ToolsResponse from "./tools-response.ts";
21
+
22
+ const asRecord = ToolsResponse.asRecord;
23
+ const maybeJsonText = ToolsResponse.maybeJsonText;
24
+
25
+ function compactRunMessages(messages: AsyncRuns.RunOutboxEvent[]): string {
26
+ if (messages.length === 0) return "\n(no actor messages)";
27
+ return `\n${messages
28
+ .map((message) =>
29
+ [
30
+ `run=${message.run}`,
31
+ `type=${message.event}`,
32
+ `level=${message.level}`,
33
+ `summary=${message.summary.replaceAll(/\s+/g, "_")}`,
34
+ ].join(" "),
35
+ )
36
+ .join("\n")}`;
37
+ }
38
+
39
+ function compactRoomPreviews(previews: Rooms.RoomMessagePreview[]): string {
40
+ if (previews.length === 0) return "\n(no room message previews)";
41
+ return `\n${previews
42
+ .map((preview) =>
43
+ [
44
+ `ts=${preview.timestamp}`,
45
+ preview.from ? `from=${preview.from}` : "",
46
+ `to=${preview.to}`,
47
+ `type=${preview.type}`,
48
+ preview.summary
49
+ ? `summary=${ToolsResponse.compactPreview(preview.summary)}`
50
+ : "",
51
+ preview.body_preview
52
+ ? `body=${ToolsResponse.compactPreview(preview.body_preview)}`
53
+ : "",
54
+ ]
55
+ .filter(Boolean)
56
+ .join(" "),
57
+ )
58
+ .join("\n")}`;
59
+ }
60
+
61
+ function compactRoomMessages(messages: Rooms.RoomTimelineEntry[]): string {
62
+ if (messages.length === 0) return "\n(no room messages)";
63
+ return `\n${messages
64
+ .map((message) =>
65
+ [
66
+ `ts=${message.received_at}`,
67
+ `from=${String(message.from ?? "<unknown>")}`,
68
+ `to=${message.to}`,
69
+ `type=${message.type}`,
70
+ `summary=${String(message.summary ?? "").replaceAll(/\s+/g, "_")}`,
71
+ ToolsResponse.compactPreview(message.body)
72
+ ? `body=${ToolsResponse.compactPreview(message.body)}`
73
+ : "",
74
+ ]
75
+ .filter(Boolean)
76
+ .join(" "),
77
+ )
78
+ .join("\n")}`;
79
+ }
80
+
81
+ function compactRoomContacts(contacts: Rooms.RoomContact[]): string {
82
+ if (contacts.length === 0) return "\n(no room contacts)";
83
+ return `\n${contacts
84
+ .map((contact) =>
85
+ [
86
+ `address=${contact.address}`,
87
+ contact.role !== undefined ? `role=${String(contact.role)}` : "",
88
+ contact.parent !== undefined ? `parent=${String(contact.parent)}` : "",
89
+ contact.caps !== undefined
90
+ ? `caps=${Array.isArray(contact.caps) ? contact.caps.join(",") : String(contact.caps)}`
91
+ : "",
92
+ contact.claim !== undefined
93
+ ? `claim=${String(contact.claim).replaceAll(/\s+/g, "_")}`
94
+ : "",
95
+ contact.status !== undefined ? `status=${String(contact.status)}` : "",
96
+ ]
97
+ .filter(Boolean)
98
+ .join(" "),
99
+ )
100
+ .join("\n")}`;
101
+ }
102
+
103
+ function compactRoomRoster(roster: Record<string, Rooms.RoomMember>): string {
104
+ const members = Object.values(roster);
105
+ if (members.length === 0) return "\n(no room members)";
106
+ return `\n${members
107
+ .map((member) =>
108
+ [
109
+ `address=${member.address}`,
110
+ `role=${String(member.role ?? "")}`,
111
+ member.parent !== undefined ? `parent=${String(member.parent)}` : "",
112
+ member.caps !== undefined
113
+ ? `caps=${Array.isArray(member.caps) ? member.caps.join(",") : String(member.caps)}`
114
+ : "",
115
+ member.claim !== undefined
116
+ ? `claim=${String(member.claim).replaceAll(/\s+/g, "_")}`
117
+ : "",
118
+ `status=${String(member.status ?? "")}`,
119
+ `last_seen=${member.last_seen}`,
120
+ ].join(" "),
121
+ )
122
+ .join("\n")}`;
123
+ }
124
+
125
+ function compactRoomStatus(status: Rooms.RoomStatus): string {
126
+ return `\nroom=${status.room} messages=${status.message_count} roster=${status.roster_count}${status.diagnostics_count ? ` diagnostics=${status.diagnostics_count}` : ""}${status.last_message_at ? ` last_message_at=${status.last_message_at}` : ""}${status.last_message_from ? ` last_from=${status.last_message_from}` : ""}${status.last_message_type ? ` last_type=${status.last_message_type}` : ""}${status.last_message_summary ? ` last_summary=${ToolsResponse.compactPreview(status.last_message_summary)}` : ""}`;
127
+ }
128
+
129
+ function compactCommunicationSnapshot(
130
+ snapshot: Rooms.ActorCommunicationSnapshot | undefined,
131
+ ): string {
132
+ if (!snapshot) return "\n(no communication snapshot)";
133
+ return `\nself=${snapshot.self} root=${snapshot.root} rooms=${snapshot.rooms.length} updated_at=${snapshot.updated_at}`;
134
+ }
135
+
136
+ function compactArtifactPath(value: unknown): string {
137
+ if (typeof value === "string") return value;
138
+ const record = asRecord(value);
139
+ return String(record.path ?? "<missing>");
140
+ }
141
+
142
+ function compactActorFiles(status: Record<string, unknown>): string {
143
+ const run = String(status.run ?? "<unknown>");
144
+ const artifacts = asRecord(status.artifacts);
145
+ const files = [
146
+ status.stdoutLog,
147
+ status.stderrLog,
148
+ status.eventsFile,
149
+ status.outboxFile,
150
+ status.state_dir
151
+ ? `${String(status.state_dir)}/communication.json`
152
+ : undefined,
153
+ status.state_dir ? `${String(status.state_dir)}/result.json` : undefined,
154
+ ].filter((file): file is string => typeof file === "string");
155
+ const artifactText = Object.keys(artifacts).length
156
+ ? ` artifacts=${Object.entries(artifacts)
157
+ .map(([key, value]) => `${key}:${compactArtifactPath(value)}`)
158
+ .join(",")}`
159
+ : "";
160
+ const nextActions = ToolsResponse.artifactNextActions(run, artifacts);
161
+ const nextText = nextActions.length
162
+ ? ` next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`
163
+ : "";
164
+ return `\nrun=${run}${artifactText}${files.length ? ` files=${files.join(",")}` : ""}${nextText}`;
165
+ }
166
+
167
+ function summarizeOtherSessions(
168
+ currentSession: string,
169
+ allRuns: Array<Record<string, unknown>>,
170
+ ): Record<string, unknown> {
171
+ const otherRuns = allRuns.filter(
172
+ (run) => run.ownerId && run.ownerId !== currentSession,
173
+ );
174
+ return {
175
+ other_runs: otherRuns.length,
176
+ other_sessions: new Set(otherRuns.map((run) => run.ownerId)).size,
177
+ };
178
+ }
179
+
180
+ function compactSessionRuns(
181
+ session: string,
182
+ runs: Array<Record<string, unknown>>,
183
+ summary: Record<string, unknown> = {},
184
+ ): string {
185
+ const suffix = [
186
+ summary.other_sessions !== undefined
187
+ ? `other_sessions=${String(summary.other_sessions)}`
188
+ : "",
189
+ summary.other_runs !== undefined
190
+ ? `other_runs=${String(summary.other_runs)}`
191
+ : "",
192
+ ]
193
+ .filter(Boolean)
194
+ .join(" ");
195
+ if (runs.length === 0)
196
+ return `\nsession=${session} runs=0${suffix ? ` ${suffix}` : ""}`;
197
+ return `\nsession=${session} runs=${runs.length}${suffix ? ` ${suffix}` : ""}\n${runs
198
+ .map((run) => {
199
+ const tokens = [
200
+ `run=${String(run.run ?? "")}`,
201
+ `status=${String(run.status ?? "")}`,
202
+ ];
203
+ if (run.recipe) tokens.push(`recipe=${String(run.recipe)}`);
204
+ if (run.retire_when)
205
+ tokens.push(`retire_when=${String(run.retire_when)}`);
206
+ return tokens.join(" ");
207
+ })
208
+ .join("\n")}`;
209
+ }
210
+
211
+ function getPiActorsRuntimeStatus(): Record<string, unknown> {
212
+ const packagedRecipeRoot = Paths.getPackagedRecipeRoot();
213
+ const packageRoot = dirname(packagedRecipeRoot);
214
+ const packageJsonPath = join(packageRoot, "package.json");
215
+ const packageJson = existsSync(packageJsonPath)
216
+ ? (JSON.parse(readFileSync(packageJsonPath, "utf8")) as Record<
217
+ string,
218
+ unknown
219
+ >)
220
+ : {};
221
+ let git_commit: string | undefined;
222
+ try {
223
+ git_commit = execFileSync(
224
+ "git",
225
+ ["-C", packageRoot, "rev-parse", "--short", "HEAD"],
226
+ {
227
+ encoding: "utf8",
228
+ stdio: ["ignore", "pipe", "ignore"],
229
+ },
230
+ ).trim();
231
+ } catch {
232
+ git_commit = undefined;
233
+ }
234
+ const entrypoint = new URL(import.meta.url).pathname;
235
+ return {
236
+ entrypoint,
237
+ git_commit,
238
+ mode: entrypoint.includes("/dist/") ? "dist" : "source",
239
+ package_name: packageJson.name ?? "@llblab/pi-actors",
240
+ package_root: packageRoot,
241
+ recipe_root: Paths.getRecipeRoot(),
242
+ packaged_recipe_root: packagedRecipeRoot,
243
+ version: packageJson.version ?? "unknown",
244
+ };
245
+ }
246
+
247
+ function compactPiActorsRuntimeStatus(status: Record<string, unknown>): string {
248
+ return `\npi-actors version=${String(status.version)} mode=${String(status.mode)} path=${String(status.package_root)} entrypoint=${String(status.entrypoint)}${status.git_commit ? ` git=${String(status.git_commit)}` : ""}`;
249
+ }
250
+
251
+ function compactToolActor(name: string, tool: Record<string, unknown>): string {
252
+ const parameters = asRecord(tool.parameters);
253
+ const required = Array.isArray(parameters.required)
254
+ ? parameters.required.join(",")
255
+ : "";
256
+ const properties = asRecord(parameters.properties);
257
+ return `\ntool=${name} description=${String(tool.description ?? "").replaceAll(/\s+/g, "_")} args=${Object.keys(properties).join(",")} required=${required}`;
258
+ }
259
+
260
+ export interface InspectToolDeps<TContext = unknown> {
261
+ getTool?: (name: string) => any | undefined;
262
+ packagedRecipeRoot?: string;
263
+ recipeRoot?: string;
264
+ }
265
+
266
+ export function createInspectToolDefinition<TContext = unknown>(
267
+ deps: InspectToolDeps<TContext> = {},
268
+ ): any {
269
+ return {
270
+ name: "inspect",
271
+ label: "Inspect",
272
+ description:
273
+ "Intentionally inspect actors at decision points, after follow-ups, or during diagnosis instead of polling. Core targets are run:<id> and tool:<name>; advanced targets include branch:<run>/<branch>, room:<run>, coordinator, session:<id>, and session:all.",
274
+ parameters: Schema.objectSchema(
275
+ {
276
+ lines: Schema.stringSchema(
277
+ "Line count for tail/messages views. Default 40.",
278
+ ),
279
+ status: Schema.stringSchema(
280
+ "Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited.",
281
+ ),
282
+ target: Schema.stringSchema(
283
+ "Actor address to inspect, e.g. run:<id> or tool:<name>; advanced: branch:<run>/<branch>, room:<run>, coordinator, session:<id>, session:all.",
284
+ ),
285
+ verbose: Schema.booleanSchema(
286
+ "Return full JSON instead of compact text where available.",
287
+ ),
288
+ view: Schema.stringSchema(
289
+ "Inspection view. Core run views: status, tail, messages, artifacts, files, mailbox. Advanced views include communication, roster, and contacts.",
290
+ ),
291
+ },
292
+ ["target", "view"],
293
+ ),
294
+ async execute(
295
+ _toolCallId: string,
296
+ params: unknown,
297
+ _signal: AbortSignal | undefined,
298
+ _onUpdate: unknown,
299
+ ctx: TContext,
300
+ ) {
301
+ const input = asRecord(params);
302
+ const target = String(input.target ?? "");
303
+ const view = String(input.view ?? "");
304
+ if (target === "recipes" || target === "recipe-registry") {
305
+ if (
306
+ view !== "status" &&
307
+ view !== "summary" &&
308
+ view !== "doctor" &&
309
+ view !== "imports"
310
+ ) {
311
+ throw new Error(
312
+ "inspect recipes supports view=status, view=summary, view=doctor, or view=imports.",
313
+ );
314
+ }
315
+ const discovered = RecipesDiscovery.discoverRecipeSources([
316
+ {
317
+ root: deps.recipeRoot ?? Paths.getRecipeRoot(),
318
+ defaultTool: true,
319
+ mutableUsage: true,
320
+ },
321
+ { root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
322
+ ]);
323
+ const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
324
+ const summaryBase = {
325
+ ...RecipesDiscovery.summarizeDiscovery(discovered),
326
+ drafts: RecipesDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
327
+ };
328
+ const summary = {
329
+ ...summaryBase,
330
+ next_actions: ToolsResponse.recipeRegistryNextActions(
331
+ summaryBase,
332
+ view,
333
+ ),
334
+ };
335
+ return {
336
+ content: [
337
+ {
338
+ type: "text" as const,
339
+ text: maybeJsonText(
340
+ summary,
341
+ input.verbose === true,
342
+ view === "doctor"
343
+ ? ToolsResponse.compactRecipeDoctor(summary)
344
+ : view === "imports"
345
+ ? ToolsResponse.compactRecipeImports(summary)
346
+ : ToolsResponse.compactRecipeRegistry(summary),
347
+ ),
348
+ },
349
+ ],
350
+ details: summary,
351
+ };
352
+ }
353
+ const address = Messages.parseActorAddress(target);
354
+ if (address.kind === "coordinator") {
355
+ if (view !== "status" && view !== "runs") {
356
+ throw new Error(
357
+ "inspect coordinator supports view=status or view=runs.",
358
+ );
359
+ }
360
+ const session = ToolsAccess.requireContextSessionId(
361
+ ctx,
362
+ "inspect coordinator",
363
+ );
364
+ const allRuns = AsyncRuns.listRuns(
365
+ undefined,
366
+ typeof input.status === "string" ? input.status : undefined,
367
+ ).map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
368
+ const runs = allRuns.filter((run) => run.ownerId === session);
369
+ const sessionSummary = summarizeOtherSessions(session, allRuns);
370
+ return {
371
+ content: [
372
+ {
373
+ type: "text" as const,
374
+ text: maybeJsonText(
375
+ { session, runs, ...sessionSummary },
376
+ input.verbose === true,
377
+ compactSessionRuns(session, runs, sessionSummary),
378
+ ),
379
+ },
380
+ ],
381
+ details: { session, runs, ...sessionSummary },
382
+ };
383
+ }
384
+ if (address.kind === "session") {
385
+ if (view !== "status" && view !== "runs") {
386
+ throw new Error(
387
+ "inspect session:<id> supports view=status or view=runs.",
388
+ );
389
+ }
390
+ const allRuns = AsyncRuns.listRuns(
391
+ undefined,
392
+ typeof input.status === "string" ? input.status : undefined,
393
+ ).map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
394
+ const runs = allRuns.filter(
395
+ (run) => address.value === "all" || run.ownerId === address.value,
396
+ );
397
+ const sessionSummary =
398
+ address.value === "all"
399
+ ? {}
400
+ : summarizeOtherSessions(address.value || "", allRuns);
401
+ return {
402
+ content: [
403
+ {
404
+ type: "text" as const,
405
+ text: maybeJsonText(
406
+ { session: address.value, runs, ...sessionSummary },
407
+ input.verbose === true,
408
+ compactSessionRuns(address.value || "", runs, sessionSummary),
409
+ ),
410
+ },
411
+ ],
412
+ details: { session: address.value, runs, ...sessionSummary },
413
+ };
414
+ }
415
+ if (address.kind === "tool" && address.value) {
416
+ if (address.value === "pi-actors") {
417
+ if (view !== "status") {
418
+ throw new Error("inspect tool:pi-actors supports view=status.");
419
+ }
420
+ const details = getPiActorsRuntimeStatus();
421
+ return {
422
+ content: [
423
+ {
424
+ type: "text" as const,
425
+ text: maybeJsonText(
426
+ details,
427
+ input.verbose === true,
428
+ compactPiActorsRuntimeStatus(details),
429
+ ),
430
+ },
431
+ ],
432
+ details,
433
+ };
434
+ }
435
+ if (view !== "status" && view !== "schema") {
436
+ throw new Error(
437
+ "inspect tool:<name> supports view=status or view=schema.",
438
+ );
439
+ }
440
+ const tool = deps.getTool?.(address.value);
441
+ if (!tool) throw new Error(`tool actor not found: ${address.value}`);
442
+ const details = {
443
+ name: address.value,
444
+ description: tool.description,
445
+ parameters: tool.parameters,
446
+ promptSnippet: tool.promptSnippet,
447
+ };
448
+ return {
449
+ content: [
450
+ {
451
+ type: "text" as const,
452
+ text: maybeJsonText(
453
+ details,
454
+ input.verbose === true || view === "schema",
455
+ compactToolActor(address.value, details),
456
+ ),
457
+ },
458
+ ],
459
+ details,
460
+ };
461
+ }
462
+ if (address.kind === "room" && address.value && address.room) {
463
+ const status = ToolsAccess.assertRunAccessibleToContext(
464
+ address.value,
465
+ ctx,
466
+ );
467
+ const stateDir = String(status.state_dir ?? "");
468
+ if (!stateDir)
469
+ throw new Error(`room:${address.value} has no run state directory.`);
470
+ if (view === "status") {
471
+ const status = Rooms.getRoomStatus(stateDir, address.room);
472
+ return {
473
+ content: [
474
+ {
475
+ type: "text" as const,
476
+ text: maybeJsonText(
477
+ status,
478
+ input.verbose === true,
479
+ compactRoomStatus(status),
480
+ ),
481
+ },
482
+ ],
483
+ details: status,
484
+ };
485
+ }
486
+ if (view === "previews") {
487
+ const previews = Rooms.readRoomMessagePreviews(
488
+ stateDir,
489
+ address.room,
490
+ Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
491
+ );
492
+ return {
493
+ content: [
494
+ {
495
+ type: "text" as const,
496
+ text: maybeJsonText(
497
+ previews,
498
+ input.verbose === true,
499
+ compactRoomPreviews(previews),
500
+ ),
501
+ },
502
+ ],
503
+ details: { previews },
504
+ };
505
+ }
506
+ if (view === "messages") {
507
+ const messages = Rooms.readRoomMessages(
508
+ stateDir,
509
+ address.room,
510
+ Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
511
+ );
512
+ return {
513
+ content: [
514
+ {
515
+ type: "text" as const,
516
+ text: maybeJsonText(
517
+ messages,
518
+ input.verbose === true,
519
+ compactRoomMessages(messages),
520
+ ),
521
+ },
522
+ ],
523
+ details: { messages },
524
+ };
525
+ }
526
+ if (view === "contacts") {
527
+ const contacts = Rooms.readRoomContacts(stateDir, address.room);
528
+ return {
529
+ content: [
530
+ {
531
+ type: "text" as const,
532
+ text: maybeJsonText(
533
+ contacts,
534
+ input.verbose === true,
535
+ compactRoomContacts(contacts),
536
+ ),
537
+ },
538
+ ],
539
+ details: { contacts },
540
+ };
541
+ }
542
+ if (view === "roster") {
543
+ const roster = Rooms.readRoomRoster(stateDir, address.room);
544
+ return {
545
+ content: [
546
+ {
547
+ type: "text" as const,
548
+ text: maybeJsonText(
549
+ roster,
550
+ input.verbose === true,
551
+ compactRoomRoster(roster),
552
+ ),
553
+ },
554
+ ],
555
+ details: { roster },
556
+ };
557
+ }
558
+ throw new Error(
559
+ "inspect room:<run> supports view=status, view=messages, view=previews, view=roster, or view=contacts.",
560
+ );
561
+ }
562
+ const runId =
563
+ address.kind === "run" || address.kind === "branch"
564
+ ? address.value
565
+ : undefined;
566
+ if (!runId)
567
+ throw new Error(
568
+ "inspect target must be run:<id>, branch:<run>/<branch>, coordinator, session:<id>, or tool:<name>.",
569
+ );
570
+ if (address.kind === "branch") {
571
+ if (view !== "mailbox")
572
+ throw new Error(
573
+ "inspect branch:<run>/<branch> supports view=mailbox.",
574
+ );
575
+ const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
576
+ const branchInbox = Rooms.readBranchInboxDiagnostics(
577
+ String(status.state_dir ?? ""),
578
+ runId,
579
+ target,
580
+ Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
581
+ );
582
+ const messages = branchInbox.messages;
583
+ return {
584
+ content: [
585
+ {
586
+ type: "text" as const,
587
+ text: maybeJsonText(
588
+ input.verbose === true ? branchInbox : messages,
589
+ input.verbose === true,
590
+ `${ToolsResponse.compactBranchInbox(messages.map((message) => ({ ...message })))}${branchInbox.corrupted > 0 ? `\ncorrupted=${branchInbox.corrupted}` : ""}`,
591
+ ),
592
+ },
593
+ ],
594
+ details: { corrupted: branchInbox.corrupted, messages },
595
+ };
596
+ }
597
+ switch (view) {
598
+ case "status": {
599
+ const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
600
+ return {
601
+ content: [
602
+ {
603
+ type: "text" as const,
604
+ text: maybeJsonText(
605
+ status,
606
+ input.verbose === true,
607
+ ToolsResponse.compactAsyncRunStatus(status),
608
+ ),
609
+ },
610
+ ],
611
+ details: status,
612
+ };
613
+ }
614
+ case "tail": {
615
+ ToolsAccess.assertRunAccessibleToContext(runId, ctx);
616
+ const text = AsyncRuns.tailRun(
617
+ runId,
618
+ Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
619
+ );
620
+ return {
621
+ content: [{ type: "text" as const, text: `\n${text}` }],
622
+ details: {},
623
+ };
624
+ }
625
+ case "messages": {
626
+ ToolsAccess.assertRunAccessibleToContext(runId, ctx);
627
+ const messages = AsyncRuns.readRunEvents(
628
+ runId,
629
+ Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
630
+ );
631
+ return {
632
+ content: [
633
+ {
634
+ type: "text" as const,
635
+ text: maybeJsonText(
636
+ messages,
637
+ input.verbose === true,
638
+ compactRunMessages(messages),
639
+ ),
640
+ },
641
+ ],
642
+ details: { messages },
643
+ };
644
+ }
645
+ case "artifacts":
646
+ case "files": {
647
+ const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
648
+ const artifactManifest = AsyncRuns.resolveArtifactManifest(
649
+ status.artifacts as
650
+ | Record<string, AsyncRuns.RunArtifactDeclaration>
651
+ | undefined,
652
+ );
653
+ const details = artifactManifest
654
+ ? {
655
+ ...status,
656
+ artifact_manifest: artifactManifest,
657
+ next_actions: ToolsResponse.artifactNextActions(
658
+ status.run ?? runId,
659
+ asRecord(status.artifacts),
660
+ ),
661
+ }
662
+ : status;
663
+ return {
664
+ content: [
665
+ {
666
+ type: "text" as const,
667
+ text: maybeJsonText(
668
+ details,
669
+ input.verbose === true,
670
+ compactActorFiles(status),
671
+ ),
672
+ },
673
+ ],
674
+ details,
675
+ };
676
+ }
677
+ case "mailbox": {
678
+ const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
679
+ const mailbox = asRecord(status.mailbox);
680
+ const normalizedMailbox =
681
+ ToolsMailbox.normalizeMailboxContracts(mailbox);
682
+ const messages = AsyncRuns.readRunInboxMessages(
683
+ runId,
684
+ Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
685
+ );
686
+ const details = {
687
+ mailbox,
688
+ normalized_mailbox: normalizedMailbox,
689
+ messages,
690
+ };
691
+ return {
692
+ content: [
693
+ {
694
+ type: "text" as const,
695
+ text: maybeJsonText(
696
+ details,
697
+ input.verbose === true,
698
+ ToolsResponse.compactRunMailbox(
699
+ String(status.run ?? runId),
700
+ mailbox,
701
+ messages,
702
+ ),
703
+ ),
704
+ },
705
+ ],
706
+ details,
707
+ };
708
+ }
709
+ case "communication": {
710
+ const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
711
+ const snapshot = Rooms.readCommunicationSnapshot(
712
+ String(status.state_dir ?? ""),
713
+ );
714
+ return {
715
+ content: [
716
+ {
717
+ type: "text" as const,
718
+ text: maybeJsonText(
719
+ snapshot ?? {},
720
+ input.verbose === true,
721
+ compactCommunicationSnapshot(snapshot),
722
+ ),
723
+ },
724
+ ],
725
+ details: { communication: snapshot },
726
+ };
727
+ }
728
+ default:
729
+ throw new Error(
730
+ "inspect view must be one of: status, tail, messages, artifacts, files, mailbox, communication; branch targets support mailbox.",
731
+ );
732
+ }
733
+ },
734
+ };
735
+ }