@robota-sdk/agent-transport 3.0.0-beta.82 → 3.0.0-beta.83

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 (53) hide show
  1. package/CHANGELOG.md +752 -0
  2. package/dist/browser/client.d.ts +2 -2
  3. package/dist/browser/client.js +1 -1
  4. package/dist/browser/index.d.ts +31 -11
  5. package/dist/browser/index.d.ts.map +1 -1
  6. package/dist/browser/index.js +1 -1
  7. package/dist/browser/index.js.map +1 -1
  8. package/dist/browser/observer-messages-Bx-xReUQ.js +2 -0
  9. package/dist/browser/observer-messages-Bx-xReUQ.js.map +1 -0
  10. package/dist/browser/{message-decoders-BB8n-H2X.d.ts → observer-messages-DC_8VHk9.d.ts} +104 -7
  11. package/dist/browser/observer-messages-DC_8VHk9.d.ts.map +1 -0
  12. package/dist/node/index.cjs +1 -1
  13. package/dist/node/index.d.cts +131 -14
  14. package/dist/node/index.d.cts.map +1 -1
  15. package/dist/node/index.d.ts +131 -14
  16. package/dist/node/index.d.ts.map +1 -1
  17. package/dist/node/index.js +1 -1
  18. package/dist/node/index.js.map +1 -1
  19. package/dist/node/node.cjs +1 -1
  20. package/dist/node/node.d.cts +69 -1
  21. package/dist/node/node.d.cts.map +1 -1
  22. package/dist/node/node.d.ts +70 -2
  23. package/dist/node/node.d.ts.map +1 -1
  24. package/dist/node/node.js +1 -1
  25. package/dist/node/node.js.map +1 -1
  26. package/package.json +13 -11
  27. package/src/__tests__/bearer-resource-gate.test.ts +120 -0
  28. package/src/__tests__/history-attached-budget.test.ts +216 -0
  29. package/src/__tests__/message-decoders.test.ts +201 -5
  30. package/src/__tests__/session-detail-loop-status.test.ts +169 -0
  31. package/src/__tests__/session-directory-messages.test.ts +192 -0
  32. package/src/__tests__/session-history-wire.test.ts +342 -0
  33. package/src/__tests__/session-message-handler-observe.test.ts +193 -0
  34. package/src/__tests__/session-message-handler.test.ts +82 -1
  35. package/src/__tests__/ws-broadcast-events.test.ts +10 -0
  36. package/src/client.ts +9 -1
  37. package/src/index.ts +14 -2
  38. package/src/loop-control-messages.ts +34 -0
  39. package/src/message-decoders.ts +82 -4
  40. package/src/node/bearer-resource-gate.ts +291 -0
  41. package/src/node/index.ts +13 -0
  42. package/src/observer-messages.ts +36 -0
  43. package/src/open-prompts.ts +76 -0
  44. package/src/outbound-delivery.ts +11 -2
  45. package/src/protocol-session.ts +7 -1
  46. package/src/session-directory-messages.ts +85 -0
  47. package/src/session-events.ts +93 -18
  48. package/src/session-message-handler.ts +53 -8
  49. package/src/session-query-messages.ts +105 -4
  50. package/src/wire-messages.ts +105 -7
  51. package/dist/browser/message-decoders-BB8n-H2X.d.ts.map +0 -1
  52. package/dist/browser/message-decoders-CDmDoZPR.js +0 -2
  53. package/dist/browser/message-decoders-CDmDoZPR.js.map +0 -1
@@ -3,9 +3,15 @@ import type {
3
3
  IPersonalUsageRequest,
4
4
  IUsageBySourceReport,
5
5
  } from '@robota-sdk/agent-interface-analytics';
6
- import type { ICommandResult } from '@robota-sdk/agent-interface-command';
6
+ import type {
7
+ ICommandListEntry,
8
+ ICommandResult,
9
+ ICommandSkillListEntry,
10
+ } from '@robota-sdk/agent-interface-command';
7
11
  import type {
8
12
  IBackgroundJobGroupState,
13
+ IExecutionDetailCursor,
14
+ IExecutionDetailPage,
9
15
  IExecutionWorkspaceSnapshot,
10
16
  TBackgroundJobGroupEvent,
11
17
  } from '@robota-sdk/agent-interface-execution';
