@agentproto/runtime 0.4.0 → 0.6.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/LICENSE +202 -21
- package/README.md +2 -1
- package/dist/config.d.ts +130 -1
- package/dist/config.mjs +24 -1
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.ts +944 -33
- package/dist/index.mjs +5490 -707
- package/dist/index.mjs.map +1 -1
- package/dist/providers-store.d.ts +1 -57
- package/dist/providers-store.mjs +1 -84
- package/dist/providers-store.mjs.map +1 -1
- package/dist/resume-strategies.d.ts +18 -5
- package/dist/resume-strategies.mjs +14 -2
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/session-story.d.ts +113 -0
- package/dist/session-story.mjs +314 -0
- package/dist/session-story.mjs.map +1 -0
- package/dist/spawn-defaults-DAbADRd4.d.ts +256 -0
- package/dist/workspaces-config.d.ts +5 -1
- package/dist/workspaces-config.mjs +6 -1
- package/dist/workspaces-config.mjs.map +1 -1
- package/package.json +29 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,136 @@
|
|
|
1
1
|
import { DoctypeSpec } from '@agentproto/manifest';
|
|
2
2
|
import { B as BuildHeartbeatAgent } from './heartbeat-COGpMrJS.js';
|
|
3
3
|
export { H as HeartbeatAgent, a as HeartbeatRunner, R as RuntimeEvent, b as RuntimeEvents, p as parseDuration } from './heartbeat-COGpMrJS.js';
|
|
4
|
-
import { AcpMcpServer } from '@agentproto/acp';
|
|
4
|
+
import { AcpPermissionResolution, AcpMcpServer } from '@agentproto/acp';
|
|
5
5
|
import { ChildProcess } from 'node:child_process';
|
|
6
|
+
import { R as ResolvedAuthSpec, D as DeclaredAdapterOption, A as AdapterAuthDescriptor } from './spawn-defaults-DAbADRd4.js';
|
|
7
|
+
export { a as AuthEcho, b as AuthResolutionError, C as CredentialSource, c as DefaultsAdapterAuthConfig, d as DefaultsAdapterConfig, e as ResolvedSpawnAuthMaterial, f as ResolvedSpawnDefaults, S as SpawnDefaultsConfig, g as credentialFingerprint, n as normalizeSkillsOption, r as resolveAuthSpec, h as resolveSpawnDefaults } from './spawn-defaults-DAbADRd4.js';
|
|
6
8
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
-
import { AdapterHandle, AdapterLister } from '@agentproto/
|
|
9
|
+
import { AdapterHandle, SetupField, AdapterResolver, AdapterLister, AdapterEntry } from '@agentproto/provider-kit';
|
|
10
|
+
import { SandboxProvider } from '@agentproto/sandbox';
|
|
11
|
+
import { FrameSink, E2eFrameSink } from '@agentproto/acp/tunnel';
|
|
12
|
+
import { DaemonIdentity } from '@agentproto/secrets/identity';
|
|
8
13
|
import { WorkspaceFs } from './workspace-fs.js';
|
|
9
14
|
export { createWorkspaceFs } from './workspace-fs.js';
|
|
10
15
|
export { ConversationMeta, ConversationStore, ConversationTurn, fileConversationStore } from './conversations.js';
|
|
16
|
+
import { ProviderPreset } from '@agentproto/provider-presets';
|
|
11
17
|
import { StepCache } from '@agentproto/workflow-runtime';
|
|
12
|
-
export { PROVIDER_ENV_VARS, ProviderEntry, ProvidersFile, injectProviderKeysIntoEnv, loadProviders, providerEnvVar, providersPath, removeProviderKey, setProviderKey } from '
|
|
18
|
+
export { PROVIDER_ENV_VARS, ProviderEntry, ProvidersFile, getProviderKey, injectProviderKeysIntoEnv, loadProviders, providerEnvVar, providersPath, removeProviderKey, setProviderKey } from '@agentproto/providers-store';
|
|
19
|
+
import '@agentproto/model-catalog';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Per-session JSONL record of a completed `command_execute` invocation
|
|
23
|
+
* (and cron's `kind: "command"` action jobs, which share the same
|
|
24
|
+
* allowlist + `runCommand` path — see cron-scheduler.ts's "one
|
|
25
|
+
* enforcement path, not two" comment).
|
|
26
|
+
*
|
|
27
|
+
* Each call gets its OWN `kind: "command"` session, minted via
|
|
28
|
+
* `SessionsRegistry.recordCommand` — same as an agent-cli or PTY session,
|
|
29
|
+
* so it shows up in `session_list` / `command_list` for free instead of
|
|
30
|
+
* needing a bespoke query surface. Its result is stored at the exact
|
|
31
|
+
* per-id path an agent-cli session's structured transcript already uses:
|
|
32
|
+
* `~/.agentproto/sessions/<id>/events.jsonl` (see transcript-writer.ts).
|
|
33
|
+
* A command session's file holds exactly one line — there's no seq/turn
|
|
34
|
+
* machinery to reconstruct because the call is already finished by the
|
|
35
|
+
* time this is written.
|
|
36
|
+
*
|
|
37
|
+
* This module only knows how to read/write that one line; minting the
|
|
38
|
+
* session itself (and deciding the file's base directory) is
|
|
39
|
+
* `sessions.ts`'s job via `recordCommand`.
|
|
40
|
+
*/
|
|
41
|
+
interface CommandLogEntry {
|
|
42
|
+
ts: string;
|
|
43
|
+
command: string;
|
|
44
|
+
args: string[];
|
|
45
|
+
cwd: string;
|
|
46
|
+
exitCode: number;
|
|
47
|
+
signal: string | null;
|
|
48
|
+
durationMs: number;
|
|
49
|
+
stdout: string;
|
|
50
|
+
stderr: string;
|
|
51
|
+
truncated?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Per-session usage snapshot — the single place that decides a session's
|
|
56
|
+
* `costUsd` and where that number came from.
|
|
57
|
+
*
|
|
58
|
+
* Two cost sources feed a session:
|
|
59
|
+
* - an adapter's own usage reader (`readUsage`, e.g. hermes reads its
|
|
60
|
+
* state.db) or an ACP `usage_update` that carries a `cost` block — these
|
|
61
|
+
* are authoritative, tagged `source: "adapter"`.
|
|
62
|
+
* - raw input/output token counts with NO adapter cost (ACP adapters like
|
|
63
|
+
* claude-code / mastracode that report tokens but not dollars) — here we
|
|
64
|
+
* price them ourselves against agentproto's in-repo LLM pricing catalog,
|
|
65
|
+
* tagged `source: "computed"`.
|
|
66
|
+
*
|
|
67
|
+
* When tokens exist but the model is absent from the catalog we NEVER
|
|
68
|
+
* fabricate a price: `costUsd` is left undefined and the snapshot is tagged
|
|
69
|
+
* `source: "no-pricing"`. A session with neither cost nor tokens is
|
|
70
|
+
* `source: "none"`.
|
|
71
|
+
*
|
|
72
|
+
* This module is pure (the catalog lookup is injected) so the decision tree is
|
|
73
|
+
* unit-testable without building the whole model catalog.
|
|
74
|
+
*/
|
|
75
|
+
/** Where a session's `costUsd` came from — see the module doc. */
|
|
76
|
+
type UsageSource = "adapter" | "computed" | "no-pricing" | "none";
|
|
77
|
+
/** Per-token USD prices for a model (per 1M tokens), the subset of the
|
|
78
|
+
* catalog's `LLMPricing` this module needs. */
|
|
79
|
+
interface TokenPricing {
|
|
80
|
+
inputPer1M: number;
|
|
81
|
+
outputPer1M: number;
|
|
82
|
+
}
|
|
83
|
+
/** Pluggable pricing accessor — defaults to the in-repo catalog's
|
|
84
|
+
* `resolvePricing`, overridable in tests. Returns undefined for an
|
|
85
|
+
* unknown model (the caller then tags `no-pricing`). */
|
|
86
|
+
type PricingResolver = (model: string) => TokenPricing | undefined;
|
|
87
|
+
/** Raw signals gathered over a turn, handed to `deriveSessionUsage`. */
|
|
88
|
+
interface UsageComputeInput {
|
|
89
|
+
/** Requested model id — needed to look up per-token prices. */
|
|
90
|
+
model?: string;
|
|
91
|
+
/** Cost the adapter reported directly (readUsage or usage_update.cost).
|
|
92
|
+
* Present → authoritative, wins over token-based computation. */
|
|
93
|
+
adapterCostUsd?: number;
|
|
94
|
+
tokensIn?: number;
|
|
95
|
+
tokensOut?: number;
|
|
96
|
+
contextSize?: number;
|
|
97
|
+
contextUsed?: number;
|
|
98
|
+
}
|
|
99
|
+
/** The resolved usage snapshot — the shape `session_usage` returns and the
|
|
100
|
+
* durable `usage_snapshot` transcript record carries. Cost/token fields are
|
|
101
|
+
* omitted when absent so a missing value never reads as a measured zero. */
|
|
102
|
+
interface SessionUsage {
|
|
103
|
+
model?: string;
|
|
104
|
+
costUsd?: number;
|
|
105
|
+
tokensIn?: number;
|
|
106
|
+
tokensOut?: number;
|
|
107
|
+
contextSize?: number;
|
|
108
|
+
contextUsed?: number;
|
|
109
|
+
source: UsageSource;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Decide a session's usage snapshot + cost source from the raw signals.
|
|
113
|
+
*
|
|
114
|
+
* Priority: an adapter-reported cost wins; else price the tokens against the
|
|
115
|
+
* catalog; else `none`. Never fabricates a price for an unpriced model.
|
|
116
|
+
*/
|
|
117
|
+
declare function deriveSessionUsage(input: UsageComputeInput, resolve?: PricingResolver): SessionUsage;
|
|
118
|
+
/** Descriptor-shaped fields `projectSessionUsage` reads. */
|
|
119
|
+
interface UsageDescriptorFields {
|
|
120
|
+
model?: string;
|
|
121
|
+
costUsd?: number;
|
|
122
|
+
tokensIn?: number;
|
|
123
|
+
tokensOut?: number;
|
|
124
|
+
contextSize?: number;
|
|
125
|
+
contextUsed?: number;
|
|
126
|
+
usageSource?: UsageSource;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Project a session descriptor's persisted usage fields into the
|
|
130
|
+
* `session_usage` response shape. Omits absent fields; defaults an
|
|
131
|
+
* unstamped `usageSource` to `"none"`.
|
|
132
|
+
*/
|
|
133
|
+
declare function projectSessionUsage(desc: UsageDescriptorFields): SessionUsage;
|
|
13
134
|
|
|
14
135
|
/**
|
|
15
136
|
* In-process pub/sub bus for session lifecycle events. Separate from
|
|
@@ -20,7 +141,7 @@ export { PROVIDER_ENV_VARS, ProviderEntry, ProvidersFile, injectProviderKeysInto
|
|
|
20
141
|
* (fire-and-forget HTTP), RoutineRunner (state machine fan-in),
|
|
21
142
|
* and session_monitor MCP tool (long-poll multiplexed).
|
|
22
143
|
*/
|
|
23
|
-
type SessionEventType = "session:turn-end" | "session:awaiting-input" | "session:exited" | "session:command-done" | "policy:passed" | "policy:failed" | "policy:commit-ready" | "policy:committed" | "cron:fired" | "cron:succeeded" | "cron:failed";
|
|
144
|
+
type SessionEventType = "session:turn-end" | "session:awaiting-input" | "session:permission-request" | "session:permission-resolved" | "session:exited" | "session:command-done" | "policy:passed" | "policy:failed" | "policy:commit-ready" | "policy:committed" | "cron:fired" | "cron:succeeded" | "cron:failed";
|
|
24
145
|
/**
|
|
25
146
|
* Structured detail on why a session is awaiting input, when derivable.
|
|
26
147
|
* `source: "structured"` — a driver-reported ACP-style prompt (e.g. a tool
|
|
@@ -51,6 +172,16 @@ interface SessionTurnEndEvent {
|
|
|
51
172
|
* Absent for drivers that don't report a reason.
|
|
52
173
|
*/
|
|
53
174
|
reason?: string;
|
|
175
|
+
/**
|
|
176
|
+
* True when the turn completed normally but produced ZERO assistant
|
|
177
|
+
* output AND zero tool calls (and wasn't awaiting input) — a silent
|
|
178
|
+
* no-op. Grok via hermes/OpenRouter, and any invalid/rejected model id,
|
|
179
|
+
* complete a turn this way: `turnsCompleted` bumps, `$0` cost, empty
|
|
180
|
+
* transcript. Surfaced so an orchestrator can flag it instead of
|
|
181
|
+
* treating a green turn-end as real progress. Absent (not `false`) on
|
|
182
|
+
* a normal, productive turn.
|
|
183
|
+
*/
|
|
184
|
+
empty?: boolean;
|
|
54
185
|
}
|
|
55
186
|
interface SessionAwaitingInputEvent {
|
|
56
187
|
type: "session:awaiting-input";
|
|
@@ -59,6 +190,34 @@ interface SessionAwaitingInputEvent {
|
|
|
59
190
|
ts: string;
|
|
60
191
|
question?: SessionAwaitingQuestion;
|
|
61
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Emitted when a permission-hold session parks a `session/request_permission`
|
|
195
|
+
* (see the pending-permissions inbox in sessions.ts). `permissionId` is the
|
|
196
|
+
* stable id `permissions_respond` / `POST /permissions/:id` resolve it with.
|
|
197
|
+
*/
|
|
198
|
+
interface SessionPermissionRequestEvent {
|
|
199
|
+
type: "session:permission-request";
|
|
200
|
+
sessionId: string;
|
|
201
|
+
permissionId: string;
|
|
202
|
+
toolName?: string;
|
|
203
|
+
text: string;
|
|
204
|
+
label?: string;
|
|
205
|
+
ts: string;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Emitted when a parked permission is resolved — by an inbox
|
|
209
|
+
* approve/deny, or auto-`cancelled` when the session is torn down while the
|
|
210
|
+
* request is still pending.
|
|
211
|
+
*/
|
|
212
|
+
interface SessionPermissionResolvedEvent {
|
|
213
|
+
type: "session:permission-resolved";
|
|
214
|
+
sessionId: string;
|
|
215
|
+
permissionId: string;
|
|
216
|
+
decision: "approve" | "deny" | "cancelled";
|
|
217
|
+
optionId?: string;
|
|
218
|
+
label?: string;
|
|
219
|
+
ts: string;
|
|
220
|
+
}
|
|
62
221
|
interface SessionExitedEvent {
|
|
63
222
|
type: "session:exited";
|
|
64
223
|
sessionId: string;
|
|
@@ -66,6 +225,12 @@ interface SessionExitedEvent {
|
|
|
66
225
|
status: "exited" | "killed" | "error";
|
|
67
226
|
label?: string;
|
|
68
227
|
ts: string;
|
|
228
|
+
/** Mirrors `SessionDescriptor.endedReason` — set when this exit was the
|
|
229
|
+
* daemon dying underneath the session (crash-discovered-at-boot or a
|
|
230
|
+
* forced shutdown kill), not an operator targeting it. Lets a watcher
|
|
231
|
+
* (completion-policy supervisor, `session_monitor`) tell "my agent died
|
|
232
|
+
* because the daemon did" apart from a deliberate kill. Absent otherwise. */
|
|
233
|
+
reason?: "daemon-restart";
|
|
69
234
|
}
|
|
70
235
|
/** Emitted when command_execute finishes. commandId matches the id
|
|
71
236
|
* returned by the command_execute MCP tool. */
|
|
@@ -140,7 +305,7 @@ interface CronFailedEvent {
|
|
|
140
305
|
error: string;
|
|
141
306
|
ts: string;
|
|
142
307
|
}
|
|
143
|
-
type SessionEvent = SessionTurnEndEvent | SessionAwaitingInputEvent | SessionExitedEvent | SessionCommandDoneEvent | PolicyPassedEvent | PolicyFailedEvent | PolicyCommitReadyEvent | PolicyCommittedEvent | CronFiredEvent | CronSucceededEvent | CronFailedEvent;
|
|
308
|
+
type SessionEvent = SessionTurnEndEvent | SessionAwaitingInputEvent | SessionPermissionRequestEvent | SessionPermissionResolvedEvent | SessionExitedEvent | SessionCommandDoneEvent | PolicyPassedEvent | PolicyFailedEvent | PolicyCommitReadyEvent | PolicyCommittedEvent | CronFiredEvent | CronSucceededEvent | CronFailedEvent;
|
|
144
309
|
interface SessionEventBus {
|
|
145
310
|
emit(ev: SessionEvent): void;
|
|
146
311
|
/** Subscribe to a specific event type. Returns an unsubscribe fn. */
|
|
@@ -152,26 +317,40 @@ interface SessionEventBus {
|
|
|
152
317
|
}
|
|
153
318
|
|
|
154
319
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* daemon restart can show "you had these running before" instead of
|
|
161
|
-
* losing all visibility on a crash.
|
|
320
|
+
* A per-session observer — the pluggable seam behind the runtime's transcript
|
|
321
|
+
* tap. `sessions.ts` drives one observer per turn (prompt in, each stream event,
|
|
322
|
+
* the turn-boundary usage snapshot, close). The transcript writer is the first
|
|
323
|
+
* and, today, only member; additional observers (e.g. a Langfuse session tracer)
|
|
324
|
+
* attach without touching the turn loop.
|
|
162
325
|
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
326
|
+
* The method set is exactly the subset of `TranscriptWriter` the session loop
|
|
327
|
+
* calls, so `TranscriptWriter` IS a `SessionObserver` (see transcript-writer.ts).
|
|
328
|
+
*/
|
|
329
|
+
|
|
330
|
+
interface SessionObserver {
|
|
331
|
+
/** Record the outgoing message that opens a new turn. */
|
|
332
|
+
recordPrompt(sessionId: string, message: unknown): void;
|
|
333
|
+
/** Record one structured stream event (text-delta, tool-call, usage_update, …). */
|
|
334
|
+
recordEvent(sessionId: string, evt: AgentStreamEvent): void;
|
|
335
|
+
/** Record the durable turn-boundary / exit usage snapshot. */
|
|
336
|
+
recordUsageSnapshot(sessionId: string, usage: SessionUsage): void;
|
|
337
|
+
/** Flush and close this session's stream. Idempotent, fire-and-forget. */
|
|
338
|
+
close(sessionId: string): Promise<void>;
|
|
339
|
+
/** Close every open session stream (synchronous shutdown path). */
|
|
340
|
+
closeAll(): Promise<void>;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Fan a `SessionObserver` out over many. Each `record*` call is forwarded to
|
|
344
|
+
* every member in order; `close`/`closeAll` await all members.
|
|
168
345
|
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
346
|
+
* Every individual member call is isolated in try/catch so one throwing observer
|
|
347
|
+
* can neither break its siblings nor propagate into the turn loop — the
|
|
348
|
+
* transcript writer is effectively infallible today, but a future network-backed
|
|
349
|
+
* observer (a Langfuse sink) must never be able to throw a turn. Failures are
|
|
350
|
+
* swallowed here (no logging dependency at this layer); observers that need
|
|
351
|
+
* visibility into their own failures own that internally.
|
|
174
352
|
*/
|
|
353
|
+
declare function composeSessionObservers(observers: readonly SessionObserver[]): SessionObserver;
|
|
175
354
|
|
|
176
355
|
/**
|
|
177
356
|
* Minimal shape we need from a driver-agent-cli session — kept as a
|
|
@@ -189,6 +368,13 @@ interface AgentSessionLike {
|
|
|
189
368
|
pid?: number;
|
|
190
369
|
send(message: unknown): AsyncIterable<AgentStreamEvent>;
|
|
191
370
|
cancel(): Promise<void>;
|
|
371
|
+
/** Resolve a permission request the driver parked in permission-hold mode
|
|
372
|
+
* (see the pending-permissions inbox below). `requestId` is the driver's
|
|
373
|
+
* own stable id, surfaced on the `agent-prompt` event's `toolCallId`.
|
|
374
|
+
* Returns true when a matching parked request was resolved. Absent for
|
|
375
|
+
* drivers that don't model held permissions (sandbox proxy, future
|
|
376
|
+
* transports). */
|
|
377
|
+
respondPermission?(requestId: string, resolution: AcpPermissionResolution): boolean | Promise<boolean>;
|
|
192
378
|
close(): Promise<void>;
|
|
193
379
|
}
|
|
194
380
|
/**
|
|
@@ -227,6 +413,11 @@ interface AgentStreamEvent {
|
|
|
227
413
|
toolName?: string;
|
|
228
414
|
/** Tool-call input, e.g. an ACP `tool_call`'s `arguments` — see @agentproto/acp's `StreamEvent`. */
|
|
229
415
|
arguments?: unknown;
|
|
416
|
+
/** True when a "tool-call" event ENRICHES one already announced under the
|
|
417
|
+
* same `toolCallId` (the agent knew the input only after announcing the
|
|
418
|
+
* call) rather than announcing a new one — see @agentproto/acp's
|
|
419
|
+
* `StreamEvent`. Consumers merge by toolCallId; it is not a second call. */
|
|
420
|
+
isUpdate?: boolean;
|
|
230
421
|
/** Tool-call output, e.g. an ACP `tool_call_update`'s `result` — see @agentproto/acp's `StreamEvent`. */
|
|
231
422
|
result?: unknown;
|
|
232
423
|
isError?: boolean;
|
|
@@ -260,9 +451,28 @@ interface AgentStreamEvent {
|
|
|
260
451
|
amount: number;
|
|
261
452
|
currency: string;
|
|
262
453
|
};
|
|
454
|
+
/** "usage_update" cumulative input/output token counts, when the adapter
|
|
455
|
+
* reports them (lets the daemon price a session whose adapter gives tokens
|
|
456
|
+
* but no `cost`). */
|
|
457
|
+
tokensIn?: number;
|
|
458
|
+
tokensOut?: number;
|
|
263
459
|
}
|
|
264
460
|
type SessionKind = "terminal" | "agent-cli" | "command" | "browser";
|
|
265
461
|
type SessionStatus = "starting" | "running" | "exited" | "killed" | "error";
|
|
462
|
+
/**
|
|
463
|
+
* The billing-auth resolver's OBSERVABLE echo, recorded on a session
|
|
464
|
+
* descriptor. `mode` + `fingerprint` are always present when recorded (a
|
|
465
|
+
* credential resolved); `provider` / `credentialSource` / `setEnv` are the
|
|
466
|
+
* multi-provider resolver's additions (DECISION 9③/10②) — optional for
|
|
467
|
+
* back-compat with descriptors from before that resolver shipped.
|
|
468
|
+
*/
|
|
469
|
+
interface SessionAuthEcho {
|
|
470
|
+
mode: "subscription" | "api-key";
|
|
471
|
+
fingerprint: string;
|
|
472
|
+
provider?: string;
|
|
473
|
+
credentialSource?: "explicit-config" | "providers-store" | "none";
|
|
474
|
+
setEnv?: string;
|
|
475
|
+
}
|
|
266
476
|
interface SessionDescriptor {
|
|
267
477
|
id: string;
|
|
268
478
|
kind: SessionKind;
|
|
@@ -275,6 +485,16 @@ interface SessionDescriptor {
|
|
|
275
485
|
startedAt: string;
|
|
276
486
|
endedAt?: string;
|
|
277
487
|
exitCode?: number;
|
|
488
|
+
/** Set alongside `status: "killed"` when the session ended NOT because an
|
|
489
|
+
* operator targeted it (`kill()`) but because the daemon process it lived
|
|
490
|
+
* in went away out from under it — a hard crash discovered at next boot
|
|
491
|
+
* (`loadHistorySnapshot`'s wasAlive reclassification), or a graceful
|
|
492
|
+
* shutdown/restart that force-kills whatever's still busy
|
|
493
|
+
* (`shutdownImpl`). Absent for every other terminal path (operator kill,
|
|
494
|
+
* natural exit, turn error) — the session's own fault, or at least not
|
|
495
|
+
* the daemon's. Lets the UI show "crashed with the daemon" instead of a
|
|
496
|
+
* bare "killed" that reads as deliberate. */
|
|
497
|
+
endedReason?: "daemon-restart";
|
|
278
498
|
/** Last time anything was written to stdout/stderr. Lets the UI
|
|
279
499
|
* spot stuck sessions ("running for 2h, last output 12min ago"). */
|
|
280
500
|
lastOutputAt?: string;
|
|
@@ -315,6 +535,25 @@ interface SessionDescriptor {
|
|
|
315
535
|
adapterSlug?: string;
|
|
316
536
|
/** The model the session was requested to run (echoed back at spawn). */
|
|
317
537
|
model?: string;
|
|
538
|
+
/**
|
|
539
|
+
* Deterministic billing-auth mode + a non-secret credential fingerprint,
|
|
540
|
+
* recorded at spawn time for adapters that resolved an explicit
|
|
541
|
+
* credential (today: claude-code — see `AgentCliAuth.modes` in
|
|
542
|
+
* `@agentproto/driver-agent-cli`). The "verifiability" answer to "what
|
|
543
|
+
* was used": `mode` is the resolved `"subscription" | "api-key"`;
|
|
544
|
+
* `fingerprint` is `credentialFingerprint(mode, credential)` — e.g.
|
|
545
|
+
* `"subscription · sk-ant-oat…3f9c"` — NEVER the raw credential. Absent
|
|
546
|
+
* when no credential resolved (every adapter besides claude-code, in
|
|
547
|
+
* practice) or for a sandboxed spawn (the box's own daemon resolves its
|
|
548
|
+
* own credential independently). Surfaced in `agentproto sessions
|
|
549
|
+
* --watch`'s DETAIL pane and `agent_sessions_list`.
|
|
550
|
+
*
|
|
551
|
+
* Carries the resolver's OBSERVABLE echo (DECISION 9③/10②): `provider`,
|
|
552
|
+
* `credentialSource`, and the `setEnv` actually set — so a verifier checks
|
|
553
|
+
* the RESOLUTION, not the model's self-report. These are optional for
|
|
554
|
+
* back-compat with descriptors persisted before the multi-provider resolver.
|
|
555
|
+
*/
|
|
556
|
+
auth?: SessionAuthEcho;
|
|
318
557
|
/** Cumulative estimated USD cost of the session — best-effort, refreshed
|
|
319
558
|
* on each turn-end from the adapter's usage reader (e.g. hermes reads its
|
|
320
559
|
* state.db). Absent for adapters with no usage source. */
|
|
@@ -322,6 +561,16 @@ interface SessionDescriptor {
|
|
|
322
561
|
/** Cumulative input / output token counts (same source + cadence as costUsd). */
|
|
323
562
|
tokensIn?: number;
|
|
324
563
|
tokensOut?: number;
|
|
564
|
+
/** Context-window size + tokens-in-context from the latest `usage_update`
|
|
565
|
+
* event (ACP adapters that report a context window). Refreshed live as
|
|
566
|
+
* usage_update events arrive, not just at turn-end. */
|
|
567
|
+
contextSize?: number;
|
|
568
|
+
contextUsed?: number;
|
|
569
|
+
/** Where `costUsd` came from — `"adapter"` (adapter's own reader or a
|
|
570
|
+
* usage_update cost block), `"computed"` (tokens × in-repo catalog price),
|
|
571
|
+
* `"no-pricing"` (tokens present but the model isn't in the catalog — cost
|
|
572
|
+
* deliberately left undefined), or `"none"`. Stamped at each turn-end. */
|
|
573
|
+
usageSource?: UsageSource;
|
|
325
574
|
/** ACP-level session id (the adapter's own handle — claude-code's
|
|
326
575
|
* conversation id, hermes' chat id, …). Set at spawnAgent time
|
|
327
576
|
* from `agentSession.sessionId`. Survives across daemon restarts
|
|
@@ -362,6 +611,14 @@ interface SessionDescriptor {
|
|
|
362
611
|
* drivers that don't report structured prompts. Cleared alongside
|
|
363
612
|
* `awaitingInput` on the next turn start. */
|
|
364
613
|
awaitingQuestion?: SessionAwaitingQuestion;
|
|
614
|
+
/** True while this session (spawned in permission-hold mode) has at least
|
|
615
|
+
* one permission request parked in the inbox awaiting a human/orchestrator
|
|
616
|
+
* decision. Distinct from `awaitingInput` (a conversational question) — a
|
|
617
|
+
* held permission is resolved via `permissions_respond` / `agentproto
|
|
618
|
+
* permissions approve|deny`, not a normal prompt. Set when a request is
|
|
619
|
+
* registered, cleared when the last pending one for this session resolves.
|
|
620
|
+
* Absent (never `false`) for sessions with no held permissions. */
|
|
621
|
+
awaitingPermission?: boolean;
|
|
365
622
|
/** Count of turns that have fully completed (turn-end emitted) on this
|
|
366
623
|
* session. Lets `session_monitor` fast-return for a session that already
|
|
367
624
|
* finished its turn before the wait subscribed — a fast turn that ends
|
|
@@ -374,6 +631,21 @@ interface SessionDescriptor {
|
|
|
374
631
|
* finished turn" from "mid-turn" so it doesn't fast-return a stale
|
|
375
632
|
* turn-end while the NEXT turn is still generating. */
|
|
376
633
|
busy?: boolean;
|
|
634
|
+
/** What the in-flight turn is currently blocked on, when classifiable
|
|
635
|
+
* from the pending tool call: a spawned sub-agent (`agent_start`) or a
|
|
636
|
+
* shell/terminal command. Deliberately NO "user" variant — waiting on
|
|
637
|
+
* the user is already covered by `awaitingInput`/`awaitingQuestion`.
|
|
638
|
+
*
|
|
639
|
+
* Set on tool-call; released (see `releaseBlockedOn`) on the MATCHING
|
|
640
|
+
* tool-result, on `error` (a failing tool never emits a result), on the
|
|
641
|
+
* next assistant `text-delta` (the model has the floor, so nothing is
|
|
642
|
+
* pending), at turn start, and in the turn's finally. A claim about the
|
|
643
|
+
* present tense — anything that disproves it must clear it. */
|
|
644
|
+
blockedOn?: "subagent" | "command";
|
|
645
|
+
/** toolCallId of the tool-call that set `blockedOn`. A tool-result only
|
|
646
|
+
* clears `blockedOn` when its toolCallId matches — so a nested or
|
|
647
|
+
* interleaved tool finishing first can't clear the flag early. */
|
|
648
|
+
pendingToolCallId?: string;
|
|
377
649
|
/** Session id of the orchestrator that spawned this session, set when
|
|
378
650
|
* the spawn arrived via the scoped orchestrator sub-gateway (the
|
|
379
651
|
* token carried the spawner's identity). Absent for direct `/mcp`
|
|
@@ -387,6 +659,17 @@ interface SessionDescriptor {
|
|
|
387
659
|
* Treated as 0 when absent (legacy rows / direct spawns that predate
|
|
388
660
|
* WP4). */
|
|
389
661
|
depth?: number;
|
|
662
|
+
/** Id of the most recently-completed `kind: "command"` session spawned
|
|
663
|
+
* with the same `cwd`, found at spawn time — see `recordCommand` and
|
|
664
|
+
* `findPriorCommandSessionId`. Deliberately a REFERENCE, not copied
|
|
665
|
+
* content: a new session's own ring buffer / structured transcript
|
|
666
|
+
* never gets a prior command_execute's stdout/stderr spliced in, it
|
|
667
|
+
* just gets told where to look (`command_log_tail({sessionId})` or
|
|
668
|
+
* `agent_export`/`command_list` resolve the id into the full record).
|
|
669
|
+
* Set on `spawnAgent`/`spawnPty` when the registry already has a
|
|
670
|
+
* matching command session; absent otherwise (including for legacy
|
|
671
|
+
* rows persisted before this field existed). */
|
|
672
|
+
priorCommandSessionId?: string;
|
|
390
673
|
/** Adapter id that drives this session (e.g. "camofox", "bureau"). */
|
|
391
674
|
browserAdapterId?: string;
|
|
392
675
|
/** Port the browser service listens on. */
|
|
@@ -395,7 +678,63 @@ interface SessionDescriptor {
|
|
|
395
678
|
browserBaseUrl?: string;
|
|
396
679
|
/** Execution location — "local" (default) or "cloud". */
|
|
397
680
|
browserLocation?: "local" | "cloud";
|
|
681
|
+
/** True when this agent-cli session is running inside a sandbox (`agent_start.sandbox`)
|
|
682
|
+
* rather than as a local subprocess — there's no local PID to check, so
|
|
683
|
+
* `processAlive` never applies (it's already absent whenever `pid` is null). */
|
|
684
|
+
remote?: boolean;
|
|
685
|
+
/** Provider-assigned sandbox id (`BootedSandbox.sandboxId`), when `remote` is true. */
|
|
686
|
+
sandboxId?: string;
|
|
687
|
+
/** What session close does to the box (PR3 lifecycle) — `"kill"` (the
|
|
688
|
+
* default, ephemeral) or `"pause"` (keeps `sandboxId` reconnectable via
|
|
689
|
+
* `agent_start.sandbox.reuse`). Only set when `remote` is true. */
|
|
690
|
+
sandboxTeardown?: "kill" | "pause";
|
|
398
691
|
}
|
|
692
|
+
/**
|
|
693
|
+
* A `session/request_permission` request parked by a permission-hold session,
|
|
694
|
+
* awaiting a human/orchestrator decision through the cross-session inbox
|
|
695
|
+
* (`permissions_list` / `permissions_respond`, `GET /permissions` /
|
|
696
|
+
* `POST /permissions/:id`, `agentproto permissions ls|approve|deny`).
|
|
697
|
+
*/
|
|
698
|
+
interface PendingPermission {
|
|
699
|
+
/** Stable id — the driver's own request id, unique across sessions. Pass to
|
|
700
|
+
* `respondPermission`. */
|
|
701
|
+
id: string;
|
|
702
|
+
sessionId: string;
|
|
703
|
+
/** ACP tool-call id the request was raised for (== `id` today; kept distinct
|
|
704
|
+
* so a future driver can surface a separate correlation id). */
|
|
705
|
+
toolCallId: string;
|
|
706
|
+
/** Tool title/kind the agent is asking permission for, when known. */
|
|
707
|
+
toolName?: string;
|
|
708
|
+
/** Human-readable "Allow X?" line. */
|
|
709
|
+
text: string;
|
|
710
|
+
/** Options the agent offered (ACP `requestPermission` shape). */
|
|
711
|
+
options: Array<{
|
|
712
|
+
optionId: string;
|
|
713
|
+
name?: string;
|
|
714
|
+
kind?: string;
|
|
715
|
+
}>;
|
|
716
|
+
/** ISO timestamp the request was parked. */
|
|
717
|
+
requestedAt: string;
|
|
718
|
+
}
|
|
719
|
+
/** How a caller resolves a pending permission — an explicit `optionId` wins
|
|
720
|
+
* over the `decision`→option mapping (approve → an allow-flavored option,
|
|
721
|
+
* deny → a reject-flavored option). `scope: "always"` prefers an
|
|
722
|
+
* allow-always option when the request offers one. */
|
|
723
|
+
interface PermissionRespondInput {
|
|
724
|
+
decision: "approve" | "deny";
|
|
725
|
+
optionId?: string;
|
|
726
|
+
scope?: "once" | "always";
|
|
727
|
+
}
|
|
728
|
+
type PermissionRespondResult = {
|
|
729
|
+
ok: true;
|
|
730
|
+
permission: PendingPermission;
|
|
731
|
+
decision: "approve" | "deny";
|
|
732
|
+
optionId?: string;
|
|
733
|
+
} | {
|
|
734
|
+
ok: false;
|
|
735
|
+
error: "not_found" | "session_gone" | "no_matching_option" | "unsupported";
|
|
736
|
+
message: string;
|
|
737
|
+
};
|
|
399
738
|
interface SessionsRegistry {
|
|
400
739
|
spawn(input: SpawnSessionInput): SessionDescriptor;
|
|
401
740
|
/** Adopt a ChildProcess that was spawned outside the registry —
|
|
@@ -417,17 +756,47 @@ interface SessionsRegistry {
|
|
|
417
756
|
* `attachPty(id, ...)`. Throws when the registry was constructed
|
|
418
757
|
* without a `spawnPty` factory (node-pty optional dep missing). */
|
|
419
758
|
spawnPty(input: SpawnPtyInput): SessionDescriptor;
|
|
759
|
+
/** Register a COMPLETED `command_execute` (or cron `kind:"command"`
|
|
760
|
+
* action) invocation as its own `kind: "command"` session. Unlike
|
|
761
|
+
* `spawn`/`spawnAgent`/`spawnPty`, there's no live process to track —
|
|
762
|
+
* the command already ran to completion via `runCommand` before this
|
|
763
|
+
* is called — so the descriptor is minted already "finished"
|
|
764
|
+
* (status exited/error, startedAt backdated by `durationMs`,
|
|
765
|
+
* endedAt now) and its full result is written to that session's own
|
|
766
|
+
* `events.jsonl` (see command-log.ts), the same per-id path an
|
|
767
|
+
* agent-cli session's structured transcript uses. Synchronous: the
|
|
768
|
+
* descriptor (and its id) is available immediately, the JSONL write
|
|
769
|
+
* itself is fire-and-forget internally. */
|
|
770
|
+
recordCommand(input: RecordCommandInput): SessionDescriptor;
|
|
771
|
+
/** Read back a `kind:"command"` session's full logged result (the
|
|
772
|
+
* `CommandLogEntry` `recordCommand` wrote). Resolves to null when the
|
|
773
|
+
* session isn't a command session or has no recorded entry. Routed
|
|
774
|
+
* through the registry (rather than callers importing
|
|
775
|
+
* `readCommandLogEntry` directly) so the read always targets the same
|
|
776
|
+
* base directory `recordCommand` wrote to — that dir is test-overridable
|
|
777
|
+
* (`transcriptDir`) and callers outside sessions.ts have no other way
|
|
778
|
+
* to know it. */
|
|
779
|
+
readCommandLog(sessionId: string): Promise<CommandLogEntry | null>;
|
|
420
780
|
/** Register an already-running browser service adapter as a tracked
|
|
421
781
|
* session (kind="browser"). Idempotent by identity — each call
|
|
422
782
|
* mints a fresh session id. The `stop` callback is invoked by
|
|
423
783
|
* `kill()` best-effort. */
|
|
424
784
|
registerBrowser(input: RegisterBrowserInput): SessionDescriptor;
|
|
425
|
-
/** Send a follow-up turn to a live agent session. Throws
|
|
426
|
-
* session is missing, not an agent-cli kind, dead (exited/
|
|
427
|
-
* error and unresumable — `SessionNotAliveError`), or busy
|
|
785
|
+
/** Send a follow-up turn to a live agent session and AWAIT it. Throws
|
|
786
|
+
* when the session is missing, not an agent-cli kind, dead (exited/
|
|
787
|
+
* killed/error and unresumable — `SessionNotAliveError`), or busy
|
|
428
788
|
* (mid-turn). The events stream into the existing ring buffer +
|
|
429
|
-
* line emitter so /stream consumers see them as they arrive.
|
|
430
|
-
|
|
789
|
+
* line emitter so /stream consumers see them as they arrive.
|
|
790
|
+
*
|
|
791
|
+
* `opts.interrupt` behaves exactly as it does on `enqueuePrompt`:
|
|
792
|
+
* a mid-turn session has its in-flight turn cancelled and settled
|
|
793
|
+
* before admission, so this prompt redirects the SAME live session
|
|
794
|
+
* instead of hitting the busy rejection. Ignored (identical to the
|
|
795
|
+
* default) when the session is idle. Omitted or `false` reproduces
|
|
796
|
+
* the previous mid-turn rejection byte-for-byte. */
|
|
797
|
+
sendPrompt(id: string, message: unknown, opts?: {
|
|
798
|
+
interrupt?: boolean;
|
|
799
|
+
}): Promise<void>;
|
|
431
800
|
/** Fire-and-forget variant of `sendPrompt` for the TURN ITSELF only.
|
|
432
801
|
* Admission (resume attempt + the missing/wrong-kind/dead/busy
|
|
433
802
|
* checks `sendPrompt` throws) is AWAITED before this resolves, so a
|
|
@@ -437,8 +806,18 @@ interface SessionsRegistry {
|
|
|
437
806
|
* mid-turn) are pushed into the ring buffer as `[error]` lines so
|
|
438
807
|
* `/stream` subscribers see them. Used by the web UI's chat input
|
|
439
808
|
* and MCP `agent_prompt`, where a long turn would otherwise freeze
|
|
440
|
-
* the caller.
|
|
441
|
-
|
|
809
|
+
* the caller.
|
|
810
|
+
*
|
|
811
|
+
* `opts.interrupt` lets a caller redirect a mid-turn session instead
|
|
812
|
+
* of hitting the busy rejection: the in-flight turn is cancelled
|
|
813
|
+
* (`agentSession.cancel()`), admission waits for that turn to
|
|
814
|
+
* actually settle (busy flips false — no fixed sleep), then the new
|
|
815
|
+
* prompt is admitted + fired on the SAME live session. Ignored
|
|
816
|
+
* (identical to the default) when the session is idle. Omitted or
|
|
817
|
+
* `false` reproduces today's mid-turn rejection byte-for-byte. */
|
|
818
|
+
enqueuePrompt(id: string, message: unknown, opts?: {
|
|
819
|
+
interrupt?: boolean;
|
|
820
|
+
}): Promise<void>;
|
|
442
821
|
/** Stamp `lastActivityAt` on a live agent-cli session's descriptor
|
|
443
822
|
* and schedule a debounced persist. Called from the `onActivity`
|
|
444
823
|
* callback threaded down through the driver → ACP client, which
|
|
@@ -452,6 +831,14 @@ interface SessionsRegistry {
|
|
|
452
831
|
* Initial backfill: synchronously invokes `onLine` once for each
|
|
453
832
|
* line currently in the ring buffer so attaches show context. */
|
|
454
833
|
attach(id: string, onLine: (line: string, stream: "stdout" | "stderr") => void): (() => void) | null;
|
|
834
|
+
/** Subscribe to a session's structured events.jsonl records as they're
|
|
835
|
+
* written — the live-push half of `GET /sessions/:id/events/stream`'s
|
|
836
|
+
* replay-then-subscribe handoff. Thin passthrough to the transcript
|
|
837
|
+
* writer's own `subscribe` (see transcript-writer.ts for the exactly-
|
|
838
|
+
* once contract this enables); no backfill, no existence check — a
|
|
839
|
+
* session id with no writer state simply never calls back. Returns an
|
|
840
|
+
* unsubscribe fn. */
|
|
841
|
+
subscribeToRecords(id: string, onRecord: (record: Record<string, unknown>) => void): () => void;
|
|
455
842
|
/** Subscribe to a PTY session's byte stream. Returns a control
|
|
456
843
|
* handle (write/resize/detach) and null when the session is
|
|
457
844
|
* missing or not a PTY kind. Replays the ring buffer
|
|
@@ -483,6 +870,17 @@ interface SessionsRegistry {
|
|
|
483
870
|
* tail. Returns null when the session is missing or not a PTY. */
|
|
484
871
|
readTerminalOutput(id: string, lastBytes?: number): Buffer | null;
|
|
485
872
|
kill(id: string, signal?: NodeJS.Signals): boolean;
|
|
873
|
+
/** List permission requests currently parked in the pending-permissions
|
|
874
|
+
* inbox across all permission-hold sessions, newest last. Optionally
|
|
875
|
+
* filtered to one session. */
|
|
876
|
+
listPendingPermissions(filter?: {
|
|
877
|
+
sessionId?: string;
|
|
878
|
+
}): PendingPermission[];
|
|
879
|
+
/** Resolve a parked permission by id — maps the decision (or explicit
|
|
880
|
+
* optionId) to one of the offered options, resolves the held driver RPC,
|
|
881
|
+
* clears the session's awaiting-permission state, and emits
|
|
882
|
+
* `session:permission-resolved`. */
|
|
883
|
+
respondPermission(id: string, input: PermissionRespondInput): Promise<PermissionRespondResult>;
|
|
486
884
|
/** Stop tracking a session (after it exited and the user clicked
|
|
487
885
|
* "clear"). Doesn't kill — use `kill` first. */
|
|
488
886
|
forget(id: string): boolean;
|
|
@@ -555,6 +953,11 @@ interface SpawnAgentInput {
|
|
|
555
953
|
depth?: number;
|
|
556
954
|
/** Requested model id — recorded on the descriptor for display + echo. */
|
|
557
955
|
model?: string;
|
|
956
|
+
/** Resolved auth echo (mode + fingerprint + provider/source/setEnv) —
|
|
957
|
+
* recorded verbatim onto {@link SessionDescriptor.auth}. See that field's
|
|
958
|
+
* doc for the full contract; the caller (`session-spawn.ts`) computes this
|
|
959
|
+
* via the billing-auth resolver, never passing the raw credential here. */
|
|
960
|
+
auth?: SessionAuthEcho;
|
|
558
961
|
/** Hard ceiling on cumulative session cost (USD). When set and the
|
|
559
962
|
* adapter's usage reader reports a higher cost at a turn-end, the session
|
|
560
963
|
* is stopped (best-effort, turn-granular — caps continuation, can't abort
|
|
@@ -569,6 +972,23 @@ interface SpawnAgentInput {
|
|
|
569
972
|
tokensIn?: number;
|
|
570
973
|
tokensOut?: number;
|
|
571
974
|
} | null>;
|
|
975
|
+
/** Opt this session into Langfuse tracing (prompt/completion + tool spans +
|
|
976
|
+
* tokens/cost). Effective opt-in is `trace ?? opts.langfuseTracingDefault ?? false`. */
|
|
977
|
+
trace?: boolean;
|
|
978
|
+
/** True when `agentSession` is a `SandboxAgentSessionProxy` (`agent_start.sandbox`)
|
|
979
|
+
* rather than a local subprocess-backed session — stamped onto the descriptor
|
|
980
|
+
* as `remote` since there's no local PID to report. */
|
|
981
|
+
remote?: boolean;
|
|
982
|
+
/** Provider-assigned sandbox id, when `remote` is true. */
|
|
983
|
+
sandboxId?: string;
|
|
984
|
+
/** What session close does to the box, when `remote` is true — see
|
|
985
|
+
* `SessionDescriptor.sandboxTeardown`. */
|
|
986
|
+
sandboxTeardown?: "kill" | "pause";
|
|
987
|
+
/** True when the driver session was started in permission-hold mode
|
|
988
|
+
* (`AgentCliStartOptions.permissionHold`) — its `agent-prompt` events carry
|
|
989
|
+
* respondable permission requests. Gates whether the registry registers
|
|
990
|
+
* them in the pending-permissions inbox. Default false. */
|
|
991
|
+
permissionHold?: boolean;
|
|
572
992
|
}
|
|
573
993
|
interface SpawnSessionInput {
|
|
574
994
|
kind: SessionKind;
|
|
@@ -612,6 +1032,24 @@ interface SpawnPtyInput {
|
|
|
612
1032
|
name?: string;
|
|
613
1033
|
label?: string;
|
|
614
1034
|
}
|
|
1035
|
+
interface RecordCommandInput {
|
|
1036
|
+
workspaceSlug: string;
|
|
1037
|
+
/** Working directory the command actually ran in (post cwd-anchoring).
|
|
1038
|
+
* Matched against a fresh session's cwd by `findPriorCommandSessionId`. */
|
|
1039
|
+
cwd: string;
|
|
1040
|
+
command: string;
|
|
1041
|
+
args: string[];
|
|
1042
|
+
/** Fields mirror `command-tools.ts`'s `ExecuteResult` — kept inline here
|
|
1043
|
+
* (rather than importing that type) so sessions.ts has no dependency
|
|
1044
|
+
* on command-tools.ts. */
|
|
1045
|
+
exitCode: number;
|
|
1046
|
+
signal: string | null;
|
|
1047
|
+
durationMs: number;
|
|
1048
|
+
stdout: string;
|
|
1049
|
+
stderr: string;
|
|
1050
|
+
truncated?: boolean;
|
|
1051
|
+
label?: string;
|
|
1052
|
+
}
|
|
615
1053
|
|
|
616
1054
|
/**
|
|
617
1055
|
* MCP tools that expose browser service adapters (Camofox, Bureau, …) to
|
|
@@ -714,7 +1152,7 @@ interface BrowserAdapterInfo {
|
|
|
714
1152
|
* `TunnelRegistry` (multi-tunnel general surface) can share the same
|
|
715
1153
|
* provider contract without creating a circular dep.
|
|
716
1154
|
*
|
|
717
|
-
* The tunnel family is the FIRST consumer of `@agentproto/
|
|
1155
|
+
* The tunnel family is the FIRST consumer of `@agentproto/provider-kit`:
|
|
718
1156
|
* {@link TunnelProviderHandle} extends the kit's generic `AdapterHandle`
|
|
719
1157
|
* (slug/name/version/description/requiresSetup/check) with the
|
|
720
1158
|
* tunnel-specific `capabilities` + the existing `start`/`stop` lifecycle.
|
|
@@ -896,13 +1334,163 @@ declare class TunnelRegistry {
|
|
|
896
1334
|
* third-party secrets) merged with descriptor-carried config (named's
|
|
897
1335
|
* hostname/tunnelId/credentialsFile), descriptor taking precedence.
|
|
898
1336
|
*/
|
|
899
|
-
|
|
1337
|
+
protected credsForDescriptor(desc: TunnelDescriptor): Promise<TunnelCreds>;
|
|
900
1338
|
private findEntryByIdOrName;
|
|
901
1339
|
private schedulePersist;
|
|
902
1340
|
private persistNow;
|
|
903
1341
|
private loadFromDisk;
|
|
904
1342
|
}
|
|
905
1343
|
|
|
1344
|
+
/**
|
|
1345
|
+
* Daemon-side pairing registry (design: DESIGN §3/§4, PLAN deliverable 2).
|
|
1346
|
+
*
|
|
1347
|
+
* Owns everything the daemon needs to be *paired with* over an untrusted
|
|
1348
|
+
* rendezvous:
|
|
1349
|
+
*
|
|
1350
|
+
* - **Offer store** — in-memory, single-use, expiry-checked one-time tokens
|
|
1351
|
+
* minted by `pair offer`. A daemon restart voids outstanding offers
|
|
1352
|
+
* (acceptable + documented). The P1 `verifyOfferToken` predicate plugs in
|
|
1353
|
+
* here and SPENDS the token on the first successful handshake.
|
|
1354
|
+
* - **Pairings store** — `~/.agentproto/pairings.json` (0600, atomic write),
|
|
1355
|
+
* one record per paired client: `{clientPub, name, fingerprint, createdAt,
|
|
1356
|
+
* lastSeen, pairRoot, rendezvousUrl}`. `pairRoot` is the long-term shared
|
|
1357
|
+
* secret (`derivePairRoot`) from which reconnect routing tokens derive.
|
|
1358
|
+
* - **Rendezvous connections** — for a fresh offer, and for every persisted
|
|
1359
|
+
* pairing on boot (autoconnect), the daemon dials the rendezvous *outbound*
|
|
1360
|
+
* (`side=daemon&t=<token>`), runs `daemonHandshakeOverSink` with the P1
|
|
1361
|
+
* crypto, and — on success — serves the spliced, E2E-wrapped channel exactly
|
|
1362
|
+
* like `serve --connect` serves a tunnel host. The serving path itself is
|
|
1363
|
+
* injected (`serve`) so this module stays free of pty/adapter concerns and
|
|
1364
|
+
* the CLI can reuse its `createTunnelServer` config verbatim.
|
|
1365
|
+
* - **Reconnect epochs** — a persisted pairing's standing connection uses the
|
|
1366
|
+
* pairing-derived routing token `t' = HKDF(pairRoot, "rv-route"‖epoch)`,
|
|
1367
|
+
* rotated per UTC day. The daemon parks on BOTH the current and previous
|
|
1368
|
+
* epoch tokens so a client whose clock straddles midnight still finds it;
|
|
1369
|
+
* reconnect-with-backoff keeps the standing connection alive.
|
|
1370
|
+
* - **Revocation** — removing a pairing drops its rendezvous connections and
|
|
1371
|
+
* stops the daemon parking on its tokens, so it can no longer be reached.
|
|
1372
|
+
*
|
|
1373
|
+
* ## Reconnect authentication (why no stable client key)
|
|
1374
|
+
*
|
|
1375
|
+
* P1's handshake carries only a client *ephemeral* key (regenerated per
|
|
1376
|
+
* handshake) — there is no persistent client identity key, and the PLAN forbids
|
|
1377
|
+
* changing the handshake. So a reconnect authenticates the client by
|
|
1378
|
+
* **possession of the epoch routing token**, which derives from `pairRoot` — a
|
|
1379
|
+
* secret only the paired client and daemon share (it fell out of the original
|
|
1380
|
+
* ECDH). The daemon dials a pairing's epoch token, and on that connection the
|
|
1381
|
+
* `verifyOfferToken` predicate accepts a hello whose offer-token field equals
|
|
1382
|
+
* that same epoch token (constant-time). Token possession ⇒ paired party. The
|
|
1383
|
+
* stored `fingerprint` (of the first handshake's ephemeral key) is a stable
|
|
1384
|
+
* display/revocation handle, not an authentication input.
|
|
1385
|
+
*/
|
|
1386
|
+
|
|
1387
|
+
declare const PAIRINGS_VERSION: 1;
|
|
1388
|
+
/** One persisted pairing. Written verbatim to `pairings.json`. */
|
|
1389
|
+
interface PairingRecord {
|
|
1390
|
+
/** The client ephemeral public key from the FIRST handshake (a snapshot —
|
|
1391
|
+
* reconnects use fresh ephemerals; this is not an auth input). */
|
|
1392
|
+
clientPub: string;
|
|
1393
|
+
/** Human-facing label the client chose on `pair accept`. */
|
|
1394
|
+
name: string;
|
|
1395
|
+
/** `identityFingerprint(clientPub)` from the first handshake — the stable
|
|
1396
|
+
* display + revocation handle. */
|
|
1397
|
+
fingerprint: string;
|
|
1398
|
+
/** ISO-8601 first-pair timestamp. */
|
|
1399
|
+
createdAt: string;
|
|
1400
|
+
/** ISO-8601 of the most recent successful (re)connect. */
|
|
1401
|
+
lastSeen: string;
|
|
1402
|
+
/** Long-term shared secret (base64), from which epoch routing tokens derive. */
|
|
1403
|
+
pairRoot: string;
|
|
1404
|
+
/** Rendezvous endpoint to reconnect through. */
|
|
1405
|
+
rendezvousUrl: string;
|
|
1406
|
+
}
|
|
1407
|
+
/** Mode of a served channel — first-contact offer vs. an established reconnect. */
|
|
1408
|
+
type PairingChannelMode = "offer" | "reconnect";
|
|
1409
|
+
/** Context handed to the injected `serve` when a channel comes up. */
|
|
1410
|
+
interface PairingChannelContext {
|
|
1411
|
+
mode: PairingChannelMode;
|
|
1412
|
+
/** Stable pairing fingerprint (present for reconnect; for an offer it's the
|
|
1413
|
+
* freshly-derived one). */
|
|
1414
|
+
fingerprint: string;
|
|
1415
|
+
/** Client label. */
|
|
1416
|
+
name: string;
|
|
1417
|
+
}
|
|
1418
|
+
/** Handle to a live served channel — the registry closes it on teardown. */
|
|
1419
|
+
interface PairingChannelHandle {
|
|
1420
|
+
close(): Promise<void>;
|
|
1421
|
+
}
|
|
1422
|
+
interface CreatedOffer {
|
|
1423
|
+
/** The one-time offer token (base64url). */
|
|
1424
|
+
token: string;
|
|
1425
|
+
/** Offer expiry, unix seconds. */
|
|
1426
|
+
exp: number;
|
|
1427
|
+
/** The full `agentproto://pair?…` offer URL. */
|
|
1428
|
+
url: string;
|
|
1429
|
+
/** The daemon's identity fingerprint (shown to the human at accept time). */
|
|
1430
|
+
fingerprint: string;
|
|
1431
|
+
/** The rendezvous the offer routes through. */
|
|
1432
|
+
rendezvousUrl: string;
|
|
1433
|
+
/** True when `rendezvousUrl` is the hosted default — i.e. neither an explicit
|
|
1434
|
+
* `--rendezvous` nor a configured `pairing.rendezvous` applied, so the offer
|
|
1435
|
+
* falls back to `HOSTED_RENDEZVOUS_URL`. Surfaced so `pair offer` can flag,
|
|
1436
|
+
* never silently, that the daemon is relaying through our infrastructure. */
|
|
1437
|
+
rendezvousIsHostedDefault: boolean;
|
|
1438
|
+
}
|
|
1439
|
+
interface CreateOfferInput {
|
|
1440
|
+
/** Time-to-live in ms. Default 10 minutes. */
|
|
1441
|
+
ttlMs?: number;
|
|
1442
|
+
/** Rendezvous URL override; falls back to the configured default. */
|
|
1443
|
+
rendezvousUrl?: string;
|
|
1444
|
+
}
|
|
1445
|
+
interface PairingRegistryDeps {
|
|
1446
|
+
/** Lazily load (create on first use) the daemon identity. Kept lazy so a
|
|
1447
|
+
* daemon that never pairs never writes an identity file. */
|
|
1448
|
+
loadIdentity: () => Promise<DaemonIdentity>;
|
|
1449
|
+
/** Path to `pairings.json`. Defaults to `~/.agentproto/pairings.json`. */
|
|
1450
|
+
pairingsPath?: string;
|
|
1451
|
+
/** Configured rendezvous URL (from `config.pairing.rendezvous`). Three states:
|
|
1452
|
+
* - `undefined` (key absent) → `createOffer` falls back to the hosted default
|
|
1453
|
+
* (`HOSTED_RENDEZVOUS_URL`).
|
|
1454
|
+
* - a non-empty URL → that endpoint is the default for offers.
|
|
1455
|
+
* - `""` (explicitly empty) → an explicit opt-out: no default applies, so an
|
|
1456
|
+
* offer without `--rendezvous` fails closed. Wire it through verbatim (do
|
|
1457
|
+
* not coerce `""` to `undefined`) or the opt-out silently reverts to the
|
|
1458
|
+
* hosted default. */
|
|
1459
|
+
defaultRendezvousUrl?: string;
|
|
1460
|
+
/** Dial a rendezvous WS and adapt it to a FrameSink. Injected by the CLI
|
|
1461
|
+
* (uses `ws` + `wrapWebSocket`). Rejects if the dial fails. The `signal`
|
|
1462
|
+
* aborts on registry shutdown. */
|
|
1463
|
+
dial: (wsUrl: string, signal: AbortSignal) => Promise<FrameSink>;
|
|
1464
|
+
/** Serve the E2E-wrapped channel (the CLI builds `createTunnelServer` over
|
|
1465
|
+
* it with the same config `serve --connect` uses). */
|
|
1466
|
+
serve: (sink: E2eFrameSink, ctx: PairingChannelContext) => PairingChannelHandle;
|
|
1467
|
+
/** Injectable clock (ms). Defaults to Date.now. */
|
|
1468
|
+
now?: () => number;
|
|
1469
|
+
/** Diagnostic log sink. */
|
|
1470
|
+
log?: (line: string) => void;
|
|
1471
|
+
/** Reconnect backoff floor / ceiling (ms). Defaults 1s / 30s. */
|
|
1472
|
+
reconnectMinMs?: number;
|
|
1473
|
+
reconnectMaxMs?: number;
|
|
1474
|
+
/** How long a parked daemon socket waits for a client hello before recycling
|
|
1475
|
+
* the connection. Kept just under the broker's park timeout. Default 110s. */
|
|
1476
|
+
handshakeTimeoutMs?: number;
|
|
1477
|
+
}
|
|
1478
|
+
interface PairingRegistry {
|
|
1479
|
+
/** Mint a one-time offer + start dialing the rendezvous for it. */
|
|
1480
|
+
createOffer(input?: CreateOfferInput): Promise<CreatedOffer>;
|
|
1481
|
+
/** All persisted pairings (copies). Loads `pairings.json` on first call. */
|
|
1482
|
+
list(): Promise<PairingRecord[]>;
|
|
1483
|
+
/** Drop a pairing by fingerprint or name; stops its rendezvous connections.
|
|
1484
|
+
* Returns false when nothing matched. */
|
|
1485
|
+
revoke(idOrName: string): Promise<boolean>;
|
|
1486
|
+
/** Start standing reconnect connections for every persisted pairing. Call
|
|
1487
|
+
* after the gateway is up (so the injected `serve` can reach it). */
|
|
1488
|
+
startAutoconnect(): Promise<void>;
|
|
1489
|
+
/** Tear down every offer + reconnect connection and served channel. */
|
|
1490
|
+
shutdown(): Promise<void>;
|
|
1491
|
+
}
|
|
1492
|
+
declare function createPairingRegistry(deps: PairingRegistryDeps): PairingRegistry;
|
|
1493
|
+
|
|
906
1494
|
/**
|
|
907
1495
|
* Fire-and-forget webhook notifier for session lifecycle events.
|
|
908
1496
|
*
|
|
@@ -1301,6 +1889,18 @@ interface OrchestratorScope {
|
|
|
1301
1889
|
/** Max concurrently-alive children the owning orchestrator may spawn
|
|
1302
1890
|
* before further spawns are rejected. */
|
|
1303
1891
|
maxChildren: number;
|
|
1892
|
+
/**
|
|
1893
|
+
* Name of the resolved role that owns this scope (spawn-role-
|
|
1894
|
+
* profiles / privilege lattice). This is the "parent role" the
|
|
1895
|
+
* `canSpawn` gate checks against when this scope is used to spawn a
|
|
1896
|
+
* further child (`session-spawn.ts`). Defaults to `"supervisor"` at
|
|
1897
|
+
* mint time when the caller doesn't say — every role that can reach
|
|
1898
|
+
* `orchestrator: true` today resolves to supervisor (executor's
|
|
1899
|
+
* `toolPolicy.delegation: "deny"` already excludes it from ever
|
|
1900
|
+
* minting a scope), so this preserves #214 behaviour exactly until a
|
|
1901
|
+
* custom role is actually granted delegation.
|
|
1902
|
+
*/
|
|
1903
|
+
role: string;
|
|
1304
1904
|
}
|
|
1305
1905
|
/**
|
|
1306
1906
|
* Narrow a caller-requested tool list to ⊆ a ceiling subset.
|
|
@@ -1329,6 +1929,9 @@ interface MintScopeOptions {
|
|
|
1329
1929
|
maxDepth?: number;
|
|
1330
1930
|
/** Per-orchestrator alive-child quota (default DEFAULT_MAX_CHILDREN). */
|
|
1331
1931
|
maxChildren?: number;
|
|
1932
|
+
/** Name of the resolved role that owns this scope. Default
|
|
1933
|
+
* `"supervisor"` — see `OrchestratorScope.role`. */
|
|
1934
|
+
role?: string;
|
|
1332
1935
|
}
|
|
1333
1936
|
interface ScopeTokenRegistry {
|
|
1334
1937
|
/** Mint a fresh scope-token. See `MintScopeOptions`. */
|
|
@@ -1447,6 +2050,11 @@ type OrchestratorInjector = (opts?: {
|
|
|
1447
2050
|
/** Override the per-orchestrator child quota for the minted scope
|
|
1448
2051
|
* (clamped to the caller's). Root-only knob. */
|
|
1449
2052
|
maxChildren?: number;
|
|
2053
|
+
/** Name of the resolved role for the session THIS scope is being
|
|
2054
|
+
* minted for (i.e. the session becoming an orchestrator) — becomes
|
|
2055
|
+
* `OrchestratorScope.role`, the "parent role" a future spawn
|
|
2056
|
+
* through this scope is gated against. Default `"supervisor"`. */
|
|
2057
|
+
role?: string;
|
|
1450
2058
|
}) => OrchestratorInjection;
|
|
1451
2059
|
/**
|
|
1452
2060
|
* Build the orchestrator injector. Closed over the gateway's
|
|
@@ -1456,6 +2064,95 @@ type OrchestratorInjector = (opts?: {
|
|
|
1456
2064
|
*/
|
|
1457
2065
|
declare function createOrchestratorInjector(deps: OrchestratorInjectorDeps): OrchestratorInjector;
|
|
1458
2066
|
|
|
2067
|
+
/**
|
|
2068
|
+
* Shared types for pluggable sandbox providers.
|
|
2069
|
+
*
|
|
2070
|
+
* The sandbox family is a `@agentproto/provider-kit` consumer, same shape as
|
|
2071
|
+
* the tunnel family (`remote-providers/types.ts`): {@link SandboxProviderHandle}
|
|
2072
|
+
* extends the kit's generic `AdapterHandle` (slug/name/version/description/
|
|
2073
|
+
* requiresSetup/check) with the AIP-36 capability namespace and the concrete
|
|
2074
|
+
* `@agentproto/sandbox` `SandboxProvider` (`boot()`) the handle wraps.
|
|
2075
|
+
*/
|
|
2076
|
+
|
|
2077
|
+
/**
|
|
2078
|
+
* Declared capabilities of a sandbox provider — pure metadata, surfaced in
|
|
2079
|
+
* `list_sandbox_providers`. Never carries secrets. Namespace mirrors AIP-36
|
|
2080
|
+
* SANDBOX.md (`network.egress`, `mounts`, `lifecycle.pause_after_idle`,
|
|
2081
|
+
* `read_only`, `limits.timeout_ms`).
|
|
2082
|
+
*/
|
|
2083
|
+
interface SandboxProviderCapabilities {
|
|
2084
|
+
/** Sandbox can be given a network egress allowlist (AIP-36 `network.egress`). */
|
|
2085
|
+
networkEgress: boolean;
|
|
2086
|
+
/** Sandbox supports mounting external filesystems (AIP-36 `mounts`). */
|
|
2087
|
+
mounts: boolean;
|
|
2088
|
+
/** Sandbox can be paused (not just killed) between turns (AIP-36 `lifecycle.pause_after_idle`). */
|
|
2089
|
+
lifecyclePause: boolean;
|
|
2090
|
+
/** Sandbox can be started read-only (AIP-36 `read_only`). */
|
|
2091
|
+
readOnly: boolean;
|
|
2092
|
+
/** Hard cap on `limits.timeout_ms`, when the provider enforces one. */
|
|
2093
|
+
maxTimeoutMs?: number;
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* A sandbox provider as an adapter-kit handle. Rides on the kit's generic
|
|
2097
|
+
* {@link AdapterHandle} and adds the sandbox-specific `capabilities` plus
|
|
2098
|
+
* the `@agentproto/sandbox` `SandboxProvider` the handle wraps — the thing
|
|
2099
|
+
* `createSandboxAgentSessionHost` actually boots.
|
|
2100
|
+
*/
|
|
2101
|
+
interface SandboxProviderHandle extends AdapterHandle {
|
|
2102
|
+
readonly provider: SandboxProvider;
|
|
2103
|
+
readonly capabilities: SandboxProviderCapabilities;
|
|
2104
|
+
/**
|
|
2105
|
+
* Credential fields this provider accepts via `setup_sandbox_provider`
|
|
2106
|
+
* (e.g. e2b's `apiKey`). Omit (or empty) when the provider needs no
|
|
2107
|
+
* credentials (e.g. `local`).
|
|
2108
|
+
*/
|
|
2109
|
+
readonly setupFields?: readonly SetupField[];
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
/**
|
|
2113
|
+
* Sandbox family on top of `@agentproto/provider-kit` — mirrors
|
|
2114
|
+
* `tunnel-adapters.ts`. This module is the entire bridge between the
|
|
2115
|
+
* generic kit and `@agentproto/sandbox`'s `SandboxProvider` concept: it
|
|
2116
|
+
* contributes nothing the kit already owns (catalog/status/creds/ledger/
|
|
2117
|
+
* list/MCP-tool plumbing); it only supplies the sandbox-family `TInfo`
|
|
2118
|
+
* (`SandboxAdapterInfo`), the static `SANDBOX_CATALOG`, and the resolver
|
|
2119
|
+
* that maps a catalog slug to a concrete {@link SandboxProviderHandle}
|
|
2120
|
+
* (`./sandbox-providers/registry.js`).
|
|
2121
|
+
*
|
|
2122
|
+
* Kit primitives used:
|
|
2123
|
+
* - `makeCredsStore` → per-slug 0600 creds under `~/.agentproto/sandbox-creds/`
|
|
2124
|
+
* - `makeSetupLedger` → `~/.agentproto/setup/<slug>.json`
|
|
2125
|
+
* - `makeAdapterResolver` → wraps the throwing `load` into null-on-miss
|
|
2126
|
+
* - `makeAdapterLister` → catalog → status-classified `AdapterEntry[]`
|
|
2127
|
+
* - `makeListTool` → registers `list_sandbox_providers`
|
|
2128
|
+
* - `makeSetupTool` → registers `setup_sandbox_provider` (multi-field
|
|
2129
|
+
* form: e2b's `apiKey`, sensitive)
|
|
2130
|
+
*
|
|
2131
|
+
* This is pure additive plumbing (introspection + setup) — it does NOT wire
|
|
2132
|
+
* a `sandbox` field into `agent_start`. That lands in a follow-up PR; the
|
|
2133
|
+
* `resolveSandboxProvider`/`listSandboxProviders` overrides below exist now
|
|
2134
|
+
* so that later wiring can inject the same resolver/lister this module
|
|
2135
|
+
* builds by default, mirroring `resolveAgentAdapter`/`listAgentAdapters`.
|
|
2136
|
+
*
|
|
2137
|
+
* Security: `toSandboxInfo` exposes only `capabilities` — never a cred
|
|
2138
|
+
* value (Appendix B). The setup tool's fields are marked SENSITIVE and the
|
|
2139
|
+
* result NEVER echoes any field value back.
|
|
2140
|
+
*/
|
|
2141
|
+
|
|
2142
|
+
/**
|
|
2143
|
+
* Family descriptor (`TInfo`). Pure metadata surfaced in
|
|
2144
|
+
* `list_sandbox_providers`. The kit's `AdapterEntry` already carries
|
|
2145
|
+
* slug/name/description/status/version, so the only sandbox-specific field
|
|
2146
|
+
* is the declared capability set. NEVER carries a cred value.
|
|
2147
|
+
*/
|
|
2148
|
+
interface SandboxAdapterInfo {
|
|
2149
|
+
capabilities: SandboxProviderCapabilities;
|
|
2150
|
+
}
|
|
2151
|
+
/** Resolve a sandbox provider slug to a handle, or null when unavailable. */
|
|
2152
|
+
type SandboxProviderResolver = AdapterResolver<SandboxProviderHandle>;
|
|
2153
|
+
/** List every sandbox provider with its live status + capabilities. */
|
|
2154
|
+
type SandboxProviderLister = AdapterLister<SandboxAdapterInfo>;
|
|
2155
|
+
|
|
1459
2156
|
/**
|
|
1460
2157
|
* Pluggable adapter resolver — keeps the runtime package free of any
|
|
1461
2158
|
* @agentproto/cli dep. The host (cli `serve`, playground, embedding
|
|
@@ -1489,6 +2186,17 @@ type AgentAdapterResolver = (slug: string) => Promise<{
|
|
|
1489
2186
|
* manifest, so a typo fails the spawn rather than silently no-op).
|
|
1490
2187
|
*/
|
|
1491
2188
|
mode?: string;
|
|
2189
|
+
/**
|
|
2190
|
+
* Manifest-declared option id → value map forwarded from
|
|
2191
|
+
* `agent_start` (AIP-45 `AgentCliHandle.options` — e.g. hermes'
|
|
2192
|
+
* `skills`). Applied at spawn time via `composeSpawn`'s option
|
|
2193
|
+
* patches (`bin_args_prepend` / `bin_args_template` /
|
|
2194
|
+
* `bin_args_append_when_true` / `env`), validated against each
|
|
2195
|
+
* option's declared `type`/`enum`/`min`/`max`. An id the adapter
|
|
2196
|
+
* doesn't declare throws `RuntimeConfigError` (composeSpawn
|
|
2197
|
+
* validates against the manifest, same as an unknown `mode`).
|
|
2198
|
+
*/
|
|
2199
|
+
options?: Record<string, boolean | number | string>;
|
|
1492
2200
|
/** Model identifier forwarded from `agent_start`. For ACP
|
|
1493
2201
|
* adapters this is applied via session/set_config_option after
|
|
1494
2202
|
* newSession (the ACP wrapper does not forward CLI args to claude).
|
|
@@ -1511,6 +2219,17 @@ type AgentAdapterResolver = (slug: string) => Promise<{
|
|
|
1511
2219
|
* handler, POST /sessions/agent) wires this to pulse
|
|
1512
2220
|
* `SessionDescriptor.lastActivityAt` via `registry.pulseActivity(id)`. */
|
|
1513
2221
|
onActivity?: () => void;
|
|
2222
|
+
/** Start the session in permission-hold mode — forwarded to the driver's
|
|
2223
|
+
* `runtime.start({ permissionHold })` so each ACP permission request is
|
|
2224
|
+
* surfaced + parked in the daemon's inbox instead of auto-answered.
|
|
2225
|
+
* Adapters/arms with no permission surface ignore it. Default false. */
|
|
2226
|
+
permissionHold?: boolean;
|
|
2227
|
+
/** FULLY-RESOLVED billing-auth spec forwarded from `agent_start` to the
|
|
2228
|
+
* driver's `runtime.start({ auth })`, computed by the runtime's
|
|
2229
|
+
* `resolveAuthSpec` (provider, ordered mode, setEnv/scrub, credential
|
|
2230
|
+
* source all pre-decided). The driver applies it mechanically. Absent
|
|
2231
|
+
* when the resolver produced no spec (ambient). */
|
|
2232
|
+
auth?: ResolvedAuthSpec;
|
|
1514
2233
|
}): Promise<AgentSessionLike>;
|
|
1515
2234
|
/** Display label for the descriptor's `command` field. */
|
|
1516
2235
|
commandPreview?: string;
|
|
@@ -1520,7 +2239,39 @@ type AgentAdapterResolver = (slug: string) => Promise<{
|
|
|
1520
2239
|
tokensIn?: number;
|
|
1521
2240
|
tokensOut?: number;
|
|
1522
2241
|
} | null>;
|
|
2242
|
+
/** AIP-45 `options[]` this adapter's manifest declares (id + type only —
|
|
2243
|
+
* no spawn internals). Lets `session-spawn.ts` fold a config-level
|
|
2244
|
+
* `defaults.skills` list into `options.skills` using the shape the
|
|
2245
|
+
* manifest actually declared (e.g. hermes' comma-joined string),
|
|
2246
|
+
* instead of guessing. Omitted/empty ⇒ the skills normalization is a
|
|
2247
|
+
* documented no-op for that adapter (e.g. claude-code, which
|
|
2248
|
+
* auto-discovers skills and declares no such option). */
|
|
2249
|
+
declaredOptions?: readonly DeclaredAdapterOption[];
|
|
2250
|
+
/** Billing-auth descriptor projected from the adapter manifest
|
|
2251
|
+
* (`provider` / `authEnforce` / `authSubscription`) — the sole input the
|
|
2252
|
+
* runtime's `resolveAuthSpec` reads about the adapter's auth capability
|
|
2253
|
+
* (keeping the catalog coupling in the runtime, the driver mechanical).
|
|
2254
|
+
* Omitted ⇒ ambient (no credential injection). */
|
|
2255
|
+
authDescriptor?: AdapterAuthDescriptor;
|
|
2256
|
+
/** Adapter's default model id (`models.default`) — lets the resolver derive
|
|
2257
|
+
* a provider for a by-model spawn that omitted `model`. */
|
|
2258
|
+
defaultModel?: string;
|
|
1523
2259
|
} | null>;
|
|
2260
|
+
/**
|
|
2261
|
+
* UI-safe projection of an AIP-45 `modes[]` entry as surfaced by
|
|
2262
|
+
* `adapter_list`. Mirrors `@agentproto/cli`'s `AdapterMode` without
|
|
2263
|
+
* importing it (the runtime deliberately carries no cli dep — see the
|
|
2264
|
+
* `AgentAdapterLister` note above). Spawn internals (`bin_args_*`, `env`)
|
|
2265
|
+
* are intentionally omitted; `status` is normalised to `"active"` by the
|
|
2266
|
+
* lister when the manifest omits it, so a declared mode is never
|
|
2267
|
+
* silently statusless.
|
|
2268
|
+
*/
|
|
2269
|
+
interface AdapterListMode {
|
|
2270
|
+
id: string;
|
|
2271
|
+
description?: string;
|
|
2272
|
+
status: "active" | "noop" | "planned";
|
|
2273
|
+
status_note?: string;
|
|
2274
|
+
}
|
|
1524
2275
|
/**
|
|
1525
2276
|
* Compact adapter metadata for the discovery endpoints. Independent
|
|
1526
2277
|
* of the resolver function above — hosts that can list installed
|
|
@@ -1535,6 +2286,10 @@ interface AdapterListEntry {
|
|
|
1535
2286
|
protocol: string;
|
|
1536
2287
|
streaming: boolean;
|
|
1537
2288
|
packageName: string;
|
|
2289
|
+
/** Declared operation modes with their honest support status, so a
|
|
2290
|
+
* client can see e.g. hermes' `lean` mode is a measured no-op instead
|
|
2291
|
+
* of being silently accepted. Empty when the adapter declares none. */
|
|
2292
|
+
modes: AdapterListMode[];
|
|
1538
2293
|
}
|
|
1539
2294
|
type AgentAdapterLister = () => Promise<AdapterListEntry[]>;
|
|
1540
2295
|
interface AuthOptions {
|
|
@@ -1621,7 +2376,7 @@ interface InboundWatcher {
|
|
|
1621
2376
|
}
|
|
1622
2377
|
|
|
1623
2378
|
/**
|
|
1624
|
-
* Browser family on top of `@agentproto/
|
|
2379
|
+
* Browser family on top of `@agentproto/provider-kit` — Phase 3 (lightest
|
|
1625
2380
|
* adoption: no creds, no wizard, no catalog file).
|
|
1626
2381
|
*
|
|
1627
2382
|
* Provides the kit-compatible types (`BrowserAdapterHandle`) and the
|
|
@@ -1680,6 +2435,137 @@ declare function formatToolCall(toolName: string, args: unknown): string;
|
|
|
1680
2435
|
*/
|
|
1681
2436
|
declare function formatToolResult(toolName: string | undefined, result: unknown, isError: boolean): string | null;
|
|
1682
2437
|
|
|
2438
|
+
/**
|
|
2439
|
+
* Dependency-injection surface for MCP credential resolution.
|
|
2440
|
+
*
|
|
2441
|
+
* `packages/runtime` intentionally does NOT depend on `@agentproto/auth`;
|
|
2442
|
+
* the daemon bootstrap (e.g. `packages/cli`) can set a broker-backed
|
|
2443
|
+
* resolver here, and `session-spawn.ts` will call it when an `mcpServers`
|
|
2444
|
+
* entry carries `credentialRef`. Errors from the hook are treated as
|
|
2445
|
+
* non-fatal by the caller.
|
|
2446
|
+
*/
|
|
2447
|
+
interface McpCredentialDeps {
|
|
2448
|
+
/** Resolve brokered headers for an `mcpServers` entry that names a
|
|
2449
|
+
* `credentialRef`. The returned headers are merged ON TOP of the
|
|
2450
|
+
* entry's static `headers` so brokered auth wins on collision. */
|
|
2451
|
+
resolveMcpCredentialHeaders?: (o: {
|
|
2452
|
+
credentialRef: string;
|
|
2453
|
+
signal?: AbortSignal;
|
|
2454
|
+
}) => Promise<Record<string, string> | undefined>;
|
|
2455
|
+
/** Resolve a single named secret (an env-var-shaped slug — e.g. a sandbox
|
|
2456
|
+
* spec's `env.passthrough` / `env.auth.state.env` entries) to its raw
|
|
2457
|
+
* value. Distinct from `resolveMcpCredentialHeaders` above (which
|
|
2458
|
+
* resolves an MCP `credentialRef` PATH into Authorization-style
|
|
2459
|
+
* headers): this is the `SecretResolver` shape `agent_start`'s sandbox
|
|
2460
|
+
* branch (`session-spawn.ts`) passes to `@agentproto/sandbox`'s
|
|
2461
|
+
* `SandboxSecretsConfig.resolver` — so a booted sandbox's env is filled
|
|
2462
|
+
* from the same host-injected broker as every other secret, never a
|
|
2463
|
+
* bare `process.env` read at the call site. Returns null when the slug
|
|
2464
|
+
* can't be resolved (missing/unconfigured). */
|
|
2465
|
+
resolveSandboxSecret?: (slug: string) => Promise<string | null>;
|
|
2466
|
+
}
|
|
2467
|
+
declare function setMcpCredentialDeps(d: McpCredentialDeps): void;
|
|
2468
|
+
declare function getMcpCredentialDeps(): McpCredentialDeps;
|
|
2469
|
+
|
|
2470
|
+
/**
|
|
2471
|
+
* MCP tools that drive the pairing registry — the daemon's "let a client pair
|
|
2472
|
+
* with me over an untrusted rendezvous" surface (design: DESIGN §6).
|
|
2473
|
+
*
|
|
2474
|
+
* Three tools, mirroring the CLI verbs:
|
|
2475
|
+
*
|
|
2476
|
+
* pair_offer — mint a one-time offer + start dialing the rendezvous; returns
|
|
2477
|
+
* the `agentproto://pair?…` URL, fingerprint, and expiry.
|
|
2478
|
+
* pair_list — list persisted pairings (name, fingerprint, lastSeen).
|
|
2479
|
+
* pair_revoke — drop a pairing by fingerprint or name; stops its rendezvous
|
|
2480
|
+
* connections so it can no longer reconnect.
|
|
2481
|
+
*
|
|
2482
|
+
* Registered beside `remote_*` (remote-tools.ts) — same closure-rebind pattern;
|
|
2483
|
+
* the registry singleton lives on the gateway.
|
|
2484
|
+
*/
|
|
2485
|
+
|
|
2486
|
+
interface RegisterPairingToolsOptions {
|
|
2487
|
+
registry: PairingRegistry;
|
|
2488
|
+
}
|
|
2489
|
+
declare function registerPairingTools(server: McpServer, opts: RegisterPairingToolsOptions): void;
|
|
2490
|
+
|
|
2491
|
+
/**
|
|
2492
|
+
* Provider-preset family on top of `@agentproto/provider-kit` — the *thin,
|
|
2493
|
+
* honest* lister (Option B). Unlike tunnels/sandboxes/eval-reporters, presets
|
|
2494
|
+
* are bundled static data with no binary to install, no package to resolve, and
|
|
2495
|
+
* no setup ledger: their only "setup" is an ambient API-key env var. So this
|
|
2496
|
+
* module does NOT use `makeAdapterResolver` / `makeAdapterLister` / the wizard
|
|
2497
|
+
* (all install-oriented, a forced fit). It reuses only `makeListTool` — the one
|
|
2498
|
+
* kit primitive that's genuinely about "JSON a lister's output as an MCP tool."
|
|
2499
|
+
*
|
|
2500
|
+
* Status is derived honestly from the daemon's own environment (the env agents
|
|
2501
|
+
* spawn into), not the CLI caller's:
|
|
2502
|
+
* "ready" — `process.env[preset.keyEnv]` is set (key available to spawn)
|
|
2503
|
+
* "available" — key absent (preset known, usable once the env var is provided)
|
|
2504
|
+
*
|
|
2505
|
+
* There is no "supported" state: presets ship in-repo, so they're always at
|
|
2506
|
+
* least "available." `version` is the literal "built-in".
|
|
2507
|
+
*
|
|
2508
|
+
* Security (Appendix B): `PresetInfo` carries only `keyEnv` (the env-var NAME),
|
|
2509
|
+
* never a key value. The list tool never returns creds — there are none to
|
|
2510
|
+
* return; keys live in the operator's environment, not a creds store.
|
|
2511
|
+
*/
|
|
2512
|
+
|
|
2513
|
+
/**
|
|
2514
|
+
* Manifest-declared AIP-45 preset, the minimum the catalog needs to merge
|
|
2515
|
+
* an adapter-contributed preset into the listing. Mirrors
|
|
2516
|
+
* `AgentCliPresetDeclaration`'s fields without importing
|
|
2517
|
+
* `@agentproto/driver-agent-cli` into the runtime package (same structural-
|
|
2518
|
+
* type pattern as `DeclaredAdapterOption` in spawn-defaults.ts).
|
|
2519
|
+
*/
|
|
2520
|
+
interface DeclaredAdapterPreset {
|
|
2521
|
+
id: string;
|
|
2522
|
+
label: string;
|
|
2523
|
+
description?: string;
|
|
2524
|
+
schemaFlavor: ProviderPreset["schemaFlavor"];
|
|
2525
|
+
baseUrl: string;
|
|
2526
|
+
keyEnv: string;
|
|
2527
|
+
scrubEnv?: string[];
|
|
2528
|
+
defaultModel?: string;
|
|
2529
|
+
homepage?: string;
|
|
2530
|
+
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Family descriptor surfaced as `AdapterEntry.info`. User-facing preset facts
|
|
2533
|
+
* only — no cred values (there are none in the registry), no adapter projection
|
|
2534
|
+
* detail (`scrubEnv` stays internal to adapter manifests).
|
|
2535
|
+
*/
|
|
2536
|
+
interface PresetInfo {
|
|
2537
|
+
/** API schema flavor — which adapter family can consume this preset. */
|
|
2538
|
+
schemaFlavor: ProviderPreset["schemaFlavor"];
|
|
2539
|
+
/** Base URL the client hits. */
|
|
2540
|
+
baseUrl: string;
|
|
2541
|
+
/** Env-var NAME holding the API key (never the value). */
|
|
2542
|
+
keyEnv: string;
|
|
2543
|
+
/** Conventional default model id, if any. */
|
|
2544
|
+
defaultModel?: string;
|
|
2545
|
+
/** Homepage/docs URL. */
|
|
2546
|
+
homepage?: string;
|
|
2547
|
+
}
|
|
2548
|
+
/**
|
|
2549
|
+
* Normalize a manifest-declared preset into the canonical `ProviderPreset`
|
|
2550
|
+
* shape the catalog lists. `scrubEnv` defaults to empty (an adapter that
|
|
2551
|
+
* scrubs in code may omit it); `description` defaults to empty. Pure.
|
|
2552
|
+
*/
|
|
2553
|
+
declare function declaredPresetToProviderPreset(decl: DeclaredAdapterPreset): ProviderPreset;
|
|
2554
|
+
/**
|
|
2555
|
+
* Map the static preset registry → status-classified catalog entries, reading
|
|
2556
|
+
* the daemon's environment for key presence. Pure (no I/O) save the `process`
|
|
2557
|
+
* .env read; safe to call on every list invocation.
|
|
2558
|
+
*
|
|
2559
|
+
* `adapterPresets` — manifest-declared presets contributed by loaded adapters
|
|
2560
|
+
* (Stage 3 AIP-45 `presets` field). Merged AFTER the built-in registry and
|
|
2561
|
+
* deduped by id: an adapter-declared id that collides with a built-in is
|
|
2562
|
+
* ignored (the registry is the source of truth for canonical providers), so
|
|
2563
|
+
* an adapter can't silently override moonshot/openrouter facts. The live
|
|
2564
|
+
* wiring that reads each adapter's `.presets` and passes them here is Stage 4;
|
|
2565
|
+
* today this is the tested seam.
|
|
2566
|
+
*/
|
|
2567
|
+
declare function listPresets(env?: Record<string, string | undefined>, adapterPresets?: readonly DeclaredAdapterPreset[]): AdapterEntry<PresetInfo>[];
|
|
2568
|
+
|
|
1683
2569
|
/** A journal-file StepCache scoped to one cacheKey. Best-effort: read/parse
|
|
1684
2570
|
* failures degrade to "no cache" (a miss), never throw into the run. */
|
|
1685
2571
|
declare function createFileStepCache(cacheKey: string, opts?: {
|
|
@@ -1984,6 +2870,27 @@ interface CreateGatewayOptions {
|
|
|
1984
2870
|
deferredTools?: {
|
|
1985
2871
|
alwaysOn?: readonly string[];
|
|
1986
2872
|
};
|
|
2873
|
+
/**
|
|
2874
|
+
* Optional sandbox provider resolver — overrides the sandbox family's
|
|
2875
|
+
* default resolver (built-ins + `@agentproto/sandbox-<slug>` dynamic
|
|
2876
|
+
* import) used by `list_sandbox_providers` / `setup_sandbox_provider`
|
|
2877
|
+
* AND `agent_start.sandbox`. Mirrors `resolveAgentAdapter`'s injection
|
|
2878
|
+
* shape.
|
|
2879
|
+
*/
|
|
2880
|
+
resolveSandboxProvider?: SandboxProviderResolver;
|
|
2881
|
+
/** Optional sandbox provider lister — mirrors `listAgentAdapters`.
|
|
2882
|
+
* Overrides the default catalog-driven lister behind `list_sandbox_providers`. */
|
|
2883
|
+
listSandboxProviders?: SandboxProviderLister;
|
|
2884
|
+
/**
|
|
2885
|
+
* Optional E2E pairing registry (see `createPairingRegistry`). When wired,
|
|
2886
|
+
* the gateway mounts the `/pairings/*` REST routes and the `pair_*` MCP
|
|
2887
|
+
* tools, and exposes the registry on the handle. The CLI builds it (injecting
|
|
2888
|
+
* the ws `dial` + a `createTunnelServer`-backed `serve`) and passes it here so
|
|
2889
|
+
* the runtime stays free of pty/adapter concerns while the pairing surface is
|
|
2890
|
+
* reachable over MCP + HTTP. Autoconnect is the caller's to start (after this
|
|
2891
|
+
* returns) so the injected `serve` can capture the finished gateway.
|
|
2892
|
+
*/
|
|
2893
|
+
pairingRegistry?: PairingRegistry;
|
|
1987
2894
|
}
|
|
1988
2895
|
interface GatewayHandle {
|
|
1989
2896
|
url: string;
|
|
@@ -1998,6 +2905,10 @@ interface GatewayHandle {
|
|
|
1998
2905
|
* local port. Exposed so embedding hosts can open tunnels
|
|
1999
2906
|
* programmatically without going through the HTTP/MCP surface. */
|
|
2000
2907
|
tunnels: TunnelRegistry;
|
|
2908
|
+
/** E2E pairing registry, when one was wired via
|
|
2909
|
+
* `CreateGatewayOptions.pairingRegistry`. Undefined otherwise. Exposed so
|
|
2910
|
+
* the CLI can `startAutoconnect()` after boot and `shutdown()` it. */
|
|
2911
|
+
pairing?: PairingRegistry;
|
|
2001
2912
|
/** Per-boot bearer token required on mutating /sessions/* routes
|
|
2002
2913
|
* + WS PTY upgrades. Exposed so an embedding host (e.g. the CLI
|
|
2003
2914
|
* shell that hosts the gateway in-process) can pass it to child
|
|
@@ -2027,4 +2938,4 @@ interface GatewayHandle {
|
|
|
2027
2938
|
*/
|
|
2028
2939
|
declare function createGateway(opts: CreateGatewayOptions): Promise<GatewayHandle>;
|
|
2029
2940
|
|
|
2030
|
-
export { type AdapterListEntry, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AttachPolicyInput, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, type CommitSpec, type CompletionPolicySupervisor, type CreateGatewayOptions, DEFAULT_ORCHESTRATOR_TOOLS, type GateSpec, type GatewayHandle, type InboundWatcher, type JudgeGateSpec, type OnFailSpec, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, type PolicyRunState, type PolicyRunStatus, type RegisterBrowserInput, type RegisterSessionInput, type RuntimeMeta, type ScopeTokenRegistry, type SessionDescriptor, type SessionKind, type SessionStatus, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, type SpawnSessionInput, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type WatcherDescriptor, type WatcherStartInput, WorkspaceFs, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createScopeTokenRegistry, daemonRegistryDir, formatToolCall, formatToolResult, makeBrowserAdapterLister, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, readDaemonRegistry, readRuntimeMeta, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
|
|
2941
|
+
export { AdapterAuthDescriptor, type AdapterListEntry, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AttachPolicyInput, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, type CommitSpec, type CompletionPolicySupervisor, type CreateGatewayOptions, type CreateOfferInput, type CreatedOffer, DEFAULT_ORCHESTRATOR_TOOLS, DeclaredAdapterOption, type DeclaredAdapterPreset, type GateSpec, type GatewayHandle, type InboundWatcher, type JudgeGateSpec, type McpCredentialDeps, type OnFailSpec, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, PAIRINGS_VERSION, type PairingChannelContext, type PairingChannelHandle, type PairingChannelMode, type PairingRecord, type PairingRegistry, type PairingRegistryDeps, type PendingPermission, type PermissionRespondInput, type PermissionRespondResult, type PolicyRunState, type PolicyRunStatus, type PresetInfo, type PricingResolver, type RegisterBrowserInput, type RegisterPairingToolsOptions, type RegisterSessionInput, ResolvedAuthSpec, type RuntimeMeta, type SandboxAdapterInfo, type SandboxProviderCapabilities, type SandboxProviderHandle, type SandboxProviderLister, type SandboxProviderResolver, type ScopeTokenRegistry, type SessionDescriptor, type SessionKind, type SessionObserver, type SessionStatus, type SessionUsage, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, type SpawnSessionInput, type TokenPricing, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type UsageComputeInput, type UsageSource, type WatcherDescriptor, type WatcherStartInput, WorkspaceFs, composeSessionObservers, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createScopeTokenRegistry, daemonRegistryDir, declaredPresetToProviderPreset, deriveSessionUsage, formatToolCall, formatToolResult, getMcpCredentialDeps, listPresets, makeBrowserAdapterLister, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, projectSessionUsage, readDaemonRegistry, readRuntimeMeta, registerPairingTools, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
|