@guuey/chat 0.4.0 → 0.6.0

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.
Files changed (69) hide show
  1. package/dist/hitl.d.ts +58 -0
  2. package/dist/hitl.d.ts.map +1 -0
  3. package/dist/hitl.js +81 -0
  4. package/dist/index.d.ts +3 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -1
  7. package/dist/native/components.d.ts +110 -0
  8. package/dist/native/components.d.ts.map +1 -0
  9. package/dist/native/components.js +341 -0
  10. package/dist/native/markdown.d.ts +31 -0
  11. package/dist/native/markdown.d.ts.map +1 -0
  12. package/dist/native/markdown.js +69 -0
  13. package/dist/native/theme-native.d.ts +27 -0
  14. package/dist/native/theme-native.d.ts.map +1 -0
  15. package/dist/native/theme-native.js +28 -0
  16. package/dist/native/transcript.d.ts +48 -0
  17. package/dist/native/transcript.d.ts.map +1 -0
  18. package/dist/native/transcript.js +86 -0
  19. package/dist/native.d.ts +26 -0
  20. package/dist/native.d.ts.map +1 -0
  21. package/dist/native.js +27 -0
  22. package/dist/plan.d.ts +15 -0
  23. package/dist/plan.d.ts.map +1 -1
  24. package/dist/plan.js +211 -13
  25. package/dist/policy.d.ts +4 -0
  26. package/dist/policy.d.ts.map +1 -1
  27. package/dist/policy.js +2 -0
  28. package/dist/react/components.d.ts +42 -5
  29. package/dist/react/components.d.ts.map +1 -1
  30. package/dist/react/components.js +51 -1
  31. package/dist/react/guuey-chat.d.ts +62 -4
  32. package/dist/react/guuey-chat.d.ts.map +1 -1
  33. package/dist/react/guuey-chat.js +73 -8
  34. package/dist/react/transcript.d.ts +1 -1
  35. package/dist/react/transcript.d.ts.map +1 -1
  36. package/dist/react/transcript.js +2 -2
  37. package/dist/react/use-transcript.d.ts +21 -2
  38. package/dist/react/use-transcript.d.ts.map +1 -1
  39. package/dist/react/use-transcript.js +57 -3
  40. package/dist/react.d.ts +2 -2
  41. package/dist/react.d.ts.map +1 -1
  42. package/dist/react.js +1 -1
  43. package/dist/strings.d.ts +13 -0
  44. package/dist/strings.d.ts.map +1 -1
  45. package/dist/strings.js +8 -0
  46. package/dist/types.d.ts +146 -7
  47. package/dist/types.d.ts.map +1 -1
  48. package/package.json +18 -7
  49. package/src/corpus/README.md +13 -1
  50. package/src/corpus/__snapshots__/corpus.test.ts.snap +304 -0
  51. package/src/corpus/drive.ts +3 -3
  52. package/src/corpus/fixtures.ts +220 -1
  53. package/src/hitl.ts +103 -0
  54. package/src/index.ts +15 -0
  55. package/src/native/components.tsx +812 -0
  56. package/src/native/markdown.tsx +205 -0
  57. package/src/native/theme-native.ts +48 -0
  58. package/src/native/transcript.tsx +189 -0
  59. package/src/native.tsx +63 -0
  60. package/src/plan.ts +233 -20
  61. package/src/policy.ts +4 -0
  62. package/src/react/components.tsx +171 -8
  63. package/src/react/guuey-chat.tsx +143 -9
  64. package/src/react/transcript.tsx +4 -3
  65. package/src/react/use-transcript.ts +83 -5
  66. package/src/react.tsx +3 -1
  67. package/src/strings.ts +25 -0
  68. package/src/types.ts +152 -6
  69. package/styles.css +21 -0
@@ -98,7 +98,22 @@ export function DefaultView({ item, ctx }) {
98
98
  // never blank.
99
99
  return (_jsx("div", { className: "guuey-chat-view guuey-chat-view-negotiating guuey-chat-shimmer", children: _jsx("p", { role: "status", children: item.label ?? ctx.strings.viewNegotiating }) }));
100
100
  }
101
- return (_jsxs("div", { className: "guuey-chat-view", children: [_jsx(GuueyView, { mount: mount, ...(ctx.viewProps ?? {}), onPhaseChange: (phase) => ctx.onViewPhase(item.key, phase) }), item.attribution !== null ? (_jsx("p", { className: "guuey-chat-attribution", children: item.attribution })) : null] }));
101
+ // The function form resolves against the ACTUAL mount (per-channel relay
102
+ // URLs, per-locator action scoping); it runs only when something mounts.
103
+ const viewProps = typeof ctx.viewProps === "function" ? ctx.viewProps(item, mount) : (ctx.viewProps ?? {});
104
+ return (_jsxs("div", { className: "guuey-chat-view", children: [_jsx(GuueyView, { mount: mount, ...viewProps, onPhaseChange: (phase) => ctx.onViewPhase(item.key, phase) }), item.attribution !== null ? (_jsx("p", { className: "guuey-chat-attribution", children: item.attribution })) : null] }));
105
+ }
106
+ /**
107
+ * guuey#204: the "promote and reference" chip — stands in for the one
108
+ * mount the host's stage shows. A real button when the host wires
109
+ * `onViewRef` (keyboard-accessible for free); a plain label otherwise.
110
+ */
111
+ export function DefaultViewRef({ item, ctx }) {
112
+ if (ctx.onViewRef === undefined) {
113
+ return (_jsx("p", { className: "guuey-chat-view-ref", role: "note", children: item.label }));
114
+ }
115
+ const onViewRef = ctx.onViewRef;
116
+ return (_jsx("button", { type: "button", className: "guuey-chat-view-ref guuey-chat-view-ref-button", onClick: () => onViewRef(item), children: item.label }));
102
117
  }
103
118
  // ─── R7 ────────────────────────────────────────────────────────────────────
104
119
  /** Inline images allow https URLs and image/* base64 data — nothing else. */
@@ -160,6 +175,22 @@ export function DefaultPrompt({ item, ctx }) {
160
175
  }
161
176
  wasPending.current = item.state === "pending";
162
177
  }, [item.state]);
