cortena-ui 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/README.md +36 -0
- package/dist/agent-chat/session.js +21 -7
- package/dist/agent-chat/session.js.map +1 -1
- package/dist/agent-chat/store.d.ts +55 -4
- package/dist/agent-chat/store.js +87 -22
- package/dist/agent-chat/store.js.map +1 -1
- package/dist/agent-chat/types.d.ts +11 -0
- package/dist/agent-chat/types.js.map +1 -1
- package/dist/agent-chat.d.ts +3 -3
- package/dist/components/agent-chat-popup.d.ts +2 -2
- package/dist/components/agent-chat-popup.js.map +1 -1
- package/dist/components/agent-chat.d.ts +169 -13
- package/dist/components/agent-chat.js +200 -27
- package/dist/components/agent-chat.js.map +1 -1
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/toast.d.ts +1 -1
- package/package.json +1 -1
- package/src/agent-chat/session.ts +35 -5
- package/src/agent-chat/store.ts +193 -21
- package/src/agent-chat/types.ts +11 -0
- package/src/components/agent-chat-popup.tsx +3 -3
- package/src/components/agent-chat.tsx +576 -149
- package/src/entries/agent-chat.ts +12 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-chat.js","names":[],"sources":["../../src/components/agent-chat.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * AgentChat — the chat surface an extension's own agent runs in.\n *\n * §17 of how-to-create-a-cortena-extension, audit rule P-25. Moved here from\n * `cortenaweb/components/chat/**` so there is one chat and not two: a cut-down\n * widget diverges on approvals, A2UI and MCP Apps within one release, and every\n * fix then has to be made twice. cortenaweb adopts this component as a\n * follow-up; until then the two are kept in step by hand.\n *\n * Everything comes from the `AgentChatClient` prop — nothing here knows about\n * HTTP, AG-UI or a store — which is what lets `test/agent-chat.test.tsx` drive\n * the whole surface from a fake that replays scripted event sequences.\n *\n * Not in this package, on purpose: the MCP App frame. Rendering an extension's\n * own screen means a sandboxed iframe, a `srcdoc` CSP, a JSON-RPC postMessage\n * bridge and a `tools/invoke` proxy holding a credential — a host concern, not\n * a component one. `renderToolCall` is the seam: return a node for a tool call\n * and it replaces the card, return nothing and the default card is drawn.\n */\n\nimport {\n Check,\n ChevronDown,\n ChevronRight,\n MessageSquarePlus,\n Send,\n ShieldCheck,\n ShieldX,\n Sparkles,\n Square,\n X,\n} from \"lucide-react\";\nimport * as React from \"react\";\nimport { A2UIRenderer } from \"@/a2ui/renderer\";\nimport { A2UIErrorCard } from \"@/a2ui/views\";\nimport type { A2UIAction, A2UIMessage } from \"@/a2ui/message\";\nimport { Badge } from \"@/components/badge\";\nimport { Button } from \"@/components/button\";\nimport { ErrorBanner } from \"@/components/error-banner\";\nimport { Markdown } from \"@/components/markdown\";\nimport { Spinner } from \"@/components/spinner\";\nimport { Textarea } from \"@/components/textarea\";\nimport { isHiddenMessage, STORED_TEXT_LIMIT } from \"@/agent-chat/session\";\nimport { isGenericLabel, truncate } from \"@/agent-chat/step-label\";\nimport { type AgentToolEntry, useAgentChat } from \"@/agent-chat/store\";\nimport type {\n AgentApprovalRequest,\n AgentChatClient,\n AgentChatMessage,\n AgentChatStep,\n AgentSession,\n} from \"@/agent-chat/types\";\nimport { sessionTitle } from \"@/agent-chat/types\";\nimport type { A2UIChatBlock } from \"@/agent-chat/a2ui-block\";\nimport { cn } from \"@/lib/cn\";\n\n/* ── stick to bottom ─────────────────────────────────────────────────────── */\n\n/**\n * Should the list jump back to the bottom after its content grew?\n *\n * Judged against the height BEFORE the growth. Measuring afterwards asks \"is\n * the newest content already visible\", which is false by construction for any\n * append taller than the threshold — a paragraph, a code block, an image — so\n * the re-pin skipped exactly when it was needed.\n */\nexport function shouldStickToBottom(params: {\n prevScrollHeight: number;\n scrollTop: number;\n clientHeight: number;\n threshold?: number;\n}): boolean {\n const { prevScrollHeight, scrollTop, clientHeight, threshold = 150 } = params;\n if (prevScrollHeight <= 0) {\n return true;\n }\n return prevScrollHeight - scrollTop - clientHeight < threshold;\n}\n\n/* ── thinking ────────────────────────────────────────────────────────────── */\n\nexport interface AgentThinkingProps {\n text: string;\n /** Open by default while the turn is live, collapsed once it is finished. */\n defaultOpen?: boolean;\n streaming?: boolean;\n}\n\nfunction AgentThinking({ text, defaultOpen, streaming }: AgentThinkingProps) {\n const [open, setOpen] = React.useState(defaultOpen ?? false);\n if (!text) {\n return null;\n }\n return (\n <div data-slot=\"agent-chat-thinking\" className=\"mt-1\">\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((v) => !v)}\n className={cn(\n \"inline-flex items-center gap-1 rounded-[var(--ds-radius-sm)] px-1 py-0.5\",\n \"text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\",\n \"transition-colors duration-[var(--ds-duration-fast)] hover:text-[color:var(--ds-foreground)]\",\n )}\n >\n {open ? <ChevronDown className=\"size-3\" aria-hidden /> : <ChevronRight className=\"size-3\" aria-hidden />}\n {streaming ? \"Thinking…\" : \"Chain of thought\"}\n </button>\n {open ? (\n <div\n data-slot=\"agent-chat-thinking-body\"\n className={cn(\n \"mt-1 max-h-64 overflow-auto rounded-[var(--ds-radius-sm)] border border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-muted)] px-3 py-2 whitespace-pre-wrap\",\n \"text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\",\n )}\n >\n {text}\n </div>\n ) : null}\n </div>\n );\n}\n\n/* ── progress steps ──────────────────────────────────────────────────────── */\n\n/**\n * How long a step runs before it starts saying how long it has been running.\n *\n * Under this, a timer is noise: every call takes a second or two and a number\n * ticking beside each one is a fidget spinner. Over it, the user has begun to\n * wonder whether anything is happening at all, and the number is the answer.\n */\nconst ELAPSED_AFTER_MS = 8_000;\n\n/**\n * `Date.now()`, re-read once a second while `active`.\n *\n * The interval only exists while something is running, so a finished chat sits\n * idle rather than re-rendering the whole transcript once a second for ever.\n */\nfunction useTickingNow(active: boolean): number {\n const [now, setNow] = React.useState(() => Date.now());\n React.useEffect(() => {\n if (!active) {\n return;\n }\n setNow(Date.now());\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, [active]);\n return now;\n}\n\nexport interface AgentStepsProps {\n steps: readonly AgentChatStep[];\n}\n\n/**\n * What the agent is doing, one plain sentence per tool call (SKILLS-9).\n *\n * This is the surface a person watches while they wait, and it is deliberately\n * not the tool strip: the strip is the record — names, arguments, output, the\n * MCP App seam — and reading it requires knowing what `broker_invoke` is. The\n * strip stays at the foot for anyone who wants the detail.\n *\n * NOT a live region of its own. The strip is drawn inside the transcript, and\n * the transcript is already `role=\"log\" aria-live=\"polite\"` — two nested live\n * regions is one announcement per change from the inner one and another from\n * the outer, which is how a screen reader ends up saying every step twice.\n * `aria-label` stays: it names the group for anyone who navigates to it.\n */\nfunction AgentSteps({ steps }: AgentStepsProps) {\n const running = steps.some((step) => step.status === \"running\");\n const now = useTickingNow(running);\n if (steps.length === 0) {\n return null;\n }\n return (\n <div\n data-slot=\"agent-chat-steps\"\n aria-label=\"Progress\"\n className=\"mt-1 flex flex-col gap-0.5\"\n >\n {steps.map((step) => {\n const elapsed = step.status === \"running\" ? now - step.startedAt : 0;\n const seconds = elapsed >= ELAPSED_AFTER_MS ? Math.floor(elapsed / 1000) : 0;\n // Held out of the announcement while the line is still the fallback.\n // The arguments arrive one event after the start, so every call passes\n // through \"Working: broker_invoke\" for a few hundred milliseconds, and\n // a live region reads it out — then reads the real sentence over the\n // top of it. The row is still drawn; it joins the announcement the\n // moment it has something to say, or when it ends, whichever is first.\n const held = step.status === \"running\" && isGenericLabel(step.toolName, step.label);\n return (\n <p\n key={step.id}\n data-slot=\"agent-chat-step\"\n data-status={step.status}\n // The tool name is the model's, not ours, and an attribute is a\n // poor place for 9 kB of it.\n data-tool={truncate(step.toolName, STORED_TEXT_LIMIT)}\n {...(held ? { \"aria-hidden\": true } : {})}\n className={cn(\n \"flex items-start gap-2 text-[length:var(--ds-text-caption-lg)]\",\n step.status === \"error\"\n ? \"text-[color:var(--ds-destructive)]\"\n : \"text-[color:var(--ds-muted-foreground)]\",\n )}\n >\n <span\n aria-hidden\n className=\"mt-0.5 inline-flex size-3.5 shrink-0 items-center justify-center\"\n >\n {step.status === \"running\" ? (\n <Spinner size={12} aria-hidden />\n ) : step.status === \"error\" ? (\n <X className=\"size-3\" />\n ) : step.status === \"stopped\" ? (\n <Square className=\"size-2.5\" />\n ) : step.status === \"paused\" ? (\n // A plain dot, drawn in the muted colour: neither a spinner\n // (nothing is happening) nor a tick (nothing has happened).\n <span className=\"size-1.5 rounded-full bg-[color:var(--ds-muted-foreground)]\" />\n ) : (\n <Check className=\"size-3 text-[color:var(--ds-success)]\" />\n )}\n </span>\n <span className=\"min-w-0\">\n {/* Keyed on the words. A step is re-worded in place when its\n arguments land, and a text node mutated under a live region\n is a change some screen readers do not announce at all; a new\n key makes it a node replacement, which they all do. */}\n <span key={step.label} data-slot=\"agent-chat-step-label\">\n {step.label}\n </span>\n {/* The seconds change once a second, and a live region reads the\n whole line again each time they do. The count is for the eye:\n a screen reader hearing \"Fetching tasks task list\" once, and\n again when it lands, is told everything it needs. */}\n {seconds > 0 ? (\n <span aria-hidden data-slot=\"agent-chat-step-elapsed\">{` · ${seconds}s`}</span>\n ) : null}\n {step.status === \"stopped\" ? (\n <span data-slot=\"agent-chat-step-note\">{\" — Stopped\"}</span>\n ) : null}\n {step.status === \"paused\" ? (\n <span data-slot=\"agent-chat-step-note\">{\" — Waiting for your approval\"}</span>\n ) : null}\n {step.status === \"error\" && step.errorMessage ? (\n <span data-slot=\"agent-chat-step-error\">{` — ${step.errorMessage}`}</span>\n ) : null}\n </span>\n </p>\n );\n })}\n </div>\n );\n}\n\n/* ── A2UI blocks ─────────────────────────────────────────────────────────── */\n\nexport interface AgentA2UIBlocksProps {\n blocks: readonly A2UIChatBlock[] | undefined;\n /** Set on the streaming bubble: actions are disabled while the turn is live. */\n streaming?: boolean;\n onAction?: (action: A2UIAction) => void;\n}\n\n/**\n * Every block of one message, in arrival order.\n *\n * A block carrying a transport `error` — an unclosed fence, a body that did not\n * parse — gets an error card rather than being dropped. A block that is merely\n * malformed inside is the renderer's error to draw, and it draws the parts that\n * were fine.\n */\nfunction AgentA2UIBlocks({ blocks, streaming, onAction }: AgentA2UIBlocksProps) {\n if (!blocks || blocks.length === 0) {\n return null;\n }\n return (\n <div className=\"mt-2 flex flex-col gap-3\" data-slot=\"agent-chat-a2ui\">\n {blocks.map((block, index) => (\n <div key={`${block.surfaceId}:${index}`}>\n {block.error ? (\n <A2UIErrorCard title=\"The agent's UI did not arrive complete\" detail={block.error} />\n ) : null}\n {block.messages.length > 0 ? (\n <A2UIRenderer\n // The payload stays `unknown[]`: it is the agent's, and only the\n // renderer validates it. Every node goes through the catalogue's\n // schema before it is drawn.\n message={block.messages as A2UIMessage[]}\n // A surface still streaming must not fire actions at the agent\n // from a half-built form, so it is read-only until final.\n {...(streaming || !onAction ? {} : { onAction })}\n />\n ) : null}\n </div>\n ))}\n </div>\n );\n}\n\n/* ── bubbles ─────────────────────────────────────────────────────────────── */\n\nfunction AssistantAvatar() {\n return (\n <span\n data-slot=\"agent-chat-avatar\"\n aria-hidden\n className={cn(\n \"grid size-7 shrink-0 place-items-center rounded-[var(--ds-radius-sm)]\",\n \"border border-[var(--ds-ring)] bg-[var(--ds-primary-soft)] text-[color:var(--ds-primary)]\",\n )}\n >\n <Sparkles className=\"size-3.5\" aria-hidden />\n </span>\n );\n}\n\nexport interface AgentMessageBubbleProps {\n message: AgentChatMessage;\n agentName: string;\n /**\n * True while a run is live. Every A2UI surface in the transcript goes\n * read-only, not just the one being drawn: an earlier surface is still a\n * form wired to `sendAction`, and pressing it mid-run starts a second\n * concurrent turn on the same session.\n */\n streaming?: boolean;\n onAction?: (action: A2UIAction) => void;\n}\n\nfunction AgentMessageBubble({ message, agentName, streaming, onAction }: AgentMessageBubbleProps) {\n if (isHiddenMessage(message)) {\n return null;\n }\n if (message.isError) {\n return (\n <div data-slot=\"agent-chat-message\" data-role=\"error\">\n <ErrorBanner message={message.text} />\n </div>\n );\n }\n if (message.role === \"user\") {\n return (\n <div data-slot=\"agent-chat-message\" data-role=\"user\" className=\"flex w-full justify-end\">\n <div\n className={cn(\n \"max-w-[85%] rounded-[var(--ds-radius-lg)] border border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-popover)] px-3.5 py-2 text-[color:var(--ds-foreground)]\",\n )}\n >\n <Markdown compact>{message.text}</Markdown>\n </div>\n </div>\n );\n }\n return (\n <div data-slot=\"agent-chat-message\" data-role=\"assistant\" className=\"flex w-full gap-3\">\n <AssistantAvatar />\n <div className=\"min-w-0 flex-1\">\n <span\n className={cn(\n \"text-[length:var(--ds-text-caption)] font-semibold\",\n \"text-[color:var(--ds-foreground)]\",\n )}\n >\n {agentName}\n </span>\n <AgentThinking text={message.thinkingText ?? \"\"} />\n {message.text ? (\n <div className=\"mt-1\">\n <Markdown compact>{message.text}</Markdown>\n </div>\n ) : null}\n <AgentA2UIBlocks\n blocks={message.a2ui}\n {...(streaming ? { streaming } : {})}\n {...(onAction ? { onAction } : {})}\n />\n </div>\n </div>\n );\n}\n\nexport interface AgentStreamingBubbleProps {\n agentName: string;\n text: string;\n thinkingText: string;\n a2ui: readonly A2UIChatBlock[];\n /** The turn's tool calls in plain words, drawn under the agent's name. */\n steps?: readonly AgentChatStep[];\n}\n\n/**\n * The assistant bubble while a turn is live.\n *\n * The filter chain is deliberately not applied: streaming text is\n * agent-authored prose in progress, and the filters are about finished\n * messages. Markdown is not processed either — partially arrived source\n * (`**bo`) looks worse than its raw form — so the streaming view is plain text\n * under `whitespace-pre-wrap` and `Markdown` takes over on `final`.\n */\nfunction AgentStreamingBubble({\n agentName,\n text,\n thinkingText,\n a2ui,\n steps,\n}: AgentStreamingBubbleProps) {\n return (\n <div data-slot=\"agent-chat-streaming\" className=\"flex w-full gap-3\">\n <AssistantAvatar />\n <div className=\"min-w-0 flex-1\">\n <span\n className={cn(\n \"text-[length:var(--ds-text-caption)] font-semibold\",\n \"text-[color:var(--ds-foreground)]\",\n )}\n >\n {agentName}\n </span>\n <AgentSteps steps={steps ?? []} />\n {thinkingText ? <AgentThinking text={thinkingText} defaultOpen streaming /> : null}\n {text ? (\n <div\n data-slot=\"agent-chat-streaming-text\"\n className=\"mt-1 break-words whitespace-pre-wrap text-[color:var(--ds-foreground)]\"\n >\n {text}\n </div>\n ) : null}\n <AgentA2UIBlocks blocks={a2ui} streaming />\n </div>\n </div>\n );\n}\n\n/* ── tool cards ──────────────────────────────────────────────────────────── */\n\n/** A `ui://` resource in a tool result is an MCP App: the extension's own screen. */\nexport function readMcpAppUri(output: unknown): string | null {\n const text =\n typeof output === \"string\" ? output : output === undefined ? \"\" : JSON.stringify(output);\n return text.match(/\"uri\"\\s*:\\s*\"(ui:\\/\\/[^\"]+)\"/)?.[1] ?? null;\n}\n\nfunction summarise(input: unknown): string {\n if (input === undefined || input === null) {\n return \"\";\n }\n if (typeof input === \"string\") {\n return input;\n }\n const record = input as Record<string, unknown>;\n for (const key of [\"command\", \"cmd\", \"query\", \"path\", \"url\", \"name\"]) {\n const value = record[key];\n if (typeof value === \"string\" && value.trim()) {\n return value;\n }\n }\n const json = JSON.stringify(input);\n return json.length > 120 ? `${json.slice(0, 117)}…` : json;\n}\n\nexport interface AgentToolCardProps {\n entry: AgentToolEntry;\n}\n\n/**\n * The default card. It shows the tool's name, its input and its output — the\n * input being the part that was missing for the whole of the AG-UI path,\n * because the bridge had no `TOOL_CALL_ARGS` case.\n */\nfunction AgentToolCard({ entry }: AgentToolCardProps) {\n const [open, setOpen] = React.useState(false);\n const summary = summarise(entry.input);\n return (\n <div\n data-slot=\"agent-tool-card\"\n data-tool={entry.name}\n data-status={entry.status}\n className={cn(\n \"overflow-hidden rounded-[var(--ds-radius-sm)] border border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-muted)]\",\n )}\n >\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((v) => !v)}\n className={cn(\n \"flex w-full items-center gap-2.5 px-3 py-2 text-left\",\n \"transition-colors duration-[var(--ds-duration-fast)] hover:bg-[var(--ds-hover)]\",\n )}\n >\n <span className=\"inline-flex size-3.5 shrink-0 items-center justify-center\">\n {entry.status === \"running\" ? (\n <Spinner size={14} />\n ) : (\n <span\n aria-hidden\n className={cn(\n \"size-2 rounded-[var(--ds-radius-full)]\",\n entry.status === \"error\" ? \"bg-[var(--ds-destructive)]\" : \"bg-[var(--ds-success)]\",\n )}\n />\n )}\n </span>\n <span className=\"font-mono text-[length:var(--ds-text-caption)] text-[color:var(--ds-primary)]\">\n {entry.name}\n </span>\n {summary ? (\n <span\n data-slot=\"agent-tool-card-input\"\n className=\"min-w-0 truncate text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\"\n >\n {summary}\n </span>\n ) : null}\n <span className=\"ml-auto shrink-0 text-[color:var(--ds-text-tertiary)]\">\n {open ? <ChevronDown className=\"size-3\" /> : <ChevronRight className=\"size-3\" />}\n </span>\n </button>\n {open ? (\n <div className=\"border-t border-[var(--ds-border-subtle)] bg-[var(--ds-background)] px-3 py-2\">\n {entry.input !== undefined ? (\n <pre\n data-slot=\"agent-tool-card-args\"\n className=\"max-h-40 overflow-auto font-mono text-[length:var(--ds-text-caption-sm)] whitespace-pre-wrap text-[color:var(--ds-muted-foreground)]\"\n >\n {typeof entry.input === \"string\" ? entry.input : JSON.stringify(entry.input, null, 2)}\n </pre>\n ) : null}\n {entry.output !== undefined && entry.output !== null ? (\n <pre className=\"mt-2 max-h-40 overflow-auto font-mono text-[length:var(--ds-text-caption-sm)] whitespace-pre-wrap text-[color:var(--ds-muted-foreground)]\">\n {typeof entry.output === \"string\"\n ? entry.output\n : JSON.stringify(entry.output, null, 2)}\n </pre>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\nexport interface AgentApprovalCardProps {\n request: AgentApprovalRequest;\n onResolve: (id: string, decision: \"allow-once\" | \"allow-always\" | \"deny\") => void;\n}\n\n/**\n * `exec` as a render-and-wait card: the run is paused on this decision.\n *\n * The three answers are the three cortenacore accepts. \"Always allow\" is not a\n * nicety — without it the same command is asked about on every turn, and a user\n * who has to answer the same question repeatedly stops reading the question.\n */\nfunction AgentApprovalCard({ request, onResolve }: AgentApprovalCardProps) {\n const command =\n typeof request.args.command === \"string\" ? request.args.command : request.toolName;\n return (\n <div\n data-slot=\"agent-approval-card\"\n role=\"group\"\n aria-label=\"Command approval required\"\n className={cn(\n \"flex flex-col gap-3 rounded-[var(--ds-radius-md)] border border-[var(--ds-border)]\",\n \"bg-[var(--ds-card)] p-3\",\n )}\n >\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"size-4 text-[color:var(--ds-primary)]\" aria-hidden />\n <span className=\"text-[length:var(--ds-text-body-sm)] font-medium text-[color:var(--ds-foreground)]\">\n Approval required\n </span>\n </div>\n <pre className=\"max-h-32 overflow-auto rounded-[var(--ds-radius-sm)] bg-[var(--ds-muted)] p-2 font-mono text-[length:var(--ds-text-caption)] whitespace-pre-wrap text-[color:var(--ds-foreground)]\">\n {command}\n </pre>\n {/* A decision the server refused. The card came back rather than\n vanishing, so the answer can be given again. */}\n {request.error ? (\n <p\n data-slot=\"agent-approval-error\"\n role=\"alert\"\n className=\"text-[length:var(--ds-text-caption)] text-[color:var(--ds-destructive)]\"\n >\n {request.error}\n </p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button size=\"sm\" onClick={() => onResolve(request.id, \"allow-once\")}>\n <ShieldCheck aria-hidden />\n Approve\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={() => onResolve(request.id, \"allow-always\")}>\n Always allow\n </Button>\n <Button size=\"sm\" variant=\"destructive\" onClick={() => onResolve(request.id, \"deny\")}>\n <ShieldX aria-hidden />\n Deny\n </Button>\n </div>\n </div>\n );\n}\n\n/* ── the surface ─────────────────────────────────────────────────────────── */\n\nexport interface AgentToolRenderContext {\n entry: AgentToolEntry;\n /** The `ui://` resource this result carries, when it carries one. */\n mcpAppUri: string | null;\n}\n\nexport interface AgentChatProps extends Omit<React.ComponentProps<\"div\">, \"children\" | \"onError\"> {\n /** The transport. `createAguiAgentChatClient` is the AG-UI implementation. */\n client: AgentChatClient;\n /** The extension name, shown on each assistant turn. */\n agentName: string;\n /**\n * Names the `sessionStorage` slot this window's session key lives in. Scope\n * it to the extension id; two chats with different scopes never share a key.\n */\n storageScope: string;\n /** Do not read or write `sessionStorage`; for a guide or a test. */\n ephemeral?: boolean;\n thinking?: \"low\" | \"medium\" | \"high\";\n /** Rendered between the tool strip and the composer — an attachment picker. */\n attachmentsSlot?: React.ReactNode;\n /**\n * Host wiring for a tool call. Return a node to replace the default card —\n * this is the seam an MCP App frame mounts through, since the sandboxed\n * iframe, its CSP and the `tools/invoke` proxy are the host's to own.\n */\n renderToolCall?: (context: AgentToolRenderContext) => React.ReactNode | undefined;\n /** Header content, e.g. the pop-up's own window controls. */\n headerSlot?: React.ReactNode;\n}\n\nfunction AgentChat({\n client,\n agentName,\n storageScope,\n ephemeral,\n thinking,\n attachmentsSlot,\n renderToolCall,\n headerSlot,\n className,\n ...props\n}: AgentChatProps) {\n const chat = useAgentChat({\n client,\n storageScope,\n ...(ephemeral === undefined ? {} : { ephemeral }),\n ...(thinking === undefined ? {} : { thinking }),\n });\n const [drawerOpen, setDrawerOpen] = React.useState(false);\n const [value, setValue] = React.useState(\"\");\n const scrollRef = React.useRef<HTMLDivElement>(null);\n const contentRef = React.useRef<HTMLDivElement>(null);\n const prevHeightRef = React.useRef(0);\n\n /**\n * Re-pin to the bottom whenever the content grows or the viewport shrinks.\n * A message keeps growing after it mounts — markdown settles, the tool strip\n * appears between the list and the composer — and every one of those leaves\n * the newest content under the composer until the reader scrolls by hand.\n *\n * Jumping rather than smooth-scrolling: this fires many times a second during\n * streaming, and queued smooth animations fight each other and lose.\n */\n React.useEffect(() => {\n const scrollEl = scrollRef.current;\n const contentEl = contentRef.current;\n if (!scrollEl || !contentEl || typeof ResizeObserver === \"undefined\") {\n return;\n }\n const observer = new ResizeObserver(() => {\n const { scrollTop, scrollHeight, clientHeight } = scrollEl;\n const stick = shouldStickToBottom({\n prevScrollHeight: prevHeightRef.current,\n scrollTop,\n clientHeight,\n });\n prevHeightRef.current = scrollHeight;\n if (stick) {\n scrollEl.scrollTop = scrollEl.scrollHeight;\n }\n });\n observer.observe(contentEl);\n observer.observe(scrollEl);\n return () => observer.disconnect();\n }, []);\n\n const submit = React.useCallback(() => {\n const trimmed = value.trim();\n // Enter reaches here while a run is live, where the button is a Stop\n // control and cannot be pressed. One run at a time, by both routes.\n if (!trimmed || chat.streaming) {\n return;\n }\n chat.send(trimmed);\n setValue(\"\");\n }, [chat, value]);\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === \"Escape\" && chat.streaming) {\n // Escape stops the run. It does not close the pop-up while a run is in\n // flight: the first Escape is nearly always \"stop\", not \"go away\".\n event.preventDefault();\n event.stopPropagation();\n chat.abort();\n return;\n }\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n submit();\n }\n },\n [chat, submit],\n );\n\n const toolEntries = chat.toolOrder\n .map((id) => chat.tools[id])\n .filter((entry): entry is AgentToolEntry => entry !== undefined);\n // The strip outlives the run rather than vanishing with it. The steps of a\n // finished turn are the record of what the agent did on the user's behalf,\n // and an MCP App drawn by one of them is a surface the user is meant to work\n // in — one that disappeared the instant the agent stopped talking would be no\n // use to anybody. The next run clears it.\n const showTools = toolEntries.length > 0 || chat.approvals.length > 0;\n const visible = chat.messages.filter((message) => !isHiddenMessage(message));\n\n return (\n <div\n data-slot=\"agent-chat\"\n className={cn(\"flex h-full min-h-0 w-full flex-col bg-[var(--ds-background)]\", className)}\n {...props}\n >\n <div\n data-slot=\"agent-chat-header\"\n className={cn(\n \"flex shrink-0 items-center gap-2 border-b border-[var(--ds-border-subtle)] px-3 py-2\",\n )}\n >\n <Button\n size=\"sm\"\n variant=\"ghost\"\n data-slot=\"agent-chat-sessions-trigger\"\n aria-expanded={drawerOpen}\n onClick={() => {\n setDrawerOpen((open) => !open);\n chat.refreshSessions();\n }}\n >\n {chat.sessions.length === 1 ? \"1 session\" : `${chat.sessions.length} sessions`}\n </Button>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n data-slot=\"agent-chat-new-session\"\n aria-label=\"New session\"\n onClick={() => {\n chat.newSession();\n setDrawerOpen(false);\n }}\n >\n <MessageSquarePlus aria-hidden />\n New\n </Button>\n <div className=\"ml-auto flex items-center gap-1\">{headerSlot}</div>\n </div>\n\n {drawerOpen ? (\n <AgentSessionDrawer\n sessions={chat.sessions}\n current={chat.sessionKey}\n loading={chat.loadingSessions}\n onResume={(key) => {\n chat.resumeSession(key);\n setDrawerOpen(false);\n }}\n />\n ) : null}\n\n <div\n ref={scrollRef}\n data-slot=\"agent-chat-scroll\"\n className=\"min-h-0 flex-1 overflow-x-hidden overflow-y-auto\"\n >\n <div\n ref={contentRef}\n role=\"log\"\n aria-live=\"polite\"\n aria-label={`${agentName} chat`}\n className=\"flex min-w-0 flex-col gap-5 px-4 py-4\"\n >\n {chat.error ? (\n <ErrorBanner message={chat.error} />\n ) : null}\n\n {chat.hasMoreHistory ? (\n <div className=\"flex justify-center\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n data-slot=\"agent-chat-load-older\"\n disabled={chat.loadingHistory}\n onClick={chat.loadOlder}\n >\n {chat.loadingHistory ? \"Loading…\" : \"Load older messages\"}\n </Button>\n </div>\n ) : null}\n\n {visible.length === 0 && !chat.streaming ? (\n <p\n data-slot=\"agent-chat-empty\"\n className=\"text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]\"\n >\n Ask {agentName} anything about this screen.\n </p>\n ) : null}\n\n {visible.map((message) => (\n <AgentMessageBubble\n key={message.id}\n message={message}\n agentName={agentName}\n streaming={chat.streaming}\n onAction={chat.sendAction}\n />\n ))}\n\n {chat.streaming ? (\n <AgentStreamingBubble\n agentName={agentName}\n text={chat.streamText}\n thinkingText={chat.streamThinkingText}\n a2ui={chat.streamA2ui}\n steps={chat.steps}\n />\n ) : chat.steps.length > 0 ? (\n // The turn is over but its steps are the record of what was done to\n // answer it, and they stay until the next run replaces them — the\n // same rule the tool strip below follows.\n <AgentSteps steps={chat.steps} />\n ) : null}\n </div>\n </div>\n\n {showTools ? (\n <div\n data-slot=\"agent-chat-tools\"\n className=\"flex shrink-0 flex-col gap-1.5 border-t border-[var(--ds-border-subtle)] px-3 py-2\"\n >\n {toolEntries.length > 0 ? (\n <Badge variant=\"neutral\" className=\"w-fit\">\n {toolEntries.length === 1 ? \"1 step\" : `${toolEntries.length} steps`}\n </Badge>\n ) : null}\n {toolEntries.map((entry) => {\n const custom = renderToolCall?.({\n entry,\n mcpAppUri: readMcpAppUri(entry.output),\n });\n return (\n <React.Fragment key={entry.id}>\n {custom ?? <AgentToolCard entry={entry} />}\n </React.Fragment>\n );\n })}\n {chat.approvals.map((request) => (\n <AgentApprovalCard\n key={request.id}\n request={request}\n onResolve={chat.resolveApproval}\n />\n ))}\n </div>\n ) : null}\n\n {attachmentsSlot ? (\n <div data-slot=\"agent-chat-attachments\" className=\"shrink-0 px-3\">\n {attachmentsSlot}\n </div>\n ) : null}\n\n <div\n data-slot=\"agent-chat-composer\"\n className=\"flex shrink-0 items-end gap-2 border-t border-[var(--ds-border-subtle)] p-3\"\n >\n <Textarea\n data-slot=\"agent-chat-input\"\n aria-label={`Message ${agentName}`}\n placeholder={`Message ${agentName}…`}\n rows={1}\n className=\"max-h-40 min-h-9 resize-none\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n onKeyDown={onKeyDown}\n />\n {chat.streaming ? (\n <Button\n size=\"icon\"\n variant=\"outline\"\n data-slot=\"agent-chat-stop\"\n aria-label=\"Stop the run\"\n onClick={chat.abort}\n >\n <Square aria-hidden />\n </Button>\n ) : (\n <Button\n size=\"icon\"\n data-slot=\"agent-chat-send\"\n aria-label=\"Send\"\n disabled={!value.trim()}\n onClick={submit}\n >\n <Send aria-hidden />\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/* ── the session drawer ──────────────────────────────────────────────────── */\n\nexport interface AgentSessionDrawerProps {\n sessions: readonly AgentSession[];\n current: string | null;\n loading?: boolean;\n onResume: (key: string) => void;\n}\n\n/**\n * Every session of this agent, resumable from any window.\n *\n * The list is not this window's history: a session started in another window is\n * here and can be resumed, which is the difference between \"sessions are per\n * window\" (which key a window starts on) and \"sessions are private to a window\"\n * (which they are not).\n */\nfunction AgentSessionDrawer({ sessions, current, loading, onResume }: AgentSessionDrawerProps) {\n return (\n <div\n data-slot=\"agent-chat-sessions\"\n className={cn(\n \"max-h-56 shrink-0 overflow-y-auto border-b border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-card)] px-2 py-1.5\",\n )}\n >\n {loading && sessions.length === 0 ? (\n <p className=\"px-2 py-1 text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\">\n Loading sessions…\n </p>\n ) : null}\n {!loading && sessions.length === 0 ? (\n <p className=\"px-2 py-1 text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\">\n No earlier sessions.\n </p>\n ) : null}\n {sessions.map((session) => (\n <button\n key={session.key}\n type=\"button\"\n data-slot=\"agent-chat-session\"\n data-current={session.key === current ? \"\" : undefined}\n onClick={() => onResume(session.key)}\n className={cn(\n \"flex w-full items-center gap-2 rounded-[var(--ds-radius-sm)] px-2 py-1.5 text-left\",\n \"text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-foreground)]\",\n \"transition-colors duration-[var(--ds-duration-fast)] hover:bg-[var(--ds-hover)]\",\n session.key === current && \"bg-[var(--ds-primary-soft)]\",\n )}\n >\n <span className=\"min-w-0 truncate\">{sessionTitle(session)}</span>\n </button>\n ))}\n </div>\n );\n}\n\nexport {\n AgentA2UIBlocks,\n AgentApprovalCard,\n AgentChat,\n AgentMessageBubble,\n AgentSessionDrawer,\n AgentSteps,\n AgentStreamingBubble,\n AgentThinking,\n AgentToolCard,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agent-chat.js","names":[],"sources":["../../src/components/agent-chat.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * AgentChat — the chat surface an extension's own agent runs in.\n *\n * §17 of how-to-create-a-cortena-extension, audit rule P-25. Moved here from\n * `cortenaweb/components/chat/**` so there is one chat and not two: a cut-down\n * widget diverges on approvals, A2UI and MCP Apps within one release, and every\n * fix then has to be made twice. cortenaweb adopts this component as a\n * follow-up; until then the two are kept in step by hand.\n *\n * Everything comes from the `AgentChatClient` prop — nothing here knows about\n * HTTP, AG-UI or a store — which is what lets `test/agent-chat.test.tsx` drive\n * the whole surface from a fake that replays scripted event sequences.\n *\n * Not in this package, on purpose: the MCP App frame. Rendering an extension's\n * own screen means a sandboxed iframe, a `srcdoc` CSP, a JSON-RPC postMessage\n * bridge and a `tools/invoke` proxy holding a credential — a host concern, not\n * a component one. `renderToolCall` is the seam: return a node for a tool call\n * and it replaces the card, return nothing and the default card is drawn.\n */\n\nimport {\n Check,\n ChevronDown,\n ChevronRight,\n MessageSquarePlus,\n Send,\n ShieldCheck,\n ShieldX,\n Sparkles,\n Square,\n X,\n} from \"lucide-react\";\nimport * as React from \"react\";\nimport { A2UIRenderer } from \"@/a2ui/renderer\";\nimport { A2UIErrorCard } from \"@/a2ui/views\";\nimport type { A2UIAction, A2UIMessage } from \"@/a2ui/message\";\nimport { Badge } from \"@/components/badge\";\nimport { Button } from \"@/components/button\";\nimport { ErrorBanner } from \"@/components/error-banner\";\nimport { Markdown } from \"@/components/markdown\";\nimport { Spinner } from \"@/components/spinner\";\nimport { Textarea } from \"@/components/textarea\";\nimport { isHiddenMessage, STORED_TEXT_LIMIT } from \"@/agent-chat/session\";\nimport { isGenericLabel, truncate } from \"@/agent-chat/step-label\";\nimport { type AgentToolEntry, useAgentChat, type UseAgentChatResult } from \"@/agent-chat/store\";\nimport type {\n AgentApprovalDecision,\n AgentApprovalRequest,\n AgentChatClient,\n AgentChatMessage,\n AgentChatStep,\n AgentSession,\n} from \"@/agent-chat/types\";\nimport { sessionTitle } from \"@/agent-chat/types\";\nimport type { A2UIChatBlock } from \"@/agent-chat/a2ui-block\";\nimport { cn } from \"@/lib/cn\";\n\n/* ── stick to bottom ─────────────────────────────────────────────────────── */\n\n/**\n * `useLayoutEffect` in a browser, `useEffect` on a server render, where React\n * warns about the former. The scroll restore after a prepend has to run\n * before the paint: an effect after it shows one frame at the old `scrollTop`,\n * with the page that just landed pushing the reader's place off the screen.\n */\nconst useLayoutEffectSafe = typeof window === \"undefined\" ? React.useEffect : React.useLayoutEffect;\n\n/**\n * Should the list jump back to the bottom after its content grew?\n *\n * Judged against the height BEFORE the growth. Measuring afterwards asks \"is\n * the newest content already visible\", which is false by construction for any\n * append taller than the threshold — a paragraph, a code block, an image — so\n * the re-pin skipped exactly when it was needed.\n */\nexport function shouldStickToBottom(params: {\n prevScrollHeight: number;\n scrollTop: number;\n clientHeight: number;\n threshold?: number;\n}): boolean {\n const { prevScrollHeight, scrollTop, clientHeight, threshold = 150 } = params;\n if (prevScrollHeight <= 0) {\n return true;\n }\n return prevScrollHeight - scrollTop - clientHeight < threshold;\n}\n\n/* ── thinking ────────────────────────────────────────────────────────────── */\n\nexport interface AgentThinkingProps {\n text: string;\n /** Open by default while the turn is live, collapsed once it is finished. */\n defaultOpen?: boolean;\n streaming?: boolean;\n}\n\nfunction AgentThinking({ text, defaultOpen, streaming }: AgentThinkingProps) {\n const [open, setOpen] = React.useState(defaultOpen ?? false);\n if (!text) {\n return null;\n }\n return (\n <div data-slot=\"agent-chat-thinking\" className=\"mt-1\">\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((v) => !v)}\n className={cn(\n \"inline-flex items-center gap-1 rounded-[var(--ds-radius-sm)] px-1 py-0.5\",\n \"text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\",\n \"transition-colors duration-[var(--ds-duration-fast)] hover:text-[color:var(--ds-foreground)]\",\n )}\n >\n {open ? <ChevronDown className=\"size-3\" aria-hidden /> : <ChevronRight className=\"size-3\" aria-hidden />}\n {streaming ? \"Thinking…\" : \"Chain of thought\"}\n </button>\n {open ? (\n <div\n data-slot=\"agent-chat-thinking-body\"\n className={cn(\n \"mt-1 max-h-64 overflow-auto rounded-[var(--ds-radius-sm)] border border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-muted)] px-3 py-2 whitespace-pre-wrap\",\n \"text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\",\n )}\n >\n {text}\n </div>\n ) : null}\n </div>\n );\n}\n\n/* ── progress steps ──────────────────────────────────────────────────────── */\n\n/**\n * How long a step runs before it starts saying how long it has been running.\n *\n * Under this, a timer is noise: every call takes a second or two and a number\n * ticking beside each one is a fidget spinner. Over it, the user has begun to\n * wonder whether anything is happening at all, and the number is the answer.\n */\nconst ELAPSED_AFTER_MS = 8_000;\n\n/**\n * `Date.now()`, re-read once a second while `active`.\n *\n * The interval only exists while something is running, so a finished chat sits\n * idle rather than re-rendering the whole transcript once a second for ever.\n */\nfunction useTickingNow(active: boolean): number {\n const [now, setNow] = React.useState(() => Date.now());\n React.useEffect(() => {\n if (!active) {\n return;\n }\n setNow(Date.now());\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, [active]);\n return now;\n}\n\nexport interface AgentStepsProps {\n steps: readonly AgentChatStep[];\n}\n\n/**\n * What the agent is doing, one plain sentence per tool call (SKILLS-9).\n *\n * This is the surface a person watches while they wait, and it is deliberately\n * not the tool strip: the strip is the record — names, arguments, output, the\n * MCP App seam — and reading it requires knowing what `broker_invoke` is. The\n * strip stays at the foot for anyone who wants the detail.\n *\n * NOT a live region of its own. The strip is drawn inside the transcript, and\n * the transcript is already `role=\"log\" aria-live=\"polite\"` — two nested live\n * regions is one announcement per change from the inner one and another from\n * the outer, which is how a screen reader ends up saying every step twice.\n * `aria-label` stays: it names the group for anyone who navigates to it.\n */\nfunction AgentSteps({ steps }: AgentStepsProps) {\n const running = steps.some((step) => step.status === \"running\");\n const now = useTickingNow(running);\n if (steps.length === 0) {\n return null;\n }\n return (\n <div\n data-slot=\"agent-chat-steps\"\n aria-label=\"Progress\"\n className=\"mt-1 flex flex-col gap-0.5\"\n >\n {steps.map((step) => {\n const elapsed = step.status === \"running\" ? now - step.startedAt : 0;\n const seconds = elapsed >= ELAPSED_AFTER_MS ? Math.floor(elapsed / 1000) : 0;\n // Held out of the announcement while the line is still the fallback.\n // The arguments arrive one event after the start, so every call passes\n // through \"Working: broker_invoke\" for a few hundred milliseconds, and\n // a live region reads it out — then reads the real sentence over the\n // top of it. The row is still drawn; it joins the announcement the\n // moment it has something to say, or when it ends, whichever is first.\n const held = step.status === \"running\" && isGenericLabel(step.toolName, step.label);\n return (\n <p\n key={step.id}\n data-slot=\"agent-chat-step\"\n data-status={step.status}\n // The tool name is the model's, not ours, and an attribute is a\n // poor place for 9 kB of it.\n data-tool={truncate(step.toolName, STORED_TEXT_LIMIT)}\n {...(held ? { \"aria-hidden\": true } : {})}\n className={cn(\n \"flex items-start gap-2 text-[length:var(--ds-text-caption-lg)]\",\n step.status === \"error\"\n ? \"text-[color:var(--ds-destructive)]\"\n : \"text-[color:var(--ds-muted-foreground)]\",\n )}\n >\n <span\n aria-hidden\n className=\"mt-0.5 inline-flex size-3.5 shrink-0 items-center justify-center\"\n >\n {step.status === \"running\" ? (\n <Spinner size={12} aria-hidden />\n ) : step.status === \"error\" ? (\n <X className=\"size-3\" />\n ) : step.status === \"stopped\" ? (\n <Square className=\"size-2.5\" />\n ) : step.status === \"paused\" ? (\n // A plain dot, drawn in the muted colour: neither a spinner\n // (nothing is happening) nor a tick (nothing has happened).\n <span className=\"size-1.5 rounded-full bg-[color:var(--ds-muted-foreground)]\" />\n ) : (\n <Check className=\"size-3 text-[color:var(--ds-success)]\" />\n )}\n </span>\n <span className=\"min-w-0\">\n {/* Keyed on the words. A step is re-worded in place when its\n arguments land, and a text node mutated under a live region\n is a change some screen readers do not announce at all; a new\n key makes it a node replacement, which they all do. */}\n <span key={step.label} data-slot=\"agent-chat-step-label\">\n {step.label}\n </span>\n {/* The seconds change once a second, and a live region reads the\n whole line again each time they do. The count is for the eye:\n a screen reader hearing \"Fetching tasks task list\" once, and\n again when it lands, is told everything it needs. */}\n {seconds > 0 ? (\n <span aria-hidden data-slot=\"agent-chat-step-elapsed\">{` · ${seconds}s`}</span>\n ) : null}\n {step.status === \"stopped\" ? (\n <span data-slot=\"agent-chat-step-note\">{\" — Stopped\"}</span>\n ) : null}\n {step.status === \"paused\" ? (\n <span data-slot=\"agent-chat-step-note\">{\" — Waiting for your approval\"}</span>\n ) : null}\n {step.status === \"error\" && step.errorMessage ? (\n <span data-slot=\"agent-chat-step-error\">{` — ${step.errorMessage}`}</span>\n ) : null}\n </span>\n </p>\n );\n })}\n </div>\n );\n}\n\n/* ── A2UI blocks ─────────────────────────────────────────────────────────── */\n\nexport interface AgentA2UIBlocksProps {\n blocks: readonly A2UIChatBlock[] | undefined;\n /** Set on the streaming bubble: actions are disabled while the turn is live. */\n streaming?: boolean;\n onAction?: (action: A2UIAction) => void;\n}\n\n/**\n * Every block of one message, in arrival order.\n *\n * A block carrying a transport `error` — an unclosed fence, a body that did not\n * parse — gets an error card rather than being dropped. A block that is merely\n * malformed inside is the renderer's error to draw, and it draws the parts that\n * were fine.\n */\nfunction AgentA2UIBlocks({ blocks, streaming, onAction }: AgentA2UIBlocksProps) {\n if (!blocks || blocks.length === 0) {\n return null;\n }\n return (\n <div className=\"mt-2 flex flex-col gap-3\" data-slot=\"agent-chat-a2ui\">\n {blocks.map((block, index) => (\n <div key={`${block.surfaceId}:${index}`}>\n {block.error ? (\n <A2UIErrorCard title=\"The agent's UI did not arrive complete\" detail={block.error} />\n ) : null}\n {block.messages.length > 0 ? (\n <A2UIRenderer\n // The payload stays `unknown[]`: it is the agent's, and only the\n // renderer validates it. Every node goes through the catalogue's\n // schema before it is drawn.\n message={block.messages as A2UIMessage[]}\n // A surface still streaming must not fire actions at the agent\n // from a half-built form, so it is read-only until final.\n {...(streaming || !onAction ? {} : { onAction })}\n />\n ) : null}\n </div>\n ))}\n </div>\n );\n}\n\n/* ── bubbles ─────────────────────────────────────────────────────────────── */\n\nfunction AssistantAvatar() {\n return (\n <span\n data-slot=\"agent-chat-avatar\"\n aria-hidden\n className={cn(\n \"grid size-7 shrink-0 place-items-center rounded-[var(--ds-radius-sm)]\",\n \"border border-[var(--ds-ring)] bg-[var(--ds-primary-soft)] text-[color:var(--ds-primary)]\",\n )}\n >\n <Sparkles className=\"size-3.5\" aria-hidden />\n </span>\n );\n}\n\nexport interface AgentMessageBubbleProps {\n message: AgentChatMessage;\n agentName: string;\n /**\n * True while a run is live. Every A2UI surface in the transcript goes\n * read-only, not just the one being drawn: an earlier surface is still a\n * form wired to `sendAction`, and pressing it mid-run starts a second\n * concurrent turn on the same session.\n */\n streaming?: boolean;\n onAction?: (action: A2UIAction) => void;\n}\n\nfunction AgentMessageBubble({ message, agentName, streaming, onAction }: AgentMessageBubbleProps) {\n if (isHiddenMessage(message)) {\n return null;\n }\n if (message.isError) {\n return (\n <div data-slot=\"agent-chat-message\" data-role=\"error\">\n <ErrorBanner message={message.text} />\n </div>\n );\n }\n if (message.role === \"user\") {\n return (\n <div data-slot=\"agent-chat-message\" data-role=\"user\" className=\"flex w-full justify-end\">\n <div\n className={cn(\n \"max-w-[85%] rounded-[var(--ds-radius-lg)] border border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-popover)] px-3.5 py-2 text-[color:var(--ds-foreground)]\",\n )}\n >\n <Markdown compact>{message.text}</Markdown>\n </div>\n </div>\n );\n }\n return (\n <div data-slot=\"agent-chat-message\" data-role=\"assistant\" className=\"flex w-full gap-3\">\n <AssistantAvatar />\n <div className=\"min-w-0 flex-1\">\n <span\n className={cn(\n \"text-[length:var(--ds-text-caption)] font-semibold\",\n \"text-[color:var(--ds-foreground)]\",\n )}\n >\n {agentName}\n </span>\n <AgentThinking text={message.thinkingText ?? \"\"} />\n {message.text ? (\n <div className=\"mt-1\">\n <Markdown compact>{message.text}</Markdown>\n </div>\n ) : null}\n <AgentA2UIBlocks\n blocks={message.a2ui}\n {...(streaming ? { streaming } : {})}\n {...(onAction ? { onAction } : {})}\n />\n </div>\n </div>\n );\n}\n\nexport interface AgentStreamingBubbleProps {\n agentName: string;\n text: string;\n thinkingText: string;\n a2ui: readonly A2UIChatBlock[];\n /** The turn's tool calls in plain words, drawn under the agent's name. */\n steps?: readonly AgentChatStep[];\n}\n\n/**\n * The assistant bubble while a turn is live.\n *\n * The filter chain is deliberately not applied: streaming text is\n * agent-authored prose in progress, and the filters are about finished\n * messages. Markdown is not processed either — partially arrived source\n * (`**bo`) looks worse than its raw form — so the streaming view is plain text\n * under `whitespace-pre-wrap` and `Markdown` takes over on `final`.\n */\nfunction AgentStreamingBubble({\n agentName,\n text,\n thinkingText,\n a2ui,\n steps,\n}: AgentStreamingBubbleProps) {\n return (\n <div data-slot=\"agent-chat-streaming\" className=\"flex w-full gap-3\">\n <AssistantAvatar />\n <div className=\"min-w-0 flex-1\">\n <span\n className={cn(\n \"text-[length:var(--ds-text-caption)] font-semibold\",\n \"text-[color:var(--ds-foreground)]\",\n )}\n >\n {agentName}\n </span>\n <AgentSteps steps={steps ?? []} />\n {thinkingText ? <AgentThinking text={thinkingText} defaultOpen streaming /> : null}\n {text ? (\n <div\n data-slot=\"agent-chat-streaming-text\"\n className=\"mt-1 break-words whitespace-pre-wrap text-[color:var(--ds-foreground)]\"\n >\n {text}\n </div>\n ) : null}\n <AgentA2UIBlocks blocks={a2ui} streaming />\n </div>\n </div>\n );\n}\n\n/* ── tool cards ──────────────────────────────────────────────────────────── */\n\n/** A `ui://` resource in a tool result is an MCP App: the extension's own screen. */\nexport function readMcpAppUri(output: unknown): string | null {\n const text =\n typeof output === \"string\" ? output : output === undefined ? \"\" : JSON.stringify(output);\n return text.match(/\"uri\"\\s*:\\s*\"(ui:\\/\\/[^\"]+)\"/)?.[1] ?? null;\n}\n\nfunction summarise(input: unknown): string {\n if (input === undefined || input === null) {\n return \"\";\n }\n if (typeof input === \"string\") {\n return input;\n }\n const record = input as Record<string, unknown>;\n for (const key of [\"command\", \"cmd\", \"query\", \"path\", \"url\", \"name\"]) {\n const value = record[key];\n if (typeof value === \"string\" && value.trim()) {\n return value;\n }\n }\n const json = JSON.stringify(input);\n return json.length > 120 ? `${json.slice(0, 117)}…` : json;\n}\n\nexport interface AgentToolCardProps {\n entry: AgentToolEntry;\n}\n\n/**\n * The default card. It shows the tool's name, its input and its output — the\n * input being the part that was missing for the whole of the AG-UI path,\n * because the bridge had no `TOOL_CALL_ARGS` case.\n */\nfunction AgentToolCard({ entry }: AgentToolCardProps) {\n const [open, setOpen] = React.useState(false);\n const summary = summarise(entry.input);\n return (\n <div\n data-slot=\"agent-tool-card\"\n data-tool={entry.name}\n data-status={entry.status}\n className={cn(\n \"overflow-hidden rounded-[var(--ds-radius-sm)] border border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-muted)]\",\n )}\n >\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((v) => !v)}\n className={cn(\n \"flex w-full items-center gap-2.5 px-3 py-2 text-left\",\n \"transition-colors duration-[var(--ds-duration-fast)] hover:bg-[var(--ds-hover)]\",\n )}\n >\n <span className=\"inline-flex size-3.5 shrink-0 items-center justify-center\">\n {entry.status === \"running\" ? (\n <Spinner size={14} />\n ) : (\n <span\n aria-hidden\n className={cn(\n \"size-2 rounded-[var(--ds-radius-full)]\",\n entry.status === \"error\" ? \"bg-[var(--ds-destructive)]\" : \"bg-[var(--ds-success)]\",\n )}\n />\n )}\n </span>\n <span className=\"font-mono text-[length:var(--ds-text-caption)] text-[color:var(--ds-primary)]\">\n {entry.name}\n </span>\n {summary ? (\n <span\n data-slot=\"agent-tool-card-input\"\n className=\"min-w-0 truncate text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\"\n >\n {summary}\n </span>\n ) : null}\n <span className=\"ml-auto shrink-0 text-[color:var(--ds-text-tertiary)]\">\n {open ? <ChevronDown className=\"size-3\" /> : <ChevronRight className=\"size-3\" />}\n </span>\n </button>\n {open ? (\n <div className=\"border-t border-[var(--ds-border-subtle)] bg-[var(--ds-background)] px-3 py-2\">\n {entry.input !== undefined ? (\n <pre\n data-slot=\"agent-tool-card-args\"\n className=\"max-h-40 overflow-auto font-mono text-[length:var(--ds-text-caption-sm)] whitespace-pre-wrap text-[color:var(--ds-muted-foreground)]\"\n >\n {typeof entry.input === \"string\" ? entry.input : JSON.stringify(entry.input, null, 2)}\n </pre>\n ) : null}\n {entry.output !== undefined && entry.output !== null ? (\n <pre className=\"mt-2 max-h-40 overflow-auto font-mono text-[length:var(--ds-text-caption-sm)] whitespace-pre-wrap text-[color:var(--ds-muted-foreground)]\">\n {typeof entry.output === \"string\"\n ? entry.output\n : JSON.stringify(entry.output, null, 2)}\n </pre>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\nexport interface AgentApprovalCardProps {\n request: AgentApprovalRequest;\n onResolve: (id: string, decision: \"allow-once\" | \"allow-always\" | \"deny\") => void;\n}\n\n/**\n * `exec` as a render-and-wait card: the run is paused on this decision.\n *\n * The three answers are the three cortenacore accepts. \"Always allow\" is not a\n * nicety — without it the same command is asked about on every turn, and a user\n * who has to answer the same question repeatedly stops reading the question.\n */\nfunction AgentApprovalCard({ request, onResolve }: AgentApprovalCardProps) {\n const command =\n typeof request.args.command === \"string\" ? request.args.command : request.toolName;\n return (\n <div\n data-slot=\"agent-approval-card\"\n role=\"group\"\n aria-label=\"Command approval required\"\n className={cn(\n \"flex flex-col gap-3 rounded-[var(--ds-radius-md)] border border-[var(--ds-border)]\",\n \"bg-[var(--ds-card)] p-3\",\n )}\n >\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"size-4 text-[color:var(--ds-primary)]\" aria-hidden />\n <span className=\"text-[length:var(--ds-text-body-sm)] font-medium text-[color:var(--ds-foreground)]\">\n Approval required\n </span>\n </div>\n <pre className=\"max-h-32 overflow-auto rounded-[var(--ds-radius-sm)] bg-[var(--ds-muted)] p-2 font-mono text-[length:var(--ds-text-caption)] whitespace-pre-wrap text-[color:var(--ds-foreground)]\">\n {command}\n </pre>\n {/* A decision the server refused. The card came back rather than\n vanishing, so the answer can be given again. */}\n {request.error ? (\n <p\n data-slot=\"agent-approval-error\"\n role=\"alert\"\n className=\"text-[length:var(--ds-text-caption)] text-[color:var(--ds-destructive)]\"\n >\n {request.error}\n </p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button size=\"sm\" onClick={() => onResolve(request.id, \"allow-once\")}>\n <ShieldCheck aria-hidden />\n Approve\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={() => onResolve(request.id, \"allow-always\")}>\n Always allow\n </Button>\n <Button size=\"sm\" variant=\"destructive\" onClick={() => onResolve(request.id, \"deny\")}>\n <ShieldX aria-hidden />\n Deny\n </Button>\n </div>\n </div>\n );\n}\n\n/* ── the surface ─────────────────────────────────────────────────────────── */\n\nexport interface AgentToolRenderContext {\n entry: AgentToolEntry;\n /** The `ui://` resource this result carries, when it carries one. */\n mcpAppUri: string | null;\n}\n\n/* ── the slots a host may fill ───────────────────────────────────────────── */\n\n/**\n * What a host's own message bubble receives. `index` and `messages` are over\n * the VISIBLE messages — the filter chain has already run — so\n * `messages[index - 1]` is the turn before this one, which a feedback control\n * or a transcript export wants.\n */\nexport interface AgentChatMessageSlotProps {\n message: AgentChatMessage;\n index: number;\n messages: readonly AgentChatMessage[];\n agentName: string;\n sessionKey: string | null;\n /** True while a run is live; every A2UI surface goes read-only. */\n streaming: boolean;\n onAction: (action: A2UIAction) => void;\n}\n\nexport interface AgentChatStreamingSlotProps {\n agentName: string;\n text: string;\n thinkingText: string;\n a2ui: readonly A2UIChatBlock[];\n /** The turn's tool calls in plain words; the default draws them under the name. */\n steps: readonly AgentChatStep[];\n}\n\nexport interface AgentChatEmptySlotProps {\n agentName: string;\n /** A history load is in flight for this session; a skeleton fits here. */\n loadingHistory: boolean;\n}\n\nexport interface AgentChatLoadOlderSlotProps {\n hasMore: boolean;\n loading: boolean;\n onLoad: () => void;\n}\n\nexport interface AgentChatToolsSlotProps {\n entries: readonly AgentToolEntry[];\n approvals: readonly AgentApprovalRequest[];\n streaming: boolean;\n /**\n * The host's `renderToolCall`, as it was passed: `undefined` for a call the\n * host declines, so the slot can draw its own line for those and still mount\n * an MCP App through the one seam the component defines.\n */\n renderToolCall: ((context: AgentToolRenderContext) => React.ReactNode | undefined) | undefined;\n onResolveApproval: (id: string, decision: AgentApprovalDecision) => void;\n}\n\nexport interface AgentChatComposerSlotProps {\n agentName: string;\n streaming: boolean;\n send: UseAgentChatResult[\"send\"];\n abort: () => void;\n}\n\n/**\n * Renders a host may substitute for the component's own, one region each.\n *\n * The surface — its state, the order of its regions, the scroller that stays\n * pinned to the bottom, the sentinel that pages history, the steps and the\n * tool-card seam — is shared; what each region LOOKS like need not be. This is\n * how cortenaweb draws its own bubble (Shiki, KaTeX, feedback controls, a\n * timestamp chip) and its own composer (attachments, agent and model\n * selectors) on the shared surface instead of keeping a second chat\n * (PLATFORM-64).\n *\n * A component given here replaces its region whole, wrapper included; nothing\n * of the default is drawn around it.\n */\nexport interface AgentChatComponents {\n Message: React.ComponentType<AgentChatMessageSlotProps>;\n /**\n * The live bubble. It is handed `steps` because the default draws the\n * progress steps INSIDE the streaming bubble; a host's own bubble draws them\n * itself, or not at all — nothing else draws them while a run is live.\n */\n Streaming: React.ComponentType<AgentChatStreamingSlotProps>;\n /**\n * Replaces the whole transcript region while there is nothing to show. Not\n * while there is an error: the transcript then mounts with the error banner\n * in it, so a failure before the first message is not hidden behind the\n * host's welcome screen.\n */\n Empty: React.ComponentType<AgentChatEmptySlotProps>;\n /** Drawn at the top of the transcript while there is, or may be, more. */\n LoadOlder: React.ComponentType<AgentChatLoadOlderSlotProps>;\n /** Replaces the tool strip and the approval cards at the foot. */\n Tools: React.ComponentType<AgentChatToolsSlotProps>;\n Composer: React.ComponentType<AgentChatComposerSlotProps>;\n}\n\n/**\n * Class names for the wrappers a host cannot otherwise reach.\n *\n * `transcript` REPLACES the default spacing (`gap-5 px-4 py-4`) rather than\n * merging with it: a reading column sets its own gutter through a class, and a\n * utility left on the element would fight it. The structural classes — flex,\n * min-width — stay in either case.\n */\nexport interface AgentChatClassNames {\n header?: string;\n scroll?: string;\n transcript?: string;\n /**\n * The history sentinel at the top of the transcript. Out of the flow by\n * default (`absolute inset-x-0 top-0 h-px`), so it adds no space; a host\n * whose column already reserved a row for it passes an in-flow class.\n */\n sentinel?: string;\n tools?: string;\n composer?: string;\n}\n\n/** What every `AgentChat` takes, however its state is owned. */\nexport interface AgentChatBaseProps\n extends Omit<React.ComponentProps<\"div\">, \"children\" | \"onError\"> {\n /** The extension name, shown on each assistant turn. */\n agentName: string;\n /** Rendered between the tool strip and the composer — an attachment picker. */\n attachmentsSlot?: React.ReactNode;\n /**\n * Host wiring for a tool call. Return a node to replace the default card —\n * this is the seam an MCP App frame mounts through, since the sandboxed\n * iframe, its CSP and the `tools/invoke` proxy are the host's to own.\n */\n renderToolCall?: (context: AgentToolRenderContext) => React.ReactNode | undefined;\n /** Header content, e.g. the pop-up's own window controls. */\n headerSlot?: React.ReactNode;\n /** No header at all — for a host with its own session list and New button. */\n hideHeader?: boolean;\n /** The host's own renders for each region. See `AgentChatComponents`. */\n components?: Partial<AgentChatComponents>;\n /** Classes for the wrappers. See `AgentChatClassNames`. */\n classNames?: AgentChatClassNames;\n /**\n * Page older history from the sentinel at the top of the transcript as the\n * reader reaches it, not only from the `LoadOlder` button. Off by default —\n * the pop-up pages on a click. When on, it is armed only once the scroller\n * overflows: a transcript shorter than its viewport has its sentinel\n * permanently in view, and paging on that pulled every page the server had\n * into a chat with three messages in it.\n */\n autoLoadOlder?: boolean;\n}\n\n/**\n * The surface owning its own state: it calls `useAgentChat` on the client it is\n * given. This is what `AgentChatPopup` mounts.\n */\nexport interface AgentChatOwnedProps extends AgentChatBaseProps {\n /** The transport. `createAguiAgentChatClient` is the AG-UI implementation. */\n client: AgentChatClient;\n /**\n * Names the `sessionStorage` slot this window's session key lives in. Scope\n * it to the extension id; two chats with different scopes never share a key.\n */\n storageScope: string;\n /** Do not read or write `sessionStorage`; for a guide or a test. */\n ephemeral?: boolean;\n thinking?: \"low\" | \"medium\" | \"high\";\n chat?: undefined;\n}\n\n/**\n * The surface drawing state the host owns.\n *\n * A host that needs the state OUTSIDE the surface — a sidebar that lists\n * sessions, a shortcut that stops the run, a page that routes on the session\n * key — holds it itself and passes a `UseAgentChatResult` here; the surface\n * then draws that state instead of holding its own. The result may come from\n * `useAgentChat`, or from a store of the host's own projected onto the same\n * shape, which is how cortenaweb drives this surface from the zustand stores\n * the rest of its shell reads (PLATFORM-64). No client, no storage: the host\n * owns both.\n */\nexport interface AgentChatHostedProps extends AgentChatBaseProps {\n chat: UseAgentChatResult;\n client?: undefined;\n storageScope?: undefined;\n ephemeral?: undefined;\n thinking?: undefined;\n}\n\nexport type AgentChatProps = AgentChatOwnedProps | AgentChatHostedProps;\n\n/**\n * Two ways in, one surface. The hook is called in a component of its own so\n * a host that hands over `chat` never calls it — and so a mount that switches\n * from one mode to the other remounts rather than changing its hook order.\n */\nfunction AgentChat(props: AgentChatProps) {\n if (props.chat) {\n const { chat, client: _client, storageScope: _scope, ephemeral: _e, thinking: _t, ...rest } =\n props;\n return <AgentChatSurface {...rest} chat={chat} />;\n }\n return <AgentChatOwned {...props} />;\n}\n\nfunction AgentChatOwned({\n client,\n storageScope,\n ephemeral,\n thinking,\n chat: _chat,\n ...rest\n}: AgentChatOwnedProps) {\n const chat = useAgentChat({\n client,\n storageScope,\n ...(ephemeral === undefined ? {} : { ephemeral }),\n ...(thinking === undefined ? {} : { thinking }),\n });\n return <AgentChatSurface {...rest} chat={chat} />;\n}\n\n/** The default at the top of the transcript: a button, when there is more. */\nfunction DefaultLoadOlder({ hasMore, loading, onLoad }: AgentChatLoadOlderSlotProps) {\n if (!hasMore) {\n return null;\n }\n return (\n <div className=\"flex justify-center\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n data-slot=\"agent-chat-load-older\"\n disabled={loading}\n onClick={onLoad}\n >\n {loading ? \"Loading…\" : \"Load older messages\"}\n </Button>\n </div>\n );\n}\n\n/** Distance from the bottom, in pixels, within which new content re-pins the view. */\nconst NEAR_BOTTOM_PX = 100;\n\nfunction AgentChatSurface({\n chat,\n agentName,\n attachmentsSlot,\n renderToolCall,\n headerSlot,\n hideHeader,\n components,\n classNames,\n autoLoadOlder = false,\n className,\n ...props\n}: AgentChatBaseProps & { chat: UseAgentChatResult }) {\n const [drawerOpen, setDrawerOpen] = React.useState(false);\n const [value, setValue] = React.useState(\"\");\n const scrollRef = React.useRef<HTMLDivElement>(null);\n const contentRef = React.useRef<HTMLDivElement>(null);\n const sentinelRef = React.useRef<HTMLDivElement>(null);\n const prevHeightRef = React.useRef(0);\n /** Whether the reader was at the bottom at the last scroll; the smooth re-pin reads it. */\n const nearBottomRef = React.useRef(true);\n /** The scroll height saved before a page of older history is prepended; 0 when none is in flight. */\n const prependHeightRef = React.useRef(0);\n /** Set by a prepend restore; the resize observer sits out the one notification the prepend produces. */\n const holdRef = React.useRef(false);\n const prevMessageCountRef = React.useRef(0);\n\n const visible = chat.messages.filter((message) => !isHiddenMessage(message));\n const Empty = components?.Empty;\n // The transcript region is replaced whole by a host's Empty while there is\n // nothing to show — a skeleton wants the height the scroller would have had,\n // and a paragraph inside the column cannot take it.\n // Not while there is an error, either: with a host's Empty holding the region\n // the banner would have nowhere to be drawn, and the failure would be hidden\n // behind a welcome screen.\n const showEmpty = visible.length === 0 && !chat.streaming && !chat.error;\n const showTranscript = !(showEmpty && Empty);\n\n /**\n * Re-pin to the bottom whenever the content grows or the viewport shrinks.\n * A message keeps growing after it mounts — markdown settles, the tool strip\n * appears between the list and the composer — and every one of those leaves\n * the newest content under the composer until the reader scrolls by hand.\n *\n * Jumping rather than smooth-scrolling: this fires many times a second during\n * streaming, and queued smooth animations fight each other and lose.\n *\n * Keyed on `showTranscript`: the scroller is unmounted while a host's Empty\n * holds the region, and the observer has to attach to the one that mounts\n * when the first message arrives.\n */\n React.useEffect(() => {\n const scrollEl = scrollRef.current;\n const contentEl = contentRef.current;\n if (!scrollEl || !contentEl || typeof ResizeObserver === \"undefined\") {\n return;\n }\n const observer = new ResizeObserver(() => {\n if (holdRef.current) {\n // The growth was a prepend, and the layout effect below has already\n // put the reader back where they were. Judging it against the height\n // before the page landed would read as \"the reader is near the\n // bottom\" and undo the hold.\n holdRef.current = false;\n prevHeightRef.current = scrollEl.scrollHeight;\n return;\n }\n const { scrollTop, scrollHeight, clientHeight } = scrollEl;\n const stick = shouldStickToBottom({\n prevScrollHeight: prevHeightRef.current,\n scrollTop,\n clientHeight,\n });\n prevHeightRef.current = scrollHeight;\n if (stick) {\n scrollEl.scrollTop = scrollEl.scrollHeight;\n }\n });\n observer.observe(contentEl);\n observer.observe(scrollEl);\n const onScroll = () => {\n nearBottomRef.current =\n scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < NEAR_BOTTOM_PX;\n };\n scrollEl.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => {\n observer.disconnect();\n scrollEl.removeEventListener(\"scroll\", onScroll);\n };\n }, [showTranscript]);\n\n /**\n * Page older history as the reader reaches the top, not only on a click —\n * when the host asks for it with `autoLoadOlder`. The sentinel is the first\n * child of the transcript; when it scrolls into the viewport and there is\n * more, the next page is asked for.\n */\n // Keyed on the METHOD, not on `chat`. The hook returns a fresh result object\n // every render, so depending on `chat` re-created this callback — and\n // re-attached the observer below — on every keystroke of a stream.\n const loadOlderChat = chat.loadOlder;\n const loadOlder = React.useCallback(() => {\n const scrollEl = scrollRef.current;\n // Measured BEFORE the call, and kept only when a load really started. The\n // store declines while one is in flight, when there is no more and when\n // there is no session; a height saved on a declined call was restored\n // against the next thing that grew the transcript — a new message — and\n // shoved the reader up by its height.\n const height = scrollEl?.scrollHeight ?? 0;\n if (loadOlderChat() === true && height > 0) {\n prependHeightRef.current = height;\n }\n }, [loadOlderChat]);\n\n React.useEffect(() => {\n const sentinel = sentinelRef.current;\n const scrollEl = scrollRef.current;\n if (!autoLoadOlder || !sentinel || !scrollEl || typeof IntersectionObserver === \"undefined\") {\n return;\n }\n const observer = new IntersectionObserver(\n (entries) => {\n if (!entries[0]?.isIntersecting) {\n return;\n }\n // Armed only once the scroller overflows. Before that the sentinel is\n // simply always in view, and \"in view\" means nothing about the reader\n // having gone looking for what came before.\n if (scrollEl.scrollHeight <= scrollEl.clientHeight) {\n return;\n }\n if (chat.hasMoreHistory && !chat.loadingHistory) {\n loadOlder();\n }\n },\n { root: scrollEl, threshold: 0.1 },\n );\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [autoLoadOlder, chat.hasMoreHistory, chat.loadingHistory, loadOlder, showTranscript]);\n\n /**\n * After a prepend, put the reader back where they were; after an append,\n * follow the newest message — smoothly, once per message rather than per\n * resize, which is why this is separate from the observer above and why it\n * is NOT keyed on the stream text: growth during a run is the observer's,\n * and a smooth scroll queued per delta fought it and lost.\n *\n * A layout effect, so the restore lands before the paint; an effect after\n * it drew one frame at the old `scrollTop` with the new page on top.\n */\n useLayoutEffectSafe(() => {\n const scrollEl = scrollRef.current;\n if (!scrollEl) {\n return;\n }\n const prevCount = prevMessageCountRef.current;\n const count = chat.messages.length;\n prevMessageCountRef.current = count;\n const saved = prependHeightRef.current;\n if (saved > 0 && !chat.loadingHistory) {\n // The load that saved this height is over — with a page, or without\n // one (a failure, or the store declining after all). Either way the\n // height is spent; it must not be restored against a later append.\n prependHeightRef.current = 0;\n if (count > prevCount && prevCount > 0) {\n const delta = scrollEl.scrollHeight - saved;\n if (delta > 0) {\n scrollEl.scrollTop += delta;\n prevHeightRef.current = scrollEl.scrollHeight;\n holdRef.current = true;\n return;\n }\n }\n }\n if (count !== prevCount && nearBottomRef.current) {\n // jsdom has no `scrollTo` on elements, and a host's page test renders\n // this surface under jsdom; the jump is the honest fallback there.\n if (typeof scrollEl.scrollTo === \"function\") {\n scrollEl.scrollTo({ top: scrollEl.scrollHeight, behavior: \"smooth\" });\n } else {\n scrollEl.scrollTop = scrollEl.scrollHeight;\n }\n }\n }, [chat.messages, chat.loadingHistory]);\n\n const submit = React.useCallback(() => {\n const trimmed = value.trim();\n // Enter reaches here while a run is live, where the button is a Stop\n // control and cannot be pressed. One run at a time, by both routes.\n if (!trimmed || chat.streaming) {\n return;\n }\n chat.send(trimmed);\n setValue(\"\");\n }, [chat, value]);\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === \"Escape\" && chat.streaming) {\n // Escape stops the run. It does not close the pop-up while a run is in\n // flight: the first Escape is nearly always \"stop\", not \"go away\".\n event.preventDefault();\n event.stopPropagation();\n chat.abort();\n return;\n }\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n submit();\n }\n },\n [chat, submit],\n );\n\n const toolEntries = chat.toolOrder\n .map((id) => chat.tools[id])\n .filter((entry): entry is AgentToolEntry => entry !== undefined);\n // The strip outlives the run rather than vanishing with it. The steps of a\n // finished turn are the record of what the agent did on the user's behalf,\n // and an MCP App drawn by one of them is a surface the user is meant to work\n // in — one that disappeared the instant the agent stopped talking would be no\n // use to anybody. The next run clears it.\n const showTools = toolEntries.length > 0 || chat.approvals.length > 0;\n\n const Message = components?.Message;\n const Streaming = components?.Streaming;\n const LoadOlder = components?.LoadOlder ?? DefaultLoadOlder;\n const Tools = components?.Tools;\n const Composer = components?.Composer;\n\n return (\n <div\n data-slot=\"agent-chat\"\n className={cn(\"flex h-full min-h-0 w-full flex-col bg-[var(--ds-background)]\", className)}\n {...props}\n >\n {hideHeader ? null : (\n <div\n data-slot=\"agent-chat-header\"\n className={cn(\n \"flex shrink-0 items-center gap-2 border-b border-[var(--ds-border-subtle)] px-3 py-2\",\n classNames?.header,\n )}\n >\n <Button\n size=\"sm\"\n variant=\"ghost\"\n data-slot=\"agent-chat-sessions-trigger\"\n aria-expanded={drawerOpen}\n onClick={() => {\n setDrawerOpen((open) => !open);\n chat.refreshSessions();\n }}\n >\n {chat.sessions.length === 1 ? \"1 session\" : `${chat.sessions.length} sessions`}\n </Button>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n data-slot=\"agent-chat-new-session\"\n aria-label=\"New session\"\n onClick={() => {\n chat.newSession();\n setDrawerOpen(false);\n }}\n >\n <MessageSquarePlus aria-hidden />\n New\n </Button>\n <div className=\"ml-auto flex items-center gap-1\">{headerSlot}</div>\n </div>\n )}\n\n {drawerOpen && !hideHeader ? (\n <AgentSessionDrawer\n sessions={chat.sessions}\n current={chat.sessionKey}\n loading={chat.loadingSessions}\n onResume={(key) => {\n chat.resumeSession(key);\n setDrawerOpen(false);\n }}\n />\n ) : null}\n\n {showTranscript ? (\n <div\n ref={scrollRef}\n data-slot=\"agent-chat-scroll\"\n // `overflow-anchor: none`: the layout effect above owns the reader's\n // place across a prepend. Chrome anchors by itself and the two\n // together shoved the reader up by a page; Safari does not anchor at\n // all. One rule, ours, in every browser.\n className={cn(\n \"min-h-0 flex-1 overflow-x-hidden overflow-y-auto [overflow-anchor:none]\",\n classNames?.scroll,\n )}\n >\n <div\n ref={contentRef}\n role=\"log\"\n aria-live=\"polite\"\n aria-label={`${agentName} chat`}\n className={cn(\n \"relative flex min-w-0 flex-col\",\n classNames?.transcript ?? \"gap-5 px-4 py-4\",\n )}\n >\n {/* The history sentinel: first, so reaching it means reaching the top. */}\n <div\n ref={sentinelRef}\n data-slot=\"agent-chat-sentinel\"\n className={classNames?.sentinel ?? \"absolute inset-x-0 top-0 h-px\"}\n />\n\n {chat.error ? (\n <ErrorBanner message={chat.error} />\n ) : null}\n\n {chat.hasMoreHistory || chat.loadingHistory ? (\n <LoadOlder\n hasMore={chat.hasMoreHistory}\n loading={chat.loadingHistory}\n onLoad={loadOlder}\n />\n ) : null}\n\n {showEmpty ? (\n <p\n data-slot=\"agent-chat-empty\"\n className=\"text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]\"\n >\n Ask {agentName} anything about this screen.\n </p>\n ) : null}\n\n {visible.map((message, index) =>\n Message ? (\n <Message\n key={message.id}\n message={message}\n index={index}\n messages={visible}\n agentName={agentName}\n sessionKey={chat.sessionKey}\n streaming={chat.streaming}\n onAction={chat.sendAction}\n />\n ) : (\n <AgentMessageBubble\n key={message.id}\n message={message}\n agentName={agentName}\n streaming={chat.streaming}\n onAction={chat.sendAction}\n />\n ),\n )}\n\n {chat.streaming ? (\n Streaming ? (\n <Streaming\n agentName={agentName}\n text={chat.streamText}\n thinkingText={chat.streamThinkingText}\n a2ui={chat.streamA2ui}\n steps={chat.steps}\n />\n ) : (\n <AgentStreamingBubble\n agentName={agentName}\n text={chat.streamText}\n thinkingText={chat.streamThinkingText}\n a2ui={chat.streamA2ui}\n steps={chat.steps}\n />\n )\n ) : chat.steps.length > 0 ? (\n // The turn is over but its steps are the record of what was done to\n // answer it, and they stay until the next run replaces them — the\n // same rule the tool strip below follows.\n <AgentSteps steps={chat.steps} />\n ) : null}\n </div>\n </div>\n ) : Empty ? (\n <Empty agentName={agentName} loadingHistory={chat.loadingHistory} />\n ) : null}\n\n {Tools ? (\n <Tools\n entries={toolEntries}\n approvals={chat.approvals}\n streaming={chat.streaming}\n renderToolCall={renderToolCall}\n onResolveApproval={chat.resolveApproval}\n />\n ) : showTools ? (\n <div\n data-slot=\"agent-chat-tools\"\n className={cn(\n \"flex shrink-0 flex-col gap-1.5 border-t border-[var(--ds-border-subtle)] px-3 py-2\",\n classNames?.tools,\n )}\n >\n {toolEntries.length > 0 ? (\n <Badge variant=\"neutral\" className=\"w-fit\">\n {toolEntries.length === 1 ? \"1 step\" : `${toolEntries.length} steps`}\n </Badge>\n ) : null}\n {toolEntries.map((entry) => {\n const custom = renderToolCall?.({\n entry,\n mcpAppUri: readMcpAppUri(entry.output),\n });\n return (\n <React.Fragment key={entry.id}>\n {custom ?? <AgentToolCard entry={entry} />}\n </React.Fragment>\n );\n })}\n {chat.approvals.map((request) => (\n <AgentApprovalCard\n key={request.id}\n request={request}\n onResolve={chat.resolveApproval}\n />\n ))}\n </div>\n ) : null}\n\n {attachmentsSlot ? (\n <div data-slot=\"agent-chat-attachments\" className=\"shrink-0 px-3\">\n {attachmentsSlot}\n </div>\n ) : null}\n\n {Composer ? (\n <Composer\n agentName={agentName}\n streaming={chat.streaming}\n send={chat.send}\n abort={chat.abort}\n />\n ) : (\n <div\n data-slot=\"agent-chat-composer\"\n className={cn(\n \"flex shrink-0 items-end gap-2 border-t border-[var(--ds-border-subtle)] p-3\",\n classNames?.composer,\n )}\n >\n <Textarea\n data-slot=\"agent-chat-input\"\n aria-label={`Message ${agentName}`}\n placeholder={`Message ${agentName}…`}\n rows={1}\n className=\"max-h-40 min-h-9 resize-none\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n onKeyDown={onKeyDown}\n />\n {chat.streaming ? (\n <Button\n size=\"icon\"\n variant=\"outline\"\n data-slot=\"agent-chat-stop\"\n aria-label=\"Stop the run\"\n onClick={chat.abort}\n >\n <Square aria-hidden />\n </Button>\n ) : (\n <Button\n size=\"icon\"\n data-slot=\"agent-chat-send\"\n aria-label=\"Send\"\n disabled={!value.trim()}\n onClick={submit}\n >\n <Send aria-hidden />\n </Button>\n )}\n </div>\n )}\n </div>\n );\n}\n\n/* ── the session drawer ──────────────────────────────────────────────────── */\n\nexport interface AgentSessionDrawerProps {\n sessions: readonly AgentSession[];\n current: string | null;\n loading?: boolean;\n onResume: (key: string) => void;\n}\n\n/**\n * Every session of this agent, resumable from any window.\n *\n * The list is not this window's history: a session started in another window is\n * here and can be resumed, which is the difference between \"sessions are per\n * window\" (which key a window starts on) and \"sessions are private to a window\"\n * (which they are not).\n */\nfunction AgentSessionDrawer({ sessions, current, loading, onResume }: AgentSessionDrawerProps) {\n return (\n <div\n data-slot=\"agent-chat-sessions\"\n className={cn(\n \"max-h-56 shrink-0 overflow-y-auto border-b border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-card)] px-2 py-1.5\",\n )}\n >\n {loading && sessions.length === 0 ? (\n <p className=\"px-2 py-1 text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\">\n Loading sessions…\n </p>\n ) : null}\n {!loading && sessions.length === 0 ? (\n <p className=\"px-2 py-1 text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\">\n No earlier sessions.\n </p>\n ) : null}\n {sessions.map((session) => (\n <button\n key={session.key}\n type=\"button\"\n data-slot=\"agent-chat-session\"\n data-current={session.key === current ? \"\" : undefined}\n onClick={() => onResume(session.key)}\n className={cn(\n \"flex w-full items-center gap-2 rounded-[var(--ds-radius-sm)] px-2 py-1.5 text-left\",\n \"text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-foreground)]\",\n \"transition-colors duration-[var(--ds-duration-fast)] hover:bg-[var(--ds-hover)]\",\n session.key === current && \"bg-[var(--ds-primary-soft)]\",\n )}\n >\n <span className=\"min-w-0 truncate\">{sessionTitle(session)}</span>\n </button>\n ))}\n </div>\n );\n}\n\nexport {\n AgentA2UIBlocks,\n AgentApprovalCard,\n AgentChat,\n AgentMessageBubble,\n AgentSessionDrawer,\n AgentSteps,\n AgentStreamingBubble,\n AgentThinking,\n AgentToolCard,\n};\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
//#region src/components/badge.d.ts
|
|
5
5
|
declare const badgeVariants: (props?: ({
|
|
6
|
-
variant?: "default" | "success" | "
|
|
6
|
+
variant?: "default" | "success" | "warning" | "warn" | "destructive" | "danger" | "info" | "neutral" | "secondary" | "outline" | null | undefined;
|
|
7
7
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
8
|
export interface BadgeProps extends React.ComponentProps<"span">, VariantProps<typeof badgeVariants> {}
|
|
9
9
|
declare function Badge({ className, variant, ...props }: BadgeProps): React.JSX.Element;
|
|
@@ -21,7 +21,7 @@ import { Toast } from "@base-ui/react/toast";
|
|
|
21
21
|
* `toastManager` to `Toaster` when you need to fire toasts outside React.
|
|
22
22
|
*/
|
|
23
23
|
declare const toastVariants: (props?: ({
|
|
24
|
-
variant?: "default" | "success" | "
|
|
24
|
+
variant?: "default" | "success" | "warning" | "destructive" | null | undefined;
|
|
25
25
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
26
26
|
/** The Base UI toast manager: `{ toasts, add, close, update, promise }`. */
|
|
27
27
|
declare const useToast: typeof Toast.useToastManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortena-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Cortena UI \u2014 the shared React component library for cortenaweb and every Cortena extension. shadcn over Base UI, styled entirely from cortena-design tokens.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"repository": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* windows on one session may both send, and cortenacore serialises the runs.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { extractA2UIBlocks, foldA2UIBlocks } from "./a2ui-block";
|
|
19
20
|
import { truncate } from "./step-label";
|
|
20
21
|
import type { AgentChatMessage, AgentChatStep } from "./types";
|
|
21
22
|
|
|
@@ -278,15 +279,25 @@ function isToolRelatedMessage(msg: Record<string, unknown>): boolean {
|
|
|
278
279
|
}
|
|
279
280
|
|
|
280
281
|
/**
|
|
281
|
-
*
|
|
282
|
-
*
|
|
282
|
+
* A leading `[Dow YYYY-MM-DD HH:MM TZ] ` stamp, as the runtime injects it
|
|
283
|
+
* ahead of every message the agent is given. The date is what makes it safe
|
|
284
|
+
* to remove: a user who opens with "[draft] ship it" keeps their bracket.
|
|
285
|
+
* Same expression as `cortena-shared/src/stores/use-chat.ts`; keep in step.
|
|
286
|
+
*/
|
|
287
|
+
const INJECTED_TIMESTAMP_PREFIX = /^\[[^\]]*\d{4}-\d{2}-\d{2} \d{2}:\d{2}[^\]]*\]\s*/;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Strip the envelope the runtime prepends to a user turn: `System: [timestamp] …`
|
|
291
|
+
* lines and `[System Message]` prefixes carrying agent context, and the
|
|
292
|
+
* injected timestamp ahead of the words themselves. None of it is user text.
|
|
283
293
|
*/
|
|
284
294
|
function stripSystemPrefixes(text: string): string {
|
|
285
295
|
return text
|
|
286
296
|
.split("\n")
|
|
287
297
|
.filter((line) => !/^System:\s*\[/.test(line) && !line.startsWith("[System Message]"))
|
|
288
298
|
.join("\n")
|
|
289
|
-
.replace(/^\n+/, "")
|
|
299
|
+
.replace(/^\n+/, "")
|
|
300
|
+
.replace(INJECTED_TIMESTAMP_PREFIX, "");
|
|
290
301
|
}
|
|
291
302
|
|
|
292
303
|
/** Lift `<thinking>…</thinking>` out of plain text. */
|
|
@@ -305,6 +316,14 @@ function parseOneHistoryMessage(raw: unknown): AgentChatMessage {
|
|
|
305
316
|
const content = msg.content as
|
|
306
317
|
| Array<{ type: string; text?: string; thinking?: string; a2ui?: unknown }>
|
|
307
318
|
| undefined;
|
|
319
|
+
// The UI the agent drew, back from the transcript. `chat.history` lifts the
|
|
320
|
+
// ```a2ui fences out of the prose and leaves them as `a2ui` content parts —
|
|
321
|
+
// the same parts a live delta carries — so a reload has a block to render
|
|
322
|
+
// rather than a fence to parse. Without this every drawn surface vanished
|
|
323
|
+
// on reopen. Ported from cortena-shared (CORTENA-44).
|
|
324
|
+
const a2ui = Array.isArray(content)
|
|
325
|
+
? extractA2UIBlocks({ content: content as Array<Record<string, unknown>> })
|
|
326
|
+
: [];
|
|
308
327
|
let text = Array.isArray(content)
|
|
309
328
|
? content
|
|
310
329
|
.filter((c) => c.type === "text" && c.text)
|
|
@@ -342,6 +361,7 @@ function parseOneHistoryMessage(raw: unknown): AgentChatMessage {
|
|
|
342
361
|
timestamp: (msg.timestamp as number) ?? Date.now(),
|
|
343
362
|
thinkingText: tagThinking,
|
|
344
363
|
...(isToolMessage ? { isToolMessage } : {}),
|
|
364
|
+
...(a2ui.length > 0 ? { a2ui } : {}),
|
|
345
365
|
};
|
|
346
366
|
}
|
|
347
367
|
}
|
|
@@ -353,6 +373,7 @@ function parseOneHistoryMessage(raw: unknown): AgentChatMessage {
|
|
|
353
373
|
timestamp: (msg.timestamp as number) ?? Date.now(),
|
|
354
374
|
...(thinkingText ? { thinkingText } : {}),
|
|
355
375
|
...(isToolMessage ? { isToolMessage } : {}),
|
|
376
|
+
...(a2ui.length > 0 ? { a2ui } : {}),
|
|
356
377
|
};
|
|
357
378
|
}
|
|
358
379
|
|
|
@@ -400,7 +421,14 @@ function mergeConsecutiveAssistantMessages(messages: AgentChatMessage[]): AgentC
|
|
|
400
421
|
}
|
|
401
422
|
}
|
|
402
423
|
const combined = cot.filter(Boolean).join("\n\n");
|
|
403
|
-
|
|
424
|
+
// A surface the agent drew in an earlier message of the same turn belongs
|
|
425
|
+
// to the answer the turn produced; folding the run must not drop it.
|
|
426
|
+
const blocks = foldA2UIBlocks(run.flatMap((entry) => entry.a2ui ?? []));
|
|
427
|
+
result.push({
|
|
428
|
+
...visible,
|
|
429
|
+
...(combined ? { thinkingText: combined } : {}),
|
|
430
|
+
...(blocks.length > 0 ? { a2ui: blocks } : {}),
|
|
431
|
+
});
|
|
404
432
|
run = [];
|
|
405
433
|
};
|
|
406
434
|
|
|
@@ -542,7 +570,9 @@ export function isHiddenMessage(message: AgentChatMessage): boolean {
|
|
|
542
570
|
return true;
|
|
543
571
|
}
|
|
544
572
|
if (message.role === "user") {
|
|
545
|
-
return
|
|
573
|
+
return (
|
|
574
|
+
!message.text.trim() && !message.attachments?.length && !message.attachmentItems?.length
|
|
575
|
+
);
|
|
546
576
|
}
|
|
547
577
|
const text = message.text;
|
|
548
578
|
return isToolResult(text) || isSkillContent(text) || isAgentDiagnostics(text) || isToolOutput(text);
|