@fungi.computer/parakeet 0.0.1 → 0.2.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 (71) hide show
  1. package/README.md +45 -6
  2. package/dist/account-commands.js +3 -11
  3. package/dist/commands/advertised.d.ts +3 -1
  4. package/dist/commands/advertised.js +89 -4
  5. package/dist/commands/builtins.js +1 -1
  6. package/dist/commands/interactions.d.ts +22 -7
  7. package/dist/commands/interactions.js +41 -17
  8. package/dist/commands/run.d.ts +3 -2
  9. package/dist/commands/run.js +5 -2
  10. package/dist/commands/slash.d.ts +2 -0
  11. package/dist/commands/slash.js +13 -7
  12. package/dist/commands/use-slash-commands.js +4 -4
  13. package/dist/components/AgentHead.d.ts +9 -12
  14. package/dist/components/AgentHead.js +12 -44
  15. package/dist/components/ChatComposer.d.ts +3 -1
  16. package/dist/components/ChatComposer.js +79 -15
  17. package/dist/components/ChatMessage.d.ts +4 -0
  18. package/dist/components/ChatMessage.js +6 -2
  19. package/dist/components/ClientRows.js +2 -2
  20. package/dist/components/InteractionPanel.d.ts +2 -4
  21. package/dist/components/InteractionPanel.js +2 -2
  22. package/dist/components/ShiitakeAvatar.d.ts +7 -5
  23. package/dist/components/ShiitakeAvatar.js +6 -4
  24. package/dist/components/TimelineItem.d.ts +2 -0
  25. package/dist/components/TimelineItem.js +31 -7
  26. package/dist/components/avatar-presentation.d.ts +7 -14
  27. package/dist/components/avatar-presentation.js +6 -13
  28. package/dist/conversation-opening.d.ts +47 -0
  29. package/dist/conversation-opening.js +173 -0
  30. package/dist/conversation.d.ts +50 -5
  31. package/dist/conversation.js +587 -87
  32. package/dist/core/author-labels.d.ts +2 -0
  33. package/dist/core/build-timeline.d.ts +4 -2
  34. package/dist/core/build-timeline.js +46 -7
  35. package/dist/core/client-actions.js +6 -3
  36. package/dist/core/client-timeline.d.ts +9 -2
  37. package/dist/core/client-timeline.js +15 -4
  38. package/dist/core/draft-storage.d.ts +63 -0
  39. package/dist/core/draft-storage.js +50 -0
  40. package/dist/core/model-identity.d.ts +2 -0
  41. package/dist/core/model-identity.js +6 -0
  42. package/dist/core/queued-recall.d.ts +35 -0
  43. package/dist/core/queued-recall.js +138 -0
  44. package/dist/core/session-projection.js +17 -0
  45. package/dist/core/session-query.d.ts +54 -11
  46. package/dist/core/session-query.js +38 -12
  47. package/dist/core/tool-groups.d.ts +1 -1
  48. package/dist/core/tool-groups.js +3 -1
  49. package/dist/core/tool-view.d.ts +1 -1
  50. package/dist/core/tool-view.js +3 -2
  51. package/dist/core/use-timeline-item-key.d.ts +8 -0
  52. package/dist/core/use-timeline-item-key.js +29 -0
  53. package/dist/headless.d.ts +6 -4
  54. package/dist/headless.js +1 -1
  55. package/dist/index.d.ts +2 -2
  56. package/dist/index.js +1 -1
  57. package/dist/model-commands.js +65 -40
  58. package/dist/parakeet.js +17 -18
  59. package/dist/provider-choices.js +10 -7
  60. package/dist/session-commands.d.ts +0 -9
  61. package/dist/session-commands.js +39 -68
  62. package/dist/session.d.ts +1 -1
  63. package/dist/store/types.d.ts +10 -4
  64. package/dist/styles.css +105 -117
  65. package/docs/api/commands/slash.md +24 -21
  66. package/docs/api/headless.md +375 -232
  67. package/docs/api/index.md +168 -60
  68. package/package.json +16 -10
  69. package/src/styles.css +4 -9
  70. package/dist/components/still-frame-cache.d.ts +0 -16
  71. package/dist/components/still-frame-cache.js +0 -63
@@ -1,53 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Monogram } from "@fungi.computer/caps/components/monogram";
3
- import { Sprite } from "@fungi.computer/sprite/react";
4
- import { memo, useEffect, useMemo, useRef, useState } from "react";
3
+ import { Sprite, useAnimationTime } from "@fungi.computer/sprite/react";
4
+ import { memo, useMemo, useState } from "react";
5
5
  import { avatarPresentation } from "./avatar-presentation.js";
6
6
  import { shiitakeSprite } from "./sprite-service.js";
7
- import { captureStillFrame, getStillFrame, setStillFrame, stillFrameKey, } from "./still-frame-cache.js";
8
7
  /**
9
- * An Agent's head at avatar size. Renders the Agent's own avatar when given
10
- * one; otherwise the pre-avatar seeded mushroom cap, byte-identical to
11
- * before avatars existed. The letter shows until a frame is ready. It holds
12
- * still unless `thinking` or `speaking`: an idle head whose render style is
13
- * "plain" and already has a cached still frame for its look never mounts a
14
- * live canvas, so a long rail is not a wall of animation. A render style
15
- * other than "plain" is a CSS filter over the live canvas, not part of the
16
- * captured pixels, so a styled head always stays live.
8
+ * An Agent's head at avatar size, drawn from the Agent's own avatar (a
9
+ * property of the Agent record, never of a conversation). The letter shows
10
+ * until a frame is ready. The live
11
+ * canvas stays mounted and is paused unless `thinking` or `speaking`, so an
12
+ * idle head shows its real look without animating.
17
13
  */
