killeros 2.0.2 → 2.0.4

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 CHANGED
@@ -4,6 +4,40 @@ All notable changes to KillerOS are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.0.4] - 2026-08-10
8
+
9
+ ### Changed
10
+
11
+ - Rebuilt `/init` around a packaged four-section guideline-synthesis workflow that preserves compatible root policy without requiring an external skill.
12
+ - Removed live blue slash-command coloring while retaining slash autocomplete, the framed multiline editor, and Shift+Enter.
13
+
14
+ ### Fixed
15
+
16
+ - Prevented `/init` from reading Git-ignored files, known secrets, private keys, linked or non-regular files, other guidance, dependencies, and files outside its frozen evidence map.
17
+ - Protected root `AGENTS.md` from concurrent replacement and reported incompatible policy conflicts without writing.
18
+ - Made `/clear` confirm before aborting active work, wait for settlement, and then start the new session.
19
+ - Preserved explicit Responses API verbosity and reasoning-summary settings while applying concise defaults only to absent fields.
20
+ - Cancelled lifecycle hook process trees with bounded cleanup when Pi aborts the parent request.
21
+ - Kept `/init` middleware from freezing shared tool input and preserved custom editors installed by other extensions.
22
+ - Removed private Pi editor-state access in favor of public rendering contracts.
23
+
24
+ ## [2.0.3] - 2026-08-09
25
+
26
+ ### Added
27
+
28
+ - Added an opt-in global completion sound with `/notification` and an enabled-state Nerd Font bell in the terminal tab title.
29
+ - Moved active goal status to the footer's right side in warning yellow, replacing the path with `/goal is active (...)` and exact seconds.
30
+
31
+ ### Changed
32
+
33
+ - Returned compaction timing, summary generation, manual instructions, retries, file tracking, and overflow recovery to Pi's public lifecycle.
34
+ - Continued active goals only after Pi reaches its settled boundary and preserved `/goal` as the sole durable objective and status owner.
35
+
36
+ ### Fixed
37
+
38
+ - Added fail-closed, revision-bound recovery after successful manual compaction, including reload and branch navigation recovery without reviving stale or explicitly paused goals.
39
+ - Rendered question help from the effective selector keybindings across supported Pi versions.
40
+
7
41
  ## [2.0.2] - 2026-08-08
8
42
 
9
43
  ### Changed
package/Killeros.ts CHANGED
@@ -1,27 +1,34 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { registerAliases, registerSlashAutocomplete } from "./killeros/commands.ts";
3
3
  import { registerConcisePrompt } from "./killeros/concise.ts";
4
- import { registerContextCompaction } from "./killeros/context-compaction.ts";
5
4
  import { registerFooter } from "./killeros/footer.ts";
6
5
  import { registerGoal, registerGoalSettlement } from "./killeros/goals.ts";
7
6
  import { registerLifecycleHooks } from "./killeros/hooks.ts";
8
7
  import { registerInitCommand, registerInitSettlement } from "./killeros/init.ts";
8
+ import {
9
+ registerCompletionNotifications,
10
+ type CompletionNotificationDependencies,
11
+ } from "./killeros/notifications.ts";
9
12
  import { registerPersonalInstructions } from "./killeros/personal-instructions.ts";
10
13
  import { registerQuestionTool } from "./killeros/question.ts";
11
- import { createCompactionRuntime, createGoalRuntime, createInitRuntime } from "./killeros/runtime.ts";
14
+ import { createGoalRuntime, createInitRuntime } from "./killeros/runtime.ts";
12
15
  import { registerShellUi } from "./killeros/shell-ui.ts";
13
16
  import { registerVariants } from "./killeros/variants.ts";
14
17
 
15
- export { CONCISE_SYSTEM_PROMPT, isConcisedEnabled } from "./killeros/concise.ts";
16
- export { contextPercentRemaining } from "./killeros/context-compaction.ts";
17
- export { formatCost, formatContextProgress } from "./killeros/footer.ts";
18
+ export { CONCISE_SYSTEM_PROMPT, isConciseEnabled, isConcisedEnabled } from "./killeros/concise.ts";
19
+ export { contextPercentRemaining, formatCost, formatContextProgress } from "./killeros/footer.ts";
18
20
  export { executeHook } from "./killeros/hooks.ts";
