@byok-sdk/cloud 0.12.0 → 0.14.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.
@@ -0,0 +1,81 @@
1
+ /**
2
+ * GAP-2: why a `cloud.steerTask` call was refused, and the one input that
3
+ * decides it.
4
+ *
5
+ * The reference server (`ConnectionHub.steerTask`, `packages/server/src/hub.ts`)
6
+ * reaches this decision from `TaskSnapshot.claimedRuntimeCapabilities` — the
7
+ * capability block the CLAIMING adapter reported for itself on its own
8
+ * `task.claim`, frozen at the `Offered -> Claimed` transition. Cloud keeps the
9
+ * same single source: {@link TaskAttempt.claimedRuntimeCapabilities}, written by
10
+ * the claim that wins the ownership CAS (`stores/ports.ts`) and never again.
11
+ *
12
+ * The gap this closes is not cosmetic: only pi's adapter implements steering,
13
+ * and Claude's and Codex's THROW on receiving `task.steer`, which stalls that
14
+ * device's redelivery cursor at that seq forever. So the refusal has to happen
15
+ * before an envelope exists, from a fact that shares a lifecycle with the
16
+ * task-to-runtime binding.
17
+ *
18
+ * Fail-closed on unknown, deliberately: `steer_unsupported_runtime` covers both
19
+ * "the claiming adapter reported `steer: false`" and "this attempt carries no
20
+ * capability snapshot at all" (a daemon whose claim predates the field, an
21
+ * attempt claimed before migration `0018`). Refusing an unknown is a
22
+ * recoverable, operator-visible error; guessing "supported" reintroduces the
23
+ * permanent cursor stall this gate exists to prevent. There is deliberately NO
24
+ * runtime-id allow-list here either — a table mapping `pi -> steerable` would be
25
+ * a second capability authority in the coordination plane, drifting the moment a
26
+ * runtime gains or loses the feature, and the daemon already reports the truth.
27
+ *
28
+ * SINGLE SOURCE, deliberately: the gate reads the claim snapshot and NOTHING
29
+ * from the connection layer. Cloud's connection-level equivalent is
30
+ * `DeviceRecord.capabilities`, written from a bearer-authenticated `conn.hello`
31
+ * (`inbound.ts`) — discovery data describing a device BUILD, not the adapter
32
+ * that took this task. A device can reconnect later with a different adapter
33
+ * set; a task already running must keep being judged against what was true when
34
+ * it was claimed. Same pin the reference server holds in
35
+ * `packages/server/src/__tests__/steer-runtime-capability-gate.test.ts`.
36
+ */
37
+ import type { RuntimeId } from '@byok-sdk/protocol';
38
+ import type { TaskAttemptStatus } from './stores/ports';
39
+ /**
40
+ * Stable strings a caller switches on (an operator UI, an HTTP surface mapping
41
+ * this to a status code) rather than matching error text. Byte-identical to the
42
+ * reference server's `SteerRejectionCode` (`packages/server/src/hub.ts`), so a
43
+ * host that moves from the embedded server to the hosted kernel keeps its own
44
+ * mapping unchanged.
45
+ *
46
+ * - `task_terminal` — the attempt already reached `complete`/`failed`/
47
+ * `cancelled`. Checked FIRST, so a terminal attempt that is also (obviously)
48
+ * not running reports the more specific truth, and a steer racing a terminal
49
+ * transition always resolves terminal-first.
50
+ * - `task_not_running` — the attempt exists and is live, but is `offered`/
51
+ * `claimed`/`cancel_requested`; there is no running turn to steer yet.
52
+ * - `steer_unsupported_runtime` — the runtime that CLAIMED this attempt cannot
53
+ * be steered, per the claim-time capability snapshot. A MISSING snapshot
54
+ * rejects under this same code: unknown is not supported.
55
+ */
56
+ export type SteerRejectionCode = 'steer_unsupported_runtime' | 'task_not_running' | 'task_terminal';
57
+ /**
58
+ * Thrown by `ByokCloud.steerTask` instead of a generic `Error`, so a caller can
59
+ * tell WHY a steer was refused without matching on message text — the same
60
+ * typed-error idiom as `StaleApprovalError` (`approval-control.ts`).
61
+ *
62
+ * A distinct class rather than a `CloudErrorCode`, for the same reason as
63
+ * `StaleApprovalError`: it carries the state and the runtime the caller needs to
64
+ * explain the refusal, and a code alone cannot.
65
+ *
66
+ * Thrown before any mailbox row is allocated — a refused steer is a NON-EVENT,
67
+ * exactly like a refused approval.
68
+ */
69
+ export declare class SteerRejectedError extends Error {
70
+ readonly taskId: string;
71
+ readonly code: SteerRejectionCode;
72
+ /** The attempt's status at the moment the steer was refused. */
73
+ readonly status: TaskAttemptStatus;
74
+ /** {@link TaskAttempt.claimedRuntime} — `undefined` when nothing was ever recorded, which is itself a reason `steer_unsupported_runtime` can fire. */
75
+ readonly runtime: RuntimeId | undefined;
76
+ constructor(taskId: string, code: SteerRejectionCode,
77
+ /** The attempt's status at the moment the steer was refused. */
78
+ status: TaskAttemptStatus,
79
+ /** {@link TaskAttempt.claimedRuntime} — `undefined` when nothing was ever recorded, which is itself a reason `steer_unsupported_runtime` can fire. */
80
+ runtime: RuntimeId | undefined);
81
+ }
@@ -8,5 +8,5 @@ export declare class InMemoryAgentEgressStore implements AgentEgressStore {
8
8
  readonly record: AgentEgressRecord;
9
9
  readonly created: boolean;
10
10
  }>;
