@fungi.computer/parakeet 0.4.0 → 0.5.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 (55) hide show
  1. package/dist/client-contributions.d.ts +1 -0
  2. package/dist/commands/awaiting-driver.d.ts +20 -0
  3. package/dist/commands/awaiting-driver.js +121 -0
  4. package/dist/commands/builtins.js +6 -4
  5. package/dist/commands/interactions.d.ts +25 -0
  6. package/dist/commands/interactions.js +76 -2
  7. package/dist/commands/run.js +3 -3
  8. package/dist/commands/slash.d.ts +7 -3
  9. package/dist/commands/slash.js +2 -0
  10. package/dist/commands/use-slash-commands.d.ts +13 -0
  11. package/dist/commands/use-slash-commands.js +58 -1
  12. package/dist/components/ChatComposer.d.ts +2 -0
  13. package/dist/components/ChatComposer.js +150 -38
  14. package/dist/components/ClientRows.js +42 -26
  15. package/dist/components/CompletionMenu.d.ts +54 -0
  16. package/dist/components/CompletionMenu.js +52 -0
  17. package/dist/components/DecisionComposer.d.ts +30 -0
  18. package/dist/components/DecisionComposer.js +146 -0
  19. package/dist/components/InteractionPanel.d.ts +10 -1
  20. package/dist/components/InteractionPanel.js +9 -4
  21. package/dist/components/SlashMenu.d.ts +1 -3
  22. package/dist/components/SlashMenu.js +5 -13
  23. package/dist/components/TimelineItem.d.ts +1 -0
  24. package/dist/components/TimelineItem.js +3 -3
  25. package/dist/components/ToolGroup.d.ts +1 -0
  26. package/dist/components/ToolGroup.js +7 -7
  27. package/dist/conversation.d.ts +5 -1
  28. package/dist/conversation.js +72 -20
  29. package/dist/core/approval-decision.d.ts +198 -0
  30. package/dist/core/approval-decision.js +291 -0
  31. package/dist/core/awaiting-answer.d.ts +229 -0
  32. package/dist/core/awaiting-answer.js +303 -0
  33. package/dist/core/build-timeline.js +56 -5
  34. package/dist/core/client-actions.d.ts +1 -0
  35. package/dist/core/client-actions.js +7 -1
  36. package/dist/core/draft-storage.d.ts +4 -0
  37. package/dist/core/draft-storage.js +12 -5
  38. package/dist/core/session-query.d.ts +8 -0
  39. package/dist/core/session-query.js +13 -11
  40. package/dist/headless.d.ts +6 -2
  41. package/dist/headless.js +5 -1
  42. package/dist/parakeet.d.ts +6 -2
  43. package/dist/parakeet.js +15 -6
  44. package/dist/store/types.d.ts +5 -1
  45. package/dist/styles.css +395 -113
  46. package/dist/use-approval-awaiting.d.ts +22 -0
  47. package/dist/use-approval-awaiting.js +81 -0
  48. package/dist/use-decision.d.ts +20 -0
  49. package/dist/use-decision.js +79 -0
  50. package/docs/api/commands/slash.md +30 -26
  51. package/docs/api/headless.md +822 -41
  52. package/docs/api/index.md +13 -4
  53. package/package.json +13 -7
  54. package/src/completion-menu.css +71 -0
  55. package/src/styles.css +174 -46
package/docs/api/index.md CHANGED
@@ -147,12 +147,13 @@ type ComposerController = Readonly<{
147
147
  recall: () => Promise<QueuedRecallOutcome>;
148
148
  checkpointDraft: () => ParakeetDraftCheckpoint;
149
149
  failure: "admission" | "failed" | "cancelled" | "interrupted" | undefined;
150
- admissionError: "queue_full" | undefined;
150
+ admissionError: "offline_queue_full" | "queue_full" | undefined;
151
151
  retry: () => void;
152
152
  dismissFailed: (requestId) => void;
153
153
  send: (delivery?) => void;
154
154
  submitCommand: (run) => Promise<WhistleExecutionOutcome | undefined>;
155
155
  setDraft: (value) => void;
156
+ setImages: (images) => void;
156
157
  }>;
