@ai-matrx/chat 0.4.21 → 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.
Files changed (60) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/agents/components/context-items/bodies/UnregisteredBody.d.ts +6 -3
  3. package/dist/agents/components/context-items/bodies/UnregisteredBody.js +3 -9
  4. package/dist/agents/components/inputs/resources/attach-resource.js +1 -0
  5. package/dist/agents/components/inputs/smart-input/InboxQueueStrip.d.ts +7 -0
  6. package/dist/agents/components/inputs/smart-input/InboxQueueStrip.js +61 -6
  7. package/dist/agents/components/inputs/smart-input/SmartAgentInput.js +16 -1
  8. package/dist/agents/components/inputs/smart-input/composer/ComposerMenu.js +3 -2
  9. package/dist/agents/components/inputs/smart-input/composer/ComposerPlusMenu.js +41 -35
  10. package/dist/agents/components/messages-display/user/AgentUserMessage.js +3 -1
  11. package/dist/agents/components/shared/AgentConversationColumn.d.ts +10 -1
  12. package/dist/agents/components/shared/AgentConversationColumn.js +26 -2
  13. package/dist/agents/redux/execution-system/inbox/inbox.selectors.d.ts +3 -1
  14. package/dist/agents/redux/execution-system/inbox/inbox.selectors.js +3 -0
  15. package/dist/agents/redux/execution-system/inbox/inbox.slice.d.ts +19 -1
  16. package/dist/agents/redux/execution-system/inbox/inbox.slice.js +36 -1
  17. package/dist/agents/redux/execution-system/messages/messages.slice.js +6 -1
  18. package/dist/agents/redux/execution-system/thunks/await-launching-conversation.d.ts +16 -0
  19. package/dist/agents/redux/execution-system/thunks/await-launching-conversation.js +16 -0
  20. package/dist/agents/redux/execution-system/thunks/execute-instance.thunk.js +34 -1
  21. package/dist/agents/redux/execution-system/thunks/process-stream.js +13 -2
  22. package/dist/agents/redux/execution-system/thunks/smart-execute.thunk.js +10 -0
  23. package/dist/host/app-data-slots.d.ts +0 -1
  24. package/dist/host/app-data-slots.js +25 -5
  25. package/dist/host/bare-host-chrome.js +4 -0
  26. package/dist/host/contract.d.ts +1 -1
  27. package/dist/host/contract.js +2 -1
  28. package/dist/host/conversation-hooks-react.d.ts +2 -0
  29. package/dist/host/conversation-hooks-react.js +12 -0
  30. package/dist/host/conversation-hooks.d.ts +79 -0
  31. package/dist/host/conversation-hooks.js +91 -0
  32. package/dist/host/defaults/bare-block-dispatch.d.ts +6 -0
  33. package/dist/host/defaults/bare-block-dispatch.js +60 -0
  34. package/dist/host/defaults/files-host.d.ts +26 -0
  35. package/dist/host/defaults/files-host.js +212 -0
  36. package/dist/host/defaults/resource-picker-menu.d.ts +14 -0
  37. package/dist/host/defaults/resource-picker-menu.js +117 -0
  38. package/dist/host/defaults/setting-control-input.d.ts +11 -0
  39. package/dist/host/defaults/setting-control-input.js +91 -0
  40. package/dist/host/index.d.ts +1 -0
  41. package/dist/host/index.js +1 -0
  42. package/dist/host/react.d.ts +1 -0
  43. package/dist/host/react.js +2 -0
  44. package/dist/host/ui-slots.d.ts +25 -1
  45. package/dist/host/ui-slots.js +59 -15
  46. package/dist/host/window-openers.d.ts +2 -0
  47. package/dist/host/window-openers.js +4 -0
  48. package/dist/react.cjs +1 -1
  49. package/dist/react.cjs.map +1 -1
  50. package/dist/react.js +1 -1
  51. package/dist/react.js.map +1 -1
  52. package/dist/tool-call-visualization/registry/registry.js +4 -4
  53. package/dist/ui/chat-panel/ChatPanel.d.ts +50 -0
  54. package/dist/ui/chat-panel/ChatPanel.js +175 -0
  55. package/dist/ui/chat-panel/drag.d.ts +87 -0
  56. package/dist/ui/chat-panel/drag.js +194 -0
  57. package/dist/window-panels/windows/agents/AgentRunWindow.js +17 -0
  58. package/dist/window-panels/windows/agents/agentRunWindowAddress.d.ts +19 -0
  59. package/dist/window-panels/windows/agents/agentRunWindowAddress.js +11 -1
  60. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
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
+
14
+ ## 0.4.22
15
+
16
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
17
+ `git diff npm/chat/v0.4.21..npm/chat/v0.4.22 -- apps/shared/chat`).
18
+ No source changes intended and no consumer action required.
19
+
20
+ ## 0.4.21 — Queued lines show their wait and their delivery
21
+
22
+ - The waiting-messages strip shows how long each queued line has waited (a live 1 s timer), and
23
+ when the agent takes a line it flashes "Delivered · waited 12s" for 1.6 s before clearing.
24
+ New slice actions `deliverInboxItem` / `clearDeliveredInboxItem` and selector
25
+ `selectDeliveredInboxItems`; delivery still leaves the queue exactly like a removal, so
26
+ `selectInboxItemStatus` reads `null` on delivery as before. Ported from the browser extension.
27
+
3
28
  ## 0.4.21
