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,60 @@
1
+ /**
2
+ * Tool capability classifier (3d / MD-C) — a PURE function mapping a tool name
3
+ * to its risk class. The 3d operator gate and the MD-C headless tool-access
4
+ * posture consume it; this module owns the MECHANISM, tempo-security owns the
5
+ * CONTENT (the name-sets below) and signs off on it.
6
+ *
7
+ * - `'exec'` — shell / arbitrary-code execution. HARD-BLOCKED when the
8
+ * headless tool-access policy is `restricted` (MD-C). The
9
+ * highest-danger class.
10
+ * - `'high-blast'` — destructive or exfiltration-capable actions (file writes,
11
+ * network fetch, ensemble mutation). Allowed unsupervised,
12
+ * but routed to the OPERATOR GATE when an operator is armed.
13
+ * - `'low-risk'` — read-only / coordination. Always bypasses the gate.
14
+ *
15
+ * UNKNOWN tool names default to {@link UNKNOWN_DEFAULT} — a FAIL-SAFE choice
16
+ * (`'high-blast'`: never silently bypass a tool we don't recognize; gate it when
17
+ * an operator is armed). Whether an unknown tool should instead hard-block
18
+ * (`'exec'`) is a tempo-security posture call — see the cue to security.
19
+ *
20
+ * Matching is CASE-INSENSITIVE on the trimmed tool name. Pi's `tool_call`
21
+ * `toolName` is a bare string (built-ins like `bash`/`read`/`edit`/`write`/`grep`
22
+ * plus the natively-registered agent-tempo MCP tools), so we normalize before
23
+ * lookup.
24
+ *
25
+ * ⚠️ NAME-SET OWNERSHIP: the three Sets are tempo-security's posture content.
26
+ * They are a STARTER set (conductor's examples + Pi built-ins + the agent-tempo
27
+ * coordination/mutation tools) pending security's authoritative sign-off; amend
28
+ * the Set membership without touching the mechanism. The classifier behavior
29
+ * (and its tests) are keyed on representative names that won't move.
30
+ */
31
+ /** Risk class for a tool, consumed by the 3d gate + MD-C policy. */
32
+ export type ToolCapability = 'exec' | 'high-blast' | 'low-risk';
33
+ /** Fail-safe class for an unrecognized tool name (never bypass the unknown). */
34
+ export declare const UNKNOWN_DEFAULT: ToolCapability;
35
+ /**
36
+ * Shell / arbitrary-code execution — hard-blocked at `restricted` (MD-C).
37
+ * CONTENT owned by tempo-security (signed off 2026-06-04).
38
+ *
39
+ * CANONICAL exec denylist (single source of truth). The F1 refactor (3d) made
40
+ * `src/pi/extension.ts` import this set via `classify(name) === 'exec'` and
41
+ * REMOVED its former local `SHELL_TOOL_NAMES` — so the live restricted-mode gate
42
+ * now hard-blocks the full set including `powershell`/`pwsh`/`cmd`/`run` (the gap
43
+ * the old local list left open). Never re-declare a shell denylist elsewhere.
44
+ */
45
+ export declare const EXEC_TOOLS: ReadonlySet<string>;
46
+ /**
47
+ * Destructive / exfiltration-capable — gated when an operator is armed.
48
+ * CONTENT owned by tempo-security (signed off 2026-06-04).
49
+ */
50
+ export declare const HIGH_BLAST_TOOLS: ReadonlySet<string>;
51
+ /**
52
+ * Read-only / coordination — always bypasses the gate.
53
+ * CONTENT owned by tempo-security (signed off 2026-06-04).
54
+ */
55
+ export declare const LOW_RISK_TOOLS: ReadonlySet<string>;
56
+ /**
57
+ * Classify a tool name into its risk class. Pure + case-insensitive; unknown
58
+ * names fall through to {@link UNKNOWN_DEFAULT} (fail-safe — never bypass).
59
+ */
60
+ export declare function classify(toolName: string): ToolCapability;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ /**
3
+ * Tool capability classifier (3d / MD-C) — a PURE function mapping a tool name
4
+ * to its risk class. The 3d operator gate and the MD-C headless tool-access
5
+ * posture consume it; this module owns the MECHANISM, tempo-security owns the
6
+ * CONTENT (the name-sets below) and signs off on it.
7
+ *
8
+ * - `'exec'` — shell / arbitrary-code execution. HARD-BLOCKED when the
9
+ * headless tool-access policy is `restricted` (MD-C). The
10
+ * highest-danger class.
11
+ * - `'high-blast'` — destructive or exfiltration-capable actions (file writes,
12
+ * network fetch, ensemble mutation). Allowed unsupervised,
13
+ * but routed to the OPERATOR GATE when an operator is armed.
14
+ * - `'low-risk'` — read-only / coordination. Always bypasses the gate.
15
+ *
16
+ * UNKNOWN tool names default to {@link UNKNOWN_DEFAULT} — a FAIL-SAFE choice
17
+ * (`'high-blast'`: never silently bypass a tool we don't recognize; gate it when
18
+ * an operator is armed). Whether an unknown tool should instead hard-block
19
+ * (`'exec'`) is a tempo-security posture call — see the cue to security.
20
+ *
21
+ * Matching is CASE-INSENSITIVE on the trimmed tool name. Pi's `tool_call`
22
+ * `toolName` is a bare string (built-ins like `bash`/`read`/`edit`/`write`/`grep`
23
+ * plus the natively-registered agent-tempo MCP tools), so we normalize before
24
+ * lookup.
25
+ *
26
+ * ⚠️ NAME-SET OWNERSHIP: the three Sets are tempo-security's posture content.
27
+ * They are a STARTER set (conductor's examples + Pi built-ins + the agent-tempo
28
+ * coordination/mutation tools) pending security's authoritative sign-off; amend
29
+ * the Set membership without touching the mechanism. The classifier behavior
30
+ * (and its tests) are keyed on representative names that won't move.
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.LOW_RISK_TOOLS = exports.HIGH_BLAST_TOOLS = exports.EXEC_TOOLS = exports.UNKNOWN_DEFAULT = void 0;
34
+ exports.classify = classify;
35
+ /** Fail-safe class for an unrecognized tool name (never bypass the unknown). */
36
+ exports.UNKNOWN_DEFAULT = 'high-blast';
37
+ /**
38
+ * Shell / arbitrary-code execution — hard-blocked at `restricted` (MD-C).
39
+ * CONTENT owned by tempo-security (signed off 2026-06-04).
40
+ *
41
+ * CANONICAL exec denylist (single source of truth). The F1 refactor (3d) made
42
+ * `src/pi/extension.ts` import this set via `classify(name) === 'exec'` and
43
+ * REMOVED its former local `SHELL_TOOL_NAMES` — so the live restricted-mode gate
44
+ * now hard-blocks the full set including `powershell`/`pwsh`/`cmd`/`run` (the gap
45
+ * the old local list left open). Never re-declare a shell denylist elsewhere.
46
+ */
47
+ exports.EXEC_TOOLS = new Set([
48
+ 'bash',
49
+ 'shell',
50
+ 'exec',
51
+ 'sh',
52
+ 'powershell',
53
+ 'pwsh',
54
+ 'cmd',
55
+ 'run',
56
+ 'process',
57
+ 'command',
58
+ 'run_command',
59
+ ]);
60
+ /**
61
+ * Destructive / exfiltration-capable — gated when an operator is armed.
62
+ * CONTENT owned by tempo-security (signed off 2026-06-04).
63
+ */
64
+ exports.HIGH_BLAST_TOOLS = new Set([
65
+ // Pi built-in mutating tools.
66
+ 'write',
67
+ 'edit',
68
+ 'multiedit',
69
+ // Network exfiltration surface (arbitrary HTTP incl. POST). NOTE: `websearch`
70
+ // is LOW_RISK (read-only results) — only the fetch/browse family gates.
71
+ 'webfetch',
72
+ 'web_fetch',
73
+ 'fetch',
74
+ 'http_request',
75
+ 'browser',
76
+ // agent-tempo tools that mutate the ensemble / spawn / tear down / control peers.
77
+ 'recruit',
78
+ 'destroy',
79
+ 'restart',
80
+ 'migrate',
81
+ 'shutdown',
82
+ 'release', // releases a held player — state change on a peer
83
+ 'broadcast', // fans out to ALL players (amplification)
84
+ 'schedule', // creates a durable autonomous trigger (unschedule is LOW_RISK)
85
+ 'pause',
86
+ 'play',
87
+ 'restore', // re-spawns a detached player (process spawn)
88
+ // State / artifact destruction (irreversible).
89
+ 'save_state',
90
+ 'clear_state',
91
+ 'coat_check_evict',
92
+ // Lineup spawn (full ensemble from YAML; save_lineup is LOW_RISK).
93
+ 'load_lineup',
94
+ // Git state + disk mutation.
95
+ 'worktree',
96
+ 'stage',
97
+ 'cancel_stage', // discards staged work (irreversible)
98
+ // Quality gates that block/unblock ensemble workflow progress.
99
+ 'quality_gate',
100
+ 'evaluate_gate',
101
+ // Drive/file mutation.
102
+ 'copy_file',
103
+ ]);
104
+ /**
105
+ * Read-only / coordination — always bypasses the gate.
106
+ * CONTENT owned by tempo-security (signed off 2026-06-04).
107
+ */
108
+ exports.LOW_RISK_TOOLS = new Set([
109
+ // Pi built-in read tools.
110
+ 'read',
111
+ 'grep',
112
+ 'glob',
113
+ 'ls',
114
+ // Read-only web search (no arbitrary POST — distinct from the web_fetch family).
115
+ 'websearch',
116
+ 'web_search',
117
+ // agent-tempo read-only / status / messaging coordination tools.
118
+ 'cue',
119
+ 'report',
120
+ 'recall',
121
+ 'listen',
122
+ 'ensemble',
123
+ 'who_am_i',
124
+ 'set_name',
125
+ 'set_part',
126
+ 'set_ensemble_description', // metadata only, no blast radius
127
+ 'hosts',
128
+ 'attachment_info',
129
+ 'agent_types',
130
+ 'schedules',
131
+ 'stages',
132
+ 'gates',
133
+ // Read-only state / coat-check (coat_check_put is bounded 32KB + TTL'd + visible).
134
+ 'fetch_state',
135
+ 'coat_check_put',
136
+ 'coat_check_get',
137
+ 'coat_check_list',
138
+ // Removes a future trigger — blast was at schedule-time, not here.
139
+ 'unschedule',
140
+ // Writes the coordinator YAML (not arbitrary file write; blast is at load_lineup).
141
+ 'save_lineup',
142
+ ]);
143
+ /**
144
+ * Classify a tool name into its risk class. Pure + case-insensitive; unknown
145
+ * names fall through to {@link UNKNOWN_DEFAULT} (fail-safe — never bypass).
146
+ */
147
+ function classify(toolName) {
148
+ const name = toolName.trim().toLowerCase();
149
+ if (exports.EXEC_TOOLS.has(name))
150
+ return 'exec';
151
+ if (exports.HIGH_BLAST_TOOLS.has(name))
152
+ return 'high-blast';
153
+ if (exports.LOW_RISK_TOOLS.has(name))
154
+ return 'low-risk';
155
+ return exports.UNKNOWN_DEFAULT;
156
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Thin CLIENT-SIDE Temporal wrapper for the Pi extension (D4 = (a)).
3
+ *
4
+ * The durable Temporal `Worker` stays in the daemon. This holds only a
5
+ * `WorkflowClient` that signals/queries/updates the session workflow — it never
6
+ * runs workflow code, so NOTHING here (or anywhere in src/pi/) is imported into
7
+ * the V8 workflow sandbox. The determinism boundary is preserved.
8
+ *
9
+ * Reuses the existing wire surface verbatim:
10
+ * - `claimAttachment` / `heartbeat` — lease lifecycle (drives `attached`)
11
+ * - `processingStart` / `processingEnd` — drives `processing` / `awaiting`
12
+ * - `requestDetach` + `adapterExited` — drives `draining` → `detached`
13
+ * - `submitOutbox` — report routing (outbox compliance)
14
+ * - `pendingMessages` + `markDelivered` — cue intake + ack
15
+ */
16
+ import { Client, type WorkflowHandle } from '@temporalio/client';
17
+ import { type Config } from '../config';
18
+ import type { AttachmentToken, DetachReason, Message, OutboxEntryInput, PendingReset, SessionMetadata } from '../types';
19
+ import type { WorkflowAction } from './phase-driver';
20
+ /**
21
+ * MD-A liveness timings (Phase 2, maintainer-approved): 30s heartbeat / 90s
22
+ * lease, holding the **lease = 3×heartbeat** invariant — a single (or even
23
+ * second) missed beat never expires the lease, but a dead process is reaped
24
+ * within ~one lease window. Parity with the other SDK adapters' `heartbeatMs`.
25
+ * Overridable per-session via `PiWorkflowClientOptions.{leaseMs,heartbeatMs}`.
26
+ * Exported so a guard test can assert the invariant doesn't silently drift.
27
+ */
28
+ export declare const PI_HEARTBEAT_MS = 30000;
29
+ export declare const PI_LEASE_MS = 90000;
30
+ /** 3c Tier-1 — coarse activity sample piggybacked onto each heartbeat. */
31
+ export type CoarseSample = {
32
+ currentTool: string | null;
33
+ contextTokens?: number;
34
+ contextPercent?: number;
35
+ };
36
+ /**
37
+ * Supplies the latest coarse sample at heartbeat time. eng's inner-loop
38
+ * publisher exposes exactly this via `getCoarseState()`; the extension wires it
39
+ * in after constructing the publisher. Absent (default) → heartbeats carry no
40
+ * coarse fields (backward-compatible with the 3a sender).
41
+ */
42
+ export type CoarseProvider = () => CoarseSample | undefined;
43
+ export interface PiWorkflowClientOptions {
44
+ client: Client;
45
+ config: Config;
46
+ metadata: SessionMetadata;
47
+ leaseMs?: number;
48
+ heartbeatMs?: number;
49
+ /** 3c — optional coarse-activity provider (eng's `publisher.getCoarseState`). */
50
+ coarseProvider?: CoarseProvider;
51
+ /**
52
+ * Restart/migrate handoff token (`AGENT_TEMPO_ATTACHMENT_ID`). When present,
53
+ * `claim()` ADOPTS the pre-created attachment via the renewal branch instead of
54
+ * racing a fresh claim — the clean anti-flap path the restart tool will use
55
+ * (the read side of that spawn wiring is a tracked Phase 3 / restart carry-item;
56
+ * this is the forward-compatible plumbing). Absent on a fresh recruit / manual
57
+ * launch → fresh claim. Mirrors the existing adapters (`base.ts` startV2Lifecycle).
58
+ */
59
+ expectedAttachmentId?: string;
60
+ }
61
+ /**
62
+ * One instance per attached Pi session — holds the session `WorkflowHandle`,
63
+ * lease token, and heartbeat timer for one fixed-workflowId player. Tool
64
+ * handlers reach this player's handle via the D11 lazy proxy (`get handle()`).
65
+ */
66
+ export declare class PiWorkflowClient {
67
+ private readonly client;
68
+ private readonly config;
69
+ private readonly metadata;
70
+ private readonly leaseMs;
71
+ private readonly heartbeatMs;
72
+ private readonly expectedAttachmentId?;
73
+ private coarseProvider?;
74
+ private wfHandle;
75
+ private token;
76
+ private heartbeatTimer;
77
+ constructor(opts: PiWorkflowClientOptions);
78
+ /**
79
+ * 3c — wire the coarse-activity provider after construction (the inner-loop
80
+ * publisher is created alongside, then handed in). Each heartbeat samples it.
81
+ */
82
+ setCoarseProvider(provider: CoarseProvider): void;
83
+ /** Build a client from config (connection-pooled; safe to share — see D12a). */
84
+ static connect(config?: Config): Promise<Client>;
85
+ get attachmentId(): string | null;
86
+ /**
87
+ * The live session `WorkflowHandle`, or `null` before {@link ensureSessionWorkflow}.
88
+ * Exposed so the D11 lazy-proxy (src/pi/lazy-proxy.ts) can resolve the player's
89
+ * OWN handle per tool call — tool handlers route through it (e.g. `submitOutbox`)
90
+ * and never `.signal()` a peer workflow directly.
91
+ */
92
+ get handle(): WorkflowHandle | null;
93
+ private get workflowId();
94
+ /**
95
+ * Ensure the session workflow exists and grab a handle. For a human-launched
96
+ * `pi`, the workflow may not exist yet; `USE_EXISTING` makes this idempotent
97
+ * when `agent-tempo up`/recruit already started it.
98
+ */
99
+ ensureSessionWorkflow(): Promise<WorkflowHandle>;
100
+ private requireHandle;
101
+ /**
102
+ * Claim (or, with a handoff token, ADOPT) the attachment: phase
103
+ * `booting → attached`. Fresh claim when `expectedAttachmentId` is absent;
104
+ * renewal/adoption branch when the restart tool handed one in. Stores the token.
105
+ */
106
+ claim(): Promise<AttachmentToken>;
107
+ /**
108
+ * Liveness heartbeat — renews the lease. This is the ENTIRE reason an abrupt
109
+ * Pi death is detectable: stop heart-beating and `expiresAt` lapses (see
110
+ * src/pi/README.md "Abrupt-death finding" / MD-A).
111
+ */
112
+ heartbeat(): Promise<void>;
113
+ /** Start the heartbeat loop. The timer is `unref`'d so it never holds the process open. */
114
+ startHeartbeat(): void;
115
+ stopHeartbeat(): void;
116
+ /** agent_start → phase `processing`. */
117
+ processingStart(messageId: string): Promise<void>;
118
+ /** agent_end → phase `awaiting`. */
119
+ processingEnd(messageId: string): Promise<void>;
120
+ /**
121
+ * Graceful self-initiated detach: stopHeartbeat → adapterExited (→ detached).
122
+ *
123
+ * `adapterExited` ALONE collapses any live phase (attached/processing/awaiting/
124
+ * draining) straight to `detached` — see the workflow handler at
125
+ * `session.ts` adapterExitedSignal (returns early only on detached/gone). No
126
+ * `requestDetach` is needed for a self-exit; that signal is the EXTERNAL ask to
127
+ * drain a running adapter someone else controls. This matches the proven
128
+ * `BaseAttachment.stopV2Lifecycle(graceful)` path. Idempotent-safe to call on
129
+ * `session_shutdown` and from the headless exit sequence.
130
+ */
131
+ detach(reason?: DetachReason): Promise<void>;
132
+ /**
133
+ * Stash the currently-active Pi SessionManager session id onto durable
134
+ * workflow metadata (`metadata.sessionId`). This is the MUTABLE resume pointer
135
+ * — SEPARATE from the fixed workflowId (identity). A later restart of this
136
+ * player reads it to resume the same conversation via Pi `continueSession`
137
+ * (reuses the #334 sessionId resume field). No-op when the id is absent.
138
+ */
139
+ updateSessionId(sessionId: string | undefined): Promise<void>;
140
+ /** Route an outbox entry through the workflow outbox (player's own handle). */
141
+ submitOutbox(entry: OutboxEntryInput): Promise<string>;
142
+ /** Pull undelivered messages (cues) queued on the workflow. */
143
+ fetchPending(): Promise<Message[]>;
144
+ /** Acknowledge delivered cue ids so the workflow stops re-serving them. */
145
+ ackDelivered(messageIds: string[]): Promise<void>;
146
+ /** Poll the workflow's single-slot pending reset (null = none). */
147
+ fetchPendingReset(): Promise<PendingReset | null>;
148
+ /**
149
+ * Ack a performed reset by id — the workflow clears the slot ONLY if the id
150
+ * still matches (race-safe: a newer reset that landed mid-wipe is preserved).
151
+ */
152
+ ackReset(resetId: string): Promise<void>;
153
+ /**
154
+ * Dispatch a {@link WorkflowAction} produced by {@link PhaseDriver}. Centralizes
155
+ * the action→wire-call mapping so the extension wiring stays declarative.
156
+ */
157
+ performAction(action: WorkflowAction): Promise<void>;
158
+ }
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PiWorkflowClient = exports.PI_LEASE_MS = exports.PI_HEARTBEAT_MS = void 0;
4
+ /**
5
+ * Thin CLIENT-SIDE Temporal wrapper for the Pi extension (D4 = (a)).
6
+ *
7
+ * The durable Temporal `Worker` stays in the daemon. This holds only a
8
+ * `WorkflowClient` that signals/queries/updates the session workflow — it never
9
+ * runs workflow code, so NOTHING here (or anywhere in src/pi/) is imported into
10
+ * the V8 workflow sandbox. The determinism boundary is preserved.
11
+ *
12
+ * Reuses the existing wire surface verbatim:
13
+ * - `claimAttachment` / `heartbeat` — lease lifecycle (drives `attached`)
14
+ * - `processingStart` / `processingEnd` — drives `processing` / `awaiting`
15
+ * - `requestDetach` + `adapterExited` — drives `draining` → `detached`
16
+ * - `submitOutbox` — report routing (outbox compliance)
17
+ * - `pendingMessages` + `markDelivered` — cue intake + ack
18
+ */
19
+ const client_1 = require("@temporalio/client");
20
+ const connection_1 = require("../connection");
21
+ const config_1 = require("../config");
22
+ const signals_1 = require("../workflows/signals");
23
+ /** Adapter identity advertised on claim. Pi interactive players are `interactive`-class. */
24
+ const PI_ADAPTER_ID = 'pi';
25
+ /**
26
+ * MD-A liveness timings (Phase 2, maintainer-approved): 30s heartbeat / 90s
27
+ * lease, holding the **lease = 3×heartbeat** invariant — a single (or even
28
+ * second) missed beat never expires the lease, but a dead process is reaped
29
+ * within ~one lease window. Parity with the other SDK adapters' `heartbeatMs`.
30
+ * Overridable per-session via `PiWorkflowClientOptions.{leaseMs,heartbeatMs}`.
31
+ * Exported so a guard test can assert the invariant doesn't silently drift.
32
+ */
33
+ exports.PI_HEARTBEAT_MS = 30_000;
34
+ exports.PI_LEASE_MS = 90_000;
35
+ const log = (...args) => {
36
+ // eslint-disable-next-line no-console
37
+ console.error('[agent-tempo:pi]', ...args);
38
+ };
39
+ /**
40
+ * One instance per attached Pi session — holds the session `WorkflowHandle`,
41
+ * lease token, and heartbeat timer for one fixed-workflowId player. Tool
42
+ * handlers reach this player's handle via the D11 lazy proxy (`get handle()`).
43
+ */
44
+ class PiWorkflowClient {
45
+ client;
46
+ config;
47
+ metadata;
48
+ leaseMs;
49
+ heartbeatMs;
50
+ expectedAttachmentId;
51
+ coarseProvider;
52
+ wfHandle = null;
53
+ token = null;
54
+ heartbeatTimer = null;
55
+ constructor(opts) {
56
+ this.client = opts.client;
57
+ this.config = opts.config;
58
+ this.metadata = opts.metadata;
59
+ this.leaseMs = opts.leaseMs ?? exports.PI_LEASE_MS;
60
+ this.heartbeatMs = opts.heartbeatMs ?? exports.PI_HEARTBEAT_MS;
61
+ this.expectedAttachmentId = opts.expectedAttachmentId;
62
+ this.coarseProvider = opts.coarseProvider;
63
+ }
64
+ /**
65
+ * 3c — wire the coarse-activity provider after construction (the inner-loop
66
+ * publisher is created alongside, then handed in). Each heartbeat samples it.
67
+ */
68
+ setCoarseProvider(provider) {
69
+ this.coarseProvider = provider;
70
+ }
71
+ /** Build a client from config (connection-pooled; safe to share — see D12a). */
72
+ static async connect(config = (0, config_1.getConfig)()) {
73
+ const connection = await (0, connection_1.createTemporalConnection)(config);
74
+ return new client_1.Client({ connection, namespace: config.temporalNamespace });
75
+ }
76
+ get attachmentId() {
77
+ return this.token?.attachmentId ?? null;
78
+ }
79
+ /**
80
+ * The live session `WorkflowHandle`, or `null` before {@link ensureSessionWorkflow}.
81
+ * Exposed so the D11 lazy-proxy (src/pi/lazy-proxy.ts) can resolve the player's
82
+ * OWN handle per tool call — tool handlers route through it (e.g. `submitOutbox`)
83
+ * and never `.signal()` a peer workflow directly.
84
+ */
85
+ get handle() {
86
+ return this.wfHandle;
87
+ }
88
+ get workflowId() {
89
+ return (0, config_1.sessionWorkflowId)(this.metadata.ensemble, this.metadata.playerId);
90
+ }
91
+ /**
92
+ * Ensure the session workflow exists and grab a handle. For a human-launched
93
+ * `pi`, the workflow may not exist yet; `USE_EXISTING` makes this idempotent
94
+ * when `agent-tempo up`/recruit already started it.
95
+ */
96
+ async ensureSessionWorkflow() {
97
+ if (this.wfHandle)
98
+ return this.wfHandle;
99
+ this.wfHandle = await this.client.workflow.start('agentSessionWorkflow', {
100
+ workflowId: this.workflowId,
101
+ taskQueue: this.config.taskQueue,
102
+ workflowIdConflictPolicy: 'USE_EXISTING',
103
+ args: [{ metadata: this.metadata, phase: 'booting' }],
104
+ });
105
+ return this.wfHandle;
106
+ }
107
+ requireHandle() {
108
+ if (!this.wfHandle) {
109
+ throw new Error('PiWorkflowClient: call ensureSessionWorkflow() before lifecycle ops');
110
+ }
111
+ return this.wfHandle;
112
+ }
113
+ /**
114
+ * Claim (or, with a handoff token, ADOPT) the attachment: phase
115
+ * `booting → attached`. Fresh claim when `expectedAttachmentId` is absent;
116
+ * renewal/adoption branch when the restart tool handed one in. Stores the token.
117
+ */
118
+ async claim() {
119
+ const handle = this.requireHandle();
120
+ this.token = await handle.executeUpdate(signals_1.claimAttachmentUpdate, {
121
+ args: [
122
+ {
123
+ host: this.metadata.hostname,
124
+ adapterId: PI_ADAPTER_ID,
125
+ adapterClass: 'interactive',
126
+ leaseMs: this.leaseMs,
127
+ ...(this.expectedAttachmentId ? { expectedAttachmentId: this.expectedAttachmentId } : {}),
128
+ },
129
+ ],
130
+ });
131
+ log(`${this.expectedAttachmentId ? 'renewed' : 'claimed'} attachment ` +
132
+ `${this.token.attachmentId} (lease ${this.leaseMs}ms)`);
133
+ return this.token;
134
+ }
135
+ /**
136
+ * Liveness heartbeat — renews the lease. This is the ENTIRE reason an abrupt
137
+ * Pi death is detectable: stop heart-beating and `expiresAt` lapses (see
138
+ * src/pi/README.md "Abrupt-death finding" / MD-A).
139
+ */
140
+ async heartbeat() {
141
+ if (!this.token)
142
+ return;
143
+ const handle = this.requireHandle();
144
+ // 3c Tier-1 — piggyback the latest coarse sample (currentTool + context).
145
+ // Sampled per-beat; a throwing/absent provider degrades to a plain heartbeat.
146
+ let coarse;
147
+ try {
148
+ coarse = this.coarseProvider?.();
149
+ }
150
+ catch {
151
+ coarse = undefined;
152
+ }
153
+ await handle.signal(signals_1.heartbeatSignal, {
154
+ attachmentId: this.token.attachmentId,
155
+ at: new Date().toISOString(),
156
+ ...(coarse ? coarse : {}),
157
+ });
158
+ }
159
+ /** Start the heartbeat loop. The timer is `unref`'d so it never holds the process open. */
160
+ startHeartbeat() {
161
+ if (this.heartbeatTimer)
162
+ return;
163
+ this.heartbeatTimer = setInterval(() => {
164
+ this.heartbeat().catch((err) => log('heartbeat failed:', err));
165
+ }, this.heartbeatMs);
166
+ if (typeof this.heartbeatTimer.unref === 'function')
167
+ this.heartbeatTimer.unref();
168
+ }
169
+ stopHeartbeat() {
170
+ if (this.heartbeatTimer) {
171
+ clearInterval(this.heartbeatTimer);
172
+ this.heartbeatTimer = null;
173
+ }
174
+ }
175
+ /** agent_start → phase `processing`. */
176
+ async processingStart(messageId) {
177
+ const handle = this.requireHandle();
178
+ await handle.executeUpdate(signals_1.processingStartUpdate, {
179
+ args: [{ messageId, expectedAttachmentId: this.token?.attachmentId }],
180
+ });
181
+ }
182
+ /** agent_end → phase `awaiting`. */
183
+ async processingEnd(messageId) {
184
+ const handle = this.requireHandle();
185
+ await handle.executeUpdate(signals_1.processingEndUpdate, {
186
+ args: [{ messageId, expectedAttachmentId: this.token?.attachmentId }],
187
+ });
188
+ }
189
+ /**
190
+ * Graceful self-initiated detach: stopHeartbeat → adapterExited (→ detached).
191
+ *
192
+ * `adapterExited` ALONE collapses any live phase (attached/processing/awaiting/
193
+ * draining) straight to `detached` — see the workflow handler at
194
+ * `session.ts` adapterExitedSignal (returns early only on detached/gone). No
195
+ * `requestDetach` is needed for a self-exit; that signal is the EXTERNAL ask to
196
+ * drain a running adapter someone else controls. This matches the proven
197
+ * `BaseAttachment.stopV2Lifecycle(graceful)` path. Idempotent-safe to call on
198
+ * `session_shutdown` and from the headless exit sequence.
199
+ */
200
+ async detach(reason = 'agent-exited') {
201
+ if (!this.wfHandle || !this.token)
202
+ return;
203
+ this.stopHeartbeat();
204
+ // Signal-DELIVERY (not a processing-ack) is sufficient by design, ruled won't-fix:
205
+ // the session workflow runs in the DAEMON worker, not this Pi process, so once
206
+ // signal() resolves the exit is durably in history and the worker transitions to
207
+ // `detached` regardless of this process disposing/exiting. stopHeartbeat() above is
208
+ // the independent backstop — a missed transition still reaps via lease expiry. No
209
+ // caller reads phase synchronously at detach()-return, so an executeUpdate ack would
210
+ // only add shutdown latency + drag determinism-sensitive workflow code into scope.
211
+ // If a future synchronous re-claim/migrate ever needs an OBSERVED `detached`, poll the
212
+ // existing attachmentInfoQuery client-side — never add an adapterExitedUpdate.
213
+ await this.wfHandle.signal(signals_1.adapterExitedSignal, {
214
+ attachmentId: this.token.attachmentId,
215
+ reason,
216
+ });
217
+ log(`detached (${reason})`);
218
+ }
219
+ /**
220
+ * Stash the currently-active Pi SessionManager session id onto durable
221
+ * workflow metadata (`metadata.sessionId`). This is the MUTABLE resume pointer
222
+ * — SEPARATE from the fixed workflowId (identity). A later restart of this
223
+ * player reads it to resume the same conversation via Pi `continueSession`
224
+ * (reuses the #334 sessionId resume field). No-op when the id is absent.
225
+ */
226
+ async updateSessionId(sessionId) {
227
+ if (!sessionId)
228
+ return;
229
+ const handle = this.requireHandle();
230
+ await handle.signal(signals_1.updateMetadataSignal, { sessionId });
231
+ }
232
+ // ── Outbox ──
233
+ /** Route an outbox entry through the workflow outbox (player's own handle). */
234
+ async submitOutbox(entry) {
235
+ const handle = this.requireHandle();
236
+ return handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
237
+ }
238
+ // ── Cue intake ──
239
+ /** Pull undelivered messages (cues) queued on the workflow. */
240
+ async fetchPending() {
241
+ const handle = this.requireHandle();
242
+ return handle.query(signals_1.pendingMessagesQuery);
243
+ }
244
+ /** Acknowledge delivered cue ids so the workflow stops re-serving them. */
245
+ async ackDelivered(messageIds) {
246
+ if (messageIds.length === 0)
247
+ return;
248
+ const handle = this.requireHandle();
249
+ await handle.signal(signals_1.markDeliveredSignal, messageIds);
250
+ }
251
+ // ── Reset intake (3d D14) ──
252
+ /** Poll the workflow's single-slot pending reset (null = none). */
253
+ async fetchPendingReset() {
254
+ const handle = this.requireHandle();
255
+ return handle.query(signals_1.pendingResetQuery);
256
+ }
257
+ /**
258
+ * Ack a performed reset by id — the workflow clears the slot ONLY if the id
259
+ * still matches (race-safe: a newer reset that landed mid-wipe is preserved).
260
+ */
261
+ async ackReset(resetId) {
262
+ const handle = this.requireHandle();
263
+ await handle.signal(signals_1.ackResetSignal, resetId);
264
+ }
265
+ /**
266
+ * Dispatch a {@link WorkflowAction} produced by {@link PhaseDriver}. Centralizes
267
+ * the action→wire-call mapping so the extension wiring stays declarative.
268
+ */
269
+ async performAction(action) {
270
+ switch (action.kind) {
271
+ case 'claim':
272
+ await this.claim();
273
+ this.startHeartbeat();
274
+ return;
275
+ case 'processingStart':
276
+ await this.processingStart(action.messageId);
277
+ return;
278
+ case 'processingEnd':
279
+ await this.processingEnd(action.messageId);
280
+ return;
281
+ case 'detach':
282
+ await this.detach();
283
+ return;
284
+ case 'none':
285
+ return;
286
+ }
287
+ }
288
+ }
289
+ exports.PiWorkflowClient = PiWorkflowClient;