@llblab/pi-actors 0.42.2 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/AGENTS.md +121 -175
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +113 -276
  4. package/dist/fixtures/protocol/control-endpoint.json +6 -0
  5. package/dist/fixtures/protocol/control-record.json +9 -0
  6. package/dist/fixtures/protocol/recipe-summary.json +4 -12
  7. package/dist/fixtures/protocol/trace-event.json +9 -0
  8. package/dist/lib/async-runs.d.ts +14 -38
  9. package/dist/lib/async-runs.js +158 -108
  10. package/dist/lib/control.d.ts +12 -0
  11. package/dist/lib/control.js +84 -0
  12. package/dist/lib/execution-sessions.d.ts +17 -0
  13. package/dist/lib/execution-sessions.js +85 -0
  14. package/dist/lib/file-state.d.ts +1 -0
  15. package/dist/lib/file-state.js +17 -5
  16. package/dist/lib/inspector-actions.d.ts +2 -2
  17. package/dist/lib/inspector-actions.js +2 -2
  18. package/dist/lib/inspector-command.js +3 -3
  19. package/dist/lib/inspector-overlay.d.ts +52 -70
  20. package/dist/lib/inspector-overlay.js +532 -905
  21. package/dist/lib/inspector.d.ts +3 -71
  22. package/dist/lib/inspector.js +19 -665
  23. package/dist/lib/limits.d.ts +4 -2
  24. package/dist/lib/limits.js +4 -2
  25. package/dist/lib/observability.d.ts +17 -17
  26. package/dist/lib/observability.js +45 -84
  27. package/dist/lib/pi.d.ts +1 -1
  28. package/dist/lib/prompts.d.ts +1 -1
  29. package/dist/lib/prompts.js +2 -2
  30. package/dist/lib/recipe-control.d.ts +7 -0
  31. package/dist/lib/recipe-control.js +39 -0
  32. package/dist/lib/recipes-discovery.js +2 -0
  33. package/dist/lib/recipes-references.d.ts +1 -14
  34. package/dist/lib/recipes-references.js +6 -21
  35. package/dist/lib/review-projection.js +1 -5
  36. package/dist/lib/run-ui-runtime.js +2 -2
  37. package/dist/lib/runs-control-delivery.d.ts +21 -0
  38. package/dist/lib/runs-control-delivery.js +127 -0
  39. package/dist/lib/runs-controls.d.ts +35 -0
  40. package/dist/lib/runs-controls.js +144 -0
  41. package/dist/lib/runs-retention.d.ts +7 -0
  42. package/dist/lib/runs-retention.js +27 -3
  43. package/dist/lib/runs-start.js +4 -2
  44. package/dist/lib/runs-status.js +11 -6
  45. package/dist/lib/runs-trace.d.ts +24 -0
  46. package/dist/lib/runs-trace.js +98 -0
  47. package/dist/lib/runtime-notifier.d.ts +1 -1
  48. package/dist/lib/runtime-notifier.js +1 -1
  49. package/dist/lib/tools-inspect.d.ts +3 -3
  50. package/dist/lib/tools-inspect.js +203 -708
  51. package/dist/lib/tools-local.js +2 -10
  52. package/dist/lib/tools-message.d.ts +7 -7
  53. package/dist/lib/tools-message.js +95 -396
  54. package/dist/lib/tools-response.d.ts +1 -4
  55. package/dist/lib/tools-response.js +5 -39
  56. package/dist/lib/tools-spawn.js +16 -28
  57. package/dist/lib/tools.js +1 -2
  58. package/dist/lib/trace-projection.d.ts +22 -0
  59. package/dist/lib/trace-projection.js +165 -0
  60. package/dist/recipes/draft-review.json +0 -10
  61. package/dist/recipes/lens-swarm.json +0 -14
  62. package/dist/recipes/music-player.json +10 -19
  63. package/dist/recipes/pipeline-architect-coordinator.json +0 -11
  64. package/dist/recipes/pipeline-artifact-bundle.json +1 -22
  65. package/dist/recipes/pipeline-artifact-report.json +1 -18
  66. package/dist/recipes/pipeline-artifact-write.json +1 -18
  67. package/dist/recipes/pipeline-async-run-ops.json +0 -12
  68. package/dist/recipes/pipeline-checkpoint-continuation.json +0 -14
  69. package/dist/recipes/pipeline-development-tasking.json +0 -12
  70. package/dist/recipes/pipeline-docs-maintenance.json +0 -12
  71. package/dist/recipes/pipeline-media-library.json +0 -12
  72. package/dist/recipes/pipeline-quorum-review.json +0 -12
  73. package/dist/recipes/pipeline-release-readiness.json +0 -12
  74. package/dist/recipes/pipeline-release-summary.json +0 -12
  75. package/dist/recipes/pipeline-repo-health.json +0 -12
  76. package/dist/recipes/pipeline-research-synthesis.json +0 -11
  77. package/dist/recipes/pipeline-review-readiness.json +0 -12
  78. package/dist/recipes/resource-locker.json +27 -0
  79. package/dist/recipes/subagent-artifact.json +0 -9
  80. package/dist/recipes/subagent-checkpoint.json +0 -10
  81. package/dist/recipes/subagent-conflict-report.json +0 -11
  82. package/dist/recipes/subagent-contradiction-map.json +0 -11
  83. package/dist/recipes/subagent-critic.json +0 -11
  84. package/dist/recipes/subagent-evidence-map.json +0 -11
  85. package/dist/recipes/subagent-followup.json +0 -10
  86. package/dist/recipes/subagent-judge.json +0 -11
  87. package/dist/recipes/subagent-merge.json +0 -11
  88. package/dist/recipes/subagent-normalize.json +0 -11
  89. package/dist/recipes/subagent-plan.json +0 -11
  90. package/dist/recipes/subagent-preflight.json +0 -11
  91. package/dist/recipes/subagent-prompt.json +0 -10
  92. package/dist/recipes/subagent-quorum.json +0 -10
  93. package/dist/recipes/subagent-review-coordinator.json +0 -14
  94. package/dist/recipes/subagent-review.json +0 -11
  95. package/dist/recipes/subagent-task-card.json +0 -11
  96. package/dist/recipes/subagent-tools.json +0 -10
  97. package/dist/recipes/subagent-verify.json +0 -11
  98. package/dist/recipes/subagents-prompts.json +0 -10
  99. package/dist/recipes/tool-review.json +0 -10
  100. package/dist/scripts/async-runner.mjs +25 -25
  101. package/dist/scripts/conformance.mjs +4 -2
  102. package/dist/scripts/locker.mjs +200 -66
  103. package/dist/scripts/music-player.mjs +159 -150
  104. package/dist/scripts/recipe-utils.mjs +6 -96
  105. package/dist/scripts/release-gates.mjs +60 -0
  106. package/dist/scripts/validate-recipe.mjs +3 -53
  107. package/dist/skills/actors/SKILL.md +53 -266
  108. package/dist/skills/swarm/SKILL.md +11 -33
  109. package/docs/0.43-baseline.md +44 -0
  110. package/docs/README.md +3 -3
  111. package/docs/actor-inspector.md +26 -64
  112. package/docs/actors-deep-reference.md +92 -50
  113. package/docs/async-runs.md +81 -328
  114. package/docs/command-templates.md +2 -2
  115. package/docs/component-recipes.md +30 -133
  116. package/docs/recipe-library.md +57 -182
  117. package/docs/task-first-recipes.md +10 -12
  118. package/docs/template-recipes.md +76 -289
  119. package/docs/tool-registry.md +41 -161
  120. package/fixtures/protocol/control-endpoint.json +6 -0
  121. package/fixtures/protocol/control-record.json +9 -0
  122. package/fixtures/protocol/recipe-summary.json +4 -12
  123. package/fixtures/protocol/trace-event.json +9 -0
  124. package/lib/async-runs.ts +202 -201
  125. package/lib/control.ts +102 -0
  126. package/lib/execution-sessions.ts +111 -0
  127. package/lib/file-state.ts +17 -4
  128. package/lib/inspector-actions.ts +2 -2
  129. package/lib/inspector-command.ts +3 -3
  130. package/lib/inspector-overlay.ts +577 -1121
  131. package/lib/inspector.ts +46 -979
  132. package/lib/limits.ts +4 -2
  133. package/lib/observability.ts +63 -104
  134. package/lib/pi.ts +1 -1
  135. package/lib/prompts.ts +2 -2
  136. package/lib/recipe-control.ts +45 -0
  137. package/lib/recipes-discovery.ts +2 -0
  138. package/lib/recipes-references.ts +9 -45
  139. package/lib/review-projection.ts +1 -5
  140. package/lib/run-ui-runtime.ts +2 -2
  141. package/lib/runs-control-delivery.ts +181 -0
  142. package/lib/runs-controls.ts +204 -0
  143. package/lib/runs-retention.ts +38 -3
  144. package/lib/runs-start.ts +4 -2
  145. package/lib/runs-status.ts +11 -6
  146. package/lib/runs-trace.ts +132 -0
  147. package/lib/runtime-notifier.ts +1 -1
  148. package/lib/tools-inspect.ts +240 -901
  149. package/lib/tools-local.ts +2 -12
  150. package/lib/tools-message.ts +112 -519
  151. package/lib/tools-response.ts +5 -52
  152. package/lib/tools-spawn.ts +16 -32
  153. package/lib/tools.ts +1 -2
  154. package/lib/trace-projection.ts +221 -0
  155. package/package.json +2 -1
  156. package/recipes/draft-review.json +0 -10
  157. package/recipes/lens-swarm.json +0 -14
  158. package/recipes/music-player.json +10 -19
  159. package/recipes/pipeline-architect-coordinator.json +0 -11
  160. package/recipes/pipeline-artifact-bundle.json +1 -22
  161. package/recipes/pipeline-artifact-report.json +1 -18
  162. package/recipes/pipeline-artifact-write.json +1 -18
  163. package/recipes/pipeline-async-run-ops.json +0 -12
  164. package/recipes/pipeline-checkpoint-continuation.json +0 -14
  165. package/recipes/pipeline-development-tasking.json +0 -12
  166. package/recipes/pipeline-docs-maintenance.json +0 -12
  167. package/recipes/pipeline-media-library.json +0 -12
  168. package/recipes/pipeline-quorum-review.json +0 -12
  169. package/recipes/pipeline-release-readiness.json +0 -12
  170. package/recipes/pipeline-release-summary.json +0 -12
  171. package/recipes/pipeline-repo-health.json +0 -12
  172. package/recipes/pipeline-research-synthesis.json +0 -11
  173. package/recipes/pipeline-review-readiness.json +0 -12
  174. package/recipes/resource-locker.json +27 -0
  175. package/recipes/subagent-artifact.json +0 -9
  176. package/recipes/subagent-checkpoint.json +0 -10
  177. package/recipes/subagent-conflict-report.json +0 -11
  178. package/recipes/subagent-contradiction-map.json +0 -11
  179. package/recipes/subagent-critic.json +0 -11
  180. package/recipes/subagent-evidence-map.json +0 -11
  181. package/recipes/subagent-followup.json +0 -10
  182. package/recipes/subagent-judge.json +0 -11
  183. package/recipes/subagent-merge.json +0 -11
  184. package/recipes/subagent-normalize.json +0 -11
  185. package/recipes/subagent-plan.json +0 -11
  186. package/recipes/subagent-preflight.json +0 -11
  187. package/recipes/subagent-prompt.json +0 -10
  188. package/recipes/subagent-quorum.json +0 -10
  189. package/recipes/subagent-review-coordinator.json +0 -14
  190. package/recipes/subagent-review.json +0 -11
  191. package/recipes/subagent-task-card.json +0 -11
  192. package/recipes/subagent-tools.json +0 -10
  193. package/recipes/subagent-verify.json +0 -11
  194. package/recipes/subagents-prompts.json +0 -10
  195. package/recipes/tool-review.json +0 -10
  196. package/scripts/async-runner.mjs +25 -25
  197. package/scripts/conformance.mjs +4 -2
  198. package/scripts/locker.mjs +200 -66
  199. package/scripts/music-player.mjs +159 -150
  200. package/scripts/recipe-utils.mjs +6 -96
  201. package/scripts/release-gates.mjs +60 -0
  202. package/scripts/validate-recipe.mjs +3 -53
  203. package/skills/actors/SKILL.md +53 -266
  204. package/skills/swarm/SKILL.md +11 -33
  205. package/dist/fixtures/protocol/actor-message-branch.json +0 -13
  206. package/dist/fixtures/protocol/mailbox-contract.json +0 -15
  207. package/dist/fixtures/protocol/room-message.json +0 -11
  208. package/dist/fixtures/protocol/room-roster.json +0 -11
  209. package/dist/fixtures/protocol/run-inbox-message.json +0 -9
  210. package/dist/fixtures/protocol/run-outbox-event.json +0 -9
  211. package/dist/lib/mailbox-loop.d.ts +0 -41
  212. package/dist/lib/mailbox-loop.js +0 -60
  213. package/dist/lib/messages.d.ts +0 -25
  214. package/dist/lib/messages.js +0 -122
  215. package/dist/lib/rooms.d.ts +0 -104
  216. package/dist/lib/rooms.js +0 -647
  217. package/dist/lib/runs-mailbox.d.ts +0 -25
  218. package/dist/lib/runs-mailbox.js +0 -146
  219. package/dist/lib/runs-messages.d.ts +0 -15
  220. package/dist/lib/runs-messages.js +0 -179
  221. package/dist/lib/runs-outbox.d.ts +0 -41
  222. package/dist/lib/runs-outbox.js +0 -87
  223. package/dist/lib/tools-mailbox.d.ts +0 -8
  224. package/dist/lib/tools-mailbox.js +0 -48
  225. package/dist/recipes/actor-worker.json +0 -39
  226. package/dist/recipes/coordinator-locker.json +0 -45
  227. package/dist/recipes/locker.json +0 -45
  228. package/dist/recipes/pipeline-room-swarm.json +0 -50
  229. package/dist/recipes/subagent-message.json +0 -32
  230. package/dist/recipes/utility-actor-message.json +0 -23
  231. package/dist/scripts/actor-worker.mjs +0 -214
  232. package/dist/scripts/coordinator.mjs +0 -799
  233. package/docs/actor-messages.md +0 -225
  234. package/fixtures/protocol/actor-message-branch.json +0 -13
  235. package/fixtures/protocol/mailbox-contract.json +0 -15
  236. package/fixtures/protocol/room-message.json +0 -11
  237. package/fixtures/protocol/room-roster.json +0 -11
  238. package/fixtures/protocol/run-inbox-message.json +0 -9
  239. package/fixtures/protocol/run-outbox-event.json +0 -9
  240. package/lib/mailbox-loop.ts +0 -144
  241. package/lib/messages.ts +0 -151
  242. package/lib/rooms.ts +0 -939
  243. package/lib/runs-mailbox.ts +0 -208
  244. package/lib/runs-messages.ts +0 -252
  245. package/lib/runs-outbox.ts +0 -144
  246. package/lib/tools-mailbox.ts +0 -56
  247. package/recipes/actor-worker.json +0 -39
  248. package/recipes/coordinator-locker.json +0 -45
  249. package/recipes/locker.json +0 -45
  250. package/recipes/pipeline-room-swarm.json +0 -50
  251. package/recipes/subagent-message.json +0 -32
  252. package/recipes/utility-actor-message.json +0 -23
  253. package/scripts/actor-worker.mjs +0 -214
  254. package/scripts/coordinator.mjs +0 -799
