@fungi.computer/parakeet 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +186 -2
  3. package/dist/account-commands.d.ts +102 -0
  4. package/dist/account-commands.js +239 -0
  5. package/dist/agent-head.d.ts +4 -0
  6. package/dist/agent-head.js +2 -0
  7. package/dist/client-contributions.d.ts +20 -0
  8. package/dist/client-contributions.js +41 -0
  9. package/dist/commands/advertised.d.ts +23 -0
  10. package/dist/commands/advertised.js +72 -0
  11. package/dist/commands/builtins.d.ts +14 -0
  12. package/dist/commands/builtins.js +91 -0
  13. package/dist/commands/hints.d.ts +36 -0
  14. package/dist/commands/hints.js +64 -0
  15. package/dist/commands/interactions.d.ts +55 -0
  16. package/dist/commands/interactions.js +101 -0
  17. package/dist/commands/run.d.ts +11 -0
  18. package/dist/commands/run.js +61 -0
  19. package/dist/commands/slash.d.ts +124 -0
  20. package/dist/commands/slash.js +182 -0
  21. package/dist/commands/use-slash-commands.d.ts +57 -0
  22. package/dist/commands/use-slash-commands.js +207 -0
  23. package/dist/components/AgentHead.d.ts +27 -0
  24. package/dist/components/AgentHead.js +53 -0
  25. package/dist/components/ChatComposer.d.ts +43 -0
  26. package/dist/components/ChatComposer.js +209 -0
  27. package/dist/components/ChatMessage.d.ts +8 -0
  28. package/dist/components/ChatMessage.js +16 -0
  29. package/dist/components/ClientRows.d.ts +11 -0
  30. package/dist/components/ClientRows.js +46 -0
  31. package/dist/components/CodeBlock.d.ts +11 -0
  32. package/dist/components/CodeBlock.js +38 -0
  33. package/dist/components/InteractionPanel.d.ts +15 -0
  34. package/dist/components/InteractionPanel.js +42 -0
  35. package/dist/components/MarkdownRenderer.d.ts +6 -0
  36. package/dist/components/MarkdownRenderer.js +48 -0
  37. package/dist/components/RunFailureNotice.d.ts +7 -0
  38. package/dist/components/RunFailureNotice.js +13 -0
  39. package/dist/components/ShiitakeAvatar.d.ts +19 -0
  40. package/dist/components/ShiitakeAvatar.js +20 -0
  41. package/dist/components/SlashMenu.d.ts +8 -0
  42. package/dist/components/SlashMenu.js +50 -0
  43. package/dist/components/ThinkingBlock.d.ts +3 -0
  44. package/dist/components/ThinkingBlock.js +18 -0
  45. package/dist/components/TimelineItem.d.ts +15 -0
  46. package/dist/components/TimelineItem.js +35 -0
  47. package/dist/components/ToolGroup.d.ts +12 -0
  48. package/dist/components/ToolGroup.js +90 -0
  49. package/dist/components/WaitingNotice.d.ts +7 -0
  50. package/dist/components/WaitingNotice.js +8 -0
  51. package/dist/components/avatar-presentation.d.ts +23 -0
  52. package/dist/components/avatar-presentation.js +16 -0
  53. package/dist/components/sprite-service.d.ts +2 -0
  54. package/dist/components/sprite-service.js +9 -0
  55. package/dist/components/still-frame-cache.d.ts +16 -0
  56. package/dist/components/still-frame-cache.js +63 -0
  57. package/dist/conversation.d.ts +164 -0
  58. package/dist/conversation.js +760 -0
  59. package/dist/core/assistant-visibility.d.ts +3 -0
  60. package/dist/core/assistant-visibility.js +4 -0
  61. package/dist/core/author-labels.d.ts +16 -0
  62. package/dist/core/author-labels.js +16 -0
  63. package/dist/core/build-timeline.d.ts +36 -0
  64. package/dist/core/build-timeline.js +328 -0
  65. package/dist/core/client-actions.d.ts +35 -0
  66. package/dist/core/client-actions.js +128 -0
  67. package/dist/core/client-contributions.d.ts +24 -0
  68. package/dist/core/client-contributions.js +115 -0
  69. package/dist/core/client-timeline.d.ts +33 -0
  70. package/dist/core/client-timeline.js +72 -0
  71. package/dist/core/format-context.d.ts +11 -0
  72. package/dist/core/format-context.js +20 -0
  73. package/dist/core/format-tokens.d.ts +2 -0
  74. package/dist/core/format-tokens.js +6 -0
  75. package/dist/core/model-identity.d.ts +3 -0
  76. package/dist/core/model-identity.js +7 -0
  77. package/dist/core/session-projection.d.ts +11 -0
  78. package/dist/core/session-projection.js +191 -0
  79. package/dist/core/session-query.d.ts +118 -0
  80. package/dist/core/session-query.js +187 -0
  81. package/dist/core/session-view-state.d.ts +66 -0
  82. package/dist/core/session-view-state.js +114 -0
  83. package/dist/core/tool-contract.d.ts +15 -0
  84. package/dist/core/tool-contract.js +3 -0
  85. package/dist/core/tool-groups.d.ts +36 -0
  86. package/dist/core/tool-groups.js +151 -0
  87. package/dist/core/tool-view.d.ts +182 -0
  88. package/dist/core/tool-view.js +618 -0
  89. package/dist/headless.d.ts +28 -0
  90. package/dist/headless.js +24 -0
  91. package/dist/index.d.ts +9 -0
  92. package/dist/index.js +4 -0
  93. package/dist/lib/failure.d.ts +2 -0
  94. package/dist/lib/failure.js +9 -0
  95. package/dist/markdown.d.ts +3 -0
  96. package/dist/markdown.js +6 -0
  97. package/dist/model-commands.d.ts +44 -0
  98. package/dist/model-commands.js +232 -0
  99. package/dist/parakeet.d.ts +77 -0
  100. package/dist/parakeet.js +276 -0
  101. package/dist/provider-choices.d.ts +8 -0
  102. package/dist/provider-choices.js +43 -0
  103. package/dist/reload-command.d.ts +43 -0
  104. package/dist/reload-command.js +72 -0
  105. package/dist/session-commands.d.ts +53 -0
  106. package/dist/session-commands.js +259 -0
  107. package/dist/session.d.ts +24 -0
  108. package/dist/session.js +1 -0
  109. package/dist/store/types.d.ts +163 -0
  110. package/dist/store/types.js +1 -0
  111. package/dist/styles.css +19156 -0
  112. package/docs/api/README.md +15 -0
  113. package/docs/api/commands/slash.md +321 -0
  114. package/docs/api/headless.md +2510 -0
  115. package/docs/api/index.md +1253 -0
  116. package/package.json +79 -3
  117. package/src/styles.css +268 -0
