@ai-matrx/chat 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/agent-copy/export.d.ts +1 -1
- package/dist/agent-copy/export.js +5 -16
- package/dist/agents/components/context-items/bodies/WebpageBody.js +12 -5
- package/dist/agents/components/debug/SandboxFileViewer.js +31 -22
- package/dist/agents/components/inputs/smart-input/RunControlsMenu.js +1 -1
- package/dist/agents/components/inputs/smart-input/composer/ComposerAgentPeek.d.ts +4 -0
- package/dist/agents/components/inputs/smart-input/composer/ComposerAgentPeek.js +50 -0
- package/dist/agents/components/inputs/smart-input/composer/ComposerAgentPill.js +16 -1
- package/dist/agents/components/messages-display/TranscriptIntegrityCopyButton.js +14 -3
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.js +18 -1
- package/dist/agents/components/run/AssistantWarning.js +12 -0
- package/dist/agents/components/run/IncompleteNote.d.ts +14 -0
- package/dist/agents/components/run/IncompleteNote.js +21 -0
- package/dist/agents/components/run-controls/PayloadTab.js +5 -1
- package/dist/agents/components/run-controls/panels/BackendTargetPanel.js +12 -1
- package/dist/agents/components/shared/AgentPeekDuplicateButton.d.ts +5 -0
- package/dist/agents/components/shared/AgentPeekDuplicateButton.js +65 -0
- package/dist/agents/redux/execution-system/active-requests/active-requests.selectors.d.ts +2 -1
- package/dist/agents/redux/execution-system/active-requests/active-requests.selectors.js +2 -1
- package/dist/agents/redux/execution-system/active-requests/answer-kept-warning.d.ts +7 -0
- package/dist/agents/redux/execution-system/active-requests/answer-kept-warning.js +12 -0
- package/dist/agents/redux/execution-system/instance-ui-state/components/InstanceUIStateCore.js +68 -9
- package/dist/agents/redux/execution-system/instance-ui-state/components/InstanceUIStateList.js +30 -8
- package/dist/agents/redux/execution-system/messages/messages.selectors.d.ts +6 -0
- package/dist/agents/redux/execution-system/messages/messages.selectors.js +17 -1
- package/dist/agents/redux/execution-system/messages/turn-outcome.d.ts +26 -0
- package/dist/agents/redux/execution-system/messages/turn-outcome.js +28 -0
- package/dist/cx-chat/components/messages/AssistantMessage.js +4 -1
- package/dist/cx-chat/components/messages/UserMessage.js +1 -8
- package/dist/cx-conversation/AssistantMessage.js +4 -1
- package/dist/cx-conversation/UserMessage.js +1 -8
- package/dist/tool-call-visualization/components/ToolTabBodies.js +11 -4
- package/dist/voice-agent/components/VoiceDebugPanel.js +22 -3
- package/dist/window-panels/windows/agents/ObservationalMemoryWindow.js +20 -5
- package/dist/window-panels/windows/agents/RunControlsWindow.js +19 -6
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.3.5 — 2026-10-07
|
|
4
|
+
### Chat mode opens the agent itself; Duplicate opens the copy in a new tab
|
|
5
|
+
|
|
6
|
+
- The Chat-mode agent pill's menu starts with "This chat's agent": its row opens the agent menu's own
|
|
7
|
+
peek card (`AgentDetailCard`) — view, open, Sneak Peek, and Duplicate when the agent is not yours.
|
|
8
|
+
- `agents/components/shared/AgentPeekDuplicateButton` moved here from matrx-frontend (one Duplicate for
|
|
9
|
+
every peek). It now opens the copy's page in a NEW TAB instead of navigating away from the chat.
|
|
10
|
+
- Needs `@ai-matrx/agents` 0.49.0.
|
|
11
|
+
- The composer's + (Chat Options) is tinted only when something was added — no longer blue on every fresh
|
|
12
|
+
chat. The Chat Options window lists its sections as start-aligned rows (`Tile`), not centred buttons.
|
|
13
|
+
- Consumer action: import `AgentPeekDuplicateButton` from
|
|
14
|
+
`@ai-matrx/chat/agents/components/shared/AgentPeekDuplicateButton` and delete the app's copy.
|
|
15
|
+
|
|
16
|
+
## 0.3.4 — 2026-10-07
|
|
17
|
+
### Re-sync before matrx-frontend consumes the published package (P27)
|
|
18
|
+
|
|
19
|
+
- Carries every matrx-frontend `packages/chat` edit after `d8078d15b2` plus the in-flight working-tree
|
|
20
|
+
edits in the shared checkout: an incomplete answer reads the same live and reloaded
|
|
21
|
+
(`messages/turn-outcome`, `active-requests/answer-kept-warning`, `run/IncompleteNote`), ALC-19 copy
|
|
22
|
+
results honored (`copyText` boolean). The kept-out `ToolTabBodies` CopyButton shim stays out.
|
|
23
|
+
- Consumer action: none.
|
|
24
|
+
|
|
3
25
|
## 0.3.3 — 2026-10-07
|
|
4
26
|
### Re-sync before matrx-frontend consumes the published package (P27)
|
|
5
27
|
|
|
@@ -25,7 +25,7 @@ export interface ExportItem {
|
|
|
25
25
|
export declare function exportFilename(base: string, extension: string): string;
|
|
26
26
|
export declare function jsonExportItem(data: unknown | (() => unknown), label?: string): ExportItem;
|
|
27
27
|
export declare function textExportItem(content: string | (() => string), label: string, extension?: string): ExportItem;
|
|
28
|
-
/** Rows → CSV. Columns: explicit list, or the union of keys across rows. */
|
|
28
|
+
/** Rows → CSV (Alchemy's one writer). Columns: explicit list, or the union of keys across rows. */
|
|
29
29
|
export declare function rowsToCsv(rows: Array<Record<string, unknown>>, columns?: Array<{
|
|
30
30
|
key: string;
|
|
31
31
|
header: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toDelimitedText } from "@ai-matrx/alchemy/operate/read";
|
|
1
2
|
function exportFilename(base, extension) {
|
|
2
3
|
const slug = base.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
3
4
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -34,27 +35,15 @@ function textExportItem(content, label, extension = "txt") {
|
|
|
34
35
|
})
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
|
-
function csvEscape(value) {
|
|
38
|
-
return /[",\n\r]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
|
|
39
|
-
}
|
|
40
38
|
function rowsToCsv(rows, columns) {
|
|
41
39
|
const cols = columns ?? [...new Set(rows.flatMap((row) => Object.keys(row)))].map((key) => ({
|
|
42
40
|
key,
|
|
43
41
|
header: key
|
|
44
42
|
}));
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const value = row[c.key];
|
|
50
|
-
if (value === null || value === void 0) return "";
|
|
51
|
-
return csvEscape(
|
|
52
|
-
typeof value === "object" ? JSON.stringify(value) : String(value)
|
|
53
|
-
);
|
|
54
|
-
}).join(",")
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
return lines.join("\n");
|
|
43
|
+
return toDelimitedText(
|
|
44
|
+
cols.map((c) => c.header),
|
|
45
|
+
rows.map((row) => cols.map((c) => row[c.key] ?? null))
|
|
46
|
+
);
|
|
58
47
|
}
|
|
59
48
|
function csvExportItem(rows, label = "CSV", columns) {
|
|
60
49
|
return {
|
|
@@ -11,10 +11,7 @@ import {
|
|
|
11
11
|
} from "@ai-matrx/design-system";
|
|
12
12
|
import { toast } from "../../../../host/notify.js";
|
|
13
13
|
import { WebpageSnapshotView } from "../../../../host/ui-slots.js";
|
|
14
|
-
import {
|
|
15
|
-
webpageTitle,
|
|
16
|
-
webpageUrl
|
|
17
|
-
} from "../../../resources/webpage-snapshot.js";
|
|
14
|
+
import { webpageTitle, webpageUrl } from "../../../resources/webpage-snapshot.js";
|
|
18
15
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
19
16
|
function firstWebpage(item) {
|
|
20
17
|
return item.refs.webpages?.[0] ?? null;
|
|
@@ -61,7 +58,17 @@ function WebpageFooter({ item }) {
|
|
|
61
58
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
62
59
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate font-mono text-[11px] text-muted-foreground", children: url }),
|
|
63
60
|
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
64
|
-
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
61
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
62
|
+
Button,
|
|
63
|
+
{
|
|
64
|
+
variant: "quiet",
|
|
65
|
+
icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}),
|
|
66
|
+
glyphTone: copied ? "success" : void 0,
|
|
67
|
+
onClick: copy,
|
|
68
|
+
"aria-label": "Copy webpage URL",
|
|
69
|
+
className: "ml-auto"
|
|
70
|
+
}
|
|
71
|
+
) }),
|
|
65
72
|
/* @__PURE__ */ jsx(TooltipContent, { children: "Copy URL" })
|
|
66
73
|
] }),
|
|
67
74
|
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
@@ -23,28 +23,27 @@ function SandboxFileViewer({
|
|
|
23
23
|
const [copied, setCopied] = useState(false);
|
|
24
24
|
const adapterRef = useRef(null);
|
|
25
25
|
if (!adapterRef.current || adapterRef.current.instanceId !== sandboxRowId) {
|
|
26
|
-
adapterRef.current = createSandboxFilesystemAdapter(
|
|
26
|
+
adapterRef.current = createSandboxFilesystemAdapter(
|
|
27
|
+
sandboxRowId
|
|
28
|
+
);
|
|
27
29
|
}
|
|
28
|
-
const load = useCallback(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
[]
|
|
47
|
-
);
|
|
30
|
+
const load = useCallback(async (p) => {
|
|
31
|
+
const target = p.trim();
|
|
32
|
+
if (!target) return;
|
|
33
|
+
setLoading(true);
|
|
34
|
+
setError(null);
|
|
35
|
+
try {
|
|
36
|
+
const adapter = adapterRef.current;
|
|
37
|
+
if (!adapter) throw new Error("Sandbox adapter not initialized");
|
|
38
|
+
const text = await adapter.readFile(target);
|
|
39
|
+
setContent(text);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
setContent(null);
|
|
42
|
+
setError(err instanceof Error ? err.message : "Failed to read file");
|
|
43
|
+
} finally {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
}, []);
|
|
48
47
|
useEffect(() => {
|
|
49
48
|
if (initialPath) void load(initialPath);
|
|
50
49
|
}, [initialPath, sandboxRowId, load]);
|
|
@@ -91,7 +90,17 @@ function SandboxFileViewer({
|
|
|
91
90
|
),
|
|
92
91
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-2 py-1 border-b border-border bg-muted/30", children: [
|
|
93
92
|
/* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-muted-foreground truncate", children: lockPath ? initialPath : content != null ? path : "" }),
|
|
94
|
-
content != null && /* @__PURE__ */ jsx(
|
|
93
|
+
content != null && /* @__PURE__ */ jsx(
|
|
94
|
+
Button,
|
|
95
|
+
{
|
|
96
|
+
variant: "quiet",
|
|
97
|
+
icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}),
|
|
98
|
+
glyphTone: copied ? "success" : void 0,
|
|
99
|
+
onClick: handleCopy,
|
|
100
|
+
className: "shrink-0",
|
|
101
|
+
children: "Copy"
|
|
102
|
+
}
|
|
103
|
+
)
|
|
95
104
|
] }),
|
|
96
105
|
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-auto", children: loading && content == null ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-3 text-xs text-muted-foreground", children: [
|
|
97
106
|
/* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
@@ -89,7 +89,7 @@ function RunControlsMenu({
|
|
|
89
89
|
Button,
|
|
90
90
|
{
|
|
91
91
|
variant: "quiet",
|
|
92
|
-
tone: rc.addedCount > 0
|
|
92
|
+
tone: rc.addedCount > 0 ? "primary" : void 0,
|
|
93
93
|
icon: /* @__PURE__ */ jsx(TriggerIcon, {}),
|
|
94
94
|
tabIndex: variant === "plus" ? -1 : void 0,
|
|
95
95
|
title: "Chat Options",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { AgentDetailCard, useAgentCatalogState } from "@ai-matrx/agents/catalog/react";
|
|
5
|
+
import { useAppDispatch, useAppSelector } from "../../../../../store/hooks.js";
|
|
6
|
+
import { selectAgentById, selectAgentReadyForBuilder } from "../../../../redux/agent-definition/selectors.js";
|
|
7
|
+
import { fetchFullAgent } from "../../../../redux/agent-definition/thunks.js";
|
|
8
|
+
import { AgentPeekDuplicateButton } from "../../../shared/AgentPeekDuplicateButton.js";
|
|
9
|
+
function ComposerAgentPeek({ agentId, onDone }) {
|
|
10
|
+
const dispatch = useAppDispatch();
|
|
11
|
+
const row = useAgentCatalogState((state) => state.byId[agentId]);
|
|
12
|
+
const record = useAppSelector((state) => selectAgentById(state, agentId));
|
|
13
|
+
const isReady = useAppSelector((state) => selectAgentReadyForBuilder(state, agentId));
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!isReady) void dispatch(fetchFullAgent(agentId)).unwrap().catch(() => void 0);
|
|
16
|
+
}, [dispatch, agentId, isReady]);
|
|
17
|
+
const agent = row ?? (record ? {
|
|
18
|
+
id: record.id,
|
|
19
|
+
name: record.name,
|
|
20
|
+
description: record.description,
|
|
21
|
+
category: record.category,
|
|
22
|
+
tags: record.tags,
|
|
23
|
+
agentType: record.agentType,
|
|
24
|
+
modelId: record.modelId,
|
|
25
|
+
isActive: record.isActive,
|
|
26
|
+
isArchived: record.isArchived,
|
|
27
|
+
isFavorite: record.isFavorite,
|
|
28
|
+
createdBy: record.createdBy,
|
|
29
|
+
organizationId: record.organizationId,
|
|
30
|
+
taskId: record.taskId,
|
|
31
|
+
sourceAgentId: record.sourceAgentId,
|
|
32
|
+
createdAt: record.createdAt,
|
|
33
|
+
updatedAt: record.updatedAt,
|
|
34
|
+
isOwner: record.isOwner,
|
|
35
|
+
accessLevel: record.accessLevel,
|
|
36
|
+
sharedByEmail: record.sharedByEmail,
|
|
37
|
+
orchestra: null
|
|
38
|
+
} : null);
|
|
39
|
+
if (!agent) return null;
|
|
40
|
+
return /* @__PURE__ */ jsx(
|
|
41
|
+
AgentDetailCard,
|
|
42
|
+
{
|
|
43
|
+
agent,
|
|
44
|
+
actions: /* @__PURE__ */ jsx(AgentPeekDuplicateButton, { agentId, onDuplicated: onDone })
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
ComposerAgentPeek
|
|
50
|
+
};
|
|
@@ -36,6 +36,7 @@ import { composerShows } from "./composer-mode-visibility.js";
|
|
|
36
36
|
import { composerPillClass } from "./composer-chip.js";
|
|
37
37
|
import { useComposerAgent, useEffectiveModelId } from "./useComposerAgent.js";
|
|
38
38
|
import { useComposerAgentFilter } from "./useComposerAgentFilter.js";
|
|
39
|
+
import { ComposerAgentPeek } from "./ComposerAgentPeek.js";
|
|
39
40
|
import { presentOrganizationRefusal } from "../../../../../host/org.js";
|
|
40
41
|
import { ensureOrgId, isOrganizationSelectionCancelled } from "../../../../../host/org.js";
|
|
41
42
|
function pillLabel(info, mode) {
|
|
@@ -150,6 +151,17 @@ function ComposerAgentPill({ conversationId, mode, size, agentControl, menuSide,
|
|
|
150
151
|
)
|
|
151
152
|
] });
|
|
152
153
|
}
|
|
154
|
+
function ThisAgentRow({ info, close }) {
|
|
155
|
+
if (!info.agentId) return null;
|
|
156
|
+
return /* @__PURE__ */ jsx(
|
|
157
|
+
ComposerSubmenu,
|
|
158
|
+
{
|
|
159
|
+
row: { icon: Webhook, label: info.agentName ?? "Agent", description: "View, open or duplicate" },
|
|
160
|
+
panelClassName: "w-[340px] h-[420px] p-0",
|
|
161
|
+
children: /* @__PURE__ */ jsx(ComposerAgentPeek, { agentId: info.agentId, onDone: close })
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
}
|
|
153
165
|
function ChatPresetsPanel({
|
|
154
166
|
conversationId,
|
|
155
167
|
info,
|
|
@@ -169,7 +181,7 @@ function ChatPresetsPanel({
|
|
|
169
181
|
if (!onSelectAgent) {
|
|
170
182
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
171
183
|
/* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Agent" }),
|
|
172
|
-
/* @__PURE__ */ jsx(
|
|
184
|
+
/* @__PURE__ */ jsx(ThisAgentRow, { info, close }),
|
|
173
185
|
/* @__PURE__ */ jsx(ComposerMenuHelp, { children: "This page always answers with this agent." }),
|
|
174
186
|
/* @__PURE__ */ jsx(ComposerMenuDivider, {}),
|
|
175
187
|
/* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Model for this chat" }),
|
|
@@ -229,6 +241,9 @@ function ChatPresetsPanel({
|
|
|
229
241
|
void savePersonalDefault("chat_default_offering", offeringId ?? "");
|
|
230
242
|
};
|
|
231
243
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
244
|
+
/* @__PURE__ */ jsx(ComposerMenuLabel, { children: "This chat's agent" }),
|
|
245
|
+
/* @__PURE__ */ jsx(ThisAgentRow, { info, close }),
|
|
246
|
+
/* @__PURE__ */ jsx(ComposerMenuDivider, {}),
|
|
232
247
|
/* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Chat agents" }),
|
|
233
248
|
info.presets.map((preset) => /* @__PURE__ */ jsx(
|
|
234
249
|
ComposerMenuRow,
|
|
@@ -43,11 +43,11 @@ function TranscriptIntegrityCopyButtonInner({
|
|
|
43
43
|
effectiveVisibleGroupLimit
|
|
44
44
|
});
|
|
45
45
|
publish({
|
|
46
|
-
|
|
46
|
+
Conversation: conversationId,
|
|
47
47
|
"Loaded rows": report.spine.loadedRows,
|
|
48
48
|
"Groups rendered": `${report.groups.filter((g) => g.rendered).length}/${report.groups.length}`,
|
|
49
49
|
"Stream phase": report.request.streamPhase,
|
|
50
|
-
|
|
50
|
+
Anomalies: report.anomalies.length === 0 ? "none" : report.anomalies
|
|
51
51
|
});
|
|
52
52
|
}, [
|
|
53
53
|
isActive,
|
|
@@ -78,7 +78,18 @@ function TranscriptIntegrityCopyButtonInner({
|
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
80
|
};
|
|
81
|
-
return /* @__PURE__ */ jsx(
|
|
81
|
+
return /* @__PURE__ */ jsx(
|
|
82
|
+
Button,
|
|
83
|
+
{
|
|
84
|
+
variant: "quiet",
|
|
85
|
+
icon: /* @__PURE__ */ jsx(Stethoscope, {}),
|
|
86
|
+
onClick: () => void copy(),
|
|
87
|
+
title: "Copy transcript integrity report (admin) \u2014 paste it to an agent when a message is missing",
|
|
88
|
+
"aria-label": "Copy transcript integrity report",
|
|
89
|
+
"data-testid": "transcript-integrity-copy",
|
|
90
|
+
className: "absolute top-2 right-4 z-10"
|
|
91
|
+
}
|
|
92
|
+
);
|
|
82
93
|
}
|
|
83
94
|
export {
|
|
84
95
|
TranscriptIntegrityCopyButton
|
|
@@ -34,8 +34,15 @@ import {
|
|
|
34
34
|
isFailedRecord,
|
|
35
35
|
extractRecordError,
|
|
36
36
|
selectIsLatestAssistantMessage,
|
|
37
|
-
persistedBodyBlocks
|
|
37
|
+
persistedBodyBlocks,
|
|
38
|
+
incompleteAnswerReason
|
|
38
39
|
} from "../../../redux/execution-system/messages/messages.selectors.js";
|
|
40
|
+
import {
|
|
41
|
+
incompleteDetail,
|
|
42
|
+
incompleteLabel,
|
|
43
|
+
isAnswerKeptWarning
|
|
44
|
+
} from "../../../redux/execution-system/messages/turn-outcome.js";
|
|
45
|
+
import { IncompleteNote } from "../../run/IncompleteNote.js";
|
|
39
46
|
import {
|
|
40
47
|
isAttachmentMessagePart,
|
|
41
48
|
isInlineAssistantMedia
|
|
@@ -443,6 +450,16 @@ function AgentAssistantMessage({
|
|
|
443
450
|
!isClosedStreamSegment && displaySources.length > 0 && /* @__PURE__ */ jsx(MessageSourcesRow, { sources: displaySources, className: "mt-2" }),
|
|
444
451
|
!isStreamActive && /* @__PURE__ */ jsx(PrefillNote, { metadata: record?.metadata }),
|
|
445
452
|
!isStreamActive && /* @__PURE__ */ jsx(StoppedNote, { metadata: record?.metadata, requestStatus }),
|
|
453
|
+
!isStreamActive && !visibleWarnings?.some(isAnswerKeptWarning) && (() => {
|
|
454
|
+
const reason = incompleteAnswerReason(record);
|
|
455
|
+
return reason ? /* @__PURE__ */ jsx(
|
|
456
|
+
IncompleteNote,
|
|
457
|
+
{
|
|
458
|
+
label: incompleteLabel(reason),
|
|
459
|
+
detail: incompleteDetail(reason)
|
|
460
|
+
}
|
|
461
|
+
) : null;
|
|
462
|
+
})(),
|
|
446
463
|
isStreamActive && !failed && (phase === "text_streaming" || phase === "interstitial") && /* @__PURE__ */ jsx("div", { className: "relative h-0 overflow-visible", children: /* @__PURE__ */ jsx(BreathingOrb, { className: "absolute left-0 top-[3px]", size: 40 }) })
|
|
447
464
|
] }),
|
|
448
465
|
!isClosedStreamSegment && visibleWarnings?.map((warning, index) => /* @__PURE__ */ jsx(
|
|
@@ -3,8 +3,20 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { AlertTriangle } from "lucide-react";
|
|
5
5
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
6
|
+
import { IncompleteNote } from "./IncompleteNote.js";
|
|
7
|
+
import { isAnswerKeptWarning } from "../../redux/execution-system/active-requests/answer-kept-warning.js";
|
|
8
|
+
import { incompleteLabel } from "../../redux/execution-system/messages/turn-outcome.js";
|
|
6
9
|
function AssistantWarning({ warning }) {
|
|
7
10
|
const [showDetails, setShowDetails] = useState(false);
|
|
11
|
+
if (isAnswerKeptWarning(warning)) {
|
|
12
|
+
return /* @__PURE__ */ jsx(
|
|
13
|
+
IncompleteNote,
|
|
14
|
+
{
|
|
15
|
+
label: incompleteLabel(warning.code),
|
|
16
|
+
detail: warning.user_message?.trim() || void 0
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
}
|
|
8
20
|
const message = warning.user_message?.trim() || "The response completed with a warning.";
|
|
9
21
|
const detail = warning.system_message?.trim();
|
|
10
22
|
const hasDetails = Boolean(
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface IncompleteNoteProps {
|
|
2
|
+
/** The short label ("Ending may be cut off"). */
|
|
3
|
+
label: string;
|
|
4
|
+
/** The reason-specific sentence, shown under Details. */
|
|
5
|
+
detail?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The quiet mark on an answer that was delivered and kept but whose end may be
|
|
9
|
+
* missing (recitation stop, length limit). Live (from the stream warning) and
|
|
10
|
+
* after a reload (from the saved finish reason) both render THIS component, so
|
|
11
|
+
* the two never look different. A state marker — not an error, no Retry.
|
|
12
|
+
*/
|
|
13
|
+
export declare function IncompleteNote({ label, detail }: IncompleteNoteProps): import("react").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { AlertTriangle } from "lucide-react";
|
|
5
|
+
import { Button } from "@ai-matrx/design-system/controls";
|
|
6
|
+
function IncompleteNote({ label, detail }) {
|
|
7
|
+
const [open, setOpen] = useState(false);
|
|
8
|
+
return /* @__PURE__ */ jsxs("div", { className: "mt-1 text-xs", "data-testid": "incomplete-note", children: [
|
|
9
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-2.5 gap-y-1", children: [
|
|
10
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-amber-700 dark:text-amber-400", children: [
|
|
11
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
12
|
+
label
|
|
13
|
+
] }),
|
|
14
|
+
detail && /* @__PURE__ */ jsx(Button, { variant: "link", onClick: () => setOpen((v) => !v), children: open ? "Hide details" : "Details" })
|
|
15
|
+
] }),
|
|
16
|
+
detail && open && /* @__PURE__ */ jsx("div", { className: "mt-1 rounded bg-amber-500/10 px-2 py-1.5 text-[11px] leading-relaxed text-muted-foreground", children: detail })
|
|
17
|
+
] });
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
IncompleteNote
|
|
21
|
+
};
|
|
@@ -115,7 +115,11 @@ function roledImagesOf(messages) {
|
|
|
115
115
|
continue;
|
|
116
116
|
}
|
|
117
117
|
const source = typeof b.url === "string" ? b.url : String(b.file_id ?? "");
|
|
118
|
-
rows.push({
|
|
118
|
+
rows.push({
|
|
119
|
+
role: b.role,
|
|
120
|
+
variable: variableNameOfImageUrl(source),
|
|
121
|
+
source
|
|
122
|
+
});
|
|
119
123
|
}
|
|
120
124
|
}
|
|
121
125
|
return rows;
|
|
@@ -44,7 +44,18 @@ function CopyableValue({
|
|
|
44
44
|
children: value
|
|
45
45
|
}
|
|
46
46
|
),
|
|
47
|
-
/* @__PURE__ */ jsx(
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
Button,
|
|
49
|
+
{
|
|
50
|
+
variant: "quiet",
|
|
51
|
+
icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}),
|
|
52
|
+
glyphTone: copied ? "success" : void 0,
|
|
53
|
+
onClick: handleCopy,
|
|
54
|
+
title: "Copy",
|
|
55
|
+
"aria-label": "Copy",
|
|
56
|
+
className: "shrink-0"
|
|
57
|
+
}
|
|
58
|
+
)
|
|
48
59
|
] });
|
|
49
60
|
}
|
|
50
61
|
function Row({
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { CopyPlus, Loader2 } from "lucide-react";
|
|
5
|
+
import { Button } from "@ai-matrx/design-system/controls";
|
|
6
|
+
import { getUserMessage } from "@ai-matrx/agents/matrx";
|
|
7
|
+
import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
|
|
8
|
+
import { selectUserId } from "../../../host/identity.js";
|
|
9
|
+
import { getBuilderDoor } from "../../../host/builder-door.js";
|
|
10
|
+
import { toast } from "../../../host/notify.js";
|
|
11
|
+
import { isOrganizationSelectionCancelled } from "../../../host/org.js";
|
|
12
|
+
import { selectAgentById, selectAgentReadyForBuilder } from "../../redux/agent-definition/selectors.js";
|
|
13
|
+
import { agentGoHref } from "../../addressing/agentAddress.js";
|
|
14
|
+
function AgentPeekDuplicateButton({
|
|
15
|
+
agentId,
|
|
16
|
+
onDuplicated
|
|
17
|
+
}) {
|
|
18
|
+
const dispatch = useAppDispatch();
|
|
19
|
+
const userId = useAppSelector(selectUserId);
|
|
20
|
+
const record = useAppSelector((state) => selectAgentById(state, agentId));
|
|
21
|
+
const isReady = useAppSelector((state) => selectAgentReadyForBuilder(state, agentId));
|
|
22
|
+
const [busy, setBusy] = useState(false);
|
|
23
|
+
const door = getBuilderDoor();
|
|
24
|
+
if (!door || !isReady || !record) return null;
|
|
25
|
+
const ownsIt = record.isOwner === true || record.createdBy != null && record.createdBy === userId;
|
|
26
|
+
if (ownsIt) return null;
|
|
27
|
+
const duplicate = async () => {
|
|
28
|
+
if (busy) return;
|
|
29
|
+
setBusy(true);
|
|
30
|
+
const tab = window.open("about:blank", "_blank");
|
|
31
|
+
try {
|
|
32
|
+
const newId = await dispatch(door.duplicateAgent(agentId)).unwrap();
|
|
33
|
+
const href = agentGoHref(newId, "/build");
|
|
34
|
+
if (tab) {
|
|
35
|
+
tab.opener = null;
|
|
36
|
+
tab.location.href = href;
|
|
37
|
+
} else {
|
|
38
|
+
window.open(href, "_blank", "noopener");
|
|
39
|
+
}
|
|
40
|
+
toast.success(`Duplicated "${record.name}" \u2014 opened in a new tab`);
|
|
41
|
+
onDuplicated?.();
|
|
42
|
+
} catch (err) {
|
|
43
|
+
tab?.close();
|
|
44
|
+
if (!isOrganizationSelectionCancelled(err)) {
|
|
45
|
+
toast.error("Could not duplicate agent", { description: getUserMessage(err) });
|
|
46
|
+
}
|
|
47
|
+
} finally {
|
|
48
|
+
setBusy(false);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return /* @__PURE__ */ jsx(
|
|
52
|
+
Button,
|
|
53
|
+
{
|
|
54
|
+
icon: busy ? /* @__PURE__ */ jsx(Loader2, { className: "animate-spin" }) : /* @__PURE__ */ jsx(CopyPlus, {}),
|
|
55
|
+
variant: "outline",
|
|
56
|
+
onClick: duplicate,
|
|
57
|
+
disabled: busy,
|
|
58
|
+
title: "Make your own copy of this agent, in a new tab",
|
|
59
|
+
children: "Duplicate"
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
AgentPeekDuplicateButton
|
|
65
|
+
};
|
|
@@ -3356,7 +3356,8 @@ export declare const selectHighWarnings: (requestId: string) => ((state: {
|
|
|
3356
3356
|
argsMemoize: typeof import("reselect").weakMapMemoize;
|
|
3357
3357
|
};
|
|
3358
3358
|
/**
|
|
3359
|
-
* High-severity warnings
|
|
3359
|
+
* High-severity warnings, any warning that says the answer was kept (an
|
|
3360
|
+
* incomplete answer, never silent) and any warning whose `code` is explicitly
|
|
3360
3361
|
* promoted (see `PROMOTED_WARNING_CODES`). This is the set `AssistantWarning`
|
|
3361
3362
|
* actually renders inline — `selectHighWarnings` stays available for callers
|
|
3362
3363
|
* that want the stricter, level-only cut.
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
liveAgentCallTrace
|
|
4
4
|
} from "../utils/agent-call-trace.js";
|
|
5
5
|
import { createSelector } from "@reduxjs/toolkit";
|
|
6
|
+
import { isAnswerKeptWarning } from "./answer-kept-warning.js";
|
|
6
7
|
import { blockMediaFileId } from "../utils/block-media-identity.js";
|
|
7
8
|
import { DECISION_ANSWERS_BLOCK_TYPE } from "../../../../utils/content-ir/decision-answers-ids.js";
|
|
8
9
|
import { decisionAnswersText } from "@ai-matrx/agents/presentation/decision-answers";
|
|
@@ -872,7 +873,7 @@ const selectVisibleWarnings = (requestId) => createSelector(
|
|
|
872
873
|
(warnings) => {
|
|
873
874
|
if (!warnings) return void 0;
|
|
874
875
|
return warnings.filter(
|
|
875
|
-
(w) => w.level === "high" || PROMOTED_WARNING_CODES.has(w.code)
|
|
876
|
+
(w) => w.level === "high" || PROMOTED_WARNING_CODES.has(w.code) || isAnswerKeptWarning(w)
|
|
876
877
|
);
|
|
877
878
|
}
|
|
878
879
|
);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stream warning that says "the answer was delivered and kept, but the
|
|
3
|
+
* provider stopped early so the end may be missing". Leaf module (no store
|
|
4
|
+
* imports) so selectors on either side can read it.
|
|
5
|
+
*/
|
|
6
|
+
import type { WarningPayload } from "@ai-matrx/agents/generated/stream-events";
|
|
7
|
+
export declare function isAnswerKeptWarning(warning: WarningPayload): boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const ANSWER_KEPT_WARNING_CODES = /* @__PURE__ */ new Set([
|
|
2
|
+
"provider_recitation_stop",
|
|
3
|
+
"truncated_response"
|
|
4
|
+
]);
|
|
5
|
+
function isAnswerKeptWarning(warning) {
|
|
6
|
+
if (ANSWER_KEPT_WARNING_CODES.has(warning.code)) return true;
|
|
7
|
+
const md = warning.metadata;
|
|
8
|
+
return !!md && typeof md === "object" && md.answer_kept === true;
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
isAnswerKeptWarning
|
|
12
|
+
};
|
package/dist/agents/redux/execution-system/instance-ui-state/components/InstanceUIStateCore.js
CHANGED
|
@@ -49,7 +49,18 @@ function UuidField({ value, label }) {
|
|
|
49
49
|
),
|
|
50
50
|
/* @__PURE__ */ jsxs("span", { className: "flex-1 min-w-0 flex items-center gap-1.5", children: [
|
|
51
51
|
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-mono text-foreground break-all", children: value }),
|
|
52
|
-
/* @__PURE__ */ jsx(
|
|
52
|
+
/* @__PURE__ */ jsx(
|
|
53
|
+
Button,
|
|
54
|
+
{
|
|
55
|
+
variant: "quiet",
|
|
56
|
+
icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}),
|
|
57
|
+
glyphTone: copied ? "success" : void 0,
|
|
58
|
+
onClick: copy,
|
|
59
|
+
title: "Copy UUID",
|
|
60
|
+
"aria-label": "Copy UUID",
|
|
61
|
+
className: "shrink-0"
|
|
62
|
+
}
|
|
63
|
+
)
|
|
53
64
|
] })
|
|
54
65
|
] });
|
|
55
66
|
}
|
|
@@ -131,10 +142,21 @@ function SectionHeader({
|
|
|
131
142
|
open ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 text-muted-foreground shrink-0" }),
|
|
132
143
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground flex-1", children: title }),
|
|
133
144
|
count !== void 0 && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground/60 shrink-0 mr-1", children: count }),
|
|
134
|
-
copyValue && /* @__PURE__ */ jsx(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
copyValue && /* @__PURE__ */ jsx(
|
|
146
|
+
Button,
|
|
147
|
+
{
|
|
148
|
+
variant: "quiet",
|
|
149
|
+
icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}),
|
|
150
|
+
glyphTone: copied ? "success" : void 0,
|
|
151
|
+
onClick: (e) => {
|
|
152
|
+
e.stopPropagation();
|
|
153
|
+
copy();
|
|
154
|
+
},
|
|
155
|
+
title: "Copy section as JSON",
|
|
156
|
+
"aria-label": "Copy section as JSON",
|
|
157
|
+
className: "opacity-0 group-hover:opacity-100 shrink-0"
|
|
158
|
+
}
|
|
159
|
+
)
|
|
138
160
|
]
|
|
139
161
|
}
|
|
140
162
|
)
|
|
@@ -195,8 +217,28 @@ function RawJsonSection({ data }) {
|
|
|
195
217
|
const { copied, copy } = useCopyText(json);
|
|
196
218
|
return /* @__PURE__ */ jsxs("div", { className: "border border-border rounded-sm overflow-hidden", children: [
|
|
197
219
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-2 py-1.5", children: [
|
|
198
|
-
/* @__PURE__ */ jsx(
|
|
199
|
-
|
|
220
|
+
/* @__PURE__ */ jsx(
|
|
221
|
+
Tile,
|
|
222
|
+
{
|
|
223
|
+
variant: "quiet",
|
|
224
|
+
icon: open ? /* @__PURE__ */ jsx(ChevronDown, {}) : /* @__PURE__ */ jsx(ChevronRight, {}),
|
|
225
|
+
title: "Raw JSON",
|
|
226
|
+
onClick: () => setOpen((v) => !v),
|
|
227
|
+
className: "flex-1"
|
|
228
|
+
}
|
|
229
|
+
),
|
|
230
|
+
/* @__PURE__ */ jsx(
|
|
231
|
+
Button,
|
|
232
|
+
{
|
|
233
|
+
variant: "quiet",
|
|
234
|
+
icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}),
|
|
235
|
+
glyphTone: copied ? "success" : void 0,
|
|
236
|
+
onClick: copy,
|
|
237
|
+
title: "Copy full JSON",
|
|
238
|
+
"aria-label": "Copy full JSON",
|
|
239
|
+
className: "shrink-0"
|
|
240
|
+
}
|
|
241
|
+
),
|
|
200
242
|
open ? /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" })
|
|
201
243
|
] }),
|
|
202
244
|
open && /* @__PURE__ */ jsx("pre", { className: "text-xs font-mono text-foreground bg-muted/30 px-3 py-2 overflow-x-auto border-t border-border/40", children: json })
|
|
@@ -233,7 +275,14 @@ function InstanceUIStateCore({
|
|
|
233
275
|
agentName && instanceTitle !== agentName && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground truncate", children: agentName })
|
|
234
276
|
] }),
|
|
235
277
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
|
|
236
|
-
/* @__PURE__ */ jsx(
|
|
278
|
+
/* @__PURE__ */ jsx(
|
|
279
|
+
Badge,
|
|
280
|
+
{
|
|
281
|
+
className: "shrink-0",
|
|
282
|
+
tone: state.displayMode === "direct" ? "info" : "neutral",
|
|
283
|
+
children: state.displayMode
|
|
284
|
+
}
|
|
285
|
+
),
|
|
237
286
|
/* @__PURE__ */ jsx(CopyEntireButton, { json: instanceJson })
|
|
238
287
|
] })
|
|
239
288
|
] }),
|
|
@@ -478,7 +527,17 @@ function InstanceUIStateCore({
|
|
|
478
527
|
}
|
|
479
528
|
function CopyEntireButton({ json }) {
|
|
480
529
|
const { copied, copy } = useCopyText(json);
|
|
481
|
-
return /* @__PURE__ */ jsx(
|
|
530
|
+
return /* @__PURE__ */ jsx(
|
|
531
|
+
Button,
|
|
532
|
+
{
|
|
533
|
+
variant: "quiet",
|
|
534
|
+
icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}),
|
|
535
|
+
glyphTone: copied ? "success" : void 0,
|
|
536
|
+
onClick: copy,
|
|
537
|
+
title: "Copy full state as JSON",
|
|
538
|
+
"aria-label": "Copy full state as JSON"
|
|
539
|
+
}
|
|
540
|
+
);
|
|
482
541
|
}
|
|
483
542
|
export {
|
|
484
543
|
InstanceUIStateCore
|
package/dist/agents/redux/execution-system/instance-ui-state/components/InstanceUIStateList.js
CHANGED
|
@@ -77,10 +77,21 @@ function InstanceRow({
|
|
|
77
77
|
}
|
|
78
78
|
),
|
|
79
79
|
title && /* @__PURE__ */ jsx("span", { className: "text-[11px] font-mono text-muted-foreground/50 shrink-0 hidden group-hover:inline", children: shortId }),
|
|
80
|
-
/* @__PURE__ */ jsx(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
/* @__PURE__ */ jsx(
|
|
81
|
+
Button,
|
|
82
|
+
{
|
|
83
|
+
variant: "quiet",
|
|
84
|
+
icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}),
|
|
85
|
+
glyphTone: copied ? "success" : void 0,
|
|
86
|
+
onClick: (e) => {
|
|
87
|
+
e.stopPropagation();
|
|
88
|
+
copy();
|
|
89
|
+
},
|
|
90
|
+
title: "Copy conversation ID",
|
|
91
|
+
"aria-label": "Copy conversation ID",
|
|
92
|
+
className: "opacity-0 group-hover:opacity-100 shrink-0"
|
|
93
|
+
}
|
|
94
|
+
)
|
|
84
95
|
]
|
|
85
96
|
}
|
|
86
97
|
);
|
|
@@ -129,10 +140,21 @@ function AgentGroup({
|
|
|
129
140
|
}
|
|
130
141
|
),
|
|
131
142
|
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 shrink-0 ml-1", children: group.conversationIds.length }),
|
|
132
|
-
group.agentId && /* @__PURE__ */ jsx(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
143
|
+
group.agentId && /* @__PURE__ */ jsx(
|
|
144
|
+
Button,
|
|
145
|
+
{
|
|
146
|
+
variant: "quiet",
|
|
147
|
+
icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}),
|
|
148
|
+
glyphTone: copied ? "success" : void 0,
|
|
149
|
+
onClick: (e) => {
|
|
150
|
+
e.stopPropagation();
|
|
151
|
+
copy();
|
|
152
|
+
},
|
|
153
|
+
title: "Copy agent ID",
|
|
154
|
+
"aria-label": "Copy agent ID",
|
|
155
|
+
className: "opacity-0 group-hover:opacity-100 shrink-0 ml-0.5"
|
|
156
|
+
}
|
|
157
|
+
)
|
|
136
158
|
]
|
|
137
159
|
}
|
|
138
160
|
),
|
|
@@ -177,6 +177,12 @@ export declare function isFailedRecord(record: MessageRecord | undefined): boole
|
|
|
177
177
|
* that row reloaded with no error at all.
|
|
178
178
|
*/
|
|
179
179
|
export declare function abnormalFinishReason(record: MessageRecord | undefined): string | null;
|
|
180
|
+
/**
|
|
181
|
+
* The stop reason of a reloaded turn that is INCOMPLETE rather than failed:
|
|
182
|
+
* an answer-stands stop on a turn that actually holds answer text and is not
|
|
183
|
+
* saved failed. Null otherwise (normal finish, no content, safety stops…).
|
|
184
|
+
*/
|
|
185
|
+
export declare function incompleteAnswerReason(record: MessageRecord | undefined): string | null;
|
|
180
186
|
/** The sentence a reloaded early-stopped turn shows. */
|
|
181
187
|
export declare function earlyStopMessage(reason: string): string;
|
|
182
188
|
/**
|
|
@@ -285,7 +285,7 @@ function isFailedRecord(record) {
|
|
|
285
285
|
if (!!md && typeof md === "object" && !Array.isArray(md) && md.failed === true) {
|
|
286
286
|
return true;
|
|
287
287
|
}
|
|
288
|
-
return abnormalFinishReason(record) !== null;
|
|
288
|
+
return abnormalFinishReason(record) !== null && incompleteAnswerReason(record) === null;
|
|
289
289
|
}
|
|
290
290
|
const NORMAL_FINISH_REASONS = /* @__PURE__ */ new Set([
|
|
291
291
|
"stop",
|
|
@@ -307,6 +307,21 @@ function abnormalFinishReason(record) {
|
|
|
307
307
|
const normalized = reason.toLowerCase().replace(/^finishreason\./, "");
|
|
308
308
|
return NORMAL_FINISH_REASONS.has(normalized) ? null : normalized;
|
|
309
309
|
}
|
|
310
|
+
const ANSWER_STANDS_REASONS = /* @__PURE__ */ new Set([
|
|
311
|
+
"recitation",
|
|
312
|
+
"max_tokens",
|
|
313
|
+
"length",
|
|
314
|
+
"max_output_tokens"
|
|
315
|
+
]);
|
|
316
|
+
function incompleteAnswerReason(record) {
|
|
317
|
+
const reason = abnormalFinishReason(record);
|
|
318
|
+
if (!reason || !ANSWER_STANDS_REASONS.has(reason)) return null;
|
|
319
|
+
if (!record || record.status === "failed" || record.error) return null;
|
|
320
|
+
const md = record.metadata;
|
|
321
|
+
if (md && !Array.isArray(md) && md.failed === true)
|
|
322
|
+
return null;
|
|
323
|
+
return extractFlatText(record).trim().length > 0 ? reason : null;
|
|
324
|
+
}
|
|
310
325
|
function earlyStopMessage(reason) {
|
|
311
326
|
return reason === "max_tokens" || reason === "length" ? "The response hit its length limit before it finished." : "The response was stopped early by the model provider, so it may be incomplete.";
|
|
312
327
|
}
|
|
@@ -581,6 +596,7 @@ export {
|
|
|
581
596
|
extractInspectableText,
|
|
582
597
|
extractRecordError,
|
|
583
598
|
fenceBoundarySeparator,
|
|
599
|
+
incompleteAnswerReason,
|
|
584
600
|
isFailedRecord,
|
|
585
601
|
persistedBodyBlocks,
|
|
586
602
|
selectApiEndpointMode,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONE verdict for how a turn ended, read the same way live and after reload.
|
|
3
|
+
*
|
|
4
|
+
* - "failed" no usable answer, a safety/other stop, or the server saved it failed
|
|
5
|
+
* - "incomplete" the answer was delivered and stands, but the provider stopped
|
|
6
|
+
* early (recitation stop, length limit): content + a short notice
|
|
7
|
+
* - "complete" a normal finish
|
|
8
|
+
*
|
|
9
|
+
* Live reads the request status + stream warnings; reload reads the saved row
|
|
10
|
+
* (`isFailedRecord` / `incompleteAnswerReason`). A guard test drives both over
|
|
11
|
+
* the same cases so they cannot drift (incomplete-answer-live-equals-reload).
|
|
12
|
+
*/
|
|
13
|
+
import type { WarningPayload } from "@ai-matrx/agents/generated/stream-events";
|
|
14
|
+
import type { MessageRecord } from "./messages.slice.js";
|
|
15
|
+
import { isAnswerKeptWarning } from "../active-requests/answer-kept-warning.js";
|
|
16
|
+
export { isAnswerKeptWarning };
|
|
17
|
+
export type TurnOutcome = "complete" | "incomplete" | "failed";
|
|
18
|
+
export declare function liveTurnOutcome(request: {
|
|
19
|
+
status: string;
|
|
20
|
+
warnings?: readonly WarningPayload[];
|
|
21
|
+
} | undefined): TurnOutcome;
|
|
22
|
+
export declare function reloadTurnOutcome(record: MessageRecord | undefined): TurnOutcome;
|
|
23
|
+
/** The short label on an incomplete answer. A label, not prose. */
|
|
24
|
+
export declare function incompleteLabel(reason: string | null): string;
|
|
25
|
+
/** The reason-specific sentence kept under Details, for a reloaded turn. */
|
|
26
|
+
export declare function incompleteDetail(reason: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
earlyStopMessage,
|
|
3
|
+
incompleteAnswerReason,
|
|
4
|
+
isFailedRecord
|
|
5
|
+
} from "./messages.selectors.js";
|
|
6
|
+
import { isAnswerKeptWarning } from "../active-requests/answer-kept-warning.js";
|
|
7
|
+
function liveTurnOutcome(request) {
|
|
8
|
+
if (!request) return "complete";
|
|
9
|
+
if (request.status === "error") return "failed";
|
|
10
|
+
return (request.warnings ?? []).some(isAnswerKeptWarning) ? "incomplete" : "complete";
|
|
11
|
+
}
|
|
12
|
+
function reloadTurnOutcome(record) {
|
|
13
|
+
if (isFailedRecord(record)) return "failed";
|
|
14
|
+
return incompleteAnswerReason(record) ? "incomplete" : "complete";
|
|
15
|
+
}
|
|
16
|
+
function incompleteLabel(reason) {
|
|
17
|
+
return reason === "max_tokens" || reason === "length" || reason === "max_output_tokens" || reason === "truncated_response" ? "Cut off at its length limit" : "Ending may be cut off";
|
|
18
|
+
}
|
|
19
|
+
function incompleteDetail(reason) {
|
|
20
|
+
return earlyStopMessage(reason);
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
incompleteDetail,
|
|
24
|
+
incompleteLabel,
|
|
25
|
+
isAnswerKeptWarning,
|
|
26
|
+
liveTurnOutcome,
|
|
27
|
+
reloadTurnOutcome
|
|
28
|
+
};
|
|
@@ -13,7 +13,10 @@ import {
|
|
|
13
13
|
Save
|
|
14
14
|
} from "lucide-react";
|
|
15
15
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
MarkdownStream,
|
|
18
|
+
AudioOutputBlockSkeleton
|
|
19
|
+
} from "../../../host/markdown-slots.js";
|
|
17
20
|
import { useDomCapturePrint } from "../../../conversation/hooks/useDomCapturePrint.js";
|
|
18
21
|
import { useAppSelector, useAppDispatch } from "../../../store/hooks.js";
|
|
19
22
|
import { selectMessageHasUnsavedChanges } from "../../_legacy-stubs.js";
|
|
@@ -193,14 +193,7 @@ function UserMessage({
|
|
|
193
193
|
}
|
|
194
194
|
),
|
|
195
195
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 justify-end", children: [
|
|
196
|
-
/* @__PURE__ */ jsx(
|
|
197
|
-
Button,
|
|
198
|
-
{
|
|
199
|
-
variant: "quiet",
|
|
200
|
-
onClick: handleEditCancel,
|
|
201
|
-
children: "Cancel"
|
|
202
|
-
}
|
|
203
|
-
),
|
|
196
|
+
/* @__PURE__ */ jsx(Button, { variant: "quiet", onClick: handleEditCancel, children: "Cancel" }),
|
|
204
197
|
/* @__PURE__ */ jsx(
|
|
205
198
|
Button,
|
|
206
199
|
{
|
|
@@ -13,7 +13,10 @@ import {
|
|
|
13
13
|
Save
|
|
14
14
|
} from "lucide-react";
|
|
15
15
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
MarkdownStream,
|
|
18
|
+
AudioOutputBlockSkeleton
|
|
19
|
+
} from "../host/markdown-slots.js";
|
|
17
20
|
import { useDomCapturePrint } from "../conversation/hooks/useDomCapturePrint.js";
|
|
18
21
|
import { useAppSelector, useAppDispatch } from "../store/hooks.js";
|
|
19
22
|
import { selectMessageHasUnsavedChanges } from "./_legacy-stubs.js";
|
|
@@ -193,14 +193,7 @@ function UserMessage({
|
|
|
193
193
|
}
|
|
194
194
|
),
|
|
195
195
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 justify-end", children: [
|
|
196
|
-
/* @__PURE__ */ jsx(
|
|
197
|
-
Button,
|
|
198
|
-
{
|
|
199
|
-
variant: "quiet",
|
|
200
|
-
onClick: handleEditCancel,
|
|
201
|
-
children: "Cancel"
|
|
202
|
-
}
|
|
203
|
-
),
|
|
196
|
+
/* @__PURE__ */ jsx(Button, { variant: "quiet", onClick: handleEditCancel, children: "Cancel" }),
|
|
204
197
|
/* @__PURE__ */ jsx(
|
|
205
198
|
Button,
|
|
206
199
|
{
|
|
@@ -106,10 +106,17 @@ const ErrorView = ({
|
|
|
106
106
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
107
107
|
routed ? /* @__PURE__ */ jsx(Route, { className: "h-3.5 w-3.5 text-muted-foreground" }) : /* @__PURE__ */ jsx(CircleAlert, { className: "h-3.5 w-3.5 text-destructive" }),
|
|
108
108
|
/* @__PURE__ */ jsx("span", { children: routed ? "Routed" : "Error" }),
|
|
109
|
-
entry.errorType && /* @__PURE__ */ jsxs(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
entry.errorType && /* @__PURE__ */ jsxs(
|
|
110
|
+
Badge,
|
|
111
|
+
{
|
|
112
|
+
variant: "outline",
|
|
113
|
+
className: "px-1.5 py-0 font-mono text-[10px]",
|
|
114
|
+
children: [
|
|
115
|
+
entry.errorType,
|
|
116
|
+
/* @__PURE__ */ jsx(ErrorAlchemyMenu, { error: entry.errorType })
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
)
|
|
113
120
|
] }),
|
|
114
121
|
/* @__PURE__ */ jsx(
|
|
115
122
|
CopyButtons,
|
|
@@ -85,7 +85,17 @@ function VoiceDebugPanel({
|
|
|
85
85
|
};
|
|
86
86
|
return /* @__PURE__ */ jsxs("div", { className: "pointer-events-auto w-full overflow-hidden rounded-lg border border-border bg-zinc-950/95 text-zinc-100 shadow-lg backdrop-blur", children: [
|
|
87
87
|
/* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-between gap-2 px-3 py-1.5", children: [
|
|
88
|
-
/* @__PURE__ */ jsx(
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
Tile,
|
|
90
|
+
{
|
|
91
|
+
variant: "quiet",
|
|
92
|
+
icon: /* @__PURE__ */ jsx(Bug, {}),
|
|
93
|
+
glyphTone: "success",
|
|
94
|
+
title: "Live voice debug",
|
|
95
|
+
onClick: () => setOpen((o) => !o),
|
|
96
|
+
className: "flex-1"
|
|
97
|
+
}
|
|
98
|
+
),
|
|
89
99
|
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
90
100
|
/* @__PURE__ */ jsx(
|
|
91
101
|
Badge,
|
|
@@ -106,7 +116,15 @@ function VoiceDebugPanel({
|
|
|
106
116
|
glyphTone: copied ? "success" : void 0
|
|
107
117
|
}
|
|
108
118
|
),
|
|
109
|
-
/* @__PURE__ */ jsx(
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
Button,
|
|
121
|
+
{
|
|
122
|
+
variant: "quiet",
|
|
123
|
+
icon: open ? /* @__PURE__ */ jsx(ChevronDown, {}) : /* @__PURE__ */ jsx(ChevronUp, {}),
|
|
124
|
+
"aria-label": open ? "Collapse" : "Expand",
|
|
125
|
+
onClick: () => setOpen((o) => !o)
|
|
126
|
+
}
|
|
127
|
+
)
|
|
110
128
|
] })
|
|
111
129
|
] }),
|
|
112
130
|
open && /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t border-zinc-800 px-3 py-2", children: [
|
|
@@ -164,7 +182,8 @@ function VoiceDebugPanel({
|
|
|
164
182
|
" ",
|
|
165
183
|
/* @__PURE__ */ jsxs("span", { className: "text-zinc-200", children: [
|
|
166
184
|
flags.lastEventType ?? "\u2014",
|
|
167
|
-
" \xB7
|
|
185
|
+
" \xB7",
|
|
186
|
+
" ",
|
|
168
187
|
formatRelativeTime(flags.lastEventAt)
|
|
169
188
|
] })
|
|
170
189
|
] }),
|
|
@@ -102,10 +102,21 @@ function SidebarRow({
|
|
|
102
102
|
children: row.label
|
|
103
103
|
}
|
|
104
104
|
),
|
|
105
|
-
/* @__PURE__ */ jsx(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
/* @__PURE__ */ jsx(
|
|
106
|
+
Button,
|
|
107
|
+
{
|
|
108
|
+
variant: "quiet",
|
|
109
|
+
icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}),
|
|
110
|
+
glyphTone: copied ? "success" : void 0,
|
|
111
|
+
onClick: (e) => {
|
|
112
|
+
e.stopPropagation();
|
|
113
|
+
copy();
|
|
114
|
+
},
|
|
115
|
+
title: "Copy conversation id",
|
|
116
|
+
"aria-label": "Copy conversation id",
|
|
117
|
+
className: "opacity-0 group-hover:opacity-100 shrink-0"
|
|
118
|
+
}
|
|
119
|
+
),
|
|
109
120
|
/* @__PURE__ */ jsx(
|
|
110
121
|
EntityDoorControls,
|
|
111
122
|
{
|
|
@@ -354,7 +365,11 @@ function ObservationalMemoryWindowInner({
|
|
|
354
365
|
sourceFeature: "agent-builder",
|
|
355
366
|
surfaceName: OBSERVATIONAL_MEMORY_SURFACE_NAME,
|
|
356
367
|
contentSource: { type: "raw" },
|
|
357
|
-
entity: selectedConversationId ? {
|
|
368
|
+
entity: selectedConversationId ? {
|
|
369
|
+
type: "cx_observational_memory",
|
|
370
|
+
id: selectedConversationId,
|
|
371
|
+
title: "Observational memory"
|
|
372
|
+
} : void 0,
|
|
358
373
|
children: selectedConversationId ? /* @__PURE__ */ jsx(
|
|
359
374
|
ObservationalMemoryCore,
|
|
360
375
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { useAppDispatch } from "../../../store/hooks.js";
|
|
5
5
|
import { closeOverlay, CHAT_WINDOWS } from "../../../host/windows.js";
|
|
@@ -20,7 +20,7 @@ import { selectInstanceAgentId } from "../../../agents/redux/execution-system/in
|
|
|
20
20
|
import { selectAgentName } from "../../../agents/redux/agent-definition/selectors.js";
|
|
21
21
|
import { NonEditableContextMenu } from "../../../host/ui-slots.js";
|
|
22
22
|
import { buildAgentMenuSection, agentEntityRef } from "../../../agents/menu/agent-actions.js";
|
|
23
|
-
import {
|
|
23
|
+
import { Tile } from "@ai-matrx/design-system/controls";
|
|
24
24
|
const OVERLAY_ID = CHAT_WINDOWS.runControlsWindow;
|
|
25
25
|
function RunControlsWindowInner({
|
|
26
26
|
conversationId,
|
|
@@ -104,10 +104,23 @@ function RunControlsWindowInner({
|
|
|
104
104
|
children: rc.tabs.map((t) => {
|
|
105
105
|
const Icon = t.icon;
|
|
106
106
|
const on = activeTab === t.id;
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
return (
|
|
108
|
+
// A list of sections is rows, start-aligned — never centred buttons.
|
|
109
|
+
/* @__PURE__ */ jsx(
|
|
110
|
+
Tile,
|
|
111
|
+
{
|
|
112
|
+
variant: "quiet",
|
|
113
|
+
selected: on,
|
|
114
|
+
icon: /* @__PURE__ */ jsx(Icon, {}),
|
|
115
|
+
title: t.label,
|
|
116
|
+
end: rc.tabTrailing(t.id),
|
|
117
|
+
role: "tab",
|
|
118
|
+
"aria-selected": on,
|
|
119
|
+
onClick: () => setTab(t.id)
|
|
120
|
+
},
|
|
121
|
+
t.id
|
|
122
|
+
)
|
|
123
|
+
);
|
|
111
124
|
})
|
|
112
125
|
}
|
|
113
126
|
),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/chat",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "THE one AI Matrx person-to-agent chat system: the full chat (running agents, conversations, tool calls, streaming, context, surfaces, voice) as per-module subpaths @ai-matrx/chat/<domain>/<module> over a host contract (/host, /host/react) whose only required value is a db; plus the kernel compatibility entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) until P28.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -202,23 +202,23 @@
|
|
|
202
202
|
"uuid": "^14.0.2",
|
|
203
203
|
"zod": "^4.6.5",
|
|
204
204
|
"@ai-matrx/agents": "latest",
|
|
205
|
-
"@ai-matrx/browser-audio": "latest",
|
|
206
|
-
"@ai-matrx/associations": "latest",
|
|
207
205
|
"@ai-matrx/alchemy": "latest",
|
|
206
|
+
"@ai-matrx/associations": "latest",
|
|
207
|
+
"@ai-matrx/browser-audio": "latest",
|
|
208
208
|
"@ai-matrx/canvas": "latest",
|
|
209
209
|
"@ai-matrx/content-ir": "latest",
|
|
210
210
|
"@ai-matrx/content-ir-react": "latest",
|
|
211
211
|
"@ai-matrx/data": "latest",
|
|
212
|
-
"@ai-matrx/diff": "latest",
|
|
213
212
|
"@ai-matrx/design-system": "latest",
|
|
213
|
+
"@ai-matrx/diff": "latest",
|
|
214
214
|
"@ai-matrx/icons": "latest",
|
|
215
|
-
"@ai-matrx/media": "latest",
|
|
216
215
|
"@ai-matrx/kit": "latest",
|
|
216
|
+
"@ai-matrx/media": "latest",
|
|
217
217
|
"@ai-matrx/print": "latest",
|
|
218
218
|
"@ai-matrx/realtime": "latest",
|
|
219
|
+
"@ai-matrx/records": "latest",
|
|
219
220
|
"@ai-matrx/records-ui": "latest",
|
|
220
|
-
"@ai-matrx/tap-target": "latest"
|
|
221
|
-
"@ai-matrx/records": "latest"
|
|
221
|
+
"@ai-matrx/tap-target": "latest"
|
|
222
222
|
},
|
|
223
223
|
"devDependencies": {
|
|
224
224
|
"@arethetypeswrong/cli": "^0.18.5",
|