@ai-matrx/chat 0.2.9 → 0.3.2

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 (60) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/agent-copy/clipboard.d.ts +2 -4
  3. package/dist/agent-copy/clipboard.js +1 -14
  4. package/dist/agent-copy/page-capture/pageCapture.d.ts +11 -62
  5. package/dist/agent-copy/page-capture/pageCapture.js +5 -22
  6. package/dist/agents/components/context-items/bodies/WebpageBody.js +1 -1
  7. package/dist/agents/components/context-preview/AttachedContextSection.js +9 -8
  8. package/dist/agents/components/context-preview/ContextCompareView.js +13 -13
  9. package/dist/agents/components/context-preview/ContextPreviewPanel.js +13 -13
  10. package/dist/agents/components/conversation-actions/conversation-verbs.js +2 -6
  11. package/dist/agents/components/debug/SandboxFileViewer.js +1 -1
  12. package/dist/agents/components/debug/StreamDebugPanel.js +2 -2
  13. package/dist/agents/components/messages-display/TranscriptIntegrityCopyButton.js +1 -1
  14. package/dist/agents/components/observational-memory/components/MemoryStateInspector.js +4 -4
  15. package/dist/agents/components/run-controls/AgentExecutionTestModal.js +2 -2
  16. package/dist/agents/components/run-controls/PayloadTab.js +7 -1
  17. package/dist/agents/components/run-controls/panels/BackendTargetPanel.js +7 -1
  18. package/dist/agents/components/shared/AgentSaveStatus.js +22 -1
  19. package/dist/agents/hooks/useAgentAutoSave.js +14 -2
  20. package/dist/agents/menu/agent-actions.js +3 -5
  21. package/dist/agents/prompt-preview/PromptPreviewContent.js +1 -1
  22. package/dist/agents/redux/agent-definition/slice.js +1 -0
  23. package/dist/agents/redux/agent-settings/agentSettingsSlice.d.ts +3 -0
  24. package/dist/agents/redux/agent-settings/agentSettingsSlice.js +12 -2
  25. package/dist/agents/redux/agent-settings/types.d.ts +7 -0
  26. package/dist/agents/redux/agent-shortcuts/thunks.js +9 -5
  27. package/dist/agents/redux/execution-system/instance-resources/remarks.d.ts +6 -0
  28. package/dist/agents/redux/execution-system/instance-resources/remarks.js +16 -0
  29. package/dist/agents/redux/execution-system/instance-ui-state/components/InstanceUIStateCore.js +7 -1
  30. package/dist/agents/redux/execution-system/instance-ui-state/components/InstanceUIStateList.js +7 -1
  31. package/dist/agents/redux/execution-system/message-crud/commit-inline-edit.thunk.js +2 -1
  32. package/dist/canvas/workspace/useCanvasWorkspaceConversation.d.ts +12 -0
  33. package/dist/canvas/workspace/useCanvasWorkspaceConversation.js +29 -1
  34. package/dist/context-menu/types.d.ts +7 -0
  35. package/dist/cx-chat/components/messages/AssistantMessage.js +8 -10
  36. package/dist/cx-chat/components/messages/UserMessage.js +7 -1
  37. package/dist/cx-conversation/AssistantMessage.js +8 -10
  38. package/dist/cx-conversation/UserMessage.js +7 -1
  39. package/dist/host/clipboard.d.ts +7 -0
  40. package/dist/host/clipboard.js +11 -0
  41. package/dist/host/db-types.d.ts +15 -12
  42. package/dist/host/ui-slots.d.ts +2 -8
  43. package/dist/host/ui-slots.js +11 -16
  44. package/dist/public-chat/components/PublicMessageOptionsMenu.js +9 -16
  45. package/dist/store/create-chat-store.d.ts +10 -1
  46. package/dist/store/create-chat-store.js +4 -2
  47. package/dist/surfaces/runtime/SurfaceRuntimeContext.d.ts +10 -0
  48. package/dist/surfaces/runtime/SurfaceRuntimeContext.js +23 -1
  49. package/dist/surfaces/services/surface-config.service.d.ts +3 -1
  50. package/dist/surfaces/services/surface-config.service.js +33 -17
  51. package/dist/tool-call-visualization/components/ToolTabBodies.d.ts +0 -4
  52. package/dist/tool-call-visualization/components/ToolTabBodies.js +2 -25
  53. package/dist/tool-call-visualization/result-fields/ResultFile.js +2 -8
  54. package/dist/tool-call-visualization/result-fields/ShortId.js +1 -23
  55. package/dist/voice-agent/components/LiveVoiceDoor.js +1 -1
  56. package/dist/voice-agent/components/VoiceDebugPanel.js +1 -1
  57. package/dist/voice-agent/hooks/useVoiceAgentInstance.js +1 -1
  58. package/dist/window-panels/windows/agents/ObservationalMemoryWindow.d.ts +0 -17
  59. package/dist/window-panels/windows/agents/ObservationalMemoryWindow.js +7 -1
  60. package/package.json +7 -7
