@robota-sdk/agent-transport 3.0.0-beta.100

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +1137 -0
  2. package/LICENSE +661 -0
  3. package/README.md +121 -0
  4. package/dist/browser/client.d.ts +2 -0
  5. package/dist/browser/client.js +1 -0
  6. package/dist/browser/index.d.ts +624 -0
  7. package/dist/browser/index.d.ts.map +1 -0
  8. package/dist/browser/index.js +2 -0
  9. package/dist/browser/index.js.map +1 -0
  10. package/dist/browser/observer-messages-4fRQZU2D.d.ts +499 -0
  11. package/dist/browser/observer-messages-4fRQZU2D.d.ts.map +1 -0
  12. package/dist/browser/observer-messages-O8jTPEQO.js +2 -0
  13. package/dist/browser/observer-messages-O8jTPEQO.js.map +1 -0
  14. package/dist/node/index.cjs +1 -0
  15. package/dist/node/index.d.cts +1115 -0
  16. package/dist/node/index.d.cts.map +1 -0
  17. package/dist/node/index.d.ts +1115 -0
  18. package/dist/node/index.d.ts.map +1 -0
  19. package/dist/node/index.js +2 -0
  20. package/dist/node/index.js.map +1 -0
  21. package/dist/node/node.cjs +1 -0
  22. package/dist/node/node.d.cts +317 -0
  23. package/dist/node/node.d.cts.map +1 -0
  24. package/dist/node/node.d.ts +317 -0
  25. package/dist/node/node.d.ts.map +1 -0
  26. package/dist/node/node.js +2 -0
  27. package/dist/node/node.js.map +1 -0
  28. package/package.json +122 -0
  29. package/src/__tests__/__fixtures__/korean-text.json +6 -0
  30. package/src/__tests__/access-token-verifier.test.ts +672 -0
  31. package/src/__tests__/admission.test.ts +130 -0
  32. package/src/__tests__/app-tool-messages.test.ts +463 -0
  33. package/src/__tests__/bearer-resource-gate.test.ts +120 -0
  34. package/src/__tests__/channel-frames.test.ts +215 -0
  35. package/src/__tests__/driver-attribution-capability.test.ts +83 -0
  36. package/src/__tests__/file-transfer.test.ts +281 -0
  37. package/src/__tests__/handoff-chunking.test.ts +261 -0
  38. package/src/__tests__/handoff-manifest.test.ts +52 -0
  39. package/src/__tests__/history-attached-budget.test.ts +216 -0
  40. package/src/__tests__/message-decoders.test.ts +870 -0
  41. package/src/__tests__/outbound-delivery.test.ts +558 -0
  42. package/src/__tests__/peer-message-ledger.test.ts +200 -0
  43. package/src/__tests__/permission-summary.test.ts +18 -0
  44. package/src/__tests__/personal-usage-report.test.ts +193 -0
  45. package/src/__tests__/project-read-messages.test.ts +118 -0
  46. package/src/__tests__/resume-buffer.test.ts +93 -0
  47. package/src/__tests__/session-detail-loop-status.test.ts +169 -0
  48. package/src/__tests__/session-directory-messages.test.ts +322 -0
  49. package/src/__tests__/session-error-classification.test.ts +113 -0
  50. package/src/__tests__/session-event-delivery.test.ts +101 -0
  51. package/src/__tests__/session-history-wire.test.ts +364 -0
  52. package/src/__tests__/session-message-handler-e5.test.ts +99 -0
  53. package/src/__tests__/session-message-handler-observe.test.ts +223 -0
  54. package/src/__tests__/session-message-handler.test.ts +726 -0
  55. package/src/__tests__/session-resume-bridge.test.ts +472 -0
  56. package/src/__tests__/settings-messages.test.ts +193 -0
  57. package/src/__tests__/stdio-carrier.test.ts +85 -0
  58. package/src/__tests__/submit-prompt-shape.test.ts +78 -0
  59. package/src/__tests__/usage-report-carrier.test.ts +98 -0
  60. package/src/__tests__/ws-broadcast-events.test.ts +94 -0
  61. package/src/app-tool-messages.ts +140 -0
  62. package/src/background-messages.ts +169 -0
  63. package/src/channel-frames.ts +182 -0
  64. package/src/client.ts +17 -0
  65. package/src/handoff-chunking.ts +275 -0
  66. package/src/index.ts +135 -0
  67. package/src/loop-control-messages.ts +34 -0
  68. package/src/message-decoders.ts +485 -0
  69. package/src/message-parser.ts +33 -0
  70. package/src/node/access-token-verifier.ts +363 -0
  71. package/src/node/admission.ts +153 -0
  72. package/src/node/bearer-resource-gate.ts +331 -0
  73. package/src/node/file-transfer.ts +530 -0
  74. package/src/node/handoff-manifest.ts +77 -0
  75. package/src/node/index.ts +45 -0
  76. package/src/node/stdio-carrier.ts +59 -0
  77. package/src/observer-messages.ts +47 -0
  78. package/src/open-prompts.ts +76 -0
  79. package/src/outbound-delivery.ts +240 -0
  80. package/src/peer-message-ledger.ts +193 -0
  81. package/src/permission-summary.ts +18 -0
  82. package/src/project-read-messages.ts +74 -0
  83. package/src/protocol-session.ts +56 -0
  84. package/src/resume-buffer.ts +123 -0
  85. package/src/session-directory-messages.ts +162 -0
  86. package/src/session-error-classification.ts +91 -0
  87. package/src/session-events.ts +290 -0
  88. package/src/session-message-handler.ts +413 -0
  89. package/src/session-query-messages.ts +160 -0
  90. package/src/session-resume-bridge.ts +275 -0
  91. package/src/settings-messages.ts +103 -0
  92. package/src/usage-messages.ts +195 -0
  93. package/src/wire-messages.ts +436 -0
