@byok-sdk/protocol 0.1.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,21 @@
1
+ export declare const TASK_STATES: readonly ['Offered', 'Claimed', 'Running', 'AwaitApproval', 'Complete', 'Failed', 'Cancelled'];
2
+ export type TaskState = (typeof TASK_STATES)[number];
3
+ /**
4
+ * Legal state transitions for a task. `Complete` / `Failed` / `Cancelled` are
5
+ * terminal (no outgoing edges). `Running` and `AwaitApproval` form a loop:
6
+ * the daemon can request approval mid-run and resume once the server
7
+ * approves (or fail/cancel out of the approval wait).
8
+ *
9
+ * `Offered -> Failed` (M1 gap #5, "Declined vs. Failed"): a daemon that
10
+ * declines an offer pre-claim (`task.decline`) reports it through the
11
+ * existing `Failed` state rather than a new `Declined` state. A decline and
12
+ * a post-claim failure are the same outcome from the dispatcher's point of
13
+ * view — this attempt produced no result, `reason`/`retryable` say why and
14
+ * whether retrying elsewhere makes sense — so reusing `Failed` keeps the
15
+ * state machine minimal instead of forking every terminal-state consumer
16
+ * into "Failed or Declined, handle both". See docs/protocol.md for the full
17
+ * writeup.
18
+ */
19
+ export declare const TASK_TRANSITIONS: Readonly<Record<TaskState, readonly TaskState[]>>;
20
+ /** Whether `from -> to` is a legal transition per {@link TASK_TRANSITIONS}. */
21
+ export declare function canTransition(from: TaskState, to: TaskState): boolean;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Wire protocol version. Bump on breaking (non-additive) changes to the envelope
3
+ * or message shapes. Additive changes (new optional fields, new message types)
4
+ * do not require a bump — servers negotiate the highest common version and
5
+ * daemons/servers must ignore unknown fields and unknown message types.
6
+ *
7
+ * FROZEN v1 (end of M2 — see docs/protocol.md "Freeze rule"): the pi, claude,
8
+ * and codex runtime adapters have all exercised the wire, and every M1/M2
9
+ * protocol gap has been closed. `PROTOCOL_VERSION` stays `1` from here
10
+ * forward; it does not bump for additive changes (new optional fields, new
11
+ * message types, new `AgentEvent` variants, new capability flags) — only for
12
+ * a breaking one (changing, removing, or retyping anything that already
13
+ * exists).
14
+ *
15
+ * IMPORTANT: changing this constant, or changing/removing/retyping any
16
+ * already-frozen schema in this package, requires a DELIBERATE update to the
17
+ * committed golden fixtures in `src/__tests__/golden/` (`v1.frozen.json`,
18
+ * `v1.envelopes.ndjson`) — see `src/__tests__/freeze-guard.test.ts`, which
19
+ * fails loudly on exactly that kind of drift. A passing freeze-guard run
20
+ * after such a change means either (a) the change was genuinely additive and
21
+ * the golden was regenerated with justification, or (b) this constant was
22
+ * bumped alongside a new golden generation for the new version — never a
23
+ * silent edit to either file to make the test pass.
24
+ */
25
+ export declare const PROTOCOL_VERSION = 1;
26
+ /**
27
+ * Capability flags exchanged during the connection handshake (`conn.hello` /
28
+ * `conn.ack`). Additional flags may be introduced without a protocol version
29
+ * bump; unrecognized flags must be ignored by both sides.
30
+ *
31
+ * `interactive-approval` is RESERVED as of this addition: it gates the
32
+ * (currently unexercised) approval seam — a server must not route an
33
+ * approval-requiring policy to a daemon that hasn't advertised this flag. No
34
+ * bundled runtime adapter emits it yet; that's expected until interactive
35
+ * approval is actually wired up in a later wave.
36
+ *
37
+ * `approval_resolved` (additive-minor): a SERVER-advertised flag meaning
38
+ * "I understand the `task.approval_resolved` message" (`messages.ts`). This
39
+ * is the N/N-1 answer for that new daemon -> server message: an old server's
40
+ * `CAPABILITY_FLAGS`/`conn.ack.capabilities` never includes it, so a new
41
+ * daemon talking to an old server never sends `task.approval_resolved` at
42
+ * all (see `packages/client`'s `task-runner.ts`) and falls back to the
43
+ * pre-existing implicit-resume inference
44
+ * (`ConnectionHub.resumeIfImplicitlyApproved`, `packages/server/src/hub.ts`)
45
+ * unconditionally, exactly as before this flag existed. Unlike
46
+ * `interactive-approval`, this one IS exercised the moment both sides
47
+ * support it — there is no reserved/dormant period for it.
48
+ */
49
+ /**
50
+ * `approval-targeting` (M5, additive-minor): unlike `approval_resolved`
51
+ * above, this flag is purely INFORMATIONAL/semantic, not a functional gate.
52
+ * `task.await_approval`/`task.approve`/`task.reject` all carry their new
53
+ * `approvalId` field UNCONDITIONALLY on both sides once each peer is
54
+ * upgraded -- the wire is tolerant (a plain, non-`.strict()` `z.object()`
55
+ * field, `messages.ts`), so no version/capability negotiation is needed just
56
+ * to send it safely; an older peer that doesn't recognize the field simply
57
+ * never reads it. Receivers decide whether to apply exact-match targeting
58
+ * by FIELD PRESENCE on the specific message at hand (does this particular
59
+ * `task.approve`/`task.reject`/`onApprovalResolved` payload carry an
60
+ * `approvalId`, and does a stored one exist to compare it against?), never
61
+ * by checking this flag -- see `ConnectionHub.approveTask`/`rejectTask`/
62
+ * `onApprovalResolved` and `TaskRunner.handleApprove`/`handleReject`
63
+ * (`packages/client`'s `task-runner.ts`). This flag exists only so each side
64
+ * can advertise, and an embedder/operator can observe (`ConnectionHub.
65
+ * getDeviceCapabilities`), whether the OTHER side is new enough to
66
+ * participate in targeting at all -- the same N/N-1-safe shape as every
67
+ * other flag here, just consumed for observability instead of gating.
68
+ */
69
+ export declare const CAPABILITY_FLAGS: readonly ['steer', 'blob-upload', 'interactive-approval', 'approval_resolved', 'approval-targeting'];
70
+ export type CapabilityFlag = (typeof CAPABILITY_FLAGS)[number];
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@byok-sdk/protocol",
3
+ "version": "0.1.0",
4
+ "description": "BYOK SDK wire protocol: envelope schema, message types, and codec helpers",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Ancienttwo/byok-sdk.git",
10
+ "directory": "packages/protocol"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/Ancienttwo/byok-sdk/issues"
14
+ },
15
+ "homepage": "https://github.com/Ancienttwo/byok-sdk#readme",
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "sideEffects": false,
20
+ "main": "./dist/index.js",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "dependencies": {
39
+ "zod": "^4.4.3"
40
+ },
41
+ "scripts": {
42
+ "build": "tsup && tsc -p tsconfig.build.json",
43
+ "dev": "tsup --watch",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest",
46
+ "typecheck": "tsc --noEmit",
47
+ "clean": "rm -rf dist"
48
+ }
49
+ }