@xynogen/pix-optimizer 1.1.14 → 1.1.16
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/package.json +2 -2
- package/src/caveman.ts +4 -13
- package/src/index.ts +1 -5
- package/src/json.test.ts +1 -3
- package/src/json.ts +5 -18
- package/src/opt.test.ts +1 -5
- package/src/opt.ts +8 -36
- package/src/ponytail.ts +4 -13
- package/src/rtk.test.ts +12 -40
- package/src/rtk.ts +3 -12
- package/src/status.test.ts +4 -20
- package/src/status.ts +3 -16
- package/src/tool-result-filter.test.ts +3 -9
- package/src/tool-result-filter.ts +1 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-optimizer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.16",
|
|
4
4
|
"description": "Performance optimization suite for Pi Coding Agent - caveman mode + RTK tool rewriting + jq/TOON JSON compression + ponytail lazy-dev mode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@xynogen/pix-pretty": "^1.7.9",
|
|
52
|
-
"@xynogen/pix-data": "
|
|
52
|
+
"@xynogen/pix-data": "^0.3.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@earendil-works/pi-coding-agent": "*",
|
package/src/caveman.ts
CHANGED
|
@@ -108,8 +108,7 @@ export function resolveLevel(arg: string): Level | null {
|
|
|
108
108
|
* Help text shown when /caveman is run with no argument.
|
|
109
109
|
*/
|
|
110
110
|
export function buildHelp(current: Level): string {
|
|
111
|
-
const statusLine =
|
|
112
|
-
current === "off" ? "off" : `${STATUS_LABELS[current]} (${current})`;
|
|
111
|
+
const statusLine = current === "off" ? "off" : `${STATUS_LABELS[current]} (${current})`;
|
|
113
112
|
return [
|
|
114
113
|
`Caveman mode: ${statusLine}`,
|
|
115
114
|
"",
|
|
@@ -133,10 +132,7 @@ export function toggleLevel(current: Level): Level {
|
|
|
133
132
|
|
|
134
133
|
// ── Pi extension ────────────────────────────────────────────────────────────
|
|
135
134
|
|
|
136
|
-
export function caveman(
|
|
137
|
-
pi: ExtensionAPI,
|
|
138
|
-
status: OptimizerStatus,
|
|
139
|
-
): OptimizerHandle {
|
|
135
|
+
export function caveman(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
|
|
140
136
|
let level: Level = "off";
|
|
141
137
|
|
|
142
138
|
// -- Status: report into the shared optimizer indicator. --
|
|
@@ -179,10 +175,7 @@ export function caveman(
|
|
|
179
175
|
|
|
180
176
|
// -- Overlay value handler (called by the /optimizer overlay) --
|
|
181
177
|
|
|
182
|
-
async function run(
|
|
183
|
-
value: string,
|
|
184
|
-
ctx: ExtensionCommandContext,
|
|
185
|
-
): Promise<void> {
|
|
178
|
+
async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
186
179
|
const resolved = resolveLevel(value);
|
|
187
180
|
if (resolved === null) return;
|
|
188
181
|
level = resolved;
|
|
@@ -192,9 +185,7 @@ export function caveman(
|
|
|
192
185
|
syncStatus(ctx);
|
|
193
186
|
|
|
194
187
|
ctx.ui.notify(
|
|
195
|
-
level === "off"
|
|
196
|
-
? "Caveman mode off."
|
|
197
|
-
: `Caveman: ${STATUS_LABELS[level]}`,
|
|
188
|
+
level === "off" ? "Caveman mode off." : `Caveman: ${STATUS_LABELS[level]}`,
|
|
198
189
|
"info",
|
|
199
190
|
);
|
|
200
191
|
}
|
package/src/index.ts
CHANGED
|
@@ -19,11 +19,7 @@ import { json } from "./json.ts";
|
|
|
19
19
|
import { registerOptCommand } from "./opt.ts";
|
|
20
20
|
import { ponytail } from "./ponytail.ts";
|
|
21
21
|
import { rtk } from "./rtk.ts";
|
|
22
|
-
import {
|
|
23
|
-
type OptimizerHandle,
|
|
24
|
-
OptimizerStatus,
|
|
25
|
-
type OptimizerTool,
|
|
26
|
-
} from "./status.ts";
|
|
22
|
+
import { type OptimizerHandle, OptimizerStatus, type OptimizerTool } from "./status.ts";
|
|
27
23
|
import { filterModelWarnings } from "./tool-result-filter.ts";
|
|
28
24
|
|
|
29
25
|
export default function optimizer(pi: ExtensionAPI) {
|
package/src/json.test.ts
CHANGED
|
@@ -71,9 +71,7 @@ describe("isUniformObjectArray", () => {
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
it("false for differing keys", () => {
|
|
74
|
-
expect(isUniformObjectArray([{ id: 1 }, { id: 2, extra: true }])).toBe(
|
|
75
|
-
false,
|
|
76
|
-
);
|
|
74
|
+
expect(isUniformObjectArray([{ id: 1 }, { id: 2, extra: true }])).toBe(false);
|
|
77
75
|
});
|
|
78
76
|
|
|
79
77
|
it("false when a value is itself an object", () => {
|
package/src/json.ts
CHANGED
|
@@ -182,8 +182,7 @@ export function isUniformObjectArray(arr: unknown[]): boolean {
|
|
|
182
182
|
for (let i = 0; i < keys.length; i++) {
|
|
183
183
|
if (elKeys[i] !== keys[i]) return false;
|
|
184
184
|
// values must be primitive for a clean tabular row
|
|
185
|
-
if (isObjectLike((el as Record<string, unknown>)[keys[i] as string]))
|
|
186
|
-
return false;
|
|
185
|
+
if (isObjectLike((el as Record<string, unknown>)[keys[i] as string])) return false;
|
|
187
186
|
}
|
|
188
187
|
return true;
|
|
189
188
|
});
|
|
@@ -209,21 +208,14 @@ export function objectDepth(value: unknown): number {
|
|
|
209
208
|
|
|
210
209
|
// ── Pi extension ──────────────────────────────────────────────────────────────
|
|
211
210
|
|
|
212
|
-
export function json(
|
|
213
|
-
pi: ExtensionAPI,
|
|
214
|
-
status: OptimizerStatus,
|
|
215
|
-
): OptimizerHandle {
|
|
211
|
+
export function json(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
|
|
216
212
|
let enabled = true;
|
|
217
213
|
let jqAvailable: boolean | null = null;
|
|
218
214
|
let toonAvailable: boolean | null = null;
|
|
219
215
|
|
|
220
216
|
// Report into the shared optimizer indicator.
|
|
221
217
|
function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
|
|
222
|
-
status.set(
|
|
223
|
-
"toon",
|
|
224
|
-
enabled && jqAvailable !== false && toonAvailable !== false,
|
|
225
|
-
ctx,
|
|
226
|
-
);
|
|
218
|
+
status.set("toon", enabled && jqAvailable !== false && toonAvailable !== false, ctx);
|
|
227
219
|
}
|
|
228
220
|
|
|
229
221
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -251,9 +243,7 @@ export function json(
|
|
|
251
243
|
if (jqAvailable === null || toonAvailable === null) {
|
|
252
244
|
const [jqRes, toonRes] = await Promise.all([
|
|
253
245
|
pi.exec("which", ["jq"], { timeout: 1000 }).catch(() => ({ code: 1 })),
|
|
254
|
-
pi
|
|
255
|
-
.exec("which", ["toon"], { timeout: 1000 })
|
|
256
|
-
.catch(() => ({ code: 1 })),
|
|
246
|
+
pi.exec("which", ["toon"], { timeout: 1000 }).catch(() => ({ code: 1 })),
|
|
257
247
|
]);
|
|
258
248
|
jqAvailable = jqRes.code === 0;
|
|
259
249
|
toonAvailable = toonRes.code === 0;
|
|
@@ -277,10 +267,7 @@ export function json(
|
|
|
277
267
|
|
|
278
268
|
// -- Overlay value handler (called by the /optimizer overlay) --
|
|
279
269
|
|
|
280
|
-
async function run(
|
|
281
|
-
value: string,
|
|
282
|
-
ctx: ExtensionCommandContext,
|
|
283
|
-
): Promise<void> {
|
|
270
|
+
async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
284
271
|
enabled = value === "on";
|
|
285
272
|
saveOptValue("toon", enabled ? "on" : "off");
|
|
286
273
|
|
package/src/opt.test.ts
CHANGED
|
@@ -4,11 +4,7 @@ import type { OptimizerHandle, OptimizerTool } from "./status.ts";
|
|
|
4
4
|
|
|
5
5
|
/** Build a handle set with fixed current values + value lists. */
|
|
6
6
|
function fakeHandles(): Record<OptimizerTool, OptimizerHandle> {
|
|
7
|
-
const mk = (
|
|
8
|
-
name: OptimizerTool,
|
|
9
|
-
current: string,
|
|
10
|
-
values: string[],
|
|
11
|
-
): OptimizerHandle => ({
|
|
7
|
+
const mk = (name: OptimizerTool, current: string, values: string[]): OptimizerHandle => ({
|
|
12
8
|
name,
|
|
13
9
|
help: `${name} — ${name} help`,
|
|
14
10
|
values,
|
package/src/opt.ts
CHANGED
|
@@ -16,11 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
import { frameLines } from "@xynogen/pix-pretty/modal-frame";
|
|
19
|
-
import type {
|
|
20
|
-
OptimizerHandle,
|
|
21
|
-
OptimizerStatus,
|
|
22
|
-
OptimizerTool,
|
|
23
|
-
} from "./status.ts";
|
|
19
|
+
import type { OptimizerHandle, OptimizerStatus, OptimizerTool } from "./status.ts";
|
|
24
20
|
import { toolIcon } from "./status.ts";
|
|
25
21
|
|
|
26
22
|
/** Min/max content width for the overlay box (excludes 4 cols of chrome). */
|
|
@@ -28,12 +24,7 @@ const MIN_CONTENT = 28;
|
|
|
28
24
|
const MAX_CONTENT = 60;
|
|
29
25
|
|
|
30
26
|
/** Fixed render order — matches the status-bar cell. */
|
|
31
|
-
const TOOL_ORDER: readonly OptimizerTool[] = [
|
|
32
|
-
"caveman",
|
|
33
|
-
"rtk",
|
|
34
|
-
"toon",
|
|
35
|
-
"ponytail",
|
|
36
|
-
];
|
|
27
|
+
const TOOL_ORDER: readonly OptimizerTool[] = ["caveman", "rtk", "toon", "ponytail"];
|
|
37
28
|
|
|
38
29
|
/** Strip the leading "name [args] — " prefix from a handle's help string. */
|
|
39
30
|
function helpSummary(help: string): string {
|
|
@@ -64,9 +55,7 @@ export function levelBar(current: string, values: readonly string[]): string {
|
|
|
64
55
|
}
|
|
65
56
|
|
|
66
57
|
/** Plain-text status fallback when there's no custom-UI host (headless/tests). */
|
|
67
|
-
export function buildOptHelp(
|
|
68
|
-
handles: Record<OptimizerTool, OptimizerHandle>,
|
|
69
|
-
): string {
|
|
58
|
+
export function buildOptHelp(handles: Record<OptimizerTool, OptimizerHandle>): string {
|
|
70
59
|
const lines = TOOL_ORDER.map(
|
|
71
60
|
(n) => ` ${n}: ${handles[n].current()} — ${helpSummary(handles[n].help)}`,
|
|
72
61
|
);
|
|
@@ -140,8 +129,7 @@ export function registerOptCommand(
|
|
|
140
129
|
};
|
|
141
130
|
|
|
142
131
|
const move = (direction: -1 | 1) => {
|
|
143
|
-
selected =
|
|
144
|
-
(selected + direction + TOOL_ORDER.length) % TOOL_ORDER.length;
|
|
132
|
+
selected = (selected + direction + TOOL_ORDER.length) % TOOL_ORDER.length;
|
|
145
133
|
};
|
|
146
134
|
|
|
147
135
|
return {
|
|
@@ -156,14 +144,8 @@ export function registerOptCommand(
|
|
|
156
144
|
tool.padEnd(nameWidth),
|
|
157
145
|
);
|
|
158
146
|
const cur = handles[tool].current();
|
|
159
|
-
const value = theme.fg(
|
|
160
|
-
|
|
161
|
-
cur.padEnd(valueWidth),
|
|
162
|
-
);
|
|
163
|
-
const bar = theme.fg(
|
|
164
|
-
on ? "accent" : "dim",
|
|
165
|
-
levelBar(cur, handles[tool].values),
|
|
166
|
-
);
|
|
147
|
+
const value = theme.fg(on ? "success" : "dim", cur.padEnd(valueWidth));
|
|
148
|
+
const bar = theme.fg(on ? "accent" : "dim", levelBar(cur, handles[tool].values));
|
|
167
149
|
return `${cursor} ${glyph} ${name} ${value} ${bar}`;
|
|
168
150
|
});
|
|
169
151
|
const lines = [
|
|
@@ -171,12 +153,7 @@ export function registerOptCommand(
|
|
|
171
153
|
"",
|
|
172
154
|
...rows,
|
|
173
155
|
"",
|
|
174
|
-
theme.fg(
|
|
175
|
-
"dim",
|
|
176
|
-
helpSummary(
|
|
177
|
-
handles[TOOL_ORDER[selected] as OptimizerTool].help,
|
|
178
|
-
),
|
|
179
|
-
),
|
|
156
|
+
theme.fg("dim", helpSummary(handles[TOOL_ORDER[selected] as OptimizerTool].help)),
|
|
180
157
|
"",
|
|
181
158
|
theme.fg("dim", "←→ change · ↑↓ move · esc close"),
|
|
182
159
|
];
|
|
@@ -192,12 +169,7 @@ export function registerOptCommand(
|
|
|
192
169
|
if (data === "k" || data === "\u001b[A") move(-1);
|
|
193
170
|
else if (data === "j" || data === "\u001b[B") move(1);
|
|
194
171
|
else if (data === "h" || data === "\u001b[D") cycle(-1);
|
|
195
|
-
else if (
|
|
196
|
-
data === "l" ||
|
|
197
|
-
data === "\u001b[C" ||
|
|
198
|
-
data === " " ||
|
|
199
|
-
data === "\r"
|
|
200
|
-
)
|
|
172
|
+
else if (data === "l" || data === "\u001b[C" || data === " " || data === "\r")
|
|
201
173
|
cycle(1);
|
|
202
174
|
else if (data === "\u001b" || data === "q") {
|
|
203
175
|
done(null);
|
package/src/ponytail.ts
CHANGED
|
@@ -113,8 +113,7 @@ export function resolveLevel(arg: string): Level | null {
|
|
|
113
113
|
* Help text shown when /opt ponytail is run with no argument.
|
|
114
114
|
*/
|
|
115
115
|
export function buildHelp(current: Level): string {
|
|
116
|
-
const statusLine =
|
|
117
|
-
current === "off" ? "off" : `${STATUS_LABELS[current]} (${current})`;
|
|
116
|
+
const statusLine = current === "off" ? "off" : `${STATUS_LABELS[current]} (${current})`;
|
|
118
117
|
return [
|
|
119
118
|
`Ponytail mode: ${statusLine}`,
|
|
120
119
|
"",
|
|
@@ -137,10 +136,7 @@ export function toggleLevel(current: Level): Level {
|
|
|
137
136
|
|
|
138
137
|
// ── Pi extension ────────────────────────────────────────────────────────────
|
|
139
138
|
|
|
140
|
-
export function ponytail(
|
|
141
|
-
pi: ExtensionAPI,
|
|
142
|
-
status: OptimizerStatus,
|
|
143
|
-
): OptimizerHandle {
|
|
139
|
+
export function ponytail(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
|
|
144
140
|
let level: Level = "off";
|
|
145
141
|
|
|
146
142
|
// -- Status: report into the shared optimizer indicator. --
|
|
@@ -182,10 +178,7 @@ export function ponytail(
|
|
|
182
178
|
|
|
183
179
|
// -- Overlay value handler (called by the /optimizer overlay) --
|
|
184
180
|
|
|
185
|
-
async function run(
|
|
186
|
-
value: string,
|
|
187
|
-
ctx: ExtensionCommandContext,
|
|
188
|
-
): Promise<void> {
|
|
181
|
+
async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
189
182
|
const resolved = resolveLevel(value);
|
|
190
183
|
if (resolved === null) return;
|
|
191
184
|
level = resolved;
|
|
@@ -195,9 +188,7 @@ export function ponytail(
|
|
|
195
188
|
syncStatus(ctx);
|
|
196
189
|
|
|
197
190
|
ctx.ui.notify(
|
|
198
|
-
level === "off"
|
|
199
|
-
? "Ponytail mode off."
|
|
200
|
-
: `Ponytail: ${STATUS_LABELS[level]}`,
|
|
191
|
+
level === "off" ? "Ponytail mode off." : `Ponytail: ${STATUS_LABELS[level]}`,
|
|
201
192
|
"info",
|
|
202
193
|
);
|
|
203
194
|
}
|
package/src/rtk.test.ts
CHANGED
|
@@ -19,11 +19,7 @@ describe("splitChain", () => {
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
it("splits on && keeping operator", () => {
|
|
22
|
-
expect(splitChain("git add . && git push")).toEqual([
|
|
23
|
-
"git add . ",
|
|
24
|
-
"&&",
|
|
25
|
-
" git push",
|
|
26
|
-
]);
|
|
22
|
+
expect(splitChain("git add . && git push")).toEqual(["git add . ", "&&", " git push"]);
|
|
27
23
|
});
|
|
28
24
|
|
|
29
25
|
it("splits on ||, ;, |", () => {
|
|
@@ -33,9 +29,7 @@ describe("splitChain", () => {
|
|
|
33
29
|
});
|
|
34
30
|
|
|
35
31
|
it("ignores operators inside double quotes", () => {
|
|
36
|
-
expect(splitChain('git commit -m "a && b"')).toEqual([
|
|
37
|
-
'git commit -m "a && b"',
|
|
38
|
-
]);
|
|
32
|
+
expect(splitChain('git commit -m "a && b"')).toEqual(['git commit -m "a && b"']);
|
|
39
33
|
});
|
|
40
34
|
|
|
41
35
|
it("ignores operators inside single quotes", () => {
|
|
@@ -59,9 +53,7 @@ describe("rewriteChain", () => {
|
|
|
59
53
|
});
|
|
60
54
|
|
|
61
55
|
it("prefixes mixed known commands", () => {
|
|
62
|
-
expect(rewriteChain("cargo build && npm test")).toBe(
|
|
63
|
-
"rtk cargo build && rtk npm test",
|
|
64
|
-
);
|
|
56
|
+
expect(rewriteChain("cargo build && npm test")).toBe("rtk cargo build && rtk npm test");
|
|
65
57
|
});
|
|
66
58
|
|
|
67
59
|
it("leaves unknown commands alone", () => {
|
|
@@ -74,22 +66,16 @@ describe("rewriteChain", () => {
|
|
|
74
66
|
});
|
|
75
67
|
|
|
76
68
|
it("only prefixes known segments in a mixed chain", () => {
|
|
77
|
-
expect(rewriteChain("cd /tmp && git status")).toBe(
|
|
78
|
-
"cd /tmp && rtk git status",
|
|
79
|
-
);
|
|
69
|
+
expect(rewriteChain("cd /tmp && git status")).toBe("cd /tmp && rtk git status");
|
|
80
70
|
});
|
|
81
71
|
|
|
82
72
|
it("does not double-prefix already-rtk commands", () => {
|
|
83
73
|
expect(rewriteChain("rtk git status")).toBe("rtk git status");
|
|
84
|
-
expect(rewriteChain("rtk git add . && git push")).toBe(
|
|
85
|
-
"rtk git add . && rtk git push",
|
|
86
|
-
);
|
|
74
|
+
expect(rewriteChain("rtk git add . && git push")).toBe("rtk git add . && rtk git push");
|
|
87
75
|
});
|
|
88
76
|
|
|
89
77
|
it("does not touch operators inside quotes", () => {
|
|
90
|
-
expect(rewriteChain('git commit -m "a && b"')).toBe(
|
|
91
|
-
'rtk git commit -m "a && b"',
|
|
92
|
-
);
|
|
78
|
+
expect(rewriteChain('git commit -m "a && b"')).toBe('rtk git commit -m "a && b"');
|
|
93
79
|
});
|
|
94
80
|
|
|
95
81
|
it("returns original on unbalanced quotes", () => {
|
|
@@ -106,9 +92,7 @@ describe("rewriteChain", () => {
|
|
|
106
92
|
});
|
|
107
93
|
|
|
108
94
|
it("handles pipes between known commands", () => {
|
|
109
|
-
expect(rewriteChain("git log | grep fix")).toBe(
|
|
110
|
-
"rtk git log | rtk grep fix",
|
|
111
|
-
);
|
|
95
|
+
expect(rewriteChain("git log | grep fix")).toBe("rtk git log | rtk grep fix");
|
|
112
96
|
});
|
|
113
97
|
});
|
|
114
98
|
|
|
@@ -120,9 +104,7 @@ describe("detectSudoSegments", () => {
|
|
|
120
104
|
});
|
|
121
105
|
|
|
122
106
|
it("detects a plain sudo segment", () => {
|
|
123
|
-
expect(detectSudoSegments(["sudo apt-get install foo"])).toEqual([
|
|
124
|
-
"sudo apt-get install foo",
|
|
125
|
-
]);
|
|
107
|
+
expect(detectSudoSegments(["sudo apt-get install foo"])).toEqual(["sudo apt-get install foo"]);
|
|
126
108
|
});
|
|
127
109
|
|
|
128
110
|
it("detects sudo in a chain (operators excluded)", () => {
|
|
@@ -132,10 +114,7 @@ describe("detectSudoSegments", () => {
|
|
|
132
114
|
|
|
133
115
|
it("detects multiple sudo segments", () => {
|
|
134
116
|
const parts = ["sudo rm -rf /tmp ", ";", " sudo reboot"];
|
|
135
|
-
expect(detectSudoSegments(parts)).toEqual([
|
|
136
|
-
"sudo rm -rf /tmp",
|
|
137
|
-
"sudo reboot",
|
|
138
|
-
]);
|
|
117
|
+
expect(detectSudoSegments(parts)).toEqual(["sudo rm -rf /tmp", "sudo reboot"]);
|
|
139
118
|
});
|
|
140
119
|
|
|
141
120
|
it("does not match 'sudoer' or 'pseudo'", () => {
|
|
@@ -170,10 +149,7 @@ describe("buildSudoBlockReason", () => {
|
|
|
170
149
|
});
|
|
171
150
|
|
|
172
151
|
it("lists all blocked commands", () => {
|
|
173
|
-
const reason = buildSudoBlockReason(
|
|
174
|
-
["sudo rm -rf /tmp", "sudo reboot"],
|
|
175
|
-
true,
|
|
176
|
-
);
|
|
152
|
+
const reason = buildSudoBlockReason(["sudo rm -rf /tmp", "sudo reboot"], true);
|
|
177
153
|
expect(reason).toContain("sudo rm -rf /tmp");
|
|
178
154
|
expect(reason).toContain("sudo reboot");
|
|
179
155
|
});
|
|
@@ -254,13 +230,9 @@ describe("applyRtkRewrite (tool_call hook step)", () => {
|
|
|
254
230
|
|
|
255
231
|
it("handles missing / non-string command safely", () => {
|
|
256
232
|
const event: BashCallEvent = { toolName: "bash", input: {} };
|
|
257
|
-
expect(applyRtkRewrite(event, { enabled: true, rtkAvailable: true })).toBe(
|
|
258
|
-
false,
|
|
259
|
-
);
|
|
233
|
+
expect(applyRtkRewrite(event, { enabled: true, rtkAvailable: true })).toBe(false);
|
|
260
234
|
const event2: BashCallEvent = { toolName: "bash", input: { command: 123 } };
|
|
261
|
-
expect(applyRtkRewrite(event2, { enabled: true, rtkAvailable: true })).toBe(
|
|
262
|
-
false,
|
|
263
|
-
);
|
|
235
|
+
expect(applyRtkRewrite(event2, { enabled: true, rtkAvailable: true })).toBe(false);
|
|
264
236
|
});
|
|
265
237
|
|
|
266
238
|
it("leaves command unchanged on unbalanced quotes", () => {
|
package/src/rtk.ts
CHANGED
|
@@ -51,10 +51,7 @@ export function detectSudoSegments(parts: string[]): string[] {
|
|
|
51
51
|
* intercepted. Directs the model to `sudo_run` when available, otherwise
|
|
52
52
|
* explains the restriction clearly.
|
|
53
53
|
*/
|
|
54
|
-
export function buildSudoBlockReason(
|
|
55
|
-
sudoCmds: string[],
|
|
56
|
-
hasSudoRunTool: boolean,
|
|
57
|
-
): string {
|
|
54
|
+
export function buildSudoBlockReason(sudoCmds: string[], hasSudoRunTool: boolean): string {
|
|
58
55
|
const list = sudoCmds.map((c) => ` - ${c}`).join("\n");
|
|
59
56
|
if (hasSudoRunTool) {
|
|
60
57
|
return (
|
|
@@ -224,10 +221,7 @@ export function rewriteChain(command: string): string {
|
|
|
224
221
|
return changed ? rewritten.join("") : command;
|
|
225
222
|
}
|
|
226
223
|
|
|
227
|
-
export function rtk(
|
|
228
|
-
pi: ExtensionAPI,
|
|
229
|
-
status: OptimizerStatus,
|
|
230
|
-
): OptimizerHandle {
|
|
224
|
+
export function rtk(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
|
|
231
225
|
let rtkStatus: RtkStatus | null = null;
|
|
232
226
|
let warnedMissing = false;
|
|
233
227
|
let enabled = true;
|
|
@@ -306,10 +300,7 @@ export function rtk(
|
|
|
306
300
|
|
|
307
301
|
// -- Overlay value handler (called by the /optimizer overlay) --
|
|
308
302
|
|
|
309
|
-
async function run(
|
|
310
|
-
value: string,
|
|
311
|
-
ctx: ExtensionCommandContext,
|
|
312
|
-
): Promise<void> {
|
|
303
|
+
async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
313
304
|
enabled = value === "on";
|
|
314
305
|
saveOptValue("rtk", enabled ? "on" : "off");
|
|
315
306
|
|
package/src/status.test.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
OptimizerStatus,
|
|
4
|
-
renderStatus,
|
|
5
|
-
STATUS_KEY,
|
|
6
|
-
toolIcon,
|
|
7
|
-
} from "./status.ts";
|
|
2
|
+
import { OptimizerStatus, renderStatus, STATUS_KEY, toolIcon } from "./status.ts";
|
|
8
3
|
|
|
9
4
|
/** Tagging colorizer: <accent>X</accent> / <dim>X</dim> for assertions. */
|
|
10
5
|
const tag = (c: string, t: string) => `<${c}>${t}</${c}>`;
|
|
@@ -17,23 +12,13 @@ const PT = toolIcon("ponytail");
|
|
|
17
12
|
|
|
18
13
|
describe("renderStatus", () => {
|
|
19
14
|
it("shows ALL icons in order, accent when enabled", () => {
|
|
20
|
-
expect(
|
|
21
|
-
renderStatus(
|
|
22
|
-
{ caveman: true, rtk: true, toon: true, ponytail: true },
|
|
23
|
-
tag,
|
|
24
|
-
),
|
|
25
|
-
).toBe(
|
|
15
|
+
expect(renderStatus({ caveman: true, rtk: true, toon: true, ponytail: true }, tag)).toBe(
|
|
26
16
|
`<accent>${CV}</accent> <accent>${RK}</accent> <accent>${TN}</accent> <accent>${PT}</accent> `,
|
|
27
17
|
);
|
|
28
18
|
});
|
|
29
19
|
|
|
30
20
|
it("dims disabled tools but still shows them", () => {
|
|
31
|
-
expect(
|
|
32
|
-
renderStatus(
|
|
33
|
-
{ caveman: false, rtk: true, toon: true, ponytail: true },
|
|
34
|
-
tag,
|
|
35
|
-
),
|
|
36
|
-
).toBe(
|
|
21
|
+
expect(renderStatus({ caveman: false, rtk: true, toon: true, ponytail: true }, tag)).toBe(
|
|
37
22
|
`<dim>${CV}</dim> <accent>${RK}</accent> <accent>${TN}</accent> <accent>${PT}</accent> `,
|
|
38
23
|
);
|
|
39
24
|
});
|
|
@@ -63,8 +48,7 @@ describe("OptimizerStatus", () => {
|
|
|
63
48
|
return {
|
|
64
49
|
calls,
|
|
65
50
|
ui: {
|
|
66
|
-
setStatus: (key: string, text: string | undefined) =>
|
|
67
|
-
calls.push({ key, text: text ?? "" }),
|
|
51
|
+
setStatus: (key: string, text: string | undefined) => calls.push({ key, text: text ?? "" }),
|
|
68
52
|
theme: { fg: (c: string, t: string) => `<${c}>${t}</${c}>` },
|
|
69
53
|
},
|
|
70
54
|
} as const;
|
package/src/status.ts
CHANGED
|
@@ -17,11 +17,7 @@ import type {
|
|
|
17
17
|
ExtensionContext,
|
|
18
18
|
ThemeColor,
|
|
19
19
|
} from "@earendil-works/pi-coding-agent";
|
|
20
|
-
import {
|
|
21
|
-
type IconKey,
|
|
22
|
-
icon,
|
|
23
|
-
onIconModeChange,
|
|
24
|
-
} from "@xynogen/pix-pretty/icon-catalog";
|
|
20
|
+
import { type IconKey, icon, onIconModeChange } from "@xynogen/pix-pretty/icon-catalog";
|
|
25
21
|
|
|
26
22
|
/**
|
|
27
23
|
* Each optimizer tool (caveman/rtk/toon/ponytail) exposes a handle so the
|
|
@@ -66,12 +62,7 @@ export function toolIcon(tool: OptimizerTool): string {
|
|
|
66
62
|
}
|
|
67
63
|
|
|
68
64
|
/** Fixed left-to-right order of icons in the cell. */
|
|
69
|
-
const TOOL_ORDER: readonly OptimizerTool[] = [
|
|
70
|
-
"caveman",
|
|
71
|
-
"rtk",
|
|
72
|
-
"toon",
|
|
73
|
-
"ponytail",
|
|
74
|
-
];
|
|
65
|
+
const TOOL_ORDER: readonly OptimizerTool[] = ["caveman", "rtk", "toon", "ponytail"];
|
|
75
66
|
|
|
76
67
|
/** Theme color for enabled icons. */
|
|
77
68
|
const ENABLED_COLOR: ThemeColor = "accent";
|
|
@@ -124,11 +115,7 @@ export class OptimizerStatus {
|
|
|
124
115
|
}
|
|
125
116
|
|
|
126
117
|
/** Update one tool's enabled state and repaint the shared cell. */
|
|
127
|
-
set(
|
|
128
|
-
tool: OptimizerTool,
|
|
129
|
-
enabled: boolean,
|
|
130
|
-
ctx: Pick<ExtensionContext, "ui">,
|
|
131
|
-
): void {
|
|
118
|
+
set(tool: OptimizerTool, enabled: boolean, ctx: Pick<ExtensionContext, "ui">): void {
|
|
132
119
|
this.states[tool] = enabled;
|
|
133
120
|
this.paint(ctx);
|
|
134
121
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
filterModelWarnings,
|
|
4
|
-
stripModelWarningParagraphs,
|
|
5
|
-
} from "./tool-result-filter.ts";
|
|
2
|
+
import { filterModelWarnings, stripModelWarningParagraphs } from "./tool-result-filter.ts";
|
|
6
3
|
|
|
7
4
|
// ── stripModelWarningParagraphs ───────────────────────────────────────────────
|
|
8
5
|
|
|
@@ -29,8 +26,7 @@ describe("stripModelWarningParagraphs", () => {
|
|
|
29
26
|
});
|
|
30
27
|
|
|
31
28
|
it("strips multiple warning paragraphs", () => {
|
|
32
|
-
const text =
|
|
33
|
-
"output\n\n⚠ BLIND WRITE — foo\n\n🔴 THRASHING — bar\n\nmore output";
|
|
29
|
+
const text = "output\n\n⚠ BLIND WRITE — foo\n\n🔴 THRASHING — bar\n\nmore output";
|
|
34
30
|
const result = stripModelWarningParagraphs(text);
|
|
35
31
|
expect(result).not.toContain("BLIND WRITE");
|
|
36
32
|
expect(result).not.toContain("THRASHING");
|
|
@@ -75,9 +71,7 @@ describe("filterModelWarnings", () => {
|
|
|
75
71
|
expect(result).not.toBe(content);
|
|
76
72
|
expect(result[0]?.type).toBe("text");
|
|
77
73
|
if (result[0]?.type === "text") {
|
|
78
|
-
expect((result[0] as { type: string; text: string }).text).not.toContain(
|
|
79
|
-
"BLIND WRITE",
|
|
80
|
-
);
|
|
74
|
+
expect((result[0] as { type: string; text: string }).text).not.toContain("BLIND WRITE");
|
|
81
75
|
}
|
|
82
76
|
});
|
|
83
77
|
|
|
@@ -28,10 +28,7 @@ export function stripModelWarningParagraphs(text: string): string {
|
|
|
28
28
|
// then rejoin. Trim trailing whitespace from the result.
|
|
29
29
|
const paragraphs = text.split(/\n\n/);
|
|
30
30
|
const kept = paragraphs.filter(
|
|
31
|
-
(p) =>
|
|
32
|
-
!MODEL_WARNING_PREFIXES.some((prefix) =>
|
|
33
|
-
p.trimStart().startsWith(prefix),
|
|
34
|
-
),
|
|
31
|
+
(p) => !MODEL_WARNING_PREFIXES.some((prefix) => p.trimStart().startsWith(prefix)),
|
|
35
32
|
);
|
|
36
33
|
if (kept.length === paragraphs.length) return text;
|
|
37
34
|
return kept.join("\n\n").trimEnd();
|