11
- get(tenant: TenantId, deviceId: string, eventId: string): Promise<AgentEgressRecord | undefined>;
11
+ get(tenant: TenantId, deviceId: string, agentRef: AgentEgressRecord['payload']['agentRef'], eventId: string): Promise<AgentEgressRecord | undefined>;
12
12
  }
@@ -1,9 +1,10 @@
1
1
  import type { Clock, TenantId } from '@byok-sdk/core';
2
- import { type ApprovalTimelineAppendInput, type ApprovalTimelineStore, type ApprovalTimelineTail } from '../../approval-timeline';
2
+ import { type ApprovalTimelineAppendInput, type ApprovalTimelineResolvePendingInput, type ApprovalTimelineResolvePendingResult, type ApprovalTimelineStore, type ApprovalTimelineTail } from '../../approval-timeline';
3
3
  export declare class InMemoryApprovalTimelineStore implements ApprovalTimelineStore {
4
4
  #private;
5
5
  private readonly clock;
6
6
  constructor(clock: Clock);
7
7
  append(tenant: TenantId, input: ApprovalTimelineAppendInput): Promise<ApprovalTimelineTail>;
8
+ resolvePending(tenant: TenantId, input: ApprovalTimelineResolvePendingInput): Promise<ApprovalTimelineResolvePendingResult>;
8
9
  read(tenant: TenantId, taskId: string): Promise<ApprovalTimelineTail | undefined>;
9
10
  }
@@ -1,17 +1,20 @@
1
1
  /**
2
2
  * In-memory {@link InboundDedupStore} (N3).
3
3
  *
4
- * A bounded ring per (tenant, device), not an unbounded set: the wire is
5
- * at-least-once (§9), so this makes processing at-most-once without letting a
6
- * chatty device grow memory without limit. Check-and-record is one call, so a
7
- * composition cannot accidentally split it into a racy read-then-write.
4
+ * A bounded ring per physical (tenant, device) or strict (tenant, device,
5
+ * AgentRef) fact, not an unbounded set: the wire is at-least-once (§9), so
6
+ * this makes processing at-most-once without letting a chatty device or Agent
7
+ * grow memory without limit. Check-and-record is one call, so a composition
8
+ * cannot accidentally split it into a racy read-then-write.
8
9
  */
9
10
  import { type TenantId } from '@byok-sdk/core';
11
+ import type { AgentRef } from '@byok-sdk/protocol';
10
12
  import type { InboundDedupStore } from '../ports';
11
- /** Ids retained per device. Same order of magnitude as the reference server's ring. */
13
+ /** Ids retained per physical or Agent-bound key. Same depth as the reference server's ring. */
12
14
  export declare const DEDUP_RING_CAPACITY = 1024;
13
15
  export declare class InMemoryInboundDedupStore implements InboundDedupStore {
14
16
  #private;
15
17
  constructor(capacity?: number);
16
18
  checkAndRecord(tenant: TenantId, deviceId: string, envelopeId: string): Promise<boolean>;
19
+ checkAndRecordAgent(tenant: TenantId, deviceId: string, agentRef: AgentRef, envelopeId: string): Promise<boolean>;
17
20
  }