4
29
 
5
30
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -1,7 +1,10 @@
1
- /** The generic default for a context-item body the host did not register: the generic body, labelled. */
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, what, props }: {
6
+ export declare function UnregisteredBody({ name, props }: {
4
7
  name: string;
5
- what: string;
8
+ what?: string;
6
9
  props: ContextItemBodyProps;
7
10
  }): import("react").JSX.Element;
@@ -1,14 +1,8 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx } from "react/jsx-runtime";
3
3
  import { GenericBody } from "./GenericBody.js";
4
- function UnregisteredBody({ name, what, props }) {
5
- return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col", "data-chat-slot-fallback": name, children: [
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";
@@ -20,6 +20,13 @@ import React from "react";
20
20
  interface InboxQueueStripProps {
21
21
  conversationId: string;
22
22
  }
23
+ /** How long a delivered line keeps its "Delivered" moment before it clears (ms). */
24
+ export declare const DELIVERED_LINGER_MS = 1600;
25
+ /**
26
+ * How long a line has waited — kit's `compact` elapsed voice, floored to whole
27
+ * seconds and rounded DOWN so the 1 s ticker never shows time that has not passed.
28
+ */
29
+ export declare function waitedLabel(fromIso: string, toMs: number): string;
23
30
  /**
24
31
  * Delivered collab notes are prefixed "[Collaboration note] Agent '<name>' …"
25
32
  * server-side; the queued text carries the same prefix. Pull the agent name
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import React, { useState } from "react";
3
+ import React, { useEffect, useState } from "react";
4
+ import { formatDurationSeconds } from "@ai-matrx/kit/format";
4
5
  import {
5
6
  ArrowUp,
7
+ Check,
6
8
  CircleHelp,
7
9
  Clock,
8
10
  Handshake,
@@ -19,7 +21,10 @@ import {
19
21
  TooltipContent,
20
22
  TooltipTrigger
21
23
  } from "@ai-matrx/design-system";
22
- import { selectInboxItems } from "../../../redux/execution-system/inbox/inbox.selectors.js";
24
+ import {
25
+ selectDeliveredInboxItems,
26
+ selectInboxItems
27
+ } from "../../../redux/execution-system/inbox/inbox.selectors.js";
23
28
  import { TERMINAL_RUNTIME_STATUSES } from "../../../runtime-reconnect/types.js";
24
29
  import { selectIsExecuting } from "../../../redux/execution-system/selectors/aggregate.selectors.js";
25
30
  import {
@@ -29,11 +34,27 @@ import {
29
34
  editInboxItem
30
35
  } from "../../../redux/execution-system/inbox/inbox.thunks.js";
31
36
  import {
37
+ clearDeliveredInboxItem,
32
38
  removeInboxItem
33
39
  } from "../../../redux/execution-system/inbox/inbox.slice.js";
34
40
  import { Button } from "@ai-matrx/design-system/controls";
35
41
  import { useConversationAbilities } from "../../../redux/execution-system/host-conversations/conversation-abilities.js";
36
42
  const queueActionClassName = "h-11 w-11 min-h-11 min-w-11 shrink-0 rounded p-0 text-muted-foreground hover:bg-muted hover:text-foreground lg:h-8 lg:w-8 lg:min-h-8 lg:min-w-8";
43
+ const DELIVERED_LINGER_MS = 1600;
44
+ function waitedLabel(fromIso, toMs) {
45
+ const seconds = Math.max(0, Math.floor((toMs - Date.parse(fromIso)) / 1e3));
46
+ return formatDurationSeconds(seconds, { style: "compact", round: "down" });
47
+ }
48
+ function useSecondTicker(active) {
49
+ const [now, setNow] = useState(() => Date.now());
50
+ useEffect(() => {
51
+ if (!active) return;
52
+ setNow(Date.now());
53
+ const id = setInterval(() => setNow(Date.now()), 1e3);
54
+ return () => clearInterval(id);
55
+ }, [active]);
56
+ return now;
57
+ }
37
58
  function statusLabel(item, running) {
38
59
  if (item.status === "failed") return item.error ?? "Failed to send";
39
60
  if (item.status === "sending") return "Sending\u2026";
@@ -71,7 +92,22 @@ function InboxQueueStripBody({ conversationId }) {
71
92
  /* @__PURE__ */ new Set()
72
93
  );
