@fungi.computer/parakeet 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/client-contributions.d.ts +1 -0
- package/dist/commands/awaiting-driver.d.ts +20 -0
- package/dist/commands/awaiting-driver.js +121 -0
- package/dist/commands/builtins.js +6 -4
- package/dist/commands/interactions.d.ts +25 -0
- package/dist/commands/interactions.js +76 -2
- package/dist/commands/run.js +3 -3
- package/dist/commands/slash.d.ts +7 -3
- package/dist/commands/slash.js +2 -0
- package/dist/commands/use-slash-commands.d.ts +13 -0
- package/dist/commands/use-slash-commands.js +58 -1
- package/dist/components/ChatComposer.d.ts +2 -0
- package/dist/components/ChatComposer.js +158 -40
- package/dist/components/ClientRows.js +42 -26
- package/dist/components/CodeBlock.js +3 -2
- package/dist/components/CompletionMenu.d.ts +54 -0
- package/dist/components/CompletionMenu.js +52 -0
- package/dist/components/ComposerModelKey.d.ts +11 -0
- package/dist/components/ComposerModelKey.js +9 -0
- package/dist/components/ComposerShell.d.ts +47 -0
- package/dist/components/ComposerShell.js +29 -0
- package/dist/components/DecisionComposer.d.ts +30 -0
- package/dist/components/DecisionComposer.js +145 -0
- package/dist/components/InteractionPanel.d.ts +10 -1
- package/dist/components/InteractionPanel.js +9 -4
- package/dist/components/SlashMenu.d.ts +1 -3
- package/dist/components/SlashMenu.js +5 -13
- package/dist/components/TimelineItem.d.ts +1 -0
- package/dist/components/TimelineItem.js +4 -4
- package/dist/components/ToolGroup.d.ts +1 -0
- package/dist/components/ToolGroup.js +8 -8
- package/dist/conversation-opening.d.ts +1 -1
- package/dist/conversation-opening.js +2 -2
- package/dist/conversation.d.ts +5 -1
- package/dist/conversation.js +84 -29
- package/dist/core/approval-decision.d.ts +198 -0
- package/dist/core/approval-decision.js +291 -0
- package/dist/core/awaiting-answer.d.ts +229 -0
- package/dist/core/awaiting-answer.js +303 -0
- package/dist/core/build-timeline.js +61 -7
- package/dist/core/client-actions.d.ts +1 -0
- package/dist/core/client-actions.js +7 -1
- package/dist/core/composer-model-name.d.ts +3 -0
- package/dist/core/composer-model-name.js +21 -0
- package/dist/core/display-text.d.ts +2 -0
- package/dist/core/display-text.js +7 -0
- package/dist/core/draft-storage.d.ts +6 -0
- package/dist/core/draft-storage.js +25 -5
- package/dist/core/session-projection.d.ts +1 -0
- package/dist/core/session-projection.js +3 -2
- package/dist/core/session-query.d.ts +8 -0
- package/dist/core/session-query.js +13 -11
- package/dist/core/session-view-state.d.ts +2 -4
- package/dist/core/session-view-state.js +27 -9
- package/dist/core/tool-groups.d.ts +3 -0
- package/dist/core/tool-groups.js +37 -1
- package/dist/core/tool-view.d.ts +6 -0
- package/dist/core/tool-view.js +33 -0
- package/dist/core/use-timeline-item-key.d.ts +3 -4
- package/dist/core/use-timeline-item-key.js +8 -14
- package/dist/headless.d.ts +10 -5
- package/dist/headless.js +9 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/parakeet.d.ts +6 -2
- package/dist/parakeet.js +24 -17
- package/dist/store/types.d.ts +14 -1
- package/dist/styles.css +911 -162
- package/dist/use-approval-awaiting.d.ts +22 -0
- package/dist/use-approval-awaiting.js +81 -0
- package/dist/use-decision.d.ts +20 -0
- package/dist/use-decision.js +79 -0
- package/docs/api/commands/slash.md +30 -26
- package/docs/api/headless.md +917 -54
- package/docs/api/index.md +166 -5
- package/package.json +17 -8
- package/src/completion-menu.css +72 -0
- package/src/composer-shell.css +125 -0
- package/src/styles.css +99 -53
- package/dist/components/WaitingNotice.d.ts +0 -7
- package/dist/components/WaitingNotice.js +0 -8
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order the items by arrival and pick the one the composer shows. A postponed
|
|
3
|
+
* item leaves the composer but stays counted: it is the footer row.
|
|
4
|
+
*/
|
|
5
|
+
export function awaitingQueue(items, later) {
|
|
6
|
+
const ordered = items
|
|
7
|
+
.map((item, index) => ({ item, index }))
|
|
8
|
+
.sort((left, right) => left.item.arrivedAt - right.item.arrivedAt || left.index - right.index)
|
|
9
|
+
.map(({ item }) => item);
|
|
10
|
+
const current = ordered.find((item) => !later.has(item.key));
|
|
11
|
+
return {
|
|
12
|
+
items: ordered,
|
|
13
|
+
current,
|
|
14
|
+
position: current === undefined ? 0 : ordered.indexOf(current) + 1,
|
|
15
|
+
total: ordered.length,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The item that owns the composer right now. An empty draft is taken over;
|
|
20
|
+
* a draft with text is never, unless the person asked for the decision.
|
|
21
|
+
*/
|
|
22
|
+
export function awaitingTakeover(awaiting, draft) {
|
|
23
|
+
return draft === "" || awaiting.forced ? awaiting.current : undefined;
|
|
24
|
+
}
|
|
25
|
+
/** The state a decision starts in. */
|
|
26
|
+
export function initialDecisionState() {
|
|
27
|
+
return {
|
|
28
|
+
highlight: 0,
|
|
29
|
+
toggled: new Set(),
|
|
30
|
+
other: "",
|
|
31
|
+
phase: "choose",
|
|
32
|
+
details: false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const unchanged = (state) => ({ state });
|
|
36
|
+
function onRow(state, prompt, highlight) {
|
|
37
|
+
return {
|
|
38
|
+
...state,
|
|
39
|
+
highlight,
|
|
40
|
+
phase: highlight === prompt.options.length ? "other" : "choose",
|
|
41
|
+
refusal: undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The one set of rules both renderers follow. Number keys only move the
|
|
46
|
+
* highlight, Enter is the only way to answer, and Esc steps back one level
|
|
47
|
+
* until it reaches the option list, where it postpones the decision.
|
|
48
|
+
*/
|
|
49
|
+
export function reduceDecision(state, intent, prompt) {
|
|
50
|
+
const optionCount = prompt.options.length;
|
|
51
|
+
const other = prompt.other;
|
|
52
|
+
const rows = optionCount + (other === undefined ? 0 : 1);
|
|
53
|
+
if (intent.type === "refused")
|
|
54
|
+
return {
|
|
55
|
+
state: { ...state, phase: "refused", refusal: intent.message },
|
|
56
|
+
};
|
|
57
|
+
if (state.phase === "sending")
|
|
58
|
+
return unchanged(state);
|
|
59
|
+
if (state.details && intent.type !== "details" && intent.type !== "escape")
|
|
60
|
+
return unchanged(state);
|
|
61
|
+
switch (intent.type) {
|
|
62
|
+
case "move":
|
|
63
|
+
return {
|
|
64
|
+
state: onRow(state, prompt, (state.highlight + intent.delta + rows) % rows),
|
|
65
|
+
};
|
|
66
|
+
case "pick":
|
|
67
|
+
return intent.number >= 1 && intent.number <= rows
|
|
68
|
+
? { state: onRow(state, prompt, intent.number - 1) }
|
|
69
|
+
: unchanged(state);
|
|
70
|
+
case "toggle": {
|
|
71
|
+
const option = prompt.options[state.highlight];
|
|
72
|
+
if (prompt.multiple !== true || option === undefined)
|
|
73
|
+
return unchanged(state);
|
|
74
|
+
const toggled = new Set(state.toggled);
|
|
75
|
+
if (!toggled.delete(option.value))
|
|
76
|
+
toggled.add(option.value);
|
|
77
|
+
return {
|
|
78
|
+
state: { ...state, toggled, phase: "choose", refusal: undefined },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
case "type":
|
|
82
|
+
case "append":
|
|
83
|
+
if (other === undefined)
|
|
84
|
+
return unchanged(state);
|
|
85
|
+
return {
|
|
86
|
+
state: {
|
|
87
|
+
...state,
|
|
88
|
+
other: (intent.type === "append"
|
|
89
|
+
? `${state.other}${intent.text}`
|
|
90
|
+
: intent.text).slice(0, other.maxLength),
|
|
91
|
+
highlight: optionCount,
|
|
92
|
+
phase: "other",
|
|
93
|
+
refusal: undefined,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
case "details":
|
|
97
|
+
return { state: { ...state, details: !state.details } };
|
|
98
|
+
case "enter": {
|
|
99
|
+
const text = state.other.trim();
|
|
100
|
+
const answerText = text === "" ? {} : { text };
|
|
101
|
+
if (prompt.multiple === true) {
|
|
102
|
+
const selected = prompt.options
|
|
103
|
+
.map((option) => option.value)
|
|
104
|
+
.filter((value) => state.toggled.has(value));
|
|
105
|
+
if (selected.length === 0 && text === "")
|
|
106
|
+
return {
|
|
107
|
+
state: onRow(state, prompt, state.highlight),
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
state: { ...state, phase: "sending", refusal: undefined },
|
|
111
|
+
effect: { type: "answer", selected, ...answerText },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const option = prompt.options[state.highlight];
|
|
115
|
+
if (option !== undefined)
|
|
116
|
+
return {
|
|
117
|
+
state: { ...state, phase: "sending", refusal: undefined },
|
|
118
|
+
effect: { type: "answer", selected: [option.value] },
|
|
119
|
+
};
|
|
120
|
+
if (text === "") {
|
|
121
|
+
if (other?.blank === undefined)
|
|
122
|
+
return { state: onRow(state, prompt, optionCount) };
|
|
123
|
+
return {
|
|
124
|
+
state: { ...state, phase: "sending", refusal: undefined },
|
|
125
|
+
effect: { type: "answer", selected: [other.blank] },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
state: { ...state, phase: "sending", refusal: undefined },
|
|
130
|
+
effect: { type: "answer", selected: [], text },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
case "escape":
|
|
134
|
+
if (state.details)
|
|
135
|
+
return { state: { ...state, details: false } };
|
|
136
|
+
if (state.phase === "other" || state.phase === "confirm")
|
|
137
|
+
return {
|
|
138
|
+
state: { ...state, highlight: 0, phase: "choose" },
|
|
139
|
+
effect: { type: "back" },
|
|
140
|
+
};
|
|
141
|
+
if (prompt.escape !== undefined)
|
|
142
|
+
return {
|
|
143
|
+
state: { ...state, phase: "sending", refusal: undefined },
|
|
144
|
+
effect: { type: "answer", selected: [prompt.escape] },
|
|
145
|
+
};
|
|
146
|
+
return { state, effect: { type: "later" } };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const noun = { question: "question", approval: "approval" };
|
|
150
|
+
/** Project a decision's state into the view every renderer draws. */
|
|
151
|
+
export function decisionView(state, prompt, context) {
|
|
152
|
+
const multiple = prompt.multiple === true;
|
|
153
|
+
const optionCount = prompt.options.length;
|
|
154
|
+
const otherText = state.other.trim();
|
|
155
|
+
const rows = [
|
|
156
|
+
...prompt.options.map((option, index) => {
|
|
157
|
+
const toggled = state.toggled.has(option.value);
|
|
158
|
+
const marker = multiple
|
|
159
|
+
? toggled
|
|
160
|
+
? "[x]"
|
|
161
|
+
: "[ ]"
|
|
162
|
+
: `${String(index + 1)}.`;
|
|
163
|
+
const caption = `${option.label}${index === 0 ? " (recommended)" : ""}`;
|
|
164
|
+
return {
|
|
165
|
+
index,
|
|
166
|
+
kind: "option",
|
|
167
|
+
marker,
|
|
168
|
+
label: option.label,
|
|
169
|
+
description: option.description,
|
|
170
|
+
recommended: index === 0,
|
|
171
|
+
highlighted: state.highlight === index,
|
|
172
|
+
toggled,
|
|
173
|
+
destructive: option.destructive === true,
|
|
174
|
+
caption,
|
|
175
|
+
text: `${marker} ${caption}`,
|
|
176
|
+
};
|
|
177
|
+
}),
|
|
178
|
+
...(prompt.other === undefined
|
|
179
|
+
? []
|
|
180
|
+
: [
|
|
181
|
+
((other) => {
|
|
182
|
+
const toggled = multiple && otherText !== "";
|
|
183
|
+
const marker = multiple
|
|
184
|
+
? toggled
|
|
185
|
+
? "[x]"
|
|
186
|
+
: "[ ]"
|
|
187
|
+
: `${String(optionCount + 1)}.`;
|
|
188
|
+
return {
|
|
189
|
+
index: optionCount,
|
|
190
|
+
kind: "other",
|
|
191
|
+
marker,
|
|
192
|
+
label: other.label,
|
|
193
|
+
description: undefined,
|
|
194
|
+
recommended: false,
|
|
195
|
+
highlighted: state.highlight === optionCount,
|
|
196
|
+
toggled,
|
|
197
|
+
destructive: false,
|
|
198
|
+
caption: other.label,
|
|
199
|
+
text: `${marker} ${other.label}`,
|
|
200
|
+
};
|
|
201
|
+
})(prompt.other),
|
|
202
|
+
]),
|
|
203
|
+
];
|
|
204
|
+
const typing = state.phase === "other";
|
|
205
|
+
const blankLabel = prompt.options
|
|
206
|
+
.find((option) => option.value === prompt.other?.blank)
|
|
207
|
+
?.label.toLowerCase();
|
|
208
|
+
const escapeLabel = prompt.options
|
|
209
|
+
.find((option) => option.value === prompt.escape)
|
|
210
|
+
?.label.toLowerCase();
|
|
211
|
+
const sending = state.phase === "sending";
|
|
212
|
+
const hasDetails = (prompt.details?.length ?? 0) > 0 || prompt.body !== undefined;
|
|
213
|
+
const hints = sending
|
|
214
|
+
? []
|
|
215
|
+
: state.details
|
|
216
|
+
? [
|
|
217
|
+
{ keys: "d", label: "back", priority: 0 },
|
|
218
|
+
{ keys: "Esc", label: "back", priority: 1 },
|
|
219
|
+
]
|
|
220
|
+
: [
|
|
221
|
+
{ keys: "↑↓", label: "move", priority: 1 },
|
|
222
|
+
...(multiple && !typing
|
|
223
|
+
? [{ keys: "Space", label: "toggle", priority: 1 }]
|
|
224
|
+
: []),
|
|
225
|
+
{
|
|
226
|
+
keys: "Enter",
|
|
227
|
+
label: typing && otherText === "" && blankLabel !== undefined
|
|
228
|
+
? blankLabel
|
|
229
|
+
: multiple || typing
|
|
230
|
+
? "send"
|
|
231
|
+
: state.highlight === optionCount
|
|
232
|
+
? "write"
|
|
233
|
+
: "choose",
|
|
234
|
+
priority: 0,
|
|
235
|
+
},
|
|
236
|
+
...(hasDetails
|
|
237
|
+
? [{ keys: typing ? "Alt+D" : "d", label: "details", priority: 2 }]
|
|
238
|
+
: []),
|
|
239
|
+
{
|
|
240
|
+
keys: "Esc",
|
|
241
|
+
label: typing ? "back" : (escapeLabel ?? "later"),
|
|
242
|
+
priority: 2,
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
return {
|
|
246
|
+
kind: context.kind,
|
|
247
|
+
label: `Agent ${noun[context.kind]} ${String(context.position)} of ${String(context.total)}`,
|
|
248
|
+
title: prompt.message,
|
|
249
|
+
counter: context.total > 1
|
|
250
|
+
? `${String(context.position)} of ${String(context.total)}`
|
|
251
|
+
: undefined,
|
|
252
|
+
summary: prompt.summary,
|
|
253
|
+
facts: prompt.facts ?? [],
|
|
254
|
+
body: prompt.body,
|
|
255
|
+
bodyLabel: prompt.bodyLabel,
|
|
256
|
+
details: prompt.details ?? [],
|
|
257
|
+
detailsOpen: state.details,
|
|
258
|
+
rows,
|
|
259
|
+
other: prompt.other === undefined
|
|
260
|
+
? undefined
|
|
261
|
+
: {
|
|
262
|
+
label: prompt.other.label,
|
|
263
|
+
placeholder: prompt.other.placeholder,
|
|
264
|
+
text: state.other,
|
|
265
|
+
focused: typing,
|
|
266
|
+
maxLength: prompt.other.maxLength,
|
|
267
|
+
},
|
|
268
|
+
multiple,
|
|
269
|
+
phase: state.phase,
|
|
270
|
+
canSend: !sending &&
|
|
271
|
+
!state.details &&
|
|
272
|
+
(multiple
|
|
273
|
+
? state.toggled.size > 0 || otherText !== ""
|
|
274
|
+
: state.highlight < optionCount ||
|
|
275
|
+
otherText !== "" ||
|
|
276
|
+
prompt.other?.blank !== undefined),
|
|
277
|
+
status: sending
|
|
278
|
+
? { tone: "info", text: "Sending…" }
|
|
279
|
+
: state.phase === "refused"
|
|
280
|
+
? { tone: "error", text: state.refusal ?? "That answer was refused." }
|
|
281
|
+
: undefined,
|
|
282
|
+
hints,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* The plain strings a renderer must show for a view, in reading order. The
|
|
287
|
+
* lockstep tests hold every surface to this list.
|
|
288
|
+
*/
|
|
289
|
+
export function decisionViewText(view) {
|
|
290
|
+
if (view.detailsOpen)
|
|
291
|
+
return [view.title];
|
|
292
|
+
return [
|
|
293
|
+
view.title,
|
|
294
|
+
...(view.counter === undefined ? [] : [view.counter]),
|
|
295
|
+
...(view.summary === undefined ? [] : [view.summary]),
|
|
296
|
+
...view.facts.map((fact) => `${fact.label} ${fact.value}`),
|
|
297
|
+
...view.rows.map((row) => row.text),
|
|
298
|
+
...(view.other === undefined || view.other.text === ""
|
|
299
|
+
? []
|
|
300
|
+
: [view.other.text]),
|
|
301
|
+
...(view.status === undefined ? [] : [view.status.text]),
|
|
302
|
+
];
|
|
303
|
+
}
|
|
@@ -53,11 +53,14 @@ function progressItem(progress) {
|
|
|
53
53
|
id: progress.messageId,
|
|
54
54
|
kind: "assistant_message",
|
|
55
55
|
pending: true,
|
|
56
|
-
text: progress.text
|
|
56
|
+
text: `${progress.textTruncated ? "…" : ""}${progress.text}`,
|
|
57
57
|
};
|
|
58
58
|
return progress.thinking === ""
|
|
59
59
|
? base
|
|
60
|
-
: {
|
|
60
|
+
: {
|
|
61
|
+
...base,
|
|
62
|
+
thinking: `${progress.thinkingTruncated ? "…" : ""}${progress.thinking}`,
|
|
63
|
+
};
|
|
61
64
|
}
|
|
62
65
|
function collectAssistantBlocks(value) {
|
|
63
66
|
const text = [];
|
|
@@ -97,11 +100,54 @@ function assistantMessageItem(id, value, blocks) {
|
|
|
97
100
|
},
|
|
98
101
|
};
|
|
99
102
|
const withProviderError = value.stopReason === "error" && value.providerError !== undefined
|
|
100
|
-
? {
|
|
103
|
+
? {
|
|
104
|
+
...item,
|
|
105
|
+
text: value.errorMessage ?? "This run couldn’t finish.",
|
|
106
|
+
providerError: value.providerError,
|
|
107
|
+
}
|
|
101
108
|
: item;
|
|
109
|
+
const withDetail = value.stopReason === "error" &&
|
|
110
|
+
value.providerError !== undefined &&
|
|
111
|
+
blocks.text.length > 0
|
|
112
|
+
? {
|
|
113
|
+
...withProviderError,
|
|
114
|
+
errorDetail: blocks.text.join("").slice(0, 160),
|
|
115
|
+
}
|
|
116
|
+
: withProviderError;
|
|
102
117
|
return blocks.thinking.length === 0
|
|
103
|
-
?
|
|
104
|
-
: { ...
|
|
118
|
+
? withDetail
|
|
119
|
+
: { ...withDetail, thinking: blocks.thinking.join("\n") };
|
|
120
|
+
}
|
|
121
|
+
/** Presentation-only folding; durable messages and their raw evidence remain intact. */
|
|
122
|
+
function collapseProviderErrors(rows, breakBefore) {
|
|
123
|
+
const items = [];
|
|
124
|
+
const aliases = new Map();
|
|
125
|
+
for (const row of rows) {
|
|
126
|
+
const prior = items.at(-1);
|
|
127
|
+
if (!breakBefore.has(row.id) &&
|
|
128
|
+
row.kind === "assistant_message" &&
|
|
129
|
+
row.isError &&
|
|
130
|
+
prior?.kind === "assistant_message" &&
|
|
131
|
+
prior.isError &&
|
|
132
|
+
row.providerError !== undefined &&
|
|
133
|
+
prior.providerError !== undefined &&
|
|
134
|
+
row.text === prior.text &&
|
|
135
|
+
row.errorDetail === prior.errorDetail &&
|
|
136
|
+
row.thinking === prior.thinking &&
|
|
137
|
+
row.author?.id === prior.author?.id &&
|
|
138
|
+
row.author?.name === prior.author?.name) {
|
|
139
|
+
items[items.length - 1] = {
|
|
140
|
+
...prior,
|
|
141
|
+
repeatCount: (prior.repeatCount ?? 1) + 1,
|
|
142
|
+
// Details show the latest response, including its request id.
|
|
143
|
+
providerError: row.providerError,
|
|
144
|
+
};
|
|
145
|
+
aliases.set(row.id, prior.id);
|
|
146
|
+
}
|
|
147
|
+
else
|
|
148
|
+
items.push(row);
|
|
149
|
+
}
|
|
150
|
+
return { items, aliases };
|
|
105
151
|
}
|
|
106
152
|
function projectAssistant(id, value, author) {
|
|
107
153
|
const blocks = collectAssistantBlocks(value);
|
|
@@ -281,11 +327,17 @@ export function buildDurableTimeline(messages, compactions = [], activity, autho
|
|
|
281
327
|
}
|
|
282
328
|
positionAnchors.set(position, anchor);
|
|
283
329
|
}
|
|
330
|
+
const collapsed = collapseProviderErrors(grouped, breakBefore);
|
|
331
|
+
for (const [command, anchor] of commandAnchors)
|
|
332
|
+
commandAnchors.set(command, collapsed.aliases.get(anchor) ?? anchor);
|
|
333
|
+
for (const [position, anchor] of positionAnchors)
|
|
334
|
+
if (anchor !== null)
|
|
335
|
+
positionAnchors.set(position, collapsed.aliases.get(anchor) ?? anchor);
|
|
284
336
|
const base = {
|
|
285
337
|
commandAnchors,
|
|
286
338
|
positionAnchors,
|
|
287
339
|
completedMessageIds: completedMessageIds(messages),
|
|
288
|
-
items:
|
|
340
|
+
items: collapsed.items,
|
|
289
341
|
commandIds,
|
|
290
342
|
requestIds: new Set(messages.flatMap((message) => message.requestId === undefined ? [] : [message.requestId])),
|
|
291
343
|
};
|
|
@@ -337,7 +389,9 @@ export function buildTimelineTail(durable, progress, queued, admissions = [], au
|
|
|
337
389
|
? "failed"
|
|
338
390
|
: admission.status === "accepted"
|
|
339
391
|
? "accepted"
|
|
340
|
-
: "
|
|
392
|
+
: admission.status === "queued-local"
|
|
393
|
+
? "queued"
|
|
394
|
+
: "pending",
|
|
341
395
|
delivery: admission.delivery,
|
|
342
396
|
text: admission.content,
|
|
343
397
|
};
|
|
@@ -22,6 +22,7 @@ export declare function createClientActions(options: Readonly<{
|
|
|
22
22
|
readonly input?: {
|
|
23
23
|
readonly [x: string]: import("effect/Schema").Json;
|
|
24
24
|
} | undefined;
|
|
25
|
+
readonly awaiting?: "question";
|
|
25
26
|
}, Readonly<{
|
|
26
27
|
/** The same advertised intent is already in flight. */
|
|
27
28
|
pending: boolean;
|
|
@@ -64,6 +64,11 @@ export function createClientActions(options) {
|
|
|
64
64
|
!advertisedActions(entry).includes(action) ||
|
|
65
65
|
(waiting && entry.value.waiting?.cancel !== action))
|
|
66
66
|
return;
|
|
67
|
+
// A decision answers in the composer; the row only asks for it.
|
|
68
|
+
if (action.awaiting !== undefined) {
|
|
69
|
+
options.interactions.awaiting.focus();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
67
72
|
const intent = key(entryId, action);
|
|
68
73
|
if (intents.get(intent)?.pending)
|
|
69
74
|
return;
|
|
@@ -76,7 +81,8 @@ export function createClientActions(options) {
|
|
|
76
81
|
};
|
|
77
82
|
if (action.input !== undefined)
|
|
78
83
|
input = { ...input, arguments: action.input };
|
|
79
|
-
|
|
84
|
+
// A client row belongs to the extension whose namespace is its entry.
|
|
85
|
+
outcome = await options.commands.execute({ namespace: entryId, id: action.id }, input);
|
|
80
86
|
}
|
|
81
87
|
catch (cause) {
|
|
82
88
|
reportFailure("client row command", cause);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ANTHROPIC_MODELS } from "@earendil-works/pi-ai/providers/anthropic.models";
|
|
2
|
+
import { CLOUDFLARE_WORKERS_AI_MODELS } from "@earendil-works/pi-ai/providers/cloudflare-workers-ai.models";
|
|
3
|
+
import { OPENAI_MODELS } from "@earendil-works/pi-ai/providers/openai.models";
|
|
4
|
+
import { OPENAI_CODEX_MODELS } from "@earendil-works/pi-ai/providers/openai-codex.models";
|
|
5
|
+
import { formatModelIdentity } from "./model-identity.js";
|
|
6
|
+
// Read the same maintained metadata as the providers, without loading their
|
|
7
|
+
// execution/auth adapters or maintaining another table of display names.
|
|
8
|
+
const names = new Map([
|
|
9
|
+
...Object.values(ANTHROPIC_MODELS),
|
|
10
|
+
...Object.values(CLOUDFLARE_WORKERS_AI_MODELS),
|
|
11
|
+
...Object.values(OPENAI_MODELS),
|
|
12
|
+
...Object.values(OPENAI_CODEX_MODELS),
|
|
13
|
+
].map((model) => [
|
|
14
|
+
formatModelIdentity({ provider: model.provider, model: model.id }),
|
|
15
|
+
model.name,
|
|
16
|
+
]));
|
|
17
|
+
/** A maintained catalog label, or the real identity rather than a guessed name. */
|
|
18
|
+
export function composerModelName(model) {
|
|
19
|
+
const identity = formatModelIdentity(model);
|
|
20
|
+
return names.get(identity) ?? identity;
|
|
21
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import stripAnsi from "strip-ansi";
|
|
2
|
+
/** Display text keeps shaping characters, never terminal programs or bidi overrides. */
|
|
3
|
+
export function sanitizeDisplayText(value) {
|
|
4
|
+
return stripAnsi(value)
|
|
5
|
+
.replace(/\r\n?/gu, "\n")
|
|
6
|
+
.replace(/[\p{Cc}\u200b\u200e\u200f\u061c\u202a-\u202e\u2066-\u2069\u2060\ufeff]/gu, (character) => character === "\n" || character === "\t" ? character : "");
|
|
7
|
+
}
|
|
@@ -12,6 +12,8 @@ export type ParakeetDraftStorage = Readonly<{
|
|
|
12
12
|
getItem: (key: string) => string | null;
|
|
13
13
|
/** Atomically replace the journal; throw if it cannot be committed. */
|
|
14
14
|
setItem: (key: string, value: string) => void;
|
|
15
|
+
/** Remove a journal, doing nothing when the key is absent. */
|
|
16
|
+
removeItem: (key: string) => void;
|
|
15
17
|
}>;
|
|
16
18
|
declare const journal: z.ZodObject<{
|
|
17
19
|
draft: z.ZodString;
|
|
@@ -39,6 +41,10 @@ declare const journal: z.ZodObject<{
|
|
|
39
41
|
}>;
|
|
40
42
|
}, z.core.$strip>>;
|
|
41
43
|
pending: z.ZodArray<z.ZodObject<{
|
|
44
|
+
status: z.ZodEnum<{
|
|
45
|
+
"queued-local": "queued-local";
|
|
46
|
+
admitting: "admitting";
|
|
47
|
+
}>;
|
|
42
48
|
requestId: z.ZodString;
|
|
43
49
|
content: z.ZodString;
|
|
44
50
|
submittedDraft: z.ZodString;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { reportFailure } from "../lib/failure.js";
|
|
2
3
|
const images = z
|
|
3
4
|
.array(z.object({
|
|
4
5
|
type: z.literal("image"),
|
|
@@ -15,6 +16,7 @@ const recalledPrompt = z.object({
|
|
|
15
16
|
delivery,
|
|
16
17
|
});
|
|
17
18
|
const pendingDraft = z.object({
|
|
19
|
+
status: z.enum(["queued-local", "admitting"]),
|
|
18
20
|
requestId: z.string().min(1),
|
|
19
21
|
content: z.string(),
|
|
20
22
|
submittedDraft: z.string(),
|
|
@@ -31,15 +33,33 @@ const journal = z.object({
|
|
|
31
33
|
});
|
|
32
34
|
/** One owner-scoped journal commits pending text and composer text together. */
|
|
33
35
|
export function draftJournal(scope, storage) {
|
|
34
|
-
const key = `fungi.parakeet.drafts.
|
|
36
|
+
const key = `fungi.parakeet.drafts.v2:${scope}`;
|
|
37
|
+
let discardObsolete = true;
|
|
35
38
|
return {
|
|
36
39
|
read() {
|
|
37
40
|
if (storage === undefined)
|
|
38
41
|
return { draft: "", pending: [] };
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
if (discardObsolete) {
|
|
43
|
+
discardObsolete = false;
|
|
44
|
+
try {
|
|
45
|
+
// Delete preview data without interpreting its superseded grammar.
|
|
46
|
+
storage.removeItem(`fungi.parakeet.drafts.v1:${scope}`);
|
|
47
|
+
}
|
|
48
|
+
catch (cause) {
|
|
49
|
+
reportFailure("discard obsolete draft journal", cause);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const saved = storage.getItem(key);
|
|
54
|
+
return saved === null
|
|
55
|
+
? { draft: "", pending: [] }
|
|
56
|
+
: journal.parse(JSON.parse(saved));
|
|
57
|
+
}
|
|
58
|
+
catch (cause) {
|
|
59
|
+
// Preview journals are disposable; a broken load must not break chat.
|
|
60
|
+
reportFailure("read saved draft", cause);
|
|
61
|
+
return { draft: "", pending: [] };
|
|
62
|
+
}
|
|
43
63
|
},
|
|
44
64
|
write(value) {
|
|
45
65
|
if (storage === undefined)
|
|
@@ -8,4 +8,5 @@ export declare function mergeTerminals(current: readonly SessionTerminal[], inco
|
|
|
8
8
|
export declare function mergeCompactions(current: readonly SessionCompactionMarker[], incoming: readonly SessionCompactionMarker[]): readonly SessionCompactionMarker[];
|
|
9
9
|
/** Accumulate compaction notices by identity; a frame never removes one. */
|
|
10
10
|
export declare function mergeCompactionNotices(current: readonly SessionCompactionNotice[], incoming: readonly SessionCompactionNotice[]): readonly SessionCompactionNotice[];
|
|
11
|
+
/** Apply one already-decoded Shiitake frame to its authoritative projection. */
|
|
11
12
|
export declare function applySessionFrame(current: SessionSnapshot, frame: SessionFrame): SessionSnapshot;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appendSessionProgress } from "@fungi.computer/shiitake/client";
|
|
1
2
|
import { sameRunningWork } from "./session-view-state.js";
|
|
2
3
|
const durableMessageIds = new WeakMap();
|
|
3
4
|
/** Derive completed durable message identity once per immutable history revision. */
|
|
@@ -103,11 +104,11 @@ function sameActivity(left, right) {
|
|
|
103
104
|
right.compaction !== null &&
|
|
104
105
|
left.compaction.trigger === right.compaction.trigger))));
|
|
105
106
|
}
|
|
106
|
-
/** Apply one already-decoded Shiitake frame to its authoritative projection. */
|
|
107
107
|
/** Extension slots are small JSON values; compare them by content. */
|
|
108
108
|
function sameExtensionSlots(next, current) {
|
|
109
109
|
return next === current || JSON.stringify(next) === JSON.stringify(current);
|
|
110
110
|
}
|
|
111
|
+
/** Apply one already-decoded Shiitake frame to its authoritative projection. */
|
|
111
112
|
export function applySessionFrame(current, frame) {
|
|
112
113
|
if (frame.kind === "reset")
|
|
113
114
|
return {
|
|
@@ -149,7 +150,7 @@ export function applySessionFrame(current, frame) {
|
|
|
149
150
|
return activity === current.activity ? current : { ...current, activity };
|
|
150
151
|
return {
|
|
151
152
|
...current,
|
|
152
|
-
progress: frame.progress,
|
|
153
|
+
progress: appendSessionProgress(current.progress, frame.progress),
|
|
153
154
|
activity,
|
|
154
155
|
};
|
|
155
156
|
}
|
|
@@ -7,6 +7,8 @@ export declare function sessionQueryOptions(session: ParakeetSession): import("@
|
|
|
7
7
|
observation: "connecting" | "live";
|
|
8
8
|
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
9
9
|
snapshot: SessionSnapshot | undefined;
|
|
10
|
+
/** Last successful watch connect or delivered frame, not a cached seed. */
|
|
11
|
+
lastReplyAt: number | undefined;
|
|
10
12
|
/** One retryable user prompt, including image cargo, survives page refreshes. */
|
|
11
13
|
latestPrompt: Readonly<{
|
|
12
14
|
/** Owner-issued identity of the original prompt command. */
|
|
@@ -21,6 +23,8 @@ export declare function sessionQueryOptions(session: ParakeetSession): import("@
|
|
|
21
23
|
observation: "connecting" | "live";
|
|
22
24
|
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
23
25
|
snapshot: SessionSnapshot | undefined;
|
|
26
|
+
/** Last successful watch connect or delivered frame, not a cached seed. */
|
|
27
|
+
lastReplyAt: number | undefined;
|
|
24
28
|
/** One retryable user prompt, including image cargo, survives page refreshes. */
|
|
25
29
|
latestPrompt: Readonly<{
|
|
26
30
|
/** Owner-issued identity of the original prompt command. */
|
|
@@ -36,6 +40,8 @@ export declare function sessionQueryOptions(session: ParakeetSession): import("@
|
|
|
36
40
|
observation: "connecting" | "live";
|
|
37
41
|
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
38
42
|
snapshot: SessionSnapshot | undefined;
|
|
43
|
+
/** Last successful watch connect or delivered frame, not a cached seed. */
|
|
44
|
+
lastReplyAt: number | undefined;
|
|
39
45
|
/** One retryable user prompt, including image cargo, survives page refreshes. */
|
|
40
46
|
latestPrompt: Readonly<{
|
|
41
47
|
/** Owner-issued identity of the original prompt command. */
|
|
@@ -51,6 +57,8 @@ export declare function sessionQueryOptions(session: ParakeetSession): import("@
|
|
|
51
57
|
observation: "connecting" | "live";
|
|
52
58
|
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
53
59
|
snapshot: SessionSnapshot | undefined;
|
|
60
|
+
/** Last successful watch connect or delivered frame, not a cached seed. */
|
|
61
|
+
lastReplyAt: number | undefined;
|
|
54
62
|
/** One retryable user prompt, including image cargo, survives page refreshes. */
|
|
55
63
|
latestPrompt: Readonly<{
|
|
56
64
|
/** Owner-issued identity of the original prompt command. */
|