killeros 2.1.23 → 2.1.25
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 +29 -0
- package/Killeros.ts +0 -2
- package/README.md +3 -5
- package/killeros/commands.ts +0 -1
- package/killeros/display.ts +7 -0
- package/killeros/footer.ts +14 -34
- package/killeros/shell-ui.ts +22 -47
- package/killeros/variants.ts +1 -171
- package/killeros/worked-for.ts +73 -22
- package/package.json +8 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@ All notable changes to KillerOS are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.1.25] - 2026-09-06
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Left-aligned settled `✓ Done` receipt lines so every row shares the assistant text edge.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
- Swapped the working indicator to the 10-frame Braille orbit at 80 ms per frame.
|
|
17
|
+
- Restored the leading `❯` prompt marker in white in the chat editor.
|
|
18
|
+
- Rendered footer model names and the `fast` badge in non-bold white.
|
|
19
|
+
- Showed the current Pi version beside the KillerOS version in the startup header.
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- Removed the `/variants` reasoning-level command in favor of Pi's native `/thinking` selector.
|
|
24
|
+
|
|
25
|
+
## [2.1.24] - 2026-09-05
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Appended the settled model display name to version 4 worked-for receipts for single-model TUI runs.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Rendered settled `Done` receipts without the `✓` marker, keeping `■ Stopped`, `× Failed`, and version 1 history unchanged.
|
|
34
|
+
- Raised the locked Pi development packages and minimum supported Pi peer version to 0.85.0, including `@earendil-works/pi-server`.
|
|
35
|
+
|
|
7
36
|
## [2.1.23] - 2026-09-04
|
|
8
37
|
|
|
9
38
|
### Changed
|
package/Killeros.ts
CHANGED
|
@@ -22,7 +22,6 @@ import { registerPersonalInstructions } from "./killeros/personal-instructions.t
|
|
|
22
22
|
import { registerQuestionTool } from "./killeros/question.ts";
|
|
23
23
|
import { createGoalRuntime, createInitRuntime } from "./killeros/runtime.ts";
|
|
24
24
|
import { registerShellUi } from "./killeros/shell-ui.ts";
|
|
25
|
-
import { registerVariants } from "./killeros/variants.ts";
|
|
26
25
|
import { registerWorkedFor } from "./killeros/worked-for.ts";
|
|
27
26
|
|
|
28
27
|
export { contextPercentRemaining, formatCost, formatContextProgress } from "./killeros/footer.ts";
|
|
@@ -49,7 +48,6 @@ export default function Killeros(pi: ExtensionAPI, options: KillerosOptions = {}
|
|
|
49
48
|
registerHandoff(pi, goalRuntime, options.handoffMaxTokens);
|
|
50
49
|
registerSlashAutocomplete(pi, commandResolver);
|
|
51
50
|
registerFooter(pi, goalRuntime);
|
|
52
|
-
registerVariants(pi);
|
|
53
51
|
registerCodexFastMode(pi);
|
|
54
52
|
registerInitCommand(pi, initRuntime, goalRuntime);
|
|
55
53
|
registerLifecycleHooks(pi);
|
package/README.md
CHANGED
|
@@ -4,10 +4,9 @@ A TypeScript extension for the [Pi coding agent](https://github.com/earendil-wor
|
|
|
4
4
|
|
|
5
5
|
## What you get
|
|
6
6
|
|
|
7
|
-
- A custom TUI: startup
|
|
7
|
+
- A custom TUI: startup masthead with versions, model, working directory, and Git branch; a dark theme with coral accents; a multiline editor with slash-command completion; a footer that tracks model, context, and goal state; settled task receipts with duration and token usage.
|
|
8
8
|
- `/goal`: set an objective and Pi keeps working toward it across turns, compaction, reloads, and branch navigation. New goals pause after 20 turns; `/goal resume` grants another 20.
|
|
9
9
|
- `/init`: generates a root `AGENTS.md` from repository evidence, preserving compatible existing rules.
|
|
10
|
-
- `/variants`: pick a reasoning level supported by the active model.
|
|
11
10
|
- `/codex-fast`: toggles the `priority` service tier on Codex requests.
|
|
12
11
|
- `/handoff`: starts a fresh linked session carrying visible continuation context.
|
|
13
12
|
- Automatic context compaction when remaining tokens drop below 15% of the window (configurable).
|
|
@@ -19,7 +18,7 @@ A TypeScript extension for the [Pi coding agent](https://github.com/earendil-wor
|
|
|
19
18
|
## Requirements
|
|
20
19
|
|
|
21
20
|
- Node.js 22.19.0+
|
|
22
|
-
- Pi 0.
|
|
21
|
+
- Pi 0.85.0 or later within the 0.x release line
|
|
23
22
|
- An interactive TUI session for the custom header, editor, footer, `question`, and `/init`
|
|
24
23
|
|
|
25
24
|
## Install
|
|
@@ -34,7 +33,7 @@ Or from GitHub:
|
|
|
34
33
|
pi install git:github.com/KyrosHendrix/pi-KillerOS
|
|
35
34
|
```
|
|
36
35
|
|
|
37
|
-
Pin a release by appending its tag, for example `@v2.1.
|
|
36
|
+
Pin a release by appending its tag, for example `@v2.1.25`. Add `-l` to install only for the current project. Restart Pi after installing.
|
|
38
37
|
|
|
39
38
|
## Commands
|
|
40
39
|
|
|
@@ -45,7 +44,6 @@ Pin a release by appending its tag, for example `@v2.1.23`. Add `-l` to install
|
|
|
45
44
|
/goal pause Stop automatic continuation
|
|
46
45
|
/goal resume Resume automatic continuation
|
|
47
46
|
/goal clear Remove the current goal
|
|
48
|
-
/variants Reasoning-level selector (/variants high sets directly)
|
|
49
47
|
/codex-fast Toggle Codex fast mode
|
|
50
48
|
/notification Configure the completion sound
|
|
51
49
|
/handoff [focus] Fresh session with continuation context
|
package/killeros/commands.ts
CHANGED
|
@@ -72,7 +72,6 @@ const BUILTIN_COMMANDS: ReadonlyArray<{ name: string; description: string }> = [
|
|
|
72
72
|
const COMMAND_SYNTAX_HINTS: Readonly<Record<string, string>> = {
|
|
73
73
|
goal: "/goal [objective|pause|resume|clear]",
|
|
74
74
|
handoff: "/handoff [next-session focus]",
|
|
75
|
-
variants: "/variants [level]",
|
|
76
75
|
model: "/model [provider/model]",
|
|
77
76
|
"scoped-models": "/scoped-models",
|
|
78
77
|
login: "/login [provider]",
|
package/killeros/display.ts
CHANGED
|
@@ -51,3 +51,10 @@ export function formatTokens(value: number): string {
|
|
|
51
51
|
if (inK >= 1_000) return `${Number((rounded / 1_000_000).toFixed(1))}M`;
|
|
52
52
|
return `${inK}k`;
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
/** Resolves a lowercase model id for display, falling back to the name. */
|
|
56
|
+
export function modelDisplayName(model: { name?: string; id?: string }): string {
|
|
57
|
+
const id = safeTerminalText(model.id ?? "").replaceAll("\n", "").trim();
|
|
58
|
+
if (id) return id.toLowerCase();
|
|
59
|
+
return safeTerminalText(model.name ?? "").replaceAll("\n", "").trim().toLowerCase();
|
|
60
|
+
}
|
package/killeros/footer.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { watch } from "node:fs";
|
|
|
3
3
|
import { type ExtensionAPI, type ExtensionContext, type Theme, type ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { truncateToWidth, visibleWidth, type TUI } from "@earendil-works/pi-tui";
|
|
5
5
|
import { isCodexFastEnabled, subscribeCodexFast } from "./codex-fast-state.ts";
|
|
6
|
-
import { formatCwd, formatTime,
|
|
6
|
+
import { formatCwd, formatTime, modelDisplayName, padRight } from "./display.ts";
|
|
7
7
|
import { goalElapsedMilliseconds } from "./goal-state.ts";
|
|
8
8
|
import type { GoalRuntime, GoalState } from "./runtime.ts";
|
|
9
9
|
import { safeTerminalText } from "./safe-terminal-text.ts";
|
|
@@ -14,7 +14,7 @@ const GIT_STATUS_TIMEOUT_MS = 5_000;
|
|
|
14
14
|
const GIT_STATUS_WATCH_DEBOUNCE_MS = 250;
|
|
15
15
|
const GIT_STATUS_WATCH_INTERVAL_MS = 5_000;
|
|
16
16
|
const CODEX_PROVIDER = "openai-codex";
|
|
17
|
-
const colorDirectory = (text: string): string => `\x1B[38;2;240;248;154m${text}\x1B[39m`;
|
|
17
|
+
export const colorDirectory = (text: string): string => `\x1B[38;2;240;248;154m${text}\x1B[39m`;
|
|
18
18
|
|
|
19
19
|
export interface GitFileChanges {
|
|
20
20
|
modified: number;
|
|
@@ -239,13 +239,13 @@ export function contextPercentRemaining(ctx: ExtensionContext): number | null {
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
export function formatContextProgress(tokensUsed: number | null, contextWindow: number, theme: Theme): string {
|
|
242
|
-
if (tokensUsed === null || !Number.isFinite(tokensUsed)) return theme.fg("dim", "—%
|
|
242
|
+
if (tokensUsed === null || !Number.isFinite(tokensUsed)) return theme.fg("dim", "ctx —%");
|
|
243
243
|
const windowSize = Number.isFinite(contextWindow) && contextWindow > 0 ? contextWindow : 128_000;
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
const color: ThemeColor =
|
|
247
|
-
const action =
|
|
248
|
-
return theme.fg(color,
|
|
244
|
+
const used = Math.max(0, Math.min(windowSize, Math.max(0, tokensUsed)));
|
|
245
|
+
const percentUsed = Math.max(0, Math.min(100, Math.round((used / windowSize) * 100)));
|
|
246
|
+
const color: ThemeColor = percentUsed > 80 ? "error" : percentUsed >= 50 ? "warning" : "success";
|
|
247
|
+
const action = percentUsed >= 85 ? " · /compact" : "";
|
|
248
|
+
return theme.fg(color, `ctx ${percentUsed}%${action}`);
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
function sumSessionCost(ctx: ExtensionContext): number {
|
|
@@ -275,30 +275,11 @@ const PROVIDER_LABELS: Readonly<Record<string, string>> = {
|
|
|
275
275
|
openrouter: "OpenRouter",
|
|
276
276
|
};
|
|
277
277
|
|
|
278
|
-
const PROVIDER_WORDS: Readonly<Record<string, string>> = {
|
|
279
|
-
ai: "AI",
|
|
280
|
-
api: "API",
|
|
281
|
-
deepseek: "DeepSeek",
|
|
282
|
-
github: "GitHub",
|
|
283
|
-
llm: "LLM",
|
|
284
|
-
openai: "OpenAI",
|
|
285
|
-
openrouter: "OpenRouter",
|
|
286
|
-
};
|
|
287
|
-
|
|
288
278
|
function formatProviderName(provider: string): string {
|
|
289
279
|
const normalized = safeTerminalText(provider).replaceAll("\n", "").trim();
|
|
290
280
|
const known = PROVIDER_LABELS[normalized.toLowerCase()];
|
|
291
|
-
if (known) return known;
|
|
292
|
-
return normalized
|
|
293
|
-
.split(/[-_]+/u)
|
|
294
|
-
.filter(Boolean)
|
|
295
|
-
.map((word) => PROVIDER_WORDS[word.toLowerCase()] ?? `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
|
|
296
|
-
.join(" ") || "Unknown provider";
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function modelDisplayName(model: NonNullable<ExtensionContext["model"]>): string {
|
|
300
|
-
const name = safeTerminalText(model.name ?? "").replaceAll("\n", "").trim();
|
|
301
|
-
return name || safeTerminalText(model.id).replaceAll("\n", "").trim() || "Unknown model";
|
|
281
|
+
if (known) return known.toLowerCase();
|
|
282
|
+
return normalized.toLowerCase().split(/[-_]+/u).filter(Boolean).join(" ") || "unknown provider";
|
|
302
283
|
}
|
|
303
284
|
|
|
304
285
|
export function formatModel(
|
|
@@ -307,11 +288,10 @@ export function formatModel(
|
|
|
307
288
|
includeProvider = true,
|
|
308
289
|
showCodexFast = false,
|
|
309
290
|
): string {
|
|
310
|
-
if (!model) return theme.fg("dim", "
|
|
311
|
-
const
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
: "";
|
|
291
|
+
if (!model) return theme.fg("dim", "no model");
|
|
292
|
+
const displayName = modelDisplayName(model) || "unknown model";
|
|
293
|
+
const name = theme.fg("text", displayName);
|
|
294
|
+
const fast = showCodexFast && model.provider === CODEX_PROVIDER ? theme.fg("text", "fast") : "";
|
|
315
295
|
const provider = includeProvider ? theme.fg("dim", formatProviderName(model.provider)) : "";
|
|
316
296
|
return [name, fast, provider].filter(Boolean).join(" ");
|
|
317
297
|
}
|
package/killeros/shell-ui.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import {
|
|
4
4
|
CustomEditor,
|
|
5
|
+
VERSION as PI_VERSION,
|
|
5
6
|
type ExtensionAPI,
|
|
6
7
|
type ExtensionContext,
|
|
7
8
|
type KeybindingsManager,
|
|
@@ -11,22 +12,18 @@ import {
|
|
|
11
12
|
CURSOR_MARKER,
|
|
12
13
|
stripTerminalSequences,
|
|
13
14
|
truncateToWidth,
|
|
14
|
-
visibleWidth,
|
|
15
15
|
wrapTextWithAnsi,
|
|
16
16
|
type EditorTheme,
|
|
17
17
|
type TUI,
|
|
18
18
|
} from "@earendil-works/pi-tui";
|
|
19
|
-
import { formatCwd, padRight } from "./display.ts";
|
|
19
|
+
import { formatCwd, modelDisplayName, padRight } from "./display.ts";
|
|
20
|
+
import { colorDirectory } from "./footer.ts";
|
|
20
21
|
import {
|
|
21
22
|
createSlashCommandResolver,
|
|
22
23
|
findSlashCommandTokens,
|
|
23
24
|
type SlashCommandResolver,
|
|
24
25
|
} from "./commands.ts";
|
|
25
26
|
import { reportError } from "./errors.ts";
|
|
26
|
-
import { formatModel } from "./footer.ts";
|
|
27
|
-
import { LEVEL_COLORS } from "./variants.ts";
|
|
28
|
-
|
|
29
|
-
const COMPACT_HEADER_MAX_WIDTH = 52;
|
|
30
27
|
|
|
31
28
|
function readPackageVersion(path: string | URL): string | undefined {
|
|
32
29
|
try {
|
|
@@ -46,7 +43,7 @@ const KILLEROS_VERSION = readPackageVersion(new URL("../package.json", import.me
|
|
|
46
43
|
|
|
47
44
|
const STARTUP_TIPS = [
|
|
48
45
|
"Press Shift+Enter to insert a line break without sending.",
|
|
49
|
-
"Run /
|
|
46
|
+
"Run /thinking to tune the model's reasoning depth.",
|
|
50
47
|
"Type / to browse every command available in this session.",
|
|
51
48
|
"Run /notification to enable a terminal bell when work settles.",
|
|
52
49
|
"Run /goal <objective> to keep long-running work moving across turns.",
|
|
@@ -119,10 +116,8 @@ function nextEditorSuggestion(): string {
|
|
|
119
116
|
return editorSuggestionDeck.pop() ?? EDITOR_SUGGESTIONS[0];
|
|
120
117
|
}
|
|
121
118
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return `${theme.fg("dim", "│")} ${padRight(content, width - 4)} ${theme.fg("dim", "│")}`;
|
|
125
|
-
}
|
|
119
|
+
const MASTHEAD_MARK = ["██████", "▓▓▓▓▓▓", "░░░░░░"];
|
|
120
|
+
const MASTHEAD_GAP = " ";
|
|
126
121
|
|
|
127
122
|
class PiStartupHeader {
|
|
128
123
|
private readonly pi: ExtensionAPI;
|
|
@@ -145,10 +140,8 @@ class PiStartupHeader {
|
|
|
145
140
|
}
|
|
146
141
|
|
|
147
142
|
private tipLines(width: number, theme: Theme): string[] {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
return wrapTextWithAnsi(text, width - indent.length)
|
|
151
|
-
.map((line) => padRight(`${indent}${line}`, width));
|
|
143
|
+
const text = theme.italic(`${theme.fg("text", theme.bold("Tip:"))}${theme.fg("dim", ` ${this.tip}`)}`);
|
|
144
|
+
return wrapTextWithAnsi(text, width);
|
|
152
145
|
}
|
|
153
146
|
|
|
154
147
|
render(width: number): string[] {
|
|
@@ -156,34 +149,18 @@ class PiStartupHeader {
|
|
|
156
149
|
const theme = this.ctx.ui.theme;
|
|
157
150
|
if (width < 28) return [truncateToWidth(theme.fg("text", theme.bold("KillerOS")), width, "")];
|
|
158
151
|
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const version = KILLEROS_VERSION ? theme.fg("dim", ` (v${KILLEROS_VERSION})`) : "";
|
|
162
|
-
const identity = `${theme.fg("dim", "›")} ${theme.fg("text", theme.bold("KillerOS"))}${version}`;
|
|
152
|
+
const textWidth = Math.max(0, width - MASTHEAD_MARK[0].length - MASTHEAD_GAP.length);
|
|
153
|
+
const identity = `${theme.fg("text", "Pi")}${theme.fg("dim", ` ${PI_VERSION} | `)}${theme.fg("text", "KillerOS")}${KILLEROS_VERSION ? theme.fg("dim", ` ${KILLEROS_VERSION}`) : ""}`;
|
|
163
154
|
const thinkingLevel = this.pi.getThinkingLevel();
|
|
164
|
-
const reasoning = this.ctx.model?.reasoning === false
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const agent = `${formatModel(this.ctx.model, theme)}${theme.fg("dim", " · ")}${reasoning}`;
|
|
155
|
+
const reasoning = this.ctx.model?.reasoning === false ? "no reasoning" : thinkingLevel;
|
|
156
|
+
const modelName = this.ctx.model ? modelDisplayName(this.ctx.model) || "unknown model" : "no model";
|
|
157
|
+
const agent = `${theme.fg("dim", "model: ")}${theme.fg("text", `${modelName} ${reasoning}`)}`;
|
|
168
158
|
const directory = formatCwd(this.ctx.cwd);
|
|
169
|
-
const repository = this.branch
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const agentCommand = `${truncateToWidth(agent, agentWidth, "…")} ${modelCommand}`;
|
|
175
|
-
const border = (left: string, right: string): string => theme.fg("dim", `${left}${"─".repeat(panelWidth - 2)}${right}`);
|
|
176
|
-
const lines = [
|
|
177
|
-
border("╭", "╮"),
|
|
178
|
-
compactBoxLine(identity, panelWidth, theme),
|
|
179
|
-
compactBoxLine("", panelWidth, theme),
|
|
180
|
-
compactBoxLine(agentCommand, panelWidth, theme),
|
|
181
|
-
compactBoxLine(repository, panelWidth, theme),
|
|
182
|
-
border("╰", "╯"),
|
|
183
|
-
" ".repeat(panelWidth),
|
|
184
|
-
...this.tipLines(panelWidth, theme),
|
|
185
|
-
];
|
|
186
|
-
return lines;
|
|
159
|
+
const repository = `${theme.fg("dim", "directory: ")}${theme.fg("text", directory)}${this.branch ? ` ${colorDirectory(this.branch)}` : ""}`;
|
|
160
|
+
const rows = [identity, agent, repository].map((text, index) =>
|
|
161
|
+
`${theme.fg("accent", MASTHEAD_MARK[index] ?? "")}${MASTHEAD_GAP}${truncateToWidth(text, textWidth, "…")}`,
|
|
162
|
+
);
|
|
163
|
+
return [...rows, "", ...this.tipLines(width, theme)];
|
|
187
164
|
}
|
|
188
165
|
|
|
189
166
|
invalidate(): void {}
|
|
@@ -353,9 +330,7 @@ class PiCodeEditor extends CustomEditor {
|
|
|
353
330
|
|
|
354
331
|
for (let index = 1; index < bottomBorderIndex; index += 1) {
|
|
355
332
|
const isPromptLine = index === 1 && !isScrolledHeader;
|
|
356
|
-
const prefix = isPromptLine
|
|
357
|
-
? this.runtimeTheme.fg(this.focused ? "accent" : "dim", "❯\u00A0")
|
|
358
|
-
: " ";
|
|
333
|
+
const prefix = isPromptLine ? this.runtimeTheme.fg("text", "❯\u00A0") : " ";
|
|
359
334
|
let content = lines[index] ?? "";
|
|
360
335
|
if (isPromptLine && this.getText() === "") {
|
|
361
336
|
const first = this.suggestion.slice(0, 1);
|
|
@@ -387,10 +362,10 @@ class PiCodeEditor extends CustomEditor {
|
|
|
387
362
|
}
|
|
388
363
|
|
|
389
364
|
const ACTIVITY_FRAMES = [
|
|
390
|
-
"
|
|
391
|
-
"
|
|
365
|
+
"⠋", "⠙", "⠹", "⠸", "⠼",
|
|
366
|
+
"⠴", "⠦", "⠧", "⠇", "⠏",
|
|
392
367
|
] as const;
|
|
393
|
-
const ACTIVITY_FRAME_INTERVAL_MS =
|
|
368
|
+
const ACTIVITY_FRAME_INTERVAL_MS = 80;
|
|
394
369
|
|
|
395
370
|
let killerosEditorFactory: ReturnType<ExtensionContext["ui"]["getEditorComponent"]>;
|
|
396
371
|
|
package/killeros/variants.ts
CHANGED
|
@@ -1,28 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SelectList, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import { safeTerminalText } from "./safe-terminal-text.ts";
|
|
1
|
+
import type { ExtensionAPI, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
4
2
|
|
|
5
3
|
export type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
6
4
|
|
|
7
|
-
const ALL_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const satisfies readonly ThinkingLevel[];
|
|
8
|
-
const LEVEL_LABELS: Readonly<Record<ThinkingLevel, string>> = {
|
|
9
|
-
off: "Off",
|
|
10
|
-
minimal: "Minimal",
|
|
11
|
-
low: "Low",
|
|
12
|
-
medium: "Medium",
|
|
13
|
-
high: "High",
|
|
14
|
-
xhigh: "Extra High",
|
|
15
|
-
max: "Maximum",
|
|
16
|
-
};
|
|
17
|
-
const LEVEL_DESCRIPTIONS: Readonly<Record<ThinkingLevel, string>> = {
|
|
18
|
-
off: "No extended reasoning",
|
|
19
|
-
minimal: "Brief reasoning",
|
|
20
|
-
low: "Light reasoning",
|
|
21
|
-
medium: "Balanced reasoning",
|
|
22
|
-
high: "Deep reasoning",
|
|
23
|
-
xhigh: "Extensive reasoning",
|
|
24
|
-
max: "Maximum supported reasoning",
|
|
25
|
-
};
|
|
26
5
|
export const LEVEL_COLORS: Readonly<Record<ThinkingLevel, ThemeColor>> = {
|
|
27
6
|
off: "thinkingOff",
|
|
28
7
|
minimal: "thinkingMinimal",
|
|
@@ -32,152 +11,3 @@ export const LEVEL_COLORS: Readonly<Record<ThinkingLevel, ThemeColor>> = {
|
|
|
32
11
|
xhigh: "thinkingXhigh",
|
|
33
12
|
max: "thinkingMax",
|
|
34
13
|
};
|
|
35
|
-
const LEVEL_ALIASES: Readonly<Record<string, ThinkingLevel>> = {
|
|
36
|
-
quick: "minimal",
|
|
37
|
-
fast: "minimal",
|
|
38
|
-
light: "low",
|
|
39
|
-
balanced: "medium",
|
|
40
|
-
deep: "high",
|
|
41
|
-
maximum: "max",
|
|
42
|
-
none: "off",
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
function isThinkingLevel(value: string): value is ThinkingLevel {
|
|
46
|
-
return ALL_LEVELS.some((level) => level === value);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function resolveThinkingLevel(input: string): ThinkingLevel | undefined {
|
|
50
|
-
const normalized = input.trim().toLowerCase();
|
|
51
|
-
return isThinkingLevel(normalized) ? normalized : LEVEL_ALIASES[normalized];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function supportedLevels(model: ExtensionContext["model"]): ThinkingLevel[] {
|
|
55
|
-
if (!model?.reasoning) return ["off"];
|
|
56
|
-
return ALL_LEVELS.filter((level) => {
|
|
57
|
-
const mapped = model.thinkingLevelMap?.[level];
|
|
58
|
-
if (mapped === null) return false;
|
|
59
|
-
return level !== "xhigh" && level !== "max" || mapped !== undefined;
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function modelLabel(model: ExtensionContext["model"]): string {
|
|
64
|
-
return model ? safeTerminalText(`${model.provider}/${model.id}`).replaceAll("\n", "") : "unknown model";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function registerVariants(pi: ExtensionAPI): void {
|
|
68
|
-
const setLevel = (ctx: ExtensionContext, level: ThinkingLevel): void => {
|
|
69
|
-
const supported = supportedLevels(ctx.model);
|
|
70
|
-
if (!supported.includes(level)) {
|
|
71
|
-
ctx.ui.notify(`${LEVEL_LABELS[level]} is not supported by ${modelLabel(ctx.model)}. Supported: ${supported.join(", ")}`, "warning");
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
pi.setThinkingLevel(level);
|
|
75
|
-
ctx.ui.notify(`Thinking: ${LEVEL_LABELS[level]}`, "info");
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
pi.registerCommand("variants", {
|
|
79
|
-
description: "Set reasoning level: off, minimal, low, medium, high, xhigh, or max",
|
|
80
|
-
handler: async (args, ctx) => {
|
|
81
|
-
if (args.trim()) {
|
|
82
|
-
const level = resolveThinkingLevel(args);
|
|
83
|
-
if (!level) {
|
|
84
|
-
ctx.ui.notify(`Unknown reasoning level "${safeTerminalText(args.trim()).replaceAll("\n", "")}". Use: ${ALL_LEVELS.join(", ")}`, "error");
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
setLevel(ctx, level);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (ctx.mode !== "tui") {
|
|
91
|
-
ctx.ui.notify("Use /variants <level> outside TUI mode", "error");
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const supported = supportedLevels(ctx.model);
|
|
96
|
-
if (supported.length === 1) {
|
|
97
|
-
ctx.ui.notify(`${modelLabel(ctx.model)} does not support extended reasoning`, "info");
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const current = pi.getThinkingLevel();
|
|
101
|
-
const items = supported.map((level) => ({
|
|
102
|
-
value: level,
|
|
103
|
-
label: level === current ? `${LEVEL_LABELS[level]} ← current` : LEVEL_LABELS[level],
|
|
104
|
-
description: LEVEL_DESCRIPTIONS[level],
|
|
105
|
-
}));
|
|
106
|
-
const selected = await ctx.ui.custom<ThinkingLevel | null>((tui, theme, keybindings, done) => {
|
|
107
|
-
const listTheme = {
|
|
108
|
-
selectedPrefix: (text: string) => theme.fg("accent", text),
|
|
109
|
-
selectedText: (text: string) => theme.fg("accent", text),
|
|
110
|
-
description: (text: string) => theme.fg("muted", text),
|
|
111
|
-
scrollInfo: (text: string) => theme.fg("dim", text),
|
|
112
|
-
noMatch: (text: string) => theme.fg("warning", text),
|
|
113
|
-
};
|
|
114
|
-
let selectList: SelectList | undefined;
|
|
115
|
-
let visibleOptionRows = 0;
|
|
116
|
-
|
|
117
|
-
const chromeFor = (rowBudget: number): "full" | "compact" | "none" => (
|
|
118
|
-
rowBudget >= 8 ? "full" : rowBudget >= 4 ? "compact" : "none"
|
|
119
|
-
);
|
|
120
|
-
const visibleRowsFor = (rowBudget: number): number => {
|
|
121
|
-
const chrome = chromeFor(rowBudget);
|
|
122
|
-
const chromeRows = chrome === "full" ? 5 : chrome === "compact" ? 2 : 0;
|
|
123
|
-
const availableListRows = Math.max(1, rowBudget - chromeRows);
|
|
124
|
-
return availableListRows >= items.length
|
|
125
|
-
? items.length
|
|
126
|
-
: Math.max(1, availableListRows - 1);
|
|
127
|
-
};
|
|
128
|
-
const ensureSelectList = (nextVisibleOptionRows: number): SelectList => {
|
|
129
|
-
if (selectList && visibleOptionRows === nextVisibleOptionRows) return selectList;
|
|
130
|
-
const selectedValue = selectList?.getSelectedItem()?.value ?? current;
|
|
131
|
-
const nextSelectList = new SelectList(items, nextVisibleOptionRows, listTheme);
|
|
132
|
-
const selectedIndex = items.findIndex((item) => item.value === selectedValue);
|
|
133
|
-
nextSelectList.setSelectedIndex(Math.max(0, selectedIndex));
|
|
134
|
-
nextSelectList.onSelect = (item) => done(isThinkingLevel(item.value) ? item.value : null);
|
|
135
|
-
nextSelectList.onCancel = () => done(null);
|
|
136
|
-
selectList = nextSelectList;
|
|
137
|
-
visibleOptionRows = nextVisibleOptionRows;
|
|
138
|
-
return nextSelectList;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const border = new DynamicBorder((text: string) => theme.fg("accent", text));
|
|
142
|
-
const title = ` ${theme.fg("accent", theme.bold("Thinking variants"))}`;
|
|
143
|
-
const model = ` ${theme.fg("dim", `Model: ${modelLabel(ctx.model)}`)}`;
|
|
144
|
-
const keyHint = (keybinding: Parameters<typeof keybindings.getKeys>[0], description: string): string => {
|
|
145
|
-
const keyText = keybindings.getKeys(keybinding)
|
|
146
|
-
.join("/")
|
|
147
|
-
.split("/")
|
|
148
|
-
.map((key) => key.split("+").map((part) => process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part).join("+"))
|
|
149
|
-
.join("/");
|
|
150
|
-
return theme.fg("dim", keyText) + theme.fg("muted", ` ${description}`);
|
|
151
|
-
};
|
|
152
|
-
const controls = ` ${theme.fg("dim", `${keyHint("tui.select.up", "up")} • ${keyHint("tui.select.down", "down")} • ${keyHint("tui.select.confirm", "select")} • ${keyHint("tui.select.cancel", "cancel")}`)}`;
|
|
153
|
-
|
|
154
|
-
const render = (width: number): string[] => {
|
|
155
|
-
const rowBudget = Math.max(0, tui.terminal.rows);
|
|
156
|
-
if (width <= 0 || rowBudget === 0) return [];
|
|
157
|
-
const chrome = chromeFor(rowBudget);
|
|
158
|
-
const list = ensureSelectList(visibleRowsFor(rowBudget));
|
|
159
|
-
const lines: string[] = [];
|
|
160
|
-
|
|
161
|
-
if (chrome === "full") lines.push(...border.render(width));
|
|
162
|
-
if (chrome !== "none") lines.push(title);
|
|
163
|
-
if (chrome === "full") lines.push(model);
|
|
164
|
-
lines.push(...list.render(width));
|
|
165
|
-
if (chrome !== "none") lines.push(controls);
|
|
166
|
-
if (chrome === "full") lines.push(...border.render(width));
|
|
167
|
-
|
|
168
|
-
return lines.slice(0, rowBudget).map((line) => truncateToWidth(line, width, ""));
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
render,
|
|
173
|
-
invalidate: () => selectList?.invalidate(),
|
|
174
|
-
handleInput: (data) => {
|
|
175
|
-
ensureSelectList(visibleRowsFor(Math.max(1, tui.terminal.rows))).handleInput(data);
|
|
176
|
-
tui.requestRender();
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
});
|
|
180
|
-
if (selected) setLevel(ctx, selected);
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
}
|
package/killeros/worked-for.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type ChangedFile,
|
|
13
13
|
type CheckAttempt,
|
|
14
14
|
} from "./change-receipt.ts";
|
|
15
|
-
import { formatTokens } from "./display.ts";
|
|
15
|
+
import { formatTokens, modelDisplayName } from "./display.ts";
|
|
16
16
|
import { errorMessage } from "./errors.ts";
|
|
17
17
|
import { safeTerminalText } from "./safe-terminal-text.ts";
|
|
18
18
|
|
|
@@ -47,15 +47,16 @@ export interface WorkedForEntryDataV4 {
|
|
|
47
47
|
changes: ChangeSummary;
|
|
48
48
|
checks: CheckAttempt[];
|
|
49
49
|
omittedChecks: { passed: number; failed: number };
|
|
50
|
+
model?: string;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
type WorkedForEntryData = WorkedForEntryDataV1 | WorkedForEntryDataV2 | WorkedForEntryDataV3 | WorkedForEntryDataV4;
|
|
53
54
|
|
|
54
55
|
const OUTCOMES = {
|
|
55
|
-
done: {
|
|
56
|
-
stopped: {
|
|
57
|
-
failed: {
|
|
58
|
-
} as const satisfies Record<WorkedForOutcome, {
|
|
56
|
+
done: { label: "✓ Done", color: "success" },
|
|
57
|
+
stopped: { label: "■ Stopped", color: "warning" },
|
|
58
|
+
failed: { label: "× Failed", color: "error" },
|
|
59
|
+
} as const satisfies Record<WorkedForOutcome, { label: string; color: string }>;
|
|
59
60
|
|
|
60
61
|
function isWorkedForOutcome(value: unknown): value is WorkedForOutcome {
|
|
61
62
|
return value === "done" || value === "stopped" || value === "failed";
|
|
@@ -121,6 +122,12 @@ function parseChanges(value: unknown): ChangeSummary | undefined {
|
|
|
121
122
|
};
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
function parseModelName(value: unknown): string | undefined {
|
|
126
|
+
if (typeof value !== "string" || value.length > 200) return undefined;
|
|
127
|
+
const sanitized = safeTerminalText(value).replaceAll("\n", "").trim();
|
|
128
|
+
return sanitized || undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
124
131
|
function parseV4(data: Record<string, unknown>): WorkedForEntryDataV4 | undefined {
|
|
125
132
|
try {
|
|
126
133
|
if (Buffer.byteLength(JSON.stringify(data), "utf8") > MAX_PAYLOAD_BYTES) return undefined;
|
|
@@ -139,6 +146,7 @@ function parseV4(data: Record<string, unknown>): WorkedForEntryDataV4 | undefine
|
|
|
139
146
|
if (!label) return undefined;
|
|
140
147
|
checks.push({ label, outcome: check.outcome });
|
|
141
148
|
}
|
|
149
|
+
const model = parseModelName(data.model);
|
|
142
150
|
return {
|
|
143
151
|
version: 4,
|
|
144
152
|
milliseconds: data.milliseconds,
|
|
@@ -147,6 +155,7 @@ function parseV4(data: Record<string, unknown>): WorkedForEntryDataV4 | undefine
|
|
|
147
155
|
changes,
|
|
148
156
|
checks,
|
|
149
157
|
omittedChecks: { passed: data.omittedChecks.passed, failed: data.omittedChecks.failed },
|
|
158
|
+
...(model ? { model } : {}),
|
|
150
159
|
};
|
|
151
160
|
}
|
|
152
161
|
|
|
@@ -210,50 +219,52 @@ class WorkedForV4Component implements Component {
|
|
|
210
219
|
if (width <= 0) return [];
|
|
211
220
|
const { data, theme } = this;
|
|
212
221
|
const outcome = OUTCOMES[data.outcome];
|
|
222
|
+
const headline = theme.fg(outcome.color, outcome.label);
|
|
223
|
+
const modelSuffix = data.model ? ` · ${data.model}` : "";
|
|
213
224
|
const lines = [
|
|
214
|
-
`${
|
|
225
|
+
`${headline}${theme.fg("dim", ` · ${formatWorkedForDuration(data.milliseconds)} · ↑ ${formatTokens(data.tokens)} tokens${modelSuffix}`)}`,
|
|
215
226
|
];
|
|
216
|
-
if (data.changes.state === "unavailable") lines.push(theme.fg("dim", "
|
|
217
|
-
else if (data.changes.totalFiles === 0) lines.push(theme.fg("dim", "
|
|
227
|
+
if (data.changes.state === "unavailable") lines.push(theme.fg("dim", "Changes unavailable"));
|
|
228
|
+
else if (data.changes.totalFiles === 0) lines.push(theme.fg("dim", "No files changed"));
|
|
218
229
|
else {
|
|
219
230
|
const count = `${data.changes.totalFiles} ${data.changes.totalFiles === 1 ? "file" : "files"}`;
|
|
220
|
-
lines.push(`${theme.fg("accent",
|
|
231
|
+
lines.push(`${theme.fg("accent", `${width < 40 ? count : `Changed ${count}`}`)}${theme.fg("dim", " · ")}${theme.fg("success", `+${data.changes.additions}`)} ${theme.fg("error", `−${data.changes.deletions}`)}`);
|
|
221
232
|
}
|
|
222
233
|
const passed = data.checks.filter((check) => check.outcome === "passed").length + data.omittedChecks.passed;
|
|
223
234
|
const failed = data.checks.filter((check) => check.outcome === "failed").length + data.omittedChecks.failed;
|
|
224
235
|
const totalChecks = passed + failed;
|
|
225
236
|
if (totalChecks === 0) {
|
|
226
|
-
if (data.changes.state === "available" && data.changes.totalFiles > 0) lines.push(theme.fg("warning", "
|
|
237
|
+
if (data.changes.state === "available" && data.changes.totalFiles > 0) lines.push(theme.fg("warning", "No check recorded"));
|
|
227
238
|
} else if (totalChecks === 1) {
|
|
228
239
|
const check = data.checks[0];
|
|
229
|
-
if (check?.outcome === "passed") lines.push(theme.fg("success", `
|
|
230
|
-
else if (check) lines.push(theme.fg("error", `
|
|
240
|
+
if (check?.outcome === "passed") lines.push(theme.fg("success", `Check passed: ${check.label} ✓`));
|
|
241
|
+
else if (check) lines.push(theme.fg("error", `Check failed: ${check.label} ×`));
|
|
231
242
|
} else if (failed === 0) {
|
|
232
|
-
lines.push(theme.fg("success", `
|
|
243
|
+
lines.push(theme.fg("success", `Checks: ${passed} passed`));
|
|
233
244
|
} else {
|
|
234
|
-
lines.push(
|
|
245
|
+
lines.push(`${theme.fg("accent", "Checks:")} ${theme.fg("success", `${passed} passed`)}${theme.fg("dim", " · ")}${theme.fg("error", `${failed} failed`)}`);
|
|
235
246
|
}
|
|
236
247
|
if (this.expanded && data.changes.state === "available") {
|
|
237
248
|
for (const file of data.changes.files) {
|
|
238
249
|
const marker = file.kind === "added" ? "A" : file.kind === "deleted" ? "D" : file.kind === "renamed" ? "R" : "M";
|
|
239
250
|
const label = file.kind === "renamed" ? `${safePath(file.previousPath)} → ${safePath(file.path)}` : safePath(file.path);
|
|
240
|
-
const prefix =
|
|
251
|
+
const prefix = `${marker} `;
|
|
241
252
|
const detail = file.detail ? ` ${file.detail}` : ` +${file.additions} −${file.deletions}`;
|
|
242
|
-
const labelWidth = width - visibleWidth(prefix) - visibleWidth(detail);
|
|
253
|
+
const labelWidth = width - 1 - visibleWidth(prefix) - visibleWidth(detail);
|
|
243
254
|
const fittedLabel = labelWidth > 0 ? truncateToWidth(label, labelWidth, "…") : "";
|
|
244
255
|
const styledDetail = file.detail
|
|
245
256
|
? theme.fg("dim", detail)
|
|
246
257
|
: `${theme.fg("success", ` +${file.additions}`)} ${theme.fg("error", `−${file.deletions}`)}`;
|
|
247
258
|
lines.push(`${theme.fg("accent", `${prefix}${fittedLabel}`)}${styledDetail}`);
|
|
248
259
|
}
|
|
249
|
-
if (data.changes.omittedFiles > 0) lines.push(theme.fg("dim",
|
|
260
|
+
if (data.changes.omittedFiles > 0) lines.push(theme.fg("dim", `… ${data.changes.omittedFiles} more files`));
|
|
250
261
|
}
|
|
251
262
|
if (this.expanded) {
|
|
252
|
-
for (const check of data.checks) lines.push(theme.fg(check.outcome === "passed" ? "success" : "error",
|
|
263
|
+
for (const check of data.checks) lines.push(theme.fg(check.outcome === "passed" ? "success" : "error", `${check.outcome === "passed" ? "✓" : "×"} ${check.label}`));
|
|
253
264
|
const omitted = data.omittedChecks.passed + data.omittedChecks.failed;
|
|
254
|
-
if (omitted > 0) lines.push(theme.fg("dim",
|
|
265
|
+
if (omitted > 0) lines.push(theme.fg("dim", `… ${omitted} more checks`));
|
|
255
266
|
}
|
|
256
|
-
return lines.map((line) => truncateToWidth(line
|
|
267
|
+
return lines.map((line) => truncateToWidth(` ${line}`, width, "…"));
|
|
257
268
|
}
|
|
258
269
|
|
|
259
270
|
invalidate(): void {}
|
|
@@ -272,8 +283,22 @@ type ActiveReceipt = {
|
|
|
272
283
|
collection: Promise<ChangeReceiptCollection>;
|
|
273
284
|
checks: CheckAttempt[];
|
|
274
285
|
omittedChecks: { passed: number; failed: number };
|
|
286
|
+
modelProvider: string | undefined;
|
|
287
|
+
modelId: string | undefined;
|
|
288
|
+
modelMismatch: boolean;
|
|
275
289
|
};
|
|
276
290
|
|
|
291
|
+
function receiptModelName(
|
|
292
|
+
settled: ActiveReceipt,
|
|
293
|
+
model: ExtensionContext["model"],
|
|
294
|
+
): string | undefined {
|
|
295
|
+
if (settled.modelMismatch || settled.modelProvider === undefined || settled.modelId === undefined) return undefined;
|
|
296
|
+
if (!model || model.provider !== settled.modelProvider || model.id !== settled.modelId) return undefined;
|
|
297
|
+
const resolved = modelDisplayName(model);
|
|
298
|
+
if (!resolved || resolved.length > 200) return undefined;
|
|
299
|
+
return resolved;
|
|
300
|
+
}
|
|
301
|
+
|
|
277
302
|
function fitPayload(data: WorkedForEntryDataV4): WorkedForEntryDataV4 {
|
|
278
303
|
if (data.changes.state === "unavailable") return data;
|
|
279
304
|
const changes = { ...data.changes, files: [...data.changes.files] };
|
|
@@ -296,11 +321,12 @@ export function registerWorkedFor(
|
|
|
296
321
|
pi.registerEntryRenderer<WorkedForEntryData>(WORKED_FOR_ENTRY_TYPE, (entry, options, theme) => {
|
|
297
322
|
const data = parseWorkedForEntryData(entry.data);
|
|
298
323
|
if (!data) return undefined;
|
|
299
|
-
if (data.version === 1) return new Text(theme.fg("dim", `✻ Worked for ${formatWorkedForDuration(data.milliseconds)}`),
|
|
324
|
+
if (data.version === 1) return new Text(theme.fg("dim", `✻ Worked for ${formatWorkedForDuration(data.milliseconds)}`), 1, 0);
|
|
300
325
|
if (data.version === 4) return new WorkedForV4Component(data, options.expanded, theme);
|
|
301
326
|
const outcome = OUTCOMES[data.outcome];
|
|
302
327
|
const tokens = data.version === 3 ? ` · ↑ ${formatTokens(data.tokens)} tokens` : "";
|
|
303
|
-
|
|
328
|
+
const headline = theme.fg(outcome.color, outcome.label);
|
|
329
|
+
return new Text(`${headline}${theme.fg("dim", ` · ${formatWorkedForDuration(data.milliseconds)}${tokens}`)}`, 1, 0);
|
|
304
330
|
});
|
|
305
331
|
|
|
306
332
|
pi.on("session_start", async () => {
|
|
@@ -319,12 +345,35 @@ export function registerWorkedFor(
|
|
|
319
345
|
collection: collect(ctx.cwd),
|
|
320
346
|
checks: [],
|
|
321
347
|
omittedChecks: { passed: 0, failed: 0 },
|
|
348
|
+
modelProvider: undefined,
|
|
349
|
+
modelId: undefined,
|
|
350
|
+
modelMismatch: false,
|
|
322
351
|
};
|
|
323
352
|
active = state;
|
|
324
353
|
const collection = await state.collection;
|
|
325
354
|
if (active !== state) await collection.dispose();
|
|
326
355
|
});
|
|
327
356
|
|
|
357
|
+
pi.on("message_end", (event, ctx) => {
|
|
358
|
+
if (ctx.mode !== "tui" || !active) return;
|
|
359
|
+
if (event.message.role !== "assistant") return;
|
|
360
|
+
const provider: unknown = event.message.provider;
|
|
361
|
+
const modelId: unknown = event.message.model;
|
|
362
|
+
if (typeof provider !== "string" || typeof modelId !== "string") {
|
|
363
|
+
active.modelMismatch = true;
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (active.modelProvider === undefined || active.modelId === undefined) {
|
|
367
|
+
active.modelProvider = provider;
|
|
368
|
+
active.modelId = modelId;
|
|
369
|
+
} else if (active.modelProvider !== provider || active.modelId !== modelId) {
|
|
370
|
+
active.modelMismatch = true;
|
|
371
|
+
}
|
|
372
|
+
if (event.message.responseModel !== undefined && event.message.responseModel !== modelId) {
|
|
373
|
+
active.modelMismatch = true;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
328
377
|
pi.on("tool_result", (event: ToolResultEvent, ctx) => {
|
|
329
378
|
if (ctx.mode !== "tui" || !active || event.toolName !== "bash" && event.toolName !== "powershell") return;
|
|
330
379
|
const check = recognizedCheck(event.input.command, event.isError);
|
|
@@ -347,6 +396,7 @@ export function registerWorkedFor(
|
|
|
347
396
|
if (ctx.mode !== "tui" || !active) return;
|
|
348
397
|
const settled = active;
|
|
349
398
|
active = undefined;
|
|
399
|
+
const model = receiptModelName(settled, ctx.model);
|
|
350
400
|
const changes = await (await settled.collection).finish();
|
|
351
401
|
if (changes.state === "unavailable" && changes.reason !== "not-git" && changes.reason !== "timeout" && !collectionNoticeShown) {
|
|
352
402
|
collectionNoticeShown = true;
|
|
@@ -361,6 +411,7 @@ export function registerWorkedFor(
|
|
|
361
411
|
changes,
|
|
362
412
|
checks: settled.checks,
|
|
363
413
|
omittedChecks: settled.omittedChecks,
|
|
414
|
+
...(model ? { model } : {}),
|
|
364
415
|
});
|
|
365
416
|
try {
|
|
366
417
|
pi.appendEntry<WorkedForEntryDataV4>(WORKED_FOR_ENTRY_TYPE, data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "killeros",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.25",
|
|
4
4
|
"description": "TUI, goals, and workflow automation for the Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -44,15 +44,16 @@
|
|
|
44
44
|
]
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@earendil-works/pi-ai": ">=0.
|
|
48
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
49
|
-
"@earendil-works/pi-tui": ">=0.
|
|
47
|
+
"@earendil-works/pi-ai": ">=0.85.0 <1",
|
|
48
|
+
"@earendil-works/pi-coding-agent": ">=0.85.0 <1",
|
|
49
|
+
"@earendil-works/pi-tui": ">=0.85.0 <1",
|
|
50
50
|
"typebox": ">=1.1.38 <2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@earendil-works/pi-ai": "0.
|
|
54
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
55
|
-
"@earendil-works/pi-
|
|
53
|
+
"@earendil-works/pi-ai": "0.85.0",
|
|
54
|
+
"@earendil-works/pi-coding-agent": "0.85.0",
|
|
55
|
+
"@earendil-works/pi-server": "0.85.0",
|
|
56
|
+
"@earendil-works/pi-tui": "0.85.0",
|
|
56
57
|
"@types/node": "24.12.4",
|
|
57
58
|
"eslint": "^10.9.1",
|
|
58
59
|
"typebox": "1.3.20",
|