@ai-matrx/chat 0.6.5 → 0.6.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 (39) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/agents/components/chat/ChatHistorySidebar.d.ts +4 -4
  3. package/dist/agents/components/chat/ChatHistorySidebar.js +2 -2
  4. package/dist/agents/components/chat/ChatSidebarMenu.js +1 -1
  5. package/dist/agents/components/conversation-actions/conversationActionRegistry.d.ts +8 -0
  6. package/dist/agents/components/conversation-actions/conversationActionRegistry.js +11 -4
  7. package/dist/agents/components/conversation-history/ConversationArchiveSection.d.ts +7 -0
  8. package/dist/agents/components/conversation-history/{ConversationTrashSection.js → ConversationArchiveSection.js} +20 -20
  9. package/dist/agents/components/conversation-history/ConversationHistorySidebar.d.ts +29 -10
  10. package/dist/agents/components/conversation-history/ConversationHistorySidebar.js +75 -76
  11. package/dist/agents/components/conversation-history/ConversationPickerWindow.js +1 -2
  12. package/dist/agents/components/inputs/smart-input/RunControlsTabPanel.d.ts +2 -0
  13. package/dist/agents/components/inputs/smart-input/RunControlsTabPanel.js +8 -1
  14. package/dist/agents/components/inputs/smart-input/RunToolPicker.js +2 -1
  15. package/dist/agents/components/run/AgentRunHeader.js +11 -5
  16. package/dist/agents/components/shared/AgentHeaderMobile.js +1 -0
  17. package/dist/agents/components/shared/AgentModeController.js +90 -27
  18. package/dist/agents/components/shared/AgentSelectorIsland.js +2 -1
  19. package/dist/agents/redux/agent-definition/run-tier-record.js +1 -0
  20. package/dist/agents/redux/conversation-history/history-filters.d.ts +6 -0
  21. package/dist/agents/redux/conversation-history/history-filters.js +4 -1
  22. package/dist/agents/redux/conversation-history/selectors.js +2 -3
  23. package/dist/agents/redux/conversation-history/slice.d.ts +6 -9
  24. package/dist/agents/redux/conversation-history/slice.js +14 -23
  25. package/dist/agents/redux/conversation-history/thunks.js +1 -23
  26. package/dist/agents/redux/conversation-history/types.d.ts +12 -12
  27. package/dist/agents/redux/conversation-history/types.js +4 -6
  28. package/dist/agents/redux/conversation-list/conversation-list.selectors.d.ts +6 -0
  29. package/dist/agents/redux/conversation-list/conversation-list.selectors.js +10 -0
  30. package/dist/agents/redux/conversation-list/conversation-list.types.d.ts +6 -0
  31. package/dist/agents/redux/conversation-list/conversation-row-actions.thunks.js +11 -2
  32. package/dist/agents/redux/conversation-list/conversation-trash.thunks.d.ts +11 -4
  33. package/dist/agents/redux/conversation-list/conversation-trash.thunks.js +41 -20
  34. package/dist/agents/redux/execution-system/message-crud/soft-delete-conversation.thunk.js +5 -2
  35. package/dist/window-panels/windows/agents/AgentRunWindow.d.ts +1 -1
  36. package/dist/window-panels/windows/agents/AgentRunWindow.js +27 -21
  37. package/dist/window-panels/windows/agents/RunControlsWindow.js +1 -0
  38. package/package.json +5 -5
  39. package/dist/agents/components/conversation-history/ConversationTrashSection.d.ts +0 -7
@@ -1,5 +1,10 @@
1
1
  /**
2
- * THE TRASH — reading soft-deleted conversations back, and restoring one.
2
+ * THE ARCHIVE — reading archived AND deleted conversations back, and restoring one.
3
+ *
4
+ * One place, called "Archived" (Arman, 2026-10-08): a conversation the person
5
+ * archived (`status = 'archived'`) and one they deleted (`deleted_at` stamped)
6
+ * both land in the same section at the foot of every conversation list, and
7
+ * Restore brings either back. The store fields keep their `trash*` names.
3
8
  *
4
9
  * DD-179. Deleting a conversation was a one-way door: `cx_soft_delete_conversation`
5
10
  * stamps `deleted_at` and every reader filters `deleted_at IS NULL`, so the row
@@ -17,7 +22,7 @@
17
22
  */
