killeros 3.0.1 → 3.0.3

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,44 @@ All notable changes to KillerOS are documented here. Releases use New Features f
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [3.0.3] - 2026-10-07
8
+
9
+ ### Changed
10
+
11
+ - Raised the minimum supported Pi version and all four locked Pi development packages to 1.0.4. Compatibility CI now targets the exact 1.0.4 minimum and latest `>=1.0.4 <2`, with unchanged stable check names and package-file immutability guards.
12
+ - Documented Pi 1.0.4's native wildcard tool filters, MCP availability under explicit tool allowlists, and codemode image reads.
13
+ - Updated Azure footer naming and fast-mode exclusion tests for Pi's renamed `azure` provider. Documented the required Azure configuration migration, the unchanged Responses API identifier, native editor/transcript navigation, and saved codemode images.
14
+ - Gave Pi compatibility CI checks stable minimum/latest names so future Pi version changes do not require renaming GitHub's required checks. Documented the one-time protection migration without weakening existing safeguards.
15
+
16
+ ### Added
17
+
18
+ - Added real Pi regressions for wildcard decision-tool selection and exclusion, codemode-only provider prompt rules, and pending-stream cancellation retries that preserve one goal turn and defer receipts and sounds until settlement.
19
+ - Added real Pi contract coverage for Azure Responses and Foundry Chat Completions requests both before and after reload, preserving sampling settings and deployment names without applying OpenAI priority service.
20
+ - Added custom-editor coverage for Pi 1.0.3's separate line-navigation and fullscreen transcript Home/End shortcuts.
21
+
22
+ ### Fixed
23
+
24
+ - Fixed a process-tree test startup race by waiting for the complete descendant PID instead of readiness-file creation. Hook termination assertions remain unchanged.
25
+ - Reused bounded cleanup retries throughout real Pi integration tests so transient Windows directory locks do not fail CI after the assertions pass. Added a repository contract against unbounded fixture cleanup.
26
+
27
+ ## [3.0.2] - 2026-10-05
28
+
29
+ ### Changed
30
+
31
+ - Raised the minimum supported Pi version and locked Pi development packages to 1.0.2. Compatibility CI now tests the exact 1.0.2 minimum and latest stable 1.x; users on older Pi versions must upgrade.
32
+ - Documented Pi's native thinking-level sampling configuration and trusted-project MCP overrides without adding KillerOS commands or settings.
33
+
34
+ ### Added
35
+
36
+ - Added real Pi contract coverage for capacity-error recovery within one logical goal turn, with task receipts and completion sounds deferred until settlement.
37
+ - Verified that native OpenAI fast mode preserves thinking-level sampling overrides across API-key and ChatGPT subscription requests, extension reloads, and provider retries.
38
+
39
+ ### Fixed
40
+
41
+ - Kept POSIX process-group cleanup pending after a timed-out or cancelled hook's shell exits, preserving forced termination and reporting bounded uncertainty unless group exit is confirmed. Fixes #42.
42
+ - Preserved goal-request ownership through settlement so automatic compaction cannot restart completed or blocked goals as ordinary work. Active-goal recovery, later ordinary requests, and compaction error reporting remain supported. Fixes #43.
43
+ - Corrected the process-tree timeout fixture's marker argument and delayed descendant writes beyond cleanup. Added a real cleanup-disabled control that detects surviving descendants and checks explicit uncertainty. Fixes #44.
44
+
7
45
  ## [3.0.1] - 2026-10-02
8
46
 
9
47
  ### Changed
package/README.md CHANGED
@@ -19,9 +19,11 @@ 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.99.2 or later below 2.0.0
22
+ - Pi 1.0.4 or later below 2.0.0
23
23
  - An interactive TUI session for the custom header, editor, footer, and `question`
24
24
 
25
+ Users on older Pi versions must upgrade.
26
+
25
27
  ## Install
26
28
 
27
29
  ```bash
@@ -34,7 +36,7 @@ Or from GitHub:
34
36
  pi install git:github.com/KyrosHendrix/pi-KillerOS
35
37
  ```
36
38
 
37
- Pin a release by appending its tag, for example `@v3.0.1`. Add `-l` to install only for the current project. Restart Pi after installing.
39
+ Pin a release by appending its tag, for example `@v3.0.3`. Add `-l` to install only for the current project. Restart Pi after installing.
38
40
 
39
41
  ## Commands
40
42
 
