pi-rtk-optimizer 0.3.3 → 0.5.0
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 +102 -67
- package/README.md +292 -290
- package/config/config.example.json +36 -35
- package/package.json +4 -4
- package/src/additional-coverage-test.ts +278 -0
- package/src/boolean-format.ts +3 -0
- package/src/command-rewriter-test.ts +160 -120
- package/src/command-rewriter.ts +594 -585
- package/src/config-modal-test.ts +168 -0
- package/src/config-modal.ts +613 -600
- package/src/config-store.ts +224 -217
- package/src/index-test.ts +54 -0
- package/src/index.ts +410 -289
- package/src/output-compactor-test.ts +500 -158
- package/src/output-compactor.ts +432 -349
- package/src/record-utils.ts +6 -0
- package/src/rewrite-bypass.ts +332 -173
- package/src/rewrite-pipeline-safety.ts +154 -0
- package/src/rewrite-rules.ts +255 -255
- package/src/rtk-command-environment.ts +64 -0
- package/src/runtime-guard-test.ts +42 -50
- package/src/runtime-guard.ts +14 -14
- package/src/techniques/build.ts +155 -155
- package/src/techniques/emoji.ts +91 -0
- package/src/techniques/git.ts +231 -229
- package/src/techniques/index.ts +10 -16
- package/src/techniques/linter.ts +151 -161
- package/src/techniques/path-utils.ts +67 -0
- package/src/techniques/rtk.ts +136 -0
- package/src/techniques/search.ts +67 -76
- package/src/techniques/source.ts +253 -253
- package/src/techniques/test-output.ts +172 -172
- package/src/test-helpers.ts +10 -0
- package/src/tool-execution-sanitizer.ts +69 -0
- package/src/types-shims.d.ts +192 -183
- package/src/types.ts +103 -114
- package/src/zellij-modal.ts +1001 -1001
- package/src/compat-commands.ts +0 -207
package/src/index.ts
CHANGED
|
@@ -1,289 +1,410 @@
|
|
|
1
|
-
import { isToolCallEventType, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
ensureConfigExists,
|
|
4
|
-
getRtkIntegrationConfigPath,
|
|
5
|
-
loadRtkIntegrationConfig,
|
|
6
|
-
normalizeRtkIntegrationConfig,
|
|
7
|
-
saveRtkIntegrationConfig,
|
|
8
|
-
} from "./config-store.js";
|
|
9
|
-
import { computeRewriteDecision } from "./command-rewriter.js";
|
|
10
|
-
import { registerRtkIntegrationCommand } from "./config-modal.js";
|
|
11
|
-
import { EXTENSION_NAME } from "./constants.js";
|
|
12
|
-
import { clearOutputMetrics, getOutputMetricsSummary } from "./output-metrics.js";
|
|
13
|
-
import { compactToolResult, type ToolResultCompactionMetadata } from "./output-compactor.js";
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
existingDetails
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (!config.enabled) {
|
|
215
|
-
return
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
1
|
+
import { isToolCallEventType, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
ensureConfigExists,
|
|
4
|
+
getRtkIntegrationConfigPath,
|
|
5
|
+
loadRtkIntegrationConfig,
|
|
6
|
+
normalizeRtkIntegrationConfig,
|
|
7
|
+
saveRtkIntegrationConfig,
|
|
8
|
+
} from "./config-store.js";
|
|
9
|
+
import { computeRewriteDecision } from "./command-rewriter.js";
|
|
10
|
+
import { registerRtkIntegrationCommand } from "./config-modal.js";
|
|
11
|
+
import { EXTENSION_NAME } from "./constants.js";
|
|
12
|
+
import { clearOutputMetrics, getOutputMetricsSummary } from "./output-metrics.js";
|
|
13
|
+
import { compactToolResult, type ToolResultCompactionMetadata } from "./output-compactor.js";
|
|
14
|
+
import { toRecord } from "./record-utils.js";
|
|
15
|
+
import { applyRtkCommandEnvironment } from "./rtk-command-environment.js";
|
|
16
|
+
import { applyRewrittenCommandShellSafetyFixups } from "./rewrite-pipeline-safety.js";
|
|
17
|
+
import { shouldRequireRtkAvailabilityForCommandHandling, shouldSkipCommandHandlingWhenRtkMissing } from "./runtime-guard.js";
|
|
18
|
+
import { sanitizeStreamingBashExecutionResult } from "./tool-execution-sanitizer.js";
|
|
19
|
+
import type { RtkIntegrationConfig, RuntimeStatus } from "./types.js";
|
|
20
|
+
import { applyWindowsBashCompatibilityFixes } from "./windows-command-helpers.js";
|
|
21
|
+
|
|
22
|
+
function trimMessage(raw: string, maxLength = 220): string {
|
|
23
|
+
const clean = raw.replace(/\s+/g, " ").trim();
|
|
24
|
+
if (clean.length <= maxLength) {
|
|
25
|
+
return clean;
|
|
26
|
+
}
|
|
27
|
+
return `${clean.slice(0, maxLength - 1)}…`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SOURCE_FILTER_TROUBLESHOOTING_NOTE =
|
|
31
|
+
"RTK note: If file edits repeatedly fail because old text does not match, run '/rtk', turn off 'Read source filtering enabled', re-read the file, apply the edit, then turn it back on.";
|
|
32
|
+
|
|
33
|
+
function mergeCompactionDetails(
|
|
34
|
+
existingDetails: unknown,
|
|
35
|
+
compaction: ToolResultCompactionMetadata,
|
|
36
|
+
): Record<string, unknown> {
|
|
37
|
+
const baseDetails = toRecord(existingDetails);
|
|
38
|
+
const baseMetadata = toRecord(baseDetails.metadata);
|
|
39
|
+
|
|
40
|
+
const nextDetails: Record<string, unknown> = {
|
|
41
|
+
...baseDetails,
|
|
42
|
+
rtkCompaction: compaction,
|
|
43
|
+
metadata: {
|
|
44
|
+
...baseMetadata,
|
|
45
|
+
rtkCompaction: compaction,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (Object.keys(baseDetails).length === 0 && existingDetails !== undefined) {
|
|
50
|
+
nextDetails.rawDetails = existingDetails;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return nextDetails;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface BoundedNoticeTracker {
|
|
57
|
+
remember(key: string): boolean;
|
|
58
|
+
reset(): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createBoundedNoticeTracker(maxEntries: number): BoundedNoticeTracker {
|
|
62
|
+
const normalizedLimit = Math.max(1, Math.floor(maxEntries));
|
|
63
|
+
const seen = new Set<string>();
|
|
64
|
+
const order: string[] = [];
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
remember(key: string): boolean {
|
|
68
|
+
if (seen.has(key)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
seen.add(key);
|
|
73
|
+
order.push(key);
|
|
74
|
+
while (order.length > normalizedLimit) {
|
|
75
|
+
const evicted = order.shift();
|
|
76
|
+
if (evicted !== undefined) {
|
|
77
|
+
seen.delete(evicted);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return true;
|
|
82
|
+
},
|
|
83
|
+
reset(): void {
|
|
84
|
+
seen.clear();
|
|
85
|
+
order.length = 0;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
|
|
91
|
+
const initialLoad = loadRtkIntegrationConfig();
|
|
92
|
+
let config: RtkIntegrationConfig = initialLoad.config;
|
|
93
|
+
let pendingLoadWarning = initialLoad.warning;
|
|
94
|
+
let runtimeStatus: RuntimeStatus = { rtkAvailable: false };
|
|
95
|
+
const warnedMessages = createBoundedNoticeTracker(100);
|
|
96
|
+
const suggestionNotices = createBoundedNoticeTracker(200);
|
|
97
|
+
const activeBashCommands = new Map<string, string>();
|
|
98
|
+
let missingRtkWarningShown = false;
|
|
99
|
+
|
|
100
|
+
const formatRewriteNotice = (originalCommand: string, rewrittenCommand: string): string => {
|
|
101
|
+
const original = trimMessage(originalCommand, 100);
|
|
102
|
+
const rewritten = trimMessage(rewrittenCommand, 120);
|
|
103
|
+
return `RTK rewrite: ${original} -> ${rewritten}`;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const warnOnce = (
|
|
107
|
+
ctx: ExtensionContext | ExtensionCommandContext,
|
|
108
|
+
message: string,
|
|
109
|
+
level: "warning" | "error" = "warning",
|
|
110
|
+
): void => {
|
|
111
|
+
if (!warnedMessages.remember(message)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.warn(`[${EXTENSION_NAME}] ${message}`);
|
|
116
|
+
if (ctx.hasUI) {
|
|
117
|
+
ctx.ui.notify(message, level);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const clearTrackedBashCommands = (): void => {
|
|
122
|
+
activeBashCommands.clear();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const trackBashCommand = (toolCallId: unknown, args: unknown): void => {
|
|
126
|
+
if (typeof toolCallId !== "string") {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const argsRecord = toRecord(args);
|
|
131
|
+
const command = typeof argsRecord.command === "string" ? argsRecord.command.trim() : "";
|
|
132
|
+
if (!command) {
|
|
133
|
+
activeBashCommands.delete(toolCallId);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
activeBashCommands.set(toolCallId, command);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const getTrackedBashCommand = (toolCallId: unknown): string | undefined => {
|
|
141
|
+
if (typeof toolCallId !== "string") {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return activeBashCommands.get(toolCallId);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const forgetTrackedBashCommand = (toolCallId: unknown): void => {
|
|
149
|
+
if (typeof toolCallId !== "string") {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
activeBashCommands.delete(toolCallId);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const refreshConfig = async (ctx?: ExtensionContext | ExtensionCommandContext): Promise<void> => {
|
|
157
|
+
const ensured = ensureConfigExists();
|
|
158
|
+
if (ensured.error && ctx) {
|
|
159
|
+
warnOnce(ctx, ensured.error);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const loaded = loadRtkIntegrationConfig();
|
|
163
|
+
config = loaded.config;
|
|
164
|
+
pendingLoadWarning = loaded.warning;
|
|
165
|
+
await refreshRuntimeStatus();
|
|
166
|
+
|
|
167
|
+
if (pendingLoadWarning && ctx) {
|
|
168
|
+
warnOnce(ctx, pendingLoadWarning);
|
|
169
|
+
pendingLoadWarning = undefined;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const setConfig = (next: RtkIntegrationConfig, ctx: ExtensionCommandContext): void => {
|
|
174
|
+
config = normalizeRtkIntegrationConfig(next);
|
|
175
|
+
const saved = saveRtkIntegrationConfig(config);
|
|
176
|
+
if (!saved.success && saved.error) {
|
|
177
|
+
ctx.ui.notify(saved.error, "error");
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const refreshRuntimeStatus = async (): Promise<RuntimeStatus> => {
|
|
182
|
+
try {
|
|
183
|
+
const result = await pi.exec("rtk", ["--version"], { timeout: 5000 });
|
|
184
|
+
if (result.code === 0) {
|
|
185
|
+
runtimeStatus = {
|
|
186
|
+
rtkAvailable: true,
|
|
187
|
+
lastCheckedAt: Date.now(),
|
|
188
|
+
};
|
|
189
|
+
missingRtkWarningShown = false;
|
|
190
|
+
return runtimeStatus;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const detail = trimMessage(
|
|
194
|
+
`${result.stderr || ""} ${result.stdout || ""} ${result.code ? `(exit ${result.code})` : ""}`,
|
|
195
|
+
);
|
|
196
|
+
runtimeStatus = {
|
|
197
|
+
rtkAvailable: false,
|
|
198
|
+
lastCheckedAt: Date.now(),
|
|
199
|
+
lastError: detail || `exit ${result.code}`,
|
|
200
|
+
};
|
|
201
|
+
return runtimeStatus;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
204
|
+
runtimeStatus = {
|
|
205
|
+
rtkAvailable: false,
|
|
206
|
+
lastCheckedAt: Date.now(),
|
|
207
|
+
lastError: trimMessage(message),
|
|
208
|
+
};
|
|
209
|
+
return runtimeStatus;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const maybeWarnRtkMissing = (ctx: ExtensionContext): void => {
|
|
214
|
+
if (!config.enabled || config.mode !== "rewrite" || !config.guardWhenRtkMissing) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (runtimeStatus.rtkAvailable) {
|
|
219
|
+
missingRtkWarningShown = false;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (missingRtkWarningShown) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
missingRtkWarningShown = true;
|
|
228
|
+
const reason = runtimeStatus.lastError ? ` (${runtimeStatus.lastError})` : "";
|
|
229
|
+
warnOnce(ctx, `${EXTENSION_NAME}: rtk binary unavailable, command rewrite bypassed${reason}.`);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const ensureRuntimeStatusFresh = async (): Promise<void> => {
|
|
233
|
+
if (!shouldRequireRtkAvailabilityForCommandHandling(config)) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const now = Date.now();
|
|
238
|
+
const isStale = !runtimeStatus.lastCheckedAt || now - runtimeStatus.lastCheckedAt > 30_000;
|
|
239
|
+
if (isStale) {
|
|
240
|
+
await refreshRuntimeStatus();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const controller = {
|
|
245
|
+
getConfig: () => config,
|
|
246
|
+
setConfig,
|
|
247
|
+
getConfigPath: getRtkIntegrationConfigPath,
|
|
248
|
+
getRuntimeStatus: () => runtimeStatus,
|
|
249
|
+
refreshRuntimeStatus,
|
|
250
|
+
getMetricsSummary: getOutputMetricsSummary,
|
|
251
|
+
clearMetrics: clearOutputMetrics,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
registerRtkIntegrationCommand(pi, controller);
|
|
255
|
+
|
|
256
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
257
|
+
warnedMessages.reset();
|
|
258
|
+
suggestionNotices.reset();
|
|
259
|
+
clearTrackedBashCommands();
|
|
260
|
+
missingRtkWarningShown = false;
|
|
261
|
+
await refreshConfig(ctx);
|
|
262
|
+
maybeWarnRtkMissing(ctx);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
pi.on("session_switch", async (_event, ctx) => {
|
|
266
|
+
warnedMessages.reset();
|
|
267
|
+
suggestionNotices.reset();
|
|
268
|
+
clearTrackedBashCommands();
|
|
269
|
+
missingRtkWarningShown = false;
|
|
270
|
+
await refreshConfig(ctx);
|
|
271
|
+
maybeWarnRtkMissing(ctx);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
pi.on("agent_end", async () => {
|
|
275
|
+
clearTrackedBashCommands();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
pi.on("tool_execution_start", async (event) => {
|
|
279
|
+
if (!config.enabled || !config.outputCompaction.enabled) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const eventRecord = toRecord(event);
|
|
284
|
+
if (eventRecord.toolName !== "bash") {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
trackBashCommand(eventRecord.toolCallId, eventRecord.args);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
pi.on("tool_execution_update", async (event) => {
|
|
292
|
+
if (!config.enabled || !config.outputCompaction.enabled) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const eventRecord = toRecord(event);
|
|
297
|
+
if (eventRecord.toolName !== "bash") {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
trackBashCommand(eventRecord.toolCallId, eventRecord.args);
|
|
302
|
+
sanitizeStreamingBashExecutionResult(
|
|
303
|
+
eventRecord.partialResult,
|
|
304
|
+
getTrackedBashCommand(eventRecord.toolCallId),
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
pi.on("tool_execution_end", async (event) => {
|
|
309
|
+
const eventRecord = toRecord(event);
|
|
310
|
+
if (eventRecord.toolName !== "bash") {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
if (config.enabled && config.outputCompaction.enabled) {
|
|
316
|
+
sanitizeStreamingBashExecutionResult(eventRecord.result, getTrackedBashCommand(eventRecord.toolCallId));
|
|
317
|
+
}
|
|
318
|
+
} finally {
|
|
319
|
+
forgetTrackedBashCommand(eventRecord.toolCallId);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
324
|
+
await ensureRuntimeStatusFresh();
|
|
325
|
+
maybeWarnRtkMissing(ctx);
|
|
326
|
+
|
|
327
|
+
if (event.systemPrompt.includes(SOURCE_FILTER_TROUBLESHOOTING_NOTE)) {
|
|
328
|
+
return {};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
systemPrompt: `${event.systemPrompt}\n\n${SOURCE_FILTER_TROUBLESHOOTING_NOTE}`,
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
337
|
+
if (!config.enabled) {
|
|
338
|
+
return {};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!isToolCallEventType("bash", event)) {
|
|
342
|
+
return {};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (config.mode === "rewrite") {
|
|
346
|
+
const compatibility = applyWindowsBashCompatibilityFixes(event.input.command);
|
|
347
|
+
if (compatibility.command !== event.input.command) {
|
|
348
|
+
event.input.command = compatibility.command;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
await ensureRuntimeStatusFresh();
|
|
353
|
+
if (shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus)) {
|
|
354
|
+
return {};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const decision = computeRewriteDecision(event.input.command, config);
|
|
358
|
+
if (!decision.changed || !decision.rule) {
|
|
359
|
+
return {};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (config.mode === "rewrite") {
|
|
363
|
+
if (config.showRewriteNotifications && ctx.hasUI) {
|
|
364
|
+
ctx.ui.notify(formatRewriteNotice(decision.originalCommand, decision.rewrittenCommand), "info");
|
|
365
|
+
}
|
|
366
|
+
const safeRewrittenCommand = applyRewrittenCommandShellSafetyFixups(decision.rewrittenCommand);
|
|
367
|
+
event.input.command = applyRtkCommandEnvironment(safeRewrittenCommand);
|
|
368
|
+
return {};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (config.mode === "suggest") {
|
|
372
|
+
const suggestionKey = `${decision.rule.id}:${decision.rewrittenCommand}`;
|
|
373
|
+
if (suggestionNotices.remember(suggestionKey) && ctx.hasUI) {
|
|
374
|
+
ctx.ui.notify(`RTK suggestion: ${decision.rewrittenCommand}`, "info");
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return {};
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
382
|
+
if (!config.enabled || !config.outputCompaction.enabled) {
|
|
383
|
+
return {};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
const outcome = compactToolResult(
|
|
388
|
+
{
|
|
389
|
+
toolName: event.toolName,
|
|
390
|
+
input: event.input,
|
|
391
|
+
content: event.content,
|
|
392
|
+
},
|
|
393
|
+
config,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
if (!outcome.changed || !outcome.content) {
|
|
397
|
+
return {};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
content: outcome.content,
|
|
402
|
+
details: outcome.metadata ? mergeCompactionDetails(event.details, outcome.metadata) : undefined,
|
|
403
|
+
};
|
|
404
|
+
} catch (error) {
|
|
405
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
406
|
+
warnOnce(ctx, `${EXTENSION_NAME}: output compaction failed, using raw output (${trimMessage(message)}).`);
|
|
407
|
+
return {};
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|