killeros 2.1.30 → 2.1.32

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
@@ -1,9 +1,24 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to KillerOS are documented here.
3
+ All notable changes to KillerOS are documented here. Releases use New Features for new capabilities, Added for smaller additions, Changed for updates and removals, and Fixed for corrections. Empty sections are omitted.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.1.32] - 2026-09-24
8
+
9
+ ### Fixed
10
+
11
+ - Restored tok/s on completed worked-for receipts using reported output tokens over observed model-response intervals, including reasoning and mixed-model tasks; incomplete or failed calls omit the rate.
12
+ - Prevented file-verified goal completion when an objective names another unquoted relative file, such as ``Fix `a.ts` plus b.ts``.
13
+ - Added release-metadata checks for ordinary npm publication and the GitHub release workflow, including the README's pinned version example.
14
+
15
+ ## [2.1.31] - 2026-09-22
16
+
17
+ ### Fixed
18
+
19
+ - Made `/handoff` honor Pi context edits by using the canonical session projection when available while retaining compatibility with Pi 0.85 and 0.86.
20
+ - Omitted worked-for throughput unless the provider explicitly reports zero reasoning tokens.
21
+
7
22
  ## [2.1.30] - 2026-09-21
8
23
 
9
24
  ### Added
@@ -21,7 +36,7 @@ All notable changes to KillerOS are documented here.
21
36
 
22
37
  ## [2.1.29] - 2026-09-19
23
38
 
24
- ### Added
39
+ ### New Features
25
40
 
26
41
  - Added `/auto-compact [status|on|off|<percent>]` to inspect and update the global automatic compaction preference without restarting Pi.
27
42
 
@@ -35,15 +50,15 @@ All notable changes to KillerOS are documented here.
35
50
 
36
51
  ## [2.1.28] - 2026-09-15
37
52
 
53
+ ### Added
54
+
55
+ - Appended the selected model ID as a gray `· <model-id>` suffix to every TUI activity working message, retained for the full request cycle and falling back to `unknown model` when no usable ID exists.
56
+
38
57
  ### Changed
39
58
 
40
59
  - Simplified goal presentation without changing goal behavior: transcript history shows one iconless event row per lifecycle change (`Goal started`, `Goal replaced`, `Goal paused`, `Goal resumed`, `Goal cleared`), progress and blocker outcomes render once through the goal tool result, the footer shows compact `/goal active · 1/20 · 10s` status, the `/goal` panel leads with status then objective then `Pause goal` / `Resume goal` / `Clear goal` actions, and duplicate TUI success notifications are suppressed while RPC confirmations remain. Active-goal labels use muted teal `#6FAEB2` through the packaged theme's extension-label role.
41
60
  - Removed the legacy `✻` marker from version-1 `Worked for …` transcript rows; stored entries are unchanged and remain readable.
42
61
 
43
- ### Added
44
-
45
- - Appended the selected model ID as a gray `· <model-id>` suffix to every TUI activity working message, retained for the full request cycle and falling back to `unknown model` when no usable ID exists.
46
-
47
62
  ### Fixed
48
63
 
49
64
  - Kept resumed blocked goals and goals paused by truncated provider errors readable after reload.
@@ -57,6 +72,10 @@ All notable changes to KillerOS are documented here.
57
72
 
58
73
  - Added `/codex-fast status` to report whether Codex fast mode is enabled without changing it.
59
74
 
75
+ ### Changed
76
+
77
+ - Removed `/init` and its `killeros_init_*` tools, prompts, workflow APIs, runtime state, and lifecycle handlers. Upgrading leaves existing `AGENTS.md` and recovery files untouched, and commands from other providers named `/init` pass through KillerOS unhandled.
78
+
60
79
  ### Fixed
61
80
 
62
81
  - Restricted automatic startup, footer, and change-receipt Git scans to trusted projects, preventing repository executables and configuration races from launching commands before trust is granted.
