killeros 2.0.3 → 2.0.5
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 +37 -0
- package/Killeros.ts +4 -2
- package/README.md +15 -11
- package/killeros/commands.ts +3 -10
- package/killeros/concise.ts +12 -8
- package/killeros/goals.ts +146 -28
- package/killeros/hooks.ts +69 -22
- package/killeros/init-evidence.ts +276 -0
- package/killeros/init-target.ts +289 -0
- package/killeros/init.ts +139 -356
- package/killeros/notifications.ts +1 -1
- package/killeros/question.ts +24 -9
- package/killeros/runtime.ts +25 -6
- package/killeros/shell-ui.ts +56 -152
- package/package.json +1 -1
package/killeros/question.ts
CHANGED
|
@@ -89,12 +89,24 @@ function oneLine(value: string): string {
|
|
|
89
89
|
return value.replace(/\s+/gu, " ").trim();
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
function boundedRenderLine(value: string, width: number, suffix: string): string {
|
|
93
|
+
return truncateToWidth(value.replace(/\r\n|\r|\n/gu, " "), width, suffix);
|
|
94
|
+
}
|
|
95
|
+
|
|
92
96
|
function visibleOptionRange(total: number, selected: number, capacity: number): { start: number; end: number } {
|
|
93
97
|
const size = Math.max(1, Math.min(total, capacity));
|
|
94
98
|
const start = Math.max(0, Math.min(selected - Math.floor(size / 2), total - size));
|
|
95
99
|
return { start, end: Math.min(total, start + size) };
|
|
96
100
|
}
|
|
97
101
|
|
|
102
|
+
function boundedQuestionLines(question: string, width: number, rowLimit: number): string[] {
|
|
103
|
+
const wrapped = wrapTextWithAnsi(question.replace(/\s+/gu, " ").trim(), width);
|
|
104
|
+
if (wrapped.length <= rowLimit) return wrapped;
|
|
105
|
+
const visible = wrapped.slice(0, rowLimit);
|
|
106
|
+
visible[rowLimit - 1] = truncateToWidth(visible[rowLimit - 1]!, width, "…");
|
|
107
|
+
return visible;
|
|
108
|
+
}
|
|
109
|
+
|
|
98
110
|
export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
99
111
|
const customInputHistory: string[] = [];
|
|
100
112
|
let customInputHistoryBytes = 0;
|
|
@@ -349,27 +361,29 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
349
361
|
if (optionIndex >= visibleOptions.length) optionIndex = Math.max(0, visibleOptions.length - 1);
|
|
350
362
|
const selected = visibleOptions[optionIndex];
|
|
351
363
|
const position = `Option ${Math.min(optionIndex + 1, visibleOptions.length)}/${visibleOptions.length}`;
|
|
352
|
-
const
|
|
364
|
+
const expandedAnswer = editor.getExpandedText();
|
|
365
|
+
const answerCount = inputCharacterCount(expandedAnswer).toLocaleString();
|
|
353
366
|
const filterCount = inputCharacterCount(filterQuery).toLocaleString();
|
|
367
|
+
const compactDraft = expandedAnswer.replace(/\r\n|\r|\n/gu, " ↵ ") || "Type an answer";
|
|
354
368
|
|
|
355
369
|
if (rowBudget <= 2) {
|
|
356
370
|
const compact = editMode
|
|
357
|
-
? [`Answer ${answerCount}/${CUSTOM_INPUT_MAX_CHARACTERS.toLocaleString()}`,
|
|
371
|
+
? [`Answer ${answerCount}/${CUSTOM_INPUT_MAX_CHARACTERS.toLocaleString()}`, compactDraft]
|
|
358
372
|
: [`${selected ? `> ${selected.label}` : "No matching options"} · ${position}`];
|
|
359
373
|
cachedWidth = renderWidth;
|
|
360
374
|
cachedRows = rowBudget;
|
|
361
|
-
cachedLines = compact.slice(0, rowBudget).map((line) =>
|
|
375
|
+
cachedLines = compact.slice(0, rowBudget).map((line) => boundedRenderLine(line, renderWidth, "…"));
|
|
362
376
|
return cachedLines;
|
|
363
377
|
}
|
|
364
378
|
|
|
365
379
|
if (rowBudget <= 5) {
|
|
366
380
|
const compact = [
|
|
367
|
-
|
|
381
|
+
...boundedQuestionLines(params.question, renderWidth, Math.max(1, rowBudget - 3)),
|
|
368
382
|
editMode
|
|
369
383
|
? `Answer ${answerCount}/${CUSTOM_INPUT_MAX_CHARACTERS.toLocaleString()}`
|
|
370
384
|
: `${selected ? `> ${selected.label}` : "No matching options"} · ${position}`,
|
|
371
385
|
editMode
|
|
372
|
-
?
|
|
386
|
+
? compactDraft
|
|
373
387
|
: filterQuery
|
|
374
388
|
? `Filter ${filterCount}/${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()}`
|
|
375
389
|
: position,
|
|
@@ -379,11 +393,12 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
379
393
|
];
|
|
380
394
|
cachedWidth = renderWidth;
|
|
381
395
|
cachedRows = rowBudget;
|
|
382
|
-
cachedLines = compact.slice(0, rowBudget).map((line) =>
|
|
396
|
+
cachedLines = compact.slice(0, rowBudget).map((line) => boundedRenderLine(line, renderWidth, "…"));
|
|
383
397
|
return cachedLines;
|
|
384
398
|
}
|
|
385
399
|
|
|
386
|
-
const
|
|
400
|
+
const questionLines = boundedQuestionLines(params.question, renderWidth, Math.max(1, rowBudget - 5));
|
|
401
|
+
const contentRows = rowBudget - questionLines.length - 4;
|
|
387
402
|
const optionCapacity = Math.max(1, Math.min(5, Math.ceil(contentRows / 2)));
|
|
388
403
|
const detailCapacity = Math.max(0, contentRows - optionCapacity);
|
|
389
404
|
const { start, end } = visibleOptionRange(visibleOptions.length, optionIndex, optionCapacity);
|
|
@@ -401,7 +416,7 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
401
416
|
: `${keyHint("tui.select.up", "up")} • ${keyHint("tui.select.down", "down")} • ${keyHint("tui.select.confirm", "select")} • ${keyHint("tui.select.cancel", filterQuery ? "clear filter" : "cancel")}`;
|
|
402
417
|
const lines: string[] = [
|
|
403
418
|
theme.fg("accent", "─".repeat(renderWidth)),
|
|
404
|
-
|
|
419
|
+
...questionLines.map((line) => theme.fg("text", line)),
|
|
405
420
|
theme.fg("muted", truncateToWidth(` ${progress}`, renderWidth, "…")),
|
|
406
421
|
];
|
|
407
422
|
|
|
@@ -459,7 +474,7 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
459
474
|
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
|
|
460
475
|
cachedWidth = renderWidth;
|
|
461
476
|
cachedRows = rowBudget;
|
|
462
|
-
cachedLines = lines.slice(0, rowBudget).map((line) =>
|
|
477
|
+
cachedLines = lines.slice(0, rowBudget).map((line) => boundedRenderLine(line, renderWidth, ""));
|
|
463
478
|
return cachedLines;
|
|
464
479
|
};
|
|
465
480
|
|
package/killeros/runtime.ts
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
|
+
import type { InitEvidenceIndex } from "./init-evidence.ts";
|
|
2
|
+
import type { InitTargetBaseline } from "./init-target.ts";
|
|
3
|
+
|
|
4
|
+
export type InitOutcome =
|
|
5
|
+
| { kind: "pending" }
|
|
6
|
+
| { kind: "written" }
|
|
7
|
+
| { kind: "policy-conflict"; reason: string }
|
|
8
|
+
| { kind: "no-outcome" };
|
|
9
|
+
|
|
1
10
|
export interface InitRuntime {
|
|
2
11
|
active: boolean;
|
|
3
12
|
targetPath?: string;
|
|
4
|
-
writeAttempted: boolean;
|
|
5
|
-
writeSucceeded: boolean;
|
|
6
13
|
projectRoot?: string;
|
|
7
14
|
activeTools?: string[];
|
|
8
|
-
|
|
15
|
+
evidence?: InitEvidenceIndex;
|
|
16
|
+
baseline?: InitTargetBaseline;
|
|
17
|
+
outcome: InitOutcome;
|
|
18
|
+
settle?: (outcome: InitOutcome) => void;
|
|
9
19
|
}
|
|
10
20
|
|
|
11
21
|
export type GoalStatus = "active" | "paused" | "blocked" | "complete";
|
|
12
22
|
|
|
23
|
+
export interface GoalBlockerAudit {
|
|
24
|
+
key: string;
|
|
25
|
+
streak: number;
|
|
26
|
+
lastTurn: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
export interface GoalState {
|
|
14
30
|
version: 1;
|
|
15
31
|
revision: number;
|
|
@@ -24,6 +40,7 @@ export interface GoalState {
|
|
|
24
40
|
baselineTokens: number;
|
|
25
41
|
result?: string;
|
|
26
42
|
resumeAfterManualCompaction?: true;
|
|
43
|
+
blockerAudit?: GoalBlockerAudit;
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
export interface GoalRuntime {
|
|
@@ -39,7 +56,7 @@ export interface GoalRuntime {
|
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
export function createInitRuntime(): InitRuntime {
|
|
42
|
-
return { active: false,
|
|
59
|
+
return { active: false, outcome: { kind: "pending" } };
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
export function createGoalRuntime(): GoalRuntime {
|
|
@@ -55,8 +72,10 @@ export function createGoalRuntime(): GoalRuntime {
|
|
|
55
72
|
export function resetInitRuntime(state: InitRuntime): void {
|
|
56
73
|
state.active = false;
|
|
57
74
|
state.targetPath = undefined;
|
|
58
|
-
state.writeAttempted = false;
|
|
59
|
-
state.writeSucceeded = false;
|
|
60
75
|
state.projectRoot = undefined;
|
|
61
76
|
state.activeTools = undefined;
|
|
77
|
+
state.evidence = undefined;
|
|
78
|
+
state.baseline = undefined;
|
|
79
|
+
state.outcome = { kind: "pending" };
|
|
80
|
+
state.settle = undefined;
|
|
62
81
|
}
|
package/killeros/shell-ui.ts
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
type EditorTheme,
|
|
20
20
|
type TUI,
|
|
21
21
|
} from "@earendil-works/pi-tui";
|
|
22
|
-
import { availableCommandNames } from "./commands.ts";
|
|
23
22
|
import { formatCwd, padRight } from "./display.ts";
|
|
24
23
|
import { reportError } from "./errors.ts";
|
|
25
24
|
import { formatModel } from "./footer.ts";
|
|
@@ -45,6 +44,13 @@ const STARTUP_TIPS = [
|
|
|
45
44
|
"Run /notification to enable a terminal bell when work settles.",
|
|
46
45
|
] as const;
|
|
47
46
|
|
|
47
|
+
const EDITOR_SUGGESTIONS = [
|
|
48
|
+
'Try "how does <filepath> work?"',
|
|
49
|
+
'Try "find edge cases in <filepath>"',
|
|
50
|
+
'Try "simplify <filepath> without changing behavior"',
|
|
51
|
+
'Try "write tests for <filepath>"',
|
|
52
|
+
] as const;
|
|
53
|
+
|
|
48
54
|
export function resolveGitBranch(cwd: string): Promise<string | undefined> {
|
|
49
55
|
return new Promise((resolve) => {
|
|
50
56
|
execFile(
|
|
@@ -68,13 +74,26 @@ export function resolveGitBranch(cwd: string): Promise<string | undefined> {
|
|
|
68
74
|
});
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
function
|
|
72
|
-
const
|
|
73
|
-
for (let index =
|
|
77
|
+
function shuffledDeck(values: readonly string[]): string[] {
|
|
78
|
+
const deck = [...values];
|
|
79
|
+
for (let index = deck.length - 1; index > 0; index -= 1) {
|
|
74
80
|
const swapIndex = Math.floor(Math.random() * (index + 1));
|
|
75
|
-
[
|
|
81
|
+
[deck[index], deck[swapIndex]] = [deck[swapIndex]!, deck[index]!];
|
|
76
82
|
}
|
|
77
|
-
return
|
|
83
|
+
return deck;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let tipDeck: string[] = [];
|
|
87
|
+
let editorSuggestionDeck: string[] = [];
|
|
88
|
+
|
|
89
|
+
function nextStartupTip(): string {
|
|
90
|
+
if (tipDeck.length === 0) tipDeck = shuffledDeck(STARTUP_TIPS);
|
|
91
|
+
return tipDeck.pop() ?? STARTUP_TIPS[0];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function nextEditorSuggestion(): string {
|
|
95
|
+
if (editorSuggestionDeck.length === 0) editorSuggestionDeck = shuffledDeck(EDITOR_SUGGESTIONS);
|
|
96
|
+
return editorSuggestionDeck.pop() ?? EDITOR_SUGGESTIONS[0];
|
|
78
97
|
}
|
|
79
98
|
|
|
80
99
|
function compactBoxLine(content: string, width: number, theme: Theme): string {
|
|
@@ -151,113 +170,6 @@ class PiStartupHeader {
|
|
|
151
170
|
}
|
|
152
171
|
|
|
153
172
|
const ANSI_REGEX = /\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
154
|
-
const ANSI_SEQUENCE_AT_START = /^\x1b\[[0-?]*[ -/]*[@-~]/u;
|
|
155
|
-
const COMMAND_TOKEN_PATTERN = /(^|[ \t])(\/[A-Za-z0-9:_-]*)/gu;
|
|
156
|
-
|
|
157
|
-
function controlSequenceAt(text: string, index: number): string | undefined {
|
|
158
|
-
if (text.startsWith(CURSOR_MARKER, index)) return CURSOR_MARKER;
|
|
159
|
-
return text.slice(index).match(ANSI_SEQUENCE_AT_START)?.[0];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
interface CommandToken {
|
|
163
|
-
text: string;
|
|
164
|
-
start: number;
|
|
165
|
-
end: number;
|
|
166
|
-
valid: boolean;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
interface EditorVisualLine {
|
|
170
|
-
logicalLine: number;
|
|
171
|
-
startCol: number;
|
|
172
|
-
length: number;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function commandTokens(text: string, normalizedNames: readonly string[]): CommandToken[] {
|
|
176
|
-
return [...text.matchAll(COMMAND_TOKEN_PATTERN)].map((match) => {
|
|
177
|
-
const token = match[2] ?? "";
|
|
178
|
-
const prefix = token.slice(1).toLocaleLowerCase();
|
|
179
|
-
const start = (match.index ?? 0) + (match[1]?.length ?? 0);
|
|
180
|
-
return {
|
|
181
|
-
text: token,
|
|
182
|
-
start,
|
|
183
|
-
end: start + token.length,
|
|
184
|
-
valid: normalizedNames.some((name) => name.startsWith(prefix)),
|
|
185
|
-
};
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function highlightTextRanges(
|
|
190
|
-
text: string,
|
|
191
|
-
ranges: Array<{ start: number; end: number }>,
|
|
192
|
-
color: (value: string) => string,
|
|
193
|
-
): string {
|
|
194
|
-
if (ranges.length === 0) return text;
|
|
195
|
-
|
|
196
|
-
let output = "";
|
|
197
|
-
let buffer = "";
|
|
198
|
-
let bufferHighlighted: boolean | undefined;
|
|
199
|
-
let plainIndex = 0;
|
|
200
|
-
const flush = (): void => {
|
|
201
|
-
if (!buffer) return;
|
|
202
|
-
output += bufferHighlighted ? color(buffer) : buffer;
|
|
203
|
-
buffer = "";
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
for (let index = 0; index < text.length;) {
|
|
207
|
-
const control = controlSequenceAt(text, index);
|
|
208
|
-
if (control) {
|
|
209
|
-
flush();
|
|
210
|
-
output += control;
|
|
211
|
-
index += control.length;
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const highlighted = ranges.some((range) => plainIndex >= range.start && plainIndex < range.end);
|
|
216
|
-
if (bufferHighlighted !== highlighted) {
|
|
217
|
-
flush();
|
|
218
|
-
bufferHighlighted = highlighted;
|
|
219
|
-
}
|
|
220
|
-
buffer += text[index];
|
|
221
|
-
plainIndex += 1;
|
|
222
|
-
index += 1;
|
|
223
|
-
}
|
|
224
|
-
flush();
|
|
225
|
-
return output;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function highlightEditorLines(
|
|
229
|
-
lines: string[],
|
|
230
|
-
sourceLines: string[],
|
|
231
|
-
visualLines: EditorVisualLine[],
|
|
232
|
-
scrollOffset: number,
|
|
233
|
-
commandNames: ReadonlySet<string>,
|
|
234
|
-
color: (value: string) => string,
|
|
235
|
-
): { lines: string[]; bottomBorderIndex: number } {
|
|
236
|
-
let bottomBorderIndex = -1;
|
|
237
|
-
for (let index = lines.length - 1; index >= 1; index -= 1) {
|
|
238
|
-
if (isBorderLine(lines[index] ?? "")) {
|
|
239
|
-
bottomBorderIndex = index;
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (bottomBorderIndex < 0) bottomBorderIndex = lines.length - 1;
|
|
244
|
-
|
|
245
|
-
const normalizedNames = [...commandNames].map((name) => name.toLocaleLowerCase());
|
|
246
|
-
for (let index = 1; index < bottomBorderIndex; index += 1) {
|
|
247
|
-
const visualLine = visualLines[scrollOffset + index - 1];
|
|
248
|
-
if (!visualLine) continue;
|
|
249
|
-
const visibleStart = visualLine.startCol;
|
|
250
|
-
const visibleEnd = visibleStart + visualLine.length;
|
|
251
|
-
const ranges = commandTokens(sourceLines[visualLine.logicalLine] ?? "", normalizedNames)
|
|
252
|
-
.filter((token) => token.valid && token.start < visibleEnd && token.end > visibleStart)
|
|
253
|
-
.map((token) => ({
|
|
254
|
-
start: Math.max(token.start, visibleStart) - visibleStart,
|
|
255
|
-
end: Math.min(token.end, visibleEnd) - visibleStart,
|
|
256
|
-
}));
|
|
257
|
-
lines[index] = highlightTextRanges(lines[index] ?? "", ranges, color);
|
|
258
|
-
}
|
|
259
|
-
return { lines, bottomBorderIndex };
|
|
260
|
-
}
|
|
261
173
|
|
|
262
174
|
function stripAnsi(text: string): string {
|
|
263
175
|
return text.replace(ANSI_REGEX, "").trim();
|
|
@@ -276,19 +188,19 @@ function isScrolledTopBorder(line: string): boolean {
|
|
|
276
188
|
class PiCodeEditor extends CustomEditor {
|
|
277
189
|
private readonly appKeybindings: KeybindingsManager;
|
|
278
190
|
private readonly runtimeTheme: Theme;
|
|
279
|
-
private readonly
|
|
191
|
+
private readonly suggestion: string;
|
|
280
192
|
|
|
281
193
|
constructor(
|
|
282
194
|
tui: TUI,
|
|
283
195
|
theme: EditorTheme,
|
|
284
196
|
appKeybindings: KeybindingsManager,
|
|
285
197
|
runtimeTheme: Theme,
|
|
286
|
-
|
|
198
|
+
suggestion: string,
|
|
287
199
|
) {
|
|
288
200
|
super(tui, theme, appKeybindings);
|
|
289
201
|
this.appKeybindings = appKeybindings;
|
|
290
202
|
this.runtimeTheme = runtimeTheme;
|
|
291
|
-
this.
|
|
203
|
+
this.suggestion = suggestion;
|
|
292
204
|
}
|
|
293
205
|
|
|
294
206
|
override handleInput(data: string): void {
|
|
@@ -305,37 +217,19 @@ class PiCodeEditor extends CustomEditor {
|
|
|
305
217
|
super.handleInput(data);
|
|
306
218
|
}
|
|
307
219
|
|
|
308
|
-
private renderWithCommandHighlighting(
|
|
309
|
-
width: number,
|
|
310
|
-
color: (value: string) => string,
|
|
311
|
-
): { lines: string[]; bottomBorderIndex: number } {
|
|
312
|
-
const lines = super.render(width);
|
|
313
|
-
const internals = this as unknown as {
|
|
314
|
-
lastWidth: number;
|
|
315
|
-
scrollOffset: number;
|
|
316
|
-
buildVisualLineMap: (layoutWidth: number) => EditorVisualLine[];
|
|
317
|
-
};
|
|
318
|
-
return highlightEditorLines(
|
|
319
|
-
lines,
|
|
320
|
-
this.getLines(),
|
|
321
|
-
internals.buildVisualLineMap(internals.lastWidth),
|
|
322
|
-
internals.scrollOffset,
|
|
323
|
-
this.getCommandNames(),
|
|
324
|
-
color,
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
220
|
override render(width: number): string[] {
|
|
329
221
|
if (width <= 0) return [];
|
|
330
|
-
|
|
331
|
-
if (width < 4) {
|
|
332
|
-
return this.renderWithCommandHighlighting(width, colorCommand)
|
|
333
|
-
.lines.map((line) => truncateToWidth(line, width, ""));
|
|
334
|
-
}
|
|
222
|
+
if (width < 4) return super.render(width).map((line) => truncateToWidth(line, width, ""));
|
|
335
223
|
const innerWidth = width - 2;
|
|
336
|
-
const
|
|
337
|
-
const { lines, bottomBorderIndex } = highlighted;
|
|
224
|
+
const lines = super.render(innerWidth);
|
|
338
225
|
if (lines.length < 2) return lines.map((line) => truncateToWidth(line, width, ""));
|
|
226
|
+
let bottomBorderIndex = lines.length - 1;
|
|
227
|
+
for (let index = lines.length - 1; index >= 1; index -= 1) {
|
|
228
|
+
if (isBorderLine(lines[index] ?? "")) {
|
|
229
|
+
bottomBorderIndex = index;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
339
233
|
|
|
340
234
|
const gray = (text: string): string => this.runtimeTheme.fg("dim", text);
|
|
341
235
|
const framed: string[] = [];
|
|
@@ -350,8 +244,16 @@ class PiCodeEditor extends CustomEditor {
|
|
|
350
244
|
}
|
|
351
245
|
|
|
352
246
|
for (let index = 1; index < bottomBorderIndex; index += 1) {
|
|
353
|
-
const
|
|
354
|
-
|
|
247
|
+
const isPromptLine = index === 1 && !isScrolledHeader;
|
|
248
|
+
const prefix = isPromptLine ? gray("❯\u00A0") : " ";
|
|
249
|
+
let content = lines[index] ?? "";
|
|
250
|
+
if (isPromptLine && this.getText() === "") {
|
|
251
|
+
const first = this.suggestion.slice(0, 1);
|
|
252
|
+
const rest = this.suggestion.slice(1);
|
|
253
|
+
const cursorMarker = this.focused ? CURSOR_MARKER : "";
|
|
254
|
+
content = `${cursorMarker}\x1B[7m${gray(first)}\x1B[27m${gray(rest)}`;
|
|
255
|
+
}
|
|
256
|
+
framed.push(`${prefix}${padRight(content, innerWidth)}`);
|
|
355
257
|
}
|
|
356
258
|
|
|
357
259
|
const bottom = stripAnsi(lines[bottomBorderIndex] ?? "");
|
|
@@ -381,16 +283,13 @@ function formatActivityMessage(word: string, theme: Theme): string {
|
|
|
381
283
|
return `${theme.fg("accent", `${word}…`)} ${theme.fg("dim", `(${theme.bold("esc")} to interrupt · thinking)`)}`;
|
|
382
284
|
}
|
|
383
285
|
|
|
286
|
+
let killerosEditorFactory: ReturnType<ExtensionContext["ui"]["getEditorComponent"]>;
|
|
287
|
+
|
|
384
288
|
export function registerShellUi(pi: ExtensionAPI): void {
|
|
385
289
|
let activeHeader: PiStartupHeader | undefined;
|
|
386
290
|
let activityDeck: string[] = [];
|
|
387
291
|
let lastActivityWord: string | undefined;
|
|
388
292
|
let activityTimer: ReturnType<typeof setInterval> | undefined;
|
|
389
|
-
let tipDeck: string[] = [];
|
|
390
|
-
const nextStartupTip = (): string => {
|
|
391
|
-
if (tipDeck.length === 0) tipDeck = shuffledTips();
|
|
392
|
-
return tipDeck.pop() ?? STARTUP_TIPS[0];
|
|
393
|
-
};
|
|
394
293
|
const refillActivityDeck = (): void => {
|
|
395
294
|
activityDeck = [...ACTIVITY_WORDS];
|
|
396
295
|
for (let index = activityDeck.length - 1; index > 0; index -= 1) {
|
|
@@ -428,8 +327,13 @@ export function registerShellUi(pi: ExtensionAPI): void {
|
|
|
428
327
|
intervalMs: ACTIVITY_FRAME_INTERVAL_MS,
|
|
429
328
|
});
|
|
430
329
|
ctx.ui.setHiddenThinkingLabel("└ Thinking…");
|
|
431
|
-
ctx.ui.
|
|
432
|
-
|
|
330
|
+
const existingEditorFactory = ctx.ui.getEditorComponent?.();
|
|
331
|
+
if (!existingEditorFactory || existingEditorFactory === killerosEditorFactory) {
|
|
332
|
+
const editorSuggestion = nextEditorSuggestion();
|
|
333
|
+
killerosEditorFactory = (tui, editorTheme, keybindings) =>
|
|
334
|
+
new PiCodeEditor(tui, editorTheme, keybindings, ctx.ui.theme, editorSuggestion);
|
|
335
|
+
ctx.ui.setEditorComponent(killerosEditorFactory);
|
|
336
|
+
}
|
|
433
337
|
} catch (error) {
|
|
434
338
|
reportError(ctx, "Killeros UI failed to initialize", error);
|
|
435
339
|
}
|