@tangle-network/agent-app 0.45.28 → 0.45.29

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.
Files changed (38) hide show
  1. package/dist/{DesignCanvas-3RRPVPCP.js → DesignCanvas-RSWQCXM3.js} +2 -2
  2. package/dist/{DesignCanvasEditor-KAMMEX7E.js → DesignCanvasEditor-XZMGIST7.js} +4 -4
  3. package/dist/assistant/index.d.ts +62 -39
  4. package/dist/assistant/index.js +23 -5
  5. package/dist/assistant/index.js.map +1 -1
  6. package/dist/chat-react/index.d.ts +25 -2
  7. package/dist/chat-react/index.js.map +1 -1
  8. package/dist/chat-routes/index.js +1 -1
  9. package/dist/{chunk-WC43OQMR.js → chunk-3I27SDU3.js} +103 -30
  10. package/dist/chunk-3I27SDU3.js.map +1 -0
  11. package/dist/{chunk-NC2V63EL.js → chunk-FNM7JYME.js} +88 -21
  12. package/dist/chunk-FNM7JYME.js.map +1 -0
  13. package/dist/{chunk-4QVG4WJQ.js → chunk-OYTMILRK.js} +10 -3
  14. package/dist/chunk-OYTMILRK.js.map +1 -0
  15. package/dist/{chunk-OYGM5WLD.js → chunk-PURHUAOR.js} +52 -15
  16. package/dist/chunk-PURHUAOR.js.map +1 -0
  17. package/dist/{chunk-AWPK7ZDS.js → chunk-QGOU3VXH.js} +3 -3
  18. package/dist/{chunk-OTLEYHOG.js → chunk-RRH23CGS.js} +24 -4
  19. package/dist/chunk-RRH23CGS.js.map +1 -0
  20. package/dist/design-canvas-react/engine.d.ts +15 -1
  21. package/dist/design-canvas-react/engine.js +6 -2
  22. package/dist/design-canvas-react/index.d.ts +13 -1
  23. package/dist/design-canvas-react/index.js +13 -8
  24. package/dist/design-canvas-react/index.js.map +1 -1
  25. package/dist/design-canvas-react/lazy.js +1 -1
  26. package/dist/sandbox/index.d.ts +11 -1
  27. package/dist/sandbox/index.js +1 -1
  28. package/dist/web-react/index.d.ts +23 -4
  29. package/dist/web-react/index.js +1 -1
  30. package/package.json +14 -3
  31. package/dist/chunk-4QVG4WJQ.js.map +0 -1
  32. package/dist/chunk-NC2V63EL.js.map +0 -1
  33. package/dist/chunk-OTLEYHOG.js.map +0 -1
  34. package/dist/chunk-OYGM5WLD.js.map +0 -1
  35. package/dist/chunk-WC43OQMR.js.map +0 -1
  36. /package/dist/{DesignCanvas-3RRPVPCP.js.map → DesignCanvas-RSWQCXM3.js.map} +0 -0
  37. /package/dist/{DesignCanvasEditor-KAMMEX7E.js.map → DesignCanvasEditor-XZMGIST7.js.map} +0 -0
  38. /package/dist/{chunk-AWPK7ZDS.js.map → chunk-QGOU3VXH.js.map} +0 -0
@@ -16,6 +16,11 @@ import { b as ChatAttachmentInput, F as FileMention } from '../wire-DOZ-O6hD.js'
16
16
  *
17
17
  * `HarnessType` is agent-interface's enum surfaced through sandbox-ui's picker;
18
18
  * `ReasoningLevel` is sandbox-ui's `auto | ReasoningEffort` union.
