@byok-sdk/client 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +28 -3
  2. package/dist/adapters/index.js +74 -5
  3. package/dist/adapters/index.js.map +1 -1
  4. package/dist/adapters/pi/pi-adapter.d.ts +3 -0
  5. package/dist/adapters/pi/subagents-policy-extension.js +1 -1
  6. package/dist/adapters/pi/subagents-policy-extension.js.map +1 -1
  7. package/dist/agent-home.d.ts +107 -0
  8. package/dist/agent-memory/index.d.ts +1 -1
  9. package/dist/agent-memory/index.js +8 -5
  10. package/dist/agent-memory/index.js.map +1 -1
  11. package/dist/bin/byok-agent-memory-mcp.js.map +1 -1
  12. package/dist/bin/byok-agent-message-mcp.js.map +1 -1
  13. package/dist/bin/byok-agent-team-mcp.js.map +1 -1
  14. package/dist/bin/byok-agent.js +1279 -642
  15. package/dist/bin/byok-agent.js.map +1 -1
  16. package/dist/bin/byok-approval-mcp.js.map +1 -1
  17. package/dist/daemon/agent-egress-controller.d.ts +4 -0
  18. package/dist/daemon/auth-manager.d.ts +20 -0
  19. package/dist/daemon/blob-client.d.ts +24 -6
  20. package/dist/daemon/connection-manager.d.ts +69 -126
  21. package/dist/daemon/control-protocol.d.ts +8 -1
  22. package/dist/daemon/create-daemon.d.ts +28 -13
  23. package/dist/daemon/device-credential-store.d.ts +16 -0
  24. package/dist/daemon/long-poll-transport.d.ts +32 -23
  25. package/dist/daemon/observer.d.ts +1 -1
  26. package/dist/daemon/replay-cursor.d.ts +9 -0
  27. package/dist/daemon/task-runner.d.ts +22 -0
  28. package/dist/daemon/url.d.ts +17 -13
  29. package/dist/diagnostics/support-bundle.d.ts +1 -1
  30. package/dist/index.d.ts +5 -4
  31. package/dist/index.js +1278 -641
  32. package/dist/index.js.map +1 -1
  33. package/package.json +5 -7
  34. package/dist/daemon/ws-transport.d.ts +0 -139
@@ -1,5 +1,6 @@
1
1
  import { type Envelope } from '@byok-sdk/protocol';
2
2
  import { AuthManager } from './auth-manager';
3
+ import { ReplayCursorTooOldError } from './replay-cursor';
3
4
  import { type TransportEndpoint } from './url';
4
5
  /**
5
6
  * A long-poll request failed in a way that today told the caller only
@@ -32,21 +33,27 @@ export interface LongPollClientOptions {
32
33
  serverUrl: string;
33
34
  auth: AuthManager;
34
35
  getCursor: () => number | undefined;
35
- onEnvelope: (envelope: Envelope) => void;
36
+ /** Returns false when the envelope was a local duplicate and no handler was queued. */
37
+ onEnvelope: (envelope: Envelope) => boolean | void;
36
38
  /**
37
39
  * Capabilities advertised by the server that produced the current poll
38
40
  * response. Called before any envelopes from that response are delivered.
39
41
  * An older responder omitting the additive field is reported as `[]`.
40
42
  */
41
43
  onServerCapabilities?: (capabilities: string[]) => void;
44
+ /** Called when a failed poll invalidates the preceding response's capability snapshot. */
45
+ onServerCapabilitiesInvalidated?: () => void;
46
+ /** Called after a poll fails and before the retry delay begins. */
47
+ onPollFailure?: () => void;
42
48
  /** Called once the device is found to be revoked (401 surfaced through {@link AuthManager}) — the loop stops itself rather than retrying. */
43
49
  onRevoked?: () => void;
