@llblab/pi-actors 0.42.3 → 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 (252) hide show
  1. package/AGENTS.md +121 -175
  2. package/CHANGELOG.md +12 -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 +16 -16
  26. package/dist/lib/observability.js +43 -82
  27. package/dist/lib/prompts.d.ts +1 -1
  28. package/dist/lib/prompts.js +2 -2
  29. package/dist/lib/recipe-control.d.ts +7 -0
  30. package/dist/lib/recipe-control.js +39 -0
  31. package/dist/lib/recipes-discovery.js +2 -0
  32. package/dist/lib/recipes-references.d.ts +1 -14
  33. package/dist/lib/recipes-references.js +6 -21
  34. package/dist/lib/review-projection.js +1 -5
  35. package/dist/lib/run-ui-runtime.js +2 -2
  36. package/dist/lib/runs-control-delivery.d.ts +21 -0
  37. package/dist/lib/runs-control-delivery.js +127 -0
  38. package/dist/lib/runs-controls.d.ts +35 -0
  39. package/dist/lib/runs-controls.js +144 -0
  40. package/dist/lib/runs-retention.d.ts +7 -0
  41. package/dist/lib/runs-retention.js +27 -3
  42. package/dist/lib/runs-start.js +4 -2
  43. package/dist/lib/runs-status.js +11 -6
  44. package/dist/lib/runs-trace.d.ts +24 -0
  45. package/dist/lib/runs-trace.js +98 -0
  46. package/dist/lib/runtime-notifier.d.ts +1 -1
  47. package/dist/lib/runtime-notifier.js +1 -1
  48. package/dist/lib/tools-inspect.d.ts +3 -3
  49. package/dist/lib/tools-inspect.js +203 -708
  50. package/dist/lib/tools-local.js +2 -10
  51. package/dist/lib/tools-message.d.ts +7 -7
  52. package/dist/lib/tools-message.js +95 -396
  53. package/dist/lib/tools-response.d.ts +1 -4
  54. package/dist/lib/tools-response.js +5 -39
  55. package/dist/lib/tools-spawn.js +16 -28
  56. package/dist/lib/tools.js +1 -2
  57. package/dist/lib/trace-projection.d.ts +22 -0
  58. package/dist/lib/trace-projection.js +165 -0
  59. package/dist/recipes/draft-review.json +0 -10
  60. package/dist/recipes/lens-swarm.json +0 -14
  61. package/dist/recipes/music-player.json +10 -19
  62. package/dist/recipes/pipeline-architect-coordinator.json +0 -11
  63. package/dist/recipes/pipeline-artifact-bundle.json +1 -22
  64. package/dist/recipes/pipeline-artifact-report.json +1 -18
  65. package/dist/recipes/pipeline-artifact-write.json +1 -18
  66. package/dist/recipes/pipeline-async-run-ops.json +0 -12
  67. package/dist/recipes/pipeline-checkpoint-continuation.json +0 -14
  68. package/dist/recipes/pipeline-development-tasking.json +0 -12
  69. package/dist/recipes/pipeline-docs-maintenance.json +0 -12
  70. package/dist/recipes/pipeline-media-library.json +0 -12
  71. package/dist/recipes/pipeline-quorum-review.json +0 -12
  72. package/dist/recipes/pipeline-release-readiness.json +0 -12
  73. package/dist/recipes/pipeline-release-summary.json +0 -12
  74. package/dist/recipes/pipeline-repo-health.json +0 -12
  75. package/dist/recipes/pipeline-research-synthesis.json +0 -11
  76. package/dist/recipes/pipeline-review-readiness.json +0 -12
  77. package/dist/recipes/resource-locker.json +27 -0
  78. package/dist/recipes/subagent-artifact.json +0 -9
  79. package/dist/recipes/subagent-checkpoint.json +0 -10
  80. package/dist/recipes/subagent-conflict-report.json +0 -11
  81. package/dist/recipes/subagent-contradiction-map.json +0 -11
  82. package/dist/recipes/subagent-critic.json +0 -11
  83. package/dist/recipes/subagent-evidence-map.json +0 -11
  84. package/dist/recipes/subagent-followup.json +0 -10
  85. package/dist/recipes/subagent-judge.json +0 -11
  86. package/dist/recipes/subagent-merge.json +0 -11
  87. package/dist/recipes/subagent-normalize.json +0 -11
  88. package/dist/recipes/subagent-plan.json +0 -11
  89. package/dist/recipes/subagent-preflight.json +0 -11
  90. package/dist/recipes/subagent-prompt.json +0 -10
  91. package/dist/recipes/subagent-quorum.json +0 -10
  92. package/dist/recipes/subagent-review-coordinator.json +0 -14
  93. package/dist/recipes/subagent-review.json +0 -11
  94. package/dist/recipes/subagent-task-card.json +0 -11
  95. package/dist/recipes/subagent-tools.json +0 -10
  96. package/dist/recipes/subagent-verify.json +0 -11
  97. package/dist/recipes/subagents-prompts.json +0 -10
  98. package/dist/recipes/tool-review.json +0 -10
  99. package/dist/scripts/async-runner.mjs +25 -25
  100. package/dist/scripts/conformance.mjs +4 -2
  101. package/dist/scripts/locker.mjs +200 -66
  102. package/dist/scripts/music-player.mjs +159 -150
  103. package/dist/scripts/recipe-utils.mjs +6 -96
  104. package/dist/scripts/release-gates.mjs +60 -0
  105. package/dist/scripts/validate-recipe.mjs +3 -53
  106. package/dist/skills/actors/SKILL.md +53 -266
  107. package/dist/skills/swarm/SKILL.md +11 -33
  108. package/docs/0.43-baseline.md +44 -0
  109. package/docs/README.md +3 -3
  110. package/docs/actor-inspector.md +26 -64
  111. package/docs/actors-deep-reference.md +92 -50
  112. package/docs/async-runs.md +81 -328
  113. package/docs/command-templates.md +2 -2
  114. package/docs/component-recipes.md +30 -133
  115. package/docs/recipe-library.md +57 -182
  116. package/docs/task-first-recipes.md +10 -12
  117. package/docs/template-recipes.md +76 -289
  118. package/docs/tool-registry.md +41 -161
  119. package/fixtures/protocol/control-endpoint.json +6 -0
  120. package/fixtures/protocol/control-record.json +9 -0
  121. package/fixtures/protocol/recipe-summary.json +4 -12
  122. package/fixtures/protocol/trace-event.json +9 -0
  123. package/lib/async-runs.ts +202 -201
  124. package/lib/control.ts +102 -0
  125. package/lib/execution-sessions.ts +111 -0
  126. package/lib/file-state.ts +17 -4
  127. package/lib/inspector-actions.ts +2 -2
  128. package/lib/inspector-command.ts +3 -3
  129. package/lib/inspector-overlay.ts +577 -1121
  130. package/lib/inspector.ts +46 -979
  131. package/lib/limits.ts +4 -2
  132. package/lib/observability.ts +60 -101
  133. package/lib/prompts.ts +2 -2
  134. package/lib/recipe-control.ts +45 -0
  135. package/lib/recipes-discovery.ts +2 -0
  136. package/lib/recipes-references.ts +9 -45
  137. package/lib/review-projection.ts +1 -5
  138. package/lib/run-ui-runtime.ts +2 -2
  139. package/lib/runs-control-delivery.ts +181 -0
  140. package/lib/runs-controls.ts +204 -0
  141. package/lib/runs-retention.ts +38 -3
  142. package/lib/runs-start.ts +4 -2
  143. package/lib/runs-status.ts +11 -6
  144. package/lib/runs-trace.ts +132 -0
  145. package/lib/runtime-notifier.ts +1 -1
  146. package/lib/tools-inspect.ts +240 -901
  147. package/lib/tools-local.ts +2 -12
  148. package/lib/tools-message.ts +112 -519
  149. package/lib/tools-response.ts +5 -52
  150. package/lib/tools-spawn.ts +16 -32
  151. package/lib/tools.ts +1 -2
  152. package/lib/trace-projection.ts +221 -0
  153. package/package.json +2 -1
  154. package/recipes/draft-review.json +0 -10
  155. package/recipes/lens-swarm.json +0 -14
  156. package/recipes/music-player.json +10 -19
  157. package/recipes/pipeline-architect-coordinator.json +0 -11
  158. package/recipes/pipeline-artifact-bundle.json +1 -22
  159. package/recipes/pipeline-artifact-report.json +1 -18
  160. package/recipes/pipeline-artifact-write.json +1 -18
  161. package/recipes/pipeline-async-run-ops.json +0 -12
  162. package/recipes/pipeline-checkpoint-continuation.json +0 -14
  163. package/recipes/pipeline-development-tasking.json +0 -12
  164. package/recipes/pipeline-docs-maintenance.json +0 -12
  165. package/recipes/pipeline-media-library.json +0 -12
  166. package/recipes/pipeline-quorum-review.json +0 -12
  167. package/recipes/pipeline-release-readiness.json +0 -12
  168. package/recipes/pipeline-release-summary.json +0 -12
  169. package/recipes/pipeline-repo-health.json +0 -12
  170. package/recipes/pipeline-research-synthesis.json +0 -11
  171. package/recipes/pipeline-review-readiness.json +0 -12
  172. package/recipes/resource-locker.json +27 -0
  173. package/recipes/subagent-artifact.json +0 -9
  174. package/recipes/subagent-checkpoint.json +0 -10
  175. package/recipes/subagent-conflict-report.json +0 -11
  176. package/recipes/subagent-contradiction-map.json +0 -11
  177. package/recipes/subagent-critic.json +0 -11
  178. package/recipes/subagent-evidence-map.json +0 -11
  179. package/recipes/subagent-followup.json +0 -10
  180. package/recipes/subagent-judge.json +0 -11
  181. package/recipes/subagent-merge.json +0 -11
  182. package/recipes/subagent-normalize.json +0 -11
  183. package/recipes/subagent-plan.json +0 -11
  184. package/recipes/subagent-preflight.json +0 -11
  185. package/recipes/subagent-prompt.json +0 -10
  186. package/recipes/subagent-quorum.json +0 -10
  187. package/recipes/subagent-review-coordinator.json +0 -14
  188. package/recipes/subagent-review.json +0 -11
  189. package/recipes/subagent-task-card.json +0 -11
  190. package/recipes/subagent-tools.json +0 -10
  191. package/recipes/subagent-verify.json +0 -11
  192. package/recipes/subagents-prompts.json +0 -10
  193. package/recipes/tool-review.json +0 -10
  194. package/scripts/async-runner.mjs +25 -25
  195. package/scripts/conformance.mjs +4 -2
  196. package/scripts/locker.mjs +200 -66
  197. package/scripts/music-player.mjs +159 -150
  198. package/scripts/recipe-utils.mjs +6 -96
  199. package/scripts/release-gates.mjs +60 -0
  200. package/scripts/validate-recipe.mjs +3 -53
  201. package/skills/actors/SKILL.md +53 -266
  202. package/skills/swarm/SKILL.md +11 -33
  203. package/dist/fixtures/protocol/actor-message-branch.json +0 -13
  204. package/dist/fixtures/protocol/mailbox-contract.json +0 -15
  205. package/dist/fixtures/protocol/room-message.json +0 -11
  206. package/dist/fixtures/protocol/room-roster.json +0 -11
  207. package/dist/fixtures/protocol/run-inbox-message.json +0 -9
  208. package/dist/fixtures/protocol/run-outbox-event.json +0 -9
  209. package/dist/lib/mailbox-loop.d.ts +0 -41
  210. package/dist/lib/mailbox-loop.js +0 -60
  211. package/dist/lib/messages.d.ts +0 -25
  212. package/dist/lib/messages.js +0 -122
  213. package/dist/lib/rooms.d.ts +0 -104
  214. package/dist/lib/rooms.js +0 -647
  215. package/dist/lib/runs-mailbox.d.ts +0 -25
  216. package/dist/lib/runs-mailbox.js +0 -146
  217. package/dist/lib/runs-messages.d.ts +0 -15
  218. package/dist/lib/runs-messages.js +0 -179
  219. package/dist/lib/runs-outbox.d.ts +0 -41
  220. package/dist/lib/runs-outbox.js +0 -87
  221. package/dist/lib/tools-mailbox.d.ts +0 -8
  222. package/dist/lib/tools-mailbox.js +0 -48
  223. package/dist/recipes/actor-worker.json +0 -39
  224. package/dist/recipes/coordinator-locker.json +0 -45
  225. package/dist/recipes/locker.json +0 -45
  226. package/dist/recipes/pipeline-room-swarm.json +0 -50
  227. package/dist/recipes/subagent-message.json +0 -32
  228. package/dist/recipes/utility-actor-message.json +0 -23
  229. package/dist/scripts/actor-worker.mjs +0 -214
  230. package/dist/scripts/coordinator.mjs +0 -799
  231. package/docs/actor-messages.md +0 -225
  232. package/fixtures/protocol/actor-message-branch.json +0 -13
  233. package/fixtures/protocol/mailbox-contract.json +0 -15
  234. package/fixtures/protocol/room-message.json +0 -11
  235. package/fixtures/protocol/room-roster.json +0 -11
  236. package/fixtures/protocol/run-inbox-message.json +0 -9
  237. package/fixtures/protocol/run-outbox-event.json +0 -9
  238. package/lib/mailbox-loop.ts +0 -144
  239. package/lib/messages.ts +0 -151
  240. package/lib/rooms.ts +0 -939
  241. package/lib/runs-mailbox.ts +0 -208
  242. package/lib/runs-messages.ts +0 -252
  243. package/lib/runs-outbox.ts +0 -144
  244. package/lib/tools-mailbox.ts +0 -56
  245. package/recipes/actor-worker.json +0 -39
  246. package/recipes/coordinator-locker.json +0 -45
  247. package/recipes/locker.json +0 -45
  248. package/recipes/pipeline-room-swarm.json +0 -50
  249. package/recipes/subagent-message.json +0 -32
  250. package/recipes/utility-actor-message.json +0 -23
  251. package/scripts/actor-worker.mjs +0 -214
  252. package/scripts/coordinator.mjs +0 -799
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Run Control delivery.
3
+ * Zones: generation-fenced endpoint resolution, FIFO/named-pipe writes, durable outcomes
4
+ * Owns delivery of persisted actor-local Controls to ready service endpoints.
5
+ */
6
+ import { closeSync, constants, existsSync, openSync, statSync, writeSync, } from "node:fs";
7
+ import { createConnection } from "node:net";
8
+ import { join } from "node:path";
9
+ import { appendRunControlInStateDir, updateRunControlStatusInStateDir, } from "./runs-controls.js";
10
+ import { readJsonFileResilient } from "./state-readers.js";
11
+ export const FIFO_ATOMIC_CONTROL_MAX_BYTES = 512;
12
+ export function readRunControlEndpoint(stateDir, runInstanceId) {
13
+ const endpoint = readJsonFileResilient(join(stateDir, "control-endpoint.json"), {}).value;
14
+ if (endpoint.run_instance_id !== runInstanceId)
15
+ return undefined;
16
+ if ((endpoint.type === "fifo" || endpoint.type === "named-pipe") &&
17
+ typeof endpoint.path === "string" &&
18
+ endpoint.path.trim()) {
19
+ return { path: endpoint.path, type: endpoint.type };
20
+ }
21
+ return undefined;
22
+ }
23
+ function sendToFifo(endpoint, payload) {
24
+ if (!existsSync(endpoint.path))
25
+ throw new Error(`Run Control FIFO not found: ${endpoint.path}`);
26
+ const stat = statSync(endpoint.path);
27
+ if ((stat.mode & constants.S_IFMT) !== constants.S_IFIFO) {
28
+ throw new Error(`Run Control endpoint is not a FIFO: ${endpoint.path}`);
29
+ }
30
+ let fd;
31
+ try {
32
+ fd = openSync(endpoint.path, constants.O_WRONLY | constants.O_NONBLOCK);
33
+ return writeSync(fd, payload);
34
+ }
35
+ finally {
36
+ if (fd !== undefined)
37
+ closeSync(fd);
38
+ }
39
+ }
40
+ function sendToNamedPipe(endpoint, payload, send) {
41
+ if (send)
42
+ return send(endpoint.path, payload);
43
+ return new Promise((resolve, reject) => {
44
+ const socket = createConnection(endpoint.path);
45
+ let settled = false;
46
+ const timeout = setTimeout(() => {
47
+ if (settled)
48
+ return;
49
+ settled = true;
50
+ socket.destroy();
51
+ reject(new Error("named pipe connection timed out"));
52
+ }, 5000);
53
+ const finish = (error) => {
54
+ if (settled)
55
+ return;
56
+ settled = true;
57
+ clearTimeout(timeout);
58
+ if (error)
59
+ reject(error);
60
+ else
61
+ resolve(Buffer.byteLength(payload));
62
+ };
63
+ socket.on("error", finish);
64
+ socket.on("connect", () => socket.end(payload, () => finish()));
65
+ });
66
+ }
67
+ export async function deliverRunControl(run, stateDir, request, options = {}) {
68
+ const control = appendRunControlInStateDir(stateDir, request);
69
+ const endpoint = readRunControlEndpoint(stateDir, request.run_instance_id);
70
+ if (!endpoint) {
71
+ updateRunControlStatusInStateDir(stateDir, control.id, "failed", { error: "control endpoint is not ready for this Run generation" }, ["queued", "delivered"]);
72
+ throw Object.assign(new Error("Run Control endpoint is not ready."), {
73
+ action: request.action,
74
+ control_id: control.id,
75
+ reason: "endpoint_not_ready",
76
+ run,
77
+ run_instance_id: request.run_instance_id,
78
+ });
79
+ }
80
+ const wire = {
81
+ id: control.id,
82
+ action: request.action,
83
+ ...(request.input !== undefined ? { input: request.input } : {}),
84
+ };
85
+ const payload = `${JSON.stringify(wire)}\n`;
86
+ const bytes = Buffer.byteLength(payload);
87
+ try {
88
+ let written;
89
+ if (endpoint.type === "fifo") {
90
+ if ((options.platform ?? process.platform) === "win32") {
91
+ throw new Error("FIFO Control delivery is unsupported on native Windows");
92
+ }
93
+ if (bytes > FIFO_ATOMIC_CONTROL_MAX_BYTES) {
94
+ throw new Error(`FIFO Control payload exceeds the ${FIFO_ATOMIC_CONTROL_MAX_BYTES}-byte portable atomic-write bound`);
95
+ }
96
+ written = sendToFifo(endpoint, payload);
97
+ }
98
+ else {
99
+ written = await sendToNamedPipe(endpoint, payload, options.namedPipeSend);
100
+ }
101
+ if (written !== bytes) {
102
+ throw new Error(`Run Control endpoint wrote ${written} of ${bytes} bytes`);
103
+ }
104
+ updateRunControlStatusInStateDir(stateDir, control.id, "delivered");
105
+ return {
106
+ action: request.action,
107
+ bytes,
108
+ control_id: control.id,
109
+ delivery: "delivered",
110
+ endpoint_type: endpoint.type,
111
+ run,
112
+ run_instance_id: request.run_instance_id,
113
+ };
114
+ }
115
+ catch (error) {
116
+ const reason = error instanceof Error ? error.message : String(error);
117
+ updateRunControlStatusInStateDir(stateDir, control.id, "failed", { error: reason }, ["queued", "delivered"]);
118
+ throw Object.assign(new Error(`Run Control delivery failed: ${reason}`), {
119
+ action: request.action,
120
+ control_id: control.id,
121
+ endpoint_type: endpoint.type,
122
+ reason: "delivery_failed",
123
+ run,
124
+ run_instance_id: request.run_instance_id,
125
+ });
126
+ }
127
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Run Control journal.
3
+ * Zones: durable Control records, claim locks, status transitions, bounded compaction
4
+ * Owns Run-local Control persistence; owner/generation authorization and transport delivery stay in lifecycle adapters.
5
+ */
6
+ export declare const RUN_CONTROL_TERMINAL_LIMIT = 128;
7
+ export type RunControlStatus = "queued" | "delivered" | "claimed" | "handled" | "failed";
8
+ export interface RunControlRecord {
9
+ id: string;
10
+ run_instance_id: string;
11
+ action: string;
12
+ input?: unknown;
13
+ status: RunControlStatus;
14
+ queued_at: string;
15
+ delivered_at?: string;
16
+ claimed_at?: string;
17
+ handled_at?: string;
18
+ failed_at?: string;
19
+ error?: string;
20
+ }
21
+ export interface ProcessRunControlsResult {
22
+ claimed: number;
23
+ failed: number;
24
+ handled: number;
25
+ }
26
+ export declare function runControlsFile(stateDir: string): string;
27
+ export declare function readRunControlsFromStateDir(stateDir: string): RunControlRecord[];
28
+ export declare function appendRunControlInStateDir(stateDir: string, request: {
29
+ run_instance_id: string;
30
+ action: string;
31
+ input?: unknown;
32
+ }): RunControlRecord;
33
+ export declare function updateRunControlStatusInStateDir(stateDir: string, id: string, nextStatus: RunControlStatus, metadata?: Pick<RunControlRecord, "error">, expectedStatuses?: readonly RunControlStatus[]): boolean;
34
+ export declare function claimRunControlInStateDir(stateDir: string, runInstanceId: string): RunControlRecord | undefined;
35
+ export declare function processRunControlsInStateDir(stateDir: string, runInstanceId: string, handler: (control: RunControlRecord) => Promise<void> | void, limit?: number): Promise<ProcessRunControlsResult>;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Run Control journal.
3
+ * Zones: durable Control records, claim locks, status transitions, bounded compaction
4
+ * Owns Run-local Control persistence; owner/generation authorization and transport delivery stay in lifecycle adapters.
5
+ */
6
+ import { randomUUID } from "node:crypto";
7
+ import { writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { acquireFileMutationLock, writeTextAtomic } from "./file-state.js";
10
+ import { readJsonlFileResilient } from "./state-readers.js";
11
+ export const RUN_CONTROL_TERMINAL_LIMIT = 128;
12
+ const TERMINAL_STATUSES = new Set(["handled", "failed"]);
13
+ export function runControlsFile(stateDir) {
14
+ return join(stateDir, "controls.jsonl");
15
+ }
16
+ export function readRunControlsFromStateDir(stateDir) {
17
+ return readJsonlFileResilient(runControlsFile(stateDir))
18
+ .records;
19
+ }
20
+ function acquireRunControlsLock(stateDir) {
21
+ return acquireFileMutationLock(runControlsFile(stateDir));
22
+ }
23
+ function compactRunControls(records) {
24
+ const active = records.filter((record) => !TERMINAL_STATUSES.has(record.status));
25
+ const terminal = records
26
+ .filter((record) => TERMINAL_STATUSES.has(record.status))
27
+ .slice(-RUN_CONTROL_TERMINAL_LIMIT);
28
+ const retained = new Set([...active, ...terminal]);
29
+ return records.filter((record) => retained.has(record));
30
+ }
31
+ function writeRunControls(stateDir, records) {
32
+ const compacted = compactRunControls(records);
33
+ writeTextAtomic(runControlsFile(stateDir), compacted.length
34
+ ? `${compacted.map((record) => JSON.stringify(record)).join("\n")}\n`
35
+ : "");
36
+ }
37
+ export function appendRunControlInStateDir(stateDir, request) {
38
+ const record = {
39
+ id: randomUUID(),
40
+ run_instance_id: request.run_instance_id,
41
+ action: request.action,
42
+ ...(request.input !== undefined ? { input: request.input } : {}),
43
+ status: "queued",
44
+ queued_at: new Date().toISOString(),
45
+ };
46
+ const releaseLock = acquireRunControlsLock(stateDir);
47
+ try {
48
+ writeFileSync(runControlsFile(stateDir), `${JSON.stringify(record)}\n`, {
49
+ flag: "a",
50
+ });
51
+ }
52
+ finally {
53
+ releaseLock();
54
+ }
55
+ return record;
56
+ }
57
+ export function updateRunControlStatusInStateDir(stateDir, id, nextStatus, metadata = {}, expectedStatuses) {
58
+ const releaseLock = acquireRunControlsLock(stateDir);
59
+ try {
60
+ const records = readRunControlsFromStateDir(stateDir);
61
+ const index = records.findIndex((record) => record.id === id);
62
+ if (index < 0)
63
+ return false;
64
+ const record = records[index];
65
+ if (expectedStatuses && !expectedStatuses.includes(record.status))
66
+ return false;
67
+ if (record.status === nextStatus)
68
+ return false;
69
+ const timestamp = new Date().toISOString();
70
+ if (nextStatus === "delivered") {
71
+ if (record.delivered_at)
72
+ return false;
73
+ records[index] = {
74
+ ...record,
75
+ delivered_at: timestamp,
76
+ ...(record.status === "queued" ? { status: "delivered" } : {}),
77
+ };
78
+ }
79
+ else {
80
+ if (TERMINAL_STATUSES.has(record.status) || nextStatus === "queued")
81
+ return false;
82
+ if ((nextStatus === "claimed" &&
83
+ record.status !== "queued" &&
84
+ record.status !== "delivered") ||
85
+ (nextStatus === "handled" && record.status !== "claimed")) {
86
+ return false;
87
+ }
88
+ const timestampKey = `${nextStatus}_at`;
89
+ records[index] = {
90
+ ...record,
91
+ ...metadata,
92
+ [timestampKey]: timestamp,
93
+ status: nextStatus,
94
+ };
95
+ }
96
+ writeRunControls(stateDir, records);
97
+ return true;
98
+ }
99
+ finally {
100
+ releaseLock();
101
+ }
102
+ }
103
+ export function claimRunControlInStateDir(stateDir, runInstanceId) {
104
+ const releaseLock = acquireRunControlsLock(stateDir);
105
+ try {
106
+ const records = readRunControlsFromStateDir(stateDir);
107
+ const index = records.findIndex((record) => record.run_instance_id === runInstanceId &&
108
+ (record.status === "queued" || record.status === "delivered"));
109
+ if (index < 0)
110
+ return undefined;
111
+ const claimed = {
112
+ ...records[index],
113
+ claimed_at: new Date().toISOString(),
114
+ status: "claimed",
115
+ };
116
+ records[index] = claimed;
117
+ writeRunControls(stateDir, records);
118
+ return claimed;
119
+ }
120
+ finally {
121
+ releaseLock();
122
+ }
123
+ }
124
+ export async function processRunControlsInStateDir(stateDir, runInstanceId, handler, limit = 1) {
125
+ const result = { claimed: 0, failed: 0, handled: 0 };
126
+ for (let index = 0; index < Math.max(1, limit); index += 1) {
127
+ const control = claimRunControlInStateDir(stateDir, runInstanceId);
128
+ if (!control)
129
+ break;
130
+ result.claimed += 1;
131
+ try {
132
+ await handler(control);
133
+ if (updateRunControlStatusInStateDir(stateDir, control.id, "handled", {}, ["claimed"])) {
134
+ result.handled += 1;
135
+ }
136
+ }
137
+ catch (error) {
138
+ if (updateRunControlStatusInStateDir(stateDir, control.id, "failed", { error: error instanceof Error ? error.message : String(error) }, ["claimed"])) {
139
+ result.failed += 1;
140
+ }
141
+ }
142
+ }
143
+ return result;
144
+ }
@@ -3,6 +3,13 @@
3
3
  * Owns: terminal-run archive and prune filesystem behavior.
4
4
  */
5
5
  import { cpSync } from "node:fs";
6
+ export type RunRetentionAction = "archive" | "prune";
7
+ export type RunRetentionOutcome = "queued" | "handled" | "failed";
8
+ export declare function appendRunRetentionEvidence(status: Record<string, unknown>, action: RunRetentionAction, outcome: RunRetentionOutcome, options?: {
9
+ error?: string;
10
+ id?: string;
11
+ result?: Record<string, unknown>;
12
+ }): string;
6
13
  export declare function archiveTerminalRun(status: Record<string, unknown>): Record<string, unknown>;
7
14
  export declare function pruneTerminalRun(status: Record<string, unknown>, options?: {
8
15
  preserveArtifacts?: boolean;
@@ -2,13 +2,37 @@
2
2
  * Async run retention operations.
3
3
  * Owns: terminal-run archive and prune filesystem behavior.
4
4
  */
5
- import { createHash } from "node:crypto";
6
- import { cpSync, mkdirSync, renameSync, rmSync } from "node:fs";
5
+ import { createHash, randomUUID } from "node:crypto";
6
+ import { appendFileSync, cpSync, mkdirSync, renameSync, rmSync } from "node:fs";
7
7
  import { basename, dirname, join } from "node:path";
8
- import { writeJsonAtomic } from "./file-state.js";
8
+ import { acquireFileMutationLock, writeJsonAtomic } from "./file-state.js";
9
9
  import { resolveArtifactManifest, } from "./runs-artifacts.js";
10
10
  import { safeRunId } from "./runs-identity.js";
11
11
  import { assertOwnedRunStateDirectory } from "./runs-ownership.js";
12
+ export function appendRunRetentionEvidence(status, action, outcome, options = {}) {
13
+ const stateDir = String(status.state_dir);
14
+ const path = join(dirname(stateDir), "retention.jsonl");
15
+ const id = options.id ?? randomUUID();
16
+ const release = acquireFileMutationLock(path);
17
+ try {
18
+ appendFileSync(path, `${JSON.stringify({
19
+ action,
20
+ ...(options.error ? { error: options.error } : {}),
21
+ id,
22
+ outcome,
23
+ ...(options.result ? { result: options.result } : {}),
24
+ run: String(status.run ?? basename(stateDir)),
25
+ ...(typeof status.run_instance_id === "string"
26
+ ? { run_instance_id: status.run_instance_id }
27
+ : {}),
28
+ ts: new Date().toISOString(),
29
+ })}\n`);
30
+ }
31
+ finally {
32
+ release();
33
+ }
34
+ return id;
35
+ }
12
36
  function retainedArtifactFilename(name, path) {
13
37
  const readableName = name.replace(/[^A-Za-z0-9_.-]+/g, "_") || "artifact";
14
38
  const identity = createHash("sha256")
@@ -45,15 +45,17 @@ export function prepareStateDirForStart(stateDir, readJson, _runnerPath) {
45
45
  }
46
46
  }
47
47
  for (const file of [
48
+ "control-endpoint.json",
49
+ "controls.jsonl",
48
50
  "events.jsonl",
49
- "inbox.jsonl",
50
- "outbox.jsonl",
51
+ "execution.json",
51
52
  "progress.json",
52
53
  "result.json",
53
54
  "stderr.log",
54
55
  "stdout.log",
55
56
  "terminal-delivery-failure.json",
56
57
  "terminal-handled.json",
58
+ "trace.jsonl",
57
59
  ]) {
58
60
  rmSync(join(stateDir, file), { force: true });
59
61
  }
@@ -7,8 +7,15 @@ import { join } from "node:path";
7
7
  import { verifyRunProcessIdentity, } from "./runs-process.js";
8
8
  import { readJsonlFileResilient } from "./state-readers.js";
9
9
  function getInterruptedRunStatus(stateDir) {
10
- const events = readJsonlFileResilient(join(stateDir, "events.jsonl")).records.slice(-200);
11
- for (const event of events.reverse()) {
10
+ const trace = readJsonlFileResilient(join(stateDir, "trace.jsonl")).records.slice(-200);
11
+ for (const event of trace.reverse()) {
12
+ if (event.kind === "run.kill")
13
+ return "killed";
14
+ if (event.kind === "run.cancel")
15
+ return "cancelled";
16
+ }
17
+ const legacyEvents = readJsonlFileResilient(join(stateDir, "events.jsonl")).records.slice(-200);
18
+ for (const event of legacyEvents.reverse()) {
12
19
  if (event.event === "run.kill")
13
20
  return "killed";
14
21
  if (event.event === "run.cancel")
@@ -33,10 +40,8 @@ export function buildRunStatus(stateDir, runOrDir, meta, readJson, _runnerPath,
33
40
  const terminalDeliveryFailure = readJson(join(stateDir, "terminal-delivery-failure.json"));
34
41
  return {
35
42
  ...meta,
36
- eventsFile: join(stateDir, "events.jsonl"),
37
- evidenceFile: join(stateDir, "review-evidence.json"),
38
- inboxFile: join(stateDir, "inbox.jsonl"),
39
- outboxFile: join(stateDir, "outbox.jsonl"),
43
+ traceFile: join(stateDir, "trace.jsonl"),
44
+ executionFile: join(stateDir, "execution.json"),
40
45
  process_identity_status: processIdentity.status,
41
46
  progress: readJson(join(stateDir, "progress.json")) || null,
42
47
  result: result || null,
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Run Trace event journal.
3
+ * Zones: structured event validation, bounded append, resilient bounded reads
4
+ * Owns Run-local semantic Trace events; lifecycle projection and owner attention delivery stay in adapters.
5
+ */
6
+ export interface TraceEvent {
7
+ id: string;
8
+ ts: string;
9
+ kind: string;
10
+ summary?: string;
11
+ data?: unknown;
12
+ level?: "info" | "warning" | "error";
13
+ attention?: "notify" | "followup";
14
+ }
15
+ export interface AppendTraceEventInput {
16
+ kind: string;
17
+ summary?: string;
18
+ data?: unknown;
19
+ level?: TraceEvent["level"];
20
+ attention?: TraceEvent["attention"];
21
+ }
22
+ export declare function runTraceFile(stateDir: string): string;
23
+ export declare function appendRunTraceEvent(stateDir: string, input: AppendTraceEventInput): TraceEvent;
24
+ export declare function readRunTraceEvents(stateDir: string, limit?: number): TraceEvent[];
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Run Trace event journal.
3
+ * Zones: structured event validation, bounded append, resilient bounded reads
4
+ * Owns Run-local semantic Trace events; lifecycle projection and owner attention delivery stay in adapters.
5
+ */
6
+ import { randomUUID } from "node:crypto";
7
+ import { writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import * as Limits from "./limits.js";
10
+ import { readJsonlFileResilient } from "./state-readers.js";
11
+ const KIND_PATTERN = /^[a-z][a-z0-9_-]*(?:\.[a-z0-9_-]+)*$/;
12
+ const TRACE_INPUT_FIELDS = new Set([
13
+ "attention",
14
+ "data",
15
+ "kind",
16
+ "level",
17
+ "summary",
18
+ ]);
19
+ const FORBIDDEN_FIELDS = new Set([
20
+ "body",
21
+ "correlation_id",
22
+ "from",
23
+ "metadata",
24
+ "reply_to",
25
+ "to",
26
+ "type",
27
+ ]);
28
+ export function runTraceFile(stateDir) {
29
+ return join(stateDir, "trace.jsonl");
30
+ }
31
+ function normalizeTraceEventInput(input) {
32
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
33
+ throw new Error("Trace event must be an object");
34
+ }
35
+ const record = input;
36
+ const forbidden = Object.keys(record).filter((key) => FORBIDDEN_FIELDS.has(key));
37
+ if (forbidden.length > 0) {
38
+ throw new Error(`Trace event fields are removed: ${forbidden.sort().join(", ")}`);
39
+ }
40
+ const unknown = Object.keys(record).filter((key) => !TRACE_INPUT_FIELDS.has(key));
41
+ if (unknown.length > 0) {
42
+ throw new Error(`Unsupported Trace event fields: ${unknown.sort().join(", ")}`);
43
+ }
44
+ if (typeof record.kind !== "string" || !KIND_PATTERN.test(record.kind)) {
45
+ throw new Error("Trace event kind must be a lowercase semantic token");
46
+ }
47
+ if (record.summary !== undefined && typeof record.summary !== "string") {
48
+ throw new Error("Trace event summary must be a string");
49
+ }
50
+ if (record.level !== undefined &&
51
+ record.level !== "info" &&
52
+ record.level !== "warning" &&
53
+ record.level !== "error") {
54
+ throw new Error("Trace event level must be info, warning, or error");
55
+ }
56
+ if (record.attention !== undefined &&
57
+ record.attention !== "notify" &&
58
+ record.attention !== "followup") {
59
+ throw new Error("Trace event attention must be notify or followup");
60
+ }
61
+ return {
62
+ kind: record.kind,
63
+ ...(record.summary !== undefined
64
+ ? { summary: record.summary.slice(0, 1_000) }
65
+ : {}),
66
+ ...(record.data !== undefined ? { data: record.data } : {}),
67
+ ...(record.level !== undefined
68
+ ? { level: record.level }
69
+ : {}),
70
+ ...(record.attention !== undefined
71
+ ? { attention: record.attention }
72
+ : {}),
73
+ };
74
+ }
75
+ export function appendRunTraceEvent(stateDir, input) {
76
+ const normalized = normalizeTraceEventInput(input);
77
+ const event = {
78
+ id: randomUUID(),
79
+ ts: new Date().toISOString(),
80
+ ...normalized,
81
+ };
82
+ let encoded;
83
+ try {
84
+ encoded = JSON.stringify(event);
85
+ }
86
+ catch {
87
+ throw new Error("Trace event data must be JSON-serializable");
88
+ }
89
+ if (Buffer.byteLength(encoded) > Limits.TRACE_EVENT_MAX_BYTES) {
90
+ throw new Error(`Trace event exceeds ${Limits.TRACE_EVENT_MAX_BYTES} bytes`);
91
+ }
92
+ writeFileSync(runTraceFile(stateDir), `${encoded}\n`, { flag: "a" });
93
+ return event;
94
+ }
95
+ export function readRunTraceEvents(stateDir, limit = Limits.TRACE_EVENT_MAX_READ) {
96
+ const records = readJsonlFileResilient(runTraceFile(stateDir)).records;
97
+ return records.slice(-Math.max(1, Math.min(limit, Limits.TRACE_EVENT_MAX_READ)));
98
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Runtime wake notifications for actor state.
3
3
  * Zones: advisory wake layer, file-backed runtime state, cross-platform notification boundary
4
- * Owns best-effort live wake signals while durable mailbox/state files remain canonical.
4
+ * Owns best-effort live wake signals while durable Run state remains canonical.
5
5
  */
6
6
  export interface RuntimeWakeEvent {
7
7
  actor: string;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Runtime wake notifications for actor state.
3
3
  * Zones: advisory wake layer, file-backed runtime state, cross-platform notification boundary
4
- * Owns best-effort live wake signals while durable mailbox/state files remain canonical.
4
+ * Owns best-effort live wake signals while durable Run state remains canonical.
5
5
  */
6
6
  import { randomUUID } from "node:crypto";
7
7
  import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, watch, } from "node:fs";
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Public inspect tool behavior
3
- * Zones: actor observation, recipe registry inspection, room/session/tool/run views
4
- * Owns the public inspect execution path and compact observation responses
2
+ * Public inspect tool behavior.
3
+ * Zones: Run Recipe/Trace/Control views and runtime/recipe/tool diagnostics
4
+ * Owns exact inspect target/view dispatch; source projection stays in domain modules.
5
5
  */
6
6
  export interface InspectToolDeps<TContext = unknown> {
7
7
  getRunStatus?: (runOrDir: string) => Record<string, any>;