@@ -27,22 +33,67 @@ import type {
27
33
  IPlanApprovalEvent,
28
34
  ISessionRenamedEvent,
29
35
  IToolState,
36
+ ISessionListing,
37
+ ISessionStatusSnapshot,
38
+ ISessionSwitchedEvent,
39
+ TSessionChangeRefusalCode,
30
40
  IUiIntentEvent,
31
41
  TPermissionResultValue,
42
+ TWaitingLoopStopOutcome,
43
+ } from '@robota-sdk/agent-interface-session';
44
+ import type {
45
+ ISessionConversationRead,
46
+ TDriverId,
47
+ TTurnSource,
32
48
  } from '@robota-sdk/agent-interface-session';
33
- import type { ISessionConversationRead, TDriverId } from '@robota-sdk/agent-interface-session';
34
49
  import type { TActionResponse } from '@robota-sdk/agent-interface-transport';
35
50
 
36
51
  export type TBackgroundControlAction = 'cancel' | 'close' | 'send';
37
52
 
53
+ type THistoryEntry = ReturnType<ISessionConversationRead['getFullHistory']>[number];
54
+
55
+ /**
56
+ * #3189: one entry of the session's full history as it crosses the wire. The same record the session
57
+ * keeps, except that `timestamp` is an ISO 8601 string: a `Date` does not survive JSON, and a
58
+ * declared `Date` that arrives as a string would let a client call `Date` methods on a string.
59
+ */
60
+ export interface IWireHistoryEntry extends Omit<THistoryEntry, 'timestamp'> {
61
+ /** ISO 8601. */
62
+ timestamp: string;
63
+ }
64
+
65
+ /**
66
+ * #3189: a turn's result as it crosses the wire. The session's whole history stays behind: it only
67
+ * grows, so a client reads it in bounded pages with `get-history` instead.
68
+ */
69
+ export type TWireExecutionResult = Omit<IExecutionResult, 'history'>;
70
+
38
71
  /** Inbound message from client to server. */
39
72
  export type TClientMessage =
40
73
  | { type: 'submit'; prompt: string }
41
- | { type: 'command'; name: string; args?: string }
74
+ // `requestId` is echoed on the command's `command_result` or `protocol_error`, so a client with
75
+ // several commands in flight knows which one each answer settles.
76
+ | { type: 'command'; name: string; args?: string; requestId?: string }
42
77
  | { type: 'abort' }
43
78
  | { type: 'cancel-queue' }
44
79
  | { type: 'get-messages' }
80
+ // #3189: the session's full history — what a client that renders the whole session (the TUI) shows —
81
+ // one bounded page at a time, from `fromIndex` (default 0). The client asks for the next page
82
+ // after the previous one arrived, so replies never pile up in the connection.
83
+ | { type: 'get-history'; fromIndex?: number }
84
+ // #3189: the permission and ask prompts open now, each sent again as the frame that asked it: a
85
+ // client that attached after a prompt was asked has not seen it.
86
+ | { type: 'get-prompts' }
45
87
  | { type: 'get-context' }
88
+ // #3186: what every client needs beside the conversation — the commands and skills it can offer
89
+ // (a `/` menu), and the session's status (model, permission mode, effort, context).
90
+ | { type: 'get-commands' }
91
+ | { type: 'get-status' }
92
+ // #3189: the host's sessions — list them, start a new one, make another current. A refused change
93
+ // answers `session_change_failed` with the same `requestId`.
94
+ | { type: 'list-sessions'; requestId: string }
95
+ | { type: 'new-session'; requestId?: string }
96
+ | { type: 'switch-session'; sessionId: string; requestId?: string }
46
97
  // SELFHOST-004: request the assembled trace/cost read-model (spans + cost-by-source) for the run.
47
98
  | { type: 'get-usage-report' }
