agent-tempo 1.3.1 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +39 -5
- package/README.md +6 -2
- 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 +1 -1
- package/dashboard/package.json +1 -1
- 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/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/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 +32 -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 +250 -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 +88 -0
- package/dist/pi/mission-control/board.js +141 -0
- package/dist/pi/mission-control/extension.d.ts +51 -0
- package/dist/pi/mission-control/extension.js +330 -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 +98 -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 +222 -0
- package/dist/pi/pi-types.js +21 -0
- package/dist/pi/probe.d.ts +99 -0
- package/dist/pi/probe.js +179 -0
- package/dist/pi/render-tools.d.ts +17 -0
- package/dist/pi/render-tools.js +56 -0
- package/dist/pi/reset-pump.d.ts +47 -0
- package/dist/pi/reset-pump.js +85 -0
- package/dist/pi/session-seed.d.ts +74 -0
- package/dist/pi/session-seed.js +103 -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/server-tools.d.ts +2 -0
- package/dist/server-tools.js +50 -46
- package/dist/spawn.d.ts +55 -0
- package/dist/spawn.js +72 -0
- 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 +29 -24
- package/dist/tools/coat-check-get.d.ts +2 -2
- package/dist/tools/coat-check-get.js +38 -33
- 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 +38 -33
- 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 +42 -37
- 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 +340 -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 +31 -26
- 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/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/package.json +4 -1
- package/workflow-bundle.js +97 -6
- package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
- package/dist/tools/helpers.d.ts +0 -21
- package/dist/tools/helpers.js +0 -25
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
rel="stylesheet"
|
|
13
13
|
href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
14
14
|
/>
|
|
15
|
-
<script type="module" crossorigin src="/dashboard/assets/index-
|
|
15
|
+
<script type="module" crossorigin src="/dashboard/assets/index-jmYe6rmS.js"></script>
|
|
16
16
|
<link rel="stylesheet" crossorigin href="/dashboard/assets/index-CB78ToNE.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
package/dashboard/package.json
CHANGED
|
@@ -2,6 +2,8 @@ import { Client } from '@temporalio/client';
|
|
|
2
2
|
import { Config } from '../config';
|
|
3
3
|
import { AgentType, MockMode, DetachReason } from '../types';
|
|
4
4
|
import type { ClaudeCodeHeadlessPermissionMode } from '../adapters/claude-code-headless/types';
|
|
5
|
+
import type { IngestTokenRegistry } from '../http/ingest-registry';
|
|
6
|
+
import type { GateRegistry } from '../http/gate-registry';
|
|
5
7
|
import { type HardTerminateInput, type HardTerminateResult } from './hard-terminate';
|
|
6
8
|
export interface DeliverCueInput {
|
|
7
9
|
ensemble: string;
|
|
@@ -76,6 +78,17 @@ export interface DeliverDestroyInput {
|
|
|
76
78
|
terminatedBy: string;
|
|
77
79
|
notifyConductor?: boolean;
|
|
78
80
|
}
|
|
81
|
+
export interface DeliverResetInput {
|
|
82
|
+
ensemble: string;
|
|
83
|
+
targetPlayerId: string;
|
|
84
|
+
/** Correlation id (the originating outbox entry id) — the extension acks with it. */
|
|
85
|
+
resetId: string;
|
|
86
|
+
/** Clean-wipe (D14 default true). */
|
|
87
|
+
fresh: boolean;
|
|
88
|
+
reason?: string;
|
|
89
|
+
/** Who requested the reset (audit). */
|
|
90
|
+
requestedBy?: string;
|
|
91
|
+
}
|
|
79
92
|
export interface DeliverRestartInput {
|
|
80
93
|
ensemble: string;
|
|
81
94
|
targetPlayerId: string;
|
|
@@ -155,6 +168,14 @@ export interface SpawnProcessInput {
|
|
|
155
168
|
* exclusive with {@link permissionMode}.
|
|
156
169
|
*/
|
|
157
170
|
dangerouslySkipPermissions?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Phase 3a / MD-C — headless Pi tool-class policy. Forwarded as
|
|
173
|
+
* `AGENT_TEMPO_TOOL_ACCESS`. Only meaningful when `agent === 'pi'`. One of
|
|
174
|
+
* `'restricted'` (default; Bash hard-blocked) | `'standard'` | `'full'`.
|
|
175
|
+
* Inline literal — kept off the workflow-sandbox import path (see the
|
|
176
|
+
* RecruitOutboxEntry note in src/types.ts).
|
|
177
|
+
*/
|
|
178
|
+
toolAccess?: 'restricted' | 'standard' | 'full';
|
|
158
179
|
}
|
|
159
180
|
export interface OutboxActivityResult {
|
|
160
181
|
success: boolean;
|
|
@@ -174,6 +195,7 @@ export interface OutboxActivities {
|
|
|
174
195
|
deliverDetach(input: DeliverDetachInput): Promise<OutboxActivityResult>;
|
|
175
196
|
deliverDestroy(input: DeliverDestroyInput): Promise<OutboxActivityResult>;
|
|
176
197
|
deliverRestart(input: DeliverRestartInput): Promise<OutboxActivityResult>;
|
|
198
|
+
deliverReset(input: DeliverResetInput): Promise<OutboxActivityResult>;
|
|
177
199
|
/**
|
|
178
200
|
* OS-level child-process-tree kill for the target session. Runs on the per-host
|
|
179
201
|
* task queue (`agent-tempo-{hostname}`) so the kill happens where the process
|
|
@@ -184,5 +206,12 @@ export interface OutboxActivities {
|
|
|
184
206
|
/**
|
|
185
207
|
* Create outbox delivery activities bound to a Temporal client and config.
|
|
186
208
|
* The returned object is registered with the worker as activities.
|
|
209
|
+
*
|
|
210
|
+
* @param ingestTokens 3c Tier-2 ingest-auth registry (daemon-owned singleton,
|
|
211
|
+
* shared with the HTTP `/inner/ingest` + `/inner/presence` validators). The pi
|
|
212
|
+
* spawn branch MINTS a per-player token here (single-token-per-workflowId —
|
|
213
|
+
* re-mint REPLACES, so a restart naturally revokes the stale token); the
|
|
214
|
+
* destroy path REVOKES it. Optional: undefined disables ingest-token minting
|
|
215
|
+
* (e.g. the dev test harness that constructs activities without the daemon).
|
|
187
216
|
*/
|
|
188
|
-
export declare function createOutboxActivities(client: Client, config: Config): OutboxActivities;
|
|
217
|
+
export declare function createOutboxActivities(client: Client, config: Config, ingestTokens?: IngestTokenRegistry, gate?: GateRegistry): OutboxActivities;
|
|
@@ -136,8 +136,15 @@ function classifyAndRethrow(err, contextPrefix) {
|
|
|
136
136
|
/**
|
|
137
137
|
* Create outbox delivery activities bound to a Temporal client and config.
|
|
138
138
|
* The returned object is registered with the worker as activities.
|
|
139
|
+
*
|
|
140
|
+
* @param ingestTokens 3c Tier-2 ingest-auth registry (daemon-owned singleton,
|
|
141
|
+
* shared with the HTTP `/inner/ingest` + `/inner/presence` validators). The pi
|
|
142
|
+
* spawn branch MINTS a per-player token here (single-token-per-workflowId —
|
|
143
|
+
* re-mint REPLACES, so a restart naturally revokes the stale token); the
|
|
144
|
+
* destroy path REVOKES it. Optional: undefined disables ingest-token minting
|
|
145
|
+
* (e.g. the dev test harness that constructs activities without the daemon).
|
|
139
146
|
*/
|
|
140
|
-
function createOutboxActivities(client, config) {
|
|
147
|
+
function createOutboxActivities(client, config, ingestTokens, gate) {
|
|
141
148
|
return {
|
|
142
149
|
async deliverCue(input) {
|
|
143
150
|
const { ensemble, fromPlayerId, targetPlayerId, message, broadcastId, attachmentTicket } = input;
|
|
@@ -247,7 +254,7 @@ function createOutboxActivities(client, config) {
|
|
|
247
254
|
// across CAN. Both adapters use the same `model` metadata field
|
|
248
255
|
// (different value shapes — bare vs `provider/model`) — the spawn
|
|
249
256
|
// path inspects `metadata.agentType` to know which env var to set.
|
|
250
|
-
...((agent === 'claude-api' || agent === 'opencode') && model ? { model } : {}),
|
|
257
|
+
...((agent === 'claude-api' || agent === 'opencode' || agent === 'pi') && model ? { model } : {}),
|
|
251
258
|
...(agentDefinition ? { playerType: agentDefinition } : {}),
|
|
252
259
|
...(agentDefinitionDescription ? { playerTypeDescription: agentDefinitionDescription } : {}),
|
|
253
260
|
recruitedBy: fromPlayerId,
|
|
@@ -305,7 +312,7 @@ function createOutboxActivities(client, config) {
|
|
|
305
312
|
}
|
|
306
313
|
},
|
|
307
314
|
async spawnProcess(input) {
|
|
308
|
-
const { targetName, workDir, isConductor, agent, systemPrompt, ensemble, temporalAddress, temporalNamespace, agentDefinition, agentDefinitionPath, nativeResolvable, resume, sessionId, allowedTools, claudeBin, attachmentId, attachmentRunId, adapterId, mockMode, mockScenario, model, permissionMode, dangerouslySkipPermissions } = input;
|
|
315
|
+
const { targetName, workDir, isConductor, agent, systemPrompt, ensemble, temporalAddress, temporalNamespace, agentDefinition, agentDefinitionPath, nativeResolvable, resume, sessionId, allowedTools, claudeBin, attachmentId, attachmentRunId, adapterId, mockMode, mockScenario, model, permissionMode, dangerouslySkipPermissions, toolAccess } = input;
|
|
309
316
|
// Read secrets from the worker's config closure — never from workflow state
|
|
310
317
|
const { temporalApiKey, temporalTlsCertPath, temporalTlsKeyPath } = config;
|
|
311
318
|
try {
|
|
@@ -436,6 +443,43 @@ function createOutboxActivities(client, config) {
|
|
|
436
443
|
});
|
|
437
444
|
log(`Spawned claude-code-headless adapter (pid ${pid}) in ${workDir} as "${targetName}"${permissionMode ? ` (permissionMode=${permissionMode})` : ''}${dangerouslySkipPermissions ? ' (dangerouslySkipPermissions=true)' : ''}${attachmentId ? ` (attachmentId=${attachmentId})` : ''}`);
|
|
438
445
|
}
|
|
446
|
+
else if (agent === 'pi') {
|
|
447
|
+
// Phase 3a — headless Pi runtime. Injects the src/pi extension into
|
|
448
|
+
// Pi's createAgentSession; the module-scope singleton owns the
|
|
449
|
+
// lifecycle (claim/heartbeat/tools/cue-pump). Tool access is governed
|
|
450
|
+
// by the MD-C `toolAccess` policy (restricted hard-blocks Bash via the
|
|
451
|
+
// extension's tool_call gate), NOT per-tool allowlists.
|
|
452
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
453
|
+
log(`Warning: allowedTools [${allowedTools.join(', ')}] specified for pi agent "${targetName}" — Pi tool access is governed by toolAccess (MD-C), skipping allowedTools`);
|
|
454
|
+
}
|
|
455
|
+
// 3c Tier-2 — mint a per-player ingest token scoped to this player's
|
|
456
|
+
// session workflowId so the headless Pi subprocess can authenticate its
|
|
457
|
+
// `POST /inner/ingest` frames. Single-token-per-workflowId (mint
|
|
458
|
+
// REPLACES) means a restart re-mints and naturally revokes the stale
|
|
459
|
+
// token. Injected into the subprocess env as AGENT_TEMPO_INGEST_TOKEN.
|
|
460
|
+
const ingestToken = ingestTokens?.mint((0, config_1.sessionWorkflowId)(ensemble, targetName));
|
|
461
|
+
const { pid } = (0, spawn_1.spawnPiHeadless)({
|
|
462
|
+
name: targetName,
|
|
463
|
+
ensemble,
|
|
464
|
+
temporalAddress,
|
|
465
|
+
temporalNamespace,
|
|
466
|
+
temporalApiKey,
|
|
467
|
+
temporalTlsCertPath,
|
|
468
|
+
temporalTlsKeyPath,
|
|
469
|
+
isConductor,
|
|
470
|
+
workDir,
|
|
471
|
+
model,
|
|
472
|
+
// Restart-resume: continue the prior Pi conversation only on a
|
|
473
|
+
// restart (resume=true); a fresh recruit starts a new Pi session.
|
|
474
|
+
continueSessionId: resume ? sessionId : undefined,
|
|
475
|
+
toolAccess,
|
|
476
|
+
attachmentId,
|
|
477
|
+
attachmentRunId,
|
|
478
|
+
adapterId,
|
|
479
|
+
ingestToken,
|
|
480
|
+
});
|
|
481
|
+
log(`Spawned pi headless adapter (pid ${pid}) in ${workDir} as "${targetName}" (toolAccess=${toolAccess ?? 'restricted'})${model ? ` (model=${model})` : ''}${resume && sessionId ? ` (continue=${sessionId})` : ''}${attachmentId ? ` (attachmentId=${attachmentId})` : ''}`);
|
|
482
|
+
}
|
|
439
483
|
else {
|
|
440
484
|
// Resolve agent flags: --agent (native) > --system-prompt (shipped/legacy)
|
|
441
485
|
let agentFlags = [];
|
|
@@ -551,6 +595,14 @@ function createOutboxActivities(client, config) {
|
|
|
551
595
|
throw activity_1.ApplicationFailure.nonRetryable(`Cannot detach "${targetPlayerId}" — session is destroyed`);
|
|
552
596
|
}
|
|
553
597
|
await handle.signal(signals_1.requestDetachSignal, { reason, deadlineMs });
|
|
598
|
+
// 3c Tier-2 — revoke the player's ingest token on detach so a dead
|
|
599
|
+
// holder's loopback POST /inner/ingest can no longer authenticate once
|
|
600
|
+
// the player goes away. Mirrors the destroy-side revoke; idempotent and a
|
|
601
|
+
// no-op for non-Pi players (they never minted one). Re-attach re-mints.
|
|
602
|
+
ingestTokens?.revoke((0, config_1.sessionWorkflowId)(ensemble, targetPlayerId));
|
|
603
|
+
// 3d MD-G — auto-disarm the gate on detach (the operator's gate posture
|
|
604
|
+
// shouldn't survive the player going away; re-attach re-arms). Idempotent.
|
|
605
|
+
gate?.clearPlayer((0, config_1.sessionWorkflowId)(ensemble, targetPlayerId));
|
|
554
606
|
log(`Detach signaled for "${targetPlayerId}" (deadline=${deadlineMs}ms)`);
|
|
555
607
|
return { success: true };
|
|
556
608
|
}
|
|
@@ -580,6 +632,21 @@ function createOutboxActivities(client, config) {
|
|
|
580
632
|
}],
|
|
581
633
|
});
|
|
582
634
|
log(`Destroyed "${targetPlayerId}"${reason ? ` (reason: ${reason})` : ''}`);
|
|
635
|
+
// 3c Tier-2 — revoke the player's ingest token on destroy so a dead
|
|
636
|
+
// holder's loopback POST /inner/ingest can no longer authenticate.
|
|
637
|
+
// Idempotent; a no-op for non-Pi players (they never minted one).
|
|
638
|
+
// NOTE (security cond. 2): the ingest endpoint authenticates on TOKEN
|
|
639
|
+
// ALONE — it does not inspect workflow phase (neither rejects nor
|
|
640
|
+
// buffers detached-phase players). So the residual surface is bounded
|
|
641
|
+
// not by phase-checking but by (a) this destroy-revoke and (b) the
|
|
642
|
+
// single-token-per-workflowId replacement on re-attach/restart.
|
|
643
|
+
// TODO(Phase 4): wire aggregate player-gone detection →
|
|
644
|
+
// revokeIngestToken(workflowId) on detach — deferred; residual surface
|
|
645
|
+
// negligible (dead holder + loopback-only + single-token replacement).
|
|
646
|
+
ingestTokens?.revoke((0, config_1.sessionWorkflowId)(ensemble, targetPlayerId));
|
|
647
|
+
// 3d MD-G — auto-disarm: drop the gate's armed-state + any pending
|
|
648
|
+
// requests for the destroyed player (idempotent; no-op for non-Pi).
|
|
649
|
+
gate?.clearPlayer((0, config_1.sessionWorkflowId)(ensemble, targetPlayerId));
|
|
583
650
|
if (notifyConductor) {
|
|
584
651
|
try {
|
|
585
652
|
const condId = (0, config_1.conductorWorkflowId)(ensemble);
|
|
@@ -603,6 +670,32 @@ function createOutboxActivities(client, config) {
|
|
|
603
670
|
classifyAndRethrow(err, `Destroy failed for "${targetPlayerId}"`);
|
|
604
671
|
}
|
|
605
672
|
},
|
|
673
|
+
/**
|
|
674
|
+
* D14 `deliverReset` — set a `pendingReset` flag on the target via
|
|
675
|
+
* `setPendingResetSignal` (a signal, like deliverCue — NOT a direct
|
|
676
|
+
* subprocess call). The Pi extension polls `pendingResetQuery`, performs the
|
|
677
|
+
* clean-wipe (`newSession`), then clears it via `ackResetSignal(resetId)`.
|
|
678
|
+
*/
|
|
679
|
+
async deliverReset(input) {
|
|
680
|
+
const { ensemble, targetPlayerId, resetId, fresh, reason, requestedBy } = input;
|
|
681
|
+
try {
|
|
682
|
+
const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
|
|
683
|
+
if (!handle) {
|
|
684
|
+
throw activity_1.ApplicationFailure.nonRetryable(`No session found for "${targetPlayerId}"`);
|
|
685
|
+
}
|
|
686
|
+
await handle.signal(signals_1.setPendingResetSignal, {
|
|
687
|
+
resetId,
|
|
688
|
+
fresh,
|
|
689
|
+
...(reason !== undefined ? { reason } : {}),
|
|
690
|
+
...(requestedBy !== undefined ? { requestedBy } : {}),
|
|
691
|
+
});
|
|
692
|
+
log(`Reset queued for "${targetPlayerId}"${reason ? ` (reason: ${reason})` : ''}`);
|
|
693
|
+
return { success: true };
|
|
694
|
+
}
|
|
695
|
+
catch (err) {
|
|
696
|
+
classifyAndRethrow(err, `Reset failed for "${targetPlayerId}"`);
|
|
697
|
+
}
|
|
698
|
+
},
|
|
606
699
|
/**
|
|
607
700
|
* PR-D `deliverRestart` — owns the §8.2 restart algorithm on the target.
|
|
608
701
|
* Graceful `requestDetach` → re-query phase → `forceDetach` (if --force
|
package/dist/adapters/base.js
CHANGED
|
@@ -1264,6 +1264,11 @@ class AdapterRegistry {
|
|
|
1264
1264
|
// Claude Code OAuth login so turns bill against subscription extra-usage.
|
|
1265
1265
|
if (agent === 'claude-code-headless')
|
|
1266
1266
|
return 'claude-code-headless';
|
|
1267
|
+
// Phase 3a — headless Pi runtime. The 'pi' descriptor is registry/identity
|
|
1268
|
+
// ONLY: the module-scope Pi extension singleton owns claim/heartbeat/phase
|
|
1269
|
+
// (no BaseAttachment subprocess driver). Opt-in via `recruit({ agent: 'pi' })`.
|
|
1270
|
+
if (agent === 'pi')
|
|
1271
|
+
return 'pi';
|
|
1267
1272
|
return 'claude-code';
|
|
1268
1273
|
}
|
|
1269
1274
|
}
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare const registry: AdapterRegistry;
|
|
|
31
31
|
* Mock adapter is intentionally excluded — it's dev-mode-only and stripped
|
|
32
32
|
* from the npm tarball at prepack time (ADR 0014 §7 gate 1).
|
|
33
33
|
*/
|
|
34
|
-
export declare const CANONICAL_ADAPTER_IDS: readonly ["claude-code", "copilot", "claude-api", "opencode", "claude-code-headless"];
|
|
34
|
+
export declare const CANONICAL_ADAPTER_IDS: readonly ["claude-code", "copilot", "claude-api", "opencode", "claude-code-headless", "pi"];
|
|
35
35
|
export { BaseAttachment, AdapterRegistry } from './base';
|
|
36
36
|
export { SdkAttachment } from './sdk/base';
|
|
37
37
|
export { InteractiveAttachment } from './claude-code';
|
package/dist/adapters/index.js
CHANGED
|
@@ -24,6 +24,7 @@ const copilot_1 = require("./copilot");
|
|
|
24
24
|
const claude_api_1 = require("./claude-api");
|
|
25
25
|
const opencode_1 = require("./opencode");
|
|
26
26
|
const claude_code_headless_1 = require("./claude-code-headless");
|
|
27
|
+
const pi_1 = require("./pi");
|
|
27
28
|
const config_1 = require("../config");
|
|
28
29
|
exports.registry = new base_1.AdapterRegistry();
|
|
29
30
|
exports.registry.register(claude_code_1.claudeCodeDescriptor);
|
|
@@ -45,6 +46,11 @@ exports.registry.register(opencode_1.opencodeDescriptor);
|
|
|
45
46
|
// `claude` binary is a system binary, not an npm dep; recruit pre-flight
|
|
46
47
|
// probes for installation + login state via `pre-flight.ts`.
|
|
47
48
|
exports.registry.register(claude_code_headless_1.claudeCodeHeadlessDescriptor);
|
|
49
|
+
// Phase 3a — headless Pi runtime. Registry/identity descriptor ONLY: the Pi
|
|
50
|
+
// extension singleton (Phase 2) owns the lifecycle, not a BaseAttachment driver
|
|
51
|
+
// (MD-D). Always-registered — the optional `@earendil-works/pi-coding-agent` SDK
|
|
52
|
+
// only matters at spawn time; recruit pre-flight gates on it (or `force: true`).
|
|
53
|
+
exports.registry.register(pi_1.piDescriptor);
|
|
48
54
|
/**
|
|
49
55
|
* Canonical list of production adapters registered at module-import time.
|
|
50
56
|
*
|
|
@@ -65,6 +71,7 @@ exports.CANONICAL_ADAPTER_IDS = [
|
|
|
65
71
|
'claude-api',
|
|
66
72
|
'opencode',
|
|
67
73
|
'claude-code-headless',
|
|
74
|
+
'pi',
|
|
68
75
|
];
|
|
69
76
|
// ADR 0014 §7 gate 2 — import-time registration gate. The mock adapter's
|
|
70
77
|
// descriptor only enters the registry when `isDevMode()` is true.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.main = main;
|
|
4
|
+
/**
|
|
5
|
+
* Headless Pi adapter entry point (Phase 3a). Spawned as a detached subprocess
|
|
6
|
+
* by `spawnPiHeadless` (src/spawn.ts) on a recruited `agent: 'pi'`. Reads its
|
|
7
|
+
* config from the env the spawn set, then delegates to `runHeadlessPi`, which
|
|
8
|
+
* injects the agent-tempo extension into Pi's `createAgentSession`.
|
|
9
|
+
*
|
|
10
|
+
* Unlike the other headless adapters, there is NO `BaseAttachment` driver here:
|
|
11
|
+
* the module-scope Pi extension singleton (Phase 2) owns claim / heartbeat /
|
|
12
|
+
* phase / tool registration / cue pump (MD-D). This file is purely the process
|
|
13
|
+
* entry + env→options wiring.
|
|
14
|
+
*
|
|
15
|
+
* Dual-purpose: importing this module is inert; only running it as the process
|
|
16
|
+
* entry (`require.main === module`) boots the runtime.
|
|
17
|
+
*/
|
|
18
|
+
const config_1 = require("../../config");
|
|
19
|
+
const headless_1 = require("../../pi/headless");
|
|
20
|
+
const log = (...args) => {
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.error('[agent-tempo:pi]', ...args);
|
|
23
|
+
};
|
|
24
|
+
/** Normalize the env tool-access value to the MD-C policy (default 'restricted'). */
|
|
25
|
+
function readToolAccess() {
|
|
26
|
+
const raw = process.env[config_1.ENV.TOOL_ACCESS];
|
|
27
|
+
return raw === 'standard' || raw === 'full' ? raw : 'restricted';
|
|
28
|
+
}
|
|
29
|
+
/** Parse the spawn-provided env into options and run. Exported for testing. */
|
|
30
|
+
async function main() {
|
|
31
|
+
await (0, headless_1.runHeadlessPi)({
|
|
32
|
+
toolAccess: readToolAccess(),
|
|
33
|
+
model: process.env[config_1.ENV.PI_MODEL] || undefined,
|
|
34
|
+
continueSessionId: process.env[config_1.ENV.PI_CONTINUE_SESSION] || undefined,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (require.main === module) {
|
|
38
|
+
main().catch((err) => {
|
|
39
|
+
log('FATAL — headless Pi adapter crashed:', err instanceof Error ? (err.stack ?? err.message) : err);
|
|
40
|
+
// eslint-disable-next-line no-process-exit
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi adapter — registry/identity descriptor ONLY (Phase 3a).
|
|
3
|
+
*
|
|
4
|
+
* Unlike the other headless adapters (copilot / opencode / claude-api /
|
|
5
|
+
* claude-code-headless), Pi does NOT have a `BaseAttachment` subprocess driver.
|
|
6
|
+
* The headless Pi runtime injects the Phase 2 `src/pi` EXTENSION into Pi's
|
|
7
|
+
* `createAgentSession` as an inline factory; the module-scope extension singleton
|
|
8
|
+
* owns claim / heartbeat / phase / tool registration / cue pump (MD-D). This
|
|
9
|
+
* descriptor exists purely for adapter identity, registry resolution, spawn
|
|
10
|
+
* routing, and heartbeat-cadence config (MD-A: 30s heartbeat, lease = 3×).
|
|
11
|
+
*
|
|
12
|
+
* Spawn entry: `src/adapters/pi/adapter.ts` (A2) → `src/pi/headless.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import type { AdapterDescriptor } from '../../types';
|
|
15
|
+
/** Pi headless adapter descriptor. `sdk` class (blocks on the LLM turn); 30s heartbeat. */
|
|
16
|
+
export declare const piDescriptor: AdapterDescriptor;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.piDescriptor = void 0;
|
|
4
|
+
/** Pi headless adapter descriptor. `sdk` class (blocks on the LLM turn); 30s heartbeat. */
|
|
5
|
+
exports.piDescriptor = {
|
|
6
|
+
adapterId: 'pi',
|
|
7
|
+
adapterClass: 'sdk',
|
|
8
|
+
blocksOnLLMTurn: true,
|
|
9
|
+
heartbeatMs: 30_000,
|
|
10
|
+
};
|
package/dist/client/core.js
CHANGED
|
@@ -432,16 +432,21 @@ function createTempoClientCore(client, opts = {}) {
|
|
|
432
432
|
// queries reject as `QueryTimeoutError`, `Promise.allSettled` sees
|
|
433
433
|
// three rejections and the existing all-rejected branch returns
|
|
434
434
|
// `null` — caller treats this player's wireMeta as missing.
|
|
435
|
-
const [runIdR, messagingR, leaseR] = await Promise.allSettled([
|
|
435
|
+
const [runIdR, messagingR, leaseR, coarseR] = await Promise.allSettled([
|
|
436
436
|
(0, query_timeout_1.queryHandleWithTimeout)(h, signals_1.getRunIdQuery),
|
|
437
437
|
(0, query_timeout_1.queryHandleWithTimeout)(h, signals_1.getMessagingStateQuery),
|
|
438
438
|
(0, query_timeout_1.queryHandleWithTimeout)(h, signals_1.getLeaseStateQuery),
|
|
439
|
+
// 3c Tier-1 — coarse activity (currentTool + context usage). Bounded like
|
|
440
|
+
// the others; an older session workflow without the handler rejects and
|
|
441
|
+
// is simply absent (additive/non-breaking).
|
|
442
|
+
(0, query_timeout_1.queryHandleWithTimeout)(h, signals_1.getCoarseActivityQuery),
|
|
439
443
|
]);
|
|
440
444
|
// If every query rejected, treat this as "session unreachable" —
|
|
441
445
|
// the caller renders no wire-meta rather than partial sentinels.
|
|
442
446
|
if (runIdR.status === 'rejected' &&
|
|
443
447
|
messagingR.status === 'rejected' &&
|
|
444
|
-
leaseR.status === 'rejected'
|
|
448
|
+
leaseR.status === 'rejected' &&
|
|
449
|
+
coarseR.status === 'rejected') {
|
|
445
450
|
return null;
|
|
446
451
|
}
|
|
447
452
|
const out = {};
|
|
@@ -451,6 +456,8 @@ function createTempoClientCore(client, opts = {}) {
|
|
|
451
456
|
out.messaging = messagingR.value;
|
|
452
457
|
if (leaseR.status === 'fulfilled')
|
|
453
458
|
out.lease = leaseR.value;
|
|
459
|
+
if (coarseR.status === 'fulfilled')
|
|
460
|
+
out.coarse = coarseR.value;
|
|
454
461
|
return out;
|
|
455
462
|
},
|
|
456
463
|
async getMessages(ensemble, limit) {
|
|
@@ -253,6 +253,12 @@ export interface TempoClientCore {
|
|
|
253
253
|
expiresAt: number | null;
|
|
254
254
|
leaseMs: number | null;
|
|
255
255
|
};
|
|
256
|
+
/** 3c Tier-1 — coarse activity (currentTool + context usage) from `getCoarseActivityQuery`. */
|
|
257
|
+
coarse?: {
|
|
258
|
+
currentTool: string | null;
|
|
259
|
+
contextTokens?: number;
|
|
260
|
+
contextPercent?: number;
|
|
261
|
+
};
|
|
256
262
|
} | null>;
|
|
257
263
|
/** Get recent messages for an ensemble. */
|
|
258
264
|
getMessages(ensemble: string, limit?: number): Promise<MaestroRelayMessage[]>;
|
package/dist/config.d.ts
CHANGED
|
@@ -48,6 +48,37 @@ export declare const ENV: {
|
|
|
48
48
|
* sandboxed contexts. Mutually exclusive with {@link PERMISSION_MODE}.
|
|
49
49
|
*/
|
|
50
50
|
readonly DANGEROUSLY_SKIP_PERMISSIONS: "AGENT_TEMPO_DANGEROUSLY_SKIP_PERMISSIONS";
|
|
51
|
+
/**
|
|
52
|
+
* Phase 3a — headless Pi runtime model selector. Pi takes a `provider/model`
|
|
53
|
+
* string (e.g. `anthropic/claude-opus-4-7`); absent → Pi's own default
|
|
54
|
+
* provider/model (the 3a anthropic-default path). Recruit `model` arg →
|
|
55
|
+
* this env → Pi default.
|
|
56
|
+
*/
|
|
57
|
+
readonly PI_MODEL: "AGENT_TEMPO_PI_MODEL";
|
|
58
|
+
/**
|
|
59
|
+
* Phase 3a — headless Pi restart-resume. The daemon reads `metadata.sessionId`
|
|
60
|
+
* (the Pi conversation id the player was in when it died) and passes it here;
|
|
61
|
+
* the headless entry resumes via Pi `continueSession(<id>)`. Absent on a fresh
|
|
62
|
+
* recruit → a new Pi session.
|
|
63
|
+
*/
|
|
64
|
+
readonly PI_CONTINUE_SESSION: "AGENT_TEMPO_PI_CONTINUE_SESSION";
|
|
65
|
+
/**
|
|
66
|
+
* Phase 3a / MD-C — headless Pi tool-access policy. One of
|
|
67
|
+
* `restricted` (default; Bash/shell/exec HARD-BLOCKED) | `standard` (scoped
|
|
68
|
+
* Bash) | `full` (unsandboxed; admin-gated at recruit). Read by the Pi
|
|
69
|
+
* extension's `tool_call` gate (mode='headless' only). Mirrors
|
|
70
|
+
* {@link PERMISSION_MODE}'s threading.
|
|
71
|
+
*/
|
|
72
|
+
readonly TOOL_ACCESS: "AGENT_TEMPO_TOOL_ACCESS";
|
|
73
|
+
/**
|
|
74
|
+
* 3c Tier-2 ingest auth. The daemon mints a per-player ingest token (scoped to
|
|
75
|
+
* the session workflowId) BEFORE spawning a headless Pi player and threads it
|
|
76
|
+
* into the subprocess env here. The player's inner-loop publisher presents it
|
|
77
|
+
* on `POST /inner/ingest` + `GET /inner/presence` (loopback), where the daemon
|
|
78
|
+
* validates it against the URL-derived workflowId (cross-player-spoof guard).
|
|
79
|
+
* Absent → the publisher's HTTP client is a no-op (no fine-tail forwarding).
|
|
80
|
+
*/
|
|
81
|
+
readonly INGEST_TOKEN: "AGENT_TEMPO_INGEST_TOKEN";
|
|
51
82
|
/**
|
|
52
83
|
* v0.25 PR-D attachment resume plumbing. When `restart` / `migrate`
|
|
53
84
|
* enqueues a spawn outbox entry, the workflow passes the pre-claimed
|
|
@@ -72,6 +103,16 @@ export declare const ENV: {
|
|
|
72
103
|
readonly DAEMON_PORT: "AGENT_TEMPO_DAEMON_PORT";
|
|
73
104
|
readonly CORS_ORIGINS: "AGENT_TEMPO_CORS_ORIGINS";
|
|
74
105
|
readonly SSE_MAX_CONNECTIONS: "AGENT_TEMPO_SSE_MAX_CONNECTIONS";
|
|
106
|
+
/**
|
|
107
|
+
* 3e RBAC (MD-E). Two-token model: the READ token (T1 — observe) may live in
|
|
108
|
+
* env or config.json and auto-generates; the ADMIN token (T1+T2+T3 — mutate +
|
|
109
|
+
* supervisory gate/inner) is ENV-VAR-ONLY (never config.json/disk, never
|
|
110
|
+
* auto-generated). `TLS_ACKNOWLEDGED=1` suppresses the non-loopback-bind
|
|
111
|
+
* plaintext-HTTP startup warning.
|
|
112
|
+
*/
|
|
113
|
+
readonly HTTP_READ_TOKEN: "AGENT_TEMPO_HTTP_READ_TOKEN";
|
|
114
|
+
readonly HTTP_ADMIN_TOKEN: "AGENT_TEMPO_HTTP_ADMIN_TOKEN";
|
|
115
|
+
readonly TLS_ACKNOWLEDGED: "AGENT_TEMPO_TLS_ACKNOWLEDGED";
|
|
75
116
|
/**
|
|
76
117
|
* Dev profile gate (ADR 0014 §5.2). One source of truth — every layer
|
|
77
118
|
* (paths, namespace, port, task queue, banner, registry gating) consults
|
|
@@ -115,8 +156,19 @@ export interface PersistedConfig {
|
|
|
115
156
|
* a request with a non-loopback `Origin`) and no token is set:
|
|
116
157
|
* `crypto.randomBytes(32).toString('base64url')`, 0600 on POSIX.
|
|
117
158
|
* Rotation = delete this field; next daemon boot regenerates.
|
|
159
|
+
*
|
|
160
|
+
* 3e: this LEGACY single token is migrated to the READ tier (T1) — a daemon
|
|
161
|
+
* with only `httpToken` set keeps read access and emits a one-time startup
|
|
162
|
+
* warning to set an admin token for writes/gate/inner. Prefer `readToken`.
|
|
118
163
|
*/
|
|
119
164
|
httpToken?: string;
|
|
165
|
+
/**
|
|
166
|
+
* 3e RBAC — the READ-tier (T1) bearer token. Env `AGENT_TEMPO_HTTP_READ_TOKEN`
|
|
167
|
+
* takes precedence over this; auto-generated here on first bearer-mode boot if
|
|
168
|
+
* neither is set. The ADMIN token is deliberately ABSENT from this file (it is
|
|
169
|
+
* env-var-only, never persisted).
|
|
170
|
+
*/
|
|
171
|
+
readToken?: string;
|
|
120
172
|
}
|
|
121
173
|
/**
|
|
122
174
|
* Dev profile defaults — one switch (`--dev` top-level flag, or
|
|
@@ -278,6 +330,33 @@ export declare function parseTemporalYaml(content: string): PersistedConfig;
|
|
|
278
330
|
* for empty/unset values so callers can use it as a source-aware default.
|
|
279
331
|
*/
|
|
280
332
|
export declare function parseAgent(value: string | undefined, source: ConfigSource): AgentType;
|
|
333
|
+
/**
|
|
334
|
+
* Result of {@link parsePiProviderModel}: the parsed parts, OR an `{ error }`
|
|
335
|
+
* describing why the selector is malformed. Non-throwing by design — a pure
|
|
336
|
+
* mapper returning a discriminated union (the recruit wiring branches
|
|
337
|
+
* `if ('error' in r) return fail(r.error)`, no try/catch).
|
|
338
|
+
*/
|
|
339
|
+
export type ProviderModel = {
|
|
340
|
+
provider: string;
|
|
341
|
+
model: string;
|
|
342
|
+
} | {
|
|
343
|
+
error: string;
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* Parse a Pi provider/model selector (e.g. `"github-copilot/gpt-4o"`) into its
|
|
347
|
+
* `{ provider, model }` parts for Pi's `createAgentSession` model option.
|
|
348
|
+
*
|
|
349
|
+
* Provider-agnostic: the segment before the FIRST `/` is the provider id,
|
|
350
|
+
* passed through VERBATIM (Copilot's pi-ai provider id is literally
|
|
351
|
+
* `github-copilot` — no normalization needed); everything after is the model
|
|
352
|
+
* id, which may itself contain `/` (e.g. `openrouter/anthropic/claude`).
|
|
353
|
+
*
|
|
354
|
+
* Fail-loud (no silent default): returns `{ error }` — never a fallback model —
|
|
355
|
+
* when the selector has no `/`, an empty provider, or an empty model. A bare
|
|
356
|
+
* provider with no model is rejected here; omitting the recruit `model` arg
|
|
357
|
+
* ENTIRELY is a different path (Pi's own default), handled upstream, not here.
|
|
358
|
+
*/
|
|
359
|
+
export declare function parsePiProviderModel(model: string): ProviderModel;
|
|
281
360
|
/** CLI flag overrides — passed down from the arg parser. */
|
|
282
361
|
export interface CliOverrides {
|
|
283
362
|
temporalAddress?: string;
|
package/dist/config.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.saveConfigFile = saveConfigFile;
|
|
|
11
11
|
exports.loadTemporalCliConfig = loadTemporalCliConfig;
|
|
12
12
|
exports.parseTemporalYaml = parseTemporalYaml;
|
|
13
13
|
exports.parseAgent = parseAgent;
|
|
14
|
+
exports.parsePiProviderModel = parsePiProviderModel;
|
|
14
15
|
exports.getConfig = getConfig;
|
|
15
16
|
exports.getConfigWithSources = getConfigWithSources;
|
|
16
17
|
exports.hostTaskQueue = hostTaskQueue;
|
|
@@ -78,6 +79,37 @@ exports.ENV = {
|
|
|
78
79
|
* sandboxed contexts. Mutually exclusive with {@link PERMISSION_MODE}.
|
|
79
80
|
*/
|
|
80
81
|
DANGEROUSLY_SKIP_PERMISSIONS: 'AGENT_TEMPO_DANGEROUSLY_SKIP_PERMISSIONS',
|
|
82
|
+
/**
|
|
83
|
+
* Phase 3a — headless Pi runtime model selector. Pi takes a `provider/model`
|
|
84
|
+
* string (e.g. `anthropic/claude-opus-4-7`); absent → Pi's own default
|
|
85
|
+
* provider/model (the 3a anthropic-default path). Recruit `model` arg →
|
|
86
|
+
* this env → Pi default.
|
|
87
|
+
*/
|
|
88
|
+
PI_MODEL: 'AGENT_TEMPO_PI_MODEL',
|
|
89
|
+
/**
|
|
90
|
+
* Phase 3a — headless Pi restart-resume. The daemon reads `metadata.sessionId`
|
|
91
|
+
* (the Pi conversation id the player was in when it died) and passes it here;
|
|
92
|
+
* the headless entry resumes via Pi `continueSession(<id>)`. Absent on a fresh
|
|
93
|
+
* recruit → a new Pi session.
|
|
94
|
+
*/
|
|
95
|
+
PI_CONTINUE_SESSION: 'AGENT_TEMPO_PI_CONTINUE_SESSION',
|
|
96
|
+
/**
|
|
97
|
+
* Phase 3a / MD-C — headless Pi tool-access policy. One of
|
|
98
|
+
* `restricted` (default; Bash/shell/exec HARD-BLOCKED) | `standard` (scoped
|
|
99
|
+
* Bash) | `full` (unsandboxed; admin-gated at recruit). Read by the Pi
|
|
100
|
+
* extension's `tool_call` gate (mode='headless' only). Mirrors
|
|
101
|
+
* {@link PERMISSION_MODE}'s threading.
|
|
102
|
+
*/
|
|
103
|
+
TOOL_ACCESS: 'AGENT_TEMPO_TOOL_ACCESS',
|
|
104
|
+
/**
|
|
105
|
+
* 3c Tier-2 ingest auth. The daemon mints a per-player ingest token (scoped to
|
|
106
|
+
* the session workflowId) BEFORE spawning a headless Pi player and threads it
|
|
107
|
+
* into the subprocess env here. The player's inner-loop publisher presents it
|
|
108
|
+
* on `POST /inner/ingest` + `GET /inner/presence` (loopback), where the daemon
|
|
109
|
+
* validates it against the URL-derived workflowId (cross-player-spoof guard).
|
|
110
|
+
* Absent → the publisher's HTTP client is a no-op (no fine-tail forwarding).
|
|
111
|
+
*/
|
|
112
|
+
INGEST_TOKEN: 'AGENT_TEMPO_INGEST_TOKEN',
|
|
81
113
|
/**
|
|
82
114
|
* v0.25 PR-D attachment resume plumbing. When `restart` / `migrate`
|
|
83
115
|
* enqueues a spawn outbox entry, the workflow passes the pre-claimed
|
|
@@ -102,6 +134,16 @@ exports.ENV = {
|
|
|
102
134
|
DAEMON_PORT: 'AGENT_TEMPO_DAEMON_PORT',
|
|
103
135
|
CORS_ORIGINS: 'AGENT_TEMPO_CORS_ORIGINS',
|
|
104
136
|
SSE_MAX_CONNECTIONS: 'AGENT_TEMPO_SSE_MAX_CONNECTIONS',
|
|
137
|
+
/**
|
|
138
|
+
* 3e RBAC (MD-E). Two-token model: the READ token (T1 — observe) may live in
|
|
139
|
+
* env or config.json and auto-generates; the ADMIN token (T1+T2+T3 — mutate +
|
|
140
|
+
* supervisory gate/inner) is ENV-VAR-ONLY (never config.json/disk, never
|
|
141
|
+
* auto-generated). `TLS_ACKNOWLEDGED=1` suppresses the non-loopback-bind
|
|
142
|
+
* plaintext-HTTP startup warning.
|
|
143
|
+
*/
|
|
144
|
+
HTTP_READ_TOKEN: 'AGENT_TEMPO_HTTP_READ_TOKEN',
|
|
145
|
+
HTTP_ADMIN_TOKEN: 'AGENT_TEMPO_HTTP_ADMIN_TOKEN',
|
|
146
|
+
TLS_ACKNOWLEDGED: 'AGENT_TEMPO_TLS_ACKNOWLEDGED',
|
|
105
147
|
/**
|
|
106
148
|
* Dev profile gate (ADR 0014 §5.2). One source of truth — every layer
|
|
107
149
|
* (paths, namespace, port, task queue, banner, registry gating) consults
|
|
@@ -420,6 +462,38 @@ function parseAgent(value, source) {
|
|
|
420
462
|
}
|
|
421
463
|
return value;
|
|
422
464
|
}
|
|
465
|
+
/**
|
|
466
|
+
* Parse a Pi provider/model selector (e.g. `"github-copilot/gpt-4o"`) into its
|
|
467
|
+
* `{ provider, model }` parts for Pi's `createAgentSession` model option.
|
|
468
|
+
*
|
|
469
|
+
* Provider-agnostic: the segment before the FIRST `/` is the provider id,
|
|
470
|
+
* passed through VERBATIM (Copilot's pi-ai provider id is literally
|
|
471
|
+
* `github-copilot` — no normalization needed); everything after is the model
|
|
472
|
+
* id, which may itself contain `/` (e.g. `openrouter/anthropic/claude`).
|
|
473
|
+
*
|
|
474
|
+
* Fail-loud (no silent default): returns `{ error }` — never a fallback model —
|
|
475
|
+
* when the selector has no `/`, an empty provider, or an empty model. A bare
|
|
476
|
+
* provider with no model is rejected here; omitting the recruit `model` arg
|
|
477
|
+
* ENTIRELY is a different path (Pi's own default), handled upstream, not here.
|
|
478
|
+
*/
|
|
479
|
+
function parsePiProviderModel(model) {
|
|
480
|
+
const raw = model.trim();
|
|
481
|
+
const slash = raw.indexOf('/');
|
|
482
|
+
if (slash < 0) {
|
|
483
|
+
return {
|
|
484
|
+
error: `model "${model}" must be a "provider/model" selector (e.g. "github-copilot/gpt-4o") — no "/" found.`,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const provider = raw.slice(0, slash).trim();
|
|
488
|
+
const modelId = raw.slice(slash + 1).trim();
|
|
489
|
+
if (!provider) {
|
|
490
|
+
return { error: `model "${model}" has an empty provider before "/" — expected e.g. "github-copilot/gpt-4o".` };
|
|
491
|
+
}
|
|
492
|
+
if (!modelId) {
|
|
493
|
+
return { error: `model "${model}" has an empty model after "/" — specify a model, e.g. "github-copilot/gpt-4o".` };
|
|
494
|
+
}
|
|
495
|
+
return { provider, model: modelId };
|
|
496
|
+
}
|
|
423
497
|
/**
|
|
424
498
|
* Resolve `defaultAgent` through the standard precedence chain and validate
|
|
425
499
|
* against the {@link AgentType} union. Each step passes its own source tag
|