@ai-matrx/chat 0.5.4 → 0.5.6

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 (46) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/agents/components/chat/ChatIncognitoHeaderButton.js +1 -1
  3. package/dist/agents/components/chat/SandboxPanel.js +1 -1
  4. package/dist/agents/components/context-items/context-item-block-types.d.ts +1 -1
  5. package/dist/agents/components/context-items/context-item-block-types.js +2 -1
  6. package/dist/agents/components/context-items/registry.js +9 -0
  7. package/dist/agents/components/conversation-history/ConversationHistorySidebar.js +1 -1
  8. package/dist/agents/components/inputs/resources/SmartAgentResourceChips.js +4 -1
  9. package/dist/agents/components/inputs/resources/useAttachRenderedArtifact.d.ts +20 -0
  10. package/dist/agents/components/inputs/resources/useAttachRenderedArtifact.js +107 -0
  11. package/dist/agents/components/inputs/smart-input/composer/ComposerAgentPill.js +135 -204
  12. package/dist/agents/components/inputs/smart-input/composer/already-answering.d.ts +1 -1
  13. package/dist/agents/components/inputs/smart-input/composer/already-answering.js +1 -1
  14. package/dist/agents/components/inputs/smart-input/composer/useComposerAgent.d.ts +1 -1
  15. package/dist/agents/components/inputs/smart-input/composer/useComposerAgent.js +1 -1
  16. package/dist/agents/components/messages-display/user/UserActionBar.js +1 -1
  17. package/dist/agents/components/run/AgentRunHeader.js +1 -1
  18. package/dist/agents/components/run/run-sidebar/SidebarHeader.js +1 -1
  19. package/dist/agents/components/run-controls/RunModelPicker.d.ts +5 -1
  20. package/dist/agents/components/run-controls/RunModelPicker.js +23 -1
  21. package/dist/agents/components/shared/AgentConversationColumn.js +3 -1
  22. package/dist/agents/components/shared/AgentHeader.js +1 -1
  23. package/dist/agents/components/shared/AgentHeaderMobile.js +1 -1
  24. package/dist/agents/components/shared/AgentNewRunButton.js +1 -1
  25. package/dist/agents/components/shared/assistant-message-layout.d.ts +21 -0
  26. package/dist/agents/components/shared/assistant-message-layout.js +7 -1
  27. package/dist/agents/components/working-document/WorkingDocumentVersionHistory.js +1 -1
  28. package/dist/agents/redux/execution-system/instance-resources/instance-resources.selectors.js +14 -0
  29. package/dist/agents/types/instance.types.d.ts +18 -3
  30. package/dist/agents/utils/renderedArtifactContext.d.ts +41 -0
  31. package/dist/agents/utils/renderedArtifactContext.js +41 -0
  32. package/dist/conversation/hooks/useDomCapturePrint.js +4 -0
  33. package/dist/conversation/utils/frame-print.d.ts +11 -0
  34. package/dist/conversation/utils/frame-print.js +46 -0
  35. package/dist/conversation/utils/markdown-print.d.ts +9 -1
  36. package/dist/conversation/utils/markdown-print.js +55 -26
  37. package/dist/cx-chat/components/ChatMobileHeaderBar.js +1 -1
  38. package/dist/cx-chat/components/ChatPanelBackButton.js +1 -1
  39. package/dist/cx-chat/components/ChatSidebarClient.js +1 -1
  40. package/dist/cx-chat/components/user-input/ConversationInput.js +2 -2
  41. package/dist/cx-chat/components/user-input/InputActionButtons.js +2 -2
  42. package/dist/host/frame-print.d.ts +17 -0
  43. package/dist/host/frame-print.js +13 -0
  44. package/dist/surfaces/components/chrome/SurfaceAgentsHeaderButton.js +1 -1
  45. package/dist/voice-agent/components/VoiceAgentSurface.js +2 -2
  46. package/package.json +6 -7
@@ -1,36 +1,30 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { useState } from "react";
4
- import { AppWindow, ChevronDown, Gauge, Layers, Star, Webhook } from "lucide-react";
5
- import { Button, Tile } from "@ai-matrx/design-system/controls";
3
+ import { useContext, useState } from "react";
4
+ import { AppWindow, ChevronDown, Cpu, Gauge, Layers, Star, Webhook } from "lucide-react";
5
+ import { Button } from "@ai-matrx/design-system/controls";
6
6
  import { MANDATE_KEYS } from "@ai-matrx/agents/mandates";
7
7
  import { Popover, PopoverContent, PopoverTrigger } from "@ai-matrx/design-system";
8
- import { AgentListDropdown } from "@ai-matrx/agents/catalog/react";
9
- import { useAppDispatch, useAppSelector } from "../../../../../store/hooks.js";
8
+ import { AgentListDropdown, AgentListPanel } from "@ai-matrx/agents/catalog/react";
9
+ import { useIsMobile } from "@ai-matrx/kit/media-query";
10
+ import { useAppSelector } from "../../../../../store/hooks.js";
10
11
  import { cn } from "@ai-matrx/design-system";
