killeros 2.0.17 → 2.0.19
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 +50 -0
- package/README.md +3 -3
- package/killeros/activity.ts +4 -0
- package/killeros/codex-fast-state.ts +16 -7
- package/killeros/commands.ts +26 -9
- package/killeros/display.ts +7 -4
- package/killeros/errors.ts +10 -1
- package/killeros/footer.ts +5 -3
- package/killeros/goals.ts +336 -140
- package/killeros/handoff.ts +4 -14
- package/killeros/hooks.ts +186 -58
- package/killeros/init-evidence.ts +4 -2
- package/killeros/init-target.ts +28 -20
- package/killeros/init.ts +16 -7
- package/killeros/notifications.ts +2 -1
- package/killeros/personal-instructions.ts +11 -12
- package/killeros/question.ts +15 -7
- package/killeros/runtime.ts +39 -7
- package/killeros/settings.ts +8 -5
- package/killeros/shell-ui.ts +14 -5
- package/killeros/variants.ts +3 -2
- package/package.json +7 -7
|
@@ -34,20 +34,21 @@ function readBoundedText(filePath: string, limit = PERSONAL_INSTRUCTIONS_LIMIT):
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export function resolvePersonalInstructions(cwd: string):
|
|
37
|
+
export function resolvePersonalInstructions(cwd: string): string | undefined {
|
|
38
38
|
const localPath = path.join(cwd, PERSONAL_INSTRUCTIONS_FILE);
|
|
39
39
|
const local = readBoundedText(localPath);
|
|
40
40
|
if (!local) return undefined;
|
|
41
41
|
|
|
42
42
|
const importMatch = local.trim().match(/^@(.+)$/u);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
let content = local;
|
|
44
|
+
if (importMatch) {
|
|
45
|
+
const requestedPath = (importMatch[1] ?? "").trim();
|
|
46
|
+
const importedPath = requestedPath.startsWith("~/") || requestedPath.startsWith("~\\")
|
|
47
|
+
? path.join(os.homedir(), requestedPath.slice(2))
|
|
48
|
+
: path.resolve(cwd, requestedPath);
|
|
49
|
+
content = readBoundedText(importedPath) ?? local;
|
|
50
|
+
}
|
|
51
|
+
return `<personal_instructions>\n${content}\n</personal_instructions>`;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
export function registerPersonalInstructions(pi: ExtensionAPI, initState: InitRuntime): void {
|
|
@@ -59,9 +60,7 @@ export function registerPersonalInstructions(pi: ExtensionAPI, initState: InitRu
|
|
|
59
60
|
systemPrompt: [
|
|
60
61
|
event.systemPrompt,
|
|
61
62
|
"",
|
|
62
|
-
|
|
63
|
-
personal.content,
|
|
64
|
-
"</personal_instructions>",
|
|
63
|
+
personal,
|
|
65
64
|
].join("\n"),
|
|
66
65
|
};
|
|
67
66
|
});
|
package/killeros/question.ts
CHANGED
|
@@ -165,7 +165,9 @@ function boundedQuestionLines(question: string, width: number, rowLimit: number)
|
|
|
165
165
|
const wrapped = wrapTextWithAnsi(question.replace(/\s+/gu, " ").trim(), width);
|
|
166
166
|
if (wrapped.length <= rowLimit) return wrapped;
|
|
167
167
|
const visible = wrapped.slice(0, rowLimit);
|
|
168
|
-
|
|
168
|
+
const finalIndex = rowLimit - 1;
|
|
169
|
+
const finalLine = visible[finalIndex];
|
|
170
|
+
if (finalLine !== undefined) visible[finalIndex] = truncateToWidth(finalLine, width, "…");
|
|
169
171
|
return visible;
|
|
170
172
|
}
|
|
171
173
|
|
|
@@ -173,12 +175,12 @@ function compactMultipleAnswers(answers: readonly string[], width: number): stri
|
|
|
173
175
|
const prefix = "✓ ";
|
|
174
176
|
if (answers.length === 0) return truncateToWidth(prefix + "No answers", width, "…");
|
|
175
177
|
const visible: string[] = [];
|
|
176
|
-
for (
|
|
178
|
+
for (const [index, answer] of answers.entries()) {
|
|
177
179
|
const remaining = answers.length - index - 1;
|
|
178
|
-
const candidate = [...visible, oneLine(
|
|
180
|
+
const candidate = [...visible, oneLine(answer)].join(", ");
|
|
179
181
|
const suffix = remaining > 0 ? `, +${remaining} more` : "";
|
|
180
182
|
if (visibleWidth(prefix + candidate + suffix) > width) break;
|
|
181
|
-
visible.push(oneLine(
|
|
183
|
+
visible.push(oneLine(answer));
|
|
182
184
|
}
|
|
183
185
|
if (visible.length === answers.length) return prefix + visible.join(", ");
|
|
184
186
|
const hidden = answers.length - visible.length;
|
|
@@ -228,7 +230,8 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
228
230
|
if (bytes > CUSTOM_INPUT_HISTORY_BYTES) return false;
|
|
229
231
|
const existingIndex = customInputHistory.indexOf(value);
|
|
230
232
|
if (existingIndex >= 0) {
|
|
231
|
-
|
|
233
|
+
const existing = customInputHistory[existingIndex];
|
|
234
|
+
if (existing !== undefined) customInputHistoryBytes -= Buffer.byteLength(existing, "utf8");
|
|
232
235
|
customInputHistory.splice(existingIndex, 1);
|
|
233
236
|
}
|
|
234
237
|
while (customInputHistory.length >= CUSTOM_INPUT_HISTORY_LIMIT || customInputHistoryBytes + bytes > CUSTOM_INPUT_HISTORY_BYTES) {
|
|
@@ -329,7 +332,11 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
329
332
|
const selectedCount = (): number => selectedOriginalIndices.size + (customAnswer === undefined ? 0 : 1);
|
|
330
333
|
const orderedMultipleSelection = () => {
|
|
331
334
|
const selectedIndices = [...selectedOriginalIndices].sort((left, right) => left - right);
|
|
332
|
-
const predefined = selectedIndices.map((index) =>
|
|
335
|
+
const predefined = selectedIndices.map((index) => {
|
|
336
|
+
const option = params.options[index - 1];
|
|
337
|
+
if (!option) throw new Error("Question selection no longer matches an available option");
|
|
338
|
+
return option.label;
|
|
339
|
+
});
|
|
333
340
|
return {
|
|
334
341
|
answers: customAnswer === undefined ? predefined : [...predefined, customAnswer],
|
|
335
342
|
selectedIndices,
|
|
@@ -640,7 +647,8 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
640
647
|
lines.push(...(draftLines.length > 0 ? draftLines : [editMode === "filter" ? "Type a filter" : "Type an answer"]).slice(-contentRows));
|
|
641
648
|
} else {
|
|
642
649
|
for (let index = start; index < end; index += 1) {
|
|
643
|
-
const option = visibleOptions[index]
|
|
650
|
+
const option = visibleOptions[index];
|
|
651
|
+
if (!option) continue;
|
|
644
652
|
const color: ThemeColor = index === optionIndex ? "accent" : "text";
|
|
645
653
|
lines.push(theme.fg(color, truncateToWidth(optionLabel(option, index), width, "…")));
|
|
646
654
|
}
|
package/killeros/runtime.ts
CHANGED
|
@@ -38,31 +38,63 @@ export interface GoalFileVerification {
|
|
|
38
38
|
baseline: GoalFileBaseline;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export interface
|
|
41
|
+
export interface GoalStateCommon {
|
|
42
42
|
version: 1;
|
|
43
43
|
revision: number;
|
|
44
44
|
objective: string;
|
|
45
|
-
status: GoalStatus;
|
|
46
45
|
createdAt: number;
|
|
47
46
|
updatedAt: number;
|
|
48
47
|
activeMilliseconds: number;
|
|
49
|
-
activeStartedAt?: number;
|
|
50
48
|
turns: number;
|
|
51
49
|
blockedAuditStartTurn: number;
|
|
52
50
|
baselineTokens: number;
|
|
53
|
-
result?: string;
|
|
54
|
-
resumeAfterManualCompaction?: true;
|
|
55
|
-
blockerAudit?: GoalBlockerAudit;
|
|
56
51
|
verification?: GoalFileVerification;
|
|
57
52
|
}
|
|
58
53
|
|
|
54
|
+
export type GoalState = GoalStateCommon & (
|
|
55
|
+
| {
|
|
56
|
+
status: "active";
|
|
57
|
+
activeStartedAt: number;
|
|
58
|
+
result?: string;
|
|
59
|
+
blockerAudit?: GoalBlockerAudit;
|
|
60
|
+
resumeAfterManualCompaction?: never;
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
status: "paused";
|
|
64
|
+
activeStartedAt?: never;
|
|
65
|
+
result?: string;
|
|
66
|
+
blockerAudit?: GoalBlockerAudit;
|
|
67
|
+
resumeAfterManualCompaction?: true;
|
|
68
|
+
}
|
|
69
|
+
| {
|
|
70
|
+
status: "blocked";
|
|
71
|
+
activeStartedAt?: never;
|
|
72
|
+
result: string;
|
|
73
|
+
blockerAudit?: GoalBlockerAudit;
|
|
74
|
+
resumeAfterManualCompaction?: never;
|
|
75
|
+
}
|
|
76
|
+
| {
|
|
77
|
+
status: "complete";
|
|
78
|
+
activeStartedAt?: never;
|
|
79
|
+
result: string;
|
|
80
|
+
blockerAudit?: never;
|
|
81
|
+
resumeAfterManualCompaction?: never;
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
export interface AutomaticGoalCompaction {
|
|
86
|
+
pausedRevision: number;
|
|
87
|
+
compactionSucceeded: boolean;
|
|
88
|
+
turnSettled: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
59
91
|
export interface GoalRuntime {
|
|
60
92
|
state?: GoalState;
|
|
61
93
|
continuationScheduled: boolean;
|
|
62
94
|
continuationHeld: boolean;
|
|
63
95
|
goalTurnInFlight: boolean;
|
|
64
96
|
agentEndObserved: boolean;
|
|
65
|
-
automaticCompaction?:
|
|
97
|
+
automaticCompaction?: AutomaticGoalCompaction;
|
|
66
98
|
persistenceRetryNeeded: boolean;
|
|
67
99
|
lastStopReason?: string;
|
|
68
100
|
lastError?: string;
|
package/killeros/settings.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { hasErrorCode } from "./errors.ts";
|
|
5
6
|
|
|
6
7
|
export type KillerosSettings = Record<string, unknown>;
|
|
7
8
|
|
|
@@ -10,15 +11,17 @@ export interface KillerosSettingsStore {
|
|
|
10
11
|
update(patch: Readonly<Record<string, unknown>>): void;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
function isSettings(value: unknown): value is KillerosSettings {
|
|
15
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
function readStoredSettings(settingsPath: string): KillerosSettings {
|
|
14
19
|
try {
|
|
15
20
|
const parsed: unknown = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
16
|
-
if (!parsed
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
return parsed as KillerosSettings;
|
|
21
|
+
if (!isSettings(parsed)) throw new Error("KillerOS settings must contain a JSON object");
|
|
22
|
+
return parsed;
|
|
20
23
|
} catch (error) {
|
|
21
|
-
if ((error
|
|
24
|
+
if (hasErrorCode(error, "ENOENT")) return {};
|
|
22
25
|
throw error;
|
|
23
26
|
}
|
|
24
27
|
}
|
package/killeros/shell-ui.ts
CHANGED
|
@@ -25,14 +25,19 @@ import {
|
|
|
25
25
|
} from "./commands.ts";
|
|
26
26
|
import { reportError } from "./errors.ts";
|
|
27
27
|
import { formatModel } from "./footer.ts";
|
|
28
|
-
import { LEVEL_COLORS
|
|
28
|
+
import { LEVEL_COLORS } from "./variants.ts";
|
|
29
29
|
|
|
30
30
|
const COMPACT_HEADER_MAX_WIDTH = 52;
|
|
31
31
|
|
|
32
32
|
function readPackageVersion(path: string | URL): string | undefined {
|
|
33
33
|
try {
|
|
34
|
-
const value = JSON.parse(readFileSync(path, "utf8"))
|
|
35
|
-
return typeof value
|
|
34
|
+
const value: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
35
|
+
return typeof value === "object"
|
|
36
|
+
&& value !== null
|
|
37
|
+
&& "version" in value
|
|
38
|
+
&& typeof value.version === "string"
|
|
39
|
+
? value.version
|
|
40
|
+
: undefined;
|
|
36
41
|
} catch {
|
|
37
42
|
return undefined;
|
|
38
43
|
}
|
|
@@ -93,7 +98,11 @@ function shuffledDeck(values: readonly string[]): string[] {
|
|
|
93
98
|
const deck = [...values];
|
|
94
99
|
for (let index = deck.length - 1; index > 0; index -= 1) {
|
|
95
100
|
const swapIndex = Math.floor(Math.random() * (index + 1));
|
|
96
|
-
|
|
101
|
+
const current = deck[index];
|
|
102
|
+
const swap = deck[swapIndex];
|
|
103
|
+
if (current === undefined || swap === undefined) continue;
|
|
104
|
+
deck[index] = swap;
|
|
105
|
+
deck[swapIndex] = current;
|
|
97
106
|
}
|
|
98
107
|
return deck;
|
|
99
108
|
}
|
|
@@ -152,7 +161,7 @@ class PiStartupHeader {
|
|
|
152
161
|
const innerWidth = panelWidth - 4;
|
|
153
162
|
const version = KILLEROS_VERSION ? theme.fg("dim", ` (v${KILLEROS_VERSION})`) : "";
|
|
154
163
|
const identity = `${theme.fg("dim", "›")} ${theme.fg("text", theme.bold("KillerOS"))}${version}`;
|
|
155
|
-
const thinkingLevel = this.pi.getThinkingLevel()
|
|
164
|
+
const thinkingLevel = this.pi.getThinkingLevel();
|
|
156
165
|
const reasoning = this.ctx.model?.reasoning === false
|
|
157
166
|
? theme.fg("thinkingOff", "no reasoning")
|
|
158
167
|
: theme.fg(LEVEL_COLORS[thinkingLevel], thinkingLevel);
|
package/killeros/variants.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DynamicBorder, type ExtensionAPI, type ExtensionContext, type ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { SelectList, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { safeTerminalText } from "./safe-terminal-text.ts";
|
|
3
4
|
|
|
4
5
|
export type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
5
6
|
|
|
@@ -60,7 +61,7 @@ function supportedLevels(model: ExtensionContext["model"]): ThinkingLevel[] {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
function modelLabel(model: ExtensionContext["model"]): string {
|
|
63
|
-
return model ? `${model.provider}/${model.id}` : "unknown model";
|
|
64
|
+
return model ? safeTerminalText(`${model.provider}/${model.id}`).replaceAll("\n", "") : "unknown model";
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export function registerVariants(pi: ExtensionAPI): void {
|
|
@@ -80,7 +81,7 @@ export function registerVariants(pi: ExtensionAPI): void {
|
|
|
80
81
|
if (args.trim()) {
|
|
81
82
|
const level = resolveThinkingLevel(args);
|
|
82
83
|
if (!level) {
|
|
83
|
-
ctx.ui.notify(`Unknown reasoning level "${args.trim()}". Use: ${ALL_LEVELS.join(", ")}`, "error");
|
|
84
|
+
ctx.ui.notify(`Unknown reasoning level "${safeTerminalText(args.trim()).replaceAll("\n", "")}". Use: ${ALL_LEVELS.join(", ")}`, "error");
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
86
87
|
setLevel(ctx, level);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "killeros",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.19",
|
|
4
4
|
"description": "TUI, goals, and workflow automation for the Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -42,15 +42,15 @@
|
|
|
42
42
|
]
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@earendil-works/pi-ai": ">=0.84.
|
|
46
|
-
"@earendil-works/pi-coding-agent": ">=0.84.
|
|
47
|
-
"@earendil-works/pi-tui": ">=0.84.
|
|
45
|
+
"@earendil-works/pi-ai": ">=0.84.3",
|
|
46
|
+
"@earendil-works/pi-coding-agent": ">=0.84.3",
|
|
47
|
+
"@earendil-works/pi-tui": ">=0.84.3",
|
|
48
48
|
"typebox": ">=1.1.38 <2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@earendil-works/pi-ai": "0.84.
|
|
52
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
53
|
-
"@earendil-works/pi-tui": "0.84.
|
|
51
|
+
"@earendil-works/pi-ai": "0.84.3",
|
|
52
|
+
"@earendil-works/pi-coding-agent": "0.84.3",
|
|
53
|
+
"@earendil-works/pi-tui": "0.84.3",
|
|
54
54
|
"@types/node": "24.12.4",
|
|
55
55
|
"typebox": "1.1.38",
|
|
56
56
|
"typescript": "5.9.3"
|