@ai-matrx/chat 0.5.16 → 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 +8 -2
- package/dist/agents/redux/execution-system/thunks/execute-instance.thunk.js +1 -1
- package/dist/agents/redux/execution-system/thunks/load-conversation.thunk.js +13 -2
- package/dist/agents/redux/execution-system/thunks/resolve-base-url.js +2 -1
- package/dist/agents/redux/execution-system/thunks/smart-execute.thunk.js +34 -1
- package/dist/agents/redux/execution-system/utils/required-organization.d.ts +10 -0
- package/dist/agents/redux/execution-system/utils/required-organization.js +6 -1
- package/dist/agents/types/instance.types.d.ts +9 -0
- package/dist/host/conversation-hooks.d.ts +17 -2
- package/dist/host/conversation-hooks.js +7 -2
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
-
## 0.5.
|
|
3
|
+
## 0.5.18
|
|
4
4
|
|
|
5
5
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
6
|
-
`git diff npm/chat/v0.5.
|
|
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
|
+
|
|
9
|
+
## 0.5.17
|
|
10
|
+
|
|
11
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
12
|
+
`git diff npm/chat/v0.5.16..npm/chat/v0.5.17 -- apps/shared/chat`).
|
|
7
13
|
No source changes intended and no consumer action required.
|
|
8
14
|
|
|
9
15
|
## 0.5.10 — message Print is the one message print
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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));
|
|
@@ -66,7 +66,11 @@ import { markResourcesSubmitted } from "../instance-resources/instance-resources
|
|
|
66
66
|
import { flushRemarkWrites, hasPendingRemarkWrites } from "../instance-resources/remarks.js";
|
|
67
67
|
import { withFreshRemarkRefs } from "../instance-resources/remarks-wire.js";
|
|
68
68
|
import { isOrganizationSelectionCancelled } from "../../../../host/org.js";
|
|
69
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
runBeforeSend,
|
|
71
|
+
startHostConversation,
|
|
72
|
+
startsConversationOnFirstSend
|
|
73
|
+
} from "../../../../host/conversation-hooks.js";
|
|
70
74
|
function hasConversationAtExecutionBoundary(state, conversationId) {
|
|
71
75
|
return state.conversations.byConversationId[conversationId] !== void 0;
|
|
72
76
|
}
|
|
@@ -154,6 +158,35 @@ function holdBehindInFlightSend(dispatch, getState, args) {
|
|
|
154
158
|
const smartExecute = createAsyncThunk(
|
|
155
159
|
"instances/smartExecute",
|
|
156
160
|
async ({ conversationId, surfaceKey, whileRunning = "queue", submission: handed }, { getState, dispatch }) => {
|
|
161
|
+
if (!handed && startsConversationOnFirstSend(conversationId)) {
|
|
162
|
+
const draft = captureSubmission(getState(), conversationId);
|
|
163
|
+
if (isEmptySubmission(draft)) return;
|
|
164
|
+
if (draft.resources.length > 0) {
|
|
165
|
+
toast.info("Attachments can't be added yet", {
|
|
166
|
+
description: "Send your first message as text \u2014 attachments work from the next one."
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
detachFromComposer(dispatch, getState, conversationId, draft);
|
|
171
|
+
let realId = null;
|
|
172
|
+
try {
|
|
173
|
+
realId = await startHostConversation(conversationId, { text: draft.text });
|
|
174
|
+
} catch (error) {
|
|
175
|
+
returnToComposer(dispatch, getState, conversationId, draft);
|
|
176
|
+
toast.error("Message not sent", {
|
|
177
|
+
description: error instanceof Error ? error.message : String(error)
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (!realId) {
|
|
182
|
+
returnToComposer(dispatch, getState, conversationId, draft);
|
|
183
|
+
toast.error("Message not sent", { description: "The conversation could not be started." });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
dispatch(setUserInputText({ conversationId: realId, text: draft.text, userValues: draft.userValues }));
|
|
187
|
+
await dispatch(smartExecute({ conversationId: realId, surfaceKey, whileRunning }));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
157
190
|
if (!handed && !hasConversationAtExecutionBoundary(getState(), conversationId)) {
|
|
158
191
|
await awaitLaunchingConversation(getState, conversationId);
|
|
159
192
|
}
|
|
@@ -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). */
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
* it. Must not throw (a throw is reported and swallowed).
|
|
16
16
|
* startConversation the host names the next conversation's id; the client
|
|
17
17
|
* never mints one.
|
|
18
|
+
* startsOnFirstSend this conversation is a DRAFT: it has no server-side
|
|
19
|
+
* conversation yet (the thing it talks about does not
|
|
20
|
+
* exist). The first send calls `startConversation` — the
|
|
21
|
+
* host creates what it must and names the REAL
|
|
22
|
+
* conversation (instance already in the store) — and the
|
|
23
|
+
* words travel to that one. Text only; the draft is
|
|
24
|
+
* never sent to a server under its own id.
|
|
18
25
|
* everyTurnThroughMandate
|
|
19
26
|
* every turn (not only turn 1) POSTs the mandate route,
|
|
20
27
|
* with the gate's variables + context — for a mandate
|
|
@@ -45,7 +52,11 @@ export interface ConversationHostHooks {
|
|
|
45
52
|
beforeSend?: (input: HostBeforeSendInput) => Promise<HostTurnExtras | HostSendRefused>;
|
|
46
53
|
held?: HostHeld;
|
|
47
54
|
onEvent?: (event: TypedStreamEvent, ctx: HostStreamTapContext) => void;
|
|
48
|
-
startConversation?: (
|
|
55
|
+
startConversation?: (input?: {
|
|
56
|
+
text: string;
|
|
57
|
+
}) => Promise<string>;
|
|
58
|
+
/** With `startConversation`: this id is a draft; the first send goes through `startConversation`. */
|
|
59
|
+
startsOnFirstSend?: boolean;
|
|
49
60
|
everyTurnThroughMandate?: boolean;
|
|
50
61
|
/** How a stored user turn reads on screen (display only; the record is unchanged). */
|
|
51
62
|
displayUserText?: (text: string) => string;
|
|
@@ -81,8 +92,12 @@ export declare function takeTurnExtras(conversationId: string): HostTurnExtras |
|
|
|
81
92
|
export declare function tapStreamEvent(conversationId: string, requestId: string, event: unknown): void;
|
|
82
93
|
/** Should this conversation's later turns also POST the mandate route? */
|
|
83
94
|
export declare function continuesThroughMandate(conversationId: string): boolean;
|
|
95
|
+
/** Is this id a draft whose first send must first create the real conversation? */
|
|
96
|
+
export declare function startsConversationOnFirstSend(conversationId: string): boolean;
|
|
84
97
|
/** Ask the conversation's host for the next conversation id; null when it has no say. */
|
|
85
|
-
export declare function startHostConversation(conversationId: string
|
|
98
|
+
export declare function startHostConversation(conversationId: string, input?: {
|
|
99
|
+
text: string;
|
|
100
|
+
}): Promise<string | null>;
|
|
86
101
|
/**
|
|
87
102
|
* Subscribe to changes of this conversation's held sentence: the host's own
|
|
88
103
|
* source, and the registry (a registration swaps which source is listened to).
|
|
@@ -58,9 +58,13 @@ function tapStreamEvent(conversationId, requestId, event) {
|
|
|
58
58
|
function continuesThroughMandate(conversationId) {
|
|
59
59
|
return hooksByConversation.get(conversationId)?.everyTurnThroughMandate === true;
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
function startsConversationOnFirstSend(conversationId) {
|
|
62
|
+
const hooks = hooksByConversation.get(conversationId);
|
|
63
|
+
return hooks?.startsOnFirstSend === true && typeof hooks.startConversation === "function";
|
|
64
|
+
}
|
|
65
|
+
async function startHostConversation(conversationId, input) {
|
|
62
66
|
const start = hooksByConversation.get(conversationId)?.startConversation;
|
|
63
|
-
return start ? start() : null;
|
|
67
|
+
return start ? start(input) : null;
|
|
64
68
|
}
|
|
65
69
|
function subscribeConversationHeld(conversationId, listener) {
|
|
66
70
|
let current;
|
|
@@ -93,6 +97,7 @@ export {
|
|
|
93
97
|
registerConversationHooks,
|
|
94
98
|
runBeforeSend,
|
|
95
99
|
startHostConversation,
|
|
100
|
+
startsConversationOnFirstSend,
|
|
96
101
|
subscribeConversationHeld,
|
|
97
102
|
subscribeConversationHooks,
|
|
98
103
|
takeTurnExtras,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/chat",
|
|
3
|
-
"version": "0.5.
|
|
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",
|
|
@@ -167,6 +167,7 @@
|
|
|
167
167
|
"uuid": "^14.0.2",
|
|
168
168
|
"zod": "^4.6.5",
|
|
169
169
|
"@ai-matrx/agents": "latest",
|
|
170
|
+
"@ai-matrx/alchemy": "latest",
|
|
170
171
|
"@ai-matrx/associations": "latest",
|
|
171
172
|
"@ai-matrx/browser-audio": "latest",
|
|
172
173
|
"@ai-matrx/canvas": "latest",
|
|
@@ -174,6 +175,7 @@
|
|
|
174
175
|
"@ai-matrx/content-ir-react": "latest",
|
|
175
176
|
"@ai-matrx/data": "latest",
|
|
176
177
|
"@ai-matrx/design-system": "latest",
|
|
178
|
+
"@ai-matrx/diff": "latest",
|
|
177
179
|
"@ai-matrx/icons": "latest",
|
|
178
180
|
"@ai-matrx/kit": "latest",
|
|
179
181
|
"@ai-matrx/media": "latest",
|
|
@@ -182,9 +184,7 @@
|
|
|
182
184
|
"@ai-matrx/records": "latest",
|
|
183
185
|
"@ai-matrx/records-ui": "latest",
|
|
184
186
|
"@ai-matrx/rich-content": "latest",
|
|
185
|
-
"@ai-matrx/rich-editor": "latest"
|
|
186
|
-
"@ai-matrx/alchemy": "latest",
|
|
187
|
-
"@ai-matrx/diff": "latest"
|
|
187
|
+
"@ai-matrx/rich-editor": "latest"
|
|
188
188
|
},
|
|
189
189
|
"devDependencies": {
|
|
190
190
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
@@ -299,5 +299,6 @@
|
|
|
299
299
|
"deps:write": "node ./scripts/finish-build.mjs --write-deps",
|
|
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": "ded4f1f1eb83b1d00da17c7fc7482f9647010157"
|
|
303
304
|
}
|