@@ -67,10 +86,6 @@ All notable changes to KillerOS are documented here.
67
86
  - Recorded focused Node test runs in response receipts without persisting their test paths.
68
87
  - Kept passive footer Git status from running configured filters, bounded loose and packed receipt blobs, and stopped stale `/handoff` summaries from replacing sessions with new work.
69
88
 
70
- ### Removed
71
-
72
- - Removed `/init` and its `killeros_init_*` tools, prompts, workflow APIs, runtime state, and lifecycle handlers. Upgrading leaves existing `AGENTS.md` and recovery files untouched, and commands from other providers named `/init` pass through KillerOS unhandled.
73
-
74
89
  ## [2.1.26] - 2026-09-08
75
90
 
76
91
  ### Fixed
@@ -81,10 +96,6 @@ All notable changes to KillerOS are documented here.
81
96
 
82
97
  ## [2.1.25] - 2026-09-06
83
98
 
84
- ### Fixed
85
-
86
- - Left-aligned settled `✓ Done` receipt lines so every row shares the assistant text edge.
87
-
88
99
  ### Changed
89
100
 
90
101
  - Replaced the boxed startup card with a three-row masthead: coral fade mark, versions, model and reasoning level, directory and branch, and an italic tip.
@@ -92,10 +103,11 @@ All notable changes to KillerOS are documented here.
92
103
  - Restored the leading `❯` prompt marker in white in the chat editor.
93
104
  - Rendered footer model names and the `fast` badge in non-bold white.
94
105
  - Showed the current Pi version beside the KillerOS version in the startup header.
106
+ - Removed the `/variants` reasoning-level command in favor of Pi's native `/thinking` selector.
95
107
 
96
- ### Removed
108
+ ### Fixed
97
109
 
98
- - Removed the `/variants` reasoning-level command in favor of Pi's native `/thinking` selector.
110
+ - Left-aligned settled `✓ Done` receipt lines so every row shares the assistant text edge.
99
111
 
100
112
  ## [2.1.24] - 2026-09-05
101
113
 
@@ -260,7 +272,7 @@ All notable changes to KillerOS are documented here.
260
272
 
261
273
  ## [2.0.15] - 2026-08-23
262
274
 
263
- ### Added
275
+ ### New Features
264
276
 
265
277
  - Added `/handoff [focus]` for fresh linked sessions with visible continuation context.
266
278
 
@@ -299,18 +311,18 @@ All notable changes to KillerOS are documented here.
299
311
 
300
312
  ## [2.0.11] - 2026-08-17
301
313
 
314
+ ### Changed
315
+
316
+ - Removed the decision-gated workflow subsystem, its skill-specific policy, public extension interface, and YAML dependency. Skills now remain instruction-only.
317
+
302
318
  ### Fixed
303
319
 
304
320
  - Made automated npm publication and GitHub release recovery idempotent, aligned the documented and tested Pi floor, and made machine-identifier casing locale-independent.
305
321
  - Extended strict type checking to release scripts and typed test suites, while correctly identifying JavaScript-only suites as `.js`.
306
322
 
307
- ### Removed
308
-
309
- - Removed the decision-gated workflow subsystem, its skill-specific policy, public extension interface, and YAML dependency. Skills now remain instruction-only.
310
-
311
323
  ## [2.0.10] - 2026-08-16
312
324
 
313
- ### Added
325
+ ### New Features
314
326
 
315
327
  - Added proactive turn-boundary compaction with Pi-owned settings and safe continuation for ordinary prompts and active goals.
316
328
  - Added reusable multi-activation decision-gated workflow registrations while preserving exact-match behavior.
@@ -328,12 +340,9 @@ All notable changes to KillerOS are documented here.
328
340
  - Added a reusable, opt-in pre-turn gate for explicitly activated decision-gated workflows. It opens the shared structured question UI before skill expansion, keeps the selected policy active, blocks unknown and disallowed tools, and clears safely across lifecycle boundaries.