@@ -44,7 +44,7 @@ function PromptPreviewContent({
44
44
  }, [conversationId, store, nonce]);
45
45
  const copy = async (label, text) => {
46
46
  try {
47
- await copyText(text);
47
+ if (!await copyText(text)) return;
48
48
  toast.success(`${label} copied`);
49
49
  } catch {
50
50
  toast.error("Couldn't copy");
@@ -182,6 +182,7 @@ function pushUndoEntry(record, field, previousValue) {
182
182
  function applyFieldEdit(record, field, value) {
183
183
  const previousValue = record[field];
184
184
  const wasDirty = hasField(record._dirtyFields, field);
185
+ if (!wasDirty && isEqual(value, previousValue)) return;
185
186
  if (!wasDirty) {
186
187
  record._fieldHistory[field] = previousValue;
187
188
  }
@@ -579,6 +579,7 @@ export declare const requestModelSwitch: import("@reduxjs/toolkit").AsyncThunk<{
579
579
  newModelControls: import("./types.js").NormalizedControls;
580
580
  mode: ResolutionMode;
581
581
  customActions: {};
582
+ offeringId?: string | undefined;
582
583
  };
583
584
  newDefaults: Partial<{
584
585
  model?: string | undefined;
@@ -831,6 +832,8 @@ export declare const requestModelSwitch: import("@reduxjs/toolkit").AsyncThunk<{
831
832
  }, {
832
833
  agentId: string;
833
834
  newModelId: string;
835
+ /** The new model's class picked in the same gesture (applied on confirm). */
836
+ offeringId?: string;
834
837
  }, import("@reduxjs/toolkit").AsyncThunkConfig>;
835
838
  /**
836
839
  * Save agent settings to the database.
@@ -154,7 +154,11 @@ const loadAgentSettingsDirect = createAsyncThunk(
154
154
  );
155
155
  const requestModelSwitch = createAsyncThunk(
156
156
  "agentSettings/requestModelSwitch",
157
- async ({ agentId, newModelId }, { getState, rejectWithValue }) => {
157
+ async ({
158
+ agentId,
159
+ newModelId,
160
+ offeringId
161
+ }, { getState, rejectWithValue }) => {
158
162
  const state = getState();
159
163
  const newModel = selectModelById(state, newModelId);
160
164
  if (!newModel) {
@@ -200,7 +204,8 @@ const requestModelSwitch = createAsyncThunk(
200
204
  // new model does not carry natively (settings-translation K7). Reset
201
205
  // or remove is the person's explicit choice in the dialog.
202
206
  mode: "keep_all",
203
- customActions: {}
207
+ customActions: {},
208
+ ...offeringId ? { offeringId } : {}
204
209
  },
205
210
  newDefaults
206
211
  };
@@ -433,6 +438,11 @@ const agentSettingsSlice = createSlice({
433
438
  ...resolved,
434
439
  model: pending.newModelId
435
440
  };
441
+ if (pending.offeringId) {
442
+ finalSettings.offering_id = pending.offeringId;
443
+ } else {
444
+ delete finalSettings.offering_id;
445
+ }
436
446
  if (entry.context === "builder") {
437
447
  entry.defaults = finalSettings;
438
448
  } else {
@@ -67,6 +67,13 @@ export interface PendingModelSwitch {
67
67
  mode: ResolutionMode;
68
68
  /** Per-conflict override when mode === 'custom' */
69
69
  customActions: Partial<Record<keyof AgentSettings, ConflictAction>>;
70
+ /**
71
+ * The CLASS (offering) picked with the new model, applied on confirm.
72
+ * Absent = no class picked → the old model's pin is dropped on confirm (a
73
+ * class belongs to exactly one model). Never written before confirm, so the
74
+ * entry never pairs the old model with the new model's class.
75
+ */
76
+ offeringId?: string;
70
77
  }
71
78
  export interface AgentVariable {
72
79
  name: string;
@@ -333,6 +333,9 @@ const saveShortcutField = createAsyncThunk(
333
333
  dispatch(markShortcutSaved({ id: shortcutId }));
334
334
  }
335
335
  );
336
+ function withGlobalShortcutAdminLane(request) {
337
+ return request.setHeader("x-matrx-admin-lane", "1");
338
+ }
336
339
  const createShortcut = createAsyncThunk("agentShortcut/create", async (shortcutData, { dispatch, getState }) => {
337
340
  const userId = selectUserId(getState());
338
341
  const organizationId = await ensureOrgId(shortcutData.organizationId);
@@ -346,7 +349,9 @@ const createShortcut = createAsyncThunk("agentShortcut/create", async (shortcutD
346
349
  createdAt: "",
347
350
  updatedAt: ""
348
351
  };
349
- const { data, error } = await shortcutTable(supabase).insert(agentShortcutToInsert(draft)).select().single();
352
+ const isGlobalWrite = shortcutData.userId === null && shortcutData.organizationId == null && shortcutData.projectId == null && shortcutData.taskId == null;
353
+ const insertRequest = shortcutTable(supabase).insert(agentShortcutToInsert(draft)).select().single();
354
+ const { data, error } = await (isGlobalWrite ? withGlobalShortcutAdminLane(insertRequest) : insertRequest);
350
355
  if (error) throw pgErrorToError(error);
351
356
  const newShortcut = toGlobalOwnershipRecord(
352
357
  dbRowToAgentShortcut(data),
@@ -392,13 +397,12 @@ const duplicateShortcut = createAsyncThunk("agentShortcut/duplicate", async (arg
392
397
  const promoteShortcutToGlobal = createAsyncThunk(
393
398
  "agentShortcut/promoteToGlobal",
394
399
  async ({ shortcutId, targetCategoryId, label }, { dispatch }) => {
395
- const { data, error } = await supabase.rpc(
396
- SHORTCUT_RPCS.promoteToGlobal,
397
- {
400
+ const { data, error } = await withGlobalShortcutAdminLane(
401
+ supabase.rpc(SHORTCUT_RPCS.promoteToGlobal, {
398
402
  p_shortcut_id: shortcutId,
399
403
  p_target_category_id: targetCategoryId,
400
404
  p_label: label && label.trim().length > 0 ? label.trim() : void 0
401
- }
405
+ })
402
406
  );
403
407
  if (error) throw pgErrorToError(error);
404
408
  const newShortcutId = data;
@@ -159,6 +159,12 @@ export interface RemarkDurability {
159
159
  }
160
160
  /** True while some chip of this conversation has not yet been saved server-side. */
161
161
  export declare function hasPendingRemarkWrites(conversationId: string): boolean;
162
+ /**
163
+ * True once the app has registered how unsent chips are kept. Without it every
164
+ * chip is browser-only and lost on reload, so a missing port is announced, never
165
+ * silently stood in for (the door a person touches must say when it is not wired).
166
+ */
167
+ export declare function isRemarkDurabilityRegistered(): boolean;
162
168
  /** Save every pending chip write and wait for its row/ref (called before a send). */
163
169
  export declare function flushRemarkWrites(conversationId: string): Promise<void>;
164
170
  /** Put a conversation's unsent chips back (any device) — the composer calls this once on mount. */
@@ -72,10 +72,23 @@ function buildSource(item, coalesceKey) {
72
72
  function hasPendingRemarkWrites(conversationId) {
73
73
  return durability?.hasPending(conversationId) ?? false;
74
74
  }
75
+ function isRemarkDurabilityRegistered() {
76
+ return durability !== null;
77
+ }
78
+ const announcedMissing = /* @__PURE__ */ new Set();
79
+ function announceMissingDurability(op) {
80
+ if (announcedMissing.has(op)) return;
81
+ announcedMissing.add(op);
82
+ console.error(
83
+ `[remarks] ${op} ran with no durability port registered: unsent chips are NOT kept server-side. The app must call registerRemarkDurability (providers/ChatSurfaceRegistrations).`
84
+ );
85
+ }
75
86
  async function flushRemarkWrites(conversationId) {
87
+ if (!durability) announceMissingDurability("flush");
76
88
  await durability?.flush(conversationId);
77
89
  }
78
90
  function restoreComposerRemarks(conversationId) {
91
+ if (!durability) announceMissingDurability("restore");
79
92
  durability?.restore(conversationId);
80
93
  }
81
94
  let durability = null;
@@ -100,6 +113,7 @@ function stageRemark(conversationId, item, options = {}) {
100
113
  dispatch(setResourceSource({ conversationId, resourceId: live.resourceId, source }));
101
114
  dispatch(setResourcePreview({ conversationId, resourceId: live.resourceId, preview: source.label }));
102
115
  if (!options.fromServer && !item.blockStateRef) {
116
+ if (!durability) announceMissingDurability("save");
103
117
  durability?.save(conversationId, live.resourceId, coalesceKey, item);
104
118
  }
105
119
  return live.resourceId;
@@ -110,6 +124,7 @@ function stageRemark(conversationId, item, options = {}) {
110
124
  dispatch(addResource({ conversationId, blockType: REMARKS_BLOCK_TYPE, source, resourceId }));
111
125
  dispatch(setResourcePreview({ conversationId, resourceId, preview: source.label }));
112
126
  if (!options.fromServer && !item.blockStateRef) {
127
+ if (!durability) announceMissingDurability("save");
113
128
  durability?.save(conversationId, resourceId, coalesceKey, item);
114
129
  }
115
130
  return resourceId;
@@ -205,6 +220,7 @@ export {
205
220
  dismissRemarkChip,
206
221
  flushRemarkWrites,
207
222
  hasPendingRemarkWrites,
223
+ isRemarkDurabilityRegistered,
208
224
  readStoredRemarks,
209
225
  registerRemarkDurability,
210
226
  remarkChipTitle,
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useClipboard } from "@ai-matrx/kit/clipboard";
4
+ import { toast } from "../../../../../host/notify.js";
3
5
  import { Badge, Button, Tile } from "@ai-matrx/design-system/controls";
4
6
  import { useState } from "react";
5
7
  import { cn } from "@ai-matrx/design-system";
@@ -18,9 +20,13 @@ import {
18
20
  selectInstanceTitle
19
21
  } from "../instance-ui-state.selectors.js";
20
22
  function useCopyText(text) {
23
+ const { copyText } = useClipboard({
24
+ notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
25
+ });
21
26
  const [copied, setCopied] = useState(false);
22
27
  const copy = () => {
23
- navigator.clipboard.writeText(text).then(() => {
28
+ copyText(text).then((copied2) => {
29
+ if (!copied2) return;
24
30
  setCopied(true);
25
31
  setTimeout(() => setCopied(false), 1500);
26
32
  });
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useClipboard } from "@ai-matrx/kit/clipboard";
4
+ import { toast } from "../../../../../host/notify.js";
3
5
  import { useState } from "react";
4
6
  import { cn } from "@ai-matrx/design-system";
5
7
  import {
@@ -19,9 +21,13 @@ import {
19
21
  } from "../instance-ui-state.selectors.js";
20
22
  import { Button } from "@ai-matrx/design-system/controls";
21
23
  function useCopyText(text) {
24
+ const { copyText } = useClipboard({
25
+ notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
26
+ });
22
27
  const [copied, setCopied] = useState(false);
23
28
  const copy = () => {
24
- navigator.clipboard.writeText(text).then(() => {
29
+ copyText(text).then((copied2) => {
30
+ if (!copied2) return;
25
31
  setCopied(true);
26
32
  setTimeout(() => setCopied(false), 1500);
27
33
  });
@@ -6,6 +6,7 @@ import { updateMessageRecord } from "../messages/messages.slice.js";
6
6
  import { saveAnswerEdit } from "./save-answer-edit.thunk.js";
7
7
  import { refetchSingleMessage } from "./refetch-single-message.thunk.js";
8
8
  import { toast } from "../../../../host/notify.js";
9
+ import { copyToHostClipboard } from "../../../../host/clipboard.js";
9
10
  import { buildContentBlocksForSave } from "../../../../cx-chat/utils/buildContentBlocksForSave.js";
10
11
  import { stripCitationMarkers } from "../messages/message-citations.js";
11
12
  import { extractFlatText } from "../messages/messages.selectors.js";
@@ -97,7 +98,7 @@ async function persistInlineEdit(dispatch, conversationId, messageId, entry) {
97
98
  action: {
98
99
  label: "Copy my edit",
99
100
  onClick: () => {
100
- void navigator.clipboard?.writeText(draft);
101
+ void copyToHostClipboard(draft);
101
102
  }
102
103
  }
103
104
  });
@@ -62,6 +62,18 @@ export declare function conversationInAddress(param: string, search: string): st
62
62
  export declare function workspaceChatMemoryKey(surfaceKey: string): string;
63
63
  export declare function recallWorkspaceConversation(surfaceKey: string): string | null;
64
64
  export declare function rememberWorkspaceConversation(surfaceKey: string, conversationId: string | null): void;
65
+ /**
66
+ * THE RESERVED ID OF AN UNSENT CHAT (2026-10-06). A new chat that has not been
67
+ * sent is unknown to the server, so a reload cannot reopen it and used to mint a
68
+ * different id — which orphaned every chip staged into it (a comment riding
69
+ * "with next message" came back gone). The id is reserved on this device per
70
+ * home and handed to the launch, so the unsent chat comes back under the SAME id
71
+ * and its saved chips are found. Released when the server has the conversation
72
+ * (the memory above takes over) and by New chat (a new chat is a new id).
73
+ */
74
+ export declare function reservedChatKey(surfaceKey: string): string;
75
+ export declare function reserveWorkspaceConversationId(surfaceKey: string, fresh: boolean): string | undefined;
76
+ export declare function releaseWorkspaceConversationId(surfaceKey: string): void;
65
77
  /** `search` with `?<param>=` set to `conversationId`, or removed when null. */
66
78
  export declare function addressWithConversation(param: string, search: string, conversationId: string | null): string;
67
79
  export interface CanvasWorkspaceConversationController {
@@ -39,6 +39,27 @@ function rememberWorkspaceConversation(surfaceKey, conversationId) {
39
39
  } catch {
40
40
  }
41
41
  }
42
+ function reservedChatKey(surfaceKey) {
43
+ return `matrx:workspace-chat-reserved:${surfaceKey}`;
44
+ }
45
+ function reserveWorkspaceConversationId(surfaceKey, fresh) {
46
+ try {
47
+ const key = reservedChatKey(surfaceKey);
48
+ const kept = fresh ? null : window.localStorage.getItem(key);
49
+ if (kept && CONVERSATION_ID.test(kept)) return kept;
50
+ const id = window.crypto.randomUUID();
51
+ window.localStorage.setItem(key, id);
52
+ return id;
53
+ } catch {
54
+ return void 0;
55
+ }
56
+ }
57
+ function releaseWorkspaceConversationId(surfaceKey) {
58
+ try {
59
+ window.localStorage.removeItem(reservedChatKey(surfaceKey));
60
+ } catch {
61
+ }
62
+ }
42
63
  function addressWithConversation(param, search, conversationId) {
43
64
  const params = new URLSearchParams(search);
44
65
  if (conversationId) params.set(param, conversationId);
@@ -99,6 +120,7 @@ function useCanvasWorkspaceConversation(surfaceKey, input) {
99
120
  if (addressParam === null || !addressRead || !conversationId) return;
100
121
  const shown = request.kind === "open" || serverHasIt ? conversationId : null;
101
122
  rememberWorkspaceConversation(surfaceKey, shown);
123
+ if (shown) releaseWorkspaceConversationId(surfaceKey);
102
124
  const search = addressWithConversation(addressParam, window.location.search, shown);
103
125
  if (search === window.location.search) return;
104
126
  replaceAddressWithoutNavigating(`${window.location.pathname}${search}${window.location.hash}`);
@@ -111,8 +133,11 @@ function useCanvasWorkspaceConversation(surfaceKey, input) {
111
133
  const stale = () => handled.current !== key;
112
134
  if (request.kind === "new" || request.kind === "agent") {
113
135
  const runtime = ownSurface ? { runtime: { surfaceName: null } } : {};
114
- const launch = request.kind === "agent" ? launchAgent(request.agentId, { surfaceKey, sourceFeature: "chat", ...runtime }) : launchMandate(request.mandateKey ?? DEFAULT_NEW_CHAT_MANDATE_KEY, {
136
+ const reserved = addressParam !== null ? reserveWorkspaceConversationId(surfaceKey, request.nonce > 0) : void 0;
137
+ const reservation = reserved ? { conversationId: reserved } : {};
138
+ const launch = request.kind === "agent" ? launchAgent(request.agentId, { surfaceKey, sourceFeature: "chat", ...reservation, ...runtime }) : launchMandate(request.mandateKey ?? DEFAULT_NEW_CHAT_MANDATE_KEY, {
115
139
  surfaceKey,
140
+ ...reservation,
116
141
  // A REGISTERED feature, never a new string: this surface IS the chat.
117
142
  sourceFeature: "chat",
118
143
  ...runtime
@@ -193,7 +218,10 @@ export {
193
218
  addressWithConversation,
194
219
  conversationInAddress,
195
220
  recallWorkspaceConversation,
221
+ releaseWorkspaceConversationId,
196
222
  rememberWorkspaceConversation,
223
+ reserveWorkspaceConversationId,
224
+ reservedChatKey,
197
225
  useCanvasWorkspaceConversation,
198
226
  workspaceChatMemoryKey
199
227
  };
@@ -4,6 +4,7 @@ import type { SourceFeature } from "../agents/types/instance.types.js";
4
4
  import type { Scope } from "../agents/redux/shared/scope.js";
5
5
  import type { ApplicationScope } from "../agents/types/scope.types.js";
6
6
  import type { EntityTypeToken } from "@ai-matrx/associations";
7
+ import type { ResolvedItem } from "@ai-matrx/alchemy/declare";
7
8
  /**
8
9
  * The few shapes the host owns (the rich document's content source and actions, the sharing
9
10
  * system's resource types). The package declares them open; the host fills them in by augmenting
@@ -399,6 +400,12 @@ export interface MenuContentProps {
399
400
  variant: MenuVariant;
400
401
  /** The clicked target's own name for the header (`CONTEXT_MENU_HEADING_KEY`). */
401
402
  heading?: ContextMenuHeading | null;
403
+ /**
404
+ * The declared item the menu opened on (a table row: `table_row`, identity `table_id` + `row_id`),
405
+ * raw-free, as `itemSources` resolved it. It becomes the click target's `item`, so Actions and
406
+ * menu rows read the declared item instead of re-deriving it. Null = not on an item.
407
+ */
408
+ item?: ResolvedItem | null;
402
409
  sourceFeature: SourceFeature;
403
410
  surfaceName?: string;
404
411
  menuVersion: number;
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useClipboard } from "@ai-matrx/kit/clipboard";
4
+ import { toast } from "../../../host/notify.js";
3
5
  import { recognizeOurFileUrl } from "@ai-matrx/media/files/engine";
4
6
  import { useState, useEffect, useCallback } from "react";
5
7
  import {
@@ -22,6 +24,7 @@ import { chatConversationsActions } from "../../_legacy-stubs.js";
22
24
  import { RichDocumentActions } from "../../../host/rich-document-slots.js";
23
25
  import { MessageTimestamp } from "../../../agents/components/messages-display/MessageTimestamp.js";
24
26
  import { ErrorAlchemyMenu } from "../../../host/ui-slots.js";
27
+ import { downloadFile } from "@ai-matrx/kit/download";
25
28
  function AssistantMessage({
26
29
  message,
27
30
  sessionId,
@@ -31,6 +34,9 @@ function AssistantMessage({
31
34
  isTtsRequest = false,
32
35
  onContentChange
33
36
  }) {
37
+ const { copyText } = useClipboard({
38
+ notify: (message2, kind) => kind === "error" ? toast.error(message2) : toast.success(message2)
39
+ });
34
40
  const [isAppearing, setIsAppearing] = useState(true);
35
41
  const [isAudioLinkCopied, setIsAudioLinkCopied] = useState(false);
36
42
  const [isDownloading, setIsDownloading] = useState(false);
@@ -97,15 +103,8 @@ function AssistantMessage({
97
103
  try {
98
104
  const resp = await fetch(audioUrl);
99
105
  const blob = await resp.blob();
100
- const objectUrl = URL.createObjectURL(blob);
101
106
  const ext = audioMimeType?.split("/")[1] ?? "wav";
102
- const link = document.createElement("a");
103
- link.href = objectUrl;
104
- link.download = `audio-response.${ext}`;
105
- document.body.appendChild(link);
106
- link.click();
107
- document.body.removeChild(link);
108
- URL.revokeObjectURL(objectUrl);
107
+ downloadFile(`audio-response.${ext}`, blob, blob.type);
109
108
  } catch {
110
109
  } finally {
111
110
  setIsDownloading(false);
@@ -186,8 +185,7 @@ function AssistantMessage({
186
185
  variant: "quiet",
187
186
  onClick: async () => {
188
187
  if (!audioUrl) return;
189
- await navigator.clipboard.writeText(audioUrl).catch(() => {
190
- });
188
+ if (!await copyText(audioUrl)) return;
191
189
  setIsAudioLinkCopied(true);
192
190
  setTimeout(() => setIsAudioLinkCopied(false), 2e3);
193
191
  },
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useClipboard } from "@ai-matrx/kit/clipboard";
4
+ import { toast } from "../../../host/notify.js";
3
5
  import { useState, useEffect, useRef, useMemo } from "react";
4
6
  import {
5
7
  Check,
@@ -84,6 +86,9 @@ function UserMessage({
84
86
  onContentChange,
85
87
  compact = false
86
88
  }) {
89
+ const { copyText } = useClipboard({
90
+ notify: (message2, kind) => kind === "error" ? toast.error(message2) : toast.success(message2)
91
+ });
87
92
  const [isCollapsed, setIsCollapsed] = useState(true);
88
93
  const [isCopied, setIsCopied] = useState(false);
89
94
  const [shouldBeCollapsible, setShouldBeCollapsible] = useState(false);
@@ -130,7 +135,8 @@ function UserMessage({
130
135
  }, [isEditing]);
131
136
  const handleCopy = (e) => {
132
137
  e.stopPropagation();
133
- navigator.clipboard.writeText(message.content).then(() => {
138
+ copyText(message.content).then((copied) => {
139
+ if (!copied) return;
134
140
  setIsCopied(true);
135
141
  setTimeout(() => setIsCopied(false), 2e3);
136
142
  });
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useClipboard } from "@ai-matrx/kit/clipboard";
4
+ import { toast } from "../host/notify.js";
3
5
  import { recognizeOurFileUrl } from "@ai-matrx/media/files/engine";
4
6
  import { useState, useEffect, useCallback } from "react";
5
7
  import {
@@ -22,6 +24,7 @@ import { chatConversationsActions } from "./_legacy-stubs.js";
22
24
  import { RichDocumentActions } from "../host/rich-document-slots.js";
23
25
  import { MessageTimestamp } from "../agents/components/messages-display/MessageTimestamp.js";
24
26
  import { ErrorAlchemyMenu } from "../host/ui-slots.js";
27
+ import { downloadFile } from "@ai-matrx/kit/download";
25
28
  function AssistantMessage({
26
29
  message,
27
30
  sessionId,
@@ -31,6 +34,9 @@ function AssistantMessage({
31
34
  isTtsRequest = false,
32
35
  onContentChange
33
36
  }) {
37
+ const { copyText } = useClipboard({
38
+ notify: (message2, kind) => kind === "error" ? toast.error(message2) : toast.success(message2)
39
+ });
34
40
  const [isAppearing, setIsAppearing] = useState(true);
35
41
  const [isAudioLinkCopied, setIsAudioLinkCopied] = useState(false);
36
42
  const [isDownloading, setIsDownloading] = useState(false);
@@ -97,15 +103,8 @@ function AssistantMessage({
97
103
  try {
98
104
  const resp = await fetch(audioUrl);
99
105
  const blob = await resp.blob();
100
- const objectUrl = URL.createObjectURL(blob);
101
106
  const ext = audioMimeType?.split("/")[1] ?? "wav";
102
- const link = document.createElement("a");
103
- link.href = objectUrl;
104
- link.download = `audio-response.${ext}`;
105
- document.body.appendChild(link);
106
- link.click();
107
- document.body.removeChild(link);
108
- URL.revokeObjectURL(objectUrl);
107
+ downloadFile(`audio-response.${ext}`, blob, blob.type);
109
108
  } catch {
110
109
  } finally {
111
110
  setIsDownloading(false);
@@ -186,8 +185,7 @@ function AssistantMessage({
186
185
  variant: "quiet",
187
186
  onClick: async () => {
188
187
  if (!audioUrl) return;
189
- await navigator.clipboard.writeText(audioUrl).catch(() => {
190
- });
188
+ if (!await copyText(audioUrl)) return;
191
189
  setIsAudioLinkCopied(true);
192
190
  setTimeout(() => setIsAudioLinkCopied(false), 2e3);
193
191
  },
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useClipboard } from "@ai-matrx/kit/clipboard";
4
+ import { toast } from "../host/notify.js";
3
5
  import { useState, useEffect, useRef, useMemo } from "react";
4
6
  import {
5
7
  Check,
@@ -84,6 +86,9 @@ function UserMessage({
84
86
  onContentChange,
85
87
  compact = false
86
88
  }) {
89
+ const { copyText } = useClipboard({
90
+ notify: (message2, kind) => kind === "error" ? toast.error(message2) : toast.success(message2)
91
+ });
87
92
  const [isCollapsed, setIsCollapsed] = useState(true);
88
93
  const [isCopied, setIsCopied] = useState(false);
89
94
  const [shouldBeCollapsible, setShouldBeCollapsible] = useState(false);
@@ -130,7 +135,8 @@ function UserMessage({
130
135
  }, [isEditing]);
131
136
  const handleCopy = (e) => {
132
137
  e.stopPropagation();
133
- navigator.clipboard.writeText(message.content).then(() => {
138
+ copyText(message.content).then((copied) => {
139
+ if (!copied) return;
134
140
  setIsCopied(true);
135
141
  setTimeout(() => setIsCopied(false), 2e3);
136
142
  });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The clipboard seam — plain-text copy for package code outside a React component.
3
+ *
4
+ * The kit `copyText` with its success and failure notices routed through the notify seam, so a
5
+ * refused copy always announces itself. Resolves to whether the copy landed.
6
+ */
7
+ export declare function copyToHostClipboard(text: string, successMessage?: string): Promise<boolean>;
@@ -0,0 +1,11 @@
1
+ import { copyText } from "@ai-matrx/kit/clipboard";
2
+ import { toast } from "./notify.js";
3
+ function copyToHostClipboard(text, successMessage) {
4
+ return copyText(text, {
5
+ successMessage,
6
+ notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
7
+ });
8
+ }
9
+ export {
10
+ copyToHostClipboard
11
+ };
@@ -18887,6 +18887,7 @@ export type ChatDatabase = {
18887
18887
  container_side: string;
18888
18888
  conveys_max: Database["public"]["Enums"]["permission_level"];
18889
18889
  created_at: string;
18890
+ id: string;
18890
18891
  is_active: boolean;
18891
18892
  label: string | null;
18892
18893
  notes: string | null;
@@ -18900,6 +18901,7 @@ export type ChatDatabase = {
18900
18901
  container_side?: string;
18901
18902
  conveys_max?: Database["public"]["Enums"]["permission_level"];
18902
18903
  created_at?: string;
18904
+ id?: string;
18903
18905
  is_active?: boolean;
18904
18906
  label?: string | null;
18905
18907
  notes?: string | null;
@@ -18913,6 +18915,7 @@ export type ChatDatabase = {
18913
18915
  container_side?: string;
18914
18916
  conveys_max?: Database["public"]["Enums"]["permission_level"];
18915
18917
  created_at?: string;
18918
+ id?: string;
18916
18919
  is_active?: boolean;
18917
18920
  label?: string | null;
18918
18921
  notes?: string | null;
@@ -38345,6 +38348,18 @@ export type ChatDatabase = {
38345
38348
  isSetofReturn: true;
38346
38349
  };
38347
38350
  };
38351
+ get_pick_list_for_selection: {
38352
+ Args: {
38353
+ p_list_id: string;
38354
+ };
38355
+ Returns: Json;
38356
+ };
38357
+ get_pick_lists_summary: {
38358
+ Args: {
38359
+ p_user_id: string;
38360
+ };
38361
+ Returns: Json;
38362
+ };
38348
38363
  get_project_members_with_users: {
38349
38364
  Args: {
38350
38365
  p_project_id: string;
@@ -38532,12 +38547,6 @@ export type ChatDatabase = {
38532
38547
  };
38533
38548
  Returns: Json;
38534
38549
  };
38535
- get_pick_list_for_selection: {
38536
- Args: {
38537
- p_list_id: string;
38538
- };
38539
- Returns: Json;
38540
- };
38541
38550
  get_tables_and_columns: {
38542
38551
  Args: never;
38543
38552
  Returns: {
@@ -38695,12 +38704,6 @@ export type ChatDatabase = {
38695
38704
  };
38696
38705
  Returns: Json;
38697
38706
  };
38698
- get_pick_lists_summary: {
38699
- Args: {
38700
- p_user_id: string;
38701
- };
38702
- Returns: Json;
38703
- };
38704
38707
  get_user_messages: {
38705
38708
  Args: {
38706
38709
  p_feedback_id: string;