pi-amq 0.1.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 (264) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -0
  4. package/THIRD_PARTY_NOTICES.md +33 -0
  5. package/dist/extension/guidance.d.ts +25 -0
  6. package/dist/extension/guidance.d.ts.map +1 -0
  7. package/dist/extension/guidance.js +121 -0
  8. package/dist/extension/guidance.js.map +1 -0
  9. package/dist/extension/guide.d.ts +30 -0
  10. package/dist/extension/guide.d.ts.map +1 -0
  11. package/dist/extension/guide.js +128 -0
  12. package/dist/extension/guide.js.map +1 -0
  13. package/dist/extension/index.d.ts +4 -0
  14. package/dist/extension/index.d.ts.map +1 -0
  15. package/dist/extension/index.js +6 -0
  16. package/dist/extension/index.js.map +1 -0
  17. package/dist/extension/runtime.d.ts +48 -0
  18. package/dist/extension/runtime.d.ts.map +1 -0
  19. package/dist/extension/runtime.js +1095 -0
  20. package/dist/extension/runtime.js.map +1 -0
  21. package/dist/extension/tools.d.ts +12 -0
  22. package/dist/extension/tools.d.ts.map +1 -0
  23. package/dist/extension/tools.js +198 -0
  24. package/dist/extension/tools.js.map +1 -0
  25. package/dist/extension/worker-runtime.d.ts +34 -0
  26. package/dist/extension/worker-runtime.d.ts.map +1 -0
  27. package/dist/extension/worker-runtime.js +974 -0
  28. package/dist/extension/worker-runtime.js.map +1 -0
  29. package/dist/extension/worker-tools.d.ts +140 -0
  30. package/dist/extension/worker-tools.d.ts.map +1 -0
  31. package/dist/extension/worker-tools.js +217 -0
  32. package/dist/extension/worker-tools.js.map +1 -0
  33. package/dist/integration/confirmed-bind-onboarding.d.ts +44 -0
  34. package/dist/integration/confirmed-bind-onboarding.d.ts.map +1 -0
  35. package/dist/integration/confirmed-bind-onboarding.js +199 -0
  36. package/dist/integration/confirmed-bind-onboarding.js.map +1 -0
  37. package/dist/integration/fleet-view.d.ts +66 -0
  38. package/dist/integration/fleet-view.d.ts.map +1 -0
  39. package/dist/integration/fleet-view.js +238 -0
  40. package/dist/integration/fleet-view.js.map +1 -0
  41. package/dist/integration/fresh-session-dispatcher.d.ts +66 -0
  42. package/dist/integration/fresh-session-dispatcher.d.ts.map +1 -0
  43. package/dist/integration/fresh-session-dispatcher.js +103 -0
  44. package/dist/integration/fresh-session-dispatcher.js.map +1 -0
  45. package/dist/integration/launch-preview.d.ts +31 -0
  46. package/dist/integration/launch-preview.d.ts.map +1 -0
  47. package/dist/integration/launch-preview.js +325 -0
  48. package/dist/integration/launch-preview.js.map +1 -0
  49. package/dist/integration/transport-port.d.ts +39 -0
  50. package/dist/integration/transport-port.d.ts.map +1 -0
  51. package/dist/integration/transport-port.js +2 -0
  52. package/dist/integration/transport-port.js.map +1 -0
  53. package/dist/integration/worker-completion-notifier.d.ts +22 -0
  54. package/dist/integration/worker-completion-notifier.d.ts.map +1 -0
  55. package/dist/integration/worker-completion-notifier.js +81 -0
  56. package/dist/integration/worker-completion-notifier.js.map +1 -0
  57. package/dist/transport/amq-cli-adapter.d.ts +41 -0
  58. package/dist/transport/amq-cli-adapter.d.ts.map +1 -0
  59. package/dist/transport/amq-cli-adapter.js +291 -0
  60. package/dist/transport/amq-cli-adapter.js.map +1 -0
  61. package/dist/transport/amq-message.d.ts +47 -0
  62. package/dist/transport/amq-message.d.ts.map +1 -0
  63. package/dist/transport/amq-message.js +173 -0
  64. package/dist/transport/amq-message.js.map +1 -0
  65. package/dist/transport/amq-operations.d.ts +105 -0
  66. package/dist/transport/amq-operations.d.ts.map +1 -0
  67. package/dist/transport/amq-operations.js +320 -0
  68. package/dist/transport/amq-operations.js.map +1 -0
  69. package/dist/transport/atomic-json.d.ts +18 -0
  70. package/dist/transport/atomic-json.d.ts.map +1 -0
  71. package/dist/transport/atomic-json.js +90 -0
  72. package/dist/transport/atomic-json.js.map +1 -0
  73. package/dist/transport/binding-resolver.d.ts +46 -0
  74. package/dist/transport/binding-resolver.d.ts.map +1 -0
  75. package/dist/transport/binding-resolver.js +389 -0
  76. package/dist/transport/binding-resolver.js.map +1 -0
  77. package/dist/transport/bridge-journal.d.ts +100 -0
  78. package/dist/transport/bridge-journal.d.ts.map +1 -0
  79. package/dist/transport/bridge-journal.js +459 -0
  80. package/dist/transport/bridge-journal.js.map +1 -0
  81. package/dist/transport/dlq-retry-audit.d.ts +28 -0
  82. package/dist/transport/dlq-retry-audit.d.ts.map +1 -0
  83. package/dist/transport/dlq-retry-audit.js +66 -0
  84. package/dist/transport/dlq-retry-audit.js.map +1 -0
  85. package/dist/transport/inbox-pump.d.ts +81 -0
  86. package/dist/transport/inbox-pump.d.ts.map +1 -0
  87. package/dist/transport/inbox-pump.js +558 -0
  88. package/dist/transport/inbox-pump.js.map +1 -0
  89. package/dist/transport/index.d.ts +12 -0
  90. package/dist/transport/index.d.ts.map +1 -0
  91. package/dist/transport/index.js +12 -0
  92. package/dist/transport/index.js.map +1 -0
  93. package/dist/transport/presence-projector.d.ts +29 -0
  94. package/dist/transport/presence-projector.d.ts.map +1 -0
  95. package/dist/transport/presence-projector.js +73 -0
  96. package/dist/transport/presence-projector.js.map +1 -0
  97. package/dist/transport/priority-delivery-scheduler.d.ts +85 -0
  98. package/dist/transport/priority-delivery-scheduler.d.ts.map +1 -0
  99. package/dist/transport/priority-delivery-scheduler.js +210 -0
  100. package/dist/transport/priority-delivery-scheduler.js.map +1 -0
  101. package/dist/transport/receiver-lease.d.ts +102 -0
  102. package/dist/transport/receiver-lease.d.ts.map +1 -0
  103. package/dist/transport/receiver-lease.js +778 -0
  104. package/dist/transport/receiver-lease.js.map +1 -0
  105. package/dist/transport/runtime-status.d.ts +48 -0
  106. package/dist/transport/runtime-status.d.ts.map +1 -0
  107. package/dist/transport/runtime-status.js +71 -0
  108. package/dist/transport/runtime-status.js.map +1 -0
  109. package/dist/workflow/atomic-storage.d.ts +12 -0
  110. package/dist/workflow/atomic-storage.d.ts.map +1 -0
  111. package/dist/workflow/atomic-storage.js +104 -0
  112. package/dist/workflow/atomic-storage.js.map +1 -0
  113. package/dist/workflow/budget.d.ts +18 -0
  114. package/dist/workflow/budget.d.ts.map +1 -0
  115. package/dist/workflow/budget.js +74 -0
  116. package/dist/workflow/budget.js.map +1 -0
  117. package/dist/workflow/index.d.ts +17 -0
  118. package/dist/workflow/index.d.ts.map +1 -0
  119. package/dist/workflow/index.js +17 -0
  120. package/dist/workflow/index.js.map +1 -0
  121. package/dist/workflow/lifecycle-projection.d.ts +18 -0
  122. package/dist/workflow/lifecycle-projection.d.ts.map +1 -0
  123. package/dist/workflow/lifecycle-projection.js +22 -0
  124. package/dist/workflow/lifecycle-projection.js.map +1 -0
  125. package/dist/workflow/model-fallback.d.ts +2 -0
  126. package/dist/workflow/model-fallback.d.ts.map +1 -0
  127. package/dist/workflow/model-fallback.js +17 -0
  128. package/dist/workflow/model-fallback.js.map +1 -0
  129. package/dist/workflow/pi-session-fork.d.ts +18 -0
  130. package/dist/workflow/pi-session-fork.d.ts.map +1 -0
  131. package/dist/workflow/pi-session-fork.js +80 -0
  132. package/dist/workflow/pi-session-fork.js.map +1 -0
  133. package/dist/workflow/run-artifacts.d.ts +5 -0
  134. package/dist/workflow/run-artifacts.d.ts.map +1 -0
  135. package/dist/workflow/run-artifacts.js +25 -0
  136. package/dist/workflow/run-artifacts.js.map +1 -0
  137. package/dist/workflow/run-cleanup.d.ts +70 -0
  138. package/dist/workflow/run-cleanup.d.ts.map +1 -0
  139. package/dist/workflow/run-cleanup.js +222 -0
  140. package/dist/workflow/run-cleanup.js.map +1 -0
  141. package/dist/workflow/run-gate.d.ts +43 -0
  142. package/dist/workflow/run-gate.d.ts.map +1 -0
  143. package/dist/workflow/run-gate.js +70 -0
  144. package/dist/workflow/run-gate.js.map +1 -0
  145. package/dist/workflow/run-store.d.ts +208 -0
  146. package/dist/workflow/run-store.d.ts.map +1 -0
  147. package/dist/workflow/run-store.js +2965 -0
  148. package/dist/workflow/run-store.js.map +1 -0
  149. package/dist/workflow/spawn-confirmation.d.ts +44 -0
  150. package/dist/workflow/spawn-confirmation.d.ts.map +1 -0
  151. package/dist/workflow/spawn-confirmation.js +228 -0
  152. package/dist/workflow/spawn-confirmation.js.map +1 -0
  153. package/dist/workflow/system-process-inspector.d.ts +9 -0
  154. package/dist/workflow/system-process-inspector.d.ts.map +1 -0
  155. package/dist/workflow/system-process-inspector.js +129 -0
  156. package/dist/workflow/system-process-inspector.js.map +1 -0
  157. package/dist/workflow/worker-child-extension.d.ts +3 -0
  158. package/dist/workflow/worker-child-extension.d.ts.map +1 -0
  159. package/dist/workflow/worker-child-extension.js +182 -0
  160. package/dist/workflow/worker-child-extension.js.map +1 -0
  161. package/dist/workflow/worker-gate.d.ts +74 -0
  162. package/dist/workflow/worker-gate.d.ts.map +1 -0
  163. package/dist/workflow/worker-gate.js +165 -0
  164. package/dist/workflow/worker-gate.js.map +1 -0
  165. package/dist/workflow/worker-launch.d.ts +65 -0
  166. package/dist/workflow/worker-launch.d.ts.map +1 -0
  167. package/dist/workflow/worker-launch.js +338 -0
  168. package/dist/workflow/worker-launch.js.map +1 -0
  169. package/dist/workflow/worker-model-catalog.d.ts +36 -0
  170. package/dist/workflow/worker-model-catalog.d.ts.map +1 -0
  171. package/dist/workflow/worker-model-catalog.js +105 -0
  172. package/dist/workflow/worker-model-catalog.js.map +1 -0
  173. package/dist/workflow/worker-process-supervisor.d.ts +110 -0
  174. package/dist/workflow/worker-process-supervisor.d.ts.map +1 -0
  175. package/dist/workflow/worker-process-supervisor.js +1188 -0
  176. package/dist/workflow/worker-process-supervisor.js.map +1 -0
  177. package/dist/workflow/worker-prompt-capsule.d.ts +32 -0
  178. package/dist/workflow/worker-prompt-capsule.d.ts.map +1 -0
  179. package/dist/workflow/worker-prompt-capsule.js +52 -0
  180. package/dist/workflow/worker-prompt-capsule.js.map +1 -0
  181. package/dist/workflow/worker-role-presets.d.ts +38 -0
  182. package/dist/workflow/worker-role-presets.d.ts.map +1 -0
  183. package/dist/workflow/worker-role-presets.js +201 -0
  184. package/dist/workflow/worker-role-presets.js.map +1 -0
  185. package/dist/workflow/workflow-doctor.d.ts +57 -0
  186. package/dist/workflow/workflow-doctor.d.ts.map +1 -0
  187. package/dist/workflow/workflow-doctor.js +289 -0
  188. package/dist/workflow/workflow-doctor.js.map +1 -0
  189. package/dist/workflow/workflow-lease.d.ts +125 -0
  190. package/dist/workflow/workflow-lease.d.ts.map +1 -0
  191. package/dist/workflow/workflow-lease.js +689 -0
  192. package/dist/workflow/workflow-lease.js.map +1 -0
  193. package/dist/workflow/workflow-types.d.ts +335 -0
  194. package/dist/workflow/workflow-types.d.ts.map +1 -0
  195. package/dist/workflow/workflow-types.js +11 -0
  196. package/dist/workflow/workflow-types.js.map +1 -0
  197. package/dist/workflow/workspace-paths.d.ts +4 -0
  198. package/dist/workflow/workspace-paths.d.ts.map +1 -0
  199. package/dist/workflow/workspace-paths.js +22 -0
  200. package/dist/workflow/workspace-paths.js.map +1 -0
  201. package/docs/conformance.md +116 -0
  202. package/docs/demo-transcript.md +34 -0
  203. package/docs/design.md +1481 -0
  204. package/docs/operator-guide.md +100 -0
  205. package/extension/guidance.ts +162 -0
  206. package/extension/guide.ts +155 -0
  207. package/extension/index.ts +8 -0
  208. package/extension/runtime.ts +1224 -0
  209. package/extension/tools.ts +221 -0
  210. package/extension/worker-runtime.ts +1150 -0
  211. package/extension/worker-tools.ts +276 -0
  212. package/integration/confirmed-bind-onboarding.ts +296 -0
  213. package/integration/fleet-view.ts +286 -0
  214. package/integration/fresh-session-dispatcher.ts +166 -0
  215. package/integration/launch-preview.ts +396 -0
  216. package/integration/transport-port.ts +29 -0
  217. package/integration/worker-completion-notifier.ts +98 -0
  218. package/package.json +55 -0
  219. package/roles/oracle.md +9 -0
  220. package/roles/researcher.md +8 -0
  221. package/roles/reviewer.md +9 -0
  222. package/roles/scout.md +8 -0
  223. package/roles/worker.md +10 -0
  224. package/scripts/demo-two-terminal.sh +59 -0
  225. package/skills/coordination-patterns/SKILL.md +41 -0
  226. package/skills/coordination-patterns/references/supervised-worker-fan-out.md +12 -0
  227. package/skills/coordination-patterns/references/today-mail-team.md +36 -0
  228. package/transport/amq-cli-adapter.ts +381 -0
  229. package/transport/amq-message.ts +218 -0
  230. package/transport/amq-operations.ts +482 -0
  231. package/transport/atomic-json.ts +115 -0
  232. package/transport/binding-resolver.ts +565 -0
  233. package/transport/bridge-journal.ts +588 -0
  234. package/transport/dlq-retry-audit.ts +97 -0
  235. package/transport/inbox-pump.ts +729 -0
  236. package/transport/index.ts +11 -0
  237. package/transport/presence-projector.ts +104 -0
  238. package/transport/priority-delivery-scheduler.ts +328 -0
  239. package/transport/receiver-lease.ts +945 -0
  240. package/transport/runtime-status.ts +112 -0
  241. package/workflow/.gitkeep +1 -0
  242. package/workflow/atomic-storage.ts +120 -0
  243. package/workflow/budget.ts +90 -0
  244. package/workflow/index.ts +16 -0
  245. package/workflow/lifecycle-projection.ts +37 -0
  246. package/workflow/model-fallback.ts +17 -0
  247. package/workflow/pi-session-fork.ts +96 -0
  248. package/workflow/run-artifacts.ts +36 -0
  249. package/workflow/run-cleanup.ts +315 -0
  250. package/workflow/run-gate.ts +122 -0
  251. package/workflow/run-store.ts +3890 -0
  252. package/workflow/spawn-confirmation.ts +305 -0
  253. package/workflow/system-process-inspector.ts +142 -0
  254. package/workflow/worker-child-extension.ts +193 -0
  255. package/workflow/worker-gate.ts +238 -0
  256. package/workflow/worker-launch.ts +485 -0
  257. package/workflow/worker-model-catalog.ts +165 -0
  258. package/workflow/worker-process-supervisor.ts +1504 -0
  259. package/workflow/worker-prompt-capsule.ts +81 -0
  260. package/workflow/worker-role-presets.ts +241 -0
  261. package/workflow/workflow-doctor.ts +400 -0
  262. package/workflow/workflow-lease.ts +905 -0
  263. package/workflow/workflow-types.ts +367 -0
  264. package/workflow/workspace-paths.ts +32 -0
