@workerdeck/protocol 0.23.0 → 1.1.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.
- package/build/index.d.mts +204 -1707
- package/build/index.mjs +56 -461
- package/build/index.mjs.map +1 -1
- package/package.json +5 -4
package/build/index.d.mts
CHANGED
|
@@ -1,120 +1,46 @@
|
|
|
1
1
|
//#region src/session-list.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* How a sessions list is filtered, grouped and sorted — the whole of the view
|
|
4
|
-
* config, kept pure and separate from the components so every surface renders
|
|
5
|
-
* one derived list and nothing else decides what is visible.
|
|
6
|
-
*
|
|
7
|
-
* Framework-free like `transcript.ts`, and for the same reason: more than one
|
|
8
|
-
* party has to agree. In the VS Code extension the webview renders the list and
|
|
9
|
-
* the extension host counts the activity-bar badge over the same rows (a badge
|
|
10
|
-
* that ignored the filter would announce work in sessions the list is
|
|
11
|
-
* deliberately hiding); in the dashboard the list and its subset line derive
|
|
12
|
-
* from it; on iOS it is mirrored to Swift the way the reducer is.
|
|
13
|
-
*
|
|
14
|
-
* Sessions are shown across ALL gateways by default; the gateway is a facet like
|
|
15
|
-
* any other, not the frame the list lives in.
|
|
16
|
-
*/
|
|
17
|
-
/** Coarse lifecycle bucket — what a person actually filters on. Raw statuses are
|
|
18
|
-
* too many and too engine-shaped ('starting' vs 'running' is not a decision). */
|
|
19
2
|
type SessionState = 'attention' | 'working' | 'idle' | 'ended';
|
|
20
3
|
declare const STATE_ORDER: readonly SessionState[];
|
|
21
4
|
declare const STATE_LABELS: Record<SessionState, string>;
|
|
22
5
|
declare function sessionState(info: SessionInfo): SessionState;
|
|
23
|
-
/**
|
|
24
|
-
* The sub-agents a list row draws as live.
|
|
25
|
-
*
|
|
26
|
-
* `sessionState` deliberately does **not** grow a `subagents` bucket — a fifth
|
|
27
|
-
* state would split `working` in two for every client that filters by it,
|
|
28
|
-
* including the ones that have not shipped this yet. Instead `working` *counts*
|
|
29
|
-
* them: a synchronous `Task` keeps the turn in flight so the status already
|
|
30
|
-
* says `working`, and a **background** agent — which outlives its turn on
|
|
31
|
-
* purpose — is the carve-out the extra arm in `sessionState` exists for.
|
|
32
|
-
* That is what makes "sub-agents are an annotation on a working row" true
|
|
33
|
-
* rather than assumed: the row is in the working bucket whichever kind is
|
|
34
|
-
* running, and this list only says more about it.
|
|
35
|
-
*/
|
|
36
6
|
declare function runningSubagents(info: SessionInfo): SubagentInfo[];
|
|
37
|
-
/**
|
|
38
|
-
* A sub-agent's identity on one line: `Explore · find the auth check`.
|
|
39
|
-
*
|
|
40
|
-
* The same two fields `taskLabel` builds its transcript row from, minus the
|
|
41
|
-
* `Task(…)` wrapper — a list row is already inside a session, so naming the tool
|
|
42
|
-
* spends the width that the description needs. Falls back to the bare agent type,
|
|
43
|
-
* then to a generic word: a row with no label at all reads as a rendering bug,
|
|
44
|
-
* and an engine is free to send neither field.
|
|
45
|
-
*/
|
|
46
|
-
/**
|
|
47
|
-
* Does this record name an **agent**, as opposed to a task the model merely
|
|
48
|
-
* described?
|
|
49
|
-
*
|
|
50
|
-
* The tracker opens a record for every spawner call and for any nested event
|
|
51
|
-
* whose parent it has not seen, so the list holds two different things wearing
|
|
52
|
-
* one shape. One carries a `subagent_type` — a delegated agent with an identity
|
|
53
|
-
* (`Explore`), whose own work is worth a surface of its own. The other carries
|
|
54
|
-
* only a description, and there is no agent there to open: a row that offered a
|
|
55
|
-
* screen and then showed a frame with nothing in it would be worse than a row
|
|
56
|
-
* that offered nothing.
|
|
57
|
-
*
|
|
58
|
-
* Here rather than in a client because it decides two things a list must not
|
|
59
|
-
* disagree about across surfaces — what is pressable, and what wears the
|
|
60
|
-
* sub-agent colour.
|
|
61
|
-
*/
|
|
62
7
|
declare function isAgentRecord(sub: SubagentInfo): boolean;
|
|
63
8
|
declare function subagentLabel(sub: SubagentInfo): string;
|
|
64
|
-
/** The facets a session can be grouped or sorted by. */
|
|
65
9
|
type Facet = 'gateway' | 'adapter' | 'state' | 'project';
|
|
66
10
|
type GroupBy = 'none' | Facet;
|
|
67
11
|
type SortBy = 'recent' | 'name' | Facet;
|
|
68
12
|
type ViewConfig = {
|
|
69
|
-
search: string;
|
|
13
|
+
search: string;
|
|
70
14
|
gateways: string[];
|
|
71
15
|
adapters: string[];
|
|
72
16
|
states: SessionState[];
|
|
73
|
-
/**
|
|
74
|
-
* Empty = no filter. Keys are {@link projectKey} output — never names, which
|
|
75
|
-
* are neither unique (two repos both called "api") nor stable (editing
|
|
76
|
-
* `.workerdeck.json` renames every session at once and must not empty a
|
|
77
|
-
* saved filter). Optional, unlike its three siblings, because stored view
|
|
78
|
-
* configs predate it: a config restored from `localStorage`/`globalState`
|
|
79
|
-
* without the key must keep filtering, so absent and empty mean the same
|
|
80
|
-
* thing.
|
|
81
|
-
*/
|
|
82
17
|
projects?: string[];
|
|
83
|
-
/** Show only sessions inside the host's own folders. Inert where there is no
|
|
84
|
-
* such notion (no folder open, a dashboard with no workspace), which is why it
|
|
85
|
-
* can default on. */
|
|
86
18
|
scoped: boolean;
|
|
87
19
|
groupBy: GroupBy;
|
|
88
20
|
sortBy: SortBy;
|
|
89
21
|
};
|
|
90
22
|
declare const DEFAULT_VIEW_CONFIG: ViewConfig;
|
|
91
|
-
/**
|
|
92
|
-
* One folder the surrounding host has open, as a place sessions can live in.
|
|
93
|
-
*
|
|
94
|
-
* `hostId` present = the folder belongs to exactly that gateway. Absent = a real
|
|
95
|
-
* local folder, which only a loopback gateway's cwds can be inside: a remote
|
|
96
|
-
* gateway's paths are on another machine, where an identical-looking path means
|
|
97
|
-
* nothing.
|
|
98
|
-
*/
|
|
99
23
|
type ScopeRoot = {
|
|
100
24
|
hostId?: string;
|
|
101
25
|
path: string;
|
|
102
26
|
};
|
|
103
|
-
/** The host's own folders — the sessions list's intrinsic scope. */
|
|
104
27
|
type WorkspaceScope = {
|
|
105
28
|
label: string;
|
|
106
29
|
roots: ScopeRoot[];
|
|
107
30
|
};
|
|
108
|
-
/** A session with everything the list needs to filter, group and label it. */
|
|
109
31
|
type SessionRow = {
|
|
110
32
|
hostId: string;
|
|
111
|
-
hostName: string;
|
|
33
|
+
hostName: string;
|
|
112
34
|
local: boolean;
|
|
113
35
|
adapter: string;
|
|
114
36
|
state: SessionState;
|
|
115
37
|
info: SessionInfo;
|
|
116
|
-
/**
|
|
117
|
-
*
|
|
38
|
+
/**
|
|
39
|
+
* Messages this client has not read — `unseenCount` against its watermark, so the
|
|
40
|
+
* unit is prose (`SessionInfo.proseCount`) wherever the gateway reports it. A session
|
|
41
|
+
* that is only running tools contributes 0: the badge answers "is there something to
|
|
42
|
+
* read", not "is anything happening", which is what `state` is for.
|
|
43
|
+
*/
|
|
118
44
|
unseen: number;
|
|
119
45
|
};
|
|
120
46
|
type SessionGroup = {
|
|
@@ -122,300 +48,88 @@ type SessionGroup = {
|
|
|
122
48
|
label?: string;
|
|
123
49
|
rows: SessionRow[];
|
|
124
50
|
};
|
|
125
|
-
/** The adapters actually present, for the filter chips — derived rather than
|
|
126
|
-
* enumerated, so a new engine needs no change here. */
|
|
127
51
|
declare function adaptersOf(rows: readonly SessionRow[]): string[];
|
|
128
|
-
/**
|
|
129
|
-
* The projects actually present, as `{ key, label }` for a filter control —
|
|
130
|
-
* derived like {@link adaptersOf}, and paired because the two halves differ:
|
|
131
|
-
* the *key* is what {@link ViewConfig.projects} holds (gateway-qualified root,
|
|
132
|
-
* so a rename regroups nothing) and the *label* is what a person picks by.
|
|
133
|
-
*
|
|
134
|
-
* Sorted by label, deduped by key. Two projects with the same name on two
|
|
135
|
-
* gateways therefore stay two entries wearing one word — which is honest: they
|
|
136
|
-
* really are two different directories, and the alternative is a filter that
|
|
137
|
-
* silently selects both.
|
|
138
|
-
*/
|
|
139
52
|
declare function projectsOf(rows: readonly SessionRow[]): {
|
|
140
53
|
key: string;
|
|
141
54
|
label: string;
|
|
142
55
|
}[];
|
|
143
56
|
declare function sessionLabel(info: SessionInfo): string;
|
|
144
|
-
/**
|
|
145
|
-
* The project facet's grouping key: gateway id + the project root, falling
|
|
146
|
-
* back to the session's cwd when no project is declared.
|
|
147
|
-
*
|
|
148
|
-
* The root and not the name, because a name is not a key (two repos can both
|
|
149
|
-
* be called "api", and a rename must regroup nothing); qualified by gateway,
|
|
150
|
-
* because a remote gateway's identical-looking path is another machine's
|
|
151
|
-
* directory — the same rule `ScopeRoot` states. The cwd fallback is what makes
|
|
152
|
-
* grouping by project useful before anyone has written a `.workerdeck.json`:
|
|
153
|
-
* undeclared sessions group by their folder, declared ones by their root, and
|
|
154
|
-
* a session in `packages/ui` joins its repo's group the moment the file
|
|
155
|
-
* exists. Sessions with no cwd at all (a filesystem-less engine) share one
|
|
156
|
-
* per-gateway bucket — see {@link projectLabel}.
|
|
157
|
-
*/
|
|
158
57
|
declare function projectKey(row: SessionRow): string;
|
|
159
|
-
/**
|
|
160
|
-
* What a project group (or a row's project slot) is called: the declared name,
|
|
161
|
-
* else the cwd's basename — the exact string clients rendered before this
|
|
162
|
-
* feature existed, so an undeclared project looks like today. 'No project' is
|
|
163
|
-
* only ever the no-cwd case (a sandboxed provider session), where there is no
|
|
164
|
-
* folder to name.
|
|
165
|
-
*
|
|
166
|
-
* Takes only the `info` it reads, so a surface holding a bare `SessionInfo` —
|
|
167
|
-
* a row component, an iOS cell — can call it without inventing the rest of a
|
|
168
|
-
* `SessionRow`. That matters more than it looks: this string is what a client
|
|
169
|
-
* renders *in place of* the cwd basename it used to draw, and two spellings of
|
|
170
|
-
* it would put the list and its group headers on different names.
|
|
171
|
-
*/
|
|
172
58
|
declare function projectLabel(row: Pick<SessionRow, 'info'>): string;
|
|
173
|
-
/**
|
|
174
|
-
* Where inside its project a session actually sits — the cwd with the project
|
|
175
|
-
* root taken off the front, or `undefined` when it sits at the root, has no
|
|
176
|
-
* declared project, or has no cwd at all.
|
|
177
|
-
*
|
|
178
|
-
* The companion to {@link projectLabel}, and it exists for one situation: a list
|
|
179
|
-
* **grouped by project**. There the header has already said the project's name,
|
|
180
|
-
* so repeating it on every row spends the row's most valuable line on the one
|
|
181
|
-
* fact the reader already has. What the header cannot say is which *part* of the
|
|
182
|
-
* project a session is working in, and two sessions in the same repo are told
|
|
183
|
-
* apart by exactly that.
|
|
184
|
-
*
|
|
185
|
-
* Undefined is the honest answer for a session at the project root, and callers
|
|
186
|
-
* must render nothing rather than a `.` or a repeated name — the slot simply
|
|
187
|
-
* goes away, which is the point.
|
|
188
|
-
*/
|
|
189
59
|
declare function projectSubpath(row: Pick<SessionRow, 'info'>): string | undefined;
|
|
190
|
-
/**
|
|
191
|
-
* This session is a job run — the queue created it, and `JobInfo.sessionId`
|
|
192
|
-
* points at it.
|
|
193
|
-
*
|
|
194
|
-
* A job run is an ordinary registry session in every other respect, which is
|
|
195
|
-
* what makes this worth spelling once: a client that renders jobs on their own
|
|
196
|
-
* surface should not list them again among the sessions, and a client with no
|
|
197
|
-
* jobs surface (the extension, the phone) should, or they would be invisible.
|
|
198
|
-
* The queue stamps `meta.jobId`; nothing else may write that key.
|
|
199
|
-
*/
|
|
200
60
|
declare function isJobRun(info: SessionInfo): boolean;
|
|
201
|
-
/**
|
|
202
|
-
* Is this session inside one of the host's folders? A gateway-tagged root only
|
|
203
|
-
* ever matches its own gateway; an untagged one only matches a loopback gateway,
|
|
204
|
-
* because a remote gateway's identical-looking path is a different machine's
|
|
205
|
-
* directory.
|
|
206
|
-
*/
|
|
207
61
|
declare function inScope(row: SessionRow, scope: WorkspaceScope): boolean;
|
|
208
|
-
/** Whether the scope filter is actually hiding anything — it is inert with no
|
|
209
|
-
* folder open, and that is the difference between a default and a filter. */
|
|
210
62
|
declare function scopeActive(config: ViewConfig, scope: WorkspaceScope | undefined): boolean;
|
|
211
63
|
declare function filterRows(rows: readonly SessionRow[], config: ViewConfig, scope?: WorkspaceScope): SessionRow[];
|
|
212
|
-
/**
|
|
213
|
-
* The list as rendered: filtered, grouped, and sorted within each group. Groups
|
|
214
|
-
* themselves come out in the sort's own order — grouping by state and sorting by
|
|
215
|
-
* name should still put "Needs attention" first, so groups are ordered by their
|
|
216
|
-
* facet rank, never by the row sort.
|
|
217
|
-
*/
|
|
218
64
|
declare function groupRows(rows: readonly SessionRow[], config: ViewConfig): SessionGroup[];
|
|
219
|
-
/**
|
|
220
|
-
* What the list is hiding, and why — the one "you are seeing a subset" signal.
|
|
221
|
-
*
|
|
222
|
-
* There used to be two: a dot on the funnel and a scope line above the list.
|
|
223
|
-
* They competed (the scope line said one thing, the dot counted a superset of
|
|
224
|
-
* it) and neither said how much was missing. This is the single rule both the
|
|
225
|
-
* count and the wording come from: absent when nothing is hidden, and otherwise
|
|
226
|
-
* naming every cause, so the line is never "12 of 30" with no way to guess why.
|
|
227
|
-
*
|
|
228
|
-
* Search is a cause like any other. Its box is visible, but the *consequence*
|
|
229
|
-
* of it — rows gone from the list — is the thing being reported, and leaving it
|
|
230
|
-
* out would make the arithmetic wrong.
|
|
231
|
-
*/
|
|
232
65
|
type SubsetSummary = {
|
|
233
66
|
shown: number;
|
|
234
67
|
total: number;
|
|
235
68
|
causes: string[];
|
|
236
69
|
};
|
|
237
70
|
declare function subsetSummary(config: ViewConfig, scope: WorkspaceScope | undefined, shown: number, total: number): SubsetSummary | undefined;
|
|
238
|
-
/**
|
|
239
|
-
* Is anything OTHER than the workspace scope narrowing the list?
|
|
240
|
-
*
|
|
241
|
-
* The distinction an empty list turns on: "this project has no sessions" wants a
|
|
242
|
-
* different sentence, and a different way out, from "your filters match none".
|
|
243
|
-
* Scope is excluded because it is on by default — it is the state, not a choice
|
|
244
|
-
* someone made.
|
|
245
|
-
*/
|
|
246
71
|
declare function hasFacetFilter(config: ViewConfig): boolean;
|
|
247
|
-
/** "Show me everything": every filter off, including scope. The group/sort
|
|
248
|
-
* choices are a layout preference and survive. */
|
|
249
72
|
declare function clearFilters(config: ViewConfig): ViewConfig;
|
|
250
73
|
//#endregion
|
|
251
74
|
//#region src/usage.d.ts
|
|
252
|
-
/**
|
|
253
|
-
* What one session was last told about the plan's windows: the transcript's own
|
|
254
|
-
* rate-limit state, and the event clock of the newest reading in it.
|
|
255
|
-
*
|
|
256
|
-
* Deliberately structural rather than `TranscriptState` — protocol may not
|
|
257
|
-
* import a client — and it is exactly the two fields the reducer keeps.
|
|
258
|
-
*/
|
|
259
75
|
type SessionUsage = {
|
|
260
|
-
|
|
261
|
-
/** Epoch ms of the newest `rate_limit` event this session saw — one clock for
|
|
262
|
-
* the whole map, which is all the reducer records. */
|
|
76
|
+
rateLimits?: Record<string, RateLimitInfo>;
|
|
263
77
|
updatedAt?: number;
|
|
264
78
|
};
|
|
265
|
-
/**
|
|
266
|
-
* The usage a client should render: the gateway's per-profile state where it has
|
|
267
|
-
* the window, this session's own reading where it does not.
|
|
268
|
-
*
|
|
269
|
-
* Why the profile wins outright rather than by comparing timestamps: the
|
|
270
|
-
* gateway's `ProfileUsageTracker` is fed from **every** session on the profile —
|
|
271
|
-
* including this one, from seq 0 — and keeps the newest reading per window by
|
|
272
|
-
* the event's own `ts`. So for any window it holds, it holds a reading at least
|
|
273
|
-
* as new as the one in this transcript, and a timestamp comparison could only
|
|
274
|
-
* ever go wrong: the reducer keeps a *single* `updatedAt` for the whole map, so
|
|
275
|
-
* a `five_hour` reading from this morning is dated with the afternoon's
|
|
276
|
-
* `seven_day` event and would beat a genuinely fresher profile entry.
|
|
277
|
-
*
|
|
278
|
-
* The session half is not a fallback for correctness but for *coverage*: the
|
|
279
|
-
* profile map is in-memory, so a restarted gateway serves nothing until a
|
|
280
|
-
* session reports again, and a session with no profile has no account state at
|
|
281
|
-
* all. In both cases the transcript's reading is the only one there is, and it
|
|
282
|
-
* is dated honestly (see {@link SessionUsage.updatedAt}) rather than as now.
|
|
283
|
-
*
|
|
284
|
-
* Absent stays absent throughout: a window nobody has reported is **unknown,
|
|
285
|
-
* never 0%**, and this returns an empty map rather than inventing entries.
|
|
286
|
-
*/
|
|
287
79
|
declare function mergeUsage(session: SessionUsage, profile: ProfileUsage | undefined): ProfileUsage;
|
|
288
|
-
/** One window as a surface draws it: the reading, its own date, and whether the
|
|
289
|
-
* gateway is the one that zeroed it. */
|
|
290
80
|
type UsageWindowRow = {
|
|
291
81
|
key: string;
|
|
292
|
-
info: RateLimitInfo;
|
|
82
|
+
info: RateLimitInfo;
|
|
293
83
|
updatedAt?: number;
|
|
294
84
|
inferredReset?: boolean;
|
|
295
85
|
};
|
|
296
|
-
/**
|
|
297
|
-
* The windows in reading order: the session window, the weekly one, then the
|
|
298
|
-
* per-model weeklies alphabetically.
|
|
299
|
-
*
|
|
300
|
-
* Discovered rather than hardcoded — the engine's set of windows is an open
|
|
301
|
-
* union and has grown before — but ordered, so the first two always mean the
|
|
302
|
-
* same thing wherever they are drawn. A window with no `utilization` is
|
|
303
|
-
* **unknown, not zero**, and is dropped entirely rather than rendered as an
|
|
304
|
-
* empty bar that reads as "plenty left".
|
|
305
|
-
*
|
|
306
|
-
* Here rather than in a client because two surfaces now render the same windows
|
|
307
|
-
* from different sources — the session panel from its merged state, the
|
|
308
|
-
* dashboard's profile page straight off `ProfileInfo.usage` — and a list that
|
|
309
|
-
* ordered or filtered differently would be the same account described two ways.
|
|
310
|
-
*/
|
|
311
86
|
declare function orderUsageWindows(usage: ProfileUsage | undefined): UsageWindowRow[];
|
|
312
|
-
/** The flat `rateLimitType → reading` map every existing renderer takes, out of
|
|
313
|
-
* the dated form. Undefined in, undefined out — so a surface can keep telling
|
|
314
|
-
* "no reading" apart from "an empty one". */
|
|
315
87
|
declare function usageInfos(usage: ProfileUsage | undefined): Record<string, RateLimitInfo> | undefined;
|
|
316
88
|
//#endregion
|
|
317
89
|
//#region src/watermarks.d.ts
|
|
318
|
-
/**
|
|
319
|
-
* "What had you seen, and when" — per session, across reloads.
|
|
320
|
-
*
|
|
321
|
-
* Two numbers because two surfaces ask different questions. A session list has
|
|
322
|
-
* only the REST rollup for sessions it isn't showing, so it compares **rows the
|
|
323
|
-
* gateway counted**; a panel has the whole transcript, so it compares **rows it
|
|
324
|
-
* rendered** and can put the mark in the right place. Keeping both means neither
|
|
325
|
-
* surface has to attach to something it isn't rendering.
|
|
326
|
-
*
|
|
327
|
-
* A watermark is only written while a session is genuinely on screen. A surface
|
|
328
|
-
* nobody can see is not being read, and marking it read is how an unread badge
|
|
329
|
-
* silently stops working.
|
|
330
|
-
*
|
|
331
|
-
* Storage is a seam (`WatermarkStore`) rather than a dependency: the VS Code
|
|
332
|
-
* extension backs it with `globalState`, the dashboard with `localStorage`, and
|
|
333
|
-
* neither belongs in this package.
|
|
334
|
-
*/
|
|
335
90
|
type Watermark = {
|
|
336
|
-
|
|
337
|
-
/** Rows the gateway had counted (`SessionInfo.activityCount`) — the same unit
|
|
338
|
-
* as `itemCount`, but from the rollup, so it is knowable for a session this
|
|
339
|
-
* client is not showing. */
|
|
91
|
+
itemCount: number;
|
|
340
92
|
activity: number;
|
|
341
|
-
/**
|
|
342
|
-
*
|
|
343
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Prose rows read (`SessionInfo.proseCount`). Optional because a mark stored before
|
|
95
|
+
* prose counting existed cannot say — see `unseenCount`, which reads that absence as
|
|
96
|
+
* "caught up" rather than badging a whole history the operator has already seen.
|
|
97
|
+
*/
|
|
98
|
+
prose?: number;
|
|
99
|
+
turns: number;
|
|
344
100
|
seenAt: number;
|
|
345
101
|
};
|
|
346
|
-
/** Where the marks are kept. Reads happen once at construction; writes are
|
|
347
|
-
* whole-map and may be async — nothing here awaits them. */
|
|
348
102
|
type WatermarkStore = {
|
|
349
103
|
read(): Record<string, Watermark> | undefined;
|
|
350
104
|
write(marks: Record<string, Watermark>): void;
|
|
351
105
|
};
|
|
352
|
-
declare
|
|
106
|
+
declare function watermarkKey(hostId: string, sessionId: string): string;
|
|
353
107
|
declare class Watermarks {
|
|
354
108
|
#private;
|
|
355
109
|
constructor(store: WatermarkStore);
|
|
356
110
|
get(hostId: string, sessionId: string): Watermark | undefined;
|
|
357
|
-
/** Every mark, for a caller deriving unread counts over a whole list. */
|
|
358
111
|
all(): Readonly<Record<string, Watermark>>;
|
|
359
|
-
/**
|
|
360
|
-
* Record what is on screen now. Monotonic on purpose: a transcript that
|
|
361
|
-
* *shrank* (a compaction, a fresh attach mid-replay) must not walk the mark
|
|
362
|
-
* backwards and resurrect rows the user already read.
|
|
363
|
-
*
|
|
364
|
-
* Returns whether the mark actually moved, because an unread badge is computed
|
|
365
|
-
* from it and nothing else will say so: rows read in a panel do not touch the
|
|
366
|
-
* sessions poll, so a caller that doesn't hear about this has no other way to
|
|
367
|
-
* learn the count is now wrong.
|
|
368
|
-
*/
|
|
369
112
|
mark(hostId: string, sessionId: string, seen: {
|
|
370
113
|
itemCount?: number;
|
|
371
114
|
activity?: number;
|
|
115
|
+
prose?: number;
|
|
372
116
|
turns?: number;
|
|
373
117
|
}, now?: number): boolean;
|
|
374
|
-
/** Forget a session — it was deleted, and its mark is now noise. */
|
|
375
118
|
forget(hostId: string, sessionId: string): void;
|
|
376
119
|
}
|
|
377
120
|
/**
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
* (five tool calls in one turn is one turn) and a stream sequence overcounts
|
|
382
|
-
* absurdly (every delta). Turns stay the fallback for a gateway too old to
|
|
383
|
-
* report it.
|
|
384
|
-
*
|
|
385
|
-
* A session never visited returns 0 — "never opened" is not "unread", and a
|
|
386
|
-
* badge that counted every session's whole history on first launch would be
|
|
387
|
-
* noise on the one day it should be quiet.
|
|
121
|
+
* The badge's number, in the best unit the pair can agree on: prose the human has not
|
|
122
|
+
* read, else rows, else turns. The ladder is what keeps an older gateway (no `proseCount`
|
|
123
|
+
* on the wire) badging exactly as it did before rather than going silent.
|
|
388
124
|
*/
|
|
389
125
|
declare function unseenCount(mark: Watermark | undefined, info: {
|
|
126
|
+
proseCount?: number;
|
|
390
127
|
activityCount?: number;
|
|
391
128
|
turns?: number;
|
|
392
129
|
}): number;
|
|
393
130
|
//#endregion
|
|
394
131
|
//#region src/index.d.ts
|
|
395
|
-
|
|
396
|
-
* @workerdeck/protocol — the wire protocol between a workerdeck server and its clients.
|
|
397
|
-
*
|
|
398
|
-
* One session = one ordered stream of {@link SessionEvent}s (each stamped with a monotonically
|
|
399
|
-
* increasing `seq`) plus a small command set ({@link SessionCommand}). Clients attach over
|
|
400
|
-
* WebSocket, optionally replaying from a known `seq`, and drive the session with commands.
|
|
401
|
-
*
|
|
402
|
-
* This package is dependency-free and browser-safe. Anthropic API message content is modeled
|
|
403
|
-
* structurally (see {@link ApiMessage}) so clients don't need the Agent SDK to render transcripts.
|
|
404
|
-
*/
|
|
405
|
-
/** Bumped on any breaking change to events, commands, or REST shapes. */
|
|
406
|
-
declare const PROTOCOL_VERSION = 7;
|
|
407
|
-
/**
|
|
408
|
-
* - `starting` — runner spawned, waiting for the SDK init handshake
|
|
409
|
-
* - `running` — a turn is in progress
|
|
410
|
-
* - `awaiting_approval` — blocked on at least one pending permission request
|
|
411
|
-
* - `idle` — between turns; accepting user messages
|
|
412
|
-
* - `parked` — waiting on a deferred tool execution. The live runner has been torn
|
|
413
|
-
* down and the session's state persisted; delivering the execution's result
|
|
414
|
-
* (`POST {basePath}/executions/:executionId/result`) rehydrates it under the same
|
|
415
|
-
* id and the run continues. Not terminal.
|
|
416
|
-
* - `failed` — the underlying query errored; terminal
|
|
417
|
-
* - `closed` — closed by a client or the host; terminal
|
|
418
|
-
*/
|
|
132
|
+
declare const PROTOCOL_VERSION = 1;
|
|
419
133
|
type SessionStatus = 'starting' | 'running' | 'awaiting_approval' | 'idle' | 'parked' | 'failed' | 'closed';
|
|
420
134
|
type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'dontAsk' | 'auto';
|
|
421
135
|
type TextBlock = {
|
|
@@ -441,192 +155,41 @@ type ToolResultBlock = {
|
|
|
441
155
|
[key: string]: unknown;
|
|
442
156
|
}>;
|
|
443
157
|
is_error?: boolean;
|
|
444
|
-
/**
|
|
445
|
-
* This block carries only the **head** of the result: the replay truncated it
|
|
446
|
-
* (see {@link TOOL_RESULT_HEAD_CHARS}), and the whole thing is one fetch away
|
|
447
|
-
* at `GET /sessions/:id/events/:seq/result?toolUseId=`.
|
|
448
|
-
*
|
|
449
|
-
* On the **block**, never the event, and that is the same argument
|
|
450
|
-
* `user_message.patch` has to make in reverse: the patch sits on the event and
|
|
451
|
-
* its doc must therefore caveat "only when the message carries exactly one
|
|
452
|
-
* `tool_result` block — with two, nothing says which one it belongs to". A
|
|
453
|
-
* message answering three calls truncates whichever of them is large, so
|
|
454
|
-
* paying that caveat a second time would make the marker unusable exactly
|
|
455
|
-
* when it matters. {@link FilePatch.truncated} is the shipped precedent.
|
|
456
|
-
*
|
|
457
|
-
* Only ever set on a **replay** a client asked for (`truncateResults`), so a
|
|
458
|
-
* client that has never heard of this field cannot receive one — which is why
|
|
459
|
-
* this is additive at protocol 7 rather than a bump. Absent means the block is
|
|
460
|
-
* whole.
|
|
461
|
-
*/
|
|
462
158
|
truncated?: boolean;
|
|
463
|
-
/** How many characters the untruncated result had. Set iff `truncated`.
|
|
464
|
-
*
|
|
465
|
-
* A client cannot compute it — it holds the head — and the number is not
|
|
466
|
-
* cosmetic: a collapsed row spells "… +N chars", and `height.ts` sizes the row
|
|
467
|
-
* by wrapping **that exact string**, so a count derived from the head would be
|
|
468
|
-
* both a lie and a different pixel height. */
|
|
469
159
|
total_chars?: number;
|
|
470
160
|
};
|
|
471
|
-
/**
|
|
472
|
-
* How much of a tool result a truncating replay keeps.
|
|
473
|
-
*
|
|
474
|
-
* Chosen against the two clients' *own* budgets, and the relationship is the
|
|
475
|
-
* whole point: the terminal theme shows ~400 characters collapsed and ~2,000
|
|
476
|
-
* open, so at 8,000 the collapsed and open states are **byte-identical to an
|
|
477
|
-
* untruncated attach** and only the uncapped "show everything" press ever
|
|
478
|
-
* fetches. That collapses the entire feature to one press, and it is asserted
|
|
479
|
-
* in a test rather than trusted — lowered below the open budget, this would
|
|
480
|
-
* silently clip the open state with no marker, which is the one failure this
|
|
481
|
-
* design must not have.
|
|
482
|
-
*
|
|
483
|
-
* Measured justification: on one 1,270-row session three `tool_result` frames
|
|
484
|
-
* were 641 / 463 / 396 KB, 68% of a 3.1 MB attach. The cut is *structural* —
|
|
485
|
-
* proportional to the thing that is actually large, wherever in the log it sits
|
|
486
|
-
* — which a row window is not.
|
|
487
|
-
*/
|
|
488
161
|
declare const TOOL_RESULT_HEAD_CHARS = 8000;
|
|
489
|
-
/**
|
|
490
|
-
* A base64 image part, delivered as an address instead of its bytes.
|
|
491
|
-
*
|
|
492
|
-
* The **seventh** rule of the family, and the first written *after* its
|
|
493
|
-
* measurement rather than before it. Across 214 local sessions, 91% of all
|
|
494
|
-
* tool-result payload is base64 image data — 489 MB against 44 MB of text — and
|
|
495
|
-
* **no client renders a byte of it**: `blockText` in the reducer and
|
|
496
|
-
* `joinedText` on iOS both fold a `tool_result` to its text parts, and both
|
|
497
|
-
* clients draw a tool's picture from a host *path* (`savedPath` → `/produced`,
|
|
498
|
-
* `/fs/read`), never from block content. So it is `replayRetains`' argument at
|
|
499
|
-
* nine times the size of the case that rule was written for: bytes whose entire
|
|
500
|
-
* effect on the reader is `return base`.
|
|
501
|
-
*
|
|
502
|
-
* A **new part type rather than a hollowed-out `image`**, and that is the one
|
|
503
|
-
* judgement here worth stating. `headOf`'s shape-preservation rule — "a
|
|
504
|
-
* truncation is a shorter result, never a different kind of one" — cuts the
|
|
505
|
-
* other way for pixels: a head *is* a valid shorter text, but an image with no
|
|
506
|
-
* bytes is not a smaller image, and spelling it `{ type: 'image', source }` with
|
|
507
|
-
* no `data` invites precisely the failure shape-preservation exists to prevent,
|
|
508
|
-
* a renderer that trusts `source.data` drawing `data:;base64,undefined`. An
|
|
509
|
-
* unfamiliar type instead falls through every fold that already exists, exactly
|
|
510
|
-
* as the CLI's own `tool_reference` part does: no `text`, so it contributes
|
|
511
|
-
* nothing, and an unaware consumer renders what it renders today, which is
|
|
512
|
-
* nothing. That is this family's safe failure.
|
|
513
|
-
*
|
|
514
|
-
* Only ever produced for a socket that asked (`imageRefs`), so a client that has
|
|
515
|
-
* never heard of this type cannot receive one — which is why this is additive at
|
|
516
|
-
* protocol 7, the same argument {@link ToolResultBlock.truncated} makes. Unlike
|
|
517
|
-
* truncation it applies to **live events as well as replays**: the client's one
|
|
518
|
-
* render path is ref-then-fetch, so bytes on a live event would either be
|
|
519
|
-
* discarded (335 KB median, once per attached watcher) or need a second
|
|
520
|
-
* decode-from-event path pinning megabytes inside the transcript cache — the
|
|
521
|
-
* disease relocated rather than cured.
|
|
522
|
-
*/
|
|
523
162
|
type ImageRefPart = {
|
|
524
163
|
type: 'image_ref';
|
|
525
|
-
/** The stored part's own media type (`image/png`, `image/jpeg` and
|
|
526
|
-
* `image/webp` are the three observed), or `application/octet-stream` when it
|
|
527
|
-
* had none. Never the membership test — that is `image` plus a base64 source. */
|
|
528
164
|
media_type: string;
|
|
529
|
-
/** Decoded size, which a client cannot compute from an address it has not
|
|
530
|
-
* fetched yet. Not cosmetic: the placeholder spells it, and in the terminal
|
|
531
|
-
* theme a rendered string *is* a row height. */
|
|
532
165
|
bytes: number;
|
|
533
|
-
/**
|
|
534
|
-
* Index of this part in the **stored** block's content array, and the address
|
|
535
|
-
* a fetch is made with.
|
|
536
|
-
*
|
|
537
|
-
* A stamped field rather than the position it arrives at, because that
|
|
538
|
-
* position is not stable: `headOf` builds a truncated head by keeping text
|
|
539
|
-
* parts up to budget and dropping every other part, so a block that is both
|
|
540
|
-
* over the text budget and image-bearing has its parts renumbered the moment
|
|
541
|
-
* the two rules compose. Stamped, the address survives any later reshaping —
|
|
542
|
-
* and the route verifies it against the stored block rather than trusting it.
|
|
543
|
-
*/
|
|
544
166
|
part_index: number;
|
|
545
167
|
};
|
|
546
|
-
/**
|
|
547
|
-
* Project one `tool_result` content part onto its {@link ImageRefPart}, or
|
|
548
|
-
* `undefined` when the part is not a base64 image and must be delivered as it
|
|
549
|
-
* stands.
|
|
550
|
-
*
|
|
551
|
-
* The rule's **one spelling**, shared by the transform that replaces parts
|
|
552
|
-
* (core), the route that serves them back (server) and the property test that
|
|
553
|
-
* proves the fold is otherwise unchanged (react) — the same reason every other
|
|
554
|
-
* member of this family lives here rather than in whichever package applies it.
|
|
555
|
-
*
|
|
556
|
-
* Deliberately narrow. The corpus holds exactly two non-text part kinds: this
|
|
557
|
-
* one, and the CLI's `tool_reference`, of which every instance across 214
|
|
558
|
-
* sessions totals 122 KB. A "drop non-text parts" rule would sweep those in for
|
|
559
|
-
* no measurable gain, and narrowness is this family's standing habit.
|
|
560
|
-
*/
|
|
561
168
|
declare function imagePartRef(part: {
|
|
562
169
|
type?: string;
|
|
563
170
|
[key: string]: unknown;
|
|
564
171
|
}, index: number): ImageRefPart | undefined;
|
|
565
|
-
/** Forward-compatible fallback for block types this protocol version doesn't model. */
|
|
566
172
|
type UnknownBlock = {
|
|
567
173
|
type: string;
|
|
568
174
|
[key: string]: unknown;
|
|
569
175
|
};
|
|
570
|
-
/**
|
|
571
|
-
* One hunk of a file edit, in unified-diff terms.
|
|
572
|
-
*
|
|
573
|
-
* The numbers are the engine's own, not the client's: `newStart` is where this
|
|
574
|
-
* hunk begins in the file *after* the edit, which is what a reader needs to jump
|
|
575
|
-
* to the change. A client cannot compute them — it has never seen the file — so
|
|
576
|
-
* a diff rendered without this is a diff with no line numbers.
|
|
577
|
-
*/
|
|
578
176
|
type PatchHunk = {
|
|
579
177
|
oldStart: number;
|
|
580
178
|
oldLines: number;
|
|
581
179
|
newStart: number;
|
|
582
180
|
newLines: number;
|
|
583
|
-
/** Body lines, each prefixed ' ' (context), '-' (removed) or '+' (added), as
|
|
584
|
-
* unified diff spells them. The prefix is part of the string. */
|
|
585
181
|
lines: string[];
|
|
586
182
|
};
|
|
587
|
-
/**
|
|
588
|
-
* What a file-editing tool changed — the renderable half of an engine's edit
|
|
589
|
-
* output, and deliberately only that half.
|
|
590
|
-
*
|
|
591
|
-
* Both engines can say far more: the Claude SDK's `FileEditOutput` carries
|
|
592
|
-
* `originalFile`, the **entire** contents of the file before the edit. That must
|
|
593
|
-
* not travel here. This log is replayed to every attaching client and captured
|
|
594
|
-
* into parking snapshots, so a whole file on every edit is paid for again on
|
|
595
|
-
* every attach, forever — the same reason attachment bytes are references (see
|
|
596
|
-
* {@link MessageAttachment}) rather than inline base64.
|
|
597
|
-
*
|
|
598
|
-
* So the runner projects the engine's output down to the hunks, which is exactly
|
|
599
|
-
* what a diff renders and nothing more.
|
|
600
|
-
*/
|
|
601
183
|
type FilePatch = {
|
|
602
|
-
|
|
184
|
+
path?: string;
|
|
603
185
|
kind?: 'create' | 'update';
|
|
604
186
|
hunks: PatchHunk[];
|
|
605
|
-
/** Hunks were dropped to keep the event small. A renderer must say so rather
|
|
606
|
-
* than present a partial diff as the whole change. */
|
|
607
187
|
truncated?: boolean;
|
|
608
188
|
};
|
|
609
189
|
type ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock | UnknownBlock;
|
|
610
|
-
/**
|
|
611
|
-
* A file the user attached to a message — a photo, a screenshot, a document.
|
|
612
|
-
*
|
|
613
|
-
* The bytes never travel on this protocol. An attachment is uploaded first
|
|
614
|
-
* (`POST {basePath}/sessions/:id/attachments`), and the command that sends the
|
|
615
|
-
* message names it by id; what lands in the seq-numbered event log is this
|
|
616
|
-
* reference. That is deliberate: the log is replayed to every attaching client
|
|
617
|
-
* and captured into parking snapshots, so a few phone photos inlined as base64
|
|
618
|
-
* would be paid for on every attach, forever. Clients render a thumbnail by
|
|
619
|
-
* fetching `GET {basePath}/sessions/:id/attachments/:attachmentId`.
|
|
620
|
-
*
|
|
621
|
-
* Lifetime is the session's, like `/files` — the store is in-memory and an
|
|
622
|
-
* attachment 404s after a server restart. The message itself is unaffected: the
|
|
623
|
-
* model saw the bytes at send time.
|
|
624
|
-
*/
|
|
625
190
|
type MessageAttachment = {
|
|
626
|
-
|
|
191
|
+
id: string;
|
|
627
192
|
name: string;
|
|
628
|
-
/** IANA media type, e.g. 'image/jpeg'. The server decides how it reaches the
|
|
629
|
-
* model (image block, document block, or inlined text) from this. */
|
|
630
193
|
mediaType: string;
|
|
631
194
|
bytes: number;
|
|
632
195
|
};
|
|
@@ -635,8 +198,6 @@ type ApiMessage = {
|
|
|
635
198
|
content: string | ContentBlock[];
|
|
636
199
|
model?: string;
|
|
637
200
|
stop_reason?: string | null;
|
|
638
|
-
/** Per-API-call token usage when the message carries it (assistant messages do).
|
|
639
|
-
* Enables mid-run token accounting; result-message usage stays authoritative. */
|
|
640
201
|
usage?: {
|
|
641
202
|
input_tokens?: number;
|
|
642
203
|
output_tokens?: number;
|
|
@@ -644,181 +205,80 @@ type ApiMessage = {
|
|
|
644
205
|
cache_read_input_tokens?: number;
|
|
645
206
|
};
|
|
646
207
|
};
|
|
647
|
-
/** A tool call promoted into a pending approval by the runner's canUseTool hook
|
|
648
|
-
* (Claude), or an engine ask-channel request surfaced by its runner (codex).
|
|
649
|
-
* The two do not share a tense: Claude asks BEFORE a tool runs; codex's command
|
|
650
|
-
* approval is usually an escalation AFTER its sandbox already ran and refused
|
|
651
|
-
* the command ("command failed; retry without sandbox?"). The runner authors
|
|
652
|
-
* `title`/`description`/`decisionReason` to say which — clients should render
|
|
653
|
-
* those fields rather than composing their own "X wants to run Y" sentence. */
|
|
654
208
|
type PermissionRequest = {
|
|
655
|
-
|
|
209
|
+
id: string;
|
|
656
210
|
toolName: string;
|
|
657
211
|
input: Record<string, unknown>;
|
|
658
|
-
toolUseId: string;
|
|
659
|
-
title?: string;
|
|
660
|
-
displayName?: string;
|
|
661
|
-
description?: string;
|
|
662
|
-
decisionReason?: string;
|
|
663
|
-
agentId?: string;
|
|
212
|
+
toolUseId: string;
|
|
213
|
+
title?: string;
|
|
214
|
+
displayName?: string;
|
|
215
|
+
description?: string;
|
|
216
|
+
decisionReason?: string;
|
|
217
|
+
agentId?: string;
|
|
664
218
|
expiresAt?: number;
|
|
665
219
|
};
|
|
666
220
|
type PermissionDecisionSource = 'client' | 'timeout' | 'policy';
|
|
667
|
-
/** One choice of an AskUserQuestion question (SDK tool-input mirror). */
|
|
668
221
|
type UserQuestionOption = {
|
|
669
222
|
label: string;
|
|
670
223
|
description?: string;
|
|
671
|
-
/** Optional preview content (markdown unless the session configures html)
|
|
672
|
-
* rendered when the option is focused. */
|
|
673
224
|
preview?: string;
|
|
674
225
|
};
|
|
675
|
-
/** One question from the AskUserQuestion tool's input. By the tool's convention the
|
|
676
|
-
* first option is the model's recommended choice. */
|
|
677
226
|
type UserQuestion = {
|
|
678
|
-
question: string;
|
|
227
|
+
question: string;
|
|
679
228
|
header: string;
|
|
680
229
|
options: UserQuestionOption[];
|
|
681
230
|
multiSelect?: boolean;
|
|
682
231
|
};
|
|
683
|
-
/** How a session treats the AskUserQuestion tool:
|
|
684
|
-
* - 'ask' (default) — a pending permission like any other: interactive UIs render the
|
|
685
|
-
* question form; job webhooks carry the full request so a remote controller can
|
|
686
|
-
* answer over REST (POST /sessions/:id/permissions/:requestId).
|
|
687
|
-
* - 'auto' — resolved immediately with each question's first (recommended) option.
|
|
688
|
-
* - 'deny' — the tool is refused with guidance to decide autonomously (unattended runs).
|
|
689
|
-
* Answers ride a permission allow as `updatedInput.answers`: question text → chosen
|
|
690
|
-
* option label(s), multi-select labels comma-joined — the shape the CLI's own UI uses. */
|
|
691
232
|
type QuestionBehavior = 'ask' | 'auto' | 'deny';
|
|
692
|
-
/** A model the session can switch to (SDK ModelInfo mirror; fields it may grow stay unknown). */
|
|
693
233
|
type ModelOption = {
|
|
694
|
-
|
|
695
|
-
/** Wire model id this row resolves to ('sonnet' → 'claude-sonnet-5'). What a
|
|
696
|
-
* session actually reports as its model is the resolved form, so this is how a
|
|
697
|
-
* client matches the running model back to the row that names it. */
|
|
234
|
+
value: string;
|
|
698
235
|
resolvedModel?: string;
|
|
699
236
|
displayName: string;
|
|
700
237
|
description?: string;
|
|
701
|
-
/** Whether this belongs in a picker's main list rather than behind a "more
|
|
702
|
-
* models" step: the newest model of each family. Derived server-side — the CLI
|
|
703
|
-
* reports one flat list — so that every client groups it the same way. */
|
|
704
238
|
primary?: boolean;
|
|
705
|
-
/** Reasoning efforts this model supports at create time (codex catalogs carry
|
|
706
|
-
* them, from the binary's own per-model list). Absent = the engine's default
|
|
707
|
-
* set ({@link EngineCapabilities.reasoningEfforts}) applies. Open strings —
|
|
708
|
-
* the binary's vocabulary outruns its SDK's union. */
|
|
709
239
|
reasoningEfforts?: readonly string[];
|
|
710
240
|
};
|
|
711
|
-
/**
|
|
712
|
-
* A skill the engine can decide to use — **not** a command.
|
|
713
|
-
*
|
|
714
|
-
* The distinction is the whole point of this type existing beside
|
|
715
|
-
* {@link SlashCommandInfo}. A slash command is wire syntax: the CLI parses
|
|
716
|
-
* `/wrapup` out of the message and runs it. A skill is a capability the model
|
|
717
|
-
* *chooses* from its description; there is no `/skillname` the engine would
|
|
718
|
-
* recognise, and sending one reaches the model as literal text.
|
|
719
|
-
*
|
|
720
|
-
* So a client may list these, and may offer them as a **typing aid** that
|
|
721
|
-
* inserts ordinary editable prose ({@link SkillInfo.defaultPrompt}) — but it
|
|
722
|
-
* must never render them as command chips, and must never put them in
|
|
723
|
-
* `capabilities.commands`, which means "the CLI accepts these as commands".
|
|
724
|
-
*/
|
|
725
241
|
type SkillInfo = {
|
|
726
|
-
|
|
727
|
-
/** What the skill is for, as its own manifest states it. This is the text the
|
|
728
|
-
* MODEL selects on, so it is also the most honest thing to show a human. */
|
|
242
|
+
name: string;
|
|
729
243
|
description?: string;
|
|
730
|
-
/** A one-liner where the skill declares one, for a list row too narrow for
|
|
731
|
-
* `description`. */
|
|
732
244
|
shortDescription?: string;
|
|
733
|
-
/** Human-facing name from the skill's own interface block, when it differs
|
|
734
|
-
* from `name`. */
|
|
735
245
|
displayName?: string;
|
|
736
|
-
/**
|
|
737
|
-
* The engine's own suggested opening message for this skill. A client that
|
|
738
|
-
* offers a picker INSERTS this as plain editable text for the user to finish
|
|
739
|
-
* and send; it is a draft, never something submitted on selection.
|
|
740
|
-
*/
|
|
741
246
|
defaultPrompt?: string;
|
|
742
|
-
/** Where the skill came from: 'user' | 'repo' | 'system' | 'admin' — kept as
|
|
743
|
-
* a string, the engine's set may grow. */
|
|
744
247
|
scope?: string;
|
|
745
|
-
/** False when the operator has this skill switched off: still listed, because
|
|
746
|
-
* "installed but off" is a different answer from "not installed". */
|
|
747
248
|
enabled: boolean;
|
|
748
249
|
};
|
|
749
|
-
/** A slash command the CLI accepts as user-message text (SDK SlashCommand mirror). */
|
|
750
250
|
type SlashCommandInfo = {
|
|
751
|
-
|
|
752
|
-
description?: string;
|
|
753
|
-
argumentHint?: string;
|
|
251
|
+
name: string;
|
|
252
|
+
description?: string;
|
|
253
|
+
argumentHint?: string;
|
|
754
254
|
aliases?: string[];
|
|
755
255
|
};
|
|
756
|
-
/** One category row from the CLI's context-usage breakdown (system prompt, tools, ...). */
|
|
757
256
|
type ContextUsageCategory = {
|
|
758
257
|
name: string;
|
|
759
258
|
tokens: number;
|
|
760
|
-
/** Color the CLI assigns the category. Often a CLI theme token name ('inactive',
|
|
761
|
-
* 'promptBorder', ...), not a CSS color — validate before styling with it. */
|
|
762
259
|
color: string;
|
|
763
260
|
};
|
|
764
|
-
/** Context-window usage snapshot (SDK getContextUsage mirror), polled after each turn. */
|
|
765
261
|
type ContextUsage = {
|
|
766
262
|
categories: ContextUsageCategory[];
|
|
767
263
|
totalTokens: number;
|
|
768
|
-
maxTokens: number;
|
|
769
|
-
percentage: number;
|
|
264
|
+
maxTokens: number;
|
|
265
|
+
percentage: number;
|
|
770
266
|
model?: string;
|
|
771
267
|
};
|
|
772
|
-
/**
|
|
773
|
-
* The context-window reading that rides the **sessions list**, as opposed to the
|
|
774
|
-
* full {@link ContextUsage} that rides the event stream.
|
|
775
|
-
*
|
|
776
|
-
* Three numbers, and the omission is the design: `categories` is a breakdown for
|
|
777
|
-
* a dialog that has a live session behind it, and this field is on every row of
|
|
778
|
-
* `GET /sessions`, which a busy client polls at 1.2s. Same attachment-bytes
|
|
779
|
-
* discipline as {@link SessionInfo.subagents}. `percentage` alone would size a
|
|
780
|
-
* ring, but the token pair is what lets a row *say* `142k / 200k` on hover or
|
|
781
|
-
* long-press without a second round trip, and it is two numbers.
|
|
782
|
-
*
|
|
783
|
-
* **Absent is a real state and is not zero.** A parked session, one that has
|
|
784
|
-
* never run a turn, or an engine that reports no window has no reading — render
|
|
785
|
-
* nothing, never an empty ring, which claims "context is empty" rather than "no
|
|
786
|
-
* answer". Also absent on an older server.
|
|
787
|
-
*/
|
|
788
268
|
type ContextReading = {
|
|
789
269
|
totalTokens: number;
|
|
790
|
-
maxTokens: number;
|
|
270
|
+
maxTokens: number;
|
|
791
271
|
percentage: number;
|
|
792
272
|
};
|
|
793
|
-
/**
|
|
794
|
-
* One rate-limit window snapshot (SDK SDKRateLimitInfo mirror). Emitted only for
|
|
795
|
-
* claude.ai subscription sessions — API-key sessions may never produce one, so
|
|
796
|
-
* clients must render nothing (not 0%) until data arrives.
|
|
797
|
-
*/
|
|
798
273
|
type RateLimitInfo = {
|
|
799
|
-
|
|
800
|
-
/** Which window: 'five_hour' (session), 'seven_day' (weekly), 'seven_day_opus',
|
|
801
|
-
* 'seven_day_sonnet', 'overage', ... — kept as string, the SDK union may grow. */
|
|
274
|
+
status: string;
|
|
802
275
|
rateLimitType?: string;
|
|
803
|
-
|
|
804
|
-
* absent as unknown, not 0. */
|
|
805
|
-
utilization?: number; /** Epoch **seconds** when the window resets (render countdowns client-side). */
|
|
276
|
+
utilization?: number;
|
|
806
277
|
resetsAt?: number;
|
|
807
278
|
isUsingOverage?: boolean;
|
|
808
279
|
};
|
|
809
|
-
/**
|
|
810
|
-
* Lifecycle of one tool execution, correlated by `executionId` end to end.
|
|
811
|
-
*
|
|
812
|
-
* - `pending` — dispatched, result not in yet (bridged to a client, or queued).
|
|
813
|
-
* - `deferred` — parked beyond this turn/process; may outlive the session's
|
|
814
|
-
* liveness and be applied on rehydration.
|
|
815
|
-
* - `settled` / `failed` — terminal. Results are applied idempotently by id, so
|
|
816
|
-
* a duplicate delivery is a no-op rather than a second application.
|
|
817
|
-
*/
|
|
818
280
|
type ToolExecutionStatus = 'pending' | 'deferred' | 'settled' | 'failed';
|
|
819
|
-
/** Where a tool execution ran (or is running). Advisory: for display and routing. */
|
|
820
281
|
type ToolExecutionBackend = 'server' | 'browser' | 'managed' | 'remote';
|
|
821
|
-
/** Result payload of a tool execution, by value — never a live host reference. */
|
|
822
282
|
type ToolExecutionOutput = {
|
|
823
283
|
type: 'text';
|
|
824
284
|
value: string;
|
|
@@ -826,14 +286,11 @@ type ToolExecutionOutput = {
|
|
|
826
286
|
type: 'json';
|
|
827
287
|
value: unknown;
|
|
828
288
|
};
|
|
829
|
-
type SessionEventBody =
|
|
289
|
+
type SessionEventBody = {
|
|
830
290
|
type: 'system_init';
|
|
831
291
|
sdkSessionId: string;
|
|
832
292
|
model: string;
|
|
833
293
|
cwd: string;
|
|
834
|
-
/** Where the session's Anthropic auth came from: 'oauth' means a claude.ai
|
|
835
|
-
* subscription login; other values ('user' | 'project' | 'org' | 'temporary')
|
|
836
|
-
* are API-key provenance. Kept as string — the SDK union may grow. */
|
|
837
294
|
apiKeySource: string;
|
|
838
295
|
tools: string[];
|
|
839
296
|
skills: string[];
|
|
@@ -848,136 +305,55 @@ type SessionEventBody = /** SDK init handshake: what this session actually is. *
|
|
|
848
305
|
type: 'status_changed';
|
|
849
306
|
status: SessionStatus;
|
|
850
307
|
detail?: string;
|
|
851
|
-
}
|
|
852
|
-
/** Models and slash commands available to this session; fetched from the CLI after
|
|
853
|
-
* init. Late attachers get it via replay like any other event. */
|
|
854
|
-
| {
|
|
308
|
+
} | {
|
|
855
309
|
type: 'capabilities';
|
|
856
310
|
models: ModelOption[];
|
|
857
311
|
commands: SlashCommandInfo[];
|
|
858
|
-
/** Wire id the session's *default* resolves to, from the CLI's own `default`
|
|
859
|
-
* row. Answers "what will this session answer as" before it has answered
|
|
860
|
-
* anything — `system_init` carries the model, but a promptless session gets
|
|
861
|
-
* no `system_init` until its first message. */
|
|
862
312
|
defaultModel?: string;
|
|
863
|
-
}
|
|
864
|
-
/**
|
|
865
|
-
* The skills this session's engine can reach ({@link SkillInfo}) — a full
|
|
866
|
-
* replacement each time, not a delta, so a late attacher's replay of several
|
|
867
|
-
* of these converges on the last one. Emitted once the session's engine has
|
|
868
|
-
* enumerated them, and again whenever the engine reports the set changed
|
|
869
|
-
* (a skill added or edited on disk).
|
|
870
|
-
*
|
|
871
|
-
* Deliberately NOT folded into `capabilities.commands`: skills are not
|
|
872
|
-
* commands (see {@link SkillInfo}). Only engines whose record sets
|
|
873
|
-
* {@link EngineCapabilities.skillsList} ever emit it.
|
|
874
|
-
*/
|
|
875
|
-
| {
|
|
313
|
+
} | {
|
|
876
314
|
type: 'skills';
|
|
877
315
|
skills: SkillInfo[];
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
* The engine wrote a file on the **host filesystem** and handed over its path
|
|
881
|
-
* — codex's `image_gen` saving a PNG is the case that motivated it. The
|
|
882
|
-
* host-filesystem sibling of `file_delivered` (which is the scratch-VFS one).
|
|
883
|
-
*
|
|
884
|
-
* Fetch it at `GET {basePath}/sessions/:id/produced/:fileId` for as long as
|
|
885
|
-
* the session lives. That route has no root allowlist and no size cap, and
|
|
886
|
-
* that is sound *because of where the path came from*: this event is authored
|
|
887
|
-
* by the runner about a file the engine itself just wrote, not by the agent
|
|
888
|
-
* about a path it chose. `/fs/*` gates the second kind and must keep doing so
|
|
889
|
-
* — a file the agent merely *read* is not a produced file and does not belong
|
|
890
|
-
* here.
|
|
891
|
-
*
|
|
892
|
-
* Re-emitting the same file is a no-op: `fileId` is derived from the path, so
|
|
893
|
-
* a runner that learns the path twice (codex reports `savedPath` on both the
|
|
894
|
-
* progress and completed item) registers it once.
|
|
895
|
-
*/
|
|
896
|
-
| {
|
|
897
|
-
type: 'file_produced'; /** Opaque, stable per session+path. The route's path segment. */
|
|
316
|
+
} | {
|
|
317
|
+
type: 'file_produced';
|
|
898
318
|
fileId: string;
|
|
899
|
-
/** Absolute host path, as the engine reported it. Shown to the operator,
|
|
900
|
-
* and what a client matches against a tool card's own `savedPath`. */
|
|
901
319
|
path: string;
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
mediaType?: string; /** Size at the time it was reported, when the runner knew it. */
|
|
905
|
-
bytes?: number; /** The tool call that produced it, when one did. */
|
|
320
|
+
mediaType?: string;
|
|
321
|
+
bytes?: number;
|
|
906
322
|
toolUseId?: string;
|
|
907
|
-
}
|
|
323
|
+
} | {
|
|
908
324
|
type: 'model_changed';
|
|
909
325
|
model?: string;
|
|
910
|
-
}
|
|
326
|
+
} | {
|
|
911
327
|
type: 'permission_mode_changed';
|
|
912
328
|
mode: PermissionMode;
|
|
913
|
-
}
|
|
329
|
+
} | {
|
|
914
330
|
type: 'context_usage';
|
|
915
331
|
usage: ContextUsage;
|
|
916
|
-
}
|
|
332
|
+
} | {
|
|
917
333
|
type: 'rate_limit';
|
|
918
334
|
info: RateLimitInfo;
|
|
919
|
-
}
|
|
920
|
-
/** Which claude.ai plan the rate-limit windows belong to: 'pro' | 'max' | 'team' |
|
|
921
|
-
* 'enterprise' — kept as string, the set may grow. Emitted from the same poll as
|
|
922
|
-
* `rate_limit`, once per change, and never for an API-key session (which has no
|
|
923
|
-
* plan). It names the windows; it does not size them — the tier suffix a
|
|
924
|
-
* subscription page shows ("Max 20x") is not in the data. */
|
|
925
|
-
| {
|
|
335
|
+
} | {
|
|
926
336
|
type: 'plan_info';
|
|
927
337
|
subscriptionType: string;
|
|
928
|
-
}
|
|
929
|
-
/**
|
|
930
|
-
* The engine started a **fresh conversation inside the same session** — the
|
|
931
|
-
* CLI's `/clear`, a plan-mode exit, and whatever fresh-conversation flows the
|
|
932
|
-
* SDK grows. The session id, registry row, workspace and scope are all
|
|
933
|
-
* unchanged; only the conversation is new. Clients empty the transcript and
|
|
934
|
-
* keep every session-scoped fact (models, commands, skills, produced files,
|
|
935
|
-
* rate limits, cwd, permission mode).
|
|
936
|
-
*
|
|
937
|
-
* The server's replay honours it too: an attach after a reset does not
|
|
938
|
-
* resurrect the cleared rows, because the runner skips *transcript content*
|
|
939
|
-
* below the latest reset (see {@link transcriptContent}) while still
|
|
940
|
-
* replaying every state-bearing event. `SessionInfo.activityCount` stays
|
|
941
|
-
* monotonic across a reset on purpose — it is an unread cursor, not an item
|
|
942
|
-
* count, and winding it back would kill every stored watermark above it.
|
|
943
|
-
*/
|
|
944
|
-
| {
|
|
338
|
+
} | {
|
|
945
339
|
type: 'conversation_reset';
|
|
946
|
-
/** The engine session id the fresh conversation runs under (the SDK's
|
|
947
|
-
* `new_conversation_id`), when the engine reported one. The follow-up
|
|
948
|
-
* `system_init` remains authoritative. */
|
|
949
340
|
sdkSessionId?: string;
|
|
950
341
|
} | {
|
|
951
342
|
type: 'assistant_message';
|
|
952
|
-
message: ApiMessage;
|
|
953
|
-
parentToolUseId: string | null;
|
|
343
|
+
message: ApiMessage;
|
|
344
|
+
parentToolUseId: string | null;
|
|
954
345
|
replay?: boolean;
|
|
955
346
|
uuid: string;
|
|
956
347
|
} | {
|
|
957
348
|
type: 'user_message';
|
|
958
349
|
message: ApiMessage;
|
|
959
|
-
parentToolUseId: string | null;
|
|
960
|
-
replay?: boolean;
|
|
350
|
+
parentToolUseId: string | null;
|
|
351
|
+
replay?: boolean;
|
|
961
352
|
synthetic?: boolean;
|
|
962
|
-
/** Files sent with this message, by reference (see {@link MessageAttachment}).
|
|
963
|
-
* `message.content` carries the typed text only — the attachment bytes went
|
|
964
|
-
* to the model, not into this log. */
|
|
965
353
|
attachments?: MessageAttachment[];
|
|
966
|
-
/**
|
|
967
|
-
* What a file-editing tool changed, when this message carries that tool's
|
|
968
|
-
* result (see {@link FilePatch}). Set by the runner from the engine's own
|
|
969
|
-
* structured output — never derived by a client from the result text.
|
|
970
|
-
*
|
|
971
|
-
* Only when the message carries exactly one `tool_result` block, which is
|
|
972
|
-
* what both engines send: with two, there is nothing that says which one
|
|
973
|
-
* the patch belongs to, and guessing would attach a diff to the wrong call.
|
|
974
|
-
*/
|
|
975
354
|
patch?: FilePatch;
|
|
976
355
|
uuid?: string;
|
|
977
|
-
}
|
|
978
|
-
/** Raw Anthropic streaming event (message_start/content_block_delta/...); emitted only
|
|
979
|
-
* when the session was created with `includePartialMessages`. */
|
|
980
|
-
| {
|
|
356
|
+
} | {
|
|
981
357
|
type: 'stream_delta';
|
|
982
358
|
event: {
|
|
983
359
|
type: string;
|
|
@@ -991,7 +367,7 @@ type SessionEventBody = /** SDK init handshake: what this session actually is. *
|
|
|
991
367
|
isError: boolean;
|
|
992
368
|
durationMs: number;
|
|
993
369
|
numTurns: number;
|
|
994
|
-
totalCostUsd: number;
|
|
370
|
+
totalCostUsd: number;
|
|
995
371
|
result?: string;
|
|
996
372
|
errors?: string[];
|
|
997
373
|
usage?: unknown;
|
|
@@ -1002,48 +378,34 @@ type SessionEventBody = /** SDK init handshake: what this session actually is. *
|
|
|
1002
378
|
type: 'permission_resolved';
|
|
1003
379
|
requestId: string;
|
|
1004
380
|
behavior: 'allow' | 'deny';
|
|
1005
|
-
resolvedBy: PermissionDecisionSource;
|
|
381
|
+
resolvedBy: PermissionDecisionSource;
|
|
1006
382
|
message?: string;
|
|
1007
|
-
}
|
|
1008
|
-
/** A tool execution was dispatched to a backend. For bridged executions this
|
|
1009
|
-
* precedes the `tool_call_request` frame; for deferred ones it is the record
|
|
1010
|
-
* that survives a teardown. */
|
|
1011
|
-
| {
|
|
383
|
+
} | {
|
|
1012
384
|
type: 'execution_dispatched';
|
|
1013
385
|
executionId: string;
|
|
1014
386
|
toolName: string;
|
|
1015
|
-
backend: ToolExecutionBackend;
|
|
1016
|
-
deferred?: boolean;
|
|
387
|
+
backend: ToolExecutionBackend;
|
|
388
|
+
deferred?: boolean;
|
|
1017
389
|
expiresAt?: number;
|
|
1018
|
-
}
|
|
390
|
+
} | {
|
|
1019
391
|
type: 'execution_result';
|
|
1020
392
|
executionId: string;
|
|
1021
|
-
output: ToolExecutionOutput;
|
|
393
|
+
output: ToolExecutionOutput;
|
|
1022
394
|
logs?: string[];
|
|
1023
395
|
durationMs?: number;
|
|
1024
|
-
}
|
|
1025
|
-
/** A dispatched execution failed, timed out, or was orphaned. The failure is fed
|
|
1026
|
-
* back into the loop as tool output so the agent can adapt — it is not a session error. */
|
|
1027
|
-
| {
|
|
396
|
+
} | {
|
|
1028
397
|
type: 'execution_failed';
|
|
1029
|
-
executionId: string;
|
|
398
|
+
executionId: string;
|
|
1030
399
|
reason: string;
|
|
1031
400
|
error: string;
|
|
1032
401
|
logs?: string[];
|
|
1033
402
|
durationMs?: number;
|
|
1034
|
-
}
|
|
1035
|
-
/** The agent handed over a file from its session scratch filesystem (the
|
|
1036
|
-
* `deliver_file` tool). Download it via `GET {basePath}/sessions/:id/files/<path>`
|
|
1037
|
-
* for as long as the session lives (the VFS is in-memory). */
|
|
1038
|
-
| {
|
|
403
|
+
} | {
|
|
1039
404
|
type: 'file_delivered';
|
|
1040
405
|
path: string;
|
|
1041
406
|
bytes: number;
|
|
1042
407
|
description?: string;
|
|
1043
|
-
}
|
|
1044
|
-
/** Any SDKMessage this protocol version doesn't model first-class (task progress,
|
|
1045
|
-
* compaction boundaries, auth status, ...). Payload is the raw SDK message. */
|
|
1046
|
-
| {
|
|
408
|
+
} | {
|
|
1047
409
|
type: 'sdk_event';
|
|
1048
410
|
payload: {
|
|
1049
411
|
type: string;
|
|
@@ -1057,68 +419,36 @@ type SessionEventBody = /** SDK init handshake: what this session actually is. *
|
|
|
1057
419
|
reason: 'client' | 'server' | 'error';
|
|
1058
420
|
};
|
|
1059
421
|
type SessionEvent = SessionEventBody & {
|
|
1060
|
-
|
|
422
|
+
seq: number;
|
|
1061
423
|
ts: number;
|
|
1062
424
|
};
|
|
1063
425
|
type SessionCommand = {
|
|
1064
426
|
type: 'user_message';
|
|
1065
427
|
text: string;
|
|
1066
|
-
/** Ids from `POST {basePath}/sessions/:id/attachments`, in the order they
|
|
1067
|
-
* should reach the model. Unknown ids fail the command rather than sending
|
|
1068
|
-
* a message that quietly lost its picture. */
|
|
1069
428
|
attachmentIds?: string[];
|
|
1070
429
|
} | {
|
|
1071
430
|
type: 'permission_decision';
|
|
1072
431
|
requestId: string;
|
|
1073
|
-
behavior: 'allow' | 'deny';
|
|
1074
|
-
updatedInput?: Record<string, unknown>;
|
|
1075
|
-
message?: string;
|
|
432
|
+
behavior: 'allow' | 'deny';
|
|
433
|
+
updatedInput?: Record<string, unknown>;
|
|
434
|
+
message?: string;
|
|
1076
435
|
interrupt?: boolean;
|
|
1077
436
|
} | {
|
|
1078
437
|
type: 'interrupt';
|
|
1079
|
-
}
|
|
1080
|
-
/**
|
|
1081
|
-
* Reset the conversation in place — same session id, same watermarks, same
|
|
1082
|
-
* row in the list, empty context. The server answers with a
|
|
1083
|
-
* `conversation_reset` event, whose replay rules are what stop a later attach
|
|
1084
|
-
* from resurrecting the cleared transcript.
|
|
1085
|
-
*
|
|
1086
|
-
* Send only where {@link EngineCapabilities.clearContext} says so: a server
|
|
1087
|
-
* that predates this command rejects it as unknown, and an engine that cannot
|
|
1088
|
-
* do it errors rather than quietly doing nothing.
|
|
1089
|
-
*
|
|
1090
|
-
* Sent while a turn is running, it **queues** behind that turn rather than
|
|
1091
|
-
* cutting it short — a clear is not an interrupt, and one that landed in the
|
|
1092
|
-
* middle of the turn it was clearing would be neither. Interrupt first if the
|
|
1093
|
-
* intent was to stop the work as well as forget it.
|
|
1094
|
-
*/
|
|
1095
|
-
| {
|
|
438
|
+
} | {
|
|
1096
439
|
type: 'clear_context';
|
|
1097
440
|
} | {
|
|
1098
441
|
type: 'set_permission_mode';
|
|
1099
442
|
mode: PermissionMode;
|
|
1100
|
-
}
|
|
443
|
+
} | {
|
|
1101
444
|
type: 'set_model';
|
|
1102
445
|
model?: string;
|
|
1103
|
-
}
|
|
1104
|
-
/**
|
|
1105
|
-
* Result of a tool execution the server bridged to this client (see
|
|
1106
|
-
* {@link ToolCallRequestFrame}). Unknown or already-settled `executionId`s are
|
|
1107
|
-
* ignored — delivery is idempotent, and a late result after a timeout must not
|
|
1108
|
-
* re-open a settled call.
|
|
1109
|
-
*
|
|
1110
|
-
* Browser-returned results are UNTRUSTED input: acceptable for the user's own
|
|
1111
|
-
* data, never a source for server-authoritative state.
|
|
1112
|
-
*/
|
|
1113
|
-
| {
|
|
446
|
+
} | {
|
|
1114
447
|
type: 'tool_call_result';
|
|
1115
448
|
executionId: string;
|
|
1116
449
|
output: ToolExecutionOutput;
|
|
1117
450
|
logs?: string[];
|
|
1118
|
-
}
|
|
1119
|
-
/** The client could not execute a bridged call (unsupported tool, guest error,
|
|
1120
|
-
* tab closing). Fed back to the agent as tool output. */
|
|
1121
|
-
| {
|
|
451
|
+
} | {
|
|
1122
452
|
type: 'tool_call_error';
|
|
1123
453
|
executionId: string;
|
|
1124
454
|
reason: string;
|
|
@@ -1127,40 +457,28 @@ type SessionCommand = {
|
|
|
1127
457
|
} | {
|
|
1128
458
|
type: 'close';
|
|
1129
459
|
};
|
|
1130
|
-
/** First frame the server sends after a successful attach. */
|
|
1131
460
|
type AttachedFrame = {
|
|
1132
461
|
type: 'attached';
|
|
1133
462
|
protocolVersion: number;
|
|
1134
|
-
session: SessionInfo;
|
|
463
|
+
session: SessionInfo;
|
|
1135
464
|
replayingFrom: number;
|
|
1136
465
|
};
|
|
1137
|
-
/**
|
|
1138
|
-
* Ask the attached client to execute a tool call in its own sandbox (browser
|
|
1139
|
-
* bridge). The client answers with `tool_call_result` or `tool_call_error`
|
|
1140
|
-
* carrying the same `executionId`.
|
|
1141
|
-
*
|
|
1142
|
-
* Only sandbox-benefiting tools are ever bridged. Authenticated/authoritative
|
|
1143
|
-
* tools (MCP, secret-bearing APIs) execute server-side and never appear here.
|
|
1144
|
-
*/
|
|
1145
466
|
type ToolCallRequestFrame = {
|
|
1146
467
|
type: 'tool_call_request';
|
|
1147
468
|
executionId: string;
|
|
1148
469
|
toolName: string;
|
|
1149
|
-
input: unknown;
|
|
470
|
+
input: unknown;
|
|
1150
471
|
vfsSeed?: Record<string, string>;
|
|
1151
472
|
limits?: {
|
|
1152
473
|
timeoutMs?: number;
|
|
1153
474
|
memoryLimitBytes?: number;
|
|
1154
|
-
};
|
|
475
|
+
};
|
|
1155
476
|
expiresAt?: number;
|
|
1156
477
|
};
|
|
1157
478
|
type ServerFrame = AttachedFrame | {
|
|
1158
479
|
type: 'event';
|
|
1159
480
|
event: SessionEvent;
|
|
1160
|
-
} | ToolCallRequestFrame
|
|
1161
|
-
/** A bridged execution no longer needs an answer (turn interrupted, timed out,
|
|
1162
|
-
* or the session closed) — the client should abandon it. */
|
|
1163
|
-
| {
|
|
481
|
+
} | ToolCallRequestFrame | {
|
|
1164
482
|
type: 'tool_call_canceled';
|
|
1165
483
|
executionId: string;
|
|
1166
484
|
reason: string;
|
|
@@ -1169,283 +487,87 @@ type ServerFrame = AttachedFrame | {
|
|
|
1169
487
|
message: string;
|
|
1170
488
|
};
|
|
1171
489
|
type ClientFrame = SessionCommand;
|
|
1172
|
-
/** Per-profile fallbacks filled into session/job requests that leave the field
|
|
1173
|
-
* unset. Defaults, not enforced caps — an explicit request value always wins. */
|
|
1174
490
|
type ProfileDefaults = {
|
|
1175
491
|
model?: string;
|
|
1176
492
|
permissionMode?: PermissionMode;
|
|
1177
493
|
};
|
|
1178
|
-
/**
|
|
1179
|
-
* A named Claude Code config directory sessions can run under: the session's CLI
|
|
1180
|
-
* process gets it as CLAUDE_CONFIG_DIR, so the profile carries that directory's
|
|
1181
|
-
* settings, memory, skills, and whatever credentials the SDK/CLI resolves from it.
|
|
1182
|
-
* Profiles are declared in server options at startup (or a 'default' one is
|
|
1183
|
-
* auto-created from the operator's own config dir) — the API only reads them.
|
|
1184
|
-
*/
|
|
1185
|
-
/**
|
|
1186
|
-
* Which engine a profile runs on. A **closed union, deliberately**: both clients
|
|
1187
|
-
* switch exhaustively, the Swift mirror ships in lockstep, and a closed set is
|
|
1188
|
-
* what lets this package carry per-engine capability defaults
|
|
1189
|
-
* ({@link ENGINE_CAPABILITIES}) browser-safe, with no server round-trip. Adding a
|
|
1190
|
-
* member is a versioned protocol event.
|
|
1191
|
-
*
|
|
1192
|
-
* - `claude` (default) — Claude Code via the Agent SDK, configured by a config dir.
|
|
1193
|
-
* - `codex` — OpenAI Codex over the codex CLI binary's `app-server` JSON-RPC
|
|
1194
|
-
* surface, configured by a CODEX_HOME (auth resolved by the binary itself,
|
|
1195
|
-
* like claude).
|
|
1196
|
-
* - `provider` — a model-agnostic provider over the AI SDK, assembled by the
|
|
1197
|
-
* host's `createEngineRunner` hook.
|
|
1198
|
-
*/
|
|
1199
494
|
type ProfileEngine = 'claude' | 'codex' | 'provider';
|
|
1200
|
-
/**
|
|
1201
|
-
* What an engine does and does not do — one axis per real difference, each field
|
|
1202
|
-
* answering a concrete UI or gateway question. Clients render from this record
|
|
1203
|
-
* instead of switching on the engine name: an absent capability means the
|
|
1204
|
-
* affordance is *hidden*, never a control that silently does nothing.
|
|
1205
|
-
*
|
|
1206
|
-
* Reaches clients in two places, same shape: `ProfileInfo.capabilities` (stamped
|
|
1207
|
-
* by the server; the create form's source) and `SessionInfo.capabilities`
|
|
1208
|
-
* (reported by the runner; the session surface's source). When the field is
|
|
1209
|
-
* absent — an older server — {@link ENGINE_CAPABILITIES} keyed by the engine
|
|
1210
|
-
* name is the browser-safe default.
|
|
1211
|
-
*/
|
|
1212
495
|
type EngineCapabilities = {
|
|
1213
|
-
/** PermissionRequest / permission_resolved can occur; approval UI is live.
|
|
1214
|
-
* False: hide approval affordances entirely (and `questionBehavior` on jobs). */
|
|
1215
496
|
interactiveApprovals: boolean;
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
permissionModes: readonly PermissionMode[]; /** Coercion target for a stored/unsupported mode choice (always ∈ permissionModes). */
|
|
1219
|
-
defaultPermissionMode: PermissionMode; /** CreateSessionRequest.resume works (an engine session id continues). */
|
|
497
|
+
permissionModes: readonly PermissionMode[];
|
|
498
|
+
defaultPermissionMode: PermissionMode;
|
|
1220
499
|
resume: boolean;
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
resumeBackfill: boolean; /** GET /sdk-sessions offers a resume picker for this engine. */
|
|
1225
|
-
listSessions: boolean; /** context_usage events can occur. False: render nothing — never a 0% ring. */
|
|
1226
|
-
contextUsage: boolean; /** rate_limit / plan_info events can occur. False: render nothing. */
|
|
500
|
+
resumeBackfill: boolean;
|
|
501
|
+
listSessions: boolean;
|
|
502
|
+
contextUsage: boolean;
|
|
1227
503
|
rateLimits: boolean;
|
|
1228
|
-
/** GET /sessions/:id/mcp works (else 501) — the engine can *list* its MCP
|
|
1229
|
-
* servers. Gates the MCP panel's existence. */
|
|
1230
504
|
mcpStatus: boolean;
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
* disable a server. Separate from {@link EngineCapabilities.mcpStatus}
|
|
1234
|
-
* because listing and acting are genuinely different powers: codex reports
|
|
1235
|
-
* rich status but exposes no per-server action on this transport, and a panel
|
|
1236
|
-
* that rendered the buttons anyway would present three controls that do
|
|
1237
|
-
* nothing and then report success. False: render the panel read-only.
|
|
1238
|
-
*/
|
|
1239
|
-
mcpServerActions: boolean; /** A session request may bring its own mcpServers. */
|
|
1240
|
-
sessionMcpServers: boolean; /** capabilities events carry slash commands (composer popover). */
|
|
505
|
+
mcpServerActions: boolean;
|
|
506
|
+
sessionMcpServers: boolean;
|
|
1241
507
|
slashCommands: boolean;
|
|
1242
|
-
/**
|
|
1243
|
-
* The `clear_context` command works — the engine can reset the conversation
|
|
1244
|
-
* *in place*, keeping the session id, its watermarks and its place in the
|
|
1245
|
-
* list, and announce it with {@link SessionEventBody} `conversation_reset`.
|
|
1246
|
-
*
|
|
1247
|
-
* Separate from {@link EngineCapabilities.slashCommands} because the two
|
|
1248
|
-
* answer different questions and only one engine has both: Claude reaches a
|
|
1249
|
-
* clear through the `/clear` its CLI already lists, codex has no command
|
|
1250
|
-
* surface at all and needs the explicit operation, and a client that gated
|
|
1251
|
-
* the control on `slashCommands` would offer it exactly where it was already
|
|
1252
|
-
* offered and hide it where it is the only route.
|
|
1253
|
-
*
|
|
1254
|
-
* Absent = false, so an older gateway hides the control rather than
|
|
1255
|
-
* presenting one that 501s.
|
|
1256
|
-
*/
|
|
1257
508
|
clearContext?: boolean;
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
* to `slashCommands`: an engine can have skills and no commands (codex), or
|
|
1261
|
-
* commands and no skill listing (claude, whose skills reach clients only as
|
|
1262
|
-
* `system_init.skills` names). */
|
|
1263
|
-
skillsList: boolean; /** settingSources / allowDangerouslySkipPermissions-style CLI options apply. */
|
|
1264
|
-
settingSources: boolean; /** maxTurns / maxBudgetUsd are honored (else the gateway 400s them). */
|
|
509
|
+
skillsList: boolean;
|
|
510
|
+
settingSources: boolean;
|
|
1265
511
|
budgets: boolean;
|
|
1266
|
-
/** Attachment kinds sendMessage can deliver to the model. Filter the attach
|
|
1267
|
-
* menu by kind; refuse locally before the server's 415. */
|
|
1268
512
|
attachments: ReadonlyArray<'image' | 'pdf' | 'text'>;
|
|
1269
|
-
|
|
1270
|
-
* Open strings — Codex's own binary already outruns its SDK's union. */
|
|
1271
|
-
reasoningEfforts?: readonly string[]; /** Sessions expose a scratch VFS (GET /sessions/:id/files, deliverables panel). */
|
|
513
|
+
reasoningEfforts?: readonly string[];
|
|
1272
514
|
vfs: boolean;
|
|
1273
|
-
/**
|
|
1274
|
-
* The engine runs against a host directory, so `CreateSessionRequest.cwd` is
|
|
1275
|
-
* required and meaningful (and a create form should ask for it). False: the
|
|
1276
|
-
* engine has no host filesystem — the gateway accepts a session with no
|
|
1277
|
-
* `cwd`, `SessionInfo.cwd` reports `''`, and there is no path to validate.
|
|
1278
|
-
*
|
|
1279
|
-
* Absent = true, so a wire copy from an older gateway keeps the old
|
|
1280
|
-
* always-required behaviour rather than silently relaxing it.
|
|
1281
|
-
*/
|
|
1282
515
|
hostCwd?: boolean;
|
|
1283
|
-
/** stream_delta granularity: per-token, coarse item updates (no typing
|
|
1284
|
-
* cursor), or none. */
|
|
1285
516
|
streaming: 'token' | 'item' | 'none';
|
|
1286
517
|
};
|
|
1287
|
-
/**
|
|
1288
|
-
* The static capability record of each engine — the browser-safe default for
|
|
1289
|
-
* `ProfileInfo.capabilities` / `SessionInfo.capabilities`, and the single place
|
|
1290
|
-
* the values are written down. Core's adapters *reference* this record and a
|
|
1291
|
-
* conformance test compares runner behaviour against it, so it cannot silently
|
|
1292
|
-
* diverge from the code. When both a wire copy and this default exist, the wire
|
|
1293
|
-
* copy wins.
|
|
1294
|
-
*/
|
|
1295
518
|
declare const ENGINE_CAPABILITIES: Record<ProfileEngine, EngineCapabilities>;
|
|
1296
|
-
/**
|
|
1297
|
-
* Permission modes the model-agnostic provider engine understands.
|
|
1298
|
-
* @deprecated Read `ENGINE_CAPABILITIES.provider.permissionModes` (this is an
|
|
1299
|
-
* alias of it, kept for protocol-5 consumers).
|
|
1300
|
-
*/
|
|
1301
|
-
declare const PROVIDER_PERMISSION_MODES: readonly PermissionMode[];
|
|
1302
|
-
/**
|
|
1303
|
-
* Whether a profile's engine can run a permission mode. The single source of
|
|
1304
|
-
* truth for the restriction: create forms filter what they offer with it, the
|
|
1305
|
-
* gateway rejects with it. An absent `engine` means 'claude' (every mode).
|
|
1306
|
-
*/
|
|
1307
519
|
declare function supportsPermissionMode(engine: ProfileEngine | undefined, mode: PermissionMode): boolean;
|
|
1308
|
-
/**
|
|
1309
|
-
* A model provider a `provider` profile can run on. Credentials are ALWAYS
|
|
1310
|
-
* resolved from the operator's environment — never carried on the wire, never
|
|
1311
|
-
* stored here. `apiKeyEnv` names the variable to read, it does not hold a key.
|
|
1312
|
-
*/
|
|
1313
520
|
type ProviderConfig = {
|
|
1314
|
-
|
|
1315
|
-
* 'openai-compatible'. Kept as a string: the set is host-extensible. */
|
|
1316
|
-
id: string; /** Default model id, e.g. 'kimi-k3'. Overridable per session. */
|
|
521
|
+
id: string;
|
|
1317
522
|
model?: string;
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
* `supportedModels()`, and only the operator knows which ids their endpoint and
|
|
1321
|
-
* key actually serve. Unset → the picker offers {@link ProviderConfig.model} alone. */
|
|
1322
|
-
models?: string[]; /** Base URL for OpenAI-compatible providers. */
|
|
1323
|
-
baseUrl?: string; /** Environment variable the operator put the key in. Never the key itself. */
|
|
523
|
+
models?: string[];
|
|
524
|
+
baseUrl?: string;
|
|
1324
525
|
apiKeyEnv?: string;
|
|
1325
526
|
};
|
|
1326
|
-
/**
|
|
1327
|
-
* A grantable capability of the model-agnostic engine, named after the tool it
|
|
1328
|
-
* yields. The always-present tools (`fs_*`, `eval_script`) are not listed: they
|
|
1329
|
-
* are the engine's scratch filesystem and sandbox, not a grant.
|
|
1330
|
-
*/
|
|
1331
527
|
type SessionCapability = 'web_search' | 'download' | 'web_fetch' | 'deliver_file';
|
|
1332
|
-
/**
|
|
1333
|
-
* What sessions under a `provider` profile get, declared by the operator. Meaning-
|
|
1334
|
-
* less for `claude` profiles, whose equivalents live in the config directory.
|
|
1335
|
-
*
|
|
1336
|
-
* MCP servers are named, never configured, here: a server's transport config can
|
|
1337
|
-
* carry credentials in its headers, and this type is served by `GET /profiles`.
|
|
1338
|
-
* The names refer to servers the host connected in `createEngineRunner`, which is
|
|
1339
|
-
* where the configs (and the credentials) stay.
|
|
1340
|
-
*/
|
|
1341
528
|
type ProfileSessionDefaults = {
|
|
1342
|
-
/** Capabilities granted to sessions under this profile. Absent = no
|
|
1343
|
-
* declaration, so a session gets whatever backends the host wired. A session
|
|
1344
|
-
* request may narrow this set, never widen it. */
|
|
1345
529
|
capabilities?: SessionCapability[];
|
|
1346
|
-
|
|
1347
|
-
* Absent = no declaration (every server the host connected). */
|
|
1348
|
-
mcpServers?: string[]; /** Prepended to the session's system prompt. */
|
|
530
|
+
mcpServers?: string[];
|
|
1349
531
|
instructions?: string;
|
|
1350
532
|
};
|
|
1351
|
-
/**
|
|
1352
|
-
* One rate-limit window of a profile's plan, as the *gateway* last saw it — the
|
|
1353
|
-
* newest {@link RateLimitInfo} any session on the profile reported, across every
|
|
1354
|
-
* session, live or since closed. The profile is the account boundary (one config
|
|
1355
|
-
* dir / codex home / provider key = one plan), so this is the single usage state
|
|
1356
|
-
* per account, where a session's own transcript only knows what *it* was last
|
|
1357
|
-
* told.
|
|
1358
|
-
*
|
|
1359
|
-
* Two rules a client must keep:
|
|
1360
|
-
* - An absent window (or an absent {@link ProfileInfo.usage} entirely) is
|
|
1361
|
-
* **unknown, not 0%** — render nothing, exactly as for session-level readings.
|
|
1362
|
-
* The map is in-memory and starts empty on a cold server.
|
|
1363
|
-
* - `inferredReset` marks a reading the server zeroed at serve time because the
|
|
1364
|
-
* reading's own `resetsAt` passed with nothing newer: the pre-reset number is
|
|
1365
|
-
* then provably wrong, and 0 is the truthful *floor* (the account may have
|
|
1366
|
-
* been used outside this gateway since). Distinguishable on the wire from an
|
|
1367
|
-
* engine-reported 0, which carries no flag.
|
|
1368
|
-
*/
|
|
1369
533
|
type ProfileUsageWindow = {
|
|
1370
|
-
/** The reading, exactly as the session event carried it — except after an
|
|
1371
|
-
* elapsed reset, when `utilization` is 0 and `resetsAt` is dropped (the old
|
|
1372
|
-
* one names the *previous* window; a countdown from it would be nonsense). */
|
|
1373
534
|
info: RateLimitInfo;
|
|
1374
|
-
|
|
1375
|
-
* lines even when the served utilization is inferred. */
|
|
1376
|
-
updatedAt: number; /** Present (true) only on the served-as-0 inference described above. */
|
|
535
|
+
updatedAt: number;
|
|
1377
536
|
inferredReset?: boolean;
|
|
1378
537
|
};
|
|
1379
|
-
/** Per-window plan usage, keyed by `rateLimitType` ('five_hour', 'seven_day',
|
|
1380
|
-
* ...) — the same keying as a transcript's rate-limit state. */
|
|
1381
538
|
type ProfileUsage = Record<string, ProfileUsageWindow>;
|
|
1382
539
|
type ProfileInfo = {
|
|
1383
|
-
|
|
1384
|
-
/** Engine this profile runs on. Defaults to 'claude' when absent, so profiles
|
|
1385
|
-
* written before provider support keep working unchanged. */
|
|
540
|
+
name: string;
|
|
1386
541
|
engine?: ProfileEngine;
|
|
1387
|
-
/** Absolute path set as CLAUDE_CONFIG_DIR for the session's CLI process.
|
|
1388
|
-
* Required for 'claude' profiles; meaningless for the other engines. */
|
|
1389
542
|
configDir?: string;
|
|
1390
|
-
|
|
1391
|
-
* process (auth, config.toml, thread storage) — the `configDir` analogue,
|
|
1392
|
-
* request-writable like it. Unset = the binary's own `~/.codex`. */
|
|
1393
|
-
codexHome?: string; /** Provider wiring for 'provider' profiles. */
|
|
543
|
+
codexHome?: string;
|
|
1394
544
|
provider?: ProviderConfig;
|
|
1395
545
|
description?: string;
|
|
1396
|
-
defaults?: ProfileDefaults;
|
|
546
|
+
defaults?: ProfileDefaults;
|
|
1397
547
|
session?: ProfileSessionDefaults;
|
|
1398
|
-
/** Response-only: the engine's model catalog, shipped with the release and
|
|
1399
|
-
* served from the first request (no process spawned, no warm-up session).
|
|
1400
|
-
* For provider profiles the ids come from `provider.models` instead. Never
|
|
1401
|
-
* contains a 'default' sentinel row — forms add their own "Profile default"
|
|
1402
|
-
* row mapping to an unset model. Ignored on the way in. */
|
|
1403
548
|
models?: ModelOption[];
|
|
1404
|
-
/** Response-only: what this profile's default model resolves to. For claude
|
|
1405
|
-
* profiles this is the operator's CLI config — unknowable statically — so it
|
|
1406
|
-
* is absent until a session on this profile reports it. */
|
|
1407
549
|
defaultModel?: string;
|
|
1408
|
-
/** Response-only: the engine's capability record (see {@link EngineCapabilities}).
|
|
1409
|
-
* Absent = use ENGINE_CAPABILITIES[engine]. Ignored on the way in. */
|
|
1410
550
|
capabilities?: EngineCapabilities;
|
|
1411
|
-
/** Response-only: whether the profile's credentials probe as usable right now.
|
|
1412
|
-
* Absent = unknown/unchecked — treat as available. **Display-only**: create
|
|
1413
|
-
* against an unavailable profile still proceeds and fails with the engine's
|
|
1414
|
-
* own error (the probe can be stale in both directions). */
|
|
1415
551
|
available?: boolean;
|
|
1416
|
-
/** Response-only: one operator-actionable line, present only when
|
|
1417
|
-
* `available === false`. */
|
|
1418
552
|
unavailableReason?: string;
|
|
1419
|
-
/** Response-only: the plan's rate-limit windows as last reported by any
|
|
1420
|
-
* session on this profile (see {@link ProfileUsageWindow}). Absent = unknown
|
|
1421
|
-
* — no session has reported yet (API-key sessions never do), or the server
|
|
1422
|
-
* restarted. **Display-only**, like `available`: never a gate. */
|
|
1423
553
|
usage?: ProfileUsage;
|
|
1424
|
-
/** Response-only, computed by the server: this profile came from the profile
|
|
1425
|
-
* store and can be edited or deleted through the API. Profiles declared in
|
|
1426
|
-
* server options are absent/false — they are code. Ignored on the way in. */
|
|
1427
554
|
managed?: boolean;
|
|
1428
555
|
};
|
|
1429
|
-
/**
|
|
1430
|
-
* Curated, read-only snapshot of what a profile's config directory contains —
|
|
1431
|
-
* the parts relevant to running worker sessions. Values that could carry secrets
|
|
1432
|
-
* (env var values) never leave the server; only names are listed.
|
|
1433
|
-
*/
|
|
1434
556
|
type ProfileConfigSnapshot = {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
defaultPermissionMode?: string;
|
|
557
|
+
settings?: {
|
|
558
|
+
model?: string;
|
|
559
|
+
defaultPermissionMode?: string;
|
|
1438
560
|
permissionRules?: {
|
|
1439
561
|
allow: number;
|
|
1440
562
|
ask: number;
|
|
1441
563
|
deny: number;
|
|
1442
|
-
};
|
|
1443
|
-
envKeys?: string[];
|
|
564
|
+
};
|
|
565
|
+
envKeys?: string[];
|
|
1444
566
|
hooks?: string[];
|
|
1445
|
-
};
|
|
1446
|
-
hasUserMemory: boolean;
|
|
1447
|
-
skills: string[];
|
|
1448
|
-
agents: string[];
|
|
567
|
+
};
|
|
568
|
+
hasUserMemory: boolean;
|
|
569
|
+
skills: string[];
|
|
570
|
+
agents: string[];
|
|
1449
571
|
commands: string[];
|
|
1450
572
|
};
|
|
1451
573
|
type McpServerConfigWire = {
|
|
@@ -1462,9 +584,6 @@ type McpServerConfigWire = {
|
|
|
1462
584
|
url: string;
|
|
1463
585
|
headers?: Record<string, string>;
|
|
1464
586
|
};
|
|
1465
|
-
/** One tool an MCP server exposes, as the session's engine reports it.
|
|
1466
|
-
* Parameters are deliberately absent: the CLI's status payload names and
|
|
1467
|
-
* describes each tool but does not carry its input schema. */
|
|
1468
587
|
type McpServerToolInfo = {
|
|
1469
588
|
name: string;
|
|
1470
589
|
description?: string;
|
|
@@ -1473,224 +592,64 @@ type McpServerToolInfo = {
|
|
|
1473
592
|
destructive?: boolean;
|
|
1474
593
|
openWorld?: boolean;
|
|
1475
594
|
};
|
|
1476
|
-
/**
|
|
1477
|
-
* The tool's JSON Schema, where the engine reports one. **Engine-dependent,
|
|
1478
|
-
* and that is not an oversight**: the Agent SDK's `McpServerStatus` names and
|
|
1479
|
-
* describes each tool but carries no schema at all, while codex's
|
|
1480
|
-
* `mcpServerStatus/list` returns the full one. So a client renders parameters
|
|
1481
|
-
* where they exist and says they are unavailable where they don't — rather
|
|
1482
|
-
* than either leaving a silent gap or claiming the absence is universal.
|
|
1483
|
-
*
|
|
1484
|
-
* Opaque on purpose: this is a JSON Schema document, not a shape this
|
|
1485
|
-
* protocol models.
|
|
1486
|
-
*/
|
|
1487
595
|
inputSchema?: unknown;
|
|
1488
596
|
};
|
|
1489
|
-
/**
|
|
1490
|
-
* Live status of one MCP server on a session — what `GET
|
|
1491
|
-
* {basePath}/sessions/:id/mcp` answers with, and what the `/mcp` screens render.
|
|
1492
|
-
*
|
|
1493
|
-
* The connection *identity* is here (transport, command, url, scope) but never
|
|
1494
|
-
* its secrets: the engine's config carries `env` for stdio servers and `headers`
|
|
1495
|
-
* for HTTP/SSE ones, and both are dropped on the way out. A client that can read
|
|
1496
|
-
* this is not thereby entitled to the tokens the operator configured.
|
|
1497
|
-
*/
|
|
1498
597
|
type McpServerStatusInfo = {
|
|
1499
598
|
name: string;
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
scope?: string; /** Present when `status` is 'failed'. */
|
|
1504
|
-
error?: string; /** Name and version the server announced on connect. */
|
|
599
|
+
status: string;
|
|
600
|
+
scope?: string;
|
|
601
|
+
error?: string;
|
|
1505
602
|
serverInfo?: {
|
|
1506
603
|
name: string;
|
|
1507
604
|
version: string;
|
|
1508
605
|
};
|
|
1509
|
-
transport?: 'stdio' | 'http' | 'sse' | 'sdk';
|
|
606
|
+
transport?: 'stdio' | 'http' | 'sse' | 'sdk';
|
|
1510
607
|
command?: string;
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
args?: string[]; /** http/sse only. */
|
|
1514
|
-
url?: string; /** Present when connected. */
|
|
608
|
+
args?: string[];
|
|
609
|
+
url?: string;
|
|
1515
610
|
tools?: McpServerToolInfo[];
|
|
1516
611
|
};
|
|
1517
612
|
type McpServersResponse = {
|
|
1518
613
|
servers: McpServerStatusInfo[];
|
|
1519
614
|
};
|
|
1520
|
-
/** `POST {basePath}/sessions/:id/mcp/:name` — answers with the refreshed status. */
|
|
1521
615
|
type McpServerActionRequest = {
|
|
1522
616
|
action: 'reconnect' | 'enable' | 'disable';
|
|
1523
617
|
};
|
|
1524
|
-
/** `POST {basePath}/sessions/:id/attachments?name=<name>` — the body is the raw
|
|
1525
|
-
* file, the `content-type` header its media type. Answers with the reference to
|
|
1526
|
-
* name on the next `user_message`. */
|
|
1527
618
|
type UploadAttachmentResponse = {
|
|
1528
619
|
attachment: MessageAttachment;
|
|
1529
620
|
};
|
|
1530
621
|
type CreateSessionRequest = {
|
|
1531
|
-
/** Directory the session is rooted at. Required for any engine whose
|
|
1532
|
-
* capability record declares {@link EngineCapabilities.hostCwd} — `cwd` is
|
|
1533
|
-
* per-query in the SDK and the server re-pins it on every call. Omittable for
|
|
1534
|
-
* an engine that has no host filesystem at all (the provider engine, whose
|
|
1535
|
-
* tools run against the in-memory VFS): there the field would be a required
|
|
1536
|
-
* lie, and `allowedCwdRoots` would look like a sandbox boundary it is not. */
|
|
1537
622
|
cwd?: string;
|
|
1538
|
-
|
|
1539
|
-
* declares more than one profile; implicit when exactly one exists. */
|
|
1540
|
-
profile?: string; /** Optional initial prompt (may be a skill invocation like "/verify-content 123"). */
|
|
623
|
+
profile?: string;
|
|
1541
624
|
prompt?: string;
|
|
1542
625
|
permissionMode?: PermissionMode;
|
|
1543
|
-
/** Pre-authorize 'bypassPermissions' (the CLI's --dangerously-skip-permissions
|
|
1544
|
-
* capability) so the mode can be switched on mid-session. Without it the CLI
|
|
1545
|
-
* rejects `set_permission_mode: 'bypassPermissions'` on a running session.
|
|
1546
|
-
* Implied when `permissionMode` is already 'bypassPermissions'. */
|
|
1547
626
|
allowDangerouslySkipPermissions?: boolean;
|
|
1548
627
|
allowedTools?: string[];
|
|
1549
628
|
disallowedTools?: string[];
|
|
1550
629
|
mcpServers?: Record<string, McpServerConfigWire>;
|
|
1551
|
-
/** Which filesystem settings the session loads. Include 'project' to pick up the
|
|
1552
|
-
* target repo's skills and CLAUDE.md ("close-to-real" fidelity). */
|
|
1553
630
|
settingSources?: Array<'user' | 'project' | 'local'>;
|
|
1554
631
|
model?: string;
|
|
1555
632
|
maxTurns?: number;
|
|
1556
|
-
maxBudgetUsd?: number;
|
|
1557
|
-
resume?: string;
|
|
633
|
+
maxBudgetUsd?: number;
|
|
634
|
+
resume?: string;
|
|
1558
635
|
forkSession?: boolean;
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
reasoningEffort?: string; /** Emit `stream_delta` events for token-by-token rendering. Default true. */
|
|
1563
|
-
includePartialMessages?: boolean; /** Per-session override of the server's permission-request timeout (ms). */
|
|
1564
|
-
approvalTimeoutMs?: number; /** AskUserQuestion handling (see {@link QuestionBehavior}). Default 'ask'. */
|
|
636
|
+
reasoningEffort?: string;
|
|
637
|
+
includePartialMessages?: boolean;
|
|
638
|
+
approvalTimeoutMs?: number;
|
|
1565
639
|
questionBehavior?: QuestionBehavior;
|
|
1566
|
-
|
|
1567
|
-
* (see {@link ProfileSessionDefaults.capabilities}). Narrowing only — naming a
|
|
1568
|
-
* capability the profile does not grant is a 400, not a silent upgrade. */
|
|
1569
|
-
capabilities?: SessionCapability[]; /** Free-form metadata echoed back on SessionInfo (host app bookkeeping). */
|
|
640
|
+
capabilities?: SessionCapability[];
|
|
1570
641
|
meta?: Record<string, unknown>;
|
|
1571
|
-
/**
|
|
1572
|
-
* Opaque string tags naming what this session *belongs to* — the gateway's
|
|
1573
|
-
* only intra-deployment scoping primitive. Assigned at create, **immutable
|
|
1574
|
-
* afterwards** (no route writes it), echoed on {@link SessionInfo}, and
|
|
1575
|
-
* carried through parking/dormancy so a restart cannot un-scope a session.
|
|
1576
|
-
*
|
|
1577
|
-
* WorkerDeck never interprets a key: an embedder writes `{ space, user }` or
|
|
1578
|
-
* `{ tenant }` or nothing at all. What the tags *mean* is the host's
|
|
1579
|
-
* `authorizeSession` predicate; absent one, the default rule is that every
|
|
1580
|
-
* key the authenticated principal pins must match here (an unset principal
|
|
1581
|
-
* scope is unrestricted — the same "unset means all" rule `allowedProfiles`
|
|
1582
|
-
* uses, so an operator's dashboard keeps working unchanged).
|
|
1583
|
-
*
|
|
1584
|
-
* NOT `meta`: `meta` is free-form, client-settable and echoed, and an
|
|
1585
|
-
* enforcement rule whose input the caller supplies is not an enforcement
|
|
1586
|
-
* rule. Values are visible to any principal the policy admits a session to,
|
|
1587
|
-
* so use opaque ids rather than names you would not show that audience.
|
|
1588
|
-
*/
|
|
1589
642
|
scope?: Record<string, string>;
|
|
1590
643
|
};
|
|
1591
|
-
/**
|
|
1592
|
-
* One sub-agent (a `Task` call and the sidechain it spawned), as a *list* surface
|
|
1593
|
-
* sees it — without attaching.
|
|
1594
|
-
*
|
|
1595
|
-
* Sub-agent work is otherwise attach-only: it exists on the wire as
|
|
1596
|
-
* `parentToolUseId` on three event bodies, and is reconstructed into rows by the
|
|
1597
|
-
* react reducer and grouped per-Task by `terminalBlocks`. A sessions list never
|
|
1598
|
-
* attaches (one live attach per session, owned by the panel), so it reads
|
|
1599
|
-
* `SessionInfo` over REST and would otherwise have no way to know a session has
|
|
1600
|
-
* six agents running inside one turn.
|
|
1601
|
-
*
|
|
1602
|
-
* This is a **runner-owned rollup computed at read time**, exactly like
|
|
1603
|
-
* {@link SessionInfo.pendingPermissionCount}: it is not an event, it is not
|
|
1604
|
-
* persisted separately, and it therefore rides the REST list, the WS attach
|
|
1605
|
-
* snapshot and parking snapshots for free.
|
|
1606
|
-
*
|
|
1607
|
-
* **The claude and codex engines both produce it; the provider engine's absence
|
|
1608
|
-
* is the truth.** The AI SDK has no multi-agent primitive and no tool that runs
|
|
1609
|
-
* a nested agent loop, so `parentToolUseId: null` on every provider event is
|
|
1610
|
-
* honest. Codex's spawn signal is the `subAgentActivity` item, whose own `id` is
|
|
1611
|
-
* the model's `spawn_agent` call id — a genuine tool-use id — so `toolUseId`
|
|
1612
|
-
* keeps its documented meaning there: the codex runner authors the anchor
|
|
1613
|
-
* `tool_use` itself and keys every event of the agent's *thread* to it
|
|
1614
|
-
* (`engines/codex/subagents.ts`). The earlier version of this comment asserted
|
|
1615
|
-
* codex had no sidechains; that was true of the exec era and has not been true
|
|
1616
|
-
* for a while.
|
|
1617
|
-
*
|
|
1618
|
-
* It is deliberately **not** the input to `taskSummary`. That string is spelled
|
|
1619
|
-
* from the absorbed transcript items and must stay that way, so a transcript
|
|
1620
|
-
* replayed tomorrow spells the same line from the same items it holds today.
|
|
1621
|
-
*/
|
|
1622
644
|
type SubagentInfo = {
|
|
1623
|
-
|
|
1624
|
-
* nested events carry as `parentToolUseId`, and therefore the handle a client
|
|
1625
|
-
* uses to jump to that Task's row. */
|
|
1626
|
-
toolUseId: string; /** The Task input's `subagent_type` (e.g. "Explore"), when it named one. */
|
|
645
|
+
toolUseId: string;
|
|
1627
646
|
agentType?: string;
|
|
1628
|
-
/** The Task input's short `description`, clipped by the runner. Together with
|
|
1629
|
-
* `agentType` this is what makes two parallel sub-agents tell apart in a list;
|
|
1630
|
-
* a row reading only `Task` answers nothing. */
|
|
1631
647
|
description?: string;
|
|
1632
|
-
|
|
1633
|
-
* `running` until the Task's own `tool_result` arrives, then `done`/`failed`
|
|
1634
|
-
* from that result's `is_error`. A turn that ends without that result — an
|
|
1635
|
-
* interrupt, a session error, a turn or budget cap — settles what is still
|
|
1636
|
-
* running as `failed`: the report never came, which is the one thing `done`
|
|
1637
|
-
* could have claimed, and a `running` badge on an idle session would be a
|
|
1638
|
-
* lie a list re-renders at every poll.
|
|
1639
|
-
*
|
|
1640
|
-
* Deliberately **narrower than `taskFailed`** in `@workerdeck/ui`'s
|
|
1641
|
-
* `tool-run.ts`, which reddens a Task row when *any child call* failed. That is
|
|
1642
|
-
* right for a transcript row the reader can expand — the failure is one press
|
|
1643
|
-
* away and hiding it would be worse. It is wrong for a list: a grep that
|
|
1644
|
-
* matched nothing inside an otherwise successful Explore agent would put
|
|
1645
|
-
* `failed` beside the session's name with nothing to open. So this reports the
|
|
1646
|
-
* sub-agent's own outcome. If you are here to "fix" the inconsistency, this is
|
|
1647
|
-
* the reason it exists.
|
|
1648
|
-
*/
|
|
1649
|
-
status: 'running' | 'done' | 'failed'; /** Epoch ms the `Task` call was emitted. */
|
|
648
|
+
status: 'running' | 'done' | 'failed';
|
|
1650
649
|
startedAt: number;
|
|
1651
|
-
/** Tool calls the sub-agent has made so far — its progress reading while
|
|
1652
|
-
* running, counted from nested `tool_use` blocks. */
|
|
1653
650
|
toolCount: number;
|
|
1654
651
|
};
|
|
1655
|
-
/**
|
|
1656
|
-
* How many *settled* sub-agents {@link SessionInfo.subagents} keeps behind the
|
|
1657
|
-
* running ones. Small on purpose: the point of the tail is that a list row does
|
|
1658
|
-
* not go blank the instant a run finishes, not that it is a history.
|
|
1659
|
-
*/
|
|
1660
652
|
declare const SUBAGENT_HISTORY = 8;
|
|
1661
|
-
/**
|
|
1662
|
-
* A project's icon, as declared by its `.workerdeck.json` — either a named
|
|
1663
|
-
* glyph or a reference to an image the gateway serves.
|
|
1664
|
-
*
|
|
1665
|
-
* A discriminated union rather than one stringly field, because the two arms
|
|
1666
|
-
* have opposite render paths: a glyph is looked up in the client's own icon
|
|
1667
|
-
* set with no I/O, an image is a fetch. Collapsing them would put "is this a
|
|
1668
|
-
* name or an address" back on every renderer, which is the inference this
|
|
1669
|
-
* family keeps refusing (`ImageRefPart` is a new part type, never a
|
|
1670
|
-
* hollowed-out `image`, for the same reason).
|
|
1671
|
-
*
|
|
1672
|
-
* `glyph.name` is a lucide icon name, validated by the gateway for *shape*
|
|
1673
|
-
* only (lowercase kebab-case): the gateway has no lucide catalog and must not
|
|
1674
|
-
* grow one — icon sets version independently of this protocol. A client whose
|
|
1675
|
-
* set lacks the name renders its no-project fallback rather than erroring;
|
|
1676
|
-
* an unknown name is a stale row, never withheld state.
|
|
1677
|
-
*
|
|
1678
|
-
* `image` carries an **address, never bytes** — the attachment-bytes rule.
|
|
1679
|
-
* `SessionInfo` rides every row of `GET /sessions`, which clients poll at 1.2s
|
|
1680
|
-
* while anything is working, so an inlined base64 icon would be paid for on
|
|
1681
|
-
* every poll of every session forever (the same argument that keeps
|
|
1682
|
-
* `originalFile` off {@link FilePatch} and message bytes off events). The
|
|
1683
|
-
* bytes come from `GET {basePath}/sessions/:id/project/icon` — session-scoped
|
|
1684
|
-
* on purpose, so the fetch rides the same `canSee` gate as every other
|
|
1685
|
-
* `/sessions/:id/*` route and a scoped principal's miss is the uniform 404. A
|
|
1686
|
-
* project-keyed route would need the project root in the URL, and a route
|
|
1687
|
-
* addressed by host paths is an existence oracle for the gateway's
|
|
1688
|
-
* filesystem. `hash` (sha256 hex of the bytes) is the cross-session cache
|
|
1689
|
-
* key: two sessions in one project serve identical bytes, so a client caches
|
|
1690
|
-
* by hash rather than by URL and fetches once per project, not once per
|
|
1691
|
-
* session. The route answers with `ETag: "<hash>"` and honors
|
|
1692
|
-
* `If-None-Match`.
|
|
1693
|
-
*/
|
|
1694
653
|
type ProjectIcon = {
|
|
1695
654
|
type: 'glyph';
|
|
1696
655
|
name: string;
|
|
@@ -1699,381 +658,74 @@ type ProjectIcon = {
|
|
|
1699
658
|
mediaType: 'image/png' | 'image/svg+xml';
|
|
1700
659
|
hash: string;
|
|
1701
660
|
};
|
|
1702
|
-
/**
|
|
1703
|
-
* Project identity for a session — what a `.workerdeck.json` in the session's
|
|
1704
|
-
* ancestry declares, resolved by the **gateway** and shipped on
|
|
1705
|
-
* {@link SessionInfo.project}.
|
|
1706
|
-
*
|
|
1707
|
-
* The gateway reads the file, not each client: the iOS app and a browser
|
|
1708
|
-
* pointed at a remote gateway have no access to that filesystem, so a
|
|
1709
|
-
* per-client reader would make the feature exist on exactly one client.
|
|
1710
|
-
* Discovery is an ancestor walk from the session's `cwd` upward — nearest
|
|
1711
|
-
* `.workerdeck.json` wins, so a session started in `packages/ui` still says
|
|
1712
|
-
* "WorkerDeck" — over the *realpath'd* cwd, which is what makes `root`
|
|
1713
|
-
* canonical below.
|
|
1714
|
-
*
|
|
1715
|
-
* The file's schema, stated here because this type is its wire projection
|
|
1716
|
-
* (clients never read the file; the gateway is its only parser):
|
|
1717
|
-
*
|
|
1718
|
-
* ```json
|
|
1719
|
-
* { "name": "WorkerDeck", "icon": "layers" }
|
|
1720
|
-
* { "name": "WorkerDeck", "icon": "./docs/assets/icon.png" }
|
|
1721
|
-
* ```
|
|
1722
|
-
*
|
|
1723
|
-
* Both keys optional, unknown keys ignored (forward compatibility). An empty
|
|
1724
|
-
* `{}` still marks its directory as the project root — grouping is the point,
|
|
1725
|
-
* and the name falls back to the root's basename. `icon` is one string with a
|
|
1726
|
-
* total classification rule: a value ending in `.png`/`.svg`
|
|
1727
|
-
* (case-insensitive) is a repo-relative image path — relative only, since the
|
|
1728
|
-
* file is checked into a repo that clones onto other machines, where an
|
|
1729
|
-
* absolute path is wrong by construction — and anything else must be a
|
|
1730
|
-
* lucide-shaped glyph name (`^[a-z0-9]+(-[a-z0-9]+)*$`) or it is ignored. The
|
|
1731
|
-
* two shapes cannot collide (a glyph name contains no dot), so the rule is a
|
|
1732
|
-
* classification, not a guess. Every degradation degrades *fieldwise and
|
|
1733
|
-
* silently*: a malformed or oversized file is skipped and the walk continues
|
|
1734
|
-
* to an ancestor (a broken nested file must not shadow the repo root's valid
|
|
1735
|
-
* one), a junk name falls back to the basename, a junk or escaping icon is
|
|
1736
|
-
* dropped — a session must never fail, or even warn, because of a display
|
|
1737
|
-
* declaration.
|
|
1738
|
-
*
|
|
1739
|
-
* `root` — the canonical (realpath'd) absolute directory holding the file, on
|
|
1740
|
-
* the **gateway's** filesystem — is the grouping key: two sessions are in the
|
|
1741
|
-
* same project iff same root *on the same gateway* (a remote gateway's
|
|
1742
|
-
* identical-looking path is another machine's directory — the `ScopeRoot`
|
|
1743
|
-
* argument). A *name* is not a key: two repos can both be called "api".
|
|
1744
|
-
* Canonicalizing at discovery is what makes two differently-spelled cwds of
|
|
1745
|
-
* one project agree on it.
|
|
1746
|
-
*
|
|
1747
|
-
* Resolved at **serve time** from a TTL cache, never persisted — the same
|
|
1748
|
-
* placement argument as the profile tracker's 0%-after-reset inference: it is
|
|
1749
|
-
* a function of the gateway's current filesystem, and a copy captured into a
|
|
1750
|
-
* parking record would replay a stale name forever. Editing the file shows up
|
|
1751
|
-
* on every session in the project within the TTL, with no migration and no
|
|
1752
|
-
* event.
|
|
1753
|
-
*
|
|
1754
|
-
* Additive at protocol **7**: an optional field on `SessionInfo`, where
|
|
1755
|
-
* absent means exactly what today's wire means (no project declared — render
|
|
1756
|
-
* the folder basename), an old client ignores it, and a new client against an
|
|
1757
|
-
* old gateway sees absent. The icon route is likewise unreachable by
|
|
1758
|
-
* accident: a client only fetches it when this gateway told it an image
|
|
1759
|
-
* exists.
|
|
1760
|
-
*/
|
|
1761
661
|
type ProjectInfo = {
|
|
1762
|
-
|
|
1763
|
-
/** Canonical absolute path of the directory holding `.workerdeck.json`, on
|
|
1764
|
-
* the gateway's filesystem. The grouping key (per gateway); an opaque string
|
|
1765
|
-
* to clients beyond equality and display. */
|
|
662
|
+
name: string;
|
|
1766
663
|
root: string;
|
|
1767
|
-
/** Absent = the file declared none (or declared one the gateway refused —
|
|
1768
|
-
* malformed, escaping, oversized — which a client cannot and must not
|
|
1769
|
-
* distinguish). */
|
|
1770
664
|
icon?: ProjectIcon;
|
|
1771
665
|
};
|
|
1772
666
|
type SessionInfo = {
|
|
1773
|
-
|
|
667
|
+
id: string;
|
|
1774
668
|
sdkSessionId?: string;
|
|
1775
669
|
status: SessionStatus;
|
|
1776
|
-
|
|
1777
|
-
* {@link CreateSessionRequest.cwd}). Deliberately not optional: every client
|
|
1778
|
-
* renders and searches it, and a synthetic path would send the workspace and
|
|
1779
|
-
* `@file` search probing a directory that does not exist. */
|
|
1780
|
-
cwd: string; /** Profile the session runs under (resolved name, present even when implicit). */
|
|
670
|
+
cwd: string;
|
|
1781
671
|
profile?: string;
|
|
1782
|
-
/** Engine actually running this session, reported by the runner itself. Lets a
|
|
1783
|
-
* session surface gate CLI-only affordances (permission modes, context usage,
|
|
1784
|
-
* rate limits) without looking the profile back up. Absent = 'claude'. */
|
|
1785
672
|
engine?: ProfileEngine;
|
|
1786
|
-
/** The engine's capability record, reported by the runner like `engine`. The
|
|
1787
|
-
* attach snapshot is the session-level source (no event carries it). Absent =
|
|
1788
|
-
* ENGINE_CAPABILITIES[engine]. */
|
|
1789
673
|
capabilities?: EngineCapabilities;
|
|
1790
674
|
model?: string;
|
|
1791
675
|
permissionMode?: PermissionMode;
|
|
1792
|
-
|
|
1793
|
-
* allows it when the process was spawned for it, so it is decided at creation
|
|
1794
|
-
* and never changes: a session that did not ask for bypass up front cannot
|
|
1795
|
-
* gain it later. Lets a picker disable the mode instead of offering a switch
|
|
1796
|
-
* the engine will refuse. Absent = unknown (an older server). */
|
|
1797
|
-
canBypassPermissions?: boolean; /** See the `system_init` event; 'oauth' = claude.ai subscription credentials. */
|
|
676
|
+
canBypassPermissions?: boolean;
|
|
1798
677
|
apiKeySource?: string;
|
|
1799
|
-
createdAt: number;
|
|
678
|
+
createdAt: number;
|
|
1800
679
|
lastSeq: number;
|
|
1801
680
|
pendingPermissionCount: number;
|
|
1802
|
-
/**
|
|
1803
|
-
* Sub-agents this session has running, plus a short tail of settled ones — see
|
|
1804
|
-
* {@link SubagentInfo}. Absent on an engine that has no sidechains and on an
|
|
1805
|
-
* older server; **absent and empty mean the same thing to a client**, so render
|
|
1806
|
-
* nothing rather than "0 sub-agents".
|
|
1807
|
-
*
|
|
1808
|
-
* Bounded on purpose. This rides every row of `GET /sessions`, which a busy
|
|
1809
|
-
* client polls at 1.2s, and it is captured into parking snapshots — the same
|
|
1810
|
-
* attachment-bytes rule that keeps whole files off {@link FilePatch}. Every
|
|
1811
|
-
* *running* sub-agent is always present (they are the live reading and there
|
|
1812
|
-
* are never many at once); settled ones are kept newest-first to
|
|
1813
|
-
* {@link SUBAGENT_HISTORY} and then dropped, so a day-long session with two
|
|
1814
|
-
* hundred Tasks does not grow an unbounded field. A client must therefore not
|
|
1815
|
-
* treat this as the session's full Task history — the transcript is that.
|
|
1816
|
-
*/
|
|
1817
681
|
subagents?: SubagentInfo[];
|
|
1818
|
-
meta?: Record<string, unknown>;
|
|
1819
|
-
title?: string;
|
|
1820
|
-
totalCostUsd?: number;
|
|
682
|
+
meta?: Record<string, unknown>;
|
|
683
|
+
title?: string;
|
|
684
|
+
totalCostUsd?: number;
|
|
1821
685
|
numTurns?: number;
|
|
686
|
+
activityCount?: number;
|
|
1822
687
|
/**
|
|
1823
|
-
*
|
|
1824
|
-
*
|
|
1825
|
-
*
|
|
1826
|
-
*
|
|
1827
|
-
*
|
|
1828
|
-
* `numTurns` cannot answer it: five tool calls inside one turn are one turn.
|
|
1829
|
-
* `lastSeq` cannot either — it counts every event, and with token streaming on
|
|
1830
|
-
* that is hundreds per reply. Absent on an older server; a client should fall
|
|
1831
|
-
* back to `numTurns` rather than showing nothing.
|
|
1832
|
-
*
|
|
1833
|
-
* Monotonic for the session's whole life, **including across a
|
|
1834
|
-
* `conversation_reset`**: after a `/clear` this deliberately exceeds the
|
|
1835
|
-
* number of rows a fresh attach renders. It is an unread *cursor* diffed
|
|
1836
|
-
* against stored monotonic watermarks (see `watermarks.ts`) — resetting it to
|
|
1837
|
-
* the new row count would leave every stored mark above it, and that
|
|
1838
|
-
* session's badge dead until the count caught back up.
|
|
688
|
+
* Rows of the kind a person is actually waiting to read — see `transcriptProse`.
|
|
689
|
+
* Absent from a gateway that predates it — additive, so no `PROTOCOL_VERSION` bump —
|
|
690
|
+
* which is why every reader falls back to `activityCount`. This is the badge's number; `activityCount` stays the "has
|
|
691
|
+
* anything happened at all" measure that sorting and dormancy read.
|
|
1839
692
|
*/
|
|
1840
|
-
|
|
693
|
+
proseCount?: number;
|
|
1841
694
|
lastActivityAt?: number;
|
|
1842
|
-
/**
|
|
1843
|
-
* The session's latest context-window reading — see {@link ContextReading}.
|
|
1844
|
-
*
|
|
1845
|
-
* The same number the session screen draws, served on the list so a row can
|
|
1846
|
-
* show where a session is bloating **without attaching to it**. That is the
|
|
1847
|
-
* whole reason it is here: context fill is the one session metric you want
|
|
1848
|
-
* across *all* sessions at once, and until now it existed only as an event on
|
|
1849
|
-
* an attached socket.
|
|
1850
|
-
*
|
|
1851
|
-
* Retained by the runner from the last `context_usage` it emitted, so it is
|
|
1852
|
-
* exactly what the transcript last showed — never recomputed on the serve
|
|
1853
|
-
* path, which would be a second answer to a question that already has one.
|
|
1854
|
-
* Absent until the first reading (a promptless session has none), and cleared
|
|
1855
|
-
* by a `conversation_reset` for the same reason the transcript state clears
|
|
1856
|
-
* it: the old window is not this conversation's.
|
|
1857
|
-
*/
|
|
1858
695
|
contextUsage?: ContextReading;
|
|
1859
|
-
/** Opaque scope tags this session was created with — see
|
|
1860
|
-
* {@link CreateSessionRequest.scope}. Echoed by the runner, re-stamped by the
|
|
1861
|
-
* gateway, and never editable. */
|
|
1862
696
|
scope?: Record<string, string>;
|
|
1863
|
-
/**
|
|
1864
|
-
* Project identity discovered from the session's `cwd` — see
|
|
1865
|
-
* {@link ProjectInfo}. Stamped by the **gateway at serve time** (runners
|
|
1866
|
-
* never set it; a runner-echoed value would be persisted into parking
|
|
1867
|
-
* records and replay a stale name forever). Absent = no `.workerdeck.json`
|
|
1868
|
-
* in the cwd's ancestry, and also = an older gateway: both mean "render the
|
|
1869
|
-
* folder basename", which is exactly today's behaviour.
|
|
1870
|
-
*/
|
|
1871
697
|
project?: ProjectInfo;
|
|
1872
698
|
};
|
|
1873
|
-
/**
|
|
1874
|
-
* The list-sized context reading an event carries, or `undefined` for the events
|
|
1875
|
-
* that carry none — the rule behind {@link SessionInfo.contextUsage}.
|
|
1876
|
-
*
|
|
1877
|
-
* Here rather than in each runner for the same reason {@link transcriptActivity}
|
|
1878
|
-
* is: it is one rule both sides have to agree on, and three copies of "which
|
|
1879
|
-
* events move the reading" is three chances to disagree. Runners fold it in
|
|
1880
|
-
* their emit path; **clearing on `conversation_reset` is the caller's half** —
|
|
1881
|
-
* this function answers "what does this event say the reading is", and a reset
|
|
1882
|
-
* says nothing about the window, it retires the conversation the window
|
|
1883
|
-
* described.
|
|
1884
|
-
*/
|
|
1885
699
|
declare function contextReading(body: SessionEventBody): ContextReading | undefined;
|
|
1886
|
-
/**
|
|
1887
|
-
* How many transcript rows an event materializes — the unit behind
|
|
1888
|
-
* {@link SessionInfo.activityCount}.
|
|
1889
|
-
*
|
|
1890
|
-
* Deliberately the *reducer's* rule (`@workerdeck/react`'s `transcript.ts`), not
|
|
1891
|
-
* a server-side approximation: one row per content block of an assistant
|
|
1892
|
-
* message (a text, a thought, each tool call), one for a user message, one per
|
|
1893
|
-
* turn result, delivered file or error. Everything else — status changes, usage
|
|
1894
|
-
* readings, stream deltas, permission bookkeeping — is state, not a row, and
|
|
1895
|
-
* counts zero.
|
|
1896
|
-
*
|
|
1897
|
-
* It lives in `protocol` because both sides need it and neither may import the
|
|
1898
|
-
* other: the runners count with it, and any client compares the totals. If the
|
|
1899
|
-
* reducer's row rule changes, change this with it.
|
|
1900
|
-
*/
|
|
1901
700
|
declare function transcriptActivity(body: SessionEventBody): number;
|
|
1902
701
|
/**
|
|
1903
|
-
*
|
|
1904
|
-
*
|
|
1905
|
-
* `
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
1908
|
-
*
|
|
1909
|
-
*
|
|
1910
|
-
* `
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
1913
|
-
*
|
|
1914
|
-
*
|
|
1915
|
-
*
|
|
1916
|
-
*
|
|
1917
|
-
*
|
|
1918
|
-
* `
|
|
1919
|
-
*
|
|
1920
|
-
*
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
*
|
|
1924
|
-
* Lives here beside {@link transcriptActivity} for the same reason: the
|
|
1925
|
-
* reducer owns the rule and the runners filter with it, and the two sides may
|
|
1926
|
-
* not import each other. If the reducer's items-mutating set changes, change
|
|
1927
|
-
* this with it. Unknown/future event types are NOT content — the safe failure
|
|
1928
|
-
* is replaying a stale row, never withholding state.
|
|
1929
|
-
*/
|
|
702
|
+
* The unread badge's unit: output **addressed to the human**, not evidence of work.
|
|
703
|
+
*
|
|
704
|
+
* `transcriptActivity` counts a tool call and a paragraph alike, which is honest as
|
|
705
|
+
* "how much has happened" and wrong as "how much is there to read" — a session that
|
|
706
|
+
* tool-loops for a minute ticks 6, 7, 8 with nothing said yet. This scores the same
|
|
707
|
+
* events through a narrower door:
|
|
708
|
+
*
|
|
709
|
+
* - assistant `text` blocks only — `thinking` is not addressed to anyone and `tool_use`
|
|
710
|
+
* is the noise being filtered out;
|
|
711
|
+
* - the **sub-agent carve-out is inherited** (`parentToolUseId != null` scores 0): prose a
|
|
712
|
+
* sub-agent wrote to its parent is not addressed to the human either;
|
|
713
|
+
* - a `turn_result` counts only when it **failed**, an interrupt or an error being a thing
|
|
714
|
+
* the human is owed; a successful turn already carried its own prose and would otherwise
|
|
715
|
+
* double-count every answer;
|
|
716
|
+
* - `session_error` and `file_delivered` count — both are output, not work;
|
|
717
|
+
* - `stream_delta` scores 0, exactly as in `transcriptActivity`. The badge is therefore
|
|
718
|
+
* correct within one poll of a message *completing*, never mid-stream, which is the
|
|
719
|
+
* deliberate price of leaving the streaming path alone.
|
|
720
|
+
*/
|
|
721
|
+
declare function transcriptProse(body: SessionEventBody): number;
|
|
1930
722
|
declare function transcriptContent(body: SessionEventBody): boolean;
|
|
1931
|
-
/**
|
|
1932
|
-
* The dedupe key for an event that is **last-write-wins** on replay, or
|
|
1933
|
-
* `undefined` for one that must always be delivered.
|
|
1934
|
-
*
|
|
1935
|
-
* The problem: the runner polls context usage and the plan's rate limits after
|
|
1936
|
-
* every turn, so a fifty-turn session's log holds fifty context readings and
|
|
1937
|
-
* fifty per rate-limit window. Replaying all of them is not merely wasteful —
|
|
1938
|
-
* it is *visible*. A client applies each in turn, so opening a session shows
|
|
1939
|
-
* the usage meters counting up from the session's first reading to its last
|
|
1940
|
-
* over the length of the replay, announcing history as if it were news.
|
|
1941
|
-
*
|
|
1942
|
-
* The fix is a backwards scan over the buffered log keeping the first
|
|
1943
|
-
* occurrence of each key, which is `staleReplaySeqs` in `@workerdeck/core`.
|
|
1944
|
-
* The key is per *window* for rate limits, not per event type: the reducer
|
|
1945
|
-
* stores them keyed by window ("so five_hour and seven_day updates don't
|
|
1946
|
-
* clobber each other"), so a single key would keep only the most recently
|
|
1947
|
-
* polled window and silently drop the others.
|
|
1948
|
-
*
|
|
1949
|
-
* **This is a claim about the reducer**, which is why it lives here rather
|
|
1950
|
-
* than in core: only the server coalesces, but only `@workerdeck/react` can
|
|
1951
|
-
* prove the rule correct, and neither package may import the other. The
|
|
1952
|
-
* property that must hold is that coalescing is *unobservable* — folding the
|
|
1953
|
-
* full log and the coalesced log through `applyEvent` yields identical state.
|
|
1954
|
-
* `packages/react/test/replay-coalesce.test.ts` asserts exactly that, over
|
|
1955
|
-
* every event kind. Extend the rule only with a case that test still passes.
|
|
1956
|
-
*
|
|
1957
|
-
* Three kinds are deliberately **excluded** despite looking eligible:
|
|
1958
|
-
*
|
|
1959
|
-
* - `capabilities` — `defaultModel: event.defaultModel ?? base.defaultModel`
|
|
1960
|
-
* is a fallback *merge*, so a later event without one would erase an earlier
|
|
1961
|
-
* event's. (It is also emitted once per session, so there is nothing to win.)
|
|
1962
|
-
* - `model_changed` — `undefined` means "reset to the server default" and the
|
|
1963
|
-
* reducer *keeps* the last known model, so the last event alone is not the
|
|
1964
|
-
* same as the fold.
|
|
1965
|
-
* - `system_init` — pure replace for the reducer, but the server's
|
|
1966
|
-
* `watchAuthSource` reads the **first** one to decide an auth policy, and
|
|
1967
|
-
* parking treats each as a resume point.
|
|
1968
|
-
*
|
|
1969
|
-
* Coalescing never drops the highest-seq event, and that is load-bearing
|
|
1970
|
-
* rather than incidental: the globally-last event is by definition the last of
|
|
1971
|
-
* its own key, so it always survives. `useClaudeSession`'s replay hold waits
|
|
1972
|
-
* for `state.lastSeq` to reach the attach's `session.lastSeq`, and would hang
|
|
1973
|
-
* on a blank panel forever if a coalescer could swallow the final event.
|
|
1974
|
-
*/
|
|
1975
723
|
declare function replayCoalesceKey(body: SessionEventBody): string | undefined;
|
|
1976
|
-
/**
|
|
1977
|
-
* Does a **replay** have to deliver this event, or may it be dropped outright?
|
|
1978
|
-
*
|
|
1979
|
-
* The fifth of the family, and the closest relative of {@link snapshotRetains} —
|
|
1980
|
-
* the same claim ("no client can tell") pointed at the wire instead of at a
|
|
1981
|
-
* store. The difference from {@link replayCoalesceKey} is that this is not
|
|
1982
|
-
* last-write-wins: there is nothing to keep. These are events the reducer reads
|
|
1983
|
-
* and *discards*, so a replay that sends them is spending the reader's network
|
|
1984
|
-
* on frames whose whole effect is `return base`.
|
|
1985
|
-
*
|
|
1986
|
-
* Today that is exactly one thing, and it is the second-largest item in a real
|
|
1987
|
-
* attach: the `stream_delta`s the reducer does not model. Measured over one
|
|
1988
|
-
* 1,270-row session, the delta run was 774 KB, and **~85% of it was frames the
|
|
1989
|
-
* reducer throws away** — `input_json_delta` (a tool call's arguments, streamed
|
|
1990
|
-
* character by character, 383 KB), `signature_delta` (encrypted-thinking
|
|
1991
|
-
* signatures, 153 KB) and the `message_start`/`content_block_start`/`_stop`
|
|
1992
|
-
* scaffolding (244 KB). The reducer models two delta kinds, `text_delta` and
|
|
1993
|
-
* `thinking_delta`; everything else falls through its switch untouched.
|
|
1994
|
-
*
|
|
1995
|
-
* What is deliberately **not** dropped, though the arithmetic would allow it:
|
|
1996
|
-
*
|
|
1997
|
-
* - `thinking_delta` — the Claude SDK delivers thinking blocks whose `thinking`
|
|
1998
|
-
* is `''`, and the reducer backfills them from the accumulated streamed text
|
|
1999
|
-
* (`streamedThinking`). Dropping these erases every thought from a replayed
|
|
2000
|
-
* transcript. This is the same carve-out `snapshotRetains` documents, and it
|
|
2001
|
-
* is the reason that rule is provider-engine-only.
|
|
2002
|
-
* - `text_delta` — superseded by the `assistant_message` that follows it, which
|
|
2003
|
-
* filters the streaming id and rebuilds from the full content blocks. It could
|
|
2004
|
-
* go, but only with a lookahead proving the message arrived, and at 24 KB in
|
|
2005
|
-
* the measured session it is not worth a rule that has to be right about
|
|
2006
|
-
* supersession. A merge is likewise not worth it: a *drop* needs no synthesized
|
|
2007
|
-
* event and therefore no invented seq.
|
|
2008
|
-
*
|
|
2009
|
-
* A live event is never affected — this is about the buffered replay alone — and
|
|
2010
|
-
* the caller must never drop the log's highest-seq event whatever this says, for
|
|
2011
|
-
* the reason {@link replayCoalesceKey} gives: the replay hold waits for
|
|
2012
|
-
* `state.lastSeq` to reach the attach's `session.lastSeq` and would hang on a
|
|
2013
|
-
* blank panel forever.
|
|
2014
|
-
*
|
|
2015
|
-
* The property is the family's usual one and is a test rather than an argument:
|
|
2016
|
-
* folding the full log and the retained log through `applyEvent` yields
|
|
2017
|
-
* identical state (`packages/react/test/replay-retain.test.ts`).
|
|
2018
|
-
*/
|
|
2019
724
|
declare function replayRetains(body: SessionEventBody): boolean;
|
|
2020
|
-
/**
|
|
2021
|
-
* Does a `RunnerSnapshot` keep this event in its persisted log?
|
|
2022
|
-
*
|
|
2023
|
-
* The fourth of the same family, and the same shape of claim as
|
|
2024
|
-
* {@link replayCoalesceKey}: which events a *store* may drop without any client
|
|
2025
|
-
* being able to tell. It exists because a snapshot embeds the whole event log,
|
|
2026
|
-
* and a log is mostly stream deltas — a four-character token rides a ~180-byte
|
|
2027
|
-
* JSON envelope, so the delta run is tens of times the size of the text it
|
|
2028
|
-
* spells, sitting on disk *beside* the `assistant_message` that respells it in
|
|
2029
|
-
* full. That was affordable while a snapshot was written once, at a park. It is
|
|
2030
|
-
* not affordable written after every turn, which is what restart-survival needs.
|
|
2031
|
-
*
|
|
2032
|
-
* So: everything is retained except `stream_delta`. The reason that is safe is
|
|
2033
|
-
* not that deltas are unimportant but that they are **superseded by
|
|
2034
|
-
* construction**. The reducer upserts them under one constant id and the
|
|
2035
|
-
* following `assistant_message` filters exactly that id out and rebuilds from
|
|
2036
|
-
* the full content blocks — and a snapshot may only be taken at a rest point,
|
|
2037
|
-
* where the stream loop has exited and flushed. Both exits flush, including the
|
|
2038
|
-
* error path: an interrupted turn pushes its half-finished buffers into a
|
|
2039
|
-
* durable `assistant_message` before it emits the failed `turn_result`. There is
|
|
2040
|
-
* no rest state in which a delta is the only record of anything.
|
|
2041
|
-
*
|
|
2042
|
-
* **Provider engine only**, and this is the carve-out that must not be lost:
|
|
2043
|
-
* against a *Claude* log the rule would be wrong. The Claude SDK delivers
|
|
2044
|
-
* thinking blocks whose text is `''`, with the human-readable summary existing
|
|
2045
|
-
* only in the delta stream, and the reducer carries the streamed text over to
|
|
2046
|
-
* fill them (`transcript.ts`, the `streamedThinking` backfill). Dropping deltas
|
|
2047
|
-
* there would silently erase every thought from a restored transcript. Today
|
|
2048
|
-
* that is unreachable rather than merely avoided — only the provider engine
|
|
2049
|
-
* implements `park()`/`snapshot()` at all, and `#restore` refuses a snapshot
|
|
2050
|
-
* from another engine — but an engine that gains one inherits this obligation.
|
|
2051
|
-
*
|
|
2052
|
-
* Two properties hold it up, both of which are tests rather than arguments:
|
|
2053
|
-
* folding the full log and the retained log through `applyEvent` yields
|
|
2054
|
-
* identical state (`packages/react/test/snapshot-retain.test.ts`, the same
|
|
2055
|
-
* property `replay-coalesce.test.ts` asserts), and the retained log's last event
|
|
2056
|
-
* still carries the snapshot's own `seq`. The second matters more than it looks:
|
|
2057
|
-
* `transcriptActivity(stream_delta)` is 0, so the count `#restore` recomputes
|
|
2058
|
-
* from the log is bit-identical — a client's unread cursor cannot move — and the
|
|
2059
|
-
* replay hold waits for `state.lastSeq` to reach the attach's `lastSeq`, which a
|
|
2060
|
-
* rule that could drop the final event would hang forever.
|
|
2061
|
-
*/
|
|
2062
725
|
declare function snapshotRetains(body: SessionEventBody): boolean;
|
|
2063
|
-
/**
|
|
2064
|
-
* A session in an engine's on-disk store (independent of this server's registry):
|
|
2065
|
-
* the Agent SDK's session files, or a codex profile's CODEX_HOME threads. Listed
|
|
2066
|
-
* so hosts can offer "resume" across server restarts: feed `sessionId` to
|
|
2067
|
-
* CreateSessionRequest.resume — under a profile of the SAME engine, since the id
|
|
2068
|
-
* only means something to the store it came from. `GET {basePath}/sdk-sessions`
|
|
2069
|
-
* takes an optional `profile` query parameter naming whose store to list; absent,
|
|
2070
|
-
* the profile is resolved implicitly when the server declares exactly one, else
|
|
2071
|
-
* the Claude engine's store is listed (the pre-engine-aware behavior). Mirrors
|
|
2072
|
-
* the SDK's SDKSessionInfo shape, kept browser-safe.
|
|
2073
|
-
*/
|
|
2074
726
|
type SdkSessionSummary = {
|
|
2075
|
-
sessionId: string;
|
|
2076
|
-
summary: string;
|
|
727
|
+
sessionId: string;
|
|
728
|
+
summary: string;
|
|
2077
729
|
lastModified: number;
|
|
2078
730
|
createdAt?: number;
|
|
2079
731
|
customTitle?: string;
|
|
@@ -2081,14 +733,10 @@ type SdkSessionSummary = {
|
|
|
2081
733
|
gitBranch?: string;
|
|
2082
734
|
cwd?: string;
|
|
2083
735
|
};
|
|
2084
|
-
/** One deliverable in the session's scratch filesystem (see the `file_delivered` event). */
|
|
2085
736
|
type SessionFileInfo = {
|
|
2086
737
|
path: string;
|
|
2087
738
|
bytes: number;
|
|
2088
739
|
};
|
|
2089
|
-
/** `GET {basePath}/sessions/:id/files` — every file currently in the session's VFS.
|
|
2090
|
-
* `GET {basePath}/sessions/:id/files/<path>` downloads one (attachment disposition).
|
|
2091
|
-
* 404 when the session's engine exposes no VFS (Claude-engine sessions). */
|
|
2092
740
|
type ListSessionFilesResponse = {
|
|
2093
741
|
files: SessionFileInfo[];
|
|
2094
742
|
};
|
|
@@ -2101,26 +749,12 @@ type CreateSessionResponse = {
|
|
|
2101
749
|
type GetSessionResponse = {
|
|
2102
750
|
session: SessionInfo;
|
|
2103
751
|
};
|
|
2104
|
-
/**
|
|
2105
|
-
* Body of `PATCH {basePath}/sessions/:id` — the host-facing edits to a live
|
|
2106
|
-
* session. Today that is only its display name: `title` writes `meta.title`,
|
|
2107
|
-
* which {@link SessionInfo.title} prefers over the derived one, and `null` (or
|
|
2108
|
-
* an empty string) clears the override so the derived title comes back. Nothing
|
|
2109
|
-
* here reaches the engine — renaming does not speak to the model.
|
|
2110
|
-
*
|
|
2111
|
-
* 409 when the session is parked: a parked session has no runner to carry the
|
|
2112
|
-
* change, and its snapshot is the host's to rewrite, not this route's.
|
|
2113
|
-
*/
|
|
2114
752
|
type UpdateSessionRequest = {
|
|
2115
753
|
title?: string | null;
|
|
2116
754
|
};
|
|
2117
755
|
type UpdateSessionResponse = {
|
|
2118
756
|
session: SessionInfo;
|
|
2119
757
|
};
|
|
2120
|
-
/** Body of `POST {basePath}/sessions/:id/permissions/:requestId` — the REST counterpart
|
|
2121
|
-
* of the WS `permission_decision` command, for remote controllers without a socket
|
|
2122
|
-
* (e.g. answering a job's AskUserQuestion from a webhook consumer). 404 = the request
|
|
2123
|
-
* is unknown, already resolved, or expired. */
|
|
2124
758
|
type ResolvePermissionRequest = {
|
|
2125
759
|
behavior: 'allow';
|
|
2126
760
|
updatedInput?: Record<string, unknown>;
|
|
@@ -2132,17 +766,6 @@ type ResolvePermissionRequest = {
|
|
|
2132
766
|
type ResolvePermissionResponse = {
|
|
2133
767
|
resolved: true;
|
|
2134
768
|
};
|
|
2135
|
-
/**
|
|
2136
|
-
* Body of `POST {basePath}/executions/:executionId/result` — the way a deferred
|
|
2137
|
-
* executor (a remote worker, a batch job, a human) delivers the outcome of an
|
|
2138
|
-
* execution the session parked on. The session is rehydrated if its runner was
|
|
2139
|
-
* torn down, and the result is folded back into the agent loop; a `failed` result
|
|
2140
|
-
* is ordinary tool output the agent adapts to, not a session error.
|
|
2141
|
-
*
|
|
2142
|
-
* Applied **idempotently by `executionId`**: a duplicate or late delivery (one
|
|
2143
|
-
* racing the execution watchdog) answers 200 with `applied: false` rather than
|
|
2144
|
-
* erroring or applying twice. 404 means no session is parked on that id.
|
|
2145
|
-
*/
|
|
2146
769
|
type SubmitExecutionResultRequest = {
|
|
2147
770
|
status: 'ok';
|
|
2148
771
|
output: ToolExecutionOutput;
|
|
@@ -2154,129 +777,70 @@ type SubmitExecutionResultRequest = {
|
|
|
2154
777
|
logs?: string[];
|
|
2155
778
|
};
|
|
2156
779
|
type SubmitExecutionResultResponse = {
|
|
2157
|
-
|
|
780
|
+
applied: boolean;
|
|
2158
781
|
sessionId: string;
|
|
2159
782
|
};
|
|
2160
783
|
type ListSdkSessionsResponse = {
|
|
2161
784
|
sdkSessions: SdkSessionSummary[];
|
|
2162
785
|
};
|
|
2163
|
-
/** `GET {basePath}/profiles` — filtered to the profiles the caller may use. */
|
|
2164
786
|
type ListProfilesResponse = {
|
|
2165
787
|
profiles: ProfileInfo[];
|
|
2166
|
-
/** Whether this caller may create profiles here — true only when the server has
|
|
2167
|
-
* a profile store AND the principal carries `canManageProfiles`. Lets a UI hide
|
|
2168
|
-
* controls that would always be refused. */
|
|
2169
788
|
canManage?: boolean;
|
|
2170
789
|
};
|
|
2171
|
-
/**
|
|
2172
|
-
* `POST {basePath}/profiles` — create a managed profile. Available only when the
|
|
2173
|
-
* server was given a profile store, and only to a principal with
|
|
2174
|
-
* `canManageProfiles`. Profiles declared in server options are code, not data:
|
|
2175
|
-
* they cannot be created, edited, or deleted through these routes.
|
|
2176
|
-
*/
|
|
2177
790
|
type CreateProfileRequest = ProfileInfo;
|
|
2178
|
-
/** `PATCH {basePath}/profiles/:name` — merge into a managed profile. The name is
|
|
2179
|
-
* the route, not the body; pass `null` to clear an optional field. */
|
|
2180
791
|
type UpdateProfileRequest = Omit<Partial<ProfileInfo>, 'name'>;
|
|
2181
|
-
/**
|
|
2182
|
-
* The **host's real project tree**, not a session's in-memory VFS — the two are
|
|
2183
|
-
* unrelated despite both being "files". {@link SessionFileInfo} is a deliverable
|
|
2184
|
-
* the agent produced inside a session; these routes read and write the operator's
|
|
2185
|
-
* actual disk.
|
|
2186
|
-
*
|
|
2187
|
-
* That makes them **operator-privileged**: they are authorized by the server's auth
|
|
2188
|
-
* key alone and deliberately sit outside the agent permission flow, because the
|
|
2189
|
-
* caller *is* the operator, not the model. A client holding the key can already
|
|
2190
|
-
* start a session with any allowed cwd; browsing that same tree grants it nothing
|
|
2191
|
-
* new. Writing does, which is why writes are separately enabled server-side.
|
|
2192
|
-
*
|
|
2193
|
-
* The whole surface is opt-in and root-scoped: with no roots configured every route
|
|
2194
|
-
* below 404s. There is no "unset means anything" default here — a phone on a tailnet
|
|
2195
|
-
* must never be one request away from `~/.ssh`.
|
|
2196
|
-
*/
|
|
2197
792
|
type HostFileRoot = {
|
|
2198
|
-
|
|
793
|
+
path: string;
|
|
2199
794
|
name: string;
|
|
2200
795
|
};
|
|
2201
|
-
/** `GET {basePath}/fs/roots` — where a client may start browsing. Empty `roots`
|
|
2202
|
-
* never happens: the routes are absent entirely when none are configured. */
|
|
2203
796
|
type ListHostRootsResponse = {
|
|
2204
|
-
roots: HostFileRoot[];
|
|
797
|
+
roots: HostFileRoot[];
|
|
2205
798
|
canWrite: boolean;
|
|
2206
799
|
};
|
|
2207
|
-
/** One entry in a host directory listing. Classified with `lstat` semantics, so a
|
|
2208
|
-
* `symlink` is reported as itself and never silently resolved — following it is the
|
|
2209
|
-
* *next* request's problem, and that request is refused if it escapes the roots. */
|
|
2210
800
|
type HostDirEntry = {
|
|
2211
|
-
name: string;
|
|
801
|
+
name: string;
|
|
2212
802
|
path: string;
|
|
2213
|
-
type: 'file' | 'dir' | 'symlink' | 'other';
|
|
2214
|
-
bytes?: number;
|
|
803
|
+
type: 'file' | 'dir' | 'symlink' | 'other';
|
|
804
|
+
bytes?: number;
|
|
2215
805
|
modifiedAt?: number;
|
|
2216
806
|
};
|
|
2217
|
-
/** `GET {basePath}/fs/list?path=<abs>` — one directory, not recursive. */
|
|
2218
807
|
type ListHostDirResponse = {
|
|
2219
|
-
|
|
2220
|
-
entries: HostDirEntry[];
|
|
808
|
+
path: string;
|
|
809
|
+
entries: HostDirEntry[];
|
|
2221
810
|
truncated?: boolean;
|
|
2222
811
|
};
|
|
2223
|
-
/** One hit from `GET {basePath}/fs/find`. */
|
|
2224
812
|
type HostFileMatch = {
|
|
2225
|
-
|
|
813
|
+
path: string;
|
|
2226
814
|
relative: string;
|
|
2227
815
|
};
|
|
2228
|
-
/**
|
|
2229
|
-
* `GET {basePath}/fs/find?path=<dir>&q=<query>&limit=<n>` — recursive fuzzy file
|
|
2230
|
-
* search under one directory, which is what an `@file` picker needs and
|
|
2231
|
-
* `/fs/list` is not: listing answers "what is in this directory", this answers
|
|
2232
|
-
* "which file in this tree did you mean".
|
|
2233
|
-
*
|
|
2234
|
-
* Subsequence matching (`seslist` finds `SessionListView.swift`), filename hits
|
|
2235
|
-
* ranked above path hits, shallow files above deep ones. An empty `q` returns the
|
|
2236
|
-
* shallowest files. Build directories (`.git`, `node_modules`, …) are skipped, as
|
|
2237
|
-
* is anything behind a symlink — so every path returned is one `/fs/read` will
|
|
2238
|
-
* accept.
|
|
2239
|
-
*/
|
|
2240
816
|
type FindHostFilesResponse = {
|
|
2241
|
-
|
|
2242
|
-
matches: HostFileMatch[];
|
|
817
|
+
base: string;
|
|
818
|
+
matches: HostFileMatch[];
|
|
2243
819
|
truncated: boolean;
|
|
2244
820
|
};
|
|
2245
|
-
/** `GET {basePath}/fs/read?path=<abs>` — one file's contents. Binary files come back
|
|
2246
|
-
* base64; 413 rather than a truncated read when the file exceeds the server's cap. */
|
|
2247
821
|
type ReadHostFileResponse = {
|
|
2248
822
|
path: string;
|
|
2249
823
|
content: string;
|
|
2250
824
|
encoding: 'utf8' | 'base64';
|
|
2251
|
-
bytes: number;
|
|
825
|
+
bytes: number;
|
|
2252
826
|
hash: string;
|
|
2253
827
|
modifiedAt: number;
|
|
2254
828
|
};
|
|
2255
|
-
/**
|
|
2256
|
-
* `PUT {basePath}/fs/write` — replace or create one file.
|
|
2257
|
-
*
|
|
2258
|
-
* The agent is editing this same tree, so a write is **conditional, always**:
|
|
2259
|
-
* `expectedHash` must be the hash from the read this edit is based on, and the
|
|
2260
|
-
* server 409s if the file has changed since. Omitting it means "create" and 409s
|
|
2261
|
-
* if the path already exists — there is no unconditional overwrite, by design.
|
|
2262
|
-
* Directories are never created implicitly: writing under a missing parent is a 404.
|
|
2263
|
-
*/
|
|
2264
829
|
type WriteHostFileRequest = {
|
|
2265
830
|
path: string;
|
|
2266
|
-
content: string;
|
|
2267
|
-
encoding?: 'utf8' | 'base64';
|
|
831
|
+
content: string;
|
|
832
|
+
encoding?: 'utf8' | 'base64';
|
|
2268
833
|
expectedHash?: string;
|
|
2269
834
|
};
|
|
2270
835
|
type WriteHostFileResponse = {
|
|
2271
836
|
path: string;
|
|
2272
|
-
bytes: number;
|
|
837
|
+
bytes: number;
|
|
2273
838
|
hash: string;
|
|
2274
839
|
modifiedAt: number;
|
|
2275
840
|
};
|
|
2276
841
|
type SaveProfileResponse = {
|
|
2277
842
|
profile: ProfileInfo;
|
|
2278
843
|
};
|
|
2279
|
-
/** `GET {basePath}/profiles/:name` — the profile plus a fresh config snapshot. */
|
|
2280
844
|
type GetProfileResponse = {
|
|
2281
845
|
profile: ProfileInfo;
|
|
2282
846
|
config: ProfileConfigSnapshot;
|
|
@@ -2284,95 +848,53 @@ type GetProfileResponse = {
|
|
|
2284
848
|
type ErrorResponse = {
|
|
2285
849
|
error: string;
|
|
2286
850
|
};
|
|
2287
|
-
|
|
2288
|
-
* The moments in an *interactive* session a person needs to hear about when they
|
|
2289
|
-
* are not watching it — the whole point being that a phone cannot hold a
|
|
2290
|
-
* WebSocket open in the background, so the server has to reach out.
|
|
2291
|
-
*
|
|
2292
|
-
* Deliberately four: this is a human-attention channel, not an event mirror. The
|
|
2293
|
-
* event log stays on the session WS (attach with `afterSeq` to catch up); if you
|
|
2294
|
-
* want every assistant message, subscribe there instead.
|
|
2295
|
-
*/
|
|
2296
|
-
type SessionNotificationType = /** The agent is blocked on an approval — the one that matters most. */'permission_requested' /** A turn finished; the session is idle and waiting for the human. */ | 'turn_completed' /** The session failed (`session_error`). */ | 'session_error' /** The session ended (`session_closed`), whoever ended it. */ | 'session_closed';
|
|
2297
|
-
/** One delivery on the session-notification channel (JSON body of a webhook POST). */
|
|
851
|
+
type SessionNotificationType = 'permission_requested' | 'turn_completed' | 'session_error' | 'session_closed';
|
|
2298
852
|
type SessionNotification = {
|
|
2299
853
|
type: SessionNotificationType;
|
|
2300
|
-
sessionId: string;
|
|
854
|
+
sessionId: string;
|
|
2301
855
|
session: SessionInfo;
|
|
2302
|
-
/** Seq of the event behind this notification; attach with `afterSeq: seq - 1` to
|
|
2303
|
-
* land on it. */
|
|
2304
856
|
seq: number;
|
|
2305
857
|
ts: number;
|
|
2306
|
-
/** One line fit for a notification body: the permission title, the turn's final
|
|
2307
|
-
* text, the error message. */
|
|
2308
858
|
preview?: string;
|
|
2309
|
-
|
|
2310
|
-
* `POST {basePath}/sessions/:id/permissions/:requestId` — which is what makes an
|
|
2311
|
-
* Approve/Deny action on a lock-screen notification possible. */
|
|
2312
|
-
request?: PermissionRequest; /** `turn_completed` only. */
|
|
859
|
+
request?: PermissionRequest;
|
|
2313
860
|
result?: {
|
|
2314
861
|
isError: boolean;
|
|
2315
862
|
durationMs: number;
|
|
2316
863
|
numTurns: number;
|
|
2317
864
|
totalCostUsd: number;
|
|
2318
|
-
};
|
|
865
|
+
};
|
|
2319
866
|
reason?: 'client' | 'server' | 'error';
|
|
2320
867
|
};
|
|
2321
|
-
/** Where session notifications are POSTed (JSON body = {@link SessionNotification}).
|
|
2322
|
-
* Server-wide, not per session: the point is to hear about sessions you did not
|
|
2323
|
-
* create yourself and are not attached to. */
|
|
2324
868
|
type SessionWebhookConfig = {
|
|
2325
|
-
url: string;
|
|
2326
|
-
headers?: Record<string, string>;
|
|
869
|
+
url: string;
|
|
870
|
+
headers?: Record<string, string>;
|
|
2327
871
|
events?: SessionNotificationType[];
|
|
2328
872
|
};
|
|
2329
|
-
/**
|
|
2330
|
-
* - `queued` — accepted, waiting for a concurrency slot (or the daily token budget)
|
|
2331
|
-
* - `running` — a session is executing the prompt
|
|
2332
|
-
* - `parked` — waiting on an external event (a deferred tool execution). Not
|
|
2333
|
-
* terminal and not consuming a concurrency slot; resumes to `running` when the
|
|
2334
|
-
* result arrives, or fails via the execution watchdog if it never does.
|
|
2335
|
-
* - `succeeded` / `failed` — terminal; `result` (and `error` on failure) are set
|
|
2336
|
-
* - `canceled` — terminal; canceled by a client before or during the run
|
|
2337
|
-
*/
|
|
2338
873
|
type JobStatus = 'queued' | 'running' | 'parked' | 'succeeded' | 'failed' | 'canceled';
|
|
2339
|
-
/** Where job progress/completion deliveries are POSTed (JSON body = {@link JobEvent}). */
|
|
2340
874
|
type WebhookConfig = {
|
|
2341
|
-
url: string;
|
|
875
|
+
url: string;
|
|
2342
876
|
headers?: Record<string, string>;
|
|
2343
|
-
/** Delivery granularity: 'messages' also POSTs job_progress per assistant message /
|
|
2344
|
-
* permission request; 'completion' only job_started + job_completed. Default 'messages'. */
|
|
2345
877
|
progress?: 'messages' | 'completion';
|
|
2346
878
|
};
|
|
2347
|
-
/**
|
|
2348
|
-
* Schedule a one-shot run: the session executes `prompt` unattended and the job
|
|
2349
|
-
* completes with that run's result. `session.prompt` is the task and is required;
|
|
2350
|
-
* `resume`/`forkSession` are not supported for queued jobs.
|
|
2351
|
-
*/
|
|
2352
879
|
type CreateJobRequest = {
|
|
2353
880
|
session: CreateSessionRequest & {
|
|
2354
881
|
prompt: string;
|
|
2355
882
|
};
|
|
2356
|
-
webhook?: WebhookConfig;
|
|
2357
|
-
maxTokens?: number;
|
|
883
|
+
webhook?: WebhookConfig;
|
|
884
|
+
maxTokens?: number;
|
|
2358
885
|
maxDurationMs?: number;
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
attempts?: number; /** Delay before the first retry, doubled for each subsequent one. Default 5000. */
|
|
2362
|
-
retryDelayMs?: number; /** Host bookkeeping echoed back on JobInfo. */
|
|
886
|
+
attempts?: number;
|
|
887
|
+
retryDelayMs?: number;
|
|
2363
888
|
meta?: Record<string, unknown>;
|
|
2364
889
|
};
|
|
2365
|
-
/** Cumulative resource usage of a job's run. `tokens` counts input + output +
|
|
2366
|
-
* cache-creation + cache-read tokens across all turns. */
|
|
2367
890
|
type JobUsage = {
|
|
2368
891
|
tokens: number;
|
|
2369
892
|
totalCostUsd: number;
|
|
2370
893
|
numTurns: number;
|
|
2371
894
|
};
|
|
2372
|
-
/** Terminal outcome of the job's run (mirrors the final turn_result). */
|
|
2373
895
|
type JobResult = {
|
|
2374
896
|
subtype: string;
|
|
2375
|
-
isError: boolean;
|
|
897
|
+
isError: boolean;
|
|
2376
898
|
result?: string;
|
|
2377
899
|
errors?: string[];
|
|
2378
900
|
durationMs: number;
|
|
@@ -2380,45 +902,30 @@ type JobResult = {
|
|
|
2380
902
|
type JobInfo = {
|
|
2381
903
|
id: string;
|
|
2382
904
|
status: JobStatus;
|
|
2383
|
-
|
|
2384
|
-
* {@link CreateSessionRequest.cwd}). */
|
|
2385
|
-
cwd: string; /** Profile the run executes under (resolved name, present even when implicit). */
|
|
905
|
+
cwd: string;
|
|
2386
906
|
profile?: string;
|
|
2387
|
-
prompt: string;
|
|
907
|
+
prompt: string;
|
|
2388
908
|
sessionId?: string;
|
|
2389
909
|
sdkSessionId?: string;
|
|
2390
910
|
createdAt: number;
|
|
2391
911
|
startedAt?: number;
|
|
2392
|
-
finishedAt?: number;
|
|
2393
|
-
attempt?: number;
|
|
2394
|
-
maxAttempts?: number;
|
|
912
|
+
finishedAt?: number;
|
|
913
|
+
attempt?: number;
|
|
914
|
+
maxAttempts?: number;
|
|
2395
915
|
nextRunAt?: number;
|
|
2396
|
-
/** Set while `status` is 'parked': when the run parked, and the execution it is
|
|
2397
|
-
* waiting on — the id to POST a result to. Cleared when it resumes. */
|
|
2398
916
|
parkedAt?: number;
|
|
2399
|
-
parkedExecutionId?: string;
|
|
917
|
+
parkedExecutionId?: string;
|
|
2400
918
|
usage: JobUsage;
|
|
2401
|
-
result?: JobResult;
|
|
919
|
+
result?: JobResult;
|
|
2402
920
|
error?: string;
|
|
2403
921
|
meta?: Record<string, unknown>;
|
|
2404
|
-
/** Scope tags of the session this job runs (see
|
|
2405
|
-
* {@link CreateSessionRequest.scope}) — copied from the request at submit so
|
|
2406
|
-
* the job routes can be gated by the same rule as the session routes. Without
|
|
2407
|
-
* it the queue would be a side door into an unscoped session. */
|
|
2408
922
|
scope?: Record<string, string>;
|
|
2409
923
|
};
|
|
2410
|
-
/** Latest mid-run activity, carried on job_progress deliveries. */
|
|
2411
924
|
type JobProgress = {
|
|
2412
|
-
kind: 'assistant_text' | 'tool_use' | 'permission_requested' | 'permission_resolved';
|
|
925
|
+
kind: 'assistant_text' | 'tool_use' | 'permission_requested' | 'permission_resolved';
|
|
2413
926
|
preview?: string;
|
|
2414
|
-
/** 'permission_requested' only: the full request (including AskUserQuestion input) so
|
|
2415
|
-
* webhook consumers can answer via POST /sessions/:sessionId/permissions/:requestId. */
|
|
2416
927
|
request?: PermissionRequest;
|
|
2417
928
|
};
|
|
2418
|
-
/** Webhook delivery payload (also the queue's local event shape). `job_submitted` goes
|
|
2419
|
-
* to local observers and the queue WS only — the submitter already has the POST
|
|
2420
|
-
* response, so webhooks start at `job_started`. `job_retrying` marks a failed run that
|
|
2421
|
-
* was re-queued (`job.nextRunAt` says when); `job_completed` is always terminal. */
|
|
2422
929
|
type JobEvent = {
|
|
2423
930
|
type: 'job_submitted';
|
|
2424
931
|
job: JobInfo;
|
|
@@ -2432,17 +939,12 @@ type JobEvent = {
|
|
|
2432
939
|
job: JobInfo;
|
|
2433
940
|
progress: JobProgress;
|
|
2434
941
|
ts: number;
|
|
2435
|
-
}
|
|
2436
|
-
/** The run parked on a deferred execution; `executionId` says what it waits on —
|
|
2437
|
-
* the id to POST a result to. The *work itself* (tool name, input, VFS seed) went
|
|
2438
|
-
* to the executor's own dispatch hook, not over this channel: a webhook consumer
|
|
2439
|
-
* learns that a run is waiting, the worker learns what to do. */
|
|
2440
|
-
| {
|
|
942
|
+
} | {
|
|
2441
943
|
type: 'job_parked';
|
|
2442
944
|
job: JobInfo;
|
|
2443
945
|
executionId: string;
|
|
2444
946
|
ts: number;
|
|
2445
|
-
}
|
|
947
|
+
} | {
|
|
2446
948
|
type: 'job_resumed';
|
|
2447
949
|
job: JobInfo;
|
|
2448
950
|
executionId: string;
|
|
@@ -2460,17 +962,12 @@ type QueueStats = {
|
|
|
2460
962
|
maxConcurrency: number;
|
|
2461
963
|
running: number;
|
|
2462
964
|
queued: number;
|
|
2463
|
-
/** Jobs waiting on a deferred execution. They hold no concurrency slot and
|
|
2464
|
-
* their wall-clock budget is not ticking. */
|
|
2465
965
|
parked: number;
|
|
2466
966
|
sessionTokenLimit?: number;
|
|
2467
|
-
dailyTokenLimit?: number;
|
|
2468
|
-
dailyTokensUsed: number;
|
|
967
|
+
dailyTokenLimit?: number;
|
|
968
|
+
dailyTokensUsed: number;
|
|
2469
969
|
paused: boolean;
|
|
2470
970
|
};
|
|
2471
|
-
/** Frames sent on the queue WS (`{basePath}/queue/ws`). The stream is one-way
|
|
2472
|
-
* (server→client): every job's lifecycle as it happens, plus refreshed stats after
|
|
2473
|
-
* lifecycle changes. Clients send nothing; job mutations stay on REST. */
|
|
2474
971
|
type QueueServerFrame = {
|
|
2475
972
|
type: 'queue_attached';
|
|
2476
973
|
protocolVersion: number;
|
|
@@ -2495,5 +992,5 @@ type QueueStatsResponse = {
|
|
|
2495
992
|
stats: QueueStats;
|
|
2496
993
|
};
|
|
2497
994
|
//#endregion
|
|
2498
|
-
export { ApiMessage, AttachedFrame, ClientFrame, ContentBlock, ContextReading, ContextUsage, ContextUsageCategory, CreateJobRequest, CreateJobResponse, CreateProfileRequest, CreateSessionRequest, CreateSessionResponse, DEFAULT_VIEW_CONFIG, ENGINE_CAPABILITIES, EngineCapabilities, ErrorResponse, Facet, FilePatch, FindHostFilesResponse, GetJobResponse, GetProfileResponse, GetSessionResponse, GroupBy, HostDirEntry, HostFileMatch, HostFileRoot, ImageRefPart, JobEvent, JobInfo, JobProgress, JobResult, JobStatus, JobUsage, ListHostDirResponse, ListHostRootsResponse, ListJobsResponse, ListProfilesResponse, ListSdkSessionsResponse, ListSessionFilesResponse, ListSessionsResponse, McpServerActionRequest, McpServerConfigWire, McpServerStatusInfo, McpServerToolInfo, McpServersResponse, MessageAttachment, ModelOption, PROTOCOL_VERSION,
|
|
995
|
+
export { ApiMessage, AttachedFrame, ClientFrame, ContentBlock, ContextReading, ContextUsage, ContextUsageCategory, CreateJobRequest, CreateJobResponse, CreateProfileRequest, CreateSessionRequest, CreateSessionResponse, DEFAULT_VIEW_CONFIG, ENGINE_CAPABILITIES, EngineCapabilities, ErrorResponse, Facet, FilePatch, FindHostFilesResponse, GetJobResponse, GetProfileResponse, GetSessionResponse, GroupBy, HostDirEntry, HostFileMatch, HostFileRoot, ImageRefPart, JobEvent, JobInfo, JobProgress, JobResult, JobStatus, JobUsage, ListHostDirResponse, ListHostRootsResponse, ListJobsResponse, ListProfilesResponse, ListSdkSessionsResponse, ListSessionFilesResponse, ListSessionsResponse, McpServerActionRequest, McpServerConfigWire, McpServerStatusInfo, McpServerToolInfo, McpServersResponse, MessageAttachment, ModelOption, PROTOCOL_VERSION, PatchHunk, PermissionDecisionSource, PermissionMode, PermissionRequest, ProfileConfigSnapshot, ProfileDefaults, ProfileEngine, ProfileInfo, ProfileSessionDefaults, ProfileUsage, ProfileUsageWindow, ProjectIcon, ProjectInfo, ProviderConfig, QuestionBehavior, QueueServerFrame, QueueStats, QueueStatsResponse, RateLimitInfo, ReadHostFileResponse, ResolvePermissionRequest, ResolvePermissionResponse, STATE_LABELS, STATE_ORDER, SUBAGENT_HISTORY, SaveProfileResponse, ScopeRoot, SdkSessionSummary, ServerFrame, SessionCapability, SessionCommand, SessionEvent, SessionEventBody, SessionFileInfo, SessionGroup, SessionInfo, SessionNotification, SessionNotificationType, SessionRow, SessionState, SessionStatus, SessionUsage, SessionWebhookConfig, SkillInfo, SlashCommandInfo, SortBy, SubagentInfo, SubmitExecutionResultRequest, SubmitExecutionResultResponse, SubsetSummary, TOOL_RESULT_HEAD_CHARS, TextBlock, ThinkingBlock, ToolCallRequestFrame, ToolExecutionBackend, ToolExecutionOutput, ToolExecutionStatus, ToolResultBlock, ToolUseBlock, UnknownBlock, UpdateProfileRequest, UpdateSessionRequest, UpdateSessionResponse, UploadAttachmentResponse, UsageWindowRow, UserQuestion, UserQuestionOption, ViewConfig, Watermark, WatermarkStore, Watermarks, WebhookConfig, WorkspaceScope, WriteHostFileRequest, WriteHostFileResponse, adaptersOf, clearFilters, contextReading, filterRows, groupRows, hasFacetFilter, imagePartRef, inScope, isAgentRecord, isJobRun, mergeUsage, orderUsageWindows, projectKey, projectLabel, projectSubpath, projectsOf, replayCoalesceKey, replayRetains, runningSubagents, scopeActive, sessionLabel, sessionState, snapshotRetains, subagentLabel, subsetSummary, supportsPermissionMode, transcriptActivity, transcriptContent, transcriptProse, unseenCount, usageInfos, watermarkKey };
|
|
2499
996
|
//# sourceMappingURL=index.d.mts.map
|