@xynogen/pix-optimizer 1.2.0 → 1.3.1
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/README.md +38 -26
- package/package.json +3 -2
- package/src/caveman.ts +4 -1
- package/src/mode.ts +4 -3
- package/src/opt.ts +2 -1
- package/src/persist.ts +19 -46
- package/src/ponytail.ts +1 -2
- package/src/rtk.ts +60 -26
- package/src/capability.ts +0 -16
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ There is no text-arg form: the overlay is the only UI. Selecting a value calls
|
|
|
23
23
|
the tool's `run()` handler, which persists the new value and repaints the
|
|
24
24
|
shared status cell. Headless/test fallbacks print a plain status summary.
|
|
25
25
|
|
|
26
|
-
State
|
|
26
|
+
State (caveman/rtk/ponytail) persists in the `optimizer` section of `~/.pi/agent/pix.json`. That section is the only place it is stored; the old `optimizer.json` file is imported once by pix-runtime and never written again.
|
|
27
27
|
|
|
28
28
|
## Status bar
|
|
29
29
|
|
|
@@ -63,7 +63,7 @@ article-dropping caveman prompt.
|
|
|
63
63
|
| micro | Experimental prompt-minimized |
|
|
64
64
|
|
|
65
65
|
The `/optimizer` overlay opens a settings dialog when needed. Default level
|
|
66
|
-
for new sessions is restored from
|
|
66
|
+
for new sessions is restored from `pix.json` → `optimizer`.
|
|
67
67
|
|
|
68
68
|
### RTK Tool Rewriting (`Rk`)
|
|
69
69
|
|
|
@@ -77,14 +77,26 @@ Two layers, both active automatically:
|
|
|
77
77
|
`||`, `;` and `|`, and every known segment is prefixed** — e.g.
|
|
78
78
|
`git add . && git push` becomes `rtk git add . && rtk git push`.
|
|
79
79
|
Operators inside quotes are ignored, and unparseable commands are left
|
|
80
|
-
untouched.
|
|
81
|
-
(warns once).
|
|
80
|
+
untouched. Commands are never rewritten while `rtk` is missing.
|
|
82
81
|
|
|
83
|
-
**
|
|
82
|
+
**Binary:** pix finds `rtk` in this order: `binary.json` → `~/.pi/agent/bin` →
|
|
83
|
+
PATH (see pix-runtime, Binaries).
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
When RTK is on and `rtk` is missing, pix downloads the latest official
|
|
86
|
+
release from `rtk-ai/rtk` into `~/.pi/agent/bin` the first time Pi loads. It
|
|
87
|
+
verifies the download against the release's `checksums.txt`.
|
|
88
|
+
|
|
89
|
+
The download is visible: a footer status while it runs, then one line naming
|
|
90
|
+
the version and source. It never blocks startup or a tool call.
|
|
91
|
+
|
|
92
|
+
The download is skipped when:
|
|
93
|
+
|
|
94
|
+
- `PI_OFFLINE` is set;
|
|
95
|
+
- RTK is off in `/optimizer`;
|
|
96
|
+
- `binary.json` names a path that doesn't exist.
|
|
97
|
+
|
|
98
|
+
If you pin a path in `binary.json`, rewritten commands use that quoted path
|
|
99
|
+
instead of a bare `rtk`.
|
|
88
100
|
|
|
89
101
|
### Ponytail Mode (`Pt`)
|
|
90
102
|
|
|
@@ -105,7 +117,7 @@ PATH dependency.
|
|
|
105
117
|
|
|
106
118
|
## Configuration via `pix.json`
|
|
107
119
|
|
|
108
|
-
|
|
120
|
+
Optimizer state lives in `~/.pi/agent/pix.json`. You can edit it by hand, or use `/optimizer`, which writes to the same section.
|
|
109
121
|
|
|
110
122
|
```jsonc
|
|
111
123
|
{
|
|
@@ -117,18 +129,6 @@ Set the initial optimizer state for new sessions in `~/.pi/agent/pix.json`. Thes
|
|
|
117
129
|
}
|
|
118
130
|
```
|
|
119
131
|
|
|
120
|
-
## Installation
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
pi install npm:@xynogen/pix-optimizer
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
> Also included in [`@xynogen/pix-core`](https://www.npmjs.com/package/@xynogen/pix-core):
|
|
127
|
-
>
|
|
128
|
-
> ```bash
|
|
129
|
-
> pi install npm:@xynogen/pix-core
|
|
130
|
-
> ```
|
|
131
|
-
|
|
132
132
|
## Architecture
|
|
133
133
|
|
|
134
134
|
| File | Role |
|
|
@@ -139,7 +139,7 @@ pi install npm:@xynogen/pix-optimizer
|
|
|
139
139
|
| `src/caveman.ts` | Caveman logic, levels, prompt |
|
|
140
140
|
| `src/rtk.ts` | RTK prompt + bash command rewriting |
|
|
141
141
|
| `src/ponytail.ts` | Ponytail logic, levels, prompt |
|
|
142
|
-
| `src/persist.ts` |
|
|
142
|
+
| `src/persist.ts` | Reads/writes the `optimizer` section of `pix.json` via pix-runtime |
|
|
143
143
|
| `src/tool-result-filter.ts` | Strips model-guidance warnings from tool_result |
|
|
144
144
|
|
|
145
145
|
Each tool registers its own lifecycle hooks and exposes an `OptimizerHandle`
|
|
@@ -172,16 +172,28 @@ All upstreams are MIT licensed. No codebase was copied directly — the logic wa
|
|
|
172
172
|
rewritten and combined into a single extension with a unified `/optimizer`
|
|
173
173
|
command and shared status bar. This package does not sync back to any upstream.
|
|
174
174
|
|
|
175
|
-
##
|
|
175
|
+
## Install
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pi install npm:@xynogen/pix-optimizer
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
> Bundled in [`@xynogen/pix-core`](https://www.npmjs.com/package/@xynogen/pix-core). Install it alone only if you do not use pix-core.
|
|
176
182
|
|
|
177
|
-
|
|
183
|
+
## Full distro
|
|
178
184
|
|
|
179
|
-
|
|
185
|
+
This package is part of [Pix](https://github.com/xynogen/pix-mono). The installer sets up Pi and the full distro. See [Install](https://github.com/xynogen/pix-mono#install) for the notes for each OS.
|
|
180
186
|
|
|
181
187
|
```bash
|
|
188
|
+
# Linux / macOS
|
|
182
189
|
curl -fsSL https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.sh | sh
|
|
183
190
|
```
|
|
184
191
|
|
|
192
|
+
```powershell
|
|
193
|
+
# Windows
|
|
194
|
+
irm https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.ps1 | iex
|
|
195
|
+
```
|
|
196
|
+
|
|
185
197
|
## License
|
|
186
198
|
|
|
187
|
-
MIT
|
|
199
|
+
MIT. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-optimizer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Performance optimization suite for Pi Coding Agent - caveman mode + RTK tool rewriting + ponytail lazy-dev mode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@xynogen/pix-pretty": "^1.
|
|
46
|
+
"@xynogen/pix-pretty": "^1.30.0",
|
|
47
|
+
"@xynogen/pix-runtime": "^0.13.0"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
50
|
"@earendil-works/pi-coding-agent": "*",
|
package/src/caveman.ts
CHANGED
|
@@ -80,7 +80,7 @@ Write in Simplified Technical English. Use short common words, the active voice,
|
|
|
80
80
|
const INTENSITY: Record<Exclude<Level, "off" | "micro">, string> = {
|
|
81
81
|
lite: `\
|
|
82
82
|
STE-flavored words. Keep the sentence, tense, active-voice, and no-phrasal-verb discipline. \
|
|
83
|
-
Relax the strict dictionary. Apply
|
|
83
|
+
Relax the strict dictionary. Apply the reply shape lightly: lead with the answer, no preamble or closer.
|
|
84
84
|
Example: "The component re-renders because you create a new object reference each render. Wrap it in \`useMemo\`."`,
|
|
85
85
|
|
|
86
86
|
full: `\
|
|
@@ -109,6 +109,9 @@ Boundaries: this governs prose, not code. "stop caveman" or "normal mode" revert
|
|
|
109
109
|
export function buildPrompt(level: Level): string {
|
|
110
110
|
if (level === "off") return "";
|
|
111
111
|
if (level === "micro") return MICRO_PROMPT;
|
|
112
|
+
// ponytail: lite relaxes most of BASE, so it ships the short MICRO rules + its intensity (~800 tokens less).
|
|
113
|
+
if (level === "lite")
|
|
114
|
+
return [MICRO_PROMPT, "", `Intensity: ${INTENSITY.lite}`, "", SAFETY].join("\n");
|
|
112
115
|
return [BASE, "", `Intensity: ${INTENSITY[level]}`, "", SAFETY].join("\n");
|
|
113
116
|
}
|
|
114
117
|
|
package/src/mode.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ExtensionContext,
|
|
15
15
|
} from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import { showTransientError } from "@xynogen/pix-pretty/transient-error";
|
|
17
|
+
import { getErrorMessage } from "@xynogen/pix-pretty/utils";
|
|
17
18
|
import { loadOptValue, saveOptValue } from "./persist.ts";
|
|
18
19
|
import type { OptimizerHandle, OptimizerStatus, OptimizerTool } from "./status.ts";
|
|
19
20
|
|
|
@@ -87,7 +88,7 @@ export function createMode<L extends string>(
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
const saved = loadOptValue(name);
|
|
90
|
-
if (
|
|
91
|
+
if (levels.includes(saved as L)) level = saved as L;
|
|
91
92
|
syncStatus(ctx);
|
|
92
93
|
});
|
|
93
94
|
|
|
@@ -102,9 +103,9 @@ export function createMode<L extends string>(
|
|
|
102
103
|
|
|
103
104
|
pi.appendEntry(customType, { level });
|
|
104
105
|
try {
|
|
105
|
-
saveOptValue(name, level);
|
|
106
|
+
await saveOptValue(name, level);
|
|
106
107
|
} catch (error) {
|
|
107
|
-
const detail =
|
|
108
|
+
const detail = getErrorMessage(error);
|
|
108
109
|
showTransientError(ctx.ui, `optimizer: failed to save ${name}: ${detail}`);
|
|
109
110
|
}
|
|
110
111
|
syncStatus(ctx);
|
package/src/opt.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
import { type KeybindingsManager, matchesKey } from "@earendil-works/pi-tui";
|
|
19
|
+
import { icon } from "@xynogen/pix-pretty/icon-catalog";
|
|
19
20
|
import {
|
|
20
21
|
frameModal,
|
|
21
22
|
MIN_MODAL_HEIGHT,
|
|
@@ -153,7 +154,7 @@ export function registerOptCommand(
|
|
|
153
154
|
width,
|
|
154
155
|
maxHeight: terminalModalHeight(tui.terminal?.rows),
|
|
155
156
|
minHeight: MIN_MODAL_HEIGHT,
|
|
156
|
-
header: [theme.fg("accent", theme.bold("
|
|
157
|
+
header: [theme.fg("accent", theme.bold(`${icon("opt.title")} Optimizer`)), ""],
|
|
157
158
|
body: rows,
|
|
158
159
|
footer: [
|
|
159
160
|
"",
|
package/src/persist.ts
CHANGED
|
@@ -1,54 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* persist.ts —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* value in one file under the agent dir so the picker survives a full quit and
|
|
7
|
-
* restart. Each tool reads its value on session_start and writes on run().
|
|
8
|
-
*
|
|
9
|
-
* ~/.pi/agent/optimizer.json → { "caveman": "lite", "rtk": "on", ... }
|
|
2
|
+
* persist.ts — /optimizer tool states live in pix.json `optimizer` (owned by
|
|
3
|
+
* pix-runtime). Replaces the old `optimizer.json` sidecar, which pix-runtime
|
|
4
|
+
* imports and archives on init; writing it again made two sources of truth
|
|
5
|
+
* that overwrote each other.
|
|
10
6
|
*/
|
|
11
7
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { config, updateConfig } from "@xynogen/pix-runtime/config";
|
|
9
|
+
import { type OptimizerConfig, optimizerSection } from "@xynogen/pix-runtime/sections";
|
|
15
10
|
import type { OptimizerTool } from "./status.ts";
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
function getStatePath(): string {
|
|
22
|
-
return join(getAgentDir(), "optimizer.json");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function readFile(): OptimizerFileConfig {
|
|
26
|
-
try {
|
|
27
|
-
const sp = getStatePath();
|
|
28
|
-
if (!existsSync(sp)) return {};
|
|
29
|
-
const raw = JSON.parse(readFileSync(sp, "utf-8")) as unknown;
|
|
30
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
31
|
-
|
|
32
|
-
const config: OptimizerFileConfig = {};
|
|
33
|
-
for (const tool of OPTIMIZER_TOOLS) {
|
|
34
|
-
const value = (raw as Record<string, unknown>)[tool];
|
|
35
|
-
if (typeof value === "string") config[tool] = value;
|
|
36
|
-
}
|
|
37
|
-
return config;
|
|
38
|
-
} catch {
|
|
39
|
-
return {};
|
|
40
|
-
}
|
|
12
|
+
/** Read a tool's value from pix.json (section defaults when unset). */
|
|
13
|
+
export function loadOptValue(tool: OptimizerTool): string {
|
|
14
|
+
return config(optimizerSection)[tool];
|
|
41
15
|
}
|
|
42
16
|
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
writeFileSync(sp, JSON.stringify(next, null, 2), "utf-8");
|
|
17
|
+
/**
|
|
18
|
+
* Persist a tool's value into pix.json. Invalid values are rejected by the
|
|
19
|
+
* section parser and snap to the default. Rejects on write failure so the UI
|
|
20
|
+
* caller can render it.
|
|
21
|
+
*/
|
|
22
|
+
export async function saveOptValue(tool: OptimizerTool, value: string): Promise<void> {
|
|
23
|
+
await updateConfig(optimizerSection, { [tool]: value } as Partial<OptimizerConfig>, {
|
|
24
|
+
origin: "command",
|
|
25
|
+
source: "pix-optimizer",
|
|
26
|
+
});
|
|
54
27
|
}
|
package/src/ponytail.ts
CHANGED
|
@@ -94,8 +94,7 @@ error handling that prevents data loss, security, accessibility, or anything the
|
|
|
94
94
|
The hardware is never the spec ideal. Leave the calibration knob.
|
|
95
95
|
Lazy code without its check is unfinished. Non-trivial logic leaves ONE runnable check behind \
|
|
96
96
|
(an assert-based self-check or one small test file, no frameworks). A trivial one-liner needs no test.
|
|
97
|
-
Output: write the code first, then at most three short lines — what you skipped, and when to add it.
|
|
98
|
-
Write these lines in Simplified Technical English: short common words, the active voice, and simple tenses.
|
|
97
|
+
Output: write the code first, then at most three short lines — what you skipped, and when to add it.
|
|
99
98
|
Boundaries: ponytail governs what you build, not how you talk. "stop ponytail" or "normal mode" reverts.`;
|
|
100
99
|
|
|
101
100
|
/**
|
package/src/rtk.ts
CHANGED
|
@@ -11,8 +11,10 @@ import type {
|
|
|
11
11
|
ExtensionCommandContext,
|
|
12
12
|
ExtensionContext,
|
|
13
13
|
} from "@earendil-works/pi-coding-agent";
|
|
14
|
+
import { reportToolStatus } from "@xynogen/pix-pretty/tool-status";
|
|
14
15
|
import { showTransientError } from "@xynogen/pix-pretty/transient-error";
|
|
15
|
-
import {
|
|
16
|
+
import { getErrorMessage } from "@xynogen/pix-pretty/utils";
|
|
17
|
+
import { ensureTool, type ResolvedTool, resolveTool } from "@xynogen/pix-runtime/binaries";
|
|
16
18
|
import { loadOptValue, saveOptValue } from "./persist.ts";
|
|
17
19
|
import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
|
|
18
20
|
|
|
@@ -75,7 +77,7 @@ export function buildSudoBlockReason(sudoCmds: string[], hasSudoRunTool: boolean
|
|
|
75
77
|
|
|
76
78
|
export function applyRtkRewrite(
|
|
77
79
|
event: BashCallEvent,
|
|
78
|
-
opts: { enabled: boolean; rtkAvailable: boolean },
|
|
80
|
+
opts: { enabled: boolean; rtkAvailable: boolean; rtkCommand?: string },
|
|
79
81
|
): boolean {
|
|
80
82
|
if (!opts.enabled) return false;
|
|
81
83
|
if (!opts.rtkAvailable) return false;
|
|
@@ -84,7 +86,7 @@ export function applyRtkRewrite(
|
|
|
84
86
|
const command = event.input?.command;
|
|
85
87
|
if (typeof command !== "string" || !command) return false;
|
|
86
88
|
|
|
87
|
-
const rewritten = rewriteChain(command);
|
|
89
|
+
const rewritten = rewriteChain(command, opts.rtkCommand);
|
|
88
90
|
if (rewritten === command) return false;
|
|
89
91
|
|
|
90
92
|
event.input.command = rewritten;
|
|
@@ -128,12 +130,31 @@ const RTK_COMMANDS = new Set([
|
|
|
128
130
|
|
|
129
131
|
interface RtkStatus {
|
|
130
132
|
available: boolean;
|
|
133
|
+
/** Command prefix to inject: bare `rtk`, or the quoted path for a binary.json pin. */
|
|
134
|
+
command: string;
|
|
131
135
|
checkedAt: number;
|
|
132
136
|
}
|
|
133
137
|
|
|
134
|
-
/**
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Prefix used in rewritten commands. The bash tool puts `<agentDir>/bin` on
|
|
140
|
+
* PATH and PATH hits resolve by name, so those stay a readable bare `rtk`;
|
|
141
|
+
* only a user-chosen path (binary.json) must be spelled out, single-quoted
|
|
142
|
+
* with forward slashes so Git Bash on Windows runs it too.
|
|
143
|
+
*/
|
|
144
|
+
export function rtkCommandFor(tool: Pick<ResolvedTool, "path" | "source">): string {
|
|
145
|
+
if (tool.source !== "user") return "rtk";
|
|
146
|
+
const posix = tool.path.replaceAll("\\", "/");
|
|
147
|
+
return `'${posix.replaceAll("'", "'\\''")}'`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Locate rtk (binary.json → agent bin → PATH) without running anything. */
|
|
151
|
+
export function probeRtk(): RtkStatus {
|
|
152
|
+
const tool = resolveTool("rtk");
|
|
153
|
+
return {
|
|
154
|
+
available: tool !== undefined,
|
|
155
|
+
command: tool ? rtkCommandFor(tool) : "rtk",
|
|
156
|
+
checkedAt: Date.now(),
|
|
157
|
+
};
|
|
137
158
|
}
|
|
138
159
|
|
|
139
160
|
/**
|
|
@@ -195,7 +216,7 @@ const CHAIN_OPERATORS = new Set(["&&", "||", ";", "|"]);
|
|
|
195
216
|
* RTK command and it is not already prefixed. Operators are preserved.
|
|
196
217
|
* Returns the rewritten command, or the original if nothing changed.
|
|
197
218
|
*/
|
|
198
|
-
export function rewriteChain(command: string): string {
|
|
219
|
+
export function rewriteChain(command: string, rtkCommand = "rtk"): string {
|
|
199
220
|
const parts = splitChain(command);
|
|
200
221
|
if (!parts) return command; // unparseable — leave untouched
|
|
201
222
|
|
|
@@ -208,11 +229,11 @@ export function rewriteChain(command: string): string {
|
|
|
208
229
|
if (!body) return part;
|
|
209
230
|
|
|
210
231
|
const firstWord = body.split(/\s+/)[0] ?? "";
|
|
211
|
-
if (firstWord === "rtk") return part;
|
|
232
|
+
if (firstWord === "rtk" || firstWord === rtkCommand) return part;
|
|
212
233
|
if (!RTK_COMMANDS.has(firstWord)) return part;
|
|
213
234
|
|
|
214
235
|
changed = true;
|
|
215
|
-
return `${leading}
|
|
236
|
+
return `${leading}${rtkCommand} ${body}`;
|
|
216
237
|
});
|
|
217
238
|
|
|
218
239
|
return changed ? rewritten.join("") : command;
|
|
@@ -239,15 +260,33 @@ export function rtk(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle
|
|
|
239
260
|
return rtkStatus;
|
|
240
261
|
}
|
|
241
262
|
|
|
242
|
-
|
|
243
|
-
rtkStatus =
|
|
244
|
-
available,
|
|
245
|
-
checkedAt: Date.now(),
|
|
246
|
-
};
|
|
247
|
-
if (available) warnedMissing = false;
|
|
263
|
+
rtkStatus = probeRtk();
|
|
264
|
+
if (rtkStatus.available) warnedMissing = false;
|
|
248
265
|
return rtkStatus;
|
|
249
266
|
};
|
|
250
267
|
|
|
268
|
+
// First load without rtk: download it once, visibly (footer status + one
|
|
269
|
+
// transient line naming repo and version). Never blocks startup or a tool
|
|
270
|
+
// call; PI_OFFLINE, a broken binary.json path, or RTK off skip it.
|
|
271
|
+
let installing: Promise<void> | undefined;
|
|
272
|
+
const installRtk = (ctx: Pick<ExtensionContext, "ui">) => {
|
|
273
|
+
installing ??= ensureTool("rtk", { onStatus: reportToolStatus(ctx.ui) })
|
|
274
|
+
.then(() => {
|
|
275
|
+
rtkStatus = null;
|
|
276
|
+
})
|
|
277
|
+
.catch((error: unknown) => {
|
|
278
|
+
if (warnedMissing) return;
|
|
279
|
+
warnedMissing = true;
|
|
280
|
+
const detail = getErrorMessage(error);
|
|
281
|
+
ctx.ui.notify(`RTK rewriting disabled: ${detail}`, "warning");
|
|
282
|
+
})
|
|
283
|
+
.then(async () => {
|
|
284
|
+
await checkRtkAvailability();
|
|
285
|
+
syncStatus(ctx);
|
|
286
|
+
});
|
|
287
|
+
return installing;
|
|
288
|
+
};
|
|
289
|
+
|
|
251
290
|
// Detect sudo_run tool availability. No system-prompt injection — the
|
|
252
291
|
// tool_call rewrite hook adds the rtk prefix on its own, so we keep zero
|
|
253
292
|
// always-on context cost (ponytail: manual `rtk err`/`rtk summary`/`rtk
|
|
@@ -266,14 +305,8 @@ export function rtk(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle
|
|
|
266
305
|
const saved = loadOptValue("rtk");
|
|
267
306
|
if (saved === "on" || saved === "off") enabled = saved === "on";
|
|
268
307
|
const probe = await checkRtkAvailability();
|
|
269
|
-
if (!probe.available && !warnedMissing) {
|
|
270
|
-
ctx.ui.notify(
|
|
271
|
-
"rtk not found — RTK rewriting disabled. Install: cargo install rtk-ai",
|
|
272
|
-
"warning",
|
|
273
|
-
);
|
|
274
|
-
warnedMissing = true;
|
|
275
|
-
}
|
|
276
308
|
syncStatus(ctx);
|
|
309
|
+
if (!probe.available && enabled) void installRtk(ctx);
|
|
277
310
|
});
|
|
278
311
|
pi.on("agent_start", async (_event, ctx) => {
|
|
279
312
|
syncStatus(ctx);
|
|
@@ -287,15 +320,16 @@ export function rtk(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle
|
|
|
287
320
|
async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
288
321
|
enabled = value === "on";
|
|
289
322
|
try {
|
|
290
|
-
saveOptValue("rtk", enabled ? "on" : "off");
|
|
323
|
+
await saveOptValue("rtk", enabled ? "on" : "off");
|
|
291
324
|
} catch (error) {
|
|
292
|
-
const detail =
|
|
325
|
+
const detail = getErrorMessage(error);
|
|
293
326
|
showTransientError(ctx.ui, `optimizer: failed to save rtk: ${detail}`);
|
|
294
327
|
}
|
|
295
328
|
|
|
296
|
-
await checkRtkAvailability();
|
|
329
|
+
const probe = await checkRtkAvailability();
|
|
297
330
|
syncStatus(ctx);
|
|
298
331
|
ctx.ui.notify(`RTK rewriting ${enabled ? "on" : "off"}.`, "info");
|
|
332
|
+
if (enabled && !probe.available) void installRtk(ctx);
|
|
299
333
|
}
|
|
300
334
|
|
|
301
335
|
// Rewrite bash commands to add rtk prefix.
|
|
@@ -339,7 +373,7 @@ export function rtk(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle
|
|
|
339
373
|
// Rewrite every segment in the command chain that uses a known RTK
|
|
340
374
|
// command (e.g. `git add . && git push` -> `rtk git add . && rtk git push`).
|
|
341
375
|
// Mutates `event.input.command` in place — the SDK's supported patch path.
|
|
342
|
-
applyRtkRewrite(event, { enabled, rtkAvailable: probe.available });
|
|
376
|
+
applyRtkRewrite(event, { enabled, rtkAvailable: probe.available, rtkCommand: probe.command });
|
|
343
377
|
return undefined;
|
|
344
378
|
});
|
|
345
379
|
|
package/src/capability.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
3
|
-
/** Test whether a command can perform a harmless operation through Pi's executor. */
|
|
4
|
-
export async function canExecute(
|
|
5
|
-
pi: Pick<ExtensionAPI, "exec">,
|
|
6
|
-
command: string,
|
|
7
|
-
args: string[],
|
|
8
|
-
timeout = 3000,
|
|
9
|
-
): Promise<boolean> {
|
|
10
|
-
try {
|
|
11
|
-
const result = await pi.exec(command, args, { timeout });
|
|
12
|
-
return result.code === 0;
|
|
13
|
-
} catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
}
|