killeros 2.1.22 → 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 +23 -0
- package/README.md +18 -23
- package/killeros/change-receipt.ts +1 -1
- package/killeros/commands.ts +1 -1
- package/killeros/display.ts +16 -7
- package/killeros/footer.ts +2 -7
- package/killeros/goal-command.ts +10 -107
- package/killeros/goal-interface.ts +48 -227
- package/killeros/goal-runtime.ts +1 -3
- package/killeros/goal-state.ts +29 -83
- package/killeros/hooks.ts +2 -82
- package/killeros/runtime.ts +0 -7
- package/killeros/worked-for.ts +59 -8
- package/package.json +8 -7
- package/killeros/goal-history.ts +0 -71
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ 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
|
+
|
|
18
|
+
## [2.1.23] - 2026-09-04
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Simplified `/goal` to five forms with proof-by-prose and silent file proof, removing configured completion checks.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Kept the automatic 20-turn goal pause silent as documented.
|
|
27
|
+
- Stripped sentence punctuation from unquoted inferred goal paths and picked token units after rounding so 999.6 shows as 1k and 999999 as 1M.
|
|
28
|
+
- Increased change-receipt Git timeouts to five seconds and stopped timeout failures from showing warning notifications.
|
|
29
|
+
|
|
7
30
|
## [2.1.22] - 2026-09-03
|
|
8
31
|
|
|
9
32
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ A TypeScript extension for the [Pi coding agent](https://github.com/earendil-wor
|
|
|
5
5
|
## What you get
|
|
6
6
|
|
|
7
7
|
- A custom TUI: startup card with version, model, provider, working directory, and Git branch; a dark theme with coral accents; a multiline editor with slash-command completion; a footer that tracks model, context, and goal state; settled task receipts with duration and token usage.
|
|
8
|
-
- `/goal`: set an objective and Pi keeps working toward it across turns, compaction, reloads, and branch navigation. New goals
|
|
8
|
+
- `/goal`: set an objective and Pi keeps working toward it across turns, compaction, reloads, and branch navigation. New goals pause after 20 turns; `/goal resume` grants another 20.
|
|
9
9
|
- `/init`: generates a root `AGENTS.md` from repository evidence, preserving compatible existing rules.
|
|
10
10
|
- `/variants`: pick a reasoning level supported by the active model.
|
|
11
11
|
- `/codex-fast`: toggles the `priority` service tier on Codex requests.
|
|
@@ -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,19 +34,17 @@ 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
|
|
|
41
41
|
```text
|
|
42
42
|
/init Generate root AGENTS.md from repository evidence
|
|
43
|
-
/goal
|
|
44
|
-
/goal
|
|
45
|
-
/goal
|
|
46
|
-
/goal
|
|
47
|
-
/goal
|
|
48
|
-
/goal history [count] Show transitions and blocker evidence on this branch
|
|
49
|
-
/goal edit|pause|resume|clear
|
|
43
|
+
/goal View the current goal
|
|
44
|
+
/goal <objective> Set an objective
|
|
45
|
+
/goal pause Stop automatic continuation
|
|
46
|
+
/goal resume Resume automatic continuation
|
|
47
|
+
/goal clear Remove the current goal
|
|
50
48
|
/variants Reasoning-level selector (/variants high sets directly)
|
|
51
49
|
/codex-fast Toggle Codex fast mode
|
|
52
50
|
/notification Configure the completion sound
|
|
@@ -60,7 +58,7 @@ Pin a release by appending its tag, for example `@v2.1.22`. Add `-l` to install
|
|
|
60
58
|
| Mode | What works |
|
|
61
59
|
| --- | --- |
|
|
62
60
|
| TUI | Everything |
|
|
63
|
-
| RPC | Goals, proactive compaction; no TUI components, `/
|
|
61
|
+
| RPC | Goals, proactive compaction; no TUI components, `/init`, sounds, title indicator |
|
|
64
62
|
| Print/JSON | No interactive questions, `/goal`, `/init`, or proactive compaction |
|
|
65
63
|
|
|
66
64
|
## Configuration
|
|
@@ -79,22 +77,19 @@ The packaged `killeros` theme activates on TUI start. Compaction triggers by def
|
|
|
79
77
|
|
|
80
78
|
`handoffMaxTokens` caps the `/handoff` summary output at 8192 tokens by default; raise it when long sessions truncate the summary.
|
|
81
79
|
|
|
82
|
-
|
|
80
|
+
State proof in the objective so the agent can verify it with its normal tools:
|
|
83
81
|
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
"goalChecks": {
|
|
87
|
-
"quality": {
|
|
88
|
-
"command": "npm run check && npm test",
|
|
89
|
-
"timeoutMs": 300000
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
82
|
+
```text
|
|
83
|
+
/goal Reduce p95 checkout latency below 120 ms, verified by the checkout benchmark, while keeping the correctness suite green
|
|
93
84
|
```
|
|
94
85
|
|
|
95
|
-
|
|
86
|
+
A direct quoted file target binds silent file proof:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
/goal Fix `killeros/footer.ts`, verified by npm test
|
|
90
|
+
```
|
|
96
91
|
|
|
97
|
-
|
|
92
|
+
KillerOS captures the file baseline at goal start and only completes when the file is created or changed. New goals pause after 20 turns without warning. An explicit `/goal resume` on an exhausted goal grants another 20 turns; compaction recovery never grants turns. Restored goals keep their persisted limit.
|
|
98
93
|
|
|
99
94
|
Completion sounds are off by default; change with `/notification` in TUI mode. The tab-title indicator requires a Nerd Font.
|
|
100
95
|
|
|
@@ -5,7 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { inflate } from "node:zlib";
|
|
7
7
|
|
|
8
|
-
const GIT_TIMEOUT_MS =
|
|
8
|
+
const GIT_TIMEOUT_MS = 5_000;
|
|
9
9
|
const GIT_OUTPUT_LIMIT = 16 * 1024 * 1024;
|
|
10
10
|
const SNAPSHOT_CONTENT_LIMIT = 128 * 1024 * 1024;
|
|
11
11
|
const MAX_DIFF_OPERATIONS = 500_000;
|
package/killeros/commands.ts
CHANGED
|
@@ -70,7 +70,7 @@ const BUILTIN_COMMANDS: ReadonlyArray<{ name: string; description: string }> = [
|
|
|
70
70
|
];
|
|
71
71
|
|
|
72
72
|
const COMMAND_SYNTAX_HINTS: Readonly<Record<string, string>> = {
|
|
73
|
-
goal: "/goal [objective|
|
|
73
|
+
goal: "/goal [objective|pause|resume|clear]",
|
|
74
74
|
handoff: "/handoff [next-session focus]",
|
|
75
75
|
variants: "/variants [level]",
|
|
76
76
|
model: "/model [provider/model]",
|
package/killeros/display.ts
CHANGED
|
@@ -40,12 +40,21 @@ export function formatTime(milliseconds: number): string {
|
|
|
40
40
|
|
|
41
41
|
export function formatTokens(value: number): string {
|
|
42
42
|
if (!Number.isFinite(value)) return "0";
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
if (
|
|
46
|
-
const precision =
|
|
47
|
-
return `${Number((
|
|
43
|
+
const rounded = Math.round(Math.max(0, value));
|
|
44
|
+
if (rounded < 1_000) return `${rounded}`;
|
|
45
|
+
if (rounded >= 1_000_000) {
|
|
46
|
+
const precision = rounded >= 10_000_000 ? 0 : 1;
|
|
47
|
+
return `${Number((rounded / 1_000_000).toFixed(precision))}M`;
|
|
48
48
|
}
|
|
49
|
-
const precision =
|
|
50
|
-
|
|
49
|
+
const precision = rounded >= 100_000 ? 0 : 1;
|
|
50
|
+
const inK = Number((rounded / 1_000).toFixed(precision));
|
|
51
|
+
if (inK >= 1_000) return `${Number((rounded / 1_000_000).toFixed(1))}M`;
|
|
52
|
+
return `${inK}k`;
|
|
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();
|
|
51
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/goal-command.ts
CHANGED
|
@@ -1,120 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { GOAL_CHECK_NAME_PATTERN, GOAL_MAX_TURNS, validateGoalObjective } from "./goal-state.ts";
|
|
3
|
-
|
|
4
|
-
const GOAL_START_USAGE = "Usage: /goal start [--check <name>] [--turns <count>] -- <objective>";
|
|
5
|
-
const GOAL_CHECK_USAGE = "Usage: /goal check <name|clear>";
|
|
6
|
-
const GOAL_CHECKS_USAGE = "Usage: /goal checks";
|
|
7
|
-
const GOAL_LIMIT_USAGE = "Usage: /goal limit <count|clear>";
|
|
8
|
-
const GOAL_HISTORY_USAGE = "Usage: /goal history [count]";
|
|
9
|
-
const RESERVED_OBJECTIVE_USAGE = "Objective begins with a reserved goal command. Use /goal start -- <objective>.";
|
|
10
|
-
|
|
11
|
-
interface ControlledGoalStart {
|
|
12
|
-
objective: string;
|
|
13
|
-
completionCheckName?: string;
|
|
14
|
-
maxTurns?: number;
|
|
15
|
-
}
|
|
1
|
+
import { validateGoalObjective } from "./goal-state.ts";
|
|
16
2
|
|
|
17
3
|
export type GoalCommand =
|
|
18
4
|
| { kind: "status" }
|
|
19
5
|
| { kind: "objective"; objective: string }
|
|
20
|
-
| { kind: "start"; objective: string; completionCheckName?: string; maxTurns?: number }
|
|
21
|
-
| { kind: "check"; value: { kind: "clear" } | { kind: "named"; name: string } }
|
|
22
|
-
| { kind: "checks" }
|
|
23
|
-
| { kind: "limit"; value: { kind: "clear" } | { kind: "count"; count: number } }
|
|
24
|
-
| { kind: "history"; count: number }
|
|
25
|
-
| { kind: "clear" }
|
|
26
|
-
| { kind: "edit" }
|
|
27
6
|
| { kind: "pause" }
|
|
28
7
|
| { kind: "resume" }
|
|
8
|
+
| { kind: "clear" }
|
|
29
9
|
| { kind: "invalid"; message: string };
|
|
30
10
|
|
|
31
|
-
const RESERVED_GOAL_WORDS = ["start", "check", "checks", "limit", "history", "clear", "edit", "pause", "resume"] as const;
|
|
32
|
-
|
|
33
|
-
function parseBoundedInteger(value: string, maximum: number): number | undefined {
|
|
34
|
-
if (!/^[1-9][0-9]*$/u.test(value)) return undefined;
|
|
35
|
-
const parsed = Number(value);
|
|
36
|
-
return Number.isSafeInteger(parsed) && parsed <= maximum ? parsed : undefined;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function parseControlledGoalStart(input: string): ControlledGoalStart | undefined {
|
|
40
|
-
const separator = input.startsWith("-- ") ? 0 : input.indexOf(" -- ");
|
|
41
|
-
if (separator < 0) return undefined;
|
|
42
|
-
const optionText = input.slice(0, separator).trim();
|
|
43
|
-
const objective = validateGoalObjective(input.slice(separator + (separator === 0 ? 3 : 4)));
|
|
44
|
-
if (!objective) return undefined;
|
|
45
|
-
const tokens = optionText ? optionText.split(/\s+/u) : [];
|
|
46
|
-
if (tokens.filter((token) => token === "--check" || token.startsWith("--check=")).length > 1
|
|
47
|
-
|| tokens.filter((token) => token === "--turns" || token.startsWith("--turns=")).length > 1) return undefined;
|
|
48
|
-
try {
|
|
49
|
-
const parsed = parseArgs({
|
|
50
|
-
args: tokens,
|
|
51
|
-
options: { check: { type: "string" }, turns: { type: "string" } },
|
|
52
|
-
strict: true,
|
|
53
|
-
allowPositionals: false,
|
|
54
|
-
});
|
|
55
|
-
const check = parsed.values.check;
|
|
56
|
-
const turns = parsed.values.turns;
|
|
57
|
-
if (check !== undefined && !GOAL_CHECK_NAME_PATTERN.test(check)) return undefined;
|
|
58
|
-
const maxTurns = turns === undefined ? undefined : parseBoundedInteger(turns, GOAL_MAX_TURNS);
|
|
59
|
-
if (turns !== undefined && maxTurns === undefined) return undefined;
|
|
60
|
-
return {
|
|
61
|
-
objective,
|
|
62
|
-
...(check === undefined ? {} : { completionCheckName: check }),
|
|
63
|
-
...(maxTurns === undefined ? {} : { maxTurns }),
|
|
64
|
-
};
|
|
65
|
-
} catch {
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
11
|
/** Parses one raw /goal argument string into a closed command variant. */
|
|
71
12
|
export function parseGoalCommand(args: string): GoalCommand {
|
|
72
13
|
const input = args.trim();
|
|
73
14
|
if (!input) return { kind: "status" };
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (firstWord === "start") {
|
|
85
|
-
const start = parseControlledGoalStart(input.slice(rawFirstWord.length).trimStart());
|
|
86
|
-
if (start) return { kind: "start", ...start };
|
|
87
|
-
return { kind: "invalid", message: rawFirstWord === firstWord ? GOAL_START_USAGE : RESERVED_OBJECTIVE_USAGE };
|
|
88
|
-
}
|
|
89
|
-
if (firstWord === "check") {
|
|
90
|
-
const value = parts[1];
|
|
91
|
-
if (parts.length === 2 && value?.toLowerCase() === "clear") return { kind: "check", value: { kind: "clear" } };
|
|
92
|
-
if (parts.length === 2 && value && GOAL_CHECK_NAME_PATTERN.test(value)) return { kind: "check", value: { kind: "named", name: value } };
|
|
93
|
-
return { kind: "invalid", message: rawFirstWord !== firstWord && parts.length > 2 ? RESERVED_OBJECTIVE_USAGE : GOAL_CHECK_USAGE };
|
|
94
|
-
}
|
|
95
|
-
if (firstWord === "checks") {
|
|
96
|
-
return parts.length === 1
|
|
97
|
-
? { kind: "checks" }
|
|
98
|
-
: { kind: "invalid", message: rawFirstWord === firstWord ? GOAL_CHECKS_USAGE : RESERVED_OBJECTIVE_USAGE };
|
|
99
|
-
}
|
|
100
|
-
if (firstWord === "limit") {
|
|
101
|
-
const value = parts[1];
|
|
102
|
-
if (parts.length === 2 && value?.toLowerCase() === "clear") return { kind: "limit", value: { kind: "clear" } };
|
|
103
|
-
const count = parts.length === 2 && value ? parseBoundedInteger(value, GOAL_MAX_TURNS) : undefined;
|
|
104
|
-
if (count !== undefined) return { kind: "limit", value: { kind: "count", count } };
|
|
105
|
-
return { kind: "invalid", message: rawFirstWord !== firstWord && parts.length > 2 ? RESERVED_OBJECTIVE_USAGE : GOAL_LIMIT_USAGE };
|
|
106
|
-
}
|
|
107
|
-
if (firstWord === "history") {
|
|
108
|
-
if (parts.length === 1) return { kind: "history", count: 20 };
|
|
109
|
-
const count = parts.length === 2 && parts[1] ? parseBoundedInteger(parts[1], 50) : undefined;
|
|
110
|
-
if (count !== undefined) return { kind: "history", count };
|
|
111
|
-
return { kind: "invalid", message: rawFirstWord !== firstWord && parts.length > 2 ? RESERVED_OBJECTIVE_USAGE : GOAL_HISTORY_USAGE };
|
|
112
|
-
}
|
|
113
|
-
if (parts.length === 1) return { kind: firstWord };
|
|
114
|
-
return {
|
|
115
|
-
kind: "invalid",
|
|
116
|
-
message: rawFirstWord === firstWord
|
|
117
|
-
? `Usage: /goal ${firstWord}`
|
|
118
|
-
: RESERVED_OBJECTIVE_USAGE,
|
|
119
|
-
};
|
|
15
|
+
const lowered = input.toLowerCase();
|
|
16
|
+
if (lowered === "pause") return { kind: "pause" };
|
|
17
|
+
if (lowered === "resume") return { kind: "resume" };
|
|
18
|
+
if (lowered === "clear") return { kind: "clear" };
|
|
19
|
+
const objective = validateGoalObjective(input);
|
|
20
|
+
return objective
|
|
21
|
+
? { kind: "objective", objective }
|
|
22
|
+
: { kind: "invalid", message: "A goal objective may not exceed 4,000 characters" };
|
|
120
23
|
}
|