@byok-sdk/client 0.8.0 → 0.9.0-rc.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 (39) hide show
  1. package/README.md +22 -1
  2. package/dist/adapters/index.js +14 -1
  3. package/dist/adapters/index.js.map +1 -1
  4. package/dist/agent-home.d.ts +29 -4
  5. package/dist/bin/agent-memory-mcp-server.d.ts +38 -0
  6. package/dist/bin/agent-message-mcp-server.d.ts +24 -0
  7. package/dist/bin/byok-agent-memory-mcp.d.ts +2 -0
  8. package/dist/bin/byok-agent-memory-mcp.js +432 -0
  9. package/dist/bin/byok-agent-memory-mcp.js.map +1 -0
  10. package/dist/bin/byok-agent-message-mcp.d.ts +2 -0
  11. package/dist/bin/byok-agent-message-mcp.js +441 -0
  12. package/dist/bin/byok-agent-message-mcp.js.map +1 -0
  13. package/dist/bin/byok-agent.js +7808 -5492
  14. package/dist/bin/byok-agent.js.map +1 -1
  15. package/dist/bin/byok-approval-mcp.js.map +1 -1
  16. package/dist/bin/commands/pair.d.ts +3 -0
  17. package/dist/daemon/agent-memory-filesystem.d.ts +23 -0
  18. package/dist/daemon/agent-memory-fs-helper.d.ts +10 -0
  19. package/dist/daemon/agent-memory.d.ts +160 -0
  20. package/dist/daemon/agent-message-outbox.d.ts +58 -0
  21. package/dist/daemon/auth-manager.d.ts +8 -0
  22. package/dist/daemon/control-protocol.d.ts +36 -0
  23. package/dist/daemon/create-daemon.d.ts +29 -2
  24. package/dist/daemon/daemon-owner.d.ts +9 -0
  25. package/dist/daemon/device-credential-store.d.ts +58 -0
  26. package/dist/daemon/device-proof-signer.d.ts +5 -4
  27. package/dist/daemon/memory-guidance.d.ts +8 -0
  28. package/dist/daemon/path-mutation-gate.d.ts +25 -0
  29. package/dist/daemon/resolve-agent-memory-mcp-bin.d.ts +6 -0
  30. package/dist/daemon/resolve-agent-message-mcp-bin.d.ts +6 -0
  31. package/dist/daemon/store.d.ts +45 -24
  32. package/dist/daemon/task-runner.d.ts +88 -3
  33. package/dist/daemon/toolset-registry.d.ts +2 -0
  34. package/dist/index.d.ts +7 -4
  35. package/dist/index.js +9976 -7469
  36. package/dist/index.js.map +1 -1
  37. package/dist/local-state-relocation.d.ts +24 -0
  38. package/dist/types.d.ts +2 -0
  39. package/package.json +7 -5
@@ -0,0 +1,58 @@
1
+ import { type AgentMessageContentType, type AgentMessageEgressRequirement, type AgentMessagePublishPayload } from '@byok-sdk/protocol';
2
+ import type { AgentRef } from '../agent-home';
3
+ export declare const AGENT_MESSAGE_DIRECTORY: string;
4
+ export declare const AGENT_MESSAGE_OUTBOX_FILENAME = "outbox-v1.jsonl";
5
+ export interface AgentMessageOutboxRecord {
6
+ readonly schema: 1;
7
+ readonly taskId: string;
8
+ readonly tenantId: string;
9
+ readonly agentRef: AgentRef;
10
+ readonly contract: string;
11
+ readonly messageId: string;
12
+ readonly cursor: number;
13
+ readonly contentType: AgentMessageContentType;
14
+ readonly body: string;
15
+ readonly contentHash: string;
16
+ readonly byteCount: number;
17
+ readonly createdAt: string;
18
+ readonly sessionRef?: string;
19
+ }
20
+ export declare class AgentMessageOutboxError extends Error {
21
+ constructor(message: string);
22
+ }
23
+ /** Agent-local, append-before-send outbox. Only exact accepted disposition retires bytes. */
24
+ export declare class AgentMessageOutbox {
25
+ readonly homeDir: string;
26
+ readonly outboxPath: string;
27
+ private readonly pendingByTask;
28
+ private readonly dispositionByTask;
29
+ private nextCursor;
30
+ private logEntries;
31
+ private writeTail;
32
+ private constructor();
33
+ static open(homeDir: string): Promise<AgentMessageOutbox>;
34
+ /** Re-open every existing Agent-local message outbox without following Agent-home symlinks. */
35
+ static recover(agentsRoot: string, tenantId: string): Promise<readonly AgentMessageOutbox[]>;
36
+ records(): readonly AgentMessageOutboxRecord[];
37
+ /** Activated records with no exact disposition yet; only these may be transport-replayed. */
38
+ retryableRecords(): readonly AgentMessageOutboxRecord[];
39
+ get(taskId: string): AgentMessageOutboxRecord | undefined;
40
+ appendDraft(input: {
41
+ readonly taskId: string;
42
+ readonly tenantId: string;
43
+ readonly agentRef: AgentRef;
44
+ readonly requirement: AgentMessageEgressRequirement;
45
+ readonly contentType: AgentMessageContentType;
46
+ readonly body: string;
47
+ readonly sessionRef?: string;
48
+ readonly maxPendingEvents: number;
49
+ readonly maxPendingBytes: number;
50
+ }): Promise<AgentMessageOutboxRecord>;
51
+ activate(taskId: string, sessionRef: string): Promise<AgentMessageOutboxRecord | undefined>;
52
+ publishPayload(record: AgentMessageOutboxRecord): AgentMessagePublishPayload;
53
+ applyDisposition(taskId: string, input: unknown): Promise<'accepted' | 'held' | 'refused' | 'mismatch' | 'unknown'>;
54
+ private load;
55
+ private appendEntry;
56
+ private compact;
57
+ private exclusive;
58
+ }
@@ -1,4 +1,5 @@
1
1
  import { DeviceStore, type DeviceRecord } from './store';
