@ai-matrx/chat 0.4.12 → 0.4.15
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 +44 -0
- package/dist/agents/components/shared/AgentModeController.d.ts +1 -1
- package/dist/agents/components/shared/AgentModeController.js +4 -0
- package/dist/agents/redux/execution-system/active-requests/active-requests.selectors.js +1 -1
- package/dist/agents/redux/execution-system/active-requests/active-requests.slice.d.ts +2 -0
- package/dist/agents/redux/execution-system/active-requests/active-requests.slice.js +7 -0
- package/dist/agents/redux/execution-system/conversations/conversation-persistence.js +2 -4
- package/dist/agents/redux/execution-system/instance-context/instance-context.slice.js +1 -1
- package/dist/agents/redux/execution-system/thunks/dispatch-matrx-extend-tool.thunk.js +3 -1
- package/dist/agents/redux/execution-system/thunks/process-stream.js +26 -23
- package/dist/agents/redux/execution-system/thunks/run-headless-agent-json.js +1 -1
- package/dist/agents/runtime-reconnect/api.d.ts +6 -25
- package/dist/agents/runtime-reconnect/api.js +7 -13
- package/dist/agents/runtime-reconnect/reconnect-server-operation.thunk.js +167 -159
- package/dist/agents/runtime-reconnect/types.d.ts +2 -0
- package/dist/agents/services/mcp-connections.service.d.ts +12 -0
- package/dist/agents/services/mcp-connections.service.js +4 -7
- package/dist/agents/types/request.types.d.ts +6 -0
- package/dist/agents/utils/json-extraction.js +2 -2
- package/dist/cx-chat/utils/settings-diff.js +1 -21
- package/dist/host/configure.js +15 -5
- package/dist/host/contract.d.ts +43 -2
- package/dist/host/db-types.d.ts +112 -0
- package/dist/host/db.js +6 -0
- package/dist/host/defaults/diagnostics.d.ts +5 -0
- package/dist/host/defaults/diagnostics.js +1 -2
- package/dist/host/defaults/identity.d.ts +26 -0
- package/dist/host/defaults/identity.js +133 -1
- package/dist/host/identity.d.ts +19 -2
- package/dist/host/identity.js +18 -10
- package/dist/host/session-retry.js +2 -2
- package/dist/host/ui-slots.js +27 -20
- package/dist/surfaces/services/bind-agent-to-surface.service.js +2 -4
- package/dist/tool-call-visualization/renderers/agent-call/CollabCallCard.js +1 -1
- package/dist/ui/mandates-storage/mandateStorage.d.ts +60 -0
- package/dist/ui/mandates-storage/shortcutStorage.d.ts +15 -0
- package/dist/utils/deep-equal.d.ts +5 -0
- package/dist/utils/deep-equal.js +24 -0
- package/package.json +7 -6
package/dist/host/contract.d.ts
CHANGED
|
@@ -738,6 +738,34 @@ export interface ChatAppIdentity {
|
|
|
738
738
|
* `"local-only"` (confidential) conversation refuses every target but the Matrx Local engine on this
|
|
739
739
|
* computer, loudly, before a byte leaves the machine. Default: `"any"` for every conversation.
|
|
740
740
|
*/
|
|
741
|
+
/** What a device-tool run answered (the same shape the browser-extension bridge answers). */
|
|
742
|
+
export type ChatDeviceToolInvocation = {
|
|
743
|
+
handled: false;
|
|
744
|
+
reason: string;
|
|
745
|
+
} | {
|
|
746
|
+
handled: true;
|
|
747
|
+
ok: true;
|
|
748
|
+
output: Record<string, unknown>;
|
|
749
|
+
} | {
|
|
750
|
+
handled: true;
|
|
751
|
+
ok: false;
|
|
752
|
+
error: string;
|
|
753
|
+
};
|
|
754
|
+
/**
|
|
755
|
+
* Tools that run ON THIS DEVICE for a cloud agent (lanes/agents-chat-proposal.md W0(c)): the
|
|
756
|
+
* browser tools when the chat runs inside Matrx Extend, local machine tools in a desktop host. The
|
|
757
|
+
* package owns the whole loop around them — the delegated call's lifecycle, the durable result
|
|
758
|
+
* submit with retry, and the resume (`surfaceDelegatedToolCall` → `submitToolResult`); the host
|
|
759
|
+
* only executes. `handled: false` = "not a tool this device owns" (the model is told, never a
|
|
760
|
+
* wedge). Absent: delegated device tools go to the installed browser extension through the server
|
|
761
|
+
* port (`invokeMatrxExtendTool`), as on the web.
|
|
762
|
+
*/
|
|
763
|
+
export interface ChatDeviceToolsPort {
|
|
764
|
+
invoke(toolName: string, args: Record<string, unknown>, context: {
|
|
765
|
+
conversationId: string;
|
|
766
|
+
callId: string;
|
|
767
|
+
}): Promise<ChatDeviceToolInvocation>;
|
|
768
|
+
}
|
|
741
769
|
export interface ChatRoutingPort {
|
|
742
770
|
policy(conversationId: string): ChatRoutingPolicy;
|
|
743
771
|
}
|
|
@@ -767,7 +795,16 @@ export interface ChatHost {
|
|
|
767
795
|
* that is not matrx-frontend never labels its rows as the main app's.
|
|
768
796
|
*/
|
|
769
797
|
app?: ChatAppIdentity;
|
|
770
|
-
/**
|
|
798
|
+
/**
|
|
799
|
+
* The token function a token-only client was built with (supabase-js `accessToken` option, the
|
|
800
|
+
* matrx-local custodian shape): pass the SAME function here and the default identity reads who is
|
|
801
|
+
* signed in from it. A token-only `db` without it is signed out, announced once with this remedy.
|
|
802
|
+
*/
|
|
803
|
+
accessToken?: () => Promise<string | null>;
|
|
804
|
+
/**
|
|
805
|
+
* Default: from db.auth (getSession + onAuthStateChange); for a token-only `db`, from
|
|
806
|
+
* `accessToken`. Admin level from admin.admins either way.
|
|
807
|
+
*/
|
|
771
808
|
identity?: ChatIdentityPort;
|
|
772
809
|
/** Default: no active org; `require` refuses loudly (never auto-picks). */
|
|
773
810
|
org?: ChatOrgPort;
|
|
@@ -803,8 +840,10 @@ export interface ChatHost {
|
|
|
803
840
|
canvas?: ChatCanvasPort;
|
|
804
841
|
/** Default: every conversation may go to any target (`"any"`). */
|
|
805
842
|
routing?: ChatRoutingPort;
|
|
843
|
+
/** Default: none — delegated device tools go to the browser extension via the server port. */
|
|
844
|
+
deviceTools?: ChatDeviceToolsPort;
|
|
806
845
|
}
|
|
807
|
-
export type ChatPortName = "identity" | "org" | "server" | "notify" | "diagnostics" | "prefs" | "navigation" | "windows" | "catalog" | "registry" | "chrome" | "feedback" | "routes" | "canvas" | "routing";
|
|
846
|
+
export type ChatPortName = "identity" | "org" | "server" | "notify" | "diagnostics" | "prefs" | "navigation" | "windows" | "catalog" | "registry" | "chrome" | "feedback" | "routes" | "canvas" | "routing" | "deviceTools";
|
|
808
847
|
/** Every port present. `overridden` names the ports the host supplied itself. */
|
|
809
848
|
export interface ResolvedChatHost {
|
|
810
849
|
/** The host's client, or the no-database stand-in when it gave only a `source`. */
|
|
@@ -831,5 +870,7 @@ export interface ResolvedChatHost {
|
|
|
831
870
|
routes: ChatRoutes;
|
|
832
871
|
canvas: ChatCanvasPort;
|
|
833
872
|
routing: ChatRoutingPort;
|
|
873
|
+
/** The host's on-device tool executor, or null. */
|
|
874
|
+
deviceTools: ChatDeviceToolsPort | null;
|
|
834
875
|
overridden: ReadonlySet<ChatPortName>;
|
|
835
876
|
}
|
package/dist/host/db-types.d.ts
CHANGED
|
@@ -16251,6 +16251,10 @@ export type ChatDatabase = {
|
|
|
16251
16251
|
token: string;
|
|
16252
16252
|
}[];
|
|
16253
16253
|
};
|
|
16254
|
+
access_shadow_status: {
|
|
16255
|
+
Args: never;
|
|
16256
|
+
Returns: Json;
|
|
16257
|
+
};
|
|
16254
16258
|
accessible_child_parents: {
|
|
16255
16259
|
Args: {
|
|
16256
16260
|
p_child_type: string;
|
|
@@ -17034,12 +17038,25 @@ export type ChatDatabase = {
|
|
|
17034
17038
|
Args: never;
|
|
17035
17039
|
Returns: boolean;
|
|
17036
17040
|
};
|
|
17041
|
+
kernel_set_form_on: {
|
|
17042
|
+
Args: {
|
|
17043
|
+
p_person: string;
|
|
17044
|
+
};
|
|
17045
|
+
Returns: boolean;
|
|
17046
|
+
};
|
|
17037
17047
|
kernel_shadow_on: {
|
|
17038
17048
|
Args: {
|
|
17039
17049
|
p_person: string;
|
|
17040
17050
|
};
|
|
17041
17051
|
Returns: boolean;
|
|
17042
17052
|
};
|
|
17053
|
+
kernel_shadow_sweep: {
|
|
17054
|
+
Args: {
|
|
17055
|
+
p_people?: number;
|
|
17056
|
+
p_tables?: number;
|
|
17057
|
+
};
|
|
17058
|
+
Returns: Json;
|
|
17059
|
+
};
|
|
17043
17060
|
lane_of: {
|
|
17044
17061
|
Args: {
|
|
17045
17062
|
p_resource_id: string;
|
|
@@ -19866,6 +19883,21 @@ export type ChatDatabase = {
|
|
|
19866
19883
|
version_live: boolean;
|
|
19867
19884
|
}[];
|
|
19868
19885
|
};
|
|
19886
|
+
run_history: {
|
|
19887
|
+
Args: {
|
|
19888
|
+
p_agent_id?: string;
|
|
19889
|
+
p_desc?: boolean;
|
|
19890
|
+
p_limit?: number;
|
|
19891
|
+
p_mandate_key?: string;
|
|
19892
|
+
p_offset?: number;
|
|
19893
|
+
p_org_id?: string;
|
|
19894
|
+
p_sort?: string;
|
|
19895
|
+
p_status?: string;
|
|
19896
|
+
p_user_id?: string;
|
|
19897
|
+
p_view?: string;
|
|
19898
|
+
};
|
|
19899
|
+
Returns: Json;
|
|
19900
|
+
};
|
|
19869
19901
|
sanitize_app_segment: {
|
|
19870
19902
|
Args: {
|
|
19871
19903
|
p_seg: string;
|
|
@@ -28302,6 +28334,26 @@ export type ChatDatabase = {
|
|
|
28302
28334
|
};
|
|
28303
28335
|
Returns: Json;
|
|
28304
28336
|
};
|
|
28337
|
+
_drill_fine_agg_sql: {
|
|
28338
|
+
Args: {
|
|
28339
|
+
p_cnt: string;
|
|
28340
|
+
p_col: string;
|
|
28341
|
+
p_filter?: string;
|
|
28342
|
+
p_measure: Json;
|
|
28343
|
+
p_once: string;
|
|
28344
|
+
};
|
|
28345
|
+
Returns: string;
|
|
28346
|
+
};
|
|
28347
|
+
_drill_fine_ratio_sql: {
|
|
28348
|
+
Args: {
|
|
28349
|
+
p_cnt: string;
|
|
28350
|
+
p_filter?: string;
|
|
28351
|
+
p_measure: Json;
|
|
28352
|
+
p_once: string;
|
|
28353
|
+
p_prefix: string;
|
|
28354
|
+
};
|
|
28355
|
+
Returns: string;
|
|
28356
|
+
};
|
|
28305
28357
|
_drill_fk: {
|
|
28306
28358
|
Args: {
|
|
28307
28359
|
p_column: string;
|
|
@@ -30411,6 +30463,10 @@ export type ChatDatabase = {
|
|
|
30411
30463
|
Args: never;
|
|
30412
30464
|
Returns: Json;
|
|
30413
30465
|
};
|
|
30466
|
+
drill_def__account_roster: {
|
|
30467
|
+
Args: never;
|
|
30468
|
+
Returns: Json;
|
|
30469
|
+
};
|
|
30414
30470
|
drill_def__agents_by_model: {
|
|
30415
30471
|
Args: never;
|
|
30416
30472
|
Returns: Json;
|
|
@@ -30427,10 +30483,46 @@ export type ChatDatabase = {
|
|
|
30427
30483
|
Args: never;
|
|
30428
30484
|
Returns: Json;
|
|
30429
30485
|
};
|
|
30486
|
+
drill_def__app_log: {
|
|
30487
|
+
Args: never;
|
|
30488
|
+
Returns: Json;
|
|
30489
|
+
};
|
|
30490
|
+
drill_def__crm_deals: {
|
|
30491
|
+
Args: never;
|
|
30492
|
+
Returns: Json;
|
|
30493
|
+
};
|
|
30494
|
+
drill_def__cx_requests: {
|
|
30495
|
+
Args: never;
|
|
30496
|
+
Returns: Json;
|
|
30497
|
+
};
|
|
30498
|
+
drill_def__hr_timesheets: {
|
|
30499
|
+
Args: never;
|
|
30500
|
+
Returns: Json;
|
|
30501
|
+
};
|
|
30430
30502
|
drill_def__kg_cost: {
|
|
30431
30503
|
Args: never;
|
|
30432
30504
|
Returns: Json;
|
|
30433
30505
|
};
|
|
30506
|
+
drill_def__ops_issue_events: {
|
|
30507
|
+
Args: never;
|
|
30508
|
+
Returns: Json;
|
|
30509
|
+
};
|
|
30510
|
+
drill_def__rs_analyses: {
|
|
30511
|
+
Args: never;
|
|
30512
|
+
Returns: Json;
|
|
30513
|
+
};
|
|
30514
|
+
drill_def__rs_syntheses: {
|
|
30515
|
+
Args: never;
|
|
30516
|
+
Returns: Json;
|
|
30517
|
+
};
|
|
30518
|
+
drill_def__system_errors: {
|
|
30519
|
+
Args: never;
|
|
30520
|
+
Returns: Json;
|
|
30521
|
+
};
|
|
30522
|
+
drill_def__tool_calls: {
|
|
30523
|
+
Args: never;
|
|
30524
|
+
Returns: Json;
|
|
30525
|
+
};
|
|
30434
30526
|
drill_def__tool_refetch: {
|
|
30435
30527
|
Args: never;
|
|
30436
30528
|
Returns: Json;
|
|
@@ -30443,6 +30535,10 @@ export type ChatDatabase = {
|
|
|
30443
30535
|
Args: never;
|
|
30444
30536
|
Returns: Json;
|
|
30445
30537
|
};
|
|
30538
|
+
drill_def__write_failures: {
|
|
30539
|
+
Args: never;
|
|
30540
|
+
Returns: Json;
|
|
30541
|
+
};
|
|
30446
30542
|
drill_definition_problems: {
|
|
30447
30543
|
Args: {
|
|
30448
30544
|
p_def: Json;
|
|
@@ -51605,6 +51701,22 @@ export type ChatDatabase = {
|
|
|
51605
51701
|
};
|
|
51606
51702
|
};
|
|
51607
51703
|
Views: {
|
|
51704
|
+
_account_facts: {
|
|
51705
|
+
Row: {
|
|
51706
|
+
blocked: boolean | null;
|
|
51707
|
+
campaign: string | null;
|
|
51708
|
+
cost: number | null;
|
|
51709
|
+
created_at: string | null;
|
|
51710
|
+
identity_state: string | null;
|
|
51711
|
+
person_id: string | null;
|
|
51712
|
+
plan_key: string | null;
|
|
51713
|
+
plan_name: string | null;
|
|
51714
|
+
referrer_host: string | null;
|
|
51715
|
+
requests: number | null;
|
|
51716
|
+
traffic_kind: string | null;
|
|
51717
|
+
};
|
|
51718
|
+
Relationships: [];
|
|
51719
|
+
};
|
|
51608
51720
|
_acquisition_facts: {
|
|
51609
51721
|
Row: {
|
|
51610
51722
|
blocked: boolean | null;
|
package/dist/host/db.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
AuthSessionMissingError
|
|
4
4
|
} from "@supabase/supabase-js";
|
|
5
5
|
import { getChatHost } from "./configure.js";
|
|
6
|
+
import { decodeTokenClaims, isTokenOnlyClient } from "./defaults/identity.js";
|
|
6
7
|
function createClient() {
|
|
7
8
|
return getChatHost().db;
|
|
8
9
|
}
|
|
@@ -35,6 +36,11 @@ function userFromClaims(claims) {
|
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
async function getClaimsUser(client, jwt) {
|
|
39
|
+
if (isTokenOnlyClient(client)) {
|
|
40
|
+
const token = jwt ?? await getChatHost().identity.getAccessToken().catch(() => null);
|
|
41
|
+
const claims = token ? decodeTokenClaims(token) : null;
|
|
42
|
+
return { data: { user: userFromClaims(claims ?? void 0) }, error: null };
|
|
43
|
+
}
|
|
38
44
|
const { data, error } = await client.auth.getClaims(jwt);
|
|
39
45
|
if (error) {
|
|
40
46
|
if (isSignedOutError(error)) return { data: { user: null }, error: null };
|
|
@@ -27,6 +27,11 @@ export interface LogClientErrorDiagnosticsOptions {
|
|
|
27
27
|
/** Default: `process.env.NODE_ENV === "production"`. */
|
|
28
28
|
persist?: boolean;
|
|
29
29
|
flushDelayMs?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Is someone signed in? The resolver passes the identity port's answer, so a token-only client
|
|
32
|
+
* works. Default (a db with an auth-js session): `db.auth.getSession()`.
|
|
33
|
+
*/
|
|
34
|
+
signedIn?: () => Promise<boolean>;
|
|
30
35
|
}
|
|
31
36
|
export declare function isChatSourceApp(value: unknown): value is ChatSourceApp;
|
|
32
37
|
export declare function createLogClientErrorDiagnostics(db: ChatDb, options: LogClientErrorDiagnosticsOptions): ChatDiagnosticsPort;
|
|
@@ -59,8 +59,7 @@ function createLogClientErrorDiagnostics(db, options) {
|
|
|
59
59
|
if (queue.length === 0) return;
|
|
60
60
|
let signedIn = false;
|
|
61
61
|
try {
|
|
62
|
-
|
|
63
|
-
signedIn = Boolean(data.session?.user);
|
|
62
|
+
signedIn = options.signedIn ? await options.signedIn() : Boolean((await db.auth.getSession()).data.session?.user);
|
|
64
63
|
} catch {
|
|
65
64
|
signedIn = false;
|
|
66
65
|
}
|
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
* admin level from `admin.admins` (RLS: a person reads their own row). Starts
|
|
4
4
|
* on first use, never at import, so SSR and tests that never ask pay nothing.
|
|
5
5
|
*/
|
|
6
|
+
import type { User } from "@supabase/supabase-js";
|
|
6
7
|
import type { ChatDb, ChatDiagnosticsPort, ChatIdentity, ChatIdentityPort } from "../contract.js";
|
|
7
8
|
export declare const SIGNED_OUT_IDENTITY: ChatIdentity;
|
|
8
9
|
export declare function createDbIdentity(db: ChatDb, diagnostics: () => ChatDiagnosticsPort): ChatIdentityPort;
|
|
10
|
+
/**
|
|
11
|
+
* Is this a supabase-js client built with the `accessToken` option? Such a client has no auth-js
|
|
12
|
+
* session: touching `db.auth` throws by construction (matrx-local's custodian client is one). The
|
|
13
|
+
* probe reads the property inside a try, so it never throws itself.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isTokenOnlyClient(db: unknown): boolean;
|
|
16
|
+
/** A JWT's payload as an object (no verification — routing metadata only). */
|
|
17
|
+
export declare function decodeTokenClaims(token: string): Record<string, unknown> | null;
|
|
18
|
+
/**
|
|
19
|
+
* The person a token names, read from its claims — routing metadata, not verification (the
|
|
20
|
+
* server and RLS verify every call that token makes). `null` when the token names nobody.
|
|
21
|
+
*/
|
|
22
|
+
export declare function userFromAccessToken(token: string | null): User | null;
|
|
23
|
+
/**
|
|
24
|
+
* Identity for a token-only client: the host hands the same `accessToken` function it gave
|
|
25
|
+
* supabase-js, and the package reads who is signed in from it. Every `getAccessToken()` re-reads
|
|
26
|
+
* the token, so a refresh or an account switch reaches the snapshot (and its subscribers) the next
|
|
27
|
+
* time anything asks; admin level is read through `db` exactly like the session default.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createTokenIdentity(db: ChatDb, getToken: () => Promise<string | null>, diagnostics: () => ChatDiagnosticsPort): ChatIdentityPort;
|
|
30
|
+
/**
|
|
31
|
+
* A token-only client with no `accessToken` function: nobody can be read. Signed out and settled
|
|
32
|
+
* (never a forever-loading screen), announced once with the remedy.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createUnreadableTokenIdentity(announce: (message: string) => void): ChatIdentityPort;
|
|
@@ -113,7 +113,139 @@ function createDbIdentity(db, diagnostics) {
|
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
|
+
function isTokenOnlyClient(db) {
|
|
117
|
+
if (db === null || typeof db !== "object") return false;
|
|
118
|
+
try {
|
|
119
|
+
const auth = db.auth;
|
|
120
|
+
if (!auth) return true;
|
|
121
|
+
void auth.getSession;
|
|
122
|
+
return false;
|
|
123
|
+
} catch {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function decodeTokenClaims(token) {
|
|
128
|
+
const payload = token.split(".")[1];
|
|
129
|
+
if (!payload) return null;
|
|
130
|
+
try {
|
|
131
|
+
const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
132
|
+
const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
|
|
133
|
+
const parsed = JSON.parse(
|
|
134
|
+
typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8")
|
|
135
|
+
);
|
|
136
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function userFromAccessToken(token) {
|
|
142
|
+
if (!token) return null;
|
|
143
|
+
const claims = decodeTokenClaims(token);
|
|
144
|
+
if (!claims || typeof claims.sub !== "string" || !claims.sub) return null;
|
|
145
|
+
const meta = claims.user_metadata;
|
|
146
|
+
const appMeta = claims.app_metadata;
|
|
147
|
+
return {
|
|
148
|
+
id: claims.sub,
|
|
149
|
+
email: typeof claims.email === "string" ? claims.email : void 0,
|
|
150
|
+
user_metadata: meta !== null && typeof meta === "object" ? meta : {},
|
|
151
|
+
app_metadata: appMeta !== null && typeof appMeta === "object" ? appMeta : {},
|
|
152
|
+
aud: typeof claims.aud === "string" ? claims.aud : "authenticated",
|
|
153
|
+
created_at: ""
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function createTokenIdentity(db, getToken, diagnostics) {
|
|
157
|
+
let snapshot = SIGNED_OUT_IDENTITY;
|
|
158
|
+
let started = false;
|
|
159
|
+
let adminFor = null;
|
|
160
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
161
|
+
function publish(next) {
|
|
162
|
+
if (snapshot.userId === next.userId && snapshot.accessToken === next.accessToken && snapshot.adminLevel === next.adminLevel && snapshot.authReady === next.authReady) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
snapshot = next;
|
|
166
|
+
for (const listener of listeners) listener();
|
|
167
|
+
}
|
|
168
|
+
async function readAdminLevel(userId) {
|
|
169
|
+
try {
|
|
170
|
+
const { data, error } = await db.schema("admin").from("admins").select("level").eq("user_id", userId).maybeSingle();
|
|
171
|
+
if (error) throw error;
|
|
172
|
+
const level = data?.level;
|
|
173
|
+
return ADMIN_LEVELS.includes(level) ? level : null;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
diagnostics().capture(error, { area: "identity", code: "admin-level-unreadable" });
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function refresh() {
|
|
180
|
+
let token;
|
|
181
|
+
try {
|
|
182
|
+
token = await getToken();
|
|
183
|
+
} catch (error) {
|
|
184
|
+
diagnostics().capture(error, { area: "identity", code: "access-token-unreadable" });
|
|
185
|
+
token = null;
|
|
186
|
+
}
|
|
187
|
+
const user = userFromAccessToken(token);
|
|
188
|
+
const level = user && user.id === snapshot.userId ? snapshot.adminLevel : null;
|
|
189
|
+
publish(identityFromUser(user, level, user ? token : null));
|
|
190
|
+
if (user && adminFor !== user.id) {
|
|
191
|
+
adminFor = user.id;
|
|
192
|
+
const read = await readAdminLevel(user.id);
|
|
193
|
+
if (snapshot.userId === user.id) publish(identityFromUser(user, read, snapshot.accessToken));
|
|
194
|
+
}
|
|
195
|
+
return user ? token : null;
|
|
196
|
+
}
|
|
197
|
+
function start() {
|
|
198
|
+
if (started) return;
|
|
199
|
+
started = true;
|
|
200
|
+
void refresh();
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
current() {
|
|
204
|
+
start();
|
|
205
|
+
return snapshot;
|
|
206
|
+
},
|
|
207
|
+
subscribe(listener) {
|
|
208
|
+
start();
|
|
209
|
+
listeners.add(listener);
|
|
210
|
+
return () => {
|
|
211
|
+
listeners.delete(listener);
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
getAccessToken: refresh
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function createUnreadableTokenIdentity(announce) {
|
|
218
|
+
const value = Object.freeze({ ...SIGNED_OUT_IDENTITY, authReady: true });
|
|
219
|
+
let said = false;
|
|
220
|
+
const say = () => {
|
|
221
|
+
if (said) return;
|
|
222
|
+
said = true;
|
|
223
|
+
announce(
|
|
224
|
+
"This chat's database client was built with an access-token function, so the chat cannot read who is signed in. Pass the same function to the chat host as `accessToken`."
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
current() {
|
|
229
|
+
say();
|
|
230
|
+
return value;
|
|
231
|
+
},
|
|
232
|
+
subscribe() {
|
|
233
|
+
say();
|
|
234
|
+
return () => {
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
async getAccessToken() {
|
|
238
|
+
say();
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
116
243
|
export {
|
|
117
244
|
SIGNED_OUT_IDENTITY,
|
|
118
|
-
createDbIdentity
|
|
245
|
+
createDbIdentity,
|
|
246
|
+
createTokenIdentity,
|
|
247
|
+
createUnreadableTokenIdentity,
|
|
248
|
+
decodeTokenClaims,
|
|
249
|
+
isTokenOnlyClient,
|
|
250
|
+
userFromAccessToken
|
|
119
251
|
};
|
package/dist/host/identity.d.ts
CHANGED
|
@@ -50,8 +50,25 @@ export declare function isNotAuthenticatedError(error: unknown): error is NotAut
|
|
|
50
50
|
/** The signed-in person's id; throws `NotAuthenticatedError` when nobody is. */
|
|
51
51
|
export declare function requireUserId(): string;
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
53
|
+
* The signed-in person's access token, or null — THE one session read in the package. It goes
|
|
54
|
+
* through the host's identity port, so a session client (auth-js) and a token-only client
|
|
55
|
+
* (`ChatHost.accessToken`) answer the same way; nothing else in the package touches `db.auth`
|
|
56
|
+
* (guard: `host/__tests__/session-reads-go-through-the-identity-port.test.ts`).
|
|
57
|
+
*/
|
|
58
|
+
export declare function readSessionToken(): Promise<string | null>;
|
|
59
|
+
/** `readSessionToken` in the structural shape `@ai-matrx/data`'s session retry reads. */
|
|
60
|
+
export declare const hostSessionSource: {
|
|
61
|
+
getSession(): Promise<{
|
|
62
|
+
data: {
|
|
63
|
+
session: {
|
|
64
|
+
access_token: string;
|
|
65
|
+
} | null;
|
|
66
|
+
};
|
|
67
|
+
error: null;
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Does this client carry a signed-in session? For skipping reads only a signed-in person can make;
|
|
55
72
|
* "no session" means an empty result, never a way around a reader a guest needs.
|
|
56
73
|
*/
|
|
57
74
|
export declare function hasBrowserSession(): Promise<boolean>;
|
package/dist/host/identity.js
CHANGED
|
@@ -40,11 +40,11 @@ function requireUserId() {
|
|
|
40
40
|
if (!id) throw new NotAuthenticatedError();
|
|
41
41
|
return id;
|
|
42
42
|
}
|
|
43
|
-
async function
|
|
44
|
-
if (!chatHostHasDatabase()) return
|
|
45
|
-
let
|
|
43
|
+
async function readSessionToken() {
|
|
44
|
+
if (!chatHostHasDatabase()) return null;
|
|
45
|
+
let host;
|
|
46
46
|
try {
|
|
47
|
-
|
|
47
|
+
host = getChatHost();
|
|
48
48
|
} catch (error) {
|
|
49
49
|
announceOnce(
|
|
50
50
|
"identity:no-host-session",
|
|
@@ -52,16 +52,22 @@ async function hasBrowserSession() {
|
|
|
52
52
|
error?.message ?? error
|
|
53
53
|
)}).`
|
|
54
54
|
);
|
|
55
|
-
return
|
|
55
|
+
return null;
|
|
56
56
|
}
|
|
57
57
|
try {
|
|
58
|
-
|
|
59
|
-
data: { session }
|
|
60
|
-
} = await db.auth.getSession();
|
|
61
|
-
return Boolean(session);
|
|
58
|
+
return await host.identity.getAccessToken();
|
|
62
59
|
} catch {
|
|
63
|
-
return
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const hostSessionSource = {
|
|
64
|
+
async getSession() {
|
|
65
|
+
const token = await readSessionToken();
|
|
66
|
+
return { data: { session: token ? { access_token: token } : null }, error: null };
|
|
64
67
|
}
|
|
68
|
+
};
|
|
69
|
+
async function hasBrowserSession() {
|
|
70
|
+
return await readSessionToken() !== null;
|
|
65
71
|
}
|
|
66
72
|
async function isSignedOutVisitor() {
|
|
67
73
|
return !getUserId() && !await hasBrowserSession();
|
|
@@ -70,8 +76,10 @@ export {
|
|
|
70
76
|
NotAuthenticatedError,
|
|
71
77
|
getUserId,
|
|
72
78
|
hasBrowserSession,
|
|
79
|
+
hostSessionSource,
|
|
73
80
|
isNotAuthenticatedError,
|
|
74
81
|
isSignedOutVisitor,
|
|
82
|
+
readSessionToken,
|
|
75
83
|
requireUserId,
|
|
76
84
|
selectAccessToken,
|
|
77
85
|
selectActiveUserName,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createSessionRetry } from "@ai-matrx/data/db";
|
|
2
|
-
import {
|
|
2
|
+
import { hostSessionSource } from "./identity.js";
|
|
3
3
|
import {
|
|
4
4
|
isMissingSessionError,
|
|
5
5
|
SessionUnavailableError
|
|
6
6
|
} from "@ai-matrx/data/db";
|
|
7
7
|
const runWithSessionRetry = createSessionRetry({
|
|
8
|
-
auth:
|
|
8
|
+
auth: hostSessionSource
|
|
9
9
|
});
|
|
10
10
|
export {
|
|
11
11
|
SessionUnavailableError,
|
package/dist/host/ui-slots.js
CHANGED
|
@@ -18,7 +18,8 @@ function slotComponent(name, Fallback) {
|
|
|
18
18
|
const Wrapper = (props) => {
|
|
19
19
|
const Impl = slots[name];
|
|
20
20
|
if (!Impl) {
|
|
21
|
-
|
|
21
|
+
const absent = !Fallback || Fallback.optionalAbsent;
|
|
22
|
+
reportUnregistered(name, absent ? "it renders nothing here" : "a plain stand-in is drawn");
|
|
22
23
|
return Fallback ? createElement(Fallback, props) : null;
|
|
23
24
|
}
|
|
24
25
|
return createElement(Impl, props);
|
|
@@ -35,6 +36,12 @@ function unregisteredWidget(name) {
|
|
|
35
36
|
UnregisteredWidget.displayName = `UnregisteredWidget(${name})`;
|
|
36
37
|
return UnregisteredWidget;
|
|
37
38
|
}
|
|
39
|
+
function optionalWidget(name) {
|
|
40
|
+
const AbsentOptionalWidget = () => null;
|
|
41
|
+
AbsentOptionalWidget.displayName = `AbsentOptionalWidget(${name})`;
|
|
42
|
+
AbsentOptionalWidget.optionalAbsent = true;
|
|
43
|
+
return AbsentOptionalWidget;
|
|
44
|
+
}
|
|
38
45
|
function hostSlot(name, Fallback) {
|
|
39
46
|
return slotComponent(name, Fallback);
|
|
40
47
|
}
|
|
@@ -160,7 +167,7 @@ function slotFn(name, declared) {
|
|
|
160
167
|
const CopyButtons = slotComponent("CopyButtons");
|
|
161
168
|
const InfoHint = slotComponent("InfoHint");
|
|
162
169
|
const AnswerValueView = slotComponent("AnswerValueView");
|
|
163
|
-
const ErrorAlchemyMenu = slotComponent("ErrorAlchemyMenu",
|
|
170
|
+
const ErrorAlchemyMenu = slotComponent("ErrorAlchemyMenu", optionalWidget("ErrorAlchemyMenu"));
|
|
164
171
|
const ErrorNotice = slotComponent(
|
|
165
172
|
"ErrorNotice",
|
|
166
173
|
({ title, message, error, actions, children, className }) => createElement(
|
|
@@ -187,16 +194,16 @@ const NonEditableContextMenu = slotComponent("NonEditableContextMenu", contentWi
|
|
|
187
194
|
const TableChooser = slotComponent("TableChooser");
|
|
188
195
|
const FileResourceChip = slotComponent("FileResourceChip");
|
|
189
196
|
const ConnectorMark = slotComponent("ConnectorMark");
|
|
190
|
-
const MessageFilesStrip = slotComponent("MessageFilesStrip",
|
|
191
|
-
const RulebookNudge = slotComponent("RulebookNudge",
|
|
192
|
-
const NegativeVerdictFollowUp = slotComponent("NegativeVerdictFollowUp",
|
|
193
|
-
const SpeakerButton = slotComponent("SpeakerButton",
|
|
194
|
-
const GmailReviewCard = slotComponent("GmailReviewCard",
|
|
195
|
-
const ShareButton = slotComponent("ShareButton",
|
|
196
|
-
const ShareModal = slotComponent("ShareModal",
|
|
197
|
-
const ReviewAnswersLink = slotComponent("ReviewAnswersLink",
|
|
198
|
-
const RecordChangeApprovalCard = slotComponent("RecordChangeApprovalCard",
|
|
199
|
-
const AttachedResourcesSection = slotComponent("AttachedResourcesSection",
|
|
197
|
+
const MessageFilesStrip = slotComponent("MessageFilesStrip", optionalWidget("MessageFilesStrip"));
|
|
198
|
+
const RulebookNudge = slotComponent("RulebookNudge", optionalWidget("RulebookNudge"));
|
|
199
|
+
const NegativeVerdictFollowUp = slotComponent("NegativeVerdictFollowUp", optionalWidget("NegativeVerdictFollowUp"));
|
|
200
|
+
const SpeakerButton = slotComponent("SpeakerButton", optionalWidget("SpeakerButton"));
|
|
201
|
+
const GmailReviewCard = slotComponent("GmailReviewCard", optionalWidget("GmailReviewCard"));
|
|
202
|
+
const ShareButton = slotComponent("ShareButton", optionalWidget("ShareButton"));
|
|
203
|
+
const ShareModal = slotComponent("ShareModal", optionalWidget("ShareModal"));
|
|
204
|
+
const ReviewAnswersLink = slotComponent("ReviewAnswersLink", optionalWidget("ReviewAnswersLink"));
|
|
205
|
+
const RecordChangeApprovalCard = slotComponent("RecordChangeApprovalCard", optionalWidget("RecordChangeApprovalCard"));
|
|
206
|
+
const AttachedResourcesSection = slotComponent("AttachedResourcesSection", optionalWidget("AttachedResourcesSection"));
|
|
200
207
|
const ConnectorPromptHost = slotComponent("ConnectorPromptHost");
|
|
201
208
|
const WebpageSnapshotView = slotComponent("WebpageSnapshotView", DefaultWebpageSnapshotView);
|
|
202
209
|
function hostFn(name, fallback) {
|
|
@@ -230,16 +237,16 @@ const traceWarRoomRenderPath = slotFn("traceWarRoomRenderPath", () => void 0);
|
|
|
230
237
|
const isWarRoomThreadAgentSurface = slotFn("isWarRoomThreadAgentSurface", () => false);
|
|
231
238
|
const useOpenCloudBrowserCanvas = slotFn("useOpenCloudBrowserCanvas", () => () => void 0);
|
|
232
239
|
const cloudBrowserCanvasSourceId = slotFn("cloudBrowserCanvasSourceId", (conversationId) => `cloud-browser:${conversationId}`);
|
|
233
|
-
const SystemInstructionModal = slotComponent("SystemInstructionModal",
|
|
240
|
+
const SystemInstructionModal = slotComponent("SystemInstructionModal", optionalWidget("SystemInstructionModal"));
|
|
234
241
|
const WindowPanel = slotComponent("WindowPanel", DefaultWindowPanel);
|
|
235
242
|
const FullScreenOverlay = slotComponent("FullScreenOverlay", DefaultFullScreenOverlay);
|
|
236
|
-
const ResourcePickerWindow = slotComponent("ResourcePickerWindow",
|
|
237
|
-
const ResourcePickerMenu = slotComponent("ResourcePickerMenu",
|
|
238
|
-
const FilesResourcePicker = slotComponent("FilesResourcePicker",
|
|
239
|
-
const NotePickerPopover = slotComponent("NotePickerPopover",
|
|
243
|
+
const ResourcePickerWindow = slotComponent("ResourcePickerWindow", optionalWidget("ResourcePickerWindow"));
|
|
244
|
+
const ResourcePickerMenu = slotComponent("ResourcePickerMenu", optionalWidget("ResourcePickerMenu"));
|
|
245
|
+
const FilesResourcePicker = slotComponent("FilesResourcePicker", optionalWidget("FilesResourcePicker"));
|
|
246
|
+
const NotePickerPopover = slotComponent("NotePickerPopover", optionalWidget("NotePickerPopover"));
|
|
240
247
|
const SmartInputMessageTemplatePicker = slotComponent(
|
|
241
248
|
"SmartInputMessageTemplatePicker",
|
|
242
|
-
|
|
249
|
+
optionalWidget("SmartInputMessageTemplatePicker")
|
|
243
250
|
);
|
|
244
251
|
const flattenResourcePickerItems = slotFn("flattenResourcePickerItems", defaultResourcePickerItems);
|
|
245
252
|
const useRunControlCounts = slotFn("useRunControlCounts", () => ({}));
|
|
@@ -253,8 +260,8 @@ const emitFullScreenEditorSave = slotFn(
|
|
|
253
260
|
() => Promise.reject(new Error("This host has no full-screen editor save target"))
|
|
254
261
|
);
|
|
255
262
|
const FileRagBadge = slotComponent("FileRagBadge");
|
|
256
|
-
const MediaAttachmentThumbnail = slotComponent("MediaAttachmentThumbnail",
|
|
257
|
-
const UnifiedImageBlockRenderer = slotComponent("UnifiedImageBlockRenderer",
|
|
263
|
+
const MediaAttachmentThumbnail = slotComponent("MediaAttachmentThumbnail", optionalWidget("MediaAttachmentThumbnail"));
|
|
264
|
+
const UnifiedImageBlockRenderer = slotComponent("UnifiedImageBlockRenderer", optionalWidget("UnifiedImageBlockRenderer"));
|
|
258
265
|
const MicrophoneIconButton = slotComponent("MicrophoneIconButton");
|
|
259
266
|
const TranscriptionLoader = slotComponent("TranscriptionLoader");
|
|
260
267
|
const MicDeviceMenu = slotComponent("MicDeviceMenu");
|