agent-tempo 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/CLAUDE.md +253 -219
  2. package/LICENSE +21 -21
  3. package/README.md +293 -289
  4. package/assets/icon-dark.svg +9 -9
  5. package/assets/icon.svg +9 -9
  6. package/assets/logo-dark.svg +11 -11
  7. package/assets/logo-light.svg +11 -11
  8. package/dashboard/README.md +91 -91
  9. package/dashboard/dist/assets/{index-D6Xyje_n.js → index-jmYe6rmS.js} +2 -2
  10. package/dashboard/dist/assets/index-jmYe6rmS.js.map +1 -0
  11. package/dashboard/dist/index.html +20 -20
  12. package/dashboard/package.json +47 -47
  13. package/dist/activities/outbox.d.ts +30 -1
  14. package/dist/activities/outbox.js +96 -3
  15. package/dist/adapters/base.js +5 -0
  16. package/dist/adapters/copilot/adapter.js +12 -1
  17. package/dist/adapters/index.d.ts +1 -1
  18. package/dist/adapters/index.js +7 -0
  19. package/dist/adapters/pi/adapter.d.ts +2 -0
  20. package/dist/adapters/pi/adapter.js +43 -0
  21. package/dist/adapters/pi/index.d.ts +16 -0
  22. package/dist/adapters/pi/index.js +10 -0
  23. package/dist/cli/global-wrapper.d.ts +19 -0
  24. package/dist/cli/global-wrapper.js +169 -0
  25. package/dist/cli/help-text.js +97 -97
  26. package/dist/cli/startup.js +11 -0
  27. package/dist/cli/upgrade-command.js +81 -81
  28. package/dist/cli.js +12 -0
  29. package/dist/client/core.js +9 -2
  30. package/dist/client/interface.d.ts +6 -0
  31. package/dist/config.d.ts +79 -0
  32. package/dist/config.js +74 -0
  33. package/dist/daemon.js +37 -1
  34. package/dist/http/aggregate.d.ts +22 -1
  35. package/dist/http/aggregate.js +41 -0
  36. package/dist/http/auth.d.ts +94 -8
  37. package/dist/http/auth.js +93 -9
  38. package/dist/http/body.d.ts +4 -1
  39. package/dist/http/body.js +6 -3
  40. package/dist/http/event-bus.js +1 -0
  41. package/dist/http/event-types.d.ts +34 -2
  42. package/dist/http/event-types.js +1 -0
  43. package/dist/http/gate-audit.d.ts +12 -0
  44. package/dist/http/gate-audit.js +95 -0
  45. package/dist/http/gate-registry.d.ts +167 -0
  46. package/dist/http/gate-registry.js +163 -0
  47. package/dist/http/gate-routes.d.ts +48 -0
  48. package/dist/http/gate-routes.js +102 -0
  49. package/dist/http/ingest-registry.d.ts +30 -0
  50. package/dist/http/ingest-registry.js +108 -0
  51. package/dist/http/inner-loop-routes.d.ts +66 -0
  52. package/dist/http/inner-loop-routes.js +182 -0
  53. package/dist/http/inner-loop.d.ts +92 -0
  54. package/dist/http/inner-loop.js +155 -0
  55. package/dist/http/server.d.ts +38 -3
  56. package/dist/http/server.js +211 -6
  57. package/dist/http/snapshot.d.ts +6 -0
  58. package/dist/http/snapshot.js +6 -0
  59. package/dist/pi/cue-pump.d.ts +61 -0
  60. package/dist/pi/cue-pump.js +95 -0
  61. package/dist/pi/extension.d.ts +45 -0
  62. package/dist/pi/extension.js +407 -0
  63. package/dist/pi/gate-client.d.ts +54 -0
  64. package/dist/pi/gate-client.js +136 -0
  65. package/dist/pi/headless.d.ts +85 -0
  66. package/dist/pi/headless.js +224 -0
  67. package/dist/pi/index.d.ts +28 -0
  68. package/dist/pi/index.js +43 -0
  69. package/dist/pi/inner-loop-client.d.ts +67 -0
  70. package/dist/pi/inner-loop-client.js +164 -0
  71. package/dist/pi/inner-loop-publisher.d.ts +187 -0
  72. package/dist/pi/inner-loop-publisher.js +236 -0
  73. package/dist/pi/lazy-proxy.d.ts +37 -0
  74. package/dist/pi/lazy-proxy.js +55 -0
  75. package/dist/pi/mission-control/actions.d.ts +48 -0
  76. package/dist/pi/mission-control/actions.js +98 -0
  77. package/dist/pi/mission-control/board.d.ts +53 -0
  78. package/dist/pi/mission-control/board.js +104 -0
  79. package/dist/pi/mission-control/extension.d.ts +44 -0
  80. package/dist/pi/mission-control/extension.js +251 -0
  81. package/dist/pi/mission-control/index.d.ts +15 -0
  82. package/dist/pi/mission-control/index.js +32 -0
  83. package/dist/pi/mission-control/inner-tail.d.ts +48 -0
  84. package/dist/pi/mission-control/inner-tail.js +76 -0
  85. package/dist/pi/mission-control/pi-ui.d.ts +43 -0
  86. package/dist/pi/mission-control/pi-ui.js +10 -0
  87. package/dist/pi/mission-control/render.d.ts +6 -0
  88. package/dist/pi/mission-control/render.js +95 -0
  89. package/dist/pi/phase-driver.d.ts +74 -0
  90. package/dist/pi/phase-driver.js +122 -0
  91. package/dist/pi/pi-types.d.ts +208 -0
  92. package/dist/pi/pi-types.js +21 -0
  93. package/dist/pi/probe.d.ts +80 -0
  94. package/dist/pi/probe.js +154 -0
  95. package/dist/pi/render-tools.d.ts +17 -0
  96. package/dist/pi/render-tools.js +51 -0
  97. package/dist/pi/reset-pump.d.ts +47 -0
  98. package/dist/pi/reset-pump.js +85 -0
  99. package/dist/pi/tool-capability.d.ts +60 -0
  100. package/dist/pi/tool-capability.js +156 -0
  101. package/dist/pi/workflow-client.d.ts +158 -0
  102. package/dist/pi/workflow-client.js +289 -0
  103. package/dist/pi/zod-to-typebox.d.ts +74 -0
  104. package/dist/pi/zod-to-typebox.js +191 -0
  105. package/dist/scripts/verify-daemon-isolation-guard.js +24 -24
  106. package/dist/server-tools.d.ts +2 -0
  107. package/dist/server-tools.js +50 -46
  108. package/dist/server.js +4 -0
  109. package/dist/spawn.d.ts +55 -0
  110. package/dist/spawn.js +84 -12
  111. package/dist/tools/agent-types.d.ts +2 -2
  112. package/dist/tools/agent-types.js +22 -17
  113. package/dist/tools/attachment-info.d.ts +2 -2
  114. package/dist/tools/attachment-info.js +38 -33
  115. package/dist/tools/broadcast.d.ts +2 -2
  116. package/dist/tools/broadcast.js +69 -64
  117. package/dist/tools/cancel-stage.d.ts +2 -2
  118. package/dist/tools/cancel-stage.js +20 -15
  119. package/dist/tools/clear-state.d.ts +2 -2
  120. package/dist/tools/clear-state.js +25 -20
  121. package/dist/tools/coat-check-evict.d.ts +2 -2
  122. package/dist/tools/coat-check-evict.js +30 -25
  123. package/dist/tools/coat-check-get.d.ts +2 -2
  124. package/dist/tools/coat-check-get.js +39 -34
  125. package/dist/tools/coat-check-list.d.ts +2 -2
  126. package/dist/tools/coat-check-list.js +48 -43
  127. package/dist/tools/coat-check-put.d.ts +2 -2
  128. package/dist/tools/coat-check-put.js +41 -36
  129. package/dist/tools/cue.d.ts +2 -2
  130. package/dist/tools/cue.js +57 -52
  131. package/dist/tools/descriptor.d.ts +72 -0
  132. package/dist/tools/descriptor.js +39 -0
  133. package/dist/tools/destroy.d.ts +2 -2
  134. package/dist/tools/destroy.js +153 -148
  135. package/dist/tools/ensemble.d.ts +2 -2
  136. package/dist/tools/ensemble.js +71 -66
  137. package/dist/tools/evaluate-gate.d.ts +2 -2
  138. package/dist/tools/evaluate-gate.js +33 -27
  139. package/dist/tools/fetch-state.d.ts +2 -2
  140. package/dist/tools/fetch-state.js +43 -38
  141. package/dist/tools/gates.d.ts +2 -2
  142. package/dist/tools/gates.js +39 -34
  143. package/dist/tools/hosts.d.ts +2 -2
  144. package/dist/tools/hosts.js +25 -20
  145. package/dist/tools/listen.d.ts +2 -2
  146. package/dist/tools/listen.js +23 -18
  147. package/dist/tools/load-lineup.d.ts +2 -2
  148. package/dist/tools/load-lineup.js +324 -319
  149. package/dist/tools/migrate.d.ts +2 -2
  150. package/dist/tools/migrate.js +45 -40
  151. package/dist/tools/pause.d.ts +2 -2
  152. package/dist/tools/pause.js +34 -29
  153. package/dist/tools/play.d.ts +2 -2
  154. package/dist/tools/play.js +53 -48
  155. package/dist/tools/quality-gate.d.ts +2 -2
  156. package/dist/tools/quality-gate.js +26 -21
  157. package/dist/tools/recall.d.ts +2 -2
  158. package/dist/tools/recall.js +32 -27
  159. package/dist/tools/recruit.d.ts +2 -2
  160. package/dist/tools/recruit.js +325 -256
  161. package/dist/tools/release.d.ts +2 -2
  162. package/dist/tools/release.js +85 -80
  163. package/dist/tools/report.d.ts +2 -2
  164. package/dist/tools/report.js +28 -23
  165. package/dist/tools/reset.d.ts +3 -0
  166. package/dist/tools/reset.js +51 -0
  167. package/dist/tools/restart.d.ts +2 -2
  168. package/dist/tools/restart.js +51 -46
  169. package/dist/tools/restore.d.ts +2 -2
  170. package/dist/tools/restore.js +76 -71
  171. package/dist/tools/save-lineup.d.ts +2 -2
  172. package/dist/tools/save-lineup.js +32 -27
  173. package/dist/tools/save-state.d.ts +2 -2
  174. package/dist/tools/save-state.js +43 -38
  175. package/dist/tools/schedule.d.ts +2 -2
  176. package/dist/tools/schedule.js +133 -128
  177. package/dist/tools/schedules.d.ts +2 -2
  178. package/dist/tools/schedules.js +41 -36
  179. package/dist/tools/set-ensemble-description.d.ts +2 -2
  180. package/dist/tools/set-ensemble-description.js +26 -21
  181. package/dist/tools/set-name.d.ts +2 -2
  182. package/dist/tools/set-name.js +38 -33
  183. package/dist/tools/set-part.d.ts +2 -2
  184. package/dist/tools/set-part.js +20 -15
  185. package/dist/tools/shutdown.d.ts +2 -2
  186. package/dist/tools/shutdown.js +39 -34
  187. package/dist/tools/stage.d.ts +2 -2
  188. package/dist/tools/stage.js +28 -23
  189. package/dist/tools/stages.d.ts +2 -2
  190. package/dist/tools/stages.js +36 -31
  191. package/dist/tools/unschedule.d.ts +2 -2
  192. package/dist/tools/unschedule.js +30 -25
  193. package/dist/tools/who-am-i.d.ts +2 -2
  194. package/dist/tools/who-am-i.js +36 -31
  195. package/dist/tools/worktree.d.ts +2 -2
  196. package/dist/tools/worktree.js +134 -129
  197. package/dist/tui/index.js +6 -6
  198. package/dist/types.d.ts +47 -2
  199. package/dist/types.js +1 -1
  200. package/dist/utils/default-part.js +1 -0
  201. package/dist/utils/grpc-shutdown-guard.d.ts +52 -0
  202. package/dist/utils/grpc-shutdown-guard.js +88 -0
  203. package/dist/utils/sdk-probe.d.ts +23 -0
  204. package/dist/utils/sdk-probe.js +46 -7
  205. package/dist/worker.d.ts +3 -1
  206. package/dist/worker.js +6 -2
  207. package/dist/workflows/session.js +70 -2
  208. package/dist/workflows/signals.d.ts +32 -2
  209. package/dist/workflows/signals.js +25 -2
  210. package/examples/agents/tempo-composer.md +56 -56
  211. package/examples/agents/tempo-conductor.md +117 -117
  212. package/examples/agents/tempo-critic.md +73 -73
  213. package/examples/agents/tempo-improv.md +74 -74
  214. package/examples/agents/tempo-liner.md +75 -75
  215. package/examples/agents/tempo-roadie.md +61 -61
  216. package/examples/agents/tempo-soloist.md +71 -71
  217. package/examples/agents/tempo-tuner.md +94 -94
  218. package/examples/ensembles/tempo-big-band.yaml +146 -146
  219. package/examples/ensembles/tempo-dev-team.yaml +58 -58
  220. package/examples/ensembles/tempo-headless-jam.yaml +77 -77
  221. package/examples/ensembles/tempo-jam-session.yaml +41 -41
  222. package/examples/ensembles/tempo-mock-jam.yaml +79 -79
  223. package/examples/ensembles/tempo-review-squad.yaml +32 -32
  224. package/package.json +176 -173
  225. package/packaging/launchd/com.agent.tempo.plist +46 -46
  226. package/packaging/systemd/agent-tempo.service +32 -32
  227. package/packaging/windows/install-task.ps1 +71 -71
  228. package/scenarios/conductor-recruit-mock.yaml +33 -33
  229. package/scenarios/echo-roundtrip.yaml +15 -15
  230. package/scenarios/multi-player-handoff.yaml +38 -38
  231. package/scenarios/recruit-cascade.yaml +38 -38
  232. package/scenarios/two-player-conversation.yaml +33 -33
  233. package/workflow-bundle.js +97 -6
  234. package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
  235. package/dist/activities/claude-stop.d.ts +0 -21
  236. package/dist/activities/claude-stop.js +0 -94
  237. package/dist/channel.d.ts +0 -3
  238. package/dist/channel.js +0 -48
  239. package/dist/copilot-bridge.d.ts +0 -22
  240. package/dist/copilot-bridge.js +0 -565
  241. package/dist/scripts/258-spotcheck.js +0 -303
  242. package/dist/tools/detach.d.ts +0 -4
  243. package/dist/tools/detach.js +0 -45
  244. package/dist/tools/encore.d.ts +0 -4
  245. package/dist/tools/encore.js +0 -31
  246. package/dist/tools/helpers.d.ts +0 -21
  247. package/dist/tools/helpers.js +0 -25
  248. package/dist/tools/pause-ensemble.d.ts +0 -4
  249. package/dist/tools/pause-ensemble.js +0 -58
  250. package/dist/tools/resume-ensemble.d.ts +0 -4
  251. package/dist/tools/resume-ensemble.js +0 -79
  252. package/dist/tools/stop.d.ts +0 -4
  253. package/dist/tools/stop.js +0 -29
  254. package/dist/tui/client.d.ts +0 -6
  255. package/dist/tui/client.js +0 -9
  256. package/dist/tui/components/ActivityLog.d.ts +0 -16
  257. package/dist/tui/components/ActivityLog.js +0 -36
  258. package/dist/tui/components/CommandOverlay.d.ts +0 -15
  259. package/dist/tui/components/CommandOverlay.js +0 -34
  260. package/dist/tui/components/ConductorChat.d.ts +0 -16
  261. package/dist/tui/components/ConductorChat.js +0 -32
  262. package/dist/tui/components/EnsembleListView.d.ts +0 -14
  263. package/dist/tui/components/EnsembleListView.js +0 -32
  264. package/dist/tui/components/EnsemblePanel.d.ts +0 -12
  265. package/dist/tui/components/EnsemblePanel.js +0 -40
  266. package/dist/tui/components/InputBar.d.ts +0 -13
  267. package/dist/tui/components/InputBar.js +0 -58
  268. package/dist/tui/components/ScheduleOverlay.d.ts +0 -13
  269. package/dist/tui/components/ScheduleOverlay.js +0 -113
  270. package/dist/tui/components/TopBar.d.ts +0 -12
  271. package/dist/tui/components/TopBar.js +0 -15
  272. package/dist/tui/core-api.d.ts +0 -26
  273. package/dist/tui/core-api.js +0 -67
  274. package/dist/tui/hooks/useEnsembleDiscovery.d.ts +0 -3
  275. package/dist/tui/hooks/useEnsembleDiscovery.js +0 -30
  276. package/dist/tui/hooks/useMaestroPoller.d.ts +0 -3
  277. package/dist/tui/hooks/useMaestroPoller.js +0 -36
  278. package/dist/tui/hooks/useSendCommand.d.ts +0 -7
  279. package/dist/tui/hooks/useSendCommand.js +0 -29
  280. package/dist/utils/bg-preflight.d.ts +0 -25
  281. package/dist/utils/bg-preflight.js +0 -154
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Fine inner-loop tail consumer (3f) — the OPERATOR egress side of the 3c
3
+ * side-channel: `GET /v1/players/:e/:p/inner` (SSE, T3 admin bearer). Distinct
4
+ * from `src/pi/inner-loop-client.ts` (the player→daemon INGRESS). Opened on
5
+ * `/tail <player>`, torn down on deselect / shutdown.
6
+ *
7
+ * The SSE-frame parser is pure + unit-tested; the stream pump is injectable
8
+ * (fetch) so the extension wires real `fetch` while tests drive frames directly.
9
+ *
10
+ * NOTE (cross-host): `/inner` is daemon-LOCAL to the player's host. Single-host
11
+ * (Meijer container) uses the local daemon; multi-host should resolve the
12
+ * player's `preferredHost` via `/v1/hosts` and target that daemon — the
13
+ * `baseUrl` option is the seam for that (don't hardcode localhost forever).
14
+ */
15
+ import type { InnerFrame } from '../inner-loop-publisher';
16
+ /**
17
+ * Parse the `data:` payloads out of one SSE text chunk, returning the decoded
18
+ * InnerFrames + any trailing partial buffer to prepend to the next chunk. Pure.
19
+ * Tolerates keepalive comment lines (`:`) and multi-line frames split on `\n\n`.
20
+ */
21
+ export declare function parseInnerSse(chunk: string, carry?: string): {
22
+ frames: InnerFrame[];
23
+ carry: string;
24
+ };
25
+ export type TailFetch = (url: string, init: {
26
+ method: string;
27
+ headers: Record<string, string>;
28
+ signal?: AbortSignal;
29
+ }) => Promise<{
30
+ status: number;
31
+ body: AsyncIterable<Uint8Array> | null;
32
+ }>;
33
+ export interface OpenInnerTailOptions {
34
+ baseUrl: string;
35
+ adminToken: string;
36
+ ensemble: string;
37
+ playerId: string;
38
+ onFrame: (frame: InnerFrame) => void;
39
+ signal: AbortSignal;
40
+ fetchFn: TailFetch;
41
+ /** Called on a non-200 / stream error (e.g. to surface in the widget). */
42
+ onError?: (message: string) => void;
43
+ }
44
+ /**
45
+ * Open the per-player inner SSE and pump frames to `onFrame` until `signal`
46
+ * aborts. Resolves when the stream ends/aborts; never throws (errors → onError).
47
+ */
48
+ export declare function openInnerTail(opts: OpenInnerTailOptions): Promise<void>;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseInnerSse = parseInnerSse;
4
+ exports.openInnerTail = openInnerTail;
5
+ /**
6
+ * Parse the `data:` payloads out of one SSE text chunk, returning the decoded
7
+ * InnerFrames + any trailing partial buffer to prepend to the next chunk. Pure.
8
+ * Tolerates keepalive comment lines (`:`) and multi-line frames split on `\n\n`.
9
+ */
10
+ function parseInnerSse(chunk, carry = '') {
11
+ const buf = carry + chunk;
12
+ const parts = buf.split('\n\n');
13
+ const rest = parts.pop() ?? ''; // last element is an incomplete event (or '')
14
+ const frames = [];
15
+ for (const evt of parts) {
16
+ // An SSE event may have multiple lines; collect `data:` lines, ignore `:`/`event:`/`id:`.
17
+ const data = evt
18
+ .split('\n')
19
+ .filter((l) => l.startsWith('data:'))
20
+ .map((l) => l.slice(5).trimStart())
21
+ .join('\n');
22
+ if (!data)
23
+ continue;
24
+ try {
25
+ const parsed = JSON.parse(data);
26
+ if (parsed && typeof parsed.type === 'string' && parsed.type.startsWith('inner.')) {
27
+ frames.push(parsed);
28
+ }
29
+ }
30
+ catch {
31
+ // ignore malformed/non-frame data
32
+ }
33
+ }
34
+ return { frames, carry: rest };
35
+ }
36
+ /**
37
+ * Open the per-player inner SSE and pump frames to `onFrame` until `signal`
38
+ * aborts. Resolves when the stream ends/aborts; never throws (errors → onError).
39
+ */
40
+ async function openInnerTail(opts) {
41
+ const url = `${opts.baseUrl.replace(/\/$/, '')}/v1/players/` +
42
+ `${encodeURIComponent(opts.ensemble)}/${encodeURIComponent(opts.playerId)}/inner`;
43
+ let res;
44
+ try {
45
+ res = await opts.fetchFn(url, {
46
+ method: 'GET',
47
+ headers: { Authorization: `Bearer ${opts.adminToken}`, Accept: 'text/event-stream' },
48
+ signal: opts.signal,
49
+ });
50
+ }
51
+ catch (err) {
52
+ if (!opts.signal.aborted)
53
+ opts.onError?.(err instanceof Error ? err.message : String(err));
54
+ return;
55
+ }
56
+ if (res.status !== 200 || !res.body) {
57
+ opts.onError?.(`inner tail HTTP ${res.status}`);
58
+ return;
59
+ }
60
+ const decoder = new TextDecoder();
61
+ let carry = '';
62
+ try {
63
+ for await (const chunk of res.body) {
64
+ if (opts.signal.aborted)
65
+ break;
66
+ const { frames, carry: next } = parseInnerSse(decoder.decode(chunk, { stream: true }), carry);
67
+ carry = next;
68
+ for (const f of frames)
69
+ opts.onFrame(f);
70
+ }
71
+ }
72
+ catch (err) {
73
+ if (!opts.signal.aborted)
74
+ opts.onError?.(err instanceof Error ? err.message : String(err));
75
+ }
76
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Hand-written structural slice of Pi's extension UI + command API that
3
+ * mission-control consumes (3f). Kept LOCAL (not in the shared `src/pi/pi-types.ts`)
4
+ * so this feature is self-contained and `tsc` stays green WITHOUT the optional
5
+ * `@earendil-works/pi-coding-agent` dep installed. Mirrors the installed SDK's
6
+ * `dist/core/extensions/types.d.ts` (verified 0.78.0): `ctx.ui.setWidget` /
7
+ * `select` / `confirm` / `input` / `notify`, `registerCommand`, `registerShortcut`.
8
+ */
9
+ export interface ExtensionUIDialogOptions {
10
+ signal?: AbortSignal;
11
+ timeout?: number;
12
+ }
13
+ /** The `ctx.ui` surface (present only when `ctx.hasUI`). */
14
+ export interface ExtensionUIContext {
15
+ /** Persistent widget: re-call with new lines to update; `undefined` clears it. */
16
+ setWidget(key: string, content: string[] | undefined, options?: {
17
+ placement?: 'aboveEditor' | 'belowEditor';
18
+ }): void;
19
+ select(title: string, options: string[], opts?: ExtensionUIDialogOptions): Promise<string | undefined>;
20
+ confirm(title: string, message: string, opts?: ExtensionUIDialogOptions): Promise<boolean>;
21
+ input(title: string, placeholder?: string, opts?: ExtensionUIDialogOptions): Promise<string | undefined>;
22
+ notify(message: string): void;
23
+ }
24
+ /** Context passed to every handler. `ui` is only safe to use when `hasUI`. */
25
+ export interface McExtensionContext {
26
+ ui: ExtensionUIContext;
27
+ hasUI: boolean;
28
+ }
29
+ export type McEventHandler = (event: unknown, ctx: McExtensionContext) => void | Promise<void>;
30
+ export type McCommandHandler = (args: string, ctx: McExtensionContext) => void | Promise<void>;
31
+ export type McShortcutHandler = (ctx: McExtensionContext) => void | Promise<void>;
32
+ /** The `pi` object passed to the extension's default export — slice we use. */
33
+ export interface McExtensionAPI {
34
+ on(event: string, handler: McEventHandler): void;
35
+ registerCommand(name: string, options: {
36
+ description?: string;
37
+ handler: McCommandHandler;
38
+ }): void;
39
+ registerShortcut(shortcut: unknown, options: {
40
+ description?: string;
41
+ handler: McShortcutHandler;
42
+ }): void;
43
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * Hand-written structural slice of Pi's extension UI + command API that
4
+ * mission-control consumes (3f). Kept LOCAL (not in the shared `src/pi/pi-types.ts`)
5
+ * so this feature is self-contained and `tsc` stays green WITHOUT the optional
6
+ * `@earendil-works/pi-coding-agent` dep installed. Mirrors the installed SDK's
7
+ * `dist/core/extensions/types.d.ts` (verified 0.78.0): `ctx.ui.setWidget` /
8
+ * `select` / `confirm` / `input` / `notify`, `registerCommand`, `registerShortcut`.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ import { type BoardModel } from './board';
2
+ /**
3
+ * Render the full board. Header + player rows (conductor first), then — when a
4
+ * player is selected — a fine inner-loop tail (last {@link TAIL_RENDER_LINES}).
5
+ */
6
+ export declare function renderBoard(model: BoardModel): string[];
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderBoard = renderBoard;
4
+ const board_1 = require("./board");
5
+ /** How many recent fine-tail frames to show under the selected player. */
6
+ const TAIL_RENDER_LINES = 12;
7
+ /** Compact phase glyph — ASCII-safe for the TUI. */
8
+ function phaseGlyph(phase) {
9
+ switch (phase) {
10
+ case 'processing': return '*'; // working
11
+ case 'awaiting': return 'o'; // idle, attached
12
+ case 'attached': return '+';
13
+ case 'booting': return '.';
14
+ case 'draining': return '~';
15
+ case 'detached': return 'x';
16
+ case 'gone': return '#';
17
+ default: return '?';
18
+ }
19
+ }
20
+ function pct(contextPercent) {
21
+ if (contextPercent === undefined)
22
+ return '';
23
+ // contextPercent may be a 0..1 fraction or a 0..100 number — normalize to %.
24
+ const p = contextPercent <= 1 ? contextPercent * 100 : contextPercent;
25
+ return `${Math.round(p)}%`;
26
+ }
27
+ function renderRow(row, selected) {
28
+ const sel = selected ? '>' : ' ';
29
+ const glyph = phaseGlyph(row.phase);
30
+ const tool = row.currentTool ? `[${row.currentTool}]` : '';
31
+ const ctx = pct(row.contextPercent);
32
+ const part = row.part ? ` ${row.part}` : '';
33
+ // sel glyph id part tool ctx
34
+ return [`${sel}${glyph} ${row.playerId}`, part, tool, ctx]
35
+ .filter((s) => s !== '')
36
+ .join(' ')
37
+ .trimEnd();
38
+ }
39
+ /** One-line summary of a fine inner-loop frame for the tail. */
40
+ function renderInnerFrame(f) {
41
+ switch (f.type) {
42
+ case 'inner.thinking':
43
+ return ` ${f.kind === 'thinking' ? '~' : '"'} ${oneLine(f.delta, 80)}`;
44
+ case 'inner.tool_call':
45
+ return ` -> ${f.tool}(${oneLine(f.argsSummary, 60)})`;
46
+ case 'inner.tool_result':
47
+ return ` <- ${f.tool}${f.isError ? ' ERR' : ''}: ${oneLine(f.resultSummary, 60)}`;
48
+ case 'inner.token':
49
+ return ` · ctx ${f.contextTokens ?? '?'} tok${f.contextPercent !== undefined ? ` (${pct(f.contextPercent)})` : ''}`;
50
+ case 'inner.turn':
51
+ return ` -- turn ${f.phase} #${f.turnIndex}`;
52
+ case 'inner.gate_pending':
53
+ // requestId front-and-center — the operator types it into `/gate <requestId> allow|deny`.
54
+ return ` [GATE ${f.requestId} ${f.tool} (${f.classification}, ${Math.round(f.timeoutMs / 1000)}s)]`;
55
+ case 'inner.gate_resolved':
56
+ return ` [GATE ${f.requestId} -> ${f.decision}]`;
57
+ default:
58
+ return ' ·';
59
+ }
60
+ }
61
+ /** Collapse whitespace + truncate for a single tail line. */
62
+ function oneLine(s, max) {
63
+ const flat = s.replace(/\s+/g, ' ').trim();
64
+ return flat.length <= max ? flat : flat.slice(0, max - 1) + '…';
65
+ }
66
+ /**
67
+ * Render the full board. Header + player rows (conductor first), then — when a
68
+ * player is selected — a fine inner-loop tail (last {@link TAIL_RENDER_LINES}).
69
+ */
70
+ function renderBoard(model) {
71
+ const ids = (0, board_1.sortedPlayerIds)(model);
72
+ const lines = [];
73
+ lines.push(`MISSION CONTROL · ${model.ensemble} · ${ids.length} player${ids.length === 1 ? '' : 's'}`);
74
+ if (ids.length === 0) {
75
+ lines.push(' (no players — waiting for the ensemble…)');
76
+ }
77
+ else {
78
+ for (const id of ids) {
79
+ const row = model.players.get(id);
80
+ lines.push(renderRow(row, id === model.selected));
81
+ }
82
+ }
83
+ if (model.selected) {
84
+ lines.push(`── tail: ${model.selected} ──`);
85
+ const recent = model.innerTail.slice(-TAIL_RENDER_LINES);
86
+ if (recent.length === 0) {
87
+ lines.push(' (no inner-loop activity yet)');
88
+ }
89
+ else {
90
+ for (const f of recent)
91
+ lines.push(renderInnerFrame(f));
92
+ }
93
+ }
94
+ return lines;
95
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Pi-event → attachment-phase state machine (PURE — no Temporal, no Pi imports).
3
+ *
4
+ * This is the de-risking core of the Phase 0 PoC and the primary unit-test
5
+ * target. It translates Pi lifecycle events into (a) the next attachment phase
6
+ * and (b) the workflow action the extension must perform to publish that phase
7
+ * through the EXISTING session-workflow wire surface (claimAttachment /
8
+ * processingStart / processingEnd / requestDetach + adapterExited).
9
+ *
10
+ * Architect's EXACT mapping (do not "improve" without an architect sign-off):
11
+ *
12
+ * session_start → claim → phase `attached`
13
+ * agent_start → processingStart → phase `processing`
14
+ * agent_end → processingEnd → phase `awaiting` (NOT detached!)
15
+ * session_shutdown → detach → phase `draining` (→ `detached`
16
+ * confirmed workflow-side after
17
+ * adapterExited; see note below)
18
+ * turn_start / turn_end → NONE → phase UNCHANGED, stamp activity
19
+ * tool_execution_start / _end → NONE → phase UNCHANGED, stamp activity
20
+ *
21
+ * CRITICAL INVARIANT: `turn_*` and `tool_execution_*` fire multiple times mid
22
+ * agent run. If they drove the phase, the session would oscillate
23
+ * processing↔awaiting many times per turn. They are ACTIVITY signals only.
24
+ */
25
+ /** Attachment phases — mirrors `AttachmentPhase` in src/types.ts (kept local to stay import-free). */
26
+ export type PiPhase = 'booting' | 'attached' | 'processing' | 'awaiting' | 'draining' | 'detached' | 'gone';
27
+ /**
28
+ * The workflow-side action the extension should perform in response to an event.
29
+ * `none` means "no phase-affecting workflow call" (activity-only events).
30
+ */
31
+ export type WorkflowAction = {
32
+ kind: 'claim';
33
+ } | {
34
+ kind: 'processingStart';
35
+ messageId: string;
36
+ } | {
37
+ kind: 'processingEnd';
38
+ messageId: string;
39
+ } | {
40
+ kind: 'detach';
41
+ } | {
42
+ kind: 'none';
43
+ };
44
+ export interface PhaseDriverResult {
45
+ /** The workflow call the extension must make (or `none`). */
46
+ action: WorkflowAction;
47
+ /** The phase AFTER applying this event (the driver's local view). */
48
+ phase: PiPhase;
49
+ /** True when this event bumped the last-activity timestamp. */
50
+ activityStamped: boolean;
51
+ }
52
+ /**
53
+ * Stateful (but side-effect-free) translator. One instance per attached Pi
54
+ * session. Re-instantiate on each `session_start` — never reuse across session
55
+ * switches (D11).
56
+ */
57
+ export declare class PhaseDriver {
58
+ private _phase;
59
+ private _lastActivityAt;
60
+ /** The in-flight message id between agent_start and agent_end (idempotency). */
61
+ private _currentMessageId;
62
+ get phase(): PiPhase;
63
+ get lastActivityAt(): string | null;
64
+ /**
65
+ * Translate a Pi lifecycle event into a phase transition + workflow action.
66
+ *
67
+ * @param event Pi lifecycle event name.
68
+ * @param payload Optional `{ messageId }` carried by the event.
69
+ * @param now ISO timestamp injected by the caller (keeps this pure/testable).
70
+ */
71
+ handle(event: string, payload: {
72
+ messageId?: string;
73
+ } | undefined, now: string): PhaseDriverResult;
74
+ }
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ /**
3
+ * Pi-event → attachment-phase state machine (PURE — no Temporal, no Pi imports).
4
+ *
5
+ * This is the de-risking core of the Phase 0 PoC and the primary unit-test
6
+ * target. It translates Pi lifecycle events into (a) the next attachment phase
7
+ * and (b) the workflow action the extension must perform to publish that phase
8
+ * through the EXISTING session-workflow wire surface (claimAttachment /
9
+ * processingStart / processingEnd / requestDetach + adapterExited).
10
+ *
11
+ * Architect's EXACT mapping (do not "improve" without an architect sign-off):
12
+ *
13
+ * session_start → claim → phase `attached`
14
+ * agent_start → processingStart → phase `processing`
15
+ * agent_end → processingEnd → phase `awaiting` (NOT detached!)
16
+ * session_shutdown → detach → phase `draining` (→ `detached`
17
+ * confirmed workflow-side after
18
+ * adapterExited; see note below)
19
+ * turn_start / turn_end → NONE → phase UNCHANGED, stamp activity
20
+ * tool_execution_start / _end → NONE → phase UNCHANGED, stamp activity
21
+ *
22
+ * CRITICAL INVARIANT: `turn_*` and `tool_execution_*` fire multiple times mid
23
+ * agent run. If they drove the phase, the session would oscillate
24
+ * processing↔awaiting many times per turn. They are ACTIVITY signals only.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.PhaseDriver = void 0;
28
+ /** Events that stamp last-activity but MUST NOT drive the phase. */
29
+ const ACTIVITY_ONLY_EVENTS = new Set([
30
+ 'turn_start',
31
+ 'turn_end',
32
+ 'tool_execution_start',
33
+ 'tool_execution_end',
34
+ ]);
35
+ /**
36
+ * Stateful (but side-effect-free) translator. One instance per attached Pi
37
+ * session. Re-instantiate on each `session_start` — never reuse across session
38
+ * switches (D11).
39
+ */
40
+ class PhaseDriver {
41
+ _phase = 'booting';
42
+ _lastActivityAt = null;
43
+ /** The in-flight message id between agent_start and agent_end (idempotency). */
44
+ _currentMessageId = null;
45
+ get phase() {
46
+ return this._phase;
47
+ }
48
+ get lastActivityAt() {
49
+ return this._lastActivityAt;
50
+ }
51
+ /**
52
+ * Translate a Pi lifecycle event into a phase transition + workflow action.
53
+ *
54
+ * @param event Pi lifecycle event name.
55
+ * @param payload Optional `{ messageId }` carried by the event.
56
+ * @param now ISO timestamp injected by the caller (keeps this pure/testable).
57
+ */
58
+ handle(event, payload = {}, now) {
59
+ if (ACTIVITY_ONLY_EVENTS.has(event)) {
60
+ // Activity-only: stamp, but DO NOT touch the phase.
61
+ this._lastActivityAt = now;
62
+ return { action: { kind: 'none' }, phase: this._phase, activityStamped: true };
63
+ }
64
+ switch (event) {
65
+ case 'session_start': {
66
+ this._currentMessageId = null; // defensive: fresh attach has no in-flight turn.
67
+ this._phase = 'attached';
68
+ return { action: { kind: 'claim' }, phase: this._phase, activityStamped: false };
69
+ }
70
+ case 'agent_start': {
71
+ // processingStart requires a stable messageId for idempotency. Prefer the
72
+ // event-supplied id; fall back to a now-derived id when Pi omits one.
73
+ const messageId = payload.messageId ?? `pi-turn-${now}`;
74
+ this._currentMessageId = messageId;
75
+ this._phase = 'processing';
76
+ this._lastActivityAt = now;
77
+ return {
78
+ action: { kind: 'processingStart', messageId },
79
+ phase: this._phase,
80
+ activityStamped: true,
81
+ };
82
+ }
83
+ case 'agent_end': {
84
+ // End the IN-FLIGHT turn. agent_end means the agent finished THIS run and
85
+ // is now waiting for input → `awaiting`, NOT `detached`.
86
+ //
87
+ // Hardening (P2-4): end with the id we STARTED the turn with — NOT the
88
+ // payload — so processingStart/End pair EXACTLY (the workflow keys its
89
+ // in-flight set on that id; a mismatched end would orphan the entry). And
90
+ // a spurious / duplicate agent_end with no turn in flight is INERT: it
91
+ // must not synthesize a bogus processingEnd nor flip the phase (which
92
+ // could mask a genuine `processing` state or spuriously leave `awaiting`).
93
+ const inFlightId = this._currentMessageId;
94
+ if (inFlightId === null) {
95
+ return { action: { kind: 'none' }, phase: this._phase, activityStamped: false };
96
+ }
97
+ this._currentMessageId = null;
98
+ this._phase = 'awaiting';
99
+ this._lastActivityAt = now;
100
+ return {
101
+ action: { kind: 'processingEnd', messageId: inFlightId },
102
+ phase: this._phase,
103
+ activityStamped: true,
104
+ };
105
+ }
106
+ case 'session_shutdown': {
107
+ // Graceful teardown: phase → draining; the extension performs
108
+ // requestDetach (→ draining) then adapterExited (→ detached). The
109
+ // `detached` collapse is authoritative WORKFLOW-side; the driver's
110
+ // local view rests at `draining` because the process is exiting.
111
+ // Clear any in-flight turn so a late stray event after shutdown is inert.
112
+ this._currentMessageId = null;
113
+ this._phase = 'draining';
114
+ return { action: { kind: 'detach' }, phase: this._phase, activityStamped: false };
115
+ }
116
+ default:
117
+ // Unknown / unhandled events are inert: no phase change, no activity stamp.
118
+ return { action: { kind: 'none' }, phase: this._phase, activityStamped: false };
119
+ }
120
+ }
121
+ }
122
+ exports.PhaseDriver = PhaseDriver;