11
- import { toast } from "../../../../../host/notify.js";
12
12
  import { announceAlreadyAnswering } from "./already-answering.js";
13
13
  import { QuickRunModelSelect } from "../../../run-controls/RunModelPicker.js";
14
- import { ModelListDropdown } from "../../../../../host/ui-slots.js";
15
- import { seedOverrides } from "../../../../redux/execution-system/instance-model-overrides/instance-model-overrides.slice.js";
16
- import { selectInstanceOverrideState } from "../../../../redux/execution-system/instance-model-overrides/instance-model-overrides.selectors.js";
17
- import {
18
- effectiveOfferingPin,
19
- setOfferingPin
20
- } from "../../../../redux/execution-system/instance-model-overrides/offering-pin.js";
21
- import { useSessionKnob } from "../../../../../host/prefs-react.js";
22
- import { knobRefusalSentence, setKnobOverride } from "../../../../../host/prefs.js";
23
- import { CHAT_DEFAULT_MODEL_KNOB } from "../../../../../ui/chat-default-model-knob.js";
24
- import { selectModelLabelWithClass } from "@ai-matrx/agents/models";
25
- import { useModelRecords } from "../../../../identity/model-catalog.js";
14
+ import { selectIsManualExecutionMode } from "../../../../redux/execution-system/selectors/aggregate.selectors.js";
26
15
  import {
16
+ COMPOSER_MENU_NO_ENTRANCE,
17
+ ComposerMenuCloseAllContext,
27
18
  ComposerMenuDivider,
28
- ComposerMenuHelp,
29
19
  ComposerMenuLabel,
20
+ ComposerMenuLevel,
21
+ ComposerMenuPresentationContext,
30
22
  ComposerMenuRow,
31
23
  ComposerSubmenu,
32
- ComposerFoldedSection
24
+ ComposerFoldedSection,
25
+ ignoreOwnWrapper
33
26
  } from "./ComposerMenu.js";
27
+ import { ComposerMenuSheet } from "./ComposerMenuSheet.js";
34
28
  import { ComposerOutputPanel } from "./ComposerOutput.js";
35
29
  import { ComposerEffortRows, useComposerEffort } from "./ComposerEffortPill.js";
36
30
  import { composerShows } from "./composer-mode-visibility.js";
@@ -38,21 +32,24 @@ import { composerPillClass } from "./composer-chip.js";
38
32
  import { useComposerAgent, useEffectiveModelId } from "./useComposerAgent.js";
39
33
  import { useComposerAgentFilter } from "./useComposerAgentFilter.js";
40
34
  import { ComposerAgentPeek } from "./ComposerAgentPeek.js";
41
- import { presentOrganizationRefusal } from "../../../../../host/org.js";
42
- import { ensureOrgId, isOrganizationSelectionCancelled } from "../../../../../host/org.js";
35
+ const AGENT_PICKER_PANEL = "w-[680px] h-[min(528px,80dvh)] p-0";
36
+ const MODEL_PICKER_PANEL = "w-[min(1060px,calc(100vw-24px))] h-[min(640px,80dvh)] p-0";
37
+ const CUSTOM_AGENT_LABEL = "Custom Agent";
43
38
  function pillLabel(info, mode) {
44
39
  if (!composerShows(mode, "agent.presets")) return info.agentName ?? "Agent";
45
- const name = info.isCustom ? "Custom" : info.preset?.name ?? info.agentName ?? "Agent";
40
+ const name = info.isCustom ? CUSTOM_AGENT_LABEL : info.preset?.name ?? info.agentName ?? "Agent";
46
41
  return info.effectiveModelLabel ? `${name} \xB7 ${info.effectiveModelLabel}` : name;
47
42
  }
