@ai-matrx/chat 0.4.7 → 0.4.9
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 +17 -0
- package/dist/agents/redux/execution-system/thunks/process-stream.js +24 -11
- package/dist/agents/redux/execution-system/thunks/run-headless-agent-json.d.ts +2 -0
- package/dist/agents/redux/execution-system/thunks/run-headless-agent-json.js +25 -4
- package/dist/agents/runtime-reconnect/reconnect-server-operation.thunk.js +10 -6
- package/dist/canvas/workspace/ChatPanelTitleMenu.d.ts +3 -1
- package/dist/canvas/workspace/ChatPanelTitleMenu.js +66 -34
- package/dist/canvas/workspace/ShellChatDock.js +1 -1
- package/dist/canvas/workspace/shell-chat-route.d.ts +9 -3
- package/dist/canvas/workspace/shell-chat-route.js +6 -1
- package/dist/host/defaults/prefs.d.ts +13 -5
- package/dist/host/defaults/prefs.js +11 -2
- package/dist/host/index.d.ts +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.4.9 — the shell chat remembers each page's own chat, and its title menu lists your conversations
|
|
4
|
+
|
|
5
|
+
- `shellChatHome`: outside boards and Education, the conversation is remembered PER PAGE
|
|
6
|
+
(`shellChatPageSurfaceKey(pathname)`), not one conversation following the person site-wide. A page
|
|
7
|
+
never chatted on starts fresh; open / closed stays remembered per page family.
|
|
8
|
+
- `ChatPanelTitleMenu`: a Popover with New chat · Rename · Open in full chat over the person's
|
|
9
|
+
conversations (`ConversationHistorySidebar`, searchable, opened in place). New required prop
|
|
10
|
+
`onOpenConversation(conversationId, agentId)`; the shell dock passes `openExisting`.
|
|
11
|
+
|
|
12
|
+
Consumer action: none (the dock is inside the package).
|
|
13
|
+
|
|
14
|
+
## 0.4.8
|
|
15
|
+
|
|
16
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
17
|
+
`git diff npm/chat/v0.4.7..npm/chat/v0.4.8 -- apps/shared/chat`).
|
|
18
|
+
No source changes intended and no consumer action required.
|
|
19
|
+
|
|
3
20
|
## 0.4.7
|
|
4
21
|
|
|
5
22
|
- The host-source column test gets an explicit 120s budget: the column now renders the real text engine
|
|
@@ -5,7 +5,11 @@ import {
|
|
|
5
5
|
classifyUsageRefusal
|
|
6
6
|
} from "../../../../host/usage-gate.js";
|
|
7
7
|
import { parseNdjsonStream } from "../../../../host/server/stream-parser.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
isStreamTransportLost,
|
|
10
|
+
readStreamCursor,
|
|
11
|
+
StreamTransportError
|
|
12
|
+
} from "@ai-matrx/agents/matrx";
|
|
9
13
|
import { monitorStream } from "@ai-matrx/data/net";
|
|
10
14
|
import { mintClientTempId } from "@ai-matrx/kit/ids";
|
|
11
15
|
import { withPerformedScript } from "../../../speech-script/types.js";
|
|
@@ -139,15 +143,7 @@ import { getCapabilitiesForConversation } from "../../../runtime/get-model-capab
|
|
|
139
143
|
import { toast } from "../../../../host/notify.js";
|
|
140
144
|
import { isDirectiveApplyEvent } from "@ai-matrx/agents/envelope";
|
|
141
145
|
import { proposeDirective } from "../../proposed-directives/proposedDirectivesSlice.js";
|
|
142
|
-
|
|
143
|
-
if (typeof event !== "object" || event === null || !("stream_seq" in event) || typeof event.stream_seq !== "number" || !Number.isSafeInteger(event.stream_seq) || event.stream_seq <= 0) {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
return {
|
|
147
|
-
streamId: "stream_id" in event && typeof event.stream_id === "string" && event.stream_id.length > 0 ? event.stream_id : null,
|
|
148
|
-
streamSeq: event.stream_seq
|
|
149
|
-
};
|
|
150
|
-
}
|
|
146
|
+
const readTransportCursor = readStreamCursor;
|
|
151
147
|
function renderBlockToContentType(type) {
|
|
152
148
|
if (type === "image" || type === "image_output") return "image";
|
|
153
149
|
if (type === "audio_output") return "audio";
|
|
@@ -349,6 +345,7 @@ async function processStream({
|
|
|
349
345
|
let pendingTransportCursor = null;
|
|
350
346
|
const batchedDispatch = (action) => dispatch(autoBatched(action));
|
|
351
347
|
let reasoningLeakReported = false;
|
|
348
|
+
let serverStructuredData = null;
|
|
352
349
|
const runJsonExtraction = (isFinal) => {
|
|
353
350
|
if (!jsonTracker) return;
|
|
354
351
|
const req = getState().activeRequests.byRequestId[requestId];
|
|
@@ -367,10 +364,22 @@ async function processStream({
|
|
|
367
364
|
const jsonState = isFinal ? jsonTracker.finalize() : jsonTracker.getState();
|
|
368
365
|
if (isFinal || jsonState.revision !== lastJsonRevision) {
|
|
369
366
|
lastJsonRevision = jsonState.revision;
|
|
367
|
+
const clientResults = jsonState.results.map(toSnapshot);
|
|
368
|
+
const results = serverStructuredData ? [
|
|
369
|
+
toSnapshot({
|
|
370
|
+
value: serverStructuredData,
|
|
371
|
+
type: Array.isArray(serverStructuredData) ? "array" : "object",
|
|
372
|
+
source: "whole-string",
|
|
373
|
+
isComplete: true,
|
|
374
|
+
repairApplied: false,
|
|
375
|
+
warnings: ["Server structured_output payload"]
|
|
376
|
+
}),
|
|
377
|
+
...clientResults
|
|
378
|
+
] : clientResults;
|
|
370
379
|
batchedDispatch(
|
|
371
380
|
updateExtractedJson({
|
|
372
381
|
requestId,
|
|
373
|
-
results
|
|
382
|
+
results,
|
|
374
383
|
revision: jsonState.revision,
|
|
375
384
|
isComplete: isFinal
|
|
376
385
|
})
|
|
@@ -1920,6 +1929,10 @@ async function processStream({
|
|
|
1920
1929
|
);
|
|
1921
1930
|
} else if (isStructuredOutputEvent(event)) {
|
|
1922
1931
|
otherEvents++;
|
|
1932
|
+
if (event.data.success && event.data.data != null && typeof event.data.data === "object") {
|
|
1933
|
+
serverStructuredData = event.data.data;
|
|
1934
|
+
runJsonExtraction(false);
|
|
1935
|
+
}
|
|
1923
1936
|
dispatch(
|
|
1924
1937
|
appendTimeline({
|
|
1925
1938
|
requestId,
|
|
@@ -276,6 +276,8 @@ export interface HeadlessAgentJsonResult {
|
|
|
276
276
|
* result passes through untouched.
|
|
277
277
|
*/
|
|
278
278
|
export declare function failWarnedOutputMissingKeys(getState: () => ChatRootState, opts: Pick<HeadlessAgentJsonOptions, "mandateKey" | "surfaceKey" | "expect">, result: HeadlessAgentJsonResult): Promise<HeadlessAgentJsonResult>;
|
|
279
|
+
/** The first JSON object in `text` that holds EVERY required key, or null. */
|
|
280
|
+
export declare function recoverByRequiredKeys(text: string | undefined, required: readonly string[]): Record<string, unknown> | null;
|
|
279
281
|
/**
|
|
280
282
|
* The plain sentence of a warned-holder failure (`failWarnedOutputMissingKeys`),
|
|
281
283
|
* or `null` for any other result. A lane that swallows failures to `null`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extractFirstJson } from "@ai-matrx/kit/json-extract";
|
|
1
|
+
import { extractAllJson, extractFirstJson } from "@ai-matrx/kit/json-extract";
|
|
2
2
|
import { extractErrorMessage } from "@ai-matrx/data/net";
|
|
3
3
|
import { destroyInstanceIfAllowed } from "../conversations/conversations.thunks.js";
|
|
4
4
|
import { captureError } from "../../../../host/diagnostics.js";
|
|
@@ -129,13 +129,18 @@ async function failWarnedOutputMissingKeys(getState, opts, result) {
|
|
|
129
129
|
if (required.length === 0) return result;
|
|
130
130
|
let missing;
|
|
131
131
|
if (result.success && isRecord) {
|
|
132
|
-
|
|
133
|
-
const answers = typeof record.answers === "object" && record.answers !== null && !Array.isArray(record.answers) ? record.answers : null;
|
|
134
|
-
missing = required.filter((key) => !(key in record) && !(answers && key in answers));
|
|
132
|
+
missing = missingRequiredKeys(data, required);
|
|
135
133
|
} else {
|
|
136
134
|
missing = [...required];
|
|
137
135
|
}
|
|
138
136
|
if (missing.length === 0) return result;
|
|
137
|
+
const recovered = recoverByRequiredKeys(result.fullResponse, required);
|
|
138
|
+
if (recovered) {
|
|
139
|
+
console.warn(
|
|
140
|
+
`[runHeadlessAgentJson] ${opts.surfaceKey}: the first JSON read lacked ${missing.join(", ")}; a candidate in the answer holding every required key was used instead.`
|
|
141
|
+
);
|
|
142
|
+
return { ...result, success: true, data: recovered, noResult: false, error: void 0, errorDetail: void 0 };
|
|
143
|
+
}
|
|
139
144
|
const warning = resolved.outputWarnings[0];
|
|
140
145
|
const holderId = warning.holderId ?? resolved.agentId;
|
|
141
146
|
let holderName;
|
|
@@ -174,6 +179,21 @@ async function failWarnedOutputMissingKeys(getState, opts, result) {
|
|
|
174
179
|
errorDetail: `mandate_output_unusable: missing ${keys}`
|
|
175
180
|
};
|
|
176
181
|
}
|
|
182
|
+
function missingRequiredKeys(record, required) {
|
|
183
|
+
const answers = typeof record.answers === "object" && record.answers !== null && !Array.isArray(record.answers) ? record.answers : null;
|
|
184
|
+
return required.filter((key) => !(key in record) && !(answers && key in answers));
|
|
185
|
+
}
|
|
186
|
+
function recoverByRequiredKeys(text, required) {
|
|
187
|
+
if (!text || required.length === 0) return null;
|
|
188
|
+
for (const candidate of extractAllJson(text, { allowFuzzy: true })) {
|
|
189
|
+
const value = candidate.value;
|
|
190
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) continue;
|
|
191
|
+
if (missingRequiredKeys(value, required).length === 0) {
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
177
197
|
function mandateOutputUnusableSentence(result) {
|
|
178
198
|
if (result.success) return null;
|
|
179
199
|
if (!result.errorDetail?.startsWith("mandate_output_unusable")) return null;
|
|
@@ -565,5 +585,6 @@ export {
|
|
|
565
585
|
failWarnedOutputMissingKeys,
|
|
566
586
|
livePosture,
|
|
567
587
|
mandateOutputUnusableSentence,
|
|
588
|
+
recoverByRequiredKeys,
|
|
568
589
|
runHeadlessAgentJson
|
|
569
590
|
};
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { retryReconnect } from "./retry-reconnect.js";
|
|
2
2
|
import { discardRetainedTransportConsumer } from "../redux/execution-system/thunks/process-stream.js";
|
|
3
3
|
import { createAsyncThunk } from "@reduxjs/toolkit";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
pickServerJobOperation,
|
|
6
|
+
runtimeOperationRejoinPath,
|
|
7
|
+
settleRunPickup
|
|
8
|
+
} from "@ai-matrx/agents/matrx";
|
|
5
9
|
import { toast } from "../../host/notify.js";
|
|
6
10
|
import { captureError } from "../../host/diagnostics.js";
|
|
7
11
|
import { setRequestStatus } from "../redux/execution-system/active-requests/active-requests.slice.js";
|
|
@@ -77,8 +81,9 @@ const reconnectServerOperation = createAsyncThunk(
|
|
|
77
81
|
clearFollower();
|
|
78
82
|
return noFollow;
|
|
79
83
|
}
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
const picked = pickServerJobOperation(byLink?.operations);
|
|
85
|
+
op = picked && !picked.is_terminal ? picked : null;
|
|
86
|
+
settledWhileDisconnected = Boolean(picked?.is_terminal);
|
|
82
87
|
} catch (err) {
|
|
83
88
|
const superseded = ctrl.signal.aborted || !stillOwner();
|
|
84
89
|
clearFollower();
|
|
@@ -263,9 +268,8 @@ const reconnectServerOperation = createAsyncThunk(
|
|
|
263
268
|
clearFollower();
|
|
264
269
|
return noFollow;
|
|
265
270
|
}
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
);
|
|
271
|
+
const latest = pickServerJobOperation(refreshed?.operations);
|
|
272
|
+
const stillLive = latest && !latest.is_terminal ? latest : null;
|
|
269
273
|
if (stillLive) {
|
|
270
274
|
if (followers.get(conversationId) === ctrl) {
|
|
271
275
|
followers.delete(conversationId);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** The panel's chat name: its real title, else "New chat". */
|
|
2
2
|
export declare function useChatPanelTitle(conversationId: string | null): string;
|
|
3
|
-
export declare function ChatPanelTitleMenu({ conversationId, onNewChat, }: {
|
|
3
|
+
export declare function ChatPanelTitleMenu({ conversationId, onNewChat, onOpenConversation, }: {
|
|
4
4
|
conversationId: string | null;
|
|
5
5
|
onNewChat: () => void;
|
|
6
|
+
/** Open a listed conversation IN this panel. */
|
|
7
|
+
onOpenConversation: (conversationId: string, agentId?: string | null) => void;
|
|
6
8
|
}): import("react").JSX.Element;
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
3
4
|
import { ChevronDown, ExternalLink, PencilLine, Plus } from "lucide-react";
|
|
4
5
|
import { AppLink } from "../../host/ui-slots.js";
|
|
5
|
-
import {
|
|
6
|
-
DropdownMenu,
|
|
7
|
-
DropdownMenuContent,
|
|
8
|
-
DropdownMenuItem,
|
|
9
|
-
DropdownMenuSeparator,
|
|
10
|
-
DropdownMenuTrigger
|
|
11
|
-
} from "@ai-matrx/design-system";
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@ai-matrx/design-system";
|
|
12
7
|
import { useAppSelector } from "../../store/hooks.js";
|
|
13
8
|
import { selectConversationTitle } from "../../agents/redux/execution-system/conversations/conversations.selectors.js";
|
|
14
9
|
import { selectConversationListItemById } from "../../agents/redux/conversation-list/conversation-list.selectors.js";
|
|
15
10
|
import { conversationRenameOpener } from "../../agents/components/conversation-actions/rename/conversationRenameOpener.js";
|
|
16
11
|
import { conversationTitleText } from "../../utils/content-ir/surfaces/kind-text-label.js";
|
|
17
|
-
import { Button } from "@ai-matrx/design-system/controls";
|
|
12
|
+
import { Button, Tile } from "@ai-matrx/design-system/controls";
|
|
13
|
+
import { ConversationHistorySidebar } from "../../agents/components/conversation-history/ConversationHistorySidebar.js";
|
|
14
|
+
const PANEL_HISTORY_SCOPE = "chat-panel-menu";
|
|
15
|
+
const ALL_AGENTS = [];
|
|
18
16
|
function useChatRealTitle(conversationId) {
|
|
19
17
|
const conversationTitle = useAppSelector(
|
|
20
18
|
(state) => conversationId ? selectConversationTitle(conversationId)(state) : null
|
|
@@ -29,35 +27,69 @@ function useChatPanelTitle(conversationId) {
|
|
|
29
27
|
}
|
|
30
28
|
function ChatPanelTitleMenu({
|
|
31
29
|
conversationId,
|
|
32
|
-
onNewChat
|
|
30
|
+
onNewChat,
|
|
31
|
+
onOpenConversation
|
|
33
32
|
}) {
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
34
|
const realTitle = useChatRealTitle(conversationId);
|
|
35
35
|
const title = realTitle ?? "New chat";
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/* @__PURE__ */
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
36
|
+
const close = () => setOpen(false);
|
|
37
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
38
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "quiet", iconEnd: /* @__PURE__ */ jsx(ChevronDown, {}), className: "min-w-0", title, children: title }) }),
|
|
39
|
+
/* @__PURE__ */ jsxs(
|
|
40
|
+
PopoverContent,
|
|
41
|
+
{
|
|
42
|
+
align: "start",
|
|
43
|
+
sideOffset: 6,
|
|
44
|
+
className: "flex h-[min(70dvh,560px)] w-80 flex-col overflow-hidden p-1",
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col", children: [
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
Tile,
|
|
49
|
+
{
|
|
50
|
+
variant: "quiet",
|
|
51
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
52
|
+
title: "New chat",
|
|
53
|
+
onClick: () => {
|
|
54
|
+
close();
|
|
55
|
+
onNewChat();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
),
|
|
59
|
+
conversationId ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
Tile,
|
|
62
|
+
{
|
|
63
|
+
variant: "quiet",
|
|
64
|
+
icon: /* @__PURE__ */ jsx(PencilLine, {}),
|
|
65
|
+
title: "Rename",
|
|
66
|
+
onClick: () => {
|
|
67
|
+
close();
|
|
68
|
+
void conversationRenameOpener.open({ conversationId, title: realTitle });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
),
|
|
72
|
+
/* @__PURE__ */ jsx(Tile, { variant: "quiet", icon: /* @__PURE__ */ jsx(ExternalLink, {}), title: "Open in full chat", asChild: true, children: /* @__PURE__ */ jsx(AppLink, { href: `/chat/${conversationId}`, onClick: close }) })
|
|
73
|
+
] }) : null
|
|
74
|
+
] }),
|
|
75
|
+
/* @__PURE__ */ jsx("div", { className: "mx-1 my-1 h-px shrink-0 bg-border", role: "separator" }),
|
|
76
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsx(
|
|
77
|
+
ConversationHistorySidebar,
|
|
78
|
+
{
|
|
79
|
+
variant: "dense",
|
|
80
|
+
scopeId: PANEL_HISTORY_SCOPE,
|
|
81
|
+
agentIds: ALL_AGENTS,
|
|
82
|
+
activeConversationId: conversationId,
|
|
83
|
+
openInPlace: true,
|
|
84
|
+
onOpenConversation: (conv) => {
|
|
85
|
+
close();
|
|
86
|
+
onOpenConversation(conv.conversationId, conv.agentId ?? null);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
) })
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
)
|
|
61
93
|
] });
|
|
62
94
|
}
|
|
63
95
|
export {
|
|
@@ -260,7 +260,7 @@ function ShellChatDock({ initialChat, initialWidth, initialMode = null, signedIn
|
|
|
260
260
|
className: "bg-card",
|
|
261
261
|
children: [
|
|
262
262
|
/* @__PURE__ */ jsxs("div", { className: "shell-chat-dock-header flex h-11 shrink-0 items-center gap-1 pr-2", children: [
|
|
263
|
-
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(ChatPanelTitleMenu, { conversationId, onNewChat: chat.startNew }) }),
|
|
263
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(ChatPanelTitleMenu, { conversationId, onNewChat: chat.startNew, onOpenConversation: chat.openExisting }) }),
|
|
264
264
|
/* @__PURE__ */ jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsx(ComposerModeSwitch, { size: "panel", initialMode }) }),
|
|
265
265
|
/* @__PURE__ */ jsx(
|
|
266
266
|
Button,
|
|
@@ -33,8 +33,14 @@ export declare function shellChatWorkspaceId(family: string): string;
|
|
|
33
33
|
* chat, on every page).
|
|
34
34
|
*/
|
|
35
35
|
export declare function shellChatHostedElsewhere(pathname: string): boolean;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* The shell chat's conversation is remembered PER PAGE (Arman, 2026-10-07: one
|
|
38
|
+
* conversation following the person across the whole site "doesn't make sense
|
|
39
|
+
* … it's confusing"). Each page — one agent's builder, one note — comes back to
|
|
40
|
+
* ITS last chat; a page the person never chatted on starts fresh.
|
|
41
|
+
*/
|
|
37
42
|
export declare const SHELL_CHAT_SURFACE_KEY = "canvas-workspace:shell";
|
|
43
|
+
export declare function shellChatPageSurfaceKey(pathname: string): string;
|
|
38
44
|
/**
|
|
39
45
|
* Where the shell's chat lives on a page:
|
|
40
46
|
* - `layoutId` — its remembered open / closed + docked / floating
|
|
@@ -61,8 +67,8 @@ export declare const SHELL_CHAT_CLOSED_BY_DEFAULT_PAGES: readonly RegExp[];
|
|
|
61
67
|
* has its conversation, signed-in Education has one. The ids, params and
|
|
62
68
|
* defaults are exactly the ones those pages used when they drew their own chat
|
|
63
69
|
* column (ChatCanvasWorkspace, until 2026-10-05), so a person's remembered
|
|
64
|
-
* layout and conversation carry over untouched. Every other page
|
|
65
|
-
* conversation
|
|
70
|
+
* layout and conversation carry over untouched. Every other page remembers its
|
|
71
|
+
* OWN conversation (shellChatPageSurfaceKey); open / closed is remembered per family.
|
|
66
72
|
*/
|
|
67
73
|
export declare function shellChatHome(pathname: string, signedIn: boolean): ShellChatHome;
|
|
68
74
|
/**
|
|
@@ -20,6 +20,10 @@ function shellChatHostedElsewhere(pathname) {
|
|
|
20
20
|
return OWN_CHAT_PATHS.some((p) => p.test(pathname));
|
|
21
21
|
}
|
|
22
22
|
const SHELL_CHAT_SURFACE_KEY = "canvas-workspace:shell";
|
|
23
|
+
function shellChatPageSurfaceKey(pathname) {
|
|
24
|
+
const page = pathname.split("?")[0].replace(/\/+$/, "") || "/";
|
|
25
|
+
return `${SHELL_CHAT_SURFACE_KEY}:${page}`;
|
|
26
|
+
}
|
|
23
27
|
const BOARD_PAGE = /^\/board\/(?!all(?:\/|$))([^/]+)\/?$/;
|
|
24
28
|
const EDUCATION_PAGES = /^\/education(?:\/|$)/;
|
|
25
29
|
const SHELL_CHAT_CLOSED_BY_DEFAULT_PAGES = [/^\/spaces(?:\/|$)/];
|
|
@@ -34,7 +38,7 @@ function shellChatHome(pathname, signedIn) {
|
|
|
34
38
|
}
|
|
35
39
|
return {
|
|
36
40
|
layoutId: shellChatWorkspaceId(shellChatFamily(pathname)),
|
|
37
|
-
surfaceKey:
|
|
41
|
+
surfaceKey: shellChatPageSurfaceKey(pathname),
|
|
38
42
|
// `pageChat`, not `chat`: /code keeps its own `?chat=`.
|
|
39
43
|
addressParam: "pageChat",
|
|
40
44
|
defaultOpen: SHELL_CHAT_CLOSED_BY_DEFAULT_PAGES.some((p) => p.test(pathname)) ? false : null
|
|
@@ -52,5 +56,6 @@ export {
|
|
|
52
56
|
shellChatFamily,
|
|
53
57
|
shellChatHome,
|
|
54
58
|
shellChatHostedElsewhere,
|
|
59
|
+
shellChatPageSurfaceKey,
|
|
55
60
|
shellChatWorkspaceId
|
|
56
61
|
};
|
|
@@ -23,14 +23,22 @@ export declare const PREFERENCE_FIELDS: Readonly<Record<string, keyof ChatPrefer
|
|
|
23
23
|
export declare function createUnhostedKnobs(): ChatKnobsPort;
|
|
24
24
|
export declare const CHAT_PREFS_PREFIX = "ai-matrx-chat:";
|
|
25
25
|
export declare function createWebPrefs(): ChatPrefsPort;
|
|
26
|
+
/** A host-kept prefs port, plus the late arrival of the host's saved values. */
|
|
27
|
+
export interface HostKeptPrefs extends ChatPrefsPort {
|
|
28
|
+
/**
|
|
29
|
+
* The host's saved values arrived after the port was made (read over a socket or IPC). A key
|
|
30
|
+
* already written this session keeps the newer value; every other key is set and announced.
|
|
31
|
+
*/
|
|
32
|
+
load(values: Readonly<Record<string, string>>): void;
|
|
33
|
+
}
|
|
26
34
|
/**
|
|
27
35
|
* A prefs port the HOST keeps (HOST-OWNED-SOURCE-PLAN §2.2 "Drafts"): a page whose origin
|
|
28
36
|
* changes between launches (a local host on a new port, a desktop window) cannot keep drafts in
|
|
29
|
-
* web storage. The host
|
|
30
|
-
* (`persist`, `null` = removed) to write where it keeps them — a file,
|
|
31
|
-
*
|
|
37
|
+
* web storage. The host hands its saved values in (`initial`, or `load` when they arrive later)
|
|
38
|
+
* and receives every change (`persist`, `null` = removed) to write where it keeps them — a file,
|
|
39
|
+
* an IPC store. Reads are synchronous from memory.
|
|
32
40
|
*/
|
|
33
41
|
export declare function createHostKeptPrefs(options: {
|
|
34
|
-
initial
|
|
42
|
+
initial?: Readonly<Record<string, string>>;
|
|
35
43
|
persist: (key: string, value: string | null) => void;
|
|
36
|
-
}):
|
|
44
|
+
}): HostKeptPrefs;
|
|
@@ -169,12 +169,14 @@ function createWebPrefs() {
|
|
|
169
169
|
return port;
|
|
170
170
|
}
|
|
171
171
|
function createHostKeptPrefs(options) {
|
|
172
|
-
const memory = new Map(Object.entries(options.initial));
|
|
172
|
+
const memory = new Map(Object.entries(options.initial ?? {}));
|
|
173
|
+
const written = /* @__PURE__ */ new Set();
|
|
173
174
|
const listeners = /* @__PURE__ */ new Set();
|
|
174
175
|
const emit = (key) => {
|
|
175
176
|
for (const listener of listeners) listener(key);
|
|
176
177
|
};
|
|
177
178
|
const write = (key, value) => {
|
|
179
|
+
written.add(key);
|
|
178
180
|
try {
|
|
179
181
|
options.persist(key, value);
|
|
180
182
|
} catch (error) {
|
|
@@ -206,7 +208,14 @@ function createHostKeptPrefs(options) {
|
|
|
206
208
|
},
|
|
207
209
|
knob: (key, fallback) => parseKnob(memory.get(key) ?? null, fallback),
|
|
208
210
|
knobs: createUnhostedKnobs(),
|
|
209
|
-
snapshot: () => Object.fromEntries(memory)
|
|
211
|
+
snapshot: () => Object.fromEntries(memory),
|
|
212
|
+
load(values) {
|
|
213
|
+
for (const [key, value] of Object.entries(values)) {
|
|
214
|
+
if (written.has(key) || memory.get(key) === value) continue;
|
|
215
|
+
memory.set(key, value);
|
|
216
|
+
emit(key);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
210
219
|
};
|
|
211
220
|
return port;
|
|
212
221
|
}
|
package/dist/host/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { createDbIdentity, SIGNED_OUT_IDENTITY } from "./defaults/identity.js";
|
|
|
10
10
|
export { createNoPickerOrg, createStoredOrg, CHAT_ACTIVE_ORG_STORAGE_KEY } from "./defaults/org.js";
|
|
11
11
|
export { createDefaultServer, DEFAULT_CHAT_SERVER_URL, } from "./defaults/server.js";
|
|
12
12
|
export { createDomNotifier } from "./defaults/notify.js";
|
|
13
|
-
export { createWebPrefs, createHostKeptPrefs, CHAT_PREFS_PREFIX } from "./defaults/prefs.js";
|
|
13
|
+
export { createWebPrefs, createHostKeptPrefs, CHAT_PREFS_PREFIX, type HostKeptPrefs } from "./defaults/prefs.js";
|
|
14
14
|
export { createWindowNavigation } from "./defaults/navigation.js";
|
|
15
15
|
export { createUnhostedWindows } from "./defaults/windows.js";
|
|
16
16
|
export { CHAT_WINDOWS, CHAT_WINDOW_IDS, DEFAULT_WINDOW_INSTANCE_ID, isChatWindowId, type ChatWindowId, } from "./windows.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/chat",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
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; plus the kernel compatibility entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) until P28.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -226,26 +226,26 @@
|
|
|
226
226
|
"server-only": "^0.0.1",
|
|
227
227
|
"uuid": "^14.0.2",
|
|
228
228
|
"zod": "^4.6.5",
|
|
229
|
-
"@ai-matrx/
|
|
229
|
+
"@ai-matrx/agents": "latest",
|
|
230
230
|
"@ai-matrx/associations": "latest",
|
|
231
231
|
"@ai-matrx/browser-audio": "latest",
|
|
232
|
+
"@ai-matrx/alchemy": "latest",
|
|
232
233
|
"@ai-matrx/canvas": "latest",
|
|
233
|
-
"@ai-matrx/agents": "latest",
|
|
234
234
|
"@ai-matrx/content-ir": "latest",
|
|
235
|
+
"@ai-matrx/diff": "latest",
|
|
235
236
|
"@ai-matrx/content-ir-react": "latest",
|
|
236
|
-
"@ai-matrx/data": "latest",
|
|
237
237
|
"@ai-matrx/design-system": "latest",
|
|
238
|
-
"@ai-matrx/
|
|
238
|
+
"@ai-matrx/data": "latest",
|
|
239
239
|
"@ai-matrx/icons": "latest",
|
|
240
|
-
"@ai-matrx/kit": "latest",
|
|
241
240
|
"@ai-matrx/media": "latest",
|
|
242
241
|
"@ai-matrx/print": "latest",
|
|
242
|
+
"@ai-matrx/kit": "latest",
|
|
243
243
|
"@ai-matrx/realtime": "latest",
|
|
244
244
|
"@ai-matrx/records": "latest",
|
|
245
|
-
"@ai-matrx/records-ui": "latest",
|
|
246
245
|
"@ai-matrx/rich-content": "latest",
|
|
247
246
|
"@ai-matrx/rich-editor": "latest",
|
|
248
|
-
"@ai-matrx/tap-target": "latest"
|
|
247
|
+
"@ai-matrx/tap-target": "latest",
|
|
248
|
+
"@ai-matrx/records-ui": "latest"
|
|
249
249
|
},
|
|
250
250
|
"devDependencies": {
|
|
251
251
|
"@arethetypeswrong/cli": "^0.18.5",
|