@@ -69,8 +71,12 @@ Pi 1.0 defaults to fullscreen. Use `--tui-mode regular` for one invocation or se
69
71
  | RPC | Goals, proactive compaction; no TUI components, sounds, title indicator |
70
72
  | Print/JSON | No interactive questions, `/goal`, or proactive compaction |
71
73
 
74
+ With Pi 1.0.3's default [keybindings](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/keybindings.md#fullscreen), `Home` and `End` move within the current editor line. In fullscreen mode, `Ctrl+Home` and `Ctrl+End` scroll to the transcript's beginning or end without moving the editor cursor. KillerOS inherits these bindings; user overrides still apply.
75
+
72
76
  `question` and the active `killeros_goal_update` tool use Pi's `model-only` exposure. They stay directly declared to the model when codemode is disabled or enabled in `on` or `only` mode, but scripts and other tools cannot call them through `ctx.executeTool()`. The goal tool remains inactive without an active goal. Questions still require TUI mode; a direct RPC call fails with `The question tool requires interactive TUI mode`. KillerOS does not enable or configure codemode.
73
77
 
78
+ Pi 1.0.4's native `--tools` and `--exclude-tools` support `*` patterns. Quote patterns such as `"killeros_*"` so the shell does not expand them. An explicit allowlist must include `question` for interactive questions and permit `killeros_goal_update` for goals. Selection does not make questions usable outside TUI mode. Excluding the goal tool pauses goals before any model request. See Pi's [tool selection](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/cli.md#tools).
79
+
74
80
  ## Configuration
75
81
 
76
82
  The packaged `killeros` theme activates on TUI start. Compaction triggers by default at 15% tokens remaining, stored in global `killeros.json`:
@@ -103,7 +109,7 @@ A direct quoted path-shaped file target binds silent file proof. For an extensio
103
109
  /goal Fix `killeros/footer.ts`, verified by npm test
104
110
  ```
105
111
 
106
- Bare quoted prose remains model-reported. KillerOS captures the file baseline at goal start and only completes when the file is created or changed. A normal response never continues a goal by itself: the agent must record `continue`, `complete`, or a blocker decision through `killeros_goal_update`. Repeated continuation reports and unavailable goal tools pause the goal. 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. Same-turn recovery requires an actual interruption; successful or skipped compaction cannot restart a normally stopped goal without a decision. Restored goals keep their persisted limit.
112
+ Bare quoted prose remains model-reported. KillerOS captures the file baseline at goal start and only completes when the file is created or changed. A normal response never continues a goal by itself: the agent must record `continue`, `complete`, or a blocker decision through `killeros_goal_update`. Repeated continuation reports and unavailable goal tools pause the goal. 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. Same-turn recovery requires an actual interruption; successful or skipped compaction cannot restart a normally stopped goal without a decision. A request that completes or blocks a goal remains goal-owned through settlement, so automatic compaction cannot restart it as ordinary work. Later ordinary requests still support compaction continuation. Restored goals keep their persisted limit.
107
113
 
108
114
  Session replacement, reload, and committed tree navigation discard unfinished task receipts. Late receipt results do not write or notify through the old session context or attach to a different branch. Cancelled navigation preserves the pending receipt.
109
115
 
@@ -113,6 +119,32 @@ Failed Git metadata reads keep the footer's last successful file counts and mark
113
119
 
114
120
  Completion sounds are off by default; change with `/notification` in TUI mode. The tab-title indicator requires a Nerd Font.
115
121
 
122
+ ### Pi model and MCP settings
123
+
124
+ Use Pi's native `/thinking` selector for reasoning levels. Pi 1.0.2 can also apply different sampling parameters for each level through `samplingParamsByThinkingLevel` in `models.json`. For example, a model entry can contain:
125
+
126
+ ```json
127
+ {
128
+ "samplingParams": { "temperature": 1.0, "top_p": 0.95 },
129
+ "samplingParamsByThinkingLevel": {
130
+ "off": { "temperature": 0.7, "top_p": 0.8 },
131
+ "high": { "temperature": 0.6 }
132
+ }
133
+ }
134
+ ```
135
+
136
+ Only configure parameters the endpoint accepts. These settings apply to `openai-completions`, `openai-responses`, and `azure-openai-responses`, not legacy `openai-codex`. Missing levels inherit model defaults; request-level sampling parameters take precedence. `/codex-fast` preserves sampling parameters when it adds the priority service tier. See Pi's [sampling configuration](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/models.md#configure-sampling-by-thinking-level) for the full file format and merge rules.
137
+
138
+ Pi 1.0.3 renames the Azure **provider** from `azure-openai-responses` to `azure` and adds Azure Foundry Chat Completions, including `azure/deepseek-v4-pro`. Rename the provider key in `auth.json` or run `/login azure` again, rename it in `models.json`, and update `defaultProvider`, `enabledModels` patterns, and `modelThinkingLevels` keys in `settings.json`. The Responses **API** identifier remains `azure-openai-responses`; Foundry Chat Completions uses `openai-completions`. The `AZURE_OPENAI_*` environment variables are unchanged. Old Azure sessions fall back to another model on resume and lose prompt-cache reuse. KillerOS does not migrate credentials or Pi settings. See Pi's [Azure configuration](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/providers.md#azure-openai).
139
+
140
+ In Pi 1.0.4 codemode, `tools.read()` on an image returns an image block that you can pass to native `image()`. See Pi's [codemode tool calls](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/codemode.md#call-tools).
141
+
142
+ When codemode is enabled, Pi 1.0.3's `image()` also saves images to temporary files and includes their paths in the result, so later turns can copy or move them. Pi creates these output files with user-only permissions on POSIX systems; Windows access follows inherited filesystem ACLs. KillerOS uses Pi's native codemode behavior; see [image generation](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/codemode.md#generate-images).
143
+
144
+ Use Pi's `/mcp` command and `.pi/mcp.json` to enable, disable, or change the exposure of a user-level MCP server for a trusted project without copying its credentials. See [project MCP overrides](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/mcp.md#configure-servers). KillerOS does not manage MCP configuration.
145
+
146
+ In Pi 1.0.4, a nonempty `--tools` list with no `mcp__` entry preserves MCP tool availability according to each server's exposure. For example, `--tools codemode` does not disable MCP connections or remove their tools. Use `--no-mcp` to disable MCP connections through Pi's built-in MCP support for one run. Explicit MCP selection and exclusion remain Pi's responsibility; see [MCP tool exposure](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/mcp.md#control-tool-exposure).
147
+
116
148
  ## Development
117
149
 
118
150
  Strict TypeScript throughout. Tests run on Node's built-in test runner:
@@ -129,7 +161,9 @@ npm ci && npm run check && npm test
129
161
  4. Merge using **Create a merge commit**. Squash and rebase merging are disabled because the automated `main`-to-`dev` sync requires shared ancestry. Avoid advancing `dev` until that sync finishes.
130
162
  5. Check the `main` CI run and the following Release run. Successful CI on the current `main` commit triggers npm publication with provenance, creates the GitHub release, and fast-forwards `dev` to the released commit. A green PR alone never publishes.
131
163
 
132
- `main` requires pull requests and the CI workflow's required checks, including for administrators. Force pushes and branch deletion are blocked. If CI job names change, update GitHub's required status checks to match.
164
+ `main` requires pull requests and the CI workflow's required checks, including for administrators. Force pushes and branch deletion are blocked. Pi compatibility checks use the stable names `Pi compatibility (minimum)` and `Pi compatibility (latest)`, independent of the tested versions.
165
+
166
+ When adopting these names, first confirm both checks passed on the release PR's exact head commit. Replace only the two old versioned Pi required checks with their corresponding stable names, preserving their GitHub Actions app bindings and every other protection. If other CI job names change, update GitHub's required status checks to match.
133
167
 
134
168
  Releases go through CI on `main`; do not push version tags manually. The package smoke job validates release metadata before packing, and the release workflow checks it again before publication. The prepublish check rejects ordinary direct `npm publish`, but `--ignore-scripts` can bypass it. Configure npm's trusted publisher for `release.yml`, set package publishing access to "Require two-factor authentication and disallow tokens", and revoke unused publish tokens. npm maintainers can still publish interactively with 2FA, so workflow-only publishing also depends on maintainer policy.
135
169
 
@@ -137,6 +171,8 @@ Releases go through CI on `main`; do not push version tags manually. The package
137
171
 
138
172
  Pi extensions run with your user permissions. Review the source before installing globally. Hook commands run only for projects Pi marks as trusted; check `.pi/killeros-hooks.json` before enabling project trust. KillerOS accepts that configuration only as a regular, non-linked file no larger than 64 KiB in the project's real `.pi` directory.
139
173
 
174
+ On POSIX systems, timed-out or cancelled hooks receive `SIGTERM`, followed by `SIGKILL` if cleanup remains pending. Shell exit alone does not confirm that the original process group stopped. Cleanup uses a two-second window and reports uncertainty if group exit cannot be confirmed.
175
+
140
176
  Handoff validation rejects recognized credential assignments in plain text, Markdown lists, bold labels, and inline-code labels before creating a destination session. This pattern check cannot detect every secret or personally identifying value. Rejected values are not included in the error notification.
141
177
 
142
178
  ## License
@@ -121,12 +121,14 @@ export function registerAutoCompaction(
121
121
  const loadPreference = dependencies.loadPreference ?? (() => defaultPreference(settingsStore));
122
122
  const getCompactionSettings = dependencies.getCompactionSettings ?? defaultCompactionSettings;
123
123
  let request: AutoCompactionRequest | undefined;
124
+ let goalRequestOwned = false;
124
125
  let armed = true;
125
126
  let rearmOnNextLogicalRequest = false;
126
127
  let settingsErrorReported = false;
127
128
 
128
129
  const resetForLifecycle = (): void => {
129
130
  request = undefined;
131
+ goalRequestOwned = false;
130
132
  armed = true;
131
133
  rearmOnNextLogicalRequest = false;
132
134
  settingsErrorReported = false;
@@ -239,6 +241,11 @@ export function registerAutoCompaction(
239
241
  }
240
242
  };
241
243
 
244
+ pi.on("agent_start", (_event, ctx) => {
245
+ // Terminal decisions clear active status before turn_end, but still own this run.
246
+ goalRequestOwned = dependencies.goal?.isActive(ctx) === true;
247
+ });
248
+
242
249
  pi.on("turn_end", (_event, ctx) => {
243
250
  if (!supportedMode(ctx) || request) return;
244
251
 
@@ -277,7 +284,8 @@ export function registerAutoCompaction(
277
284
 
278
285
  armed = false;
279
286
  const token = Symbol();
280
- const goal = dependencies.goal?.isActive(ctx) === true;
287
+ const activeGoal = dependencies.goal?.isActive(ctx) === true;
288
+ const goal = goalRequestOwned || activeGoal;
281
289
  request = {
282
290
  phase: "compacting",
283
291
  goal,
@@ -285,7 +293,7 @@ export function registerAutoCompaction(
285
293
  turnSettled: false,
286
294
  compactionCompleted: false,
287
295
  };
288
- if (goal && dependencies.goal) {
296
+ if (activeGoal && dependencies.goal) {
289
297
  try {
290
298
  dependencies.goal.onRequested();
291
299
  } catch (error) {
@@ -299,26 +307,29 @@ export function registerAutoCompaction(
299
307
  onComplete: () => {
300
308
  if (request?.phase !== "compacting" || request.token !== token) return;
301
309
  rearmOnNextLogicalRequest = preference.percentRemaining === 100;
302
- if (goal && dependencies.goal) {
310
+ if (goal) {
303
311
  request = undefined;
304
- try {
305
- dependencies.goal.onCompleted(ctx);
306
- } catch (error) {
307
- notifyFailure(ctx, error);
312
+ if (activeGoal && dependencies.goal) {
313
+ try {
314
+ dependencies.goal.onCompleted(ctx);
315
+ } catch (error) {
316
+ notifyFailure(ctx, error);
317
+ }
308
318
  }
309
319
  return;
310
320
  }
311
321
  request.compactionCompleted = true;
312
322
  finalizeOrdinaryContinuation(ctx, token);
313
323
  },
314
- onError: (error) => finishRequestError(ctx, token, goal, error),
324
+ onError: (error) => finishRequestError(ctx, token, activeGoal, error),
315
325
  });
316
326
  } catch (error) {
317
- finishRequestError(ctx, token, goal, error);
327
+ finishRequestError(ctx, token, activeGoal, error);
318
328
  }
319
329
  });
320
330
 
321
331
  pi.on("agent_settled", (_event, ctx) => {
332
+ goalRequestOwned = false;
322
333
  if (request?.phase === "continuation-dispatched") {
323
334
  request = undefined;
324
335
  notifyFailure(ctx, new Error("continuation was not accepted"));
@@ -280,7 +280,7 @@ function sumSessionCost(ctx: ExtensionContext): number {
280
280
 
281
281
  const PROVIDER_LABELS: Readonly<Record<string, string>> = {
282
282
  "amazon-bedrock": "Amazon Bedrock",
283
- "azure-openai-responses": "Azure OpenAI",
283
+ azure: "Azure",
284
284
  "github-copilot": "GitHub Copilot",
285
285
  "google-vertex": "Google Vertex",
286
286
  "openai-codex": "OpenAI",
package/killeros/hooks.ts CHANGED
@@ -236,6 +236,17 @@ function terminateHookProcess(child: HookChildProcess, force: boolean): void {
236
236
  }
237
237
  }
238
238
 
239
+ function hookProcessGroupExited(child: HookChildProcess): boolean {
240
+ if (!child.pid) return false;
241
+ try {
242
+ process.kill(-child.pid, 0);
243
+ return false;
244
+ } catch (error) {
245
+ // Permission and other failures are uncertainty, not proof of exit.
246
+ return error instanceof Error && "code" in error && error.code === "ESRCH";
247
+ }
248
+ }
249
+
239
250
  export function executeHook(options: ExecuteHookOptions): Promise<HookExecutionResult>;
240
251
  /** @deprecated Use the object-argument form. The positional adapter will be removed in the next major release. */
241
252
  export function executeHook(
@@ -297,6 +308,8 @@ export function executeHook(
297
308
  let forceTimer: NodeJS.Timeout | undefined;
298
309
  let settleTimer: NodeJS.Timeout | undefined;
299
310
  let windowsCleanupPending = false;
311
+ let posixCleanupPending = false;
312
+ let shellClosed = false;
300
313
  const finish = (code: number, exitUnconfirmed = false): void => {
301
314
  if (completed) return;
302
315
  completed = true;
@@ -334,10 +347,15 @@ export function executeHook(
334
347
  }, 2_000);
335
348
  return;
336
349
  }
350
+ posixCleanupPending = process.platform !== "win32" && child.pid !== undefined;
337
351
  forceTimer = setTimeout(() => {
338
352
  if (completed) return;
339
353
  terminateHookProcess(child, true);
340
- settleTimer = setTimeout(() => finish(terminationCode(), true), 1_000);
354
+ if (completed) return;
355
+ settleTimer = setTimeout(() => {
356
+ const confirmed = posixCleanupPending && shellClosed && hookProcessGroupExited(child);
357
+ finish(terminationCode(), !confirmed);
358
+ }, 1_000);
341
359
  }, 1_000);
342
360
  terminateHookProcess(child, false);
343
361
  };
@@ -349,10 +367,13 @@ export function executeHook(
349
367
  if (child.stderr instanceof EventEmitter) child.stderr.on("error", captureStreamError);
350
368
  child.on("error", (error) => {
351
369
  appendBounded(stderr, error.message);
352
- if (!windowsCleanupPending) finish(termination ? terminationCode() : 1);
370
+ if (!windowsCleanupPending && !posixCleanupPending) finish(termination ? terminationCode() : 1);
353
371
  });
354
372
  child.once("close", (code) => {
355
- if (!windowsCleanupPending) finish(termination ? terminationCode() : code ?? 1);
373
+ shellClosed = true;
374
+ if (!windowsCleanupPending && (!posixCleanupPending || hookProcessGroupExited(child))) {
375
+ finish(termination ? terminationCode() : code ?? 1);
376
+ }
356
377
  });
357
378
  if (completed) return;
358
379
  const boundedTimeoutMs = Number.isNaN(timeoutMs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "killeros",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "TUI, goals, and workflow automation for the Pi coding agent",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -45,16 +45,16 @@
45
45
  ]
46
46
  },
47
47
  "peerDependencies": {
48
- "@earendil-works/pi-ai": ">=0.99.2 <2",
49
- "@earendil-works/pi-coding-agent": ">=0.99.2 <2",
50
- "@earendil-works/pi-tui": ">=0.99.2 <2",
48
+ "@earendil-works/pi-ai": ">=1.0.4 <2",
49
+ "@earendil-works/pi-coding-agent": ">=1.0.4 <2",
50
+ "@earendil-works/pi-tui": ">=1.0.4 <2",
51
51
  "typebox": ">=1.1.38 <2"
52
52
  },
53
53
  "devDependencies": {
54
- "@earendil-works/pi-ai": "1.0.0",
55
- "@earendil-works/pi-coding-agent": "1.0.0",
56
- "@earendil-works/pi-server": "1.0.0",
57
- "@earendil-works/pi-tui": "1.0.0",
54
+ "@earendil-works/pi-ai": "1.0.4",
55
+ "@earendil-works/pi-coding-agent": "1.0.4",
56
+ "@earendil-works/pi-server": "1.0.4",
57
+ "@earendil-works/pi-tui": "1.0.4",
58
58
  "@types/node": "24.12.4",
59
59
  "eslint": "^10.9.1",
60
60
  "typebox": "1.3.20",