48
43
  function ComposerAgentPill({ conversationId, mode, size, agentControl, menuSide, folded = false }) {
49
44
  const [open, setOpen] = useState(false);
45
+ const isMobile = useIsMobile();
50
46
  const effort = useComposerEffort(conversationId, useEffectiveModelId(conversationId));
51
47
  const info = useComposerAgent(conversationId);
52
48
  const label = pillLabel(info, mode);
53
49
  const onSelectAgent = agentControl?.onSelectAgent;
54
50
  const agentFilter = useComposerAgentFilter(conversationId);
55
- const showOverride = info.modelOverridden && !info.isCustom && composerShows(mode, "agent.presets");
51
+ const chatPresets = composerShows(mode, "agent.presets");
52
+ const showOverride = info.modelOverridden && chatPresets;
56
53
  const overrideTitle = info.agentModelLabel ? `Agent's own model: ${info.agentModelLabel}` : "Not the agent's own model";
57
54
  const pill = /* @__PURE__ */ jsx(
58
55
  Button,
@@ -62,55 +59,12 @@ function ComposerAgentPill({ conversationId, mode, size, agentControl, menuSide,
62
59
  "aria-label": `Agent: ${label}${showOverride ? " (model changed)" : ""}`,
63
60
  title: showOverride ? `${label} \u2014 ${overrideTitle}` : label,
64
61
  "data-model-override": showOverride ? "" : void 0,
62
+ onClick: isMobile ? () => setOpen(true) : void 0,
65
63
  className: "min-w-0 shrink",
66
64
  children: showOverride ? `${label} \xB7 changed` : label
67
65
  }
68
66
  );
69
- if (folded) {
70
- const chatPresets = composerShows(mode, "agent.presets");
71
- const showEffort = Boolean(effort) && composerShows(mode, "meta.effort");
72
- return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, modal: false, children: [
73
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: pill }),
74
- /* @__PURE__ */ jsxs(
75
- PopoverContent,
76
- {
77
- side: menuSide,
78
- align: "end",
79
- sideOffset: 8,
80
- className: "flex w-80 max-h-[var(--radix-popover-content-available-height)] flex-col overflow-y-auto p-1",
81
- children: [
82
- chatPresets ? /* @__PURE__ */ jsx(
83
- ChatPresetsPanel,
84
- {
85
- conversationId,
86
- info,
87
- agentControl,
88
- close: () => setOpen(false)
89
- }
90
- ) : onSelectAgent ? /* @__PURE__ */ jsx(
91
- AgentListDropdown,
92
- {
93
- onSelect: (agentId) => {
94
- setOpen(false);
95
- if (agentId !== info.agentId) onSelectAgent(agentId);
96
- else announceAlreadyAnswering(info);
97
- },
98
- activeAgentId: info.agentId,
99
- agentFilter,
100
- contentSide: "left",
101
- triggerSlot: /* @__PURE__ */ jsx(Tile, { variant: "quiet", icon: /* @__PURE__ */ jsx(Webhook, {}), title: info.agentName ?? "Agent", end: /* @__PURE__ */ jsx(ChevronDown, {}) })
102
- }
103
- ) : /* @__PURE__ */ jsx(ComposerMenuRow, { icon: Webhook, label: info.agentName ?? "Agent", checked: true }),
104
- /* @__PURE__ */ jsxs(ComposerFoldedSection, { children: [
105
- /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: AppWindow, label: "Output" }, panelClassName: "w-80", children: /* @__PURE__ */ jsx(ComposerOutputPanel, { conversationId }) }),
106
- showEffort && effort ? /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Gauge, label: "Effort", detail: effort.word }, panelClassName: "w-56 p-1", children: (close) => /* @__PURE__ */ jsx(ComposerEffortRows, { effort, onChosen: close }) }) : null
107
- ] })
108
- ]
109
- }
110
- )
111
- ] });
112
- }
113
- if (!composerShows(mode, "agent.presets")) {
67
+ if (!chatPresets && !folded) {
114
68
  if (!onSelectAgent) {
115
69
  return /* @__PURE__ */ jsx("span", { className: cn(composerPillClass(size, false), "hover:bg-transparent"), title: "This page always answers with this agent", children: /* @__PURE__ */ jsx("span", { className: "truncate font-medium text-foreground", children: label }) });
116
70
  }
@@ -128,6 +82,39 @@ function ComposerAgentPill({ conversationId, mode, size, agentControl, menuSide,
128
82
  }
129
83
  );
130
84
  }
85
+ const close = () => setOpen(false);
86
+ const choose = (agentId) => {
87
+ close();
88
+ if (!onSelectAgent) return;
89
+ if (agentId !== info.agentId) onSelectAgent(agentId);
90
+ else announceAlreadyAnswering(info);
91
+ };
92
+ const showEffort = folded && Boolean(effort) && composerShows(mode, "meta.effort");
93
+ const body = /* @__PURE__ */ jsx(ComposerMenuCloseAllContext.Provider, { value: close, children: /* @__PURE__ */ jsxs(ComposerMenuLevel, { children: [
94
+ chatPresets ? /* @__PURE__ */ jsx(ChatAgentMenu, { conversationId, info, agentControl, choose, close }) : onSelectAgent ? (
95
+ // Work+, narrow: the agent picker is one row of the folded menu.
96
+ /* @__PURE__ */ jsx(
97
+ AnyAgentRow,
98
+ {
99
+ icon: Webhook,
100
+ label: `Agent: ${info.agentName ?? "Agent"}`,
101
+ info,
102
+ agentFilter,
103
+ choose
104
+ }
105
+ )
106
+ ) : /* @__PURE__ */ jsx(ComposerMenuRow, { icon: Webhook, label: `Agent: ${info.agentName ?? "Agent"}` }),
107
+ folded ? /* @__PURE__ */ jsxs(ComposerFoldedSection, { children: [
108
+ /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: AppWindow, label: "Output" }, panelClassName: "w-80", children: /* @__PURE__ */ jsx(ComposerOutputPanel, { conversationId }) }),
109
+ showEffort && effort ? /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Gauge, label: "Effort", detail: effort.word }, panelClassName: "w-56 p-1", children: (closePanel) => /* @__PURE__ */ jsx(ComposerEffortRows, { effort, onChosen: closePanel }) }) : null
110
+ ] }) : null
111
+ ] }) });
112
+ if (isMobile) {
113
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
114
+ pill,
115
+ /* @__PURE__ */ jsx(ComposerMenuSheet, { open, onOpenChange: setOpen, accessibleName: "Agent and model", children: body })
116
+ ] });
117
+ }
131
118
  return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, modal: false, children: [
132
119
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: pill }),
133
120
  /* @__PURE__ */ jsx(
@@ -136,163 +123,107 @@ function ComposerAgentPill({ conversationId, mode, size, agentControl, menuSide,
136
123
  side: menuSide,
137
124
  align: "end",
138
125
  sideOffset: 8,
139
- className: "flex w-80 max-h-[var(--radix-popover-content-available-height)] flex-col overflow-y-auto p-1",
140
- children: /* @__PURE__ */ jsx(
141
- ChatPresetsPanel,
142
- {
143
- conversationId,
144
- info,
145
- agentControl,
146
- close: () => setOpen(false)
147
- }
148
- )
126
+ onPointerDownOutside: ignoreOwnWrapper,
127
+ className: cn(
128
+ "flex w-80 max-h-[var(--radix-popover-content-available-height)] flex-col overflow-y-auto p-1",
129
+ COMPOSER_MENU_NO_ENTRANCE
130
+ ),
131
+ children: body
149
132
  }
150
133
  )
151
134
  ] });
