@byok-sdk/client 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +91 -1
  2. package/dist/adapters/index.js +183 -34
  3. package/dist/adapters/index.js.map +1 -1
  4. package/dist/adapters/pi/events.d.ts +1 -1
  5. package/dist/adapters/pi/mcp-config.d.ts +1 -0
  6. package/dist/adapters/pi/mcp-extension.js +25 -0
  7. package/dist/adapters/pi/mcp-extension.js.map +1 -0
  8. package/dist/adapters/pi/permission-mapping.d.ts +1 -1
  9. package/dist/adapters/pi/pi-adapter.d.ts +5 -0
  10. package/dist/adapters/pi/resolve-extensions.d.ts +11 -0
  11. package/dist/agent-home.d.ts +106 -0
  12. package/dist/bin/byok-agent.js +12902 -9340
  13. package/dist/bin/byok-agent.js.map +1 -1
  14. package/dist/bin/byok-approval-mcp.js.map +1 -1
  15. package/dist/bin/commands/toolsets.d.ts +8 -0
  16. package/dist/bin/config.d.ts +3 -1
  17. package/dist/bin/format.d.ts +5 -1
  18. package/dist/bin/official-release.d.ts +1 -0
  19. package/dist/bin/version.d.ts +2 -0
  20. package/dist/daemon/agent-content-audit-store.d.ts +35 -0
  21. package/dist/daemon/agent-content-read.d.ts +169 -0
  22. package/dist/daemon/agent-egress-controller.d.ts +76 -0
  23. package/dist/daemon/agent-egress-policy.d.ts +36 -0
  24. package/dist/daemon/agent-egress-sanitizer.d.ts +38 -0
  25. package/dist/daemon/agent-egress-spool.d.ts +116 -0
  26. package/dist/daemon/agent-session-handoff-store.d.ts +82 -0
  27. package/dist/daemon/blob-client.d.ts +6 -2
  28. package/dist/daemon/connection-manager.d.ts +4 -2
  29. package/dist/daemon/control-protocol.d.ts +12 -0
  30. package/dist/daemon/create-daemon.d.ts +83 -3
  31. package/dist/daemon/long-poll-transport.d.ts +60 -0
  32. package/dist/daemon/presence-publisher.d.ts +9 -3
  33. package/dist/daemon/task-runner.d.ts +73 -4
  34. package/dist/daemon/toolset-registry.d.ts +30 -0
  35. package/dist/daemon/url.d.ts +21 -0
  36. package/dist/daemon/ws-transport.d.ts +32 -5
  37. package/dist/index.d.ts +14 -2
  38. package/dist/index.js +12682 -9191
  39. package/dist/index.js.map +1 -1
  40. package/dist/release-identity.d.ts +15 -0
  41. package/dist/types.d.ts +44 -0
  42. package/package.json +7 -5
@@ -1,6 +1,9 @@
1
- import type { RuntimeId } from '@byok-sdk/protocol';
1
+ import type { AgentEgressPolicy, RuntimeId } from '@byok-sdk/protocol';
2
2
  import type { PermissionPolicy } from '@byok-sdk/protocol';
3
- import type { RuntimeAdapter, GitWorkspaceConfig, McpToolsetConfig } from '../types';
3
+ import type { RuntimeAdapter, GitWorkspaceConfig, McpToolsetConfig, McpToolsetObservation, McpToolsetRegistryStatus, McpToolsetReloadReceipt } from '../types';
4
+ import { type AgentHomeProjection } from '../agent-home';
5
+ import type { AgentRef } from '../agent-home';
6
+ import { type LocalAgentReleaseIdentity } from '../release-identity';
4
7
  import type { BackoffOptions, LivenessOptions } from './ws-transport';
5
8
  import { type OperationalHealthSnapshot } from './operational-health';
6
9
  import { type DaemonEventListener, type DaemonTaskInfo, type Unsubscribe } from './observer';
@@ -12,6 +15,10 @@ import { type JournalOpenFaultSeam } from './journal/sqlite-support';
12
15
  import { LocalStoragePressureEngine, type LocalStoragePolicyInput } from './journal/storage-policy';
13
16
  import { type ResultDocumentExtractor } from './task-runner';
14
17
  import { type ProgressBatcherOptions } from './progress-batcher';
18
+ import { type AgentEgressReliableAppendResult } from './agent-egress-controller';
19
+ import { type AgentEgressStatus } from './agent-egress-policy';
20
+ import { type AgentEgressSanitizer } from './agent-egress-sanitizer';
21
+ import { type AgentContentReadRoot } from './agent-content-read';
15
22
  /**
16
23
  * Optional white-label product display info — purely opaque passthrough
17
24
  * (never interpreted, validated, or rendered by the daemon itself). Carried
@@ -83,12 +90,34 @@ export interface HostedJournalConfig {
83
90
  storagePolicy?: LocalStoragePolicyInput;
84
91
  }
85
92
  export interface DaemonConfig {
93
+ /** Distribution-owned application release; observability only, never a protocol/capability gate. */
94
+ localAgentRelease: LocalAgentReleaseIdentity;
86
95
  productName: string;
