mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 18:55:22 +00:00
feat: 接入 Pi 内置 codemode,并修复全面审查发现的问题
- codemode:Tool Search 常驻(alwaysEnabled)、权限 allow(嵌套调用仍逐个过权限门)、 tools.jsonc 登记使用规则;RTK 跳过 parentToolCallId 嵌套调用,避免丢失 bash structuredContent;Tool Search 目录过滤 hidden / codemode(script-only)exposure; 扩展加载测试加载 builtin:codemode 并断言其常驻激活 - 搜索 key 不再经命令行参数传递:search-auth.mjs 改为 --set-stdin(先读校验再加锁), configure.sh 交互输入经管道、非交互用 --<provider> - - CodeGraph 客户端监听子进程 stdin error,避免 EPIPE 未处理导致崩溃 - 移除 PI_TOOL_SEARCH/HERMES_MEMORY/RTK_CONFIG_PATH 环境变量入口,测试改用工厂 configPath 注入 - pi-subagents 固定 vitest / coverage-istanbul 4.1.10,修复 npm ls 版本冲突 - 补 pi-web-search / pi-worktree UPSTREAM.md 与 pi-minimal-footer AGENTS.md, files 白名单补齐并加测试;.codegraph/.gitignore 移出跟踪 - detect_kitty 优先识别官方安装目录 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
56cbc83296
commit
f9a7f743dd
@@ -5,6 +5,7 @@
|
||||
- RTK 默认只压缩非搜索输出,包括本地 `bash` 与远端 `ssh_bash` 的 ANSI 清理、测试聚合、构建过滤、Git 压缩、Lint 聚合;兜底截断默认关闭,新结果的展示截断由 pi-overflow 统一负责;`ssh_bash` 只复用输出处理,远端命令不得进入 RTK rewrite。
|
||||
- RTK 命令改写默认关闭(`commandRewritingEnabled` 默认 `false`);默认安装不依赖系统 `rtk` CLI,只有用户主动开启命令改写时才需要 `rtk rewrite`,其命令支持策略由已安装的 `rtk rewrite` 决定,不在扩展内重复维护规则。
|
||||
- FFF override 独占搜索工具与搜索输出;RTK 不得处理本地或远端搜索工具的调用/结果(含 `grep` 工具结果),也不得通过默认命令改写接管 `rg`、`grep`、`find`、`fd`。
|
||||
- RTK 不处理带 `parentToolCallId` 的嵌套调用(codemode 脚本内调用),否则会丢失 bash 的 structuredContent。
|
||||
- `readCompaction.enabled`、`sourceCodeFilteringEnabled` 和 `smartTruncate.enabled` 当前均默认 `false`,源码读取保持原样;未经用户明确决定,不因节省上下文而改变这些默认值。若以后开启 read 压缩,优先考虑 `readCompaction + smartTruncate`,源码过滤仍独立评估;必须保留精确 `offset/limit` 读取、短文件和行锚点的完整性。
|
||||
- read 压缩属于有损能力。任何默认值调整都必须同时说明哪些内容可能被省略、精确读取如何恢复原文,以及对 `edit` 文本匹配和排障证据的影响。
|
||||
- 工具输出压缩可能损失证据。排障和审计相关改动应优先保证原始输出可恢复,并覆盖锚点完整性和截断边界。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
import { resolveConfigPath } from "./constants.js";
|
||||
import { CONFIG_PATH } from "./constants.js";
|
||||
import { toRecord } from "./record-utils.js";
|
||||
import {
|
||||
DEFAULT_RTK_INTEGRATION_CONFIG,
|
||||
@@ -151,7 +151,7 @@ export function normalizeRtkIntegrationConfig(raw: unknown): RtkIntegrationConfi
|
||||
};
|
||||
}
|
||||
|
||||
export function ensureConfigExists(configPath = resolveConfigPath()): EnsureConfigResult {
|
||||
export function ensureConfigExists(configPath = CONFIG_PATH): EnsureConfigResult {
|
||||
if (existsSync(configPath)) {
|
||||
return { created: false };
|
||||
}
|
||||
@@ -169,7 +169,7 @@ export function ensureConfigExists(configPath = resolveConfigPath()): EnsureConf
|
||||
}
|
||||
}
|
||||
|
||||
export function loadRtkIntegrationConfig(configPath = resolveConfigPath()): ConfigLoadResult {
|
||||
export function loadRtkIntegrationConfig(configPath = CONFIG_PATH): ConfigLoadResult {
|
||||
if (!existsSync(configPath)) {
|
||||
return { config: structuredClone(DEFAULT_RTK_INTEGRATION_CONFIG) };
|
||||
}
|
||||
@@ -189,7 +189,7 @@ export function loadRtkIntegrationConfig(configPath = resolveConfigPath()): Conf
|
||||
|
||||
export function saveRtkIntegrationConfig(
|
||||
config: RtkIntegrationConfig,
|
||||
configPath = resolveConfigPath(),
|
||||
configPath = CONFIG_PATH,
|
||||
): ConfigSaveResult {
|
||||
const normalized = normalizeRtkIntegrationConfig(config);
|
||||
const tmpPath = `${configPath}.tmp`;
|
||||
@@ -218,6 +218,6 @@ export function saveRtkIntegrationConfig(
|
||||
}
|
||||
}
|
||||
|
||||
export function getRtkIntegrationConfigPath(configPath = resolveConfigPath()): string {
|
||||
export function getRtkIntegrationConfigPath(configPath = CONFIG_PATH): string {
|
||||
return configPath;
|
||||
}
|
||||
|
||||
@@ -9,11 +9,3 @@ export const EXTENSION_NAME = "pi-rtk-optimizer";
|
||||
export const CONFIG_PATH = fileURLToPath(
|
||||
new URL("../../config/rtk-optimizer.json", import.meta.url),
|
||||
);
|
||||
|
||||
/**
|
||||
* Resolve the effective config path. `PI_RTK_CONFIG_PATH` relocates the file
|
||||
* (tests point it at a temp file; it is never a persisted config value).
|
||||
*/
|
||||
export function resolveConfigPath(): string {
|
||||
return process.env.PI_RTK_CONFIG_PATH ?? CONFIG_PATH;
|
||||
}
|
||||
|
||||
@@ -36,16 +36,15 @@ mock.module("@earendil-works/pi-tui", {
|
||||
|
||||
const indexModule = await import("./index.ts");
|
||||
const { createBoundedNoticeTracker, shouldInjectSourceFilterTroubleshootingNote } = indexModule;
|
||||
const rtkIntegrationExtension = indexModule.default;
|
||||
const { DEFAULT_RTK_INTEGRATION_CONFIG } = await import("./types.ts");
|
||||
|
||||
// Tests must never write the bundle's config/ file: relocate the config to a
|
||||
// throwaway temp file before any config-store call resolves its path.
|
||||
// throwaway temp file via the extension factory's configPath option.
|
||||
const TEST_CONFIG_PATH = join(
|
||||
mkdtempSync(join(tmpdir(), "my-pi-rtk-test-")),
|
||||
"rtk-optimizer.json",
|
||||
);
|
||||
process.env.PI_RTK_CONFIG_PATH = TEST_CONFIG_PATH;
|
||||
const rtkIntegrationExtension = indexModule.createRtkOptimizerExtension({ configPath: TEST_CONFIG_PATH });
|
||||
|
||||
function writeTestConfig(commandRewritingEnabled: boolean): void {
|
||||
writeFileSync(
|
||||
@@ -401,6 +400,69 @@ await runTest("tool_result lifecycle merges compaction metadata with existing de
|
||||
assert.equal(notifications.length, 0);
|
||||
});
|
||||
|
||||
await runTest("nested tool calls and results (parentToolCallId) are left untouched", async () => {
|
||||
writeTestConfig(true);
|
||||
const handlers: Record<string, ExtensionHandler> = {};
|
||||
const notifications: Notification[] = [];
|
||||
let rewriteCalls = 0;
|
||||
|
||||
rtkIntegrationExtension({
|
||||
exec: async (_command: string, args: string[]) => {
|
||||
if (args[0] === "--version") {
|
||||
return { code: 0, stdout: "rtk 1.0.0", stderr: "" };
|
||||
}
|
||||
if (args[0] === "rewrite") {
|
||||
rewriteCalls += 1;
|
||||
return { code: 3, stdout: "rtk git status", stderr: "" };
|
||||
}
|
||||
return { code: 1, stdout: "", stderr: "unexpected" };
|
||||
},
|
||||
on(eventName: string, handler: ExtensionHandler) {
|
||||
handlers[eventName] = handler;
|
||||
},
|
||||
registerCommand() {},
|
||||
} as never);
|
||||
|
||||
const toolCallHandler = handlers.tool_call;
|
||||
const toolResultHandler = handlers.tool_result;
|
||||
assert.ok(toolCallHandler);
|
||||
assert.ok(toolResultHandler);
|
||||
|
||||
// A codemode script's nested bash result must survive intact: rewriting
|
||||
// content without structuredContent would drop bash's outputSchema payload.
|
||||
const nestedResult = await toolResultHandler(
|
||||
{
|
||||
toolName: "bash",
|
||||
parentToolCallId: "codemode-1",
|
||||
input: { command: "printf TODO" },
|
||||
content: [{ type: "text", text: "\x1B[31msrc/a.ts\n 1: TODO\x1B[0m\n" }],
|
||||
},
|
||||
createNotificationContext(notifications),
|
||||
);
|
||||
assert.ok(!nestedResult || !("content" in nestedResult));
|
||||
|
||||
const nestedCall = {
|
||||
toolName: "bash",
|
||||
parentToolCallId: "codemode-1",
|
||||
input: { command: "git status" },
|
||||
};
|
||||
await toolCallHandler(nestedCall, createNotificationContext(notifications));
|
||||
assert.equal(nestedCall.input.command, "git status");
|
||||
assert.equal(rewriteCalls, 0);
|
||||
|
||||
const directResult = await toolResultHandler(
|
||||
{
|
||||
toolName: "bash",
|
||||
input: { command: "printf TODO" },
|
||||
content: [{ type: "text", text: "\x1B[31msrc/a.ts\n 1: TODO\x1B[0m\n" }],
|
||||
},
|
||||
createNotificationContext(notifications),
|
||||
);
|
||||
assert.ok(directResult && "content" in directResult);
|
||||
assert.equal(firstText(directResult.content), "src/a.ts\n 1: TODO\n");
|
||||
writeTestConfig(false);
|
||||
});
|
||||
|
||||
await runTest("tool_call surfaces RTK rewrite errors through existing UI warning path", async () => {
|
||||
writeTestConfig(true);
|
||||
const handlers: Record<string, (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<Record<string, unknown> | void>> = {};
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "./config-store.js";
|
||||
import { computeRewriteDecision } from "./command-rewriter.js";
|
||||
import { registerRtkIntegrationCommand } from "./command-register.js";
|
||||
import { EXTENSION_NAME } from "./constants.js";
|
||||
import { CONFIG_PATH, EXTENSION_NAME } from "./constants.js";
|
||||
import { createLazyModuleLoader } from "./lazy-module-loader.js";
|
||||
import { clearOutputMetrics, getOutputMetricsSummary } from "./output-metrics.js";
|
||||
import type { ToolResultCompactionMetadata } from "./output-compactor.js";
|
||||
@@ -103,8 +103,12 @@ export function createBoundedNoticeTracker(maxEntries: number): BoundedNoticeTra
|
||||
};
|
||||
}
|
||||
|
||||
export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
|
||||
const initialLoad = loadRtkIntegrationConfig();
|
||||
export interface RtkOptimizerExtensionOptions {
|
||||
configPath?: string;
|
||||
}
|
||||
|
||||
function rtkIntegrationExtension(pi: ExtensionAPI, configPath: string): void {
|
||||
const initialLoad = loadRtkIntegrationConfig(configPath);
|
||||
let config: RtkIntegrationConfig = initialLoad.config;
|
||||
if (!config.enabled) {
|
||||
return;
|
||||
@@ -200,12 +204,12 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
|
||||
};
|
||||
|
||||
const refreshConfig = async (ctx?: ExtensionContext | ExtensionCommandContext): Promise<void> => {
|
||||
const ensured = ensureConfigExists();
|
||||
const ensured = ensureConfigExists(configPath);
|
||||
if (ensured.error && ctx) {
|
||||
warnOnce(ctx, ensured.error);
|
||||
}
|
||||
|
||||
const loaded = loadRtkIntegrationConfig();
|
||||
const loaded = loadRtkIntegrationConfig(configPath);
|
||||
config = loaded.config;
|
||||
pendingLoadWarning = loaded.warning;
|
||||
await refreshRuntimeStatus();
|
||||
@@ -218,7 +222,7 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
|
||||
|
||||
const setConfig = (next: RtkIntegrationConfig, ctx: ExtensionCommandContext): void => {
|
||||
config = normalizeRtkIntegrationConfig(next);
|
||||
const saved = saveRtkIntegrationConfig(config);
|
||||
const saved = saveRtkIntegrationConfig(config, configPath);
|
||||
if (!saved.success && saved.error) {
|
||||
ctx.ui.notify(saved.error, "error");
|
||||
}
|
||||
@@ -310,7 +314,7 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
|
||||
const controller = {
|
||||
getConfig: () => config,
|
||||
setConfig,
|
||||
getConfigPath: getRtkIntegrationConfigPath,
|
||||
getConfigPath: () => getRtkIntegrationConfigPath(configPath),
|
||||
getRuntimeStatus: () => runtimeStatus,
|
||||
refreshRuntimeStatus,
|
||||
getMetricsSummary: getOutputMetricsSummary,
|
||||
@@ -396,6 +400,10 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
|
||||
});
|
||||
|
||||
pi.on("tool_call", async (event, ctx) => {
|
||||
if (event.parentToolCallId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!config.enabled || !config.commandRewritingEnabled) {
|
||||
return {};
|
||||
}
|
||||
@@ -455,6 +463,10 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
|
||||
});
|
||||
|
||||
pi.on("tool_result", async (event, ctx) => {
|
||||
if (event.parentToolCallId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!config.enabled || !config.outputCompaction.enabled) {
|
||||
return {};
|
||||
}
|
||||
@@ -485,3 +497,12 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function createRtkOptimizerExtension(
|
||||
options?: RtkOptimizerExtensionOptions,
|
||||
): (pi: ExtensionAPI) => void {
|
||||
const configPath = options?.configPath ?? CONFIG_PATH;
|
||||
return (pi) => rtkIntegrationExtension(pi, configPath);
|
||||
}
|
||||
|
||||
export default createRtkOptimizerExtension();
|
||||
|
||||
+1
@@ -76,6 +76,7 @@ declare module "@earendil-works/pi-coding-agent" {
|
||||
input: Record<string, unknown>;
|
||||
content: Array<Record<string, unknown>>;
|
||||
details?: unknown;
|
||||
parentToolCallId?: string;
|
||||
}
|
||||
|
||||
export interface BashToolCallEvent {
|
||||
|
||||
Reference in New Issue
Block a user