@ai-matrx/chat 0.7.9 → 0.7.10
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 +14 -0
- package/dist/agents/components/messages-display/AgentConversationDisplay.d.ts +1 -2
- package/dist/agents/components/messages-display/AgentConversationDisplay.js +3 -7
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.d.ts +1 -3
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.js +1 -23
- package/dist/agents/components/messages-display/assistant/AssistantTurnGroup.d.ts +1 -2
- package/dist/agents/components/messages-display/assistant/AssistantTurnGroup.js +2 -4
- package/dist/agents/components/messages-display/assistant/answerless-turn.d.ts +0 -2
- package/dist/agents/components/messages-display/assistant/answerless-turn.js +0 -1
- package/dist/agents/components/shared/AgentConversationColumn.js +0 -1
- package/dist/agents/hooks/useHeadlessAgentJson.js +9 -1
- package/dist/agents/redux/execution-system/thunks/record-run-output-refused.d.ts +14 -0
- package/dist/agents/redux/execution-system/thunks/record-run-output-refused.js +24 -0
- package/package.json +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.7.10
|
|
4
|
+
### a refused answer is a failed run (lane M3)
|
|
5
|
+
|
|
6
|
+
- **`useHeadlessAgentJson.run`**: when `coerce` refuses the answer the model gave, the run is recorded as failed
|
|
7
|
+
(`chat.user_request` status `failed`, `error` = the reason, `metadata.output_refused`) through the guarded
|
|
8
|
+
`mergeJsonColumn` write (`recordRunOutputRefused`); a write that does not land is a `console.error`. Before, the run
|
|
9
|
+
stayed `completed` with `error` null forever. The error is re-thrown unchanged for the surface to say plainly.
|
|
10
|
+
- **Consumer action:** none.
|
|
11
|
+
|
|
3
12
|
## 0.7.9
|
|
4
13
|
|
|
5
14
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
@@ -15,6 +24,11 @@ No source changes intended and no consumer action required.
|
|
|
15
24
|
## 0.7.7
|
|
16
25
|
### Unreleased
|
|
17
26
|
|
|
27
|
+
- **No first-load layout shift from assistant messages** (spend register item 32): `AgentAssistantMessage` no longer swaps a fixed-height
|
|
28
|
+
skeleton for the markdown after 140 ms (measured CLS 0.48 on the run page). Persisted answers render in place on first paint.
|
|
29
|
+
`deferColdMarkdown` is removed from `AgentAssistantMessage`, `AssistantTurnGroup` and `AgentConversationDisplay` (the column keeps its
|
|
30
|
+
own history staging). **Consumer action:** none.
|
|
31
|
+
|
|
18
32
|
- **`continueAgentJson`** (`agents/redux/execution-system/thunks/continue-agent-json`): the NEXT turn of a
|
|
19
33
|
conversation whose first turn ran headlessly, waited on like the first (`adoptHeadlessAgentJson`). A turn is
|
|
20
34
|
the person's words (`userInput`) or a turn the HOST writes (`hostTurn` — sent as `host_turn`, never
|
|
@@ -8,9 +8,8 @@ interface AgentConversationDisplayProps {
|
|
|
8
8
|
*/
|
|
9
9
|
surfaceKey?: string;
|
|
10
10
|
compact?: boolean;
|
|
11
|
-
deferColdMarkdown?: boolean;
|
|
12
11
|
fallbackVisibleGroupLimit?: number | null;
|
|
13
12
|
bottomPinned?: boolean;
|
|
14
13
|
}
|
|
15
|
-
export declare function AgentConversationDisplay({ conversationId, surfaceKey, compact,
|
|
14
|
+
export declare function AgentConversationDisplay({ conversationId, surfaceKey, compact, fallbackVisibleGroupLimit, bottomPinned, }: AgentConversationDisplayProps): import("react").JSX.Element;
|
|
16
15
|
export {};
|
|
@@ -53,7 +53,6 @@ function AgentConversationDisplay({
|
|
|
53
53
|
conversationId,
|
|
54
54
|
surfaceKey,
|
|
55
55
|
compact = false,
|
|
56
|
-
deferColdMarkdown = false,
|
|
57
56
|
fallbackVisibleGroupLimit = null,
|
|
58
57
|
bottomPinned = false
|
|
59
58
|
}) {
|
|
@@ -262,8 +261,7 @@ function AgentConversationDisplay({
|
|
|
262
261
|
isStreamActive: false,
|
|
263
262
|
surfaceKey,
|
|
264
263
|
compact: true,
|
|
265
|
-
hideActionBar: true
|
|
266
|
-
deferColdMarkdown
|
|
264
|
+
hideActionBar: true
|
|
267
265
|
},
|
|
268
266
|
member.messageId
|
|
269
267
|
)
|
|
@@ -307,8 +305,7 @@ function AgentConversationDisplay({
|
|
|
307
305
|
isStreamActive: group.isStreamActive,
|
|
308
306
|
surfaceKey,
|
|
309
307
|
compact,
|
|
310
|
-
canRetry: group.canRetry
|
|
311
|
-
deferColdMarkdown
|
|
308
|
+
canRetry: group.canRetry
|
|
312
309
|
}
|
|
313
310
|
)
|
|
314
311
|
},
|
|
@@ -327,8 +324,7 @@ function AgentConversationDisplay({
|
|
|
327
324
|
conversationId,
|
|
328
325
|
surfaceKey,
|
|
329
326
|
compact,
|
|
330
|
-
members: group.members
|
|
331
|
-
deferColdMarkdown
|
|
327
|
+
members: group.members
|
|
332
328
|
}
|
|
333
329
|
)
|
|
334
330
|
},
|
|
@@ -33,8 +33,6 @@ export interface AgentAssistantMessageProps {
|
|
|
33
33
|
* button. Retry is non-destructive (the failed turn stays in history).
|
|
34
34
|
*/
|
|
35
35
|
canRetry?: boolean;
|
|
36
|
-
/** Chat cold-load only: show a short text skeleton before DB markdown mounts. */
|
|
37
|
-
deferColdMarkdown?: boolean;
|
|
38
36
|
/**
|
|
39
37
|
* False for the intermediate iterations of a multi-step agentic turn. Only
|
|
40
38
|
* the LAST member of a turn carries the answer, so only it may say "this run
|
|
@@ -43,4 +41,4 @@ export interface AgentAssistantMessageProps {
|
|
|
43
41
|
*/
|
|
44
42
|
isTurnAnswer?: boolean;
|
|
45
43
|
}
|
|
46
|
-
export declare function AgentAssistantMessage({ conversationId, requestId, messageId, isStreamActive, recordMessageIds, streamSlotStart, streamSlotEnd, surfaceKey, hideActionBar, canRetry,
|
|
44
|
+
export declare function AgentAssistantMessage({ conversationId, requestId, messageId, isStreamActive, recordMessageIds, streamSlotStart, streamSlotEnd, surfaceKey, hideActionBar, canRetry, isTurnAnswer, }: AgentAssistantMessageProps): import("react").JSX.Element;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import {
|
|
4
|
-
startTransition,
|
|
5
4
|
useCallback,
|
|
6
5
|
useMemo,
|
|
7
6
|
useState,
|
|
@@ -117,7 +116,6 @@ function AgentAssistantMessage({
|
|
|
117
116
|
surfaceKey,
|
|
118
117
|
hideActionBar = false,
|
|
119
118
|
canRetry = false,
|
|
120
|
-
deferColdMarkdown = false,
|
|
121
119
|
isTurnAnswer = true
|
|
122
120
|
}) {
|
|
123
121
|
useDebugContext("AgentAssistantMessage");
|
|
@@ -137,17 +135,7 @@ function AgentAssistantMessage({
|
|
|
137
135
|
}, [surfaceKey, conversationId, messageId, requestId, isStreamActive]);
|
|
138
136
|
const dispatch = useAppDispatch();
|
|
139
137
|
const [retrying, setRetrying] = useState(false);
|
|
140
|
-
const shouldDeferColdMarkdown = deferColdMarkdown && !requestId && !!messageId && !isStreamActive;
|
|
141
|
-
const [coldMarkdownSettledMessageId, setColdMarkdownSettledMessageId] = useState(null);
|
|
142
|
-
const coldMarkdownReady = !shouldDeferColdMarkdown || coldMarkdownSettledMessageId === messageId;
|
|
143
138
|
const [providerRetryBusyAction, setProviderRetryBusyAction] = useState(null);
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
if (!shouldDeferColdMarkdown || !messageId) return void 0;
|
|
146
|
-
const timer = window.setTimeout(() => {
|
|
147
|
-
startTransition(() => setColdMarkdownSettledMessageId(messageId));
|
|
148
|
-
}, 140);
|
|
149
|
-
return () => window.clearTimeout(timer);
|
|
150
|
-
}, [shouldDeferColdMarkdown, messageId]);
|
|
151
139
|
const { captureRef, isCapturing, captureAsPDF } = useDomCapturePrint();
|
|
152
140
|
const handleFullPrint = useCallback(() => {
|
|
153
141
|
captureAsPDF({
|
|
@@ -308,7 +296,6 @@ function AgentAssistantMessage({
|
|
|
308
296
|
rowParts: extractContentBlocks(record)
|
|
309
297
|
}),
|
|
310
298
|
failed,
|
|
311
|
-
coldMarkdownReady,
|
|
312
299
|
messageId,
|
|
313
300
|
renderedText,
|
|
314
301
|
attachmentCount: attachmentParts.length,
|
|
@@ -394,7 +381,7 @@ function AgentAssistantMessage({
|
|
|
394
381
|
"data-message-id": messageId ?? void 0,
|
|
395
382
|
className: "group/assistant-msg rounded transition-shadow",
|
|
396
383
|
children: [
|
|
397
|
-
|
|
384
|
+
bufferStream && isStreamActive && !failed ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
398
385
|
showProviderRetry && providerRetry && /* @__PURE__ */ jsx(
|
|
399
386
|
ProviderRetryCard,
|
|
400
387
|
{
|
|
@@ -538,15 +525,6 @@ function readErrorDoor(details) {
|
|
|
538
525
|
const { label, href } = door;
|
|
539
526
|
return typeof label === "string" && typeof href === "string" && href.startsWith("/") ? { label, href } : null;
|
|
540
527
|
}
|
|
541
|
-
function AssistantMarkdownSkeleton() {
|
|
542
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 py-1", "aria-hidden": true, children: [
|
|
543
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-[96%] animate-pulse rounded bg-muted/55" }),
|
|
544
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-[88%] animate-pulse rounded bg-muted/50" }),
|
|
545
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-[72%] animate-pulse rounded bg-muted/45" }),
|
|
546
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-[94%] animate-pulse rounded bg-muted/40" }),
|
|
547
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-[42%] animate-pulse rounded bg-muted/35" })
|
|
548
|
-
] });
|
|
549
|
-
}
|
|
550
528
|
export {
|
|
551
529
|
AgentAssistantMessage
|
|
552
530
|
};
|
|
@@ -16,7 +16,6 @@ interface AssistantTurnGroupProps {
|
|
|
16
16
|
compact?: boolean;
|
|
17
17
|
/** Every assistant member of this logical turn, in transcript order. */
|
|
18
18
|
members: AssistantTurnGroupMember[];
|
|
19
|
-
deferColdMarkdown?: boolean;
|
|
20
19
|
}
|
|
21
|
-
export declare function AssistantTurnGroup({ conversationId, surfaceKey, compact, members: rawMembers,
|
|
20
|
+
export declare function AssistantTurnGroup({ conversationId, surfaceKey, compact, members: rawMembers, }: AssistantTurnGroupProps): import("react").JSX.Element;
|
|
22
21
|
export {};
|
|
@@ -26,8 +26,7 @@ function AssistantTurnGroup({
|
|
|
26
26
|
conversationId,
|
|
27
27
|
surfaceKey,
|
|
28
28
|
compact = false,
|
|
29
|
-
members: rawMembers
|
|
30
|
-
deferColdMarkdown = false
|
|
29
|
+
members: rawMembers
|
|
31
30
|
}) {
|
|
32
31
|
const { captureRef, isCapturing, captureAsPDF } = useDomCapturePrint();
|
|
33
32
|
const persistedView = useAppSelector(
|
|
@@ -96,8 +95,7 @@ function AssistantTurnGroup({
|
|
|
96
95
|
surfaceKey,
|
|
97
96
|
compact,
|
|
98
97
|
hideActionBar: true,
|
|
99
|
-
isTurnAnswer: idx === members.length - 1 && m.streamSlotEnd === void 0
|
|
100
|
-
deferColdMarkdown: deferColdMarkdown && idx < members.length - 1
|
|
98
|
+
isTurnAnswer: idx === members.length - 1 && m.streamSlotEnd === void 0
|
|
101
99
|
}
|
|
102
100
|
) }, m.key))
|
|
103
101
|
}
|
|
@@ -113,8 +113,6 @@ export interface AnswerlessTurnInput {
|
|
|
113
113
|
isStreamActive: boolean;
|
|
114
114
|
/** True when the turn failed — the error path owns that case. */
|
|
115
115
|
failed: boolean;
|
|
116
|
-
/** False while a cold-load skeleton stands in for the persisted markdown. */
|
|
117
|
-
coldMarkdownReady: boolean;
|
|
118
116
|
/** `cx_message.id`; null/undefined until the row settles. */
|
|
119
117
|
messageId: string | null | undefined;
|
|
120
118
|
/** The text this turn renders (citation markers included). */
|
|
@@ -31,7 +31,6 @@ function isAnswerlessTurn(input) {
|
|
|
31
31
|
if (input.isStreamActive) return false;
|
|
32
32
|
if (input.awaitingPerson) return false;
|
|
33
33
|
if (input.failed) return false;
|
|
34
|
-
if (!input.coldMarkdownReady) return false;
|
|
35
34
|
if (!input.messageId) return false;
|
|
36
35
|
if (input.attachmentCount > 0) return false;
|
|
37
36
|
if (input.mediaBlockCount > 0) return false;
|
|
@@ -299,7 +299,6 @@ function AgentConversationColumn({
|
|
|
299
299
|
{
|
|
300
300
|
conversationId: displayId,
|
|
301
301
|
surfaceKey,
|
|
302
|
-
deferColdMarkdown,
|
|
303
302
|
bottomPinned: shouldPinColdScroll,
|
|
304
303
|
fallbackVisibleGroupLimit: deferColdMarkdown ? chatVisibleGroupLimit ?? CHAT_INITIAL_VISIBLE_GROUPS : null
|
|
305
304
|
}
|
|
@@ -3,6 +3,7 @@ import { useCallback, useState } from "react";
|
|
|
3
3
|
import { useAppDispatch, useAppSelector, useAppStore } from "../../store/hooks.js";
|
|
4
4
|
import { selectConversationRequestIds } from "../redux/execution-system/active-requests/active-requests.selectors.js";
|
|
5
5
|
import { useRetainRequestForViewer } from "../redux/execution-system/active-requests/useRetainRequestForViewer.js";
|
|
6
|
+
import { recordRunOutputRefused } from "../redux/execution-system/thunks/record-run-output-refused.js";
|
|
6
7
|
import {
|
|
7
8
|
runHeadlessAgentJson
|
|
8
9
|
} from "../redux/execution-system/thunks/run-headless-agent-json.js";
|
|
@@ -49,7 +50,14 @@ function useHeadlessAgentJson() {
|
|
|
49
50
|
...result.conversationId ? { conversationId: result.conversationId } : {}
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
+
if (!opts.coerce) return result.data;
|
|
54
|
+
try {
|
|
55
|
+
return opts.coerce(result.data, result);
|
|
56
|
+
} catch (refused) {
|
|
57
|
+
const why = refused instanceof Error ? refused.message : String(refused);
|
|
58
|
+
if (result.requestId) void recordRunOutputRefused(result.requestId, why);
|
|
59
|
+
throw refused;
|
|
60
|
+
}
|
|
53
61
|
} catch (e) {
|
|
54
62
|
const message = e instanceof Error ? e.message : "Failed to run the agent";
|
|
55
63
|
setError(message);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recordRunOutputRefused — a run whose answer the BROWSER refused is a failed run.
|
|
3
|
+
*
|
|
4
|
+
* The server marks a run `completed` the moment the model stops; whether the answer was usable is only
|
|
5
|
+
* known where it is read (the feature's `coerce`, its own check). Before this, such a run stayed
|
|
6
|
+
* `completed` with `error` null forever (Make's describe box, 2026-10-09: "personal" is not one of
|
|
7
|
+
* organization, individual). The refusal is written onto the run's own `chat.user_request` row —
|
|
8
|
+
* status `failed`, `error` = the reason, and `metadata.output_refused` — through the guarded
|
|
9
|
+
* read-modify-write (`mergeJsonColumn`, a version guard, never a blind update).
|
|
10
|
+
*
|
|
11
|
+
* Never silent: a write that does not land is a `console.error` naming the run.
|
|
12
|
+
*/
|
|
13
|
+
export declare const OUTPUT_REFUSED_METADATA_KEY = "output_refused";
|
|
14
|
+
export declare function recordRunOutputRefused(requestId: string, reason: string): Promise<boolean>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mergeJsonColumn } from "@ai-matrx/data/db";
|
|
2
|
+
import { supabase } from "../../../../host/db.js";
|
|
3
|
+
import { hasBrowserSession } from "../../../../host/identity.js";
|
|
4
|
+
const OUTPUT_REFUSED_METADATA_KEY = "output_refused";
|
|
5
|
+
async function recordRunOutputRefused(requestId, reason) {
|
|
6
|
+
if (!requestId || !await hasBrowserSession()) return false;
|
|
7
|
+
const result = await mergeJsonColumn({
|
|
8
|
+
fetchCurrent: () => supabase.schema("chat").from("user_request").select("id, version, metadata").eq("id", requestId).maybeSingle(),
|
|
9
|
+
readColumn: (row) => row.metadata,
|
|
10
|
+
merge: (current) => ({ ...current, [OUTPUT_REFUSED_METADATA_KEY]: { reason, at: (/* @__PURE__ */ new Date()).toISOString() } }),
|
|
11
|
+
applyUpdate: ({ value, expectedVersion, nextVersion }) => supabase.schema("chat").from("user_request").update({ metadata: value, version: nextVersion, status: "failed", error: reason.slice(0, 500) }).eq("id", requestId).eq("version", expectedVersion).select("id, version, metadata").maybeSingle()
|
|
12
|
+
});
|
|
13
|
+
if (result.status !== "saved") {
|
|
14
|
+
console.error(
|
|
15
|
+
`[run-output-refused] Could not record the refusal on run ${requestId}: ` + (result.status === "error" ? String(result.error) : `write ${result.status}`) + `. The run still reads as completed. Reason: ${reason}`
|
|
16
|
+
);
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
OUTPUT_REFUSED_METADATA_KEY,
|
|
23
|
+
recordRunOutputRefused
|
|
24
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/chat",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.10",
|
|
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 explicit per-module subpaths @ai-matrx/chat/<domain>/<module> (no wildcards: scripts/public-modules.json) over a host contract (/host, /host/react) whose only required value is a db. One chat, one renderer: the old kernel entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) are retired.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -4681,25 +4681,25 @@
|
|
|
4681
4681
|
"server-only": "^0.0.1",
|
|
4682
4682
|
"uuid": "^14.0.2",
|
|
4683
4683
|
"zod": "^4.6.5",
|
|
4684
|
-
"@ai-matrx/agents": "latest",
|
|
4685
4684
|
"@ai-matrx/alchemy": "latest",
|
|
4686
4685
|
"@ai-matrx/associations": "latest",
|
|
4687
4686
|
"@ai-matrx/browser-audio": "latest",
|
|
4688
4687
|
"@ai-matrx/canvas": "latest",
|
|
4689
4688
|
"@ai-matrx/content-ir": "latest",
|
|
4690
|
-
"@ai-matrx/
|
|
4691
|
-
"@ai-matrx/
|
|
4692
|
-
"@ai-matrx/kit": "latest",
|
|
4693
|
-
"@ai-matrx/media": "latest",
|
|
4689
|
+
"@ai-matrx/content-ir-react": "latest",
|
|
4690
|
+
"@ai-matrx/agents": "latest",
|
|
4694
4691
|
"@ai-matrx/icons": "latest",
|
|
4695
|
-
"@ai-matrx/print": "latest",
|
|
4696
4692
|
"@ai-matrx/diff": "latest",
|
|
4693
|
+
"@ai-matrx/media": "latest",
|
|
4694
|
+
"@ai-matrx/kit": "latest",
|
|
4695
|
+
"@ai-matrx/print": "latest",
|
|
4697
4696
|
"@ai-matrx/realtime": "latest",
|
|
4698
|
-
"@ai-matrx/records": "latest",
|
|
4699
4697
|
"@ai-matrx/records-ui": "latest",
|
|
4698
|
+
"@ai-matrx/design-system": "latest",
|
|
4700
4699
|
"@ai-matrx/rich-content": "latest",
|
|
4701
|
-
"@ai-matrx/
|
|
4702
|
-
"@ai-matrx/
|
|
4700
|
+
"@ai-matrx/records": "latest",
|
|
4701
|
+
"@ai-matrx/data": "latest",
|
|
4702
|
+
"@ai-matrx/rich-editor": "latest"
|
|
4703
4703
|
},
|
|
4704
4704
|
"devDependencies": {
|
|
4705
4705
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
@@ -4818,5 +4818,5 @@
|
|
|
4818
4818
|
"check:side-effects": "node ../scripts/check-side-effects-listed.mjs . && node ../scripts/check-side-effects-listed.mjs --self-test",
|
|
4819
4819
|
"check:public-surface": "node ./scripts/sync-public-surface.mjs --check && node ./scripts/sync-public-surface.mjs --self-test"
|
|
4820
4820
|
},
|
|
4821
|
-
"gitHead": "
|
|
4821
|
+
"gitHead": "c1236d2d5f8a42087300bafce53317efb25700af"
|
|
4822
4822
|
}
|