152
135
  }
153
- function ThisAgentRow({ info, close }) {
154
- if (!info.agentId) return null;
155
- return /* @__PURE__ */ jsx(
156
- ComposerSubmenu,
157
- {
158
- row: { icon: Webhook, label: info.agentName ?? "Agent", description: "View, open or duplicate" },
159
- panelClassName: "w-[340px] h-[420px] p-0",
160
- children: /* @__PURE__ */ jsx(ComposerAgentPeek, { agentId: info.agentId, onDone: close })
161
- }
162
- );
163
- }
164
- function ChatPresetsPanel({
136
+ function ChatAgentMenu({
165
137
  conversationId,
166
138
  info,
167
139
  agentControl,
140
+ choose,
168
141
  close
169
142
  }) {
170
- const dispatch = useAppDispatch();
171
- const organizationId = useAppSelector((s) => s.appContext?.organization_id ?? null);
172
- const userId = useAppSelector((s) => s.userAuth?.id ?? null);
173
- const personalModel = useSessionKnob(CHAT_DEFAULT_MODEL_KNOB);
174
- const personalModelId = typeof personalModel === "string" && personalModel.trim() ? personalModel : null;
175
- const overrideState = useAppSelector(selectInstanceOverrideState(conversationId));
176
- const pinnedOfferingId = effectiveOfferingPin(overrideState);
177
- const personalModelLabel = useModelRecords((s) => selectModelLabelWithClass(s, personalModelId, pinnedOfferingId)) ?? null;
178
- const onSelectAgent = agentControl?.onSelectAgent;
143
+ const isManualMode = useAppSelector(selectIsManualExecutionMode(conversationId));
144
+ const inSheet = useContext(ComposerMenuPresentationContext) === "sheet";
179
145
  const agentFilter = useComposerAgentFilter(conversationId);
180
- if (!onSelectAgent) {
181
- return /* @__PURE__ */ jsxs(Fragment, { children: [
182
- /* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Agent" }),
183
- /* @__PURE__ */ jsx(ThisAgentRow, { info, close }),
184
- /* @__PURE__ */ jsx(ComposerMenuHelp, { children: "This page always answers with this agent." }),
185
- /* @__PURE__ */ jsx(ComposerMenuDivider, {}),
186
- /* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Model for this chat" }),
187
- /* @__PURE__ */ jsx("div", { className: "px-1.5 pb-1", children: /* @__PURE__ */ jsx(QuickRunModelSelect, { conversationId, className: "h-8 w-full" }) })
188
- ] });
189
- }
190
- const choose = (agentId) => {
191
- close();
192
- if (agentId !== info.agentId) onSelectAgent(agentId);
193
- else announceAlreadyAnswering(info);
194
- };
146
+ const onSelectAgent = agentControl?.onSelectAgent;
147
+ const agentLabel = info.isCustom ? CUSTOM_AGENT_LABEL : info.agentName ?? "Agent";
195
148
  const chooseCustom = () => {
196
- if (!info.customAgentId) return;
149
+ if (!info.customAgentId || !onSelectAgent) return;
197
150
  close();
198
- if (!info.isCustom) {
199
- onSelectAgent(info.customAgentId, { mandateKey: MANDATE_KEYS.chat__default_new_chat });
200
- }
201
- };
202
- const savePersonalDefault = async (key, value) => {
203
- if (!userId) {
204
- toast.error("Sign in first \u2014 your default model is saved to your account.");
205
- return false;
206
- }
207
- let targetOrganizationId;
208
- try {
209
- targetOrganizationId = organizationId ?? await ensureOrgId(null);
210
- } catch (error) {
211
- if (isOrganizationSelectionCancelled(error)) return false;
212
- if (presentOrganizationRefusal(error, { subject: "Your default model", act: "saved" })) return false;
213
- throw error;
214
- }
215
- const result = await setKnobOverride({
216
- feature: "agents.model_prefs",
217
- key,
218
- scopeKind: "user",
219
- scopeId: userId,
220
- organizationId: targetOrganizationId,
221
- value,
222
- note: "Picked under Custom in the chat composer"
223
- });
224
- if (!result.ok) {
225
- toast.error(`Your default chat model was not saved: ${knobRefusalSentence(result)}`);
226
- return false;
227
- }
228
- return true;
229
- };
230
- const setPersonalModel = async (modelId) => {
231
- if (!await savePersonalDefault("chat_default_model", modelId)) return;
232
- if (info.isCustom) {
233
- dispatch(seedOverrides({ conversationId, changes: { model: modelId } }));
234
- } else {
235
- chooseCustom();
236
- }
237
- };
238
- const setPersonalClass = (offeringId) => {
239
- dispatch(setOfferingPin({ conversationId, offeringId, seeded: true }));
240
- void savePersonalDefault("chat_default_offering", offeringId ?? "");
151
+ if (info.isCustom) announceAlreadyAnswering(info);
152
+ else onSelectAgent(info.customAgentId, { mandateKey: MANDATE_KEYS.chat__cx_default });
241
153
  };
242
154
  return /* @__PURE__ */ jsxs(Fragment, { children: [
243
- /* @__PURE__ */ jsx(ComposerMenuLabel, { children: "This chat's agent" }),
244
- /* @__PURE__ */ jsx(ThisAgentRow, { info, close }),
245
- /* @__PURE__ */ jsx(ComposerMenuDivider, {}),
246
- /* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Chat agents" }),
247
- info.presets.map((preset) => /* @__PURE__ */ jsx(
248
- ComposerMenuRow,
249
- {
250
- icon: Star,
251
- label: preset.name,
252
- detail: preset.modelLabel ?? void 0,
253
- checked: preset.id === info.agentId,
254
- onClick: () => choose(preset.id)
255
- },
256
- preset.id
257
- )),
258
- /* @__PURE__ */ jsx(
155
+ info.agentId ? /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Webhook, label: `Agent: ${agentLabel}` }, panelClassName: "w-[340px] h-[420px] p-0", children: /* @__PURE__ */ jsx(ComposerAgentPeek, { agentId: info.agentId, onDone: close }) }) : null,
156
+ isManualMode ? /* @__PURE__ */ jsx(
259
157
  ComposerMenuRow,
260
158
  {
261
- label: "Custom",
262
- description: "Your own default chat, on the model you pick",
263
- checked: info.isCustom,
264
- disabled: !info.customAgentId,
265
- onClick: chooseCustom
159
+ icon: Cpu,
160
+ label: `Model: ${info.effectiveModelLabel ?? "Agent's model"}`,
161
+ disabled: true,
162
+ title: "Manual mode runs the agent's own model"
266
163
  }
267
- ),
268
- /* @__PURE__ */ jsx(
269
- AgentListDropdown,
164
+ ) : /* @__PURE__ */ jsx(
165
+ ComposerSubmenu,
270
166
  {
271
- onSelect: (agentId) => choose(agentId),
272
- activeAgentId: info.agentId,
273
- agentFilter,
274
- contentSide: "left",
275
- triggerSlot: /* @__PURE__ */ jsx(Tile, { variant: "quiet", icon: /* @__PURE__ */ jsx(Layers, {}), title: "All agents", end: /* @__PURE__ */ jsx(ChevronDown, {}) })
167
+ row: { icon: Cpu, label: `Model: ${info.effectiveModelLabel ?? "Choose a model"}` },
168
+ panelClassName: MODEL_PICKER_PANEL,
169
+ children: (closePanel) => /* @__PURE__ */ jsx(
170
+ QuickRunModelSelect,
171
+ {
172
+ conversationId,
173
+ presentation: "inline",
174
+ onChosen: () => {
175
+ closePanel();
176
+ close();
177
+ }
178
+ }
179
+ )
276
180
  }
277
181
  ),
278
- /* @__PURE__ */ jsx(ComposerMenuDivider, {}),
279
- /* @__PURE__ */ jsx(ComposerMenuLabel, { children: info.isCustom ? "Model \u2014 your default for Custom" : "Model for this chat" }),
280
- /* @__PURE__ */ jsx("div", { className: "px-1.5 pb-1", children: info.isCustom ? /* @__PURE__ */ jsx(
281
- ModelListDropdown,
282
- {
283
- value: personalModelId ?? info.effectiveModelId,
284
- onValueChange: (modelId) => void setPersonalModel(modelId),
285
- pinnedOfferingId,
286
- onOfferingPinChange: setPersonalClass,
287
- inputModalities: [],
288
- outputModalities: ["text"],
289
- placeholder: personalModelLabel ?? info.effectiveModelLabel ?? "Pick a model",
290
- "aria-label": "Model for Custom",
291
- className: "w-full"
292
- }
293
- ) : /* @__PURE__ */ jsx(QuickRunModelSelect, { conversationId, className: "h-8 w-full" }) })
182
+ onSelectAgent ? /* @__PURE__ */ jsxs(Fragment, { children: [
183
+ inSheet ? null : /* @__PURE__ */ jsx(ComposerMenuDivider, {}),
184
+ /* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Choose your chat agent" }),
185
+ info.presets.map((preset) => /* @__PURE__ */ jsx(
186
+ ComposerMenuRow,
187
+ {
188
+ icon: Star,
189
+ label: preset.name,
190
+ checked: preset.id === info.agentId,
191
+ onClick: () => choose(preset.id)
192
+ },
193
+ preset.id
194
+ )),
195
+ /* @__PURE__ */ jsx(
196
+ ComposerMenuRow,
197
+ {
198
+ icon: Star,
199
+ label: CUSTOM_AGENT_LABEL,
200
+ checked: info.isCustom,
201
+ disabled: !info.customAgentId,
202
+ onClick: chooseCustom
203
+ }
204
+ ),
205
+ /* @__PURE__ */ jsx(AnyAgentRow, { icon: Layers, label: "Choose any agent", info, agentFilter, choose })
206
+ ] }) : null
294
207
  ] });