@@ -1,14 +1,16 @@
1
1
  /**
2
2
  * Shared output and preview size limits.
3
- * Zones: output governance, actor-message previews, inspect defaults
3
+ * Zones: output governance, Trace previews, inspect defaults
4
4
  */
5
5
  export declare const DEFAULT_INSPECT_LINES = 40;
6
6
  export declare const TOOL_OUTPUT_MAX_BYTES: number;
7
7
  export declare const TOOL_OUTPUT_MAX_LINES = 2000;
8
8
  export declare const COMPACT_PREVIEW_CHARS = 160;
9
+ export declare const CONTROL_INPUT_MAX_BYTES: number;
9
10
  export declare const INSPECTOR_BODY_PREVIEW_CHARS = 320;
10
- export declare const ROOM_MESSAGE_PREVIEW_CHARS = 320;
11
11
  export declare const DOCTOR_ACTION_PREVIEW_CHARS = 72;
12
12
  export declare const SESSION_EVIDENCE_MAX_TURNS = 100;
13
13
  export declare const SESSION_EVIDENCE_TEXT_CHARS = 4000;
14
14
  export declare const SESSION_EVIDENCE_MAX_TOOL_CALLS = 100;
15
+ export declare const TRACE_EVENT_MAX_BYTES: number;
16
+ export declare const TRACE_EVENT_MAX_READ = 200;
@@ -1,14 +1,16 @@
1
1
  /**
2
2
  * Shared output and preview size limits.
3
- * Zones: output governance, actor-message previews, inspect defaults
3
+ * Zones: output governance, Trace previews, inspect defaults
4
4
  */