19
- export { INIT_WORKFLOW_PROMPT, writeInitAgentsFile } from "./killeros/init.ts";
21
+ export { INIT_WORKFLOW_PROMPT } from "./killeros/init.ts";
22
+ export { buildInitEvidence, listInitEvidence, readInitEvidence } from "./killeros/init-evidence.ts";
23
+ export { captureInitTargetBaseline, installInitAgentsFile, validateGeneratedGuidance, writeInitAgentsFile } from "./killeros/init-target.ts";
20
24
 
21
- export default function Killeros(pi: ExtensionAPI): void {
25
+ export interface KillerosOptions {
26
+ completionNotifications?: CompletionNotificationDependencies;
27
+ }
28
+
29
+ export default function Killeros(pi: ExtensionAPI, options: KillerosOptions = {}): void {
22
30
  const initRuntime = createInitRuntime();
23
31
  const goalRuntime = createGoalRuntime();
24
- const compactionRuntime = createCompactionRuntime();
25
32
  registerShellUi(pi);
26
33
  registerConcisePrompt(pi);
27
34
  registerGoal(pi, goalRuntime, initRuntime);
@@ -33,7 +40,7 @@ export default function Killeros(pi: ExtensionAPI): void {
33
40
  registerVariants(pi);
34
41
  registerInitCommand(pi, initRuntime, goalRuntime);
35
42
  registerLifecycleHooks(pi);
36
- registerContextCompaction(pi, compactionRuntime, goalRuntime);
37
- registerGoalSettlement(pi, goalRuntime, initRuntime, compactionRuntime);
43
+ registerGoalSettlement(pi, goalRuntime, initRuntime);
38
44
  registerInitSettlement(pi, initRuntime);
45
+ registerCompletionNotifications(pi, options.completionNotifications);
39
46
  }
package/README.md CHANGED
@@ -31,7 +31,7 @@ pi install git:github.com/KyrosHendrix/pi-KillerOS
31
31
  Pin an install to a release:
32
32
 
33
33
  ```bash
34
- pi install git:github.com/KyrosHendrix/pi-KillerOS@v2.0.2
34
+ pi install git:github.com/KyrosHendrix/pi-KillerOS@v2.0.4
35
35
  ```
36
36
 
37
37
  Add `-l` to either command for a project-only install. Restart Pi after installing.
@@ -41,16 +41,17 @@ Add `-l` to either command for a project-only install. Restart Pi after installi
41
41
  - 52-column Compact startup card with inline version, polished model/provider identity, adjacent `/model`, directory, conditional Git branch, and a shuffled session-stable tip
42
42
  - Cohesive dark theme with coral accents and neutral tool-call containers across pending, success, and error states
43
43
  - Animated orange 12-frame activity glyph loop at 120 ms per frame, with orange shuffled Claude-adjacent verbs changing every 2.5 seconds, a gray `(esc to interrupt · thinking)` status with bold `esc`, and a quiet hidden-thinking label
44
- - Framed multiline editor with Shift+Enter support and live command-blue highlighting for recognized slash command prefixes
44
+ - Framed multiline editor with Shift+Enter support and slash-command autocomplete; KillerOS preserves an editor factory configured by another extension
45
45
  - Responsive footer with polished model/provider identity, plain-language context, and active goal state remaining; reasoning, Git branch, elapsed time, cost, and path cut down by available width
46
- - Automatic model-backed context compaction at 40% remaining; active goals continue after the saved summary
46
+ - Pi-owned context compaction with active goals continuing from Pi's settled boundary after manual, threshold, and overflow compaction
47
+ - Optional completion sounds after successful or failed settled requests, excluding manual aborts
47
48
  - `/variants` selector and direct reasoning-level arguments
48
49
  - Codex-style `/goal` with an interactive status/action panel, durable objectives, pause, resume, edit, confirmed panel clearing, automatic continuation, and explicit completion
49
- - Claude Code-style `/init` that scans the repository and generates a concise root `AGENTS.md` without setup questions
50
+ - Automatic `/init` guideline synthesis with a frozen safe evidence map, protected existing policy, and the four packaged behavioral sections adapted from `writing-great-guidelines`
50
51
  - `question` tool with height-bounded option windows, configured Pi keybindings, live option/input progress, proposal previews, custom answers, history, cancellation, and compact expandable transcript rendering
51
52
  - Mid-prompt slash completion with current Pi `0.82.1` commands, extensions, prompts, and skills; paths, URLs, and invalid commands remain plain text
52
- - `/clear` for a confirmed new session, plus `/exit` for graceful shutdown
53
- - Concise system-prompt guidance without modifying completed assistant messages
53
+ - Goal-aware `/clear` that confirms, aborts active work, waits for settlement, and starts a new session, plus `/exit` for graceful shutdown
54
+ - Concise system-prompt guidance and supported native concise defaults that preserve explicit provider settings
54
55
 
55
56
  ## Commands
56
57
 
@@ -64,13 +65,16 @@ Add `-l` to either command for a project-only install. Restart Pi after installi
64
65
  /goal clear Remove the current goal
65
66
  /variants Open the reasoning-level selector
66
67
  /variants high Set a reasoning level directly
68
+ /notification Configure the completion sound
67
69
  /clear Start a new session after confirmation
68
70
  /exit Quit Pi gracefully
69
71
  ```
70
72
 
71
73
  `/goal` requires a saved session in TUI or RPC mode. Goal state is stored in versioned session entries on the active branch and restored after reload, resume, fork, or tree navigation. Active goals inject their unchanged objective every turn and continue one settled turn at a time. The model must use KillerOS’s private goal tool to mark verified completion or a blocker repeated across at least three goal turns; final prose alone does not end the loop. Aborted turns, provider failures, and continuation failures pause safely. Replacing unfinished work requires confirmation, and `/goal edit` requires TUI mode.
72
74
 
73
- `/init` builds a bounded project map, reads high-value manifests, documentation, and CI configuration, and lets the active model inspect additional implementation files before generating root `AGENTS.md`. Existing `AGENTS.md` and `CLAUDE.md` content is intentionally excluded so stale guidance is not inherited. The command asks no setup questions, starts no second model process, writes only `AGENTS.md`, and reloads Pi resources when finished.
75
+ `/init` freezes a safe project-file map and exposes only dedicated read and list operations while it generates root `AGENTS.md`. Git-ignored files, known secret paths, private-key formats, other guidance, dependencies, links, non-regular files, and files outside that map are unavailable. Existing root `AGENTS.md` is separate protected policy: compatible rules are preserved, a real policy conflict leaves it unchanged with a reason, and any concurrent target change aborts installation without replacing the newer file.
76
+
77
+ The generated file uses the four behavioral sections adapted from `writing-great-guidelines`; no external skill installation is required. `/init` asks no setup questions, starts no second model process, writes no other file, and reloads Pi resources only after a successful write.
74
78
 
75
79
  Supported reasoning levels are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. KillerOS limits choices to levels supported by the current model.
76
80
 
@@ -78,21 +82,25 @@ Supported reasoning levels are `off`, `minimal`, `low`, `medium`, `high`, `xhigh
78
82
 
79
83
  KillerOS activates its packaged `killeros` theme when a TUI session starts. Tool-call backgrounds stay neutral across pending, successful, and failed states; restrained text and icons preserve status visibility.
80
84
 
81
- KillerOS displays session costs in USD. The footer uses Pi's human-readable model name when available, keeps the provider visually secondary, and renders context as `percent left (tokens)` without a progress bar. Active, paused, and blocked goals remain visible; completed goals remain in transcript history and `/goal` status rather than the footer. At narrow widths, context pressure and actionable goal state take priority.
85
+ The completion sound is a global user preference stored in Pi's agent directory and is off by default. Run `/notification` in TUI mode to enable or disable it; enabling does not play a preview. Enabled TUI tabs append `󰂚`, which requires a Nerd Font in the terminal tab UI. An unsupported font may show a box without affecting sound. KillerOS uses the terminal's audible bell and cannot produce sound when the terminal disables it.
86
+
87
+ KillerOS displays session costs in USD. The footer uses Pi's human-readable model name when available, keeps the provider visually secondary, and renders context as `percent left (tokens)` without a progress bar. An active goal replaces the right-side path with warning-yellow `/goal is active (...)` and keeps exact seconds in minute and hour formats. Paused and blocked goals retain their existing placement; completed goals remain in transcript history and `/goal` status rather than the footer. At narrow widths, context pressure and actionable goal state take priority.
88
+
89
+ Pi decides when compaction runs and Pi writes the summary, applies manual focus instructions, tracks files, retries summarization, and handles overflow recovery. KillerOS does not add a second threshold or replace Pi's summary. Active `/goal` work continues from Pi's settled boundary, after Pi finishes retries, compaction, and queued work.
82
90
 
83
- KillerOS checks context after each agent turn. At 40% remaining, it starts Pi's model-backed compaction after the current run settles, so the active turn is not aborted. Manual `/compact` uses the same model path and keeps custom focus instructions. If model compaction is unavailable or exhausts its retries, KillerOS uses the disclosed deterministic fallback and warns that repeated compaction can reduce accuracy.
91
+ Manual `/compact` aborts the current goal turn before summarization, so KillerOS records an honest temporary pause for that exact goal revision. After Pi saves the manual summary, KillerOS resumes that revision automatically. A failed or cancelled manual compaction stays paused; run `/goal pause` during the pause to cancel automatic recovery.
84
92
 
85
93
  For trusted projects, KillerOS loads `AGENTS.local.md` after Pi's shared repository context. A one-line `@path` or `@~/path` file imports personal guidance from another location.
86
94
 
87
- Lifecycle hooks are loaded from `.pi/killeros-hooks.json` at session start. Supported event keys are `tool_call`, `tool_result`, and `agent_settled`; matchers are JavaScript regular expressions over Pi tool names. Hook commands run from the repository root with `KILLEROS_EVENT`, `KILLEROS_TOOL`, and `KILLEROS_PAYLOAD` environment variables. Failed `tool_call` hooks block the tool, while later-event failures notify the user.
95
+ Lifecycle hooks are loaded from `.pi/killeros-hooks.json` at session start. Supported event keys are `tool_call`, `tool_result`, and `agent_settled`; matchers are JavaScript regular expressions over Pi tool names. Hook commands run from the repository root with `KILLEROS_EVENT`, `KILLEROS_TOOL`, and `KILLEROS_PAYLOAD` environment variables. Failed `tool_call` hooks block the tool, while later-event failures notify the user. Aborting the parent request stops the hook process tree with bounded graceful and forced cleanup without reporting cancellation as a hook failure.
88
96
 
89
97
  ## Behavior by mode
90
98
 
91
99
  | Mode | Behavior |
92
100
  |---|---|
93
- | TUI | All features are available |
94
- | RPC | Goal set/view/pause/resume/clear and concise prompt guidance work; TUI components, `/goal edit`, and `/init` are disabled |
95
- | Print/JSON | Concise prompt guidance works; interactive questions, `/goal`, and `/init` fail explicitly |
101
+ | TUI | All features are available, including the completion sound and tab-title indicator |
102
+ | RPC | Goal set/view/pause/resume/clear and concise prompt guidance work; TUI components, `/goal edit`, `/init`, completion sounds, and the title indicator are disabled |
103
+ | Print/JSON | Concise prompt guidance works; interactive questions, `/goal`, and `/init` fail explicitly; completion sounds and the title indicator are disabled |
96
104
 
97
105
  ## Validation
98
106
 
@@ -8,8 +8,9 @@ async function confirmNewSession(ctx: ExtensionCommandContext): Promise<boolean>
8
8
 
9
9
  export function registerAliases(pi: ExtensionAPI): void {
10
10
  const startNewSession = async (_args: string, ctx: ExtensionCommandContext): Promise<void> => {
11
- await ctx.waitForIdle();
12
11
  if (!await confirmNewSession(ctx)) return;
12
+ if (!ctx.isIdle()) ctx.abort();
13
+ await ctx.waitForIdle();
13
14
  await ctx.newSession();
14
15
  };
15
16
  pi.registerCommand("clear", { description: "Start a new session after confirmation", handler: startNewSession });
@@ -54,13 +55,6 @@ const BUILTIN_COMMANDS: ReadonlyArray<{ name: string; description: string }> = [
54
55
  { name: "quit", description: "Quit Pi" },
55
56
  ];
56
57
 
57
- export function availableCommandNames(pi: ExtensionAPI): ReadonlySet<string> {
58
- return new Set([
59
- ...BUILTIN_COMMANDS.map((command) => command.name),
60
- ...pi.getCommands().map((command) => command.name),
61
- ]);
62
- }
63
-
64
58
  const COMMAND_SYNTAX_HINTS: Readonly<Record<string, string>> = {
65
59
  goal: "/goal [objective|clear|edit|pause|resume]",
66
60
  variants: "/variants [level]",
@@ -41,22 +41,26 @@ function applyConciseModelSettings(payload: unknown, api: unknown, modelId: unkn
41
41
  const supportsVerbosity = api === "openai-codex-responses"
42
42
  || api === "openai-responses" && typeof modelId === "string" && /^gpt-5(?:[.-]|$)/u.test(modelId);
43
43
  let updated = payload;
44
- if (supportsVerbosity) {
45
- updated = {
46
- ...updated,
47
- text: { ...(isRecord(payload.text) ? payload.text : {}), verbosity: "low" },
48
- };
44
+ const text = isRecord(payload.text) ? payload.text : undefined;
45
+ if (supportsVerbosity && !Object.hasOwn(text ?? {}, "verbosity")) {
46
+ updated = { ...updated, text: { ...(text ?? {}), verbosity: "low" } };
49
47
  }
50
- if (supportsSummary && isRecord(payload.reasoning)) {
51
- updated = { ...updated, reasoning: { ...payload.reasoning, summary: "concise" } };
48
+ const reasoning = isRecord(payload.reasoning) ? payload.reasoning : undefined;
49
+ if (supportsSummary && reasoning && !Object.hasOwn(reasoning, "summary")) {
50
+ updated = { ...updated, reasoning: { ...reasoning, summary: "concise" } };
52
51
  }
53
52
  return updated;
54
53
  }
55
54
 
56
- export function isConcisedEnabled(): boolean {
55
+ export function isConciseEnabled(): boolean {
57
56
  return true;
58
57
  }
59
58
 
59
+ /** @deprecated Use isConciseEnabled instead. */
60
+ export function isConcisedEnabled(): boolean {
61
+ return isConciseEnabled();
62
+ }
63
+
60
64
  export function registerConcisePrompt(pi: ExtensionAPI): void {
61
65
  pi.on("before_agent_start", (event) => ({
62
66
  systemPrompt: `${event.systemPrompt}\n\n${CONCISE_SYSTEM_PROMPT}`,
@@ -12,6 +12,20 @@ export function formatCost(usd: number): string {
12
12
  return `$${usd.toFixed(2)}`;
13
13
  }
14
14
 
15
+ export function contextPercentRemaining(ctx: ExtensionContext): number | null {
16
+ let usage: ReturnType<ExtensionContext["getContextUsage"]>;
17
+ try {
18
+ usage = ctx.getContextUsage();
19
+ } catch {
20
+ return null;
21
+ }
22
+ if (!usage || !Number.isFinite(usage.contextWindow) || usage.contextWindow <= 0) return null;
23
+ if (usage.tokens === null || !Number.isFinite(usage.tokens)) return null;
24
+
25
+ const percentRemaining = ((usage.contextWindow - Math.max(0, usage.tokens)) / usage.contextWindow) * 100;
26
+ return Math.round(Math.max(0, Math.min(100, percentRemaining)));
27
+ }
28
+
15
29
  export function formatContextProgress(tokensUsed: number | null, contextWindow: number, theme: Theme): string {
16
30
  if (tokensUsed === null || !Number.isFinite(tokensUsed)) return theme.fg("dim", "—% left (—)");
17
31
  const windowSize = Number.isFinite(contextWindow) && contextWindow > 0 ? contextWindow : 128_000;
@@ -111,9 +125,26 @@ function renderFooterRow(left: string, right: string, width: number): string {
111
125
  return ` ${clippedLeft}${gap}${clippedRight} `;
112
126
  }
113
127
 
128
+ function formatGoalElapsed(milliseconds: number): string {
129
+ const totalSeconds = Number.isFinite(milliseconds) ? Math.max(0, Math.floor(milliseconds / 1_000)) : 0;
130
+ if (totalSeconds < 60) return `${totalSeconds}s`;
131
+
132
+ const seconds = totalSeconds % 60;
133
+ const totalMinutes = Math.floor(totalSeconds / 60);
134
+ if (totalMinutes < 60) return `${totalMinutes}m ${seconds.toString().padStart(2, "0")}s`;
135
+
136
+ const hours = Math.floor(totalMinutes / 60);
137
+ const minutes = totalMinutes % 60;
138
+ return `${hours}h ${minutes.toString().padStart(2, "0")}m ${seconds.toString().padStart(2, "0")}s`;
139
+ }
140
+
141
+ function formatActiveGoalFooter(state: GoalState | undefined, theme: Theme): string {
142
+ if (state?.status !== "active") return "";
143
+ return theme.fg("warning", `/goal is active (${formatGoalElapsed(goalElapsedMilliseconds(state))})`);
144
+ }
145
+
114
146
  function formatGoalFooter(state: GoalState | undefined, theme: Theme): string {
115
147
  if (!state) return "";
116
- if (state.status === "active") return theme.fg("accent", `✻ goal · ${formatTime(goalElapsedMilliseconds(state))}`);
117
148
  if (state.status === "paused") return theme.fg("warning", "Ⅱ goal paused");
118
149
  if (state.status === "blocked") return theme.fg("error", "! goal blocked");
119
150
  return "";
@@ -180,6 +211,7 @@ export function registerFooter(pi: ExtensionAPI, goalRuntime: GoalRuntime): void
180
211
  const signature = formatModel(model, theme);
181
212
  const fullDirectory = theme.fg("dim", cwd);
182
213
  const focusedDirectory = theme.fg("dim", compactDirectory(cwd));
214
+ const activeGoal = formatActiveGoalFooter(goalRuntime.state, theme);
183
215
  const goal = formatGoalFooter(goalRuntime.state, theme);
184
216
  const rich = joinFooterParts([
185
217
  signature,
@@ -192,6 +224,19 @@ export function registerFooter(pi: ExtensionAPI, goalRuntime: GoalRuntime): void
192
224
  ], theme);
193
225
  const focused = joinFooterParts([signature, context, goal], theme);
194
226
 
227
+ if (activeGoal) {
228
+ if (footerRowFits(rich, activeGoal, width)) {
229
+ return [renderFooterRow(rich, activeGoal, width)];
230
+ }
231
+ if (footerRowFits(focused, activeGoal, width)) {
232
+ return [renderFooterRow(focused, activeGoal, width)];
233
+ }
234
+ if (footerRowFits(context, activeGoal, width)) {
235
+ return [renderFooterRow(context, activeGoal, width)];
236
+ }
237
+ return [renderFooterRow("", activeGoal, width)];
238
+ }
239
+
195
240
  if (footerRowFits(rich, fullDirectory, width)) {
196
241
  return [renderFooterRow(rich, fullDirectory, width)];
197
242
  }