@byok-sdk/client 0.6.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 (38) 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 +12892 -9473
  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/format.d.ts +3 -0
  17. package/dist/daemon/agent-content-audit-store.d.ts +35 -0
  18. package/dist/daemon/agent-content-read.d.ts +169 -0
  19. package/dist/daemon/agent-egress-controller.d.ts +76 -0
  20. package/dist/daemon/agent-egress-policy.d.ts +36 -0
  21. package/dist/daemon/agent-egress-sanitizer.d.ts +38 -0
  22. package/dist/daemon/agent-egress-spool.d.ts +116 -0
  23. package/dist/daemon/agent-session-handoff-store.d.ts +82 -0
  24. package/dist/daemon/blob-client.d.ts +6 -2
  25. package/dist/daemon/connection-manager.d.ts +2 -2
  26. package/dist/daemon/control-protocol.d.ts +9 -0
  27. package/dist/daemon/create-daemon.d.ts +78 -3
  28. package/dist/daemon/long-poll-transport.d.ts +60 -0
  29. package/dist/daemon/presence-publisher.d.ts +2 -2
  30. package/dist/daemon/task-runner.d.ts +53 -4
  31. package/dist/daemon/toolset-registry.d.ts +30 -0
  32. package/dist/daemon/url.d.ts +21 -0
  33. package/dist/daemon/ws-transport.d.ts +30 -5
  34. package/dist/index.d.ts +12 -2
  35. package/dist/index.js +8646 -5272
  36. package/dist/index.js.map +1 -1
  37. package/dist/types.d.ts +44 -0
  38. package/package.json +7 -5
