@kangentic/protocol 0.14.0 → 0.16.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.
- package/dist/capabilities/verbs.d.ts +10 -1
- package/dist/events/event.d.ts +61 -7
- package/dist/events/payloads.d.ts +77 -0
- package/dist/index.cjs +1227 -1186
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +1222 -1186
- package/dist/wire/framing.d.ts +30 -1
- package/dist/wire/messages.d.ts +15 -0
- package/dist/wire/payloads.d.ts +67 -3
- package/package.json +1 -1
package/dist/wire/framing.d.ts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import type { BridgeMessage } from './messages';
|
|
1
|
+
import type { BridgeMessage, CapabilityErrorCode } from './messages';
|
|
2
2
|
export declare const MAX_FRAME_LENGTH: number;
|
|
3
3
|
/** Upper bound on the JSON a compressed frame may declare/inflate to. */
|
|
4
4
|
export declare const MAX_DECODED_LENGTH: number;
|
|
5
5
|
/** JSON below this size ships raw - deflate overhead is not worth it. */
|
|
6
6
|
export declare const COMPRESSION_THRESHOLD: number;
|
|
7
|
+
/** The `code` a desktop puts on its refusal of a verb its build does not know. */
|
|
8
|
+
export declare const UNSUPPORTED_VERB_ERROR_CODE: CapabilityErrorCode;
|
|
9
|
+
/**
|
|
10
|
+
* Thrown by `decodeMessage` for a capability-request whose envelope is
|
|
11
|
+
* well-formed (a string `requestId`, a string `verb`, a JSON `payload`) but
|
|
12
|
+
* whose verb this build's `CAPABILITY_VERBS` does not carry. It is the one
|
|
13
|
+
* decode failure that names its request, so the receiver can ANSWER it
|
|
14
|
+
* (`{ ok: false, code: 'unsupported-verb' }`) instead of dropping the frame.
|
|
15
|
+
*
|
|
16
|
+
* Without this a desktop that predates a verb sent nothing back at all, and
|
|
17
|
+
* the phone that sent it timed out, unable to tell an old desktop from an
|
|
18
|
+
* unreachable one. Every verb added to the protocol has that failure mode on
|
|
19
|
+
* a mixed-version pair. A frame that is NOT a well-formed request (a missing
|
|
20
|
+
* `requestId`, a non-string `verb`, a non-JSON `payload`) still throws a
|
|
21
|
+
* plain `Error` and stays a silent rejection: there is nothing to answer, and
|
|
22
|
+
* answering unstructured input would hand a probe to whoever sent it.
|
|
23
|
+
*/
|
|
24
|
+
export declare class UnsupportedVerbError extends Error {
|
|
25
|
+
readonly requestId: string;
|
|
26
|
+
readonly verb: string;
|
|
27
|
+
constructor(requestId: string, verb: string);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Keyed on `name`, not `instanceof`: the desktop consumes this package from
|
|
31
|
+
* source through a workspace alias while the mobile app resolves the published
|
|
32
|
+
* `dist`, and a bundler can hold two copies of the class, which `instanceof`
|
|
33
|
+
* would tell apart.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isUnsupportedVerbError(error: unknown): error is UnsupportedVerbError;
|
|
7
36
|
export declare function encodeMessage(message: BridgeMessage): Uint8Array;
|
|
8
37
|
export declare function decodeMessage(bytes: Uint8Array): BridgeMessage;
|
package/dist/wire/messages.d.ts
CHANGED
|
@@ -30,6 +30,15 @@ export interface CapabilityRequestMessage {
|
|
|
30
30
|
verb: CapabilityVerb;
|
|
31
31
|
payload: JsonValue;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* A stable key a client can branch on, beside the human-readable `error`.
|
|
35
|
+
* Today the one member is the refusal a desktop sends for a verb its build
|
|
36
|
+
* does not know (see `UnsupportedVerbError` in framing.ts), so a newer phone
|
|
37
|
+
* can tell "desktop is old" from "desktop is unreachable" and show the right
|
|
38
|
+
* copy. A union rather than a bare string so a second code is a deliberate
|
|
39
|
+
* addition here, not a typo at a call site.
|
|
40
|
+
*/
|
|
41
|
+
export type CapabilityErrorCode = 'unsupported-verb';
|
|
33
42
|
export interface CapabilityResponseMessage {
|
|
34
43
|
type: 'capability-response';
|
|
35
44
|
requestId: string;
|
|
@@ -37,6 +46,12 @@ export interface CapabilityResponseMessage {
|
|
|
37
46
|
payload?: JsonValue;
|
|
38
47
|
/** Present only when ok is false. */
|
|
39
48
|
error?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Present only when ok is false AND the refusal has a stable key. Additive
|
|
51
|
+
* on the wire: a peer whose decoder predates the field rebuilds the message
|
|
52
|
+
* from the keys it knows and keeps `error`, so the text still shows.
|
|
53
|
+
*/
|
|
54
|
+
code?: CapabilityErrorCode;
|
|
40
55
|
}
|
|
41
56
|
export interface EventMessage {
|
|
42
57
|
type: 'event';
|
package/dist/wire/payloads.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface ReadStreamRequestPayload {
|
|
|
53
53
|
* snapshot races the session's teardown.
|
|
54
54
|
*/
|
|
55
55
|
export type ReadStreamSessionStatusWire = 'running' | 'queued' | 'suspended' | 'exited';
|
|
56
|
+
/** True for a value that is one of the four session lifecycle statuses. */
|
|
57
|
+
export declare function isReadStreamSessionStatusWire(value: unknown): value is ReadStreamSessionStatusWire;
|
|
56
58
|
/** Initial snapshot returned on subscribe; live updates arrive as TerminalEvent/ActivityEvent/TranscriptEvent. */
|
|
57
59
|
export interface ReadStreamResponsePayload {
|
|
58
60
|
scrollback: string;
|
|
@@ -80,9 +82,34 @@ export interface ReadStreamResponsePayload {
|
|
|
80
82
|
ptyDimensions?: TerminalDimensionsWire;
|
|
81
83
|
/**
|
|
82
84
|
* The session's lifecycle status at snapshot time. Absent from
|
|
83
|
-
* pre-0.5.0 desktops; the phone then assumes 'running'.
|
|
85
|
+
* pre-0.5.0 desktops; the phone then assumes 'running'. Later changes for
|
|
86
|
+
* the same session id arrive as the `status` activity payload.
|
|
84
87
|
*/
|
|
85
88
|
sessionStatus?: ReadStreamSessionStatusWire;
|
|
89
|
+
/**
|
|
90
|
+
* True when the desktop spawned this session as a resume of an earlier
|
|
91
|
+
* conversation. The desktop card then reads "Resuming agent..." where it
|
|
92
|
+
* would read "Starting agent...", for as long as a running session has no
|
|
93
|
+
* model name yet. Fixed for the life of a session id. Absent from pre-0.16.0
|
|
94
|
+
* desktops; read absent as false.
|
|
95
|
+
*/
|
|
96
|
+
resuming?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The streamed session's copy of `BoardTaskWire.resumable`: true when this
|
|
99
|
+
* session is paused (`suspended`) and the desktop's own task view would
|
|
100
|
+
* offer Resume for it. Gate a Resume control on the BOARD ROW's field, not
|
|
101
|
+
* this one: a desktop pause clears the task's `session_id`, so a phone holds
|
|
102
|
+
* no stream on a paused session once its board refreshes. This copy keeps a
|
|
103
|
+
* session screen that is open during the suspend current until then, and it
|
|
104
|
+
* carries the same promise: `start-session` resumes rather than starts.
|
|
105
|
+
*
|
|
106
|
+
* Recomputed on each lifecycle edge of this session, and a `status` push
|
|
107
|
+
* carries it even when it is the only value that changed. A move to Done or
|
|
108
|
+
* an archive of a paused task is no edge of its row, so this copy can stay
|
|
109
|
+
* true after the board row's turns false. Absent from pre-0.16.0 desktops,
|
|
110
|
+
* which a client reads as "offer no Resume".
|
|
111
|
+
*/
|
|
112
|
+
resumable?: boolean;
|
|
86
113
|
}
|
|
87
114
|
/** Phone-side narrowing of a read-stream subscribe response. Throws on a malformed required field. */
|
|
88
115
|
export declare function parseReadStreamResponsePayload(payload: JsonValue): ReadStreamResponsePayload;
|
|
@@ -105,7 +132,10 @@ export declare function parseTranscriptWindowResponsePayload(payload: JsonValue)
|
|
|
105
132
|
* - 'sessions': columns, per-column task counts, and only the non-archived
|
|
106
133
|
* tasks that carry a `session_id`. This is what an agent feed renders: it
|
|
107
134
|
* watches every project at once but only ever draws the handful of tasks
|
|
108
|
-
* with a live agent on them.
|
|
135
|
+
* with a live agent on them. From 0.16.0 it also keeps a task with a
|
|
136
|
+
* `spawn_progress` label in flight (a first start, or a respawn that has
|
|
137
|
+
* nulled `session_id`) and a `resumable` paused task (a desktop pause
|
|
138
|
+
* nulls `session_id` too), so the feed can draw "Paused" with a Resume.
|
|
109
139
|
* - 'full': every non-archived task, for the one project whose board the user
|
|
110
140
|
* actually has open.
|
|
111
141
|
*
|
|
@@ -195,7 +225,7 @@ export interface ReadBoardProjectListResponsePayload {
|
|
|
195
225
|
export interface ReadBoardSnapshotResponsePayload {
|
|
196
226
|
projectId: string;
|
|
197
227
|
columns: BoardColumnWire[];
|
|
198
|
-
/** Non-archived tasks; filtered
|
|
228
|
+
/** Non-archived tasks; filtered when `view` is 'sessions' (see ReadBoardView for which tasks it keeps). */
|
|
199
229
|
tasks: BoardTaskWire[];
|
|
200
230
|
/** Absent whenever the request carried a `view` (protocol 0.9.0) - no phone has ever rendered it. */
|
|
201
231
|
backlog?: BacklogItemWire[];
|
|
@@ -278,6 +308,38 @@ export interface MoveTaskRequestPayload {
|
|
|
278
308
|
export interface MoveTaskResponsePayload {
|
|
279
309
|
ok: boolean;
|
|
280
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Keyed by task, not by session: the state this verb exists for is a task
|
|
313
|
+
* whose session has ENDED, so there is no live session id to name. The
|
|
314
|
+
* desktop spawns or resumes in the task's current column with that column's
|
|
315
|
+
* settings; a live session is an idempotent no-op.
|
|
316
|
+
*/
|
|
317
|
+
export interface StartSessionRequestPayload {
|
|
318
|
+
taskId: string;
|
|
319
|
+
projectId: string;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* What the desktop did with an accepted start. `starting`: the worktree and
|
|
323
|
+
* PTY work run behind this response and the successor's arrival reaches the
|
|
324
|
+
* phone as a board / stream event, the same way a column move's does. `live`:
|
|
325
|
+
* the task already had a running session, nothing was spawned, and NO event
|
|
326
|
+
* is coming, so a phone that tapped Start from a stale screen must refresh
|
|
327
|
+
* its board and stream itself rather than wait.
|
|
328
|
+
*/
|
|
329
|
+
export type StartSessionOutcome = 'starting' | 'live';
|
|
330
|
+
/**
|
|
331
|
+
* `ok` means the start was ACCEPTED (the task exists, is in a column that may
|
|
332
|
+
* run an agent, and either has no live session or already has one), not that
|
|
333
|
+
* a successor is up: the desktop answers before the worktree and PTY work so
|
|
334
|
+
* the response fits the phone's per-verb budget. `outcome` says which of the
|
|
335
|
+
* two accepted shapes this was.
|
|
336
|
+
*/
|
|
337
|
+
export interface StartSessionResponsePayload {
|
|
338
|
+
ok: boolean;
|
|
339
|
+
outcome: StartSessionOutcome;
|
|
340
|
+
}
|
|
341
|
+
/** Phone-side guard: narrows a decoded start-session response before the phone acts on it. */
|
|
342
|
+
export declare function parseStartSessionResponsePayload(payload: JsonValue): StartSessionResponsePayload;
|
|
281
343
|
export interface AnswerPermissionPromptRequestPayload {
|
|
282
344
|
sessionId: string;
|
|
283
345
|
/** The prompt id the phone believes is outstanding - the handler rejects a stale/replayed answer whose id no longer matches the live awaited prompt. */
|
|
@@ -363,6 +425,7 @@ export interface CapabilityRequestPayloadMap {
|
|
|
363
425
|
'board-tool-read': BoardToolRequestPayload;
|
|
364
426
|
'board-tool-write': BoardToolRequestPayload;
|
|
365
427
|
'register-push': RegisterPushRequestPayload;
|
|
428
|
+
'start-session': StartSessionRequestPayload;
|
|
366
429
|
}
|
|
367
430
|
export interface CapabilityResponsePayloadMap {
|
|
368
431
|
'read-stream': ReadStreamResponsePayload | TranscriptWindowResponsePayload;
|
|
@@ -375,6 +438,7 @@ export interface CapabilityResponsePayloadMap {
|
|
|
375
438
|
'board-tool-read': BoardToolResponsePayload;
|
|
376
439
|
'board-tool-write': BoardToolResponsePayload;
|
|
377
440
|
'register-push': RegisterPushResponsePayload;
|
|
441
|
+
'start-session': StartSessionResponsePayload;
|
|
378
442
|
}
|
|
379
443
|
/**
|
|
380
444
|
* Validates and narrows a decoded capability-request's generic JsonValue
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kangentic/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Kangentic mobile bridge wire protocol: framing, Noise-based pairing and session handshakes, signed device roster, and capability verbs shared between desktop and mobile.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|