@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.
- package/README.md +45 -6
- package/dist/account-commands.js +3 -11
- package/dist/commands/advertised.d.ts +3 -1
- package/dist/commands/advertised.js +89 -4
- package/dist/commands/builtins.js +1 -1
- package/dist/commands/interactions.d.ts +22 -7
- package/dist/commands/interactions.js +41 -17
- package/dist/commands/run.d.ts +3 -2
- package/dist/commands/run.js +5 -2
- package/dist/commands/slash.d.ts +2 -0
- package/dist/commands/slash.js +13 -7
- package/dist/commands/use-slash-commands.js +4 -4
- package/dist/components/AgentHead.d.ts +9 -12
- package/dist/components/AgentHead.js +12 -44
- package/dist/components/ChatComposer.d.ts +3 -1
- package/dist/components/ChatComposer.js +79 -15
- package/dist/components/ChatMessage.d.ts +4 -0
- package/dist/components/ChatMessage.js +6 -2
- package/dist/components/ClientRows.js +2 -2
- package/dist/components/InteractionPanel.d.ts +2 -4
- package/dist/components/InteractionPanel.js +2 -2
- package/dist/components/ShiitakeAvatar.d.ts +7 -5
- package/dist/components/ShiitakeAvatar.js +6 -4
- package/dist/components/TimelineItem.d.ts +2 -0
- package/dist/components/TimelineItem.js +31 -7
- package/dist/components/avatar-presentation.d.ts +7 -14
- package/dist/components/avatar-presentation.js +6 -13
- package/dist/conversation-opening.d.ts +47 -0
- package/dist/conversation-opening.js +173 -0
- package/dist/conversation.d.ts +50 -5
- package/dist/conversation.js +587 -87
- package/dist/core/author-labels.d.ts +2 -0
- package/dist/core/build-timeline.d.ts +4 -2
- package/dist/core/build-timeline.js +46 -7
- package/dist/core/client-actions.js +6 -3
- package/dist/core/client-timeline.d.ts +9 -2
- package/dist/core/client-timeline.js +15 -4
- package/dist/core/draft-storage.d.ts +63 -0
- package/dist/core/draft-storage.js +50 -0
- package/dist/core/model-identity.d.ts +2 -0
- package/dist/core/model-identity.js +6 -0
- package/dist/core/queued-recall.d.ts +35 -0
- package/dist/core/queued-recall.js +138 -0
- package/dist/core/session-projection.js +17 -0
- package/dist/core/session-query.d.ts +54 -11
- package/dist/core/session-query.js +38 -12
- package/dist/core/tool-groups.d.ts +1 -1
- package/dist/core/tool-groups.js +3 -1
- package/dist/core/tool-view.d.ts +1 -1
- package/dist/core/tool-view.js +3 -2
- package/dist/core/use-timeline-item-key.d.ts +8 -0
- package/dist/core/use-timeline-item-key.js +29 -0
- package/dist/headless.d.ts +6 -4
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/model-commands.js +65 -40
- package/dist/parakeet.js +17 -18
- package/dist/provider-choices.js +10 -7
- package/dist/session-commands.d.ts +0 -9
- package/dist/session-commands.js +39 -68
- package/dist/session.d.ts +1 -1
- package/dist/store/types.d.ts +10 -4
- package/dist/styles.css +105 -117
- package/docs/api/commands/slash.md +24 -21
- package/docs/api/headless.md +375 -232
- package/docs/api/index.md +168 -60
- package/package.json +16 -10
- package/src/styles.css +4 -9
- package/dist/components/still-frame-cache.d.ts +0 -16
- 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,
|
|
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
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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,
|
|
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
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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-
|
|
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
|
-
/**
|
|
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
|
-
|
|
183
|
-
|
|
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
|
|
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:
|
|
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: "
|
|
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 (
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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" }),
|
|
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
|
|
3
|
+
/** The current activity and expression supplied to a live avatar. */
|
|
4
4
|
export type AvatarState = Readonly<Pick<AvatarPresentation, "activity" | "expression">>;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
5
|
-
* drives moment to moment.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
12
|
-
appearance:
|
|
13
|
-
expression:
|
|
14
|
-
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
|
+
};
|