@@ -0,0 +1,974 @@
1
+ import { spawn as spawnChild } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { access, readFile } from "node:fs/promises";
4
+ import { constants } from "node:fs";
5
+ import { delimiter, isAbsolute, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { openLaunchPreview, } from "../integration/launch-preview.js";
8
+ import { WorkerCompletionNotifier, } from "../integration/worker-completion-notifier.js";
9
+ import { limited, UNLIMITED } from "../workflow/budget.js";
10
+ import { describeCurrentPiSessionFork } from "../workflow/pi-session-fork.js";
11
+ import { ensureRunArtifactDirectory, withRunArtifactContext, } from "../workflow/run-artifacts.js";
12
+ import { confirmCleanupDeletion, WorkflowRunCleanup, } from "../workflow/run-cleanup.js";
13
+ import { effectiveWorkerContract, WorkflowRunStore, } from "../workflow/run-store.js";
14
+ import { SystemProcessIdentityInspector } from "../workflow/system-process-inspector.js";
15
+ import { WorkflowDoctor } from "../workflow/workflow-doctor.js";
16
+ import { reportMatchesSchema } from "../workflow/worker-gate.js";
17
+ import { buildWorkerModelGuidance, floorAuditorModel, selectWorkerModel, } from "../workflow/worker-model-catalog.js";
18
+ import { buildWorkerPrompt } from "../workflow/worker-prompt-capsule.js";
19
+ import { WorkerRolePresetCatalog, workerRoleContract, } from "../workflow/worker-role-presets.js";
20
+ import { LaunchPreviewCoordinator, loadConfirmSpawnsPolicy, } from "../workflow/spawn-confirmation.js";
21
+ import { projectWorkerLifecycle } from "../workflow/lifecycle-projection.js";
22
+ import { NodePiProcessFactory, WorkerProcessSupervisor, } from "../workflow/worker-process-supervisor.js";
23
+ import { WorkflowLeaseManager } from "../workflow/workflow-lease.js";
24
+ import { defaultPiStateDirectory, workflowNamespaceDirectory, workflowWorkspaceId, } from "../workflow/workspace-paths.js";
25
+ const DRIVER_DOCTRINE = [
26
+ "## Supervised Workers (pi-amq)",
27
+ "You are the driver: delegate bounded evidence-producing work, retain final synthesis and acceptance authority, and collect before concluding.",
28
+ "Use spawn_worker/list_workers/collect/cancel/steer/revise_contract only. A collected candidate is not done until every declared Gate fact passes.",
29
+ "Stall facts are observations only; never infer permission to nudge, retry, or cancel.",
30
+ ].join("\n");
31
+ const CAPABILITY_TOOLS = {
32
+ "repo.read": ["read", "grep", "find", "ls"],
33
+ "repo.write": ["read", "grep", "find", "ls", "write", "edit"],
34
+ shell: ["bash"],
35
+ message_worker: ["message_worker"],
36
+ };
37
+ export class PiWorkerRuntime {
38
+ #publishFleet;
39
+ #unlimitedDeclaration;
40
+ #publishLifecycle;
41
+ #publishNotice;
42
+ #launchPreview = new LaunchPreviewCoordinator();
43
+ #previewAuditFloors = new Map();
44
+ #lifecycleStates = new Map();
45
+ #cwd;
46
+ #store;
47
+ #supervisor;
48
+ #cleanup;
49
+ #doctor;
50
+ #artifactDirectory;
51
+ #requestKey;
52
+ #error;
53
+ #guidance = DRIVER_DOCTRINE;
54
+ #runtimes = new Map();
55
+ constructor(publishFleet = () => { }, unlimitedDeclaration = () => undefined, publishLifecycle = async () => { }, publishNotice = () => { }) {
56
+ this.#publishFleet = publishFleet;
57
+ this.#unlimitedDeclaration = unlimitedDeclaration;
58
+ this.#publishLifecycle = publishLifecycle;
59
+ this.#publishNotice = publishNotice;
60
+ }
61
+ async start(ctx) {
62
+ await this.refreshGuidance(ctx);
63
+ const existing = this.#runtimes.get(ctx.cwd);
64
+ if (existing) {
65
+ this.#cwd = ctx.cwd;
66
+ this.#store = existing.store;
67
+ this.#supervisor = existing.supervisor;
68
+ this.#cleanup = existing.cleanup;
69
+ this.#doctor = existing.doctor;
70
+ this.#artifactDirectory = existing.artifactDirectory;
71
+ this.#requestKey = existing.requestKey;
72
+ existing.supervisor.updateLaunchAuthority(await launchAuthority(ctx));
73
+ this.#publish(await requiredRun(existing.store, existing.requestKey));
74
+ void this.#publishWithConversation(await requiredRun(existing.store, existing.requestKey));
75
+ return;
76
+ }
77
+ try {
78
+ const runBudget = await confirmedRunBudget(ctx, this.#unlimitedDeclaration());
79
+ const authority = await launchAuthority(ctx);
80
+ const namespaceDirectory = workflowNamespaceDirectory(defaultPiStateDirectory(), ctx.cwd);
81
+ const inspector = new SystemProcessIdentityInspector();
82
+ const leaseManager = new WorkflowLeaseManager({
83
+ namespaceDirectory,
84
+ workspaceId: workflowWorkspaceId(ctx.cwd),
85
+ inspector,
86
+ });
87
+ const acquired = await leaseManager.acquireSupervisor();
88
+ if (acquired.kind !== "acquired") {
89
+ throw new Error(acquired.kind === "held"
90
+ ? `worker supervisor is held by PID ${acquired.owner.pid}`
91
+ : acquired.diagnostic);
92
+ }
93
+ const store = new WorkflowRunStore({
94
+ namespaceDirectory,
95
+ leaseManager,
96
+ supervisorGeneration: acquired.owner.generation,
97
+ });
98
+ const sessionIdentity = ctx.sessionManager.getSessionFile() ?? `process:${process.pid}`;
99
+ const requestKey = `pi-session:${hash(`${ctx.cwd}\0${sessionIdentity}`)}`;
100
+ await store.startRun({
101
+ requestKey,
102
+ source: {
103
+ kind: "interactive",
104
+ amqMessageId: null,
105
+ driverSessionId: sessionIdentity,
106
+ },
107
+ objective: {
108
+ schema: 1,
109
+ goal: "Supervise bounded workers for the current pi session",
110
+ acceptance: ["Every accepted worker outcome passes its declared Gate facts"],
111
+ constraints: ["Workers stay within explicit capability and workspace grants"],
112
+ permissions: Object.keys(CAPABILITY_TOOLS),
113
+ budget: runBudget,
114
+ },
115
+ });
116
+ const artifactDirectory = await ensureRunArtifactDirectory(namespaceDirectory, requestKey);
117
+ const supervisor = new WorkerProcessSupervisor({
118
+ namespaceDirectory,
119
+ store,
120
+ leaseManager,
121
+ supervisorGeneration: acquired.owner.generation,
122
+ inspector,
123
+ processFactory: new NodePiProcessFactory(),
124
+ launchAuthority: authority,
125
+ });
126
+ const cleanup = new WorkflowRunCleanup({
127
+ namespaceDirectory,
128
+ store,
129
+ leaseManager,
130
+ supervisorGeneration: acquired.owner.generation,
131
+ });
132
+ const doctor = new WorkflowDoctor({
133
+ namespaceDirectory,
134
+ runs: store,
135
+ leases: leaseManager,
136
+ inspector,
137
+ });
138
+ const initial = await requiredRun(store, requestKey);
139
+ this.#baselineLifecycle(initial);
140
+ const notifier = new WorkerCompletionNotifier({ publish: this.#publishNotice });
141
+ notifier.baseline(initial);
142
+ const unsubscribeStore = store.subscribe((projection) => {
143
+ this.#publish(projection);
144
+ void this.#publishWithConversation(projection);
145
+ void this.#publishLifecycleChanges(projection).catch(() => { });
146
+ notifier.observe(projection);
147
+ });
148
+ const unsubscribe = () => {
149
+ unsubscribeStore();
150
+ notifier.dispose();
151
+ };
152
+ const reconciled = await supervisor.reconcileRun(requestKey);
153
+ this.#cwd = ctx.cwd;
154
+ this.#store = store;
155
+ this.#supervisor = supervisor;
156
+ this.#cleanup = cleanup;
157
+ this.#doctor = doctor;
158
+ this.#artifactDirectory = artifactDirectory;
159
+ this.#requestKey = requestKey;
160
+ this.#runtimes.set(ctx.cwd, {
161
+ store,
162
+ supervisor,
163
+ cleanup,
164
+ doctor,
165
+ artifactDirectory,
166
+ requestKey,
167
+ unsubscribe,
168
+ });
169
+ this.#error = undefined;
170
+ this.#publish(reconciled);
171
+ }
172
+ catch (error) {
173
+ this.#error = toError(error).message;
174
+ this.#store = undefined;
175
+ this.#supervisor = undefined;
176
+ this.#cleanup = undefined;
177
+ this.#doctor = undefined;
178
+ this.#artifactDirectory = undefined;
179
+ this.#requestKey = undefined;
180
+ }
181
+ }
182
+ async shutdown() {
183
+ // Workers own a parent-liveness descriptor. Session changes do not imply cancellation,
184
+ // so the runtime intentionally retains the supervisor instead of inventing a stop policy.
185
+ }
186
+ async refreshGuidance(ctx) {
187
+ try {
188
+ const current = currentLiveModel(ctx);
189
+ if (!current) {
190
+ this.#guidance = DRIVER_DOCTRINE;
191
+ return;
192
+ }
193
+ const guidance = await buildWorkerModelGuidance({
194
+ available: availableLiveModels(ctx),
195
+ current,
196
+ globalNotesPath: join(defaultPiStateDirectory(), "pi-amq", "model-notes.md"),
197
+ projectNotesPath: join(ctx.cwd, ".pi", "pi-amq", "model-notes.md"),
198
+ projectTrusted: ctx.isProjectTrusted(),
199
+ });
200
+ this.#guidance = `${DRIVER_DOCTRINE}\n\n${guidance.text}${guidance.notices.length > 0 ? `\n${guidance.notices.join("\n")}` : ""}`;
201
+ if (this.#cwd === ctx.cwd) {
202
+ this.#supervisor?.updateLaunchAuthority(await launchAuthority(ctx));
203
+ }
204
+ }
205
+ catch (error) {
206
+ this.#guidance = `${DRIVER_DOCTRINE}\n\nWorker catalog unavailable: ${toError(error).message}`;
207
+ }
208
+ }
209
+ appendGuidance(systemPrompt) {
210
+ return `${systemPrompt}\n\n${this.#guidance}`;
211
+ }
212
+ async spawn(params, signal, ctx) {
213
+ const runtime = this.#required();
214
+ await this.refreshGuidance(ctx);
215
+ const roles = await WorkerRolePresetCatalog.load({
216
+ projectDirectory: ctx.cwd,
217
+ projectTrusted: ctx.isProjectTrusted(),
218
+ });
219
+ const requestedRole = params.role ?? "worker";
220
+ const preset = roles.resolve(requestedRole);
221
+ let auditFloor;
222
+ if (requestedRole.trim().toLowerCase() === "auditor" && !params.audits_worker_id) {
223
+ throw new Error("An auditor must name audits_worker_id");
224
+ }
225
+ if (params.audits_worker_id) {
226
+ const run = await requiredRun(runtime.store, runtime.requestKey);
227
+ const audited = effectiveWorkerContract(requiredWorker(run, params.audits_worker_id)).model;
228
+ auditFloor = availableLiveModels(ctx).find((model) => model.provider === audited.provider && model.id === audited.id);
229
+ if (!auditFloor)
230
+ throw new Error("Audited implementer model is not in the live catalog");
231
+ }
232
+ const request = withRunArtifactContext(this.#workerRequest(params, ctx, auditFloor, preset), runtime.artifactDirectory);
233
+ const policy = await loadConfirmSpawnsPolicy({
234
+ projectDirectory: ctx.cwd,
235
+ projectTrusted: ctx.isProjectTrusted(),
236
+ });
237
+ if (auditFloor)
238
+ this.#previewAuditFloors.set(request.requestKey, auditFloor);
239
+ try {
240
+ const gated = await this.#launchPreview.submit({
241
+ groupKey: `${ctx.cwd}\0${ctx.sessionManager.getSessionId()}`,
242
+ policy,
243
+ tui: ctx.mode === "tui" && ctx.hasUI,
244
+ request,
245
+ ...(signal ? { signal } : {}),
246
+ preview: async (requests) => openLaunchPreview(ctx, this.#launchPreviewOptions(requests, roles, ctx)),
247
+ launch: async (accepted) => {
248
+ const result = await runtime.supervisor.spawnWorker(runtime.requestKey, {
249
+ request: accepted,
250
+ prompt: buildWorkerPrompt(accepted),
251
+ depth: 1,
252
+ ...(params.continue_worker_id === undefined
253
+ ? {}
254
+ : { continueWorkerId: params.continue_worker_id }),
255
+ });
256
+ const current = await requiredRun(runtime.store, runtime.requestKey);
257
+ this.#publish(current);
258
+ if (result.kind === "started") {
259
+ void result.settlement.then((projection) => this.#publish(projection)).catch(() => { });
260
+ }
261
+ return {
262
+ kind: result.kind,
263
+ worker: projectWorkerForTools(result.worker),
264
+ attempt: result.attempt,
265
+ };
266
+ },
267
+ });
268
+ return gated.kind === "cancelled"
269
+ ? gated
270
+ : gated.value;
271
+ }
272
+ finally {
273
+ this.#previewAuditFloors.delete(request.requestKey);
274
+ }
275
+ }
276
+ async list(params, _ctx) {
277
+ const runtime = this.#required();
278
+ let run = await runtime.store.readRun(runtime.requestKey);
279
+ if (!run)
280
+ throw new Error("Current worker run is missing");
281
+ for (const worker of Object.values(run.workers)) {
282
+ if (isTerminal(worker.state) || worker.stall.silent.detected)
283
+ continue;
284
+ try {
285
+ run = await runtime.store.recordSilentStall(runtime.requestKey, { workerId: worker.workerId });
286
+ }
287
+ catch {
288
+ // Silence has not crossed its budget-derived cadence.
289
+ }
290
+ }
291
+ this.#publish(run);
292
+ return Object.values(run.workers).slice(0, params.limit ?? 50).map(projectWorkerForTools);
293
+ }
294
+ async collect(params, signal, ctx) {
295
+ const runtime = this.#required();
296
+ const collected = [];
297
+ for (const rawWorkerId of params.worker_ids) {
298
+ if (signal?.aborted)
299
+ throw signal.reason ?? new Error("collect aborted");
300
+ const workerId = rawWorkerId;
301
+ let run = await requiredRun(runtime.store, runtime.requestKey);
302
+ let worker = requiredWorker(run, workerId);
303
+ const contract = effectiveWorkerContract(worker);
304
+ const candidate = worker.attempts.at(-1)?.candidateOutcome.valueOrRef ?? null;
305
+ const report = params.structured_reports?.[workerId] ?? parseJsonCandidate(candidate);
306
+ if (report !== undefined) {
307
+ run = await runtime.store.recordCollectObservation(runtime.requestKey, {
308
+ workerId,
309
+ structuredPayload: report,
310
+ consecutiveRounds: 3,
311
+ });
312
+ worker = requiredWorker(run, workerId);
313
+ }
314
+ const reportMatches = contract.report_schema
315
+ ? reportMatchesSchema(contract.report_schema, report)
316
+ : null;
317
+ const acceptanceCheckExitCode = contract.acceptance_check
318
+ ? await runAcceptanceCheck(contract, signal)
319
+ : null;
320
+ const humanApproved = contract.requires_human_approval === true
321
+ ? await requestHumanApproval(ctx, worker)
322
+ : null;
323
+ run = await runtime.store.recordGateEvaluation(runtime.requestKey, {
324
+ workerId,
325
+ reportMatches,
326
+ acceptanceCheckExitCode,
327
+ humanApproved,
328
+ evidenceExists: report !== undefined || candidate !== null,
329
+ });
330
+ collected.push(projectWorkerForTools(requiredWorker(run, workerId)));
331
+ this.#publish(run);
332
+ }
333
+ return collected;
334
+ }
335
+ async cancel(params, _signal, _ctx) {
336
+ const runtime = this.#required();
337
+ const result = await runtime.supervisor.cancelWorker(runtime.requestKey, {
338
+ workerId: params.worker_id,
339
+ reason: params.reason,
340
+ });
341
+ this.#publish(result.projection);
342
+ return result;
343
+ }
344
+ async steer(params, _signal, _ctx) {
345
+ const runtime = this.#required();
346
+ const projection = await runtime.supervisor.steerWorker(runtime.requestKey, {
347
+ workerId: params.worker_id,
348
+ message: params.message,
349
+ ...(params.correlation === undefined ? {} : { correlation: params.correlation }),
350
+ });
351
+ this.#publish(projection);
352
+ return projectWorkerForTools(requiredWorker(projection, params.worker_id));
353
+ }
354
+ async reviseContract(params, _signal, _ctx) {
355
+ const runtime = this.#required();
356
+ const projection = await runtime.supervisor.reviseWorkerContract(runtime.requestKey, {
357
+ workerId: params.worker_id,
358
+ ...(params.correlation === undefined ? {} : { correlation: params.correlation }),
359
+ ...(params.goal === undefined ? {} : { goal: params.goal }),
360
+ ...(params.acceptance === undefined ? {} : { acceptance: params.acceptance }),
361
+ ...(params.constraints === undefined ? {} : { constraints: params.constraints }),
362
+ ...(params.report_schema === undefined ? {} : { report_schema: params.report_schema }),
363
+ ...(params.requires_human_approval === undefined
364
+ ? {}
365
+ : { requires_human_approval: params.requires_human_approval }),
366
+ ...(params.acceptance_check === undefined
367
+ ? {}
368
+ : { acceptance_check: params.acceptance_check }),
369
+ });
370
+ this.#publish(projection);
371
+ return projectWorkerForTools(requiredWorker(projection, params.worker_id));
372
+ }
373
+ async cleanupRuns(params, _signal, ctx) {
374
+ const runtime = this.#required();
375
+ const dryRun = params.dry_run !== false;
376
+ if (dryRun)
377
+ return runtime.cleanup.plan(params.older_than_ms);
378
+ const confirmed = await confirmCleanupDeletion({
379
+ dryRun,
380
+ yes: params.yes === true,
381
+ tui: ctx.mode === "tui" && ctx.hasUI,
382
+ ...(ctx.mode === "tui" && ctx.hasUI ? {
383
+ confirm: () => ctx.ui.confirm("Delete old pi-amq workflow runs?", "This permanently removes the listed terminal runs, worker session files, and artifacts."),
384
+ } : {}),
385
+ });
386
+ return runtime.cleanup.execute({ olderThanMs: params.older_than_ms, confirmed });
387
+ }
388
+ async doctorWorkflow(_signal, _ctx) {
389
+ return this.#required().doctor.inspect();
390
+ }
391
+ async canReplaceSession() {
392
+ if (!this.#store || !this.#requestKey)
393
+ return true;
394
+ try {
395
+ const run = await requiredRun(this.#store, this.#requestKey);
396
+ return canReplaceWorkerRun(run);
397
+ }
398
+ catch {
399
+ return false;
400
+ }
401
+ }
402
+ async steerFromFleet(workerId, generation, message) {
403
+ const runtime = this.#required();
404
+ const run = await requiredRun(runtime.store, runtime.requestKey);
405
+ const worker = requiredWorker(run, workerId);
406
+ const attempt = worker.attempts.at(-1);
407
+ if (!attempt || attempt.workerAttemptGeneration !== generation) {
408
+ throw new Error("Fleet steer refused: worker generation is stale");
409
+ }
410
+ if (effectiveWorkerContract(worker).channel !== "rpc") {
411
+ throw new Error("one-shot worker, cannot steer");
412
+ }
413
+ await runtime.supervisor.steerWorker(runtime.requestKey, {
414
+ workerId: worker.workerId,
415
+ message,
416
+ });
417
+ }
418
+ async cancelFromFleet(workerId, generation) {
419
+ const runtime = this.#required();
420
+ const run = await requiredRun(runtime.store, runtime.requestKey);
421
+ const worker = requiredWorker(run, workerId);
422
+ const attempt = worker.attempts.at(-1);
423
+ if (!attempt || attempt.workerAttemptGeneration !== generation) {
424
+ throw new Error("Fleet cancel refused: worker generation is stale");
425
+ }
426
+ await runtime.supervisor.cancelWorker(runtime.requestKey, {
427
+ workerId: worker.workerId,
428
+ reason: "operator requested stop from FleetView",
429
+ });
430
+ }
431
+ #workerRequest(params, ctx, auditFloor, preset) {
432
+ if (params.context === "fork" && params.continue_worker_id !== undefined) {
433
+ throw new TypeError("context=fork and continue_worker_id are mutually exclusive");
434
+ }
435
+ const current = currentLiveModel(ctx);
436
+ if (!current)
437
+ throw new Error("Current pi model is unavailable");
438
+ const available = availableLiveModels(ctx);
439
+ const scoped = scopedLiveModels(ctx);
440
+ const difficulty = params.difficulty ?? preset.difficulty;
441
+ let selected = params.model
442
+ ? available.find((model) => model.provider === params.model?.provider && model.id === params.model.id)
443
+ : selectWorkerModel({
444
+ difficulty,
445
+ available,
446
+ scoped,
447
+ current,
448
+ }).model;
449
+ if (!selected)
450
+ throw new Error(`Requested model is not in the live catalog`);
451
+ if (auditFloor && preset.name === "reviewer")
452
+ selected = floorAuditorModel(selected, auditFloor);
453
+ const thinkingLevel = params.model?.thinking_level
454
+ ?? selected.thinkingLevel
455
+ ?? (selected.provider === current.provider && selected.id === current.id
456
+ ? ctx.thinkingLevel
457
+ : undefined)
458
+ ?? (selected.reasoning ? "medium" : "off");
459
+ const modelFallbacks = (params.fallback_models ?? []).map((fallback) => {
460
+ const live = available.find((model) => model.provider === fallback.provider && model.id === fallback.id);
461
+ if (!live)
462
+ throw new Error(`Fallback model is not in the live catalog: ${fallback.provider}/${fallback.id}`);
463
+ return {
464
+ provider: live.provider,
465
+ id: live.id,
466
+ thinkingLevel: fallback.thinking_level
467
+ ?? live.thinkingLevel
468
+ ?? (live.reasoning ? "medium" : "off"),
469
+ };
470
+ });
471
+ const modelKeys = [
472
+ `${selected.provider}/${selected.id}`,
473
+ ...modelFallbacks.map((model) => `${model.provider}/${model.id}`),
474
+ ];
475
+ if (new Set(modelKeys).size !== modelKeys.length) {
476
+ throw new TypeError("Primary and fallback model chain entries must be unique");
477
+ }
478
+ const context = params.context ?? "fresh";
479
+ const forkSource = context === "fork"
480
+ ? describeCurrentPiSessionFork(ctx.sessionManager)
481
+ : undefined;
482
+ return {
483
+ schema: 1,
484
+ requestKey: params.request_key,
485
+ difficulty,
486
+ role: workerRoleContract(preset),
487
+ context,
488
+ ...(forkSource ? { forkSource } : {}),
489
+ goal: params.goal,
490
+ acceptance: params.acceptance,
491
+ constraints: params.constraints,
492
+ budget: {
493
+ maxAttempts: params.budget.max_attempts,
494
+ maxWallClockMs: params.budget.max_wall_clock_ms,
495
+ maxTokens: params.budget.max_tokens,
496
+ maxCostUsd: params.budget.max_cost_usd,
497
+ maxOutputBytes: params.budget.max_output_bytes,
498
+ },
499
+ model: { provider: selected.provider, id: selected.id, thinkingLevel },
500
+ ...(modelFallbacks.length > 0 ? { modelFallbacks } : {}),
501
+ capabilities: params.capabilities ?? preset.capabilities,
502
+ workspace: {
503
+ path: params.workspace.path,
504
+ mode: params.workspace.mode,
505
+ ...(params.workspace.worktree_path === undefined
506
+ ? {}
507
+ : { worktreePath: params.workspace.worktree_path }),
508
+ },
509
+ contextRefs: params.context_refs,
510
+ channel: params.channel,
511
+ sideEffectPolicy: params.side_effect_policy,
512
+ ...(params.report_schema === undefined ? {} : { report_schema: params.report_schema }),
513
+ ...(params.requires_human_approval === undefined
514
+ ? {}
515
+ : { requires_human_approval: params.requires_human_approval }),
516
+ ...(params.acceptance_check === undefined
517
+ ? {}
518
+ : { acceptance_check: params.acceptance_check }),
519
+ };
520
+ }
521
+ #launchPreviewOptions(requests, roles, ctx) {
522
+ const current = currentLiveModel(ctx);
523
+ if (!current)
524
+ throw new Error("Current pi model is unavailable");
525
+ const available = availableLiveModels(ctx);
526
+ const scoped = scopedLiveModels(ctx);
527
+ const defaultFor = (difficulty) => toModelSpec(selectWorkerModel({ difficulty, available, scoped, current }).model, ctx);
528
+ const normalizeModel = (requestKey, roleName, model) => {
529
+ const auditFloor = this.#previewAuditFloors.get(requestKey);
530
+ if (roleName !== "reviewer" || !auditFloor)
531
+ return model;
532
+ const selected = available.find((candidate) => candidate.provider === model.provider && candidate.id === model.id);
533
+ if (!selected)
534
+ throw new Error("Preview model is not in the live catalog");
535
+ return toModelSpec(floorAuditorModel(selected, auditFloor), ctx);
536
+ };
537
+ return {
538
+ requests,
539
+ roles: roles.presets().map((role) => ({
540
+ preset: role,
541
+ defaultModel: defaultFor(role.difficulty),
542
+ })),
543
+ models: available.map((model) => toModelSpec(model, ctx)),
544
+ difficultyModels: {
545
+ easy: defaultFor("easy"),
546
+ medium: defaultFor("medium"),
547
+ hard: defaultFor("hard"),
548
+ },
549
+ normalizeModel,
550
+ };
551
+ }
552
+ #required() {
553
+ if (!this.#store || !this.#supervisor || !this.#cleanup || !this.#doctor
554
+ || !this.#artifactDirectory || !this.#requestKey) {
555
+ throw new Error(`worker runtime is unavailable${this.#error ? `: ${this.#error}` : ""}`);
556
+ }
557
+ return {
558
+ store: this.#store,
559
+ supervisor: this.#supervisor,
560
+ cleanup: this.#cleanup,
561
+ doctor: this.#doctor,
562
+ artifactDirectory: this.#artifactDirectory,
563
+ requestKey: this.#requestKey,
564
+ };
565
+ }
566
+ #publish(run) {
567
+ this.#publishFleet(projectFleetRecords(run));
568
+ }
569
+ async #publishWithConversation(run) {
570
+ const records = await Promise.all(Object.values(run.workers).map(async (worker) => {
571
+ const attempt = worker.attempts.at(-1);
572
+ const contract = effectiveWorkerContract(worker);
573
+ const selectedModel = attempt?.modelSelection?.model ?? contract.model;
574
+ const tierDegraded = worker.attempts.some((candidate) => candidate.modelSelection?.fallbackReason);
575
+ const activeWait = worker.waits.filter((wait) => wait.status === "waiting").at(-1);
576
+ const conversation = attempt?.session.file
577
+ ? await readConversationTail(attempt.session.file)
578
+ : [];
579
+ return {
580
+ id: worker.workerId,
581
+ handle: worker.workerId,
582
+ role: "worker",
583
+ model: `${selectedModel.provider}/${selectedModel.id}`,
584
+ state: fleetState(worker.state),
585
+ spend: attempt?.usage.costUsd ?? null,
586
+ lastActivity: worker.lastLedgerAt,
587
+ mode: contract.channel === "rpc" ? "rpc" : "json",
588
+ ...(attempt ? { generation: attempt.workerAttemptGeneration } : {}),
589
+ conversation: conversation.length > 0 ? conversation : [
590
+ `attempt_state=${attempt?.state ?? "none"}`,
591
+ `tier_degraded=${tierDegraded}`,
592
+ `peer.pending=${worker.peerMessages.filter((message) => message.status === "pending").length}`,
593
+ `wait.reason=${activeWait?.reason ?? "none"}`,
594
+ `wait.correlation=${activeWait?.correlation ?? "none"}`,
595
+ `control=${attempt?.control.status ?? "none"}`,
596
+ `gate.blockers=${worker.gate.blockers.join(",") || "none"}`,
597
+ ],
598
+ };
599
+ }));
600
+ if (this.#requestKey === run.requestKey)
601
+ this.#publishFleet(records);
602
+ }
603
+ #baselineLifecycle(run) {
604
+ for (const worker of Object.values(run.workers)) {
605
+ this.#lifecycleStates.set(`${run.requestKey}\0${worker.workerId}`, worker.state);
606
+ }
607
+ }
608
+ async #publishLifecycleChanges(run) {
609
+ for (const worker of Object.values(run.workers)) {
610
+ const key = `${run.requestKey}\0${worker.workerId}`;
611
+ if (this.#lifecycleStates.get(key) === worker.state)
612
+ continue;
613
+ this.#lifecycleStates.set(key, worker.state);
614
+ await this.#publishLifecycle(projectWorkerLifecycle(worker.workerId, worker.state)).catch(() => { });
615
+ }
616
+ }
617
+ }
618
+ export function projectFleetRecords(run) {
619
+ return Object.values(run.workers).map((worker) => {
620
+ const attempt = worker.attempts.at(-1);
621
+ const contract = effectiveWorkerContract(worker);
622
+ const selectedModel = attempt?.modelSelection?.model ?? contract.model;
623
+ const tierDegraded = worker.attempts.some((candidate) => candidate.modelSelection?.fallbackReason);
624
+ const activeWait = worker.waits.filter((wait) => wait.status === "waiting").at(-1);
625
+ return {
626
+ id: worker.workerId,
627
+ handle: worker.workerId,
628
+ role: "worker",
629
+ model: `${selectedModel.provider}/${selectedModel.id}`,
630
+ state: fleetState(worker.state),
631
+ spend: attempt?.usage.costUsd ?? null,
632
+ lastActivity: worker.lastLedgerAt,
633
+ mode: contract.channel === "rpc" ? "rpc" : "json",
634
+ ...(attempt ? { generation: attempt.workerAttemptGeneration } : {}),
635
+ conversation: [
636
+ `attempt_state=${attempt?.state ?? "none"}`,
637
+ `tier_degraded=${tierDegraded}`,
638
+ `peer.pending=${worker.peerMessages.filter((message) => message.status === "pending").length}`,
639
+ `wait.reason=${activeWait?.reason ?? "none"}`,
640
+ `wait.correlation=${activeWait?.correlation ?? "none"}`,
641
+ `stall.silent=${worker.stall.silent.detected}`,
642
+ `stall.no_progress=${worker.stall.noProgress.detected}`,
643
+ `gate.blockers=${worker.gate.blockers.join(",") || "none"}`,
644
+ ],
645
+ };
646
+ });
647
+ }
648
+ export function canReplaceWorkerRun(run) {
649
+ if (run.state === "ambiguous" || run.state === "needs-attention")
650
+ return false;
651
+ return Object.values(run.workers).every((worker) => {
652
+ const attempt = worker.attempts.at(-1);
653
+ if (!attempt)
654
+ return true;
655
+ if (attempt.control.status === "detached" || attempt.sideEffects.status === "uncertain") {
656
+ return false;
657
+ }
658
+ return isTerminal(worker.state);
659
+ });
660
+ }
661
+ async function readConversationTail(path) {
662
+ try {
663
+ const raw = await readFile(path, "utf8");
664
+ const bounded = raw.slice(-128 * 1024);
665
+ const lines = [];
666
+ for (const line of bounded.split("\n").filter(Boolean).slice(-80)) {
667
+ try {
668
+ const value = JSON.parse(line);
669
+ const message = value["message"];
670
+ if (typeof message !== "object" || message === null)
671
+ continue;
672
+ const role = message["role"];
673
+ const content = message["content"];
674
+ const text = typeof content === "string"
675
+ ? content
676
+ : Array.isArray(content)
677
+ ? content.flatMap((part) => typeof part === "object" && part !== null
678
+ && typeof part["text"] === "string"
679
+ ? [part["text"]]
680
+ : []).join(" ")
681
+ : "";
682
+ if (text)
683
+ lines.push(`${typeof role === "string" ? role : "message"}: ${text.slice(0, 2_000)}`);
684
+ }
685
+ catch {
686
+ // Session tails may begin in the middle of a JSONL line.
687
+ }
688
+ }
689
+ return lines.slice(-24);
690
+ }
691
+ catch {
692
+ return [];
693
+ }
694
+ }
695
+ const RUN_BUDGET_DIMENSIONS = [
696
+ "workers", "concurrency", "depth", "attempts", "wall-clock", "tokens", "cost", "output",
697
+ ];
698
+ export function parseUnlimitedBudgetDimensions(value) {
699
+ if (value === undefined || value === false || value === "")
700
+ return [];
701
+ if (typeof value !== "string")
702
+ throw new TypeError("--worker-unlimited must be a comma-separated string");
703
+ const requested = value.split(",").map((item) => item.trim()).filter(Boolean);
704
+ if (requested.includes("all")) {
705
+ if (requested.length !== 1)
706
+ throw new TypeError("--worker-unlimited=all cannot be combined with dimensions");
707
+ return RUN_BUDGET_DIMENSIONS;
708
+ }
709
+ const allowed = new Set(RUN_BUDGET_DIMENSIONS);
710
+ for (const dimension of requested) {
711
+ if (!allowed.has(dimension))
712
+ throw new TypeError(`Unknown unlimited budget dimension: ${dimension}`);
713
+ }
714
+ return [...new Set(requested)];
715
+ }
716
+ export async function confirmedRunBudget(ctx, declaration) {
717
+ const unlimited = parseUnlimitedBudgetDimensions(declaration);
718
+ if (unlimited.length > 0) {
719
+ if (ctx.mode !== "tui" || !ctx.hasUI) {
720
+ throw new Error("Unlimited worker budgets require explicit TUI confirmation before run creation");
721
+ }
722
+ const confirmed = await ctx.ui.confirm("Confirm unlimited worker budget", `Unlimited dimensions: ${unlimited.join(", ")}. Worker contracts must re-declare each dimension.`);
723
+ if (!confirmed)
724
+ throw new Error("Unlimited worker budget was not confirmed; no run was created");
725
+ }
726
+ const selected = new Set(unlimited);
727
+ const limit = (dimension, value) => selected.has(dimension) ? UNLIMITED : limited(value);
728
+ return {
729
+ maxWorkers: limit("workers", 8),
730
+ maxConcurrency: limit("concurrency", 4),
731
+ maxDepth: limit("depth", 3),
732
+ maxAttempts: limit("attempts", 16),
733
+ maxWallClockMs: limit("wall-clock", 3_600_000),
734
+ maxTokens: limit("tokens", 500_000),
735
+ maxCostUsd: limit("cost", 50),
736
+ maxOutputBytes: limit("output", 67_108_864),
737
+ };
738
+ }
739
+ async function launchAuthority(ctx) {
740
+ const models = availablePiModels(ctx);
741
+ return {
742
+ piExecutable: await resolvePiExecutable(),
743
+ childExtensionPath: await resolveChildExtensionPath(),
744
+ grantedCapabilities: Object.keys(CAPABILITY_TOOLS),
745
+ capabilityTools: CAPABILITY_TOOLS,
746
+ availableModels: models.map((model) => {
747
+ const defaultLevel = defaultThinkingLevel(model, ctx);
748
+ const thinkingLevels = supportedThinkingLevels(model, defaultLevel);
749
+ return {
750
+ provider: model.provider,
751
+ id: model.id,
752
+ thinkingLevels,
753
+ defaultThinkingLevel: defaultLevel,
754
+ };
755
+ }),
756
+ projectTrust: ctx.isProjectTrusted() ? "trusted" : "untrusted",
757
+ requiresProjectResources: true,
758
+ inheritedEnvironment: process.env,
759
+ };
760
+ }
761
+ async function resolvePiExecutable() {
762
+ const configured = process.env.PI_AMQ_PI_EXECUTABLE;
763
+ if (configured !== undefined) {
764
+ if (!isAbsolute(configured))
765
+ throw new Error("PI_AMQ_PI_EXECUTABLE must be absolute");
766
+ await access(configured, constants.X_OK);
767
+ return configured;
768
+ }
769
+ const argvExecutable = process.argv[1];
770
+ if (argvExecutable && isAbsolute(argvExecutable)) {
771
+ try {
772
+ await access(argvExecutable, constants.X_OK);
773
+ return argvExecutable;
774
+ }
775
+ catch { }
776
+ }
777
+ for (const directory of (process.env.PATH ?? "").split(delimiter)) {
778
+ if (!directory)
779
+ continue;
780
+ const candidate = join(directory, "pi");
781
+ try {
782
+ await access(candidate, constants.X_OK);
783
+ return candidate;
784
+ }
785
+ catch { }
786
+ }
787
+ throw new Error("pi executable was not found; set PI_AMQ_PI_EXECUTABLE");
788
+ }
789
+ async function resolveChildExtensionPath() {
790
+ const candidates = [
791
+ fileURLToPath(new URL("../workflow/worker-child-extension.js", import.meta.url)),
792
+ fileURLToPath(new URL("../workflow/worker-child-extension.ts", import.meta.url)),
793
+ ];
794
+ for (const candidate of candidates) {
795
+ try {
796
+ await access(candidate, constants.R_OK);
797
+ return candidate;
798
+ }
799
+ catch { }
800
+ }
801
+ throw new Error("worker child extension is missing from the package");
802
+ }
803
+ function availablePiModels(ctx) {
804
+ return ctx.modelRegistry.getAvailable();
805
+ }
806
+ function availableLiveModels(ctx) {
807
+ return availablePiModels(ctx).map((model) => toLiveModel(model));
808
+ }
809
+ function scopedLiveModels(ctx) {
810
+ return ctx.scopedModels.map((scoped) => ({
811
+ ...toLiveModel(scoped.model),
812
+ ...(scoped.thinkingLevel === undefined ? {} : { thinkingLevel: scoped.thinkingLevel }),
813
+ }));
814
+ }
815
+ function currentLiveModel(ctx) {
816
+ return ctx.model ? {
817
+ ...toLiveModel(ctx.model),
818
+ ...(ctx.thinkingLevel === undefined ? {} : { thinkingLevel: ctx.thinkingLevel }),
819
+ } : undefined;
820
+ }
821
+ function toLiveModel(model) {
822
+ return {
823
+ provider: model.provider,
824
+ id: model.id,
825
+ name: model.name,
826
+ cost: model.cost.input + model.cost.output,
827
+ contextWindow: model.contextWindow,
828
+ maxTokens: model.maxTokens,
829
+ reasoning: model.reasoning,
830
+ };
831
+ }
832
+ function supportedThinkingLevels(model, defaultLevel) {
833
+ const levels = ["off", "minimal", "low", "medium", "high", "xhigh"]
834
+ .filter((level) => model.thinkingLevelMap?.[level] !== null);
835
+ return levels.includes(defaultLevel) ? levels : [...levels, defaultLevel];
836
+ }
837
+ function defaultThinkingLevel(model, ctx) {
838
+ if (ctx.model?.provider === model.provider && ctx.model.id === model.id && ctx.thinkingLevel) {
839
+ return ctx.thinkingLevel;
840
+ }
841
+ const scoped = ctx.scopedModels.find((candidate) => candidate.model.provider === model.provider && candidate.model.id === model.id);
842
+ return scoped?.thinkingLevel ?? (model.reasoning ? "medium" : "off");
843
+ }
844
+ function toModelSpec(model, ctx) {
845
+ const catalogModel = availablePiModels(ctx).find((candidate) => candidate.provider === model.provider && candidate.id === model.id);
846
+ if (!catalogModel)
847
+ throw new Error("Preview model is not in the live catalog");
848
+ return {
849
+ provider: model.provider,
850
+ id: model.id,
851
+ thinkingLevel: model.thinkingLevel ?? defaultThinkingLevel(catalogModel, ctx),
852
+ };
853
+ }
854
+ async function runAcceptanceCheck(contract, signal) {
855
+ const check = contract.acceptance_check;
856
+ if (!check)
857
+ return null;
858
+ if (signal?.aborted)
859
+ throw signal.reason ?? new Error("acceptance check aborted");
860
+ return new Promise((resolve, reject) => {
861
+ const child = spawnChild(check.command, [...check.args], {
862
+ cwd: contract.workspace.worktreePath ?? contract.workspace.path,
863
+ shell: false,
864
+ stdio: "ignore",
865
+ });
866
+ const onAbort = () => { child.kill("SIGTERM"); };
867
+ signal?.addEventListener("abort", onAbort, { once: true });
868
+ child.once("error", (error) => {
869
+ signal?.removeEventListener("abort", onAbort);
870
+ reject(error);
871
+ });
872
+ child.once("exit", (code) => {
873
+ signal?.removeEventListener("abort", onAbort);
874
+ resolve(code);
875
+ });
876
+ });
877
+ }
878
+ async function requestHumanApproval(ctx, worker) {
879
+ if (ctx.mode !== "tui" || !ctx.hasUI)
880
+ return null;
881
+ return ctx.ui.confirm("Accept worker outcome?", `${worker.workerId}: ${effectiveWorkerContract(worker).goal}`);
882
+ }
883
+ export function projectWorkerForTools(worker) {
884
+ const attempt = worker.attempts.at(-1);
885
+ const contract = effectiveWorkerContract(worker);
886
+ return {
887
+ worker_id: worker.workerId,
888
+ role: contract.role ? {
889
+ name: contract.role.name,
890
+ tier: contract.role.tier,
891
+ preset_source: contract.role.presetSource,
892
+ } : null,
893
+ proposal: contract.proposal ?? null,
894
+ context: contract.context,
895
+ fork_source: contract.forkSource ?? null,
896
+ attempted_models: worker.attempts.map((candidate) => ({
897
+ model: candidate.modelSelection?.model ?? candidate.launchContract?.model ?? contract.model,
898
+ fallback_reason: candidate.modelSelection?.fallbackReason ?? null,
899
+ })),
900
+ tier_degraded: worker.attempts.some((candidate) => candidate.modelSelection?.fallbackReason),
901
+ peer_messages: worker.peerMessages,
902
+ waits: worker.waits,
903
+ continuation: worker.continuation,
904
+ context_refs: contract.contextRefs,
905
+ state: worker.state,
906
+ attempt: attempt ? {
907
+ attempt_id: attempt.attemptId,
908
+ state: attempt.state,
909
+ candidate: attempt.candidateOutcome,
910
+ usage: attempt.usage,
911
+ side_effects: attempt.sideEffects,
912
+ } : null,
913
+ gate: worker.gate,
914
+ stall: worker.stall,
915
+ contract_revisions: worker.contractRevisions.map((revision) => ({
916
+ revision_id: revision.revisionId,
917
+ revision: revision.revision,
918
+ status: revision.status,
919
+ correlation: revision.correlation,
920
+ requested_at: revision.requestedAt,
921
+ delivered_at: revision.deliveredAt,
922
+ })),
923
+ };
924
+ }
925
+ function requiredWorker(run, workerId) {
926
+ const worker = run.workers[workerId];
927
+ if (!worker)
928
+ throw new Error(`Unknown worker ${workerId}`);
929
+ return worker;
930
+ }
931
+ async function requiredRun(store, requestKey) {
932
+ const run = await store.readRun(requestKey);
933
+ if (!run)
934
+ throw new Error(`Unknown run ${requestKey}`);
935
+ return run;
936
+ }
937
+ function parseJsonCandidate(candidate) {
938
+ if (candidate === null)
939
+ return undefined;
940
+ try {
941
+ return JSON.parse(candidate);
942
+ }
943
+ catch {
944
+ return undefined;
945
+ }
946
+ }
947
+ function isTerminal(state) {
948
+ return state === "done" || state === "failed" || state === "cancelled";
949
+ }
950
+ function fleetState(state) {
951
+ switch (state) {
952
+ case "pending":
953
+ return "idle";
954
+ case "running":
955
+ return "busy";
956
+ case "blocked":
957
+ return "paused";
958
+ case "ambiguous":
959
+ return "ambiguous";
960
+ case "done":
961
+ case "failed":
962
+ case "cancelled":
963
+ return "offline";
964
+ default:
965
+ return "ambiguous";
966
+ }
967
+ }
968
+ function hash(value) {
969
+ return createHash("sha256").update(value).digest("hex");
970
+ }
971
+ function toError(error) {
972
+ return error instanceof Error ? error : new Error(String(error));
973
+ }
974
+ //# sourceMappingURL=worker-runtime.js.map