73
94
  const visible = items.filter((i) => i.isVisibleToUser || isCollabNoteItem(i));
74
- if (visible.length === 0) return null;
95
+ const delivered = useAppSelector(selectDeliveredInboxItems(conversationId));
96
+ const hasWaiting = visible.some(
97
+ (i) => (i.status === "pending" || i.status === "sending") && !isCollabNoteItem(i)
98
+ );
99
+ const now = useSecondTicker(hasWaiting);
100
+ useEffect(() => {
101
+ if (delivered.length === 0) return;
102
+ const timers = delivered.map(
103
+ (d) => setTimeout(
104
+ () => dispatch(clearDeliveredInboxItem({ conversationId, injectionId: d.injectionId })),
105
+ Math.max(0, DELIVERED_LINGER_MS - (Date.now() - Date.parse(d.deliveredAt)))
106
+ )
107
+ );
108
+ return () => timers.forEach(clearTimeout);
109
+ }, [delivered, conversationId, dispatch]);
110
+ if (visible.length === 0 && delivered.length === 0) return null;
75
111
  const isDeparting = (item) => !isCollabNoteItem(item) && (item.mode === "steer" || promotingIds.has(item.injectionId));
76
112
  const departing = visible.filter(isDeparting);
77
113
  const waiting = visible.filter((item) => !isDeparting(item));
@@ -142,13 +178,16 @@ function InboxQueueStripBody({ conversationId }) {
142
178
  )
143
179
  ] }),
144
180
  /* @__PURE__ */ jsxs(Tooltip, { children: [
145
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
181
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
146
182
  "span",
147
183
  {
148
184
  tabIndex: 0,
149
185
  role: "status",
150
186
  className: `block truncate rounded-sm text-[11px] leading-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring lg:cursor-help ${failed ? "text-destructive" : "text-muted-foreground"}`,
151
- children: promoting ? "Sending\u2026" : statusLabel(item, running)
187
+ children: [
188
+ promoting ? "Sending\u2026" : statusLabel(item, running),
189
+ (item.status === "pending" || item.status === "sending") && !collabNote ? ` \xB7 ${waitedLabel(item.queuedAt, now)}` : null
190
+ ]
152
191
  }
153
192
  ) }),
154
193
  /* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "max-w-[20rem]", children: statusHelp })
@@ -262,6 +301,20 @@ function InboxQueueStripBody({ conversationId }) {
262
301
  )
263
302
  ] }, item.injectionId);
264
303
  }),
304
+ delivered.map((d) => /* @__PURE__ */ jsxs(
305
+ "div",
306
+ {
307
+ "data-inbox-delivered": "",
308
+ role: "status",
309
+ className: "flex min-w-0 items-center gap-2 rounded-lg border border-emerald-500/30 bg-emerald-500/[0.08] px-3 py-2 text-xs motion-safe:animate-out motion-safe:fade-out motion-safe:delay-1000 motion-safe:duration-500",
310
+ children: [
311
+ /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 shrink-0 text-emerald-600 dark:text-emerald-400" }),
312
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-foreground", children: d.text }),
313
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: `Delivered \xB7 waited ${waitedLabel(d.queuedAt, Date.parse(d.deliveredAt))}` })
314
+ ]
315
+ },
316
+ `delivered-${d.injectionId}`
317
+ )),
265
318
  /* @__PURE__ */ jsx(
266
319
  TextInputDialog,
267
320
  {
@@ -296,6 +349,8 @@ function InboxQueueStripBody({ conversationId }) {
296
349
  ] });
