@ai-matrx/chat 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @ai-matrx/chat
2
2
 
3
+ ## 0.3.5 — 2026-10-07
4
+ ### Chat mode opens the agent itself; Duplicate opens the copy in a new tab
5
+
6
+ - The Chat-mode agent pill's menu starts with "This chat's agent": its row opens the agent menu's own
7
+ peek card (`AgentDetailCard`) — view, open, Sneak Peek, and Duplicate when the agent is not yours.
8
+ - `agents/components/shared/AgentPeekDuplicateButton` moved here from matrx-frontend (one Duplicate for
9
+ every peek). It now opens the copy's page in a NEW TAB instead of navigating away from the chat.
10
+ - Needs `@ai-matrx/agents` 0.49.0.
11
+ - The composer's + (Chat Options) is tinted only when something was added — no longer blue on every fresh
12
+ chat. The Chat Options window lists its sections as start-aligned rows (`Tile`), not centred buttons.
13
+ - Consumer action: import `AgentPeekDuplicateButton` from
14
+ `@ai-matrx/chat/agents/components/shared/AgentPeekDuplicateButton` and delete the app's copy.
15
+
3
16
  ## 0.3.4 — 2026-10-07
4
17
  ### Re-sync before matrx-frontend consumes the published package (P27)
5
18
 