295
208
  }
209
+ function AnyAgentRow({
210
+ icon,
211
+ label,
212
+ info,
213
+ agentFilter,
214
+ choose
215
+ }) {
216
+ return /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon, label }, panelClassName: AGENT_PICKER_PANEL, children: /* @__PURE__ */ jsx(
217
+ AgentListPanel,
218
+ {
219
+ consumerId: "composer-agent-pill",
220
+ fill: true,
221
+ onSelect: choose,
222
+ activeAgentId: info.agentId,
223
+ agentFilter
224
+ }
225
+ ) });
226
+ }
296
227
  export {
297
228
  ComposerAgentPill
298
229
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Choosing the agent that is already answering is never silent: the composer
3
- * pill says "Custom" when that agent holds the default-chat job, while the
3
+ * pill says "Custom Agent" for the Matrx Chat agent, while the
4
4
  * agent list names it by its own name — so the press answers who is in the seat.
5
5
  */
6
6
  export declare function announceAlreadyAnswering(info: {
@@ -2,7 +2,7 @@ import { toast } from "../../../../../host/notify.js";
2
2
  function announceAlreadyAnswering(info) {
3
3
  const name = info.agentName ?? "This agent";
4
4
  toast.info(
5
- info.isCustom ? `${name} is your Custom chat, and it is already answering this conversation.` : `${name} is already answering this conversation.`
5
+ info.isCustom ? `Custom Agent (${name}) is already answering this conversation.` : `${name} is already answering this conversation.`
6
6
  );
7
7
  }
8
8
  export {
@@ -15,7 +15,7 @@ export interface ComposerAgentInfo {
15
15
  modelOverridden: boolean;
16
16
  /** The agent's own model label — what runs when no override is in force. */
17
17
  agentModelLabel: string | null;
18
- /** The agent currently holding `chat.default_new_chat` — shown as "Custom". */
18
+ /** The Matrx Chat agent (`chat.cx_default`) — shown as "Custom Agent". */
19
19
  customAgentId: string | null;
20
20
  isCustom: boolean;
21
21
  preset: ComposerPreset | null;
@@ -52,7 +52,7 @@ function useComposerAgent(conversationId) {
52
52
  const effectiveModelLabel = useModelRecords((state) => selectModelLabelWithClass(state, effectiveModelId, effectivePin)) ?? null;
53
53
  const agentOwnModelId = useAgentOwnModelId(conversationId);
54
54
  const agentModelLabel = useModelRecords((state) => selectModelLabelWithClass(state, agentOwnModelId, agentOwnPin)) ?? null;
55
- const { mandate: customMandate } = useMandate(MANDATE_KEYS.chat__default_new_chat, { optional: true });
55
+ const { mandate: customMandate } = useMandate(MANDATE_KEYS.chat__cx_default, { optional: true });
56
56
  const customAgentId = customMandate?.agentId ?? null;
57
57
  const rows = useAgentCatalogRows();
58
58
  const presetRows = rows.filter(isPresetRow);
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  TapTargetButtonForGroup,
17
17
  TapTargetButtonGroup
18
- } from "@ai-matrx/tap-target";
18
+ } from "@ai-matrx/design-system/tap-target";
19
19
  import { SpeakerButton } from "../../../../host/ui-slots.js";
20
20
  import { CopySplitButton } from "@ai-matrx/rich-content/copy/CopySplitButton";
21
21
  import { copyRichContent } from "@ai-matrx/rich-content/copy/copy-commands";
@@ -3,7 +3,7 @@ import { AgentModeController } from "../shared/AgentModeController.js";
3
3
  import { AgentSaveStatus, AgentOptionsMenu } from "../../../host/ui-slots.js";
4
4
  import { ReviewAnswersLink } from "../../../host/ui-slots.js";
5
5
  import { AgentSelectorIsland } from "../shared/AgentSelectorIsland.js";
6
- import { ChevronLeftTapButton } from "@ai-matrx/tap-target/buttons";
6
+ import { ChevronLeftTapButton } from "@ai-matrx/design-system/tap-target/buttons";
7
7
  import { AgentNewRunButton } from "../shared/AgentNewRunButton.js";
8
8
  import { AgentHeaderMobile } from "../shared/AgentHeaderMobile.js";
9
9
  function AgentRunHeader({
@@ -4,7 +4,7 @@ import { AgentSelectorIsland } from "../../shared/AgentSelectorIsland.js";
4
4
  import {
5
5
  PanelLeftTapButton,
6
6
  ChevronLeftTapButton
7
- } from "@ai-matrx/tap-target/buttons";
7
+ } from "@ai-matrx/design-system/tap-target/buttons";
8
8
  import { PowerTapButton } from "@ai-matrx/icons/tap-buttons";
9
9
  import { SearchGroup, SearchGroupTrigger } from "../../../../host/ui-slots.js";
10
10
  import { AgentNewRunButton } from "../../shared/AgentNewRunButton.js";
@@ -4,9 +4,13 @@
4
4
  * ModelListDropdown as `/agents/[id]/build`; only the write target differs.
5
5
  * agent default pinned first; reset icon appears only when overridden.
6
6
  */
7
- export declare function QuickRunModelSelect({ conversationId, className, }: {
7
+ export declare function QuickRunModelSelect({ conversationId, className, presentation, onChosen, }: {
8
8
  conversationId: string;
9
9
  className?: string;
10
+ /** `inline`: the picker body fills its parent (a menu's panel or page). */
11
+ presentation?: "picker" | "inline";
12
+ /** A model was picked (an inline host closes its panel). */
13
+ onChosen?: () => void;
10
14
  }): import("react").JSX.Element;
11
15
  export declare function RunModelPicker({ conversationId }: {
12
16
  conversationId: string;
@@ -50,7 +50,9 @@ function useModelOverride(conversationId) {
50
50
  }
51
51
  function QuickRunModelSelect({
52
52
  conversationId,
53
- className
53
+ className,
54
+ presentation = "picker",
55
+ onChosen
54
56
  }) {
55
57
  const isManualMode = useAppSelector(
56
58
  selectIsManualExecutionMode(conversationId)
@@ -69,6 +71,26 @@ function QuickRunModelSelect({
69
71
  handleReset
70
72
  } = useModelOverride(conversationId);
71
73
  const displayModelId = isManualMode ? agentModelId ?? effectiveModel ?? baseModel ?? null : effectiveModel ?? baseModel ?? null;
74
+ if (presentation === "inline") {
75
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full min-h-0 min-w-0 flex-col", className), children: [
76
+ isOverridden && !isManualMode ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 justify-end border-b border-border p-1", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(RotateCcw, {}), onClick: handleReset, children: "Agent's own model" }) }) : null,
77
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(
78
+ ModelListDropdown,
79
+ {
80
+ presentation: "inline",
81
+ value: displayModelId,
82
+ onValueChange: (modelId) => {
83
+ handleChange(modelId);
84
+ onChosen?.();
85
+ },
86
+ pinnedOfferingId,
87
+ onOfferingPinChange: handleOfferingPinChange,
88
+ inputModalities: [],
89
+ outputModalities: ["text"]
90
+ }
91
+ ) })
92
+ ] });
93
+ }
72
94
  return /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 items-center gap-1", className), children: [
73
95
  /* @__PURE__ */ jsx(
74
96
  ModelListDropdown,
@@ -33,7 +33,8 @@ import {
33
33
  } from "./transcript-audience.js";
34
34
  import { cn } from "@ai-matrx/design-system";
35
35
  import {
36
- ASSISTANT_MESSAGE_COLUMN_INSET_CLASS
36
+ ASSISTANT_MESSAGE_COLUMN_INSET_CLASS,
37
+ WIDE_FIGURE_SCOPE_ATTRIBUTE
37
38
  } from "./assistant-message-layout.js";
38
39
  import {
39
40
  TranscriptLayoutProvider,
@@ -246,6 +247,7 @@ function AgentConversationColumn({
246
247
  {
247
248
  ref: scrollRef,
248
249
  onScroll: handleScroll,
250
+ ...edgeScroll && resolvedLayout.width === "reading" && resolvedLayout.align === "center" ? { [WIDE_FIGURE_SCOPE_ATTRIBUTE]: "" } : {},
249
251
  className: cn(
250
252
  // A SIZE container: what renders inside (the empty-state hero)
251
253
  // answers the pane's real height — a 438px split pane beside
@@ -5,7 +5,7 @@ import { AgentSaveStatus, AgentOptionsMenu } from "../../../host/ui-slots.js";
5
5
  import { AgentReferenceCopyButton } from "./AgentReferenceCopyButton.js";
6
6
  import { AgentHeaderMobile } from "./AgentHeaderMobile.js";
7
7
  import { ReviewAnswersLink } from "../../../host/ui-slots.js";
8
- import { ChevronLeftTapButton } from "@ai-matrx/tap-target/buttons";
8
+ import { ChevronLeftTapButton } from "@ai-matrx/design-system/tap-target/buttons";
9
9
  function AgentHeader({
10
10
  agentId,
11
11
  agentName,
@@ -8,7 +8,7 @@ import { selectAgentIsDirty } from "../../redux/agent-definition/selectors.js";
8
8
  import {
9
9
  TapTargetButtonForGroup,
10
10
  TapTargetButtonGroup
11
- } from "@ai-matrx/tap-target";
11
+ } from "@ai-matrx/design-system/tap-target";
12
12
  import { AgentListDropdown } from "@ai-matrx/agents/catalog/react";
13
13
  import {
14
14
  AlertDialog,
@@ -4,7 +4,7 @@ import { useCallback } from "react";
4
4
  import { useRouter, usePathname, useSearchParams } from "../../../host/navigation.js";
5
5
  import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
6
6
  import { startNewConversation } from "../../redux/execution-system/thunks/create-instance.thunk.js";
7
- import { PlusTapButton } from "@ai-matrx/tap-target/buttons";
7
+ import { PlusTapButton } from "@ai-matrx/design-system/tap-target/buttons";
8
8
  import { selectFocusedConversation } from "../../redux/execution-system/conversation-focus/conversation-focus.selectors.js";
9
9
  import { pushAddressWithoutNavigating } from "@ai-matrx/rich-content/utils/addressWithoutNavigating";
10
10
  function AgentNewRunButton({ surfaceKey }) {
@@ -6,3 +6,24 @@
6
6
  export declare const ASSISTANT_MESSAGE_COLUMN_CLASS = "mx-auto w-full max-w-3xl";
7
7
  /** Chat's exact horizontal inset inside the canonical reading column. */
8
8
  export declare const ASSISTANT_MESSAGE_COLUMN_INSET_CLASS = "px-2";
9
+ /**
10
+ * WIDE FIGURES — a figure (a rendered web page, a wide visual) is not prose.
11
+ * A transcript that opts in carries `WIDE_FIGURE_SCOPE_ATTRIBUTE` on its scroll
12
+ * area, which is a CSS size container; inside it an element with
13
+ * `WIDE_FIGURE_CLASS` breaks out of the reading column to the transcript's own
14
+ * width (`100cqi`), minus a 1rem gutter each side, centered on the column, and
15
+ * capped at `WIDE_FIGURE_MAX_WIDTH_PX`.
16
+ *
17
+ * 1100px: wide enough that the common 960–1080px layouts AI-written pages use
18
+ * (a 960px `main` plus its padding) render at full size; no wider, so a large
19
+ * monitor never stretches a figure into empty bands. `max(100%, …)` means a
20
+ * transcript no wider than the column (phones, split panes) leaves the figure
21
+ * exactly column-wide. Outside an opted-in transcript (notes, rich-document
22
+ * surfaces, a `width: "fill"` host, a start-aligned column) it does nothing.
23
+ *
24
+ * Classes are literal strings so the host's Tailwind scan of this package
25
+ * finds them; keep 1100px in sync with WIDE_FIGURE_MAX_WIDTH_PX.
26
+ */
27
+ export declare const WIDE_FIGURE_MAX_WIDTH_PX = 1100;
28
+ export declare const WIDE_FIGURE_SCOPE_ATTRIBUTE = "data-wide-figures";
29
+ export declare const WIDE_FIGURE_CLASS = "[[data-wide-figures]_&]:w-[max(100%,min(1100px,100cqi_-_2rem))] [[data-wide-figures]_&]:ml-[calc((100%_-_max(100%,min(1100px,100cqi_-_2rem)))/2)]";
@@ -1,6 +1,12 @@
1
1
  const ASSISTANT_MESSAGE_COLUMN_CLASS = "mx-auto w-full max-w-3xl";
2
2
  const ASSISTANT_MESSAGE_COLUMN_INSET_CLASS = "px-2";
3
+ const WIDE_FIGURE_MAX_WIDTH_PX = 1100;
4
+ const WIDE_FIGURE_SCOPE_ATTRIBUTE = "data-wide-figures";
5
+ const WIDE_FIGURE_CLASS = "[[data-wide-figures]_&]:w-[max(100%,min(1100px,100cqi_-_2rem))] [[data-wide-figures]_&]:ml-[calc((100%_-_max(100%,min(1100px,100cqi_-_2rem)))/2)]";
3
6
  export {
4
7
  ASSISTANT_MESSAGE_COLUMN_CLASS,
5
- ASSISTANT_MESSAGE_COLUMN_INSET_CLASS
8
+ ASSISTANT_MESSAGE_COLUMN_INSET_CLASS,
9
+ WIDE_FIGURE_CLASS,
10
+ WIDE_FIGURE_MAX_WIDTH_PX,
11
+ WIDE_FIGURE_SCOPE_ATTRIBUTE
6
12
  };
@@ -15,7 +15,7 @@ import { useWorkingDocumentVersions } from "./useWorkingDocumentVersions.js";
15
15
  import { useWorkingDocument } from "../../hooks/useWorkingDocument.js";
16
16
  import { setWorkingDocMainView } from "./workingDocumentViewStore.js";
17
17
  import { ErrorAlchemyMenu } from "../../../host/ui-slots.js";
18
- import { ChevronLeftTapButton, ChevronRightTapButton } from "@ai-matrx/tap-target/buttons";
18
+ import { ChevronLeftTapButton, ChevronRightTapButton } from "@ai-matrx/design-system/tap-target/buttons";
19
19
  function formatWhen(iso) {
20
20
  if (!iso) return "Now";
21
21
  try {