297
350
  }
298
351
  export {
352
+ DELIVERED_LINGER_MS,
299
353
  InboxQueueStrip,
300
- parseCollabNoteAgent
354
+ parseCollabNoteAgent,
355
+ waitedLabel
301
356
  };
@@ -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 sendDisabled = disableSend || ambientHasNothingToSend;
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__ */ jsx(
172
- ComposerSubmenu,
173
- {
174
- row: { icon: Boxes, label: "From your workspace" },
175
- panelClassName: CONTENT_PANEL,
176
- drillInPlace: true,
177
- children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col p-1.5", children: [
178
- /* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Attach from AI Matrx" }),
179
- workspaceRows().map((row) => /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: row.icon, label: row.label }, children: (back) => picker(row.view, back) }, row.view))
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") ? presentation === "sheet" ? /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Lightbulb, label: "Skills", badge: counts.skills }, children: /* @__PURE__ */ jsx(RunSkillPicker, { conversationId }) }) : /* @__PURE__ */ jsx(
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") ? presentation === "sheet" ? (
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 trimmedText = text.trim();
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
  };
@@ -2,7 +2,9 @@
2
2
  * conversationInbox selectors — queued-while-running message cards.
3
3
  */
4
4
  import type { ChatRootState } from "../../../../store/root-state.js";
5
- import type { ConversationInboxItem } from "./inbox.slice.js";
5
+ import type { ConversationInboxItem, DeliveredInboxItem } from "./inbox.slice.js";
6
+ /** Lines the agent just took, still showing their "Delivered" moment. Stable empty reference. */
7
+ export declare const selectDeliveredInboxItems: (conversationId: string) => (state: ChatRootState) => DeliveredInboxItem[];
6
8
  /** All queued items for a conversation, FIFO. Stable empty reference. */
7
9
  export declare const selectInboxItems: (conversationId: string) => (state: ChatRootState) => ConversationInboxItem[];
8
10
  /** Count of items still waiting (sending + pending). Primitive — no memo needed. */
@@ -1,4 +1,6 @@
1
1
  const EMPTY_ITEMS = [];
2
+ const EMPTY_DELIVERED = [];
3
+ const selectDeliveredInboxItems = (conversationId) => (state) => state.conversationInbox?.deliveredByConversationId?.[conversationId] ?? EMPTY_DELIVERED;
2
4
  const selectInboxItems = (conversationId) => (state) => state.conversationInbox?.byConversationId[conversationId] ?? EMPTY_ITEMS;
3
5
  const selectInboxWaitingCount = (conversationId) => (state) => {
4
6
  const items = state.conversationInbox?.byConversationId[conversationId];
@@ -17,6 +19,7 @@ function isStrandablePersonLine(item) {
17
19
  }
18
20
  export {
19
21
  isStrandablePersonLine,
22
+ selectDeliveredInboxItems,
20
23
  selectInboxItemStatus,
21
24
  selectInboxItems,
22
25
  selectInboxWaitingCount
@@ -49,9 +49,20 @@ export interface ConversationInboxItem {
49
49
  */
50
50
  source?: string | null;
51
51
  }
52
+ /** A queued line the agent just took — shown as "Delivered" for a moment, then cleared. */
53
+ export interface DeliveredInboxItem {
54
+ injectionId: string;
55
+ text: string;
56
+ mode: InboxItemMode;
57
+ queuedAt: string;
58
+ /** ISO timestamp — when `injection_consumed` named it. */
59
+ deliveredAt: string;
60
+ }
52
61
  export interface ConversationInboxState {
53
62
  /** FIFO per conversation — order mirrors server `enqueued_seq`. */
54
63
  byConversationId: Record<string, ConversationInboxItem[]>;
64
+ /** Items the stream just delivered, per conversation (the strip clears each after a beat). */
65
+ deliveredByConversationId?: Record<string, DeliveredInboxItem[]>;
55
66
  }
56
67
  export declare const addInboxItem: import("@reduxjs/toolkit").ActionCreatorWithPayload<ConversationInboxItem, "conversationInbox/addInboxItem">, confirmInboxItem: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
57
68
  conversationId: string;
@@ -68,7 +79,14 @@ export declare const addInboxItem: import("@reduxjs/toolkit").ActionCreatorWithP
68
79
  }, "conversationInbox/setInboxItemText">, removeInboxItem: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
69
80
  conversationId: string;
70
81
  injectionId: string;
71
- }, "conversationInbox/removeInboxItem">, hydrateInboxItems: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
82
+ }, "conversationInbox/removeInboxItem">, deliverInboxItem: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
83
+ conversationId: string;
84
+ injectionId: string;
85
+ deliveredAt?: string;
86
+ }, "conversationInbox/deliverInboxItem">, clearDeliveredInboxItem: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
87
+ conversationId: string;
88
+ injectionId: string;
89
+ }, "conversationInbox/clearDeliveredInboxItem">, hydrateInboxItems: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
72
90
  conversationId: string;
