@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.
- package/CHANGELOG.md +1137 -0
- package/LICENSE +661 -0
- package/README.md +121 -0
- package/dist/browser/client.d.ts +2 -0
- package/dist/browser/client.js +1 -0
- package/dist/browser/index.d.ts +624 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/observer-messages-4fRQZU2D.d.ts +499 -0
- package/dist/browser/observer-messages-4fRQZU2D.d.ts.map +1 -0
- package/dist/browser/observer-messages-O8jTPEQO.js +2 -0
- package/dist/browser/observer-messages-O8jTPEQO.js.map +1 -0
- package/dist/node/index.cjs +1 -0
- package/dist/node/index.d.cts +1115 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +1115 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +2 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/node.cjs +1 -0
- package/dist/node/node.d.cts +317 -0
- package/dist/node/node.d.cts.map +1 -0
- package/dist/node/node.d.ts +317 -0
- package/dist/node/node.d.ts.map +1 -0
- package/dist/node/node.js +2 -0
- package/dist/node/node.js.map +1 -0
- package/package.json +122 -0
- package/src/__tests__/__fixtures__/korean-text.json +6 -0
- package/src/__tests__/access-token-verifier.test.ts +672 -0
- package/src/__tests__/admission.test.ts +130 -0
- package/src/__tests__/app-tool-messages.test.ts +463 -0
- package/src/__tests__/bearer-resource-gate.test.ts +120 -0
- package/src/__tests__/channel-frames.test.ts +215 -0
- package/src/__tests__/driver-attribution-capability.test.ts +83 -0
- package/src/__tests__/file-transfer.test.ts +281 -0
- package/src/__tests__/handoff-chunking.test.ts +261 -0
- package/src/__tests__/handoff-manifest.test.ts +52 -0
- package/src/__tests__/history-attached-budget.test.ts +216 -0
- package/src/__tests__/message-decoders.test.ts +870 -0
- package/src/__tests__/outbound-delivery.test.ts +558 -0
- package/src/__tests__/peer-message-ledger.test.ts +200 -0
- package/src/__tests__/permission-summary.test.ts +18 -0
- package/src/__tests__/personal-usage-report.test.ts +193 -0
- package/src/__tests__/project-read-messages.test.ts +118 -0
- package/src/__tests__/resume-buffer.test.ts +93 -0
- package/src/__tests__/session-detail-loop-status.test.ts +169 -0
- package/src/__tests__/session-directory-messages.test.ts +322 -0
- package/src/__tests__/session-error-classification.test.ts +113 -0
- package/src/__tests__/session-event-delivery.test.ts +101 -0
- package/src/__tests__/session-history-wire.test.ts +364 -0
- package/src/__tests__/session-message-handler-e5.test.ts +99 -0
- package/src/__tests__/session-message-handler-observe.test.ts +223 -0
- package/src/__tests__/session-message-handler.test.ts +726 -0
- package/src/__tests__/session-resume-bridge.test.ts +472 -0
- package/src/__tests__/settings-messages.test.ts +193 -0
- package/src/__tests__/stdio-carrier.test.ts +85 -0
- package/src/__tests__/submit-prompt-shape.test.ts +78 -0
- package/src/__tests__/usage-report-carrier.test.ts +98 -0
- package/src/__tests__/ws-broadcast-events.test.ts +94 -0
- package/src/app-tool-messages.ts +140 -0
- package/src/background-messages.ts +169 -0
- package/src/channel-frames.ts +182 -0
- package/src/client.ts +17 -0
- package/src/handoff-chunking.ts +275 -0
- package/src/index.ts +135 -0
- package/src/loop-control-messages.ts +34 -0
- package/src/message-decoders.ts +485 -0
- package/src/message-parser.ts +33 -0
- package/src/node/access-token-verifier.ts +363 -0
- package/src/node/admission.ts +153 -0
- package/src/node/bearer-resource-gate.ts +331 -0
- package/src/node/file-transfer.ts +530 -0
- package/src/node/handoff-manifest.ts +77 -0
- package/src/node/index.ts +45 -0
- package/src/node/stdio-carrier.ts +59 -0
- package/src/observer-messages.ts +47 -0
- package/src/open-prompts.ts +76 -0
- package/src/outbound-delivery.ts +240 -0
- package/src/peer-message-ledger.ts +193 -0
- package/src/permission-summary.ts +18 -0
- package/src/project-read-messages.ts +74 -0
- package/src/protocol-session.ts +56 -0
- package/src/resume-buffer.ts +123 -0
- package/src/session-directory-messages.ts +162 -0
- package/src/session-error-classification.ts +91 -0
- package/src/session-events.ts +290 -0
- package/src/session-message-handler.ts +413 -0
- package/src/session-query-messages.ts +160 -0
- package/src/session-resume-bridge.ts +275 -0
- package/src/settings-messages.ts +103 -0
- package/src/usage-messages.ts +195 -0
- package/src/wire-messages.ts +436 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound session-event subscription for the WS/WebRTC transports (split from `session-message-handler.ts` —
|
|
3
|
+
* the inbound message routing stays there; this module owns the session→`TServerMessage` fan-out).
|
|
4
|
+
*
|
|
5
|
+
* Used by `createSessionMessageHandler` (one subscription per channel) and by the persistent
|
|
6
|
+
* `SessionResumeBridge` (REMOTE-013 E4 — a SINGLE subscription that outlives per-channel handlers).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { holdOpenPrompts } from './open-prompts.js';
|
|
10
|
+
import { classifySessionErrorForWire } from './session-error-classification.js';
|
|
11
|
+
|
|
12
|
+
import type { TOutboundDeliver } from './outbound-delivery.js';
|
|
13
|
+
import type { IProtocolSession } from './protocol-session.js';
|
|
14
|
+
import type { TWireExecutionResult } from './wire-messages.js';
|
|
15
|
+
import type {
|
|
16
|
+
IExecutionWorkspaceEvent,
|
|
17
|
+
TBackgroundJobGroupEvent,
|
|
18
|
+
TBackgroundTaskEvent,
|
|
19
|
+
} from '@robota-sdk/agent-interface-execution';
|
|
20
|
+
import type {
|
|
21
|
+
TDriverId,
|
|
22
|
+
TInteractiveEventName,
|
|
23
|
+
TTurnSource,
|
|
24
|
+
} from '@robota-sdk/agent-interface-session';
|
|
25
|
+
import type {
|
|
26
|
+
IAskRequestEvent,
|
|
27
|
+
IBranchEvent,
|
|
28
|
+
ICompactEvent,
|
|
29
|
+
IContextFileRefreshedEvent,
|
|
30
|
+
IExecutionResult,
|
|
31
|
+
IPermissionRequestEvent,
|
|
32
|
+
IPromptResolvedEvent,
|
|
33
|
+
IPlanApprovalEvent,
|
|
34
|
+
ISessionRenamedEvent,
|
|
35
|
+
ISessionStatusSnapshot,
|
|
36
|
+
ISessionSwitchedEvent,
|
|
37
|
+
IToolState,
|
|
38
|
+
IUiIntentEvent,
|
|
39
|
+
} from '@robota-sdk/agent-interface-session';
|
|
40
|
+
|
|
41
|
+
export type TProtocolSessionEventClassification = 'forwarded' | 'requester-routed' | 'non-surface';
|
|
42
|
+
|
|
43
|
+
/** Exhaustive protocol-owner policy over the shared session-event vocabulary. */
|
|
44
|
+
export const PROTOCOL_SESSION_EVENT_CLASSIFICATION = {
|
|
45
|
+
text_delta: 'forwarded',
|
|
46
|
+
tool_start: 'forwarded',
|
|
47
|
+
tool_end: 'forwarded',
|
|
48
|
+
thinking: 'forwarded',
|
|
49
|
+
thinking_delta: 'non-surface',
|
|
50
|
+
complete: 'forwarded',
|
|
51
|
+
error: 'forwarded',
|
|
52
|
+
context_update: 'forwarded',
|
|
53
|
+
compact: 'forwarded',
|
|
54
|
+
interrupted: 'forwarded',
|
|
55
|
+
skill_activation: 'forwarded',
|
|
56
|
+
background_task_event: 'forwarded',
|
|
57
|
+
background_job_group_event: 'forwarded',
|
|
58
|
+
execution_workspace_event: 'forwarded',
|
|
59
|
+
user_message: 'forwarded',
|
|
60
|
+
turn_source: 'forwarded',
|
|
61
|
+
context_file_refreshed: 'forwarded',
|
|
62
|
+
memory_event: 'forwarded',
|
|
63
|
+
goal_event: 'non-surface',
|
|
64
|
+
plan_event: 'forwarded',
|
|
65
|
+
branch_event: 'forwarded',
|
|
66
|
+
permission_request: 'forwarded',
|
|
67
|
+
ask_request: 'forwarded',
|
|
68
|
+
prompt_resolved: 'forwarded',
|
|
69
|
+
ui_intent: 'requester-routed',
|
|
70
|
+
session_renamed: 'forwarded',
|
|
71
|
+
history_cleared: 'forwarded',
|
|
72
|
+
session_switched: 'forwarded',
|
|
73
|
+
status_changed: 'forwarded',
|
|
74
|
+
} as const satisfies Record<TInteractiveEventName, TProtocolSessionEventClassification>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Options for {@link subscribeSessionEvents}.
|
|
78
|
+
*
|
|
79
|
+
* ARCH-030: `onDeliveryError` is NOT here. Carrier-failure containment moved to the one
|
|
80
|
+
* connection-scoped {@link TOutboundDeliver} boundary this function now receives, so the fan-out and
|
|
81
|
+
* every reply share a single guard instead of the fan-out having its own.
|
|
82
|
+
*/
|
|
83
|
+
export interface ISubscribeSessionEventsOptions {
|
|
84
|
+
/**
|
|
85
|
+
* CMD-004 Stage D: lazily read the SERVER-ASSIGNED driver id of THIS surface — `ui_intent` is
|
|
86
|
+
* requester-routed against it (lazy because the resume bridge binds the id only after pairing).
|
|
87
|
+
*/
|
|
88
|
+
getSurfaceDriverId?: () => TDriverId | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* `false` leaves `permission_request` and `ask_request` unsubscribed. The session parks a prompt
|
|
91
|
+
* only while someone listens for it, so a surface that may not answer must not listen: otherwise an
|
|
92
|
+
* unattended session would hold a prompt nobody can settle instead of failing it closed at once.
|
|
93
|
+
* Defaults to `true`.
|
|
94
|
+
*/
|
|
95
|
+
receivePrompts?: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Subscribe the session's events and forward each as a `TServerMessage` through the connection's
|
|
100
|
+
* outbound boundary; returns an unsubscribe. Exported (REMOTE-013 E4) so the persistent
|
|
101
|
+
* `SessionResumeBridge` can own a SINGLE subscription that outlives per-channel handlers.
|
|
102
|
+
*
|
|
103
|
+
* ARCH-030: the second parameter is the boundary, not a raw sink — a carrier `send` cannot be passed
|
|
104
|
+
* here, which is what keeps the fan-out and the reply paths on one guard.
|
|
105
|
+
*/
|
|
106
|
+
export function subscribeSessionEvents(
|
|
107
|
+
session: IProtocolSession,
|
|
108
|
+
deliver: TOutboundDeliver,
|
|
109
|
+
options: ISubscribeSessionEventsOptions = {},
|
|
110
|
+
): () => void {
|
|
111
|
+
// ARCH-030: no local guard wrapper any more, and no event name passed alongside the message. The name
|
|
112
|
+
// existed so the local guard could label a failure with it; the boundary labels with `message.type`,
|
|
113
|
+
// the frame that could not be delivered. Keeping the argument would have been a parameter that
|
|
114
|
+
// documents nothing and is read by nobody.
|
|
115
|
+
// REMOTE-014 E5: stamp the ACTIVE turn's driver id onto TURN-AUTHORED events (co-drive authorship,
|
|
116
|
+
// display-only), read at emit time. Only these events — background/goal/memory/execution-workspace events
|
|
117
|
+
// are NOT authored by a driver turn and carry no `driverId`. `undefined` when idle or unattributed.
|
|
118
|
+
const attr = (): { driverId?: TDriverId } => {
|
|
119
|
+
// ARCH-012: a plain call, not `?.() ?? undefined`. The member is required, so `null` means
|
|
120
|
+
// nobody is driving — it can no longer also mean "this host cannot answer".
|
|
121
|
+
const driverId = session.getActiveDriverId();
|
|
122
|
+
return driverId ? { driverId } : {};
|
|
123
|
+
};
|
|
124
|
+
const onUserMessage = (content: string): void =>
|
|
125
|
+
deliver({ type: 'user_message', content, ...attr() });
|
|
126
|
+
const onTextDelta = (delta: string): void => deliver({ type: 'text_delta', delta, ...attr() });
|
|
127
|
+
const onToolStart = (state: IToolState): void =>
|
|
128
|
+
deliver({ type: 'tool_start', state, ...attr() });
|
|
129
|
+
const onToolEnd = (state: IToolState): void => deliver({ type: 'tool_end', state, ...attr() });
|
|
130
|
+
const onThinking = (isThinking: boolean): void =>
|
|
131
|
+
deliver({ type: 'thinking', isThinking, ...attr() });
|
|
132
|
+
const onComplete = (result: IExecutionResult): void =>
|
|
133
|
+
deliver({ type: 'complete', result: withoutHistory(result), ...attr() });
|
|
134
|
+
const onInterrupted = (result: IExecutionResult): void =>
|
|
135
|
+
deliver({ type: 'interrupted', result: withoutHistory(result), ...attr() });
|
|
136
|
+
const onError = (error: Error): void =>
|
|
137
|
+
deliver({
|
|
138
|
+
type: 'error',
|
|
139
|
+
message: error.message,
|
|
140
|
+
...classifySessionErrorForWire(error),
|
|
141
|
+
...attr(),
|
|
142
|
+
});
|
|
143
|
+
const onBackgroundTaskEvent = (event: TBackgroundTaskEvent): void =>
|
|
144
|
+
deliver({ type: 'background_task_event', event });
|
|
145
|
+
const onBackgroundJobGroupEvent = (event: TBackgroundJobGroupEvent): void =>
|
|
146
|
+
deliver({ type: 'background_job_group_event', event });
|
|
147
|
+
const onExecutionWorkspace = (event: IExecutionWorkspaceEvent): void =>
|
|
148
|
+
deliver({
|
|
149
|
+
type: 'execution_workspace_event',
|
|
150
|
+
snapshot: event.snapshot,
|
|
151
|
+
});
|
|
152
|
+
const onPlanEvent = (event: IPlanApprovalEvent): void => deliver({ type: 'plan_event', event });
|
|
153
|
+
const onContextFileRefreshed = (event: IContextFileRefreshedEvent): void =>
|
|
154
|
+
deliver({ type: 'context_file_refreshed', event });
|
|
155
|
+
const onBranchEvent = (event: IBranchEvent): void => deliver({ type: 'branch_event', event });
|
|
156
|
+
// REMOTE-007: forward the transport-neutral prompt events so a remote surface can render + answer the
|
|
157
|
+
// SAME permission/ask prompt; `prompt_resolved` dismisses it when another surface answered first.
|
|
158
|
+
// #3189: kept while open, so a client that attaches later can ask for it with `get-prompts`.
|
|
159
|
+
const receivePrompts = options.receivePrompts ?? true;
|
|
160
|
+
const openPrompts = receivePrompts ? holdOpenPrompts(session) : undefined;
|
|
161
|
+
const onPermissionRequest = (event: IPermissionRequestEvent): void => {
|
|
162
|
+
const frame = { type: 'permission_request', event } as const;
|
|
163
|
+
openPrompts?.record(frame);
|
|
164
|
+
deliver(frame);
|
|
165
|
+
};
|
|
166
|
+
const onAskRequest = (event: IAskRequestEvent): void => {
|
|
167
|
+
const frame = { type: 'ask_request', event } as const;
|
|
168
|
+
openPrompts?.record(frame);
|
|
169
|
+
deliver(frame);
|
|
170
|
+
};
|
|
171
|
+
const onPromptResolved = (event: IPromptResolvedEvent): void => {
|
|
172
|
+
openPrompts?.forget(event.id);
|
|
173
|
+
deliver({ type: 'prompt_resolved', event });
|
|
174
|
+
};
|
|
175
|
+
// CMD-004 Stage D: `ui_intent` is REQUESTER-ROUTED — delivered only to the surface whose
|
|
176
|
+
// server-assigned driver id issued the command. An UNATTRIBUTED intent (no requester id, e.g. an
|
|
177
|
+
// idle model-invoked command) is unroutable and reaches every surface — never a silent drop.
|
|
178
|
+
const onUiIntent = (event: IUiIntentEvent): void => {
|
|
179
|
+
if (
|
|
180
|
+
event.requesterDriverId !== undefined &&
|
|
181
|
+
event.requesterDriverId !== options.getSurfaceDriverId?.()
|
|
182
|
+
) {
|
|
183
|
+
return; // another surface's intent — this surface never sees it (and never buffers it)
|
|
184
|
+
}
|
|
185
|
+
deliver({ type: 'ui_intent', event });
|
|
186
|
+
};
|
|
187
|
+
// CMD-004 Stage E: BROADCAST session-state events — the host executed the rename/clear; EVERY
|
|
188
|
+
// attached surface (co-driving included) reflects it. Never requester-filtered, unlike `ui_intent`.
|
|
189
|
+
const onSessionRenamed = (event: ISessionRenamedEvent): void =>
|
|
190
|
+
deliver({ type: 'session_renamed', event });
|
|
191
|
+
const onHistoryCleared = (): void => deliver({ type: 'history_cleared' });
|
|
192
|
+
// #3189: this surface's session is now another one; it re-reads what it shows. Only surfaces
|
|
193
|
+
// subscribed to the session that moved hear it: a host that binds each connection to its own session
|
|
194
|
+
// sends it to the connection that switched, never to the others.
|
|
195
|
+
const onSessionSwitched = (event: ISessionSwitchedEvent): void => {
|
|
196
|
+
// This connection no longer answers the previous session's prompts; it reads the new session's anew.
|
|
197
|
+
openPrompts?.clear();
|
|
198
|
+
deliver({ type: 'session_switched', event });
|
|
199
|
+
};
|
|
200
|
+
// #3189: what a client that renders the whole session (the TUI) needs beside the streamed turn.
|
|
201
|
+
// The context window is pushed in the frame `get-context` answers with. A compaction, a skill
|
|
202
|
+
// activation or a memory event adds history entries no streamed frame carries, so the client is
|
|
203
|
+
// told the history changed and re-reads it with `get-history`; the entries themselves are not sent.
|
|
204
|
+
const onContextUpdate = (state: ReturnType<IProtocolSession['getContextState']>): void =>
|
|
205
|
+
deliver({ type: 'context', state });
|
|
206
|
+
const onHistoryChanged = (): void => deliver({ type: 'history_changed' });
|
|
207
|
+
const onCompact = (event: ICompactEvent): void =>
|
|
208
|
+
deliver({
|
|
209
|
+
type: 'history_changed',
|
|
210
|
+
compact: {
|
|
211
|
+
trigger: event.trigger,
|
|
212
|
+
beforeUsedPercentage: event.before.usedPercentage,
|
|
213
|
+
afterUsedPercentage: event.after.usedPercentage,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
const onTurnSource = (source: TTurnSource): void => deliver({ type: 'turn_source', source });
|
|
217
|
+
// One client's `/mode` or `/model` shows on every client on this session, in the frame
|
|
218
|
+
// `get-status` answers with.
|
|
219
|
+
const onStatusChanged = (status: ISessionStatusSnapshot): void =>
|
|
220
|
+
deliver({ type: 'session_status', status });
|
|
221
|
+
|
|
222
|
+
session.on('user_message', onUserMessage);
|
|
223
|
+
session.on('text_delta', onTextDelta);
|
|
224
|
+
session.on('tool_start', onToolStart);
|
|
225
|
+
session.on('tool_end', onToolEnd);
|
|
226
|
+
session.on('thinking', onThinking);
|
|
227
|
+
session.on('complete', onComplete);
|
|
228
|
+
session.on('interrupted', onInterrupted);
|
|
229
|
+
session.on('error', onError);
|
|
230
|
+
session.on('background_task_event', onBackgroundTaskEvent);
|
|
231
|
+
session.on('background_job_group_event', onBackgroundJobGroupEvent);
|
|
232
|
+
session.on('execution_workspace_event', onExecutionWorkspace);
|
|
233
|
+
session.on('plan_event', onPlanEvent);
|
|
234
|
+
session.on('context_file_refreshed', onContextFileRefreshed);
|
|
235
|
+
session.on('branch_event', onBranchEvent);
|
|
236
|
+
if (receivePrompts) {
|
|
237
|
+
session.on('permission_request', onPermissionRequest);
|
|
238
|
+
session.on('ask_request', onAskRequest);
|
|
239
|
+
}
|
|
240
|
+
session.on('prompt_resolved', onPromptResolved);
|
|
241
|
+
session.on('ui_intent', onUiIntent);
|
|
242
|
+
session.on('session_renamed', onSessionRenamed);
|
|
243
|
+
session.on('history_cleared', onHistoryCleared);
|
|
244
|
+
session.on('session_switched', onSessionSwitched);
|
|
245
|
+
session.on('context_update', onContextUpdate);
|
|
246
|
+
session.on('compact', onCompact);
|
|
247
|
+
session.on('skill_activation', onHistoryChanged);
|
|
248
|
+
session.on('memory_event', onHistoryChanged);
|
|
249
|
+
session.on('turn_source', onTurnSource);
|
|
250
|
+
session.on('status_changed', onStatusChanged);
|
|
251
|
+
|
|
252
|
+
return (): void => {
|
|
253
|
+
session.off('user_message', onUserMessage);
|
|
254
|
+
session.off('text_delta', onTextDelta);
|
|
255
|
+
session.off('tool_start', onToolStart);
|
|
256
|
+
session.off('tool_end', onToolEnd);
|
|
257
|
+
session.off('thinking', onThinking);
|
|
258
|
+
session.off('complete', onComplete);
|
|
259
|
+
session.off('interrupted', onInterrupted);
|
|
260
|
+
session.off('error', onError);
|
|
261
|
+
session.off('background_task_event', onBackgroundTaskEvent);
|
|
262
|
+
session.off('background_job_group_event', onBackgroundJobGroupEvent);
|
|
263
|
+
session.off('execution_workspace_event', onExecutionWorkspace);
|
|
264
|
+
session.off('plan_event', onPlanEvent);
|
|
265
|
+
session.off('context_file_refreshed', onContextFileRefreshed);
|
|
266
|
+
session.off('branch_event', onBranchEvent);
|
|
267
|
+
if (receivePrompts) {
|
|
268
|
+
session.off('permission_request', onPermissionRequest);
|
|
269
|
+
session.off('ask_request', onAskRequest);
|
|
270
|
+
}
|
|
271
|
+
session.off('prompt_resolved', onPromptResolved);
|
|
272
|
+
session.off('ui_intent', onUiIntent);
|
|
273
|
+
session.off('session_renamed', onSessionRenamed);
|
|
274
|
+
session.off('history_cleared', onHistoryCleared);
|
|
275
|
+
session.off('session_switched', onSessionSwitched);
|
|
276
|
+
session.off('context_update', onContextUpdate);
|
|
277
|
+
session.off('compact', onCompact);
|
|
278
|
+
session.off('skill_activation', onHistoryChanged);
|
|
279
|
+
session.off('memory_event', onHistoryChanged);
|
|
280
|
+
session.off('turn_source', onTurnSource);
|
|
281
|
+
session.off('status_changed', onStatusChanged);
|
|
282
|
+
openPrompts?.release();
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** The turn's result without the session's history: that only grows, and `get-history` pages it. */
|
|
287
|
+
function withoutHistory(result: IExecutionResult): TWireExecutionResult {
|
|
288
|
+
const { history: _history, ...rest } = result;
|
|
289
|
+
return rest;
|
|
290
|
+
}
|