@ai-matrx/chat 0.7.6 → 0.7.7

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 (151) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/agents/api/fetch-pending-calls.js +4 -3
  3. package/dist/agents/api/submit-tool-results.js +3 -6
  4. package/dist/agents/components/chat/begin-fresh-chat.js +2 -2
  5. package/dist/agents/components/inputs/smart-input/composer/ComposerSplash.js +2 -3
  6. package/dist/agents/components/inputs/smart-input/composer/useComposerMode.js +4 -2
  7. package/dist/agents/components/live-run/LiveRunDisplay.d.ts +7 -1
  8. package/dist/agents/components/live-run/LiveRunDisplay.js +3 -1
  9. package/dist/agents/components/messages-display/AgentConversationDisplay.js +1 -1
  10. package/dist/agents/components/messages-display/assistant/AgentEmptyMessageDisplay.js +4 -4
  11. package/dist/agents/components/messages-display/assistant/AssistantNoAnswer.js +1 -1
  12. package/dist/agents/components/messages-display/assistant/BreathingOrb.js +1 -1
  13. package/dist/agents/components/messages-display/assistant/InPlaceAnswerEditor.js +1 -1
  14. package/dist/agents/components/messages-display/citations/MessageSourcesRow.js +1 -1
  15. package/dist/agents/components/messages-display/conversation-tools/ConversationFindBar.js +4 -4
  16. package/dist/agents/components/messages-display/message-options/EditHistoryDialog.js +7 -7
  17. package/dist/agents/components/messages-display/user/AgentUserMessage.js +4 -4
  18. package/dist/agents/components/messages-display/user/CollabNoteMessage.js +2 -2
  19. package/dist/agents/components/messages-display/user/FirstTurnVariables.js +3 -3
  20. package/dist/agents/components/messages-display/user/PendingSendMessage.js +4 -4
  21. package/dist/agents/components/messages-display/user/RemarksTranscriptView.js +7 -7
  22. package/dist/agents/components/messages-display/user/ResourceAttachmentTile.js +9 -9
  23. package/dist/agents/components/messages-display/user/ResourceEditableToggleSamples.js +4 -4
  24. package/dist/agents/components/messages-display/user/UserActionBar.js +3 -3
  25. package/dist/agents/components/messages-display/user/UserMessageAttachmentStyleComparison.js +12 -12
  26. package/dist/agents/components/messages-display/user/UserMessageChipsDemo.js +19 -19
  27. package/dist/agents/components/messages-display/user/UserMessageHybridTileSamples.js +22 -22
  28. package/dist/agents/components/run/AgentRunnerPage.js +4 -4
  29. package/dist/agents/components/run/AssistantError.js +1 -1
  30. package/dist/agents/components/run/AssistantWarning.js +1 -1
  31. package/dist/agents/components/run/GenerationJobCard.js +5 -5
  32. package/dist/agents/components/run/IncompleteNote.js +1 -1
  33. package/dist/agents/components/run/RunJobWorkingLine.js +1 -1
  34. package/dist/agents/components/shared/AgentConversationColumn.js +1 -1
  35. package/dist/agents/components/shared/AgentHeaderMobile.js +6 -6
  36. package/dist/agents/components/shared/AgentModeController.js +1 -1
  37. package/dist/agents/components/shared/AutoInjectionSwitch.js +6 -6
  38. package/dist/agents/components/shared/DesktopFilterPanel.js +8 -8
  39. package/dist/agents/components/smart/AgentRunner.js +1 -1
  40. package/dist/agents/components/smart/CreateWithAiTabs.js +1 -1
  41. package/dist/agents/redux/agent-definition/agent-cache-bust-request.js +7 -7
  42. package/dist/agents/redux/execution-system/context-rules/receipt-check.js +2 -2
  43. package/dist/agents/redux/execution-system/inbox/inbox.thunks.d.ts +2 -2
  44. package/dist/agents/redux/execution-system/inbox/inbox.thunks.js +7 -15
  45. package/dist/agents/redux/execution-system/observational-memory/fetch-memory-cost.thunk.d.ts +1 -1
  46. package/dist/agents/redux/execution-system/observational-memory/fetch-memory-cost.thunk.js +7 -4
  47. package/dist/agents/redux/execution-system/selectors/aggregate.selectors.d.ts +1 -114
  48. package/dist/agents/redux/execution-system/selectors/aggregate.selectors.js +28 -43
  49. package/dist/agents/redux/execution-system/thunks/call-conversation-api.d.ts +44 -0
  50. package/dist/agents/redux/execution-system/thunks/call-conversation-api.js +78 -0
  51. package/dist/agents/redux/execution-system/thunks/continue-agent-json.d.ts +41 -0
  52. package/dist/agents/redux/execution-system/thunks/continue-agent-json.js +66 -0
  53. package/dist/agents/redux/execution-system/thunks/execute-instance.thunk.d.ts +20 -0
  54. package/dist/agents/redux/execution-system/thunks/execute-instance.thunk.js +9 -2
  55. package/dist/agents/redux/execution-system/thunks/launch-agent-execution.thunk.js +4 -6
  56. package/dist/agents/redux/execution-system/thunks/notify-and-play-dispatcher.d.ts +1 -5
  57. package/dist/agents/redux/execution-system/thunks/notify-and-play-dispatcher.js +2 -1
  58. package/dist/agents/redux/execution-system/thunks/resolve-base-url.js +7 -17
  59. package/dist/agents/redux/execution-system/thunks/settle-after-stop.thunk.js +2 -2
  60. package/dist/agents/redux/execution-system/thunks/smart-execute.thunk.js +3 -3
  61. package/dist/agents/redux/execution-system/utils/continuation-body.d.ts +8 -2
  62. package/dist/agents/redux/execution-system/utils/continuation-body.js +3 -3
  63. package/dist/agents/redux/execution-system/utils/required-organization.d.ts +18 -0
  64. package/dist/agents/redux/execution-system/utils/required-organization.js +14 -10
  65. package/dist/agents/services/mcp-connections.service.js +5 -8
  66. package/dist/agents/ui-first-tools/dispatcher/dispatch-ui-first-tool.thunk.js +2 -1
  67. package/dist/host/defaults/files-host.js +11 -1
  68. package/dist/host/defaults/server-api.d.ts +1 -15
  69. package/dist/host/defaults/server-api.js +0 -21
  70. package/dist/host/defaults/server.d.ts +2 -1
  71. package/dist/host/defaults/server.js +8 -6
  72. package/dist/host/request-headers.d.ts +26 -0
  73. package/dist/host/request-headers.js +15 -0
  74. package/dist/host/server/call-api.d.ts +0 -13
  75. package/dist/host/server/call-api.js +0 -2
  76. package/dist/host/server/matrx-transport.d.ts +0 -1
  77. package/dist/host/server/matrx-transport.js +0 -2
  78. package/dist/store/root-state.d.ts +4 -21
  79. package/dist/surfaces/hooks/useSurfaceConfig.js +2 -1
  80. package/dist/surfaces/runtime/surface-feedback.js +2 -2
  81. package/dist/tool-call-visualization/components/ArtifactResultBar.js +4 -4
  82. package/dist/tool-call-visualization/components/ChatResultColumn.d.ts +1 -1
  83. package/dist/tool-call-visualization/components/ToolCallBatch.js +2 -2
  84. package/dist/tool-call-visualization/components/ToolCallVisualization.js +5 -5
  85. package/dist/tool-call-visualization/components/ToolTabBodies.js +12 -12
  86. package/dist/tool-call-visualization/components/ToolUpdatesOverlay.js +4 -4
  87. package/dist/tool-call-visualization/configured-rows/ConfiguredToolRow.js +8 -8
  88. package/dist/tool-call-visualization/configured-rows/CopyToolButton.js +1 -1
  89. package/dist/tool-call-visualization/configured-rows/ToolProgressView.js +7 -7
  90. package/dist/tool-call-visualization/configured-rows/field-components.js +6 -6
  91. package/dist/tool-call-visualization/registry/GenericRenderer.js +4 -4
  92. package/dist/tool-call-visualization/renderers/_shared-entity/EmptyResultCard.js +1 -1
  93. package/dist/tool-call-visualization/renderers/_shared-entity/EntityCard.js +5 -5
  94. package/dist/tool-call-visualization/renderers/_shared-entity/PartPeekPopover.js +2 -2
  95. package/dist/tool-call-visualization/renderers/_shared-entity/ToolGlyph.js +1 -1
  96. package/dist/tool-call-visualization/renderers/_shared-entity/ToolResultCard.js +6 -6
  97. package/dist/tool-call-visualization/renderers/agent-call/AgentCallInline.js +5 -5
  98. package/dist/tool-call-visualization/renderers/agent-call/CollabCallCard.js +4 -4
  99. package/dist/tool-call-visualization/renderers/agent-call/ImageGenerationResult.js +1 -1
  100. package/dist/tool-call-visualization/renderers/ask/AskInline.js +3 -3
  101. package/dist/tool-call-visualization/renderers/ask-person/AskPersonInline.js +2 -2
  102. package/dist/tool-call-visualization/renderers/cloud-browser/CloudBrowserRunCard.js +5 -5
  103. package/dist/tool-call-visualization/renderers/ctx/CtxBatchInline.js +3 -3
  104. package/dist/tool-call-visualization/renderers/ctx/CtxGetInline.js +2 -2
  105. package/dist/tool-call-visualization/renderers/ctx/CtxItemCard.js +3 -3
  106. package/dist/tool-call-visualization/renderers/ctx/CtxPatchInline.js +6 -6
  107. package/dist/tool-call-visualization/renderers/dictionary/DictEntryList.js +12 -12
  108. package/dist/tool-call-visualization/renderers/dictionary/DictionaryOverlay.js +5 -5
  109. package/dist/tool-call-visualization/renderers/fs/FsInline.js +7 -7
  110. package/dist/tool-call-visualization/renderers/news-api/NewsInline.js +17 -17
  111. package/dist/tool-call-visualization/renderers/news-api/NewsOverlay.js +12 -12
  112. package/dist/tool-call-visualization/renderers/random-wheel/RandomWheelInline.js +34 -34
  113. package/dist/tool-call-visualization/renderers/records/RecordsInline.js +1 -1
  114. package/dist/tool-call-visualization/renderers/research/ResearchInline.js +10 -10
  115. package/dist/tool-call-visualization/renderers/research/ResearchOverlay.js +19 -19
  116. package/dist/tool-call-visualization/renderers/research/SubagentReportBlock.js +11 -11
  117. package/dist/tool-call-visualization/renderers/scrape/ScrapeInline.js +18 -18
  118. package/dist/tool-call-visualization/renderers/scrape/ScrapeOverlay.js +17 -17
  119. package/dist/tool-call-visualization/renderers/search/SearchInline.js +30 -30
  120. package/dist/tool-call-visualization/renderers/search/SearchOverlay.js +40 -40
  121. package/dist/tool-call-visualization/renderers/shell/ShellInline.js +3 -3
  122. package/dist/tool-call-visualization/renderers/skill/SkillInline.js +5 -5
  123. package/dist/tool-call-visualization/renderers/sql/DbSchemaInline.js +7 -7
  124. package/dist/tool-call-visualization/renderers/sql/SqlInline.js +4 -4
  125. package/dist/tool-call-visualization/renderers/workbook/WorkbookGrid.js +4 -4
  126. package/dist/tool-call-visualization/renderers/workbook/WorkbookInline.js +2 -2
  127. package/dist/tool-call-visualization/renderers/workbook/WorkbookOverlay.js +7 -7
  128. package/dist/tool-call-visualization/renderers/working-document/PatchDiffInline.js +3 -3
  129. package/dist/tool-call-visualization/result-fields/EmptyResult.js +1 -1
  130. package/dist/tool-call-visualization/result-fields/KeyValueGrid.js +9 -9
  131. package/dist/tool-call-visualization/result-fields/ResultFile.js +4 -4
  132. package/dist/tool-call-visualization/result-fields/ResultJson.js +1 -1
  133. package/dist/tool-call-visualization/result-fields/ResultMedia.js +2 -2
  134. package/dist/tool-call-visualization/result-fields/ResultScalar.js +2 -2
  135. package/dist/tool-call-visualization/result-fields/ResultTable.js +10 -10
  136. package/dist/tool-call-visualization/result-fields/ResultValue.js +7 -7
  137. package/dist/tool-call-visualization/result-fields/ShortId.js +3 -3
  138. package/dist/tool-call-visualization/result-fields/ToolErrorCard.js +4 -4
  139. package/dist/tool-call-visualization/result-fields/UrlChips.js +3 -3
  140. package/dist/tool-call-visualization/surface-write/SurfaceWriteDiff.js +2 -2
  141. package/dist/tool-call-visualization/surface-write/SurfaceWriteStress.js +1 -1
  142. package/dist/tool-call-visualization/window-panel/ToolCallWindowPanel.js +9 -9
  143. package/dist/ui/mandates-storage/mandateStorage.d.ts +36 -0
  144. package/dist/ui/mandates-storage/shortcutStorage.d.ts +9 -0
  145. package/dist/ui/markdown-stream/EnhancedChatMarkdown.js +22 -0
  146. package/dist/ui/markdown-stream/MarkdownStreamImpl.js +3 -3
  147. package/dist/ui/markdown-stream/StreamTextFallback.d.ts +6 -0
  148. package/dist/ui/markdown-stream/StreamTextFallback.js +23 -0
  149. package/dist/ui/markdown-stream/merge-thinking-across-hidden.d.ts +19 -0
  150. package/dist/ui/markdown-stream/merge-thinking-across-hidden.js +21 -0
  151. package/package.json +12 -7