@@ -0,0 +1,76 @@
1
+ import type { AgentEvent } from '@byok-sdk/protocol';
2
+ import type { AgentRef } from '../agent-home';
3
+ import { type AgentEgressDropReason, type AgentEgressDropReceipt, type AgentEgressPolicy, type AgentEgressStatus } from './agent-egress-policy';
4
+ import { type AgentReliableAck, type AgentContentReceiptWithoutReliableIdentity, type AgentReliableEgressRecord } from './agent-egress-spool';
5
+ import { type AgentEgressSanitizer } from './agent-egress-sanitizer';
6
+ export interface AgentEgressControllerOptions {
7
+ readonly policy: Readonly<AgentEgressPolicy>;
8
+ /** Authenticated tenant identity, never accepted from an egress event. */
9
+ readonly tenantId?: string;
10
+ readonly sanitizer?: AgentEgressSanitizer;
11
+ }
12
+ export interface AgentEgressProgressInput {
13
+ readonly agentRef?: AgentRef;
14
+ readonly taskId: string;
15
+ readonly events: readonly AgentEvent[];
16
+ readonly serverCapabilities: readonly string[];
17
+ }
18
+ export interface AgentEgressReliableInput {
19
+ readonly homeDir: string;
20
+ readonly agentRef: AgentRef;
21
+ readonly payload: unknown;
22
+ readonly sessionRef: string;
23
+ readonly taskId?: string;
24
+ readonly eventId?: string;
25
+ }
26
+ export interface AgentEgressContentReceiptInput {
27
+ readonly homeDir: string;
28
+ readonly agentRef: AgentRef;
29
+ /** The content-free payload before the spool supplies stable event/cursor identity. */
30
+ readonly payload: AgentContentReceiptWithoutReliableIdentity;
31
+ readonly taskId?: string;
32
+ }
33
+ export type AgentEgressReliableAppendResult = Readonly<{
34
+ ok: true;
35
+ record: AgentReliableEgressRecord;
36
+ }> | Readonly<{
37
+ ok: false;
38
+ reason: AgentEgressDropReason;
39
+ }>;
40
+ /**
41
+ * The one daemon-owned policy consumer. Reliable and latest-value retain
42
+ * distinct types and stores; retries are sends, and only exact acknowledgments
43
+ * retire durable records.
44
+ */
45
+ export declare class AgentEgressController {
46
+ private readonly options;
47
+ private readonly latest;
48
+ private readonly spools;
49
+ private readonly latestStatus;
50
+ private readonly reliableStatus;
51
+ private readonly drops;
52
+ constructor(options: AgentEgressControllerOptions);
53
+ get policy(): Readonly<AgentEgressPolicy>;
54
+ status(): AgentEgressStatus;
55
+ dropReceipts(): readonly AgentEgressDropReceipt[];
56
+ noteTransportDrop(reason: AgentEgressDropReason, agentRef?: AgentRef): void;
57
+ /** Project before TaskRunner builds a `task.progress` envelope. */
58
+ projectLatestValue(input: AgentEgressProgressInput): readonly AgentEvent[];
59
+ appendReliable(input: AgentEgressReliableInput): Promise<AgentEgressReliableAppendResult>;
60
+ /**
61
+ * Content decisions are reliable facts too. They never enter the generic
62
+ * egress payload authority: the spool persists their exact protocol payload
63
+ * with `wireType: agent.content.receipt` before any transport attempt.
64
+ */
65
+ appendContentReceipt(input: AgentEgressContentReceiptInput): Promise<AgentEgressReliableAppendResult>;
66
+ /** Retires only the record whose full Agent/tenant/revision/id/cursor tuple matches. */
67
+ acknowledge(ack: AgentReliableAck): Promise<boolean>;
68
+ /** Re-open every existing Agent-local spool before retrying stable records after restart. */
69
+ recover(agentsRoot: string): Promise<void>;
70
+ reliableRecords(): readonly AgentReliableEgressRecord[];
71
+ /** Missing ack capability holds records in their reliable lane; it never makes them lossy. */
72
+ retryableReliableRecords(serverCapabilities: readonly string[]): readonly AgentReliableEgressRecord[];
73
+ private spoolFor;
74
+ private tenantPendingBytes;
75
+ private noteDrop;
76
+ }
@@ -0,0 +1,36 @@
1
+ import { type AgentEgressDropReason, type AgentEgressPolicy, type AgentEvent } from '@byok-sdk/protocol';
2
+ export type { AgentEgressDropReason, AgentEgressPolicy } from '@byok-sdk/protocol';
3
+ export interface AgentEgressDropReceipt {
4
+ lane: 'latest-value' | 'reliable';
5
+ reason: AgentEgressDropReason;
6
+ agentId?: string;
7
+ tenantId?: string;
8
+ eventId?: string;
9
+ occurredAt: string;
10
+ }
11
+ export interface AgentEgressLaneStatus {
12
+ pendingEvents: number;
13
+ pendingBytes: number;
14
+ replaced: number;
15
+ dropped: number;
16
+ lastDropReason?: AgentEgressDropReason;
17
+ }
18
+ export interface AgentEgressStatus {
19
+ policyRevision: string;
20
+ latestValue: AgentEgressLaneStatus;
21
+ reliable: AgentEgressLaneStatus;
22
+ }
23
+ /** Safe policy selected only when the host has not opted into content. */
24
+ export declare const DEFAULT_AGENT_EGRESS_POLICY: Readonly<AgentEgressPolicy>;
25
+ export declare class AgentEgressPolicyError extends Error {
26
+ constructor(message: string);
27
+ }
28
+ /** Resolve/validate once at construction; unknown policy shapes never become an implicit default. */
29
+ export declare function resolveAgentEgressPolicy(policy: AgentEgressPolicy | undefined): Readonly<AgentEgressPolicy>;
30
+ /**
31
+ * Default activity projection. Every retained string is SDK-authored; no
32
+ * runtime trajectory, tool, prompt, environment, argv, path, or credential
33
+ * value survives this transformation.
34
+ */
35
+ export declare function metadataStatusEvent(event: AgentEvent): AgentEvent;
36
+ export declare function eventBytes(event: AgentEvent): number;
@@ -0,0 +1,38 @@
1
+ import { type Envelope } from '@byok-sdk/protocol';
2
+ import { type AgentEgressDropReason, type AgentEgressPolicy } from './agent-egress-policy';
3
+ export interface AgentEgressSanitizerContext {
4
+ readonly lane: 'latest-value' | 'reliable';
5
+ readonly policyRevision: string;
6
+ readonly envelopeType?: string;
7
+ readonly agentId?: string;
8
+ readonly tenantId?: string;
9
+ }
10
+ /**
11
+ * Optional named host redaction hook for an explicitly contentful policy.
12
+ * It receives the SDK-projected value, never a second raw wire
13
+ * representation. Throwing/refusing drops the event; callers never receive
14
+ * an original-payload fallback.
15
+ */
16
+ export type AgentEgressSanitizer = (value: unknown, context: AgentEgressSanitizerContext) => unknown;
17
+ export declare class AgentEgressSanitizationError extends Error {
18
+ readonly reason: AgentEgressDropReason;
19
+ constructor(message: string, reason?: AgentEgressDropReason);
20
+ }
21
+ export type SanitizedEnvelope = Readonly<{
22
+ ok: true;
23
+ envelope: Envelope;
24
+ }> | Readonly<{
25
+ ok: false;
26
+ reason: AgentEgressDropReason;
27
+ }>;
28
+ /**
29
+ * The one envelope-boundary sanitizer used before either transport sees an
30
+ * envelope. It parses the final value through the frozen protocol so a
31
+ * broken custom sanitizer also fails locally, before WS bytes or long-poll
32
+ * JSON can be created.
33
+ */
34
+ export declare function sanitizeEgressEnvelope(envelope: Envelope, policy: Readonly<AgentEgressPolicy>, sanitizer: AgentEgressSanitizer | undefined, context?: Omit<AgentEgressSanitizerContext, 'lane' | 'policyRevision' | 'envelopeType'> & {
35
+ lane?: 'latest-value' | 'reliable';
36
+ }): SanitizedEnvelope;
37
+ /** Sanitizes a reliable payload before it is hashed/appended, never after. */
38
+ export declare function sanitizeReliablePayload(payload: unknown, policy: Readonly<AgentEgressPolicy>, sanitizer: AgentEgressSanitizer | undefined, context?: Omit<AgentEgressSanitizerContext, 'lane' | 'policyRevision'>): unknown;
@@ -0,0 +1,116 @@
1
+ import { type AgentContentReceiptPayload } from '@byok-sdk/protocol';
2
+ import type { AgentRef } from '../agent-home';
3
+ import { type AgentEgressPolicy, type AgentEgressDropReason } from './agent-egress-policy';
4
+ export declare const AGENT_EGRESS_DIRECTORY: string;
5
+ export declare const AGENT_RELIABLE_SPOOL_FILENAME = "reliable-v1.jsonl";
6
+ /** A spool row's intended envelope type, never inferred from its payload. */
7
+ export declare const AGENT_RELIABLE_WIRE_TYPES: readonly ['agent.egress.reliable', 'agent.content.receipt'];
8
+ export type AgentReliableWireType = (typeof AGENT_RELIABLE_WIRE_TYPES)[number];
9
+ type OmitReliableIdentity<T> = T extends unknown ? Omit<T, 'eventId' | 'cursor'> : never;
10
+ export type AgentContentReceiptWithoutReliableIdentity = OmitReliableIdentity<AgentContentReceiptPayload>;
11
+ export interface AgentReliableEgressRecord {
12
+ readonly schema: 1;
13
+ readonly wireType: AgentReliableWireType;
14
+ readonly agentRef: AgentRef;
15
+ readonly tenantId: string;
16
+ readonly policyRevision: string;
17
+ readonly eventId: string;
18
+ readonly cursor: number;
19
+ readonly payload: unknown;
20
+ readonly payloadHash: string;
21
+ readonly byteCount: number;
22
+ readonly createdAt: string;
23
+ readonly sessionRef?: string;
24
+ readonly taskId?: string;
25
+ }
26
+ export interface AgentReliableAppendInput {
27
+ readonly agentRef: AgentRef;
28
+ readonly tenantId: string;
29
+ readonly policyRevision: string;
30
+ readonly payload: unknown;
31
+ readonly sessionRef?: string;
32
+ readonly taskId?: string;
33
+ readonly eventId?: string;
34
+ readonly createdAt?: string;
35
+ }
36
+ /**
37
+ * Content receipts use the existing durable spool but retain their own wire
38
+ * type and validated receipt payload. The request id is the stable event id:
39
+ * a retried local read cannot mint a competing receipt identity.
40
+ */
41
+ export interface AgentContentReceiptAppendInput {
42
+ readonly agentRef: AgentRef;
43
+ readonly tenantId: string;
44
+ readonly policyRevision: string;
45
+ readonly sessionRef: string;
46
+ readonly payload: AgentContentReceiptWithoutReliableIdentity;
47
+ readonly taskId?: string;
48
+ }
49
+ export interface AgentReliableAck {
50
+ readonly agentRef: AgentRef;
51
+ readonly tenantId: string;
52
+ readonly sessionRef: string;
53
+ readonly policyRevision: string;
54
+ readonly eventId: string;
55
+ readonly cursor: number;
56
+ }
57
+ export declare class AgentReliableSpoolError extends Error {
58
+ constructor(message: string);
59
+ }
60
+ export declare class AgentReliableQuotaError extends AgentReliableSpoolError {
61
+ readonly reason: Extract<AgentEgressDropReason, 'quota_exceeded' | 'backpressure'>;
62
+ constructor(reason: Extract<AgentEgressDropReason, 'quota_exceeded' | 'backpressure'>, message: string);
63
+ }
64
+ /**
65
+ * Per-Agent durable append-before-send log. It is deliberately separate from
66
+ * the inbound task journal: its only truth is outbound reliable egress
67
+ * pending/exact-ack state.
68
+ */
69
+ export declare class AgentReliableSpool {
70
+ readonly homeDir: string;
71
+ readonly spoolPath: string;
72
+ private readonly pending;
73
+ private nextCursor;
74
+ private logEntries;
75
+ private writeTail;
76
+ private constructor();
77
+ static open(homeDir: string): Promise<AgentReliableSpool>;
78
+ get pendingEvents(): number;
79
+ get pendingBytes(): number;
80
+ records(): readonly AgentReliableEgressRecord[];
81
+ append(input: AgentReliableAppendInput, policy: Readonly<AgentEgressPolicy>, tenantPendingBytes: number): Promise<AgentReliableEgressRecord>;
82
+ /**
83
+ * Append one complete, protocol-validated content receipt before its first
84
+ * send. `eventId` is fixed to `requestId`; the durable spool alone allocates
85
+ * the positive cursor, then validates the final payload before fsync.
86
+ */
87
+ appendContentReceipt(input: AgentContentReceiptAppendInput, policy: Readonly<AgentEgressPolicy>, tenantPendingBytes: number): Promise<AgentReliableEgressRecord>;
88
+ /** Exact matching ack is the only transition which retires a record. */
89
+ acknowledge(ack: AgentReliableAck): Promise<boolean>;
90
+ private load;
91
+ private appendEntry;
92
+ private compact;
93
+ private exclusive;
94
+ }
95
+ export interface LatestValueRecord {
96
+ readonly agentRef: AgentRef;
97
+ readonly tenantId: string;
98
+ readonly event: import('@byok-sdk/protocol').AgentEvent;
99
+ readonly byteCount: number;
100
+ readonly updatedAt: string;
101
+ }
102
+ /** In-memory latest-value state; it is never replayed as durable history. */
103
+ export declare class AgentLatestValueState {
104
+ private readonly recordsByAgent;
105
+ offer(record: Omit<LatestValueRecord, 'byteCount' | 'updatedAt'>, policy: Readonly<AgentEgressPolicy>): Readonly<{
106
+ accepted: true;
107
+ replaced: boolean;
108
+ record: LatestValueRecord;
109
+ }> | Readonly<{
110
+ accepted: false;
111
+ reason: Extract<AgentEgressDropReason, 'quota_exceeded' | 'backpressure'>;
112
+ }>;
113
+ get pendingEvents(): number;
114
+ get pendingBytes(): number;
115
+ }
116
+ export {};
@@ -0,0 +1,82 @@
1
+ import { type AgentRef } from '../agent-home';
2
+ export type AgentTerminalCause = 'complete' | 'failed' | 'cancelled';
3
+ export interface AgentSessionHandoff {
4
+ readonly agentRef: AgentRef;
5
+ /** Task that created the native runtime session. */
6
+ readonly taskId: string;
7
+ readonly sessionRef: string;
8
+ readonly runtimeId: string;
9
+ /** Canonical Agent home and runtime cwd; these are intentionally one value. */
10
+ readonly cwd: string;
11
+ readonly leaseId: string;
12
+ readonly terminalCause?: AgentTerminalCause;
13
+ readonly terminalReason?: string;
14
+ readonly updatedAt: string;
15
+ }
16
+ export interface AgentSessionHandoffMatch {
17
+ readonly agentRef: AgentRef;
18
+ readonly sessionRef: string;
19
+ readonly runtimeId: string;
20
+ readonly cwd: string;
21
+ }
22
+ export interface AgentTaskTerminalEvidence {
23
+ readonly agentRef: AgentRef;
24
+ readonly taskId: string;
25
+ readonly runtimeId: string;
26
+ /** Canonical Agent home and sealed runtime cwd. */
27
+ readonly cwd: string;
28
+ readonly leaseId: string;
29
+ /** Present when adapter start succeeded but handoff persistence failed. */
30
+ readonly sessionRef?: string;
31
+ readonly terminalCause: 'failed';
32
+ readonly terminalReason: string;
33
+ readonly updatedAt: string;
34
+ }
35
+ export interface AgentTaskTerminalMatch {
36
+ readonly agentRef: AgentRef;
37
+ readonly taskId: string;
38
+ readonly runtimeId: string;
39
+ readonly cwd: string;
40
+ }
41
+ export declare class AgentSessionHandoffStoreError extends Error {
42
+ constructor(message: string);
43
+ }
44
+ export declare class AgentSessionHandoffCorruptError extends AgentSessionHandoffStoreError {
45
+ constructor(message: string);
46
+ }
47
+ export declare class AgentSessionHandoffMismatchError extends AgentSessionHandoffStoreError {
48
+ constructor(message: string);
49
+ }
50
+ /**
51
+ * Durable, fail-closed session evidence stored inside the canonical Agent
52
+ * home. Each session gets one hash-addressed append-only JSONL ledger under
53
+ * `.byok/runtime-sessions/`; session text never becomes a pathname. Unlike
54
+ * the legacy SessionWorkspaceStore, corrupt bytes are never interpreted as a
55
+ * missing mapping.
56
+ */
57
+ export declare class AgentSessionHandoffStore {
58
+ private readonly queues;
59
+ get(expected: AgentSessionHandoffMatch): Promise<AgentSessionHandoff | undefined>;
60
+ /** Append-only readback for audit/recovery; every historical terminal remains visible. */
61
+ history(expected: AgentSessionHandoffMatch): Promise<readonly AgentSessionHandoff[]>;
62
+ /** Exact identity check used before a strict Agent resume is admitted. */
63
+ requireMatch(expected: AgentSessionHandoffMatch): Promise<AgentSessionHandoff>;
64
+ /** Append-only, fsynced write. The caller awaits this before task.started. */
65
+ record(input: Omit<AgentSessionHandoff, 'updatedAt' | 'terminalCause' | 'terminalReason'>): Promise<AgentSessionHandoff>;
66
+ /** Records the first terminal cause without changing the exact handoff identity. */
67
+ recordTerminal(expected: AgentSessionHandoffMatch, cause: AgentTerminalCause, reason?: string): Promise<AgentSessionHandoff>;
68
+ /**
69
+ * Persists a claimed Agent task failure that happened before an active
70
+ * session handoff existed. Callers await the fsync before sending
71
+ * `task.fail`, so cloud state can never outrun the Agent-local evidence.
72
+ */
73
+ recordTaskTerminal(input: Omit<AgentTaskTerminalEvidence, 'updatedAt' | 'terminalCause'>): Promise<AgentTaskTerminalEvidence>;
74
+ getTaskTerminal(expectedInput: AgentTaskTerminalMatch): Promise<AgentTaskTerminalEvidence | undefined>;
75
+ private filePath;
76
+ private taskTerminalFilePath;
77
+ private enqueue;
78
+ private load;
79
+ private loadAll;
80
+ private loadTaskTerminal;
81
+ private append;
82
+ }
@@ -3,7 +3,9 @@ import type { AuthManager } from './auth-manager';
3
3
  /** Seam `TaskRunner` depends on, so tests can substitute a fake without spinning up real HTTP endpoints. */
