@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 +91 -713
- package/build/index.mjs +211 -533
- package/build/index.mjs.map +1 -1
- package/package.json +14 -14
package/build/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/transcript.ts","../src/lib/transcript-cache.ts","../src/lib/attach-plan.ts","../src/hooks/use-session.ts","../src/hooks/use-attachments.ts","../src/lib/prompt-tokens.ts","../src/lib/host-tree.ts","../src/hooks/use-host-files.ts","../src/hooks/use-project-icons.ts","../src/hooks/use-profile-usage.ts","../src/hooks/use-session-info.ts","../src/lib/open-files.ts","../src/hooks/use-open-files.ts","../src/lib/tool-host.ts","../src/hooks/use-tool-host.ts","../src/lib/recap.ts"],"sourcesContent":["import { ENGINE_CAPABILITIES, mergeUsage, orderUsageWindows } from '@workerdeck/protocol'\nimport type {\n ContentBlock,\n ContextUsage,\n EngineCapabilities,\n FilePatch,\n MessageAttachment,\n ModelOption,\n PermissionMode,\n PermissionRequest,\n ProfileEngine,\n RateLimitInfo,\n SessionEvent,\n SessionInfo,\n SessionStatus,\n SkillInfo,\n SlashCommandInfo,\n ToolExecutionBackend,\n ToolExecutionOutput,\n ToolResultBlock,\n UsageWindowRow,\n} from '@workerdeck/protocol'\n\n/**\n * Pure transcript state machine over the wire-protocol event stream. Framework-free\n * so it can be unit-tested and reused outside React.\n */\n\nexport type TranscriptItem =\n | {\n kind: 'user'\n id: string\n text: string\n attachments?: MessageAttachment[]\n /**\n * The `Task` call this prompt was addressed to, when it is a subagent's\n * brief rather than something a person typed.\n *\n * Optional where the other kinds carry it as `string | null`, and the\n * asymmetry is the point: on those it is a fact about every instance, so\n * forgetting to stamp it should not typecheck. Here the overwhelming case\n * is a human prompt, which has no parent at all — `undefined` says that,\n * where `null` on 24 construction sites would only say \"somebody\n * remembered\".\n */\n parentToolUseId?: string\n }\n | {\n kind: 'assistant_text'\n id: string\n text: string\n streaming: boolean\n parentToolUseId: string | null\n }\n | { kind: 'thinking'; id: string; text: string; parentToolUseId: string | null }\n | {\n kind: 'tool_call'\n id: string\n name: string\n input: unknown\n parentToolUseId: string | null\n /**\n * When the model called it — the event's own `ts`, so it is replay-stable\n * rather than a receive time (the mistake `rateLimitsUpdatedAt` makes on\n * iOS). Optional because it is stamped at creation only: an item\n * reconstructed by an older path has none, and absent must read as \"no\n * elapsed\" rather than as the epoch.\n *\n * Added for the sub-agent takeover's header, which is the one surface that\n * has to say how long an agent has been going: `SubagentInfo.startedAt`\n * cannot answer it, being frozen at attach for anything spawned later.\n * Immutable after creation, which is what makes it safe for iOS's\n * `Equatable` row-plan cache key to mirror later.\n */\n ts?: number\n /**\n * - `running` — the model called it; execution has not been reported\n * - `pending` — dispatched to an executor (bridged to this client, queued)\n * - `deferred` — parked beyond this turn; may outlive the session's liveness\n * - `settled` / `failed` — terminal\n *\n * Derive UI from this, not from `result` being present: a pending or\n * deferred call has no result yet and is not the same as a running one.\n */\n status: 'running' | 'pending' | 'deferred' | 'settled' | 'failed'\n /**\n * `truncated`/`totalChars`/`sourceSeq` are set **only** when the replay\n * delivered a head (protocol's {@link ToolResultBlock.truncated}), so\n * every other result stays byte-identical to what it was before this\n * feature existed. That matters beyond tidiness: on iOS `ToolCallItem` is\n * `Equatable` and is half the row-plan cache key.\n *\n * `sourceSeq` is what makes the press possible at all — the item is what a\n * renderer holds, and it must be able to name the event to fetch. It goes\n * away again on hydration, along with the other two, so a hydrated result\n * is indistinguishable from one that was never cut.\n */\n result?: {\n text: string\n isError: boolean\n truncated?: boolean\n totalChars?: number\n sourceSeq?: number\n /**\n * The pictures this result carried, as addresses rather than bytes —\n * set **only** when the replay delivered `image_ref` parts, so every\n * other result stays byte-identical (the `Equatable` argument above,\n * again).\n *\n * Each entry carries its **own** `sourceSeq`, which is not redundant\n * with the one beside it: that one is cleared by text hydration, and a\n * reader who pressed \"show everything\" must still be able to load the\n * screenshot afterwards.\n *\n * Raw base64 `image` parts are still dropped on arrival, as they always\n * were. Folding them in would pin megabytes inside `TranscriptState`,\n * which the transcript LRU then retains across session switches.\n */\n images?: ReadonlyArray<{\n partIndex: number\n mediaType: string\n bytes: number\n sourceSeq: number\n }>\n }\n /**\n * What this call changed on disk, when it was a file edit — the engine's\n * own hunks and line numbers (see protocol's {@link FilePatch}).\n *\n * Only ever set from the wire. A client cannot derive it: it has never\n * seen the file, so a diff it computed from the tool's *input* would have\n * no line numbers, and one parsed out of the result prose would be welded\n * to an engine's text formatting.\n */\n patch?: FilePatch\n /** Correlation id when this call is executed outside the model loop. */\n executionId?: string\n /** Which backend is executing it, when known. */\n backend?: ToolExecutionBackend\n /** Logs captured by the executor (guest console output). */\n logs?: string[]\n }\n | {\n kind: 'turn_result'\n id: string\n subtype: string\n isError: boolean\n durationMs: number\n totalCostUsd: number\n errors?: string[]\n }\n | { kind: 'notice'; id: string; level: 'info' | 'error'; text: string }\n /** The agent handed over a session file (`file_delivered`). Render a download\n * card; the file is served by GET /sessions/:id/files/<path> while the\n * session lives. */\n | { kind: 'file_delivered'; id: string; path: string; bytes: number; description?: string }\n\n/** A `file_produced` announcement, as the transcript keeps it. */\nexport type ProducedFileRef = {\n fileId: string\n mediaType?: string\n bytes?: number\n}\n\nexport type TranscriptState = {\n status: SessionStatus\n statusDetail?: string\n model?: string\n cwd?: string\n sdkSessionId?: string\n /** Engine running the session, from the attach snapshot. Gates CLI-only\n * affordances; absent (an older server) reads as 'claude'. */\n engine?: ProfileEngine\n /**\n * What this session's engine does and does not do: the runner-reported record\n * from the attach snapshot when present, else {@link ENGINE_CAPABILITIES} for\n * the engine. Always defined, so a surface can render every affordance from it\n * rather than switching on the engine name — an absent capability means the\n * affordance is *hidden*, never a control that silently does nothing.\n */\n capabilities: EngineCapabilities\n /**\n * The most recent attach snapshot, whole. The session-level facts no event\n * carries — profile, apiKeySource, canBypassPermissions, createdAt, numTurns —\n * live only here. Unlike the fields above it is replaced on every attach: it is\n * the server's answer, not something the event stream refines.\n */\n session?: SessionInfo\n /** Models the session can switch to (from the `capabilities` event). */\n models?: ModelOption[]\n /** Slash commands the CLI accepts (from the `capabilities` event). */\n commands?: SlashCommandInfo[]\n /**\n * Skills the engine can reach (from the `skills` event), replaced whole each\n * time. Absent until the engine has enumerated them — which for codex is on\n * its first turn, since listing needs a live child. So gate the affordance on\n * *this being defined*, not on `capabilities.skillsList` alone: the flag says\n * the engine can answer, this says it has.\n *\n * Not commands, and must not be offered as such — see the protocol's\n * `SkillInfo`.\n */\n skills?: SkillInfo[]\n /**\n * Files the engine wrote on the host, keyed by the absolute path it reported\n * (from `file_produced`). A tool card holding a `savedPath` looks itself up\n * here to turn that path into a fetchable id — `client.producedFileUrl` — so\n * the picture renders without the operator having declared a host-file root.\n */\n producedFiles?: Record<string, ProducedFileRef>\n\n /** What this session's default model resolves to (from `capabilities`). Known\n * before the first turn, which `model` is not — a promptless session has no\n * `system_init` until it is spoken to. */\n defaultModel?: string\n /** Seeded from `system_init`, updated on `permission_mode_changed`. */\n permissionMode?: PermissionMode\n /** Latest context-window snapshot; absent until the first turn completes. */\n contextUsage?: ContextUsage\n /** Latest rate-limit snapshot per window ('five_hour', 'seven_day', ...).\n * Absent for API-key sessions — render nothing, not 0%. */\n rateLimits?: Record<string, RateLimitInfo>\n /**\n * When the newest window reading was *taken* (the event's `ts`), not when this\n * client received it — so a reading replayed on attach is dated honestly\n * rather than as \"just now\". Updates come one per turn at best, which makes a\n * stale reading normal and worth saying out loud.\n */\n rateLimitsUpdatedAt?: number\n /** claude.ai plan the rate-limit windows belong to ('pro', 'max', ...), from\n * `plan_info`. Absent for API-key sessions, like the windows themselves. */\n subscriptionType?: string\n items: TranscriptItem[]\n pendingApprovals: PermissionRequest[]\n totalCostUsd: number\n lastSeq: number\n}\n\nexport const initialTranscriptState: TranscriptState = {\n status: 'starting',\n // The protocol's own default for an absent `engine`, so a surface has a record\n // to render from before the first attach frame lands.\n capabilities: ENGINE_CAPABILITIES.claude,\n items: [],\n pendingApprovals: [],\n totalCostUsd: 0,\n lastSeq: 0,\n}\n\n/**\n * The in-flight streamed text and thought — a singleton **per agent**, not per\n * session.\n *\n * It was one id for the whole stream, which was right while one thread streamed\n * at a time. It is not: with subagent text forwarded, a `Task` and the thread\n * that spawned it stream *concurrently*, and three parallel Tasks stream three\n * ways at once. Under one id every one of those deltas accumulates into the same\n * item — a row welding several agents' half-sentences together — and the first\n * `assistant_message` to land wipes all of them, including the ones still being\n * written.\n *\n * So the id carries the agent: `streaming` for the main thread (unchanged, so\n * nothing that keys off it moves) and `streaming:<parentToolUseId>` inside a\n * subagent.\n */\nconst STREAMING_ID = 'streaming'\nconst STREAMING_THINKING_ID = 'streaming-thinking'\nconst streamingTextId = (parentToolUseId: string | null): string =>\n parentToolUseId == null ? STREAMING_ID : `${STREAMING_ID}:${parentToolUseId}`\nconst streamingThinkingId = (parentToolUseId: string | null): string =>\n parentToolUseId == null ? STREAMING_THINKING_ID : `${STREAMING_THINKING_ID}:${parentToolUseId}`\n/** Is this item an in-flight stream — anyone's? The turn's end finalizes every\n * one of them, since a subagent's last text is as unrecoverable as the main\n * thread's when a turn is interrupted. */\nconst isStreamingItem = (item: TranscriptItem): boolean =>\n (item.kind === 'assistant_text' && item.id.startsWith(STREAMING_ID)) ||\n (item.kind === 'thinking' && item.id.startsWith(STREAMING_THINKING_ID))\n\nfunction blockText(content: ToolResultBlock['content']): string {\n if (content === undefined) return ''\n if (typeof content === 'string') return content\n return content\n .map((part) => (typeof part.text === 'string' ? part.text : ''))\n .filter(Boolean)\n .join('\\n')\n}\n\n/** The `image_ref` addresses in a result's content, or undefined when it holds\n * none — which is the common case, and is why this returns undefined rather than\n * an empty array: an absent field keeps the item byte-identical. */\nfunction imageRefsOf(\n content: ToolResultBlock['content'],\n seq: number,\n): ReadonlyArray<{ partIndex: number; mediaType: string; bytes: number; sourceSeq: number }> | undefined {\n if (!Array.isArray(content)) return undefined\n const refs = content.flatMap((part) =>\n part.type === 'image_ref'\n ? [\n {\n partIndex: Number(part.part_index),\n mediaType: String(part.media_type ?? 'application/octet-stream'),\n bytes: Number(part.bytes ?? 0),\n sourceSeq: seq,\n },\n ]\n : [],\n )\n return refs.length > 0 ? refs : undefined\n}\n\nfunction contentToBlocks(content: string | ContentBlock[]): ContentBlock[] {\n return typeof content === 'string' ? [{ type: 'text', text: content }] : content\n}\n\n/** Render an execution's by-value output for the transcript. */\nfunction outputText(output: ToolExecutionOutput): string {\n if (output.type === 'text') return output.value\n try {\n return JSON.stringify(output.value)\n } catch {\n return String(output.value)\n }\n}\n\n/** CLI-side command output arrives as user text wrapped in local-command tags. */\nconst LOCAL_COMMAND_OUTPUT = /^<local-command-(stdout|stderr)>([\\s\\S]*?)<\\/local-command-\\1>$/\n\n/**\n * A slash command the person ran, as the CLI writes it into the transcript:\n * `<command-message>…</command-message><command-name>/wrapup</command-name>\n * <command-args>…</command-args>`, in whichever order.\n *\n * Rendered as the command line rather than hidden. It *is* a person's turn — it\n * is the reason everything after it happened — but the raw wrapper is markup\n * nobody typed, and it showed up verbatim in every resumed transcript. Not\n * suppressed in the runner for that same reason: hiding it would erase the\n * turn's cause and, since `transcriptActivity` counts a non-synthetic user\n * message as one row, silently disagree with the unread count.\n */\nconst COMMAND_NAME = /<command-name>([\\s\\S]*?)<\\/command-name>/\nconst COMMAND_ARGS = /<command-args>([\\s\\S]*?)<\\/command-args>/\n\n/** The typed command line, or undefined when this is ordinary prose. */\nfunction slashCommandText(text: string): string | undefined {\n const name = COMMAND_NAME.exec(text)?.[1]?.trim()\n if (!name) return undefined\n const args = COMMAND_ARGS.exec(text)?.[1]?.trim()\n return args ? `${name} ${args}` : name\n}\n\nfunction upsert(items: TranscriptItem[], item: TranscriptItem): TranscriptItem[] {\n const index = items.findIndex((existing) => existing.id === item.id && existing.kind === item.kind)\n if (index === -1) return [...items, item]\n const next = [...items]\n next[index] = item\n return next\n}\n\n/**\n * Seed transcript state from the attach snapshot (the `attached` frame's SessionInfo).\n * A promptless session emits no `system_init` until its first message, so fields like\n * `permissionMode` and `model` would otherwise stay empty — fill only what events\n * haven't set yet; the event stream stays authoritative.\n */\nexport function seedFromSessionInfo(state: TranscriptState, info: SessionInfo): TranscriptState {\n // Never changes for a live session, and no event carries it — the snapshot is\n // the only source, so take it whenever it is present.\n const engine = info.engine ?? state.engine\n return {\n ...state,\n // Before any event has arrived, the snapshot status is fresher than 'starting'.\n // With state already held (a reconnect, or a warm transcript-cache seed) the\n // held status stands: any change since is a `status_changed` in the replay\n // span — state-bearing, always replayed, last occurrence kept — arriving on\n // the same socket flush as this frame, so the event stream stays the one\n // authority instead of a snapshot racing it.\n status: state.lastSeq === 0 ? info.status : state.status,\n model: state.model ?? info.model,\n permissionMode: state.permissionMode ?? info.permissionMode,\n cwd: state.cwd ?? info.cwd,\n sdkSessionId: state.sdkSessionId ?? info.sdkSessionId,\n engine,\n // The wire copy wins over the static default when both exist, per the\n // protocol — the runner knows what it actually wired up.\n capabilities: info.capabilities ?? ENGINE_CAPABILITIES[engine ?? 'claude'],\n session: info,\n }\n}\n\n/**\n * The session's rate-limit windows in reading order: the session window, the\n * weekly window, then whichever per-model weekly windows it reports.\n *\n * The ordering and the drop-the-unknown rule are protocol's `orderUsageWindows`\n * — the dashboard renders the same windows straight off `ProfileInfo.usage`,\n * with no transcript anywhere near it, and two orderings would be one account\n * described two ways. This stays as the transcript-shaped door to it.\n */\nexport function rateLimitWindows(state: TranscriptState): UsageWindowRow[] {\n return orderUsageWindows(\n mergeUsage({ rateLimits: state.rateLimits, updatedAt: state.rateLimitsUpdatedAt }, undefined),\n )\n}\n\n/**\n * Put a fetched tool result back where its head was — the other half of\n * `truncateResults`.\n *\n * Into **transcript state**, not row-local state, and the three reasons are the\n * design: the copy button then copies the whole thing rather than the head, the\n * transcript cache retains it across a session switch, and no later event can\n * re-truncate it. The markers are cleared, so a hydrated result is\n * indistinguishable from one that was never cut and every renderer needs a\n * branch for exactly one state, not two.\n *\n * Keyed on `toolUseId`, which is the id the row already holds; `seq` is what the\n * *fetch* needed, not what the fold needs. Unknown id returns `state` unchanged\n * — a press answered after the session was cleared must not resurrect a row.\n */\nexport function hydrateToolResult(\n state: TranscriptState,\n toolUseId: string,\n text: string,\n): TranscriptState {\n let changed = false\n const items = state.items.map((item) => {\n if (item.kind !== 'tool_call' || item.id !== toolUseId || !item.result?.truncated) return item\n changed = true\n // `images` survives: hydration answers the *text* press, and clearing the\n // addresses beside it would leave the row's pictures unloadable forever.\n return {\n ...item,\n result: {\n text,\n isError: item.result.isError,\n ...(item.result.images && { images: item.result.images }),\n },\n }\n })\n return changed ? { ...state, items } : state\n}\n\nexport function applyEvent(state: TranscriptState, event: SessionEvent): TranscriptState {\n if (event.seq <= state.lastSeq) return state\n const base: TranscriptState = { ...state, lastSeq: event.seq }\n\n switch (event.type) {\n case 'system_init':\n return {\n ...base,\n model: event.model,\n cwd: event.cwd,\n sdkSessionId: event.sdkSessionId,\n permissionMode: event.permissionMode,\n }\n\n case 'status_changed':\n return { ...base, status: event.status, statusDetail: event.detail }\n\n case 'capabilities':\n return {\n ...base,\n models: event.models,\n commands: event.commands,\n defaultModel: event.defaultModel ?? base.defaultModel,\n }\n\n case 'skills':\n // Replaced whole, never merged: the event is the engine's current answer,\n // so a skill deleted on disk has to be able to disappear from the list.\n return { ...base, skills: event.skills }\n\n case 'file_produced':\n // Keyed by PATH, not by fileId, because the lookup a card does is\n // \"here is the savedPath in my tool input — is there anything to fetch?\".\n return {\n ...base,\n producedFiles: {\n ...base.producedFiles,\n [event.path]: {\n fileId: event.fileId,\n ...(event.mediaType ? { mediaType: event.mediaType } : {}),\n ...(event.bytes !== undefined ? { bytes: event.bytes } : {}),\n },\n },\n }\n\n case 'model_changed':\n // undefined = reset to the server default; keep showing the last known model.\n return event.model === undefined ? base : { ...base, model: event.model }\n\n case 'permission_mode_changed':\n return { ...base, permissionMode: event.mode }\n\n case 'context_usage':\n return { ...base, contextUsage: event.usage }\n\n case 'rate_limit': {\n // Keyed by window so five_hour and seven_day updates don't clobber each other.\n const key = event.info.rateLimitType\n if (!key) return base\n return {\n ...base,\n rateLimits: { ...base.rateLimits, [key]: event.info },\n rateLimitsUpdatedAt: event.ts,\n }\n }\n\n case 'plan_info':\n return { ...base, subscriptionType: event.subscriptionType }\n\n case 'conversation_reset':\n // Same session, fresh conversation (/clear, plan-mode exit). Only\n // conversation-scoped state resets: the items, the context reading (the\n // window now holds an almost-empty conversation; the runner re-polls),\n // and the engine session id when the event names the new one. Everything\n // session-scoped survives — models/commands/skills, produced files (still\n // fetchable), rate limits and plan (account-level), cwd, model,\n // permission mode, cumulative cost — and so do pending approvals: the\n // runner still holds them and they still need answering.\n return {\n ...base,\n items: [],\n contextUsage: undefined,\n sdkSessionId: event.sdkSessionId ?? base.sdkSessionId,\n }\n\n case 'user_message': {\n let items = base.items\n for (const block of contentToBlocks(event.message.content)) {\n if (block.type === 'tool_result') {\n const toolResult = block as ToolResultBlock\n const isError = toolResult.is_error === true\n items = items.map((item) =>\n item.kind === 'tool_call' && item.id === toolResult.tool_use_id\n ? {\n ...item,\n status: isError ? 'failed' : 'settled',\n result: {\n text: blockText(toolResult.content),\n isError,\n // Set together or not at all: a marker without the seq is a\n // press that cannot be answered.\n ...(toolResult.truncated && {\n truncated: true as const,\n totalChars: toolResult.total_chars,\n sourceSeq: event.seq,\n }),\n ...(imageRefsOf(toolResult.content, event.seq) && {\n images: imageRefsOf(toolResult.content, event.seq),\n }),\n },\n // Absent on most results; the runner sets it only for a file\n // edit, and only when the message answers one call.\n ...(event.patch && { patch: event.patch }),\n }\n : item,\n )\n } else if (block.type === 'text' && !event.synthetic) {\n const text = (block as { text: string }).text\n const localOutput = LOCAL_COMMAND_OUTPUT.exec(text.trim())\n if (localOutput) {\n items = upsert(items, {\n kind: 'notice',\n id: event.uuid ?? `user-${event.seq}`,\n level: localOutput[1] === 'stderr' ? 'error' : 'info',\n text: localOutput[2].trim(),\n })\n } else {\n items = upsert(items, {\n kind: 'user',\n id: event.uuid ?? `user-${event.seq}`,\n // A slash command reads as the command line, not as the wrapper\n // the CLI stored it in.\n text: slashCommandText(text) ?? text,\n // References, not bytes — render them by fetching\n // `/sessions/:id/attachments/:attachmentId`.\n attachments: event.attachments,\n // A subagent's brief arrives here too — a real, non-synthetic\n // user message with a parent. Unstamped it renders as a `❯`\n // prompt row in the main thread, which reads as something the\n // person typed and is the one row in a transcript that must\n // never be wrong about who said it.\n ...(event.parentToolUseId != null && {\n parentToolUseId: event.parentToolUseId,\n }),\n })\n }\n }\n }\n return { ...base, items }\n }\n\n case 'assistant_message': {\n // Encrypted thinking arrives as a signature-only block on the final message: `thinking`\n // is '' and the human-readable summary, when the model surfaces one at all, exists only\n // in the thinking_delta stream. Carry the streamed text over rather than let the full\n // message overwrite it with nothing.\n const streamingText = streamingTextId(event.parentToolUseId)\n const streamingThought = streamingThinkingId(event.parentToolUseId)\n let streamedThinking =\n base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'thinking' }> =>\n item.kind === 'thinking' && item.id === streamingThought,\n )?.text ?? ''\n // The full message supersedes any in-flight streamed text/thinking — this\n // agent's, and only this agent's. A subagent's finished message must not\n // wipe the sentence its parent is still writing.\n let items = base.items.filter(\n (item) =>\n !(item.kind === 'assistant_text' && item.id === streamingText) &&\n !(item.kind === 'thinking' && item.id === streamingThought),\n )\n const blocks = contentToBlocks(event.message.content)\n blocks.forEach((block, index) => {\n const id = `${event.uuid}-${index}`\n if (block.type === 'text') {\n items = upsert(items, {\n kind: 'assistant_text',\n id,\n text: (block as { text: string }).text,\n streaming: false,\n parentToolUseId: event.parentToolUseId,\n })\n } else if (block.type === 'thinking') {\n const text = (block as { thinking: string }).thinking || streamedThinking\n // One streamed thought backfills at most one block, so a multi-block message\n // doesn't repeat it.\n streamedThinking = ''\n // No summary anywhere: drop the block instead of leaving a \"Thought process\" row\n // that expands to nothing (and, across consecutive messages, stacks up).\n if (text.trim() === '') return\n items = upsert(items, {\n kind: 'thinking',\n id,\n text,\n parentToolUseId: event.parentToolUseId,\n })\n } else if (block.type === 'tool_use') {\n const toolUse = block as { id: string; name: string; input: unknown }\n items = upsert(items, {\n kind: 'tool_call',\n id: toolUse.id,\n name: toolUse.name,\n input: toolUse.input,\n parentToolUseId: event.parentToolUseId,\n status: 'running',\n ts: event.ts,\n })\n }\n })\n return { ...base, items }\n }\n\n case 'stream_delta': {\n const delta = event.event as {\n type: string\n delta?: { type?: string; text?: string; thinking?: string }\n }\n if (delta.type !== 'content_block_delta') return base\n if (delta.delta?.type === 'text_delta') {\n const id = streamingTextId(event.parentToolUseId)\n const existing = base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'assistant_text' }> =>\n item.kind === 'assistant_text' && item.id === id,\n )\n const item: TranscriptItem = {\n kind: 'assistant_text',\n id,\n text: (existing?.text ?? '') + (delta.delta.text ?? ''),\n streaming: true,\n parentToolUseId: event.parentToolUseId,\n }\n return { ...base, items: upsert(base.items, item) }\n }\n if (delta.delta?.type === 'thinking_delta') {\n const id = streamingThinkingId(event.parentToolUseId)\n const existing = base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'thinking' }> =>\n item.kind === 'thinking' && item.id === id,\n )\n const text = (existing?.text ?? '') + (delta.delta.thinking ?? '')\n // The same guard the finalized block gets, and for the same reason: a\n // `thinking_delta` can carry no visible text at all (an empty or\n // whitespace-only `thinking`, which is what encrypted reasoning looks\n // like on this channel), and a thinking item with a blank body renders\n // as a bare `✻` marker with nothing after it. Worse, it does not go\n // away — `turn_result` finalizes whatever is still streaming under a\n // stable id, so the empty row outlives the turn that produced it.\n // Skipping it here costs nothing: the next delta that does carry text\n // creates the item, since the accumulated text is rebuilt from\n // `existing` each time.\n if (text.trim() === '') return base\n const item: TranscriptItem = {\n kind: 'thinking',\n id,\n text,\n parentToolUseId: event.parentToolUseId,\n }\n return { ...base, items: upsert(base.items, item) }\n }\n return base\n }\n\n case 'turn_result':\n return {\n ...base,\n // total_cost_usd is session-cumulative on each SDK result message.\n totalCostUsd: event.totalCostUsd,\n items: [\n // The turn is over: whatever is still streaming is this turn's final\n // text — an interrupted or failed turn never sends the\n // assistant_message that normally supersedes it. Finalize it under a\n // stable id, or it stays the singleton streaming item: the *next*\n // turn's message would wipe it (a minute of interrupted output\n // vanishing on the next question) and the next turn's deltas would\n // append to it, gluing two turns' text into one row.\n // Every agent's, not just the main thread's: a subagent interrupted\n // mid-sentence has the same unrecoverable text, and one left under a\n // `streaming:<id>` key would be adopted by the next Task that reused\n // the id. The stable id carries the agent for the same reason the\n // streaming one does — two agents finalizing on one `turn_result`\n // would otherwise land on a single id, and `upsert` keys by id.\n ...base.items.map((item) => {\n if (!isStreamingItem(item)) return item\n const agent = 'parentToolUseId' in item && item.parentToolUseId ? `-${item.parentToolUseId}` : ''\n return item.kind === 'assistant_text'\n ? { ...item, id: `text-${event.seq}${agent}`, streaming: false }\n : { ...item, id: `thinking-${event.seq}${agent}` }\n }),\n {\n kind: 'turn_result',\n id: `turn-${event.seq}`,\n subtype: event.subtype,\n isError: event.isError,\n durationMs: event.durationMs,\n totalCostUsd: event.totalCostUsd,\n errors: event.errors,\n },\n ],\n }\n\n case 'permission_requested':\n return { ...base, pendingApprovals: [...base.pendingApprovals, event.request] }\n\n case 'permission_resolved':\n return {\n ...base,\n pendingApprovals: base.pendingApprovals.filter((r) => r.id !== event.requestId),\n }\n\n // Execution lifecycle for tool calls that run outside the model loop\n // (bridged to this client, queued, or deferred). Keyed by executionId, which\n // equals the tool_use id for calls the model made. Events for an unknown id\n // are ignored rather than fabricating an item: the tool_use that explains it\n // may simply not have arrived (or belongs to another session).\n case 'execution_dispatched':\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: event.deferred ? 'deferred' : 'pending',\n executionId: event.executionId,\n backend: event.backend,\n }\n : item,\n ),\n }\n\n case 'execution_result':\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: 'settled',\n executionId: event.executionId,\n result: { text: outputText(event.output), isError: false },\n logs: event.logs ?? item.logs,\n }\n : item,\n ),\n }\n\n case 'execution_failed':\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: 'failed',\n executionId: event.executionId,\n result: { text: `${event.reason}: ${event.error}`, isError: true },\n logs: event.logs ?? item.logs,\n }\n : item,\n ),\n }\n\n case 'file_delivered':\n return {\n ...base,\n items: [\n ...base.items,\n {\n kind: 'file_delivered',\n id: `file-${event.seq}`,\n path: event.path,\n bytes: event.bytes,\n description: event.description,\n },\n ],\n }\n\n case 'session_error':\n return {\n ...base,\n items: [\n ...base.items,\n { kind: 'notice', id: `err-${event.seq}`, level: 'error', text: event.message },\n ],\n }\n\n case 'session_closed':\n return {\n ...base,\n items: [\n ...base.items,\n {\n kind: 'notice',\n id: `closed-${event.seq}`,\n level: 'info',\n text: `Session closed (${event.reason})`,\n },\n ],\n }\n\n case 'sdk_event':\n default:\n return base\n }\n}\n","import type { WorkerDeckClient } from '@workerdeck/client'\nimport type { TranscriptState } from './transcript.ts'\n\n/**\n * Module-scope cache of detached transcript states, so switching back to a\n * recently viewed session paints its transcript in the mount frame and\n * re-attaches with `afterSeq: lastSeq` — the wire replays only what happened\n * while the panel was away, instead of the whole event log.\n *\n * Module-scope for the same reason `useSessions` and the watermarks are: the\n * consumers that need it (the VS Code panel, the dashboard's session route)\n * remount `SessionPanel` per session, so any per-hook copy would die with the\n * unmount that is the entire point of surviving.\n *\n * Entries are the same `TranscriptState` objects the reducer held — retention,\n * not duplication — and the bound is what keeps retention from becoming a\n * leak. Eviction is least-recently-STORED: every detach stores, so store\n * recency is viewing recency, and reads don't need to reorder.\n *\n * Keys come from {@link transcriptCacheKey} and carry the client's\n * `identityKey` (gateway + auth headers), never the session id alone: a\n * session id is unique only within one gateway, and an entry must never be\n * readable through a client speaking as a different principal.\n */\n\n/**\n * How many detached transcripts stay warm.\n *\n * Five covers the working set the feature exists for — an operator alternating\n * between the handful of sessions that are simultaneously working or awaiting\n * them — while keeping the pathological case (five `perf`-fixture-sized\n * transcripts of ~4k items each) in the tens of megabytes, no more than a few\n * times what the one mounted panel already holds. Too small degrades to\n * today's behaviour (a replay on switch-back); too large is memory held\n * forever in a webview — the asymmetry favours small.\n */\nconst MAX_ENTRIES = 5\n\nconst entries = new Map<string, TranscriptState>()\n\n/** Cache key for one session as seen through one (gateway, principal). The\n * NUL separator is unambiguous: the identity key is `JSON.stringify` output,\n * which escapes control characters, so no two (identity, session) pairs can\n * spell the same key. */\nexport function transcriptCacheKey(client: WorkerDeckClient, sessionId: string): string {\n return `${client.identityKey}\\u0000${sessionId}`\n}\n\nexport function readTranscriptCache(key: string): TranscriptState | undefined {\n return entries.get(key)\n}\n\nexport function writeTranscriptCache(key: string, state: TranscriptState): void {\n entries.delete(key)\n entries.set(key, state)\n if (entries.size > MAX_ENTRIES) {\n const oldest = entries.keys().next().value\n if (oldest !== undefined) entries.delete(oldest)\n }\n}\n\nexport function deleteTranscriptCache(key: string): void {\n entries.delete(key)\n}\n\n/**\n * Drop every cached transcript. For an embedder changing principals in place\n * (a logout that keeps the page alive) — entries are unreachable through the\n * new principal's client either way, but scrubbing them is free and final.\n */\nexport function clearTranscriptCache(): void {\n entries.clear()\n}\n","import { initialTranscriptState, type TranscriptState } from './transcript.ts'\n\n/**\n * The attach effect's decisions, pure.\n *\n * `useClaudeSession` is never rendered in tests — this package deliberately\n * carries no jsdom and no testing-library — so the logic that used to live\n * inline in the attach effect (which state an attach holds, whether the\n * reducer must be re-seeded, which `afterSeq` to request, whether the parting\n * state may go back into the cache) is decided here, where plain vitest\n * reaches it, and the effect keeps only glue: read its refs into inputs,\n * apply the returned instructions, subscribe. The refs themselves stay in the\n * hook — a decision function that owned React state would be the untestable\n * thing again — so everything stateful arrives as a value and leaves as an\n * instruction.\n */\n\n/**\n * Which (resync, client identity, session) a reducer state was seeded for.\n * One format, shared by the hook's mount initializer and {@link planAttach},\n * so the two sites cannot drift: a token that dropped `resyncSeq` would leave\n * the stale-log retry looking already-seeded, and the fresh replay would\n * compose into the condemned state the resync just discarded.\n */\nexport function attachSeedToken(resyncSeq: number, key: string): string {\n return `${resyncSeq}:${key}`\n}\n\n/** Everything the decision reads — the hook's refs and options, as values.\n * `warm` is the caller's ONE cache read for this attach; whether it may be\n * used is decided here, and `afterSeq` derives only from what is actually\n * held. */\nexport type AttachInputs = {\n /** Bumped by the hook after a stale-log detection to force a fresh attach. */\n resyncSeq: number\n /** `transcriptCacheKey(client, sessionId)` — gateway identity + session. */\n key: string\n /** The token the reducer's current state was seeded under (seededForRef). */\n seededFor: string\n /** What the reducer holds right now (stateRef). */\n current: TranscriptState\n /** `options.cacheTranscript !== false`, read at attach time. */\n cacheEnabled: boolean\n /** True between a stale-log detection and its retry (skipCacheRef). */\n skipCache: boolean\n /** The cache entry under `key`, if any. */\n warm: TranscriptState | undefined\n}\n\nexport type AttachPlan = {\n /** The state this attach composes onto. `afterSeq` was derived from it and\n * from nothing else. */\n held: TranscriptState\n /** True when the reducer must be re-seeded with `held` before any frame can\n * arrive; the effect then records `seedToken` as what it seeded for. */\n seed: boolean\n /** The token `held` belongs to — `attachSeedToken(resyncSeq, key)`. */\n seedToken: string\n /** Attach with `afterSeq` — a warm attach, replaying only the missed span —\n * or, when absent, attach cold from seq 0. */\n afterSeq?: number\n}\n\n/**\n * Decide what one run of the attach effect does before it opens the socket.\n */\nexport function planAttach(input: AttachInputs): AttachPlan {\n const seedToken = attachSeedToken(input.resyncSeq, input.key)\n // The warm entry is admissible only when caching is on and no stale-log\n // detection stands between us and it: after one, the retry must attach cold\n // even if another mount re-wrote the key after the delete — holding that\n // write would re-open the exact silence the resync exists to escape.\n const warm = input.cacheEnabled && !input.skipCache ? input.warm : undefined\n // What the reducer holds for THIS attach. When its state was seeded for this\n // very token (the mount whose initializer read the cache), use it as-is;\n // otherwise seed before any frame arrives — applyEvent's `seq <= lastSeq`\n // dedupe would silently swallow a new session's (or a fresh log's) entire\n // replay into old state.\n const seed = input.seededFor !== seedToken\n const held = seed ? (warm ?? initialTranscriptState) : input.current\n // `afterSeq` comes from the state actually held, never from a second cache\n // read: deriving both from one object keeps a racing write from another\n // mount from opening a gap between what is painted and what replays.\n return { held, seed, seedToken, ...(held.lastSeq > 0 ? { afterSeq: held.lastSeq } : {}) }\n}\n\n/**\n * Whether the effect's cleanup may keep the parting transcript warm for a\n * switch-back. Refused when caching is off; after a stale-log detection —\n * writing the condemned state back would re-poison the very retry that just\n * discarded it; and when there is nothing real to keep — `lastSeq === 0` also\n * protects an existing entry from being clobbered by a mount that never\n * finished attaching, and a state with no `session` never saw its attached\n * frame at all.\n */\nexport function shouldWriteParting(input: {\n cacheEnabled: boolean\n skipCache: boolean\n parting: TranscriptState\n}): boolean {\n return (\n input.cacheEnabled &&\n !input.skipCache &&\n input.parting.lastSeq > 0 &&\n input.parting.session !== undefined\n )\n}\n","import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'\nimport type { WorkerDeckClient, SessionHandle } from '@workerdeck/client'\nimport { PROTOCOL_VERSION } from '@workerdeck/protocol'\nimport type {\n AttachedFrame,\n ModelOption,\n PermissionMode,\n SessionEvent,\n} from '@workerdeck/protocol'\nimport {\n applyEvent,\n initialTranscriptState,\n hydrateToolResult,\n seedFromSessionInfo,\n type TranscriptState,\n} from '../lib/transcript.ts'\nimport {\n deleteTranscriptCache,\n readTranscriptCache,\n transcriptCacheKey,\n writeTranscriptCache,\n} from '../lib/transcript-cache.ts'\nimport { attachSeedToken, planAttach, shouldWriteParting } from '../lib/attach-plan.ts'\n\n/** Replace the state wholesale — an in-place session switch, or the stale-log\n * resync. Internal to the hook; the wire never carries it. */\ntype SeedAction = { type: 'transcript_seed'; state: TranscriptState }\n/** A fetched tool result landing back on the row that showed its head. Local,\n * not an event: nothing was emitted, and inventing a seq for it would put a\n * frame in the log that no other client will ever see. */\ntype HydrateAction = { type: 'transcript_hydrate_result'; toolUseId: string; text: string }\n\n/** Session events drive the reducer; the attach snapshot seeds fields (permission\n * mode, model) that a promptless session's event stream doesn't carry yet. */\nfunction reduce(\n state: TranscriptState,\n action: SessionEvent | AttachedFrame | SeedAction | HydrateAction,\n): TranscriptState {\n if (action.type === 'transcript_seed') return action.state\n if (action.type === 'transcript_hydrate_result')\n return hydrateToolResult(state, action.toolUseId, action.text)\n return action.type === 'attached'\n ? seedFromSessionInfo(state, action.session)\n : applyEvent(state, action)\n}\n\n/**\n * How the client is doing at reaching the gateway — deliberately not the session's\n * status. The two are orthogonal, and while the socket is down the status a client\n * holds is *stale*, so a surface that merges them must say so rather than keep\n * claiming \"idle\".\n *\n * The handle retries forever, so `offline` is a judgement about how long it has\n * been failing rather than a state the transport reports.\n */\nexport type ConnectionState = 'live' | 'reconnecting' | 'offline'\n\n/** Failed attempts in a row before \"reconnecting…\" stops being the honest word.\n * Three is ~3.5s of backoff — past a blip. Matches the iOS client. */\nconst OFFLINE_AFTER_ATTEMPTS = 3\n\n/**\n * The seq the initial attach replay ends on, or undefined when there is nothing\n * to hold for.\n *\n * This is an exact signal, not a heuristic: the `attached` frame is sent before\n * any replayed `event` frame and carries the runner's seq at attach time\n * (`session.lastSeq`), so the moment the frame arrives the client knows\n * precisely which seq the replay ends on. Every runner keeps its full event log\n * and always delivers the highest-seq event on a fresh replay (the\n * `conversation_reset` skip is strictly-below-the-reset, and the reset's seq is\n * itself ≤ lastSeq), so `TranscriptState.lastSeq >= target` means the replay\n * has landed. No quiet window or other arrival heuristic belongs here.\n *\n * Only a FRESH attach yields a target (`replayingFrom === 0`): a reconnect\n * replays into a transcript the reader is already looking at, and blanking it\n * mid-turn would be a worse bug than the flicker the hold exists to fix. A\n * brand-new session (`lastSeq === 0`) has nothing to replay and never holds.\n */\nexport function initialReplayTarget(frame: AttachedFrame): number | undefined {\n return frame.replayingFrom === 0 && frame.session.lastSeq > 0 ? frame.session.lastSeq : undefined\n}\n\n/**\n * Whether an attach frame describes a DIFFERENT event log than the transcript\n * `held` was built from — in which case attaching with `afterSeq: held.lastSeq`\n * has already gone wrong: every event in the new log has seq ≤ afterSeq, so\n * nothing will ever arrive and the stale rows would stand forever, with no\n * error. The only recovery is to forget the state and re-attach from seq 0.\n *\n * A log resets on routine paths, not corner cases: a dormant session\n * (claude/codex surviving a gateway restart) is rebuilt with a brand-new\n * runner whose log starts at 0 and refills from the engine's own store. Two\n * checks, each of which the other misses:\n *\n * - `session.lastSeq < held.lastSeq` — the server's log is shorter than what\n * we hold. Within one log seq only grows, so this is proof of a reset. It\n * catches a rebuilt runner that has not yet re-run far — but not one whose\n * backfill already advanced past us.\n * - `session.createdAt !== held.session.createdAt` — a different runner\n * incarnation. The claude and codex runners stamp `Date.now()` at\n * construction, so a dormant rebuild always changes it; the provider runner\n * restores `createdAt` from its snapshot precisely when it also restores\n * the event log and seq counter (ai-sdk-runner's `#restore`), so equality\n * truthfully means \"same log\" for every engine.\n *\n * A full replay (`replayingFrom === 0`) is never stale — it carries the whole\n * log, so the caller heals by resetting state and applying it — and holding\n * nothing (`held.lastSeq === 0`) has nothing to be stale about. That first\n * clause is also what makes the recovery loop-proof: the re-attach from 0 can\n * never re-trigger this predicate.\n *\n * Not cache-specific: a live handle reconnecting after a gateway restart\n * re-attaches with its own advanced `afterSeq` against the rebuilt log and\n * hits the identical silence, so the hook applies this to every attach frame.\n */\nexport function staleAttach(frame: AttachedFrame, held: TranscriptState): boolean {\n if (frame.replayingFrom === 0 || held.lastSeq === 0) return false\n if (frame.session.lastSeq < held.lastSeq) return true\n return held.session !== undefined && frame.session.createdAt !== held.session.createdAt\n}\n\n/**\n * Backstop for the replay hold: if the target seq has not landed after this\n * long, reveal what has arrived. On a healthy attach the target is always\n * reached (see {@link initialReplayTarget}); the backstop exists because a\n * blank panel forever would be a much worse failure than a visible stream, so\n * the hold is bounded no matter what a future filter or a lossy path does. It\n * runs from the attach — a per-event re-arm would be a quiet-window heuristic\n * in a new costume.\n */\nexport const REPLAY_HOLD_MAX_MS = 1500\n\nexport type UseClaudeSessionOptions = {\n /** Called when the server rejects a command with a protocol_error frame — e.g. a\n * permission-mode switch the CLI refuses. Without a handler these are dropped\n * silently and the UI looks like \"nothing happened\". */\n onProtocolError?: (message: string) => void\n /**\n * Keep this session's transcript warm after unmount (default true): the next\n * mount of the same (client identity, session) paints the cached rows in its\n * first frame and attaches with `afterSeq`, replaying only what it missed.\n * Bounded module-scope LRU, keyed by the client's `identityKey` (gateway +\n * auth headers) so nothing crosses gateways or credentials; if the attach\n * frame shows a different event log (see {@link staleAttach}), the entry is\n * discarded and the hook re-attaches from seq 0.\n *\n * Set `false` for an embedder whose principal varies on one base URL by\n * means the client cannot see (a custom `fetchImpl` switching users, say) —\n * or call `clearTranscriptCache()` on logout. Read at attach time.\n */\n cacheTranscript?: boolean\n}\n\nexport type UseClaudeSessionResult = {\n state: TranscriptState\n /** True while the socket is open. {@link UseClaudeSessionResult.connection}\n * carries the same fact with the \"has it been failing a while\" distinction. */\n connected: boolean\n connection: ConnectionState\n /**\n * True while the initial attach replay is still landing: the `attached` frame\n * said events up to `session.lastSeq` follow, and they have not all been\n * applied yet. A surface can hold its paint on this — keep the rows mounted\n * and measuring, show nothing — and reveal a settled transcript in one frame,\n * instead of streaming hundreds of replayed rows past the reader. Always\n * false on a reconnect (only a fresh attach holds; see\n * {@link initialReplayTarget}) and bounded by {@link REPLAY_HOLD_MAX_MS}.\n */\n replaying: boolean\n /** The server's `PROTOCOL_VERSION` when it disagrees with the one this build\n * mirrors — undefined when they match. Some events may not render. */\n protocolMismatch?: number\n /**\n * What a model picker should offer. Two sources, and which is authoritative\n * depends on the engine: the `capabilities` event is the CLI asked what it\n * supports, so for claude it wins; codex never sends one — its models are a\n * catalog shipped with the release and served on the profile — so without the\n * fallback its picker would be permanently empty and the session unswitchable.\n */\n models: ModelOption[]\n /** The model this session answers as: the one it reported, or, before it has\n * reported anything, the default it will use. */\n effectiveModel?: string\n /** The live attach handle, for wiring companions that must ride the SAME\n * socket — e.g. useToolCallHost: the bridge asks the first attached client,\n * so a host on a second handle would never see the requests. Undefined until\n * attached and after unmount. */\n handle: SessionHandle | undefined\n /** Attachment ids come from `client.uploadAttachment`, in send order. */\n send: (text: string, attachmentIds?: string[]) => void\n approve: (requestId: string, updatedInput?: Record<string, unknown>) => void\n /** `message` is fed back to the agent, which can then try something else;\n * `interrupt` also stops the turn (\"deny & stop\"). */\n deny: (requestId: string, message?: string, interrupt?: boolean) => void\n interrupt: () => void\n /**\n * Reset the conversation in place: same session, empty transcript. Gate the\n * affordance on `session.capabilities?.clearContext` — an engine or a gateway\n * that cannot do it answers with an error, which is the wrong way for a user\n * to find out.\n */\n clearContext: () => void\n setPermissionMode: (mode: PermissionMode) => void\n setModel: (model?: string) => void\n closeSession: () => void\n /** Skip the reconnect backoff — what a tab returning to the foreground does. */\n reconnectNow: () => void\n /**\n * Fetch the whole of a tool result the replay delivered as a head, and put it\n * back on its row (`result.truncated` clears with it).\n *\n * Resolves `false` when there was nothing to do — an untruncated row, an\n * unknown id, or a gateway that refused (a stale `sourceSeq` after a dormant\n * rebuild 404s by design; re-attaching is what fixes that, not a retry). It\n * never throws, because the caller is a press on a row and an exception there\n * has nowhere sensible to go.\n */\n loadFullResult: (toolUseId: string) => Promise<boolean>\n}\n\n/** Attach to a session and maintain live transcript state. Detaches on unmount. */\nexport function useClaudeSession(\n client: WorkerDeckClient,\n sessionId: string | undefined,\n options?: UseClaudeSessionOptions,\n): UseClaudeSessionResult {\n // Seeded from the transcript cache when this (client identity, session) was\n // viewed recently — the cached rows are the mount frame's paint, which is the\n // whole \"switching back is instant\" feature. A cold key starts blank as before.\n const [state, dispatch] = useReducer(\n reduce,\n undefined,\n (): TranscriptState =>\n (options?.cacheTranscript !== false && sessionId !== undefined\n ? readTranscriptCache(transcriptCacheKey(client, sessionId))\n : undefined) ?? initialTranscriptState,\n )\n const [connection, setConnection] = useState<ConnectionState>('reconnecting')\n const [protocolMismatch, setProtocolMismatch] = useState<number | undefined>()\n /** Where the current attach's replay ends, while one is being held for. */\n const [replayTarget, setReplayTarget] = useState<number | undefined>()\n /** Bumped to force a fresh attach from seq 0 after a stale-log detection. */\n const [resyncSeq, setResyncSeq] = useState(0)\n // Ref for the stable callbacks below; state so consumers of `handle` re-render\n // when the socket opens or the session switches.\n const [handleState, setHandleState] = useState<SessionHandle | undefined>()\n const handleRef = useRef<SessionHandle | null>(null)\n // Ref'd so a new inline callback doesn't tear down and reopen the socket.\n const optionsRef = useRef(options)\n optionsRef.current = options\n // The latest rendered state, for the attach effect and its cleanup — both run\n // outside render and must see what the transcript actually holds.\n const stateRef = useRef(state)\n stateRef.current = state\n // Which (resync, client identity, session) the reducer state belongs to. The\n // initializer above seeded for the mount's token; the effect re-seeds when its\n // token differs (an in-place session switch, or a resync).\n const seededForRef = useRef(\n attachSeedToken(0, sessionId === undefined ? '' : transcriptCacheKey(client, sessionId)),\n )\n // True from a stale-log detection until the next attach: the cleanup must not\n // write the condemned state back into the cache (it would re-poison the very\n // retry that just discarded it), and the retry must attach cold even if some\n // other mount re-wrote the entry meanwhile.\n const skipCacheRef = useRef(false)\n\n useEffect(() => {\n if (!sessionId) return\n const cache = optionsRef.current?.cacheTranscript !== false\n const key = transcriptCacheKey(client, sessionId)\n // Every decision — which state this attach holds, whether to re-seed the\n // reducer, which afterSeq to request — is made in planAttach, pure and\n // unit-tested (test/attach-plan.test.ts), because this hook itself never\n // renders in a test: the package carries no jsdom, by design. This effect\n // only reads its refs into inputs and applies the plan's instructions.\n const plan = planAttach({\n resyncSeq,\n key,\n seededFor: seededForRef.current,\n current: stateRef.current,\n cacheEnabled: cache,\n skipCache: skipCacheRef.current,\n warm: readTranscriptCache(key),\n })\n skipCacheRef.current = false\n if (plan.seed) {\n dispatch({ type: 'transcript_seed', state: plan.held })\n seededForRef.current = plan.seedToken\n }\n // `truncateResults` is asked for **here**, and here only: this hook is the\n // unit that renders, so it is the one that knows a head can be fetched back\n // (see `AttachOptions.truncateResults`). An embedder holding `client`\n // without `react` gets whole results, which is the safe default.\n //\n // `imageRefs` is asked for on the same grounds and in the same breath: the\n // reducer knows how to hold an address and the panel knows how to fetch it,\n // so this hook is the only place that may say so. Measured, it is 91% of\n // the tool-result payload and none of what was ever drawn.\n const handle = client.attach(sessionId, {\n truncateResults: true,\n imageRefs: true,\n ...(plan.afterSeq === undefined ? {} : { afterSeq: plan.afterSeq }),\n })\n handleRef.current = handle\n setHandleState(handle)\n const offEvent = handle.on('event', (event: SessionEvent) => dispatch(event))\n const offAttached = handle.on('attached', (frame: AttachedFrame) => {\n if (staleAttach(frame, stateRef.current)) {\n // The server's log is not the one this transcript came from (dormant\n // rebuild, restart): we attached past events we never saw, so this\n // socket delivers either nothing or another log's events — see\n // staleAttach. Stop listening NOW (a rebuilt log that advanced past us\n // replays new-log events in this same tick, and they must not compose\n // into old-log state), forget everything, and re-attach from seq 0;\n // the hold below then blanks the stale rows until the real replay\n // lands. Cannot loop: the retry ignores the cache and attaches with\n // afterSeq 0, for which staleAttach is false by definition.\n offEvent()\n deleteTranscriptCache(key)\n skipCacheRef.current = true\n setResyncSeq((n) => n + 1)\n return\n }\n dispatch(frame)\n // A reconnect's frame (`replayingFrom > 0`) computes to undefined, which\n // also RELEASES a hold whose replay was cut short by a socket drop: the\n // re-attach picks up from whatever landed, streaming the rest visibly\n // rather than holding for a target the first socket never delivered.\n setReplayTarget(initialReplayTarget(frame))\n setProtocolMismatch(\n frame.protocolVersion === PROTOCOL_VERSION ? undefined : frame.protocolVersion,\n )\n })\n const offConn = handle.on('connectionChange', (open: boolean) =>\n setConnection(open ? 'live' : 'reconnecting'),\n )\n const offRetry = handle.on('reconnectAttempt', (attempts: number) =>\n setConnection(attempts >= OFFLINE_AFTER_ATTEMPTS ? 'offline' : 'reconnecting'),\n )\n const offProtocolError = handle.on('protocolError', (message: string) => {\n optionsRef.current?.onProtocolError?.(message)\n })\n return () => {\n offEvent()\n offAttached()\n offConn()\n offRetry()\n offProtocolError()\n handle.detach()\n handleRef.current = null\n setHandleState(undefined)\n setConnection('reconnecting')\n setProtocolMismatch(undefined)\n setReplayTarget(undefined)\n // Keep the transcript warm for a switch-back — when shouldWriteParting\n // allows it (the guards, and the bugs each one prevents, live on that\n // function).\n const parting = stateRef.current\n if (shouldWriteParting({ cacheEnabled: cache, skipCache: skipCacheRef.current, parting })) {\n writeTranscriptCache(key, parting)\n }\n }\n }, [client, sessionId, resyncSeq])\n\n // The hold's backstop. Armed once per hold (the target is set exactly once,\n // at the attach) and NOT re-armed per event — that would be a quiet-window\n // latch, the thing this design exists to not be. When the target is reached\n // the derived `replaying` below flips false in that same render; this state\n // is then cleared so the next attach starts clean.\n useEffect(() => {\n if (replayTarget === undefined) return\n const timer = setTimeout(() => setReplayTarget(undefined), REPLAY_HOLD_MAX_MS)\n return () => clearTimeout(timer)\n }, [replayTarget])\n useEffect(() => {\n if (replayTarget !== undefined && state.lastSeq >= replayTarget) setReplayTarget(undefined)\n }, [replayTarget, state.lastSeq])\n\n const models = useProfileModelFallback(client, sessionId, state)\n\n const connected = connection === 'live'\n // Derived at render, not in an effect, so the reveal happens in the SAME\n // commit that applies the replay's final event — an effect would reveal one\n // render late, and that render is a visible frame.\n const replaying = replayTarget !== undefined && state.lastSeq < replayTarget\n const reconnectNow = useCallback(() => handleRef.current?.reconnectNow(), [])\n\n // The press's other half. The seq comes off the *item* (`result.sourceSeq`),\n // never off anything the caller passes: the row is what a reader pressed, and\n // making the caller carry a seq would invite a stale one from a cache. Read\n // through `stateRef` so this identity is stable across every render — it is a\n // prop on a virtualized row, and a new function each render is a new prop on\n // every row in the transcript.\n const loadFullResult = useCallback(\n async (toolUseId: string): Promise<boolean> => {\n if (!sessionId) return false\n const item = stateRef.current.items.find(\n (candidate) => candidate.kind === 'tool_call' && candidate.id === toolUseId,\n )\n const result = item?.kind === 'tool_call' ? item.result : undefined\n if (!result?.truncated || result.sourceSeq === undefined) return false\n try {\n const full = await client.toolResult(sessionId, result.sourceSeq, toolUseId)\n const text =\n typeof full.content === 'string'\n ? full.content\n : (full.content ?? [])\n .map((part) => (typeof part.text === 'string' ? part.text : ''))\n .filter(Boolean)\n .join('\\n')\n dispatch({ type: 'transcript_hydrate_result', toolUseId, text })\n return true\n } catch {\n // A 404 here means the log this seq belonged to is gone (dormant\n // rebuild, restart). The head stays, with its marker, and the row still\n // says what it is — which is the honest state, and better than an error\n // toast about a press.\n return false\n }\n },\n [client, sessionId],\n )\n\n return useMemo(\n () => ({\n state,\n connected,\n connection,\n replaying,\n protocolMismatch,\n models,\n effectiveModel: state.model ?? state.defaultModel,\n handle: handleState,\n send: (text, attachmentIds) => handleRef.current?.send(text, attachmentIds),\n approve: (requestId, updatedInput) => handleRef.current?.approve(requestId, updatedInput),\n deny: (requestId, message, interrupt) =>\n handleRef.current?.deny(requestId, message, interrupt),\n interrupt: () => handleRef.current?.interrupt(),\n clearContext: () => handleRef.current?.clearContext(),\n setPermissionMode: (mode) => handleRef.current?.setPermissionMode(mode),\n setModel: (model) => handleRef.current?.setModel(model),\n closeSession: () => handleRef.current?.closeSession(),\n reconnectNow,\n loadFullResult,\n }),\n [\n state,\n connected,\n connection,\n replaying,\n protocolMismatch,\n models,\n handleState,\n reconnectNow,\n loadFullResult,\n ],\n )\n}\n\n/**\n * The session's profile catalog, fetched once and only when it could matter —\n * i.e. when the engine has reported no models of its own.\n *\n * Fire-and-forget on purpose: an empty catalog is exactly the state a picker\n * already handles, so a failed or 404'd `/profiles` (a server predating them)\n * degrades to the old behaviour rather than raising an error about a list the\n * operator may never open.\n */\nfunction useProfileModelFallback(\n client: WorkerDeckClient,\n sessionId: string | undefined,\n state: TranscriptState,\n): ModelOption[] {\n const [catalog, setCatalog] = useState<ModelOption[]>([])\n const profile = state.session?.profile\n const reported = state.models\n const hasReported = !!reported?.length\n\n useEffect(() => setCatalog([]), [sessionId])\n\n useEffect(() => {\n if (!profile || hasReported) return\n let cancelled = false\n client\n .listProfiles()\n .then((response) => {\n if (!cancelled) {\n setCatalog(response.profiles.find((p) => p.name === profile)?.models ?? [])\n }\n })\n .catch(() => {\n // No catalog: the picker falls back to whatever the session reports.\n })\n return () => {\n cancelled = true\n }\n }, [client, profile, hasReported])\n\n return hasReported ? reported : catalog\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type { EngineCapabilities, ProfileEngine } from '@workerdeck/protocol'\n\n/**\n * Files staged for the next message.\n *\n * The upload happens as soon as something is picked, not at send time — the\n * message names attachment *ids*, so the bytes must already be the server's\n * before a turn can reference them, and the wait is spent while the user is\n * still typing rather than after they hit send. It also keeps base64 out of the\n * event log entirely, which is the protocol's rule.\n */\nexport type StagedAttachment = {\n /** Local identity, stable across a retry — the React key while uploading. */\n key: string\n name: string\n mediaType: string\n bytes: number\n /** Object URL for an image thumbnail, revoked when the item goes away. */\n previewUrl?: string\n status: 'uploading' | 'ready' | 'failed'\n /** The server's id once uploaded — what `send` names. */\n id?: string\n /** Why the upload failed, verbatim from the gateway (413, 415, …). */\n error?: string\n}\n\n/** The kind vocabulary of {@link EngineCapabilities.attachments}. */\nexport type AttachmentKind = 'image' | 'pdf' | 'text'\n\n/**\n * How a media type reaches a model, in the capability record's vocabulary.\n * `undefined` means this build can't classify it — the upload still goes,\n * because the gateway's vocabulary is the authoritative one.\n */\nexport function attachmentKind(mediaType: string): AttachmentKind | undefined {\n const type = mediaType.split(';')[0]!.trim().toLowerCase()\n if (type.startsWith('image/')) return 'image'\n if (type === 'application/pdf') return 'pdf'\n if (type.startsWith('text/')) return 'text'\n if (TEXTUAL_TYPES.has(type)) return 'text'\n return undefined\n}\n\n/** Textual types whose media type doesn't start with `text/` — mirrors core. */\nconst TEXTUAL_TYPES = new Set([\n 'application/json',\n 'application/xml',\n 'application/yaml',\n 'application/x-yaml',\n 'application/toml',\n 'application/javascript',\n 'application/typescript',\n 'application/x-sh',\n 'application/sql',\n])\n\n/** Longest edge an image is downscaled to before upload. Anthropic's own\n * recommendation, and the same number the iOS client uses — a phone photo is\n * several times this in each direction and costs tokens for nothing. */\nconst MAX_IMAGE_EDGE = 1568\n\nexport type UseAttachmentsOptions = {\n /** The session's capability record — its `attachments` list decides which\n * kinds are offered and which are refused locally. */\n capabilities: EngineCapabilities\n /** Named in a local refusal, so \"the codex engine does not take pdf\n * attachments\" says which engine meant it. */\n engine?: ProfileEngine\n}\n\nexport type UseAttachmentsResult = {\n items: StagedAttachment[]\n /** Uploaded ids in staging order — what {@link UseClaudeSessionResult.send} names. */\n readyIds: string[]\n /** An id that hasn't landed can't be named, so send waits. */\n uploading: boolean\n /** A refused file must be dealt with before the message goes. */\n hasFailure: boolean\n /** Accept attribute for a file input, narrowed to what the engine takes. */\n accept: string\n /** True when the engine takes no attachments at all — hide the affordance\n * entirely rather than offer one with no meaning. */\n disabled: boolean\n add: (files: Iterable<File>) => void\n retry: (key: string) => void\n remove: (key: string) => void\n clear: () => void\n /** A local refusal (wrong kind), surfaced once rather than silently dropped. */\n error?: string\n dismissError: () => void\n}\n\n/**\n * Stage, upload and track files for the next message of a session.\n *\n * Refusals happen as early as they can be known: a kind the capability record\n * forswears never reaches the network (the gateway would 415 it), and everything\n * else is the gateway's call — its vocabulary is authoritative, so an unknown\n * media type is uploaded rather than guessed at.\n */\nexport function useAttachments(\n client: WorkerDeckClient,\n sessionId: string | undefined,\n { capabilities, engine }: UseAttachmentsOptions,\n): UseAttachmentsResult {\n const [items, setItems] = useState<StagedAttachment[]>([])\n const [error, setError] = useState<string | undefined>()\n const counter = useRef(0)\n /** The originals, kept so a failed upload can be retried without re-picking. */\n const fileByKey = useRef(new Map<string, File>())\n /** Mirrors the live preview URLs so unmount can revoke them all — an unmount\n * with blobs outstanding is a leak the GC does not clean up. */\n const previewUrls = useRef<string[]>([])\n previewUrls.current = items.flatMap((item) => (item.previewUrl ? [item.previewUrl] : []))\n const accepts = capabilities.attachments\n\n useEffect(\n () => () => {\n for (const url of previewUrls.current) URL.revokeObjectURL(url)\n },\n [],\n )\n\n const patch = useCallback((key: string, next: Partial<StagedAttachment>) => {\n setItems((current) =>\n current.map((item) => (item.key === key ? { ...item, ...next } : item)),\n )\n }, [])\n\n const upload = useCallback(\n (key: string, file: File) => {\n if (!sessionId) return\n patch(key, { status: 'uploading', error: undefined })\n void (async () => {\n try {\n const data = await prepare(file)\n const uploaded = await client.uploadAttachment(sessionId, {\n name: file.name,\n mediaType: data.mediaType,\n data: data.body,\n })\n patch(key, { status: 'ready', id: uploaded.id, bytes: uploaded.bytes ?? file.size })\n } catch (e) {\n patch(key, { status: 'failed', error: e instanceof Error ? e.message : 'Upload failed' })\n }\n })()\n },\n [client, patch, sessionId],\n )\n\n const add = useCallback(\n (files: Iterable<File>) => {\n const staged: StagedAttachment[] = []\n const pending: Array<{ key: string; file: File }> = []\n for (const file of files) {\n const mediaType = file.type || 'application/octet-stream'\n const kind = attachmentKind(mediaType)\n // A kind this build can't classify still goes through: the gateway's\n // vocabulary is the authoritative one, and it answers with a real reason.\n if (kind && !accepts.includes(kind)) {\n setError(`The ${engine ?? 'claude'} engine does not take ${kind} attachments.`)\n continue\n }\n const key = `att-${++counter.current}`\n staged.push({\n key,\n name: file.name,\n mediaType,\n bytes: file.size,\n previewUrl: kind === 'image' ? URL.createObjectURL(file) : undefined,\n status: 'uploading',\n })\n pending.push({ key, file })\n }\n if (staged.length === 0) return\n setItems((current) => [...current, ...staged])\n fileByKey.current = new Map([\n ...fileByKey.current,\n ...pending.map(({ key, file }) => [key, file] as const),\n ])\n for (const { key, file } of pending) upload(key, file)\n },\n [accepts, engine, upload],\n )\n\n const forget = useCallback((keys: string[]) => {\n setItems((current) => {\n for (const item of current) {\n if (keys.includes(item.key) && item.previewUrl) URL.revokeObjectURL(item.previewUrl)\n }\n return current.filter((item) => !keys.includes(item.key))\n })\n for (const key of keys) fileByKey.current.delete(key)\n }, [])\n\n const remove = useCallback((key: string) => forget([key]), [forget])\n\n const clear = useCallback(() => {\n setItems((current) => {\n for (const item of current) if (item.previewUrl) URL.revokeObjectURL(item.previewUrl)\n return []\n })\n fileByKey.current.clear()\n }, [])\n\n const retry = useCallback(\n (key: string) => {\n const file = fileByKey.current.get(key)\n if (file) upload(key, file)\n },\n [upload],\n )\n\n return useMemo(\n () => ({\n items,\n readyIds: items.flatMap((item) => (item.id ? [item.id] : [])),\n uploading: items.some((item) => item.status === 'uploading'),\n hasFailure: items.some((item) => item.status === 'failed'),\n accept: acceptAttribute(accepts),\n disabled: accepts.length === 0 || !sessionId,\n add,\n retry,\n remove,\n clear,\n error,\n dismissError: () => setError(undefined),\n }),\n [items, accepts, sessionId, add, retry, remove, clear, error],\n )\n}\n\n/** What a file input should offer. The full set keeps the open door (anything —\n * the gateway refuses the rest with a clear message); a narrower record narrows\n * the browsing too, so most refusals never happen. */\nfunction acceptAttribute(kinds: readonly AttachmentKind[]): string {\n if (kinds.length === 0) return ''\n const parts: string[] = []\n if (kinds.includes('image')) parts.push('image/*')\n if (kinds.includes('pdf')) parts.push('application/pdf')\n if (kinds.includes('text')) parts.push('text/*', '.md', '.json', '.yaml', '.yml', '.toml')\n return kinds.length === 3 ? '' : parts.join(',')\n}\n\n/**\n * The two browser APIs the downscale needs, reached through `globalThis` and\n * typed structurally.\n *\n * This package compiles without the DOM lib — Node-only consumers (the smoke\n * tsconfig) pull its source in — so naming `document` or `createImageBitmap`\n * directly is a type error there. Feature-detecting them is what the code has to\n * do at runtime anyway: the downscale is an optimisation, and a host that can't\n * do it uploads the original.\n */\ntype ImageBitmapLike = { width: number; height: number; close(): void }\ntype CanvasLike = {\n width: number\n height: number\n getContext(contextId: '2d'): {\n drawImage(image: ImageBitmapLike, dx: number, dy: number, dw: number, dh: number): void\n } | null\n toBlob(callback: (blob: Blob | null) => void, type?: string, quality?: number): void\n}\nconst imaging = globalThis as unknown as {\n createImageBitmap?: (source: Blob) => Promise<ImageBitmapLike>\n document?: { createElement(tagName: 'canvas'): CanvasLike }\n}\n\n/**\n * The bytes to upload, and the type they are.\n *\n * Oversized images are redrawn to {@link MAX_IMAGE_EDGE} first: a modern phone\n * photo is 4000px on its long edge, which costs tokens for detail no model\n * reads, and often exceeds the gateway's per-file cap outright. Everything else\n * — and anything the browser can't decode — is uploaded as-is, so a failure here\n * is never worse than not trying.\n */\nasync function prepare(file: File): Promise<{ body: Blob; mediaType: string }> {\n const mediaType = file.type || 'application/octet-stream'\n const { createImageBitmap, document } = imaging\n // GIFs are excluded because a redraw would keep one frame of an animation.\n if (!createImageBitmap || !document || !mediaType.startsWith('image/')) {\n return { body: file, mediaType }\n }\n if (mediaType === 'image/gif') return { body: file, mediaType }\n try {\n const bitmap = await createImageBitmap(file)\n const longest = Math.max(bitmap.width, bitmap.height)\n if (longest <= MAX_IMAGE_EDGE) {\n bitmap.close()\n return { body: file, mediaType }\n }\n const scale = MAX_IMAGE_EDGE / longest\n const canvas = document.createElement('canvas')\n canvas.width = Math.round(bitmap.width * scale)\n canvas.height = Math.round(bitmap.height * scale)\n const context = canvas.getContext('2d')\n if (!context) {\n bitmap.close()\n return { body: file, mediaType }\n }\n context.drawImage(bitmap, 0, 0, canvas.width, canvas.height)\n bitmap.close()\n const blob = await new Promise<Blob | null>((resolve) =>\n canvas.toBlob(resolve, 'image/jpeg', 0.85),\n )\n return blob ? { body: blob, mediaType: 'image/jpeg' } : { body: file, mediaType }\n } catch {\n // A format the browser can't decode (HEIC on most desktops) — let the\n // gateway answer with its own 415 rather than inventing one here.\n return { body: file, mediaType }\n }\n}\n","/**\n * The two prompt tokens the CLI understands — `@file` and `/command` — found in\n * text that has already been sent.\n *\n * The mirror of the iOS client's `PromptTokens.scan`, and deliberately the same\n * rules: a message should read the same after sending as it did in the composer,\n * on either client. It lives here, beside the transcript reducer, for the same\n * reason its Swift twin lives in the kit rather than the app — every interesting\n * case is an edge (an `@` mid-word, an email address, a slash that is really an\n * absolute path), so it is the part that gets unit-tested.\n *\n * Only the finished-text half is here; the composer's completion is the\n * prompt-area's own trigger machinery.\n */\nexport type PromptToken = {\n kind: 'file' | 'command'\n /** Offsets into the scanned string, prefix included. */\n start: number\n end: number\n text: string\n}\n\n/** Characters a command name may contain after the slash. Deliberately excludes\n * `/`, so an absolute path pasted into a message (`/Users/me/…`) is not mistaken\n * for a command; `:` is in because namespaced skills (`dev:wrapup`) are spelled\n * that way. */\nconst COMMAND_BODY = /^[A-Za-z0-9\\-_.:]+$/\n\n/** Trailing punctuation that belongs to the sentence, not the token — so\n * \"see @README.md.\" styles the path and leaves the period alone. */\nconst SENTENCE_TAIL = new Set(['.', ',', ';', ':', '!', '?', ')', ']', '}', '\"', \"'\"])\n\n/**\n * Every token in a sent message.\n *\n * Stricter than what a composer completes: a bare `@` is a token being typed, but\n * in a sent message it is just an at sign.\n */\nexport function scanPromptTokens(text: string): PromptToken[] {\n const tokens: PromptToken[] = []\n // Word starts: the beginning of the text, and every position after whitespace.\n const words = /\\S+/g\n let match: RegExpExecArray | null\n while ((match = words.exec(text)) !== null) {\n const word = match[0]\n const kind = word[0] === '@' ? 'file' : word[0] === '/' ? 'command' : undefined\n if (!kind) continue\n let end = match.index + word.length\n while (end > match.index && SENTENCE_TAIL.has(text[end - 1]!)) end--\n const body = text.slice(match.index + 1, end)\n if (!body) continue\n if (kind === 'command' && !COMMAND_BODY.test(body)) continue\n tokens.push({ kind, start: match.index, end, text: text.slice(match.index, end) })\n }\n return tokens\n}\n","import type { HostDirEntry } from '@workerdeck/protocol'\n\n/**\n * One directory as the tree knows it: what `/fs/list` answered, plus whether the\n * server held entries back.\n *\n * A directory that has never been asked for is simply absent from the map — which\n * is not the same as an empty directory, and the difference is what tells the\n * renderer to show a spinner rather than \"nothing here\".\n */\nexport type HostDirState = {\n entries: HostDirEntry[]\n /** The directory held more entries than the server will return. */\n truncated?: boolean\n}\n\n/** One rendered row of the tree — a flat list is what a scroll container wants,\n * and indentation is a number, not a nesting of DOM. */\nexport type HostTreeRow = {\n entry: HostDirEntry\n /** 0 for the root's own children. */\n depth: number\n /** Directories only: whether this row's children are showing. */\n expanded?: boolean\n /** Set on an expanded directory whose listing hasn't arrived yet. */\n loading?: boolean\n /** Set on an expanded directory the server truncated. */\n truncated?: boolean\n}\n\n/**\n * Flatten the loaded directories into the rows the tree shows.\n *\n * Pure, so the interesting part of a file tree — which nodes are visible at what\n * depth once a few directories are expanded and one of them is still loading —\n * is testable without a DOM or a gateway.\n *\n * Only *expanded* directories contribute children, and only if their listing has\n * arrived. An expanded-but-unlisted directory yields its own row with\n * `loading: true` and no children: expansion is a request the user already made,\n * so the row must say the answer is coming rather than look like an empty folder.\n */\nexport function flattenHostTree(\n root: string,\n dirs: ReadonlyMap<string, HostDirState>,\n expanded: ReadonlySet<string>,\n): HostTreeRow[] {\n const rows: HostTreeRow[] = []\n // Iterative rather than recursive: a deep tree is a user's checkout, not a\n // bounded structure, and blowing the stack on someone's monorepo would be a\n // silly way to fail.\n const walk = (dir: string, depth: number) => {\n const state = dirs.get(dir)\n if (!state) return\n for (const entry of state.entries) {\n if (entry.type !== 'dir') {\n rows.push({ entry, depth })\n continue\n }\n const isExpanded = expanded.has(entry.path)\n const childState = dirs.get(entry.path)\n rows.push({\n entry,\n depth,\n expanded: isExpanded,\n loading: isExpanded && !childState,\n truncated: isExpanded ? childState?.truncated : undefined,\n })\n if (isExpanded && childState) walk(entry.path, depth + 1)\n }\n }\n walk(root, 0)\n return rows\n}\n\n/**\n * Every ancestor of `path` below `root`, outermost first — the directories that\n * must be expanded for `path` to be on screen.\n *\n * Returns `[]` when `path` is not under `root` rather than guessing: revealing a\n * file the tree cannot contain is a no-op, not an error worth raising, and the\n * caller has no better answer either.\n *\n * The prefix test is on a **path boundary** (`root` + `/`), so `/src/app` is not\n * treated as living under `/src/a`.\n */\nexport function ancestorsWithin(root: string, path: string): string[] {\n const base = root.endsWith('/') ? root.slice(0, -1) : root\n if (path === base || !path.startsWith(`${base}/`)) return []\n const rest = path.slice(base.length + 1).split('/')\n // The last segment is the file itself, which is not a directory to expand.\n const out: string[] = []\n let current = base\n for (const segment of rest.slice(0, -1)) {\n current = `${current}/${segment}`\n out.push(current)\n }\n return out\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { WorkerDeckError, type WorkerDeckClient } from '@workerdeck/client'\nimport type { HostFileMatch } from '@workerdeck/protocol'\nimport { ancestorsWithin, flattenHostTree, type HostDirState, type HostTreeRow } from '../lib/host-tree.ts'\n\nexport type UseHostFileSearchResult = {\n /**\n * Whether `@file` completion is on offer at all: the session's cwd is known\n * and this gateway hasn't already 404'd the search. Read it before advertising\n * the affordance — a server without host files configured has none.\n */\n available: boolean\n /**\n * Run one search. Safe to call per keystroke — the route is built for it\n * (bounded walk, build directories skipped) — and it answers `[]` rather than\n * throwing, because a failed lookup is not worth an error banner over an\n * affordance the user can ignore.\n */\n search: (query: string, options?: { limit?: number; signal?: AbortSignal }) => Promise<HostFileMatch[]>\n}\n\n/**\n * Fuzzy file search rooted at a session's working directory — what an `@file`\n * picker needs.\n *\n * Deliberately session-scoped: the server's `hostFiles.roots` are the security\n * boundary, but what someone wants while talking to an agent is *this* project's\n * tree, so this never offers the roots list.\n *\n * A gateway that answers 404 once has answered for the session: host files are\n * either configured or they aren't, and the answer will not change while the cwd\n * holds. Asking again on every character would be a request per keystroke for a\n * feature that does not exist here.\n */\nexport function useHostFileSearch(\n client: WorkerDeckClient,\n cwd: string | undefined,\n): UseHostFileSearchResult {\n const [unsupported, setUnsupported] = useState(false)\n // A resume into a different directory invalidates the verdict as well as the\n // results — the new cwd may well be under a configured root.\n const lastCwd = useRef(cwd)\n useEffect(() => {\n if (lastCwd.current !== cwd) {\n lastCwd.current = cwd\n setUnsupported(false)\n }\n }, [cwd])\n\n const search = useCallback(\n async (query: string, options?: { limit?: number; signal?: AbortSignal }) => {\n if (!cwd || unsupported) return []\n try {\n const response = await client.findHostFiles(cwd, query, options?.limit ?? 8)\n return options?.signal?.aborted ? [] : response.matches\n } catch (e) {\n // No host files on this gateway (or the cwd isn't under a root).\n if (e instanceof WorkerDeckError && e.status === 404) setUnsupported(true)\n return []\n }\n },\n [client, cwd, unsupported],\n )\n\n return { available: !!cwd && !unsupported, search }\n}\n\nexport type UseHostFileRootsResult = {\n /** Whether this gateway serves host files at all. */\n available: boolean\n /**\n * Whether `PUT /fs/write` is enabled here.\n *\n * Read it before offering an editor. Writing is a **separate** server opt-in\n * from reading and defaults off, so a gateway that happily lists and reads a\n * tree may still refuse every save — and finding that out at save time, with\n * edits already made, is the worst moment for it.\n */\n canWrite: boolean\n}\n\n/**\n * Whether host files are served here, and whether they may be written.\n *\n * One request per client, cached for the life of the hook: the roots and the\n * write flag are gateway configuration, not session state, and they do not\n * change while the tab is open.\n */\nexport function useHostFileRoots(client: WorkerDeckClient): UseHostFileRootsResult {\n const [result, setResult] = useState<UseHostFileRootsResult>({\n available: false,\n canWrite: false,\n })\n useEffect(() => {\n let cancelled = false\n client\n .listHostRoots()\n .then((response) => {\n if (!cancelled) setResult({ available: true, canWrite: response.canWrite })\n })\n // A 404 means no host files here; anything else means we could not find\n // out. Both answer the same way, because the safe default for \"may I\n // write to the operator's disk?\" is no.\n .catch(() => {\n if (!cancelled) setResult({ available: false, canWrite: false })\n })\n return () => {\n cancelled = true\n }\n }, [client])\n return result\n}\n\nexport type UseHostFileTreeResult = {\n /**\n * Whether a tree can be shown at all: the cwd is known and this gateway serves\n * host files. Read it before rendering the rail — a gateway with no\n * `hostFiles` configured has no tree, and that is a layout decision, not an\n * error to display.\n */\n available: boolean\n /** The directory the tree is rooted at — the session's cwd. */\n root: string | undefined\n /** The visible tree, flattened. Empty until the root listing arrives. */\n rows: HostTreeRow[]\n /** True while the root listing is outstanding and there is nothing to show. */\n loading: boolean\n /** A listing that failed, verbatim from the gateway. */\n error: string | undefined\n /** Expand or collapse a directory. Expanding lists it once and remembers. */\n toggle: (path: string) => void\n /** Expand every directory between the root and this path, so it is on screen. */\n reveal: (path: string) => void\n /** Re-list one directory (default: the root), keeping what is expanded. */\n refresh: (path?: string) => void\n}\n\n/**\n * An expandable file tree rooted at a session's working directory.\n *\n * Rooted at the cwd rather than at `/fs/roots` for the same reason\n * {@link useHostFileSearch} is: the roots are the *security* boundary the server\n * enforces on every request, but what someone wants while watching an agent work\n * is this project's tree. The roots may well be broader; showing them would\n * offer navigation to directories the session has nothing to do with.\n *\n * Listings are cached per directory and kept across a collapse, so reopening a\n * folder is instant and does not re-ask. That staleness is deliberate and\n * bounded: `refresh` exists, and knowing when to call it is the *next* problem\n * (the agent is editing this same tree), not something a tree can guess.\n *\n * Like the search hook, a 404 is answered once for the session: host files are\n * either configured here or they are not.\n */\nexport function useHostFileTree(\n client: WorkerDeckClient,\n cwd: string | undefined,\n): UseHostFileTreeResult {\n const [dirs, setDirs] = useState<Map<string, HostDirState>>(() => new Map())\n const [expanded, setExpanded] = useState<Set<string>>(() => new Set())\n const [unsupported, setUnsupported] = useState(false)\n const [error, setError] = useState<string | undefined>()\n\n // A resume into a different project invalidates everything, including the\n // 404 verdict — the new cwd may well be under a configured root.\n const lastCwd = useRef(cwd)\n useEffect(() => {\n if (lastCwd.current === cwd) return\n lastCwd.current = cwd\n setDirs(new Map())\n setExpanded(new Set())\n setUnsupported(false)\n setError(undefined)\n }, [cwd])\n\n const alive = useRef(true)\n useEffect(() => {\n alive.current = true\n return () => {\n alive.current = false\n }\n }, [])\n\n // Directories whose listing has been asked for. A ref rather than state: it\n // must not re-render anything, and it is what keeps an expand-collapse-expand\n // from issuing three requests.\n const requested = useRef(new Set<string>())\n\n const list = useCallback(\n (target: string, { force = false } = {}) => {\n if (unsupported) return\n if (!force && requested.current.has(target)) return\n requested.current.add(target)\n client\n .listHostDir(target)\n .then((response) => {\n if (!alive.current) return\n setDirs((previous) => {\n const next = new Map(previous)\n // Keyed on the requested path, not the canonical one the server\n // answers with: the tree navigates by the paths `/fs/list` gave it,\n // and re-keying on a resolved path would orphan the node that asked.\n next.set(target, { entries: response.entries, truncated: response.truncated })\n return next\n })\n })\n .catch((e: unknown) => {\n if (!alive.current) return\n requested.current.delete(target)\n if (e instanceof WorkerDeckError && e.status === 404) {\n // No host files on this gateway, or the cwd is not under a root.\n // Not an error banner — the rail simply is not on offer.\n setUnsupported(true)\n return\n }\n setError(e instanceof Error ? e.message : 'Could not read that directory')\n })\n },\n [client, unsupported],\n )\n\n // The root lists itself; everything below is listed on expand.\n useEffect(() => {\n if (cwd) list(cwd)\n }, [cwd, list])\n\n const toggle = useCallback(\n (path: string) => {\n setExpanded((previous) => {\n const next = new Set(previous)\n if (next.has(path)) next.delete(path)\n else next.add(path)\n return next\n })\n // Outside the updater on purpose — React may run an updater twice, and a\n // request fired from inside one is a side effect in a place that promises\n // not to have any. Listing is idempotent (`requested` guards it) and the\n // first action on a directory is always an expand, so the call this makes\n // on a *collapse* has already been answered and does nothing.\n list(path)\n },\n [list],\n )\n\n const reveal = useCallback(\n (path: string) => {\n if (!cwd) return\n const ancestors = ancestorsWithin(cwd, path)\n if (ancestors.length === 0) return\n for (const dir of ancestors) list(dir)\n setExpanded((previous) => {\n const next = new Set(previous)\n for (const dir of ancestors) next.add(dir)\n return next\n })\n },\n [cwd, list],\n )\n\n const refresh = useCallback(\n (path?: string) => {\n const target = path ?? cwd\n if (!target) return\n setError(undefined)\n list(target, { force: true })\n },\n [cwd, list],\n )\n\n const rows = useMemo(\n () => (cwd ? flattenHostTree(cwd, dirs, expanded) : []),\n [cwd, dirs, expanded],\n )\n\n return {\n available: !!cwd && !unsupported,\n root: cwd,\n rows,\n loading: !!cwd && !unsupported && !dirs.has(cwd) && !error,\n error,\n toggle,\n reveal,\n refresh,\n }\n}\n","import { useEffect, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type { SessionRow } from '@workerdeck/protocol'\n\n/**\n * Project icon bytes for a list of sessions, as object URLs keyed by the icon's\n * own content hash.\n *\n * **Keyed by hash, and cached for the life of the page.** That is what the\n * wire's `ProjectIcon.image.hash` is for: every session in one project serves\n * identical bytes, so twelve rows of one repo cost one request, and two\n * *different* projects that happen to declare the same file cost one between\n * them. A hash names its bytes, so an entry can never go stale — editing the\n * icon changes the hash, which arrives on the next poll as a key this cache has\n * not seen. The old entry is dead weight rather than a wrong answer, and the\n * population is bounded by how many distinct icons an operator has open.\n *\n * The cache is **module scope on purpose**, like `useSessions`' store: the\n * sidebar and any other surface rendering rows mount this at once, and a\n * per-hook cache would be N copies each fetching the same bytes.\n *\n * A failure is cached as a failure. The route's 404 is the uniform \"no icon\"\n * (no project, a glyph, or one the gateway refused), so retrying it every poll\n * would be a request per session per poll for a picture that is never coming.\n *\n * Object URLs are never revoked, which is the same decision stated twice: they\n * are the cache. Revoking one would break every row still pointing at it, and\n * the whole point of hashing is that nothing here is ever superseded.\n *\n * The VS Code extension has the same three-set structure in `project-icons.ts`\n * and cannot share this one — its webview has no external `connect-src` at all,\n * so its bytes arrive as data URLs pushed from the extension host. One design,\n * two implementations, for a reason that is in the transport rather than here.\n */\nconst byHash = new Map<string, string>()\nconst inFlight = new Set<string>()\nconst failed = new Set<string>()\n\nexport type ClientForHost = (hostId: string) => WorkerDeckClient | undefined\n\nexport function useProjectIcons(\n rows: readonly SessionRow[],\n clientFor: ClientForHost,\n): Record<string, string> {\n // Held as state rather than read from the map, so a resolution re-renders.\n // The value is a snapshot of the module cache, which is why every consumer\n // sees an icon the moment any of them fetched it.\n const [resolved, setResolved] = useState<Record<string, string>>(() =>\n Object.fromEntries(byHash),\n )\n\n useEffect(() => {\n let alive = true\n for (const row of rows) {\n const icon = row.info.project?.icon\n if (icon?.type !== 'image') continue\n const { hash } = icon\n if (byHash.has(hash) || inFlight.has(hash) || failed.has(hash)) continue\n const client = clientFor(row.hostId)\n // An unreachable gateway is not an iconless one: fall out without\n // recording a failure, so a later render tries again once it is back.\n if (!client) continue\n inFlight.add(hash)\n void client\n .projectIcon(row.info.id)\n .then((blob) => {\n byHash.set(hash, URL.createObjectURL(blob))\n if (alive) setResolved(Object.fromEntries(byHash))\n })\n .catch(() => {\n // Any refusal is the uniform \"no icon\" — never retried, never surfaced.\n failed.add(hash)\n })\n .finally(() => inFlight.delete(hash))\n }\n return () => {\n alive = false\n }\n }, [rows, clientFor])\n\n return resolved\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { WorkerDeckError, type WorkerDeckClient } from '@workerdeck/client'\nimport type { ProfileUsage } from '@workerdeck/protocol'\n\nexport type UseProfileUsageOptions = {\n /** How often to re-ask while enabled. Default 60s. */\n intervalMs?: number\n /** Set false to hold the poll — a panel that is off screen has nothing to\n * refresh. Default true. */\n enabled?: boolean\n}\n\nexport type UseProfileUsageResult = {\n /** The gateway's plan-usage state for this profile, or undefined when there\n * is none to have: no profile, an older gateway, or nothing reported yet.\n * Absent is **unknown, never 0%** — see `ProfileUsageWindow`. */\n usage: ProfileUsage | undefined\n /** Ask again now. */\n refresh: () => void\n}\n\n/**\n * The gateway's per-profile plan usage, over REST.\n *\n * The session's own event stream carries a `rate_limit` reading only when the\n * engine volunteers one — for claude that is at a turn's edges and nowhere else,\n * so a session idle since yesterday replays yesterday's number, and a session\n * opened today knows nothing of what a sibling on the same account spent an hour\n * ago. `GET /profiles` answers the account-wide question, which is why this is a\n * poll and not a subscription: nothing pushes it.\n *\n * Polling and not attaching, deliberately — a second WebSocket per surface is\n * exactly what the bridge's \"asks the first attached client\" rule forbids, and\n * this is one small GET a minute.\n *\n * Self-disabling on a 404, like {@link useHostFileSearch}: a gateway without the\n * route will never grow one mid-session, so stop asking rather than log a miss\n * every minute.\n */\nexport function useProfileUsage(\n client: WorkerDeckClient,\n profile: string | undefined,\n options: UseProfileUsageOptions = {},\n): UseProfileUsageResult {\n const { intervalMs = 60_000, enabled = true } = options\n const [usage, setUsage] = useState<ProfileUsage | undefined>()\n const [unsupported, setUnsupported] = useState(false)\n const [nonce, setNonce] = useState(0)\n const refresh = useCallback(() => setNonce((n) => n + 1), [])\n\n // The previous profile's reading must not stand under a new one — it is\n // another account's plan, not a stale view of this one.\n useEffect(() => setUsage(undefined), [client, profile])\n\n const alive = useRef(true)\n useEffect(() => {\n alive.current = true\n return () => {\n alive.current = false\n }\n }, [])\n\n useEffect(() => {\n if (!profile || !enabled || unsupported) return\n let cancelled = false\n const load = () => {\n // A hidden tab's meters are not being read; skip the tick rather than\n // keep a background timer talking to the gateway. Read off `globalThis`\n // rather than the global `document`, because this package is typechecked\n // without the DOM lib in the extras project (smoke/, examples/).\n if ((globalThis as { document?: { hidden?: boolean } }).document?.hidden) return\n client\n .listProfiles()\n .then((res) => {\n if (cancelled || !alive.current) return\n setUsage(res.profiles.find((p) => p.name === profile)?.usage)\n })\n .catch((e: unknown) => {\n if (cancelled || !alive.current) return\n if (e instanceof WorkerDeckError && e.status === 404) setUnsupported(true)\n // Anything else is a blip: keep the last reading, which is dated, and\n // try again on the next tick. Dropping it would replace a known-old\n // number with nothing at all.\n })\n }\n load()\n const timer = setInterval(load, intervalMs)\n return () => {\n cancelled = true\n clearInterval(timer)\n }\n }, [client, profile, enabled, unsupported, intervalMs, nonce])\n\n return { usage, refresh }\n}\n","import { useEffect, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type { SessionInfo } from '@workerdeck/protocol'\n\nexport type UseSessionInfoResult = {\n info: SessionInfo | undefined\n /** True until the first answer — distinguishes \"still asking\" from \"no such session\". */\n loading: boolean\n /** Set when the gateway refused; `info` stays undefined. */\n error: string | undefined\n}\n\n/**\n * The registry's record of one session, over REST.\n *\n * Separate from {@link useClaudeSession} on purpose: that hook attaches a\n * WebSocket and streams a transcript, which is far more than a caller needs to\n * know a session's `cwd` or title — and a second attach would be a second\n * client on the bridge, which is the one thing the bridge's \"asks the first\n * attached client\" rule cannot tolerate.\n *\n * Fetched once per session id. The record is registry state, not a live feed;\n * anything that changes during a run arrives on the session's event stream.\n */\nexport function useSessionInfo(\n client: WorkerDeckClient,\n sessionId: string | undefined,\n): UseSessionInfoResult {\n const [info, setInfo] = useState<SessionInfo | undefined>()\n const [loading, setLoading] = useState(!!sessionId)\n const [error, setError] = useState<string | undefined>()\n\n useEffect(() => {\n if (!sessionId) {\n setInfo(undefined)\n setLoading(false)\n setError(undefined)\n return\n }\n let cancelled = false\n setLoading(true)\n setError(undefined)\n // The previous session's record must not linger under the new id — a stale\n // cwd would root a file tree in the wrong project.\n setInfo(undefined)\n client\n .getSession(sessionId)\n .then((next) => {\n if (cancelled) return\n setInfo(next)\n setLoading(false)\n })\n .catch((e: unknown) => {\n if (cancelled) return\n setError(e instanceof Error ? e.message : 'Session not found')\n setLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [client, sessionId])\n\n return { info, loading, error }\n}\n","/**\n * One open file, in whatever state its read got to.\n *\n * A tab exists from the moment it is opened, before any bytes arrive — the tab\n * strip is the record of what the user asked for, not of what the gateway has\n * answered, and a tab that only appeared once the read landed would make a slow\n * read look like a dead click.\n */\nexport type OpenFile = {\n /** Absolute host path — the tab's identity. Opening the same path twice\n * focuses the existing tab rather than making a second one. */\n path: string\n /** Last segment, for the tab label. */\n name: string\n status: 'loading' | 'ready' | 'binary' | 'error'\n /** The text **as last seen on disk** — never the user's edits. */\n content?: string\n /**\n * The user's unsaved text. Absent when nothing has been typed since the last\n * read or save.\n *\n * Kept separate from `content` rather than overwriting it, because a\n * conditional write needs to know both: what is being sent, and what the\n * `hash` describes. Collapsing them would make \"did this change?\" unanswerable\n * after the first keystroke.\n */\n draft?: string\n bytes?: number\n /**\n * sha256 of the bytes `content` was read from — the `expectedHash` for the\n * next write.\n *\n * This is the whole safety mechanism: `/fs/write` is conditional *always*, so\n * a tab that lost its hash could not save at all without re-reading, and\n * re-reading to save is precisely the race the conditional write exists to\n * prevent.\n */\n hash?: string\n modifiedAt?: number\n /** Why the read failed, verbatim from the gateway. */\n error?: string\n /** A write is in flight. */\n saving?: boolean\n /** Why the last write failed, verbatim from the gateway. */\n saveError?: string\n /**\n * The file changed on disk since this tab read it — the gateway answered 409.\n *\n * Held as a distinct flag rather than folded into `saveError` because it is\n * the one failure with a *choice* attached (reload, overwrite, keep editing)\n * rather than a message to read.\n */\n conflict?: boolean\n}\n\n/** Whether a tab has edits that are not on disk. Derived, so typing something\n * and undoing it back leaves the tab clean — which is what an editor should do\n * and what a boolean flag set on first keystroke would get wrong. */\nexport function isDirty(file: OpenFile): boolean {\n return file.draft !== undefined && file.draft !== file.content\n}\n\n/** What a tab would write: its edits if it has any, else what it read. */\nexport function currentText(file: OpenFile): string {\n return file.draft ?? file.content ?? ''\n}\n\nexport type OpenFilesState = {\n /** Tab order, left to right. */\n files: OpenFile[]\n /** Absolute path of the focused tab, or undefined when nothing is open. */\n activePath?: string\n}\n\nexport type OpenFilesAction =\n | { type: 'open'; path: string }\n | { type: 'close'; path: string }\n | { type: 'closeAll' }\n | { type: 'activate'; path: string }\n /** A read landed. Ignored if the tab was closed while it was in flight. */\n | {\n type: 'loaded'\n path: string\n content: string\n encoding: 'utf8' | 'base64'\n bytes: number\n hash: string\n modifiedAt: number\n }\n | { type: 'failed'; path: string; error: string }\n /** The user typed. */\n | { type: 'edit'; path: string; content: string }\n /** Throw away unsaved edits and go back to what was read. */\n | { type: 'revert'; path: string }\n | { type: 'saveStart'; path: string }\n /** A write succeeded. `content` is **what was written**, not what the tab\n * holds now — the user may have kept typing while it was in flight. */\n | { type: 'saved'; path: string; content: string; bytes: number; hash: string; modifiedAt: number }\n | { type: 'saveFailed'; path: string; error: string; conflict?: boolean }\n /** Dismiss the conflict banner and carry on editing. */\n | { type: 'dismissConflict'; path: string }\n\nexport const initialOpenFilesState: OpenFilesState = { files: [] }\n\n/**\n * The tab strip and the editor's whole behaviour, as a pure function.\n *\n * The rules worth stating, because they are the ones a naive implementation\n * gets wrong:\n *\n * - **Opening an open path never re-reads it.** It focuses the tab. Re-reading\n * would silently discard that tab's unsaved edits on a double click.\n * - **Closing the focused tab focuses its right-hand neighbour**, falling back\n * to the left when it was last. Focusing \"the first tab\" instead is what makes\n * closing several tabs in a row jump the user around.\n * - **A successful save is applied against the text that was sent**, not against\n * the tab's current text. Typing during a save is normal; treating the write's\n * completion as \"the tab is now clean\" would silently drop those keystrokes.\n * - **Nothing here discards edits implicitly.** `revert` and `loaded` are the\n * only two things that clear a draft, and both are the direct result of\n * someone asking for it. The conditional write exists so a browser edit cannot\n * clobber the agent mid-run; this holds the same line in the other direction.\n *\n * Late results are addressed by path and dropped if that tab is gone, so a slow\n * read of a closed file cannot resurrect it.\n */\nexport function openFilesReducer(\n state: OpenFilesState,\n action: OpenFilesAction,\n): OpenFilesState {\n switch (action.type) {\n case 'open': {\n if (state.files.some((f) => f.path === action.path)) {\n return state.activePath === action.path ? state : { ...state, activePath: action.path }\n }\n const file: OpenFile = { path: action.path, name: baseName(action.path), status: 'loading' }\n return { files: [...state.files, file], activePath: action.path }\n }\n\n case 'close': {\n const index = state.files.findIndex((f) => f.path === action.path)\n if (index === -1) return state\n const files = state.files.filter((f) => f.path !== action.path)\n if (state.activePath !== action.path) return { ...state, files }\n // The neighbour that was to the right has slid into this index; if the\n // closed tab was last, take the one now at the end.\n const next = files[index] ?? files[index - 1]\n return { files, activePath: next?.path }\n }\n\n case 'closeAll':\n return initialOpenFilesState\n\n case 'activate':\n if (!state.files.some((f) => f.path === action.path)) return state\n return state.activePath === action.path ? state : { ...state, activePath: action.path }\n\n case 'loaded':\n // Also the \"reload from disk\" path: the draft goes, deliberately, because\n // the only way here with a dirty tab is someone choosing to discard.\n return patch(state, action.path, () => ({\n path: action.path,\n name: baseName(action.path),\n // A base64 answer means the bytes are not text. The viewer says so\n // rather than rendering the base64, which is the one thing nobody wants\n // to look at — and an editor must never open it, because saving it back\n // as utf8 would corrupt the file.\n status: action.encoding === 'utf8' ? 'ready' : 'binary',\n content: action.encoding === 'utf8' ? action.content : undefined,\n bytes: action.bytes,\n hash: action.hash,\n modifiedAt: action.modifiedAt,\n }))\n\n case 'failed':\n return patch(state, action.path, (file) => ({ ...file, status: 'error', error: action.error }))\n\n case 'edit':\n // Only a readable text file can be edited; a binary or errored tab has no\n // content the editor could have been showing.\n return patch(state, action.path, (file) =>\n file.status === 'ready' ? { ...file, draft: action.content } : file,\n )\n\n case 'revert':\n return patch(state, action.path, (file) => ({\n ...file,\n draft: undefined,\n saveError: undefined,\n conflict: false,\n }))\n\n case 'saveStart':\n return patch(state, action.path, (file) => ({\n ...file,\n saving: true,\n saveError: undefined,\n conflict: false,\n }))\n\n case 'saved':\n return patch(state, action.path, (file) => ({\n ...file,\n saving: false,\n saveError: undefined,\n conflict: false,\n content: action.content,\n bytes: action.bytes,\n hash: action.hash,\n modifiedAt: action.modifiedAt,\n // Keystrokes that landed mid-flight survive; a draft equal to what was\n // written is simply no longer a draft.\n draft: file.draft === action.content ? undefined : file.draft,\n }))\n\n case 'saveFailed':\n return patch(state, action.path, (file) => ({\n ...file,\n saving: false,\n saveError: action.error,\n conflict: action.conflict ?? false,\n }))\n\n case 'dismissConflict':\n return patch(state, action.path, (file) => ({\n ...file,\n conflict: false,\n saveError: undefined,\n }))\n }\n}\n\n/** Replace one file in place, preserving tab order; a no-op if it was closed\n * while the request was in flight. */\nfunction patch(\n state: OpenFilesState,\n path: string,\n next: (file: OpenFile) => OpenFile,\n): OpenFilesState {\n const index = state.files.findIndex((f) => f.path === path)\n if (index === -1) return state\n const current = state.files[index]!\n const updated = next(current)\n if (updated === current) return state\n const files = state.files.slice()\n files[index] = updated\n return { ...state, files }\n}\n\n/** Last path segment. Trailing slashes are not expected here — these are file\n * paths from `/fs/list` and `/fs/find` — but a bare `/` should still show as\n * something rather than as an empty tab. */\nfunction baseName(path: string): string {\n const trimmed = path.endsWith('/') ? path.slice(0, -1) : path\n return trimmed.slice(trimmed.lastIndexOf('/') + 1) || trimmed || path\n}\n","import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react'\nimport { WorkerDeckError, type WorkerDeckClient } from '@workerdeck/client'\nimport {\n currentText,\n initialOpenFilesState,\n isDirty,\n openFilesReducer,\n type OpenFile,\n type OpenFilesState,\n} from '../lib/open-files.ts'\n\nexport type UseOpenFilesResult = OpenFilesState & {\n /** The focused file, resolved — what the editor renders. */\n active: OpenFile | undefined\n /** Any tab with unsaved edits — what a close or unload guard asks. */\n hasUnsaved: boolean\n /** Open a path, or focus it if it is already open. */\n open: (path: string) => void\n close: (path: string) => void\n closeAll: () => void\n activate: (path: string) => void\n /** Record a keystroke. Pure state; nothing is written until `save`. */\n edit: (path: string, content: string) => void\n /** Write the tab's edits, conditional on the hash it read. No-op if clean. */\n save: (path: string) => Promise<void>\n /** Throw the tab's edits away and go back to what was read. */\n revert: (path: string) => void\n /** Re-read from disk. **Discards unsaved edits** — only call on an explicit\n * choice, never to \"refresh\". */\n reload: (path: string) => void\n /** Resolve a conflict by taking this tab's version: re-read for the current\n * hash, then write the draft against it. */\n overwrite: (path: string) => Promise<void>\n /** Dismiss the conflict banner without resolving it. */\n dismissConflict: (path: string) => void\n}\n\n/**\n * The open-file tabs of a workspace: which files are open, which one is focused,\n * the bytes behind each, and the edits on top of them.\n *\n * Reads are fired from an effect keyed on \"which tabs are still loading\" rather\n * than from `open` itself, so the reducer stays pure and a tab that was opened,\n * closed and reopened does not carry a stale in-flight request with it.\n *\n * Deliberately **not** given the session's cwd: a tab is an absolute host path,\n * and where it came from — the tree, a search hit, a path in the transcript — is\n * the caller's business. Containment is the server's job on every `/fs/read` and\n * `/fs/write`, not something re-derived here from a directory this hook would\n * have to trust.\n */\nexport function useOpenFiles(client: WorkerDeckClient): UseOpenFilesResult {\n const [state, dispatch] = useReducer(openFilesReducer, initialOpenFilesState)\n\n // Paths whose read has been started. Not derived from status, because a tab\n // stays 'loading' for the whole round trip and the effect re-runs on every\n // unrelated tab change in the meantime.\n const requested = useRef(new Set<string>())\n // Reads and writes outlive the component on a fast close-and-unmount; the flag\n // is what stops a resolved promise dispatching into a dead reducer.\n const alive = useRef(true)\n useEffect(() => {\n alive.current = true\n return () => {\n alive.current = false\n }\n }, [])\n\n // The latest state, for callbacks that must read a tab at call time rather\n // than close over the render they were created in — `save` is invoked from a\n // keybinding that outlives any single render.\n const latest = useRef(state)\n useEffect(() => {\n latest.current = state\n }, [state])\n\n const loading = state.files.filter((f) => f.status === 'loading')\n // Join the paths so the effect's identity tracks the *set* of pending reads,\n // not the array that the reducer rebuilds on every action.\n const pending = loading.map((f) => f.path).join('\\n')\n\n const read = useCallback(\n (path: string) =>\n client.readHostFile(path).then((response) => {\n if (!alive.current) return undefined\n dispatch({\n type: 'loaded',\n // The gateway answers with the canonical path; the tab is keyed on\n // what was asked for, so dispatch under that and let the response's\n // own path stay an implementation detail of the read.\n path,\n content: response.content,\n encoding: response.encoding,\n bytes: response.bytes,\n hash: response.hash,\n modifiedAt: response.modifiedAt,\n })\n return response\n }),\n [client],\n )\n\n useEffect(() => {\n for (const path of pending ? pending.split('\\n') : []) {\n if (requested.current.has(path)) continue\n requested.current.add(path)\n read(path).catch((e: unknown) => {\n if (!alive.current) return\n dispatch({\n type: 'failed',\n path,\n error: e instanceof Error ? e.message : 'Could not read that file',\n })\n })\n }\n }, [read, pending])\n\n const open = useCallback((path: string) => dispatch({ type: 'open', path }), [])\n const close = useCallback((path: string) => {\n // Forget the request too, so reopening the tab reads again rather than\n // sitting on 'loading' forever.\n requested.current.delete(path)\n dispatch({ type: 'close', path })\n }, [])\n const closeAll = useCallback(() => {\n requested.current.clear()\n dispatch({ type: 'closeAll' })\n }, [])\n const activate = useCallback((path: string) => dispatch({ type: 'activate', path }), [])\n const edit = useCallback(\n (path: string, content: string) => dispatch({ type: 'edit', path, content }),\n [],\n )\n const revert = useCallback((path: string) => dispatch({ type: 'revert', path }), [])\n const dismissConflict = useCallback(\n (path: string) => dispatch({ type: 'dismissConflict', path }),\n [],\n )\n\n const reload = useCallback(\n (path: string) => {\n requested.current.add(path)\n read(path).catch((e: unknown) => {\n if (!alive.current) return\n dispatch({\n type: 'failed',\n path,\n error: e instanceof Error ? e.message : 'Could not re-read that file',\n })\n })\n },\n [read],\n )\n\n /** One conditional write. Shared by `save` and `overwrite`, which differ only\n * in where the hash came from. */\n const write = useCallback(\n async (path: string, text: string, expectedHash: string | undefined) => {\n try {\n const response = await client.writeHostFile({ path, content: text, expectedHash })\n if (!alive.current) return\n dispatch({\n type: 'saved',\n path,\n content: text,\n bytes: response.bytes,\n hash: response.hash,\n modifiedAt: response.modifiedAt,\n })\n } catch (e) {\n if (!alive.current) return\n // 409 is the whole point of the conditional write: the file moved under\n // this tab. It is a choice to offer, not a message to print.\n const conflict = e instanceof WorkerDeckError && e.status === 409\n dispatch({\n type: 'saveFailed',\n path,\n conflict,\n error: conflict\n ? 'This file changed on disk since you opened it.'\n : e instanceof Error\n ? e.message\n : 'Could not save that file',\n })\n }\n },\n [client],\n )\n\n const save = useCallback(\n async (path: string) => {\n const file = latest.current.files.find((f) => f.path === path)\n if (!file || file.saving || !isDirty(file)) return\n dispatch({ type: 'saveStart', path })\n await write(path, currentText(file), file.hash)\n },\n [write],\n )\n\n const overwrite = useCallback(\n async (path: string) => {\n const file = latest.current.files.find((f) => f.path === path)\n if (!file || file.saving) return\n // The text to keep, captured before the re-read — `loaded` would clear the\n // draft, which is exactly what \"take mine\" must not do.\n const mine = currentText(file)\n dispatch({ type: 'saveStart', path })\n try {\n // There is no unconditional overwrite by design, so taking this tab's\n // version means learning the *current* hash and writing against it. The\n // window between this read and the write is small but real; a second 409\n // is the correct answer if the agent writes inside it.\n const fresh = await client.readHostFile(path)\n if (!alive.current) return\n await write(path, mine, fresh.hash)\n } catch (e) {\n if (!alive.current) return\n dispatch({\n type: 'saveFailed',\n path,\n error: e instanceof Error ? e.message : 'Could not save that file',\n })\n }\n },\n [client, write],\n )\n\n const active = useMemo(\n () => state.files.find((f) => f.path === state.activePath),\n [state.files, state.activePath],\n )\n const hasUnsaved = useMemo(() => state.files.some(isDirty), [state.files])\n\n return {\n ...state,\n active,\n hasUnsaved,\n open,\n close,\n closeAll,\n activate,\n edit,\n save,\n revert,\n reload,\n overwrite,\n dismissConflict,\n }\n}\n","import type { SessionHandle } from '@workerdeck/client'\nimport type { RunScriptResult, SandboxEngine, SandboxVfs } from '@workerdeck/sandbox'\nimport type { ToolCallRequestFrame } from '@workerdeck/protocol'\n\n/** What the host was asked to do and how it went (for UI/telemetry). */\nexport type ToolHostExecution = {\n executionId: string\n toolName: string\n status: 'running' | 'settled' | 'failed' | 'canceled'\n reason?: string\n startedAt: number\n endedAt?: number\n}\n\nexport type ToolHostRunner = (request: {\n script: string\n vfs: SandboxVfs\n timeoutMs: number\n memoryLimitBytes: number\n signal: AbortSignal\n}) => Promise<RunScriptResult>\n\n/**\n * Result a client tool handler returns. Return a plain value and it is sent as\n * JSON; return an object with `error` to fail the call with a reason the agent\n * can adapt to.\n */\nexport type ClientToolResult =\n | { value: unknown }\n | { error: string; reason?: string }\n\n/**\n * Handler for a client-registered tool. Receives the model's validated input\n * and returns a result — or throws, which is treated as a host error.\n */\nexport type ClientToolHandler = (\n input: unknown,\n context: { executionId: string; signal: AbortSignal },\n) => ClientToolResult | Promise<ClientToolResult>\n\nexport type ToolCallHostOptions = {\n /** Tools this client will execute. Anything else is refused, so a server can\n * never talk this tab into running something it didn't opt into.\n * Default: `['eval_script']`. */\n tools?: string[]\n /**\n * Client-side tool handlers, keyed by tool name. When a `tool_call_request`\n * arrives for a name in this map, the handler is called instead of the\n * sandbox. The tool must also appear in {@link tools} (it is added\n * automatically when `clientTools` is set).\n *\n * This is the client half of the round trip — the server half is registering\n * the tool's schema (via `tools` on `ProviderRunnerOptions` or\n * `EngineSessionOptions`). Together they let an embedder define a tool the\n * model can call and the client handles:\n *\n * ```ts\n * // Server: register the schema\n * tools: { app_navigate: { trust: 'sandboxed', tool: tool({ ... }) } }\n * // Client: handle the call\n * <SessionPanel clientTools={{ app_navigate: (input) => ({ value: 'ok' }) }} />\n * ```\n */\n clientTools?: Record<string, ClientToolHandler>\n /** Guest wall-clock limit, unless the request asks for less. Default 5000. */\n timeoutMs?: number\n /** Guest allocator cap, unless the request asks for less. Default 64 MiB. */\n memoryLimitBytes?: number\n /**\n * Load the WASM guest engine. Called at most once, on the first bridged call\n * — nothing is downloaded or parsed until a session actually bridges one.\n * Defaults to `@workerdeck/sandbox` with the single-file browser build.\n */\n loadEngine?: () => Promise<SandboxEngine>\n /**\n * Run the script. Defaults to executing on this thread, which is fine for the\n * short, time-boxed evaluations this is built for. Supply your own (a Web\n * Worker running the same engine) to keep long evaluations off the UI thread\n * — the guest deadline preempts the interpreter, but only between bytecode\n * ops on whichever thread it runs on.\n */\n execute?: ToolHostRunner\n /** Host-gated fetch for the guest. Omitted = the guest has no network at all. */\n fetchText?: (url: string) => Promise<string>\n /** Observe executions (rendering, logging). */\n onExecution?: (execution: ToolHostExecution) => void\n}\n\n/**\n * Answers server-bridged tool calls by executing them in this browser tab.\n * Framework-free — {@link useToolCallHost} is a thin React wrapper.\n *\n * The point is data locality: documents fetched or held client-side can be\n * evaluated here and never touch the server. The engine loads lazily, so a page\n * that never bridges a call never pays for the WASM guest.\n */\nexport function createToolCallHost(\n handle: SessionHandle,\n options: ToolCallHostOptions = {},\n): { dispose: () => void } {\n const inFlight = new Map<string, AbortController>()\n let enginePromise: Promise<SandboxEngine> | undefined\n let disposed = false\n\n const track = (execution: ToolHostExecution) => options.onExecution?.(execution)\n\n const refuse = (frame: ToolCallRequestFrame, reason: string, error: string, startedAt: number) => {\n handle.sendToolCallError(frame.executionId, reason, error)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'failed',\n reason,\n startedAt,\n endedAt: Date.now(),\n })\n }\n\n const runClientTool = async (\n frame: ToolCallRequestFrame,\n handler: ClientToolHandler,\n ): Promise<void> => {\n const startedAt = Date.now()\n const controller = new AbortController()\n inFlight.set(frame.executionId, controller)\n track({ executionId: frame.executionId, toolName: frame.toolName, status: 'running', startedAt })\n\n try {\n const result = await handler(frame.input, {\n executionId: frame.executionId,\n signal: controller.signal,\n })\n if (disposed || !inFlight.has(frame.executionId)) return\n if ('error' in result) {\n handle.sendToolCallError(frame.executionId, result.reason ?? 'client_error', result.error)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'failed',\n reason: result.reason ?? 'client_error',\n startedAt,\n endedAt: Date.now(),\n })\n } else {\n handle.sendToolCallResult(frame.executionId, { type: 'json', value: result.value })\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'settled',\n startedAt,\n endedAt: Date.now(),\n })\n }\n } catch (error) {\n if (disposed || !inFlight.has(frame.executionId)) return\n refuse(frame, 'host_error', error instanceof Error ? error.message : String(error), startedAt)\n } finally {\n inFlight.delete(frame.executionId)\n }\n }\n\n const run = async (frame: ToolCallRequestFrame): Promise<void> => {\n const startedAt = Date.now()\n // Client tool handlers take priority: they are purpose-built for the tool.\n const clientHandler = options.clientTools?.[frame.toolName]\n if (clientHandler) {\n return runClientTool(frame, clientHandler)\n }\n const allowed = options.tools ?? ['eval_script']\n if (!allowed.includes(frame.toolName)) {\n refuse(frame, 'unsupported_tool', `this client does not execute '${frame.toolName}'`, startedAt)\n return\n }\n const script = (frame.input as { script?: unknown } | undefined)?.script\n if (typeof script !== 'string') {\n refuse(frame, 'invalid_input', 'expected a string `script` input', startedAt)\n return\n }\n\n const controller = new AbortController()\n inFlight.set(frame.executionId, controller)\n track({ executionId: frame.executionId, toolName: frame.toolName, status: 'running', startedAt })\n\n try {\n const sandbox = await import('@workerdeck/sandbox')\n const vfs = sandbox.createVfs(frame.vfsSeed)\n // Never exceed what the server asked for: it owns the deadline it will\n // give up at, and answering after that is wasted work.\n const timeoutMs = Math.min(\n frame.limits?.timeoutMs ?? Number.POSITIVE_INFINITY,\n options.timeoutMs ?? 5000,\n )\n const memoryLimitBytes = Math.min(\n frame.limits?.memoryLimitBytes ?? Number.POSITIVE_INFINITY,\n options.memoryLimitBytes ?? 64 * 1024 * 1024,\n )\n\n const result = options.execute\n ? await options.execute({ script, vfs, timeoutMs, memoryLimitBytes, signal: controller.signal })\n : await (async () => {\n enginePromise ??= (options.loadEngine ?? defaultLoadEngine)()\n return sandbox.runScript(await enginePromise, {\n script,\n vfs,\n timeoutMs,\n memoryLimitBytes,\n signal: controller.signal,\n fetchText: options.fetchText,\n })\n })()\n\n // Cancelled or torn down while we worked: the server is no longer waiting.\n if (disposed || !inFlight.has(frame.executionId)) return\n const logs = result.logs.map((l) => `[${l.level}] ${l.text}`)\n if (result.ok) {\n handle.sendToolCallResult(frame.executionId, { type: 'json', value: result.value }, logs)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'settled',\n startedAt,\n endedAt: Date.now(),\n })\n } else {\n handle.sendToolCallError(frame.executionId, result.reason, result.error, logs)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'failed',\n reason: result.reason,\n startedAt,\n endedAt: Date.now(),\n })\n }\n } catch (error) {\n if (disposed || !inFlight.has(frame.executionId)) return\n // Engine load failures land here �� tell the server so the agent can adapt\n // instead of waiting out the deadline.\n refuse(frame, 'host_error', error instanceof Error ? error.message : String(error), startedAt)\n } finally {\n inFlight.delete(frame.executionId)\n }\n }\n\n const offRequest = handle.on('toolCallRequest', (frame) => void run(frame))\n const offCancel = handle.on('toolCallCanceled', ({ executionId, reason }) => {\n const controller = inFlight.get(executionId)\n if (!controller) return\n controller.abort()\n inFlight.delete(executionId)\n track({\n executionId,\n toolName: '',\n status: 'canceled',\n reason,\n startedAt: Date.now(),\n endedAt: Date.now(),\n })\n })\n\n return {\n dispose: () => {\n disposed = true\n offRequest()\n offCancel()\n for (const controller of inFlight.values()) controller.abort()\n inFlight.clear()\n },\n }\n}\n\n/** The single-file browser build keeps this to one lazy chunk — no separate\n * .wasm fetch, and nothing at all until the first bridged call. */\nasync function defaultLoadEngine(): Promise<SandboxEngine> {\n const [sandbox, variant] = await Promise.all([\n import('@workerdeck/sandbox'),\n import('@jitl/quickjs-singlefile-browser-release-asyncify'),\n ])\n return sandbox.loadEngine(variant as never)\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type { SessionHandle } from '@workerdeck/client'\nimport {\n createToolCallHost,\n type ToolCallHostOptions,\n type ToolHostExecution,\n} from '../lib/tool-host.ts'\n\nexport type UseToolCallHostOptions = ToolCallHostOptions & {\n /** Turn the host off without unmounting. Default true. */\n enabled?: boolean\n /** How many recent executions to keep for rendering. Default 50. */\n historyLimit?: number\n}\n\n/**\n * React wrapper around {@link createToolCallHost}: subscribes while mounted and\n * exposes recent executions for rendering. All the logic lives in the\n * framework-free host — this only manages the subscription's lifetime.\n */\nexport function useToolCallHost(\n handle: SessionHandle | undefined,\n options: UseToolCallHostOptions = {},\n): { executions: ToolHostExecution[] } {\n const [executions, setExecutions] = useState<ToolHostExecution[]>([])\n // Read options at call time so re-renders never tear down the subscription.\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n useEffect(() => {\n if (!handle || options.enabled === false) return\n const host = createToolCallHost(handle, {\n // Delegate every option through the ref, so a caller passing inline\n // objects/closures (the common case) doesn't resubscribe each render.\n get tools() {\n // Merge explicit `tools` with the names from `clientTools` so the host\n // accepts calls for both sandbox-executed and client-handled tools.\n const base = optionsRef.current.tools\n const client = optionsRef.current.clientTools\n if (!client) return base\n const clientNames = Object.keys(client)\n return base ? [...new Set([...base, ...clientNames])] : clientNames\n },\n get clientTools() {\n return optionsRef.current.clientTools\n },\n get timeoutMs() {\n return optionsRef.current.timeoutMs\n },\n get memoryLimitBytes() {\n return optionsRef.current.memoryLimitBytes\n },\n get loadEngine() {\n return optionsRef.current.loadEngine\n },\n get execute() {\n return optionsRef.current.execute\n },\n get fetchText() {\n return optionsRef.current.fetchText\n },\n onExecution: (execution) => {\n optionsRef.current.onExecution?.(execution)\n const limit = optionsRef.current.historyLimit ?? 50\n setExecutions((prev) => [\n ...prev.filter((e) => e.executionId !== execution.executionId),\n execution,\n ].slice(-limit))\n },\n })\n return () => host.dispose()\n }, [handle, options.enabled])\n\n return { executions }\n}\n","import type { TranscriptItem } from './transcript.ts'\n\n/**\n * \"What happened while you were away\", counted rather than written.\n *\n * Deterministic on purpose. A prose recap would mean spending a turn — tokens,\n * context and latency — on a summary nobody asked the model for, and it would\n * be wrong in the one case that matters most (a session that failed while\n * unattended, where the model is exactly who you shouldn't ask). Everything\n * here is already in the transcript; this only counts it.\n *\n * Framework-free and pure, like the reducer it reads from: both clients render\n * the same recap from the same numbers.\n */\nexport type RecapSummary = {\n /** Completed turns — `turn_result` rows, the engine's own unit of work. */\n turns: number\n /** Messages the model wrote. Streaming ones count: they are on screen. */\n replies: number\n /** Tool calls started, and the distinct names, most-used first. */\n tools: number\n toolNames: string[]\n /** Files the agent handed over (`file_delivered`). */\n files: number\n /** Failed turns and failed tool calls, together — what you'd want to know\n * first on coming back. */\n errors: number\n /** Approvals still waiting. Not a count of what happened, but the reason to\n * look now rather than later. */\n pending: number\n /** Any of the above non-zero. A recap of nothing is noise. */\n any: boolean\n}\n\n/** The `TranscriptState` fields a recap reads — structural, so a caller can\n * pass the whole state or just these. */\nexport type RecapInput = {\n items: readonly TranscriptItem[]\n pendingApprovals?: readonly unknown[]\n}\n\n/**\n * Summarize the items from `fromIndex` onward — the boundary being the number\n * of items that existed when the session was last looked at.\n *\n * An out-of-range boundary is clamped rather than rejected: a transcript can\n * *shrink* (a `/clear`, a fresh attach after a compaction), and the honest\n * reading of \"you last saw 40 items, there are now 12\" is \"everything here is\n * new\", not a negative count.\n */\nexport function summarizeSince(state: RecapInput, fromIndex: number): RecapSummary {\n const start = Math.max(0, Math.min(fromIndex, state.items.length))\n const fresh = state.items.slice(start)\n const toolCounts = new Map<string, number>()\n let turns = 0\n let replies = 0\n let tools = 0\n let files = 0\n let errors = 0\n\n for (const item of fresh) {\n switch (item.kind) {\n case 'turn_result':\n turns += 1\n if (item.isError) errors += 1\n break\n case 'assistant_text':\n replies += 1\n break\n case 'tool_call':\n tools += 1\n toolCounts.set(item.name, (toolCounts.get(item.name) ?? 0) + 1)\n if (item.status === 'failed' || item.result?.isError) errors += 1\n break\n case 'file_delivered':\n files += 1\n break\n case 'notice':\n if (item.level === 'error') errors += 1\n break\n default:\n break\n }\n }\n\n const toolNames = [...toolCounts.entries()]\n .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))\n .map(([name]) => name)\n const pending = state.pendingApprovals?.length ?? 0\n return {\n turns,\n replies,\n tools,\n toolNames,\n files,\n errors,\n pending,\n any: turns + replies + tools + files + errors + pending > 0,\n }\n}\n\n/**\n * The recap as one line of text, in the order a person reads it: what got done,\n * what it used, what went wrong, what is waiting.\n *\n * Returns `undefined` when there is nothing to say, so a caller can render the\n * row or not on the value alone.\n */\nexport function recapLine(summary: RecapSummary): string | undefined {\n if (!summary.any) return undefined\n const parts: string[] = []\n if (summary.turns > 0) parts.push(plural(summary.turns, 'turn'))\n else if (summary.replies > 0) parts.push(plural(summary.replies, 'reply', 'replies'))\n if (summary.tools > 0) {\n // Three names is enough to recognise what it was doing; beyond that the\n // count carries more than the list.\n const named = summary.toolNames.slice(0, 3).join(', ')\n const rest = summary.toolNames.length - 3\n parts.push(`${plural(summary.tools, 'tool call')}${named ? ` (${named}${rest > 0 ? `, +${rest}` : ''})` : ''}`)\n }\n if (summary.files > 0) parts.push(plural(summary.files, 'file'))\n if (summary.errors > 0) parts.push(plural(summary.errors, 'error'))\n if (summary.pending > 0) parts.push(`${plural(summary.pending, 'approval')} waiting`)\n return parts.join(' · ')\n}\n\nfunction plural(count: number, one: string, many = `${one}s`): string {\n return `${count} ${count === 1 ? one : many}`\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["entries"],"sources":["../src/lib/transcript.ts","../src/lib/transcript-cache.ts","../src/lib/attach-plan.ts","../src/hooks/use-session.ts","../src/lib/profile-usage-cache.ts","../src/lib/draft-store.ts","../src/hooks/use-attachments.ts","../src/lib/prompt-tokens.ts","../src/lib/async-guards.ts","../src/lib/host-tree.ts","../src/hooks/use-host-files.ts","../src/hooks/use-project-icons.ts","../src/hooks/use-draft.ts","../src/hooks/use-profile-usage.ts","../src/hooks/use-session-info.ts","../src/lib/open-files.ts","../src/hooks/use-open-files.ts","../src/lib/tool-host.ts","../src/hooks/use-tool-host.ts","../src/lib/recap.ts"],"sourcesContent":["import { ENGINE_CAPABILITIES, mergeUsage, orderUsageWindows } from '@workerdeck/protocol'\nimport type {\n ContentBlock,\n ContextUsage,\n EngineCapabilities,\n FilePatch,\n MessageAttachment,\n ModelOption,\n PermissionMode,\n PermissionRequest,\n ProfileEngine,\n RateLimitInfo,\n SessionEvent,\n SessionInfo,\n SessionStatus,\n SkillInfo,\n SlashCommandInfo,\n ToolExecutionBackend,\n ToolExecutionOutput,\n ToolResultBlock,\n UsageWindowRow,\n} from '@workerdeck/protocol'\n\nexport type ToolResultImageRef = { partIndex: number; mediaType: string; bytes: number; sourceSeq: number }\n\nexport type TranscriptItem =\n | {\n kind: 'user'\n id: string\n text: string\n attachments?: MessageAttachment[]\n // Optional, not `string | null` like the other kinds: there forgetting to stamp it must not typecheck; here almost every prompt has no parent.\n parentToolUseId?: string\n }\n | {\n kind: 'assistant_text'\n id: string\n text: string\n streaming: boolean\n parentToolUseId: string | null\n }\n | { kind: 'thinking'; id: string; text: string; parentToolUseId: string | null }\n | {\n kind: 'tool_call'\n id: string\n name: string\n input: unknown\n parentToolUseId: string | null\n // The event's own `ts`, never a receive time — replay-stable, stamped at creation only; absent reads as \"no elapsed\", never as the epoch.\n ts?: number\n status: 'running' | 'pending' | 'deferred' | 'settled' | 'failed'\n result?: {\n text: string\n isError: boolean\n truncated?: boolean\n totalChars?: number\n sourceSeq?: number\n images?: ReadonlyArray<ToolResultImageRef>\n }\n patch?: FilePatch\n executionId?: string\n backend?: ToolExecutionBackend\n logs?: string[]\n }\n | {\n kind: 'turn_result'\n id: string\n subtype: string\n isError: boolean\n durationMs: number\n totalCostUsd: number\n errors?: string[]\n }\n | { kind: 'notice'; id: string; level: 'info' | 'error'; text: string }\n | { kind: 'file_delivered'; id: string; path: string; bytes: number; description?: string }\n\nexport type ProducedFileRef = {\n fileId: string\n mediaType?: string\n bytes?: number\n}\n\nexport type TranscriptState = {\n status: SessionStatus\n statusDetail?: string\n model?: string\n cwd?: string\n sdkSessionId?: string\n engine?: ProfileEngine\n capabilities: EngineCapabilities\n session?: SessionInfo\n models?: ModelOption[]\n commands?: SlashCommandInfo[]\n skills?: SkillInfo[]\n // Keyed by the absolute path the runner reported: a tool card looks up the `savedPath` in its input and resolves it via `client.producedFileUrl`.\n producedFiles?: Record<string, ProducedFileRef>\n\n defaultModel?: string\n permissionMode?: PermissionMode\n contextUsage?: ContextUsage\n rateLimits?: Record<string, RateLimitInfo>\n rateLimitsUpdatedAt?: number\n subscriptionType?: string\n items: TranscriptItem[]\n pendingApprovals: PermissionRequest[]\n totalCostUsd: number\n lastSeq: number\n}\n\nexport const initialTranscriptState: TranscriptState = {\n status: 'starting',\n capabilities: ENGINE_CAPABILITIES.claude,\n items: [],\n pendingApprovals: [],\n totalCostUsd: 0,\n lastSeq: 0,\n}\n\nconst STREAMING_ID = 'streaming'\nconst STREAMING_THINKING_ID = 'streaming-thinking'\n\nconst LOCAL_COMMAND_OUTPUT = /^<local-command-(stdout|stderr)>([\\s\\S]*?)<\\/local-command-\\1>$/\n\nconst COMMAND_NAME = /<command-name>([\\s\\S]*?)<\\/command-name>/\nconst COMMAND_ARGS = /<command-args>([\\s\\S]*?)<\\/command-args>/\nfunction streamingTextId(parentToolUseId: string | null): string {\n return parentToolUseId == null ? STREAMING_ID : `${STREAMING_ID}:${parentToolUseId}`\n}\nfunction streamingThinkingId(parentToolUseId: string | null): string {\n return parentToolUseId == null ? STREAMING_THINKING_ID : `${STREAMING_THINKING_ID}:${parentToolUseId}`\n}\nfunction isStreamingItem(item: TranscriptItem): boolean {\n return (\n (item.kind === 'assistant_text' && item.id.startsWith(STREAMING_ID)) ||\n (item.kind === 'thinking' && item.id.startsWith(STREAMING_THINKING_ID))\n )\n}\n\nfunction blockText(content: ToolResultBlock['content']): string {\n if (content === undefined) {\n return ''\n }\n if (typeof content === 'string') {\n return content\n }\n return content\n .map((part) => (typeof part.text === 'string' ? part.text : ''))\n .filter(Boolean)\n .join('\\n')\n}\n\nfunction imageRefsOf(content: ToolResultBlock['content'], seq: number): ReadonlyArray<ToolResultImageRef> | undefined {\n if (!Array.isArray(content)) {\n return undefined\n }\n const refs = content.flatMap((part) =>\n part.type === 'image_ref'\n ? [\n {\n partIndex: Number(part.part_index),\n mediaType: String(part.media_type ?? 'application/octet-stream'),\n bytes: Number(part.bytes ?? 0),\n sourceSeq: seq,\n },\n ]\n : [],\n )\n return refs.length > 0 ? refs : undefined\n}\n\nfunction contentToBlocks(content: string | ContentBlock[]): ContentBlock[] {\n return typeof content === 'string' ? [{ type: 'text', text: content }] : content\n}\n\nfunction outputText(output: ToolExecutionOutput): string {\n if (output.type === 'text') {\n return output.value\n }\n try {\n return JSON.stringify(output.value)\n } catch {\n return String(output.value)\n }\n}\n\nfunction slashCommandText(text: string): string | undefined {\n const name = COMMAND_NAME.exec(text)?.[1]?.trim()\n if (!name) {\n return undefined\n }\n const args = COMMAND_ARGS.exec(text)?.[1]?.trim()\n return args ? `${name} ${args}` : name\n}\n\nfunction upsert(items: TranscriptItem[], item: TranscriptItem): TranscriptItem[] {\n const index = items.findIndex((existing) => existing.id === item.id && existing.kind === item.kind)\n if (index === -1) {\n return [...items, item]\n }\n const next = [...items]\n next[index] = item\n return next\n}\n\nexport function seedFromSessionInfo(state: TranscriptState, info: SessionInfo): TranscriptState {\n // No event carries the engine — the snapshot is the only source.\n const engine = info.engine ?? state.engine\n return {\n ...state,\n // With held state (reconnect, warm cache seed) the held status stands: any change since is a `status_changed` in the replay span — events stay the one authority.\n status: state.lastSeq === 0 ? info.status : state.status,\n model: state.model ?? info.model,\n permissionMode: state.permissionMode ?? info.permissionMode,\n cwd: state.cwd ?? info.cwd,\n sdkSessionId: state.sdkSessionId ?? info.sdkSessionId,\n engine,\n capabilities: info.capabilities ?? ENGINE_CAPABILITIES[engine ?? 'claude'],\n session: info,\n }\n}\n\nexport function rateLimitWindows(state: TranscriptState): UsageWindowRow[] {\n return orderUsageWindows(mergeUsage({ rateLimits: state.rateLimits, updatedAt: state.rateLimitsUpdatedAt }, undefined))\n}\n\nexport function hydrateToolResult(state: TranscriptState, toolUseId: string, text: string): TranscriptState {\n let changed = false\n const items = state.items.map((item) => {\n if (item.kind !== 'tool_call' || item.id !== toolUseId || !item.result?.truncated) {\n return item\n }\n changed = true\n // `images` survives the text hydration: the refs carry their own `sourceSeq` because the result's clears here, and without them the row's pictures are unloadable.\n return {\n ...item,\n result: {\n text,\n isError: item.result.isError,\n ...(item.result.images && { images: item.result.images }),\n },\n }\n })\n return changed ? { ...state, items } : state\n}\n\nexport function applyEvent(state: TranscriptState, event: SessionEvent): TranscriptState {\n if (event.seq <= state.lastSeq) {\n return state\n }\n const base: TranscriptState = { ...state, lastSeq: event.seq }\n\n switch (event.type) {\n case 'system_init': {\n return {\n ...base,\n model: event.model,\n cwd: event.cwd,\n sdkSessionId: event.sdkSessionId,\n permissionMode: event.permissionMode,\n }\n }\n\n case 'status_changed': {\n return { ...base, status: event.status, statusDetail: event.detail }\n }\n\n case 'capabilities': {\n return {\n ...base,\n models: event.models,\n commands: event.commands,\n defaultModel: event.defaultModel ?? base.defaultModel,\n }\n }\n\n case 'skills': {\n return { ...base, skills: event.skills }\n }\n\n case 'file_produced': {\n return {\n ...base,\n producedFiles: {\n ...base.producedFiles,\n [event.path]: {\n fileId: event.fileId,\n ...(event.mediaType ? { mediaType: event.mediaType } : {}),\n ...(event.bytes !== undefined ? { bytes: event.bytes } : {}),\n },\n },\n }\n }\n\n case 'model_changed': {\n return event.model === undefined ? base : { ...base, model: event.model }\n }\n\n case 'permission_mode_changed': {\n return { ...base, permissionMode: event.mode }\n }\n\n case 'context_usage': {\n return { ...base, contextUsage: event.usage }\n }\n\n case 'rate_limit': {\n const key = event.info.rateLimitType\n if (!key) {\n return base\n }\n return {\n ...base,\n rateLimits: { ...base.rateLimits, [key]: event.info },\n rateLimitsUpdatedAt: event.ts,\n }\n }\n\n case 'plan_info': {\n return { ...base, subscriptionType: event.subscriptionType }\n }\n\n case 'conversation_reset': {\n return {\n ...base,\n items: [],\n contextUsage: undefined,\n sdkSessionId: event.sdkSessionId ?? base.sdkSessionId,\n }\n }\n\n case 'user_message': {\n let items = base.items\n for (const block of contentToBlocks(event.message.content)) {\n if (block.type === 'tool_result') {\n const toolResult = block as ToolResultBlock\n const isError = toolResult.is_error === true\n items = items.map((item) =>\n item.kind === 'tool_call' && item.id === toolResult.tool_use_id\n ? {\n ...item,\n status: isError ? 'failed' : 'settled',\n result: {\n text: blockText(toolResult.content),\n isError,\n ...(toolResult.truncated && {\n truncated: true as const,\n totalChars: toolResult.total_chars,\n sourceSeq: event.seq,\n }),\n ...(imageRefsOf(toolResult.content, event.seq) && {\n images: imageRefsOf(toolResult.content, event.seq),\n }),\n },\n ...(event.patch && { patch: event.patch }),\n }\n : item,\n )\n } else if (block.type === 'text' && !event.synthetic) {\n const text = (block as { text: string }).text\n const localOutput = LOCAL_COMMAND_OUTPUT.exec(text.trim())\n if (localOutput) {\n items = upsert(items, {\n kind: 'notice',\n id: event.uuid ?? `user-${event.seq}`,\n level: localOutput[1] === 'stderr' ? 'error' : 'info',\n text: localOutput[2].trim(),\n })\n } else {\n items = upsert(items, {\n kind: 'user',\n id: event.uuid ?? `user-${event.seq}`,\n text: slashCommandText(text) ?? text,\n attachments: event.attachments,\n ...(event.parentToolUseId != null && {\n parentToolUseId: event.parentToolUseId,\n }),\n })\n }\n }\n }\n return { ...base, items }\n }\n\n case 'assistant_message': {\n const streamingText = streamingTextId(event.parentToolUseId)\n const streamingThought = streamingThinkingId(event.parentToolUseId)\n let streamedThinking =\n base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'thinking' }> => item.kind === 'thinking' && item.id === streamingThought,\n )?.text ?? ''\n let items = base.items.filter(\n (item) =>\n !(item.kind === 'assistant_text' && item.id === streamingText) && !(item.kind === 'thinking' && item.id === streamingThought),\n )\n const blocks = contentToBlocks(event.message.content)\n blocks.forEach((block, index) => {\n const id = `${event.uuid}-${index}`\n if (block.type === 'text') {\n items = upsert(items, {\n kind: 'assistant_text',\n id,\n text: (block as { text: string }).text,\n streaming: false,\n parentToolUseId: event.parentToolUseId,\n })\n } else if (block.type === 'thinking') {\n const text = (block as { thinking: string }).thinking || streamedThinking\n streamedThinking = ''\n if (text.trim() === '') {\n return\n }\n items = upsert(items, {\n kind: 'thinking',\n id,\n text,\n parentToolUseId: event.parentToolUseId,\n })\n } else if (block.type === 'tool_use') {\n const toolUse = block as { id: string; name: string; input: unknown }\n items = upsert(items, {\n kind: 'tool_call',\n id: toolUse.id,\n name: toolUse.name,\n input: toolUse.input,\n parentToolUseId: event.parentToolUseId,\n status: 'running',\n ts: event.ts,\n })\n }\n })\n return { ...base, items }\n }\n\n case 'stream_delta': {\n const delta = event.event as {\n type: string\n delta?: { type?: string; text?: string; thinking?: string }\n }\n if (delta.type !== 'content_block_delta') {\n return base\n }\n if (delta.delta?.type === 'text_delta') {\n const id = streamingTextId(event.parentToolUseId)\n const existing = base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'assistant_text' }> => item.kind === 'assistant_text' && item.id === id,\n )\n const item: TranscriptItem = {\n kind: 'assistant_text',\n id,\n text: (existing?.text ?? '') + (delta.delta.text ?? ''),\n streaming: true,\n parentToolUseId: event.parentToolUseId,\n }\n return { ...base, items: upsert(base.items, item) }\n }\n if (delta.delta?.type === 'thinking_delta') {\n const id = streamingThinkingId(event.parentToolUseId)\n const existing = base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'thinking' }> => item.kind === 'thinking' && item.id === id,\n )\n const text = (existing?.text ?? '') + (delta.delta.thinking ?? '')\n // Whitespace-only (encrypted) thinking creates no item — `turn_result` would finalize a permanent empty row; text rebuilds from `existing`, so skipping loses nothing.\n if (text.trim() === '') {\n return base\n }\n const item: TranscriptItem = {\n kind: 'thinking',\n id,\n text,\n parentToolUseId: event.parentToolUseId,\n }\n return { ...base, items: upsert(base.items, item) }\n }\n return base\n }\n\n case 'turn_result': {\n return {\n ...base,\n totalCostUsd: event.totalCostUsd,\n items: [\n ...base.items.map((item) => {\n if (!isStreamingItem(item)) {\n return item\n }\n const agent = 'parentToolUseId' in item && item.parentToolUseId ? `-${item.parentToolUseId}` : ''\n return item.kind === 'assistant_text'\n ? { ...item, id: `text-${event.seq}${agent}`, streaming: false }\n : { ...item, id: `thinking-${event.seq}${agent}` }\n }),\n {\n kind: 'turn_result',\n id: `turn-${event.seq}`,\n subtype: event.subtype,\n isError: event.isError,\n durationMs: event.durationMs,\n totalCostUsd: event.totalCostUsd,\n errors: event.errors,\n },\n ],\n }\n }\n\n case 'permission_requested': {\n return { ...base, pendingApprovals: [...base.pendingApprovals, event.request] }\n }\n\n case 'permission_resolved': {\n return {\n ...base,\n pendingApprovals: base.pendingApprovals.filter((r) => r.id !== event.requestId),\n }\n }\n\n case 'execution_dispatched': {\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: event.deferred ? 'deferred' : 'pending',\n executionId: event.executionId,\n backend: event.backend,\n }\n : item,\n ),\n }\n }\n\n case 'execution_result': {\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: 'settled',\n executionId: event.executionId,\n result: { text: outputText(event.output), isError: false },\n logs: event.logs ?? item.logs,\n }\n : item,\n ),\n }\n }\n\n case 'execution_failed': {\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: 'failed',\n executionId: event.executionId,\n result: { text: `${event.reason}: ${event.error}`, isError: true },\n logs: event.logs ?? item.logs,\n }\n : item,\n ),\n }\n }\n\n case 'file_delivered': {\n return {\n ...base,\n items: [\n ...base.items,\n {\n kind: 'file_delivered',\n id: `file-${event.seq}`,\n path: event.path,\n bytes: event.bytes,\n description: event.description,\n },\n ],\n }\n }\n\n case 'session_error': {\n return {\n ...base,\n items: [...base.items, { kind: 'notice', id: `err-${event.seq}`, level: 'error', text: event.message }],\n }\n }\n\n case 'session_closed': {\n return {\n ...base,\n items: [\n ...base.items,\n {\n kind: 'notice',\n id: `closed-${event.seq}`,\n level: 'info',\n text: `Session closed (${event.reason})`,\n },\n ],\n }\n }\n\n case 'sdk_event':\n default: {\n return base\n }\n }\n}\n","import type { WorkerDeckClient } from '@workerdeck/client'\nimport type { TranscriptState } from './transcript.ts'\n\nconst MAX_ENTRIES = 5\n\nconst entries = new Map<string, TranscriptState>()\n\n// NUL separates unambiguously: `identityKey` is JSON.stringify output, so no two pairs spell one key.\nexport function transcriptCacheKey(client: WorkerDeckClient, sessionId: string): string {\n return `${client.identityKey}\\u0000${sessionId}`\n}\n\nexport function readTranscriptCache(key: string): TranscriptState | undefined {\n return entries.get(key)\n}\n\nexport function writeTranscriptCache(key: string, state: TranscriptState): void {\n entries.delete(key)\n entries.set(key, state)\n if (entries.size > MAX_ENTRIES) {\n const oldest = entries.keys().next().value\n if (oldest !== undefined) {\n entries.delete(oldest)\n }\n }\n}\n\nexport function deleteTranscriptCache(key: string): void {\n entries.delete(key)\n}\n\nexport function clearTranscriptCache(): void {\n entries.clear()\n}\n","import { initialTranscriptState, type TranscriptState } from './transcript.ts'\n\nexport function attachSeedToken(resyncSeq: number, key: string): string {\n return `${resyncSeq}:${key}`\n}\n\nexport type AttachInputs = {\n resyncSeq: number\n key: string\n seededFor: string\n current: TranscriptState\n cacheEnabled: boolean\n skipCache: boolean\n warm: TranscriptState | undefined\n}\n\nexport type AttachPlan = {\n held: TranscriptState\n seed: boolean\n seedToken: string\n afterSeq?: number\n}\n\nexport function planAttach(input: AttachInputs): AttachPlan {\n const seedToken = attachSeedToken(input.resyncSeq, input.key)\n const warm = input.cacheEnabled && !input.skipCache ? input.warm : undefined\n const seed = input.seededFor !== seedToken\n const held = seed ? (warm ?? initialTranscriptState) : input.current\n // `afterSeq` derives from the state held and never from a second cache read, which a racing write could move.\n return { held, seed, seedToken, ...(held.lastSeq > 0 ? { afterSeq: held.lastSeq } : {}) }\n}\n\nexport function shouldWriteParting(input: { cacheEnabled: boolean; skipCache: boolean; parting: TranscriptState }): boolean {\n return input.cacheEnabled && !input.skipCache && input.parting.lastSeq > 0 && input.parting.session !== undefined\n}\n","import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'\nimport type { WorkerDeckClient, SessionHandle } from '@workerdeck/client'\nimport { PROTOCOL_VERSION } from '@workerdeck/protocol'\nimport type { AttachedFrame, ModelOption, PermissionMode, SessionEvent } from '@workerdeck/protocol'\nimport { applyEvent, initialTranscriptState, hydrateToolResult, seedFromSessionInfo, type TranscriptState } from '../lib/transcript.ts'\nimport { deleteTranscriptCache, readTranscriptCache, transcriptCacheKey, writeTranscriptCache } from '../lib/transcript-cache.ts'\nimport { attachSeedToken, planAttach, shouldWriteParting } from '../lib/attach-plan.ts'\n\ntype SeedAction = { type: 'transcript_seed'; state: TranscriptState }\ntype HydrateAction = { type: 'transcript_hydrate_result'; toolUseId: string; text: string }\n\nfunction reduce(state: TranscriptState, action: SessionEvent | AttachedFrame | SeedAction | HydrateAction): TranscriptState {\n if (action.type === 'transcript_seed') {\n return action.state\n }\n if (action.type === 'transcript_hydrate_result') {\n return hydrateToolResult(state, action.toolUseId, action.text)\n }\n return action.type === 'attached' ? seedFromSessionInfo(state, action.session) : applyEvent(state, action)\n}\n\nexport type ConnectionState = 'live' | 'reconnecting' | 'offline'\n\n// Three failed attempts is ~3.5s of backoff — past a blip; the iOS client hardcodes the same threshold.\nconst OFFLINE_AFTER_ATTEMPTS = 3\n\nexport function initialReplayTarget(frame: AttachedFrame): number | undefined {\n return frame.replayingFrom === 0 && frame.session.lastSeq > 0 ? frame.session.lastSeq : undefined\n}\n\nexport function staleAttach(frame: AttachedFrame, held: TranscriptState): boolean {\n if (frame.replayingFrom === 0 || held.lastSeq === 0) {\n return false\n }\n if (frame.session.lastSeq < held.lastSeq) {\n return true\n }\n return held.session !== undefined && frame.session.createdAt !== held.session.createdAt\n}\n\nexport const REPLAY_HOLD_MAX_MS = 1500\n\nexport type UseClaudeSessionOptions = {\n onProtocolError?: (message: string) => void\n cacheTranscript?: boolean\n}\n\nexport type UseClaudeSessionResult = {\n state: TranscriptState\n connected: boolean\n connection: ConnectionState\n replaying: boolean\n protocolMismatch?: number\n models: ModelOption[]\n effectiveModel?: string\n handle: SessionHandle | undefined\n send: (text: string, attachmentIds?: string[]) => void\n approve: (requestId: string, updatedInput?: Record<string, unknown>) => void\n deny: (requestId: string, message?: string, interrupt?: boolean) => void\n interrupt: () => void\n clearContext: () => void\n setPermissionMode: (mode: PermissionMode) => void\n setModel: (model?: string) => void\n closeSession: () => void\n reconnectNow: () => void\n loadFullResult: (toolUseId: string) => Promise<boolean>\n}\n\nexport function useClaudeSession(\n client: WorkerDeckClient,\n sessionId: string | undefined,\n options?: UseClaudeSessionOptions,\n): UseClaudeSessionResult {\n const [state, dispatch] = useReducer(\n reduce,\n undefined,\n (): TranscriptState =>\n (options?.cacheTranscript !== false && sessionId !== undefined\n ? readTranscriptCache(transcriptCacheKey(client, sessionId))\n : undefined) ?? initialTranscriptState,\n )\n const [connection, setConnection] = useState<ConnectionState>('reconnecting')\n const [protocolMismatch, setProtocolMismatch] = useState<number | undefined>()\n const [replayTarget, setReplayTarget] = useState<number | undefined>()\n const [resyncSeq, setResyncSeq] = useState(0)\n // Ref for the stable callbacks below; state so consumers of `handle` re-render when the socket opens or the session switches.\n const [handleState, setHandleState] = useState<SessionHandle | undefined>()\n const handleRef = useRef<SessionHandle | null>(null)\n const optionsRef = useRef(options)\n optionsRef.current = options\n const stateRef = useRef(state)\n stateRef.current = state\n const seededForRef = useRef(attachSeedToken(0, sessionId === undefined ? '' : transcriptCacheKey(client, sessionId)))\n const skipCacheRef = useRef(false)\n\n useEffect(() => {\n if (!sessionId) {\n return\n }\n const cache = optionsRef.current?.cacheTranscript !== false\n const key = transcriptCacheKey(client, sessionId)\n const plan = planAttach({\n resyncSeq,\n key,\n seededFor: seededForRef.current,\n current: stateRef.current,\n cacheEnabled: cache,\n skipCache: skipCacheRef.current,\n warm: readTranscriptCache(key),\n })\n skipCacheRef.current = false\n if (plan.seed) {\n dispatch({ type: 'transcript_seed', state: plan.held })\n seededForRef.current = plan.seedToken\n }\n const handle = client.attach(sessionId, {\n truncateResults: true,\n imageRefs: true,\n ...(plan.afterSeq === undefined ? {} : { afterSeq: plan.afterSeq }),\n })\n handleRef.current = handle\n setHandleState(handle)\n const offEvent = handle.on('event', (event: SessionEvent) => dispatch(event))\n const offAttached = handle.on('attached', (frame: AttachedFrame) => {\n if (staleAttach(frame, stateRef.current)) {\n offEvent()\n deleteTranscriptCache(key)\n skipCacheRef.current = true\n setResyncSeq((n) => n + 1)\n return\n }\n dispatch(frame)\n setReplayTarget(initialReplayTarget(frame))\n setProtocolMismatch(frame.protocolVersion === PROTOCOL_VERSION ? undefined : frame.protocolVersion)\n })\n const offConn = handle.on('connectionChange', (open: boolean) => setConnection(open ? 'live' : 'reconnecting'))\n const offRetry = handle.on('reconnectAttempt', (attempts: number) =>\n setConnection(attempts >= OFFLINE_AFTER_ATTEMPTS ? 'offline' : 'reconnecting'),\n )\n const offProtocolError = handle.on('protocolError', (message: string) => {\n optionsRef.current?.onProtocolError?.(message)\n })\n return () => {\n offEvent()\n offAttached()\n offConn()\n offRetry()\n offProtocolError()\n handle.detach()\n handleRef.current = null\n setHandleState(undefined)\n setConnection('reconnecting')\n setProtocolMismatch(undefined)\n setReplayTarget(undefined)\n const parting = stateRef.current\n if (shouldWriteParting({ cacheEnabled: cache, skipCache: skipCacheRef.current, parting })) {\n writeTranscriptCache(key, parting)\n }\n }\n }, [client, sessionId, resyncSeq])\n\n useEffect(() => {\n if (replayTarget === undefined) {\n return\n }\n const timer = setTimeout(() => setReplayTarget(undefined), REPLAY_HOLD_MAX_MS)\n return () => clearTimeout(timer)\n }, [replayTarget])\n useEffect(() => {\n if (replayTarget !== undefined && state.lastSeq >= replayTarget) {\n setReplayTarget(undefined)\n }\n }, [replayTarget, state.lastSeq])\n\n const models = useProfileModelFallback(client, sessionId, state)\n\n const connected = connection === 'live'\n // Derived at render, not in an effect, so the reveal lands in the same commit as the replay's final event — an effect is one visible frame late.\n const replaying = replayTarget !== undefined && state.lastSeq < replayTarget\n const reconnectNow = useCallback(() => handleRef.current?.reconnectNow(), [])\n\n const loadFullResult = useCallback(\n async (toolUseId: string): Promise<boolean> => {\n if (!sessionId) {\n return false\n }\n const item = stateRef.current.items.find((candidate) => candidate.kind === 'tool_call' && candidate.id === toolUseId)\n const result = item?.kind === 'tool_call' ? item.result : undefined\n if (!result?.truncated || result.sourceSeq === undefined) {\n return false\n }\n try {\n const full = await client.toolResult(sessionId, result.sourceSeq, toolUseId)\n const text =\n typeof full.content === 'string'\n ? full.content\n : (full.content ?? [])\n .map((part) => (typeof part.text === 'string' ? part.text : ''))\n .filter(Boolean)\n .join('\\n')\n dispatch({ type: 'transcript_hydrate_result', toolUseId, text })\n return true\n } catch {\n return false\n }\n },\n [client, sessionId],\n )\n\n return useMemo(\n () => ({\n state,\n connected,\n connection,\n replaying,\n protocolMismatch,\n models,\n effectiveModel: state.model ?? state.defaultModel,\n handle: handleState,\n send: (text, attachmentIds) => handleRef.current?.send(text, attachmentIds),\n approve: (requestId, updatedInput) => handleRef.current?.approve(requestId, updatedInput),\n deny: (requestId, message, interrupt) => handleRef.current?.deny(requestId, message, interrupt),\n interrupt: () => handleRef.current?.interrupt(),\n clearContext: () => handleRef.current?.clearContext(),\n setPermissionMode: (mode) => handleRef.current?.setPermissionMode(mode),\n setModel: (model) => handleRef.current?.setModel(model),\n closeSession: () => handleRef.current?.closeSession(),\n reconnectNow,\n loadFullResult,\n }),\n [state, connected, connection, replaying, protocolMismatch, models, handleState, reconnectNow, loadFullResult],\n )\n}\n\nfunction useProfileModelFallback(client: WorkerDeckClient, sessionId: string | undefined, state: TranscriptState): ModelOption[] {\n const [catalog, setCatalog] = useState<ModelOption[]>([])\n const profile = state.session?.profile\n const reported = state.models\n const hasReported = !!reported?.length\n\n useEffect(() => setCatalog([]), [sessionId])\n\n useEffect(() => {\n if (!profile || hasReported) {\n return\n }\n let cancelled = false\n client\n .listProfiles()\n .then((response) => {\n if (!cancelled) {\n setCatalog(response.profiles.find((p) => p.name === profile)?.models ?? [])\n }\n })\n .catch(() => {})\n return () => {\n cancelled = true\n }\n }, [client, profile, hasReported])\n\n return hasReported ? reported : catalog\n}\n","import type { WorkerDeckClient } from '@workerdeck/client'\nimport type { ProfileUsage } from '@workerdeck/protocol'\n\n/**\n * Last known profile usage, kept outside React so a session switch does not blank it.\n *\n * Usage belongs to the *account*, not the session, but it is fetched by a hook that lives inside the per-session\n * panel — so remounting that panel used to drop the authoritative reading to `undefined` for a whole round trip,\n * leaving only the newly-attached session's own replayed (and possibly days-old) numbers to render. That is the\n * \"switching sessions resets my weekly usage to 1%, then it catches up\" report.\n *\n * Keyed by client identity + profile because a different profile is a different account's plan, never a stale view\n * of this one. Same shape and reasoning as `transcript-cache.ts` next door.\n */\nconst entries = new Map<string, ProfileUsage>()\n\n// NUL separates unambiguously: `identityKey` is JSON.stringify output, so no two pairs spell one key.\nexport function profileUsageCacheKey(client: WorkerDeckClient, profile: string): string {\n return `${client.identityKey}\\u0000${profile}`\n}\n\nexport function readProfileUsageCache(key: string): ProfileUsage | undefined {\n return entries.get(key)\n}\n\nexport function writeProfileUsageCache(key: string, usage: ProfileUsage | undefined): void {\n if (usage === undefined) {\n return\n }\n entries.set(key, usage)\n}\n\nexport function clearProfileUsageCache(): void {\n entries.clear()\n}\n","import type { WorkerDeckClient } from '@workerdeck/client'\n\n/**\n * Unsent composer text, kept per session on the client that typed it.\n *\n * A draft is not session state: it never reaches the gateway and never syncs between clients. Two people looking at\n * one session are each mid-sentence in their own way, and a half-written prompt is not something either of them\n * asked to publish.\n *\n * It is persisted rather than merely held in memory because the two ways drafts got lost are different failures. A\n * session switch remounts the composer, which a module-scope map alone would survive; a Vite HMR reload or a VS Code\n * `dev:host` webview re-render replaces the whole document, which it would not.\n */\nconst KEY = 'workerdeck.drafts.v1'\n\n/** Drafts are a convenience, so the store stays small and drops the least recently touched first. */\nconst MAX_DRAFTS = 20\n\ntype Draft = { text: string; savedAt: number }\n\nlet memory: Record<string, Draft> | undefined\n\nfunction storage(): Storage | undefined {\n try {\n // Through globalThis: this package is typechecked with no DOM lib, and a webview may deny storage outright.\n return (globalThis as { localStorage?: Storage }).localStorage\n } catch {\n return undefined\n }\n}\n\nfunction load(): Record<string, Draft> {\n if (memory) {\n return memory\n }\n memory = {}\n const raw = storage()?.getItem(KEY)\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Record<string, Draft>\n for (const [key, draft] of Object.entries(parsed)) {\n if (typeof draft?.text === 'string' && typeof draft.savedAt === 'number') {\n memory[key] = draft\n }\n }\n } catch {\n // A corrupt blob is not worth a broken composer; start over.\n }\n }\n return memory\n}\n\nfunction persist(drafts: Record<string, Draft>): void {\n const entries = Object.entries(drafts)\n if (entries.length > MAX_DRAFTS) {\n entries.sort((a, b) => b[1].savedAt - a[1].savedAt)\n for (const [key] of entries.slice(MAX_DRAFTS)) {\n delete drafts[key]\n }\n }\n try {\n storage()?.setItem(KEY, JSON.stringify(drafts))\n } catch {\n // Out of quota, or storage denied. The in-memory copy still carries the session switch.\n }\n}\n\n// NUL separates unambiguously: `identityKey` is JSON.stringify output, so no two pairs spell one key.\nexport function draftKey(client: WorkerDeckClient, sessionId: string): string {\n return `${client.identityKey}\\u0000${sessionId}`\n}\n\nexport function readDraft(key: string): string {\n return load()[key]?.text ?? ''\n}\n\nexport function writeDraft(key: string, text: string, now = Date.now()): void {\n const drafts = load()\n // An empty draft is the absence of one: keeping it would evict a real draft under the cap.\n if (text.trim() === '') {\n if (drafts[key] === undefined) {\n return\n }\n delete drafts[key]\n } else {\n drafts[key] = { text, savedAt: now }\n }\n persist(drafts)\n}\n\nexport function clearDrafts(): void {\n memory = {}\n try {\n storage()?.removeItem(KEY)\n } catch {}\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type { EngineCapabilities, ProfileEngine } from '@workerdeck/protocol'\n\nexport type StagedAttachment = {\n key: string\n name: string\n mediaType: string\n bytes: number\n previewUrl?: string\n status: 'uploading' | 'ready' | 'failed'\n id?: string\n error?: string\n}\n\nexport type AttachmentKind = 'image' | 'pdf' | 'text'\n\nconst TEXTUAL_TYPES = new Set([\n 'application/json',\n 'application/xml',\n 'application/yaml',\n 'application/x-yaml',\n 'application/toml',\n 'application/javascript',\n 'application/typescript',\n 'application/x-sh',\n 'application/sql',\n])\n\nconst MAX_IMAGE_EDGE = 1568\n\nexport function attachmentKind(mediaType: string): AttachmentKind | undefined {\n const type = mediaType.split(';')[0]!.trim().toLowerCase()\n if (type.startsWith('image/')) {\n return 'image'\n }\n if (type === 'application/pdf') {\n return 'pdf'\n }\n if (type.startsWith('text/')) {\n return 'text'\n }\n if (TEXTUAL_TYPES.has(type)) {\n return 'text'\n }\n return undefined\n}\n\nexport type UseAttachmentsOptions = {\n capabilities: EngineCapabilities\n engine?: ProfileEngine\n}\n\nexport type UseAttachmentsResult = {\n items: StagedAttachment[]\n readyIds: string[]\n uploading: boolean\n hasFailure: boolean\n accept: string\n disabled: boolean\n add: (files: Iterable<File>) => void\n retry: (key: string) => void\n remove: (key: string) => void\n clear: () => void\n error?: string\n dismissError: () => void\n}\n\nexport function useAttachments(\n client: WorkerDeckClient,\n sessionId: string | undefined,\n { capabilities, engine }: UseAttachmentsOptions,\n): UseAttachmentsResult {\n const [items, setItems] = useState<StagedAttachment[]>([])\n const [error, setError] = useState<string | undefined>()\n const counter = useRef(0)\n const fileByKey = useRef(new Map<string, File>())\n const previewUrls = useRef<string[]>([])\n previewUrls.current = items.flatMap((item) => (item.previewUrl ? [item.previewUrl] : []))\n const accepts = capabilities.attachments\n\n useEffect(\n () => () => {\n for (const url of previewUrls.current) {\n URL.revokeObjectURL(url)\n }\n },\n [],\n )\n\n const patch = useCallback((key: string, next: Partial<StagedAttachment>) => {\n setItems((current) => current.map((item) => (item.key === key ? { ...item, ...next } : item)))\n }, [])\n\n const upload = useCallback(\n (key: string, file: File) => {\n if (!sessionId) {\n return\n }\n patch(key, { status: 'uploading', error: undefined })\n void (async () => {\n try {\n const data = await prepare(file)\n const uploaded = await client.uploadAttachment(sessionId, {\n name: file.name,\n mediaType: data.mediaType,\n data: data.body,\n })\n patch(key, { status: 'ready', id: uploaded.id, bytes: uploaded.bytes ?? file.size })\n } catch (e) {\n patch(key, { status: 'failed', error: e instanceof Error ? e.message : 'Upload failed' })\n }\n })()\n },\n [client, patch, sessionId],\n )\n\n const add = useCallback(\n (files: Iterable<File>) => {\n const staged: StagedAttachment[] = []\n const pending: Array<{ key: string; file: File }> = []\n for (const file of files) {\n const mediaType = file.type || 'application/octet-stream'\n const kind = attachmentKind(mediaType)\n // An unclassifiable type still goes: the gateway's vocabulary is the authoritative one.\n if (kind && !accepts.includes(kind)) {\n setError(`The ${engine ?? 'claude'} engine does not take ${kind} attachments.`)\n continue\n }\n const key = `att-${++counter.current}`\n staged.push({\n key,\n name: file.name,\n mediaType,\n bytes: file.size,\n previewUrl: kind === 'image' ? URL.createObjectURL(file) : undefined,\n status: 'uploading',\n })\n pending.push({ key, file })\n }\n if (staged.length === 0) {\n return\n }\n setItems((current) => [...current, ...staged])\n fileByKey.current = new Map([...fileByKey.current, ...pending.map(({ key, file }) => [key, file] as const)])\n for (const { key, file } of pending) {\n upload(key, file)\n }\n },\n [accepts, engine, upload],\n )\n\n const forget = useCallback((keys: string[]) => {\n setItems((current) => {\n for (const item of current) {\n if (keys.includes(item.key) && item.previewUrl) {\n URL.revokeObjectURL(item.previewUrl)\n }\n }\n return current.filter((item) => !keys.includes(item.key))\n })\n for (const key of keys) {\n fileByKey.current.delete(key)\n }\n }, [])\n\n const remove = useCallback((key: string) => forget([key]), [forget])\n\n const clear = useCallback(() => {\n setItems((current) => {\n for (const item of current) {\n if (item.previewUrl) {\n URL.revokeObjectURL(item.previewUrl)\n }\n }\n return []\n })\n fileByKey.current.clear()\n }, [])\n\n const retry = useCallback(\n (key: string) => {\n const file = fileByKey.current.get(key)\n if (file) {\n upload(key, file)\n }\n },\n [upload],\n )\n\n return useMemo(\n () => ({\n items,\n readyIds: items.flatMap((item) => (item.id ? [item.id] : [])),\n uploading: items.some((item) => item.status === 'uploading'),\n hasFailure: items.some((item) => item.status === 'failed'),\n accept: acceptAttribute(accepts),\n disabled: accepts.length === 0 || !sessionId,\n add,\n retry,\n remove,\n clear,\n error,\n dismissError: () => setError(undefined),\n }),\n [items, accepts, sessionId, add, retry, remove, clear, error],\n )\n}\n\nfunction acceptAttribute(kinds: readonly AttachmentKind[]): string {\n if (kinds.length === 0) {\n return ''\n }\n const parts: string[] = []\n if (kinds.includes('image')) {\n parts.push('image/*')\n }\n if (kinds.includes('pdf')) {\n parts.push('application/pdf')\n }\n if (kinds.includes('text')) {\n parts.push('text/*', '.md', '.json', '.yaml', '.yml', '.toml')\n }\n // All three kinds is no narrowing at all, and an empty accept leaves the picker open.\n return kinds.length === 3 ? '' : parts.join(',')\n}\n\n// Reached through globalThis, not named directly: smoke/ typechecks this source with no DOM lib.\ntype ImageBitmapLike = { width: number; height: number; close(): void }\ntype CanvasLike = {\n width: number\n height: number\n getContext(contextId: '2d'): {\n drawImage(image: ImageBitmapLike, dx: number, dy: number, dw: number, dh: number): void\n } | null\n toBlob(callback: (blob: Blob | null) => void, type?: string, quality?: number): void\n}\nconst imaging = globalThis as unknown as {\n createImageBitmap?: (source: Blob) => Promise<ImageBitmapLike>\n document?: { createElement(tagName: 'canvas'): CanvasLike }\n}\n\nasync function prepare(file: File): Promise<{ body: Blob; mediaType: string }> {\n const mediaType = file.type || 'application/octet-stream'\n const { createImageBitmap, document } = imaging\n if (!createImageBitmap || !document || !mediaType.startsWith('image/')) {\n return { body: file, mediaType }\n }\n // A redraw of an animation would keep one frame of it.\n if (mediaType === 'image/gif') {\n return { body: file, mediaType }\n }\n try {\n const bitmap = await createImageBitmap(file)\n const longest = Math.max(bitmap.width, bitmap.height)\n if (longest <= MAX_IMAGE_EDGE) {\n bitmap.close()\n return { body: file, mediaType }\n }\n const scale = MAX_IMAGE_EDGE / longest\n const canvas = document.createElement('canvas')\n canvas.width = Math.round(bitmap.width * scale)\n canvas.height = Math.round(bitmap.height * scale)\n const context = canvas.getContext('2d')\n if (!context) {\n bitmap.close()\n return { body: file, mediaType }\n }\n context.drawImage(bitmap, 0, 0, canvas.width, canvas.height)\n bitmap.close()\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, 'image/jpeg', 0.85))\n return blob ? { body: blob, mediaType: 'image/jpeg' } : { body: file, mediaType }\n } catch {\n return { body: file, mediaType }\n }\n}\n","export type PromptToken = {\n kind: 'file' | 'command'\n start: number\n end: number\n text: string\n}\n\n// No `/`, so a pasted absolute path is not a command; `:` is in for namespaced skills (`dev:wrapup`).\nconst COMMAND_BODY = /^[A-Za-z0-9\\-_.:]+$/\n\nconst SENTENCE_TAIL = new Set(['.', ',', ';', ':', '!', '?', ')', ']', '}', '\"', \"'\"])\n\nexport function scanPromptTokens(text: string): PromptToken[] {\n const tokens: PromptToken[] = []\n const words = /\\S+/g\n let match: RegExpExecArray | null\n while ((match = words.exec(text)) !== null) {\n const word = match[0]\n const kind = word[0] === '@' ? 'file' : word[0] === '/' ? 'command' : undefined\n if (!kind) {\n continue\n }\n let end = match.index + word.length\n while (end > match.index && SENTENCE_TAIL.has(text[end - 1]!)) {\n end--\n }\n const body = text.slice(match.index + 1, end)\n if (!body) {\n continue\n }\n if (kind === 'command' && !COMMAND_BODY.test(body)) {\n continue\n }\n tokens.push({ kind, start: match.index, end, text: text.slice(match.index, end) })\n }\n return tokens\n}\n","import { useEffect, useRef, type RefObject } from 'react'\nimport { WorkerDeckError } from '@workerdeck/client'\n\n// Set in an effect, not at declaration: StrictMode remounts, and a once-initialised ref stays false.\nexport function useAliveRef(): RefObject<boolean> {\n const alive = useRef(true)\n useEffect(() => {\n alive.current = true\n return () => {\n alive.current = false\n }\n }, [])\n return alive\n}\n\nexport function isRouteUnsupported(e: unknown): boolean {\n return e instanceof WorkerDeckError && e.status === 404\n}\n","import type { HostDirEntry } from '@workerdeck/protocol'\n\nexport type HostDirState = {\n entries: HostDirEntry[]\n truncated?: boolean\n}\n\nexport type HostTreeRow = {\n entry: HostDirEntry\n depth: number\n expanded?: boolean\n loading?: boolean\n truncated?: boolean\n}\n\nexport function flattenHostTree(root: string, dirs: ReadonlyMap<string, HostDirState>, expanded: ReadonlySet<string>): HostTreeRow[] {\n const rows: HostTreeRow[] = []\n const rootState = dirs.get(root)\n if (!rootState) {\n return rows\n }\n // An explicit stack rather than recursion. Depth is bounded by how many directories the user has\n // expanded, not by tree size, so this is a small risk — but it is the user's clicks that set the\n // bound, and an expanded chain deep enough to exhaust the call stack should still just render.\n const stack: { entries: readonly HostDirEntry[]; index: number; depth: number }[] = [{ entries: rootState.entries, index: 0, depth: 0 }]\n while (stack.length > 0) {\n const frame = stack[stack.length - 1]\n const entry = frame.entries[frame.index]\n if (entry === undefined) {\n stack.pop()\n continue\n }\n frame.index += 1\n const depth = frame.depth\n if (entry.type !== 'dir') {\n rows.push({ entry, depth })\n continue\n }\n const isExpanded = expanded.has(entry.path)\n const childState = dirs.get(entry.path)\n rows.push({\n entry,\n depth,\n expanded: isExpanded,\n loading: isExpanded && !childState,\n truncated: isExpanded ? childState?.truncated : undefined,\n })\n if (isExpanded && childState) {\n stack.push({ entries: childState.entries, index: 0, depth: depth + 1 })\n }\n }\n return rows\n}\n\nexport function ancestorsWithin(root: string, path: string): string[] {\n const base = root.endsWith('/') ? root.slice(0, -1) : root\n if (path === base || !path.startsWith(`${base}/`)) {\n return []\n }\n const rest = path.slice(base.length + 1).split('/')\n const out: string[] = []\n let current = base\n for (const segment of rest.slice(0, -1)) {\n current = `${current}/${segment}`\n out.push(current)\n }\n return out\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport { isRouteUnsupported, useAliveRef } from '../lib/async-guards.ts'\nimport type { HostFileMatch } from '@workerdeck/protocol'\nimport { ancestorsWithin, flattenHostTree, type HostDirState, type HostTreeRow } from '../lib/host-tree.ts'\n\nexport type UseHostFileSearchResult = {\n available: boolean\n search: (query: string, options?: { limit?: number; signal?: AbortSignal }) => Promise<HostFileMatch[]>\n}\n\nexport function useHostFileSearch(client: WorkerDeckClient, cwd: string | undefined): UseHostFileSearchResult {\n const [unsupported, setUnsupported] = useState(false)\n const lastCwd = useRef(cwd)\n useEffect(() => {\n if (lastCwd.current !== cwd) {\n lastCwd.current = cwd\n setUnsupported(false)\n }\n }, [cwd])\n\n const search = useCallback(\n async (query: string, options?: { limit?: number; signal?: AbortSignal }) => {\n if (!cwd || unsupported) {\n return []\n }\n try {\n const response = await client.findHostFiles(cwd, query, options?.limit ?? 8)\n return options?.signal?.aborted ? [] : response.matches\n } catch (e) {\n if (isRouteUnsupported(e)) {\n setUnsupported(true)\n }\n return []\n }\n },\n [client, cwd, unsupported],\n )\n\n return { available: !!cwd && !unsupported, search }\n}\n\nexport type UseHostFileRootsResult = {\n available: boolean\n canWrite: boolean\n}\n\nexport function useHostFileRoots(client: WorkerDeckClient): UseHostFileRootsResult {\n const [result, setResult] = useState<UseHostFileRootsResult>({\n available: false,\n canWrite: false,\n })\n useEffect(() => {\n let cancelled = false\n client\n .listHostRoots()\n .then((response) => {\n if (!cancelled) {\n setResult({ available: true, canWrite: response.canWrite })\n }\n })\n .catch(() => {\n if (!cancelled) {\n setResult({ available: false, canWrite: false })\n }\n })\n return () => {\n cancelled = true\n }\n }, [client])\n return result\n}\n\nexport type UseHostFileTreeResult = {\n available: boolean\n root: string | undefined\n rows: HostTreeRow[]\n loading: boolean\n error: string | undefined\n toggle: (path: string) => void\n reveal: (path: string) => void\n refresh: (path?: string) => void\n}\n\nexport function useHostFileTree(client: WorkerDeckClient, cwd: string | undefined): UseHostFileTreeResult {\n const [dirs, setDirs] = useState<Map<string, HostDirState>>(() => new Map())\n const [expanded, setExpanded] = useState<Set<string>>(() => new Set())\n const [unsupported, setUnsupported] = useState(false)\n const [error, setError] = useState<string | undefined>()\n\n const lastCwd = useRef(cwd)\n useEffect(() => {\n if (lastCwd.current === cwd) {\n return\n }\n lastCwd.current = cwd\n setDirs(new Map())\n setExpanded(new Set())\n setUnsupported(false)\n setError(undefined)\n }, [cwd])\n\n const alive = useAliveRef()\n\n const requested = useRef(new Set<string>())\n\n const list = useCallback(\n (target: string, { force = false } = {}) => {\n if (unsupported) {\n return\n }\n if (!force && requested.current.has(target)) {\n return\n }\n requested.current.add(target)\n client\n .listHostDir(target)\n .then((response) => {\n if (!alive.current) {\n return\n }\n setDirs((previous) => {\n const next = new Map(previous)\n // Keyed on the path asked for, never the canonical one answered: re-keying orphans the node that asked.\n next.set(target, { entries: response.entries, truncated: response.truncated })\n return next\n })\n })\n .catch((e: unknown) => {\n if (!alive.current) {\n return\n }\n requested.current.delete(target)\n if (isRouteUnsupported(e)) {\n setUnsupported(true)\n return\n }\n setError(e instanceof Error ? e.message : 'Could not read that directory')\n })\n },\n [client, unsupported],\n )\n\n useEffect(() => {\n if (cwd) {\n list(cwd)\n }\n }, [cwd, list])\n\n const toggle = useCallback(\n (path: string) => {\n setExpanded((previous) => {\n const next = new Set(previous)\n if (next.has(path)) {\n next.delete(path)\n } else {\n next.add(path)\n }\n return next\n })\n // Outside the updater, which React may run twice; `requested` makes this a no-op on the collapse.\n list(path)\n },\n [list],\n )\n\n const reveal = useCallback(\n (path: string) => {\n if (!cwd) {\n return\n }\n const ancestors = ancestorsWithin(cwd, path)\n if (ancestors.length === 0) {\n return\n }\n for (const dir of ancestors) {\n list(dir)\n }\n setExpanded((previous) => {\n const next = new Set(previous)\n for (const dir of ancestors) {\n next.add(dir)\n }\n return next\n })\n },\n [cwd, list],\n )\n\n const refresh = useCallback(\n (path?: string) => {\n const target = path ?? cwd\n if (!target) {\n return\n }\n setError(undefined)\n list(target, { force: true })\n },\n [cwd, list],\n )\n\n const rows = useMemo(() => (cwd ? flattenHostTree(cwd, dirs, expanded) : []), [cwd, dirs, expanded])\n\n return {\n available: !!cwd && !unsupported,\n root: cwd,\n rows,\n loading: !!cwd && !unsupported && !dirs.has(cwd) && !error,\n error,\n toggle,\n reveal,\n refresh,\n }\n}\n","import { useEffect, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type { SessionRow } from '@workerdeck/protocol'\n\nconst byHash = new Map<string, string>()\nconst inFlight = new Set<string>()\nconst failed = new Set<string>()\n\nexport type ClientForHost = (hostId: string) => WorkerDeckClient | undefined\n\nexport function useProjectIcons(rows: readonly SessionRow[], clientFor: ClientForHost): Record<string, string> {\n const [resolved, setResolved] = useState<Record<string, string>>(() => Object.fromEntries(byHash))\n\n useEffect(() => {\n let alive = true\n for (const row of rows) {\n const icon = row.info.project?.icon\n if (icon?.type !== 'image') {\n continue\n }\n const { hash } = icon\n if (byHash.has(hash) || inFlight.has(hash) || failed.has(hash)) {\n continue\n }\n const client = clientFor(row.hostId)\n // An unreachable gateway is not an iconless one: no failure recorded, so a later render retries.\n if (!client) {\n continue\n }\n inFlight.add(hash)\n void client\n .projectIcon(row.info.id)\n .then((blob) => {\n byHash.set(hash, URL.createObjectURL(blob))\n if (alive) {\n setResolved(Object.fromEntries(byHash))\n }\n })\n .catch(() => {\n failed.add(hash)\n })\n .finally(() => inFlight.delete(hash))\n }\n return () => {\n alive = false\n }\n }, [rows, clientFor])\n\n return resolved\n}\n","import { useCallback, useMemo } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport { draftKey, readDraft, writeDraft } from '../lib/draft-store.ts'\n\nexport type UseDraftResult = {\n /** What was left unsent last time, read once so it can seed the composer on mount. */\n initialText: string\n save: (text: string) => void\n clear: () => void\n}\n\n/**\n * Remember unsent composer text for a session. Purely local: it never reaches the gateway and never syncs between\n * clients, because a half-written prompt is not something anyone asked to publish.\n */\nexport function useDraft(client: WorkerDeckClient, sessionId: string | undefined): UseDraftResult {\n const key = sessionId ? draftKey(client, sessionId) : undefined\n // Read once per key: re-reading on render would fight whatever is being typed.\n const initialText = useMemo(() => (key ? readDraft(key) : ''), [key])\n const save = useCallback(\n (text: string) => {\n if (key) {\n writeDraft(key, text)\n }\n },\n [key],\n )\n const clear = useCallback(() => {\n if (key) {\n writeDraft(key, '')\n }\n }, [key])\n return { initialText, save, clear }\n}\n","import { useCallback, useEffect, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport { isRouteUnsupported, useAliveRef } from '../lib/async-guards.ts'\nimport { profileUsageCacheKey, readProfileUsageCache, writeProfileUsageCache } from '../lib/profile-usage-cache.ts'\nimport type { ProfileUsage } from '@workerdeck/protocol'\n\nexport type UseProfileUsageOptions = {\n intervalMs?: number\n enabled?: boolean\n}\n\nexport type UseProfileUsageResult = {\n usage: ProfileUsage | undefined\n refresh: () => void\n}\n\nexport function useProfileUsage(\n client: WorkerDeckClient,\n profile: string | undefined,\n options: UseProfileUsageOptions = {},\n): UseProfileUsageResult {\n const { intervalMs = 60_000, enabled = true } = options\n const cacheKey = profile ? profileUsageCacheKey(client, profile) : undefined\n const [usage, setUsage] = useState<ProfileUsage | undefined>(() => (cacheKey ? readProfileUsageCache(cacheKey) : undefined))\n const [unsupported, setUnsupported] = useState(false)\n const [nonce, setNonce] = useState(0)\n const refresh = useCallback(() => setNonce((n) => n + 1), [])\n\n // A previous profile's reading is another account's plan, not a stale view of this one — so switch to what we last\n // knew about *this* profile rather than to nothing. Blanking here is what made a session switch fall back on the\n // newly-attached session's own replayed numbers, which is the usage-reverts report.\n useEffect(() => setUsage(cacheKey ? readProfileUsageCache(cacheKey) : undefined), [cacheKey])\n\n const alive = useAliveRef()\n\n useEffect(() => {\n if (!profile || !enabled || unsupported) {\n return\n }\n let cancelled = false\n const load = () => {\n // `document` through globalThis: the extras project typechecks this source with no DOM lib.\n if ((globalThis as { document?: { hidden?: boolean } }).document?.hidden) {\n return\n }\n client\n .listProfiles()\n .then((res) => {\n if (cancelled || !alive.current) {\n return\n }\n const next = res.profiles.find((p) => p.name === profile)?.usage\n if (cacheKey) {\n writeProfileUsageCache(cacheKey, next)\n }\n setUsage(next)\n })\n .catch((e: unknown) => {\n if (cancelled || !alive.current) {\n return\n }\n if (isRouteUnsupported(e)) {\n setUnsupported(true)\n }\n })\n }\n load()\n const timer = setInterval(load, intervalMs)\n return () => {\n cancelled = true\n clearInterval(timer)\n }\n }, [client, profile, enabled, unsupported, intervalMs, nonce, cacheKey])\n\n return { usage, refresh }\n}\n","import { useEffect, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type { SessionInfo } from '@workerdeck/protocol'\n\nexport type UseSessionInfoResult = {\n info: SessionInfo | undefined\n loading: boolean\n error: string | undefined\n}\n\nexport function useSessionInfo(client: WorkerDeckClient, sessionId: string | undefined): UseSessionInfoResult {\n const [info, setInfo] = useState<SessionInfo | undefined>()\n const [loading, setLoading] = useState(!!sessionId)\n const [error, setError] = useState<string | undefined>()\n\n useEffect(() => {\n if (!sessionId) {\n setInfo(undefined)\n setLoading(false)\n setError(undefined)\n return\n }\n let cancelled = false\n setLoading(true)\n setError(undefined)\n setInfo(undefined)\n client\n .getSession(sessionId)\n .then((next) => {\n if (cancelled) {\n return\n }\n setInfo(next)\n setLoading(false)\n })\n .catch((e: unknown) => {\n if (cancelled) {\n return\n }\n setError(e instanceof Error ? e.message : 'Session not found')\n setLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [client, sessionId])\n\n return { info, loading, error }\n}\n","export type OpenFile = {\n path: string\n name: string\n status: 'loading' | 'ready' | 'binary' | 'error'\n content?: string\n draft?: string\n bytes?: number\n hash?: string\n modifiedAt?: number\n error?: string\n saving?: boolean\n saveError?: string\n conflict?: boolean\n}\n\nexport function isDirty(file: OpenFile): boolean {\n return file.draft !== undefined && file.draft !== file.content\n}\n\nexport function currentText(file: OpenFile): string {\n return file.draft ?? file.content ?? ''\n}\n\nexport type OpenFilesState = {\n files: OpenFile[]\n activePath?: string\n}\n\nexport type OpenFilesAction =\n | { type: 'open'; path: string }\n | { type: 'close'; path: string }\n | { type: 'closeAll' }\n | { type: 'activate'; path: string }\n | {\n type: 'loaded'\n path: string\n content: string\n encoding: 'utf8' | 'base64'\n bytes: number\n hash: string\n modifiedAt: number\n }\n | { type: 'failed'; path: string; error: string }\n | { type: 'edit'; path: string; content: string }\n | { type: 'revert'; path: string }\n | { type: 'saveStart'; path: string }\n | { type: 'saved'; path: string; content: string; bytes: number; hash: string; modifiedAt: number }\n | { type: 'saveFailed'; path: string; error: string; conflict?: boolean }\n | { type: 'dismissConflict'; path: string }\n\nexport const initialOpenFilesState: OpenFilesState = { files: [] }\n\nexport function openFilesReducer(state: OpenFilesState, action: OpenFilesAction): OpenFilesState {\n switch (action.type) {\n case 'open': {\n if (state.files.some((f) => f.path === action.path)) {\n return state.activePath === action.path ? state : { ...state, activePath: action.path }\n }\n const file: OpenFile = { path: action.path, name: baseName(action.path), status: 'loading' }\n return { files: [...state.files, file], activePath: action.path }\n }\n\n case 'close': {\n const index = state.files.findIndex((f) => f.path === action.path)\n if (index === -1) {\n return state\n }\n const files = state.files.filter((f) => f.path !== action.path)\n if (state.activePath !== action.path) {\n return { ...state, files }\n }\n // The right-hand neighbour has slid into this index; a closed last tab has nothing there.\n const next = files[index] ?? files[index - 1]\n return { files, activePath: next?.path }\n }\n\n case 'closeAll': {\n return initialOpenFilesState\n }\n\n case 'activate': {\n if (!state.files.some((f) => f.path === action.path)) {\n return state\n }\n return state.activePath === action.path ? state : { ...state, activePath: action.path }\n }\n\n case 'loaded': {\n return patch(state, action.path, () => ({\n path: action.path,\n name: baseName(action.path),\n status: action.encoding === 'utf8' ? 'ready' : 'binary',\n content: action.encoding === 'utf8' ? action.content : undefined,\n bytes: action.bytes,\n hash: action.hash,\n modifiedAt: action.modifiedAt,\n }))\n }\n\n case 'failed': {\n return patch(state, action.path, (file) => ({ ...file, status: 'error', error: action.error }))\n }\n\n case 'edit': {\n return patch(state, action.path, (file) => (file.status === 'ready' ? { ...file, draft: action.content } : file))\n }\n\n case 'revert': {\n return patch(state, action.path, (file) => ({\n ...file,\n draft: undefined,\n saveError: undefined,\n conflict: false,\n }))\n }\n\n case 'saveStart': {\n return patch(state, action.path, (file) => ({\n ...file,\n saving: true,\n saveError: undefined,\n conflict: false,\n }))\n }\n\n case 'saved': {\n return patch(state, action.path, (file) => ({\n ...file,\n saving: false,\n saveError: undefined,\n conflict: false,\n content: action.content,\n bytes: action.bytes,\n hash: action.hash,\n modifiedAt: action.modifiedAt,\n draft: file.draft === action.content ? undefined : file.draft,\n }))\n }\n\n case 'saveFailed': {\n return patch(state, action.path, (file) => ({\n ...file,\n saving: false,\n saveError: action.error,\n conflict: action.conflict ?? false,\n }))\n }\n\n case 'dismissConflict': {\n return patch(state, action.path, (file) => ({\n ...file,\n conflict: false,\n saveError: undefined,\n }))\n }\n }\n}\n\nfunction patch(state: OpenFilesState, path: string, next: (file: OpenFile) => OpenFile): OpenFilesState {\n const index = state.files.findIndex((f) => f.path === path)\n if (index === -1) {\n return state\n }\n const current = state.files[index]!\n const updated = next(current)\n if (updated === current) {\n return state\n }\n const files = state.files.slice()\n files[index] = updated\n return { ...state, files }\n}\n\nfunction baseName(path: string): string {\n const trimmed = path.endsWith('/') ? path.slice(0, -1) : path\n return trimmed.slice(trimmed.lastIndexOf('/') + 1) || trimmed || path\n}\n","import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react'\nimport { WorkerDeckError, type WorkerDeckClient } from '@workerdeck/client'\nimport { currentText, initialOpenFilesState, isDirty, openFilesReducer, type OpenFile, type OpenFilesState } from '../lib/open-files.ts'\n\nexport type UseOpenFilesResult = OpenFilesState & {\n active: OpenFile | undefined\n hasUnsaved: boolean\n open: (path: string) => void\n close: (path: string) => void\n closeAll: () => void\n activate: (path: string) => void\n edit: (path: string, content: string) => void\n save: (path: string) => Promise<void>\n revert: (path: string) => void\n reload: (path: string) => void\n overwrite: (path: string) => Promise<void>\n dismissConflict: (path: string) => void\n}\n\nexport function useOpenFiles(client: WorkerDeckClient): UseOpenFilesResult {\n const [state, dispatch] = useReducer(openFilesReducer, initialOpenFilesState)\n\n const requested = useRef(new Set<string>())\n const alive = useRef(true)\n useEffect(() => {\n alive.current = true\n return () => {\n alive.current = false\n }\n }, [])\n\n const latest = useRef(state)\n useEffect(() => {\n latest.current = state\n }, [state])\n\n const loading = state.files.filter((f) => f.status === 'loading')\n // Joined so the effect's identity is the set of pending reads, not the array the reducer rebuilds.\n const pending = loading.map((f) => f.path).join('\\n')\n\n const read = useCallback(\n (path: string) =>\n client.readHostFile(path).then((response) => {\n if (!alive.current) {\n return undefined\n }\n dispatch({\n type: 'loaded',\n // The tab is keyed on the path asked for, never the canonical one the gateway answers with.\n path,\n content: response.content,\n encoding: response.encoding,\n bytes: response.bytes,\n hash: response.hash,\n modifiedAt: response.modifiedAt,\n })\n return response\n }),\n [client],\n )\n\n useEffect(() => {\n for (const path of pending ? pending.split('\\n') : []) {\n if (requested.current.has(path)) {\n continue\n }\n requested.current.add(path)\n read(path).catch((e: unknown) => {\n if (!alive.current) {\n return\n }\n dispatch({\n type: 'failed',\n path,\n error: e instanceof Error ? e.message : 'Could not read that file',\n })\n })\n }\n }, [read, pending])\n\n const open = useCallback((path: string) => dispatch({ type: 'open', path }), [])\n const close = useCallback((path: string) => {\n requested.current.delete(path)\n dispatch({ type: 'close', path })\n }, [])\n const closeAll = useCallback(() => {\n requested.current.clear()\n dispatch({ type: 'closeAll' })\n }, [])\n const activate = useCallback((path: string) => dispatch({ type: 'activate', path }), [])\n const edit = useCallback((path: string, content: string) => dispatch({ type: 'edit', path, content }), [])\n const revert = useCallback((path: string) => dispatch({ type: 'revert', path }), [])\n const dismissConflict = useCallback((path: string) => dispatch({ type: 'dismissConflict', path }), [])\n\n const reload = useCallback(\n (path: string) => {\n requested.current.add(path)\n read(path).catch((e: unknown) => {\n if (!alive.current) {\n return\n }\n dispatch({\n type: 'failed',\n path,\n error: e instanceof Error ? e.message : 'Could not re-read that file',\n })\n })\n },\n [read],\n )\n\n const write = useCallback(\n async (path: string, text: string, expectedHash: string | undefined) => {\n try {\n const response = await client.writeHostFile({ path, content: text, expectedHash })\n if (!alive.current) {\n return\n }\n dispatch({\n type: 'saved',\n path,\n content: text,\n bytes: response.bytes,\n hash: response.hash,\n modifiedAt: response.modifiedAt,\n })\n } catch (e) {\n if (!alive.current) {\n return\n }\n const conflict = e instanceof WorkerDeckError && e.status === 409\n dispatch({\n type: 'saveFailed',\n path,\n conflict,\n error: conflict ? 'This file changed on disk since you opened it.' : e instanceof Error ? e.message : 'Could not save that file',\n })\n }\n },\n [client],\n )\n\n const save = useCallback(\n async (path: string) => {\n const file = latest.current.files.find((f) => f.path === path)\n if (!file || file.saving || !isDirty(file)) {\n return\n }\n dispatch({ type: 'saveStart', path })\n await write(path, currentText(file), file.hash)\n },\n [write],\n )\n\n const overwrite = useCallback(\n async (path: string) => {\n const file = latest.current.files.find((f) => f.path === path)\n if (!file || file.saving) {\n return\n }\n // Captured before the re-read, whose `loaded` clears the draft — the one thing \"take mine\" must not do.\n const mine = currentText(file)\n dispatch({ type: 'saveStart', path })\n try {\n const fresh = await client.readHostFile(path)\n if (!alive.current) {\n return\n }\n await write(path, mine, fresh.hash)\n } catch (e) {\n if (!alive.current) {\n return\n }\n dispatch({\n type: 'saveFailed',\n path,\n error: e instanceof Error ? e.message : 'Could not save that file',\n })\n }\n },\n [client, write],\n )\n\n const active = useMemo(() => state.files.find((f) => f.path === state.activePath), [state.files, state.activePath])\n const hasUnsaved = useMemo(() => state.files.some(isDirty), [state.files])\n\n return {\n ...state,\n active,\n hasUnsaved,\n open,\n close,\n closeAll,\n activate,\n edit,\n save,\n revert,\n reload,\n overwrite,\n dismissConflict,\n }\n}\n","import type { SessionHandle } from '@workerdeck/client'\nimport type { RunScriptResult, SandboxEngine, SandboxVfs } from '@workerdeck/sandbox'\nimport type { ToolCallRequestFrame } from '@workerdeck/protocol'\n\nexport type ToolHostExecution = {\n executionId: string\n toolName: string\n status: 'running' | 'settled' | 'failed' | 'canceled'\n reason?: string\n startedAt: number\n endedAt?: number\n}\n\nexport type ToolHostRunner = (request: {\n script: string\n vfs: SandboxVfs\n timeoutMs: number\n memoryLimitBytes: number\n signal: AbortSignal\n}) => Promise<RunScriptResult>\n\nexport type ClientToolResult = { value: unknown } | { error: string; reason?: string }\n\nexport type ClientToolHandler = (\n input: unknown,\n context: { executionId: string; signal: AbortSignal },\n) => ClientToolResult | Promise<ClientToolResult>\n\nexport type ToolCallHostOptions = {\n tools?: string[]\n clientTools?: Record<string, ClientToolHandler>\n timeoutMs?: number\n memoryLimitBytes?: number\n loadEngine?: () => Promise<SandboxEngine>\n // The guest deadline preempts the interpreter only on the thread it runs on; a Web Worker is the way off this one.\n execute?: ToolHostRunner\n fetchText?: (url: string) => Promise<string>\n onExecution?: (execution: ToolHostExecution) => void\n}\n\nexport function createToolCallHost(handle: SessionHandle, options: ToolCallHostOptions = {}): { dispose: () => void } {\n const inFlight = new Map<string, AbortController>()\n let enginePromise: Promise<SandboxEngine> | undefined\n let disposed = false\n\n const track = (execution: ToolHostExecution) => options.onExecution?.(execution)\n\n const refuse = (frame: ToolCallRequestFrame, reason: string, error: string, startedAt: number) => {\n handle.sendToolCallError(frame.executionId, reason, error)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'failed',\n reason,\n startedAt,\n endedAt: Date.now(),\n })\n }\n\n const runClientTool = async (frame: ToolCallRequestFrame, handler: ClientToolHandler): Promise<void> => {\n const startedAt = Date.now()\n const controller = new AbortController()\n inFlight.set(frame.executionId, controller)\n track({ executionId: frame.executionId, toolName: frame.toolName, status: 'running', startedAt })\n\n try {\n const result = await handler(frame.input, {\n executionId: frame.executionId,\n signal: controller.signal,\n })\n if (disposed || !inFlight.has(frame.executionId)) {\n return\n }\n if ('error' in result) {\n handle.sendToolCallError(frame.executionId, result.reason ?? 'client_error', result.error)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'failed',\n reason: result.reason ?? 'client_error',\n startedAt,\n endedAt: Date.now(),\n })\n } else {\n handle.sendToolCallResult(frame.executionId, { type: 'json', value: result.value })\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'settled',\n startedAt,\n endedAt: Date.now(),\n })\n }\n } catch (error) {\n if (disposed || !inFlight.has(frame.executionId)) {\n return\n }\n refuse(frame, 'host_error', error instanceof Error ? error.message : String(error), startedAt)\n } finally {\n inFlight.delete(frame.executionId)\n }\n }\n\n const run = async (frame: ToolCallRequestFrame): Promise<void> => {\n const startedAt = Date.now()\n const clientHandler = options.clientTools?.[frame.toolName]\n if (clientHandler) {\n return runClientTool(frame, clientHandler)\n }\n const allowed = options.tools ?? ['eval_script']\n if (!allowed.includes(frame.toolName)) {\n refuse(frame, 'unsupported_tool', `this client does not execute '${frame.toolName}'`, startedAt)\n return\n }\n const script = (frame.input as { script?: unknown } | undefined)?.script\n if (typeof script !== 'string') {\n refuse(frame, 'invalid_input', 'expected a string `script` input', startedAt)\n return\n }\n\n const controller = new AbortController()\n inFlight.set(frame.executionId, controller)\n track({ executionId: frame.executionId, toolName: frame.toolName, status: 'running', startedAt })\n\n try {\n const sandbox = await import('@workerdeck/sandbox')\n const vfs = sandbox.createVfs(frame.vfsSeed)\n // Never above what the server asked for: it owns the deadline it gives up at.\n const timeoutMs = Math.min(frame.limits?.timeoutMs ?? Number.POSITIVE_INFINITY, options.timeoutMs ?? 5000)\n const memoryLimitBytes = Math.min(\n frame.limits?.memoryLimitBytes ?? Number.POSITIVE_INFINITY,\n options.memoryLimitBytes ?? 64 * 1024 * 1024,\n )\n\n const result = options.execute\n ? await options.execute({ script, vfs, timeoutMs, memoryLimitBytes, signal: controller.signal })\n : await (async () => {\n enginePromise ??= (options.loadEngine ?? defaultLoadEngine)()\n return sandbox.runScript(await enginePromise, {\n script,\n vfs,\n timeoutMs,\n memoryLimitBytes,\n signal: controller.signal,\n fetchText: options.fetchText,\n })\n })()\n\n if (disposed || !inFlight.has(frame.executionId)) {\n return\n }\n const logs = result.logs.map((l) => `[${l.level}] ${l.text}`)\n if (result.ok) {\n handle.sendToolCallResult(frame.executionId, { type: 'json', value: result.value }, logs)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'settled',\n startedAt,\n endedAt: Date.now(),\n })\n } else {\n handle.sendToolCallError(frame.executionId, result.reason, result.error, logs)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'failed',\n reason: result.reason,\n startedAt,\n endedAt: Date.now(),\n })\n }\n } catch (error) {\n if (disposed || !inFlight.has(frame.executionId)) {\n return\n }\n refuse(frame, 'host_error', error instanceof Error ? error.message : String(error), startedAt)\n } finally {\n inFlight.delete(frame.executionId)\n }\n }\n\n const offRequest = handle.on('toolCallRequest', (frame) => void run(frame))\n const offCancel = handle.on('toolCallCanceled', ({ executionId, reason }) => {\n const controller = inFlight.get(executionId)\n if (!controller) {\n return\n }\n controller.abort()\n inFlight.delete(executionId)\n track({\n executionId,\n toolName: '',\n status: 'canceled',\n reason,\n startedAt: Date.now(),\n endedAt: Date.now(),\n })\n })\n\n return {\n dispose: () => {\n disposed = true\n offRequest()\n offCancel()\n for (const controller of inFlight.values()) {\n controller.abort()\n }\n inFlight.clear()\n },\n }\n}\n\nasync function defaultLoadEngine(): Promise<SandboxEngine> {\n const [sandbox, variant] = await Promise.all([import('@workerdeck/sandbox'), import('@jitl/quickjs-singlefile-browser-release-asyncify')])\n return sandbox.loadEngine(variant as never)\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type { SessionHandle } from '@workerdeck/client'\nimport { createToolCallHost, type ToolCallHostOptions, type ToolHostExecution } from '../lib/tool-host.ts'\n\nexport type UseToolCallHostOptions = ToolCallHostOptions & {\n enabled?: boolean\n historyLimit?: number\n}\n\nexport function useToolCallHost(\n handle: SessionHandle | undefined,\n options: UseToolCallHostOptions = {},\n): { executions: ToolHostExecution[] } {\n const [executions, setExecutions] = useState<ToolHostExecution[]>([])\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n useEffect(() => {\n if (!handle || options.enabled === false) {\n return\n }\n const host = createToolCallHost(handle, {\n // Getters read the ref at call time, so inline objects and closures never resubscribe.\n get tools() {\n const base = optionsRef.current.tools\n const client = optionsRef.current.clientTools\n if (!client) {\n return base\n }\n const clientNames = Object.keys(client)\n return base ? [...new Set([...base, ...clientNames])] : clientNames\n },\n get clientTools() {\n return optionsRef.current.clientTools\n },\n get timeoutMs() {\n return optionsRef.current.timeoutMs\n },\n get memoryLimitBytes() {\n return optionsRef.current.memoryLimitBytes\n },\n get loadEngine() {\n return optionsRef.current.loadEngine\n },\n get execute() {\n return optionsRef.current.execute\n },\n get fetchText() {\n return optionsRef.current.fetchText\n },\n onExecution: (execution) => {\n optionsRef.current.onExecution?.(execution)\n const limit = optionsRef.current.historyLimit ?? 50\n setExecutions((prev) => [...prev.filter((e) => e.executionId !== execution.executionId), execution].slice(-limit))\n },\n })\n return () => host.dispose()\n }, [handle, options.enabled])\n\n return { executions }\n}\n","import type { TranscriptItem } from './transcript.ts'\n\nexport type RecapSummary = {\n turns: number\n replies: number\n tools: number\n toolNames: string[]\n files: number\n errors: number\n pending: number\n any: boolean\n}\n\nexport type RecapInput = {\n items: readonly TranscriptItem[]\n pendingApprovals?: readonly unknown[]\n}\n\n// The boundary is clamped, never rejected: a transcript can shrink (a `/clear`, a compaction).\nexport function summarizeSince(state: RecapInput, fromIndex: number): RecapSummary {\n const start = Math.max(0, Math.min(fromIndex, state.items.length))\n const fresh = state.items.slice(start)\n const toolCounts = new Map<string, number>()\n let turns = 0\n let replies = 0\n let tools = 0\n let files = 0\n let errors = 0\n\n for (const item of fresh) {\n switch (item.kind) {\n case 'turn_result': {\n turns += 1\n if (item.isError) {\n errors += 1\n }\n break\n }\n case 'assistant_text': {\n replies += 1\n break\n }\n case 'tool_call': {\n tools += 1\n toolCounts.set(item.name, (toolCounts.get(item.name) ?? 0) + 1)\n if (item.status === 'failed' || item.result?.isError) {\n errors += 1\n }\n break\n }\n case 'file_delivered': {\n files += 1\n break\n }\n case 'notice': {\n if (item.level === 'error') {\n errors += 1\n }\n break\n }\n default: {\n break\n }\n }\n }\n\n const toolNames = [...toolCounts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([name]) => name)\n const pending = state.pendingApprovals?.length ?? 0\n return {\n turns,\n replies,\n tools,\n toolNames,\n files,\n errors,\n pending,\n any: turns + replies + tools + files + errors + pending > 0,\n }\n}\n\nexport function recapLine(summary: RecapSummary): string | undefined {\n if (!summary.any) {\n return undefined\n }\n const parts: string[] = []\n if (summary.turns > 0) {\n parts.push(plural(summary.turns, 'turn'))\n } else if (summary.replies > 0) {\n parts.push(plural(summary.replies, 'reply', 'replies'))\n }\n if (summary.tools > 0) {\n const named = summary.toolNames.slice(0, 3).join(', ')\n const rest = summary.toolNames.length - 3\n parts.push(`${plural(summary.tools, 'tool call')}${named ? ` (${named}${rest > 0 ? `, +${rest}` : ''})` : ''}`)\n }\n if (summary.files > 0) {\n parts.push(plural(summary.files, 'file'))\n }\n if (summary.errors > 0) {\n parts.push(plural(summary.errors, 'error'))\n }\n if (summary.pending > 0) {\n parts.push(`${plural(summary.pending, 'approval')} waiting`)\n }\n return parts.join(' · ')\n}\n\nfunction plural(count: number, one: string, many = `${one}s`): string {\n return `${count} ${count === 1 ? one : many}`\n}\n"],"mappings":"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"}
|