@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.
- package/dist/hitl.d.ts +58 -0
- package/dist/hitl.d.ts.map +1 -0
- package/dist/hitl.js +81 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/native/components.d.ts +110 -0
- package/dist/native/components.d.ts.map +1 -0
- package/dist/native/components.js +341 -0
- package/dist/native/markdown.d.ts +31 -0
- package/dist/native/markdown.d.ts.map +1 -0
- package/dist/native/markdown.js +69 -0
- package/dist/native/theme-native.d.ts +27 -0
- package/dist/native/theme-native.d.ts.map +1 -0
- package/dist/native/theme-native.js +28 -0
- package/dist/native/transcript.d.ts +48 -0
- package/dist/native/transcript.d.ts.map +1 -0
- package/dist/native/transcript.js +86 -0
- package/dist/native.d.ts +26 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +27 -0
- package/dist/plan.d.ts +15 -0
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +211 -13
- package/dist/policy.d.ts +4 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +2 -0
- package/dist/react/components.d.ts +42 -5
- package/dist/react/components.d.ts.map +1 -1
- package/dist/react/components.js +51 -1
- package/dist/react/guuey-chat.d.ts +62 -4
- package/dist/react/guuey-chat.d.ts.map +1 -1
- package/dist/react/guuey-chat.js +73 -8
- package/dist/react/transcript.d.ts +1 -1
- package/dist/react/transcript.d.ts.map +1 -1
- package/dist/react/transcript.js +2 -2
- package/dist/react/use-transcript.d.ts +21 -2
- package/dist/react/use-transcript.d.ts.map +1 -1
- package/dist/react/use-transcript.js +57 -3
- package/dist/react.d.ts +2 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +1 -1
- package/dist/strings.d.ts +13 -0
- package/dist/strings.d.ts.map +1 -1
- package/dist/strings.js +8 -0
- package/dist/types.d.ts +146 -7
- package/dist/types.d.ts.map +1 -1
- package/package.json +18 -7
- package/src/corpus/README.md +13 -1
- package/src/corpus/__snapshots__/corpus.test.ts.snap +304 -0
- package/src/corpus/drive.ts +3 -3
- package/src/corpus/fixtures.ts +220 -1
- package/src/hitl.ts +103 -0
- package/src/index.ts +15 -0
- package/src/native/components.tsx +812 -0
- package/src/native/markdown.tsx +205 -0
- package/src/native/theme-native.ts +48 -0
- package/src/native/transcript.tsx +189 -0
- package/src/native.tsx +63 -0
- package/src/plan.ts +233 -20
- package/src/policy.ts +4 -0
- package/src/react/components.tsx +171 -8
- package/src/react/guuey-chat.tsx +143 -9
- package/src/react/transcript.tsx +4 -3
- package/src/react/use-transcript.ts +83 -5
- package/src/react.tsx +3 -1
- package/src/strings.ts +25 -0
- package/src/types.ts +152 -6
- package/styles.css +21 -0
package/dist/react/components.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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"
|
|
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
|
|
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
|
|
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
|
@@ -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
|
-
|
|
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,
|
|
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"}
|
package/dist/react/transcript.js
CHANGED
|
@@ -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;
|
|
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
|
package/dist/react.d.ts.map
CHANGED
|
@@ -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,
|
|
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;
|
package/dist/strings.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|