@@ -0,0 +1,15 @@
1
+ import { type ReactNode } from "react";
2
+ import { type ToolDisclosureState } from "../core/tool-groups.js";
3
+ import type { TimelineItem as TimelineRow, ToolItem } from "../store/types.js";
4
+ import { type ToggleToolDisclosure } from "./ToolGroup.js";
5
+ export declare const TimelineItem: import("react").NamedExoticComponent<Readonly<{
6
+ item: TimelineRow;
7
+ onDismissFailed?: ((requestId: string) => void) | undefined;
8
+ renderToolActions?: ((item: ToolItem) => ReactNode) | undefined;
9
+ renderAssistantAttachment?: ((text: string) => ReactNode) | undefined;
10
+ formatAssistantText?: ((text: string) => string) | undefined;
11
+ /** View-only open state for tool groups and calls. */
12
+ toolDisclosure?: ToolDisclosureState | undefined;
13
+ /** Flip one tool group or call. */
14
+ onToggleTool?: ToggleToolDisclosure | undefined;
15
+ }>>;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { memo } from "react";
3
+ import { initialToolDisclosure, } from "../core/tool-groups.js";
4
+ import { assistantMessageVisible } from "../core/assistant-visibility.js";
5
+ import { formatTokenCount } from "../core/format-context.js";
6
+ import { ChatMessage } from "./ChatMessage.js";
7
+ import { ToolGroup } from "./ToolGroup.js";
8
+ const ignoreToggle = () => undefined;
9
+ export const TimelineItem = memo(function TimelineItem({ item, onDismissFailed, renderToolActions, renderAssistantAttachment, formatAssistantText, toolDisclosure = initialToolDisclosure, onToggleTool = ignoreToggle, }) {
10
+ switch (item.kind) {
11
+ case "user_message": {
12
+ const requestId = item.requestId;
13
+ const dismissAction = requestId === undefined || onDismissFailed === undefined ? null : (_jsx("button", { "aria-label": "Dismiss failed prompt", className: "btn btn-ghost btn-xs", onClick: () => {
14
+ onDismissFailed(requestId);
15
+ }, 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] }));
17
+ }
18
+ case "assistant_message":
19
+ if (!assistantMessageVisible(item))
20
+ return null;
21
+ 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, { isError: item.isError === true, pending: item.pending, speaker: "agent", ...(item.thinking ? { thinking: item.thinking } : {}), children: formatAssistantText?.(item.text) ?? item.text }), !item.pending && !item.isError
22
+ ? renderAssistantAttachment?.(item.text)
23
+ : null] }));
24
+ case "tool_group":
25
+ return (_jsx(ToolGroup, { disclosure: toolDisclosure, group: item, onToggle: onToggleTool, renderToolActions: renderToolActions }));
26
+ case "compaction_divider":
27
+ return (_jsx("div", { className: "divider text-dim my-2 text-xs", role: "separator", children: item.tokensAfter === null
28
+ ? "compacted · the agent’s memory starts below"
29
+ : `compacted ${formatTokenCount(item.tokensBefore)} → ${formatTokenCount(item.tokensAfter)}` }));
30
+ 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}`] }));
34
+ }
35
+ });
@@ -0,0 +1,12 @@
1
+ import { type ReactNode } from "react";
2
+ import { type ToolDisclosureState } from "../core/tool-groups.js";
3
+ import type { ToolGroupItem, ToolItem } from "../store/types.js";
4
+ /** Flip one group or call; the caller owns the view-only disclosure state. */
5
+ export type ToggleToolDisclosure = (id: string, defaultOpen: boolean) => void;
6
+ /** Recent finished calls, or a live group, with reader-owned disclosure. */
7
+ export declare const ToolGroup: import("react").NamedExoticComponent<Readonly<{
8
+ group: ToolGroupItem;
9
+ disclosure: ToolDisclosureState;
10
+ onToggle: ToggleToolDisclosure;
11
+ renderToolActions?: ((item: ToolItem) => ReactNode) | undefined;
12
+ }>>;
@@ -0,0 +1,90 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Badge } from "@fungi.computer/caps/components/badge";
3
+ import { DiffView } from "@fungi.computer/caps/components/diff-view";
4
+ import { Disclosure } from "@fungi.computer/caps/components/disclosure";
5
+ import { Status } from "@fungi.computer/caps/components/status";
6
+ import { Sigil } from "@fungi.computer/sigils";
7
+ import { memo } from "react";
8
+ import { disclosureOpen, toolCallDefaultOpen, toolGroupDefaultOpen, visibleToolCalls, } from "../core/tool-groups.js";
9
+ import { formatToolDuration, toolStatusTone as statusTone, toolKindSigils, } from "../core/tool-view.js";
10
+ import { CodeBlock } from "./CodeBlock.js";
11
+ /** Items beyond a section's preview, unless the reader asked for all. */
12
+ function visible(items, preview, full) {
13
+ return full || items.length <= preview
14
+ ? { shown: items, hidden: 0 }
15
+ : { shown: items.slice(0, preview), hidden: items.length - preview };
16
+ }
17
+ function ShowMore({ hidden, onShowAll, }) {
18
+ if (hidden === 0)
19
+ return null;
20
+ return (_jsxs("button", { className: "btn btn-ghost btn-xs text-dim", onClick: onShowAll, type: "button", children: ["Show ", String(hidden), " more lines"] }));
21
+ }
22
+ const monoBlock = "not-prose m-0 overflow-x-auto rounded-lg border border-base-300 bg-base-200 px-sm py-xs font-mono text-[10px] leading-relaxed";
23
+ function SectionView({ section, full, onShowAll, }) {
24
+ switch (section.kind) {
25
+ case "diff": {
26
+ const { shown, hidden } = visible(section.lines, section.preview, full);
27
+ return (_jsxs("div", { className: "not-prose", children: [_jsx(DiffView, { hiddenLines: 0, lines: shown }), _jsx(ShowMore, { hidden: hidden, onShowAll: onShowAll })] }));
28
+ }
29
+ case "excerpt": {
30
+ const { shown, hidden } = visible(section.lines, section.preview, full);
31
+ return (_jsxs("div", { children: [_jsx("pre", { className: monoBlock, "data-parakeet-section": "excerpt", children: shown.map((line, index) => (_jsxs("span", { className: "block whitespace-pre", children: [line.number === undefined ? null : (_jsx("span", { className: "text-dim mr-sm inline-block w-8 select-none text-right", children: String(line.number) })), line.text] }, String(index)))) }), _jsx(ShowMore, { hidden: hidden, onShowAll: onShowAll })] }));
32
+ }
33
+ case "matches": {
34
+ const { shown, hidden } = visible(section.matches, section.preview, full);
35
+ return (_jsxs("div", { children: [_jsx("pre", { className: monoBlock, "data-parakeet-section": "matches", children: shown.map((match, index) => (_jsxs("span", { className: "block whitespace-pre", children: [match.location === "" ? null : (_jsx("span", { className: "text-info mr-sm", children: match.location })), match.text] }, String(index)))) }), _jsx(ShowMore, { hidden: hidden, onShowAll: onShowAll })] }));
36
+ }
37
+ case "command":
38
+ case "script": {
39
+ const { shown, hidden } = visible(section.lines, section.preview, full);
40
+ return (_jsxs("div", { children: [_jsx("pre", { className: monoBlock, "data-parakeet-section": section.kind, children: shown.map((line, index) => (_jsxs("span", { className: "block whitespace-pre", children: [section.kind === "command" && index === 0 ? (_jsx("span", { className: "text-dim select-none", children: "$ " })) : null, line] }, String(index)))) }), _jsx(ShowMore, { hidden: hidden, onShowAll: onShowAll })] }));
41
+ }
42
+ case "output": {
43
+ const { shown, hidden } = visible(section.lines, section.preview, full);
44
+ return (_jsxs("div", { children: [_jsx("pre", { className: monoBlock, "data-parakeet-section": "output", children: shown.map((line, index) => (_jsxs("span", { className: line.stream === "stderr"
45
+ ? "block whitespace-pre text-error"
46
+ : "block whitespace-pre", "data-stream": line.stream, children: [line.stream === "stderr" ? (_jsx("span", { className: "select-none", children: "\u258D" })) : null, line.text] }, String(index)))) }), _jsx(ShowMore, { hidden: hidden, onShowAll: onShowAll })] }));
47
+ }
48
+ case "text": {
49
+ const { shown, hidden } = visible(section.lines, section.preview, full);
50
+ return (_jsxs("div", { children: [_jsx(CodeBlock, { className: "overflow-hidden rounded-lg", content: shown.join("\n"), header: section.label, ...(section.language === undefined
51
+ ? {}
52
+ : { language: section.language }) }), _jsx(ShowMore, { hidden: hidden, onShowAll: onShowAll })] }));
53
+ }
54
+ }
55
+ }
56
+ function CallBody({ call, tool, disclosure, onToggle, }) {
57
+ const fullId = `${call.id}:full`;
58
+ const full = disclosureOpen(disclosure, fullId, false);
59
+ const showAll = () => {
60
+ onToggle(fullId, false);
61
+ };
62
+ return (_jsxs("div", { className: "space-y-1.5 py-1.5 pr-2", "data-parakeet-call-body": true, children: [call.error === undefined ? null : (_jsx("p", { className: "not-prose m-0 font-mono text-[11px] text-error", role: "alert", children: call.error })), call.sections.map((section, index) => (_jsx(SectionView, { full: full, onShowAll: showAll, section: section }, `${section.kind}:${String(index)}`))), tool?.result?.content
63
+ .filter((part) => part.type === "image")
64
+ .map((part, index) => (_jsx("img", { alt: "Tool result", className: "max-w-full", src: `data:${part.mimeType};base64,${part.data}`, style: { maxHeight: 400 } }, `${call.id}-image-${String(index)}`)))] }));
65
+ }
66
+ function CallRow({ call, tool, disclosureId, size, disclosure, onToggle, renderToolActions, }) {
67
+ const defaultOpen = toolCallDefaultOpen(call);
68
+ const open = disclosureOpen(disclosure, disclosureId, defaultOpen);
69
+ const running = call.status === "running";
70
+ return (_jsxs("div", { "data-parakeet-call": call.kind, "data-status": call.status, children: [_jsx(Disclosure, { leading: _jsx(Sigil, { name: toolKindSigils[call.kind], size: 16 }), onOpenChange: () => {
71
+ onToggle(disclosureId, defaultOpen);
72
+ }, open: open, size: size, summary: _jsxs(_Fragment, { children: [_jsx("span", { className: "text-base-content", children: running ? call.activeVerb : call.verb }), " ", _jsx("span", { className: "font-mono", children: call.label })] }), tone: call.status === "error" ? "error" : "neutral", trailing: _jsxs(_Fragment, { children: [call.exitCode === undefined ? null : (_jsxs(Badge, { size: "sm", tone: "error", children: ["exit ", String(call.exitCode)] })), call.durationMs === undefined ? null : (_jsx("span", { children: formatToolDuration(call.durationMs) })), _jsx(Status, { motion: running ? "bounce" : "none", title: call.status === "unknown" ? "result unavailable" : call.status, tone: statusTone(call.status) })] }), children: _jsx(CallBody, { call: call, disclosure: disclosure, onToggle: onToggle, tool: tool }) }), call.status === "completed" && tool?.result !== undefined
73
+ ? renderToolActions?.(tool)
74
+ : null] }));
75
+ }
76
+ /** Recent finished calls, or a live group, with reader-owned disclosure. */
77
+ export const ToolGroup = memo(function ToolGroup({ group, disclosure, onToggle, renderToolActions, }) {
78
+ const defaultOpen = toolGroupDefaultOpen(group);
79
+ const open = disclosureOpen(disclosure, group.id, defaultOpen);
80
+ const running = group.status === "running";
81
+ const active = group.active;
82
+ const single = group.calls.length === 1 ? group.calls[0] : undefined;
83
+ if (single !== undefined)
84
+ return (_jsx("div", { className: "px-1", children: _jsx(CallRow, { call: single, disclosure: disclosure, disclosureId: single.id, onToggle: onToggle, renderToolActions: renderToolActions, size: "row", tool: group.tools[0] }) }));
85
+ return (_jsxs("div", { className: "px-1", children: [_jsx(Disclosure, { className: "[&>.caps-disclosure-panel]:hidden", leading: _jsx(Sigil, { name: "list", size: 16 }), onOpenChange: () => {
86
+ onToggle(group.id, defaultOpen);
87
+ }, open: open, summary: running && active !== undefined ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-base-content", children: active.activeVerb }), " ", _jsx("span", { className: "font-mono", children: active.label }), " \u00B7", " ", String(group.calls.length), " ", group.calls.length === 1 ? "call" : "calls"] })) : (_jsxs("span", { className: "text-base-content", children: [group.summary, " \u00B7 ", String(group.calls.length), " ", group.calls.length === 1 ? "call" : "calls"] })), tone: group.status === "error" ? "error" : "neutral", trailing: _jsxs(_Fragment, { children: [group.failedCount > 0 ? (_jsxs("span", { className: "text-error", children: [String(group.failedCount), " failed"] })) : null, group.durationMs === undefined ? null : (_jsx("span", { children: formatToolDuration(group.durationMs) })), _jsx(Status, { motion: running ? "bounce" : "none", title: group.status === "unknown" ? "result unavailable" : group.status, tone: statusTone(group.status) })] }) }), visibleToolCalls(group, open).map((call) => (_jsx(CallRow, { call: call, disclosure: disclosure, disclosureId: call.id, onToggle: onToggle, renderToolActions: renderToolActions, size: "row", tool: group.tools.find((tool) => tool.id === call.id) }, call.id))), group.status === "completed" && group.calls.length > 3 ? (_jsx("button", { "aria-expanded": open, className: "btn btn-ghost btn-xs text-dim", onClick: () => {
88
+ onToggle(group.id, defaultOpen);
89
+ }, type: "button", children: open ? "Show less" : `Show all ${String(group.calls.length)} calls` })) : null] }));
90
+ });
@@ -0,0 +1,7 @@
1
+ import { type SessionViewState } from "../core/session-view-state.js";
2
+ /** A queued Session reports the executor fact; stopping a turn leaves its queue intact. */
3
+ export declare function WaitingNotice({ waiting, }: Readonly<{
4
+ waiting: Extract<SessionViewState, {
5
+ kind: "queued";
6
+ }> | null;
7
+ }>): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { queuedSessionLabel, } from "../core/session-view-state.js";
3
+ /** A queued Session reports the executor fact; stopping a turn leaves its queue intact. */
4
+ export function WaitingNotice({ waiting, }) {
5
+ if (waiting === null)
6
+ return null;
7
+ return (_jsx("div", { role: "status", className: "flex items-center justify-between gap-2 px-3 py-1.5 text-xs text-dim", "data-parakeet-waiting": "waiting", children: _jsx("span", { children: queuedSessionLabel(waiting) }) }));
8
+ }
@@ -0,0 +1,23 @@
1
+ import { type Avatar } from "@fungi.computer/sprite/avatar";
2
+ import type { AvatarPresentation as SpriteAvatarPresentation } from "@fungi.computer/sprite";
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.
10
+ */
11
+ export type AvatarSpriteProps = {
12
+ readonly src: SpriteAvatarPresentation["src"];
13
+ readonly appearance: NonNullable<SpriteAvatarPresentation["appearance"]>;
14
+ readonly expression: NonNullable<SpriteAvatarPresentation["expression"]>;
15
+ readonly renderStyle: NonNullable<SpriteAvatarPresentation["renderStyle"]>;
16
+ };
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;
@@ -0,0 +1,16 @@
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);
10
+ return {
11
+ src: { local: resolved.character },
12
+ appearance: resolved.appearance,
13
+ expression: resolved.expression,
14
+ renderStyle: resolved.renderStyle,
15
+ };
16
+ }
@@ -0,0 +1,2 @@
1
+ /** The one Sprite service Parakeet's mascot components share. */
2
+ export declare const shiitakeSprite: import("@fungi.computer/sprite").SpriteService;
@@ -0,0 +1,9 @@
1
+ import { createSprite } from "@fungi.computer/sprite";
2
+ /** The one Sprite service Parakeet's mascot components share. */
3
+ export const shiitakeSprite = createSprite({
4
+ characters: {
5
+ mushroom: async () => (await import("@fungi.computer/sprite-zdog/mushroom")).default,
6
+ cat: async () => (await import("@fungi.computer/sprite-zdog/cat")).default,
7
+ burger: async () => (await import("@fungi.computer/sprite-zdog/burger")).default,
8
+ },
9
+ });
@@ -0,0 +1,16 @@
1
+ import type { AvatarSpriteProps } from "./avatar-presentation.js";
2
+ /** The still-frame cache key for one presentation at one pixel size. */
3
+ export declare function stillFrameKey(presentation: AvatarSpriteProps, size: number, devicePixelRatio: number): string;
4
+ /** The cached still frame for `key`, refreshing its recency, or `undefined`. */
5
+ export declare function getStillFrame(key: string): string | undefined;
6
+ /** Cache `value` (a data URL) for `key`, evicting the oldest entry past 128. */
7
+ export declare function setStillFrame(key: string, value: string): void;
8
+ /**
9
+ * Capture the idle frame of a just-rendered Sprite host as a still image, or
10
+ * `undefined` when the host has no live canvas yet (not ready) or the
11
+ * canvas export is denied because the canvas is tainted. Other export failures
12
+ * propagate with their cause.
13
+ */
14
+ export declare function captureStillFrame(host: HTMLElement | null): string | undefined;
15
+ /** Test-only: clears every cached still frame so tests do not leak between runs. */
16
+ export declare function clearStillFrameCache(): void;
@@ -0,0 +1,63 @@
1
+ const MAX_ENTRIES = 128;
2
+ /**
3
+ * Idle heads shown over and over (an Agent rail) do not need their own live
4
+ * canvas: once one instance of a descriptor has rendered, every other idle
5
+ * instance can show the same still frame. Keyed by the resolved presentation
6
+ * plus the pixel size it was drawn at, so a resize or a different size
7
+ * elsewhere never serves the wrong frame. An LRU of about 128 entries; the
8
+ * oldest is evicted once that is exceeded.
9
+ */
10
+ const cache = new Map();
11
+ /** The still-frame cache key for one presentation at one pixel size. */
12
+ export function stillFrameKey(presentation, size, devicePixelRatio) {
13
+ return JSON.stringify([
14
+ presentation.src,
15
+ presentation.appearance,
16
+ presentation.expression,
17
+ presentation.renderStyle,
18
+ size,
19
+ devicePixelRatio,
20
+ ]);
21
+ }
22
+ /** The cached still frame for `key`, refreshing its recency, or `undefined`. */
23
+ export function getStillFrame(key) {
24
+ const value = cache.get(key);
25
+ if (value === undefined)
26
+ return undefined;
27
+ cache.delete(key);
28
+ cache.set(key, value);
29
+ return value;
30
+ }
31
+ /** Cache `value` (a data URL) for `key`, evicting the oldest entry past 128. */
32
+ export function setStillFrame(key, value) {
33
+ cache.delete(key);
34
+ cache.set(key, value);
35
+ if (cache.size <= MAX_ENTRIES)
36
+ return;
37
+ const oldestKey = cache.keys().next().value;
38
+ if (oldestKey !== undefined)
39
+ cache.delete(oldestKey);
40
+ }
41
+ /**
42
+ * Capture the idle frame of a just-rendered Sprite host as a still image, or
43
+ * `undefined` when the host has no live canvas yet (not ready) or the
44
+ * canvas export is denied because the canvas is tainted. Other export failures
45
+ * propagate with their cause.
46
+ */
47
+ export function captureStillFrame(host) {
48
+ const canvas = host?.querySelector("canvas");
49
+ if (!canvas)
50
+ return undefined;
51
+ try {
52
+ return canvas.toDataURL("image/png");
53
+ }
54
+ catch (cause) {
55
+ if (cause instanceof DOMException && cause.name === "SecurityError")
56
+ return undefined;
57
+ throw new Error("Could not capture an avatar still frame", { cause });
58
+ }
59
+ }
60
+ /** Test-only: clears every cached still frame so tests do not leak between runs. */
61
+ export function clearStillFrameCache() {
62
+ cache.clear();
63
+ }
@@ -0,0 +1,164 @@
1
+ import { type ParakeetAuthorOf } from "./core/author-labels.js";
2
+ import { type PromptDelivery, type SessionContext, type SessionExtensionSlots, type SessionPendingPrompts } from "@fungi.computer/shiitake/client";
3
+ import type { ClientEntryV1 } from "@fungi.computer/shiitake/client-entry";
4
+ import { type ClientContribution } from "./core/client-contributions.js";
5
+ import { type ParakeetInteractions } from "./commands/interactions.js";
6
+ import type { WhistleExecutionOutcome } from "@fungi.computer/whistle";
7
+ import { type ReactNode } from "react";
8
+ import { type SessionViewState } from "./core/session-view-state.js";
9
+ import type { ParakeetSession } from "./session.js";
10
+ import type { TimelineItem } from "./store/types.js";
11
+ /** Stable model identity presented by the current Session. */
12
+ export type ParakeetModel = Readonly<{
13
+ /** Provider model identifier. */
14
+ model: string;
15
+ /** Provider identifier. */
16
+ provider: string;
17
+ }>;
18
+ /** Aggregate usage presented by the current Session timeline. */
19
+ export type ParakeetUsage = Readonly<{
20
+ /** Accumulated provider cost. */
21
+ cost: number;
22
+ /** Accumulated token count. */
23
+ tokens: number;
24
+ }>;
25
+ /** Conversation activity presented by Parakeet. */
26
+ export type ParakeetStatus = "connecting" | "cancelling" | "error" | "idle" | "queued" | "retrying" | "sending" | "streaming" | "thinking" | "disconnected";
27
+ /** Owner activity with the connection states shown while observation opens. */
28
+ export type ParakeetSessionViewState = SessionViewState | Readonly<{
29
+ /** Session observation is opening. */
30
+ kind: "connecting";
31
+ }> | Readonly<{
32
+ /** Session observation is unavailable. */
33
+ kind: "disconnected";
34
+ /** Reason suitable for presentation. */
35
+ cause: string;
36
+ }>;
37
+ /** Health of the current Session observation. */
38
+ export type ParakeetObservation = Readonly<{
39
+ /** Observation is opening. */
40
+ status: "connecting";
41
+ }> | Readonly<{
42
+ /** Observation is current. */
43
+ status: "live";
44
+ }> | Readonly<{
45
+ /** Observation failed and can be reopened. */
46
+ status: "unavailable";
47
+ /** Sanitized reason suitable for presentation. */
48
+ reason: string;
49
+ }>;
50
+ /** Stable history and replaceable work rendered by a conversation client. */
51
+ export type TimelineController = Readonly<{
52
+ /** Durable-backed rows whose identity is stable across progress revisions. */
53
+ settled: readonly TimelineItem[];
54
+ /** Replaceable progress, queued, and optimistic rows. */
55
+ tail: readonly TimelineItem[];
56
+ /** Last actually rendered durable row for each loaded command. */
57
+ commandAnchors: ReadonlyMap<string, string>;
58
+ /** Current owner-admitted prompts, independent of the visible history page. */
59
+ queued: SessionPendingPrompts;
60
+ /** Whether more admitted prompts exist beyond this projection. */
61
+ pendingHasMore: boolean;
62
+ /** Whether older history remains, unloaded, before `settled[0]`. */
63
+ hasMoreHistory: boolean;
64
+ /** An older history page is currently loading. */
65
+ loadingOlderHistory: boolean;
66
+ /** Load the next older history page; a no-op while one is in flight. */
67
+ loadOlderHistory: () => void;
68
+ /** Whether a newer evicted page lies beyond the retained history window. */
69
+ hasNewerHistory: boolean;
70
+ /** Refetch the nearest newer page after browsing backward. */
71
+ loadNewerHistory: () => void;
72
+ /** Keep the currently read page visible while the live tail advances. */
73
+ pinVisibleHistory: () => void;
74
+ /** Return to the live page and release paged history. */
75
+ jumpToLatest: () => void;
76
+ /** Newest durable assistant model identity. */
77
+ model?: ParakeetModel | undefined;
78
+ /** Accumulated durable usage. */
79
+ usage?: ParakeetUsage | undefined;
80
+ }>;
81
+ /** Draft and prompt operations for the current conversation. */
82
+ export type ComposerController = Readonly<{
83
+ /** Stop the current turn. Queued prompts keep draining; the draft is unchanged. */
84
+ cancel: () => Promise<void>;
85
+ /** Current local input. */
86
+ draft: string;
87
+ /** Typed reason the latest prompt did not complete. */
88
+ failure: "admission" | "failed" | "cancelled" | "interrupted" | undefined;
89
+ /** Current send could not enter the bounded local admission queue. */
90
+ admissionError: "queue_full" | undefined;
91
+ /** Retry the exact most recent failed prompt. */
92
+ retry: () => void;
93
+ /** Remove one failed local admission from this conversation. */
94
+ dismissFailed: (requestId: string) => void;
95
+ /** Submit the current draft with the requested delivery intent. */
96
+ send: (delivery?: PromptDelivery) => void;
97
+ /**
98
+ * Run a host command, such as Whistle slash dispatch, for the current draft.
99
+ * Parakeet clears the draft only when the command handled it without
100
+ * declining (a refused replacement keeps the text) and the draft was
101
+ * not edited while the command ran.
102
+ */
103
+ submitCommand: (run: (draft: string) => Promise<WhistleExecutionOutcome | undefined>) => Promise<WhistleExecutionOutcome | undefined>;
104
+ /** Replace the local input. */
105
+ setDraft: (value: string) => void;
106
+ }>;
107
+ /** Session activity, observation, and Agent-lifetime controls. */
108
+ export type StatusController = Readonly<{
109
+ /** Owner-issued identity of the running or queued Session command. */
110
+ commandId: string | undefined;
111
+ /** Health of the shared Session observation. */
112
+ observation: ParakeetObservation;
113
+ /** Reopen the shared Session observation. */
114
+ reconnect: () => Promise<ParakeetReconnectOutcome>;
115
+ /** Current conversation activity. */
116
+ status: ParakeetStatus;
117
+ /** Owner state, or a typed client connection state when the owner is unreachable. */
118
+ viewState: ParakeetSessionViewState;
119
+ /** Context use as the Session's compaction measures it, once known. */
120
+ context: SessionContext | undefined;
121
+ }>;
122
+ /** Result of retrying the shared Session observation. */
123
+ export type ParakeetReconnectOutcome = Readonly<{
124
+ /** No reconnect was needed. */
125
+ kind: "nothing_to_reconnect";
126
+ /** Existing observation state. */
127
+ state: "connected" | "connecting";
128
+ }> | Readonly<{
129
+ /** Session observation reopened. */
130
+ kind: "reconnected";
131
+ }> | Readonly<{
132
+ /** Reopening failed. */
133
+ kind: "failed";
134
+ /** Reason suitable for presentation. */
135
+ cause: string;
136
+ }>;
137
+ /** Own one renderer-neutral conversation scope over the existing query cache. */
138
+ export declare function ParakeetProvider({ children, session, clients, interactionStore, authorOf, }: Readonly<{
139
+ children: ReactNode;
140
+ session: ParakeetSession;
141
+ clients?: readonly ClientEntryV1[] | undefined;
142
+ interactionStore?: ParakeetInteractions | undefined;
143
+ /**
144
+ * Read the author to show from a message's opaque host metadata. Absent,
145
+ * lines show no author.
146
+ */
147
+ authorOf?: ParakeetAuthorOf | undefined;
148
+ }>): import("react/jsx-runtime").JSX.Element;
149
+ /** Read stable history and replaceable live work. */
150
+ export declare function useTimeline(): TimelineController;
151
+ /** Read and mutate the current conversation draft and prompt. */
152
+ export declare function useComposer(): ComposerController;
153
+ /** Dismiss a failed prompt without subscribing the timeline to composer edits. */
154
+ export declare function useDismissFailedPrompt(): (requestId: string) => void;
155
+ /** The Session this conversation observes. */
156
+ export declare function useParakeetSession(): ParakeetSession;
157
+ /** Read the Session's extension slots, including advertised command catalogs. */
158
+ export declare function useExtensionSlots(): SessionExtensionSlots;
159
+ /** Read Session activity and operate its observation. */
160
+ export declare function useStatus(): StatusController;
161
+ /** Presentation admitted once from the selected Session's already observed slots. */
162
+ export declare function useClientContributions(): readonly ClientContribution[];
163
+ /** The selected Session's ordinary command interaction owner. */
164
+ export declare function useParakeetInteractions(): ParakeetInteractions;