48
99
  | {
@@ -55,6 +106,16 @@ export type TClientMessage =
55
106
  | { type: 'get-executing' }
56
107
  | { type: 'get-pending' }
57
108
  | { type: 'get-execution-workspace' }
109
+ // One page of what a workspace entry recorded, from `cursor` (the previous page's `nextCursor`).
110
+ // Answered by `execution_detail` or `execution_detail_error` with the same `requestId`.
111
+ | {
112
+ type: 'read-execution-detail';
113
+ requestId: string;
114
+ entryId: string;
115
+ cursor?: IExecutionDetailCursor;
116
+ }
117
+ // Stop the self-paced loop that is waiting for its next wake; answered by `waiting_loop_stop`.
118
+ | { type: 'stop-waiting-loop'; requestId: string }
58
119
  | { type: 'get-background-tasks'; filter?: IBackgroundTaskListFilter }
59
120
  | { type: 'get-background-task'; taskId: string }
60
121
  | { type: 'get-background-job-groups' }
@@ -82,8 +143,8 @@ export type TServerMessage =
82
143
  | { type: 'tool_start'; state: IToolState; driverId?: TDriverId }
83
144
  | { type: 'tool_end'; state: IToolState; driverId?: TDriverId }
84
145
  | { type: 'thinking'; isThinking: boolean; driverId?: TDriverId }
85
- | { type: 'complete'; result: IExecutionResult; driverId?: TDriverId }
86
- | { type: 'interrupted'; result: IExecutionResult; driverId?: TDriverId }
146
+ | { type: 'complete'; result: TWireExecutionResult; driverId?: TDriverId }
147
+ | { type: 'interrupted'; result: TWireExecutionResult; driverId?: TDriverId }
87
148
  | { type: 'error'; message: string; driverId?: TDriverId }
88
149
  | {
89
150
  type: 'command_result';
@@ -91,9 +152,40 @@ export type TServerMessage =
91
152
  message: string;
92
153
  success: boolean;
93
154
  data?: ICommandResult['data'];
155
+ /** The `requestId` of the `command` this answers, when it carried one. */
156
+ requestId?: string;
94
157
  }
95
158
  | { type: 'messages'; messages: ReturnType<ISessionConversationRead['getMessages']> }
159
+ // #3189: one page of the full history. `entries` start at `startIndex` of the `total` the session
160
+ // holds now, and stop before a page grows past a bounded size (a single larger entry is sent alone).
161
+ | { type: 'history'; startIndex: number; total: number; entries: IWireHistoryEntry[] }
162
+ // Sent in reply to `get-context`, and pushed whenever the session's context window changes.
96
163
  | { type: 'context'; state: ReturnType<ISessionConversationRead['getContextState']> }
164
+ // #3189: the full history gained entries that no streamed frame carries (a compaction, a skill
165
+ // activation, a memory event). A client that shows the full history re-reads it with `get-history`.
166
+ | { type: 'history_changed' }
167
+ // #3189: where the turn now starting came from (the user, a wake-up, a peer, an external event).
168
+ | { type: 'turn_source'; source: TTurnSource }
169
+ | { type: 'commands'; commands: ICommandListEntry[]; skills: ICommandSkillListEntry[] }
170
+ // Sent in reply to `get-status`, and pushed whenever the session's status changes.
171
+ | { type: 'session_status'; status: ISessionStatusSnapshot }
172
+ | { type: 'sessions'; requestId: string; listing: ISessionListing }
173
+ | {
174
+ type: 'sessions_error';
175
+ requestId: string;
176
+ code: 'not_available' | 'list_failed';
177
+ message: string;
178
+ }
179
+ // This client's session is now another one; it re-reads what it shows. A host that binds each
180
+ // client to its own session sends it only to the client that moved.
181
+ | { type: 'session_switched'; event: ISessionSwitchedEvent }
182
+ // #3189: a new-session or switch-session was refused; `code` says why without parsing `message`.
183
+ | {
184
+ type: 'session_change_failed';
185
+ code: TSessionChangeRefusalCode;
186
+ message: string;
187
+ requestId?: string;
188
+ }
97
189
  // SELFHOST-004 (P5, TC-08): carry the assembled trace/cost read-model (per-op span timeline +
98
190
  // cost-by-source) across the sidecar boundary — no existing variant carries per-op `durationMs` or
99
191
  // per-source `costUsd`. The GUI renders it renderer-side.
@@ -119,8 +211,13 @@ export type TServerMessage =
119
211
  message: string;
120
212
  }