@@ -16,7 +16,11 @@
16
16
  * unfalsifiable.
17
17
  */
18
18
  import { type Clock, type TenantId } from '@byok-sdk/core';
19
- import { type AgentMessageAdmission, type AgentRef, type TaskAttempt, type TaskAttemptStatus, type TaskAttemptStore } from '../ports';
19
+ import type { RuntimeCapabilities, RuntimeId } from '@byok-sdk/protocol';
20
+ import { type AgentMessageAdmission, type AgentRef, type TaskAttempt, type TaskAttemptListQuery, type TaskAttemptPage, type TaskAttemptStatus, type TaskAttemptStore } from '../ports';
21
+ interface StoredAgentMessageAdmission extends AgentMessageAdmission {
22
+ readonly deviceId: string;
23
+ }
20
24
  export declare class InMemoryTaskAttemptStore implements TaskAttemptStore {
21
25
  #private;
22
26
  constructor(clock: Clock, state?: InMemoryTaskAttemptState);
@@ -54,9 +58,12 @@ export declare class InMemoryTaskAttemptStore implements TaskAttemptStore {
54
58
  }): Promise<AgentMessageAdmission | undefined>;
55
59
  get(tenant: TenantId, taskId: string): Promise<TaskAttempt | undefined>;
56
60
  getMany(tenant: TenantId, taskIds: readonly string[]): Promise<readonly TaskAttempt[]>;
61
+ list(tenant: TenantId, query: TaskAttemptListQuery): Promise<TaskAttemptPage>;
57
62
  claim(tenant: TenantId, input: {
58
63
  taskId: string;
59
64
  deviceId: string;
65
+ runtime?: RuntimeId;
66
+ capabilities?: RuntimeCapabilities;
60
67
  }): Promise<TaskAttempt | undefined>;
