@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.
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAqD,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACzG,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"}
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, toolResultViewMount, uiLocator } from "@guuey/mcp-apps-host";
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 ? (uiLocator(result.uiData) ?? null) : null,
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()` localStorage thread persistence + the web SSE
95
- * transport (cookie/guest identity, saturation + cold-start retries).
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
- /** R6 locator resolution (history cards). See `useTranscript`. */
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":"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"}
1
+ {"version":3,"file":"guuey-chat.d.ts","sourceRoot":"","sources":["../../src/react/guuey-chat.tsx"],"names":[],"mappings":"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"}
@@ -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 ?? createWebAdapters(), [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
- ...(reader !== undefined ? { reader } : {}),
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.0",
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.0",
43
- "@guuey/mcp-apps-host": "0.6.0"
42
+ "@guuey/agent-client": "0.6.1",
43
+ "@guuey/mcp-apps-host": "0.6.1"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "react": ">=18",
@@ -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
+ `;
@@ -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, toolResultViewMount, uiLocator, type ViewMount } from "@guuey/mcp-apps-host";
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 ? (uiLocator(result.uiData) ?? null) : null,
414
+ actionScope: result ? (toolResultLocator(result) ?? null) : null,
415
415
  };
416
416
  view.label = viewLabel(view, policy);
417
417
  items.push(view);
@@ -55,7 +55,11 @@ import {
55
55
  type CSSProperties,
56
56
  type ReactNode,
57
57
  } from "react";
58
- import { createWebAdapters, type AgentInvokeAdapters } from "@guuey/agent-client";
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()` localStorage thread persistence + the web SSE
106
- * transport (cookie/guest identity, saturation + cold-start retries).
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
- /** R6 locator resolution (history cards). See `useTranscript`. */
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(() => adaptersProp ?? createWebAdapters(), [adaptersProp]);
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
- ...(reader !== undefined ? { reader } : {}),
276
+ ...(effectiveReader !== undefined ? { reader: effectiveReader } : {}),
204
277
  ...(onDebugEvent !== undefined ? { onDebugEvent } : {}),
205
278
  });
206
279