@ai-matrx/chat 0.4.9 → 0.4.12

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 (123) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/agents/components/context-items/bodies/ContextInputBody.js +1 -1
  3. package/dist/agents/components/context-items/bodies/GenericBody.js +1 -1
  4. package/dist/agents/components/conversation-actions/conversation-verbs.js +1 -1
  5. package/dist/agents/components/inputs/smart-input/RunSkillPicker.js +1 -1
  6. package/dist/agents/components/live-run/LiveRunDisplay.js +1 -1
  7. package/dist/agents/components/live-run/LiveRunProgress.js +1 -1
  8. package/dist/agents/components/live-run/RunSetDisplay.js +1 -1
  9. package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.js +2 -2
  10. package/dist/agents/components/messages-display/assistant/InPlaceAnswerEditor.js +1 -1
  11. package/dist/agents/components/messages-display/conversation-tools/find-in-conversation.js +1 -1
  12. package/dist/agents/components/messages-display/message-options/buildTaskSeedFromMessage.js +1 -1
  13. package/dist/agents/components/messages-display/user/AgentUserMessage.js +1 -1
  14. package/dist/agents/components/messages-display/user/FirstTurnVariables.js +1 -1
  15. package/dist/agents/components/run/AgentStatusIndicator.js +1 -1
  16. package/dist/agents/components/run/RunJobWorkingLine.js +1 -1
  17. package/dist/agents/components/shared/AgentNewRunButton.js +1 -1
  18. package/dist/agents/components/shared/AgentPlanningIndicator.js +1 -1
  19. package/dist/agents/conversation-export/conversation-transfer.js +1 -1
  20. package/dist/agents/conversation-export/document-markdown.js +1 -1
  21. package/dist/agents/hooks/useConversationRoutePromotion.js +1 -1
  22. package/dist/agents/hooks/useModelControls.js +1 -1
  23. package/dist/agents/redux/agent-definition/converters.js +1 -1
  24. package/dist/agents/redux/agent-definition/slice.js +1 -1
  25. package/dist/agents/redux/conversation-list/conversation-list.thunks.js +1 -1
  26. package/dist/agents/redux/execution-system/active-requests/active-requests.selectors.js +1 -1
  27. package/dist/agents/redux/execution-system/instance-context/sync-canvas-item-context.thunk.js +1 -1
  28. package/dist/agents/redux/execution-system/thunks/process-stream.js +1 -1
  29. package/dist/agents/redux/execution-system/thunks/resolve-base-url.js +17 -0
  30. package/dist/agents/redux/execution-system/thunks/run-ai-stream.d.ts +7 -0
  31. package/dist/agents/redux/execution-system/thunks/run-ai-stream.js +13 -0
  32. package/dist/agents/redux/execution-system/thunks/run-headless-agent-json.js +14 -2
  33. package/dist/agents/redux/execution-system/utils/assemble-cx-content-blocks.js +1 -1
  34. package/dist/agents/redux/execution-system/utils/normalize-content-blocks.js +1 -1
  35. package/dist/agents/redux/execution-system/utils/output-contract-guard.js +1 -1
  36. package/dist/agents/redux/execution-system/utils/stream-block-accumulator.js +8 -8
  37. package/dist/agents/routing/confidential-routing.d.ts +52 -0
  38. package/dist/agents/routing/confidential-routing.js +38 -0
  39. package/dist/agents/utils/canvasItemContext.js +1 -1
  40. package/dist/canvas/workspace/ChatPanelTitleMenu.js +1 -1
  41. package/dist/canvas/workspace/useCanvasWorkspaceConversation.js +1 -1
  42. package/dist/cx-chat/components/SsrSidebarChats.js +1 -1
  43. package/dist/cx-chat/components/core/ChatConversationClient.js +1 -1
  44. package/dist/cx-chat/components/messages/AssistantMessage.js +1 -1
  45. package/dist/cx-chat/hooks/useInstanceBootstrap.js +1 -1
  46. package/dist/cx-conversation/AssistantMessage.js +1 -1
  47. package/dist/host/configure.js +4 -1
  48. package/dist/host/contract.d.ts +14 -1
  49. package/dist/host/index.d.ts +1 -0
  50. package/dist/host/index.js +1 -0
  51. package/dist/mandates/llm-params.d.ts +1 -1
  52. package/dist/mandates/output-contract.js +1 -1
  53. package/dist/mandates/service.d.ts +1 -1
  54. package/dist/mandates/service.js +1 -1
  55. package/dist/mandates/test-run-core.js +1 -1
  56. package/dist/public-chat/components/PublicMessageOptionsMenu.js +1 -1
  57. package/dist/surfaces/services/surface-config.service.js +1 -1
  58. package/dist/tool-call-visualization/components/ToolCallBatch.js +1 -1
  59. package/dist/tool-call-visualization/components/ToolCallVisualization.js +1 -1
  60. package/dist/tool-call-visualization/renderers/agent-call/AgentCallInline.js +1 -1
  61. package/dist/tool-call-visualization/renderers/agent-call/CollabCallCard.js +1 -1
  62. package/dist/tool-call-visualization/renderers/agent-call/collab-subtitle.js +3 -3
  63. package/dist/tool-call-visualization/renderers/skill/SkillInline.js +1 -1
  64. package/dist/tool-call-visualization/result-fields/KeyValueGrid.js +1 -1
  65. package/dist/tool-call-visualization/result-fields/ResultValue.js +1 -1
  66. package/dist/tool-call-visualization/result-fields/shape.js +1 -1
  67. package/dist/tool-call-visualization/utils/cxToolCallToLifecycleEntry.js +2 -2
  68. package/dist/tool-call-visualization/utils/human-copy.js +3 -3
  69. package/dist/ui/mandates-storage/provision-shapes.d.ts +1 -1
  70. package/dist/ui/mandates-storage/provision-shapes.js +1 -1
  71. package/dist/ui/markdown-stream/EnhancedChatMarkdown.js +1 -1
  72. package/dist/ui/treatment-shape.d.ts +1 -1
  73. package/dist/ui/treatment-shape.js +1 -1
  74. package/dist/voice-agent/relay/useVoiceRelaySession.js +1 -1
  75. package/package.json +26 -9
  76. package/dist/agents/hooks/useWarmAgent.d.ts +0 -8
  77. package/dist/agents/hooks/useWarmAgent.js +0 -25
  78. package/dist/agents/utils/artifactId.d.ts +0 -34
  79. package/dist/agents/utils/artifactId.js +0 -15
  80. package/dist/ui/ShimmerText.d.ts +0 -5
  81. package/dist/ui/ShimmerText.js +0 -21
  82. package/dist/ui/addressWithoutNavigating.d.ts +0 -68
  83. package/dist/ui/addressWithoutNavigating.js +0 -60
  84. package/dist/utils/content-ir/decision-answers-ids.d.ts +0 -3
  85. package/dist/utils/content-ir/decision-answers-ids.js +0 -6
  86. package/dist/utils/content-ir/envelope-parity.d.ts +0 -12
  87. package/dist/utils/content-ir/envelope-parity.js +0 -32
  88. package/dist/utils/content-ir/instance-title.d.ts +0 -26
  89. package/dist/utils/content-ir/instance-title.js +0 -39
  90. package/dist/utils/content-ir/kinds/kind-markdown-utils.d.ts +0 -84
  91. package/dist/utils/content-ir/kinds/kind-markdown-utils.js +0 -233
  92. package/dist/utils/content-ir/kinds/kind-value-markdown.d.ts +0 -5
  93. package/dist/utils/content-ir/kinds/kind-value-markdown.js +0 -17
  94. package/dist/utils/content-ir/legacy-quiz-envelope.d.ts +0 -3
  95. package/dist/utils/content-ir/legacy-quiz-envelope.js +0 -45
  96. package/dist/utils/content-ir/surfaces/embedded-kind-json.d.ts +0 -140
  97. package/dist/utils/content-ir/surfaces/embedded-kind-json.js +0 -527
  98. package/dist/utils/content-ir/surfaces/json-kind-signal.d.ts +0 -307
  99. package/dist/utils/content-ir/surfaces/json-kind-signal.js +0 -945
  100. package/dist/utils/content-ir/surfaces/kind-image-alt.d.ts +0 -27
  101. package/dist/utils/content-ir/surfaces/kind-image-alt.js +0 -117
  102. package/dist/utils/content-ir/surfaces/kind-one-line.d.ts +0 -67
  103. package/dist/utils/content-ir/surfaces/kind-one-line.js +0 -209
  104. package/dist/utils/content-ir/surfaces/kind-text-label.d.ts +0 -20
  105. package/dist/utils/content-ir/surfaces/kind-text-label.js +0 -50
  106. package/dist/utils/content-ir/surfaces/kind-text-to-markdown.d.ts +0 -47
  107. package/dist/utils/content-ir/surfaces/kind-text-to-markdown.js +0 -399
  108. package/dist/utils/content-ir/surfaces/markdown-escaped-kind.d.ts +0 -54
  109. package/dist/utils/content-ir/surfaces/markdown-escaped-kind.js +0 -128
  110. package/dist/utils/content-ir/surfaces/quoted-kind-lift.d.ts +0 -61
  111. package/dist/utils/content-ir/surfaces/quoted-kind-lift.js +0 -215
  112. package/dist/utils/inline-decision/decision-options.d.ts +0 -21
  113. package/dist/utils/inline-decision/decision-options.js +0 -32
  114. package/dist/utils/json.d.ts +0 -63
  115. package/dist/utils/json.js +0 -22
  116. package/dist/utils/markdown/plain-title.d.ts +0 -25
  117. package/dist/utils/markdown/plain-title.js +0 -51
  118. package/dist/utils/rendered-find.d.ts +0 -9
  119. package/dist/utils/rendered-find.js +0 -68
  120. package/dist/utils/rich-editor/core/caret-context.d.ts +0 -24
  121. package/dist/utils/rich-editor/core/caret-context.js +0 -73
  122. package/dist/utils/rich-editor/in-place/caret-handoff.d.ts +0 -3
  123. package/dist/utils/rich-editor/in-place/caret-handoff.js +0 -15
