@kangentic/protocol 0.17.0 → 0.19.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.
@@ -16,9 +16,15 @@
16
16
  * `start-session` is the second lifecycle verb beside `move-task`: it spawns
17
17
  * or resumes a task's session in the column the task is already in, so a
18
18
  * phone's "Session ended" state can bring the agent back without moving the
19
- * task. A live session is an idempotent no-op; nothing is ever stopped.
19
+ * task. A live session is an idempotent no-op; `start-session` never stops
20
+ * anything.
21
+ *
22
+ * `pause-session` is the third lifecycle verb: it suspends a task's live
23
+ * session exactly as the desktop's Pause button does, without moving the task,
24
+ * and keeps the conversation for a later `start-session` Resume. Gate it on
25
+ * `BoardTaskWire.pausable`.
20
26
  */
21
- export declare const CAPABILITY_VERBS: readonly ["read-stream", "read-board", "read-diff", "send-user-message", "move-task", "answer-permission-prompt", "interactive-terminal", "board-tool-read", "board-tool-write", "register-push", "start-session"];
27
+ export declare const CAPABILITY_VERBS: readonly ["read-stream", "read-board", "read-diff", "send-user-message", "move-task", "answer-permission-prompt", "interactive-terminal", "board-tool-read", "board-tool-write", "register-push", "start-session", "pause-session"];
22
28
  export type CapabilityVerb = (typeof CAPABILITY_VERBS)[number];
23
29
  export declare function isCapabilityVerb(value: string): value is CapabilityVerb;
24
30
  /** Deny-by-default: a verb absent from the set is not authorized. */
@@ -409,9 +409,14 @@ export interface BoardTaskWire {
409
409
  * a Command Terminal) and no live one. No column or archive gate applies. It
410
410
  * is the fact the desktop card's "Paused" footer reads, so a paused task in
