agent-tempo 1.3.1 → 1.4.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 (199) hide show
  1. package/CLAUDE.md +39 -5
  2. package/README.md +6 -2
  3. package/dashboard/dist/assets/{index-D6Xyje_n.js → index-jmYe6rmS.js} +2 -2
  4. package/dashboard/dist/assets/index-jmYe6rmS.js.map +1 -0
  5. package/dashboard/dist/index.html +1 -1
  6. package/dashboard/package.json +1 -1
  7. package/dist/activities/outbox.d.ts +30 -1
  8. package/dist/activities/outbox.js +96 -3
  9. package/dist/adapters/base.js +5 -0
  10. package/dist/adapters/index.d.ts +1 -1
  11. package/dist/adapters/index.js +7 -0
  12. package/dist/adapters/pi/adapter.d.ts +2 -0
  13. package/dist/adapters/pi/adapter.js +43 -0
  14. package/dist/adapters/pi/index.d.ts +16 -0
  15. package/dist/adapters/pi/index.js +10 -0
  16. package/dist/client/core.js +9 -2
  17. package/dist/client/interface.d.ts +6 -0
  18. package/dist/config.d.ts +79 -0
  19. package/dist/config.js +74 -0
  20. package/dist/daemon.js +32 -1
  21. package/dist/http/aggregate.d.ts +22 -1
  22. package/dist/http/aggregate.js +41 -0
  23. package/dist/http/auth.d.ts +94 -8
  24. package/dist/http/auth.js +93 -9
  25. package/dist/http/body.d.ts +4 -1
  26. package/dist/http/body.js +6 -3
  27. package/dist/http/event-bus.js +1 -0
  28. package/dist/http/event-types.d.ts +34 -2
  29. package/dist/http/event-types.js +1 -0
  30. package/dist/http/gate-audit.d.ts +12 -0
  31. package/dist/http/gate-audit.js +95 -0
  32. package/dist/http/gate-registry.d.ts +167 -0
  33. package/dist/http/gate-registry.js +163 -0
  34. package/dist/http/gate-routes.d.ts +48 -0
  35. package/dist/http/gate-routes.js +102 -0
  36. package/dist/http/ingest-registry.d.ts +30 -0
  37. package/dist/http/ingest-registry.js +108 -0
  38. package/dist/http/inner-loop-routes.d.ts +66 -0
  39. package/dist/http/inner-loop-routes.js +182 -0
  40. package/dist/http/inner-loop.d.ts +92 -0
  41. package/dist/http/inner-loop.js +155 -0
  42. package/dist/http/server.d.ts +38 -3
  43. package/dist/http/server.js +211 -6
  44. package/dist/http/snapshot.d.ts +6 -0
  45. package/dist/http/snapshot.js +6 -0
  46. package/dist/pi/cue-pump.d.ts +61 -0
  47. package/dist/pi/cue-pump.js +95 -0
  48. package/dist/pi/extension.d.ts +45 -0
  49. package/dist/pi/extension.js +407 -0
  50. package/dist/pi/gate-client.d.ts +54 -0
  51. package/dist/pi/gate-client.js +136 -0
  52. package/dist/pi/headless.d.ts +85 -0
  53. package/dist/pi/headless.js +250 -0
  54. package/dist/pi/index.d.ts +28 -0
  55. package/dist/pi/index.js +43 -0
  56. package/dist/pi/inner-loop-client.d.ts +67 -0
  57. package/dist/pi/inner-loop-client.js +164 -0
  58. package/dist/pi/inner-loop-publisher.d.ts +187 -0
  59. package/dist/pi/inner-loop-publisher.js +236 -0
  60. package/dist/pi/lazy-proxy.d.ts +37 -0
  61. package/dist/pi/lazy-proxy.js +55 -0
  62. package/dist/pi/mission-control/actions.d.ts +48 -0
  63. package/dist/pi/mission-control/actions.js +98 -0
  64. package/dist/pi/mission-control/board.d.ts +88 -0
  65. package/dist/pi/mission-control/board.js +141 -0
  66. package/dist/pi/mission-control/extension.d.ts +51 -0
  67. package/dist/pi/mission-control/extension.js +330 -0
  68. package/dist/pi/mission-control/index.d.ts +15 -0
  69. package/dist/pi/mission-control/index.js +32 -0
  70. package/dist/pi/mission-control/inner-tail.d.ts +48 -0
  71. package/dist/pi/mission-control/inner-tail.js +76 -0
  72. package/dist/pi/mission-control/pi-ui.d.ts +43 -0
  73. package/dist/pi/mission-control/pi-ui.js +10 -0
  74. package/dist/pi/mission-control/render.d.ts +6 -0
  75. package/dist/pi/mission-control/render.js +98 -0
  76. package/dist/pi/phase-driver.d.ts +74 -0
  77. package/dist/pi/phase-driver.js +122 -0
  78. package/dist/pi/pi-types.d.ts +222 -0
  79. package/dist/pi/pi-types.js +21 -0
  80. package/dist/pi/probe.d.ts +99 -0
  81. package/dist/pi/probe.js +179 -0
  82. package/dist/pi/render-tools.d.ts +17 -0
  83. package/dist/pi/render-tools.js +56 -0
  84. package/dist/pi/reset-pump.d.ts +47 -0
  85. package/dist/pi/reset-pump.js +85 -0
  86. package/dist/pi/session-seed.d.ts +74 -0
  87. package/dist/pi/session-seed.js +103 -0
  88. package/dist/pi/tool-capability.d.ts +60 -0
  89. package/dist/pi/tool-capability.js +156 -0
  90. package/dist/pi/workflow-client.d.ts +158 -0
  91. package/dist/pi/workflow-client.js +289 -0
  92. package/dist/pi/zod-to-typebox.d.ts +74 -0
  93. package/dist/pi/zod-to-typebox.js +191 -0
  94. package/dist/server-tools.d.ts +2 -0
  95. package/dist/server-tools.js +50 -46
  96. package/dist/spawn.d.ts +55 -0
  97. package/dist/spawn.js +72 -0
  98. package/dist/tools/agent-types.d.ts +2 -2
  99. package/dist/tools/agent-types.js +22 -17
  100. package/dist/tools/attachment-info.d.ts +2 -2
  101. package/dist/tools/attachment-info.js +38 -33
  102. package/dist/tools/broadcast.d.ts +2 -2
  103. package/dist/tools/broadcast.js +69 -64
  104. package/dist/tools/cancel-stage.d.ts +2 -2
  105. package/dist/tools/cancel-stage.js +20 -15
  106. package/dist/tools/clear-state.d.ts +2 -2
  107. package/dist/tools/clear-state.js +25 -20
  108. package/dist/tools/coat-check-evict.d.ts +2 -2
  109. package/dist/tools/coat-check-evict.js +29 -24
  110. package/dist/tools/coat-check-get.d.ts +2 -2
  111. package/dist/tools/coat-check-get.js +38 -33
  112. package/dist/tools/coat-check-list.d.ts +2 -2
  113. package/dist/tools/coat-check-list.js +48 -43
  114. package/dist/tools/coat-check-put.d.ts +2 -2
  115. package/dist/tools/coat-check-put.js +38 -33
  116. package/dist/tools/cue.d.ts +2 -2
  117. package/dist/tools/cue.js +57 -52
  118. package/dist/tools/descriptor.d.ts +72 -0
  119. package/dist/tools/descriptor.js +39 -0
  120. package/dist/tools/destroy.d.ts +2 -2
  121. package/dist/tools/destroy.js +153 -148
  122. package/dist/tools/ensemble.d.ts +2 -2
  123. package/dist/tools/ensemble.js +71 -66
  124. package/dist/tools/evaluate-gate.d.ts +2 -2
  125. package/dist/tools/evaluate-gate.js +33 -27
  126. package/dist/tools/fetch-state.d.ts +2 -2
  127. package/dist/tools/fetch-state.js +42 -37
  128. package/dist/tools/gates.d.ts +2 -2
  129. package/dist/tools/gates.js +39 -34
  130. package/dist/tools/hosts.d.ts +2 -2
  131. package/dist/tools/hosts.js +25 -20
  132. package/dist/tools/listen.d.ts +2 -2
  133. package/dist/tools/listen.js +23 -18
  134. package/dist/tools/load-lineup.d.ts +2 -2
  135. package/dist/tools/load-lineup.js +324 -319
  136. package/dist/tools/migrate.d.ts +2 -2
  137. package/dist/tools/migrate.js +45 -40
  138. package/dist/tools/pause.d.ts +2 -2
  139. package/dist/tools/pause.js +34 -29
  140. package/dist/tools/play.d.ts +2 -2
  141. package/dist/tools/play.js +53 -48
  142. package/dist/tools/quality-gate.d.ts +2 -2
  143. package/dist/tools/quality-gate.js +26 -21
  144. package/dist/tools/recall.d.ts +2 -2
  145. package/dist/tools/recall.js +32 -27
  146. package/dist/tools/recruit.d.ts +2 -2
  147. package/dist/tools/recruit.js +340 -256
  148. package/dist/tools/release.d.ts +2 -2
  149. package/dist/tools/release.js +85 -80
  150. package/dist/tools/report.d.ts +2 -2
  151. package/dist/tools/report.js +28 -23
  152. package/dist/tools/reset.d.ts +3 -0
  153. package/dist/tools/reset.js +51 -0
  154. package/dist/tools/restart.d.ts +2 -2
  155. package/dist/tools/restart.js +51 -46
  156. package/dist/tools/restore.d.ts +2 -2
  157. package/dist/tools/restore.js +76 -71
  158. package/dist/tools/save-lineup.d.ts +2 -2
  159. package/dist/tools/save-lineup.js +32 -27
  160. package/dist/tools/save-state.d.ts +2 -2
  161. package/dist/tools/save-state.js +31 -26
  162. package/dist/tools/schedule.d.ts +2 -2
  163. package/dist/tools/schedule.js +133 -128
  164. package/dist/tools/schedules.d.ts +2 -2
  165. package/dist/tools/schedules.js +41 -36
  166. package/dist/tools/set-ensemble-description.d.ts +2 -2
  167. package/dist/tools/set-ensemble-description.js +26 -21
  168. package/dist/tools/set-name.d.ts +2 -2
  169. package/dist/tools/set-name.js +38 -33
  170. package/dist/tools/set-part.d.ts +2 -2
  171. package/dist/tools/set-part.js +20 -15
  172. package/dist/tools/shutdown.d.ts +2 -2
  173. package/dist/tools/shutdown.js +39 -34
  174. package/dist/tools/stage.d.ts +2 -2
  175. package/dist/tools/stage.js +28 -23
  176. package/dist/tools/stages.d.ts +2 -2
  177. package/dist/tools/stages.js +36 -31
  178. package/dist/tools/unschedule.d.ts +2 -2
  179. package/dist/tools/unschedule.js +30 -25
  180. package/dist/tools/who-am-i.d.ts +2 -2
  181. package/dist/tools/who-am-i.js +36 -31
  182. package/dist/tools/worktree.d.ts +2 -2
  183. package/dist/tools/worktree.js +134 -129
  184. package/dist/tui/index.js +6 -6
  185. package/dist/types.d.ts +47 -2
  186. package/dist/types.js +1 -1
  187. package/dist/utils/default-part.js +1 -0
  188. package/dist/utils/sdk-probe.d.ts +23 -0
  189. package/dist/utils/sdk-probe.js +46 -7
  190. package/dist/worker.d.ts +3 -1
  191. package/dist/worker.js +6 -2
  192. package/dist/workflows/session.js +70 -2
  193. package/dist/workflows/signals.d.ts +32 -2
  194. package/dist/workflows/signals.js +25 -2
  195. package/package.json +4 -1
  196. package/workflow-bundle.js +97 -6
  197. package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
  198. package/dist/tools/helpers.d.ts +0 -21
  199. package/dist/tools/helpers.js +0 -25
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Local, hand-written structural declarations of the slice of Pi's
3
+ * `ExtensionAPI` surface that the agent-tempo Phase 0 PoC consumes.
4
+ *
5
+ * WHY HAND-WRITTEN (Phase 0 shortcut — see src/pi/README.md "Known limitations"):
6
+ * The real types live in `@earendil-works/pi-coding-agent`, which is an
7
+ * OPTIONAL dependency requiring Node 22.19+. Declaring these locally keeps
8
+ * `tsc` (root build + test build) green WITHOUT Pi installed, so the unit
9
+ * suite runs on any CI node. The cost is drift risk: if Pi's real
10
+ * `ExtensionAPI` changes, these decls won't catch it at compile time.
11
+ *
12
+ * Phase 1 should switch to importing the real Pi types at type-check time
13
+ * (Pi as a true optional/peer dep) so we get compile-time API-drift
14
+ * detection. These decls are a temporary stand-in, NOT the end state.
15
+ *
16
+ * Surface mirrored against `badlogic/pi-mono` @ 564ad70 (packages 0.78.0):
17
+ * - `core/extensions/types.ts` (ExtensionAPI, ToolDefinition, events)
18
+ * - `core/agent-session.ts` (sendCustomMessage / steer / followUp)
19
+ */
20
+ /**
21
+ * Pi lifecycle events. Phase 0 only wires the lifecycle-relevant subset; the
22
+ * real `ExtensionAPI.on` accepts many more event names.
23
+ *
24
+ * IMPORTANT (architect's phase spec): `turn_start` / `turn_end` /
25
+ * `tool_execution_start` / `tool_execution_end` fire MULTIPLE times within a
26
+ * single agent run and therefore MUST NOT drive the attachment phase — they
27
+ * only stamp a last-activity timestamp. See `phase-driver.ts`.
28
+ */
29
+ export type PiLifecycleEvent = 'session_start' | 'agent_start' | 'agent_end' | 'turn_start' | 'turn_end' | 'tool_call' | 'tool_execution_start' | 'tool_execution_end' | 'message_update' | 'session_shutdown';
30
+ /** Options for `sendCustomMessage` — D10 (FLIPPED): default `steer` + `triggerTurn`. */
31
+ export interface PiCustomMessageOptions {
32
+ /** Start a new agent turn after delivery. */
33
+ triggerTurn?: boolean;
34
+ /**
35
+ * `steer` — interrupt an in-flight turn and inject immediately (priority).
36
+ * `followUp` — queue behind the current turn.
37
+ */
38
+ deliverAs?: 'steer' | 'followUp';
39
+ }
40
+ /** A message injected into a live Pi session. */
41
+ export interface PiOutboundMessage {
42
+ /** Free-form tag Pi surfaces to the agent (we use `'cue'`). */
43
+ customType?: string;
44
+ content: string;
45
+ /** Render the injected content in the human-visible transcript. */
46
+ display?: boolean;
47
+ }
48
+ /**
49
+ * The live, human-attached agent session. `sendCustomMessage` is bound in the
50
+ * `AgentSession` constructor (no mode gate) — confirmed by the spike — so a
51
+ * cue can be injected into a running interactive session.
52
+ */
53
+ export interface PiAgentSession {
54
+ /**
55
+ * Inject a message into the live session. Pi's `AgentSession` exposes this as
56
+ * `sendCustomMessage` (NOT `sendMessage` — verified against the installed SDK's
57
+ * `agent-session.d.ts` during the 3a live smoke). The earlier spike modeled it
58
+ * as `sendMessage`; the real method name is `sendCustomMessage`.
59
+ */
60
+ sendCustomMessage(msg: PiOutboundMessage, opts?: PiCustomMessageOptions): void | Promise<void>;
61
+ /**
62
+ * 3d D14 — wipe the conversation and start a FRESH context (no replay). The
63
+ * reset handler calls this on a clean-wipe reset. Optional in the slice (Pi
64
+ * provides it; older Pi / a fake session in tests may not).
65
+ */
66
+ newSession?(): void | Promise<void>;
67
+ /** Pi's stable session identifier (reconciled with workflow metadata — D11). */
68
+ readonly id?: string;
69
+ }
70
+ /**
71
+ * Payload delivered to a `pi.on(event, handler)` callback. Kept structural and
72
+ * open: Pi passes an event-specific object; we read only what Phase 0 needs and
73
+ * RE-ACQUIRE the session from each payload (never cache across switches — D11).
74
+ */
75
+ export interface PiEventPayload {
76
+ /** The live session, present on most lifecycle events. */
77
+ session?: PiAgentSession;
78
+ /** A per-message/per-turn identifier when the event carries one. */
79
+ messageId?: string;
80
+ /**
81
+ * Discriminator on `session_start` / `session_shutdown`:
82
+ * `{ new | resume | fork | reload | quit }`. Drives Option-C teardown — only
83
+ * `quit` detaches; switch reasons rebind. Unknown/missing → treated as a
84
+ * switch (no detach), so a future Pi value can't cause a flap.
85
+ */
86
+ reason?: string;
87
+ /** Open for forward-compat with Pi event fields we don't consume yet. */
88
+ [key: string]: unknown;
89
+ }
90
+ /**
91
+ * Pi context-window usage — the PULL-only token signal (3c). There is NO token
92
+ * event in Pi 0.78; usage is queried on demand via `ExtensionContext.getContextUsage()`.
93
+ * Mirrors pi-ai's `ContextUsage` (verified against the installed 0.78 `.d.ts`).
94
+ */
95
+ export interface PiContextUsage {
96
+ /** Estimated context tokens in use, or `null` when unknown. */
97
+ tokens: number | null;
98
+ /** Model context-window size. */
99
+ contextWindow: number;
100
+ /** Usage as a fraction/percent of the window, or `null` when unknown. */
101
+ percent: number | null;
102
+ }
103
+ /**
104
+ * Second argument Pi passes to every `pi.on(event, handler)` callback
105
+ * (`ExtensionHandler<E,R> = (event, ctx) => …`). Minimal structural slice — only
106
+ * the members 3c consumes. `getContextUsage()` is the sole source of token/usage
107
+ * data (pull-only — see {@link PiContextUsage}).
108
+ */
109
+ export interface PiExtensionContext {
110
+ getContextUsage(): PiContextUsage | undefined;
111
+ isIdle(): boolean;
112
+ /**
113
+ * 3d — the in-flight turn's AbortSignal (Esc / cancel). Pi passes it to handlers
114
+ * (the shipped `permission-gate.ts` precedent threads it into an awaited gate);
115
+ * the operator-gate await uses it to cancel a pending poll so a cancelled turn
116
+ * never hangs on an unresolved decision (Pi #2381). Optional — absent on older
117
+ * Pi / non-turn handlers.
118
+ */
119
+ signal?: AbortSignal;
120
+ }
121
+ /**
122
+ * Handlers may take an optional second `ctx` arg (3c — needed for
123
+ * `getContextUsage()`). Optional so existing one-arg handlers (Phase 0-2) still
124
+ * satisfy the type — widening is additive/backward-compatible.
125
+ */
126
+ export type PiEventHandler = (payload: PiEventPayload, ctx?: PiExtensionContext) => void | Promise<void>;
127
+ /**
128
+ * One streaming delta inside a `message_update` event's `assistantMessageEvent`
129
+ * (a discriminated union in pi-ai). 3c forwards `thinking_delta` / `text_delta`
130
+ * as `inner.thinking` frames; `.delta` is the incremental string.
131
+ */
132
+ export interface PiAssistantDelta {
133
+ /** `'text_delta'` | `'thinking_delta'` | `'toolcall_delta'` | … */
134
+ type?: string;
135
+ /** Orders multi-block content within a turn. */
136
+ contentIndex?: number;
137
+ /** The incremental text fragment. */
138
+ delta?: string;
139
+ /** Cumulative `AssistantMessage` so far (unused by 3c). */
140
+ partial?: unknown;
141
+ }
142
+ /** `message_update` payload — carries the streaming `assistantMessageEvent`. */
143
+ export interface PiMessageUpdatePayload extends PiEventPayload {
144
+ assistantMessageEvent?: PiAssistantDelta;
145
+ }
146
+ /** `tool_execution_start` payload. `toolName` is a bare string (NOT a literal union). */
147
+ export interface PiToolExecutionStartPayload extends PiEventPayload {
148
+ toolCallId?: string;
149
+ toolName?: string;
150
+ args?: unknown;
151
+ }
152
+ /** `tool_execution_end` payload — structured result + error flag. */
153
+ export interface PiToolExecutionEndPayload extends PiEventPayload {
154
+ toolCallId?: string;
155
+ toolName?: string;
156
+ result?: unknown;
157
+ isError?: boolean;
158
+ }
159
+ /** `turn_start` / `turn_end` payload. No phase field, no token counts (3c finding). */
160
+ export interface PiTurnPayload extends PiEventPayload {
161
+ turnIndex?: number;
162
+ timestamp?: number;
163
+ }
164
+ /**
165
+ * `tool_call` pre-execution event — Pi fires this before running a tool, letting
166
+ * an extension allow/deny it. `toolName` is Pi's built-in or registered tool id
167
+ * (`bash` | `read` | `edit` | `write` | `grep` | …). The MD-C headless gate reads
168
+ * it to hard-block the shell/exec class at `toolAccess='restricted'`.
169
+ */
170
+ export interface PiToolCallEvent {
171
+ type?: 'tool_call';
172
+ toolCallId?: string;
173
+ toolName: string;
174
+ input?: Record<string, unknown>;
175
+ }
176
+ /** Result of a `tool_call` handler: `block:true` denies the tool (with a reason). */
177
+ export interface PiToolCallResult {
178
+ block?: boolean;
179
+ reason?: string;
180
+ }
181
+ /**
182
+ * Pi tool result (`AgentToolResult`). The exact streaming shape is UNCONFIRMED
183
+ * (spike gap D12b) — Phase 0 uses the minimal `{ output, isError }` form, which
184
+ * is sufficient for a non-streaming tool like `report`.
185
+ */
186
+ export interface PiToolResult {
187
+ output?: string;
188
+ isError?: boolean;
189
+ [key: string]: unknown;
190
+ }
191
+ /**
192
+ * Pi native tool definition. `parameters` is a TypeBox schema (NOT zod) — see
193
+ * `render-tools.ts` (derives it from the zod descriptor via the converter).
194
+ */
195
+ export interface PiToolDefinition {
196
+ name: string;
197
+ description?: string;
198
+ /** TypeBox schema object. Typed `unknown` to avoid coupling pi-types to typebox. */
199
+ parameters: unknown;
200
+ /**
201
+ * Pi invokes `execute` POSITIONALLY at runtime:
202
+ * execute(toolCallId, params, signal?, onUpdate?, ctx?)
203
+ * (installed Pi 0.78 `core/extensions/types.d.ts:354` + the wrapper at
204
+ * `core/tools/tool-definition-wrapper.js:10,31`). The VALIDATED params object
205
+ * is the SECOND positional — the FIRST is the `toolCallId` string.
206
+ *
207
+ * Declaring the full positional shape (not a single `args`) is LOAD-BEARING: a
208
+ * one-arg signature silently accepts `(args) => handler(args)`, which binds the
209
+ * toolCallId string to what the handler treats as the params object — the
210
+ * shipped arg-order bug (v1.4.0) this corrected type now makes a compile error.
211
+ * `signal`/`onUpdate`/`ctx` are optional + `unknown`: we don't consume them and
212
+ * avoid coupling to Pi's internal callback/context types.
213
+ */
214
+ execute: (toolCallId: string, params: Record<string, unknown>, signal?: unknown, onUpdate?: unknown, ctx?: unknown) => Promise<PiToolResult> | PiToolResult;
215
+ }
216
+ /** The `pi` object passed to `export default function(pi: ExtensionAPI) {}`. */
217
+ export interface ExtensionAPI {
218
+ on(event: PiLifecycleEvent | string, handler: PiEventHandler): void;
219
+ registerTool(def: PiToolDefinition): void;
220
+ }
221
+ /** An extension is a default-exported function receiving the `ExtensionAPI`. */
222
+ export type PiExtension = (pi: ExtensionAPI) => void | Promise<void>;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ /**
3
+ * Local, hand-written structural declarations of the slice of Pi's
4
+ * `ExtensionAPI` surface that the agent-tempo Phase 0 PoC consumes.
5
+ *
6
+ * WHY HAND-WRITTEN (Phase 0 shortcut — see src/pi/README.md "Known limitations"):
7
+ * The real types live in `@earendil-works/pi-coding-agent`, which is an
8
+ * OPTIONAL dependency requiring Node 22.19+. Declaring these locally keeps
9
+ * `tsc` (root build + test build) green WITHOUT Pi installed, so the unit
10
+ * suite runs on any CI node. The cost is drift risk: if Pi's real
11
+ * `ExtensionAPI` changes, these decls won't catch it at compile time.
12
+ *
13
+ * Phase 1 should switch to importing the real Pi types at type-check time
14
+ * (Pi as a true optional/peer dep) so we get compile-time API-drift
15
+ * detection. These decls are a temporary stand-in, NOT the end state.
16
+ *
17
+ * Surface mirrored against `badlogic/pi-mono` @ 564ad70 (packages 0.78.0):
18
+ * - `core/extensions/types.ts` (ExtensionAPI, ToolDefinition, events)
19
+ * - `core/agent-session.ts` (sendCustomMessage / steer / followUp)
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,99 @@
1
+ /** Canonical Pi package (the npm `@mariozechner/...` name is an alias). */
2
+ export declare const PI_PACKAGE = "@earendil-works/pi-coding-agent";
3
+ /** Pi's model/typing helpers (`StringEnum`, providers). */
4
+ export declare const PI_AI_PACKAGE = "@earendil-works/pi-ai";
5
+ /** Tested-pinned Pi version — drift is a maintainer decision (D6). */
6
+ export declare const TESTED_PI_VERSION = "~0.78";
7
+ /**
8
+ * Minimum Pi version the integration requires. 0.78.0 covers the Pi fixes the
9
+ * cue-pump + headless paths rely on (#2860 / #5080 / #5115). Bumping this is a
10
+ * D6 maintainer decision; the headless/Copilot pre-flight hard-fails below it.
11
+ */
12
+ export declare const PI_VERSION_FLOOR = "0.78.0";
13
+ /** Node floor imposed by the Pi packages (NOT by typebox or agent-tempo core). */
14
+ export declare const PI_NODE_FLOOR = "22.19.0";
15
+ export interface PiProbeResult {
16
+ available: boolean;
17
+ /** Human-readable, actionable reason when unavailable. */
18
+ reason?: string;
19
+ }
20
+ /**
21
+ * Check whether the Pi runtime packages are installed. Returns a structured
22
+ * result so callers choose whether to warn or hard-fail (the extension warns;
23
+ * a headless spawner would hard-fail).
24
+ */
25
+ export declare function probePi(): PiProbeResult;
26
+ /**
27
+ * Pure semver FLOOR check: is `installed` >= `floor`? Compares major, then
28
+ * minor, then patch (a missing patch defaults to 0). Any pre-release/build
29
+ * suffix (`-beta`, `+sha`) is ignored — a pre-release of a version at/above the
30
+ * floor counts as meeting it. An unparseable `installed` returns `false`
31
+ * (conservative: unknown version is treated as below the floor).
32
+ */
33
+ export declare function meetsVersionFloor(installed: string, floor?: string): boolean;
34
+ /**
35
+ * Node-floor preflight (Decision B, #645). The Pi optional deps require
36
+ * Node >= {@link PI_NODE_FLOOR} (22.19). Enforced ONLY at the Pi recruit/spawn
37
+ * boundary — `package.json#engines` stays `>=20` so non-Pi users are untouched.
38
+ *
39
+ * Pure + injectable (`nodeVersion` defaults to the live runtime) so it is the
40
+ * single source of truth shared by the recruit pre-flight (authoritative,
41
+ * pre-spawn) AND the {@link runHeadlessPi} backstop (direct/manual launches).
42
+ * Reuses {@link meetsVersionFloor}'s major.minor.patch comparison.
43
+ *
44
+ * @returns `{ ok: true }` when the floor is met, else `{ ok: false, reason }`
45
+ * with an actionable, fail-clean message (mirrors the `probeSdkInstall` /
46
+ * `resolveModel` style). The `reason` omits any `force: true` hint so each
47
+ * caller can frame the bypass in its own voice.
48
+ */
49
+ export declare function checkPiNodeFloor(nodeVersion?: string): {
50
+ ok: boolean;
51
+ reason?: string;
52
+ };
53
+ /**
54
+ * Injectable collaborators for {@link probeCopilotPiPreflight}. All default to
55
+ * real implementations; tests override them to exercise each branch without a
56
+ * live Pi install or real `~/.pi` / env.
57
+ */
58
+ export interface CopilotPiPreflightDeps {
59
+ /** Whether a package is installed. Default: filesystem-walk {@link probeSdkInstall}. */
60
+ isInstalled?: (pkg: string) => boolean;
61
+ /** Installed version of a package, or null. Default: {@link readSdkPackageVersion}. */
62
+ installedVersion?: (pkg: string) => string | null;
63
+ /** Environment to read `COPILOT_GITHUB_TOKEN` from. Default: `process.env`. */
64
+ env?: NodeJS.ProcessEnv;
65
+ /** Whether the mounted `~/.pi/agent/auth.json` exists. Default: real `existsSync`. */
66
+ authFileExists?: () => boolean;
67
+ /**
68
+ * The parsed recruit selector to validate against Pi's model index (the
69
+ * `{ provider, model }` from {@link parsePiProviderModel}). Omit it — e.g. no
70
+ * `model` recruit arg, the Pi-default path — to SKIP the model-index gate.
71
+ */
72
+ requestedModel?: {
73
+ provider: string;
74
+ model: string;
75
+ };
76
+ /**
77
+ * Resolve a `(provider, modelName)` against Pi's build-time model index,
78
+ * returning a truthy Model when indexed and `undefined` when not (the
79
+ * empirically-confirmed contract of pi-ai's `getModel`). The recruit caller
80
+ * (A4) injects pi-ai's `getModel` here — B2 deliberately does NOT import the
81
+ * ESM-only `@earendil-works/pi-ai` from the CJS recruit context. Omit it to
82
+ * SKIP the model-index gate (e.g. unit tests, or callers without pi-ai); the
83
+ * A4 `getModel` backstop then covers an unindexed model.
84
+ */
85
+ resolveModel?: (provider: string, modelName: string) => unknown;
86
+ }
87
+ /**
88
+ * Hard pre-flight for recruiting a Copilot-backed Pi player (headless). Three
89
+ * gates, each with an actionable, `force: true`-bypassable error:
90
+ * 1. The Pi optional deps (`@earendil-works/pi-coding-agent` + `pi-ai`) are
91
+ * installed.
92
+ * 2. The Pi SDK version meets {@link PI_VERSION_FLOOR}.
93
+ * 3. Copilot auth is present — either `COPILOT_GITHUB_TOKEN` in the env or a
94
+ * mounted `~/.pi/agent/auth.json`.
95
+ *
96
+ * Mirrors the opencode / claude-code-headless recruit pre-flights. Returns a
97
+ * {@link PiProbeResult} so the caller (recruit) chooses warn vs hard-fail.
98
+ */
99
+ export declare function probeCopilotPiPreflight(deps?: CopilotPiPreflightDeps): PiProbeResult;
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PI_NODE_FLOOR = exports.PI_VERSION_FLOOR = exports.TESTED_PI_VERSION = exports.PI_AI_PACKAGE = exports.PI_PACKAGE = void 0;
4
+ exports.probePi = probePi;
5
+ exports.meetsVersionFloor = meetsVersionFloor;
6
+ exports.checkPiNodeFloor = checkPiNodeFloor;
7
+ exports.probeCopilotPiPreflight = probeCopilotPiPreflight;
8
+ /**
9
+ * Pi dependency preflight — mirrors the opencode / claude-api optional-dep gate.
10
+ *
11
+ * `@earendil-works/pi-coding-agent` (and `@earendil-works/pi-ai`) are OPTIONAL
12
+ * dependencies requiring Node 22.19+. The extension only runs INSIDE Pi, so the
13
+ * runtime guarantees Pi is present — this probe exists for the headless path and
14
+ * for a clear, actionable error if someone wires the extension where Pi isn't
15
+ * installed.
16
+ *
17
+ * Uses `probeSdkInstall` (filesystem walk) rather than `require.resolve` because
18
+ * Pi packages ship ESM-only `exports` maps with no CJS-resolvable entry.
19
+ */
20
+ const fs_1 = require("fs");
21
+ const os_1 = require("os");
22
+ const path_1 = require("path");
23
+ const sdk_probe_1 = require("../utils/sdk-probe");
24
+ /** Canonical Pi package (the npm `@mariozechner/...` name is an alias). */
25
+ exports.PI_PACKAGE = '@earendil-works/pi-coding-agent';
26
+ /** Pi's model/typing helpers (`StringEnum`, providers). */
27
+ exports.PI_AI_PACKAGE = '@earendil-works/pi-ai';
28
+ /** Tested-pinned Pi version — drift is a maintainer decision (D6). */
29
+ exports.TESTED_PI_VERSION = '~0.78';
30
+ /**
31
+ * Minimum Pi version the integration requires. 0.78.0 covers the Pi fixes the
32
+ * cue-pump + headless paths rely on (#2860 / #5080 / #5115). Bumping this is a
33
+ * D6 maintainer decision; the headless/Copilot pre-flight hard-fails below it.
34
+ */
35
+ exports.PI_VERSION_FLOOR = '0.78.0';
36
+ /** Node floor imposed by the Pi packages (NOT by typebox or agent-tempo core). */
37
+ exports.PI_NODE_FLOOR = '22.19.0';
38
+ /**
39
+ * Check whether the Pi runtime packages are installed. Returns a structured
40
+ * result so callers choose whether to warn or hard-fail (the extension warns;
41
+ * a headless spawner would hard-fail).
42
+ */
43
+ function probePi() {
44
+ if (!(0, sdk_probe_1.probeSdkInstall)(exports.PI_PACKAGE)) {
45
+ return {
46
+ available: false,
47
+ reason: `${exports.PI_PACKAGE} is not installed.\n` +
48
+ `Install it with: npm install -g ${exports.PI_PACKAGE}\n` +
49
+ `(requires Node >= ${exports.PI_NODE_FLOOR}).`,
50
+ };
51
+ }
52
+ return { available: true };
53
+ }
54
+ /**
55
+ * Pure semver FLOOR check: is `installed` >= `floor`? Compares major, then
56
+ * minor, then patch (a missing patch defaults to 0). Any pre-release/build
57
+ * suffix (`-beta`, `+sha`) is ignored — a pre-release of a version at/above the
58
+ * floor counts as meeting it. An unparseable `installed` returns `false`
59
+ * (conservative: unknown version is treated as below the floor).
60
+ */
61
+ function meetsVersionFloor(installed, floor = exports.PI_VERSION_FLOOR) {
62
+ const parse = (v) => {
63
+ const m = v.trim().match(/^v?(\d+)\.(\d+)(?:\.(\d+))?/);
64
+ if (!m)
65
+ return [-1, -1, -1];
66
+ return [Number(m[1]), Number(m[2]), Number(m[3] ?? 0)];
67
+ };
68
+ const [iMaj, iMin, iPat] = parse(installed);
69
+ if (iMaj < 0)
70
+ return false; // unparseable → below floor
71
+ const [fMaj, fMin, fPat] = parse(floor);
72
+ if (iMaj !== fMaj)
73
+ return iMaj > fMaj;
74
+ if (iMin !== fMin)
75
+ return iMin > fMin;
76
+ return iPat >= fPat;
77
+ }
78
+ /**
79
+ * Node-floor preflight (Decision B, #645). The Pi optional deps require
80
+ * Node >= {@link PI_NODE_FLOOR} (22.19). Enforced ONLY at the Pi recruit/spawn
81
+ * boundary — `package.json#engines` stays `>=20` so non-Pi users are untouched.
82
+ *
83
+ * Pure + injectable (`nodeVersion` defaults to the live runtime) so it is the
84
+ * single source of truth shared by the recruit pre-flight (authoritative,
85
+ * pre-spawn) AND the {@link runHeadlessPi} backstop (direct/manual launches).
86
+ * Reuses {@link meetsVersionFloor}'s major.minor.patch comparison.
87
+ *
88
+ * @returns `{ ok: true }` when the floor is met, else `{ ok: false, reason }`
89
+ * with an actionable, fail-clean message (mirrors the `probeSdkInstall` /
90
+ * `resolveModel` style). The `reason` omits any `force: true` hint so each
91
+ * caller can frame the bypass in its own voice.
92
+ */
93
+ function checkPiNodeFloor(nodeVersion = process.versions.node) {
94
+ if (meetsVersionFloor(nodeVersion, exports.PI_NODE_FLOOR))
95
+ return { ok: true };
96
+ return {
97
+ ok: false,
98
+ reason: `agent: "pi" requires Node >= ${exports.PI_NODE_FLOOR}, but this host runs Node ${nodeVersion}. ` +
99
+ `Upgrade Node (e.g. via nvm/fnm) and retry.`,
100
+ };
101
+ }
102
+ /** Default `~/.pi/agent/auth.json` presence check. */
103
+ function defaultAuthFileExists() {
104
+ return (0, fs_1.existsSync)((0, path_1.join)((0, os_1.homedir)(), '.pi', 'agent', 'auth.json'));
105
+ }
106
+ /**
107
+ * Hard pre-flight for recruiting a Copilot-backed Pi player (headless). Three
108
+ * gates, each with an actionable, `force: true`-bypassable error:
109
+ * 1. The Pi optional deps (`@earendil-works/pi-coding-agent` + `pi-ai`) are
110
+ * installed.
111
+ * 2. The Pi SDK version meets {@link PI_VERSION_FLOOR}.
112
+ * 3. Copilot auth is present — either `COPILOT_GITHUB_TOKEN` in the env or a
113
+ * mounted `~/.pi/agent/auth.json`.
114
+ *
115
+ * Mirrors the opencode / claude-code-headless recruit pre-flights. Returns a
116
+ * {@link PiProbeResult} so the caller (recruit) chooses warn vs hard-fail.
117
+ */
118
+ function probeCopilotPiPreflight(deps = {}) {
119
+ const isInstalled = deps.isInstalled ?? ((pkg) => (0, sdk_probe_1.probeSdkInstall)(pkg));
120
+ const installedVersion = deps.installedVersion ?? ((pkg) => (0, sdk_probe_1.readSdkPackageVersion)(pkg));
121
+ const env = deps.env ?? process.env;
122
+ const authFileExists = deps.authFileExists ?? defaultAuthFileExists;
123
+ // 1. Optional Pi deps present.
124
+ const missing = [exports.PI_PACKAGE, exports.PI_AI_PACKAGE].filter((pkg) => !isInstalled(pkg));
125
+ if (missing.length > 0) {
126
+ return {
127
+ available: false,
128
+ reason: `Copilot-via-Pi requires the Pi optional dependencies (missing: ${missing.join(', ')}).\n` +
129
+ `Install with: npm install -g ${exports.PI_PACKAGE} ${exports.PI_AI_PACKAGE} (requires Node >= ${exports.PI_NODE_FLOOR}).\n` +
130
+ `Or recruit with force: true to bypass this pre-flight.`,
131
+ };
132
+ }
133
+ // 2. Version floor.
134
+ const version = installedVersion(exports.PI_PACKAGE);
135
+ if (!version) {
136
+ return {
137
+ available: false,
138
+ reason: `Could not read ${exports.PI_PACKAGE} version to verify the >= ${exports.PI_VERSION_FLOOR} floor.\n` +
139
+ `Reinstall ${exports.PI_PACKAGE}, or recruit with force: true to bypass.`,
140
+ };
141
+ }
142
+ if (!meetsVersionFloor(version, exports.PI_VERSION_FLOOR)) {
143
+ return {
144
+ available: false,
145
+ reason: `${exports.PI_PACKAGE} ${version} is below the required >= ${exports.PI_VERSION_FLOOR} floor ` +
146
+ `(covers Pi #2860/#5080/#5115).\n` +
147
+ `Upgrade with: npm install -g ${exports.PI_PACKAGE}@latest, or recruit with force: true to bypass.`,
148
+ };
149
+ }
150
+ // 3. Copilot auth.
151
+ if (!env.COPILOT_GITHUB_TOKEN && !authFileExists()) {
152
+ return {
153
+ available: false,
154
+ reason: `Copilot auth not found. Set COPILOT_GITHUB_TOKEN, or run \`pi /login\` (GitHub Copilot) ` +
155
+ `to write ~/.pi/agent/auth.json.\n` +
156
+ `Or recruit with force: true to bypass this pre-flight.`,
157
+ };
158
+ }
159
+ // 4. Model indexability (catch-early; runs only when BOTH the requested model
160
+ // and a resolver are supplied — A4 injects pi-ai's getModel). Pi's Copilot
161
+ // model set is compiled at build time from models.dev, so a model on the
162
+ // user's subscription but unindexed resolves to `undefined` (NOT a throw,
163
+ // NOT null — empirically confirmed). Fail the recruit cleanly here rather
164
+ // than spawning a process getModel kills at the headless entry. A4's
165
+ // getModel backstop covers the skip cases (no resolver / session-only).
166
+ if (deps.requestedModel && deps.resolveModel) {
167
+ const { provider, model } = deps.requestedModel;
168
+ if (deps.resolveModel(provider, model) === undefined) {
169
+ return {
170
+ available: false,
171
+ reason: `model "${provider}/${model}" is not in Pi's model index (getModel returned undefined).\n` +
172
+ `Run \`pi --list-models\` for valid ids; the Copilot set is compiled from models.dev, ` +
173
+ `so a model on your subscription but unindexed is rejected (Pi #4599/#2891).\n` +
174
+ `Or recruit with force: true to bypass this pre-flight.`,
175
+ };
176
+ }
177
+ }
178
+ return { available: true };
179
+ }
@@ -0,0 +1,17 @@
1
+ import type { TempoToolDescriptor, TempoToolResult } from '../tools/descriptor';
2
+ import type { ExtensionAPI, PiToolResult } from './pi-types';
3
+ /**
4
+ * Map a neutral {@link TempoToolResult} onto Pi's `AgentToolResult` shape.
5
+ *
6
+ * Phase 0 confirmed Pi's result is `{ output, isError }` for a non-streaming
7
+ * tool (D12). The neutral `{ text, isError? }` maps directly: `text → output`,
8
+ * `isError` passes through.
9
+ */
10
+ export declare function toPiResult(r: TempoToolResult): PiToolResult;
11
+ /**
12
+ * Register every descriptor onto the Pi extension API. The TypeBox schema is
13
+ * derived per-tool from the zod shape; an unsupported zod construct throws
14
+ * `UnsupportedZodFeatureError` from the converter (fail-loud — D1), surfacing
15
+ * the offending `tool.field` so the parity test points at the exact site.
16
+ */
17
+ export declare function renderToPi(pi: ExtensionAPI, descriptors: TempoToolDescriptor[]): void;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toPiResult = toPiResult;
4
+ exports.renderToPi = renderToPi;
5
+ /**
6
+ * Pi front-end renderer — registers transport-neutral tool descriptors onto a
7
+ * Pi `ExtensionAPI` (the counterpart to `renderToMcp` in src/tools/descriptor.ts).
8
+ *
9
+ * The MCP renderer passes the zod param shape to `server.tool` raw; here we
10
+ * DERIVE a TypeBox schema from the same zod shape via the converter
11
+ * (`zod-to-typebox.ts`). zod stays the single source of truth — no dual-define,
12
+ * no drift. The CI parity test (test/pi-tool-parity.test.ts) asserts the MCP
13
+ * and Pi front-ends register the identical tool set with identical required
14
+ * params.
15
+ *
16
+ * OUTBOX-COMPLIANCE INVARIANT (load-bearing): `renderToPi` reuses the
17
+ * descriptor's `handler` VERBATIM — it never reimplements tool logic. The
18
+ * handler still routes through `handle.executeUpdate(submitOutboxUpdate, …)` on
19
+ * the player's OWN workflow handle. The Pi extension's WorkflowClient builds
20
+ * only that handle; there is ZERO `.signal()` to peer workflows.
21
+ *
22
+ * Determinism note: client-side only. src/pi imports the descriptor type FROM
23
+ * src/tools; never the reverse.
24
+ */
25
+ const zod_to_typebox_1 = require("./zod-to-typebox");
26
+ /**
27
+ * Map a neutral {@link TempoToolResult} onto Pi's `AgentToolResult` shape.
28
+ *
29
+ * Phase 0 confirmed Pi's result is `{ output, isError }` for a non-streaming
30
+ * tool (D12). The neutral `{ text, isError? }` maps directly: `text → output`,
31
+ * `isError` passes through.
32
+ */
33
+ function toPiResult(r) {
34
+ return r.isError ? { output: r.text, isError: true } : { output: r.text };
35
+ }
36
+ /**
37
+ * Register every descriptor onto the Pi extension API. The TypeBox schema is
38
+ * derived per-tool from the zod shape; an unsupported zod construct throws
39
+ * `UnsupportedZodFeatureError` from the converter (fail-loud — D1), surfacing
40
+ * the offending `tool.field` so the parity test points at the exact site.
41
+ */
42
+ function renderToPi(pi, descriptors) {
43
+ for (const d of descriptors) {
44
+ pi.registerTool({
45
+ name: d.name,
46
+ description: d.description,
47
+ parameters: (0, zod_to_typebox_1.zodShapeToTypeBox)(d.params, d.name),
48
+ // Pi calls execute POSITIONALLY: (toolCallId, params, signal, onUpdate, ctx).
49
+ // The validated params object is the SECOND positional — ignore the
50
+ // toolCallId string (1st) and hand `params` to the descriptor handler.
51
+ // (Passing the 1st positional was the v1.4.0 arg-order bug: handlers got the
52
+ // toolCallId string instead of params.) See PiToolDefinition.execute.
53
+ execute: async (_toolCallId, params) => toPiResult(await d.handler(params)),
54
+ });
55
+ }
56
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Reset pump (3d D14) — polls the session workflow's single-slot pending reset
3
+ * and performs a CLEAN-WIPE on the live Pi session, then acks it. Sibling to
4
+ * {@link CuePump}: Pi has no reverse-RPC from Temporal, so reset (an operator/
5
+ * conductor CONTROL op — it bypasses the MD-G tool gate) is delivered by polling
6
+ * `pendingReset` and acking via the race-safe `ackReset(resetId)` (the workflow
7
+ * clears the slot only if the id still matches, so a newer reset landing during
8
+ * the wipe is preserved for the next tick).
9
+ *
10
+ * D14 (maintainer-ruled): reset = clean-wipe via Pi `session.newSession()` (fresh
11
+ * context, NO replay). Seeded reset is a separate concern (`restart` +
12
+ * `loadFromState`), so a `fresh:false` here is defensively logged + acked (never
13
+ * silently wiped) — the reset tool only ever sends `fresh:true` today.
14
+ */
15
+ import type { PendingReset } from '../types';
16
+ import type { PiAgentSession } from './pi-types';
17
+ /** Source of the pending reset + ack — satisfied by `PiWorkflowClient`. */
18
+ export interface ResetSource {
19
+ fetchPendingReset(): Promise<PendingReset | null>;
20
+ ackReset(resetId: string): Promise<void>;
21
+ }
22
+ /** Resolves the CURRENT live Pi session at wipe time (re-acquired each tick — D11). */
23
+ export type SessionResolver = () => PiAgentSession | null;
24
+ export interface ResetPumpOptions {
25
+ source: ResetSource;
26
+ resolveSession: SessionResolver;
27
+ /** Poll interval (ms). */
28
+ intervalMs?: number;
29
+ }
30
+ export declare class ResetPump {
31
+ private readonly source;
32
+ private readonly resolveSession;
33
+ private readonly intervalMs;
34
+ private timer;
35
+ private draining;
36
+ constructor(opts: ResetPumpOptions);
37
+ start(): void;
38
+ stop(): void;
39
+ /**
40
+ * One poll cycle: fetch the pending reset; if present + a live session is
41
+ * attached, perform the wipe and ack. Re-entrancy guarded so a slow tick never
42
+ * overlaps the next interval. Public for unit tests to drive directly.
43
+ */
44
+ tick(): Promise<void>;
45
+ /** Wipe (D14 clean-wipe) + deliver the "context wiped" notice. */
46
+ private performReset;
47
+ }