87
96
  productId: string;
88
97
  serverUrl: string;
89
98
  deviceName?: string;
90
99
  workspaceRoot: string;
91
- /** Disabled by default. Enables local-only Git checkpoint repositories. */
100
+ /**
101
+ * Strict Agent execution boundary. The host selects one absolute branded
102
+ * storage root; the SDK alone composes `agents/<agentId>`, initializes the
103
+ * durable home, and binds it as runtime cwd. `projection` may write opaque,
104
+ * redacted host content into the canonical home supplied by the SDK.
105
+ */
106
+ agentHome?: {
107
+ hostStorageRoot: string;
108
+ projection?: AgentHomeProjection;
109
+ };
110
+ /**
111
+ * Explicit Agent-local/cloud egress selection. Omission still enforces the
112
+ * SDK metadata/status projection, but does not advertise or admit the new
113
+ * policy/reliable protocol surface.
114
+ */
115
+ agentEgress?: AgentEgressConfig;
116
+ /**
117
+ * Disabled by default. Enables local-only Git checkpoint repositories for
118
+ * legacy task workspaces. Mutually exclusive with `agentHome`: strict Agent
119
+ * execution has one canonical workspace authority.
120
+ */
92
121
  gitWorkspace?: GitWorkspaceConfig;
93
122
  /** Disabled by default. Enables the durable local task journal — see {@link HostedJournalConfig}. */
94
123
  hostedJournal?: HostedJournalConfig;
@@ -317,6 +346,45 @@ export interface DaemonConfig {
317
346
  */
318
347
  deviceAssertion?: DeviceAssertionConfig;
319
348
  }
349
+ export interface AgentEgressConfig {
350
+ /** Authenticated deployment tenant bound into every local reliable record. */
351
+ tenantId: string;
352
+ /** Exact policy the daemon is willing to consume from an Agent offer. */
353
+ policy: AgentEgressPolicy;
354
+ /** Named redaction hook for explicit contentful trajectory only. */
355
+ sanitizer?: AgentEgressSanitizer;
356
+ /**
357
+ * Device-local additions required to make one server-selected transfer
358
+ * policy executable. These values only supplement `policy.transfers`: a
359
+ * locally configured surface never enables a server-disabled transfer, and
360
+ * cannot widen its maxBytes or MIME authority.
361
+ */
362
+ contentRead?: AgentContentReadConfig;
363
+ }
364
+ /** Local root and text handling authority for one independently-gated surface. */
365
+ export interface AgentContentReadSurfaceConfig {
366
+ readonly root: AgentContentReadRoot;
367
+ readonly maxTextBytes: number;
368
+ readonly textMimeTypes: readonly string[];
369
+ readonly sensitiveNames?: readonly string[];
370
+ }
371
+ /**
372
+ * Host-local portions of the content-read contract. The audit ledger has no
373
+ * host path option: SDK composition fixes it per Agent home.
374
+ */
375
+ export interface AgentContentReadConfig {
376
+ readonly workspace?: AgentContentReadSurfaceConfig;
377
+ readonly transcript?: AgentContentReadSurfaceConfig;
378
+ readonly artifact?: AgentContentReadSurfaceConfig;
379
+ readonly runtimeAllowlistedRoots?: readonly string[];
380
+ }
381
+ export interface AgentReliableEgressInput {
382
+ agentRef: AgentRef;
383
+ sessionRef: string;
384
+ payload: unknown;
385
+ taskId?: string;
386
+ eventId?: string;
387
+ }
320
388
  /**
321
389
  * Plan `device-assertion-broker`. Two fields, both about what this daemon will
322
390
  * refuse.
@@ -366,6 +434,8 @@ export interface PresenceConfig {
366
434
  minimumIntervalMs?: number;
367
435
  }
368
436
  export interface DaemonStatus {
437
+ /** Process-immutable Local Agent application release captured at construction. */
438
+ localAgentRelease: Readonly<LocalAgentReleaseIdentity>;
369
439
  paired: boolean;
370
440
  connected: boolean;
371
441
  /** True once the connection has fallen back to long-poll (protocol §8) — transport info only (finding F6): long-poll is a full transport, so work still proceeds normally while this holds; outbound envelopes POST to /byok/messages instead of going out over WS. */
@@ -378,12 +448,22 @@ export interface DaemonStatus {
378
448
  branding?: DaemonBranding;
379
449
  /** Local lifecycle/retry budget, separate from transport fallback state. */
380
450
  operationalHealth: OperationalHealthSnapshot;
451
+ /** Redacted, content-addressed device-local MCP registry status. */
452
+ toolsets: McpToolsetRegistryStatus;
453
+ /** Content-free egress lane watermarks and typed last-drop facts. */
454
+ egress: AgentEgressStatus;
381
455
  }