2
+ import type { DeviceCredentialStore, InMemoryDeviceCredentialStore } from './device-credential-store';
2
3
  /**
3
4
  * Thrown when the server has revoked this device: a 401 on `/byok/challenge`
4
5
  * or `/byok/token` (protocol §6.3). The only recourse is a fresh
@@ -11,6 +12,8 @@ export declare class DeviceRevokedError extends Error {
11
12
  export interface AuthManagerOptions {
12
13
  serverUrl: string;
13
14
  store: DeviceStore;
15
+ /** Internal-only credential custody seam. Product construction uses store.credentials. */
16
+ credentials?: DeviceCredentialStore | InMemoryDeviceCredentialStore;
14
17
  deviceName?: string;
15
18
  /** Called once revocation is detected, so a caller (ConnectionManager) can stop retrying and surface the state instead of looping. */
16
19
  onRevoked?: () => void;
@@ -31,9 +34,12 @@ export declare class AuthManager {
31
34
  private stopped;
32
35
  private pairing;
33
36
  private credentialMutationTail;
37
+ private readonly credentials;
34
38
  constructor(opts: AuthManagerOptions);
35
39
  get deviceId(): string | undefined;
36
40
  isRevoked(): boolean;
41
+ /** Internal signer read: always recompose metadata with the current OS secret authority. */
42
+ readCurrent(): Promise<DeviceRecord | undefined>;
37
43
  /** Load a previously-paired device record from disk, if any (idempotent — a second call is a no-op once loaded). */
38
44
  loadExisting(): Promise<DeviceRecord | undefined>;
39
45
  /** `POST /byok/pair` (v2): generates a device keypair on first pair, reuses it on any subsequent (e.g. post-revocation) re-pair. */
@@ -49,4 +55,6 @@ export declare class AuthManager {
49
55
  private markRevoked;
50
56
  private scheduleProactiveRenewal;
51
57
  private runCredentialMutation;
58
+ /** Read the current paired authority afresh; metadata without its OS secret is re-pair required. */
59
+ private loadRecord;
52
60
  }
@@ -281,6 +281,17 @@ export interface ToolsetsReloadParams {
281
281
  expectedRevision: string;
282
282
  mcpToolsets: Record<string, McpToolsetConfig>;
283
283
  }
284
+ /** Maximum opaque pairing-code payload accepted over local control IPC. */
285
+ export declare const ENROLLMENT_PAIRING_CODE_MAX_BYTES = 1024;
286
+ export interface EnrollmentPairParams {
287
+ pairingCode: string;
288
+ }
289
+ /**
290
+ * Exact shape gate for service-identity pairing. The code is opaque server
291
+ * authority: this only bounds transport bytes and rejects unknown fields; it
292
+ * never parses, normalizes or logs the code.
293
+ */
294
+ export declare function parseEnrollmentPairParams(value: unknown): EnrollmentPairParams | undefined;
284
295
  /** Shape-only parser; executable definition validation remains registry-owned. */
285
296
  export declare function parseToolsetsReloadParams(value: unknown): ToolsetsReloadParams | undefined;
286
297
  export interface ApprovalsListResult {
@@ -312,6 +323,31 @@ export interface ApprovalsRequestResult {
312
323
  approved: boolean;
313
324
  reason?: string;
314
325
  }
326
+ export interface AgentMessagePublishParams {
327
+ contextToken: string;
328
+ contentType: 'text/plain' | 'text/markdown';
329
+ body: string;
330
+ }
331
+ export declare function parseAgentMessagePublishParams(value: unknown): AgentMessagePublishParams | undefined;
332
+ export interface AgentMessagePublishResult {
333
+ messageId: string;
334
+ state: 'staged' | 'pending';
335
+ }
336
+ export interface AgentMemoryRecallParams {
337
+ contextToken: string;
338
+ path: string;
339
+ ifRevision?: string;
340
+ }
341
+ export interface AgentMemorySaveParams {
342
+ contextToken: string;
343
+ op: 'replace' | 'delete';
344
+ path: string;
345
+ expectedRevision: string;
346
+ content?: string;
347
+ }
348
+ /** Parser only validates the local IPC shape. Agent identity and memory root stay daemon-owned. */
349
+ export declare function parseAgentMemoryRecallParams(value: unknown): AgentMemoryRecallParams | undefined;
350
+ export declare function parseAgentMemorySaveParams(value: unknown): AgentMemorySaveParams | undefined;
315
351
  /**
316
352
  * Params for `assertion.issue`: a sibling local process (the host's own CLI,
317
353
  * installed alongside this daemon) asking the daemon to mint one short-lived,
@@ -9,7 +9,7 @@ import { type OperationalHealthSnapshot } from './operational-health';
9
9
  import { type DaemonEventListener, type DaemonTaskInfo, type Unsubscribe } from './observer';
10
10
  import { GitWorkspaceManager } from './git-workspace';
11
11
  import { GitWorkspaceStore } from './git-workspace-store';
12
- import { type DeviceRecord } from './store';
12
+ import { type DeviceEnrollment } from './store';
13
13
  import { type LocalTaskJournal } from './journal/journal';
14
14
  import { type JournalOpenFaultSeam } from './journal/sqlite-support';
15
15
  import { LocalStoragePressureEngine, type LocalStoragePolicyInput } from './journal/storage-policy';
@@ -18,6 +18,8 @@ import { type ProgressBatcherOptions } from './progress-batcher';
18
18
  import { type AgentEgressReliableAppendResult } from './agent-egress-controller';
19
19
  import { type AgentEgressStatus } from './agent-egress-policy';
20
20
  import { type AgentEgressSanitizer } from './agent-egress-sanitizer';
21
+ import { type AgentMemoryHostedProjection } from './agent-memory';
22
+ import type { AgentMemoryFilesystemHelperConfig } from './agent-memory-filesystem';
21
23
  import { type AgentContentReadRoot } from './agent-content-read';
22
24
  /**
23
25
  * Optional white-label product display info — purely opaque passthrough
@@ -100,6 +102,19 @@ export interface DaemonConfig {
100
102
  hostStorageRoot: string;
101
103
  projection?: AgentHomeProjection;
102
104
  };
105
+ /** Optional, one-way hosted projection. Without all guards it has zero network activity. */
106
+ agentMemory?: AgentMemoryHostedProjection;
107
+ /**
108
+ * Product-owned external secure-filesystem helper. The path must be absolute;
109
+ * the SDK never searches PATH or bundles a native addon. Required for Phase
110
+ * 2 on macOS. Windows remains fail-closed pending its native race proof.
111
+ */
112
+ agentMemoryFilesystem?: AgentMemoryFilesystemHelperConfig;
113
+ /**
114
+ * Refuse legacy task offers locally. This is an additive capability only
115
+ * after the SDK-owned Agent home has passed construction-time preflight.
116
+ */
117
+ strictAgentOnly?: boolean;
103
118
  /**
104
119
  * Explicit Agent-local/cloud egress selection. Omission still enforces the
105
120
  * SDK metadata/status projection, but does not advertise or admit the new
@@ -188,6 +203,17 @@ export interface DaemonConfig {
188
203
  */
189
204
  permissionDefaults?: PermissionPolicy;
190
205
  storeDir?: string;
206
+ /**
207
+ * Opt-in host composition for a daemon launched under a different OS
208
+ * principal than the interactive CLI (notably a WinSW service). When true,
209
+ * an unpaired daemon holds the normal writer lease and exposes only the
210
+ * existing HMAC-authenticated local control surface so `enrollment.pair`
211
+ * can persist the credential under the daemon's own OS token. Absent by
212
+ * default: ordinary foreground `start()` keeps rejecting an unpaired device.
213
+ */
214
+ serviceEnrollment?: {
215
+ readonly enabled: true;
216
+ };
191
217
  /** Optional white-label branding — see `DaemonBranding`. Carried through verbatim to `status().branding`. */
192
218
  branding?: DaemonBranding;
193
219
  /**
@@ -448,7 +474,8 @@ export interface DaemonStatus {
448
474
  egress: AgentEgressStatus;
449
475
  }
450
476
  export interface Daemon {
451
- pair(pairingCode: string): Promise<DeviceRecord>;
477
+ /** Pairing result is intentionally credential-blind. */
478
+ pair(pairingCode: string): Promise<DeviceEnrollment>;
452
479
  start(): Promise<void>;
453
480
  stop(): Promise<void>;
454
481
  status(): DaemonStatus;
@@ -52,6 +52,15 @@ export declare class DaemonOwnerActiveError extends Error {
52
52
  readonly role: OwnerRecord['role'] | 'unknown';
53
53
  constructor(role: OwnerRecord['role'] | 'unknown');
54
54
  }
55
+ /**
56
+ * Read-only relocation inspection. The caller must already hold this store's
57
+ * path-mutation gate, so a conforming writer cannot publish after this check.
58
+ * Any persisted owner or reclaim object is a refusal; relocation never
59
+ * reclaims crash residue or repairs SDK-private state.
60
+ *
61
+ * @internal Used only by the high-level local-state relocation coordinator.
62
+ */
63
+ export declare function assertDaemonStoreQuiescent(storeDir: string): Promise<void>;
55
64
  /**
56
65
  * Cross-process, fail-closed ownership for store mutations that must never
57
66
  * race a daemon. A separate exclusive reclaim file serializes stale-owner recovery so two
@@ -0,0 +1,58 @@
1
+ /** Secret fields inside the internal complete enrollment authority. */
2
+ export interface DeviceCredentials {
3
+ readonly accessToken: string;
4
+ readonly expiresAt: string;
5
+ readonly devicePrivateKeyPem: string;
6
+ }
7
+ /** Non-secret deterministic projection of the authenticated enrollment. */
8
+ export interface DeviceMetadata {
9
+ readonly deviceId: string;
10
+ readonly tenantId: string;
11
+ readonly devicePublicKey: string;
12
+ }
13
+ /**
14
+ * The single local enrollment authority. Keeping identity and credential
15
+ * bytes in one OS-managed entry prevents a crash from composing a token/key
16
+ * from one pairing response with metadata from another.
17
+ */
18
+ export type DeviceRecord = DeviceMetadata & DeviceCredentials;
19
+ export interface DeviceCommandResult {
20
+ readonly exitCode: number;
21
+ readonly stdout: string;
22
+ readonly stderr: string;
23
+ }
24
+ export type DeviceCommandRunner = (executable: string, args: readonly string[], stdin?: string) => Promise<DeviceCommandResult>;
25
+ /** Typed unavailability; callers must surface re-pair/operational failure, never write a file fallback. */
26
+ export declare class DeviceCredentialStoreUnavailableError extends Error {
27
+ constructor(message?: string);
28
+ }
29
+ export declare class DeviceCredentialStoreError extends Error {
30
+ constructor(message: string);
31
+ }
32
+ export interface DeviceCredentialStoreOptions {
33
+ readonly productId: string;
34
+ readonly platform?: NodeJS.Platform;
35
+ readonly commandRunner?: DeviceCommandRunner;
36
+ }
37
+ /**
38
+ * Internal OS-backed authority for the bearer token and device private key.
39
+ * The constructor deliberately accepts no path or backend selector: real
40
+ * callers get the platform provider; tests import this internal module and
41
+ * inject a double directly.
42
+ */
43
+ export declare class DeviceCredentialStore {
44
+ #private;
45
+ constructor(options: DeviceCredentialStoreOptions);
46
+ read(): Promise<DeviceRecord | undefined>;
47
+ replace(record: DeviceRecord): Promise<void>;
48
+ /** Returns true only after the sole secret authority is confirmed absent. */
49
+ clear(): Promise<boolean>;
50
+ }
51
+ /** Test-only double; it is intentionally internal and never selected by a production config. */
52
+ export declare class InMemoryDeviceCredentialStore {
53
+ #private;
54
+ read(): Promise<DeviceRecord | undefined>;
55
+ replace(record: DeviceRecord): Promise<void>;
56
+ clear(): Promise<boolean>;
57
+ }
58
+ export declare function runDeviceCommand(executable: string, args: readonly string[], stdin?: string): Promise<DeviceCommandResult>;
@@ -1,5 +1,5 @@
1
1
  import { type DeviceProofEnvelopeV1 } from '@byok-sdk/core';
2
- import type { DeviceStore } from './store';
2
+ import type { AuthManager } from './auth-manager';
3
3
  export interface DeviceProofRequest {
4
4
  readonly method: string;
5
5
  /** Exact origin-relative path, including the query string when present. */
@@ -17,7 +17,7 @@ export interface DeviceProofSigner {
17
17
  sign(request: DeviceProofRequest): Promise<DeviceProofEnvelopeV1>;
18
18
  }
19
19
  export interface StoredDeviceProofSignerOptions {
20
- readonly store: Pick<DeviceStore, 'load'>;
20
+ readonly auth: Pick<AuthManager, 'readCurrent'>;
21
21
  /** Explicit host configuration. Pairing/bearer state is never mined for tenant identity. */
22
22
  readonly tenantId: string;
23
23
  readonly productId: string;
@@ -28,8 +28,9 @@ export interface StoredDeviceProofSignerOptions {
28
28
  /**
29
29
  * Signs request-bound S6 proofs with the paired device identity key.
30
30
  *
31
- * The store is read for every signature rather than cached: clearing the paired
32
- * record immediately removes local signing authority. Canonicalization is
31
+ * The authenticated enrollment authority is read for every signature rather
32
+ * than cached: clearing the OS credential immediately removes local signing
33
+ * authority. Canonicalization is
33
34
  * imported from `@byok-sdk/core`, the one frozen byte authority; this module only
34
35
  * supplies the Node Ed25519 operation.
35
36
  */
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Runtime-neutral instructions for an Agent's model-authored local memory.
3
+ *
4
+ * This is deliberately prompt guidance only: the SDK does not read memory
5
+ * content, infer durable values, or auto-inject files into the operation.
6
+ */
7
+ export declare const AGENT_MEMORY_GUIDANCE: string;
8
+ export declare function prependAgentMemoryGuidance(instruction: string): string;
@@ -0,0 +1,25 @@
1
+ export type PathMutationGateScope = 'store' | 'agent-home-root';
2
+ export interface PathMutationGateInput {
3
+ readonly scope: PathMutationGateScope;
4
+ readonly targetPath: string;
5
+ }
6
+ export interface PathMutationGateAcquireOptions {
7
+ /** Bounded wait for another conforming short-lived writer; relocation uses zero. */
8
+ readonly waitMs?: number;
9
+ }
10
+ export interface PathMutationGate {
11
+ readonly scope: PathMutationGateScope;
12
+ readonly targetPath: string;
13
+ readonly identity: string;
14
+ release(): Promise<void>;
15
+ }
16
+ export declare class PathMutationGateBusyError extends Error {
17
+ readonly scope: PathMutationGateScope;
18
+ readonly targetPath: string;
19
+ constructor(scope: PathMutationGateScope, targetPath: string);
20
+ }
21
+ /** Canonicalize through the deepest existing ancestor without creating the target. */
22
+ export declare function resolvePathWithoutCreate(input: string): Promise<string>;
23
+ /** Acquire multiple path gates in one deterministic order to prevent deadlock. */
24
+ export declare function acquirePathMutationGates(inputs: readonly PathMutationGateInput[]): Promise<readonly PathMutationGate[]>;
25
+ export declare function acquirePathMutationGate(input: PathMutationGateInput, options?: PathMutationGateAcquireOptions): Promise<PathMutationGate>;
@@ -0,0 +1,6 @@
1
+ export interface ResolvedAgentMemoryMcpBin {
2
+ readonly command: string;
3
+ readonly args: readonly string[];
4
+ }
5
+ /** Resolve the SDK-owned stdio Agent-memory MCP helper shipped beside the client bundle. */
6
+ export declare function resolveAgentMemoryMcpBin(externalHelperConfigured?: boolean): ResolvedAgentMemoryMcpBin | undefined;
@@ -0,0 +1,6 @@
1
+ export interface ResolvedAgentMessageMcpBin {
2
+ readonly command: string;
3
+ readonly args: readonly string[];
4
+ }
5
+ /** Resolve the SDK-owned stdio MCP helper shipped beside the client bundle. */
6
+ export declare function resolveAgentMessageMcpBin(): ResolvedAgentMessageMcpBin;
@@ -1,17 +1,32 @@
1
1
  import { type EnsureSecureDirOptions } from '../util/secure-dir';
2
- export interface DeviceRecord {
3
- deviceId: string;
4
- /** Opaque tenant binding returned by the authenticated pairing response. */
5
- tenantId: string;
6
- /** Current access token (JWT), renewed via challenge/token without re-pairing (protocol §6.2). */
7
- accessToken: string;
8
- /** ISO-8601 expiry for `accessToken` (our best knowledge of it — see auth-manager.ts for how this is derived after `/byok/pair`, which reports no explicit expiry itself). */
9
- expiresAt: string;
10
- /** Ed25519 private key, PKCS8 PEM. Never leaves this device. Stored as a 0600 file; OS keychain integration is deferred (tracked as a future roadmap item, not promised for any specific milestone). */
11
- devicePrivateKeyPem: string;
12
- /** Ed25519 public key, base64url — re-sent verbatim on a post-revocation re-pair (protocol §6.3). */
13
- devicePublicKey: string;
2
+ import { DeviceCredentialStore, InMemoryDeviceCredentialStore, type DeviceMetadata } from './device-credential-store';
3
+ export type { DeviceMetadata, DeviceRecord } from './device-credential-store';
4
+ /**
5
+ * Non-secret projection of an authenticated device enrollment. This is the
6
+ * complete permitted `device.json` shape. The complete record, including
7
+ * these authenticated metadata fields and secret bytes, lives atomically in
8
+ * DeviceCredentialStore; this file is only its deterministic projection.
9
+ *
10
+ * Internal only: the package root exposes DeviceEnrollment/status, never this
11
+ * storage record.
12
+ */
13
+ /** Public credential-blind result of explicit pairing. */
14
+ export interface DeviceEnrollment {
15
+ readonly deviceId: string;
16
+ }
17
+ export interface DeviceEnrollmentStatusOptions {
18
+ productId: string;
19
+ storeDir?: string;
14
20
  }
21
+ /** Credential-blind cold read model for host setup and diagnostics. */
22
+ export type DeviceEnrollmentStatus = {
23
+ state: 'unpaired';
24
+ } | {
25
+ state: 'paired';
26
+ deviceId: string;
27
+ } | {
28
+ state: 're_pair_required';
29
+ };
15
30
  /**
16
31
  * A durable enrollment record cannot be used by any steady-state path. Only
17
32
  * the explicit pair operation may replace it with a fresh authenticated row.
@@ -20,17 +35,17 @@ export declare class DeviceRecordRePairRequiredError extends Error {
20
35
  constructor();
21
36
  }
22
37
  /**
23
- * Persists the device identity issued by `pair()` deviceId, current
24
- * access token + its expiry, and the device's own Ed25519 keypair. This is
25
- * the ONLY credential material the daemon itself ever holds — never a
26
- * runtime's own credentials (see the credential-isolation rule on
27
- * `RuntimeAdapter`). Stored 0600 under `storeDir` (default
28
- * `~/.byok/<productId>/`); OS keychain storage is deferred (tracked as a
29
- * future roadmap item, not promised for any specific milestone).
38
+ * Persists only bounded non-secret enrollment projection. The paired bearer
39
+ * token and private key are never accepted here and are owned by the internal
40
+ * OS DeviceCredentialStore.
30
41
  */
31
42
  export declare class DeviceStore {
32
43
  private readonly secureDirOptions?;
44
+ /** Process-local keyed doubles preserve restart semantics in isolated tests. */
45
+ private static readonly testCredentials;
33
46
  private readonly filePath;
47
+ /** Internal test seam. Product construction always supplies productId and gets an OS store. */
48
+ readonly credentials: DeviceCredentialStore | InMemoryDeviceCredentialStore;
34
49
  /**
35
50
  * `secureDirOptions` is a test-only DI seam (mirrors `EnsureSecureDirOptions`'s
36
51
  * own `run`/`platform` overrides) — every real caller omits it, getting
@@ -41,7 +56,7 @@ export declare class DeviceStore {
41
56
  * ACL-unprotected credential") is verifiable from a real `darwin`/`linux`
42
57
  * CI/dev machine, not just asserted.
43
58
  */
44
- constructor(storeDir: string, secureDirOptions?: EnsureSecureDirOptions | undefined);
59
+ constructor(storeDir: string, secureDirOptions?: EnsureSecureDirOptions | undefined, productId?: string);
45
60
  static defaultDir(productId: string): string;
46
61
  /**
47
62
  * Resolve the one store pathname every daemon/CLI component must share.
@@ -50,14 +65,20 @@ export declare class DeviceStore {
50
65
  * cwd to pin a quarantine directory inode.
51
66
  */
52
67
  static resolveDir(productId: string, configured?: string): string;
53
- load(): Promise<DeviceRecord | undefined>;
68
+ load(): Promise<DeviceMetadata | undefined>;
54
69
  /**
55
70
  * Read and remove the exact bounded, no-follow device record under the
56
71
  * caller's mutation lease. The hard-link guard keeps the inspected inode
57
72
  * identifiable until the synchronous pathname check and unlink complete.
58
73
  */
59
- remove(): Promise<DeviceRecord | undefined>;
60
- save(record: DeviceRecord): Promise<void>;
61
- clear(): Promise<void>;
74
+ remove(): Promise<DeviceMetadata | undefined>;
75
+ save(record: DeviceMetadata): Promise<void>;
62
76
  private openBounded;
63
77
  }
78
+ /**
79
+ * Read the canonical device store without projecting credential or tenant
80
+ * material. A legacy/tampered record remains distinct from an absent record so
81
+ * hosts can require explicit re-pair instead of silently changing semantics.
82
+ * Filesystem and pathname-safety failures intentionally remain errors.
83
+ */
84
+ export declare function readDeviceEnrollmentStatus(options: DeviceEnrollmentStatusOptions): Promise<DeviceEnrollmentStatus>;
@@ -1,4 +1,4 @@
1
- import { type AgentEgressPolicy, type Envelope, type PermissionPolicy, type RuntimeId, type TaskOfferPayload, type TaskOfferForAgentPayload, type TaskOfferForAgentWithEgressPayload, type TaskOfferForAgentWithEgressFreshPayload, type TaskOfferWithToolsetsPayload } from '@byok-sdk/protocol';
1
+ import { type AgentMessageContentType, type AgentEgressPolicy, type Envelope, type PermissionPolicy, type RuntimeId, type TerminalProjectionSelection, type TaskOfferPayload, type TaskOfferForAgentPayload, type TaskOfferForAgentWithEgressPayload, type TaskOfferForAgentWithEgressFreshPayload, type TaskOfferWithToolsetsPayload } from '@byok-sdk/protocol';
2
2
  import { type McpToolsetConfig, type RuntimeAdapter } from '../types';
3
3
  import { AgentHomeManager, type AgentRef } from '../agent-home';
4
4
  import { AgentSessionHandoffStore, type AgentTerminalCause } from './agent-session-handoff-store';
@@ -12,6 +12,9 @@ import type { SessionWorkspaceStore } from './session-workspace-store';
12
12
  import type { GitWorkspaceManager, GitWorkspaceObservation } from './git-workspace';
13
13
  import type { GitWorkspaceStore, GitWorkspacePhase } from './git-workspace-store';
14
14
  import type { AgentEgressController } from './agent-egress-controller';
15
+ import type { ResolvedAgentMessageMcpBin } from './resolve-agent-message-mcp-bin';
16
+ import type { ResolvedAgentMemoryMcpBin } from './resolve-agent-memory-mcp-bin';
17
+ import { type AgentMemoryAuditWarning, type AgentMemoryHostedProjection } from './agent-memory';
15
18
  /**
16
19
  * M4 Phase 3: default wait for `requestApproval` (see its own doc comment)
17
20
  * before force-resolving an unanswered out-of-band approval as a fail-closed
@@ -138,12 +141,15 @@ export declare const RESULT_DOCUMENT_UNDELIVERABLE_REASON_PREFIX = "result docum
138
141
  export interface ResultDocumentTask {
139
142
  readonly taskId: string;
140
143
  readonly sessionRef: string;
144
+ /** Exact offer-scoped second projection; absent for legacy and message-only offers. */
145
+ readonly terminalProjection?: Readonly<TerminalProjectionSelection>;
141
146
  }
142
147
  /**
143
148
  * Host-supplied glue that turns a finished task's final output into the
144
149
  * product's structured terminal result (`task.complete.document`). Returning
145
- * `undefined` means "this task has no structured result" and completes the
146
- * task exactly as it would have without an extractor configured at all.
150
+ * `undefined` means "this task has no structured result" for legacy offers.
151
+ * An explicit `terminalProjection.mode: 'result-document'` instead treats
152
+ * `undefined` as a fail-closed missing required document.
147
153
  *
148
154
  * SYNCHRONOUS by contract, like every other single-purpose callback on
149
155
  * `TaskRunnerDeps`, and the runtime ENFORCES that rather than trusting it:
@@ -195,6 +201,8 @@ export interface TaskRunnerDeps {
195
201
  workspaceRoot: string;
196
202
  /** Strict Agent offer authority. Absent means legacy offers never resolve an Agent home. */
197
203
  agentHome?: AgentHomeManager;
204
+ /** Local authority: legacy offers are declined after journal/dedup/cancel precedence. */
205
+ strictAgentOnly?: boolean;
198
206
  /** Exact host-selected policy accepted by `task.offer_for_agent_with_egress`. */
199
207
  agentEgressPolicy?: Readonly<AgentEgressPolicy>;
200
208
  /** Always-present projection/sanitizer consumer; it defaults to metadata-only. */
@@ -256,6 +264,8 @@ export interface TaskRunnerDeps {
256
264
  approvalRegistry: ApprovalRegistry;
257
265
  storeDir: string;
258
266
  productId: string;
267
+ /** Authenticated enrollment tenant projection; required by Agent message durability/recovery. */
268
+ tenantId?: string;
259
269
  /**
260
270
  * The already-resolved, process-immutable U4a Local Agent release identity.
261
271
  * `TaskRunner` only consumes this value; it never creates, normalizes, or
@@ -357,6 +367,14 @@ export interface TaskRunnerDeps {
357
367
  resultDocument?: {
358
368
  readonly extract: ResultDocumentExtractor;
359
369
  };
370
+ /** SDK-owned, task-scoped MCP helper. Required only for offers declaring messageEgress. */
371
+ agentMessageMcpBin?: Readonly<ResolvedAgentMessageMcpBin>;
372
+ /** SDK-owned MCP helper injected only into strict Agent tasks. */
373
+ agentMemoryMcpBin?: Readonly<ResolvedAgentMemoryMcpBin>;
374
+ /** Explicit external secure-fs helper. No PATH discovery or bundled native addon exists. */
375
+ agentMemoryFilesystemHelperBin?: string;
376
+ /** Optional local-to-hosted redacted projection port. Omission is zero-network. */
377
+ agentMemoryHostedProjection?: AgentMemoryHostedProjection;
360
378
  }
361
379
  /** See {@link TaskRunnerDeps.admissionGuard}. */
362
380
  export type AdmissionGuardDecision = {
@@ -385,6 +403,16 @@ type AcceptedOfferPayload = TaskOfferPayload | TaskOfferWithToolsetsPayload | Ta
385
403
  export declare class TaskRunner {
386
404
  private readonly deps;
387
405
  private readonly tasks;
406
+ private readonly pendingMessageTasks;
407
+ private readonly messageContextByToken;
408
+ private readonly messageContextByTask;
409
+ private readonly memoryContextByToken;
410
+ private readonly memoryContextByTask;
411
+ private readonly memoryInFlightByTask;
412
+ private readonly memoryClosingTasks;
413
+ private readonly memoryFilesystemByTask;
414
+ private readonly recoveredMessageOutboxes;
415
+ private readonly recoveredMessageRetryTimers;
388
416
  /**
389
417
  * Finding F4 (cancel lost during the offer-processing window): a
390
418
  * `task.cancel` for a taskId that hasn't finished `handleOffer` yet (still
@@ -479,6 +507,12 @@ export declare class TaskRunner {
479
507
  * cancellation intent.
480
508
  */
481
509
  private readonly finishedTaskIds;
510
+ /**
511
+ * Bounded local receive dedup for strict legacy declines. A decline is not a
512
+ * task terminal receipt, so it must never enter `finishedTaskIds`; retaining
513
+ * it separately keeps replay idempotent without claiming or finishing work.
514
+ */
515
+ private readonly strictDeclinedTaskIds;
482
516
  /**
483
517
  * M4 Phase 2 (daemon control socket `shutdown` RPC): set once by
484
518
  * {@link stopAcceptingOffers}, checked at the very top of `handleOffer` —
@@ -507,6 +541,44 @@ export declare class TaskRunner {
507
541
  * approval count, not the adapter's own event-queue depth.
508
542
  */
509
543
  getQueueWatermarks(): TaskQueueWatermark[];
544
+ /** Authenticated control-socket entry used only by the SDK-owned task MCP helper. */
545
+ publishAgentMessage(input: {
546
+ readonly contextToken: string;
547
+ readonly contentType: AgentMessageContentType;
548
+ readonly body: string;
549
+ }): Promise<{
550
+ messageId: string;
551
+ state: 'staged' | 'pending';
552
+ }>;
553
+ /** Authenticated control-socket entry used only by the SDK-owned memory MCP helper. */
554
+ recallAgentMemory(input: {
555
+ readonly contextToken: string;
556
+ readonly path: string;
557
+ readonly ifRevision?: string;
558
+ }): Promise<{
559
+ path: string;
560
+ revision: string;
561
+ content: string;
562
+ auditWarning?: AgentMemoryAuditWarning;
563
+ }>;
564
+ /** Authenticated control-socket entry used only by the SDK-owned memory MCP helper. */
565
+ saveAgentMemory(input: {
566
+ readonly contextToken: string;
567
+ readonly op: 'replace' | 'delete';
568
+ readonly path: string;
569
+ readonly expectedRevision: string;
570
+ readonly content?: string;
571
+ }): Promise<{
572
+ path: string;
573
+ revision?: string;
574
+ deleted: boolean;
575
+ }>;
576
+ /** Restore activated, unaccepted message drafts before transport admission on daemon restart. */
577
+ recoverAgentMessageOutboxes(agentsRoot: string): Promise<void>;
578
+ /** Retry stable recovered records after a transport handshake/re-handshake. */
579
+ retryRecoveredAgentMessages(): void;
580
+ private sendAgentMessageRecord;
581
+ private handleAgentMessageDisposition;
510
582
  /** M4 Phase 2: stop claiming any FUTURE `task.offer` — see `stoppingOffers`'s own doc comment. Idempotent. */
511
583
  stopAcceptingOffers(): void;
512
584
  /**
@@ -612,11 +684,23 @@ export declare class TaskRunner {
612
684
  private armMaxDurationTimer;
613
685
  handleEnvelope(envelope: Envelope): Promise<void>;
614
686
  private handleOffer;
687
+ private withAgentMessageMcp;
688
+ private revokeAgentMessageContext;
689
+ /** Injected only after strict Agent admission; a host registry may never replace this reserved name. */
690
+ private withAgentMemoryMcp;
691
+ /** Reconstruct all sensitive context from the active sealed task, never from MCP/model arguments. */
692
+ private activeMemoryContext;
693
+ private runMemoryOperation;
694
+ private quiesceAndSnapshotAgentMemory;
695
+ private bindAgentMemoryFilesystem;
696
+ private closeAgentMemoryFilesystem;
697
+ private revokeAgentMemoryContext;
615
698
  /** Protocol §7: an instruction too large to inline arrives as a `blobRef` — resolve it via the blob client rather than failing closed. */
616
699
  private resolveInstruction;
617
700
  /** Resolve every requested logical id locally and reject missing/colliding server authority before claim. */
618
701
  private resolveMcpServers;
619
702
  private pump;
703
+ private publishSuccessfulCompletion;
620
704
  /**
621
705
  * Protocol §7: an `artifact` `AgentEvent` only names a file the runtime
622
706
  * wrote into the task workspace (`name`/`contentType` — it carries no
@@ -1019,6 +1103,7 @@ export declare class TaskRunner {
1019
1103
  private reserveSemanticTerminal;
1020
1104
  /** 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). */
1021
1105
  private addFinishedTaskId;
1106
+ private addStrictDeclinedTaskId;
1022
1107
  /** `reuseDir`, when set (a known sessionRef's recorded workspace), is used verbatim instead of a fresh `workspaceRoot/<taskId>` directory — `mkdir recursive` is idempotent either way, so ensuring-exists is safe to do unconditionally. */
1023
1108
  private resolveWorkspaceDir;
1024
1109
  /**
@@ -1,5 +1,7 @@
1
1
  import { type ToolsetId } from '@byok-sdk/protocol';
2
2
  import type { McpToolsetConfig, McpToolsetObservation, McpToolsetRegistryStatus, McpToolsetReloadReceipt } from '../types';
3
+ export declare const AGENT_MESSAGE_MCP_SERVER_NAME = "byokagentmessage";
4
+ export declare const AGENT_MEMORY_MCP_SERVER_NAME = "byokagentmemory";
3
5
  export type McpToolsetConfigInput = Record<string, McpToolsetConfig> | undefined;
4
6
  export interface McpToolsetRegistrySnapshot {
5
7
  revision: string;