178
+ if (item.promptKind === "hitl") {
179
+ const s = ctx.strings;
180
+ // Settled records: resolved shows the CHOSEN MODE'S LABEL (ids are
181
+ // echo-only identity — asker-scoped semantics, spec §7); declined is
182
+ // the durable deny. `cancelled` is guuey's re-askable dismissal: it
183
+ // collapses to a record but stays answerable when expanded.
184
+ if (item.state === "resolved" || item.state === "declined") {
185
+ return (_jsx("p", { className: `guuey-chat-prompt-record guuey-chat-prompt-${item.state}`, children: item.state === "resolved"
186
+ ? item.chosenModeLabel !== null
187
+ ? s.promptAnsweredWith(item.chosenModeLabel)
188
+ : s.promptAccept
189
+ : s.promptDeclinedRecord }));
190
+ }
191
+ const answerable = item.state === "pending" || (item.state === "cancelled" && item.expanded);
192
+ return (_jsxs("div", { className: "guuey-chat-prompt", role: "group", children: [item.state === "cancelled" && (_jsx("button", { type: "button", className: "guuey-chat-prompt-record guuey-chat-prompt-cancelled", "aria-expanded": item.expanded, onClick: () => ctx.onToggle(item.key), children: s.promptDismissed })), answerable && (_jsxs(_Fragment, { children: [item.message !== null && _jsx("p", { className: "guuey-chat-prompt-ask", children: item.message }), _jsxs("div", { className: "guuey-chat-prompt-actions", children: [item.grantModes.length === 0 ? (_jsx("button", { ref: firstAction, type: "button", className: "guuey-chat-prompt-accept", onClick: () => ctx.onPromptAction?.(item, "accept"), children: s.promptAccept })) : (item.grantModes.map((mode, i) => (_jsx("button", { ref: i === 0 ? firstAction : undefined, type: "button", className: "guuey-chat-prompt-accept", title: mode.description, onClick: () => ctx.onPromptAction?.(item, { grantModeId: mode.id }), children: mode.label ?? mode.id }, mode.id)))), _jsx("button", { type: "button", onClick: () => ctx.onPromptAction?.(item, "decline"), children: s.promptDecline })] })] }))] }));
193
+ }
163
194
  if (item.state !== "pending") {
164
195
  return (_jsxs("p", { className: `guuey-chat-prompt-record guuey-chat-prompt-${item.state}`, children: [item.promptKind, ": ", item.state] }));
165
196
  }
@@ -167,6 +198,15 @@ export function DefaultPrompt({ item, ctx }) {
167
198
  ? `${item.appId} requests ${item.requested} access`
168
199
  : `Link your account to ${item.appId}` }), _jsxs("div", { className: "guuey-chat-prompt-actions", children: [_jsx("button", { ref: firstAction, type: "button", className: "guuey-chat-prompt-accept", onClick: () => ctx.onPromptAction?.(item, "accept"), children: "Allow" }), _jsx("button", { type: "button", onClick: () => ctx.onPromptAction?.(item, "decline"), children: "Decline" })] }), item.raw !== null ? (_jsx("pre", { className: "guuey-chat-prompt-raw", children: JSON.stringify(item.raw, null, 2) })) : null] }));
169
200
  }
201
+ // ─── R16 ──────────────────────────────────────────────────────────────────
202
+ /**
203
+ * A `role:"notice"` session annotation (spec draft.2) — a labeled,
204
+ * non-conversational row that must never read as agent-authored. Calm
205
+ * shows the quiet label; debug appends the provenance facet verbatim.
206
+ */
207
+ export function DefaultNotice({ item, ctx }) {
208
+ return (_jsxs("div", { className: "guuey-chat-notice", role: "note", children: [_jsxs("span", { className: "guuey-chat-notice-label", children: [ctx.strings.noticeLabel, item.sourceLabel !== null ? ` · ${item.sourceLabel}` : ""] }), item.text !== "" && _jsx("span", { className: "guuey-chat-notice-text", children: item.text })] }));
209
+ }
170
210
  // ─── R11 ───────────────────────────────────────────────────────────────────