329
341
  - Added a disposable decision-gated workflow fixture and focused coverage for activation ordering, pending safety, policy allowlists, lifecycle cleanup, adapter reuse, and Pi 0.84.2 compatibility.
330
342
 
331
- ### Removed
332
-
333
- - Removed KillerOS's always-on concise response policy and provider-specific concise defaults; Pi now owns response-style guidance.
334
-
335
343
  ### Changed
336
344
 
345
+ - Removed KillerOS's always-on concise response policy and provider-specific concise defaults; Pi now owns response-style guidance.
337
346
  - Raised the locked direct Pi development packages to 0.84.2 while keeping the peer dependency floor at 0.84.1.
338
347
 
339
348
  ## [2.0.7] - 2026-08-13
@@ -520,11 +529,14 @@ All notable changes to KillerOS are documented here.
520
529
 
521
530
  ## [1.3.0] - 2026-07-31
522
531
 
523
- ### Added
532
+ ### New Features
524
533
 
525
534
  - Claude Code-style `/init` that builds a bounded repository snapshot and generates a concise root `AGENTS.md` with one controlled write.
526
535
  - Codex-style `/goal` with durable branch-scoped state, automatic one-turn continuation, pause/resume/edit/clear controls, and explicit model-reported completion or blocking.
527
536
  - Trusted `AGENTS.local.md` personal guidance and project lifecycle hooks for tool-call, tool-result, and settled-agent events.
537
+
538
+ ### Added
539
+
528
540
  - Focused coverage for goal transitions, recovery paths, branch restoration, mode gating, footer cutdowns, and repository initialization safeguards.
529
541
 
530
542
  ### Changed
@@ -534,12 +546,15 @@ All notable changes to KillerOS are documented here.
534
546
 
535
547
  ## [1.2.0] - 2026-07-30
536
548
 
537
- ### Added
549
+ ### New Features
538
550
 
539
551
  - A 52-column Compact startup card with inline version, polished model/provider identity, reasoning level, `/model`, working directory, and conditional Git branch.
540
- - A shuffled startup-tip deck that keeps one tip stable per session and exhausts the bank before repeating.
541
552
  - A responsive footer that preserves model and context while progressively removing lower-priority telemetry.
542
553
  - Packaged KillerOS theme with coral accents and one neutral tool-call surface across pending, success, and error states.
554
+
555
+ ### Added
556
+
557
+ - A shuffled startup-tip deck that keeps one tip stable per session and exhausts the bank before repeating.
543
558
  - Single-glyph Spark activity indicator with a restrained color pulse.
544
559
  - Claude-adjacent activity word bank that advances between agent runs.
545
560
  - Static `└ Thinking…` label for hidden reasoning blocks.
package/README.md CHANGED
@@ -32,7 +32,7 @@ Or from GitHub:
32
32
  pi install git:github.com/KyrosHendrix/pi-KillerOS
33
33
  ```
34
34
 
35
- Pin a release by appending its tag, for example `@v2.1.30`. Add `-l` to install only for the current project. Restart Pi after installing.
35
+ Pin a release by appending its tag, for example `@v2.1.32`. Add `-l` to install only for the current project. Restart Pi after installing.
36
36
 
37
37
  ## Commands
38
38
 
@@ -101,7 +101,7 @@ Strict TypeScript throughout. Tests run on Node's built-in test runner:
101
101
  npm ci && npm run check && npm test
102
102
  ```
103
103
 
104
- Releases go through CI on `main`; do not push version tags manually.
104
+ Releases go through CI on `main`; do not push version tags manually. 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.
105
105
 
106
106
  ## Security
107
107
 
