@workerdeck/react 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 CHANGED
@@ -1,28 +1,18 @@
1
1
  import { AttachedFrame, ContextUsage, EngineCapabilities, FilePatch, HostDirEntry, HostFileMatch, MessageAttachment, ModelOption, PermissionMode, PermissionRequest, ProfileEngine, ProfileUsage, RateLimitInfo, SessionEvent, SessionInfo, SessionRow, SessionStatus, SkillInfo, SlashCommandInfo, ToolExecutionBackend, UsageWindowRow } from "@workerdeck/protocol";
2
2
  import { SessionHandle, WorkerDeckClient } from "@workerdeck/client";
3
3
  import { RunScriptResult, SandboxEngine, SandboxVfs } from "@workerdeck/sandbox";
4
-
5
4
  //#region src/lib/transcript.d.ts
6
- /**
7
- * Pure transcript state machine over the wire-protocol event stream. Framework-free
8
- * so it can be unit-tested and reused outside React.
9
- */
5
+ type ToolResultImageRef = {
6
+ partIndex: number;
7
+ mediaType: string;
8
+ bytes: number;
9
+ sourceSeq: number;
10
+ };
10
11
  type TranscriptItem = {
11
12
  kind: 'user';
12
13
  id: string;
13
14
  text: string;
14
15
  attachments?: MessageAttachment[];
15
- /**
16
- * The `Task` call this prompt was addressed to, when it is a subagent's
17
- * brief rather than something a person typed.
18
- *
19
- * Optional where the other kinds carry it as `string | null`, and the
20
- * asymmetry is the point: on those it is a fact about every instance, so
21
- * forgetting to stamp it should not typecheck. Here the overwhelming case
22
- * is a human prompt, which has no parent at all — `undefined` says that,
23
- * where `null` on 24 construction sites would only say "somebody
24
- * remembered".
25
- */
26
16
  parentToolUseId?: string;
27
17
  } | {
28
18
  kind: 'assistant_text';
@@ -41,82 +31,19 @@ type TranscriptItem = {
41
31
  name: string;
42
32
  input: unknown;
43
33
  parentToolUseId: string | null;
44
- /**
45
- * When the model called it — the event's own `ts`, so it is replay-stable
46
- * rather than a receive time (the mistake `rateLimitsUpdatedAt` makes on
47
- * iOS). Optional because it is stamped at creation only: an item
48
- * reconstructed by an older path has none, and absent must read as "no
49
- * elapsed" rather than as the epoch.
50
- *
51
- * Added for the sub-agent takeover's header, which is the one surface that
52
- * has to say how long an agent has been going: `SubagentInfo.startedAt`
53
- * cannot answer it, being frozen at attach for anything spawned later.
54
- * Immutable after creation, which is what makes it safe for iOS's
55
- * `Equatable` row-plan cache key to mirror later.
56
- */
57
34
  ts?: number;
58
- /**
59
- * - `running` — the model called it; execution has not been reported
60
- * - `pending` — dispatched to an executor (bridged to this client, queued)
61
- * - `deferred` — parked beyond this turn; may outlive the session's liveness
62
- * - `settled` / `failed` — terminal
63
- *
64
- * Derive UI from this, not from `result` being present: a pending or
65
- * deferred call has no result yet and is not the same as a running one.
66
- */
67
35
  status: 'running' | 'pending' | 'deferred' | 'settled' | 'failed';
68
- /**
69
- * `truncated`/`totalChars`/`sourceSeq` are set **only** when the replay
70
- * delivered a head (protocol's {@link ToolResultBlock.truncated}), so
71
- * every other result stays byte-identical to what it was before this
72
- * feature existed. That matters beyond tidiness: on iOS `ToolCallItem` is
73
- * `Equatable` and is half the row-plan cache key.
74
- *
75
- * `sourceSeq` is what makes the press possible at all — the item is what a
76
- * renderer holds, and it must be able to name the event to fetch. It goes
77
- * away again on hydration, along with the other two, so a hydrated result
78
- * is indistinguishable from one that was never cut.
79
- */
80
36
  result?: {
81
37
  text: string;
82
38
  isError: boolean;
83
39
  truncated?: boolean;
84
40
  totalChars?: number;
85
41
  sourceSeq?: number;
86
- /**
87
- * The pictures this result carried, as addresses rather than bytes —
88
- * set **only** when the replay delivered `image_ref` parts, so every
89
- * other result stays byte-identical (the `Equatable` argument above,
90
- * again).
91
- *
92
- * Each entry carries its **own** `sourceSeq`, which is not redundant
93
- * with the one beside it: that one is cleared by text hydration, and a
94
- * reader who pressed "show everything" must still be able to load the
95
- * screenshot afterwards.
96
- *
97
- * Raw base64 `image` parts are still dropped on arrival, as they always
98
- * were. Folding them in would pin megabytes inside `TranscriptState`,
99
- * which the transcript LRU then retains across session switches.
100
- */
101
- images?: ReadonlyArray<{
102
- partIndex: number;
103
- mediaType: string;
104
- bytes: number;
105
- sourceSeq: number;
106
- }>;
42
+ images?: ReadonlyArray<ToolResultImageRef>;
107
43
  };
108
- /**
109
- * What this call changed on disk, when it was a file edit — the engine's
110
- * own hunks and line numbers (see protocol's {@link FilePatch}).
111
- *
112
- * Only ever set from the wire. A client cannot derive it: it has never
113
- * seen the file, so a diff it computed from the tool's *input* would have
114
- * no line numbers, and one parsed out of the result prose would be welded
115
- * to an engine's text formatting.
116
- */
117
- patch?: FilePatch; /** Correlation id when this call is executed outside the model loop. */
118
- executionId?: string; /** Which backend is executing it, when known. */
119
- backend?: ToolExecutionBackend; /** Logs captured by the executor (guest console output). */
44
+ patch?: FilePatch;
45
+ executionId?: string;
46
+ backend?: ToolExecutionBackend;
120
47
  logs?: string[];
121
48
  } | {
122
49
  kind: 'turn_result';
@@ -131,18 +58,13 @@ type TranscriptItem = {
131
58
  id: string;
132
59
  level: 'info' | 'error';
133
60
  text: string;
134
- }
135
- /** The agent handed over a session file (`file_delivered`). Render a download
136
- * card; the file is served by GET /sessions/:id/files/<path> while the
137
- * session lives. */
138
- | {
61
+ } | {
139
62
  kind: 'file_delivered';
140
63
  id: string;
141
64
  path: string;
142
65
  bytes: number;
143
66
  description?: string;
144
67
  };
145
- /** A `file_produced` announcement, as the transcript keeps it. */
146
68
  type ProducedFileRef = {
147
69
  fileId: string;
148
70
  mediaType?: string;
@@ -154,62 +76,18 @@ type TranscriptState = {
154
76
  model?: string;
155
77
  cwd?: string;
156
78
  sdkSessionId?: string;
157
- /** Engine running the session, from the attach snapshot. Gates CLI-only
158
- * affordances; absent (an older server) reads as 'claude'. */
159
79
  engine?: ProfileEngine;
160
- /**
161
- * What this session's engine does and does not do: the runner-reported record
162
- * from the attach snapshot when present, else {@link ENGINE_CAPABILITIES} for
163
- * the engine. Always defined, so a surface can render every affordance from it
164
- * rather than switching on the engine name — an absent capability means the
165
- * affordance is *hidden*, never a control that silently does nothing.
166
- */
167
80
  capabilities: EngineCapabilities;
168
- /**
169
- * The most recent attach snapshot, whole. The session-level facts no event
170
- * carries — profile, apiKeySource, canBypassPermissions, createdAt, numTurns —
171
- * live only here. Unlike the fields above it is replaced on every attach: it is
172
- * the server's answer, not something the event stream refines.
173
- */
174
- session?: SessionInfo; /** Models the session can switch to (from the `capabilities` event). */
175
- models?: ModelOption[]; /** Slash commands the CLI accepts (from the `capabilities` event). */
81
+ session?: SessionInfo;
82
+ models?: ModelOption[];
176
83
  commands?: SlashCommandInfo[];
177
- /**
178
- * Skills the engine can reach (from the `skills` event), replaced whole each
179
- * time. Absent until the engine has enumerated them — which for codex is on
180
- * its first turn, since listing needs a live child. So gate the affordance on
181
- * *this being defined*, not on `capabilities.skillsList` alone: the flag says
182
- * the engine can answer, this says it has.
183
- *
184
- * Not commands, and must not be offered as such — see the protocol's
185
- * `SkillInfo`.
186
- */
187
84
  skills?: SkillInfo[];
188
- /**
189
- * Files the engine wrote on the host, keyed by the absolute path it reported
190
- * (from `file_produced`). A tool card holding a `savedPath` looks itself up
191
- * here to turn that path into a fetchable id — `client.producedFileUrl` — so
192
- * the picture renders without the operator having declared a host-file root.
193
- */
194
85
  producedFiles?: Record<string, ProducedFileRef>;
195
- /** What this session's default model resolves to (from `capabilities`). Known
196
- * before the first turn, which `model` is not — a promptless session has no
197
- * `system_init` until it is spoken to. */
198
- defaultModel?: string; /** Seeded from `system_init`, updated on `permission_mode_changed`. */
199
- permissionMode?: PermissionMode; /** Latest context-window snapshot; absent until the first turn completes. */
86
+ defaultModel?: string;
87
+ permissionMode?: PermissionMode;
200
88
  contextUsage?: ContextUsage;
201
- /** Latest rate-limit snapshot per window ('five_hour', 'seven_day', ...).
202
- * Absent for API-key sessions — render nothing, not 0%. */
203
89
  rateLimits?: Record<string, RateLimitInfo>;
204
- /**
205
- * When the newest window reading was *taken* (the event's `ts`), not when this
206
- * client received it — so a reading replayed on attach is dated honestly
207
- * rather than as "just now". Updates come one per turn at best, which makes a
208
- * stale reading normal and worth saying out loud.
209
- */
210
90
  rateLimitsUpdatedAt?: number;
211
- /** claude.ai plan the rate-limit windows belong to ('pro', 'max', ...), from
212
- * `plan_info`. Absent for API-key sessions, like the windows themselves. */
213
91
  subscriptionType?: string;
214
92
  items: TranscriptItem[];
215
93
  pendingApprovals: PermissionRequest[];
@@ -217,556 +95,189 @@ type TranscriptState = {
217
95
  lastSeq: number;
218
96
  };
219
97
  declare const initialTranscriptState: TranscriptState;
220
- /**
221
- * Seed transcript state from the attach snapshot (the `attached` frame's SessionInfo).
222
- * A promptless session emits no `system_init` until its first message, so fields like
223
- * `permissionMode` and `model` would otherwise stay empty — fill only what events
224
- * haven't set yet; the event stream stays authoritative.
225
- */
226
98
  declare function seedFromSessionInfo(state: TranscriptState, info: SessionInfo): TranscriptState;
227
- /**
228
- * The session's rate-limit windows in reading order: the session window, the
229
- * weekly window, then whichever per-model weekly windows it reports.
230
- *
231
- * The ordering and the drop-the-unknown rule are protocol's `orderUsageWindows`
232
- * — the dashboard renders the same windows straight off `ProfileInfo.usage`,
233
- * with no transcript anywhere near it, and two orderings would be one account
234
- * described two ways. This stays as the transcript-shaped door to it.
235
- */
236
99
  declare function rateLimitWindows(state: TranscriptState): UsageWindowRow[];
237
- /**
238
- * Put a fetched tool result back where its head was — the other half of
239
- * `truncateResults`.
240
- *
241
- * Into **transcript state**, not row-local state, and the three reasons are the
242
- * design: the copy button then copies the whole thing rather than the head, the
243
- * transcript cache retains it across a session switch, and no later event can
244
- * re-truncate it. The markers are cleared, so a hydrated result is
245
- * indistinguishable from one that was never cut and every renderer needs a
246
- * branch for exactly one state, not two.
247
- *
248
- * Keyed on `toolUseId`, which is the id the row already holds; `seq` is what the
249
- * *fetch* needed, not what the fold needs. Unknown id returns `state` unchanged
250
- * — a press answered after the session was cleared must not resurrect a row.
251
- */
252
100
  declare function hydrateToolResult(state: TranscriptState, toolUseId: string, text: string): TranscriptState;
253
101
  declare function applyEvent(state: TranscriptState, event: SessionEvent): TranscriptState;
254
102
  //#endregion
255
103
  //#region src/hooks/use-session.d.ts
256
- /**
257
- * How the client is doing at reaching the gateway — deliberately not the session's
258
- * status. The two are orthogonal, and while the socket is down the status a client
259
- * holds is *stale*, so a surface that merges them must say so rather than keep
260
- * claiming "idle".
261
- *
262
- * The handle retries forever, so `offline` is a judgement about how long it has
263
- * been failing rather than a state the transport reports.
264
- */
265
104
  type ConnectionState = 'live' | 'reconnecting' | 'offline';
266
- /**
267
- * The seq the initial attach replay ends on, or undefined when there is nothing
268
- * to hold for.
269
- *
270
- * This is an exact signal, not a heuristic: the `attached` frame is sent before
271
- * any replayed `event` frame and carries the runner's seq at attach time
272
- * (`session.lastSeq`), so the moment the frame arrives the client knows
273
- * precisely which seq the replay ends on. Every runner keeps its full event log
274
- * and always delivers the highest-seq event on a fresh replay (the
275
- * `conversation_reset` skip is strictly-below-the-reset, and the reset's seq is
276
- * itself ≤ lastSeq), so `TranscriptState.lastSeq >= target` means the replay
277
- * has landed. No quiet window or other arrival heuristic belongs here.
278
- *
279
- * Only a FRESH attach yields a target (`replayingFrom === 0`): a reconnect
280
- * replays into a transcript the reader is already looking at, and blanking it
281
- * mid-turn would be a worse bug than the flicker the hold exists to fix. A
282
- * brand-new session (`lastSeq === 0`) has nothing to replay and never holds.
283
- */
284
105
  declare function initialReplayTarget(frame: AttachedFrame): number | undefined;
285
- /**
286
- * Whether an attach frame describes a DIFFERENT event log than the transcript
287
- * `held` was built from — in which case attaching with `afterSeq: held.lastSeq`
288
- * has already gone wrong: every event in the new log has seq ≤ afterSeq, so
289
- * nothing will ever arrive and the stale rows would stand forever, with no
290
- * error. The only recovery is to forget the state and re-attach from seq 0.
291
- *
292
- * A log resets on routine paths, not corner cases: a dormant session
293
- * (claude/codex surviving a gateway restart) is rebuilt with a brand-new
294
- * runner whose log starts at 0 and refills from the engine's own store. Two
295
- * checks, each of which the other misses:
296
- *
297
- * - `session.lastSeq < held.lastSeq` — the server's log is shorter than what
298
- * we hold. Within one log seq only grows, so this is proof of a reset. It
299
- * catches a rebuilt runner that has not yet re-run far — but not one whose
300
- * backfill already advanced past us.
301
- * - `session.createdAt !== held.session.createdAt` — a different runner
302
- * incarnation. The claude and codex runners stamp `Date.now()` at
303
- * construction, so a dormant rebuild always changes it; the provider runner
304
- * restores `createdAt` from its snapshot precisely when it also restores
305
- * the event log and seq counter (ai-sdk-runner's `#restore`), so equality
306
- * truthfully means "same log" for every engine.
307
- *
308
- * A full replay (`replayingFrom === 0`) is never stale — it carries the whole
309
- * log, so the caller heals by resetting state and applying it — and holding
310
- * nothing (`held.lastSeq === 0`) has nothing to be stale about. That first
311
- * clause is also what makes the recovery loop-proof: the re-attach from 0 can
312
- * never re-trigger this predicate.
313
- *
314
- * Not cache-specific: a live handle reconnecting after a gateway restart
315
- * re-attaches with its own advanced `afterSeq` against the rebuilt log and
316
- * hits the identical silence, so the hook applies this to every attach frame.
317
- */
318
106
  declare function staleAttach(frame: AttachedFrame, held: TranscriptState): boolean;
319
- /**
320
- * Backstop for the replay hold: if the target seq has not landed after this
321
- * long, reveal what has arrived. On a healthy attach the target is always
322
- * reached (see {@link initialReplayTarget}); the backstop exists because a
323
- * blank panel forever would be a much worse failure than a visible stream, so
324
- * the hold is bounded no matter what a future filter or a lossy path does. It
325
- * runs from the attach — a per-event re-arm would be a quiet-window heuristic
326
- * in a new costume.
327
- */
328
107
  declare const REPLAY_HOLD_MAX_MS = 1500;
329
108
  type UseClaudeSessionOptions = {
330
- /** Called when the server rejects a command with a protocol_error frame — e.g. a
331
- * permission-mode switch the CLI refuses. Without a handler these are dropped
332
- * silently and the UI looks like "nothing happened". */
333
109
  onProtocolError?: (message: string) => void;
334
- /**
335
- * Keep this session's transcript warm after unmount (default true): the next
336
- * mount of the same (client identity, session) paints the cached rows in its
337
- * first frame and attaches with `afterSeq`, replaying only what it missed.
338
- * Bounded module-scope LRU, keyed by the client's `identityKey` (gateway +
339
- * auth headers) so nothing crosses gateways or credentials; if the attach
340
- * frame shows a different event log (see {@link staleAttach}), the entry is
341
- * discarded and the hook re-attaches from seq 0.
342
- *
343
- * Set `false` for an embedder whose principal varies on one base URL by
344
- * means the client cannot see (a custom `fetchImpl` switching users, say) —
345
- * or call `clearTranscriptCache()` on logout. Read at attach time.
346
- */
347
110
  cacheTranscript?: boolean;
348
111
  };
349
112
  type UseClaudeSessionResult = {
350
113
  state: TranscriptState;
351
- /** True while the socket is open. {@link UseClaudeSessionResult.connection}
352
- * carries the same fact with the "has it been failing a while" distinction. */
353
114
  connected: boolean;
354
115
  connection: ConnectionState;
355
- /**
356
- * True while the initial attach replay is still landing: the `attached` frame
357
- * said events up to `session.lastSeq` follow, and they have not all been
358
- * applied yet. A surface can hold its paint on this — keep the rows mounted
359
- * and measuring, show nothing — and reveal a settled transcript in one frame,
360
- * instead of streaming hundreds of replayed rows past the reader. Always
361
- * false on a reconnect (only a fresh attach holds; see
362
- * {@link initialReplayTarget}) and bounded by {@link REPLAY_HOLD_MAX_MS}.
363
- */
364
116
  replaying: boolean;
365
- /** The server's `PROTOCOL_VERSION` when it disagrees with the one this build
366
- * mirrors — undefined when they match. Some events may not render. */
367
117
  protocolMismatch?: number;
368
- /**
369
- * What a model picker should offer. Two sources, and which is authoritative
370
- * depends on the engine: the `capabilities` event is the CLI asked what it
371
- * supports, so for claude it wins; codex never sends one — its models are a
372
- * catalog shipped with the release and served on the profile — so without the
373
- * fallback its picker would be permanently empty and the session unswitchable.
374
- */
375
118
  models: ModelOption[];
376
- /** The model this session answers as: the one it reported, or, before it has
377
- * reported anything, the default it will use. */
378
119
  effectiveModel?: string;
379
- /** The live attach handle, for wiring companions that must ride the SAME
380
- * socket — e.g. useToolCallHost: the bridge asks the first attached client,
381
- * so a host on a second handle would never see the requests. Undefined until
382
- * attached and after unmount. */
383
- handle: SessionHandle | undefined; /** Attachment ids come from `client.uploadAttachment`, in send order. */
120
+ handle: SessionHandle | undefined;
384
121
  send: (text: string, attachmentIds?: string[]) => void;
385
122
  approve: (requestId: string, updatedInput?: Record<string, unknown>) => void;
386
- /** `message` is fed back to the agent, which can then try something else;
387
- * `interrupt` also stops the turn ("deny & stop"). */
388
123
  deny: (requestId: string, message?: string, interrupt?: boolean) => void;
389
124
  interrupt: () => void;
390
- /**
391
- * Reset the conversation in place: same session, empty transcript. Gate the
392
- * affordance on `session.capabilities?.clearContext` — an engine or a gateway
393
- * that cannot do it answers with an error, which is the wrong way for a user
394
- * to find out.
395
- */
396
125
  clearContext: () => void;
397
126
  setPermissionMode: (mode: PermissionMode) => void;
398
127
  setModel: (model?: string) => void;
399
- closeSession: () => void; /** Skip the reconnect backoff — what a tab returning to the foreground does. */
128
+ closeSession: () => void;
400
129
  reconnectNow: () => void;
401
- /**
402
- * Fetch the whole of a tool result the replay delivered as a head, and put it
403
- * back on its row (`result.truncated` clears with it).
404
- *
405
- * Resolves `false` when there was nothing to do — an untruncated row, an
406
- * unknown id, or a gateway that refused (a stale `sourceSeq` after a dormant
407
- * rebuild 404s by design; re-attaching is what fixes that, not a retry). It
408
- * never throws, because the caller is a press on a row and an exception there
409
- * has nowhere sensible to go.
410
- */
411
130
  loadFullResult: (toolUseId: string) => Promise<boolean>;
412
131
  };
413
- /** Attach to a session and maintain live transcript state. Detaches on unmount. */
414
132
  declare function useClaudeSession(client: WorkerDeckClient, sessionId: string | undefined, options?: UseClaudeSessionOptions): UseClaudeSessionResult;
415
133
  //#endregion
416
134
  //#region src/lib/transcript-cache.d.ts
417
- /**
418
- * Drop every cached transcript. For an embedder changing principals in place
419
- * (a logout that keeps the page alive) — entries are unreachable through the
420
- * new principal's client either way, but scrubbing them is free and final.
421
- */
422
135
  declare function clearTranscriptCache(): void;
423
136
  //#endregion
137
+ //#region src/lib/profile-usage-cache.d.ts
138
+ declare function clearProfileUsageCache(): void;
139
+ //#endregion
140
+ //#region src/lib/draft-store.d.ts
141
+ declare function clearDrafts(): void;
142
+ //#endregion
424
143
  //#region src/hooks/use-attachments.d.ts
425
- /**
426
- * Files staged for the next message.
427
- *
428
- * The upload happens as soon as something is picked, not at send time — the
429
- * message names attachment *ids*, so the bytes must already be the server's
430
- * before a turn can reference them, and the wait is spent while the user is
431
- * still typing rather than after they hit send. It also keeps base64 out of the
432
- * event log entirely, which is the protocol's rule.
433
- */
434
144
  type StagedAttachment = {
435
- /** Local identity, stable across a retry — the React key while uploading. */key: string;
145
+ key: string;
436
146
  name: string;
437
147
  mediaType: string;
438
- bytes: number; /** Object URL for an image thumbnail, revoked when the item goes away. */
148
+ bytes: number;
439
149
  previewUrl?: string;
440
- status: 'uploading' | 'ready' | 'failed'; /** The server's id once uploaded — what `send` names. */
441
- id?: string; /** Why the upload failed, verbatim from the gateway (413, 415, …). */
150
+ status: 'uploading' | 'ready' | 'failed';
151
+ id?: string;
442
152
  error?: string;
443
153
  };
444
- /** The kind vocabulary of {@link EngineCapabilities.attachments}. */
445
154
  type AttachmentKind = 'image' | 'pdf' | 'text';
446
- /**
447
- * How a media type reaches a model, in the capability record's vocabulary.
448
- * `undefined` means this build can't classify it — the upload still goes,
449
- * because the gateway's vocabulary is the authoritative one.
450
- */
451
155
  declare function attachmentKind(mediaType: string): AttachmentKind | undefined;
452
156
  type UseAttachmentsOptions = {
453
- /** The session's capability record — its `attachments` list decides which
454
- * kinds are offered and which are refused locally. */
455
157
  capabilities: EngineCapabilities;
456
- /** Named in a local refusal, so "the codex engine does not take pdf
457
- * attachments" says which engine meant it. */
458
158
  engine?: ProfileEngine;
459
159
  };
460
160
  type UseAttachmentsResult = {
461
- items: StagedAttachment[]; /** Uploaded ids in staging order — what {@link UseClaudeSessionResult.send} names. */
462
- readyIds: string[]; /** An id that hasn't landed can't be named, so send waits. */
463
- uploading: boolean; /** A refused file must be dealt with before the message goes. */
464
- hasFailure: boolean; /** Accept attribute for a file input, narrowed to what the engine takes. */
161
+ items: StagedAttachment[];
162
+ readyIds: string[];
163
+ uploading: boolean;
164
+ hasFailure: boolean;
465
165
  accept: string;
466
- /** True when the engine takes no attachments at all — hide the affordance
467
- * entirely rather than offer one with no meaning. */
468
166
  disabled: boolean;
469
167
  add: (files: Iterable<File>) => void;
470
168
  retry: (key: string) => void;
471
169
  remove: (key: string) => void;
472
- clear: () => void; /** A local refusal (wrong kind), surfaced once rather than silently dropped. */
170
+ clear: () => void;
473
171
  error?: string;
474
172
  dismissError: () => void;
475
173
  };
476
- /**
477
- * Stage, upload and track files for the next message of a session.
478
- *
479
- * Refusals happen as early as they can be known: a kind the capability record
480
- * forswears never reaches the network (the gateway would 415 it), and everything
481
- * else is the gateway's call — its vocabulary is authoritative, so an unknown
482
- * media type is uploaded rather than guessed at.
483
- */
484
- declare function useAttachments(client: WorkerDeckClient, sessionId: string | undefined, {
485
- capabilities,
486
- engine
487
- }: UseAttachmentsOptions): UseAttachmentsResult;
174
+ declare function useAttachments(client: WorkerDeckClient, sessionId: string | undefined, { capabilities, engine }: UseAttachmentsOptions): UseAttachmentsResult;
488
175
  //#endregion
489
176
  //#region src/lib/prompt-tokens.d.ts
490
- /**
491
- * The two prompt tokens the CLI understands — `@file` and `/command` — found in
492
- * text that has already been sent.
493
- *
494
- * The mirror of the iOS client's `PromptTokens.scan`, and deliberately the same
495
- * rules: a message should read the same after sending as it did in the composer,
496
- * on either client. It lives here, beside the transcript reducer, for the same
497
- * reason its Swift twin lives in the kit rather than the app — every interesting
498
- * case is an edge (an `@` mid-word, an email address, a slash that is really an
499
- * absolute path), so it is the part that gets unit-tested.
500
- *
501
- * Only the finished-text half is here; the composer's completion is the
502
- * prompt-area's own trigger machinery.
503
- */
504
177
  type PromptToken = {
505
- kind: 'file' | 'command'; /** Offsets into the scanned string, prefix included. */
178
+ kind: 'file' | 'command';
506
179
  start: number;
507
180
  end: number;
508
181
  text: string;
509
182
  };
510
- /**
511
- * Every token in a sent message.
512
- *
513
- * Stricter than what a composer completes: a bare `@` is a token being typed, but
514
- * in a sent message it is just an at sign.
515
- */
516
183
  declare function scanPromptTokens(text: string): PromptToken[];
517
184
  //#endregion
518
185
  //#region src/lib/host-tree.d.ts
519
- /**
520
- * One directory as the tree knows it: what `/fs/list` answered, plus whether the
521
- * server held entries back.
522
- *
523
- * A directory that has never been asked for is simply absent from the map — which
524
- * is not the same as an empty directory, and the difference is what tells the
525
- * renderer to show a spinner rather than "nothing here".
526
- */
527
186
  type HostDirState = {
528
- entries: HostDirEntry[]; /** The directory held more entries than the server will return. */
187
+ entries: HostDirEntry[];
529
188
  truncated?: boolean;
530
189
  };
531
- /** One rendered row of the tree — a flat list is what a scroll container wants,
532
- * and indentation is a number, not a nesting of DOM. */
533
190
  type HostTreeRow = {
534
- entry: HostDirEntry; /** 0 for the root's own children. */
535
- depth: number; /** Directories only: whether this row's children are showing. */
536
- expanded?: boolean; /** Set on an expanded directory whose listing hasn't arrived yet. */
537
- loading?: boolean; /** Set on an expanded directory the server truncated. */
191
+ entry: HostDirEntry;
192
+ depth: number;
193
+ expanded?: boolean;
194
+ loading?: boolean;
538
195
  truncated?: boolean;
539
196
  };
540
- /**
541
- * Flatten the loaded directories into the rows the tree shows.
542
- *
543
- * Pure, so the interesting part of a file tree — which nodes are visible at what
544
- * depth once a few directories are expanded and one of them is still loading —
545
- * is testable without a DOM or a gateway.
546
- *
547
- * Only *expanded* directories contribute children, and only if their listing has
548
- * arrived. An expanded-but-unlisted directory yields its own row with
549
- * `loading: true` and no children: expansion is a request the user already made,
550
- * so the row must say the answer is coming rather than look like an empty folder.
551
- */
552
197
  declare function flattenHostTree(root: string, dirs: ReadonlyMap<string, HostDirState>, expanded: ReadonlySet<string>): HostTreeRow[];
553
- /**
554
- * Every ancestor of `path` below `root`, outermost first — the directories that
555
- * must be expanded for `path` to be on screen.
556
- *
557
- * Returns `[]` when `path` is not under `root` rather than guessing: revealing a
558
- * file the tree cannot contain is a no-op, not an error worth raising, and the
559
- * caller has no better answer either.
560
- *
561
- * The prefix test is on a **path boundary** (`root` + `/`), so `/src/app` is not
562
- * treated as living under `/src/a`.
563
- */
564
198
  declare function ancestorsWithin(root: string, path: string): string[];
565
199
  //#endregion
566
200
  //#region src/hooks/use-host-files.d.ts
567
201
  type UseHostFileSearchResult = {
568
- /**
569
- * Whether `@file` completion is on offer at all: the session's cwd is known
570
- * and this gateway hasn't already 404'd the search. Read it before advertising
571
- * the affordance — a server without host files configured has none.
572
- */
573
202
  available: boolean;
574
- /**
575
- * Run one search. Safe to call per keystroke — the route is built for it
576
- * (bounded walk, build directories skipped) — and it answers `[]` rather than
577
- * throwing, because a failed lookup is not worth an error banner over an
578
- * affordance the user can ignore.
579
- */
580
203
  search: (query: string, options?: {
581
204
  limit?: number;
582
205
  signal?: AbortSignal;
583
206
  }) => Promise<HostFileMatch[]>;
584
207
  };
585
- /**
586
- * Fuzzy file search rooted at a session's working directory — what an `@file`
587
- * picker needs.
588
- *
589
- * Deliberately session-scoped: the server's `hostFiles.roots` are the security
590
- * boundary, but what someone wants while talking to an agent is *this* project's
591
- * tree, so this never offers the roots list.
592
- *
593
- * A gateway that answers 404 once has answered for the session: host files are
594
- * either configured or they aren't, and the answer will not change while the cwd
595
- * holds. Asking again on every character would be a request per keystroke for a
596
- * feature that does not exist here.
597
- */
598
208
  declare function useHostFileSearch(client: WorkerDeckClient, cwd: string | undefined): UseHostFileSearchResult;
599
209
  type UseHostFileRootsResult = {
600
- /** Whether this gateway serves host files at all. */available: boolean;
601
- /**
602
- * Whether `PUT /fs/write` is enabled here.
603
- *
604
- * Read it before offering an editor. Writing is a **separate** server opt-in
605
- * from reading and defaults off, so a gateway that happily lists and reads a
606
- * tree may still refuse every save — and finding that out at save time, with
607
- * edits already made, is the worst moment for it.
608
- */
210
+ available: boolean;
609
211
  canWrite: boolean;
610
212
  };
611
- /**
612
- * Whether host files are served here, and whether they may be written.
613
- *
614
- * One request per client, cached for the life of the hook: the roots and the
615
- * write flag are gateway configuration, not session state, and they do not
616
- * change while the tab is open.
617
- */
618
213
  declare function useHostFileRoots(client: WorkerDeckClient): UseHostFileRootsResult;
619
214
  type UseHostFileTreeResult = {
620
- /**
621
- * Whether a tree can be shown at all: the cwd is known and this gateway serves
622
- * host files. Read it before rendering the rail — a gateway with no
623
- * `hostFiles` configured has no tree, and that is a layout decision, not an
624
- * error to display.
625
- */
626
- available: boolean; /** The directory the tree is rooted at — the session's cwd. */
627
- root: string | undefined; /** The visible tree, flattened. Empty until the root listing arrives. */
628
- rows: HostTreeRow[]; /** True while the root listing is outstanding and there is nothing to show. */
629
- loading: boolean; /** A listing that failed, verbatim from the gateway. */
630
- error: string | undefined; /** Expand or collapse a directory. Expanding lists it once and remembers. */
631
- toggle: (path: string) => void; /** Expand every directory between the root and this path, so it is on screen. */
632
- reveal: (path: string) => void; /** Re-list one directory (default: the root), keeping what is expanded. */
215
+ available: boolean;
216
+ root: string | undefined;
217
+ rows: HostTreeRow[];
218
+ loading: boolean;
219
+ error: string | undefined;
220
+ toggle: (path: string) => void;
221
+ reveal: (path: string) => void;
633
222
  refresh: (path?: string) => void;
634
223
  };
635
- /**
636
- * An expandable file tree rooted at a session's working directory.
637
- *
638
- * Rooted at the cwd rather than at `/fs/roots` for the same reason
639
- * {@link useHostFileSearch} is: the roots are the *security* boundary the server
640
- * enforces on every request, but what someone wants while watching an agent work
641
- * is this project's tree. The roots may well be broader; showing them would
642
- * offer navigation to directories the session has nothing to do with.
643
- *
644
- * Listings are cached per directory and kept across a collapse, so reopening a
645
- * folder is instant and does not re-ask. That staleness is deliberate and
646
- * bounded: `refresh` exists, and knowing when to call it is the *next* problem
647
- * (the agent is editing this same tree), not something a tree can guess.
648
- *
649
- * Like the search hook, a 404 is answered once for the session: host files are
650
- * either configured here or they are not.
651
- */
652
224
  declare function useHostFileTree(client: WorkerDeckClient, cwd: string | undefined): UseHostFileTreeResult;
653
225
  //#endregion
654
226
  //#region src/hooks/use-project-icons.d.ts
655
227
  type ClientForHost = (hostId: string) => WorkerDeckClient | undefined;
656
228
  declare function useProjectIcons(rows: readonly SessionRow[], clientFor: ClientForHost): Record<string, string>;
657
229
  //#endregion
230
+ //#region src/hooks/use-draft.d.ts
231
+ type UseDraftResult = {
232
+ /** What was left unsent last time, read once so it can seed the composer on mount. */
233
+ initialText: string;
234
+ save: (text: string) => void;
235
+ clear: () => void;
236
+ };
237
+ /**
238
+ * Remember unsent composer text for a session. Purely local: it never reaches the gateway and never syncs between
239
+ * clients, because a half-written prompt is not something anyone asked to publish.
240
+ */
241
+ declare function useDraft(client: WorkerDeckClient, sessionId: string | undefined): UseDraftResult;
242
+ //#endregion
658
243
  //#region src/hooks/use-profile-usage.d.ts
659
244
  type UseProfileUsageOptions = {
660
- /** How often to re-ask while enabled. Default 60s. */intervalMs?: number;
661
- /** Set false to hold the poll — a panel that is off screen has nothing to
662
- * refresh. Default true. */
245
+ intervalMs?: number;
663
246
  enabled?: boolean;
664
247
  };
665
248
  type UseProfileUsageResult = {
666
- /** The gateway's plan-usage state for this profile, or undefined when there
667
- * is none to have: no profile, an older gateway, or nothing reported yet.
668
- * Absent is **unknown, never 0%** — see `ProfileUsageWindow`. */
669
- usage: ProfileUsage | undefined; /** Ask again now. */
249
+ usage: ProfileUsage | undefined;
670
250
  refresh: () => void;
671
251
  };
672
- /**
673
- * The gateway's per-profile plan usage, over REST.
674
- *
675
- * The session's own event stream carries a `rate_limit` reading only when the
676
- * engine volunteers one — for claude that is at a turn's edges and nowhere else,
677
- * so a session idle since yesterday replays yesterday's number, and a session
678
- * opened today knows nothing of what a sibling on the same account spent an hour
679
- * ago. `GET /profiles` answers the account-wide question, which is why this is a
680
- * poll and not a subscription: nothing pushes it.
681
- *
682
- * Polling and not attaching, deliberately — a second WebSocket per surface is
683
- * exactly what the bridge's "asks the first attached client" rule forbids, and
684
- * this is one small GET a minute.
685
- *
686
- * Self-disabling on a 404, like {@link useHostFileSearch}: a gateway without the
687
- * route will never grow one mid-session, so stop asking rather than log a miss
688
- * every minute.
689
- */
690
252
  declare function useProfileUsage(client: WorkerDeckClient, profile: string | undefined, options?: UseProfileUsageOptions): UseProfileUsageResult;
691
253
  //#endregion
692
254
  //#region src/hooks/use-session-info.d.ts
693
255
  type UseSessionInfoResult = {
694
- info: SessionInfo | undefined; /** True until the first answer — distinguishes "still asking" from "no such session". */
695
- loading: boolean; /** Set when the gateway refused; `info` stays undefined. */
256
+ info: SessionInfo | undefined;
257
+ loading: boolean;
696
258
  error: string | undefined;
697
259
  };
698
- /**
699
- * The registry's record of one session, over REST.
700
- *
701
- * Separate from {@link useClaudeSession} on purpose: that hook attaches a
702
- * WebSocket and streams a transcript, which is far more than a caller needs to
703
- * know a session's `cwd` or title — and a second attach would be a second
704
- * client on the bridge, which is the one thing the bridge's "asks the first
705
- * attached client" rule cannot tolerate.
706
- *
707
- * Fetched once per session id. The record is registry state, not a live feed;
708
- * anything that changes during a run arrives on the session's event stream.
709
- */
710
260
  declare function useSessionInfo(client: WorkerDeckClient, sessionId: string | undefined): UseSessionInfoResult;
711
261
  //#endregion
712
262
  //#region src/lib/open-files.d.ts
713
- /**
714
- * One open file, in whatever state its read got to.
715
- *
716
- * A tab exists from the moment it is opened, before any bytes arrive — the tab
717
- * strip is the record of what the user asked for, not of what the gateway has
718
- * answered, and a tab that only appeared once the read landed would make a slow
719
- * read look like a dead click.
720
- */
721
263
  type OpenFile = {
722
- /** Absolute host path — the tab's identity. Opening the same path twice
723
- * focuses the existing tab rather than making a second one. */
724
- path: string; /** Last segment, for the tab label. */
264
+ path: string;
725
265
  name: string;
726
- status: 'loading' | 'ready' | 'binary' | 'error'; /** The text **as last seen on disk** — never the user's edits. */
266
+ status: 'loading' | 'ready' | 'binary' | 'error';
727
267
  content?: string;
728
- /**
729
- * The user's unsaved text. Absent when nothing has been typed since the last
730
- * read or save.
731
- *
732
- * Kept separate from `content` rather than overwriting it, because a
733
- * conditional write needs to know both: what is being sent, and what the
734
- * `hash` describes. Collapsing them would make "did this change?" unanswerable
735
- * after the first keystroke.
736
- */
737
268
  draft?: string;
738
269
  bytes?: number;
739
- /**
740
- * sha256 of the bytes `content` was read from — the `expectedHash` for the
741
- * next write.
742
- *
743
- * This is the whole safety mechanism: `/fs/write` is conditional *always*, so
744
- * a tab that lost its hash could not save at all without re-reading, and
745
- * re-reading to save is precisely the race the conditional write exists to
746
- * prevent.
747
- */
748
270
  hash?: string;
749
- modifiedAt?: number; /** Why the read failed, verbatim from the gateway. */
750
- error?: string; /** A write is in flight. */
751
- saving?: boolean; /** Why the last write failed, verbatim from the gateway. */
271
+ modifiedAt?: number;
272
+ error?: string;
273
+ saving?: boolean;
752
274
  saveError?: string;
753
- /**
754
- * The file changed on disk since this tab read it — the gateway answered 409.
755
- *
756
- * Held as a distinct flag rather than folded into `saveError` because it is
757
- * the one failure with a *choice* attached (reload, overwrite, keep editing)
758
- * rather than a message to read.
759
- */
760
275
  conflict?: boolean;
761
276
  };
762
- /** Whether a tab has edits that are not on disk. Derived, so typing something
763
- * and undoing it back leaves the tab clean — which is what an editor should do
764
- * and what a boolean flag set on first keystroke would get wrong. */
765
277
  declare function isDirty(file: OpenFile): boolean;
766
- /** What a tab would write: its edits if it has any, else what it read. */
767
278
  declare function currentText(file: OpenFile): string;
768
279
  type OpenFilesState = {
769
- /** Tab order, left to right. */files: OpenFile[]; /** Absolute path of the focused tab, or undefined when nothing is open. */
280
+ files: OpenFile[];
770
281
  activePath?: string;
771
282
  };
772
283
  type OpenFilesAction = {
@@ -780,7 +291,7 @@ type OpenFilesAction = {
780
291
  } | {
781
292
  type: 'activate';
782
293
  path: string;
783
- } /** A read landed. Ignored if the tab was closed while it was in flight. */ | {
294
+ } | {
784
295
  type: 'loaded';
785
296
  path: string;
786
297
  content: string;
@@ -792,20 +303,17 @@ type OpenFilesAction = {
792
303
  type: 'failed';
793
304
  path: string;
794
305
  error: string;
795
- } /** The user typed. */ | {
306
+ } | {
796
307
  type: 'edit';
797
308
  path: string;
798
309
  content: string;
799
- } /** Throw away unsaved edits and go back to what was read. */ | {
310
+ } | {
800
311
  type: 'revert';
801
312
  path: string;
802
313
  } | {
803
314
  type: 'saveStart';
804
315
  path: string;
805
- }
806
- /** A write succeeded. `content` is **what was written**, not what the tab
807
- * holds now — the user may have kept typing while it was in flight. */
808
- | {
316
+ } | {
809
317
  type: 'saved';
810
318
  path: string;
811
319
  content: string;
@@ -817,72 +325,31 @@ type OpenFilesAction = {
817
325
  path: string;
818
326
  error: string;
819
327
  conflict?: boolean;
820
- } /** Dismiss the conflict banner and carry on editing. */ | {
328
+ } | {
821
329
  type: 'dismissConflict';
822
330
  path: string;
823
331
  };
824
332
  declare const initialOpenFilesState: OpenFilesState;
825
- /**
826
- * The tab strip and the editor's whole behaviour, as a pure function.
827
- *
828
- * The rules worth stating, because they are the ones a naive implementation
829
- * gets wrong:
830
- *
831
- * - **Opening an open path never re-reads it.** It focuses the tab. Re-reading
832
- * would silently discard that tab's unsaved edits on a double click.
833
- * - **Closing the focused tab focuses its right-hand neighbour**, falling back
834
- * to the left when it was last. Focusing "the first tab" instead is what makes
835
- * closing several tabs in a row jump the user around.
836
- * - **A successful save is applied against the text that was sent**, not against
837
- * the tab's current text. Typing during a save is normal; treating the write's
838
- * completion as "the tab is now clean" would silently drop those keystrokes.
839
- * - **Nothing here discards edits implicitly.** `revert` and `loaded` are the
840
- * only two things that clear a draft, and both are the direct result of
841
- * someone asking for it. The conditional write exists so a browser edit cannot
842
- * clobber the agent mid-run; this holds the same line in the other direction.
843
- *
844
- * Late results are addressed by path and dropped if that tab is gone, so a slow
845
- * read of a closed file cannot resurrect it.
846
- */
847
333
  declare function openFilesReducer(state: OpenFilesState, action: OpenFilesAction): OpenFilesState;
848
334
  //#endregion
849
335
  //#region src/hooks/use-open-files.d.ts
850
336
  type UseOpenFilesResult = OpenFilesState & {
851
- /** The focused file, resolved — what the editor renders. */active: OpenFile | undefined; /** Any tab with unsaved edits — what a close or unload guard asks. */
852
- hasUnsaved: boolean; /** Open a path, or focus it if it is already open. */
337
+ active: OpenFile | undefined;
338
+ hasUnsaved: boolean;
853
339
  open: (path: string) => void;
854
340
  close: (path: string) => void;
855
341
  closeAll: () => void;
856
- activate: (path: string) => void; /** Record a keystroke. Pure state; nothing is written until `save`. */
857
- edit: (path: string, content: string) => void; /** Write the tab's edits, conditional on the hash it read. No-op if clean. */
858
- save: (path: string) => Promise<void>; /** Throw the tab's edits away and go back to what was read. */
342
+ activate: (path: string) => void;
343
+ edit: (path: string, content: string) => void;
344
+ save: (path: string) => Promise<void>;
859
345
  revert: (path: string) => void;
860
- /** Re-read from disk. **Discards unsaved edits** — only call on an explicit
861
- * choice, never to "refresh". */
862
346
  reload: (path: string) => void;
863
- /** Resolve a conflict by taking this tab's version: re-read for the current
864
- * hash, then write the draft against it. */
865
- overwrite: (path: string) => Promise<void>; /** Dismiss the conflict banner without resolving it. */
347
+ overwrite: (path: string) => Promise<void>;
866
348
  dismissConflict: (path: string) => void;
867
349
  };
868
- /**
869
- * The open-file tabs of a workspace: which files are open, which one is focused,
870
- * the bytes behind each, and the edits on top of them.
871
- *
872
- * Reads are fired from an effect keyed on "which tabs are still loading" rather
873
- * than from `open` itself, so the reducer stays pure and a tab that was opened,
874
- * closed and reopened does not carry a stale in-flight request with it.
875
- *
876
- * Deliberately **not** given the session's cwd: a tab is an absolute host path,
877
- * and where it came from — the tree, a search hit, a path in the transcript — is
878
- * the caller's business. Containment is the server's job on every `/fs/read` and
879
- * `/fs/write`, not something re-derived here from a directory this hook would
880
- * have to trust.
881
- */
882
350
  declare function useOpenFiles(client: WorkerDeckClient): UseOpenFilesResult;
883
351
  //#endregion
884
352
  //#region src/lib/tool-host.d.ts
885
- /** What the host was asked to do and how it went (for UI/telemetry). */
886
353
  type ToolHostExecution = {
887
354
  executionId: string;
888
355
  toolName: string;
@@ -898,145 +365,56 @@ type ToolHostRunner = (request: {
898
365
  memoryLimitBytes: number;
899
366
  signal: AbortSignal;
900
367
  }) => Promise<RunScriptResult>;
901
- /**
902
- * Result a client tool handler returns. Return a plain value and it is sent as
903
- * JSON; return an object with `error` to fail the call with a reason the agent
904
- * can adapt to.
905
- */
906
368
  type ClientToolResult = {
907
369
  value: unknown;
908
370
  } | {
909
371
  error: string;
910
372
  reason?: string;
911
373
  };
912
- /**
913
- * Handler for a client-registered tool. Receives the model's validated input
914
- * and returns a result — or throws, which is treated as a host error.
915
- */
916
374
  type ClientToolHandler = (input: unknown, context: {
917
375
  executionId: string;
918
376
  signal: AbortSignal;
919
377
  }) => ClientToolResult | Promise<ClientToolResult>;
920
378
  type ToolCallHostOptions = {
921
- /** Tools this client will execute. Anything else is refused, so a server can
922
- * never talk this tab into running something it didn't opt into.
923
- * Default: `['eval_script']`. */
924
379
  tools?: string[];
925
- /**
926
- * Client-side tool handlers, keyed by tool name. When a `tool_call_request`
927
- * arrives for a name in this map, the handler is called instead of the
928
- * sandbox. The tool must also appear in {@link tools} (it is added
929
- * automatically when `clientTools` is set).
930
- *
931
- * This is the client half of the round trip — the server half is registering
932
- * the tool's schema (via `tools` on `ProviderRunnerOptions` or
933
- * `EngineSessionOptions`). Together they let an embedder define a tool the
934
- * model can call and the client handles:
935
- *
936
- * ```ts
937
- * // Server: register the schema
938
- * tools: { app_navigate: { trust: 'sandboxed', tool: tool({ ... }) } }
939
- * // Client: handle the call
940
- * <SessionPanel clientTools={{ app_navigate: (input) => ({ value: 'ok' }) }} />
941
- * ```
942
- */
943
- clientTools?: Record<string, ClientToolHandler>; /** Guest wall-clock limit, unless the request asks for less. Default 5000. */
944
- timeoutMs?: number; /** Guest allocator cap, unless the request asks for less. Default 64 MiB. */
380
+ clientTools?: Record<string, ClientToolHandler>;
381
+ timeoutMs?: number;
945
382
  memoryLimitBytes?: number;
946
- /**
947
- * Load the WASM guest engine. Called at most once, on the first bridged call
948
- * — nothing is downloaded or parsed until a session actually bridges one.
949
- * Defaults to `@workerdeck/sandbox` with the single-file browser build.
950
- */
951
383
  loadEngine?: () => Promise<SandboxEngine>;
952
- /**
953
- * Run the script. Defaults to executing on this thread, which is fine for the
954
- * short, time-boxed evaluations this is built for. Supply your own (a Web
955
- * Worker running the same engine) to keep long evaluations off the UI thread
956
- * — the guest deadline preempts the interpreter, but only between bytecode
957
- * ops on whichever thread it runs on.
958
- */
959
- execute?: ToolHostRunner; /** Host-gated fetch for the guest. Omitted = the guest has no network at all. */
960
- fetchText?: (url: string) => Promise<string>; /** Observe executions (rendering, logging). */
384
+ execute?: ToolHostRunner;
385
+ fetchText?: (url: string) => Promise<string>;
961
386
  onExecution?: (execution: ToolHostExecution) => void;
962
387
  };
963
- /**
964
- * Answers server-bridged tool calls by executing them in this browser tab.
965
- * Framework-free — {@link useToolCallHost} is a thin React wrapper.
966
- *
967
- * The point is data locality: documents fetched or held client-side can be
968
- * evaluated here and never touch the server. The engine loads lazily, so a page
969
- * that never bridges a call never pays for the WASM guest.
970
- */
971
388
  declare function createToolCallHost(handle: SessionHandle, options?: ToolCallHostOptions): {
972
389
  dispose: () => void;
973
390
  };
974
391
  //#endregion
975
392
  //#region src/hooks/use-tool-host.d.ts
976
393
  type UseToolCallHostOptions = ToolCallHostOptions & {
977
- /** Turn the host off without unmounting. Default true. */enabled?: boolean; /** How many recent executions to keep for rendering. Default 50. */
394
+ enabled?: boolean;
978
395
  historyLimit?: number;
979
396
  };
980
- /**
981
- * React wrapper around {@link createToolCallHost}: subscribes while mounted and
982
- * exposes recent executions for rendering. All the logic lives in the
983
- * framework-free host — this only manages the subscription's lifetime.
984
- */
985
397
  declare function useToolCallHost(handle: SessionHandle | undefined, options?: UseToolCallHostOptions): {
986
398
  executions: ToolHostExecution[];
987
399
  };
988
400
  //#endregion
989
401
  //#region src/lib/recap.d.ts
990
- /**
991
- * "What happened while you were away", counted rather than written.
992
- *
993
- * Deterministic on purpose. A prose recap would mean spending a turn — tokens,
994
- * context and latency — on a summary nobody asked the model for, and it would
995
- * be wrong in the one case that matters most (a session that failed while
996
- * unattended, where the model is exactly who you shouldn't ask). Everything
997
- * here is already in the transcript; this only counts it.
998
- *
999
- * Framework-free and pure, like the reducer it reads from: both clients render
1000
- * the same recap from the same numbers.
1001
- */
1002
402
  type RecapSummary = {
1003
- /** Completed turns — `turn_result` rows, the engine's own unit of work. */turns: number; /** Messages the model wrote. Streaming ones count: they are on screen. */
1004
- replies: number; /** Tool calls started, and the distinct names, most-used first. */
403
+ turns: number;
404
+ replies: number;
1005
405
  tools: number;
1006
- toolNames: string[]; /** Files the agent handed over (`file_delivered`). */
406
+ toolNames: string[];
1007
407
  files: number;
1008
- /** Failed turns and failed tool calls, together — what you'd want to know
1009
- * first on coming back. */
1010
408
  errors: number;
1011
- /** Approvals still waiting. Not a count of what happened, but the reason to
1012
- * look now rather than later. */
1013
- pending: number; /** Any of the above non-zero. A recap of nothing is noise. */
409
+ pending: number;
1014
410
  any: boolean;
1015
411
  };
1016
- /** The `TranscriptState` fields a recap reads — structural, so a caller can
1017
- * pass the whole state or just these. */
1018
412
  type RecapInput = {
1019
413
  items: readonly TranscriptItem[];
1020
414
  pendingApprovals?: readonly unknown[];
1021
415
  };
1022
- /**
1023
- * Summarize the items from `fromIndex` onward — the boundary being the number
1024
- * of items that existed when the session was last looked at.
1025
- *
1026
- * An out-of-range boundary is clamped rather than rejected: a transcript can
1027
- * *shrink* (a `/clear`, a fresh attach after a compaction), and the honest
1028
- * reading of "you last saw 40 items, there are now 12" is "everything here is
1029
- * new", not a negative count.
1030
- */
1031
416
  declare function summarizeSince(state: RecapInput, fromIndex: number): RecapSummary;
1032
- /**
1033
- * The recap as one line of text, in the order a person reads it: what got done,
1034
- * what it used, what went wrong, what is waiting.
1035
- *
1036
- * Returns `undefined` when there is nothing to say, so a caller can render the
1037
- * row or not on the value alone.
1038
- */
1039
417
  declare function recapLine(summary: RecapSummary): string | undefined;
1040
418
  //#endregion
1041
- export { type AttachmentKind, type ClientForHost, type ClientToolHandler, type ClientToolResult, type ConnectionState, type HostDirState, type HostTreeRow, type OpenFile, type OpenFilesAction, type OpenFilesState, type ProducedFileRef, type PromptToken, REPLAY_HOLD_MAX_MS, type RecapInput, type RecapSummary, type StagedAttachment, type ToolCallHostOptions, type ToolHostExecution, type ToolHostRunner, type TranscriptItem, type TranscriptState, type UseAttachmentsOptions, type UseAttachmentsResult, type UseClaudeSessionOptions, type UseClaudeSessionResult, type UseHostFileRootsResult, type UseHostFileSearchResult, type UseHostFileTreeResult, type UseOpenFilesResult, type UseProfileUsageOptions, type UseProfileUsageResult, type UseSessionInfoResult, type UseToolCallHostOptions, ancestorsWithin, applyEvent, attachmentKind, clearTranscriptCache, createToolCallHost, currentText, flattenHostTree, hydrateToolResult, initialOpenFilesState, initialReplayTarget, initialTranscriptState, isDirty, openFilesReducer, rateLimitWindows, recapLine, scanPromptTokens, seedFromSessionInfo, staleAttach, summarizeSince, useAttachments, useClaudeSession, useHostFileRoots, useHostFileSearch, useHostFileTree, useOpenFiles, useProfileUsage, useProjectIcons, useSessionInfo, useToolCallHost };
419
+ export { type AttachmentKind, type ClientForHost, type ClientToolHandler, type ClientToolResult, type ConnectionState, type HostDirState, type HostTreeRow, type OpenFile, type OpenFilesAction, type OpenFilesState, type ProducedFileRef, type PromptToken, REPLAY_HOLD_MAX_MS, type RecapInput, type RecapSummary, type StagedAttachment, type ToolCallHostOptions, type ToolHostExecution, type ToolHostRunner, type ToolResultImageRef, type TranscriptItem, type TranscriptState, type UseAttachmentsOptions, type UseAttachmentsResult, type UseClaudeSessionOptions, type UseClaudeSessionResult, type UseDraftResult, type UseHostFileRootsResult, type UseHostFileSearchResult, type UseHostFileTreeResult, type UseOpenFilesResult, type UseProfileUsageOptions, type UseProfileUsageResult, type UseSessionInfoResult, type UseToolCallHostOptions, ancestorsWithin, applyEvent, attachmentKind, clearDrafts, clearProfileUsageCache, clearTranscriptCache, createToolCallHost, currentText, flattenHostTree, hydrateToolResult, initialOpenFilesState, initialReplayTarget, initialTranscriptState, isDirty, openFilesReducer, rateLimitWindows, recapLine, scanPromptTokens, seedFromSessionInfo, staleAttach, summarizeSince, useAttachments, useClaudeSession, useDraft, useHostFileRoots, useHostFileSearch, useHostFileTree, useOpenFiles, useProfileUsage, useProjectIcons, useSessionInfo, useToolCallHost };
1042
420
  //# sourceMappingURL=index.d.mts.map