18
23
  import type { ChatDispatch, ChatRootState } from "../../../store/root-state.js";
19
24
  import type { ConversationListItem } from "./conversation-list.types.js";
20
- export declare const fetchTrashedConversations: import("@reduxjs/toolkit").AsyncThunk<{
25
+ export declare const fetchArchivedConversations: import("@reduxjs/toolkit").AsyncThunk<{
21
26
  items: ConversationListItem[];
22
27
  }, void | {
23
28
  limit?: number;
@@ -41,8 +46,10 @@ interface RestoreResult {
41
46
  restored: boolean;
42
47
  }
43
48
  /**
44
- * Undo a soft delete. ONE door: `public.cx_restore_conversation`, the exact
45
- * inverse of `cx_soft_delete_conversation`.
49
+ * Bring a row out of the Archived section. A deleted row goes back through
50
+ * ONE door, `public.cx_restore_conversation` (the exact inverse of
51
+ * `cx_soft_delete_conversation`); an archived row goes back through
52
+ * `setConversationArchived`. A row that is both takes both doors.
46
53
  *
47
54
  * `false` back from the RPC means the row is absent, was never deleted, or is
48
55
  * not this caller's to restore — all of which are a refusal, not a success, so
@@ -12,11 +12,12 @@ import {
12
12
  } from "./conversation-list.slice.js";
13
13
  import { upsertConversationIntoScopes } from "../conversation-history/slice.js";
14
14
  import { getUserId } from "../../../host/identity.js";
15
+ import { setConversationArchived } from "./conversation-row-actions.thunks.js";
15
16
  const CONVERSATION_TRASH_PAGE_SIZE_KNOB = {
16
17
  feature: "agents.conversations",
17
18
  key: "trash_page_size"
18
19
  };
19
- const fetchTrashedConversations = createAsyncThunk(
20
+ const fetchArchivedConversations = createAsyncThunk(
20
21
  "conversationList/fetchTrashed",
21
22
  async (rawArgs, { dispatch, getState, rejectWithValue }) => {
22
23
  dispatch(setTrashLoading());
@@ -43,18 +44,18 @@ const fetchTrashedConversations = createAsyncThunk(
43
44
  }
44
45
  limit = resolved;
45
46
  } catch (error2) {
46
- const message = `The trash could not be opened: the setting that decides how many deleted conversations one page shows ("${CONVERSATION_TRASH_PAGE_SIZE_KNOB.feature}.${CONVERSATION_TRASH_PAGE_SIZE_KNOB.key}") could not be read \u2014 ${error2 instanceof Error ? error2.message : String(error2)}.`;
47
+ const message = `The archive could not be opened: the setting that decides how many archived conversations one page shows ("${CONVERSATION_TRASH_PAGE_SIZE_KNOB.feature}.${CONVERSATION_TRASH_PAGE_SIZE_KNOB.key}") could not be read \u2014 ${error2 instanceof Error ? error2.message : String(error2)}.`;
47
48
  dispatch(setTrashError(message));
48
49
  return rejectWithValue({ message });
49
50
  }
50
51
  }
51
52
  let query = supabase.schema("chat").from("conversation").select(
52
53
  "id, title, description, status, message_count, initial_agent_id, last_model_id, source_app, source_feature, created_at, updated_at, deleted_at, exclude_from_kg"
53
- ).not("deleted_at", "is", null).eq("is_ephemeral", false).order("deleted_at", { ascending: false }).limit(limit);
54
+ ).or("deleted_at.not.is.null,status.eq.archived").eq("is_ephemeral", false).order("updated_at", { ascending: false }).limit(limit);
54
55
  if (viewerId) query = query.eq("created_by", viewerId);
55
56
  const { data, error } = await query;
56
57
  if (error) {
57
- const message = operationFailed("open the trash", error).message;
58
+ const message = operationFailed("open the archive", error).message;
58
59
  dispatch(setTrashError(message));
59
60
  return rejectWithValue({ message });
60
61
  }
@@ -73,7 +74,8 @@ const fetchTrashedConversations = createAsyncThunk(
73
74
  agentId: row.initial_agent_id ?? null,
74
75
  lastModelId: row.last_model_id ?? null,
75
76
  sourceApp: row.source_app ?? void 0,
76
- sourceFeature: row.source_feature ?? void 0
77
+ sourceFeature: row.source_feature ?? void 0,
78
+ deletedAt: row.deleted_at ?? null
77
79
  }));
78
80
  dispatch(setTrashSuccess({ items }));
79
81
  return { items };
@@ -82,27 +84,46 @@ const fetchTrashedConversations = createAsyncThunk(
82
84
  const restoreConversation = createAsyncThunk(
83
85
  "conversationList/restore",
84
86
  async ({ conversationId }, { dispatch, getState, rejectWithValue }) => {
85
- const { data, error } = await supabase.rpc("cx_restore_conversation", {
86
- p_conversation_id: conversationId
87
- });
88
- if (error) {
89
- return rejectWithValue({
90
- message: operationFailed("restore this conversation", error).message
87
+ const item = getState().conversationList.trashByConversationId[conversationId];
88
+ const wasDeleted = !item || Boolean(item.deletedAt);
89
+ const wasArchived = item?.status === "archived";
90
+ if (wasDeleted) {
91
+ const { data, error } = await supabase.rpc("cx_restore_conversation", {
92
+ p_conversation_id: conversationId
91
93
  });
94
+ if (error) {
95
+ return rejectWithValue({
96
+ message: operationFailed("restore this conversation", error).message
97
+ });
98
+ }
99
+ if (data !== true) {
100
+ return rejectWithValue({
101
+ message: operationFailed("restore this conversation").message
102
+ });
103
+ }
92
104
  }
93
- if (data !== true) {
94
- return rejectWithValue({
95
- message: operationFailed("restore this conversation").message
96
- });
105
+ if (wasArchived) {
106
+ const unarchived = await dispatch(
107
+ setConversationArchived({ conversationId, archived: false })
108
+ );
109
+ if (setConversationArchived.rejected.match(unarchived)) {
110
+ return rejectWithValue({
111
+ message: unarchived.payload?.message ?? operationFailed("restore this conversation").message
112
+ });
113
+ }
97
114
  }
98
- const item = getState().conversationList.trashByConversationId[conversationId];
99
115
  dispatch(removeFromTrash(conversationId));
100
116
  if (item) {
101
- dispatch(prependConversation(item));
117
+ const row = {
118
+ ...item,
119
+ status: wasArchived ? "active" : item.status,
120
+ deletedAt: null
121
+ };
122
+ dispatch(prependConversation(row));
102
123
  dispatch(
103
124
  upsertConversationIntoScopes({
104
- row: item,
105
- agentId: item.agentId ?? ""
125
+ row,
126
+ agentId: row.agentId ?? ""
106
127
  })
107
128
  );
108
129
  }
@@ -110,6 +131,6 @@ const restoreConversation = createAsyncThunk(
110
131
  }
111
132
  );
112
133
  export {
113
- fetchTrashedConversations,
134
+ fetchArchivedConversations,
114
135
  restoreConversation
115
136
  };
@@ -9,12 +9,13 @@ import {
9
9
  addToTrash
10
10
  } from "../../conversation-list/conversation-list.slice.js";
11
11
  import { removeConversationFromScopes } from "../../conversation-history/slice.js";
12
+ import { findConversationRow } from "../../conversation-list/conversation-list.selectors.js";
12
13
  import { clearCacheBypass } from "./cache-bypass.slice.js";
13
14
  import { invalidateConversationCache } from "./invalidate-conversation-cache.thunk.js";
14
15
  const softDeleteConversation = createAsyncThunk(
15
16
  "conversations/softDelete",
16
17
  async ({ conversationId }, { dispatch, getState, rejectWithValue }) => {
17
- const snapshot = getState().conversationList.byConversationId[conversationId] ?? null;
18
+ const snapshot = findConversationRow(getState(), conversationId);
18
19
  const { data, error } = await supabase.rpc("cx_soft_delete_conversation", {
19
20
  p_conversation_id: conversationId
20
21
  });
@@ -31,7 +32,9 @@ const softDeleteConversation = createAsyncThunk(
31
32
  }
32
33
  void dispatch(invalidateConversationCache({ conversationId }));
33
34
  dispatch(clearCacheBypass(conversationId));
34
- if (snapshot) dispatch(addToTrash(snapshot));
35
+ if (snapshot) {
36
+ dispatch(addToTrash({ ...snapshot, deletedAt: (/* @__PURE__ */ new Date()).toISOString() }));
37
+ }
35
38
  dispatch(removeFromConversationList(conversationId));
