killeros 1.5.8 → 2.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.
- package/CHANGELOG.md +17 -130
- package/Killeros.ts +1 -25
- package/README.md +130 -209
- package/killeros/bounded-text.ts +25 -0
- package/killeros/commands.ts +8 -238
- package/killeros/display.ts +6 -2
- package/killeros/footer.ts +35 -5
- package/killeros/goals.ts +53 -25
- package/killeros/hooks.ts +1 -1
- package/killeros/limits.ts +1 -0
- package/killeros/personal-instructions.ts +3 -1
- package/killeros/question.ts +144 -97
- package/killeros/shell-ui.ts +230 -54
- package/killeros/variants.ts +6 -2
- package/package.json +1 -6
- package/themes/killeros.json +4 -3
- package/killeros/subagent-lifecycle.ts +0 -761
- package/killeros/subagent-persistence.ts +0 -572
- package/killeros/subagent-process.ts +0 -626
- package/killeros/subagent-ui.ts +0 -245
- package/killeros/subagents.ts +0 -3257
- package/subagent-lifecycle.ts +0 -1
- package/subagent-process.ts +0 -1
- package/subagent-ui.ts +0 -1
- package/subagents.ts +0 -1
package/killeros/question.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
import { type ExtensionAPI, type ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import { keyHint, type ExtensionAPI, type ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
|
-
Container,
|
|
4
3
|
decodeKittyPrintable,
|
|
5
4
|
Editor,
|
|
6
|
-
Key,
|
|
7
5
|
Markdown,
|
|
8
|
-
matchesKey,
|
|
9
|
-
SelectList,
|
|
10
|
-
Text,
|
|
11
6
|
truncateToWidth,
|
|
12
|
-
visibleWidth,
|
|
13
7
|
wrapTextWithAnsi,
|
|
14
8
|
type EditorTheme,
|
|
15
9
|
} from "@earendil-works/pi-tui";
|
|
16
10
|
import { Type } from "typebox";
|
|
11
|
+
import { BoundedText } from "./bounded-text.ts";
|
|
17
12
|
|
|
18
13
|
const OptionSchema = Type.Object({
|
|
19
14
|
label: Type.String({ minLength: 1, maxLength: 200, description: "Display label for the option" }),
|
|
@@ -90,6 +85,16 @@ function removeLastGrapheme(value: string): string {
|
|
|
90
85
|
return last ? value.slice(0, last.index) : "";
|
|
91
86
|
}
|
|
92
87
|
|
|
88
|
+
function oneLine(value: string): string {
|
|
89
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function visibleOptionRange(total: number, selected: number, capacity: number): { start: number; end: number } {
|
|
93
|
+
const size = Math.max(1, Math.min(total, capacity));
|
|
94
|
+
const start = Math.max(0, Math.min(selected - Math.floor(size / 2), total - size));
|
|
95
|
+
return { start, end: Math.min(total, start + size) };
|
|
96
|
+
}
|
|
97
|
+
|
|
93
98
|
export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
94
99
|
const customInputHistory: string[] = [];
|
|
95
100
|
let customInputHistoryBytes = 0;
|
|
@@ -153,11 +158,12 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
153
158
|
];
|
|
154
159
|
|
|
155
160
|
let finishFromAbort: (() => void) | undefined;
|
|
156
|
-
const resultPromise = ctx.ui.custom<QuestionSelection>((tui, theme,
|
|
161
|
+
const resultPromise = ctx.ui.custom<QuestionSelection>((tui, theme, keybindings, done) => {
|
|
157
162
|
let optionIndex = 0;
|
|
158
163
|
let editMode = false;
|
|
159
164
|
let filterQuery = "";
|
|
160
165
|
let cachedWidth: number | undefined;
|
|
166
|
+
let cachedRows: number | undefined;
|
|
161
167
|
let cachedLines: string[] | undefined;
|
|
162
168
|
let completed = false;
|
|
163
169
|
|
|
@@ -191,6 +197,7 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
191
197
|
|
|
192
198
|
const invalidate = (): void => {
|
|
193
199
|
cachedWidth = undefined;
|
|
200
|
+
cachedRows = undefined;
|
|
194
201
|
cachedLines = undefined;
|
|
195
202
|
editor.invalidate();
|
|
196
203
|
};
|
|
@@ -241,7 +248,7 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
241
248
|
|
|
242
249
|
const handleInput = (data: string): void => {
|
|
243
250
|
if (editMode) {
|
|
244
|
-
if (
|
|
251
|
+
if (keybindings.matches(data, "tui.select.cancel")) {
|
|
245
252
|
editMode = false;
|
|
246
253
|
editor.setText("");
|
|
247
254
|
refresh();
|
|
@@ -260,24 +267,35 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
260
267
|
|
|
261
268
|
const visibleOptions = filteredOptions();
|
|
262
269
|
if (optionIndex >= visibleOptions.length) optionIndex = Math.max(0, visibleOptions.length - 1);
|
|
263
|
-
|
|
270
|
+
const pageSize = Math.max(1, Math.min(5, Math.ceil(Math.max(1, tui.terminal.rows - 5) / 2)));
|
|
271
|
+
if (keybindings.matches(data, "tui.select.up")) {
|
|
264
272
|
optionIndex = Math.max(0, optionIndex - 1);
|
|
265
273
|
refresh();
|
|
266
274
|
return;
|
|
267
275
|
}
|
|
268
|
-
if (
|
|
276
|
+
if (keybindings.matches(data, "tui.select.down")) {
|
|
269
277
|
optionIndex = Math.min(visibleOptions.length - 1, optionIndex + 1);
|
|
270
278
|
refresh();
|
|
271
279
|
return;
|
|
272
280
|
}
|
|
273
|
-
if (
|
|
281
|
+
if (keybindings.matches(data, "tui.select.pageUp")) {
|
|
282
|
+
optionIndex = Math.max(0, optionIndex - pageSize);
|
|
283
|
+
refresh();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (keybindings.matches(data, "tui.select.pageDown")) {
|
|
287
|
+
optionIndex = Math.min(visibleOptions.length - 1, optionIndex + pageSize);
|
|
288
|
+
refresh();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (keybindings.matches(data, "tui.select.confirm")) {
|
|
274
292
|
const selected = visibleOptions[optionIndex];
|
|
275
293
|
if (!selected) return;
|
|
276
294
|
if (selected.isOther) enterCustomMode();
|
|
277
295
|
else finish({ kind: "selected", answer: selected.label, originalIndex: selected.originalIndex });
|
|
278
296
|
return;
|
|
279
297
|
}
|
|
280
|
-
if (
|
|
298
|
+
if (keybindings.matches(data, "tui.select.cancel")) {
|
|
281
299
|
if (filterQuery) {
|
|
282
300
|
filterQuery = "";
|
|
283
301
|
optionIndex = 0;
|
|
@@ -287,7 +305,7 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
287
305
|
}
|
|
288
306
|
return;
|
|
289
307
|
}
|
|
290
|
-
if (
|
|
308
|
+
if (keybindings.matches(data, "tui.editor.deleteCharBackward")) {
|
|
291
309
|
if (filterQuery) {
|
|
292
310
|
filterQuery = removeLastGrapheme(filterQuery);
|
|
293
311
|
optionIndex = 0;
|
|
@@ -304,60 +322,94 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
304
322
|
else finish({ kind: "selected", answer: selected.label, originalIndex: selected.originalIndex });
|
|
305
323
|
return;
|
|
306
324
|
}
|
|
307
|
-
if (printableInput)
|
|
308
|
-
appendFilterInput(printableInput);
|
|
309
|
-
}
|
|
325
|
+
if (printableInput) appendFilterInput(printableInput);
|
|
310
326
|
};
|
|
311
327
|
|
|
312
328
|
const render = (width: number): string[] => {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
+
if (width <= 0) return [];
|
|
330
|
+
const renderWidth = width;
|
|
331
|
+
const rowBudget = Math.max(1, tui.terminal.rows);
|
|
332
|
+
if (cachedLines && cachedWidth === renderWidth && cachedRows === rowBudget) return cachedLines;
|
|
333
|
+
const visibleOptions = filteredOptions();
|
|
334
|
+
if (optionIndex >= visibleOptions.length) optionIndex = Math.max(0, visibleOptions.length - 1);
|
|
335
|
+
const selected = visibleOptions[optionIndex];
|
|
336
|
+
const position = `Option ${Math.min(optionIndex + 1, visibleOptions.length)}/${visibleOptions.length}`;
|
|
337
|
+
const answerCount = inputCharacterCount(editor.getExpandedText()).toLocaleString();
|
|
338
|
+
const filterCount = inputCharacterCount(filterQuery).toLocaleString();
|
|
339
|
+
|
|
340
|
+
if (rowBudget <= 2) {
|
|
341
|
+
const compact = editMode
|
|
342
|
+
? [`Answer ${answerCount}/${CUSTOM_INPUT_MAX_CHARACTERS.toLocaleString()}`, editor.getExpandedText() || "Type an answer"]
|
|
343
|
+
: [`${selected ? `> ${selected.label}` : "No matching options"} · ${position}`];
|
|
344
|
+
cachedWidth = renderWidth;
|
|
345
|
+
cachedRows = rowBudget;
|
|
346
|
+
cachedLines = compact.slice(0, rowBudget).map((line) => truncateToWidth(line, renderWidth, "…"));
|
|
347
|
+
return cachedLines;
|
|
348
|
+
}
|
|
329
349
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
350
|
+
if (rowBudget <= 5) {
|
|
351
|
+
const compact = [
|
|
352
|
+
truncateToWidth(params.question.replace(/\s+/gu, " "), renderWidth, "…"),
|
|
353
|
+
editMode
|
|
354
|
+
? `Answer ${answerCount}/${CUSTOM_INPUT_MAX_CHARACTERS.toLocaleString()}`
|
|
355
|
+
: `${selected ? `> ${selected.label}` : "No matching options"} · ${position}`,
|
|
356
|
+
editMode
|
|
357
|
+
? editor.getExpandedText() || "Type an answer"
|
|
358
|
+
: filterQuery
|
|
359
|
+
? `Filter ${filterCount}/${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()}`
|
|
360
|
+
: position,
|
|
361
|
+
editMode
|
|
362
|
+
? `${keyHint("tui.input.submit", "submit")} • ${keyHint("tui.select.cancel", "options")}`
|
|
363
|
+
: `${keyHint("tui.select.confirm", "select")} • ${keyHint("tui.select.cancel", "cancel")}`,
|
|
364
|
+
];
|
|
365
|
+
cachedWidth = renderWidth;
|
|
366
|
+
cachedRows = rowBudget;
|
|
367
|
+
cachedLines = compact.slice(0, rowBudget).map((line) => truncateToWidth(line, renderWidth, "…"));
|
|
368
|
+
return cachedLines;
|
|
336
369
|
}
|
|
337
370
|
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
371
|
+
const contentRows = rowBudget - 5;
|
|
372
|
+
const optionCapacity = Math.max(1, Math.min(5, Math.ceil(contentRows / 2)));
|
|
373
|
+
const detailCapacity = Math.max(0, contentRows - optionCapacity);
|
|
374
|
+
const { start, end } = visibleOptionRange(visibleOptions.length, optionIndex, optionCapacity);
|
|
375
|
+
const hiddenAbove = start > 0 ? `↑ ${start}` : "";
|
|
376
|
+
const hiddenBelowCount = visibleOptions.length - end;
|
|
377
|
+
const hiddenBelow = hiddenBelowCount > 0 ? `↓ ${hiddenBelowCount}` : "";
|
|
378
|
+
const hiddenStatus = [hiddenAbove, hiddenBelow].filter(Boolean).join(" · ");
|
|
379
|
+
const progress = editMode
|
|
380
|
+
? `Answer ${answerCount}/${CUSTOM_INPUT_MAX_CHARACTERS.toLocaleString()}`
|
|
381
|
+
: filterQuery
|
|
382
|
+
? `Filter ${filterCount}/${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()} · ${position}`
|
|
383
|
+
: `${position}${hiddenStatus ? ` · ${hiddenStatus}` : ""}`;
|
|
384
|
+
const hint = editMode
|
|
385
|
+
? `${keyHint("tui.input.submit", "submit")} • ${keyHint("tui.select.cancel", "options")}`
|
|
386
|
+
: `${keyHint("tui.select.up", "up")} • ${keyHint("tui.select.down", "down")} • ${keyHint("tui.select.confirm", "select")} • ${keyHint("tui.select.cancel", filterQuery ? "clear filter" : "cancel")}`;
|
|
387
|
+
const lines: string[] = [
|
|
388
|
+
theme.fg("accent", "─".repeat(renderWidth)),
|
|
389
|
+
theme.fg("text", truncateToWidth(` ${params.question.replace(/\s+/gu, " ")}`, renderWidth, "…")),
|
|
390
|
+
theme.fg("muted", truncateToWidth(` ${progress}`, renderWidth, "…")),
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
if (editMode) {
|
|
394
|
+
const editorLines = editor.render(renderWidth);
|
|
395
|
+
const draftLines = editorLines.length > 2 ? editorLines.slice(1, -1) : editorLines;
|
|
396
|
+
lines.push(...(draftLines.length > 0 ? draftLines : ["Type an answer"]).slice(-contentRows));
|
|
397
|
+
} else {
|
|
398
|
+
for (let index = start; index < end; index += 1) {
|
|
399
|
+
const option = visibleOptions[index]!;
|
|
400
|
+
const isSelected = index === optionIndex;
|
|
401
|
+
const prefix = isSelected ? "> " : " ";
|
|
402
|
+
const color: ThemeColor = isSelected ? "accent" : "text";
|
|
403
|
+
lines.push(theme.fg(color, truncateToWidth(`${prefix}${index + 1}. ${option.label}`, renderWidth, "…")));
|
|
347
404
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
lines.push("");
|
|
357
|
-
addWrappedWithPrefix(" ", theme.fg("accent", theme.bold("Proposal preview")));
|
|
358
|
-
const markdownLines = new Markdown(
|
|
359
|
-
selectedPreview,
|
|
360
|
-
1,
|
|
405
|
+
|
|
406
|
+
const detailLines: string[] = [];
|
|
407
|
+
if (selected?.description) detailLines.push(...wrapTextWithAnsi(theme.fg("muted", selected.description), renderWidth));
|
|
408
|
+
if (selected?.preview) {
|
|
409
|
+
detailLines.push(theme.fg("accent", theme.bold("Proposal preview")));
|
|
410
|
+
detailLines.push(...new Markdown(
|
|
411
|
+
selected.preview,
|
|
412
|
+
0,
|
|
361
413
|
0,
|
|
362
414
|
{
|
|
363
415
|
heading: (text) => theme.fg("accent", theme.bold(text)),
|
|
@@ -376,35 +428,23 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
376
428
|
underline: (text) => theme.underline(text),
|
|
377
429
|
},
|
|
378
430
|
{ color: (text) => theme.fg("muted", text) },
|
|
379
|
-
).render(renderWidth);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
431
|
+
).render(renderWidth));
|
|
432
|
+
}
|
|
433
|
+
if (detailCapacity > 0 && detailLines.length > detailCapacity) {
|
|
434
|
+
const visibleDetailRows = Math.max(0, detailCapacity - 1);
|
|
435
|
+
lines.push(...detailLines.slice(0, visibleDetailRows));
|
|
436
|
+
const hiddenRows = detailLines.length - visibleDetailRows;
|
|
437
|
+
lines.push(theme.fg("dim", `… ${hiddenRows} more line${hiddenRows === 1 ? "" : "s"}`));
|
|
438
|
+
} else {
|
|
439
|
+
lines.push(...detailLines.slice(0, detailCapacity));
|
|
389
440
|
}
|
|
390
441
|
}
|
|
391
442
|
|
|
392
|
-
|
|
393
|
-
lines.push("");
|
|
394
|
-
addWrappedWithPrefix(" ", theme.fg("muted", "Your answer:"));
|
|
395
|
-
editor.render(Math.max(1, renderWidth - 2)).forEach((line) => lines.push(` ${line}`));
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
lines.push("");
|
|
399
|
-
const hint = editMode
|
|
400
|
-
? `Enter submit • Esc options${customInputHistory.length ? " • ↑↓ history" : ""}`
|
|
401
|
-
: filterQuery
|
|
402
|
-
? "1-9 select • ↑↓ navigate • Enter select • Esc clear filter"
|
|
403
|
-
: "1-9 select • type to filter • ↑↓ navigate • Enter select • Esc cancel";
|
|
404
|
-
addWrappedWithPrefix(" ", theme.fg("dim", hint));
|
|
443
|
+
lines.push(theme.fg("dim", truncateToWidth(` ${hint}`, renderWidth, "…")));
|
|
405
444
|
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
|
|
406
445
|
cachedWidth = renderWidth;
|
|
407
|
-
|
|
446
|
+
cachedRows = rowBudget;
|
|
447
|
+
cachedLines = lines.slice(0, rowBudget).map((line) => truncateToWidth(line, renderWidth, ""));
|
|
408
448
|
return cachedLines;
|
|
409
449
|
};
|
|
410
450
|
|
|
@@ -457,27 +497,34 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
457
497
|
};
|
|
458
498
|
},
|
|
459
499
|
|
|
460
|
-
renderCall(args, theme) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
.map((option, index) => `${index + 1}. ${option}`);
|
|
465
|
-
text += `\n${theme.fg("dim", ` Options: ${numbered.join(", ")}`)}`;
|
|
500
|
+
renderCall(args, theme, context) {
|
|
501
|
+
if (!context.expanded) {
|
|
502
|
+
const summary = `${theme.fg("toolTitle", theme.bold("question "))}${theme.fg("muted", oneLine(args.question))}\n${theme.fg("dim", ` ${args.options.length} option${args.options.length === 1 ? "" : "s"}`)}`;
|
|
503
|
+
return new BoundedText(summary, 3);
|
|
466
504
|
}
|
|
467
|
-
|
|
505
|
+
|
|
506
|
+
const lines = [`${theme.fg("toolTitle", theme.bold("question "))}${theme.fg("muted", args.question)}`];
|
|
507
|
+
args.options.forEach((option, index) => {
|
|
508
|
+
lines.push(theme.fg("text", `${index + 1}. ${option.label}`));
|
|
509
|
+
if (option.description) lines.push(theme.fg("muted", ` ${option.description}`));
|
|
510
|
+
if (option.preview) lines.push(theme.fg("dim", option.preview));
|
|
511
|
+
});
|
|
512
|
+
lines.push(theme.fg("text", `${args.options.length + 1}. Type a custom answer`));
|
|
513
|
+
return new BoundedText(lines.join("\n"));
|
|
468
514
|
},
|
|
469
515
|
|
|
470
|
-
renderResult(result,
|
|
516
|
+
renderResult(result, options, theme) {
|
|
471
517
|
const details = result.details;
|
|
472
518
|
if (!details) {
|
|
473
519
|
const first = result.content[0];
|
|
474
|
-
return new
|
|
520
|
+
return new BoundedText(first?.type === "text" ? first.text : "", options.expanded ? undefined : 3);
|
|
475
521
|
}
|
|
476
|
-
if (details.cancelled || details.answer === null) return new
|
|
522
|
+
if (details.cancelled || details.answer === null) return new BoundedText(theme.fg("warning", "Cancelled"));
|
|
477
523
|
if (details.wasCustom) {
|
|
478
|
-
|
|
524
|
+
const text = `${theme.fg("success", "✓ ")}${theme.fg("muted", "(wrote) ")}${theme.fg("accent", details.answer)}`;
|
|
525
|
+
return new BoundedText(text, options.expanded ? undefined : 3);
|
|
479
526
|
}
|
|
480
|
-
return new
|
|
527
|
+
return new BoundedText(`${theme.fg("success", "✓ ")}${theme.fg("accent", details.answer)}`);
|
|
481
528
|
},
|
|
482
529
|
});
|
|
483
530
|
}
|