5
5
  export const DEFAULT_INSPECT_LINES = 40;
6
6
  export const TOOL_OUTPUT_MAX_BYTES = 50 * 1024;
7
7
  export const TOOL_OUTPUT_MAX_LINES = 2_000;
8
8
  export const COMPACT_PREVIEW_CHARS = 160;
9
+ export const CONTROL_INPUT_MAX_BYTES = 64 * 1024;
9
10
  export const INSPECTOR_BODY_PREVIEW_CHARS = 320;
10
- export const ROOM_MESSAGE_PREVIEW_CHARS = 320;
11
11
  export const DOCTOR_ACTION_PREVIEW_CHARS = 72;
12
12
  export const SESSION_EVIDENCE_MAX_TURNS = 100;
13
13
  export const SESSION_EVIDENCE_TEXT_CHARS = 4_000;
14
14
  export const SESSION_EVIDENCE_MAX_TOOL_CALLS = 100;
15
+ export const TRACE_EVENT_MAX_BYTES = 64 * 1024;
16
+ export const TRACE_EVENT_MAX_READ = 200;
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Async run observability helpers
3
3
  * Zones: async runtime, ambient UI, diagnostics
4
- * Owns ambient summaries, terminal events, and run outbox delivery for detached command-template runs
4
+ * Owns ambient summaries, terminal events, and Trace-attention delivery for detached Runs
5
5
  */
6
6
  import { type FSWatcher } from "node:fs";
7
7
  import * as AsyncRuns from "./async-runs.ts";
8
8
  export type RunObservedStatus = "running" | "done" | "failed" | "exited" | "cancelled" | "killed";
