killeros 2.0.11 → 2.0.13

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,22 @@ All notable changes to KillerOS are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.0.13] - 2026-08-21
8
+
9
+ ### Added
10
+
11
+ - Added the process-local bare `/codex-fast` toggle for Codex priority requests, with a bold inline `Fast` footer indicator while an active Codex model uses it.
12
+
13
+ ### Changed
14
+
15
+ - Converted all tracked test suites from JavaScript to strict TypeScript while keeping Node's built-in test runner.
16
+
17
+ ## [2.0.12] - 2026-08-18
18
+
19
+ ### Fixed
20
+
21
+ - Resumed active goals exactly once after Pi reports automatic compaction complete.
22
+
7
23
  ## [2.0.11] - 2026-08-17
8
24
 
9
25
  ### Fixed
package/Killeros.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  registerCompletionNotifications,
15
15
  type CompletionNotificationDependencies,
16
16
  } from "./killeros/notifications.ts";
17
+ import { registerCodexFastMode } from "./killeros/codex-fast.ts";
17
18
  import { registerPersonalInstructions } from "./killeros/personal-instructions.ts";
18
19
  import { registerQuestionTool } from "./killeros/question.ts";
19
20
  import { createGoalRuntime, createInitRuntime } from "./killeros/runtime.ts";