@@ -89,7 +89,7 @@ function RunControlsMenu({
89
89
  Button,
90
90
  {
91
91
  variant: "quiet",
92
- tone: rc.addedCount > 0 || rc.isCustomized ? "primary" : void 0,
92
+ tone: rc.addedCount > 0 ? "primary" : void 0,
93
93
  icon: /* @__PURE__ */ jsx(TriggerIcon, {}),
94
94
  tabIndex: variant === "plus" ? -1 : void 0,
95
95
  title: "Chat Options",
@@ -0,0 +1,4 @@
1
+ export declare function ComposerAgentPeek({ agentId, onDone }: {
2
+ agentId: string;
3
+ onDone?: () => void;
4
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect } from "react";
4
+ import { AgentDetailCard, useAgentCatalogState } from "@ai-matrx/agents/catalog/react";
5
+ import { useAppDispatch, useAppSelector } from "../../../../../store/hooks.js";
6
+ import { selectAgentById, selectAgentReadyForBuilder } from "../../../../redux/agent-definition/selectors.js";
7
+ import { fetchFullAgent } from "../../../../redux/agent-definition/thunks.js";
8
+ import { AgentPeekDuplicateButton } from "../../../shared/AgentPeekDuplicateButton.js";
9
+ function ComposerAgentPeek({ agentId, onDone }) {
10
+ const dispatch = useAppDispatch();
11
+ const row = useAgentCatalogState((state) => state.byId[agentId]);
12
+ const record = useAppSelector((state) => selectAgentById(state, agentId));
13
+ const isReady = useAppSelector((state) => selectAgentReadyForBuilder(state, agentId));
14
+ useEffect(() => {
15
+ if (!isReady) void dispatch(fetchFullAgent(agentId)).unwrap().catch(() => void 0);
16
+ }, [dispatch, agentId, isReady]);
17
+ const agent = row ?? (record ? {
18
+ id: record.id,
19
+ name: record.name,
20
+ description: record.description,
21
+ category: record.category,
22
+ tags: record.tags,
23
+ agentType: record.agentType,
24
+ modelId: record.modelId,
25
+ isActive: record.isActive,
26
+ isArchived: record.isArchived,
27
+ isFavorite: record.isFavorite,
28
+ createdBy: record.createdBy,
29
+ organizationId: record.organizationId,
30
+ taskId: record.taskId,
31
+ sourceAgentId: record.sourceAgentId,
32
+ createdAt: record.createdAt,
33
+ updatedAt: record.updatedAt,
34
+ isOwner: record.isOwner,
35
+ accessLevel: record.accessLevel,
36
+ sharedByEmail: record.sharedByEmail,
37
+ orchestra: null
38
+ } : null);
39
+ if (!agent) return null;
40
+ return /* @__PURE__ */ jsx(
41
+ AgentDetailCard,
42
+ {
43
+ agent,
44
+ actions: /* @__PURE__ */ jsx(AgentPeekDuplicateButton, { agentId, onDuplicated: onDone })
45
+ }
46
+ );
47
+ }
48
+ export {
49
+ ComposerAgentPeek
50
+ };
@@ -36,6 +36,7 @@ import { composerShows } from "./composer-mode-visibility.js";
36
36
  import { composerPillClass } from "./composer-chip.js";
37
37
  import { useComposerAgent, useEffectiveModelId } from "./useComposerAgent.js";
38
38
  import { useComposerAgentFilter } from "./useComposerAgentFilter.js";
39
+ import { ComposerAgentPeek } from "./ComposerAgentPeek.js";
39
40
  import { presentOrganizationRefusal } from "../../../../../host/org.js";
40
41
  import { ensureOrgId, isOrganizationSelectionCancelled } from "../../../../../host/org.js";
41
42
  function pillLabel(info, mode) {
@@ -150,6 +151,17 @@ function ComposerAgentPill({ conversationId, mode, size, agentControl, menuSide,
150
151
  )
151
152
  ] });
152
153
  }
154
+ function ThisAgentRow({ info, close }) {
155
+ if (!info.agentId) return null;
156
+ return /* @__PURE__ */ jsx(
157
+ ComposerSubmenu,
158
+ {
159
+ row: { icon: Webhook, label: info.agentName ?? "Agent", description: "View, open or duplicate" },
160
+ panelClassName: "w-[340px] h-[420px] p-0",
161
+ children: /* @__PURE__ */ jsx(ComposerAgentPeek, { agentId: info.agentId, onDone: close })
162
+ }
163
+ );
164
+ }
153
165
  function ChatPresetsPanel({
154
166
  conversationId,
155
167
  info,
@@ -169,7 +181,7 @@ function ChatPresetsPanel({
169
181
  if (!onSelectAgent) {
170
182
  return /* @__PURE__ */ jsxs(Fragment, { children: [
171
183
  /* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Agent" }),
172
- /* @__PURE__ */ jsx(ComposerMenuRow, { label: info.agentName ?? "Agent", checked: true }),
184
+ /* @__PURE__ */ jsx(ThisAgentRow, { info, close }),
173
185
  /* @__PURE__ */ jsx(ComposerMenuHelp, { children: "This page always answers with this agent." }),
174
186
  /* @__PURE__ */ jsx(ComposerMenuDivider, {}),
175
187
  /* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Model for this chat" }),
@@ -229,6 +241,9 @@ function ChatPresetsPanel({
229
241
  void savePersonalDefault("chat_default_offering", offeringId ?? "");
230
242
  };
231
243
  return /* @__PURE__ */ jsxs(Fragment, { children: [
244
+ /* @__PURE__ */ jsx(ComposerMenuLabel, { children: "This chat's agent" }),
245
+ /* @__PURE__ */ jsx(ThisAgentRow, { info, close }),
246
+ /* @__PURE__ */ jsx(ComposerMenuDivider, {}),
232
247
  /* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Chat agents" }),
233
248
  info.presets.map((preset) => /* @__PURE__ */ jsx(
234
249
  ComposerMenuRow,
@@ -0,0 +1,5 @@
1
+ export declare function AgentPeekDuplicateButton({ agentId, onDuplicated, }: {
2
+ agentId: string;
3
+ /** Close the peek once the copy opens. */
4
+ onDuplicated?: () => void;
5
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { CopyPlus, Loader2 } from "lucide-react";
5
+ import { Button } from "@ai-matrx/design-system/controls";
6
+ import { getUserMessage } from "@ai-matrx/agents/matrx";
7
+ import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
8
+ import { selectUserId } from "../../../host/identity.js";
9
+ import { getBuilderDoor } from "../../../host/builder-door.js";
10
+ import { toast } from "../../../host/notify.js";
11
+ import { isOrganizationSelectionCancelled } from "../../../host/org.js";
12
+ import { selectAgentById, selectAgentReadyForBuilder } from "../../redux/agent-definition/selectors.js";
13
+ import { agentGoHref } from "../../addressing/agentAddress.js";
14
+ function AgentPeekDuplicateButton({
15
+ agentId,
16
+ onDuplicated
17
+ }) {
18
+ const dispatch = useAppDispatch();
19
+ const userId = useAppSelector(selectUserId);
20
+ const record = useAppSelector((state) => selectAgentById(state, agentId));
21
+ const isReady = useAppSelector((state) => selectAgentReadyForBuilder(state, agentId));
22
+ const [busy, setBusy] = useState(false);
23
+ const door = getBuilderDoor();
24
+ if (!door || !isReady || !record) return null;
25
+ const ownsIt = record.isOwner === true || record.createdBy != null && record.createdBy === userId;
26
+ if (ownsIt) return null;
27
+ const duplicate = async () => {
28
+ if (busy) return;
29
+ setBusy(true);
30
+ const tab = window.open("about:blank", "_blank");
31
+ try {
32
+ const newId = await dispatch(door.duplicateAgent(agentId)).unwrap();
33
+ const href = agentGoHref(newId, "/build");
34
+ if (tab) {
35
+ tab.opener = null;
36
+ tab.location.href = href;
37
+ } else {
38
+ window.open(href, "_blank", "noopener");
39
+ }
40
+ toast.success(`Duplicated "${record.name}" \u2014 opened in a new tab`);
41
+ onDuplicated?.();
42
+ } catch (err) {
43
+ tab?.close();
44
+ if (!isOrganizationSelectionCancelled(err)) {
45
+ toast.error("Could not duplicate agent", { description: getUserMessage(err) });
46
+ }
47
+ } finally {
48
+ setBusy(false);
49
+ }
50
+ };
51
+ return /* @__PURE__ */ jsx(
52
+ Button,
53
+ {
54
+ icon: busy ? /* @__PURE__ */ jsx(Loader2, { className: "animate-spin" }) : /* @__PURE__ */ jsx(CopyPlus, {}),
55
+ variant: "outline",
56
+ onClick: duplicate,
57
+ disabled: busy,
58
+ title: "Make your own copy of this agent, in a new tab",
59
+ children: "Duplicate"
60
+ }
61
+ );
62
+ }
63
+ export {
64
+ AgentPeekDuplicateButton
65
+ };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { useAppDispatch } from "../../../store/hooks.js";
5
5
  import { closeOverlay, CHAT_WINDOWS } from "../../../host/windows.js";
@@ -20,7 +20,7 @@ import { selectInstanceAgentId } from "../../../agents/redux/execution-system/in
20
20
  import { selectAgentName } from "../../../agents/redux/agent-definition/selectors.js";
21
21
  import { NonEditableContextMenu } from "../../../host/ui-slots.js";
22
22
  import { buildAgentMenuSection, agentEntityRef } from "../../../agents/menu/agent-actions.js";
23
- import { Button } from "@ai-matrx/design-system/controls";
23
+ import { Tile } from "@ai-matrx/design-system/controls";
24
24
  const OVERLAY_ID = CHAT_WINDOWS.runControlsWindow;
25
25
  function RunControlsWindowInner({
26
26
  conversationId,
@@ -104,10 +104,23 @@ function RunControlsWindowInner({
104
104
  children: rc.tabs.map((t) => {
105
105
  const Icon = t.icon;
106
106
  const on = activeTab === t.id;
107
- return /* @__PURE__ */ jsxs(Button, { variant: "quiet", pressed: on, icon: /* @__PURE__ */ jsx(Icon, {}), role: "tab", "aria-selected": on, onClick: () => setTab(t.id), children: [
108
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: t.label }),
109
- rc.tabTrailing(t.id)
110
- ] }, t.id);
107
+ return (
108
+ // A list of sections is rows, start-aligned — never centred buttons.
109
+ /* @__PURE__ */ jsx(
110
+ Tile,
111
+ {
112
+ variant: "quiet",
113
+ selected: on,
114
+ icon: /* @__PURE__ */ jsx(Icon, {}),
115
+ title: t.label,
116
+ end: rc.tabTrailing(t.id),
117
+ role: "tab",
118
+ "aria-selected": on,
119
+ onClick: () => setTab(t.id)
120
+ },
121
+ t.id
122
+ )
123
+ );
111
124
  })
112
125
  }
113
126
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/chat",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "THE one AI Matrx person-to-agent chat system: the full chat (running agents, conversations, tool calls, streaming, context, surfaces, voice) as per-module subpaths @ai-matrx/chat/<domain>/<module> over a host contract (/host, /host/react) whose only required value is a db; plus the kernel compatibility entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) until P28.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -201,22 +201,22 @@
201
201
  "server-only": "^0.0.1",
202
202
  "uuid": "^14.0.2",
203
203
  "zod": "^4.6.5",
204
- "@ai-matrx/alchemy": "latest",
205
204
  "@ai-matrx/agents": "latest",
205
+ "@ai-matrx/alchemy": "latest",
206
+ "@ai-matrx/associations": "latest",
206
207
  "@ai-matrx/browser-audio": "latest",
207
208
  "@ai-matrx/canvas": "latest",
208
209
  "@ai-matrx/content-ir": "latest",
209
210
  "@ai-matrx/content-ir-react": "latest",
210
211
  "@ai-matrx/data": "latest",
211
212
  "@ai-matrx/design-system": "latest",
212
- "@ai-matrx/associations": "latest",
213
213
  "@ai-matrx/diff": "latest",
214
+ "@ai-matrx/icons": "latest",
214
215
  "@ai-matrx/kit": "latest",
215
216
  "@ai-matrx/media": "latest",
216
- "@ai-matrx/icons": "latest",
217
+ "@ai-matrx/print": "latest",
217
218
  "@ai-matrx/realtime": "latest",
218
219
  "@ai-matrx/records": "latest",
219
- "@ai-matrx/print": "latest",
220
220
  "@ai-matrx/records-ui": "latest",
221
221
  "@ai-matrx/tap-target": "latest"
222
222
  },