157
158
  ```
158
159
 
@@ -494,6 +495,7 @@ type ParakeetProps = Readonly<{
494
495
  clients?: readonly ClientEntryV1[];
495
496
  composerActionVariant?: "primary" | "outline";
496
497
  renderToolActions?: (item) => ReactNode;
498
+ claimsToolMedia?: (item) => boolean;
497
499
  renderAssistantAttachment?: (text) => ReactNode;
498
500
  formatAssistantText?: (text) => string;
499
501
  onLink?: (href) => boolean;
@@ -515,6 +517,7 @@ type ParakeetViewProps = Readonly<{
515
517
  visitor?: ParakeetVisitor;
516
518
  composerActionVariant?: "primary" | "outline";
517
519
  renderToolActions?: (item) => ReactNode;
520
+ claimsToolMedia?: (item) => boolean;
518
521
  renderAssistantAttachment?: (text) => ReactNode;
519
522
  formatAssistantText?: (text) => string;
520
523
  onLink?: (href) => boolean;
@@ -558,6 +561,7 @@ type ParakeetSessionCommand = {
558
561
  | Readonly<{
559
562
  status: "accepted";
560
563
  commandId: string;
564
+ sessionId?: string;
561
565
  }>
562
566
  | Readonly<{
563
567
  status: "recorded";
@@ -665,6 +669,7 @@ Return the owner-issued address of a forked Session.
665
669
  | Readonly<{
666
670
  status: "accepted";
667
671
  commandId: string;
672
+ sessionId?: string;
668
673
  }>
669
674
  | Readonly<{
670
675
  status: "recorded";
@@ -693,9 +698,10 @@ Dispatch a typed command without inventing an admission result.
693
698
  ##### Returns
694
699
 
695
700
  `Promise`\< \| `Readonly`\<\{ `status`: `"accepted"`; `commandId`: `string`;
696
- \}\> \| `Readonly`\<\{ `status`: `"recorded"`; \}\> \| `Readonly`\<\{ `status`:
697
- `"too_late"`; `commandId`: `string`; \}\> \| `Readonly`\<\{ `status`:
698
- `"unknown"`; \}\> \| `Readonly`\<\{ `sessionId`: `string`; \}\> \| `undefined`\>
701
+ `sessionId?`: `string`; \}\> \| `Readonly`\<\{ `status`: `"recorded"`; \}\> \|
702
+ `Readonly`\<\{ `status`: `"too_late"`; `commandId`: `string`; \}\> \|
703
+ `Readonly`\<\{ `status`: `"unknown"`; \}\> \| `Readonly`\<\{ `sessionId`:
704
+ `string`; \}\> \| `undefined`\>
699
705
 
700
706
  ---
701
707
 
@@ -748,6 +754,9 @@ type ParakeetAssistantMessageItem = Readonly<{
748
754
  pending: boolean;
749
755
  at?: number;
750
756
  text: string;
757
+ providerError?: string;
758
+ errorDetail?: string;
759
+ repeatCount?: number;
751
760
  thinking?: string;
752
761
  usage?: Readonly<{
753
762
  cost: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fungi.computer/parakeet",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "private": false,
6
6
  "description": "Standalone React composer for a typed Shiitake Session",
@@ -31,6 +31,11 @@
31
31
  "types": "./dist/commands/slash.d.ts",
32
32
  "import": "./dist/commands/slash.js"
33
33
  },
34
+ "./completion-menu": {
35
+ "types": "./dist/components/CompletionMenu.d.ts",
36
+ "import": "./dist/components/CompletionMenu.js"
37
+ },
38
+ "./completion-menu.css": "./src/completion-menu.css",
34
39
  "./styles.css": "./dist/styles.css",
35
40
  "./tailwind.css": "./src/styles.css"
36
41
  },
@@ -39,6 +44,7 @@
39
44
  "README.md",
40
45
  "LICENSE",
41
46
  "src/styles.css",
47
+ "src/completion-menu.css",
42
48
  "docs/api"
43
49
  ],
44
50
  "peerDependencies": {
@@ -47,17 +53,17 @@
47
53
  "react-dom": "^18.2.0 || ^19.2.0"
48
54
  },
49
55
  "dependencies": {
50
- "@fungi.computer/agar": "0.4.0",
51
- "@fungi.computer/bird-dog": "0.2.1",
56
+ "@fungi.computer/agar": "0.5.0",
57
+ "@fungi.computer/bird-dog": "0.3.0",
52
58
  "@fungi.computer/cairn": "0.1.0",
53
- "@fungi.computer/caps": "0.3.1",
59
+ "@fungi.computer/caps": "0.4.0",
54
60
  "@fungi.computer/gills": "0.2.0",
55
- "@fungi.computer/knapsack": "0.3.1",
56
- "@fungi.computer/shiitake": "0.5.0",
61
+ "@fungi.computer/knapsack": "0.4.0",
62
+ "@fungi.computer/shiitake": "0.6.0",
57
63
  "@fungi.computer/sigils": "0.2.0",
58
64
  "@fungi.computer/sprite": "0.2.0",
59
65
  "@fungi.computer/sprite-zdog": "0.2.0",
60
- "@fungi.computer/whistle": "0.2.0",
66
+ "@fungi.computer/whistle": "0.3.0",
61
67
  "@tanstack/markdown": "0.0.12",
62
68
  "@tanstack/react-virtual": "3.14.10",
63
69
  "diff": "8.0.4",
@@ -0,0 +1,71 @@
1
+ /* The shared completion menu's look: Grove's slash commands and Prints' slash and @character menus. */
2
+
3
+ /*
4
+ * The menu joins the composer surface instead of floating as a second box:
5
+ * same width and inset, flush on its top edge, and the surface's own top
6
+ * border is the one line between them. --parakeet-completion-inset is the
7
+ * distance from the menu's containing block to the surface's outer edge: an
8
+ * anchor's padding (Grove) or minus a positioned card's border (Prints).
9
+ * --parakeet-completion-face and --parakeet-completion-edge carry the
10
+ * surface's own background and border colour, so the two read as one.
11
+ */
12
+ .caps-popover[data-parakeet-slash][data-side="top"] {
13
+ inset-inline: var(--parakeet-completion-inset, 0);
14
+ bottom: calc(100% - var(--parakeet-completion-inset, 0px));
15
+ border-color: var(
16
+ --parakeet-completion-edge,
17
+ color-mix(in oklab, var(--color-base-content) 16%, transparent)
18
+ );
19
+ border-bottom-width: 0;
20
+ background: var(--parakeet-completion-face, var(--color-base-200));
21
+ border-end-start-radius: 0;
22
+ border-end-end-radius: 0;
23
+ box-shadow: none;
24
+ }
25
+ .parakeet-slash-menu .parakeet-slash-label {
26
+ min-width: 7rem;
27
+ font-family: var(--font-mono);
28
+ font-weight: 600;
29
+ }
30
+
31
+ .parakeet-slash-match {
32
+ background: none;
33
+ color: var(--color-primary);
34
+ }
35
+
36
+ .parakeet-slash-detail {
37
+ flex: 1;
38
+ min-width: 0;
39
+ overflow: hidden;
40
+ text-overflow: ellipsis;
41
+ white-space: nowrap;
42
+ color: var(--stipe-fg-muted);
43
+ }
44
+
45
+ .parakeet-slash-keys {
46
+ font-family: var(--font-mono);
47
+ font-size: 0.65rem;
48
+ color: var(--stipe-fg-muted);
49
+ }
50
+
51
+ .parakeet-slash-hint {
52
+ margin: 0;
53
+ padding: 0.25rem 0.5rem;
54
+ font-family: var(--font-mono);
55
+ font-size: 0.75rem;
56
+ color: var(--stipe-fg-muted);
57
+ }
58
+
59
+ .parakeet-slash-menu [data-disabled="true"] {
60
+ opacity: 0.5;
61
+ }
62
+
63
+ /* A row with a thumbnail centres its text on the thumbnail rather than its top. */
64
+ .parakeet-slash-menu [role="option"]:has(> .parakeet-slash-media) {
65
+ align-items: center;
66
+ }
67
+
68
+ .parakeet-slash-media {
69
+ display: flex;
70
+ flex: none;
71
+ }
package/src/styles.css CHANGED
@@ -4,6 +4,7 @@
4
4
  * and chat composition: consumers load it instead of an additional Caps sheet.
5
5
  */
6
6
  @import "@fungi.computer/caps/tailwind.css";
7
+ @import "./completion-menu.css";
7
8
 
8
9
  /* In the workspace Tailwind scans the component source; published, this
9
10
  * entry ships without it, and the compiled components in dist carry the same
@@ -13,12 +14,6 @@
13
14
 
14
15
  @plugin "@tailwindcss/typography";
15
16
 
16
- @layer components {
17
- .chat-composer {
18
- border-top: 1px solid var(--color-base-300);
19
- }
20
- }
21
-
22
17
  :root {
23
18
  --prose-max-width: 80ch;
24
19
  --tool-error-rail: #fff7a0;
@@ -43,66 +38,190 @@
43
38
  overflow-wrap: break-word;
44
39
  }
45
40
 
41
+ /* The composer is one surface, seated like the Prints composer: a Caps Card
42
+ docked at the bottom. Text is typed straight onto it, and its bottom row is
43
+ the same Caps StatusTray row Prints uses: attach, model, Settings, Send. */
46
44
  .chat-composer {
47
- background: var(--color-base-100);
48
45
  flex-shrink: 0;
46
+ padding: 0 var(--spacing-xs) var(--spacing-xs);
49
47
  }
50
48
 
51
49
  .chat-composer .composer-body {
50
+ /* The completion menu rises from the card's top edge, in the card's own
51
+ face and edge, so menu and card read as one surface. */
52
+ --parakeet-completion-inset: 0px;
53
+ --parakeet-completion-face: var(--color-base-200);
54
+ --parakeet-completion-edge: var(--stipe-hairline);
55
+ display: block;
56
+ }
57
+
58
+ /* An open completion menu sits on the card's top edge, so the card squares off to join it. */
59
+ .chat-composer .composer-body:has(> [data-parakeet-slash]) .composer-card {
60
+ border-start-start-radius: 0;
61
+ border-start-end-radius: 0;
62
+ }
63
+
64
+ .chat-composer .composer-card {
65
+ padding: var(--spacing-sm);
66
+ }
67
+
68
+ .chat-composer .composer-card .caps-composer-input {
69
+ padding: var(--spacing-xs) var(--spacing-sm) 0;
70
+ }
71
+
72
+ .chat-composer .composer-attachments {
52
73
  display: flex;
53
- align-items: center;
54
- gap: 0.5rem;
55
- padding: 0.5rem;
74
+ flex-wrap: wrap;
75
+ gap: var(--spacing-sm);
76
+ margin: 0;
77
+ padding: var(--spacing-xs) var(--spacing-sm);
78
+ list-style: none;
79
+ }
80
+
81
+ .chat-composer .composer-attachments li {
82
+ position: relative;
83
+ }
84
+
85
+ .chat-composer .composer-attachments img {
86
+ display: block;
87
+ width: 3.5rem;
88
+ height: 3.5rem;
89
+ object-fit: cover;
90
+ }
91
+
92
+ /* Remove shows on hover or focus, the way a Prints reference tile does. */
93
+ .chat-composer .composer-attachment-remove {
94
+ position: absolute;
95
+ inset-block-start: 0;
96
+ inset-inline-end: 0;
97
+ background: var(--color-base-100);
98
+ opacity: 0;
99
+ }
100
+ .chat-composer .composer-attachments li:hover .composer-attachment-remove,
101
+ .chat-composer
102
+ .composer-attachments
103
+ li:focus-within
104
+ .composer-attachment-remove {
105
+ opacity: 1;
106
+ }
107
+ @media (hover: none) {
108
+ .chat-composer .composer-attachment-remove {
109
+ opacity: 1;
110
+ }
56
111
  }
57
112
 
58
113
  .chat-composer .composer-footer {
59
- display: flex;
60
- height: 32px;
61
- align-items: center;
62
- gap: 0.5rem;
63
- padding: 0 0.75rem;
64
- background: var(--color-base-200);
65
- color: var(--color-base-content);
66
- font-family: var(--font-sans);
67
- font-size: 0.65rem;
114
+ padding: var(--spacing-xs);
68
115
  }
69
116
 
70
- .chat-composer .parakeet-slash-label {
71
- min-width: 7rem;
117
+ /* Model key and Settings chip read like Prints' model select and chip. */
118
+ .chat-composer .composer-model-key {
119
+ flex: 0 1 220px;
120
+ min-width: 0;
121
+ font-size: var(--text-xs);
122
+ }
123
+
124
+ .chat-composer .composer-model-readout,
125
+ .chat-composer .composer-status-chip {
126
+ color: var(--stipe-fg-muted);
127
+ font-size: var(--text-xs);
128
+ }
129
+
130
+ .chat-composer .composer-model-readout {
72
131
  font-family: var(--font-mono);
73
- font-weight: 600;
74
132
  }
75
133
 
76
- .chat-composer .parakeet-slash-match {
77
- background: none;
78
- color: var(--color-primary);
134
+ .chat-composer .composer-settings-chip {
135
+ display: inline-flex;
136
+ align-items: center;
137
+ gap: var(--spacing-xs);
138
+ }
139
+
140
+ .chat-composer .composer-settings-chip > svg {
141
+ width: var(--stipe-icon-size-sm);
142
+ height: var(--stipe-icon-size-sm);
79
143
  }
80
144
 
81
- .chat-composer .parakeet-slash-detail {
82
- flex: 1;
145
+ .chat-composer .composer-model-key .caps-keycap-button-label {
83
146
  min-width: 0;
84
147
  overflow: hidden;
85
148
  text-overflow: ellipsis;
86
149
  white-space: nowrap;
87
- color: var(--stipe-fg-muted);
88
150
  }
89
151
 
90
- .chat-composer .parakeet-slash-keys {
91
- font-family: var(--font-mono);
92
- font-size: 0.65rem;
93
- color: var(--stipe-fg-muted);
152
+ /* Narrow composers keep one row (attach, model, Settings, Send) like Prints:
153
+ the model key gives up width first, and Send stays one key at the end. */
154
+ .chat-composer .composer-card {
155
+ container-type: inline-size;
156
+ }
157
+ @container (max-width: 34rem) {
158
+ .chat-composer .composer-model-key {
159
+ flex: 1 1 4rem;
160
+ }
161
+ .chat-composer [data-status-tray-chip="settings"] {
162
+ flex: 0 1 auto;
163
+ min-width: 0;
164
+ }
165
+ /* Narrow, the readout steps aside; the Settings tray still holds it. */
166
+ .chat-composer .composer-readout {
167
+ display: none;
168
+ }
94
169
  }
95
170
 
96
- .chat-composer .parakeet-slash-hint {
97
- margin: 0;
98
- padding: 0.25rem 0.5rem;
99
- font-family: var(--font-mono);
100
- font-size: 0.75rem;
171
+ /* Context, tokens and cost read as quiet text beside Send, not a chip. */
172
+ .chat-composer .composer-readout {
173
+ margin-inline-start: auto;
101
174
  color: var(--stipe-fg-muted);
175
+ font-size: var(--text-xs);
176
+ line-height: 1;
177
+ font-variant-numeric: tabular-nums;
178
+ white-space: nowrap;
179
+ }
180
+
181
+ /* Send sits at the row's end, like Generate in Prints. */
182
+ .chat-composer .composer-send-key {
183
+ margin-inline-start: auto;
184
+ }
185
+ .chat-composer .composer-readout + .composer-send-key {
186
+ margin-inline-start: var(--spacing-sm);
187
+ }
188
+
189
+ /* A decision that took the composer over sits on the same surface: its
190
+ choices are rows on the composer, not a floating list box inside it. */
191
+ .chat-composer .parakeet-decision {
192
+ padding: var(--spacing-xs) var(--spacing-sm) 0;
193
+ }
194
+ /* The options are the composer's own rows, not a box inside it. Rows and
195
+ * the Details trigger keep a small hover inset and pull out by it, so their
196
+ * text shares the title's left edge. */
197
+ .chat-composer .parakeet-decision-list {
198
+ width: auto;
199
+ margin-inline: calc(-1 * var(--spacing-xs));
200
+ border: 0;
201
+ background: transparent;
202
+ box-shadow: none;
203
+ padding: 0;
204
+ }
205
+ .chat-composer .parakeet-decision .caps-disclosure-trigger {
206
+ width: auto;
207
+ margin-inline: calc(-1 * var(--spacing-xs));
208
+ padding-inline: var(--spacing-xs);
209
+ }
210
+ .chat-composer .composer-model-key {
211
+ --caps-keycap-justify: space-between;
102
212
  }
103
213
 
104
- .chat-composer [data-disabled="true"] {
105
- opacity: 0.5;
214
+ /* A decision's highlight is the reducer's, not the list's own active row. */
215
+ .parakeet-decision .parakeet-decision-list [role="option"] {
216
+ padding-inline: var(--spacing-xs);
217
+ background: transparent;
218
+ }
219
+
220
+ .parakeet-decision > [data-highlight],
221
+ .parakeet-decision
222
+ .parakeet-decision-list
223
+ [role="option"]:has([data-highlight]) {
224
+ background: var(--color-base-200);
106
225
  }
107
226
 
108
227
  .chat-composer .parakeet-command-notice[data-tone="error"] {
@@ -113,13 +232,6 @@
113
232
  color: var(--color-success);
114
233
  }
115
234
 
116
- .chat-composer .footer-divider {
117
- width: 3px;
118
- height: 3px;
119
- border-radius: 50%;
120
- background: var(--stipe-fg-muted);
121
- }
122
-
123
235
  /* TanStack Highlight's retained Catppuccin theme. The token selectors are
124
236
  emitted here with Parakeet's browser CSS instead of injected by a host. */
125
237
  html:not([data-theme]),
@@ -261,3 +373,19 @@ pre.th-code {
261
373
  .th-code {
262
374
  font-family: var(--font-mono);
263
375
  }
376
+
377
+ /* The decision's Later and Send sit on the composer's bottom edge, Send as the
378
+ same keycap the composer sends with. */
379
+ .parakeet-decision-actions {
380
+ display: flex;
381
+ align-items: center;
382
+ justify-content: flex-end;
383
+ gap: var(--spacing-xs);
384
+ --caps-keycap-width: auto;
385
+ --caps-keycap-justify: center;
386
+ --caps-keycap-padding: 0 var(--spacing-md);
387
+ --caps-keycap-height: calc(var(--size-field) * 8);
388
+ }
389
+ .parakeet-decision-actions > .caps-keycap-button {
390
+ height: var(--caps-keycap-height);
391
+ }