@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
package/lib/async-runs.ts CHANGED
@@ -47,40 +47,35 @@ import {
47
47
  signalOwnedRunProcess,
48
48
  type RunProcessSignalPlan,
49
49
  } from "./runs-control.ts";
50
- import {
51
- buildRunOutboxEventPayload,
52
- parseRunOutboxEventLine,
53
- type RunOutboxEvent,
54
- } from "./runs-outbox.ts";
55
50
  import { claimRunStateDirectory } from "./runs-ownership.ts";
56
- import { archiveTerminalRun, pruneTerminalRun } from "./runs-retention.ts";
51
+ import {
52
+ appendRunRetentionEvidence,
53
+ archiveTerminalRun,
54
+ pruneTerminalRun,
55
+ type RunRetentionAction,
56
+ } from "./runs-retention.ts";
57
57
  import {
58
58
  captureRunProcessIdentity,
59
59
  verifyRunProcessIdentity,
60
60
  type RunProcessIdentity,
61
61
  } from "./runs-process.ts";
62
+ import * as RuntimeIdentity from "./runtime-identity.ts";
62
63
  import * as RunsStart from "./runs-start.ts";
64
+ import { appendRunTraceEvent } from "./runs-trace.ts";
63
65
  import * as RunsIndex from "./runs-index.ts";
64
66
  import * as RunsParentTeardown from "./runs-parent-teardown.ts";
65
67
  import {
66
- claimRunInboxMessageInStateDir,
67
- parseRunInboxLine,
68
- processRunInboxMessagesInStateDir,
69
- readRunInboxMessagesFromStateDir,
70
- runInboxFile,
71
- type ProcessRunInboxResult,
72
- type RunInboxMessage,
73
- type RunInboxStatus,
74
- updateRunInboxMessageStatusInStateDir,
75
- } from "./runs-mailbox.ts";
68
+ appendRunControlInStateDir,
69
+ updateRunControlStatusInStateDir,
70
+ } from "./runs-controls.ts";
76
71
  import {
77
- deliverRunMessage,
78
- type SendRunMessageOptions,
79
- } from "./runs-messages.ts";
72
+ deliverRunControl,
73
+ type DeliverRunControlOptions,
74
+ type DeliverRunControlRequest,
75
+ } from "./runs-control-delivery.ts";
80
76
  import {
81
77
  buildRunStatus,
82
78
  tailFile,
83
- tailLines,
84
79
  type AsyncRunStatus,
85
80
  } from "./runs-status.ts";
86
81
  import { readJsonFileResilient } from "./state-readers.ts";
@@ -91,7 +86,7 @@ export type AsyncRunLaunchSource = "spawn" | "tool";
91
86
 
92
87
  export interface AsyncRunControlEndpoint {
93
88
  path: string;
94
- type: "fifo" | "mailbox" | "named-pipe";
89
+ type: "fifo" | "named-pipe";
95
90
  }
96
91
 