36
39
  dispatch(removeConversationFromScopes({ conversationId }));
37
40
  dispatch(clearMessages(conversationId));
@@ -5,7 +5,7 @@ import type { AnyMandateKey } from "@ai-matrx/agents/mandates";
5
5
  * Floating-window equivalent of `/agents/[id]/run`.
6
6
  *
7
7
  * ┌──────────────────────────────────────────────────────────────┐
8
- * │ ▼ Agent ▾ Run · Agent Name ⊕ ✕ │ ← WindowPanel title bar
8
+ * │ ●●● ◧ ✎ Chat title ▾ (or agent name) │ ← WindowPanel title bar
9
9
  * ├──────────────┬───────────────────────────────────────────────┤
10
10
  * │ Conversations│ AgentConversationColumn (main experience) │
11
11
  * │ (sidebar) │ │
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { logFailure } from "../../../ui/expectedRefusal.js";
4
4
  import { useCallback, useEffect, useRef, useState } from "react";
5
- import { AlertTriangle, Brain, Loader2, Plus, RotateCw } from "lucide-react";
5
+ import { AlertTriangle, Brain, Loader2, RotateCw, SquarePen } from "lucide-react";
6
6
  import { Button } from "@ai-matrx/design-system/controls";
7
7
  import { WindowPanel } from "../../../host/ui-slots.js";