4
4
  export interface BlobResolver {
5
5
  resolveInstruction(blobRef: BlobRef): Promise<string>;
6
- uploadArtifact(content: string | Uint8Array, contentType: string): Promise<BlobRef>;
6
+ uploadArtifact(content: string | Uint8Array, contentType: string, options?: {
7
+ readonly idempotencyKey?: string;
8
+ }): Promise<BlobRef>;
7
9
  }
8
10
  /**
9
11
  * HTTP-side blob transfer (protocol §7): resolving an instruction `blobRef`
@@ -18,5 +20,7 @@ export declare class BlobClient implements BlobResolver {
18
20
  /** `blobRef` -> `GET /byok/blobs/:id/url` -> fetch the presigned download URL -> text content. Always resolves fresh rather than trusting any inlined `BlobRef.url`, per docs/protocol.md §7. */
19
21
  resolveInstruction(blobRef: BlobRef): Promise<string>;
20
22
  /** `POST /byok/blobs` (declares size/contentType/contentHash) -> PUT the bytes to the presigned upload URL -> a `BlobRef` for `task.artifact.blobRef`. */
21
- uploadArtifact(content: string | Uint8Array, contentType: string): Promise<BlobRef>;
23
+ uploadArtifact(content: string | Uint8Array, contentType: string, options?: {
24
+ readonly idempotencyKey?: string;
25
+ }): Promise<BlobRef>;
22
26
  }