9
- export type RunOutboxDelivery = "log" | "notify" | "followup";
10
- export type RunOutboxLevel = "info" | "warning" | "error";
9
+ export type RunTraceAttention = "log" | "notify" | "followup";
10
+ export type RunTraceLevel = "info" | "warning" | "error";
11
11
  export interface RunObservation {
12
12
  activeSubagents?: number;
13
13
  completed?: number;
@@ -41,13 +41,13 @@ export interface RunSummary {
41
41
  total: number;
42
42
  }
43
43
  export interface RunUiObservationState {
44
+ attentionEventIds: Map<string, Set<string>>;
44
45
  eventLines: Map<string, number>;
45
46
  frame: number;
46
47
  observed: Map<string, RunObservedStatus>;
47
- outboxEventIds: Map<string, Set<string>>;
48
48
  }
49
49
  export interface RunUiSnapshot {
50
- outboxEvents: RunOutboxEvent[];
50
+ attentionEvents: RunAttentionEvent[];
51
51
  status: string | undefined;
52
52
  summary: RunSummary;
53
53
  transitions: RunTransition[];
@@ -57,13 +57,13 @@ export interface RunUiNotificationSink {
57
57
  sendFollowUp(message: {
58
58
  customType: string;
59
59
  content: string;
60
- display: true;
60
+ display: false;
61
61
  details: unknown;
62
62
  }): void;
63
63
  }
64
64
  export declare function createRunUiObservationState(): RunUiObservationState;
65
65
  export declare function readRunUiSnapshot(state: RunUiObservationState, ownerId: string, options?: {
66
- includeOutbox?: boolean;
66
+ includeAttention?: boolean;
67
67
  stateRoot?: string;
68
68
  }): RunUiSnapshot;
69
69
  export declare function pruneRunUiObservationState(state: RunUiObservationState, snapshot: Pick<RunUiSnapshot, "summary" | "transitions">): void;
@@ -75,7 +75,7 @@ export declare function reconcileRunTerminalNotifications(input: {
75
75
  state: RunUiObservationState;
76
76
  stateRoot?: string;
77
77
  }): RunUiSnapshot;
78
- export declare function deliverRunOutboxNotifications(events: RunOutboxEvent[], sink: RunUiNotificationSink): void;
78
+ export declare function deliverRunAttentionNotifications(events: RunAttentionEvent[], sink: RunUiNotificationSink): void;
79
79
  export interface RunRetirementCandidate {
80
80
  activeSubagents: number;
81
81
  childRuns: number;
@@ -150,13 +150,13 @@ export interface RunTerminalSemanticResult {
150
150
  synthesized: boolean;
151
151
  type: string;
152
152
  }
153
- export interface RunOutboxEvent {
153
+ export interface RunAttentionEvent {
154
154
  body?: unknown;
155
155
  data?: unknown;
156
- delivery: RunOutboxDelivery;
157
- event: string;
156
+ attention: RunTraceAttention;
158
157
  id: string;
159
- level: RunOutboxLevel;
158
+ kind: string;
159
+ level: RunTraceLevel;
160
160
  metadata?: Record<string, unknown>;
161
161
  run: string;
162
162
  stateDir: string;
@@ -173,11 +173,11 @@ export declare function findRunRetirementCandidates(summary: RunSummary): RunRet
173
173
  export declare function executeRunRetirements(summary: RunSummary, options: RunRetirementExecutorOptions): Promise<RunRetirementExecution[]>;
174
174
  export declare function detectRunTransitions(previous: Map<string, RunObservedStatus>, summary: RunSummary): RunTransition[];
175
175
  export declare function pruneRunObservationState(previousStatuses: Map<string, RunObservedStatus>, previousLineCounts: Map<string, number>, summary: RunSummary, terminalRuns?: Iterable<string>, seenEventIds?: Map<string, Set<string>>): void;
176
- export declare function detectRunOutboxEvents(previousLineCounts: Map<string, number>, summary: RunSummary, seenEventIds?: Map<string, Set<string>>): RunOutboxEvent[];
177
- export declare function getRunOutboxNotificationType(event: RunOutboxEvent): RunTransitionNotificationType;
178
- export declare function shouldNotifyRunOutboxEvent(event: RunOutboxEvent): boolean;
179
- export declare function shouldSendRunOutboxFollowUp(event: RunOutboxEvent): boolean;
180
- export declare function formatRunOutboxMessage(event: RunOutboxEvent): string;
176
+ export declare function detectRunAttentionEvents(previousLineCounts: Map<string, number>, summary: RunSummary, seenEventIds?: Map<string, Set<string>>): RunAttentionEvent[];
177
+ export declare function getRunAttentionNotificationType(event: RunAttentionEvent): RunTransitionNotificationType;
178
+ export declare function shouldNotifyRunAttentionEvent(event: RunAttentionEvent): boolean;
179
+ export declare function shouldSendRunAttentionFollowUp(event: RunAttentionEvent): boolean;
180
+ export declare function formatRunAttentionMessage(event: RunAttentionEvent): string;
181
181
  export declare function getRunTransitionNotificationType(transition: RunTransition): RunTransitionNotificationType;
182
182
  export declare function shouldNotifyRunTransition(transition: RunTransition): boolean;
183
183
  export declare function shouldSendRunTransitionFollowUp(transition: RunTransition): boolean;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Async run observability helpers
3
3
  * Zones: async runtime, ambient UI, diagnostics
4
- * Owns ambient summaries, terminal events, and run outbox delivery for detached command-template runs
4
+ * Owns ambient summaries, terminal events, and Trace-attention delivery for detached Runs
5
5
  */
6
6
  import { closeSync, existsSync, fstatSync, openSync, readdirSync, readFileSync, readSync, watch, } from "node:fs";
7
7
  import { basename, dirname, isAbsolute, join, relative, } from "node:path";
@@ -10,26 +10,26 @@ import * as Paths from "./paths.js";
10
10
  import { readJsonlFileResilient } from "./state-readers.js";
11
11
  export function createRunUiObservationState() {
12
12
  return {
13
+ attentionEventIds: new Map(),
13
14
  eventLines: new Map(),
14
15
  frame: 0,
15
16
  observed: new Map(),
16
- outboxEventIds: new Map(),
17
17
  };
18
18
  }
19
19
  export function readRunUiSnapshot(state, ownerId, options = {}) {
20
20
  const summary = summarizeRuns(options.stateRoot, ownerId);
21
21
  const status = renderRunStatus(summary, state.frame++);
22
22
  return {
23
- outboxEvents: options.includeOutbox === false
23
+ attentionEvents: options.includeAttention === false
24
24
  ? []
25
- : detectRunOutboxEvents(state.eventLines, summary, state.outboxEventIds),
25
+ : detectRunAttentionEvents(state.eventLines, summary, state.attentionEventIds),
26
26
  status,
27
27
  summary,
28
28
  transitions: detectRunTransitions(state.observed, summary),
29
29
  };
30
30
  }
31
31
  export function pruneRunUiObservationState(state, snapshot) {
32
- pruneRunObservationState(state.observed, state.eventLines, snapshot.summary, snapshot.transitions.map((transition) => transition.stateDir ?? transition.run), state.outboxEventIds);
32
+ pruneRunObservationState(state.observed, state.eventLines, snapshot.summary, snapshot.transitions.map((transition) => transition.stateDir ?? transition.run), state.attentionEventIds);
33
33
  }
34
34
  export function deliverRunTransitionNotifications(transitions, sink, inFlight = new Set()) {
35
35
  for (const transition of transitions) {
@@ -47,7 +47,7 @@ export function deliverRunTransitionNotifications(transitions, sink, inFlight =
47
47
  sink.sendFollowUp({
48
48
  customType: "pi-actors-run",
49
49
  content: text,
50
- display: true,
50
+ display: false,
51
51
  details: transition,
52
52
  });
53
53
  if (transition.stateDir) {
@@ -68,25 +68,25 @@ export function deliverRunTransitionNotifications(transitions, sink, inFlight =
68
68
  }
69
69
  export function reconcileRunTerminalNotifications(input) {
70
70
  const snapshot = readRunUiSnapshot(input.state, input.ownerId, {
71
- includeOutbox: false,
71
+ includeAttention: false,
72
72
  stateRoot: input.stateRoot,
73
73
  });
74
74
  deliverRunTransitionNotifications(snapshot.transitions, input.sink, input.inFlight);
75
75
  pruneRunUiObservationState(input.state, snapshot);
76
76
  return snapshot;
77
77
  }
78
- export function deliverRunOutboxNotifications(events, sink) {
78
+ export function deliverRunAttentionNotifications(events, sink) {
79
79
  for (const event of events) {
80
- if (!shouldNotifyRunOutboxEvent(event))
80
+ if (!shouldNotifyRunAttentionEvent(event))
81
81
  continue;
82
- const text = formatRunOutboxMessage(event);
83
- sink.notify(text, getRunOutboxNotificationType(event));
84
- if (!shouldSendRunOutboxFollowUp(event))
82
+ const text = formatRunAttentionMessage(event);
83
+ sink.notify(text, getRunAttentionNotificationType(event));
84
+ if (!shouldSendRunAttentionFollowUp(event))
85
85
  continue;
86
86
  sink.sendFollowUp({
87
- customType: "pi-actors-run-message",
87
+ customType: "pi-actors-run-trace",
88
88
  content: text,
89
- display: true,
89
+ display: false,
90
90
  details: event,
91
91
  });
92
92
  }
@@ -326,46 +326,7 @@ function terminalSemanticResult(status, stateDir, observedStatus) {
326
326
  : typeof correlation.tool_call_id === "string"
327
327
  ? correlation.tool_call_id
328
328
  : undefined;
329
- const mailbox = status.mailbox &&
330
- typeof status.mailbox === "object" &&
331
- !Array.isArray(status.mailbox)
332
- ? status.mailbox
333
- : {};
334
- const emits = Array.isArray(mailbox.emits)
335
- ? mailbox.emits.filter((item) => typeof item === "string")
336
- : [];
337
- const outbox = readJsonlFileResilient(join(stateDir, "outbox.jsonl")).records;
338
- const explicit = outbox.findLast((record) => {
339
- const type = String(record.type ?? record.event ?? "");
340
- return emits.includes(type) && !["command.done", "run.done", "run.failed"].includes(type);
341
- });
342
- if (explicit) {
343
- const type = String(explicit.type ?? explicit.event);
344
- return {
345
- ...(explicit.body === undefined
346
- ? {} : { body: formatSemanticBody(explicit.body) }),
347
- ...(typeof explicit.correlation_id === "string"
348
- ? { correlationId: explicit.correlation_id } : correlationId ? { correlationId } : {}),
349
- metadata: {
350
- ...(explicit.metadata &&
351
- typeof explicit.metadata === "object" &&
352
- !Array.isArray(explicit.metadata)
353
- ? explicit.metadata : {}),
354
- ...(status.transport_context &&
355
- typeof status.transport_context === "object" &&
356
- !Array.isArray(status.transport_context)
357
- ? {
358
- transport_context: status.transport_context,
359
- } : {}),
360
- run: String(status.run ?? ""),
361
- status: observedStatus,
362
- },
363
- summary: String(explicit.summary ?? type),
364
- synthesized: false,
365
- type,
366
- };
367
- }
368
- const reviewCompleted = observedStatus === "done" && emits.includes("review.completed");
329
+ const reviewCompleted = false;
369
330
  const result = status.result &&
370
331
  typeof status.result === "object" &&
371
332
  !Array.isArray(status.result)
@@ -396,14 +357,6 @@ function terminalSemanticResult(status, stateDir, observedStatus) {
396
357
  type,
397
358
  };
398
359
  }
399
- function formatSemanticBody(body) {
400
- const rendered = typeof body === "string" ? body : JSON.stringify(body);
401
- const text = typeof rendered === "string" ? rendered : String(body);
402
- const compact = text.trim();
403
- return compact.length > TERMINAL_RESULT_CHARS
404
- ? `${compact.slice(0, TERMINAL_RESULT_CHARS - 1)}…`
405
- : compact;
406
- }
407
360
  function observeRun(stateDir) {
408
361
  try {
409
362
  const status = AsyncRuns.getRunStatus(stateDir);
@@ -771,18 +724,20 @@ export function detectRunTransitions(previous, summary) {
771
724
  }
772
725
  return transitions;
773
726
  }
774
- function normalizeOutboxDelivery(value) {
727
+ function normalizeTraceAttention(value) {
775
728
  return value === "notify" || value === "followup" ? value : "log";
776
729
  }
777
- function normalizeOutboxLevel(value) {
730
+ function normalizeTraceLevel(value) {
778
731
  return value === "warning" || value === "error" ? value : "info";
779
732
  }
780
- function parseOutboxRecord(raw, run, index) {
733
+ function parseAttentionRecord(raw, run, index) {
781
734
  if (!run.stateDir)
782
735
  return undefined;
783
- const event = typeof raw.event === "string" && raw.event.trim()
784
- ? raw.event.trim()
785
- : "run.event";
736
+ const event = typeof raw.kind === "string" && raw.kind.trim()
737
+ ? raw.kind.trim()
738
+ : typeof raw.event === "string" && raw.event.trim()
739
+ ? raw.event.trim()
740
+ : "run.event";
786
741
  const summary = typeof raw.summary === "string" && raw.summary.trim()
787
742
  ? raw.summary.trim()
788
743
  : event;
@@ -795,10 +750,12 @@ function parseOutboxRecord(raw, run, index) {
795
750
  return {
796
751
  ...(raw.body !== undefined ? { body: raw.body } : {}),
797
752
  ...(raw.data !== undefined ? { data: raw.data } : {}),
798
- delivery: normalizeOutboxDelivery(raw.delivery),
799
- event,
753
+ attention: raw.attention === "notify" || raw.attention === "followup"
754
+ ? raw.attention
755
+ : normalizeTraceAttention(raw.delivery),
800
756
  id,
801
- level: normalizeOutboxLevel(raw.level),
757
+ kind: event,
758
+ level: normalizeTraceLevel(raw.level),
802
759
  ...(raw.metadata &&
803
760
  typeof raw.metadata === "object" &&
804
761
  !Array.isArray(raw.metadata)
@@ -810,9 +767,13 @@ function parseOutboxRecord(raw, run, index) {
810
767
  ts,
811
768
  };
812
769
  }
813
- function readOutboxRecords(run) {
770
+ function readTraceAttentionRecords(run) {
814
771
  if (!run.stateDir)
815
772
  return [];
773
+ const tracePath = join(run.stateDir, "trace.jsonl");
774
+ if (existsSync(tracePath)) {
775
+ return readJsonlFileResilient(tracePath).records;
776
+ }
816
777
  return readJsonlFileResilient(join(run.stateDir, "outbox.jsonl")).records;
817
778
  }
818
779
  export function pruneRunObservationState(previousStatuses, previousLineCounts, summary, terminalRuns = [], seenEventIds = new Map()) {
@@ -839,11 +800,11 @@ export function pruneRunObservationState(previousStatuses, previousLineCounts, s
839
800
  }
840
801
  }
841
802
  }
842
- export function detectRunOutboxEvents(previousLineCounts, summary, seenEventIds = new Map()) {
803
+ export function detectRunAttentionEvents(previousLineCounts, summary, seenEventIds = new Map()) {
843
804
  const events = [];
844
805
  for (const run of summary.runs) {
845
806
  const key = run.stateDir ?? run.run;
846
- const records = readOutboxRecords(run);
807
+ const records = readTraceAttentionRecords(run);
847
808
  const previousCount = previousLineCounts.get(key) ?? 0;
848
809
  if (run.notificationPolicy === "silent") {
849
810
  previousLineCounts.set(key, records.length);
@@ -853,7 +814,7 @@ export function detectRunOutboxEvents(previousLineCounts, summary, seenEventIds
853
814
  const start = Math.min(previousCount, records.length);
854
815
  const seen = seenEventIds.get(key) ?? new Set();
855
816
  for (let index = start; index < records.length; index += 1) {
856
- const event = parseOutboxRecord(records[index], run, index);
817
+ const event = parseAttentionRecord(records[index], run, index);
857
818
  if (!event || seen.has(event.id))
858
819
  continue;
859
820
  events.push(event);
@@ -864,14 +825,14 @@ export function detectRunOutboxEvents(previousLineCounts, summary, seenEventIds
864
825
  }
865
826
  return events;
866
827
  }
867
- export function getRunOutboxNotificationType(event) {
828
+ export function getRunAttentionNotificationType(event) {
868
829
  return event.level;
869
830
  }
870
- export function shouldNotifyRunOutboxEvent(event) {
871
- return event.delivery === "notify" || event.delivery === "followup";
831
+ export function shouldNotifyRunAttentionEvent(event) {
832
+ return event.attention === "notify" || event.attention === "followup";
872
833
  }
873
- export function shouldSendRunOutboxFollowUp(event) {
874
- return event.delivery === "followup";
834
+ export function shouldSendRunAttentionFollowUp(event) {
835
+ return event.attention === "followup";
875
836
  }
876
837
  function commonDirectory(paths) {
877
838
  if (paths.length === 0)
@@ -919,7 +880,7 @@ function formatNamedArtifacts(artifacts) {
919
880
  return "";
920
881
  return formatPathGroup("Artifacts", Object.values(artifacts).filter((path) => typeof path === "string"));
921
882
  }
922
- function getOutboxField(event, key) {
883
+ function getAttentionDataField(event, key) {
923
884
  return event.data &&
924
885
  typeof event.data === "object" &&
925
886
  !Array.isArray(event.data)
@@ -935,10 +896,10 @@ function formatBodyPreview(body) {
935
896
  return "";
936
897
  return `\nBody: ${compact.length > 500 ? `${compact.slice(0, 500)}…` : compact}`;
937
898
  }
938
- export function formatRunOutboxMessage(event) {
939
- if (event.event === "command.done")
899
+ export function formatRunAttentionMessage(event) {
900
+ if (event.kind === "command.done")
940
901
  return `Run ${event.run}: ${event.summary}`;
941
- return `Run ${event.run}: ${event.summary}${formatBodyPreview(event.body)}${formatNamedArtifacts(getOutboxField(event, "artifacts"))}${formatRunFileList(getOutboxField(event, "run_files"))}`;
902
+ return `Run ${event.run}: ${event.summary}${formatBodyPreview(event.body)}${formatNamedArtifacts(getAttentionDataField(event, "artifacts"))}${formatRunFileList(getAttentionDataField(event, "run_files"))}`;
942
903
  }
943
904
  export function getRunTransitionNotificationType(transition) {
944
905
  if (transition.to === "done" || transition.to === "cancelled")
package/dist/lib/pi.d.ts CHANGED
@@ -10,7 +10,7 @@ export interface PiNotificationSink {
10
10
  sendFollowUp(message: {
11
11
  customType: string;
12
12
  content: string;
13
- display: true;
13
+ display: false;
14
14
  details: unknown;
15
15
  }): void;
16
16
  }
@@ -6,7 +6,7 @@
6
6
  export declare const REGISTER_TOOL_DESCRIPTION: string;
7
7
  export declare const REGISTER_TOOL_PROMPT_SNIPPET = "Register persistent command templates as agent-callable tools";
8
8
  export declare const REGISTER_TOOL_GUIDELINES: string[];
9
- export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync and shell-free: string leaves split into executable + argv, so operators such as && are literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, output.\n- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.\n- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.\n- Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.\n- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.\n- Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.\n- Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid runtime/FIFO/outbox vocabulary in public guidance.\n- Run state lives under ~/.pi/agent/tmp/pi-actors/runs. Inspect intentionally and avoid busy-polling. Terminal and coordinator-bound notifications queue as Pi follow-ups so concurrently completed actors can reach the coordinator after current work instead of steering between tool calls. Terminal follow-up content stays minimal: run, status, one base path, and relative artifact names only; semantic output stays in non-LLM details and run state. When a deferred actor result gates the next step, wait for its terminal follow-up; do not schedule continuation loops, repeatedly inspect, or mutate its reviewed scope while it runs. Inspect early only for an operator request, a meaningful actor event, or diagnosis of an overdue/stuck run.\n- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.\n- Prefer maintained packaged recipes/pipelines with spawn file=<recipe> before ad hoc scripts/wrappers; review swarms inherit current model/thinking, preflight before fanout, and expose quorum/concurrency/TTL knobs unless explicit args are passed.\n- For any non-trivial actor use or pi-actors change, read the bundled actors skill first. Before launching multiple actors/subagents for parallel implementation, independent artifact generation, delegated audit, or review, also read the bundled swarm skill; the coordinator owns decomposition, disjoint scopes, launch correctness, integration, and final validation. For deeper guidance, inspect installed extension sources/docs/recipes because README/docs are not automatically in context.";
9
+ export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync and shell-free: string leaves split into executable + argv, so operators such as && are literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, output.\n- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.\n- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.\n- Recipes own template directly and may declare metadata/defaults/imports/control/artifacts; files >1 MiB or import depth >32 fail closed.\n- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.\n- Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.\n- Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid internal transport vocabulary in public guidance.\n- Run state lives under ~/.pi/agent/tmp/pi-actors/runs. Inspect intentionally and avoid busy-polling. Terminal and coordinator-bound notifications queue as Pi follow-ups so concurrently completed actors can reach the coordinator after current work instead of steering between tool calls. Terminal follow-up content stays minimal: run, status, one base path, and relative artifact names only; semantic output stays in non-LLM details and run state. When a deferred actor result gates the next step, wait for its terminal follow-up; do not schedule continuation loops, repeatedly inspect, or mutate its reviewed scope while it runs. Inspect early only for an operator request, a meaningful actor event, or diagnosis of an overdue/stuck run.\n- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.\n- Prefer maintained packaged recipes/pipelines with spawn file=<recipe> before ad hoc scripts/wrappers; review swarms inherit current model/thinking, preflight before fanout, and expose quorum/concurrency/TTL knobs unless explicit args are passed.\n- For any non-trivial actor use or pi-actors change, read the bundled actors skill first. Before launching multiple actors/subagents for parallel implementation, independent artifact generation, delegated audit, or review, also read the bundled swarm skill; the coordinator owns decomposition, disjoint scopes, launch correctness, integration, and final validation. For deeper guidance, inspect installed extension sources/docs/recipes because README/docs are not automatically in context.";
10
10
  export declare const REGISTER_TOOL_PARAM_DESCRIPTIONS: {
11
11
  readonly name: "Tool name in snake_case (e.g., 'transcribe')";
12
12
  readonly description: "Describe what the tool does for the LLM. Required unless deleting; omitted updates keep the old description.";
@@ -19,10 +19,10 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
19
19
  - Command templates stay sync and shell-free: string leaves split into executable + argv, so operators such as && are literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, output.
20
20
  - Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.
21
21
  - ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.
22
- - Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.
22
+ - Recipes own template directly and may declare metadata/defaults/imports/control/artifacts; files >1 MiB or import depth >32 fail closed.
23
23
  - Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.
24
24
  - Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.
25
- - Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid runtime/FIFO/outbox vocabulary in public guidance.
25
+ - Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid internal transport vocabulary in public guidance.
26
26
  - Run state lives under ~/.pi/agent/tmp/pi-actors/runs. Inspect intentionally and avoid busy-polling. Terminal and coordinator-bound notifications queue as Pi follow-ups so concurrently completed actors can reach the coordinator after current work instead of steering between tool calls. Terminal follow-up content stays minimal: run, status, one base path, and relative artifact names only; semantic output stays in non-LLM details and run state. When a deferred actor result gates the next step, wait for its terminal follow-up; do not schedule continuation loops, repeatedly inspect, or mutate its reviewed scope while it runs. Inspect early only for an operator request, a meaningful actor event, or diagnosis of an overdue/stuck run.
27
27
  - Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.
28
28
  - Prefer maintained packaged recipes/pipelines with spawn file=<recipe> before ad hoc scripts/wrappers; review swarms inherit current model/thinking, preflight before fanout, and expose quorum/concurrency/TTL knobs unless explicit args are passed.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Recipe Control contract.
3
+ * Zones: actor-local action declarations, normalization, reserved-action fencing
4
+ * Owns pure Recipe control validation; Recipe loading and Run capture stay in recipe/run domains.
5
+ */
6
+ export declare function normalizeRecipeControl(value: unknown): string[] | undefined;
7
+ export declare function assertRecipeHasNoMailbox(recipe: Record<string, unknown>): void;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Recipe Control contract.
3
+ * Zones: actor-local action declarations, normalization, reserved-action fencing
4
+ * Owns pure Recipe control validation; Recipe loading and Run capture stay in recipe/run domains.
5
+ */
6
+ const ACTION_PATTERN = /^[a-z][a-z0-9_-]*(?:\.[a-z0-9_-]+)*$/;
7
+ const RUNTIME_RUN_ACTIONS = new Set(["archive", "kill", "prune"]);
8
+ export function normalizeRecipeControl(value) {
9
+ if (value === undefined)
10
+ return undefined;
11
+ if (!Array.isArray(value)) {
12
+ throw new Error("recipe.control must be an array of action strings");
13
+ }
14
+ const actions = [];
15
+ const seen = new Set();
16
+ for (const raw of value) {
17
+ if (typeof raw !== "string" || !raw.trim()) {
18
+ throw new Error("recipe.control actions must be non-empty strings");
19
+ }
20
+ const action = raw.trim();
21
+ if (!ACTION_PATTERN.test(action)) {
22
+ throw new Error(`invalid recipe.control action: ${action}`);
23
+ }
24
+ if (RUNTIME_RUN_ACTIONS.has(action)) {
25
+ throw new Error(`recipe.control action is runtime-reserved and must not be declared: ${action}`);
26
+ }
27
+ if (seen.has(action)) {
28
+ throw new Error(`duplicate recipe.control action: ${action}`);
29
+ }
30
+ seen.add(action);
31
+ actions.push(action);
32
+ }
33
+ return actions;
34
+ }
35
+ export function assertRecipeHasNoMailbox(recipe) {
36
+ if (Object.hasOwn(recipe, "mailbox")) {
37
+ throw new Error("recipe.mailbox was removed; replace it with control: [\"action\"] for actor-local inputs and emit Trace events for outputs");
38
+ }
39
+ }
@@ -356,6 +356,8 @@ function diagnosticSuggestedAction(message) {
356
356
  return "break the import cycle";
357
357
  if (/exceeds.*size/i.test(message))
358
358
  return "split large prompt or data into separate files";
359
+ if (/recipe\.mailbox was removed/i.test(message))
360
+ return "replace mailbox.accepts with control actions and route outputs to Trace events";
359
361
  if (/repeat must/i.test(message))
360
362
  return "use a positive repeat count or an array-typed repeat source";
361
363
  if (/shadows/i.test(message))
@@ -11,19 +11,6 @@ export interface TemplateRecipeImportBinding {
11
11
  values?: Record<string, unknown>;
12
12
  }
13
13
  export type TemplateRecipeImport = string | TemplateRecipeImportBinding;
14
- export type TemplateRecipeMailboxEntry = string | {
15
- type: string;
16
- body_schema?: unknown;
17
- ack?: boolean | string;
18
- idempotency?: string;
19
- requires_response?: boolean;
20
- level?: string;
21
- summary?: string;
22
- };
23
- export interface TemplateRecipeMailbox {
24
- accepts?: TemplateRecipeMailboxEntry[];
25
- emits?: TemplateRecipeMailboxEntry[];
26
- }
27
14
  export interface TemplateRecipeDefinition {
28
15
  name?: string;
29
16
  description?: string;
@@ -42,7 +29,7 @@ export interface TemplateRecipeDefinition {
42
29
  accept_output?: "review_evidence";
43
30
  output?: string;
44
31
  artifacts?: Record<string, string>;
45
- mailbox?: TemplateRecipeMailbox;
32
+ control?: string[];
46
33
  retire_when?: "children_terminal";
47
34
  retry?: number | string;
48
35
  failure?: CommandTemplates.CommandTemplateFailureScope;
@@ -8,6 +8,7 @@ import { homedir } from "node:os";
8
8
  import { basename, dirname, extname, resolve } from "node:path";
9
9
  import * as CommandTemplates from "./command-templates.js";
10
10
  import * as Paths from "./paths.js";
11
+ import * as RecipeControl from "./recipe-control.js";
11
12
  const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
12
13
  const MAX_RECIPE_IMPORT_DEPTH = 32;
13
14
  function hasWhitespace(value) {
@@ -629,6 +630,7 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
629
630
  const raw = readRawRecipeConfig(path);
630
631
  if (!raw || !Object.hasOwn(raw, "template"))
631
632
  return undefined;
633
+ RecipeControl.assertRecipeHasNoMailbox(raw);
632
634
  const imports = {};
633
635
  for (const [alias, binding] of Object.entries(getRecipeImports(raw))) {
634
636
  const importPath = resolveRecipeImportPath(getImportFrom(binding), dirname(path));
@@ -671,9 +673,9 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
671
673
  const artifactSource = isRecord(substituted.artifacts)
672
674
  ? substituted.artifacts
673
675
  : delegated?.artifacts;
674
- const mailboxSource = isRecord(substituted.mailbox)
675
- ? substituted.mailbox
676
- : delegated?.mailbox;
676
+ const control = RecipeControl.normalizeRecipeControl(Object.hasOwn(substituted, "control")
677
+ ? substituted.control
678
+ : delegated?.control);
677
679
  return {
678
680
  name: recipeName,
679
681
  ...(typeof substituted.description === "string" &&
@@ -743,24 +745,7 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
743
745
  artifacts: Object.fromEntries(Object.entries(artifactSource).filter((entry) => typeof entry[1] === "string")),
744
746
  }
745
747
  : {}),
746
- ...(isRecord(mailboxSource)
747
- ? {
748
- mailbox: {
749
- ...(Array.isArray(mailboxSource.accepts)
750
- ? {
751
- accepts: mailboxSource.accepts.filter((value) => typeof value === "string" ||
752
- (isRecord(value) && typeof value.type === "string")),
753
- }
754
- : {}),
755
- ...(Array.isArray(mailboxSource.emits)
756
- ? {
757
- emits: mailboxSource.emits.filter((value) => typeof value === "string" ||
758
- (isRecord(value) && typeof value.type === "string")),
759
- }
760
- : {}),
761
- },
762
- }
763
- : {}),
748
+ ...(control !== undefined ? { control } : {}),
764
749
  ...(substituted.retire_when === "children_terminal" ||
765
750
  delegated?.retire_when === "children_terminal"
766
751
  ? { retire_when: "children_terminal" }
@@ -68,17 +68,13 @@ function templateShape(value) {
68
68
  function contractShape(recipe) {
69
69
  if (!recipe)
70
70
  return undefined;
71
- const mailbox = recipe.mailbox && typeof recipe.mailbox === "object" && !Array.isArray(recipe.mailbox)
72
- ? recipe.mailbox
73
- : {};
74
71
  return {
75
72
  argument_count: countArray(recipe.args),
76
73
  artifact_count: countRecord(recipe.artifacts),
77
74
  async: recipe.async === true,
78
75
  default_count: countRecord(recipe.defaults),
79
76
  import_count: countRecord(recipe.imports),
80
- mailbox_accepts_count: countArray(mailbox.accepts),
81
- mailbox_emits_count: countArray(mailbox.emits),
77
+ control_action_count: countArray(recipe.control),
82
78
  template_shape: templateShape(recipe.template),
83
79
  };
84
80
  }
@@ -19,7 +19,7 @@ export function createRunUiRuntime(deps) {
19
19
  attempted: retirementAttempts,
20
20
  cancelRun: (candidate) => AsyncRuns.cancelRun(candidate.stateDir),
21
21
  notify: (message, level) => ctx.ui.notify(message, level),
22
- sendStop: (candidate) => AsyncRuns.sendRunMessage(candidate.stateDir, "stop"),
22
+ sendStop: async (candidate) => AsyncRuns.cancelRun(candidate.stateDir),
23
23
  });
24
24
  };
25
25
  const update = (ctx, notify = false, terminalOnly = false) => {
@@ -33,7 +33,7 @@ export function createRunUiRuntime(deps) {
33
33
  Observability.deliverRunTransitionNotifications(snapshot.transitions, sink, terminalNotificationsInFlight);
34
34
  Observability.pruneRunUiObservationState(observation, snapshot);
35
35
  if (!terminalOnly) {
36
- Observability.deliverRunOutboxNotifications(snapshot.outboxEvents, sink);
36
+ Observability.deliverRunAttentionNotifications(snapshot.attentionEvents, sink);
37
37
  }
38
38
  };
39
39
  const reportDiagnostics = (ctx) => {