8
8
  import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
@@ -65,7 +65,7 @@ const AGENT_RUN_WINDOW_POSITIONS = [
65
65
  "bottom-right",
66
66
  "bottom-left"
67
67
  ];
68
- const HEADER_NEW_RUN_BTN = "flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-primary p-0 text-primary-foreground shadow-sm transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-40 [&_svg]:h-2.5 [&_svg]:w-2.5";
68
+ const HEADER_NEW_RUN_BTN = "flex shrink-0 items-center justify-center rounded p-0.5 text-foreground/60 transition-colors hover:bg-accent/60 hover:text-foreground disabled:opacity-40 [&_svg]:h-3.5 [&_svg]:w-3.5";
69
69
  function SessionChatRow({
70
70
  chat,
71
71
  active,
@@ -155,7 +155,6 @@ function AgentRunWindowSidebar({
155
155
  openInPlace: true,
156
156
  surfaceKey: surfaceKey ?? void 0,
157
157
  getConversationHref,
158
- historyLabel: "Agent history",
159
158
  topSlot: sessionSection,
160
159
  className: "bg-transparent"
161
160
  }
@@ -187,28 +186,34 @@ function AgentRunWindowNewRunButton({
187
186
  title: "New chat",
188
187
  "aria-label": "New chat",
189
188
  className: HEADER_NEW_RUN_BTN,
190
- children: /* @__PURE__ */ jsx(Plus, { strokeWidth: 2.5 })
189
+ children: /* @__PURE__ */ jsx(SquarePen, {})
191
190
  }
192
191
  );
193
192
  }
194
193
  function WindowTitleContent({
195
194
  agentId,
196
- displayName,
195
+ agentName,
196
+ conversationId,
197
197
  onAgentSelect
198
198
  }) {
199
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
200
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground shrink-0", children: "Run" }),
201
- /* @__PURE__ */ jsx(
202
- AgentListDropdown,
203
- {
204
- onSelect: onAgentSelect,
205
- label: agentId ? displayName : "Select agent\u2026",
206
- noBorder: true,
207
- compact: true,
208
- className: "max-w-[180px] md:max-w-[240px] rounded-none bg-transparent"
209
- }
210
- )
211
- ] });
199
+ const listTitle = useAppSelector(
200
+ (state) => conversationId ? selectConversationListItemById(conversationId)(state)?.title ?? null : null
201
+ );
202
+ const localTitle = useAppSelector(
203
+ (state) => conversationId ? state.messages.byConversationId[conversationId]?.title ?? null : null
204
+ );
205
+ const conversationTitle = (listTitle ?? localTitle)?.trim() || null;
206
+ const label = !agentId ? "Select agent\u2026" : conversationTitle ?? agentName;
207
+ return /* @__PURE__ */ jsx(
208
+ AgentListDropdown,
209
+ {
210
+ onSelect: onAgentSelect,
211
+ label,
212
+ noBorder: true,
213
+ compact: true,
214
+ className: "max-w-[260px] md:max-w-[360px] rounded-none bg-transparent"
215
+ }
216
+ );
212
217
  }