73
91
  items: ConversationInboxItem[];
74
92
  }, "conversationInbox/hydrateInboxItems">;
@@ -1,7 +1,8 @@
1
1
  import { createSlice } from "@reduxjs/toolkit";
2
2
  import { destroyInstance } from "../conversations/conversations.slice.js";
3
3
  const initialState = {
4
- byConversationId: {}
4
+ byConversationId: {},
5
+ deliveredByConversationId: {}
5
6
  };
6
7
  function bucket(state, conversationId) {
7
8
  if (!state.byConversationId[conversationId]) {
@@ -64,6 +65,35 @@ const conversationInboxSlice = createSlice({
64
65
  (i) => i.injectionId !== injectionId
65
66
  );
66
67
  },
68
+ /**
69
+ * The stream delivered an item (`injection_consumed`): it leaves the queue
70
+ * exactly like `removeInboxItem` (so `selectInboxItemStatus` reads null — the
71
+ * delivery ack), and a visible person line is remembered as just delivered.
72
+ */
73
+ deliverInboxItem(state, action) {
74
+ const { conversationId, injectionId } = action.payload;
75
+ const items = state.byConversationId[conversationId];
76
+ const item = items?.find((i) => i.injectionId === injectionId);
77
+ if (!items || !item) return;
78
+ state.byConversationId[conversationId] = items.filter((i) => i.injectionId !== injectionId);
79
+ if (!item.isVisibleToUser || item.source === "agent_collab") return;
80
+ const delivered = state.deliveredByConversationId ??= {};
81
+ (delivered[conversationId] ??= []).push({
82
+ injectionId,
83
+ text: item.text,
84
+ mode: item.mode,
85
+ queuedAt: item.queuedAt,
86
+ deliveredAt: action.payload.deliveredAt ?? (/* @__PURE__ */ new Date()).toISOString()
87
+ });
88
+ },
89
+ /** The "Delivered" moment is over — drop it. */
90
+ clearDeliveredInboxItem(state, action) {
91
+ const list = state.deliveredByConversationId?.[action.payload.conversationId];
92
+ if (!list) return;
93
+ state.deliveredByConversationId[action.payload.conversationId] = list.filter(
94
+ (i) => i.injectionId !== action.payload.injectionId
95
+ );
96
+ },
67
97
  /**
68
98
  * Replace the pending set from a server list (`GET …/inbox?status=pending`)
69
99
  * — reopen-mid-run rehydration. Local `sending`/`failed` items are kept
@@ -80,6 +110,7 @@ const conversationInboxSlice = createSlice({
80
110
  extraReducers: (builder) => {
81
111
  builder.addCase(destroyInstance, (state, action) => {
82
112
  delete state.byConversationId[action.payload];
113
+ delete state.deliveredByConversationId?.[action.payload];
83
114
  });
84
115
  }
85
116
  });
@@ -89,13 +120,17 @@ const {
89
120
  failInboxItem,
90
121
  setInboxItemText,
91
122
  removeInboxItem,
123
+ deliverInboxItem,
124
+ clearDeliveredInboxItem,
92
125
  hydrateInboxItems
93
126
  } = conversationInboxSlice.actions;
94
127
  var inbox_slice_default = conversationInboxSlice.reducer;
95
128
  export {
96
129
  addInboxItem,
130
+ clearDeliveredInboxItem,
97
131
  confirmInboxItem,
98
132
  inbox_slice_default as default,
133
+ deliverInboxItem,
99
134
  failInboxItem,
100
135
  hydrateInboxItems,
101
136
  removeInboxItem,
@@ -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
  }