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,396 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+
3
+ import type { WorkerRolePreset } from "../workflow/worker-role-presets.js";
4
+ import { workerRoleContract } from "../workflow/worker-role-presets.js";
5
+ import type {
6
+ EditedWorkerProposal,
7
+ LaunchPreviewDecision,
8
+ } from "../workflow/spawn-confirmation.js";
9
+ import type {
10
+ ModelSpec,
11
+ WorkerBudget,
12
+ WorkerProposalEditableField,
13
+ WorkerRequest,
14
+ } from "../workflow/workflow-types.js";
15
+
16
+ export interface LaunchPreviewRoleOption {
17
+ readonly preset: WorkerRolePreset;
18
+ readonly defaultModel: ModelSpec;
19
+ }
20
+
21
+ export interface LaunchPreviewModelOptions {
22
+ readonly requests: readonly WorkerRequest[];
23
+ readonly roles: readonly LaunchPreviewRoleOption[];
24
+ readonly models: readonly ModelSpec[];
25
+ readonly difficultyModels: Readonly<Record<"easy" | "medium" | "hard", ModelSpec>>;
26
+ readonly normalizeModel?: (requestKey: string, roleName: string, model: ModelSpec) => ModelSpec;
27
+ }
28
+
29
+ interface EditableProposal {
30
+ request: WorkerRequest;
31
+ readonly originalBudget: WorkerBudget;
32
+ readonly edited: Set<WorkerProposalEditableField>;
33
+ }
34
+
35
+ export type BudgetDimension = keyof WorkerBudget;
36
+
37
+ const EDITABLE_FIELDS = ["role", "task", "model", "difficulty", "budget"] as const;
38
+ const BUDGET_DIMENSIONS = [
39
+ "maxAttempts",
40
+ "maxWallClockMs",
41
+ "maxTokens",
42
+ "maxCostUsd",
43
+ "maxOutputBytes",
44
+ ] as const satisfies readonly BudgetDimension[];
45
+ const DIFFICULTIES = ["easy", "medium", "hard"] as const;
46
+
47
+ export class LaunchPreviewModel {
48
+ readonly #proposals: EditableProposal[];
49
+ readonly #roles: readonly LaunchPreviewRoleOption[];
50
+ readonly #models: readonly ModelSpec[];
51
+ readonly #difficultyModels: LaunchPreviewModelOptions["difficultyModels"];
52
+ readonly #normalizeModel: (requestKey: string, roleName: string, model: ModelSpec) => ModelSpec;
53
+
54
+ constructor(options: LaunchPreviewModelOptions) {
55
+ if (options.requests.length === 0) throw new TypeError("Launch preview requires a proposal");
56
+ if (options.roles.length === 0) throw new TypeError("Launch preview requires role options");
57
+ if (options.models.length === 0) throw new TypeError("Launch preview requires live model options");
58
+ this.#proposals = options.requests.map((request) => ({
59
+ request: cloneRequest(request),
60
+ originalBudget: cloneBudget(request.budget),
61
+ edited: new Set(),
62
+ }));
63
+ this.#roles = [...options.roles];
64
+ this.#models = [...options.models];
65
+ this.#difficultyModels = options.difficultyModels;
66
+ this.#normalizeModel = options.normalizeModel ?? ((_requestKey, _roleName, model) => model);
67
+ }
68
+
69
+ get count(): number {
70
+ return this.#proposals.length;
71
+ }
72
+
73
+ request(index: number): WorkerRequest {
74
+ return cloneRequest(this.#entry(index).request);
75
+ }
76
+
77
+ editedFields(index: number): readonly WorkerProposalEditableField[] {
78
+ const edited = this.#entry(index).edited;
79
+ return EDITABLE_FIELDS.filter((field) => edited.has(field));
80
+ }
81
+
82
+ cycleRole(index: number, delta: -1 | 1): void {
83
+ const entry = this.#entry(index);
84
+ const current = this.#roles.findIndex((option) => option.preset.name === entry.request.role?.name);
85
+ const selected = this.#roles[wrap(current < 0 ? 0 : current, delta, this.#roles.length)]!;
86
+ entry.request = {
87
+ ...entry.request,
88
+ role: workerRoleContract(selected.preset),
89
+ difficulty: selected.preset.difficulty,
90
+ capabilities: selected.preset.capabilities,
91
+ model: this.#normalizeModel(entry.request.requestKey, selected.preset.name, selected.defaultModel),
92
+ };
93
+ entry.edited.add("role");
94
+ }
95
+
96
+ cycleModel(index: number, delta: -1 | 1): void {
97
+ const entry = this.#entry(index);
98
+ const current = this.#models.findIndex((model) => sameModel(model, entry.request.model));
99
+ entry.request = {
100
+ ...entry.request,
101
+ model: this.#normalizeModel(
102
+ entry.request.requestKey,
103
+ entry.request.role?.name ?? "worker",
104
+ this.#models[wrap(current < 0 ? 0 : current, delta, this.#models.length)]!,
105
+ ),
106
+ };
107
+ entry.edited.add("model");
108
+ }
109
+
110
+ cycleDifficulty(index: number, delta: -1 | 1): void {
111
+ const entry = this.#entry(index);
112
+ const current = DIFFICULTIES.indexOf(entry.request.difficulty ?? "medium");
113
+ const difficulty = DIFFICULTIES[wrap(current, delta, DIFFICULTIES.length)]!;
114
+ entry.request = {
115
+ ...entry.request,
116
+ difficulty,
117
+ model: this.#normalizeModel(
118
+ entry.request.requestKey,
119
+ entry.request.role?.name ?? "worker",
120
+ this.#difficultyModels[difficulty],
121
+ ),
122
+ };
123
+ entry.edited.add("difficulty");
124
+ }
125
+
126
+ replaceTask(index: number, goal: string): void {
127
+ const normalized = goal.trim();
128
+ if (normalized.length === 0) throw new TypeError("Worker task must not be empty");
129
+ const entry = this.#entry(index);
130
+ entry.request = { ...entry.request, goal: normalized };
131
+ entry.edited.add("task");
132
+ }
133
+
134
+ lowerBudget(index: number, dimension: BudgetDimension, value: number): void {
135
+ if (!Number.isFinite(value) || value <= 0) throw new TypeError("Budget value must be positive");
136
+ if (dimension !== "maxCostUsd" && !Number.isSafeInteger(value)) {
137
+ throw new TypeError(`${dimension} must be a positive integer`);
138
+ }
139
+ const entry = this.#entry(index);
140
+ const original = entry.originalBudget[dimension];
141
+ if (original.kind === "limited" && value > original.value) {
142
+ throw new TypeError(`${dimension} may only stay at or below its proposed value ${original.value}`);
143
+ }
144
+ entry.request = {
145
+ ...entry.request,
146
+ budget: { ...entry.request.budget, [dimension]: { kind: "limited", value } },
147
+ };
148
+ entry.edited.add("budget");
149
+ }
150
+
151
+ accepted(): LaunchPreviewDecision {
152
+ return {
153
+ kind: "accepted",
154
+ proposals: this.#proposals.map((entry): EditedWorkerProposal => ({
155
+ request: cloneRequest(entry.request),
156
+ humanEditedFields: EDITABLE_FIELDS.filter((field) => entry.edited.has(field)),
157
+ })),
158
+ };
159
+ }
160
+
161
+ #entry(index: number): EditableProposal {
162
+ const entry = this.#proposals[index];
163
+ if (!entry) throw new RangeError(`Unknown proposal index ${index}`);
164
+ return entry;
165
+ }
166
+ }
167
+
168
+ export async function openLaunchPreview(
169
+ ctx: ExtensionContext,
170
+ options: LaunchPreviewModelOptions,
171
+ ): Promise<LaunchPreviewDecision> {
172
+ if (ctx.mode !== "tui" || !ctx.hasUI) {
173
+ throw new Error("Launch preview requires an interactive TUI");
174
+ }
175
+ const model = new LaunchPreviewModel(options);
176
+ return ctx.ui.custom<LaunchPreviewDecision>((tui, _theme, _keys, done) => {
177
+ return new LaunchPreviewComponent(model, done, () => tui.requestRender());
178
+ }, { overlay: true });
179
+ }
180
+
181
+ class LaunchPreviewComponent {
182
+ readonly #model: LaunchPreviewModel;
183
+ readonly #done: (decision: LaunchPreviewDecision) => void;
184
+ readonly #invalidate: () => void;
185
+ #worker = 0;
186
+ #field = 0;
187
+ #budgetDimension = 0;
188
+ #editing: "task" | "budget" | undefined;
189
+ #buffer = "";
190
+ #diagnostic = "";
191
+
192
+ constructor(
193
+ model: LaunchPreviewModel,
194
+ done: (decision: LaunchPreviewDecision) => void,
195
+ invalidate: () => void,
196
+ ) {
197
+ this.#model = model;
198
+ this.#done = done;
199
+ this.#invalidate = invalidate;
200
+ }
201
+
202
+ handleInput(data: string): void {
203
+ if (data === "\u001b") {
204
+ this.#done({ kind: "cancelled" });
205
+ return;
206
+ }
207
+ if (this.#editing) {
208
+ if (isEnter(data)) {
209
+ try {
210
+ if (this.#editing === "task") this.#model.replaceTask(this.#worker, this.#buffer);
211
+ else this.#model.lowerBudget(
212
+ this.#worker,
213
+ BUDGET_DIMENSIONS[this.#budgetDimension]!,
214
+ Number(this.#buffer),
215
+ );
216
+ this.#editing = undefined;
217
+ this.#buffer = "";
218
+ this.#diagnostic = "";
219
+ } catch (error) {
220
+ this.#diagnostic = error instanceof Error ? error.message : String(error);
221
+ }
222
+ this.#invalidate();
223
+ return;
224
+ }
225
+ if (data === "\u007f" || data === "\b") this.#buffer = this.#buffer.slice(0, -1);
226
+ else if (!data.startsWith("\u001b") && [...data].every((character) => character >= " ")) {
227
+ this.#buffer += data;
228
+ }
229
+ this.#invalidate();
230
+ return;
231
+ }
232
+ if (isEnter(data)) {
233
+ this.#done(this.#model.accepted());
234
+ return;
235
+ }
236
+ if (isUp(data)) this.#worker = Math.max(0, this.#worker - 1);
237
+ else if (isDown(data)) this.#worker = Math.min(this.#model.count - 1, this.#worker + 1);
238
+ else if (data === "\t") this.#field = wrap(this.#field, 1, EDITABLE_FIELDS.length);
239
+ else if (data === "\u001b[Z") this.#field = wrap(this.#field, -1, EDITABLE_FIELDS.length);
240
+ else if (data === "[" && EDITABLE_FIELDS[this.#field] === "budget") {
241
+ this.#budgetDimension = wrap(this.#budgetDimension, -1, BUDGET_DIMENSIONS.length);
242
+ } else if (data === "]" && EDITABLE_FIELDS[this.#field] === "budget") {
243
+ this.#budgetDimension = wrap(this.#budgetDimension, 1, BUDGET_DIMENSIONS.length);
244
+ } else if (isLeft(data)) this.#cycle(-1);
245
+ else if (isRight(data)) this.#cycle(1);
246
+ else if ((data === "e" || data === "E") && (
247
+ EDITABLE_FIELDS[this.#field] === "task" || EDITABLE_FIELDS[this.#field] === "budget"
248
+ )) {
249
+ this.#editing = EDITABLE_FIELDS[this.#field] as "task" | "budget";
250
+ this.#buffer = "";
251
+ this.#diagnostic = "Type a replacement value, then Enter. Esc cancels the entire launch.";
252
+ }
253
+ this.#invalidate();
254
+ }
255
+
256
+ render(width: number): string[] {
257
+ const request = this.#model.request(this.#worker);
258
+ const field = EDITABLE_FIELDS[this.#field]!;
259
+ const dimension = BUDGET_DIMENSIONS[this.#budgetDimension]!;
260
+ const lines = [
261
+ `Launch preview · ${this.#model.count} proposed worker${this.#model.count === 1 ? "" : "s"}`,
262
+ "↑↓ worker · Tab field · ←→ cycle · [ ] budget dimension · e edit · Enter launch all · Esc cancel all",
263
+ "",
264
+ ...Array.from({ length: this.#model.count }, (_, index) => {
265
+ const candidate = this.#model.request(index);
266
+ const edited = this.#model.editedFields(index).length > 0 ? " edited" : "";
267
+ return `${index === this.#worker ? ">" : " "} ${index + 1}. ${candidate.role?.name ?? "worker"} · ${candidate.goal}${edited}`;
268
+ }),
269
+ "",
270
+ row("role", request.role?.name ?? "worker", field),
271
+ row("task", request.goal, field),
272
+ row("model", renderModel(request.model), field),
273
+ ...(request.modelFallbacks && request.modelFallbacks.length > 0
274
+ ? [` fallback chain: ${request.modelFallbacks.map(renderModel).join(" → ")}`]
275
+ : []),
276
+ row("difficulty", request.difficulty ?? "medium", field),
277
+ row(`budget.${budgetLabel(dimension)}`, renderLimit(request.budget[dimension]), field),
278
+ ` context: ${request.context ?? "fresh"}${request.forkSource
279
+ ? ` · ${request.forkSource.inheritedEntries} entries · ${request.forkSource.inheritedBytes} bytes · leaf ${request.forkSource.branchPointId}`
280
+ : ""}`,
281
+ ` capabilities: ${request.capabilities.join(", ") || "none"}`,
282
+ ...peerGrantLines(request.capabilities),
283
+ ...(this.#editing ? [` ${this.#editing}> ${this.#buffer}_`] : []),
284
+ ...(this.#diagnostic ? [` ${this.#diagnostic}`] : []),
285
+ ];
286
+ return lines.map((line) => truncate(line, Math.max(20, width)));
287
+ }
288
+
289
+ invalidate(): void {}
290
+
291
+ #cycle(delta: -1 | 1): void {
292
+ switch (EDITABLE_FIELDS[this.#field]) {
293
+ case "role":
294
+ this.#model.cycleRole(this.#worker, delta);
295
+ return;
296
+ case "model":
297
+ this.#model.cycleModel(this.#worker, delta);
298
+ return;
299
+ case "difficulty":
300
+ this.#model.cycleDifficulty(this.#worker, delta);
301
+ return;
302
+ case "task":
303
+ case "budget":
304
+ this.#diagnostic = "Press e to replace this value";
305
+ return;
306
+ }
307
+ }
308
+ }
309
+
310
+ function cloneRequest(request: WorkerRequest): WorkerRequest {
311
+ return {
312
+ ...request,
313
+ acceptance: [...request.acceptance],
314
+ constraints: [...request.constraints],
315
+ budget: cloneBudget(request.budget),
316
+ model: { ...request.model },
317
+ ...(request.modelFallbacks
318
+ ? { modelFallbacks: request.modelFallbacks.map((model) => ({ ...model })) }
319
+ : {}),
320
+ capabilities: [...request.capabilities],
321
+ workspace: { ...request.workspace },
322
+ contextRefs: request.contextRefs.map((reference) => ({ ...reference })),
323
+ ...(request.role ? { role: { ...request.role } } : {}),
324
+ ...(request.proposal ? {
325
+ proposal: {
326
+ ...request.proposal,
327
+ humanEditedFields: [...request.proposal.humanEditedFields],
328
+ },
329
+ } : {}),
330
+ };
331
+ }
332
+
333
+ function cloneBudget(budget: WorkerBudget): WorkerBudget {
334
+ return {
335
+ maxAttempts: { ...budget.maxAttempts },
336
+ maxWallClockMs: { ...budget.maxWallClockMs },
337
+ maxTokens: { ...budget.maxTokens },
338
+ maxCostUsd: { ...budget.maxCostUsd },
339
+ maxOutputBytes: { ...budget.maxOutputBytes },
340
+ };
341
+ }
342
+
343
+ function renderModel(model: ModelSpec): string {
344
+ return `${model.provider}/${model.id}${model.thinkingLevel ? ` (${model.thinkingLevel})` : ""}`;
345
+ }
346
+
347
+ function peerGrantLines(capabilities: readonly string[]): readonly string[] {
348
+ const grants = capabilities.filter((capability) => capability.startsWith("message_worker:"));
349
+ return grants.length === 0 ? [] : [` peer grants: ${grants.join(", ")}`];
350
+ }
351
+
352
+ function sameModel(left: ModelSpec, right: ModelSpec): boolean {
353
+ return left.provider === right.provider
354
+ && left.id === right.id;
355
+ }
356
+
357
+ function renderLimit(limit: WorkerBudget[BudgetDimension]): string {
358
+ return limit.kind === "unlimited" ? "unlimited" : String(limit.value);
359
+ }
360
+
361
+ function budgetLabel(dimension: BudgetDimension): string {
362
+ return dimension.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
363
+ }
364
+
365
+ function row(label: string, value: string, selected: string): string {
366
+ return `${selected === label.split(".")[0] ? ">" : " "} ${label}: ${value}`;
367
+ }
368
+
369
+ function wrap(index: number, delta: -1 | 1, length: number): number {
370
+ return (index + delta + length) % length;
371
+ }
372
+
373
+ function isEnter(data: string): boolean {
374
+ return data === "\r" || data === "\n";
375
+ }
376
+
377
+ function isUp(data: string): boolean {
378
+ return data === "\u001b[A";
379
+ }
380
+
381
+ function isDown(data: string): boolean {
382
+ return data === "\u001b[B";
383
+ }
384
+
385
+ function isRight(data: string): boolean {
386
+ return data === "\u001b[C";
387
+ }
388
+
389
+ function isLeft(data: string): boolean {
390
+ return data === "\u001b[D";
391
+ }
392
+
393
+ function truncate(value: string, width: number): string {
394
+ const characters = [...value];
395
+ return characters.length <= width ? value : `${characters.slice(0, Math.max(1, width - 1)).join("")}…`;
396
+ }
@@ -0,0 +1,29 @@
1
+ export type TransportState =
2
+ | { readonly kind: "dormant"; readonly reason: string }
3
+ | { readonly kind: "send-only"; readonly binding: unknown }
4
+ | {
5
+ readonly kind: "receiving";
6
+ readonly binding: unknown;
7
+ readonly receiverGeneration: string;
8
+ };
9
+
10
+ export type TransportResult<T> =
11
+ | { readonly ok: true; readonly value: T }
12
+ | { readonly ok: false; readonly error: TransportError };
13
+
14
+ export type TransportError =
15
+ | {
16
+ readonly code: "unavailable" | "invalid-request" | "context-mismatch";
17
+ readonly committed: false;
18
+ readonly cause?: unknown;
19
+ }
20
+ | { readonly code: "cancelled"; readonly committed: false; readonly cause?: unknown }
21
+ | { readonly code: "commit-indeterminate"; readonly committed: "unknown"; readonly cause: unknown };
22
+
23
+ /** Internal cross-domain stub; concrete command/result types arrive with their owning phases. */
24
+ export interface TransportPort {
25
+ state(): TransportState;
26
+ send(command: unknown, signal: AbortSignal): Promise<TransportResult<unknown>>;
27
+ reply(command: unknown, signal: AbortSignal): Promise<TransportResult<unknown>>;
28
+ waitForReceipt(query: unknown, signal: AbortSignal): Promise<TransportResult<unknown>>;
29
+ }
@@ -0,0 +1,98 @@
1
+ import type { RunProjectionV2, WorkerId } from "../workflow/workflow-types.js";
2
+
3
+ export interface WorkerNotice {
4
+ readonly level: "info" | "warning" | "error";
5
+ readonly message: string;
6
+ }
7
+
8
+ export interface CompletionTimerScheduler {
9
+ schedule(delayMs: number, callback: () => void): { cancel(): void };
10
+ }
11
+
12
+ export class WorkerCompletionNotifier {
13
+ readonly #publish: (notice: WorkerNotice) => void;
14
+ readonly #timers: CompletionTimerScheduler;
15
+ readonly #windowMs: number;
16
+ readonly #states = new Map<WorkerId, string>();
17
+ readonly #successful = new Set<WorkerId>();
18
+ #flush: { cancel(): void } | undefined;
19
+
20
+ constructor(options: {
21
+ readonly publish: (notice: WorkerNotice) => void;
22
+ readonly timers?: CompletionTimerScheduler;
23
+ readonly windowMs?: number;
24
+ }) {
25
+ this.#publish = options.publish;
26
+ this.#timers = options.timers ?? new NodeCompletionTimers();
27
+ this.#windowMs = options.windowMs ?? 350;
28
+ }
29
+
30
+ baseline(run: RunProjectionV2): void {
31
+ for (const worker of Object.values(run.workers)) {
32
+ this.#states.set(worker.workerId, worker.state);
33
+ }
34
+ }
35
+
36
+ observe(run: RunProjectionV2): void {
37
+ for (const worker of Object.values(run.workers)) {
38
+ const previous = this.#states.get(worker.workerId);
39
+ this.#states.set(worker.workerId, worker.state);
40
+ if (previous === undefined || previous === worker.state) continue;
41
+ switch (worker.state) {
42
+ case "done":
43
+ this.#successful.add(worker.workerId);
44
+ this.#scheduleSuccessFlush();
45
+ break;
46
+ case "failed":
47
+ this.#emit({ level: "error", message: `Worker ${worker.workerId} failed.` });
48
+ break;
49
+ case "blocked":
50
+ this.#emit({ level: "warning", message: `Worker ${worker.workerId} paused for typed input.` });
51
+ break;
52
+ case "ambiguous":
53
+ this.#emit({ level: "warning", message: `Worker ${worker.workerId} requires reconciliation.` });
54
+ break;
55
+ default:
56
+ break;
57
+ }
58
+ }
59
+ }
60
+
61
+ dispose(): void {
62
+ this.#flush?.cancel();
63
+ this.#flush = undefined;
64
+ this.#successful.clear();
65
+ }
66
+
67
+ #scheduleSuccessFlush(): void {
68
+ if (this.#flush) return;
69
+ this.#flush = this.#timers.schedule(this.#windowMs, () => {
70
+ this.#flush = undefined;
71
+ const ids = [...this.#successful].sort();
72
+ this.#successful.clear();
73
+ if (ids.length === 0) return;
74
+ this.#emit({
75
+ level: "info",
76
+ message: ids.length === 1
77
+ ? `Worker ${ids[0]} completed.`
78
+ : `Workers completed: ${ids.join(", ")}.`,
79
+ });
80
+ });
81
+ }
82
+
83
+ #emit(notice: WorkerNotice): void {
84
+ try {
85
+ this.#publish(notice);
86
+ } catch {
87
+ // UI delivery is advisory and cannot invalidate committed ledger truth.
88
+ }
89
+ }
90
+ }
91
+
92
+ class NodeCompletionTimers implements CompletionTimerScheduler {
93
+ schedule(delayMs: number, callback: () => void): { cancel(): void } {
94
+ const timer = setTimeout(callback, Math.max(0, delayMs));
95
+ timer.unref();
96
+ return { cancel: () => clearTimeout(timer) };
97
+ }
98
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "pi-amq",
3
+ "version": "0.1.1",
4
+ "description": "A daemonless AMQ bridge and governed worker runtime for pi",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "pi-package",
9
+ "amq",
10
+ "agent"
11
+ ],
12
+ "engines": {
13
+ "node": ">=22.19.0"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "docs",
18
+ "extension",
19
+ "integration",
20
+ "scripts",
21
+ "skills",
22
+ "roles",
23
+ "CHANGELOG.md",
24
+ "THIRD_PARTY_NOTICES.md",
25
+ "transport",
26
+ "workflow",
27
+ "README.md"
28
+ ],
29
+ "pi": {
30
+ "extensions": [
31
+ "./extension/index.ts"
32
+ ],
33
+ "skills": [
34
+ "./skills"
35
+ ]
36
+ },
37
+ "scripts": {
38
+ "build": "tsc --project tsconfig.build.json",
39
+ "typecheck": "tsc --project tsconfig.json",
40
+ "test:compile": "tsc --project tsconfig.test.json",
41
+ "test": "npm run test:compile && node --test .test-dist/test/*.test.js",
42
+ "prepack": "npm run build"
43
+ },
44
+ "peerDependencies": {
45
+ "@earendil-works/pi-coding-agent": ">=0.84.2"
46
+ },
47
+ "devDependencies": {
48
+ "@earendil-works/pi-coding-agent": "^0.84.2",
49
+ "@types/node": "24.12.4",
50
+ "typescript": "5.9.3"
51
+ },
52
+ "dependencies": {
53
+ "typebox": "^1.3.7"
54
+ }
55
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: oracle
3
+ tier: premium
4
+ difficulty: hard
5
+ capabilities:
6
+ - repo.read
7
+ - shell
8
+ ---
9
+ Resolve a high-consequence ambiguity from authoritative evidence. State the strongest supported judgment, competing explanations, falsifiers, and the smallest next check that would change the decision.
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: researcher
3
+ tier: workhorse
4
+ difficulty: medium
5
+ capabilities:
6
+ - repo.read
7
+ ---
8
+ Research the bounded question against primary sources. Separate sourced facts from inference, retain precise citations or local evidence paths, and identify material uncertainty without padding the report.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: reviewer
3
+ tier: workhorse
4
+ difficulty: medium
5
+ capabilities:
6
+ - repo.read
7
+ - shell
8
+ ---
9
+ Review independently from implementation. Lead with verdict and source-backed findings, rerun decisive checks when authorized, and do not edit unless the contract explicitly requests remediation.
package/roles/scout.md ADDED
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: scout
3
+ tier: economy
4
+ difficulty: easy
5
+ capabilities:
6
+ - repo.read
7
+ ---
8
+ Map the requested surface quickly. Return exact paths, relevant symbols, and unresolved questions. Do not edit files or turn adjacent observations into scope.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: worker
3
+ tier: workhorse
4
+ difficulty: medium
5
+ capabilities:
6
+ - repo.read
7
+ - repo.write
8
+ - shell
9
+ ---
10
+ Implement one bounded contract end to end. Preserve unrelated work, validate at the nearest stable seam, and report the changed artifacts, evidence, and residual risk.
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ AMQ_BIN="${AMQ_BIN:-/opt/homebrew/bin/amq}"
5
+ DEMO_OWNED=0
6
+ if [[ -n "${PI_AMQ_DEMO_DIR:-}" ]]; then
7
+ DEMO_DIR="${PI_AMQ_DEMO_DIR}"
8
+ else
9
+ DEMO_DIR="$(mktemp -d "${TMPDIR:-/tmp}/pi-amq-demo.XXXXXX")"
10
+ DEMO_OWNED=1
11
+ fi
12
+ DEMO_ROOT="${DEMO_DIR}/mail"
13
+
14
+ amq_demo() {
15
+ env \
16
+ -u AM_ROOT -u AM_ROOT_ID -u AM_BASE_ROOT -u AM_BASE_ROOT_ID \
17
+ -u AM_SESSION -u AM_ME \
18
+ "${AMQ_BIN}" "$@"
19
+ }
20
+
21
+ json_id() {
22
+ node -e 'let value=""; process.stdin.on("data", chunk => value += chunk); process.stdin.on("end", () => process.stdout.write(JSON.parse(value).id));'
23
+ }
24
+
25
+ mkdir -p "${DEMO_DIR}"
26
+ (
27
+ cd "${DEMO_DIR}"
28
+ amq_demo coop init --root "${DEMO_ROOT}" --agents terminal-a,terminal-b,user --no-gitignore --json >/dev/null
29
+ )
30
+
31
+ echo "demo_root=${DEMO_ROOT}"
32
+ echo "[terminal-a] publish status and send urgent/normal/low"
33
+ amq_demo presence set --root "${DEMO_ROOT}" --me terminal-a --status busy --note "sending demo cohort" --json
34
+ URGENT_ID="$(amq_demo send --root "${DEMO_ROOT}" --me terminal-a --to terminal-b --priority urgent --kind todo --subject urgent --body "urgent demo" --json | json_id)"
35
+ NORMAL_ID="$(amq_demo send --root "${DEMO_ROOT}" --me terminal-a --to terminal-b --priority normal --kind question --subject normal --body "normal demo question" --json | json_id)"
36
+ LOW_ID="$(amq_demo send --root "${DEMO_ROOT}" --me terminal-a --to terminal-b --priority low --kind status --subject low --body "low demo status" --json | json_id)"
37
+ printf 'sent urgent=%s normal=%s low=%s\n' "${URGENT_ID}" "${NORMAL_ID}" "${LOW_ID}"
38
+
39
+ echo "[terminal-b] inspect priority order, claim exact IDs, and reply"
40
+ amq_demo list --root "${DEMO_ROOT}" --me terminal-b --new --limit 10 --json
41
+ amq_demo read --root "${DEMO_ROOT}" --me terminal-b --id "${URGENT_ID}" --json
42
+ amq_demo read --root "${DEMO_ROOT}" --me terminal-b --id "${NORMAL_ID}" --json
43
+ amq_demo read --root "${DEMO_ROOT}" --me terminal-b --id "${LOW_ID}" --json
44
+ REPLY_ID="$(amq_demo reply --root "${DEMO_ROOT}" --me terminal-b --id "${NORMAL_ID}" --kind answer --body "normal demo answer" --json | json_id)"
45
+ printf 'reply=%s\n' "${REPLY_ID}"
46
+ amq_demo presence set --root "${DEMO_ROOT}" --me terminal-b --status idle --note "demo cohort drained" --json
47
+
48
+ echo "[terminal-a] claim reply, inspect receipts, and list status"
49
+ amq_demo read --root "${DEMO_ROOT}" --me terminal-a --id "${REPLY_ID}" --json
50
+ amq_demo receipts list --root "${DEMO_ROOT}" --me terminal-b --msg-id "${URGENT_ID}" --json
51
+ amq_demo receipts list --root "${DEMO_ROOT}" --me terminal-b --msg-id "${NORMAL_ID}" --json
52
+ amq_demo receipts list --root "${DEMO_ROOT}" --me terminal-a --msg-id "${REPLY_ID}" --json
53
+ amq_demo presence list --root "${DEMO_ROOT}" --me terminal-a --json
54
+
55
+ if [[ "${DEMO_OWNED}" == "1" && "${KEEP_DEMO:-0}" != "1" ]]; then
56
+ rm -rf -- "${DEMO_DIR}"
57
+ else
58
+ echo "kept_demo_dir=${DEMO_DIR}"
59
+ fi