Files
my-pi/pi-tool-search/extensions/bundle-groups.ts
T
云服务部-叶林立andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 56cbc83296 refactor: 移除 context-mode,Chrome 快照改为直接返回
context-mode 的 ctx_execute 系工具以真实 shell 执行且权限为 allow,绕过了
bash 策略与 protectedWrites;其每轮注入、独立 MCP 子进程与 skills 也与组合包
提示词单一所有权冲突。大输出职责已由 pi-overflow(入口截断)与
pi-compaction(压力遮蔽)承担:
- 删除 context-mode 依赖、wrapper/过滤器、skills,以及 ctx_* 的权限、
  tools.jsonc、路由规则、tool-search 常驻与 4 个分组
- better-sqlite3@12 仅为 context-mode 传递依赖,随之移除根 allowScripts
- pi-chrome:按用户决定移除快照隐私规则与 .pi/chrome-context 临时文件机制,
  快照 JSON 直接作为工具结果返回、details 只留小型元数据,超长由 pi-overflow
  截断并用 overflow_query 提取;授权生命周期不变

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-21 20:45:33 -07:00

244 lines
12 KiB
TypeScript

import type { ToolInfo } from "@earendil-works/pi-coding-agent";
import {
CATALOG_VERSION,
applyCatalogOverrides,
createFallbackCatalog,
type CatalogConstraints,
type GroupCard,
type ToolCard,
type ToolCatalog,
} from "./catalog.ts";
interface BundleGroupDefinition {
id: string;
title: string;
summary: string;
useWhen: string[];
avoidWhen: string[];
keywords: string[];
tools: string[];
}
/** Curated groups for every non-core tool exposed by the my-pi bundle. */
export const BUNDLE_GROUP_DEFINITIONS: BundleGroupDefinition[] = [
{
id: "filesystem-navigation",
title: "Filesystem navigation",
summary: "Inspect directory layouts and run bounded multi-pattern literal searches.",
useWhen: ["You need directory entries or several literal searches after narrowing a path"],
avoidWhen: ["Code relationships or types are the real question"],
keywords: ["files", "directory", "list", "multi grep", "目录", "文件", "多模式搜索"],
tools: ["ls", "multi_grep"],
},
{
id: "ssh-connection",
title: "SSH connection",
summary: "Connect sequentially to an explicitly imported SSH host through the reviewed agent tool flow.",
useWhen: ["The user names a remote server as part of a concrete task and no matching SSH2 connection is active; call ssh_connect separately and wait for success before dependent remote tools"],
avoidWhen: ["The task is local, the server was not explicitly named, or the host has not been imported"],
keywords: ["ssh", "connect", "server", "host", "remote", "连接服务器", "远程主机", "SSH"],
tools: ["ssh_connect"],
},
{
id: "ssh-remote-files",
title: "SSH remote files",
summary: "Read, write, and edit files in the currently connected remote SSH workspace.",
useWhen: ["You need explicit file operations on an active SSH target rather than the local workspace"],
avoidWhen: ["A remote shell command is required, or the files are local"],
keywords: ["ssh", "remote", "read", "write", "edit", "远程", "文件", "SSH"],
tools: ["ssh_read", "ssh_write", "ssh_edit"],
},
{
id: "ssh-remote-search",
title: "SSH remote search",
summary: "Run bounded adaptive filename and content searches on the active SSH target.",
useWhen: ["You need to locate remote files or literals before reading or operating on them"],
avoidWhen: ["The search belongs to the local workspace, or a known remote path can be read directly"],
keywords: ["ssh", "remote", "find", "grep", "search", "远程搜索", "文件查找", "SSH"],
tools: ["ssh_find", "ssh_grep"],
},
{
id: "ssh-remote-shell",
title: "SSH remote workspace shell",
summary: "Change the active remote cwd as a separate reviewed step, or run a reviewed shell command in that workspace.",
useWhen: ["You need to change the active remote workspace or run builds, tests, Git, or other shell operations there; call ssh_cd separately and wait for success before dependent remote calls"],
avoidWhen: ["Structured remote file tools are sufficient, or the operation should run locally"],
keywords: ["ssh", "remote", "bash", "cd", "cwd", "workspace", "build", "test", "git", "远程命令", "工作目录", "SSH"],
tools: ["ssh_cd", "ssh_bash"],
},
{
id: "code-intelligence",
title: "Code intelligence",
summary: "Explore code relationships and query language-server definitions, references, types, symbols, and diagnostics.",
useWhen: ["Understanding architecture, call paths, symbol identity, types, references, or compiler diagnostics"],
avoidWhen: ["Only an exact literal text match is required"],
keywords: ["codegraph", "lsp", "definition", "references", "diagnostics", "代码结构", "定义", "引用", "诊断"],
tools: ["codegraph_explore", "lsp_definition", "lsp_references", "lsp_hover", "lsp_symbols", "lsp_diagnostics"],
},
{
id: "web-search",
title: "Web search and fetch",
summary: "Search the web for current or citable information and read selected pages as clean markdown.",
useWhen: ["Current events, external facts, documentation, papers, or page content are needed"],
avoidWhen: ["The answer is already in the workspace or local code tools suffice"],
keywords: ["web", "search", "fetch", "news", "docs", "网页", "搜索", "新闻", "文档"],
tools: ["web_search", "web_fetch"],
},
{
id: "memory-recall",
title: "Persistent memory recall",
summary: "Search durable user, global, project, and failure memories, search indexed past conversations, or recover pruned or overflow-stored tool-call output.",
useWhen: ["The task may depend on durable context from previous sessions: preferences, project conventions, prior decisions, known failures, corrections, or condensed outputs"],
avoidWhen: ["The required evidence is already in the current visible context, or the question is a generic one-off that durable memory would not help"],
keywords: ["memory", "recall", "search", "session", "history", "recover", "overflow", "记忆", "召回", "检索", "历史会话", "恢复输出"],
tools: ["memory_search", "session_search", "context_tree_query", "overflow_query"],
},
{
id: "memory-management",
title: "Persistent memory management",
summary: "Save, update, or remove durable memories, and create or maintain reusable procedural skills.",
useWhen: ["A durable fact, preference, convention, correction, or non-obvious reusable procedure should persist across sessions"],
avoidWhen: ["The information is temporary task progress, derivable from the repository, or a one-off detail that would add noise"],
keywords: ["remember", "save", "replace", "remove", "skill", "procedure", "记住", "保存", "修改记忆", "技能", "流程"],
tools: ["memory_add", "memory_replace", "memory_remove", "skill_manage"],
},
{
id: "subagents",
title: "Subagent orchestration",
summary: "Launch isolated subagents, collect background results, and steer running delegated work.",
useWhen: ["A bounded task benefits from an isolated specialist, background execution, or delegated follow-up"],
avoidWhen: ["The current agent can complete a small direct task without delegation overhead"],
keywords: ["subagent", "agent", "delegate", "background", "steer", "子代理", "委派", "后台"],
tools: ["Agent", "get_subagent_result", "steer_subagent"],
},
{
id: "user-interaction",
title: "Interactive user questions",
summary: "Pause the agent and collect one or more structured decisions from the user in the TUI.",
useWhen: ["A missing requirement, preference, confirmation, or choice must come directly from the user before continuing"],
avoidWhen: ["The user's direct messages already provide the answer, or the task can proceed without another decision"],
keywords: ["ask", "question", "clarify", "choice", "preference", "提问", "澄清", "选择", "确认"],
tools: ["ask_user_question"],
},
{
id: "chrome-navigation",
title: "Chrome navigation and inspection",
summary: "Connect to the authorized Chrome bridge, manage tabs, navigate, inspect page structure, wait for state, and capture screenshots.",
useWhen: ["You need to inspect or navigate the user's existing signed-in Chrome profile after explicit /chrome authorization"],
avoidWhen: ["A normal web search or HTTP page fetch is sufficient, or Chrome has not been explicitly authorized"],
keywords: ["chrome", "browser", "tab", "navigate", "snapshot", "inspect", "screenshot", "浏览器", "标签页", "网页操作"],
tools: ["chrome_launch", "chrome_tab", "chrome_navigate", "chrome_snapshot", "chrome_find", "chrome_inspect", "chrome_wait_for", "chrome_screenshot"],
},
{
id: "chrome-interaction",
title: "Chrome page interaction",
summary: "Interact with an authorized Chrome page using real pointer, keyboard, touch, form, drag, and scroll input.",
useWhen: ["You must click, type, fill, press keys, hover, drag, tap, or scroll in the user's authorized Chrome session"],
avoidWhen: ["Page observation is enough, or the action is irreversible and the user has not approved it"],
keywords: ["chrome", "browser", "click", "type", "fill", "drag", "scroll", "点击", "输入", "滚动", "浏览器交互"],
tools: ["chrome_click", "chrome_type", "chrome_fill", "chrome_key", "chrome_hover", "chrome_drag", "chrome_tap", "chrome_scroll"],
},
{
id: "chrome-debugging",
title: "Chrome diagnostics and upload",
summary: "Evaluate page JavaScript, inspect captured console/network activity, retrieve response bodies, or attach local files.",
useWhen: ["Debugging the authorized Chrome page, examining fetch/XHR behavior, evaluating page state, or uploading an explicitly named local file"],
avoidWhen: ["Navigation, DOM inspection, or ordinary page interaction tools are sufficient"],
keywords: ["chrome", "browser", "console", "network", "javascript", "upload", "调试", "网络请求", "文件上传"],
tools: ["chrome_evaluate", "chrome_list_console_messages", "chrome_list_network_requests", "chrome_get_network_request", "chrome_upload_file"],
},
];
export interface BundleCatalogResult {
catalog: ToolCatalog;
coveredNames: Set<string>;
unknownTools: ToolInfo[];
}
function compactDescription(tool: ToolInfo): string {
return tool.description.replace(/\s+/gu, " ").trim().slice(0, 240) || `Use the ${tool.name} tool.`;
}
export function createBundleCatalog(
tools: ToolInfo[],
manifestHash: string,
constraints: CatalogConstraints,
): BundleCatalogResult {
const available = new Map(tools.map((tool) => [tool.name, tool]));
const coveredNames = new Set<string>();
const groups: GroupCard[] = [];
const cards: ToolCard[] = [];
for (const definition of BUNDLE_GROUP_DEFINITIONS) {
const members = definition.tools.flatMap((name) => {
const tool = available.get(name);
return tool ? [tool] : [];
});
if (members.length === 0) continue;
for (const tool of members) {
coveredNames.add(tool.name);
cards.push({
name: tool.name,
summary: compactDescription(tool),
useWhen: [...definition.useWhen],
avoidWhen: [...definition.avoidWhen],
keywords: [...new Set([...definition.keywords, ...tool.name.split(/[_-]/u)])],
primaryGroup: definition.id,
});
}
groups.push({
id: definition.id,
title: definition.title,
summary: definition.summary,
useWhen: [...definition.useWhen],
avoidWhen: [...definition.avoidWhen],
tools: members.map((tool) => tool.name),
});
}
const unknownTools = tools.filter((tool) => !coveredNames.has(tool.name));
const fallback = createFallbackCatalog(unknownTools, manifestHash, {
maxToolsPerGroup: constraints.maxToolsPerGroup,
groupOverrides: {},
});
const reservedIds = new Set(groups.map((group) => group.id));
const renamedGroups = new Map<string, string>();
for (const group of fallback.groups) {
let id = group.id;
while (reservedIds.has(id)) id = `custom-${id}`;
reservedIds.add(id);
renamedGroups.set(group.id, id);
groups.push({ ...group, id, title: id.replace(/-/gu, " ") });
}
for (const card of fallback.tools) {
cards.push({ ...card, primaryGroup: renamedGroups.get(card.primaryGroup) ?? card.primaryGroup });
}
const overridden = applyCatalogOverrides(groups, cards, constraints.groupOverrides);
return {
catalog: {
version: CATALOG_VERSION,
manifestHash,
generatedAt: new Date().toISOString(),
generatedBy: "my-pi bundle seed",
groups: overridden.groups,
tools: overridden.tools,
},
coveredNames,
unknownTools,
};
}
export function catalogPreservesBundleAssignments(
candidate: ToolCatalog,
bundleCatalog: ToolCatalog,
coveredNames: Set<string>,
): boolean {
const expected = new Map(
bundleCatalog.tools.filter((tool) => coveredNames.has(tool.name)).map((tool) => [tool.name, tool.primaryGroup]),
);
const actual = new Map(candidate.tools.map((tool) => [tool.name, tool.primaryGroup]));
return [...expected].every(([name, group]) => actual.get(name) === group);
}