@@ -0,0 +1,66 @@
1
+ import { removeContextEntry, setContextEntries } from "../instance-context/instance-context.slice.js";
2
+ import { setUserInputText } from "../instance-user-input/instance-user-input.slice.js";
3
+ import { executeInstance } from "./execute-instance.thunk.js";
4
+ import { loadConversation } from "./load-conversation.thunk.js";
5
+ import {
6
+ adoptHeadlessAgentJson
7
+ } from "./run-headless-agent-json.js";
8
+ function continuationTurnProblem(opts) {
9
+ const typed = typeof opts.userInput === "string" && opts.userInput.trim().length > 0;
10
+ if (typed && opts.hostTurn) return "A turn is the person's words or a host turn, never both.";
11
+ if (!typed && !opts.hostTurn) return "Nothing to send: the turn has neither the person's words nor a host turn.";
12
+ if (opts.hostTurn && (!opts.hostTurn.text.trim() || !opts.hostTurn.reason.trim())) return "A host turn needs its text and its reason.";
13
+ return null;
14
+ }
15
+ async function continueAgentJson(dispatch, getState, opts) {
16
+ const { conversationId } = opts;
17
+ const problem = continuationTurnProblem(opts);
18
+ if (problem) return { success: false, data: null, fullResponse: "", error: problem, conversationId };
19
+ try {
20
+ if (!getState().conversations.byConversationId[conversationId]) {
21
+ await dispatch(loadConversation({ conversationId })).unwrap();
22
+ }
23
+ const context = Object.entries(opts.context ?? {});
24
+ const writes = context.filter(([, value]) => value !== null && value !== void 0);
25
+ for (const [key] of context.filter(([, value]) => value === null)) dispatch(removeContextEntry({ conversationId, key }));
26
+ if (writes.length) dispatch(setContextEntries({ conversationId, entries: writes.map(([key, value]) => ({ key, value })) }));
27
+ if (opts.userInput) dispatch(setUserInputText({ conversationId, text: opts.userInput }));
28
+ let requestId = null;
29
+ const sent = await dispatch(
30
+ executeInstance({
31
+ conversationId,
32
+ initiation: opts.initiation ?? (opts.hostTurn ? "auto" : "user"),
33
+ ...opts.hostTurn ? { hostTurn: opts.hostTurn } : {},
34
+ onRequestId: (id) => {
35
+ requestId = id;
36
+ opts.onRequestId?.(id);
37
+ }
38
+ })
39
+ );
40
+ if (executeInstance.rejected.match(sent)) {
41
+ const reason = typeof sent.payload === "string" ? sent.payload : sent.error.message ?? "The turn could not be sent.";
42
+ return { success: false, data: null, fullResponse: "", error: reason, conversationId, ...requestId ? { requestId } : {} };
43
+ }
44
+ return await adoptHeadlessAgentJson(dispatch, getState, {
45
+ requestId: sent.payload.requestId,
46
+ conversationId,
47
+ surfaceKey: opts.surfaceKey,
48
+ keepInstance: opts.keepInstance ?? true,
49
+ ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
50
+ ...opts.pollIntervalMs !== void 0 ? { pollIntervalMs: opts.pollIntervalMs } : {},
51
+ ...opts.settleMs !== void 0 ? { settleMs: opts.settleMs } : {},
52
+ ...opts.expect ? { expect: opts.expect } : {},
53
+ ...opts.onResult ? { onResult: opts.onResult } : {},
54
+ ...opts.signal ? { signal: opts.signal } : {},
55
+ ...opts.failureMessages ? { failureMessages: opts.failureMessages } : {}
56
+ });
57
+ } catch (err) {
58
+ const message = err instanceof Error ? err.message : String(err);
59
+ console.error("[continueAgentJson] the turn failed before it ran", { conversationId, err });
60
+ return { success: false, data: null, fullResponse: "", error: message, conversationId };
61
+ }
62
+ }
63
+ export {
64
+ continuationTurnProblem,
65
+ continueAgentJson
66
+ };
@@ -36,6 +36,8 @@ export declare function isEmptyUserInput(userInput: unknown): boolean;
36
36
  */
