killeros 2.1.23 → 2.1.24
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 +11 -0
- package/README.md +2 -2
- package/killeros/display.ts +7 -0
- package/killeros/footer.ts +2 -7
- package/killeros/worked-for.ts +58 -7
- package/package.json +8 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ All notable changes to KillerOS are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.1.24] - 2026-09-05
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Appended the settled model display name to version 4 worked-for receipts for single-model TUI runs.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Rendered settled `Done` receipts without the `✓` marker, keeping `■ Stopped`, `× Failed`, and version 1 history unchanged.
|
|
16
|
+
- Raised the locked Pi development packages and minimum supported Pi peer version to 0.85.0, including `@earendil-works/pi-server`.
|
|
17
|
+
|
|
7
18
|
## [2.1.23] - 2026-09-04
|
|
8
19
|
|
|
9
20
|
### Changed
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ A TypeScript extension for the [Pi coding agent](https://github.com/earendil-wor
|
|
|
19
19
|
## Requirements
|
|
20
20
|
|
|
21
21
|
- Node.js 22.19.0+
|
|
22
|
-
- Pi 0.
|
|
22
|
+
- Pi 0.85.0 or later within the 0.x release line
|
|
23
23
|
- An interactive TUI session for the custom header, editor, footer, `question`, and `/init`
|
|
24
24
|
|
|
25
25
|
## Install
|
|
@@ -34,7 +34,7 @@ Or from GitHub:
|
|
|
34
34
|
pi install git:github.com/KyrosHendrix/pi-KillerOS
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Pin a release by appending its tag, for example `@v2.1.
|
|
37
|
+
Pin a release by appending its tag, for example `@v2.1.24`. Add `-l` to install only for the current project. Restart Pi after installing.
|
|
38
38
|
|
|
39
39
|
## Commands
|
|
40
40
|
|
package/killeros/display.ts
CHANGED
|
@@ -51,3 +51,10 @@ export function formatTokens(value: number): string {
|
|
|
51
51
|
if (inK >= 1_000) return `${Number((rounded / 1_000_000).toFixed(1))}M`;
|
|
52
52
|
return `${inK}k`;
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
/** Resolves a terminal-safe model display name, preferring the name over the id. */
|
|
56
|
+
export function modelDisplayName(model: { name?: string; id?: string }): string {
|
|
57
|
+
const name = safeTerminalText(model.name ?? "").replaceAll("\n", "").trim();
|
|
58
|
+
if (name) return name;
|
|
59
|
+
return safeTerminalText(model.id ?? "").replaceAll("\n", "").trim();
|
|
60
|
+
}
|
package/killeros/footer.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { watch } from "node:fs";
|
|
|
3
3
|
import { type ExtensionAPI, type ExtensionContext, type Theme, type ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { truncateToWidth, visibleWidth, type TUI } from "@earendil-works/pi-tui";
|
|
5
5
|
import { isCodexFastEnabled, subscribeCodexFast } from "./codex-fast-state.ts";
|
|
6
|
-
import { formatCwd, formatTime, formatTokens, padRight } from "./display.ts";
|
|
6
|
+
import { formatCwd, formatTime, formatTokens, modelDisplayName, padRight } from "./display.ts";
|
|
7
7
|
import { goalElapsedMilliseconds } from "./goal-state.ts";
|
|
8
8
|
import type { GoalRuntime, GoalState } from "./runtime.ts";
|
|
9
9
|
import { safeTerminalText } from "./safe-terminal-text.ts";
|
|
@@ -296,11 +296,6 @@ function formatProviderName(provider: string): string {
|
|
|
296
296
|
.join(" ") || "Unknown provider";
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
function modelDisplayName(model: NonNullable<ExtensionContext["model"]>): string {
|
|
300
|
-
const name = safeTerminalText(model.name ?? "").replaceAll("\n", "").trim();
|
|
301
|
-
return name || safeTerminalText(model.id).replaceAll("\n", "").trim() || "Unknown model";
|
|
302
|
-
}
|
|
303
|
-
|
|
304
299
|
export function formatModel(
|
|
305
300
|
model: ExtensionContext["model"],
|
|
306
301
|
theme: Theme,
|
|
@@ -308,7 +303,7 @@ export function formatModel(
|
|
|
308
303
|
showCodexFast = false,
|
|
309
304
|
): string {
|
|
310
305
|
if (!model) return theme.fg("dim", "No model");
|
|
311
|
-
const name = theme.fg("text", theme.bold(modelDisplayName(model)));
|
|
306
|
+
const name = theme.fg("text", theme.bold(modelDisplayName(model) || "Unknown model"));
|
|
312
307
|
const fast = showCodexFast && model.provider === CODEX_PROVIDER
|
|
313
308
|
? theme.fg("accent", theme.bold("Fast"))
|
|
314
309
|
: "";
|
package/killeros/worked-for.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type ChangedFile,
|
|
13
13
|
type CheckAttempt,
|
|
14
14
|
} from "./change-receipt.ts";
|
|
15
|
-
import { formatTokens } from "./display.ts";
|
|
15
|
+
import { formatTokens, modelDisplayName } from "./display.ts";
|
|
16
16
|
import { errorMessage } from "./errors.ts";
|
|
17
17
|
import { safeTerminalText } from "./safe-terminal-text.ts";
|
|
18
18
|
|
|
@@ -47,15 +47,16 @@ export interface WorkedForEntryDataV4 {
|
|
|
47
47
|
changes: ChangeSummary;
|
|
48
48
|
checks: CheckAttempt[];
|
|
49
49
|
omittedChecks: { passed: number; failed: number };
|
|
50
|
+
model?: string;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
type WorkedForEntryData = WorkedForEntryDataV1 | WorkedForEntryDataV2 | WorkedForEntryDataV3 | WorkedForEntryDataV4;
|
|
53
54
|
|
|
54
55
|
const OUTCOMES = {
|
|
55
|
-
done: {
|
|
56
|
-
stopped: {
|
|
57
|
-
failed: {
|
|
58
|
-
} as const satisfies Record<WorkedForOutcome, {
|
|
56
|
+
done: { label: "Done", color: "success" },
|
|
57
|
+
stopped: { label: "■ Stopped", color: "warning" },
|
|
58
|
+
failed: { label: "× Failed", color: "error" },
|
|
59
|
+
} as const satisfies Record<WorkedForOutcome, { label: string; color: string }>;
|
|
59
60
|
|
|
60
61
|
function isWorkedForOutcome(value: unknown): value is WorkedForOutcome {
|
|
61
62
|
return value === "done" || value === "stopped" || value === "failed";
|
|
@@ -121,6 +122,12 @@ function parseChanges(value: unknown): ChangeSummary | undefined {
|
|
|
121
122
|
};
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
function parseModelName(value: unknown): string | undefined {
|
|
126
|
+
if (typeof value !== "string" || value.length > 200) return undefined;
|
|
127
|
+
const sanitized = safeTerminalText(value).replaceAll("\n", "").trim();
|
|
128
|
+
return sanitized || undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
124
131
|
function parseV4(data: Record<string, unknown>): WorkedForEntryDataV4 | undefined {
|
|
125
132
|
try {
|
|
126
133
|
if (Buffer.byteLength(JSON.stringify(data), "utf8") > MAX_PAYLOAD_BYTES) return undefined;
|
|
@@ -139,6 +146,7 @@ function parseV4(data: Record<string, unknown>): WorkedForEntryDataV4 | undefine
|
|
|
139
146
|
if (!label) return undefined;
|
|
140
147
|
checks.push({ label, outcome: check.outcome });
|
|
141
148
|
}
|
|
149
|
+
const model = parseModelName(data.model);
|
|
142
150
|
return {
|
|
143
151
|
version: 4,
|
|
144
152
|
milliseconds: data.milliseconds,
|
|
@@ -147,6 +155,7 @@ function parseV4(data: Record<string, unknown>): WorkedForEntryDataV4 | undefine
|
|
|
147
155
|
changes,
|
|
148
156
|
checks,
|
|
149
157
|
omittedChecks: { passed: data.omittedChecks.passed, failed: data.omittedChecks.failed },
|
|
158
|
+
...(model ? { model } : {}),
|
|
150
159
|
};
|
|
151
160
|
}
|
|
152
161
|
|
|
@@ -210,8 +219,10 @@ class WorkedForV4Component implements Component {
|
|
|
210
219
|
if (width <= 0) return [];
|
|
211
220
|
const { data, theme } = this;
|
|
212
221
|
const outcome = OUTCOMES[data.outcome];
|
|
222
|
+
const headline = theme.fg(outcome.color, outcome.label);
|
|
223
|
+
const modelSuffix = data.model ? ` · ${data.model}` : "";
|
|
213
224
|
const lines = [
|
|
214
|
-
`${
|
|
225
|
+
`${headline}${theme.fg("dim", ` · ${formatWorkedForDuration(data.milliseconds)} · ↑ ${formatTokens(data.tokens)} tokens${modelSuffix}`)}`,
|
|
215
226
|
];
|
|
216
227
|
if (data.changes.state === "unavailable") lines.push(theme.fg("dim", " Changes unavailable"));
|
|
217
228
|
else if (data.changes.totalFiles === 0) lines.push(theme.fg("dim", " No files changed"));
|
|
@@ -272,8 +283,22 @@ type ActiveReceipt = {
|
|
|
272
283
|
collection: Promise<ChangeReceiptCollection>;
|
|
273
284
|
checks: CheckAttempt[];
|
|
274
285
|
omittedChecks: { passed: number; failed: number };
|
|
286
|
+
modelProvider: string | undefined;
|
|
287
|
+
modelId: string | undefined;
|
|
288
|
+
modelMismatch: boolean;
|
|
275
289
|
};
|
|
276
290
|
|
|
291
|
+
function receiptModelName(
|
|
292
|
+
settled: ActiveReceipt,
|
|
293
|
+
model: ExtensionContext["model"],
|
|
294
|
+
): string | undefined {
|
|
295
|
+
if (settled.modelMismatch || settled.modelProvider === undefined || settled.modelId === undefined) return undefined;
|
|
296
|
+
if (!model || model.provider !== settled.modelProvider || model.id !== settled.modelId) return undefined;
|
|
297
|
+
const resolved = modelDisplayName(model);
|
|
298
|
+
if (!resolved || resolved.length > 200) return undefined;
|
|
299
|
+
return resolved;
|
|
300
|
+
}
|
|
301
|
+
|
|
277
302
|
function fitPayload(data: WorkedForEntryDataV4): WorkedForEntryDataV4 {
|
|
278
303
|
if (data.changes.state === "unavailable") return data;
|
|
279
304
|
const changes = { ...data.changes, files: [...data.changes.files] };
|
|
@@ -300,7 +325,8 @@ export function registerWorkedFor(
|
|
|
300
325
|
if (data.version === 4) return new WorkedForV4Component(data, options.expanded, theme);
|
|
301
326
|
const outcome = OUTCOMES[data.outcome];
|
|
302
327
|
const tokens = data.version === 3 ? ` · ↑ ${formatTokens(data.tokens)} tokens` : "";
|
|
303
|
-
|
|
328
|
+
const headline = theme.fg(outcome.color, outcome.label);
|
|
329
|
+
return new Text(`${headline}${theme.fg("dim", ` · ${formatWorkedForDuration(data.milliseconds)}${tokens}`)}`, 0, 0);
|
|
304
330
|
});
|
|
305
331
|
|
|
306
332
|
pi.on("session_start", async () => {
|
|
@@ -319,12 +345,35 @@ export function registerWorkedFor(
|
|
|
319
345
|
collection: collect(ctx.cwd),
|
|
320
346
|
checks: [],
|
|
321
347
|
omittedChecks: { passed: 0, failed: 0 },
|
|
348
|
+
modelProvider: undefined,
|
|
349
|
+
modelId: undefined,
|
|
350
|
+
modelMismatch: false,
|
|
322
351
|
};
|
|
323
352
|
active = state;
|
|
324
353
|
const collection = await state.collection;
|
|
325
354
|
if (active !== state) await collection.dispose();
|
|
326
355
|
});
|
|
327
356
|
|
|
357
|
+
pi.on("message_end", (event, ctx) => {
|
|
358
|
+
if (ctx.mode !== "tui" || !active) return;
|
|
359
|
+
if (event.message.role !== "assistant") return;
|
|
360
|
+
const provider: unknown = event.message.provider;
|
|
361
|
+
const modelId: unknown = event.message.model;
|
|
362
|
+
if (typeof provider !== "string" || typeof modelId !== "string") {
|
|
363
|
+
active.modelMismatch = true;
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (active.modelProvider === undefined || active.modelId === undefined) {
|
|
367
|
+
active.modelProvider = provider;
|
|
368
|
+
active.modelId = modelId;
|
|
369
|
+
} else if (active.modelProvider !== provider || active.modelId !== modelId) {
|
|
370
|
+
active.modelMismatch = true;
|
|
371
|
+
}
|
|
372
|
+
if (event.message.responseModel !== undefined && event.message.responseModel !== modelId) {
|
|
373
|
+
active.modelMismatch = true;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
328
377
|
pi.on("tool_result", (event: ToolResultEvent, ctx) => {
|
|
329
378
|
if (ctx.mode !== "tui" || !active || event.toolName !== "bash" && event.toolName !== "powershell") return;
|
|
330
379
|
const check = recognizedCheck(event.input.command, event.isError);
|
|
@@ -347,6 +396,7 @@ export function registerWorkedFor(
|
|
|
347
396
|
if (ctx.mode !== "tui" || !active) return;
|
|
348
397
|
const settled = active;
|
|
349
398
|
active = undefined;
|
|
399
|
+
const model = receiptModelName(settled, ctx.model);
|
|
350
400
|
const changes = await (await settled.collection).finish();
|
|
351
401
|
if (changes.state === "unavailable" && changes.reason !== "not-git" && changes.reason !== "timeout" && !collectionNoticeShown) {
|
|
352
402
|
collectionNoticeShown = true;
|
|
@@ -361,6 +411,7 @@ export function registerWorkedFor(
|
|
|
361
411
|
changes,
|
|
362
412
|
checks: settled.checks,
|
|
363
413
|
omittedChecks: settled.omittedChecks,
|
|
414
|
+
...(model ? { model } : {}),
|
|
364
415
|
});
|
|
365
416
|
try {
|
|
366
417
|
pi.appendEntry<WorkedForEntryDataV4>(WORKED_FOR_ENTRY_TYPE, data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "killeros",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.24",
|
|
4
4
|
"description": "TUI, goals, and workflow automation for the Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -44,15 +44,16 @@
|
|
|
44
44
|
]
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@earendil-works/pi-ai": ">=0.
|
|
48
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
49
|
-
"@earendil-works/pi-tui": ">=0.
|
|
47
|
+
"@earendil-works/pi-ai": ">=0.85.0 <1",
|
|
48
|
+
"@earendil-works/pi-coding-agent": ">=0.85.0 <1",
|
|
49
|
+
"@earendil-works/pi-tui": ">=0.85.0 <1",
|
|
50
50
|
"typebox": ">=1.1.38 <2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@earendil-works/pi-ai": "0.
|
|
54
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
55
|
-
"@earendil-works/pi-
|
|
53
|
+
"@earendil-works/pi-ai": "0.85.0",
|
|
54
|
+
"@earendil-works/pi-coding-agent": "0.85.0",
|
|
55
|
+
"@earendil-works/pi-server": "0.85.0",
|
|
56
|
+
"@earendil-works/pi-tui": "0.85.0",
|
|
56
57
|
"@types/node": "24.12.4",
|
|
57
58
|
"eslint": "^10.9.1",
|
|
58
59
|
"typebox": "1.3.20",
|