agent-tempo 1.3.1 → 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 (197) 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 +224 -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 +53 -0
  65. package/dist/pi/mission-control/board.js +104 -0
  66. package/dist/pi/mission-control/extension.d.ts +44 -0
  67. package/dist/pi/mission-control/extension.js +251 -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 +95 -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 +208 -0
  79. package/dist/pi/pi-types.js +21 -0
  80. package/dist/pi/probe.d.ts +80 -0
  81. package/dist/pi/probe.js +154 -0
  82. package/dist/pi/render-tools.d.ts +17 -0
  83. package/dist/pi/render-tools.js +51 -0
  84. package/dist/pi/reset-pump.d.ts +47 -0
  85. package/dist/pi/reset-pump.js +85 -0
  86. package/dist/pi/tool-capability.d.ts +60 -0
  87. package/dist/pi/tool-capability.js +156 -0
  88. package/dist/pi/workflow-client.d.ts +158 -0
  89. package/dist/pi/workflow-client.js +289 -0
  90. package/dist/pi/zod-to-typebox.d.ts +74 -0
  91. package/dist/pi/zod-to-typebox.js +191 -0
  92. package/dist/server-tools.d.ts +2 -0
  93. package/dist/server-tools.js +50 -46
  94. package/dist/spawn.d.ts +55 -0
  95. package/dist/spawn.js +72 -0
  96. package/dist/tools/agent-types.d.ts +2 -2
  97. package/dist/tools/agent-types.js +22 -17
  98. package/dist/tools/attachment-info.d.ts +2 -2
  99. package/dist/tools/attachment-info.js +38 -33
  100. package/dist/tools/broadcast.d.ts +2 -2
  101. package/dist/tools/broadcast.js +69 -64
  102. package/dist/tools/cancel-stage.d.ts +2 -2
  103. package/dist/tools/cancel-stage.js +20 -15
  104. package/dist/tools/clear-state.d.ts +2 -2
  105. package/dist/tools/clear-state.js +25 -20
  106. package/dist/tools/coat-check-evict.d.ts +2 -2
  107. package/dist/tools/coat-check-evict.js +29 -24
  108. package/dist/tools/coat-check-get.d.ts +2 -2
  109. package/dist/tools/coat-check-get.js +38 -33
  110. package/dist/tools/coat-check-list.d.ts +2 -2
  111. package/dist/tools/coat-check-list.js +48 -43
  112. package/dist/tools/coat-check-put.d.ts +2 -2
  113. package/dist/tools/coat-check-put.js +38 -33
  114. package/dist/tools/cue.d.ts +2 -2
  115. package/dist/tools/cue.js +57 -52
  116. package/dist/tools/descriptor.d.ts +72 -0
  117. package/dist/tools/descriptor.js +39 -0
  118. package/dist/tools/destroy.d.ts +2 -2
  119. package/dist/tools/destroy.js +153 -148
  120. package/dist/tools/ensemble.d.ts +2 -2
  121. package/dist/tools/ensemble.js +71 -66
  122. package/dist/tools/evaluate-gate.d.ts +2 -2
  123. package/dist/tools/evaluate-gate.js +33 -27
  124. package/dist/tools/fetch-state.d.ts +2 -2
  125. package/dist/tools/fetch-state.js +42 -37
  126. package/dist/tools/gates.d.ts +2 -2
  127. package/dist/tools/gates.js +39 -34
  128. package/dist/tools/hosts.d.ts +2 -2
  129. package/dist/tools/hosts.js +25 -20
  130. package/dist/tools/listen.d.ts +2 -2
  131. package/dist/tools/listen.js +23 -18
  132. package/dist/tools/load-lineup.d.ts +2 -2
  133. package/dist/tools/load-lineup.js +324 -319
  134. package/dist/tools/migrate.d.ts +2 -2
  135. package/dist/tools/migrate.js +45 -40
  136. package/dist/tools/pause.d.ts +2 -2
  137. package/dist/tools/pause.js +34 -29
  138. package/dist/tools/play.d.ts +2 -2
  139. package/dist/tools/play.js +53 -48
  140. package/dist/tools/quality-gate.d.ts +2 -2
  141. package/dist/tools/quality-gate.js +26 -21
  142. package/dist/tools/recall.d.ts +2 -2
  143. package/dist/tools/recall.js +32 -27
  144. package/dist/tools/recruit.d.ts +2 -2
  145. package/dist/tools/recruit.js +325 -256
  146. package/dist/tools/release.d.ts +2 -2
  147. package/dist/tools/release.js +85 -80
  148. package/dist/tools/report.d.ts +2 -2
  149. package/dist/tools/report.js +28 -23
  150. package/dist/tools/reset.d.ts +3 -0
  151. package/dist/tools/reset.js +51 -0
  152. package/dist/tools/restart.d.ts +2 -2
  153. package/dist/tools/restart.js +51 -46
  154. package/dist/tools/restore.d.ts +2 -2
  155. package/dist/tools/restore.js +76 -71
  156. package/dist/tools/save-lineup.d.ts +2 -2
  157. package/dist/tools/save-lineup.js +32 -27
  158. package/dist/tools/save-state.d.ts +2 -2
  159. package/dist/tools/save-state.js +31 -26
  160. package/dist/tools/schedule.d.ts +2 -2
  161. package/dist/tools/schedule.js +133 -128
  162. package/dist/tools/schedules.d.ts +2 -2
  163. package/dist/tools/schedules.js +41 -36
  164. package/dist/tools/set-ensemble-description.d.ts +2 -2
  165. package/dist/tools/set-ensemble-description.js +26 -21
  166. package/dist/tools/set-name.d.ts +2 -2
  167. package/dist/tools/set-name.js +38 -33
  168. package/dist/tools/set-part.d.ts +2 -2
  169. package/dist/tools/set-part.js +20 -15
  170. package/dist/tools/shutdown.d.ts +2 -2
  171. package/dist/tools/shutdown.js +39 -34
  172. package/dist/tools/stage.d.ts +2 -2
  173. package/dist/tools/stage.js +28 -23
  174. package/dist/tools/stages.d.ts +2 -2
  175. package/dist/tools/stages.js +36 -31
  176. package/dist/tools/unschedule.d.ts +2 -2
  177. package/dist/tools/unschedule.js +30 -25
  178. package/dist/tools/who-am-i.d.ts +2 -2
  179. package/dist/tools/who-am-i.js +36 -31
  180. package/dist/tools/worktree.d.ts +2 -2
  181. package/dist/tools/worktree.js +134 -129
  182. package/dist/tui/index.js +6 -6
  183. package/dist/types.d.ts +47 -2
  184. package/dist/types.js +1 -1
  185. package/dist/utils/default-part.js +1 -0
  186. package/dist/utils/sdk-probe.d.ts +23 -0
  187. package/dist/utils/sdk-probe.js +46 -7
  188. package/dist/worker.d.ts +3 -1
  189. package/dist/worker.js +6 -2
  190. package/dist/workflows/session.js +70 -2
  191. package/dist/workflows/signals.d.ts +32 -2
  192. package/dist/workflows/signals.js +25 -2
  193. package/package.json +4 -1
  194. package/workflow-bundle.js +97 -6
  195. package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
  196. package/dist/tools/helpers.d.ts +0 -21
  197. package/dist/tools/helpers.js +0 -25
