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.
- package/CLAUDE.md +253 -219
- package/LICENSE +21 -21
- package/README.md +293 -289
- package/assets/icon-dark.svg +9 -9
- package/assets/icon.svg +9 -9
- package/assets/logo-dark.svg +11 -11
- package/assets/logo-light.svg +11 -11
- package/dashboard/README.md +91 -91
- package/dashboard/dist/assets/{index-D6Xyje_n.js → index-jmYe6rmS.js} +2 -2
- package/dashboard/dist/assets/index-jmYe6rmS.js.map +1 -0
- package/dashboard/dist/index.html +20 -20
- package/dashboard/package.json +47 -47
- package/dist/activities/outbox.d.ts +30 -1
- package/dist/activities/outbox.js +96 -3
- package/dist/adapters/base.js +5 -0
- package/dist/adapters/copilot/adapter.js +12 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +7 -0
- package/dist/adapters/pi/adapter.d.ts +2 -0
- package/dist/adapters/pi/adapter.js +43 -0
- package/dist/adapters/pi/index.d.ts +16 -0
- package/dist/adapters/pi/index.js +10 -0
- package/dist/cli/global-wrapper.d.ts +19 -0
- package/dist/cli/global-wrapper.js +169 -0
- package/dist/cli/help-text.js +97 -97
- package/dist/cli/startup.js +11 -0
- package/dist/cli/upgrade-command.js +81 -81
- package/dist/cli.js +12 -0
- package/dist/client/core.js +9 -2
- package/dist/client/interface.d.ts +6 -0
- package/dist/config.d.ts +79 -0
- package/dist/config.js +74 -0
- package/dist/daemon.js +37 -1
- package/dist/http/aggregate.d.ts +22 -1
- package/dist/http/aggregate.js +41 -0
- package/dist/http/auth.d.ts +94 -8
- package/dist/http/auth.js +93 -9
- package/dist/http/body.d.ts +4 -1
- package/dist/http/body.js +6 -3
- package/dist/http/event-bus.js +1 -0
- package/dist/http/event-types.d.ts +34 -2
- package/dist/http/event-types.js +1 -0
- package/dist/http/gate-audit.d.ts +12 -0
- package/dist/http/gate-audit.js +95 -0
- package/dist/http/gate-registry.d.ts +167 -0
- package/dist/http/gate-registry.js +163 -0
- package/dist/http/gate-routes.d.ts +48 -0
- package/dist/http/gate-routes.js +102 -0
- package/dist/http/ingest-registry.d.ts +30 -0
- package/dist/http/ingest-registry.js +108 -0
- package/dist/http/inner-loop-routes.d.ts +66 -0
- package/dist/http/inner-loop-routes.js +182 -0
- package/dist/http/inner-loop.d.ts +92 -0
- package/dist/http/inner-loop.js +155 -0
- package/dist/http/server.d.ts +38 -3
- package/dist/http/server.js +211 -6
- package/dist/http/snapshot.d.ts +6 -0
- package/dist/http/snapshot.js +6 -0
- package/dist/pi/cue-pump.d.ts +61 -0
- package/dist/pi/cue-pump.js +95 -0
- package/dist/pi/extension.d.ts +45 -0
- package/dist/pi/extension.js +407 -0
- package/dist/pi/gate-client.d.ts +54 -0
- package/dist/pi/gate-client.js +136 -0
- package/dist/pi/headless.d.ts +85 -0
- package/dist/pi/headless.js +224 -0
- package/dist/pi/index.d.ts +28 -0
- package/dist/pi/index.js +43 -0
- package/dist/pi/inner-loop-client.d.ts +67 -0
- package/dist/pi/inner-loop-client.js +164 -0
- package/dist/pi/inner-loop-publisher.d.ts +187 -0
- package/dist/pi/inner-loop-publisher.js +236 -0
- package/dist/pi/lazy-proxy.d.ts +37 -0
- package/dist/pi/lazy-proxy.js +55 -0
- package/dist/pi/mission-control/actions.d.ts +48 -0
- package/dist/pi/mission-control/actions.js +98 -0
- package/dist/pi/mission-control/board.d.ts +53 -0
- package/dist/pi/mission-control/board.js +104 -0
- package/dist/pi/mission-control/extension.d.ts +44 -0
- package/dist/pi/mission-control/extension.js +251 -0
- package/dist/pi/mission-control/index.d.ts +15 -0
- package/dist/pi/mission-control/index.js +32 -0
- package/dist/pi/mission-control/inner-tail.d.ts +48 -0
- package/dist/pi/mission-control/inner-tail.js +76 -0
- package/dist/pi/mission-control/pi-ui.d.ts +43 -0
- package/dist/pi/mission-control/pi-ui.js +10 -0
- package/dist/pi/mission-control/render.d.ts +6 -0
- package/dist/pi/mission-control/render.js +95 -0
- package/dist/pi/phase-driver.d.ts +74 -0
- package/dist/pi/phase-driver.js +122 -0
- package/dist/pi/pi-types.d.ts +208 -0
- package/dist/pi/pi-types.js +21 -0
- package/dist/pi/probe.d.ts +80 -0
- package/dist/pi/probe.js +154 -0
- package/dist/pi/render-tools.d.ts +17 -0
- package/dist/pi/render-tools.js +51 -0
- package/dist/pi/reset-pump.d.ts +47 -0
- package/dist/pi/reset-pump.js +85 -0
- package/dist/pi/tool-capability.d.ts +60 -0
- package/dist/pi/tool-capability.js +156 -0
- package/dist/pi/workflow-client.d.ts +158 -0
- package/dist/pi/workflow-client.js +289 -0
- package/dist/pi/zod-to-typebox.d.ts +74 -0
- package/dist/pi/zod-to-typebox.js +191 -0
- package/dist/scripts/verify-daemon-isolation-guard.js +24 -24
- package/dist/server-tools.d.ts +2 -0
- package/dist/server-tools.js +50 -46
- package/dist/server.js +4 -0
- package/dist/spawn.d.ts +55 -0
- package/dist/spawn.js +84 -12
- package/dist/tools/agent-types.d.ts +2 -2
- package/dist/tools/agent-types.js +22 -17
- package/dist/tools/attachment-info.d.ts +2 -2
- package/dist/tools/attachment-info.js +38 -33
- package/dist/tools/broadcast.d.ts +2 -2
- package/dist/tools/broadcast.js +69 -64
- package/dist/tools/cancel-stage.d.ts +2 -2
- package/dist/tools/cancel-stage.js +20 -15
- package/dist/tools/clear-state.d.ts +2 -2
- package/dist/tools/clear-state.js +25 -20
- package/dist/tools/coat-check-evict.d.ts +2 -2
- package/dist/tools/coat-check-evict.js +30 -25
- package/dist/tools/coat-check-get.d.ts +2 -2
- package/dist/tools/coat-check-get.js +39 -34
- package/dist/tools/coat-check-list.d.ts +2 -2
- package/dist/tools/coat-check-list.js +48 -43
- package/dist/tools/coat-check-put.d.ts +2 -2
- package/dist/tools/coat-check-put.js +41 -36
- package/dist/tools/cue.d.ts +2 -2
- package/dist/tools/cue.js +57 -52
- package/dist/tools/descriptor.d.ts +72 -0
- package/dist/tools/descriptor.js +39 -0
- package/dist/tools/destroy.d.ts +2 -2
- package/dist/tools/destroy.js +153 -148
- package/dist/tools/ensemble.d.ts +2 -2
- package/dist/tools/ensemble.js +71 -66
- package/dist/tools/evaluate-gate.d.ts +2 -2
- package/dist/tools/evaluate-gate.js +33 -27
- package/dist/tools/fetch-state.d.ts +2 -2
- package/dist/tools/fetch-state.js +43 -38
- package/dist/tools/gates.d.ts +2 -2
- package/dist/tools/gates.js +39 -34
- package/dist/tools/hosts.d.ts +2 -2
- package/dist/tools/hosts.js +25 -20
- package/dist/tools/listen.d.ts +2 -2
- package/dist/tools/listen.js +23 -18
- package/dist/tools/load-lineup.d.ts +2 -2
- package/dist/tools/load-lineup.js +324 -319
- package/dist/tools/migrate.d.ts +2 -2
- package/dist/tools/migrate.js +45 -40
- package/dist/tools/pause.d.ts +2 -2
- package/dist/tools/pause.js +34 -29
- package/dist/tools/play.d.ts +2 -2
- package/dist/tools/play.js +53 -48
- package/dist/tools/quality-gate.d.ts +2 -2
- package/dist/tools/quality-gate.js +26 -21
- package/dist/tools/recall.d.ts +2 -2
- package/dist/tools/recall.js +32 -27
- package/dist/tools/recruit.d.ts +2 -2
- package/dist/tools/recruit.js +325 -256
- package/dist/tools/release.d.ts +2 -2
- package/dist/tools/release.js +85 -80
- package/dist/tools/report.d.ts +2 -2
- package/dist/tools/report.js +28 -23
- package/dist/tools/reset.d.ts +3 -0
- package/dist/tools/reset.js +51 -0
- package/dist/tools/restart.d.ts +2 -2
- package/dist/tools/restart.js +51 -46
- package/dist/tools/restore.d.ts +2 -2
- package/dist/tools/restore.js +76 -71
- package/dist/tools/save-lineup.d.ts +2 -2
- package/dist/tools/save-lineup.js +32 -27
- package/dist/tools/save-state.d.ts +2 -2
- package/dist/tools/save-state.js +43 -38
- package/dist/tools/schedule.d.ts +2 -2
- package/dist/tools/schedule.js +133 -128
- package/dist/tools/schedules.d.ts +2 -2
- package/dist/tools/schedules.js +41 -36
- package/dist/tools/set-ensemble-description.d.ts +2 -2
- package/dist/tools/set-ensemble-description.js +26 -21
- package/dist/tools/set-name.d.ts +2 -2
- package/dist/tools/set-name.js +38 -33
- package/dist/tools/set-part.d.ts +2 -2
- package/dist/tools/set-part.js +20 -15
- package/dist/tools/shutdown.d.ts +2 -2
- package/dist/tools/shutdown.js +39 -34
- package/dist/tools/stage.d.ts +2 -2
- package/dist/tools/stage.js +28 -23
- package/dist/tools/stages.d.ts +2 -2
- package/dist/tools/stages.js +36 -31
- package/dist/tools/unschedule.d.ts +2 -2
- package/dist/tools/unschedule.js +30 -25
- package/dist/tools/who-am-i.d.ts +2 -2
- package/dist/tools/who-am-i.js +36 -31
- package/dist/tools/worktree.d.ts +2 -2
- package/dist/tools/worktree.js +134 -129
- package/dist/tui/index.js +6 -6
- package/dist/types.d.ts +47 -2
- package/dist/types.js +1 -1
- package/dist/utils/default-part.js +1 -0
- package/dist/utils/grpc-shutdown-guard.d.ts +52 -0
- package/dist/utils/grpc-shutdown-guard.js +88 -0
- package/dist/utils/sdk-probe.d.ts +23 -0
- package/dist/utils/sdk-probe.js +46 -7
- package/dist/worker.d.ts +3 -1
- package/dist/worker.js +6 -2
- package/dist/workflows/session.js +70 -2
- package/dist/workflows/signals.d.ts +32 -2
- package/dist/workflows/signals.js +25 -2
- package/examples/agents/tempo-composer.md +56 -56
- package/examples/agents/tempo-conductor.md +117 -117
- package/examples/agents/tempo-critic.md +73 -73
- package/examples/agents/tempo-improv.md +74 -74
- package/examples/agents/tempo-liner.md +75 -75
- package/examples/agents/tempo-roadie.md +61 -61
- package/examples/agents/tempo-soloist.md +71 -71
- package/examples/agents/tempo-tuner.md +94 -94
- package/examples/ensembles/tempo-big-band.yaml +146 -146
- package/examples/ensembles/tempo-dev-team.yaml +58 -58
- package/examples/ensembles/tempo-headless-jam.yaml +77 -77
- package/examples/ensembles/tempo-jam-session.yaml +41 -41
- package/examples/ensembles/tempo-mock-jam.yaml +79 -79
- package/examples/ensembles/tempo-review-squad.yaml +32 -32
- package/package.json +176 -173
- package/packaging/launchd/com.agent.tempo.plist +46 -46
- package/packaging/systemd/agent-tempo.service +32 -32
- package/packaging/windows/install-task.ps1 +71 -71
- package/scenarios/conductor-recruit-mock.yaml +33 -33
- package/scenarios/echo-roundtrip.yaml +15 -15
- package/scenarios/multi-player-handoff.yaml +38 -38
- package/scenarios/recruit-cascade.yaml +38 -38
- package/scenarios/two-player-conversation.yaml +33 -33
- package/workflow-bundle.js +97 -6
- package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
- package/dist/activities/claude-stop.d.ts +0 -21
- package/dist/activities/claude-stop.js +0 -94
- package/dist/channel.d.ts +0 -3
- package/dist/channel.js +0 -48
- package/dist/copilot-bridge.d.ts +0 -22
- package/dist/copilot-bridge.js +0 -565
- package/dist/scripts/258-spotcheck.js +0 -303
- package/dist/tools/detach.d.ts +0 -4
- package/dist/tools/detach.js +0 -45
- package/dist/tools/encore.d.ts +0 -4
- package/dist/tools/encore.js +0 -31
- package/dist/tools/helpers.d.ts +0 -21
- package/dist/tools/helpers.js +0 -25
- package/dist/tools/pause-ensemble.d.ts +0 -4
- package/dist/tools/pause-ensemble.js +0 -58
- package/dist/tools/resume-ensemble.d.ts +0 -4
- package/dist/tools/resume-ensemble.js +0 -79
- package/dist/tools/stop.d.ts +0 -4
- package/dist/tools/stop.js +0 -29
- package/dist/tui/client.d.ts +0 -6
- package/dist/tui/client.js +0 -9
- package/dist/tui/components/ActivityLog.d.ts +0 -16
- package/dist/tui/components/ActivityLog.js +0 -36
- package/dist/tui/components/CommandOverlay.d.ts +0 -15
- package/dist/tui/components/CommandOverlay.js +0 -34
- package/dist/tui/components/ConductorChat.d.ts +0 -16
- package/dist/tui/components/ConductorChat.js +0 -32
- package/dist/tui/components/EnsembleListView.d.ts +0 -14
- package/dist/tui/components/EnsembleListView.js +0 -32
- package/dist/tui/components/EnsemblePanel.d.ts +0 -12
- package/dist/tui/components/EnsemblePanel.js +0 -40
- package/dist/tui/components/InputBar.d.ts +0 -13
- package/dist/tui/components/InputBar.js +0 -58
- package/dist/tui/components/ScheduleOverlay.d.ts +0 -13
- package/dist/tui/components/ScheduleOverlay.js +0 -113
- package/dist/tui/components/TopBar.d.ts +0 -12
- package/dist/tui/components/TopBar.js +0 -15
- package/dist/tui/core-api.d.ts +0 -26
- package/dist/tui/core-api.js +0 -67
- package/dist/tui/hooks/useEnsembleDiscovery.d.ts +0 -3
- package/dist/tui/hooks/useEnsembleDiscovery.js +0 -30
- package/dist/tui/hooks/useMaestroPoller.d.ts +0 -3
- package/dist/tui/hooks/useMaestroPoller.js +0 -36
- package/dist/tui/hooks/useSendCommand.d.ts +0 -7
- package/dist/tui/hooks/useSendCommand.js +0 -29
- package/dist/utils/bg-preflight.d.ts +0 -25
- package/dist/utils/bg-preflight.js +0 -154
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GateClient = exports.INGEST_TOKEN_ENV = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Gate poll-bridge client (3d / MD-G) — the SUBPROCESS side of the operator gate.
|
|
6
|
+
*
|
|
7
|
+
* When the Pi `tool_call` handler engages the gate, it awaits {@link
|
|
8
|
+
* GateClient.awaitDecision}, which polls the daemon's resolution route
|
|
9
|
+
* (`GET /v1/players/:e/:p/gate/:requestId/resolution`, ingest-token auth, same
|
|
10
|
+
* loopback boundary as the 3c inner-loop) until the operator decides OR the
|
|
11
|
+
* daemon's authoritative 45s auto-allow lands. R1 (resolved): Pi awaits the
|
|
12
|
+
* returned `tool_call` Promise, so this inline poll-await is safe.
|
|
13
|
+
*
|
|
14
|
+
* TWO bounds, BOTH required (architect/conductor):
|
|
15
|
+
* - `signal` (Pi's `ctx.signal`): if the turn is cancelled (Esc/abort), the
|
|
16
|
+
* poll stops immediately and resolves `allow` (the tool won't run anyway —
|
|
17
|
+
* never leave the loop hung; Pi #2381).
|
|
18
|
+
* - `timeoutMs` (default just beyond the daemon's 45s): a safety net for an
|
|
19
|
+
* UNREACHABLE daemon — autonomous-first, so a timeout resolves `allow`.
|
|
20
|
+
* In the normal case the daemon answers first (operator decision, or its lazy
|
|
21
|
+
* 45s auto-allow), so this subprocess deadline is only the daemon-down backstop.
|
|
22
|
+
*
|
|
23
|
+
* Effect mapping: operator `deny` → `deny` (block the tool); `allow` /
|
|
24
|
+
* `auto-allow` / timeout / abort → `allow` (permit). Network/transport errors on
|
|
25
|
+
* a single poll are swallowed (retry next tick); only the bounds end the loop.
|
|
26
|
+
*
|
|
27
|
+
* Client-side ONLY (src/pi). Not imported by workflows.
|
|
28
|
+
*/
|
|
29
|
+
const port_file_1 = require("../http/port-file");
|
|
30
|
+
/** Env var carrying the per-player ingest token (threaded in at spawn, shared w/ inner-loop). */
|
|
31
|
+
exports.INGEST_TOKEN_ENV = 'AGENT_TEMPO_INGEST_TOKEN';
|
|
32
|
+
/** Default poll cadence — reuse the inner-loop short-poll so a fresh decision lands within ~1s. */
|
|
33
|
+
const DEFAULT_POLL_MS = 1_000;
|
|
34
|
+
/**
|
|
35
|
+
* Subprocess deadline — slightly beyond the daemon's 45s auto-allow so, when the
|
|
36
|
+
* daemon is reachable, its authoritative answer always wins; this only fires when
|
|
37
|
+
* the daemon is unreachable.
|
|
38
|
+
*/
|
|
39
|
+
const DEFAULT_TIMEOUT_MS = 50_000;
|
|
40
|
+
const log = (...args) => {
|
|
41
|
+
// eslint-disable-next-line no-console
|
|
42
|
+
console.error('[agent-tempo:pi]', ...args);
|
|
43
|
+
};
|
|
44
|
+
function resolveFetch() {
|
|
45
|
+
const g = globalThis.fetch;
|
|
46
|
+
return typeof g === 'function' ? g : null;
|
|
47
|
+
}
|
|
48
|
+
/** Default cancellable sleep — resolves after `ms`, or early if `signal` aborts. */
|
|
49
|
+
function defaultSleep(ms, signal) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
if (signal?.aborted) {
|
|
52
|
+
resolve();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(); }, ms);
|
|
56
|
+
const onAbort = () => { clearTimeout(timer); resolve(); };
|
|
57
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Loopback poll-bridge to the daemon gate. Construct one per headless player.
|
|
62
|
+
* The `awaitDecision` poll is the only public surface used by the engagement path.
|
|
63
|
+
*/
|
|
64
|
+
class GateClient {
|
|
65
|
+
ensemble;
|
|
66
|
+
playerId;
|
|
67
|
+
ingestToken;
|
|
68
|
+
readPort;
|
|
69
|
+
fetchFn;
|
|
70
|
+
pollIntervalMs;
|
|
71
|
+
timeoutMs;
|
|
72
|
+
now;
|
|
73
|
+
sleep;
|
|
74
|
+
constructor(opts) {
|
|
75
|
+
this.ensemble = opts.ensemble;
|
|
76
|
+
this.playerId = opts.playerId;
|
|
77
|
+
this.ingestToken = opts.ingestToken ?? process.env[exports.INGEST_TOKEN_ENV];
|
|
78
|
+
this.readPort = opts.readPort ?? (() => (0, port_file_1.readPortFile)());
|
|
79
|
+
this.fetchFn = opts.fetchFn ?? resolveFetch();
|
|
80
|
+
this.pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
81
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
82
|
+
this.now = opts.now ?? Date.now;
|
|
83
|
+
this.sleep = opts.sleep ?? defaultSleep;
|
|
84
|
+
}
|
|
85
|
+
get enabled() {
|
|
86
|
+
return Boolean(this.ingestToken) && this.fetchFn !== null;
|
|
87
|
+
}
|
|
88
|
+
resolutionUrl(port, requestId) {
|
|
89
|
+
return `http://127.0.0.1:${port}/v1/players/` +
|
|
90
|
+
`${encodeURIComponent(this.ensemble)}/${encodeURIComponent(this.playerId)}/gate/` +
|
|
91
|
+
`${encodeURIComponent(requestId)}/resolution`;
|
|
92
|
+
}
|
|
93
|
+
/** One poll. Returns the effect on a resolved answer, or null to keep polling. */
|
|
94
|
+
async pollOnce(requestId) {
|
|
95
|
+
const port = this.readPort();
|
|
96
|
+
if (port == null)
|
|
97
|
+
return null; // daemon HTTP down → keep trying until the bound
|
|
98
|
+
try {
|
|
99
|
+
const res = await this.fetchFn(this.resolutionUrl(port, requestId), {
|
|
100
|
+
method: 'GET',
|
|
101
|
+
headers: { 'X-Ingest-Token': this.ingestToken },
|
|
102
|
+
});
|
|
103
|
+
if (res.status !== 200)
|
|
104
|
+
return null; // 404 (not-yet-registered race) / 403 → retry
|
|
105
|
+
const data = (await res.json());
|
|
106
|
+
if (data.status !== 'resolved')
|
|
107
|
+
return null;
|
|
108
|
+
return data.decision === 'deny' ? 'deny' : 'allow'; // allow + auto-allow → allow
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null; // transport error → retry next tick
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Poll the daemon for the operator's decision on `requestId`, blocking until
|
|
116
|
+
* resolved / timeout / abort. FAIL-OPEN: `allow` unless the operator explicitly
|
|
117
|
+
* denied. Without a token/transport (e.g. interactive Pi, daemon HTTP off) →
|
|
118
|
+
* immediate `allow` (the gate is a daemon-mediated feature).
|
|
119
|
+
*/
|
|
120
|
+
async awaitDecision(requestId, opts = {}) {
|
|
121
|
+
if (!this.enabled)
|
|
122
|
+
return 'allow';
|
|
123
|
+
const deadline = this.now() + (opts.timeoutMs ?? this.timeoutMs);
|
|
124
|
+
while (this.now() < deadline) {
|
|
125
|
+
if (opts.signal?.aborted)
|
|
126
|
+
return 'allow'; // turn cancelled — don't block a dying turn
|
|
127
|
+
const effect = await this.pollOnce(requestId);
|
|
128
|
+
if (effect !== null)
|
|
129
|
+
return effect;
|
|
130
|
+
await this.sleep(this.pollIntervalMs, opts.signal);
|
|
131
|
+
}
|
|
132
|
+
log(`gate decision timed out for ${requestId} (daemon unreachable?) — auto-allow`);
|
|
133
|
+
return 'allow'; // autonomous-first backstop (daemon-down)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.GateClient = GateClient;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless Pi runtime (Phase 3a). Spawned by the daemon (spawnPiHeadless →
|
|
3
|
+
* src/adapters/pi/adapter.ts) for a recruited `agent: 'pi'` player. No human, no
|
|
4
|
+
* terminal: it constructs Pi's `createAgentSession` with the agent-tempo
|
|
5
|
+
* extension injected INLINE, and the module-scope extension singleton
|
|
6
|
+
* (createPiExtension, mode='headless') owns claim/heartbeat/tool-registration/
|
|
7
|
+
* cue-pump on `session_start`. Reuses ~everything from Phases 1–2.
|
|
8
|
+
*
|
|
9
|
+
* Lifecycle:
|
|
10
|
+
* 1. probe Pi SDK; resolve the model via pi-ai `getModel` — a bad/unindexed
|
|
11
|
+
* model fails CLEAN (exit before attach, no orphan — architect's backstop).
|
|
12
|
+
* 2. createAgentSession({ resourceLoader: DefaultResourceLoader({
|
|
13
|
+
* extensionFactories: [ext] }), model? }).
|
|
14
|
+
* 3. await session.bindExtensions({}) → fires session_start → the singleton
|
|
15
|
+
* attaches (claim + heartbeat + tools + cue pump). bindExtensions IS the
|
|
16
|
+
* explicit bootstrap (not "hope session_start fires").
|
|
17
|
+
* 4. stay alive until a shutdown signal (SIGTERM/SIGINT).
|
|
18
|
+
* 5. RELIABLE detach (headless owns the exit): detachAllPiRuntimesForExit()
|
|
19
|
+
* [await adapterExited] → session.dispose() → process exit.
|
|
20
|
+
*
|
|
21
|
+
* ESM note: the Pi SDK is an ESM-only optional dep; we import it via a
|
|
22
|
+
* `Function`-wrapped dynamic `import()` so tsc (module=commonjs) doesn't
|
|
23
|
+
* downlevel it to `require()` — Node resolves the real ESM module at runtime.
|
|
24
|
+
*
|
|
25
|
+
* Determinism boundary: client-side only.
|
|
26
|
+
*/
|
|
27
|
+
import { type Config } from '../config';
|
|
28
|
+
import { type PiToolAccess } from './extension';
|
|
29
|
+
export interface RunHeadlessPiOptions {
|
|
30
|
+
config?: Config;
|
|
31
|
+
toolAccess?: PiToolAccess;
|
|
32
|
+
/** `provider/model` selector; absent → Pi default. */
|
|
33
|
+
model?: string;
|
|
34
|
+
/** Restart-resume: prior Pi conversation id (A4 wires SessionManager). */
|
|
35
|
+
continueSessionId?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build the `DefaultResourceLoader` options for a headless Pi player.
|
|
39
|
+
*
|
|
40
|
+
* SECURITY — S2 (MD-C deny-list soundness). The `restricted` tool gate is a
|
|
41
|
+
* DENY-LIST over shell/exec tool *names* (tool-capability.ts EXEC_TOOLS, via
|
|
42
|
+
* `classify(name) === 'exec'` — F1 replaced extension.ts's former local set). That
|
|
43
|
+
* guarantee — "restricted = no host execution" — holds ONLY IF no third-party
|
|
44
|
+
* extension can register an un-blacklisted execution tool (e.g. a custom
|
|
45
|
+
* `python` / `npm` / `run` tool). It therefore depends on a hard structural
|
|
46
|
+
* fact: which extensions Pi loads.
|
|
47
|
+
*
|
|
48
|
+
* Verified against the installed Pi SDK 0.78 source (NOT assumed):
|
|
49
|
+
* - `DefaultResourceLoader.reload()` (resource-loader.js:271-276) builds
|
|
50
|
+
* `extensionPaths = noExtensions ? cliEnabledExtensions
|
|
51
|
+
* : merge(cliEnabledExtensions, enabledExtensions)`
|
|
52
|
+
* where `enabledExtensions` (line 229) are the DISK/package extensions from
|
|
53
|
+
* `packageManager.resolve()` (`~/.pi/agent/extensions/`, `<cwd>/.pi/extensions/`,
|
|
54
|
+
* installed packages). `loadExtensions(extensionPaths)` then loads them and
|
|
55
|
+
* MERGES with our inline factories (lines 274-276).
|
|
56
|
+
* - `noExtensions` defaults to `false` (constructor, line 132) — so the naive
|
|
57
|
+
* loader DOES load disk extensions. That is the S2 gap.
|
|
58
|
+
*
|
|
59
|
+
* Fix (= security's "exclude the extensions dir", done structurally):
|
|
60
|
+
* - `noExtensions: true` → `extensionPaths` collapses to `cliEnabledExtensions`,
|
|
61
|
+
* which is empty because we pass NO `additionalExtensionPaths`. So
|
|
62
|
+
* `loadExtensions([])` registers nothing from disk/packages.
|
|
63
|
+
* - Inline `extensionFactories` load UNCONDITIONALLY (reload() line 275 is not
|
|
64
|
+
* gated by `noExtensions`), so our agent-tempo extension still attaches.
|
|
65
|
+
* Net: the ONLY tools present are Pi's built-ins (bash/read/edit/write/grep —
|
|
66
|
+
* all covered by the deny-list) + our agent-tempo MCP tools (no exec). No
|
|
67
|
+
* third-party tool can slip past the deny-list. Skills/prompts/themes cannot
|
|
68
|
+
* register tools, so they are not a vector and are left at defaults.
|
|
69
|
+
*
|
|
70
|
+
* Kept as a pure, exported helper so the `noExtensions: true` invariant has a
|
|
71
|
+
* unit regression test (test/pi-headless-loader.test.ts) without needing the Pi
|
|
72
|
+
* SDK installed.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildPiResourceLoaderOptions(params: {
|
|
75
|
+
cwd: string;
|
|
76
|
+
agentDir: string;
|
|
77
|
+
/** The inline agent-tempo extension factory (`createPiExtension(...)`). Typed
|
|
78
|
+
* with a bottom param so any concrete factory arity is assignable. */
|
|
79
|
+
extensionFactory: (pi: never) => void;
|
|
80
|
+
}): Record<string, unknown>;
|
|
81
|
+
/**
|
|
82
|
+
* Boot + run a headless Pi player until shutdown. Resolves when the process has
|
|
83
|
+
* cleanly detached + disposed (it also calls process.exit on the terminal path).
|
|
84
|
+
*/
|
|
85
|
+
export declare function runHeadlessPi(opts?: RunHeadlessPiOptions): Promise<void>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildPiResourceLoaderOptions = buildPiResourceLoaderOptions;
|
|
4
|
+
exports.runHeadlessPi = runHeadlessPi;
|
|
5
|
+
/**
|
|
6
|
+
* Headless Pi runtime (Phase 3a). Spawned by the daemon (spawnPiHeadless →
|
|
7
|
+
* src/adapters/pi/adapter.ts) for a recruited `agent: 'pi'` player. No human, no
|
|
8
|
+
* terminal: it constructs Pi's `createAgentSession` with the agent-tempo
|
|
9
|
+
* extension injected INLINE, and the module-scope extension singleton
|
|
10
|
+
* (createPiExtension, mode='headless') owns claim/heartbeat/tool-registration/
|
|
11
|
+
* cue-pump on `session_start`. Reuses ~everything from Phases 1–2.
|
|
12
|
+
*
|
|
13
|
+
* Lifecycle:
|
|
14
|
+
* 1. probe Pi SDK; resolve the model via pi-ai `getModel` — a bad/unindexed
|
|
15
|
+
* model fails CLEAN (exit before attach, no orphan — architect's backstop).
|
|
16
|
+
* 2. createAgentSession({ resourceLoader: DefaultResourceLoader({
|
|
17
|
+
* extensionFactories: [ext] }), model? }).
|
|
18
|
+
* 3. await session.bindExtensions({}) → fires session_start → the singleton
|
|
19
|
+
* attaches (claim + heartbeat + tools + cue pump). bindExtensions IS the
|
|
20
|
+
* explicit bootstrap (not "hope session_start fires").
|
|
21
|
+
* 4. stay alive until a shutdown signal (SIGTERM/SIGINT).
|
|
22
|
+
* 5. RELIABLE detach (headless owns the exit): detachAllPiRuntimesForExit()
|
|
23
|
+
* [await adapterExited] → session.dispose() → process exit.
|
|
24
|
+
*
|
|
25
|
+
* ESM note: the Pi SDK is an ESM-only optional dep; we import it via a
|
|
26
|
+
* `Function`-wrapped dynamic `import()` so tsc (module=commonjs) doesn't
|
|
27
|
+
* downlevel it to `require()` — Node resolves the real ESM module at runtime.
|
|
28
|
+
*
|
|
29
|
+
* Determinism boundary: client-side only.
|
|
30
|
+
*/
|
|
31
|
+
const config_1 = require("../config");
|
|
32
|
+
const sdk_probe_1 = require("../utils/sdk-probe");
|
|
33
|
+
const extension_1 = require("./extension");
|
|
34
|
+
const probe_1 = require("./probe");
|
|
35
|
+
const log = (...args) => {
|
|
36
|
+
// eslint-disable-next-line no-console
|
|
37
|
+
console.error('[agent-tempo:pi-headless]', ...args);
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* True dynamic ESM import that survives tsc's commonjs downleveling. `import(x)`
|
|
41
|
+
* with a literal would be rewritten to a `require`-based helper (breaks on an
|
|
42
|
+
* ESM-only package); the `Function` indirection keeps a native `import()` at
|
|
43
|
+
* runtime so Node loads the real ESM module.
|
|
44
|
+
*/
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
46
|
+
const esmImport = new Function('specifier', 'return import(specifier)');
|
|
47
|
+
/**
|
|
48
|
+
* Resolve a Pi `Model` object from a `provider/model` string via pi-ai's
|
|
49
|
+
* `getModel`. Returns `{ model }` on success, `{ fatal }` with an actionable
|
|
50
|
+
* message on an invalid/unindexed model (getModel returns `undefined` — a plain
|
|
51
|
+
* check, no throw), or `{}` when no model was requested (Pi uses its own default
|
|
52
|
+
* — the 3a anthropic-default path).
|
|
53
|
+
*/
|
|
54
|
+
async function resolveModel(modelStr) {
|
|
55
|
+
if (!modelStr)
|
|
56
|
+
return {};
|
|
57
|
+
const slash = modelStr.indexOf('/');
|
|
58
|
+
if (slash <= 0 || slash === modelStr.length - 1) {
|
|
59
|
+
return { fatal: `Invalid Pi model "${modelStr}" — expected "provider/model" (e.g. anthropic/claude-opus-4-5).` };
|
|
60
|
+
}
|
|
61
|
+
const provider = modelStr.slice(0, slash);
|
|
62
|
+
const modelName = modelStr.slice(slash + 1);
|
|
63
|
+
try {
|
|
64
|
+
const piAi = await esmImport(probe_1.PI_AI_PACKAGE);
|
|
65
|
+
const getModel = piAi.getModel;
|
|
66
|
+
const model = getModel(provider, modelName);
|
|
67
|
+
if (model === undefined || model === null) {
|
|
68
|
+
return {
|
|
69
|
+
fatal: `Pi model "${modelStr}" not found in Pi's provider index (provider="${provider}"). ` +
|
|
70
|
+
`Check the model id against \`pi --list-models\` / models.dev.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { model };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return { fatal: `Failed to resolve Pi model "${modelStr}": ${err instanceof Error ? err.message : String(err)}` };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build the `DefaultResourceLoader` options for a headless Pi player.
|
|
81
|
+
*
|
|
82
|
+
* SECURITY — S2 (MD-C deny-list soundness). The `restricted` tool gate is a
|
|
83
|
+
* DENY-LIST over shell/exec tool *names* (tool-capability.ts EXEC_TOOLS, via
|
|
84
|
+
* `classify(name) === 'exec'` — F1 replaced extension.ts's former local set). That
|
|
85
|
+
* guarantee — "restricted = no host execution" — holds ONLY IF no third-party
|
|
86
|
+
* extension can register an un-blacklisted execution tool (e.g. a custom
|
|
87
|
+
* `python` / `npm` / `run` tool). It therefore depends on a hard structural
|
|
88
|
+
* fact: which extensions Pi loads.
|
|
89
|
+
*
|
|
90
|
+
* Verified against the installed Pi SDK 0.78 source (NOT assumed):
|
|
91
|
+
* - `DefaultResourceLoader.reload()` (resource-loader.js:271-276) builds
|
|
92
|
+
* `extensionPaths = noExtensions ? cliEnabledExtensions
|
|
93
|
+
* : merge(cliEnabledExtensions, enabledExtensions)`
|
|
94
|
+
* where `enabledExtensions` (line 229) are the DISK/package extensions from
|
|
95
|
+
* `packageManager.resolve()` (`~/.pi/agent/extensions/`, `<cwd>/.pi/extensions/`,
|
|
96
|
+
* installed packages). `loadExtensions(extensionPaths)` then loads them and
|
|
97
|
+
* MERGES with our inline factories (lines 274-276).
|
|
98
|
+
* - `noExtensions` defaults to `false` (constructor, line 132) — so the naive
|
|
99
|
+
* loader DOES load disk extensions. That is the S2 gap.
|
|
100
|
+
*
|
|
101
|
+
* Fix (= security's "exclude the extensions dir", done structurally):
|
|
102
|
+
* - `noExtensions: true` → `extensionPaths` collapses to `cliEnabledExtensions`,
|
|
103
|
+
* which is empty because we pass NO `additionalExtensionPaths`. So
|
|
104
|
+
* `loadExtensions([])` registers nothing from disk/packages.
|
|
105
|
+
* - Inline `extensionFactories` load UNCONDITIONALLY (reload() line 275 is not
|
|
106
|
+
* gated by `noExtensions`), so our agent-tempo extension still attaches.
|
|
107
|
+
* Net: the ONLY tools present are Pi's built-ins (bash/read/edit/write/grep —
|
|
108
|
+
* all covered by the deny-list) + our agent-tempo MCP tools (no exec). No
|
|
109
|
+
* third-party tool can slip past the deny-list. Skills/prompts/themes cannot
|
|
110
|
+
* register tools, so they are not a vector and are left at defaults.
|
|
111
|
+
*
|
|
112
|
+
* Kept as a pure, exported helper so the `noExtensions: true` invariant has a
|
|
113
|
+
* unit regression test (test/pi-headless-loader.test.ts) without needing the Pi
|
|
114
|
+
* SDK installed.
|
|
115
|
+
*/
|
|
116
|
+
function buildPiResourceLoaderOptions(params) {
|
|
117
|
+
return {
|
|
118
|
+
cwd: params.cwd,
|
|
119
|
+
agentDir: params.agentDir,
|
|
120
|
+
extensionFactories: [params.extensionFactory],
|
|
121
|
+
// SECURITY (S2): hard-exclude all disk/package extensions. Do NOT add
|
|
122
|
+
// `additionalExtensionPaths` here — that would re-introduce the exec-tool
|
|
123
|
+
// vector this flag closes.
|
|
124
|
+
noExtensions: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Boot + run a headless Pi player until shutdown. Resolves when the process has
|
|
129
|
+
* cleanly detached + disposed (it also calls process.exit on the terminal path).
|
|
130
|
+
*/
|
|
131
|
+
async function runHeadlessPi(opts = {}) {
|
|
132
|
+
const config = opts.config ?? (0, config_1.getConfig)();
|
|
133
|
+
const toolAccess = opts.toolAccess ?? 'restricted';
|
|
134
|
+
// 1) Probe — the spawn entry is the only place the Pi SDK is REQUIRED.
|
|
135
|
+
if (!(0, sdk_probe_1.probeSdkInstall)(probe_1.PI_PACKAGE)) {
|
|
136
|
+
log(`FATAL: ${probe_1.PI_PACKAGE} is not installed — cannot run headless Pi. Exiting.`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// 2) Resolve the model BEFORE creating the session — a bad model fails clean
|
|
141
|
+
// (exit before attach, no half-attached orphan).
|
|
142
|
+
const { model, fatal } = await resolveModel(opts.model);
|
|
143
|
+
if (fatal) {
|
|
144
|
+
log(`FATAL: ${fatal} Exiting without attaching.`);
|
|
145
|
+
process.exit(2);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// 3) Inline extension factory — headless mode → the MD-C tool gate is active.
|
|
149
|
+
const extensionFactory = (0, extension_1.createPiExtension)({ mode: 'headless', toolAccess });
|
|
150
|
+
// 4) Construct the Pi SDK session with the extension injected inline.
|
|
151
|
+
const piSdk = await esmImport(probe_1.PI_PACKAGE);
|
|
152
|
+
const createAgentSession = piSdk.createAgentSession;
|
|
153
|
+
const DefaultResourceLoader = piSdk.DefaultResourceLoader;
|
|
154
|
+
// Pi's DefaultResourceLoader REQUIRES agentDir (normalizePath does
|
|
155
|
+
// `.startsWith()` on it) — getAgentDir() resolves ~/.pi/agent. Pass it to BOTH
|
|
156
|
+
// createAgentSession and the loader. (Found in the 3a live smoke — devops.)
|
|
157
|
+
const getAgentDir = piSdk.getAgentDir;
|
|
158
|
+
const agentDir = getAgentDir();
|
|
159
|
+
const resourceLoader = new DefaultResourceLoader(buildPiResourceLoaderOptions({ cwd: process.cwd(), agentDir, extensionFactory }));
|
|
160
|
+
// CRITICAL (3a live smoke — devops): createAgentSession only calls
|
|
161
|
+
// resourceLoader.reload() when IT constructs the loader (sdk.js:99-101). When we
|
|
162
|
+
// pass our OWN loader, reload() is skipped — and DefaultResourceLoader inits
|
|
163
|
+
// `extensionsResult.extensions = []` (resource-loader.js:146) and only populates
|
|
164
|
+
// it during reload(). So without this explicit reload our extension never
|
|
165
|
+
// registers, and session_start fires into an empty handler list (no claim, no
|
|
166
|
+
// heartbeat). The SDK's own doc comment (sdk.js:74-83) prescribes this exact
|
|
167
|
+
// construct → reload() → pass-as-resourceLoader sequence.
|
|
168
|
+
await resourceLoader.reload();
|
|
169
|
+
const { session } = await createAgentSession({
|
|
170
|
+
cwd: process.cwd(),
|
|
171
|
+
agentDir,
|
|
172
|
+
...(model ? { model } : {}),
|
|
173
|
+
resourceLoader,
|
|
174
|
+
// NOTE (A4): restart-resume via a SessionManager seeded from
|
|
175
|
+
// opts.continueSessionId / ENV.PI_CONTINUE_SESSION lands in A4; 3a proves the
|
|
176
|
+
// loop on a fresh session.
|
|
177
|
+
});
|
|
178
|
+
// 5) Explicit bootstrap — fires session_start → the singleton claims/attaches.
|
|
179
|
+
await session.bindExtensions({});
|
|
180
|
+
// Headless session_start carries NO `session` field (interactive does), so the
|
|
181
|
+
// extension can't wire the cue pump's session ref itself. We hold the SDK
|
|
182
|
+
// session here — set it on the now-claimed runtime so the cue pump can inject.
|
|
183
|
+
// (3a live smoke — devops; the headless-vs-interactive session-wiring gap.)
|
|
184
|
+
const playerId = process.env[config_1.ENV.PLAYER_NAME] || `pi-${process.pid}`;
|
|
185
|
+
(0, extension_1.setRuntimeSession)((0, config_1.sessionWorkflowId)(config.ensemble, playerId), session);
|
|
186
|
+
log(`headless Pi session bound (toolAccess=${toolAccess}, ` +
|
|
187
|
+
`model=${opts.model ?? 'pi-default'}${opts.continueSessionId ? `, continue=${opts.continueSessionId}` : ''}, ` +
|
|
188
|
+
`sessionId=${session.sessionId ?? '?'})`);
|
|
189
|
+
// 6) Stay alive until a shutdown signal, then RELIABLE detach → dispose → exit.
|
|
190
|
+
// Keep the event loop alive with a REF'd timer: the heartbeat + cue-pump timers
|
|
191
|
+
// are `.unref()`'d by design (so they never block a clean exit), and the
|
|
192
|
+
// SIGTERM/SIGINT once-listeners aren't active handles — so WITHOUT this the loop
|
|
193
|
+
// drains and Node exits code 0 immediately after bindExtensions in headless mode.
|
|
194
|
+
// (Found in the 3a live smoke — devops.)
|
|
195
|
+
const keepAlive = setInterval(() => { }, 30_000);
|
|
196
|
+
try {
|
|
197
|
+
await new Promise((resolveShutdown) => {
|
|
198
|
+
const onSignal = (sig) => { log(`received ${sig} — shutting down`); resolveShutdown(); };
|
|
199
|
+
process.once('SIGTERM', () => onSignal('SIGTERM'));
|
|
200
|
+
process.once('SIGINT', () => onSignal('SIGINT'));
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
clearInterval(keepAlive);
|
|
205
|
+
}
|
|
206
|
+
// Headless owns the exit sequence: await adapterExited (unmaps the runtime)
|
|
207
|
+
// THEN dispose the SDK session (the dispose-fired session_shutdown finds no
|
|
208
|
+
// mapped runtime → no-op, so no double-detach).
|
|
209
|
+
try {
|
|
210
|
+
await (0, extension_1.detachAllPiRuntimesForExit)();
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
log('detach failed (reaper backstops):', err);
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
session.dispose();
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
log('dispose failed:', err);
|
|
220
|
+
}
|
|
221
|
+
log('headless Pi clean-exit complete');
|
|
222
|
+
// eslint-disable-next-line no-process-exit
|
|
223
|
+
process.exit(0);
|
|
224
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-tempo Pi integration — barrel.
|
|
3
|
+
*
|
|
4
|
+
* Default export is the Pi extension factory (`export default function(pi)`).
|
|
5
|
+
* Named exports expose the testable units and the client-side wrapper for reuse
|
|
6
|
+
* by the headless Pi runtime (Phase 3).
|
|
7
|
+
*
|
|
8
|
+
* Tool registration: the extension renders the shared transport-neutral tool
|
|
9
|
+
* descriptors (src/tools/descriptor.ts) onto Pi via `renderToPi`, deriving
|
|
10
|
+
* TypeBox param schemas from zod through `zod-to-typebox.ts`. There is no
|
|
11
|
+
* Pi-specific re-implementation of any tool.
|
|
12
|
+
*
|
|
13
|
+
* See src/pi/README.md for the Phase 0/2 findings (abrupt-death / MD-A, D12a)
|
|
14
|
+
* and known limitations.
|
|
15
|
+
*/
|
|
16
|
+
export { default } from './extension';
|
|
17
|
+
export { PhaseDriver } from './phase-driver';
|
|
18
|
+
export type { PiPhase, WorkflowAction, PhaseDriverResult } from './phase-driver';
|
|
19
|
+
export { PiWorkflowClient } from './workflow-client';
|
|
20
|
+
export type { PiWorkflowClientOptions } from './workflow-client';
|
|
21
|
+
export { CuePump } from './cue-pump';
|
|
22
|
+
export type { CueSource, SessionResolver, CuePumpOptions } from './cue-pump';
|
|
23
|
+
export { renderToPi, toPiResult } from './render-tools';
|
|
24
|
+
export { createLazyProxy } from './lazy-proxy';
|
|
25
|
+
export { zodShapeToTypeBox, UnsupportedZodFeatureError } from './zod-to-typebox';
|
|
26
|
+
export { probePi, PI_PACKAGE, PI_AI_PACKAGE, TESTED_PI_VERSION, PI_NODE_FLOOR } from './probe';
|
|
27
|
+
export type { PiProbeResult } from './probe';
|
|
28
|
+
export type { ExtensionAPI, PiExtension, PiAgentSession, PiEventPayload, PiToolDefinition, PiToolResult, PiLifecycleEvent, } from './pi-types';
|
package/dist/pi/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PI_NODE_FLOOR = exports.TESTED_PI_VERSION = exports.PI_AI_PACKAGE = exports.PI_PACKAGE = exports.probePi = exports.UnsupportedZodFeatureError = exports.zodShapeToTypeBox = exports.createLazyProxy = exports.toPiResult = exports.renderToPi = exports.CuePump = exports.PiWorkflowClient = exports.PhaseDriver = exports.default = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* agent-tempo Pi integration — barrel.
|
|
9
|
+
*
|
|
10
|
+
* Default export is the Pi extension factory (`export default function(pi)`).
|
|
11
|
+
* Named exports expose the testable units and the client-side wrapper for reuse
|
|
12
|
+
* by the headless Pi runtime (Phase 3).
|
|
13
|
+
*
|
|
14
|
+
* Tool registration: the extension renders the shared transport-neutral tool
|
|
15
|
+
* descriptors (src/tools/descriptor.ts) onto Pi via `renderToPi`, deriving
|
|
16
|
+
* TypeBox param schemas from zod through `zod-to-typebox.ts`. There is no
|
|
17
|
+
* Pi-specific re-implementation of any tool.
|
|
18
|
+
*
|
|
19
|
+
* See src/pi/README.md for the Phase 0/2 findings (abrupt-death / MD-A, D12a)
|
|
20
|
+
* and known limitations.
|
|
21
|
+
*/
|
|
22
|
+
var extension_1 = require("./extension");
|
|
23
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(extension_1).default; } });
|
|
24
|
+
var phase_driver_1 = require("./phase-driver");
|
|
25
|
+
Object.defineProperty(exports, "PhaseDriver", { enumerable: true, get: function () { return phase_driver_1.PhaseDriver; } });
|
|
26
|
+
var workflow_client_1 = require("./workflow-client");
|
|
27
|
+
Object.defineProperty(exports, "PiWorkflowClient", { enumerable: true, get: function () { return workflow_client_1.PiWorkflowClient; } });
|
|
28
|
+
var cue_pump_1 = require("./cue-pump");
|
|
29
|
+
Object.defineProperty(exports, "CuePump", { enumerable: true, get: function () { return cue_pump_1.CuePump; } });
|
|
30
|
+
var render_tools_1 = require("./render-tools");
|
|
31
|
+
Object.defineProperty(exports, "renderToPi", { enumerable: true, get: function () { return render_tools_1.renderToPi; } });
|
|
32
|
+
Object.defineProperty(exports, "toPiResult", { enumerable: true, get: function () { return render_tools_1.toPiResult; } });
|
|
33
|
+
var lazy_proxy_1 = require("./lazy-proxy");
|
|
34
|
+
Object.defineProperty(exports, "createLazyProxy", { enumerable: true, get: function () { return lazy_proxy_1.createLazyProxy; } });
|
|
35
|
+
var zod_to_typebox_1 = require("./zod-to-typebox");
|
|
36
|
+
Object.defineProperty(exports, "zodShapeToTypeBox", { enumerable: true, get: function () { return zod_to_typebox_1.zodShapeToTypeBox; } });
|
|
37
|
+
Object.defineProperty(exports, "UnsupportedZodFeatureError", { enumerable: true, get: function () { return zod_to_typebox_1.UnsupportedZodFeatureError; } });
|
|
38
|
+
var probe_1 = require("./probe");
|
|
39
|
+
Object.defineProperty(exports, "probePi", { enumerable: true, get: function () { return probe_1.probePi; } });
|
|
40
|
+
Object.defineProperty(exports, "PI_PACKAGE", { enumerable: true, get: function () { return probe_1.PI_PACKAGE; } });
|
|
41
|
+
Object.defineProperty(exports, "PI_AI_PACKAGE", { enumerable: true, get: function () { return probe_1.PI_AI_PACKAGE; } });
|
|
42
|
+
Object.defineProperty(exports, "TESTED_PI_VERSION", { enumerable: true, get: function () { return probe_1.TESTED_PI_VERSION; } });
|
|
43
|
+
Object.defineProperty(exports, "PI_NODE_FLOOR", { enumerable: true, get: function () { return probe_1.PI_NODE_FLOOR; } });
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { InnerFrame, InnerLoopRegistry } from './inner-loop-publisher';
|
|
2
|
+
/** Env var carrying the per-player ingest token (threaded in at spawn). */
|
|
3
|
+
export declare const INGEST_TOKEN_ENV = "AGENT_TEMPO_INGEST_TOKEN";
|
|
4
|
+
/** DOS backstop — frames over this are dropped (summaries are already ~2KB-truncated). */
|
|
5
|
+
export declare const MAX_FRAME_BYTES: number;
|
|
6
|
+
/** Minimal `fetch` shape this client needs — injectable for tests. */
|
|
7
|
+
export type InnerLoopFetch = (url: string, init: {
|
|
8
|
+
method: string;
|
|
9
|
+
headers: Record<string, string>;
|
|
10
|
+
body?: string;
|
|
11
|
+
}) => Promise<{
|
|
12
|
+
status: number;
|
|
13
|
+
json(): Promise<unknown>;
|
|
14
|
+
}>;
|
|
15
|
+
export interface InnerLoopHttpClientOptions {
|
|
16
|
+
/** The player's ensemble (URL path segment). */
|
|
17
|
+
ensemble: string;
|
|
18
|
+
/** The player's id (URL path segment). */
|
|
19
|
+
playerId: string;
|
|
20
|
+
/** Ingest token. Defaults to `process.env[INGEST_TOKEN_ENV]`. */
|
|
21
|
+
ingestToken?: string;
|
|
22
|
+
/** Daemon port discovery. Defaults to {@link readPortFile}. */
|
|
23
|
+
readPort?: () => number | null;
|
|
24
|
+
/** HTTP transport. Defaults to global `fetch` (no-op if absent). */
|
|
25
|
+
fetchFn?: InnerLoopFetch;
|
|
26
|
+
/** Min interval between presence GETs (default 1000ms). */
|
|
27
|
+
presencePollMs?: number;
|
|
28
|
+
/** Injected clock (tests). Defaults to `Date.now`. */
|
|
29
|
+
now?: () => number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Loopback-HTTP {@link InnerLoopRegistry}. Construct one per headless player with
|
|
33
|
+
* its ensemble + playerId; the publisher calls `publish` / `subscriberCount`.
|
|
34
|
+
* The `workflowId` argument is the player's own and is unused — the URL is built
|
|
35
|
+
* from the ctor ensemble + playerId.
|
|
36
|
+
*/
|
|
37
|
+
export declare class InnerLoopHttpClient implements InnerLoopRegistry {
|
|
38
|
+
private readonly ensemble;
|
|
39
|
+
private readonly playerId;
|
|
40
|
+
private readonly ingestToken;
|
|
41
|
+
private readonly readPort;
|
|
42
|
+
private readonly fetchFn;
|
|
43
|
+
private readonly presencePollMs;
|
|
44
|
+
private readonly now;
|
|
45
|
+
/** Last presence count from the daemon. 0 until the first GET resolves / on failure. */
|
|
46
|
+
private cachedSubscribers;
|
|
47
|
+
/** 3d — last gateArmed flag from the daemon (folded into the presence response). */
|
|
48
|
+
private cachedGateArmed;
|
|
49
|
+
private lastPresenceRefresh;
|
|
50
|
+
constructor(opts: InnerLoopHttpClientOptions);
|
|
51
|
+
/** Whether the client can talk to the daemon at all (token + transport present). */
|
|
52
|
+
private get enabled();
|
|
53
|
+
private baseUrl;
|
|
54
|
+
/** Fire-and-forget POST of one frame. Drops (never throws/blocks) on any failure. */
|
|
55
|
+
publish(_workflowId: string, frame: InnerFrame): void;
|
|
56
|
+
/** Synchronous cached presence (stale-while-revalidate). Default 0 / fail-safe 0. */
|
|
57
|
+
subscriberCount(_workflowId: string): number;
|
|
58
|
+
/**
|
|
59
|
+
* 3d — synchronous cached `gateArmed` (same stale-while-revalidate presence GET
|
|
60
|
+
* that feeds subscriberCount; short-poll keeps it within ~1s). Default false /
|
|
61
|
+
* fail-safe false (a missed/failed presence read never spuriously engages the
|
|
62
|
+
* gate). The engagement check reads this together with subscriberCount.
|
|
63
|
+
*/
|
|
64
|
+
gateArmed(_workflowId: string): boolean;
|
|
65
|
+
/** Fire a rate-limited background presence GET that updates the cache. */
|
|
66
|
+
private maybeRefreshPresence;
|
|
67
|
+
}
|