@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
@@ -3,7 +3,6 @@
3
3
  * Zones: user recipe tools, generated schemas, async recipe launch
4
4
  * Owns wrapping saved local capabilities as executable pi tools
5
5
  */
6
- import * as Rooms from "./rooms.js";
7
6
  import * as AsyncRuns from "./async-runs.js";
8
7
  import * as ModelContext from "./model-context.js";
9
8
  import * as Execution from "./execution.js";
@@ -101,7 +100,6 @@ export function createRuntimeToolDefinition(cfg, exec) {
101
100
  if (isAsyncRecipe)
102
101
  paramSchema.run_id = Schema.stringSchema("Optional run id override for this async template recipe invocation.");
103
102
  if (isAsyncRecipe) {
104
- paramSchema.correlation_id = Schema.stringSchema("Optional workflow correlation id preserved in terminal follow-up delivery.");
105
103
  paramSchema.transport_context = Schema.looseObjectSchema("Optional originating transport route preserved for detached terminal follow-up.");
106
104
  }
107
105
  return {
@@ -120,7 +118,7 @@ export function createRuntimeToolDefinition(cfg, exec) {
120
118
  }
121
119
  if (isAsyncRecipe) {
122
120
  const input = params;
123
- const { correlation_id, run_id, transport_context, ...values } = input;
121
+ const { run_id, transport_context, ...values } = input;
124
122
  const base = cfg.recipe ? cfg.recipe : { file: String(cfg.template) };
125
123
  const runId = typeof run_id === "string" && run_id.trim()
126
124
  ? run_id.trim()
@@ -128,11 +126,7 @@ export function createRuntimeToolDefinition(cfg, exec) {
128
126
  const meta = AsyncRuns.startRun({
129
127
  ...base,
130
128
  launch_source: "tool",
131
- launch_correlation: {
132
- ...(typeof correlation_id === "string"
133
- ? { correlation_id } : {}),
134
- tool_call_id: toolCallId,
135
- },
129
+ launch_correlation: { tool_call_id: toolCallId },
136
130
  ...(transport_context &&
137
131
  typeof transport_context === "object" &&
138
132
  !Array.isArray(transport_context)
@@ -145,8 +139,6 @@ export function createRuntimeToolDefinition(cfg, exec) {
145
139
  policy_values: ModelContext.withCurrentModelValues({ ...(cfg.recipe?.values ?? {}), ...values }, ctx),
146
140
  values: Schema.normalizeRuntimeValues(ModelContext.withCurrentModelValues({ ...(cfg.recipe?.values ?? {}), ...cfg.defaults, ...values }, ctx), cfg.argTypes),
147
141
  }, ctx.cwd);
148
- Rooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
149
- Rooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
150
142
  return {
151
143
  content: [
152
144
  {
@@ -1,10 +1,9 @@
1
1
  /**
2
- * Public message tool behavior
3
- * Zones: addressed actor messages, run controls, branch/room delivery, tool actor invocation
4
- * Owns the public message execution path and compact delivery responses
2
+ * Public Control facade under the stable `message` tool name.
3
+ * Zones: Control request validation, Run/runtime dispatch, compact receipts
4
+ * Owns public Control execution; journaling, delivery, and lifecycle mutation stay in Run domains.
5
5
  */
6
- export interface ActorMessageToolDeps<TContext = unknown> {
7
- getTool?: (name: string) => any | undefined;
8
- handleRuntimeMessage?: (type: string, body: unknown) => Record<string, unknown>;
6
+ export interface ControlToolDeps {
7
+ handleRuntimeControl?: (action: string, input: unknown) => Record<string, unknown>;
9
8
  }
10
- export declare function createActorMessageToolDefinition<TContext = unknown>(deps?: ActorMessageToolDeps<TContext>): any;
9
+ export declare function createControlToolDefinition<TContext = unknown>(deps?: ControlToolDeps): any;
@@ -1,439 +1,138 @@
1
1
  /**
2
- * Public message tool behavior
3
- * Zones: addressed actor messages, run controls, branch/room delivery, tool actor invocation
4
- * Owns the public message execution path and compact delivery responses
2
+ * Public Control facade under the stable `message` tool name.
3
+ * Zones: Control request validation, Run/runtime dispatch, compact receipts
4
+ * Owns public Control execution; journaling, delivery, and lifecycle mutation stay in Run domains.
5
5
  */
6
6
  import * as AsyncRuns from "./async-runs.js";
7
- import * as Messages from "./messages.js";
8
- import * as Rooms from "./rooms.js";
7
+ import * as Control from "./control.js";
9
8
  import * as Schema from "./schema.js";
10
9
  import * as ToolsAccess from "./tools-access.js";
11
- import * as ToolsMailbox from "./tools-mailbox.js";
12
10
  import * as ToolsResponse from "./tools-response.js";
13
11
  function asRecord(value) {
14
12
  return value && typeof value === "object" && !Array.isArray(value)
15
13
  ? value
16
14
  : {};
17
15
  }
18
- function maybeJsonText(value, verbose, compact) {
19
- return verbose ? ToolsResponse.jsonText(value) : compact;
20
- }
21
- function messageBodyToRunLine(message) {
22
- if (message.type !== "run.message")
23
- return JSON.stringify(message);
24
- if (typeof message.body === "string")
25
- return message.body;
26
- if (message.body === undefined)
27
- return message.type;
28
- return JSON.stringify(message.body);
29
- }
30
- function messageBodyToToolParams(message) {
31
- if (message.body &&
32
- typeof message.body === "object" &&
33
- !Array.isArray(message.body)) {
34
- return message.body;
35
- }
36
- if (message.body === undefined)
37
- return {};
38
- return { input: message.body };
39
- }
40
- function formatToolActorFailure(tool, message, params, error) {
41
- const original = error instanceof Error ? error.message : String(error);
42
- const paramsPreview = ToolsResponse.compactPreview(params, 240) ?? "{}";
43
- return Object.assign(new Error(`tool actor ${tool} failed for message type ${message.type}: ${original}; params=${paramsPreview}`), {
44
- message_type: message.type,
45
- original_error: original,
46
- params_preview: paramsPreview,
47
- tool,
48
- });
49
- }
50
- function assertMessageSenderBelongsToRun(message, run, routeLabel) {
51
- if (!message.from) {
52
- throw new Error(`message to ${message.to} requires from=<actor address>.`);
53
- }
54
- const sender = Messages.parseActorAddress(message.from);
55
- if ((sender.kind !== "run" && sender.kind !== "branch") ||
56
- sender.value !== run) {
57
- throw new Error(`message to ${routeLabel} requires from=run:${run} or branch:${run}/<branch>; got ${message.from}.`);
58
- }
59
- }
60
- async function routeBranchEnvelope(stateDir, runId, recipient, message, _options) {
61
- const branchMessage = { ...message, to: recipient };
62
- Rooms.appendBranchInboxMessage(stateDir, runId, recipient, branchMessage);
63
- try {
64
- return await AsyncRuns.sendRunMessage(runId, JSON.stringify(branchMessage));
65
- }
66
- catch (error) {
67
- const record = error && typeof error === "object"
68
- ? error
69
- : {};
70
- if (record.queued === true) {
71
- return {
72
- control_path: record.control_path,
73
- control_type: record.control_type,
74
- delivery_error: record.delivery_error ??
75
- (error instanceof Error ? error.message : String(error)),
76
- inbox_id: record.inbox_id,
77
- queued: true,
78
- run: runId,
79
- sent: false,
80
- state_dir: stateDir,
81
- };
82
- }
83
- throw error;
84
- }
85
- }
86
- function getRoomMulticastRecipients(message, run) {
87
- const raw = message.metadata?.recipients;
88
- if (raw === undefined)
89
- return [];
90
- if (!Array.isArray(raw)) {
91
- throw new Error("room multicast metadata.recipients must be an array.");
92
- }
93
- return raw.map((recipient) => {
94
- if (typeof recipient !== "string") {
95
- throw new Error("room multicast recipients must be actor addresses.");
96
- }
97
- const parsed = Messages.parseActorAddress(recipient);
98
- if (parsed.kind !== "branch" || parsed.value !== run) {
99
- throw new Error(`room multicast recipient must be branch:${run}/<branch>; got ${recipient}.`);
100
- }
101
- return Messages.formatActorAddress(parsed);
102
- });
103
- }
104
- function normalizeDeliveryOutcome(address, result) {
105
- if (result.reason)
106
- return result;
107
- if (address.kind === "run") {
108
- if (result.stopped === true) {
109
- return {
110
- ...result,
111
- consumer: "run-control",
112
- delivered: true,
113
- persisted: true,
114
- reason: "control_applied",
115
- };
116
- }
117
- return {
118
- ...result,
119
- consumer: result.control_type ?? result.control ?? "run-control",
120
- delivered: result.sent === true && result.queued !== true,
121
- persisted: true,
122
- reason: result.delivery_error
123
- ? "delivery_failed_persisted"
124
- : result.queued === true
125
- ? "queued_mailbox"
126
- : "delivered",
127
- };
128
- }
129
- if (address.kind === "branch") {
130
- return {
131
- ...result,
132
- consumer: "branch-mailbox",
133
- delivered: result.sent === true,
134
- persisted: true,
135
- reason: result.delivery_error
136
- ? "branch_persisted_parent_unavailable"
137
- : "branch_persisted_forwarded",
138
- };
139
- }
140
- if (address.kind === "room") {
141
- return {
142
- ...result,
143
- consumer: "room-timeline",
144
- delivered: true,
145
- forwarded: Number(result.multicast_count ?? 0) > 0,
146
- persisted: true,
147
- reason: "room_persisted",
148
- };
149
- }
150
- if (address.kind === "tool") {
151
- return {
152
- ...result,
153
- consumer: "tool",
154
- delivered: true,
155
- persisted: false,
156
- reason: "tool_invoked",
157
- };
158
- }
159
- if (address.kind === "coordinator" || address.kind === "session") {
160
- return {
161
- ...result,
162
- consumer: "run-outbox",
163
- delivered: false,
164
- persisted: true,
165
- queued: true,
166
- reason: `${address.kind}_outbox_persisted`,
167
- };
168
- }
169
- return result;
170
- }
171
- function actorMessageNextActions(message, result) {
172
- const actions = [];
173
- const address = Messages.parseActorAddress(message.to);
174
- if (result.delivery_error || result.sent === false) {
175
- if (address.kind === "run" && address.value) {
176
- actions.push(`inspect target=run:${address.value} view=status`);
177
- actions.push(`inspect target=run:${address.value} view=mailbox`);
178
- }
179
- else if (address.kind === "branch" && address.value) {
180
- actions.push(`inspect target=branch:${address.value}/${address.branch ?? "main"} view=mailbox`);
181
- actions.push(`inspect target=run:${address.value} view=status`);
182
- }
183
- }
184
- if (result.queued === true) {
185
- if (address.kind === "branch" && address.value) {
186
- actions.push(`inspect target=branch:${address.value}/${address.branch ?? "main"} view=mailbox`);
187
- }
188
- else if (address.kind === "run" && address.value) {
189
- actions.push(`inspect target=run:${address.value} view=mailbox`);
190
- }
191
- }
192
- return [...new Set(actions)].slice(0, 3);
193
- }
194
- function compactActorMessageResult(message, result) {
16
+ function compactControlResult(request, result) {
195
17
  const tokens = [
196
- `to=${message.to}`,
197
- `type=${message.type}`,
198
- `message=${result.sent === true || result.stopped === true ? "sent" : "not_sent"}`,
18
+ `target=${request.target}`,
19
+ `action=${request.action}`,
20
+ `status=${String(result.delivery ?? result.status ?? (result.stopped === true ? "handled" : "accepted"))}`,
199
21
  ];
200
- if (result.bytes !== undefined)
201
- tokens.push(`bytes=${String(result.bytes)}`);
202
- if (result.delivered !== undefined)
203
- tokens.push(`delivered=${String(result.delivered)}`);
204
- if (result.queued === true)
205
- tokens.push("queued=true");
206
- if (result.persisted !== undefined)
207
- tokens.push(`persisted=${String(result.persisted)}`);
208
- if (result.forwarded !== undefined)
209
- tokens.push(`forwarded=${String(result.forwarded)}`);
210
- if (result.consumer)
211
- tokens.push(`consumer=${String(result.consumer)}`);
22
+ if (result.control_id)
23
+ tokens.push(`control=${String(result.control_id)}`);
212
24
  if (result.reason)
213
- tokens.push(`reason=${String(result.reason)}`);
214
- if (result.control)
215
- tokens.push(`control=${String(result.control)}`);
216
- if (result.outbox)
217
- tokens.push(`messages=${String(result.outbox)}`);
218
- if (result.message_count !== undefined)
219
- tokens.push(`messages=${String(result.message_count)}`);
220
- if (result.roster_count !== undefined)
221
- tokens.push(`roster=${String(result.roster_count)}`);
222
- if (result.room)
223
- tokens.push(`room=${String(result.room)}`);
224
- if (result.tool)
225
- tokens.push(`tool=${String(result.tool)}`);
226
- if (result.stopped === true)
227
- tokens.push("stopped=true");
228
- if (result.signal)
229
- tokens.push(`signal=${String(result.signal)}`);
230
- if (result.invoked === true)
231
- tokens.push("invoked=true");
232
- if (result.delivery_error) {
233
- tokens.push(`delivery_error=${ToolsResponse.compactPreview(result.delivery_error, 96)}`);
25
+ tokens.push(`reason=${String(result.reason).replaceAll(/\s+/g, "_")}`);
26
+ if (Array.isArray(result.next_actions) && result.next_actions.length > 0) {
27
+ tokens.push(`next=${result.next_actions
28
+ .map((action) => action.replaceAll(/\s+/g, "_"))
29
+ .join("|")}`);
234
30
  }
235
- const nextActions = Array.isArray(result.next_actions)
236
- ? result.next_actions
237
- : actorMessageNextActions(message, result);
238
- if (nextActions.length > 0)
239
- tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
240
31
  return `\n${tokens.join(" ")}`;
241
32
  }
242
- export function createActorMessageToolDefinition(deps = {}) {
33
+ function runNextActions(run, result) {
34
+ if (result.delivery === "queued" || result.delivery === "delivered") {
35
+ return [`inspect target=run:${run} view=control`];
36
+ }
37
+ if (result.stopped === true)
38
+ return [`inspect target=run:${run} view=control`];
39
+ return [];
40
+ }
41
+ export function createControlToolDefinition(deps = {}) {
243
42
  return {
244
43
  name: "message",
245
- label: "Message",
246
- description: "Send one typed addressed message to steer an existing actor instead of restarting it. Core routes are run:<id> and tool:<name>; advanced routes include branch:<run>/<branch>, room:<run> group timelines, coordinator, and session:<id>.",
44
+ label: "Control",
45
+ description: "Apply one Control action to an existing Run or the pi-actors runtime.",
247
46
  parameters: Schema.objectSchema({
248
- body: Schema.unionSchema([
249
- Schema.stringSchema("Message body. For run:<id>, this is the run-local command line."),
250
- Schema.looseObjectSchema("Structured JSON message body."),
251
- Schema.arraySchema("Structured JSON message body array."),
47
+ target: Schema.stringSchema("Run target run:<id>, or literal runtime."),
48
+ action: Schema.stringSchema("Lowercase ASCII Control action, at most 64 characters."),
49
+ input: Schema.unionSchema([
50
+ Schema.stringSchema("Optional Control input string; serialized JSON must fit 380 bytes."),
51
+ Schema.looseObjectSchema("Optional structured Control input; serialized JSON must fit 380 bytes."),
52
+ Schema.arraySchema("Optional structured Control input array; serialized JSON must fit 380 bytes."),
252
53
  ]),
253
- correlation_id: Schema.stringSchema("Optional correlation id for workflow/task linkage."),
254
- from: Schema.stringSchema("Optional sender address, such as coordinator or run:<id>."),
255
- metadata: Schema.looseObjectSchema("Optional structured metadata for routing or domain hints."),
256
- reply_to: Schema.stringSchema("Optional message id this message replies to."),
257
- summary: Schema.stringSchema("Optional short human-facing summary."),
258
- to: Schema.stringSchema("Destination actor address, e.g. run:<id> or tool:<name>; advanced: branch:<run>/<branch>, room:<run>, coordinator, session:<id>."),
259
- type: Schema.stringSchema("Semantic message type, e.g. control.approve or checkpoint.needs_scope."),
260
54
  verbose: Schema.booleanSchema("Return full JSON instead of compact text."),
261
- }, ["to", "type"]),
55
+ }, ["target", "action"]),
262
56
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
263
- const input = asRecord(params);
264
- const message = Messages.normalizeActorMessage(input);
265
- const address = Messages.parseActorAddress(message.to);
57
+ const request = Control.normalizeControlRequest(params);
266
58
  let result;
267
- if (address.kind === "run" && address.value) {
268
- const status = ToolsAccess.assertRunAccessibleToContext(address.value, ctx);
269
- const normalizedMailbox = ToolsMailbox.normalizeMailboxContracts(asRecord(status.mailbox));
270
- const acceptedTypes = new Set(ToolsMailbox.mailboxTypes(normalizedMailbox.accepts));
271
- const advisoryWarnings = acceptedTypes.size > 0 && !acceptedTypes.has(message.type)
272
- ? [
273
- `Message type ${message.type} is not declared in mailbox.accepts for run:${address.value}.`,
274
- ]
275
- : [];
276
- if (message.type === "control.kill") {
277
- result = typeof status.run_instance_id === "string"
278
- ? AsyncRuns.killRun(address.value, {
279
- ...(typeof status.ownerId === "string"
280
- ? { ownerId: status.ownerId }
281
- : {}),
282
- runInstanceId: status.run_instance_id,
283
- })
284
- : { killed: false, reason: "run generation unavailable" };
59
+ if (request.target === "runtime") {
60
+ if (request.action !== "review.retry" && request.action !== "review.reset") {
61
+ throw new Error(`unsupported runtime Control action: ${request.action}; use review.retry or review.reset`);
285
62
  }
286
- else if (message.type === "control.archive") {
287
- result = AsyncRuns.archiveRun(address.value);
63
+ if (!deps.handleRuntimeControl) {
64
+ throw new Error("runtime Control is unavailable");
288
65
  }
289
- else if (message.type === "control.prune") {
290
- const body = asRecord(message.body);
291
- result = AsyncRuns.pruneRun(address.value, {
292
- preserveArtifacts: body.preserve_artifacts === true ||
293
- body.preserveArtifacts === true,
66
+ result = deps.handleRuntimeControl(request.action, request.input);
67
+ }
68
+ else {
69
+ const run = request.target.slice(4);
70
+ const status = ToolsAccess.assertRunAccessibleToContext(run, ctx);
71
+ const runInstanceId = typeof status.run_instance_id === "string"
72
+ ? status.run_instance_id
73
+ : undefined;
74
+ if (!runInstanceId)
75
+ throw new Error(`Run generation unavailable: ${run}`);
76
+ if (request.action === "kill") {
77
+ result = AsyncRuns.killRun(run, {
78
+ ...(typeof status.ownerId === "string"
79
+ ? { ownerId: status.ownerId }
80
+ : {}),
81
+ runInstanceId,
294
82
  });
295
83
  }
296
- else {
297
- result = await AsyncRuns.sendRunMessage(address.value, messageBodyToRunLine(message));
298
- }
299
- if (advisoryWarnings.length > 0)
300
- result = { ...result, warnings: advisoryWarnings };
301
- }
302
- else if (address.kind === "branch" && address.value) {
303
- const runId = address.value;
304
- if (message.from)
305
- assertMessageSenderBelongsToRun(message, runId, `branch:${runId}/<branch>`);
306
- const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
307
- const stateDir = String(status.state_dir ?? "");
308
- if (stateDir && address.branch) {
309
- const ensureBranchMember = (actorAddress) => {
310
- Rooms.ensureRoomMember(stateDir, runId, "main", actorAddress, {
311
- parent: `run:${runId}`,
312
- role: "branch",
313
- status: "present",
314
- }, "Branch joined default room");
315
- Rooms.writeBranchCommunicationSnapshot(stateDir, runId, actorAddress);
316
- };
317
- ensureBranchMember(message.to);
318
- if (message.from) {
319
- const sender = Messages.parseActorAddress(message.from);
320
- if (sender.kind === "branch" && sender.value === runId) {
321
- ensureBranchMember(message.from);
322
- }
323
- }
324
- Rooms.writeCommunicationSnapshot(stateDir, runId);
84
+ else if (request.action === "archive") {
85
+ result = AsyncRuns.archiveRun(run, {
86
+ ...(typeof status.ownerId === "string"
87
+ ? { ownerId: status.ownerId }
88
+ : {}),
89
+ runInstanceId,
90
+ });
325
91
  }
326
- result = await routeBranchEnvelope(stateDir, runId, message.to, message, {
327
- source: "direct",
328
- });
329
- }
330
- else if (address.kind === "room" && address.value && address.room) {
331
- const runId = address.value;
332
- assertMessageSenderBelongsToRun(message, runId, `room:${runId}`);
333
- const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
334
- const stateDir = String(status.state_dir ?? "");
335
- if (!stateDir)
336
- throw new Error(`${message.to} has no run state directory.`);
337
- const recipients = getRoomMulticastRecipients(message, runId);
338
- const roomResult = Rooms.appendRoomMessage(stateDir, address.room, message);
339
- const multicastResults = await Promise.all(recipients.map(async (recipient) => normalizeDeliveryOutcome({ kind: "branch", branch: recipient.split("/").at(-1), value: runId }, await routeBranchEnvelope(stateDir, runId, recipient, message, {
340
- source: "room-multicast",
341
- }))));
342
- result = {
343
- ...roomResult,
344
- ...(recipients.length > 0
345
- ? {
346
- multicast: recipients,
347
- multicast_count: recipients.length,
348
- multicast_results: multicastResults,
349
- }
350
- : {}),
351
- };
352
- }
353
- else if (address.kind === "tool" && address.value) {
354
- if (address.value === "pi-actors" && deps.handleRuntimeMessage) {
355
- result = deps.handleRuntimeMessage(message.type, message.body);
92
+ else if (request.action === "prune") {
93
+ const input = asRecord(request.input);
94
+ result = AsyncRuns.pruneRun(run, {
95
+ preserveArtifacts: input.preserve_artifacts === true ||
96
+ input.preserveArtifacts === true,
97
+ }, {
98
+ ...(typeof status.ownerId === "string"
99
+ ? { ownerId: status.ownerId }
100
+ : {}),
101
+ runInstanceId,
102
+ });
356
103
  }
357
104
  else {
358
- const tool = deps.getTool?.(address.value);
359
- if (!tool || typeof tool.execute !== "function") {
360
- throw new Error(`tool actor not found or not executable: ${address.value}`);
361
- }
362
- const toolParams = messageBodyToToolParams(message);
363
- let toolResult;
364
- try {
365
- toolResult = await tool.execute(`message:${message.type}`, toolParams, _signal, _onUpdate, ctx);
366
- }
367
- catch (error) {
368
- throw formatToolActorFailure(address.value, message, toolParams, error);
369
- }
370
- result = {
371
- invoked: true,
372
- sent: true,
373
- tool: address.value,
374
- tool_result: toolResult,
375
- };
376
- }
377
- }
378
- else if (address.kind === "coordinator" || address.kind === "session") {
379
- if (!message.from) {
380
- throw new Error(`message to ${address.kind} requires from=run:<id>.`);
381
- }
382
- const sender = Messages.parseActorAddress(message.from);
383
- if (sender.kind !== "run" || !sender.value) {
384
- throw new Error(`message to ${address.kind} currently requires from=run:<id>.`);
385
- }
386
- const senderStatus = ToolsAccess.assertRunAccessibleToContext(sender.value, ctx);
387
- if (address.kind === "session") {
388
- if (!senderStatus.ownerId) {
389
- throw ToolsAccess.sessionMismatchError({
390
- currentSession: undefined,
391
- expectedSession: address.value,
392
- run: sender.value,
393
- target: `session:${address.value}`,
394
- });
395
- }
396
- if (senderStatus.ownerId !== address.value) {
397
- throw ToolsAccess.sessionMismatchError({
398
- currentSession: String(senderStatus.ownerId),
399
- expectedSession: address.value,
400
- run: sender.value,
401
- target: `session:${address.value}`,
402
- });
105
+ const declared = Array.isArray(status.control)
106
+ ? status.control.filter((action) => typeof action === "string")
107
+ : [];
108
+ if (!declared.includes(request.action)) {
109
+ throw new Error(`undeclared Run Control action: ${request.action}; available=${declared.join(",") || "none"}`);
403
110
  }
111
+ result = await AsyncRuns.sendRunControl(run, {
112
+ action: request.action,
113
+ ...(request.input !== undefined ? { input: request.input } : {}),
114
+ run_instance_id: runInstanceId,
115
+ }, {
116
+ ...(typeof status.ownerId === "string"
117
+ ? { ownerId: status.ownerId }
118
+ : {}),
119
+ });
404
120
  }
405
- result = AsyncRuns.appendRunOutboxEvent(sender.value, {
406
- body: message.body,
407
- correlation_id: message.correlation_id,
408
- delivery: message.metadata?.requires_response === true ||
409
- address.kind === "session"
410
- ? "followup"
411
- : undefined,
412
- event: message.type,
413
- from: message.from,
414
- metadata: message.metadata,
415
- reply_to: message.reply_to,
416
- summary: message.summary,
417
- to: message.to,
418
- type: message.type,
419
- });
420
- }
421
- else {
422
- throw new Error(`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`);
121
+ const nextActions = runNextActions(run, result);
122
+ if (nextActions.length > 0)
123
+ result = { ...result, next_actions: nextActions };
423
124
  }
424
- result = normalizeDeliveryOutcome(address, result);
425
- const nextActions = actorMessageNextActions(message, result);
426
- const resultWithNext = nextActions.length
427
- ? { ...result, next_actions: nextActions }
428
- : result;
125
+ const response = { control: request, result };
429
126
  return {
430
127
  content: [
431
128
  {
432
129
  type: "text",
433
- text: maybeJsonText({ message, result: resultWithNext }, input.verbose === true, compactActorMessageResult(message, resultWithNext)),
130
+ text: request.verbose === true
131
+ ? ToolsResponse.jsonText(response)
132
+ : compactControlResult(request, result),
434
133
  },
435
134
  ],
436
- details: { message, result: resultWithNext },
135
+ details: response,
437
136
  };
438
137
  },
439
138
  };
@@ -10,12 +10,8 @@ export declare function asRecord(value: unknown): Record<string, unknown>;
10
10
  export declare function jsonText(value: unknown): string;
11
11
  export declare function compactPreview(value: unknown, maxLength?: number): string | undefined;
12
12
  export declare function compactNextActions(actions: string[]): string;
13
- export declare function actorRunNextActions(run: unknown): string[];
13
+ export declare function runNextActions(run: unknown): string[];
14
14
  export declare function compactAsyncRunStatus(value: unknown): string;
15
- export declare function compactInboxMessages(messages: Array<Record<string, unknown>>, emptyLabel: string): string;
16
- export declare function compactBranchInbox(messages: Array<Record<string, unknown>>): string;
17
- export declare function compactRunMailbox(run: string, mailbox: Record<string, unknown>, messages: Array<Record<string, unknown>>): string;
18
- export declare function artifactNextActions(run: unknown, artifacts: Record<string, unknown>): string[];
19
15
  export declare function maybeJsonText(value: unknown, verbose: boolean | undefined, compact: string): string;
20
16
  export declare function compactRecipeImports(summary: Record<string, unknown>): string;
21
17
  export declare function compactRecipeDoctor(summary: Record<string, unknown>): string;