@ai-matrx/chat 0.4.26 → 0.4.29
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 +27 -0
- package/dist/agents/coding-session-reply/CodingReplyNotice.d.ts +15 -0
- package/dist/agents/coding-session-reply/CodingReplyNotice.js +60 -0
- package/dist/agents/coding-session-reply/reply-door.d.ts +53 -0
- package/dist/agents/coding-session-reply/reply-door.js +40 -0
- package/dist/agents/coding-session-reply/useChatServerResponderRead.d.ts +6 -0
- package/dist/agents/coding-session-reply/useChatServerResponderRead.js +28 -0
- package/dist/agents/coding-session-reply/useCodingReplyResponder.d.ts +20 -0
- package/dist/agents/coding-session-reply/useCodingReplyResponder.js +37 -0
- package/dist/agents/components/messages-display/assistant/AssistantMessageFooter.d.ts +1 -0
- package/dist/agents/components/messages-display/assistant/AssistantMessageFooter.js +1 -0
- package/dist/agents/components/messages-display/user/UserActionBar.d.ts +1 -0
- package/dist/agents/components/messages-display/user/UserActionBar.js +1 -0
- package/dist/agents/redux/execution-system/instance-user-input/append-to-composer-draft.thunk.d.ts +3 -0
- package/dist/agents/redux/execution-system/instance-user-input/append-to-composer-draft.thunk.js +19 -0
- package/dist/agents/redux/execution-system/utils/required-organization.js +3 -1
- package/dist/host/conversation-hooks.d.ts +5 -1
- package/dist/host/conversation-hooks.js +16 -14
- package/dist/host/ui-slots.d.ts +1 -1
- package/dist/host/ui-slots.js +6 -28
- package/dist/ui/markdown-stream/MarkdownStreamImpl.d.ts +1 -0
- package/dist/ui/markdown-stream/MarkdownStreamImpl.js +1 -0
- package/dist/ui/markdown-stream/rich-content-defaults.d.ts +29 -0
- package/dist/ui/markdown-stream/rich-content-defaults.js +26 -0
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.4.29 — a bare host needs only a db: identity and rich-content optionals are the package's own
|
|
4
|
+
|
|
5
|
+
- The signed-in user, email, name and admin level the package reads (`buildAmbientContext`,
|
|
6
|
+
`isFingerprintGuestExecution`) come from its own `chatHost.identity`, never a host `userAuth` /
|
|
7
|
+
`userProfile` slice. P24g's host-read allow-list is now empty.
|
|
8
|
+
- `useHideReasoning`, `useHideToolResults`, `useMachineFramesVisible` answer from chat's own settings
|
|
9
|
+
when the host registers no rich-content `app` bindings; `useDocumentDialogsHost`, `RecordAnnotations`
|
|
10
|
+
and `resolveArtifactDef` are quiet optionals. No warning for them on a bare host. Guard:
|
|
11
|
+
`a-bare-host-gets-the-rich-content-optionals-quietly.test.tsx`.
|
|
12
|
+
|
|
13
|
+
Consumer action: none (a host's own `app` bindings still replace these wholesale).
|
|
14
|
+
|
|
15
|
+
## 0.4.28 — IconButton is the real one in any host
|
|
16
|
+
|
|
17
|
+
- The `IconButton` slot defaults to `@ai-matrx/design-system` 0.83.0's `IconButton` (the website's,
|
|
18
|
+
moved whole). Guard: `slots-draw-their-package-default.test.tsx`.
|
|
19
|
+
|
|
20
|
+
Consumer action: none (a host's own registration still wins).
|
|
21
|
+
|
|
22
|
+
## 0.4.27 — the error card and the stale-data notice are the real ones in any host
|
|
23
|
+
|
|
24
|
+
- `ErrorNotice` and `StaleDataNotice` slots default to `@ai-matrx/design-system`'s components (the
|
|
25
|
+
website's, moved whole); the package's plain stand-ins are gone. Guard:
|
|
26
|
+
`slots-draw-their-package-default.test.tsx`.
|
|
27
|
+
|
|
28
|
+
Consumer action: none.
|
|
29
|
+
|
|
3
30
|
## 0.4.26
|
|
4
31
|
|
|
5
32
|
- `RichCopySplit` renders the content action set (`ContentActions` from rich-content 0.2.52): Copy, Plain (with the new `viewKey` prop), Export, Print and Transform (the host's Alchemy palette) each one click. New `exportTitle` prop names exported files.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one visible "who answers here" notice on a mirrored coding-session
|
|
3
|
+
* conversation: always in plain sight (never a tooltip), always the server's
|
|
4
|
+
* sentence, and the announced stand-in when the platform default is answering.
|
|
5
|
+
* It is the only thing between a reply box and a person believing they just
|
|
6
|
+
* messaged their coding tool. Renders nothing for an ordinary conversation.
|
|
7
|
+
*/
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { type ReplyDoorState } from "./reply-door.js";
|
|
10
|
+
export declare function CodingReplyNotice({ state, labelId, className, }: {
|
|
11
|
+
state: ReplyDoorState;
|
|
12
|
+
/** Id for the sentence, so a refused field can point `aria-describedby` at it. */
|
|
13
|
+
labelId?: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
}): React.JSX.Element | null;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AlertTriangle, Info, Loader2 } from "lucide-react";
|
|
3
|
+
import { ErrorAlchemyMenu } from "../../host/ui-slots.js";
|
|
4
|
+
import { replyDoorErrorSentence, replyDoorView } from "./reply-door.js";
|
|
5
|
+
function CodingReplyNotice({
|
|
6
|
+
state,
|
|
7
|
+
labelId,
|
|
8
|
+
className
|
|
9
|
+
}) {
|
|
10
|
+
const door = replyDoorView(state);
|
|
11
|
+
const warn = "flex items-start gap-2 text-xs text-amber-700 dark:text-amber-300";
|
|
12
|
+
let line = null;
|
|
13
|
+
if (state.status === "loading") {
|
|
14
|
+
line = /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-testid": "reply-door-checking", children: [
|
|
15
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 shrink-0 animate-spin" }),
|
|
16
|
+
/* @__PURE__ */ jsx("span", { children: "Checking who answers here\u2026" })
|
|
17
|
+
] });
|
|
18
|
+
} else if (state.status === "error") {
|
|
19
|
+
line = /* @__PURE__ */ jsxs("p", { className: warn, "data-testid": "reply-door-error", children: [
|
|
20
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
|
|
21
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
22
|
+
replyDoorErrorSentence(state.error),
|
|
23
|
+
" ",
|
|
24
|
+
/* @__PURE__ */ jsx(ErrorAlchemyMenu, { error: state.error })
|
|
25
|
+
] })
|
|
26
|
+
] });
|
|
27
|
+
} else if (door.label) {
|
|
28
|
+
line = /* @__PURE__ */ jsxs("p", { id: labelId, className: warn, "data-testid": "reply-door-label", children: [
|
|
29
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
|
|
30
|
+
/* @__PURE__ */ jsx("span", { children: door.label })
|
|
31
|
+
] });
|
|
32
|
+
} else if (door.refusalSentence) {
|
|
33
|
+
line = /* @__PURE__ */ jsxs("p", { id: labelId, className: warn, "data-testid": "reply-door-label", children: [
|
|
34
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
|
|
35
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
36
|
+
door.refusalSentence,
|
|
37
|
+
" ",
|
|
38
|
+
/* @__PURE__ */ jsx(ErrorAlchemyMenu, { error: door.refusalSentence })
|
|
39
|
+
] })
|
|
40
|
+
] });
|
|
41
|
+
}
|
|
42
|
+
if (!line && !door.standInNotice) return null;
|
|
43
|
+
return /* @__PURE__ */ jsxs("div", { className, "data-testid": "coding-reply-notice", children: [
|
|
44
|
+
line,
|
|
45
|
+
door.standInNotice ? /* @__PURE__ */ jsxs(
|
|
46
|
+
"p",
|
|
47
|
+
{
|
|
48
|
+
className: "mt-1 flex items-start gap-2 text-[11px] leading-relaxed text-muted-foreground",
|
|
49
|
+
"data-testid": "reply-door-stand-in",
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsx(Info, { className: "mt-0.5 h-3 w-3 shrink-0" }),
|
|
52
|
+
/* @__PURE__ */ jsx("span", { children: door.standInNotice })
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
) : null
|
|
56
|
+
] });
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
CodingReplyNotice
|
|
60
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reply door on a mirrored coding-session conversation — ONE decision for
|
|
3
|
+
* every app (website `/work/conversations`, Matrx Local Cloud Chat, the package
|
|
4
|
+
* chat). Moved here from the two copies the website and the desktop each kept.
|
|
5
|
+
*
|
|
6
|
+
* Everything is derived from the server's report
|
|
7
|
+
* (`GET /coding-sessions/conversations/{id}/responder`). Nothing is invented:
|
|
8
|
+
* the label, the refusal sentence and the stand-in notice are the server's
|
|
9
|
+
* strings, composed from the same resolution that picks the answering agent,
|
|
10
|
+
* and `inputBlocked` follows the server's `can_reply` — the very predicate the
|
|
11
|
+
* reply POST gates on. Never re-derive, re-word or template those sentences,
|
|
12
|
+
* and never keep a client-side one as a fallback (V-XT/V3, 2026-09-15: a
|
|
13
|
+
* hardcoded "AI Matrx is answering" hid a silent platform-default stand-in).
|
|
14
|
+
*/
|
|
15
|
+
import type { components } from "@ai-matrx/agents/generated/api-types";
|
|
16
|
+
export type CodingReplyResponderReport = components["schemas"]["CodingReplyResponderReport"];
|
|
17
|
+
/**
|
|
18
|
+
* The `source_feature` a reply from this door carries. aidream reads it to
|
|
19
|
+
* stamp `metadata.origin = "ai_matrx_reply"` and the responder's `agent_id`
|
|
20
|
+
* onto both the reply and the answer.
|
|
21
|
+
*/
|
|
22
|
+
export declare const CODING_SESSION_REPLY_SOURCE_FEATURE = "coding_session_reply";
|
|
23
|
+
export type ReplyDoorStatus = "idle" | "loading" | "ready" | "error";
|
|
24
|
+
export interface ReplyDoorState {
|
|
25
|
+
status: ReplyDoorStatus;
|
|
26
|
+
/** The server's report. Null until it lands and after a failed read. */
|
|
27
|
+
report: CodingReplyResponderReport | null;
|
|
28
|
+
/** The read's own failure, in the server's words. Null unless status is "error". */
|
|
29
|
+
error: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface ReplyDoorView {
|
|
32
|
+
/** True when this conversation is a mirrored coding session at all. */
|
|
33
|
+
isMirror: boolean;
|
|
34
|
+
/** The server's sentence naming who answers, or null when it said nothing. */
|
|
35
|
+
label: string | null;
|
|
36
|
+
/** Shown when the server refuses the reply: its label, else its reason. */
|
|
37
|
+
refusalSentence: string | null;
|
|
38
|
+
/** The platform-default stand-in notice, only when a stand-in was used. */
|
|
39
|
+
standInNotice: string | null;
|
|
40
|
+
/** True while we do not yet know, or the server refused the reply. */
|
|
41
|
+
inputBlocked: boolean;
|
|
42
|
+
/** True only when the SERVER refused (never for a pending or failed read). */
|
|
43
|
+
replyRefused: boolean;
|
|
44
|
+
/** The composer placeholder for this state. */
|
|
45
|
+
placeholder: string;
|
|
46
|
+
/** What a send from this door files itself as, or null for a normal chat. */
|
|
47
|
+
sourceFeature: string | null;
|
|
48
|
+
}
|
|
49
|
+
export declare const REPLY_DOOR_PLACEHOLDER = "Ask AI Matrx about this session, or say what to do next.";
|
|
50
|
+
export declare const REPLY_DOOR_REFUSED_PLACEHOLDER = "A reply cannot be sent here.";
|
|
51
|
+
export declare function replyDoorView(state: ReplyDoorState): ReplyDoorView;
|
|
52
|
+
/** The sentence a failed read shows — the real error, never a shrug. */
|
|
53
|
+
export declare function replyDoorErrorSentence(error: string | null): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const CODING_SESSION_REPLY_SOURCE_FEATURE = "coding_session_reply";
|
|
2
|
+
const REPLY_DOOR_PLACEHOLDER = "Ask AI Matrx about this session, or say what to do next.";
|
|
3
|
+
const REPLY_DOOR_REFUSED_PLACEHOLDER = "A reply cannot be sent here.";
|
|
4
|
+
const NOTHING = {
|
|
5
|
+
isMirror: false,
|
|
6
|
+
label: null,
|
|
7
|
+
refusalSentence: null,
|
|
8
|
+
standInNotice: null,
|
|
9
|
+
replyRefused: false,
|
|
10
|
+
placeholder: REPLY_DOOR_PLACEHOLDER,
|
|
11
|
+
sourceFeature: null
|
|
12
|
+
};
|
|
13
|
+
function replyDoorView(state) {
|
|
14
|
+
if (state.status === "loading") return { ...NOTHING, inputBlocked: true };
|
|
15
|
+
if (state.status !== "ready" || state.report === null) return { ...NOTHING, inputBlocked: false };
|
|
16
|
+
const report = state.report;
|
|
17
|
+
const isMirror = report.is_coding_session_mirror === true;
|
|
18
|
+
const label = isMirror && report.composer_label ? report.composer_label : null;
|
|
19
|
+
const refused = report.can_reply !== true;
|
|
20
|
+
return {
|
|
21
|
+
isMirror,
|
|
22
|
+
label,
|
|
23
|
+
refusalSentence: refused ? label ?? report.reason ?? null : null,
|
|
24
|
+
standInNotice: report.responder?.used_platform_default ? report.stand_in_notice ?? null : null,
|
|
25
|
+
inputBlocked: refused,
|
|
26
|
+
replyRefused: refused,
|
|
27
|
+
placeholder: refused ? REPLY_DOOR_REFUSED_PLACEHOLDER : REPLY_DOOR_PLACEHOLDER,
|
|
28
|
+
sourceFeature: isMirror ? CODING_SESSION_REPLY_SOURCE_FEATURE : null
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function replyDoorErrorSentence(error) {
|
|
32
|
+
return `Who answers here could not be loaded, so this screen is not naming anyone: ${error || "no reason given"}`;
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
CODING_SESSION_REPLY_SOURCE_FEATURE,
|
|
36
|
+
REPLY_DOOR_PLACEHOLDER,
|
|
37
|
+
REPLY_DOOR_REFUSED_PLACEHOLDER,
|
|
38
|
+
replyDoorErrorSentence,
|
|
39
|
+
replyDoorView
|
|
40
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The responder read through the chat package's own server port — for hosts
|
|
3
|
+
* that configured `@ai-matrx/chat/host` (the package chat on every app).
|
|
4
|
+
*/
|
|
5
|
+
import type { CodingReplyResponderRead } from "./useCodingReplyResponder.js";
|
|
6
|
+
export declare function useChatServerResponderRead(organizationId?: string | null): CodingReplyResponderRead;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { extractErrorMessage } from "@ai-matrx/data/net";
|
|
3
|
+
import { useAppDispatch } from "../../store/hooks.js";
|
|
4
|
+
import { callApi } from "../../host/server/call-api.js";
|
|
5
|
+
function useChatServerResponderRead(organizationId) {
|
|
6
|
+
const dispatch = useAppDispatch();
|
|
7
|
+
return useCallback(
|
|
8
|
+
async (conversationId) => {
|
|
9
|
+
const result = await dispatch(
|
|
10
|
+
callApi({
|
|
11
|
+
path: "/coding-sessions/conversations/{conversation_id}/responder",
|
|
12
|
+
method: "GET",
|
|
13
|
+
pathParams: { conversation_id: conversationId },
|
|
14
|
+
scopeOverrides: organizationId ? { organization_id: organizationId } : void 0
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
if (result.error) {
|
|
18
|
+
const detail = result.error.serverDetail ? extractErrorMessage(result.error.serverDetail) : "";
|
|
19
|
+
throw new Error(detail || result.error.message || "Unknown error");
|
|
20
|
+
}
|
|
21
|
+
return result.data ?? null;
|
|
22
|
+
},
|
|
23
|
+
[dispatch, organizationId]
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
useChatServerResponderRead
|
|
28
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who answers a reply typed on this conversation, asked BEFORE the person
|
|
3
|
+
* types. The hook holds nothing but the server's answer and the state of
|
|
4
|
+
* asking for it; every sentence comes from `replyDoorView`.
|
|
5
|
+
*
|
|
6
|
+
* The read itself is the host's (`read`): the website reads through its API
|
|
7
|
+
* door, the desktop through its custodian token, the package chat through the
|
|
8
|
+
* chat server port (`useChatServerResponderRead`). A fast conversation switch
|
|
9
|
+
* never paints one conversation's responder onto another's composer.
|
|
10
|
+
*/
|
|
11
|
+
import type { CodingReplyResponderReport, ReplyDoorState } from "./reply-door.js";
|
|
12
|
+
export type CodingReplyResponderRead = (conversationId: string, signal: AbortSignal) => Promise<CodingReplyResponderReport>;
|
|
13
|
+
export declare function useCodingReplyResponder(opts: {
|
|
14
|
+
conversationId: string | null;
|
|
15
|
+
/** Read only where a reply can be typed; `false` makes no request (idle). */
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
read: CodingReplyResponderRead;
|
|
18
|
+
}): ReplyDoorState & {
|
|
19
|
+
refresh: () => void;
|
|
20
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
const IDLE = { status: "idle", report: null, error: null };
|
|
3
|
+
function useCodingReplyResponder(opts) {
|
|
4
|
+
const { conversationId, enabled, read } = opts;
|
|
5
|
+
const [state, setState] = useState(enabled && conversationId ? { ...IDLE, status: "loading" } : IDLE);
|
|
6
|
+
const [attempt, setAttempt] = useState(0);
|
|
7
|
+
const refresh = useCallback(() => setAttempt((value) => value + 1), []);
|
|
8
|
+
const readRef = useRef(read);
|
|
9
|
+
readRef.current = read;
|
|
10
|
+
const wanted = useRef(null);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!enabled || !conversationId) {
|
|
13
|
+
wanted.current = null;
|
|
14
|
+
setState(IDLE);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
wanted.current = conversationId;
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
let cancelled = false;
|
|
20
|
+
setState({ status: "loading", report: null, error: null });
|
|
21
|
+
readRef.current(conversationId, controller.signal).then((report) => {
|
|
22
|
+
if (cancelled || wanted.current !== conversationId) return;
|
|
23
|
+
setState({ status: "ready", report: report ?? null, error: null });
|
|
24
|
+
}).catch((cause) => {
|
|
25
|
+
if (cancelled || controller.signal.aborted || wanted.current !== conversationId) return;
|
|
26
|
+
setState({ status: "error", report: null, error: cause instanceof Error ? cause.message : String(cause) });
|
|
27
|
+
});
|
|
28
|
+
return () => {
|
|
29
|
+
cancelled = true;
|
|
30
|
+
controller.abort();
|
|
31
|
+
};
|
|
32
|
+
}, [attempt, conversationId, enabled]);
|
|
33
|
+
return { ...state, refresh };
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
useCodingReplyResponder
|
|
37
|
+
};
|
|
@@ -32,6 +32,7 @@ import { buildConversationMessageTitle } from "../../../utils/conversation-messa
|
|
|
32
32
|
import { resolveAssistantEditTarget } from "../message-options/resolveAssistantEditTarget.js";
|
|
33
33
|
import { MessageTimestamp } from "../MessageTimestamp.js";
|
|
34
34
|
import { useTranscriptLayout } from "../../shared/transcript-layout.js";
|
|
35
|
+
import "../../../../ui/markdown-stream/rich-content-defaults.js";
|
|
35
36
|
function useAssistantMessageActions({
|
|
36
37
|
messageId,
|
|
37
38
|
conversationId,
|
|
@@ -28,6 +28,7 @@ import { DeleteMessageDialog } from "../message-options/DeleteMessageDialog.js";
|
|
|
28
28
|
import { openStructuredRawViewer } from "../message-options/openAssistantMessageEditor.js";
|
|
29
29
|
import { extractErrorMessage } from "@ai-matrx/data/net";
|
|
30
30
|
import { useConversationAbilities } from "../../../redux/execution-system/host-conversations/conversation-abilities.js";
|
|
31
|
+
import "../../../../ui/markdown-stream/rich-content-defaults.js";
|
|
31
32
|
import {
|
|
32
33
|
USER_EDIT_ACTIONS,
|
|
33
34
|
routeUserEditAction
|
package/dist/agents/redux/execution-system/instance-user-input/append-to-composer-draft.thunk.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ChatDispatch, ChatRootState } from "../../../../store/root-state.js";
|
|
2
|
+
export declare function appendedDraftText(current: string, text: string): string;
|
|
3
|
+
export declare function appendToComposerDraft(conversationId: string, text: string): (dispatch: ChatDispatch, getState: () => ChatRootState) => string;
|
package/dist/agents/redux/execution-system/instance-user-input/append-to-composer-draft.thunk.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { setUserInputText } from "./instance-user-input.slice.js";
|
|
2
|
+
function appendedDraftText(current, text) {
|
|
3
|
+
const trimmed = current.trimEnd();
|
|
4
|
+
return trimmed ? `${trimmed}
|
|
5
|
+
${text}` : text;
|
|
6
|
+
}
|
|
7
|
+
function appendToComposerDraft(conversationId, text) {
|
|
8
|
+
return (dispatch, getState) => {
|
|
9
|
+
if (!text) return getState().instanceUserInput.byConversationId[conversationId]?.text ?? "";
|
|
10
|
+
const current = getState().instanceUserInput.byConversationId[conversationId]?.text ?? "";
|
|
11
|
+
const next = appendedDraftText(current, text);
|
|
12
|
+
dispatch(setUserInputText({ conversationId, text: next }));
|
|
13
|
+
return next;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
appendToComposerDraft,
|
|
18
|
+
appendedDraftText
|
|
19
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { adminLaneOrganizationId } from "../../../../host/server/admin-lane.js";
|
|
2
2
|
import { selectOrganizationId } from "../../../../host/org.js";
|
|
3
|
+
import { chatHostHasDatabase } from "../../../../host/configure.js";
|
|
3
4
|
import { selectAccessToken, selectFingerprintId } from "../../../../host/identity.js";
|
|
4
5
|
function isFingerprintGuestExecution(state) {
|
|
5
6
|
return !selectAccessToken(state) && Boolean(selectFingerprintId(state));
|
|
@@ -34,7 +35,8 @@ function requireExecutionOrganizationId(state, conversationId) {
|
|
|
34
35
|
throw new Error(`Conversation ${conversationId} not found.`);
|
|
35
36
|
}
|
|
36
37
|
if (instance.cacheOnly !== false) {
|
|
37
|
-
const startOrganizationId = instance.organizationId ??
|
|
38
|
+
const startOrganizationId = instance.organizationId ?? // A host with no database has no admin seat to bill (G-DOOR: this door branches on the host).
|
|
39
|
+
(chatHostHasDatabase() ? adminLaneOrganizationId() : void 0) ?? selectOrganizationId(state);
|
|
38
40
|
if (startOrganizationId) return startOrganizationId;
|
|
39
41
|
throw new Error(
|
|
40
42
|
"Select an organization before sending this message. The request was not sent."
|
|
@@ -73,7 +73,11 @@ export declare function tapStreamEvent(conversationId: string, requestId: string
|
|
|
73
73
|
export declare function continuesThroughMandate(conversationId: string): boolean;
|
|
74
74
|
/** Ask the conversation's host for the next conversation id; null when it has no say. */
|
|
75
75
|
export declare function startHostConversation(conversationId: string): Promise<string | null>;
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Subscribe to changes of this conversation's held sentence: the host's own
|
|
78
|
+
* source, and the registry (a registration swaps which source is listened to).
|
|
79
|
+
* Never re-subscribes from inside the source's own notification.
|
|
80
|
+
*/
|
|
77
81
|
export declare function subscribeConversationHeld(conversationId: string | null | undefined, listener: () => void): () => void;
|
|
78
82
|
/** The held sentence right now, or null. */
|
|
79
83
|
export declare function readConversationHeld(conversationId: string | null | undefined): string | null;
|
|
@@ -3,7 +3,7 @@ const hooksByConversation = /* @__PURE__ */ new Map();
|
|
|
3
3
|
const extrasByConversation = /* @__PURE__ */ new Map();
|
|
4
4
|
const registryListeners = /* @__PURE__ */ new Set();
|
|
5
5
|
function notifyRegistry() {
|
|
6
|
-
for (const listener of registryListeners) listener();
|
|
6
|
+
for (const listener of [...registryListeners]) listener();
|
|
7
7
|
}
|
|
8
8
|
function registerConversationHooks(conversationId, hooks) {
|
|
9
9
|
hooksByConversation.set(conversationId, hooks);
|
|
@@ -11,7 +11,6 @@ function registerConversationHooks(conversationId, hooks) {
|
|
|
11
11
|
return () => {
|
|
12
12
|
if (hooksByConversation.get(conversationId) === hooks) {
|
|
13
13
|
hooksByConversation.delete(conversationId);
|
|
14
|
-
extrasByConversation.delete(conversationId);
|
|
15
14
|
notifyRegistry();
|
|
16
15
|
}
|
|
17
16
|
};
|
|
@@ -58,20 +57,23 @@ async function startHostConversation(conversationId) {
|
|
|
58
57
|
return start ? start() : null;
|
|
59
58
|
}
|
|
60
59
|
function subscribeConversationHeld(conversationId, listener) {
|
|
61
|
-
let
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
let current;
|
|
61
|
+
let releaseHeld;
|
|
62
|
+
const bind = () => {
|
|
63
|
+
const next = conversationId ? hooksByConversation.get(conversationId)?.held : void 0;
|
|
64
|
+
if (next === current) return;
|
|
65
|
+
releaseHeld?.();
|
|
66
|
+
current = next;
|
|
67
|
+
releaseHeld = next?.subscribe(listener);
|
|
68
|
+
};
|
|
69
|
+
const onRegistry = () => {
|
|
70
|
+
bind();
|
|
65
71
|
listener();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
function subscribeOnce(conversationId, listener) {
|
|
70
|
-
registryListeners.add(listener);
|
|
71
|
-
const held = conversationId ? hooksByConversation.get(conversationId)?.held : void 0;
|
|
72
|
-
const releaseHeld = held?.subscribe(listener);
|
|
72
|
+
};
|
|
73
|
+
bind();
|
|
74
|
+
registryListeners.add(onRegistry);
|
|
73
75
|
return () => {
|
|
74
|
-
registryListeners.delete(
|
|
76
|
+
registryListeners.delete(onRegistry);
|
|
75
77
|
releaseHeld?.();
|
|
76
78
|
};
|
|
77
79
|
}
|
package/dist/host/ui-slots.d.ts
CHANGED
|
@@ -452,7 +452,7 @@ export declare const InfoHint: AnyComponent;
|
|
|
452
452
|
export declare const AnswerValueView: AnyComponent;
|
|
453
453
|
/** A host with no error menu draws one labelled line; the error sentence itself is already on screen. */
|
|
454
454
|
export declare const ErrorAlchemyMenu: AnyComponent;
|
|
455
|
-
/**
|
|
455
|
+
/** THE inline error card is design-system's ErrorNotice (moved from matrx-frontend, host-slot batch 2). */
|
|
456
456
|
export declare const ErrorNotice: AnyComponent;
|
|
457
457
|
export declare const EntityRef: AnyComponent;
|
|
458
458
|
export declare const AdvancedMenu: AnyComponent;
|
package/dist/host/ui-slots.js
CHANGED
|
@@ -13,7 +13,10 @@ import { DefaultResourcePickerMenu } from "./defaults/resource-picker-menu.js";
|
|
|
13
13
|
import { captureReactRenderError } from "./diagnostics.js";
|
|
14
14
|
import {
|
|
15
15
|
ConfirmDialog as PackageConfirmDialog,
|
|
16
|
+
ErrorNotice as PackageErrorNotice,
|
|
16
17
|
FloatingSheet as PackageFloatingSheet,
|
|
18
|
+
IconButton as PackageIconButton,
|
|
19
|
+
StaleDataNotice as PackageStaleDataNotice,
|
|
17
20
|
ReadFailure as PackageReadFailure,
|
|
18
21
|
TextInputDialog as PackageTextInputDialog,
|
|
19
22
|
UntrustedCount as PackageUntrustedCount
|
|
@@ -211,17 +214,7 @@ const CopyButtons = slotComponent("CopyButtons");
|
|
|
211
214
|
const InfoHint = slotComponent("InfoHint");
|
|
212
215
|
const AnswerValueView = slotComponent("AnswerValueView");
|
|
213
216
|
const ErrorAlchemyMenu = slotComponent("ErrorAlchemyMenu", optionalWidget("ErrorAlchemyMenu"));
|
|
214
|
-
const ErrorNotice = slotComponent(
|
|
215
|
-
"ErrorNotice",
|
|
216
|
-
({ title, message, error, actions, children, className }) => createElement(
|
|
217
|
-
"div",
|
|
218
|
-
{ className: `rounded-md border border-destructive/40 p-2 text-sm ${className ?? ""}`, "data-chat-slot-fallback": "ErrorNotice" },
|
|
219
|
-
title ? createElement("div", { className: "font-medium" }, title) : null,
|
|
220
|
-
createElement("div", null, message || (error instanceof Error ? error.message : typeof error === "string" ? error : "")),
|
|
221
|
-
children ?? null,
|
|
222
|
-
actions ?? null
|
|
223
|
-
)
|
|
224
|
-
);
|
|
217
|
+
const ErrorNotice = slotComponent("ErrorNotice", withPackageDefault(PackageErrorNotice));
|
|
225
218
|
const EntityRef = slotComponent("EntityRef");
|
|
226
219
|
const AdvancedMenu = slotComponent("AdvancedMenu");
|
|
227
220
|
const AuthGateDialog = slotComponent("AuthGateDialog");
|
|
@@ -334,22 +327,7 @@ const readListRpc = slotFn("readListRpc");
|
|
|
334
327
|
const ReadFailure = slotComponent("ReadFailure", withPackageDefault(PackageReadFailure));
|
|
335
328
|
const ItemRow = slotComponent("ItemRow", unregisteredWidget("ItemRow"));
|
|
336
329
|
const UntrustedCount = slotComponent("UntrustedCount", withPackageDefault(PackageUntrustedCount));
|
|
337
|
-
|
|
338
|
-
const what = props.what ?? "this";
|
|
339
|
-
const sentence = props.hasData ? `Showing what was last read; ${what} could not be refreshed.` : `Could not read ${what}.`;
|
|
340
|
-
return createElement(
|
|
341
|
-
"div",
|
|
342
|
-
{ role: "status", className: "text-xs text-muted-foreground", "data-stale-data-notice": "" },
|
|
343
|
-
sentence,
|
|
344
|
-
props.detail ? ` ${props.detail}` : null,
|
|
345
|
-
props.onRetry ? createElement(
|
|
346
|
-
"button",
|
|
347
|
-
{ type: "button", className: "ml-2 underline", onClick: () => props.onRetry?.() },
|
|
348
|
-
"Try again"
|
|
349
|
-
) : null
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
const StaleDataNotice = slotComponent("StaleDataNotice", PlainStaleDataNotice);
|
|
330
|
+
const StaleDataNotice = slotComponent("StaleDataNotice", withPackageDefault(PackageStaleDataNotice));
|
|
353
331
|
const WorkspaceGate = slotComponent("WorkspaceGate", DefaultWorkspaceGate);
|
|
354
332
|
const OrganizationContextNotice = slotComponent("OrganizationContextNotice", unregisteredWidget("OrganizationContextNotice"));
|
|
355
333
|
const JsonInspector = slotComponent("JsonInspector", unregisteredWidget("JsonInspector"));
|
|
@@ -385,7 +363,7 @@ const VoiceTextarea = slotComponent("VoiceTextarea", (props) => {
|
|
|
385
363
|
});
|
|
386
364
|
const FloatingSheet = slotComponent("FloatingSheet", withPackageDefault(PackageFloatingSheet));
|
|
387
365
|
const AppLink = slotComponent("AppLink", ({ href, children, className, ...rest }) => createElement("a", { href, className, ...rest }, children));
|
|
388
|
-
const IconButton = slotComponent("IconButton",
|
|
366
|
+
const IconButton = slotComponent("IconButton", withPackageDefault(PackageIconButton));
|
|
389
367
|
const LightSwitchToggle = slotComponent("LightSwitchToggle", withPackageDefault(PackageLightSwitchToggle));
|
|
390
368
|
const CitationChip = slotComponent("CitationChip", unregisteredWidget("CitationChip"));
|
|
391
369
|
const MatrxEnvelopeBlock = slotComponent("MatrxEnvelopeBlock", unregisteredWidget("MatrxEnvelopeBlock"));
|
|
@@ -6,6 +6,7 @@ import { MarkdownErrorBoundary } from "@ai-matrx/rich-content/display/chat-markd
|
|
|
6
6
|
import { StreamAwareChatMarkdown } from "./StreamAwareChatMarkdown.js";
|
|
7
7
|
import { BlockRenderingProvider } from "@ai-matrx/rich-content/display/chat-markdown/BlockRenderingContext";
|
|
8
8
|
import { withImagePolicy } from "@ai-matrx/rich-content/levels/prose/remote-image-policy";
|
|
9
|
+
import "./rich-content-defaults.js";
|
|
9
10
|
const MarkdownStreamImpl = ({
|
|
10
11
|
content = "",
|
|
11
12
|
events,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rich-content bindings a bare chat host gets from this package (PACKAGE-INDEPENDENCE P29a).
|
|
3
|
+
*
|
|
4
|
+
* The block renderer asks its host for a few chat-shaped things: whether to hide reasoning and tool
|
|
5
|
+
* results, whether machine frames are visible, document dialogs, record annotations, artifact types.
|
|
6
|
+
* The first three are chat's own settings, so the package answers them itself; the rest are quiet
|
|
7
|
+
* optionals (no dialogs, children untouched, no artifact types). A host that registers its own
|
|
8
|
+
* `app` bindings replaces these wholesale, exactly as before, and never sees a warning for them.
|
|
9
|
+
*/
|
|
10
|
+
import { type ReactNode } from "react";
|
|
11
|
+
import { useMachineFramesVisible } from "../../agents/components/shared/transcript-audience.js";
|
|
12
|
+
export declare const CHAT_RICH_CONTENT_APP_DEFAULTS: {
|
|
13
|
+
useHideReasoning: (conversationId: string | null) => boolean;
|
|
14
|
+
useHideToolResults: (conversationId: string | null) => boolean;
|
|
15
|
+
useMachineFramesVisible: typeof useMachineFramesVisible;
|
|
16
|
+
useDocumentDialogsHost: () => {
|
|
17
|
+
callbacks: {};
|
|
18
|
+
};
|
|
19
|
+
RecordAnnotations: (props: {
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
}) => import("react").DetailedReactHTMLElement<{
|
|
22
|
+
style: {
|
|
23
|
+
display: "contents";
|
|
24
|
+
};
|
|
25
|
+
}, HTMLElement>;
|
|
26
|
+
resolveArtifactDef: () => null;
|
|
27
|
+
};
|
|
28
|
+
/** Register the package defaults unless the host has registered its own `app` bindings. */
|
|
29
|
+
export declare function registerChatRichContentDefaults(): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { configureRichContent, hostCapability } from "@ai-matrx/rich-content/host";
|
|
3
|
+
import { useAppSelector } from "../../store/hooks.js";
|
|
4
|
+
import {
|
|
5
|
+
selectHideReasoning,
|
|
6
|
+
selectHideToolResults
|
|
7
|
+
} from "../../agents/redux/execution-system/instance-ui-state/instance-ui-state.selectors.js";
|
|
8
|
+
import { useMachineFramesVisible } from "../../agents/components/shared/transcript-audience.js";
|
|
9
|
+
const never = () => false;
|
|
10
|
+
const CHAT_RICH_CONTENT_APP_DEFAULTS = {
|
|
11
|
+
useHideReasoning: (conversationId) => useAppSelector(conversationId ? selectHideReasoning(conversationId) : never),
|
|
12
|
+
useHideToolResults: (conversationId) => useAppSelector(conversationId ? selectHideToolResults(conversationId) : never),
|
|
13
|
+
useMachineFramesVisible,
|
|
14
|
+
useDocumentDialogsHost: () => ({ callbacks: {} }),
|
|
15
|
+
RecordAnnotations: (props) => createElement("span", { style: { display: "contents" } }, props.children),
|
|
16
|
+
resolveArtifactDef: () => null
|
|
17
|
+
};
|
|
18
|
+
function registerChatRichContentDefaults() {
|
|
19
|
+
if (hostCapability("app")) return;
|
|
20
|
+
configureRichContent({ app: CHAT_RICH_CONTENT_APP_DEFAULTS });
|
|
21
|
+
}
|
|
22
|
+
registerChatRichContentDefaults();
|
|
23
|
+
export {
|
|
24
|
+
CHAT_RICH_CONTENT_APP_DEFAULTS,
|
|
25
|
+
registerChatRichContentDefaults
|
|
26
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/chat",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.29",
|
|
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",
|
|
@@ -227,24 +227,24 @@
|
|
|
227
227
|
"uuid": "^14.0.2",
|
|
228
228
|
"zod": "^4.6.5",
|
|
229
229
|
"@ai-matrx/agents": "latest",
|
|
230
|
-
"@ai-matrx/alchemy": "latest",
|
|
231
230
|
"@ai-matrx/associations": "latest",
|
|
232
231
|
"@ai-matrx/browser-audio": "latest",
|
|
233
|
-
"@ai-matrx/
|
|
232
|
+
"@ai-matrx/alchemy": "latest",
|
|
234
233
|
"@ai-matrx/content-ir": "latest",
|
|
235
|
-
"@ai-matrx/
|
|
234
|
+
"@ai-matrx/canvas": "latest",
|
|
236
235
|
"@ai-matrx/data": "latest",
|
|
237
|
-
"@ai-matrx/design-system": "latest",
|
|
238
236
|
"@ai-matrx/diff": "latest",
|
|
239
|
-
"@ai-matrx/
|
|
240
|
-
"@ai-matrx/
|
|
241
|
-
"@ai-matrx/print": "latest",
|
|
237
|
+
"@ai-matrx/content-ir-react": "latest",
|
|
238
|
+
"@ai-matrx/design-system": "latest",
|
|
242
239
|
"@ai-matrx/kit": "latest",
|
|
240
|
+
"@ai-matrx/print": "latest",
|
|
243
241
|
"@ai-matrx/realtime": "latest",
|
|
244
|
-
"@ai-matrx/records": "latest",
|
|
245
242
|
"@ai-matrx/records-ui": "latest",
|
|
246
|
-
"@ai-matrx/
|
|
243
|
+
"@ai-matrx/records": "latest",
|
|
247
244
|
"@ai-matrx/rich-editor": "latest",
|
|
245
|
+
"@ai-matrx/rich-content": "latest",
|
|
246
|
+
"@ai-matrx/media": "latest",
|
|
247
|
+
"@ai-matrx/icons": "latest",
|
|
248
248
|
"@ai-matrx/tap-target": "latest"
|
|
249
249
|
},
|
|
250
250
|
"devDependencies": {
|