382
456
  export interface Daemon {
383
457
  pair(pairingCode: string): Promise<DeviceRecord>;
384
458
  start(): Promise<void>;
385
459
  stop(): Promise<void>;
386
460
  status(): DaemonStatus;
461
+ /** Append one sanitized reliable record before its first transport attempt. */
462
+ publishReliableAgentEgress?(input: AgentReliableEgressInput): Promise<AgentEgressReliableAppendResult>;
463
+ /** Atomically replace the local registry when its current revision matches. */
464
+ reloadMcpToolsets(mcpToolsets: Record<string, McpToolsetConfig> | undefined, expectedRevision: string): McpToolsetReloadReceipt;
465
+ /** Record one explicit host-owned lifecycle observation for a configured toolset. */
466
+ reportMcpToolsetObservation(toolsetId: string, expectedDefinitionRevision: string, observation: McpToolsetObservation): void;
387
467
  /**
388
468
  * M3-2a: local observability — subscribe to live `DaemonEvent`s (task
389
469
  * feed, connection/pairing state changes, runtime-detection results) as
@@ -1,5 +1,33 @@
1
1
  import { type Envelope } from '@byok-sdk/protocol';
2
2
  import { AuthManager } from './auth-manager';
3
+ import { type TransportEndpoint } from './url';
4
+ /**
5
+ * A long-poll request failed in a way that today told the caller only
6
+ * `false`/"retry in 2s" — this names WHICH of the transport's two routes it
7
+ * was and what the server said, so a stuck fallback loop is diagnosable
8
+ * without a packet capture.
9
+ *
10
+ * Scope (review finding — honest attribution): this type represents ONLY an
11
+ * actual route request/response cycle failing. Anything that happens BEFORE
12
+ * the request exists — in practice credential acquisition
13
+ * (`AuthManager.getValidAccessToken`) — is not a route failure and is never
14
+ * reported as one; neither is {@link DeviceRevokedError}, which is a device
15
+ * lifecycle fact rather than something the route did. See
16
+ * `LongPollClient.loop`/`postBatch` for where that boundary is drawn.
17
+ *
18
+ * `status` is the HTTP status of the response the route produced, INCLUDING
19
+ * the case where the response arrived intact and its body then failed to read
20
+ * or parse (a 200 whose payload is malformed is still a 200 — the parse error
21
+ * rides in `cause`). `undefined` means no response was ever produced: the
22
+ * `fetch` itself rejected (DNS/TLS/connection failure, abort). The underlying
23
+ * error is kept in `cause` rather than flattened into the message, so nothing
24
+ * about the original failure is lost.
25
+ */
26
+ export declare class LongPollRouteError extends Error {
27
+ readonly endpoint: TransportEndpoint;
28
+ readonly status: number | undefined;
29
+ constructor(endpoint: TransportEndpoint, status: number | undefined, cause: unknown);
30
+ }
3
31
  export interface LongPollClientOptions {
4
32
  serverUrl: string;
5
33
  auth: AuthManager;
@@ -131,7 +159,39 @@ export declare class LongPollClient {
131
159
  * eviction bookkeeping for a case this unlikely.
132
160
  */
133
161
  private readonly warnedValidationFailureSeqs;
162
+ /**
163
+ * `path:status` keys this loop has already warned about — same one-warn-per-key
164
+ * discipline (and same rare-path soft-cap reset) as
165
+ * {@link warnedValidationFailureSeqs}, and for the same reason: an
166
+ * unreachable or misconfigured route fails again every `retryDelayMs` (2s
167
+ * by default) for as long as the fallback is engaged, so an unguarded warn
168
+ * would bury every other line in the log within a minute. Keyed by route
169
+ * AND status so a route that starts failing differently (503 -> 401) still
170
+ * warns once for the new condition.
171
+ */
172
+ private readonly warnedRouteFailures;
173
+ /**
174
+ * Both routes this transport can fail against, built once (see
175
+ * {@link describeEndpoint} for why constructing them in one place is what
176
+ * keeps credentials out of every diagnostic derived from them).
177
+ */
178
+ private readonly eventsEndpoint;
179
+ private readonly messagesEndpoint;
134
180
  constructor(opts: LongPollClientOptions);
181
+ /**
182
+ * One warn per `path:status`, carrying the typed {@link LongPollRouteError}
183
+ * as the second argument so a caller inspecting the log (or a test) reads
184
+ * the route off the error rather than re-parsing the message.
185
+ */
186
+ private warnRouteFailure;
187
+ /**
188
+ * {@link DeviceRevokedError} is a device lifecycle fact, not a route
189
+ * failure: it stops this loop outright (retrying cannot help) and is
190
+ * deliberately reported through `onRevoked` ONLY — never additionally as a
191
+ * {@link LongPollRouteError}. Returns whether the error was that case, so
192
+ * each call site can skip its route-failure warn for it.
193
+ */
194
+ private noteRevoked;
135
195
  start(): void;
136
196
  stop(): void;
137
197
  /**
@@ -24,7 +24,7 @@
24
24
  * publisher permanently — there is no recourse but a fresh `pair()`, so
25
25
  * retrying would be a pure spin.
26
26
  */
27
- import { type ToolsetId } from '@byok-sdk/protocol';
27
+ import { type RuntimeInfo, type ToolsetId } from '@byok-sdk/protocol';
28
28
  import type { AuthManager } from './auth-manager';
29
29
  /**
30
30
  * Client-side defaults, chosen against the hosted defaults
@@ -57,8 +57,14 @@ export declare function assertPresenceHeartbeatCadence(cadence: {
57
57
  export interface PresencePublisherOptions {
58
58
  serverUrl: string;
59
59
  auth: AuthManager;
60
- /** Sorted logical IDs only. Executable MCP definitions and credentials remain device-local. */
61
- configuredToolsets?: readonly ToolsetId[];
60
+ /** Reads current sorted logical IDs only; executable definitions and credentials remain device-local. */
61
+ getConfiguredToolsets?: () => readonly ToolsetId[];
62
+ /** U4a Local Agent release version; never inferred from the host package. */
63
+ clientVersion?: string;
64
+ /** The same runtime/auth snapshot sent in `conn.hello`. */
65
+ runtimes?: readonly RuntimeInfo[];
66
+ /** The same protocol-version snapshot sent in `conn.hello`. */
67
+ protocolVersions?: readonly number[];
62
68
  /** Heartbeat cadence. Must sit strictly between {@link PresencePublisherOptions.minimumIntervalMs} and {@link PresencePublisherOptions.ttlMs}. */
63
69
  intervalMs?: number;
64
70
  /** The deployment's presence hint TTL, as this daemon understands it. Only used to validate the cadence. */
@@ -1,13 +1,17 @@
1
- import { type Envelope, type PermissionPolicy, type RuntimeId, type TaskOfferPayload, type TaskOfferWithToolsetsPayload } from '@byok-sdk/protocol';
1
+ import { type AgentEgressPolicy, type Envelope, type PermissionPolicy, type RuntimeId, type TaskOfferPayload, type TaskOfferForAgentPayload, type TaskOfferForAgentWithEgressPayload, type TaskOfferWithToolsetsPayload } from '@byok-sdk/protocol';
2
2
  import { type McpToolsetConfig, type RuntimeAdapter } from '../types';
3
+ import { AgentHomeManager, type AgentRef } from '../agent-home';
4
+ import { AgentSessionHandoffStore, type AgentTerminalCause } from './agent-session-handoff-store';
3
5
  import { type RuntimeDisposalStage } from '../runtime-failure';
4
6
  import { type ApprovalDecision, type ApprovalOrigin, type ApprovalRegistry } from './approvals';
5
7
  import type { BlobResolver } from './blob-client';
6
8
  import type { TaskQueueWatermark } from './control-protocol';
9
+ import type { LocalAgentReleaseIdentity } from '../release-identity';
7
10
  import { type ProgressBatcherOptions } from './progress-batcher';
8
11
  import type { SessionWorkspaceStore } from './session-workspace-store';
9
12
  import type { GitWorkspaceManager, GitWorkspaceObservation } from './git-workspace';
10
13
  import type { GitWorkspaceStore, GitWorkspacePhase } from './git-workspace-store';
14
+ import type { AgentEgressController } from './agent-egress-controller';
11
15
  /**
12
16
  * M4 Phase 3: default wait for `requestApproval` (see its own doc comment)
13
17
  * before force-resolving an unanswered out-of-band approval as a fail-closed
@@ -40,6 +44,8 @@ export declare const DEFAULT_APPROVAL_TIMEOUT_MS: number;
40
44
  * `DaemonOverrides.shutdown.taskInterruptTimeoutMs` — see `create-daemon.ts`).
41
45
  */
42
46
  export declare const DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS = 5000;
47
+ /** Bounded retry before terminal publication degrades observably. */
48
+ export declare const AGENT_TERMINAL_EVIDENCE_MAX_ATTEMPTS = 3;
43
49
  /**
44
50
  * M4 Phase 4 (fold-in from the P3 gate): bound on how many `requestApproval`
45
51
  * calls may sit QUEUED (not yet dispatched — see that method's own doc
@@ -183,10 +189,18 @@ export interface TaskRunnerDeps {
183
189
  runtimeEnvironment?: Record<string, {
184
190
  allow?: string[];
185
191
  }>;
186
- /** Validated, device-local registry keyed by wire-level logical toolset id. */
187
- mcpToolsets?: ReadonlyMap<string, McpToolsetConfig>;
192
+ /** Reads the daemon's current validated device-local registry once per offer. */
193
+ getMcpToolsets?: () => ReadonlyMap<string, McpToolsetConfig>;
188
194
  permissionDefaults?: PermissionPolicy;
189
195
  workspaceRoot: string;
196
+ /** Strict Agent offer authority. Absent means legacy offers never resolve an Agent home. */
197
+ agentHome?: AgentHomeManager;
198
+ /** Exact host-selected policy accepted by `task.offer_for_agent_with_egress`. */
199
+ agentEgressPolicy?: Readonly<AgentEgressPolicy>;
200
+ /** Always-present projection/sanitizer consumer; it defaults to metadata-only. */
201
+ agentEgress?: AgentEgressController;
202
+ /** Durable exact-match Agent session handoff authority. */
203
+ agentSessionHandoffs?: AgentSessionHandoffStore;
190
204
  deviceId: string;
191
205
  send: (envelope: Envelope) => void;
192
206
  blobClient: BlobResolver;
@@ -215,6 +229,22 @@ export interface TaskRunnerDeps {
215
229
  stage: RuntimeDisposalStage;
216
230
  reason: string;
217
231
  }) => void;
232
+ /**
233
+ * Local audit signal emitted only after bounded Agent-home terminal
234
+ * evidence retries are exhausted. The wire terminal still proceeds so a
235
+ * cloud task cannot remain Claimed/Running forever behind auxiliary local
236
+ * storage failure.
237
+ */
238
+ onAgentTerminalEvidenceFailure?: (event: {
239
+ taskId: string;
240
+ agentRef: AgentRef;
241
+ runtimeId: string;
242
+ cwd: string;
243
+ cause: AgentTerminalCause;
244
+ reason?: string;
245
+ attempts: number;
246
+ error: string;
247
+ }) => void;
218
248
  /**
219
249
  * M4 Phase 3: this daemon's control-socket identity + the shared registry
220
250
  * backing the control socket's own `approvals.list`/`approvals.resolve`
@@ -226,6 +256,14 @@ export interface TaskRunnerDeps {
226
256
  approvalRegistry: ApprovalRegistry;
227
257
  storeDir: string;
228
258
  productId: string;
259
+ /**
260
+ * The already-resolved, process-immutable U4a Local Agent release identity.
261
+ * `TaskRunner` only consumes this value; it never creates, normalizes, or
262
+ * revalidates a second version authority. It remains optional for direct
263
+ * internal harnesses and old embedders: absence omits terminal usage rather
264
+ * than fabricating a client version.
265
+ */
266
+ localAgentRelease?: Readonly<LocalAgentReleaseIdentity>;
229
267
  /** Default `requestApproval` timeout — see {@link DEFAULT_APPROVAL_TIMEOUT_MS}. */
230
268
  approvalTimeoutMs?: number;
231
269
  /**
@@ -328,7 +366,7 @@ export type AdmissionGuardDecision = {
328
366
  readonly reason: string;
329
367
  readonly retryable: boolean;
330
368
  };
331
- type AcceptedOfferPayload = TaskOfferPayload | TaskOfferWithToolsetsPayload;
369
+ type AcceptedOfferPayload = TaskOfferPayload | TaskOfferWithToolsetsPayload | TaskOfferForAgentPayload | TaskOfferForAgentWithEgressPayload;
332
370
  /**
333
371
  * Per-connection task orchestration: offer -> (decline | prepare -> seal ->
334
372
  * claim -> prepared operation -> started) -> seq-ordered progress batches -> complete/fail/
@@ -453,6 +491,12 @@ export declare class TaskRunner {
453
491
  private stoppingOffers;
454
492
  constructor(deps: TaskRunnerDeps);
455
493
  get activeTaskCount(): number;
494
+ /**
495
+ * Transport-boundary classification for the currently active task. Legacy
496
+ * tasks and plain Agent-home offers are deliberately false: the additive
497
+ * egress contract must never reclassify their existing wire semantics.
498
+ */
499
+ usesAgentEgress(taskId: string): boolean;
456
500
  /** M5 batch-3 (workstream 2): effective `maxTaskOutputBytes` cap for this daemon — see {@link DEFAULT_MAX_TASK_OUTPUT_BYTES}'s own doc comment. */
457
501
  private get maxTaskOutputBytes();
458
502
  /**
@@ -888,6 +932,27 @@ export declare class TaskRunner {
888
932
  /** Pre-claim, fail-closed rejection (protocol §3.2) — never claims first. */
889
933
  private decline;
890
934
  private fail;
935
+ /**
936
+ * Claimed Agent failures before ActiveTask registration still carry the
937
+ * exact AgentRef and normally have Agent-local, fsynced terminal evidence
938
+ * first. A bounded storage failure degrades observably but cannot strand
939
+ * the already-claimed cloud task forever; the exact terminal still goes on
940
+ * the wire and handleOffer's finally block releases the lease.
941
+ */
942
+ private failClaimedAgent;
943
+ /**
944
+ * Build the optional terminal observation from facts this running daemon
945
+ * actually has. No offered `dispatchSelection` is echoed here: it is a
946
+ * requested execution target, not an adapter-reported provider/model fact.
947
+ * The bundled adapter event contracts currently expose token observations
948
+ * (Codex and Claude) but no provider/model observation, so those keys stay
949
+ * absent. Pi exposes no native usage observation, so its terminal payload
950
+ * omits this optional block rather than fabricating a usage observation from
951
+ * independently known runtime, elapsed duration, or Local Agent version.
952
+ */
953
+ private terminalInferenceUsagePayload;
954
+ /** Exact Agent identity projection for claim/terminal wire payloads. */
955
+ private agentTerminalPayload;
891
956
  /**
892
957
  * additive-minor (`task.complete.document`): the whole daemon-side gate
893
958
  * between a configured {@link ResultDocumentExtractor} and the wire —
@@ -946,6 +1011,10 @@ export declare class TaskRunner {
946
1011
  private hasResultDocumentCapability;
947
1012
  private observeGit;
948
1013
  private updateGitPhaseBestEffort;
1014
+ /** Persist Agent terminal truth before wire when local storage is available. */
1015
+ private persistAgentTerminalEvidence;
1016
+ private retryAgentTerminalEvidence;
1017
+ private reportAgentTerminalEvidenceFailure;
949
1018
  private finish;
950
1019
  private reserveSemanticTerminal;
951
1020
  /** M3-B: bounded insert for `finishedTaskIds` — see its class-level doc comment and `MAX_TRACKED_TASK_IDS`. Evicts the oldest (first-inserted) entry once over cap, same idiom as `ConnectionHub.checkAndRecordDuplicate` (packages/server/src/hub.ts). */
@@ -0,0 +1,30 @@
1
+ import { type ToolsetId } from '@byok-sdk/protocol';
2
+ import type { McpToolsetConfig, McpToolsetObservation, McpToolsetRegistryStatus, McpToolsetReloadReceipt } from '../types';
3
+ export type McpToolsetConfigInput = Record<string, McpToolsetConfig> | undefined;
4
+ export interface McpToolsetRegistrySnapshot {
5
+ revision: string;
6
+ toolsets: ReadonlyMap<string, McpToolsetConfig>;
7
+ configuredToolsets: readonly ToolsetId[];
8
+ }
9
+ export declare class McpToolsetRevisionConflictError extends Error {
10
+ readonly expectedRevision: string;
11
+ readonly actualRevision: string;
12
+ constructor(expectedRevision: string, actualRevision: string);
13
+ }
14
+ export declare class McpToolsetDefinitionRevisionConflictError extends Error {
15
+ readonly toolsetId: string;
16
+ readonly expectedRevision: string;
17
+ readonly actualRevision: string;
18
+ constructor(toolsetId: string, expectedRevision: string, actualRevision: string);
19
+ }
20
+ /** Single mutable owner of immutable-at-a-time device-local toolset snapshots. */
21
+ export declare class McpToolsetRegistry {
22
+ private state;
23
+ private observations;
24
+ constructor(configured?: McpToolsetConfigInput);
25
+ snapshot(): McpToolsetRegistrySnapshot;
26
+ status(): McpToolsetRegistryStatus;
27
+ reload(configured: McpToolsetConfigInput, expectedRevision: string): McpToolsetReloadReceipt;
28
+ report(toolsetId: string, expectedDefinitionRevision: string, observation: McpToolsetObservation): void;
29
+ private statusRows;
30
+ }
@@ -2,6 +2,27 @@
2
2
  export declare function toHttpBase(serverUrl: string): string;
3
3
  /** Derive the `/byok/ws` WebSocket URL from a configured `serverUrl`. */
4
4
  export declare function toWsUrl(serverUrl: string): string;
5
+ /**
6
+ * Which route a transport diagnostic is about, in the only two fields that
7
+ * are safe to keep: the host (with port) and the path.
8
+ *
9
+ * Both are read off a parsed `URL` in {@link describeEndpoint}, so the
10
+ * redaction is STRUCTURAL rather than a scrub pass — userinfo, query and
11
+ * fragment are the only places a bearer token or a presigned signature ever
12
+ * travels in this SDK, and none of the three survive the projection onto
13
+ * these two fields. There is exactly one construction site, so a future
14
+ * diagnostic cannot accidentally reintroduce a credential-bearing component
15
+ * by formatting a raw URL of its own.
16
+ */
17
+ export interface TransportEndpoint {
18
+ readonly transport: 'ws' | 'long-poll';
19
+ /** `URL.host` — hostname plus port when non-default. Never userinfo. */
20
+ readonly host: string;
21
+ /** `URL.pathname` — no query, no fragment. */
22
+ readonly path: string;
23
+ }
24
+ /** The single construction site for {@link TransportEndpoint} — see that interface's own doc comment for why it is the only one. */
25
+ export declare function describeEndpoint(transport: TransportEndpoint['transport'], url: string | URL): TransportEndpoint;
5
26
  /**
6
27
  * M5: thrown by {@link assertServerUrlAllowed} — see that function's own doc
7
28
  * comment for the full allow/deny rule this names. Deliberately ONE error
@@ -1,9 +1,22 @@
1
1
  import { type CapabilityFlag, type Envelope, type RuntimeInfo, type ToolsetId } from '@byok-sdk/protocol';
2
+ import { type TransportEndpoint } from './url';
2
3
  export type ConnectionState = 'connecting' | 'open' | 'closed' | 'degraded' | 'revoked';
3
- /** The WS upgrade itself was rejected with a non-101 HTTP status (e.g. 401 for an expired/invalid bearer token). Surfaced via `onConnectOutcome` so `ConnectionManager` can force a reactive token renewal before the next attempt (protocol §6.2, "reactively on 401"). */
4
+ /**
5
+ * The WS upgrade itself was rejected with a non-101 HTTP status (e.g. 401 for
6
+ * an expired/invalid bearer token). Surfaced via `onConnectOutcome` so
7
+ * `ConnectionManager` can force a reactive token renewal before the next
8
+ * attempt (protocol §6.2, "reactively on 401").
9
+ *
10
+ * Carries the {@link TransportEndpoint} the rejected upgrade was aimed at, so
11
+ * a 401 in a log names WHICH server and path refused it instead of leaving
12
+ * the reader to guess between a stale `serverUrl` and a genuinely expired
13
+ * token. The endpoint's own construction is what keeps the bearer token out
14
+ * of this message — see {@link describeEndpoint}.
15
+ */
4
16
  export declare class WsUnexpectedStatusError extends Error {
5
17
  readonly status: number;
6
- constructor(status: number);
18
+ readonly endpoint: TransportEndpoint;
19
+ constructor(status: number, endpoint: TransportEndpoint);
7
20
  }
8
21
  export interface BackoffOptions {
9
22
  baseMs?: number;
@@ -23,10 +36,12 @@ export interface WsTransportOptions {
23
36
  deviceId: string;
24
37
  productId: string;
25
38
  capabilities: CapabilityFlag[];
39
+ /** U4a Local Agent release version; the sole client-version authority. */
40
+ clientVersion?: string;
26
41
  /** Detected runtimes, sent on every `conn.hello` (protocol §10 gap #4/#11). */
27
42
  runtimes?: RuntimeInfo[];
28
- /** Sorted logical IDs configured locally; no MCP executable definition crosses the wire. */
29
- configuredToolsets?: readonly ToolsetId[];
43
+ /** Reads current sorted logical IDs for every hello; no executable definition crosses the wire. */
44
+ getConfiguredToolsets?: () => readonly ToolsetId[];
30
45
  /** The redelivery cursor to send as `conn.hello.cursor` (protocol §9) — read fresh on every connect so a value learned mid-connection is used on the next reconnect. */
31
46
  getCursor?: () => number | undefined;
32
47
  onEnvelope: (envelope: Envelope) => void;
@@ -49,13 +64,25 @@ export interface WsTransportOptions {
49
64
  * and the causing error when the attempt failed before a socket even
50
65
  * opened (e.g. `getToken()` rejecting). Used by `ConnectionManager` to
51
66
  * count consecutive failures for the long-poll fallback (protocol §8).
67
+ *
68
+ * `endpoint` names the route this attempt was aimed at (computed once per
69
+ * `openSocket()`, so every outcome site reports the same one) — a
70
+ * consecutive-failure count in a log is only actionable once it says WHICH
71
+ * server kept refusing.
52
72
  */
53
- onConnectOutcome?: (acked: boolean, err?: unknown) => void;
73
+ onConnectOutcome?: (acked: boolean, err: unknown, endpoint: TransportEndpoint) => void;
54
74
  backoff?: BackoffOptions;
55
75
  liveness?: LivenessOptions;
56
76
  /** Deterministic automatic reconnect delay. Manual `connect({auto:false})` never reaches this scheduler. */
57
77
  reconnectDelayMs?: (attempt: number, baseDelayMs: number) => number;
58
78
  }
79
+ /**
80
+ * One canonical authenticated capability snapshot for both transports.
81
+ * Long-poll sends this envelope through `POST /byok/messages`; WS sends the
82
+ * same shape as its opening frame. Keeping construction here prevents one
83
+ * transport from silently omitting a newly added daemon capability.
84
+ */
85
+ export declare function createConnectionHelloEnvelope(opts: Pick<WsTransportOptions, 'deviceId' | 'productId' | 'capabilities' | 'clientVersion' | 'runtimes' | 'getConfiguredToolsets' | 'getCursor'>): Envelope;
59
86
  /**
60
87
  * The daemon's outbound-only WS connection: opens, sends `conn.hello`, waits
61
88
  * for `conn.ack`, and reconnects with capped exponential backoff + jitter on
package/dist/index.d.ts CHANGED
@@ -1,6 +1,13 @@
1
- export type { RuntimeAdapter, RuntimeAdapterDescriptor, RuntimeAdapterPrepareInput, RuntimeAdapterPrepareResult, RuntimeAdapterRejectedOperation, RuntimeAdapterPreparedOperation, PreparedRuntimeOperation, RuntimeOperationManifest, RuntimeOperationStartInput, RuntimeCapabilities, RuntimeDetectResult, Session, GitWorkspaceConfig, McpStdioServerConfig, McpToolsetConfig, } from './types';
1
+ export type { RuntimeAdapter, RuntimeAdapterDescriptor, RuntimeAdapterPrepareInput, RuntimeAdapterPrepareResult, RuntimeAdapterRejectedOperation, RuntimeAdapterPreparedOperation, PreparedRuntimeOperation, RuntimeOperationManifest, RuntimeOperationStartInput, RuntimeCapabilities, RuntimeDetectResult, Session, GitWorkspaceConfig, McpStdioServerConfig, McpToolsetConfig, McpToolsetLifecycleState, McpToolsetObservation, McpToolsetStatus, McpToolsetRegistryStatus, McpToolsetReloadReceipt, AgentEgressPolicy, } from './types';
2
+ export type { AgentRef } from './agent-home';
3
+ export { AgentHomeError, AgentRefValidationError, AgentHomeResolutionError, AgentHomeCollisionError, AgentHomeBusyError, AgentHomeLeaseCorruptError, AgentHomeLayout, AgentHomeLeaseManager, AgentHomeManager, createAgentHomeProjection, stableAgentHomeOwnerId, validateAgentRef, } from './agent-home';
4
+ export { AgentSessionHandoffStore, AgentSessionHandoffStoreError, AgentSessionHandoffCorruptError, AgentSessionHandoffMismatchError, } from './daemon/agent-session-handoff-store';
5
+ export type { AgentSessionHandoff, AgentSessionHandoffMatch, AgentTaskTerminalEvidence, AgentTaskTerminalMatch, AgentTerminalCause, } from './daemon/agent-session-handoff-store';
6
+ export type { AgentHomeResolution, AgentHomeProjection, AgentHomeProjectionInput, AgentHomeProjectionFunction, AgentHomeLease, AgentHomeBinding, } from './agent-home';
2
7
  export { PolicyUnsupportedError, SteerUnsupportedError, freezeRuntimeAdapterDescriptor, sealRuntimeOperationManifest } from './types';
3
8
  export type { RuntimeEnvironmentRequirements } from './daemon/environment';
9
+ export { resolveLocalAgentReleaseIdentity } from './release-identity';
10
+ export type { LocalAgentReleaseIdentity } from './release-identity';
4
11
  export { RuntimeExecutionFailure, RuntimeDisposalFailure, RUNTIME_ADAPTER_CONTRACT_VIOLATION_REASON, isRuntimeDisposalFailure, isRuntimeExecutionFailure, projectRuntimeBoundaryFailure, projectRuntimeExecutionFailure, } from './runtime-failure';
5
12
  export type { RuntimeExecutionFailureInput, RuntimeDisposalFailureInput, RuntimeDisposalStage, RuntimeFailureCategory, RuntimeFailurePhase, RuntimeFailureProjection, RuntimeRetryDisposition, } from './runtime-failure';
6
13
  export { GitWorkspaceManager, GitWorkspaceError, isGitWorkspaceConfig, prependGitWorkspaceGuidance } from './daemon/git-workspace';
@@ -8,7 +15,12 @@ export type { GitWorkspaceObservation, GitWorkspaceLease, GitWorkspaceOptions, G
8
15
  export { GitWorkspaceStore } from './daemon/git-workspace-store';
9
16
  export type { GitWorkspaceLedger, GitWorkspaceLedgerRecord, GitWorkspacePhase } from './daemon/git-workspace-store';
10
17
  export { createDaemon, createDaemonWithAdapters } from './daemon/create-daemon';
11
- export type { Daemon, DaemonConfig, DaemonStatus, DaemonOverrides, DaemonBranding, HostedJournalConfig, DeviceAssertionConfig } from './daemon/create-daemon';
18
+ export type { Daemon, DaemonConfig, DaemonStatus, DaemonOverrides, DaemonBranding, HostedJournalConfig, DeviceAssertionConfig, AgentEgressConfig, AgentContentReadConfig, AgentContentReadSurfaceConfig, AgentReliableEgressInput, } from './daemon/create-daemon';
19
+ export type { AgentEgressDropReceipt, AgentEgressLaneStatus, AgentEgressStatus, } from './daemon/agent-egress-policy';
20
+ export type { AgentEgressSanitizer, AgentEgressSanitizerContext } from './daemon/agent-egress-sanitizer';
21
+ export { AGENT_CONTENT_READ_CAPABILITIES, AGENT_CONTENT_READ_CAPABILITY_WORKSPACE, AGENT_CONTENT_READ_CAPABILITY_TRANSCRIPT, AGENT_CONTENT_READ_CAPABILITY_ARTIFACT, } from './daemon/agent-content-read';
22
+ export type { AgentContentReadSurface, AgentContentReadDecision, AgentContentReadReason, AgentContentReadRoot, AgentContentReadPolicy, AgentContentReadPolicySelection, AgentContentReadRequest, AgentContentReadResult, AgentContentReadAllowed, AgentContentReadDenied, AgentContentSessionIdentity, AgentContentAuditReceipt, } from './daemon/agent-content-read';
23
+ export { McpToolsetRevisionConflictError, McpToolsetDefinitionRevisionConflictError, } from './daemon/toolset-registry';
12
24
  export type { ProgressBatcherOptions } from './daemon/progress-batcher';
13
25
  /**
14
26
  * Plan `device-assertion-broker`: the ONLY control-socket capability this