213
218
  function AgentRunBody({
214
219
  agentId,
@@ -450,7 +455,7 @@ function AgentRunBodyMenu({
450
455
  {
451
456
  sourceFeature: "agent-builder",
452
457
  contentSource: { type: "raw" },
453
- entity: { type: "conversation", id: conversationId, title: agentName ?? "Run" },
458
+ entity: { type: "conversation", id: conversationId, title: agentName ?? "Chat" },
454
459
  extraSections: [agentSection],
455
460
  children
456
461
  }
@@ -616,7 +621,8 @@ function AgentRunWindowInner({
616
621
  WindowTitleContent,
617
622
  {
618
623
  agentId,
619
- displayName: agentName,
624
+ agentName,
625
+ conversationId: activeConversationId,
620
626
  onAgentSelect: (id) => handleAgentSelect(id)
621
627
  }
622
628
  ),
@@ -635,7 +641,7 @@ function AgentRunWindowInner({
635
641
  ...addressedConversationId ? { [AGENT_RUN_WINDOW_CONVERSATION_ARG]: addressedConversationId } : {}
636
642
  },
637
643
  onCollectData: collectData,
638
- actionsRight: agentId && surfaceKey ? /* @__PURE__ */ jsx(
644
+ actionsLeft: agentId && surfaceKey ? /* @__PURE__ */ jsx(
639
645
  AgentRunWindowNewRunButton,
640
646
  {
641
647
  surfaceKey,
@@ -86,6 +86,7 @@ function RunControlsWindowInner({
86
86
  id: "run-controls-window",
87
87
  overlayId: OVERLAY_ID,
88
88
  onClose,
89
+ closeOnEscape: true,
89
90
  title: "Chat Options",
90
91
  initialRect: roomy ? { x: 72, y: 56, width: 980, height: 680 } : { x: 72, y: 56, width: 480, height: 640 },
91
92
  height: roomy ? 680 : 640,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/chat",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
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. One chat, one renderer: the old kernel entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) are retired.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -172,19 +172,19 @@
172
172
  "@ai-matrx/browser-audio": "latest",
173
173
  "@ai-matrx/canvas": "latest",
174
174
  "@ai-matrx/content-ir": "latest",
175
- "@ai-matrx/content-ir-react": "latest",
176
175
  "@ai-matrx/data": "latest",
177
176
  "@ai-matrx/design-system": "latest",
178
177
  "@ai-matrx/diff": "latest",
179
178
  "@ai-matrx/icons": "latest",
179
+ "@ai-matrx/content-ir-react": "latest",
180
180
  "@ai-matrx/kit": "latest",
181
181
  "@ai-matrx/media": "latest",
182
182
  "@ai-matrx/print": "latest",
183
183
  "@ai-matrx/realtime": "latest",
184
- "@ai-matrx/records": "latest",
185
184
  "@ai-matrx/records-ui": "latest",
186
185
  "@ai-matrx/rich-content": "latest",
187
- "@ai-matrx/rich-editor": "latest"
186
+ "@ai-matrx/rich-editor": "latest",
187
+ "@ai-matrx/records": "latest"
188
188
  },
189
189
  "devDependencies": {
190
190
  "@arethetypeswrong/cli": "^0.18.5",
@@ -300,5 +300,5 @@
300
300
  "check:side-panel-bundle": "node ./scripts/check-side-panel-bundle.mjs",
301
301
  "check:side-effects": "node ../scripts/check-side-effects-listed.mjs . && node ../scripts/check-side-effects-listed.mjs --self-test"
302
302
  },
303
- "gitHead": "4aeaa8a60ac16fdf4f22e712d199bfce23f523ac"
303
+ "gitHead": "5ddae9857dcebf5690d1b00678ce3b7d1daef758"
304
304
  }
@@ -1,7 +0,0 @@
1
- interface ConversationTrashSectionProps {
2
- /** `"consumer"` matches the comfortable /chat sidebar; `"dense"` the rest. */
3
- variant?: "dense" | "consumer";
4
- className?: string;
5
- }
6
- export declare function ConversationTrashSection({ variant, className, }: ConversationTrashSectionProps): import("react").JSX.Element;
7
- export default ConversationTrashSection;