@ai-matrx/chat 0.7.18 → 0.7.19
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 +17 -0
- package/dist/agents/components/chat/ChatHistorySidebar.js +3 -0
- package/dist/agents/components/chat/ChatSidebarMenu.js +3 -0
- package/dist/agents/components/chat/PinnedAgentsSection.d.ts +3 -5
- package/dist/agents/components/chat/PinnedAgentsSection.js +7 -4
- package/dist/agents/components/conversation-history/ConversationHistorySidebar.d.ts +14 -3
- package/dist/agents/components/conversation-history/ConversationHistorySidebar.js +57 -37
- package/dist/agents/components/conversation-history/ConversationSourceFilterTree.js +3 -3
- package/dist/agents/components/inputs/smart-input/ConversationContextChip.js +18 -7
- package/dist/agents/components/inputs/smart-input/ConversationContextRail.js +4 -2
- package/dist/agents/components/inputs/smart-input/DesktopPresenceIndicator.js +6 -0
- package/dist/agents/components/inputs/smart-input/composer/ComposerAgentPill.js +21 -7
- package/dist/agents/components/inputs/smart-input/composer/ComposerChipsRow.js +3 -3
- package/dist/agents/components/inputs/smart-input/composer/ComposerOutput.js +22 -15
- package/dist/agents/components/inputs/smart-input/composer/ComposerSplash.js +19 -3
- package/dist/agents/components/inputs/smart-input/composer/useComposerAgent.d.ts +24 -3
- package/dist/agents/components/inputs/smart-input/composer/useComposerAgent.js +32 -30
- package/dist/agents/components/inputs/smart-input/use-compute-target-actions.d.ts +20 -0
- package/dist/agents/components/inputs/smart-input/use-compute-target-actions.js +11 -0
- package/dist/agents/hooks/useConversationMaterialized.js +42 -9
- package/dist/agents/hooks/useCreatorOwnershipSync.js +7 -3
- package/dist/agents/hooks/useWorkingDocument.d.ts +12 -5
- package/dist/agents/hooks/useWorkingDocument.js +18 -6
- package/dist/agents/identity/agent-identity.d.ts +7 -1
- package/dist/agents/identity/agent-identity.js +11 -6
- package/dist/agents/identity/model-catalog.d.ts +7 -0
- package/dist/agents/identity/model-catalog.js +16 -1
- package/dist/agents/redux/conversation-history/slice.d.ts +2 -0
- package/dist/agents/redux/conversation-history/slice.js +2 -0
- package/dist/agents/redux/conversation-history/thunks.d.ts +2 -0
- package/dist/agents/redux/conversation-history/thunks.js +6 -3
- package/dist/agents/redux/conversation-history/types.d.ts +6 -0
- package/dist/agents/redux/conversation-history/types.js +1 -0
- package/dist/agents/redux/conversation-list/conversation-rows.d.ts +7 -0
- package/dist/agents/redux/conversation-list/conversation-rows.js +3 -1
- package/dist/agents/redux/execution-system/client-capabilities/desktop-native.provider.js +4 -1
- package/dist/agents/redux/execution-system/context-rules/mandate-kill-switch.d.ts +4 -0
- package/dist/agents/redux/execution-system/context-rules/mandate-kill-switch.js +26 -2
- package/dist/agents/redux/execution-system/conversations/conversations.selectors.d.ts +10 -0
- package/dist/agents/redux/execution-system/conversations/conversations.selectors.js +3 -0
- package/dist/agents/redux/execution-system/instance-user-input/useComposerDraftRestore.js +6 -1
- package/dist/agents/redux/execution-system/instance-working-document/scratchpad.thunks.js +9 -0
- package/dist/agents/redux/execution-system/thunks/launch-agent-execution.thunk.js +1 -8
- package/dist/agents/ui-first-tools/ui/lists/TaskPanelChip.js +4 -2
- package/dist/mandates/service.js +47 -32
- package/dist/next/navigation.js +5 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.7.19
|
|
4
|
+
|
|
5
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
6
|
+
`git diff npm/chat/v0.7.18..npm/chat/v0.7.19 -- apps/shared/chat`).
|
|
7
|
+
No source changes intended and no consumer action required.
|
|
8
|
+
|
|
3
9
|
## 0.7.18
|
|
4
10
|
|
|
5
11
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
@@ -8,6 +14,17 @@ No source changes intended and no consumer action required.
|
|
|
8
14
|
|
|
9
15
|
## 0.7.17
|
|
10
16
|
### Unreleased
|
|
17
|
+
### the chat sidebar loads a short list of names, only when seen; a draft reads nothing
|
|
18
|
+
|
|
19
|
+
- `ConversationHistorySidebar` reads only once on screen (`useInView`); new prop `nameRowsOnly` (slim
|
|
20
|
+
`CONVERSATION_NAME_ROW_COLUMNS` read; `ChatHistorySidebar` uses it with 20 rows). Source facets load on
|
|
21
|
+
engagement (filter open, search, "Hide"). `useNextNavigation().Link` defaults `prefetch={false}`.
|
|
22
|
+
- New `selectIsDraftConversation`; draft conversations skip agent-lists, document/scratchpad restores,
|
|
23
|
+
remark restore and the materialized probe (now shared per conversation). Active scratchpad reads when the
|
|
24
|
+
conversation's scratch gate is on. Desktop-presence warm waits for page idle. New `useComputeBinding`
|
|
25
|
+
(bound box without the targets list) — at-rest chips no longer request `/api/compute-targets`.
|
|
26
|
+
- **Consumer action:** none. A host Link that should prefetch passes `prefetch` explicitly.
|
|
27
|
+
|
|
11
28
|
### a turn carries what the person attached (lane A1, Applet builder)
|
|
12
29
|
|
|
13
30
|
- **`runHeadlessAgentJson({ resources })`** and **`continueAgentJson({ resources })`**: the person's "+" picks (`Resource[]`) go
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { ConversationHistorySidebar } from "../conversation-history/ConversationHistorySidebar.js";
|
|
4
|
+
const CHAT_HISTORY_PAGE_SIZE = 20;
|
|
4
5
|
function ChatHistorySidebar({
|
|
5
6
|
scopeId,
|
|
6
7
|
activeConversationId,
|
|
@@ -30,6 +31,8 @@ function ChatHistorySidebar({
|
|
|
30
31
|
autoFocusSearch,
|
|
31
32
|
hideSearchAffordance,
|
|
32
33
|
serverSearch: true,
|
|
34
|
+
pageSize: CHAT_HISTORY_PAGE_SIZE,
|
|
35
|
+
nameRowsOnly: true,
|
|
33
36
|
className
|
|
34
37
|
}
|
|
35
38
|
);
|
|
@@ -105,6 +105,7 @@ function ChatSidebarMenu({ expanded }) {
|
|
|
105
105
|
Link,
|
|
106
106
|
{
|
|
107
107
|
href: CHAT_HOME_HREF,
|
|
108
|
+
prefetch: false,
|
|
108
109
|
title: "Chat",
|
|
109
110
|
"aria-label": "Chat",
|
|
110
111
|
"aria-current": isChatHome ? "page" : void 0,
|
|
@@ -249,6 +250,7 @@ function ChatSidebarMenu({ expanded }) {
|
|
|
249
250
|
Link,
|
|
250
251
|
{
|
|
251
252
|
href: MESSAGE_TEMPLATES_HREF,
|
|
253
|
+
prefetch: false,
|
|
252
254
|
title: "Message templates",
|
|
253
255
|
"aria-label": "Message templates",
|
|
254
256
|
"aria-current": isMessageTemplatesRoute ? "page" : void 0,
|
|
@@ -272,6 +274,7 @@ function ChatSidebarMenu({ expanded }) {
|
|
|
272
274
|
Link,
|
|
273
275
|
{
|
|
274
276
|
href: VOICE_AGENT_HREF,
|
|
277
|
+
prefetch: false,
|
|
275
278
|
title: "Voice agent",
|
|
276
279
|
"aria-label": "Voice agent",
|
|
277
280
|
"aria-current": isVoiceRoute ? "page" : void 0,
|
|
@@ -14,11 +14,9 @@ interface PinnedAgentsSectionProps {
|
|
|
14
14
|
* with the rest of the app, and shares the agent registry with the dropdown
|
|
15
15
|
* (no parallel fetches).
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* favorites. The list populates as soon as the user opens the dropdown — same
|
|
21
|
-
* lazy behavior as everywhere else.
|
|
17
|
+
* The catalogue is asked for after the page is idle (see the effect below),
|
|
18
|
+
* so the section is absent at first paint and appears once the list lands —
|
|
19
|
+
* or sooner, when the person opens any agent picker.
|
|
22
20
|
*/
|
|
23
21
|
export declare function PinnedAgentsSection({ activeAgentId, }: PinnedAgentsSectionProps): import("react").JSX.Element | null;
|
|
24
22
|
export {};
|
|
@@ -3,7 +3,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { useRouter, Link } from "../../../host/navigation.js";
|
|
5
5
|
import { cn } from "@ai-matrx/design-system";
|
|
6
|
-
import { useAppDispatch } from "../../../store/hooks.js";
|
|
7
6
|
import {
|
|
8
7
|
FavoriteAgentButton,
|
|
9
8
|
useAgentCatalogRows,
|
|
@@ -14,6 +13,7 @@ import { useAppStore } from "../../../store/hooks.js";
|
|
|
14
13
|
import { stageChatAgentSwitch } from "./begin-fresh-chat.js";
|
|
15
14
|
import { Button, DisclosureHeader } from "@ai-matrx/design-system/controls";
|
|
16
15
|
import { ensureAgentCatalog } from "../../identity/agent-identity.js";
|
|
16
|
+
import { whenPageIdle } from "@ai-matrx/kit/idle-scheduler";
|
|
17
17
|
const CONSUMER_ID = "chat-sidebar-pinned";
|
|
18
18
|
const PINNED_COLLAPSED_LIMIT = 5;
|
|
19
19
|
const selectPinnedAgents = makeSelectFilteredAgents();
|
|
@@ -23,13 +23,16 @@ function pinnedAgentHref(agentId) {
|
|
|
23
23
|
function PinnedAgentsSection({
|
|
24
24
|
activeAgentId
|
|
25
25
|
}) {
|
|
26
|
-
const dispatch = useAppDispatch();
|
|
27
26
|
const router = useRouter();
|
|
28
27
|
const store = useAppStore();
|
|
29
28
|
const consumer = useAgentConsumer(CONSUMER_ID);
|
|
30
29
|
useEffect(() => {
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
void whenPageIdle(controller.signal).then((idle) => {
|
|
32
|
+
if (idle) void ensureAgentCatalog();
|
|
33
|
+
});
|
|
34
|
+
return () => controller.abort();
|
|
35
|
+
}, []);
|
|
33
36
|
useEffect(() => {
|
|
34
37
|
if (consumer.favFilter !== "yes") consumer.setFavFilter("yes");
|
|
35
38
|
}, [consumer]);
|
|
@@ -16,9 +16,14 @@
|
|
|
16
16
|
* apps) or `"consumer"` (the comfortable /chat look). Both share ONE data
|
|
17
17
|
* controller, so they never drift.
|
|
18
18
|
*
|
|
19
|
-
* Self-contained data-loading: the component triggers the initial fetch
|
|
20
|
-
*
|
|
21
|
-
* `fetchConversationHistory`.
|
|
19
|
+
* Self-contained data-loading: the component triggers the initial fetch once
|
|
20
|
+
* the list is ON SCREEN (and again whenever `agentIds` or the resolved source
|
|
21
|
+
* filter changes) via `fetchConversationHistory`. A list mounted in a collapsed
|
|
22
|
+
* rail, a closed drawer, a hidden desktop sidebar on a phone or an inactive tab
|
|
23
|
+
* reads nothing until it can be seen (`useInView`); until then, and while the
|
|
24
|
+
* first page loads, it shows its loading row — never a blank. The source facets
|
|
25
|
+
* (filter tree, lane counts, "Hide <source>") load when the person engages
|
|
26
|
+
* them: opening the filter, searching, or choosing "Hide" in a row menu.
|
|
22
27
|
*/
|
|
23
28
|
import React from "react";
|
|
24
29
|
import { type HistoryGrouping } from "../../redux/conversation-history/types.js";
|
|
@@ -61,6 +66,12 @@ export interface ConversationHistorySidebarProps {
|
|
|
61
66
|
defaultGrouping?: HistoryGrouping;
|
|
62
67
|
/** Page size passed to the fetch thunk. Default: 30. */
|
|
63
68
|
pageSize?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Read only what a row shows (name, time, provenance for its menu) — no
|
|
71
|
+
* description, message count or model. For a list whose rows show nothing
|
|
72
|
+
* else (the /chat sidebar). Default false.
|
|
73
|
+
*/
|
|
74
|
+
nameRowsOnly?: boolean;
|
|
64
75
|
/**
|
|
65
76
|
* Legacy DENY-list on `source_feature` (server-side `.neq`). Prefer
|
|
66
77
|
* `surfaceId` (ALLOW-list) for new surfaces. Both AND together if set.
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useState
|
|
9
9
|
} from "react";
|
|
10
10
|
import { formatRelativeTime } from "@ai-matrx/kit/format";
|
|
11
|
+
import { useInView } from "@ai-matrx/kit/hooks";
|
|
11
12
|
import {
|
|
12
13
|
Cpu,
|
|
13
14
|
CalendarDays,
|
|
@@ -78,6 +79,7 @@ function useConversationHistoryController(props) {
|
|
|
78
79
|
surfaceId,
|
|
79
80
|
defaultGrouping = "date",
|
|
80
81
|
pageSize = 30,
|
|
82
|
+
nameRowsOnly = false,
|
|
81
83
|
excludeSourceFeatures,
|
|
82
84
|
isFavorite,
|
|
83
85
|
onToggleFavorite,
|
|
@@ -89,6 +91,7 @@ function useConversationHistoryController(props) {
|
|
|
89
91
|
} = props;
|
|
90
92
|
const dispatch = useAppDispatch();
|
|
91
93
|
const readFor = useRef(null);
|
|
94
|
+
const [rootRef, inView] = useInView();
|
|
92
95
|
const selectScope = useMemo(
|
|
93
96
|
() => makeSelectConversationHistoryScope(scopeId),
|
|
94
97
|
[scopeId]
|
|
@@ -154,16 +157,18 @@ function useConversationHistoryController(props) {
|
|
|
154
157
|
})
|
|
155
158
|
);
|
|
156
159
|
}
|
|
160
|
+
if (!inView) return;
|
|
157
161
|
const fetchArgs = {
|
|
158
162
|
scopeId,
|
|
159
163
|
agentIds: agentIds.slice(),
|
|
160
164
|
pageSize,
|
|
165
|
+
nameRowsOnly,
|
|
161
166
|
replace: true
|
|
162
167
|
};
|
|
163
168
|
if (excludeSourceFeatures !== void 0) {
|
|
164
169
|
fetchArgs.excludeSourceFeatures = excludeSourceFeatures;
|
|
165
170
|
}
|
|
166
|
-
const readKey = [scopeId, agentIdsKey, pageSize, excludeKey, resolvedKey, lanesKey, onlyKey].join("|");
|
|
171
|
+
const readKey = [scopeId, agentIdsKey, pageSize, nameRowsOnly, excludeKey, resolvedKey, lanesKey, onlyKey].join("|");
|
|
167
172
|
if (keepLoaded) {
|
|
168
173
|
if (readFor.current === readKey) return;
|
|
169
174
|
const fresh = dispatch((_d, getState) => {
|
|
@@ -187,7 +192,9 @@ function useConversationHistoryController(props) {
|
|
|
187
192
|
lanesKey,
|
|
188
193
|
surfaceId,
|
|
189
194
|
keepLoaded,
|
|
190
|
-
onlyKey
|
|
195
|
+
onlyKey,
|
|
196
|
+
inView,
|
|
197
|
+
nameRowsOnly
|
|
191
198
|
]);
|
|
192
199
|
const onSearchChange = useCallback(
|
|
193
200
|
(value) => {
|
|
@@ -207,7 +214,7 @@ function useConversationHistoryController(props) {
|
|
|
207
214
|
}, [dispatch, scopeId, hasMore, status]);
|
|
208
215
|
const onRefresh = () => {
|
|
209
216
|
void dispatch(fetchConversationHistory({ scopeId, replace: true }));
|
|
210
|
-
if (surfaceId) {
|
|
217
|
+
if (surfaceId && facetsStatus !== "idle") {
|
|
211
218
|
void dispatch(fetchSourceFacets({ force: true }));
|
|
212
219
|
}
|
|
213
220
|
};
|
|
@@ -248,9 +255,10 @@ function useConversationHistoryController(props) {
|
|
|
248
255
|
(conv) => getConversationHref ? getConversationHref(conv) : `/chat/${conv.conversationId}`,
|
|
249
256
|
[getConversationHref]
|
|
250
257
|
);
|
|
258
|
+
const searching = serverSearch && searchTerm.trim() !== "";
|
|
251
259
|
useEffect(() => {
|
|
252
|
-
if (
|
|
253
|
-
}, [dispatch,
|
|
260
|
+
if (searching) void dispatch(fetchSourceFacets(void 0));
|
|
261
|
+
}, [dispatch, searching]);
|
|
254
262
|
const commitSourceFilter = useCallback(
|
|
255
263
|
(features, includeEmpty) => {
|
|
256
264
|
dispatch(
|
|
@@ -276,18 +284,6 @@ function useConversationHistoryController(props) {
|
|
|
276
284
|
}
|
|
277
285
|
const featureKey = sourceKey(conv.sourceFeature);
|
|
278
286
|
const isEmpty = featureKey === EMPTY_SOURCE_KEY;
|
|
279
|
-
const selected = new Set(scope.includeSourceFeatures);
|
|
280
|
-
if (scope.includeSourceApps.length > 0) {
|
|
281
|
-
const apps = new Set(scope.includeSourceApps);
|
|
282
|
-
for (const f of facets) {
|
|
283
|
-
if (apps.has(sourceKey(f.sourceApp))) {
|
|
284
|
-
selected.add(sourceKey(f.sourceFeature));
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
selected.delete(EMPTY_SOURCE_KEY);
|
|
289
|
-
let includeEmpty = scope.includeEmptySource;
|
|
290
|
-
const unfiltered = selected.size === 0 && !includeEmpty;
|
|
291
287
|
return {
|
|
292
288
|
app: conv.sourceApp,
|
|
293
289
|
feature: conv.sourceFeature,
|
|
@@ -300,27 +296,49 @@ function useConversationHistoryController(props) {
|
|
|
300
296
|
toast.success(`Showing only ${featureLabel(featureKey)}`);
|
|
301
297
|
},
|
|
302
298
|
onHide: () => {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
299
|
+
void (async () => {
|
|
300
|
+
const read = await dispatch(fetchSourceFacets(void 0));
|
|
301
|
+
const known = fetchSourceFacets.fulfilled.match(read) ? read.payload : [];
|
|
302
|
+
if (known.length === 0) {
|
|
303
|
+
toast.error(
|
|
304
|
+
`Couldn't hide ${featureLabel(featureKey)}: your sources didn't load. Try again.`
|
|
305
|
+
);
|
|
306
|
+
return;
|
|
311
307
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
308
|
+
const selected = new Set(scope.includeSourceFeatures);
|
|
309
|
+
if (scope.includeSourceApps.length > 0) {
|
|
310
|
+
const apps = new Set(scope.includeSourceApps);
|
|
311
|
+
for (const f of known) {
|
|
312
|
+
if (apps.has(sourceKey(f.sourceApp))) {
|
|
313
|
+
selected.add(sourceKey(f.sourceFeature));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
selected.delete(EMPTY_SOURCE_KEY);
|
|
318
|
+
let includeEmpty = scope.includeEmptySource;
|
|
319
|
+
const unfiltered = selected.size === 0 && !includeEmpty;
|
|
320
|
+
let next;
|
|
321
|
+
if (unfiltered) {
|
|
322
|
+
next = /* @__PURE__ */ new Set();
|
|
323
|
+
let hasEmpty = false;
|
|
324
|
+
for (const f of known) {
|
|
325
|
+
const k = sourceKey(f.sourceFeature);
|
|
326
|
+
if (k === EMPTY_SOURCE_KEY) hasEmpty = true;
|
|
327
|
+
else next.add(k);
|
|
328
|
+
}
|
|
329
|
+
includeEmpty = hasEmpty;
|
|
330
|
+
} else {
|
|
331
|
+
next = selected;
|
|
332
|
+
}
|
|
333
|
+
if (isEmpty) includeEmpty = false;
|
|
334
|
+
else next.delete(featureKey);
|
|
335
|
+
commitSourceFilter(next, includeEmpty);
|
|
336
|
+
toast.success(`Hiding ${featureLabel(featureKey)} here`);
|
|
337
|
+
})();
|
|
320
338
|
}
|
|
321
339
|
};
|
|
322
340
|
},
|
|
323
|
-
[surfaceId, scope,
|
|
341
|
+
[dispatch, surfaceId, scope, commitSourceFilter]
|
|
324
342
|
);
|
|
325
343
|
const getSourceMenuCtx = useCallback(
|
|
326
344
|
(conv) => {
|
|
@@ -337,6 +355,7 @@ function useConversationHistoryController(props) {
|
|
|
337
355
|
[getSourceMenuBase, removeFromList]
|
|
338
356
|
);
|
|
339
357
|
return {
|
|
358
|
+
rootRef,
|
|
340
359
|
scope,
|
|
341
360
|
byDate,
|
|
342
361
|
byAgent,
|
|
@@ -414,7 +433,7 @@ const DenseView = ({
|
|
|
414
433
|
) : cachedSearchItems;
|
|
415
434
|
const empty = !allLanesOff && status !== "loading" && status !== "idle" && !error && count === 0 && !searchTerm.trim() && emptyState;
|
|
416
435
|
const showControls = showSearch || showGroupingToggle || !!headerActions || !!surfaceId;
|
|
417
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full min-h-0 flex-col", className), children: [
|
|
436
|
+
return /* @__PURE__ */ jsxs("div", { ref: ctl.rootRef, className: cn("flex h-full min-h-0 flex-col", className), children: [
|
|
418
437
|
headerSlot,
|
|
419
438
|
topSlot,
|
|
420
439
|
showControls && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1 border-b border-border px-2 py-1", children: [
|
|
@@ -477,7 +496,7 @@ const DenseView = ({
|
|
|
477
496
|
] }),
|
|
478
497
|
/* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto", children: [
|
|
479
498
|
error && /* @__PURE__ */ jsx(ReadFailure, { error, what: "your conversations", onRetry: onRefresh, className: "m-2" }),
|
|
480
|
-
status === "loading" && count === 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-3 text-[11px] text-muted-foreground", children: [
|
|
499
|
+
(status === "loading" || status === "idle") && count === 0 && !allLanesOff && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-3 text-[11px] text-muted-foreground", children: [
|
|
481
500
|
/* @__PURE__ */ jsx(Loader2, { size: 12, className: "animate-spin" }),
|
|
482
501
|
"Loading conversations\u2026"
|
|
483
502
|
] }),
|
|
@@ -680,6 +699,7 @@ const ConsumerView = ({
|
|
|
680
699
|
return /* @__PURE__ */ jsxs(
|
|
681
700
|
"div",
|
|
682
701
|
{
|
|
702
|
+
ref: ctl.rootRef,
|
|
683
703
|
className: cn(
|
|
684
704
|
"matrx-touch-targets flex h-full min-h-0 flex-col bg-card",
|
|
685
705
|
className
|
|
@@ -736,7 +756,7 @@ const ConsumerView = ({
|
|
|
736
756
|
topSlot,
|
|
737
757
|
/* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto pt-1", children: [
|
|
738
758
|
error && !searchActive && /* @__PURE__ */ jsx(ReadFailure, { error, what: "your conversations", onRetry: onRefresh, className: "m-2" }),
|
|
739
|
-
status === "loading" && count === 0 && !searchActive && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-3 text-xs text-muted-foreground", children: [
|
|
759
|
+
(status === "loading" || status === "idle") && count === 0 && !searchActive && !allLanesOff && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-3 text-xs text-muted-foreground", children: [
|
|
740
760
|
/* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }),
|
|
741
761
|
"Loading conversations\u2026"
|
|
742
762
|
] }),
|
|
@@ -212,8 +212,8 @@ const ConversationSourceFilterTree = ({
|
|
|
212
212
|
const facetsStatus = useAppSelector(selectSourceFacetsStatus);
|
|
213
213
|
const facetsError = useAppSelector(selectSourceFacetsError);
|
|
214
214
|
useEffect(() => {
|
|
215
|
-
void dispatch(fetchSourceFacets(void 0));
|
|
216
|
-
}, [dispatch]);
|
|
215
|
+
if (open) void dispatch(fetchSourceFacets(void 0));
|
|
216
|
+
}, [dispatch, open]);
|
|
217
217
|
const includeLanes = scope.includeLanes;
|
|
218
218
|
const visibleFacets = useMemo(
|
|
219
219
|
() => includeLanes === null ? facets : facets.filter((f) => f.lane !== null && includeLanes.includes(f.lane)),
|
|
@@ -459,7 +459,7 @@ const ConversationSourceFilterTree = ({
|
|
|
459
459
|
}) })
|
|
460
460
|
] }),
|
|
461
461
|
/* @__PURE__ */ jsxs("div", { className: "max-h-[min(60dvh,420px)] overflow-y-auto py-1", children: [
|
|
462
|
-
facetsStatus === "loading" && tree.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-3 text-[11px] text-muted-foreground", children: "Loading sources\u2026" }),
|
|
462
|
+
(facetsStatus === "loading" || facetsStatus === "idle") && tree.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-3 text-[11px] text-muted-foreground", children: "Loading sources\u2026" }),
|
|
463
463
|
facetsStatus === "failed" && tree.length === 0 && /* @__PURE__ */ jsx(
|
|
464
464
|
ReadFailure,
|
|
465
465
|
{
|
|
@@ -32,7 +32,11 @@ import {
|
|
|
32
32
|
surfaceLevelPlace
|
|
33
33
|
} from "../../../redux/execution-system/context-rules/context-hierarchy.js";
|
|
34
34
|
import { resolveClientSurface } from "../../../redux/execution-system/utils/build-tool-injection.js";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
peekMandateKillSwitch,
|
|
37
|
+
resolveMandateKillSwitch
|
|
38
|
+
} from "../../../redux/execution-system/context-rules/mandate-kill-switch.js";
|
|
39
|
+
import { whenPageIdle } from "@ai-matrx/kit/idle-scheduler";
|
|
36
40
|
import { useMachineFramesVisible } from "../../shared/transcript-audience.js";
|
|
37
41
|
import { useChatCanvasTab } from "../../../../host/canvas.js";
|
|
38
42
|
import { CONVERSATION_CONTEXT_KIND } from "../../../../host/canvas-tabs.js";
|
|
@@ -53,10 +57,11 @@ function valueGroupName(surfaceName) {
|
|
|
53
57
|
return /^(page\s+)?context(\s+items?)?$/i.test(label) ? "" : label;
|
|
54
58
|
}
|
|
55
59
|
function useMandateKillSwitch(mandateKey) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
const known = peekMandateKillSwitch(mandateKey);
|
|
61
|
+
const [state, setState] = useState(() => ({
|
|
62
|
+
key: known === void 0 ? null : mandateKey ?? null,
|
|
63
|
+
on: known === true
|
|
64
|
+
}));
|
|
60
65
|
useEffect(() => {
|
|
61
66
|
if (!mandateKey) return void 0;
|
|
62
67
|
let live = true;
|
|
@@ -142,10 +147,16 @@ function ConversationContextChip({
|
|
|
142
147
|
const saveMany = useSaveContextRules();
|
|
143
148
|
const hierarchy = useConversationContextHierarchy(conversationId);
|
|
144
149
|
useEffect(() => {
|
|
145
|
-
|
|
150
|
+
const idle = new AbortController();
|
|
151
|
+
void whenPageIdle(idle.signal).then((ready) => {
|
|
152
|
+
if (ready) void dispatch(ensureSurfaceFeatureLoaded(CONTEXT_RULES_FEATURE));
|
|
153
|
+
});
|
|
146
154
|
const onFocus = () => void dispatch(reloadContextRules());
|
|
147
155
|
window.addEventListener("focus", onFocus);
|
|
148
|
-
return () =>
|
|
156
|
+
return () => {
|
|
157
|
+
idle.abort();
|
|
158
|
+
window.removeEventListener("focus", onFocus);
|
|
159
|
+
};
|
|
149
160
|
}, [dispatch]);
|
|
150
161
|
const mandateKey = useAppSelector(
|
|
151
162
|
(state) => state.conversations.byConversationId[conversationId]?.mandateKey ?? null
|
|
@@ -91,6 +91,7 @@ import { useConversationAbilities } from "../../../redux/execution-system/host-c
|
|
|
91
91
|
import { ComposerAttachmentSourceChips } from "../../../../host/composer-extensions.js";
|
|
92
92
|
import { ConversationCompanionPills } from "./ConversationCompanionPills.js";
|
|
93
93
|
import { RailPill } from "./rail-pill.js";
|
|
94
|
+
import { selectIsDraftConversation } from "../../../redux/execution-system/conversations/conversations.selectors.js";
|
|
94
95
|
function entryHasValue(e) {
|
|
95
96
|
const v = e.value;
|
|
96
97
|
if (v === void 0 || v === null) return false;
|
|
@@ -160,14 +161,15 @@ function ConversationContextRailBody({
|
|
|
160
161
|
const hasLists = useAppSelector(selectHasAgentListsContent(conversationId));
|
|
161
162
|
const taskCounts = useAppSelector(selectAgentTaskCounts(conversationId));
|
|
162
163
|
const todoCounts = useAppSelector(selectUserTodoCounts(conversationId));
|
|
164
|
+
const isDraft = useAppSelector(selectIsDraftConversation(conversationId));
|
|
163
165
|
useEffect(() => {
|
|
164
|
-
if (!conversationId) return void 0;
|
|
166
|
+
if (!conversationId || isDraft) return void 0;
|
|
165
167
|
void dispatch(ensureAgentLists(conversationId));
|
|
166
168
|
dispatch(subscribeAgentLists(conversationId));
|
|
167
169
|
return () => {
|
|
168
170
|
dispatch(unsubscribeAgentLists(conversationId));
|
|
169
171
|
};
|
|
170
|
-
}, [conversationId, dispatch]);
|
|
172
|
+
}, [conversationId, isDraft, dispatch]);
|
|
171
173
|
const agentContextPolicies = useAppSelector(
|
|
172
174
|
(state) => agentId ? selectAgentContextPolicies(state, agentId) : void 0
|
|
173
175
|
);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
computeTargetIconColor,
|
|
11
11
|
computeTargetKindLabel,
|
|
12
|
+
useComputeBinding,
|
|
12
13
|
useComputeTargetActions
|
|
13
14
|
} from "./use-compute-target-actions.js";
|
|
14
15
|
import { describeBoundTargetState } from "../../../../compute/targets.js";
|
|
@@ -22,6 +23,11 @@ function TargetIcon({
|
|
|
22
23
|
function DesktopPresenceIndicator({
|
|
23
24
|
conversationId
|
|
24
25
|
}) {
|
|
26
|
+
const { boundView, sandboxBlocked } = useComputeBinding(conversationId);
|
|
27
|
+
if (sandboxBlocked || !boundView) return null;
|
|
28
|
+
return /* @__PURE__ */ jsx(BoundTargetButton, { conversationId });
|
|
29
|
+
}
|
|
30
|
+
function BoundTargetButton({ conversationId }) {
|
|
25
31
|
const {
|
|
26
32
|
loading,
|
|
27
33
|
boundView,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useContext, useState } from "react";
|
|
3
|
+
import { useContext, useEffect, useState } from "react";
|
|
4
4
|
import { AppWindow, ChevronDown, Cpu, Gauge, Layers, Star, Webhook } from "lucide-react";
|
|
5
5
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
6
6
|
import { MANDATE_KEYS } from "@ai-matrx/agents/mandates";
|
|
@@ -29,7 +29,9 @@ import { ComposerOutputPanel } from "./ComposerOutput.js";
|
|
|
29
29
|
import { ComposerEffortRows, useComposerEffort } from "./ComposerEffortPill.js";
|
|
30
30
|
import { composerShows } from "./composer-mode-visibility.js";
|
|
31
31
|
import { composerPillClass } from "./composer-chip.js";
|
|
32
|
-
import { useComposerAgent, useEffectiveModelId } from "./useComposerAgent.js";
|
|
32
|
+
import { useComposerAgent, useComposerPresets, useEffectiveModelId } from "./useComposerAgent.js";
|
|
33
|
+
import { getModelRecords } from "../../../../identity/model-catalog.js";
|
|
34
|
+
import { chatHostHasDatabase } from "../../../../../host/configure.js";
|
|
33
35
|
import { useComposerAgentFilter } from "./useComposerAgentFilter.js";
|
|
34
36
|
import { ComposerAgentPeek } from "./ComposerAgentPeek.js";
|
|
35
37
|
const AGENT_PICKER_PANEL = "w-[680px] h-[min(528px,80dvh)] p-0";
|
|
@@ -37,14 +39,22 @@ const MODEL_PICKER_PANEL = "w-[min(1060px,calc(100vw-24px))] h-[min(640px,80dvh)
|
|
|
37
39
|
const CUSTOM_AGENT_LABEL = "Custom Agent";
|
|
38
40
|
function pillLabel(info, mode) {
|
|
39
41
|
if (!composerShows(mode, "agent.presets")) return info.agentName ?? "Agent";
|
|
40
|
-
const name = info.isCustom ? CUSTOM_AGENT_LABEL : info.
|
|
42
|
+
const name = info.isCustom ? CUSTOM_AGENT_LABEL : info.agentName ?? "Agent";
|
|
41
43
|
return info.effectiveModelLabel ? `${name} \xB7 ${info.effectiveModelLabel}` : name;
|
|
42
44
|
}
|
|
43
45
|
function ComposerAgentPill({ conversationId, mode, size, agentControl, menuSide, folded = false }) {
|
|
44
|
-
const [open,
|
|
46
|
+
const [open, setOpenState] = useState(false);
|
|
47
|
+
const [engaged, setEngaged] = useState(false);
|
|
48
|
+
const setOpen = (next) => {
|
|
49
|
+
setOpenState(next);
|
|
50
|
+
if (next) setEngaged(true);
|
|
51
|
+
};
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (engaged && chatHostHasDatabase()) void getModelRecords().loadOptions();
|
|
54
|
+
}, [engaged]);
|
|
45
55
|
const isMobile = useIsMobile();
|
|
46
56
|
const effort = useComposerEffort(conversationId, useEffectiveModelId(conversationId));
|
|
47
|
-
const info = useComposerAgent(conversationId);
|
|
57
|
+
const info = useComposerAgent(conversationId, { engaged });
|
|
48
58
|
const label = pillLabel(info, mode);
|
|
49
59
|
const onSelectAgent = agentControl?.onSelectAgent;
|
|
50
60
|
const agentFilter = useComposerAgentFilter(conversationId);
|
|
@@ -78,7 +88,10 @@ function ComposerAgentPill({ conversationId, mode, size, agentControl, menuSide,
|
|
|
78
88
|
activeAgentId: info.agentId,
|
|
79
89
|
agentFilter,
|
|
80
90
|
contentSide: menuSide,
|
|
81
|
-
triggerSlot: pill
|
|
91
|
+
triggerSlot: pill,
|
|
92
|
+
onOpenChange: (next) => {
|
|
93
|
+
if (next) setEngaged(true);
|
|
94
|
+
}
|
|
82
95
|
}
|
|
83
96
|
);
|
|
84
97
|
}
|
|
@@ -145,6 +158,7 @@ function ChatAgentMenu({
|
|
|
145
158
|
const agentFilter = useComposerAgentFilter(conversationId);
|
|
146
159
|
const onSelectAgent = agentControl?.onSelectAgent;
|
|
147
160
|
const agentLabel = info.isCustom ? CUSTOM_AGENT_LABEL : info.agentName ?? "Agent";
|
|
161
|
+
const { presets } = useComposerPresets(info.agentId);
|
|
148
162
|
const chooseCustom = () => {
|
|
149
163
|
if (!info.customAgentId || !onSelectAgent) return;
|
|
150
164
|
close();
|
|
@@ -183,7 +197,7 @@ function ChatAgentMenu({
|
|
|
183
197
|
onSelectAgent ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
184
198
|
inSheet ? null : /* @__PURE__ */ jsx(ComposerMenuDivider, {}),
|
|
185
199
|
/* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Choose your chat agent" }),
|
|
186
|
-
|
|
200
|
+
presets.map((preset) => /* @__PURE__ */ jsx(
|
|
187
201
|
ComposerMenuRow,
|
|
188
202
|
{
|
|
189
203
|
icon: Star,
|
|
@@ -8,7 +8,7 @@ import { useOpenRunControlsWindow } from "../../../../../host/window-openers.js"
|
|
|
8
8
|
import { useOpenCloudBrowserCanvas } from "../../../../../host/ui-slots.js";
|
|
9
9
|
import { ChatConnectionsStrip } from "../ChatConnectionsStrip.js";
|
|
10
10
|
import { ComposerConnectPromo } from "./ComposerConnectPromo.js";
|
|
11
|
-
import {
|
|
11
|
+
import { useComputeBinding } from "../use-compute-target-actions.js";
|
|
12
12
|
import { ComposerEnvironmentPanel } from "./ComposerPlusMenu.js";
|
|
13
13
|
import { COMPOSER_ROW_CLASS } from "./composer-chip.js";
|
|
14
14
|
import { ComposerChip } from "../ComposerChip.js";
|
|
@@ -26,10 +26,10 @@ function ComposerChipsRowBody({
|
|
|
26
26
|
chipShape = "md"
|
|
27
27
|
}) {
|
|
28
28
|
const [envOpen, setEnvOpen] = useState(false);
|
|
29
|
-
const compute =
|
|
29
|
+
const compute = useComputeBinding(conversationId);
|
|
30
30
|
const openRunControlsWindow = useOpenRunControlsWindow();
|
|
31
31
|
const openCloudBrowser = useOpenCloudBrowserCanvas();
|
|
32
|
-
const boundName = compute.boundView?.
|
|
32
|
+
const boundName = compute.boundView?.name ?? null;
|
|
33
33
|
const EnvIcon = boundName ? Server : Cloud;
|
|
34
34
|
return /* @__PURE__ */ jsxs(
|
|
35
35
|
"div",
|
|
@@ -32,6 +32,7 @@ import { selectBuilderAdvancedSettings } from "../../../../redux/execution-syste
|
|
|
32
32
|
import { setBuilderAdvancedSettings } from "../../../../redux/execution-system/instance-ui-state/instance-ui-state.slice.js";
|
|
33
33
|
import { DEFAULT_BUILDER_ADVANCED_SETTINGS } from "../../../../types/instance.types.js";
|
|
34
34
|
import { useSkills } from "../../../../identity/skill-catalog.js";
|
|
35
|
+
import { whenPageIdle } from "@ai-matrx/kit/idle-scheduler";
|
|
35
36
|
import { useClippedContentGuard } from "../../../../../host/ui-slots.js";
|
|
36
37
|
import { ComposerMenuDivider, ComposerMenuHelp, ComposerMenuLabel, ComposerMenuRow } from "./ComposerMenu.js";
|
|
37
38
|
import {
|
|
@@ -67,7 +68,7 @@ const TYPE_ICONS = {
|
|
|
67
68
|
const kindLabel = (kind) => knownShapeLabel(kind) ?? (humanizeIdentifier(kind) || kind);
|
|
68
69
|
const LOCKED_REASON = "This agent always answers in its own shape";
|
|
69
70
|
const pickWarning = (lockedShapes) => lockedPickWarning(lockedShapes, kindLabel);
|
|
70
|
-
function useLockState(conversationId) {
|
|
71
|
+
function useLockState(conversationId, engaged) {
|
|
71
72
|
const agentId = useAppSelector(
|
|
72
73
|
(state) => state.conversations.byConversationId[conversationId]?.agentId ?? null
|
|
73
74
|
);
|
|
@@ -80,30 +81,36 @@ function useLockState(conversationId) {
|
|
|
80
81
|
if (!cancelled) setRead({ agentId, result });
|
|
81
82
|
};
|
|
82
83
|
const fail = (error) => settle({ status: "failed", error, retry: () => setAttempt((n) => n + 1) });
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const ready = engaged ? Promise.resolve(true) : whenPageIdle(controller.signal);
|
|
86
|
+
void ready.then((idle) => {
|
|
87
|
+
if (!idle || cancelled) return;
|
|
88
|
+
fetchAgentOutputSchemas([agentId]).then((byId) => {
|
|
89
|
+
const verdict = classifyLockRead(byId, agentId);
|
|
90
|
+
if (verdict.status === "failed") {
|
|
91
|
+
fail(new Error("The agent's output schema could not be read."));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
settle(verdict);
|
|
95
|
+
}, fail);
|
|
96
|
+
});
|
|
91
97
|
return () => {
|
|
92
98
|
cancelled = true;
|
|
99
|
+
controller.abort();
|
|
93
100
|
};
|
|
94
|
-
}, [agentId, attempt]);
|
|
101
|
+
}, [agentId, attempt, engaged]);
|
|
95
102
|
if (!agentId) return { status: "none" };
|
|
96
103
|
return read && read.agentId === agentId ? read.result : { status: "reading" };
|
|
97
104
|
}
|
|
98
|
-
function useComposerOutput(conversationId) {
|
|
105
|
+
function useComposerOutput(conversationId, engaged) {
|
|
99
106
|
const dispatch = useAppDispatch();
|
|
100
|
-
const { skills } = useSkills();
|
|
101
107
|
const settings = useAppSelector(selectBuilderAdvancedSettings(conversationId)) ?? DEFAULT_BUILDER_ADVANCED_SETTINGS;
|
|
102
108
|
const types = readOutputTypes(settings.outputTypes);
|
|
103
109
|
const outputKinds = settings.outputKinds ?? [];
|
|
104
110
|
const addedSkills = settings.addedSkills ?? [];
|
|
111
|
+
const { skills } = useSkills({ skipAutoFetch: !engaged && addedSkills.length === 0 });
|
|
105
112
|
const kinds = selectedOutputKinds(outputKinds, addedSkills, skills);
|
|
106
|
-
const lock = useLockState(conversationId);
|
|
113
|
+
const lock = useLockState(conversationId, engaged);
|
|
107
114
|
const migrator = useRef(null);
|
|
108
115
|
useEffect(() => {
|
|
109
116
|
migrator.current ??= createKindSkillMigrator();
|
|
@@ -314,7 +321,7 @@ function ShapePicker({
|
|
|
314
321
|
] });
|
|
315
322
|
}
|
|
316
323
|
function ComposerOutputPanel({ conversationId }) {
|
|
317
|
-
const output = useComposerOutput(conversationId);
|
|
324
|
+
const output = useComposerOutput(conversationId, true);
|
|
318
325
|
return /* @__PURE__ */ jsxs("div", { className: "@container flex min-h-0 flex-1 flex-col", children: [
|
|
319
326
|
/* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Output types" }),
|
|
320
327
|
/* @__PURE__ */ jsx(TypeGrid, { types: output.types, onToggle: output.toggleType }),
|
|
@@ -334,7 +341,7 @@ function ComposerOutputPill({
|
|
|
334
341
|
}) {
|
|
335
342
|
const [open, setOpen] = useState(false);
|
|
336
343
|
const [roomierSide, setRoomierSide] = useState(null);
|
|
337
|
-
const output = useComposerOutput(conversationId);
|
|
344
|
+
const output = useComposerOutput(conversationId, false);
|
|
338
345
|
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, modal: false, children: [
|
|
339
346
|
/* @__PURE__ */ jsxs(SplitButton, { className: "min-w-0", children: [
|
|
340
347
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|