@@ -0,0 +1,154 @@
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.probeCopilotPiPreflight = probeCopilotPiPreflight;
7
+ /**
8
+ * Pi dependency preflight — mirrors the opencode / claude-api optional-dep gate.
9
+ *
10
+ * `@earendil-works/pi-coding-agent` (and `@earendil-works/pi-ai`) are OPTIONAL
11
+ * dependencies requiring Node 22.19+. The extension only runs INSIDE Pi, so the
12
+ * runtime guarantees Pi is present — this probe exists for the headless path and
13
+ * for a clear, actionable error if someone wires the extension where Pi isn't
14
+ * installed.
15
+ *
16
+ * Uses `probeSdkInstall` (filesystem walk) rather than `require.resolve` because
17
+ * Pi packages ship ESM-only `exports` maps with no CJS-resolvable entry.
18
+ */
19
+ const fs_1 = require("fs");
20
+ const os_1 = require("os");
21
+ const path_1 = require("path");
22
+ const sdk_probe_1 = require("../utils/sdk-probe");
23
+ /** Canonical Pi package (the npm `@mariozechner/...` name is an alias). */
24
+ exports.PI_PACKAGE = '@earendil-works/pi-coding-agent';
25
+ /** Pi's model/typing helpers (`StringEnum`, providers). */
26
+ exports.PI_AI_PACKAGE = '@earendil-works/pi-ai';
27
+ /** Tested-pinned Pi version — drift is a maintainer decision (D6). */
28
+ exports.TESTED_PI_VERSION = '~0.78';
29
+ /**
30
+ * Minimum Pi version the integration requires. 0.78.0 covers the Pi fixes the
31
+ * cue-pump + headless paths rely on (#2860 / #5080 / #5115). Bumping this is a
32
+ * D6 maintainer decision; the headless/Copilot pre-flight hard-fails below it.
33
+ */
34
+ exports.PI_VERSION_FLOOR = '0.78.0';
35
+ /** Node floor imposed by the Pi packages (NOT by typebox or agent-tempo core). */
36
+ exports.PI_NODE_FLOOR = '22.19.0';
37
+ /**
38
+ * Check whether the Pi runtime packages are installed. Returns a structured
39
+ * result so callers choose whether to warn or hard-fail (the extension warns;
40
+ * a headless spawner would hard-fail).
41
+ */
42
+ function probePi() {
43
+ if (!(0, sdk_probe_1.probeSdkInstall)(exports.PI_PACKAGE)) {
44
+ return {
45
+ available: false,
46
+ reason: `${exports.PI_PACKAGE} is not installed.\n` +
47
+ `Install it with: npm install -g ${exports.PI_PACKAGE}\n` +
48
+ `(requires Node >= ${exports.PI_NODE_FLOOR}).`,
49
+ };
50
+ }
51
+ return { available: true };
52
+ }
53
+ /**
54
+ * Pure semver FLOOR check: is `installed` >= `floor`? Compares major, then
55
+ * minor, then patch (a missing patch defaults to 0). Any pre-release/build
56
+ * suffix (`-beta`, `+sha`) is ignored — a pre-release of a version at/above the
57
+ * floor counts as meeting it. An unparseable `installed` returns `false`
58
+ * (conservative: unknown version is treated as below the floor).
59
+ */
60
+ function meetsVersionFloor(installed, floor = exports.PI_VERSION_FLOOR) {
61
+ const parse = (v) => {
62
+ const m = v.trim().match(/^v?(\d+)\.(\d+)(?:\.(\d+))?/);
63
+ if (!m)
64
+ return [-1, -1, -1];
65
+ return [Number(m[1]), Number(m[2]), Number(m[3] ?? 0)];
66
+ };
67
+ const [iMaj, iMin, iPat] = parse(installed);
68
+ if (iMaj < 0)
69
+ return false; // unparseable → below floor
70
+ const [fMaj, fMin, fPat] = parse(floor);
71
+ if (iMaj !== fMaj)
72
+ return iMaj > fMaj;
73
+ if (iMin !== fMin)
74
+ return iMin > fMin;
75
+ return iPat >= fPat;
76
+ }
77
+ /** Default `~/.pi/agent/auth.json` presence check. */
78
+ function defaultAuthFileExists() {
79
+ return (0, fs_1.existsSync)((0, path_1.join)((0, os_1.homedir)(), '.pi', 'agent', 'auth.json'));
80
+ }
81
+ /**
82
+ * Hard pre-flight for recruiting a Copilot-backed Pi player (headless). Three
83
+ * gates, each with an actionable, `force: true`-bypassable error:
84
+ * 1. The Pi optional deps (`@earendil-works/pi-coding-agent` + `pi-ai`) are
85
+ * installed.
86
+ * 2. The Pi SDK version meets {@link PI_VERSION_FLOOR}.
87
+ * 3. Copilot auth is present — either `COPILOT_GITHUB_TOKEN` in the env or a
88
+ * mounted `~/.pi/agent/auth.json`.
89
+ *
90
+ * Mirrors the opencode / claude-code-headless recruit pre-flights. Returns a
91
+ * {@link PiProbeResult} so the caller (recruit) chooses warn vs hard-fail.
92
+ */
93
+ function probeCopilotPiPreflight(deps = {}) {
94
+ const isInstalled = deps.isInstalled ?? ((pkg) => (0, sdk_probe_1.probeSdkInstall)(pkg));
95
+ const installedVersion = deps.installedVersion ?? ((pkg) => (0, sdk_probe_1.readSdkPackageVersion)(pkg));
96
+ const env = deps.env ?? process.env;
97
+ const authFileExists = deps.authFileExists ?? defaultAuthFileExists;
98
+ // 1. Optional Pi deps present.
99
+ const missing = [exports.PI_PACKAGE, exports.PI_AI_PACKAGE].filter((pkg) => !isInstalled(pkg));
100
+ if (missing.length > 0) {
101
+ return {
102
+ available: false,
103
+ reason: `Copilot-via-Pi requires the Pi optional dependencies (missing: ${missing.join(', ')}).\n` +
104
+ `Install with: npm install -g ${exports.PI_PACKAGE} ${exports.PI_AI_PACKAGE} (requires Node >= ${exports.PI_NODE_FLOOR}).\n` +
105
+ `Or recruit with force: true to bypass this pre-flight.`,
106
+ };
107
+ }
108
+ // 2. Version floor.
109
+ const version = installedVersion(exports.PI_PACKAGE);
110
+ if (!version) {
111
+ return {
112
+ available: false,
113
+ reason: `Could not read ${exports.PI_PACKAGE} version to verify the >= ${exports.PI_VERSION_FLOOR} floor.\n` +
114
+ `Reinstall ${exports.PI_PACKAGE}, or recruit with force: true to bypass.`,
115
+ };
116
+ }
117
+ if (!meetsVersionFloor(version, exports.PI_VERSION_FLOOR)) {
118
+ return {
119
+ available: false,
120
+ reason: `${exports.PI_PACKAGE} ${version} is below the required >= ${exports.PI_VERSION_FLOOR} floor ` +
121
+ `(covers Pi #2860/#5080/#5115).\n` +
122
+ `Upgrade with: npm install -g ${exports.PI_PACKAGE}@latest, or recruit with force: true to bypass.`,
123
+ };
124
+ }
125
+ // 3. Copilot auth.
126
+ if (!env.COPILOT_GITHUB_TOKEN && !authFileExists()) {
127
+ return {
128
+ available: false,
129
+ reason: `Copilot auth not found. Set COPILOT_GITHUB_TOKEN, or run \`pi /login\` (GitHub Copilot) ` +
130
+ `to write ~/.pi/agent/auth.json.\n` +
131
+ `Or recruit with force: true to bypass this pre-flight.`,
132
+ };
133
+ }
134
+ // 4. Model indexability (catch-early; runs only when BOTH the requested model
135
+ // and a resolver are supplied — A4 injects pi-ai's getModel). Pi's Copilot
136
+ // model set is compiled at build time from models.dev, so a model on the
137
+ // user's subscription but unindexed resolves to `undefined` (NOT a throw,
138
+ // NOT null — empirically confirmed). Fail the recruit cleanly here rather
139
+ // than spawning a process getModel kills at the headless entry. A4's
140
+ // getModel backstop covers the skip cases (no resolver / session-only).
141
+ if (deps.requestedModel && deps.resolveModel) {
142
+ const { provider, model } = deps.requestedModel;
143
+ if (deps.resolveModel(provider, model) === undefined) {
144
+ return {
145
+ available: false,
146
+ reason: `model "${provider}/${model}" is not in Pi's model index (getModel returned undefined).\n` +
147
+ `Run \`pi --list-models\` for valid ids; the Copilot set is compiled from models.dev, ` +
148
+ `so a model on your subscription but unindexed is rejected (Pi #4599/#2891).\n` +
149
+ `Or recruit with force: true to bypass this pre-flight.`,
150
+ };
151
+ }
152
+ }
153
+ return { available: true };
154
+ }
@@ -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,51 @@
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
+ execute: async (args) => toPiResult(await d.handler(args)),
49
+ });
50
+ }
51
+ }
@@ -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
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResetPump = void 0;
4
+ const DEFAULT_POLL_MS = 1_000;
5
+ const log = (...args) => {
6
+ // eslint-disable-next-line no-console
7
+ console.error('[agent-tempo:pi]', ...args);
8
+ };
9
+ class ResetPump {
10
+ source;
11
+ resolveSession;
12
+ intervalMs;
13
+ timer = null;
14
+ draining = false;
15
+ constructor(opts) {
16
+ this.source = opts.source;
17
+ this.resolveSession = opts.resolveSession;
18
+ this.intervalMs = opts.intervalMs ?? DEFAULT_POLL_MS;
19
+ }
20
+ start() {
21
+ if (this.timer)
22
+ return;
23
+ this.timer = setInterval(() => {
24
+ this.tick().catch((err) => log('reset-pump tick failed:', err));
25
+ }, this.intervalMs);
26
+ if (typeof this.timer.unref === 'function')
27
+ this.timer.unref();
28
+ }
29
+ stop() {
30
+ if (this.timer) {
31
+ clearInterval(this.timer);
32
+ this.timer = null;
33
+ }
34
+ }
35
+ /**
36
+ * One poll cycle: fetch the pending reset; if present + a live session is
37
+ * attached, perform the wipe and ack. Re-entrancy guarded so a slow tick never
38
+ * overlaps the next interval. Public for unit tests to drive directly.
39
+ */
40
+ async tick() {
41
+ if (this.draining)
42
+ return;
43
+ this.draining = true;
44
+ try {
45
+ const pr = await this.source.fetchPendingReset();
46
+ if (!pr)
47
+ return;
48
+ const session = this.resolveSession();
49
+ if (!session)
50
+ return; // no live session yet — leave it pending; next tick retries
51
+ await this.performReset(session, pr);
52
+ await this.source.ackReset(pr.resetId);
53
+ }
54
+ finally {
55
+ this.draining = false;
56
+ }
57
+ }
58
+ /** Wipe (D14 clean-wipe) + deliver the "context wiped" notice. */
59
+ async performReset(session, pr) {
60
+ if (!pr.fresh) {
61
+ // D14: reset is clean-wipe ONLY. A seeded reset would be restart+loadFromState
62
+ // (not this path). Don't guess — log + fall through to ack (clear the slot).
63
+ log(`reset ${pr.resetId}: fresh=false — no wipe (seeded reset is restart's job)`);
64
+ return;
65
+ }
66
+ if (typeof session.newSession !== 'function') {
67
+ log(`reset ${pr.resetId}: session.newSession() unavailable — skipping wipe (will still ack)`);
68
+ return;
69
+ }
70
+ await session.newSession(); // clean-wipe: fresh context, no replay
71
+ const by = pr.requestedBy ? ` (requested by ${pr.requestedBy})` : '';
72
+ const notice = `[reset] context wiped — fresh start${by}.${pr.reason ? ` reason: ${pr.reason}` : ''}`;
73
+ log(notice);
74
+ // Surface the notice INTO the fresh session (after the wipe, so it survives),
75
+ // non-triggering so it doesn't kick off an unsolicited turn — the agent reads
76
+ // it on its next cue.
77
+ try {
78
+ await session.sendCustomMessage({ customType: 'system', content: notice, display: true }, { deliverAs: 'followUp', triggerTurn: false });
79
+ }
80
+ catch (err) {
81
+ log(`reset ${pr.resetId}: notice injection failed (non-fatal):`, err);
82
+ }
83
+ }
84
+ }
85
+ exports.ResetPump = ResetPump;
@@ -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
+ }