@@ -0,0 +1,436 @@
1
+ import type {
2
+ IToolAppView,
3
+ IToolExecutionResult,
4
+ TToolParameters,
5
+ TUniversalMessagePart,
6
+ } from '@robota-sdk/agent-core';
7
+ import type {
8
+ IPersonalUsageReport,
9
+ IPersonalUsageRequest,
10
+ IUsageBySourceReport,
11
+ } from '@robota-sdk/agent-interface-analytics';
12
+ import type {
13
+ ICommandListEntry,
14
+ ICommandResult,
15
+ ICommandSkillListEntry,
16
+ } from '@robota-sdk/agent-interface-command';
17
+ import type {
18
+ IBackgroundJobGroupState,
19
+ IExecutionDetailCursor,
20
+ IExecutionDetailPage,
21
+ IExecutionWorkspaceSnapshot,
22
+ TBackgroundJobGroupEvent,
23
+ } from '@robota-sdk/agent-interface-execution';
24
+ import type {
25
+ IBackgroundTaskInput,
26
+ IBackgroundTaskListFilter,
27
+ IBackgroundTaskLogCursor,
28
+ IBackgroundTaskLogPage,
29
+ IBackgroundTaskState,
30
+ TBackgroundTaskEvent,
31
+ } from '@robota-sdk/agent-interface-execution';
32
+ import type {
33
+ IAskRequestEvent,
34
+ IBranchEvent,
35
+ ICompactEvent,
36
+ IExecutionResult,
37
+ IContextFileRefreshedEvent,
38
+ IModelListGroup,
39
+ IPermissionRequestEvent,
40
+ IPromptResolvedEvent,
41
+ IPlanApprovalEvent,
42
+ ISessionRenamedEvent,
43
+ ISettingsSnapshot,
44
+ IToolState,
45
+ ISessionListing,
46
+ ISessionStatusSnapshot,
47
+ ISessionSwitchedEvent,
48
+ TSessionChangeRefusalCode,
49
+ TSessionDeleteRefusalCode,
50
+ TSettingsPatch,
51
+ IUiIntentEvent,
52
+ TPermissionResultValue,
53
+ TProjectDiffRead,
54
+ TProjectMemoryRead,
55
+ TProjectStatusRead,
56
+ TWaitingLoopStopOutcome,
57
+ } from '@robota-sdk/agent-interface-session';
58
+ import type {
59
+ ISessionConversationRead,
60
+ TDriverId,
61
+ TTurnSource,
62
+ } from '@robota-sdk/agent-interface-session';
63
+ import type { TActionResponse } from '@robota-sdk/agent-interface-transport';
64
+
65
+ export type TBackgroundControlAction = 'cancel' | 'close' | 'send';
66
+
67
+ type THistoryEntry = ReturnType<ISessionConversationRead['getFullHistory']>[number];
68
+
69
+ /**
70
+ * #3282 §4: one agent the switcher can offer. `definedIn` is a plain-words location — a discovered
71
+ * file's path, or "Built-in" — never a raw internal id.
72
+ */
73
+ export interface IWireAgentDefinitionSummary {
74
+ name: string;
75
+ description: string;
76
+ definedIn: string;
77
+ }
78
+
79
+ /**
80
+ * #3189: one entry of the session's full history as it crosses the wire. The same record the session
81
+ * keeps, except that `timestamp` is an ISO 8601 string: a `Date` does not survive JSON, and a
82
+ * declared `Date` that arrives as a string would let a client call `Date` methods on a string.
83
+ */
84
+ export interface IWireHistoryEntry extends Omit<THistoryEntry, 'timestamp'> {
85
+ /** ISO 8601. */
86
+ timestamp: string;
87
+ }
88
+
89
+ /**
90
+ * #3189: a turn's result as it crosses the wire. The session's whole history stays behind: it only
91
+ * grows, so a client reads it in bounded pages with `get-history` instead.
92
+ */
93
+ export type TWireExecutionResult = Omit<IExecutionResult, 'history'>;
94
+
95
+ /** Inbound message from client to server. */
96
+ export type TClientMessage =
97
+ | { type: 'submit'; prompt: string }
98
+ // `requestId` is echoed on the command's `command_result` or `protocol_error`, so a client with
99
+ // several commands in flight knows which one each answer settles.
100
+ | { type: 'command'; name: string; args?: string; requestId?: string }
101
+ | { type: 'abort' }
102
+ | { type: 'cancel-queue' }
103
+ | { type: 'get-messages' }
104
+ // #3189: the session's full history — what a client that renders the whole session (the TUI) shows —
105
+ // one bounded page at a time, from `fromIndex` (default 0). The client asks for the next page
106
+ // after the previous one arrived, so replies never pile up in the connection.
107
+ | { type: 'get-history'; fromIndex?: number }
108
+ // #3189: the permission and ask prompts open now, each sent again as the frame that asked it: a
109
+ // client that attached after a prompt was asked has not seen it.
110
+ | { type: 'get-prompts' }
111
+ | { type: 'get-context' }
112
+ // #3186: what every client needs beside the conversation — the commands and skills it can offer
113
+ // (a `/` menu), and the session's status (model, permission mode, effort, context).
114
+ | { type: 'get-commands' }
115
+ | { type: 'get-status' }
116
+ // #3282 §2: the models the GUI's model menu offers, grouped by configured provider profile — so
117
+ // the GUI never scrapes `/model`'s picker text. Choosing one sends `command` with `name: 'model'`,
118
+ // the SAME path `/model <id>` runs; this message only reads the choices.
119
+ | { type: 'list-models'; requestId: string }
120
+ // #3282 §4: the agent switcher's roster and its currently-selected default — so the GUI never
121
+ // scrapes `/agent`'s text. Choosing one sends `command` with `name: 'agent'`, the SAME path
122
+ // `/agent <name>` runs; this message only reads the roster and the current selection.
123
+ | { type: 'get-agent-definitions'; requestId: string }
124
+ // #3189: the host's sessions — list them, start a new one, make another current. A refused change
125
+ // answers `session_change_failed` with the same `requestId`.
126
+ | { type: 'list-sessions'; requestId: string }
127
+ | { type: 'new-session'; requestId?: string }
128
+ | { type: 'switch-session'; sessionId: string; requestId?: string }
129
+ // #3289 §1: rename a session from the list — current or not. Answered by `session_renamed_in_list`
130
+ // or `session_rename_failed`, both carrying the same `requestId`.
131
+ | { type: 'rename-session'; sessionId: string; name: string; requestId: string }
132
+ // #3289 §1: remove a stored session's record for good. Answered by `session_deleted` or
133
+ // `session_delete_failed`, both carrying the same `requestId`.
134
+ | { type: 'delete-session'; sessionId: string; requestId: string }
135
+ // SELFHOST-004: request the assembled trace/cost read-model (spans + cost-by-source) for the run.
136
+ | { type: 'get-usage-report' }
137
+ | {
138
+ type: 'get-personal-usage-report';
139
+ requestId: string;
140
+ period: IPersonalUsageRequest['period'];
141
+ timezone: string;
142
+ }
143
+ | { type: 'get-stored-session-usage-report'; requestId: string; sessionId: string }
144
+ | { type: 'get-executing' }
145
+ | { type: 'get-pending' }
146
+ | { type: 'get-execution-workspace' }
147
+ // One page of what a workspace entry recorded, from `cursor` (the previous page's `nextCursor`).
148
+ // Answered by `execution_detail` or `execution_detail_error` with the same `requestId`.
149
+ | {
150
+ type: 'read-execution-detail';
151
+ requestId: string;
152
+ entryId: string;
153
+ cursor?: IExecutionDetailCursor;
154
+ }
155
+ // Stop the self-paced loop that is waiting for its next wake; answered by `waiting_loop_stop`.
156
+ | { type: 'stop-waiting-loop'; requestId: string }
157
+ // #3282 §4c: the Project panel's "Changes" section — this workspace's git status. Answered by
158
+ // `project_status`, echoing `requestId`.
159
+ | { type: 'project-status'; requestId: string }
160
+ // The Project panel's "File diff" section for one file `project_status` reported — `path` exactly
161
+ // as reported (workspace-relative; the server re-checks it stays inside the workspace). Answered by
162
+ // `project_diff`.
163
+ | { type: 'project-diff'; requestId: string; path: string }
164
+ // The Project panel's "Memory" section — project memory as readable text. Answered by
165
+ // `project_memory`.
166
+ | { type: 'project-memory'; requestId: string }
167
+ // A driving surface invokes one runtime tool for a hosted app view, through the session's
168
+ // permission gate; a prompt it raises is attributed to that surface's driver. `viewId` names the
169
+ // view (from a `tool_end`'s `appView`) and limits the call to the tools that view's own source
170
+ // declared. Answered by `app_tool_result`; a call that decoded but is refused (the view is gone,
171
+ // the tool is not the view's, the invocation is refused) is answered by `protocol_error` echoing
172
+ // `requestId`. A frame that does not decode, or one an observer sends, carries no `requestId`.
173
+ | {
174
+ type: 'call-app-tool';
175
+ requestId: string;
176
+ viewId: string;
177
+ name: string;
178
+ arguments: TToolParameters;
179
+ }
180
+ // A driving surface reads the view a finished model call named in its `tool_end` (`appView`).
181
+ // Answered by `app_view`; a read that decoded but cannot be served (gone, or too large to send)
182
+ // is answered by `protocol_error` echoing `requestId`.
183
+ | { type: 'read-app-view'; requestId: string; viewId: string }
184
+ | { type: 'get-background-tasks'; filter?: IBackgroundTaskListFilter }
185
+ | { type: 'get-background-task'; taskId: string }
186
+ | { type: 'get-background-job-groups' }
187
+ | { type: 'get-background-job-group'; groupId: string }
188
+ | { type: 'wait-background-job-group'; groupId: string }
189
+ | { type: 'cancel-background-task'; taskId: string; reason?: string }
190
+ | { type: 'close-background-task'; taskId: string }
191
+ | { type: 'send-background-task'; taskId: string; input: IBackgroundTaskInput }
192
+ | { type: 'read-background-task-log'; taskId: string; cursor?: IBackgroundTaskLogCursor }
193
+ // REMOTE-007: a driving client answers a pending permission/ask prompt by id (first answer wins).
194
+ | { type: 'permission-response'; id: string; result: TPermissionResultValue }
195
+ | { type: 'ask-response'; id: string; response: TActionResponse }
196
+ // REMOTE-013 E4 session-resume: `resume` asks the host to replay the tail after `lastSeq` (the last seq the
197
+ // client applied); `ack` lets the host free its un-acked buffer up to `seq`. Only meaningful post-E3-accept.
198
+ | { type: 'resume'; lastSeq: number }
199
+ | { type: 'ack'; seq: number }
200
+ // #3282 §4a: the GUI Settings screen. A snapshot fetch and a discriminated single-field patch,
201
+ // never command text — the server applies a patch through the same function its slash command
202
+ // uses, so the two paths cannot drift. Answered by `settings` or `settings_error`.
203
+ | { type: 'get-settings'; requestId: string }
204
+ | { type: 'update-settings'; requestId: string; patch: TSettingsPatch };
205
+
206
+ /** Outbound message from server to client. */
207
+ export type TServerMessage =
208
+ // REMOTE-014 E5: turn-authored events optionally carry the ACTIVE turn's `driverId` (co-drive authorship,
209
+ // display-only). Stamped at `subscribeSessionEvents` from `getActiveDriverId()`; background/goal/memory/
210
+ // execution-workspace events are NEVER stamped (they are not authored by a driver turn).
211
+ | { type: 'text_delta'; delta: string; driverId?: TDriverId }
212
+ | { type: 'user_message'; content: string; driverId?: TDriverId }
213
+ | { type: 'tool_start'; state: IToolState; driverId?: TDriverId }
214
+ | { type: 'tool_end'; state: IToolState; driverId?: TDriverId }
215
+ | { type: 'thinking'; isThinking: boolean; driverId?: TDriverId }
216
+ | { type: 'complete'; result: TWireExecutionResult; driverId?: TDriverId }
217
+ | { type: 'interrupted'; result: TWireExecutionResult; driverId?: TDriverId }
218
+ | {
219
+ type: 'error';
220
+ /** The raw failure text — kept for a "Details" disclosure; never the whole story on its own. */
221
+ message: string;
222
+ /**
223
+ * What kind of failure this was, when it's known — so a renderer can say what happened and
224
+ * what to do next in plain words instead of showing `message` verbatim (#3289 §3). Additive:
225
+ * absent for a session error this classification does not recognize (unchanged behavior).
226
+ */
227
+ code?: 'auth' | 'rate_limit' | 'model_unavailable' | 'network' | 'provider';
228
+ /** The provider/profile name the failure came from, when `code` names one. */
229
+ provider?: string;
230
+ /** For `code: 'rate_limit'`, how long until a retry may succeed, when the provider said so. */
231
+ retryAfterSeconds?: number;
232
+ /**
233
+ * For `code: 'model_unavailable'`, the model the failed request tried — captured at the moment
234
+ * of failure so a renderer never has to fall back to whatever model is live by the time it
235
+ * draws this notice (which may already be a different one the person switched to).
236
+ */
237
+ model?: string;
238
+ driverId?: TDriverId;
239
+ }
240
+ | {
241
+ type: 'command_result';
242
+ name: string;
243
+ message: string;
244
+ success: boolean;
245
+ data?: ICommandResult['data'];
246
+ /** The `requestId` of the `command` this answers, when it carried one. */
247
+ requestId?: string;
248
+ }
249
+ | {
250
+ type: 'messages';
251
+ messages: ReturnType<ISessionConversationRead['getMessages']>;
252
+ /**
253
+ * #3288 §2: the SAME history, projected into display segments (text runs and finished tool
254
+ * calls, diffs included) — so a reload/reconnect/resume replay shows tool rows instead of bare
255
+ * text. Optional so an older host that has not grown `getMessagesDisplay()` yet still sends a
256
+ * valid frame; a client without this field falls back to `messages`-only (text bubbles), the
257
+ * same as before this existed.
258
+ */
259
+ display?: ReturnType<ISessionConversationRead['getMessagesDisplay']>;
260
+ /**
261
+ * #3289 §3: this connection's own server-assigned driver id, carried on the first frame every
262
+ * connection already gets — so a client can tell its OWN turns from a co-driver's instead of
263
+ * treating everything but the literal `'owner'` id as someone else (REMOTE-014 E5 display-only).
264
+ */
265
+ driverId?: TDriverId;
266
+ }
267
+ // #3189: one page of the full history. `entries` start at `startIndex` of the `total` the session
268
+ // holds now, and stop before a page grows past a bounded size (a single larger entry is sent alone).
269
+ | { type: 'history'; startIndex: number; total: number; entries: IWireHistoryEntry[] }
270
+ // Sent in reply to `get-context`, and pushed whenever the session's context window changes.
271
+ | { type: 'context'; state: ReturnType<ISessionConversationRead['getContextState']> }
272
+ // #3189: the full history gained entries that no streamed frame carries (a compaction, a skill
273
+ // activation, a memory event). A client that shows the full history re-reads it with `get-history`.
274
+ | {
275
+ type: 'history_changed';
276
+ /** A completed compaction, for clients that do not render the full system history. */
277
+ compact?: {
278
+ trigger: ICompactEvent['trigger'];
279
+ beforeUsedPercentage: number;
280
+ afterUsedPercentage: number;
281
+ };
282
+ }
283
+ // #3189: where the turn now starting came from (the user, a wake-up, a peer, an external event).
284
+ | { type: 'turn_source'; source: TTurnSource }
285
+ | { type: 'commands'; commands: ICommandListEntry[]; skills: ICommandSkillListEntry[] }
286
+ // Sent in reply to `get-status`, and pushed whenever the session's status changes.
287
+ | { type: 'session_status'; status: ISessionStatusSnapshot }
288
+ // #3282 §2: reply to `list-models`, echoing its `requestId`. `groups` is current-profile first;
289
+ // `currentProfile` is absent only when no provider profile is configured at all.
290
+ | {
291
+ type: 'model_list';
292
+ requestId: string;
293
+ groups: readonly IModelListGroup[];
294
+ currentProfile?: string;
295
+ currentModel: string;
296
+ }
297
+ // #3282 §4: reply to `get-agent-definitions`, echoing its `requestId`. `current` is always one of
298
+ // `agents`' names (the fallback default when nothing was ever selected).
299
+ | {
300
+ type: 'agent_definitions';
301
+ requestId: string;
302
+ agents: readonly IWireAgentDefinitionSummary[];
303
+ current: string;
304
+ }
305
+ | { type: 'sessions'; requestId: string; listing: ISessionListing }
306
+ | {
307
+ type: 'sessions_error';
308
+ requestId: string;
309
+ code: 'not_available' | 'list_failed';
310
+ message: string;
311
+ }
312
+ // This client's session is now another one; it re-reads what it shows. A host that binds each
313
+ // client to its own session sends it only to the client that moved.
314
+ | { type: 'session_switched'; event: ISessionSwitchedEvent }
315
+ // #3189: a new-session or switch-session was refused; `code` says why without parsing `message`.
316
+ | {
317
+ type: 'session_change_failed';
318
+ code: TSessionChangeRefusalCode;
319
+ message: string;
320
+ requestId?: string;
321
+ }
322
+ // #3289 §1: a `rename-session` succeeded (current session or not); the client refreshes its listing.
323
+ | { type: 'session_renamed_in_list'; requestId: string; sessionId: string; name: string }
324
+ | { type: 'session_rename_failed'; requestId: string; message: string }
325
+ // #3289 §1: a `delete-session` succeeded; the client refreshes its listing and, if it was showing
326
+ // the deleted session, re-reads what it now shows (the host has already switched it away).
327
+ | { type: 'session_deleted'; requestId: string; sessionId: string }
328
+ | {
329
+ type: 'session_delete_failed';
330
+ requestId: string;
331
+ code: TSessionDeleteRefusalCode;
332
+ message: string;
333
+ }
334
+ // SELFHOST-004 (P5, TC-08): carry the assembled trace/cost read-model (per-op span timeline +
335
+ // cost-by-source) across the sidecar boundary — no existing variant carries per-op `durationMs` or
336
+ // per-source `costUsd`. The GUI renders it renderer-side.
337
+ | { type: 'usage_report'; report: IUsageBySourceReport }
338
+ | { type: 'personal_usage_report'; requestId: string; report: IPersonalUsageReport }
339
+ | {
340
+ type: 'personal_usage_report_error';
341
+ requestId: string;
342
+ code: 'not_available' | 'report_failed';
343
+ message: string;
344
+ }
345
+ | {
346
+ type: 'stored_session_usage_report';
347
+ requestId: string;
348
+ sessionId: string;
349
+ report: IUsageBySourceReport;
350
+ }
351
+ | {
352
+ type: 'stored_session_usage_report_error';
353
+ requestId: string;
354
+ sessionId: string;
355
+ code: 'not_available' | 'report_failed';
356
+ message: string;
357
+ }
358
+ | { type: 'executing'; executing: boolean }
359
+ // The next queued prompt and how many wait. Sent in reply to `get-pending`, and to a `submit` once
360
+ // the host has taken the prompt (queued it behind a running turn, or run it).
361
+ | { type: 'pending'; pending: string | null; pendingCount?: number }
362
+ | { type: 'execution_workspace_event'; snapshot: IExecutionWorkspaceSnapshot }
363
+ | { type: 'execution_detail'; requestId: string; page: IExecutionDetailPage }
364
+ | { type: 'execution_detail_error'; requestId: string; message: string }
365
+ | { type: 'waiting_loop_stop'; requestId: string; outcome: TWaitingLoopStopOutcome }
366
+ // #3282 §4c: the Project panel's reads. Each result crosses the wire UNCHANGED as the session
367
+ // method's own return value (same convention as `waiting_loop_stop`'s `outcome` above) — the
368
+ // `kind` discriminant carries the non-repository / outside-workspace / unavailable / failed cases,
369
+ // so there is no separate `_error` sibling to keep in sync with these.
370
+ | { type: 'project_status'; requestId: string; result: TProjectStatusRead }
371
+ | { type: 'project_diff'; requestId: string; result: TProjectDiffRead }
372
+ | { type: 'project_memory'; requestId: string; result: TProjectMemoryRead }
373
+ // The outcome of a `call-app-tool`, correlated by `requestId`: the tool's own result, as the
374
+ // session returned it.
375
+ | {
376
+ type: 'app_tool_result';
377
+ requestId: string;
378
+ success: boolean;
379
+ data?: IToolExecutionResult['result'];
380
+ parts?: TUniversalMessagePart[];
381
+ error?: string;
382
+ }
383
+ // The answer to a `read-app-view`: the whole document, to the surface that asked and no other.
384
+ | { type: 'app_view'; requestId: string; view: IToolAppView }
385
+ | { type: 'background_task_event'; event: TBackgroundTaskEvent }
386
+ | { type: 'background_job_group_event'; event: TBackgroundJobGroupEvent }
387
+ | { type: 'plan_event'; event: IPlanApprovalEvent }
388
+ | { type: 'context_file_refreshed'; event: IContextFileRefreshedEvent }
389
+ | { type: 'branch_event'; event: IBranchEvent }
390
+ | { type: 'background_tasks'; tasks: IBackgroundTaskState[] }
391
+ | { type: 'background_task'; taskId: string; task: IBackgroundTaskState | null }
392
+ | { type: 'background_job_groups'; groups: IBackgroundJobGroupState[] }
393
+ | { type: 'background_job_group'; groupId: string; group: IBackgroundJobGroupState | null }
394
+ | { type: 'background_task_log'; taskId: string; page: IBackgroundTaskLogPage }
395
+ // REMOTE-007: forward the session's transport-neutral prompt events so a remote surface can render +
396
+ // answer the SAME prompt (permission/ask). `prompt_resolved` dismisses it when another surface won.
397
+ | { type: 'permission_request'; event: IPermissionRequestEvent }
398
+ | { type: 'ask_request'; event: IAskRequestEvent }
399
+ | { type: 'prompt_resolved'; event: IPromptResolvedEvent }
400
+ // CMD-004 Phase 2: forward command-issued UI intents (same pattern as `ask_request`). The event is
401
+ // requester-routed — a client renders it only when `event.requesterDriverId` is its own driver id;
402
+ // an unsupported intent yields an explicit "not available on this surface" notice, never a silent drop.
403
+ | { type: 'ui_intent'; event: IUiIntentEvent }
404
+ // CMD-004 Phase 2 Stage E: BROADCAST session events — every attached surface receives them
405
+ // (co-driving titles/transcripts follow the host-executed rename/clear; never requester-filtered).
406
+ | { type: 'session_renamed'; event: ISessionRenamedEvent }
407
+ | { type: 'history_cleared' }
408
+ | {
409
+ type: 'background_task_control_result';
410
+ action: TBackgroundControlAction;
411
+ taskId: string;
412
+ success: boolean;
413
+ message?: string;
414
+ }
415
+ // `requestId` names the `command` whose failure this reports; a refusal of anything else has none.
416
+ | { type: 'protocol_error'; message: string; requestId?: string }
417
+ // REMOTE-013 E4: sent instead of a replay when the client's `lastSeq` predates the host's retained buffer
418
+ // (overrun) — the client must do a full `get-messages` refresh rather than accept a silent gap.
419
+ | { type: 'resume_gap' }
420
+ // #3282 §4a: the Settings screen's snapshot, in reply to `get-settings` or `update-settings`. The
421
+ // same type answers both, the way `session_status` answers `get-status` and a status change alike.
422
+ | { type: 'settings'; requestId: string; settings: ISettingsSnapshot }
423
+ | {
424
+ type: 'settings_error';
425
+ requestId: string;
426
+ code: 'not_available' | 'invalid' | 'refused' | 'update_failed';
427
+ message: string;
428
+ };
429
+
430
+ /**
431
+ * REMOTE-013 E4: a server message stamped with its monotonic session sequence number (added by the
432
+ * {@link SessionResumeBridge} on the reconnectable WebRTC path). Intersecting over the union distributes the
433
+ * `seq` field onto every variant. The WS localhost path never stamps it (a `type`-dispatching client ignores
434
+ * an absent/extra `seq`).
435
+ */
436
+ export type TSeqServerMessage = TServerMessage & { seq: number };