mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45: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
@@ -3,7 +3,7 @@
|
||||
- 扩展入口是 `pi-tool-search/extensions/index.ts`,组合包权威分组位于 `extensions/bundle-groups.ts`,目录生成/校验/缓存位于 `extensions/catalog.ts`,配置读取与路径解析位于 `extensions/config.ts`(直接读取组合包 `config/tool-search.json`;目录缓存仍在 `<agentDir>/my-pi/tool-search/catalog-v1.json`),测试保留在 `test/`。
|
||||
- 同步上游必须记录明确 tag/commit,保留 README、CHANGELOG、文档、作者和许可证说明;不得导入上游 `.pi`、嵌套 `.git`、`node_modules` 或构建产物。
|
||||
- 根包必须通过 `file:./pi-tool-search` 提供本地依赖并直接加载仓库源码,不得同时安装或加载 npm `pi-tool-search` 实现;同步上游时先核对本地增量动态工具改造,禁止用旧 npm 入口或上游旧宿主实现直接覆盖。
|
||||
- 默认常驻核心 `read` / `write` / `edit` / `bash` / `grep` / `find`、`codegraph_explore`、`lsp_diagnostics`、六个记忆工具与 `context_tree_query`、`overflow_query`,以及 `tool_search`;Windows 上内置 `powershell` 必须与 `bash` 同属固定本地 Shell 核心工具,非 Windows 平台必须从目录中过滤。配置从已安装组合包内的 `config/tool-search.json` 直接读取,文件缺失或无效时回退 `BUNDLE_TOOL_SEARCH_DEFAULTS`;扩展自身不播种、不迁移,也不再读写 `settings.json` 的 `toolSearch` 段。
|
||||
- 默认常驻核心 `read` / `write` / `edit` / `bash` / `grep` / `find`、`codegraph_explore`、`lsp_diagnostics`、六个记忆工具与 `context_tree_query`、`overflow_query`、内置 `codemode`(exposure `model-only`),以及 `tool_search`;Windows 上内置 `powershell` 必须与 `bash` 同属固定本地 Shell 核心工具,非 Windows 平台必须从目录中过滤。exposure 为 `hidden` 或 `codemode`(script-only、模型不可直达)的工具一律不得进入目录或常驻集合。配置从已安装组合包内的 `config/tool-search.json` 直接读取,文件缺失或无效时回退 `BUNDLE_TOOL_SEARCH_DEFAULTS`;扩展自身不播种、不迁移,也不再读写 `settings.json` 的 `toolSearch` 段。
|
||||
- 其余组合包工具必须优先使用 `extensions/bundle-groups.ts` 中提交的权威工作流分组;精确已有分组必须直接激活,不得等待无关未知工具的模型增强。默认最多 5 个动态组、模型组最多 8 个工具、动态工具总数最多 28;`groups` 可原子激活最多 5 个精确组,完整请求超限时不得改变当前状态;加载或组内调用更新 LRU,超限先移除最久未用的非请求组。自然语言查询必须优先精确工具名和已提交 workflow alias,弱匹配或近似同分只能返回候选,不得任意激活;`/tool-search-status` 必须显示当前容量、LRU 顺序与最近淘汰原因。
|
||||
- 标准组合包工具全部命中预置目录时不得调用模型、创建用户缓存或发送隐藏 schema。只有存在未识别第三方工具时才允许惰性模型增强;生成结果必须保留全部预置 assignment,模型元数据不得替代真实名称、参数或 schema。缓存必须位于 agent 目录且权限 `600`;模型失败必须继续使用预置目录 + 确定性未知工具分组。`groupOverrides` 优先级高于预置分组。
|
||||
- 必须直接使用当前 `@earendil-works/pi-coding-agent`、`typebox`、`ModelRegistry.complete()` 和 `setActiveTools()`;首次纯增加载使用当前 Pi 的增量结果传播,LRU 替换允许走宿主安全 fallback。不得安装或映射旧 `@mariozechner` runtime,不得恢复 provider payload 改写、代理分发或隐藏 `sendMessage` steer/retry 兼容循环。Tool Search 是唯一可调用 `getActiveTools()` / `setActiveTools()` 管理工具显隐的组合包扩展;`pi-permission-system` 只能在调用时裁决。
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
## Unreleased — my-pi maintained source
|
||||
|
||||
### Changed
|
||||
- Pinned Pi's built-in `codemode` (exposure `model-only`) in `alwaysEnabled` so batching several tool calls through one script never depends on a `tool_search` activation; `platformToolPolicy` now also drops tools with `hidden` or `codemode` (script-only) exposure so they can never enter the catalog or the unknown-tools path.
|
||||
- Removed the `PI_TOOL_SEARCH_CONFIG_PATH` environment override; the extension reads the bundle `config/tool-search.json`, and tests inject a path through the `createToolSearchExtension({ configPath })` factory.
|
||||
- Removed the retired context-mode integration: the `ctx_execute` / `ctx_execute_file` / `ctx_batch_execute` pins left `alwaysEnabled` and the four `context-*` bundle groups were dropped. Large tool results are now bounded by pi-overflow and recoverable through the pinned `overflow_query`.
|
||||
- Imported upstream `v0.3.6` at `ddfb23646fd3957b791214de278e23aa393c9b13` for direct local maintenance.
|
||||
- Migrated runtime imports to `@earendil-works/pi-coding-agent` and current `typebox`.
|
||||
|
||||
@@ -10,7 +10,7 @@ Generated metadata never replaces executable schemas. Exact names, parameter typ
|
||||
|
||||
## Catalog lifecycle
|
||||
|
||||
1. `session_start` keeps `tool_search`, Pi core tools, and configured `alwaysEnabled` names active. PowerShell is filtered out on non-Windows hosts; on Windows it is pinned beside Bash as the same local-shell capability class.
|
||||
1. `session_start` keeps `tool_search`, Pi core tools, and configured `alwaysEnabled` names active. PowerShell is filtered out on non-Windows hosts; on Windows it is pinned beside Bash as the same local-shell capability class. Tools whose exposure is `hidden` or `codemode` (script-only, unreachable by the model directly) are excluded entirely; `model-only` tools such as the built-in `codemode` stay reachable and can be pinned.
|
||||
2. Hidden tool definitions are hashed with the grouping constraints.
|
||||
3. Every tool exposed by the standard `my-pi` bundle is matched against the checked-in catalog in `extensions/bundle-groups.ts`. If all tools are recognized, no model call and no user cache are needed.
|
||||
4. User `groupOverrides` take priority over checked-in assignments. Optional or unavailable bundle tools are simply filtered out of their predefined groups.
|
||||
@@ -41,7 +41,7 @@ Run `/tool-search-status` to inspect the current catalog source, pinned count, d
|
||||
| `chrome-interaction` | Chrome click/type/fill/key/hover/drag/tap/scroll input |
|
||||
| `chrome-debugging` | Chrome page evaluation, console/network diagnostics, response retrieval, and file upload |
|
||||
|
||||
Pinned tools are removed from these dynamic groups at runtime. The defaults pin `codegraph_explore`, `lsp_diagnostics`, and all six Hermes memory tools, so none of them consume dynamic group or tool capacity.
|
||||
Pinned tools are removed from these dynamic groups at runtime. The defaults pin `codegraph_explore`, `lsp_diagnostics`, all six Hermes memory tools, `context_tree_query`, `overflow_query`, and `codemode`, so none of them consume dynamic group or tool capacity.
|
||||
|
||||
## Loading and eviction
|
||||
|
||||
@@ -81,7 +81,8 @@ Pi reads this configuration directly from the installed bundle's `config/tool-se
|
||||
"memory_remove",
|
||||
"skill_manage",
|
||||
"context_tree_query",
|
||||
"overflow_query"
|
||||
"overflow_query",
|
||||
"codemode"
|
||||
],
|
||||
"showToolSearchFooterStatus": false,
|
||||
"maxActiveGroups": 5,
|
||||
@@ -93,7 +94,7 @@ Pi reads this configuration directly from the installed bundle's `config/tool-se
|
||||
|
||||
| Key | Bundle default | Description |
|
||||
|---|---|---|
|
||||
| `alwaysEnabled` | CodeGraph explore, LSP diagnostics, all six Hermes memory tools, `context_tree_query`, `overflow_query` | Exact names pinned alongside Pi core tools |
|
||||
| `alwaysEnabled` | CodeGraph explore, LSP diagnostics, all six Hermes memory tools, `context_tree_query`, `overflow_query`, `codemode` | Exact names pinned alongside Pi core tools |
|
||||
| `showToolSearchFooterStatus` | `false` | Show active tool/group counts |
|
||||
| `maxActiveGroups` | `5` | Maximum simultaneously active dynamic groups |
|
||||
| `maxToolsPerGroup` | `8` | Maximum size accepted from model-generated groups |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
`pi-tool-search` keeps full low-frequency schemas hidden behind a compact, validated group catalog.
|
||||
|
||||
1. `session_start` activates `tool_search`, the six cross-platform Pi core tools, and configured `alwaysEnabled` names. The my-pi defaults also pin CodeGraph exploration, LSP diagnostics, and the six Hermes memory tools. On Windows, `powershell` is pinned beside `bash`; on non-Windows hosts it is excluded. Pinned tools do not consume the dynamic-group or dynamic-tool budget.
|
||||
1. `session_start` activates `tool_search`, the six cross-platform Pi core tools, and configured `alwaysEnabled` names. The my-pi defaults also pin CodeGraph exploration, LSP diagnostics, the six Hermes memory tools, and Pi's built-in `codemode` (exposure `model-only`). On Windows, `powershell` is pinned beside `bash`; on non-Windows hosts it is excluded. Tools with `hidden` or `codemode` (script-only) exposure are filtered out before catalog and pin processing. Pinned tools do not consume the dynamic-group or dynamic-tool budget.
|
||||
2. Hidden definitions are hashed with grouping constraints and matched against `extensions/bundle-groups.ts`. Available standard tools receive checked-in groups; unavailable optional tools are omitted.
|
||||
3. If every hidden tool is recognized, the bundle catalog is used directly. No model call or per-user cache is required.
|
||||
4. Additional tools receive immediate deterministic prefix/source groups. A matching model-enriched cache is reused only if it preserves all checked-in bundle assignments.
|
||||
|
||||
@@ -11,8 +11,6 @@ const TOOL_SEARCH_DIR = join("my-pi", "tool-search");
|
||||
* agent directory.
|
||||
*/
|
||||
export function getToolSearchConfigPath(): string {
|
||||
const override = process.env.PI_TOOL_SEARCH_CONFIG_PATH;
|
||||
if (override) return override;
|
||||
return fileURLToPath(new URL("../../config/tool-search.json", import.meta.url));
|
||||
}
|
||||
|
||||
@@ -33,6 +31,7 @@ export const BUNDLE_TOOL_SEARCH_DEFAULTS = {
|
||||
"skill_manage",
|
||||
"context_tree_query",
|
||||
"overflow_query",
|
||||
"codemode",
|
||||
],
|
||||
showToolSearchFooterStatus: false,
|
||||
maxActiveGroups: 5,
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
createBundleCatalog,
|
||||
type BundleCatalogResult,
|
||||
} from "./bundle-groups.ts";
|
||||
import { getToolSearchCatalogPath, readToolSearchConfig } from "./config.ts";
|
||||
import { getToolSearchCatalogPath, getToolSearchConfigPath, readToolSearchConfig } from "./config.ts";
|
||||
|
||||
const TOOL_SEARCH_NAME = "tool_search";
|
||||
const POWERSHELL_TOOL_NAME = "powershell";
|
||||
@@ -39,13 +39,19 @@ type CatalogSource = "bundle" | "cache" | "fallback" | "hybrid" | "model";
|
||||
type ModelUsage = Awaited<ReturnType<ExtensionContext["modelRegistry"]["complete"]>>["usage"];
|
||||
type CatalogGenerationResult = { notice?: string; usage?: ModelUsage };
|
||||
|
||||
export function platformToolPolicy<T extends { name: string }>(
|
||||
export function platformToolPolicy<T extends { name: string; exposure?: string }>(
|
||||
definitions: readonly T[],
|
||||
platform: NodeJS.Platform,
|
||||
): { tools: T[]; coreTools: string[] } {
|
||||
// Script-only and hidden tools are unreachable by the model directly and
|
||||
// must never enter the catalog (or the pinned set); "model-only" tools like
|
||||
// codemode stay reachable and can be pinned.
|
||||
const reachable = definitions.filter(
|
||||
(tool) => tool.exposure !== "hidden" && tool.exposure !== "codemode",
|
||||
);
|
||||
const tools = platform === "win32"
|
||||
? [...definitions]
|
||||
: definitions.filter((tool) => tool.name !== POWERSHELL_TOOL_NAME);
|
||||
? [...reachable]
|
||||
: reachable.filter((tool) => tool.name !== POWERSHELL_TOOL_NAME);
|
||||
const coreTools = platform === "win32"
|
||||
? [...CORE_TOOLS, POWERSHELL_TOOL_NAME]
|
||||
: [...CORE_TOOLS];
|
||||
@@ -110,11 +116,11 @@ function groupDescription(catalog: ToolCatalog, source: CatalogSource): string {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export default function toolSearchExtension(pi: ExtensionAPI): void {
|
||||
function toolSearchExtension(pi: ExtensionAPI, configPath: string): void {
|
||||
const agentDir = getAgentDir();
|
||||
const cachePath = getToolSearchCatalogPath(agentDir);
|
||||
|
||||
let config = readToolSearchConfig();
|
||||
let config = readToolSearchConfig(configPath);
|
||||
let tools: ToolInfo[] = [];
|
||||
let catalog: ToolCatalog = createFallbackCatalog([], "", {
|
||||
maxToolsPerGroup: Math.min(config.maxToolsPerGroup, config.maxDynamicTools),
|
||||
@@ -440,7 +446,7 @@ export default function toolSearchExtension(pi: ExtensionAPI): void {
|
||||
}
|
||||
|
||||
function refreshState(ctx: Pick<ExtensionContext, "ui">, forceReset: boolean): void {
|
||||
const nextConfig = readToolSearchConfig();
|
||||
const nextConfig = readToolSearchConfig(configPath);
|
||||
const registeredTools = pi.getAllTools().filter((tool) => tool.name !== TOOL_SEARCH_NAME);
|
||||
const platformPolicy = platformToolPolicy(registeredTools, process.platform);
|
||||
const allTools = platformPolicy.tools;
|
||||
@@ -542,3 +548,16 @@ export default function toolSearchExtension(pi: ExtensionAPI): void {
|
||||
if (group && activeGroups.has(group)) activeGroups.set(group, ++clock);
|
||||
});
|
||||
}
|
||||
|
||||
export interface ToolSearchExtensionOptions {
|
||||
configPath?: string;
|
||||
}
|
||||
|
||||
export function createToolSearchExtension(
|
||||
options?: ToolSearchExtensionOptions,
|
||||
): (pi: ExtensionAPI) => void {
|
||||
const configPath = options?.configPath ?? getToolSearchConfigPath();
|
||||
return (pi) => toolSearchExtension(pi, configPath);
|
||||
}
|
||||
|
||||
export default createToolSearchExtension();
|
||||
|
||||
@@ -10,6 +10,7 @@ function tool(name: string): ToolInfo {
|
||||
name,
|
||||
description: `Full description for ${name}`,
|
||||
parameters: { type: "object", properties: {} } as ToolInfo["parameters"],
|
||||
exposure: "direct",
|
||||
sourceInfo: { source: "extension", scope: "user", path: `/test/${name}.ts` } as unknown as ToolInfo["sourceInfo"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ function tool(name: string, description: string): ToolInfo {
|
||||
name,
|
||||
description,
|
||||
parameters: { type: "object", properties: { query: { type: "string" } } } as ToolInfo["parameters"],
|
||||
exposure: "direct",
|
||||
sourceInfo: { source: "extension", scope: "user", path: `/test/${name}.ts` } as unknown as ToolInfo["sourceInfo"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ test("bundle defaults keep the committed tool list", () => {
|
||||
"skill_manage",
|
||||
"context_tree_query",
|
||||
"overflow_query",
|
||||
"codemode",
|
||||
]);
|
||||
assert.deepEqual(bundleFallbackConfig(), {
|
||||
alwaysEnabled: [...BUNDLE_TOOL_SEARCH_DEFAULTS.alwaysEnabled],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||
import toolSearchExtension, { platformToolPolicy } from "../extensions/index.ts";
|
||||
import toolSearchExtension, { createToolSearchExtension, platformToolPolicy } from "../extensions/index.ts";
|
||||
|
||||
interface ToolSearchResult {
|
||||
details: {
|
||||
@@ -49,13 +49,12 @@ function testContext(overrides: Partial<ExtensionContext> = {}): ExtensionContex
|
||||
test("loads whole groups and evicts the least-recently-used group", async () => {
|
||||
const agentDir = await mkdtemp(join(tmpdir(), "my-pi-tool-search-extension-"));
|
||||
const previousAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||
const previousConfigPath = process.env.PI_TOOL_SEARCH_CONFIG_PATH;
|
||||
const configPath = join(agentDir, "tool-search-config.json");
|
||||
process.env.PI_CODING_AGENT_DIR = agentDir;
|
||||
process.env.PI_TOOL_SEARCH_CONFIG_PATH = join(agentDir, "tool-search-config.json");
|
||||
|
||||
try {
|
||||
await writeFile(
|
||||
process.env.PI_TOOL_SEARCH_CONFIG_PATH,
|
||||
configPath,
|
||||
JSON.stringify({
|
||||
alwaysEnabled: ["codegraph_explore", "lsp_diagnostics"],
|
||||
showToolSearchFooterStatus: false,
|
||||
@@ -97,7 +96,7 @@ test("loads whole groups and evicts the least-recently-used group", async () =>
|
||||
on: (event: string, handler: (...args: any[]) => unknown) => handlers.set(event, handler),
|
||||
} as unknown as ExtensionAPI;
|
||||
|
||||
toolSearchExtension(api);
|
||||
createToolSearchExtension({ configPath })(api);
|
||||
const ctx = testContext({
|
||||
hasUI: true,
|
||||
ui: {
|
||||
@@ -159,8 +158,6 @@ test("loads whole groups and evicts the least-recently-used group", async () =>
|
||||
} finally {
|
||||
if (previousAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
|
||||
else process.env.PI_CODING_AGENT_DIR = previousAgentDir;
|
||||
if (previousConfigPath === undefined) delete process.env.PI_TOOL_SEARCH_CONFIG_PATH;
|
||||
else process.env.PI_TOOL_SEARCH_CONFIG_PATH = previousConfigPath;
|
||||
await rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -328,6 +325,57 @@ test("keeps PowerShell with Bash only on Windows", () => {
|
||||
assert.ok(windows.coreTools.includes("powershell"));
|
||||
});
|
||||
|
||||
test("drops hidden and script-only tools but keeps reachable exposures", () => {
|
||||
const definitions = [
|
||||
sourceTool("read", "Read a file"),
|
||||
{ ...sourceTool("hidden_helper", "Internal helper"), exposure: "hidden" },
|
||||
{ ...sourceTool("script_only", "Only callable from scripts"), exposure: "codemode" },
|
||||
{ ...sourceTool("codemode", "Batch several tool calls in one script"), exposure: "model-only" },
|
||||
{ ...sourceTool("lazy_tool", "Activated on demand"), exposure: "deferred" },
|
||||
];
|
||||
|
||||
const { tools } = platformToolPolicy(definitions, "darwin");
|
||||
assert.deepEqual(tools.map((tool) => tool.name), ["read", "codemode", "lazy_tool"]);
|
||||
});
|
||||
|
||||
test("pins a model-only codemode tool instead of hiding it", async () => {
|
||||
const agentDir = await mkdtemp(join(tmpdir(), "my-pi-tool-search-codemode-"));
|
||||
const previousAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||
process.env.PI_CODING_AGENT_DIR = agentDir;
|
||||
|
||||
try {
|
||||
const handlers = new Map<string, (...args: any[]) => unknown>();
|
||||
const registered = new Map<string, RegisteredTool>();
|
||||
const activeCalls: string[][] = [];
|
||||
const sourceTools = [
|
||||
sourceTool("read", "Read a file"),
|
||||
{ ...sourceTool("codemode", "Batch several tool calls in one script"), exposure: "model-only" },
|
||||
];
|
||||
const api = {
|
||||
getAllTools: () => sourceTools,
|
||||
registerTool: (definition: unknown) => {
|
||||
const tool = definition as RegisteredTool & { name: string };
|
||||
registered.set(tool.name, tool);
|
||||
},
|
||||
registerCommand: () => {},
|
||||
setActiveTools: (names: string[]) => activeCalls.push([...names]),
|
||||
on: (event: string, handler: (...args: any[]) => unknown) => handlers.set(event, handler),
|
||||
} as unknown as ExtensionAPI;
|
||||
// The bundle config pins "codemode" in alwaysEnabled; exposure "model-only"
|
||||
// stays reachable, so it must land in the active set, not a dynamic group.
|
||||
toolSearchExtension(api);
|
||||
handlers.get("session_start")?.({}, testContext());
|
||||
|
||||
assert.ok(activeCalls.at(-1)?.includes("codemode"));
|
||||
assert.ok(activeCalls.at(-1)?.includes("tool_search"));
|
||||
assert.ok(activeCalls.at(-1)?.includes("read"));
|
||||
} finally {
|
||||
if (previousAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
|
||||
else process.env.PI_CODING_AGENT_DIR = previousAgentDir;
|
||||
await rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("loads an exact known group without waiting for unknown-tool model enrichment", async () => {
|
||||
const agentDir = await mkdtemp(join(tmpdir(), "my-pi-tool-search-fast-path-"));
|
||||
const previousAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||
|
||||
Reference in New Issue
Block a user