@@ -12,8 +12,8 @@ export interface ConnectionManagerOptions {
12
12
  /** U4a Local Agent release version; passed unchanged to both transports. */
13
13
  clientVersion?: string;
14
14
  runtimes: RuntimeInfo[];
15
- /** Sorted logical IDs from the validated local registry; executable definitions stay local. */
16
- configuredToolsets?: readonly ToolsetId[];
15
+ /** Reads current sorted logical IDs from the validated local registry for every WS hello. */
16
+ getConfiguredToolsets?: () => readonly ToolsetId[];
17
17
  auth: AuthManager;
18
18
  cursorStore: CursorStore;
19
19
  /**
@@ -4,6 +4,7 @@ import type { StorageCategory } from './journal/journal';
4
4
  import type { StoragePressureState } from './journal/storage-policy';
5
5
  import type { OperationalHealthSnapshot } from './operational-health';
6
6
  import type { LocalAgentReleaseIdentity } from '../release-identity';
7
+ import type { McpToolsetConfig, McpToolsetRegistryStatus } from '../types';
7
8
  /**
8
9
  * M4 Phase 2: shared local-IPC contract between the daemon's control server
9
10
  * (`control-server.ts`) and the CLI's control client (`bin/control-client.ts`)
@@ -273,7 +274,15 @@ export interface ControlStatusResult {
273
274
  storage?: ControlStorageStatus;
274
275
  /** Local lifecycle/retry budget. This is not the transport state above. */
275
276
  operationalHealth: OperationalHealthSnapshot;
277
+ /** Redacted content-addressed status from the daemon's single local registry. */
278
+ toolsets: McpToolsetRegistryStatus;
276
279
  }