411
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`.
412
+ * false. The desktop draws an archived task as a compact card with no
413
+ * footer, so a client that matches the desktop card draws "Paused" only
414
+ * when `archived_at` is null. A move from another column into Done
415
+ * archives the task in the same step, so only a task that sits in Done
416
+ * unarchived gets the full card and its footer. A move into To Do removes
417
+ * the task's session rows, so a task there normally reads false. Gate
418
+ * Resume on `resumable` only; `resumable: true` always comes with
419
+ * `paused: true`.
415
420
  *
416
421
  * A desktop pause clears the task's `session_id`, so find a paused session
417
422
  * from this field, not from `session_id`. A respawn queued behind the
@@ -429,6 +434,37 @@ export interface BoardTaskWire {
429
434
  * the key from every real wire response.
430
435
  */
431
436
  paused?: boolean | null;
437
+ /**
438
+ * True when the task has a live session (`running` or `queued`) and is not
439
+ * in To Do. It is the desktop's promise that `pause-session` for this task
440
+ * pauses that session exactly as its Pause button does: the conversation is
441
+ * kept for a later Resume, and the pause sticks, so a column with auto-spawn
442
+ * on does not start the task again by itself. Gate a phone's Pause control on
443
+ * THIS field.
444
+ *
445
+ * Done and archived do not clear it, matching the desktop, whose Pause stays
446
+ * available there because it is the only in-window stop for a live agent. A
447
+ * move into Done suspends the task's session in the same step, so a task
448
+ * there normally has no live session and reads false. It reads true only
449
+ * when that suspend failed and left the agent running.
450
+ *
451
+ * Never true while `paused` is true: a paused task has no live session. While
452
+ * `spawn_progress` is non-null and no session is live, the desktop card still
453
+ * shows a Pause control, but its click only cancels an in-flight resume or
454
+ * start, which `pause-session` does not promise. This field reads false
455
+ * then, and the verb refuses a task with no live session.
456
+ *
457
+ * A pause, a session's start and its end each reach the phone as a
458
+ * `task-updated` board event, after which the next snapshot carries the new
459
+ * value. Null or absent means a desktop that predates the field
460
+ * (pre-0.18.0), and a client offers no Pause then: a 0.15.0 to 0.17.0
461
+ * desktop refuses `pause-session` with the `unsupported-verb` code, and an
462
+ * older one does not answer it at all.
463
+ *
464
+ * Declared OPTIONAL (`?`) like `paused`; `parseBoardTaskWire` populates the
465
+ * key from every real wire response.
466
+ */
467
+ pausable?: boolean | null;
432
468
  }
433
469
  /** Phone-needed subset of the desktop's BacklogTask row. */
434
470
  export interface BacklogItemWire {
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  PUSH_CATEGORIES: () => PUSH_CATEGORIES,
40
40
  PUSH_ENVELOPE_MAX_AGE_MS: () => PUSH_ENVELOPE_MAX_AGE_MS,
41
41
  PUSH_ENVELOPE_MAX_FUTURE_SKEW_MS: () => PUSH_ENVELOPE_MAX_FUTURE_SKEW_MS,
42
+ RESPONSE_TOO_LARGE_ERROR_CODE: () => RESPONSE_TOO_LARGE_ERROR_CODE,
42
43
  SecretstreamState: () => SecretstreamState,
43
44
  SessionFrameKind: () => SessionFrameKind,
44
45
  UNSUPPORTED_VERB_ERROR_CODE: () => UNSUPPORTED_VERB_ERROR_CODE,
@@ -58,6 +59,7 @@ __export(index_exports, {
58
59
  deriveSessionSlotId: () => deriveSessionSlotId,
59
60
  deriveShortAuthenticationString: () => deriveShortAuthenticationString,
60
61
  encodeMessage: () => encodeMessage,
62
+ encodeMessageFailure: () => encodeMessageFailure,
61
63
  encodePairingQrPayload: () => encodePairingQrPayload,
62
64
  encodeProtocolVersion: () => encodeProtocolVersion,
63
65
  encodeRosterEntryForSigning: () => encodeRosterEntryForSigning,
@@ -90,6 +92,7 @@ __export(index_exports, {
90
92
  parseCapabilityRequestPayload: () => parseCapabilityRequestPayload,
91
93
  parseDiffFileContentWire: () => parseDiffFileContentWire,
92
94
  parseDiffFileListWire: () => parseDiffFileListWire,
95
+ parsePauseSessionResponsePayload: () => parsePauseSessionResponsePayload,
93
96
  parsePushEnvelopePlaintext: () => parsePushEnvelopePlaintext,
94
97
  parseReadBoardResponsePayload: () => parseReadBoardResponsePayload,
95
98
  parseReadDiffResponsePayload: () => parseReadDiffResponsePayload,
@@ -4971,7 +4974,8 @@ var CAPABILITY_VERBS = [
4971
4974
  "board-tool-read",
4972
4975
  "board-tool-write",
4973
4976
  "register-push",
4974
- "start-session"
4977
+ "start-session",
4978
+ "pause-session"
4975
4979
  ];
4976
4980
  function isCapabilityVerb(value) {
4977
4981
  return CAPABILITY_VERBS.includes(value);
@@ -5008,6 +5012,7 @@ var FRAME_FORMAT_DEFLATE = 1;
5008
5012
  var JSON_OPEN_BRACE = 123;
5009
5013
  var DEFLATE_HEADER_LENGTH = 5;
5010
5014
  var UNSUPPORTED_VERB_ERROR_CODE = "unsupported-verb";
5015
+ var RESPONSE_TOO_LARGE_ERROR_CODE = "response-too-large";
5011
5016
  var UNSUPPORTED_VERB_ERROR_NAME = "UnsupportedVerbError";
5012
5017
  var UnsupportedVerbError = class extends Error {
5013
5018
  requestId;
@@ -5044,6 +5049,15 @@ function encodeMessage(message) {
5044
5049
  }
5045
5050
  return frame;
5046
5051
  }
5052
+ function encodeMessageFailure(message) {
5053
+ try {
5054
+ if (new TextEncoder().encode(JSON.stringify(message)).length <= MAX_FRAME_LENGTH) return null;
5055
+ encodeMessage(message);
5056
+ return null;
5057
+ } catch (error) {
5058
+ return error instanceof Error ? error.message : String(error);
5059
+ }
5060
+ }
5047
5061
  function decodeMessage(bytes) {
5048
5062
  if (bytes.length > MAX_FRAME_LENGTH) {
5049
5063
  throw new Error(`Bridge message frame exceeds ${MAX_FRAME_LENGTH} bytes`);
@@ -5466,7 +5480,8 @@ function parseBoardTaskWire(value) {
5466
5480
  updated_at: requireString(value, "updated_at", "board task"),
5467
5481
  spawn_progress: nullableString(value, "spawn_progress"),
5468
5482
  resumable: nullableBoolean(value, "resumable"),
5469
- paused: nullableBoolean(value, "paused")
5483
+ paused: nullableBoolean(value, "paused"),
5484
+ pausable: nullableBoolean(value, "pausable")
5470
5485
  };
5471
5486
  }
5472
5487
  function parseBacklogItemWire(value) {
@@ -5778,6 +5793,20 @@ function parseStartSessionResponsePayload(payload) {
5778
5793
  }
5779
5794
  return { ok: payload.ok, outcome: payload.outcome };
5780
5795
  }
5796
+ function parsePauseSessionRequestPayload(payload) {
5797
+ if (!isRecord(payload)) throw new Error("pause-session payload must be an object");
5798
+ if (typeof payload.taskId !== "string") throw new Error('pause-session payload missing "taskId"');
5799
+ if (typeof payload.projectId !== "string") throw new Error('pause-session payload missing "projectId"');
5800
+ return {
5801
+ taskId: payload.taskId,
5802
+ projectId: payload.projectId
5803
+ };
5804
+ }
5805
+ function parsePauseSessionResponsePayload(payload) {
5806
+ if (!isRecord(payload)) throw new Error("pause-session response must be an object");
5807
+ if (typeof payload.ok !== "boolean") throw new Error('pause-session response missing "ok"');
5808
+ return { ok: payload.ok };
5809
+ }
5781
5810
  function parseAnswerPermissionPromptRequestPayload(payload) {
5782
5811
  if (!isRecord(payload)) throw new Error("answer-permission-prompt payload must be an object");
5783
5812
  if (typeof payload.sessionId !== "string") throw new Error('answer-permission-prompt payload missing "sessionId"');
@@ -5875,6 +5904,8 @@ function parseCapabilityRequestPayload(verb, payload) {
5875
5904
  return parseRegisterPushRequestPayload(payload);
5876
5905
  case "start-session":
5877
5906
  return parseStartSessionRequestPayload(payload);
5907
+ case "pause-session":
5908
+ return parsePauseSessionRequestPayload(payload);
5878
5909
  default: {
5879
5910
  const exhaustiveCheck = verb;
5880
5911
  throw new Error(`Unknown capability verb: ${String(exhaustiveCheck)}`);
@@ -6227,6 +6258,7 @@ function isBridgeEvent(value) {
6227
6258
  PUSH_CATEGORIES,
6228
6259
  PUSH_ENVELOPE_MAX_AGE_MS,
6229
6260
  PUSH_ENVELOPE_MAX_FUTURE_SKEW_MS,
6261
+ RESPONSE_TOO_LARGE_ERROR_CODE,
6230
6262
  SecretstreamState,
6231
6263
  SessionFrameKind,
6232
6264
  UNSUPPORTED_VERB_ERROR_CODE,
@@ -6246,6 +6278,7 @@ function isBridgeEvent(value) {
6246
6278
  deriveSessionSlotId,
6247
6279
  deriveShortAuthenticationString,
6248
6280
  encodeMessage,
6281
+ encodeMessageFailure,
6249
6282
  encodePairingQrPayload,
6250
6283
  encodeProtocolVersion,
6251
6284
  encodeRosterEntryForSigning,
@@ -6278,6 +6311,7 @@ function isBridgeEvent(value) {
6278
6311
  parseCapabilityRequestPayload,
6279
6312
  parseDiffFileContentWire,
6280
6313
  parseDiffFileListWire,
6314
+ parsePauseSessionResponsePayload,
6281
6315
  parsePushEnvelopePlaintext,
6282
6316
  parseReadBoardResponsePayload,
6283
6317
  parseReadDiffResponsePayload,
package/dist/index.d.ts CHANGED
@@ -9,10 +9,10 @@ export { SecretstreamState, deriveSecretstreamPair, FrameTag, type SecretstreamD
9
9
  export { deriveShortAuthenticationString, type ShortAuthenticationString } from './crypto/sas';
10
10
  export { deriveSessionSlotId, derivePairingSlotId } from './crypto/slot';
11
11
  export type { JsonValue, BridgeMessage, HeartbeatMessage, CapabilityRequestMessage, CapabilityResponseMessage, CapabilityErrorCode, EventMessage } from './wire/messages';
12
- export { encodeMessage, decodeMessage, UnsupportedVerbError, isUnsupportedVerbError, UNSUPPORTED_VERB_ERROR_CODE, MAX_FRAME_LENGTH, MAX_DECODED_LENGTH, COMPRESSION_THRESHOLD, } from './wire/framing';
12
+ export { encodeMessage, encodeMessageFailure, decodeMessage, UnsupportedVerbError, isUnsupportedVerbError, UNSUPPORTED_VERB_ERROR_CODE, RESPONSE_TOO_LARGE_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, 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';
15
+ export { parseCapabilityRequestPayload, parseReadStreamResponsePayload, parseReadBoardResponsePayload, parseReadDiffResponsePayload, parseTranscriptWindowResponsePayload, parseRegisterPushRequestPayload, parseStartSessionResponsePayload, parsePauseSessionResponsePayload, 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, type PauseSessionRequestPayload, type PauseSessionResponsePayload, } 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
@@ -4845,7 +4845,8 @@ var CAPABILITY_VERBS = [
4845
4845
  "board-tool-read",
4846
4846
  "board-tool-write",
4847
4847
  "register-push",
4848
- "start-session"
4848
+ "start-session",
4849
+ "pause-session"
4849
4850
  ];
4850
4851
  function isCapabilityVerb(value) {
4851
4852
  return CAPABILITY_VERBS.includes(value);
@@ -4882,6 +4883,7 @@ var FRAME_FORMAT_DEFLATE = 1;
4882
4883
  var JSON_OPEN_BRACE = 123;
4883
4884
  var DEFLATE_HEADER_LENGTH = 5;
4884
4885
  var UNSUPPORTED_VERB_ERROR_CODE = "unsupported-verb";
4886
+ var RESPONSE_TOO_LARGE_ERROR_CODE = "response-too-large";
4885
4887
  var UNSUPPORTED_VERB_ERROR_NAME = "UnsupportedVerbError";
4886
4888
  var UnsupportedVerbError = class extends Error {
4887
4889
  requestId;
@@ -4918,6 +4920,15 @@ function encodeMessage(message) {
4918
4920
  }
4919
4921
  return frame;
4920
4922
  }
4923
+ function encodeMessageFailure(message) {
4924
+ try {
4925
+ if (new TextEncoder().encode(JSON.stringify(message)).length <= MAX_FRAME_LENGTH) return null;
4926
+ encodeMessage(message);
4927
+ return null;
4928
+ } catch (error) {
4929
+ return error instanceof Error ? error.message : String(error);
4930
+ }
4931
+ }
4921
4932
  function decodeMessage(bytes) {
4922
4933
  if (bytes.length > MAX_FRAME_LENGTH) {
4923
4934
  throw new Error(`Bridge message frame exceeds ${MAX_FRAME_LENGTH} bytes`);
@@ -5340,7 +5351,8 @@ function parseBoardTaskWire(value) {
5340
5351
  updated_at: requireString(value, "updated_at", "board task"),
5341
5352
  spawn_progress: nullableString(value, "spawn_progress"),
5342
5353
  resumable: nullableBoolean(value, "resumable"),
5343
- paused: nullableBoolean(value, "paused")
5354
+ paused: nullableBoolean(value, "paused"),
5355
+ pausable: nullableBoolean(value, "pausable")
5344
5356
  };
5345
5357
  }
5346
5358
  function parseBacklogItemWire(value) {
@@ -5652,6 +5664,20 @@ function parseStartSessionResponsePayload(payload) {
5652
5664
  }
5653
5665
  return { ok: payload.ok, outcome: payload.outcome };
5654
5666
  }
5667
+ function parsePauseSessionRequestPayload(payload) {
5668
+ if (!isRecord(payload)) throw new Error("pause-session payload must be an object");
5669
+ if (typeof payload.taskId !== "string") throw new Error('pause-session payload missing "taskId"');
5670
+ if (typeof payload.projectId !== "string") throw new Error('pause-session payload missing "projectId"');
5671
+ return {
5672
+ taskId: payload.taskId,
5673
+ projectId: payload.projectId
5674
+ };
5675
+ }
5676
+ function parsePauseSessionResponsePayload(payload) {
5677
+ if (!isRecord(payload)) throw new Error("pause-session response must be an object");
5678
+ if (typeof payload.ok !== "boolean") throw new Error('pause-session response missing "ok"');
5679
+ return { ok: payload.ok };
5680
+ }
5655
5681
  function parseAnswerPermissionPromptRequestPayload(payload) {
5656
5682
  if (!isRecord(payload)) throw new Error("answer-permission-prompt payload must be an object");
5657
5683
  if (typeof payload.sessionId !== "string") throw new Error('answer-permission-prompt payload missing "sessionId"');
@@ -5749,6 +5775,8 @@ function parseCapabilityRequestPayload(verb, payload) {
5749
5775
  return parseRegisterPushRequestPayload(payload);
5750
5776
  case "start-session":
5751
5777
  return parseStartSessionRequestPayload(payload);
5778
+ case "pause-session":
5779
+ return parsePauseSessionRequestPayload(payload);
5752
5780
  default: {
5753
5781
  const exhaustiveCheck = verb;
5754
5782
  throw new Error(`Unknown capability verb: ${String(exhaustiveCheck)}`);
@@ -6100,6 +6128,7 @@ export {
6100
6128
  PUSH_CATEGORIES,
6101
6129
  PUSH_ENVELOPE_MAX_AGE_MS,
6102
6130
  PUSH_ENVELOPE_MAX_FUTURE_SKEW_MS,
6131
+ RESPONSE_TOO_LARGE_ERROR_CODE,
6103
6132
  SecretstreamState,
6104
6133
  SessionFrameKind,
6105
6134
  UNSUPPORTED_VERB_ERROR_CODE,
@@ -6119,6 +6148,7 @@ export {
6119
6148
  deriveSessionSlotId,
6120
6149
  deriveShortAuthenticationString,
6121
6150
  encodeMessage,
6151
+ encodeMessageFailure,
6122
6152
  encodePairingQrPayload,
6123
6153
  encodeProtocolVersion,
6124
6154
  encodeRosterEntryForSigning,
@@ -6151,6 +6181,7 @@ export {
6151
6181
  parseCapabilityRequestPayload,
6152
6182
  parseDiffFileContentWire,
6153
6183
  parseDiffFileListWire,
6184
+ parsePauseSessionResponsePayload,
6154
6185
  parsePushEnvelopePlaintext,
6155
6186
  parseReadBoardResponsePayload,
6156
6187
  parseReadDiffResponsePayload,
@@ -6,6 +6,8 @@ export declare const MAX_DECODED_LENGTH: number;
6
6
  export declare const COMPRESSION_THRESHOLD: number;
7
7
  /** The `code` a desktop puts on its refusal of a verb its build does not know. */
8
8
  export declare const UNSUPPORTED_VERB_ERROR_CODE: CapabilityErrorCode;
9
+ /** The `code` a desktop puts on its refusal of a response that would not fit the frame caps above. */
10
+ export declare const RESPONSE_TOO_LARGE_ERROR_CODE: CapabilityErrorCode;
9
11
  /**
10
12
  * Thrown by `decodeMessage` for a capability-request whose envelope is
11
13
  * well-formed (a string `requestId`, a string `verb`, a JSON `payload`) but
@@ -34,4 +36,17 @@ export declare class UnsupportedVerbError extends Error {
34
36
  */
35
37
  export declare function isUnsupportedVerbError(error: unknown): error is UnsupportedVerbError;
36
38
  export declare function encodeMessage(message: BridgeMessage): Uint8Array;
39
+ /**
40
+ * Why `encodeMessage` would throw for `message`, or null when it would not,
41
+ * without keeping the frame. A sender with side effects asks this BEFORE
42
+ * committing them, so it can send something smaller instead of committing to
43
+ * a message it cannot send (the desktop re-takes a shorter terminal seed).
44
+ *
45
+ * Exact, not an estimate. Raw UTF-8 JSON at or under MAX_FRAME_LENGTH always
46
+ * fits, because `encodeMessage` only switches to deflate when deflate is
47
+ * smaller, so that common case costs one stringify. A larger message runs the
48
+ * real encode, deflate included, and a sender that then sends it pays that
49
+ * encode a second time. Measured terminal seeds are far under the threshold.
50
+ */
51
+ export declare function encodeMessageFailure(message: BridgeMessage): string | null;
37
52
  export declare function decodeMessage(bytes: Uint8Array): BridgeMessage;
@@ -32,13 +32,21 @@ export interface CapabilityRequestMessage {
32
32
  }
33
33
  /**
34
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.
35
+ * A union rather than a bare string so a new code is a deliberate addition
36
+ * here, not a typo at a call site.
37
+ *
38
+ * - `unsupported-verb`: the desktop's build does not know the verb (see
39
+ * `UnsupportedVerbError` in framing.ts), so a newer phone can tell "desktop
40
+ * is old" from "desktop is unreachable" and show the right copy.
41
+ * - `response-too-large`: the answer would not fit the frame caps
42
+ * (MAX_DECODED_LENGTH before compression, MAX_FRAME_LENGTH after), so the
43
+ * desktop refused instead of sending it. The request was valid and its
44
+ * target still exists, so a client must not read it as the target being
45
+ * gone (a session ending, a project removed). It is a backstop. A desktop
46
+ * shrinks a `read-stream` terminal seed to fit rather than refuse it, so a
47
+ * phone's terminal subscribe is never answered with this code.
40
48
  */
41
- export type CapabilityErrorCode = 'unsupported-verb';
49
+ export type CapabilityErrorCode = 'unsupported-verb' | 'response-too-large';
42
50
  export interface CapabilityResponseMessage {
43
51
  type: 'capability-response';
44
52
  requestId: string;
@@ -342,6 +342,29 @@ export interface StartSessionResponsePayload {
342
342
  }
343
343
  /** Phone-side guard: narrows a decoded start-session response before the phone acts on it. */
344
344
  export declare function parseStartSessionResponsePayload(payload: JsonValue): StartSessionResponsePayload;
345
+ /**
346
+ * Keyed by task, like `start-session`: the desktop resolves the task's live
347
+ * session itself (a pause clears the task's `session_id`, and a stale phone
348
+ * view may name a session that already ended). It suspends that session the
349
+ * way the desktop's Pause button does, in the column the task is already in.
350
+ */
351
+ export interface PauseSessionRequestPayload {
352
+ taskId: string;
353
+ projectId: string;
354
+ }
355
+ /**
356
+ * `ok` means the pause was ACCEPTED: the task had a live session, and the
357
+ * desktop has recorded it as paused by the user. The agent's own shutdown runs
358
+ * behind this response, because it can outlast the phone's per-verb budget,
359
+ * and its end reaches the phone as the board event that carries
360
+ * `paused: true`. A task with no live session is refused (`ok: false` on the
361
+ * capability response), so there is no idempotent shape to report here.
362
+ */
363
+ export interface PauseSessionResponsePayload {
364
+ ok: boolean;
365
+ }
366
+ /** Phone-side guard: narrows a decoded pause-session response before the phone acts on it. */
367
+ export declare function parsePauseSessionResponsePayload(payload: JsonValue): PauseSessionResponsePayload;
345
368
  export interface AnswerPermissionPromptRequestPayload {
346
369
  sessionId: string;
347
370
  /** The prompt id the phone believes is outstanding - the handler rejects a stale/replayed answer whose id no longer matches the live awaited prompt. */
@@ -428,6 +451,7 @@ export interface CapabilityRequestPayloadMap {
428
451
  'board-tool-write': BoardToolRequestPayload;
429
452
  'register-push': RegisterPushRequestPayload;
430
453
  'start-session': StartSessionRequestPayload;
454
+ 'pause-session': PauseSessionRequestPayload;
431
455
  }
432
456
  export interface CapabilityResponsePayloadMap {
433
457
  'read-stream': ReadStreamResponsePayload | TranscriptWindowResponsePayload;
@@ -441,6 +465,7 @@ export interface CapabilityResponsePayloadMap {
441
465
  'board-tool-write': BoardToolResponsePayload;
442
466
  'register-push': RegisterPushResponsePayload;
443
467
  'start-session': StartSessionResponsePayload;
468
+ 'pause-session': PauseSessionResponsePayload;
444
469
  }
445
470
  /**
446
471
  * 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.17.0",
3
+ "version": "0.19.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",