97
92
  export function normalizeRunTransportContext(
@@ -119,7 +114,7 @@ export function normalizeRunTransportContext(
119
114
 
120
115
  export interface AsyncRunStartParams {
121
116
  async?: boolean;
122
- control?: AsyncRunControlEndpoint;
117
+ control_endpoint?: AsyncRunControlEndpoint;
123
118
  file?: string;
124
119
  launch_source?: AsyncRunLaunchSource;
125
120
  lifecycleHooks?: {
@@ -147,7 +142,7 @@ export interface AsyncRunStartParams {
147
142
  accept_output?: "review_evidence";
148
143
  output?: string;
149
144
  artifacts?: Record<string, RunArtifactDeclaration>;
150
- mailbox?: RecipesReferences.TemplateRecipeMailbox;
145
+ control?: string[];
151
146
  notification_policy?: "normal" | "silent";
152
147
  retire_when?: "children_terminal";
153
148
  retry?: number | string;
@@ -179,13 +174,14 @@ export interface AsyncRunMeta {
179
174
  run: string;
180
175
  run_instance_id: string;
181
176
  state_dir: string;
177
+ state_schema: typeof RuntimeIdentity.RUN_STATE_SCHEMA;
182
178
  status: AsyncRunStatus;
183
179
  tool?: string;
184
180
  template: CommandTemplateValue;
185
181
  values: Record<string, unknown>;
186
182
  artifacts?: Record<string, RunArtifactDeclaration>;
187
- control?: AsyncRunControlEndpoint;
188
- mailbox?: RecipesReferences.TemplateRecipeMailbox;
183
+ control?: string[];
184
+ control_endpoint?: AsyncRunControlEndpoint;
189
185
  model_policy?: CurrentPolicyProvenance;
190
186
  notification_policy?: "normal" | "silent";
191
187
  process_identity?: RunProcessIdentity;
@@ -471,13 +467,26 @@ export function startRun(
471
467
  const resolved = resolveRunTemplate(startParams);
472
468
  const run = safeRunId(startParams.run_id);
473
469
  const stateDir = resolveStateDir(startParams, run);
470
+ const recipeFile = startParams.file
471
+ ? resolveRecipeFile(startParams.file)
472
+ : undefined;
473
+ const packagedRecipeRoot = resolve(Paths.getPackagedRecipeRoot());
474
+ const recipeRelation = recipeFile
475
+ ? relative(packagedRecipeRoot, recipeFile)
476
+ : undefined;
477
+ const packagedRepo =
478
+ startParams.defaults?.repo === "~/.pi/agent/extensions/pi-actors" &&
479
+ recipeRelation &&
480
+ !recipeRelation.startsWith("..") &&
481
+ !isAbsolute(recipeRelation)
482
+ ? dirname(packagedRecipeRoot)
483
+ : undefined;
474
484
  const values = {
485
+ ...(packagedRepo ? { repo: packagedRepo } : {}),
475
486
  ...(startParams.values || {}),
476
- actor_address: `run:${run}`,
477
- communication_file: join(stateDir, "communication.json"),
478
- default_room: `room:${run}`,
479
487
  run_id: run,
480
488
  state_dir: stateDir,
489
+ trace_file: join(stateDir, "trace.jsonl"),
481
490
  };
482
491
  const modelPolicy = describeCurrentPolicyProvenance({
483
492
  defaults: startParams.defaults,
@@ -501,9 +510,6 @@ export function startRun(
501
510
  prepareStateDirForStart(stateDir);
502
511
  const stdout = join(stateDir, "stdout.log");
503
512
  const stderr = join(stateDir, "stderr.log");
504
- const recipeFile = startParams.file
505
- ? resolveRecipeFile(startParams.file)
506
- : undefined;
507
513
  const recipe = startParams.name || getRunIdFromFile(recipeFile);
508
514
  const includeActorRecipeContext =
509
515
  startParams.actor_context !== false &&
@@ -552,6 +558,7 @@ export function startRun(
552
558
  run,
553
559
  run_instance_id: randomUUID(),
554
560
  state_dir: stateDir,
561
+ state_schema: RuntimeIdentity.RUN_STATE_SCHEMA,
555
562
  status: "running",
556
563
  ...(startParams.tool ? { tool: startParams.tool } : {}),
557
564
  template: resolved.template,
@@ -559,7 +566,9 @@ export function startRun(
559
566
  model_policy: modelPolicy,
560
567
  ...(artifacts ? { artifacts } : {}),
561
568
  ...(startParams.control ? { control: startParams.control } : {}),
562
- ...(startParams.mailbox ? { mailbox: startParams.mailbox } : {}),
569
+ ...(startParams.control_endpoint
570
+ ? { control_endpoint: startParams.control_endpoint }
571
+ : {}),
563
572
  ...(startParams.notification_policy === "silent"
564
573
  ? { notification_policy: "silent" as const }
565
574
  : {}),
@@ -596,11 +605,14 @@ export function startRun(
596
605
  );
597
606
  if (processIdentity) meta.process_identity = processIdentity;
598
607
  writeJsonAtomic(join(stateDir, "run.json"), meta);
599
- writeFileSync(
600
- join(stateDir, "events.jsonl"),
601
- `${JSON.stringify({ event: "run.start", run, run_instance_id: meta.run_instance_id, pid: meta.pid, ts: new Date().toISOString() })}\n`,
602
- { flag: "a" },
603
- );
608
+ appendRunTraceEvent(stateDir, {
609
+ kind: "run.start",
610
+ summary: `Run ${run} started`,
611
+ data: {
612
+ pid: meta.pid,
613
+ run_instance_id: meta.run_instance_id,
614
+ },
615
+ });
604
616
  child.unref();
605
617
  return meta;
606
618
  } finally {
@@ -608,13 +620,6 @@ export function startRun(
608
620
  }
609
621
  }
610
622
 
611
- export { parseRunOutboxEventLine } from "./runs-outbox.ts";
612
- export type {
613
- RunOutboxDelivery,
614
- RunOutboxEvent,
615
- RunOutboxLevel,
616
- } from "./runs-outbox.ts";
617
-
618
623
  function resolveRunStateDir(runOrDir: string): string {
619
624
  return resolve(
620
625
  /[\\/]/u.test(runOrDir)
@@ -714,21 +719,19 @@ export function teardownRunsOwnedByParent(
714
719
  ) {
715
720
  throw new Error("run generation changed before teardown evidence");
716
721
  }
717
- writeFileSync(
718
- join(attempt.stateDir, "events.jsonl"),
719
- `${JSON.stringify({
720
- event: "run.parent_teardown",
721
- ownerId: attempt.ownerId,
722
+ appendRunTraceEvent(attempt.stateDir, {
723
+ kind: "run.parent_teardown",
724
+ summary: `Parent teardown ${attempt.outcome}`,
725
+ data: {
722
726
  outcome: attempt.outcome,
727
+ owner_id: attempt.ownerId,
723
728
  reason: attempt.reason,
724
729
  run: attempt.run,
725
730
  run_instance_id: attempt.runInstanceId,
726
- type: "control.kill",
727
731
  trigger: options.trigger ?? "parent_shutdown",
728
- ts: new Date().toISOString(),
729
- })}\n`,
730
- { flag: "a" },
731
- );
732
+ },
733
+ ...(attempt.outcome === "failed" ? { level: "error" as const } : {}),
734
+ });
732
735
  },
733
736
  });
734
737
  const summaryPath = join(
@@ -764,139 +767,63 @@ export function teardownRunsOwnedByParent(
764
767
  export function tailRun(runOrDir: string, lines = 40): string {
765
768
  const status = getRunStatus(runOrDir);
766
769
  const stateDir = String(status.state_dir);
767
- const events = tailFile(join(stateDir, "events.jsonl"), lines);
768
- if (events) return events;
770
+ const trace = tailFile(join(stateDir, "trace.jsonl"), lines);
771
+ if (trace) return trace;
769
772
  return (
770
773
  tailFile(join(stateDir, "stdout.log"), lines) ||
771
774
  tailFile(join(stateDir, "stderr.log"), lines)
772
775
  );
773
776
  }
774
777
 
775
- export function readRunEvents(runOrDir: string, lines = 40): RunOutboxEvent[] {
776
- const status = getRunStatus(runOrDir);
777
- const stateDir = String(status.state_dir);
778
- const run = String(status.run ?? runOrDir);
779
- return tailLines(join(stateDir, "outbox.jsonl"), lines)
780
- .map((line, index) => parseRunOutboxEventLine(line, run, stateDir, index))
781
- .filter((event): event is RunOutboxEvent => Boolean(event));
782
- }
783
-
784
778
  export type {
785
- ProcessRunInboxResult,
786
- RunInboxMessage,
787
- RunInboxStatus,
788
- } from "./runs-mailbox.ts";
779
+ DeliverRunControlOptions,
780
+ DeliverRunControlRequest,
781
+ } from "./runs-control-delivery.ts";
789
782
 
790
- export function readRunInboxMessages(
791
- runOrDir: string,
792
- lines = 40,
793
- ): RunInboxMessage[] {
794
- const status = getRunStatus(runOrDir);
795
- const stateDir = String(status.state_dir);
796
- return tailLines(runInboxFile(stateDir), lines)
797
- .map(parseRunInboxLine)
798
- .filter((message): message is RunInboxMessage => Boolean(message));
799
- }
800
-
801
- export function updateRunInboxMessageStatus(
802
- runOrDir: string,
803
- id: string,
804
- nextStatus: RunInboxStatus,
805
- metadata: Record<string, unknown> = {},
806
- ): boolean {
807
- const status = getRunStatus(runOrDir);
808
- const stateDir = String(status.state_dir);
809
- return updateRunInboxMessageStatusInStateDir(
810
- stateDir,
811
- id,
812
- nextStatus,
813
- metadata,
814
- );
815
- }
816
-
817
- export function claimRunInboxMessage(
818
- runOrDir: string,
819
- owner = "runtime",
820
- statuses: string[] = ["queued"],
821
- ): RunInboxMessage | undefined {
822
- const status = getRunStatus(runOrDir);
823
- const stateDir = String(status.state_dir);
824
- return claimRunInboxMessageInStateDir(stateDir, owner, statuses);
825
- }
826
-
827
- export async function processRunInboxMessages(
828
- runOrDir: string,
829
- handler: (message: RunInboxMessage) => Promise<void> | void,
830
- options: { limit?: number; owner?: string; statuses?: string[] } = {},
831
- ): Promise<ProcessRunInboxResult> {
832
- const status = getRunStatus(runOrDir);
833
- return processRunInboxMessagesInStateDir(
834
- String(status.state_dir),
835
- handler,
836
- options,
837
- );
838
- }
839
-
840
- export function appendRunOutboxEvent(
841
- runOrDir: string,
842
- event: {
843
- body?: unknown;
844
- correlation_id?: string;
845
- data?: unknown;
846
- delivery?: string;
847
- event?: string;
848
- from?: string;
849
- level?: string;
850
- metadata?: Record<string, unknown>;
851
- reply_to?: string;
852
- summary?: string;
853
- to?: string;
854
- type?: string;
855
- },
856
- ): Record<string, unknown> {
857
- const status = getRunStatus(runOrDir);
858
- const stateDir = String(status.state_dir);
859
- const run = String(status.run ?? runOrDir);
860
- const payload = buildRunOutboxEventPayload(run, event);
861
- const line = `${JSON.stringify(payload)}\n`;
862
- writeFileSync(join(stateDir, "outbox.jsonl"), line, { flag: "a" });
863
- return {
864
- bytes: Buffer.byteLength(line),
865
- outbox: "outbox.jsonl",
866
- run,
867
- sent: true,
868
- state_dir: stateDir,
869
- };
783
+ export interface SendRunControlOptions extends DeliverRunControlOptions {
784
+ ownerId?: string;
870
785
  }
871
786
 
872
- export type { SendRunMessageOptions } from "./runs-messages.ts";
873
-
874
- export async function sendRunMessage(
787
+ export async function sendRunControl(
875
788
  runOrDir: string,
876
- message: string,
877
- options: SendRunMessageOptions = {},
789
+ request: DeliverRunControlRequest,
790
+ options: SendRunControlOptions = {},
878
791
  ): Promise<Record<string, unknown>> {
879
- const status = getRunStatus(runOrDir);
880
- const stateDir = String(status.state_dir);
881
- const run = String(status.run ?? runOrDir);
882
- const pid = Number(status.pid || 0);
883
- const identity = verifyRunProcessIdentity(
884
- pid,
885
- status.process_identity as RunProcessIdentity | undefined,
886
- );
887
- if (status.status !== "running") {
888
- if (
889
- identity.status === "owner_mismatch" ||
890
- identity.status === "unsupported_proof"
891
- ) {
892
- throw new Error(`Run process identity ${identity.status}: ${run}`);
792
+ const stateDir = resolveRunStateDir(runOrDir);
793
+ const releaseControlLock = RunsStart.acquireStateStartLock(stateDir);
794
+ try {
795
+ const status = getRunStatus(stateDir);
796
+ const run = String(status.run ?? runOrDir);
797
+ if (options.ownerId !== undefined && status.ownerId !== options.ownerId) {
798
+ throw Object.assign(new Error(`Run ownership changed: ${run}`), {
799
+ reason: "owner_mismatch",
800
+ });
893
801
  }
894
- throw new Error(`Run is not running: ${run}`);
895
- }
896
- if (!identity.valid) {
897
- throw new Error(`Run process identity ${identity.status}: ${run}`);
802
+ if (status.run_instance_id !== request.run_instance_id) {
803
+ throw Object.assign(new Error(`Run generation changed: ${run}`), {
804
+ reason: "generation_mismatch",
805
+ });
806
+ }
807
+ if (status.status !== "running") {
808
+ throw Object.assign(new Error(`Run is not running: ${run}`), {
809
+ reason: "terminal_state",
810
+ });
811
+ }
812
+ const pid = Number(status.pid || 0);
813
+ const identity = verifyRunProcessIdentity(
814
+ pid,
815
+ status.process_identity as RunProcessIdentity | undefined,
816
+ );
817
+ if (!identity.valid) {
818
+ throw Object.assign(
819
+ new Error(`Run process identity ${identity.status}: ${run}`),
820
+ { reason: `process_identity_${identity.status}` },
821
+ );
822
+ }
823
+ return await deliverRunControl(run, stateDir, request, options);
824
+ } finally {
825
+ releaseControlLock();
898
826
  }
899
- return deliverRunMessage(status, run, stateDir, message, options);
900
827
  }
901
828
 
902
829
  export { getRunProcessSignalPlan } from "./runs-control.ts";
@@ -917,13 +844,13 @@ function markTerminalProgress(
917
844
  );
918
845
  }
919
846
 
920
- function finalizeInterruptedReviewEvidence(
847
+ function finalizeInterruptedExecution(
921
848
  stateDir: string,
922
849
  phase: "cancelled" | "killed",
923
850
  signal: NodeJS.Signals,
924
851
  ): void {
925
- const evidencePath = join(stateDir, "review-evidence.json");
926
- const manifest = readJson(evidencePath);
852
+ const executionPath = join(stateDir, "execution.json");
853
+ const manifest = readJson(executionPath);
927
854
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) return;
928
855
  const record = manifest as Record<string, unknown>;
929
856
  if (!Array.isArray(record.commands)) return;
@@ -969,7 +896,7 @@ function finalizeInterruptedReviewEvidence(
969
896
  : {}),
970
897
  };
971
898
  });
972
- writeJsonAtomic(evidencePath, {
899
+ writeJsonAtomic(executionPath, {
973
900
  ...record,
974
901
  status: phase,
975
902
  commands,
@@ -1006,9 +933,37 @@ function stopRun(
1006
933
  ) {
1007
934
  return { stopped: false, reason: "run generation changed", status };
1008
935
  }
936
+ const control =
937
+ event === "run.kill" && typeof status.run_instance_id === "string"
938
+ ? appendRunControlInStateDir(stateDir, {
939
+ action: "kill",
940
+ run_instance_id: status.run_instance_id,
941
+ })
942
+ : undefined;
943
+ if (control) {
944
+ updateRunControlStatusInStateDir(
945
+ stateDir,
946
+ control.id,
947
+ "claimed",
948
+ {},
949
+ ["queued"],
950
+ );
951
+ }
952
+ const finish = (result: Record<string, unknown>): Record<string, unknown> => {
953
+ if (!control) return result;
954
+ const handled = result.stopped === true;
955
+ updateRunControlStatusInStateDir(
956
+ stateDir,
957
+ control.id,
958
+ handled ? "handled" : "failed",
959
+ handled ? {} : { error: String(result.reason ?? "kill rejected") },
960
+ ["claimed"],
961
+ );
962
+ return { ...result, control_id: control.id };
963
+ };
1009
964
  const pid = Number(status.pid || 0);
1010
965
  if (status.status !== "running" && status.status !== "exited") {
1011
- return { stopped: false, reason: "not running", status };
966
+ return finish({ stopped: false, reason: "not running", status });
1012
967
  }
1013
968
  const identity = verifyRunProcessIdentity(
1014
969
  pid,
@@ -1019,43 +974,59 @@ function stopRun(
1019
974
  identity.status === "owner_mismatch" ||
1020
975
  identity.status === "unsupported_proof"
1021
976
  ) {
1022
- return {
977
+ return finish({
1023
978
  stopped: false,
1024
979
  reason: identity.status.replaceAll("_", " "),
1025
980
  process_identity_status: identity.status,
1026
981
  status,
1027
- };
982
+ });
1028
983
  }
1029
- return { stopped: false, reason: "not running", status };
984
+ return finish({ stopped: false, reason: "not running", status });
1030
985
  }
1031
986
  if (!identity.valid) {
1032
- return {
987
+ return finish({
1033
988
  stopped: false,
1034
989
  reason: identity.status.replaceAll("_", " "),
1035
990
  process_identity_status: identity.status,
1036
991
  status,
1037
- };
992
+ });
1038
993
  }
1039
- const signalResult = signalOwnedRunProcess(
1040
- pid,
1041
- signal,
1042
- status.process_identity as RunProcessIdentity,
1043
- );
1044
- writeFileSync(
1045
- join(stateDir, "events.jsonl"),
1046
- `${JSON.stringify({ event, pid, signal, ...signalResult, ts: new Date().toISOString() })}\n`,
1047
- { flag: "a" },
1048
- );
994
+ let signalResult: RunProcessSignalPlan;
995
+ try {
996
+ signalResult = signalOwnedRunProcess(
997
+ pid,
998
+ signal,
999
+ status.process_identity as RunProcessIdentity,
1000
+ );
1001
+ } catch (error) {
1002
+ if (control) {
1003
+ updateRunControlStatusInStateDir(stateDir, control.id, "failed", {
1004
+ error: error instanceof Error ? error.message : String(error),
1005
+ });
1006
+ }
1007
+ throw error;
1008
+ }
1009
+ appendRunTraceEvent(stateDir, {
1010
+ kind: event,
1011
+ summary: `${event === "run.kill" ? "Killed" : "Cancelled"} Run process`,
1012
+ data: { pid, signal, ...signalResult },
1013
+ });
1049
1014
  markTerminalHandled(stateDir, { event, signal });
1050
1015
  if (event === "run.kill") {
1051
- finalizeInterruptedReviewEvidence(stateDir, "killed", signal);
1016
+ finalizeInterruptedExecution(stateDir, "killed", signal);
1052
1017
  markTerminalProgress(stateDir, "killed");
1053
1018
  }
1054
1019
  if (event === "run.cancel") {
1055
- finalizeInterruptedReviewEvidence(stateDir, "cancelled", signal);
1020
+ finalizeInterruptedExecution(stateDir, "cancelled", signal);
1056
1021
  markTerminalProgress(stateDir, "cancelled");
1057
1022
  }
1058
- return { stopped: true, pid, signal, ...signalResult, state_dir: stateDir };
1023
+ return finish({
1024
+ stopped: true,
1025
+ pid,
1026
+ signal,
1027
+ ...signalResult,
1028
+ state_dir: stateDir,
1029
+ });
1059
1030
  } finally {
1060
1031
  releaseControlLock();
1061
1032
  }
@@ -1100,23 +1071,66 @@ export function cancelRun(
1100
1071
  : result;
1101
1072
  }
1102
1073
 
1103
- function assertTerminalRun(runOrDir: string): Record<string, unknown> {
1104
- const status = getRunStatus(runOrDir);
1105
- if (status.status === "running") {
1106
- throw new Error("Only terminal runs can be archived or pruned.");
1074
+ function retainRun(
1075
+ runOrDir: string,
1076
+ action: RunRetentionAction,
1077
+ expected: RunControlExpectation,
1078
+ options: { preserveArtifacts?: boolean } = {},
1079
+ ): Record<string, unknown> {
1080
+ const stateDir = resolveRunStateDir(runOrDir);
1081
+ const releaseControlLock = RunsStart.acquireStateStartLock(stateDir);
1082
+ let status: Record<string, unknown> | undefined;
1083
+ let evidenceId: string | undefined;
1084
+ try {
1085
+ expected.onLocked?.();
1086
+ status = getRunStatus(stateDir);
1087
+ if (expected.ownerId !== undefined && status.ownerId !== expected.ownerId) {
1088
+ return { [`${action}d`]: false, reason: "ownership changed", status };
1089
+ }
1090
+ if (
1091
+ expected.runInstanceId !== undefined &&
1092
+ status.run_instance_id !== expected.runInstanceId
1093
+ ) {
1094
+ return { [`${action}d`]: false, reason: "run generation changed", status };
1095
+ }
1096
+ if (status.status === "running") {
1097
+ throw new Error("Only terminal runs can be archived or pruned.");
1098
+ }
1099
+ evidenceId = appendRunRetentionEvidence(status, action, "queued");
1100
+ const result = action === "archive"
1101
+ ? archiveTerminalRun(status)
1102
+ : pruneTerminalRun(status, options);
1103
+ appendRunRetentionEvidence(status, action, "handled", {
1104
+ id: evidenceId,
1105
+ result,
1106
+ });
1107
+ return { ...result, retention_id: evidenceId };
1108
+ } catch (error) {
1109
+ if (status && evidenceId) {
1110
+ appendRunRetentionEvidence(status, action, "failed", {
1111
+ error: error instanceof Error ? error.message : String(error),
1112
+ id: evidenceId,
1113
+ });
1114
+ }
1115
+ throw error;
1116
+ } finally {
1117
+ releaseControlLock();
1107
1118
  }
1108
- return status;
1109
1119
  }
1110
1120
 
1111
- export function archiveRun(runOrDir: string): Record<string, unknown> {
1112
- return archiveTerminalRun(assertTerminalRun(runOrDir));
1121
+ export function archiveRun(
1122
+ runOrDir: string,
1123
+ expected: RunControlExpectation = {},
1124
+ ): Record<string, unknown> {
1125
+ return retainRun(runOrDir, "archive", expected);
1113
1126
  }
1114
1127
 
1115
1128
  export function pruneRun(
1116
1129
  runOrDir: string,
1117
1130
  options: { preserveArtifacts?: boolean } = {},
1131
+ expected: RunControlExpectation = {},
1118
1132
  ): Record<string, unknown> {
1119
- return pruneTerminalRun(assertTerminalRun(runOrDir), options);
1133
+ return retainRun(runOrDir, "prune", expected, options);
1120
1134
  }
1121
1135
 
1122
1136
  export function killRun(
@@ -16,7 +16,7 @@ import * as ToolReviewScheduler from "./tool-review-scheduler.ts";
16
16
 
17
17
  export interface AutomaticReviewRuntime {
18
18
  close(): void;
19
- handleMessage(type: string, body: unknown): Record<string, unknown>;
19
+ handleControl(action: string, input: unknown): Record<string, unknown>;
20
20
  schedule(): void;
21
21
  start(ctx: Pi.ExtensionContext): void;
22
22
  }
@@ -52,18 +52,18 @@ export function createAutomaticReviewRuntime(
52
52
 
53
53
  return {
54
54
  close,
55
- handleMessage(type, body) {
56
- if (type !== "review.retry" && type !== "review.reset") {
57
- throw new Error("tool:pi-actors accepts review.retry or review.reset messages.");
55
+ handleControl(action, input) {
56
+ if (action !== "review.retry" && action !== "review.reset") {
57
+ throw new Error("runtime accepts review.retry or review.reset Controls.");
58
58
  }
59
- if (type === "review.retry" && !Paths.isAutomaticRecipeReviewEnabled()) {
59
+ if (action === "review.retry" && !Paths.isAutomaticRecipeReviewEnabled()) {
60
60
  throw new Error(
61
61
  "Automatic recipe review is disabled by PI_ACTORS_AUTOMATIC_REVIEW.",
62
62
  );
63
63
  }
64
64
  return ReviewControl.controlAutomaticReview(
65
- type,
66
- ReviewControl.parseAutomaticReviewScope(body),
65
+ action,
66
+ ReviewControl.parseAutomaticReviewScope(input),
67
67
  {
68
68
  scheduleDraft: () => draftScheduler?.schedule(),
69
69
  scheduleTool: () => toolScheduler?.schedule(),