@llblab/pi-actors 0.42.3 → 0.43.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 (279) hide show
  1. package/AGENTS.md +127 -175
  2. package/BACKLOG.md +189 -1
  3. package/CHANGELOG.md +183 -292
  4. package/README.md +115 -276
  5. package/dist/fixtures/protocol/control-endpoint.json +6 -0
  6. package/dist/fixtures/protocol/control-record.json +9 -0
  7. package/dist/fixtures/protocol/recipe-summary.json +4 -12
  8. package/dist/fixtures/protocol/trace-event.json +9 -0
  9. package/dist/index.js +1 -1
  10. package/dist/lib/async-runs.d.ts +15 -38
  11. package/dist/lib/async-runs.js +173 -111
  12. package/dist/lib/automatic-review-runtime.d.ts +1 -1
  13. package/dist/lib/automatic-review-runtime.js +5 -5
  14. package/dist/lib/command-templates.d.ts +2 -0
  15. package/dist/lib/command-templates.js +38 -4
  16. package/dist/lib/control-projection.d.ts +20 -0
  17. package/dist/lib/control-projection.js +66 -0
  18. package/dist/lib/control.d.ts +15 -0
  19. package/dist/lib/control.js +97 -0
  20. package/dist/lib/draft-sleep.js +3 -3
  21. package/dist/lib/execution-sessions.d.ts +17 -0
  22. package/dist/lib/execution-sessions.js +85 -0
  23. package/dist/lib/file-state.d.ts +2 -0
  24. package/dist/lib/file-state.js +114 -46
  25. package/dist/lib/inspector-actions.d.ts +2 -2
  26. package/dist/lib/inspector-actions.js +2 -2
  27. package/dist/lib/inspector-command.js +3 -3
  28. package/dist/lib/inspector-overlay.d.ts +54 -70
  29. package/dist/lib/inspector-overlay.js +576 -910
  30. package/dist/lib/inspector.d.ts +3 -71
  31. package/dist/lib/inspector.js +19 -665
  32. package/dist/lib/limits.d.ts +7 -3
  33. package/dist/lib/limits.js +7 -3
  34. package/dist/lib/observability.d.ts +16 -16
  35. package/dist/lib/observability.js +43 -82
  36. package/dist/lib/prompts.d.ts +1 -1
  37. package/dist/lib/prompts.js +3 -3
  38. package/dist/lib/recipe-control.d.ts +7 -0
  39. package/dist/lib/recipe-control.js +43 -0
  40. package/dist/lib/recipes-discovery.js +2 -0
  41. package/dist/lib/recipes-references.d.ts +1 -14
  42. package/dist/lib/recipes-references.js +6 -21
  43. package/dist/lib/review-control.d.ts +1 -1
  44. package/dist/lib/review-control.js +4 -5
  45. package/dist/lib/review-projection.js +1 -5
  46. package/dist/lib/run-ui-runtime.js +2 -2
  47. package/dist/lib/runs-control-delivery.d.ts +28 -0
  48. package/dist/lib/runs-control-delivery.js +150 -0
  49. package/dist/lib/runs-controls.d.ts +37 -0
  50. package/dist/lib/runs-controls.js +146 -0
  51. package/dist/lib/runs-retention.d.ts +7 -0
  52. package/dist/lib/runs-retention.js +27 -3
  53. package/dist/lib/runs-start.js +4 -2
  54. package/dist/lib/runs-status.js +11 -6
  55. package/dist/lib/runs-trace.d.ts +24 -0
  56. package/dist/lib/runs-trace.js +102 -0
  57. package/dist/lib/runtime-identity.d.ts +7 -0
  58. package/dist/lib/runtime-identity.js +35 -0
  59. package/dist/lib/runtime-notifier.d.ts +1 -1
  60. package/dist/lib/runtime-notifier.js +1 -1
  61. package/dist/lib/runtime-triage.d.ts +29 -0
  62. package/dist/lib/runtime-triage.js +76 -0
  63. package/dist/lib/tool-review-scheduler.js +7 -7
  64. package/dist/lib/tools-inspect.d.ts +3 -3
  65. package/dist/lib/tools-inspect.js +241 -707
  66. package/dist/lib/tools-local.js +2 -10
  67. package/dist/lib/tools-message.d.ts +6 -7
  68. package/dist/lib/tools-message.js +95 -396
  69. package/dist/lib/tools-response.d.ts +1 -5
  70. package/dist/lib/tools-response.js +5 -48
  71. package/dist/lib/tools-spawn.js +16 -28
  72. package/dist/lib/tools.d.ts +1 -1
  73. package/dist/lib/tools.js +2 -3
  74. package/dist/lib/trace-projection.d.ts +22 -0
  75. package/dist/lib/trace-projection.js +185 -0
  76. package/dist/recipes/draft-review.json +0 -10
  77. package/dist/recipes/lens-swarm.json +0 -14
  78. package/dist/recipes/music-player.json +10 -19
  79. package/dist/recipes/pipeline-architect-coordinator.json +0 -11
  80. package/dist/recipes/pipeline-artifact-bundle.json +1 -22
  81. package/dist/recipes/pipeline-artifact-report.json +1 -18
  82. package/dist/recipes/pipeline-artifact-write.json +1 -18
  83. package/dist/recipes/pipeline-async-run-ops.json +0 -12
  84. package/dist/recipes/pipeline-checkpoint-continuation.json +0 -14
  85. package/dist/recipes/pipeline-development-tasking.json +0 -12
  86. package/dist/recipes/pipeline-docs-maintenance.json +0 -12
  87. package/dist/recipes/pipeline-media-library.json +0 -12
  88. package/dist/recipes/pipeline-quorum-review.json +0 -12
  89. package/dist/recipes/pipeline-release-readiness.json +0 -12
  90. package/dist/recipes/pipeline-release-summary.json +0 -12
  91. package/dist/recipes/pipeline-repo-health.json +0 -12
  92. package/dist/recipes/pipeline-research-synthesis.json +0 -11
  93. package/dist/recipes/pipeline-review-readiness.json +0 -12
  94. package/dist/recipes/resource-locker.json +27 -0
  95. package/dist/recipes/subagent-artifact.json +0 -9
  96. package/dist/recipes/subagent-checkpoint.json +0 -10
  97. package/dist/recipes/subagent-conflict-report.json +0 -11
  98. package/dist/recipes/subagent-contradiction-map.json +0 -11
  99. package/dist/recipes/subagent-critic.json +0 -11
  100. package/dist/recipes/subagent-evidence-map.json +0 -11
  101. package/dist/recipes/subagent-followup.json +0 -10
  102. package/dist/recipes/subagent-judge.json +0 -11
  103. package/dist/recipes/subagent-merge.json +0 -11
  104. package/dist/recipes/subagent-normalize.json +0 -11
  105. package/dist/recipes/subagent-plan.json +0 -11
  106. package/dist/recipes/subagent-preflight.json +0 -11
  107. package/dist/recipes/subagent-prompt.json +0 -10
  108. package/dist/recipes/subagent-quorum.json +0 -10
  109. package/dist/recipes/subagent-review-coordinator.json +0 -14
  110. package/dist/recipes/subagent-review.json +0 -11
  111. package/dist/recipes/subagent-task-card.json +0 -11
  112. package/dist/recipes/subagent-tools.json +0 -10
  113. package/dist/recipes/subagent-verify.json +0 -11
  114. package/dist/recipes/subagents-prompts.json +0 -10
  115. package/dist/recipes/tool-review.json +0 -10
  116. package/dist/scripts/async-runner.mjs +25 -25
  117. package/dist/scripts/conformance.mjs +4 -2
  118. package/dist/scripts/locker.mjs +196 -69
  119. package/dist/scripts/music-player.mjs +162 -159
  120. package/dist/scripts/recipe-utils.mjs +6 -96
  121. package/dist/scripts/release-gates.mjs +91 -0
  122. package/dist/scripts/validate-recipe.mjs +8 -57
  123. package/dist/skills/actors/SKILL.md +57 -265
  124. package/dist/skills/swarm/SKILL.md +10 -34
  125. package/docs/0.43-baseline.md +39 -0
  126. package/docs/README.md +4 -6
  127. package/docs/actor-inspector.md +26 -64
  128. package/docs/async-runs.md +81 -328
  129. package/docs/command-templates.md +8 -118
  130. package/docs/recipe-library.md +55 -182
  131. package/docs/releasing.md +28 -0
  132. package/docs/template-recipes.md +76 -289
  133. package/docs/tool-registry.md +41 -161
  134. package/fixtures/protocol/control-endpoint.json +6 -0
  135. package/fixtures/protocol/control-record.json +9 -0
  136. package/fixtures/protocol/recipe-summary.json +4 -12
  137. package/fixtures/protocol/trace-event.json +9 -0
  138. package/index.ts +1 -1
  139. package/lib/async-runs.ts +218 -204
  140. package/lib/automatic-review-runtime.ts +7 -7
  141. package/lib/command-templates.ts +44 -4
  142. package/lib/control-projection.ts +105 -0
  143. package/lib/control.ts +117 -0
  144. package/lib/draft-sleep.ts +3 -3
  145. package/lib/execution-sessions.ts +111 -0
  146. package/lib/file-state.ts +84 -64
  147. package/lib/inspector-actions.ts +2 -2
  148. package/lib/inspector-command.ts +3 -3
  149. package/lib/inspector-overlay.ts +617 -1126
  150. package/lib/inspector.ts +46 -979
  151. package/lib/limits.ts +7 -3
  152. package/lib/observability.ts +60 -101
  153. package/lib/prompts.ts +3 -3
  154. package/lib/recipe-control.ts +52 -0
  155. package/lib/recipes-discovery.ts +2 -0
  156. package/lib/recipes-references.ts +9 -45
  157. package/lib/review-control.ts +4 -5
  158. package/lib/review-projection.ts +1 -5
  159. package/lib/run-ui-runtime.ts +2 -2
  160. package/lib/runs-control-delivery.ts +209 -0
  161. package/lib/runs-controls.ts +213 -0
  162. package/lib/runs-retention.ts +38 -3
  163. package/lib/runs-start.ts +4 -2
  164. package/lib/runs-status.ts +11 -6
  165. package/lib/runs-trace.ts +136 -0
  166. package/lib/runtime-identity.ts +39 -0
  167. package/lib/runtime-notifier.ts +1 -1
  168. package/lib/runtime-triage.ts +120 -0
  169. package/lib/tool-review-scheduler.ts +7 -7
  170. package/lib/tools-inspect.ts +283 -900
  171. package/lib/tools-local.ts +2 -12
  172. package/lib/tools-message.ts +112 -520
  173. package/lib/tools-response.ts +5 -64
  174. package/lib/tools-spawn.ts +16 -32
  175. package/lib/tools.ts +5 -6
  176. package/lib/trace-projection.ts +244 -0
  177. package/package.json +2 -1
  178. package/recipes/draft-review.json +0 -10
  179. package/recipes/lens-swarm.json +0 -14
  180. package/recipes/music-player.json +10 -19
  181. package/recipes/pipeline-architect-coordinator.json +0 -11
  182. package/recipes/pipeline-artifact-bundle.json +1 -22
  183. package/recipes/pipeline-artifact-report.json +1 -18
  184. package/recipes/pipeline-artifact-write.json +1 -18
  185. package/recipes/pipeline-async-run-ops.json +0 -12
  186. package/recipes/pipeline-checkpoint-continuation.json +0 -14
  187. package/recipes/pipeline-development-tasking.json +0 -12
  188. package/recipes/pipeline-docs-maintenance.json +0 -12
  189. package/recipes/pipeline-media-library.json +0 -12
  190. package/recipes/pipeline-quorum-review.json +0 -12
  191. package/recipes/pipeline-release-readiness.json +0 -12
  192. package/recipes/pipeline-release-summary.json +0 -12
  193. package/recipes/pipeline-repo-health.json +0 -12
  194. package/recipes/pipeline-research-synthesis.json +0 -11
  195. package/recipes/pipeline-review-readiness.json +0 -12
  196. package/recipes/resource-locker.json +27 -0
  197. package/recipes/subagent-artifact.json +0 -9
  198. package/recipes/subagent-checkpoint.json +0 -10
  199. package/recipes/subagent-conflict-report.json +0 -11
  200. package/recipes/subagent-contradiction-map.json +0 -11
  201. package/recipes/subagent-critic.json +0 -11
  202. package/recipes/subagent-evidence-map.json +0 -11
  203. package/recipes/subagent-followup.json +0 -10
  204. package/recipes/subagent-judge.json +0 -11
  205. package/recipes/subagent-merge.json +0 -11
  206. package/recipes/subagent-normalize.json +0 -11
  207. package/recipes/subagent-plan.json +0 -11
  208. package/recipes/subagent-preflight.json +0 -11
  209. package/recipes/subagent-prompt.json +0 -10
  210. package/recipes/subagent-quorum.json +0 -10
  211. package/recipes/subagent-review-coordinator.json +0 -14
  212. package/recipes/subagent-review.json +0 -11
  213. package/recipes/subagent-task-card.json +0 -11
  214. package/recipes/subagent-tools.json +0 -10
  215. package/recipes/subagent-verify.json +0 -11
  216. package/recipes/subagents-prompts.json +0 -10
  217. package/recipes/tool-review.json +0 -10
  218. package/scripts/async-runner.mjs +25 -25
  219. package/scripts/conformance.mjs +4 -2
  220. package/scripts/locker.mjs +196 -69
  221. package/scripts/music-player.mjs +162 -159
  222. package/scripts/recipe-utils.mjs +6 -96
  223. package/scripts/release-gates.mjs +91 -0
  224. package/scripts/validate-recipe.mjs +8 -57
  225. package/skills/actors/SKILL.md +57 -265
  226. package/skills/swarm/SKILL.md +10 -34
  227. package/dist/fixtures/protocol/actor-message-branch.json +0 -13
  228. package/dist/fixtures/protocol/mailbox-contract.json +0 -15
  229. package/dist/fixtures/protocol/room-message.json +0 -11
  230. package/dist/fixtures/protocol/room-roster.json +0 -11
  231. package/dist/fixtures/protocol/run-inbox-message.json +0 -9
  232. package/dist/fixtures/protocol/run-outbox-event.json +0 -9
  233. package/dist/lib/mailbox-loop.d.ts +0 -41
  234. package/dist/lib/mailbox-loop.js +0 -60
  235. package/dist/lib/messages.d.ts +0 -25
  236. package/dist/lib/messages.js +0 -122
  237. package/dist/lib/rooms.d.ts +0 -104
  238. package/dist/lib/rooms.js +0 -647
  239. package/dist/lib/runs-mailbox.d.ts +0 -25
  240. package/dist/lib/runs-mailbox.js +0 -146
  241. package/dist/lib/runs-messages.d.ts +0 -15
  242. package/dist/lib/runs-messages.js +0 -179
  243. package/dist/lib/runs-outbox.d.ts +0 -41
  244. package/dist/lib/runs-outbox.js +0 -87
  245. package/dist/lib/tools-mailbox.d.ts +0 -8
  246. package/dist/lib/tools-mailbox.js +0 -48
  247. package/dist/recipes/actor-worker.json +0 -39
  248. package/dist/recipes/coordinator-locker.json +0 -45
  249. package/dist/recipes/locker.json +0 -45
  250. package/dist/recipes/pipeline-room-swarm.json +0 -50
  251. package/dist/recipes/subagent-message.json +0 -32
  252. package/dist/recipes/utility-actor-message.json +0 -23
  253. package/dist/scripts/actor-worker.mjs +0 -214
  254. package/dist/scripts/coordinator.mjs +0 -799
  255. package/docs/actor-messages.md +0 -225
  256. package/docs/actors-deep-reference.md +0 -66
  257. package/docs/component-recipes.md +0 -148
  258. package/docs/task-first-recipes.md +0 -263
  259. package/fixtures/protocol/actor-message-branch.json +0 -13
  260. package/fixtures/protocol/mailbox-contract.json +0 -15
  261. package/fixtures/protocol/room-message.json +0 -11
  262. package/fixtures/protocol/room-roster.json +0 -11
  263. package/fixtures/protocol/run-inbox-message.json +0 -9
  264. package/fixtures/protocol/run-outbox-event.json +0 -9
  265. package/lib/mailbox-loop.ts +0 -144
  266. package/lib/messages.ts +0 -151
  267. package/lib/rooms.ts +0 -939
  268. package/lib/runs-mailbox.ts +0 -208
  269. package/lib/runs-messages.ts +0 -252
  270. package/lib/runs-outbox.ts +0 -144
  271. package/lib/tools-mailbox.ts +0 -56
  272. package/recipes/actor-worker.json +0 -39
  273. package/recipes/coordinator-locker.json +0 -45
  274. package/recipes/locker.json +0 -45
  275. package/recipes/pipeline-room-swarm.json +0 -50
  276. package/recipes/subagent-message.json +0 -32
  277. package/recipes/utility-actor-message.json +0 -23
  278. package/scripts/actor-worker.mjs +0 -214
  279. package/scripts/coordinator.mjs +0 -799
