killeros 2.0.18 → 2.0.20
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 +24 -0
- package/Killeros.ts +3 -1
- package/README.md +5 -2
- package/killeros/activity.ts +4 -0
- package/killeros/auto-compaction.ts +40 -10
- package/killeros/codex-fast-state.ts +5 -7
- package/killeros/commands.ts +5 -3
- package/killeros/errors.ts +8 -0
- package/killeros/footer.ts +3 -3
- package/killeros/goals.ts +326 -116
- package/killeros/handoff.ts +40 -10
- package/killeros/hooks.ts +121 -48
- package/killeros/init-evidence.ts +4 -2
- package/killeros/init-target.ts +28 -20
- package/killeros/init.ts +12 -5
- package/killeros/personal-instructions.ts +3 -4
- package/killeros/question.ts +15 -8
- package/killeros/runtime.ts +42 -7
- package/killeros/settings.ts +8 -5
- package/killeros/shell-ui.ts +14 -6
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ All notable changes to KillerOS are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.0.20] - 2026-08-25
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Added a typescript-eslint type-checked lint gate (`npm run lint`) alongside `tsc --noEmit`, and cleaned up the dead imports and untyped boundaries it surfaced.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Treated Pi's exact `Nothing to compact (session too small)` rejection as an expected automatic-compaction skip: no failure notification, silent retry on the next eligible turn, and active goals resume through a dedicated skip recovery instead of pausing for `/goal resume`.
|
|
16
|
+
- Made `/handoff` resilient to an unreadable `killeros.json`: explicit `KillerosOptions` budgets bypass the file, while other summaries fall back to the default budget with a visible warning.
|
|
17
|
+
- Named `/handoff` output truncation instead of reporting "did not finish", raised the default summary budget to 8192 tokens for reasoning models, and made the budget configurable through `KillerosOptions` or the `killeros.json` `handoffMaxTokens` key.
|
|
18
|
+
|
|
19
|
+
## [2.0.19] - 2026-08-24
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Hardened JSON and caught-error boundaries, made goal and `/init` target states constructive, added exhaustive outcome handling, and replaced unsafe test doubles with checked adapters.
|
|
24
|
+
- Deprecated positional `executeHook` arguments in favor of object options; the compatibility adapter will be removed in the next major release.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Paused active goals during automatic compaction and resumed the exact paused revision once after compaction and turn settlement succeed.
|
|
29
|
+
- Restored active goals saved by v2.0.18 shutdown checkpoints that omitted their stopped clock timestamp.
|
|
30
|
+
|
|
7
31
|
## [2.0.18] - 2026-08-24
|
|
8
32
|
|
|
9
33
|
### Added
|
package/Killeros.ts
CHANGED
|
@@ -30,6 +30,8 @@ export { buildInitEvidence, listInitEvidence, readInitEvidence } from "./killero
|
|
|
30
30
|
export { captureInitTargetBaseline, installInitAgentsFile, validateGeneratedGuidance, writeInitAgentsFile } from "./killeros/init-target.ts";
|
|
31
31
|
export interface KillerosOptions {
|
|
32
32
|
completionNotifications?: CompletionNotificationDependencies;
|
|
33
|
+
/** Output-token budget for /handoff summaries; invalid values fall back to killeros.json, then the default. */
|
|
34
|
+
handoffMaxTokens?: number;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export default function Killeros(pi: ExtensionAPI, options: KillerosOptions = {}): void {
|
|
@@ -41,7 +43,7 @@ export default function Killeros(pi: ExtensionAPI, options: KillerosOptions = {}
|
|
|
41
43
|
registerPersonalInstructions(pi, initRuntime);
|
|
42
44
|
registerQuestionTool(pi);
|
|
43
45
|
registerAliases(pi);
|
|
44
|
-
registerHandoff(pi, goalRuntime);
|
|
46
|
+
registerHandoff(pi, goalRuntime, options.handoffMaxTokens);
|
|
45
47
|
registerSlashAutocomplete(pi, commandResolver);
|
|
46
48
|
registerFooter(pi, goalRuntime);
|
|
47
49
|
registerVariants(pi);
|
package/README.md
CHANGED
|
@@ -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.0.
|
|
37
|
+
Pin a release by appending its tag, for example `@v2.0.20`. Add `-l` to install only for the current project. Restart Pi after installing.
|
|
38
38
|
|
|
39
39
|
## Commands
|
|
40
40
|
|
|
@@ -67,10 +67,13 @@ The packaged `killeros` theme activates on TUI start. Compaction triggers by def
|
|
|
67
67
|
"autoCompaction": {
|
|
68
68
|
"enabled": true,
|
|
69
69
|
"percentRemaining": 15
|
|
70
|
-
}
|
|
70
|
+
},
|
|
71
|
+
"handoffMaxTokens": 8192
|
|
71
72
|
}
|
|
72
73
|
```
|
|
73
74
|
|
|
75
|
+
`handoffMaxTokens` caps the `/handoff` summary output at 8192 tokens by default; raise it when long sessions truncate the summary.
|
|
76
|
+
|
|
74
77
|
Completion sounds are off by default; change with `/notification` in TUI mode. The tab-title indicator requires a Nerd Font.
|
|
75
78
|
|
|
76
79
|
## Development
|
package/killeros/activity.ts
CHANGED
|
@@ -52,6 +52,10 @@ export function formatActivityMessage(message: ActivityMessage, theme: Theme): s
|
|
|
52
52
|
detail = `using ${safeToolName(message.toolName)}`;
|
|
53
53
|
}
|
|
54
54
|
break;
|
|
55
|
+
default: {
|
|
56
|
+
const exhaustive: never = message;
|
|
57
|
+
return exhaustive;
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
return `${theme.fg("accent", verb)} ${theme.fg("dim", `(${theme.bold("esc")} to interrupt · ${detail})`)}`;
|
|
@@ -12,6 +12,8 @@ import { createKillerosSettingsStore } from "./settings.ts";
|
|
|
12
12
|
export const DEFAULT_AUTO_COMPACTION_PERCENT_REMAINING = 15;
|
|
13
13
|
export const AUTO_COMPACTION_MESSAGE_TYPE = "killeros-auto-compaction";
|
|
14
14
|
export const AUTO_COMPACTION_MESSAGE = "Continue the interrupted task from the compacted context.";
|
|
15
|
+
/** Pi's exact rejection when a session has no eligible history to summarize; an expected skip, not a failure. */
|
|
16
|
+
export const SESSION_TOO_SMALL_COMPACTION_ERROR = "Nothing to compact (session too small)";
|
|
15
17
|
|
|
16
18
|
export interface AutoCompactionPreference {
|
|
17
19
|
enabled: boolean;
|
|
@@ -23,6 +25,8 @@ export interface AutoCompactionGoalHandlers {
|
|
|
23
25
|
onRequested(): void;
|
|
24
26
|
onCompleted(ctx: ExtensionContext): void;
|
|
25
27
|
onFailed(ctx: ExtensionContext, error: unknown): void;
|
|
28
|
+
/** Recovers the goal paused for a request Pi rejected as session-too-small, without claiming success. */
|
|
29
|
+
onSkipped(ctx: ExtensionContext): void;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export interface AutoCompactionDependencies {
|
|
@@ -72,6 +76,11 @@ function reserveTokens(settings: Pick<CompactionSettings, "reserveTokens">): num
|
|
|
72
76
|
: 0;
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
/** Matches only Pi's exact rejection text on a real Error; near-matches and stringified values stay failures. */
|
|
80
|
+
export function isSessionTooSmallCompactionError(error: unknown): boolean {
|
|
81
|
+
return error instanceof Error && error.message === SESSION_TOO_SMALL_COMPACTION_ERROR;
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
/** Triggers at the stricter of the user's percentage and Pi's token reserve. */
|
|
76
85
|
export function shouldTriggerAutoCompaction(
|
|
77
86
|
usage: Pick<ContextUsage, "tokens" | "contextWindow"> | undefined,
|
|
@@ -116,13 +125,7 @@ export function registerAutoCompaction(
|
|
|
116
125
|
ctx.ui.notify(`Automatic compaction failed: ${errorMessage(error)}`, "error");
|
|
117
126
|
};
|
|
118
127
|
|
|
119
|
-
const finishFailure = (
|
|
120
|
-
ctx: ExtensionContext,
|
|
121
|
-
token: symbol,
|
|
122
|
-
goal: boolean,
|
|
123
|
-
error: unknown,
|
|
124
|
-
): void => {
|
|
125
|
-
if (!request || request.token !== token) return;
|
|
128
|
+
const finishFailure = (ctx: ExtensionContext, goal: boolean, error: unknown): void => {
|
|
126
129
|
request = undefined;
|
|
127
130
|
if (goal && dependencies.goal) {
|
|
128
131
|
try {
|
|
@@ -135,6 +138,33 @@ export function registerAutoCompaction(
|
|
|
135
138
|
notifyFailure(ctx, error);
|
|
136
139
|
};
|
|
137
140
|
|
|
141
|
+
/** Ends a request with Pi's expected eligibility rejection: silent, rearmed, and never a failure. */
|
|
142
|
+
const finishSkip = (ctx: ExtensionContext, goal: boolean): void => {
|
|
143
|
+
request = undefined;
|
|
144
|
+
armed = true;
|
|
145
|
+
if (!goal) return;
|
|
146
|
+
try {
|
|
147
|
+
dependencies.goal?.onSkipped(ctx);
|
|
148
|
+
} catch (callbackError) {
|
|
149
|
+
notifyFailure(ctx, callbackError);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** Single entry point for rejected requests: classifies before any sanitization, stale tokens stay inert. */
|
|
154
|
+
const finishRequestError = (
|
|
155
|
+
ctx: ExtensionContext,
|
|
156
|
+
token: symbol,
|
|
157
|
+
goal: boolean,
|
|
158
|
+
error: unknown,
|
|
159
|
+
): void => {
|
|
160
|
+
if (!request || request.token !== token) return;
|
|
161
|
+
if (isSessionTooSmallCompactionError(error)) {
|
|
162
|
+
finishSkip(ctx, goal);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
finishFailure(ctx, goal, error);
|
|
166
|
+
};
|
|
167
|
+
|
|
138
168
|
pi.on("turn_end", (_event, ctx) => {
|
|
139
169
|
if (!supportedMode(ctx) || request) return;
|
|
140
170
|
|
|
@@ -179,7 +209,7 @@ export function registerAutoCompaction(
|
|
|
179
209
|
try {
|
|
180
210
|
dependencies.goal.onRequested();
|
|
181
211
|
} catch (error) {
|
|
182
|
-
finishFailure(ctx,
|
|
212
|
+
finishFailure(ctx, true, error);
|
|
183
213
|
return;
|
|
184
214
|
}
|
|
185
215
|
}
|
|
@@ -207,10 +237,10 @@ export function registerAutoCompaction(
|
|
|
207
237
|
notifyFailure(ctx, error);
|
|
208
238
|
}
|
|
209
239
|
},
|
|
210
|
-
onError: (error) =>
|
|
240
|
+
onError: (error) => finishRequestError(ctx, token, goal, error),
|
|
211
241
|
});
|
|
212
242
|
} catch (error) {
|
|
213
|
-
|
|
243
|
+
finishRequestError(ctx, token, goal, error);
|
|
214
244
|
}
|
|
215
245
|
});
|
|
216
246
|
|
|
@@ -5,10 +5,9 @@ interface CodexFastState {
|
|
|
5
5
|
listeners: Set<CodexFastStateListener>;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
8
|
+
declare global {
|
|
9
|
+
var __killerosCodexFastState: unknown;
|
|
10
|
+
}
|
|
12
11
|
|
|
13
12
|
// Accepts process-global state only when it is safe to reuse across extension versions.
|
|
14
13
|
function isCodexFastState(value: unknown): value is CodexFastState {
|
|
@@ -18,14 +17,13 @@ function isCodexFastState(value: unknown): value is CodexFastState {
|
|
|
18
17
|
&& [...value.listeners].every((listener) => typeof listener === "function");
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
const
|
|
22
|
-
const savedState = globalState[GLOBAL_STATE_KEY];
|
|
20
|
+
const savedState = globalThis.__killerosCodexFastState;
|
|
23
21
|
const state: CodexFastState = isCodexFastState(savedState) ? savedState : {
|
|
24
22
|
enabled: typeof savedState === "object" && savedState !== null
|
|
25
23
|
&& "enabled" in savedState && savedState.enabled === true,
|
|
26
24
|
listeners: new Set<CodexFastStateListener>(),
|
|
27
25
|
};
|
|
28
|
-
|
|
26
|
+
globalThis.__killerosCodexFastState = state;
|
|
29
27
|
|
|
30
28
|
export function isCodexFastEnabled(): boolean {
|
|
31
29
|
return state.enabled;
|
package/killeros/commands.ts
CHANGED
|
@@ -234,9 +234,11 @@ export function registerSlashAutocomplete(
|
|
|
234
234
|
};
|
|
235
235
|
},
|
|
236
236
|
applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
237
|
+
const commandName = "killerosCommand" in item && typeof item.killerosCommand === "string"
|
|
238
|
+
? item.killerosCommand
|
|
239
|
+
: undefined;
|
|
240
|
+
if (!commandName) return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
241
|
+
usage.set(commandName, (usage.get(commandName) ?? 0) + 1);
|
|
240
242
|
const line = lines[cursorLine] ?? "";
|
|
241
243
|
const beforeCursor = line.slice(0, cursorCol);
|
|
242
244
|
const afterCursor = line.slice(cursorCol);
|
package/killeros/errors.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { safeTerminalText } from "./safe-terminal-text.ts";
|
|
3
3
|
|
|
4
|
+
/** Checks a caught value for one exact string error code. */
|
|
5
|
+
export function hasErrorCode(error: unknown, code: string): boolean {
|
|
6
|
+
return typeof error === "object"
|
|
7
|
+
&& error !== null
|
|
8
|
+
&& "code" in error
|
|
9
|
+
&& error.code === code;
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
/** Converts unknown caught values into text suitable for user-facing errors. */
|
|
5
13
|
export function errorMessage(error: unknown): string {
|
|
6
14
|
return safeTerminalText(error instanceof Error ? error.message : String(error));
|
package/killeros/footer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { type ExtensionAPI, type ExtensionContext, type Theme, type ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { truncateToWidth, visibleWidth, type TUI } from "@earendil-works/pi-tui";
|
|
3
3
|
import { isCodexFastEnabled, subscribeCodexFast } from "./codex-fast-state.ts";
|
|
4
4
|
import { formatCwd, formatTime, formatTokens, padRight } from "./display.ts";
|
|
5
5
|
import { goalElapsedMilliseconds } from "./goals.ts";
|
|
@@ -196,7 +196,7 @@ export function registerFooter(pi: ExtensionAPI, goalRuntime: GoalRuntime): void
|
|
|
196
196
|
unsubscribeCodexFast = subscribeCodexFast(() => activeTui?.requestRender());
|
|
197
197
|
const sessionStart = Date.now();
|
|
198
198
|
currentModel = ctx.model;
|
|
199
|
-
thinkingLevel = pi.getThinkingLevel()
|
|
199
|
+
thinkingLevel = pi.getThinkingLevel();
|
|
200
200
|
const cwd = formatCwd(ctx.cwd);
|
|
201
201
|
|
|
202
202
|
ctx.ui.setFooter((tui, theme, footerData) => {
|