@kangentic/protocol 0.15.0 → 0.17.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/events/event.d.ts +57 -6
- package/dist/events/payloads.d.ts +104 -0
- package/dist/index.cjs +35 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +34 -6
- package/dist/wire/payloads.d.ts +35 -3
- package/package.json +1 -1
package/dist/events/event.d.ts
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* event before its router dispatches on `kind`.
|
|
28
28
|
*/
|
|
29
29
|
import type { JsonValue } from '../wire/messages';
|
|
30
|
+
import { type ReadStreamSessionStatusWire } from '../wire/payloads';
|
|
30
31
|
import { type ActivityReasonWire, type ActivityStateWire, type SessionEventWire, type SessionUsageWire, type TerminalDimensionsWire, type TranscriptEventPayload } from './payloads';
|
|
31
32
|
export interface TranscriptEvent {
|
|
32
33
|
kind: 'transcript';
|
|
@@ -60,11 +61,25 @@ export type ActivityEventPayload = {
|
|
|
60
61
|
options?: string[];
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
63
|
-
* The
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
64
|
+
* The feed's last word: the desktop's read-stream subscription pushes this
|
|
65
|
+
* once, right before it tears itself down, so the phone learns "this
|
|
66
|
+
* session is over" from the feed itself instead of inferring it from
|
|
67
|
+
* silence. Sent when the streamed session's PTY exits. From 0.16.0 it is
|
|
68
|
+
* also sent when a paused row, which the feed holds without a PTY, is
|
|
69
|
+
* replaced or removed. `intentional` distinguishes a deliberate stop
|
|
70
|
+
* (desktop Stop button, suspend, shutdown, a resume replacing a paused row)
|
|
71
|
+
* from a crash.
|
|
72
|
+
*
|
|
73
|
+
* `successorSessionId`, when present, names the session that replaced this
|
|
74
|
+
* one for the same task. A desktop resume never reuses the paused session's
|
|
75
|
+
* id: it spawns a new session and drops the paused row, so a feed held on
|
|
76
|
+
* the paused session can only end, never turn back into `running`. The task
|
|
77
|
+
* continues as the successor; subscribe to it. Its snapshot carries
|
|
78
|
+
* `sessionStatus` (`queued` when the desktop's concurrency limit holds it)
|
|
79
|
+
* and `resuming: true`. Absent when no successor exists (a removed paused
|
|
80
|
+
* row, or a successor whose own spawn failed), and from pre-0.16.0
|
|
81
|
+
* desktops, which never end a feed this way and leave it silent instead. An
|
|
82
|
+
* older phone ignores the field and still sees an ended session.
|
|
68
83
|
*
|
|
69
84
|
* `spawnProgressLabel`, when present, means the desktop had a respawn in
|
|
70
85
|
* flight for this task when the session ended - a same-column respawn
|
|
@@ -81,7 +96,8 @@ export type ActivityEventPayload = {
|
|
|
81
96
|
* the desktop's own display text for what it is doing ("Switching
|
|
82
97
|
* model...", "Starting agent...", a live git-queue wait count) - treat it
|
|
83
98
|
* as untrusted display text, not a key to switch on, cap its length, and
|
|
84
|
-
* fall back to generic copy rather than parsing it.
|
|
99
|
+
* fall back to generic copy rather than parsing it. From 0.16.0 the desktop
|
|
100
|
+
* sends it sanitized as `BoardTaskWire.spawn_progress` describes.
|
|
85
101
|
*
|
|
86
102
|
* INTENT, not a guarantee, on the same terms `BoardColumnWire.spawns_session`
|
|
87
103
|
* documents: the desktop can suspend without a successor ever landing (a
|
|
@@ -107,6 +123,41 @@ export type ActivityEventPayload = {
|
|
|
107
123
|
type: 'session-ended';
|
|
108
124
|
intentional: boolean;
|
|
109
125
|
spawnProgressLabel?: string;
|
|
126
|
+
successorSessionId?: string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The streamed session's lifecycle status changed after the subscribe
|
|
130
|
+
* snapshot: `queued` -> `running` when the desktop's concurrency limit
|
|
131
|
+
* frees a slot, -> `suspended` on a pause or a respawn, -> `exited` when the
|
|
132
|
+
* desktop retires a session whose agent already ended. Sent only on an
|
|
133
|
+
* actual change, for the same session id only. A resume is a NEW session
|
|
134
|
+
* id, so suspended -> running never arrives here: see `session-ended`'s
|
|
135
|
+
* `successorSessionId`.
|
|
136
|
+
*
|
|
137
|
+
* A `suspended` push precedes the PTY's `session-ended` when a running
|
|
138
|
+
* session is suspended, so for an instant both say the session is parked.
|
|
139
|
+
*
|
|
140
|
+
* Precedence, matching the desktop card: a non-null `spawn_progress` on the
|
|
141
|
+
* task's board row overrides `suspended` (the desktop suspends a session
|
|
142
|
+
* before every model, agent or effort respawn), and never `running` or
|
|
143
|
+
* `queued`. Read `suspended` alone as "Paused"; read it next to a label as
|
|
144
|
+
* that label.
|
|
145
|
+
*
|
|
146
|
+
* `resuming` and `resumable` repeat the snapshot fields so the payload is
|
|
147
|
+
* complete on its own. The desktop re-reads all three on each lifecycle
|
|
148
|
+
* edge of this session and sends the payload when any of them differs. A
|
|
149
|
+
* move to Done or an archive of a paused task is no edge of its row, so
|
|
150
|
+
* `resumable` here can stay true until the feed ends; gate Resume on
|
|
151
|
+
* `BoardTaskWire.resumable`, which the board event for that move refreshes.
|
|
152
|
+
* Absent from pre-0.16.0 desktops, whose phones drop the unknown
|
|
153
|
+
* type through `isBridgeEvent` and keep inferring status from activity
|
|
154
|
+
* events.
|
|
155
|
+
*/
|
|
156
|
+
| {
|
|
157
|
+
type: 'status';
|
|
158
|
+
status: ReadStreamSessionStatusWire;
|
|
159
|
+
resuming: boolean;
|
|
160
|
+
resumable: boolean;
|
|
110
161
|
}
|
|
111
162
|
/**
|
|
112
163
|
* The agent's most recent assistant message, already collapsed to a short
|
|
@@ -160,6 +160,12 @@ export interface SessionUsageWire {
|
|
|
160
160
|
totalCostUsd: number;
|
|
161
161
|
totalDurationMs: number;
|
|
162
162
|
};
|
|
163
|
+
/**
|
|
164
|
+
* Completed tool calls across every run of the session. A resume (an app
|
|
165
|
+
* restart, a pause and resume) is a new run, and the desktop adds the
|
|
166
|
+
* session's earlier runs to the live count. Optional: an older desktop
|
|
167
|
+
* omits it.
|
|
168
|
+
*/
|
|
163
169
|
toolCallCount?: number;
|
|
164
170
|
model: {
|
|
165
171
|
id: string;
|
|
@@ -325,6 +331,104 @@ export interface BoardTaskWire {
|
|
|
325
331
|
archived_at: string | null;
|
|
326
332
|
created_at: string;
|
|
327
333
|
updated_at: string;
|
|
334
|
+
/**
|
|
335
|
+
* The task's in-flight spawn-progress label, the text of the desktop card's
|
|
336
|
+
* preparing footer: "Creating worktree...", "Fetching latest...",
|
|
337
|
+
* "Waiting (2 ahead)", "Switching model...", "Starting agent...", a git-queue
|
|
338
|
+
* wait with its elapsed time, and any staleness note the desktop appends
|
|
339
|
+
* ("Starting agent... (base 3 behind)"). The desktop announces every change
|
|
340
|
+
* with a `task-updated` board event, coalesced to at most about one a
|
|
341
|
+
* second per task.
|
|
342
|
+
*
|
|
343
|
+
* Untrusted display text, the same terms as `session-ended`'s
|
|
344
|
+
* `spawnProgressLabel`: never parse it or switch on it, and fall back to
|
|
345
|
+
* generic copy. It can carry text the desktop did not write: a column
|
|
346
|
+
* automation's name, in `Running "<name>"...`, from a config a team may
|
|
347
|
+
* share. From 0.16.0 the desktop strips escape sequences, control characters,
|
|
348
|
+
* invisible format characters (bidi controls, the zero-width space, the soft
|
|
349
|
+
* hyphen, the byte order mark) and tag characters outside the England,
|
|
350
|
+
* Scotland and Wales flag emoji, turns line breaks and tabs into spaces, and
|
|
351
|
+
* caps it at
|
|
352
|
+
* 120 characters ending in "...", but a client still caps it for its own
|
|
353
|
+
* layout.
|
|
354
|
+
*
|
|
355
|
+
* Precedence, matching the desktop card: a label overrides a `suspended`
|
|
356
|
+
* session status (a respawn is in flight behind a session that was just
|
|
357
|
+
* suspended for it), and never a `running` or `queued` one. A client that
|
|
358
|
+
* reads the read-stream feed's `status: 'suspended'` during a model or
|
|
359
|
+
* effort switch shows this label, not "Paused".
|
|
360
|
+
*
|
|
361
|
+
* A spawn path that dies without clearing its label leaves it in place for
|
|
362
|
+
* up to the desktop's 120s spawn-progress TTL, and the TTL expiry sends no
|
|
363
|
+
* board event, so a stale label can outlive its spawn until the next
|
|
364
|
+
* snapshot. Null or absent means no spawn is in flight, or a desktop that
|
|
365
|
+
* predates the field (pre-0.16.0). Under `view: 'sessions'` a task with a
|
|
366
|
+
* label is kept even while its `session_id` is null, so a first start and
|
|
367
|
+
* a respawn's gap stay visible.
|
|
368
|
+
*
|
|
369
|
+
* Declared OPTIONAL (`?`) like `pr_merge_readiness`; `parseBoardTaskWire`
|
|
370
|
+
* populates the key from every real wire response.
|
|
371
|
+
*/
|
|
372
|
+
spawn_progress?: string | null;
|
|
373
|
+
/**
|
|
374
|
+
* True when the task's session is paused and the desktop's own task view
|
|
375
|
+
* would offer Resume for it: the task is not in To Do or Done and not
|
|
376
|
+
* archived. It is the desktop's promise that `start-session` for this task
|
|
377
|
+
* resumes the paused session exactly as its Resume button does: the same
|
|
378
|
+
* conversation, the column's model, effort and permission, a "Resuming
|
|
379
|
+
* session..." label, and NO enter automations or column message. Gate a
|
|
380
|
+
* phone's Resume control on THIS field.
|
|
381
|
+
*
|
|
382
|
+
* On the board row because the phone always holds the board: a desktop
|
|
383
|
+
* pause clears the task's `session_id`, so a phone keeps no read-stream on
|
|
384
|
+
* a paused session (the stream's `resumable` covers only an open session
|
|
385
|
+
* screen through the suspend itself). A pause, a resume, a column move and
|
|
386
|
+
* an archive each reach the phone as a `task-updated` board event, after
|
|
387
|
+
* which the next snapshot carries the new value. Under `view: 'sessions'` a
|
|
388
|
+
* resumable task is kept even though its `session_id` is null, so an agent
|
|
389
|
+
* feed can draw it as Paused with a Resume control.
|
|
390
|
+
*
|
|
391
|
+
* Explicit on purpose, never inferred from another field: a pre-0.16.0
|
|
392
|
+
* desktop answers `start-session` by STARTING the column, which re-runs its
|
|
393
|
+
* enter automations and resends its message (a paused task in Code Review
|
|
394
|
+
* would get `/code-review` again). Null or absent means such a desktop; a
|
|
395
|
+
* client offers no Resume then. While `spawn_progress` is non-null a spawn
|
|
396
|
+
* is already in flight and the desktop card shows that label instead of a
|
|
397
|
+
* Resume control; a client applies the same precedence. `start-session`
|
|
398
|
+
* re-checks the column and refuses a To Do, Done or archived task with the
|
|
399
|
+
* desktop's Resume copy, so a value overtaken by a move is safe to act on.
|
|
400
|
+
* A paused task that offers no Resume reads false here. From 0.17.0 `paused`
|
|
401
|
+
* tells it apart from a task with no paused session.
|
|
402
|
+
*
|
|
403
|
+
* Declared OPTIONAL (`?`) like `spawns_session`; `parseBoardTaskWire`
|
|
404
|
+
* populates the key from every real wire response.
|
|
405
|
+
*/
|
|
406
|
+
resumable?: boolean | null;
|
|
407
|
+
/**
|
|
408
|
+
* True when the task has a paused session (a `suspended` session that is not
|
|
409
|
+
* a Command Terminal) and no live one. No column or archive gate applies. It
|
|
410
|
+
* is the fact the desktop card's "Paused" footer reads, so a paused task in
|
|
411
|
+
* Done or on the archived page reads true here while `resumable` reads
|
|
412
|
+
* false. A move into To Do removes the task's session rows, so a task there
|
|
413
|
+
* normally reads false. Gate Resume on `resumable` only; `resumable: true`
|
|
414
|
+
* always comes with `paused: true`.
|
|
415
|
+
*
|
|
416
|
+
* A desktop pause clears the task's `session_id`, so find a paused session
|
|
417
|
+
* from this field, not from `session_id`. A respawn queued behind the
|
|
418
|
+
* desktop's concurrency limit leaves the task holding a paused session and a
|
|
419
|
+
* queued one; that task reads false, as the desktop card shows the queued
|
|
420
|
+
* session. While `spawn_progress` is non-null the desktop card shows that
|
|
421
|
+
* label instead of "Paused"; a client applies the same precedence.
|
|
422
|
+
*
|
|
423
|
+
* Under `view: 'sessions'` a paused task that offers no Resume stays out of
|
|
424
|
+
* the snapshot, because an agent feed has nothing to act on for it. Null or
|
|
425
|
+
* absent means a desktop that predates the field (pre-0.17.0); a client
|
|
426
|
+
* then reads a paused task from `resumable` alone, as before.
|
|
427
|
+
*
|
|
428
|
+
* Declared OPTIONAL (`?`) like `resumable`; `parseBoardTaskWire` populates
|
|
429
|
+
* the key from every real wire response.
|
|
430
|
+
*/
|
|
431
|
+
paused?: boolean | null;
|
|
328
432
|
}
|
|
329
433
|
/** Phone-needed subset of the desktop's BacklogTask row. */
|
|
330
434
|
export interface BacklogItemWire {
|
package/dist/index.cjs
CHANGED
|
@@ -75,6 +75,7 @@ __export(index_exports, {
|
|
|
75
75
|
isDoneRole: () => isDoneRole,
|
|
76
76
|
isJsonValue: () => isJsonValue,
|
|
77
77
|
isPushCategory: () => isPushCategory,
|
|
78
|
+
isReadStreamSessionStatusWire: () => isReadStreamSessionStatusWire,
|
|
78
79
|
isRecord: () => isRecord,
|
|
79
80
|
isRosterEntryExpired: () => isRosterEntryExpired,
|
|
80
81
|
isSecureRelayAddress: () => isSecureRelayAddress,
|
|
@@ -5462,7 +5463,10 @@ function parseBoardTaskWire(value) {
|
|
|
5462
5463
|
attachment_count: typeof value.attachment_count === "number" ? value.attachment_count : 0,
|
|
5463
5464
|
archived_at: nullableString(value, "archived_at"),
|
|
5464
5465
|
created_at: requireString(value, "created_at", "board task"),
|
|
5465
|
-
updated_at: requireString(value, "updated_at", "board task")
|
|
5466
|
+
updated_at: requireString(value, "updated_at", "board task"),
|
|
5467
|
+
spawn_progress: nullableString(value, "spawn_progress"),
|
|
5468
|
+
resumable: nullableBoolean(value, "resumable"),
|
|
5469
|
+
paused: nullableBoolean(value, "paused")
|
|
5466
5470
|
};
|
|
5467
5471
|
}
|
|
5468
5472
|
function parseBacklogItemWire(value) {
|
|
@@ -5514,6 +5518,9 @@ function parseDiffFileContentWire(payload) {
|
|
|
5514
5518
|
|
|
5515
5519
|
// src/wire/payloads.ts
|
|
5516
5520
|
var READ_STREAM_SESSION_STATUSES = ["running", "queued", "suspended", "exited"];
|
|
5521
|
+
function isReadStreamSessionStatusWire(value) {
|
|
5522
|
+
return typeof value === "string" && READ_STREAM_SESSION_STATUSES.includes(value);
|
|
5523
|
+
}
|
|
5517
5524
|
function parseReadStreamResponsePayload(payload) {
|
|
5518
5525
|
if (!isRecord(payload)) throw new Error("read-stream response must be an object");
|
|
5519
5526
|
if (typeof payload.scrollback !== "string") throw new Error('read-stream response is missing "scrollback"');
|
|
@@ -5544,11 +5551,19 @@ function parseReadStreamResponsePayload(payload) {
|
|
|
5544
5551
|
response.ptyDimensions = parseTerminalDimensionsWire(payload.ptyDimensions);
|
|
5545
5552
|
}
|
|
5546
5553
|
if (payload.sessionStatus !== void 0) {
|
|
5547
|
-
if (
|
|
5554
|
+
if (!isReadStreamSessionStatusWire(payload.sessionStatus)) {
|
|
5548
5555
|
throw new Error('read-stream response has an invalid "sessionStatus"');
|
|
5549
5556
|
}
|
|
5550
5557
|
response.sessionStatus = payload.sessionStatus;
|
|
5551
5558
|
}
|
|
5559
|
+
if (payload.resuming !== void 0) {
|
|
5560
|
+
if (typeof payload.resuming !== "boolean") throw new Error('read-stream response has an invalid "resuming"');
|
|
5561
|
+
response.resuming = payload.resuming;
|
|
5562
|
+
}
|
|
5563
|
+
if (payload.resumable !== void 0) {
|
|
5564
|
+
if (typeof payload.resumable !== "boolean") throw new Error('read-stream response has an invalid "resumable"');
|
|
5565
|
+
response.resumable = payload.resumable;
|
|
5566
|
+
}
|
|
5552
5567
|
return response;
|
|
5553
5568
|
}
|
|
5554
5569
|
function parseTranscriptWindowResponsePayload(payload) {
|
|
@@ -6116,11 +6131,24 @@ function parseActivityEventPayload(payload) {
|
|
|
6116
6131
|
}
|
|
6117
6132
|
case "session-ended": {
|
|
6118
6133
|
if (typeof payload.intentional !== "boolean") throw new Error('session-ended payload is missing "intentional"');
|
|
6119
|
-
if (payload.spawnProgressLabel
|
|
6120
|
-
|
|
6134
|
+
if (payload.spawnProgressLabel !== void 0 && typeof payload.spawnProgressLabel !== "string") {
|
|
6135
|
+
throw new Error('session-ended payload has an invalid "spawnProgressLabel"');
|
|
6136
|
+
}
|
|
6137
|
+
if (payload.successorSessionId !== void 0 && typeof payload.successorSessionId !== "string") {
|
|
6138
|
+
throw new Error('session-ended payload has an invalid "successorSessionId"');
|
|
6121
6139
|
}
|
|
6122
|
-
|
|
6123
|
-
|
|
6140
|
+
return {
|
|
6141
|
+
type: "session-ended",
|
|
6142
|
+
intentional: payload.intentional,
|
|
6143
|
+
...payload.spawnProgressLabel !== void 0 ? { spawnProgressLabel: payload.spawnProgressLabel } : {},
|
|
6144
|
+
...payload.successorSessionId !== void 0 ? { successorSessionId: payload.successorSessionId } : {}
|
|
6145
|
+
};
|
|
6146
|
+
}
|
|
6147
|
+
case "status": {
|
|
6148
|
+
if (!isReadStreamSessionStatusWire(payload.status)) throw new Error('status payload has an invalid "status"');
|
|
6149
|
+
if (typeof payload.resuming !== "boolean") throw new Error('status payload is missing "resuming"');
|
|
6150
|
+
if (typeof payload.resumable !== "boolean") throw new Error('status payload is missing "resumable"');
|
|
6151
|
+
return { type: "status", status: payload.status, resuming: payload.resuming, resumable: payload.resumable };
|
|
6124
6152
|
}
|
|
6125
6153
|
case "message-preview": {
|
|
6126
6154
|
if (typeof payload.text !== "string") throw new Error('message-preview payload is missing "text"');
|
|
@@ -6235,6 +6263,7 @@ function isBridgeEvent(value) {
|
|
|
6235
6263
|
isDoneRole,
|
|
6236
6264
|
isJsonValue,
|
|
6237
6265
|
isPushCategory,
|
|
6266
|
+
isReadStreamSessionStatusWire,
|
|
6238
6267
|
isRecord,
|
|
6239
6268
|
isRosterEntryExpired,
|
|
6240
6269
|
isSecureRelayAddress,
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type { JsonValue, BridgeMessage, HeartbeatMessage, CapabilityRequestMessa
|
|
|
12
12
|
export { encodeMessage, decodeMessage, UnsupportedVerbError, isUnsupportedVerbError, UNSUPPORTED_VERB_ERROR_CODE, MAX_FRAME_LENGTH, MAX_DECODED_LENGTH, COMPRESSION_THRESHOLD, } from './wire/framing';
|
|
13
13
|
export { isJsonValue, isRecord } from './wire/json-value';
|
|
14
14
|
export { SessionFrameKind, wrapSessionFrame, unwrapSessionFrame } from './wire/session-frame';
|
|
15
|
-
export { parseCapabilityRequestPayload, parseReadStreamResponsePayload, parseReadBoardResponsePayload, parseReadDiffResponsePayload, parseTranscriptWindowResponsePayload, parseRegisterPushRequestPayload, parseStartSessionResponsePayload, type CapabilityRequestPayloadMap, type CapabilityResponsePayloadMap, type ReadStreamRequestPayload, type ReadStreamResponsePayload, type ReadStreamSessionStatusWire, type TranscriptWindowResponsePayload, type ReadBoardRequestPayload, type ReadBoardView, type ReadBoardProjectSummary, type ReadBoardProjectGroup, type ReadBoardProjectListResponsePayload, type ReadBoardSnapshotResponsePayload, type ReadBoardArchivedResponsePayload, type ReadBoardResponsePayload, type ReadDiffScope, type ReadDiffRequestPayload, type ReadDiffResponsePayload, type SendUserMessageRequestPayload, type SendUserMessageResponsePayload, type MoveTaskRequestPayload, type MoveTaskResponsePayload, type AnswerPermissionPromptRequestPayload, type AnswerPermissionPromptResponsePayload, type InteractiveTerminalRequestPayload, type InteractiveTerminalResponsePayload, type BoardToolRequestPayload, type BoardToolResponsePayload, type RegisterPushRequestPayload, type RegisterPushResponsePayload, type StartSessionRequestPayload, type StartSessionResponsePayload, type StartSessionOutcome, } from './wire/payloads';
|
|
15
|
+
export { parseCapabilityRequestPayload, parseReadStreamResponsePayload, parseReadBoardResponsePayload, parseReadDiffResponsePayload, parseTranscriptWindowResponsePayload, parseRegisterPushRequestPayload, parseStartSessionResponsePayload, isReadStreamSessionStatusWire, type CapabilityRequestPayloadMap, type CapabilityResponsePayloadMap, type ReadStreamRequestPayload, type ReadStreamResponsePayload, type ReadStreamSessionStatusWire, type TranscriptWindowResponsePayload, type ReadBoardRequestPayload, type ReadBoardView, type ReadBoardProjectSummary, type ReadBoardProjectGroup, type ReadBoardProjectListResponsePayload, type ReadBoardSnapshotResponsePayload, type ReadBoardArchivedResponsePayload, type ReadBoardResponsePayload, type ReadDiffScope, type ReadDiffRequestPayload, type ReadDiffResponsePayload, type SendUserMessageRequestPayload, type SendUserMessageResponsePayload, type MoveTaskRequestPayload, type MoveTaskResponsePayload, type AnswerPermissionPromptRequestPayload, type AnswerPermissionPromptResponsePayload, type InteractiveTerminalRequestPayload, type InteractiveTerminalResponsePayload, type BoardToolRequestPayload, type BoardToolResponsePayload, type RegisterPushRequestPayload, type RegisterPushResponsePayload, type StartSessionRequestPayload, type StartSessionResponsePayload, type StartSessionOutcome, } from './wire/payloads';
|
|
16
16
|
export { PUSH_CATEGORIES, isPushCategory, parsePushEnvelopePlaintext, sealPushEnvelope, openPushEnvelope, PUSH_ENVELOPE_MAX_AGE_MS, PUSH_ENVELOPE_MAX_FUTURE_SKEW_MS, type PushCategory, type PushEnvelopePlaintext, } from './crypto/push-envelope';
|
|
17
17
|
export { PAIRING_URI_SCHEME, encodePairingQrPayload, decodePairingQrPayload, type PairingQrPayload, } from './pairing/qr';
|
|
18
18
|
export { sealPairingConfirm, openPairingConfirm } from './pairing/confirm';
|
package/dist/index.mjs
CHANGED
|
@@ -5337,7 +5337,10 @@ function parseBoardTaskWire(value) {
|
|
|
5337
5337
|
attachment_count: typeof value.attachment_count === "number" ? value.attachment_count : 0,
|
|
5338
5338
|
archived_at: nullableString(value, "archived_at"),
|
|
5339
5339
|
created_at: requireString(value, "created_at", "board task"),
|
|
5340
|
-
updated_at: requireString(value, "updated_at", "board task")
|
|
5340
|
+
updated_at: requireString(value, "updated_at", "board task"),
|
|
5341
|
+
spawn_progress: nullableString(value, "spawn_progress"),
|
|
5342
|
+
resumable: nullableBoolean(value, "resumable"),
|
|
5343
|
+
paused: nullableBoolean(value, "paused")
|
|
5341
5344
|
};
|
|
5342
5345
|
}
|
|
5343
5346
|
function parseBacklogItemWire(value) {
|
|
@@ -5389,6 +5392,9 @@ function parseDiffFileContentWire(payload) {
|
|
|
5389
5392
|
|
|
5390
5393
|
// src/wire/payloads.ts
|
|
5391
5394
|
var READ_STREAM_SESSION_STATUSES = ["running", "queued", "suspended", "exited"];
|
|
5395
|
+
function isReadStreamSessionStatusWire(value) {
|
|
5396
|
+
return typeof value === "string" && READ_STREAM_SESSION_STATUSES.includes(value);
|
|
5397
|
+
}
|
|
5392
5398
|
function parseReadStreamResponsePayload(payload) {
|
|
5393
5399
|
if (!isRecord(payload)) throw new Error("read-stream response must be an object");
|
|
5394
5400
|
if (typeof payload.scrollback !== "string") throw new Error('read-stream response is missing "scrollback"');
|
|
@@ -5419,11 +5425,19 @@ function parseReadStreamResponsePayload(payload) {
|
|
|
5419
5425
|
response.ptyDimensions = parseTerminalDimensionsWire(payload.ptyDimensions);
|
|
5420
5426
|
}
|
|
5421
5427
|
if (payload.sessionStatus !== void 0) {
|
|
5422
|
-
if (
|
|
5428
|
+
if (!isReadStreamSessionStatusWire(payload.sessionStatus)) {
|
|
5423
5429
|
throw new Error('read-stream response has an invalid "sessionStatus"');
|
|
5424
5430
|
}
|
|
5425
5431
|
response.sessionStatus = payload.sessionStatus;
|
|
5426
5432
|
}
|
|
5433
|
+
if (payload.resuming !== void 0) {
|
|
5434
|
+
if (typeof payload.resuming !== "boolean") throw new Error('read-stream response has an invalid "resuming"');
|
|
5435
|
+
response.resuming = payload.resuming;
|
|
5436
|
+
}
|
|
5437
|
+
if (payload.resumable !== void 0) {
|
|
5438
|
+
if (typeof payload.resumable !== "boolean") throw new Error('read-stream response has an invalid "resumable"');
|
|
5439
|
+
response.resumable = payload.resumable;
|
|
5440
|
+
}
|
|
5427
5441
|
return response;
|
|
5428
5442
|
}
|
|
5429
5443
|
function parseTranscriptWindowResponsePayload(payload) {
|
|
@@ -5991,11 +6005,24 @@ function parseActivityEventPayload(payload) {
|
|
|
5991
6005
|
}
|
|
5992
6006
|
case "session-ended": {
|
|
5993
6007
|
if (typeof payload.intentional !== "boolean") throw new Error('session-ended payload is missing "intentional"');
|
|
5994
|
-
if (payload.spawnProgressLabel
|
|
5995
|
-
|
|
6008
|
+
if (payload.spawnProgressLabel !== void 0 && typeof payload.spawnProgressLabel !== "string") {
|
|
6009
|
+
throw new Error('session-ended payload has an invalid "spawnProgressLabel"');
|
|
6010
|
+
}
|
|
6011
|
+
if (payload.successorSessionId !== void 0 && typeof payload.successorSessionId !== "string") {
|
|
6012
|
+
throw new Error('session-ended payload has an invalid "successorSessionId"');
|
|
5996
6013
|
}
|
|
5997
|
-
|
|
5998
|
-
|
|
6014
|
+
return {
|
|
6015
|
+
type: "session-ended",
|
|
6016
|
+
intentional: payload.intentional,
|
|
6017
|
+
...payload.spawnProgressLabel !== void 0 ? { spawnProgressLabel: payload.spawnProgressLabel } : {},
|
|
6018
|
+
...payload.successorSessionId !== void 0 ? { successorSessionId: payload.successorSessionId } : {}
|
|
6019
|
+
};
|
|
6020
|
+
}
|
|
6021
|
+
case "status": {
|
|
6022
|
+
if (!isReadStreamSessionStatusWire(payload.status)) throw new Error('status payload has an invalid "status"');
|
|
6023
|
+
if (typeof payload.resuming !== "boolean") throw new Error('status payload is missing "resuming"');
|
|
6024
|
+
if (typeof payload.resumable !== "boolean") throw new Error('status payload is missing "resumable"');
|
|
6025
|
+
return { type: "status", status: payload.status, resuming: payload.resuming, resumable: payload.resumable };
|
|
5999
6026
|
}
|
|
6000
6027
|
case "message-preview": {
|
|
6001
6028
|
if (typeof payload.text !== "string") throw new Error('message-preview payload is missing "text"');
|
|
@@ -6109,6 +6136,7 @@ export {
|
|
|
6109
6136
|
isDoneRole,
|
|
6110
6137
|
isJsonValue,
|
|
6111
6138
|
isPushCategory,
|
|
6139
|
+
isReadStreamSessionStatusWire,
|
|
6112
6140
|
isRecord,
|
|
6113
6141
|
isRosterEntryExpired,
|
|
6114
6142
|
isSecureRelayAddress,
|
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,12 @@ 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.
|
|
139
|
+
* A paused task that offers no Resume (in Done, say) stays out even though
|
|
140
|
+
* its row reads `paused: true` from 0.17.0 (see `BoardTaskWire.paused`).
|
|
109
141
|
* - 'full': every non-archived task, for the one project whose board the user
|
|
110
142
|
* actually has open.
|
|
111
143
|
*
|
|
@@ -195,7 +227,7 @@ export interface ReadBoardProjectListResponsePayload {
|
|
|
195
227
|
export interface ReadBoardSnapshotResponsePayload {
|
|
196
228
|
projectId: string;
|
|
197
229
|
columns: BoardColumnWire[];
|
|
198
|
-
/** Non-archived tasks; filtered
|
|
230
|
+
/** Non-archived tasks; filtered when `view` is 'sessions' (see ReadBoardView for which tasks it keeps). */
|
|
199
231
|
tasks: BoardTaskWire[];
|
|
200
232
|
/** Absent whenever the request carried a `view` (protocol 0.9.0) - no phone has ever rendered it. */
|
|
201
233
|
backlog?: BacklogItemWire[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kangentic/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|