mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
用仓库内 pi-hermes-memory 替换 hippo-memory-pi,清理 Hippo 在组合包中的
全部注入点。
移除 Hippo:
- 删除 hippo-memory-pi/ 与 config/hippo-memory-version。
- extensions/plugin-init.ts 只保留 CodeGraph 初始化目标与预检。
- install.sh / update.sh / uninstall.sh 移除 Hippo CLI 的安装、升级、卸载
与版本校验逻辑;uninstall.sh 同时删除仅服务于该提示的死代码 ask_yes_no。
- 删除 tests/hippo-extension.test.ts 与 tests/hippo-scripts.test.ts。
- tests/extension-load.test.ts 反转“不得加载 Hermes”的断言为必须加载,
并断言其排在 pi-compaction 之前。
接入 pi-hermes-memory:
- package.json 增加 file:./pi-hermes-memory 依赖、files 条目与加载入口;
加载顺序位于 pi-compaction 之前,否则 flushOnCompact 会被 pi-compaction
的 { cancel: true } 静默跳过。
- 六个记忆工具在 Tool Search 中常驻:memory_search 与 session_search 连同
context_tree_query 归入 memory-recall,memory_add / memory_replace /
memory_remove / skill_manage 归入 memory-management。常驻是必需的——注入
的 memory policy 会在普通工作中指示模型调用它们,schema 不可见会让模型
静默放弃而不是去 tool_search,同时常驻也保持请求前缀缓存稳定。
- bundleDefaultsVersion 由 2 升到 3;migrate-tool-search-settings.mjs 新增
上一版快照到当前版本的迁移路径,仍只迁移完整匹配的默认快照并保留自定义
配置。
- config/pi-permission-policy.jsonc 为六个工具设置 allow:它们只写扩展自己的
agent 目录数据、不触碰工作区,且 memory_search 每轮都可能被调用,落入
默认 ask 会产生每轮 AutoReview 往返。
依赖:
- pi-hermes-memory 的 devDependencies 由上游 ^0.80.2 等范围改为与根包一致的
精确版本。上游范围与根包 0.84.2 不兼容,会让 npm 在本目录嵌套安装第二套
Pi runtime(实测 229 MB),违反“不得在组合包内再安装一套 Pi runtime”的
纪律。
- 根 allowScripts 保持只有 better-sqlite3@12.11.1。hermes 的
better-sqlite3@13 运行时 dlopen 的是包内 N-API 预编译二进制
prebuilds/<platform>.node,npm 因 binding.gyp 推断出的 node-gyp rebuild
无需执行;实测删除 build/ 目录后仍正常工作。
文档:AGENTS.md 与 README.md 改写记忆章节,新增 pi-hermes-memory 开发约定,
替换文末的 Hippo 项目记忆块。
验证:根 12 个测试文件、pi-tool-search 的 typecheck/test/build、
pi-permission-system 的 typecheck/2569 测试、pi-hermes-memory 自带的 59 个
测试文件全部通过;隔离 Pi 环境联合加载无错误并生成 bundleDefaultsVersion 3;
npm install 幂等;迁移 helper 对 v2 快照迁移、对自定义配置保留。
307 lines
16 KiB
TypeScript
307 lines
16 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-tavily",
|
|
title: "Tavily web discovery",
|
|
summary: "Discover broad, current, or news-oriented web sources with Tavily and fetch selected pages.",
|
|
useWhen: ["Keywords are uncertain, many candidate sources help, or current/news coverage is needed"],
|
|
avoidWhen: ["Official technical sources or Chinese site/date constraints are more important"],
|
|
keywords: ["tavily", "web", "news", "discovery", "网页", "新闻", "广泛搜索"],
|
|
tools: ["tavily_web_search", "tavily_web_fetch"],
|
|
},
|
|
{
|
|
id: "web-exa",
|
|
title: "Exa precision research",
|
|
summary: "Find precise official, technical, academic, company, or API sources with Exa and read selected pages.",
|
|
useWhen: ["Source precision, official documentation, papers, releases, or advanced filters matter"],
|
|
avoidWhen: ["Broad news discovery or Chinese policy search is the primary need"],
|
|
keywords: ["exa", "official docs", "paper", "api", "research", "官方文档", "论文", "精确搜索"],
|
|
tools: ["exa_web_search", "exa_web_search_advanced", "exa_web_fetch"],
|
|
},
|
|
{
|
|
id: "web-keenable",
|
|
title: "Keenable focused search",
|
|
summary: "Search Chinese-language, site-restricted, policy, government, or date-filtered sources and fetch selected pages.",
|
|
useWhen: ["Chinese pages, policies, government notices, domain restrictions, or publication dates matter"],
|
|
avoidWhen: ["Broad international discovery or academic semantic search is a better fit"],
|
|
keywords: ["keenable", "Chinese", "policy", "site", "date", "中文", "政策", "站点", "日期"],
|
|
tools: ["keenable_search", "keenable_fetch"],
|
|
},
|
|
{
|
|
id: "context-execution",
|
|
title: "Context-isolated execution",
|
|
summary: "Run commands or analyze large files in a sandbox while returning only compact derived output.",
|
|
useWhen: ["Logs, tests, builds, generated data, commands, or workspace files may produce large output"],
|
|
avoidWhen: ["Exact source lines are needed for an anchored edit"],
|
|
keywords: ["execute", "large file", "logs", "tests", "build", "大文件", "日志", "测试", "构建"],
|
|
tools: ["ctx_execute", "ctx_execute_file", "ctx_batch_execute"],
|
|
},
|
|
{
|
|
id: "context-knowledge",
|
|
title: "Context knowledge base",
|
|
summary: "Index local or web documentation and retrieve focused passages from the persistent context-mode knowledge base.",
|
|
useWhen: ["Documentation or session knowledge should be stored and queried without rereading raw content"],
|
|
avoidWhen: ["A one-shot small source can be read directly"],
|
|
keywords: ["index", "search", "knowledge base", "fetch docs", "索引", "知识库", "文档检索"],
|
|
tools: ["ctx_index", "ctx_search", "ctx_fetch_and_index"],
|
|
},
|
|
{
|
|
id: "context-observability",
|
|
title: "Context observability",
|
|
summary: "Inspect context-mode health, savings statistics, and the hosted Insight dashboard.",
|
|
useWhen: ["Diagnosing context-mode or reviewing context savings and analytics"],
|
|
avoidWhen: ["The task is normal code or data processing"],
|
|
keywords: ["stats", "doctor", "insight", "health", "统计", "诊断", "上下文节省"],
|
|
tools: ["ctx_stats", "ctx_doctor", "ctx_insight"],
|
|
},
|
|
{
|
|
id: "context-administration",
|
|
title: "Context administration",
|
|
summary: "Upgrade context-mode or destructively purge indexed context data.",
|
|
useWhen: ["The user explicitly asks to upgrade context-mode or purge a named scope"],
|
|
avoidWhen: ["Routine searching, indexing, or performance inspection"],
|
|
keywords: ["upgrade", "purge", "delete", "升级", "清除", "删除知识库"],
|
|
tools: ["ctx_upgrade", "ctx_purge"],
|
|
},
|
|
{
|
|
id: "memory-recall",
|
|
title: "Persistent memory recall",
|
|
summary: "Search durable user, global, project, and failure memories, search indexed past conversations, or recover pruned 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", "记忆", "召回", "检索", "历史会话", "恢复输出"],
|
|
tools: ["memory_search", "session_search", "context_tree_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"],
|
|
},
|
|
{
|
|
id: "mcp-management",
|
|
title: "MCP management",
|
|
summary: "Inspect and manage MCP server connections exposed by Pi's shared adapter.",
|
|
useWhen: ["The user explicitly asks about MCP server status or operations"],
|
|
avoidWhen: ["A mapped first-class MCP tool already handles the task"],
|
|
keywords: ["mcp", "server", "connection", "MCP 服务", "连接"],
|
|
tools: ["mcp"],
|
|
},
|
|
];
|
|
|
|
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);
|
|
}
|