kritya 0.8.39-beta → 0.8.40-beta
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 +2 -1
- package/dist/ui/App.js +41 -1
- package/dist/ui/palette.js +45 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,7 +195,8 @@ Custom `/commands` you define (see [docs/CONFIGURATION.md](docs/CONFIGURATION.md
|
|
|
195
195
|
|
|
196
196
|
`Esc` cancels a running request. `↑/↓` recalls input history. `Ctrl+O` toggles
|
|
197
197
|
full tool output. `Ctrl+E` opens the file the agent touched most recently in
|
|
198
|
-
`$EDITOR`. `Ctrl+
|
|
198
|
+
`$EDITOR`. `Ctrl+P` opens the command palette (fuzzy-find commands and
|
|
199
|
+
checkpoints). `Ctrl+K` is the kill switch (see below). `Ctrl+C` exits.
|
|
199
200
|
|
|
200
201
|
### More features
|
|
201
202
|
|
package/dist/ui/App.js
CHANGED
|
@@ -17,6 +17,7 @@ import { ModelPicker } from "./ModelPicker.js";
|
|
|
17
17
|
import { ElicitationPrompt } from "./ElicitationPrompt.js";
|
|
18
18
|
import { PermissionPrompt } from "./PermissionPrompt.js";
|
|
19
19
|
import { SelectList } from "./SelectList.js";
|
|
20
|
+
import { buildPaletteItems } from "./palette.js";
|
|
20
21
|
import { Spinner } from "./Spinner.js";
|
|
21
22
|
import { StatusLine } from "./StatusLine.js";
|
|
22
23
|
import { StreamViewport } from "./StreamViewport.js";
|
|
@@ -86,6 +87,7 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
|
|
|
86
87
|
const [inputKey, setInputKey] = useState(0);
|
|
87
88
|
const [steerInput, setSteerInput] = useState("");
|
|
88
89
|
const [resumeFilter, setResumeFilter] = useState("");
|
|
90
|
+
const [paletteFilter, setPaletteFilter] = useState("");
|
|
89
91
|
const [cmdIndex, setCmdIndex] = useState(0);
|
|
90
92
|
const [fileIndex, setFileIndex] = useState(0);
|
|
91
93
|
const [fileList, setFileList] = useState([]);
|
|
@@ -243,6 +245,19 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
|
|
|
243
245
|
engageKill("Ctrl+K");
|
|
244
246
|
return;
|
|
245
247
|
}
|
|
248
|
+
// Ctrl+P opens the command palette — fuzzy-find commands and checkpoints.
|
|
249
|
+
// Idle prompt only, like Ctrl+E: opening it mid-turn would race the agent,
|
|
250
|
+
// and the kill switch above must keep its chord unshared.
|
|
251
|
+
if (key.ctrl && _input === "p") {
|
|
252
|
+
if (phase === "input") {
|
|
253
|
+
setPaletteFilter("");
|
|
254
|
+
setPhase("palette");
|
|
255
|
+
}
|
|
256
|
+
else if (phase === "palette") {
|
|
257
|
+
setPhase("input");
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
246
261
|
if (key.escape && phase === "working") {
|
|
247
262
|
setActivity("cancelling…");
|
|
248
263
|
abortRef.current?.abort();
|
|
@@ -315,6 +330,13 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
|
|
|
315
330
|
setResumeFilter((f) => f + _input);
|
|
316
331
|
}
|
|
317
332
|
}
|
|
333
|
+
if (phase === "palette") {
|
|
334
|
+
if (key.backspace || key.delete)
|
|
335
|
+
setPaletteFilter((f) => f.slice(0, -1));
|
|
336
|
+
else if (_input && !key.upArrow && !key.downArrow && !key.return && !key.escape && !key.tab) {
|
|
337
|
+
setPaletteFilter((f) => f + _input);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
318
340
|
if (suggestions.length) {
|
|
319
341
|
if (key.upArrow)
|
|
320
342
|
setCmdIndex((i) => (i - 1 + suggestions.length) % suggestions.length);
|
|
@@ -385,6 +407,24 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
|
|
|
385
407
|
};
|
|
386
408
|
void runCommand(cmd, ctx);
|
|
387
409
|
};
|
|
410
|
+
// Command palette selection. Commands are inserted into the input line —
|
|
411
|
+
// never executed blind: a fuzzy finder that runs on Enter is how
|
|
412
|
+
// conversations get cleared by accident. Checkpoints rewind immediately;
|
|
413
|
+
// the name is complete and unambiguous, exactly like /rewind <name>.
|
|
414
|
+
const onPaletteSelect = (value) => {
|
|
415
|
+
if (value.startsWith("checkpoint:")) {
|
|
416
|
+
const name = value.slice("checkpoint:".length);
|
|
417
|
+
setPhase("input");
|
|
418
|
+
handleSlash(`/rewind ${name}`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (value.startsWith("cmd:")) {
|
|
422
|
+
setInput(`${value.slice("cmd:".length)} `);
|
|
423
|
+
setInputKey((k) => k + 1);
|
|
424
|
+
setPhase("input");
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const paletteItems = buildPaletteItems(allCommands, agent.listCheckpoints(), paletteFilter);
|
|
388
428
|
const expandMentions = async (text) => {
|
|
389
429
|
const mentions = [...new Set([...text.matchAll(MENTION_ALL_RE)].map((m) => m[1]))];
|
|
390
430
|
let extra = "";
|
|
@@ -517,7 +557,7 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
|
|
|
517
557
|
label: s.name || s.title,
|
|
518
558
|
value: s.file,
|
|
519
559
|
hint: `${s.shortId} · ${s.date} · ${s.count} msgs`,
|
|
520
|
-
})), onSelect: onResumeSelect, onCancel: () => onResumeSelect("") })] })), phase === "input" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { color: "green", children: "\u276F " }), _jsx(TextInput, { value: input, onChange: (v) => {
|
|
560
|
+
})), onSelect: onResumeSelect, onCancel: () => onResumeSelect("") })] })), phase === "palette" && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "cyan", children: ["Command palette", " ", _jsx(Text, { dimColor: true, children: "(type to filter \u00B7 \u2191\u2193 navigate \u00B7 Enter selects \u00B7 Esc closes \u00B7 Ctrl+P toggles)" })] }), paletteFilter ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "filter: " }), paletteFilter] })) : null, _jsx(SelectList, { items: paletteItems, onSelect: onPaletteSelect, onCancel: () => setPhase("input") })] })), phase === "input" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { color: "green", children: "\u276F " }), _jsx(TextInput, { value: input, onChange: (v) => {
|
|
521
561
|
setInput(v.replace(/\t/g, ""));
|
|
522
562
|
setCmdIndex(0);
|
|
523
563
|
setFileIndex(0);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subsequence match, case-insensitive: "rwd" matches "/rewind", "exmd"
|
|
3
|
+
* matches "/export". Order matters ("mre" does not match "/rename") but
|
|
4
|
+
* contiguity doesn't — this is what makes a palette feel like a palette
|
|
5
|
+
* rather than the `/` prefix completion.
|
|
6
|
+
*/
|
|
7
|
+
export function fuzzyMatch(haystack, needle) {
|
|
8
|
+
const h = haystack.toLowerCase();
|
|
9
|
+
const n = needle.trim().toLowerCase();
|
|
10
|
+
if (!n)
|
|
11
|
+
return true;
|
|
12
|
+
let i = 0;
|
|
13
|
+
for (const ch of h) {
|
|
14
|
+
if (ch === n[i])
|
|
15
|
+
i++;
|
|
16
|
+
if (i === n.length)
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The palette's rows: every command first, then every checkpoint. Values are
|
|
23
|
+
* prefixed so the selection handler knows what was picked without a second
|
|
24
|
+
* lookup — `cmd:/rename` inserts the command into the input line (never runs
|
|
25
|
+
* it blind: a fuzzy finder that executes on Enter is how conversations get
|
|
26
|
+
* cleared by accident), `checkpoint:<name>` rewinds to that checkpoint.
|
|
27
|
+
*/
|
|
28
|
+
export function buildPaletteItems(commands, checkpoints, filter) {
|
|
29
|
+
const items = [];
|
|
30
|
+
for (const c of commands) {
|
|
31
|
+
if (!fuzzyMatch(`${c.name} ${c.description}`, filter))
|
|
32
|
+
continue;
|
|
33
|
+
items.push({ label: c.name, value: `cmd:${c.name}`, hint: c.description });
|
|
34
|
+
}
|
|
35
|
+
for (const cp of checkpoints) {
|
|
36
|
+
if (!fuzzyMatch(`checkpoint ${cp.name}`, filter))
|
|
37
|
+
continue;
|
|
38
|
+
items.push({
|
|
39
|
+
label: `checkpoint: ${cp.name}`,
|
|
40
|
+
value: `checkpoint:${cp.name}`,
|
|
41
|
+
hint: `rewind · saved ${new Date(cp.createdAt).toLocaleTimeString()}`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return items;
|
|
45
|
+
}
|