@ai-matrx/chat 0.6.4 → 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.
- package/CHANGELOG.md +55 -0
- package/dist/agents/components/chat/ChatHistorySidebar.d.ts +4 -4
- package/dist/agents/components/chat/ChatHistorySidebar.js +2 -2
- package/dist/agents/components/chat/ChatSidebarMenu.js +1 -1
- package/dist/agents/components/conversation-actions/conversationActionRegistry.d.ts +8 -0
- package/dist/agents/components/conversation-actions/conversationActionRegistry.js +11 -4
- package/dist/agents/components/conversation-history/ConversationArchiveSection.d.ts +7 -0
- package/dist/agents/components/conversation-history/{ConversationTrashSection.js → ConversationArchiveSection.js} +20 -20
- package/dist/agents/components/conversation-history/ConversationHistorySidebar.d.ts +29 -10
- package/dist/agents/components/conversation-history/ConversationHistorySidebar.js +75 -76
- package/dist/agents/components/conversation-history/ConversationPickerWindow.js +1 -2
- package/dist/agents/components/inputs/smart-input/RunControlsTabPanel.d.ts +2 -0
- package/dist/agents/components/inputs/smart-input/RunControlsTabPanel.js +8 -1
- package/dist/agents/components/inputs/smart-input/RunToolPicker.js +2 -1
- package/dist/agents/components/run/AgentRunHeader.js +11 -5
- package/dist/agents/components/shared/AgentHeaderMobile.js +1 -0
- package/dist/agents/components/shared/AgentModeController.js +90 -27
- package/dist/agents/components/shared/AgentSelectorIsland.js +2 -1
- package/dist/agents/redux/agent-definition/run-tier-record.js +1 -0
- package/dist/agents/redux/conversation-history/history-filters.d.ts +6 -0
- package/dist/agents/redux/conversation-history/history-filters.js +4 -1
- package/dist/agents/redux/conversation-history/selectors.js +2 -3
- package/dist/agents/redux/conversation-history/slice.d.ts +6 -9
- package/dist/agents/redux/conversation-history/slice.js +14 -23
- package/dist/agents/redux/conversation-history/thunks.js +1 -23
- package/dist/agents/redux/conversation-history/types.d.ts +12 -12
- package/dist/agents/redux/conversation-history/types.js +4 -6
- package/dist/agents/redux/conversation-list/conversation-list.selectors.d.ts +6 -0
- package/dist/agents/redux/conversation-list/conversation-list.selectors.js +10 -0
- package/dist/agents/redux/conversation-list/conversation-list.types.d.ts +6 -0
- package/dist/agents/redux/conversation-list/conversation-row-actions.thunks.js +11 -2
- package/dist/agents/redux/conversation-list/conversation-trash.thunks.d.ts +11 -4
- package/dist/agents/redux/conversation-list/conversation-trash.thunks.js +41 -20
- package/dist/agents/redux/execution-system/message-crud/soft-delete-conversation.thunk.js +5 -2
- package/dist/window-panels/windows/agents/AgentRunWindow.d.ts +1 -1
- package/dist/window-panels/windows/agents/AgentRunWindow.js +27 -21
- package/dist/window-panels/windows/agents/RunControlsWindow.js +1 -0
- package/package.json +6 -6
- package/dist/agents/components/conversation-history/ConversationTrashSection.d.ts +0 -7
|
@@ -153,6 +153,9 @@ function useRunControlsState(conversationId, includeAttach) {
|
|
|
153
153
|
if (tabId === "tools" && addedCount > 0) {
|
|
154
154
|
return /* @__PURE__ */ jsx(Badge, { tone: "primary", children: addedCount });
|
|
155
155
|
}
|
|
156
|
+
if (tabId === "skills" && addedSkillsCount > 0) {
|
|
157
|
+
return /* @__PURE__ */ jsx(Badge, { tone: "primary", children: addedSkillsCount });
|
|
158
|
+
}
|
|
156
159
|
if (tabId === "model" && hasModelOverride) {
|
|
157
160
|
return /* @__PURE__ */ jsx(TabStatusDot, { label: "overridden" });
|
|
158
161
|
}
|
|
@@ -169,7 +172,11 @@ function useRunControlsState(conversationId, includeAttach) {
|
|
|
169
172
|
defaultTab,
|
|
170
173
|
resolveTab,
|
|
171
174
|
tabTrailing,
|
|
172
|
-
|
|
175
|
+
// EVERYTHING the person added to this run (tools + skills): the + control's
|
|
176
|
+
// label and tint read this one number, so Skills count exactly like Tools.
|
|
177
|
+
addedCount: addedCount + addedSkillsCount,
|
|
178
|
+
addedToolsCount: addedCount,
|
|
179
|
+
addedSkillsCount,
|
|
173
180
|
hasModelOverride,
|
|
174
181
|
hasActiveContext,
|
|
175
182
|
anyDocActive,
|
|
@@ -38,6 +38,7 @@ import { DEFAULT_BUILDER_ADVANCED_SETTINGS } from "../../../types/instance.types
|
|
|
38
38
|
import { filterAndSortBySearch } from "@ai-matrx/kit/search-scoring";
|
|
39
39
|
import { getToolDisplayName } from "../../../../tool-call-visualization/registry/registry.js";
|
|
40
40
|
import { groupToolCatalog, toolCategoryLabel } from "./run-tool-catalog.js";
|
|
41
|
+
import { isBundleListerName } from "@ai-matrx/agents/tools";
|
|
41
42
|
import { loadAvailableTools, selectAllTools, selectToolsError, selectToolsStatus, useToolCatalog } from "../../../identity/tool-catalog.js";
|
|
42
43
|
function RunToolPicker({ conversationId }) {
|
|
43
44
|
const dispatch = useAppDispatch();
|
|
@@ -123,7 +124,7 @@ function RunToolPicker({ conversationId }) {
|
|
|
123
124
|
}
|
|
124
125
|
})
|
|
125
126
|
);
|
|
126
|
-
const list = tools ?? [];
|
|
127
|
+
const list = (tools ?? []).filter((t) => !isBundleListerName(t.name));
|
|
127
128
|
const toolMap = new Map(list.map((t) => [t.id, t]));
|
|
128
129
|
const builtInIds = Array.isArray(agentToolIds) ? agentToolIds : [];
|
|
129
130
|
const agentOwned = new Set(builtInIds);
|
|
@@ -15,7 +15,7 @@ function AgentRunHeader({
|
|
|
15
15
|
currentPath
|
|
16
16
|
}) {
|
|
17
17
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
18
|
+
/* @__PURE__ */ jsx("div", { className: "w-full @min-[44rem]/shell-header:hidden", children: /* @__PURE__ */ jsx(
|
|
19
19
|
AgentHeaderMobile,
|
|
20
20
|
{
|
|
21
21
|
agentId,
|
|
@@ -24,8 +24,8 @@ function AgentRunHeader({
|
|
|
24
24
|
extraActions: /* @__PURE__ */ jsx(AgentNewRunButton, { surfaceKey })
|
|
25
25
|
}
|
|
26
26
|
) }),
|
|
27
|
-
/* @__PURE__ */ jsxs("div", { className: "@container/agent-header hidden
|
|
28
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
|
|
27
|
+
/* @__PURE__ */ jsxs("div", { className: "@container/agent-header hidden @min-[44rem]/shell-header:flex items-center justify-between w-full min-w-0 gap-2", children: [
|
|
28
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 shrink items-center", children: [
|
|
29
29
|
/* @__PURE__ */ jsx(ChevronLeftTapButton, { variant: "transparent", href: backHref, "aria-label": "Back to Agents" }),
|
|
30
30
|
/* @__PURE__ */ jsx("div", { className: "flex min-w-0 items-center ps-[calc(var(--matrx-tap-gap)/2)]", children: /* @__PURE__ */ jsx(
|
|
31
31
|
AgentSelectorIsland,
|
|
@@ -42,7 +42,7 @@ function AgentRunHeader({
|
|
|
42
42
|
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "w-[calc(var(--matrx-tap-gap)/2)] shrink-0" }),
|
|
43
43
|
/* @__PURE__ */ jsx(AgentNewRunButton, { surfaceKey })
|
|
44
44
|
] }),
|
|
45
|
-
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
45
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsx(
|
|
46
46
|
AgentModeController,
|
|
47
47
|
{
|
|
48
48
|
agentId,
|
|
@@ -51,7 +51,13 @@ function AgentRunHeader({
|
|
|
51
51
|
}
|
|
52
52
|
) }),
|
|
53
53
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 pt-0.5 shrink-0", children: [
|
|
54
|
-
/* @__PURE__ */ jsx(
|
|
54
|
+
/* @__PURE__ */ jsx(
|
|
55
|
+
AgentSaveStatus,
|
|
56
|
+
{
|
|
57
|
+
agentId,
|
|
58
|
+
versionClassName: "hidden @min-[40rem]/agent-header:inline"
|
|
59
|
+
}
|
|
60
|
+
),
|
|
55
61
|
basePath === "/agents" && /* @__PURE__ */ jsx(ReviewAnswersLink, { agentId }),
|
|
56
62
|
/* @__PURE__ */ jsx(AgentOptionsMenu, { agentId, basePath })
|
|
57
63
|
] })
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useTransition } from "react";
|
|
3
|
+
import { useRef, useState, useTransition } from "react";
|
|
4
4
|
import { useRouter, usePathname, Link } from "../../../host/navigation.js";
|
|
5
5
|
import {
|
|
6
6
|
Activity,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
History,
|
|
10
10
|
Layers,
|
|
11
11
|
LayoutGrid,
|
|
12
|
+
MoreHorizontal,
|
|
12
13
|
Pencil,
|
|
13
14
|
Play,
|
|
14
15
|
Plus,
|
|
@@ -26,7 +27,11 @@ import {
|
|
|
26
27
|
AlertDialogDescription,
|
|
27
28
|
AlertDialogFooter,
|
|
28
29
|
AlertDialogHeader,
|
|
29
|
-
AlertDialogTitle
|
|
30
|
+
AlertDialogTitle,
|
|
31
|
+
DropdownMenu,
|
|
32
|
+
DropdownMenuContent,
|
|
33
|
+
DropdownMenuItem,
|
|
34
|
+
DropdownMenuTrigger
|
|
30
35
|
} from "@ai-matrx/design-system";
|
|
31
36
|
import {
|
|
32
37
|
NavItemTooltip,
|
|
@@ -46,6 +51,20 @@ const MODES = [
|
|
|
46
51
|
{ id: "widgets", label: "Widgets", icon: LayoutGrid },
|
|
47
52
|
{ id: "new", label: "New", icon: Plus }
|
|
48
53
|
];
|
|
54
|
+
const INLINE_FROM = {
|
|
55
|
+
run: "",
|
|
56
|
+
edit: "hidden @min-[500px]/agent-header:flex",
|
|
57
|
+
view: "hidden @min-[540px]/agent-header:flex",
|
|
58
|
+
runs: "hidden @min-[580px]/agent-header:flex",
|
|
59
|
+
versions: "hidden @min-[620px]/agent-header:flex",
|
|
60
|
+
hindsight: "hidden @min-[660px]/agent-header:flex",
|
|
61
|
+
shortcuts: "hidden @min-[700px]/agent-header:flex",
|
|
62
|
+
apps: "hidden @min-[740px]/agent-header:flex",
|
|
63
|
+
surfaces: "hidden @min-[780px]/agent-header:flex",
|
|
64
|
+
widgets: "hidden @min-[820px]/agent-header:flex",
|
|
65
|
+
new: "hidden @min-[860px]/agent-header:flex"
|
|
66
|
+
};
|
|
67
|
+
const MORE_HIDDEN_FROM = "@min-[860px]/agent-header:hidden";
|
|
49
68
|
function getAgentModeHref(mode, agentId, basePath) {
|
|
50
69
|
if (mode === "new") return `${basePath}/new`;
|
|
51
70
|
const map = {
|
|
@@ -112,32 +131,76 @@ function AgentModeController({
|
|
|
112
131
|
navigateTo(getAgentModeHref(next, agentId, basePath));
|
|
113
132
|
};
|
|
114
133
|
const getModeHref = (id) => getAgentModeHref(id, agentId, basePath);
|
|
134
|
+
const inlineRefs = useRef({});
|
|
135
|
+
const [folded, setFolded] = useState([]);
|
|
136
|
+
const readFolded = () => setFolded(
|
|
137
|
+
MODES.map((m) => m.id).filter((id) => {
|
|
138
|
+
const el = inlineRefs.current[id];
|
|
139
|
+
return !el || el.getClientRects().length === 0;
|
|
140
|
+
})
|
|
141
|
+
);
|
|
115
142
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
116
|
-
/* @__PURE__ */ jsx(NavTooltipProvider, { children: /* @__PURE__ */
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
/* @__PURE__ */ jsx(NavTooltipProvider, { children: /* @__PURE__ */ jsxs("div", { className: "matrx-glass-thin-border flex items-center gap-0 rounded-full p-0.5", children: [
|
|
144
|
+
MODES.map(({ id, label, icon: Icon }) => {
|
|
145
|
+
const isActive = id === mode;
|
|
146
|
+
return /* @__PURE__ */ jsx(NavItemTooltip, { label, children: /* @__PURE__ */ jsxs(
|
|
147
|
+
Link,
|
|
148
|
+
{
|
|
149
|
+
ref: (el) => {
|
|
150
|
+
inlineRefs.current[id] = el;
|
|
151
|
+
},
|
|
152
|
+
href: getModeHref(id),
|
|
153
|
+
onClick: (e) => {
|
|
154
|
+
if (e.metaKey || e.ctrlKey) return;
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
handleModeChange(id);
|
|
157
|
+
},
|
|
158
|
+
"aria-label": label,
|
|
159
|
+
className: cn(
|
|
160
|
+
"flex items-center justify-center gap-1 py-0.5 text-[0.6875rem] font-medium rounded-full transition-colors cursor-pointer",
|
|
161
|
+
"px-2.5",
|
|
162
|
+
"[&_svg]:w-3.5 [&_svg]:h-3.5",
|
|
163
|
+
isActive ? NAV_ITEM_SELECTED : NAV_ITEM_UNSELECTED,
|
|
164
|
+
!isActive && INLINE_FROM[id]
|
|
165
|
+
),
|
|
166
|
+
children: [
|
|
167
|
+
/* @__PURE__ */ jsx(Icon, {}),
|
|
168
|
+
/* @__PURE__ */ jsx("span", { className: "hidden @min-[1240px]/agent-header:inline", children: label })
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
) }, id);
|
|
172
|
+
}),
|
|
173
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { onOpenChange: (open) => open && readFolded(), children: [
|
|
174
|
+
/* @__PURE__ */ jsx(NavItemTooltip, { label: "More", children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
175
|
+
"button",
|
|
176
|
+
{
|
|
177
|
+
type: "button",
|
|
178
|
+
"aria-label": "More modes",
|
|
179
|
+
className: cn(
|
|
180
|
+
"flex items-center justify-center py-0.5 px-2.5 rounded-full transition-colors cursor-pointer",
|
|
181
|
+
"[&_svg]:w-3.5 [&_svg]:h-3.5",
|
|
182
|
+
NAV_ITEM_UNSELECTED,
|
|
183
|
+
MORE_HIDDEN_FROM
|
|
184
|
+
),
|
|
185
|
+
children: /* @__PURE__ */ jsx(MoreHorizontal, {})
|
|
186
|
+
}
|
|
187
|
+
) }) }),
|
|
188
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", className: "min-w-40", children: MODES.filter((m) => folded.includes(m.id)).map(
|
|
189
|
+
({ id, label, icon: Icon }) => /* @__PURE__ */ jsxs(
|
|
190
|
+
DropdownMenuItem,
|
|
191
|
+
{
|
|
192
|
+
onSelect: () => handleModeChange(id),
|
|
193
|
+
className: "gap-2",
|
|
194
|
+
children: [
|
|
195
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground" }),
|
|
196
|
+
label
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
id
|
|
200
|
+
)
|
|
201
|
+
) })
|
|
202
|
+
] })
|
|
203
|
+
] }) }),
|
|
141
204
|
/* @__PURE__ */ jsx(AlertDialog, { open: showDirtyDialog, onOpenChange: setShowDirtyDialog, children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
|
|
142
205
|
/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
|
|
143
206
|
/* @__PURE__ */ jsx(AlertDialogTitle, { children: "Unsaved Changes" }),
|
|
@@ -44,7 +44,8 @@ function AgentSelectorIsland({
|
|
|
44
44
|
label: displayName,
|
|
45
45
|
activeAgentId: agentId,
|
|
46
46
|
triggerSlot,
|
|
47
|
-
noBorder: true
|
|
47
|
+
noBorder: true,
|
|
48
|
+
className: "min-w-0 shrink"
|
|
48
49
|
}
|
|
49
50
|
),
|
|
50
51
|
showVersion && /* @__PURE__ */ jsxs("span", { className: "text-[0.625rem] font-medium text-[var(--shell-nav-text)] tabular-nums shrink-0", children: [
|
|
@@ -45,6 +45,7 @@ function completeAgentRecord(record, state) {
|
|
|
45
45
|
if (unread("contextPolicies")) fill("contextPolicies", rt.context_policies ?? []);
|
|
46
46
|
if (unread("autoContextDisabled")) fill("autoContextDisabled", rt.auto_context_disabled === true);
|
|
47
47
|
if (rt.model_id && unread("modelId")) fill("modelId", rt.model_id);
|
|
48
|
+
if (rt.settings && unread("settings")) fill("settings", rt.settings);
|
|
48
49
|
if (unread("uiGates")) fill("uiGates", rt.ui_gates ?? {});
|
|
49
50
|
if (rt.tool_ids && unread("tools")) fill("tools", rt.tool_ids);
|
|
50
51
|
if (!out.name && unread("name")) fill("name", rt.name);
|
|
@@ -15,6 +15,12 @@ export interface HistoryFilterInput {
|
|
|
15
15
|
includeOriginClasses: string[];
|
|
16
16
|
/** Lane gate. `null` = no gate; `[]` = every lane off. */
|
|
17
17
|
includeLanes: ConversationLane[] | null;
|
|
18
|
+
/**
|
|
19
|
+
* Only these conversations (`id in (...)`): a surface whose list is "the
|
|
20
|
+
* conversations that belong to THIS thing" (a board's). `null`/absent = no
|
|
21
|
+
* restriction; `[]` = none belong, so nothing is fetched.
|
|
22
|
+
*/
|
|
23
|
+
onlyConversationIds?: string[] | null;
|
|
18
24
|
}
|
|
19
25
|
/** The slice of the PostgREST filter builder the history filters use. */
|
|
20
26
|
export interface HistoryFilterable<Q> {
|
|
@@ -4,7 +4,9 @@ function excludeHiddenLane(query) {
|
|
|
4
4
|
}
|
|
5
5
|
function applyHistoryFilters(query, f) {
|
|
6
6
|
if (f.includeLanes !== null && f.includeLanes.length === 0) return null;
|
|
7
|
+
if (f.onlyConversationIds && f.onlyConversationIds.length === 0) return null;
|
|
7
8
|
let q = query;
|
|
9
|
+
if (f.onlyConversationIds) q = q.in("id", f.onlyConversationIds);
|
|
8
10
|
if (f.includeLanes !== null) {
|
|
9
11
|
q = q.in("lane", f.includeLanes);
|
|
10
12
|
} else {
|
|
@@ -39,7 +41,8 @@ function historyFilterInputFromScope(scope, overrides = {}) {
|
|
|
39
41
|
includeSourceApps: overrides.includeSourceApps ?? scope.includeSourceApps,
|
|
40
42
|
includeEmptySource: overrides.includeEmptySource ?? scope.includeEmptySource,
|
|
41
43
|
includeOriginClasses: scope.includeOriginClasses ?? [],
|
|
42
|
-
includeLanes: scope.includeLanes ?? null
|
|
44
|
+
includeLanes: scope.includeLanes ?? null,
|
|
45
|
+
onlyConversationIds: scope.onlyConversationIds ?? null
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
48
|
export {
|
|
@@ -2,7 +2,7 @@ import { createSelector } from "@reduxjs/toolkit";
|
|
|
2
2
|
import { idMatchesQuery } from "@ai-matrx/kit/search-scoring";
|
|
3
3
|
import {
|
|
4
4
|
defaultScopeState,
|
|
5
|
-
|
|
5
|
+
isLiveConversationRow
|
|
6
6
|
} from "./types.js";
|
|
7
7
|
import {
|
|
8
8
|
CONVERSATION_LANES,
|
|
@@ -35,9 +35,8 @@ const makeSelectConversationHistoryScope = (scopeId) => createSelector(selectCon
|
|
|
35
35
|
const makeSelectConversationHistoryItems = (scopeId) => {
|
|
36
36
|
const selectScope = makeSelectConversationHistoryScope(scopeId);
|
|
37
37
|
return createSelector(selectScope, (scope) => {
|
|
38
|
-
const view = scope.archiveView ?? "active";
|
|
39
38
|
const inView = scope.items.filter(
|
|
40
|
-
(item) =>
|
|
39
|
+
(item) => isLiveConversationRow(item.status)
|
|
41
40
|
);
|
|
42
41
|
const term = scope.searchTerm.trim().toLowerCase();
|
|
43
42
|
if (!term) return inView;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConversationListItem } from "../conversation-list/conversation-list.types.js";
|
|
2
|
-
import { type ConversationHistoryState, type HistoryGrouping, type
|
|
2
|
+
import { type ConversationHistoryState, type HistoryGrouping, type HistoryStatus, type SourceFacet, type SourceFacetsStatus } from "./types.js";
|
|
3
3
|
import { type ConversationLane } from "./lanes.js";
|
|
4
4
|
export declare const configureScope: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
5
5
|
scopeId: string;
|
|
@@ -21,7 +21,10 @@ export declare const configureScope: import("@reduxjs/toolkit").ActionCreatorWit
|
|
|
21
21
|
}, "conversationHistory/configureScope">, setScopeAgentIds: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
22
22
|
scopeId: string;
|
|
23
23
|
agentIds: string[];
|
|
24
|
-
}, "conversationHistory/setScopeAgentIds">,
|
|
24
|
+
}, "conversationHistory/setScopeAgentIds">, setScopeOnlyConversationIds: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
25
|
+
scopeId: string;
|
|
26
|
+
ids: string[] | null;
|
|
27
|
+
}, "conversationHistory/setScopeOnlyConversationIds">, setScopeSourceFilter: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
25
28
|
scopeId: string;
|
|
26
29
|
includeSourceFeatures: string[];
|
|
27
30
|
includeSourceApps: string[];
|
|
@@ -58,13 +61,7 @@ export declare const configureScope: import("@reduxjs/toolkit").ActionCreatorWit
|
|
|
58
61
|
}, "conversationHistory/setScopePageSuccess">, patchConversationInScopes: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
59
62
|
conversationId: string;
|
|
60
63
|
patch: Partial<ConversationListItem>;
|
|
61
|
-
}, "conversationHistory/patchConversationInScopes">,
|
|
62
|
-
scopeId: string;
|
|
63
|
-
view: HistoryArchiveView;
|
|
64
|
-
}, "conversationHistory/setScopeArchiveView">, setScopeArchivedCount: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
65
|
-
scopeId: string;
|
|
66
|
-
count: number | null;
|
|
67
|
-
}, "conversationHistory/setScopeArchivedCount">, removeConversationFromScopes: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
64
|
+
}, "conversationHistory/patchConversationInScopes">, removeConversationFromScopes: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
68
65
|
conversationId: string;
|
|
69
66
|
}, "conversationHistory/removeConversationFromScopes">, upsertConversationIntoScopes: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
70
67
|
row: ConversationListItem;
|
|
@@ -32,8 +32,6 @@ function ensureScope(state, scopeId) {
|
|
|
32
32
|
}
|
|
33
33
|
if (scope.includeOriginClasses === void 0) scope.includeOriginClasses = [];
|
|
34
34
|
if (scope.includeLanes === void 0) scope.includeLanes = null;
|
|
35
|
-
if (scope.archiveView === void 0) scope.archiveView = "active";
|
|
36
|
-
if (scope.archivedCount === void 0) scope.archivedCount = null;
|
|
37
35
|
return scope;
|
|
38
36
|
}
|
|
39
37
|
function sortByUpdated(items) {
|
|
@@ -152,6 +150,15 @@ const slice = createSlice({
|
|
|
152
150
|
scope.includeLanes = lanes === null ? null : [...lanes];
|
|
153
151
|
invalidateScopeWindow(scope);
|
|
154
152
|
},
|
|
153
|
+
setScopeOnlyConversationIds(state, action) {
|
|
154
|
+
const { scopeId, ids } = action.payload;
|
|
155
|
+
const scope = ensureScope(state, scopeId);
|
|
156
|
+
const current = scope.onlyConversationIds ?? null;
|
|
157
|
+
const unchanged = current === ids || current !== null && ids !== null && sameStringArray(current, ids);
|
|
158
|
+
if (unchanged) return;
|
|
159
|
+
scope.onlyConversationIds = ids === null ? null : [...ids];
|
|
160
|
+
invalidateScopeWindow(scope);
|
|
161
|
+
},
|
|
155
162
|
setScopeAgentIds(state, action) {
|
|
156
163
|
const scope = ensureScope(state, action.payload.scopeId);
|
|
157
164
|
const sameLen = scope.agentIds.length === action.payload.agentIds.length;
|
|
@@ -168,16 +175,6 @@ const slice = createSlice({
|
|
|
168
175
|
setScopeSearch(state, action) {
|
|
169
176
|
ensureScope(state, action.payload.scopeId).searchTerm = action.payload.searchTerm;
|
|
170
177
|
},
|
|
171
|
-
/** "Archived (N)" ⇄ back: switches which half the list shows and refetches. */
|
|
172
|
-
setScopeArchiveView(state, action) {
|
|
173
|
-
const scope = ensureScope(state, action.payload.scopeId);
|
|
174
|
-
if (scope.archiveView === action.payload.view) return;
|
|
175
|
-
scope.archiveView = action.payload.view;
|
|
176
|
-
invalidateScopeWindow(scope);
|
|
177
|
-
},
|
|
178
|
-
setScopeArchivedCount(state, action) {
|
|
179
|
-
ensureScope(state, action.payload.scopeId).archivedCount = action.payload.count;
|
|
180
|
-
},
|
|
181
178
|
setScopeGrouping(state, action) {
|
|
182
179
|
ensureScope(state, action.payload.scopeId).grouping = action.payload.grouping;
|
|
183
180
|
},
|
|
@@ -222,14 +219,7 @@ const slice = createSlice({
|
|
|
222
219
|
(i) => i.conversationId === conversationId
|
|
223
220
|
);
|
|
224
221
|
if (idx === -1) continue;
|
|
225
|
-
const before = scope.items[idx].status;
|
|
226
222
|
scope.items[idx] = { ...scope.items[idx], ...patch };
|
|
227
|
-
if (patch.status !== void 0 && scope.archivedCount !== null && scope.archivedCount !== void 0 && before === "archived" !== (patch.status === "archived")) {
|
|
228
|
-
scope.archivedCount = Math.max(
|
|
229
|
-
0,
|
|
230
|
-
scope.archivedCount + (patch.status === "archived" ? 1 : -1)
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
223
|
}
|
|
234
224
|
},
|
|
235
225
|
/**
|
|
@@ -247,6 +237,9 @@ const slice = createSlice({
|
|
|
247
237
|
if (scope.agentIds.length > 0 && !scope.agentIds.includes(agentId)) {
|
|
248
238
|
continue;
|
|
249
239
|
}
|
|
240
|
+
if (scope.onlyConversationIds && !scope.onlyConversationIds.includes(row.conversationId)) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
250
243
|
const feature = row.sourceFeature ?? "";
|
|
251
244
|
if (scope.excludeSourceFeatures.includes(feature)) continue;
|
|
252
245
|
const hasAllowList = scope.includeSourceFeatures.length > 0 || scope.includeSourceApps.length > 0 || scope.includeEmptySource;
|
|
@@ -305,6 +298,7 @@ const slice = createSlice({
|
|
|
305
298
|
const {
|
|
306
299
|
configureScope,
|
|
307
300
|
setScopeAgentIds,
|
|
301
|
+
setScopeOnlyConversationIds,
|
|
308
302
|
setScopeSourceFilter,
|
|
309
303
|
seedScopeSourceFilter,
|
|
310
304
|
setScopeLanes,
|
|
@@ -313,8 +307,6 @@ const {
|
|
|
313
307
|
setScopeStatus,
|
|
314
308
|
setScopePageSuccess,
|
|
315
309
|
patchConversationInScopes,
|
|
316
|
-
setScopeArchiveView,
|
|
317
|
-
setScopeArchivedCount,
|
|
318
310
|
removeConversationFromScopes,
|
|
319
311
|
upsertConversationIntoScopes,
|
|
320
312
|
clearScope,
|
|
@@ -332,10 +324,9 @@ export {
|
|
|
332
324
|
removeConversationFromScopes,
|
|
333
325
|
seedScopeSourceFilter,
|
|
334
326
|
setScopeAgentIds,
|
|
335
|
-
setScopeArchiveView,
|
|
336
|
-
setScopeArchivedCount,
|
|
337
327
|
setScopeGrouping,
|
|
338
328
|
setScopeLanes,
|
|
329
|
+
setScopeOnlyConversationIds,
|
|
339
330
|
setScopePageSuccess,
|
|
340
331
|
setScopeSearch,
|
|
341
332
|
setScopeSourceFilter,
|
|
@@ -2,7 +2,6 @@ import { createAsyncThunk } from "@reduxjs/toolkit";
|
|
|
2
2
|
import { supabase } from "../../../host/db.js";
|
|
3
3
|
import { applyFavoritesFromUes } from "../conversation-list/conversation-list.thunks.js";
|
|
4
4
|
import {
|
|
5
|
-
setScopeArchivedCount,
|
|
6
5
|
setScopePageSuccess,
|
|
7
6
|
setScopeStatus,
|
|
8
7
|
configureScope,
|
|
@@ -70,8 +69,7 @@ const fetchConversationHistory = createAsyncThunk(
|
|
|
70
69
|
if (agentIds.length > 0) {
|
|
71
70
|
query = query.in("initial_agent_id", agentIds);
|
|
72
71
|
}
|
|
73
|
-
|
|
74
|
-
query = archiveView === "archived" ? query.eq("status", "archived") : query.neq("status", "archived");
|
|
72
|
+
query = query.neq("status", "archived");
|
|
75
73
|
const filterInput = historyFilterInputFromScope(scope, {
|
|
76
74
|
excludeSourceFeatures,
|
|
77
75
|
includeSourceFeatures,
|
|
@@ -79,26 +77,6 @@ const fetchConversationHistory = createAsyncThunk(
|
|
|
79
77
|
includeEmptySource
|
|
80
78
|
});
|
|
81
79
|
const filtered = applyHistoryFilters(query, filterInput);
|
|
82
|
-
if (replace) {
|
|
83
|
-
let countQuery = supabase.schema("chat").from("conversation").select("id", { count: "exact", head: true }).is("deleted_at", null).eq("is_ephemeral", false).eq("status", "archived");
|
|
84
|
-
if (viewerId) countQuery = countQuery.eq("created_by", viewerId);
|
|
85
|
-
if (agentIds.length > 0) {
|
|
86
|
-
countQuery = countQuery.in("initial_agent_id", agentIds);
|
|
87
|
-
}
|
|
88
|
-
const countFiltered = applyHistoryFilters(countQuery, filterInput);
|
|
89
|
-
if (countFiltered === null) {
|
|
90
|
-
dispatch(setScopeArchivedCount({ scopeId: args.scopeId, count: 0 }));
|
|
91
|
-
} else {
|
|
92
|
-
void Promise.resolve(countFiltered).then(({ count, error: countError }) => {
|
|
93
|
-
dispatch(
|
|
94
|
-
setScopeArchivedCount({
|
|
95
|
-
scopeId: args.scopeId,
|
|
96
|
-
count: countError ? null : count ?? 0
|
|
97
|
-
})
|
|
98
|
-
);
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
80
|
if (filtered === null) {
|
|
103
81
|
dispatch(
|
|
104
82
|
setScopePageSuccess({
|
|
@@ -33,6 +33,11 @@ export type HistoryStatus = "idle" | "loading" | "loading-more" | "succeeded" |
|
|
|
33
33
|
export interface ConversationHistoryScopeState {
|
|
34
34
|
/** Agent ids whose conversations should appear in this scope. Empty = all. */
|
|
35
35
|
agentIds: string[];
|
|
36
|
+
/**
|
|
37
|
+
* Restrict the list to these conversations (a board's own). `null` = no
|
|
38
|
+
* restriction (the default); `[]` = the list is empty without a read.
|
|
39
|
+
*/
|
|
40
|
+
onlyConversationIds?: string[] | null;
|
|
36
41
|
/**
|
|
37
42
|
* `cx_conversation.source_feature` values to hide from this scope.
|
|
38
43
|
* Empty = no filtering. Used by `/chat` to hide voice-agent transcripts
|
|
@@ -102,19 +107,14 @@ export interface ConversationHistoryScopeState {
|
|
|
102
107
|
error: string | null;
|
|
103
108
|
/** Last `Date.now()` at which a fresh fetch completed. */
|
|
104
109
|
lastFetchedAt: number | null;
|
|
105
|
-
/**
|
|
106
|
-
* THE ARCHIVED-ITEMS LAW (common-docs/policies/archived-items.md): the list
|
|
107
|
-
* hides archived conversations by default; "Archived (N)" switches this to
|
|
108
|
-
* `"archived"` in one click. The server query and the client view both obey
|
|
109
|
-
* it, so a conversation archived from any ⋯ leaves the list at once.
|
|
110
|
-
*/
|
|
111
|
-
archiveView: HistoryArchiveView;
|
|
112
|
-
/** Archived conversations under this scope's filters; null = not known yet. */
|
|
113
|
-
archivedCount: number | null;
|
|
114
110
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Does this row belong in a live conversation list? Archived conversations
|
|
113
|
+
* never sit beside live ones: they live in the ONE "Archived" section at the
|
|
114
|
+
* foot of every conversation list, together with deleted ones (Arman,
|
|
115
|
+
* 2026-10-08: one place, called Archived). The one test, shared.
|
|
116
|
+
*/
|
|
117
|
+
export declare function isLiveConversationRow(status: string | null | undefined): boolean;
|
|
118
118
|
/**
|
|
119
119
|
* A distinct (lane, source_app, source_feature) grouping with the user's
|
|
120
120
|
* conversation count. Returned by `get_cx_conversation_lane_facets` and
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
function
|
|
2
|
-
return status
|
|
1
|
+
function isLiveConversationRow(status) {
|
|
2
|
+
return status !== "archived";
|
|
3
3
|
}
|
|
4
4
|
const defaultScopeState = {
|
|
5
5
|
agentIds: [],
|
|
@@ -18,9 +18,7 @@ const defaultScopeState = {
|
|
|
18
18
|
hasMore: false,
|
|
19
19
|
status: "idle",
|
|
20
20
|
error: null,
|
|
21
|
-
lastFetchedAt: null
|
|
22
|
-
archiveView: "active",
|
|
23
|
-
archivedCount: null
|
|
21
|
+
lastFetchedAt: null
|
|
24
22
|
};
|
|
25
23
|
const CONVERSATION_HISTORY_TTL_MS = 6e4;
|
|
26
24
|
const SOURCE_FACETS_TTL_MS = 5 * 6e4;
|
|
@@ -28,5 +26,5 @@ export {
|
|
|
28
26
|
CONVERSATION_HISTORY_TTL_MS,
|
|
29
27
|
SOURCE_FACETS_TTL_MS,
|
|
30
28
|
defaultScopeState,
|
|
31
|
-
|
|
29
|
+
isLiveConversationRow
|
|
32
30
|
};
|
|
@@ -190,4 +190,10 @@ export declare function selectAgentConversationsEntry(state: ChatRootState, agen
|
|
|
190
190
|
* instance → agent map key → canonical agent id, then reads the cache.
|
|
191
191
|
*/
|
|
192
192
|
export declare function selectAgentConversationsEntryForInstance(state: ChatRootState, instanceId: string, versionFilter: number | null): ConversationListAgentCacheEntry | undefined;
|
|
193
|
+
/**
|
|
194
|
+
* The row for a conversation from whichever store holds it — the global list
|
|
195
|
+
* or any mounted sidebar scope. The Archived section needs a title for a row
|
|
196
|
+
* that was archived or deleted from a list only one of them had loaded.
|
|
197
|
+
*/
|
|
198
|
+
export declare function findConversationRow(state: ChatRootState, conversationId: string): ConversationListItem | null;
|
|
193
199
|
export {};
|
|
@@ -80,7 +80,17 @@ function selectAgentConversationsEntryForInstance(state, instanceId, versionFilt
|
|
|
80
80
|
const canonicalAgentId = agent?.parentAgentId ?? agent?.id ?? mapKey;
|
|
81
81
|
return selectAgentConversationsEntry(state, canonicalAgentId, versionFilter);
|
|
82
82
|
}
|
|
83
|
+
function findConversationRow(state, conversationId) {
|
|
84
|
+
const fromList = state.conversationList.byConversationId[conversationId];
|
|
85
|
+
if (fromList) return fromList;
|
|
86
|
+
for (const scope of Object.values(state.conversationHistory.scopes)) {
|
|
87
|
+
const row = scope.items.find((i) => i.conversationId === conversationId);
|
|
88
|
+
if (row) return row;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
83
92
|
export {
|
|
93
|
+
findConversationRow,
|
|
84
94
|
makeSelectAgentConversationList,
|
|
85
95
|
makeSelectAgentConversations,
|
|
86
96
|
selectAgentConversationIds,
|
|
@@ -39,6 +39,12 @@ export interface ConversationListItem {
|
|
|
39
39
|
* unknown. Never client-written — display/filter only.
|
|
40
40
|
*/
|
|
41
41
|
originClass?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Set only on rows in the "Archived" section that were DELETED (soft delete,
|
|
44
|
+
* `deleted_at`) rather than archived (`status = 'archived'`). Restore picks
|
|
45
|
+
* its door from it.
|
|
46
|
+
*/
|
|
47
|
+
deletedAt?: string | null;
|
|
42
48
|
}
|
|
43
49
|
export type ConversationListLoadStatus = "idle" | "loading" | "succeeded" | "failed";
|
|
44
50
|
/**
|
|
@@ -6,8 +6,11 @@ import { isScopesRpcErr } from "../../../context/sources/scopes.js";
|
|
|
6
6
|
import {
|
|
7
7
|
patchConversation,
|
|
8
8
|
renameConversation as renameConversationListItem,
|
|
9
|
-
revertRename
|
|
9
|
+
revertRename,
|
|
10
|
+
addToTrash,
|
|
11
|
+
removeFromTrash
|
|
10
12
|
} from "./conversation-list.slice.js";
|
|
13
|
+
import { findConversationRow } from "./conversation-list.selectors.js";
|
|
11
14
|
import { patchConversationInScopes } from "../conversation-history/slice.js";
|
|
12
15
|
import { patchConversation as patchConversationRecord } from "../execution-system/conversations/conversations.slice.js";
|
|
13
16
|
import { setConversationLabel as setMessagesConversationLabel } from "../execution-system/messages/messages.slice.js";
|
|
@@ -103,7 +106,8 @@ const setConversationFavorite = createAsyncThunk(
|
|
|
103
106
|
);
|
|
104
107
|
const setConversationArchived = createAsyncThunk(
|
|
105
108
|
"conversationRow/setArchived",
|
|
106
|
-
async ({ conversationId, archived }, { dispatch, rejectWithValue }) => {
|
|
109
|
+
async ({ conversationId, archived }, { dispatch, getState, rejectWithValue }) => {
|
|
110
|
+
const before = findConversationRow(getState(), conversationId);
|
|
107
111
|
const nextStatus = archived ? "archived" : "active";
|
|
108
112
|
const action = archived ? "archive this conversation" : "restore this conversation";
|
|
109
113
|
const { data, error } = await supabase.schema("chat").from("conversation").update({ status: nextStatus, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", conversationId).select("id");
|
|
@@ -121,6 +125,11 @@ const setConversationArchived = createAsyncThunk(
|
|
|
121
125
|
dispatch(
|
|
122
126
|
patchConversationRecord({ conversationId, persistedStatus: nextStatus })
|
|
123
127
|
);
|
|
128
|
+
if (archived) {
|
|
129
|
+
if (before) dispatch(addToTrash({ ...before, status: nextStatus }));
|
|
130
|
+
} else {
|
|
131
|
+
dispatch(removeFromTrash(conversationId));
|
|
132
|
+
}
|
|
124
133
|
return { conversationId, status: nextStatus };
|
|
125
134
|
}
|
|
126
135
|
);
|