@@ -349,6 +349,7 @@ const GOAL_URL_PATTERN = /(?:https?|file):\/\/[^\s"'`]+/giu;
349
349
  const GOAL_QUOTED_PATTERN = /`([^`\r\n]+)`|"([^"\r\n]+)"|'([^'\r\n]+)'/gu;
350
350
  const GOAL_ABSOLUTE_PATTERN = /(?:^|[\s"'`(\[{,;])([A-Za-z]:[\\/][^\s,;'"`]+|\/[^\s,;'"`]+)/gu;
351
351
  const GOAL_LIST_FILE_PATTERN = /(?:\band\b|\bor\b|[,;&+])\s*(?:`([^`\r\n]+)`|"([^"\r\n]+)"|'([^'\r\n]+)'|([A-Za-z0-9_][A-Za-z0-9_.-]*(?:[\\/][A-Za-z0-9_.-]+)*\.[A-Za-z0-9]{1,12})\b)/giu;
352
+ const GOAL_RELATIVE_FILE_PATTERN = /(?:^|[\s"'`(\[{,;])((?:\.{1,2}[\\/])*[A-Za-z0-9_][A-Za-z0-9_.-]*(?:[\\/][A-Za-z0-9_.-]+)*\.[A-Za-z0-9]{1,12})\b/gu;
352
353
 
353
354
  function isQuotedGoalFileMention(raw: string): boolean {
354
355
  const value = raw.trim();
@@ -381,6 +382,9 @@ function countGoalTargetFiles(objective: string, cwd: string, target: string): n
381
382
  if (match[1]) add(stripUnquotedPathPunctuation(match[1].trim()));
382
383
  }
383
384
  for (const match of prose.matchAll(GOAL_LIST_FILE_PATTERN)) add(match[1] ?? match[2] ?? match[3] ?? match[4] ?? "");
385
+ for (const match of prose.replace(GOAL_QUOTED_PATTERN, " ").matchAll(GOAL_RELATIVE_FILE_PATTERN)) {
386
+ if (match[1]) add(match[1]);
387
+ }
384
388
  return mentions.size;
385
389
  }
386
390
 
@@ -240,7 +240,13 @@ export function registerHandoff(pi: ExtensionAPI, goalRuntime: GoalRuntime, hand
240
240
  try {
241
241
  const entries = ctx.sessionManager.buildContextEntries();
242
242
  const sourceLeaf = entries.at(-1)?.id;
243
- const conversation = serializeConversation(convertToLlm(entries.flatMap(sessionEntryToContextMessages)));
243
+ const sessionManager = ctx.sessionManager as typeof ctx.sessionManager & {
244
+ buildSessionProjection?: () => { messages: ReturnType<typeof sessionEntryToContextMessages> };
245
+ };
246
+ const messages = typeof sessionManager.buildSessionProjection === "function"
247
+ ? sessionManager.buildSessionProjection().messages
248
+ : entries.flatMap(sessionEntryToContextMessages);
249
+ const conversation = serializeConversation(convertToLlm(messages));
244
250
  if (!conversation.trim()) throw new Error("No usable session context is available");
245
251
  focus = safeTerminalText(args).trim();
246
252
  let maxTokens = handoffMaxTokens;
@@ -50,6 +50,7 @@ export interface WorkedForEntryDataV4 {
50
50
  model?: string;
51
51
  outputTokens?: number;
52
52
  decodeMilliseconds?: number;
53
+ responseMilliseconds?: number;
53
54
  }
54
55
 
55
56
  type WorkedForEntryData = WorkedForEntryDataV1 | WorkedForEntryDataV2 | WorkedForEntryDataV3 | WorkedForEntryDataV4;
@@ -72,6 +73,10 @@ function positiveInteger(value: unknown): value is number {
72
73
  return integer(value) && value > 0;
73
74
  }
74
75
 
76
+ function positiveDuration(value: unknown): value is number {
77
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
78
+ }
79
+
75
80
  function record(value: unknown): value is Record<string, unknown> {
76
81
  return typeof value === "object" && value !== null && !Array.isArray(value);
77
82
  }
@@ -154,9 +159,11 @@ function parseV4(data: Record<string, unknown>): WorkedForEntryDataV4 | undefine
154
159
  checks.push({ label, outcome: check.outcome });
155
160
  }
156
161
  const model = parseModelName(data.model);
157
- const throughput = positiveInteger(data.outputTokens) && positiveInteger(data.decodeMilliseconds)
158
- ? { outputTokens: data.outputTokens, decodeMilliseconds: data.decodeMilliseconds }
159
- : {};
162
+ const throughput = positiveInteger(data.outputTokens) && data.decodeMilliseconds === undefined && positiveDuration(data.responseMilliseconds)
163
+ ? { outputTokens: data.outputTokens, responseMilliseconds: data.responseMilliseconds }
164
+ : positiveInteger(data.outputTokens) && data.responseMilliseconds === undefined && positiveInteger(data.decodeMilliseconds)
165
+ ? { outputTokens: data.outputTokens, decodeMilliseconds: data.decodeMilliseconds }
166
+ : {};
160
167
  return {
161
168
  version: 4,
162
169
  milliseconds: data.milliseconds,
@@ -231,11 +238,12 @@ class WorkedForV4Component implements Component {
231
238
  const { data, theme } = this;
232
239
  const outcome = OUTCOMES[data.outcome];
233
240
  const headline = theme.fg(outcome.color, outcome.label);
234
- const tokensPerSecond = data.outputTokens !== undefined && data.decodeMilliseconds !== undefined
235
- ? data.outputTokens / (data.decodeMilliseconds / 1_000)
241
+ const duration = data.responseMilliseconds ?? data.decodeMilliseconds;
242
+ const tokensPerSecond = data.outputTokens !== undefined && duration !== undefined && (data.responseMilliseconds === undefined || data.outcome === "done")
243
+ ? data.outputTokens / (duration / 1_000)
236
244
  : undefined;
237
245
  const speedSuffix = tokensPerSecond !== undefined && Number.isFinite(tokensPerSecond) && tokensPerSecond > 0
238
- ? ` · ${tokensPerSecond.toFixed(1)} tok/s`
246
+ ? ` · ${data.responseMilliseconds !== undefined && tokensPerSecond < 0.05 ? "<0.1" : tokensPerSecond.toFixed(1)} tok/s`
239
247
  : "";
240
248
  const modelSuffix = data.model ? ` · ${data.model}` : "";
241
249
  const lines = [
@@ -304,31 +312,19 @@ type ActiveReceipt = {
304
312
  modelId: string | undefined;
305
313
  modelMismatch: boolean;
306
314
  outputTokens: number;
307
- decodeMilliseconds: number;
308
- decodeStartedAt: number | undefined;
315
+ responseMilliseconds: number;
316
+ responseStartedAt: number | undefined;
309
317
  throughputInvalid: boolean;
310
318
  };
311
319
 
312
- function finishAssistantStream(
313
- receipt: ActiveReceipt,
314
- outputTokens: number,
315
- endedAt: number,
316
- failed: boolean,
317
- ): void {
318
- if (failed) {
319
- if (receipt.decodeStartedAt !== undefined) receipt.throughputInvalid = true;
320
- } else if (receipt.decodeStartedAt !== undefined) {
321
- const decodeMilliseconds = endedAt - receipt.decodeStartedAt;
322
- if (positiveInteger(outputTokens) && positiveInteger(decodeMilliseconds)) {
323
- receipt.outputTokens += outputTokens;
324
- receipt.decodeMilliseconds += decodeMilliseconds;
325
- } else {
326
- receipt.throughputInvalid = true;
327
- }
328
- } else if (outputTokens !== 0) {
329
- receipt.throughputInvalid = true;
320
+ function finishAssistantStream(receipt: ActiveReceipt, outputTokens: unknown, endedAt: number, failed: boolean): void {
321
+ const duration = receipt.responseStartedAt === undefined ? undefined : endedAt - receipt.responseStartedAt;
322
+ if (failed || !integer(outputTokens) || !positiveDuration(duration)) receipt.throughputInvalid = true;
323
+ else {
324
+ receipt.outputTokens += outputTokens;
325
+ receipt.responseMilliseconds += duration;
330
326
  }
331
- receipt.decodeStartedAt = undefined;
327
+ receipt.responseStartedAt = undefined;
332
328
  }
333
329
 
334
330
  function receiptModelName(
@@ -392,8 +388,8 @@ export function registerWorkedFor(
392
388
  modelId: undefined,
393
389
  modelMismatch: false,
394
390
  outputTokens: 0,
395
- decodeMilliseconds: 0,
396
- decodeStartedAt: undefined,
391
+ responseMilliseconds: 0,
392
+ responseStartedAt: undefined,
397
393
  throughputInvalid: false,
398
394
  };
399
395
  active = state;
@@ -401,11 +397,10 @@ export function registerWorkedFor(
401
397
  if (active !== state) await collection.dispose();
402
398
  });
403
399
 
404
- pi.on("message_update", (event, ctx) => {
405
- if (ctx.mode !== "tui" || !active) return;
406
- const update = event.assistantMessageEvent;
407
- if ((update.type === "text_delta" || update.type === "thinking_delta" || update.type === "toolcall_delta")
408
- && update.delta.length > 0) active.decodeStartedAt ??= now();
400
+ pi.on("message_start", (event, ctx) => {
401
+ if (ctx.mode !== "tui" || !active || event.message.role !== "assistant") return;
402
+ if (active.responseStartedAt !== undefined) active.throughputInvalid = true;
403
+ active.responseStartedAt = now();
409
404
  });
410
405
 
411
406
  pi.on("message_end", (event, ctx) => {
@@ -427,7 +422,7 @@ export function registerWorkedFor(
427
422
  }
428
423
  finishAssistantStream(
429
424
  active,
430
- event.message.usage.output,
425
+ event.message.usage?.output,
431
426
  now(),
432
427
  event.message.stopReason === "error" || event.message.stopReason === "aborted",
433
428
  );
@@ -462,14 +457,15 @@ export function registerWorkedFor(
462
457
  ctx.ui.notify(`Change receipt unavailable: ${changes.reason}`, "warning");
463
458
  }
464
459
  const settledTokens = sessionTokenTotal(ctx);
465
- const throughput = !settled.throughputInvalid && settled.decodeStartedAt === undefined && !settled.modelMismatch
466
- && positiveInteger(settled.outputTokens) && positiveInteger(settled.decodeMilliseconds)
467
- ? { outputTokens: settled.outputTokens, decodeMilliseconds: settled.decodeMilliseconds }
460
+ const outcome = workedForOutcome(settled.stopReason);
461
+ const throughput = outcome === "done" && !settled.throughputInvalid && settled.responseStartedAt === undefined
462
+ && positiveInteger(settled.outputTokens) && positiveDuration(settled.responseMilliseconds)
463
+ ? { outputTokens: settled.outputTokens, responseMilliseconds: settled.responseMilliseconds }
468
464
  : {};
469
465
  const data = fitPayload({
470
466
  version: 4,
471
467
  milliseconds: Math.max(0, now() - settled.startedAt),
472
- outcome: workedForOutcome(settled.stopReason),
468
+ outcome,
473
469
  tokens: settled.startedTokens === undefined || settledTokens === undefined ? 0 : Math.max(0, settledTokens - settled.startedTokens),
474
470
  changes,
475
471
  checks: settled.checks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "killeros",
3
- "version": "2.1.30",
3
+ "version": "2.1.32",
4
4
  "description": "TUI, goals, and workflow automation for the Pi coding agent",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -33,6 +33,7 @@
33
33
  "benchmark:change-receipt": "node --test --experimental-strip-types test/ChangeReceipt.bench.ts",
34
34
  "check": "tsc --noEmit && eslint .",
35
35
  "lint": "eslint .",
36
+ "prepublishOnly": "node --experimental-strip-types scripts/verify-release.ts",
36
37
  "test": "node --test --test-force-exit --experimental-strip-types test/*.test.*"
37
38
  },
38
39
  "pi": {