killeros 3.0.1 → 3.0.2

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,24 @@ All notable changes to KillerOS are documented here. Releases use New Features f
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [3.0.2] - 2026-10-05
8
+
9
+ ### Changed
10
+
11
+ - 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.
12
+ - Documented Pi's native thinking-level sampling configuration and trusted-project MCP overrides without adding KillerOS commands or settings.
13
+
14
+ ### Added
15
+
16
+ - Added real Pi contract coverage for capacity-error recovery within one logical goal turn, with task receipts and completion sounds deferred until settlement.
17
+ - Verified that native OpenAI fast mode preserves thinking-level sampling overrides across API-key and ChatGPT subscription requests, extension reloads, and provider retries.
18
+
19
+ ### Fixed
20
+
21
+ - 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.
22
+ - 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.
23
+ - 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.
24
+
7
25
  ## [3.0.1] - 2026-10-02
8
26
 
9
27
  ### Changed
package/README.md CHANGED
@@ -19,7 +19,7 @@ A TypeScript extension for the [Pi coding agent](https://github.com/earendil-wor
19
19
  ## Requirements
20
20
 
21
21
  - Node.js 22.19.0+
22
- - Pi 0.99.2 or later below 2.0.0
22
+ - Pi 1.0.2 or later below 2.0.0
23
23
  - An interactive TUI session for the custom header, editor, footer, and `question`
24
24
 
25
25
  ## Install
@@ -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 `@v3.0.1`. Add `-l` to install only for the current project. Restart Pi after installing.
37
+ Pin a release by appending its tag, for example `@v3.0.2`. Add `-l` to install only for the current project. Restart Pi after installing.
38
38
 
39
39
  ## Commands
40
40
 
@@ -103,7 +103,7 @@ A direct quoted path-shaped file target binds silent file proof. For an extensio
103
103
  /goal Fix `killeros/footer.ts`, verified by npm test
104
104
  ```
105
105
 
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.
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. 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
107
 
108
108
  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
109
 
@@ -113,6 +113,24 @@ Failed Git metadata reads keep the footer's last successful file counts and mark
113
113
 
114
114
  Completion sounds are off by default; change with `/notification` in TUI mode. The tab-title indicator requires a Nerd Font.
115
115
 
116
+ ### Pi model and MCP settings
117
+
118
+ 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:
119
+
120
+ ```json
121
+ {
122
+ "samplingParams": { "temperature": 1.0, "top_p": 0.95 },
123
+ "samplingParamsByThinkingLevel": {
124
+ "off": { "temperature": 0.7, "top_p": 0.8 },
125
+ "high": { "temperature": 0.6 }
126
+ }
127
+ }
128
+ ```
129
+
130
+ 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.
131
+
132
+ 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.
133
+
116
134
  ## Development
117
135
 
118
136
  Strict TypeScript throughout. Tests run on Node's built-in test runner:
@@ -137,6 +155,8 @@ Releases go through CI on `main`; do not push version tags manually. The package
137
155
 
138
156
  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
157
 
158
+ 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.
159
+
140
160
  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
161
 
142
162
  ## 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"));
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.2",
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.2 <2",
49
+ "@earendil-works/pi-coding-agent": ">=1.0.2 <2",
50
+ "@earendil-works/pi-tui": ">=1.0.2 <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.2",
55
+ "@earendil-works/pi-coding-agent": "1.0.2",
56
+ "@earendil-works/pi-server": "1.0.2",
57
+ "@earendil-works/pi-tui": "1.0.2",
58
58
  "@types/node": "24.12.4",
59
59
  "eslint": "^10.9.1",
60
60
  "typebox": "1.3.20",