@@ -42,6 +43,7 @@ export default function Killeros(pi: ExtensionAPI, options: KillerosOptions = {}
42
43
  registerSlashAutocomplete(pi, commandResolver);
43
44
  registerFooter(pi, goalRuntime);
44
45
  registerVariants(pi);
46
+ registerCodexFastMode(pi);
45
47
  registerInitCommand(pi, initRuntime, goalRuntime);
46
48
  registerLifecycleHooks(pi);
47
49
  const goalCompaction = registerGoalSettlement(pi, goalRuntime, initRuntime);
package/README.md CHANGED
@@ -1,57 +1,54 @@
1
1
  # KillerOS
2
2
 
3
- A production-hardened Pi extension that combines a custom TUI, repository initialization, long-running goals, reasoning controls, interactive questions, and command aliases.
3
+ KillerOS is a TypeScript extension for the Pi coding agent. It adds a custom TUI, repository initialization, long-running goals, reasoning controls, interactive questions, lifecycle hooks, and a small set of command aliases.
4
4
 
5
5
  ## Requirements
6
6
 
7
7
  - Node.js `22.19.0` or later
8
8
  - Pi `0.84.2` or later
9
- - Interactive TUI mode for the custom header, editor, footer, `question` tool, and `/init`
9
+ - An interactive TUI session for the custom header, editor, footer, `question`, and `/init`
10
10
 
11
- The extension is strict TypeScript. Pi provides the runtime modules.
11
+ KillerOS ships as TypeScript. Pi supplies the runtime modules listed as peer dependencies.
12
12
 
13
13
  ## Install
14
14
 
15
- ### npm
16
-
17
- Install KillerOS:
15
+ Install the current npm release:
18
16
 
19
17
  ```bash
20
18
  pi install npm:killeros
21
19
  ```
22
20
 
23
- ### Git
24
-
25
- Install the latest commit:
21
+ Install from GitHub:
26
22
 
27
23
  ```bash
28
24
  pi install git:github.com/KyrosHendrix/pi-KillerOS
29
25
  ```
30
26
 
31
- Pin an install to a release:
27
+ Pin an install to version `v2.0.13`:
32
28
 
33
29
  ```bash
34
- pi install git:github.com/KyrosHendrix/pi-KillerOS@v2.0.11
30
+ pi install git:github.com/KyrosHendrix/pi-KillerOS@v2.0.13
35
31
  ```
36
32
 
37
- Add `-l` to either command for a project-only install. Restart Pi after installing.
33
+ Add `-l` to either command to install only for the current project. Restart Pi after installing.
38
34
 
39
35
  ## Features
40
36
 
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
- - Cohesive dark theme with coral accents and neutral tool-call containers across pending, success, and error states
43
- - Animated orange 12-frame activity glyph loop at 120 ms per frame with contextual copy derived from request, tool, result, and response events, plus a quiet hidden-thinking label
44
- - Frameless multiline editor with one focus-aware `❯`, a shuffled session-stable empty-state suggestion, overflow-only scroll indicators, Shift+Enter support, and slash-command autocomplete; KillerOS preserves an editor factory configured by another extension
45
- - One compact TUI transcript line reporting truthful `Done`, `Stopped`, or `Failed` settlement with elapsed time while preserving older `✻ Worked for …` entries
46
- - Compact two-deck footer with session state above workspace state; model, context, and active goals stay prioritized as reasoning, time, cost, branch, and path reduce by available width
47
- - Proactive turn-boundary context compaction in TUI and RPC modes, with ordinary prompts and active goals continuing safely after a successful summary
48
- - Optional completion sounds after successful or failed settled requests, excluding manual aborts
49
- - `/variants` selector and direct reasoning-level arguments
50
- - Codex-style `/goal` with an interactive status/action panel, durable objectives, immediate pause and clear cancellation, automatic continuation, explicit completion, and durable blocker audits
51
- - Automatic `/init` guideline synthesis with a frozen safe evidence map, protected existing policy, and the four packaged behavioral sections adapted from `writing-great-guidelines`
52
- - `question` tool with single-select and opt-in bounded multi-select, height-bounded option windows, configured Pi keybindings, live option/input progress, proposal previews, custom answers, history, cancellation, and compact expandable transcript rendering
53
- - Mid-prompt slash completion with current Pi `0.84.2` commands, extensions, prompts, and skills; paths, URLs, and invalid commands remain plain text
54
- - Goal-aware `/clear` that confirms, aborts active work, waits for settlement, and starts a new session, plus `/exit` for graceful shutdown
37
+ - A compact startup card with the extension version, model, provider, `/model`, working directory, Git branch, and a session-stable tip.
38
+ - A dark theme with coral accents and neutral tool-call containers for pending, successful, and failed calls.
39
+ - A 12-frame orange activity glyph with event-based status text and a quiet hidden-thinking label.
40
+ - A multiline editor with a single focus-aware prompt arrow, a session-stable empty-state suggestion, overflow-only scroll indicators, Shift+Enter support, and slash-command completion.
41
+ - A settled transcript line that reports `Done`, `Stopped`, or `Failed` with elapsed time, while preserving older `✻ Worked for ...` entries.
42
+ - A responsive footer that keeps model, context, and goal state visible as the terminal gets narrower.
43
+ - Automatic turn-boundary context compaction in TUI and RPC modes.
44
+ - Optional completion sounds for successful and failed settled requests.
45
+ - `/variants` for selecting a model reasoning level.
46
+ - `/codex-fast` for toggling the `priority` service tier on Codex requests.
47
+ - `/goal` for durable objectives with pause, resume, edit, clear, completion, continuation, and blocker audits.
48
+ - `/init` for generating a root `AGENTS.md` from a bounded, safe set of repository files.
49
+ - A `question` tool with single-select and opt-in multi-select controls.
50
+ - Slash completion based on Pi's registered commands, extensions, prompts, and skills.
51
+ - Goal-aware `/clear` and graceful `/exit` handling.
55
52
 
56
53
  ## Commands
57
54
 
@@ -65,34 +62,61 @@ Add `-l` to either command for a project-only install. Restart Pi after installi
65
62
  /goal clear Stop current goal work and remove the goal
66
63
  /variants Open the reasoning-level selector
67
64
  /variants high Set a reasoning level directly
65
+ /codex-fast Toggle process-local Codex fast mode
68
66
  /notification Configure the completion sound
69
67
  /clear Start a new session after confirmation
70
68
  /exit Quit Pi gracefully
71
69
  ```
72
70
 
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 report completion. For an objective that clearly asks to create, write, save, or generate a named file-like deliverable at one quoted absolute path, KillerOS saves that exact path with the goal and verifies that a regular file exists there before accepting completion. Other objectives retain model-reported completion. Blocking requires one stable lowercase blocker key recorded on three consecutive goal turns; a changed key, skipped turn, resume, or edit resets the streak. Final prose alone does not end the loop.
71
+ ### Codex fast mode
72
+
73
+ `/codex-fast` takes no arguments. It toggles a process-local setting. When it is enabled and the active model uses the `openai-codex` provider, KillerOS adds `service_tier: "priority"` to the provider request. The footer shows bold `Fast` between the model and provider.
74
+
75
+ The setting survives a session reload within the same process, does not change other providers, is not saved to KillerOS configuration, and starts disabled after Pi restarts. A provider failure leaves the setting enabled and follows Pi's normal error handling.
76
+
77
+ ### Goals
78
+
79
+ `/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, and tree navigation.
80
+
81
+ An active goal injects its unchanged objective on each turn and continues one settled turn at a time. The model must use KillerOS's private goal tool to report completion. If the objective explicitly asks for a named file-like deliverable at one quoted absolute path, KillerOS records that path and checks that a regular file exists before accepting completion. Other objectives use the model's completion report.
82
+
83
+ KillerOS marks a goal blocked only after a stable lowercase blocker key recorded on three consecutive goal turns. A changed key, skipped turn, resume, or edit resets the streak. Final prose does not end the loop.
84
+
85
+ Active goals replace the footer path with warning-yellow `/goal is active (...)` and keep the exact elapsed time visible.
86
+
87
+ `/goal pause` and `/goal clear` save paused or cleared state before aborting current goal work, so settlement cannot restart it. Aborted turns, provider failures, and continuation failures pause safely. Failed edit and replacement writes dispatch no edited objective. Replacing unfinished work requires confirmation, and `/goal edit` works only in TUI mode.
74
88
 
75
- `/goal pause` and `/goal clear` save paused or cleared state before aborting current goal work, so settlement cannot restart it. Aborted turns, provider failures, and continuation failures otherwise pause safely. Failed edit and replacement writes dispatch no edited objective; an active prior objective pauses fail-closed, while inactive durable state remains unchanged. Replacing unfinished work requires confirmation, and `/goal edit` requires TUI mode.
89
+ ### Repository initialization
76
90
 
77
- `/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.
91
+ `/init` freezes a safe project-file map and exposes only dedicated read and list operations while it generates the root `AGENTS.md`. Git-ignored files, known secret paths, private-key formats, other guidance files, dependencies, links, non-regular files, and files outside the map are unavailable to the generation step.
78
92
 
79
- 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.
93
+ An existing root `AGENTS.md` is protected policy. Compatible rules are preserved. A real policy conflict leaves the file unchanged with a reason, and a concurrent target change aborts installation instead of replacing the newer file.
94
+
95
+ The generated file uses four behavioral sections adapted from `writing-great-guidelines`. `/init` does not require another skill installation, ask setup questions, start a second model process, or write another file. Pi resources reload only after a successful write.
80
96
 
81
97
  ### Interactive questions
82
98
 
83
- Single-select remains the default. Explicit `minSelections: 1` and `maxSelections: 1` are equivalent to omitting both bounds; other single-select bounds are rejected. An agent opts into multi-select with `mode: "multiple"` and may set `minSelections` and `maxSelections`; the custom answer counts as one selection.
99
+ Single-select remains the default. Explicit `minSelections: 1` and `maxSelections: 1` are accepted; other single-select bounds are rejected. Use `mode: "multiple"` to opt into multi-select. The custom answer counts as one selection.
84
100
 
85
- In multi-select, use Space or a visible number to toggle an option, `/` to filter, and Enter to submit. The filter accepts spaces; Enter applies it and Escape returns to the choices. Checked options remain selected when the filter changes. Select **Type a custom answer** with Enter to add or edit one custom item alongside checked options.
101
+ In multi-select mode, use Space or a visible number to toggle an option, `/` to filter, and Enter to submit. The filter accepts spaces. Enter applies the filter and Escape returns to the choices. Checked options remain selected when the filter changes. Select `Type a custom answer` to add or edit one custom item alongside the checked options.
86
102
 
87
- Supported reasoning levels are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. KillerOS limits choices to levels supported by the current model.
103
+ Supported reasoning levels are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. KillerOS limits the selector to levels supported by the current model.
88
104
 
89
105
  ## Configuration
90
106
 
91
- 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.
107
+ KillerOS activates its packaged `killeros` theme when a TUI session starts. Tool-call backgrounds stay neutral in pending, successful, and failed states.
108
+
109
+ The completion sound is a global user preference in Pi's agent directory and is off by default. Run `/notification` in TUI mode to change it. Enabled TUI tabs append `󰂚`, which requires a Nerd Font. KillerOS uses the terminal's audible bell, so a terminal that disables the bell cannot play the sound.
110
+
111
+ ### Automatic compaction
92
112
 
93
- 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.
113
+ Automatic compaction is enabled by default when Pi's effective `compaction.enabled` setting is true. After each completed assistant turn, including tool execution, KillerOS reads the active model's context usage and calls Pi's public compaction API when:
114
+
115
+ ```text
116
+ remainingTokens <= max(contextWindow * percentRemaining / 100, reserveTokens)
117
+ ```
94
118
 
95
- Automatic compaction is enabled by default when Pi's effective `compaction.enabled` setting is true. After each completed assistant turn, including its tool execution, KillerOS reads the active model's current context usage and triggers the public Pi compaction API when `remainingTokens <= max(contextWindow * percentRemaining / 100, reserveTokens)`. The default `percentRemaining` is `15`; `reserveTokens` and `keepRecentTokens` remain Pi-owned. KillerOS reads those effective settings with Pi's public `SettingsManager` using `getAgentDir()` and the current project trust state. The KillerOS-only preference lives in the same global `killeros.json` file:
119
+ The default `percentRemaining` is `15`. Pi owns `reserveTokens` and `keepRecentTokens`. KillerOS reads the effective settings through Pi's public `SettingsManager` with `getAgentDir()` and stores its own preference in the global `killeros.json` file:
96
120
 
97
121
  ```json
98
122
  {
@@ -103,29 +127,33 @@ Automatic compaction is enabled by default when Pi's effective `compaction.enabl
103
127
  }
104
128
  ```
105
129
 
106
- Missing context readings skip the check. Successful ordinary-prompt compaction queues one hidden continuation; active `/goal` runs use the existing session-compaction and goal-continuation path. A failed compaction does not automatically retry. Manual `/compact` behavior is unchanged.
130
+ Missing context readings skip the check. Successful ordinary-prompt compaction queues one hidden continuation. Active `/goal` runs use the existing session-compaction and goal-continuation path. Failed compaction does not retry automatically, and manual `/compact` behavior is unchanged.
107
131
 
108
- 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.
132
+ Pi writes the summary, applies manual focus instructions, tracks files, retries summarization, and handles overflow recovery. KillerOS only decides when to request proactive compaction.
109
133
 
110
- Pi writes the summary, applies manual focus instructions, tracks files, retries summarization, and handles overflow recovery. KillerOS owns only the proactive turn-boundary trigger and does not replace Pi's compaction implementation. Active `/goal` work continues from the settled compaction boundary, after Pi finishes retries, compaction, and queued work.
134
+ Manual `/compact` pauses the current goal turn before summarization. After Pi saves the summary, KillerOS resumes that goal revision automatically. A failed or cancelled manual compaction stays paused. Run `/goal pause` during the pause to cancel automatic recovery.
111
135
 
112
- 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.
136
+ ### Project instructions and hooks
113
137
 
114
138
  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.
115
139
 
116
- Lifecycle hooks are loaded from `.pi/killeros-hooks.json` at session start. Supported event keys are `tool_call`, `tool_result`, and `agent_settled`. Optional matchers are JavaScript regular expressions over Pi tool names, so they are valid only for `tool_call` and `tool_result`; KillerOS rejects an `agent_settled` hook that defines a matcher. 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.
140
+ Lifecycle hooks load from `.pi/killeros-hooks.json` at session start. Supported event keys are `tool_call`, `tool_result`, and `agent_settled`. Regular-expression matchers apply only to the first two events. KillerOS rejects a matcher on `agent_settled`.
141
+
142
+ Hook commands run from the repository root with `KILLEROS_EVENT`, `KILLEROS_TOOL`, and `KILLEROS_PAYLOAD` environment variables. A failed `tool_call` hook blocks the tool. Failures for later events notify the user. If the parent request is aborted, KillerOS stops the hook process tree with bounded graceful and forced cleanup without reporting cancellation as a hook failure.
117
143
 
118
144
  ## Behavior by mode
119
145
 
120
146
  | Mode | Behavior |
121
- |---|---|
122
- | TUI | All features are available, including proactive compaction, the completion sound, and the tab-title indicator |
123
- | RPC | Proactive compaction and goal set/view/pause/resume/clear work; TUI components, `/goal edit`, `/init`, completion sounds, and the title indicator are disabled |
124
- | Print/JSON | Interactive questions, `/goal`, `/init`, and proactive compaction are disabled; completion sounds and the title indicator are disabled |
147
+ | --- | --- |
148
+ | TUI | All features are available, including proactive compaction, completion sounds, and the tab-title indicator. |
149
+ | RPC | Proactive compaction and goal set/view/pause/resume/clear work. TUI components, `/goal edit`, `/init`, completion sounds, and the title indicator are disabled. |
150
+ | Print/JSON | Interactive questions, `/goal`, `/init`, and proactive compaction are disabled. Completion sounds and the title indicator are disabled. |
151
+
152
+ ## Development and validation
125
153
 
126
- ## Validation
154
+ Source and tests use strict TypeScript. Tests run with Node's built-in test runner and type stripping.
127
155
 
128
- Before release, run:
156
+ Before a release, run:
129
157
 
130
158
  ```bash
131
159
  npm ci
@@ -135,17 +163,23 @@ npm pack --dry-run
135
163
  pi -ne -e . --mode rpc
136
164
  ```
137
165
 
138
- The package manifest lists Pis built-in modules as peer dependencies, so npm does not bundle a second copy.
166
+ The package manifest lists Pi's built-in modules as peer dependencies, so npm does not bundle another copy.
167
+
168
+ ## Releases
169
+
170
+ For a normal release:
139
171
 
140
- ## Publish
172
+ 1. Update the version in `package.json` and both matching version fields in `package-lock.json`.
173
+ 2. Add a dated section with the same version to `CHANGELOG.md`.
174
+ 3. Push the release commit to `main`.
141
175
 
142
- To release KillerOS, update the version in `package.json` and `package-lock.json`, add the matching `CHANGELOG.md` section, and push the release commit to `main`. After the full CI workflow passes, the release workflow publishes the package to npm through trusted publishing, then creates the matching tag and GitHub release from that verified commit. The [`pi-package`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md) keyword makes the npm release visible in Pis package catalog.
176
+ After the full CI workflow passes on `main`, the release workflow validates the commit and changelog, publishes the package to npm through trusted publishing, and creates the matching tag and GitHub release. The [`pi-package` keyword](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md) makes the npm package visible in Pi's package catalog.
143
177
 
144
- Do not manually tag a normal release. If automation must recover a missing GitHub release, push the matching version tag. The workflow validates the tag against the package and changelog, skips npm publication when that version already exists, and creates only the missing release.
178
+ Do not create a tag for the normal path. If a release is missing, use the manual tag recovery path and push the matching version tag. The recovery path checks the tag against the package and changelog, skips npm publication when that version already exists, and creates only the missing GitHub release.
145
179
 
146
180
  ## Security
147
181
 
148
- Pi extensions run with your user permissions. Review the source before installing KillerOS globally. KillerOS executes lifecycle hook commands only for projects Pi marks as trusted; review `.pi/killeros-hooks.json` before enabling project trust.
182
+ Pi extensions run with your user permissions. Review the source before installing KillerOS globally. KillerOS runs lifecycle hook commands only for projects Pi marks as trusted. Review `.pi/killeros-hooks.json` before enabling project trust.
149
183
 
150
184
  ## License
151
185
 
@@ -0,0 +1,38 @@
1
+ type CodexFastStateListener = () => void;
2
+
3
+ interface CodexFastState {
4
+ enabled: boolean;
5
+ listeners: Set<CodexFastStateListener>;
6
+ }
7
+
8
+ const GLOBAL_STATE_KEY = "__killerosCodexFastState";
9
+ type GlobalWithCodexFastState = typeof globalThis & {
10
+ [GLOBAL_STATE_KEY]?: CodexFastState;
11
+ };
12
+
13
+ const globalState = globalThis as GlobalWithCodexFastState;
14
+ const state = globalState[GLOBAL_STATE_KEY] ??= {
15
+ enabled: false,
16
+ listeners: new Set<CodexFastStateListener>(),
17
+ };
18
+
19
+ export function isCodexFastEnabled(): boolean {
20
+ return state.enabled;
21
+ }
22
+
23
+ export function toggleCodexFast(): boolean {
24
+ state.enabled = !state.enabled;
25
+ for (const listener of [...state.listeners]) listener();
26
+ return state.enabled;
27
+ }
28
+
29
+ export function subscribeCodexFast(listener: CodexFastStateListener): () => void {
30
+ state.listeners.add(listener);
31
+ return () => state.listeners.delete(listener);
32
+ }
33
+
34
+ /** Test-only reset for the process-global state between isolated harnesses. */
35
+ export function resetCodexFastState(): void {
36
+ state.enabled = false;
37
+ state.listeners.clear();
38
+ }
@@ -0,0 +1,31 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { isCodexFastEnabled, toggleCodexFast } from "./codex-fast-state.ts";
3
+
4
+ const CODEX_PROVIDER = "openai-codex";
5
+ type RequestPayload = Record<string, unknown>;
6
+
7
+ function isRequestPayload(value: unknown): value is RequestPayload {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+
11
+ export function registerCodexFastMode(pi: ExtensionAPI): void {
12
+ pi.registerCommand("codex-fast", {
13
+ description: "Toggle Codex fast mode",
14
+ handler: async (args, ctx) => {
15
+ if (args.trim()) {
16
+ ctx.ui.notify("Usage: /codex-fast", "error");
17
+ return;
18
+ }
19
+
20
+ const enabled = toggleCodexFast();
21
+ ctx.ui.notify(`Fast ${enabled ? "enabled" : "disabled"}`, "info");
22
+ },
23
+ });
24
+
25
+ pi.on("before_provider_request", (event, ctx) => {
26
+ if (!isCodexFastEnabled() || ctx.model?.provider !== CODEX_PROVIDER || !isRequestPayload(event.payload)) {
27
+ return event.payload;
28
+ }
29
+ return { ...event.payload, service_tier: "priority" };
30
+ });
31
+ }
@@ -1,11 +1,13 @@
1
1
  import { DynamicBorder, type ExtensionAPI, type ExtensionContext, type Theme, type ThemeColor } from "@earendil-works/pi-coding-agent";
2
2
  import { Container, Text, truncateToWidth, visibleWidth, type TUI } from "@earendil-works/pi-tui";
3
+ import { isCodexFastEnabled, subscribeCodexFast } from "./codex-fast-state.ts";
3
4
  import { formatCwd, formatTime, formatTokens, padRight } from "./display.ts";
4
5
  import { goalElapsedMilliseconds } from "./goals.ts";
5
6
  import type { GoalRuntime, GoalState } from "./runtime.ts";
6
7
  import { LEVEL_COLORS, type ThinkingLevel } from "./variants.ts";
7
8
 
8
9
  const FOOTER_REFRESH_INTERVAL_MS = 1_000;
10
+ const CODEX_PROVIDER = "openai-codex";
9
11
 
10
12
  export function formatCost(usd: number): string {
11
13
  if (!Number.isFinite(usd)) return "$—";
@@ -88,10 +90,19 @@ function modelDisplayName(model: NonNullable<ExtensionContext["model"]>): string
88
90
  return model.name?.trim() || model.id;
89
91
  }
90
92
 
91
- export function formatModel(model: ExtensionContext["model"], theme: Theme, includeProvider = true): string {
93
+ export function formatModel(
94
+ model: ExtensionContext["model"],
95
+ theme: Theme,
96
+ includeProvider = true,
97
+ showCodexFast = false,
98
+ ): string {
92
99
  if (!model) return theme.fg("dim", "No model");
93
100
  const name = theme.fg("text", theme.bold(modelDisplayName(model)));
94
- return includeProvider ? `${name} ${theme.fg("dim", formatProviderName(model.provider))}` : name;
101
+ const fast = showCodexFast && model.provider === CODEX_PROVIDER
102
+ ? theme.fg("accent", theme.bold("Fast"))
103
+ : "";
104
+ const provider = includeProvider ? theme.fg("dim", formatProviderName(model.provider)) : "";
105
+ return [name, fast, provider].filter(Boolean).join(" ");
95
106
  }
96
107
 
97
108
  function compactDirectory(cwd: string): string {
@@ -158,6 +169,7 @@ export function registerFooter(pi: ExtensionAPI, goalRuntime: GoalRuntime): void
158
169
  let activeTui: TUI | undefined;
159
170
  let cachedSessionCost = 0;
160
171
  let sessionCostDirty = true;
172
+ let unsubscribeCodexFast: (() => void) | undefined;
161
173
  const resetSessionCost = (): void => {
162
174
  cachedSessionCost = 0;
163
175
  sessionCostDirty = true;
@@ -178,6 +190,8 @@ export function registerFooter(pi: ExtensionAPI, goalRuntime: GoalRuntime): void
178
190
  pi.on("session_start", (_event, ctx) => {
179
191
  resetSessionCost();
180
192
  if (ctx.mode !== "tui") return;
193
+ unsubscribeCodexFast?.();
194
+ unsubscribeCodexFast = subscribeCodexFast(() => activeTui?.requestRender());
181
195
  const sessionStart = Date.now();
182
196
  currentModel = ctx.model;
183
197
  thinkingLevel = pi.getThinkingLevel() as ThinkingLevel;
@@ -210,7 +224,7 @@ export function registerFooter(pi: ExtensionAPI, goalRuntime: GoalRuntime): void
210
224
  const contextWindow = usage?.contextWindow ?? model?.contextWindow ?? 128_000;
211
225
  const context = formatContextProgress(usage?.tokens ?? null, contextWindow, theme);
212
226
  const branch = footerData.getGitBranch();
213
- const signature = formatModel(model, theme);
227
+ const signature = formatModel(model, theme, true, isCodexFastEnabled());
214
228
  const fullDirectory = theme.fg("dim", cwd);
215
229
  const focusedDirectory = theme.fg("dim", compactDirectory(cwd));
216
230
  const goal = formatGoalFooter(goalRuntime.state, theme);
@@ -224,7 +238,7 @@ export function registerFooter(pi: ExtensionAPI, goalRuntime: GoalRuntime): void
224
238
  theme.fg("dim", formatTime(Date.now() - sessionStart)),
225
239
  theme.fg("dim", formatCost(getSessionCost(ctx))),
226
240
  ], theme);
227
- const essentialModel = formatModel(model, theme, false);
241
+ const essentialModel = formatModel(model, theme, false, isCodexFastEnabled());
228
242
  const primaryRow = footerRowFits(primary, session, width)
229
243
  ? renderFooterRow(primary, session, width)
230
244
  : footerRowFits(primary, "", width)
@@ -241,7 +255,6 @@ export function registerFooter(pi: ExtensionAPI, goalRuntime: GoalRuntime): void
241
255
  : footerRowFits(branchLabel, focusedDirectory, width)
242
256
  ? renderFooterRow(branchLabel, focusedDirectory, width)
243
257
  : renderFooterRow(branchLabel, "", width);
244
-
245
258
  return renderFooter([primaryRow, secondaryRow], width, theme);
246
259
  },
247
260
  };
@@ -263,6 +276,8 @@ export function registerFooter(pi: ExtensionAPI, goalRuntime: GoalRuntime): void
263
276
  activeTui?.requestRender();
264
277
  });
265
278
  pi.on("session_shutdown", () => {
279
+ unsubscribeCodexFast?.();
280
+ unsubscribeCodexFast = undefined;
266
281
  resetSessionCost();
267
282
  activeTui = undefined;
268
283
  goalRuntime.requestRender = undefined;
package/killeros/goals.ts CHANGED
@@ -468,8 +468,6 @@ function completeAutomaticCompaction(
468
468
  runtime.automaticCompaction = undefined;
469
469
  return;
470
470
  }
471
- runtime.automaticCompaction = "completed";
472
- if (runtime.goalTurnInFlight || !ctx.isIdle()) return;
473
471
  runtime.automaticCompaction = undefined;
474
472
  setImmediate(() => scheduleGoalContinuation(pi, runtime, initState, ctx));
475
473
  }
@@ -1058,12 +1056,7 @@ export function registerGoalSettlement(
1058
1056
  const reason = runtime.lastError || "the agent turn was aborted";
1059
1057
  runtime.lastStopReason = undefined;
1060
1058
  runtime.lastError = undefined;
1061
- if (runtime.automaticCompaction !== undefined) {
1062
- if (runtime.automaticCompaction === "completed") {
1063
- completeAutomaticCompaction(pi, runtime, initState, ctx);
1064
- }
1065
- return;
1066
- }
1059
+ if (runtime.automaticCompaction !== undefined) return;
1067
1060
  pauseGoalForPossibleManualCompaction(pi, runtime, ctx, reason);
1068
1061
  return;
1069
1062
  }
@@ -1080,10 +1073,7 @@ export function registerGoalSettlement(
1080
1073
  });
1081
1074
 
1082
1075
  pi.on("session_compact", (event, ctx) => {
1083
- if (runtime.automaticCompaction !== undefined) {
1084
- completeAutomaticCompaction(pi, runtime, initState, ctx);
1085
- return;
1086
- }
1076
+ if (runtime.automaticCompaction !== undefined) return;
1087
1077
  if (event.reason !== "manual") return;
1088
1078
  recoverGoalAfterManualCompaction(pi, runtime, initState, ctx);
1089
1079
  });
@@ -57,7 +57,7 @@ export interface GoalRuntime {
57
57
  continuationHeld: boolean;
58
58
  goalTurnInFlight: boolean;
59
59
  agentEndObserved: boolean;
60
- automaticCompaction?: "pending" | "completed";
60
+ automaticCompaction?: "pending";
61
61
  persistenceRetryNeeded: boolean;
62
62
  lastStopReason?: string;
63
63
  lastError?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "killeros",
3
- "version": "2.0.11",
3
+ "version": "2.0.13",
4
4
  "description": "TUI, goals, and workflow automation for the Pi coding agent",
5
5
  "type": "module",
6
6
  "keywords": [