package/CHANGELOG.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # @ai-matrx/chat
2
2
 
3
+ ## Unreleased
4
+
5
+ - Chat's own message surfaces import `ui/markdown-stream/MarkdownStream` by its default export, so a host test that mocks that module (default only) replaces it everywhere, as it did when it was a slot.
6
+
7
+ ## 0.4.12 — chat keeps no copy of another package's code
8
+
9
+ - Deleted `src/utils/content-ir/**` (14 modules, 4.4k lines): every import now reads
10
+ `@ai-matrx/content-ir/surfaces`, whose versions are the stricter ones (the kind-value-markdown
11
+ registration lives on `globalThis`, so dual loaders share one registration).
12
+ - Deleted nine modules that were byte-identical to their owner and import the owner instead:
13
+ `agents/utils/artifactId`, `ui/ShimmerText`, `ui/addressWithoutNavigating`,
14
+ `utils/inline-decision/decision-options`, `utils/json`, `utils/markdown/plain-title`,
15
+ `utils/rendered-find` → `@ai-matrx/rich-content/utils/…`; `utils/rich-editor/core/caret-context`,
16
+ `utils/rich-editor/in-place/caret-handoff` → `@ai-matrx/rich-editor/…` (one caret hand-off
17
+ state instead of two).
18
+
19
+ Consumer action: replace these specifiers —
20
+ `@ai-matrx/chat/utils/content-ir/**` → `@ai-matrx/content-ir/surfaces` (re-register
21
+ `registerKindValueMarkdown` from there); `@ai-matrx/chat/agents/utils/artifactId` →
22
+ `@ai-matrx/rich-content/utils/lifted/artifactId`; `@ai-matrx/chat/ui/ShimmerText` →
23
+ `@ai-matrx/rich-content/utils/lifted/ShimmerText`; `@ai-matrx/chat/ui/addressWithoutNavigating` →
24
+ `@ai-matrx/rich-content/utils/addressWithoutNavigating`;
25
+ `@ai-matrx/chat/utils/inline-decision/decision-options` → `@ai-matrx/rich-content/utils/decision-options`;
26
+ `@ai-matrx/chat/utils/json` → `@ai-matrx/rich-content/utils/json`;
27
+ `@ai-matrx/chat/utils/markdown/plain-title` → `@ai-matrx/rich-content/utils/plain-title`;
28
+ `@ai-matrx/chat/utils/rendered-find` → `@ai-matrx/rich-content/utils/rendered-find`.
29
+
30
+ ## 0.4.11 — every AI run request reports its client latency
31
+
32
+ - `runAiStream` sends `X-Matrx-Client-Sent-At` (epoch ms of the frozen submission moment) and
33
+ `X-Matrx-Client-Prep-Ms` (submission to fetch dispatch) on every turn and resume request
34
+ (`buildLatencyHeaders`); the server persists one `platform.ai_request_latency` row per request.
35
+ - Forcing test `agents/redux/execution-system/thunks/__tests__/every-ai-run-request-reports-its-client-latency.test.ts`.
36
+
37
+ Consumer action: none.
38
+
39
+ ## 0.4.10 — a confidential conversation never reaches a cloud target
40
+
41
+ - THE CONFIDENTIAL-ROUTING RULE moves from the frozen kernel (`src/compat`) into the real chat:
42
+ `agents/routing/confidential-routing.ts` (pure: `decideRouting`, `localityOfChannel`,
43
+ `ConfidentialRoutingRefusedError`), also exported from `@ai-matrx/chat/host`.
44
+ - New host port `routing: { policy(conversationId) => "any" | "local-only" }` (default `"any"`).
45
+ - `resolveBackendForConversation` — the one place every send path resolves its target — refuses a
46
+ `local-only` conversation on every channel but `local-runtime` (the Matrx Local engine on this
47
+ computer), including the cloud fallback when a local-pc conversation finds no engine. It throws
48
+ `ConfidentialRoutingRefusedError` (message = the sentence the person reads, `remedy`, `code`
49
+ `confidential_thread_cloud_target_refused`) before any request is made.
50
+ - Forcing test `agents/routing/__tests__/confidential-conversation-never-reaches-the-cloud.test.ts`
51
+ (red on the unfixed resolver; three plant.py mutations red).
52
+
53
+ Consumer action: none for hosts without confidential conversations (the default policy is `"any"`).
54
+ A host with confidential conversations (matrx-local) passes `routing` to its chat host.
55
+
3
56
  ## 0.4.9 — the shell chat remembers each page's own chat, and its title menu lists your conversations