@@ -1,489 +1,311 @@
1
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
2
+ * Public inspect tool behavior.
3
+ * Zones: Run Recipe/Trace/Control views and runtime/recipe/tool diagnostics
4
+ * Owns exact inspect target/view dispatch; source projection stays in domain modules.
5
5
  */
6
6
 
7
- import { execFileSync } from "node:child_process";
8
- import { existsSync, readFileSync } from "node:fs";
9
- import { dirname, join } from "node:path";
10
- import { fileURLToPath } from "node:url";
7
+ import { join } from "node:path";
11
8
 
12
9
  import * as AsyncRuns from "./async-runs.ts";
10
+ import * as ControlProjection from "./control-projection.ts";
11
+ import * as Inspector from "./inspector.ts";
13
12
  import * as Limits from "./limits.ts";
14
- import * as Messages from "./messages.ts";
15
13
  import * as Paths from "./paths.ts";
16
14
  import * as RecipesDiscovery from "./recipes-discovery.ts";
17
15
  import * as ReviewDiagnostics from "./review-diagnostics.ts";
18
- import * as Rooms from "./rooms.ts";
16
+ import * as RunsControls from "./runs-controls.ts";
17
+ import * as RunControlDelivery from "./runs-control-delivery.ts";
18
+ import * as RunsTrace from "./runs-trace.ts";
19
+ import * as RuntimeIdentity from "./runtime-identity.ts";
20
+ import * as RuntimeTriage from "./runtime-triage.ts";
19
21
  import * as Schema from "./schema.ts";