61
68
  recordStatus(tenant: TenantId, input: {
62
69
  readonly taskId: string;
@@ -69,9 +76,10 @@ export declare class InMemoryTaskAttemptStore implements TaskAttemptStore {
69
76
  export declare class InMemoryTaskAttemptState {
70
77
  #private;
71
78
  readonly attempts: Map<string, TaskAttempt>;
72
- readonly messageAdmissions: Map<string, AgentMessageAdmission>;
79
+ readonly messageAdmissions: Map<string, StoredAgentMessageAdmission>;
73
80
  constructor(clock: Clock);
74
81
  now(): string;
75
82
  /** Serialize every state-changing operation for one tenant/task key. */
76
83
  mutate<T>(key: string, operation: () => T | Promise<T>): Promise<T>;
77
84
  }
85
+ export {};
@@ -33,7 +33,7 @@
33
33
  * bytes (see the blobs section below).
34
34
  */
35
35
  import type { MailboxBody, MailboxMessage, PresenceStore, StorageReservation, TenantId, TenantReadiness } from '@byok-sdk/core';
36
- import type { AgentEgressReliablePayload, AgentRef } from '@byok-sdk/protocol';
36
+ import type { AgentEgressReliablePayload, AgentRef, RuntimeCapabilities, RuntimeId } from '@byok-sdk/protocol';
37
37
  export type { AgentEgressReliablePayload, AgentRef } from '@byok-sdk/protocol';
38
38
  import type { ActivityStore } from '../activity';
39
39
  import type { ApprovalTimelineStore } from '../approval-timeline';
@@ -184,12 +184,17 @@ export interface NonceStore {
184
184
  }
185
185
  export interface InboundDedupStore {
186
186
  /**
187
- * `true` when `envelopeId` was already seen for this (tenant, device);
188
- * otherwise records it and returns `false`. Bounded per device — the wire is
189
- * at-least-once (§9); this makes processing at-most-once without an
190
- * unbounded set.
187
+ * Device-physical envelope ledger (`conn.hello` and facts with no Agent
188
+ * binding). `true` when `envelopeId` was already seen for this (tenant,
189
+ * device); otherwise records it and returns `false`.
191
190
  */
192
191
  checkAndRecord(tenant: TenantId, deviceId: string, envelopeId: string): Promise<boolean>;
192
+ /**
193
+ * Agent-bound envelope ledger. The AgentRef is required: a same-device
194
+ * second Agent must never resolve or suppress this Agent's completed fact.
195
+ * Retention remains bounded per exact (tenant, device, AgentRef).
196
+ */
197
+ checkAndRecordAgent(tenant: TenantId, deviceId: string, agentRef: AgentRef, envelopeId: string): Promise<boolean>;
193
198
  }
194
199
  export declare const TASK_ATTEMPT_STATUSES: readonly ['offered', 'claimed', 'running', 'cancel_requested', 'complete', 'failed', 'cancelled'];
195
200
  export type TaskAttemptStatus = (typeof TASK_ATTEMPT_STATUSES)[number];
@@ -209,6 +214,40 @@ export interface TaskAttempt {
209
214
  /** Set by `task.claim`, and only by the first one. Legacy attempts remain claimable by any tenant device until then; strict Agent attempts are target-device bound before this field is consulted. */
210
215
  readonly ownerDeviceId?: string;
211
216
  readonly status: TaskAttemptStatus;
217
+ /**
218
+ * The runtime the CLAIMING daemon reported for itself on its own
219
+ * `task.claim` (`TaskClaimPayload.runtime`), snapshotted at the
220
+ * `offered -> claimed` transition and never written again — a redelivered or
221
+ * losing claim leaves it exactly as the winning claim left it.
222
+ *
223
+ * ADR-028 forbids cloud holding EXECUTION state (a live `running`/`thinking`
224
+ * phase, a turn, a PID, the current tool call) — quantities that keep
225
+ * changing while an attempt runs. This is not one: it is written once, at
226
+ * the moment ownership is decided, and is an attribution fact about the
227
+ * attempt. Byte-for-byte the same semantics as the reference server's
228
+ * `TaskSnapshot.claimedRuntime` (`packages/server/src/types.ts`).
229
+ *
230
+ * Absent for a daemon whose `task.claim` carried no `runtime` at all.
231
+ */
232
+ readonly claimedRuntime?: RuntimeId;
233
+ /**
234
+ * The capability block the CLAIMING adapter reported for ITSELF on that same
235
+ * `task.claim` (`TaskClaimPayload.capabilities`), snapshotted under exactly
236
+ * the same write-once rule as {@link TaskAttempt.claimedRuntime}.
237
+ *
238
+ * This is the ONLY input to the steer gate (`steer-control.ts`,
239
+ * `ByokCloud.steerTask`). The connection-level snapshot cloud keeps —
240
+ * `DeviceRecord.capabilities`, written from `conn.hello`
241
+ * (`inbound.ts`) — describes a device BUILD and never feeds it: a device can
242
+ * reconnect later with a different adapter set, while a task that is already
243
+ * running must keep being judged against what was true when it was claimed.
244
+ * Same single-source rule the reference server pins
245
+ * (`packages/server/src/__tests__/steer-runtime-capability-gate.test.ts`).
246
+ *
247
+ * Absent for a daemon that predates the field, which the gate reads as
248
+ * "unknown" and refuses — unknown is not supported.
249
+ */
250
+ readonly claimedRuntimeCapabilities?: RuntimeCapabilities;
212
251
  /** Runtime-reported terminal cause from the first winning terminal. */
213
252
  readonly terminalCause?: string;
214
253
  /** Durable host cancellation authority. Its presence outranks later device terminal receipts. */
@@ -218,6 +257,43 @@ export interface TaskAttempt {
218
257
  };
219
258
  readonly updatedAt: string;
220
259
  }
260
+ /**
261
+ * One bounded page request over a tenant's task attempts.
262
+ *
263
+ * Keyset-paged by `taskId` ASCENDING, deliberately, and NOT chronologically: an
264
+ * attempt carries no monotonic sequence, and `updatedAt` moves under an
265
+ * in-flight page (a status transition would re-order rows mid-walk and let a
266
+ * caller skip or repeat one). `taskId` is `task_<uuid>` — stable, unique per
267
+ * tenant, and never rewritten — so it is the only column that gives a walk the
268
+ * exactly-once property. The order is therefore arbitrary but TOTAL, which is
269
+ * what a paged read model needs; a caller that wants recency sorts a page
270
+ * itself.
271
+ *
272
+ * Adding a sequence column to get chronological order was rejected: it is a
273
+ * migration across every composition to serve a read-model nicety, and a second
274
+ * ordering authority to keep consistent with the row it decorates.
275
+ */
276
+ export interface TaskAttemptListQuery {
277
+ /** Maximum attempts in the page. Required and fail-closed: a non-positive or non-integer limit is rejected, never silently defaulted or clamped. */
278
+ readonly limit: number;
279
+ /**
280
+ * The `nextCursor` from the previous page — opaque to the caller, and an
281
+ * EXCLUSIVE lower bound on `taskId`. Absent starts at the beginning. A cursor
282
+ * naming a task that has since been deleted still resolves: it is a bound,
283
+ * not a lookup.
284
+ */
285
+ readonly cursor?: string;
286
+ }
287
+ export interface TaskAttemptPage {
288
+ readonly attempts: readonly TaskAttempt[];
289
+ /**
290
+ * Pass as the next request's `cursor`. ABSENT means this page is the end of
291
+ * the walk — not "start over" — so a caller stops on absence rather than on
292
+ * an empty page, and a page that exactly fills `limit` with nothing after it
293
+ * still terminates.
294
+ */
295
+ readonly nextCursor?: string;
296
+ }
221
297
  export interface TaskAttemptStore {
222
298
  /** Called when an offer is enqueued: records the pending attempt with no owner yet. */
223
299
  open(tenant: TenantId, input: {
@@ -240,8 +316,10 @@ export interface TaskAttemptStore {
240
316
  /**
241
317
  * Atomically binds one message payload to a live task before an external
242
318
  * consumer can run. `pending` is an existing exact reservation whose terminal
243
- * receipt has not been recorded yet; callers must fail closed rather than
244
- * invoke the consumer again.
319
+ * receipt has not been recorded yet; callers retry the same product consumer
320
+ * contract so its own durable idempotency can reconcile a commit whose local
321
+ * finalization failed. Exact pending lookup precedes the live-task gate, while
322
+ * every new reservation remains lifecycle-gated.
245
323
  */
246
324
  reserveAgentMessage(tenant: TenantId, input: {
247
325
  readonly taskId: string;
@@ -267,10 +345,28 @@ export interface TaskAttemptStore {
267
345
  get(tenant: TenantId, taskId: string): Promise<TaskAttempt | undefined>;
268
346
  /** Batch lookup used by mailbox projection; implementations must not turn one poll into N queries. */
269
347
  getMany(tenant: TenantId, taskIds: readonly string[]): Promise<readonly TaskAttempt[]>;
270
- /** First claim wins the ownership; a later claim by the same device is idempotent. No-op (returns `undefined`) for a task this tenant never offered. */
348
+ /**
349
+ * One bounded page of this tenant's attempts, keyset-paged by `taskId` — see
350
+ * {@link TaskAttemptListQuery} for why that key and not a timestamp.
351
+ * Implementations must read one page in one query (`taskId > cursor ORDER BY
352
+ * taskId LIMIT limit + 1`), never the whole tenant filtered in memory.
353
+ */
354
+ list(tenant: TenantId, query: TaskAttemptListQuery): Promise<TaskAttemptPage>;
355
+ /**
356
+ * First claim wins the ownership; a later claim by the same device is
357
+ * idempotent. No-op (returns `undefined`) for a task this tenant never
358
+ * offered.
359
+ *
360
+ * `runtime`/`capabilities` are the claiming daemon's self-report, written
361
+ * ONLY by the claim that actually wins the ownership CAS — an idempotent
362
+ * re-claim (and a losing one) must leave the recorded snapshot untouched,
363
+ * including when it carries a different or absent value.
364
+ */
271
365
  claim(tenant: TenantId, input: {
272
366
  readonly taskId: string;
273
367
  readonly deviceId: string;
368
+ readonly runtime?: RuntimeId;
369
+ readonly capabilities?: RuntimeCapabilities;
274
370
  }): Promise<TaskAttempt | undefined>;
275
371
  /** Record a lifecycle transition. No-op (returns `undefined`) for an unknown task — same shape as the reference server's per-type handlers. */
276
372
  recordStatus(tenant: TenantId, input: {
@@ -335,7 +431,8 @@ export interface AgentEgressStore {
335
431
  readonly record: AgentEgressRecord;
336
432
  readonly created: boolean;
337
433
  }>;
338
- get(tenant: TenantId, deviceId: string, eventId: string): Promise<AgentEgressRecord | undefined>;
434
+ /** Exact reliable fact lookup; device plus event id alone is ambiguous. */
435
+ get(tenant: TenantId, deviceId: string, agentRef: AgentRef, eventId: string): Promise<AgentEgressRecord | undefined>;
339
436
  }
340
437
  export interface ProofRequestReceipt {
341
438
  readonly tenantId: TenantId;
@@ -22,6 +22,7 @@
22
22
  * device-facing handler must not be able to reach them.
23
23
  */
24
24
  import { type BoardClaimInput, type BoardItem, type BoardItemInput, type BoardListQuery, type BoardPage, type BoardStatusUpdateInput, type BoardUnclaimInput, type CoreStores, type MailboxAdvanceCursorInput, type MailboxAppendInput, type MailboxCursorState, type MailboxMessage, type MailboxPage, type MailboxReadQuery, type MailboxRecordDeliveryInput, type Principal, type PresenceHint, type PresenceHintInput, type TenantReadiness, type StorageFinalizeInput, type StorageFinalizeResult, type StorageReservation, type StorageReservationInput, type TenantId } from '@byok-sdk/core';
25
+ import type { AgentRef, RuntimeCapabilities, RuntimeId } from '@byok-sdk/protocol';
25
26
  import type { ActivityAppendInput, ActivityTail } from './activity';
26
27
  import type { ApprovalTimelineAppendInput, ApprovalTimelineTail } from './approval-timeline';
27
28
  import type { BlobObservation, CloudStores, DeviceRecord, AgentEgressRecord, RequestReceipt, TaskCancellationMutation, TaskCancellationRequest, TaskAttempt, TaskAttemptStatus } from './stores/ports';
@@ -99,9 +100,12 @@ export interface TenantBoundTaskAttempts {
99
100
  }): Promise<import('./stores/ports').AgentMessageAdmission | undefined>;
100
101
  get(taskId: string): Promise<TaskAttempt | undefined>;
101
102
  getMany(taskIds: readonly string[]): Promise<readonly TaskAttempt[]>;
103
+ list(query: import('./stores/ports').TaskAttemptListQuery): Promise<import('./stores/ports').TaskAttemptPage>;
102
104
  claim(input: {
103
105
  readonly taskId: string;
104
106
  readonly deviceId: string;
107
+ readonly runtime?: RuntimeId;
108
+ readonly capabilities?: RuntimeCapabilities;
105
109
  }): Promise<TaskAttempt | undefined>;
106
110
  recordStatus(input: {
107
111
  readonly taskId: string;
@@ -114,7 +118,10 @@ export interface TenantBoundTaskCancellations {
114
118
  request(input: TaskCancellationRequest): Promise<TaskCancellationMutation | undefined>;
115
119
  }
116
120
  export interface TenantBoundDedup {
121
+ /** Device-physical envelope ledger. */
117
122
  checkAndRecord(deviceId: string, envelopeId: string): Promise<boolean>;
123
+ /** Strict Agent-bound envelope ledger. */
124
+ checkAndRecordAgent(deviceId: string, agentRef: AgentRef, envelopeId: string): Promise<boolean>;
118
125
  }
119
126
  export interface TenantBoundReceipts {
120
127
  record(input: {
@@ -131,7 +138,7 @@ export interface TenantBoundAgentEgress {
131
138
  readonly record: AgentEgressRecord;
132
139
  readonly created: boolean;
133
140
  }>;
134
- get(deviceId: string, eventId: string): Promise<AgentEgressRecord | undefined>;
141
+ get(deviceId: string, agentRef: AgentRef, eventId: string): Promise<AgentEgressRecord | undefined>;
135
142
  }
136
143
  export interface TenantBoundBlobs {
137
144
  createUpload(reservation: StorageReservation): Promise<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byok-sdk/cloud",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "BYOK SDK hosted device surface: stateless frozen-v1 HTTP handlers over tenant-first @byok-sdk/core ports",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -44,8 +44,8 @@
44
44
  "clean": "rm -rf dist"
45
45
  },
46
46
  "dependencies": {
47
- "@byok-sdk/core": "0.12.0",
48
- "@byok-sdk/protocol": "0.12.0",
47
+ "@byok-sdk/core": "0.14.0",
48
+ "@byok-sdk/protocol": "0.14.0",
49
49
  "hono": "^4.12.30",
50
50
  "zod": "^4.4.3"
51
51
  }