pi-rtk-optimizer 0.7.0 → 0.8.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.
@@ -1,5 +1,5 @@
1
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
- import type { SettingItem } from "@mariozechner/pi-tui";
1
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import type { SettingItem } from "@earendil-works/pi-tui";
3
3
  import { toOnOff } from "./boolean-format.js";
4
4
  import { ZellijModal, ZellijSettingsModal } from "./zellij-modal.js";
5
5
  import { getRtkArgumentCompletions } from "./command-completions.js";
@@ -31,6 +31,70 @@ const TRUNCATE_MAX_CHAR_VALUES = ["4000", "8000", "12000", "20000", "50000", "10
31
31
  const SMART_TRUNCATE_LINE_VALUES = ["40", "80", "120", "160", "220", "320", "500", "1000", "2000", "4000"];
32
32
  const RTK_USAGE_TEXT =
33
33
  "Usage: /rtk [show|path|verify|stats|clear-stats|reset|help] (or run /rtk with no args to open settings modal)";
34
+ const SETTINGS_TAB_DEFINITIONS = [
35
+ {
36
+ label: "General",
37
+ settingIds: ["enabled", "mode", "showRewriteNotifications", "guardWhenRtkMissing"],
38
+ },
39
+ {
40
+ label: "Compaction",
41
+ settingIds: [
42
+ "outputCompactionEnabled",
43
+ "outputStripAnsi",
44
+ "outputAggregateTestOutput",
45
+ "outputFilterBuildOutput",
46
+ "outputCompactGitOutput",
47
+ "outputAggregateLinterOutput",
48
+ "outputGroupSearchOutput",
49
+ "outputTrackSavings",
50
+ ],
51
+ },
52
+ {
53
+ label: "Read & Source",
54
+ settingIds: [
55
+ "outputReadCompactionEnabled",
56
+ "outputSourceFilteringEnabled",
57
+ "outputSourceFiltering",
58
+ "outputPreserveExactSkillReads",
59
+ ],
60
+ },
61
+ {
62
+ label: "Truncation",
63
+ settingIds: [
64
+ "outputTruncateEnabled",
65
+ "outputTruncateMaxChars",
66
+ "outputSmartTruncate",
67
+ "outputSmartTruncateMaxLines",
68
+ ],
69
+ },
70
+ ] as const;
71
+
72
+ function buildTabbedSettingGroups(settings: SettingItem[]): Array<{ label: string; settings: SettingItem[] }> {
73
+ const byId = new Map(settings.map((setting) => [setting.id, setting]));
74
+ const assignedIds = new Set<string>();
75
+
76
+ const tabs = SETTINGS_TAB_DEFINITIONS.map(({ label, settingIds }) => ({
77
+ label,
78
+ settings: settingIds.map((id) => {
79
+ const setting = byId.get(id);
80
+ if (!setting) {
81
+ throw new Error(`Missing setting item for tab '${label}': ${id}`);
82
+ }
83
+ if (assignedIds.has(id)) {
84
+ throw new Error(`Setting item assigned to multiple tabs: ${id}`);
85
+ }
86
+ assignedIds.add(id);
87
+ return setting;
88
+ }),
89
+ }));
90
+
91
+ const unassignedIds = settings.map((setting) => setting.id).filter((id) => !assignedIds.has(id));
92
+ if (unassignedIds.length > 0) {
93
+ throw new Error(`Unassigned setting items: ${unassignedIds.join(", ")}`);
94
+ }
95
+
96
+ return tabs;
97
+ }
34
98
 
35
99
  function parseSourceFilterLevel(
36
100
  value: string,
@@ -53,12 +117,21 @@ function parseIntegerInRange(value: string, min: number, max: number): number |
53
117
  return parsed;
54
118
  }
55
119
 
56
- function summarizeConfig(config: RtkIntegrationConfig, runtimeStatus: RuntimeStatus): string {
120
+ function summarizeRuntimeStatus(runtimeStatus: RuntimeStatus): string {
57
121
  const runtime = runtimeStatus.rtkAvailable
58
122
  ? "rtk=available"
59
123
  : `rtk=missing${runtimeStatus.lastError ? ` (${runtimeStatus.lastError})` : ""}`;
124
+ const executable = runtimeStatus.rtkExecutablePath
125
+ ? `, rtkPath=${runtimeStatus.rtkExecutablePath}`
126
+ : runtimeStatus.rtkExecutableResolutionWarning
127
+ ? `, rtkPath=unresolved (${runtimeStatus.rtkExecutableResolutionWarning})`
128
+ : "";
60
129
 
61
- return `enabled=${config.enabled}, mode=${config.mode}, rewriteSource=rtk, rewriteNotice=${config.showRewriteNotifications}, compaction=${config.outputCompaction.enabled}, readCompaction=${config.outputCompaction.readCompaction.enabled}, sourceFilterEnabled=${config.outputCompaction.sourceCodeFilteringEnabled}, preserveSkillReads=${config.outputCompaction.preserveExactSkillReads}, sourceFilter=${config.outputCompaction.sourceCodeFiltering}, ${runtime}`;
130
+ return `${runtime}${executable}`;
131
+ }
132
+
133
+ function summarizeConfig(config: RtkIntegrationConfig, runtimeStatus: RuntimeStatus): string {
134
+ return `enabled=${config.enabled}, mode=${config.mode}, rewriteSource=rtk, rewriteNotice=${config.showRewriteNotifications}, compaction=${config.outputCompaction.enabled}, readCompaction=${config.outputCompaction.readCompaction.enabled}, sourceFilterEnabled=${config.outputCompaction.sourceCodeFilteringEnabled}, preserveSkillReads=${config.outputCompaction.preserveExactSkillReads}, sourceFilter=${config.outputCompaction.sourceCodeFiltering}, ${summarizeRuntimeStatus(runtimeStatus)}`;
62
135
  }
63
136
 
64
137
  function buildSettingItems(config: RtkIntegrationConfig): SettingItem[] {
@@ -391,12 +464,14 @@ async function openSettingsModal(ctx: ExtensionCommandContext, controller: RtkIn
391
464
  (tui, theme, _keybindings, done) => {
392
465
  let current = controller.getConfig();
393
466
  let settingsModal: ZellijSettingsModal | null = null;
467
+ const allSettings = buildSettingItems(current);
468
+ const tabs = buildTabbedSettingGroups(allSettings);
394
469
 
395
470
  settingsModal = new ZellijSettingsModal(
396
471
  {
397
- title: "RTK Integration Settings",
398
- description: "Bash rewrite + tool output compaction for lower token usage",
399
- settings: buildSettingItems(current),
472
+ title: "Pi RTK Optimizer",
473
+ tabs,
474
+ activeTabIndex: 0,
400
475
  onChange: (id, newValue) => {
401
476
  current = applySetting(current, id, newValue);
402
477
  controller.setConfig(current, ctx);
@@ -406,7 +481,7 @@ async function openSettingsModal(ctx: ExtensionCommandContext, controller: RtkIn
406
481
  }
407
482
  },
408
483
  onClose: () => done(),
409
- helpText: `/rtk show • /rtk verify • /rtk stats • /rtk reset • ${controller.getConfigPath()}`,
484
+ helpText: `Config: ${controller.getConfigPath()}`,
410
485
  enableSearch: true,
411
486
  },
412
487
  theme,
@@ -417,11 +492,14 @@ async function openSettingsModal(ctx: ExtensionCommandContext, controller: RtkIn
417
492
  {
418
493
  borderStyle: "rounded",
419
494
  titleBar: {
420
- left: "RTK Integration Settings",
421
- right: "pi-rtk-optimizer",
495
+ left: "Pi RTK Optimizer",
422
496
  },
423
497
  helpUndertitle: {
424
- text: "Esc: close | ↑↓: navigate | Space: toggle",
498
+ variants: [
499
+ "←/→ tabs • Type to search • Enter/Space change • Esc close",
500
+ "←/→ tabs • Type to search • Esc close",
501
+ "←/→ tabs • Esc close",
502
+ ],
425
503
  color: "dim",
426
504
  },
427
505
  overlay: overlayOptions,
@@ -474,7 +552,8 @@ async function handleArgs(
474
552
  if (normalized === "verify") {
475
553
  const runtimeStatus = await controller.refreshRuntimeStatus();
476
554
  if (runtimeStatus.rtkAvailable) {
477
- ctx.ui.notify("RTK binary is available.", "info");
555
+ const pathDetail = runtimeStatus.rtkExecutablePath ? ` at ${runtimeStatus.rtkExecutablePath}` : "";
556
+ ctx.ui.notify(`RTK binary is available${pathDetail}.`, "info");
478
557
  } else {
479
558
  ctx.ui.notify(
480
559
  `RTK binary is not available${runtimeStatus.lastError ? `: ${runtimeStatus.lastError}` : ""}.`,
package/src/constants.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
1
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
2
  import { join } from "node:path";
3
3
 
4
4
  export const EXTENSION_NAME = "pi-rtk-optimizer";
package/src/index-test.ts CHANGED
@@ -3,13 +3,13 @@ import { mock } from "bun:test";
3
3
 
4
4
  import { runTest } from "./test-helpers.ts";
5
5
 
6
- mock.module("@mariozechner/pi-coding-agent", () => ({
6
+ mock.module("@earendil-works/pi-coding-agent", () => ({
7
7
  getAgentDir: () => "/tmp/.pi/agent",
8
8
  getSettingsListTheme: () => ({}),
9
9
  isToolCallEventType: (toolName: string, event: Record<string, unknown>) => event.toolName === toolName,
10
10
  }));
11
11
 
12
- mock.module("@mariozechner/pi-tui", () => ({
12
+ mock.module("@earendil-works/pi-tui", () => ({
13
13
  Box: class {},
14
14
  Container: class {
15
15
  addChild(): void {}
@@ -28,9 +28,33 @@ mock.module("@mariozechner/pi-tui", () => ({
28
28
  visibleWidth: (text: string) => text.length,
29
29
  }));
30
30
 
31
- const { createBoundedNoticeTracker, shouldInjectSourceFilterTroubleshootingNote } = await import("./index.ts");
31
+ const indexModule = await import("./index.ts");
32
+ const { createBoundedNoticeTracker, shouldInjectSourceFilterTroubleshootingNote } = indexModule;
33
+ const rtkIntegrationExtension = indexModule.default;
32
34
  const { DEFAULT_RTK_INTEGRATION_CONFIG } = await import("./types.ts");
33
35
 
36
+ type Notification = { message: string; level: "info" | "warning" | "error" };
37
+ type ExtensionHandler = (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<Record<string, unknown> | void>;
38
+
39
+ function createNotificationContext(notifications: Notification[]): Record<string, unknown> {
40
+ return {
41
+ hasUI: true,
42
+ ui: {
43
+ notify(message: string, level: "info" | "warning" | "error") {
44
+ notifications.push({ message, level });
45
+ },
46
+ },
47
+ };
48
+ }
49
+
50
+ function firstText(content: unknown): string {
51
+ if (!Array.isArray(content) || content.length === 0) {
52
+ return "";
53
+ }
54
+ const block = content[0] as { type?: string; text?: string };
55
+ return block.type === "text" && typeof block.text === "string" ? block.text : "";
56
+ }
57
+
34
58
  function configWith(overrides: {
35
59
  enabled?: boolean;
36
60
  compactionEnabled?: boolean;
@@ -160,4 +184,175 @@ runTest("source-filter note skipped when all read filtering safeguards are disab
160
184
  );
161
185
  });
162
186
 
187
+ await runTest("session_start refreshes RTK provenance and runtime guard skips missing rewrites", async () => {
188
+ const handlers: Record<string, ExtensionHandler> = {};
189
+ const notifications: Notification[] = [];
190
+ const execCommands: string[] = [];
191
+ let rtkAvailable = false;
192
+ let rewriteCalls = 0;
193
+
194
+ rtkIntegrationExtension({
195
+ exec: async (command: string, args: string[]) => {
196
+ execCommands.push(command);
197
+ if (command === "which" || command === "where") {
198
+ return { code: 0, stdout: "/opt/rtk/bin/rtk\n", stderr: "" };
199
+ }
200
+ if (args[0] === "--version") {
201
+ return rtkAvailable
202
+ ? { code: 0, stdout: "rtk 1.0.0", stderr: "" }
203
+ : { code: 1, stdout: "", stderr: "missing rtk" };
204
+ }
205
+ if (args[0] === "rewrite") {
206
+ rewriteCalls += 1;
207
+ return { code: 3, stdout: "rtk git status", stderr: "" };
208
+ }
209
+ return { code: 1, stdout: "", stderr: "unexpected" };
210
+ },
211
+ on(eventName: string, handler: ExtensionHandler) {
212
+ handlers[eventName] = handler;
213
+ },
214
+ registerCommand() {},
215
+ } as never);
216
+
217
+ const sessionStartHandler = handlers.session_start;
218
+ const toolCallHandler = handlers.tool_call;
219
+ assert.ok(sessionStartHandler);
220
+ assert.ok(toolCallHandler);
221
+
222
+ await sessionStartHandler({}, createNotificationContext(notifications));
223
+ const skippedEvent = { toolName: "bash", input: { command: "git status" } };
224
+ await toolCallHandler(skippedEvent, createNotificationContext(notifications));
225
+
226
+ assert.equal((skippedEvent.input as { command: string }).command, "git status");
227
+ assert.equal(rewriteCalls, 0);
228
+ assert.ok(notifications.some((notice) => notice.message.includes("rtk binary unavailable")));
229
+
230
+ rtkAvailable = true;
231
+ await sessionStartHandler({}, createNotificationContext(notifications));
232
+ const rewrittenEvent = { toolName: "bash", input: { command: "git status" } };
233
+ await toolCallHandler(rewrittenEvent, createNotificationContext(notifications));
234
+
235
+ assert.equal(rewriteCalls, 1);
236
+ assert.ok((rewrittenEvent.input as { command: string }).command.includes("rtk git status"));
237
+ assert.ok(execCommands.includes("/opt/rtk/bin/rtk"));
238
+ });
239
+
240
+ await runTest("tool execution lifecycle sanitizes streamed bash output", async () => {
241
+ const handlers: Record<string, ExtensionHandler> = {};
242
+
243
+ rtkIntegrationExtension({
244
+ exec: async () => ({ code: 0, stdout: "rtk 1.0.0", stderr: "" }),
245
+ on(eventName: string, handler: ExtensionHandler) {
246
+ handlers[eventName] = handler;
247
+ },
248
+ registerCommand() {},
249
+ } as never);
250
+
251
+ const startHandler = handlers.tool_execution_start;
252
+ const updateHandler = handlers.tool_execution_update;
253
+ const endHandler = handlers.tool_execution_end;
254
+ assert.ok(startHandler);
255
+ assert.ok(updateHandler);
256
+ assert.ok(endHandler);
257
+
258
+ await startHandler(
259
+ { toolName: "bash", toolCallId: "bash-1", args: { command: "rtk git status" } },
260
+ {},
261
+ );
262
+ const updateEvent = {
263
+ toolName: "bash",
264
+ toolCallId: "bash-1",
265
+ args: { command: "rtk git status" },
266
+ partialResult: {
267
+ content: [
268
+ {
269
+ type: "text",
270
+ text: "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n\nworking tree clean\n",
271
+ },
272
+ ],
273
+ },
274
+ };
275
+ await updateHandler(updateEvent, {});
276
+ assert.equal(firstText(updateEvent.partialResult.content), "working tree clean\n");
277
+
278
+ const endEvent = {
279
+ toolName: "bash",
280
+ toolCallId: "bash-1",
281
+ result: { content: [{ type: "text", text: "📄 src/file.ts\n✅ Files are identical\n" }] },
282
+ };
283
+ await endHandler(endEvent, {});
284
+ assert.equal(firstText(endEvent.result.content), "> src/file.ts\n[OK] Files are identical\n");
285
+ });
286
+
287
+ await runTest("tool_result lifecycle merges compaction metadata with existing details", async () => {
288
+ const handlers: Record<string, ExtensionHandler> = {};
289
+ const notifications: Notification[] = [];
290
+
291
+ rtkIntegrationExtension({
292
+ exec: async () => ({ code: 0, stdout: "rtk 1.0.0", stderr: "" }),
293
+ on(eventName: string, handler: ExtensionHandler) {
294
+ handlers[eventName] = handler;
295
+ },
296
+ registerCommand() {},
297
+ } as never);
298
+
299
+ const toolResultHandler = handlers.tool_result;
300
+ assert.ok(toolResultHandler);
301
+ const result = await toolResultHandler(
302
+ {
303
+ toolName: "grep",
304
+ input: { pattern: "TODO" },
305
+ content: [{ type: "text", text: "src/a.ts:1:TODO\nsrc/b.ts:2:TODO\n" }],
306
+ details: { metadata: { requestId: "abc" }, traceId: "trace-1" },
307
+ },
308
+ createNotificationContext(notifications),
309
+ );
310
+
311
+ assert.ok(result);
312
+ assert.ok(firstText(result.content).startsWith("2 matches in 2 files:"));
313
+ assert.equal((result.details as { traceId?: string }).traceId, "trace-1");
314
+ const details = result.details as { rtkCompaction?: { applied: boolean }; metadata?: Record<string, unknown> };
315
+ assert.equal(details.rtkCompaction?.applied, true);
316
+ assert.deepEqual(details.metadata?.requestId, "abc");
317
+ assert.equal((details.metadata?.rtkCompaction as { applied?: boolean } | undefined)?.applied, true);
318
+ assert.equal(notifications.length, 0);
319
+ });
320
+
321
+ await runTest("tool_call surfaces RTK rewrite errors through existing UI warning path", async () => {
322
+ const handlers: Record<string, (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<Record<string, unknown> | void>> = {};
323
+ const notifications: Notification[] = [];
324
+
325
+ rtkIntegrationExtension({
326
+ exec: async (_command: string, args: string[]) => {
327
+ if (args[0] === "--version") {
328
+ return { code: 0, stdout: "rtk 1.0.0", stderr: "" };
329
+ }
330
+
331
+ return { code: 2, stdout: "", stderr: "denied unsafe rewrite" };
332
+ },
333
+ on(eventName: string, handler: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<Record<string, unknown> | void>) {
334
+ handlers[eventName] = handler;
335
+ },
336
+ registerCommand() {},
337
+ } as never);
338
+
339
+ const toolCallHandler = handlers.tool_call;
340
+ assert.ok(toolCallHandler);
341
+ const event = { toolName: "bash", input: { command: "git status" } };
342
+ await toolCallHandler(event, {
343
+ hasUI: true,
344
+ ui: {
345
+ notify(message: string, level: "info" | "warning" | "error") {
346
+ notifications.push({ message, level });
347
+ },
348
+ },
349
+ });
350
+
351
+ assert.equal((event.input as { command: string }).command, "git status");
352
+ assert.equal(notifications.length, 1);
353
+ assert.equal(notifications[0]?.level, "warning");
354
+ assert.ok(notifications[0]?.message.includes("rtk rewrite skipped"));
355
+ assert.ok(notifications[0]?.message.includes("denied unsafe rewrite"));
356
+ });
357
+
163
358
  console.log("All index tests passed.");
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { isToolCallEventType, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import { isToolCallEventType, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
3
  ensureConfigExists,
4
4
  getRtkIntegrationConfigPath,
@@ -13,6 +13,7 @@ import { clearOutputMetrics, getOutputMetricsSummary } from "./output-metrics.js
13
13
  import { compactToolResult, type ToolResultCompactionMetadata } from "./output-compactor.js";
14
14
  import { toRecord } from "./record-utils.js";
15
15
  import { applyRtkCommandEnvironment } from "./rtk-command-environment.js";
16
+ import { resolveRtkExecutable, type RtkExecutableResolution } from "./rtk-executable-resolver.js";
16
17
  import { applyRewrittenCommandShellSafetyFixups } from "./rewrite-pipeline-safety.js";
17
18
  import { shouldRequireRtkAvailabilityForCommandHandling, shouldSkipCommandHandlingWhenRtkMissing } from "./runtime-guard.js";
18
19
  import { sanitizeStreamingBashExecutionResult } from "./tool-execution-sanitizer.js";
@@ -115,6 +116,12 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
115
116
  return `RTK rewrite: ${original} -> ${rewritten}`;
116
117
  };
117
118
 
119
+ const formatRewriteWarning = (command: string, warning: string): string => {
120
+ const target = trimMessage(command, 100);
121
+ const detail = trimMessage(warning, 120);
122
+ return `${EXTENSION_NAME}: rtk rewrite skipped for '${target}' (${detail}).`;
123
+ };
124
+
118
125
  const warnOnce = (
119
126
  ctx: ExtensionContext | ExtensionCommandContext,
120
127
  message: string,
@@ -190,12 +197,18 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
190
197
  };
191
198
 
192
199
  const refreshRuntimeStatus = async (): Promise<RuntimeStatus> => {
200
+ let executableResolution: RtkExecutableResolution | undefined;
193
201
  try {
194
- const result = await pi.exec("rtk", ["--version"], { timeout: 5000 });
202
+ executableResolution = await resolveRtkExecutable(pi);
203
+ const result = await pi.exec(executableResolution.command, ["--version"], { timeout: 5000 });
195
204
  if (result.code === 0) {
196
205
  runtimeStatus = {
197
206
  rtkAvailable: true,
198
207
  lastCheckedAt: Date.now(),
208
+ rtkExecutablePath: executableResolution.resolvedPath,
209
+ rtkExecutableCommand: executableResolution.command,
210
+ rtkExecutableResolver: executableResolution.resolver,
211
+ rtkExecutableResolutionWarning: executableResolution.warning,
199
212
  };
200
213
  missingRtkWarningShown = false;
201
214
  return runtimeStatus;
@@ -208,6 +221,10 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
208
221
  rtkAvailable: false,
209
222
  lastCheckedAt: Date.now(),
210
223
  lastError: detail || `exit ${result.code}`,
224
+ rtkExecutablePath: executableResolution.resolvedPath,
225
+ rtkExecutableCommand: executableResolution.command,
226
+ rtkExecutableResolver: executableResolution.resolver,
227
+ rtkExecutableResolutionWarning: executableResolution.warning,
211
228
  };
212
229
  return runtimeStatus;
213
230
  } catch (error) {
@@ -216,6 +233,10 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
216
233
  rtkAvailable: false,
217
234
  lastCheckedAt: Date.now(),
218
235
  lastError: trimMessage(message),
236
+ rtkExecutablePath: executableResolution?.resolvedPath,
237
+ rtkExecutableCommand: executableResolution?.command,
238
+ rtkExecutableResolver: executableResolution?.resolver,
239
+ rtkExecutableResolutionWarning: executableResolution?.warning,
219
240
  };
220
241
  return runtimeStatus;
221
242
  }
@@ -303,10 +324,13 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
303
324
  }
304
325
 
305
326
  trackBashCommand(eventRecord.toolCallId, eventRecord.args);
306
- sanitizeStreamingBashExecutionResult(
327
+ const sanitization = sanitizeStreamingBashExecutionResult(
307
328
  eventRecord.partialResult,
308
329
  getTrackedBashCommand(eventRecord.toolCallId),
309
330
  );
331
+ if (sanitization.changed) {
332
+ eventRecord.partialResult = sanitization.result;
333
+ }
310
334
  });
311
335
 
312
336
  pi.on("tool_execution_end", async (event) => {
@@ -317,7 +341,13 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
317
341
 
318
342
  try {
319
343
  if (config.enabled && config.outputCompaction.enabled) {
320
- sanitizeStreamingBashExecutionResult(eventRecord.result, getTrackedBashCommand(eventRecord.toolCallId));
344
+ const sanitization = sanitizeStreamingBashExecutionResult(
345
+ eventRecord.result,
346
+ getTrackedBashCommand(eventRecord.toolCallId),
347
+ );
348
+ if (sanitization.changed) {
349
+ eventRecord.result = sanitization.result;
350
+ }
321
351
  }
322
352
  } finally {
323
353
  forgetTrackedBashCommand(eventRecord.toolCallId);
@@ -362,8 +392,22 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
362
392
  return {};
363
393
  }
364
394
 
365
- const decision = await computeRewriteDecision(event.input.command, config, pi);
395
+ let executableResolution: RtkExecutableResolution | undefined;
396
+ if (runtimeStatus.rtkExecutableCommand) {
397
+ const resolver: RtkExecutableResolution["resolver"] =
398
+ runtimeStatus.rtkExecutableResolver === "where" ? "where" : "which";
399
+ executableResolution = {
400
+ command: runtimeStatus.rtkExecutableCommand,
401
+ resolvedPath: runtimeStatus.rtkExecutablePath,
402
+ resolver,
403
+ warning: runtimeStatus.rtkExecutableResolutionWarning,
404
+ };
405
+ }
406
+ const decision = await computeRewriteDecision(event.input.command, config, pi, { executableResolution });
366
407
  if (!decision.changed) {
408
+ if (decision.warning) {
409
+ warnOnce(ctx, formatRewriteWarning(decision.originalCommand, decision.warning));
410
+ }
367
411
  return {};
368
412
  }
369
413