20
22
  import * as ToolsAccess from "./tools-access.ts";
21
- import * as ToolsMailbox from "./tools-mailbox.ts";
22
23
  import * as ToolsResponse from "./tools-response.ts";
24
+ import * as TraceProjection from "./trace-projection.ts";
23
25
 
24
26
  const asRecord = ToolsResponse.asRecord;
25
27
  const maybeJsonText = ToolsResponse.maybeJsonText;
26
28
 
27
- function compactAutomaticReviewDiagnostics(
28
- diagnostics: Record<string, unknown>,
29
- ): string {
30
- const draft = asRecord(diagnostics.draft_review);
31
- const tool = asRecord(diagnostics.tool_review);
32
- return [
33
- `automatic_reviews draft=${String(draft.phase ?? "idle")} tool=${String(tool.phase ?? "idle")}`,
34
- `lineages=${String(diagnostics.lineage_count ?? 0)} revision_snapshots=${String(diagnostics.revision_snapshots ?? 0)}`,
35
- ].join("\n");
36
- }
37
-
38
- function compactRunMessages(messages: AsyncRuns.RunOutboxEvent[]): string {
39
- if (messages.length === 0) return "\n(no actor messages)";
40
- return `\n${messages
41
- .map((message) =>
42
- [
43
- `run=${message.run}`,
44
- `type=${message.event}`,
45
- `level=${message.level}`,
46
- `summary=${message.summary.replaceAll(/\s+/g, "_")}`,
47
- ].join(" "),
48
- )
49
- .join("\n")}`;
50
- }
51
-
52
- function compactRoomPreviews(previews: Rooms.RoomMessagePreview[]): string {
53
- if (previews.length === 0) return "\n(no room message previews)";
54
- return `\n${previews
55
- .map((preview) =>
56
- [
57
- `ts=${preview.timestamp}`,
58
- preview.from ? `from=${preview.from}` : "",
59
- `to=${preview.to}`,
60
- `type=${preview.type}`,
61
- preview.summary
62
- ? `summary=${ToolsResponse.compactPreview(preview.summary)}`
63
- : "",
64
- preview.body_preview
65
- ? `body=${ToolsResponse.compactPreview(preview.body_preview)}`
66
- : "",
67
- ]
68
- .filter(Boolean)
69
- .join(" "),
70
- )
71
- .join("\n")}`;
72
- }
73
-
74
- function compactRoomMessages(messages: Rooms.RoomTimelineEntry[]): string {
75
- if (messages.length === 0) return "\n(no room messages)";
76
- return `\n${messages
77
- .map((message) =>
78
- [
79
- `ts=${message.received_at}`,
80
- `from=${String(message.from ?? "<unknown>")}`,
81
- `to=${message.to}`,
82
- `type=${message.type}`,
83
- `summary=${String(message.summary ?? "").replaceAll(/\s+/g, "_")}`,
84
- ToolsResponse.compactPreview(message.body)
85
- ? `body=${ToolsResponse.compactPreview(message.body)}`
86
- : "",
87
- ]
88
- .filter(Boolean)
89
- .join(" "),
90
- )
91
- .join("\n")}`;
92
- }
93
-
94
- function compactRoomContacts(contacts: Rooms.RoomContact[]): string {
95
- if (contacts.length === 0) return "\n(no room contacts)";
96
- return `\n${contacts
97
- .map((contact) =>
98
- [
99
- `address=${contact.address}`,
100
- contact.role !== undefined ? `role=${String(contact.role)}` : "",
101
- contact.parent !== undefined ? `parent=${String(contact.parent)}` : "",
102
- contact.caps !== undefined
103
- ? `caps=${Array.isArray(contact.caps) ? contact.caps.join(",") : String(contact.caps)}`
104
- : "",
105
- contact.claim !== undefined
106
- ? `claim=${String(contact.claim).replaceAll(/\s+/g, "_")}`
107
- : "",
108
- contact.status !== undefined ? `status=${String(contact.status)}` : "",
109
- ]
110
- .filter(Boolean)
111
- .join(" "),
112
- )
113
- .join("\n")}`;
114
- }
115
-
116
- function compactRoomRoster(roster: Record<string, Rooms.RoomMember>): string {
117
- const members = Object.values(roster);
118
- if (members.length === 0) return "\n(no room members)";
119
- return `\n${members
120
- .map((member) =>
121
- [
122
- `address=${member.address}`,
123
- `role=${String(member.role ?? "")}`,
124
- member.parent !== undefined ? `parent=${String(member.parent)}` : "",
125
- member.caps !== undefined
126
- ? `caps=${Array.isArray(member.caps) ? member.caps.join(",") : String(member.caps)}`
127
- : "",
128
- member.claim !== undefined
129
- ? `claim=${String(member.claim).replaceAll(/\s+/g, "_")}`
130
- : "",
131
- `status=${String(member.status ?? "")}`,
132
- `last_seen=${member.last_seen}`,
133
- ].join(" "),
134
- )
135
- .join("\n")}`;
136
- }
137
-
138
- function compactRoomStatus(status: Rooms.RoomStatus): string {
139
- 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)}` : ""}`;
140
- }
141
-
142
- function compactCommunicationSnapshot(
143
- snapshot: Rooms.ActorCommunicationSnapshot | undefined,
144
- ): string {
145
- if (!snapshot) return "\n(no communication snapshot)";
146
- return `\nself=${snapshot.self} root=${snapshot.root} rooms=${snapshot.rooms.length} updated_at=${snapshot.updated_at}`;
147
- }
148
-
149
- function compactArtifactPath(value: unknown): string {
150
- if (typeof value === "string") return value;
151
- const record = asRecord(value);
152
- return String(record.path ?? "<missing>");
153
- }
154
-
155
- function readBoundedReviewEvidence(
156
- status: Record<string, unknown>,
157
- limit: number,
158
- ): Record<string, unknown> | undefined {
159
- const evidenceFile = status.evidenceFile;
160
- if (typeof evidenceFile !== "string" || !existsSync(evidenceFile))
161
- return undefined;
162
- try {
163
- const manifest = asRecord(JSON.parse(readFileSync(evidenceFile, "utf8")));
164
- const commands = Array.isArray(manifest.commands) ? manifest.commands : [];
165
- const bounded = commands.slice(-Math.max(1, limit));
166
- return {
167
- ...manifest,
168
- commands: bounded,
169
- commands_total: commands.length,
170
- ...(bounded.length < commands.length ? { commands_truncated: true } : {}),
171
- };
172
- } catch {
173
- return {
174
- status: "invalid",
175
- error: "review evidence manifest is unreadable",
176
- };
177
- }
178
- }
179
-
180
- function compactActorFiles(status: Record<string, unknown>): string {
181
- const run = String(status.run ?? "<unknown>");
182
- const artifacts = asRecord(status.artifacts);
183
- const files = [
184
- status.stdoutLog,
185
- status.stderrLog,
186
- status.eventsFile,
187
- status.outboxFile,
188
- status.evidenceFile,
189
- status.state_dir
190
- ? `${String(status.state_dir)}/communication.json`
191
- : undefined,
192
- status.state_dir ? `${String(status.state_dir)}/result.json` : undefined,
193
- ].filter((file): file is string => typeof file === "string");
194
- const artifactText = Object.keys(artifacts).length
195
- ? ` artifacts=${Object.entries(artifacts)
196
- .map(([key, value]) => `${key}:${compactArtifactPath(value)}`)
197
- .join(",")}`
198
- : "";
199
- const nextActions = ToolsResponse.artifactNextActions(run, artifacts);
200
- const nextText = nextActions.length
201
- ? ` next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`
202
- : "";
203
- return `\nrun=${run}${artifactText}${files.length ? ` files=${files.join(",")}` : ""}${nextText}`;
29
+ export interface InspectToolDeps<TContext = unknown> {
30
+ getRunStatus?: (runOrDir: string) => Record<string, any>;
31
+ getTool?: (name: string) => any | undefined;
32
+ listRuns?: () => Array<Record<string, any>>;
33
+ packagedRecipeRoot?: string;
34
+ recipeRoot?: string;
204
35
  }
205
36
 
206
- function summarizeOtherSessions(
207
- currentSession: string,
208
- allRuns: Array<Record<string, unknown>>,
209
- ): Record<string, unknown> {
210
- const otherRuns = allRuns.filter(
211
- (run) => run.ownerId && run.ownerId !== currentSession,
212
- );
37
+ function runtimeStatus(): Record<string, unknown> {
213
38
  return {
214
- other_runs: otherRuns.length,
215
- other_sessions: new Set(otherRuns.map((run) => run.ownerId)).size,
39
+ automatic_review: process.env.PI_ACTORS_AUTOMATIC_REVIEW !== "off",
40
+ run_root: Paths.getRunStateRoot(),
41
+ state_schema: RuntimeIdentity.RUN_STATE_SCHEMA,
42
+ version: RuntimeIdentity.getPackageVersion(),
216
43
  };
217
44
  }
218
45
 
219
- function compactSessionRuns(
220
- session: string,
221
- runs: Array<Record<string, unknown>>,
222
- summary: Record<string, unknown> = {},
223
- ): string {
224
- const suffix = [
225
- summary.other_sessions !== undefined
226
- ? `other_sessions=${String(summary.other_sessions)}`
227
- : "",
228
- summary.other_runs !== undefined
229
- ? `other_runs=${String(summary.other_runs)}`
230
- : "",
231
- ]
232
- .filter(Boolean)
233
- .join(" ");
234
- if (runs.length === 0)
235
- return `\nsession=${session} runs=0${suffix ? ` ${suffix}` : ""}`;
236
- return `\nsession=${session} runs=${runs.length}${suffix ? ` ${suffix}` : ""}\n${runs
46
+ function runtimeRuns(
47
+ ctx: unknown,
48
+ deps: InspectToolDeps,
49
+ status: string | undefined,
50
+ ): Record<string, unknown> {
51
+ const session = ToolsAccess.getContextSessionId(ctx);
52
+ const listed = deps.listRuns
53
+ ? deps.listRuns()
54
+ : AsyncRuns.listRuns(undefined, status);
55
+ const runs = listed
237
56
  .map((run) => {
238
- const tokens = [
239
- `run=${String(run.run ?? "")}`,
240
- `status=${String(run.status ?? "")}`,
241
- ];
242
- if (run.recipe) tokens.push(`recipe=${String(run.recipe)}`);
243
- if (run.retire_when)
244
- tokens.push(`retire_when=${String(run.retire_when)}`);
245
- return tokens.join(" ");
57
+ try {
58
+ return deps.getRunStatus
59
+ ? deps.getRunStatus(String(run.state_dir ?? run.run))
60
+ : AsyncRuns.getRunStatus(String(run.state_dir ?? run.run));
61
+ } catch {
62
+ return run;
63
+ }
246
64
  })
247
- .join("\n")}`;
65
+ .filter((run) => !session || !run.ownerId || run.ownerId === session);
66
+ return { ...(session ? { owner_session: session } : {}), runs };
248
67
  }
249
68
 
250
- function getPiActorsRuntimeStatus(): Record<string, unknown> {
251
- const packagedRecipeRoot = Paths.getPackagedRecipeRoot();
252
- const packageRoot = dirname(packagedRecipeRoot);
253
- const packageJsonPath = join(packageRoot, "package.json");
254
- const packageJson = existsSync(packageJsonPath)
255
- ? (JSON.parse(readFileSync(packageJsonPath, "utf8")) as Record<
256
- string,
257
- unknown
258
- >)
259
- : {};
260
- let git_commit: string | undefined;
261
- try {
262
- git_commit = execFileSync(
263
- "git",
264
- ["-C", packageRoot, "rev-parse", "--short", "HEAD"],
265
- {
266
- encoding: "utf8",
267
- stdio: ["ignore", "pipe", "ignore"],
268
- },
269
- ).trim();
270
- } catch {
271
- git_commit = undefined;
69
+ function runtimeTriage(
70
+ ctx: unknown,
71
+ deps: InspectToolDeps,
72
+ ): Record<string, unknown> {
73
+ const inventory = runtimeRuns(ctx, deps, undefined);
74
+ const runs = inventory.runs as Array<Record<string, unknown>>;
75
+ const failed = runs.filter((run) => run.status === "failed");
76
+ const pendingControls: RuntimeTriage.RuntimePendingControl[] = [];
77
+ const staleControls: RuntimeTriage.RuntimePendingControl[] = [];
78
+ const controlDiagnostics: Array<Record<string, unknown>> = [];
79
+ const nowMs = Date.now();
80
+ for (const run of runs) {
81
+ const stateDir = typeof run.state_dir === "string" ? run.state_dir : undefined;
82
+ if (!stateDir) continue;
83
+ const runId = String(run.run ?? "unknown");
84
+ const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
85
+ controlDiagnostics.push(...journal.diagnostics.map((diagnostic) => ({
86
+ ...(diagnostic.line === undefined ? {} : { line: diagnostic.line }),
87
+ reason: diagnostic.line === undefined
88
+ ? "unreadable_control_journal"
89
+ : "invalid_control_json",
90
+ run: runId,
91
+ })));
92
+ for (const control of journal.records) {
93
+ const classified = RuntimeTriage.classifyRuntimeControl(
94
+ {
95
+ run: runId,
96
+ ...(typeof run.run_instance_id === "string"
97
+ ? { runInstanceId: run.run_instance_id }
98
+ : {}),
99
+ status: String(run.status ?? "unknown"),
100
+ },
101
+ control,
102
+ nowMs,
103
+ );
104
+ if (classified.pending) pendingControls.push(classified.pending);
105
+ if (classified.stale) staleControls.push(classified.stale);
106
+ if (classified.diagnostic) controlDiagnostics.push({ ...classified.diagnostic });
107
+ }
272
108
  }
273
- const entrypoint = fileURLToPath(import.meta.url);
109
+ const attentionEvents = runs.flatMap((run) => {
110
+ const stateDir = typeof run.state_dir === "string" ? run.state_dir : undefined;
111
+ if (!stateDir) return [];
112
+ return RunsTrace.readRunTraceEvents(stateDir)
113
+ .filter((event) => event.attention === "notify" || event.attention === "followup")
114
+ .map((event) => ({
115
+ run: run.run,
116
+ id: event.id,
117
+ kind: event.kind,
118
+ attention: event.attention,
119
+ summary: event.summary,
120
+ ts: event.ts,
121
+ }));
122
+ });
123
+ const boundedStaleControls = staleControls.slice(0, 40);
124
+ const staleKeys = new Set(boundedStaleControls.map((control) =>
125
+ `${control.run}\0${control.run_instance_id}\0${control.id}`,
126
+ ));
127
+ const boundedPendingControls = [
128
+ ...boundedStaleControls,
129
+ ...pendingControls.filter((control) =>
130
+ !staleKeys.has(`${control.run}\0${control.run_instance_id}\0${control.id}`),
131
+ ),
132
+ ].slice(0, 40);
274
133
  return {
275
- automatic_recipe_review: Paths.isAutomaticRecipeReviewEnabled(),
276
- entrypoint,
277
- git_commit,
278
- mode: entrypoint.includes("/dist/") ? "dist" : "source",
279
- package_name: packageJson.name ?? "@llblab/pi-actors",
280
- package_root: packageRoot,
281
- recipe_root: Paths.getRecipeRoot(),
282
- packaged_recipe_root: packagedRecipeRoot,
283
- version: packageJson.version ?? "unknown",
134
+ ...inventory,
135
+ failed_runs: failed,
136
+ pending_control_count: pendingControls.length,
137
+ pending_controls: boundedPendingControls,
138
+ stale_control_count: staleControls.length,
139
+ stale_controls: boundedStaleControls,
140
+ control_diagnostic_count: controlDiagnostics.length,
141
+ control_diagnostics: controlDiagnostics.slice(0, 40),
142
+ attention_events: attentionEvents.slice(-40).reverse(),
143
+ next_actions: [
144
+ ...(failed.length ? ["inspect target=runtime view=runs status=failed"] : []),
145
+ ...staleControls.slice(0, 3).map((entry) =>
146
+ `inspect target=run:${entry.run} view=control`,
147
+ ),
148
+ ...(attentionEvents.length
149
+ ? attentionEvents.slice(-3).map((entry) =>
150
+ `inspect target=run:${String(entry.run)} view=trace source=runtime`,
151
+ )
152
+ : []),
153
+ ],
284
154
  };
285
155
  }
286
156
 
287
- function compactPiActorsRuntimeStatus(status: Record<string, unknown>): string {
288
- return `\npi-actors version=${String(status.version)} mode=${String(status.mode)} automatic_review=${String(status.automatic_recipe_review)} path=${String(status.package_root)} entrypoint=${String(status.entrypoint)}${status.git_commit ? ` git=${String(status.git_commit)}` : ""}`;
289
- }
290
-
291
- function isStaleClaim(message: AsyncRuns.RunInboxMessage, now: number): boolean {
292
- if (message.status !== "claimed") return false;
293
- const claimedAt = Date.parse(String(message.claimed_at ?? ""));
294
- return Number.isFinite(claimedAt) && now - claimedAt > 5 * 60 * 1000;
157
+ function compactRuntime(view: string, details: Record<string, unknown>): string {
158
+ if (view === "runs") {
159
+ return `\nruntime runs=${(details.runs as unknown[] | undefined)?.length ?? 0}`;
160
+ }
161
+ if (view === "triage") {
162
+ return `\nruntime failed=${(details.failed_runs as unknown[] | undefined)?.length ?? 0} pending_controls=${Number(details.pending_control_count ?? 0)} stale_controls=${Number(details.stale_control_count ?? 0)} attention=${(details.attention_events as unknown[] | undefined)?.length ?? 0}`;
163
+ }
164
+ return `\nruntime version=${String(details.version)} state_schema=${String(details.state_schema)} automatic_review=${String(details.automatic_review)}`;
295
165
  }
296
166
 
297
- function getRunTriageSignals(
298
- runs: Array<Record<string, unknown>>,
167
+ function inspectRuntime(
168
+ view: string,
169
+ input: Record<string, unknown>,
170
+ ctx: unknown,
171
+ deps: InspectToolDeps,
299
172
  ): Record<string, unknown> {
300
- const now = Date.now();
301
- const staleClaims: Array<Record<string, unknown>> = [];
302
- const attentionMessages: Array<Record<string, unknown>> = [];
303
- for (const run of runs) {
304
- const stateDir = String(run.state_dir ?? "");
305
- const runId = String(run.run ?? "");
306
- if (!stateDir) continue;
307
- try {
308
- for (const message of AsyncRuns.readRunInboxMessages(stateDir, 200)) {
309
- if (!isStaleClaim(message, now)) continue;
310
- staleClaims.push({
311
- run: runId,
312
- id: message.id,
313
- claimed_at: message.claimed_at,
314
- claimed_by: message.claimed_by,
315
- type: message.type,
316
- });
317
- }
318
- } catch {}
319
- try {
320
- for (const event of AsyncRuns.readRunEvents(stateDir, 80)) {
321
- if (event.metadata?.requires_response !== true) continue;
322
- attentionMessages.push({
323
- run: runId,
324
- id: event.id,
325
- summary: event.summary,
326
- type: event.type ?? event.event,
327
- });
328
- }
329
- } catch {}
173
+ if (view === "status") return runtimeStatus();
174
+ if (view === "runs") {
175
+ return runtimeRuns(
176
+ ctx,
177
+ deps,
178
+ typeof input.status === "string" ? input.status : undefined,
179
+ );
330
180
  }
331
- return { attention_messages: attentionMessages, stale_claims: staleClaims };
332
- }
333
-
334
- function isTriageHighRiskRecipe(recipe: Record<string, unknown>): boolean {
335
- if (recipe.tool !== true) return false;
336
- const labels = Array.isArray(recipe.risk_labels)
337
- ? recipe.risk_labels.map((label) => String(label))
338
- : [];
339
- return labels.some((label) => label !== "risk.long_running");
181
+ if (view === "triage") return runtimeTriage(ctx, deps);
182
+ throw new Error("inspect runtime supports view=status, view=runs, or view=triage.");
340
183
  }
341
184
 
342
- function getPiActorsTriage(
343
- ctx: unknown,
185
+ function inspectRecipes(
186
+ view: string,
344
187
  deps: InspectToolDeps,
345
188
  ): Record<string, unknown> {
346
- const runtime = getPiActorsRuntimeStatus();
347
- const currentSession = ToolsAccess.getContextSessionId(ctx);
348
- const listRuns = deps.listRuns ?? AsyncRuns.listRuns;
349
- const getRunStatus = deps.getRunStatus ?? AsyncRuns.getRunStatus;
350
- const allRuns = listRuns().flatMap((run) => {
351
- try {
352
- return [getRunStatus(String(run.state_dir))];
353
- } catch {
354
- return [];
355
- }
356
- });
357
- const visibleRuns = currentSession
358
- ? allRuns.filter(
359
- (run) => !run.ownerId || run.ownerId === currentSession,
360
- )
361
- : allRuns;
362
- const activeRuns = visibleRuns.filter((run) => run.status === "running");
363
- const failedRuns = visibleRuns.filter((run) => run.status === "failed");
364
- const otherRuns = currentSession
365
- ? allRuns.filter((run) => run.ownerId && run.ownerId !== currentSession)
366
- : [];
189
+ if (
190
+ view !== "status" &&
191
+ view !== "summary" &&
192
+ view !== "doctor" &&
193
+ view !== "imports" &&
194
+ view !== "reviews"
195
+ ) {
196
+ throw new Error(
197
+ "inspect recipes supports view=status, view=summary, view=doctor, view=imports, or view=reviews.",
198
+ );
199
+ }
367
200
  const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
201
+ if (view === "reviews") {
202
+ return ReviewDiagnostics.readAutomaticReviewDiagnostics({ recipeRoot });
203
+ }
368
204
  const discovered = RecipesDiscovery.discoverRecipeSources([
369
205
  { root: recipeRoot, defaultTool: true, mutableUsage: true },
370
206
  { root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
371
207
  ]);
372
- const recipeSummary: Record<string, unknown> = {
208
+ const summary = {
373
209
  ...RecipesDiscovery.summarizeDiscovery(discovered),
374
210
  drafts: RecipesDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
375
211
  };
376
- const activeRecipes = Array.isArray(recipeSummary.active)
377
- ? (recipeSummary.active as Array<Record<string, unknown>>)
378
- : [];
379
- const highRiskRecipes = activeRecipes.filter(isTriageHighRiskRecipe);
380
- const signals = getRunTriageSignals(visibleRuns);
381
- const attentionMessages = signals.attention_messages as Array<
382
- Record<string, unknown>
383
- >;
384
- const staleClaims = signals.stale_claims as Array<Record<string, unknown>>;
385
- const invalidRecipes = Array.isArray(recipeSummary.invalid)
386
- ? (recipeSummary.invalid as Array<Record<string, unknown>>)
387
- : [];
388
- const remediations = Array.isArray(recipeSummary.remediations)
389
- ? (recipeSummary.remediations as Array<Record<string, unknown>>)
390
- : [];
391
- const drafts = Array.isArray(recipeSummary.drafts)
392
- ? (recipeSummary.drafts as Array<Record<string, unknown>>)
393
- : [];
394
- const nextActions = [
395
- invalidRecipes.length || remediations.length
396
- ? "inspect target=recipes view=doctor"
397
- : "",
398
- drafts.length ? "inspect target=recipes view=summary verbose=true" : "",
399
- failedRuns[0]?.run
400
- ? `inspect target=run:${String(failedRuns[0].run)} view=tail lines=80`
401
- : "",
402
- attentionMessages[0]?.run
403
- ? `inspect target=run:${String(attentionMessages[0].run)} view=messages`
404
- : "",
405
- activeRuns.length
406
- ? "inspect target=session:all view=runs status=active"
407
- : "",
408
- ].filter(Boolean);
409
212
  return {
410
- runtime,
411
- current_session: currentSession ?? null,
412
- active_runs: activeRuns.map((run) => ({
413
- run: run.run,
414
- ownerId: run.ownerId,
415
- recipe: run.recipe,
416
- status: run.status,
417
- })),
418
- other_session_runs: otherRuns.length,
419
- invalid_recipes: invalidRecipes,
420
- blocking_recipes: remediations.filter((item) =>
421
- String(item.kind ?? "").startsWith("blocking_"),
422
- ),
423
- high_risk_recipes: highRiskRecipes.map((recipe) => ({
424
- id: recipe.id,
425
- path: recipe.path,
426
- risk_labels: recipe.risk_labels,
427
- })),
428
- draft_recipes: drafts,
429
- stale_claims: staleClaims,
430
- recent_failed_runs: failedRuns.slice(0, 5).map((run) => ({
431
- run: run.run,
432
- recipe: run.recipe,
433
- status: run.status,
434
- })),
435
- attention_messages: attentionMessages.slice(-10),
436
- next_actions: [...new Set(nextActions)].slice(0, 5),
213
+ ...summary,
214
+ next_actions: ToolsResponse.recipeRegistryNextActions(summary, view),
437
215
  };
438
216
  }
439
217
 
440
- function compactPiActorsTriage(summary: Record<string, unknown>): string {
441
- const runtime = asRecord(summary.runtime);
442
- const activeRuns = Array.isArray(summary.active_runs)
443
- ? summary.active_runs.length
444
- : 0;
445
- const invalidRecipes = Array.isArray(summary.invalid_recipes)
446
- ? summary.invalid_recipes.length
447
- : 0;
448
- const blockingRecipes = Array.isArray(summary.blocking_recipes)
449
- ? summary.blocking_recipes.length
450
- : 0;
451
- const highRiskRecipes = Array.isArray(summary.high_risk_recipes)
452
- ? summary.high_risk_recipes.length
453
- : 0;
454
- const drafts = Array.isArray(summary.draft_recipes)
455
- ? summary.draft_recipes.length
456
- : 0;
457
- const staleClaims = Array.isArray(summary.stale_claims)
458
- ? summary.stale_claims.length
459
- : 0;
460
- const failedRuns = Array.isArray(summary.recent_failed_runs)
461
- ? summary.recent_failed_runs.length
462
- : 0;
463
- const attention = Array.isArray(summary.attention_messages)
464
- ? summary.attention_messages.length
465
- : 0;
466
- const nextActions = Array.isArray(summary.next_actions)
467
- ? (summary.next_actions as string[])
468
- : [];
469
- return `\ntriage version=${String(runtime.version ?? "unknown")} mode=${String(runtime.mode ?? "unknown")} active_runs=${activeRuns} other_runs=${String(summary.other_session_runs ?? 0)} invalid_recipes=${invalidRecipes} blocking_recipes=${blockingRecipes} high_risk_recipes=${highRiskRecipes} drafts=${drafts} stale_claims=${staleClaims} failed_runs=${failedRuns} attention=${attention}${ToolsResponse.compactNextActions(nextActions)}`;
218
+ function parseRunTarget(target: string): string | undefined {
219
+ if (!target.startsWith("run:")) return undefined;
220
+ const run = target.slice(4);
221
+ return run && /^[A-Za-z0-9_.-]+$/.test(run) ? run : undefined;
222
+ }
223
+
224
+ function inspectRun(
225
+ run: string,
226
+ view: string,
227
+ input: Record<string, unknown>,
228
+ ctx: unknown,
229
+ deps: InspectToolDeps,
230
+ ): Record<string, unknown> {
231
+ if (view !== "recipe" && view !== "trace" && view !== "control") {
232
+ throw new Error("inspect run:<id> supports view=recipe, view=trace, or view=control.");
233
+ }
234
+ const status = deps.getRunStatus
235
+ ? deps.getRunStatus(run)
236
+ : ToolsAccess.assertRunAccessibleToContext(run, ctx);
237
+ const stateDir = String(status.state_dir);
238
+ if (view === "recipe") {
239
+ return Inspector.readActorInspectorRecipe(stateDir) as unknown as Record<string, unknown>;
240
+ }
241
+ if (view === "trace") {
242
+ const source = typeof input.source === "string" ? input.source : "all";
243
+ if (
244
+ source !== "all" &&
245
+ source !== "lifecycle" &&
246
+ source !== "control" &&
247
+ source !== "process" &&
248
+ source !== "agent" &&
249
+ source !== "artifact" &&
250
+ source !== "runtime"
251
+ ) {
252
+ throw new Error(`unsupported Trace source: ${source}`);
253
+ }
254
+ return {
255
+ run,
256
+ source,
257
+ items: TraceProjection.projectRunTrace(stateDir, {
258
+ artifacts: status.artifacts as
259
+ | Record<string, AsyncRuns.RunArtifactDeclaration>
260
+ | undefined,
261
+ limit: Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
262
+ source,
263
+ }),
264
+ };
265
+ }
266
+ const runInstanceId = String(status.run_instance_id ?? "");
267
+ return {
268
+ run,
269
+ run_instance_id: runInstanceId,
270
+ status: status.status,
271
+ actor_actions: Array.isArray(status.control) ? status.control : [],
272
+ runtime_actions: status.status === "running" ? ["kill"] : ["archive", "prune"],
273
+ endpoint: runInstanceId
274
+ ? RunControlDelivery.readRunControlEndpoint(stateDir, runInstanceId)
275
+ : undefined,
276
+ recent_controls: RunsControls.readRunControlsFromStateDir(stateDir)
277
+ .slice(-Math.max(1, Number(input.lines || 20)))
278
+ .reverse()
279
+ .map(ControlProjection.projectRunControl),
280
+ };
470
281
  }
471
282
 
472
- function compactToolActor(name: string, tool: Record<string, unknown>): string {
473
- const parameters = asRecord(tool.parameters);
474
- const required = Array.isArray(parameters.required)
475
- ? parameters.required.join(",")
476
- : "";
477
- const properties = asRecord(parameters.properties);
478
- return `\ntool=${name} description=${String(tool.description ?? "").replaceAll(/\s+/g, "_")} args=${Object.keys(properties).join(",")} required=${required}`;
283
+ function inspectTool(
284
+ name: string,
285
+ view: string,
286
+ deps: InspectToolDeps,
287
+ ): Record<string, unknown> {
288
+ if (!name || !/^[A-Za-z0-9_.-]+$/.test(name)) {
289
+ throw new Error(`invalid tool target: tool:${name}`);
290
+ }
291
+ if (view !== "status" && view !== "schema") {
292
+ throw new Error("inspect tool:<name> supports view=status or view=schema.");
293
+ }
294
+ const tool = deps.getTool?.(name);
295
+ if (!tool) throw new Error(`registered tool not found: ${name}`);
296
+ return {
297
+ name,
298
+ description: tool.description,
299
+ parameters: tool.parameters,
300
+ promptSnippet: tool.promptSnippet,
301
+ };
479
302
  }
480
303
 
481
- export interface InspectToolDeps<TContext = unknown> {
482
- getRunStatus?: (runOrDir: string) => Record<string, any>;
483
- getTool?: (name: string) => any | undefined;
484
- listRuns?: () => Array<Record<string, any>>;
485
- packagedRecipeRoot?: string;
486
- recipeRoot?: string;
304
+ function compactResult(target: string, view: string, details: Record<string, unknown>): string {
305
+ if (target === "runtime") return compactRuntime(view, details);
306
+ if (target === "recipes") return ToolsResponse.compactRecipeRegistry(details);
307
+ if (target.startsWith("tool:")) return `\ntool=${target.slice(5)} view=${view}`;
308
+ return `\nrun=${target.slice(4)} view=${view}`;
487
309
  }
488
310
 
489
311
  export function createInspectToolDefinition<TContext = unknown>(
@@ -493,23 +315,18 @@ export function createInspectToolDefinition<TContext = unknown>(
493
315
  name: "inspect",
494
316
  label: "Inspect",
495
317
  description:
496
- "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.",
318
+ "Inspect a Run's Recipe, Trace, or Control evidence, or runtime/recipe/tool diagnostics.",
497
319
  parameters: Schema.objectSchema(
498
320
  {
499
- lines: Schema.stringSchema(
500
- "Line count for tail/messages views. Default 40.",
501
- ),
502
- status: Schema.stringSchema(
503
- "Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited.",
504
- ),
505
- target: Schema.stringSchema(
506
- "Actor address to inspect, e.g. run:<id> or tool:<name>; advanced: branch:<run>/<branch>, room:<run>, coordinator, session:<id>, session:all.",
507
- ),
508
- verbose: Schema.booleanSchema(
509
- "Return full JSON instead of compact text where available.",
321
+ lines: Schema.stringSchema("Bounded item count for Trace or recent Controls."),
322
+ source: Schema.stringSchema(
323
+ "Optional Trace source: all, lifecycle, control, process, agent, artifact, or runtime.",
510
324
  ),
325
+ status: Schema.stringSchema("Optional runtime Run status filter."),
326
+ target: Schema.stringSchema("Target: run:<id>, runtime, recipes, or tool:<name>."),
327
+ verbose: Schema.booleanSchema("Return full JSON instead of compact text."),
511
328
  view: Schema.stringSchema(
512
- "Inspection view. Core run views: status, tail, messages, artifacts, files, mailbox. Advanced views include communication, roster, and contacts.",
329
+ "Run view recipe|trace|control, or target-specific management view.",
513
330
  ),
514
331
  },
515
332
  ["target", "view"],
@@ -524,467 +341,33 @@ export function createInspectToolDefinition<TContext = unknown>(
524
341
  const input = asRecord(params);
525
342
  const target = String(input.target ?? "");
526
343
  const view = String(input.view ?? "");
527
- if (target === "recipes" || target === "recipe-registry") {
528
- if (
529
- view !== "status" &&
530
- view !== "summary" &&
531
- view !== "doctor" &&
532
- view !== "imports" &&
533
- view !== "reviews"
534
- ) {
344
+ let details: Record<string, unknown>;
345
+ if (target === "runtime") {
346
+ details = inspectRuntime(view, input, ctx, deps);
347
+ } else if (target === "recipes") {
348
+ details = inspectRecipes(view, deps);
349
+ } else {
350
+ const run = parseRunTarget(target);
351
+ if (run) details = inspectRun(run, view, input, ctx, deps);
352
+ else if (target.startsWith("tool:")) {
353
+ details = inspectTool(target.slice(5), view, deps);
354
+ } else {
535
355
  throw new Error(
536
- "inspect recipes supports view=status, view=summary, view=doctor, view=imports, or view=reviews.",
356
+ `unsupported inspect target: ${target}; use run:<id>, runtime, recipes, or tool:<name>`,
537
357
  );
538
358
  }
539
- if (view === "reviews") {
540
- const diagnostics = ReviewDiagnostics.readAutomaticReviewDiagnostics({
541
- recipeRoot: deps.recipeRoot ?? Paths.getRecipeRoot(),
542
- });
543
- return {
544
- content: [{
545
- type: "text" as const,
546
- text: maybeJsonText(
547
- diagnostics,
548
- input.verbose === true,
549
- compactAutomaticReviewDiagnostics(diagnostics),
550
- ),
551
- }],
552
- details: diagnostics,
553
- };
554
- }
555
- const discovered = RecipesDiscovery.discoverRecipeSources([
556
- {
557
- root: deps.recipeRoot ?? Paths.getRecipeRoot(),
558
- defaultTool: true,
559
- mutableUsage: true,
560
- },
561
- { root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
562
- ]);
563
- const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
564
- const summaryBase = {
565
- ...RecipesDiscovery.summarizeDiscovery(discovered),
566
- drafts: RecipesDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
567
- };
568
- const summary = {
569
- ...summaryBase,
570
- next_actions: ToolsResponse.recipeRegistryNextActions(
571
- summaryBase,
572
- view,
573
- ),
574
- };
575
- return {
576
- content: [
577
- {
578
- type: "text" as const,
579
- text: maybeJsonText(
580
- summary,
581
- input.verbose === true,
582
- view === "doctor"
583
- ? ToolsResponse.compactRecipeDoctor(summary)
584
- : view === "imports"
585
- ? ToolsResponse.compactRecipeImports(summary)
586
- : ToolsResponse.compactRecipeRegistry(summary),
587
- ),
588
- },
589
- ],
590
- details: summary,
591
- };
592
359
  }
593
- const address = Messages.parseActorAddress(target);
594
- if (address.kind === "coordinator") {
595
- if (view !== "status" && view !== "runs") {
596
- throw new Error(
597
- "inspect coordinator supports view=status or view=runs.",
598
- );
599
- }
600
- const session = ToolsAccess.requireContextSessionId(
601
- ctx,
602
- "inspect coordinator",
603
- );
604
- const allRuns = AsyncRuns.listRuns(
605
- undefined,
606
- typeof input.status === "string" ? input.status : undefined,
607
- ).map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
608
- const runs = allRuns.filter((run) => run.ownerId === session);
609
- const sessionSummary = summarizeOtherSessions(session, allRuns);
610
- return {
611
- content: [
612
- {
613
- type: "text" as const,
614
- text: maybeJsonText(
615
- { session, runs, ...sessionSummary },
616
- input.verbose === true,
617
- compactSessionRuns(session, runs, sessionSummary),
618
- ),
619
- },
620
- ],
621
- details: { session, runs, ...sessionSummary },
622
- };
623
- }
624
- if (address.kind === "session") {
625
- if (view !== "status" && view !== "runs") {
626
- throw new Error(
627
- "inspect session:<id> supports view=status or view=runs.",
628
- );
629
- }
630
- const allRuns = AsyncRuns.listRuns(
631
- undefined,
632
- typeof input.status === "string" ? input.status : undefined,
633
- ).map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
634
- const runs = allRuns.filter(
635
- (run) => address.value === "all" || run.ownerId === address.value,
636
- );
637
- const sessionSummary =
638
- address.value === "all"
639
- ? {}
640
- : summarizeOtherSessions(address.value || "", allRuns);
641
- return {
642
- content: [
643
- {
644
- type: "text" as const,
645
- text: maybeJsonText(
646
- { session: address.value, runs, ...sessionSummary },
647
- input.verbose === true,
648
- compactSessionRuns(address.value || "", runs, sessionSummary),
649
- ),
650
- },
651
- ],
652
- details: { session: address.value, runs, ...sessionSummary },
653
- };
654
- }
655
- if (address.kind === "tool" && address.value) {
656
- if (address.value === "pi-actors") {
657
- if (view !== "status" && view !== "triage") {
658
- throw new Error(
659
- "inspect tool:pi-actors supports view=status or view=triage.",
660
- );
661
- }
662
- const details =
663
- view === "triage"
664
- ? getPiActorsTriage(ctx, deps)
665
- : getPiActorsRuntimeStatus();
666
- return {
667
- content: [
668
- {
669
- type: "text" as const,
670
- text: maybeJsonText(
671
- details,
672
- input.verbose === true,
673
- view === "triage"
674
- ? compactPiActorsTriage(details)
675
- : compactPiActorsRuntimeStatus(details),
676
- ),
677
- },
678
- ],
360
+ return {
361
+ content: [{
362
+ type: "text" as const,
363
+ text: maybeJsonText(
679
364
  details,
680
- };
681
- }
682
- if (view !== "status" && view !== "schema") {
683
- throw new Error(
684
- "inspect tool:<name> supports view=status or view=schema.",
685
- );
686
- }
687
- const tool = deps.getTool?.(address.value);
688
- if (!tool) throw new Error(`tool actor not found: ${address.value}`);
689
- const details = {
690
- name: address.value,
691
- description: tool.description,
692
- parameters: tool.parameters,
693
- promptSnippet: tool.promptSnippet,
694
- };
695
- return {
696
- content: [
697
- {
698
- type: "text" as const,
699
- text: maybeJsonText(
700
- details,
701
- input.verbose === true || view === "schema",
702
- compactToolActor(address.value, details),
703
- ),
704
- },
705
- ],
706
- details,
707
- };
708
- }
709
- if (address.kind === "room" && address.value && address.room) {
710
- const status = ToolsAccess.assertRunAccessibleToContext(
711
- address.value,
712
- ctx,
713
- );
714
- const stateDir = String(status.state_dir ?? "");
715
- if (!stateDir)
716
- throw new Error(`room:${address.value} has no run state directory.`);
717
- if (view === "status") {
718
- const status = Rooms.getRoomStatus(stateDir, address.room);
719
- return {
720
- content: [
721
- {
722
- type: "text" as const,
723
- text: maybeJsonText(
724
- status,
725
- input.verbose === true,
726
- compactRoomStatus(status),
727
- ),
728
- },
729
- ],
730
- details: status,
731
- };
732
- }
733
- if (view === "previews") {
734
- const previews = Rooms.readRoomMessagePreviews(
735
- stateDir,
736
- address.room,
737
- Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
738
- );
739
- return {
740
- content: [
741
- {
742
- type: "text" as const,
743
- text: maybeJsonText(
744
- previews,
745
- input.verbose === true,
746
- compactRoomPreviews(previews),
747
- ),
748
- },
749
- ],
750
- details: { previews },
751
- };
752
- }
753
- if (view === "messages") {
754
- const messages = Rooms.readRoomMessages(
755
- stateDir,
756
- address.room,
757
- Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
758
- );
759
- return {
760
- content: [
761
- {
762
- type: "text" as const,
763
- text: maybeJsonText(
764
- messages,
765
- input.verbose === true,
766
- compactRoomMessages(messages),
767
- ),
768
- },
769
- ],
770
- details: { messages },
771
- };
772
- }
773
- if (view === "contacts") {
774
- const contacts = Rooms.readRoomContacts(stateDir, address.room);
775
- return {
776
- content: [
777
- {
778
- type: "text" as const,
779
- text: maybeJsonText(
780
- contacts,
781
- input.verbose === true,
782
- compactRoomContacts(contacts),
783
- ),
784
- },
785
- ],
786
- details: { contacts },
787
- };
788
- }
789
- if (view === "roster") {
790
- const roster = Rooms.readRoomRoster(stateDir, address.room);
791
- return {
792
- content: [
793
- {
794
- type: "text" as const,
795
- text: maybeJsonText(
796
- roster,
797
- input.verbose === true,
798
- compactRoomRoster(roster),
799
- ),
800
- },
801
- ],
802
- details: { roster },
803
- };
804
- }
805
- throw new Error(
806
- "inspect room:<run> supports view=status, view=messages, view=previews, view=roster, or view=contacts.",
807
- );
808
- }
809
- const runId =
810
- address.kind === "run" || address.kind === "branch"
811
- ? address.value
812
- : undefined;
813
- if (!runId)
814
- throw new Error(
815
- "inspect target must be run:<id>, branch:<run>/<branch>, coordinator, session:<id>, or tool:<name>.",
816
- );
817
- if (address.kind === "branch") {
818
- if (view !== "mailbox")
819
- throw new Error(
820
- "inspect branch:<run>/<branch> supports view=mailbox.",
821
- );
822
- const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
823
- const branchInbox = Rooms.readBranchInboxDiagnostics(
824
- String(status.state_dir ?? ""),
825
- runId,
826
- target,
827
- Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
828
- );
829
- const messages = branchInbox.messages;
830
- return {
831
- content: [
832
- {
833
- type: "text" as const,
834
- text: maybeJsonText(
835
- input.verbose === true ? branchInbox : messages,
836
- input.verbose === true,
837
- `${ToolsResponse.compactBranchInbox(messages.map((message) => ({ ...message })))}${branchInbox.corrupted > 0 ? `\ncorrupted=${branchInbox.corrupted}` : ""}`,
838
- ),
839
- },
840
- ],
841
- details: { corrupted: branchInbox.corrupted, messages },
842
- };
843
- }
844
- switch (view) {
845
- case "status": {
846
- const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
847
- return {
848
- content: [
849
- {
850
- type: "text" as const,
851
- text: maybeJsonText(
852
- status,
853
- input.verbose === true,
854
- ToolsResponse.compactAsyncRunStatus(status),
855
- ),
856
- },
857
- ],
858
- details: status,
859
- };
860
- }
861
- case "tail": {
862
- ToolsAccess.assertRunAccessibleToContext(runId, ctx);
863
- const text = AsyncRuns.tailRun(
864
- runId,
865
- Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
866
- );
867
- return {
868
- content: [{ type: "text" as const, text: `\n${text}` }],
869
- details: {},
870
- };
871
- }
872
- case "messages": {
873
- ToolsAccess.assertRunAccessibleToContext(runId, ctx);
874
- const messages = AsyncRuns.readRunEvents(
875
- runId,
876
- Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
877
- );
878
- return {
879
- content: [
880
- {
881
- type: "text" as const,
882
- text: maybeJsonText(
883
- messages,
884
- input.verbose === true,
885
- compactRunMessages(messages),
886
- ),
887
- },
888
- ],
889
- details: { messages },
890
- };
891
- }
892
- case "artifacts":
893
- case "files": {
894
- const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
895
- const artifactManifest = AsyncRuns.resolveArtifactManifest(
896
- status.artifacts as
897
- | Record<string, AsyncRuns.RunArtifactDeclaration>
898
- | undefined,
899
- );
900
- const reviewEvidence = readBoundedReviewEvidence(
901
- status,
902
- Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
903
- );
904
- const details = artifactManifest
905
- ? {
906
- ...status,
907
- ...(reviewEvidence ? { review_evidence: reviewEvidence } : {}),
908
- artifact_manifest: artifactManifest,
909
- next_actions: ToolsResponse.artifactNextActions(
910
- status.run ?? runId,
911
- asRecord(status.artifacts),
912
- ),
913
- }
914
- : {
915
- ...status,
916
- ...(reviewEvidence ? { review_evidence: reviewEvidence } : {}),
917
- };
918
- return {
919
- content: [
920
- {
921
- type: "text" as const,
922
- text: maybeJsonText(
923
- details,
924
- input.verbose === true,
925
- compactActorFiles(status),
926
- ),
927
- },
928
- ],
929
- details,
930
- };
931
- }
932
- case "mailbox": {
933
- const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
934
- const mailbox = asRecord(status.mailbox);
935
- const normalizedMailbox =
936
- ToolsMailbox.normalizeMailboxContracts(mailbox);
937
- const messages = AsyncRuns.readRunInboxMessages(
938
- runId,
939
- Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
940
- );
941
- const details = {
942
- mailbox,
943
- normalized_mailbox: normalizedMailbox,
944
- messages,
945
- };
946
- return {
947
- content: [
948
- {
949
- type: "text" as const,
950
- text: maybeJsonText(
951
- details,
952
- input.verbose === true,
953
- ToolsResponse.compactRunMailbox(
954
- String(status.run ?? runId),
955
- mailbox,
956
- messages,
957
- ),
958
- ),
959
- },
960
- ],
961
- details,
962
- };
963
- }
964
- case "communication": {
965
- const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
966
- const snapshot = Rooms.readCommunicationSnapshot(
967
- String(status.state_dir ?? ""),
968
- );
969
- return {
970
- content: [
971
- {
972
- type: "text" as const,
973
- text: maybeJsonText(
974
- snapshot ?? {},
975
- input.verbose === true,
976
- compactCommunicationSnapshot(snapshot),
977
- ),
978
- },
979
- ],
980
- details: { communication: snapshot },
981
- };
982
- }
983
- default:
984
- throw new Error(
985
- "inspect view must be one of: status, tail, messages, artifacts, files, mailbox, communication; branch targets support mailbox.",
986
- );
987
- }
365
+ input.verbose === true || (target.startsWith("tool:") && view === "schema"),
366
+ compactResult(target, view, details),
367
+ ),
368
+ }],
369
+ details,
370
+ };
988
371
  },
989
372
  };
990
373
  }