18
- export const AgentHead = memo(function AgentHead({ activity = "idle", avatar, className, name, seed, size = 24, }) {
14
+ export const AgentHead = memo(function AgentHead({ activity = "idle", avatar, className, name, size = 24, timeMs: explicitTimeMs, }) {
15
+ const timeMs = useAnimationTime(explicitTimeMs);
19
16
  const [ready, setReady] = useState(false);
20
- const host = useRef(null);
21
- const presentation = useMemo(() => avatarPresentation(avatar, seed), [avatar, seed]);
22
- const dpr = window.devicePixelRatio;
23
- const cacheKey = useMemo(() => stillFrameKey(presentation, size, dpr), [presentation, size, dpr]);
24
- const [frame, setFrame] = useState(() => getStillFrame(cacheKey));
25
- useEffect(() => {
26
- setFrame(getStillFrame(cacheKey));
27
- }, [cacheKey]);
28
- // A render style is a CSS filter on the live stage (render-style.ts), not
29
- // part of the captured canvas pixels, so a styled head must stay live;
30
- // only a "plain" idle head can be replaced by its cached still frame.
31
- const live = activity !== "idle" ||
32
- frame === undefined ||
33
- presentation.renderStyle !== "plain";
34
- useEffect(() => {
35
- // A cached idle head has no player; its next player must become ready anew.
36
- if (!live)
37
- setReady(false);
38
- }, [live]);
39
- return (_jsxs("span", { "aria-hidden": "true", className: `relative inline-block shrink-0 ${className ?? ""}`, "data-agent-head": frame !== undefined || ready ? "ready" : "loading", style: { height: size, width: size }, children: [frame === undefined && !ready ? (_jsx(Monogram, { className: "absolute inset-0", name: name, seed: seed })) : null, frame !== undefined &&
40
- presentation.renderStyle === "plain" &&
41
- (!live || !ready) ? (_jsx("img", { alt: "", className: "absolute inset-0 h-full w-full", src: frame })) : null, live ? (_jsx(Sprite, { activity: activity, appearance: presentation.appearance, className: "absolute inset-0", expression: presentation.expression, onState: (state) => {
17
+ const presentation = useMemo(() => avatarPresentation(avatar), [avatar]);
18
+ return (_jsxs("span", { "aria-hidden": "true", className: `relative inline-block shrink-0 ${className ?? ""}`, "data-agent-head": ready ? "ready" : "loading", style: { height: size, width: size }, children: [ready ? null : (_jsx(Monogram, { className: "absolute inset-0", name: name, seed: name })), _jsx(Sprite, { activity: activity, ...(timeMs === undefined ? {} : { timeMs }), appearance: presentation.appearance, className: "absolute inset-0", expression: presentation.expression, onState: (state) => {
42
19
  setReady(state.status === "ready");
43
- if (state.status === "ready" &&
44
- activity === "idle" &&
45
- presentation.renderStyle === "plain") {
46
- const captured = captureStillFrame(host.current);
47
- if (captured !== undefined) {
48
- setStillFrame(cacheKey, captured);
49
- setFrame(captured);
50
- }
51
- }
52
- }, paused: activity === "idle", ref: host, renderStyle: presentation.renderStyle, service: shiitakeSprite, src: presentation.src })) : null] }));
20
+ }, paused: timeMs !== undefined || activity === "idle", renderStyle: presentation.renderStyle, service: shiitakeSprite, src: presentation.src })] }));
53
21
  });
@@ -11,6 +11,8 @@ export type ChatComposerUsage = Readonly<{
11
11
  tokens: number;
12
12
  }>;
13
13
  export type ChatComposerProps = {
14
+ /** Undefined for visitor composers without a caller-owned pending queue. */
15
+ queuedRecall: Pick<ComposerController, "recall" | "recallState" | "images"> | undefined;
14
16
  /** Caller-owned prompt hint; omitted for the ordinary Session composer. */
15
17
  placeholder?: string | undefined;
16
18
  /** Hide internal model, usage and status readouts for visitors. */
@@ -38,6 +40,6 @@ export type ChatComposerProps = {
38
40
  /** Parakeet's slash controller; the composer only draws and forwards keys. */
39
41
  slash?: SlashCommandController | undefined;
40
42
  };
41
- /** Shared visible and screen-reader readout of the observed Session state. */
43
+ /** Full Session readout for announcements; the footer only draws attention states. */
42
44
  export declare function sessionReadout(status: ParakeetStatus, view: ParakeetSessionViewState): string;
43
45
  export declare const ChatComposer: import("react").NamedExoticComponent<ChatComposerProps>;
@@ -55,11 +55,21 @@ const presentations = {
55
55
  tone: "neutral",
56
56
  },
57
57
  };
58
- function HostComposerTextarea({ placeholder, onChange, onSend, onAbort, slash, slashListId, textareaId, textareaRef, value, }) {
58
+ function HostComposerTextarea({ placeholder, onChange, onSend, onAbort, slash, slashListId, textareaId, textareaRef, value, queuedRecall, }) {
59
59
  const latestValue = useRef(value);
60
60
  const [history, setHistory] = useState([]);
61
61
  const [historyIndex, setHistoryIndex] = useState(-1);
62
62
  latestValue.current = value;
63
+ const recallState = queuedRecall?.recallState;
64
+ const recalling = recallState?.kind === "recalling";
65
+ useLayoutEffect(() => {
66
+ if (recallState?.kind !== "restored")
67
+ return;
68
+ setHistoryIndex(-1);
69
+ const textarea = textareaRef.current;
70
+ if (textarea !== null)
71
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length);
72
+ }, [recallState, textareaRef]);
63
73
  useLayoutEffect(() => {
64
74
  const textarea = textareaRef.current;
65
75
  if (!textarea)
@@ -69,15 +79,18 @@ function HostComposerTextarea({ placeholder, onChange, onSend, onAbort, slash, s
69
79
  }, [textareaRef, value]);
70
80
  const saveAndSend = useCallback(() => {
71
81
  const draft = latestValue.current.trim();
72
- if (!draft)
82
+ if (!draft && (queuedRecall?.images?.length ?? 0) === 0)
73
83
  return;
74
84
  onSend();
75
- setHistory((previous) => previous.at(-1) === draft ? previous : [...previous, draft]);
85
+ if (draft)
86
+ setHistory((previous) => previous.at(-1) === draft ? previous : [...previous, draft]);
76
87
  setHistoryIndex(-1);
77
- }, [onSend]);
88
+ }, [onSend, queuedRecall?.images?.length]);
78
89
  const onKeyDown = useCallback((event) => {
79
90
  if (isImeComposing(event))
80
91
  return;
92
+ if (recalling)
93
+ return;
81
94
  if (slash?.open === true) {
82
95
  const handled = event.key === "ArrowDown"
83
96
  ? slash.move(1)
@@ -98,6 +111,35 @@ function HostComposerTextarea({ placeholder, onChange, onSend, onAbort, slash, s
98
111
  onAbort();
99
112
  return;
100
113
  }
114
+ if (queuedRecall !== undefined &&
115
+ event.key === "ArrowUp" &&
116
+ !event.repeat &&
117
+ !event.altKey &&
118
+ !event.ctrlKey &&
119
+ !event.metaKey &&
120
+ !event.shiftKey &&
121
+ slash?.open !== true &&
122
+ latestValue.current === "" &&
123
+ (queuedRecall.images?.length ?? 0) === 0 &&
124
+ document.activeElement === event.currentTarget) {
125
+ event.preventDefault();
126
+ const input = event.currentTarget;
127
+ void queuedRecall.recall().then((outcome) => {
128
+ if (outcome !== "empty" ||
129
+ !input.isConnected ||
130
+ input.value !== "" ||
131
+ textareaRef.current !== input ||
132
+ history.length === 0)
133
+ return;
134
+ const next = historyIndex === -1 ? history.length - 1 : historyIndex - 1;
135
+ const entry = history[next];
136
+ if (entry !== undefined) {
137
+ setHistoryIndex(next);
138
+ onChange(entry);
139
+ }
140
+ });
141
+ return;
142
+ }
101
143
  if (event.key === "Enter" && !event.shiftKey) {
102
144
  event.preventDefault();
103
145
  saveAndSend();
@@ -129,19 +171,36 @@ function HostComposerTextarea({ placeholder, onChange, onSend, onAbort, slash, s
129
171
  onChange(entry);
130
172
  }
131
173
  }
132
- }, [history, historyIndex, onAbort, onChange, saveAndSend, slash, textareaRef]);
174
+ }, [
175
+ history,
176
+ historyIndex,
177
+ onAbort,
178
+ onChange,
179
+ saveAndSend,
180
+ slash,
181
+ textareaRef,
182
+ queuedRecall,
183
+ recalling,
184
+ ]);
133
185
  const listOpen = slash?.open === true && slash.completion.kind !== "none"
134
186
  ? slash.completion.items.length > 0
135
187
  : false;
136
- return (_jsxs("div", { className: "relative min-w-0 flex-1", children: [_jsx("label", { className: "sr-only", htmlFor: textareaId, children: "Message" }), _jsx(Textarea, { "aria-autocomplete": "list", "aria-controls": listOpen ? slashListId : undefined, "aria-expanded": listOpen, "aria-activedescendant": listOpen && slash !== undefined
188
+ return (_jsxs("div", { className: "relative min-w-0 flex-1", children: [_jsx("label", { className: "sr-only", htmlFor: textareaId, children: "Message" }), _jsx(Textarea, { "aria-describedby": [
189
+ slash === undefined ? undefined : `${textareaId}-guidance`,
190
+ queuedRecall === undefined
191
+ ? undefined
192
+ : `${textareaId}-recall-guidance`,
193
+ ]
194
+ .filter((id) => id !== undefined)
195
+ .join(" ") || undefined, "aria-busy": recalling, readOnly: recalling, "aria-autocomplete": "list", "aria-controls": listOpen ? slashListId : undefined, "aria-expanded": listOpen, "aria-activedescendant": listOpen && slash !== undefined
137
196
  ? slashOptionId(slashListId, slash.selected)
138
197
  : undefined, className: "join-item min-h-[48px] max-h-[320px] w-full resize-none overflow-x-hidden overflow-y-auto whitespace-pre-wrap break-words px-3 py-[calc((3rem-1.25rem)/2)] font-sans text-xs text-base-content !border-0 !bg-transparent !shadow-none focus:!outline-none", id: textareaId, onChange: (event) => {
139
198
  onChange(event.target.value);
140
199
  setHistoryIndex(-1);
141
200
  }, onKeyDown: onKeyDown, placeholder: placeholder ??
142
- (slash === undefined ? "Ask agent..." : "Ask agent… (/ for commands)"), role: slash === undefined ? undefined : "combobox", ref: textareaRef, rows: 1, value: value })] }));
201
+ (slash === undefined ? "Ask agent..." : "Ask agent… (/ for commands)"), role: slash === undefined ? undefined : "combobox", ref: textareaRef, rows: 1, value: value }), queuedRecall === undefined ? null : (_jsx("span", { id: `${textareaId}-recall-guidance`, className: "sr-only", children: "Press Up on an empty composer to recall your newest queued message." })), (queuedRecall?.images?.length ?? 0) === 0 ? null : (_jsxs("p", { className: "text-xs", children: [queuedRecall?.images?.length, " attached image(s)"] }))] }));
143
202
  }
144
- /** Shared visible and screen-reader readout of the observed Session state. */
203
+ /** Full Session readout for announcements; the footer only draws attention states. */
145
204
  export function sessionReadout(status, view) {
146
205
  if (view.kind === "connecting")
147
206
  return "connecting";
@@ -179,19 +238,24 @@ export function sessionReadout(status, view) {
179
238
  }
180
239
  function ComposerFooter({ statusChips, model, status, viewState, usage, context, }) {
181
240
  const tone = viewState.kind === "error" ? "error" : presentations[status].tone;
182
- const label = sessionReadout(status, viewState);
183
- return (_jsxs("div", { className: "composer-footer", children: [_jsx(Status, { label: label, title: label, tone: tone }), statusChips?.map((chip, index) => (_jsx("span", { className: "min-w-0 truncate", title: chip, children: chip }, index))), model ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "footer-divider" }), _jsx("span", { className: "font-mono font-semibold", children: formatModelIdentity(model) })] })) : null, context ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "footer-divider" }), _jsx("span", { className: "font-mono", title: "Context used / model context", children: formatContextUsage(context) })] })) : null, usage ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "footer-divider" }), _jsx("span", { className: "font-mono", children: formatTokens(usage.tokens) }), _jsx("span", { className: "footer-divider" }), _jsx("span", { className: "font-mono", children: formatMoney(usage.cost) })] })) : null] }));
241
+ // Admission must not replace an outstanding owner attention readout.
242
+ const label = sessionReadout("idle", viewState);
243
+ const needsAttention = viewState.kind === "waiting_for_you" || viewState.kind === "error";
244
+ return (_jsxs("div", { className: "composer-footer", children: [needsAttention ? (_jsx(Status, { label: label, title: label, tone: tone })) : null, statusChips?.map((chip, index) => (_jsx("span", { className: "min-w-0 truncate", title: chip, children: chip }, index))), model ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "footer-divider" }), _jsx("span", { className: "font-mono font-semibold", children: formatModelIdentity(model) })] })) : null, context ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "footer-divider" }), _jsx("span", { className: "font-mono", title: "Context used / model context", children: formatContextUsage(context) })] })) : null, usage ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "footer-divider" }), _jsx("span", { className: "font-mono", children: formatTokens(usage.tokens) }), _jsx("span", { className: "footer-divider" }), _jsx("span", { className: "font-mono", children: formatMoney(usage.cost) })] })) : null] }));
184
245
  }
