@guuey/chat 0.6.0 → 0.6.1
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/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +2 -2
- package/dist/react/guuey-chat.d.ts +30 -3
- package/dist/react/guuey-chat.d.ts.map +1 -1
- package/dist/react/guuey-chat.js +42 -4
- package/package.json +3 -3
- package/src/corpus/README.md +5 -1
- package/src/corpus/__snapshots__/corpus.test.ts.snap +44 -0
- package/src/corpus/fixtures.ts +29 -0
- package/src/plan.ts +2 -2
- package/src/react/guuey-chat.tsx +79 -6
package/dist/plan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAkCA,OAAO,
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAkCA,OAAO,EAA6D,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,KAAK,EASV,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EAIf,MAAM,YAAY,CAAC;AAslBpB,uCAAuC;AACvC,wBAAgB,cAAc,CAC5B,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,gBAAgB,EACxB,SAAS,GAAE,mBAAwB,GAClC,cAAc,CA0QhB;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,GAAG,cAAc,CAAC,GACxD;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,GAAG,SAAS,CAiB5D"}
|
package/dist/plan.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { snapshotViewMount,
|
|
1
|
+
import { snapshotViewMount, toolResultLocator, toolResultViewMount } from "@guuey/mcp-apps-host";
|
|
2
2
|
import { grantModeDisplay } from "./hitl.js";
|
|
3
3
|
/** R5's giant threshold: above this byte count the state is `giant`. */
|
|
4
4
|
const GIANT_RESULT_BYTES = 16_384;
|
|
@@ -308,7 +308,7 @@ function planAssistantSource(source, slot, inputs, policy, overrides) {
|
|
|
308
308
|
// `result` is narrowed by `mount !== undefined` above; the scope
|
|
309
309
|
// is the PERSISTED locator (`uiData.resourceUri`), never the
|
|
310
310
|
// mount payload's own uri (synthetic for a ggui shell).
|
|
311
|
-
actionScope: result ? (
|
|
311
|
+
actionScope: result ? (toolResultLocator(result) ?? null) : null,
|
|
312
312
|
};
|
|
313
313
|
view.label = viewLabel(view, policy);
|
|
314
314
|
items.push(view);
|
|
@@ -89,10 +89,33 @@ export interface GuueyChatProps {
|
|
|
89
89
|
endpointUrl: string | null;
|
|
90
90
|
/** Owning app id — namespaces the persisted threadId. */
|
|
91
91
|
appId?: string;
|
|
92
|
+
/**
|
|
93
|
+
* The guuey public API base (`…/v1`). Enables the batteries-included
|
|
94
|
+
* read paths without hand-wiring: when set and no `adapters` are given,
|
|
95
|
+
* the default `createWebAdapters` gains transcript history; when set and
|
|
96
|
+
* no `reader` is given, a `UiResourceReader` is built over the same
|
|
97
|
+
* identity so generative-UI locators resolve (guuey#221 — a guest kit
|
|
98
|
+
* user has no bearer to construct one with). Explicit `adapters` /
|
|
99
|
+
* `reader` always win. Absent → today's behavior (no history, no
|
|
100
|
+
* default reader; locators render as expired, labeled).
|
|
101
|
+
*/
|
|
102
|
+
apiBaseUrl?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Identity for the default adapters + default reader — the same two
|
|
105
|
+
* resolvers `createWebAdapters` takes (bearer wins; guest secret next;
|
|
106
|
+
* neither → cookie identity). Ignored when explicit `adapters` and
|
|
107
|
+
* `reader` are both supplied.
|
|
108
|
+
*/
|
|
109
|
+
getAccessToken?: (opts?: {
|
|
110
|
+
forceRefresh?: boolean;
|
|
111
|
+
}) => Promise<string | null>;
|
|
112
|
+
getGuestSecret?: () => string | null;
|
|
92
113
|
/**
|
|
93
114
|
* Host couplings (storage / id / transport / history). Default:
|
|
94
|
-
* `createWebAdapters(
|
|
95
|
-
* transport (cookie/guest
|
|
115
|
+
* `createWebAdapters({ apiBaseUrl, getAccessToken, getGuestSecret })` —
|
|
116
|
+
* localStorage thread persistence + the web SSE transport (cookie/guest
|
|
117
|
+
* identity, saturation + cold-start retries), plus history when
|
|
118
|
+
* `apiBaseUrl` is set.
|
|
96
119
|
*/
|
|
97
120
|
adapters?: AgentInvokeAdapters;
|
|
98
121
|
/** Policy preset (spec §5). Default `"calm"`. */
|
|
@@ -107,7 +130,11 @@ export interface GuueyChatProps {
|
|
|
107
130
|
mode?: ThemeMode;
|
|
108
131
|
/** DOM windowing (§3.2). `false` renders everything. */
|
|
109
132
|
window?: TranscriptWindowing | false;
|
|
110
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* R6 locator resolution (history cards + meta-less live renders). See
|
|
135
|
+
* `useTranscript`. Defaults from `apiBaseUrl` (+ `endpointUrl` for the
|
|
136
|
+
* pod door) when omitted; an explicit reader always wins.
|
|
137
|
+
*/
|
|
111
138
|
reader?: UiResourceReader;
|
|
112
139
|
/** The debug sink (spec §5) — fires only under the debug policy. */
|
|
113
140
|
onDebugEvent?: (event: ChatDebugEvent) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guuey-chat.d.ts","sourceRoot":"","sources":["../../src/react/guuey-chat.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,OAAO,EAQL,KAAK,aAAa,EAEnB,MAAM,OAAO,CAAC;AACf,OAAO,
|
|
1
|
+
{"version":3,"file":"guuey-chat.d.ts","sourceRoot":"","sources":["../../src/react/guuey-chat.tsx"],"names":[],"mappings":"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"}
|
package/dist/react/guuey-chat.js
CHANGED
|
@@ -46,7 +46,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
46
46
|
* native GuueyChat, so there is no native surface to put a handle on yet.
|
|
47
47
|
*/
|
|
48
48
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
|
|
49
|
-
import { createWebAdapters } from "@guuey/agent-client";
|
|
49
|
+
import { createUiResourceReader, createWebAdapters, } from "@guuey/agent-client";
|
|
50
50
|
import { useAgentInvoke } from "@guuey/agent-client/react";
|
|
51
51
|
import { calmPolicy, debugPolicy } from "../policy.js";
|
|
52
52
|
import { defaultChatStrings } from "../strings.js";
|
|
@@ -59,9 +59,47 @@ const PROMPT_STATE = {
|
|
|
59
59
|
dismiss: "dismissed",
|
|
60
60
|
};
|
|
61
61
|
export const GuueyChat = forwardRef(function GuueyChat(props, ref) {
|
|
62
|
-
const { endpointUrl, appId, adapters: adaptersProp, preset = "calm", policy: policyOverrides, components, strings: stringOverrides, theme = DEFAULT_CHAT_THEME, mode = "light", window: windowing, reader, onDebugEvent, viewProps, onPromptAction, onHitlAnswer, onErrorAction, onReady, className, style, } = props;
|
|
63
|
-
const adapters = useMemo(() => adaptersProp ??
|
|
62
|
+
const { endpointUrl, appId, apiBaseUrl, getAccessToken, getGuestSecret, adapters: adaptersProp, preset = "calm", policy: policyOverrides, components, strings: stringOverrides, theme = DEFAULT_CHAT_THEME, mode = "light", window: windowing, reader, onDebugEvent, viewProps, onPromptAction, onHitlAnswer, onErrorAction, onReady, className, style, } = props;
|
|
63
|
+
const adapters = useMemo(() => adaptersProp ??
|
|
64
|
+
createWebAdapters({
|
|
65
|
+
...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
|
|
66
|
+
...(getAccessToken !== undefined ? { getAccessToken } : {}),
|
|
67
|
+
...(getGuestSecret !== undefined ? { getGuestSecret } : {}),
|
|
68
|
+
}), [adaptersProp, apiBaseUrl, getAccessToken, getGuestSecret]);
|
|
64
69
|
const invoke = useAgentInvoke({ endpointUrl, ...(appId !== undefined ? { appId } : {}), adapters, preserveBlocks: true });
|
|
70
|
+
// Default reader (guuey#221): built over the SAME identity as the
|
|
71
|
+
// transport/history, targeting the pod door (live turns) then the
|
|
72
|
+
// platform door (persisted). The threadId hydrates after mount and can
|
|
73
|
+
// change on reset, so the reader is a stable function that reads the
|
|
74
|
+
// CURRENT thread through a ref — `useTranscript` sees one reader for the
|
|
75
|
+
// component's life and never re-resolves every mount on a thread flip.
|
|
76
|
+
const threadIdRef = useRef(invoke.threadId);
|
|
77
|
+
threadIdRef.current = invoke.threadId;
|
|
78
|
+
const defaultReader = useMemo(() => {
|
|
79
|
+
if (apiBaseUrl === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
return async (resourceUri) => {
|
|
82
|
+
const threadId = threadIdRef.current;
|
|
83
|
+
// No thread yet ⇒ nothing persisted to scope a read to; a live-turn
|
|
84
|
+
// locator always arrives with the thread already admitted (the
|
|
85
|
+
// `session` frame precedes tool results).
|
|
86
|
+
if (threadId === null)
|
|
87
|
+
return undefined;
|
|
88
|
+
// Assembled per read: `createUiResourceReader` is a cheap closure, and
|
|
89
|
+
// the guest secret is re-resolved each call so a rotation takes
|
|
90
|
+
// effect immediately — the same per-request property
|
|
91
|
+
// `createWebAdapters` documents for its own resolvers.
|
|
92
|
+
const read = createUiResourceReader({
|
|
93
|
+
apiBaseUrl,
|
|
94
|
+
threadId,
|
|
95
|
+
endpointUrl,
|
|
96
|
+
...(getAccessToken !== undefined ? { getAccessToken } : {}),
|
|
97
|
+
guestSecret: getGuestSecret ? getGuestSecret() : null,
|
|
98
|
+
});
|
|
99
|
+
return read(resourceUri);
|
|
100
|
+
};
|
|
101
|
+
}, [apiBaseUrl, endpointUrl, getAccessToken, getGuestSecret]);
|
|
102
|
+
const effectiveReader = reader ?? defaultReader;
|
|
65
103
|
const policy = useMemo(() => {
|
|
66
104
|
const factory = preset === "debug" ? debugPolicy : calmPolicy;
|
|
67
105
|
const strings = {
|
|
@@ -75,7 +113,7 @@ export const GuueyChat = forwardRef(function GuueyChat(props, ref) {
|
|
|
75
113
|
const { plan, toggle, resolvedMounts, onViewPhase } = useTranscript({
|
|
76
114
|
inputs,
|
|
77
115
|
policy,
|
|
78
|
-
...(
|
|
116
|
+
...(effectiveReader !== undefined ? { reader: effectiveReader } : {}),
|
|
79
117
|
...(onDebugEvent !== undefined ? { onDebugEvent } : {}),
|
|
80
118
|
});
|
|
81
119
|
// ── Composer ─────────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guuey/chat",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "The default end-user transcript UI for guuey agents — the headless view-model (planTranscript), calm/debug presets, the GuueyChatTheme token schema, the ChatStrings i18n seam, and (at ./react) the component kit that renders it: per-category components, <Transcript>, the live + history input assemblers, and stick-to-bottom/windowed/accessible transcript behavior; at ./native, the React Native renderer over the same view-model.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"@silverprotocol/core": "0.5.0",
|
|
40
40
|
"@silverprotocol/richtext": "0.5.0",
|
|
41
41
|
"zod": "^4.3.6",
|
|
42
|
-
"@guuey/agent-client": "0.6.
|
|
43
|
-
"@guuey/mcp-apps-host": "0.6.
|
|
42
|
+
"@guuey/agent-client": "0.6.1",
|
|
43
|
+
"@guuey/mcp-apps-host": "0.6.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"react": ">=18",
|
package/src/corpus/README.md
CHANGED
|
@@ -44,7 +44,11 @@ witness.
|
|
|
44
44
|
paths, labeled and never agent-voiced, provenance under debug only),
|
|
45
45
|
and family 22 (`promoted-view` — guuey#204 "promote and reference": the
|
|
46
46
|
one mount a host stage shows chips via `promotedViewKey`, every other
|
|
47
|
-
mount stays full, no/stale key is byte-identical to today)
|
|
47
|
+
mount stays full, no/stale key is byte-identical to today), and family
|
|
48
|
+
23 (`meta-less-locator` — guuey#209 route-A: a `_meta`-withholding
|
|
49
|
+
producer's `ui://` locator rides `structuredContent` (AgJSON §2.1 model-
|
|
50
|
+
channel routing) and STILL mounts as a locator — before this it rendered
|
|
51
|
+
nothing at all, the corpus's first "dark, not degraded" shape).
|
|
48
52
|
- `capture.ts` / `captures/` — family 18: production-capture replay
|
|
49
53
|
through the real `invokeTurn` (provenance in the module header).
|
|
50
54
|
- `corpus.test.ts` — the runner: per-fixture assertions + snapshots, plain
|
|
@@ -1892,3 +1892,47 @@ exports[`corpus > 22. promoted-view — the stage's mount chips (guuey#204); oth
|
|
|
1892
1892
|
"status": null,
|
|
1893
1893
|
}
|
|
1894
1894
|
`;
|
|
1895
|
+
|
|
1896
|
+
exports[`corpus > 23. meta-less-locator — a \`_meta\`-withholding producer's render still mounts (as a locator), never dark (guuey#209) 1`] = `
|
|
1897
|
+
{
|
|
1898
|
+
"items": [
|
|
1899
|
+
{
|
|
1900
|
+
"expanded": true,
|
|
1901
|
+
"key": "u0",
|
|
1902
|
+
"kind": "user",
|
|
1903
|
+
"retry": false,
|
|
1904
|
+
"state": "sent",
|
|
1905
|
+
"text": "render a slot picker",
|
|
1906
|
+
},
|
|
1907
|
+
{
|
|
1908
|
+
"argsPreview": null,
|
|
1909
|
+
"attribution": true,
|
|
1910
|
+
"expanded": false,
|
|
1911
|
+
"key": "tool.t1",
|
|
1912
|
+
"kind": "tool",
|
|
1913
|
+
"name": "ggui_render",
|
|
1914
|
+
"result": null,
|
|
1915
|
+
"state": "done",
|
|
1916
|
+
"title": "ggui render",
|
|
1917
|
+
"toolCallId": "t1",
|
|
1918
|
+
},
|
|
1919
|
+
{
|
|
1920
|
+
"actionScope": "ui://ggui/render/render_dark/h1",
|
|
1921
|
+
"attribution": "via ggui render",
|
|
1922
|
+
"channel": "locator",
|
|
1923
|
+
"expanded": true,
|
|
1924
|
+
"key": "view.t1",
|
|
1925
|
+
"kind": "view",
|
|
1926
|
+
"label": "Loading view…",
|
|
1927
|
+
"mount": {
|
|
1928
|
+
"channel": "locator",
|
|
1929
|
+
"resourceUri": "ui://ggui/render/render_dark/h1",
|
|
1930
|
+
},
|
|
1931
|
+
"phase": "negotiating",
|
|
1932
|
+
"toolTitle": "ggui render",
|
|
1933
|
+
},
|
|
1934
|
+
],
|
|
1935
|
+
"recovery": null,
|
|
1936
|
+
"status": null,
|
|
1937
|
+
}
|
|
1938
|
+
`;
|
package/src/corpus/fixtures.ts
CHANGED
|
@@ -555,3 +555,32 @@ export function promotedView(): { inputs: TranscriptInputs; promotedKey: string
|
|
|
555
555
|
const inputs = driveTurn(events, { userText: "show me two cards" });
|
|
556
556
|
return { inputs, promotedKey: "view.t2" };
|
|
557
557
|
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* 23. meta-less-locator — guuey#209 route-A finding: a producer that
|
|
561
|
+
* withholds tool-result `_meta` (ggui's non-prod posture; any plain-locator
|
|
562
|
+
* MCP server) yields a fold block with NO uiData and NO _meta — AgJSON §2.1
|
|
563
|
+
* routes the sibling structuredContent to the MODEL channel — and the
|
|
564
|
+
* `ui://` locator rides `structuredContent.resourceUri`. Wire keys per the
|
|
565
|
+
* 2026-08-16 dev dump: type,toolCallId,content,outcome,isError,
|
|
566
|
+
* structuredContent. Before #209 this rendered NOTHING (dark, not
|
|
567
|
+
* "expired") and persisted no placeholder row.
|
|
568
|
+
*/
|
|
569
|
+
export function metaLessLocator(): TranscriptInputs {
|
|
570
|
+
const s = seqSource();
|
|
571
|
+
const events: InvokeTurnEvent[] = [
|
|
572
|
+
session,
|
|
573
|
+
frame(boot(s), { status: "thinking" }),
|
|
574
|
+
frame(
|
|
575
|
+
toolPart(s, "t1", "ggui_render", {
|
|
576
|
+
content: [{ type: "text", text: "rendered" }],
|
|
577
|
+
outcome: "ok",
|
|
578
|
+
isError: false,
|
|
579
|
+
structuredContent: { resourceUri: "ui://ggui/render/render_dark/h1", sessionId: "render_dark" },
|
|
580
|
+
}),
|
|
581
|
+
{ status: "using-tool", activeTool: "ggui_render" },
|
|
582
|
+
),
|
|
583
|
+
doneEvent,
|
|
584
|
+
];
|
|
585
|
+
return driveTurn(events, { userText: "render a slot picker" });
|
|
586
|
+
}
|
package/src/plan.ts
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* append ordinals, so every existing key survives each re-plan.
|
|
33
33
|
*/
|
|
34
34
|
import type { AgBlock, AgReduceResult, JsonValue } from "@silverprotocol/core";
|
|
35
|
-
import { snapshotViewMount,
|
|
35
|
+
import { snapshotViewMount, toolResultLocator, toolResultViewMount, type ViewMount } from "@guuey/mcp-apps-host";
|
|
36
36
|
import type { TranscriptPolicy } from "./policy.js";
|
|
37
37
|
import { grantModeDisplay } from "./hitl.js";
|
|
38
38
|
import type {
|
|
@@ -411,7 +411,7 @@ function planAssistantSource(
|
|
|
411
411
|
// `result` is narrowed by `mount !== undefined` above; the scope
|
|
412
412
|
// is the PERSISTED locator (`uiData.resourceUri`), never the
|
|
413
413
|
// mount payload's own uri (synthetic for a ggui shell).
|
|
414
|
-
actionScope: result ? (
|
|
414
|
+
actionScope: result ? (toolResultLocator(result) ?? null) : null,
|
|
415
415
|
};
|
|
416
416
|
view.label = viewLabel(view, policy);
|
|
417
417
|
items.push(view);
|
package/src/react/guuey-chat.tsx
CHANGED
|
@@ -55,7 +55,11 @@ import {
|
|
|
55
55
|
type CSSProperties,
|
|
56
56
|
type ReactNode,
|
|
57
57
|
} from "react";
|
|
58
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
createUiResourceReader,
|
|
60
|
+
createWebAdapters,
|
|
61
|
+
type AgentInvokeAdapters,
|
|
62
|
+
} from "@guuey/agent-client";
|
|
59
63
|
import type { AgHitlAnswer, AgPausedAsk } from "@silverprotocol/core";
|
|
60
64
|
import { useAgentInvoke } from "@guuey/agent-client/react";
|
|
61
65
|
import type { UiResourceReader } from "@guuey/mcp-apps-host";
|
|
@@ -100,10 +104,31 @@ export interface GuueyChatProps {
|
|
|
100
104
|
endpointUrl: string | null;
|
|
101
105
|
/** Owning app id — namespaces the persisted threadId. */
|
|
102
106
|
appId?: string;
|
|
107
|
+
/**
|
|
108
|
+
* The guuey public API base (`…/v1`). Enables the batteries-included
|
|
109
|
+
* read paths without hand-wiring: when set and no `adapters` are given,
|
|
110
|
+
* the default `createWebAdapters` gains transcript history; when set and
|
|
111
|
+
* no `reader` is given, a `UiResourceReader` is built over the same
|
|
112
|
+
* identity so generative-UI locators resolve (guuey#221 — a guest kit
|
|
113
|
+
* user has no bearer to construct one with). Explicit `adapters` /
|
|
114
|
+
* `reader` always win. Absent → today's behavior (no history, no
|
|
115
|
+
* default reader; locators render as expired, labeled).
|
|
116
|
+
*/
|
|
117
|
+
apiBaseUrl?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Identity for the default adapters + default reader — the same two
|
|
120
|
+
* resolvers `createWebAdapters` takes (bearer wins; guest secret next;
|
|
121
|
+
* neither → cookie identity). Ignored when explicit `adapters` and
|
|
122
|
+
* `reader` are both supplied.
|
|
123
|
+
*/
|
|
124
|
+
getAccessToken?: (opts?: { forceRefresh?: boolean }) => Promise<string | null>;
|
|
125
|
+
getGuestSecret?: () => string | null;
|
|
103
126
|
/**
|
|
104
127
|
* Host couplings (storage / id / transport / history). Default:
|
|
105
|
-
* `createWebAdapters(
|
|
106
|
-
* transport (cookie/guest
|
|
128
|
+
* `createWebAdapters({ apiBaseUrl, getAccessToken, getGuestSecret })` —
|
|
129
|
+
* localStorage thread persistence + the web SSE transport (cookie/guest
|
|
130
|
+
* identity, saturation + cold-start retries), plus history when
|
|
131
|
+
* `apiBaseUrl` is set.
|
|
107
132
|
*/
|
|
108
133
|
adapters?: AgentInvokeAdapters;
|
|
109
134
|
/** Policy preset (spec §5). Default `"calm"`. */
|
|
@@ -118,7 +143,11 @@ export interface GuueyChatProps {
|
|
|
118
143
|
mode?: ThemeMode;
|
|
119
144
|
/** DOM windowing (§3.2). `false` renders everything. */
|
|
120
145
|
window?: TranscriptWindowing | false;
|
|
121
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* R6 locator resolution (history cards + meta-less live renders). See
|
|
148
|
+
* `useTranscript`. Defaults from `apiBaseUrl` (+ `endpointUrl` for the
|
|
149
|
+
* pod door) when omitted; an explicit reader always wins.
|
|
150
|
+
*/
|
|
122
151
|
reader?: UiResourceReader;
|
|
123
152
|
/** The debug sink (spec §5) — fires only under the debug policy. */
|
|
124
153
|
onDebugEvent?: (event: ChatDebugEvent) => void;
|
|
@@ -164,6 +193,9 @@ export const GuueyChat = forwardRef<GuueyChatHandle, GuueyChatProps>(function Gu
|
|
|
164
193
|
const {
|
|
165
194
|
endpointUrl,
|
|
166
195
|
appId,
|
|
196
|
+
apiBaseUrl,
|
|
197
|
+
getAccessToken,
|
|
198
|
+
getGuestSecret,
|
|
167
199
|
adapters: adaptersProp,
|
|
168
200
|
preset = "calm",
|
|
169
201
|
policy: policyOverrides,
|
|
@@ -183,9 +215,50 @@ export const GuueyChat = forwardRef<GuueyChatHandle, GuueyChatProps>(function Gu
|
|
|
183
215
|
style,
|
|
184
216
|
} = props;
|
|
185
217
|
|
|
186
|
-
const adapters = useMemo(
|
|
218
|
+
const adapters = useMemo(
|
|
219
|
+
() =>
|
|
220
|
+
adaptersProp ??
|
|
221
|
+
createWebAdapters({
|
|
222
|
+
...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
|
|
223
|
+
...(getAccessToken !== undefined ? { getAccessToken } : {}),
|
|
224
|
+
...(getGuestSecret !== undefined ? { getGuestSecret } : {}),
|
|
225
|
+
}),
|
|
226
|
+
[adaptersProp, apiBaseUrl, getAccessToken, getGuestSecret],
|
|
227
|
+
);
|
|
187
228
|
const invoke = useAgentInvoke({ endpointUrl, ...(appId !== undefined ? { appId } : {}), adapters, preserveBlocks: true });
|
|
188
229
|
|
|
230
|
+
// Default reader (guuey#221): built over the SAME identity as the
|
|
231
|
+
// transport/history, targeting the pod door (live turns) then the
|
|
232
|
+
// platform door (persisted). The threadId hydrates after mount and can
|
|
233
|
+
// change on reset, so the reader is a stable function that reads the
|
|
234
|
+
// CURRENT thread through a ref — `useTranscript` sees one reader for the
|
|
235
|
+
// component's life and never re-resolves every mount on a thread flip.
|
|
236
|
+
const threadIdRef = useRef<string | null>(invoke.threadId);
|
|
237
|
+
threadIdRef.current = invoke.threadId;
|
|
238
|
+
const defaultReader = useMemo<UiResourceReader | undefined>(() => {
|
|
239
|
+
if (apiBaseUrl === undefined) return undefined;
|
|
240
|
+
return async (resourceUri: string) => {
|
|
241
|
+
const threadId = threadIdRef.current;
|
|
242
|
+
// No thread yet ⇒ nothing persisted to scope a read to; a live-turn
|
|
243
|
+
// locator always arrives with the thread already admitted (the
|
|
244
|
+
// `session` frame precedes tool results).
|
|
245
|
+
if (threadId === null) return undefined;
|
|
246
|
+
// Assembled per read: `createUiResourceReader` is a cheap closure, and
|
|
247
|
+
// the guest secret is re-resolved each call so a rotation takes
|
|
248
|
+
// effect immediately — the same per-request property
|
|
249
|
+
// `createWebAdapters` documents for its own resolvers.
|
|
250
|
+
const read = createUiResourceReader({
|
|
251
|
+
apiBaseUrl,
|
|
252
|
+
threadId,
|
|
253
|
+
endpointUrl,
|
|
254
|
+
...(getAccessToken !== undefined ? { getAccessToken } : {}),
|
|
255
|
+
guestSecret: getGuestSecret ? getGuestSecret() : null,
|
|
256
|
+
});
|
|
257
|
+
return read(resourceUri);
|
|
258
|
+
};
|
|
259
|
+
}, [apiBaseUrl, endpointUrl, getAccessToken, getGuestSecret]);
|
|
260
|
+
const effectiveReader = reader ?? defaultReader;
|
|
261
|
+
|
|
189
262
|
const policy = useMemo(() => {
|
|
190
263
|
const factory = preset === "debug" ? debugPolicy : calmPolicy;
|
|
191
264
|
const strings: ChatStrings = {
|
|
@@ -200,7 +273,7 @@ export const GuueyChat = forwardRef<GuueyChatHandle, GuueyChatProps>(function Gu
|
|
|
200
273
|
const { plan, toggle, resolvedMounts, onViewPhase } = useTranscript({
|
|
201
274
|
inputs,
|
|
202
275
|
policy,
|
|
203
|
-
...(
|
|
276
|
+
...(effectiveReader !== undefined ? { reader: effectiveReader } : {}),
|
|
204
277
|
...(onDebugEvent !== undefined ? { onDebugEvent } : {}),
|
|
205
278
|
});
|
|
206
279
|
|