171
211
  export function DefaultError({ item, ctx }) {
172
212
  return (_jsxs("div", { className: `guuey-chat-error guuey-chat-error-${item.family}`, role: "alert", children: [_jsx("p", { children: item.copy }), ctx.onErrorAction !== undefined && (item.family === "transient" || item.family === "auth") ? (_jsx("button", { type: "button", className: "guuey-chat-error-action", onClick: () => ctx.onErrorAction?.(item), children: item.family === "auth" ? ctx.strings.errorAuth : ctx.strings.userRetry })) : null, item.verbatim !== null ? _jsx("pre", { className: "guuey-chat-error-verbatim", children: item.verbatim }) : null] }));
@@ -193,10 +233,12 @@ export const defaultTranscriptComponents = {
193
233
  toolGroup: DefaultToolGroup,
194
234
  dataResult: DefaultDataResult,
195
235
  view: DefaultView,
236
+ viewRef: DefaultViewRef,
196
237
  media: DefaultMedia,
197
238
  code: DefaultCode,
198
239
  citations: DefaultCitations,
199
240
  prompt: DefaultPrompt,
241
+ notice: DefaultNotice,
200
242
  error: DefaultError,
201
243
  history: DefaultHistoryBoundary,
202
244
  compaction: DefaultCompaction,
@@ -234,6 +276,10 @@ export function renderItem(item, components, ctx) {
234
276
  const C = components.view;
235
277
  return _jsx(C, { item: item, ctx: ctx }, item.key);
236
278
  }
279
+ case "viewRef": {
280
+ const C = components.viewRef;
281
+ return _jsx(C, { item: item, ctx: ctx }, item.key);
282
+ }
237
283
  case "media": {
238
284
  const C = components.media;
239
285
  return _jsx(C, { item: item, ctx: ctx }, item.key);
@@ -250,6 +296,10 @@ export function renderItem(item, components, ctx) {
250
296
  const C = components.prompt;
251
297
  return _jsx(C, { item: item, ctx: ctx }, item.key);
252
298
  }
299
+ case "notice": {
300
+ const C = components.notice;
301
+ return _jsx(C, { item: item, ctx: ctx }, item.key);
302
+ }
253
303
  case "error": {
254
304
  const C = components.error;
255
305
  return _jsx(C, { item: item, ctx: ctx }, item.key);
@@ -31,17 +31,59 @@
31
31
  * adapter (e.g. `createWebAdapters({ apiBaseUrl, … })`) and a persisted
32
32
  * threadId rehydrates on mount — text transcript + persisted cards, which
33
33
  * mount through the same R6 path as live views. Nothing here to configure.
34
+ *
35
+ * ## The imperative seam (guuey#210)
36
+ *
37
+ * Suggested-prompt chips and other host-driven sends stay on the
38
+ * batteries-included path via {@link GuueyChatHandle} — a ref handle
39
+ * (`forwardRef`) and/or the `onReady` callback, ONE stable object for the
40
+ * component's whole life. `send` runs through exactly the Send button's
41
+ * gate (never bypasses it; the typed draft is left untouched — a chip send
42
+ * must not eat a half-typed message); `prefill` mirrors the widget's
43
+ * staged-composer semantics (append joins with a space, never clobbers).
44
+ * Web-only for now: the native tier ships `<NativeTranscript>` without a
45
+ * native GuueyChat, so there is no native surface to put a handle on yet.
34
46
  */
35
- import { type CSSProperties, type ReactNode } from "react";
47
+ import { type CSSProperties } from "react";
36
48
  import { type AgentInvokeAdapters } from "@guuey/agent-client";
49
+ import type { AgHitlAnswer, AgPausedAsk } from "@silverprotocol/core";
37
50
  import type { UiResourceReader } from "@guuey/mcp-apps-host";
38
51
  import { type TranscriptPolicy } from "../policy.js";
39
52
  import { type ChatStrings } from "../strings.js";
40
53
  import { type GuueyChatTheme } from "../theme.js";
41
- import type { ErrorItem, PromptItem } from "../types.js";
54
+ import type { ChatDebugEvent, ErrorItem, PromptItem } from "../types.js";
42
55
  import type { ThemeMode } from "./theme-css.js";
43
56
  import { type TranscriptWindowing } from "./transcript.js";
44
57
  import type { TranscriptComponents, TranscriptItemContext } from "./components.js";
58
+ /**
59
+ * The imperative seam (guuey#210): programmatic send/prefill/focus for
60
+ * hosts that stay on the batteries-included path (suggested-prompt chips
61
+ * being the filing use case). Reach it via `ref` or `onReady` — both
62
+ * deliver the SAME stable object, valid for the component's whole life.
63
+ */
64
+ export interface GuueyChatHandle {
65
+ /**
66
+ * Send `text` through exactly the Send button's gate: returns `false`
67
+ * and does nothing when chat is unavailable (`endpointUrl === null`), a
68
+ * turn is in flight, or the text is blank — it NEVER bypasses the
69
+ * composer's rules. The typed draft is left untouched (a programmatic
70
+ * send must not eat a half-typed message). Failures surface the same
71
+ * way a composer send's do (the hook's `error` / R0 failed-send).
72
+ */
73
+ send(text: string): boolean;
74
+ /**
75
+ * Put `text` into the composer draft. `append: true` joins onto a
76
+ * non-empty draft with a single space (the widget's staged-composer
77
+ * semantic — never clobbers); default replaces. Focuses the input
78
+ * unless `focus: false`.
79
+ */
80
+ prefill(text: string, opts?: {
81
+ focus?: boolean;
82
+ append?: boolean;
83
+ }): void;
84
+ /** Focus the composer input. */
85
+ focusComposer(): void;
86
+ }
45
87
  export interface GuueyChatProps {
46
88
  /** Pod base URL (with or without `/agent/invoke`). `null` disables chat. */
47
89
  endpointUrl: string | null;
@@ -67,6 +109,8 @@ export interface GuueyChatProps {
67
109
  window?: TranscriptWindowing | false;
68
110
  /** R6 locator resolution (history cards). See `useTranscript`. */
69
111
  reader?: UiResourceReader;
112
+ /** The debug sink (spec §5) — fires only under the debug policy. */
113
+ onDebugEvent?: (event: ChatDebugEvent) => void;
70
114
  /** R6 pass-through (relay hook, sandbox page/flags, host context…). */
71
115
  viewProps?: TranscriptItemContext["viewProps"];
72
116
  /**
@@ -74,11 +118,25 @@ export interface GuueyChatProps {
74
118
  * The transcript record moves regardless; without a handler the prompt
75
119
  * card is record-only.
76
120
  */
77
- onPromptAction?: (item: PromptItem, action: "accept" | "decline" | "dismiss") => void;
121
+ onPromptAction?: (item: PromptItem, action: "accept" | "decline" | "dismiss" | {
122
+ grantModeId: string;
123
+ }) => void;
124
+ /**
125
+ * Receives the VALIDATED wire answer for an AgJSON HITL ask (spec
126
+ * draft.2) — the host owns delivering it (the kit has no answer
127
+ * transport). Fired after the transcript record moves.
128
+ */
129
+ onHitlAnswer?: (answer: AgHitlAnswer, ask: AgPausedAsk) => void;
78
130
  /** R11 action slot (sign-in / retry affordances). */
79
131
  onErrorAction?: (item: ErrorItem) => void;
132
+ /**
133
+ * Callback route to the {@link GuueyChatHandle} for hosts that prefer
134
+ * wiring over refs. Fires ONCE per component instance, on mount, with
135
+ * the same stable handle the ref receives.
136
+ */
137
+ onReady?: (handle: GuueyChatHandle) => void;
80
138
  className?: string;
81
139
  style?: CSSProperties;
82
140
  }
83
- export declare function GuueyChat(props: GuueyChatProps): ReactNode;
141
+ export declare const GuueyChat: import("react").ForwardRefExoticComponent<GuueyChatProps & import("react").RefAttributes<GuueyChatHandle>>;
84
142
  //# sourceMappingURL=guuey-chat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"guuey-chat.d.ts","sourceRoot":"","sources":["../../src/react/guuey-chat.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAkC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3F,OAAO,EAAqB,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAElF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAc,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGnF,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,uEAAuE;IACvE,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACnC,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC3C,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/B,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,wDAAwD;IACxD,MAAM,CAAC,EAAE,mBAAmB,GAAG,KAAK,CAAC;IACrC,kEAAkE;IAClE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,uEAAuE;IACvE,SAAS,CAAC,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAC/C;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,KAAK,IAAI,CAAC;IACtF,qDAAqD;IACrD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAQD,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CAsI1D"}
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,EAAqB,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAC1F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAc,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGnF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1E,gCAAgC;IAChC,aAAa,IAAI,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,uEAAuE;IACvE,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACnC,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC3C,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/B,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,wDAAwD;IACxD,MAAM,CAAC,EAAE,mBAAmB,GAAG,KAAK,CAAC;IACrC,kEAAkE;IAClE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,oEAAoE;IACpE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC/C,uEAAuE;IACvE,SAAS,CAAC,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAC/C;;;;OAIG;IACH,cAAc,CAAC,EAAE,CACf,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,KAC/D,IAAI,CAAC;IACV;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,WAAW,KAAK,IAAI,CAAC;IAChE,qDAAqD;IACrD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1C;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAQD,eAAO,MAAM,SAAS,4GAyMpB,CAAC"}
@@ -32,8 +32,20 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
32
32
  * adapter (e.g. `createWebAdapters({ apiBaseUrl, … })`) and a persisted
33
33
  * threadId rehydrates on mount — text transcript + persisted cards, which
34
34
  * mount through the same R6 path as live views. Nothing here to configure.
35
+ *
36
+ * ## The imperative seam (guuey#210)
37
+ *
38
+ * Suggested-prompt chips and other host-driven sends stay on the
39
+ * batteries-included path via {@link GuueyChatHandle} — a ref handle
40
+ * (`forwardRef`) and/or the `onReady` callback, ONE stable object for the
41
+ * component's whole life. `send` runs through exactly the Send button's
42
+ * gate (never bypasses it; the typed draft is left untouched — a chip send
43
+ * must not eat a half-typed message); `prefill` mirrors the widget's
44
+ * staged-composer semantics (append joins with a space, never clobbers).
45
+ * Web-only for now: the native tier ships `<NativeTranscript>` without a
46
+ * native GuueyChat, so there is no native surface to put a handle on yet.
35
47
  */
36
- import { useCallback, useMemo, useState } from "react";
48
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
37
49
  import { createWebAdapters } from "@guuey/agent-client";
38
50
  import { useAgentInvoke } from "@guuey/agent-client/react";
39
51
  import { calmPolicy, debugPolicy } from "../policy.js";
@@ -46,8 +58,8 @@ const PROMPT_STATE = {
46
58
  decline: "declined",
47
59
  dismiss: "dismissed",
48
60
  };
49
- export function GuueyChat(props) {
50
- const { endpointUrl, appId, adapters: adaptersProp, preset = "calm", policy: policyOverrides, components, strings: stringOverrides, theme = DEFAULT_CHAT_THEME, mode = "light", window: windowing, reader, viewProps, onPromptAction, onErrorAction, className, style, } = props;
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;
51
63
  const adapters = useMemo(() => adaptersProp ?? createWebAdapters(), [adaptersProp]);
52
64
  const invoke = useAgentInvoke({ endpointUrl, ...(appId !== undefined ? { appId } : {}), adapters, preserveBlocks: true });
53
65
  const policy = useMemo(() => {
@@ -59,17 +71,61 @@ export function GuueyChat(props) {
59
71
  };
60
72
  return factory({ ...policyOverrides, strings });
61
73
  }, [preset, policyOverrides, stringOverrides]);
62
- const { inputs, resolvePrompt } = useTranscriptInputs(invoke);
74
+ const { inputs, resolvePrompt, answerHitlPrompt } = useTranscriptInputs(invoke);
63
75
  const { plan, toggle, resolvedMounts, onViewPhase } = useTranscript({
64
76
  inputs,
65
77
  policy,
66
78
  ...(reader !== undefined ? { reader } : {}),
79
+ ...(onDebugEvent !== undefined ? { onDebugEvent } : {}),
67
80
  });
68
81
  // ── Composer ─────────────────────────────────────────────────────────
69
82
  const [input, setInput] = useState("");
83
+ const inputRef = useRef(null);
70
84
  const busy = invoke.status !== "ready";
71
85
  const available = endpointUrl !== null;
72
86
  const canSend = available && !busy && input.trim() !== "";
87
+ // ── The imperative seam (guuey#210) ──────────────────────────────────
88
+ // ONE stable handle for the component's whole life (hosts capture it in
89
+ // `onReady` and keep it), reading live truth through a ref so a call
90
+ // always sees the CURRENT gate — never a stale closure's.
91
+ const liveRef = useRef({ available, busy, invoke, onReady });
92
+ useEffect(() => {
93
+ liveRef.current = { available, busy, invoke, onReady };
94
+ });
95
+ const handle = useMemo(() => ({
96
+ send: (text) => {
97
+ const live = liveRef.current;
98
+ const trimmed = text.trim();
99
+ // Exactly the Send button's gate — a handle send never bypasses it.
100
+ if (!live.available || live.busy || trimmed === "")
101
+ return false;
102
+ void live.invoke.send(trimmed).catch(() => {
103
+ // Same contract as submit: the hook owns failure surfacing.
104
+ });
105
+ return true;
106
+ },
107
+ prefill: (text, opts) => {
108
+ setInput((prev) =>
109
+ // The widget's staged-composer semantic: append joins with a
110
+ // space onto a non-empty draft, never clobbers it.
111
+ opts?.append === true && prev.trim() !== "" ? `${prev.trimEnd()} ${text}` : text);
112
+ if (opts?.focus !== false)
113
+ inputRef.current?.focus();
114
+ },
115
+ focusComposer: () => {
116
+ inputRef.current?.focus();
117
+ },
118
+ }), []);
119
+ useImperativeHandle(ref, () => handle, [handle]);
120
+ // `onReady` fires once per instance, on mount, with the stable handle
121
+ // (guarded ref: StrictMode's remount cycle must not double-fire it).
122
+ const readyFiredRef = useRef(false);
123
+ useEffect(() => {
124
+ if (readyFiredRef.current)
125
+ return;
126
+ readyFiredRef.current = true;
127
+ liveRef.current.onReady?.(handle);
128
+ }, [handle]);
73
129
  const submit = useCallback(() => {
74
130
  const text = input.trim();
75
131
  if (text === "" || !available || busy)
@@ -85,14 +141,23 @@ export function GuueyChat(props) {
85
141
  });
86
142
  }, [invoke]);
87
143
  const handlePromptAction = useCallback((item, action) => {
88
- resolvePrompt(item.promptId, PROMPT_STATE[action]);
144
+ if (item.promptKind === "hitl") {
145
+ const answer = answerHitlPrompt(item.ask, typeof action === "object" ? action : action === "accept" ? "accept" : action);
146
+ onHitlAnswer?.(answer, item.ask);
147
+ onPromptAction?.(item, action);
148
+ return;
149
+ }
150
+ // Profile prompts only ever receive the string actions (the default
151
+ // card renders no mode buttons for them).
152
+ if (typeof action !== "object")
153
+ resolvePrompt(item.promptId, PROMPT_STATE[action]);
89
154
  onPromptAction?.(item, action);
90
- }, [resolvePrompt, onPromptAction]);
155
+ }, [resolvePrompt, answerHitlPrompt, onHitlAnswer, onPromptAction]);
91
156
  const strings = policy.strings;
92
157
  return (_jsxs("div", { className: `guuey-chat-surface${className !== undefined ? ` ${className}` : ""}`, style: style, children: [_jsx(Transcript, { plan: plan, strings: strings, theme: theme, mode: mode, ...(windowing !== undefined ? { window: windowing } : {}), ...(components !== undefined ? { components } : {}), onToggle: toggle, onRetry: handleRetry, onPromptAction: handlePromptAction, ...(onErrorAction !== undefined ? { onErrorAction } : {}), resolvedMounts: resolvedMounts, onViewPhase: onViewPhase, ...(viewProps !== undefined ? { viewProps } : {}) }), _jsxs("form", { className: "guuey-chat-composer", onSubmit: (e) => {
93
158
  e.preventDefault();
94
159
  submit();
95
- }, children: [_jsx("textarea", { className: "guuey-chat-composer-input", rows: 1, value: input, onChange: (e) => setInput(e.target.value), onKeyDown: (e) => {
160
+ }, children: [_jsx("textarea", { ref: inputRef, className: "guuey-chat-composer-input", rows: 1, value: input, onChange: (e) => setInput(e.target.value), onKeyDown: (e) => {
96
161
  // `isComposing` guards IME input: an Enter that commits a
97
162
  // candidate must not also send the message.
98
163
  if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
@@ -100,4 +165,4 @@ export function GuueyChat(props) {
100
165
  submit();
101
166
  }
102
167
  }, disabled: !available, "aria-label": strings.composerLabel, placeholder: available ? strings.composerPlaceholder : strings.composerUnavailable }), busy ? (_jsx("button", { type: "button", className: "guuey-chat-composer-stop", onClick: () => invoke.abort(), children: strings.stop })) : (_jsx("button", { type: "submit", className: "guuey-chat-composer-send", disabled: !canSend, children: strings.send }))] })] }));
103
- }
168
+ });
@@ -25,7 +25,7 @@ export interface TranscriptWindowing {
25
25
  /** Trailing items rendered; earlier ones sit behind the expander. */
26
26
  tail: number;
27
27
  }
28
- export interface TranscriptProps extends Pick<TranscriptItemContext, "onToggle" | "onRetry" | "onPromptAction" | "onErrorAction" | "resolvedMounts" | "onViewPhase" | "viewProps"> {
28
+ export interface TranscriptProps extends Pick<TranscriptItemContext, "onToggle" | "onRetry" | "onPromptAction" | "onErrorAction" | "resolvedMounts" | "onViewPhase" | "onViewRef" | "viewProps"> {
29
29
  plan: TranscriptPlan;
30
30
  /** Per-slot component overrides (spec §3's override column). */
31
31
  components?: Partial<TranscriptComponents>;
@@ -1 +1 @@
1
- {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/react/transcript.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAK9D,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eACf,SAAQ,IAAI,CACV,qBAAqB,EACrB,UAAU,GAAG,SAAS,GAAG,gBAAgB,GAAG,eAAe,GAAG,gBAAgB,GAAG,aAAa,GAAG,WAAW,CAC7G;IACD,IAAI,EAAE,cAAc,CAAC;IACrB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC3C,gEAAgE;IAChE,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,yEAAyE;IACzE,MAAM,CAAC,EAAE,mBAAmB,GAAG,KAAK,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,CA2H5D"}
1
+ {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/react/transcript.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAK9D,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eACf,SAAQ,IAAI,CACV,qBAAqB,EACrB,UAAU,GAAG,SAAS,GAAG,gBAAgB,GAAG,eAAe,GAAG,gBAAgB,GAAG,aAAa,GAAG,WAAW,GAAG,WAAW,CAC3H;IACD,IAAI,EAAE,cAAc,CAAC;IACrB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC3C,gEAAgE;IAChE,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,yEAAyE;IACzE,MAAM,CAAC,EAAE,mBAAmB,GAAG,KAAK,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,CA4H5D"}
@@ -24,9 +24,9 @@ import { themeCssVars } from "./theme-css.js";
24
24
  /** How close to the bottom (px) still counts as pinned. */
25
25
  const PIN_THRESHOLD_PX = 48;
26
26
  export function Transcript(props) {
27
- const { plan, components, strings = defaultChatStrings, theme = DEFAULT_CHAT_THEME, mode = "light", window: windowing = { tail: 80 }, className, style, onToggle, onRetry, onPromptAction, onErrorAction, resolvedMounts, onViewPhase, viewProps, } = props;
27
+ const { plan, components, strings = defaultChatStrings, theme = DEFAULT_CHAT_THEME, mode = "light", window: windowing = { tail: 80 }, className, style, onToggle, onRetry, onPromptAction, onErrorAction, resolvedMounts, onViewPhase, onViewRef, viewProps, } = props;
28
28
  const resolvedComponents = useMemo(() => ({ ...defaultTranscriptComponents, ...components }), [components]);
29
- const ctx = useMemo(() => ({ strings, onToggle, onRetry, onPromptAction, onErrorAction, resolvedMounts, onViewPhase, viewProps }), [strings, onToggle, onRetry, onPromptAction, onErrorAction, resolvedMounts, onViewPhase, viewProps]);
29
+ const ctx = useMemo(() => ({ strings, onToggle, onRetry, onPromptAction, onErrorAction, resolvedMounts, onViewPhase, onViewRef, viewProps }), [strings, onToggle, onRetry, onPromptAction, onErrorAction, resolvedMounts, onViewPhase, onViewRef, viewProps]);
30
30
  // ── Windowing ────────────────────────────────────────────────────────
31
31
  const [extraShown, setExtraShown] = useState(0);
32
32
  const tail = windowing === false ? Number.POSITIVE_INFINITY : windowing.tail;
@@ -1,7 +1,9 @@
1
1
  import type { UseAgentInvokeReturn } from "@guuey/agent-client";
2
+ import type { AgHitlAnswer, AgPausedAsk } from "@silverprotocol/core";
2
3
  import { type ResolvedViewMount, type UiResourceReader, type ViewHostPhase } from "@guuey/mcp-apps-host";
4
+ import { type HitlPromptAction } from "../hitl.js";
3
5
  import type { TranscriptPolicy } from "../policy.js";
4
- import type { ItemKey, TranscriptInputs, TranscriptOverrides, TranscriptPlan } from "../types.js";
6
+ import type { ChatDebugEvent, ItemKey, TranscriptInputs, TranscriptOverrides, TranscriptPlan } from "../types.js";
5
7
  export interface UseTranscriptArgs {
6
8
  inputs: TranscriptInputs;
7
9
  policy: TranscriptPolicy;
@@ -12,6 +14,13 @@ export interface UseTranscriptArgs {
12
14
  * resolution — labeled, never blank.
13
15
  */
14
16
  reader?: UiResourceReader;
17
+ /**
18
+ * The debug sink (spec §5, real API since the #135 refinement wave):
19
+ * receives {@link ChatDebugEvent}s — view-phase transitions, R15
20
+ * unknown-block sightings, the #192 recovered-turn marker. Fires ONLY
21
+ * under the debug policy (`debugDetail`); `calm` ignores it by design.
22
+ */
23
+ onDebugEvent?: (event: ChatDebugEvent) => void;
15
24
  }
16
25
  export interface UseTranscriptResult {
17
26
  plan: TranscriptPlan;
@@ -24,7 +33,7 @@ export interface UseTranscriptResult {
24
33
  /** Locator resolutions: mount material, or `"expired"` for a miss. */
25
34
  resolvedMounts: ReadonlyMap<ItemKey, ResolvedViewMount | "expired">;
26
35
  }
27
- export declare function useTranscript({ inputs, policy, reader }: UseTranscriptArgs): UseTranscriptResult;
36
+ export declare function useTranscript({ inputs, policy, reader, onDebugEvent, }: UseTranscriptArgs): UseTranscriptResult;
28
37
  export interface UseTranscriptInputsResult {
29
38
  inputs: TranscriptInputs;
30
39
  /**
@@ -34,6 +43,16 @@ export interface UseTranscriptInputsResult {
34
43
  * without a recorded action reads as `dismissed`.
35
44
  */
36
45
  resolvePrompt: (id: string, state: "answered" | "declined" | "dismissed") => void;
46
+ /**
47
+ * Answer an AgJSON HITL ask (spec draft.2): constructs the wire answer,
48
+ * VALIDATES it against the ask's persisted record (`validateHitlAnswer`
49
+ * — required-iff-declared, echo-must-be-declared, requestState byte-echo)
50
+ * BEFORE anything dispatches, records it in the ledger, and returns it
51
+ * for the HOST to deliver — the kit renders and validates; the answer
52
+ * transport is the host's (no client→pod hitl-answer channel exists on
53
+ * the guuey wire today; see the #16 producer flag).
54
+ */
55
+ answerHitlPrompt: (ask: AgPausedAsk, action: HitlPromptAction) => AgHitlAnswer;
37
56
  }
38
57
  export declare function useTranscriptInputs(invoke: UseAgentInvokeReturn): UseTranscriptInputsResult;
39
58
  //# sourceMappingURL=use-transcript.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-transcript.d.ts","sourceRoot":"","sources":["../../src/react/use-transcript.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EACV,OAAO,EAEP,gBAAgB,EAEhB,mBAAmB,EACnB,cAAc,EACf,MAAM,aAAa,CAAC;AAIrB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,wEAAwE;IACxE,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/B,mEAAmE;IACnE,SAAS,EAAE,mBAAmB,CAAC;IAC/B,0EAA0E;IAC1E,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1D,sEAAsE;IACtE,cAAc,EAAE,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC;CACrE;AAED,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,iBAAiB,GAAG,mBAAmB,CAwEhG;AAID,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,gBAAgB,CAAC;IACzB;;;;;OAKG;IACH,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,WAAW,KAAK,IAAI,CAAC;CACnF;AAKD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,yBAAyB,CA6H3F"}
1
+ {"version":3,"file":"use-transcript.d.ts","sourceRoot":"","sources":["../../src/react/use-transcript.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAA+D,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEhH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EACV,cAAc,EACd,OAAO,EAEP,gBAAgB,EAEhB,mBAAmB,EACnB,cAAc,EACf,MAAM,aAAa,CAAC;AAIrB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,wEAAwE;IACxE,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/B,mEAAmE;IACnE,SAAS,EAAE,mBAAmB,CAAC;IAC/B,0EAA0E;IAC1E,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1D,sEAAsE;IACtE,cAAc,EAAE,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC;CACrE;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,MAAM,EACN,MAAM,EACN,YAAY,GACb,EAAE,iBAAiB,GAAG,mBAAmB,CA2GzC;AAID,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,gBAAgB,CAAC;IACzB;;;;;OAKG;IACH,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,WAAW,KAAK,IAAI,CAAC;IAClF;;;;;;;;OAQG;IACH,gBAAgB,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,gBAAgB,KAAK,YAAY,CAAC;CAChF;AAKD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,yBAAyB,CA+I3F"}
@@ -13,11 +13,15 @@
13
13
  */
14
14
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
15
15
  import { resolveViewMount, } from "@guuey/mcp-apps-host";
16
+ import { buildHitlAnswer, hitlPromptsFromFold } from "../hitl.js";
16
17
  import { planTranscript } from "../plan.js";
17
- export function useTranscript({ inputs, policy, reader }) {
18
+ export function useTranscript({ inputs, policy, reader, onDebugEvent, }) {
18
19
  const [overrides, setOverrides] = useState({});
19
20
  const [phases, setPhases] = useState({});
20
21
  const [resolvedMounts, setResolvedMounts] = useState(new Map());
22
+ // The debug sink (gated on the debug policy — calm ignores it, spec §5).
23
+ const debugSink = useRef(null);
24
+ debugSink.current = policy.debugDetail && onDebugEvent !== undefined ? onDebugEvent : null;
21
25
  const merged = useMemo(() => ({ ...inputs, viewPhases: { ...inputs.viewPhases, ...phases } }), [inputs, phases]);
22
26
  const plan = useMemo(() => planTranscript(merged, policy, overrides), [merged, policy, overrides]);
23
27
  // Toggle flips the item's CURRENT resolved state (policy default or a
@@ -46,9 +50,38 @@ export function useTranscript({ inputs, policy, reader }) {
46
50
  const current = findExpanded();
47
51
  setOverrides((prev) => ({ ...prev, [key]: { expanded: !current } }));
48
52
  }, []);
53
+ // Mirror of `phases` for the change check OUTSIDE the state updater — a
54
+ // sink call inside an updater would double-fire under StrictMode.
55
+ const phasesRef = useRef({});
49
56
  const onViewPhase = useCallback((key, phase) => {
57
+ if (phasesRef.current[key] !== phase) {
58
+ phasesRef.current = { ...phasesRef.current, [key]: phase };
59
+ debugSink.current?.({ type: "view-phase", key, phase });
60
+ }
50
61
  setPhases((prev) => (prev[key] === phase ? prev : { ...prev, [key]: phase }));
51
62
  }, []);
63
+ // Plan-derived debug events, emitted once per sighting (post-render — the
64
+ // plan itself stays pure data).
65
+ const emittedUnknowns = useRef(new Set());
66
+ const recoveryEmitted = useRef(false);
67
+ useEffect(() => {
68
+ const sink = debugSink.current;
69
+ if (sink === null)
70
+ return;
71
+ for (const item of plan.items) {
72
+ if (item.kind !== "unknown" || emittedUnknowns.current.has(item.key))
73
+ continue;
74
+ emittedUnknowns.current.add(item.key);
75
+ sink({ type: "unknown-block", key: item.key, typeName: item.typeName, byteSize: item.byteSize });
76
+ }
77
+ if (plan.recovery !== null && !recoveryEmitted.current) {
78
+ recoveryEmitted.current = true;
79
+ sink({ type: "turn-recovered", marker: plan.recovery });
80
+ }
81
+ else if (plan.recovery === null) {
82
+ recoveryEmitted.current = false;
83
+ }
84
+ }, [plan]);
52
85
  // Locator resolution — one read per locator key, misses become "expired".
53
86
  const readerRef = useRef(reader);
54
87
  readerRef.current = reader;
@@ -66,6 +99,10 @@ export function useTranscript({ inputs, policy, reader }) {
66
99
  inFlight.current.add(item.key);
67
100
  const settle = (value) => {
68
101
  inFlight.current.delete(item.key);
102
+ // The R13 expired verdict is a phase transition too (debug sink).
103
+ if (value === "expired") {
104
+ debugSink.current?.({ type: "view-phase", key: item.key, phase: "expired" });
105
+ }
69
106
  setResolvedMounts((prev) => {
70
107
  const next = new Map(prev);
71
108
  next.set(item.key, value);
@@ -156,6 +193,22 @@ export function useTranscriptInputs(invoke) {
156
193
  if (pending?.kind === "link")
157
194
  invoke.clearProfileLinkRequest();
158
195
  }, [invoke, prompts]);
196
+ // HITL ledger (spec draft.2): asks come from the FOLD's persisted
197
+ // records; this ledger holds only the host-side answers, keyed by askId.
198
+ // A `cancelled` record keeps the card re-askable (guuey's #16 ruling) —
199
+ // a later action on the same ask simply overwrites it.
200
+ const [hitlAnswers, setHitlAnswers] = useState({});
201
+ const answerHitlPrompt = useCallback((ask, action) => {
202
+ const answer = buildHitlAnswer(ask, action);
203
+ setHitlAnswers((prev) => ({
204
+ ...prev,
205
+ [ask.askId]: {
206
+ status: answer.status,
207
+ ...(answer.grantModeId !== undefined ? { grantModeId: answer.grantModeId } : {}),
208
+ },
209
+ }));
210
+ return answer;
211
+ }, []);
159
212
  const inputs = useMemo(() => {
160
213
  // Source-ownership split (plan.ts's rules): the trailing assistant
161
214
  // entry is the IN-FLIGHT fold (or the abort-kept partial) — it moves to
@@ -176,7 +229,7 @@ export function useTranscriptInputs(invoke) {
176
229
  statusElapsedMs: elapsedMs,
177
230
  activeTool: invoke.activeTool,
178
231
  error: invoke.error !== null ? { message: invoke.error, code: invoke.errorCode } : null,
179
- prompts,
232
+ prompts: [...prompts, ...hitlPromptsFromFold(invoke.reduceResult, hitlAnswers)],
180
233
  messages,
181
234
  ...(invoke.historyCards.length > 0 ? { historyCards: invoke.historyCards } : {}),
182
235
  sendStates: invoke.sendStates,
@@ -196,6 +249,7 @@ export function useTranscriptInputs(invoke) {
196
249
  invoke.adopted,
197
250
  elapsedMs,
198
251
  prompts,
252
+ hitlAnswers,
199
253
  ]);
200
- return { inputs, resolvePrompt };
254
+ return { inputs, resolvePrompt, answerHitlPrompt };
201
255
  }
package/dist/react.d.ts CHANGED
@@ -13,9 +13,9 @@
13
13
  * → drop to `planTranscript` + `attachViewHost` and render it all yourself.
14
14
  */
15
15
  export { Transcript, type TranscriptProps, type TranscriptWindowing, } from "./react/transcript.js";
16
- export { defaultTranscriptComponents, renderItem, DefaultUserMessage, DefaultText, DefaultReasoning, DefaultTool, DefaultToolGroup, DefaultDataResult, DefaultView, DefaultMedia, DefaultCode, DefaultCitations, DefaultPrompt, DefaultError, DefaultHistoryBoundary, DefaultCompaction, DefaultUnknown, DefaultStatus, type TranscriptComponents, type TranscriptItemContext, } from "./react/components.js";
16
+ export { defaultTranscriptComponents, renderItem, DefaultUserMessage, DefaultText, DefaultReasoning, DefaultTool, DefaultToolGroup, DefaultDataResult, DefaultView, DefaultViewRef, DefaultMedia, DefaultCode, DefaultCitations, DefaultPrompt, DefaultError, DefaultHistoryBoundary, DefaultCompaction, DefaultUnknown, DefaultStatus, type TranscriptComponents, type TranscriptItemContext, type ViewSlotProps, } from "./react/components.js";
17
17
  export { useTranscript, useTranscriptInputs, type UseTranscriptArgs, type UseTranscriptResult, type UseTranscriptInputsResult, } from "./react/use-transcript.js";
18
- export { GuueyChat, type GuueyChatProps } from "./react/guuey-chat.js";
18
+ export { GuueyChat, type GuueyChatProps, type GuueyChatHandle } from "./react/guuey-chat.js";
19
19
  export { Markdown } from "./react/markdown.js";
20
20
  export { themeCssVars, type ThemeMode } from "./react/theme-css.js";
21
21
  //# sourceMappingURL=react.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,mBAAmB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,2BAA2B,EAC3B,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,GAC/B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,mBAAmB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,2BAA2B,EAC3B,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,GAC/B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7F,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/react.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * → drop to `planTranscript` + `attachViewHost` and render it all yourself.
14
14
  */
15
15
  export { Transcript, } from "./react/transcript.js";
16
- export { defaultTranscriptComponents, renderItem, DefaultUserMessage, DefaultText, DefaultReasoning, DefaultTool, DefaultToolGroup, DefaultDataResult, DefaultView, DefaultMedia, DefaultCode, DefaultCitations, DefaultPrompt, DefaultError, DefaultHistoryBoundary, DefaultCompaction, DefaultUnknown, DefaultStatus, } from "./react/components.js";
16
+ export { defaultTranscriptComponents, renderItem, DefaultUserMessage, DefaultText, DefaultReasoning, DefaultTool, DefaultToolGroup, DefaultDataResult, DefaultView, DefaultViewRef, DefaultMedia, DefaultCode, DefaultCitations, DefaultPrompt, DefaultError, DefaultHistoryBoundary, DefaultCompaction, DefaultUnknown, DefaultStatus, } from "./react/components.js";
17
17
  export { useTranscript, useTranscriptInputs, } from "./react/use-transcript.js";
18
18
  export { GuueyChat } from "./react/guuey-chat.js";
19
19
  export { Markdown } from "./react/markdown.js";
package/dist/strings.d.ts CHANGED
@@ -47,6 +47,10 @@ export interface ChatStrings {
47
47
  viewInlineFallback: string;
48
48
  viewExpired: string;
49
49
  viewSandboxUnavailable: string;
50
+ /** guuey#204: the chip text for a mount promoted to a host stage/canvas. */
51
+ viewPromoted: (title: string) => string;
52
+ /** Chip title when the mount has no producing-call title (history cards). */
53
+ viewRefFallbackTitle: string;
50
54
  /** #192 debug-preset marker (calm never shows it — spec §3, F10). */
51
55
  recoveredFromHistory: string;
52
56
  /** R5 empty result. */
@@ -61,6 +65,15 @@ export interface ChatStrings {
61
65
  showEarlier: (count: number) => string;
62
66
  copy: string;
63
67
  copied: string;
68
+ /** R10 hitl actions (spec draft.2) — mode buttons use the ASKER's labels. */
69
+ promptAccept: string;
70
+ promptDecline: string;
71
+ promptDismissed: string;
72
+ /** The answered record line, e.g. `Allowed — Always`. */
73
+ promptAnsweredWith: (modeLabel: string) => string;
74
+ promptDeclinedRecord: string;
75
+ /** R16 — the notice row's label (provenance shows only under debug). */
76
+ noticeLabel: string;
64
77
  /** The 3c composer (`<GuueyChat>`). */
65
78
  composerPlaceholder: string;
66
79
  composerUnavailable: string;
@@ -1 +1 @@
1
- {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../src/strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAEhB,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IAErB,aAAa;IACb,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,eAAe,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAEvC,UAAU;IACV,cAAc,EAAE,MAAM,CAAC;IAEvB,UAAU;IACV,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAErC,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC;IAErB,WAAW;IACX,UAAU,EAAE,MAAM,CAAC;IAEnB,UAAU;IACV,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAElB,iBAAiB;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB,EAAE,MAAM,CAAC;IAE/B,qEAAqE;IACrE,oBAAoB,EAAE,MAAM,CAAC;IAE7B,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAErC,kBAAkB;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IAEnB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IAEf,uCAAuC;IACvC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,eAAO,MAAM,kBAAkB,EAAE,WAsDhC,CAAC"}
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../src/strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAEhB,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IAErB,aAAa;IACb,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,eAAe,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAEvC,UAAU;IACV,cAAc,EAAE,MAAM,CAAC;IAEvB,UAAU;IACV,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAErC,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC;IAErB,WAAW;IACX,UAAU,EAAE,MAAM,CAAC;IAEnB,UAAU;IACV,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAElB,iBAAiB;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,4EAA4E;IAC5E,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,CAAC;IAE7B,qEAAqE;IACrE,oBAAoB,EAAE,MAAM,CAAC;IAE7B,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAErC,kBAAkB;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IAEnB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IAEf,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,oBAAoB,EAAE,MAAM,CAAC;IAE7B,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IAEpB,uCAAuC;IACvC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,eAAO,MAAM,kBAAkB,EAAE,WAgEhC,CAAC"}