50
+ /** Called when the server cannot replay the durable cursor supplied to this poll. */
51
+ onReplayCursorTooOld?: (error: ReplayCursorTooOldError) => void;
44
52
  /**
45
53
  * M4 Phase 4 (version-negotiation drill fix), scope narrowed by finding F1:
46
54
  * called ONLY for a batch entry that failed to parse because its `type`
47
55
  * is entirely unrecognized (`parseMessage` throwing
48
- * {@link UnknownMessageTypeError} mirrors `ws-transport.ts`'s identical
49
- * per-frame tolerance for that SPECIFIC failure) and which still carries a
56
+ * {@link UnknownMessageTypeError}) and which still carries a
50
57
  * numeric envelope-level `seq` AND a recognizably task-class `type` (a
51
58
  * `task.` prefix — see `extractSkippableSeq`'s own doc comment for why a
52
59
  * `conn.*`-shaped or type-less entry is deliberately excluded, mirroring
@@ -64,11 +71,8 @@ export interface LongPollClientOptions {
64
71
  * not forward-compat tolerance — forwarding its `seq` here would
65
72
  * permanently ack a message the daemon never actually understood (the
66
73
  * server would stop redelivering it, silently stranding whatever it was
67
- * offering). The WS path never had this hazard (an unparseable WS frame
68
- * has no skip-side cursor bookkeeping at all — see
69
- * `ws-transport.ts` — so it simply gets redelivered later); this callback
70
- * being scoped to `UnknownMessageTypeError` only is what makes long-poll
71
- * match that same "no silent permanent ack" property for real. Optional
74
+ * offering). This callback being scoped to `UnknownMessageTypeError` only
75
+ * preserves the no-silent-permanent-ack property. Optional
72
76
  * only for constructor/test convenience — `ConnectionManager` always
73
77
  * supplies it.
74
78
  */
