@ai-matrx/chat 0.5.17 → 0.5.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ai-matrx/chat
2
2
 
3
+ ## 0.5.18
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/chat/v0.5.17..npm/chat/v0.5.18 -- apps/shared/chat`).
7
+ No source changes intended and no consumer action required.
8
+
3
9
  ## 0.5.17
4
10
 
5
11
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -348,7 +348,7 @@ const executeInstance = createAsyncThunk(
348
348
  );
349
349
  return rejectWithValue("Nothing to send \u2014 the message is empty");
350
350
  }
351
- if (payload.organization_id && instance.organizationId !== payload.organization_id) {
351
+ if (payload.organization_id && instance.replySeat !== "admin" && instance.organizationId !== payload.organization_id) {
352
352
  dispatch(
353
353
  patchConversation({
354
354
  conversationId,
@@ -72,6 +72,8 @@ import {
72
72
  } from "./conversation-bundle.js";
73
73
  import { getClaimsUser } from "../../../../host/db.js";
74
74
  import { canActOn } from "../../../../ui/canActOn.js";
75
+ import { adminLaneOrganizationId } from "../../../../host/server/admin-lane.js";
76
+ import { selectIsAdmin } from "../../../../host/identity.js";
75
77
  import { ensureAgentCatalog } from "../../../identity/agent-identity.js";
76
78
  const NOT_READABLE_DETAIL = "it may have been removed, or your sign-in lost access to it";
77
79
  const loadConversation = createAsyncThunk(
@@ -190,11 +192,20 @@ const loadConversation = createAsyncThunk(
190
192
  }
191
193
  }
192
194
  }
193
- const viewerCanReply = authedUserId && conv.created_by !== authedUserId ? await canActOn("conversation", conversationId, "editor") : void 0;
195
+ let replySeat;
196
+ let viewerCanReply;
197
+ if (authedUserId && conv.created_by !== authedUserId) {
198
+ viewerCanReply = await canActOn("conversation", conversationId, "editor");
199
+ if (!viewerCanReply && adminLaneOrganizationId() !== null && selectIsAdmin(getState())) {
200
+ viewerCanReply = true;
201
+ replySeat = "admin";
202
+ }
203
+ }
194
204
  dispatch(
195
205
  hydrateConversation({
196
206
  conversationId,
197
- ...viewerCanReply !== void 0 ? { viewerCanReply } : {},
207
+ // replySeat is written (cleared too) whenever the viewer was asked about.
208
+ ...viewerCanReply !== void 0 ? { viewerCanReply, replySeat } : {},
198
209
  agentId: conv.initial_agent_id ?? "",
199
210
  agentType: "user",
200
211
  origin: "manual",
@@ -1,4 +1,5 @@
1
1
  import { adminLaneHeadersFor, adminLaneOrganizationId } from "../../../../host/server/admin-lane.js";
2
+ import { adminSeatReplyOrganizationId } from "../utils/required-organization.js";
2
3
  import {
3
4
  selectResolvedBaseUrl,
4
5
  selectActiveServer
@@ -95,7 +96,7 @@ function resolveBackendUnchecked(state, conversationId) {
95
96
  headers["X-Fingerprint-ID"] = fingerprintId;
96
97
  }
97
98
  const conversationOrganizationId = state.conversations?.byConversationId?.[conversationId]?.organizationId ?? null;
98
- const organizationId = conversationOrganizationId ?? adminLaneOrganizationId() ?? selectOrganizationId(state);
99
+ const organizationId = adminSeatReplyOrganizationId(state, conversationId) ?? conversationOrganizationId ?? adminLaneOrganizationId() ?? selectOrganizationId(state);
99
100
  if (organizationId) {
100
101
  headers["X-Organization-Id"] = organizationId;
101
102
  Object.assign(headers, adminLaneHeadersFor(organizationId));
@@ -11,6 +11,16 @@ import type { ChatRootState } from "../../../../store/root-state.js";
11
11
  * still names its organization exactly as before.
12
12
  */
13
13
  export declare function isFingerprintGuestExecution(state: ChatRootState): boolean;
14
+ /**
15
+ * THE ADMIN SEAT REPLY (Arman: admins keep full access inside the admin apps,
16
+ * and only there). When `loadConversation` let a platform admin reply ONLY
17
+ * because they stand on the admin seat (`replySeat: "admin"`), every request
18
+ * for that conversation names the platform tenant — the one organization the
19
+ * server's admin lane admits (re-verified in `admin.admins`, audited). Null on
20
+ * every user page (the seat is closed there), for every conversation the
21
+ * viewer reaches by ordinary access, and in a host with no database.
22
+ */
23
+ export declare function adminSeatReplyOrganizationId(state: ChatRootState, conversationId: string): string | null;
14
24
  /**
15
25
  * The organization a start request names: the conversation's / selected
16
26
  * organization for every signed-in lane (refusing exactly as
@@ -5,6 +5,10 @@ import { selectAccessToken, selectFingerprintId } from "../../../../host/identit
5
5
  function isFingerprintGuestExecution(state) {
6
6
  return !selectAccessToken(state) && Boolean(selectFingerprintId(state));
7
7
  }
8
+ function adminSeatReplyOrganizationId(state, conversationId) {
9
+ if (state.conversations?.byConversationId?.[conversationId]?.replySeat !== "admin") return null;
10
+ return chatHostHasDatabase() ? adminLaneOrganizationId() : null;
11
+ }
8
12
  function executionOrganizationForRequest(state, conversationId) {
9
13
  if (isFingerprintGuestExecution(state)) return void 0;
10
14
  return requireExecutionOrganizationId(state, conversationId);
@@ -42,7 +46,7 @@ function requireExecutionOrganizationId(state, conversationId) {
42
46
  "Select an organization before sending this message. The request was not sent."
43
47
  );
44
48
  }
45
- const organizationId = instance.organizationId;
49
+ const organizationId = adminSeatReplyOrganizationId(state, conversationId) ?? instance.organizationId;
46
50
  if (!organizationId) {
47
51
  throw new Error(
48
52
  "This conversation has no organization. Reload it before sending another message."
@@ -51,6 +55,7 @@ function requireExecutionOrganizationId(state, conversationId) {
51
55
  return organizationId;
52
56
  }
53
57
  export {
58
+ adminSeatReplyOrganizationId,
54
59
  ensureExecutionOrganization,
55
60
  executionOrganizationForRequest,
56
61
  isFingerprintGuestExecution,
@@ -168,6 +168,15 @@ export interface ExecutionInstance {
168
168
  * view-only bar (W-65). Read only through `selectViewerCanReply`.
169
169
  */
170
170
  viewerCanReply?: boolean;
171
+ /**
172
+ * `"admin"` when the viewer may reply ONLY because they are a platform admin
173
+ * on the admin seat (an /administration page — Arman's rule: admins keep full
174
+ * access inside the admin apps, and only there). Such a turn is sent in the
175
+ * platform tenant on the admin lane (`adminSeatReplyOrganizationId`), which
176
+ * the server admits and audits; it never moves this record's own
177
+ * `organizationId`. Unset everywhere else, including every user page.
178
+ */
179
+ replySeat?: "admin";
171
180
  /** Canonical DB column name for the agent that started this conversation. */
172
181
  initialAgentId?: string | null;
173
182
  /** Agent version that started this conversation (pinned for shortcuts/apps). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/chat",
3
- "version": "0.5.17",
3
+ "version": "0.5.18",
4
4
  "description": "THE one AI Matrx person-to-agent chat system: the full chat (running agents, conversations, tool calls, streaming, context, surfaces, voice) as per-module subpaths @ai-matrx/chat/<domain>/<module> over a host contract (/host, /host/react) whose only required value is a db. One chat, one renderer: the old kernel entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) are retired.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -172,19 +172,19 @@
172
172
  "@ai-matrx/browser-audio": "latest",
173
173
  "@ai-matrx/canvas": "latest",
174
174
  "@ai-matrx/content-ir": "latest",
175
- "@ai-matrx/data": "latest",
176
175
  "@ai-matrx/content-ir-react": "latest",
177
- "@ai-matrx/diff": "latest",
176
+ "@ai-matrx/data": "latest",
178
177
  "@ai-matrx/design-system": "latest",
178
+ "@ai-matrx/diff": "latest",
179
+ "@ai-matrx/icons": "latest",
179
180
  "@ai-matrx/kit": "latest",
181
+ "@ai-matrx/media": "latest",
182
+ "@ai-matrx/print": "latest",
180
183
  "@ai-matrx/realtime": "latest",
181
184
  "@ai-matrx/records": "latest",
182
- "@ai-matrx/rich-content": "latest",
183
185
  "@ai-matrx/records-ui": "latest",
184
- "@ai-matrx/rich-editor": "latest",
185
- "@ai-matrx/print": "latest",
186
- "@ai-matrx/icons": "latest",
187
- "@ai-matrx/media": "latest"
186
+ "@ai-matrx/rich-content": "latest",
187
+ "@ai-matrx/rich-editor": "latest"
188
188
  },
189
189
  "devDependencies": {
190
190
  "@arethetypeswrong/cli": "^0.18.5",
@@ -300,5 +300,5 @@
300
300
  "check:side-panel-bundle": "node ./scripts/check-side-panel-bundle.mjs",
301
301
  "check:side-effects": "node ../scripts/check-side-effects-listed.mjs . && node ../scripts/check-side-effects-listed.mjs --self-test"
302
302
  },
303
- "gitHead": "72c166ca9e3b90dc0f6648754257142aba4756b6"
303
+ "gitHead": "ded4f1f1eb83b1d00da17c7fc7482f9647010157"
304
304
  }