@ai-matrx/chat 0.4.22 → 0.4.24
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 +11 -0
- package/dist/agents/components/context-items/bodies/UnregisteredBody.d.ts +6 -3
- package/dist/agents/components/context-items/bodies/UnregisteredBody.js +3 -9
- package/dist/agents/components/inputs/resources/attach-resource.js +1 -0
- package/dist/agents/components/inputs/smart-input/SmartAgentInput.js +16 -1
- package/dist/agents/components/inputs/smart-input/composer/ComposerMenu.js +3 -2
- package/dist/agents/components/inputs/smart-input/composer/ComposerPlusMenu.js +41 -35
- package/dist/agents/components/messages-display/user/AgentUserMessage.js +3 -1
- package/dist/agents/components/shared/AgentConversationColumn.d.ts +10 -1
- package/dist/agents/components/shared/AgentConversationColumn.js +26 -2
- package/dist/agents/redux/execution-system/messages/messages.slice.js +6 -1
- package/dist/agents/redux/execution-system/thunks/await-launching-conversation.d.ts +16 -0
- package/dist/agents/redux/execution-system/thunks/await-launching-conversation.js +16 -0
- package/dist/agents/redux/execution-system/thunks/execute-instance.thunk.js +34 -1
- package/dist/agents/redux/execution-system/thunks/process-stream.js +11 -0
- package/dist/agents/redux/execution-system/thunks/smart-execute.thunk.js +10 -0
- package/dist/host/app-data-slots.d.ts +0 -1
- package/dist/host/app-data-slots.js +25 -5
- package/dist/host/bare-host-chrome.js +4 -0
- package/dist/host/contract.d.ts +1 -1
- package/dist/host/contract.js +2 -1
- package/dist/host/conversation-hooks-react.d.ts +2 -0
- package/dist/host/conversation-hooks-react.js +12 -0
- package/dist/host/conversation-hooks.d.ts +79 -0
- package/dist/host/conversation-hooks.js +91 -0
- package/dist/host/defaults/bare-block-dispatch.d.ts +6 -0
- package/dist/host/defaults/bare-block-dispatch.js +60 -0
- package/dist/host/defaults/files-host.d.ts +26 -0
- package/dist/host/defaults/files-host.js +212 -0
- package/dist/host/defaults/resource-picker-menu.d.ts +14 -0
- package/dist/host/defaults/resource-picker-menu.js +117 -0
- package/dist/host/defaults/setting-control-input.d.ts +11 -0
- package/dist/host/defaults/setting-control-input.js +91 -0
- package/dist/host/index.d.ts +1 -0
- package/dist/host/index.js +1 -0
- package/dist/host/react.d.ts +1 -0
- package/dist/host/react.js +2 -0
- package/dist/host/ui-slots.d.ts +25 -1
- package/dist/host/ui-slots.js +59 -15
- package/dist/host/window-openers.d.ts +2 -0
- package/dist/host/window-openers.js +4 -0
- package/dist/react.cjs +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/tool-call-visualization/registry/registry.js +4 -4
- package/dist/ui/chat-panel/ChatPanel.d.ts +50 -0
- package/dist/ui/chat-panel/ChatPanel.js +175 -0
- package/dist/ui/chat-panel/drag.d.ts +87 -0
- package/dist/ui/chat-panel/drag.js +194 -0
- package/dist/window-panels/windows/agents/AgentRunWindow.js +17 -0
- package/dist/window-panels/windows/agents/agentRunWindowAddress.d.ts +19 -0
- package/dist/window-panels/windows/agents/agentRunWindowAddress.js +11 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.4.24
|
|
4
|
+
|
|
5
|
+
Republish of 0.4.23 (its publish failed in finish-build on a member call read as an import; fixed in 4008c0cbbc). No consumer action required.
|
|
6
|
+
|
|
7
|
+
## 0.4.23
|
|
8
|
+
|
|
9
|
+
- An answer open in the in-place editor stays open when its conversation is re-read (`hydrateMessages` carries `_editingInPlace`); in the floating chat window the editor closed ~1.5s after a double-click. Guard: `hydrate-keeps-an-open-in-place-editor.test.ts`.
|
|
10
|
+
- A Chat link naming its agent and conversation (`agent:<id>:m-run_a-<agent>_c-<conversation>`) opens them: the window adopts a seed that arrives after it mounted, never over the person's own choice (`adoptLateWindowSeed`). Guard: `a-chat-link-opens-its-conversation.test.ts`.
|
|
11
|
+
|
|
12
|
+
No consumer action required.
|
|
13
|
+
|
|
3
14
|
## 0.4.22
|
|
4
15
|
|
|
5
16
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The default for a context-item body the host did not register: the generic body, which shows the
|
|
3
|
+
* item itself (the missing editor is reported once by the slot; never a "not set up here" line).
|
|
4
|
+
*/
|
|
2
5
|
import type { ContextItemBodyProps } from "../types.js";
|
|
3
|
-
export declare function UnregisteredBody({ name,
|
|
6
|
+
export declare function UnregisteredBody({ name, props }: {
|
|
4
7
|
name: string;
|
|
5
|
-
what
|
|
8
|
+
what?: string;
|
|
6
9
|
props: ContextItemBodyProps;
|
|
7
10
|
}): import("react").JSX.Element;
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { GenericBody } from "./GenericBody.js";
|
|
4
|
-
function UnregisteredBody({ name,
|
|
5
|
-
return /* @__PURE__ */
|
|
6
|
-
/* @__PURE__ */ jsxs("div", { className: "px-4 pt-3 text-[11px] text-muted-foreground", children: [
|
|
7
|
-
what,
|
|
8
|
-
" is not set up here"
|
|
9
|
-
] }),
|
|
10
|
-
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(GenericBody, { ...props }) })
|
|
11
|
-
] });
|
|
4
|
+
function UnregisteredBody({ name, props }) {
|
|
5
|
+
return /* @__PURE__ */ jsx("div", { className: "flex h-full min-h-0 flex-col", "data-chat-slot-fallback": name, children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(GenericBody, { ...props }) }) });
|
|
12
6
|
}
|
|
13
7
|
export {
|
|
14
8
|
UnregisteredBody
|
|
@@ -54,6 +54,7 @@ function resourceLabel(resource) {
|
|
|
54
54
|
case "table":
|
|
55
55
|
return resource.data.table_name ?? "Table";
|
|
56
56
|
case "webpage":
|
|
57
|
+
if (typeof resource.data === "string") return resource.data.replace(/^https?:\/\/(www\.)?/i, "").replace(/\/$/, "");
|
|
57
58
|
return resource.data.title ?? resource.data.url ?? "Webpage";
|
|
58
59
|
case "youtube":
|
|
59
60
|
return resource.data.title ?? "YouTube";
|
|
@@ -8,6 +8,8 @@ import { InboxQueueStrip } from "./InboxQueueStrip.js";
|
|
|
8
8
|
import { selectViewerCanReply } from "../../../redux/execution-system/conversations/conversations.selectors.js";
|
|
9
9
|
import { ViewOnlyComposerBar } from "./ViewOnlyComposerBar.js";
|
|
10
10
|
import { selectHasUserInput } from "../../../redux/execution-system/instance-user-input/instance-user-input.selectors.js";
|
|
11
|
+
import { useConversationHeld } from "../../../../host/conversation-hooks-react.js";
|
|
12
|
+
import { Hourglass } from "lucide-react";
|
|
11
13
|
function SmartAgentInput({
|
|
12
14
|
conversationId,
|
|
13
15
|
uploadRoot = "userContent",
|
|
@@ -41,7 +43,8 @@ function SmartAgentInput({
|
|
|
41
43
|
const ambientHasNothingToSend = useAppSelector(
|
|
42
44
|
(state) => isLauncher && conversationId ? !selectHasUserInput(conversationId)(state) : false
|
|
43
45
|
);
|
|
44
|
-
const
|
|
46
|
+
const heldSentence = useConversationHeld(conversationId);
|
|
47
|
+
const sendDisabled = disableSend || ambientHasNothingToSend || heldSentence !== null;
|
|
45
48
|
const viewerCanReply = useAppSelector(
|
|
46
49
|
(state) => conversationId ? selectViewerCanReply(conversationId)(state) : true
|
|
47
50
|
);
|
|
@@ -52,6 +55,18 @@ function SmartAgentInput({
|
|
|
52
55
|
}
|
|
53
56
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
54
57
|
queueStrip,
|
|
58
|
+
heldSentence ? /* @__PURE__ */ jsxs(
|
|
59
|
+
"div",
|
|
60
|
+
{
|
|
61
|
+
role: "status",
|
|
62
|
+
"data-testid": "composer-held",
|
|
63
|
+
className: "mx-auto mb-1.5 flex w-full items-center gap-2 px-1 text-xs text-muted-foreground",
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ jsx(Hourglass, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
|
|
66
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", title: heldSentence, children: heldSentence })
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
) : null,
|
|
55
70
|
/* @__PURE__ */ jsx(
|
|
56
71
|
SmartAgentInputStacked,
|
|
57
72
|
{
|
|
@@ -12,6 +12,7 @@ import { Check, ChevronRight } from "lucide-react";
|
|
|
12
12
|
import { Popover, PopoverAnchor, PopoverContent } from "@ai-matrx/design-system";
|
|
13
13
|
import { Switch } from "../../../../../ui/switch.js";
|
|
14
14
|
import { cn } from "@ai-matrx/design-system";
|
|
15
|
+
import { SlotBoundary } from "../../../../../host/ui-slots.js";
|
|
15
16
|
const ComposerMenuPresentationContext = createContext("popover");
|
|
16
17
|
const ComposerSheetNavContext = createContext(null);
|
|
17
18
|
const SHEET_ROW = "sheet-row flex w-full min-w-0 shrink-0 items-center gap-3 bg-card px-3 text-left text-base text-foreground transition-colors active:bg-accent first:rounded-t-xl last:rounded-b-xl [.sheet-gap+&]:rounded-t-xl [&:has(+.sheet-gap)]:rounded-b-xl [.sheet-row+&]:border-t [.sheet-row+&]:border-border/60";
|
|
@@ -272,7 +273,7 @@ function ComposerSubmenu({
|
|
|
272
273
|
// A panel with no fixed height on desktop is content-sized on the
|
|
273
274
|
// phone too — one sizing decision, both presentations.
|
|
274
275
|
fit: panelClassName && !/(^|\s)h-\[/.test(panelClassName) ? "content" : "fill",
|
|
275
|
-
render: () => typeof children === "function" ? children(sheetNav.pop) : children
|
|
276
|
+
render: () => /* @__PURE__ */ jsx(SlotBoundary, { name: `menu:${typeof row.label === "string" ? row.label : "panel"}`, children: typeof children === "function" ? children(sheetNav.pop) : children })
|
|
276
277
|
})
|
|
277
278
|
}
|
|
278
279
|
);
|
|
@@ -321,7 +322,7 @@ function ComposerSubmenu({
|
|
|
321
322
|
panelClassName ?? "w-72",
|
|
322
323
|
"max-sm:!w-[calc(100vw-1rem)]"
|
|
323
324
|
),
|
|
324
|
-
children: /* @__PURE__ */ jsx(ComposerMenuLevel, { children: drillInPlace ? /* @__PURE__ */ jsx(InPlaceNav, { children: typeof children === "function" ? children(close) : children }) : typeof children === "function" ? children(close) : children })
|
|
325
|
+
children: /* @__PURE__ */ jsx(ComposerMenuLevel, { children: drillInPlace ? /* @__PURE__ */ jsx(InPlaceNav, { children: /* @__PURE__ */ jsx(SlotBoundary, { name: `menu:${typeof row.label === "string" ? row.label : "panel"}`, children: typeof children === "function" ? children(close) : children }) }) : /* @__PURE__ */ jsx(SlotBoundary, { name: `menu:${typeof row.label === "string" ? row.label : "panel"}`, children: typeof children === "function" ? children(close) : children }) })
|
|
325
326
|
}
|
|
326
327
|
)
|
|
327
328
|
] });
|
|
@@ -62,7 +62,8 @@ import { setUserInputText } from "../../../../redux/execution-system/instance-us
|
|
|
62
62
|
import { prependTemplateToDraft } from "../prepend-template-to-draft.js";
|
|
63
63
|
import { SmartInputMessageTemplatePicker } from "../../../../../host/ui-slots.js";
|
|
64
64
|
import { useOpenContextPreviewPanel } from "../../../../../host/window-openers.js";
|
|
65
|
-
import { useOpenRunControlsWindow } from "../../../../../host/window-openers.js";
|
|
65
|
+
import { useHasChatWindowOpener, useOpenRunControlsWindow } from "../../../../../host/window-openers.js";
|
|
66
|
+
import { isHostSlotRegistered } from "../../../../../host/ui-slots.js";
|
|
66
67
|
import { useOpenCloudBrowserCanvas } from "../../../../../host/ui-slots.js";
|
|
67
68
|
import { ActiveContextTree } from "../../../../../context/sources/scopes.js";
|
|
68
69
|
import { useComputeTargetActions } from "../use-compute-target-actions.js";
|
|
@@ -123,6 +124,9 @@ function ComposerPlusMenu({
|
|
|
123
124
|
const agentId = useAppSelector(selectAgentIdFromInstance(conversationId));
|
|
124
125
|
const openContextPreview = useOpenContextPreviewPanel();
|
|
125
126
|
const openRunControlsWindow = useOpenRunControlsWindow();
|
|
127
|
+
const runControlsWindowHosted = useHasChatWindowOpener("openRunControlsWindow");
|
|
128
|
+
const runControlsInline = presentation === "sheet" || !runControlsWindowHosted;
|
|
129
|
+
const hostPicker = isHostSlotRegistered("ResourcePickerMenu");
|
|
126
130
|
const openCloudBrowser = useOpenCloudBrowserCanvas();
|
|
127
131
|
const sandboxBlocked = useSandboxBindingBlocked(conversationId);
|
|
128
132
|
const workingDocEnabled = useAppSelector(selectWorkingDocEnabled(conversationId));
|
|
@@ -168,40 +172,42 @@ function ComposerPlusMenu({
|
|
|
168
172
|
/* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: FolderOpen, label: "Add files or photos" }, panelClassName: PICKER_PANEL, children: (closeCascade) => picker("files", closeCascade) })
|
|
169
173
|
),
|
|
170
174
|
/* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Link2, label: "Add a link" }, panelClassName: LINK_PANEL, children: (closeCascade) => picker("webpage", closeCascade) }),
|
|
171
|
-
/* @__PURE__ */
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
/* @__PURE__ */
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
),
|
|
183
|
-
/* @__PURE__ */ jsx(
|
|
184
|
-
ComposerSubmenu,
|
|
185
|
-
{
|
|
186
|
-
row: { icon: contextValuesItem().icon, label: contextValuesItem().label },
|
|
187
|
-
panelClassName: PICKER_PANEL,
|
|
188
|
-
children: (closeCascade) => picker("context_values", closeCascade)
|
|
189
|
-
}
|
|
190
|
-
),
|
|
191
|
-
/* @__PURE__ */ jsx(
|
|
192
|
-
ComposerMenuRow,
|
|
193
|
-
{
|
|
194
|
-
icon: Search,
|
|
195
|
-
label: "Search your knowledge",
|
|
196
|
-
detail: touchOnly ? void 0 : "\u2318K",
|
|
197
|
-
onClick: () => {
|
|
198
|
-
close();
|
|
199
|
-
openAttachSearch();
|
|
175
|
+
hostPicker ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
176
|
+
/* @__PURE__ */ jsx(
|
|
177
|
+
ComposerSubmenu,
|
|
178
|
+
{
|
|
179
|
+
row: { icon: Boxes, label: "From your workspace" },
|
|
180
|
+
panelClassName: CONTENT_PANEL,
|
|
181
|
+
drillInPlace: true,
|
|
182
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col p-1.5", children: [
|
|
183
|
+
/* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Attach from AI Matrx" }),
|
|
184
|
+
workspaceRows().map((row) => /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: row.icon, label: row.label }, children: (back) => picker(row.view, back) }, row.view))
|
|
185
|
+
] })
|
|
200
186
|
}
|
|
201
|
-
|
|
202
|
-
|
|
187
|
+
),
|
|
188
|
+
/* @__PURE__ */ jsx(
|
|
189
|
+
ComposerSubmenu,
|
|
190
|
+
{
|
|
191
|
+
row: { icon: contextValuesItem().icon, label: contextValuesItem().label },
|
|
192
|
+
panelClassName: PICKER_PANEL,
|
|
193
|
+
children: (closeCascade) => picker("context_values", closeCascade)
|
|
194
|
+
}
|
|
195
|
+
),
|
|
196
|
+
/* @__PURE__ */ jsx(
|
|
197
|
+
ComposerMenuRow,
|
|
198
|
+
{
|
|
199
|
+
icon: Search,
|
|
200
|
+
label: "Search your knowledge",
|
|
201
|
+
detail: touchOnly ? void 0 : "\u2318K",
|
|
202
|
+
onClick: () => {
|
|
203
|
+
close();
|
|
204
|
+
openAttachSearch();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
] }) : null,
|
|
203
209
|
shows("plus.skills") || shows("plus.tools") || shows("plus.connectors") || shows("plus.environment") ? /* @__PURE__ */ jsx(ComposerMenuDivider, {}) : null,
|
|
204
|
-
shows("plus.skills") ?
|
|
210
|
+
shows("plus.skills") ? runControlsInline ? /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Lightbulb, label: "Skills", badge: counts.skills }, children: /* @__PURE__ */ jsx(RunSkillPicker, { conversationId }) }) : /* @__PURE__ */ jsx(
|
|
205
211
|
ComposerMenuRow,
|
|
206
212
|
{
|
|
207
213
|
icon: Lightbulb,
|
|
@@ -213,7 +219,7 @@ function ComposerPlusMenu({
|
|
|
213
219
|
}
|
|
214
220
|
}
|
|
215
221
|
) : null,
|
|
216
|
-
shows("plus.tools") ?
|
|
222
|
+
shows("plus.tools") ? runControlsInline ? (
|
|
217
223
|
// Phone: a full-height page — THE Tools surface, one pane at a time.
|
|
218
224
|
/* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Wrench, label: "Tools", badge: counts.tools }, children: /* @__PURE__ */ jsx(RunToolPicker, { conversationId }) })
|
|
219
225
|
) : (
|
|
@@ -234,7 +240,7 @@ function ComposerPlusMenu({
|
|
|
234
240
|
) : null,
|
|
235
241
|
shows("plus.connectors") ? /* @__PURE__ */ jsxs(ComposerSubmenu, { row: { icon: Plug, label: "Connections" }, panelClassName: "w-[360px] h-[min(70dvh,480px)]", children: [
|
|
236
242
|
/* @__PURE__ */ jsx(ComposerConnectorsPanel, { conversationId, onNavigate: close }),
|
|
237
|
-
/* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Globe, label: "Google Workspace files" }, panelClassName: PICKER_PANEL, children: (closeCascade) => picker("google", closeCascade) })
|
|
243
|
+
hostPicker ? /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Globe, label: "Google Workspace files" }, panelClassName: PICKER_PANEL, children: (closeCascade) => picker("google", closeCascade) }) : null
|
|
238
244
|
] }) : null,
|
|
239
245
|
shows("plus.environment") ? /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Server, label: "Environment" }, panelClassName: "w-80", children: /* @__PURE__ */ jsx(
|
|
240
246
|
ComposerEnvironmentPanel,
|
|
@@ -41,6 +41,7 @@ import { isAttachmentMessagePart } from "../../context-items/normalize.js";
|
|
|
41
41
|
import MarkdownStream from "../../../../ui/markdown-stream/MarkdownStream.js";
|
|
42
42
|
import "../rich-document/chat-message-source.js";
|
|
43
43
|
import { literalUserText } from "./literal-user-text.js";
|
|
44
|
+
import { getConversationHooks } from "../../../../host/conversation-hooks.js";
|
|
44
45
|
import { buildVariableDisplayLines } from "../../../utils/variable-display-lines.js";
|
|
45
46
|
import {
|
|
46
47
|
recordTranscriptEvent,
|
|
@@ -57,7 +58,8 @@ function AgentUserMessageContent({
|
|
|
57
58
|
bodyBlocks,
|
|
58
59
|
variables
|
|
59
60
|
}) {
|
|
60
|
-
const
|
|
61
|
+
const shown = getConversationHooks(conversationId)?.displayUserText?.(text) ?? text;
|
|
62
|
+
const trimmedText = shown.trim();
|
|
61
63
|
const hasBodyBlocks = !!bodyBlocks && bodyBlocks.length > 0;
|
|
62
64
|
if (!trimmedText && !hasBodyBlocks && attachmentParts.length === 0 && (!variables || buildVariableDisplayLines(variables).length === 0)) {
|
|
63
65
|
return null;
|
|
@@ -120,6 +120,15 @@ interface AgentConversationColumnBaseProps {
|
|
|
120
120
|
* web chat exactly. See ./transcript-layout.tsx.
|
|
121
121
|
*/
|
|
122
122
|
layout?: TranscriptLayout;
|
|
123
|
+
/** A quiet note UNDER the composer (host chrome). Absent ⇒ nothing is drawn. */
|
|
124
|
+
belowInput?: React.ReactNode;
|
|
125
|
+
/**
|
|
126
|
+
* Words to put in an EMPTY composer, once per distinct value (a hand-off
|
|
127
|
+
* from elsewhere on the page). Never overwrites text the person typed.
|
|
128
|
+
*/
|
|
129
|
+
draftSeed?: string | null;
|
|
130
|
+
/** The column's accessible name (a host's keyboard scope may key on it). */
|
|
131
|
+
label?: string;
|
|
123
132
|
}
|
|
124
|
-
export declare function AgentConversationColumn({ conversationId, displayConversationId, surfaceKey, constrainWidth, edgeToEdgeScroll, smartInputProps, landingContent, hideInput, hideCreatorPanel, afterMessages, aboveInput, deferColdMarkdown, audience, layout, }: AgentConversationColumnProps): import("react").JSX.Element;
|
|
133
|
+
export declare function AgentConversationColumn({ conversationId, displayConversationId, surfaceKey, constrainWidth, edgeToEdgeScroll, smartInputProps, landingContent, hideInput, hideCreatorPanel, afterMessages, aboveInput, deferColdMarkdown, audience, layout, belowInput, draftSeed, label, }: AgentConversationColumnProps): import("react").JSX.Element;
|
|
125
134
|
export {};
|
|
@@ -19,6 +19,7 @@ import { PendingAsksZone } from "../../ui-first-tools/ui/PendingAsksZone.js";
|
|
|
19
19
|
import { ServerOperationBanner } from "../../runtime-reconnect/ServerOperationBanner.js";
|
|
20
20
|
import { ProposedDirectivesZone } from "../../../host/ui-slots.js";
|
|
21
21
|
import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
|
|
22
|
+
import { setUserInputText } from "../../redux/execution-system/instance-user-input/instance-user-input.slice.js";
|
|
22
23
|
import { selectMessageCount } from "../../redux/execution-system/messages/messages.selectors.js";
|
|
23
24
|
import {
|
|
24
25
|
revealOlderGroups,
|
|
@@ -63,12 +64,16 @@ function AgentConversationColumn({
|
|
|
63
64
|
aboveInput,
|
|
64
65
|
deferColdMarkdown = false,
|
|
65
66
|
audience = "builder",
|
|
66
|
-
layout
|
|
67
|
+
layout,
|
|
68
|
+
belowInput,
|
|
69
|
+
draftSeed,
|
|
70
|
+
label
|
|
67
71
|
}) {
|
|
68
72
|
const resolvedLayout = resolveTranscriptLayout(layout);
|
|
69
73
|
const columnClass = transcriptColumnClass(resolvedLayout);
|
|
70
74
|
const dispatch = useAppDispatch();
|
|
71
75
|
const displayId = displayConversationId ?? conversationId;
|
|
76
|
+
useDraftSeed(conversationId, draftSeed ?? null);
|
|
72
77
|
const scrollRef = useRef(null);
|
|
73
78
|
const contentRef = useRef(null);
|
|
74
79
|
const didAutoRevealRef = useRef(false);
|
|
@@ -213,6 +218,8 @@ function AgentConversationColumn({
|
|
|
213
218
|
return /* @__PURE__ */ jsx(ConversationAbilitiesProvider, { conversationId: displayId, children: /* @__PURE__ */ jsx(TranscriptAudienceProvider, { audience, children: /* @__PURE__ */ jsx(TranscriptLayoutProvider, { layout: resolvedLayout, children: /* @__PURE__ */ jsxs(
|
|
214
219
|
"div",
|
|
215
220
|
{
|
|
221
|
+
"data-chat-column": "",
|
|
222
|
+
...label ? { "aria-label": label, role: "region" } : {},
|
|
216
223
|
className: cn(
|
|
217
224
|
"h-full flex flex-col overflow-hidden",
|
|
218
225
|
// Legacy: pad the whole column. Edge mode: padding lives on the
|
|
@@ -349,7 +356,8 @@ function AgentConversationColumn({
|
|
|
349
356
|
surfaceKey,
|
|
350
357
|
...smartInputProps
|
|
351
358
|
}
|
|
352
|
-
)
|
|
359
|
+
),
|
|
360
|
+
belowInput ? /* @__PURE__ */ jsx("div", { className: "px-1 pb-1 text-xs text-muted-foreground", "data-chat-below-input": "", children: belowInput }) : null
|
|
353
361
|
]
|
|
354
362
|
}
|
|
355
363
|
)
|
|
@@ -357,6 +365,22 @@ function AgentConversationColumn({
|
|
|
357
365
|
}
|
|
358
366
|
) }) }) });
|
|
359
367
|
}
|
|
368
|
+
function useDraftSeed(conversationId, seed) {
|
|
369
|
+
const dispatch = useAppDispatch();
|
|
370
|
+
const current = useAppSelector(
|
|
371
|
+
(state) => state.instanceUserInput?.byConversationId[conversationId]?.text ?? null
|
|
372
|
+
);
|
|
373
|
+
const exists = useAppSelector(
|
|
374
|
+
(state) => state.instanceUserInput?.byConversationId[conversationId] !== void 0
|
|
375
|
+
);
|
|
376
|
+
const appliedRef = useRef(null);
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
if (!seed || !exists || appliedRef.current === seed) return;
|
|
379
|
+
appliedRef.current = seed;
|
|
380
|
+
if (current && current.trim()) return;
|
|
381
|
+
dispatch(setUserInputText({ conversationId, text: seed }));
|
|
382
|
+
}, [seed, exists, current, conversationId, dispatch]);
|
|
383
|
+
}
|
|
360
384
|
export {
|
|
361
385
|
AgentConversationColumn
|
|
362
386
|
};
|
|
@@ -349,7 +349,12 @@ const messagesSlice = createSlice({
|
|
|
349
349
|
// The live receipt link — the persisted receipt wins once the
|
|
350
350
|
// hydrated row carries one (selectMessageContextReceipt).
|
|
351
351
|
...live?._requestId ? { _requestId: live._requestId } : {},
|
|
352
|
-
...live?._liveContextReceipt ? { _liveContextReceipt: live._liveContextReceipt } : {}
|
|
352
|
+
...live?._liveContextReceipt ? { _liveContextReceipt: live._liveContextReceipt } : {},
|
|
353
|
+
// An answer open in the in-place editor stays open: the flag is
|
|
354
|
+
// client-only, and a re-read of the conversation (a window loading
|
|
355
|
+
// the conversation the page already loaded) used to drop it, closing
|
|
356
|
+
// the editor ~1.5s after a double-click and discarding the draft.
|
|
357
|
+
...live?._editingInPlace ? { _editingInPlace: live._editingInPlace } : {}
|
|
353
358
|
};
|
|
354
359
|
entry.orderedIds.push(msg.id);
|
|
355
360
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A SEND THAT BEATS ITS LAUNCH WAITS FOR IT.
|
|
3
|
+
*
|
|
4
|
+
* A fresh chat's composer is interactive as soon as it mounts, holding the id the launcher
|
|
5
|
+
* reserved, while the launcher is still creating that conversation (the agent's run controls, the
|
|
6
|
+
* organization, the mandate are being read). A person who types fast pressed Send in that window
|
|
7
|
+
* and the send was dropped as "no longer exists in this tab" (P29a canary: a send ~7 s after load,
|
|
8
|
+
* right after the workspace was chosen). The conversation was not gone; it had not landed yet.
|
|
9
|
+
*
|
|
10
|
+
* So the send waits, briefly, for its conversation to appear. A conversation that really was
|
|
11
|
+
* reaped never appears, and the caller still says so after the wait.
|
|
12
|
+
*/
|
|
13
|
+
import type { ChatRootState } from "../../../../store/root-state.js";
|
|
14
|
+
/** How long a send waits for a launching conversation before calling it gone. */
|
|
15
|
+
export declare const LAUNCHING_CONVERSATION_WAIT_MS = 15000;
|
|
16
|
+
export declare function awaitLaunchingConversation(getState: () => ChatRootState, conversationId: string, timeoutMs?: number): Promise<boolean>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const LAUNCHING_CONVERSATION_WAIT_MS = 15e3;
|
|
2
|
+
const POLL_MS = 50;
|
|
3
|
+
async function awaitLaunchingConversation(getState, conversationId, timeoutMs = LAUNCHING_CONVERSATION_WAIT_MS) {
|
|
4
|
+
const exists = () => getState().conversations.byConversationId[conversationId] !== void 0;
|
|
5
|
+
if (exists()) return true;
|
|
6
|
+
const deadline = Date.now() + timeoutMs;
|
|
7
|
+
while (Date.now() < deadline) {
|
|
8
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
9
|
+
if (exists()) return true;
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
LAUNCHING_CONVERSATION_WAIT_MS,
|
|
15
|
+
awaitLaunchingConversation
|
|
16
|
+
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executionRejectionMeta
|
|
3
3
|
} from "./execution-rejection-meta.js";
|
|
4
|
+
import {
|
|
5
|
+
continuesThroughMandate,
|
|
6
|
+
takeTurnExtras
|
|
7
|
+
} from "../../../../host/conversation-hooks.js";
|
|
4
8
|
import { isHostConversation } from "../host-conversations/host-conversations.selectors.js";
|
|
5
9
|
import { attributionForSend } from "../../../../host/app-identity.js";
|
|
6
10
|
import { mintClientTempId } from "@ai-matrx/kit/ids";
|
|
@@ -483,7 +487,24 @@ const executeInstance = createAsyncThunk(
|
|
|
483
487
|
const pendingBypass = dispatch(
|
|
484
488
|
consumePendingCacheBypass(conversationId)
|
|
485
489
|
);
|
|
486
|
-
|
|
490
|
+
const hostExtras = takeTurnExtras(conversationId);
|
|
491
|
+
if (isContinuation && instance.mandateKey && !instance.initialAgentVersionId && continuesThroughMandate(conversationId)) {
|
|
492
|
+
const start = resolveStartPath({
|
|
493
|
+
agentId: instance.agentId,
|
|
494
|
+
pinnedVersionId: null,
|
|
495
|
+
mandateKey: instance.mandateKey,
|
|
496
|
+
overrideConfig: overrideConfig ?? void 0
|
|
497
|
+
});
|
|
498
|
+
url = `${baseUrl}${start.path}`;
|
|
499
|
+
routedPayload = {
|
|
500
|
+
...payload,
|
|
501
|
+
conversation_id: conversationId,
|
|
502
|
+
is_new: false,
|
|
503
|
+
store: true,
|
|
504
|
+
...retry && { retry: true },
|
|
505
|
+
...pendingBypass && { cache_bypass: pendingBypass }
|
|
506
|
+
};
|
|
507
|
+
} else if (isContinuation) {
|
|
487
508
|
const convPath = resolveEndpointPath(
|
|
488
509
|
"/ai/conversations/{conversation_id}",
|
|
489
510
|
overrideConfig
|
|
@@ -510,6 +531,18 @@ const executeInstance = createAsyncThunk(
|
|
|
510
531
|
...pendingBypass && { cache_bypass: pendingBypass }
|
|
511
532
|
};
|
|
512
533
|
}
|
|
534
|
+
if (hostExtras?.variables) {
|
|
535
|
+
routedPayload.variables = {
|
|
536
|
+
...routedPayload.variables ?? {},
|
|
537
|
+
...hostExtras.variables
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
if (hostExtras?.context) {
|
|
541
|
+
routedPayload.context = {
|
|
542
|
+
...routedPayload.context ?? {},
|
|
543
|
+
...hostExtras.context
|
|
544
|
+
};
|
|
545
|
+
}
|
|
513
546
|
{
|
|
514
547
|
const { overrides, error } = resolveRequestOverrides(
|
|
515
548
|
state,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { tapStreamEvent } from "../../../../host/conversation-hooks.js";
|
|
1
2
|
import { selectHostConversation } from "../host-conversations/host-conversations.selectors.js";
|
|
2
3
|
import {
|
|
3
4
|
applyServerUsageState,
|
|
@@ -493,8 +494,16 @@ async function processStream({
|
|
|
493
494
|
}) : rawEvents;
|
|
494
495
|
streamFailure = null;
|
|
495
496
|
let sawTerminalEvent = false;
|
|
497
|
+
let tapHeld = null;
|
|
498
|
+
const flushTap = () => {
|
|
499
|
+
if (!tapHeld) return;
|
|
500
|
+
const held = tapHeld;
|
|
501
|
+
tapHeld = null;
|
|
502
|
+
tapStreamEvent(conversationId, requestId, held.event);
|
|
503
|
+
};
|
|
496
504
|
try {
|
|
497
505
|
for await (const event of events) {
|
|
506
|
+
flushTap();
|
|
498
507
|
const stamp = readStreamCursor(event);
|
|
499
508
|
if (stamp !== null) {
|
|
500
509
|
if (!transportCursor.admit(event)) continue;
|
|
@@ -506,6 +515,7 @@ async function processStream({
|
|
|
506
515
|
scheduleBatchEvent();
|
|
507
516
|
}
|
|
508
517
|
totalEvents++;
|
|
518
|
+
tapHeld = { event };
|
|
509
519
|
const now = performance.now();
|
|
510
520
|
if (activeOnEvent) {
|
|
511
521
|
try {
|
|
@@ -2080,6 +2090,7 @@ async function processStream({
|
|
|
2080
2090
|
} catch (err) {
|
|
2081
2091
|
streamFailure = err instanceof Error ? err : new Error(String(err));
|
|
2082
2092
|
}
|
|
2093
|
+
flushTap();
|
|
2083
2094
|
if (sawTerminalEvent && streamFailure !== null) {
|
|
2084
2095
|
streamFailure = null;
|
|
2085
2096
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
SUBMISSION_REFUSED_AT_ADMISSION
|
|
7
7
|
} from "./execute-instance.thunk.js";
|
|
8
8
|
import { executeManualInstance } from "./execute-manual-instance.thunk.js";
|
|
9
|
+
import { awaitLaunchingConversation } from "./await-launching-conversation.js";
|
|
9
10
|
import { splitInputIntoNewConversation } from "./create-instance.thunk.js";
|
|
10
11
|
import { abortConversation, hasAbortController } from "./abort-registry.js";
|
|
11
12
|
import { setInstanceStatus } from "../conversations/conversations.slice.js";
|
|
@@ -65,6 +66,7 @@ import { markResourcesSubmitted } from "../instance-resources/instance-resources
|
|
|
65
66
|
import { flushRemarkWrites, hasPendingRemarkWrites } from "../instance-resources/remarks.js";
|
|
66
67
|
import { withFreshRemarkRefs } from "../instance-resources/remarks-wire.js";
|
|
67
68
|
import { isOrganizationSelectionCancelled } from "../../../../host/org.js";
|
|
69
|
+
import { runBeforeSend } from "../../../../host/conversation-hooks.js";
|
|
68
70
|
function hasConversationAtExecutionBoundary(state, conversationId) {
|
|
69
71
|
return state.conversations.byConversationId[conversationId] !== void 0;
|
|
70
72
|
}
|
|
@@ -152,6 +154,9 @@ function holdBehindInFlightSend(dispatch, getState, args) {
|
|
|
152
154
|
const smartExecute = createAsyncThunk(
|
|
153
155
|
"instances/smartExecute",
|
|
154
156
|
async ({ conversationId, surfaceKey, whileRunning = "queue", submission: handed }, { getState, dispatch }) => {
|
|
157
|
+
if (!handed && !hasConversationAtExecutionBoundary(getState(), conversationId)) {
|
|
158
|
+
await awaitLaunchingConversation(getState, conversationId);
|
|
159
|
+
}
|
|
155
160
|
const entryState = getState();
|
|
156
161
|
if (!handed) {
|
|
157
162
|
const inputEntry = entryState.instanceUserInput?.byConversationId[conversationId];
|
|
@@ -322,6 +327,11 @@ const smartExecute = createAsyncThunk(
|
|
|
322
327
|
})
|
|
323
328
|
);
|
|
324
329
|
preSendShown = true;
|
|
330
|
+
const hostGate = await runBeforeSend(conversationId, submission.text);
|
|
331
|
+
if (!hostGate.ok) {
|
|
332
|
+
toast.info("Not sent", { description: hostGate.sentence });
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
325
335
|
try {
|
|
326
336
|
await dispatch(
|
|
327
337
|
refreshSurfaceScope({ conversationId, composerText: submission.text })
|
|
@@ -76,7 +76,6 @@ export declare const useAnchorRecords: (anchor: {
|
|
|
76
76
|
type: string;
|
|
77
77
|
id: string;
|
|
78
78
|
} | null, enabled?: boolean) => AnchorRecordsState;
|
|
79
|
-
/** A host with no shape catalog has no shapes to browse: the read says so, the picker shows it. */
|
|
80
79
|
export declare const fetchShapePage: (query: EntityListQuery, sort: EntityListSort) => Promise<EntityListPage<ShapeBrowseRow>>;
|
|
81
80
|
export declare const fetchShapeByKind: (kind: string) => Promise<ShapeBrowseRow | null>;
|
|
82
81
|
/** A host with no Source Inspector opens a web source in a new tab; a file source has no target here. */
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { hostFn, hostSlot } from "./ui-slots.js";
|
|
2
|
+
import { listOrgParam } from "@ai-matrx/records/list";
|
|
3
|
+
import { supabase } from "./db.js";
|
|
2
4
|
const CONVERSATION_ANCHOR_TYPE = "conversation";
|
|
3
5
|
const contentIrKindValidator = hostFn("contentIrKindValidator", () => ({
|
|
4
6
|
validate: async (_value, kind) => ({
|
|
@@ -20,11 +22,29 @@ const useAnchorRecords = hostFn("useAnchorRecords", () => ({
|
|
|
20
22
|
message: null,
|
|
21
23
|
reload: () => void 0
|
|
22
24
|
}));
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
async function readShapePage(query, sort) {
|
|
26
|
+
const { data, error } = await supabase.rpc("shx_list_scoped", {
|
|
27
|
+
p_scope: query.scope.kind,
|
|
28
|
+
p_org_id: listOrgParam(query),
|
|
29
|
+
p_search: query.search.trim() || void 0,
|
|
30
|
+
p_deep: query.deep,
|
|
31
|
+
p_sort: sort.sort,
|
|
32
|
+
p_dir: sort.direction,
|
|
33
|
+
p_filters: query.filters,
|
|
34
|
+
p_limit: sort.pageSize,
|
|
35
|
+
p_offset: (query.page - 1) * sort.pageSize
|
|
36
|
+
});
|
|
37
|
+
if (error) throw new Error(`Could not load the shapes: ${error.message ?? "the database returned an error with no message."}`);
|
|
38
|
+
const rows = data ?? [];
|
|
39
|
+
return { rows, total: rows.length > 0 ? Number(rows[0].total_count) : 0 };
|
|
40
|
+
}
|
|
41
|
+
const fetchShapePage = hostFn("fetchShapePage", readShapePage);
|
|
42
|
+
const fetchShapeByKind = hostFn("fetchShapeByKind", async (kind) => {
|
|
43
|
+
const page = await readShapePage(
|
|
44
|
+
{ scope: { kind: "all" }, orgId: null, search: "", deep: false, archived: "active", filters: { kind: { kind: "text", value: kind } }, page: 1 },
|
|
45
|
+
{ sort: "label", direction: "asc", favoritesFirst: false, pageSize: 50 }
|
|
46
|
+
);
|
|
47
|
+
return page.rows.find((row) => row.kind === kind) ?? null;
|
|
28
48
|
});
|
|
29
49
|
const useOpenCitationSource = hostFn("useOpenCitationSource", () => (source) => {
|
|
30
50
|
if (source.url && typeof window !== "undefined") window.open(source.url, "_blank", "noopener,noreferrer");
|
|
@@ -8,6 +8,8 @@ import { AgentCatalogProvider } from "@ai-matrx/agents/catalog/react";
|
|
|
8
8
|
import { createModelCatalog, createModelFavorites } from "@ai-matrx/agents/models";
|
|
9
9
|
import { ModelCatalogProvider } from "@ai-matrx/agents/models/react";
|
|
10
10
|
import { hasChatScopes, registerChatScopes } from "../context/sources/scopes.js";
|
|
11
|
+
import { configureBareFilesHost } from "./defaults/files-host.js";
|
|
12
|
+
import { registerBareReadableBlocks } from "./defaults/bare-block-dispatch.js";
|
|
11
13
|
const SAVE_REASON = "save this";
|
|
12
14
|
function BareHostChrome({ host, children }) {
|
|
13
15
|
const [associations] = useState(
|
|
@@ -33,6 +35,8 @@ function BareHostChrome({ host, children }) {
|
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
});
|
|
38
|
+
useState(() => configureBareFilesHost(host));
|
|
39
|
+
useState(() => registerBareReadableBlocks());
|
|
36
40
|
const [catalog] = useState(() => host.catalog());
|
|
37
41
|
const [models] = useState(() => ({
|
|
38
42
|
catalog: createModelCatalog({ client: host.db }),
|
package/dist/host/contract.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export type ChatDb = SupabaseClient<ChatDatabase>;
|
|
|
38
38
|
* (migrations/log_client_error_names_the_feature_too.sql). A value outside it
|
|
39
39
|
* makes the database refuse the row, so the diagnostics default checks it first.
|
|
40
40
|
*/
|
|
41
|
-
export declare const CHAT_SOURCE_APPS: readonly ["matrx-frontend", "matrx-extend", "matrx-local", "matrx-mobile"];
|
|
41
|
+
export declare const CHAT_SOURCE_APPS: readonly ["matrx-frontend", "matrx-extend", "matrx-local", "matrx-mobile", "workflow-studio"];
|
|
42
42
|
export type ChatSourceApp = (typeof CHAT_SOURCE_APPS)[number];
|
|
43
43
|
export type ChatAdminLevel = "developer" | "senior_admin" | "super_admin";
|
|
44
44
|
export interface ChatIdentity {
|
package/dist/host/contract.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
import { readConversationHeld, subscribeConversationHeld } from "./conversation-hooks.js";
|
|
3
|
+
function useConversationHeld(conversationId) {
|
|
4
|
+
return useSyncExternalStore(
|
|
5
|
+
(listener) => subscribeConversationHeld(conversationId, listener),
|
|
6
|
+
() => readConversationHeld(conversationId),
|
|
7
|
+
() => null
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
useConversationHeld
|
|
12
|
+
};
|