121
213
  | { type: 'executing'; executing: boolean }
122
- | { type: 'pending'; pending: string | null }
214
+ // The next queued prompt and how many wait. Sent in reply to `get-pending`, and to a `submit` once
215
+ // the host has taken the prompt (queued it behind a running turn, or run it).
216
+ | { type: 'pending'; pending: string | null; pendingCount?: number }
123
217
  | { type: 'execution_workspace_event'; snapshot: IExecutionWorkspaceSnapshot }
218
+ | { type: 'execution_detail'; requestId: string; page: IExecutionDetailPage }
219
+ | { type: 'execution_detail_error'; requestId: string; message: string }
220
+ | { type: 'waiting_loop_stop'; requestId: string; outcome: TWaitingLoopStopOutcome }
124
221
  | { type: 'background_task_event'; event: TBackgroundTaskEvent }
125
222
  | { type: 'background_job_group_event'; event: TBackgroundJobGroupEvent }
126
223
  | { type: 'plan_event'; event: IPlanApprovalEvent }
@@ -151,7 +248,8 @@ export type TServerMessage =
151
248
  success: boolean;
152
249
  message?: string;
153
250
  }
154
- | { type: 'protocol_error'; message: string }
251
+ // `requestId` names the `command` whose failure this reports; a refusal of anything else has none.
252
+ | { type: 'protocol_error'; message: string; requestId?: string }
155
253
  // REMOTE-013 E4: sent instead of a replay when the client's `lastSeq` predates the host's retained buffer
156
254
  // (overrun) — the client must do a full `get-messages` refresh rather than accept a silent gap.
157
255
  | { type: 'resume_gap' };
