@tangle-network/agent-app 0.43.4 → 0.43.5

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/assistant/sse.ts","../../src/assistant/client.ts","../../src/assistant/client-context.tsx","../../src/assistant/useAssistantChat.ts","../../src/assistant/persistence.ts","../../src/assistant/presentation.ts","../../src/assistant/reducer.ts","../../src/assistant/useAssistantModels.ts","../../src/assistant/useAssistantThreads.ts","../../src/assistant/AssistantDock.tsx","../../src/assistant/AssistantPanel.tsx","../../src/assistant/AssistantHistory.tsx","../../src/assistant/time-ago.ts","../../src/assistant/ProposalCard.tsx","../../src/assistant/provider-label.ts","../../src/assistant/transcript.tsx","../../src/assistant/usePanelPrefs.ts","../../src/assistant/launcher.tsx","../../src/assistant/ResizeHandle.tsx"],"sourcesContent":["/**\n * POST-based SSE reading for the assistant chat stream. The browser\n * `EventSource` is GET-only and can't send a request body, so the stream is read\n * off a `fetch` POST response.\n *\n * The framing parser is vendored here (rather than depending on an internal SDK)\n * so this module stays self-contained and consumable by any host. It mirrors the\n * standard SSE wire format: events separated by a blank line, `event:` / `data:`\n * fields, `:`-prefixed comments ignored, multi-line `data:` joined with `\\n`, and\n * each event's data JSON-parsed (falling back to the raw string).\n */\n\nexport interface ParsedSSEEvent<T = unknown> {\n data: T;\n rawData: string;\n eventId?: string;\n eventType?: string;\n}\n\n/**\n * Incremental SSE parser. Feed decoded string chunks via `push()`; call\n * `flush()` once the stream closes to emit any final buffered event.\n */\nexport class SSEChunkParser<T = unknown> {\n private buffer = \"\";\n private current: { id?: string; event?: string; data?: string } = {};\n\n push(chunk: string): ParsedSSEEvent<T>[] {\n this.buffer += chunk;\n const lines = this.buffer.split(\"\\n\");\n // The last element is a (possibly empty) partial line; hold it for the next\n // chunk so an event split across reads isn't parsed half-formed.\n this.buffer = lines.pop() ?? \"\";\n return this.processLines(lines);\n }\n\n flush(): ParsedSSEEvent<T>[] {\n const lines = this.buffer ? [this.buffer] : [];\n this.buffer = \"\";\n const events = this.processLines(lines);\n const finalEvent = this.parseCurrent();\n if (finalEvent) {\n events.push(finalEvent);\n this.current = {};\n }\n return events;\n }\n\n private processLines(lines: string[]): ParsedSSEEvent<T>[] {\n const events: ParsedSSEEvent<T>[] = [];\n for (const rawLine of lines) {\n // Tolerate CRLF framing: strip a trailing CR the \"\\n\" split left behind.\n const line = rawLine.endsWith(\"\\r\") ? rawLine.slice(0, -1) : rawLine;\n\n if (line.startsWith(\":\")) continue; // comment / keepalive\n\n if (line === \"\") {\n const parsed = this.parseCurrent();\n if (parsed) events.push(parsed);\n this.current = {};\n continue;\n }\n\n if (line.startsWith(\"id:\")) {\n this.current.id = line.slice(3).trim();\n } else if (line.startsWith(\"event:\")) {\n this.current.event = line.slice(6).trim();\n } else if (line.startsWith(\"data:\")) {\n let value = line.slice(5);\n if (value.startsWith(\" \")) value = value.slice(1);\n this.current.data =\n this.current.data !== undefined\n ? `${this.current.data}\\n${value}`\n : value;\n }\n }\n return events;\n }\n\n private parseCurrent(): ParsedSSEEvent<T> | null {\n if (this.current.data === undefined) return null;\n const rawData = this.current.data.trim();\n if (!rawData) return null;\n let data: T;\n try {\n data = JSON.parse(rawData) as T;\n } catch {\n // A non-JSON payload is surfaced as the raw string; the caller's typed\n // mapper drops anything that isn't a well-formed object.\n data = rawData as unknown as T;\n }\n return {\n data,\n rawData,\n eventId: this.current.id,\n eventType: this.current.event,\n };\n }\n}\n\n/**\n * Read a fetch `Response` body and invoke `onEvent` for each parsed SSE event\n * (its `eventType` plus JSON-parsed `data`), in wire order. Resolves when the\n * stream closes. The caller owns abort (via the fetch `signal`).\n */\nexport async function readSSEEvents(\n body: ReadableStream<Uint8Array>,\n onEvent: (event: ParsedSSEEvent) => void,\n): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n const parser = new SSEChunkParser();\n // releaseLock in finally so an abort (read rejects) or a throwing consumer\n // can't leave the reader lock held on the stream.\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n for (const event of parser.push(decoder.decode(value, { stream: true }))) {\n onEvent(event);\n }\n }\n // Flush any bytes the streaming decoder held back (a multi-byte character\n // split across the final chunk), then any event still buffered in the parser.\n const tail = decoder.decode();\n if (tail) {\n for (const event of parser.push(tail)) onEvent(event);\n }\n for (const event of parser.flush()) onEvent(event);\n } catch (err) {\n // Cancel the underlying stream so a read error or a throwing handler doesn't\n // leave the response open and buffering for the rest of the session. Swallow\n // a cancel failure so it can't mask the original error.\n await reader.cancel(err).catch(() => {});\n throw err;\n } finally {\n reader.releaseLock();\n }\n}\n","/**\n * Configurable network client for the assistant panel: the chat SSE stream, the\n * model/thread/history reads, and the proposal-confirmation call.\n *\n * The transport is injected via {@link AssistantClientConfig} so the same UI can\n * run in different hosts: a same-origin app authenticates with the session\n * cookie (`credentials: \"include\"`) and an `X-Requested-With` marker, while a\n * cross-origin host points `baseUrl` at the API and supplies a bearer token via\n * `headers`. The request shapes and the defensive wire parsing are identical\n * across hosts — only the base URL and the auth headers vary.\n */\n\nimport { readSSEEvents } from \"./sse\";\nimport type {\n AssistantStreamEvent,\n ChatMessage,\n ChatRequest,\n ConnectionRequirement,\n PendingProposal,\n} from \"./types\";\n\n/** Host-supplied transport configuration for {@link createAssistantClient}. */\nexport interface AssistantClientConfig {\n /**\n * Base URL the five assistant endpoints hang off, with no trailing slash —\n * e.g. `\"/api/v1/assistant\"` for a same-origin SSR edge, or\n * `\"https://id.tangle.tools/api/v1/assistant\"` cross-origin. Each method\n * appends its own path (`/chat`, `/models`, `/threads`, …).\n */\n baseUrl: string;\n /**\n * `fetch` credentials mode. Defaults to `\"include\"` so a same-origin cookie\n * session authenticates; a token-based cross-origin host may pass `\"omit\"`\n * and carry the credential in {@link AssistantClientConfig.headers}.\n */\n credentials?: RequestCredentials;\n /**\n * Headers applied to every request — the auth token and/or the CSRF marker.\n * Called per request so a rotating token is read fresh, never captured once.\n */\n headers?: () => Record<string, string>;\n}\n\nexport interface AssistantModelOption {\n slug: string;\n label: string;\n /** USD per million prompt tokens, when the catalog carries pricing. */\n promptUsdPerMillion?: number;\n /** Context window in tokens, when known. */\n contextTokens?: number;\n}\n\nexport interface AssistantModels {\n /** The slug the server uses when a turn selects no model. */\n default: string | null;\n models: AssistantModelOption[];\n}\n\n/** One past conversation in the history switcher. */\nexport interface AssistantThreadSummary {\n id: string;\n /** Truncated first user message; may be null for an untitled thread. */\n title: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Outcome of a model-list fetch. `ok` drives caching: the caller caches on `ok`\n * and retries on `!ok`. An EMPTY list is reported as `!ok` — the server always\n * offers at least the default model when the router is reachable, so an empty\n * menu means the catalog couldn't be loaded and should be retried, not cached\n * for the whole session.\n */\nexport interface AssistantModelsResult {\n ok: boolean;\n data: AssistantModels;\n}\n\n/**\n * Outcome of a thread-history restore. The three cases drive different recovery:\n * `ok` rehydrates the transcript; `gone` (the thread 404s — deleted or from a\n * reset DB) tells the caller to drop the dead thread id so the next turn starts\n * fresh; `error` (transient/network/aborted) keeps the thread id and simply\n * doesn't restore, so a later attempt or send still targets the live thread.\n */\nexport type ThreadHistoryResult =\n | { status: \"ok\"; messages: ChatMessage[]; proposals: PendingProposal[] }\n | { status: \"gone\" }\n | { status: \"error\" };\n\nexport type ConfirmResult =\n | { ok: true; output: unknown; retryable?: boolean }\n | { ok: false; error: string };\n\n/** The assistant network surface, bound to one host's transport config. */\nexport interface AssistantClient {\n fetchModels(signal?: AbortSignal): Promise<AssistantModelsResult>;\n fetchThreads(signal?: AbortSignal): Promise<AssistantThreadSummary[] | null>;\n fetchThreadHistory(\n threadId: string,\n signal?: AbortSignal,\n ): Promise<ThreadHistoryResult>;\n streamChat(\n req: ChatRequest,\n onEvent: (event: AssistantStreamEvent) => void,\n signal: AbortSignal,\n ): Promise<void>;\n confirmProposal(proposalId: string): Promise<ConfirmResult>;\n /** Delete a thread and its server-side turns/proposals. Resolves `{ ok }`; a\n * 404 (already gone) is treated as success so a double-delete is harmless.\n * Optional so a host with no delete endpoint stays a valid client — the panel\n * hides the delete affordance when it's absent (see `useAssistantThreads`). */\n deleteThread?(threadId: string): Promise<{ ok: boolean }>;\n}\n\nconst EMPTY_MODELS: AssistantModels = { default: null, models: [] };\n\n/** A parsed event payload narrowed to a plain (non-array) object, or null. The\n * shared parser JSON-parses each `data:` payload; a non-object (e.g. a\n * malformed frame it left as a raw string) is dropped. */\nfunction asObject(v: unknown): Record<string, unknown> | null {\n return v && typeof v === \"object\" && !Array.isArray(v)\n ? (v as Record<string, unknown>)\n : null;\n}\n\n/** A required wire string: the value when it's a non-empty string, else null so\n * a malformed frame is dropped rather than coerced to \"undefined\". */\nfunction reqStr(v: unknown): string | null {\n return typeof v === \"string\" && v !== \"\" ? v : null;\n}\n\nfunction numOrNull(v: unknown): number | null {\n return typeof v === \"number\" && Number.isFinite(v) ? v : null;\n}\n\n/**\n * Validate one restored connection requirement, or null to drop a malformed\n * element. The card reads `provider`/`connected`/`kind`/`connectUrl` directly\n * (e.g. `providerLabel(r.provider)` calls `.toLowerCase()`), so an element with\n * a non-string provider would throw at render — validate the shape rather than\n * trusting the wire blindly, mirroring `parseRestoredProposal`'s own posture.\n */\nfunction parseRequirement(raw: unknown): ConnectionRequirement | null {\n if (!raw || typeof raw !== \"object\") return null;\n const r = raw as Record<string, unknown>;\n if (typeof r.provider !== \"string\" || r.provider === \"\") return null;\n if (typeof r.connected !== \"boolean\") return null;\n const kind =\n r.kind === \"integration\" || r.kind === \"github_app\" ? r.kind : undefined;\n // connectUrl is `string | null` on the wire; anything else is dropped to\n // undefined so the card falls back to its kind-based default.\n const connectUrl =\n typeof r.connectUrl === \"string\" || r.connectUrl === null\n ? r.connectUrl\n : undefined;\n return {\n provider: r.provider,\n connected: r.connected,\n ...(kind ? { kind } : {}),\n ...(connectUrl !== undefined ? { connectUrl } : {}),\n };\n}\n\n/** Parse a proposal's connection requirements, dropping malformed entries.\n * Returns undefined when absent (non-authoring proposal) so the field stays\n * optional rather than an empty array. Delegates to `parseRequirement` so the\n * live `tool_proposal` path and the restore-from-history path preserve the\n * SAME fields — notably `kind` and `connectUrl`, which the card needs to tell a\n * missing GitHub App installation (\"GitHub App / not installed\", Install link)\n * apart from a missing OAuth connection (\"GitHub / not connected\", Connect\n * link); dropping `kind` here collapsed both to the integration rendering. */\nfunction parseRequirements(v: unknown): ConnectionRequirement[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: ConnectionRequirement[] = [];\n for (const item of v) {\n const parsed = parseRequirement(item);\n if (parsed) out.push(parsed);\n }\n return out;\n}\n\n/**\n * Map a parsed SSE event (name + already-parsed data) to a typed stream event.\n * Unknown event names (e.g. the `ping` keepalive) and malformed payloads yield\n * null and are dropped.\n */\nfunction toStreamEvent(\n event: string | null,\n data: unknown,\n): AssistantStreamEvent | null {\n const obj = asObject(data);\n if (!obj) return null;\n switch (event) {\n case \"thread\": {\n const threadId = reqStr(obj.threadId);\n const turnId = reqStr(obj.turnId);\n if (!threadId || !turnId) return null;\n return {\n type: \"thread\",\n data: { threadId, turnId, model: reqStr(obj.model) },\n };\n }\n case \"delta\": {\n // \"\" is a valid (if empty) delta, but a non-string is malformed.\n if (typeof obj.text !== \"string\") return null;\n return { type: \"delta\", data: { text: obj.text } };\n }\n case \"reasoning\": {\n if (typeof obj.text !== \"string\") return null;\n return { type: \"reasoning\", data: { text: obj.text } };\n }\n case \"tool_call\": {\n const callId = reqStr(obj.callId);\n const name = reqStr(obj.name);\n if (!callId || !name) return null;\n return { type: \"tool_call\", data: { callId, name } };\n }\n case \"tool_result\": {\n const callId = reqStr(obj.callId);\n const name = reqStr(obj.name);\n if (!callId || !name) return null;\n return {\n type: \"tool_result\",\n data: {\n callId,\n name,\n ok: Boolean(obj.ok),\n output: obj.output,\n error: obj.error as { code: string; message: string } | undefined,\n },\n };\n }\n case \"tool_proposal\": {\n const callId = reqStr(obj.callId);\n const name = reqStr(obj.name);\n if (!callId || !name) return null;\n return {\n type: \"tool_proposal\",\n data: {\n proposalId: obj.proposalId == null ? null : reqStr(obj.proposalId),\n callId,\n name,\n args: obj.args,\n requirements: parseRequirements(obj.requirements),\n },\n };\n }\n case \"usage\":\n return {\n type: \"usage\",\n data: {\n promptTokens: numOrNull(obj.promptTokens),\n completionTokens: numOrNull(obj.completionTokens),\n costUsd: numOrNull(obj.costUsd),\n balanceUsd: numOrNull(obj.balanceUsd),\n replayed: Boolean(obj.replayed),\n },\n };\n case \"done\": {\n const turnId = reqStr(obj.turnId);\n const status = reqStr(obj.status);\n if (!turnId || !status) return null;\n return {\n type: \"done\",\n data: {\n turnId,\n status,\n proposed: Boolean(obj.proposed),\n capped: Boolean(obj.capped),\n },\n };\n }\n case \"error\":\n return {\n type: \"error\",\n data: {\n code: reqStr(obj.code) ?? \"STREAM_FAILED\",\n message: reqStr(obj.message) ?? \"The assistant stream failed\",\n },\n };\n default:\n return null;\n }\n}\n\n/**\n * Read the JSON error body of a non-2xx chat response. Pre-stream failures\n * (auth, validation, insufficient balance, busy thread) are returned as\n * `{ success: false, error: { code, message } }` rather than an SSE stream.\n */\nasync function readErrorEvent(res: Response): Promise<AssistantStreamEvent> {\n try {\n const body = (await res.json()) as {\n error?: { code?: string; message?: string };\n };\n return {\n type: \"error\",\n data: {\n code: body.error?.code ?? `HTTP_${res.status}`,\n message: body.error?.message ?? `Request failed (${res.status})`,\n },\n };\n } catch {\n return {\n type: \"error\",\n data: {\n code: `HTTP_${res.status}`,\n message: `Request failed (${res.status})`,\n },\n };\n }\n}\n\n/** Parse one restored proposal from the history payload into a `PendingProposal`,\n * or null when the row is malformed (dropped rather than rendered as a broken\n * card). Mirrors the live `tool_proposal` event shape: a server-minted id, the\n * tool call id + name, the stored args, and — for an authoring proposal — the\n * freshly-recomputed connection requirements the card renders. */\nfunction parseRestoredProposal(raw: unknown): PendingProposal | null {\n if (!raw || typeof raw !== \"object\") return null;\n const r = raw as Record<string, unknown>;\n if (typeof r.proposalId !== \"string\" || r.proposalId === \"\") return null;\n if (typeof r.callId !== \"string\" || r.callId === \"\") return null;\n if (typeof r.name !== \"string\" || r.name === \"\") return null;\n // Validate each requirement element rather than casting the array wholesale —\n // a malformed entry would otherwise reach the card and throw at render.\n const requirements = Array.isArray(r.requirements)\n ? r.requirements\n .map(parseRequirement)\n .filter((x): x is ConnectionRequirement => x !== null)\n : undefined;\n return {\n proposalId: r.proposalId,\n callId: r.callId,\n name: r.name,\n args: r.args,\n ...(requirements ? { requirements } : {}),\n };\n}\n\n/**\n * Build an assistant client bound to one host's transport. The returned methods\n * carry no module state, so a host may create one client per config (or share a\n * single same-origin client for the whole app).\n */\nexport function createAssistantClient(\n config: AssistantClientConfig,\n): AssistantClient {\n const base = config.baseUrl.replace(/\\/+$/, \"\");\n const credentials: RequestCredentials = config.credentials ?? \"include\";\n const authHeaders = (): Record<string, string> => config.headers?.() ?? {};\n const url = (path: string): string => `${base}${path}`;\n\n /**\n * POST JSON and flatten the response to `{ success, data, error }`. On a\n * non-2xx the server's `{ error: { message } }` is collapsed to a string; on a\n * 2xx the body's `data` envelope is unwrapped when present.\n */\n async function postJson<T>(\n path: string,\n body: unknown,\n ): Promise<{ success: boolean; data?: T; error?: string }> {\n try {\n const res = await fetch(url(path), {\n method: \"POST\",\n headers: { ...authHeaders(), \"Content-Type\": \"application/json\" },\n credentials,\n body: JSON.stringify(body),\n });\n const json = (await res.json()) as {\n data?: T;\n error?: { message?: string };\n };\n if (!res.ok) {\n return {\n success: false,\n error: json?.error?.message || `HTTP ${res.status}`,\n };\n }\n return { success: true, data: (json?.data ?? json) as T };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : \"Request failed\",\n };\n }\n }\n\n return {\n async fetchModels(signal) {\n try {\n const res = await fetch(url(\"/models\"), {\n method: \"GET\",\n headers: authHeaders(),\n credentials,\n signal,\n });\n if (!res.ok) return { ok: false, data: EMPTY_MODELS };\n const body = (await res.json()) as {\n default?: unknown;\n models?: Array<{\n slug?: unknown;\n label?: unknown;\n promptUsdPerMillion?: unknown;\n contextTokens?: unknown;\n }>;\n };\n // A well-formed response must carry a models array; anything else is\n // treated as a failure so the caller retries rather than caching garbage.\n if (!Array.isArray(body.models))\n return { ok: false, data: EMPTY_MODELS };\n const models: AssistantModelOption[] = [];\n for (const m of body.models) {\n const slug = typeof m.slug === \"string\" ? m.slug : null;\n if (!slug) continue;\n const label = typeof m.label === \"string\" ? m.label : slug;\n const option: AssistantModelOption = { slug, label };\n if (typeof m.promptUsdPerMillion === \"number\") {\n option.promptUsdPerMillion = m.promptUsdPerMillion;\n }\n if (typeof m.contextTokens === \"number\") {\n option.contextTokens = m.contextTokens;\n }\n models.push(option);\n }\n return {\n // Empty ⇒ catalog unavailable: report not-ok so the caller retries next\n // mount instead of caching an empty picker for the session.\n ok: models.length > 0,\n data: {\n default: typeof body.default === \"string\" ? body.default : null,\n models,\n },\n };\n } catch {\n return { ok: false, data: EMPTY_MODELS };\n }\n },\n\n async fetchThreads(signal) {\n try {\n const res = await fetch(url(\"/threads\"), {\n method: \"GET\",\n headers: authHeaders(),\n credentials,\n signal,\n });\n if (!res.ok) return null;\n const body = (await res.json()) as {\n threads?: Array<{\n id?: unknown;\n title?: unknown;\n createdAt?: unknown;\n updatedAt?: unknown;\n }>;\n };\n if (!Array.isArray(body.threads)) return null;\n const out: AssistantThreadSummary[] = [];\n for (const t of body.threads) {\n const id = typeof t.id === \"string\" ? t.id : null;\n if (!id) continue;\n out.push({\n id,\n title: typeof t.title === \"string\" ? t.title : null,\n createdAt: typeof t.createdAt === \"string\" ? t.createdAt : \"\",\n updatedAt: typeof t.updatedAt === \"string\" ? t.updatedAt : \"\",\n });\n }\n return out;\n } catch {\n return null;\n }\n },\n\n async fetchThreadHistory(threadId, signal) {\n try {\n const res = await fetch(\n url(`/threads/${encodeURIComponent(threadId)}/messages`),\n {\n method: \"GET\",\n headers: authHeaders(),\n credentials,\n signal,\n },\n );\n // A 404 means the thread no longer exists — distinct from a transient\n // failure: the caller must drop the dead id rather than keep retrying it.\n if (res.status === 404) return { status: \"gone\" };\n if (!res.ok) return { status: \"error\" };\n const body = (await res.json()) as {\n messages?: Array<{ id?: unknown; role?: unknown; text?: unknown }>;\n proposals?: unknown[];\n };\n if (!Array.isArray(body.messages)) return { status: \"error\" };\n const out: ChatMessage[] = [];\n for (const m of body.messages) {\n const id = typeof m.id === \"string\" ? m.id : null;\n const role =\n m.role === \"user\" || m.role === \"assistant\" ? m.role : null;\n const text = typeof m.text === \"string\" ? m.text : null;\n // Skip a malformed row rather than coercing it into a blank bubble.\n if (id && role && text != null) out.push({ id, role, text });\n }\n // Restore unconfirmed proposals so the card survives reload. Absent on an\n // older server (or a non-tool deployment) → an empty list, no cards.\n const proposals: PendingProposal[] = [];\n if (Array.isArray(body.proposals)) {\n for (const p of body.proposals) {\n const parsed = parseRestoredProposal(p);\n if (parsed) proposals.push(parsed);\n }\n }\n return { status: \"ok\", messages: out, proposals };\n } catch {\n if (signal?.aborted) return { status: \"error\" };\n return { status: \"error\" };\n }\n },\n\n async streamChat(req, onEvent, signal) {\n // Raw fetch, not `postJson`: that helper reads `res.json()`, which would\n // consume the body and defeat streaming. CSRF is covered the same way as\n // every other authenticated POST in the app — a SameSite cookie plus the\n // configured `X-Requested-With` marker (in `authHeaders`) and Origin\n // validation server-side; this only adds the SSE `Accept`.\n const res = await fetch(url(\"/chat\"), {\n method: \"POST\",\n headers: {\n ...authHeaders(),\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n },\n credentials,\n body: JSON.stringify(req),\n signal,\n });\n\n if (!res.ok) {\n onEvent(await readErrorEvent(res));\n return;\n }\n if (!res.body) {\n onEvent({\n type: \"error\",\n data: {\n code: \"NO_BODY\",\n message: \"The assistant stream is unavailable\",\n },\n });\n return;\n }\n\n // A well-formed turn ends with a `done` (or, on failure, an `error`) frame.\n // If the body closes without one, settle anyway so the UI doesn't hang in\n // the streaming state forever.\n let settled = false;\n await readSSEEvents(res.body, (frame) => {\n const ev = toStreamEvent(frame.eventType ?? null, frame.data);\n if (!ev) return;\n if (ev.type === \"done\" || ev.type === \"error\") settled = true;\n onEvent(ev);\n });\n if (!settled) {\n onEvent({\n type: \"error\",\n data: {\n code: \"STREAM_CLOSED\",\n message: \"The assistant stream ended unexpectedly\",\n },\n });\n }\n },\n\n async confirmProposal(proposalId) {\n const res = await postJson<{\n success?: boolean;\n output?: unknown;\n retryable?: boolean;\n error?: { code: string; message: string };\n }>(\"/tools/execute\", { proposalId });\n\n // postJson reports transport/HTTP success in `success`; on a non-2xx it has\n // already flattened the server's error to a message string.\n if (!res.success) {\n return {\n ok: false,\n error: res.error ?? \"The action could not be completed\",\n };\n }\n const body = res.data;\n if (body?.success) {\n return { ok: true, output: body.output, retryable: body.retryable };\n }\n return {\n ok: false,\n error: body?.error?.message ?? \"The action could not be completed\",\n };\n },\n\n async deleteThread(threadId) {\n try {\n const res = await fetch(url(`/threads/${encodeURIComponent(threadId)}`), {\n method: \"DELETE\",\n headers: authHeaders(),\n credentials,\n });\n // 404 ⇒ already gone; treat as success so a retry/double-delete is a no-op.\n return { ok: res.ok || res.status === 404 };\n } catch {\n return { ok: false };\n }\n },\n };\n}\n","/**\n * Provides the configured {@link AssistantClient} to the assistant hooks. The\n * host (a same-origin app, or a cross-origin embedder) builds one client with\n * its transport config and supplies it here; the hooks read it rather than\n * importing a hard-wired same-origin transport, which is what makes the panel\n * portable across hosts.\n */\n\nimport { createContext, type ReactNode, useContext } from \"react\";\nimport type { AssistantClient } from \"./client\";\n\nconst AssistantClientContext = createContext<AssistantClient | null>(null);\n\nexport function AssistantClientProvider({\n client,\n children,\n}: {\n client: AssistantClient;\n children: ReactNode;\n}) {\n return (\n <AssistantClientContext.Provider value={client}>\n {children}\n </AssistantClientContext.Provider>\n );\n}\n\n/**\n * The assistant client for the current host. Throws when no provider is mounted\n * rather than silently falling back to a default transport — a missing provider\n * is a wiring bug, and a hidden default would mask it (and could target the\n * wrong origin).\n */\nexport function useAssistantClient(): AssistantClient {\n const client = useContext(AssistantClientContext);\n if (!client) {\n throw new Error(\n \"useAssistantClient must be used within an <AssistantClientProvider>\",\n );\n }\n return client;\n}\n","/**\n * React binding for the assistant panel: owns the reducer, drives the chat SSE\n * stream and the proposal-confirmation call, and persists the thread across\n * reloads. All rendering decisions live in the pure reducer + presentation\n * helpers; this hook is the glue between them and the network.\n */\n\nimport { useCallback, useEffect, useReducer, useRef, useState } from \"react\";\nimport { useAssistantClient } from \"./client-context\";\nimport { loadThread, saveThread } from \"./persistence\";\nimport { resolveConfirmation } from \"./presentation\";\nimport {\n type AssistantState,\n assistantReducer,\n initialAssistantState,\n selectVisibleState,\n} from \"./reducer\";\nimport type { ChatMessage, PendingProposal } from \"./types\";\n\n/** Host integration callbacks for {@link useAssistantChat}. */\nexport interface UseAssistantChatOptions {\n /**\n * Called after a workflow-mutating tool (`create_workflow`, `author_workflow`,\n * …) is confirmed successfully — the host re-fetches its workflow list so the\n * result appears without a manual reload. Replaces the in-app cross-module\n * signal the platform used.\n */\n onWorkflowMutation?: () => void;\n}\n\nconst EMPTY_IDS: ReadonlySet<string> = new Set();\n\n/** Confirmed tools whose success changes the caller's workflow set — on success\n * the Workflows page is signaled to refetch so the result appears without a\n * manual reload. */\nconst WORKFLOW_MUTATING_TOOLS: ReadonlySet<string> = new Set([\n \"create_workflow\",\n \"author_workflow\",\n \"update_workflow\",\n \"set_workflow_enabled\",\n]);\n\nfunction statusMessage(text: string): ChatMessage {\n return { id: `status-${uuid()}`, role: \"status\", text };\n}\n\nfunction uuid(): string {\n return crypto.randomUUID();\n}\n\nexport interface AssistantChat {\n state: AssistantState;\n /** Proposal ids whose confirmation is currently in flight (for disabling). */\n confirmingIds: ReadonlySet<string>;\n /** The user's selected model slug, or null to use the server default. */\n selectedModel: string | null;\n /** Choose the model for subsequent turns (persisted per user). */\n setModel: (model: string | null) => void;\n send: (message: string) => void;\n stop: () => void;\n confirm: (proposal: PendingProposal) => Promise<void>;\n cancel: (proposal: PendingProposal) => void;\n reset: () => void;\n /** Open an existing thread from history, loading its transcript. */\n switchThread: (threadId: string) => void;\n /** True while a switched-to thread's transcript is loading — the composer is\n * held closed until it resolves so a turn can't run against hidden context. */\n restoring: boolean;\n}\n\nexport function useAssistantChat(\n userId: string | null,\n options?: UseAssistantChatOptions,\n): AssistantChat {\n const [state, dispatch] = useReducer(\n assistantReducer,\n userId,\n (uid): AssistantState => {\n return {\n ...initialAssistantState(),\n ownerId: uid,\n threadId: loadThread(uid).threadId,\n };\n },\n );\n\n const abortRef = useRef<AbortController | null>(null);\n // Aborts an in-flight thread-history restore when the user changes or the\n // panel unmounts, so a late response can't land in a different conversation.\n const historyAbortRef = useRef<AbortController | null>(null);\n // Records which userId the state has already been hydrated for, so the\n // user-change effect fires exactly once per switch. Persistence does NOT key\n // off this — it keys off `state.ownerId`, which moves atomically with the data.\n const hydratedUserRef = useRef<string | null>(userId);\n // Monotonic token identifying the authoritative stream. Aborting is async, so\n // a superseded stream can still have buffered events in flight; each stream\n // captures its token at start and its callbacks no-op once the token moves on.\n // This is what stops a prior user's late events from landing in a new user's\n // hydrated state (a cross-account leak that abort alone cannot prevent).\n const streamSeqRef = useRef(0);\n // Same idea for the (non-abortable) confirmation request: it captures this\n // token before awaiting and no-ops once the conversation it belonged to has\n // been replaced by a user switch or reset, so a late confirmation response\n // cannot append into a different user's hydrated thread.\n const confirmSeqRef = useRef(0);\n // Latest state + current userId, readable from event-handler closures without\n // re-creating them. Written during render (not a passive effect) so the owner\n // guard in `send`/`confirm` sees the new user on the very commit after an auth\n // change — a passive effect would lag by a frame, leaving a window where a send\n // could still target the prior user's thread.\n //\n // These refs are read only in event handlers, which fire after a commit (by\n // which point React has committed the latest render and updated them), so they\n // reflect committed state at read time. A render thrown away under concurrent\n // mode could momentarily leave a ref pointing at uncommitted state, but the\n // failure degrades safely: the owner guard (`ownerId !== userIdRef.current`)\n // would at worst DROP a send — never leak — and a mismatched thread id is\n // rejected server-side (404) regardless.\n const stateRef = useRef(state);\n const userIdRef = useRef(userId);\n stateRef.current = state;\n userIdRef.current = userId;\n\n // The transport, held in a ref so the event-handler callbacks (send/confirm/\n // switchThread) keep stable identities while still reaching the current client.\n const client = useAssistantClient();\n const clientRef = useRef(client);\n clientRef.current = client;\n\n // Host callbacks held in a ref so the event-handler callbacks below keep\n // stable identities while still calling the latest-supplied handler.\n const onWorkflowMutationRef = useRef(options?.onWorkflowMutation);\n onWorkflowMutationRef.current = options?.onWorkflowMutation;\n\n // Proposal ids whose confirmation request is in flight. The ref is the\n // synchronous guard against a double-click issuing a duplicate execute; the\n // state mirror drives disabling the card's buttons.\n const confirmingRef = useRef<Set<string>>(new Set());\n const [confirmingIds, setConfirmingIds] =\n useState<ReadonlySet<string>>(EMPTY_IDS);\n\n // Synchronous \"a chat request is in flight\" guard. Set before the fetch and\n // cleared when it settles/stops/resets, so two submits in the same tick can't\n // both pass the status check and start two billable streams.\n const sendingRef = useRef(false);\n\n // True while a thread opened from history is loading its transcript. The\n // composer is disabled until it resolves so a send can't run a turn against\n // server-side context the user can't yet see. The ref is the synchronous guard\n // for `send`; the state drives the disabled composer. Only `switchThread` sets\n // it (the mount restore resumes the user's own current thread).\n const restoringRef = useRef(false);\n const [restoring, setRestoring] = useState(false);\n const setRestoringBoth = useCallback((v: boolean) => {\n restoringRef.current = v;\n setRestoring(v);\n }, []);\n\n // The user's selected model (a per-user preference, persisted alongside the\n // thread id). A ref mirror lets `send` read the current choice without being\n // re-created. null → the server's default model.\n const [selectedModel, setSelectedModel] = useState<string | null>(\n () => loadThread(userId).model,\n );\n const selectedModelRef = useRef(selectedModel);\n selectedModelRef.current = selectedModel;\n\n // When the signed-in user changes under a mounted panel (auth refresh, or a\n // mount before auth resolved), invalidate the in-flight stream, abort it, and\n // reload that user's own thread. Without this, the prior user's transcript\n // would persist under the new user's key — a cross-account leak.\n useEffect(() => {\n if (hydratedUserRef.current === userId) return;\n streamSeqRef.current += 1;\n confirmSeqRef.current += 1;\n abortRef.current?.abort();\n // Abort a pending thread-history load and re-open the composer so a switch\n // that was mid-restore for the prior user doesn't leave the new user's\n // conversation wedged closed.\n historyAbortRef.current?.abort();\n setRestoringBoth(false);\n sendingRef.current = false;\n confirmingRef.current.clear();\n setConfirmingIds(EMPTY_IDS);\n hydratedUserRef.current = userId;\n // Load the new user's own model preference (the prior user's must not carry\n // over). The ref is updated synchronously so a send on this commit reads it.\n const nextModel = loadThread(userId).model;\n selectedModelRef.current = nextModel;\n setSelectedModel(nextModel);\n dispatch({\n type: \"hydrate\",\n ownerId: userId,\n threadId: loadThread(userId).threadId,\n messages: [],\n });\n }, [userId, setRestoringBoth]);\n\n // Restore the visible transcript for the persisted thread from the server,\n // keyed by user (runs on mount and whenever the signed-in user changes). The\n // transcript is never cached client-side — it can carry workflow YAML or\n // pasted secrets, and localStorage survives logout — so a reload starts blank\n // and the prior conversation is rehydrated here from the durable thread.\n // The reducer applies it only if the conversation is still idle/empty for this\n // exact owner+thread, so a late response can never clobber a started turn, a\n // new chat, or a switched account.\n useEffect(() => {\n const threadId = loadThread(userId).threadId;\n if (!userId || !threadId) return;\n const ac = new AbortController();\n historyAbortRef.current?.abort();\n historyAbortRef.current = ac;\n void clientRef.current\n .fetchThreadHistory(threadId, ac.signal)\n .then((result) => {\n if (ac.signal.aborted) return;\n if (result.status === \"ok\") {\n // An existing thread with no completed turns AND no pending proposals\n // yields nothing to restore — keep the thread id (it's live). A pending\n // proposal alone is enough to restore (its card must come back).\n if (result.messages.length > 0 || result.proposals.length > 0) {\n dispatch({\n type: \"restore_history\",\n ownerId: userId,\n threadId,\n messages: result.messages,\n proposals: result.proposals,\n });\n }\n } else if (result.status === \"gone\") {\n // The thread was deleted server-side (404). Drop the dead id so the next\n // send starts fresh instead of 404-ing forever. The reducer setting\n // threadId to null cascades to the persistence effect, clearing storage.\n dispatch({ type: \"thread_gone\", ownerId: userId, threadId });\n }\n // status === \"error\" → transient; keep the thread id and don't restore.\n })\n .catch(() => {\n // fetchThreadHistory returns a typed {status:\"error\"} rather than\n // rejecting; guard a future change from leaking an unhandled rejection.\n });\n return () => ac.abort();\n }, [userId]);\n\n // Persist thread id + transcript whenever the conversation settles, under the\n // owner carried in state. Because `state.ownerId` and the data it labels move\n // together (set atomically by the reducer), a write can never land under a\n // different user's key. Skipped while streaming: `state.messages` gets a fresh\n // reference on every delta, so persisting per-delta would hammer localStorage;\n // the turn is saved once it settles (status leaves \"streaming\").\n useEffect(() => {\n if (state.status === \"streaming\") return;\n // Skip while the data's owner doesn't match the live user — the window\n // between an auth change and the hydrate that follows it. `selectedModelRef`\n // lives outside the reducer, so it can already hold the NEW user's preference\n // while `state.ownerId` still holds the OLD user's id; persisting then would\n // write the new user's model under the old user's key. The owner guard closes\n // that cross-account write, matching the same pattern `send`/`confirm` use.\n if (state.ownerId !== userIdRef.current) return;\n saveThread(state.ownerId, {\n threadId: state.threadId,\n model: selectedModelRef.current,\n });\n // `selectedModel` is intentionally NOT a dependency: a model-only change is\n // persisted immediately by `setModel` itself, so this effect only needs to\n // re-run on the conversation transitions above. If that direct persist is\n // ever removed, add `selectedModel` here.\n }, [state.ownerId, state.threadId, state.status]);\n\n // Abort any in-flight stream on unmount so a closed panel stops billing.\n useEffect(() => {\n return () => abortRef.current?.abort();\n }, []);\n\n const send = useCallback((message: string) => {\n // Synchronous in-flight guard — closes the window where two submits in the\n // same tick both read a not-yet-committed `idle` status and start two\n // billable streams.\n if (sendingRef.current) return;\n const text = message.trim();\n const current = stateRef.current;\n // Refuse if the loaded conversation doesn't belong to the current user. This\n // only differs for the brief committed frame between an auth change and the\n // hydrate that follows it, where `current` still holds the prior user's\n // thread — sending then would attach this message to that thread id.\n if (current.ownerId !== userIdRef.current) return;\n // Refuse while a switched-to thread's transcript is still loading — sending\n // now would run a turn against context the user can't see yet.\n if (restoringRef.current) return;\n // Refuse while a turn is streaming, or while a mutating proposal is awaiting\n // the user's decision — a new turn must not abandon an unresolved proposal.\n if (!text || current.status === \"streaming\") return;\n if (current.pendingProposals.length > 0) return;\n\n dispatch({\n type: \"send\",\n messageId: uuid(),\n assistantId: uuid(),\n text,\n });\n\n const seq = ++streamSeqRef.current;\n const ac = new AbortController();\n abortRef.current = ac;\n sendingRef.current = true;\n clientRef.current\n .streamChat(\n {\n message: text,\n // null → omit, so the server applies its default model.\n model: selectedModelRef.current ?? undefined,\n threadId: current.threadId ?? undefined,\n turnKey: uuid(),\n },\n (event) => {\n // Drop events from a stream that has been superseded (new turn, stop,\n // reset, or user switch) so they cannot mutate unrelated state.\n if (streamSeqRef.current === seq) dispatch({ type: \"stream\", event });\n },\n ac.signal,\n )\n .catch((err: unknown) => {\n // A user-initiated abort is reported via the `stopped` action, not as a\n // failure; a superseded stream is ignored entirely. Only surface a\n // genuine network/parse error for the still-current stream.\n if (ac.signal.aborted || streamSeqRef.current !== seq) return;\n dispatch({\n type: \"stream_failed\",\n error: {\n code: \"NETWORK\",\n message:\n err instanceof Error ? err.message : \"The connection failed\",\n },\n });\n })\n .finally(() => {\n // This stream is over (settled, failed, or aborted) — allow the next\n // send. A superseded stream clearing the flag is harmless: a newer send\n // already set its own.\n if (streamSeqRef.current === seq) sendingRef.current = false;\n });\n }, []);\n\n const stop = useCallback(() => {\n streamSeqRef.current += 1;\n abortRef.current?.abort();\n sendingRef.current = false;\n dispatch({ type: \"stopped\" });\n }, []);\n\n const confirm = useCallback(async (proposal: PendingProposal) => {\n const pid = proposal.proposalId;\n if (!pid) {\n dispatch({\n type: \"proposal_resolved\",\n callId: proposal.callId,\n status: null,\n error: {\n code: \"TOOL_FAILED\",\n message: \"This action can no longer be confirmed.\",\n },\n });\n return;\n }\n\n // Guard against a double-click issuing a duplicate execute for the same\n // proposal — the second would otherwise hit PROPOSAL_ALREADY_CONSUMED and\n // overwrite the first's success with an error.\n if (confirmingRef.current.has(pid)) return;\n confirmingRef.current.add(pid);\n setConfirmingIds(new Set(confirmingRef.current));\n\n // Snapshot the conversation generation; if a user switch or reset replaces\n // the conversation while this request is in flight, the late response must\n // not land in the new conversation.\n const seq = confirmSeqRef.current;\n try {\n const result = await clientRef.current.confirmProposal(pid);\n if (confirmSeqRef.current !== seq) return;\n // A RETRYABLE failure (the workflow references an integration that isn't\n // connected): the server re-opened the proposal, so KEEP the card and show\n // the reason on it. The user connects the integration (the card's Connect\n // button) and confirms again — no need to re-ask the assistant.\n if (result.ok && result.retryable) {\n const { error } = resolveConfirmation(proposal.name, result);\n dispatch({\n type: \"proposal_retry_failed\",\n callId: proposal.callId,\n message:\n error?.message ??\n \"Connect the required integration, then confirm again.\",\n });\n return;\n }\n const { statusText, error } = resolveConfirmation(proposal.name, result);\n dispatch({\n type: \"proposal_resolved\",\n callId: proposal.callId,\n status: statusText ? statusMessage(statusText) : null,\n error,\n });\n // A successful workflow mutation won't show on an already-open Workflows\n // page (it fetches on mount and shares no cache) — signal it to refetch.\n // `error === null` is the clean-success signal from resolveConfirmation.\n if (!error && WORKFLOW_MUTATING_TOOLS.has(proposal.name)) {\n onWorkflowMutationRef.current?.();\n }\n } catch (err) {\n if (confirmSeqRef.current !== seq) return;\n // confirmProposal returns a typed outcome rather than throwing, but guard\n // anyway: an unexpected throw must still clear the card and surface an\n // error instead of escaping as an unhandled rejection.\n dispatch({\n type: \"proposal_resolved\",\n callId: proposal.callId,\n status: null,\n error: {\n code: \"TOOL_FAILED\",\n message:\n err instanceof Error\n ? err.message\n : \"The action could not be completed\",\n },\n });\n } finally {\n // Only touch the in-flight set if this confirmation still owns the\n // conversation; a switch/reset already cleared it.\n if (confirmSeqRef.current === seq) {\n confirmingRef.current.delete(pid);\n setConfirmingIds(new Set(confirmingRef.current));\n }\n }\n }, []);\n\n const cancel = useCallback((proposal: PendingProposal) => {\n // Once a confirmation is in flight the action is already running server-side\n // and can't be cancelled — ignore a cancel click that races it, so the\n // transcript can't show both \"cancelled\" and the action's success.\n if (proposal.proposalId && confirmingRef.current.has(proposal.proposalId)) {\n return;\n }\n dispatch({\n type: \"proposal_resolved\",\n callId: proposal.callId,\n status: statusMessage(\"Action cancelled.\"),\n error: null,\n });\n }, []);\n\n const reset = useCallback(() => {\n streamSeqRef.current += 1;\n confirmSeqRef.current += 1;\n abortRef.current?.abort();\n historyAbortRef.current?.abort();\n setRestoringBoth(false);\n sendingRef.current = false;\n confirmingRef.current.clear();\n setConfirmingIds(EMPTY_IDS);\n dispatch({ type: \"reset\" });\n }, [setRestoringBoth]);\n\n // Open a past thread from the history switcher: invalidate any in-flight\n // stream/confirmation (so their late events can't land in the switched-to\n // conversation), pin the chosen thread, persist it, then load its transcript.\n // Guarded by owner so a stale render can't switch under a different account.\n const switchThread = useCallback(\n (threadId: string) => {\n const current = stateRef.current;\n const uid = userIdRef.current;\n if (current.ownerId !== uid) return;\n if (threadId === current.threadId) return;\n // Refuse while a turn is streaming or a proposal is awaiting confirmation —\n // the same guard `send` uses. Navigating away would abandon the live turn /\n // unresolved proposal; the user finishes it, or \"New chat\" is the explicit\n // discard. The DISABLED switcher in the UI is the primary guard; this is the\n // backstop, so it silently no-ops by design for a programmatic/keyboard\n // caller that bypasses the disabled state.\n if (\n current.status === \"streaming\" ||\n current.pendingProposals.length > 0\n ) {\n return;\n }\n streamSeqRef.current += 1;\n confirmSeqRef.current += 1;\n abortRef.current?.abort();\n sendingRef.current = false;\n confirmingRef.current.clear();\n setConfirmingIds(EMPTY_IDS);\n dispatch({ type: \"switch_thread\", threadId });\n // Hold the composer closed until the transcript loads — a send before then\n // would run a turn against context the user can't yet see.\n setRestoringBoth(true);\n // Persist the new active thread now so a reload before the next turn restores\n // it (the settled-save effect also covers it). Keep the current model choice.\n saveThread(uid, { threadId, model: selectedModelRef.current });\n // Load the chosen thread's transcript, superseding any in-flight history\n // fetch. restore_history only applies on a matching idle owner+thread, so a\n // mid-load send or a second switch can't be clobbered by this response.\n const ac = new AbortController();\n historyAbortRef.current?.abort();\n historyAbortRef.current = ac;\n void clientRef.current\n .fetchThreadHistory(threadId, ac.signal)\n .then((result) => {\n if (ac.signal.aborted) return;\n if (result.status === \"ok\") {\n if (result.messages.length > 0 || result.proposals.length > 0) {\n dispatch({\n type: \"restore_history\",\n ownerId: uid,\n threadId,\n messages: result.messages,\n proposals: result.proposals,\n });\n }\n } else if (result.status === \"gone\") {\n dispatch({ type: \"thread_gone\", ownerId: uid, threadId });\n } else {\n // Transient load failure: drop the active thread (it carries\n // server-side context the user can't see) and surface a visible\n // error. The next send then starts a FRESH thread rather than running\n // against the unloaded conversation's hidden context.\n dispatch({\n type: \"history_failed\",\n ownerId: uid,\n threadId,\n error: {\n code: \"HISTORY_LOAD_FAILED\",\n message:\n \"Couldn't load that conversation. You're in a new chat — reopen it from history to try again.\",\n },\n });\n }\n // Re-open the composer once the load settles (success shows the\n // transcript; failure dropped the thread + showed the error above).\n // Only the current fetch clears it — a superseded one returned early on\n // `aborted`.\n setRestoringBoth(false);\n })\n .catch(() => {\n // fetchThreadHistory returns a typed {status:\"error\"} rather than\n // rejecting; guard a future change from leaking an unhandled rejection.\n if (!ac.signal.aborted) setRestoringBoth(false);\n });\n },\n [setRestoringBoth],\n );\n\n // Choose the model for subsequent turns. The model preference is per user, not\n // per turn, so persist it immediately (alongside the current thread id) rather\n // than waiting for a turn to settle.\n const setModel = useCallback((model: string | null) => {\n selectedModelRef.current = model;\n setSelectedModel(model);\n // Same owner guard the persistence effect uses: in the window between an auth\n // change and the hydrate that follows it, stateRef still holds the prior\n // user's thread while userIdRef already points to the new user. Persisting\n // then would write the new user's model (and the OLD user's thread id) under\n // a mismatched key. Skip it — the state still updates, and the next settled\n // save persists it once the owner is consistent.\n const currentUserId = userIdRef.current;\n if (stateRef.current.ownerId === currentUserId) {\n saveThread(currentUserId, {\n threadId: stateRef.current.threadId,\n model,\n });\n }\n }, []);\n\n // Reactive recovery: if a turn is rejected because the selected model is no\n // longer offered (e.g. ASSISTANT_MODELS was changed and a stale slug was still\n // persisted), clear the selection so the NEXT send falls back to the server\n // default instead of failing again. The picker also reconciles proactively\n // once the model list loads; this closes the window where a send races that\n // load. Guarded by owner so a late error from a prior user can't act here.\n useEffect(() => {\n if (\n state.error?.code === \"MODEL_NOT_ALLOWED\" &&\n state.ownerId === userIdRef.current &&\n selectedModelRef.current !== null\n ) {\n setModel(null);\n }\n }, [state.error, state.ownerId, setModel]);\n\n // Expose only state that belongs to the current user: between an auth change\n // and the hydrate effect that follows it, the raw state still holds the prior\n // user's conversation, which must never render. Operations (send/confirm) read\n // the raw state via `stateRef`, so they still act on the true thread.\n const visibleState = selectVisibleState(state, userId);\n return {\n state: visibleState,\n confirmingIds,\n selectedModel,\n setModel,\n send,\n stop,\n confirm,\n cancel,\n reset,\n switchThread,\n restoring,\n };\n}\n","/**\n * Thread-id persistence for the assistant panel. Only the opaque server thread\n * id is kept in localStorage so a reload continues the same server-side\n * conversation. The message transcript is deliberately NOT cached: it can\n * contain workflow definitions, integration data, or pasted secrets, and\n * localStorage survives logout and is readable by any script on the origin —\n * caching it would be a privacy regression on shared devices.\n *\n * The visible transcript is instead rehydrated from the server on load (the\n * `GET /assistant/threads/:id/messages` endpoint, called by `useAssistantChat`),\n * keyed by this persisted thread id — so a reload restores the prior\n * conversation without ever caching its contents locally.\n *\n * Keyed by user id so two accounts on one browser never share a thread.\n * Anonymous (null-user) sessions are NOT persisted — otherwise every\n * unauthenticated visitor on a shared device would read the same \"anon\" thread.\n */\n\nconst VERSION = \"v1\";\n\nexport interface PersistedThread {\n threadId: string | null;\n /** The user's last-selected model slug — a non-sensitive UI preference, so\n * unlike the transcript it is safe to cache. null → use the server default. */\n model: string | null;\n}\n\n/** Storage key for a signed-in user, or null for an anonymous session (which is\n * never persisted). */\nfunction keyFor(userId: string | null): string | null {\n return userId ? `assistant:${VERSION}:${userId}` : null;\n}\n\nexport function loadThread(userId: string | null): PersistedThread {\n const key = keyFor(userId);\n if (!key) return { threadId: null, model: null };\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return { threadId: null, model: null };\n const parsed = JSON.parse(raw) as Partial<PersistedThread>;\n return {\n threadId: typeof parsed.threadId === \"string\" ? parsed.threadId : null,\n model: typeof parsed.model === \"string\" ? parsed.model : null,\n };\n } catch {\n return { threadId: null, model: null };\n }\n}\n\nexport function saveThread(\n userId: string | null,\n thread: PersistedThread,\n): void {\n const key = keyFor(userId);\n if (!key) return;\n try {\n localStorage.setItem(\n key,\n JSON.stringify({ threadId: thread.threadId, model: thread.model }),\n );\n } catch {\n // Storage unavailable (private mode, quota) — persistence is best-effort.\n }\n}\n","/**\n * Pure view-model mappers for the assistant panel: how an error code becomes an\n * inline message + actionable next step, and how a proposed tool call becomes a\n * confirmation card. Kept free of React so the rendering decisions are unit\n * testable in isolation.\n */\n\nimport type { PendingProposal } from \"./types\";\n\n/** USD balance below which the panel surfaces a low-balance warning. Mirrors\n * the wallet warning threshold on the Billing page. */\nexport const LOW_BALANCE_THRESHOLD = 1;\n\nexport interface ErrorCta {\n label: string;\n to: string;\n}\n\nexport interface ErrorView {\n message: string;\n cta: ErrorCta | null;\n}\n\nconst ADD_CREDITS_CTA: ErrorCta = { label: \"Add credits\", to: \"/app/billing\" };\nconst CONNECT_CTA: ErrorCta = {\n label: \"Connect an integration\",\n to: \"/app/integrations\",\n};\n\n/**\n * Map a server error code + message to what the user sees and can do next.\n * Codes with a clear remedy carry a CTA; the rest fall back to the server's own\n * message, which is already written for the end user.\n */\nexport function presentError(code: string, message: string): ErrorView {\n switch (code) {\n case \"INSUFFICIENT_BALANCE\":\n return {\n message:\n \"You're out of credits. Add credits to keep using the assistant.\",\n cta: ADD_CREDITS_CTA,\n };\n case \"MODEL_ACCESS_UNCONFIGURED\":\n return {\n message:\n \"Model access isn't configured for your account yet. Please contact support.\",\n cta: null,\n };\n case \"BILLING_UNAVAILABLE\":\n return {\n message: \"Billing is temporarily unavailable. Try again in a moment.\",\n cta: null,\n };\n case \"TOO_MANY_STREAMS\":\n return {\n message:\n \"You have too many assistant requests in flight. Wait a moment and retry.\",\n cta: null,\n };\n case \"THREAD_BUSY\":\n case \"TURN_IN_PROGRESS\":\n return {\n message: \"A previous request is still finishing. Try again shortly.\",\n cta: null,\n };\n case \"INTEGRATION_DISCONNECTED\":\n return {\n message: `${message} Connect the integration, then ask again.`,\n cta: CONNECT_CTA,\n };\n case \"TOOL_FAILED\":\n case \"NETWORK\":\n return { message: message || \"Something went wrong.\", cta: null };\n default:\n return { message: message || \"Something went wrong.\", cta: null };\n }\n}\n\nexport interface ProposalField {\n label: string;\n value: string;\n}\n\n/** A new skill minted alongside a workflow, shown as a named line on the card\n * so the user sees what's being created without the raw skills JSON. */\nexport interface ProposalSkill {\n name: string;\n description: string | null;\n}\n\nexport interface ProposalView {\n /** Verb-first heading, e.g. \"Create workflow\". */\n title: string;\n /** A body preview with its own label — a workflow's YAML (`kind: \"workflow\"`,\n * rendered as a node graph with a YAML toggle) or a skill's instructions\n * (`kind: \"text\"`, shown verbatim). Null when the action has no body. */\n preview: { label: string; content: string; kind: \"workflow\" | \"text\" } | null;\n /** Scalar arguments to show as a key/value list. */\n fields: ProposalField[];\n /** New skills minted alongside a workflow (author_workflow); omitted otherwise. */\n skills?: ProposalSkill[];\n}\n\nfunction asRecord(args: unknown): Record<string, unknown> {\n return args && typeof args === \"object\" && !Array.isArray(args)\n ? (args as Record<string, unknown>)\n : {};\n}\n\nfunction str(v: unknown): string {\n if (v == null) return \"\";\n return typeof v === \"string\" ? v : JSON.stringify(v);\n}\n\n/** A non-empty string value, else null — so an empty/absent body yields no\n * (empty) preview rather than a blank monospace block. */\nfunction nonEmptyStr(v: unknown): string | null {\n return typeof v === \"string\" && v.trim() !== \"\" ? v : null;\n}\n\n/** Map an author_workflow `skills` arg to display lines, dropping malformed\n * entries. Returns undefined when there are no new skills to show. */\nfunction parseProposalSkills(v: unknown): ProposalSkill[] | undefined {\n if (!Array.isArray(v) || v.length === 0) return undefined;\n const out: ProposalSkill[] = [];\n for (const item of v) {\n const rec = asRecord(item);\n const name = nonEmptyStr(rec.name);\n if (!name) continue;\n out.push({ name, description: nonEmptyStr(rec.description) });\n }\n return out.length > 0 ? out : undefined;\n}\n\n/** Humanize an unknown tool name (`set_workflow_enabled` → \"Set workflow enabled\"). */\nexport function humanizeToolName(name: string): string {\n const spaced = name.replace(/_/g, \" \").trim();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\n/** Present-tense labels for the inline tool-activity chips (\"Validating\n * workflow…\"). Falls back to a humanized tool name for any unmapped tool, so a\n * newly added read-only tool still renders a sensible label. */\nconst TOOL_ACTIVITY_LABELS: Record<string, string> = {\n get_workflow_schema: \"Reading the workflow format\",\n list_workflows: \"Listing workflows\",\n get_workflow: \"Reading workflow\",\n validate_workflow: \"Validating workflow\",\n list_skills: \"Listing skills\",\n get_skill: \"Reading skill\",\n list_integrations: \"Checking integrations\",\n get_credit_balance: \"Checking balance\",\n get_usage: \"Checking usage\",\n list_api_keys: \"Listing API keys\",\n};\n\nexport function describeToolActivity(name: string): string {\n return TOOL_ACTIVITY_LABELS[name] ?? humanizeToolName(name);\n}\n\n/**\n * Describe a proposed mutating action for its confirmation card. Workflow\n * create/update surface a YAML preview (the issue's required behavior); other\n * actions surface their scalar arguments. Unknown tools fall back to a generic\n * heading plus a JSON dump of the arguments so a newly added mutating tool is\n * never silently un-renderable.\n */\nexport function describeProposal(proposal: PendingProposal): ProposalView {\n const args = asRecord(proposal.args);\n const workflowYaml = nonEmptyStr(args.yaml);\n const workflowPreview = workflowYaml\n ? {\n label: \"Workflow definition\",\n content: workflowYaml,\n kind: \"workflow\" as const,\n }\n : null;\n switch (proposal.name) {\n case \"create_workflow\":\n return { title: \"Create workflow\", preview: workflowPreview, fields: [] };\n // author_workflow creates a workflow PLUS the new skills it needs in one\n // unit; show the YAML and name each new skill rather than dumping the raw\n // skills JSON (the card is the canonical, readable view of the proposal).\n case \"author_workflow\":\n return {\n title: \"Create workflow\",\n preview: workflowPreview,\n fields: [],\n skills: parseProposalSkills(args.skills),\n };\n case \"update_workflow\":\n return {\n title: \"Update workflow\",\n preview: workflowPreview,\n fields: [{ label: \"Workflow id\", value: str(args.id) }],\n };\n case \"set_workflow_enabled\":\n return {\n title: args.enabled ? \"Enable workflow\" : \"Disable workflow\",\n preview: null,\n fields: [{ label: \"Workflow id\", value: str(args.id) }],\n };\n case \"create_skill\": {\n const prompt = nonEmptyStr(args.systemPrompt);\n const fields: ProposalField[] = [\n { label: \"Name\", value: str(args.name) },\n ];\n if (nonEmptyStr(args.description))\n fields.push({ label: \"Description\", value: str(args.description) });\n return {\n title: \"Create skill\",\n preview: prompt\n ? { label: \"Instructions\", content: prompt, kind: \"text\" as const }\n : null,\n fields,\n };\n }\n case \"update_skill\": {\n const prompt = nonEmptyStr(args.systemPrompt);\n const fields: ProposalField[] = [\n { label: \"Skill id\", value: str(args.id) },\n ];\n if (nonEmptyStr(args.name))\n fields.push({ label: \"Name\", value: str(args.name) });\n if (nonEmptyStr(args.description))\n fields.push({ label: \"Description\", value: str(args.description) });\n return {\n title: \"Update skill\",\n preview: prompt\n ? { label: \"Instructions\", content: prompt, kind: \"text\" as const }\n : null,\n fields,\n };\n }\n case \"delete_skill\":\n return {\n title: \"Delete skill\",\n preview: null,\n fields: [{ label: \"Skill id\", value: str(args.id) }],\n };\n case \"create_api_key\": {\n const fields: ProposalField[] = [\n { label: \"Name\", value: str(args.name) },\n ];\n if (args.product != null)\n fields.push({ label: \"Product\", value: str(args.product) });\n if (args.budgetUsd != null)\n fields.push({ label: \"Budget (USD)\", value: str(args.budgetUsd) });\n return { title: \"Create API key\", preview: null, fields };\n }\n case \"revoke_api_key\":\n return {\n title: \"Revoke API key\",\n preview: null,\n fields: [{ label: \"Key id\", value: str(args.keyId) }],\n };\n case \"invoke_integration\": {\n const fields: ProposalField[] = [\n { label: \"Action\", value: str(args.path) },\n ];\n if (args.input != null)\n fields.push({ label: \"Input\", value: str(args.input) });\n return { title: \"Run integration action\", preview: null, fields };\n }\n default: {\n const fields = Object.entries(args).map(([label, value]) => ({\n label,\n value: str(value),\n }));\n return { title: humanizeToolName(proposal.name), preview: null, fields };\n }\n }\n}\n\n/**\n * Summarize a confirmed action's result for the transcript. Best-effort and\n * defensive: the output shape is the tool's return value, which varies by tool.\n */\nexport function describeOutcome(name: string, output: unknown): string {\n const o = asRecord(output);\n switch (name) {\n case \"author_workflow\":\n case \"create_workflow\": {\n const wf = asRecord(o.workflow);\n const skillCount = Array.isArray(o.skills) ? o.skills.length : 0;\n if (wf.name) {\n return skillCount > 0\n ? `Created workflow \"${str(wf.name)}\" and ${skillCount} skill${skillCount === 1 ? \"\" : \"s\"}.`\n : `Created workflow \"${str(wf.name)}\".`;\n }\n return \"Workflow created.\";\n }\n case \"update_workflow\": {\n const wf = asRecord(o.workflow);\n return wf.name\n ? `Updated workflow \"${str(wf.name)}\".`\n : \"Workflow updated.\";\n }\n case \"set_workflow_enabled\": {\n const wf = asRecord(o.workflow);\n return wf.enabled ? \"Workflow enabled.\" : \"Workflow disabled.\";\n }\n case \"create_api_key\":\n return o.prefix\n ? `Created API key (${str(o.prefix)}…). Copy it from the API Keys page.`\n : \"API key created.\";\n case \"revoke_api_key\":\n return \"API key revoked.\";\n case \"invoke_integration\":\n return \"Integration action completed.\";\n default:\n return \"Action completed.\";\n }\n}\n\n/**\n * Summarize a confirmed action's FAILURE for the error banner. Mutating tools\n * report a domain failure by returning a negative outcome (see\n * `resolveConfirmation`); this turns that outcome into a human message,\n * preferring the server's own `errors[]`/`message` (already end-user-written)\n * and falling back to the not-found/conflict markers. Best-effort and\n * defensive: the shape varies by tool.\n */\nexport function describeFailure(output: unknown): string {\n const o = asRecord(output);\n const errors = Array.isArray(o.errors) ? o.errors : [];\n const joined = errors\n // An element may be a structured `{ message }` (the workflow compiler shape)\n // or a bare string — surface either rather than dropping a string-only error.\n .map((e) => (typeof e === \"string\" ? e : str(asRecord(e).message)))\n .filter((m) => m.length > 0)\n .join(\"; \");\n if (joined) return joined;\n if (typeof o.message === \"string\" && o.message) return o.message;\n if (o.notFound === true) return \"That no longer exists.\";\n if (o.conflict === true) return \"It changed since it was loaded. Try again.\";\n return \"The action could not be completed.\";\n}\n\nexport function isLowBalance(balanceUsd: number | null): boolean {\n return balanceUsd != null && balanceUsd < LOW_BALANCE_THRESHOLD;\n}\n\n/** The outcome of a confirmed tool call, mirroring `ConfirmResult` from the\n * stream layer without coupling presentation to it. */\nexport type ConfirmOutcome =\n | { ok: true; output: unknown }\n | { ok: false; error: string };\n\nexport interface ConfirmResolution {\n /** Transcript note to append, or null when there's nothing to say. */\n statusText: string | null;\n /** Error banner to surface, or null on a clean success. */\n error: { code: string; message: string } | null;\n}\n\n/**\n * Decide what a confirmed proposal's result means for the transcript and the\n * error banner. The `invoke_integration` tool reports a not-connected provider\n * as a structured `{ ok: false, code: \"NOT_CONNECTED\" }` outcome inside `output`\n * (see the hub integration invoker); that exact signal maps to an\n * `INTEGRATION_DISCONNECTED` error so the panel can offer a \"Connect\" step.\n * Other failures surface as `TOOL_FAILED`. Pure so the classification is\n * unit-testable without the hook.\n */\nexport function resolveConfirmation(\n name: string,\n result: ConfirmOutcome,\n): ConfirmResolution {\n if (result.ok) {\n const out = asRecord(result.output);\n // Match the precise structured signal, not a substring of the whole output —\n // scanning the serialized blob false-positives on unrelated text (e.g. a\n // workflow named \"…NotConnected\").\n if (out.ok === false && out.code === \"NOT_CONNECTED\") {\n return {\n statusText: null,\n error: {\n code: \"INTEGRATION_DISCONNECTED\",\n message: str(out.message) || \"That integration isn't connected.\",\n },\n };\n }\n // A mutating tool reports a DOMAIN failure by RETURNING a negative outcome\n // inside an HTTP-200 success envelope — `callTool` only sets `ok:false` for\n // an unexpected throw, so a rejected create/update/delete arrives here as a\n // \"success\" whose body says it failed. The negative markers, by tool:\n // workflow/skill create → `created:false`; update → `updated:false`; skill\n // delete → `deleted:false`; set-enabled / integration → `ok:false`. Surface\n // the cause instead of reading it as a completion note (the bug where a\n // rejected workflow create showed \"Action completed.\").\n if (\n out.created === false ||\n out.updated === false ||\n out.deleted === false ||\n out.ok === false\n ) {\n return {\n statusText: null,\n error: { code: \"TOOL_FAILED\", message: describeFailure(result.output) },\n };\n }\n return { statusText: describeOutcome(name, result.output), error: null };\n }\n // A failed confirmation (proposal expired, tool error, network). The\n // not-connected case never arrives here — it is an HTTP-200 success whose\n // output carries `ok: false` — so this path is always a genuine failure.\n return {\n statusText: null,\n error: { code: \"TOOL_FAILED\", message: result.error },\n };\n}\n","/**\n * Pure state machine driving the assistant panel. Every UI transition — a user\n * message, each streamed SSE event, a stream failure, a confirmed/cancelled\n * proposal, a manual stop — is modeled as an action here, so the panel's\n * behavior can be verified without a DOM or a live stream.\n */\n\nimport type {\n AssistantStreamEvent,\n ChatMessage,\n PendingProposal,\n ToolOutcome,\n UsageInfo,\n} from \"./types\";\n\nexport type ChatStatus = \"idle\" | \"streaming\" | \"awaiting_confirm\";\n\n/** Cap on in-memory messages. A session can survive route changes and drawer\n * open/close for a long time, so the transcript is bounded to the most recent\n * turns. The streaming message is always appended last, so trimming from the\n * front never drops it. */\nconst MAX_MESSAGES = 200;\n\nfunction capMessages(messages: ChatMessage[]): ChatMessage[] {\n return messages.length > MAX_MESSAGES\n ? messages.slice(-MAX_MESSAGES)\n : messages;\n}\n\nexport interface AssistantState {\n /** The signed-in user this conversation belongs to. Carried in state so it\n * moves atomically with the data it labels — persistence keys off it, and a\n * render whose owner doesn't match the current user is masked (see\n * `selectVisibleState`), preventing a one-frame cross-account leak. */\n ownerId: string | null;\n /** Persisted across reloads to continue the same server-side thread. */\n threadId: string | null;\n messages: ChatMessage[];\n status: ChatStatus;\n /** Id of the assistant message currently accumulating deltas, if any. Set to\n * null mid-turn when a tool runs, so the next text delta opens a fresh\n * assistant bubble — keeping each reasoning segment visually distinct. */\n streamingId: string | null;\n /** Base id for the current turn's assistant bubbles (the `send` assistant id).\n * Post-tool segments derive a unique id from it; null between turns. */\n streamBaseId: string | null;\n /** How many assistant bubbles the current turn has opened (0 = just the first).\n * Drives the per-segment bubble id so segments never collide across turns. */\n segmentSeq: number;\n pendingProposals: PendingProposal[];\n /** Cost/balance from the most recently settled turn. */\n usage: UsageInfo | null;\n /** Model slug the current/most-recent turn ran against (from the thread\n * event), or null before any turn this session. */\n model: string | null;\n /** The current turn's accumulated reasoning/thinking text (reasoning models\n * stream this before the answer). Shown dim while the answer is still pending;\n * reset at the start of each turn. Null when the model emits no reasoning. */\n reasoning: string | null;\n error: { code: string; message: string } | null;\n}\n\nexport type AssistantAction =\n | { type: \"send\"; messageId: string; assistantId: string; text: string }\n | { type: \"stream\"; event: AssistantStreamEvent }\n | { type: \"stream_failed\"; error: { code: string; message: string } }\n | {\n type: \"proposal_resolved\";\n /** The card's canonical identity (the model's tool-call id). */\n callId: string;\n status: ChatMessage | null;\n /** Set on a failed confirmation; null clears any prior error. */\n error: { code: string; message: string } | null;\n }\n | {\n type: \"proposal_retry_failed\";\n /** The card to KEEP (a retryable confirm failure — an unconnected\n * integration). The card stays confirmable; the message is shown on it. */\n callId: string;\n message: string;\n }\n | { type: \"stopped\" }\n | {\n type: \"hydrate\";\n ownerId: string | null;\n threadId: string | null;\n messages: ChatMessage[];\n }\n | {\n type: \"restore_history\";\n ownerId: string | null;\n threadId: string;\n messages: ChatMessage[];\n /** Unconfirmed proposals restored alongside the transcript so a card\n * survives reload. Empty when none are pending. */\n proposals: PendingProposal[];\n }\n | { type: \"thread_gone\"; ownerId: string | null; threadId: string }\n | {\n type: \"history_failed\";\n ownerId: string | null;\n threadId: string;\n error: { code: string; message: string };\n }\n | { type: \"switch_thread\"; threadId: string }\n | { type: \"reset\" };\n\nexport function initialAssistantState(): AssistantState {\n return {\n ownerId: null,\n threadId: null,\n messages: [],\n status: \"idle\",\n streamingId: null,\n streamBaseId: null,\n segmentSeq: 0,\n pendingProposals: [],\n usage: null,\n model: null,\n reasoning: null,\n error: null,\n };\n}\n\n/**\n * The state safe to render for `userId`. When the conversation in state belongs\n * to a different user (the single commit between an auth change and the hydrate\n * that follows it), return a fresh empty state instead of the prior user's\n * transcript — so an account's messages and proposals are never shown, even for\n * one frame, under another account.\n */\nexport function selectVisibleState(\n state: AssistantState,\n userId: string | null,\n): AssistantState {\n return state.ownerId === userId ? state : initialAssistantState();\n}\n\n/** Drop the streaming assistant bubble if it never received any text (e.g. a\n * turn that only proposed an action, or a failure before the first delta). */\nfunction dropEmptyStreaming(\n messages: ChatMessage[],\n streamingId: string | null,\n): ChatMessage[] {\n if (!streamingId) return messages;\n const msg = messages.find((m) => m.id === streamingId);\n if (msg && msg.role === \"assistant\" && msg.text === \"\") {\n return messages.filter((m) => m.id !== streamingId);\n }\n return messages;\n}\n\nfunction appendDelta(\n messages: ChatMessage[],\n streamingId: string | null,\n text: string,\n): ChatMessage[] {\n if (!streamingId) return messages;\n return messages.map((m) =>\n m.id === streamingId ? { ...m, text: m.text + text } : m,\n );\n}\n\nfunction applyStreamEvent(\n state: AssistantState,\n event: AssistantStreamEvent,\n): AssistantState {\n switch (event.type) {\n case \"thread\":\n return {\n ...state,\n threadId: event.data.threadId,\n // The model the server actually ran this turn against (lets the picker\n // reflect reality even when the user never explicitly chose one).\n model: event.data.model ?? state.model,\n };\n\n case \"delta\": {\n if (state.streamingId) {\n return {\n ...state,\n messages: appendDelta(\n state.messages,\n state.streamingId,\n event.data.text,\n ),\n };\n }\n // No open bubble: a tool ran and finalized the prior segment. Open a fresh\n // assistant bubble for this next reasoning segment so the agent's pre- and\n // post-tool text are distinct messages, not one concatenated blob.\n if (!state.streamBaseId) return state; // stray delta outside a turn\n const segmentSeq = state.segmentSeq + 1;\n const id = `${state.streamBaseId}-s${segmentSeq}`;\n return {\n ...state,\n segmentSeq,\n streamingId: id,\n messages: capMessages([\n ...state.messages,\n { id, role: \"assistant\", text: event.data.text },\n ]),\n };\n }\n\n case \"reasoning\":\n // Accumulate the turn's reasoning text; the panel shows it dim while the\n // answer is still pending so a long thinking gap doesn't read as frozen.\n return {\n ...state,\n reasoning: (state.reasoning ?? \"\") + event.data.text,\n };\n\n case \"tool_call\": {\n // A read-only tool started. Finalize the current text segment (dropping it\n // if it never received text, so a tool that runs before any preamble leaves\n // no empty bubble), then append a live \"running\" activity chip. Dedupe by\n // callId so a re-delivered event can't double-add the chip.\n const trimmed = dropEmptyStreaming(state.messages, state.streamingId);\n const chipId = `tool-${event.data.callId}`;\n if (trimmed.some((m) => m.id === chipId)) {\n return { ...state, messages: trimmed, streamingId: null };\n }\n const chip: ChatMessage = {\n id: chipId,\n role: \"tool\",\n text: \"\",\n tool: {\n name: event.data.name,\n status: \"running\",\n args: event.data.args,\n },\n };\n return {\n ...state,\n streamingId: null,\n messages: capMessages([...trimmed, chip]),\n };\n }\n\n case \"tool_result\": {\n // Resolve the activity chip the matching tool_call opened: mark it ok, or\n // failed with the error text. (Mutating tools never arrive here — they are\n // proposed and confirmed through the execute endpoint.)\n const chipId = `tool-${event.data.callId}`;\n const status = event.data.ok ? \"ok\" : \"failed\";\n const errText = event.data.ok\n ? \"\"\n : (event.data.error?.message ?? \"unknown error\");\n // Retain the outcome on the chip so a renderer can show the tool's result\n // body, not just name + status. (The error text stays in `.text` too, for\n // the built-in timeline's one-line chip.)\n const outcome: ToolOutcome = event.data.ok\n ? { ok: true, result: event.data.output }\n : { ok: false, error: event.data.error };\n if (state.messages.some((m) => m.id === chipId)) {\n return {\n ...state,\n messages: state.messages.map((m) =>\n m.id === chipId\n ? {\n ...m,\n text: errText,\n // Preserve the args the matching tool_call recorded — the\n // result event doesn't carry them.\n tool: {\n name: event.data.name,\n status,\n args: m.tool?.args,\n outcome,\n },\n }\n : m,\n ),\n };\n }\n // Defensive: a result with no preceding tool_call (shouldn't happen now\n // that the server emits both) — surface it as a finished chip rather than\n // dropping the information.\n const chip: ChatMessage = {\n id: chipId,\n role: \"tool\",\n text: errText,\n tool: { name: event.data.name, status, outcome },\n };\n return { ...state, messages: capMessages([...state.messages, chip]) };\n }\n\n case \"tool_proposal\": {\n if (state.pendingProposals.some((p) => p.callId === event.data.callId)) {\n return state;\n }\n // event.data carries requirements (when authoring) — stored verbatim as\n // the PendingProposal so the card can render them.\n return {\n ...state,\n pendingProposals: [...state.pendingProposals, event.data],\n };\n }\n\n case \"usage\":\n return {\n ...state,\n usage: {\n costUsd: event.data.costUsd,\n balanceUsd: event.data.balanceUsd,\n promptTokens: event.data.promptTokens,\n completionTokens: event.data.completionTokens,\n durationMs: event.data.durationMs ?? null,\n replayed: event.data.replayed ?? false,\n },\n };\n\n case \"done\": {\n let messages = dropEmptyStreaming(state.messages, state.streamingId);\n // A capped turn hit the per-turn step limit before the model finished its\n // plan (e.g. partway through authoring a workflow). Surface it as a status\n // note so a partial reply is never mistaken for a complete answer, and the\n // user knows they can ask it to continue (the thread keeps the context).\n if (event.data.capped) {\n messages = capMessages([\n ...messages,\n {\n id: `cap-${event.data.turnId}`,\n role: \"status\",\n text: \"I reached the step limit for this turn. Ask me to continue and I'll pick up where I left off.\",\n },\n ]);\n }\n return {\n ...state,\n messages,\n streamingId: null,\n status: state.pendingProposals.length > 0 ? \"awaiting_confirm\" : \"idle\",\n };\n }\n\n case \"error\": {\n const messages = dropEmptyStreaming(state.messages, state.streamingId);\n return {\n ...state,\n messages,\n streamingId: null,\n status: \"idle\",\n // A turn that didn't complete cleanly leaves no confirmable action: a\n // proposal buffered before the failure must not stay actionable.\n pendingProposals: [],\n error: { code: event.data.code, message: event.data.message },\n };\n }\n }\n}\n\nexport function assistantReducer(\n state: AssistantState,\n action: AssistantAction,\n): AssistantState {\n switch (action.type) {\n case \"send\":\n // Pending proposals are intentionally preserved, not cleared: a new turn\n // must never silently drop an unconfirmed mutating action (which would\n // orphan its server-side proposal). The hook + composer block sending\n // while any proposal is pending, so a correct flow never reaches here with\n // one outstanding.\n return {\n ...state,\n messages: capMessages([\n ...state.messages,\n { id: action.messageId, role: \"user\", text: action.text },\n { id: action.assistantId, role: \"assistant\", text: \"\" },\n ]),\n status: \"streaming\",\n streamingId: action.assistantId,\n // The first bubble is segment 0; tool-finalized segments derive their id\n // from this base, so they stay unique across turns.\n streamBaseId: action.assistantId,\n segmentSeq: 0,\n usage: null,\n reasoning: null,\n error: null,\n };\n\n case \"stream\":\n return applyStreamEvent(state, action.event);\n\n case \"stream_failed\": {\n const messages = dropEmptyStreaming(state.messages, state.streamingId);\n return {\n ...state,\n messages,\n streamingId: null,\n status: \"idle\",\n // A failed turn leaves no confirmable action (see the `error` case).\n pendingProposals: [],\n error: action.error,\n };\n }\n\n case \"proposal_resolved\": {\n // Identify the card by callId — the model's tool-call id, which is always\n // present (proposalId can be null) and unique per proposal. confirm/cancel\n // always carry the card's own callId, so this removes exactly that card and\n // never leaves one stuck.\n const pendingProposals = state.pendingProposals.filter(\n (p) => p.callId !== action.callId,\n );\n const messages = action.status\n ? capMessages([...state.messages, action.status])\n : state.messages;\n return {\n ...state,\n messages,\n pendingProposals,\n error: action.error,\n status:\n pendingProposals.length > 0\n ? \"awaiting_confirm\"\n : state.status === \"awaiting_confirm\"\n ? \"idle\"\n : state.status,\n };\n }\n\n case \"proposal_retry_failed\": {\n // A retryable confirm failure (an unconnected integration): KEEP the card\n // and attach the message to it so the user can connect the provider and\n // confirm again. No top-level error banner; the message lives on the card,\n // next to the requirements + connect affordance that fix it.\n const pendingProposals = state.pendingProposals.map((p) =>\n p.callId === action.callId ? { ...p, retryError: action.message } : p,\n );\n // Derive status from whether a proposal is still pending — never force\n // `awaiting_confirm` unconditionally. In the live flow the card is always\n // still here (this handler keeps it), but computing the status the same\n // way `proposal_resolved` does keeps the state machine correct-by-\n // construction: a dropped proposal can't resurrect a stale awaiting state.\n return {\n ...state,\n pendingProposals,\n status:\n pendingProposals.length > 0\n ? \"awaiting_confirm\"\n : state.status === \"awaiting_confirm\"\n ? \"idle\"\n : state.status,\n };\n }\n\n case \"stopped\":\n // Keep whatever text already streamed; just stop accumulating. Drop the\n // assistant bubble if it never received a delta (stopped before the first\n // token), and drop a proposal buffered before the stop — the aborted turn\n // leaves no confirmable action.\n return {\n ...state,\n messages: dropEmptyStreaming(state.messages, state.streamingId),\n status: \"idle\",\n streamingId: null,\n pendingProposals: [],\n };\n\n case \"hydrate\":\n // Replace the whole conversation with a freshly loaded thread, dropping\n // all transient state, and stamp it with its owner. Used when the\n // signed-in user changes under a mounted panel so one account's transcript\n // can never carry into another.\n return {\n ...initialAssistantState(),\n ownerId: action.ownerId,\n threadId: action.threadId,\n messages: action.messages,\n };\n\n case \"restore_history\":\n // Apply the server-restored transcript ONLY if the conversation hasn't\n // moved on since the fetch began: same owner + thread, still idle, nothing\n // shown yet, AND no live proposal already pending. Otherwise the user\n // already started interacting (or switched account / started a new chat)\n // and a late restore must not clobber the live state or resurrect a prior\n // thread. The `pendingProposals` check is belt-and-suspenders: an idle,\n // empty conversation has no pending proposals (the `done`/resolve handlers\n // keep `idle ⟺ no proposals`), so this can't fire in normal flow — but it\n // makes the restore self-protective against a future invariant change\n // rather than relying on a cross-handler guarantee to avoid dropping a\n // live, unconfirmed proposal (the exact loss this PR exists to prevent).\n if (\n state.ownerId !== action.ownerId ||\n state.threadId !== action.threadId ||\n state.status !== \"idle\" ||\n state.messages.length > 0 ||\n state.pendingProposals.length > 0\n ) {\n return state;\n }\n // Restore the transcript AND any unconfirmed proposals — a card is\n // otherwise client-ephemeral, so without this the user loses the ability to\n // confirm a pending action on reload. A restored proposal puts the\n // conversation back in `awaiting_confirm` so the card renders and the\n // composer stays gated until it's resolved.\n return {\n ...state,\n messages: capMessages(action.messages),\n pendingProposals: action.proposals,\n status: action.proposals.length > 0 ? \"awaiting_confirm\" : state.status,\n };\n\n case \"thread_gone\":\n // The persisted thread no longer exists server-side (history restore got a\n // 404). Drop the dead id — but only if the conversation hasn't moved on\n // (same owner + thread, still idle, nothing shown), mirroring the\n // restore_history guard — so the next send starts a fresh thread instead of\n // 404-ing forever against a thread that's gone. A started turn / new chat /\n // switched account is left untouched.\n if (\n state.ownerId !== action.ownerId ||\n state.threadId !== action.threadId ||\n state.status !== \"idle\" ||\n state.messages.length > 0\n ) {\n return state;\n }\n return { ...state, threadId: null };\n\n case \"history_failed\":\n // A switched-to thread's transcript couldn't be loaded. Drop the active\n // thread and surface the error — the thread carries server-side context\n // (prior turns, possibly secrets) that isn't on screen, so the next send\n // must NOT run against it; it starts a fresh thread instead. Guarded like\n // restore_history so a late failure can't reset a conversation that has\n // since moved on (a started turn / new chat / switched account).\n if (\n state.ownerId !== action.ownerId ||\n state.threadId !== action.threadId ||\n state.status !== \"idle\" ||\n state.messages.length > 0\n ) {\n return state;\n }\n return {\n ...initialAssistantState(),\n ownerId: state.ownerId,\n error: action.error,\n };\n\n case \"switch_thread\":\n // Open an existing thread the user picked from history. Reset to a clean\n // idle state for the SAME owner and pin the chosen thread id, so the\n // follow-up history fetch (restore_history) — which only applies on a\n // matching owner+thread that is still idle/empty — lands its transcript\n // here. A no-op if it's already the active thread.\n if (state.threadId === action.threadId) return state;\n return {\n ...initialAssistantState(),\n ownerId: state.ownerId,\n threadId: action.threadId,\n };\n\n case \"reset\":\n // \"New chat\" for the same user — start a fresh thread while the prior ones\n // stay reachable from history. Keep the owner so the cleared state stays\n // visible (a null owner would be masked by selectVisibleState).\n return { ...initialAssistantState(), ownerId: state.ownerId };\n }\n}\n","/**\n * The assistant's selectable models for the composer's picker. The list is\n * deployment config (not per-user), so it is fetched once per transport and\n * shared across panel mounts via a cache keyed by the `AssistantClient`.\n *\n * The cache has no TTL: it lives for the page session and is revalidated by a\n * page refresh — acceptable for deployment-config data that changes only on a\n * redeploy. An empty/failed fetch is NOT cached, so it retries on the next mount.\n *\n * Keyed by client so a host that swaps the transport (a tenant/origin/account\n * change) never serves the previous client's catalog or skips the fetch for the\n * new one. A WeakMap lets a discarded client's bucket be collected with it.\n *\n * Client-only by design (a `createRoot` SPA, no React SSR), so the cache lives\n * in one browser tab and is never shared across server requests. Under Strict\n * Mode's double-mount the two effect runs share the one in-flight fetch; the\n * first run's `active = false` makes its callback a no-op, the second commits —\n * dedup holds, no torn state.\n */\n\nimport { useEffect, useReducer } from \"react\";\nimport type {\n AssistantClient,\n AssistantModels,\n AssistantModelsResult,\n} from \"./client\";\nimport { useAssistantClient } from \"./client-context\";\n\nconst EMPTY: AssistantModels = { default: null, models: [] };\n\ninterface ModelCache {\n cache: AssistantModels | null;\n inflight: Promise<AssistantModelsResult> | null;\n}\n\nconst byClient = new WeakMap<AssistantClient, ModelCache>();\n\nfunction cacheFor(client: AssistantClient): ModelCache {\n let entry = byClient.get(client);\n if (!entry) {\n entry = { cache: null, inflight: null };\n byClient.set(client, entry);\n }\n return entry;\n}\n\nexport function useAssistantModels(): AssistantModels {\n const client = useAssistantClient();\n // The per-client cache is the source of truth; `bump` just forces a re-read\n // once an async fetch settles. Deriving the return value during render (below)\n // — rather than mirroring it into state via an effect — means a client swap\n // shows the new client's catalog (or empty) on the SAME commit, with no stale\n // frame from the previous client.\n const [, bump] = useReducer((n: number) => n + 1, 0);\n\n useEffect(() => {\n const entry = cacheFor(client);\n if (entry.cache) return;\n let active = true;\n entry.inflight ??= client.fetchModels();\n void entry.inflight\n .then((result) => {\n // Cache a successful fetch (a well-formed, non-empty list) and release\n // the settled promise — the cache now guards re-fetch. A failed fetch OR\n // an empty list (reported as !ok — catalog unavailable) leaves the cache\n // unset so the next mount retries instead of serving an empty picker.\n if (result.ok) entry.cache = result.data;\n entry.inflight = null;\n if (active) bump();\n })\n .catch(() => {\n // fetchModels swallows its own errors, so this only fires if a future\n // change lets it reject — release the slot so the next mount retries.\n entry.inflight = null;\n });\n return () => {\n active = false;\n };\n }, [client]);\n\n // Always the CURRENT client's catalog — synchronously correct across a swap.\n return cacheFor(client).cache ?? EMPTY;\n}\n","/**\n * The user's recent assistant chat threads for the history switcher. Unlike the\n * model list (deployment config, fetched once), the thread list changes as the\n * user chats, so it is fetched ON DEMAND — call `refresh()` to (re)load it; the\n * panel does so when the history view opens and after a turn settles a new\n * thread into being. It does NOT fetch on mount, so `threads` stays empty and\n * `loaded` false until the first `refresh()`.\n *\n * Self-protective across account AND transport swaps. The list is tagged with the\n * (user, client) it belongs to and is masked to empty on the SAME commit if that\n * no longer matches the current props — so a swap never shows the prior scope's\n * threads for even one frame. In flight, a late result is dropped if either the\n * user or the client changed, and the request is aborted on the swap.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { AssistantClient, AssistantThreadSummary } from \"./client\";\nimport { useAssistantClient } from \"./client-context\";\n\nexport interface AssistantThreads {\n threads: AssistantThreadSummary[];\n loading: boolean;\n /** True once a fetch has settled at least once (drives empty-vs-loading copy). */\n loaded: boolean;\n /** Load (or reload) the thread list. Must be called to populate `threads` —\n * the hook never fetches on mount (the panel calls this when history opens). */\n refresh: () => void;\n /** Delete a thread. Optimistically drops it from the list (within the current\n * owner scope); on failure the list is reloaded to restore the true state. A\n * no-op resolving `{ ok: false }` when the client has no `deleteThread`. */\n remove: (threadId: string) => Promise<{ ok: boolean }>;\n /** Whether the configured client supports deletion — drives whether a host\n * shows the delete affordance. */\n canRemove: boolean;\n}\n\ninterface ThreadsState {\n threads: AssistantThreadSummary[];\n loading: boolean;\n loaded: boolean;\n /** The (user, client) the data belongs to; the hook masks to empty unless both\n * match the current props, so a swap can't show the prior scope's list. */\n ownerUserId: string | null;\n ownerClient: AssistantClient | null;\n}\n\nexport function useAssistantThreads(userId: string | null): AssistantThreads {\n const client = useAssistantClient();\n const userRef = useRef(userId);\n userRef.current = userId;\n const clientRef = useRef(client);\n clientRef.current = client;\n const abortRef = useRef<AbortController | null>(null);\n // Ids being (or already) deleted. A refresh whose fetch began before the\n // server delete completed can return a row we optimistically removed; filter\n // these out of every refresh commit so a deleted thread never reappears.\n const pendingDeletesRef = useRef<Set<string>>(new Set());\n\n const [state, setState] = useState<ThreadsState>(() => ({\n threads: [],\n loading: false,\n loaded: false,\n ownerUserId: userId,\n ownerClient: client,\n }));\n\n const refresh = useCallback(() => {\n // Capture the user AND client this fetch is FOR; the commit and the owner tag\n // both use them, so a result can never land under a different scope.\n const requestedUserId = userRef.current;\n const requestedClient = clientRef.current;\n if (!requestedUserId) {\n setState({\n threads: [],\n loading: false,\n loaded: true,\n ownerUserId: requestedUserId,\n ownerClient: requestedClient,\n });\n return;\n }\n // Supersede any in-flight fetch so a rapid re-open can't land a stale list.\n abortRef.current?.abort();\n const ac = new AbortController();\n abortRef.current = ac;\n setState((s) => ({\n ...s,\n loading: true,\n ownerUserId: requestedUserId,\n ownerClient: requestedClient,\n }));\n const isCurrent = () =>\n !ac.signal.aborted &&\n userRef.current === requestedUserId &&\n clientRef.current === requestedClient;\n void requestedClient\n .fetchThreads(ac.signal)\n .then((result) => {\n if (!isCurrent()) return;\n setState((s) => ({\n // null = transient failure: keep the prior list, just drop the spinner.\n // Drop any in-flight/finished deletions so a stale fetch can't resurrect\n // a row we already removed.\n threads: (result ?? s.threads).filter(\n (t) => !pendingDeletesRef.current.has(t.id),\n ),\n loading: false,\n loaded: true,\n ownerUserId: requestedUserId,\n ownerClient: requestedClient,\n }));\n })\n .catch(() => {\n // fetchThreads returns null rather than rejecting; this guards a future\n // change (or a throw in a state setter) from wedging the spinner.\n if (isCurrent()) {\n setState((s) => ({ ...s, loading: false, loaded: true }));\n }\n });\n }, []);\n\n const remove = useCallback(\n async (threadId: string) => {\n const requestedClient = clientRef.current;\n const requestedUserId = userRef.current;\n // A client without delete support can't remove anything — no-op rather\n // than optimistically drop a row that will never be deleted server-side.\n if (!requestedClient.deleteThread) return { ok: false };\n // Mark it deleting so a concurrent refresh's commit filters it out, then\n // optimistically drop the row — but only within the scope we're deleting\n // under (never mutate a swapped-in scope's list).\n pendingDeletesRef.current.add(threadId);\n setState((s) =>\n s.ownerClient === requestedClient && s.ownerUserId === requestedUserId\n ? { ...s, threads: s.threads.filter((t) => t.id !== threadId) }\n : s,\n );\n // Normalize a rejecting client to `{ ok: false }` so the rollback below\n // always runs (the bundled client resolves, but the interface allows any).\n let res: { ok: boolean };\n try {\n res = await requestedClient.deleteThread(threadId);\n } catch {\n res = { ok: false };\n }\n // On failure, un-mark it and reload to restore the row we optimistically\n // removed (only if we're still in the same scope). On success it stays\n // marked — the thread is gone for good and must never resurface.\n if (!res.ok) {\n pendingDeletesRef.current.delete(threadId);\n if (\n userRef.current === requestedUserId &&\n clientRef.current === requestedClient\n ) {\n refresh();\n }\n }\n return res;\n },\n [refresh],\n );\n\n // Abort an in-flight fetch on a scope swap (its result is already masked and\n // the commit guard rejects it; this just frees the network promptly) and on\n // unmount, so a late `.then` can't act after the panel closed.\n useEffect(() => {\n return () => abortRef.current?.abort();\n }, [userId, client]);\n\n // Drop the pending-delete ids only on true unmount — NOT on a scope swap. A\n // remove() awaiting its DELETE can outlive a swap-and-return to the same\n // scope; clearing on the swap would un-filter that id and let a stale refresh\n // resurrect the row. Thread ids are server-minted and globally unique, so the\n // set never false-filters another scope's list, and within one mount it only\n // grows by the user's own deletions (released here on unmount).\n useEffect(() => {\n return () => pendingDeletesRef.current.clear();\n }, []);\n\n // Mask synchronously: a list owned by a different (user, client) than the\n // current props is hidden on the same commit — no one-frame cross-scope leak.\n const stale = state.ownerUserId !== userId || state.ownerClient !== client;\n return {\n threads: stale ? [] : state.threads,\n loading: stale ? false : state.loading,\n loaded: stale ? false : state.loaded,\n refresh,\n remove,\n canRemove: typeof client.deleteThread === \"function\",\n };\n}\n","/**\n * Persistent assistant entry point: a floating launcher that opens the chat\n * panel as a right-side drawer (full-screen on small viewports), with focus\n * trapping and a resizable width. Owns the chat state (via useAssistantChat) so\n * the conversation survives the drawer closing — host-shell concerns (the user,\n * navigation, balance, money formatting, the graph renderer, and the workflow-\n * mutation signal) are injected.\n *\n * Mount inside an <AssistantClientProvider> (transport) and an\n * <AssistantLauncherProvider> (open/seed state).\n */\n\nimport { MessageSquare } from \"lucide-react\";\nimport {\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n useEffect,\n useRef,\n} from \"react\";\nimport type { ToolDetailRenderers } from \"../web-react\";\nimport { AssistantPanel } from \"./AssistantPanel\";\nimport { useAssistantLauncher } from \"./launcher\";\nimport { ResizeHandle } from \"./ResizeHandle\";\nimport type { AssistantTranscriptView } from \"./types\";\nimport { useAssistantChat } from \"./useAssistantChat\";\nimport { useIsDesktop, usePanelWidth } from \"./usePanelPrefs\";\n\nexport interface AssistantDockProps {\n /** The signed-in user this conversation belongs to (null when signed out). */\n userId: string | null;\n /** Host navigation for error CTAs and connect targets. */\n navigate?: (path: string) => void;\n balanceUsd?: number | null;\n formatMoney?: (usd: number | null) => string;\n /** Render workflow YAML as a node graph in a proposal card. */\n renderGraph?: (yaml: string) => ReactNode;\n /** Called after a workflow-mutating tool is confirmed (host re-fetches its list). */\n onWorkflowMutation?: () => void;\n /** Markdown renderer for assistant message content (plain text when absent). */\n renderMarkdown?: (content: string) => ReactNode;\n /** Per-tool custom detail renderers for expanded tool cards in the transcript. */\n toolRenderers?: ToolDetailRenderers;\n /** Swap the conversation rendering for a host-supplied renderer (see\n * {@link AssistantPanelProps.renderTranscript}); the dock chrome, composer,\n * transport, and proposal flow stay owned by the panel. */\n renderTranscript?: (view: AssistantTranscriptView) => ReactNode;\n}\n\n/** Visible, focusable descendants of a container, in tab order. Visibility is\n * checked via getClientRects rather than offsetParent, which is null for\n * position:fixed elements and would wrongly exclude them. */\nfunction focusableWithin(container: HTMLElement): HTMLElement[] {\n const selector =\n 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n return Array.from(container.querySelectorAll<HTMLElement>(selector)).filter(\n (el) => el.getClientRects().length > 0,\n );\n}\n\nexport function AssistantDock({\n userId,\n navigate,\n balanceUsd = null,\n formatMoney,\n renderGraph,\n onWorkflowMutation,\n renderMarkdown,\n toolRenderers,\n renderTranscript,\n}: AssistantDockProps) {\n const { open, openAssistant, closeAssistant } = useAssistantLauncher();\n const chat = useAssistantChat(userId, { onWorkflowMutation });\n\n const isDesktop = useIsDesktop();\n const { width, maxWidth, setWidth, previewWidth, nudgeWidth } =\n usePanelWidth();\n\n const launcherRef = useRef<HTMLButtonElement | null>(null);\n const dialogRef = useRef<HTMLDivElement | null>(null);\n const returnFocusRef = useRef<HTMLElement | null>(null);\n const wasOpenRef = useRef(false);\n\n // Close on Escape.\n useEffect(() => {\n if (!open) return;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") closeAssistant();\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [open, closeAssistant]);\n\n // Move focus into the dialog on open; restore it to the opener on close.\n useEffect(() => {\n if (open) {\n if (!wasOpenRef.current && !returnFocusRef.current) {\n returnFocusRef.current = document.activeElement as HTMLElement | null;\n }\n wasOpenRef.current = true;\n const el = dialogRef.current;\n if (el) (focusableWithin(el)[0] ?? el).focus();\n } else if (wasOpenRef.current) {\n wasOpenRef.current = false;\n const target = returnFocusRef.current?.isConnected\n ? returnFocusRef.current\n : launcherRef.current;\n target?.focus();\n returnFocusRef.current = null;\n }\n }, [open]);\n\n const openDialog = () => {\n returnFocusRef.current = document.activeElement as HTMLElement | null;\n openAssistant();\n };\n\n if (!open) {\n return (\n <button\n ref={launcherRef}\n type=\"button\"\n onClick={openDialog}\n aria-label=\"Open assistant\"\n className=\"fixed right-4 bottom-4 z-40 flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-colors hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-ring\"\n >\n <MessageSquare className=\"h-6 w-6\" />\n </button>\n );\n }\n\n // Keep Tab focus within the dialog while it's open.\n const trapTab = (e: ReactKeyboardEvent<HTMLDivElement>) => {\n if (e.key !== \"Tab\") return;\n const dialog = dialogRef.current;\n if (!dialog) return;\n const focusables = focusableWithin(dialog);\n if (focusables.length === 0) {\n e.preventDefault();\n dialog.focus();\n return;\n }\n // Non-empty here (length === 0 returned above); the index access is safe.\n const first = focusables[0]!;\n const last = focusables[focusables.length - 1]!;\n const active = document.activeElement;\n const inside = active instanceof Node && dialog.contains(active);\n if (e.shiftKey) {\n if (!inside || active === first || active === dialog) {\n e.preventDefault();\n last.focus();\n }\n } else if (!inside || active === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n return (\n <>\n <div\n aria-hidden=\"true\"\n className=\"fixed inset-0 z-40 bg-black/40\"\n onClick={() => closeAssistant()}\n />\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-label=\"Assistant\"\n aria-modal=\"true\"\n tabIndex={-1}\n onKeyDown={trapTab}\n style={isDesktop ? { width: `${width}px` } : undefined}\n className=\"fixed inset-y-0 right-0 z-50 flex w-full flex-col border-border border-l shadow-xl focus:outline-none\"\n >\n <AssistantPanel\n key={userId ?? \"anon\"}\n chat={chat}\n userId={userId}\n onClose={() => closeAssistant()}\n navigate={navigate}\n balanceUsd={balanceUsd}\n formatMoney={formatMoney}\n renderGraph={renderGraph}\n renderMarkdown={renderMarkdown}\n toolRenderers={toolRenderers}\n renderTranscript={renderTranscript}\n />\n {isDesktop && (\n <ResizeHandle\n width={width}\n maxWidth={maxWidth}\n onPreview={previewWidth}\n onCommit={setWidth}\n onNudge={nudgeWidth}\n />\n )}\n </div>\n </>\n );\n}\n","/**\n * The assistant chat panel, built on web-react's chat components. The reducer\n * state is rendered by `AssistantTranscript` (web-react `ChatMessages`: transcript,\n * tool chips, reasoning preview, cost, proposal cards) and the composer is a\n * `ChatComposer` carrying the `ModelPicker` in its controls slot. The header's\n * history toggle swaps the conversation area for a full-panel, searchable history\n * view. App-shell concerns — the signed-in user, navigation, the credit balance,\n * money formatting, the markdown + tool-detail renderers, and the workflow-graph\n * renderer — are injected so the panel is portable across hosts. Chat state is\n * owned by the dock and passed in, so the conversation survives the drawer closing.\n */\n\nimport { History, MessageSquarePlus, Minus, Plus, X } from \"lucide-react\";\nimport { type ReactNode, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { CatalogModel } from \"../runtime/model-catalog\";\nimport { ChatComposer, ModelPicker, type ToolDetailRenderers } from \"../web-react\";\nimport { AssistantHistory } from \"./AssistantHistory\";\nimport type { AssistantModels } from \"./client\";\nimport { isLowBalance, presentError } from \"./presentation\";\nimport { ProposalCard } from \"./ProposalCard\";\nimport { AssistantTranscript, assistantIsThinking } from \"./transcript\";\nimport type { AssistantTranscriptView } from \"./types\";\nimport type { AssistantChat } from \"./useAssistantChat\";\nimport { useAssistantModels } from \"./useAssistantModels\";\nimport { useAssistantThreads } from \"./useAssistantThreads\";\nimport { useFontScale } from \"./usePanelPrefs\";\n\nexport interface AssistantPanelProps {\n chat: AssistantChat;\n userId: string | null;\n onClose: () => void;\n /** Host navigation for error CTAs and connect targets. */\n navigate?: (path: string) => void;\n /** The user's credit balance, for the header tile + low-balance nudge. */\n balanceUsd?: number | null;\n /** Format a USD amount; defaults to Intl currency formatting. */\n formatMoney?: (usd: number | null) => string;\n /** Render workflow YAML as a node graph in a proposal card (the `./workflows`\n * WorkflowGraph). When absent, proposals show YAML as text. */\n renderGraph?: (yaml: string) => ReactNode;\n /** Markdown renderer for assistant message content. When absent, content\n * renders as plain pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode;\n /** Per-tool custom detail renderers for expanded tool cards in the transcript. */\n toolRenderers?: ToolDetailRenderers;\n /** Swap ONLY the conversation rendering for a host-supplied renderer (e.g. a\n * different chat-message component), while the panel keeps owning the header,\n * composer, model picker, history, transport, and proposal orchestration.\n * Receives the transcript slice plus a bound proposal card to place. When\n * absent, the built-in transcript (web-react `ChatMessages`) renders the\n * conversation. */\n renderTranscript?: (view: AssistantTranscriptView) => ReactNode;\n}\n\nconst EMPTY_STATE =\n \"Ask me to create a workflow, check your usage, or manage your API keys.\";\n\nfunction defaultFormatMoney(usd: number | null): string {\n if (usd == null) return \"—\";\n return new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: \"USD\",\n }).format(usd);\n}\n\n/**\n * Map the assistant catalog onto the shared ModelPicker's wire shape. The slug\n * is already a canonical, provider-prefixed id, so it doubles as the picker's\n * value.\n *\n * Both the server `default` and the currently-`selected` slug are guaranteed a\n * row even when the catalog omits them (each appended only when not already\n * listed, so no duplicate is produced). Keeping the active selection visible is\n * what lets the picker show exactly what the next turn will send without the\n * panel ever rewriting the user's choice to avoid an orphaned value — a stale\n * slug (e.g. a model retired between refetches, or one missing from a filtered\n * catalog) stays selectable until the user changes it or the server rejects it,\n * which is when `useAssistantChat` clears it.\n *\n * An absent context window is omitted rather than passed as `undefined`; pricing\n * is omitted entirely because the catalog carries only a prompt price, which the\n * picker's \"prompt / completion\" line would misreport as a free completion.\n */\n/** The provider segment of a canonical, provider-prefixed slug\n * (\"anthropic/claude-…\" → \"anthropic\"); \"other\" when the slug isn't prefixed.\n * Drives the picker's provider grouping + logo. */\nfunction providerOf(slug: string): string {\n const i = slug.indexOf(\"/\");\n return i > 0 ? slug.slice(0, i) : \"other\";\n}\n\nexport function toPickerModels(\n models: AssistantModels,\n selected: string | null,\n): CatalogModel[] {\n const row = (slug: string, label?: string, contextTokens?: number): CatalogModel => ({\n id: slug,\n name: label ?? slug,\n provider: providerOf(slug),\n supportsTools: true,\n supportsReasoning: false,\n featured: false,\n ...(contextTokens != null ? { contextLength: contextTokens } : {}),\n });\n const mapped: CatalogModel[] = models.models.map((m) =>\n row(m.slug, m.label, m.contextTokens),\n );\n for (const slug of [models.default, selected]) {\n if (slug && !mapped.some((m) => m.id === slug)) mapped.push(row(slug));\n }\n return mapped;\n}\n\n/** Small animated \"working\" cue shown above the composer while a turn streams —\n * a redundant, renderer-independent signal alongside the composer's Stop button\n * and the transcript's own thinking row. */\nfunction WorkingIndicator() {\n return (\n <span className=\"inline-flex items-center gap-1.5 text-muted-foreground text-xs\">\n <span className=\"h-1.5 w-1.5 animate-pulse rounded-full bg-primary\" />\n Working…\n </span>\n );\n}\n\n/**\n * The chat-state value to store for a model id chosen in the picker. Picking the\n * server default clears the preference to `null` — preserving the native-select\n * contract where \"default\" means \"omit the model and follow whatever the server\n * default is\", rather than pinning the default's slug (which would freeze the\n * user to it even after the server default changes). Any other id is stored as-is.\n */\nexport function nextModelSelection(\n id: string,\n defaultSlug: string | null,\n): string | null {\n if (defaultSlug != null && id === defaultSlug) return null;\n return id || null;\n}\n\nexport function AssistantPanel({\n chat,\n userId,\n onClose,\n navigate,\n balanceUsd = null,\n formatMoney = defaultFormatMoney,\n renderGraph,\n renderMarkdown,\n toolRenderers,\n renderTranscript,\n}: AssistantPanelProps) {\n const models = useAssistantModels();\n const threads = useAssistantThreads(userId);\n const font = useFontScale();\n // Which surface the conversation area shows: the live chat, or the full-panel\n // history list. The header's history button toggles between them.\n const [view, setView] = useState<\"chat\" | \"history\">(\"chat\");\n const historyButtonRef = useRef<HTMLButtonElement | null>(null);\n // The conversation/history scroll container, used to scope the history-view\n // Escape handler and to move focus into the history view when it opens.\n const logRef = useRef<HTMLDivElement | null>(null);\n\n const pickerModels = useMemo<CatalogModel[]>(\n () => toPickerModels(models, chat.selectedModel),\n [models, chat.selectedModel],\n );\n // `toPickerModels` guarantees both the selected slug and the default a row, so\n // this value always resolves to a real option — the displayed model is exactly\n // the slug the next turn will send, with no panel-side rewrite of the user's\n // choice. Falls through to the default, then empty, only when nothing is set.\n const pickerValue = chat.selectedModel ?? models.default ?? \"\";\n\n const { state } = chat;\n // Always-current chat handle, so an async delete can re-check the LIVE thread\n // + status after awaiting (the closure's `chat`/`state` are render-time stale).\n const chatRef = useRef(chat);\n chatRef.current = chat;\n\n // When the history view opens, move focus into its search box, so keyboard\n // users land ready to type and the scoped Escape handler below receives the\n // key event. Focusing the input is more reliable than focusing the\n // tabIndex=-1 container, which some browsers handle inconsistently; the\n // container is a fallback only if the input isn't present.\n useEffect(() => {\n if (view !== \"history\") return;\n const search = logRef.current?.querySelector<HTMLInputElement>(\n 'input[type=\"search\"]',\n );\n (search ?? logRef.current)?.focus();\n }, [view]);\n\n // In the history view, Escape returns to the conversation (and refocuses the\n // toggle) rather than closing the whole assistant. Scoped to Escapes that\n // originate inside the history view or from the toggle, so it never swallows\n // an Escape meant for an open composer popover/menu; handled in the capture\n // phase with stopImmediatePropagation so it preempts the dock's own\n // Escape-to-close. In the chat view no handler is installed, so Escape falls\n // through to the dock's close as usual.\n useEffect(() => {\n if (view !== \"history\") return;\n const onKeyDownCapture = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n const target = e.target as Node;\n if (\n !logRef.current?.contains(target) &&\n !historyButtonRef.current?.contains(target)\n ) {\n return;\n }\n e.stopImmediatePropagation();\n setView(\"chat\");\n historyButtonRef.current?.focus();\n };\n document.addEventListener(\"keydown\", onKeyDownCapture, true);\n return () => document.removeEventListener(\"keydown\", onKeyDownCapture, true);\n }, [view]);\n\n // Prefer the just-settled turn's balance (from the usage event, immediate)\n // over the injected fetched balance, which may lag a turn behind.\n const effectiveBalance = state.usage?.balanceUsd ?? balanceUsd;\n const errorView = state.error\n ? presentError(state.error.code, state.error.message)\n : null;\n const low = isLowBalance(effectiveBalance) && !errorView;\n const streaming = state.status === \"streaming\";\n // The active conversation's title — the first user message, truncated, mirroring\n // the server's own thread titling (a thread title IS its truncated first user\n // message). Derived client-side so it shows immediately on the first send and\n // on a restored thread, with no extra fetch. Null for a fresh, empty chat.\n const firstUserText = state.messages\n .find((m) => m.role === \"user\")\n ?.text.trim();\n // Truncate by code point (Array.from), not UTF-16 code unit, so a 60-char cut\n // can't split a surrogate pair (emoji / astral script) into a replacement char.\n const titleChars = firstUserText ? Array.from(firstUserText) : [];\n const conversationTitle = firstUserText\n ? titleChars.length > 60\n ? `${titleChars.slice(0, 60).join(\"\")}…`\n : firstUserText\n : null;\n\n const renderProposal = (proposal: (typeof state.pendingProposals)[number]) => (\n <ProposalCard\n proposal={proposal}\n confirming={\n proposal.proposalId ? chat.confirmingIds.has(proposal.proposalId) : false\n }\n onConfirm={() => chat.confirm(proposal)}\n onCancel={() => chat.cancel(proposal)}\n navigate={navigate}\n renderGraph={renderGraph}\n />\n );\n\n const isThinking = assistantIsThinking(state);\n\n // The transcript slice — fed to either a host-supplied renderer or the\n // built-in `AssistantTranscript` (web-react `ChatMessages`).\n const transcriptView: AssistantTranscriptView = {\n messages: state.messages,\n reasoning: state.reasoning,\n streamingId: state.streamingId,\n model: state.model,\n isStreaming: streaming,\n isThinking,\n pendingProposals: state.pendingProposals,\n usage: state.usage,\n renderProposal,\n };\n\n // Entering history loads (or reloads) the thread list — the hook never fetches\n // on mount, so this is what populates it.\n const showHistory = () => {\n threads.refresh();\n setView(\"history\");\n };\n const toggleHistory = () => {\n if (view === \"history\") setView(\"chat\");\n else showHistory();\n };\n\n // Delete a past conversation. Deleting the *active* thread is refused while it\n // is mid-turn (the stream is still writing to it). The list row drops\n // optimistically (in the hook), but the LIVE conversation is only reset once\n // the server confirms the delete — so a failed delete never strands the user\n // on a fresh thread while the server still has the conversation.\n const deleteThread = async (threadId: string) => {\n // Refuse deleting the active thread while it is mid-turn. Read LIVE status\n // through the ref (not the render-time `state`) so the guard is authoritative\n // regardless of when this closure was created or how long the confirm sat\n // open — never delete a thread the stream is still writing to.\n const pre = chatRef.current.state;\n if (pre.threadId === threadId && pre.status !== \"idle\") return;\n if (!window.confirm(\"Delete this conversation? This can't be undone.\")) {\n return;\n }\n const res = await threads.remove(threadId);\n // Reset the live conversation only if the just-deleted thread is STILL the\n // active, idle one. Re-checked through the ref because the user may have\n // switched threads or started a turn while the delete was in flight —\n // resetting then would wipe a different or now-busy conversation.\n const live = chatRef.current.state;\n if (res.ok && live.threadId === threadId && live.status === \"idle\") {\n chatRef.current.reset();\n }\n };\n\n return (\n <div className=\"relative flex h-full flex-col bg-background\">\n {/* Header: identity + active-conversation title, and the conversation-level\n actions (text size, history, new, close). */}\n <div className=\"border-border border-b\">\n <div className=\"flex items-center justify-between gap-2 px-4 pt-3 pb-2.5\">\n <div className=\"flex min-w-0 flex-col\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"font-medium text-foreground text-sm\">\n Assistant\n </span>\n <span\n aria-label=\"Your credit balance\"\n className=\"text-muted-foreground text-xs\"\n >\n {formatMoney(effectiveBalance)}\n </span>\n </div>\n {conversationTitle && (\n <span\n className=\"truncate text-muted-foreground text-xs\"\n title={conversationTitle}\n >\n {conversationTitle}\n </span>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-1\">\n {/* Text size — zooms the transcript. A panel-level control, so it\n lives in the header action row rather than over the composer. */}\n <div\n className=\"flex items-center overflow-hidden rounded-md border border-border\"\n role=\"group\"\n aria-label=\"Text size\"\n >\n <button\n type=\"button\"\n onClick={font.decrease}\n disabled={!font.canDecrease}\n aria-label=\"Decrease text size\"\n className=\"px-1.5 py-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent\"\n >\n <Minus className=\"h-3.5 w-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={font.increase}\n disabled={!font.canIncrease}\n aria-label=\"Increase text size\"\n className=\"border-border border-l px-1.5 py-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n <button\n ref={historyButtonRef}\n type=\"button\"\n onClick={toggleHistory}\n aria-label=\"Chat history\"\n aria-pressed={view === \"history\"}\n className={`rounded-md p-1.5 transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring ${\n view === \"history\"\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground\"\n }`}\n >\n <History className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => {\n chat.reset();\n setView(\"chat\");\n }}\n aria-label=\"New chat\"\n title=\"New chat\"\n className=\"rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MessageSquarePlus className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close assistant\"\n className=\"rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </div>\n\n {/* Conversation — the full-panel history view, the host-swappable\n renderer, or the built-in timeline. */}\n <div\n ref={logRef}\n tabIndex={-1}\n aria-label=\"Conversation\"\n // role=\"log\" + aria-live announce streaming transcript updates; applied\n // only in the chat view so the history view's search box and buttons are\n // not announced as live conversation activity.\n role={view === \"chat\" ? \"log\" : undefined}\n aria-live={view === \"chat\" ? \"polite\" : undefined}\n className=\"min-h-0 flex-1 overflow-y-auto focus:outline-none\"\n >\n {view === \"history\" ? (\n <AssistantHistory\n threads={threads.threads}\n loaded={threads.loaded}\n activeThreadId={state.threadId}\n activeBusy={state.status !== \"idle\"}\n canRemove={threads.canRemove}\n onSelect={(id) => {\n chat.switchThread(id);\n setView(\"chat\");\n }}\n onDelete={(id) => void deleteThread(id)}\n />\n ) : (\n // The text-size control zooms the transcript only — not the history\n // view's search box and buttons. `zoom` scales every descendant\n // uniformly regardless of which renderer draws the conversation; an\n // inline `font-size` would not (the transcript's text utilities set\n // absolute rem sizes), and `transform: scale` would break the scroll\n // container by keeping the original layout box.\n <div className=\"px-2 py-3\" style={{ zoom: font.scale }}>\n {renderTranscript ? (\n renderTranscript(transcriptView)\n ) : (\n <AssistantTranscript\n view={transcriptView}\n renderMarkdown={renderMarkdown}\n toolRenderers={toolRenderers}\n emptyState={\n <p className=\"px-4 py-8 text-center text-muted-foreground text-sm\">\n {EMPTY_STATE}\n </p>\n }\n />\n )}\n </div>\n )}\n </div>\n\n {/* Error / low-balance banners */}\n {errorView && (\n <div\n role=\"alert\"\n className=\"mx-4 mb-2 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm\"\n >\n <p className=\"text-foreground\">{errorView.message}</p>\n {errorView.cta && (\n <button\n type=\"button\"\n onClick={() => navigate?.(errorView.cta?.to ?? \"\")}\n className=\"mt-1 text-primary text-xs\"\n >\n {errorView.cta.label} →\n </button>\n )}\n </div>\n )}\n {low && (\n <div\n role=\"status\"\n className=\"mx-4 mb-2 rounded-lg border border-border bg-muted/40 px-3 py-2 text-sm\"\n >\n <p className=\"text-foreground\">Your credit balance is running low.</p>\n <button\n type=\"button\"\n onClick={() => navigate?.(\"/app/billing\")}\n className=\"mt-1 text-primary text-xs\"\n >\n Add credits →\n </button>\n </div>\n )}\n\n {/* Composer: the model picker sits directly above the input, so the model\n the next turn will use reads as part of the composer. */}\n <div className=\"border-border border-t p-2\">\n {/* Running indicator: while a turn streams, the composer's Send becomes a\n Stop button — on its own an easy-to-miss signal. This animated row makes\n \"the assistant is working\" unmistakable regardless of the transcript\n renderer in use. */}\n {streaming && (\n <div className=\"px-2 pb-1.5\" aria-label=\"Assistant is working\">\n <WorkingIndicator />\n </div>\n )}\n <ChatComposer\n onSend={(message) => {\n setView(\"chat\");\n chat.send(message);\n }}\n onCancel={chat.stop}\n isStreaming={streaming}\n disabled={chat.restoring || state.status === \"awaiting_confirm\"}\n placeholder=\"Message the assistant…\"\n controls={\n pickerModels.length > 0 ? (\n <ModelPicker\n value={pickerValue}\n onChange={(id) =>\n chat.setModel(nextModelSelection(id, models.default))\n }\n models={pickerModels}\n />\n ) : (\n <span className=\"px-1 text-muted-foreground text-xs\">\n Default model\n </span>\n )\n }\n />\n </div>\n </div>\n );\n}\n","/**\n * The assistant's conversation history as a full-panel view: a searchable,\n * recency-sorted list of past threads, each showing its title and a relative\n * \"last active\" time, with inline delete. Replaces the cramped header dropdown —\n * inside an already-narrow side panel, a full-height list is far easier to scan\n * and navigate. Selection, deletion, and refresh are owned by the host panel;\n * this component is presentational and holds only its own search query.\n */\n\nimport { Search, Trash2 } from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\nimport { timeAgo } from \"./time-ago\";\nimport type { AssistantThreadSummary } from \"./client\";\n\nexport interface AssistantHistoryProps {\n threads: AssistantThreadSummary[];\n /** True once a fetch has settled at least once (drives empty-vs-loading copy). */\n loaded: boolean;\n /** The thread the live conversation is on, highlighted in the list. */\n activeThreadId: string | null;\n /** Whether the active thread is mid-turn — its delete is disabled (the stream\n * is still writing to it). */\n activeBusy: boolean;\n /** Whether the transport supports deletion (drives the delete affordance). */\n canRemove: boolean;\n onSelect: (threadId: string) => void;\n onDelete: (threadId: string) => void;\n}\n\n/**\n * Parse an ISO timestamp to epoch ms for `timeAgo`. Returns null for an absent\n * or unparseable value, so a row simply omits its time rather than rendering\n * \"NaN\" — thread summaries can carry an empty `updatedAt` on older servers.\n */\nfunction parsedTime(iso: string): number | null {\n if (!iso) return null;\n const ms = Date.parse(iso);\n return Number.isFinite(ms) ? ms : null;\n}\n\nexport function AssistantHistory({\n threads,\n loaded,\n activeThreadId,\n activeBusy,\n canRemove,\n onSelect,\n onDelete,\n}: AssistantHistoryProps) {\n const [query, setQuery] = useState(\"\");\n\n // Most-recently-updated first; an unparseable/absent time sorts last. The\n // spread keeps the hook's array intact, and a stable sort preserves the\n // server's order among equal times.\n const sorted = useMemo(\n () =>\n [...threads].sort((a, b) => {\n const ta = parsedTime(a.updatedAt);\n const tb = parsedTime(b.updatedAt);\n // Most-recently-updated first; rows without a parseable time sort last,\n // and two such rows keep their existing order.\n if (ta === null && tb === null) return 0;\n if (ta === null) return 1;\n if (tb === null) return -1;\n return tb - ta;\n }),\n [threads],\n );\n\n const trimmed = query.trim().toLowerCase();\n const visible = useMemo(\n () =>\n trimmed\n ? // Match the title as displayed, so searching \"untitled\" finds the\n // rows that render as \"Untitled conversation\".\n sorted.filter((t) =>\n (t.title ?? \"Untitled conversation\")\n .toLowerCase()\n .includes(trimmed),\n )\n : sorted,\n [sorted, trimmed],\n );\n\n return (\n <div className=\"flex h-full flex-col\">\n <div className=\"border-border border-b p-2\">\n <div className=\"relative\">\n <Search className=\"-translate-y-1/2 pointer-events-none absolute top-1/2 left-2.5 h-3.5 w-3.5 text-muted-foreground\" />\n <input\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search conversations\"\n aria-label=\"Search conversations\"\n className=\"w-full rounded-md border border-border bg-surface-container-high py-1.5 pr-2 pl-8 text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n </div>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {visible.length === 0 ? (\n <p className=\"px-3 py-6 text-center text-muted-foreground text-xs\">\n {!loaded\n ? \"Loading…\"\n : trimmed\n ? \"No conversations match your search.\"\n : \"No past conversations yet.\"}\n </p>\n ) : (\n <ul className=\"py-1\">\n {visible.map((t) => {\n const active = t.id === activeThreadId;\n const ms = parsedTime(t.updatedAt);\n const busyActive = active && activeBusy;\n const title = t.title ?? \"Untitled conversation\";\n return (\n <li\n key={t.id}\n className={`group flex items-center transition-colors hover:bg-muted/60 ${\n active ? \"bg-primary/10\" : \"\"\n }`}\n >\n <button\n type=\"button\"\n onClick={() => onSelect(t.id)}\n className=\"flex min-w-0 flex-1 flex-col gap-0.5 px-3 py-2 text-left\"\n >\n <span\n className={`truncate text-sm ${\n active ? \"font-medium text-foreground\" : \"text-foreground\"\n }`}\n >\n {title}\n </span>\n {ms != null && (\n <span className=\"text-[11px] text-muted-foreground\">\n {timeAgo(ms)}\n </span>\n )}\n </button>\n {canRemove && (\n <button\n type=\"button\"\n onClick={() => onDelete(t.id)}\n disabled={busyActive}\n aria-label={`Delete conversation: ${title}`}\n title={\n busyActive\n ? \"Can't delete while this conversation is active\"\n : \"Delete conversation\"\n }\n // Always visible on touch devices (no hover to reveal it).\n className=\"shrink-0 p-2 text-muted-foreground opacity-0 transition [@media(hover:none)]:opacity-100 hover:text-destructive focus-visible:opacity-100 group-hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-30\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </div>\n );\n}\n","/** Relative time label from an epoch-ms timestamp (\"just now\", \"5m ago\", \"3h\n * ago\"). Self-contained so the assistant subpath carries no design-system dep. */\nexport function timeAgo(ts: number): string {\n const secs = Math.floor((Date.now() - ts) / 1000)\n if (secs < 5) return \"just now\"\n if (secs < 60) return `${secs}s ago`\n const mins = Math.floor(secs / 60)\n if (mins < 60) return `${mins}m ago`\n const hrs = Math.floor(mins / 60)\n return `${hrs}h ago`\n}\n","/**\n * Confirmation card for a mutating action the assistant proposed (create a\n * workflow, author a workflow + skills, run a workflow, manage a key, …). Shows\n * the action heading, its scalar fields, any new skills, a body preview (a\n * workflow renders as a node graph via the injected `renderGraph`, with a YAML\n * toggle; other bodies render verbatim), the integration requirements with a\n * connect affordance, and Confirm/Cancel.\n *\n * The body preview's graph is injected so this card — in the always-loaded\n * `./assistant` entry — doesn't pull the graph's `@xyflow/react` dependency; the\n * host wires `renderGraph` from `./workflows`. Navigation is injected too.\n */\n\nimport { type ReactNode, useState } from \"react\";\nimport { ProviderLogo } from \"../web-react/provider-logo\";\nimport { describeProposal } from \"./presentation\";\nimport { providerLabel } from \"./provider-label\";\nimport type { ConnectionRequirement, PendingProposal } from \"./types\";\n\nexport interface ProposalCardProps {\n proposal: PendingProposal;\n /** True while this proposal's confirmation is in flight (disables the buttons). */\n confirming: boolean;\n onConfirm: () => void;\n onCancel: () => void;\n /** Host navigation for connect targets / the integrations page. */\n navigate?: (path: string) => void;\n /** Render the workflow YAML as a node graph (the `./workflows` WorkflowGraph).\n * When absent, the YAML is shown as text. */\n renderGraph?: (yaml: string) => ReactNode;\n}\n\nexport function ProposalCard({\n proposal,\n confirming,\n onConfirm,\n onCancel,\n navigate,\n renderGraph,\n}: ProposalCardProps) {\n const view = describeProposal(proposal);\n const [tab, setTab] = useState<\"graph\" | \"yaml\">(\"graph\");\n const isWorkflow = view.preview?.kind === \"workflow\";\n const showGraph = isWorkflow && !!renderGraph;\n\n return (\n <div className=\"rounded-lg border border-primary/40 bg-card p-3 text-sm\">\n <p className=\"font-medium text-foreground\">{view.title}</p>\n <p className=\"text-muted-foreground text-xs\">\n Confirm to run this action on your account.\n </p>\n\n {view.fields.length > 0 && (\n <dl className=\"mt-2 space-y-1\">\n {view.fields.map((f) => (\n <div key={f.label} className=\"flex gap-2 text-xs\">\n <dt className=\"shrink-0 text-muted-foreground\">{f.label}</dt>\n <dd className=\"truncate text-foreground\" title={f.value}>\n {f.value}\n </dd>\n </div>\n ))}\n </dl>\n )}\n\n {view.skills && view.skills.length > 0 && (\n <div className=\"mt-2\">\n <p className=\"text-muted-foreground text-xs\">New skills</p>\n <ul className=\"mt-1 space-y-0.5\">\n {view.skills.map((s) => (\n <li key={s.name} className=\"text-foreground text-xs\">\n <span className=\"font-medium\">{s.name}</span>\n {s.description ? (\n <span className=\"text-muted-foreground\"> — {s.description}</span>\n ) : null}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {view.preview && (\n <div className=\"mt-2\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-muted-foreground text-xs\">{view.preview.label}</p>\n {showGraph && (\n <div className=\"flex gap-2 text-xs\">\n <button\n type=\"button\"\n onClick={() => setTab(\"graph\")}\n className={\n tab === \"graph\" ? \"text-foreground\" : \"text-muted-foreground\"\n }\n >\n Graph\n </button>\n <button\n type=\"button\"\n onClick={() => setTab(\"yaml\")}\n className={\n tab === \"yaml\" ? \"text-foreground\" : \"text-muted-foreground\"\n }\n >\n YAML\n </button>\n </div>\n )}\n </div>\n {showGraph && tab === \"graph\" ? (\n <div className=\"mt-1 h-64 overflow-hidden rounded border border-border\">\n {renderGraph?.(view.preview.content)}\n </div>\n ) : (\n <pre className=\"mt-1 max-h-48 overflow-auto rounded border border-border bg-muted/50 p-2 text-xs\">\n <code>{view.preview.content}</code>\n </pre>\n )}\n </div>\n )}\n\n {proposal.requirements && proposal.requirements.length > 0 && (\n <div className=\"mt-3 rounded border border-border p-2\">\n <p className=\"text-muted-foreground text-xs\">Integrations</p>\n <ul className=\"mt-1 space-y-1\">\n {proposal.requirements.map((r) => (\n <RequirementRow\n key={`${r.provider}-${r.kind ?? \"integration\"}`}\n req={r}\n navigate={navigate}\n />\n ))}\n </ul>\n <p className=\"mt-1 text-muted-foreground text-xs\">\n Connect the items above, then confirm — your proposal stays here until\n you do.\n </p>\n </div>\n )}\n\n {proposal.retryError && (\n <p role=\"alert\" className=\"mt-2 text-destructive text-xs\">\n {proposal.retryError}\n </p>\n )}\n\n <div className=\"mt-3 flex gap-2\">\n <button\n type=\"button\"\n onClick={onConfirm}\n disabled={confirming || !proposal.proposalId}\n className=\"rounded bg-primary px-3 py-1.5 text-primary-foreground text-sm disabled:opacity-50\"\n >\n {confirming ? \"Confirming…\" : \"Confirm\"}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={confirming}\n className=\"rounded border border-border px-3 py-1.5 text-foreground text-sm disabled:opacity-50\"\n >\n Cancel\n </button>\n </div>\n </div>\n );\n}\n\nfunction openConnect(target: string, navigate?: (path: string) => void) {\n // Protocol-relative URLs (//host) inherit the page scheme and point off-site —\n // never a legitimate connect target, so reject outright.\n if (target.startsWith(\"//\")) return;\n // Canonicalize before the scheme check so it can't be smuggled past with\n // leading whitespace or an embedded tab/newline that browsers strip (a regex\n // guard misses those). Only http(s) may EVER navigate — via window.open OR\n // window.location.assign — which closes the `javascript:`/`data:` XSS vector.\n let url: URL;\n try {\n url = new URL(target, window.location.origin);\n } catch {\n return;\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return;\n // A bare relative path (no scheme) is in-app navigation → host router; an\n // absolute http(s) URL is an external link → new tab.\n if (/^[a-z][a-z0-9+.-]*:/i.test(target)) {\n window.open(url.href, \"_blank\", \"noopener,noreferrer\");\n } else if (navigate) {\n navigate(target);\n } else {\n window.location.assign(url.href);\n }\n}\n\nfunction RequirementRow({\n req,\n navigate,\n}: {\n req: ConnectionRequirement;\n navigate?: (path: string) => void;\n}) {\n const label = providerLabel(req.provider);\n const isApp = req.kind === \"github_app\";\n const kindLabel = isApp ? `${label} App` : label;\n const statusText = req.connected\n ? isApp\n ? \"installed\"\n : \"connected\"\n : isApp\n ? \"not installed\"\n : \"not connected\";\n // connectUrl === null means \"no connect target to offer\" (e.g. a github_app\n // requirement on a deploy with no app slug) — show the status without a link.\n const canConnect = !req.connected && req.connectUrl !== null;\n const target = req.connectUrl ?? \"/app/integrations\";\n\n return (\n <li className=\"flex items-center justify-between gap-2 text-xs\">\n <span className=\"flex min-w-0 items-center gap-2\">\n <ProviderLogo provider={req.provider} size={16} />\n <span className=\"truncate text-foreground\">{kindLabel}</span>\n <span className=\"flex shrink-0 items-center gap-1\">\n {/* Filled vs outlined dot is a non-color (shape) cue for the\n connected state, so it reads for color-blind users too — the\n status text alone would lean on color. */}\n <span\n aria-hidden=\"true\"\n className={`h-1.5 w-1.5 rounded-full ${\n req.connected ? \"bg-primary\" : \"border border-muted-foreground\"\n }`}\n />\n <span\n className={req.connected ? \"text-primary\" : \"text-muted-foreground\"}\n >\n {statusText}\n </span>\n </span>\n </span>\n {canConnect && (\n <button\n type=\"button\"\n onClick={() => openConnect(target, navigate)}\n className=\"shrink-0 text-primary\"\n >\n {isApp ? \"Install\" : \"Connect\"} →\n </button>\n )}\n </li>\n );\n}\n","/**\n * Display label for a connector slug (\"github\" → \"GitHub\"). Kept in its own\n * module — separate from the graph model — so consumers in the always-loaded app\n * shell (e.g. ProposalIntegrations) can import the label without pulling the\n * `yaml` parser (a `model.ts` dependency) into the main bundle.\n */\n\nconst PROVIDER_LABELS: Record<string, string> = {\n github: \"GitHub\",\n gitlab: \"GitLab\",\n slack: \"Slack\",\n stripe: \"Stripe\",\n notion: \"Notion\",\n linear: \"Linear\",\n discord: \"Discord\",\n};\n\nexport function providerLabel(provider: string): string {\n const key = provider.toLowerCase();\n return (\n PROVIDER_LABELS[key] ?? provider.charAt(0).toUpperCase() + provider.slice(1)\n );\n}\n","/**\n * The assistant's default transcript renderer, built on web-react's\n * `ChatMessages`. The reducer streams a FLAT, per-segment transcript (user /\n * assistant / `tool` chip / `status` messages, plus turn-level reasoning and\n * pending proposals); `adaptTranscript` collapses each turn into one assistant\n * message whose ordered `segments` carry that turn's text runs and tool chips in\n * emission order, so `ChatMessages` renders them interleaved (text → tool →\n * text) rather than as one text blob followed by a tool group.\n *\n * A host can swap this whole renderer via `AssistantPanelProps.renderTranscript`;\n * the markdown renderer and per-tool detail renderers are injected so this\n * subpath stays free of any product-specific markdown/tool dependency.\n */\n\nimport { useCallback, useMemo, type ReactNode } from \"react\";\nimport {\n ChatMessages,\n type ChatMessageSegment,\n type ChatUiMessage,\n type ToolDetailRenderers,\n} from \"../web-react\";\nimport type { AssistantState } from \"./reducer\";\nimport type {\n AssistantTranscriptView,\n PendingProposal,\n ToolOutcome,\n} from \"./types\";\n\n/**\n * True while a turn is streaming but the model hasn't emitted its first answer\n * token yet — drives the \"thinking\" affordance so a reasoning gap reads as\n * working, not a frozen panel.\n */\nexport function assistantIsThinking(state: AssistantState): boolean {\n if (state.status !== \"streaming\") return false;\n const streaming = state.streamingId\n ? state.messages.find((m) => m.id === state.streamingId)\n : undefined;\n // Thinking until the open assistant bubble receives text (a tool_call closes\n // the bubble, so a running tool also reads as no-open-bubble = still working).\n return !streaming || streaming.text === \"\";\n}\n\ntype ToolStatus = Extract<ChatMessageSegment, { kind: \"tool\" }>[\"call\"][\"status\"];\n\nconst TOOL_STATUS: Record<string, ToolStatus> = {\n running: \"running\",\n ok: \"done\",\n failed: \"error\",\n};\n\nexport interface AdaptedTranscript {\n messages: ChatUiMessage[];\n /** The assistant message under which pending proposals should render, or null\n * when there are none. */\n proposalHostId: string | null;\n /** The current/most-recent turn's assistant message — where the turn cost line\n * renders (it carries the turn's metrics), or null when there is none. */\n metricsHostId: string | null;\n}\n\n/**\n * Reshape a `ToolOutcome` into what web-react's tool-detail card reads. A success\n * (`{ ok: true, result }`) already matches. A failure keeps its error under\n * `outcome.error`, but web-react reads a top-level `outcome.message`/`code` — so\n * flatten it, else an expanded failed tool card shows a generic \"Tool failed\"\n * instead of the real server error.\n */\nfunction adaptToolResult(outcome: ToolOutcome): unknown {\n if (outcome.ok) return { ok: true, result: outcome.result };\n return { ok: false, message: outcome.error?.message, code: outcome.error?.code };\n}\n\n/** An assistant turn message with `segments` guaranteed present, so the fold can\n * push to it directly. Every turn message is created by `openTurn`. */\ntype TurnMessage = ChatUiMessage & { segments: ChatMessageSegment[] };\n\n/**\n * Fold the transcript view into web-react `ChatUiMessage[]`: each user message is\n * 1:1; the assistant/`tool`/`status` messages between two user turns collapse\n * into one assistant message whose ordered `segments` carry the turn's text runs\n * and tool chips IN EMISSION ORDER (with each finished tool's outcome as the chip\n * `result`). The joined text is also kept on `content` — web-react reads it as the\n * \"answer has started\" signal that gates the reasoning box. The live turn's\n * reasoning preview and model label hang on the last assistant message, and\n * `proposalHostId` names the message the pending proposals render under.\n */\nexport function adaptTranscript(view: AssistantTranscriptView): AdaptedTranscript {\n const messages: ChatUiMessage[] = [];\n let turn: TurnMessage | null = null;\n // The assistant message of the CURRENT turn — the one opened since the most\n // recent user message — or null when the live turn has produced no assistant\n // segment yet. Reset on each user message so the live turn's reasoning, model\n // label, and pending proposal can never attach to a previous turn's bubble.\n let currentTurnAssistant: TurnMessage | null = null;\n\n const openTurn = (id: string): TurnMessage => {\n const message: TurnMessage = { id, role: \"assistant\", content: \"\", segments: [] };\n messages.push(message);\n turn = message;\n currentTurnAssistant = message;\n return message;\n };\n\n // Append a text run to both the ordered segments (the rendered, interleaved\n // body) and the joined `content` (which gates the reasoning box). Kept in\n // lockstep so the two never disagree.\n const appendText = (message: TurnMessage, text: string) => {\n if (!text.trim()) return;\n message.segments.push({ kind: \"text\", content: text });\n message.content = message.content ? `${message.content}\\n\\n${text}` : text;\n };\n\n for (const msg of view.messages) {\n if (msg.role === \"user\") {\n messages.push({ id: msg.id, role: \"user\", content: msg.text });\n turn = null;\n currentTurnAssistant = null;\n } else if (msg.role === \"assistant\") {\n const active = turn ?? openTurn(msg.id);\n appendText(active, msg.text);\n currentTurnAssistant = active;\n } else if (msg.role === \"tool\") {\n // A tool row exists only to carry its activity chip; with no tool metadata\n // there is nothing to render, so skip it rather than open a phantom bubble.\n if (!msg.tool) continue;\n // When the tool opens the turn (no preamble text), the synthesized\n // assistant bubble needs an id distinct from the tool chip's (which reuses\n // `msg.id`), or the two would collide.\n const active = turn ?? openTurn(`turn-${msg.id}`);\n currentTurnAssistant = active;\n active.segments.push({\n kind: \"tool\",\n call: {\n id: msg.id,\n name: msg.tool.name,\n // An unmapped status resolves to \"error\", not \"running\": a stuck\n // spinner would hide a finished or failed tool.\n status: TOOL_STATUS[msg.tool.status] ?? \"error\",\n ...(msg.tool.args ? { args: msg.tool.args } : {}),\n ...(msg.tool.outcome ? { result: adaptToolResult(msg.tool.outcome) } : {}),\n },\n });\n } else {\n // `status` — an informational system note that ends the assistant turn.\n messages.push({ id: msg.id, role: \"system\", content: msg.text });\n turn = null;\n }\n }\n\n let proposalHostId: string | null = null;\n if (view.pendingProposals.length > 0) {\n // A propose-only turn may carry no assistant segment yet — synthesize a host\n // in the current turn so the proposal card still has somewhere to render.\n if (!currentTurnAssistant) {\n currentTurnAssistant = openTurn(\n `proposal-host-${view.pendingProposals[0]!.callId}`,\n );\n }\n proposalHostId = currentTurnAssistant.id;\n }\n\n // Live reasoning + model label + settled metrics belong to the current turn's\n // assistant bubble (including a host synthesized just above for a propose-only\n // turn, so a turn that only reasons then proposes still shows its thinking).\n if (currentTurnAssistant) {\n if (view.reasoning) currentTurnAssistant.reasoning = view.reasoning;\n if (view.model) currentTurnAssistant.modelUsed = view.model;\n if (view.usage) {\n if (view.usage.completionTokens != null)\n currentTurnAssistant.completionTokens = view.usage.completionTokens;\n if (view.usage.promptTokens != null)\n currentTurnAssistant.promptTokens = view.usage.promptTokens;\n if (view.usage.durationMs != null)\n currentTurnAssistant.durationMs = view.usage.durationMs;\n }\n }\n\n // A turn that produced no body and had nothing turn-level hung on it renders as\n // a bare \"Assistant\" header. That state is the at-send frame before the first\n // delta; drop it so an empty turn never flashes a blank bubble. The proposal\n // host is exempt: it intentionally carries the pending proposal card.\n const isEmptyShell = (m: ChatUiMessage): boolean =>\n m.role === \"assistant\" &&\n m.content === \"\" &&\n (m.segments?.length ?? 0) === 0 &&\n m.reasoning == null &&\n m.modelUsed == null &&\n m.completionTokens == null &&\n m.promptTokens == null &&\n m.durationMs == null &&\n m.id !== proposalHostId;\n\n return {\n messages: messages.filter((m) => !isEmptyShell(m)),\n proposalHostId,\n metricsHostId:\n currentTurnAssistant && !isEmptyShell(currentTurnAssistant)\n ? currentTurnAssistant.id\n : null,\n };\n}\n\n/** Sub-cent turn costs need more precision than dollars-and-cents. */\nfunction formatTurnCost(costUsd: number): string {\n return costUsd < 0.01 ? `$${costUsd.toFixed(4)}` : `$${costUsd.toFixed(2)}`;\n}\n\n/** A named component (rather than calling `render()` inline in a map) gives React\n * a stable, keyed element per proposal so cards reconcile instead of remount. */\nfunction ProposalSlot({\n proposal,\n render,\n}: {\n proposal: PendingProposal;\n render: (proposal: PendingProposal) => ReactNode;\n}) {\n return <>{render(proposal)}</>;\n}\n\nexport interface AssistantTranscriptProps {\n view: AssistantTranscriptView;\n /** Markdown renderer for assistant content; defaults to plain pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode;\n /** Per-tool custom detail renderers for expanded tool cards. */\n toolRenderers?: ToolDetailRenderers;\n /** Zero-state shown for a fresh, non-streaming thread. */\n emptyState?: ReactNode;\n}\n\n/**\n * Render the assistant conversation with web-react's `ChatMessages`. Pending\n * proposals render via the panel's bound `view.renderProposal`, placed inline\n * after the proposing turn through `renderExtras`; the settled turn cost renders\n * once under its assistant bubble.\n */\nexport function AssistantTranscript({\n view,\n renderMarkdown,\n toolRenderers,\n emptyState,\n}: AssistantTranscriptProps) {\n const { messages, proposalHostId, metricsHostId } = useMemo(\n () => adaptTranscript(view),\n [view],\n );\n\n // Stable identity: web-react memoizes its per-message markdown parse on the\n // `renderMarkdown` reference, so a fresh closure each render (the `view` object\n // changes every stream tick) would re-parse every message on every token.\n const markdown = useCallback(\n (content: string) => (renderMarkdown ? renderMarkdown(content) : content),\n [renderMarkdown],\n );\n\n if (messages.length === 0 && !view.isStreaming) {\n return <>{emptyState}</>;\n }\n\n return (\n <ChatMessages\n messages={messages}\n // ChatMessages derives the streaming message internally, so only\n // `isStreaming` is needed; `view.isThinking` is a subset of it.\n loading={view.isStreaming}\n agentLabel=\"Assistant\"\n renderMarkdown={markdown}\n toolRenderers={toolRenderers}\n renderEmpty={() => <>{emptyState}</>}\n renderExtras={(message) => {\n const proposals =\n message.id === proposalHostId && view.pendingProposals.length > 0 ? (\n <div className=\"mt-3 flex flex-col gap-3\">\n {view.pendingProposals.map((proposal) => (\n <ProposalSlot\n key={proposal.callId}\n proposal={proposal}\n render={view.renderProposal}\n />\n ))}\n </div>\n ) : null;\n // The settled turn's at-cost figure, shown once under its assistant\n // bubble. Hidden while streaming and for a replayed (uncharged) turn.\n const cost =\n message.id === metricsHostId &&\n !view.isStreaming &&\n view.usage?.costUsd != null &&\n !view.usage.replayed ? (\n <p className=\"mt-1 text-[11px] text-muted-foreground\">\n {formatTurnCost(view.usage.costUsd)} this turn\n </p>\n ) : null;\n if (!proposals && !cost) return null;\n return (\n <>\n {proposals}\n {cost}\n </>\n );\n }}\n />\n );\n}\n","/**\n * Persisted, user-adjustable presentation preferences for the assistant drawer:\n * its width (drag-to-resize) and a font-size scale. Both survive reloads via\n * localStorage and are clamped to sane bounds. Kept out of the components so the\n * SSR-safe persistence and clamping live in one place and stay testable.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\n/** Narrowest the drawer may be dragged — below this the chat is unusable. */\nexport const MIN_PANEL_WIDTH = 360;\n/** Default drawer width — matches the previous fixed `max-w-md` (28rem). */\nexport const DEFAULT_PANEL_WIDTH = 448;\n/** Widest the drawer may occupy, as a fraction of the viewport. */\nconst MAX_PANEL_WIDTH_FRACTION = 0.95;\n\n/** Font-size scale bounds and step for the A−/A+ control. 1 = the design\n * default; the panel applies this as a CSS `zoom` on the transcript so the\n * whole conversation scales uniformly. */\nexport const MIN_FONT_SCALE = 0.875;\nexport const MAX_FONT_SCALE = 1.5;\nexport const DEFAULT_FONT_SCALE = 1;\nconst FONT_SCALE_STEP = 0.125;\n\nconst WIDTH_KEY = \"assistant.panel.width\";\nconst FONT_SCALE_KEY = \"assistant.panel.fontScale\";\n\nfunction readNumber(key: string): number | null {\n try {\n const raw = window.localStorage.getItem(key);\n // Empty/whitespace → fall back to the default. Without this, `Number(\"\")`\n // is 0 (finite), which would clamp to MIN instead of using the default.\n if (raw == null || raw.trim() === \"\") return null;\n const n = Number(raw);\n return Number.isFinite(n) ? n : null;\n } catch {\n return null;\n }\n}\n\nfunction writeNumber(key: string, value: number): void {\n try {\n window.localStorage.setItem(key, String(value));\n } catch {\n // Storage can be unavailable (private mode, quota) — preferences are a\n // convenience, never a hard dependency, so a failed write is silent.\n }\n}\n\n/** The largest width the drawer may take on the current viewport. Returns a\n * large FINITE fallback with no window (the app is a client SPA, so this is\n * belt-and-suspenders) — Infinity would be an invalid `aria-valuemax`. */\nfunction maxPanelWidth(): number {\n if (typeof window === \"undefined\") return 9999;\n return Math.round(window.innerWidth * MAX_PANEL_WIDTH_FRACTION);\n}\n\nfunction clampWidth(value: number): number {\n return Math.min(\n Math.max(Math.round(value), MIN_PANEL_WIDTH),\n maxPanelWidth(),\n );\n}\n\nfunction clampScale(value: number): number {\n // Round to the step grid so repeated +/- never accumulates float drift.\n const stepped = Math.round(value / FONT_SCALE_STEP) * FONT_SCALE_STEP;\n return Math.min(Math.max(stepped, MIN_FONT_SCALE), MAX_FONT_SCALE);\n}\n\nexport interface PanelWidth {\n /** Current width in px. Apply as an inline `width` only on desktop. */\n width: number;\n /** Current max allowed width in px (viewport-derived; updates on resize).\n * Exposed for an accurate `aria-valuemax` on the resize control. */\n maxWidth: number;\n /** Set an absolute width (clamped + persisted). Use on drag end / discrete\n * changes — NOT on every drag tick. */\n setWidth: (next: number) => void;\n /** Set an absolute width (clamped, NOT persisted). Use during a live drag so\n * the panel tracks the pointer without thrashing localStorage every tick. */\n previewWidth: (next: number) => void;\n /** Nudge by a delta (keyboard resize); clamped + persisted. */\n nudgeWidth: (deltaPx: number) => void;\n}\n\n/**\n * The drawer's persisted width. Initialized to the default so first render is\n * stable; the stored value is read in an effect and applied after mount.\n * Re-clamps on viewport resize so a stored width can never exceed the current\n * window, and tracks the live max for the resize control's ARIA bounds.\n */\nexport function usePanelWidth(): PanelWidth {\n const [width, setWidthState] = useState(DEFAULT_PANEL_WIDTH);\n const [maxWidth, setMaxWidth] = useState(() => maxPanelWidth());\n // The user's explicit width preference. The rendered `width` is this clamped\n // to the current viewport; a transient shrink clamps only the display, so when\n // the viewport grows back the preference is restored (rather than the shrink\n // permanently overwriting the choice).\n const desiredRef = useRef(DEFAULT_PANEL_WIDTH);\n\n useEffect(() => {\n setMaxWidth(maxPanelWidth());\n const stored = readNumber(WIDTH_KEY);\n if (stored != null) {\n desiredRef.current = Math.max(Math.round(stored), MIN_PANEL_WIDTH);\n setWidthState(clampWidth(stored));\n }\n }, []);\n\n // On viewport change, recompute the display from the DESIRED preference (not\n // the possibly-already-clamped current width) so growing the window restores\n // it. Keep the reported max current too, for an accurate aria-valuemax.\n useEffect(() => {\n const onResize = () => {\n setMaxWidth(maxPanelWidth());\n setWidthState(clampWidth(desiredRef.current));\n };\n window.addEventListener(\"resize\", onResize);\n return () => window.removeEventListener(\"resize\", onResize);\n }, []);\n\n const setWidth = useCallback((next: number) => {\n const clamped = clampWidth(next);\n desiredRef.current = clamped;\n writeNumber(WIDTH_KEY, clamped);\n setWidthState(clamped);\n }, []);\n\n const previewWidth = useCallback((next: number) => {\n // Live drag: display only — no persist, no change to the desired preference\n // (drag end calls setWidth to commit).\n setWidthState(clampWidth(next));\n }, []);\n\n const nudgeWidth = useCallback((deltaPx: number) => {\n const clamped = clampWidth(desiredRef.current + deltaPx);\n desiredRef.current = clamped;\n writeNumber(WIDTH_KEY, clamped);\n setWidthState(clamped);\n }, []);\n\n return { width, maxWidth, setWidth, previewWidth, nudgeWidth };\n}\n\nexport interface FontScale {\n scale: number;\n increase: () => void;\n decrease: () => void;\n canIncrease: boolean;\n canDecrease: boolean;\n}\n\n/** The panel's persisted font-size scale, with bounded A−/A+ controls. */\nexport function useFontScale(): FontScale {\n const [scale, setScaleState] = useState(DEFAULT_FONT_SCALE);\n\n useEffect(() => {\n const stored = readNumber(FONT_SCALE_KEY);\n if (stored != null) setScaleState(clampScale(stored));\n }, []);\n\n const step = useCallback((delta: number) => {\n setScaleState((prev) => {\n const clamped = clampScale(prev + delta);\n writeNumber(FONT_SCALE_KEY, clamped);\n return clamped;\n });\n }, []);\n\n return {\n scale,\n increase: () => step(FONT_SCALE_STEP),\n decrease: () => step(-FONT_SCALE_STEP),\n canIncrease: scale < MAX_FONT_SCALE - 1e-9,\n canDecrease: scale > MIN_FONT_SCALE + 1e-9,\n };\n}\n\n/**\n * Whether the viewport is at the `md` breakpoint or wider. The drawer is a\n * full-screen sheet below `md` (no resize), and a width-constrained side panel\n * at/above it. Defaults to `true` for SSR/first paint — the dialog only renders\n * after a client interaction, by which point the effect has corrected it.\n */\nexport function useIsDesktop(): boolean {\n // Read matchMedia synchronously on first render (client SPA) so the drawer\n // never first-paints at desktop width on a mobile viewport; the default only\n // applies in the no-window/no-matchMedia case.\n const [isDesktop, setIsDesktop] = useState(() =>\n typeof window !== \"undefined\" && typeof window.matchMedia === \"function\"\n ? window.matchMedia(\"(min-width: 768px)\").matches\n : true,\n );\n useEffect(() => {\n // Mirror the initializer guard: a runtime without matchMedia (jsdom, some\n // embedded webviews) keeps the desktop default rather than throwing — the\n // dock is in the always-mounted shell, so a throw here would break it.\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return;\n }\n const mq = window.matchMedia(\"(min-width: 768px)\");\n const update = () => setIsDesktop(mq.matches);\n update();\n mq.addEventListener(\"change\", update);\n return () => mq.removeEventListener(\"change\", update);\n }, []);\n return isDesktop;\n}\n","/**\n * Shared launcher state for the assistant dock. The dock is mounted once in the\n * app shell, but other surfaces (e.g. the Workflows page) need to open it and\n * prefill the composer with a starter prompt. This context owns the dock's\n * open state plus a one-shot composer seed, so any `/app/*` surface can call\n * `openAssistant(\"Create a workflow that …\")` without reaching into the dock.\n */\n\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useMemo,\n useState,\n} from \"react\";\n\nexport interface AssistantLauncher {\n /** Whether the assistant drawer is open. */\n open: boolean;\n /** A one-shot starter prompt to prefill the composer with, or null. */\n seed: string | null;\n /** Open the drawer; optionally prefill the composer with `seed`. */\n openAssistant: (seed?: string) => void;\n closeAssistant: () => void;\n /** Clear the pending seed once the composer has applied it (consume-once). */\n clearSeed: () => void;\n}\n\nconst AssistantLauncherContext = createContext<AssistantLauncher | null>(null);\n\nexport function AssistantLauncherProvider({\n children,\n}: {\n children: ReactNode;\n}) {\n const [open, setOpen] = useState(false);\n const [seed, setSeed] = useState<string | null>(null);\n\n const openAssistant = useCallback((next?: string) => {\n // Only replace the seed when one is supplied, so opening the drawer with no\n // argument never clobbers a starter another caller just set.\n if (next != null) setSeed(next);\n setOpen(true);\n }, []);\n const closeAssistant = useCallback(() => setOpen(false), []);\n const clearSeed = useCallback(() => setSeed(null), []);\n\n const value = useMemo(\n () => ({ open, seed, openAssistant, closeAssistant, clearSeed }),\n [open, seed, openAssistant, closeAssistant, clearSeed],\n );\n\n return (\n <AssistantLauncherContext.Provider value={value}>\n {children}\n </AssistantLauncherContext.Provider>\n );\n}\n\nexport function useAssistantLauncher(): AssistantLauncher {\n const ctx = useContext(AssistantLauncherContext);\n if (!ctx) {\n throw new Error(\n \"useAssistantLauncher must be used within an AssistantLauncherProvider\",\n );\n }\n return ctx;\n}\n","import { type KeyboardEvent, type PointerEvent, useRef } from \"react\";\nimport { MIN_PANEL_WIDTH } from \"./usePanelPrefs\";\n\n/**\n * Drag-to-resize grip on the assistant drawer's left edge. Pointer capture keeps\n * the drag alive while the cursor moves anywhere on screen; arrow keys resize in\n * coarse steps for keyboard users. The drawer is right-anchored, so dragging\n * left widens it. The in-memory width updates every move (`onPreview`); the\n * final width is persisted once on release (`onCommit`).\n */\nexport function ResizeHandle({\n width,\n maxWidth,\n onPreview,\n onCommit,\n onNudge,\n}: {\n width: number;\n maxWidth: number;\n /** Live (non-persisted) width update during a drag. The value is the raw\n * pointer-derived width and is NOT clamped — the consumer must clamp it to its\n * own min/max (the bundled `usePanelWidth.previewWidth` does). */\n onPreview: (next: number) => void;\n /** Persist the final width (drag end). */\n onCommit: (next: number) => void;\n /** Keyboard resize delta (clamped + persisted). */\n onNudge: (deltaPx: number) => void;\n}) {\n const dragRef = useRef<{\n startX: number;\n startWidth: number;\n lastWidth: number;\n } | null>(null);\n\n const onPointerDown = (e: PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return; // primary button / touch / pen only\n e.preventDefault();\n e.currentTarget.setPointerCapture(e.pointerId);\n dragRef.current = {\n startX: e.clientX,\n startWidth: width,\n lastWidth: width,\n };\n };\n\n const onPointerMove = (e: PointerEvent<HTMLDivElement>) => {\n const drag = dragRef.current;\n if (!drag) return;\n // Update the in-memory width every tick (smooth), but don't persist — that\n // would hit localStorage on every pointermove.\n const next = drag.startWidth + (drag.startX - e.clientX);\n // Skip sub-pixel jitter so a touch drag doesn't re-render the panel subtree\n // on every noise event.\n if (Math.abs(next - drag.lastWidth) < 1) return;\n drag.lastWidth = next;\n onPreview(next);\n };\n\n const endDrag = (e: PointerEvent<HTMLDivElement>) => {\n const drag = dragRef.current;\n if (!drag) return;\n dragRef.current = null;\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId);\n }\n // Persist once, on release.\n onCommit(drag.lastWidth);\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n const STEP = 24;\n if (e.key === \"ArrowLeft\") {\n e.preventDefault();\n onNudge(STEP);\n } else if (e.key === \"ArrowRight\") {\n e.preventDefault();\n onNudge(-STEP);\n }\n };\n\n return (\n // biome-ignore lint/a11y/useSemanticElements: a focusable drag handle is an ARIA window-splitter (role=separator); no native HTML element provides this.\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"Resize assistant panel\"\n aria-valuemin={MIN_PANEL_WIDTH}\n aria-valuemax={maxWidth}\n aria-valuenow={width}\n tabIndex={0}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={onKeyDown}\n className=\"group absolute inset-y-0 left-0 z-10 flex w-2 -translate-x-1/2 cursor-ew-resize touch-none items-center justify-center focus:outline-none\"\n >\n <span\n aria-hidden=\"true\"\n className=\"h-10 w-1 rounded-full bg-border transition-colors group-hover:bg-primary group-focus:bg-primary\"\n />\n </div>\n );\n}\n"],"mappings":"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","names":["error","useEffect","useReducer","useReducer","useEffect","useCallback","useEffect","useRef","useState","useRef","useState","useCallback","useEffect","useEffect","useRef","useEffect","useMemo","useRef","useState","useState","jsx","useState","useState","jsx","jsxs","useState","useCallback","useMemo","jsx","jsxs","useMemo","useCallback","useCallback","useEffect","useRef","useState","jsx","jsxs","useState","useRef","useMemo","useEffect","createContext","useCallback","useContext","useMemo","useState","jsx","useRef","jsx","useRef","Fragment","jsx","jsxs","useRef","useEffect"]}
1
+ {"version":3,"sources":["../../src/assistant/sse.ts","../../src/assistant/client.ts","../../src/assistant/client-context.tsx","../../src/assistant/useAssistantChat.ts","../../src/assistant/persistence.ts","../../src/assistant/presentation.ts","../../src/assistant/reducer.ts","../../src/assistant/useAssistantModels.ts","../../src/assistant/useAssistantThreads.ts","../../src/assistant/AssistantDock.tsx","../../src/assistant/AssistantPanel.tsx","../../src/assistant/AssistantHistory.tsx","../../src/assistant/time-ago.ts","../../src/assistant/ProposalCard.tsx","../../src/assistant/provider-label.ts","../../src/assistant/transcript.tsx","../../src/assistant/usePanelPrefs.ts","../../src/assistant/use-stick-to-bottom.ts","../../src/assistant/launcher.tsx","../../src/assistant/ResizeHandle.tsx"],"sourcesContent":["/**\n * POST-based SSE reading for the assistant chat stream. The browser\n * `EventSource` is GET-only and can't send a request body, so the stream is read\n * off a `fetch` POST response.\n *\n * The framing parser is vendored here (rather than depending on an internal SDK)\n * so this module stays self-contained and consumable by any host. It mirrors the\n * standard SSE wire format: events separated by a blank line, `event:` / `data:`\n * fields, `:`-prefixed comments ignored, multi-line `data:` joined with `\\n`, and\n * each event's data JSON-parsed (falling back to the raw string).\n */\n\nexport interface ParsedSSEEvent<T = unknown> {\n data: T;\n rawData: string;\n eventId?: string;\n eventType?: string;\n}\n\n/**\n * Incremental SSE parser. Feed decoded string chunks via `push()`; call\n * `flush()` once the stream closes to emit any final buffered event.\n */\nexport class SSEChunkParser<T = unknown> {\n private buffer = \"\";\n private current: { id?: string; event?: string; data?: string } = {};\n\n push(chunk: string): ParsedSSEEvent<T>[] {\n this.buffer += chunk;\n const lines = this.buffer.split(\"\\n\");\n // The last element is a (possibly empty) partial line; hold it for the next\n // chunk so an event split across reads isn't parsed half-formed.\n this.buffer = lines.pop() ?? \"\";\n return this.processLines(lines);\n }\n\n flush(): ParsedSSEEvent<T>[] {\n const lines = this.buffer ? [this.buffer] : [];\n this.buffer = \"\";\n const events = this.processLines(lines);\n const finalEvent = this.parseCurrent();\n if (finalEvent) {\n events.push(finalEvent);\n this.current = {};\n }\n return events;\n }\n\n private processLines(lines: string[]): ParsedSSEEvent<T>[] {\n const events: ParsedSSEEvent<T>[] = [];\n for (const rawLine of lines) {\n // Tolerate CRLF framing: strip a trailing CR the \"\\n\" split left behind.\n const line = rawLine.endsWith(\"\\r\") ? rawLine.slice(0, -1) : rawLine;\n\n if (line.startsWith(\":\")) continue; // comment / keepalive\n\n if (line === \"\") {\n const parsed = this.parseCurrent();\n if (parsed) events.push(parsed);\n this.current = {};\n continue;\n }\n\n if (line.startsWith(\"id:\")) {\n this.current.id = line.slice(3).trim();\n } else if (line.startsWith(\"event:\")) {\n this.current.event = line.slice(6).trim();\n } else if (line.startsWith(\"data:\")) {\n let value = line.slice(5);\n if (value.startsWith(\" \")) value = value.slice(1);\n this.current.data =\n this.current.data !== undefined\n ? `${this.current.data}\\n${value}`\n : value;\n }\n }\n return events;\n }\n\n private parseCurrent(): ParsedSSEEvent<T> | null {\n if (this.current.data === undefined) return null;\n const rawData = this.current.data.trim();\n if (!rawData) return null;\n let data: T;\n try {\n data = JSON.parse(rawData) as T;\n } catch {\n // A non-JSON payload is surfaced as the raw string; the caller's typed\n // mapper drops anything that isn't a well-formed object.\n data = rawData as unknown as T;\n }\n return {\n data,\n rawData,\n eventId: this.current.id,\n eventType: this.current.event,\n };\n }\n}\n\n/**\n * Read a fetch `Response` body and invoke `onEvent` for each parsed SSE event\n * (its `eventType` plus JSON-parsed `data`), in wire order. Resolves when the\n * stream closes. The caller owns abort (via the fetch `signal`).\n */\nexport async function readSSEEvents(\n body: ReadableStream<Uint8Array>,\n onEvent: (event: ParsedSSEEvent) => void,\n): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n const parser = new SSEChunkParser();\n // releaseLock in finally so an abort (read rejects) or a throwing consumer\n // can't leave the reader lock held on the stream.\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n for (const event of parser.push(decoder.decode(value, { stream: true }))) {\n onEvent(event);\n }\n }\n // Flush any bytes the streaming decoder held back (a multi-byte character\n // split across the final chunk), then any event still buffered in the parser.\n const tail = decoder.decode();\n if (tail) {\n for (const event of parser.push(tail)) onEvent(event);\n }\n for (const event of parser.flush()) onEvent(event);\n } catch (err) {\n // Cancel the underlying stream so a read error or a throwing handler doesn't\n // leave the response open and buffering for the rest of the session. Swallow\n // a cancel failure so it can't mask the original error.\n await reader.cancel(err).catch(() => {});\n throw err;\n } finally {\n reader.releaseLock();\n }\n}\n","/**\n * Configurable network client for the assistant panel: the chat SSE stream, the\n * model/thread/history reads, and the proposal-confirmation call.\n *\n * The transport is injected via {@link AssistantClientConfig} so the same UI can\n * run in different hosts: a same-origin app authenticates with the session\n * cookie (`credentials: \"include\"`) and an `X-Requested-With` marker, while a\n * cross-origin host points `baseUrl` at the API and supplies a bearer token via\n * `headers`. The request shapes and the defensive wire parsing are identical\n * across hosts — only the base URL and the auth headers vary.\n */\n\nimport { readSSEEvents } from \"./sse\";\nimport type {\n AssistantStreamEvent,\n ChatMessage,\n ChatRequest,\n ConnectionRequirement,\n PendingProposal,\n} from \"./types\";\n\n/** Host-supplied transport configuration for {@link createAssistantClient}. */\nexport interface AssistantClientConfig {\n /**\n * Base URL the five assistant endpoints hang off, with no trailing slash —\n * e.g. `\"/api/v1/assistant\"` for a same-origin SSR edge, or\n * `\"https://id.tangle.tools/api/v1/assistant\"` cross-origin. Each method\n * appends its own path (`/chat`, `/models`, `/threads`, …).\n */\n baseUrl: string;\n /**\n * `fetch` credentials mode. Defaults to `\"include\"` so a same-origin cookie\n * session authenticates; a token-based cross-origin host may pass `\"omit\"`\n * and carry the credential in {@link AssistantClientConfig.headers}.\n */\n credentials?: RequestCredentials;\n /**\n * Headers applied to every request — the auth token and/or the CSRF marker.\n * Called per request so a rotating token is read fresh, never captured once.\n */\n headers?: () => Record<string, string>;\n}\n\nexport interface AssistantModelOption {\n slug: string;\n label: string;\n /** USD per million prompt tokens, when the catalog carries pricing. */\n promptUsdPerMillion?: number;\n /** Context window in tokens, when known. */\n contextTokens?: number;\n}\n\nexport interface AssistantModels {\n /** The slug the server uses when a turn selects no model. */\n default: string | null;\n models: AssistantModelOption[];\n}\n\n/** One past conversation in the history switcher. */\nexport interface AssistantThreadSummary {\n id: string;\n /** Truncated first user message; may be null for an untitled thread. */\n title: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Outcome of a model-list fetch. `ok` drives caching: the caller caches on `ok`\n * and retries on `!ok`. An EMPTY list is reported as `!ok` — the server always\n * offers at least the default model when the router is reachable, so an empty\n * menu means the catalog couldn't be loaded and should be retried, not cached\n * for the whole session.\n */\nexport interface AssistantModelsResult {\n ok: boolean;\n data: AssistantModels;\n}\n\n/**\n * Outcome of a thread-history restore. The three cases drive different recovery:\n * `ok` rehydrates the transcript; `gone` (the thread 404s — deleted or from a\n * reset DB) tells the caller to drop the dead thread id so the next turn starts\n * fresh; `error` (transient/network/aborted) keeps the thread id and simply\n * doesn't restore, so a later attempt or send still targets the live thread.\n */\nexport type ThreadHistoryResult =\n | { status: \"ok\"; messages: ChatMessage[]; proposals: PendingProposal[] }\n | { status: \"gone\" }\n | { status: \"error\" };\n\nexport type ConfirmResult =\n | { ok: true; output: unknown; retryable?: boolean }\n | { ok: false; error: string };\n\n/** The assistant network surface, bound to one host's transport config. */\nexport interface AssistantClient {\n fetchModels(signal?: AbortSignal): Promise<AssistantModelsResult>;\n fetchThreads(signal?: AbortSignal): Promise<AssistantThreadSummary[] | null>;\n fetchThreadHistory(\n threadId: string,\n signal?: AbortSignal,\n ): Promise<ThreadHistoryResult>;\n streamChat(\n req: ChatRequest,\n onEvent: (event: AssistantStreamEvent) => void,\n signal: AbortSignal,\n ): Promise<void>;\n confirmProposal(proposalId: string): Promise<ConfirmResult>;\n /** Delete a thread and its server-side turns/proposals. Resolves `{ ok }`; a\n * 404 (already gone) is treated as success so a double-delete is harmless.\n * Optional so a host with no delete endpoint stays a valid client — the panel\n * hides the delete affordance when it's absent (see `useAssistantThreads`). */\n deleteThread?(threadId: string): Promise<{ ok: boolean }>;\n}\n\nconst EMPTY_MODELS: AssistantModels = { default: null, models: [] };\n\n/** A parsed event payload narrowed to a plain (non-array) object, or null. The\n * shared parser JSON-parses each `data:` payload; a non-object (e.g. a\n * malformed frame it left as a raw string) is dropped. */\nfunction asObject(v: unknown): Record<string, unknown> | null {\n return v && typeof v === \"object\" && !Array.isArray(v)\n ? (v as Record<string, unknown>)\n : null;\n}\n\n/** A required wire string: the value when it's a non-empty string, else null so\n * a malformed frame is dropped rather than coerced to \"undefined\". */\nfunction reqStr(v: unknown): string | null {\n return typeof v === \"string\" && v !== \"\" ? v : null;\n}\n\nfunction numOrNull(v: unknown): number | null {\n return typeof v === \"number\" && Number.isFinite(v) ? v : null;\n}\n\n/**\n * Validate one restored connection requirement, or null to drop a malformed\n * element. The card reads `provider`/`connected`/`kind`/`connectUrl` directly\n * (e.g. `providerLabel(r.provider)` calls `.toLowerCase()`), so an element with\n * a non-string provider would throw at render — validate the shape rather than\n * trusting the wire blindly, mirroring `parseRestoredProposal`'s own posture.\n */\nfunction parseRequirement(raw: unknown): ConnectionRequirement | null {\n if (!raw || typeof raw !== \"object\") return null;\n const r = raw as Record<string, unknown>;\n if (typeof r.provider !== \"string\" || r.provider === \"\") return null;\n if (typeof r.connected !== \"boolean\") return null;\n const kind =\n r.kind === \"integration\" || r.kind === \"github_app\" ? r.kind : undefined;\n // connectUrl is `string | null` on the wire; anything else is dropped to\n // undefined so the card falls back to its kind-based default.\n const connectUrl =\n typeof r.connectUrl === \"string\" || r.connectUrl === null\n ? r.connectUrl\n : undefined;\n return {\n provider: r.provider,\n connected: r.connected,\n ...(kind ? { kind } : {}),\n ...(connectUrl !== undefined ? { connectUrl } : {}),\n };\n}\n\n/** Parse a proposal's connection requirements, dropping malformed entries.\n * Returns undefined when absent (non-authoring proposal) so the field stays\n * optional rather than an empty array. Delegates to `parseRequirement` so the\n * live `tool_proposal` path and the restore-from-history path preserve the\n * SAME fields — notably `kind` and `connectUrl`, which the card needs to tell a\n * missing GitHub App installation (\"GitHub App / not installed\", Install link)\n * apart from a missing OAuth connection (\"GitHub / not connected\", Connect\n * link); dropping `kind` here collapsed both to the integration rendering. */\nfunction parseRequirements(v: unknown): ConnectionRequirement[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: ConnectionRequirement[] = [];\n for (const item of v) {\n const parsed = parseRequirement(item);\n if (parsed) out.push(parsed);\n }\n return out;\n}\n\n/**\n * Map a parsed SSE event (name + already-parsed data) to a typed stream event.\n * Unknown event names (e.g. the `ping` keepalive) and malformed payloads yield\n * null and are dropped.\n */\nfunction toStreamEvent(\n event: string | null,\n data: unknown,\n): AssistantStreamEvent | null {\n const obj = asObject(data);\n if (!obj) return null;\n switch (event) {\n case \"thread\": {\n const threadId = reqStr(obj.threadId);\n const turnId = reqStr(obj.turnId);\n if (!threadId || !turnId) return null;\n return {\n type: \"thread\",\n data: { threadId, turnId, model: reqStr(obj.model) },\n };\n }\n case \"delta\": {\n // \"\" is a valid (if empty) delta, but a non-string is malformed.\n if (typeof obj.text !== \"string\") return null;\n return { type: \"delta\", data: { text: obj.text } };\n }\n case \"reasoning\": {\n if (typeof obj.text !== \"string\") return null;\n return { type: \"reasoning\", data: { text: obj.text } };\n }\n case \"tool_call\": {\n const callId = reqStr(obj.callId);\n const name = reqStr(obj.name);\n if (!callId || !name) return null;\n return { type: \"tool_call\", data: { callId, name } };\n }\n case \"tool_result\": {\n const callId = reqStr(obj.callId);\n const name = reqStr(obj.name);\n if (!callId || !name) return null;\n return {\n type: \"tool_result\",\n data: {\n callId,\n name,\n ok: Boolean(obj.ok),\n output: obj.output,\n error: obj.error as { code: string; message: string } | undefined,\n },\n };\n }\n case \"tool_proposal\": {\n const callId = reqStr(obj.callId);\n const name = reqStr(obj.name);\n if (!callId || !name) return null;\n return {\n type: \"tool_proposal\",\n data: {\n proposalId: obj.proposalId == null ? null : reqStr(obj.proposalId),\n callId,\n name,\n args: obj.args,\n requirements: parseRequirements(obj.requirements),\n },\n };\n }\n case \"usage\":\n return {\n type: \"usage\",\n data: {\n promptTokens: numOrNull(obj.promptTokens),\n completionTokens: numOrNull(obj.completionTokens),\n costUsd: numOrNull(obj.costUsd),\n balanceUsd: numOrNull(obj.balanceUsd),\n replayed: Boolean(obj.replayed),\n },\n };\n case \"done\": {\n const turnId = reqStr(obj.turnId);\n const status = reqStr(obj.status);\n if (!turnId || !status) return null;\n return {\n type: \"done\",\n data: {\n turnId,\n status,\n proposed: Boolean(obj.proposed),\n capped: Boolean(obj.capped),\n },\n };\n }\n case \"error\":\n return {\n type: \"error\",\n data: {\n code: reqStr(obj.code) ?? \"STREAM_FAILED\",\n message: reqStr(obj.message) ?? \"The assistant stream failed\",\n },\n };\n default:\n return null;\n }\n}\n\n/**\n * Read the JSON error body of a non-2xx chat response. Pre-stream failures\n * (auth, validation, insufficient balance, busy thread) are returned as\n * `{ success: false, error: { code, message } }` rather than an SSE stream.\n */\nasync function readErrorEvent(res: Response): Promise<AssistantStreamEvent> {\n try {\n const body = (await res.json()) as {\n error?: { code?: string; message?: string };\n };\n return {\n type: \"error\",\n data: {\n code: body.error?.code ?? `HTTP_${res.status}`,\n message: body.error?.message ?? `Request failed (${res.status})`,\n },\n };\n } catch {\n return {\n type: \"error\",\n data: {\n code: `HTTP_${res.status}`,\n message: `Request failed (${res.status})`,\n },\n };\n }\n}\n\n/** Parse one restored proposal from the history payload into a `PendingProposal`,\n * or null when the row is malformed (dropped rather than rendered as a broken\n * card). Mirrors the live `tool_proposal` event shape: a server-minted id, the\n * tool call id + name, the stored args, and — for an authoring proposal — the\n * freshly-recomputed connection requirements the card renders. */\nfunction parseRestoredProposal(raw: unknown): PendingProposal | null {\n if (!raw || typeof raw !== \"object\") return null;\n const r = raw as Record<string, unknown>;\n if (typeof r.proposalId !== \"string\" || r.proposalId === \"\") return null;\n if (typeof r.callId !== \"string\" || r.callId === \"\") return null;\n if (typeof r.name !== \"string\" || r.name === \"\") return null;\n // Validate each requirement element rather than casting the array wholesale —\n // a malformed entry would otherwise reach the card and throw at render.\n const requirements = Array.isArray(r.requirements)\n ? r.requirements\n .map(parseRequirement)\n .filter((x): x is ConnectionRequirement => x !== null)\n : undefined;\n return {\n proposalId: r.proposalId,\n callId: r.callId,\n name: r.name,\n args: r.args,\n ...(requirements ? { requirements } : {}),\n };\n}\n\n/**\n * Build an assistant client bound to one host's transport. The returned methods\n * carry no module state, so a host may create one client per config (or share a\n * single same-origin client for the whole app).\n */\nexport function createAssistantClient(\n config: AssistantClientConfig,\n): AssistantClient {\n const base = config.baseUrl.replace(/\\/+$/, \"\");\n const credentials: RequestCredentials = config.credentials ?? \"include\";\n const authHeaders = (): Record<string, string> => config.headers?.() ?? {};\n const url = (path: string): string => `${base}${path}`;\n\n /**\n * POST JSON and flatten the response to `{ success, data, error }`. On a\n * non-2xx the server's `{ error: { message } }` is collapsed to a string; on a\n * 2xx the body's `data` envelope is unwrapped when present.\n */\n async function postJson<T>(\n path: string,\n body: unknown,\n ): Promise<{ success: boolean; data?: T; error?: string }> {\n try {\n const res = await fetch(url(path), {\n method: \"POST\",\n headers: { ...authHeaders(), \"Content-Type\": \"application/json\" },\n credentials,\n body: JSON.stringify(body),\n });\n const json = (await res.json()) as {\n data?: T;\n error?: { message?: string };\n };\n if (!res.ok) {\n return {\n success: false,\n error: json?.error?.message || `HTTP ${res.status}`,\n };\n }\n return { success: true, data: (json?.data ?? json) as T };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : \"Request failed\",\n };\n }\n }\n\n return {\n async fetchModels(signal) {\n try {\n const res = await fetch(url(\"/models\"), {\n method: \"GET\",\n headers: authHeaders(),\n credentials,\n signal,\n });\n if (!res.ok) return { ok: false, data: EMPTY_MODELS };\n const body = (await res.json()) as {\n default?: unknown;\n models?: Array<{\n slug?: unknown;\n label?: unknown;\n promptUsdPerMillion?: unknown;\n contextTokens?: unknown;\n }>;\n };\n // A well-formed response must carry a models array; anything else is\n // treated as a failure so the caller retries rather than caching garbage.\n if (!Array.isArray(body.models))\n return { ok: false, data: EMPTY_MODELS };\n const models: AssistantModelOption[] = [];\n for (const m of body.models) {\n const slug = typeof m.slug === \"string\" ? m.slug : null;\n if (!slug) continue;\n const label = typeof m.label === \"string\" ? m.label : slug;\n const option: AssistantModelOption = { slug, label };\n if (typeof m.promptUsdPerMillion === \"number\") {\n option.promptUsdPerMillion = m.promptUsdPerMillion;\n }\n if (typeof m.contextTokens === \"number\") {\n option.contextTokens = m.contextTokens;\n }\n models.push(option);\n }\n return {\n // Empty ⇒ catalog unavailable: report not-ok so the caller retries next\n // mount instead of caching an empty picker for the session.\n ok: models.length > 0,\n data: {\n default: typeof body.default === \"string\" ? body.default : null,\n models,\n },\n };\n } catch {\n return { ok: false, data: EMPTY_MODELS };\n }\n },\n\n async fetchThreads(signal) {\n try {\n const res = await fetch(url(\"/threads\"), {\n method: \"GET\",\n headers: authHeaders(),\n credentials,\n signal,\n });\n if (!res.ok) return null;\n const body = (await res.json()) as {\n threads?: Array<{\n id?: unknown;\n title?: unknown;\n createdAt?: unknown;\n updatedAt?: unknown;\n }>;\n };\n if (!Array.isArray(body.threads)) return null;\n const out: AssistantThreadSummary[] = [];\n for (const t of body.threads) {\n const id = typeof t.id === \"string\" ? t.id : null;\n if (!id) continue;\n out.push({\n id,\n title: typeof t.title === \"string\" ? t.title : null,\n createdAt: typeof t.createdAt === \"string\" ? t.createdAt : \"\",\n updatedAt: typeof t.updatedAt === \"string\" ? t.updatedAt : \"\",\n });\n }\n return out;\n } catch {\n return null;\n }\n },\n\n async fetchThreadHistory(threadId, signal) {\n try {\n const res = await fetch(\n url(`/threads/${encodeURIComponent(threadId)}/messages`),\n {\n method: \"GET\",\n headers: authHeaders(),\n credentials,\n signal,\n },\n );\n // A 404 means the thread no longer exists — distinct from a transient\n // failure: the caller must drop the dead id rather than keep retrying it.\n if (res.status === 404) return { status: \"gone\" };\n if (!res.ok) return { status: \"error\" };\n const body = (await res.json()) as {\n messages?: Array<{ id?: unknown; role?: unknown; text?: unknown }>;\n proposals?: unknown[];\n };\n if (!Array.isArray(body.messages)) return { status: \"error\" };\n const out: ChatMessage[] = [];\n for (const m of body.messages) {\n const id = typeof m.id === \"string\" ? m.id : null;\n const role =\n m.role === \"user\" || m.role === \"assistant\" ? m.role : null;\n const text = typeof m.text === \"string\" ? m.text : null;\n // Skip a malformed row rather than coercing it into a blank bubble.\n if (id && role && text != null) out.push({ id, role, text });\n }\n // Restore unconfirmed proposals so the card survives reload. Absent on an\n // older server (or a non-tool deployment) → an empty list, no cards.\n const proposals: PendingProposal[] = [];\n if (Array.isArray(body.proposals)) {\n for (const p of body.proposals) {\n const parsed = parseRestoredProposal(p);\n if (parsed) proposals.push(parsed);\n }\n }\n return { status: \"ok\", messages: out, proposals };\n } catch {\n if (signal?.aborted) return { status: \"error\" };\n return { status: \"error\" };\n }\n },\n\n async streamChat(req, onEvent, signal) {\n // Raw fetch, not `postJson`: that helper reads `res.json()`, which would\n // consume the body and defeat streaming. CSRF is covered the same way as\n // every other authenticated POST in the app — a SameSite cookie plus the\n // configured `X-Requested-With` marker (in `authHeaders`) and Origin\n // validation server-side; this only adds the SSE `Accept`.\n const res = await fetch(url(\"/chat\"), {\n method: \"POST\",\n headers: {\n ...authHeaders(),\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n },\n credentials,\n body: JSON.stringify(req),\n signal,\n });\n\n if (!res.ok) {\n onEvent(await readErrorEvent(res));\n return;\n }\n if (!res.body) {\n onEvent({\n type: \"error\",\n data: {\n code: \"NO_BODY\",\n message: \"The assistant stream is unavailable\",\n },\n });\n return;\n }\n\n // A well-formed turn ends with a `done` (or, on failure, an `error`) frame.\n // If the body closes without one, settle anyway so the UI doesn't hang in\n // the streaming state forever.\n let settled = false;\n await readSSEEvents(res.body, (frame) => {\n const ev = toStreamEvent(frame.eventType ?? null, frame.data);\n if (!ev) return;\n if (ev.type === \"done\" || ev.type === \"error\") settled = true;\n onEvent(ev);\n });\n if (!settled) {\n onEvent({\n type: \"error\",\n data: {\n code: \"STREAM_CLOSED\",\n message: \"The assistant stream ended unexpectedly\",\n },\n });\n }\n },\n\n async confirmProposal(proposalId) {\n const res = await postJson<{\n success?: boolean;\n output?: unknown;\n retryable?: boolean;\n error?: { code: string; message: string };\n }>(\"/tools/execute\", { proposalId });\n\n // postJson reports transport/HTTP success in `success`; on a non-2xx it has\n // already flattened the server's error to a message string.\n if (!res.success) {\n return {\n ok: false,\n error: res.error ?? \"The action could not be completed\",\n };\n }\n const body = res.data;\n if (body?.success) {\n return { ok: true, output: body.output, retryable: body.retryable };\n }\n return {\n ok: false,\n error: body?.error?.message ?? \"The action could not be completed\",\n };\n },\n\n async deleteThread(threadId) {\n try {\n const res = await fetch(url(`/threads/${encodeURIComponent(threadId)}`), {\n method: \"DELETE\",\n headers: authHeaders(),\n credentials,\n });\n // 404 ⇒ already gone; treat as success so a retry/double-delete is a no-op.\n return { ok: res.ok || res.status === 404 };\n } catch {\n return { ok: false };\n }\n },\n };\n}\n","/**\n * Provides the configured {@link AssistantClient} to the assistant hooks. The\n * host (a same-origin app, or a cross-origin embedder) builds one client with\n * its transport config and supplies it here; the hooks read it rather than\n * importing a hard-wired same-origin transport, which is what makes the panel\n * portable across hosts.\n */\n\nimport { createContext, type ReactNode, useContext } from \"react\";\nimport type { AssistantClient } from \"./client\";\n\nconst AssistantClientContext = createContext<AssistantClient | null>(null);\n\nexport function AssistantClientProvider({\n client,\n children,\n}: {\n client: AssistantClient;\n children: ReactNode;\n}) {\n return (\n <AssistantClientContext.Provider value={client}>\n {children}\n </AssistantClientContext.Provider>\n );\n}\n\n/**\n * The assistant client for the current host. Throws when no provider is mounted\n * rather than silently falling back to a default transport — a missing provider\n * is a wiring bug, and a hidden default would mask it (and could target the\n * wrong origin).\n */\nexport function useAssistantClient(): AssistantClient {\n const client = useContext(AssistantClientContext);\n if (!client) {\n throw new Error(\n \"useAssistantClient must be used within an <AssistantClientProvider>\",\n );\n }\n return client;\n}\n","/**\n * React binding for the assistant panel: owns the reducer, drives the chat SSE\n * stream and the proposal-confirmation call, and persists the thread across\n * reloads. All rendering decisions live in the pure reducer + presentation\n * helpers; this hook is the glue between them and the network.\n */\n\nimport { useCallback, useEffect, useReducer, useRef, useState } from \"react\";\nimport { useAssistantClient } from \"./client-context\";\nimport { loadThread, saveThread } from \"./persistence\";\nimport { resolveConfirmation } from \"./presentation\";\nimport {\n type AssistantState,\n assistantReducer,\n initialAssistantState,\n selectVisibleState,\n} from \"./reducer\";\nimport type { ChatMessage, PendingProposal } from \"./types\";\n\n/** Host integration callbacks for {@link useAssistantChat}. */\nexport interface UseAssistantChatOptions {\n /**\n * Called after a workflow-mutating tool (`create_workflow`, `author_workflow`,\n * …) is confirmed successfully — the host re-fetches its workflow list so the\n * result appears without a manual reload. Replaces the in-app cross-module\n * signal the platform used.\n */\n onWorkflowMutation?: () => void;\n}\n\nconst EMPTY_IDS: ReadonlySet<string> = new Set();\n\n/** Confirmed tools whose success changes the caller's workflow set — on success\n * the Workflows page is signaled to refetch so the result appears without a\n * manual reload. */\nconst WORKFLOW_MUTATING_TOOLS: ReadonlySet<string> = new Set([\n \"create_workflow\",\n \"author_workflow\",\n \"update_workflow\",\n \"set_workflow_enabled\",\n]);\n\nfunction statusMessage(text: string): ChatMessage {\n return { id: `status-${uuid()}`, role: \"status\", text };\n}\n\nfunction uuid(): string {\n return crypto.randomUUID();\n}\n\nexport interface AssistantChat {\n state: AssistantState;\n /** Proposal ids whose confirmation is currently in flight (for disabling). */\n confirmingIds: ReadonlySet<string>;\n /** The user's selected model slug, or null to use the server default. */\n selectedModel: string | null;\n /** Choose the model for subsequent turns (persisted per user). */\n setModel: (model: string | null) => void;\n send: (message: string) => void;\n stop: () => void;\n confirm: (proposal: PendingProposal) => Promise<void>;\n cancel: (proposal: PendingProposal) => void;\n reset: () => void;\n /** Open an existing thread from history, loading its transcript. */\n switchThread: (threadId: string) => void;\n /** True while a switched-to thread's transcript is loading — the composer is\n * held closed until it resolves so a turn can't run against hidden context. */\n restoring: boolean;\n}\n\nexport function useAssistantChat(\n userId: string | null,\n options?: UseAssistantChatOptions,\n): AssistantChat {\n const [state, dispatch] = useReducer(\n assistantReducer,\n userId,\n (uid): AssistantState => {\n return {\n ...initialAssistantState(),\n ownerId: uid,\n threadId: loadThread(uid).threadId,\n };\n },\n );\n\n const abortRef = useRef<AbortController | null>(null);\n // Aborts an in-flight thread-history restore when the user changes or the\n // panel unmounts, so a late response can't land in a different conversation.\n const historyAbortRef = useRef<AbortController | null>(null);\n // Records which userId the state has already been hydrated for, so the\n // user-change effect fires exactly once per switch. Persistence does NOT key\n // off this — it keys off `state.ownerId`, which moves atomically with the data.\n const hydratedUserRef = useRef<string | null>(userId);\n // Monotonic token identifying the authoritative stream. Aborting is async, so\n // a superseded stream can still have buffered events in flight; each stream\n // captures its token at start and its callbacks no-op once the token moves on.\n // This is what stops a prior user's late events from landing in a new user's\n // hydrated state (a cross-account leak that abort alone cannot prevent).\n const streamSeqRef = useRef(0);\n // Same idea for the (non-abortable) confirmation request: it captures this\n // token before awaiting and no-ops once the conversation it belonged to has\n // been replaced by a user switch or reset, so a late confirmation response\n // cannot append into a different user's hydrated thread.\n const confirmSeqRef = useRef(0);\n // Latest state + current userId, readable from event-handler closures without\n // re-creating them. Written during render (not a passive effect) so the owner\n // guard in `send`/`confirm` sees the new user on the very commit after an auth\n // change — a passive effect would lag by a frame, leaving a window where a send\n // could still target the prior user's thread.\n //\n // These refs are read only in event handlers, which fire after a commit (by\n // which point React has committed the latest render and updated them), so they\n // reflect committed state at read time. A render thrown away under concurrent\n // mode could momentarily leave a ref pointing at uncommitted state, but the\n // failure degrades safely: the owner guard (`ownerId !== userIdRef.current`)\n // would at worst DROP a send — never leak — and a mismatched thread id is\n // rejected server-side (404) regardless.\n const stateRef = useRef(state);\n const userIdRef = useRef(userId);\n stateRef.current = state;\n userIdRef.current = userId;\n\n // The transport, held in a ref so the event-handler callbacks (send/confirm/\n // switchThread) keep stable identities while still reaching the current client.\n const client = useAssistantClient();\n const clientRef = useRef(client);\n clientRef.current = client;\n\n // Host callbacks held in a ref so the event-handler callbacks below keep\n // stable identities while still calling the latest-supplied handler.\n const onWorkflowMutationRef = useRef(options?.onWorkflowMutation);\n onWorkflowMutationRef.current = options?.onWorkflowMutation;\n\n // Proposal ids whose confirmation request is in flight. The ref is the\n // synchronous guard against a double-click issuing a duplicate execute; the\n // state mirror drives disabling the card's buttons.\n const confirmingRef = useRef<Set<string>>(new Set());\n const [confirmingIds, setConfirmingIds] =\n useState<ReadonlySet<string>>(EMPTY_IDS);\n\n // Synchronous \"a chat request is in flight\" guard. Set before the fetch and\n // cleared when it settles/stops/resets, so two submits in the same tick can't\n // both pass the status check and start two billable streams.\n const sendingRef = useRef(false);\n\n // True while a thread opened from history is loading its transcript. The\n // composer is disabled until it resolves so a send can't run a turn against\n // server-side context the user can't yet see. The ref is the synchronous guard\n // for `send`; the state drives the disabled composer. Only `switchThread` sets\n // it (the mount restore resumes the user's own current thread).\n const restoringRef = useRef(false);\n const [restoring, setRestoring] = useState(false);\n const setRestoringBoth = useCallback((v: boolean) => {\n restoringRef.current = v;\n setRestoring(v);\n }, []);\n\n // The user's selected model (a per-user preference, persisted alongside the\n // thread id). A ref mirror lets `send` read the current choice without being\n // re-created. null → the server's default model.\n const [selectedModel, setSelectedModel] = useState<string | null>(\n () => loadThread(userId).model,\n );\n const selectedModelRef = useRef(selectedModel);\n selectedModelRef.current = selectedModel;\n\n // When the signed-in user changes under a mounted panel (auth refresh, or a\n // mount before auth resolved), invalidate the in-flight stream, abort it, and\n // reload that user's own thread. Without this, the prior user's transcript\n // would persist under the new user's key — a cross-account leak.\n useEffect(() => {\n if (hydratedUserRef.current === userId) return;\n streamSeqRef.current += 1;\n confirmSeqRef.current += 1;\n abortRef.current?.abort();\n // Abort a pending thread-history load and re-open the composer so a switch\n // that was mid-restore for the prior user doesn't leave the new user's\n // conversation wedged closed.\n historyAbortRef.current?.abort();\n setRestoringBoth(false);\n sendingRef.current = false;\n confirmingRef.current.clear();\n setConfirmingIds(EMPTY_IDS);\n hydratedUserRef.current = userId;\n // Load the new user's own model preference (the prior user's must not carry\n // over). The ref is updated synchronously so a send on this commit reads it.\n const nextModel = loadThread(userId).model;\n selectedModelRef.current = nextModel;\n setSelectedModel(nextModel);\n dispatch({\n type: \"hydrate\",\n ownerId: userId,\n threadId: loadThread(userId).threadId,\n messages: [],\n });\n }, [userId, setRestoringBoth]);\n\n // Restore the visible transcript for the persisted thread from the server,\n // keyed by user (runs on mount and whenever the signed-in user changes). The\n // transcript is never cached client-side — it can carry workflow YAML or\n // pasted secrets, and localStorage survives logout — so a reload starts blank\n // and the prior conversation is rehydrated here from the durable thread.\n // The reducer applies it only if the conversation is still idle/empty for this\n // exact owner+thread, so a late response can never clobber a started turn, a\n // new chat, or a switched account.\n useEffect(() => {\n const threadId = loadThread(userId).threadId;\n if (!userId || !threadId) return;\n const ac = new AbortController();\n historyAbortRef.current?.abort();\n historyAbortRef.current = ac;\n void clientRef.current\n .fetchThreadHistory(threadId, ac.signal)\n .then((result) => {\n if (ac.signal.aborted) return;\n if (result.status === \"ok\") {\n // An existing thread with no completed turns AND no pending proposals\n // yields nothing to restore — keep the thread id (it's live). A pending\n // proposal alone is enough to restore (its card must come back).\n if (result.messages.length > 0 || result.proposals.length > 0) {\n dispatch({\n type: \"restore_history\",\n ownerId: userId,\n threadId,\n messages: result.messages,\n proposals: result.proposals,\n });\n }\n } else if (result.status === \"gone\") {\n // The thread was deleted server-side (404). Drop the dead id so the next\n // send starts fresh instead of 404-ing forever. The reducer setting\n // threadId to null cascades to the persistence effect, clearing storage.\n dispatch({ type: \"thread_gone\", ownerId: userId, threadId });\n }\n // status === \"error\" → transient; keep the thread id and don't restore.\n })\n .catch(() => {\n // fetchThreadHistory returns a typed {status:\"error\"} rather than\n // rejecting; guard a future change from leaking an unhandled rejection.\n });\n return () => ac.abort();\n }, [userId]);\n\n // Persist thread id + transcript whenever the conversation settles, under the\n // owner carried in state. Because `state.ownerId` and the data it labels move\n // together (set atomically by the reducer), a write can never land under a\n // different user's key. Skipped while streaming: `state.messages` gets a fresh\n // reference on every delta, so persisting per-delta would hammer localStorage;\n // the turn is saved once it settles (status leaves \"streaming\").\n useEffect(() => {\n if (state.status === \"streaming\") return;\n // Skip while the data's owner doesn't match the live user — the window\n // between an auth change and the hydrate that follows it. `selectedModelRef`\n // lives outside the reducer, so it can already hold the NEW user's preference\n // while `state.ownerId` still holds the OLD user's id; persisting then would\n // write the new user's model under the old user's key. The owner guard closes\n // that cross-account write, matching the same pattern `send`/`confirm` use.\n if (state.ownerId !== userIdRef.current) return;\n saveThread(state.ownerId, {\n threadId: state.threadId,\n model: selectedModelRef.current,\n });\n // `selectedModel` is intentionally NOT a dependency: a model-only change is\n // persisted immediately by `setModel` itself, so this effect only needs to\n // re-run on the conversation transitions above. If that direct persist is\n // ever removed, add `selectedModel` here.\n }, [state.ownerId, state.threadId, state.status]);\n\n // Abort any in-flight stream on unmount so a closed panel stops billing.\n useEffect(() => {\n return () => abortRef.current?.abort();\n }, []);\n\n const send = useCallback((message: string) => {\n // Synchronous in-flight guard — closes the window where two submits in the\n // same tick both read a not-yet-committed `idle` status and start two\n // billable streams.\n if (sendingRef.current) return;\n const text = message.trim();\n const current = stateRef.current;\n // Refuse if the loaded conversation doesn't belong to the current user. This\n // only differs for the brief committed frame between an auth change and the\n // hydrate that follows it, where `current` still holds the prior user's\n // thread — sending then would attach this message to that thread id.\n if (current.ownerId !== userIdRef.current) return;\n // Refuse while a switched-to thread's transcript is still loading — sending\n // now would run a turn against context the user can't see yet.\n if (restoringRef.current) return;\n // Refuse while a turn is streaming, or while a mutating proposal is awaiting\n // the user's decision — a new turn must not abandon an unresolved proposal.\n if (!text || current.status === \"streaming\") return;\n if (current.pendingProposals.length > 0) return;\n\n dispatch({\n type: \"send\",\n messageId: uuid(),\n assistantId: uuid(),\n text,\n });\n\n const seq = ++streamSeqRef.current;\n const ac = new AbortController();\n abortRef.current = ac;\n sendingRef.current = true;\n clientRef.current\n .streamChat(\n {\n message: text,\n // null → omit, so the server applies its default model.\n model: selectedModelRef.current ?? undefined,\n threadId: current.threadId ?? undefined,\n turnKey: uuid(),\n },\n (event) => {\n // Drop events from a stream that has been superseded (new turn, stop,\n // reset, or user switch) so they cannot mutate unrelated state.\n if (streamSeqRef.current === seq) dispatch({ type: \"stream\", event });\n },\n ac.signal,\n )\n .catch((err: unknown) => {\n // A user-initiated abort is reported via the `stopped` action, not as a\n // failure; a superseded stream is ignored entirely. Only surface a\n // genuine network/parse error for the still-current stream.\n if (ac.signal.aborted || streamSeqRef.current !== seq) return;\n dispatch({\n type: \"stream_failed\",\n error: {\n code: \"NETWORK\",\n message:\n err instanceof Error ? err.message : \"The connection failed\",\n },\n });\n })\n .finally(() => {\n // This stream is over (settled, failed, or aborted) — allow the next\n // send. A superseded stream clearing the flag is harmless: a newer send\n // already set its own.\n if (streamSeqRef.current === seq) sendingRef.current = false;\n });\n }, []);\n\n const stop = useCallback(() => {\n streamSeqRef.current += 1;\n abortRef.current?.abort();\n sendingRef.current = false;\n dispatch({ type: \"stopped\" });\n }, []);\n\n const confirm = useCallback(async (proposal: PendingProposal) => {\n const pid = proposal.proposalId;\n if (!pid) {\n dispatch({\n type: \"proposal_resolved\",\n callId: proposal.callId,\n status: null,\n error: {\n code: \"TOOL_FAILED\",\n message: \"This action can no longer be confirmed.\",\n },\n });\n return;\n }\n\n // Guard against a double-click issuing a duplicate execute for the same\n // proposal — the second would otherwise hit PROPOSAL_ALREADY_CONSUMED and\n // overwrite the first's success with an error.\n if (confirmingRef.current.has(pid)) return;\n confirmingRef.current.add(pid);\n setConfirmingIds(new Set(confirmingRef.current));\n\n // Snapshot the conversation generation; if a user switch or reset replaces\n // the conversation while this request is in flight, the late response must\n // not land in the new conversation.\n const seq = confirmSeqRef.current;\n try {\n const result = await clientRef.current.confirmProposal(pid);\n if (confirmSeqRef.current !== seq) return;\n // A RETRYABLE failure (the workflow references an integration that isn't\n // connected): the server re-opened the proposal, so KEEP the card and show\n // the reason on it. The user connects the integration (the card's Connect\n // button) and confirms again — no need to re-ask the assistant.\n if (result.ok && result.retryable) {\n const { error } = resolveConfirmation(proposal.name, result);\n dispatch({\n type: \"proposal_retry_failed\",\n callId: proposal.callId,\n message:\n error?.message ??\n \"Connect the required integration, then confirm again.\",\n });\n return;\n }\n const { statusText, error } = resolveConfirmation(proposal.name, result);\n dispatch({\n type: \"proposal_resolved\",\n callId: proposal.callId,\n status: statusText ? statusMessage(statusText) : null,\n error,\n });\n // A successful workflow mutation won't show on an already-open Workflows\n // page (it fetches on mount and shares no cache) — signal it to refetch.\n // `error === null` is the clean-success signal from resolveConfirmation.\n if (!error && WORKFLOW_MUTATING_TOOLS.has(proposal.name)) {\n onWorkflowMutationRef.current?.();\n }\n } catch (err) {\n if (confirmSeqRef.current !== seq) return;\n // confirmProposal returns a typed outcome rather than throwing, but guard\n // anyway: an unexpected throw must still clear the card and surface an\n // error instead of escaping as an unhandled rejection.\n dispatch({\n type: \"proposal_resolved\",\n callId: proposal.callId,\n status: null,\n error: {\n code: \"TOOL_FAILED\",\n message:\n err instanceof Error\n ? err.message\n : \"The action could not be completed\",\n },\n });\n } finally {\n // Only touch the in-flight set if this confirmation still owns the\n // conversation; a switch/reset already cleared it.\n if (confirmSeqRef.current === seq) {\n confirmingRef.current.delete(pid);\n setConfirmingIds(new Set(confirmingRef.current));\n }\n }\n }, []);\n\n const cancel = useCallback((proposal: PendingProposal) => {\n // Once a confirmation is in flight the action is already running server-side\n // and can't be cancelled — ignore a cancel click that races it, so the\n // transcript can't show both \"cancelled\" and the action's success.\n if (proposal.proposalId && confirmingRef.current.has(proposal.proposalId)) {\n return;\n }\n dispatch({\n type: \"proposal_resolved\",\n callId: proposal.callId,\n status: statusMessage(\"Action cancelled.\"),\n error: null,\n });\n }, []);\n\n const reset = useCallback(() => {\n streamSeqRef.current += 1;\n confirmSeqRef.current += 1;\n abortRef.current?.abort();\n historyAbortRef.current?.abort();\n setRestoringBoth(false);\n sendingRef.current = false;\n confirmingRef.current.clear();\n setConfirmingIds(EMPTY_IDS);\n dispatch({ type: \"reset\" });\n }, [setRestoringBoth]);\n\n // Open a past thread from the history switcher: invalidate any in-flight\n // stream/confirmation (so their late events can't land in the switched-to\n // conversation), pin the chosen thread, persist it, then load its transcript.\n // Guarded by owner so a stale render can't switch under a different account.\n const switchThread = useCallback(\n (threadId: string) => {\n const current = stateRef.current;\n const uid = userIdRef.current;\n if (current.ownerId !== uid) return;\n if (threadId === current.threadId) return;\n // Refuse while a turn is streaming or a proposal is awaiting confirmation —\n // the same guard `send` uses. Navigating away would abandon the live turn /\n // unresolved proposal; the user finishes it, or \"New chat\" is the explicit\n // discard. The DISABLED switcher in the UI is the primary guard; this is the\n // backstop, so it silently no-ops by design for a programmatic/keyboard\n // caller that bypasses the disabled state.\n if (\n current.status === \"streaming\" ||\n current.pendingProposals.length > 0\n ) {\n return;\n }\n streamSeqRef.current += 1;\n confirmSeqRef.current += 1;\n abortRef.current?.abort();\n sendingRef.current = false;\n confirmingRef.current.clear();\n setConfirmingIds(EMPTY_IDS);\n dispatch({ type: \"switch_thread\", threadId });\n // Hold the composer closed until the transcript loads — a send before then\n // would run a turn against context the user can't yet see.\n setRestoringBoth(true);\n // Persist the new active thread now so a reload before the next turn restores\n // it (the settled-save effect also covers it). Keep the current model choice.\n saveThread(uid, { threadId, model: selectedModelRef.current });\n // Load the chosen thread's transcript, superseding any in-flight history\n // fetch. restore_history only applies on a matching idle owner+thread, so a\n // mid-load send or a second switch can't be clobbered by this response.\n const ac = new AbortController();\n historyAbortRef.current?.abort();\n historyAbortRef.current = ac;\n void clientRef.current\n .fetchThreadHistory(threadId, ac.signal)\n .then((result) => {\n if (ac.signal.aborted) return;\n if (result.status === \"ok\") {\n if (result.messages.length > 0 || result.proposals.length > 0) {\n dispatch({\n type: \"restore_history\",\n ownerId: uid,\n threadId,\n messages: result.messages,\n proposals: result.proposals,\n });\n }\n } else if (result.status === \"gone\") {\n dispatch({ type: \"thread_gone\", ownerId: uid, threadId });\n } else {\n // Transient load failure: drop the active thread (it carries\n // server-side context the user can't see) and surface a visible\n // error. The next send then starts a FRESH thread rather than running\n // against the unloaded conversation's hidden context.\n dispatch({\n type: \"history_failed\",\n ownerId: uid,\n threadId,\n error: {\n code: \"HISTORY_LOAD_FAILED\",\n message:\n \"Couldn't load that conversation. You're in a new chat — reopen it from history to try again.\",\n },\n });\n }\n // Re-open the composer once the load settles (success shows the\n // transcript; failure dropped the thread + showed the error above).\n // Only the current fetch clears it — a superseded one returned early on\n // `aborted`.\n setRestoringBoth(false);\n })\n .catch(() => {\n // fetchThreadHistory returns a typed {status:\"error\"} rather than\n // rejecting; guard a future change from leaking an unhandled rejection.\n if (!ac.signal.aborted) setRestoringBoth(false);\n });\n },\n [setRestoringBoth],\n );\n\n // Choose the model for subsequent turns. The model preference is per user, not\n // per turn, so persist it immediately (alongside the current thread id) rather\n // than waiting for a turn to settle.\n const setModel = useCallback((model: string | null) => {\n selectedModelRef.current = model;\n setSelectedModel(model);\n // Same owner guard the persistence effect uses: in the window between an auth\n // change and the hydrate that follows it, stateRef still holds the prior\n // user's thread while userIdRef already points to the new user. Persisting\n // then would write the new user's model (and the OLD user's thread id) under\n // a mismatched key. Skip it — the state still updates, and the next settled\n // save persists it once the owner is consistent.\n const currentUserId = userIdRef.current;\n if (stateRef.current.ownerId === currentUserId) {\n saveThread(currentUserId, {\n threadId: stateRef.current.threadId,\n model,\n });\n }\n }, []);\n\n // Reactive recovery: if a turn is rejected because the selected model is no\n // longer offered (e.g. ASSISTANT_MODELS was changed and a stale slug was still\n // persisted), clear the selection so the NEXT send falls back to the server\n // default instead of failing again. The picker also reconciles proactively\n // once the model list loads; this closes the window where a send races that\n // load. Guarded by owner so a late error from a prior user can't act here.\n useEffect(() => {\n if (\n state.error?.code === \"MODEL_NOT_ALLOWED\" &&\n state.ownerId === userIdRef.current &&\n selectedModelRef.current !== null\n ) {\n setModel(null);\n }\n }, [state.error, state.ownerId, setModel]);\n\n // Expose only state that belongs to the current user: between an auth change\n // and the hydrate effect that follows it, the raw state still holds the prior\n // user's conversation, which must never render. Operations (send/confirm) read\n // the raw state via `stateRef`, so they still act on the true thread.\n const visibleState = selectVisibleState(state, userId);\n return {\n state: visibleState,\n confirmingIds,\n selectedModel,\n setModel,\n send,\n stop,\n confirm,\n cancel,\n reset,\n switchThread,\n restoring,\n };\n}\n","/**\n * Thread-id persistence for the assistant panel. Only the opaque server thread\n * id is kept in localStorage so a reload continues the same server-side\n * conversation. The message transcript is deliberately NOT cached: it can\n * contain workflow definitions, integration data, or pasted secrets, and\n * localStorage survives logout and is readable by any script on the origin —\n * caching it would be a privacy regression on shared devices.\n *\n * The visible transcript is instead rehydrated from the server on load (the\n * `GET /assistant/threads/:id/messages` endpoint, called by `useAssistantChat`),\n * keyed by this persisted thread id — so a reload restores the prior\n * conversation without ever caching its contents locally.\n *\n * Keyed by user id so two accounts on one browser never share a thread.\n * Anonymous (null-user) sessions are NOT persisted — otherwise every\n * unauthenticated visitor on a shared device would read the same \"anon\" thread.\n */\n\nconst VERSION = \"v1\";\n\nexport interface PersistedThread {\n threadId: string | null;\n /** The user's last-selected model slug — a non-sensitive UI preference, so\n * unlike the transcript it is safe to cache. null → use the server default. */\n model: string | null;\n}\n\n/** Storage key for a signed-in user, or null for an anonymous session (which is\n * never persisted). */\nfunction keyFor(userId: string | null): string | null {\n return userId ? `assistant:${VERSION}:${userId}` : null;\n}\n\nexport function loadThread(userId: string | null): PersistedThread {\n const key = keyFor(userId);\n if (!key) return { threadId: null, model: null };\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return { threadId: null, model: null };\n const parsed = JSON.parse(raw) as Partial<PersistedThread>;\n return {\n threadId: typeof parsed.threadId === \"string\" ? parsed.threadId : null,\n model: typeof parsed.model === \"string\" ? parsed.model : null,\n };\n } catch {\n return { threadId: null, model: null };\n }\n}\n\nexport function saveThread(\n userId: string | null,\n thread: PersistedThread,\n): void {\n const key = keyFor(userId);\n if (!key) return;\n try {\n localStorage.setItem(\n key,\n JSON.stringify({ threadId: thread.threadId, model: thread.model }),\n );\n } catch {\n // Storage unavailable (private mode, quota) — persistence is best-effort.\n }\n}\n","/**\n * Pure view-model mappers for the assistant panel: how an error code becomes an\n * inline message + actionable next step, and how a proposed tool call becomes a\n * confirmation card. Kept free of React so the rendering decisions are unit\n * testable in isolation.\n */\n\nimport type { PendingProposal } from \"./types\";\n\n/** USD balance below which the panel surfaces a low-balance warning. Mirrors\n * the wallet warning threshold on the Billing page. */\nexport const LOW_BALANCE_THRESHOLD = 1;\n\nexport interface ErrorCta {\n label: string;\n to: string;\n}\n\nexport interface ErrorView {\n message: string;\n cta: ErrorCta | null;\n}\n\nconst ADD_CREDITS_CTA: ErrorCta = { label: \"Add credits\", to: \"/app/billing\" };\nconst CONNECT_CTA: ErrorCta = {\n label: \"Connect an integration\",\n to: \"/app/integrations\",\n};\n\n/**\n * Map a server error code + message to what the user sees and can do next.\n * Codes with a clear remedy carry a CTA; the rest fall back to the server's own\n * message, which is already written for the end user.\n */\nexport function presentError(code: string, message: string): ErrorView {\n switch (code) {\n case \"INSUFFICIENT_BALANCE\":\n return {\n message:\n \"You're out of credits. Add credits to keep using the assistant.\",\n cta: ADD_CREDITS_CTA,\n };\n case \"MODEL_ACCESS_UNCONFIGURED\":\n return {\n message:\n \"Model access isn't configured for your account yet. Please contact support.\",\n cta: null,\n };\n case \"BILLING_UNAVAILABLE\":\n return {\n message: \"Billing is temporarily unavailable. Try again in a moment.\",\n cta: null,\n };\n case \"TOO_MANY_STREAMS\":\n return {\n message:\n \"You have too many assistant requests in flight. Wait a moment and retry.\",\n cta: null,\n };\n case \"THREAD_BUSY\":\n case \"TURN_IN_PROGRESS\":\n return {\n message: \"A previous request is still finishing. Try again shortly.\",\n cta: null,\n };\n case \"INTEGRATION_DISCONNECTED\":\n return {\n message: `${message} Connect the integration, then ask again.`,\n cta: CONNECT_CTA,\n };\n case \"TOOL_FAILED\":\n case \"NETWORK\":\n return { message: message || \"Something went wrong.\", cta: null };\n default:\n return { message: message || \"Something went wrong.\", cta: null };\n }\n}\n\nexport interface ProposalField {\n label: string;\n value: string;\n}\n\n/** A new skill minted alongside a workflow, shown as a named line on the card\n * so the user sees what's being created without the raw skills JSON. */\nexport interface ProposalSkill {\n name: string;\n description: string | null;\n}\n\nexport interface ProposalView {\n /** Verb-first heading, e.g. \"Create workflow\". */\n title: string;\n /** A body preview with its own label — a workflow's YAML (`kind: \"workflow\"`,\n * rendered as a node graph with a YAML toggle) or a skill's instructions\n * (`kind: \"text\"`, shown verbatim). Null when the action has no body. */\n preview: { label: string; content: string; kind: \"workflow\" | \"text\" } | null;\n /** Scalar arguments to show as a key/value list. */\n fields: ProposalField[];\n /** New skills minted alongside a workflow (author_workflow); omitted otherwise. */\n skills?: ProposalSkill[];\n}\n\nfunction asRecord(args: unknown): Record<string, unknown> {\n return args && typeof args === \"object\" && !Array.isArray(args)\n ? (args as Record<string, unknown>)\n : {};\n}\n\nfunction str(v: unknown): string {\n if (v == null) return \"\";\n return typeof v === \"string\" ? v : JSON.stringify(v);\n}\n\n/** A non-empty string value, else null — so an empty/absent body yields no\n * (empty) preview rather than a blank monospace block. */\nfunction nonEmptyStr(v: unknown): string | null {\n return typeof v === \"string\" && v.trim() !== \"\" ? v : null;\n}\n\n/** Map an author_workflow `skills` arg to display lines, dropping malformed\n * entries. Returns undefined when there are no new skills to show. */\nfunction parseProposalSkills(v: unknown): ProposalSkill[] | undefined {\n if (!Array.isArray(v) || v.length === 0) return undefined;\n const out: ProposalSkill[] = [];\n for (const item of v) {\n const rec = asRecord(item);\n const name = nonEmptyStr(rec.name);\n if (!name) continue;\n out.push({ name, description: nonEmptyStr(rec.description) });\n }\n return out.length > 0 ? out : undefined;\n}\n\n/** Humanize an unknown tool name (`set_workflow_enabled` → \"Set workflow enabled\"). */\nexport function humanizeToolName(name: string): string {\n const spaced = name.replace(/_/g, \" \").trim();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\n/** Present-tense labels for the inline tool-activity chips (\"Validating\n * workflow…\"). Falls back to a humanized tool name for any unmapped tool, so a\n * newly added read-only tool still renders a sensible label. */\nconst TOOL_ACTIVITY_LABELS: Record<string, string> = {\n get_workflow_schema: \"Reading the workflow format\",\n list_workflows: \"Listing workflows\",\n get_workflow: \"Reading workflow\",\n validate_workflow: \"Validating workflow\",\n list_skills: \"Listing skills\",\n get_skill: \"Reading skill\",\n list_integrations: \"Checking integrations\",\n get_credit_balance: \"Checking balance\",\n get_usage: \"Checking usage\",\n list_api_keys: \"Listing API keys\",\n};\n\nexport function describeToolActivity(name: string): string {\n return TOOL_ACTIVITY_LABELS[name] ?? humanizeToolName(name);\n}\n\n/**\n * Describe a proposed mutating action for its confirmation card. Workflow\n * create/update surface a YAML preview (the issue's required behavior); other\n * actions surface their scalar arguments. Unknown tools fall back to a generic\n * heading plus a JSON dump of the arguments so a newly added mutating tool is\n * never silently un-renderable.\n */\nexport function describeProposal(proposal: PendingProposal): ProposalView {\n const args = asRecord(proposal.args);\n const workflowYaml = nonEmptyStr(args.yaml);\n const workflowPreview = workflowYaml\n ? {\n label: \"Workflow definition\",\n content: workflowYaml,\n kind: \"workflow\" as const,\n }\n : null;\n switch (proposal.name) {\n case \"create_workflow\":\n return { title: \"Create workflow\", preview: workflowPreview, fields: [] };\n // author_workflow creates a workflow PLUS the new skills it needs in one\n // unit; show the YAML and name each new skill rather than dumping the raw\n // skills JSON (the card is the canonical, readable view of the proposal).\n case \"author_workflow\":\n return {\n title: \"Create workflow\",\n preview: workflowPreview,\n fields: [],\n skills: parseProposalSkills(args.skills),\n };\n case \"update_workflow\":\n return {\n title: \"Update workflow\",\n preview: workflowPreview,\n fields: [{ label: \"Workflow id\", value: str(args.id) }],\n };\n case \"set_workflow_enabled\":\n return {\n title: args.enabled ? \"Enable workflow\" : \"Disable workflow\",\n preview: null,\n fields: [{ label: \"Workflow id\", value: str(args.id) }],\n };\n case \"create_skill\": {\n const prompt = nonEmptyStr(args.systemPrompt);\n const fields: ProposalField[] = [\n { label: \"Name\", value: str(args.name) },\n ];\n if (nonEmptyStr(args.description))\n fields.push({ label: \"Description\", value: str(args.description) });\n return {\n title: \"Create skill\",\n preview: prompt\n ? { label: \"Instructions\", content: prompt, kind: \"text\" as const }\n : null,\n fields,\n };\n }\n case \"update_skill\": {\n const prompt = nonEmptyStr(args.systemPrompt);\n const fields: ProposalField[] = [\n { label: \"Skill id\", value: str(args.id) },\n ];\n if (nonEmptyStr(args.name))\n fields.push({ label: \"Name\", value: str(args.name) });\n if (nonEmptyStr(args.description))\n fields.push({ label: \"Description\", value: str(args.description) });\n return {\n title: \"Update skill\",\n preview: prompt\n ? { label: \"Instructions\", content: prompt, kind: \"text\" as const }\n : null,\n fields,\n };\n }\n case \"delete_skill\":\n return {\n title: \"Delete skill\",\n preview: null,\n fields: [{ label: \"Skill id\", value: str(args.id) }],\n };\n case \"create_api_key\": {\n const fields: ProposalField[] = [\n { label: \"Name\", value: str(args.name) },\n ];\n if (args.product != null)\n fields.push({ label: \"Product\", value: str(args.product) });\n if (args.budgetUsd != null)\n fields.push({ label: \"Budget (USD)\", value: str(args.budgetUsd) });\n return { title: \"Create API key\", preview: null, fields };\n }\n case \"revoke_api_key\":\n return {\n title: \"Revoke API key\",\n preview: null,\n fields: [{ label: \"Key id\", value: str(args.keyId) }],\n };\n case \"invoke_integration\": {\n const fields: ProposalField[] = [\n { label: \"Action\", value: str(args.path) },\n ];\n if (args.input != null)\n fields.push({ label: \"Input\", value: str(args.input) });\n return { title: \"Run integration action\", preview: null, fields };\n }\n default: {\n const fields = Object.entries(args).map(([label, value]) => ({\n label,\n value: str(value),\n }));\n return { title: humanizeToolName(proposal.name), preview: null, fields };\n }\n }\n}\n\n/**\n * Summarize a confirmed action's result for the transcript. Best-effort and\n * defensive: the output shape is the tool's return value, which varies by tool.\n */\nexport function describeOutcome(name: string, output: unknown): string {\n const o = asRecord(output);\n switch (name) {\n case \"author_workflow\":\n case \"create_workflow\": {\n const wf = asRecord(o.workflow);\n const skillCount = Array.isArray(o.skills) ? o.skills.length : 0;\n if (wf.name) {\n return skillCount > 0\n ? `Created workflow \"${str(wf.name)}\" and ${skillCount} skill${skillCount === 1 ? \"\" : \"s\"}.`\n : `Created workflow \"${str(wf.name)}\".`;\n }\n return \"Workflow created.\";\n }\n case \"update_workflow\": {\n const wf = asRecord(o.workflow);\n return wf.name\n ? `Updated workflow \"${str(wf.name)}\".`\n : \"Workflow updated.\";\n }\n case \"set_workflow_enabled\": {\n const wf = asRecord(o.workflow);\n return wf.enabled ? \"Workflow enabled.\" : \"Workflow disabled.\";\n }\n case \"create_api_key\":\n return o.prefix\n ? `Created API key (${str(o.prefix)}…). Copy it from the API Keys page.`\n : \"API key created.\";\n case \"revoke_api_key\":\n return \"API key revoked.\";\n case \"invoke_integration\":\n return \"Integration action completed.\";\n default:\n return \"Action completed.\";\n }\n}\n\n/**\n * Summarize a confirmed action's FAILURE for the error banner. Mutating tools\n * report a domain failure by returning a negative outcome (see\n * `resolveConfirmation`); this turns that outcome into a human message,\n * preferring the server's own `errors[]`/`message` (already end-user-written)\n * and falling back to the not-found/conflict markers. Best-effort and\n * defensive: the shape varies by tool.\n */\nexport function describeFailure(output: unknown): string {\n const o = asRecord(output);\n const errors = Array.isArray(o.errors) ? o.errors : [];\n const joined = errors\n // An element may be a structured `{ message }` (the workflow compiler shape)\n // or a bare string — surface either rather than dropping a string-only error.\n .map((e) => (typeof e === \"string\" ? e : str(asRecord(e).message)))\n .filter((m) => m.length > 0)\n .join(\"; \");\n if (joined) return joined;\n if (typeof o.message === \"string\" && o.message) return o.message;\n if (o.notFound === true) return \"That no longer exists.\";\n if (o.conflict === true) return \"It changed since it was loaded. Try again.\";\n return \"The action could not be completed.\";\n}\n\nexport function isLowBalance(balanceUsd: number | null): boolean {\n return balanceUsd != null && balanceUsd < LOW_BALANCE_THRESHOLD;\n}\n\n/** The outcome of a confirmed tool call, mirroring `ConfirmResult` from the\n * stream layer without coupling presentation to it. */\nexport type ConfirmOutcome =\n | { ok: true; output: unknown }\n | { ok: false; error: string };\n\nexport interface ConfirmResolution {\n /** Transcript note to append, or null when there's nothing to say. */\n statusText: string | null;\n /** Error banner to surface, or null on a clean success. */\n error: { code: string; message: string } | null;\n}\n\n/**\n * Decide what a confirmed proposal's result means for the transcript and the\n * error banner. The `invoke_integration` tool reports a not-connected provider\n * as a structured `{ ok: false, code: \"NOT_CONNECTED\" }` outcome inside `output`\n * (see the hub integration invoker); that exact signal maps to an\n * `INTEGRATION_DISCONNECTED` error so the panel can offer a \"Connect\" step.\n * Other failures surface as `TOOL_FAILED`. Pure so the classification is\n * unit-testable without the hook.\n */\nexport function resolveConfirmation(\n name: string,\n result: ConfirmOutcome,\n): ConfirmResolution {\n if (result.ok) {\n const out = asRecord(result.output);\n // Match the precise structured signal, not a substring of the whole output —\n // scanning the serialized blob false-positives on unrelated text (e.g. a\n // workflow named \"…NotConnected\").\n if (out.ok === false && out.code === \"NOT_CONNECTED\") {\n return {\n statusText: null,\n error: {\n code: \"INTEGRATION_DISCONNECTED\",\n message: str(out.message) || \"That integration isn't connected.\",\n },\n };\n }\n // A mutating tool reports a DOMAIN failure by RETURNING a negative outcome\n // inside an HTTP-200 success envelope — `callTool` only sets `ok:false` for\n // an unexpected throw, so a rejected create/update/delete arrives here as a\n // \"success\" whose body says it failed. The negative markers, by tool:\n // workflow/skill create → `created:false`; update → `updated:false`; skill\n // delete → `deleted:false`; set-enabled / integration → `ok:false`. Surface\n // the cause instead of reading it as a completion note (the bug where a\n // rejected workflow create showed \"Action completed.\").\n if (\n out.created === false ||\n out.updated === false ||\n out.deleted === false ||\n out.ok === false\n ) {\n return {\n statusText: null,\n error: { code: \"TOOL_FAILED\", message: describeFailure(result.output) },\n };\n }\n return { statusText: describeOutcome(name, result.output), error: null };\n }\n // A failed confirmation (proposal expired, tool error, network). The\n // not-connected case never arrives here — it is an HTTP-200 success whose\n // output carries `ok: false` — so this path is always a genuine failure.\n return {\n statusText: null,\n error: { code: \"TOOL_FAILED\", message: result.error },\n };\n}\n","/**\n * Pure state machine driving the assistant panel. Every UI transition — a user\n * message, each streamed SSE event, a stream failure, a confirmed/cancelled\n * proposal, a manual stop — is modeled as an action here, so the panel's\n * behavior can be verified without a DOM or a live stream.\n */\n\nimport type {\n AssistantStreamEvent,\n ChatMessage,\n PendingProposal,\n ToolOutcome,\n UsageInfo,\n} from \"./types\";\n\nexport type ChatStatus = \"idle\" | \"streaming\" | \"awaiting_confirm\";\n\n/** Cap on in-memory messages. A session can survive route changes and drawer\n * open/close for a long time, so the transcript is bounded to the most recent\n * turns. The streaming message is always appended last, so trimming from the\n * front never drops it. */\nconst MAX_MESSAGES = 200;\n\nfunction capMessages(messages: ChatMessage[]): ChatMessage[] {\n return messages.length > MAX_MESSAGES\n ? messages.slice(-MAX_MESSAGES)\n : messages;\n}\n\nexport interface AssistantState {\n /** The signed-in user this conversation belongs to. Carried in state so it\n * moves atomically with the data it labels — persistence keys off it, and a\n * render whose owner doesn't match the current user is masked (see\n * `selectVisibleState`), preventing a one-frame cross-account leak. */\n ownerId: string | null;\n /** Persisted across reloads to continue the same server-side thread. */\n threadId: string | null;\n messages: ChatMessage[];\n status: ChatStatus;\n /** Id of the assistant message currently accumulating deltas, if any. Set to\n * null mid-turn when a tool runs, so the next text delta opens a fresh\n * assistant bubble — keeping each reasoning segment visually distinct. */\n streamingId: string | null;\n /** Base id for the current turn's assistant bubbles (the `send` assistant id).\n * Post-tool segments derive a unique id from it; null between turns. */\n streamBaseId: string | null;\n /** How many assistant bubbles the current turn has opened (0 = just the first).\n * Drives the per-segment bubble id so segments never collide across turns. */\n segmentSeq: number;\n pendingProposals: PendingProposal[];\n /** Cost/balance from the most recently settled turn. */\n usage: UsageInfo | null;\n /** Model slug the current/most-recent turn ran against (from the thread\n * event), or null before any turn this session. */\n model: string | null;\n /** The current turn's accumulated reasoning/thinking text (reasoning models\n * stream this before the answer). Shown dim while the answer is still pending;\n * reset at the start of each turn. Null when the model emits no reasoning. */\n reasoning: string | null;\n error: { code: string; message: string } | null;\n /** True when the most-recent turn stopped at the per-turn step limit before the\n * model finished (a partial reply). Drives the one-click \"Continue\" affordance;\n * cleared when the next turn starts. */\n capped: boolean;\n}\n\nexport type AssistantAction =\n | { type: \"send\"; messageId: string; assistantId: string; text: string }\n | { type: \"stream\"; event: AssistantStreamEvent }\n | { type: \"stream_failed\"; error: { code: string; message: string } }\n | {\n type: \"proposal_resolved\";\n /** The card's canonical identity (the model's tool-call id). */\n callId: string;\n status: ChatMessage | null;\n /** Set on a failed confirmation; null clears any prior error. */\n error: { code: string; message: string } | null;\n }\n | {\n type: \"proposal_retry_failed\";\n /** The card to KEEP (a retryable confirm failure — an unconnected\n * integration). The card stays confirmable; the message is shown on it. */\n callId: string;\n message: string;\n }\n | { type: \"stopped\" }\n | {\n type: \"hydrate\";\n ownerId: string | null;\n threadId: string | null;\n messages: ChatMessage[];\n }\n | {\n type: \"restore_history\";\n ownerId: string | null;\n threadId: string;\n messages: ChatMessage[];\n /** Unconfirmed proposals restored alongside the transcript so a card\n * survives reload. Empty when none are pending. */\n proposals: PendingProposal[];\n }\n | { type: \"thread_gone\"; ownerId: string | null; threadId: string }\n | {\n type: \"history_failed\";\n ownerId: string | null;\n threadId: string;\n error: { code: string; message: string };\n }\n | { type: \"switch_thread\"; threadId: string }\n | { type: \"reset\" };\n\nexport function initialAssistantState(): AssistantState {\n return {\n ownerId: null,\n threadId: null,\n messages: [],\n status: \"idle\",\n streamingId: null,\n streamBaseId: null,\n segmentSeq: 0,\n pendingProposals: [],\n usage: null,\n model: null,\n reasoning: null,\n error: null,\n capped: false,\n };\n}\n\n/**\n * The state safe to render for `userId`. When the conversation in state belongs\n * to a different user (the single commit between an auth change and the hydrate\n * that follows it), return a fresh empty state instead of the prior user's\n * transcript — so an account's messages and proposals are never shown, even for\n * one frame, under another account.\n */\nexport function selectVisibleState(\n state: AssistantState,\n userId: string | null,\n): AssistantState {\n return state.ownerId === userId ? state : initialAssistantState();\n}\n\n/** Drop the streaming assistant bubble if it never received any text (e.g. a\n * turn that only proposed an action, or a failure before the first delta). */\nfunction dropEmptyStreaming(\n messages: ChatMessage[],\n streamingId: string | null,\n): ChatMessage[] {\n if (!streamingId) return messages;\n const msg = messages.find((m) => m.id === streamingId);\n if (msg && msg.role === \"assistant\" && msg.text === \"\") {\n return messages.filter((m) => m.id !== streamingId);\n }\n return messages;\n}\n\nfunction appendDelta(\n messages: ChatMessage[],\n streamingId: string | null,\n text: string,\n): ChatMessage[] {\n if (!streamingId) return messages;\n return messages.map((m) =>\n m.id === streamingId ? { ...m, text: m.text + text } : m,\n );\n}\n\nfunction applyStreamEvent(\n state: AssistantState,\n event: AssistantStreamEvent,\n): AssistantState {\n switch (event.type) {\n case \"thread\":\n return {\n ...state,\n threadId: event.data.threadId,\n // The model the server actually ran this turn against (lets the picker\n // reflect reality even when the user never explicitly chose one).\n model: event.data.model ?? state.model,\n };\n\n case \"delta\": {\n if (state.streamingId) {\n return {\n ...state,\n messages: appendDelta(\n state.messages,\n state.streamingId,\n event.data.text,\n ),\n };\n }\n // No open bubble: a tool ran and finalized the prior segment. Open a fresh\n // assistant bubble for this next reasoning segment so the agent's pre- and\n // post-tool text are distinct messages, not one concatenated blob.\n if (!state.streamBaseId) return state; // stray delta outside a turn\n const segmentSeq = state.segmentSeq + 1;\n const id = `${state.streamBaseId}-s${segmentSeq}`;\n return {\n ...state,\n segmentSeq,\n streamingId: id,\n messages: capMessages([\n ...state.messages,\n { id, role: \"assistant\", text: event.data.text },\n ]),\n };\n }\n\n case \"reasoning\":\n // Accumulate the turn's reasoning text; the panel shows it dim while the\n // answer is still pending so a long thinking gap doesn't read as frozen.\n return {\n ...state,\n reasoning: (state.reasoning ?? \"\") + event.data.text,\n };\n\n case \"tool_call\": {\n // A read-only tool started. Finalize the current text segment (dropping it\n // if it never received text, so a tool that runs before any preamble leaves\n // no empty bubble), then append a live \"running\" activity chip. Dedupe by\n // callId so a re-delivered event can't double-add the chip.\n const trimmed = dropEmptyStreaming(state.messages, state.streamingId);\n const chipId = `tool-${event.data.callId}`;\n if (trimmed.some((m) => m.id === chipId)) {\n return { ...state, messages: trimmed, streamingId: null };\n }\n const chip: ChatMessage = {\n id: chipId,\n role: \"tool\",\n text: \"\",\n tool: {\n name: event.data.name,\n status: \"running\",\n args: event.data.args,\n },\n };\n return {\n ...state,\n streamingId: null,\n messages: capMessages([...trimmed, chip]),\n };\n }\n\n case \"tool_result\": {\n // Resolve the activity chip the matching tool_call opened: mark it ok, or\n // failed with the error text. (Mutating tools never arrive here — they are\n // proposed and confirmed through the execute endpoint.)\n const chipId = `tool-${event.data.callId}`;\n const status = event.data.ok ? \"ok\" : \"failed\";\n const errText = event.data.ok\n ? \"\"\n : (event.data.error?.message ?? \"unknown error\");\n // Retain the outcome on the chip so a renderer can show the tool's result\n // body, not just name + status. (The error text stays in `.text` too, for\n // the built-in timeline's one-line chip.)\n const outcome: ToolOutcome = event.data.ok\n ? { ok: true, result: event.data.output }\n : { ok: false, error: event.data.error };\n if (state.messages.some((m) => m.id === chipId)) {\n return {\n ...state,\n messages: state.messages.map((m) =>\n m.id === chipId\n ? {\n ...m,\n text: errText,\n // Preserve the args the matching tool_call recorded — the\n // result event doesn't carry them.\n tool: {\n name: event.data.name,\n status,\n args: m.tool?.args,\n outcome,\n },\n }\n : m,\n ),\n };\n }\n // Defensive: a result with no preceding tool_call (shouldn't happen now\n // that the server emits both) — surface it as a finished chip rather than\n // dropping the information.\n const chip: ChatMessage = {\n id: chipId,\n role: \"tool\",\n text: errText,\n tool: { name: event.data.name, status, outcome },\n };\n return { ...state, messages: capMessages([...state.messages, chip]) };\n }\n\n case \"tool_proposal\": {\n if (state.pendingProposals.some((p) => p.callId === event.data.callId)) {\n return state;\n }\n // event.data carries requirements (when authoring) — stored verbatim as\n // the PendingProposal so the card can render them.\n return {\n ...state,\n pendingProposals: [...state.pendingProposals, event.data],\n };\n }\n\n case \"usage\":\n return {\n ...state,\n usage: {\n costUsd: event.data.costUsd,\n balanceUsd: event.data.balanceUsd,\n promptTokens: event.data.promptTokens,\n completionTokens: event.data.completionTokens,\n durationMs: event.data.durationMs ?? null,\n replayed: event.data.replayed ?? false,\n },\n };\n\n case \"done\": {\n let messages = dropEmptyStreaming(state.messages, state.streamingId);\n // A capped turn stopped at the per-turn step limit before the model\n // finished its plan (e.g. partway through authoring a workflow). Surface it\n // as a status note so a partial reply is never mistaken for a complete\n // answer; the `capped` flag drives a one-click Continue affordance (the\n // thread keeps the full context, so continuing resumes where it left off).\n const capped = event.data.capped === true;\n if (capped) {\n messages = capMessages([\n ...messages,\n {\n id: `cap-${event.data.turnId}`,\n role: \"status\",\n text: \"Paused after a lot of steps — continue when you're ready and I'll pick up where I left off.\",\n },\n ]);\n }\n return {\n ...state,\n messages,\n streamingId: null,\n capped,\n status: state.pendingProposals.length > 0 ? \"awaiting_confirm\" : \"idle\",\n };\n }\n\n case \"error\": {\n const messages = dropEmptyStreaming(state.messages, state.streamingId);\n return {\n ...state,\n messages,\n streamingId: null,\n status: \"idle\",\n // A turn that didn't complete cleanly leaves no confirmable action: a\n // proposal buffered before the failure must not stay actionable.\n pendingProposals: [],\n error: { code: event.data.code, message: event.data.message },\n };\n }\n }\n}\n\nexport function assistantReducer(\n state: AssistantState,\n action: AssistantAction,\n): AssistantState {\n switch (action.type) {\n case \"send\":\n // Pending proposals are intentionally preserved, not cleared: a new turn\n // must never silently drop an unconfirmed mutating action (which would\n // orphan its server-side proposal). The hook + composer block sending\n // while any proposal is pending, so a correct flow never reaches here with\n // one outstanding.\n return {\n ...state,\n messages: capMessages([\n ...state.messages,\n { id: action.messageId, role: \"user\", text: action.text },\n { id: action.assistantId, role: \"assistant\", text: \"\" },\n ]),\n status: \"streaming\",\n streamingId: action.assistantId,\n // The first bubble is segment 0; tool-finalized segments derive their id\n // from this base, so they stay unique across turns.\n streamBaseId: action.assistantId,\n segmentSeq: 0,\n // A new turn clears the prior turn's cap state (and its Continue button).\n capped: false,\n usage: null,\n reasoning: null,\n error: null,\n };\n\n case \"stream\":\n return applyStreamEvent(state, action.event);\n\n case \"stream_failed\": {\n const messages = dropEmptyStreaming(state.messages, state.streamingId);\n return {\n ...state,\n messages,\n streamingId: null,\n status: \"idle\",\n // A failed turn leaves no confirmable action (see the `error` case).\n pendingProposals: [],\n error: action.error,\n };\n }\n\n case \"proposal_resolved\": {\n // Identify the card by callId — the model's tool-call id, which is always\n // present (proposalId can be null) and unique per proposal. confirm/cancel\n // always carry the card's own callId, so this removes exactly that card and\n // never leaves one stuck.\n const pendingProposals = state.pendingProposals.filter(\n (p) => p.callId !== action.callId,\n );\n const messages = action.status\n ? capMessages([...state.messages, action.status])\n : state.messages;\n return {\n ...state,\n messages,\n pendingProposals,\n error: action.error,\n status:\n pendingProposals.length > 0\n ? \"awaiting_confirm\"\n : state.status === \"awaiting_confirm\"\n ? \"idle\"\n : state.status,\n };\n }\n\n case \"proposal_retry_failed\": {\n // A retryable confirm failure (an unconnected integration): KEEP the card\n // and attach the message to it so the user can connect the provider and\n // confirm again. No top-level error banner; the message lives on the card,\n // next to the requirements + connect affordance that fix it.\n const pendingProposals = state.pendingProposals.map((p) =>\n p.callId === action.callId ? { ...p, retryError: action.message } : p,\n );\n // Derive status from whether a proposal is still pending — never force\n // `awaiting_confirm` unconditionally. In the live flow the card is always\n // still here (this handler keeps it), but computing the status the same\n // way `proposal_resolved` does keeps the state machine correct-by-\n // construction: a dropped proposal can't resurrect a stale awaiting state.\n return {\n ...state,\n pendingProposals,\n status:\n pendingProposals.length > 0\n ? \"awaiting_confirm\"\n : state.status === \"awaiting_confirm\"\n ? \"idle\"\n : state.status,\n };\n }\n\n case \"stopped\":\n // Keep whatever text already streamed; just stop accumulating. Drop the\n // assistant bubble if it never received a delta (stopped before the first\n // token), and drop a proposal buffered before the stop — the aborted turn\n // leaves no confirmable action.\n return {\n ...state,\n messages: dropEmptyStreaming(state.messages, state.streamingId),\n status: \"idle\",\n streamingId: null,\n pendingProposals: [],\n };\n\n case \"hydrate\":\n // Replace the whole conversation with a freshly loaded thread, dropping\n // all transient state, and stamp it with its owner. Used when the\n // signed-in user changes under a mounted panel so one account's transcript\n // can never carry into another.\n return {\n ...initialAssistantState(),\n ownerId: action.ownerId,\n threadId: action.threadId,\n messages: action.messages,\n };\n\n case \"restore_history\":\n // Apply the server-restored transcript ONLY if the conversation hasn't\n // moved on since the fetch began: same owner + thread, still idle, nothing\n // shown yet, AND no live proposal already pending. Otherwise the user\n // already started interacting (or switched account / started a new chat)\n // and a late restore must not clobber the live state or resurrect a prior\n // thread. The `pendingProposals` check is belt-and-suspenders: an idle,\n // empty conversation has no pending proposals (the `done`/resolve handlers\n // keep `idle ⟺ no proposals`), so this can't fire in normal flow — but it\n // makes the restore self-protective against a future invariant change\n // rather than relying on a cross-handler guarantee to avoid dropping a\n // live, unconfirmed proposal (the exact loss this PR exists to prevent).\n if (\n state.ownerId !== action.ownerId ||\n state.threadId !== action.threadId ||\n state.status !== \"idle\" ||\n state.messages.length > 0 ||\n state.pendingProposals.length > 0\n ) {\n return state;\n }\n // Restore the transcript AND any unconfirmed proposals — a card is\n // otherwise client-ephemeral, so without this the user loses the ability to\n // confirm a pending action on reload. A restored proposal puts the\n // conversation back in `awaiting_confirm` so the card renders and the\n // composer stays gated until it's resolved.\n return {\n ...state,\n messages: capMessages(action.messages),\n pendingProposals: action.proposals,\n status: action.proposals.length > 0 ? \"awaiting_confirm\" : state.status,\n };\n\n case \"thread_gone\":\n // The persisted thread no longer exists server-side (history restore got a\n // 404). Drop the dead id — but only if the conversation hasn't moved on\n // (same owner + thread, still idle, nothing shown), mirroring the\n // restore_history guard — so the next send starts a fresh thread instead of\n // 404-ing forever against a thread that's gone. A started turn / new chat /\n // switched account is left untouched.\n if (\n state.ownerId !== action.ownerId ||\n state.threadId !== action.threadId ||\n state.status !== \"idle\" ||\n state.messages.length > 0\n ) {\n return state;\n }\n return { ...state, threadId: null };\n\n case \"history_failed\":\n // A switched-to thread's transcript couldn't be loaded. Drop the active\n // thread and surface the error — the thread carries server-side context\n // (prior turns, possibly secrets) that isn't on screen, so the next send\n // must NOT run against it; it starts a fresh thread instead. Guarded like\n // restore_history so a late failure can't reset a conversation that has\n // since moved on (a started turn / new chat / switched account).\n if (\n state.ownerId !== action.ownerId ||\n state.threadId !== action.threadId ||\n state.status !== \"idle\" ||\n state.messages.length > 0\n ) {\n return state;\n }\n return {\n ...initialAssistantState(),\n ownerId: state.ownerId,\n error: action.error,\n };\n\n case \"switch_thread\":\n // Open an existing thread the user picked from history. Reset to a clean\n // idle state for the SAME owner and pin the chosen thread id, so the\n // follow-up history fetch (restore_history) — which only applies on a\n // matching owner+thread that is still idle/empty — lands its transcript\n // here. A no-op if it's already the active thread.\n if (state.threadId === action.threadId) return state;\n return {\n ...initialAssistantState(),\n ownerId: state.ownerId,\n threadId: action.threadId,\n };\n\n case \"reset\":\n // \"New chat\" for the same user — start a fresh thread while the prior ones\n // stay reachable from history. Keep the owner so the cleared state stays\n // visible (a null owner would be masked by selectVisibleState).\n return { ...initialAssistantState(), ownerId: state.ownerId };\n }\n}\n","/**\n * The assistant's selectable models for the composer's picker. The list is\n * deployment config (not per-user), so it is fetched once per transport and\n * shared across panel mounts via a cache keyed by the `AssistantClient`.\n *\n * The cache has no TTL: it lives for the page session and is revalidated by a\n * page refresh — acceptable for deployment-config data that changes only on a\n * redeploy. An empty/failed fetch is NOT cached, so it retries on the next mount.\n *\n * Keyed by client so a host that swaps the transport (a tenant/origin/account\n * change) never serves the previous client's catalog or skips the fetch for the\n * new one. A WeakMap lets a discarded client's bucket be collected with it.\n *\n * Client-only by design (a `createRoot` SPA, no React SSR), so the cache lives\n * in one browser tab and is never shared across server requests. Under Strict\n * Mode's double-mount the two effect runs share the one in-flight fetch; the\n * first run's `active = false` makes its callback a no-op, the second commits —\n * dedup holds, no torn state.\n */\n\nimport { useEffect, useReducer } from \"react\";\nimport type {\n AssistantClient,\n AssistantModels,\n AssistantModelsResult,\n} from \"./client\";\nimport { useAssistantClient } from \"./client-context\";\n\nconst EMPTY: AssistantModels = { default: null, models: [] };\n\ninterface ModelCache {\n cache: AssistantModels | null;\n inflight: Promise<AssistantModelsResult> | null;\n}\n\nconst byClient = new WeakMap<AssistantClient, ModelCache>();\n\nfunction cacheFor(client: AssistantClient): ModelCache {\n let entry = byClient.get(client);\n if (!entry) {\n entry = { cache: null, inflight: null };\n byClient.set(client, entry);\n }\n return entry;\n}\n\nexport function useAssistantModels(): AssistantModels {\n const client = useAssistantClient();\n // The per-client cache is the source of truth; `bump` just forces a re-read\n // once an async fetch settles. Deriving the return value during render (below)\n // — rather than mirroring it into state via an effect — means a client swap\n // shows the new client's catalog (or empty) on the SAME commit, with no stale\n // frame from the previous client.\n const [, bump] = useReducer((n: number) => n + 1, 0);\n\n useEffect(() => {\n const entry = cacheFor(client);\n if (entry.cache) return;\n let active = true;\n entry.inflight ??= client.fetchModels();\n void entry.inflight\n .then((result) => {\n // Cache a successful fetch (a well-formed, non-empty list) and release\n // the settled promise — the cache now guards re-fetch. A failed fetch OR\n // an empty list (reported as !ok — catalog unavailable) leaves the cache\n // unset so the next mount retries instead of serving an empty picker.\n if (result.ok) entry.cache = result.data;\n entry.inflight = null;\n if (active) bump();\n })\n .catch(() => {\n // fetchModels swallows its own errors, so this only fires if a future\n // change lets it reject — release the slot so the next mount retries.\n entry.inflight = null;\n });\n return () => {\n active = false;\n };\n }, [client]);\n\n // Always the CURRENT client's catalog — synchronously correct across a swap.\n return cacheFor(client).cache ?? EMPTY;\n}\n","/**\n * The user's recent assistant chat threads for the history switcher. Unlike the\n * model list (deployment config, fetched once), the thread list changes as the\n * user chats, so it is fetched ON DEMAND — call `refresh()` to (re)load it; the\n * panel does so when the history view opens and after a turn settles a new\n * thread into being. It does NOT fetch on mount, so `threads` stays empty and\n * `loaded` false until the first `refresh()`.\n *\n * Self-protective across account AND transport swaps. The list is tagged with the\n * (user, client) it belongs to and is masked to empty on the SAME commit if that\n * no longer matches the current props — so a swap never shows the prior scope's\n * threads for even one frame. In flight, a late result is dropped if either the\n * user or the client changed, and the request is aborted on the swap.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { AssistantClient, AssistantThreadSummary } from \"./client\";\nimport { useAssistantClient } from \"./client-context\";\n\nexport interface AssistantThreads {\n threads: AssistantThreadSummary[];\n loading: boolean;\n /** True once a fetch has settled at least once (drives empty-vs-loading copy). */\n loaded: boolean;\n /** Load (or reload) the thread list. Must be called to populate `threads` —\n * the hook never fetches on mount (the panel calls this when history opens). */\n refresh: () => void;\n /** Delete a thread. Optimistically drops it from the list (within the current\n * owner scope); on failure the list is reloaded to restore the true state. A\n * no-op resolving `{ ok: false }` when the client has no `deleteThread`. */\n remove: (threadId: string) => Promise<{ ok: boolean }>;\n /** Whether the configured client supports deletion — drives whether a host\n * shows the delete affordance. */\n canRemove: boolean;\n}\n\ninterface ThreadsState {\n threads: AssistantThreadSummary[];\n loading: boolean;\n loaded: boolean;\n /** The (user, client) the data belongs to; the hook masks to empty unless both\n * match the current props, so a swap can't show the prior scope's list. */\n ownerUserId: string | null;\n ownerClient: AssistantClient | null;\n}\n\nexport function useAssistantThreads(userId: string | null): AssistantThreads {\n const client = useAssistantClient();\n const userRef = useRef(userId);\n userRef.current = userId;\n const clientRef = useRef(client);\n clientRef.current = client;\n const abortRef = useRef<AbortController | null>(null);\n // Ids being (or already) deleted. A refresh whose fetch began before the\n // server delete completed can return a row we optimistically removed; filter\n // these out of every refresh commit so a deleted thread never reappears.\n const pendingDeletesRef = useRef<Set<string>>(new Set());\n\n const [state, setState] = useState<ThreadsState>(() => ({\n threads: [],\n loading: false,\n loaded: false,\n ownerUserId: userId,\n ownerClient: client,\n }));\n\n const refresh = useCallback(() => {\n // Capture the user AND client this fetch is FOR; the commit and the owner tag\n // both use them, so a result can never land under a different scope.\n const requestedUserId = userRef.current;\n const requestedClient = clientRef.current;\n if (!requestedUserId) {\n setState({\n threads: [],\n loading: false,\n loaded: true,\n ownerUserId: requestedUserId,\n ownerClient: requestedClient,\n });\n return;\n }\n // Supersede any in-flight fetch so a rapid re-open can't land a stale list.\n abortRef.current?.abort();\n const ac = new AbortController();\n abortRef.current = ac;\n setState((s) => ({\n ...s,\n loading: true,\n ownerUserId: requestedUserId,\n ownerClient: requestedClient,\n }));\n const isCurrent = () =>\n !ac.signal.aborted &&\n userRef.current === requestedUserId &&\n clientRef.current === requestedClient;\n void requestedClient\n .fetchThreads(ac.signal)\n .then((result) => {\n if (!isCurrent()) return;\n setState((s) => ({\n // null = transient failure: keep the prior list, just drop the spinner.\n // Drop any in-flight/finished deletions so a stale fetch can't resurrect\n // a row we already removed.\n threads: (result ?? s.threads).filter(\n (t) => !pendingDeletesRef.current.has(t.id),\n ),\n loading: false,\n loaded: true,\n ownerUserId: requestedUserId,\n ownerClient: requestedClient,\n }));\n })\n .catch(() => {\n // fetchThreads returns null rather than rejecting; this guards a future\n // change (or a throw in a state setter) from wedging the spinner.\n if (isCurrent()) {\n setState((s) => ({ ...s, loading: false, loaded: true }));\n }\n });\n }, []);\n\n const remove = useCallback(\n async (threadId: string) => {\n const requestedClient = clientRef.current;\n const requestedUserId = userRef.current;\n // A client without delete support can't remove anything — no-op rather\n // than optimistically drop a row that will never be deleted server-side.\n if (!requestedClient.deleteThread) return { ok: false };\n // Mark it deleting so a concurrent refresh's commit filters it out, then\n // optimistically drop the row — but only within the scope we're deleting\n // under (never mutate a swapped-in scope's list).\n pendingDeletesRef.current.add(threadId);\n setState((s) =>\n s.ownerClient === requestedClient && s.ownerUserId === requestedUserId\n ? { ...s, threads: s.threads.filter((t) => t.id !== threadId) }\n : s,\n );\n // Normalize a rejecting client to `{ ok: false }` so the rollback below\n // always runs (the bundled client resolves, but the interface allows any).\n let res: { ok: boolean };\n try {\n res = await requestedClient.deleteThread(threadId);\n } catch {\n res = { ok: false };\n }\n // On failure, un-mark it and reload to restore the row we optimistically\n // removed (only if we're still in the same scope). On success it stays\n // marked — the thread is gone for good and must never resurface.\n if (!res.ok) {\n pendingDeletesRef.current.delete(threadId);\n if (\n userRef.current === requestedUserId &&\n clientRef.current === requestedClient\n ) {\n refresh();\n }\n }\n return res;\n },\n [refresh],\n );\n\n // Abort an in-flight fetch on a scope swap (its result is already masked and\n // the commit guard rejects it; this just frees the network promptly) and on\n // unmount, so a late `.then` can't act after the panel closed.\n useEffect(() => {\n return () => abortRef.current?.abort();\n }, [userId, client]);\n\n // Drop the pending-delete ids only on true unmount — NOT on a scope swap. A\n // remove() awaiting its DELETE can outlive a swap-and-return to the same\n // scope; clearing on the swap would un-filter that id and let a stale refresh\n // resurrect the row. Thread ids are server-minted and globally unique, so the\n // set never false-filters another scope's list, and within one mount it only\n // grows by the user's own deletions (released here on unmount).\n useEffect(() => {\n return () => pendingDeletesRef.current.clear();\n }, []);\n\n // Mask synchronously: a list owned by a different (user, client) than the\n // current props is hidden on the same commit — no one-frame cross-scope leak.\n const stale = state.ownerUserId !== userId || state.ownerClient !== client;\n return {\n threads: stale ? [] : state.threads,\n loading: stale ? false : state.loading,\n loaded: stale ? false : state.loaded,\n refresh,\n remove,\n canRemove: typeof client.deleteThread === \"function\",\n };\n}\n","/**\n * Persistent assistant entry point: a floating launcher that opens the chat\n * panel as a right-side drawer (full-screen on small viewports), with focus\n * trapping and a resizable width. Owns the chat state (via useAssistantChat) so\n * the conversation survives the drawer closing — host-shell concerns (the user,\n * navigation, balance, money formatting, the graph renderer, and the workflow-\n * mutation signal) are injected.\n *\n * Mount inside an <AssistantClientProvider> (transport) and an\n * <AssistantLauncherProvider> (open/seed state).\n */\n\nimport { MessageSquare } from \"lucide-react\";\nimport {\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n useEffect,\n useRef,\n} from \"react\";\nimport type { ToolDetailRenderers } from \"../web-react\";\nimport { AssistantPanel } from \"./AssistantPanel\";\nimport { useAssistantLauncher } from \"./launcher\";\nimport { ResizeHandle } from \"./ResizeHandle\";\nimport type { AssistantTranscriptView } from \"./types\";\nimport { useAssistantChat } from \"./useAssistantChat\";\nimport { useIsDesktop, usePanelWidth } from \"./usePanelPrefs\";\n\nexport interface AssistantDockProps {\n /** The signed-in user this conversation belongs to (null when signed out). */\n userId: string | null;\n /** Host navigation for error CTAs and connect targets. */\n navigate?: (path: string) => void;\n balanceUsd?: number | null;\n formatMoney?: (usd: number | null) => string;\n /** Render workflow YAML as a node graph in a proposal card. */\n renderGraph?: (yaml: string) => ReactNode;\n /** Called after a workflow-mutating tool is confirmed (host re-fetches its list). */\n onWorkflowMutation?: () => void;\n /** Markdown renderer for assistant message content (plain text when absent). */\n renderMarkdown?: (content: string) => ReactNode;\n /** Per-tool custom detail renderers for expanded tool cards in the transcript. */\n toolRenderers?: ToolDetailRenderers;\n /** Swap the conversation rendering for a host-supplied renderer (see\n * {@link AssistantPanelProps.renderTranscript}); the dock chrome, composer,\n * transport, and proposal flow stay owned by the panel. */\n renderTranscript?: (view: AssistantTranscriptView) => ReactNode;\n}\n\n/** Visible, focusable descendants of a container, in tab order. Visibility is\n * checked via getClientRects rather than offsetParent, which is null for\n * position:fixed elements and would wrongly exclude them. */\nfunction focusableWithin(container: HTMLElement): HTMLElement[] {\n const selector =\n 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n return Array.from(container.querySelectorAll<HTMLElement>(selector)).filter(\n (el) => el.getClientRects().length > 0,\n );\n}\n\nexport function AssistantDock({\n userId,\n navigate,\n balanceUsd = null,\n formatMoney,\n renderGraph,\n onWorkflowMutation,\n renderMarkdown,\n toolRenderers,\n renderTranscript,\n}: AssistantDockProps) {\n const { open, openAssistant, closeAssistant } = useAssistantLauncher();\n const chat = useAssistantChat(userId, { onWorkflowMutation });\n\n const isDesktop = useIsDesktop();\n const { width, maxWidth, setWidth, previewWidth, nudgeWidth } =\n usePanelWidth();\n\n const launcherRef = useRef<HTMLButtonElement | null>(null);\n const dialogRef = useRef<HTMLDivElement | null>(null);\n const returnFocusRef = useRef<HTMLElement | null>(null);\n const wasOpenRef = useRef(false);\n\n // Close on Escape.\n useEffect(() => {\n if (!open) return;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") closeAssistant();\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [open, closeAssistant]);\n\n // Move focus into the dialog on open; restore it to the opener on close.\n useEffect(() => {\n if (open) {\n if (!wasOpenRef.current && !returnFocusRef.current) {\n returnFocusRef.current = document.activeElement as HTMLElement | null;\n }\n wasOpenRef.current = true;\n const el = dialogRef.current;\n if (el) (focusableWithin(el)[0] ?? el).focus();\n } else if (wasOpenRef.current) {\n wasOpenRef.current = false;\n const target = returnFocusRef.current?.isConnected\n ? returnFocusRef.current\n : launcherRef.current;\n target?.focus();\n returnFocusRef.current = null;\n }\n }, [open]);\n\n const openDialog = () => {\n returnFocusRef.current = document.activeElement as HTMLElement | null;\n openAssistant();\n };\n\n if (!open) {\n return (\n <button\n ref={launcherRef}\n type=\"button\"\n onClick={openDialog}\n aria-label=\"Open assistant\"\n className=\"fixed right-4 bottom-4 z-40 flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-colors hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-ring\"\n >\n <MessageSquare className=\"h-6 w-6\" />\n </button>\n );\n }\n\n // Keep Tab focus within the dialog while it's open.\n const trapTab = (e: ReactKeyboardEvent<HTMLDivElement>) => {\n if (e.key !== \"Tab\") return;\n const dialog = dialogRef.current;\n if (!dialog) return;\n const focusables = focusableWithin(dialog);\n if (focusables.length === 0) {\n e.preventDefault();\n dialog.focus();\n return;\n }\n // Non-empty here (length === 0 returned above); the index access is safe.\n const first = focusables[0]!;\n const last = focusables[focusables.length - 1]!;\n const active = document.activeElement;\n const inside = active instanceof Node && dialog.contains(active);\n if (e.shiftKey) {\n if (!inside || active === first || active === dialog) {\n e.preventDefault();\n last.focus();\n }\n } else if (!inside || active === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n return (\n <>\n <div\n aria-hidden=\"true\"\n className=\"fixed inset-0 z-40 bg-black/40\"\n onClick={() => closeAssistant()}\n />\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-label=\"Assistant\"\n aria-modal=\"true\"\n tabIndex={-1}\n onKeyDown={trapTab}\n style={isDesktop ? { width: `${width}px` } : undefined}\n className=\"fixed inset-y-0 right-0 z-50 flex w-full flex-col border-border border-l shadow-xl focus:outline-none\"\n >\n <AssistantPanel\n key={userId ?? \"anon\"}\n chat={chat}\n userId={userId}\n onClose={() => closeAssistant()}\n navigate={navigate}\n balanceUsd={balanceUsd}\n formatMoney={formatMoney}\n renderGraph={renderGraph}\n renderMarkdown={renderMarkdown}\n toolRenderers={toolRenderers}\n renderTranscript={renderTranscript}\n />\n {isDesktop && (\n <ResizeHandle\n width={width}\n maxWidth={maxWidth}\n onPreview={previewWidth}\n onCommit={setWidth}\n onNudge={nudgeWidth}\n />\n )}\n </div>\n </>\n );\n}\n","/**\n * The assistant chat panel, built on web-react's chat components. The reducer\n * state is rendered by `AssistantTranscript` (web-react `ChatMessages`: transcript,\n * tool chips, reasoning preview, cost, proposal cards) and the composer is a\n * `ChatComposer` carrying the `ModelPicker` in its controls slot. The header's\n * history toggle swaps the conversation area for a full-panel, searchable history\n * view. App-shell concerns — the signed-in user, navigation, the credit balance,\n * money formatting, the markdown + tool-detail renderers, and the workflow-graph\n * renderer — are injected so the panel is portable across hosts. Chat state is\n * owned by the dock and passed in, so the conversation survives the drawer closing.\n */\n\nimport { History, MessageSquarePlus, Minus, Plus, X } from \"lucide-react\";\nimport { type ReactNode, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { CatalogModel } from \"../runtime/model-catalog\";\nimport { ChatComposer, ModelPicker, type ToolDetailRenderers } from \"../web-react\";\nimport { AssistantHistory } from \"./AssistantHistory\";\nimport type { AssistantModels } from \"./client\";\nimport { isLowBalance, presentError } from \"./presentation\";\nimport { ProposalCard } from \"./ProposalCard\";\nimport { AssistantTranscript, assistantIsThinking } from \"./transcript\";\nimport type { AssistantTranscriptView } from \"./types\";\nimport type { AssistantChat } from \"./useAssistantChat\";\nimport { useAssistantModels } from \"./useAssistantModels\";\nimport { useAssistantThreads } from \"./useAssistantThreads\";\nimport { useFontScale } from \"./usePanelPrefs\";\nimport { useStickToBottom } from \"./use-stick-to-bottom\";\n\nexport interface AssistantPanelProps {\n chat: AssistantChat;\n userId: string | null;\n onClose: () => void;\n /** Host navigation for error CTAs and connect targets. */\n navigate?: (path: string) => void;\n /** The user's credit balance, for the header tile + low-balance nudge. */\n balanceUsd?: number | null;\n /** Format a USD amount; defaults to Intl currency formatting. */\n formatMoney?: (usd: number | null) => string;\n /** Render workflow YAML as a node graph in a proposal card (the `./workflows`\n * WorkflowGraph). When absent, proposals show YAML as text. */\n renderGraph?: (yaml: string) => ReactNode;\n /** Markdown renderer for assistant message content. When absent, content\n * renders as plain pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode;\n /** Per-tool custom detail renderers for expanded tool cards in the transcript. */\n toolRenderers?: ToolDetailRenderers;\n /** Swap ONLY the conversation rendering for a host-supplied renderer (e.g. a\n * different chat-message component), while the panel keeps owning the header,\n * composer, model picker, history, transport, and proposal orchestration.\n * Receives the transcript slice plus a bound proposal card to place. When\n * absent, the built-in transcript (web-react `ChatMessages`) renders the\n * conversation. */\n renderTranscript?: (view: AssistantTranscriptView) => ReactNode;\n}\n\nconst EMPTY_STATE =\n \"Ask me to create a workflow, check your usage, or manage your API keys.\";\n\nfunction defaultFormatMoney(usd: number | null): string {\n if (usd == null) return \"—\";\n return new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: \"USD\",\n }).format(usd);\n}\n\n/**\n * Map the assistant catalog onto the shared ModelPicker's wire shape. The slug\n * is already a canonical, provider-prefixed id, so it doubles as the picker's\n * value.\n *\n * Both the server `default` and the currently-`selected` slug are guaranteed a\n * row even when the catalog omits them (each appended only when not already\n * listed, so no duplicate is produced). Keeping the active selection visible is\n * what lets the picker show exactly what the next turn will send without the\n * panel ever rewriting the user's choice to avoid an orphaned value — a stale\n * slug (e.g. a model retired between refetches, or one missing from a filtered\n * catalog) stays selectable until the user changes it or the server rejects it,\n * which is when `useAssistantChat` clears it.\n *\n * An absent context window is omitted rather than passed as `undefined`; pricing\n * is omitted entirely because the catalog carries only a prompt price, which the\n * picker's \"prompt / completion\" line would misreport as a free completion.\n */\n/** The provider segment of a canonical, provider-prefixed slug\n * (\"anthropic/claude-…\" → \"anthropic\"); \"other\" when the slug isn't prefixed.\n * Drives the picker's provider grouping + logo. */\nfunction providerOf(slug: string): string {\n const i = slug.indexOf(\"/\");\n return i > 0 ? slug.slice(0, i) : \"other\";\n}\n\nexport function toPickerModels(\n models: AssistantModels,\n selected: string | null,\n): CatalogModel[] {\n const row = (slug: string, label?: string, contextTokens?: number): CatalogModel => ({\n id: slug,\n name: label ?? slug,\n provider: providerOf(slug),\n supportsTools: true,\n supportsReasoning: false,\n featured: false,\n ...(contextTokens != null ? { contextLength: contextTokens } : {}),\n });\n const mapped: CatalogModel[] = models.models.map((m) =>\n row(m.slug, m.label, m.contextTokens),\n );\n for (const slug of [models.default, selected]) {\n if (slug && !mapped.some((m) => m.id === slug)) mapped.push(row(slug));\n }\n return mapped;\n}\n\n/** Small animated \"working\" cue shown above the composer while a turn streams —\n * a redundant, renderer-independent signal alongside the composer's Stop button\n * and the transcript's own thinking row. */\nfunction WorkingIndicator() {\n return (\n <span className=\"inline-flex items-center gap-1.5 text-muted-foreground text-xs\">\n <span className=\"h-1.5 w-1.5 animate-pulse rounded-full bg-primary\" />\n Working…\n </span>\n );\n}\n\n/**\n * The chat-state value to store for a model id chosen in the picker. Picking the\n * server default clears the preference to `null` — preserving the native-select\n * contract where \"default\" means \"omit the model and follow whatever the server\n * default is\", rather than pinning the default's slug (which would freeze the\n * user to it even after the server default changes). Any other id is stored as-is.\n */\nexport function nextModelSelection(\n id: string,\n defaultSlug: string | null,\n): string | null {\n if (defaultSlug != null && id === defaultSlug) return null;\n return id || null;\n}\n\nexport function AssistantPanel({\n chat,\n userId,\n onClose,\n navigate,\n balanceUsd = null,\n formatMoney = defaultFormatMoney,\n renderGraph,\n renderMarkdown,\n toolRenderers,\n renderTranscript,\n}: AssistantPanelProps) {\n const models = useAssistantModels();\n const threads = useAssistantThreads(userId);\n const font = useFontScale();\n // Which surface the conversation area shows: the live chat, or the full-panel\n // history list. The header's history button toggles between them.\n const [view, setView] = useState<\"chat\" | \"history\">(\"chat\");\n const historyButtonRef = useRef<HTMLButtonElement | null>(null);\n // The conversation/history scroll container, used to scope the history-view\n // Escape handler and to move focus into the history view when it opens.\n const logRef = useRef<HTMLDivElement | null>(null);\n\n const pickerModels = useMemo<CatalogModel[]>(\n () => toPickerModels(models, chat.selectedModel),\n [models, chat.selectedModel],\n );\n // `toPickerModels` guarantees both the selected slug and the default a row, so\n // this value always resolves to a real option — the displayed model is exactly\n // the slug the next turn will send, with no panel-side rewrite of the user's\n // choice. Falls through to the default, then empty, only when nothing is set.\n const pickerValue = chat.selectedModel ?? models.default ?? \"\";\n\n const { state } = chat;\n // Always-current chat handle, so an async delete can re-check the LIVE thread\n // + status after awaiting (the closure's `chat`/`state` are render-time stale).\n const chatRef = useRef(chat);\n chatRef.current = chat;\n\n // When the history view opens, move focus into its search box, so keyboard\n // users land ready to type and the scoped Escape handler below receives the\n // key event. Focusing the input is more reliable than focusing the\n // tabIndex=-1 container, which some browsers handle inconsistently; the\n // container is a fallback only if the input isn't present.\n useEffect(() => {\n if (view !== \"history\") return;\n const search = logRef.current?.querySelector<HTMLInputElement>(\n 'input[type=\"search\"]',\n );\n (search ?? logRef.current)?.focus();\n }, [view]);\n\n // In the history view, Escape returns to the conversation (and refocuses the\n // toggle) rather than closing the whole assistant. Scoped to Escapes that\n // originate inside the history view or from the toggle, so it never swallows\n // an Escape meant for an open composer popover/menu; handled in the capture\n // phase with stopImmediatePropagation so it preempts the dock's own\n // Escape-to-close. In the chat view no handler is installed, so Escape falls\n // through to the dock's close as usual.\n useEffect(() => {\n if (view !== \"history\") return;\n const onKeyDownCapture = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n const target = e.target as Node;\n if (\n !logRef.current?.contains(target) &&\n !historyButtonRef.current?.contains(target)\n ) {\n return;\n }\n e.stopImmediatePropagation();\n setView(\"chat\");\n historyButtonRef.current?.focus();\n };\n document.addEventListener(\"keydown\", onKeyDownCapture, true);\n return () => document.removeEventListener(\"keydown\", onKeyDownCapture, true);\n }, [view]);\n\n // Auto-follow: pin the transcript to the newest content as it streams, yielding\n // when the user scrolls up. `contentSignature` must move on EVERY visible\n // transcript mutation so the follow re-runs: streamed text growth, a new\n // message, reasoning growth, the turn's status, AND a tool card appearing or\n // changing status (tool activity is carried on separate `tool` messages, so a\n // text-length-only signature would miss a turn that streams tool cards before\n // any answer text). See `useStickToBottom`.\n const contentSignature = useMemo(() => {\n let sig = `${state.reasoning?.length ?? 0}|${state.status}`;\n for (const m of state.messages) {\n // `text` is typed non-null, but thread history is external data — guard so\n // a malformed message can't throw and blank the panel.\n sig += `|${m.text?.length ?? 0}`;\n if (m.tool) sig += `:${m.tool.status}`;\n }\n // Pending proposal cards render in the transcript too, so a newly-inserted\n // approval card must move the signature to scroll it into view.\n for (const p of state.pendingProposals) sig += `|p:${p.callId}`;\n return sig;\n }, [\n state.messages,\n state.reasoning,\n state.status,\n state.pendingProposals,\n ]);\n const { onScroll: handleConversationScroll } = useStickToBottom(logRef, {\n enabled: view === \"chat\",\n contentSignature,\n streamingId: state.streamingId,\n threadId: state.threadId,\n });\n\n // Prefer the just-settled turn's balance (from the usage event, immediate)\n // over the injected fetched balance, which may lag a turn behind.\n const effectiveBalance = state.usage?.balanceUsd ?? balanceUsd;\n const errorView = state.error\n ? presentError(state.error.code, state.error.message)\n : null;\n const low = isLowBalance(effectiveBalance) && !errorView;\n const streaming = state.status === \"streaming\";\n // The active conversation's title — the first user message, truncated, mirroring\n // the server's own thread titling (a thread title IS its truncated first user\n // message). Derived client-side so it shows immediately on the first send and\n // on a restored thread, with no extra fetch. Null for a fresh, empty chat.\n const firstUserText = state.messages\n .find((m) => m.role === \"user\")\n ?.text.trim();\n // Truncate by code point (Array.from), not UTF-16 code unit, so a 60-char cut\n // can't split a surrogate pair (emoji / astral script) into a replacement char.\n const titleChars = firstUserText ? Array.from(firstUserText) : [];\n const conversationTitle = firstUserText\n ? titleChars.length > 60\n ? `${titleChars.slice(0, 60).join(\"\")}…`\n : firstUserText\n : null;\n\n const renderProposal = (proposal: (typeof state.pendingProposals)[number]) => (\n <ProposalCard\n proposal={proposal}\n confirming={\n proposal.proposalId ? chat.confirmingIds.has(proposal.proposalId) : false\n }\n onConfirm={() => chat.confirm(proposal)}\n onCancel={() => chat.cancel(proposal)}\n navigate={navigate}\n renderGraph={renderGraph}\n />\n );\n\n const isThinking = assistantIsThinking(state);\n\n // The transcript slice — fed to either a host-supplied renderer or the\n // built-in `AssistantTranscript` (web-react `ChatMessages`).\n const transcriptView: AssistantTranscriptView = {\n messages: state.messages,\n reasoning: state.reasoning,\n streamingId: state.streamingId,\n model: state.model,\n isStreaming: streaming,\n isThinking,\n pendingProposals: state.pendingProposals,\n usage: state.usage,\n renderProposal,\n };\n\n // Entering history loads (or reloads) the thread list — the hook never fetches\n // on mount, so this is what populates it.\n const showHistory = () => {\n threads.refresh();\n setView(\"history\");\n };\n const toggleHistory = () => {\n if (view === \"history\") setView(\"chat\");\n else showHistory();\n };\n\n // Delete a past conversation. Deleting the *active* thread is refused while it\n // is mid-turn (the stream is still writing to it). The list row drops\n // optimistically (in the hook), but the LIVE conversation is only reset once\n // the server confirms the delete — so a failed delete never strands the user\n // on a fresh thread while the server still has the conversation.\n const deleteThread = async (threadId: string) => {\n // Refuse deleting the active thread while it is mid-turn. Read LIVE status\n // through the ref (not the render-time `state`) so the guard is authoritative\n // regardless of when this closure was created or how long the confirm sat\n // open — never delete a thread the stream is still writing to.\n const pre = chatRef.current.state;\n if (pre.threadId === threadId && pre.status !== \"idle\") return;\n if (!window.confirm(\"Delete this conversation? This can't be undone.\")) {\n return;\n }\n const res = await threads.remove(threadId);\n // Reset the live conversation only if the just-deleted thread is STILL the\n // active, idle one. Re-checked through the ref because the user may have\n // switched threads or started a turn while the delete was in flight —\n // resetting then would wipe a different or now-busy conversation.\n const live = chatRef.current.state;\n if (res.ok && live.threadId === threadId && live.status === \"idle\") {\n chatRef.current.reset();\n }\n };\n\n return (\n <div className=\"relative flex h-full flex-col bg-background\">\n {/* Header: identity + active-conversation title, and the conversation-level\n actions (text size, history, new, close). */}\n <div className=\"border-border border-b\">\n <div className=\"flex items-center justify-between gap-2 px-4 pt-3 pb-2.5\">\n <div className=\"flex min-w-0 flex-col\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"font-medium text-foreground text-sm\">\n Assistant\n </span>\n <span\n aria-label=\"Your credit balance\"\n className=\"text-muted-foreground text-xs\"\n >\n {formatMoney(effectiveBalance)}\n </span>\n </div>\n {conversationTitle && (\n <span\n className=\"truncate text-muted-foreground text-xs\"\n title={conversationTitle}\n >\n {conversationTitle}\n </span>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-1\">\n {/* Text size — zooms the transcript. A panel-level control, so it\n lives in the header action row rather than over the composer. */}\n <div\n className=\"flex items-center overflow-hidden rounded-md border border-border\"\n role=\"group\"\n aria-label=\"Text size\"\n >\n <button\n type=\"button\"\n onClick={font.decrease}\n disabled={!font.canDecrease}\n aria-label=\"Decrease text size\"\n className=\"px-1.5 py-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent\"\n >\n <Minus className=\"h-3.5 w-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={font.increase}\n disabled={!font.canIncrease}\n aria-label=\"Increase text size\"\n className=\"border-border border-l px-1.5 py-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n <button\n ref={historyButtonRef}\n type=\"button\"\n onClick={toggleHistory}\n aria-label=\"Chat history\"\n aria-pressed={view === \"history\"}\n className={`rounded-md p-1.5 transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring ${\n view === \"history\"\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground\"\n }`}\n >\n <History className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => {\n chat.reset();\n setView(\"chat\");\n }}\n aria-label=\"New chat\"\n title=\"New chat\"\n className=\"rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MessageSquarePlus className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close assistant\"\n className=\"rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </div>\n\n {/* Conversation — the full-panel history view, the host-swappable\n renderer, or the built-in timeline. */}\n <div\n ref={logRef}\n tabIndex={-1}\n aria-label=\"Conversation\"\n onScroll={handleConversationScroll}\n // role=\"log\" + aria-live announce streaming transcript updates; applied\n // only in the chat view so the history view's search box and buttons are\n // not announced as live conversation activity.\n role={view === \"chat\" ? \"log\" : undefined}\n aria-live={view === \"chat\" ? \"polite\" : undefined}\n className=\"min-h-0 flex-1 overflow-y-auto focus:outline-none\"\n >\n {view === \"history\" ? (\n <AssistantHistory\n threads={threads.threads}\n loaded={threads.loaded}\n activeThreadId={state.threadId}\n activeBusy={state.status !== \"idle\"}\n canRemove={threads.canRemove}\n onSelect={(id) => {\n chat.switchThread(id);\n setView(\"chat\");\n }}\n onDelete={(id) => void deleteThread(id)}\n />\n ) : (\n // The text-size control zooms the transcript only — not the history\n // view's search box and buttons. `zoom` scales every descendant\n // uniformly regardless of which renderer draws the conversation; an\n // inline `font-size` would not (the transcript's text utilities set\n // absolute rem sizes), and `transform: scale` would break the scroll\n // container by keeping the original layout box.\n <div className=\"px-2 py-3\" style={{ zoom: font.scale }}>\n {renderTranscript ? (\n renderTranscript(transcriptView)\n ) : (\n <AssistantTranscript\n view={transcriptView}\n renderMarkdown={renderMarkdown}\n toolRenderers={toolRenderers}\n emptyState={\n <p className=\"px-4 py-8 text-center text-muted-foreground text-sm\">\n {EMPTY_STATE}\n </p>\n }\n />\n )}\n </div>\n )}\n </div>\n\n {/* Error / low-balance banners */}\n {errorView && (\n <div\n role=\"alert\"\n className=\"mx-4 mb-2 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm\"\n >\n <p className=\"text-foreground\">{errorView.message}</p>\n {errorView.cta && (\n <button\n type=\"button\"\n onClick={() => navigate?.(errorView.cta?.to ?? \"\")}\n className=\"mt-1 text-primary text-xs\"\n >\n {errorView.cta.label} →\n </button>\n )}\n </div>\n )}\n {low && (\n <div\n role=\"status\"\n className=\"mx-4 mb-2 rounded-lg border border-border bg-muted/40 px-3 py-2 text-sm\"\n >\n <p className=\"text-foreground\">Your credit balance is running low.</p>\n <button\n type=\"button\"\n onClick={() => navigate?.(\"/app/billing\")}\n className=\"mt-1 text-primary text-xs\"\n >\n Add credits →\n </button>\n </div>\n )}\n\n {/* Continue: a capped turn stopped mid-plan (the step-limit backstop). One\n click resumes it — the thread keeps the full context — instead of making\n the user type \"continue\". Shown only when idle in the chat view; a new\n turn (including this one) clears `capped`. */}\n {state.capped &&\n state.status === \"idle\" &&\n !chat.restoring &&\n view === \"chat\" && (\n <div\n role=\"status\"\n className=\"mx-4 mb-2 flex items-center gap-3 rounded-lg border border-border bg-muted/40 px-3 py-2 text-sm\"\n >\n <p className=\"min-w-0 flex-1 text-foreground\">\n Paused after a lot of steps.\n </p>\n <button\n type=\"button\"\n onClick={() => {\n setView(\"chat\");\n // Sends \"continue\" as an ordinary user message — the same words\n // the prior capped-turn copy told users to type. Resume needs no\n // special backend handling: the server replays the thread's full\n // history on every turn, so the model picks up the interrupted\n // work from context. (This is the automated form of that manual\n // instruction, not a new magic string the backend must decode.)\n chat.send(\"continue\");\n }}\n className=\"shrink-0 rounded-lg bg-primary px-3 py-1.5 font-semibold text-primary-foreground text-xs transition hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n Continue\n </button>\n </div>\n )}\n\n {/* Composer: the model picker sits directly above the input, so the model\n the next turn will use reads as part of the composer. */}\n <div className=\"border-border border-t p-2\">\n {/* Running indicator: while a turn streams, the composer's Send becomes a\n Stop button — on its own an easy-to-miss signal. This animated row makes\n \"the assistant is working\" unmistakable regardless of the transcript\n renderer in use. */}\n {streaming && (\n <div className=\"px-2 pb-1.5\" aria-label=\"Assistant is working\">\n <WorkingIndicator />\n </div>\n )}\n <ChatComposer\n onSend={(message) => {\n setView(\"chat\");\n chat.send(message);\n }}\n onCancel={chat.stop}\n isStreaming={streaming}\n disabled={chat.restoring || state.status === \"awaiting_confirm\"}\n placeholder=\"Message the assistant…\"\n controls={\n pickerModels.length > 0 ? (\n <ModelPicker\n value={pickerValue}\n onChange={(id) =>\n chat.setModel(nextModelSelection(id, models.default))\n }\n models={pickerModels}\n />\n ) : (\n <span className=\"px-1 text-muted-foreground text-xs\">\n Default model\n </span>\n )\n }\n />\n </div>\n </div>\n );\n}\n","/**\n * The assistant's conversation history as a full-panel view: a searchable,\n * recency-sorted list of past threads, each showing its title and a relative\n * \"last active\" time, with inline delete. Replaces the cramped header dropdown —\n * inside an already-narrow side panel, a full-height list is far easier to scan\n * and navigate. Selection, deletion, and refresh are owned by the host panel;\n * this component is presentational and holds only its own search query.\n */\n\nimport { Search, Trash2 } from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\nimport { timeAgo } from \"./time-ago\";\nimport type { AssistantThreadSummary } from \"./client\";\n\nexport interface AssistantHistoryProps {\n threads: AssistantThreadSummary[];\n /** True once a fetch has settled at least once (drives empty-vs-loading copy). */\n loaded: boolean;\n /** The thread the live conversation is on, highlighted in the list. */\n activeThreadId: string | null;\n /** Whether the active thread is mid-turn — its delete is disabled (the stream\n * is still writing to it). */\n activeBusy: boolean;\n /** Whether the transport supports deletion (drives the delete affordance). */\n canRemove: boolean;\n onSelect: (threadId: string) => void;\n onDelete: (threadId: string) => void;\n}\n\n/**\n * Parse an ISO timestamp to epoch ms for `timeAgo`. Returns null for an absent\n * or unparseable value, so a row simply omits its time rather than rendering\n * \"NaN\" — thread summaries can carry an empty `updatedAt` on older servers.\n */\nfunction parsedTime(iso: string): number | null {\n if (!iso) return null;\n const ms = Date.parse(iso);\n return Number.isFinite(ms) ? ms : null;\n}\n\nexport function AssistantHistory({\n threads,\n loaded,\n activeThreadId,\n activeBusy,\n canRemove,\n onSelect,\n onDelete,\n}: AssistantHistoryProps) {\n const [query, setQuery] = useState(\"\");\n\n // Most-recently-updated first; an unparseable/absent time sorts last. The\n // spread keeps the hook's array intact, and a stable sort preserves the\n // server's order among equal times.\n const sorted = useMemo(\n () =>\n [...threads].sort((a, b) => {\n const ta = parsedTime(a.updatedAt);\n const tb = parsedTime(b.updatedAt);\n // Most-recently-updated first; rows without a parseable time sort last,\n // and two such rows keep their existing order.\n if (ta === null && tb === null) return 0;\n if (ta === null) return 1;\n if (tb === null) return -1;\n return tb - ta;\n }),\n [threads],\n );\n\n const trimmed = query.trim().toLowerCase();\n const visible = useMemo(\n () =>\n trimmed\n ? // Match the title as displayed, so searching \"untitled\" finds the\n // rows that render as \"Untitled conversation\".\n sorted.filter((t) =>\n (t.title ?? \"Untitled conversation\")\n .toLowerCase()\n .includes(trimmed),\n )\n : sorted,\n [sorted, trimmed],\n );\n\n return (\n <div className=\"flex h-full flex-col\">\n <div className=\"border-border border-b p-2\">\n <div className=\"relative\">\n <Search className=\"-translate-y-1/2 pointer-events-none absolute top-1/2 left-2.5 h-3.5 w-3.5 text-muted-foreground\" />\n <input\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search conversations\"\n aria-label=\"Search conversations\"\n className=\"w-full rounded-md border border-border bg-surface-container-high py-1.5 pr-2 pl-8 text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n </div>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {visible.length === 0 ? (\n <p className=\"px-3 py-6 text-center text-muted-foreground text-xs\">\n {!loaded\n ? \"Loading…\"\n : trimmed\n ? \"No conversations match your search.\"\n : \"No past conversations yet.\"}\n </p>\n ) : (\n <ul className=\"py-1\">\n {visible.map((t) => {\n const active = t.id === activeThreadId;\n const ms = parsedTime(t.updatedAt);\n const busyActive = active && activeBusy;\n const title = t.title ?? \"Untitled conversation\";\n return (\n <li\n key={t.id}\n className={`group flex items-center transition-colors hover:bg-muted/60 ${\n active ? \"bg-primary/10\" : \"\"\n }`}\n >\n <button\n type=\"button\"\n onClick={() => onSelect(t.id)}\n className=\"flex min-w-0 flex-1 flex-col gap-0.5 px-3 py-2 text-left\"\n >\n <span\n className={`truncate text-sm ${\n active ? \"font-medium text-foreground\" : \"text-foreground\"\n }`}\n >\n {title}\n </span>\n {ms != null && (\n <span className=\"text-[11px] text-muted-foreground\">\n {timeAgo(ms)}\n </span>\n )}\n </button>\n {canRemove && (\n <button\n type=\"button\"\n onClick={() => onDelete(t.id)}\n disabled={busyActive}\n aria-label={`Delete conversation: ${title}`}\n title={\n busyActive\n ? \"Can't delete while this conversation is active\"\n : \"Delete conversation\"\n }\n // Always visible on touch devices (no hover to reveal it).\n className=\"shrink-0 p-2 text-muted-foreground opacity-0 transition [@media(hover:none)]:opacity-100 hover:text-destructive focus-visible:opacity-100 group-hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-30\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </div>\n );\n}\n","/** Relative time label from an epoch-ms timestamp (\"just now\", \"5m ago\", \"3h\n * ago\"). Self-contained so the assistant subpath carries no design-system dep. */\nexport function timeAgo(ts: number): string {\n const secs = Math.floor((Date.now() - ts) / 1000)\n if (secs < 5) return \"just now\"\n if (secs < 60) return `${secs}s ago`\n const mins = Math.floor(secs / 60)\n if (mins < 60) return `${mins}m ago`\n const hrs = Math.floor(mins / 60)\n return `${hrs}h ago`\n}\n","/**\n * Confirmation card for a mutating action the assistant proposed (create a\n * workflow, author a workflow + skills, run a workflow, manage a key, …). Shows\n * the action heading, its scalar fields, any new skills, a body preview (a\n * workflow renders as a node graph via the injected `renderGraph`, with a YAML\n * toggle; other bodies render verbatim), the integration requirements with a\n * connect affordance, and Confirm/Cancel.\n *\n * The body preview's graph is injected so this card — in the always-loaded\n * `./assistant` entry — doesn't pull the graph's `@xyflow/react` dependency; the\n * host wires `renderGraph` from `./workflows`. Navigation is injected too.\n */\n\nimport { type ReactNode, useState } from \"react\";\nimport { ProviderLogo } from \"../web-react/provider-logo\";\nimport { describeProposal } from \"./presentation\";\nimport { providerLabel } from \"./provider-label\";\nimport type { ConnectionRequirement, PendingProposal } from \"./types\";\n\nexport interface ProposalCardProps {\n proposal: PendingProposal;\n /** True while this proposal's confirmation is in flight (disables the buttons). */\n confirming: boolean;\n onConfirm: () => void;\n onCancel: () => void;\n /** Host navigation for connect targets / the integrations page. */\n navigate?: (path: string) => void;\n /** Render the workflow YAML as a node graph (the `./workflows` WorkflowGraph).\n * When absent, the YAML is shown as text. */\n renderGraph?: (yaml: string) => ReactNode;\n}\n\nexport function ProposalCard({\n proposal,\n confirming,\n onConfirm,\n onCancel,\n navigate,\n renderGraph,\n}: ProposalCardProps) {\n const view = describeProposal(proposal);\n const [tab, setTab] = useState<\"graph\" | \"yaml\">(\"graph\");\n const isWorkflow = view.preview?.kind === \"workflow\";\n const showGraph = isWorkflow && !!renderGraph;\n\n return (\n <div className=\"rounded-lg border border-primary/40 bg-card p-3 text-sm\">\n <p className=\"font-medium text-foreground\">{view.title}</p>\n <p className=\"text-muted-foreground text-xs\">\n Confirm to run this action on your account.\n </p>\n\n {view.fields.length > 0 && (\n <dl className=\"mt-2 space-y-1\">\n {view.fields.map((f) => (\n <div key={f.label} className=\"flex gap-2 text-xs\">\n <dt className=\"shrink-0 text-muted-foreground\">{f.label}</dt>\n <dd className=\"truncate text-foreground\" title={f.value}>\n {f.value}\n </dd>\n </div>\n ))}\n </dl>\n )}\n\n {view.skills && view.skills.length > 0 && (\n <div className=\"mt-2\">\n <p className=\"text-muted-foreground text-xs\">New skills</p>\n <ul className=\"mt-1 space-y-0.5\">\n {view.skills.map((s) => (\n <li key={s.name} className=\"text-foreground text-xs\">\n <span className=\"font-medium\">{s.name}</span>\n {s.description ? (\n <span className=\"text-muted-foreground\"> — {s.description}</span>\n ) : null}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {view.preview && (\n <div className=\"mt-2\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-muted-foreground text-xs\">{view.preview.label}</p>\n {showGraph && (\n <div className=\"flex gap-2 text-xs\">\n <button\n type=\"button\"\n onClick={() => setTab(\"graph\")}\n className={\n tab === \"graph\" ? \"text-foreground\" : \"text-muted-foreground\"\n }\n >\n Graph\n </button>\n <button\n type=\"button\"\n onClick={() => setTab(\"yaml\")}\n className={\n tab === \"yaml\" ? \"text-foreground\" : \"text-muted-foreground\"\n }\n >\n YAML\n </button>\n </div>\n )}\n </div>\n {showGraph && tab === \"graph\" ? (\n <div className=\"mt-1 h-64 overflow-hidden rounded border border-border\">\n {renderGraph?.(view.preview.content)}\n </div>\n ) : (\n <pre className=\"mt-1 max-h-48 overflow-auto rounded border border-border bg-muted/50 p-2 text-xs\">\n <code>{view.preview.content}</code>\n </pre>\n )}\n </div>\n )}\n\n {proposal.requirements && proposal.requirements.length > 0 && (\n <div className=\"mt-3 rounded border border-border p-2\">\n <p className=\"text-muted-foreground text-xs\">Integrations</p>\n <ul className=\"mt-1 space-y-1\">\n {proposal.requirements.map((r) => (\n <RequirementRow\n key={`${r.provider}-${r.kind ?? \"integration\"}`}\n req={r}\n navigate={navigate}\n />\n ))}\n </ul>\n <p className=\"mt-1 text-muted-foreground text-xs\">\n Connect the items above, then confirm — your proposal stays here until\n you do.\n </p>\n </div>\n )}\n\n {proposal.retryError && (\n <p role=\"alert\" className=\"mt-2 text-destructive text-xs\">\n {proposal.retryError}\n </p>\n )}\n\n <div className=\"mt-3 flex gap-2\">\n <button\n type=\"button\"\n onClick={onConfirm}\n disabled={confirming || !proposal.proposalId}\n className=\"rounded bg-primary px-3 py-1.5 text-primary-foreground text-sm disabled:opacity-50\"\n >\n {confirming ? \"Confirming…\" : \"Confirm\"}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={confirming}\n className=\"rounded border border-border px-3 py-1.5 text-foreground text-sm disabled:opacity-50\"\n >\n Cancel\n </button>\n </div>\n </div>\n );\n}\n\nfunction openConnect(target: string, navigate?: (path: string) => void) {\n // Protocol-relative URLs (//host) inherit the page scheme and point off-site —\n // never a legitimate connect target, so reject outright.\n if (target.startsWith(\"//\")) return;\n // Canonicalize before the scheme check so it can't be smuggled past with\n // leading whitespace or an embedded tab/newline that browsers strip (a regex\n // guard misses those). Only http(s) may EVER navigate — via window.open OR\n // window.location.assign — which closes the `javascript:`/`data:` XSS vector.\n let url: URL;\n try {\n url = new URL(target, window.location.origin);\n } catch {\n return;\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return;\n // A bare relative path (no scheme) is in-app navigation → host router; an\n // absolute http(s) URL is an external link → new tab.\n if (/^[a-z][a-z0-9+.-]*:/i.test(target)) {\n window.open(url.href, \"_blank\", \"noopener,noreferrer\");\n } else if (navigate) {\n navigate(target);\n } else {\n window.location.assign(url.href);\n }\n}\n\nfunction RequirementRow({\n req,\n navigate,\n}: {\n req: ConnectionRequirement;\n navigate?: (path: string) => void;\n}) {\n const label = providerLabel(req.provider);\n const isApp = req.kind === \"github_app\";\n const kindLabel = isApp ? `${label} App` : label;\n const statusText = req.connected\n ? isApp\n ? \"installed\"\n : \"connected\"\n : isApp\n ? \"not installed\"\n : \"not connected\";\n // connectUrl === null means \"no connect target to offer\" (e.g. a github_app\n // requirement on a deploy with no app slug) — show the status without a link.\n const canConnect = !req.connected && req.connectUrl !== null;\n const target = req.connectUrl ?? \"/app/integrations\";\n\n return (\n <li className=\"flex items-center justify-between gap-2 text-xs\">\n <span className=\"flex min-w-0 items-center gap-2\">\n <ProviderLogo provider={req.provider} size={16} />\n <span className=\"truncate text-foreground\">{kindLabel}</span>\n <span className=\"flex shrink-0 items-center gap-1\">\n {/* Filled vs outlined dot is a non-color (shape) cue for the\n connected state, so it reads for color-blind users too — the\n status text alone would lean on color. */}\n <span\n aria-hidden=\"true\"\n className={`h-1.5 w-1.5 rounded-full ${\n req.connected ? \"bg-primary\" : \"border border-muted-foreground\"\n }`}\n />\n <span\n className={req.connected ? \"text-primary\" : \"text-muted-foreground\"}\n >\n {statusText}\n </span>\n </span>\n </span>\n {canConnect && (\n <button\n type=\"button\"\n onClick={() => openConnect(target, navigate)}\n className=\"shrink-0 text-primary\"\n >\n {isApp ? \"Install\" : \"Connect\"} →\n </button>\n )}\n </li>\n );\n}\n","/**\n * Display label for a connector slug (\"github\" → \"GitHub\"). Kept in its own\n * module — separate from the graph model — so consumers in the always-loaded app\n * shell (e.g. ProposalIntegrations) can import the label without pulling the\n * `yaml` parser (a `model.ts` dependency) into the main bundle.\n */\n\nconst PROVIDER_LABELS: Record<string, string> = {\n github: \"GitHub\",\n gitlab: \"GitLab\",\n slack: \"Slack\",\n stripe: \"Stripe\",\n notion: \"Notion\",\n linear: \"Linear\",\n discord: \"Discord\",\n};\n\nexport function providerLabel(provider: string): string {\n const key = provider.toLowerCase();\n return (\n PROVIDER_LABELS[key] ?? provider.charAt(0).toUpperCase() + provider.slice(1)\n );\n}\n","/**\n * The assistant's default transcript renderer, built on web-react's\n * `ChatMessages`. The reducer streams a FLAT, per-segment transcript (user /\n * assistant / `tool` chip / `status` messages, plus turn-level reasoning and\n * pending proposals); `adaptTranscript` collapses each turn into one assistant\n * message whose ordered `segments` carry that turn's text runs and tool chips in\n * emission order, so `ChatMessages` renders them interleaved (text → tool →\n * text) rather than as one text blob followed by a tool group.\n *\n * A host can swap this whole renderer via `AssistantPanelProps.renderTranscript`;\n * the markdown renderer and per-tool detail renderers are injected so this\n * subpath stays free of any product-specific markdown/tool dependency.\n */\n\nimport { useCallback, useMemo, type ReactNode } from \"react\";\nimport {\n ChatMessages,\n type ChatMessageSegment,\n type ChatUiMessage,\n type ToolDetailRenderers,\n} from \"../web-react\";\nimport type { AssistantState } from \"./reducer\";\nimport type {\n AssistantTranscriptView,\n PendingProposal,\n ToolOutcome,\n} from \"./types\";\n\n/**\n * True while a turn is streaming but the model hasn't emitted its first answer\n * token yet — drives the \"thinking\" affordance so a reasoning gap reads as\n * working, not a frozen panel.\n */\nexport function assistantIsThinking(state: AssistantState): boolean {\n if (state.status !== \"streaming\") return false;\n const streaming = state.streamingId\n ? state.messages.find((m) => m.id === state.streamingId)\n : undefined;\n // Thinking until the open assistant bubble receives text (a tool_call closes\n // the bubble, so a running tool also reads as no-open-bubble = still working).\n return !streaming || streaming.text === \"\";\n}\n\ntype ToolStatus = Extract<ChatMessageSegment, { kind: \"tool\" }>[\"call\"][\"status\"];\n\nconst TOOL_STATUS: Record<string, ToolStatus> = {\n running: \"running\",\n ok: \"done\",\n failed: \"error\",\n};\n\nexport interface AdaptedTranscript {\n messages: ChatUiMessage[];\n /** The assistant message under which pending proposals should render, or null\n * when there are none. */\n proposalHostId: string | null;\n /** The current/most-recent turn's assistant message — where the turn cost line\n * renders (it carries the turn's metrics), or null when there is none. */\n metricsHostId: string | null;\n}\n\n/**\n * Reshape a `ToolOutcome` into what web-react's tool-detail card reads. A success\n * (`{ ok: true, result }`) already matches. A failure keeps its error under\n * `outcome.error`, but web-react reads a top-level `outcome.message`/`code` — so\n * flatten it, else an expanded failed tool card shows a generic \"Tool failed\"\n * instead of the real server error.\n */\nfunction adaptToolResult(outcome: ToolOutcome): unknown {\n if (outcome.ok) return { ok: true, result: outcome.result };\n return { ok: false, message: outcome.error?.message, code: outcome.error?.code };\n}\n\n/** An assistant turn message with `segments` guaranteed present, so the fold can\n * push to it directly. Every turn message is created by `openTurn`. */\ntype TurnMessage = ChatUiMessage & { segments: ChatMessageSegment[] };\n\n/**\n * Fold the transcript view into web-react `ChatUiMessage[]`: each user message is\n * 1:1; the assistant/`tool`/`status` messages between two user turns collapse\n * into one assistant message whose ordered `segments` carry the turn's text runs\n * and tool chips IN EMISSION ORDER (with each finished tool's outcome as the chip\n * `result`). The joined text is also kept on `content` — web-react reads it as the\n * \"answer has started\" signal that gates the reasoning box. The live turn's\n * reasoning preview and model label hang on the last assistant message, and\n * `proposalHostId` names the message the pending proposals render under.\n */\nexport function adaptTranscript(view: AssistantTranscriptView): AdaptedTranscript {\n const messages: ChatUiMessage[] = [];\n let turn: TurnMessage | null = null;\n // The assistant message of the CURRENT turn — the one opened since the most\n // recent user message — or null when the live turn has produced no assistant\n // segment yet. Reset on each user message so the live turn's reasoning, model\n // label, and pending proposal can never attach to a previous turn's bubble.\n let currentTurnAssistant: TurnMessage | null = null;\n\n const openTurn = (id: string): TurnMessage => {\n const message: TurnMessage = { id, role: \"assistant\", content: \"\", segments: [] };\n messages.push(message);\n turn = message;\n currentTurnAssistant = message;\n return message;\n };\n\n // Append a text run to both the ordered segments (the rendered, interleaved\n // body) and the joined `content` (which gates the reasoning box). Kept in\n // lockstep so the two never disagree.\n const appendText = (message: TurnMessage, text: string) => {\n if (!text.trim()) return;\n message.segments.push({ kind: \"text\", content: text });\n message.content = message.content ? `${message.content}\\n\\n${text}` : text;\n };\n\n for (const msg of view.messages) {\n if (msg.role === \"user\") {\n messages.push({ id: msg.id, role: \"user\", content: msg.text });\n turn = null;\n currentTurnAssistant = null;\n } else if (msg.role === \"assistant\") {\n const active = turn ?? openTurn(msg.id);\n appendText(active, msg.text);\n currentTurnAssistant = active;\n } else if (msg.role === \"tool\") {\n // A tool row exists only to carry its activity chip; with no tool metadata\n // there is nothing to render, so skip it rather than open a phantom bubble.\n if (!msg.tool) continue;\n // When the tool opens the turn (no preamble text), the synthesized\n // assistant bubble needs an id distinct from the tool chip's (which reuses\n // `msg.id`), or the two would collide.\n const active = turn ?? openTurn(`turn-${msg.id}`);\n currentTurnAssistant = active;\n active.segments.push({\n kind: \"tool\",\n call: {\n id: msg.id,\n name: msg.tool.name,\n // An unmapped status resolves to \"error\", not \"running\": a stuck\n // spinner would hide a finished or failed tool.\n status: TOOL_STATUS[msg.tool.status] ?? \"error\",\n ...(msg.tool.args ? { args: msg.tool.args } : {}),\n ...(msg.tool.outcome ? { result: adaptToolResult(msg.tool.outcome) } : {}),\n },\n });\n } else {\n // `status` — an informational system note that ends the assistant turn.\n messages.push({ id: msg.id, role: \"system\", content: msg.text });\n turn = null;\n }\n }\n\n let proposalHostId: string | null = null;\n if (view.pendingProposals.length > 0) {\n // A propose-only turn may carry no assistant segment yet — synthesize a host\n // in the current turn so the proposal card still has somewhere to render.\n if (!currentTurnAssistant) {\n currentTurnAssistant = openTurn(\n `proposal-host-${view.pendingProposals[0]!.callId}`,\n );\n }\n proposalHostId = currentTurnAssistant.id;\n }\n\n // Live reasoning + model label + settled metrics belong to the current turn's\n // assistant bubble (including a host synthesized just above for a propose-only\n // turn, so a turn that only reasons then proposes still shows its thinking).\n if (currentTurnAssistant) {\n if (view.reasoning) currentTurnAssistant.reasoning = view.reasoning;\n if (view.model) currentTurnAssistant.modelUsed = view.model;\n if (view.usage) {\n if (view.usage.completionTokens != null)\n currentTurnAssistant.completionTokens = view.usage.completionTokens;\n if (view.usage.promptTokens != null)\n currentTurnAssistant.promptTokens = view.usage.promptTokens;\n if (view.usage.durationMs != null)\n currentTurnAssistant.durationMs = view.usage.durationMs;\n }\n }\n\n // A turn that produced no body and had nothing turn-level hung on it renders as\n // a bare \"Assistant\" header. That state is the at-send frame before the first\n // delta; drop it so an empty turn never flashes a blank bubble. The proposal\n // host is exempt: it intentionally carries the pending proposal card.\n const isEmptyShell = (m: ChatUiMessage): boolean =>\n m.role === \"assistant\" &&\n m.content === \"\" &&\n (m.segments?.length ?? 0) === 0 &&\n m.reasoning == null &&\n m.modelUsed == null &&\n m.completionTokens == null &&\n m.promptTokens == null &&\n m.durationMs == null &&\n m.id !== proposalHostId;\n\n return {\n messages: messages.filter((m) => !isEmptyShell(m)),\n proposalHostId,\n metricsHostId:\n currentTurnAssistant && !isEmptyShell(currentTurnAssistant)\n ? currentTurnAssistant.id\n : null,\n };\n}\n\n/** Sub-cent turn costs need more precision than dollars-and-cents. */\nfunction formatTurnCost(costUsd: number): string {\n return costUsd < 0.01 ? `$${costUsd.toFixed(4)}` : `$${costUsd.toFixed(2)}`;\n}\n\n/** A named component (rather than calling `render()` inline in a map) gives React\n * a stable, keyed element per proposal so cards reconcile instead of remount. */\nfunction ProposalSlot({\n proposal,\n render,\n}: {\n proposal: PendingProposal;\n render: (proposal: PendingProposal) => ReactNode;\n}) {\n return <>{render(proposal)}</>;\n}\n\nexport interface AssistantTranscriptProps {\n view: AssistantTranscriptView;\n /** Markdown renderer for assistant content; defaults to plain pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode;\n /** Per-tool custom detail renderers for expanded tool cards. */\n toolRenderers?: ToolDetailRenderers;\n /** Zero-state shown for a fresh, non-streaming thread. */\n emptyState?: ReactNode;\n}\n\n/**\n * Render the assistant conversation with web-react's `ChatMessages`. Pending\n * proposals render via the panel's bound `view.renderProposal`, placed inline\n * after the proposing turn through `renderExtras`; the settled turn cost renders\n * once under its assistant bubble.\n */\nexport function AssistantTranscript({\n view,\n renderMarkdown,\n toolRenderers,\n emptyState,\n}: AssistantTranscriptProps) {\n const { messages, proposalHostId, metricsHostId } = useMemo(\n () => adaptTranscript(view),\n [view],\n );\n\n // Stable identity: web-react memoizes its per-message markdown parse on the\n // `renderMarkdown` reference, so a fresh closure each render (the `view` object\n // changes every stream tick) would re-parse every message on every token.\n const markdown = useCallback(\n (content: string) => (renderMarkdown ? renderMarkdown(content) : content),\n [renderMarkdown],\n );\n\n if (messages.length === 0 && !view.isStreaming) {\n return <>{emptyState}</>;\n }\n\n return (\n <ChatMessages\n messages={messages}\n // ChatMessages derives the streaming message internally, so only\n // `isStreaming` is needed; `view.isThinking` is a subset of it.\n loading={view.isStreaming}\n agentLabel=\"Assistant\"\n renderMarkdown={markdown}\n toolRenderers={toolRenderers}\n renderEmpty={() => <>{emptyState}</>}\n renderExtras={(message) => {\n const proposals =\n message.id === proposalHostId && view.pendingProposals.length > 0 ? (\n <div className=\"mt-3 flex flex-col gap-3\">\n {view.pendingProposals.map((proposal) => (\n <ProposalSlot\n key={proposal.callId}\n proposal={proposal}\n render={view.renderProposal}\n />\n ))}\n </div>\n ) : null;\n // The settled turn's at-cost figure, shown once under its assistant\n // bubble. Hidden while streaming and for a replayed (uncharged) turn.\n const cost =\n message.id === metricsHostId &&\n !view.isStreaming &&\n view.usage?.costUsd != null &&\n !view.usage.replayed ? (\n <p className=\"mt-1 text-[11px] text-muted-foreground\">\n {formatTurnCost(view.usage.costUsd)} this turn\n </p>\n ) : null;\n if (!proposals && !cost) return null;\n return (\n <>\n {proposals}\n {cost}\n </>\n );\n }}\n />\n );\n}\n","/**\n * Persisted, user-adjustable presentation preferences for the assistant drawer:\n * its width (drag-to-resize) and a font-size scale. Both survive reloads via\n * localStorage and are clamped to sane bounds. Kept out of the components so the\n * SSR-safe persistence and clamping live in one place and stay testable.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\n/** Narrowest the drawer may be dragged — below this the chat is unusable. */\nexport const MIN_PANEL_WIDTH = 360;\n/** Default drawer width — matches the previous fixed `max-w-md` (28rem). */\nexport const DEFAULT_PANEL_WIDTH = 448;\n/** Widest the drawer may occupy, as a fraction of the viewport. */\nconst MAX_PANEL_WIDTH_FRACTION = 0.95;\n\n/** Font-size scale bounds and step for the A−/A+ control. 1 = the design\n * default; the panel applies this as a CSS `zoom` on the transcript so the\n * whole conversation scales uniformly. */\nexport const MIN_FONT_SCALE = 0.875;\nexport const MAX_FONT_SCALE = 1.5;\nexport const DEFAULT_FONT_SCALE = 1;\nconst FONT_SCALE_STEP = 0.125;\n\nconst WIDTH_KEY = \"assistant.panel.width\";\nconst FONT_SCALE_KEY = \"assistant.panel.fontScale\";\n\nfunction readNumber(key: string): number | null {\n try {\n const raw = window.localStorage.getItem(key);\n // Empty/whitespace → fall back to the default. Without this, `Number(\"\")`\n // is 0 (finite), which would clamp to MIN instead of using the default.\n if (raw == null || raw.trim() === \"\") return null;\n const n = Number(raw);\n return Number.isFinite(n) ? n : null;\n } catch {\n return null;\n }\n}\n\nfunction writeNumber(key: string, value: number): void {\n try {\n window.localStorage.setItem(key, String(value));\n } catch {\n // Storage can be unavailable (private mode, quota) — preferences are a\n // convenience, never a hard dependency, so a failed write is silent.\n }\n}\n\n/** The largest width the drawer may take on the current viewport. Returns a\n * large FINITE fallback with no window (the app is a client SPA, so this is\n * belt-and-suspenders) — Infinity would be an invalid `aria-valuemax`. */\nfunction maxPanelWidth(): number {\n if (typeof window === \"undefined\") return 9999;\n return Math.round(window.innerWidth * MAX_PANEL_WIDTH_FRACTION);\n}\n\nfunction clampWidth(value: number): number {\n return Math.min(\n Math.max(Math.round(value), MIN_PANEL_WIDTH),\n maxPanelWidth(),\n );\n}\n\nfunction clampScale(value: number): number {\n // Round to the step grid so repeated +/- never accumulates float drift.\n const stepped = Math.round(value / FONT_SCALE_STEP) * FONT_SCALE_STEP;\n return Math.min(Math.max(stepped, MIN_FONT_SCALE), MAX_FONT_SCALE);\n}\n\nexport interface PanelWidth {\n /** Current width in px. Apply as an inline `width` only on desktop. */\n width: number;\n /** Current max allowed width in px (viewport-derived; updates on resize).\n * Exposed for an accurate `aria-valuemax` on the resize control. */\n maxWidth: number;\n /** Set an absolute width (clamped + persisted). Use on drag end / discrete\n * changes — NOT on every drag tick. */\n setWidth: (next: number) => void;\n /** Set an absolute width (clamped, NOT persisted). Use during a live drag so\n * the panel tracks the pointer without thrashing localStorage every tick. */\n previewWidth: (next: number) => void;\n /** Nudge by a delta (keyboard resize); clamped + persisted. */\n nudgeWidth: (deltaPx: number) => void;\n}\n\n/**\n * The drawer's persisted width. Initialized to the default so first render is\n * stable; the stored value is read in an effect and applied after mount.\n * Re-clamps on viewport resize so a stored width can never exceed the current\n * window, and tracks the live max for the resize control's ARIA bounds.\n */\nexport function usePanelWidth(): PanelWidth {\n const [width, setWidthState] = useState(DEFAULT_PANEL_WIDTH);\n const [maxWidth, setMaxWidth] = useState(() => maxPanelWidth());\n // The user's explicit width preference. The rendered `width` is this clamped\n // to the current viewport; a transient shrink clamps only the display, so when\n // the viewport grows back the preference is restored (rather than the shrink\n // permanently overwriting the choice).\n const desiredRef = useRef(DEFAULT_PANEL_WIDTH);\n\n useEffect(() => {\n setMaxWidth(maxPanelWidth());\n const stored = readNumber(WIDTH_KEY);\n if (stored != null) {\n desiredRef.current = Math.max(Math.round(stored), MIN_PANEL_WIDTH);\n setWidthState(clampWidth(stored));\n }\n }, []);\n\n // On viewport change, recompute the display from the DESIRED preference (not\n // the possibly-already-clamped current width) so growing the window restores\n // it. Keep the reported max current too, for an accurate aria-valuemax.\n useEffect(() => {\n const onResize = () => {\n setMaxWidth(maxPanelWidth());\n setWidthState(clampWidth(desiredRef.current));\n };\n window.addEventListener(\"resize\", onResize);\n return () => window.removeEventListener(\"resize\", onResize);\n }, []);\n\n const setWidth = useCallback((next: number) => {\n const clamped = clampWidth(next);\n desiredRef.current = clamped;\n writeNumber(WIDTH_KEY, clamped);\n setWidthState(clamped);\n }, []);\n\n const previewWidth = useCallback((next: number) => {\n // Live drag: display only — no persist, no change to the desired preference\n // (drag end calls setWidth to commit).\n setWidthState(clampWidth(next));\n }, []);\n\n const nudgeWidth = useCallback((deltaPx: number) => {\n const clamped = clampWidth(desiredRef.current + deltaPx);\n desiredRef.current = clamped;\n writeNumber(WIDTH_KEY, clamped);\n setWidthState(clamped);\n }, []);\n\n return { width, maxWidth, setWidth, previewWidth, nudgeWidth };\n}\n\nexport interface FontScale {\n scale: number;\n increase: () => void;\n decrease: () => void;\n canIncrease: boolean;\n canDecrease: boolean;\n}\n\n/** The panel's persisted font-size scale, with bounded A−/A+ controls. */\nexport function useFontScale(): FontScale {\n const [scale, setScaleState] = useState(DEFAULT_FONT_SCALE);\n\n useEffect(() => {\n const stored = readNumber(FONT_SCALE_KEY);\n if (stored != null) setScaleState(clampScale(stored));\n }, []);\n\n const step = useCallback((delta: number) => {\n setScaleState((prev) => {\n const clamped = clampScale(prev + delta);\n writeNumber(FONT_SCALE_KEY, clamped);\n return clamped;\n });\n }, []);\n\n return {\n scale,\n increase: () => step(FONT_SCALE_STEP),\n decrease: () => step(-FONT_SCALE_STEP),\n canIncrease: scale < MAX_FONT_SCALE - 1e-9,\n canDecrease: scale > MIN_FONT_SCALE + 1e-9,\n };\n}\n\n/**\n * Whether the viewport is at the `md` breakpoint or wider. The drawer is a\n * full-screen sheet below `md` (no resize), and a width-constrained side panel\n * at/above it. Defaults to `true` for SSR/first paint — the dialog only renders\n * after a client interaction, by which point the effect has corrected it.\n */\nexport function useIsDesktop(): boolean {\n // Read matchMedia synchronously on first render (client SPA) so the drawer\n // never first-paints at desktop width on a mobile viewport; the default only\n // applies in the no-window/no-matchMedia case.\n const [isDesktop, setIsDesktop] = useState(() =>\n typeof window !== \"undefined\" && typeof window.matchMedia === \"function\"\n ? window.matchMedia(\"(min-width: 768px)\").matches\n : true,\n );\n useEffect(() => {\n // Mirror the initializer guard: a runtime without matchMedia (jsdom, some\n // embedded webviews) keeps the desktop default rather than throwing — the\n // dock is in the always-mounted shell, so a throw here would break it.\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return;\n }\n const mq = window.matchMedia(\"(min-width: 768px)\");\n const update = () => setIsDesktop(mq.matches);\n update();\n mq.addEventListener(\"change\", update);\n return () => mq.removeEventListener(\"change\", update);\n }, []);\n return isDesktop;\n}\n","import { type RefObject, useCallback, useLayoutEffect, useRef } from \"react\";\n\n/** Distance (px) from the bottom within which we still consider the user\n * \"pinned\" — a small slack so sub-pixel rounding, or a programmatic scroll that\n * lands a hair short, doesn't unstick auto-follow. */\nconst STICK_SLACK_PX = 48;\n\nexport interface StickToBottomOptions {\n /** Auto-follow only applies while this is true (e.g. the chat view is shown,\n * not the history view). */\n enabled: boolean;\n /** A value that changes whenever streamed content grows or the turn's shape\n * changes — the trigger to (re-)scroll to the bottom while pinned. */\n contentSignature: string | number;\n /** Current streaming turn id (null between turns). A null→id transition\n * re-arms follow so a fresh response scrolls from the top. */\n streamingId: string | null;\n /** Current thread id (null before any thread loads). A change re-arms follow so\n * switching threads lands at the newest content. */\n threadId: string | null;\n}\n\n/**\n * Keep a scroll container pinned to its newest content as it streams, while\n * yielding the instant the user scrolls up to read. It re-arms when the user\n * returns to the bottom (via {@link onScroll}) or when a new turn/thread starts.\n * Returns the `onScroll` handler to attach to the container.\n *\n * Extracted from the panel so the follow/yield/re-arm contract is unit-testable\n * independent of the full component (jsdom doesn't compute scroll geometry, so a\n * hook test mocks the element's `scrollHeight`/`clientHeight`/`scrollTop`).\n */\nexport function useStickToBottom(\n ref: RefObject<HTMLElement | null>,\n { enabled, contentSignature, streamingId, threadId }: StickToBottomOptions,\n): { onScroll: () => void } {\n const stuckRef = useRef(true);\n const prevStreamingRef = useRef<string | null>(streamingId);\n const prevThreadRef = useRef<string | null>(threadId);\n\n // The user's scroll position drives whether we keep following. A programmatic\n // scroll also fires this, so compare against a small slack rather than exact\n // equality. Ignore scrolls while disabled: the same container is reused for\n // the (disabled) history view, and scrolling that must not unstick the chat.\n const onScroll = useCallback(() => {\n if (!enabled) return;\n const el = ref.current;\n if (!el) return;\n stuckRef.current =\n el.scrollHeight - el.scrollTop - el.clientHeight < STICK_SLACK_PX;\n }, [ref, enabled]);\n\n // ONE layout effect (pre-paint) does the re-arm and then the scroll, in that\n // order. Re-arming here — not in a passive `useEffect` — is what makes a thread\n // switch work: switching threads updates `threadId` AND loads the new thread's\n // content (`contentSignature`) in the SAME render, so if the user was scrolled\n // up a passive re-arm would run too late (after this effect already saw the\n // stale unstuck ref and skipped the scroll), leaving them pinned to the old\n // position. Doing both here keeps them synchronous.\n useLayoutEffect(() => {\n // A new streaming turn (streamingId null→id) or a thread switch re-arms\n // follow, so a fresh response scrolls from the top even if the user had\n // scrolled up during the previous turn. A turn ENDING (id→null) is neither,\n // so it never yanks a user who scrolled up to read.\n const turnStarted =\n streamingId != null && streamingId !== prevStreamingRef.current;\n const threadChanged = threadId !== prevThreadRef.current;\n prevStreamingRef.current = streamingId;\n prevThreadRef.current = threadId;\n if (turnStarted || threadChanged) stuckRef.current = true;\n\n if (!enabled || !stuckRef.current) return;\n const el = ref.current;\n if (el) el.scrollTop = el.scrollHeight;\n }, [ref, enabled, contentSignature, streamingId, threadId]);\n\n return { onScroll };\n}\n","/**\n * Shared launcher state for the assistant dock. The dock is mounted once in the\n * app shell, but other surfaces (e.g. the Workflows page) need to open it and\n * prefill the composer with a starter prompt. This context owns the dock's\n * open state plus a one-shot composer seed, so any `/app/*` surface can call\n * `openAssistant(\"Create a workflow that …\")` without reaching into the dock.\n */\n\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useMemo,\n useState,\n} from \"react\";\n\nexport interface AssistantLauncher {\n /** Whether the assistant drawer is open. */\n open: boolean;\n /** A one-shot starter prompt to prefill the composer with, or null. */\n seed: string | null;\n /** Open the drawer; optionally prefill the composer with `seed`. */\n openAssistant: (seed?: string) => void;\n closeAssistant: () => void;\n /** Clear the pending seed once the composer has applied it (consume-once). */\n clearSeed: () => void;\n}\n\nconst AssistantLauncherContext = createContext<AssistantLauncher | null>(null);\n\nexport function AssistantLauncherProvider({\n children,\n}: {\n children: ReactNode;\n}) {\n const [open, setOpen] = useState(false);\n const [seed, setSeed] = useState<string | null>(null);\n\n const openAssistant = useCallback((next?: string) => {\n // Only replace the seed when one is supplied, so opening the drawer with no\n // argument never clobbers a starter another caller just set.\n if (next != null) setSeed(next);\n setOpen(true);\n }, []);\n const closeAssistant = useCallback(() => setOpen(false), []);\n const clearSeed = useCallback(() => setSeed(null), []);\n\n const value = useMemo(\n () => ({ open, seed, openAssistant, closeAssistant, clearSeed }),\n [open, seed, openAssistant, closeAssistant, clearSeed],\n );\n\n return (\n <AssistantLauncherContext.Provider value={value}>\n {children}\n </AssistantLauncherContext.Provider>\n );\n}\n\nexport function useAssistantLauncher(): AssistantLauncher {\n const ctx = useContext(AssistantLauncherContext);\n if (!ctx) {\n throw new Error(\n \"useAssistantLauncher must be used within an AssistantLauncherProvider\",\n );\n }\n return ctx;\n}\n","import { type KeyboardEvent, type PointerEvent, useRef } from \"react\";\nimport { MIN_PANEL_WIDTH } from \"./usePanelPrefs\";\n\n/**\n * Drag-to-resize grip on the assistant drawer's left edge. Pointer capture keeps\n * the drag alive while the cursor moves anywhere on screen; arrow keys resize in\n * coarse steps for keyboard users. The drawer is right-anchored, so dragging\n * left widens it. The in-memory width updates every move (`onPreview`); the\n * final width is persisted once on release (`onCommit`).\n */\nexport function ResizeHandle({\n width,\n maxWidth,\n onPreview,\n onCommit,\n onNudge,\n}: {\n width: number;\n maxWidth: number;\n /** Live (non-persisted) width update during a drag. The value is the raw\n * pointer-derived width and is NOT clamped — the consumer must clamp it to its\n * own min/max (the bundled `usePanelWidth.previewWidth` does). */\n onPreview: (next: number) => void;\n /** Persist the final width (drag end). */\n onCommit: (next: number) => void;\n /** Keyboard resize delta (clamped + persisted). */\n onNudge: (deltaPx: number) => void;\n}) {\n const dragRef = useRef<{\n startX: number;\n startWidth: number;\n lastWidth: number;\n } | null>(null);\n\n const onPointerDown = (e: PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return; // primary button / touch / pen only\n e.preventDefault();\n e.currentTarget.setPointerCapture(e.pointerId);\n dragRef.current = {\n startX: e.clientX,\n startWidth: width,\n lastWidth: width,\n };\n };\n\n const onPointerMove = (e: PointerEvent<HTMLDivElement>) => {\n const drag = dragRef.current;\n if (!drag) return;\n // Update the in-memory width every tick (smooth), but don't persist — that\n // would hit localStorage on every pointermove.\n const next = drag.startWidth + (drag.startX - e.clientX);\n // Skip sub-pixel jitter so a touch drag doesn't re-render the panel subtree\n // on every noise event.\n if (Math.abs(next - drag.lastWidth) < 1) return;\n drag.lastWidth = next;\n onPreview(next);\n };\n\n const endDrag = (e: PointerEvent<HTMLDivElement>) => {\n const drag = dragRef.current;\n if (!drag) return;\n dragRef.current = null;\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId);\n }\n // Persist once, on release.\n onCommit(drag.lastWidth);\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n const STEP = 24;\n if (e.key === \"ArrowLeft\") {\n e.preventDefault();\n onNudge(STEP);\n } else if (e.key === \"ArrowRight\") {\n e.preventDefault();\n onNudge(-STEP);\n }\n };\n\n return (\n // biome-ignore lint/a11y/useSemanticElements: a focusable drag handle is an ARIA window-splitter (role=separator); no native HTML element provides this.\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"Resize assistant panel\"\n aria-valuemin={MIN_PANEL_WIDTH}\n aria-valuemax={maxWidth}\n aria-valuenow={width}\n tabIndex={0}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={onKeyDown}\n className=\"group absolute inset-y-0 left-0 z-10 flex w-2 -translate-x-1/2 cursor-ew-resize touch-none items-center justify-center focus:outline-none\"\n >\n <span\n aria-hidden=\"true\"\n className=\"h-10 w-1 rounded-full bg-border transition-colors group-hover:bg-primary group-focus:bg-primary\"\n />\n </div>\n );\n}\n"],"mappings":"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","names":["error","useEffect","useReducer","useReducer","useEffect","useCallback","useEffect","useRef","useState","useRef","useState","useCallback","useEffect","useEffect","useRef","useEffect","useMemo","useRef","useState","useState","jsx","useState","useState","jsx","jsxs","useState","useCallback","useMemo","jsx","jsxs","useMemo","useCallback","useCallback","useEffect","useRef","useState","useCallback","useRef","jsx","jsxs","useState","useRef","useMemo","useEffect","createContext","useCallback","useContext","useMemo","useState","jsx","useRef","jsx","useRef","Fragment","jsx","jsxs","useRef","useEffect"]}