185
- export const ChatComposer = memo(function ChatComposer({ placeholder, showFooter = true, statusChips, actionVariant = "primary", draft, failure, failureCause, admissionError, model, onAbort, onChange, onRetry, onSend, status, viewState, textareaRef, usage, context, slash, }) {
246
+ export const ChatComposer = memo(function ChatComposer({ queuedRecall, placeholder, showFooter = true, statusChips, actionVariant = "primary", draft, failure, failureCause, admissionError, model, onAbort, onChange, onRetry, onSend, status, viewState, textareaRef, usage, context, slash, }) {
186
247
  const textareaId = useId();
187
248
  const slashListId = `${textareaId}-commands`;
188
249
  const { busy } = presentations[status];
189
- const composerAction = busy ? (_jsxs("div", { className: "flex items-stretch", children: [_jsx(Button, { "aria-label": "Stop current turn", className: "join-item h-auto min-h-0 self-stretch", "data-testid": "composer-stop", disabled: status === "queued" || status === "cancelling", onClick: onAbort, title: "Stop the current turn. Queued messages keep running in order.", size: "lg", variant: "ghost", children: "Stop" }), _jsx(Button, { "aria-label": "Queue follow-up", className: "join-item h-auto min-h-0 self-stretch", disabled: draft.trim().length === 0, onClick: () => {
250
+ const recallBusy = queuedRecall?.recallState.kind === "recalling";
251
+ const cannotSend = recallBusy ||
252
+ (draft.trim().length === 0 && (queuedRecall?.images?.length ?? 0) === 0);
253
+ const composerAction = busy ? (_jsxs("div", { className: "flex items-stretch", children: [_jsx(Button, { "aria-label": "Stop current turn", className: "join-item h-auto min-h-0 self-stretch", "data-testid": "composer-stop", disabled: recallBusy || status === "queued" || status === "cancelling", onClick: onAbort, title: "Stop the current turn. Queued messages keep running in order.", size: "lg", variant: "ghost", children: "Stop" }), _jsx(Button, { "aria-label": "Queue follow-up", className: "join-item h-auto min-h-0 self-stretch", disabled: cannotSend, onClick: () => {
190
254
  onSend("followUp");
191
- }, size: "lg", variant: actionVariant, children: "Queue" })] })) : (_jsx(Button, { "aria-label": "Send message", className: "join-item h-auto min-h-0 self-stretch", disabled: draft.trim().length === 0, onClick: () => {
255
+ }, size: "lg", variant: actionVariant, children: "Queue" })] })) : (_jsx(Button, { "aria-label": "Send message", className: "join-item h-auto min-h-0 self-stretch", disabled: cannotSend, onClick: () => {
192
256
  onSend("steer");
193
257
  }, title: "Send", size: "lg", variant: actionVariant, children: "Send" }));
194
- const composerInput = (_jsx(HostComposerTextarea, { placeholder: placeholder, onChange: onChange, onAbort: onAbort, onSend: () => {
258
+ const composerInput = (_jsx(HostComposerTextarea, { queuedRecall: queuedRecall, placeholder: placeholder, onChange: onChange, onAbort: onAbort, onSend: () => {
195
259
  onSend(busy ? "followUp" : "steer");
196
260
  }, slash: slash, slashListId: slashListId, textareaId: textareaId, textareaRef: textareaRef, value: draft }));
197
261
  const composerProps = {
@@ -205,5 +269,5 @@ export const ChatComposer = memo(function ChatComposer({ placeholder, showFooter
205
269
  footerProps.usage = usage;
206
270
  if (context)
207
271
  footerProps.context = context;
208
- return (_jsxs("div", { className: "chat-composer not-prose flex-shrink-0 border-t border-base-300", children: [_jsxs("div", { className: "relative", children: [admissionError === "queue_full" ? (_jsx("div", { className: "px-3 py-1.5 text-xs text-error", role: "alert", children: "Too many prompts are still awaiting admission. Try again shortly." })) : null, failure !== undefined ? (_jsx(RunFailureNotice, { failure: failure, cause: failureCause, onRetry: onRetry })) : null, slash?.interactions.notice === undefined ? null : (_jsx("div", { className: "parakeet-command-notice px-3 py-1.5 text-xs", "data-tone": slash.interactions.notice.tone, role: slash.interactions.notice.tone === "error" ? "alert" : "status", children: slash.interactions.notice.message })), _jsxs(PopoverAnchor, { className: "composer-body", children: [slash === undefined ? null : (_jsx(Popover, { open: slash.open, side: "top", "data-parakeet-slash": "", children: _jsx(SlashMenu, { slash: slash, listId: slashListId }) })), _jsx(Composer, { ...composerProps })] })] }), showFooter ? _jsx(ComposerFooter, { ...footerProps }) : null, slash === undefined ? null : (_jsx("p", { className: "parakeet-key-hints", "aria-live": "polite", children: formatHints(slash.hints) }))] }));
272
+ return (_jsxs("div", { className: "chat-composer not-prose flex-shrink-0 border-t border-base-300", children: [_jsxs("div", { className: "relative", children: [admissionError === "queue_full" ? (_jsx("div", { className: "px-3 py-1.5 text-xs text-error", role: "alert", children: "Too many prompts are still awaiting admission. Try again shortly." })) : null, failure !== undefined ? (_jsx(RunFailureNotice, { failure: failure, cause: failureCause, onRetry: onRetry })) : null, slash?.interactions.notice === undefined ? null : (_jsx("div", { className: "parakeet-command-notice px-3 py-1.5 text-xs", "data-tone": slash.interactions.notice.tone, role: slash.interactions.notice.tone === "error" ? "alert" : "status", children: slash.interactions.notice.message })), _jsxs(PopoverAnchor, { className: "composer-body", children: [slash === undefined ? null : (_jsx(Popover, { open: slash.open, side: "top", "data-parakeet-slash": "", children: _jsx(SlashMenu, { slash: slash, listId: slashListId }) })), _jsx(Composer, { ...composerProps })] })] }), showFooter ? _jsx(ComposerFooter, { ...footerProps }) : null, slash === undefined ? null : (_jsx("p", { className: "sr-only", id: `${textareaId}-guidance`, children: formatHints(slash.hints) }))] }));
209
273
  });
@@ -1,3 +1,7 @@
1
+ /** User-message admission state, shared by full and compact transcript rows. */
2
+ export declare function PendingMessageStatus({ pending, }: Readonly<{
3
+ pending: boolean | undefined;
4
+ }>): import("react/jsx-runtime").JSX.Element | null;
1
5
  export declare function ChatMessage({ children, isError, pending, speaker, thinking, markdownPolicy, }: Readonly<{
2
6
  children: string;
3
7
  isError?: boolean;
@@ -1,13 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { MarkdownRenderer } from "./MarkdownRenderer.js";
3
3
  import { ThinkingBlock } from "./ThinkingBlock.js";
4
+ /** User-message admission state, shared by full and compact transcript rows. */
5
+ export function PendingMessageStatus({ pending, }) {
6
+ return pending ? (_jsx("span", { className: "sr-only", role: "status", children: "Sending" })) : null;
7
+ }
4
8
  export function ChatMessage({ children, isError, pending, speaker, thinking, markdownPolicy, }) {
5
9
  const content = (_jsx(MarkdownRenderer, { policy: markdownPolicy, children: children }));
6
10
  if (speaker === "user") {
7
- return (_jsx("div", { className: `px-3 py-2 transition-opacity motion-reduce:transition-none ${pending ? "opacity-50" : ""}`, "data-pending": pending ? "true" : undefined, "data-speaker": "user", style: {
11
+ return (_jsxs("div", { className: `px-3 py-2 transition-opacity motion-reduce:transition-none ${pending ? "opacity-50" : ""}`, "data-pending": pending ? "true" : undefined, "data-speaker": "user", style: {
8
12
  background: "var(--stipe-surface)",
9
13
  borderLeft: "3px solid var(--color-primary)",
10
- }, children: _jsx("div", { className: "message-body text-base-content", children: content }) }));
14
+ }, children: [_jsx(PendingMessageStatus, { pending: pending }), _jsx("div", { className: "message-body text-base-content", children: content })] }));
11
15
  }
12
16
  const hasText = children.trim().length > 0;
13
17
  if (!hasText && !thinking)
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button } from "@fungi.computer/caps/components/button";
3
3
  import { DataList, DataRow, DataListHeading, } from "@fungi.computer/caps/components/data-list";
4
4
  import { Status } from "@fungi.computer/caps/components/status";
@@ -17,7 +17,7 @@ export function ClientRows({ entryId, rows, actions, onOpen, }) {
17
17
  const { inspection } = useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot);
18
18
  if (rows.length === 0)
19
19
  return null;
20
- return (_jsx("div", { className: "px-3 text-xs text-base-content", "data-client-entry": entryId, children: rows.map((row) => (_jsxs("div", { "data-client-row": row.id, className: "py-1", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsxs("details", { className: "min-w-0 flex-1", children: [_jsxs("summary", { className: `cursor-pointer break-words leading-4 ${row.tone === undefined ? "" : rowColors[row.tone]}`, children: [row.title, row.inspection === undefined || row.detail === undefined
20
+ return (_jsx("div", { className: "px-3 text-xs text-base-content", "data-client-entry": entryId, children: rows.map((row) => (_jsxs("div", { "data-client-row": row.id, className: "py-1", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { "aria-hidden": "true", "data-client-row-dot": "", className: `w-3 shrink-0 text-center leading-4 ${row.tone === undefined ? "" : rowColors[row.tone]}`, children: row.tone === undefined ? "" : "●" }), _jsxs("details", { className: "min-w-0 flex-1", children: [_jsxs("summary", { className: `cursor-pointer break-words leading-4 ${row.tone === undefined ? "" : rowColors[row.tone]}`, children: [row.title, row.inspection === undefined || row.detail === undefined
21
21
  ? null
22
22
  : ` · ${row.detail}`] }), row.detail === undefined ? null : (_jsx("p", { className: "max-h-32 overflow-y-auto overscroll-contain whitespace-pre-wrap break-words pb-2", children: row.detail }))] }), row.open === undefined ? null : (_jsx(Button, { size: "xs", variant: "outline", disabled: onOpen === undefined, onClick: () => {
23
23
  if (row.open !== undefined)
@@ -1,15 +1,13 @@
1
1
  import type { WhistleInteractionHost } from "@fungi.computer/whistle/interaction";
2
- /** The interaction to draw and the page's close and retry callbacks. */
2
+ /** The interaction to draw and the page's close callback. */
3
3
  export type InteractionPanelProps = Readonly<{
4
4
  /** The renderer-neutral interaction store. */
5
5
  host: WhistleInteractionHost;
6
6
  /** Close a settled interaction. */
7
7
  onClose?: () => void;
8
- /** Run the same command again after a failure. */
9
- onRetry?: () => void;
10
8
  }>;
11
9
  /**
12
10
  * Browser renderer for one Whistle interaction. It draws the same host
13
11
  * snapshot the terminal draws; the command, not the page, defines the steps.
14
12
  */
15
- export declare function InteractionPanel({ host, onClose, onRetry, }: InteractionPanelProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function InteractionPanel({ host, onClose }: InteractionPanelProps): import("react/jsx-runtime").JSX.Element;
@@ -29,7 +29,7 @@ function PromptView({ host, pending, }) {
29
29
  * Browser renderer for one Whistle interaction. It draws the same host
30
30
  * snapshot the terminal draws; the command, not the page, defines the steps.
31
31
  */
32
- export function InteractionPanel({ host, onClose, onRetry, }) {
32
+ export function InteractionPanel({ host, onClose }) {
33
33
  const state = useSyncExternalStore(host.subscribe, host.snapshot);
34
34
  const active = state.status === "working" || state.status === "prompting";
35
35
  return (_jsxs("section", { "aria-label": state.title, className: "flex flex-col gap-sm", "data-status": state.status, children: [_jsx("h2", { className: "text-lg font-semibold", children: state.title }), state.handoff === undefined ? null : (_jsx(Display, { density: "compact", children: _jsxs("div", { className: "flex flex-col gap-xs p-sm", children: [_jsx("span", { className: "text-sm", children: state.handoff.message ??
@@ -38,5 +38,5 @@ export function InteractionPanel({ host, onClose, onRetry, }) {
38
38
  : "Open this page and enter the code") }), _jsx("a", { className: "link break-all font-mono", href: state.handoff.url, rel: "noreferrer", target: "_blank", children: state.handoff.url }), state.handoff.code === undefined ? null : (_jsx("span", { className: "font-mono text-2xl tracking-widest", children: state.handoff.code }))] }) })), state.message === undefined || state.status === "failed" ? null : (_jsxs("p", { className: "flex items-center gap-xs", children: [_jsx(Status, { tone: state.message.type === "success" ? "success" : "info", motion: state.message.type === "progress" && active ? "pulse" : "none" }), state.message.message] })), state.links.map((link) => (_jsx("a", { className: "link break-all", href: link.url, rel: "noreferrer", target: "_blank", children: link.label ?? link.url }, link.url))), state.prompt === undefined ? null : (_jsx(PromptView, { host: host, pending: state.prompt }, state.prompt.sequence)), state.status === "failed" ? (_jsx(Alert, { tone: "error", title: "That did not work", description: state.failure })) : null, _jsxs("div", { className: "flex justify-end gap-xs", children: [active ? (_jsx(Button, { variant: "ghost", onClick: host.cancel, children: "Cancel" })) : null, state.status === "failed" ? (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", onClick: () => {
39
39
  host.cancel();
40
40
  onClose?.();
41
- }, children: "Close" }), onRetry === undefined ? null : (_jsx(Button, { onClick: onRetry, children: "Try again" }))] })) : null, state.status === "done" || state.status === "cancelled" ? (_jsx(Button, { onClick: onClose, children: "Done" })) : null] })] }));
41
+ }, children: "Close" }), !state.retryable ? null : (_jsx(Button, { onClick: host.retry, children: "Try again" }))] })) : null, state.status === "done" || state.status === "cancelled" ? (_jsx(Button, { onClick: onClose, children: "Done" })) : null] })] }));
42
42
  }
@@ -1,14 +1,16 @@
1
1
  import type { Avatar } from "@fungi.computer/sprite/avatar";
2
2
  import { type AvatarPresentation, type SpriteService } from "@fungi.computer/sprite";
3
- /** The current activity and expression supplied to a composer avatar. */
3
+ /** The current activity and expression supplied to a live avatar. */
4
4
  export type AvatarState = Readonly<Pick<AvatarPresentation, "activity" | "expression">>;
5
5
  /**
6
- * Shiitake's sprite head, drawn in the activity and expression it is given.
7
- * With no `avatar`, this is the product mascot, exactly as before avatars
8
- * existed: no seeded colour, the character's own defaults.
6
+ * A sprite head drawn in the live activity and expression it is given. With
7
+ * an `avatar` it draws that look (a member's own, in their settings); with
8
+ * none it is Shiitake, the product mascot, in the character's own defaults.
9
+ * Conversations have no avatar: an Agent's head is `AgentHead`, drawn from
10
+ * the Agent record.
9
11
  */
10
12
  export declare const ShiitakeAvatar: import("react").NamedExoticComponent<{
11
- /** The open conversation's avatar; omitted or `null` draws the mascot. */
13
+ /** The look to draw; omitted or `null` draws the mascot. */
12
14
  avatar?: Avatar | null;
13
15
  /** Replaces the default 3rem size, for a host that sizes the head itself. */
14
16
  className?: string;
@@ -5,12 +5,14 @@ import { memo, useMemo } from "react";
5
5
  import { avatarPresentation } from "./avatar-presentation.js";
6
6
  import { shiitakeSprite } from "./sprite-service.js";
7
7
  /**
8
- * Shiitake's sprite head, drawn in the activity and expression it is given.
9
- * With no `avatar`, this is the product mascot, exactly as before avatars
10
- * existed: no seeded colour, the character's own defaults.
8
+ * A sprite head drawn in the live activity and expression it is given. With
9
+ * an `avatar` it draws that look (a member's own, in their settings); with
10
+ * none it is Shiitake, the product mascot, in the character's own defaults.
11
+ * Conversations have no avatar: an Agent's head is `AgentHead`, drawn from
12
+ * the Agent record.
11
13
  */
12
14
  export const ShiitakeAvatar = memo(function ShiitakeAvatar({ avatar, className = "h-12 w-12 shrink-0", service = shiitakeSprite, state, }) {
13
- const presentation = useMemo(() => (avatar ? avatarPresentation(avatar, "shiitake-mascot") : undefined), [avatar]);
15
+ const presentation = useMemo(() => (avatar ? avatarPresentation(avatar) : undefined), [avatar]);
14
16
  return (_jsx(Sprite, { "aria-hidden": "true", className: className, service: service, src: presentation?.src ?? { local: "mushroom" }, ...(presentation
15
17
  ? {
16
18
  appearance: presentation.appearance,
@@ -5,6 +5,8 @@ import { type ToggleToolDisclosure } from "./ToolGroup.js";
5
5
  export declare const TimelineItem: import("react").NamedExoticComponent<Readonly<{
6
6
  item: TimelineRow;
7
7
  onDismissFailed?: ((requestId: string) => void) | undefined;
8
+ onCancelQueued?: ((commandId: string) => void) | undefined;
9
+ cancellingQueued?: boolean | undefined;
8
10
  renderToolActions?: ((item: ToolItem) => ReactNode) | undefined;
9
11
  renderAssistantAttachment?: ((text: string) => ReactNode) | undefined;
10
12
  formatAssistantText?: ((text: string) => string) | undefined;
@@ -1,19 +1,45 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Sigil } from "@fungi.computer/sigils";
3
+ import { Button } from "@fungi.computer/caps/components/button";
2
4
  import { memo } from "react";
3
- import { initialToolDisclosure, } from "../core/tool-groups.js";
5
+ import { disclosureOpen, initialToolDisclosure, } from "../core/tool-groups.js";
4
6
  import { assistantMessageVisible } from "../core/assistant-visibility.js";
5
7
  import { formatTokenCount } from "../core/format-context.js";
6
- import { ChatMessage } from "./ChatMessage.js";
8
+ import { ChatMessage, PendingMessageStatus } from "./ChatMessage.js";
7
9
  import { ToolGroup } from "./ToolGroup.js";
8
10
  const ignoreToggle = () => undefined;
9
- export const TimelineItem = memo(function TimelineItem({ item, onDismissFailed, renderToolActions, renderAssistantAttachment, formatAssistantText, toolDisclosure = initialToolDisclosure, onToggleTool = ignoreToggle, }) {
11
+ const compactionNoticeLabel = {
12
+ nothing_to_compact: "nothing to compact",
13
+ failed: "compaction failed",
14
+ stale: "compaction skipped: the context moved on",
15
+ insufficient: "compaction skipped: the summary would not fit",
16
+ };
17
+ export const TimelineItem = memo(function TimelineItem({ item, onDismissFailed, onCancelQueued, cancellingQueued = false, renderToolActions, renderAssistantAttachment, formatAssistantText, toolDisclosure = initialToolDisclosure, onToggleTool = ignoreToggle, }) {
10
18
  switch (item.kind) {
11
19
  case "user_message": {
20
+ const pending = item.status === "pending" || item.status === "queued";
12
21
  const requestId = item.requestId;
22
+ const commandId = item.commandId;
23
+ const cancelAction = item.status === "queued" &&
24
+ commandId !== undefined &&
25
+ onCancelQueued !== undefined ? (_jsx("div", { className: "flex justify-end px-3", children: _jsx(Button, { "aria-label": "Cancel queued message", disabled: cancellingQueued, size: "sm", variant: "ghost", onClick: () => {
26
+ onCancelQueued(commandId);
27
+ }, children: "Cancel" }) })) : null;
13
28
  const dismissAction = requestId === undefined || onDismissFailed === undefined ? null : (_jsx("button", { "aria-label": "Dismiss failed prompt", className: "btn btn-ghost btn-xs", onClick: () => {
14
29
  onDismissFailed(requestId);
15
30
  }, type: "button", children: "Dismiss" }));
16
- return (_jsxs(_Fragment, { children: [item.author ? (_jsx("div", { className: "px-3 text-xs text-dim", "data-author": item.author.id, children: item.author.name })) : null, _jsx(ChatMessage, { pending: item.status !== "confirmed", speaker: "user", children: item.text }), item.status === "failed" ? (_jsxs("div", { className: "flex items-center gap-2 px-3 text-xs text-error", role: "alert", children: [_jsx("span", { children: "Your prompt didn\u2019t make it through." }), dismissAction] })) : null] }));
31
+ const failed = item.status === "failed" ? (_jsxs("div", { className: "flex items-center gap-2 px-3 text-xs text-error", role: "alert", children: [_jsx("span", { children: item.author?.kind === "agent"
32
+ ? "That Agent message didn’t make it through."
33
+ : "Your prompt didn’t make it through." }), dismissAction] })) : null;
34
+ if (item.author?.kind === "agent") {
35
+ const open = disclosureOpen(toolDisclosure, item.id, false);
36
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: item.status === "confirmed"
37
+ ? "px-3 text-xs"
38
+ : "px-3 text-xs opacity-60", "data-agent-message": item.author.id, "data-author": item.author.id, children: [_jsx(PendingMessageStatus, { pending: pending }), _jsxs("button", { "aria-expanded": open, className: "text-dim flex w-full min-w-0 items-center gap-sm text-left", onClick: () => {
39
+ onToggleTool(item.id, false);
40
+ }, type: "button", children: [_jsx("span", { className: "flex shrink-0", children: _jsx(Sigil, { name: "agent", size: 16 }) }), _jsx("span", { className: "shrink-0", children: item.author.name }), open ? null : (_jsxs("span", { className: "min-w-0 truncate", children: ["\u00B7 ", item.text] }))] }), open ? (_jsx("div", { className: "whitespace-pre-wrap break-words pl-3", children: item.text })) : null] }), failed, cancelAction] }));
41
+ }
42
+ return (_jsxs(_Fragment, { children: [item.author ? (_jsx("div", { className: "px-3 text-xs text-dim", "data-author": item.author.id, children: item.author.name })) : null, _jsx(ChatMessage, { pending: pending, speaker: "user", children: item.text }), failed, cancelAction] }));
17
43
  }
18
44
  case "assistant_message":
19
45
  if (!assistantMessageVisible(item))
@@ -28,8 +54,6 @@ export const TimelineItem = memo(function TimelineItem({ item, onDismissFailed,
28
54
  ? "compacted · the agent’s memory starts below"
29
55
  : `compacted ${formatTokenCount(item.tokensBefore)} → ${formatTokenCount(item.tokensAfter)}` }));
30
56
  case "compaction_notice":
31
- return (_jsxs("div", { className: `px-3 text-xs ${item.result === "failed" ? "text-error" : "text-dim"}`, role: "status", children: ["\u00B7", " ", item.result === "failed"
32
- ? "compaction failed"
33
- : "nothing to compact", item.diagnostic === null ? "" : `: ${item.diagnostic}`] }));
57
+ return (_jsxs("div", { className: `px-3 text-xs ${item.result === "failed" ? "text-error" : "text-dim"}`, role: "status", children: ["\u00B7 ", compactionNoticeLabel[item.result], item.diagnostic === null ? "" : `: ${item.diagnostic}`] }));
34
58
  }
35
59
  });
@@ -1,12 +1,10 @@
1
- import { type Avatar } from "@fungi.computer/sprite/avatar";
1
+ import type { Avatar } from "@fungi.computer/sprite/avatar";
2
2
  import type { AvatarPresentation as SpriteAvatarPresentation } from "@fungi.computer/sprite";
3
3
  /**
4
- * Sprite props for one avatar, independent of the live `activity` a composer
5
- * drives moment to moment. Every avatar-aware Parakeet component builds its
6
- * `<Sprite>` props through this one mapper, so a stored avatar and the
7
- * pre-avatar seeded look reach the canvas the same way. Unlike the Sprite
8
- * service's own `AvatarPresentation`, these are never optional: the mapper
9
- * always resolves a complete avatar, stored or legacy.
4
+ * Sprite props for one Agent's avatar, independent of the live `activity` a
5
+ * host drives moment to moment. Unlike the Sprite service's own
6
+ * `AvatarPresentation`, these are never optional: every Agent record carries
7
+ * a complete avatar.
10
8
  */
11
9
  export type AvatarSpriteProps = {
12
10
  readonly src: SpriteAvatarPresentation["src"];
@@ -14,10 +12,5 @@ export type AvatarSpriteProps = {
14
12
  readonly expression: NonNullable<SpriteAvatarPresentation["expression"]>;
15
13
  readonly renderStyle: NonNullable<SpriteAvatarPresentation["renderStyle"]>;
16
14
  };
17
- /**
18
- * Map a stored avatar (or `null`/`undefined`, meaning none is saved yet) to
19
- * Sprite props. The fallback is `legacyAvatar(seed)`: the mushroom, in the
20
- * same seeded cap colour Agents have always worn, so an existing Agent
21
- * renders byte-identically to before avatars existed.
22
- */
23
- export declare function avatarPresentation(avatar: Avatar | null | undefined, seed: string): AvatarSpriteProps;
15
+ /** Map an Agent's avatar to Sprite props. */
16
+ export declare function avatarPresentation(avatar: Avatar): AvatarSpriteProps;
@@ -1,16 +1,9 @@
1
- import { legacyAvatar } from "@fungi.computer/sprite/avatar";
2
- /**
3
- * Map a stored avatar (or `null`/`undefined`, meaning none is saved yet) to
4
- * Sprite props. The fallback is `legacyAvatar(seed)`: the mushroom, in the
5
- * same seeded cap colour Agents have always worn, so an existing Agent
6
- * renders byte-identically to before avatars existed.
7
- */
8
- export function avatarPresentation(avatar, seed) {
9
- const resolved = avatar ?? legacyAvatar(seed);
1
+ /** Map an Agent's avatar to Sprite props. */
2
+ export function avatarPresentation(avatar) {
10
3
  return {
11
- src: { local: resolved.character },
12
- appearance: resolved.appearance,
13
- expression: resolved.expression,
14
- renderStyle: resolved.renderStyle,
4
+ src: { local: avatar.character },
5
+ appearance: avatar.appearance,
6
+ expression: avatar.expression,
7
+ renderStyle: avatar.renderStyle,
15
8
  };
16
9
  }
@@ -0,0 +1,47 @@
1
+ import type { AppRoot } from "@fungi.computer/agar";
2
+ import { openAppAgent } from "@fungi.computer/agar/agents";
3
+ import { type ParakeetSession } from "./headless.js";
4
+ import type { QueryClient } from "@tanstack/react-query";
5
+ import { createShiitakeClient, type ShiitakeAgent, type ShiitakeEventSourceConstructor, type ShiitakeSession } from "@fungi.computer/shiitake/client";
6
+ export type OpenConversation<Session extends ParakeetSession = ShiitakeSession> = Readonly<{
7
+ agentId: string;
8
+ sessionId: string;
9
+ session: Session;
10
+ }>;
11
+ export type PendingSelection = Readonly<{
12
+ agentId: string;
13
+ sessionId: string;
14
+ }>;
15
+ export type InboxClient = Pick<ShiitakeAgent, "createSession" | "deleteSession" | "session" | "close">;
16
+ /** What the opening owner needs of a client: one addressable Session, closable. */
17
+ export type OpeningClient<Session extends ParakeetSession = ShiitakeSession> = Readonly<{
18
+ session: (sessionId: string) => Session;
19
+ close: () => Promise<void>;
20
+ }>;
21
+ /** The owner's own view of its services; a full {@link Services} satisfies it. */
22
+ export type OpeningServices<Session extends ParakeetSession = ShiitakeSession> = Readonly<{
23
+ openAgent: typeof openAppAgent;
24
+ createClient: (options: Parameters<typeof createShiitakeClient>[0]) => OpeningClient<Session>;
25
+ }>;
26
+ export type Services = Readonly<{
27
+ openAgent: typeof openAppAgent;
28
+ createClient: (options: Parameters<typeof createShiitakeClient>[0]) => InboxClient;
29
+ }>;
30
+ export declare function eventSourceFor(fetcher: typeof globalThis.fetch): ShiitakeEventSourceConstructor;
31
+ /** Owns the selected conversation's Agent, client and Parakeet query lifetimes. */
32
+ export declare function createConversationOpeningOwner<Session extends ParakeetSession = ShiitakeSession>(options: Readonly<{
33
+ connection: Pick<AppRoot, "capabilities"> & {
34
+ ui: Pick<AppRoot["ui"], "reportAgentSessionSelection">;
35
+ };
36
+ queryClient: QueryClient;
37
+ services: OpeningServices<Session>;
38
+ onPending: (selection: PendingSelection | null) => void;
39
+ onConversation: (conversation: OpenConversation<Session> | null) => void;
40
+ onError: (message: string | null) => void;
41
+ /** Only the primary conversation is the selection the host shell sees. */
42
+ reportsSelection?: boolean;
43
+ }>): {
44
+ select: (agentId: string, sessionId: string, fromOpenRequest?: boolean, waitForBaseline?: boolean) => void;
45
+ clear: () => void;
46
+ close: () => void;
47
+ };