@@ -1 +0,0 @@
1
- {"version":3,"file":"message-decoders-BB8n-H2X.d.ts","names":[],"sources":["../../src/wire-messages.ts","../../src/message-decoders.ts"],"mappings":";;;;;;KAmCY;;KAGA;EACN;EAAgB;;EAChB;EAAiB;EAAc;;EAC/B;;EACA;;EACA;;EACA;;EAEA;;EAEA;EACA;EACA,QAAQ;EACR;;EAEA;EAAyC;EAAmB;;EAC5D;;EACA;;EACA;;EACA;EAA8B,SAAS;;EACvC;EAA6B;;EAC7B;;EACA;EAAkC;;EAClC;EAAmC;;EACnC;EAAgC;EAAgB;;EAChD;EAA+B;;EAC/B;EAA8B;EAAgB,OAAO;;EACrD;EAAkC;EAAgB,SAAS;;EAE3D;EAA6B;EAAY,QAAQ;;EACjD;EAAsB;EAAY,UAAU;;EAG5C;EAAgB;;EAChB;EAAa;;;KAGP;EAIN;EAAoB;EAAe,WAAW;;EAC9C;EAAsB;EAAiB,WAAW;;EAClD;EAAoB,OAAO;EAAY,WAAW;;EAClD;EAAkB,OAAO;EAAY,WAAW;;EAChD;EAAkB;EAAqB,WAAW;;EAClD;EAAkB,QAAQ;EAAkB,WAAW;;EACvD;EAAqB,QAAQ;EAAkB,WAAW;;EAC1D;EAAe;EAAiB,WAAW;;EAE3C;EACA;EACA;EACA;EACA,OAAO;;EAEP;EAAkB,UAAU,WAAW;;EACvC;EAAiB,OAAO,WAAW;;EAInC;EAAsB,QAAQ;;EAC9B;EAA+B;EAAmB,QAAQ;;EAE1D;EACA;EACA;EACA;;EAGA;EACA;EACA;EACA,QAAQ;;EAGR;EACA;EACA;EACA;EACA;;EAEA;EAAmB;;EACnB;EAAiB;;EACjB;EAAmC,UAAU;;EAC7C;EAA+B,OAAO;;EACtC;EAAoC,OAAO;;EAC3C;EAAoB,OAAO;;EAC3B;EAAgC,OAAO;;EACvC;EAAsB,OAAO;;EAC7B;EAA0B,OAAO;;EACjC;EAAyB;EAAgB,MAAM;;EAC/C;EAA+B,QAAQ;;EACvC;EAA8B;EAAiB,OAAO;;EACtD;EAA6B;EAAgB,MAAM;;EAGnD;EAA4B,OAAO;;EACnC;EAAqB,OAAO;;EAC5B;EAAyB,OAAO;;EAIhC;EAAmB,OAAO;;EAG1B;EAAyB,OAAO;;EAChC;;EAEA;EACA,QAAQ;EACR;EACA;EACA;;EAEA;EAAwB;;EAGxB;;;;;;;;KAQM,oBAAoB;EAAmB;;;;KClJvC,qBAAqB;WAClB;WAAmB,SAAS;;WAC5B;WAAoB;;;cAGtB;;iBAgLG,oBAAoB,iBAAiB,qBAAqB;;iBAO1D,oBAAoB,iBAAiB,qBAAqB;;iBAS1D,YAAY,UAC1B,cACA,SAAS,mBAAmB,qBAAqB,YAChD,qBAAqB"}
@@ -1,2 +0,0 @@
1
- const e=4*1024*1024;function t(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}const n=e=>typeof e==`string`,r=e=>typeof e==`string`&&e.length>0,i=e=>typeof e==`boolean`,a=e=>typeof e==`number`&&Number.isFinite(e),o=e=>t=>t===void 0||e(t),s=e=>Array.isArray(e)&&e.every(t),c=e=>e===null||t(e),l=e=>t=>typeof t==`string`&&e.includes(t),u=[`agent`,`process`,`scheduled`],d=[`foreground`,`background`],f=[`queued`,`running`,`waiting_permission`,`sleeping`,`paused`,`completed`,`failed`,`cancelled`],p=[`cancel`,`close`,`send`],m={submit:{prompt:r},command:{name:r,args:o(n)},abort:{},"cancel-queue":{},"get-messages":{},"get-context":{},"get-usage-report":{},"get-personal-usage-report":{requestId:r,period:l([`7d`,`30d`]),timezone:r},"get-stored-session-usage-report":{requestId:r,sessionId:r},"get-executing":{},"get-pending":{},"get-execution-workspace":{},"get-background-tasks":{filter:o(e=>t(e)&&o(l(u))(e.kind)&&o(l(f))(e.status)&&o(l(d))(e.mode)&&o(i)(e.includeClosed))},"get-background-task":{taskId:r},"get-background-job-groups":{},"get-background-job-group":{groupId:r},"wait-background-job-group":{groupId:r},"cancel-background-task":{taskId:r,reason:o(n)},"close-background-task":{taskId:r},"send-background-task":{taskId:r,input:e=>t(e)&&o(n)(e.prompt)&&o(n)(e.stdin)},"read-background-task-log":{taskId:r,cursor:o(e=>t(e)&&a(e.offset))},"permission-response":{id:r,result:e=>i(e)||e===`allow-session`||e===`allow-project`},"ask-response":{id:r,response:e=>t(e)&&(e.type===`answer`&&Array.isArray(e.values)&&e.values.every(n)&&o(n)(e.text)||e.type===`cancelled`&&e.values===void 0&&e.text===void 0)},resume:{lastSeq:a},ack:{seq:a}},h={driverId:o(n)},g={text_delta:{delta:n,...h},user_message:{content:n,...h},tool_start:{state:t,...h},tool_end:{state:t,...h},thinking:{isThinking:i,...h},complete:{result:t,...h},interrupted:{result:t,...h},error:{message:n,...h},command_result:{name:n,message:n,success:i},messages:{messages:s},context:{state:t},usage_report:{report:t},personal_usage_report:{requestId:r,report:t},personal_usage_report_error:{requestId:r,code:l([`not_available`,`report_failed`]),message:n},stored_session_usage_report:{requestId:r,sessionId:r,report:t},stored_session_usage_report_error:{requestId:r,sessionId:r,code:l([`not_available`,`report_failed`]),message:n},executing:{executing:i},pending:{pending:e=>e===null||n(e)},execution_workspace_event:{snapshot:t},background_task_event:{event:t},background_job_group_event:{event:t},plan_event:{event:t},context_file_refreshed:{event:t},branch_event:{event:t},background_tasks:{tasks:s},background_task:{taskId:n,task:c},background_job_groups:{groups:s},background_job_group:{groupId:n,group:c},background_task_log:{taskId:n,page:t},permission_request:{event:t},ask_request:{event:t},prompt_resolved:{event:t},ui_intent:{event:t},session_renamed:{event:t},history_cleared:{},background_task_control_result:{action:l(p),taskId:n,success:i,message:o(n)},protocol_error:{message:n},resume_gap:{}};function _(e,r,i){if(!t(e))return{ok:!1,reason:`message must be a JSON object`};let a=e.type;if(!n(a))return{ok:!1,reason:`message type must be a string`};let o=Object.prototype.hasOwnProperty.call(r,a)?r[a]:void 0;if(o===void 0)return{ok:!1,reason:`Unknown message type: ${a}`};for(let[t,n]of Object.entries(o))if(!n(e[t]))return{ok:!1,reason:i(t)};return{ok:!0,message:e}}function v(e){return _(e,m,e=>e===`prompt`?`prompt must be a non-empty string`:`${e} has an invalid shape`)}function y(e){return _(e,g,e=>`${e} has an invalid shape`)}function b(e,t){if(e.length>4194304)return{ok:!1,reason:`Frame too large`};let n;try{n=JSON.parse(e)}catch{return{ok:!1,reason:`Invalid JSON`}}return t(n)}export{y as i,v as n,b as r,e as t};
2
- //# sourceMappingURL=message-decoders-CDmDoZPR.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message-decoders-CDmDoZPR.js","names":[],"sources":["../../src/message-decoders.ts"],"sourcesContent":["/**\n * Owner-side runtime decoders for the wire message unions (issue #2045 / TRANS-005 adjacent).\n *\n * `wire-messages.ts` owns the TypeScript unions; this file owns turning `JSON.parse` output into one\n * of them. Before it, the server did `JSON.parse(data) as TClientMessage` and each client did the\n * same for `TServerMessage`, so valid JSON with an invalid shape crossed the boundary wearing a\n * protocol type. Carriers now implement `raw string → owner decoder → typed message` and answer a\n * refusal with the protocol's own `protocol_error` (server side) or client error (client side).\n *\n * `decodeClientMessage` is TOTAL: every variant and every nested field it carries is validated.\n * `decodeServerMessage` validates every variant's discriminator and declared top-level fields; a\n * payload OWNED BY ANOTHER PACKAGE (`IToolState`, `IExecutionResult`, background events, …) is\n * checked as a non-array record (or array of records) here — its own total decoding belongs to its\n * owner and is tracked there, not duplicated in a transport package.\n */\n\nimport type { TBackgroundControlAction, TClientMessage, TServerMessage } from './wire-messages.js';\n\nexport type TMessageDecodeResult<TMessage> =\n | { readonly ok: true; readonly message: TMessage }\n | { readonly ok: false; readonly reason: string };\n\n/** Serialized frames larger than this are refused before any parse (a bounded inbound budget). */\nexport const MAX_INBOUND_FRAME_BYTES = 4 * 1024 * 1024;\n\ntype TRecord = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is TRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\nconst isString = (v: unknown): v is string => typeof v === 'string';\nconst isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.length > 0;\nconst isBoolean = (v: unknown): v is boolean => typeof v === 'boolean';\nconst isFiniteNumber = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);\nconst isOptional =\n (check: (v: unknown) => boolean) =>\n (v: unknown): boolean =>\n v === undefined || check(v);\nconst isRecordArray = (v: unknown): boolean => Array.isArray(v) && v.every(isRecord);\nconst isNullableRecord = (v: unknown): boolean => v === null || isRecord(v);\nconst oneOf =\n (members: readonly string[]) =>\n (v: unknown): boolean =>\n typeof v === 'string' && members.includes(v);\n\ntype TFieldCheck = (value: unknown) => boolean;\ntype TVariantShape = Readonly<Record<string, TFieldCheck>>;\n\nconst BACKGROUND_TASK_KINDS = ['agent', 'process', 'scheduled'] as const;\nconst BACKGROUND_TASK_MODES = ['foreground', 'background'] as const;\nconst BACKGROUND_TASK_STATUSES = [\n 'queued',\n 'running',\n 'waiting_permission',\n 'sleeping',\n 'paused',\n 'completed',\n 'failed',\n 'cancelled',\n] as const;\nconst BACKGROUND_CONTROL_ACTIONS: readonly TBackgroundControlAction[] = ['cancel', 'close', 'send'];\n\nconst isBackgroundTaskListFilter: TFieldCheck = (v) =>\n isRecord(v) &&\n isOptional(oneOf(BACKGROUND_TASK_KINDS))(v['kind']) &&\n isOptional(oneOf(BACKGROUND_TASK_STATUSES))(v['status']) &&\n isOptional(oneOf(BACKGROUND_TASK_MODES))(v['mode']) &&\n isOptional(isBoolean)(v['includeClosed']);\nconst isBackgroundTaskInput: TFieldCheck = (v) =>\n isRecord(v) && isOptional(isString)(v['prompt']) && isOptional(isString)(v['stdin']);\nconst isBackgroundTaskLogCursor: TFieldCheck = (v) => isRecord(v) && isFiniteNumber(v['offset']);\nconst isPermissionResultValue: TFieldCheck = (v) =>\n isBoolean(v) || v === 'allow-session' || v === 'allow-project';\nconst isActionResponse: TFieldCheck = (v) =>\n isRecord(v) &&\n ((v['type'] === 'answer' &&\n Array.isArray(v['values']) &&\n v['values'].every(isString) &&\n isOptional(isString)(v['text'])) ||\n (v['type'] === 'cancelled' && v['values'] === undefined && v['text'] === undefined));\n\n/** One entry per `TClientMessage` variant — a variant added to the union without one fails the test. */\nexport const CLIENT_MESSAGE_SHAPES: Readonly<Record<TClientMessage['type'], TVariantShape>> = {\n submit: { prompt: isNonEmptyString },\n command: { name: isNonEmptyString, args: isOptional(isString) },\n abort: {},\n 'cancel-queue': {},\n 'get-messages': {},\n 'get-context': {},\n 'get-usage-report': {},\n 'get-personal-usage-report': {\n requestId: isNonEmptyString,\n period: oneOf(['7d', '30d']),\n timezone: isNonEmptyString,\n },\n 'get-stored-session-usage-report': {\n requestId: isNonEmptyString,\n sessionId: isNonEmptyString,\n },\n 'get-executing': {},\n 'get-pending': {},\n 'get-execution-workspace': {},\n 'get-background-tasks': { filter: isOptional(isBackgroundTaskListFilter) },\n 'get-background-task': { taskId: isNonEmptyString },\n 'get-background-job-groups': {},\n 'get-background-job-group': { groupId: isNonEmptyString },\n 'wait-background-job-group': { groupId: isNonEmptyString },\n 'cancel-background-task': { taskId: isNonEmptyString, reason: isOptional(isString) },\n 'close-background-task': { taskId: isNonEmptyString },\n 'send-background-task': { taskId: isNonEmptyString, input: isBackgroundTaskInput },\n 'read-background-task-log': {\n taskId: isNonEmptyString,\n cursor: isOptional(isBackgroundTaskLogCursor),\n },\n 'permission-response': { id: isNonEmptyString, result: isPermissionResultValue },\n 'ask-response': { id: isNonEmptyString, response: isActionResponse },\n resume: { lastSeq: isFiniteNumber },\n ack: { seq: isFiniteNumber },\n};\n\nconst authored: TVariantShape = { driverId: isOptional(isString) };\n\n/** One entry per `TServerMessage` variant. Foreign-owned payloads are checked as records (see header). */\nexport const SERVER_MESSAGE_SHAPES: Readonly<Record<TServerMessage['type'], TVariantShape>> = {\n text_delta: { delta: isString, ...authored },\n user_message: { content: isString, ...authored },\n tool_start: { state: isRecord, ...authored },\n tool_end: { state: isRecord, ...authored },\n thinking: { isThinking: isBoolean, ...authored },\n complete: { result: isRecord, ...authored },\n interrupted: { result: isRecord, ...authored },\n error: { message: isString, ...authored },\n command_result: { name: isString, message: isString, success: isBoolean },\n messages: { messages: isRecordArray },\n context: { state: isRecord },\n usage_report: { report: isRecord },\n personal_usage_report: { requestId: isNonEmptyString, report: isRecord },\n personal_usage_report_error: {\n requestId: isNonEmptyString,\n code: oneOf(['not_available', 'report_failed']),\n message: isString,\n },\n stored_session_usage_report: {\n requestId: isNonEmptyString,\n sessionId: isNonEmptyString,\n report: isRecord,\n },\n stored_session_usage_report_error: {\n requestId: isNonEmptyString,\n sessionId: isNonEmptyString,\n code: oneOf(['not_available', 'report_failed']),\n message: isString,\n },\n executing: { executing: isBoolean },\n pending: { pending: (v) => v === null || isString(v) },\n execution_workspace_event: { snapshot: isRecord },\n background_task_event: { event: isRecord },\n background_job_group_event: { event: isRecord },\n plan_event: { event: isRecord },\n context_file_refreshed: { event: isRecord },\n branch_event: { event: isRecord },\n background_tasks: { tasks: isRecordArray },\n background_task: { taskId: isString, task: isNullableRecord },\n background_job_groups: { groups: isRecordArray },\n background_job_group: { groupId: isString, group: isNullableRecord },\n background_task_log: { taskId: isString, page: isRecord },\n permission_request: { event: isRecord },\n ask_request: { event: isRecord },\n prompt_resolved: { event: isRecord },\n ui_intent: { event: isRecord },\n session_renamed: { event: isRecord },\n history_cleared: {},\n background_task_control_result: {\n action: oneOf(BACKGROUND_CONTROL_ACTIONS),\n taskId: isString,\n success: isBoolean,\n message: isOptional(isString),\n },\n protocol_error: { message: isString },\n resume_gap: {},\n};\n\nfunction decodeVariant<TMessage extends { type: string }>(\n value: unknown,\n shapes: Readonly<Record<string, TVariantShape>>,\n reasonFor: (field: string) => string,\n): TMessageDecodeResult<TMessage> {\n if (!isRecord(value)) return { ok: false, reason: 'message must be a JSON object' };\n const type = value['type'];\n if (!isString(type)) return { ok: false, reason: 'message type must be a string' };\n const shape = Object.prototype.hasOwnProperty.call(shapes, type) ? shapes[type] : undefined;\n if (shape === undefined) return { ok: false, reason: `Unknown message type: ${type}` };\n for (const [field, check] of Object.entries(shape)) {\n if (!check(value[field])) return { ok: false, reason: reasonFor(field) };\n }\n return { ok: true, message: value as unknown as TMessage };\n}\n\n/** Total decode of a parsed client frame. */\nexport function decodeClientMessage(value: unknown): TMessageDecodeResult<TClientMessage> {\n return decodeVariant<TClientMessage>(value, CLIENT_MESSAGE_SHAPES, (field) =>\n field === 'prompt' ? 'prompt must be a non-empty string' : `${field} has an invalid shape`,\n );\n}\n\n/** Variant-level decode of a parsed server frame (foreign-owned payloads as records, see header). */\nexport function decodeServerMessage(value: unknown): TMessageDecodeResult<TServerMessage> {\n return decodeVariant<TServerMessage>(\n value,\n SERVER_MESSAGE_SHAPES,\n (field) => `${field} has an invalid shape`,\n );\n}\n\n/** `raw string → JSON → owner decoder`, the one path a carrier implements. */\nexport function decodeFrame<TMessage>(\n data: string,\n decode: (value: unknown) => TMessageDecodeResult<TMessage>,\n): TMessageDecodeResult<TMessage> {\n if (data.length > MAX_INBOUND_FRAME_BYTES) return { ok: false, reason: 'Frame too large' };\n let parsed: unknown;\n try {\n parsed = JSON.parse(data);\n // allow-fallback: the parse failure is returned as a typed refusal the carrier answers on the wire\n } catch {\n return { ok: false, reason: 'Invalid JSON' };\n }\n return decode(parsed);\n}\n"],"mappings":"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"}