@kangentic/protocol 0.13.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.
@@ -65,10 +65,45 @@ export type ActivityEventPayload = {
65
65
  * phone learns "this session is over" from the feed itself instead of
66
66
  * inferring it from silence. `intentional` distinguishes a deliberate
67
67
  * stop (desktop Stop button, suspend, shutdown) from a crash.
68
+ *
69
+ * `spawnProgressLabel`, when present, means the desktop had a respawn in
70
+ * flight for this task when the session ended - a same-column respawn
71
+ * (model switch, agent switch, effort change, isolated-session-track
72
+ * switch) rather than a genuine park. Read it as what the desktop
73
+ * believed, not as a fact about what happens next; the caveats below
74
+ * bound it. `intentional` cannot carry this distinction on its own:
75
+ * the desktop's SessionManager.suspend() sets `status = 'suspended'`
76
+ * before the force-kill for every caller, a respawn and a real park
77
+ * alike, so both reach the phone as `intentional: true`. The string is
78
+ * the desktop's own display text for what it is doing ("Switching
79
+ * model...", "Starting agent...", a live git-queue wait count) - treat it
80
+ * as untrusted display text, not a key to switch on, cap its length, and
81
+ * fall back to generic copy rather than parsing it.
82
+ *
83
+ * INTENT, not a guarantee, on the same terms `BoardColumnWire.spawns_session`
84
+ * documents: the desktop can suspend without a successor ever landing (a
85
+ * board profile change, a failed worktree checkout, a shutdown, a racing
86
+ * move), so a client MUST keep whatever timeout already bounds its
87
+ * session-swap wait and use the label only to skip a redundant one.
88
+ *
89
+ * Absent from pre-0.14.0 desktops, and never sent as `null`. A park that
90
+ * goes through `task-move.ts` (move to To Do, to Done, or into an
91
+ * `auto_spawn=false` column) clears the label first, so it carries none.
92
+ * Five other genuine parks do not clear it - a manual pause, the
93
+ * idle-timeout suspend, the `kill_session` action, project-relocate, and
94
+ * auto-spawn-reconcile - so one of those firing while a label is still in
95
+ * flight sends the label on a real park. The label then survives at most
96
+ * the desktop's 120s spawn-progress TTL. That gap is why the timeout above
97
+ * is mandatory, not advisory.
98
+ *
99
+ * A label can also ride an `intentional: false` exit, since a respawn whose
100
+ * spawn fails still has its label in flight when the PTY dies. The two
101
+ * fields are independent; do not read them as mutually exclusive.
68
102
  */
69
103
  | {
70
104
  type: 'session-ended';
71
105
  intentional: boolean;
106
+ spawnProgressLabel?: string;
72
107
  }
73
108
  /**
74
109
  * The agent's most recent assistant message, already collapsed to a short
@@ -279,8 +279,7 @@ export interface BoardColumnWire {
279
279
  * Declared OPTIONAL (`?`) so a hand-built `BoardColumnWire` literal does
280
280
  * not have to list it. `parseBoardColumnWire` populates the key from every
281
281
  * real wire response regardless, so the optionality costs nothing at
282
- * runtime. `docs/mobile-bridge.md` records why this differs from
283
- * `pr_merge_readiness`, which shipped required.
282
+ * runtime.
284
283
  *
285
284
  * Ordering precondition this field depends on: the phone must learn a
286
285
  * MOVE's destination column before the old session's `session-ended`
@@ -313,8 +312,12 @@ export interface BoardTaskWire {
313
312
  * judged. `queued` and `running` mean a blocking check is still in flight.
314
313
  * Absent from desktops that predate it, which the parser reads as null; a
315
314
  * value a client does not know should render as plain open.
315
+ *
316
+ * Declared OPTIONAL (`?`) so a hand-built `BoardTaskWire` literal does not
317
+ * have to list it. `parseBoardTaskWire` populates the key from every real
318
+ * wire response regardless, so the optionality costs nothing at runtime.
316
319
  */
317
- pr_merge_readiness: string | null;
320
+ pr_merge_readiness?: string | null;
318
321
  base_branch: string | null;
319
322
  labels: string[];
320
323
  priority: number;
package/dist/index.cjs CHANGED
@@ -6107,7 +6107,11 @@ function parseActivityEventPayload(payload) {
6107
6107
  }
6108
6108
  case "session-ended": {
6109
6109
  if (typeof payload.intentional !== "boolean") throw new Error('session-ended payload is missing "intentional"');
6110
- return { type: "session-ended", intentional: payload.intentional };
6110
+ if (payload.spawnProgressLabel === void 0) {
6111
+ return { type: "session-ended", intentional: payload.intentional };
6112
+ }
6113
+ if (typeof payload.spawnProgressLabel !== "string") throw new Error('session-ended payload has an invalid "spawnProgressLabel"');
6114
+ return { type: "session-ended", intentional: payload.intentional, spawnProgressLabel: payload.spawnProgressLabel };
6111
6115
  }
6112
6116
  case "message-preview": {
6113
6117
  if (typeof payload.text !== "string") throw new Error('message-preview payload is missing "text"');
package/dist/index.mjs CHANGED
@@ -5986,7 +5986,11 @@ function parseActivityEventPayload(payload) {
5986
5986
  }
5987
5987
  case "session-ended": {
5988
5988
  if (typeof payload.intentional !== "boolean") throw new Error('session-ended payload is missing "intentional"');
5989
- return { type: "session-ended", intentional: payload.intentional };
5989
+ if (payload.spawnProgressLabel === void 0) {
5990
+ return { type: "session-ended", intentional: payload.intentional };
5991
+ }
5992
+ if (typeof payload.spawnProgressLabel !== "string") throw new Error('session-ended payload has an invalid "spawnProgressLabel"');
5993
+ return { type: "session-ended", intentional: payload.intentional, spawnProgressLabel: payload.spawnProgressLabel };
5990
5994
  }
5991
5995
  case "message-preview": {
5992
5996
  if (typeof payload.text !== "string") throw new Error('message-preview payload is missing "text"');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kangentic/protocol",
3
- "version": "0.13.0",
3
+ "version": "0.14.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",