280
+ export interface ToolsetsReloadParams {
281
+ expectedRevision: string;
282
+ mcpToolsets: Record<string, McpToolsetConfig>;
283
+ }
284
+ /** Shape-only parser; executable definition validation remains registry-owned. */
285
+ export declare function parseToolsetsReloadParams(value: unknown): ToolsetsReloadParams | undefined;
277
286
  export interface ApprovalsListResult {
278
287
  approvals: PendingApproval[];
279
288
  }
@@ -1,6 +1,8 @@
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';
4
6
  import { type LocalAgentReleaseIdentity } from '../release-identity';
5
7
  import type { BackoffOptions, LivenessOptions } from './ws-transport';
6
8
  import { type OperationalHealthSnapshot } from './operational-health';
@@ -13,6 +15,10 @@ import { type JournalOpenFaultSeam } from './journal/sqlite-support';
13
15
  import { LocalStoragePressureEngine, type LocalStoragePolicyInput } from './journal/storage-policy';
14
16
  import { type ResultDocumentExtractor } from './task-runner';
15
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';
16
22
  /**
17
23
  * Optional white-label product display info — purely opaque passthrough
18
24
  * (never interpreted, validated, or rendered by the daemon itself). Carried
@@ -91,7 +97,27 @@ export interface DaemonConfig {
91
97
  serverUrl: string;
92
98
  deviceName?: string;
93
99
  workspaceRoot: string;
94
- /** 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
+ */
95
121
  gitWorkspace?: GitWorkspaceConfig;
96
122
  /** Disabled by default. Enables the durable local task journal — see {@link HostedJournalConfig}. */
97
123
  hostedJournal?: HostedJournalConfig;
@@ -320,6 +346,45 @@ export interface DaemonConfig {
320
346
  */
321
347
  deviceAssertion?: DeviceAssertionConfig;
322
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
+ }
323
388
  /**
324
389
  * Plan `device-assertion-broker`. Two fields, both about what this daemon will
325
390
  * refuse.
@@ -383,12 +448,22 @@ export interface DaemonStatus {
383
448
  branding?: DaemonBranding;
384
449
  /** Local lifecycle/retry budget, separate from transport fallback state. */
385
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;
386
455
  }
387
456
  export interface Daemon {
388
457
  pair(pairingCode: string): Promise<DeviceRecord>;
389
458
  start(): Promise<void>;
390
459
  stop(): Promise<void>;
391
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;
392
467
  /**
393
468
  * M3-2a: local observability — subscribe to live `DaemonEvent`s (task
394
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
  /**
@@ -57,8 +57,8 @@ 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
62
  /** U4a Local Agent release version; never inferred from the host package. */
63
63
  clientVersion?: string;
64
64
  /** The same runtime/auth snapshot sent in `conn.hello`. */