@workerdeck/react 0.22.0 → 1.0.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 +100 -681
- package/build/index.mjs +260 -525
- package/build/index.mjs.map +1 -1
- package/package.json +6 -6
package/build/index.d.mts
CHANGED
|
@@ -3,26 +3,17 @@ import { SessionHandle, WorkerDeckClient } from "@workerdeck/client";
|
|
|
3
3
|
import { RunScriptResult, SandboxEngine, SandboxVfs } from "@workerdeck/sandbox";
|
|
4
4
|
|
|
5
5
|
//#region src/lib/transcript.d.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
type ToolResultImageRef = {
|
|
7
|
+
partIndex: number;
|
|
8
|
+
mediaType: string;
|
|
9
|
+
bytes: number;
|
|
10
|
+
sourceSeq: number;
|
|
11
|
+
};
|
|
10
12
|
type TranscriptItem = {
|
|
11
13
|
kind: 'user';
|
|
12
14
|
id: string;
|
|
13
15
|
text: string;
|
|
14
16
|
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
17
|
parentToolUseId?: string;
|
|
27
18
|
} | {
|
|
28
19
|
kind: 'assistant_text';
|
|
@@ -41,82 +32,19 @@ type TranscriptItem = {
|
|
|
41
32
|
name: string;
|
|
42
33
|
input: unknown;
|
|
43
34
|
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
35
|
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
36
|
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
37
|
result?: {
|
|
81
38
|
text: string;
|
|
82
39
|
isError: boolean;
|
|
83
40
|
truncated?: boolean;
|
|
84
41
|
totalChars?: number;
|
|
85
42
|
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
|
-
}>;
|
|
43
|
+
images?: ReadonlyArray<ToolResultImageRef>;
|
|
107
44
|
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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). */
|
|
45
|
+
patch?: FilePatch;
|
|
46
|
+
executionId?: string;
|
|
47
|
+
backend?: ToolExecutionBackend;
|
|
120
48
|
logs?: string[];
|
|
121
49
|
} | {
|
|
122
50
|
kind: 'turn_result';
|
|
@@ -131,18 +59,13 @@ type TranscriptItem = {
|
|
|
131
59
|
id: string;
|
|
132
60
|
level: 'info' | 'error';
|
|
133
61
|
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
|
-
| {
|
|
62
|
+
} | {
|
|
139
63
|
kind: 'file_delivered';
|
|
140
64
|
id: string;
|
|
141
65
|
path: string;
|
|
142
66
|
bytes: number;
|
|
143
67
|
description?: string;
|
|
144
68
|
};
|
|
145
|
-
/** A `file_produced` announcement, as the transcript keeps it. */
|
|
146
69
|
type ProducedFileRef = {
|
|
147
70
|
fileId: string;
|
|
148
71
|
mediaType?: string;
|
|
@@ -154,62 +77,18 @@ type TranscriptState = {
|
|
|
154
77
|
model?: string;
|
|
155
78
|
cwd?: string;
|
|
156
79
|
sdkSessionId?: string;
|
|
157
|
-
/** Engine running the session, from the attach snapshot. Gates CLI-only
|
|
158
|
-
* affordances; absent (an older server) reads as 'claude'. */
|
|
159
80
|
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
81
|
capabilities: EngineCapabilities;
|
|
168
|
-
|
|
169
|
-
|
|
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). */
|
|
82
|
+
session?: SessionInfo;
|
|
83
|
+
models?: ModelOption[];
|
|
176
84
|
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
85
|
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
86
|
producedFiles?: Record<string, ProducedFileRef>;
|
|
195
|
-
|
|
196
|
-
|
|
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. */
|
|
87
|
+
defaultModel?: string;
|
|
88
|
+
permissionMode?: PermissionMode;
|
|
200
89
|
contextUsage?: ContextUsage;
|
|
201
|
-
/** Latest rate-limit snapshot per window ('five_hour', 'seven_day', ...).
|
|
202
|
-
* Absent for API-key sessions — render nothing, not 0%. */
|
|
203
90
|
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
91
|
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
92
|
subscriptionType?: string;
|
|
214
93
|
items: TranscriptItem[];
|
|
215
94
|
pendingApprovals: PermissionRequest[];
|
|
@@ -217,556 +96,191 @@ type TranscriptState = {
|
|
|
217
96
|
lastSeq: number;
|
|
218
97
|
};
|
|
219
98
|
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
99
|
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
100
|
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
101
|
declare function hydrateToolResult(state: TranscriptState, toolUseId: string, text: string): TranscriptState;
|
|
253
102
|
declare function applyEvent(state: TranscriptState, event: SessionEvent): TranscriptState;
|
|
254
103
|
//#endregion
|
|
255
104
|
//#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
105
|
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
106
|
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
107
|
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
108
|
declare const REPLAY_HOLD_MAX_MS = 1500;
|
|
329
109
|
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
110
|
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
111
|
cacheTranscript?: boolean;
|
|
348
112
|
};
|
|
349
113
|
type UseClaudeSessionResult = {
|
|
350
114
|
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
115
|
connected: boolean;
|
|
354
116
|
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
117
|
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
118
|
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
119
|
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
120
|
effectiveModel?: string;
|
|
379
|
-
|
|
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. */
|
|
121
|
+
handle: SessionHandle | undefined;
|
|
384
122
|
send: (text: string, attachmentIds?: string[]) => void;
|
|
385
123
|
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
124
|
deny: (requestId: string, message?: string, interrupt?: boolean) => void;
|
|
389
125
|
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
126
|
clearContext: () => void;
|
|
397
127
|
setPermissionMode: (mode: PermissionMode) => void;
|
|
398
128
|
setModel: (model?: string) => void;
|
|
399
|
-
closeSession: () => void;
|
|
129
|
+
closeSession: () => void;
|
|
400
130
|
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
131
|
loadFullResult: (toolUseId: string) => Promise<boolean>;
|
|
412
132
|
};
|
|
413
|
-
/** Attach to a session and maintain live transcript state. Detaches on unmount. */
|
|
414
133
|
declare function useClaudeSession(client: WorkerDeckClient, sessionId: string | undefined, options?: UseClaudeSessionOptions): UseClaudeSessionResult;
|
|
415
134
|
//#endregion
|
|
416
135
|
//#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
136
|
declare function clearTranscriptCache(): void;
|
|
423
137
|
//#endregion
|
|
138
|
+
//#region src/lib/profile-usage-cache.d.ts
|
|
139
|
+
declare function clearProfileUsageCache(): void;
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/lib/draft-store.d.ts
|
|
142
|
+
declare function clearDrafts(): void;
|
|
143
|
+
//#endregion
|
|
424
144
|
//#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
145
|
type StagedAttachment = {
|
|
435
|
-
|
|
146
|
+
key: string;
|
|
436
147
|
name: string;
|
|
437
148
|
mediaType: string;
|
|
438
|
-
bytes: number;
|
|
149
|
+
bytes: number;
|
|
439
150
|
previewUrl?: string;
|
|
440
|
-
status: 'uploading' | 'ready' | 'failed';
|
|
441
|
-
id?: string;
|
|
151
|
+
status: 'uploading' | 'ready' | 'failed';
|
|
152
|
+
id?: string;
|
|
442
153
|
error?: string;
|
|
443
154
|
};
|
|
444
|
-
/** The kind vocabulary of {@link EngineCapabilities.attachments}. */
|
|
445
155
|
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
156
|
declare function attachmentKind(mediaType: string): AttachmentKind | undefined;
|
|
452
157
|
type UseAttachmentsOptions = {
|
|
453
|
-
/** The session's capability record — its `attachments` list decides which
|
|
454
|
-
* kinds are offered and which are refused locally. */
|
|
455
158
|
capabilities: EngineCapabilities;
|
|
456
|
-
/** Named in a local refusal, so "the codex engine does not take pdf
|
|
457
|
-
* attachments" says which engine meant it. */
|
|
458
159
|
engine?: ProfileEngine;
|
|
459
160
|
};
|
|
460
161
|
type UseAttachmentsResult = {
|
|
461
|
-
items: StagedAttachment[];
|
|
462
|
-
readyIds: string[];
|
|
463
|
-
uploading: boolean;
|
|
464
|
-
hasFailure: boolean;
|
|
162
|
+
items: StagedAttachment[];
|
|
163
|
+
readyIds: string[];
|
|
164
|
+
uploading: boolean;
|
|
165
|
+
hasFailure: boolean;
|
|
465
166
|
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
167
|
disabled: boolean;
|
|
469
168
|
add: (files: Iterable<File>) => void;
|
|
470
169
|
retry: (key: string) => void;
|
|
471
170
|
remove: (key: string) => void;
|
|
472
|
-
clear: () => void;
|
|
171
|
+
clear: () => void;
|
|
473
172
|
error?: string;
|
|
474
173
|
dismissError: () => void;
|
|
475
174
|
};
|
|
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
175
|
declare function useAttachments(client: WorkerDeckClient, sessionId: string | undefined, {
|
|
485
176
|
capabilities,
|
|
486
177
|
engine
|
|
487
178
|
}: UseAttachmentsOptions): UseAttachmentsResult;
|
|
488
179
|
//#endregion
|
|
489
180
|
//#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
181
|
type PromptToken = {
|
|
505
|
-
kind: 'file' | 'command';
|
|
182
|
+
kind: 'file' | 'command';
|
|
506
183
|
start: number;
|
|
507
184
|
end: number;
|
|
508
185
|
text: string;
|
|
509
186
|
};
|
|
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
187
|
declare function scanPromptTokens(text: string): PromptToken[];
|
|
517
188
|
//#endregion
|
|
518
189
|
//#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
190
|
type HostDirState = {
|
|
528
|
-
entries: HostDirEntry[];
|
|
191
|
+
entries: HostDirEntry[];
|
|
529
192
|
truncated?: boolean;
|
|
530
193
|
};
|
|
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
194
|
type HostTreeRow = {
|
|
534
|
-
entry: HostDirEntry;
|
|
535
|
-
depth: number;
|
|
536
|
-
expanded?: boolean;
|
|
537
|
-
loading?: boolean;
|
|
195
|
+
entry: HostDirEntry;
|
|
196
|
+
depth: number;
|
|
197
|
+
expanded?: boolean;
|
|
198
|
+
loading?: boolean;
|
|
538
199
|
truncated?: boolean;
|
|
539
200
|
};
|
|
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
201
|
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
202
|
declare function ancestorsWithin(root: string, path: string): string[];
|
|
565
203
|
//#endregion
|
|
566
204
|
//#region src/hooks/use-host-files.d.ts
|
|
567
205
|
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
206
|
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
207
|
search: (query: string, options?: {
|
|
581
208
|
limit?: number;
|
|
582
209
|
signal?: AbortSignal;
|
|
583
210
|
}) => Promise<HostFileMatch[]>;
|
|
584
211
|
};
|
|
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
212
|
declare function useHostFileSearch(client: WorkerDeckClient, cwd: string | undefined): UseHostFileSearchResult;
|
|
599
213
|
type UseHostFileRootsResult = {
|
|
600
|
-
|
|
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
|
-
*/
|
|
214
|
+
available: boolean;
|
|
609
215
|
canWrite: boolean;
|
|
610
216
|
};
|
|
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
217
|
declare function useHostFileRoots(client: WorkerDeckClient): UseHostFileRootsResult;
|
|
619
218
|
type UseHostFileTreeResult = {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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. */
|
|
219
|
+
available: boolean;
|
|
220
|
+
root: string | undefined;
|
|
221
|
+
rows: HostTreeRow[];
|
|
222
|
+
loading: boolean;
|
|
223
|
+
error: string | undefined;
|
|
224
|
+
toggle: (path: string) => void;
|
|
225
|
+
reveal: (path: string) => void;
|
|
633
226
|
refresh: (path?: string) => void;
|
|
634
227
|
};
|
|
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
228
|
declare function useHostFileTree(client: WorkerDeckClient, cwd: string | undefined): UseHostFileTreeResult;
|
|
653
229
|
//#endregion
|
|
654
230
|
//#region src/hooks/use-project-icons.d.ts
|
|
655
231
|
type ClientForHost = (hostId: string) => WorkerDeckClient | undefined;
|
|
656
232
|
declare function useProjectIcons(rows: readonly SessionRow[], clientFor: ClientForHost): Record<string, string>;
|
|
657
233
|
//#endregion
|
|
234
|
+
//#region src/hooks/use-draft.d.ts
|
|
235
|
+
type UseDraftResult = {
|
|
236
|
+
/** What was left unsent last time, read once so it can seed the composer on mount. */initialText: string;
|
|
237
|
+
save: (text: string) => void;
|
|
238
|
+
clear: () => void;
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* Remember unsent composer text for a session. Purely local: it never reaches the gateway and never syncs between
|
|
242
|
+
* clients, because a half-written prompt is not something anyone asked to publish.
|
|
243
|
+
*/
|
|
244
|
+
declare function useDraft(client: WorkerDeckClient, sessionId: string | undefined): UseDraftResult;
|
|
245
|
+
//#endregion
|
|
658
246
|
//#region src/hooks/use-profile-usage.d.ts
|
|
659
247
|
type UseProfileUsageOptions = {
|
|
660
|
-
|
|
661
|
-
/** Set false to hold the poll — a panel that is off screen has nothing to
|
|
662
|
-
* refresh. Default true. */
|
|
248
|
+
intervalMs?: number;
|
|
663
249
|
enabled?: boolean;
|
|
664
250
|
};
|
|
665
251
|
type UseProfileUsageResult = {
|
|
666
|
-
|
|
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. */
|
|
252
|
+
usage: ProfileUsage | undefined;
|
|
670
253
|
refresh: () => void;
|
|
671
254
|
};
|
|
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
255
|
declare function useProfileUsage(client: WorkerDeckClient, profile: string | undefined, options?: UseProfileUsageOptions): UseProfileUsageResult;
|
|
691
256
|
//#endregion
|
|
692
257
|
//#region src/hooks/use-session-info.d.ts
|
|
693
258
|
type UseSessionInfoResult = {
|
|
694
|
-
info: SessionInfo | undefined;
|
|
695
|
-
loading: boolean;
|
|
259
|
+
info: SessionInfo | undefined;
|
|
260
|
+
loading: boolean;
|
|
696
261
|
error: string | undefined;
|
|
697
262
|
};
|
|
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
263
|
declare function useSessionInfo(client: WorkerDeckClient, sessionId: string | undefined): UseSessionInfoResult;
|
|
711
264
|
//#endregion
|
|
712
265
|
//#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
266
|
type OpenFile = {
|
|
722
|
-
|
|
723
|
-
* focuses the existing tab rather than making a second one. */
|
|
724
|
-
path: string; /** Last segment, for the tab label. */
|
|
267
|
+
path: string;
|
|
725
268
|
name: string;
|
|
726
|
-
status: 'loading' | 'ready' | 'binary' | 'error';
|
|
269
|
+
status: 'loading' | 'ready' | 'binary' | 'error';
|
|
727
270
|
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
271
|
draft?: string;
|
|
738
272
|
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
273
|
hash?: string;
|
|
749
|
-
modifiedAt?: number;
|
|
750
|
-
error?: string;
|
|
751
|
-
saving?: boolean;
|
|
274
|
+
modifiedAt?: number;
|
|
275
|
+
error?: string;
|
|
276
|
+
saving?: boolean;
|
|
752
277
|
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
278
|
conflict?: boolean;
|
|
761
279
|
};
|
|
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
280
|
declare function isDirty(file: OpenFile): boolean;
|
|
766
|
-
/** What a tab would write: its edits if it has any, else what it read. */
|
|
767
281
|
declare function currentText(file: OpenFile): string;
|
|
768
282
|
type OpenFilesState = {
|
|
769
|
-
|
|
283
|
+
files: OpenFile[];
|
|
770
284
|
activePath?: string;
|
|
771
285
|
};
|
|
772
286
|
type OpenFilesAction = {
|
|
@@ -780,7 +294,7 @@ type OpenFilesAction = {
|
|
|
780
294
|
} | {
|
|
781
295
|
type: 'activate';
|
|
782
296
|
path: string;
|
|
783
|
-
}
|
|
297
|
+
} | {
|
|
784
298
|
type: 'loaded';
|
|
785
299
|
path: string;
|
|
786
300
|
content: string;
|
|
@@ -792,20 +306,17 @@ type OpenFilesAction = {
|
|
|
792
306
|
type: 'failed';
|
|
793
307
|
path: string;
|
|
794
308
|
error: string;
|
|
795
|
-
}
|
|
309
|
+
} | {
|
|
796
310
|
type: 'edit';
|
|
797
311
|
path: string;
|
|
798
312
|
content: string;
|
|
799
|
-
}
|
|
313
|
+
} | {
|
|
800
314
|
type: 'revert';
|
|
801
315
|
path: string;
|
|
802
316
|
} | {
|
|
803
317
|
type: 'saveStart';
|
|
804
318
|
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
|
-
| {
|
|
319
|
+
} | {
|
|
809
320
|
type: 'saved';
|
|
810
321
|
path: string;
|
|
811
322
|
content: string;
|
|
@@ -817,72 +328,31 @@ type OpenFilesAction = {
|
|
|
817
328
|
path: string;
|
|
818
329
|
error: string;
|
|
819
330
|
conflict?: boolean;
|
|
820
|
-
}
|
|
331
|
+
} | {
|
|
821
332
|
type: 'dismissConflict';
|
|
822
333
|
path: string;
|
|
823
334
|
};
|
|
824
335
|
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
336
|
declare function openFilesReducer(state: OpenFilesState, action: OpenFilesAction): OpenFilesState;
|
|
848
337
|
//#endregion
|
|
849
338
|
//#region src/hooks/use-open-files.d.ts
|
|
850
339
|
type UseOpenFilesResult = OpenFilesState & {
|
|
851
|
-
|
|
852
|
-
hasUnsaved: boolean;
|
|
340
|
+
active: OpenFile | undefined;
|
|
341
|
+
hasUnsaved: boolean;
|
|
853
342
|
open: (path: string) => void;
|
|
854
343
|
close: (path: string) => void;
|
|
855
344
|
closeAll: () => void;
|
|
856
|
-
activate: (path: string) => void;
|
|
857
|
-
edit: (path: string, content: string) => void;
|
|
858
|
-
save: (path: string) => Promise<void>;
|
|
345
|
+
activate: (path: string) => void;
|
|
346
|
+
edit: (path: string, content: string) => void;
|
|
347
|
+
save: (path: string) => Promise<void>;
|
|
859
348
|
revert: (path: string) => void;
|
|
860
|
-
/** Re-read from disk. **Discards unsaved edits** — only call on an explicit
|
|
861
|
-
* choice, never to "refresh". */
|
|
862
349
|
reload: (path: string) => void;
|
|
863
|
-
|
|
864
|
-
* hash, then write the draft against it. */
|
|
865
|
-
overwrite: (path: string) => Promise<void>; /** Dismiss the conflict banner without resolving it. */
|
|
350
|
+
overwrite: (path: string) => Promise<void>;
|
|
866
351
|
dismissConflict: (path: string) => void;
|
|
867
352
|
};
|
|
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
353
|
declare function useOpenFiles(client: WorkerDeckClient): UseOpenFilesResult;
|
|
883
354
|
//#endregion
|
|
884
355
|
//#region src/lib/tool-host.d.ts
|
|
885
|
-
/** What the host was asked to do and how it went (for UI/telemetry). */
|
|
886
356
|
type ToolHostExecution = {
|
|
887
357
|
executionId: string;
|
|
888
358
|
toolName: string;
|
|
@@ -898,107 +368,56 @@ type ToolHostRunner = (request: {
|
|
|
898
368
|
memoryLimitBytes: number;
|
|
899
369
|
signal: AbortSignal;
|
|
900
370
|
}) => Promise<RunScriptResult>;
|
|
371
|
+
type ClientToolResult = {
|
|
372
|
+
value: unknown;
|
|
373
|
+
} | {
|
|
374
|
+
error: string;
|
|
375
|
+
reason?: string;
|
|
376
|
+
};
|
|
377
|
+
type ClientToolHandler = (input: unknown, context: {
|
|
378
|
+
executionId: string;
|
|
379
|
+
signal: AbortSignal;
|
|
380
|
+
}) => ClientToolResult | Promise<ClientToolResult>;
|
|
901
381
|
type ToolCallHostOptions = {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
tools?: string[]; /** Guest wall-clock limit, unless the request asks for less. Default 5000. */
|
|
906
|
-
timeoutMs?: number; /** Guest allocator cap, unless the request asks for less. Default 64 MiB. */
|
|
382
|
+
tools?: string[];
|
|
383
|
+
clientTools?: Record<string, ClientToolHandler>;
|
|
384
|
+
timeoutMs?: number;
|
|
907
385
|
memoryLimitBytes?: number;
|
|
908
|
-
/**
|
|
909
|
-
* Load the WASM guest engine. Called at most once, on the first bridged call
|
|
910
|
-
* — nothing is downloaded or parsed until a session actually bridges one.
|
|
911
|
-
* Defaults to `@workerdeck/sandbox` with the single-file browser build.
|
|
912
|
-
*/
|
|
913
386
|
loadEngine?: () => Promise<SandboxEngine>;
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
* short, time-boxed evaluations this is built for. Supply your own (a Web
|
|
917
|
-
* Worker running the same engine) to keep long evaluations off the UI thread
|
|
918
|
-
* — the guest deadline preempts the interpreter, but only between bytecode
|
|
919
|
-
* ops on whichever thread it runs on.
|
|
920
|
-
*/
|
|
921
|
-
execute?: ToolHostRunner; /** Host-gated fetch for the guest. Omitted = the guest has no network at all. */
|
|
922
|
-
fetchText?: (url: string) => Promise<string>; /** Observe executions (rendering, logging). */
|
|
387
|
+
execute?: ToolHostRunner;
|
|
388
|
+
fetchText?: (url: string) => Promise<string>;
|
|
923
389
|
onExecution?: (execution: ToolHostExecution) => void;
|
|
924
390
|
};
|
|
925
|
-
/**
|
|
926
|
-
* Answers server-bridged tool calls by executing them in this browser tab.
|
|
927
|
-
* Framework-free — {@link useToolCallHost} is a thin React wrapper.
|
|
928
|
-
*
|
|
929
|
-
* The point is data locality: documents fetched or held client-side can be
|
|
930
|
-
* evaluated here and never touch the server. The engine loads lazily, so a page
|
|
931
|
-
* that never bridges a call never pays for the WASM guest.
|
|
932
|
-
*/
|
|
933
391
|
declare function createToolCallHost(handle: SessionHandle, options?: ToolCallHostOptions): {
|
|
934
392
|
dispose: () => void;
|
|
935
393
|
};
|
|
936
394
|
//#endregion
|
|
937
395
|
//#region src/hooks/use-tool-host.d.ts
|
|
938
396
|
type UseToolCallHostOptions = ToolCallHostOptions & {
|
|
939
|
-
|
|
397
|
+
enabled?: boolean;
|
|
940
398
|
historyLimit?: number;
|
|
941
399
|
};
|
|
942
|
-
/**
|
|
943
|
-
* React wrapper around {@link createToolCallHost}: subscribes while mounted and
|
|
944
|
-
* exposes recent executions for rendering. All the logic lives in the
|
|
945
|
-
* framework-free host — this only manages the subscription's lifetime.
|
|
946
|
-
*/
|
|
947
400
|
declare function useToolCallHost(handle: SessionHandle | undefined, options?: UseToolCallHostOptions): {
|
|
948
401
|
executions: ToolHostExecution[];
|
|
949
402
|
};
|
|
950
403
|
//#endregion
|
|
951
404
|
//#region src/lib/recap.d.ts
|
|
952
|
-
/**
|
|
953
|
-
* "What happened while you were away", counted rather than written.
|
|
954
|
-
*
|
|
955
|
-
* Deterministic on purpose. A prose recap would mean spending a turn — tokens,
|
|
956
|
-
* context and latency — on a summary nobody asked the model for, and it would
|
|
957
|
-
* be wrong in the one case that matters most (a session that failed while
|
|
958
|
-
* unattended, where the model is exactly who you shouldn't ask). Everything
|
|
959
|
-
* here is already in the transcript; this only counts it.
|
|
960
|
-
*
|
|
961
|
-
* Framework-free and pure, like the reducer it reads from: both clients render
|
|
962
|
-
* the same recap from the same numbers.
|
|
963
|
-
*/
|
|
964
405
|
type RecapSummary = {
|
|
965
|
-
|
|
966
|
-
replies: number;
|
|
406
|
+
turns: number;
|
|
407
|
+
replies: number;
|
|
967
408
|
tools: number;
|
|
968
|
-
toolNames: string[];
|
|
409
|
+
toolNames: string[];
|
|
969
410
|
files: number;
|
|
970
|
-
/** Failed turns and failed tool calls, together — what you'd want to know
|
|
971
|
-
* first on coming back. */
|
|
972
411
|
errors: number;
|
|
973
|
-
|
|
974
|
-
* look now rather than later. */
|
|
975
|
-
pending: number; /** Any of the above non-zero. A recap of nothing is noise. */
|
|
412
|
+
pending: number;
|
|
976
413
|
any: boolean;
|
|
977
414
|
};
|
|
978
|
-
/** The `TranscriptState` fields a recap reads — structural, so a caller can
|
|
979
|
-
* pass the whole state or just these. */
|
|
980
415
|
type RecapInput = {
|
|
981
416
|
items: readonly TranscriptItem[];
|
|
982
417
|
pendingApprovals?: readonly unknown[];
|
|
983
418
|
};
|
|
984
|
-
/**
|
|
985
|
-
* Summarize the items from `fromIndex` onward — the boundary being the number
|
|
986
|
-
* of items that existed when the session was last looked at.
|
|
987
|
-
*
|
|
988
|
-
* An out-of-range boundary is clamped rather than rejected: a transcript can
|
|
989
|
-
* *shrink* (a `/clear`, a fresh attach after a compaction), and the honest
|
|
990
|
-
* reading of "you last saw 40 items, there are now 12" is "everything here is
|
|
991
|
-
* new", not a negative count.
|
|
992
|
-
*/
|
|
993
419
|
declare function summarizeSince(state: RecapInput, fromIndex: number): RecapSummary;
|
|
994
|
-
/**
|
|
995
|
-
* The recap as one line of text, in the order a person reads it: what got done,
|
|
996
|
-
* what it used, what went wrong, what is waiting.
|
|
997
|
-
*
|
|
998
|
-
* Returns `undefined` when there is nothing to say, so a caller can render the
|
|
999
|
-
* row or not on the value alone.
|
|
1000
|
-
*/
|
|
1001
420
|
declare function recapLine(summary: RecapSummary): string | undefined;
|
|
1002
421
|
//#endregion
|
|
1003
|
-
export { type AttachmentKind, type ClientForHost, 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 };
|
|
422
|
+
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 };
|
|
1004
423
|
//# sourceMappingURL=index.d.mts.map
|