37
37
  export declare function refusesEmptyTurn(args: {
38
38
  retry: boolean;
39
+ /** A host-written turn is a message even with no `user_input`. */
40
+ hostTurn?: boolean;
39
41
  hasPriorTurns: boolean;
40
42
  cacheOnly: boolean;
41
43
  isEphemeral: boolean;
@@ -123,6 +125,24 @@ interface ExecuteInstanceArgs {
123
125
  * composer: the person may already be typing the next message in it.
124
126
  */
125
127
  submission?: FrozenSubmission;
128
+ /**
129
+ * A turn the HOST writes on a persisted conversation — never something the
130
+ * person typed (THE USER-INPUT LAW). Sent as `host_turn` instead of
131
+ * `user_input`: a short activation line the model reads, plus which host
132
+ * feature wrote it; the turn's content rides `context`. Only a continuation
133
+ * (the conversation already has turns). The Applet builder's automatic fix
134
+ * round is one (lane F6b).
135
+ */
136
+ hostTurn?: HostTurnInput;
137
+ }
138
+ /** `ConversationContinueRequest.host_turn` — what a host-written turn carries. */
139
+ export interface HostTurnInput {
140
+ /** The activation line the model reads. */
141
+ text: string;
142
+ /** Which host feature wrote it (≤80 chars). */
143
+ reason: string;
144
+ /** Extra stamps for the stored message. */
145
+ metadata?: Record<string, unknown>;
126
146
  }
127
147
  interface ExecuteInstanceResult {
128
148
  requestId: string;
@@ -155,7 +155,7 @@ function isEmptyUserInput(userInput) {
155
155
  });
156
156
  }
157
157
  function refusesEmptyTurn(args) {
158
- if (args.retry || !args.hasPriorTurns) return false;
158
+ if (args.retry || args.hostTurn || !args.hasPriorTurns) return false;
159
159
  if (!shouldContinuePersistedConversation(
160
160
  args.cacheOnly,
161
161
  args.hasPriorTurns,
@@ -254,7 +254,8 @@ const executeInstance = createAsyncThunk(
254
254
  initiation,
255
255
  onRequestId,
256
256
  surfaceRefreshed = false,
257
- submission
257
+ submission,
258
+ hostTurn
258
259
  }, { getState, dispatch, rejectWithValue: reject }) => {
259
260
  const requestId = generateRequestId();
260
261
  const rejectWithValue = (value, originalErrorName) => reject(
@@ -340,6 +341,7 @@ const executeInstance = createAsyncThunk(
340
341
  }
341
342
  if (refusesEmptyTurn({
342
343
  retry,
344
+ hostTurn: Boolean(hostTurn),
343
345
  hasPriorTurns: selectMessageCount(conversationId)(state) > 0,
344
346
  cacheOnly: instance.cacheOnly,
345
347
  isEphemeral: instance.isEphemeral === true,
@@ -535,11 +537,16 @@ const executeInstance = createAsyncThunk(
535
537
  ).replace("{conversation_id}", encodeURIComponent(conversationId));
536
538
  url = `${baseUrl}${convPath}`;
537
539
  routedPayload = buildContinuationBody(payload, {
540
+ hostTurn,
538
541
  retry: Boolean(retry),
539
542
  debug: Boolean(debug),
540
543
  cacheBypass: pendingBypass
541
544
  });
542
545
  } else {
546
+ if (hostTurn) {
547
+ console.error(`[execute-instance] a host turn ("${hostTurn.reason}") was sent on conversation "${conversationId}", which has no persisted turns \u2014 nothing was sent.`);
548
+ return rejectWithValue("A host turn continues a conversation; this one has no turns yet");
549
+ }
543
550
  const start = resolveStartPath({
544
551
  agentId: instance.agentId,
545
552
  pinnedVersionId: instance.initialAgentVersionId ?? null,
@@ -26,10 +26,7 @@ import { alwaysOnSurfaceKeys } from "../../../../surfaces/utils/always-on-contex
26
26
  import {
27
27
  fetchAgentRunTier
28
28
  } from "../../agent-definition/thunks.js";
29
- import {
30
- isSignedOutVisitor,
31
- NotAuthenticatedError
32
- } from "../../../../host/identity.js";
29
+ import { isSignedOutVisitor, NotAuthenticatedError, selectAccessToken } from "../../../../host/identity.js";
33
30
  import { applyLaunchModelOverrides } from "../instance-model-overrides/launch-model-overrides.js";
34
31
  import { selectAgentRunTier, selectAgentById } from "../../agent-definition/selectors.js";
35
32
  import { getShortcutRecordFromState } from "../../agent-shortcuts/selectors.js";
@@ -84,8 +81,9 @@ import {
84
81
  resolvePerLaunchMappings
85
82
  } from "./surface-scope-mapping.js";
86
83
  import { ensureAgentCatalog } from "../../../identity/agent-identity.js";
84
+ import { selectOrganizationId } from "../../../../host/org.js";
87
85
  function hasSignedInSession(state) {
88
- return Boolean(state.userAuth?.accessToken);
86
+ return Boolean(selectAccessToken(state));
89
87
  }
90
88
  const INTERACTIVE_MODES = /* @__PURE__ */ new Set([
91
89
  "modal-full",
@@ -144,7 +142,7 @@ const launchAgentExecution = createAsyncThunk("instances/launch", async (options
144
142
  );
145
143
  }
146
144
  if (!agentId) {
147
- const selectedOrganizationId = adminLaneOrganizationId() ?? getState().appContext?.organization_id ?? null;
145
+ const selectedOrganizationId = adminLaneOrganizationId() ?? selectOrganizationId(getState());
148
146
  const questionOrganizationId = organizationId ?? selectedOrganizationId ?? await (await import("../../../../host/org.js")).ensureOrgId(null);
149
147
  const displayKey = mandateKey;
150
148
  const resolved = await resolveMandate(displayKey, { organizationId: questionOrganizationId });
@@ -34,11 +34,7 @@ export interface NotifyAndPlayEntry<S> {
34
34
  run: (args: never, ctx: NotifyAndPlayContext<S>) => Promise<unknown>;
35
35
  };
36
36
  }
37
- export declare function createNotifyAndPlayDispatcher<S extends {
38
- userAuth?: {
39
- id?: string | null;
40
- } | null;
41
- }>(options: {
37
+ export declare function createNotifyAndPlayDispatcher<S>(options: {
42
38
  /** Redux action type prefix, e.g. "scribeTools/dispatch". */
43
39
  typePrefix: string;
44
40
  /** Human label used in the unknown-tool message, e.g. "scribe". */
@@ -1,12 +1,13 @@
1
1
  import { createAsyncThunk } from "@reduxjs/toolkit";
2
2
  import { extractErrorMessage } from "@ai-matrx/data/net";
3
3
  import { postDelegatedToolOutcome } from "./post-delegated-tool-outcome.js";
4
+ import { selectUserId } from "../../../../host/identity.js";
4
5
  function createNotifyAndPlayDispatcher(options) {
5
6
  const { typePrefix, family, isToolName, getEntry } = options;
6
7
  return createAsyncThunk(
7
8
  typePrefix,
8
9
  async ({ conversationId, requestId, callId, toolName, args }, { dispatch, getState }) => {
9
- const userId = getState().userAuth?.id ?? null;
10
+ const userId = selectUserId(getState());
10
11
  const base = { conversationId, requestId, callId, toolName };
11
12
  const fail = (errorType, message, durationMs) => postDelegatedToolOutcome(dispatch, {
12
13
  ...base,
@@ -1,5 +1,5 @@
1
- import { adminLaneHeadersFor, adminLaneOrganizationId } from "../../../../host/server/admin-lane.js";
2
- import { adminSeatReplyOrganizationId } from "../utils/required-organization.js";
1
+ import { chatRequestHeaders } from "../../../../host/request-headers.js";
2
+ import { conversationRequestOrganizationId } from "../utils/required-organization.js";
3
3
  import {
4
4
  selectResolvedBaseUrl,
5
5
  selectActiveServer
@@ -14,7 +14,6 @@ import {
14
14
  getCachedLocalEngine,
15
15
  supportsLocalAgentExecution
16
16
  } from "../../../../host/server/local-engine.js";
17
- import { selectOrganizationId } from "../../../../host/org.js";
18
17
  import { getChatHost, isChatHostConfigured } from "../../../../host/configure.js";
19
18
  import {
20
19
  ConfidentialRoutingRefusedError,
@@ -86,21 +85,12 @@ function resolveBackendUnchecked(state, conversationId) {
86
85
  const entry = state.instanceUIState?.byConversationId?.[conversationId] ?? null;
87
86
  const overrideUrl = entry?.serverOverrideUrl ?? null;
88
87
  const headers = {
89
- "Content-Type": "application/json"
88
+ "Content-Type": "application/json",
89
+ ...chatRequestHeaders(
90
+ { accessToken: selectAccessToken(state), fingerprintId: selectFingerprintId(state) },
91
+ conversationRequestOrganizationId(state, conversationId)
92
+ )
90
93
  };
91
- const accessToken = selectAccessToken(state);
92
- const fingerprintId = selectFingerprintId(state);
93
- if (accessToken) {
94
- headers["Authorization"] = `Bearer ${accessToken}`;
95
- } else if (fingerprintId) {
96
- headers["X-Fingerprint-ID"] = fingerprintId;
97
- }
98
- const conversationOrganizationId = state.conversations?.byConversationId?.[conversationId]?.organizationId ?? null;
99
- const organizationId = adminSeatReplyOrganizationId(state, conversationId) ?? conversationOrganizationId ?? adminLaneOrganizationId() ?? selectOrganizationId(state);
100
- if (organizationId) {
101
- headers["X-Organization-Id"] = organizationId;
102
- Object.assign(headers, adminLaneHeadersFor(organizationId));
103
- }
104
94
  if (overrideUrl) {
105
95
  const ref = resolveAgentSandboxRef(state, conversationId);
106
96
  if (ref && ref.kind !== "local-pc") {
@@ -1,5 +1,5 @@
1
1
  import { createAsyncThunk } from "@reduxjs/toolkit";
2
- import { cancelAgentRunRequest } from "../../../../host/server/matrx-transport.js";
2
+ import { cancelConversationRun } from "./call-conversation-api.js";
3
3
  import { fetchOperationsByLink } from "../../../runtime-reconnect/api.js";
4
4
  import { resolveBackendForConversation } from "./resolve-base-url.js";
5
5
  import { loadConversation } from "./load-conversation.thunk.js";
@@ -82,7 +82,7 @@ const settleAfterStop = createAsyncThunk(
82
82
  }
83
83
  if (op && !op.is_terminal && !cancelSentFor && op.request_id) {
84
84
  cancelSentFor = op.request_id;
85
- const result = await dispatch(cancelAgentRunRequest(op.request_id));
85
+ const result = await dispatch(cancelConversationRun(conversationId, op.request_id));
86
86
  if (result.error) {
87
87
  console.warn("[settle-after-stop] late server cancel failed", {
88
88
  conversationId,
@@ -37,7 +37,7 @@ import {
37
37
  selectInFlightRequestIds,
38
38
  settleAfterStop
39
39
  } from "./settle-after-stop.thunk.js";
40
- import { cancelAgentRunRequest } from "../../../../host/server/matrx-transport.js";
40
+ import { cancelConversationRun } from "./call-conversation-api.js";
41
41
  import { toast } from "../../../../host/notify.js";
42
42
  import { selectHostConversation } from "../host-conversations/host-conversations.selectors.js";
43
43
  import { sendToHost, stopHostTurn } from "../host-conversations/host-conversation.thunks.js";
@@ -508,7 +508,7 @@ const cancelExecution = createAsyncThunk(
508
508
  serverRequestId
509
509
  );
510
510
  void dispatch(
511
- cancelAgentRunRequest(serverRequestId, "cancel", seenSeq)
511
+ cancelConversationRun(conversationId, serverRequestId, "cancel", seenSeq)
512
512
  ).then((result) => {
513
513
  if (result.error) {
514
514
  console.warn(
@@ -564,7 +564,7 @@ const interruptAndSend = createAsyncThunk(
564
564
  serverRequestId
565
565
  );
566
566
  void dispatch(
567
- cancelAgentRunRequest(serverRequestId, "interrupt", seenSeq)
567
+ cancelConversationRun(conversationId, serverRequestId, "interrupt", seenSeq)
568
568
  ).then(
569
569
  (result) => {
570
570
  if (result.error) {
@@ -56,11 +56,17 @@ export declare const CONTINUATION_FIELD_ROUTING: {
56
56
  readonly max_iterations: "not assembled by this thunk";
57
57
  readonly max_retries_per_iteration: "not assembled by this thunk";
58
58
  readonly responder_agent_id: "coding-mirror replies only; not assembled by this thunk";
59
- readonly host_turn: "host-owned turns only; not assembled by this thunk";
59
+ readonly host_turn: "set by the builder from the hostTurn option (never copied from the payload)";
60
60
  };
61
61
  export interface ContinuationBodyOptions {
62
62
  retry: boolean;
63
+ /** A host-written turn: sent as `host_turn`, and `user_input` is omitted (the person said nothing). */
64
+ hostTurn?: {
65
+ text: string;
66
+ reason: string;
67
+ metadata?: Record<string, unknown>;
68
+ };
63
69
  debug: boolean;
64
70
  cacheBypass: unknown;
65
71
  }
66
- export declare function buildContinuationBody(payload: AssembledAgentStartRequest, { retry, debug, cacheBypass }: ContinuationBodyOptions): Record<string, unknown>;
72
+ export declare function buildContinuationBody(payload: AssembledAgentStartRequest, { retry, debug, cacheBypass, hostTurn }: ContinuationBodyOptions): Record<string, unknown>;
@@ -50,12 +50,12 @@ const CONTINUATION_FIELD_ROUTING = {
50
50
  max_iterations: "not assembled by this thunk",
51
51
  max_retries_per_iteration: "not assembled by this thunk",
52
52
  responder_agent_id: "coding-mirror replies only; not assembled by this thunk",
53
- host_turn: "host-owned turns only; not assembled by this thunk"
53
+ host_turn: "set by the builder from the hostTurn option (never copied from the payload)"
54
54
  };
55
55
  const EMPTY_IS_MEANINGFUL = /* @__PURE__ */ new Set(["tools", "output_kinds"]);
56
56
  const FALSE_IS_MEANINGFUL = /* @__PURE__ */ new Set(["memory", "tools_replace", "context_withheld"]);
57
- function buildContinuationBody(payload, { retry, debug, cacheBypass }) {
58
- const body = retry ? { retry: true } : { user_input: payload.user_input };
57
+ function buildContinuationBody(payload, { retry, debug, cacheBypass, hostTurn }) {
58
+ const body = retry ? { retry: true } : hostTurn ? { host_turn: hostTurn } : { user_input: payload.user_input };
59
59
  body.stream = true;
60
60
  const source = payload;
61
61
  for (const [key, route] of Object.entries(CONTINUATION_FIELD_ROUTING)) {
@@ -80,4 +80,22 @@ export declare function launchAsksForOrganizationBeforeOpen(launch: {
80
80
  opensWindow: boolean;
81
81
  }): boolean;
82
82
  export declare function ensureExecutionOrganization(state: ChatRootState, conversationId: string): Promise<string | null>;
83
+ /**
84
+ * THE ORG RULE (Audit B family 26): which organization a request on this conversation names.
85
+ * ONE function, read by the turn (`requireExecutionOrganizationId`) and by the conversation door
86
+ * every conversation-bound call rides (`resolveBackendForConversation`), so a turn and an inbox /
87
+ * tool-result / pending-calls / memory-cost / cancel call on the same conversation can never name
88
+ * different organizations. Guard: `__tests__/one-org-rule-per-conversation.test.ts`.
89
+ *
90
+ * - Not yet persisted (`cacheOnly !== false`, or not in the store): the organization a launcher
91
+ * declared, then the admin seat's platform tenant (only in a host with a database), then the
92
+ * app-selected organization.
93
+ * - Persisted: the row owns its organization forever. The one exception is a platform admin
94
+ * replying from the admin seat to a conversation they reach only as an admin: the request names
95
+ * the platform tenant (the admin lane admits nothing else); the row's own organization is
96
+ * restored server-side and never changes.
97
+ *
98
+ * Returns null when nothing names one (the caller decides whether that refuses).
99
+ */
100
+ export declare function conversationRequestOrganizationId(state: ChatRootState, conversationId: string): string | null;
83
101
  export declare function requireExecutionOrganizationId(state: ChatRootState, conversationId: string): string;
@@ -33,29 +33,33 @@ async function ensureExecutionOrganization(state, conversationId) {
33
33
  return null;
34
34
  }
35
35
  }
36
+ function conversationRequestOrganizationId(state, conversationId) {
37
+ const instance = state.conversations?.byConversationId?.[conversationId];
38
+ if (instance && instance.cacheOnly === false) {
39
+ return adminSeatReplyOrganizationId(state, conversationId) ?? instance.organizationId ?? null;
40
+ }
41
+ return instance?.organizationId ?? // A host with no database has no admin seat to bill (G-DOOR: this door branches on the host).
42
+ (chatHostHasDatabase() ? adminLaneOrganizationId() : null) ?? selectOrganizationId(state) ?? null;
43
+ }
36
44
  function requireExecutionOrganizationId(state, conversationId) {
37
45
  const instance = state.conversations.byConversationId[conversationId];
38
46
  if (!instance) {
39
47
  throw new Error(`Conversation ${conversationId} not found.`);
40
48
  }
49
+ const organizationId = conversationRequestOrganizationId(state, conversationId);
50
+ if (organizationId) return organizationId;
41
51
  if (instance.cacheOnly !== false) {
42
- const startOrganizationId = instance.organizationId ?? // A host with no database has no admin seat to bill (G-DOOR: this door branches on the host).
43
- (chatHostHasDatabase() ? adminLaneOrganizationId() : void 0) ?? selectOrganizationId(state);
44
- if (startOrganizationId) return startOrganizationId;
45
52
  throw new Error(
46
53
  "Select an organization before sending this message. The request was not sent."
47
54
  );
48
55
  }
49
- const organizationId = adminSeatReplyOrganizationId(state, conversationId) ?? instance.organizationId;
50
- if (!organizationId) {
51
- throw new Error(
52
- "This conversation has no organization. Reload it before sending another message."
53
- );
54
- }
55
- return organizationId;
56
+ throw new Error(
57
+ "This conversation has no organization. Reload it before sending another message."
58
+ );
56
59
  }
57
60
  export {
58
61
  adminSeatReplyOrganizationId,
62
+ conversationRequestOrganizationId,
59
63
  ensureExecutionOrganization,
60
64
  executionOrganizationForRequest,
61
65
  isFingerprintGuestExecution,
@@ -4,7 +4,7 @@ import {
4
4
  readMatrxJsonResponse,
5
5
  sendMatrxRequest
6
6
  } from "@ai-matrx/agents/matrx";
7
- import { applyOrganizationContextHeader } from "../../host/server/organization-context.js";
7
+ import { chatRequestHeaders } from "../../host/request-headers.js";
8
8
  import { ensureOrganizationForRequest } from "../../host/org.js";
9
9
  import { readSessionToken } from "../../host/identity.js";
10
10
  function backendBase() {
@@ -17,13 +17,10 @@ async function authHeaders(method, explicitOrganizationId) {
17
17
  method,
18
18
  organizationId: explicitOrganizationId
19
19
  });
20
- return applyOrganizationContextHeader(
21
- {
22
- Authorization: `Bearer ${accessToken}`,
23
- "Content-Type": "application/json"
24
- },
25
- organizationId
26
- );
20
+ return {
21
+ "Content-Type": "application/json",
22
+ ...chatRequestHeaders({ accessToken, fingerprintId: null }, organizationId)
23
+ };
27
24
  }
28
25
  async function mcpFetch(path, init, explicitOrganizationId) {
29
26
  const headers = await authHeaders(init?.method ?? "GET", explicitOrganizationId);
@@ -3,11 +3,12 @@ import { extractErrorMessage } from "@ai-matrx/data/net";
3
3
  import { submitToolResult } from "../../api/submit-tool-results.js";
4
4
  import { upsertToolLifecycle } from "../../redux/execution-system/active-requests/active-requests.slice.js";
5
5
  import { getUiFirstToolEntry } from "../tools/registry.js";
6
+ import { selectUserId } from "../../../host/identity.js";
6
7
  const dispatchUiFirstTool = createAsyncThunk(
7
8
  "uiFirstTools/dispatch",
8
9
  async ({ conversationId, requestId, callId, toolName, args }, { dispatch, getState }) => {
9
10
  const state = getState();
10
- const userId = state.userAuth?.id ?? null;
11
+ const userId = selectUserId(state);
11
12
  if (!userId) {
12
13
  dispatch(
13
14
  upsertToolLifecycle({
@@ -3,6 +3,7 @@ import {
3
3
  isFilesHostConfigured
4
4
  } from "@ai-matrx/media/files/engine/host/configure";
5
5
  import { getStoreSingleton } from "../../store/store-singleton.js";
6
+ import { chatRequestHeaders } from "../request-headers.js";
6
7
  const DEFAULT_FILES_SERVICE_URL = "https://files.matrxserver.com";
7
8
  const UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
8
9
  const FILES_SERVICE_ROUTES = [
@@ -45,7 +46,16 @@ function createDefaultFilesServer(host) {
45
46
  async function buildHeaders(opts, includeContentType) {
46
47
  const requestId = opts.requestId ?? (globalThis.crypto?.randomUUID?.() ?? `${Date.now()}`);
47
48
  const headers = { ...await host().server.headers?.() ?? {}, "X-Request-Id": requestId };
48
- if (opts.organizationId) headers["X-Organization-Id"] = opts.organizationId;
49
+ if (opts.organizationId) {
50
+ const identity = host().identity;
51
+ Object.assign(
52
+ headers,
53
+ chatRequestHeaders(
54
+ { accessToken: await identity.getAccessToken(), fingerprintId: identity.current().fingerprintId },
55
+ opts.organizationId
56
+ )
57
+ );
58
+ }
49
59
  if (opts.idempotencyKey) headers["Idempotency-Key"] = opts.idempotencyKey;
50
60
  if (includeContentType) headers["Content-Type"] = "application/json";
51
61
  return { headers, requestId };
@@ -10,7 +10,7 @@
10
10
  * values here — no admin lane, no admin door, no endpoint overrides, no local
11
11
  * engine, no browser extension (each refusal says so once).
12
12
  */
13
- import { applyOrganizationContextHeader, type MatrxCallError, type MatrxCallResult, type MatrxCancelResponse, type MatrxTransport, type ProviderSessionFailure, type ProviderSessionFailureVerdict } from "@ai-matrx/agents/matrx";
13
+ import { applyOrganizationContextHeader, type MatrxCallError, type MatrxCallResult, type MatrxTransport, type ProviderSessionFailure, type ProviderSessionFailureVerdict } from "@ai-matrx/agents/matrx";
14
14
  import type { TypedStreamEvent } from "@ai-matrx/agents/generated/stream-events";
15
15
  import type { ApiPaths, ApiSchemas as Schemas, DefaultBrokeredCredential, DefaultRequestOptions, GetResult, PatchBody, PatchResult, PathWith, PostBody, PostResult } from "./typed-paths.js";
16
16
  import type { ChatIdentityPort, ChatOrgPort, ChatServerPort } from "../contract.js";
@@ -113,19 +113,6 @@ export declare function createDefaultServerApi(host: () => ServerHostView): {
113
113
  resolveScope: (_state?: unknown, overrides?: Partial<DefaultCallScope>) => DefaultCallScope;
114
114
  buildRequestBody: (body: unknown, scope: DefaultCallScope) => Record<string, unknown>;
115
115
  waitForAuthReady: (_getState?: AnyGetState) => Promise<boolean>;
116
- callConversationMemoryCost: (conversationId: string, options?: {
117
- signal?: AbortSignal;
118
- scopeOverrides?: Partial<DefaultCallScope>;
119
- }) => DefaultServerThunk<Promise<DefaultApiCallResult<{
120
- conversation_id: string;
121
- total_cost: number;
122
- total_input_tokens: number;
123
- total_output_tokens: number;
124
- event_count: number;
125
- by_event_type: {
126
- [key: string]: import("@ai-matrx/agents/generated/api-types").components["schemas"]["MemoryCostByEventType"];
127
- };
128
- }>>>;
129
116
  callConversationFork: (options: {
130
117
  conversationId: string;
131
118
  body?: unknown;
@@ -302,7 +289,6 @@ export declare function createDefaultServerApi(host: () => ServerHostView): {
302
289
  source?: string;
303
290
  expectedErrorStatuses?: readonly number[];
304
291
  }) => MatrxTransport;
305
- cancelAgentRunRequest: (requestId: string, mode?: "cancel" | "interrupt", seenSeq?: number) => DefaultServerThunk<Promise<DefaultApiCallResult<MatrxCancelResponse>>>;
306
292
  peekSelectedOrganizationId: () => string | null;
307
293
  waitForOrganizationAdmission: () => Promise<DefaultChatServerTypes["OrganizationAdmission"]>;
308
294
  adminLaneOrganizationId: () => string | null;
@@ -3,7 +3,6 @@ import {
3
3
  applyOrganizationContextHeader,
4
4
  buildMatrxRequestBody,
5
5
  buildMatrxRequestUrl,
6
- cancelAgentRun,
7
6
  createMatrxTransport as createPackageTransport,
8
7
  executeMatrxCall,
9
8
  normalizeMatrxError,
@@ -156,14 +155,6 @@ function createDefaultServerApi(host) {
156
155
  resolveScope,
157
156
  buildRequestBody,
158
157
  waitForAuthReady: async (_getState) => true,
159
- callConversationMemoryCost: (conversationId, options) => callApi({
160
- path: "/ai/conversations/{conversation_id}/memory_cost",
161
- method: "GET",
162
- pathParams: { conversation_id: conversationId },
163
- stream: false,
164
- ...options?.signal ? { signal: options.signal } : {},
165
- ...options?.scopeOverrides ? { scopeOverrides: options.scopeOverrides } : {}
166
- }),
167
158
  callConversationFork: call("POST", "/cx/conversations/{conversation_id}/fork"),
168
159
  callConversationForkAndRun: call("POST", "/ai/conversations/{conversation_id}/fork-and-run", true),
169
160
  callBatchDeleteMessages: call("POST", "/cx/conversations/{conversation_id}/messages/delete"),
@@ -184,18 +175,6 @@ function createDefaultServerApi(host) {
184
175
  ...options.source ? { source: options.source } : {},
185
176
  ...options.expectedErrorStatuses ? { expectedErrorStatuses: options.expectedErrorStatuses } : {}
186
177
  }),
187
- cancelAgentRunRequest: (requestId, mode = "cancel", seenSeq) => async () => {
188
- const base = transport();
189
- const sender = seenSeq === void 0 ? base : {
190
- fetch: (path, init) => base.fetch(`${path}${path.includes("?") ? "&" : "?"}seen_seq=${seenSeq}`, init)
191
- };
192
- try {
193
- const data = await cancelAgentRun(sender, requestId, { mode });
194
- return { data, requestId: data.request_id };
195
- } catch (error) {
196
- return { error: normalizeMatrxError(error) };
197
- }
198
- },
199
178
  // organization admission + the admin seat (none in a bare host)
200
179
  peekSelectedOrganizationId: () => host().org.active()?.id ?? null,
201
180
  waitForOrganizationAdmission: async () => host().org.active() ? "ready" : "unresolved",
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Default server port: the production AI Dream server; headers carry the
3
- * bearer token and the active organization.
3
+ * session credential and the active organization, built by THE ONE header
4
+ * builder (`../request-headers.ts`).
4
5
  */
5
6
  import type { ChatIdentityPort, ChatOrgPort, ChatServerPort } from "../contract.js";
6
7
  export declare const DEFAULT_CHAT_SERVER_URL = "https://server.app.matrxserver.com";
@@ -1,14 +1,16 @@
1
+ import { chatRequestHeaders } from "../request-headers.js";
1
2
  const DEFAULT_CHAT_SERVER_URL = "https://server.app.matrxserver.com";
2
3
  function createDefaultServer(identity, org) {
3
4
  return {
4
5
  baseUrl: () => DEFAULT_CHAT_SERVER_URL,
5
6
  async headers() {
6
- const headers = {};
7
- const token = await identity().getAccessToken();
8
- if (token) headers.Authorization = `Bearer ${token}`;
9
- const active = org().active();
10
- if (active) headers["X-Organization-Id"] = active.id;
11
- return headers;
7
+ return chatRequestHeaders(
8
+ {
9
+ accessToken: await identity().getAccessToken(),
10
+ fingerprintId: identity().current().fingerprintId
11
+ },
12
+ org().active()?.id ?? null
13
+ );
12
14
  }
13
15
  };
14
16
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * THE ONE HEADER BUILDER for every request the chat package sends to an AI Matrx server
3
+ * (Audit B family 8, "the inbox class").
4
+ *
5
+ * Who is asking (credential) and which organization the request names, as headers:
6
+ * - `Authorization: Bearer <jwt>` for a signed-in session, else `X-Fingerprint-ID` for a guest
7
+ * (the platform's one credential mapping, `credentialToHeaders` in `@ai-matrx/data`, which
8
+ * `@ai-matrx/api` carries forward unchanged);
9
+ * - `X-Organization-Id` when an organization is named;
10
+ * - the admin-lane marker when that organization is the platform tenant on the admin seat
11
+ * (`adminLaneHeadersFor`, the host's rule; none in a bare host).
12
+ *
13
+ * Every request path uses it: the conversation door (`resolveBackendForConversation`, which every
14
+ * turn and every conversation-bound call rides), the package default server port and the web
15
+ * app's server port. WHICH organization a conversation request names is a separate, single rule:
16
+ * `conversationRequestOrganizationId` (`agents/redux/execution-system/utils/required-organization.ts`).
17
+ * Guard: `host/__tests__/one-header-builder.test.ts` fails when a second builder appears.
18
+ */
19
+ export interface ChatRequestCredential {
20
+ /** The session's bearer token, or null. */
21
+ accessToken: string | null;
22
+ /** The guest fingerprint a signed-out visitor is known by, or null. */
23
+ fingerprintId: string | null;
24
+ }
25
+ /** The headers that say who is asking and in which organization. */
26
+ export declare function chatRequestHeaders(credential: ChatRequestCredential, organizationId: string | null | undefined): Record<string, string>;
@@ -0,0 +1,15 @@
1
+ import { credentialToHeaders } from "@ai-matrx/data";
2
+ import { adminLaneHeadersFor } from "./server/admin-lane.js";
3
+ function chatRequestHeaders(credential, organizationId) {
4
+ const presented = credential.accessToken ? { kind: "user", accessToken: credential.accessToken } : credential.fingerprintId ? { kind: "guest", fingerprintId: credential.fingerprintId } : null;
5
+ const headers = credentialToHeaders(presented);
6
+ if (!organizationId) return headers;
7
+ return {
8
+ ...headers,
9
+ "X-Organization-Id": organizationId,
10
+ ...adminLaneHeadersFor(organizationId)
11
+ };
12
+ }
13
+ export {
14
+ chatRequestHeaders
15
+ };