19
+ *
20
+ * LEGACY: these spellings serve the deprecated `ComposerAgentControls` adapter.
21
+ * Canonical code speaks `/harness`'s `Harness` union and plain effort-id
22
+ * strings (see `/web-react`'s `EffortPicker`); they are re-exported here only
23
+ * so the legacy adapter's props keep resolving.
19
24
  */
20
25
 
21
26
  interface ComposerPlanModeSelection {
@@ -71,6 +76,11 @@ interface ComposerEffortSelection {
71
76
  * session; the composer only needs the display catalog and persistence hooks.
72
77
  */
73
78
  type ComposerProfileSelection = AgentSessionProfileControl;
79
+ /**
80
+ * @deprecated Part of the legacy sandbox-ui adapter — see
81
+ * {@link ComposerAgentControls}. New code uses `AgentSessionControlsProps`
82
+ * from `@tangle-network/agent-app/web-react`.
83
+ */
74
84
  interface ComposerAgentControlsProps {
75
85
  model: ComposerModelSelection;
76
86
  /** Omit on a router-backed composer: a direct model call has no CLI backend,
@@ -117,8 +127,16 @@ interface ComposerAgentControlsProps {
117
127
  * The composer's agent-identity control: profile, backend, model, and
118
128
  * reasoning effort, over sandbox-ui's `AgentSessionControls`.
119
129
  *
120
- * This is an ADAPTER, not a second implementation. sandbox-ui owns the control
121
- * and the harness↔model coherence policy (the catalog is filtered to the
130
+ * @deprecated Use `AgentSessionControls` from `@tangle-network/agent-app/web-react`
131
+ * the canonical picker cluster (its model menu IS the canonical
132
+ * `ModelPicker`). sandbox-ui's `AgentSessionControls` and `dashboard/ModelPicker`
133
+ * are legacy: deprecated, frozen, and removed at sandbox-ui's next major. This
134
+ * adapter stays exported and fully functional because removing a published
135
+ * symbol is a major, but it renders the OLD design; new code must not adopt
136
+ * it. Props mapping for the migration lives in `docs/ui-picker-canon.md`.
137
+ *
138
+ * This is an ADAPTER, not a second implementation. The harness↔model coherence
139
+ * policy is the shared one (the catalog is filtered to the
122
140
  * selected harness, the effort re-clamps to what the harness/model pair
123
141
  * supports, and a harness that ignores a picker is marked with its dead control
124
142
  * disabled). What lives here is the app-shell wiring every product otherwise
@@ -159,6 +177,11 @@ interface EntryComposerProps {
159
177
  * omit it and the composer ships without one. Omitting is a real choice for a
160
178
  * surface with nothing to choose — it should never be an oversight, which is
161
179
  * why this is one prop rather than a free-form slot a caller can forget.
180
+ *
181
+ * NOTE: this prop routes through the deprecated `ComposerAgentControls`
182
+ * adapter (sandbox-ui's legacy strip). It keeps working until sandbox-ui's
183
+ * next major; the canonical picker cluster is `AgentSessionControls` from
184
+ * `/web-react` (see `docs/ui-picker-canon.md`).
162
185
  */
163
186
  agent?: ComposerAgentControlsProps;
164
187
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/chat-react/composer-agent-controls.tsx","../../src/chat-react/entry-composer.tsx","../../src/chat-react/composer-mode-controls.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef } from 'react'\nimport {\n AgentSessionControls,\n type AgentSessionProfileControl,\n} from '@tangle-network/sandbox-ui/chat'\nimport { canonicalModelId } from '@tangle-network/sandbox-ui/dashboard'\nimport type { ModelInfo } from '@tangle-network/sandbox-ui/dashboard'\n// Browser-safe: model-catalog.ts imports nothing. Taken from the source module\n// rather than the `/catalog` subpath so this stays one build graph.\nimport { isChatCapableModel, type RouterModel } from '../runtime/model-catalog'\nimport type { HarnessType, ReasoningEffort, ReasoningLevel } from './types'\n\n/**\n * The model half of the composer's agent identity. `models` is the catalog the\n * picker renders; `value` may be a bare or a canonical (provider-prefixed) id —\n * the adapter maps it to canonical form for the picker and hands the canonical\n * id back to `onChange`, so a product that stores bare ids only has to accept\n * canonical ones in its setter.\n */\nexport interface ComposerModelSelection {\n value: string\n onChange: (next: string) => void\n models: ModelInfo[]\n loading?: boolean\n disabled?: boolean\n}\n\nexport interface ComposerHarnessSelection {\n value: HarnessType\n onChange: (next: HarnessType) => void\n /** Restrict the offered backends. Omit to take the context default. */\n available?: ReadonlyArray<HarnessType>\n disabled?: boolean\n /**\n * A thread's sidecar session is pinned to one harness at its first message,\n * so once the thread has messages the picker locks. With `onNewChat` the\n * locked chip becomes the informative lock (explains the binding + a button\n * to start a fresh session on another backend).\n */\n locked?: boolean\n lockReason?: string\n onNewChat?: () => void\n}\n\nexport interface ComposerEffortSelection {\n value: ReasoningLevel\n onChange: (next: ReasoningLevel) => void\n /**\n * Depths this product's backend actually applies. Omit for the default set.\n * `auto` is NOT a member: the picker adds that sentinel itself, so listing it\n * here would offer it twice.\n */\n available?: ReadonlyArray<ReasoningEffort>\n disabled?: boolean\n}\n\n/**\n * A selectable named bundle of persona and tool groups. The product resolves\n * the selected id to the runtime AgentProfile before creating or continuing a\n * session; the composer only needs the display catalog and persistence hooks.\n */\nexport type ComposerProfileSelection = AgentSessionProfileControl\n\nexport interface ComposerAgentControlsProps {\n model: ComposerModelSelection\n /** Omit on a router-backed composer: a direct model call has no CLI backend,\n * so the picker would be inert. Model + effort still render. */\n harness?: ComposerHarnessSelection\n /** Universal named agent profile: persona + tool groups, independent of backend. */\n profile?: ComposerProfileSelection\n effort?: ComposerEffortSelection\n className?: string\n /**\n * Which surface the controls live on. `'chat'` (the default) drops the\n * shell-only `cli-base` backend — it has no conversational agent — and tells\n * the shared control to use its chat-only default set. It also drops models\n * that cannot serve a text turn (image / TTS / embedding / audio-in) from\n * the model picker. `'all'` keeps every backend and every model for non-chat\n * / scheduled surfaces.\n */\n context?: 'chat' | 'all'\n /**\n * Trigger layout. `'responsive'` (the default) lays the pickers out in a\n * row on wider screens and uses a compact gear on narrow screens. `'combined'`\n * collapses the pickers behind one\n * labeled trigger reading the current selection (`harness · model · effort`).\n * `'gear'` uses an anonymous gear icon instead of the label; `'inline'` lays\n * the pickers out in a row. `'responsive'` uses the labeled trigger on wider\n * screens and the compact gear on narrow screens so Send stays reachable.\n */\n layout?: 'inline' | 'gear' | 'combined' | 'responsive'\n /**\n * Where the pickers open. `'down'` pins them downward for a composer floating\n * in open space (the centered entry surface) so a tall menu can't flip up over\n * the heading. Defaults to `'auto'` (collision-aware).\n */\n menuPlacement?: 'auto' | 'down'\n /**\n * Restrict the model catalog to what the selected harness can run instead of\n * letting an incompatible model snap the harness. ON by default: a workspace\n * harness is sandbox-bound, and a silent swap tears down and recreates the\n * sandbox. Turn OFF only on a model-first surface with no bound sandbox.\n */\n filterModelsToHarness?: boolean\n}\n\n/**\n * The chat copilot must never offer `cli-base`: it is a shell-only backend with\n * no conversational agent. sandbox-ui's `context='chat'` drops it from the\n * default set, but an explicit `available` list (a plan-filtered one may include\n * `cli-base`) overrides that trim. So strip it here at the data layer whenever\n * the surface is chat — no `cli-base` ever reaches render.\n */\nfunction harnessesForContext(\n available: ReadonlyArray<HarnessType> | undefined,\n context: 'chat' | 'all',\n): ReadonlyArray<HarnessType> | undefined {\n if (!available) return available\n if (context !== 'chat') return available\n return available.filter((h) => h !== 'cli-base')\n}\n\n/**\n * The model-side twin of the harness trim above. Products hand the picker the\n * router's raw `/v1/models`, which lists every routeable endpoint — image\n * generators, TTS voices, embedding models, audio-in transcription. None of\n * them can serve a chat turn, and on the live catalogue that is 35 of 504\n * entries a user could pick and get a broken turn from.\n *\n * `context: 'all'` keeps the full list for non-chat surfaces.\n *\n * The selected model is never filtered out, whatever its modalities say: a\n * product may deliberately pin something exotic, and a picker whose own value\n * is missing from its list renders as blank.\n *\n * That escape hatch has to recognise BOTH spellings of the id. A product stores\n * whatever `onChange` handed it, and `onChange` hands back the canonical\n * provider-prefixed form — so a selection pinned before this trim existed is\n * stored as `openai/gpt-image-1` while the catalog entry's own `id` is the bare\n * `gpt-image-1`. Matching only the bare form drops exactly the model the guard\n * exists to keep, and the picker renders unset, which reads as data loss.\n */\nfunction modelsForContext(\n models: ModelInfo[],\n selectedId: string,\n context: 'chat' | 'all',\n): ModelInfo[] {\n if (context !== 'chat') return models\n return models.filter(\n (m) =>\n m.id === selectedId ||\n canonicalModelId(m) === selectedId ||\n isChatCapableModel(m as unknown as RouterModel),\n )\n}\n\n/**\n * The composer's agent-identity control: profile, backend, model, and\n * reasoning effort, over sandbox-ui's `AgentSessionControls`.\n *\n * This is an ADAPTER, not a second implementation. sandbox-ui owns the control\n * and the harness↔model coherence policy (the catalog is filtered to the\n * selected harness, the effort re-clamps to what the harness/model pair\n * supports, and a harness that ignores a picker is marked with its dead control\n * disabled). What lives here is the app-shell wiring every product otherwise\n * re-derives:\n *\n * - MODEL-ID BOUNDARY. Products store bare ids (what a chat send body expects)\n * while the picker matches on canonical provider-prefixed ids. The canonical\n * form goes in; the canonical id comes back out to `onChange`.\n * - HARNESS-SNAP SUPPRESSION. The shared control snaps the model whenever the\n * harness changes, calling the model `onChange` synchronously with a default\n * compatible model. A product that remembers a per-harness pick must ignore\n * that snap, or it both discards the remembered pick and persists it under\n * the wrong harness's key. The guard is armed for exactly the synchronous\n * window of a harness change and disarmed on the next microtask, so it can\n * never stay stuck — a later genuine pick is never swallowed, even when the\n * harness change was a no-op.\n * - CHAT-CONTEXT TRIM. On a chat surface, `cli-base` never reaches the harness\n * picker and a non-chat model never reaches the model picker. Products feed\n * the picker the router's raw model list, where 35 of 504 entries (image,\n * TTS, embeddings, audio-in transcription) cannot serve a chat turn at all.\n *\n * Behavioral modes (a plan toggle) are NOT agent identity and do not belong\n * here — they dock on the other side of the composer via `controls`.\n */\nexport function ComposerAgentControls({\n model,\n harness,\n profile,\n effort,\n className,\n context = 'chat',\n layout = 'responsive',\n menuPlacement,\n filterModelsToHarness = true,\n}: ComposerAgentControlsProps) {\n const canonicalSelected = useMemo(() => {\n const selected = model.models.find((m) => m.id === model.value)\n if (selected) return canonicalModelId(selected)\n // Already canonical, or the catalog has not loaded — pass through unchanged\n // so a provider-prefixed id still matches once models arrive.\n return model.value\n }, [model.value, model.models])\n\n const offeredModels = useMemo(\n () => modelsForContext(model.models, model.value, context),\n [model.models, model.value, context],\n )\n\n const harnessSwitching = useRef(false)\n const harnessOnChange = harness?.onChange\n const onHarnessChange = useCallback(\n (next: HarnessType) => {\n harnessSwitching.current = true\n harnessOnChange?.(next)\n queueMicrotask(() => {\n harnessSwitching.current = false\n })\n },\n [harnessOnChange],\n )\n\n const modelOnChange = model.onChange\n const onModelChange = useCallback(\n (canonical: string) => {\n if (harnessSwitching.current) return\n modelOnChange(canonical)\n },\n [modelOnChange],\n )\n\n const sharedProps = {\n context,\n menuPlacement,\n filterModelsToHarness,\n model: {\n value: canonicalSelected,\n onChange: onModelChange,\n models: offeredModels,\n loading: model.loading,\n disabled: model.disabled,\n },\n harness: harness\n ? {\n value: harness.value,\n onChange: onHarnessChange,\n available: harnessesForContext(harness.available, context),\n disabled: harness.disabled,\n locked: harness.locked,\n lockReason: harness.lockReason,\n onNewChat: harness.onNewChat,\n }\n : undefined,\n profile,\n reasoning: effort\n ? {\n value: effort.value,\n onChange: effort.onChange,\n available: effort.available,\n disabled: effort.disabled,\n }\n : undefined,\n }\n\n if (layout === 'responsive') {\n return (\n <>\n <AgentSessionControls\n {...sharedProps}\n className={className ? `hidden sm:flex ${className}` : 'hidden sm:flex'}\n layout=\"inline\"\n />\n <AgentSessionControls\n {...sharedProps}\n className={className ? `flex sm:hidden ${className}` : 'flex sm:hidden'}\n layout=\"gear\"\n />\n </>\n )\n }\n\n return (\n <AgentSessionControls\n className={className}\n {...sharedProps}\n layout={layout}\n />\n )\n}\n","import { useState, type ReactNode } from 'react'\nimport { AgentComposer } from '@tangle-network/sandbox-ui/chat'\nimport { ATTACHMENT_ACCEPT, useComposerAttachments } from '../web-react/use-composer-attachments'\nimport type { UseFileMentionsResult } from '../web-react/use-file-mentions'\nimport type { ChatAttachmentInput, FileMention } from '../chat-routes/wire'\nimport {\n ComposerAgentControls,\n type ComposerAgentControlsProps,\n} from './composer-agent-controls'\nimport { ComposerModeControls } from './composer-mode-controls'\nimport type { ComposerPlanModeSelection } from './types'\n\nexport interface EntryComposerProps {\n /** The one line above the input. Domain copy — always a product parameter. */\n heading?: ReactNode\n /** Optional supporting line under the heading. */\n subheading?: ReactNode\n placeholder?: string\n initialValue?: string\n sendLabel?: string\n disabled?: boolean\n /**\n * Agent identity (profile/backend/model/effort). Pass it and the control row renders;\n * omit it and the composer ships without one. Omitting is a real choice for a\n * surface with nothing to choose — it should never be an oversight, which is\n * why this is one prop rather than a free-form slot a caller can forget.\n */\n agent?: ComposerAgentControlsProps\n /**\n * Product-specific behavioral controls docked on the LEFT. A mode is an\n * on/off switch, not a value picker, so it sits apart from agent identity.\n * For the standard plan-approval control, prefer `planMode`.\n */\n modes?: ReactNode\n /**\n * Standard plan-approval mode. Pass only when the selected backend supports\n * it; omitted means the control is not rendered.\n */\n planMode?: ComposerPlanModeSelection\n /**\n * Upload endpoint for staged attachments. Omit and the attach affordance is\n * hidden — a composer with no place to put a file must not offer one.\n */\n uploadUrl?: string\n accept?: string\n onAttachmentError?: (reason: string) => void\n onRejectFiles?: Parameters<typeof AgentComposer>[0]['onRejectFiles']\n /**\n * `@`-file mentions. The hook itself is shared (`useFileMentions`), but the\n * cold-box policy around it — whether pressing `@` is worth starting a\n * sandbox — is a per-surface product call, so the result is injected rather\n * than created here.\n */\n mentions?: UseFileMentionsResult\n mentionPopoverClassName?: string\n /** Rendered under the composer: suggestion pills, an onboarding nudge, a\n * disclaimer. All domain copy. */\n footer?: ReactNode\n /** Block submit until a persisted selection has resolved against the catalog,\n * so the first turn cannot go out under the wrong model. Defaults to true. */\n ready?: boolean\n onSubmit: (\n prompt: string,\n attachments: ChatAttachmentInput[],\n mentions: FileMention[],\n ) => void\n className?: string\n composerClassName?: string\n /** Max width of the centered column. Defaults to the fleet's 820px. */\n maxWidth?: number\n}\n\n/**\n * `EntryComposer` — the centered \"what do you want to work on?\" surface a\n * product shows before a conversation exists (a new thread, an empty session,\n * a workspace overview).\n *\n * It exists because three products each grew their own, and they drifted into\n * three different capability sets rather than three different looks: one lost\n * the attach button, one lost the model and effort pickers entirely, one\n * hand-rolled a `<textarea>` and wired pickers from three separate packages\n * into one row. The composer, the pickers, the attachment queue and the mention\n * index were all ALREADY shared — what was not shared was the assembly, so\n * every product re-derived which controls an entry surface gets, and each\n * re-derivation dropped a different one.\n *\n * Mechanism lives here: layout, the attachment queue, the mention wiring, the\n * submit gate (nothing sends while an upload is pending or failed, or before\n * the model selection has resolved). Domain stays a parameter: `heading`,\n * `placeholder`, `footer` (suggestion pills, disclaimers) and the selections\n * themselves.\n *\n * The docked in-thread composer is NOT this component — it renders\n * `AgentComposer` directly with the same {@link ComposerAgentControls}, because\n * a docked composer has a transcript above it and no hero layout.\n */\nexport function EntryComposer({\n heading,\n subheading,\n placeholder,\n initialValue = '',\n sendLabel,\n disabled,\n agent,\n modes,\n planMode,\n uploadUrl,\n accept = ATTACHMENT_ACCEPT,\n onAttachmentError,\n onRejectFiles,\n mentions,\n mentionPopoverClassName,\n footer,\n ready = true,\n onSubmit,\n className,\n composerClassName,\n maxWidth = 820,\n}: EntryComposerProps) {\n const [value, setValue] = useState(initialValue)\n const attachments = useComposerAttachments({\n uploadUrl,\n enabled: !!uploadUrl,\n onReject: onAttachmentError,\n onError: onAttachmentError,\n })\n\n function submit(prompt: string) {\n if (!ready || disabled) return\n const next = prompt.trim()\n // A pending or failed upload must not send: the turn would reference an\n // attachment the store does not have. Surface why rather than no-op'ing.\n if (attachments.hasPending || attachments.hasError) {\n if (attachments.blockReason) onAttachmentError?.(attachments.blockReason)\n return\n }\n if (!next && attachments.references.length === 0) return\n onSubmit(next, attachments.references, mentions?.mentions ?? [])\n setValue('')\n attachments.clear()\n mentions?.clearMentions()\n }\n\n return (\n <div\n className={\n className ??\n 'relative flex flex-1 flex-col items-center justify-center overflow-hidden bg-background px-5'\n }\n >\n <div className=\"w-full\" style={{ maxWidth }}>\n {heading ? (\n <h2 className=\"mb-4 text-center text-[1.75rem] font-medium tracking-tight text-foreground\">\n {heading}\n </h2>\n ) : null}\n {subheading ? (\n <p className=\"mx-auto mb-9 max-w-md text-center text-sm leading-relaxed text-muted-foreground\">\n {subheading}\n </p>\n ) : (\n heading ? <div className=\"mb-7\" /> : null\n )}\n <AgentComposer\n className={composerClassName ?? 'vt-composer'}\n value={value}\n onChange={setValue}\n onSubmit={() => submit(value)}\n placeholder={placeholder}\n sendLabel={sendLabel}\n disabled={disabled}\n autoFocus\n canSubmitAttachmentsOnly\n accept={accept}\n attachments={attachments.composerFiles}\n onAttach={uploadUrl ? (files) => void attachments.addFiles(files) : undefined}\n onRejectFiles={onRejectFiles}\n onRemoveFile={attachments.removeAttachment}\n onRetryFile={attachments.retry}\n mention={\n mentions\n ? {\n ...mentions.mention,\n // Sidebar tone + hairline so the popover reads as app chrome\n // (the same treatment the picker menus get) instead of the\n // brighter overlay tone the component defaults to.\n popoverClassName:\n mentionPopoverClassName ??\n 'bg-surface-container-low border-[var(--md3-outline-variant)]',\n }\n : undefined\n }\n controls={modes ?? <ComposerModeControls planMode={planMode} />}\n trailing={\n agent ? <ComposerAgentControls menuPlacement=\"down\" {...agent} /> : undefined\n }\n />\n {footer ? <div className=\"mt-7\">{footer}</div> : null}\n </div>\n </div>\n )\n}\n","import { ListChecks } from 'lucide-react'\nimport { cn } from '@tangle-network/sandbox-ui/utils'\nimport type { ComposerPlanModeSelection } from './types'\n\nexport interface ComposerModeControlsProps {\n /**\n * Plan-approval mode. Products pass it only when the selected backend can\n * propose a plan and wait for approval; omitted means nothing renders.\n */\n planMode?: ComposerPlanModeSelection\n}\n\n/**\n * The shared plan-mode toggle for the left side of an agent composer.\n * Plan mode is a behavioral switch, not part of the profile/backend/model/\n * thinking identity controls on the right.\n */\nexport function ComposerModeControls({ planMode }: ComposerModeControlsProps) {\n if (!planMode) return null\n\n return (\n <button\n type=\"button\"\n aria-pressed={planMode.enabled}\n disabled={planMode.saving}\n onClick={() => planMode.setEnabled(!planMode.enabled)}\n title=\"Plan mode: the agent proposes a plan you approve before it executes\"\n className={cn(\n // Inset ring: this chip sits in a composer row that clips its overflow,\n // so an outward ring loses three of its four sides. Only the offset is\n // overridden — width and colour stay with the tokens.\n 'inline-flex h-7 items-center gap-1 rounded-full border px-2.5 text-xs transition-colors focus-visible:[outline-offset:-2px]',\n planMode.enabled\n ? 'border-primary/50 bg-primary/10 text-primary'\n : 'border-border bg-transparent text-muted-foreground hover:text-foreground',\n planMode.saving && 'opacity-60',\n )}\n >\n <ListChecks className=\"h-3.5 w-3.5\" />\n Plan\n </button>\n )\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs"]}
1
+ {"version":3,"sources":["../../src/chat-react/composer-agent-controls.tsx","../../src/chat-react/entry-composer.tsx","../../src/chat-react/composer-mode-controls.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef } from 'react'\nimport {\n AgentSessionControls,\n type AgentSessionProfileControl,\n} from '@tangle-network/sandbox-ui/chat'\nimport { canonicalModelId } from '@tangle-network/sandbox-ui/dashboard'\nimport type { ModelInfo } from '@tangle-network/sandbox-ui/dashboard'\n// Browser-safe: model-catalog.ts imports nothing. Taken from the source module\n// rather than the `/catalog` subpath so this stays one build graph.\nimport { isChatCapableModel, type RouterModel } from '../runtime/model-catalog'\nimport type { HarnessType, ReasoningEffort, ReasoningLevel } from './types'\n\n/**\n * The model half of the composer's agent identity. `models` is the catalog the\n * picker renders; `value` may be a bare or a canonical (provider-prefixed) id —\n * the adapter maps it to canonical form for the picker and hands the canonical\n * id back to `onChange`, so a product that stores bare ids only has to accept\n * canonical ones in its setter.\n */\nexport interface ComposerModelSelection {\n value: string\n onChange: (next: string) => void\n models: ModelInfo[]\n loading?: boolean\n disabled?: boolean\n}\n\nexport interface ComposerHarnessSelection {\n value: HarnessType\n onChange: (next: HarnessType) => void\n /** Restrict the offered backends. Omit to take the context default. */\n available?: ReadonlyArray<HarnessType>\n disabled?: boolean\n /**\n * A thread's sidecar session is pinned to one harness at its first message,\n * so once the thread has messages the picker locks. With `onNewChat` the\n * locked chip becomes the informative lock (explains the binding + a button\n * to start a fresh session on another backend).\n */\n locked?: boolean\n lockReason?: string\n onNewChat?: () => void\n}\n\nexport interface ComposerEffortSelection {\n value: ReasoningLevel\n onChange: (next: ReasoningLevel) => void\n /**\n * Depths this product's backend actually applies. Omit for the default set.\n * `auto` is NOT a member: the picker adds that sentinel itself, so listing it\n * here would offer it twice.\n */\n available?: ReadonlyArray<ReasoningEffort>\n disabled?: boolean\n}\n\n/**\n * A selectable named bundle of persona and tool groups. The product resolves\n * the selected id to the runtime AgentProfile before creating or continuing a\n * session; the composer only needs the display catalog and persistence hooks.\n */\nexport type ComposerProfileSelection = AgentSessionProfileControl\n\n/**\n * @deprecated Part of the legacy sandbox-ui adapter — see\n * {@link ComposerAgentControls}. New code uses `AgentSessionControlsProps`\n * from `@tangle-network/agent-app/web-react`.\n */\nexport interface ComposerAgentControlsProps {\n model: ComposerModelSelection\n /** Omit on a router-backed composer: a direct model call has no CLI backend,\n * so the picker would be inert. Model + effort still render. */\n harness?: ComposerHarnessSelection\n /** Universal named agent profile: persona + tool groups, independent of backend. */\n profile?: ComposerProfileSelection\n effort?: ComposerEffortSelection\n className?: string\n /**\n * Which surface the controls live on. `'chat'` (the default) drops the\n * shell-only `cli-base` backend — it has no conversational agent — and tells\n * the shared control to use its chat-only default set. It also drops models\n * that cannot serve a text turn (image / TTS / embedding / audio-in) from\n * the model picker. `'all'` keeps every backend and every model for non-chat\n * / scheduled surfaces.\n */\n context?: 'chat' | 'all'\n /**\n * Trigger layout. `'responsive'` (the default) lays the pickers out in a\n * row on wider screens and uses a compact gear on narrow screens. `'combined'`\n * collapses the pickers behind one\n * labeled trigger reading the current selection (`harness · model · effort`).\n * `'gear'` uses an anonymous gear icon instead of the label; `'inline'` lays\n * the pickers out in a row. `'responsive'` uses the labeled trigger on wider\n * screens and the compact gear on narrow screens so Send stays reachable.\n */\n layout?: 'inline' | 'gear' | 'combined' | 'responsive'\n /**\n * Where the pickers open. `'down'` pins them downward for a composer floating\n * in open space (the centered entry surface) so a tall menu can't flip up over\n * the heading. Defaults to `'auto'` (collision-aware).\n */\n menuPlacement?: 'auto' | 'down'\n /**\n * Restrict the model catalog to what the selected harness can run instead of\n * letting an incompatible model snap the harness. ON by default: a workspace\n * harness is sandbox-bound, and a silent swap tears down and recreates the\n * sandbox. Turn OFF only on a model-first surface with no bound sandbox.\n */\n filterModelsToHarness?: boolean\n}\n\n/**\n * The chat copilot must never offer `cli-base`: it is a shell-only backend with\n * no conversational agent. sandbox-ui's `context='chat'` drops it from the\n * default set, but an explicit `available` list (a plan-filtered one may include\n * `cli-base`) overrides that trim. So strip it here at the data layer whenever\n * the surface is chat — no `cli-base` ever reaches render.\n */\nfunction harnessesForContext(\n available: ReadonlyArray<HarnessType> | undefined,\n context: 'chat' | 'all',\n): ReadonlyArray<HarnessType> | undefined {\n if (!available) return available\n if (context !== 'chat') return available\n return available.filter((h) => h !== 'cli-base')\n}\n\n/**\n * The model-side twin of the harness trim above. Products hand the picker the\n * router's raw `/v1/models`, which lists every routeable endpoint — image\n * generators, TTS voices, embedding models, audio-in transcription. None of\n * them can serve a chat turn, and on the live catalogue that is 35 of 504\n * entries a user could pick and get a broken turn from.\n *\n * `context: 'all'` keeps the full list for non-chat surfaces.\n *\n * The selected model is never filtered out, whatever its modalities say: a\n * product may deliberately pin something exotic, and a picker whose own value\n * is missing from its list renders as blank.\n *\n * That escape hatch has to recognise BOTH spellings of the id. A product stores\n * whatever `onChange` handed it, and `onChange` hands back the canonical\n * provider-prefixed form — so a selection pinned before this trim existed is\n * stored as `openai/gpt-image-1` while the catalog entry's own `id` is the bare\n * `gpt-image-1`. Matching only the bare form drops exactly the model the guard\n * exists to keep, and the picker renders unset, which reads as data loss.\n */\nfunction modelsForContext(\n models: ModelInfo[],\n selectedId: string,\n context: 'chat' | 'all',\n): ModelInfo[] {\n if (context !== 'chat') return models\n return models.filter(\n (m) =>\n m.id === selectedId ||\n canonicalModelId(m) === selectedId ||\n isChatCapableModel(m as unknown as RouterModel),\n )\n}\n\n/**\n * The composer's agent-identity control: profile, backend, model, and\n * reasoning effort, over sandbox-ui's `AgentSessionControls`.\n *\n * @deprecated Use `AgentSessionControls` from `@tangle-network/agent-app/web-react`\n * — the canonical picker cluster (its model menu IS the canonical\n * `ModelPicker`). sandbox-ui's `AgentSessionControls` and `dashboard/ModelPicker`\n * are legacy: deprecated, frozen, and removed at sandbox-ui's next major. This\n * adapter stays exported and fully functional because removing a published\n * symbol is a major, but it renders the OLD design; new code must not adopt\n * it. Props mapping for the migration lives in `docs/ui-picker-canon.md`.\n *\n * This is an ADAPTER, not a second implementation. The harness↔model coherence\n * policy is the shared one (the catalog is filtered to the\n * selected harness, the effort re-clamps to what the harness/model pair\n * supports, and a harness that ignores a picker is marked with its dead control\n * disabled). What lives here is the app-shell wiring every product otherwise\n * re-derives:\n *\n * - MODEL-ID BOUNDARY. Products store bare ids (what a chat send body expects)\n * while the picker matches on canonical provider-prefixed ids. The canonical\n * form goes in; the canonical id comes back out to `onChange`.\n * - HARNESS-SNAP SUPPRESSION. The shared control snaps the model whenever the\n * harness changes, calling the model `onChange` synchronously with a default\n * compatible model. A product that remembers a per-harness pick must ignore\n * that snap, or it both discards the remembered pick and persists it under\n * the wrong harness's key. The guard is armed for exactly the synchronous\n * window of a harness change and disarmed on the next microtask, so it can\n * never stay stuck — a later genuine pick is never swallowed, even when the\n * harness change was a no-op.\n * - CHAT-CONTEXT TRIM. On a chat surface, `cli-base` never reaches the harness\n * picker and a non-chat model never reaches the model picker. Products feed\n * the picker the router's raw model list, where 35 of 504 entries (image,\n * TTS, embeddings, audio-in transcription) cannot serve a chat turn at all.\n *\n * Behavioral modes (a plan toggle) are NOT agent identity and do not belong\n * here — they dock on the other side of the composer via `controls`.\n */\nexport function ComposerAgentControls({\n model,\n harness,\n profile,\n effort,\n className,\n context = 'chat',\n layout = 'responsive',\n menuPlacement,\n filterModelsToHarness = true,\n}: ComposerAgentControlsProps) {\n const canonicalSelected = useMemo(() => {\n const selected = model.models.find((m) => m.id === model.value)\n if (selected) return canonicalModelId(selected)\n // Already canonical, or the catalog has not loaded — pass through unchanged\n // so a provider-prefixed id still matches once models arrive.\n return model.value\n }, [model.value, model.models])\n\n const offeredModels = useMemo(\n () => modelsForContext(model.models, model.value, context),\n [model.models, model.value, context],\n )\n\n const harnessSwitching = useRef(false)\n const harnessOnChange = harness?.onChange\n const onHarnessChange = useCallback(\n (next: HarnessType) => {\n harnessSwitching.current = true\n harnessOnChange?.(next)\n queueMicrotask(() => {\n harnessSwitching.current = false\n })\n },\n [harnessOnChange],\n )\n\n const modelOnChange = model.onChange\n const onModelChange = useCallback(\n (canonical: string) => {\n if (harnessSwitching.current) return\n modelOnChange(canonical)\n },\n [modelOnChange],\n )\n\n const sharedProps = {\n context,\n menuPlacement,\n filterModelsToHarness,\n model: {\n value: canonicalSelected,\n onChange: onModelChange,\n models: offeredModels,\n loading: model.loading,\n disabled: model.disabled,\n },\n harness: harness\n ? {\n value: harness.value,\n onChange: onHarnessChange,\n available: harnessesForContext(harness.available, context),\n disabled: harness.disabled,\n locked: harness.locked,\n lockReason: harness.lockReason,\n onNewChat: harness.onNewChat,\n }\n : undefined,\n profile,\n reasoning: effort\n ? {\n value: effort.value,\n onChange: effort.onChange,\n available: effort.available,\n disabled: effort.disabled,\n }\n : undefined,\n }\n\n if (layout === 'responsive') {\n return (\n <>\n <AgentSessionControls\n {...sharedProps}\n className={className ? `hidden sm:flex ${className}` : 'hidden sm:flex'}\n layout=\"inline\"\n />\n <AgentSessionControls\n {...sharedProps}\n className={className ? `flex sm:hidden ${className}` : 'flex sm:hidden'}\n layout=\"gear\"\n />\n </>\n )\n }\n\n return (\n <AgentSessionControls\n className={className}\n {...sharedProps}\n layout={layout}\n />\n )\n}\n","import { useState, type ReactNode } from 'react'\nimport { AgentComposer } from '@tangle-network/sandbox-ui/chat'\nimport { ATTACHMENT_ACCEPT, useComposerAttachments } from '../web-react/use-composer-attachments'\nimport type { UseFileMentionsResult } from '../web-react/use-file-mentions'\nimport type { ChatAttachmentInput, FileMention } from '../chat-routes/wire'\nimport {\n ComposerAgentControls,\n type ComposerAgentControlsProps,\n} from './composer-agent-controls'\nimport { ComposerModeControls } from './composer-mode-controls'\nimport type { ComposerPlanModeSelection } from './types'\n\nexport interface EntryComposerProps {\n /** The one line above the input. Domain copy — always a product parameter. */\n heading?: ReactNode\n /** Optional supporting line under the heading. */\n subheading?: ReactNode\n placeholder?: string\n initialValue?: string\n sendLabel?: string\n disabled?: boolean\n /**\n * Agent identity (profile/backend/model/effort). Pass it and the control row renders;\n * omit it and the composer ships without one. Omitting is a real choice for a\n * surface with nothing to choose — it should never be an oversight, which is\n * why this is one prop rather than a free-form slot a caller can forget.\n *\n * NOTE: this prop routes through the deprecated `ComposerAgentControls`\n * adapter (sandbox-ui's legacy strip). It keeps working until sandbox-ui's\n * next major; the canonical picker cluster is `AgentSessionControls` from\n * `/web-react` (see `docs/ui-picker-canon.md`).\n */\n agent?: ComposerAgentControlsProps\n /**\n * Product-specific behavioral controls docked on the LEFT. A mode is an\n * on/off switch, not a value picker, so it sits apart from agent identity.\n * For the standard plan-approval control, prefer `planMode`.\n */\n modes?: ReactNode\n /**\n * Standard plan-approval mode. Pass only when the selected backend supports\n * it; omitted means the control is not rendered.\n */\n planMode?: ComposerPlanModeSelection\n /**\n * Upload endpoint for staged attachments. Omit and the attach affordance is\n * hidden — a composer with no place to put a file must not offer one.\n */\n uploadUrl?: string\n accept?: string\n onAttachmentError?: (reason: string) => void\n onRejectFiles?: Parameters<typeof AgentComposer>[0]['onRejectFiles']\n /**\n * `@`-file mentions. The hook itself is shared (`useFileMentions`), but the\n * cold-box policy around it — whether pressing `@` is worth starting a\n * sandbox — is a per-surface product call, so the result is injected rather\n * than created here.\n */\n mentions?: UseFileMentionsResult\n mentionPopoverClassName?: string\n /** Rendered under the composer: suggestion pills, an onboarding nudge, a\n * disclaimer. All domain copy. */\n footer?: ReactNode\n /** Block submit until a persisted selection has resolved against the catalog,\n * so the first turn cannot go out under the wrong model. Defaults to true. */\n ready?: boolean\n onSubmit: (\n prompt: string,\n attachments: ChatAttachmentInput[],\n mentions: FileMention[],\n ) => void\n className?: string\n composerClassName?: string\n /** Max width of the centered column. Defaults to the fleet's 820px. */\n maxWidth?: number\n}\n\n/**\n * `EntryComposer` — the centered \"what do you want to work on?\" surface a\n * product shows before a conversation exists (a new thread, an empty session,\n * a workspace overview).\n *\n * It exists because three products each grew their own, and they drifted into\n * three different capability sets rather than three different looks: one lost\n * the attach button, one lost the model and effort pickers entirely, one\n * hand-rolled a `<textarea>` and wired pickers from three separate packages\n * into one row. The composer, the pickers, the attachment queue and the mention\n * index were all ALREADY shared — what was not shared was the assembly, so\n * every product re-derived which controls an entry surface gets, and each\n * re-derivation dropped a different one.\n *\n * Mechanism lives here: layout, the attachment queue, the mention wiring, the\n * submit gate (nothing sends while an upload is pending or failed, or before\n * the model selection has resolved). Domain stays a parameter: `heading`,\n * `placeholder`, `footer` (suggestion pills, disclaimers) and the selections\n * themselves.\n *\n * The docked in-thread composer is NOT this component — it renders\n * `AgentComposer` directly with the same {@link ComposerAgentControls}, because\n * a docked composer has a transcript above it and no hero layout.\n */\nexport function EntryComposer({\n heading,\n subheading,\n placeholder,\n initialValue = '',\n sendLabel,\n disabled,\n agent,\n modes,\n planMode,\n uploadUrl,\n accept = ATTACHMENT_ACCEPT,\n onAttachmentError,\n onRejectFiles,\n mentions,\n mentionPopoverClassName,\n footer,\n ready = true,\n onSubmit,\n className,\n composerClassName,\n maxWidth = 820,\n}: EntryComposerProps) {\n const [value, setValue] = useState(initialValue)\n const attachments = useComposerAttachments({\n uploadUrl,\n enabled: !!uploadUrl,\n onReject: onAttachmentError,\n onError: onAttachmentError,\n })\n\n function submit(prompt: string) {\n if (!ready || disabled) return\n const next = prompt.trim()\n // A pending or failed upload must not send: the turn would reference an\n // attachment the store does not have. Surface why rather than no-op'ing.\n if (attachments.hasPending || attachments.hasError) {\n if (attachments.blockReason) onAttachmentError?.(attachments.blockReason)\n return\n }\n if (!next && attachments.references.length === 0) return\n onSubmit(next, attachments.references, mentions?.mentions ?? [])\n setValue('')\n attachments.clear()\n mentions?.clearMentions()\n }\n\n return (\n <div\n className={\n className ??\n 'relative flex flex-1 flex-col items-center justify-center overflow-hidden bg-background px-5'\n }\n >\n <div className=\"w-full\" style={{ maxWidth }}>\n {heading ? (\n <h2 className=\"mb-4 text-center text-[1.75rem] font-medium tracking-tight text-foreground\">\n {heading}\n </h2>\n ) : null}\n {subheading ? (\n <p className=\"mx-auto mb-9 max-w-md text-center text-sm leading-relaxed text-muted-foreground\">\n {subheading}\n </p>\n ) : (\n heading ? <div className=\"mb-7\" /> : null\n )}\n <AgentComposer\n className={composerClassName ?? 'vt-composer'}\n value={value}\n onChange={setValue}\n onSubmit={() => submit(value)}\n placeholder={placeholder}\n sendLabel={sendLabel}\n disabled={disabled}\n autoFocus\n canSubmitAttachmentsOnly\n accept={accept}\n attachments={attachments.composerFiles}\n onAttach={uploadUrl ? (files) => void attachments.addFiles(files) : undefined}\n onRejectFiles={onRejectFiles}\n onRemoveFile={attachments.removeAttachment}\n onRetryFile={attachments.retry}\n mention={\n mentions\n ? {\n ...mentions.mention,\n // Sidebar tone + hairline so the popover reads as app chrome\n // (the same treatment the picker menus get) instead of the\n // brighter overlay tone the component defaults to.\n popoverClassName:\n mentionPopoverClassName ??\n 'bg-surface-container-low border-[var(--md3-outline-variant)]',\n }\n : undefined\n }\n controls={modes ?? <ComposerModeControls planMode={planMode} />}\n trailing={\n agent ? <ComposerAgentControls menuPlacement=\"down\" {...agent} /> : undefined\n }\n />\n {footer ? <div className=\"mt-7\">{footer}</div> : null}\n </div>\n </div>\n )\n}\n","import { ListChecks } from 'lucide-react'\nimport { cn } from '@tangle-network/sandbox-ui/utils'\nimport type { ComposerPlanModeSelection } from './types'\n\nexport interface ComposerModeControlsProps {\n /**\n * Plan-approval mode. Products pass it only when the selected backend can\n * propose a plan and wait for approval; omitted means nothing renders.\n */\n planMode?: ComposerPlanModeSelection\n}\n\n/**\n * The shared plan-mode toggle for the left side of an agent composer.\n * Plan mode is a behavioral switch, not part of the profile/backend/model/\n * thinking identity controls on the right.\n */\nexport function ComposerModeControls({ planMode }: ComposerModeControlsProps) {\n if (!planMode) return null\n\n return (\n <button\n type=\"button\"\n aria-pressed={planMode.enabled}\n disabled={planMode.saving}\n onClick={() => planMode.setEnabled(!planMode.enabled)}\n title=\"Plan mode: the agent proposes a plan you approve before it executes\"\n className={cn(\n // Inset ring: this chip sits in a composer row that clips its overflow,\n // so an outward ring loses three of its four sides. Only the offset is\n // overridden — width and colour stay with the tokens.\n 'inline-flex h-7 items-center gap-1 rounded-full border px-2.5 text-xs transition-colors focus-visible:[outline-offset:-2px]',\n planMode.enabled\n ? 'border-primary/50 bg-primary/10 text-primary'\n : 'border-border bg-transparent text-muted-foreground hover:text-foreground',\n planMode.saving && 'opacity-60',\n )}\n >\n <ListChecks className=\"h-3.5 w-3.5\" />\n Plan\n </button>\n )\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs"]}
@@ -109,7 +109,7 @@ import {
109
109
  flattenHistory,
110
110
  readSandboxBinaryBytes,
111
111
  statSandboxFileSize
112
- } from "../chunk-OTLEYHOG.js";
112
+ } from "../chunk-RRH23CGS.js";
113
113
  import "../chunk-TH7L265V.js";
114
114
  import "../chunk-LWSJK546.js";
115
115
  import "../chunk-P5PIAQVS.js";
@@ -242,9 +242,17 @@ function ModelPicker({ value, onChange, models, loading, renderProviderBadge, re
242
242
  const [query, setQuery] = useState2("");
243
243
  const { containerRef, triggerProps } = usePopover(open, setOpen);
244
244
  const inputRef = useRef2(null);
245
+ const popoverRef = useRef2(null);
245
246
  useEffect2(() => {
246
247
  if (open) inputRef.current?.focus();
247
248
  }, [open]);
249
+ useEffect2(() => {
250
+ if (!open) return;
251
+ const el = popoverRef.current;
252
+ if (!el) return;
253
+ const overflowRight = el.getBoundingClientRect().right - (window.innerWidth - 16);
254
+ el.style.transform = overflowRight > 0 ? `translateX(-${Math.ceil(overflowRight)}px)` : "";
255
+ }, [open]);
248
256
  const selected = models.find((m) => m.id === value);
249
257
  const filtered = useMemo(() => {
250
258
  const q = query.trim().toLowerCase();
@@ -286,7 +294,7 @@ function ModelPicker({ value, onChange, models, loading, renderProviderBadge, re
286
294
  ]
287
295
  }
288
296
  ),
289
- open && /* @__PURE__ */ jsxs2("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-[420px] max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl border border-border bg-card shadow-lg", children: [
297
+ open && /* @__PURE__ */ jsxs2("div", { ref: popoverRef, className: "absolute bottom-full left-0 z-50 mb-2 w-[420px] max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl border border-border bg-card shadow-lg", children: [
290
298
  /* @__PURE__ */ jsx2("div", { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2", children: [
291
299
  /* @__PURE__ */ jsx2(SearchGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
292
300
  /* @__PURE__ */ jsx2(
@@ -1630,6 +1638,7 @@ function ChatComposer({
1630
1638
  dropTitle = "Drop files to add context",
1631
1639
  dropDescription = "They attach to your next message.",
1632
1640
  focusShortcut = true,
1641
+ floating = false,
1633
1642
  sendLabel = "Send",
1634
1643
  className
1635
1644
  }) {
@@ -1909,7 +1918,7 @@ function ChatComposer({
1909
1918
  "div",
1910
1919
  {
1911
1920
  "data-testid": "composer-card",
1912
- className: "flex flex-col gap-1.5 rounded-2xl border border-border bg-card px-3 py-2.5 transition focus-within:border-primary/40 focus-within:ring-2 focus-within:ring-primary/15",
1921
+ className: `flex flex-col gap-1.5 rounded-2xl border border-border bg-card px-3 py-2.5 transition focus-within:border-primary/40 focus-within:ring-2 focus-within:ring-primary/15 ${floating ? "shadow-[0_1px_2px_hsl(var(--foreground)/0.05),0_12px_28px_hsl(var(--foreground)/0.07)] dark:shadow-[0_1px_2px_hsl(var(--foreground)/0.14),0_12px_28px_hsl(var(--foreground)/0.22)]" : ""}`,
1913
1922
  children: [
1914
1923
  /* @__PURE__ */ jsx9(
1915
1924
  "textarea",
@@ -1966,7 +1975,14 @@ function ChatComposer({
1966
1975
  }
1967
1976
  )
1968
1977
  ] }),
1969
- /* @__PURE__ */ jsx9("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-1.5", children: showInline && controls }),
1978
+ /* @__PURE__ */ jsx9(
1979
+ "div",
1980
+ {
1981
+ "data-testid": "composer-controls",
1982
+ className: "flex min-w-0 flex-1 flex-wrap items-center gap-1.5",
1983
+ children: showInline && controls
1984
+ }
1985
+ ),
1970
1986
  isStreaming ? /* @__PURE__ */ jsxs7(
1971
1987
  "button",
1972
1988
  {
@@ -6028,6 +6044,49 @@ function SegmentedBody({
6028
6044
  streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */ jsx16(StreamingCaret, {})
6029
6045
  ] });
6030
6046
  }
6047
+ var QUIET_META_LANE_CLASS = "mt-1 flex h-[18px] items-center gap-2 text-[11px] tracking-wide text-muted-foreground opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-within:opacity-100 motion-reduce:transition-none [@media(hover:none)]:opacity-100";
6048
+ function copyTextOf(msg) {
6049
+ const textRuns = msg.segments?.filter((s) => s.kind === "text") ?? [];
6050
+ if (textRuns.length > 0) return textRuns.map((s) => s.content).join("\n\n");
6051
+ return msg.content;
6052
+ }
6053
+ function CopyMessageButton({ text }) {
6054
+ const [copied, setCopied] = useState16(false);
6055
+ const timerRef = useRef11(null);
6056
+ useEffect12(
6057
+ () => () => {
6058
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
6059
+ },
6060
+ []
6061
+ );
6062
+ const copy = () => {
6063
+ const clipboard = navigator.clipboard;
6064
+ if (!clipboard) return;
6065
+ void clipboard.writeText(text).then(
6066
+ () => {
6067
+ setCopied(true);
6068
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
6069
+ timerRef.current = setTimeout(() => setCopied(false), 1200);
6070
+ },
6071
+ () => {
6072
+ }
6073
+ );
6074
+ };
6075
+ return /* @__PURE__ */ jsx16(
6076
+ "button",
6077
+ {
6078
+ type: "button",
6079
+ onClick: copy,
6080
+ "aria-label": "Copy message",
6081
+ title: "Copy message",
6082
+ className: "rounded p-0.5 text-muted-foreground transition hover:bg-accent/40 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
6083
+ children: copied ? /* @__PURE__ */ jsx16("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx16("polyline", { points: "20 6 9 17 4 12" }) }) : /* @__PURE__ */ jsxs14("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
6084
+ /* @__PURE__ */ jsx16("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
6085
+ /* @__PURE__ */ jsx16("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
6086
+ ] })
6087
+ }
6088
+ );
6089
+ }
6031
6090
  function AssistantMessageImpl({
6032
6091
  msg,
6033
6092
  streaming,
@@ -6041,7 +6100,8 @@ function AssistantMessageImpl({
6041
6100
  durableCards,
6042
6101
  resolveAttachmentUrl,
6043
6102
  workProductCards,
6044
- messageClassName
6103
+ messageClassName,
6104
+ chrome
6045
6105
  }) {
6046
6106
  const content = useSmoothText(msg.content, streaming);
6047
6107
  const reasoning = useSmoothText(msg.reasoning ?? "", streaming);
@@ -6064,8 +6124,9 @@ function AssistantMessageImpl({
6064
6124
  const thinkingSeconds = useThinkingSeconds(
6065
6125
  streaming && !!reasoning && !hasAnswerText
6066
6126
  );
6067
- return /* @__PURE__ */ jsxs14("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
6068
- /* @__PURE__ */ jsxs14("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground", children: [
6127
+ const quiet = chrome === "quiet";
6128
+ return /* @__PURE__ */ jsxs14("div", { className: `mx-auto w-full max-w-3xl px-6 ${quiet ? "group pb-1 pt-3" : "py-3"}`, children: [
6129
+ !quiet && /* @__PURE__ */ jsxs14("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground", children: [
6069
6130
  /* @__PURE__ */ jsx16("span", { className: "font-semibold uppercase", children: agentLabel }),
6070
6131
  msg.modelUsed && /* @__PURE__ */ jsx16("span", { className: "font-mono normal-case", children: msg.modelUsed }),
6071
6132
  formatTokensPerSecond(msg) && /* @__PURE__ */ jsx16("span", { children: formatTokensPerSecond(msg) }),
@@ -6133,7 +6194,13 @@ function AssistantMessageImpl({
6133
6194
  resolveFileUrl: resolveAttachmentUrl,
6134
6195
  justify: "start"
6135
6196
  }
6136
- ) })
6197
+ ) }),
6198
+ quiet && /* @__PURE__ */ jsxs14("div", { "data-testid": "message-meta-lane", className: QUIET_META_LANE_CLASS, children: [
6199
+ /* @__PURE__ */ jsx16(CopyMessageButton, { text: copyTextOf(msg) }),
6200
+ msg.modelUsed && /* @__PURE__ */ jsx16("span", { className: "font-mono", children: msg.modelUsed }),
6201
+ formatTokensPerSecond(msg) && /* @__PURE__ */ jsx16("span", { children: formatTokensPerSecond(msg) }),
6202
+ formatModelCost(msg, models) && /* @__PURE__ */ jsx16("span", { children: formatModelCost(msg, models) })
6203
+ ] })
6137
6204
  ] });
6138
6205
  }
6139
6206
  var AssistantMessage = memo(AssistantMessageImpl);
@@ -6147,10 +6214,10 @@ function useThinkingSeconds(active) {
6147
6214
  }, [active]);
6148
6215
  return seconds;
6149
6216
  }
6150
- function ThinkingRow({ agentLabel }) {
6217
+ function ThinkingRow({ agentLabel, chrome = "labeled" }) {
6151
6218
  const seconds = useThinkingSeconds(true);
6152
6219
  return /* @__PURE__ */ jsxs14("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
6153
- /* @__PURE__ */ jsx16("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: agentLabel }),
6220
+ chrome !== "quiet" && /* @__PURE__ */ jsx16("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: agentLabel }),
6154
6221
  /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
6155
6222
  /* @__PURE__ */ jsx16("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx16("path", { d: "M21 12a9 9 0 1 1-6.219-8.56", strokeLinecap: "round" }) }),
6156
6223
  "Thinking",
@@ -6179,6 +6246,7 @@ function StreamErrorRow({ message, onRetry }) {
6179
6246
  function ChatMessages({
6180
6247
  messages,
6181
6248
  messageSize = "default",
6249
+ chrome = "labeled",
6182
6250
  models = [],
6183
6251
  renderMarkdown,
6184
6252
  renderExtras,
@@ -6203,6 +6271,7 @@ function ChatMessages({
6203
6271
  [renderMarkdown]
6204
6272
  );
6205
6273
  const lastIsUser = messages[messages.length - 1]?.role === "user";
6274
+ const quiet = chrome === "quiet";
6206
6275
  if (messages.length === 0 && !loading && !error) {
6207
6276
  const empty = renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx16(ChatEmptyState, { ...emptyState });
6208
6277
  return /* @__PURE__ */ jsxs14(Fragment8, { children: [
@@ -6213,24 +6282,27 @@ function ChatMessages({
6213
6282
  return /* @__PURE__ */ jsxs14(Fragment8, { children: [
6214
6283
  header,
6215
6284
  messages.map(
6216
- (msg) => msg.role === "user" ? /* @__PURE__ */ jsx16("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs14("div", { className: "ml-auto w-fit max-w-[85%]", children: [
6217
- /* @__PURE__ */ jsx16("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: userLabel }),
6218
- /* @__PURE__ */ jsx16(
6219
- "div",
6220
- {
6221
- className: `rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 ${messageClassName}`,
6222
- children: /* @__PURE__ */ jsx16("p", { className: "whitespace-pre-wrap", children: msg.content })
6223
- }
6224
- ),
6225
- resolveAttachmentUrl && attachmentPartsFromMessageParts(msg.parts).length > 0 && /* @__PURE__ */ jsx16("div", { className: "mt-1.5", children: /* @__PURE__ */ jsx16(
6226
- MessageAttachments,
6227
- {
6228
- parts: attachmentPartsFromMessageParts(msg.parts),
6229
- resolveFileUrl: resolveAttachmentUrl,
6230
- justify: "end"
6231
- }
6232
- ) })
6233
- ] }) }, msg.id) : /* @__PURE__ */ jsx16(
6285
+ (msg) => msg.role === "user" ? /* @__PURE__ */ jsxs14("div", { className: `mx-auto w-full max-w-3xl px-6 ${quiet ? "group pb-1 pt-3" : "py-3"}`, children: [
6286
+ /* @__PURE__ */ jsxs14("div", { className: `ml-auto w-fit ${quiet ? "max-w-[72%]" : "max-w-[85%]"}`, children: [
6287
+ !quiet && /* @__PURE__ */ jsx16("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: userLabel }),
6288
+ /* @__PURE__ */ jsx16(
6289
+ "div",
6290
+ {
6291
+ className: quiet ? `rounded-2xl bg-[color-mix(in_srgb,hsl(var(--secondary))_65%,hsl(var(--background)))] px-4 py-2.5 ${messageClassName}` : `rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 ${messageClassName}`,
6292
+ children: /* @__PURE__ */ jsx16("p", { className: "whitespace-pre-wrap", children: msg.content })
6293
+ }
6294
+ ),
6295
+ resolveAttachmentUrl && attachmentPartsFromMessageParts(msg.parts).length > 0 && /* @__PURE__ */ jsx16("div", { className: "mt-1.5", children: /* @__PURE__ */ jsx16(
6296
+ MessageAttachments,
6297
+ {
6298
+ parts: attachmentPartsFromMessageParts(msg.parts),
6299
+ resolveFileUrl: resolveAttachmentUrl,
6300
+ justify: "end"
6301
+ }
6302
+ ) })
6303
+ ] }),
6304
+ quiet && /* @__PURE__ */ jsx16("div", { "data-testid": "message-meta-lane", className: `${QUIET_META_LANE_CLASS} justify-end`, children: /* @__PURE__ */ jsx16(CopyMessageButton, { text: msg.content }) })
6305
+ ] }, msg.id) : /* @__PURE__ */ jsx16(
6234
6306
  AssistantMessage,
6235
6307
  {
6236
6308
  msg,
@@ -6245,12 +6317,13 @@ function ChatMessages({
6245
6317
  durableCards,
6246
6318
  resolveAttachmentUrl,
6247
6319
  workProductCards,
6248
- messageClassName
6320
+ messageClassName,
6321
+ chrome
6249
6322
  },
6250
6323
  msg.id
6251
6324
  )
6252
6325
  ),
6253
- loading && lastIsUser && /* @__PURE__ */ jsx16(ThinkingRow, { agentLabel }),
6326
+ loading && lastIsUser && /* @__PURE__ */ jsx16(ThinkingRow, { agentLabel, chrome }),
6254
6327
  error && !loading && /* @__PURE__ */ jsx16(StreamErrorRow, { message: error, onRetry })
6255
6328
  ] });
6256
6329
  }
@@ -6375,4 +6448,4 @@ export {
6375
6448
  useThinkingSeconds,
6376
6449
  ChatMessages
6377
6450
  };
6378
- //# sourceMappingURL=chunk-WC43OQMR.js.map
6451
+ //# sourceMappingURL=chunk-3I27SDU3.js.map