4
57
 
5
58
  - `shellChatHome`: outside boards and Education, the conversation is remembered PER PAGE
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { humanizeIdentifier } from "@ai-matrx/kit/text-case";
4
4
  import { AnswerValueView } from "../../../../host/ui-slots.js";
5
- import { valueCarriesKind } from "../../../../utils/content-ir/surfaces/json-kind-signal.js";
5
+ import { valueCarriesKind } from "@ai-matrx/content-ir/surfaces";
6
6
  function displayValue(value) {
7
7
  if (typeof value === "string") return value;
8
8
  if (value === null) return "null";
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { AnswerValueView } from "../../../../host/ui-slots.js";
4
- import { hasKindKeyAnySpelling, valueCarriesKind } from "../../../../utils/content-ir/surfaces/json-kind-signal.js";
4
+ import { hasKindKeyAnySpelling, valueCarriesKind } from "@ai-matrx/content-ir/surfaces";
5
5
  function carriesKind(value) {
6
6
  return valueCarriesKind(value);
7
7
  }
@@ -7,7 +7,7 @@ import {
7
7
  renameConversation
8
8
  } from "../../redux/conversation-list/conversation-row-actions.thunks.js";
9
9
  import { conversationRenameOpener } from "./rename/conversationRenameOpener.js";
10
- import { conversationTitleText } from "../../../utils/content-ir/surfaces/kind-text-label.js";
10
+ import { conversationTitleText } from "@ai-matrx/content-ir/surfaces";
11
11
  function displayConversationTitle(title) {
12
12
  if (!title) return "Untitled conversation";
13
13
  return title.trim().length > 0 ? conversationTitleText(title) ?? title : "Untitled conversation";
@@ -28,7 +28,7 @@ import {
28
28
  PicksSkeletons
29
29
  } from "./RunPicksSurface.js";
30
30
  import { groupCatalog, toolCategoryLabel } from "./run-tool-catalog.js";
31
- import { catalogProseText } from "../../../../utils/content-ir/surfaces/kind-one-line.js";
31
+ import { catalogProseText } from "@ai-matrx/content-ir/surfaces";
32
32
  function tierForSkill(skillId, config) {
33
33
  if (config.included.includes(skillId)) return "included";
34
34
  if (config.listed.includes(skillId)) return "listed";
@@ -4,7 +4,7 @@ import { useEffect, useRef } from "react";
4
4
  import { Loader2, TriangleAlert, X } from "lucide-react";
5
5
  import { cn } from "@ai-matrx/design-system";
6
6
  import { useAppSelector } from "../../../store/hooks.js";
7
- import { MarkdownStream } from "../../../ui/markdown-stream/MarkdownStream.js";
7
+ import MarkdownStream from "../../../ui/markdown-stream/MarkdownStream.js";
8
8
  import { AgentAssistantMessage } from "../messages-display/assistant/AgentAssistantMessage.js";
9
9
  import { useRetainRequestForViewer } from "../../redux/execution-system/active-requests/useRetainRequestForViewer.js";
10
10
  import {
@@ -5,7 +5,7 @@ import { cn } from "@ai-matrx/design-system";
5
5
  import {
6
6
  honestProgressSummary
7
7
  } from "@ai-matrx/kit/progress";
8
- import { isJsonObject } from "../../../utils/json.js";
8
+ import { isJsonObject } from "@ai-matrx/rich-content/utils/json";
9
9
  const STATUSES = /* @__PURE__ */ new Set([
10
10
  "waiting",
11
11
  "running",
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
4
- import { MarkdownStream } from "../../../ui/markdown-stream/MarkdownStream.js";
4
+ import MarkdownStream from "../../../ui/markdown-stream/MarkdownStream.js";
5
5
  import { cn } from "@ai-matrx/design-system";
6
6
  import { LiveRunDisplay } from "./LiveRunDisplay.js";
7
7
  import { selectRunSetEntries } from "../../redux/execution-system/run-sets/run-sets.slice.js";
@@ -7,7 +7,7 @@ import {
7
7
  useState,
8
8
  useEffect
9
9
  } from "react";
10
- import { MarkdownStream } from "../../../../ui/markdown-stream/MarkdownStream.js";
10
+ import MarkdownStream from "../../../../ui/markdown-stream/MarkdownStream.js";
11
11
  import "../rich-document/chat-message-source.js";
12
12
  import { RecordAnnotations, annotationRecordOf } from "@ai-matrx/rich-content/host/app-bindings";
13
13
  import { useAppDispatch, useAppSelector } from "../../../../store/hooks.js";
@@ -75,7 +75,7 @@ import { retryConversationTurn } from "../../../redux/execution-system/message-c
75
75
  import { commitInlineContentEdit } from "../../../redux/execution-system/message-crud/commit-inline-edit.thunk.js";
76
76
  import { InPlaceAnswerEditor } from "./InPlaceAnswerEditor.js";
77
77
  import { updateMessageRecord } from "../../../redux/execution-system/messages/messages.slice.js";
78
- import { handInPlaceCaret } from "../../../../utils/rich-editor/in-place/caret-handoff.js";
78
+ import { handInPlaceCaret } from "@ai-matrx/rich-editor/in-place/caret-handoff";
79
79
  import { toast } from "../../../../host/notify.js";
80
80
  import { useDomCapturePrint } from "../../../../conversation/hooks/useDomCapturePrint.js";
81
81
  import { MessageFilesStrip } from "../../../../host/ui-slots.js";
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
4
  import { Loader2 } from "lucide-react";
5
- import { takeInPlaceCaret } from "../../../../utils/rich-editor/in-place/caret-handoff.js";
5
+ import { takeInPlaceCaret } from "@ai-matrx/rich-editor/in-place/caret-handoff";
6
6
  import { useAppDispatch } from "../../../../store/hooks.js";
7
7
  import { updateMessageRecord } from "../../../redux/execution-system/messages/messages.slice.js";
8
8
  import {
@@ -1,4 +1,4 @@
1
- import { collectRenderedFindRanges, findRenderedTextMatches } from "../../../../utils/rendered-find.js";
1
+ import { collectRenderedFindRanges, findRenderedTextMatches } from "@ai-matrx/rich-content/utils/rendered-find";
2
2
  function findTextMatches(text, query) {
3
3
  return findRenderedTextMatches(text, query.trim());
4
4
  }
@@ -1,5 +1,5 @@
1
1
  import { durableRecordId } from "@ai-matrx/kit/ids";
2
- import { plainTitleFromMarkdown } from "../../../../utils/markdown/plain-title.js";
2
+ import { plainTitleFromMarkdown } from "@ai-matrx/rich-content/utils/plain-title";
3
3
  import { cleanMarkdown } from "@ai-matrx/content-ir/source";
4
4
  import { buildConversationMessageTitle } from "../../../utils/conversation-message-title.js";
5
5
  function buildMessagePreviewLabel(content, maxLength = 160) {
@@ -36,7 +36,7 @@ import {
36
36
  } from "../../../redux/execution-system/instance-variable-values/instance-variable-values.selectors.js";
37
37
  import { MessageAttachmentStrip } from "../MessageAttachmentStrip.js";
38
38
  import { isAttachmentMessagePart } from "../../context-items/normalize.js";
39
- import { MarkdownStream } from "../../../../ui/markdown-stream/MarkdownStream.js";
39
+ import MarkdownStream from "../../../../ui/markdown-stream/MarkdownStream.js";
40
40
  import "../rich-document/chat-message-source.js";
41
41
  import { literalUserText } from "./literal-user-text.js";
42
42
  import { buildVariableDisplayLines } from "../../../utils/variable-display-lines.js";
@@ -11,7 +11,7 @@ import { buildVariableDisplayLines } from "../../../utils/variable-display-lines
11
11
  import { EntityRef } from "../../../../host/ui-slots.js";
12
12
  import { useEntityTitles } from "../../../../host/ui-slots.js";
13
13
  import { AnswerValueView } from "../../../../host/ui-slots.js";
14
- import { isKindJsonText, valueCarriesKind } from "../../../../utils/content-ir/surfaces/json-kind-signal.js";
14
+ import { isKindJsonText, valueCarriesKind } from "@ai-matrx/content-ir/surfaces";
15
15
  function kindCarryingValue(value) {
16
16
  if (!valueCarriesKind(value)) return null;
17
17
  if (typeof value === "string" && isKindJsonText(value)) {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { ShimmerText } from "../../../ui/ShimmerText.js";
3
+ import { ShimmerText } from "@ai-matrx/rich-content/utils/lifted/ShimmerText";
4
4
  function AgentStatusIndicator({
5
5
  message,
6
6
  compact = false
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
- import { ShimmerText } from "../../../ui/ShimmerText.js";
4
+ import { ShimmerText } from "@ai-matrx/rich-content/utils/lifted/ShimmerText";
5
5
  import { useAppSelector } from "../../../store/hooks.js";
6
6
  import {
7
7
  selectRequestGenerationJob,
@@ -6,7 +6,7 @@ import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
6
6
  import { startNewConversation } from "../../redux/execution-system/thunks/create-instance.thunk.js";
7
7
  import { PlusTapButton } from "@ai-matrx/tap-target/buttons";
8
8
  import { selectFocusedConversation } from "../../redux/execution-system/conversation-focus/conversation-focus.selectors.js";
9
- import { pushAddressWithoutNavigating } from "../../../ui/addressWithoutNavigating.js";
9
+ import { pushAddressWithoutNavigating } from "@ai-matrx/rich-content/utils/addressWithoutNavigating";
10
10
  function AgentNewRunButton({ surfaceKey }) {
11
11
  const conversationId = useAppSelector(selectFocusedConversation(surfaceKey));
12
12
  const dispatch = useAppDispatch();
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { ShimmerText } from "../../../ui/ShimmerText.js";
3
+ import { ShimmerText } from "@ai-matrx/rich-content/utils/lifted/ShimmerText";
4
4
  function AgentPlanningIndicator({
5
5
  compact = false,
6
6
  label = "Planning..."
@@ -1,6 +1,6 @@
1
1
  import { toast } from "../../host/notify.js";
2
2
  import { unwrapKindEnvelopes } from "@ai-matrx/content-ir/source";
3
- import { kindTextToMarkdown } from "../../utils/content-ir/surfaces/kind-text-to-markdown.js";
3
+ import { kindTextToMarkdown } from "@ai-matrx/content-ir/surfaces";
4
4
  import { extractFlatText } from "../redux/execution-system/messages/messages.selectors.js";
5
5
  import { selectConversationTitle } from "../redux/execution-system/conversations/conversations.selectors.js";
6
6
  import { isMessagePinned } from "../message-pins/pinned-messages-store.js";
@@ -1,5 +1,5 @@
1
1
  import { unwrapKindEnvelopes } from "@ai-matrx/content-ir/source";
2
- import { kindTextToMarkdown } from "../../utils/content-ir/surfaces/kind-text-to-markdown.js";
2
+ import { kindTextToMarkdown } from "@ai-matrx/content-ir/surfaces";
3
3
  function documentMarkdown(markdown) {
4
4
  return kindTextToMarkdown(unwrapKindEnvelopes(markdown));
5
5
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { useEffect, useEffectEvent, useRef } from "react";
3
3
  import { useRouter } from "../../host/navigation.js";
4
- import { replaceAddressWithoutNavigating } from "../../ui/addressWithoutNavigating.js";
4
+ import { replaceAddressWithoutNavigating } from "@ai-matrx/rich-content/utils/addressWithoutNavigating";
5
5
  import { useAppDispatch, useAppSelector, useAppStore } from "../../store/hooks.js";
6
6
  import { waitForConversationPersisted } from "../redux/execution-system/conversations/conversation-persistence.js";
7
7
  import { selectMessageCount } from "../redux/execution-system/messages/messages.selectors.js";
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { LLM_PARAMS_KEYS } from "@ai-matrx/agents/generated/llm-enums";
3
3
  import { UI_GATE_KEYS } from "../redux/agent-settings/ui-gates.js";
4
- import { isJsonObject } from "../../utils/json.js";
4
+ import { isJsonObject } from "@ai-matrx/rich-content/utils/json";
5
5
  import { outputFormatControlKey } from "@ai-matrx/agents/models";
6
6
  function supportsTools(normalizedControls) {
7
7
  const normalizedTools = normalizedControls?.tools;
@@ -15,7 +15,7 @@ import { sanitizeAgentToolIds } from "./sanitize-tool-ids.js";
15
15
  import { stripNullish } from "../../../ui/payload.js";
16
16
  import { OrganizationContextError } from "@ai-matrx/agents/matrx";
17
17
  import { parseUiGates } from "../agent-settings/ui-gates.js";
18
- import { isJsonObject } from "../../../utils/json.js";
18
+ import { isJsonObject } from "@ai-matrx/rich-content/utils/json";
19
19
  import {
20
20
  recordAgentDataIssue,
21
21
  recoverAgentDataField
@@ -1,5 +1,5 @@
1
1
  import { createSlice } from "@reduxjs/toolkit";
2
- import { isJsonObject } from "../../../utils/json.js";
2
+ import { isJsonObject } from "@ai-matrx/rich-content/utils/json";
3
3
  import {
4
4
  shouldUpgradeFetchStatus
5
5
  } from "../../types/agent-definition.types.js";
@@ -16,7 +16,7 @@ import {
16
16
  import { CONVERSATION_LIST_PAGE_SIZE } from "./conversation-list.types.js";
17
17
  import { excludeHiddenLane } from "../conversation-history/history-filters.js";
18
18
  import { getUserId, hasBrowserSession } from "../../../host/identity.js";
19
- import { conversationTitleText } from "../../../utils/content-ir/surfaces/kind-text-label.js";
19
+ import { conversationTitleText } from "@ai-matrx/content-ir/surfaces";
20
20
  function resolveCanonicalAgentIdForConversationsFetch(input, state) {
21
21
  const versionFilter = input.versionFilter === void 0 ? null : input.versionFilter;
22
22
  let mapKey;
@@ -5,7 +5,7 @@ import {
5
5
  import { createSelector } from "@reduxjs/toolkit";
6
6
  import { isAnswerKeptWarning } from "./answer-kept-warning.js";
7
7
  import { blockMediaFileId } from "../utils/block-media-identity.js";
8
- import { DECISION_ANSWERS_BLOCK_TYPE } from "../../../../utils/content-ir/decision-answers-ids.js";
8
+ import { DECISION_ANSWERS_BLOCK_TYPE } from "@ai-matrx/content-ir/surfaces";
9
9
  import { decisionAnswersText } from "@ai-matrx/agents/presentation/decision-answers";
10
10
  import {
11
11
  readEnvelope,
@@ -8,7 +8,7 @@ import { applyContextDeltaToContent } from "../instance-working-document/context
8
8
  import { CANVAS_ITEM_UPDATED_EVENT } from "../../../utils/canvas-item-event.js";
9
9
  import { invalidateCanvasItemCache } from "../../../../host/ui-slots.js";
10
10
  import { canvasGetVersionHistory, canvasGetById } from "../../../../host/ui-slots.js";
11
- import { isMaterializedArtifactId } from "../../../utils/artifactId.js";
11
+ import { isMaterializedArtifactId } from "@ai-matrx/rich-content/utils/lifted/artifactId";
12
12
  import { recordUnavailable } from "../../../../host/diagnostics.js";
13
13
  function extractBody(content) {
14
14
  const stored = content;
@@ -187,7 +187,7 @@ import { StreamProfiler } from "../../../../ui/stream-profiler.js";
187
187
  import { makePartialKindStalenessGate } from "@ai-matrx/content-ir/wire";
188
188
  import { prepareInboundRenderBlock } from "../utils/inbound-render-block.js";
189
189
  import { progressDataRenderBlock } from "../utils/progress-data-block.js";
190
- import { DECISION_ANSWERS_BLOCK_TYPE } from "../../../../utils/content-ir/decision-answers-ids.js";
190
+ import { DECISION_ANSWERS_BLOCK_TYPE } from "@ai-matrx/content-ir/surfaces";
191
191
  import { recordContextReceipt } from "../context-rules/receipt-check.js";
192
192
  import { assembleMessageParts } from "../utils/assemble-cx-content-blocks.js";
193
193
  import { materializeMessageArtifacts } from "../../../../host/ui-slots.js";
@@ -14,6 +14,12 @@ import {
14
14
  supportsLocalAgentExecution
15
15
  } from "../../../../host/server/local-engine.js";
16
16
  import { selectOrganizationId } from "../../../../host/org.js";
17
+ import { getChatHost, isChatHostConfigured } from "../../../../host/configure.js";
18
+ import {
19
+ ConfidentialRoutingRefusedError,
20
+ decideRouting,
21
+ localityOfChannel
22
+ } from "../../../routing/confidential-routing.js";
17
23
  const EC2_SANDBOX_SERVER_URL = typeof process !== "undefined" && process.env.NEXT_PUBLIC_EC2_SANDBOX_SERVER_URL || "";
18
24
  function dedicatedEc2ServerForConversation(state, conversationId) {
19
25
  if (selectActiveServer(state) !== "production") return null;
@@ -65,6 +71,17 @@ function resolveBaseUrlForConversation(state, conversationId) {
65
71
  return resolved.endsWith("/") ? resolved.slice(0, -1) : resolved;
66
72
  }
67
73
  function resolveBackendForConversation(state, conversationId) {
74
+ const resolved = resolveBackendUnchecked(state, conversationId);
75
+ if (resolved && isChatHostConfigured()) {
76
+ const decision = decideRouting(
77
+ getChatHost().routing.policy(conversationId),
78
+ localityOfChannel(resolved.channel)
79
+ );
80
+ if (!decision.allowed) throw new ConfidentialRoutingRefusedError(conversationId, decision);
81
+ }
82
+ return resolved;
83
+ }
84
+ function resolveBackendUnchecked(state, conversationId) {
68
85
  const entry = state.instanceUIState?.byConversationId?.[conversationId] ?? null;
69
86
  const overrideUrl = entry?.serverOverrideUrl ?? null;
70
87
  const headers = {
@@ -201,6 +201,13 @@ export interface UnprocessableErrorClassification {
201
201
  }
202
202
  /** Classify HTTP 422s by server code; message text is legacy fallback only. */
203
203
  export declare function classifyUnprocessableError(errorCode: string | null, serverMessage: string): UnprocessableErrorClassification;
204
+ /**
205
+ * `X-Matrx-Client-Sent-At` (epoch ms of the frozen submission moment) and
206
+ * `X-Matrx-Client-Prep-Ms` (submission → fetch dispatch). `submitAt` is a
207
+ * `performance.now()` stamp, so the epoch time is derived from the monotonic
208
+ * elapsed time — immune to wall-clock steps between submit and dispatch.
209
+ */
210
+ export declare function buildLatencyHeaders(submitAt: number, nowPerf?: number, nowEpoch?: number): Record<string, string>;
204
211
  /**
205
212
  * Retry only after the server explicitly rejected the POST before execution
206
213
  * with `X-Matrx-Drain`. Once that proof exists, brief connection failures are
@@ -180,6 +180,13 @@ function waitForDrainRetry(ms, signal) {
180
180
  signal.addEventListener("abort", onAbort, { once: true });
181
181
  });
182
182
  }
183
+ function buildLatencyHeaders(submitAt, nowPerf = performance.now(), nowEpoch = Date.now()) {
184
+ const prepMs = Math.max(0, nowPerf - submitAt);
185
+ return {
186
+ "X-Matrx-Client-Sent-At": String(Math.round(nowEpoch - prepMs)),
187
+ "X-Matrx-Client-Prep-Ms": String(Math.round(prepMs))
188
+ };
189
+ }
183
190
  async function fetchThroughDeploymentDrain(url, init, signal) {
184
191
  const deadline = Date.now() + DEPLOYMENT_DRAIN_MAX_WAIT_MS;
185
192
  let drainObserved = false;
@@ -299,6 +306,11 @@ async function runAiStreamOnce(args) {
299
306
  );
300
307
  }
301
308
  }
309
+ if (kind !== "rejoin") {
310
+ Object.assign(fetchInit, {
311
+ headers: { ...headers, ...buildLatencyHeaders(submitAt) }
312
+ });
313
+ }
302
314
  response = kind === "rejoin" ? await fetchRejoin(
303
315
  url,
304
316
  fetchInit,
@@ -586,6 +598,7 @@ export {
586
598
  StreamCancelledError,
587
599
  StreamPhaseError,
588
600
  StreamRejoinUnavailableError,
601
+ buildLatencyHeaders,
589
602
  classifyUnprocessableError,
590
603
  fetchThroughDeploymentDrain,
591
604
  parseApiErrorBody,
@@ -151,7 +151,8 @@ async function failWarnedOutputMissingKeys(getState, opts, result) {
151
151
  }
152
152
  const who = holderName ?? (warning.holderType === "workflow" ? "The chosen workflow" : "The chosen agent");
153
153
  const keys = missing.join(", ");
154
- const sentence = `${who} ran, but its answer is missing ${keys} this job needs, so nothing was saved. It was chosen although it does not declare those keys \u2014 pick one that does.`;
154
+ const brokenJson = !(result.success && isRecord) && looksLikeBrokenJson(result.fullResponse);
155
+ const sentence = brokenJson ? `${who} ran, but its answer came back as broken JSON, so it couldn't be read and nothing was saved. Run it again.` : `${who} ran, but its answer is missing ${keys} this job needs, so nothing was saved. It was chosen although it does not declare those keys \u2014 pick one that does.`;
155
156
  captureError({
156
157
  source: "agent-json-result",
157
158
  code: "mandate_output_unusable",
@@ -162,7 +163,7 @@ async function failWarnedOutputMissingKeys(getState, opts, result) {
162
163
  ...result.requestId ? { requestId: result.requestId } : {},
163
164
  ...result.conversationId ? { conversationId: result.conversationId } : {},
164
165
  raw: {
165
- defect: "warned-holder-answer-missing-required-keys",
166
+ defect: brokenJson ? "answer-is-broken-json" : "warned-holder-answer-missing-required-keys",
166
167
  mandateKey: opts.mandateKey,
167
168
  surfaceKey: opts.surfaceKey,
168
169
  holderId,
@@ -179,6 +180,17 @@ async function failWarnedOutputMissingKeys(getState, opts, result) {
179
180
  errorDetail: `mandate_output_unusable: missing ${keys}`
180
181
  };
181
182
  }
183
+ function looksLikeBrokenJson(text) {
184
+ if (!text) return false;
185
+ const t = text.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
186
+ if (!t.startsWith("{") && !t.startsWith("[")) return false;
187
+ try {
188
+ JSON.parse(t);
189
+ return false;
190
+ } catch {
191
+ return true;
192
+ }
193
+ }
182
194
  function missingRequiredKeys(record, required) {
183
195
  const answers = typeof record.answers === "object" && record.answers !== null && !Array.isArray(record.answers) ? record.answers : null;
184
196
  return required.filter((key) => !(key in record) && !(answers && key in answers));
@@ -9,7 +9,7 @@ import { readEnvelope } from "@ai-matrx/content-ir";
9
9
  import {
10
10
  DECISION_ANSWERS_BLOCK_TYPE,
11
11
  DECISION_ANSWERS_KIND
12
- } from "../../../../utils/content-ir/decision-answers-ids.js";
12
+ } from "@ai-matrx/content-ir/surfaces";
13
13
  function isReasoningEnd(e) {
14
14
  return e.kind === "reasoning_end";
15
15
  }
@@ -4,7 +4,7 @@ import {
4
4
  fromCxAudioPart,
5
5
  fromCxVideoPart
6
6
  } from "@ai-matrx/media/files";
7
- import { DECISION_ANSWERS_BLOCK_TYPE } from "../../../../utils/content-ir/decision-answers-ids.js";
7
+ import { DECISION_ANSWERS_BLOCK_TYPE } from "@ai-matrx/content-ir/surfaces";
8
8
  import { withPerformedScript } from "../../../speech-script/types.js";
9
9
  import { seedPersistedEnvelopeCache } from "@ai-matrx/rich-content/kinds/registry/region-envelope-memo";
10
10
  function normalizeContentBlocks(rawBlocks) {
@@ -3,7 +3,7 @@ import { hasField } from "@ai-matrx/agents/field-flags";
3
3
  import { peekMandateCatalogueEntry } from "../../../../host/ui-slots.js";
4
4
  import { fetchAgentOutputSchemas } from "../../../../mandates/output-contract.js";
5
5
  import { SURFACE_WRITE_TOOL_NAME } from "../../../../surfaces/runtime/surface-writeback.js";
6
- import { isJsonObject } from "../../../../utils/json.js";
6
+ import { isJsonObject } from "@ai-matrx/rich-content/utils/json";
7
7
  import { readAgentName } from "../../../identity/agent-identity.js";
8
8
  function schemaName(schema) {
9
9
  if (!isJsonObject(schema)) return null;
@@ -1,9 +1,9 @@
1
- import { parseDecisionOptionsFromBody } from "../../../../utils/inline-decision/decision-options.js";
2
- import { QuotedKindLift } from "../../../../utils/content-ir/surfaces/quoted-kind-lift.js";
3
- import { canonicalizeCompletedLegacyQuizEnvelope } from "../../../../utils/content-ir/legacy-quiz-envelope.js";
4
- import { envelopeMatchesParsedSource } from "../../../../utils/content-ir/envelope-parity.js";
5
- import { MarkdownEscapedKindJson } from "../../../../utils/content-ir/surfaces/markdown-escaped-kind.js";
6
- import { KindImageAltUnwrap } from "../../../../utils/content-ir/surfaces/kind-image-alt.js";
1
+ import { parseDecisionOptionsFromBody } from "@ai-matrx/rich-content/utils/decision-options";
2
+ import { QuotedKindLift } from "@ai-matrx/content-ir/surfaces";
3
+ import { canonicalizeCompletedLegacyQuizEnvelope } from "@ai-matrx/content-ir/surfaces";
4
+ import { envelopeMatchesParsedSource } from "@ai-matrx/content-ir/surfaces";
5
+ import { MarkdownEscapedKindJson } from "@ai-matrx/content-ir/surfaces";
6
+ import { KindImageAltUnwrap } from "@ai-matrx/content-ir/surfaces";
7
7
  import { FENCE_META_KEY, splitFenceInfo } from "@ai-matrx/content-ir/source";
8
8
  import {
9
9
  hasUnclosedBacktickRun,
@@ -16,7 +16,7 @@ import {
16
16
  kindGrammar,
17
17
  kindObjectProseBreak,
18
18
  bareRegionBreaksIntoProse
19
- } from "../../../../utils/content-ir/surfaces/json-kind-signal.js";
19
+ } from "@ai-matrx/content-ir/surfaces";
20
20
  import {
21
21
  findBalancedXmlClose,
22
22
  initialXmlBalance
@@ -68,7 +68,7 @@ import {
68
68
  import {
69
69
  normalizeRecoveredContainerPiece,
70
70
  splitAroundEmbeddedKindJson
71
- } from "../../../../utils/content-ir/surfaces/embedded-kind-json.js";
71
+ } from "@ai-matrx/content-ir/surfaces";
72
72
  import { captureError } from "../../../../host/diagnostics.js";
73
73
  const isSpecialCodeLanguage = (language) => SPECIAL_CODE_LANGUAGES.includes(language);
74
74
  const envelopeForCompletedXmlRegion = (tag, regionText) => hostSplitterEnvelopes()?.xmlRegion(tag, regionText) ?? null;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * THE CONFIDENTIAL-ROUTING RULE (CHAT-UI-PACKAGE-DESIGN §12 risk 4 — the highest-severity item).
3
+ *
4
+ * A conversation pinned `local-only` (matrx-local's "Confidential") REFUSES every target that is
5
+ * not the person's own machine. It does not downgrade, it does not warn-and-send, and it never
6
+ * quietly picks a different target: the send fails with a named reason and a remedy, before a byte
7
+ * leaves the machine. Re-deriving this per host is how private text reaches a cloud provider.
8
+ *
9
+ * Ported from the frozen kernel (`src/compat/config.ts` `decideRouting`) into the real chat
10
+ * (lanes/agents-chat-proposal.md W0(a)). Enforced at the one place every send path resolves its
11
+ * target: `resolveBackendForConversation` (`agents/redux/execution-system/thunks/resolve-base-url.ts`).
12
+ * The policy comes from the host's `routing` port (`host/contract.ts`; default: `"any"`).
13
+ *
14
+ * PURE on purpose: no imports, no React, no Redux, no `window`. The decision is headless so it can
15
+ * move (e.g. into `@ai-matrx/agents`) without a rewrite.
16
+ */
17
+ /**
18
+ * Where a conversation's text is allowed to go.
19
+ * - `"any"` — the platform default: any target the host offers.
20
+ * - `"local-only"` — CONFIDENTIAL: only the person's own machine (the Matrx Local engine).
21
+ */
22
+ export type ChatRoutingPolicy = "any" | "local-only";
23
+ /** Where a resolved target sends text. */
24
+ export type ChatTargetLocality = "local" | "cloud";
25
+ /** The refusal a confidential conversation produces instead of a silent fallback. */
26
+ export interface ChatRoutingRefusal {
27
+ readonly allowed: false;
28
+ readonly code: "confidential_thread_cloud_target_refused";
29
+ readonly userMessage: string;
30
+ readonly remedy: string;
31
+ }
32
+ export type ChatRoutingDecision = {
33
+ readonly allowed: true;
34
+ } | ChatRoutingRefusal;
35
+ export declare const CONFIDENTIAL_REFUSAL_MESSAGE = "This conversation is marked confidential, so it cannot be sent to a cloud AI provider.";
36
+ export declare const CONFIDENTIAL_REFUSAL_REMEDY = "Run it on Matrx Local on this computer, or start a new conversation that is not marked confidential.";
37
+ /** The one decision. Anything that is not `"local"` is treated as cloud by its callers. */
38
+ export declare function decideRouting(policy: ChatRoutingPolicy, target: ChatTargetLocality): ChatRoutingDecision;
39
+ /**
40
+ * The locality of a resolved backend channel. Only `"local-runtime"` — the Matrx Local engine on
41
+ * this computer — is local. Every other channel (the global server, a sandbox override, a dedicated
42
+ * EC2 server, and any channel added later) is cloud: an unknown channel can never pass the rule.
43
+ */
44
+ export declare function localityOfChannel(channel: string): ChatTargetLocality;
45
+ /** Thrown by the target resolver when a confidential conversation would reach the cloud. */
46
+ export declare class ConfidentialRoutingRefusedError extends Error {
47
+ readonly code: "confidential_thread_cloud_target_refused";
48
+ readonly remedy: string;
49
+ readonly conversationId: string;
50
+ constructor(conversationId: string, refusal: ChatRoutingRefusal);
51
+ }
52
+ export declare function isConfidentialRoutingRefusedError(error: unknown): error is ConfidentialRoutingRefusedError;
@@ -0,0 +1,38 @@
1
+ const CONFIDENTIAL_REFUSAL_MESSAGE = "This conversation is marked confidential, so it cannot be sent to a cloud AI provider.";
2
+ const CONFIDENTIAL_REFUSAL_REMEDY = "Run it on Matrx Local on this computer, or start a new conversation that is not marked confidential.";
3
+ function decideRouting(policy, target) {
4
+ if (policy === "local-only" && target !== "local") {
5
+ return {
6
+ allowed: false,
7
+ code: "confidential_thread_cloud_target_refused",
8
+ userMessage: CONFIDENTIAL_REFUSAL_MESSAGE,
9
+ remedy: CONFIDENTIAL_REFUSAL_REMEDY
10
+ };
11
+ }
12
+ return { allowed: true };
13
+ }
14
+ function localityOfChannel(channel) {
15
+ return channel === "local-runtime" ? "local" : "cloud";
16
+ }
17
+ class ConfidentialRoutingRefusedError extends Error {
18
+ code = "confidential_thread_cloud_target_refused";
19
+ remedy;
20
+ conversationId;
21
+ constructor(conversationId, refusal) {
22
+ super(refusal.userMessage);
23
+ this.name = "ConfidentialRoutingRefusedError";
24
+ this.remedy = refusal.remedy;
25
+ this.conversationId = conversationId;
26
+ }
27
+ }
28
+ function isConfidentialRoutingRefusedError(error) {
29
+ return error instanceof ConfidentialRoutingRefusedError;
30
+ }
31
+ export {
32
+ CONFIDENTIAL_REFUSAL_MESSAGE,
33
+ CONFIDENTIAL_REFUSAL_REMEDY,
34
+ ConfidentialRoutingRefusedError,
35
+ decideRouting,
36
+ isConfidentialRoutingRefusedError,
37
+ localityOfChannel
38
+ };