@agentproto/runtime 0.5.0 → 0.7.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 +123 -2
- package/dist/config.mjs +24 -1
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.ts +662 -13
- package/dist/index.mjs +8468 -6266
- package/dist/index.mjs.map +1 -1
- package/dist/providers-store.d.ts +1 -57
- package/dist/providers-store.mjs +1 -90
- package/dist/providers-store.mjs.map +1 -1
- package/dist/resume-strategies.mjs +868 -42
- 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 +15 -1
- package/dist/workspaces-config.mjs +27 -2
- package/dist/workspaces-config.mjs.map +1 -1
- package/package.json +27 -19
- package/dist/spawn-defaults-d5gAhNkV.d.ts +0 -61
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
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 { D as DeclaredAdapterOption } from './spawn-defaults-
|
|
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';
|
|
7
8
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
9
|
import { AdapterHandle, SetupField, AdapterResolver, AdapterLister, AdapterEntry } from '@agentproto/provider-kit';
|
|
9
10
|
import { SandboxProvider } from '@agentproto/sandbox';
|
|
11
|
+
import { FrameSink, E2eFrameSink } from '@agentproto/acp/tunnel';
|
|
12
|
+
import { DaemonIdentity } from '@agentproto/secrets/identity';
|
|
10
13
|
import { WorkspaceFs } from './workspace-fs.js';
|
|
11
14
|
export { createWorkspaceFs } from './workspace-fs.js';
|
|
12
15
|
export { ConversationMeta, ConversationStore, ConversationTurn, fileConversationStore } from './conversations.js';
|
|
13
16
|
import { ProviderPreset } from '@agentproto/provider-presets';
|
|
14
17
|
import { StepCache } from '@agentproto/workflow-runtime';
|
|
15
|
-
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';
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* Per-session JSONL record of a completed `command_execute` invocation
|
|
@@ -137,7 +141,7 @@ declare function projectSessionUsage(desc: UsageDescriptorFields): SessionUsage;
|
|
|
137
141
|
* (fire-and-forget HTTP), RoutineRunner (state machine fan-in),
|
|
138
142
|
* and session_monitor MCP tool (long-poll multiplexed).
|
|
139
143
|
*/
|
|
140
|
-
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";
|
|
141
145
|
/**
|
|
142
146
|
* Structured detail on why a session is awaiting input, when derivable.
|
|
143
147
|
* `source: "structured"` — a driver-reported ACP-style prompt (e.g. a tool
|
|
@@ -168,6 +172,16 @@ interface SessionTurnEndEvent {
|
|
|
168
172
|
* Absent for drivers that don't report a reason.
|
|
169
173
|
*/
|
|
170
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;
|
|
171
185
|
}
|
|
172
186
|
interface SessionAwaitingInputEvent {
|
|
173
187
|
type: "session:awaiting-input";
|
|
@@ -176,6 +190,34 @@ interface SessionAwaitingInputEvent {
|
|
|
176
190
|
ts: string;
|
|
177
191
|
question?: SessionAwaitingQuestion;
|
|
178
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
|
+
}
|
|
179
221
|
interface SessionExitedEvent {
|
|
180
222
|
type: "session:exited";
|
|
181
223
|
sessionId: string;
|
|
@@ -183,6 +225,12 @@ interface SessionExitedEvent {
|
|
|
183
225
|
status: "exited" | "killed" | "error";
|
|
184
226
|
label?: string;
|
|
185
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";
|
|
186
234
|
}
|
|
187
235
|
/** Emitted when command_execute finishes. commandId matches the id
|
|
188
236
|
* returned by the command_execute MCP tool. */
|
|
@@ -257,7 +305,7 @@ interface CronFailedEvent {
|
|
|
257
305
|
error: string;
|
|
258
306
|
ts: string;
|
|
259
307
|
}
|
|
260
|
-
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;
|
|
261
309
|
interface SessionEventBus {
|
|
262
310
|
emit(ev: SessionEvent): void;
|
|
263
311
|
/** Subscribe to a specific event type. Returns an unsubscribe fn. */
|
|
@@ -320,6 +368,13 @@ interface AgentSessionLike {
|
|
|
320
368
|
pid?: number;
|
|
321
369
|
send(message: unknown): AsyncIterable<AgentStreamEvent>;
|
|
322
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>;
|
|
323
378
|
close(): Promise<void>;
|
|
324
379
|
}
|
|
325
380
|
/**
|
|
@@ -358,6 +413,11 @@ interface AgentStreamEvent {
|
|
|
358
413
|
toolName?: string;
|
|
359
414
|
/** Tool-call input, e.g. an ACP `tool_call`'s `arguments` — see @agentproto/acp's `StreamEvent`. */
|
|
360
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;
|
|
361
421
|
/** Tool-call output, e.g. an ACP `tool_call_update`'s `result` — see @agentproto/acp's `StreamEvent`. */
|
|
362
422
|
result?: unknown;
|
|
363
423
|
isError?: boolean;
|
|
@@ -399,6 +459,20 @@ interface AgentStreamEvent {
|
|
|
399
459
|
}
|
|
400
460
|
type SessionKind = "terminal" | "agent-cli" | "command" | "browser";
|
|
401
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
|
+
}
|
|
402
476
|
interface SessionDescriptor {
|
|
403
477
|
id: string;
|
|
404
478
|
kind: SessionKind;
|
|
@@ -411,6 +485,33 @@ interface SessionDescriptor {
|
|
|
411
485
|
startedAt: string;
|
|
412
486
|
endedAt?: string;
|
|
413
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";
|
|
498
|
+
/** Whether a turn was actually in flight the INSTANT `status` flipped to
|
|
499
|
+
* "killed" — captured before anything else runs, because `busy` itself
|
|
500
|
+
* cannot be trusted after the fact: `runAgentTurn`'s `finally` is what
|
|
501
|
+
* clears `busy`, and that `finally` never fires for a generator that's
|
|
502
|
+
* never resumed (a killed child mid-tool-call, or a dead daemon), so a
|
|
503
|
+
* post-hoc read of `busy` on a killed session may just be showing you
|
|
504
|
+
* whatever it froze at. This field exists because `status: "killed"`
|
|
505
|
+
* alone can't tell a human's Stop mid-turn apart from a supervisor
|
|
506
|
+
* reaping a child that had already finished — both leave `exitCode:
|
|
507
|
+
* null`, and `turnsCompleted` alone is too weak (a session killed
|
|
508
|
+
* mid-SECOND-turn also has `turnsCompleted: 1`). `killedMidTurn: true`
|
|
509
|
+
* paired with any `turnsCompleted` means interrupted; `false`/absent
|
|
510
|
+
* alongside `turnsCompleted > 0` means the work was done before the
|
|
511
|
+
* kill — see activityFor in the vscode package for the read. Set by
|
|
512
|
+
* `kill()`, `shutdownImpl`'s force-kill, and `loadHistorySnapshot`'s
|
|
513
|
+
* wasAlive reclassification; absent for every other terminal path. */
|
|
514
|
+
killedMidTurn?: boolean;
|
|
414
515
|
/** Last time anything was written to stdout/stderr. Lets the UI
|
|
415
516
|
* spot stuck sessions ("running for 2h, last output 12min ago"). */
|
|
416
517
|
lastOutputAt?: string;
|
|
@@ -430,6 +531,10 @@ interface SessionDescriptor {
|
|
|
430
531
|
/** Free-text label the spawner can attach (e.g. conversation id,
|
|
431
532
|
* operator name) so the UI can group/filter. */
|
|
432
533
|
label?: string;
|
|
534
|
+
/** Derived from the session's FIRST prompt — what this conversation is
|
|
535
|
+
* about, for a UI that would otherwise show the adapter's argv. Distinct
|
|
536
|
+
* from `label`, which the spawner supplies and which always wins. */
|
|
537
|
+
title?: string;
|
|
433
538
|
/** True when the session was spawned under a real PTY (node-pty)
|
|
434
539
|
* instead of `child_process.spawn`. PTY sessions carry raw ANSI
|
|
435
540
|
* bytes (alt-screen, key bindings, colors); attach goes through
|
|
@@ -445,12 +550,50 @@ interface SessionDescriptor {
|
|
|
445
550
|
argv?: readonly string[];
|
|
446
551
|
/** Working directory the session was spawned in. Cloned by restart. */
|
|
447
552
|
cwd?: string;
|
|
553
|
+
/** Root of the git worktree the session was spawned in — the session→
|
|
554
|
+
* worktree edge, resolved from `cwd` at spawn time
|
|
555
|
+
* (`resolveWorktreeIdentity`). Distinct from `cwd`, which may be a
|
|
556
|
+
* subdirectory of it. Absent when `cwd` isn't inside a linked worktree (a
|
|
557
|
+
* plain checkout, a non-repo dir) and for every session persisted before
|
|
558
|
+
* this field existed.
|
|
559
|
+
*
|
|
560
|
+
* Recorded rather than computed at read time because it can't be
|
|
561
|
+
* recovered later: a worktree removed after the session ran leaves nothing
|
|
562
|
+
* on disk to re-resolve. */
|
|
563
|
+
worktreePath?: string;
|
|
564
|
+
/** Generation id of that worktree, read at spawn from the provision marker
|
|
565
|
+
* (`agentproto-worktree.json` in the worktree's private gitdir, written by
|
|
566
|
+
* `worktree.provision`). Absent whenever `worktreePath` is, and also for a
|
|
567
|
+
* worktree created by a bare `git worktree add` — nothing writes a marker
|
|
568
|
+
* there. The marker is what distinguishes generations, so a
|
|
569
|
+
* `worktreePath` without an id identifies a PATH, which a later worktree
|
|
570
|
+
* may reuse; the pair identifies one specific worktree. */
|
|
571
|
+
worktreeId?: string;
|
|
448
572
|
/** Adapter slug for agent-cli sessions — restart uses this with
|
|
449
573
|
* `/sessions/agent` to spin up a fresh ACP runtime. Undefined for
|
|
450
574
|
* pty/command kinds. */
|
|
451
575
|
adapterSlug?: string;
|
|
452
576
|
/** The model the session was requested to run (echoed back at spawn). */
|
|
453
577
|
model?: string;
|
|
578
|
+
/**
|
|
579
|
+
* Deterministic billing-auth mode + a non-secret credential fingerprint,
|
|
580
|
+
* recorded at spawn time for adapters that resolved an explicit
|
|
581
|
+
* credential (today: claude-code — see `AgentCliAuth.modes` in
|
|
582
|
+
* `@agentproto/driver-agent-cli`). The "verifiability" answer to "what
|
|
583
|
+
* was used": `mode` is the resolved `"subscription" | "api-key"`;
|
|
584
|
+
* `fingerprint` is `credentialFingerprint(mode, credential)` — e.g.
|
|
585
|
+
* `"subscription · sk-ant-oat…3f9c"` — NEVER the raw credential. Absent
|
|
586
|
+
* when no credential resolved (every adapter besides claude-code, in
|
|
587
|
+
* practice) or for a sandboxed spawn (the box's own daemon resolves its
|
|
588
|
+
* own credential independently). Surfaced in `agentproto sessions
|
|
589
|
+
* --watch`'s DETAIL pane and `agent_sessions_list`.
|
|
590
|
+
*
|
|
591
|
+
* Carries the resolver's OBSERVABLE echo (DECISION 9③/10②): `provider`,
|
|
592
|
+
* `credentialSource`, and the `setEnv` actually set — so a verifier checks
|
|
593
|
+
* the RESOLUTION, not the model's self-report. These are optional for
|
|
594
|
+
* back-compat with descriptors persisted before the multi-provider resolver.
|
|
595
|
+
*/
|
|
596
|
+
auth?: SessionAuthEcho;
|
|
454
597
|
/** Cumulative estimated USD cost of the session — best-effort, refreshed
|
|
455
598
|
* on each turn-end from the adapter's usage reader (e.g. hermes reads its
|
|
456
599
|
* state.db). Absent for adapters with no usage source. */
|
|
@@ -508,6 +651,14 @@ interface SessionDescriptor {
|
|
|
508
651
|
* drivers that don't report structured prompts. Cleared alongside
|
|
509
652
|
* `awaitingInput` on the next turn start. */
|
|
510
653
|
awaitingQuestion?: SessionAwaitingQuestion;
|
|
654
|
+
/** True while this session (spawned in permission-hold mode) has at least
|
|
655
|
+
* one permission request parked in the inbox awaiting a human/orchestrator
|
|
656
|
+
* decision. Distinct from `awaitingInput` (a conversational question) — a
|
|
657
|
+
* held permission is resolved via `permissions_respond` / `agentproto
|
|
658
|
+
* permissions approve|deny`, not a normal prompt. Set when a request is
|
|
659
|
+
* registered, cleared when the last pending one for this session resolves.
|
|
660
|
+
* Absent (never `false`) for sessions with no held permissions. */
|
|
661
|
+
awaitingPermission?: boolean;
|
|
511
662
|
/** Count of turns that have fully completed (turn-end emitted) on this
|
|
512
663
|
* session. Lets `session_monitor` fast-return for a session that already
|
|
513
664
|
* finished its turn before the wait subscribed — a fast turn that ends
|
|
@@ -524,8 +675,12 @@ interface SessionDescriptor {
|
|
|
524
675
|
* from the pending tool call: a spawned sub-agent (`agent_start`) or a
|
|
525
676
|
* shell/terminal command. Deliberately NO "user" variant — waiting on
|
|
526
677
|
* the user is already covered by `awaitingInput`/`awaitingQuestion`.
|
|
527
|
-
*
|
|
528
|
-
*
|
|
678
|
+
*
|
|
679
|
+
* Set on tool-call; released (see `releaseBlockedOn`) on the MATCHING
|
|
680
|
+
* tool-result, on `error` (a failing tool never emits a result), on the
|
|
681
|
+
* next assistant `text-delta` (the model has the floor, so nothing is
|
|
682
|
+
* pending), at turn start, and in the turn's finally. A claim about the
|
|
683
|
+
* present tense — anything that disproves it must clear it. */
|
|
529
684
|
blockedOn?: "subagent" | "command";
|
|
530
685
|
/** toolCallId of the tool-call that set `blockedOn`. A tool-result only
|
|
531
686
|
* clears `blockedOn` when its toolCallId matches — so a nested or
|
|
@@ -574,6 +729,52 @@ interface SessionDescriptor {
|
|
|
574
729
|
* `agent_start.sandbox.reuse`). Only set when `remote` is true. */
|
|
575
730
|
sandboxTeardown?: "kill" | "pause";
|
|
576
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* A `session/request_permission` request parked by a permission-hold session,
|
|
734
|
+
* awaiting a human/orchestrator decision through the cross-session inbox
|
|
735
|
+
* (`permissions_list` / `permissions_respond`, `GET /permissions` /
|
|
736
|
+
* `POST /permissions/:id`, `agentproto permissions ls|approve|deny`).
|
|
737
|
+
*/
|
|
738
|
+
interface PendingPermission {
|
|
739
|
+
/** Stable id — the driver's own request id, unique across sessions. Pass to
|
|
740
|
+
* `respondPermission`. */
|
|
741
|
+
id: string;
|
|
742
|
+
sessionId: string;
|
|
743
|
+
/** ACP tool-call id the request was raised for (== `id` today; kept distinct
|
|
744
|
+
* so a future driver can surface a separate correlation id). */
|
|
745
|
+
toolCallId: string;
|
|
746
|
+
/** Tool title/kind the agent is asking permission for, when known. */
|
|
747
|
+
toolName?: string;
|
|
748
|
+
/** Human-readable "Allow X?" line. */
|
|
749
|
+
text: string;
|
|
750
|
+
/** Options the agent offered (ACP `requestPermission` shape). */
|
|
751
|
+
options: Array<{
|
|
752
|
+
optionId: string;
|
|
753
|
+
name?: string;
|
|
754
|
+
kind?: string;
|
|
755
|
+
}>;
|
|
756
|
+
/** ISO timestamp the request was parked. */
|
|
757
|
+
requestedAt: string;
|
|
758
|
+
}
|
|
759
|
+
/** How a caller resolves a pending permission — an explicit `optionId` wins
|
|
760
|
+
* over the `decision`→option mapping (approve → an allow-flavored option,
|
|
761
|
+
* deny → a reject-flavored option). `scope: "always"` prefers an
|
|
762
|
+
* allow-always option when the request offers one. */
|
|
763
|
+
interface PermissionRespondInput {
|
|
764
|
+
decision: "approve" | "deny";
|
|
765
|
+
optionId?: string;
|
|
766
|
+
scope?: "once" | "always";
|
|
767
|
+
}
|
|
768
|
+
type PermissionRespondResult = {
|
|
769
|
+
ok: true;
|
|
770
|
+
permission: PendingPermission;
|
|
771
|
+
decision: "approve" | "deny";
|
|
772
|
+
optionId?: string;
|
|
773
|
+
} | {
|
|
774
|
+
ok: false;
|
|
775
|
+
error: "not_found" | "session_gone" | "no_matching_option" | "unsupported";
|
|
776
|
+
message: string;
|
|
777
|
+
};
|
|
577
778
|
interface SessionsRegistry {
|
|
578
779
|
spawn(input: SpawnSessionInput): SessionDescriptor;
|
|
579
780
|
/** Adopt a ChildProcess that was spawned outside the registry —
|
|
@@ -621,12 +822,21 @@ interface SessionsRegistry {
|
|
|
621
822
|
* mints a fresh session id. The `stop` callback is invoked by
|
|
622
823
|
* `kill()` best-effort. */
|
|
623
824
|
registerBrowser(input: RegisterBrowserInput): SessionDescriptor;
|
|
624
|
-
/** Send a follow-up turn to a live agent session. Throws
|
|
625
|
-
* session is missing, not an agent-cli kind, dead (exited/
|
|
626
|
-
* error and unresumable — `SessionNotAliveError`), or busy
|
|
825
|
+
/** Send a follow-up turn to a live agent session and AWAIT it. Throws
|
|
826
|
+
* when the session is missing, not an agent-cli kind, dead (exited/
|
|
827
|
+
* killed/error and unresumable — `SessionNotAliveError`), or busy
|
|
627
828
|
* (mid-turn). The events stream into the existing ring buffer +
|
|
628
|
-
* line emitter so /stream consumers see them as they arrive.
|
|
629
|
-
|
|
829
|
+
* line emitter so /stream consumers see them as they arrive.
|
|
830
|
+
*
|
|
831
|
+
* `opts.interrupt` behaves exactly as it does on `enqueuePrompt`:
|
|
832
|
+
* a mid-turn session has its in-flight turn cancelled and settled
|
|
833
|
+
* before admission, so this prompt redirects the SAME live session
|
|
834
|
+
* instead of hitting the busy rejection. Ignored (identical to the
|
|
835
|
+
* default) when the session is idle. Omitted or `false` reproduces
|
|
836
|
+
* the previous mid-turn rejection byte-for-byte. */
|
|
837
|
+
sendPrompt(id: string, message: unknown, opts?: {
|
|
838
|
+
interrupt?: boolean;
|
|
839
|
+
}): Promise<void>;
|
|
630
840
|
/** Fire-and-forget variant of `sendPrompt` for the TURN ITSELF only.
|
|
631
841
|
* Admission (resume attempt + the missing/wrong-kind/dead/busy
|
|
632
842
|
* checks `sendPrompt` throws) is AWAITED before this resolves, so a
|
|
@@ -648,6 +858,22 @@ interface SessionsRegistry {
|
|
|
648
858
|
enqueuePrompt(id: string, message: unknown, opts?: {
|
|
649
859
|
interrupt?: boolean;
|
|
650
860
|
}): Promise<void>;
|
|
861
|
+
/** Cancel the in-flight turn on a live agent-cli session and leave the
|
|
862
|
+
* session itself alive and idle — the bare "interrupt, no next prompt"
|
|
863
|
+
* primitive `sendPrompt`/`enqueuePrompt`'s `opts.interrupt` arm lacks
|
|
864
|
+
* on its own, since that arm always exists to redirect onto a NEW
|
|
865
|
+
* prompt. Reuses the same `interruptInFlightTurn` helper those two
|
|
866
|
+
* share.
|
|
867
|
+
*
|
|
868
|
+
* Idempotent by design: idle, unknown-alive (starting/running only —
|
|
869
|
+
* same liveness `validateAgentTurn` checks), or already-terminal
|
|
870
|
+
* (exited/killed/error) all resolve `{ wasBusy: false }` rather than
|
|
871
|
+
* throwing — a no-op interrupt is not an error. Throws only when the
|
|
872
|
+
* id is unknown, or when `cancel()` itself rejects ("does not support
|
|
873
|
+
* interrupt", propagated from `interruptInFlightTurn`). */
|
|
874
|
+
interruptSession(id: string): Promise<{
|
|
875
|
+
wasBusy: boolean;
|
|
876
|
+
}>;
|
|
651
877
|
/** Stamp `lastActivityAt` on a live agent-cli session's descriptor
|
|
652
878
|
* and schedule a debounced persist. Called from the `onActivity`
|
|
653
879
|
* callback threaded down through the driver → ACP client, which
|
|
@@ -661,6 +887,14 @@ interface SessionsRegistry {
|
|
|
661
887
|
* Initial backfill: synchronously invokes `onLine` once for each
|
|
662
888
|
* line currently in the ring buffer so attaches show context. */
|
|
663
889
|
attach(id: string, onLine: (line: string, stream: "stdout" | "stderr") => void): (() => void) | null;
|
|
890
|
+
/** Subscribe to a session's structured events.jsonl records as they're
|
|
891
|
+
* written — the live-push half of `GET /sessions/:id/events/stream`'s
|
|
892
|
+
* replay-then-subscribe handoff. Thin passthrough to the transcript
|
|
893
|
+
* writer's own `subscribe` (see transcript-writer.ts for the exactly-
|
|
894
|
+
* once contract this enables); no backfill, no existence check — a
|
|
895
|
+
* session id with no writer state simply never calls back. Returns an
|
|
896
|
+
* unsubscribe fn. */
|
|
897
|
+
subscribeToRecords(id: string, onRecord: (record: Record<string, unknown>) => void): () => void;
|
|
664
898
|
/** Subscribe to a PTY session's byte stream. Returns a control
|
|
665
899
|
* handle (write/resize/detach) and null when the session is
|
|
666
900
|
* missing or not a PTY kind. Replays the ring buffer
|
|
@@ -692,6 +926,17 @@ interface SessionsRegistry {
|
|
|
692
926
|
* tail. Returns null when the session is missing or not a PTY. */
|
|
693
927
|
readTerminalOutput(id: string, lastBytes?: number): Buffer | null;
|
|
694
928
|
kill(id: string, signal?: NodeJS.Signals): boolean;
|
|
929
|
+
/** List permission requests currently parked in the pending-permissions
|
|
930
|
+
* inbox across all permission-hold sessions, newest last. Optionally
|
|
931
|
+
* filtered to one session. */
|
|
932
|
+
listPendingPermissions(filter?: {
|
|
933
|
+
sessionId?: string;
|
|
934
|
+
}): PendingPermission[];
|
|
935
|
+
/** Resolve a parked permission by id — maps the decision (or explicit
|
|
936
|
+
* optionId) to one of the offered options, resolves the held driver RPC,
|
|
937
|
+
* clears the session's awaiting-permission state, and emits
|
|
938
|
+
* `session:permission-resolved`. */
|
|
939
|
+
respondPermission(id: string, input: PermissionRespondInput): Promise<PermissionRespondResult>;
|
|
695
940
|
/** Stop tracking a session (after it exited and the user clicked
|
|
696
941
|
* "clear"). Doesn't kill — use `kill` first. */
|
|
697
942
|
forget(id: string): boolean;
|
|
@@ -764,6 +1009,11 @@ interface SpawnAgentInput {
|
|
|
764
1009
|
depth?: number;
|
|
765
1010
|
/** Requested model id — recorded on the descriptor for display + echo. */
|
|
766
1011
|
model?: string;
|
|
1012
|
+
/** Resolved auth echo (mode + fingerprint + provider/source/setEnv) —
|
|
1013
|
+
* recorded verbatim onto {@link SessionDescriptor.auth}. See that field's
|
|
1014
|
+
* doc for the full contract; the caller (`session-spawn.ts`) computes this
|
|
1015
|
+
* via the billing-auth resolver, never passing the raw credential here. */
|
|
1016
|
+
auth?: SessionAuthEcho;
|
|
767
1017
|
/** Hard ceiling on cumulative session cost (USD). When set and the
|
|
768
1018
|
* adapter's usage reader reports a higher cost at a turn-end, the session
|
|
769
1019
|
* is stopped (best-effort, turn-granular — caps continuation, can't abort
|
|
@@ -790,6 +1040,11 @@ interface SpawnAgentInput {
|
|
|
790
1040
|
/** What session close does to the box, when `remote` is true — see
|
|
791
1041
|
* `SessionDescriptor.sandboxTeardown`. */
|
|
792
1042
|
sandboxTeardown?: "kill" | "pause";
|
|
1043
|
+
/** True when the driver session was started in permission-hold mode
|
|
1044
|
+
* (`AgentCliStartOptions.permissionHold`) — its `agent-prompt` events carry
|
|
1045
|
+
* respondable permission requests. Gates whether the registry registers
|
|
1046
|
+
* them in the pending-permissions inbox. Default false. */
|
|
1047
|
+
permissionHold?: boolean;
|
|
793
1048
|
}
|
|
794
1049
|
interface SpawnSessionInput {
|
|
795
1050
|
kind: SessionKind;
|
|
@@ -1142,6 +1397,156 @@ declare class TunnelRegistry {
|
|
|
1142
1397
|
private loadFromDisk;
|
|
1143
1398
|
}
|
|
1144
1399
|
|
|
1400
|
+
/**
|
|
1401
|
+
* Daemon-side pairing registry (design: DESIGN §3/§4, PLAN deliverable 2).
|
|
1402
|
+
*
|
|
1403
|
+
* Owns everything the daemon needs to be *paired with* over an untrusted
|
|
1404
|
+
* rendezvous:
|
|
1405
|
+
*
|
|
1406
|
+
* - **Offer store** — in-memory, single-use, expiry-checked one-time tokens
|
|
1407
|
+
* minted by `pair offer`. A daemon restart voids outstanding offers
|
|
1408
|
+
* (acceptable + documented). The P1 `verifyOfferToken` predicate plugs in
|
|
1409
|
+
* here and SPENDS the token on the first successful handshake.
|
|
1410
|
+
* - **Pairings store** — `~/.agentproto/pairings.json` (0600, atomic write),
|
|
1411
|
+
* one record per paired client: `{clientPub, name, fingerprint, createdAt,
|
|
1412
|
+
* lastSeen, pairRoot, rendezvousUrl}`. `pairRoot` is the long-term shared
|
|
1413
|
+
* secret (`derivePairRoot`) from which reconnect routing tokens derive.
|
|
1414
|
+
* - **Rendezvous connections** — for a fresh offer, and for every persisted
|
|
1415
|
+
* pairing on boot (autoconnect), the daemon dials the rendezvous *outbound*
|
|
1416
|
+
* (`side=daemon&t=<token>`), runs `daemonHandshakeOverSink` with the P1
|
|
1417
|
+
* crypto, and — on success — serves the spliced, E2E-wrapped channel exactly
|
|
1418
|
+
* like `serve --connect` serves a tunnel host. The serving path itself is
|
|
1419
|
+
* injected (`serve`) so this module stays free of pty/adapter concerns and
|
|
1420
|
+
* the CLI can reuse its `createTunnelServer` config verbatim.
|
|
1421
|
+
* - **Reconnect epochs** — a persisted pairing's standing connection uses the
|
|
1422
|
+
* pairing-derived routing token `t' = HKDF(pairRoot, "rv-route"‖epoch)`,
|
|
1423
|
+
* rotated per UTC day. The daemon parks on BOTH the current and previous
|
|
1424
|
+
* epoch tokens so a client whose clock straddles midnight still finds it;
|
|
1425
|
+
* reconnect-with-backoff keeps the standing connection alive.
|
|
1426
|
+
* - **Revocation** — removing a pairing drops its rendezvous connections and
|
|
1427
|
+
* stops the daemon parking on its tokens, so it can no longer be reached.
|
|
1428
|
+
*
|
|
1429
|
+
* ## Reconnect authentication (why no stable client key)
|
|
1430
|
+
*
|
|
1431
|
+
* P1's handshake carries only a client *ephemeral* key (regenerated per
|
|
1432
|
+
* handshake) — there is no persistent client identity key, and the PLAN forbids
|
|
1433
|
+
* changing the handshake. So a reconnect authenticates the client by
|
|
1434
|
+
* **possession of the epoch routing token**, which derives from `pairRoot` — a
|
|
1435
|
+
* secret only the paired client and daemon share (it fell out of the original
|
|
1436
|
+
* ECDH). The daemon dials a pairing's epoch token, and on that connection the
|
|
1437
|
+
* `verifyOfferToken` predicate accepts a hello whose offer-token field equals
|
|
1438
|
+
* that same epoch token (constant-time). Token possession ⇒ paired party. The
|
|
1439
|
+
* stored `fingerprint` (of the first handshake's ephemeral key) is a stable
|
|
1440
|
+
* display/revocation handle, not an authentication input.
|
|
1441
|
+
*/
|
|
1442
|
+
|
|
1443
|
+
declare const PAIRINGS_VERSION: 1;
|
|
1444
|
+
/** One persisted pairing. Written verbatim to `pairings.json`. */
|
|
1445
|
+
interface PairingRecord {
|
|
1446
|
+
/** The client ephemeral public key from the FIRST handshake (a snapshot —
|
|
1447
|
+
* reconnects use fresh ephemerals; this is not an auth input). */
|
|
1448
|
+
clientPub: string;
|
|
1449
|
+
/** Human-facing label the client chose on `pair accept`. */
|
|
1450
|
+
name: string;
|
|
1451
|
+
/** `identityFingerprint(clientPub)` from the first handshake — the stable
|
|
1452
|
+
* display + revocation handle. */
|
|
1453
|
+
fingerprint: string;
|
|
1454
|
+
/** ISO-8601 first-pair timestamp. */
|
|
1455
|
+
createdAt: string;
|
|
1456
|
+
/** ISO-8601 of the most recent successful (re)connect. */
|
|
1457
|
+
lastSeen: string;
|
|
1458
|
+
/** Long-term shared secret (base64), from which epoch routing tokens derive. */
|
|
1459
|
+
pairRoot: string;
|
|
1460
|
+
/** Rendezvous endpoint to reconnect through. */
|
|
1461
|
+
rendezvousUrl: string;
|
|
1462
|
+
}
|
|
1463
|
+
/** Mode of a served channel — first-contact offer vs. an established reconnect. */
|
|
1464
|
+
type PairingChannelMode = "offer" | "reconnect";
|
|
1465
|
+
/** Context handed to the injected `serve` when a channel comes up. */
|
|
1466
|
+
interface PairingChannelContext {
|
|
1467
|
+
mode: PairingChannelMode;
|
|
1468
|
+
/** Stable pairing fingerprint (present for reconnect; for an offer it's the
|
|
1469
|
+
* freshly-derived one). */
|
|
1470
|
+
fingerprint: string;
|
|
1471
|
+
/** Client label. */
|
|
1472
|
+
name: string;
|
|
1473
|
+
}
|
|
1474
|
+
/** Handle to a live served channel — the registry closes it on teardown. */
|
|
1475
|
+
interface PairingChannelHandle {
|
|
1476
|
+
close(): Promise<void>;
|
|
1477
|
+
}
|
|
1478
|
+
interface CreatedOffer {
|
|
1479
|
+
/** The one-time offer token (base64url). */
|
|
1480
|
+
token: string;
|
|
1481
|
+
/** Offer expiry, unix seconds. */
|
|
1482
|
+
exp: number;
|
|
1483
|
+
/** The full `agentproto://pair?…` offer URL. */
|
|
1484
|
+
url: string;
|
|
1485
|
+
/** The daemon's identity fingerprint (shown to the human at accept time). */
|
|
1486
|
+
fingerprint: string;
|
|
1487
|
+
/** The rendezvous the offer routes through. */
|
|
1488
|
+
rendezvousUrl: string;
|
|
1489
|
+
/** True when `rendezvousUrl` is the hosted default — i.e. neither an explicit
|
|
1490
|
+
* `--rendezvous` nor a configured `pairing.rendezvous` applied, so the offer
|
|
1491
|
+
* falls back to `HOSTED_RENDEZVOUS_URL`. Surfaced so `pair offer` can flag,
|
|
1492
|
+
* never silently, that the daemon is relaying through our infrastructure. */
|
|
1493
|
+
rendezvousIsHostedDefault: boolean;
|
|
1494
|
+
}
|
|
1495
|
+
interface CreateOfferInput {
|
|
1496
|
+
/** Time-to-live in ms. Default 10 minutes. */
|
|
1497
|
+
ttlMs?: number;
|
|
1498
|
+
/** Rendezvous URL override; falls back to the configured default. */
|
|
1499
|
+
rendezvousUrl?: string;
|
|
1500
|
+
}
|
|
1501
|
+
interface PairingRegistryDeps {
|
|
1502
|
+
/** Lazily load (create on first use) the daemon identity. Kept lazy so a
|
|
1503
|
+
* daemon that never pairs never writes an identity file. */
|
|
1504
|
+
loadIdentity: () => Promise<DaemonIdentity>;
|
|
1505
|
+
/** Path to `pairings.json`. Defaults to `~/.agentproto/pairings.json`. */
|
|
1506
|
+
pairingsPath?: string;
|
|
1507
|
+
/** Configured rendezvous URL (from `config.pairing.rendezvous`). Three states:
|
|
1508
|
+
* - `undefined` (key absent) → `createOffer` falls back to the hosted default
|
|
1509
|
+
* (`HOSTED_RENDEZVOUS_URL`).
|
|
1510
|
+
* - a non-empty URL → that endpoint is the default for offers.
|
|
1511
|
+
* - `""` (explicitly empty) → an explicit opt-out: no default applies, so an
|
|
1512
|
+
* offer without `--rendezvous` fails closed. Wire it through verbatim (do
|
|
1513
|
+
* not coerce `""` to `undefined`) or the opt-out silently reverts to the
|
|
1514
|
+
* hosted default. */
|
|
1515
|
+
defaultRendezvousUrl?: string;
|
|
1516
|
+
/** Dial a rendezvous WS and adapt it to a FrameSink. Injected by the CLI
|
|
1517
|
+
* (uses `ws` + `wrapWebSocket`). Rejects if the dial fails. The `signal`
|
|
1518
|
+
* aborts on registry shutdown. */
|
|
1519
|
+
dial: (wsUrl: string, signal: AbortSignal) => Promise<FrameSink>;
|
|
1520
|
+
/** Serve the E2E-wrapped channel (the CLI builds `createTunnelServer` over
|
|
1521
|
+
* it with the same config `serve --connect` uses). */
|
|
1522
|
+
serve: (sink: E2eFrameSink, ctx: PairingChannelContext) => PairingChannelHandle;
|
|
1523
|
+
/** Injectable clock (ms). Defaults to Date.now. */
|
|
1524
|
+
now?: () => number;
|
|
1525
|
+
/** Diagnostic log sink. */
|
|
1526
|
+
log?: (line: string) => void;
|
|
1527
|
+
/** Reconnect backoff floor / ceiling (ms). Defaults 1s / 30s. */
|
|
1528
|
+
reconnectMinMs?: number;
|
|
1529
|
+
reconnectMaxMs?: number;
|
|
1530
|
+
/** How long a parked daemon socket waits for a client hello before recycling
|
|
1531
|
+
* the connection. Kept just under the broker's park timeout. Default 110s. */
|
|
1532
|
+
handshakeTimeoutMs?: number;
|
|
1533
|
+
}
|
|
1534
|
+
interface PairingRegistry {
|
|
1535
|
+
/** Mint a one-time offer + start dialing the rendezvous for it. */
|
|
1536
|
+
createOffer(input?: CreateOfferInput): Promise<CreatedOffer>;
|
|
1537
|
+
/** All persisted pairings (copies). Loads `pairings.json` on first call. */
|
|
1538
|
+
list(): Promise<PairingRecord[]>;
|
|
1539
|
+
/** Drop a pairing by fingerprint or name; stops its rendezvous connections.
|
|
1540
|
+
* Returns false when nothing matched. */
|
|
1541
|
+
revoke(idOrName: string): Promise<boolean>;
|
|
1542
|
+
/** Start standing reconnect connections for every persisted pairing. Call
|
|
1543
|
+
* after the gateway is up (so the injected `serve` can reach it). */
|
|
1544
|
+
startAutoconnect(): Promise<void>;
|
|
1545
|
+
/** Tear down every offer + reconnect connection and served channel. */
|
|
1546
|
+
shutdown(): Promise<void>;
|
|
1547
|
+
}
|
|
1548
|
+
declare function createPairingRegistry(deps: PairingRegistryDeps): PairingRegistry;
|
|
1549
|
+
|
|
1145
1550
|
/**
|
|
1146
1551
|
* Fire-and-forget webhook notifier for session lifecycle events.
|
|
1147
1552
|
*
|
|
@@ -1440,6 +1845,19 @@ interface PolicyRunState {
|
|
|
1440
1845
|
nextPolicyId?: string;
|
|
1441
1846
|
error?: string;
|
|
1442
1847
|
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Does `policy` watch `sessionId`? True when the id is the representative
|
|
1850
|
+
* `sessionId` or appears anywhere in the fan-in `sessionIds` group — the two
|
|
1851
|
+
* ways `AttachPolicyInput` can name a session.
|
|
1852
|
+
*
|
|
1853
|
+
* This is the reverse of the policy→session link, and it is deliberately
|
|
1854
|
+
* computed on demand over `list()` rather than persisted: a session→policy
|
|
1855
|
+
* index in policies.json (or a `policies` field on the session record) would
|
|
1856
|
+
* be a second source of truth to keep in sync across attach, fan-in nudges,
|
|
1857
|
+
* `next` chaining, and snapshot reload. A filtered pass is O(policies) on a
|
|
1858
|
+
* set the daemon already holds in memory.
|
|
1859
|
+
*/
|
|
1860
|
+
declare function policyWatchesSession(policy: PolicyRunState, sessionId: string): boolean;
|
|
1443
1861
|
interface CompletionPolicySupervisor {
|
|
1444
1862
|
/**
|
|
1445
1863
|
* Attach a completion policy to an already-running session.
|
|
@@ -1870,6 +2288,17 @@ type AgentAdapterResolver = (slug: string) => Promise<{
|
|
|
1870
2288
|
* handler, POST /sessions/agent) wires this to pulse
|
|
1871
2289
|
* `SessionDescriptor.lastActivityAt` via `registry.pulseActivity(id)`. */
|
|
1872
2290
|
onActivity?: () => void;
|
|
2291
|
+
/** Start the session in permission-hold mode — forwarded to the driver's
|
|
2292
|
+
* `runtime.start({ permissionHold })` so each ACP permission request is
|
|
2293
|
+
* surfaced + parked in the daemon's inbox instead of auto-answered.
|
|
2294
|
+
* Adapters/arms with no permission surface ignore it. Default false. */
|
|
2295
|
+
permissionHold?: boolean;
|
|
2296
|
+
/** FULLY-RESOLVED billing-auth spec forwarded from `agent_start` to the
|
|
2297
|
+
* driver's `runtime.start({ auth })`, computed by the runtime's
|
|
2298
|
+
* `resolveAuthSpec` (provider, ordered mode, setEnv/scrub, credential
|
|
2299
|
+
* source all pre-decided). The driver applies it mechanically. Absent
|
|
2300
|
+
* when the resolver produced no spec (ambient). */
|
|
2301
|
+
auth?: ResolvedAuthSpec;
|
|
1873
2302
|
}): Promise<AgentSessionLike>;
|
|
1874
2303
|
/** Display label for the descriptor's `command` field. */
|
|
1875
2304
|
commandPreview?: string;
|
|
@@ -1887,6 +2316,15 @@ type AgentAdapterResolver = (slug: string) => Promise<{
|
|
|
1887
2316
|
* documented no-op for that adapter (e.g. claude-code, which
|
|
1888
2317
|
* auto-discovers skills and declares no such option). */
|
|
1889
2318
|
declaredOptions?: readonly DeclaredAdapterOption[];
|
|
2319
|
+
/** Billing-auth descriptor projected from the adapter manifest
|
|
2320
|
+
* (`provider` / `authEnforce` / `authSubscription`) — the sole input the
|
|
2321
|
+
* runtime's `resolveAuthSpec` reads about the adapter's auth capability
|
|
2322
|
+
* (keeping the catalog coupling in the runtime, the driver mechanical).
|
|
2323
|
+
* Omitted ⇒ ambient (no credential injection). */
|
|
2324
|
+
authDescriptor?: AdapterAuthDescriptor;
|
|
2325
|
+
/** Adapter's default model id (`models.default`) — lets the resolver derive
|
|
2326
|
+
* a provider for a by-model spawn that omitted `model`. */
|
|
2327
|
+
defaultModel?: string;
|
|
1890
2328
|
} | null>;
|
|
1891
2329
|
/**
|
|
1892
2330
|
* UI-safe projection of an AIP-45 `modes[]` entry as surfaced by
|
|
@@ -2098,6 +2536,27 @@ interface McpCredentialDeps {
|
|
|
2098
2536
|
declare function setMcpCredentialDeps(d: McpCredentialDeps): void;
|
|
2099
2537
|
declare function getMcpCredentialDeps(): McpCredentialDeps;
|
|
2100
2538
|
|
|
2539
|
+
/**
|
|
2540
|
+
* MCP tools that drive the pairing registry — the daemon's "let a client pair
|
|
2541
|
+
* with me over an untrusted rendezvous" surface (design: DESIGN §6).
|
|
2542
|
+
*
|
|
2543
|
+
* Three tools, mirroring the CLI verbs:
|
|
2544
|
+
*
|
|
2545
|
+
* pair_offer — mint a one-time offer + start dialing the rendezvous; returns
|
|
2546
|
+
* the `agentproto://pair?…` URL, fingerprint, and expiry.
|
|
2547
|
+
* pair_list — list persisted pairings (name, fingerprint, lastSeen).
|
|
2548
|
+
* pair_revoke — drop a pairing by fingerprint or name; stops its rendezvous
|
|
2549
|
+
* connections so it can no longer reconnect.
|
|
2550
|
+
*
|
|
2551
|
+
* Registered beside `remote_*` (remote-tools.ts) — same closure-rebind pattern;
|
|
2552
|
+
* the registry singleton lives on the gateway.
|
|
2553
|
+
*/
|
|
2554
|
+
|
|
2555
|
+
interface RegisterPairingToolsOptions {
|
|
2556
|
+
registry: PairingRegistry;
|
|
2557
|
+
}
|
|
2558
|
+
declare function registerPairingTools(server: McpServer, opts: RegisterPairingToolsOptions): void;
|
|
2559
|
+
|
|
2101
2560
|
/**
|
|
2102
2561
|
* Provider-preset family on top of `@agentproto/provider-kit` — the *thin,
|
|
2103
2562
|
* honest* lister (Option B). Unlike tunnels/sandboxes/eval-reporters, presets
|
|
@@ -2176,6 +2635,158 @@ declare function declaredPresetToProviderPreset(decl: DeclaredAdapterPreset): Pr
|
|
|
2176
2635
|
*/
|
|
2177
2636
|
declare function listPresets(env?: Record<string, string | undefined>, adapterPresets?: readonly DeclaredAdapterPreset[]): AdapterEntry<PresetInfo>[];
|
|
2178
2637
|
|
|
2638
|
+
/**
|
|
2639
|
+
* Per-workspace state buckets — AIP-46 §State partitioning.
|
|
2640
|
+
*
|
|
2641
|
+
* AIP-46's premise is "a single host serve many bound directories", and
|
|
2642
|
+
* §Workspaces delivered the addressing half of it: a slug resolves to a
|
|
2643
|
+
* path. The state those directories generate stayed pooled in one global
|
|
2644
|
+
* `~/.agentproto/sessions.json` with `workspaceSlug` demoted to a column
|
|
2645
|
+
* on each row — a label, not a boundary.
|
|
2646
|
+
*
|
|
2647
|
+
* What that costs is measurable rather than theoretical. `HISTORY_CAP`
|
|
2648
|
+
* bounds retention across the *union* of every workspace, so the cap is
|
|
2649
|
+
* spent by whoever was busiest. A store pooling a few hundred rows from
|
|
2650
|
+
* several workspaces sits at that ceiling in ordinary use, and the
|
|
2651
|
+
* workspace contributing a handful of them is one busy afternoon in a
|
|
2652
|
+
* NEIGHBOUR away from losing all of them — having done nothing itself.
|
|
2653
|
+
* The eviction is silent and lands on the quietest workspace.
|
|
2654
|
+
*
|
|
2655
|
+
* This module is the slug→bucket rule and nothing more:
|
|
2656
|
+
*
|
|
2657
|
+
* ~/.agentproto/
|
|
2658
|
+
* ├── workspaces.json # the registry (workspaces-config.ts)
|
|
2659
|
+
* └── workspaces/ # one bucket per workspace (here)
|
|
2660
|
+
* ├── agentik-studio/sessions.json
|
|
2661
|
+
* └── default/sessions.json
|
|
2662
|
+
*
|
|
2663
|
+
* The registry (a file) and the state (a directory) are deliberate
|
|
2664
|
+
* siblings: one names the workspaces, the other holds what each
|
|
2665
|
+
* accumulated.
|
|
2666
|
+
*
|
|
2667
|
+
* ## Membership is the validation
|
|
2668
|
+
*
|
|
2669
|
+
* `workspaceSlug` arrives on a spawn request — it is caller input
|
|
2670
|
+
* (`session-spawn.ts` passes `input.workspaceSlug` straight through when
|
|
2671
|
+
* an explicit `cwd` accompanies it, so nothing sanitises it on the way
|
|
2672
|
+
* to the descriptor). A bucket is a directory name. Joining the two
|
|
2673
|
+
* without a check hands the caller `../../` under the daemon's own state
|
|
2674
|
+
* root, as the daemon's UID.
|
|
2675
|
+
*
|
|
2676
|
+
* So a slug does not become a directory name by being cleaned up — it
|
|
2677
|
+
* becomes one by coming *back from* the registry lookup. The registry's
|
|
2678
|
+
* slugs were sanitised by `sanitizeSlug` on the way in, so the only
|
|
2679
|
+
* strings that ever reach the filesystem are ones the host itself
|
|
2680
|
+
* minted. An unregistered slug isn't rejected or escaped; it fails to
|
|
2681
|
+
* match and lands in `default` like any other unregistered work. This is
|
|
2682
|
+
* why the rule is membership rather than sanitisation: you cannot forget
|
|
2683
|
+
* to validate a value you never chose.
|
|
2684
|
+
*
|
|
2685
|
+
* ## What this does NOT buy
|
|
2686
|
+
*
|
|
2687
|
+
* Partitioning bounds what a workspace's state *costs* other workspaces.
|
|
2688
|
+
* It does not bound who may read it — every bucket is still served to
|
|
2689
|
+
* every authorised caller, because no workspace-scoped credential
|
|
2690
|
+
* exists to serve them differently (pairings carry no workspace concept
|
|
2691
|
+
* at all; the orchestrator scope-token's `session_list` is daemon-wide
|
|
2692
|
+
* by documented debt, `orchestrator-gateway.ts`). The files moved; the
|
|
2693
|
+
* access did not. AIP-46 §Security Considerations says this in the
|
|
2694
|
+
* negative — do not read this module as an isolation control.
|
|
2695
|
+
*/
|
|
2696
|
+
/** The bucket every record lands in when its slug doesn't resolve —
|
|
2697
|
+
* absent, empty, or naming a workspace that isn't registered.
|
|
2698
|
+
*
|
|
2699
|
+
* A real bucket, not an error state. Refusing to persist an
|
|
2700
|
+
* unregistered session would make this a breaking change for every
|
|
2701
|
+
* one-off `cwd` spawn, and the obvious workaround (auto-register)
|
|
2702
|
+
* turns a registry of the user's intent into a registry of everything
|
|
2703
|
+
* that ever ran. The cost is stated plainly in the AIP: `default`'s
|
|
2704
|
+
* occupants get no separation from *each other* — registering a
|
|
2705
|
+
* workspace is what buys that. */
|
|
2706
|
+
declare const DEFAULT_BUCKET = "default";
|
|
2707
|
+
/** Root of the per-workspace state buckets. Sibling of the
|
|
2708
|
+
* `workspaces.json` registry that names them. */
|
|
2709
|
+
declare const BUCKETS_ROOT: () => string;
|
|
2710
|
+
/** The pre-partition global snapshot. Still read (to migrate from) and
|
|
2711
|
+
* never written after the split — see `migrateLegacySessionsFile`. */
|
|
2712
|
+
declare const LEGACY_SESSIONS_FILE: () => string;
|
|
2713
|
+
/** Marker recording that the legacy split already ran. Its presence —
|
|
2714
|
+
* not the legacy file's absence — is what makes migration
|
|
2715
|
+
* once-and-only-once, so a user who deletes rows from a bucket doesn't
|
|
2716
|
+
* get them resurrected on the next boot. */
|
|
2717
|
+
declare const migrationMarkerPath: (root: string) => string;
|
|
2718
|
+
declare const bucketDir: (root: string, slug: string) => string;
|
|
2719
|
+
declare const bucketSessionsFile: (root: string, slug: string) => string;
|
|
2720
|
+
/** Per-bucket transcript directory (AIP-46 §Layout).
|
|
2721
|
+
*
|
|
2722
|
+
* Not yet wired: the registry still writes transcripts to the shared
|
|
2723
|
+
* `~/.agentproto/sessions/`. Moving them needs the read side to move
|
|
2724
|
+
* too, and several readers currently ignore the configured base dir
|
|
2725
|
+
* entirely (`http-server.ts` and `transcript-export.ts` call
|
|
2726
|
+
* `sessionEventsPath(id)` with no `baseDir`, so they resolve off
|
|
2727
|
+
* `homedir()` regardless) — a pre-existing bug that a partial move
|
|
2728
|
+
* would turn into missing transcripts. Exported so the follow-up has
|
|
2729
|
+
* the path rule in one place. The AIP makes transcripts a SHOULD, not
|
|
2730
|
+
* a MUST, for exactly this reason. */
|
|
2731
|
+
declare const bucketTranscriptDir: (root: string, slug: string) => string;
|
|
2732
|
+
declare const isSafeBucketSlug: (slug: string) => boolean;
|
|
2733
|
+
/**
|
|
2734
|
+
* The slug→bucket rule (AIP-46 §Bucket resolution).
|
|
2735
|
+
*
|
|
2736
|
+
* 1. non-empty AND registered → that workspace's bucket
|
|
2737
|
+
* 2. otherwise → `default`
|
|
2738
|
+
*
|
|
2739
|
+
* `registered` must come from the workspaces registry. Passing a set
|
|
2740
|
+
* built from caller input defeats the entire point of this function.
|
|
2741
|
+
*/
|
|
2742
|
+
declare function resolveBucketSlug(workspaceSlug: string | undefined | null, registered: ReadonlySet<string>): string;
|
|
2743
|
+
/** Registered slugs, read fresh. Cheap (the registry is <1KB) and read
|
|
2744
|
+
* per persist rather than cached at boot, so a workspace registered
|
|
2745
|
+
* while the daemon is up starts bucketing immediately instead of
|
|
2746
|
+
* silently pooling into `default` until restart. Never throws: a
|
|
2747
|
+
* missing/corrupt registry degrades to "nothing is registered", i.e.
|
|
2748
|
+
* everything lands in `default` — today's pooled behaviour, which is
|
|
2749
|
+
* the right failure direction. */
|
|
2750
|
+
declare function readRegisteredSlugs(configPath?: string): ReadonlySet<string>;
|
|
2751
|
+
/** Bucket directories that exist on disk. Order is not meaningful. */
|
|
2752
|
+
declare function listBuckets(root: string): string[];
|
|
2753
|
+
interface MigrationMarker {
|
|
2754
|
+
version: 1;
|
|
2755
|
+
migratedAt: string;
|
|
2756
|
+
/** Absolute path of the legacy artifact this split read from. Left
|
|
2757
|
+
* untouched on disk — recorded so the provenance survives. */
|
|
2758
|
+
from: string;
|
|
2759
|
+
/** Total records read out of the legacy artifact. */
|
|
2760
|
+
rows: number;
|
|
2761
|
+
/** Records landed, per bucket. Sums to `rows` — the migration never
|
|
2762
|
+
* drops a record it cannot place; it sends it to `default`. */
|
|
2763
|
+
byBucket: Record<string, number>;
|
|
2764
|
+
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Split the legacy global snapshot into buckets (AIP-46 §Migration).
|
|
2767
|
+
*
|
|
2768
|
+
* **Additive by mandate.** The legacy file is opened read-only and left
|
|
2769
|
+
* exactly where it is — never deleted, truncated, or rewritten. It is
|
|
2770
|
+
* the user's data and their only rollback; the buckets are a derivative
|
|
2771
|
+
* until they decide otherwise. Reclaiming it is a separate, human-gated
|
|
2772
|
+
* call, and this module deliberately ships no GC: a real store is backed
|
|
2773
|
+
* by a transcript directory that can reach hundreds of megabytes and may
|
|
2774
|
+
* be someone's only copy of months of work — a deleter that guesses
|
|
2775
|
+
* wrong there is unrecoverable.
|
|
2776
|
+
*
|
|
2777
|
+
* Idempotent via the marker, not via the legacy file's absence — the
|
|
2778
|
+
* file stays, so absence would never fire and every boot would re-import
|
|
2779
|
+
* rows the user had since deleted from a bucket.
|
|
2780
|
+
*
|
|
2781
|
+
* Returns the marker it wrote, or `null` when there was nothing to do
|
|
2782
|
+
* (already migrated, or no legacy file).
|
|
2783
|
+
*/
|
|
2784
|
+
declare function migrateLegacySessionsFile(opts: {
|
|
2785
|
+
root: string;
|
|
2786
|
+
legacyFile: string;
|
|
2787
|
+
registered: ReadonlySet<string>;
|
|
2788
|
+
}): MigrationMarker | null;
|
|
2789
|
+
|
|
2179
2790
|
/** A journal-file StepCache scoped to one cacheKey. Best-effort: read/parse
|
|
2180
2791
|
* failures degrade to "no cache" (a miss), never throw into the run. */
|
|
2181
2792
|
declare function createFileStepCache(cacheKey: string, opts?: {
|
|
@@ -2454,6 +3065,30 @@ interface CreateGatewayOptions {
|
|
|
2454
3065
|
* tool family, and the `/sessions/:id/pty` WebSocket. Without it,
|
|
2455
3066
|
* those routes return 501 / the MCP tools aren't registered. */
|
|
2456
3067
|
spawnPty?: PtyFactory;
|
|
3068
|
+
/** Enable filesystem persistence for every store this gateway owns —
|
|
3069
|
+
* the sessions registry plus the supervisor / routine / cron / workflow
|
|
3070
|
+
* / inbound-watcher run stores. Defaults to `true`, i.e. production is
|
|
3071
|
+
* unchanged.
|
|
3072
|
+
*
|
|
3073
|
+
* Tests pass `false`. Without it, a test gateway writes its fake rows
|
|
3074
|
+
* into the *developer's real* `~/.agentproto/sessions.json` (and the
|
|
3075
|
+
* sibling `policies.json` / `routine-runs.json` / `cron-jobs.json` /
|
|
3076
|
+
* `workflow-runs.json`): those paths resolve off `homedir()`, and the
|
|
3077
|
+
* gateway opted each store into persistence unconditionally. Since
|
|
3078
|
+
* `loadHistorySnapshot` re-reads sessions.json at boot, the real daemon
|
|
3079
|
+
* then restores the fakes and shows them in the dashboard — and against
|
|
3080
|
+
* `HISTORY_CAP` (200) they evict genuine history.
|
|
3081
|
+
*
|
|
3082
|
+
* The sibling stores already default to persist-off when constructed
|
|
3083
|
+
* directly (see `createRoutineRunner`); this knob is what lets a
|
|
3084
|
+
* gateway stop overriding that on their behalf. */
|
|
3085
|
+
persist?: boolean;
|
|
3086
|
+
/** Override the sessions-registry persistence path — tests pin a tmpdir
|
|
3087
|
+
* so an assertion can look at the file the gateway would have written
|
|
3088
|
+
* instead of the developer's real one. Defaults to
|
|
3089
|
+
* `~/.agentproto/sessions.json`. The structured-transcript dir follows
|
|
3090
|
+
* this path's parent, so pinning it isolates transcripts too. */
|
|
3091
|
+
persistPath?: string;
|
|
2457
3092
|
/** Override the per-boot bearer token. Default: `randomUUID()`.
|
|
2458
3093
|
* Tests can pin a known value; production should always let
|
|
2459
3094
|
* the gateway generate fresh. The token is written into
|
|
@@ -2491,6 +3126,16 @@ interface CreateGatewayOptions {
|
|
|
2491
3126
|
/** Optional sandbox provider lister — mirrors `listAgentAdapters`.
|
|
2492
3127
|
* Overrides the default catalog-driven lister behind `list_sandbox_providers`. */
|
|
2493
3128
|
listSandboxProviders?: SandboxProviderLister;
|
|
3129
|
+
/**
|
|
3130
|
+
* Optional E2E pairing registry (see `createPairingRegistry`). When wired,
|
|
3131
|
+
* the gateway mounts the `/pairings/*` REST routes and the `pair_*` MCP
|
|
3132
|
+
* tools, and exposes the registry on the handle. The CLI builds it (injecting
|
|
3133
|
+
* the ws `dial` + a `createTunnelServer`-backed `serve`) and passes it here so
|
|
3134
|
+
* the runtime stays free of pty/adapter concerns while the pairing surface is
|
|
3135
|
+
* reachable over MCP + HTTP. Autoconnect is the caller's to start (after this
|
|
3136
|
+
* returns) so the injected `serve` can capture the finished gateway.
|
|
3137
|
+
*/
|
|
3138
|
+
pairingRegistry?: PairingRegistry;
|
|
2494
3139
|
}
|
|
2495
3140
|
interface GatewayHandle {
|
|
2496
3141
|
url: string;
|
|
@@ -2505,6 +3150,10 @@ interface GatewayHandle {
|
|
|
2505
3150
|
* local port. Exposed so embedding hosts can open tunnels
|
|
2506
3151
|
* programmatically without going through the HTTP/MCP surface. */
|
|
2507
3152
|
tunnels: TunnelRegistry;
|
|
3153
|
+
/** E2E pairing registry, when one was wired via
|
|
3154
|
+
* `CreateGatewayOptions.pairingRegistry`. Undefined otherwise. Exposed so
|
|
3155
|
+
* the CLI can `startAutoconnect()` after boot and `shutdown()` it. */
|
|
3156
|
+
pairing?: PairingRegistry;
|
|
2508
3157
|
/** Per-boot bearer token required on mutating /sessions/* routes
|
|
2509
3158
|
* + WS PTY upgrades. Exposed so an embedding host (e.g. the CLI
|
|
2510
3159
|
* shell that hosts the gateway in-process) can pass it to child
|
|
@@ -2534,4 +3183,4 @@ interface GatewayHandle {
|
|
|
2534
3183
|
*/
|
|
2535
3184
|
declare function createGateway(opts: CreateGatewayOptions): Promise<GatewayHandle>;
|
|
2536
3185
|
|
|
2537
|
-
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 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, type PolicyRunState, type PolicyRunStatus, type PresetInfo, type PricingResolver, type RegisterBrowserInput, type RegisterSessionInput, 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, createScopeTokenRegistry, daemonRegistryDir, declaredPresetToProviderPreset, deriveSessionUsage, formatToolCall, formatToolResult, getMcpCredentialDeps, listPresets, makeBrowserAdapterLister, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, projectSessionUsage, readDaemonRegistry, readRuntimeMeta, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
|
|
3186
|
+
export { AdapterAuthDescriptor, type AdapterListEntry, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AttachPolicyInput, BUCKETS_ROOT, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, type CommitSpec, type CompletionPolicySupervisor, type CreateGatewayOptions, type CreateOfferInput, type CreatedOffer, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, DeclaredAdapterOption, type DeclaredAdapterPreset, type GateSpec, type GatewayHandle, type InboundWatcher, type JudgeGateSpec, LEGACY_SESSIONS_FILE, type McpCredentialDeps, type MigrationMarker, 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, bucketDir, bucketSessionsFile, bucketTranscriptDir, composeSessionObservers, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createScopeTokenRegistry, daemonRegistryDir, declaredPresetToProviderPreset, deriveSessionUsage, formatToolCall, formatToolResult, getMcpCredentialDeps, isSafeBucketSlug, listBuckets, listPresets, makeBrowserAdapterLister, migrateLegacySessionsFile, migrationMarkerPath, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, policyWatchesSession, projectSessionUsage, readDaemonRegistry, readRegisteredSlugs, readRuntimeMeta, registerPairingTools, resolveBucketSlug, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
|