@@ -111,7 +115,7 @@ export interface LongPollClientOptions {
111
115
  * `ConnectionManager` always supplies it.
112
116
  */
113
117
  isStalled?: () => boolean;
114
- /** Backoff between failed poll attempts (network/HTTP errors), AND between cycles that made no cursor progress while stalled (finding P2/Fix 2a — see {@link isStalled}). The reference server holds each successful, non-stalled request open ~50s itself (protocol §8), so this only matters when a request errors outright or is stalled. Default 2s. */
118
+ /** Backoff between failed poll attempts (network/HTTP errors), stalled cycles, and duplicate-only cycles that made no cursor progress. The reference server holds a genuinely idle request open ~50s itself (protocol §8). Default 2s. */
115
119
  retryDelayMs?: number;
116
120
  /** Deterministic delay authority for automatic failed/stalled cycles. */
117
121
  retryDelayForAttempt?: (attempt: number, baseDelayMs: number) => number;
@@ -125,24 +129,29 @@ export interface LongPollClientOptions {
125
129
  */
126
130
  idleDelayMs?: number;
127
131
  }
132
+ /** A fully-read frozen-v1 count acknowledgement for the batch that was posted. */
133
+ export interface MessageBatchPostResult {
134
+ readonly accepted: number;
135
+ readonly rejected?: number;
136
+ }
128
137
  /**
129
- * Protocol §8 long-poll fallback: `GET /byok/events?cursor=N` in a loop,
130
- * used while WS connectivity is unavailable (see `ConnectionManager`), plus
131
- * `POST /byok/messages` for the daemon's own outbound envelopes while in
132
- * this mode (finding F6 — long-poll is a full transport, not receive-only:
133
- * see docs/protocol.md §8).
138
+ * Protocol §8 long-poll transport: `GET /byok/events?cursor=N` in a loop,
139
+ * plus `POST /byok/messages` for the daemon's own outbound envelopes
140
+ * (finding F6 long-poll is a full transport, not receive-only: see
141
+ * docs/protocol.md §8).
134
142
  *
135
- * Design B (finding N4): this is a stateless drainer, symmetric with
136
- * `WsTransport.sendNow` it holds no outbound queue of its own.
137
- * `ConnectionManager` owns the single shared outbox both transports drain
138
- * from (so a transport switch never strands a queued envelope);
139
- * `postBatch` is a single POST attempt, reporting back whether the server
140
- * accepted it. All retry/backoff policy (and re-checking which transport is
141
- * currently active) lives in the caller (`ConnectionManager.drainOutbox`).
143
+ * Design B (finding N4): this is a stateless drainer; it holds no outbound
144
+ * queue of its own. `ConnectionManager` owns the single shared outbox;
145
+ * `postBatch` is a single POST attempt, reporting only frozen-v1 accepted and
146
+ * rejected counts after its response body has been read and validated. All
147
+ * retry/backoff and rejection isolation policy lives in the caller
148
+ * (`ConnectionManager.drainOutbox`).
142
149
  */
143
150
  export declare class LongPollClient {
144
151
  private readonly opts;
145
152
  private running;
153
+ /** Owns exactly one active loop generation, including its held GET and retry delays. */
154
+ private loopAbortController;
146
155
  /**
147
156
  * Finding R1: seqs this loop has already `console.warn`'d about for a
148
157
  * validation-failed (recognized-type, invalid-payload) entry — a poison
@@ -201,8 +210,8 @@ export declare class LongPollClient {
201
210
  * (`ConnectionHub.handleInbound`), so a resend of the SAME batch (same
202
211
  * envelope `id`s — the caller must never rebuild them) is deduped
203
212
  * server-side into a safe no-op rather than reprocessed (§9). Returns
204
- * `true` once the server has accepted the batch.
213
+ * validated frozen-v1 counts only after a readable response body.
205
214
  */
206
- postBatch(envelopes: Envelope[]): Promise<boolean>;
215
+ postBatch(envelopes: Envelope[]): Promise<MessageBatchPostResult | undefined>;
207
216
  private loop;
208
217
  }
@@ -1,5 +1,5 @@
1
1
  import { type AgentEvent, type BlobRef, type Envelope, type RuntimeInfo, type TaskState } from '@byok-sdk/protocol';
2
- import type { ConnectionState } from './ws-transport';
2
+ import type { ConnectionState } from './connection-manager';
3
3
  /**
4
4
  * M3-2a: local observability for the daemon — the seam a CLI (M3-2b) drives a
5
5
  * live task feed, a task list, and approve/reject/unpair from, all LOCALLY
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The server retained no contiguous replay history after the cursor the
3
+ * daemon acknowledged. This is terminal for the current device enrollment:
4
+ * retrying the same cursor can only repeat the loss condition.
5
+ */
6
+ export declare class ReplayCursorTooOldError extends Error {
7
+ readonly recoverableFrom?: number | undefined;
8
+ constructor(recoverableFrom?: number | undefined);
9
+ }
@@ -177,6 +177,15 @@ export type ResultDocumentExtractor = (finalOutput: string, task: ResultDocument
177
177
  * opt-out pin.
178
178
  */
179
179
  export declare const DEFAULT_MAX_TASK_OUTPUT_BYTES: number;
180
+ /**
181
+ * WP0: default number of Attempts allowed to execute concurrently in one
182
+ * canonical Agent home. One — the canonical home is every Agent session's
183
+ * cwd, so a second concurrent Attempt is a second writer of the same
184
+ * `MEMORY.md`, `notes/` and `.git`. Raising it is an explicit host choice
185
+ * (`DaemonConfig.maxConcurrentMutableSessionsPerAgentHome`) that re-enables
186
+ * the 0.12.0 co-writing exposure; there is no implicit fallback to it.
187
+ */
188
+ export declare const DEFAULT_MAX_CONCURRENT_MUTABLE_SESSIONS_PER_AGENT_HOME = 1;
180
189
  export interface TaskRunnerDeps {
181
190
  adapters: RuntimeAdapter[];
182
191
  runtimeAllowlist?: string[];
@@ -204,6 +213,13 @@ export interface TaskRunnerDeps {
204
213
  agentHome?: AgentHomeManager;
205
214
  /** Local authority: legacy offers are declined after journal/dedup/cancel precedence. */
206
215
  strictAgentOnly?: boolean;
216
+ /**
217
+ * WP0: how many Attempts may execute concurrently in ONE canonical Agent
218
+ * home — see `DaemonConfig.maxConcurrentMutableSessionsPerAgentHome`'s own
219
+ * doc comment (`create-daemon.ts`) for the validated contract. Unset
220
+ * defaults to {@link DEFAULT_MAX_CONCURRENT_MUTABLE_SESSIONS_PER_AGENT_HOME}.
221
+ */
222
+ maxConcurrentMutableSessionsPerAgentHome?: number;
207
223
  /** Exact host-selected policy accepted by `task.offer_for_agent_with_egress`. */
208
224
  agentEgressPolicy?: Readonly<AgentEgressPolicy>;
209
225
  /** Always-present projection/sanitizer consumer; it defaults to metadata-only. */
@@ -421,6 +437,7 @@ export declare class TaskRunner {
421
437
  private readonly deps;
422
438
  private readonly tasks;
423
439
  private readonly pendingMessageTasks;
440
+ private readonly messageOutboxesByHome;
424
441
  private readonly messageContextByToken;
425
442
  private readonly messageContextByTask;
426
443
  private readonly memoryContextByToken;
@@ -481,6 +498,8 @@ export declare class TaskRunner {
481
498
  * costs nothing.
482
499
  */
483
500
  private readonly inFlightOffers;
501
+ /** Blob I/O before an offer becomes an active task still belongs to that offer's cancellation authority. */
502
+ private readonly inFlightBlobAborts;
484
503
  /**
485
504
  * Finding P2 (Fix 2c): taskIds that have reached a terminal outcome
486
505
  * (Complete/Failed/Cancelled) this session — populated in `finish()`.
@@ -550,6 +569,8 @@ export declare class TaskRunner {
550
569
  usesAgentEgress(taskId: string): boolean;
551
570
  /** M5 batch-3 (workstream 2): effective `maxTaskOutputBytes` cap for this daemon — see {@link DEFAULT_MAX_TASK_OUTPUT_BYTES}'s own doc comment. */
552
571
  private get maxTaskOutputBytes();
572
+ /** WP0: effective per-canonical-Agent-home Attempt cap — see {@link DEFAULT_MAX_CONCURRENT_MUTABLE_SESSIONS_PER_AGENT_HOME}. */
573
+ private get maxConcurrentMutableSessionsPerAgentHome();
553
574
  /**
554
575
  * M4 Phase 4 (part B.3, observability): per-active-task queue watermarks
555
576
  * for the control socket's `status` result — see
@@ -592,6 +613,7 @@ export declare class TaskRunner {
592
613
  }>;
593
614
  /** Restore activated, unaccepted message drafts before transport admission on daemon restart. */
594
615
  recoverAgentMessageOutboxes(agentsRoot: string): Promise<void>;
616
+ private agentMessageOutbox;
595
617
  /** Retry stable recovered records after a transport handshake/re-handshake. */
596
618
  retryRecoveredAgentMessages(): void;
597
619
  private sendAgentMessageRecord;
@@ -1,7 +1,5 @@
1
- /** Normalize a configured `serverUrl` (http/https/ws/wss, any path) to an http(s) base with no path/query. */
1
+ /** Normalize a configured HTTP(S) `serverUrl` (any path) to a base with no path/query. */
2
2
  export declare function toHttpBase(serverUrl: string): string;
3
- /** Derive the `/byok/ws` WebSocket URL from a configured `serverUrl`. */
4
- export declare function toWsUrl(serverUrl: string): string;
5
3
  /**
6
4
  * Which route a transport diagnostic is about, in the only two fields that
7
5
  * are safe to keep: the host (with port) and the path.
@@ -15,7 +13,7 @@ export declare function toWsUrl(serverUrl: string): string;
15
13
  * by formatting a raw URL of its own.
16
14
  */
17
15
  export interface TransportEndpoint {
18
- readonly transport: 'ws' | 'long-poll';
16
+ readonly transport: 'long-poll';
19
17
  /** `URL.host` — hostname plus port when non-default. Never userinfo. */
20
18
  readonly host: string;
21
19
  /** `URL.pathname` — no query, no fragment. */
@@ -23,6 +21,12 @@ export interface TransportEndpoint {
23
21
  }
24
22
  /** The single construction site for {@link TransportEndpoint} — see that interface's own doc comment for why it is the only one. */
25
23
  export declare function describeEndpoint(transport: TransportEndpoint['transport'], url: string | URL): TransportEndpoint;
24
+ /**
25
+ * The only URL projection used by validation errors. Reading from the parsed
26
+ * structure means userinfo, query, and fragment never enter the diagnostic.
27
+ */
28
+ /** The sole secret-safe projection for a configured server URL diagnostic. */
29
+ export declare function formatServerUrl(value: string | URL): string;
26
30
  /**
27
31
  * M5: thrown by {@link assertServerUrlAllowed} — see that function's own doc
28
32
  * comment for the full allow/deny rule this names. Deliberately ONE error
@@ -37,7 +41,7 @@ export declare class InsecureServerUrlError extends Error {
37
41
  }
38
42
  export interface AssertServerUrlAllowedOptions {
39
43
  /**
40
- * Explicit escape hatch: when `true`, a `http:`/`ws:` `serverUrl` whose
44
+ * Explicit escape hatch: when `true`, an `http:` `serverUrl` whose
41
45
  * host is NOT loopback is allowed through instead of throwing. Does
42
46
  * nothing for an unsupported scheme (see {@link assertServerUrlAllowed}'s
43
47
  * own doc comment) — that rejection is unconditional. Threaded from
@@ -51,25 +55,25 @@ export interface AssertServerUrlAllowedOptions {
51
55
  }
52
56
  /**
53
57
  * M5: transport-security gate for a configured `serverUrl` — refuses
54
- * plaintext (`ws:`/`http:`) transport to any non-loopback host, so a device
58
+ * plaintext (`http:`) transport to any non-loopback host, so a device
55
59
  * can never be talked into pairing with (and sending its pairing code /
56
60
  * device credentials to) a remote host in the clear. Call this ONCE at each
57
61
  * real entry point a raw, operator-supplied `serverUrl` first enters the
58
62
  * client (`create-daemon.ts`'s `pair()`/`start()`) rather than inside
59
- * `toHttpBase`/`toWsUrl` themselves: ws-transport, the long-poll fallback,
60
- * and blob-client all read `serverUrl` from that SAME `DaemonConfig`, never
63
+ * `toHttpBase` itself: long-poll and blob-client both read `serverUrl` from
64
+ * that same `DaemonConfig`, never
61
65
  * an independently-supplied URL of their own, so those two call sites are
62
66
  * already the single common path every one of them goes through.
63
67
  *
64
68
  * Rules, checked in order:
65
- * - `https:`/`wss:` — always allowed, any host (TLS is the actual
69
+ * - `https:` — always allowed, any host (TLS is the actual
66
70
  * plaintext-network defense; this gate has nothing further to add there).
67
- * - `http:`/`ws:` — allowed only when the hostname is loopback: exactly
71
+ * - `http:` — allowed only when the hostname is loopback: exactly
68
72
  * `localhost` or any `*.localhost` subdomain, an IPv4 literal in
69
73
  * `127.0.0.0/8`, or the IPv6 loopback `::1` — see {@link isLoopbackHostname}.
70
- * - `http:`/`ws:` to any other host — refused with a clear, typed
71
- * {@link InsecureServerUrlError} naming the offending URL and the fix
72
- * (use `wss:`/`https:`, or pass `dangerouslyAllowInsecureRemote: true` if
74
+ * - `http:` to any other host — refused with a clear, typed
75
+ * {@link InsecureServerUrlError} naming the redacted scheme/host/path and the fix
76
+ * (use `https:`, or pass `dangerouslyAllowInsecureRemote: true` if
73
77
  * this is a deliberate, understood exception) — UNLESS
74
78
  * `opts.dangerouslyAllowInsecureRemote` is `true`.
75
79
  * - Any other scheme (or a `serverUrl` that fails to parse as a URL at
@@ -28,7 +28,7 @@ export interface SupportBundle {
28
28
  status: 'online';
29
29
  pid: number;
30
30
  uptimeMs: number;
31
- transport: 'connecting' | 'open' | 'closed' | 'degraded' | 'revoked' | 'unavailable';
31
+ transport: 'connecting' | 'open' | 'closed' | 'revoked' | 'unavailable';
32
32
  activeTaskCount: number;
33
33
  pendingApprovalCount: number;
34
34
  operationalHealth: {
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { AgentRef } from './agent-home';
3
3
  export { AgentHomeError, AgentRefValidationError, AgentHomeResolutionError, AgentHomeCollisionError, AgentHomeBusyError, AgentHomeLeaseCorruptError, AgentHomeLayout, AgentHomeLeaseManager, AgentHomeManager, createAgentHomeProjection, createAgentHomeProjectionConsumer, AGENT_HOME_PROJECTION_STATE_FILE, stableAgentHomeOwnerId, validateAgentRef, } from './agent-home';
4
4
  export { AgentSessionHandoffStore, AgentSessionHandoffStoreError, AgentSessionHandoffCorruptError, AgentSessionHandoffMismatchError, } from './daemon/agent-session-handoff-store';
5
5
  export type { AgentSessionHandoff, AgentSessionHandoffMatch, AgentTaskTerminalEvidence, AgentTaskTerminalMatch, AgentTerminalCause, } from './daemon/agent-session-handoff-store';
6
- export type { AgentHomeResolution, AgentHomeProjection, AgentHomeProjectionInput, AgentHomeProjectionApplyInput, AgentHomeProjectionFunction, AgentHomeProjectionApplyFunction, AgentHomeLease, AgentHomeBinding, } from './agent-home';
6
+ export type { AgentHomeResolution, AgentHomeProjection, AgentHomeProjectionInput, AgentHomeProjectionApplyInput, AgentHomeProjectionFunction, AgentHomeProjectionApplyFunction, AgentHomeLease, AgentHomeBinding, AgentHomeExecutionLease, AgentHomeExecutionBinding, AgentHomeExecutionStatus, } from './agent-home';
7
7
  export { localStateRelocation, LocalStateRelocationError, LocalStateRelocationBusyError, LocalStateRelocationIntegrityError, } from './local-state-relocation';
8
8
  export type { LocalStateRelocationInput, LocalStateRelocationLease, } from './local-state-relocation';
9
9
  export { PolicyUnsupportedError, SteerUnsupportedError, freezeRuntimeAdapterDescriptor, sealRuntimeOperationManifest } from './types';
@@ -61,9 +61,10 @@ export { SKILL_PACKS_CAPABILITY, SKILL_PACKS_DIRNAME, SKILL_PACK_AUDIT_FILENAME,
61
61
  export type { InstallSkillPacksOptions, InstalledSkillPack, ProjectedSkillPack, SkillPackInstallErrorCode, SkillPackInstallResult, SkillPackLock, } from './daemon/skill-pack-installer';
62
62
  export { TruthMemoryClient, TruthMemoryClientError } from './daemon/truth-memory-client';
63
63
  export type { LocalMemoryFilter, MemorySelector, TruthManifestQueryInput, TruthManifestRecord, TruthMemoryClientErrorCode, TruthMemoryClientOptions, TruthMemoryMetric, TruthSnapshotCandidateInput, TruthSnapshotWriteInput, TruthTerminalWriteInput, TruthWriteBody, TruthWriteResult, VerifiedTruthRecord, } from './daemon/truth-memory-client';
64
- export type { ConnectionState } from './daemon/ws-transport';
65
- export { BlobClient } from './daemon/blob-client';
66
- export type { BlobResolver } from './daemon/blob-client';
64
+ export type { ConnectionState } from './daemon/connection-manager';
65
+ export { ReplayCursorTooOldError } from './daemon/replay-cursor';
66
+ export { BlobClient, BlobRequestAbortedError } from './daemon/blob-client';
67
+ export type { BlobClientOptions, BlobRequestAbortReason, BlobRequestOptions, BlobResolver } from './daemon/blob-client';
67
68
  export { DaemonObserver } from './daemon/observer';
68
69
  export type { DaemonEvent, DaemonEventKind, DaemonEventListener, DaemonTaskInfo, Unsubscribe } from './daemon/observer';
69
70
  export { createServiceLifecycle, UnsupportedServicePlatformError } from './lifecycle/create-service-lifecycle';