feat(pi-hermes-memory): 接入持久记忆扩展并移除 Hippo

用仓库内 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 快照迁移、对自定义配置保留。
This commit is contained in:
云服务部-叶林立
2026-10-14 00:56:41 -07:00
parent 312bb2ba5a
commit 73f3117a7e
31 changed files with 976 additions and 1198 deletions
+35 -45
View File
@@ -25,7 +25,7 @@
- `@keenable/pi-search`:加载 `keenable_search` / `keenable_fetch` 及其 skill,默认 keyless,`KEENABLE_API_KEY` 仅用于提高速率限制。 - `@keenable/pi-search`:加载 `keenable_search` / `keenable_fetch` 及其 skill,默认 keyless,`KEENABLE_API_KEY` 仅用于提高速率限制。
- `search_config.sh` 与 `extensions/search-config.ts`:以交互式或参数方式把三家搜索 key 写入用户级 `search.env`(权限 `600`),并在搜索扩展初始化前加载;显式进程环境变量优先。 - `search_config.sh` 与 `extensions/search-config.ts`:以交互式或参数方式把三家搜索 key 写入用户级 `search.env`(权限 `600`),并在搜索扩展初始化前加载;显式进程环境变量优先。
- `extensions/mcp.ts`:通过单个共享 `pi-mcp-adapter` 实例同时连接 Exa 托管 MCP 与机器现有的 `codegraph serve --mcp`,避免重复注册 Pi 的全局 MCP flag 与命令;Exa key 只通过 `x-api-key` 请求头发送,原始工具映射为统一的来源前缀形式,CodeGraph 只暴露 `codegraph_explore`。 - `extensions/mcp.ts`:通过单个共享 `pi-mcp-adapter` 实例同时连接 Exa 托管 MCP 与机器现有的 `codegraph serve --mcp`,避免重复注册 Pi 的全局 MCP flag 与命令;Exa key 只通过 `x-api-key` 请求头发送,原始工具映射为统一的来源前缀形式,CodeGraph 只暴露 `codegraph_explore`。
- `extensions/plugin-init.ts`:注册用户显式触发的 `/plugin_init`,在确认当前项目后只初始化缺失的 CodeGraph/Hippo 状态,并在全部成功后调用 Pi 官方热重载。 - `extensions/plugin-init.ts`:注册用户显式触发的 `/plugin_init`,在确认当前项目后只初始化缺失的 CodeGraph 状态,并在全部成功后调用 Pi 官方热重载。
- `context-mode`:加载上游 Pi adapter 与 skills,提供 `ctx_*` 工具、隔离式大输出处理和会话连续性。 - `context-mode`:加载上游 Pi adapter 与 skills,提供 `ctx_*` 工具、隔离式大输出处理和会话连续性。
- `extensions/hashline.ts`:部署组合包权威 `config/pi-hashline-edit.json` 后加载 `pi-hashline-edit`,以行哈希锚点覆盖内置 `read` / `edit`;Hashline 自带 `grep` 强制关闭。 - `extensions/hashline.ts`:部署组合包权威 `config/pi-hashline-edit.json` 后加载 `pi-hashline-edit`,以行哈希锚点覆盖内置 `read` / `edit`;Hashline 自带 `grep` 强制关闭。
- `extensions/permission-system.ts`:直接加载仓库内 `pi-permission-system/src/index.ts`;只在目标缺失时把 `config/pi-permission-policy.jsonc` / `config/pi-permission-hard-guards.jsonc` 原子部署为 owner-only 全局用户配置,旧全局 `config.json` 仅复制迁移为 `policy.jsonc`。项目级 policy 不读取;`update.sh` 只可在当前文件精确匹配已知旧默认种子时备份并迁移到新种子,自定义或无效文件必须保留。 - `extensions/permission-system.ts`:直接加载仓库内 `pi-permission-system/src/index.ts`;只在目标缺失时把 `config/pi-permission-policy.jsonc` / `config/pi-permission-hard-guards.jsonc` 原子部署为 owner-only 全局用户配置,旧全局 `config.json` 仅复制迁移为 `policy.jsonc`。项目级 policy 不读取;`update.sh` 只可在当前文件精确匹配已知旧默认种子时备份并迁移到新种子,自定义或无效文件必须保留。
@@ -44,10 +44,10 @@
- 初始参考快照:`dcd461925db2edf69a43c8135db1180d418afd54`(2026-08-24)。 - 初始参考快照:`dcd461925db2edf69a43c8135db1180d418afd54`(2026-08-24)。
- 该目录由本仓库直接维护,保留上游 MIT 许可证与来源说明;根组合包直接加载源码并通过本地 `file:` 依赖打包。 - 该目录由本仓库直接维护,保留上游 MIT 许可证与来源说明;根组合包直接加载源码并通过本地 `file:` 依赖打包。
- `pi-lsp@0.1.7`:提供声明式 LSP 接入;组合包内置 `typescript-language-server@5.3.0` + `typescript@6.0.3`,并配置机器级 `kotlin-lsp --stdio` 与 `jdtls`。 - `pi-lsp@0.1.7`:提供声明式 LSP 接入;组合包内置 `typescript-language-server@5.3.0` + `typescript@6.0.3`,并配置机器级 `kotlin-lsp --stdio` 与 `jdtls`。
- `hippo-memory-pi/`:从官方 `hippo-memory` 仓库的 `extensions/pi-extension/` 导入并由本仓库直接维护;提供 session start 项目记忆注入、工具错误过滤捕获、session shutdown sleep 和 5 个 `hippo_*` 工具。 - `pi-hermes-memory/`:从官方 `pi-hermes-memory` 仓库导入并由本仓库直接维护的 Hermes 风格持久记忆扩展;提供全局/项目/用户/失败四类记忆、SQLite FTS5 trigram 会话与记忆检索、`SKILL.md` 过程记忆、correction 检测、自动 consolidation、`STANDING.md` 常驻指令和 6 个记忆工具。
- 上游来源:<https://github.com/kitfunso/hippo-memory> - 上游来源:<https://github.com/chandra447/pi-hermes-memory>
- 初始导入快照:`e928179a3b35e8fe5837878aed071d6025ced45c`(`v1.33.0`)。 - 初始导入快照:`df624056a2e8c68bc89ba0a81e1c120856ec9665`(`v0.9.10`)。
- 官方 npm tarball 不包含 Pi Extension;仓库保留官方 README、LICENSE 和来源说明,匹配 CLI 版本由 `config/hippo-memory-version` 声明。 - 该目录从明确 tag 导入并由本仓库直接维护,不使用 npm 预编译产物,不是 submodule,也不保留嵌套 `.git`、上游 `.github` 工作流或构建产物。
- `pi-minimal-footer/`:基于上游紧凑 footer,保留上下文仪表、订阅用量条和扩展状态,并在 Codex Fast 模式开启时显示 `Fast on`。 - `pi-minimal-footer/`:基于上游紧凑 footer,保留上下文仪表、订阅用量条和扩展状态,并在 Codex Fast 模式开启时显示 `Fast on`。
- 上游来源:<https://github.com/ogulcancelik/pi-extensions/tree/main/packages/pi-minimal-footer> - 上游来源:<https://github.com/ogulcancelik/pi-extensions/tree/main/packages/pi-minimal-footer>
- 初始导入快照:`77bd1e175003cd08e6d05d9e7fed695f86ae87b7`(`@ogulcancelik/pi-minimal-footer@0.1.10`)。 - 初始导入快照:`77bd1e175003cd08e6d05d9e7fed695f86ae87b7`(`@ogulcancelik/pi-minimal-footer@0.1.10`)。
@@ -64,10 +64,10 @@
- 上游参考一:<https://github.com/jjuraszek/pi-condense>,快照 `59c95110aacd8131db71e21296765dd80b72fae8`(`v2.9.1`)。 - 上游参考一:<https://github.com/jjuraszek/pi-condense>,快照 `59c95110aacd8131db71e21296765dd80b72fae8`(`v2.9.1`)。
- 上游来源二:<https://github.com/ogulcancelik/pi-extensions/tree/main/packages/pi-codex-compaction>,导入快照 `c4d6ca8fb484ba51684296ba8b4715f8c7b3ac91`(`0.1.4`)。 - 上游来源二:<https://github.com/ogulcancelik/pi-extensions/tree/main/packages/pi-codex-compaction>,导入快照 `c4d6ca8fb484ba51684296ba8b4715f8c7b3ac91`(`0.1.4`)。
- `pi-compaction` 采用全新的 `pi-compaction` version 1 Session 记录和 `settings.json.piCompaction` 配置,不读取或迁移旧 `context-prune-*`、`contextPrune` 或 `openai-codex-native-compaction` 状态;切换后应使用新 Session。两份上游 MIT 许可证及来源差异保留在包内。 - `pi-compaction` 采用全新的 `pi-compaction` version 1 Session 记录和 `settings.json.piCompaction` 配置,不读取或迁移旧 `context-prune-*`、`contextPrune` 或 `openai-codex-native-compaction` 状态;切换后应使用新 Session。两份上游 MIT 许可证及来源差异保留在包内。
- 根包还固定安装 `@tavily/pi-extension@0.1.2`、`@keenable/pi-search@0.1.2`、`pi-hashline-edit@0.8.3`、`typescript-language-server@5.3.0` 和 `typescript@6.0.3`;`@firstpick/pi-extension-codex-fast-mode`、`@gotgenes/pi-permission-system`、`@smoose/pi-notify`、`pi-ask-user`、`pi-chrome`、`pi-compaction`、`pi-minimal-footer`、`pi-ssh` 与 `pi-tool-search` 分别指向仓库内对应目录的本地 `file:` 依赖;`pi-subagents/` 仅保留源码,不作为根运行时依赖。 - 根包还固定安装 `@tavily/pi-extension@0.1.2`、`@keenable/pi-search@0.1.2`、`pi-hashline-edit@0.8.3`、`typescript-language-server@5.3.0` 和 `typescript@6.0.3`;`@firstpick/pi-extension-codex-fast-mode`、`@gotgenes/pi-permission-system`、`@smoose/pi-notify`、`pi-ask-user`、`pi-chrome`、`pi-compaction`、`pi-hermes-memory`、`pi-minimal-footer`、`pi-ssh` 与 `pi-tool-search` 分别指向仓库内对应目录的本地 `file:` 依赖;`pi-subagents/` 仅保留源码,不作为根运行时依赖。
- `install.sh`:先安装根组合包,再交互检查 Hippo Memory CLI、Kitty/Solarized Dark、Oh My Zsh/Powerlevel10k/Zsh 插件、CodeGraph、Kotlin LSP、Java 21+ 和 JDT LS;缺失项目只在用户明确选择 `Y` 后安装或配置。Hippo 项目目录与 `hippo init` 始终由用户自行决定。Powerlevel10k 默认配置来自仓库内置的 `config/p10k.zsh`(当前 Rainbow/ASCII 单行紧凑主题)。 - `install.sh`:先安装根组合包,再交互检查 Kitty/Solarized Dark、Oh My Zsh/Powerlevel10k/Zsh 插件、CodeGraph、Kotlin LSP、Java 21+ 和 JDT LS;缺失项目只在用户明确选择 `Y` 后安装或配置。Powerlevel10k 默认配置来自仓库内置的 `config/p10k.zsh`(当前 Rainbow/ASCII 单行紧凑主题)。
- `update.sh`:先通过 `pi update` 升级根组合包,再只升级当前已安装的终端环境和机器级依赖;未安装项直接跳过。不得在 `pi update` 前预清理安装缓存;仅当默认 Git 来源首次升级失败、Pi 的精确中断标记存在、canonical checkout 根目录和 `origin` 均通过身份校验时,才允许对该缓存执行最多 4 次连续 `git clean -fdx`,随后只重试一次 `pi update`,由 Pi 自己恢复依赖和清除标记;自定义来源、标记缺失或身份不匹配必须保持 fail-fast。升级成功后只能调用可定位的更新后组合包 Node helper 检查 Pi agent `settings.json`:只有 `toolSearch` 完整精确匹配旧版 `3` 组 / `20` 工具默认快照时,才创建从生成起即为权限 `600` 的时间戳备份、以同目录临时文件原子迁移到 `5` / `28` 并写入 `bundleDefaultsVersion: 2`;任何自定义或无效配置必须保留,提交前检测到内容变化必须中止。无法从 `PI_PACKAGE_SOURCE` 定位更新后 helper 时必须保留配置并报告失败,不能回退到可能陈旧的脚本目录 helper。其他组件升级前先查询并比较本地与远端版本,只有版本不同时才下载或替换;Powerlevel10k 配置优先从 `pi update` 后的已安装组合包读取,并与 `.zshrc` 受管块一起按内容比较后增量同步。 - `update.sh`:先通过 `pi update` 升级根组合包,再只升级当前已安装的终端环境和机器级依赖;未安装项直接跳过。不得在 `pi update` 前预清理安装缓存;仅当默认 Git 来源首次升级失败、Pi 的精确中断标记存在、canonical checkout 根目录和 `origin` 均通过身份校验时,才允许对该缓存执行最多 4 次连续 `git clean -fdx`,随后只重试一次 `pi update`,由 Pi 自己恢复依赖和清除标记;自定义来源、标记缺失或身份不匹配必须保持 fail-fast。升级成功后只能调用可定位的更新后组合包 Node helper 检查 Pi agent `settings.json`:只有 `toolSearch` 完整精确匹配旧版 `3` 组 / `20` 工具或上一版 `5` 组 / `28` 工具默认快照时,才创建从生成起即为权限 `600` 的时间戳备份、以同目录临时文件原子迁移到当前默认值并写入当前 `bundleDefaultsVersion`;任何自定义或无效配置必须保留,提交前检测到内容变化必须中止。无法从 `PI_PACKAGE_SOURCE` 定位更新后 helper 时必须保留配置并报告失败,不能回退到可能陈旧的脚本目录 helper。其他组件升级前先查询并比较本地与远端版本,只有版本不同时才下载或替换;Powerlevel10k 配置优先从 `pi update` 后的已安装组合包读取,并与 `.zshrc` 受管块一起按内容比较后增量同步。
- `uninstall.sh`:先移除根组合包;若检测到 Hippo Memory CLI,再明确询问是否卸载确认属于 npm 全局安装的 `hippo-memory`,默认保留且始终不删除 `.hippo/` 或用户记忆数据。其他共享终端环境和机器工具不卸载。 - `uninstall.sh`:先移除根组合包,其他共享终端环境和机器工具不卸载。不得删除 `.pi/agent/pi-hermes-memory/`、`projects-memory/`、`.hippo/` 或任何用户记忆数据。
## 当前职责与默认行为 ## 当前职责与默认行为
@@ -86,7 +86,7 @@
- 标准组合包工具全部命中预置目录时 Tool Search 不得调用模型、创建用户缓存或发送隐藏 schema。只有存在未识别第三方工具时才允许惰性模型增强;生成结果必须保留全部预置 assignment,模型元数据不得替代真实名称、参数或 schema。缓存必须位于 agent 目录且权限 `600`;模型失败必须继续使用预置目录 + 确定性未知工具分组。`groupOverrides` 优先级高于预置分组。 - 标准组合包工具全部命中预置目录时 Tool Search 不得调用模型、创建用户缓存或发送隐藏 schema。只有存在未识别第三方工具时才允许惰性模型增强;生成结果必须保留全部预置 assignment,模型元数据不得替代真实名称、参数或 schema。缓存必须位于 agent 目录且权限 `600`;模型失败必须继续使用预置目录 + 确定性未知工具分组。`groupOverrides` 优先级高于预置分组。
- Tool Search 必须直接使用当前 `@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` 只能在调用时裁决,不能读写 active tool 列表。 - Tool Search 必须直接使用当前 `@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` 只能在调用时裁决,不能读写 active tool 列表。
- `.pi-debug/` 是 `/dump-system-prompt` 生成的本地诊断目录,不提交到仓库,也不作为组合包运行时配置源。 - `.pi-debug/` 是 `/dump-system-prompt` 生成的本地诊断目录,不提交到仓库,也不作为组合包运行时配置源。
- CodeGraph MCP 扩展只配置 Pi 到外部 `codegraph` 命令的连接。根包安装本身不安装 CodeGraph;便捷脚本仅在组合包安装完成且用户明确选择 `Y` 后调用官方安装器。安装、升级和普通启动流程不得执行 `codegraph init`;只有用户在目标项目显式调用 `/plugin_init` 并确认后,命令才可以 `.codegraph/codegraph.db` 与 `.hippo/hippo.db` 为权威标志初始化缺失状态。命令必须先预检两个 CLI,任一步失败不得热重载,也不得隐瞒此前已完成的部分初始化;不得改动 CodeGraph 更新或遥测设置。 - CodeGraph MCP 扩展只配置 Pi 到外部 `codegraph` 命令的连接。根包安装本身不安装 CodeGraph;便捷脚本仅在组合包安装完成且用户明确选择 `Y` 后调用官方安装器。安装、升级和普通启动流程不得执行 `codegraph init`;只有用户在目标项目显式调用 `/plugin_init` 并确认后,命令才可以 `.codegraph/codegraph.db` 为权威标志初始化缺失状态。命令必须先预检 CLI,失败不得热重载,也不得隐瞒此前已完成的部分初始化;不得改动 CodeGraph 更新或遥测设置。
- CodeGraph MCP 使用 `keep-alive` 并只直接暴露 `codegraph_explore`;命令缺失、项目未初始化或连接失败时不得阻止其他扩展加载。 - CodeGraph MCP 使用 `keep-alive` 并只直接暴露 `codegraph_explore`;命令缺失、项目未初始化或连接失败时不得阻止其他扩展加载。
- 所有 MCP 服务必须合并到 `extensions/mcp.ts` 创建的唯一 `pi-mcp-adapter` 实例;不得新增独立 adapter 扩展入口,否则会重复注册 `--mcp-config`、`/mcp` 等全局接口。根测试必须扫描全部本地扩展入口并强制这一不变量。 - 所有 MCP 服务必须合并到 `extensions/mcp.ts` 创建的唯一 `pi-mcp-adapter` 实例;不得新增独立 adapter 扩展入口,否则会重复注册 `--mcp-config`、`/mcp` 等全局接口。根测试必须扫描全部本地扩展入口并强制这一不变量。
- Exa MCP 使用托管 Streamable HTTP 端点和 `eager` lifecycle;`EXA_API_KEY` 通过 `x-api-key` 请求头发送,不得放入 URL、仓库文件或日志。所有在线搜索工具统一采用“来源名 + 原始语义工具名”的命名形式,例如 `tavily_web_search`、`exa_web_search`、`keenable_search`。Exa 高级搜索必须显式约束结果数量和文本长度;Tavily 输出默认控制 `max_results` 且非必要不请求 raw content;Keenable 优先利用中文、站点和日期筛选能力。 - Exa MCP 使用托管 Streamable HTTP 端点和 `eager` lifecycle;`EXA_API_KEY` 通过 `x-api-key` 请求头发送,不得放入 URL、仓库文件或日志。所有在线搜索工具统一采用“来源名 + 原始语义工具名”的命名形式,例如 `tavily_web_search`、`exa_web_search`、`keenable_search`。Exa 高级搜索必须显式约束结果数量和文本长度;Tavily 输出默认控制 `max_results` 且非必要不请求 raw content;Keenable 优先利用中文、站点和日期筛选能力。
@@ -110,13 +110,14 @@
- 终端环境配置只属于便捷脚本:Kitty 可选安装后可通过官方 kitten 启用 Solarized Dark;Oh My Zsh 使用不切换 shell 的 unattended 安装;Powerlevel10k 将仓库内置的 `config/p10k.zsh`(Rainbow/ASCII 单行紧凑主题)部署到 `~/.config/my-pi/p10k.zsh`(遵循 `XDG_CONFIG_HOME`),不得覆盖用户自己的 `~/.p10k.zsh`。`.zshrc` 中脚本拥有的内容必须使用 `# >>> my-pi:<id> >>>` / `# <<< my-pi:<id> <<<` 受管块,更新时只替换块内内容;标记不完整或重复时拒绝修改。实际修改前必须创建带时间戳的备份。`git`、`zsh-autosuggestions`、`zsh-syntax-highlighting` 在安装流程中逐项询问后才安装或启用。 - 终端环境配置只属于便捷脚本:Kitty 可选安装后可通过官方 kitten 启用 Solarized Dark;Oh My Zsh 使用不切换 shell 的 unattended 安装;Powerlevel10k 将仓库内置的 `config/p10k.zsh`(Rainbow/ASCII 单行紧凑主题)部署到 `~/.config/my-pi/p10k.zsh`(遵循 `XDG_CONFIG_HOME`),不得覆盖用户自己的 `~/.p10k.zsh`。`.zshrc` 中脚本拥有的内容必须使用 `# >>> my-pi:<id> >>>` / `# <<< my-pi:<id> <<<` 受管块,更新时只替换块内内容;标记不完整或重复时拒绝修改。实际修改前必须创建带时间戳的备份。`git`、`zsh-autosuggestions`、`zsh-syntax-highlighting` 在安装流程中逐项询问后才安装或启用。
- 原生远程压缩限定为 `openai-codex` + `openai-codex-responses` 与 `sub-api` + `openai-responses`,默认在 turn boundary 达到 effective 下界 90% 时触发。`pi-compaction/src/native-provider.ts` 统一资格与传输选择:Codex 保留原 endpoint/JWT/account ID;SubAPI 从显式 `baseUrl` 解析公共 `/responses`,使用配置的 API key 和请求头,不解析 ChatGPT account ID、不硬编码网关域名,也不调用独立 `/responses/compact`。两者复用 projection、`compaction_trigger`、SSE 结果、native checkpoint 恢复和 portable 模型切换摘要;SubAPI 部署须支持原生 compaction v2。普通聊天支持不代表压缩支持,失败回退 Pi,不自动改写用户模型配置。 - 原生远程压缩限定为 `openai-codex` + `openai-codex-responses` 与 `sub-api` + `openai-responses`,默认在 turn boundary 达到 effective 下界 90% 时触发。`pi-compaction/src/native-provider.ts` 统一资格与传输选择:Codex 保留原 endpoint/JWT/account ID;SubAPI 从显式 `baseUrl` 解析公共 `/responses`,使用配置的 API key 和请求头,不解析 ChatGPT account ID、不硬编码网关域名,也不调用独立 `/responses/compact`。两者复用 projection、`compaction_trigger`、SSE 结果、native checkpoint 恢复和 portable 模型切换摘要;SubAPI 部署须支持原生 compaction v2。普通聊天支持不代表压缩支持,失败回退 Pi,不自动改写用户模型配置。
- SubAPI 原生压缩认证必须保留非空显式 `Authorization`(大小写不敏感)的优先级;没有该头时才使用非空 API key 生成 Bearer,均缺失时可接受非空 `cf-aig-authorization` 代理认证头。不得要求 header-only 配置额外提供假 API key,也不得生成 `Bearer undefined`;空白或缺失凭据在网络请求前拒绝并回退 Pi。Codex 仍必须提供有效 JWT,不能用请求头绕过。 - SubAPI 原生压缩认证必须保留非空显式 `Authorization`(大小写不敏感)的优先级;没有该头时才使用非空 API key 生成 Bearer,均缺失时可接受非空 `cf-aig-authorization` 代理认证头。不得要求 header-only 配置额外提供假 API key,也不得生成 `Bearer undefined`;空白或缺失凭据在网络请求前拒绝并回退 Pi。Codex 仍必须提供有效 JWT,不能用请求头绕过。
- 官方 Hippo Extension 在已初始化项目的 session start 注入上下文、过滤捕获工具错误,并在 session shutdown 执行 `hippo sleep`。 - 记忆由 `pi-hermes-memory` 统一负责:默认 `policy-only` 模式只注入 `<memory-policy>` 指令而不注入记忆正文,因此它不改变 system prompt 前缀、对 DeepSeek 前缀缓存友好;模型需要历史上下文时主动调用 `memory_search` / `session_search`,写入用 `memory_add` / `memory_replace` / `memory_remove`,过程记忆用 `skill_manage`。
- Hippo Extension 直接使用 PATH 中的官方全局 `hippo` CLI;`install.sh` 只在 CLI 缺失且用户明确确认后安装 `config/hippo-memory-version` 指定的 npm 版本,不得扫描项目、选择初始化目录或执行 `hippo init`。用户必须在自己选择的项目目录中手动初始化。 - 六个记忆工具必须由 `pi-tool-search` 的 `alwaysEnabled` 常驻,不得放入动态组:注入的 policy 会在普通工作中指示模型调用它们,模型看不到 schema 就会静默放弃而不是去 `tool_search`;常驻同时保持请求前缀稳定。它们只写扩展自己的 agent 目录数据,不触碰工作区,因此在权限策略中默认 `allow`,避免每轮 AutoReview 往返。
- `update.sh` 只升级已经安装且属于 npm 全局包的 Hippo CLI;必须从 `pi update` 后的组合包读取目标版本,先用 `npm view` 确认,再比较本地版本,版本相同不得重新安装,CLI 缺失或来源未知时保留现状。 - `pi-hermes-memory` 必须注册在 `pi-compaction` 之前。`ExtensionRunner.emit()` 在第一个返回 `{ cancel: true }` 的 `session_before_compact` handler 处立即停止,而 `pi-compaction` 在判定无需压缩时正是这样返回;注册在其后的 handler 会被跳过,从而静默禁用 `flushOnCompact`。
- `uninstall.sh` 只在根包移除成功后询问是否卸载 npm 全局 Hippo CLI,默认拒绝;不得删除项目 `.hippo/`、全局记忆或旧 Hermes 数据。 - `pi-hermes-memory` 依赖 `better-sqlite3@13`,运行时实际 `dlopen` 的是包内 N-API 预编译二进制 `prebuilds/<platform>.node`,不依赖 node-gyp 编译产物。npm 会因该包存在 `binding.gyp` 而推断出隐式 `node-gyp rebuild`,该步骤既无必要也不得批准;根 `allowScripts` 必须保持不新增条目,同时保留既有的 `better-sqlite3@12.11.1` 放行。
- `skill_manage` 是 `pi-hermes-memory` 提供的能力,不是 Pi 宿主能力:Pi 只发现并加载 skill 目录(`~/.pi/agent/skills/` 加扩展通过 `resources_discover` 注册的目录),没有创建 skill 的工具。该扩展同时承担 skill 写入与目录注册;不加载它,`projects-memory/<项目>/skills/` 下的既有 skill 也不会被 Pi 发现。
- `install.sh` 默认从 `git:git@bitbucket.org:siakitem/my-pi.git` 安装,并允许用 `PI_PACKAGE_SOURCE` 覆盖来源;组合包安装失败时立即停止,机器级依赖安装失败时继续检查其余依赖并最终返回非零状态。 - `install.sh` 默认从 `git:git@bitbucket.org:siakitem/my-pi.git` 安装,并允许用 `PI_PACKAGE_SOURCE` 覆盖来源;组合包安装失败时立即停止,机器级依赖安装失败时继续检查其余依赖并最终返回非零状态。
- CodeGraph 的可选安装使用官方远程安装脚本;macOS Kitty 使用 Homebrew cask,其他系统使用 Kitty 官方安装器;Kotlin LSP、Java 21 和 JDT LS 使用 Homebrew;Oh My Zsh 使用官方安装器,Powerlevel10k 与两个第三方 Zsh 插件从各自官方 Git 仓库克隆。升级时 Git checkout、Homebrew 包、Kitty 官方安装和 CodeGraph 都必须先比较本地与远端版本,版本一致时不得重新下载安装;无法可靠比较时保留现状并报告。缺少 Homebrew、curl 或 git 时保留已经完成的组合包安装并报告失败。 - CodeGraph 的可选安装使用官方远程安装脚本;macOS Kitty 使用 Homebrew cask,其他系统使用 Kitty 官方安装器;Kotlin LSP、Java 21 和 JDT LS 使用 Homebrew;Oh My Zsh 使用官方安装器,Powerlevel10k 与两个第三方 Zsh 插件从各自官方 Git 仓库克隆。升级时 Git checkout、Homebrew 包、Kitty 官方安装和 CodeGraph 都必须先比较本地与远端版本,版本一致时不得重新下载安装;无法可靠比较时保留现状并报告。缺少 Homebrew、curl 或 git 时保留已经完成的组合包安装并报告失败。
- `install.sh` 不检查默认不需要的 `rtk` CLI;`update.sh` 和 `uninstall.sh` 使用同一个 `PI_PACKAGE_SOURCE` 规则分别调用 `pi update` 和 `pi remove`。运行 `update.sh` 视为明确授权升级已安装组件及同步已有受管配置;其中 Tool Search 只能自动迁移完整匹配仓库旧默认快照的配置,不能逐字段猜测或覆盖自定义值。不得安装缺失组件或改写受管块之外的用户配置;卸载仅可在用户当次明确确认后移除 npm 全局 Hippo CLI,其他共享机器级工具保持不变。 - `install.sh` 不检查默认不需要的 `rtk` CLI;`update.sh` 和 `uninstall.sh` 使用同一个 `PI_PACKAGE_SOURCE` 规则分别调用 `pi update` 和 `pi remove`。运行 `update.sh` 视为明确授权升级已安装组件及同步已有受管配置;其中 Tool Search 只能自动迁移完整匹配仓库已知默认快照的配置,不能逐字段猜测或覆盖自定义值。不得安装缺失组件或改写受管块之外的用户配置;卸载不得删除用户记忆数据,其他共享机器级工具保持不变。
## 修改边界 ## 修改边界
@@ -124,7 +125,7 @@
- 保留原项目的 `LICENSE`、版权信息和必要的来源说明。 - 保留原项目的 `LICENSE`、版权信息和必要的来源说明。
- 扩展运行目录中的用户 `policy.jsonc` / `hard-guards.jsonc`、旧 `config.json`、审批 ledger/proposals、日志、构建产物、覆盖率目录和依赖目录属于本地状态,不应提交;`config/pi-permission-policy.jsonc`、`config/pi-permission-hard-guards.jsonc`、`config/pi-hashline-edit.json` 与 `config/lsp.json` 分别是新安装权限种子、Hashline 默认行为和 LSP 后端的组合包权威源配置,必须提交并维护。 - 扩展运行目录中的用户 `policy.jsonc` / `hard-guards.jsonc`、旧 `config.json`、审批 ledger/proposals、日志、构建产物、覆盖率目录和依赖目录属于本地状态,不应提交;`config/pi-permission-policy.jsonc`、`config/pi-permission-hard-guards.jsonc`、`config/pi-hashline-edit.json` 与 `config/lsp.json` 分别是新安装权限种子、Hashline 默认行为和 LSP 后端的组合包权威源配置,必须提交并维护。
- 外部 Pi 扩展依赖必须在根 `package.json` 中使用精确版本,自维护 Fast mode 等扩展必须使用本地 `file:` 依赖,并更新根 `package-lock.json`;不要用仓库级 `.pi/settings.json` 代替组合包依赖。 - 外部 Pi 扩展依赖必须在根 `package.json` 中使用精确版本,自维护 Fast mode 等扩展必须使用本地 `file:` 依赖,并更新根 `package-lock.json`;不要用仓库级 `.pi/settings.json` 代替组合包依赖。
- 需要原生构建的依赖只按锁定版本加入根 `allowScripts`;当前 `better-sqlite3@12.11.1` 仍由 Context Mode 使用并保持精确放行。官方 Hippo CLI 是独立机器级 npm 包,不得因此向根包批量批准其他 install scripts。 - 需要原生构建的依赖只按锁定版本加入根 `allowScripts`;当前 `better-sqlite3@12.11.1` 仍由 Context Mode 使用并保持精确放行。`pi-hermes-memory` 的 `better-sqlite3@13` 运行时使用包内 N-API 预编译二进制,npm 因 `binding.gyp` 推断出的 `node-gyp rebuild` 无需执行,不得为此新增放行条目。
- Pi 核心包只作为宿主 peer dependencies,不得在组合包内再安装或打包一套 Pi runtime;保留根 `.npmrc` 的 peer 安装策略。 - Pi 核心包只作为宿主 peer dependencies,不得在组合包内再安装或打包一套 Pi runtime;保留根 `.npmrc` 的 peer 安装策略。
- `pi-tool-search/` 的根依赖必须保持 `file:./pi-tool-search`,组合包直接加载 `pi-tool-search/extensions/index.ts`;同步上游时先核对本地增量动态工具改造,禁止用旧 npm 入口或上游旧宿主实现直接覆盖。 - `pi-tool-search/` 的根依赖必须保持 `file:./pi-tool-search`,组合包直接加载 `pi-tool-search/extensions/index.ts`;同步上游时先核对本地增量动态工具改造,禁止用旧 npm 入口或上游旧宿主实现直接覆盖。
- 新引入或同步自维护扩展时,必须先联网确认其官方源码仓库地址,再从官方 Git 仓库的明确 tag/commit 拉取源码快照;禁止以 npm tarball、npm 缓存、`node_modules`、`dist/` 或其他预编译发布产物作为导入源。npm registry 只能用于核对包名、版本和发布元数据,不能替代源码仓库。 - 新引入或同步自维护扩展时,必须先联网确认其官方源码仓库地址,再从官方 Git 仓库的明确 tag/commit 拉取源码快照;禁止以 npm tarball、npm 缓存、`node_modules`、`dist/` 或其他预编译发布产物作为导入源。npm registry 只能用于核对包名、版本和发布元数据,不能替代源码仓库。
@@ -171,12 +172,16 @@
- 修改 reviewer 策略、可信证据边界、配置结构、hard-guard precedence、ledger、后台分析、模型解析或 authorizer/lifecycle 注册时,必须同步更新 README、配置文档、架构文档、schema 和相应测试。 - 修改 reviewer 策略、可信证据边界、配置结构、hard-guard precedence、ledger、后台分析、模型解析或 authorizer/lifecycle 注册时,必须同步更新 README、配置文档、架构文档、schema 和相应测试。
## `hippo-memory-pi` 开发约定 ## `pi-hermes-memory` 开发约定
- 扩展入口是 `hippo-memory-pi/index.ts`,来源记录是 `hippo-memory-pi/UPSTREAM.md`;只从官方 `kitfunso/hippo-memory` 的明确 tag/commit 移植 `extensions/pi-extension/`,保留官方 README、LICENSE 和版权。 - 扩展入口是 `pi-hermes-memory/src/index.ts`,来源记录是 `pi-hermes-memory/UPSTREAM.md`;只从官方 `chandra447/pi-hermes-memory` 的明确 tag/commit 移植并保留官方 README、LICENSE 和版权。
- 官方 npm tarball 当前不包含 Pi Extension,因此根包直接打包并加载本地源码;不得同时加载第三方 `@the-forge-flow/hippo-memory-pi` 或第二个 Hippo Pi Extension。 - 根包必须通过 `file:./pi-hermes-memory` 提供依赖并直接加载仓库源码;不得同时安装或加载 npm 预编译入口,也不得保留第二个记忆扩展(`hippo-memory-pi` 已移除)。
- CLI 版本权威源是 `config/hippo-memory-version`;修改它时必须同步上游源码快照、README、AGENTS、安装/升级 mock 测试和扩展加载验证。 - `package.json` 的 `pi.extensions` 必须把 `./pi-hermes-memory/src/index.ts` 排在 `./pi-compaction/index.ts` 之前,否则 `flushOnCompact` 会被 `pi-compaction` 的 `{ cancel: true }` 静默跳过;根 `tests/extension-load.test.ts` 必须断言该顺序。
- 扩展依赖 PATH 中的全局 `hippo`。CLI 缺失时必须 fail-safe,不得阻止其他扩展加载;项目未执行 `hippo init` 时不得擅自创建 `.hippo/`。 - 六个记忆工具必须在 `pi-tool-search/extensions/config.ts` 的 `BUNDLE_TOOL_SEARCH_DEFAULTS.alwaysEnabled` 中常驻,并由 `pi-tool-search/extensions/bundle-groups.ts` 的 `memory-recall` / `memory-management` 分组提供目录元数据;`context_tree_query` 继续留在 `memory-recall`。新增、删除或重命名这些工具时必须同步 `bundle-groups.ts`、Tool Search 测试和权限策略。
- 依赖必须保持 `better-sqlite3@^13.0.3`:13.x 运行时使用包内 N-API 预编译二进制 `prebuilds/<platform>.node`,不依赖 node-gyp 编译产物,必须能由 npm 嵌套安装并与根 `better-sqlite3@12.11.1` 并存。不得把它降级回 12.x(Node 24 下会在进程退出时 SIGABRT),也不得让根包提升为单一版本。
- `devDependencies` 中的 `@earendil-works/*` 必须与根包当前版本精确一致。若写成 `^0.80.2` 这类与根不兼容的范围,npm 会在本目录嵌套安装第二套 Pi runtime,违反"不得在组合包内再安装一套 Pi runtime"的纪律;`typebox`、`tsx`、`typescript`、`@types/better-sqlite3` 同理,必须钉到根已有版本。
- 全局记忆、项目记忆与 skill 位于 `~/.pi/agent/pi-hermes-memory/` 与 `~/.pi/agent/projects-memory/`,配置位于 `~/.pi/agent/hermes-memory-config.json`;这些属于用户数据,安装、升级与卸载脚本不得创建、迁移或删除。
- 修改工具注册、`before_agent_start` 注入策略、`resources_discover` 目录注册、SQLite 迁移或 consolidation 后,必须同步包内 README/CHANGELOG/docs,并运行根扩展联合加载与 Tool Search 测试。
## `pi-extension-codex-fast-mode` 开发约定 ## `pi-extension-codex-fast-mode` 开发约定
@@ -274,7 +279,7 @@
安装、升级、卸载、搜索或 SSH 配置脚本变化时,至少运行 `sh -n install.sh`、`sh -n update.sh`、`sh -n uninstall.sh`、`sh -n search_config.sh`、`sh -n ssh_config.sh` 和 ShellCheck,并核对脚本仍具有可执行权限、仍包含在根 `package.json` 的 `files` 中、README 描述与实际流程一致。Git 升级中断恢复必须用隔离 agent dir 和 mock `pi` / `git` 覆盖合法标记连续清理后成功、无标记保持失败、checkout 身份不匹配拒绝清理及单次 Pi 重试边界。Tool Search settings 迁移必须用隔离 agent dir 覆盖完整旧快照迁移、当前值幂等、自定义配置保留、无效 JSON fail-safe、备份/权限、`pi update` 失败不迁移和 `PI_CODING_AGENT_DIR`。搜索配置测试只能使用虚拟 key 和隔离 HOME;SSH 配置测试必须使用隔离 HOME、虚拟凭据与 mock transport,不得连接真实服务器或把真实密码写入测试输出。涉及真实 `pi install`、`pi update`、`pi remove`、Homebrew、远程安装器、Git 克隆、真实 SSH 连接或真实用户终端配置的端到端验证属于外部写操作,未经明确要求不得执行;可以使用隔离的临时 HOME 和 mock 命令验证分支行为。 安装、升级、卸载、搜索或 SSH 配置脚本变化时,至少运行 `sh -n install.sh`、`sh -n update.sh`、`sh -n uninstall.sh`、`sh -n search_config.sh`、`sh -n ssh_config.sh` 和 ShellCheck,并核对脚本仍具有可执行权限、仍包含在根 `package.json` 的 `files` 中、README 描述与实际流程一致。Git 升级中断恢复必须用隔离 agent dir 和 mock `pi` / `git` 覆盖合法标记连续清理后成功、无标记保持失败、checkout 身份不匹配拒绝清理及单次 Pi 重试边界。Tool Search settings 迁移必须用隔离 agent dir 覆盖完整旧快照迁移、当前值幂等、自定义配置保留、无效 JSON fail-safe、备份/权限、`pi update` 失败不迁移和 `PI_CODING_AGENT_DIR`。搜索配置测试只能使用虚拟 key 和隔离 HOME;SSH 配置测试必须使用隔离 HOME、虚拟凭据与 mock transport,不得连接真实服务器或把真实密码写入测试输出。涉及真实 `pi install`、`pi update`、`pi remove`、Homebrew、远程安装器、Git 克隆、真实 SSH 连接或真实用户终端配置的端到端验证属于外部写操作,未经明确要求不得执行;可以使用隔离的临时 HOME 和 mock 命令验证分支行为。
Hippo 脚本变化必须额外用隔离 HOME/PATH 和 mock `pi`、`npm`、`hippo` 验证:安装缺失 CLI 但不调用 `hippo init`、相同版本不升级、不同版本精确升级、未知来源 CLI 不替换、卸载默认保留、明确确认后只卸载 npm 全局包且保留数据。不得在测试中执行真实全局 npm 写入或修改真实 `.hippo/`。 记忆扩展变化必须额外验证:`pi-hermes-memory` 在隔离临时 Pi agent 目录中可加载且注册 6 个工具、`pi.extensions` 中排在 `pi-compaction` 之前、Tool Search 目录把六个工具归入 `memory-recall` / `memory-management` 且全部常驻、权限策略默认 `allow`、`better-sqlite3@13` 嵌套安装与根 `12.11.1` 并存。不得在测试中连接真实数据库或修改真实 `~/.pi/agent/` 记忆数据。
在 `pi-permission-system/` 内至少运行 `npm run typecheck`、`npm run test` 和 `npm run build`。涉及内置 AutoReview、硬防御或后台分析时,还要验证 authorizer 对普通工具与 `path` 的 `allow` / `deny` / `defer`、hard-guard precedence、500 次 checkpoint、owner-only 持久化、shutdown resume 与 same-session follow-up。 在 `pi-permission-system/` 内至少运行 `npm run typecheck`、`npm run test` 和 `npm run build`。涉及内置 AutoReview、硬防御或后台分析时,还要验证 authorizer 对普通工具与 `path` 的 `allow` / `deny` / `defer`、hard-guard precedence、500 次 checkpoint、owner-only 持久化、shutdown resume 与 same-session follow-up。
@@ -295,28 +300,13 @@ Hippo 脚本变化必须额外用隔离 HOME/PATH 和 mock `pi`、`npm`、`hippo
若环境缺少依赖或未执行某项验证,交付时明确说明,不以静态检查代替运行结果。 若环境缺少依赖或未执行某项验证,交付时明确说明,不以静态检查代替运行结果。
<!-- hippo:start --> <!-- pi-hermes-memory:start -->
## Project Memory (Hippo) ## 项目记忆(pi-hermes-memory)
At the start of every task, run: 持久记忆由 `pi-hermes-memory` 提供,默认 `policy-only` 模式:system prompt 只注入 `<memory-policy>` 指令,不注入记忆正文。
```bash
hippo context --auto --budget 1500
```
Read the output before writing any code.
On errors or unexpected behaviour: - 任务开始时若可能依赖历史上下文,先调用 `memory_search`;需要历史对话原话时用 `session_search`。
```bash - 发现值得持久化的事实、偏好、约定、纠正或失败教训时,主动用 `memory_add` 保存,不必等用户开口;更新或删除用 `memory_replace` / `memory_remove`。
hippo remember "<description of what went wrong>" --error - 完成需要反复试错、或用户教了具体流程的任务后,用 `skill_manage` 把可复用流程沉淀为 skill(`scope: "global"` 跨项目,`scope: "project"` 本项目)。
``` - 记忆与 skill 数据位于 `~/.pi/agent/pi-hermes-memory/` 与 `~/.pi/agent/projects-memory/`,属于用户数据,不得提交、自动迁移或删除。
<!-- pi-hermes-memory:end -->
On task completion:
```bash
hippo outcome --good
```
When Hippo's Codex wrapper is installed, session-end capture runs automatically.
If the wrapper is not installed, capture a brief summary manually:
```bash
hippo capture --stdin <<< '<decisions, errors, lessons — 2-5 bullets>'
```
<!-- hippo:end -->
+40 -25
View File
@@ -11,7 +11,7 @@
- `extensions/context-view.ts`:通过 `/context` 只读显示 `pi-compaction` 的 effective context,不建立第二套上下文口径。 - `extensions/context-view.ts`:通过 `/context` 只读显示 `pi-compaction` 的 effective context,不建立第二套上下文口径。
- 本仓库维护的 `pi-extension-codex-fast-mode`:基于 `@firstpick/pi-extension-codex-fast-mode@0.1.1`,为 Codex provider 提供跨会话持久化的 `/fast-mode`。 - 本仓库维护的 `pi-extension-codex-fast-mode`:基于 `@firstpick/pi-extension-codex-fast-mode@0.1.1`,为 Codex provider 提供跨会话持久化的 `/fast-mode`。
- `pi-lsp@0.1.7`:为 TypeScript/JavaScript、Kotlin 和 Java 提供 LSP 诊断、跳转与符号工具;TypeScript 后端由组合包内置。 - `pi-lsp@0.1.7`:为 TypeScript/JavaScript、Kotlin 和 Java 提供 LSP 诊断、跳转与符号工具;TypeScript 后端由组合包内置。
- `hippo-memory-pi/`:从官方 `hippo-memory v1.33.0` 源码导入的 Pi Extension,提供项目记忆注入、错误捕获和 sleep consolidation。 - `pi-hermes-memory/`:从官方 `pi-hermes-memory v0.9.10` 源码导入的持久记忆扩展,提供全局/项目/用户/失败四类记忆、SQLite FTS5 会话与记忆检索、`SKILL.md` 过程记忆、correction 检测与自动 consolidation。
- 本仓库维护的 `pi-minimal-footer`:基于 `@ogulcancelik/pi-minimal-footer@0.1.10`,用紧凑的上下文仪表和订阅用量条替换默认 footer,并在 Codex Fast 模式开启时显示 `Fast on`。 - 本仓库维护的 `pi-minimal-footer`:基于 `@ogulcancelik/pi-minimal-footer@0.1.10`,用紧凑的上下文仪表和订阅用量条替换默认 footer,并在 Codex Fast 模式开启时显示 `Fast on`。
- 本仓库维护的 `pi-notify`:基于 `@smoose/pi-notify@0.1.1`,使用 Kitty OSC 99 在 Pi 完全 settled 或权限系统即将请求人工确认时通知,并支持点击精确聚焦来源 Kitty 窗口。 - 本仓库维护的 `pi-notify`:基于 `@smoose/pi-notify@0.1.1`,使用 Kitty OSC 99 在 Pi 完全 settled 或权限系统即将请求人工确认时通知,并支持点击精确聚焦来源 Kitty 窗口。
- 本仓库维护的 `pi-compaction`:以增量投影统一确定性工具结果压缩、后台语义摘要、effective token 计量、`context_tree_query`、portable checkpoint 与 Codex native compaction。 - 本仓库维护的 `pi-compaction`:以增量投影统一确定性工具结果压缩、后台语义摘要、effective token 计量、`context_tree_query`、portable checkpoint 与 Codex native compaction。
@@ -108,14 +108,22 @@ PI_PACKAGE_SOURCE="git:git@bitbucket.org:siakitem/my-pi.git" ./update.sh
根 `package.json` 对外部依赖使用精确版本,对本仓库维护的包(包括 Codex Fast mode)使用 `file:` 路径;`package-lock.json` 固定完整依赖树。不要再用项目级 `.pi/settings.json` 重复安装这些扩展,否则同一扩展可能被加载两次。 根 `package.json` 对外部依赖使用精确版本,对本仓库维护的包(包括 Codex Fast mode)使用 `file:` 路径;`package-lock.json` 固定完整依赖树。不要再用项目级 `.pi/settings.json` 重复安装这些扩展,否则同一扩展可能被加载两次。
Hippo 官方 Pi Extension 存在于上游 Git 仓库的 `extensions/pi-extension/`,但没有包含在 持久记忆由本仓库维护的 `pi-hermes-memory` 提供。它从官方 `chandra447/pi-hermes-memory`
`hippo-memory@1.33.0` npm tarball 中。本仓库从官方 `v1.33.0` / 的 `v0.9.10` / `df624056a2e8c68bc89ba0a81e1c120856ec9665` 导入源码到 `pi-hermes-memory/` 并直接加载,
`e928179a3b35e8fe5837878aed071d6025ced45c` 导入源码到 `hippo-memory-pi/` 并直接加载; 通过本地 `file:` 依赖安装,不需要额外的机器级 CLI。
匹配的 CLI 版本由 `config/hippo-memory-version` 声明。`install.sh` 会在 CLI 缺失时询问是否执行
`npm install -g hippo-memory@<固定版本>`,但安装脚本不会选择或初始化项目。进入目标项目后可在 Pi 中执行 默认 `policy-only` 模式只把 `<memory-policy>` 指令注入 system prompt,不注入记忆正文,因此不会改变
`/plugin_init`:命令会显示当前目录并要求确认,以 `.codegraph/codegraph.db` 和 `.hippo/hippo.db` 为 prompt 前缀、对前缀缓存友好。模型需要历史上下文时主动调用 `memory_search` / `session_search`,
初始化标志,只为缺失状态运行 `codegraph init` 与 `hippo init`;全部成功后通过 Pi 官方热重载重新连接 CodeGraph MCP 并重新触发 Hippo 写入用 `memory_add` / `memory_replace` / `memory_remove`,过程记忆用 `skill_manage`。这六个工具由
session start。也可以继续在终端手动执行两个 CLI 的 `init` 命令。 Tool Search 常驻,保证 policy 指示与模型可见工具一致。
记忆与 skill 数据位于 `~/.pi/agent/pi-hermes-memory/`(全局)与 `~/.pi/agent/projects-memory/<项目>/`
(项目级),可选配置为 `~/.pi/agent/hermes-memory-config.json`。这些是用户数据,安装、升级与卸载脚本
不会创建、迁移或删除。
进入目标项目后可在 Pi 中执行 `/plugin_init`:命令会显示当前目录并要求确认,以
`.codegraph/codegraph.db` 为初始化标志,只为缺失状态运行 `codegraph init`;全部成功后通过 Pi 官方热重载
重新连接 CodeGraph MCP。也可以继续在终端手动执行 `codegraph init`。
默认配置不需要系统 `rtk` CLI。只有以后在 `/rtk` 中主动开启 RTK command rewriting 时,才需要另外安装 `rtk` 可执行文件。 默认配置不需要系统 `rtk` CLI。只有以后在 `/rtk` 中主动开启 RTK command rewriting 时,才需要另外安装 `rtk` 可执行文件。
@@ -261,13 +269,13 @@ codegraph --version
codegraph status codegraph status
``` ```
在当前项目需要同时初始化 CodeGraph 和 Hippo 时,可直接执行: 在当前项目需要初始化 CodeGraph 时,可直接执行:
```text ```text
/plugin_init /plugin_init
``` ```
该命令拒绝文件系统根目录和用户主目录,先确认两个 CLI 均可用,再依次初始化缺失数据库状态。任一步失败都不会热重载,也不会回滚此前已成功完成的初始化;两者已初始化时仍可用该命令快速热重载扩展。 该命令拒绝文件系统根目录和用户主目录,先确认 CLI 可用,再初始化缺失数据库状态。失败时不会热重载,也不会回滚此前已成功完成的初始化;已初始化时仍可用该命令快速热重载扩展。
### 统一权限系统、硬防御与 AutoReview ### 统一权限系统、硬防御与 AutoReview
@@ -310,23 +318,30 @@ brew install JetBrains/utils/kotlin-lsp
JDT LS 需要 Java 21 或更高版本;可使用带 `jdtls` wrapper 的官方发行包或系统包。 JDT LS 需要 Java 21 或更高版本;可使用带 `jdtls` wrapper 的官方发行包或系统包。
### Hippo Memory ### 持久记忆(pi-hermes-memory)
官方扩展要求全局 `hippo` CLI,并要求目标项目存在 `.hippo/`。组合包加载仓库内官方扩展源码, 组合包加载仓库内 `pi-hermes-memory` 源码并注册六个工具:
注册 `hippo_recall`、`hippo_remember`、`hippo_outcome`、`hippo_status` 和 `hippo_context` 五个工具。
Session 启动时它注入项目记忆上下文,失败工具结果经过噪声过滤、每 Session 上限和去重后自动记录,
Session 关闭时运行 `hippo sleep`,从 Git 学习、整理记忆并执行官方共享逻辑。项目 `.hippo/` 是
本机记忆状态,本仓库默认忽略提交。
Hermes 的 `memory_add` / `session_search` / `skill_manage` 与 Markdown/SQLite 数据不会被 Hippo 自动迁移; | 工具 | 作用 |
切换扩展不会删除 `~/.pi/agent/pi-hermes-memory/` 或 `~/.pi/agent/projects-memory/` 中的旧数据。需要保留的 |---|---|
旧记忆应另行审核后导入,不在安装、升级或卸载流程中自动转换或删除。 | `memory_search` | 检索持久记忆:全局、用户、项目、失败四类 |
| `session_search` | 在 SQLite FTS5 trigram 索引中检索历史会话原话 |
| `memory_add` | 新增一条持久记忆 |
| `memory_replace` | 按子串匹配修改已有记忆 |
| `memory_remove` | 按子串匹配删除已有记忆 |
| `skill_manage` | 创建、查看、修补、重写或删除 `SKILL.md` 过程记忆 |
`install.sh` 只对缺失的 Hippo CLI 询问一次,默认拒绝;缺少 npm 时保留已完成的组合包安装并报告失败。 默认 `policy-only` 模式不在 system prompt 中注入记忆正文,只注入 `<memory-policy>` 指令告诉模型何时
脚本不会扫描仓库、选择目录或执行 `hippo init`。`update.sh` 只处理已经存在且确认属于 npm 全局安装的 检索、何时写入。因此首轮 token 成本很低,也保持 prompt 前缀稳定。写工具只写扩展自己的数据目录,
Hippo CLI:从升级后的组合包读取固定版本,先通过 npm 确认版本存在,再比较本地版本,只有不同时才升级。 不触碰工作区。
`uninstall.sh` 移除组合包后询问是否一并执行 `npm uninstall -g hippo-memory`,默认保留;无论选择什么,
`.hippo/` 和用户记忆数据都不会被脚本删除。 `skill_manage` 是扩展提供的能力:Pi 宿主只负责发现并加载 skill 目录,本身没有创建 skill 的工具。
该扩展同时注册 `~/.pi/agent/projects-memory/<项目>/skills/` 供 Pi 发现,因此不加载它时,这些既有
skill 也不会出现在 Pi 的 skill 索引中。
记忆与 skill 位于 `~/.pi/agent/pi-hermes-memory/`(全局)和 `~/.pi/agent/projects-memory/<项目>/`
(项目级),配置位于 `~/.pi/agent/hermes-memory-config.json`。这些是用户数据:安装、升级和卸载脚本都
不会创建、迁移或删除它们,也不会自动转换旧 Hippo `.hippo/` 数据。
### Kitty 完成与权限确认通知 ### Kitty 完成与权限确认通知
-1
View File
@@ -1 +0,0 @@
1.33.0
+11
View File
@@ -45,6 +45,17 @@
"tool_search": "allow", "tool_search": "allow",
"ask_user_question": "allow", "ask_user_question": "allow",
// Hermes memory tools write only to the extension's own agent-directory
// stores and never touch the workspace, so they stay allowed. memory_search
// is invoked by the injected memory policy on most turns and must not
// trigger an AutoReview round trip each time.
"memory_search": "allow",
"session_search": "allow",
"memory_add": "allow",
"memory_replace": "allow",
"memory_remove": "allow",
"skill_manage": "allow",
"ssh_connect": "ask", "ssh_connect": "ask",
"ssh_cd": "ask", "ssh_cd": "ask",
"ssh_read": "ask", "ssh_read": "ask",
+4 -10
View File
@@ -16,8 +16,8 @@ export type PluginInitExecutor = (
) => Promise<ExecResult>; ) => Promise<ExecResult>;
interface InitTarget { interface InitTarget {
name: "CodeGraph" | "Hippo"; name: "CodeGraph";
command: "codegraph" | "hippo"; command: "codegraph";
database: string; database: string;
initArgs(cwd: string): string[]; initArgs(cwd: string): string[];
} }
@@ -29,12 +29,6 @@ const INIT_TARGETS: InitTarget[] = [
database: join(".codegraph", "codegraph.db"), database: join(".codegraph", "codegraph.db"),
initArgs: (cwd) => ["init", cwd], initArgs: (cwd) => ["init", cwd],
}, },
{
name: "Hippo",
command: "hippo",
database: join(".hippo", "hippo.db"),
initArgs: () => ["init"],
},
]; ];
async function isFile(path: string): Promise<boolean> { async function isFile(path: string): Promise<boolean> {
@@ -100,7 +94,7 @@ export async function initializeMissingPlugins(
): Promise<void> { ): Promise<void> {
const projectRoot = validatePluginInitCwd(cwd); const projectRoot = validatePluginInitCwd(cwd);
// Preflight both CLIs before creating either project's state. // Preflight every CLI before creating project state.
for (const target of INIT_TARGETS) { for (const target of INIT_TARGETS) {
await executeChecked(exec, target, ["--version"], projectRoot, "检测"); await executeChecked(exec, target, ["--version"], projectRoot, "检测");
} }
@@ -123,7 +117,7 @@ export async function initializeMissingPlugins(
export default function pluginInitExtension(pi: ExtensionAPI): void { export default function pluginInitExtension(pi: ExtensionAPI): void {
pi.registerCommand("plugin_init", { pi.registerCommand("plugin_init", {
description: "Initialize CodeGraph and Hippo for the current project, then hot-reload Pi", description: "Initialize CodeGraph for the current project, then hot-reload Pi",
handler: async (_args, ctx) => { handler: async (_args, ctx) => {
if (!ctx.hasUI) return; if (!ctx.hasUI) return;
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 Keith So
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-77
View File
@@ -1,77 +0,0 @@
# Hippo Memory - Pi Extension
> **my-pi packaging note:** this official source is bundled and loaded directly by the root package; do not copy it to another Pi extension directory. Use the root `install.sh`, `update.sh`, and `uninstall.sh` prompts to manage the matching global CLI. Upstream's original manual instructions are retained below for reference.
Biologically-inspired memory for [Pi coding agent](https://github.com/badlogic/pi-mono). Memories decay by default, retrieval strengthens them, errors stick longer, and sleep consolidation compresses episodes into patterns.
## Install
```bash
# Install hippo CLI
npm install -g hippo-memory
# Init hippo in your project (or scan all repos)
hippo init
# or: hippo init --scan ~
# Copy extension to Pi's extensions directory
cp -r extensions/pi-extension ~/.pi/agent/extensions/hippo-memory
```
Or for project-level:
```bash
cp -r extensions/pi-extension .pi/extensions/hippo-memory
```
## What it does
| Automatic | When |
|---|---|
| Injects memory context | Session start |
| Captures tool errors | After any failed tool call |
| Learns from git | On `hippo sleep` (session end) |
| Consolidates memories | On `hippo sleep` (session end) |
| Shares to global store | On `hippo sleep` (session end) |
Error capture has three filters to prevent memory pollution:
1. Noise pattern filter (timeouts, ECONNREFUSED, etc.)
2. Per-session rate limit (max 5 error memories)
3. Per-session deduplication
## Tools
The extension registers 5 tools for the LLM:
| Tool | Description |
|---|---|
| `hippo_recall` | Search memories by topic |
| `hippo_remember` | Store a new memory |
| `hippo_outcome` | Report if memories were helpful |
| `hippo_status` | Check memory health |
| `hippo_context` | Smart context from git state |
## Config
Edit the `DEFAULT_CONFIG` object at the top of `index.ts`:
```typescript
const DEFAULT_CONFIG = {
budget: 4000, // Token budget for recall
contextBudget: 1500, // Token budget for auto-context
framing: 'observe', // observe | suggest | assert
autoContext: true, // Inject context at session start
autoLearn: true, // Capture tool errors
autoSleep: true, // Run consolidation on exit
maxErrorsPerSession: 5, // Error capture rate limit
};
```
## Requirements
- `hippo-memory` CLI installed globally: `npm install -g hippo-memory`
- `.hippo/` initialized in your project: `hippo init`
- Pi coding agent
## How it differs from the OpenClaw plugin
Same core behavior. The Pi extension uses Pi's event system (`session_start`, `tool_result`, `session_shutdown`) instead of OpenClaw's hook system. Both use `execFileSync` with args arrays (no shell injection).
-11
View File
@@ -1,11 +0,0 @@
# 上游来源
- 上游仓库:<https://github.com/kitfunso/hippo-memory>
- 导入版本:`v1.33.0`
- 导入 commit:`e928179a3b35e8fe5837878aed071d6025ced45c`
- 导入源码路径:`extensions/pi-extension/`
- 初始导入日期:2026-08-19
本目录包含官方 Hippo Memory Pi 扩展源码。该源码存在于上游 Git 仓库中,但被排除在 `hippo-memory@1.33.0` 的 npm tarball 之外。该扩展由 `my-pi` 直接维护,不是 submodule,也不保留嵌套 `.git` 目录。
匹配的 CLI 版本声明在 `config/hippo-memory-version` 中,由组合包便捷脚本作为可选的机器级依赖管理。未来同步上游时必须从明确 tag/commit 移植,并与本地改动对照审查,不得整体覆盖本目录。
-356
View File
@@ -1,356 +0,0 @@
/**
* Hippo Memory - Pi Coding Agent Extension
*
* Auto-injects memory context at session start, captures tool errors,
* and runs consolidation on shutdown. Registers hippo tools for the LLM.
*
* Install: copy to ~/.pi/agent/extensions/hippo-memory/
* Or add to .pi/extensions/ in your project.
*
* Requires: hippo-memory CLI installed globally (npm install -g hippo-memory)
*/
import { execFileSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------
interface HippoConfig {
budget: number;
contextBudget: number;
framing: 'observe' | 'suggest' | 'assert';
autoContext: boolean;
autoLearn: boolean;
autoSleep: boolean;
maxErrorsPerSession: number;
}
const DEFAULT_CONFIG: HippoConfig = {
budget: 4000,
contextBudget: 1500,
framing: 'observe',
autoContext: true,
autoLearn: true,
autoSleep: true,
maxErrorsPerSession: 5,
};
// ---------------------------------------------------------------------------
// Noise filtering (matches openclaw-plugin patterns)
// ---------------------------------------------------------------------------
const NOISE_ERROR_PATTERNS: RegExp[] = [
/Local media path is not under an allowed directory/i,
/timed out\.?\s*Restart/i,
/EISDIR:\s*illegal operation on a directory/i,
/Missing required parameter:\s*path/i,
/ENOENT:\s*no such file or directory/i,
/EACCES:\s*permission denied/i,
/EPERM:\s*operation not permitted/i,
/socket hang up/i,
/ECONNREFUSED/i,
/ECONNRESET/i,
/ERR_SOCKET_CONNECTION_TIMEOUT/i,
/net::ERR_/i,
/Navigation timeout/i,
];
function isNoiseError(error: string): boolean {
return NOISE_ERROR_PATTERNS.some((p) => p.test(error));
}
/** True for a string value, without assuming the input's shape — used on
* fields read off the untyped Pi extension event/plugin surface. */
function isString<T>(value: T): value is T & string {
return typeof value === 'string';
}
function hashError(toolName: string, error: string): string {
return `${toolName}::${error.replace(/\s+/g, ' ').trim().slice(0, 80).toLowerCase()}`;
}
// ---------------------------------------------------------------------------
// Session state
// ---------------------------------------------------------------------------
let sessionErrorCount = 0;
const sessionErrorHashes = new Set<string>();
let sessionMemoryCount = 0;
// ---------------------------------------------------------------------------
// Hippo CLI wrapper (no shell, args as array)
// ---------------------------------------------------------------------------
function runHippo(args: readonly string[], cwd?: string): string {
try {
const result = execFileSync('hippo', args, {
cwd: cwd || process.cwd(),
encoding: 'utf8',
timeout: 30_000,
stdio: ['pipe', 'pipe', 'pipe'],
});
// `encoding: 'utf8'` above selects the ExecFileSyncOptionsWithStringEncoding
// overload, so `result` is always a `string` here — no runtime check needed.
return result.trim();
} catch (err: any) {
return err.stdout?.trim() || err.message || 'hippo command failed';
}
}
function hippoAvailable(): boolean {
try {
execFileSync('hippo', ['status'], {
encoding: 'utf8',
timeout: 5000,
stdio: ['pipe', 'pipe', 'pipe'],
});
return true;
} catch {
return false;
}
}
function hippoInitialized(cwd: string): boolean {
return existsSync(join(cwd, '.hippo'));
}
// ---------------------------------------------------------------------------
// Extension entry point
// ---------------------------------------------------------------------------
export default function (pi: any) {
// Check hippo is available
if (!hippoAvailable()) {
console.error('[hippo] hippo-memory CLI not found. Install: npm install -g hippo-memory');
return;
}
const config = { ...DEFAULT_CONFIG };
// -------------------------------------------------------------------------
// Session start: inject memory context
// -------------------------------------------------------------------------
pi.on('session_start', async (_event: any, ctx: any) => {
sessionErrorCount = 0;
sessionErrorHashes.clear();
sessionMemoryCount = 0;
if (!config.autoContext) return;
const cwd = ctx.cwd || process.cwd();
if (!hippoInitialized(cwd)) return;
const context = runHippo(
['context', '--auto', '--budget', String(config.contextBudget), '--framing', config.framing],
cwd,
);
if (context && context.length > 10 && !context.includes('No hippo store')) {
return {
systemPromptAppend: `\n\n## Project Memory (Hippo)\n\n${context}\n\nWhen you learn something important, tell the user to run: hippo remember "<lesson>"\nWhen you encounter errors, suggest: hippo remember "<error description>" --error`,
};
}
});
// -------------------------------------------------------------------------
// Tool errors: auto-capture (with noise filtering + rate limiting + dedup)
// -------------------------------------------------------------------------
if (config.autoLearn) {
pi.on('tool_result', async (event: any, ctx: any) => {
if (!event.isError) return;
const error = isString(event.content)
? event.content
: Array.isArray(event.content)
? event.content.map((c: any) => c.text || '').join(' ')
: '';
if (!error || error.length < 10) return;
// Filter 1: noise
if (isNoiseError(error)) return;
// Filter 2: rate limit
if (sessionErrorCount >= config.maxErrorsPerSession) return;
// Filter 3: dedup
const hash = hashError(event.toolName || 'unknown', error);
if (sessionErrorHashes.has(hash)) return;
const cwd = ctx.cwd || process.cwd();
if (!hippoInitialized(cwd)) return;
const toolTag = (event.toolName || 'unknown')
.toLowerCase()
.replace(/[^a-z0-9-]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 30);
const truncated = error.replace(/\s+/g, ' ').trim().slice(0, 500);
const text = `Tool '${event.toolName || 'unknown'}' failed: ${truncated}`;
const args: string[] = ['remember', text, '--error', '--observed', '--tag', 'pi-agent'];
if (toolTag) args.push('--tag', toolTag);
const result = runHippo(args, cwd);
if (result.includes('Remembered [')) {
sessionErrorHashes.add(hash);
sessionErrorCount++;
sessionMemoryCount++;
}
});
}
// -------------------------------------------------------------------------
// Session shutdown: run consolidation
// -------------------------------------------------------------------------
pi.on('session_shutdown', async (_event: any, ctx: any) => {
if (!config.autoSleep) return;
const cwd = ctx.cwd || process.cwd();
if (!hippoInitialized(cwd)) return;
// Only sleep if we captured some memories this session
if (sessionMemoryCount < 1) {
// Still run sleep for the auto-learn-from-git + auto-share features
try { runHippo(['sleep'], cwd); } catch { /* best effort */ }
return;
}
try {
runHippo(['sleep'], cwd);
} catch {
// Best effort — don't block shutdown
}
});
// -------------------------------------------------------------------------
// Tools: hippo_recall
// -------------------------------------------------------------------------
pi.registerTool({
name: 'hippo_recall',
description: 'Search project memory for relevant context. Returns memories ranked by relevance and strength.',
promptSnippet: 'hippo_recall: Search project memory by topic',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'What to search for (natural language)' },
budget: { type: 'number', description: 'Max tokens to return (default: 4000)' },
},
required: ['query'],
},
async execute(_toolCallId: string, params: any, _signal: any, _onUpdate: any, ctx: any) {
const cwd = ctx.cwd || process.cwd();
const budget = params.budget || config.budget;
const result = runHippo(
['recall', params.query, '--budget', String(budget), '--framing', config.framing],
cwd,
);
return { content: [{ type: 'text', text: result || 'No relevant memories found.' }] };
},
});
// -------------------------------------------------------------------------
// Tools: hippo_remember
// -------------------------------------------------------------------------
pi.registerTool({
name: 'hippo_remember',
description: 'Store a lesson, insight, or error in project memory. Memories decay over time unless retrieved.',
promptSnippet: 'hippo_remember: Store a memory (lessons, errors, decisions)',
parameters: {
type: 'object',
properties: {
text: { type: 'string', description: 'The memory content' },
error: { type: 'boolean', description: 'Tag as error (doubles retention)' },
tag: { type: 'string', description: 'Optional tag for categorization' },
pin: { type: 'boolean', description: 'Pin memory (never decays)' },
},
required: ['text'],
},
async execute(_toolCallId: string, params: any, _signal: any, _onUpdate: any, ctx: any) {
const cwd = ctx.cwd || process.cwd();
const args: string[] = ['remember', params.text];
if (params.error) args.push('--error');
if (params.pin) args.push('--pin');
if (params.tag) {
const safe = params.tag.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 30);
if (safe) args.push('--tag', safe);
}
const result = runHippo(args, cwd);
if (result.includes('Remembered [')) sessionMemoryCount++;
return { content: [{ type: 'text', text: result || 'Memory stored.' }] };
},
});
// -------------------------------------------------------------------------
// Tools: hippo_outcome
// -------------------------------------------------------------------------
pi.registerTool({
name: 'hippo_outcome',
description: 'Report whether recalled memories were helpful. Strengthens or weakens them.',
promptSnippet: 'hippo_outcome: Rate recalled memories as good or bad',
parameters: {
type: 'object',
properties: {
good: { type: 'boolean', description: 'true = helpful, false = not helpful' },
},
required: ['good'],
},
async execute(_toolCallId: string, params: any, _signal: any, _onUpdate: any, ctx: any) {
const cwd = ctx.cwd || process.cwd();
const flag = params.good ? '--good' : '--bad';
const result = runHippo(['outcome', flag], cwd);
return { content: [{ type: 'text', text: result || 'Outcome recorded.' }] };
},
});
// -------------------------------------------------------------------------
// Tools: hippo_status
// -------------------------------------------------------------------------
pi.registerTool({
name: 'hippo_status',
description: 'Show memory health: counts, strength distribution, conflicts.',
promptSnippet: 'hippo_status: Check memory store health',
parameters: { type: 'object', properties: {} },
async execute(_toolCallId: string, _params: any, _signal: any, _onUpdate: any, ctx: any) {
const cwd = ctx.cwd || process.cwd();
const result = runHippo(['status'], cwd);
return { content: [{ type: 'text', text: result || 'No hippo store found.' }] };
},
});
// -------------------------------------------------------------------------
// Tools: hippo_context
// -------------------------------------------------------------------------
pi.registerTool({
name: 'hippo_context',
description: 'Get smart memory context based on current git state and task.',
promptSnippet: 'hippo_context: Smart context injection from git state',
parameters: {
type: 'object',
properties: {
budget: { type: 'number', description: 'Token budget (default: 1500)' },
},
},
async execute(_toolCallId: string, params: any, _signal: any, _onUpdate: any, ctx: any) {
const cwd = ctx.cwd || process.cwd();
const budget = params.budget || config.contextBudget;
const result = runHippo(
['context', '--auto', '--budget', String(budget), '--framing', config.framing],
cwd,
);
return { content: [{ type: 'text', text: result || 'No context available.' }] };
},
});
}
-60
View File
@@ -13,7 +13,6 @@ fi
PACKAGE_SOURCE=${PI_PACKAGE_SOURCE:-git:git@bitbucket.org:siakitem/my-pi.git} PACKAGE_SOURCE=${PI_PACKAGE_SOURCE:-git:git@bitbucket.org:siakitem/my-pi.git}
POWERLEVEL10K_PRESET_FILE=$SCRIPT_DIR/config/p10k.zsh POWERLEVEL10K_PRESET_FILE=$SCRIPT_DIR/config/p10k.zsh
HIPPO_MEMORY_VERSION_FILE=$SCRIPT_DIR/config/hippo-memory-version
CODEGRAPH_INSTALL_URL=https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh CODEGRAPH_INSTALL_URL=https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh
KITTY_INSTALL_URL=https://sw.kovidgoyal.net/kitty/installer.sh KITTY_INSTALL_URL=https://sw.kovidgoyal.net/kitty/installer.sh
OH_MY_ZSH_INSTALL_URL=https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh OH_MY_ZSH_INSTALL_URL=https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
@@ -92,43 +91,6 @@ install_codegraph() {
command -v codegraph >/dev/null 2>&1 command -v codegraph >/dev/null 2>&1
} }
read_hippo_memory_version() {
if [ ! -f "$HIPPO_MEMORY_VERSION_FILE" ]; then
warn "找不到 Hippo Memory 版本文件:$HIPPO_MEMORY_VERSION_FILE"
return 1
fi
hippo_target_version=$(sed -n '1p' "$HIPPO_MEMORY_VERSION_FILE")
case $hippo_target_version in
''|*[!0-9.]*)
warn "Hippo Memory 版本文件内容无效:$hippo_target_version"
return 1
;;
esac
printf '%s\n' "$hippo_target_version"
}
installed_hippo_memory_version() {
hippo --version 2>/dev/null | sed -n 's/^[^0-9]*\([0-9][0-9.]*\).*$/\1/p' | sed -n '1p'
}
install_hippo_memory() {
if ! command -v npm >/dev/null 2>&1; then
warn "安装 Hippo Memory CLI 需要 npm。"
return 1
fi
hippo_target_version=$(read_hippo_memory_version) || return 1
npm install -g "hippo-memory@$hippo_target_version" || return 1
if ! command -v hippo >/dev/null 2>&1; then
warn "npm 已完成安装,但当前 PATH 中仍找不到 hippo。"
return 1
fi
hippo_installed_version=$(installed_hippo_memory_version)
if [ "$hippo_installed_version" != "$hippo_target_version" ]; then
warn "Hippo Memory CLI 版本校验失败:期望 $hippo_target_version,实际 ${hippo_installed_version:-未知}。"
return 1
fi
}
install_kotlin_lsp() { install_kotlin_lsp() {
require_brew || return 1 require_brew || return 1
brew install JetBrains/utils/kotlin-lsp || return 1 brew install JetBrains/utils/kotlin-lsp || return 1
@@ -373,28 +335,6 @@ fi
say "Pi 组合包安装完成,开始检查终端环境和机器级 CLI 依赖。" say "Pi 组合包安装完成,开始检查终端环境和机器级 CLI 依赖。"
if command -v hippo >/dev/null 2>&1; then
hippo_installed_version=$(installed_hippo_memory_version)
hippo_target_version=$(read_hippo_memory_version || true)
if [ -n "$hippo_installed_version" ] && [ "$hippo_installed_version" = "$hippo_target_version" ]; then
say "✓ Hippo Memory CLI $hippo_installed_version 已安装"
else
say "✓ Hippo Memory CLI ${hippo_installed_version:-未知版本} 已安装;组合包目标版本为 ${hippo_target_version:-未知},可运行 update.sh 同步。"
fi
elif ask_to_install "Hippo Memory CLI(官方 Pi 记忆扩展运行时)"; then
if install_hippo_memory; then
say "✓ Hippo Memory CLI 安装完成"
else
record_install_failure "Hippo Memory CLI"
fi
else
say "- 已跳过 Hippo Memory CLI;官方 Hippo Pi 扩展本次将不可用。"
fi
if command -v hippo >/dev/null 2>&1; then
say "- 未自动运行 hippo init;请由用户在需要启用记忆的项目目录中自行执行。"
fi
kitty_ok=0 kitty_ok=0
if detect_kitty; then if detect_kitty; then
kitty_ok=1 kitty_ok=1
+580
View File
@@ -21,6 +21,7 @@
"pi-chrome": "file:./pi-chrome", "pi-chrome": "file:./pi-chrome",
"pi-compaction": "file:./pi-compaction", "pi-compaction": "file:./pi-compaction",
"pi-hashline-edit": "0.8.3", "pi-hashline-edit": "0.8.3",
"pi-hermes-memory": "file:./pi-hermes-memory",
"pi-lsp": "0.1.7", "pi-lsp": "0.1.7",
"pi-mcp-adapter": "2.26.0", "pi-mcp-adapter": "2.26.0",
"pi-ssh": "file:./pi-ssh", "pi-ssh": "file:./pi-ssh",
@@ -2611,6 +2612,448 @@
"node": ">=22.19.0" "node": ">=22.19.0"
} }
}, },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
"integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
"integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
"integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
"integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
"integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
"integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
"integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
"integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
"integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
"integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
"integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
"integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
"integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
"integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
"integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
"integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
"integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
"integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
"integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
"integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
"integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
"integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
"integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
"integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
"integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
"integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@ff-labs/fff-bin-android-arm64": { "node_modules/@ff-labs/fff-bin-android-arm64": {
"version": "0.10.5", "version": "0.10.5",
"resolved": "https://registry.npmjs.org/@ff-labs/fff-bin-android-arm64/-/fff-bin-android-arm64-0.10.5.tgz", "resolved": "https://registry.npmjs.org/@ff-labs/fff-bin-android-arm64/-/fff-bin-android-arm64-0.10.5.tgz",
@@ -4174,6 +4617,16 @@
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/better-sqlite3": {
"version": "7.6.13",
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/chai": { "node_modules/@types/chai": {
"version": "5.2.3", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -4561,6 +5014,18 @@
} }
} }
}, },
"node_modules/ansi-regex": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz",
"integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/asn1": { "node_modules/asn1": {
"version": "0.2.6", "version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
@@ -5218,6 +5683,48 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/esbuild": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.28.2",
"@esbuild/android-arm": "0.28.2",
"@esbuild/android-arm64": "0.28.2",
"@esbuild/android-x64": "0.28.2",
"@esbuild/darwin-arm64": "0.28.2",
"@esbuild/darwin-x64": "0.28.2",
"@esbuild/freebsd-arm64": "0.28.2",
"@esbuild/freebsd-x64": "0.28.2",
"@esbuild/linux-arm": "0.28.2",
"@esbuild/linux-arm64": "0.28.2",
"@esbuild/linux-ia32": "0.28.2",
"@esbuild/linux-loong64": "0.28.2",
"@esbuild/linux-mips64el": "0.28.2",
"@esbuild/linux-ppc64": "0.28.2",
"@esbuild/linux-riscv64": "0.28.2",
"@esbuild/linux-s390x": "0.28.2",
"@esbuild/linux-x64": "0.28.2",
"@esbuild/netbsd-arm64": "0.28.2",
"@esbuild/netbsd-x64": "0.28.2",
"@esbuild/openbsd-arm64": "0.28.2",
"@esbuild/openbsd-x64": "0.28.2",
"@esbuild/openharmony-arm64": "0.28.2",
"@esbuild/sunos-x64": "0.28.2",
"@esbuild/win32-arm64": "0.28.2",
"@esbuild/win32-ia32": "0.28.2",
"@esbuild/win32-x64": "0.28.2"
}
},
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -6749,6 +7256,10 @@
"@sinclair/typebox": "*" "@sinclair/typebox": "*"
} }
}, },
"node_modules/pi-hermes-memory": {
"resolved": "pi-hermes-memory",
"link": true
},
"node_modules/pi-lsp": { "node_modules/pi-lsp": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/pi-lsp/-/pi-lsp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/pi-lsp/-/pi-lsp-0.1.7.tgz",
@@ -7580,6 +8091,21 @@
"safe-buffer": "~5.2.0" "safe-buffer": "~5.2.0"
} }
}, },
"node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-json-comments": { "node_modules/strip-json-comments": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
@@ -7755,6 +8281,25 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tsx": {
"version": "4.23.15",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz",
"integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.28.0"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tunnel-agent": { "node_modules/tunnel-agent": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -8282,6 +8827,41 @@
"@earendil-works/pi-coding-agent": "*" "@earendil-works/pi-coding-agent": "*"
} }
}, },
"pi-hermes-memory": {
"version": "0.9.10",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^13.0.3",
"strip-ansi": "7.2.0"
},
"devDependencies": {
"@earendil-works/pi-ai": "0.84.2",
"@earendil-works/pi-coding-agent": "0.84.2",
"@earendil-works/pi-tui": "0.84.2",
"@types/better-sqlite3": "7.6.13",
"tsx": "4.23.15",
"typebox": "1.3.7",
"typescript": "6.0.3"
},
"peerDependencies": {
"@earendil-works/pi-ai": ">=0.80.6",
"@earendil-works/pi-coding-agent": ">=0.80.6",
"@earendil-works/pi-tui": "*"
}
},
"pi-hermes-memory/node_modules/better-sqlite3": {
"version": "13.0.3",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.3.tgz",
"integrity": "sha512-RbOBxmLBG8uvFUc15X9+9SFemKcQ0WBuISBVkpuiaUB2qblC8UWlHEjdWVoZ8AdhSwmoEgsiXKfopX0CQxaACQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-addon-api": "^8.0.0"
},
"engines": {
"node": ">=22"
}
},
"pi-minimal-footer": { "pi-minimal-footer": {
"name": "@ogulcancelik/pi-minimal-footer", "name": "@ogulcancelik/pi-minimal-footer",
"version": "0.1.10-my-pi.1", "version": "0.1.10-my-pi.1",
+3 -2
View File
@@ -10,11 +10,11 @@
], ],
"files": [ "files": [
"extensions", "extensions",
"hippo-memory-pi",
"pi-ask-user", "pi-ask-user",
"pi-compaction", "pi-compaction",
"pi-chrome", "pi-chrome",
"pi-extension-codex-fast-mode", "pi-extension-codex-fast-mode",
"pi-hermes-memory",
"pi-rtk-optimizer", "pi-rtk-optimizer",
"pi-minimal-footer", "pi-minimal-footer",
"pi-subagents", "pi-subagents",
@@ -59,7 +59,7 @@
"./node_modules/context-mode/build/adapters/pi/extension.js", "./node_modules/context-mode/build/adapters/pi/extension.js",
"./extensions/lsp.ts", "./extensions/lsp.ts",
"./pi-extension-codex-fast-mode/index.ts", "./pi-extension-codex-fast-mode/index.ts",
"./hippo-memory-pi/index.ts", "./pi-hermes-memory/src/index.ts",
"./pi-compaction/index.ts", "./pi-compaction/index.ts",
"./extensions/context-view.ts", "./extensions/context-view.ts",
"./pi-minimal-footer/index.ts", "./pi-minimal-footer/index.ts",
@@ -90,6 +90,7 @@
"pi-chrome": "file:./pi-chrome", "pi-chrome": "file:./pi-chrome",
"pi-compaction": "file:./pi-compaction", "pi-compaction": "file:./pi-compaction",
"pi-hashline-edit": "0.8.3", "pi-hashline-edit": "0.8.3",
"pi-hermes-memory": "file:./pi-hermes-memory",
"pi-lsp": "0.1.7", "pi-lsp": "0.1.7",
"pi-mcp-adapter": "2.26.0", "pi-mcp-adapter": "2.26.0",
"pi-ssh": "file:./pi-ssh", "pi-ssh": "file:./pi-ssh",
+27 -7
View File
@@ -4,20 +4,40 @@
- 导入发布 tag:`v0.9.10` - 导入发布 tag:`v0.9.10`
- 导入 commit:`df624056a2e8c68bc89ba0a81e1c120856ec9665`(`chore: bump version to 0.9.10 (#277)`,2026-10-02) - 导入 commit:`df624056a2e8c68bc89ba0a81e1c120856ec9665`(`chore: bump version to 0.9.10 (#277)`,2026-10-02)
- 初始本地导入:2026-10-13 - 初始本地导入:2026-10-13
- 接入组合包:2026-10-14
- 许可证:MIT;上游 `LICENSE` 文件原样保留。 - 许可证:MIT;上游 `LICENSE` 文件原样保留。
快照取自官方 Git 仓库中 `v0.9.10` tag 指向的 commit。导入时上游 `main` 已领先该 tag(`24fcd8ad26fc27badb8d7dbaf8034645de07b893`,2026-10-11);选择 tag 是为了让导入状态对应一个已发布、可复现的版本。不保留嵌套 `.git`、上游 `.github` 工作流、依赖目录、覆盖率与构建产物。 快照取自官方 Git 仓库中 `v0.9.10` tag 指向的 commit。导入时上游 `main` 已领先该 tag(`24fcd8ad26fc27badb8d7dbaf8034645de07b893`,2026-10-11);选择 tag 是为了让导入状态对应一个已发布、可复现的版本。不保留嵌套 `.git`、上游 `.github` 工作流、依赖目录、覆盖率与构建产物。
## 本地维护 ## 本地维护
本目录由 `my-pi` 组合包直接维护,不是 submodule。根包直接加载 `pi-hermes-memory/src/index.ts`。 本目录由 `my-pi` 组合包直接维护,不是 submodule。根包通过 `file:./pi-hermes-memory` 依赖并直接加载 `pi-hermes-memory/src/index.ts`,不得同时安装或加载 npm 预编译入口。
导入时尚未接入组合包。启用该扩展前必须先完成以下事项,记录在此以免待决决定丢失: ### 加载顺序
- 根 `package.json` 必须新增本地 `file:./pi-hermes-memory` 依赖,并在 `pi.extensions` 中注册扩展入口。 **该扩展必须注册在 `pi-compaction` 之前。** `ExtensionRunner.emit()` 在遇到第一个返回 `{ cancel: true }` 的 `session_before_compact` handler 时立即返回,而 `pi-compaction/index.ts` 在判定无需压缩时正是这样返回。注册在它之后的 handler 在这些轮次会被跳过,从而静默禁用该扩展的 `flushOnCompact`。
- 注册顺序很重要:该扩展必须在 `pi-compaction` **之前**加载。`ExtensionRunner.emit()` 会在遇到第一个返回 `{ cancel: true }` 的 `session_before_compact` handler 时停止,而 `pi-compaction/index.ts` 在判定无需压缩时正是这样返回。注册在它之后的 handler 在这些轮次会被跳过,从而静默禁用该扩展的 `flushOnCompact`。
- 该扩展依赖 `better-sqlite3@^13.0.3`。根 `allowScripts` 当前只放行 `better-sqlite3@12.11.1`(Context Mode 使用);安装前必须对齐原生构建批准。 ### 依赖
- `strip-ansi@7.2.0` 是第二个运行时依赖,必须能与现有依赖树干净解析。
- 它注册 `memory_add` / `memory_replace` / `memory_remove` / `memory_search` / `session_search` / `skill_manage`,以及 `/memory-*` 命令。这些名称当前与组合包中的任何内容都不冲突。 - `better-sqlite3@^13.0.3`:13.x 运行时实际 `dlopen` 的是包内 N-API 预编译二进制 `prebuilds/<platform>.node`,**不依赖 node-gyp 编译产物**。npm 会因该包存在 `binding.gyp` 而推断出隐式 `node-gyp rebuild`,但该步骤既无必要也不得批准。npm 会把它嵌套安装到本目录的 `node_modules/`,与 Context Mode 使用的根 `better-sqlite3@12.11.1` 并存;因此根 `allowScripts` 无需新增条目,也不得删除既有的 `better-sqlite3@12.11.1` 放行。
- `strip-ansi@7.2.0`:解析到根 `node_modules`。
### 本地改动
- `devDependencies` 中的 `@earendil-works/pi-ai`、`@earendil-works/pi-coding-agent`、`@earendil-works/pi-tui`、`typebox`、`tsx`、`typescript`、`@types/better-sqlite3` 已从上游的 `^0.80.2` 等范围改为与根包一致的精确版本。上游的宽松范围与根包的 `0.84.2` 不兼容,会让 npm 在本目录嵌套安装第二套 Pi runtime(实测 229 MB),违反组合包"不得再安装一套 Pi runtime"的纪律;`pi-notify/package.json` 是同样的先例。运行时依赖(`better-sqlite3`、`strip-ansi`)与 `peerDependencies` 保持上游原样。
### 工具与常驻
该扩展注册 `memory_add`、`memory_replace`、`memory_remove`、`memory_search`、`session_search`、`skill_manage`,以及 `/memory-*` 命令。六个工具全部通过 `pi-tool-search` 的 `alwaysEnabled` 常驻,并在 `pi-permission-system` 策略中默认 `allow`。
注意 `skill_manage` 由本扩展提供,不是 Pi 宿主能力:Pi 只负责发现并加载 skill 目录(`~/.pi/agent/skills/` 加扩展通过 `resources_discover` 注册的目录),本身没有创建 skill 的工具。因此本扩展同时承担 skill 的**写入**与**目录注册**两件事;不加载它,`projects-memory/<项目>/skills/` 下的既有 skill 也不会被 Pi 发现。
### 用户数据
- 全局记忆与 skill:`~/.pi/agent/pi-hermes-memory/`
- 项目记忆与 skill:`~/.pi/agent/projects-memory/<项目>/`
- 配置:`~/.pi/agent/hermes-memory-config.json`(可选,缺省时使用内置默认值)
这些路径是用户数据,不属于组合包,也不应提交或自动清理。
同步上游时,请从明确 tag/commit 移植改动,并与本地改动对照审查,不得整体覆盖本目录。 同步上游时,请从明确 tag/commit 移植改动,并与本地改动对照审查,不得整体覆盖本目录。
+114 -164
View File
@@ -13,13 +13,13 @@
"strip-ansi": "7.2.0" "strip-ansi": "7.2.0"
}, },
"devDependencies": { "devDependencies": {
"@earendil-works/pi-ai": "^0.80.2", "@earendil-works/pi-ai": "0.84.2",
"@earendil-works/pi-coding-agent": "^0.80.2", "@earendil-works/pi-coding-agent": "0.84.2",
"@earendil-works/pi-tui": "^0.80.2", "@earendil-works/pi-tui": "0.84.2",
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "7.6.13",
"tsx": "^4.21.0", "tsx": "4.23.15",
"typebox": "^1.1.33", "typebox": "1.3.7",
"typescript": "^6.0.3" "typescript": "6.0.3"
}, },
"peerDependencies": { "peerDependencies": {
"@earendil-works/pi-ai": ">=0.80.6", "@earendil-works/pi-ai": ">=0.80.6",
@@ -514,23 +514,23 @@
} }
}, },
"node_modules/@earendil-works/pi-ai": { "node_modules/@earendil-works/pi-ai": {
"version": "0.80.2", "version": "0.84.2",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.2.tgz", "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.84.2.tgz",
"integrity": "sha512-5GNKfdrRJ4uZ5Zd9iudoXggi/BbUcKnD/xfRHtdR+7q4vWqPvfx8auFuaT+ewGBVI8K4wj87eigFQ/iCSuy9RQ==", "integrity": "sha512-6MzsrYIYNVlE7SfpbL2yYb67Qo58p/7Q+xWG1RZvoX1P80aRCHSod2/13aFpxkow1lPO2LEh3c495J0Gwmyjig==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "0.91.1", "@anthropic-ai/sdk": "0.91.1",
"@aws-sdk/client-bedrock-runtime": "3.1048.0", "@aws-sdk/client-bedrock-runtime": "3.1048.0",
"@earendil-works/pi-telemetry": "^0.84.2",
"@google/genai": "1.52.0", "@google/genai": "1.52.0",
"@mistralai/mistralai": "2.2.6",
"@opentelemetry/api": "1.9.0", "@opentelemetry/api": "1.9.0",
"@smithy/node-http-handler": "4.7.3", "@smithy/node-http-handler": "4.7.3",
"http-proxy-agent": "7.0.2", "http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6", "https-proxy-agent": "7.0.6",
"openai": "6.26.0", "openai": "6.40.0",
"partial-json": "0.1.7", "partial-json": "0.1.7",
"typebox": "1.1.38" "typebox": "1.3.7"
}, },
"bin": { "bin": {
"pi-ai": "dist/cli.js" "pi-ai": "dist/cli.js"
@@ -561,21 +561,24 @@
} }
}, },
"node_modules/@earendil-works/pi-coding-agent": { "node_modules/@earendil-works/pi-coding-agent": {
"version": "0.80.2", "version": "0.84.2",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.2.tgz", "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.84.2.tgz",
"integrity": "sha512-m9v7OUit0s9LklWfh61ca/XY5INjUzjtYtNZwy3cNvyjOLk3IpBgghP8aAp0iH35rLaiRwuuWiJ8t88ODMWY+A==", "integrity": "sha512-l4E+B7hgXKWddRo8bC/eSue2aWZjEgJ9xIpf5p0Og+lq8a2TArCwJ0HCoCPCgaBP/tN4zbYH/wOwvx9pJpeLCA==",
"dev": true, "dev": true,
"hasShrinkwrap": true, "hasShrinkwrap": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@earendil-works/pi-agent-core": "^0.80.2", "@earendil-works/pi-agent-core": "^0.84.2",
"@earendil-works/pi-ai": "^0.80.2", "@earendil-works/pi-ai": "^0.84.2",
"@earendil-works/pi-tui": "^0.80.2", "@earendil-works/pi-client": "^0.84.2",
"@earendil-works/pi-protocol": "^0.84.2",
"@earendil-works/pi-tui": "^0.84.2",
"@silvia-odwyer/photon-node": "0.3.4", "@silvia-odwyer/photon-node": "0.3.4",
"chalk": "5.6.2", "chalk": "5.6.2",
"cross-spawn": "7.0.6", "cross-spawn": "7.0.6",
"diff": "8.0.4", "diff": "8.0.4",
"glob": "13.0.6", "glob": "13.0.6",
"grok-mermaid": "0.2.2",
"highlight.js": "10.7.3", "highlight.js": "10.7.3",
"hosted-git-info": "9.0.3", "hosted-git-info": "9.0.3",
"ignore": "7.0.5", "ignore": "7.0.5",
@@ -583,8 +586,8 @@
"minimatch": "10.2.5", "minimatch": "10.2.5",
"proper-lockfile": "4.1.2", "proper-lockfile": "4.1.2",
"semver": "7.8.0", "semver": "7.8.0",
"typebox": "1.1.38", "typebox": "1.3.7",
"undici": "8.5.0", "undici": "8.9.0",
"yaml": "2.9.0" "yaml": "2.9.0"
}, },
"bin": { "bin": {
@@ -1060,14 +1063,16 @@
} }
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": {
"version": "0.80.2", "version": "0.84.2",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.2.tgz", "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.84.2.tgz",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@earendil-works/pi-ai": "^0.80.2", "@earendil-works/pi-ai": "^0.84.2",
"@earendil-works/pi-telemetry": "^0.84.2",
"diff": "8.0.4",
"ignore": "7.0.5", "ignore": "7.0.5",
"typebox": "1.1.38", "typebox": "1.3.7",
"yaml": "2.9.0" "yaml": "2.9.0"
}, },
"engines": { "engines": {
@@ -1075,22 +1080,22 @@
} }
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": {
"version": "0.80.2", "version": "0.84.2",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.2.tgz", "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.84.2.tgz",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "0.91.1", "@anthropic-ai/sdk": "0.91.1",
"@aws-sdk/client-bedrock-runtime": "3.1048.0", "@aws-sdk/client-bedrock-runtime": "3.1048.0",
"@earendil-works/pi-telemetry": "^0.84.2",
"@google/genai": "1.52.0", "@google/genai": "1.52.0",
"@mistralai/mistralai": "2.2.6",
"@opentelemetry/api": "1.9.0", "@opentelemetry/api": "1.9.0",
"@smithy/node-http-handler": "4.7.3", "@smithy/node-http-handler": "4.7.3",
"http-proxy-agent": "7.0.2", "http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6", "https-proxy-agent": "7.0.6",
"openai": "6.26.0", "openai": "6.40.0",
"partial-json": "0.1.7", "partial-json": "0.1.7",
"typebox": "1.1.38" "typebox": "1.3.7"
}, },
"bin": { "bin": {
"pi-ai": "dist/cli.js" "pi-ai": "dist/cli.js"
@@ -1099,9 +1104,42 @@
"node": ">=22.19.0" "node": ">=22.19.0"
} }
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-client": {
"version": "0.84.2",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-client/-/pi-client-0.84.2.tgz",
"dev": true,
"license": "MIT",
"dependencies": {
"@earendil-works/pi-protocol": "^0.84.2"
},
"engines": {
"node": ">=22.19.0"
}
},
"node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-protocol": {
"version": "0.84.2",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-protocol/-/pi-protocol-0.84.2.tgz",
"dev": true,
"license": "MIT",
"dependencies": {
"typebox": "1.3.7"
},
"engines": {
"node": ">=22.19.0"
}
},
"node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-telemetry": {
"version": "0.84.2",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-0.84.2.tgz",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=22.19.0"
}
},
"node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": {
"version": "0.80.2", "version": "0.84.2",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.2.tgz", "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.84.2.tgz",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1327,27 +1365,6 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/@mistralai/mistralai": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz",
"integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.40.0",
"ws": "^8.18.0",
"zod": "^3.25.0 || ^4.0.0",
"zod-to-json-schema": "^3.25.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.9.0"
},
"peerDependenciesMeta": {
"@opentelemetry/api": {
"optional": true
}
}
},
"node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": { "node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz",
@@ -1371,16 +1388,6 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/semantic-conventions": {
"version": "1.41.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz",
"integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -1652,16 +1659,16 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": { "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": {
"version": "5.0.6", "version": "5.0.9",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^4.0.2" "balanced-match": "^4.0.2"
}, },
"engines": { "engines": {
"node": "18 || 20 || >=22" "node": "20 || >=22"
} }
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": { "node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": {
@@ -1926,6 +1933,16 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/grok-mermaid": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/grok-mermaid/-/grok-mermaid-0.2.2.tgz",
"integrity": "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/@earendil-works/pi-coding-agent/node_modules/highlight.js": { "node_modules/@earendil-works/pi-coding-agent/node_modules/highlight.js": {
"version": "10.7.3", "version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
@@ -2155,14 +2172,11 @@
} }
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/openai": { "node_modules/@earendil-works/pi-coding-agent/node_modules/openai": {
"version": "6.26.0", "version": "6.40.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", "resolved": "https://registry.npmjs.org/openai/-/openai-6.40.0.tgz",
"integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", "integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": {
"openai": "bin/cli"
},
"peerDependencies": { "peerDependencies": {
"ws": "^8.18.0", "ws": "^8.18.0",
"zod": "^3.25 || ^4.0" "zod": "^3.25 || ^4.0"
@@ -2270,9 +2284,9 @@
} }
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": { "node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": {
"version": "7.6.4", "version": "7.6.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
"integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
@@ -2395,16 +2409,16 @@
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": { "node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": {
"version": "1.1.38", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz",
"integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", "integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": {
"version": "8.5.0", "version": "8.9.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",
"integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -2498,30 +2512,20 @@
"url": "https://github.com/sponsors/eemeli" "url": "https://github.com/sponsors/eemeli"
} }
}, },
"node_modules/@earendil-works/pi-coding-agent/node_modules/zod": { "node_modules/@earendil-works/pi-telemetry": {
"version": "3.25.76", "version": "0.84.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-0.84.4.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "integrity": "sha512-8e2CuxM+ht+hedQXTZmi5JVl6/xDK9RpSDL2+MbITevKYQhMZ/z6lJOTFgox3HQyGxO8mOZEtYGVeQNaD4OzqA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"funding": { "engines": {
"url": "https://github.com/sponsors/colinhacks" "node": ">=22.19.0"
}
},
"node_modules/@earendil-works/pi-coding-agent/node_modules/zod-to-json-schema": {
"version": "3.25.2",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
"integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
"dev": true,
"license": "ISC",
"peerDependencies": {
"zod": "^3.25.28 || ^4"
} }
}, },
"node_modules/@earendil-works/pi-tui": { "node_modules/@earendil-works/pi-tui": {
"version": "0.80.2", "version": "0.84.2",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.2.tgz", "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.84.2.tgz",
"integrity": "sha512-OvOAMIbXiC9OSse17YMiXIsI9AS5XM/ZV8N/k+UzdlRpPILDQYmLElevgGW92kkXR8qHBClIdzhCjuzlBGvphA==", "integrity": "sha512-ds2TLihOnM5sLJB3VpXV6y0uR5efVuHf4MN7yDpsty6hA2DUO/EDVzjp/0od0G2JslzVLMjT8T8zavtxVb+qbg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2999,27 +3003,6 @@
} }
} }
}, },
"node_modules/@mistralai/mistralai": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz",
"integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.40.0",
"ws": "^8.18.0",
"zod": "^3.25.0 || ^4.0.0",
"zod-to-json-schema": "^3.25.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.9.0"
},
"peerDependenciesMeta": {
"@opentelemetry/api": {
"optional": true
}
}
},
"node_modules/@opentelemetry/api": { "node_modules/@opentelemetry/api": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
@@ -3030,16 +3013,6 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/@opentelemetry/semantic-conventions": {
"version": "1.41.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz",
"integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"node_modules/@protobufjs/aspromise": { "node_modules/@protobufjs/aspromise": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -3704,14 +3677,11 @@
} }
}, },
"node_modules/openai": { "node_modules/openai": {
"version": "6.26.0", "version": "6.40.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", "resolved": "https://registry.npmjs.org/openai/-/openai-6.40.0.tgz",
"integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", "integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": {
"openai": "bin/cli"
},
"peerDependencies": { "peerDependencies": {
"ws": "^8.18.0", "ws": "^8.18.0",
"zod": "^3.25 || ^4.0" "zod": "^3.25 || ^4.0"
@@ -3831,9 +3801,9 @@
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tsx": { "node_modules/tsx": {
"version": "4.22.4", "version": "4.23.15",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz",
"integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3850,9 +3820,9 @@
} }
}, },
"node_modules/typebox": { "node_modules/typebox": {
"version": "1.1.38", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz",
"integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", "integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -3908,26 +3878,6 @@
"optional": true "optional": true
} }
} }
},
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-to-json-schema": {
"version": "3.25.2",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
"integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
"dev": true,
"license": "ISC",
"peerDependencies": {
"zod": "^3.25.28 || ^4"
}
} }
} }
} }
+7 -7
View File
@@ -53,13 +53,13 @@
"@earendil-works/pi-tui": "*" "@earendil-works/pi-tui": "*"
}, },
"devDependencies": { "devDependencies": {
"@earendil-works/pi-ai": "^0.80.2", "@earendil-works/pi-ai": "0.84.2",
"@earendil-works/pi-coding-agent": "^0.80.2", "@earendil-works/pi-coding-agent": "0.84.2",
"@earendil-works/pi-tui": "^0.80.2", "@earendil-works/pi-tui": "0.84.2",
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "7.6.13",
"tsx": "^4.21.0", "tsx": "4.23.15",
"typebox": "^1.1.33", "typebox": "1.3.7",
"typescript": "^6.0.3" "typescript": "6.0.3"
}, },
"dependencies": { "dependencies": {
"better-sqlite3": "^13.0.3", "better-sqlite3": "^13.0.3",
+2
View File
@@ -11,12 +11,14 @@
- Added limits for active groups, generated group size, and total dynamic tools, plus current-host catalog/config/LRU tests. - Added limits for active groups, generated group size, and total dynamic tools, plus current-host catalog/config/LRU tests.
- Added checked-in authoritative groups for every tool exposed by the standard my-pi bundle, eliminating first-use model generation and user caching unless unrecognized third-party tools are present. - Added checked-in authoritative groups for every tool exposed by the standard my-pi bundle, eliminating first-use model generation and user caching unless unrecognized third-party tools are present.
- Replaced the Hermes and third-party Hippo assignments with the five tools exposed by the official Hippo Pi extension, grouped into recall and management workflows. - Replaced the Hermes and third-party Hippo assignments with the five tools exposed by the official Hippo Pi extension, grouped into recall and management workflows.
- Replaced the retired Hippo `hippo_*` assignments with the vendored Hermes memory extension's six tools, grouped into `memory-recall` (`memory_search`, `session_search`, `context_tree_query`) and `memory-management` (`memory_add`, `memory_replace`, `memory_remove`, `skill_manage`).
- Added the enabled bundle's `Agent`, `get_subagent_result`, and `steer_subagent` tools to a checked-in `subagents` orchestration group. - Added the enabled bundle's `Agent`, `get_subagent_result`, and `steer_subagent` tools to a checked-in `subagents` orchestration group.
- Added authoritative `ssh-connection`, `ssh-remote-files`, `ssh-remote-search`, and `ssh-remote-shell` groups for the maintained `pi-ssh` tools, keeping reviewed connection, bounded discovery, structured file operations, and reviewed shell execution separate; the connection workflow now requires sequential `ssh_connect` to succeed before dependent tools. - Added authoritative `ssh-connection`, `ssh-remote-files`, `ssh-remote-search`, and `ssh-remote-shell` groups for the maintained `pi-ssh` tools, keeping reviewed connection, bounded discovery, structured file operations, and reviewed shell execution separate; the connection workflow now requires sequential `ssh_connect` to succeed before dependent tools.
- Added sequential `ssh_cd` to the authoritative remote-shell workflow and direct the model to wait for a successful workspace change before dependent remote calls, avoiding both the unknown-tools fallback and cwd races. - Added sequential `ssh_cd` to the authoritative remote-shell workflow and direct the model to wait for a successful workspace change before dependent remote calls, avoiding both the unknown-tools fallback and cwd races.
- Added the authoritative `user-interaction` group for the maintained `ask_user_question` TUI tool. - Added the authoritative `user-interaction` group for the maintained `ask_user_question` TUI tool.
- Added authoritative `chrome-navigation`, `chrome-interaction`, and `chrome-debugging` groups for all 21 tools registered by the pinned `pi-chrome` bridge after explicit session authorization. - Added authoritative `chrome-navigation`, `chrome-interaction`, and `chrome-debugging` groups for all 21 tools registered by the pinned `pi-chrome` bridge after explicit session authorization.
- Raised the bundle defaults to five active groups and 28 dynamic tools, and pinned the high-frequency Context Mode execution trio alongside CodeGraph exploration and LSP diagnostics. - Raised the bundle defaults to five active groups and 28 dynamic tools, and pinned the high-frequency Context Mode execution trio alongside CodeGraph exploration and LSP diagnostics.
- Pinned all six Hermes memory tools in the bundle defaults and raised `bundleDefaultsVersion` to 3. The injected memory policy expects these schemas every turn, and pinning also keeps the request prefix cache-stable instead of paying a `tool_search` round trip plus a cache invalidation. `scripts/migrate-tool-search-settings.mjs` migrates the complete version-2 snapshot while leaving customized configurations untouched.
- Added atomic multi-group activation through `tool_search.groups`, bounded to five exact group ids and rejected without state changes when the complete workflow cannot fit. - Added atomic multi-group activation through `tool_search.groups`, bounded to five exact group ids and rejected without state changes when the complete workflow cannot fit.
- Weighted exact tool names, group metadata, and common SSH/Chrome/Context aliases during query routing; weak or ambiguous matches now return candidates instead of activating arbitrarily. - Weighted exact tool names, group metadata, and common SSH/Chrome/Context aliases during query routing; weak or ambiguous matches now return candidates instead of activating arbitrarily.
- Added `/tool-search-status` for catalog source, capacity, active-group LRU order, and latest eviction diagnostics. - Added `/tool-search-status` for catalog source, capacity, active-group LRU order, and latest eviction diagnostics.
+12 -6
View File
@@ -39,8 +39,8 @@ Run `/tool-search-status` to inspect the current catalog source, pinned count, d
| `context-knowledge` | Context indexing, searching, and web-doc ingestion | | `context-knowledge` | Context indexing, searching, and web-doc ingestion |
| `context-observability` | Context stats, doctor, and Insight | | `context-observability` | Context stats, doctor, and Insight |
| `context-administration` | Context upgrade and destructive purge | | `context-administration` | Context upgrade and destructive purge |
| `memory-recall` | Official Hippo recall/context/status and condensed-output recovery | | `memory-recall` | Hermes memory/session search and condensed-output recovery |
| `memory-management` | Official Hippo remember/outcome tools | | `memory-management` | Hermes memory writes and procedural skill management |
| `subagents` | Launch agents, collect background results, and steer running agents | | `subagents` | Launch agents, collect background results, and steer running agents |
| `user-interaction` | `ask_user_question` model-initiated TUI/RPC questions | | `user-interaction` | `ask_user_question` model-initiated TUI/RPC questions |
| `chrome-navigation` | Chrome bridge setup, tabs, navigation, snapshots/find/inspect, waits, and screenshots | | `chrome-navigation` | Chrome bridge setup, tabs, navigation, snapshots/find/inspect, waits, and screenshots |
@@ -48,7 +48,7 @@ Run `/tool-search-status` to inspect the current catalog source, pinned count, d
| `chrome-debugging` | Chrome page evaluation, console/network diagnostics, response retrieval, and file upload | | `chrome-debugging` | Chrome page evaluation, console/network diagnostics, response retrieval, and file upload |
| `mcp-management` | Shared MCP adapter management | | `mcp-management` | Shared MCP adapter management |
Pinned tools are removed from these dynamic groups at runtime. The defaults pin `codegraph_explore`, `lsp_diagnostics`, and the three high-frequency Context Mode execution 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`, the three high-frequency Context Mode execution tools, and all six Hermes memory tools, so none of them consume dynamic group or tool capacity.
## Loading and eviction ## Loading and eviction
@@ -84,21 +84,27 @@ Pi reads this configuration from `<agent-dir>/settings.json` (normally `~/.pi/ag
"lsp_diagnostics", "lsp_diagnostics",
"ctx_execute", "ctx_execute",
"ctx_execute_file", "ctx_execute_file",
"ctx_batch_execute" "ctx_batch_execute",
"memory_search",
"session_search",
"memory_add",
"memory_replace",
"memory_remove",
"skill_manage"
], ],
"showToolSearchFooterStatus": false, "showToolSearchFooterStatus": false,
"maxActiveGroups": 5, "maxActiveGroups": 5,
"maxToolsPerGroup": 8, "maxToolsPerGroup": 8,
"maxDynamicTools": 28, "maxDynamicTools": 28,
"groupOverrides": {}, "groupOverrides": {},
"bundleDefaultsVersion": 2 "bundleDefaultsVersion": 3
} }
} }
``` ```
| Key | Bundle default | Description | | Key | Bundle default | Description |
|---|---|---| |---|---|---|
| `alwaysEnabled` | CodeGraph explore, LSP diagnostics, Context execution trio | Exact names pinned alongside Pi core tools | | `alwaysEnabled` | CodeGraph explore, LSP diagnostics, Context execution trio, all six Hermes memory tools | Exact names pinned alongside Pi core tools |
| `showToolSearchFooterStatus` | `false` | Show active tool/group counts | | `showToolSearchFooterStatus` | `false` | Show active tool/group counts |
| `maxActiveGroups` | `5` | Maximum simultaneously active dynamic groups | | `maxActiveGroups` | `5` | Maximum simultaneously active dynamic groups |
| `maxToolsPerGroup` | `8` | Maximum size accepted from model-generated groups | | `maxToolsPerGroup` | `8` | Maximum size accepted from model-generated groups |
+2 -2
View File
@@ -2,7 +2,7 @@
`pi-tool-search` keeps full low-frequency schemas hidden behind a compact, validated group catalog. `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 three high-frequency Context Mode execution 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 three high-frequency Context Mode execution tools, 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.
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. 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. 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. 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.
@@ -13,7 +13,7 @@
9. Natural-language lookup gives exact tool names and workflow aliases more weight than descriptive overlap. Weak or closely tied matches return candidates without activation. 9. Natural-language lookup gives exact tool names and workflow aliases more weight than descriptive overlap. Weak or closely tied matches return candidates without activation.
10. `turn_start` notices tool or grouping-configuration changes by hash, resets stale dynamic groups, and reconstructs the checked-in/hybrid catalog. 10. `turn_start` notices tool or grouping-configuration changes by hash, resets stale dynamic groups, and reconstructs the checked-in/hybrid catalog.
The standard bundle assigns the official Hippo Pi extension's five `hippo_*` tools to recall and management groups, and assigns `Agent`, `get_subagent_result`, and `steer_subagent` to the `subagents` orchestration group. `context_tree_query` remains in `memory-recall` because it recovers condensed tool output; retired Hermes names and third-party `tff-memory_*` names are not seeded. The standard bundle assigns the Hermes memory extension's six tools to recall and management groups: `memory_search` and `session_search` join `context_tree_query` in `memory-recall`, while `memory_add`, `memory_replace`, `memory_remove`, and `skill_manage` form `memory-management`. All six are pinned by default, because the injected `<memory-policy>` tells the model to consult and write memory during normal work, and a schema the model cannot see would silently disable that loop. Third-party `tff-memory_*` names and the retired Hippo `hippo_*` names are not seeded. It also assigns `Agent`, `get_subagent_result`, and `steer_subagent` to the `subagents` orchestration group.
The maintained `pi-ssh` tools are also split by workflow: sequential reviewed agent-controlled connection lives in `ssh-connection`, structured `ssh_read`/`ssh_write`/`ssh_edit` file operations live in `ssh-remote-files`, bounded `ssh_find`/`ssh_grep` discovery lives in `ssh-remote-search`, and sequential reviewed workspace changes plus reviewed command execution live in `ssh-remote-shell`. The workflows tell the model to call `ssh_connect` or `ssh_cd` separately and wait for success before issuing remote calls that depend on the new connection or cwd. The maintained `pi-ssh` tools are also split by workflow: sequential reviewed agent-controlled connection lives in `ssh-connection`, structured `ssh_read`/`ssh_write`/`ssh_edit` file operations live in `ssh-remote-files`, bounded `ssh_find`/`ssh_grep` discovery lives in `ssh-remote-search`, and sequential reviewed workspace changes plus reviewed command execution live in `ssh-remote-shell`. The workflows tell the model to call `ssh_connect` or `ssh_cd` separately and wait for success before issuing remote calls that depend on the new connection or cwd.
The maintained `ask_user_question` tool has its own `user-interaction` group so the model can load the complete interactive schema only when a missing decision or preference must come from the user. The maintained `ask_user_question` tool has its own `user-interaction` group so the model can load the complete interactive schema only when a missing decision or preference must come from the user.
The pinned `pi-chrome` bridge registers its 21 `chrome_*` tools only after explicit per-session authorization. Once registered, catalog refresh assigns them to `chrome-navigation`, `chrome-interaction`, and `chrome-debugging`; the 28-tool default allows all three groups to coexist. Revocation remains enforced inside every upstream tool execution even if a schema is later reactivated by Tool Search. The pinned `pi-chrome` bridge registers its 21 `chrome_*` tools only after explicit per-session authorization. Once registered, catalog refresh assigns them to `chrome-navigation`, `chrome-interaction`, and `chrome-debugging`; the 28-tool default allows all three groups to coexist. Revocation remains enforced inside every upstream tool execution even if a schema is later reactivated by Tool Search.
+12 -12
View File
@@ -141,21 +141,21 @@ export const BUNDLE_GROUP_DEFINITIONS: BundleGroupDefinition[] = [
}, },
{ {
id: "memory-recall", id: "memory-recall",
title: "Official Hippo memory recall", title: "Persistent memory recall",
summary: "Recall project memories, inspect Hippo status/context, or recover pruned tool-call output.", summary: "Search durable user, global, project, and failure memories, search indexed past conversations, or recover pruned tool-call output.",
useWhen: ["Past project lessons, errors, decisions, or condensed outputs may answer the question"], 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"], 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", "context", "status", "recover", "记忆", "召回", "恢复输出"], keywords: ["memory", "recall", "search", "session", "history", "recover", "记忆", "召回", "检索", "历史会话", "恢复输出"],
tools: ["hippo_recall", "hippo_context", "hippo_status", "context_tree_query"], tools: ["memory_search", "session_search", "context_tree_query"],
}, },
{ {
id: "memory-management", id: "memory-management",
title: "Official Hippo memory management", title: "Persistent memory management",
summary: "Store durable project lessons or report whether recalled Hippo memories were useful.", summary: "Save, update, or remove durable memories, and create or maintain reusable procedural skills.",
useWhen: ["A non-obvious project lesson, error, or decision should persist, or recalled memories need feedback"], useWhen: ["A durable fact, preference, convention, correction, or non-obvious reusable procedure should persist across sessions"],
avoidWhen: ["The information is temporary task progress or derivable from the repository"], avoidWhen: ["The information is temporary task progress, derivable from the repository, or a one-off detail that would add noise"],
keywords: ["remember", "outcome", "lesson", "error", "记住", "反馈", "项目经验"], keywords: ["remember", "save", "replace", "remove", "skill", "procedure", "记住", "保存", "修改记忆", "技能", "流程"],
tools: ["hippo_remember", "hippo_outcome"], tools: ["memory_add", "memory_replace", "memory_remove", "skill_manage"],
}, },
{ {
id: "subagents", id: "subagents",
+7 -1
View File
@@ -20,6 +20,12 @@ export const BUNDLE_TOOL_SEARCH_DEFAULTS = {
"ctx_execute", "ctx_execute",
"ctx_execute_file", "ctx_execute_file",
"ctx_batch_execute", "ctx_batch_execute",
"memory_search",
"session_search",
"memory_add",
"memory_replace",
"memory_remove",
"skill_manage",
], ],
showToolSearchFooterStatus: false, showToolSearchFooterStatus: false,
maxActiveGroups: 5, maxActiveGroups: 5,
@@ -28,7 +34,7 @@ export const BUNDLE_TOOL_SEARCH_DEFAULTS = {
groupOverrides: {}, groupOverrides: {},
} as const; } as const;
export const BUNDLE_TOOL_SEARCH_DEFAULTS_VERSION = 2; export const BUNDLE_TOOL_SEARCH_DEFAULTS_VERSION = 3;
export interface ToolSearchConfig { export interface ToolSearchConfig {
alwaysEnabled: string[]; alwaysEnabled: string[];
+4 -2
View File
@@ -27,8 +27,10 @@ test("bundle seed names are unique and cover the declared my-pi tools", () => {
assert.equal(result.catalog.tools.length, names.length); assert.equal(result.catalog.tools.length, names.length);
assert.equal(result.catalog.tools.find((card) => card.name === "ctx_purge")?.primaryGroup, "context-administration"); assert.equal(result.catalog.tools.find((card) => card.name === "ctx_purge")?.primaryGroup, "context-administration");
assert.equal(result.catalog.tools.find((card) => card.name === "tavily_web_fetch")?.primaryGroup, "web-tavily"); assert.equal(result.catalog.tools.find((card) => card.name === "tavily_web_fetch")?.primaryGroup, "web-tavily");
assert.equal(result.catalog.tools.find((card) => card.name === "hippo_recall")?.primaryGroup, "memory-recall"); assert.equal(result.catalog.tools.find((card) => card.name === "memory_search")?.primaryGroup, "memory-recall");
assert.equal(result.catalog.tools.find((card) => card.name === "hippo_remember")?.primaryGroup, "memory-management"); assert.equal(result.catalog.tools.find((card) => card.name === "session_search")?.primaryGroup, "memory-recall");
assert.equal(result.catalog.tools.find((card) => card.name === "memory_add")?.primaryGroup, "memory-management");
assert.equal(result.catalog.tools.find((card) => card.name === "skill_manage")?.primaryGroup, "memory-management");
assert.equal(result.catalog.tools.find((card) => card.name === "Agent")?.primaryGroup, "subagents"); assert.equal(result.catalog.tools.find((card) => card.name === "Agent")?.primaryGroup, "subagents");
assert.equal(result.catalog.tools.find((card) => card.name === "get_subagent_result")?.primaryGroup, "subagents"); assert.equal(result.catalog.tools.find((card) => card.name === "get_subagent_result")?.primaryGroup, "subagents");
assert.equal(result.catalog.tools.find((card) => card.name === "steer_subagent")?.primaryGroup, "subagents"); assert.equal(result.catalog.tools.find((card) => card.name === "steer_subagent")?.primaryGroup, "subagents");
+7 -1
View File
@@ -24,6 +24,12 @@ test("writes bundle defaults when toolSearch is absent", async () => {
"ctx_execute", "ctx_execute",
"ctx_execute_file", "ctx_execute_file",
"ctx_batch_execute", "ctx_batch_execute",
"memory_search",
"session_search",
"memory_add",
"memory_replace",
"memory_remove",
"skill_manage",
]); ]);
assert.deepEqual(JSON.parse(await readFile(join(agentDir, "settings.json"), "utf8")), { assert.deepEqual(JSON.parse(await readFile(join(agentDir, "settings.json"), "utf8")), {
toolSearch: { toolSearch: {
@@ -33,7 +39,7 @@ test("writes bundle defaults when toolSearch is absent", async () => {
maxToolsPerGroup: 8, maxToolsPerGroup: 8,
maxDynamicTools: 28, maxDynamicTools: 28,
groupOverrides: {}, groupOverrides: {},
bundleDefaultsVersion: 2, bundleDefaultsVersion: 3,
}, },
}); });
assert.equal((await stat(join(agentDir, "settings.json"))).mode & 0o777, 0o600); assert.equal((await stat(join(agentDir, "settings.json"))).mode & 0o777, 0o600);
+33 -3
View File
@@ -14,7 +14,7 @@ export const LEGACY_TOOL_SEARCH_DEFAULTS = {
groupOverrides: {}, groupOverrides: {},
}; };
const CURRENT_TOOL_SEARCH_RUNTIME_DEFAULTS = { const PREVIOUS_TOOL_SEARCH_RUNTIME_DEFAULTS = {
alwaysEnabled: [ alwaysEnabled: [
"codegraph_explore", "codegraph_explore",
"lsp_diagnostics", "lsp_diagnostics",
@@ -29,9 +29,35 @@ const CURRENT_TOOL_SEARCH_RUNTIME_DEFAULTS = {
groupOverrides: {}, groupOverrides: {},
}; };
export const PREVIOUS_TOOL_SEARCH_DEFAULTS = {
...PREVIOUS_TOOL_SEARCH_RUNTIME_DEFAULTS,
bundleDefaultsVersion: 2,
};
export const CURRENT_TOOL_SEARCH_RUNTIME_DEFAULTS = {
alwaysEnabled: [
"codegraph_explore",
"lsp_diagnostics",
"ctx_execute",
"ctx_execute_file",
"ctx_batch_execute",
"memory_search",
"session_search",
"memory_add",
"memory_replace",
"memory_remove",
"skill_manage",
],
showToolSearchFooterStatus: false,
maxActiveGroups: 5,
maxToolsPerGroup: 8,
maxDynamicTools: 28,
groupOverrides: {},
};
export const CURRENT_TOOL_SEARCH_DEFAULTS = { export const CURRENT_TOOL_SEARCH_DEFAULTS = {
...CURRENT_TOOL_SEARCH_RUNTIME_DEFAULTS, ...CURRENT_TOOL_SEARCH_RUNTIME_DEFAULTS,
bundleDefaultsVersion: 2, bundleDefaultsVersion: 3,
}; };
function isObject(value) { function isObject(value) {
@@ -95,7 +121,11 @@ export async function migrateToolSearchSettings(settingsPath, options = {}) {
exactJsonValue(settings.toolSearch, CURRENT_TOOL_SEARCH_DEFAULTS) exactJsonValue(settings.toolSearch, CURRENT_TOOL_SEARCH_DEFAULTS)
|| exactJsonValue(settings.toolSearch, CURRENT_TOOL_SEARCH_RUNTIME_DEFAULTS) || exactJsonValue(settings.toolSearch, CURRENT_TOOL_SEARCH_RUNTIME_DEFAULTS)
) return { status: "current" }; ) return { status: "current" };
if (!exactJsonValue(settings.toolSearch, LEGACY_TOOL_SEARCH_DEFAULTS)) return { status: "custom" }; const matchesPreviousSnapshot = exactJsonValue(settings.toolSearch, PREVIOUS_TOOL_SEARCH_DEFAULTS)
|| exactJsonValue(settings.toolSearch, PREVIOUS_TOOL_SEARCH_RUNTIME_DEFAULTS);
if (!exactJsonValue(settings.toolSearch, LEGACY_TOOL_SEARCH_DEFAULTS) && !matchesPreviousSnapshot) {
return { status: "custom" };
}
const updated = `${JSON.stringify({ ...settings, toolSearch: CURRENT_TOOL_SEARCH_DEFAULTS }, null, 2)}\n`; const updated = `${JSON.stringify({ ...settings, toolSearch: CURRENT_TOOL_SEARCH_DEFAULTS }, null, 2)}\n`;
const directory = dirname(settingsPath); const directory = dirname(settingsPath);
+27 -8
View File
@@ -1,5 +1,5 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path"; import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
@@ -29,14 +29,15 @@ test("all package extensions load together without global registration conflicts
dependencies: Record<string, string>; dependencies: Record<string, string>;
files: string[]; files: string[];
pi: { extensions: string[] }; pi: { extensions: string[] };
allowScripts: Record<string, boolean>;
}; };
assert.ok( assert.ok(
packageJson.pi.extensions.includes("./extensions/plugin-init.ts"), packageJson.pi.extensions.includes("./extensions/plugin-init.ts"),
"the bundle must expose the project plugin initializer command", "the bundle must expose the project plugin initializer command",
); );
assert.ok( assert.ok(
packageJson.pi.extensions.includes("./hippo-memory-pi/index.ts"), packageJson.pi.extensions.includes("./pi-hermes-memory/src/index.ts"),
"the bundle must load the vendored official Hippo Memory extension", "the bundle must load the vendored Hermes memory extension",
); );
assert.ok( assert.ok(
packageJson.pi.extensions.includes("./pi-minimal-footer/index.ts"), packageJson.pi.extensions.includes("./pi-minimal-footer/index.ts"),
@@ -145,19 +146,37 @@ test("all package extensions load together without global registration conflicts
assert.equal(packageJson.dependencies["@ogulcancelik/pi-minimal-footer"], "file:./pi-minimal-footer"); assert.equal(packageJson.dependencies["@ogulcancelik/pi-minimal-footer"], "file:./pi-minimal-footer");
assert.equal(packageJson.dependencies["@tintinweb/pi-subagents"], undefined); assert.equal(packageJson.dependencies["@tintinweb/pi-subagents"], undefined);
assert.equal(packageJson.pi.extensions.includes("./extensions/subagents.ts"), false); assert.equal(packageJson.pi.extensions.includes("./extensions/subagents.ts"), false);
assert.ok(
packageJson.pi.extensions.includes("./pi-hermes-memory/src/index.ts"),
"the bundle must load the locally maintained Hermes memory extension",
);
assert.equal(packageJson.dependencies["pi-hermes-memory"], "file:./pi-hermes-memory");
assert.ok(
packageJson.files.includes("pi-hermes-memory"),
"the packed bundle must include the locally maintained Hermes memory source",
);
assert.ok(
packageJson.pi.extensions.indexOf("./pi-hermes-memory/src/index.ts") <
packageJson.pi.extensions.indexOf("./pi-compaction/index.ts"),
"the memory extension must register its session_before_compact flush before the compaction authority can cancel the event",
);
assert.equal( assert.equal(
packageJson.pi.extensions.some((entry) => entry.includes("pi-hermes-memory") || entry.includes("the-forge-flow")), packageJson.pi.extensions.some((entry) => entry.includes("hippo-memory-pi") || entry.includes("the-forge-flow")),
false, false,
"retired Hermes and third-party Hippo extensions must not be loaded", "retired Hippo and third-party memory extensions must not be loaded",
); );
assert.equal(packageJson.dependencies["@the-forge-flow/hippo-memory-pi"], undefined); assert.equal(packageJson.dependencies["@the-forge-flow/hippo-memory-pi"], undefined);
assert.equal(packageJson.dependencies["better-sqlite3"], undefined);
assert.equal(packageJson.allowScripts["better-sqlite3@12.11.1"], true);
assert.equal(
packageJson.allowScripts["better-sqlite3@13.0.3"],
undefined,
"Hermes uses better-sqlite3 13 N-API prebuilds, so no extra install-script approval is required",
);
assert.equal(packageJson.dependencies["@sinclair/typebox"], undefined); assert.equal(packageJson.dependencies["@sinclair/typebox"], undefined);
const home = await mkdtemp(join(tmpdir(), "my-pi-extension-load-")); const home = await mkdtemp(join(tmpdir(), "my-pi-extension-load-"));
const mockBin = join(home, "bin"); const mockBin = join(home, "bin");
await mkdir(mockBin, { recursive: true }); await mkdir(mockBin, { recursive: true });
const hippo = join(mockBin, "hippo");
await writeFile(hippo, "#!/bin/sh\nprintf '%s\\n' 'Hippo Memory status'\n", "utf8");
await chmod(hippo, 0o755);
const args = ["--no-extensions"]; const args = ["--no-extensions"];
for (const entry of packageJson.pi.extensions) args.push("-e", resolve(repositoryRoot, entry)); for (const entry of packageJson.pi.extensions) args.push("-e", resolve(repositoryRoot, entry));
args.push("--mode", "rpc"); args.push("--mode", "rpc");
-67
View File
@@ -1,67 +0,0 @@
import assert from "node:assert/strict";
import { chmod, mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import hippoExtension from "../hippo-memory-pi/index.ts";
test("official Hippo extension registers five tools and runs lifecycle CLI calls", async () => {
const root = await mkdtemp(join(tmpdir(), "my-pi-hippo-extension-"));
const bin = join(root, "bin");
const project = join(root, "project");
const log = join(root, "hippo.log");
await mkdir(bin, { recursive: true });
await mkdir(join(project, ".hippo"), { recursive: true });
const hippo = join(bin, "hippo");
await writeFile(
hippo,
`#!/bin/sh\nprintf '%s\\n' "$*" >> "$HIPPO_TEST_LOG"\ncase "$1" in\n status) printf '%s\\n' 'Hippo status ok' ;;\n context) printf '%s\\n' 'Remembered project context for testing' ;;\n recall) printf '%s\\n' 'Relevant project memory' ;;\n remember) printf '%s\\n' 'Remembered [test]' ;;\n outcome) printf '%s\\n' 'Outcome recorded' ;;\n sleep) printf '%s\\n' 'Sleep complete' ;;\nesac\n`,
"utf8",
);
await chmod(hippo, 0o755);
const previousPath = process.env.PATH;
const previousLog = process.env.HIPPO_TEST_LOG;
process.env.PATH = `${bin}:${previousPath ?? ""}`;
process.env.HIPPO_TEST_LOG = log;
try {
const handlers = new Map<string, Array<(event: any, ctx: any) => Promise<any>>>();
const tools = new Map<string, any>();
hippoExtension({
on(name: string, handler: (event: any, ctx: any) => Promise<any>) {
const entries = handlers.get(name) ?? [];
entries.push(handler);
handlers.set(name, entries);
},
registerTool(tool: any) {
tools.set(tool.name, tool);
},
});
assert.deepEqual(
[...tools.keys()].sort(),
["hippo_context", "hippo_outcome", "hippo_recall", "hippo_remember", "hippo_status"],
);
const ctx = { cwd: project };
const startResult = await handlers.get("session_start")?.[0]?.({}, ctx);
assert.match(startResult.systemPromptAppend, /Remembered project context/);
const rememberResult = await tools.get("hippo_remember").execute("call-1", { text: "A durable lesson" }, undefined, undefined, ctx);
assert.match(rememberResult.content[0].text, /Remembered \[test\]/);
await handlers.get("tool_result")?.[0]?.(
{ isError: true, toolName: "build", content: "Compilation failed because the generated symbol was missing" },
ctx,
);
await handlers.get("session_shutdown")?.[0]?.({}, ctx);
const calls = await readFile(log, "utf8");
assert.match(calls, /context --auto/);
assert.match(calls, /remember A durable lesson/);
assert.match(calls, /remember Tool 'build' failed:/);
assert.match(calls, /sleep/);
} finally {
process.env.PATH = previousPath;
if (previousLog === undefined) delete process.env.HIPPO_TEST_LOG;
else process.env.HIPPO_TEST_LOG = previousLog;
}
});
-187
View File
@@ -1,187 +0,0 @@
import assert from "node:assert/strict";
import { chmod, mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { spawnSync } from "node:child_process";
import test from "node:test";
import { fileURLToPath } from "node:url";
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
async function executable(path: string, content: string): Promise<void> {
await writeFile(path, `#!/bin/sh\n${content}\n`, "utf8");
await chmod(path, 0o755);
}
async function fixture(initialHippoVersion?: string) {
const root = await mkdtemp(join(tmpdir(), "my-pi-hippo-scripts-"));
const home = join(root, "home");
const bin = join(root, "bin");
const project = join(root, "project");
const log = join(root, "commands.log");
const versionState = join(root, "hippo-version");
await mkdir(home, { recursive: true });
await mkdir(bin, { recursive: true });
await mkdir(project, { recursive: true });
await writeFile(log, "", "utf8");
if (initialHippoVersion) await writeFile(versionState, `${initialHippoVersion}\n`, "utf8");
await executable(join(bin, "pi"), 'printf "pi %s\\n" "$*" >> "$MOCK_LOG"');
await executable(
join(bin, "npm"),
String.raw`printf 'npm %s\n' "$*" >> "$MOCK_LOG"
if [ "$1" = "view" ]; then
printf '%s\n' '1.33.0'
elif [ "$1" = "install" ] && [ "$2" = "-g" ]; then
printf '%s\n' '1.33.0' > "$HIPPO_VERSION_STATE"
cat > "$MOCK_BIN/hippo" <<'HIPPO'
#!/bin/sh
if [ "$1" = "--version" ]; then cat "$HIPPO_VERSION_STATE"; exit 0; fi
if [ "$1" = "init" ]; then mkdir -p .hippo; printf 'hippo init\n' >> "$MOCK_LOG"; exit 0; fi
exit 0
HIPPO
chmod +x "$MOCK_BIN/hippo"
fi`,
);
if (initialHippoVersion) {
await executable(
join(bin, "hippo"),
String.raw`if [ "$1" = "--version" ]; then cat "$HIPPO_VERSION_STATE"; exit 0; fi
if [ "$1" = "init" ]; then mkdir -p .hippo; printf 'hippo init\n' >> "$MOCK_LOG"; exit 0; fi
exit 0`,
);
}
const env = {
...process.env,
HOME: home,
PATH: `${bin}:/usr/bin:/bin`,
MOCK_BIN: bin,
MOCK_LOG: log,
HIPPO_VERSION_STATE: versionState,
PI_PACKAGE_SOURCE: repositoryRoot,
ZSH: "",
ZSH_CUSTOM: "",
ZDOTDIR: home,
};
return { root, home, bin, project, log, versionState, env };
}
async function logText(path: string): Promise<string> {
return readFile(path, "utf8");
}
test("install.sh leaves Hippo absent when installation is declined", async () => {
const f = await fixture();
const result = spawnSync("sh", [join(repositoryRoot, "install.sh")], {
cwd: f.project,
env: f.env,
input: "n\n".repeat(14),
encoding: "utf8",
timeout: 30_000,
});
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.doesNotMatch(await logText(f.log), /npm install -g hippo-memory/);
assert.match(result.stdout, /已跳过 Hippo Memory CLI/);
});
test("install.sh installs the pinned Hippo CLI without choosing an init directory", async () => {
const f = await fixture();
const input = `y\n${"n\n".repeat(12)}`;
const result = spawnSync("sh", [join(repositoryRoot, "install.sh")], {
cwd: f.project,
env: f.env,
input,
encoding: "utf8",
timeout: 30_000,
});
assert.equal(result.status, 0, result.stderr || result.stdout);
const log = await logText(f.log);
assert.match(log, /npm install -g hippo-memory@1\.33\.0/);
assert.doesNotMatch(log, /hippo init/);
assert.equal(await readFile(f.versionState, "utf8"), "1.33.0\n");
assert.match(result.stdout, /未自动运行 hippo init/);
});
test("update.sh leaves an already matching Hippo CLI untouched", async () => {
const f = await fixture("1.33.0");
const result = spawnSync("sh", [join(repositoryRoot, "update.sh")], {
cwd: f.project,
env: f.env,
encoding: "utf8",
timeout: 30_000,
});
assert.equal(result.status, 0, result.stderr || result.stdout);
const log = await logText(f.log);
assert.match(log, /npm view hippo-memory@1\.33\.0 version/);
assert.doesNotMatch(log, /npm install -g/);
assert.match(result.stdout, /已是组合包目标版本/);
});
test("update.sh upgrades an npm-managed older Hippo CLI exactly once", async () => {
const f = await fixture("1.32.0");
const result = spawnSync("sh", [join(repositoryRoot, "update.sh")], {
cwd: f.project,
env: f.env,
encoding: "utf8",
timeout: 30_000,
});
assert.equal(result.status, 0, result.stderr || result.stdout);
const log = await logText(f.log);
assert.match(log, /npm list -g --depth=0 hippo-memory/);
assert.equal((log.match(/npm install -g hippo-memory@1\.33\.0/g) ?? []).length, 1);
assert.equal(await readFile(f.versionState, "utf8"), "1.33.0\n");
});
test("update.sh preserves a Hippo executable that is not npm globally managed", async () => {
const f = await fixture("1.32.0");
await executable(
join(f.bin, "npm"),
String.raw`printf 'npm %s\n' "$*" >> "$MOCK_LOG"
if [ "$1" = "view" ]; then printf '%s\n' '1.33.0'; exit 0; fi
if [ "$1" = "list" ]; then exit 1; fi
exit 0`,
);
const result = spawnSync("sh", [join(repositoryRoot, "update.sh")], {
cwd: f.project,
env: f.env,
encoding: "utf8",
timeout: 30_000,
});
assert.equal(result.status, 1);
const log = await logText(f.log);
assert.doesNotMatch(log, /npm install -g/);
assert.match(result.stderr, /不属于 npm 全局/);
});
test("uninstall.sh keeps Hippo by default and removes only after explicit approval", async () => {
const keep = await fixture("1.33.0");
await mkdir(join(keep.project, ".hippo"), { recursive: true });
await writeFile(join(keep.project, ".hippo", "sentinel"), "keep\n", "utf8");
const keepResult = spawnSync("sh", [join(repositoryRoot, "uninstall.sh")], {
cwd: keep.project,
env: keep.env,
input: "n\n",
encoding: "utf8",
timeout: 30_000,
});
assert.equal(keepResult.status, 0, keepResult.stderr || keepResult.stdout);
assert.doesNotMatch(await logText(keep.log), /npm uninstall/);
assert.equal(await readFile(join(keep.project, ".hippo", "sentinel"), "utf8"), "keep\n");
const remove = await fixture("1.33.0");
await mkdir(join(remove.project, ".hippo"), { recursive: true });
await writeFile(join(remove.project, ".hippo", "sentinel"), "keep\n", "utf8");
const removeResult = spawnSync("sh", [join(repositoryRoot, "uninstall.sh")], {
cwd: remove.project,
env: remove.env,
input: "y\n",
encoding: "utf8",
timeout: 30_000,
});
assert.equal(removeResult.status, 0, removeResult.stderr || removeResult.stdout);
assert.match(await logText(remove.log), /npm uninstall -g hippo-memory/);
assert.equal(await readFile(join(remove.project, ".hippo", "sentinel"), "utf8"), "keep\n");
});
+13 -23
View File
@@ -11,12 +11,12 @@ import pluginInitExtension, {
validatePluginInitCwd, validatePluginInitCwd,
} from "../extensions/plugin-init.ts"; } from "../extensions/plugin-init.ts";
test("initializes missing CodeGraph and Hippo state after preflighting both CLIs", async () => { test("initializes missing CodeGraph state after preflighting the CLI", async () => {
const cwd = await mkdtemp(join(tmpdir(), "my-pi-plugin-init-")); const cwd = await mkdtemp(join(tmpdir(), "my-pi-plugin-init-"));
const calls: Array<{ command: string; args: string[]; cwd: string }> = []; const calls: Array<{ command: string; args: string[]; cwd: string }> = [];
try { try {
const targets = await findMissingPluginInitializers(cwd); const targets = await findMissingPluginInitializers(cwd);
assert.deepEqual(targets.map(({ name }) => name), ["CodeGraph", "Hippo"]); assert.deepEqual(targets.map(({ name }) => name), ["CodeGraph"]);
await initializeMissingPlugins(cwd, targets, async (command, args, options) => { await initializeMissingPlugins(cwd, targets, async (command, args, options) => {
calls.push({ command, args, cwd: options.cwd }); calls.push({ command, args, cwd: options.cwd });
@@ -25,30 +25,25 @@ test("initializes missing CodeGraph and Hippo state after preflighting both CLIs
assert.deepEqual(calls, [ assert.deepEqual(calls, [
{ command: "codegraph", args: ["--version"], cwd }, { command: "codegraph", args: ["--version"], cwd },
{ command: "hippo", args: ["--version"], cwd },
{ command: "codegraph", args: ["init", cwd], cwd }, { command: "codegraph", args: ["init", cwd], cwd },
{ command: "hippo", args: ["init"], cwd },
]); ]);
} finally { } finally {
await rm(cwd, { recursive: true, force: true }); await rm(cwd, { recursive: true, force: true });
} }
}); });
test("uses the CLIs' database files as authoritative initialization markers", async () => { test("uses the CLI's database file as the authoritative initialization marker", async () => {
const cwd = await mkdtemp(join(tmpdir(), "my-pi-plugin-init-")); const cwd = await mkdtemp(join(tmpdir(), "my-pi-plugin-init-"));
const calls: string[] = []; const calls: string[] = [];
try { try {
await mkdir(join(cwd, ".codegraph")); await mkdir(join(cwd, ".codegraph"));
await mkdir(join(cwd, ".hippo"));
assert.deepEqual( assert.deepEqual(
(await findMissingPluginInitializers(cwd)).map(({ name }) => name), (await findMissingPluginInitializers(cwd)).map(({ name }) => name),
["CodeGraph", "Hippo"], ["CodeGraph"],
"bare state directories must not count as initialized", "a bare state directory must not count as initialized",
); );
await writeFile(join(cwd, ".codegraph", "codegraph.db"), ""); await writeFile(join(cwd, ".codegraph", "codegraph.db"), "");
assert.deepEqual((await findMissingPluginInitializers(cwd)).map(({ name }) => name), ["Hippo"]);
await writeFile(join(cwd, ".hippo", "hippo.db"), "");
const targets = await findMissingPluginInitializers(cwd); const targets = await findMissingPluginInitializers(cwd);
assert.deepEqual(targets, []); assert.deepEqual(targets, []);
@@ -56,7 +51,7 @@ test("uses the CLIs' database files as authoritative initialization markers", as
calls.push(`${command} ${args.join(" ")}`); calls.push(`${command} ${args.join(" ")}`);
return { code: 0, stdout: "ok", stderr: "" }; return { code: 0, stdout: "ok", stderr: "" };
}); });
assert.deepEqual(calls, ["codegraph --version", "hippo --version"]); assert.deepEqual(calls, ["codegraph --version"]);
} finally { } finally {
await rm(cwd, { recursive: true, force: true }); await rm(cwd, { recursive: true, force: true });
} }
@@ -67,7 +62,7 @@ test("rejects unsafe project roots", () => {
assert.throws(() => validatePluginInitCwd(homedir()), /用户主目录/); assert.throws(() => validatePluginInitCwd(homedir()), /用户主目录/);
}); });
test("preflights both CLIs before creating project state", async () => { test("preflights the CLI before creating project state", async () => {
const cwd = await mkdtemp(join(tmpdir(), "my-pi-plugin-init-")); const cwd = await mkdtemp(join(tmpdir(), "my-pi-plugin-init-"));
const calls: string[] = []; const calls: string[] = [];
try { try {
@@ -75,13 +70,11 @@ test("preflights both CLIs before creating project state", async () => {
await assert.rejects( await assert.rejects(
initializeMissingPlugins(cwd, targets, async (command, args) => { initializeMissingPlugins(cwd, targets, async (command, args) => {
calls.push(`${command} ${args.join(" ")}`); calls.push(`${command} ${args.join(" ")}`);
return command === "hippo" return { code: 1, stdout: "", stderr: "not installed" };
? { code: 1, stdout: "", stderr: "not installed" }
: { code: 0, stdout: "ok", stderr: "" };
}), }),
/Hippo 检测失败:not installed/, /CodeGraph 检测失败:not installed/,
); );
assert.deepEqual(calls, ["codegraph --version", "hippo --version"]); assert.deepEqual(calls, ["codegraph --version"]);
} finally { } finally {
await rm(cwd, { recursive: true, force: true }); await rm(cwd, { recursive: true, force: true });
} }
@@ -125,9 +118,7 @@ test("plugin_init confirms the target and hot-reloads after initialization", asy
assert.match(confirmation, new RegExp(cwd.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))); assert.match(confirmation, new RegExp(cwd.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
assert.deepEqual(calls, [ assert.deepEqual(calls, [
"codegraph --version", "codegraph --version",
"hippo --version",
`codegraph init ${cwd}`, `codegraph init ${cwd}`,
"hippo init",
]); ]);
assert.equal(reloaded, true); assert.equal(reloaded, true);
} finally { } finally {
@@ -135,14 +126,14 @@ test("plugin_init confirms the target and hot-reloads after initialization", asy
} }
}); });
test("plugin_init reports retained partial state and does not reload when a later initializer fails", async () => { test("plugin_init reports the failure and does not reload when initialization fails", async () => {
let command: { handler: (args: string, ctx: any) => Promise<void> } | undefined; let command: { handler: (args: string, ctx: any) => Promise<void> } | undefined;
const pi = { const pi = {
registerCommand(_name: string, definition: typeof command) { registerCommand(_name: string, definition: typeof command) {
command = definition; command = definition;
}, },
exec: async (program: string, args: string[]) => ({ exec: async (program: string, args: string[]) => ({
code: program === "hippo" && args[0] === "init" ? 1 : 0, code: program === "codegraph" && args[0] === "init" ? 1 : 0,
stdout: "", stdout: "",
stderr: "mock failure", stderr: "mock failure",
}), }),
@@ -168,8 +159,7 @@ test("plugin_init reports retained partial state and does not reload when a late
reloaded = true; reloaded = true;
}, },
}); });
assert.match(error, /Hippo 初始化失败:mock failure/); assert.match(error, /CodeGraph 初始化失败:mock failure/);
assert.match(error, /已完成的 CodeGraph 初始化会保留,不会自动回滚/);
assert.equal(reloaded, false); assert.equal(reloaded, false);
} finally { } finally {
await rm(cwd, { recursive: true, force: true }); await rm(cwd, { recursive: true, force: true });
+33 -2
View File
@@ -9,6 +9,7 @@ import { fileURLToPath } from "node:url";
import { import {
CURRENT_TOOL_SEARCH_DEFAULTS, CURRENT_TOOL_SEARCH_DEFAULTS,
LEGACY_TOOL_SEARCH_DEFAULTS, LEGACY_TOOL_SEARCH_DEFAULTS,
PREVIOUS_TOOL_SEARCH_DEFAULTS,
migrateToolSearchSettings, migrateToolSearchSettings,
} from "../scripts/migrate-tool-search-settings.mjs"; } from "../scripts/migrate-tool-search-settings.mjs";
@@ -89,6 +90,36 @@ test("recognizes current defaults and skips files without a Tool Search section"
assert.deepEqual(await migrateToolSearchSettings(absent.settingsPath), { status: "missing-section" }); assert.deepEqual(await migrateToolSearchSettings(absent.settingsPath), { status: "missing-section" });
}); });
test("migrates the previous versioned default snapshot and pins the Hermes memory tools", async (t) => {
const { settingsPath } = await fixture(t);
await writeFile(settingsPath, JSON.stringify({ toolSearch: PREVIOUS_TOOL_SEARCH_DEFAULTS }), "utf8");
const result = await migrateToolSearchSettings(settingsPath);
assert.equal(result.status, "migrated");
const migrated = JSON.parse(await readFile(settingsPath, "utf8")).toolSearch;
assert.deepEqual(migrated, CURRENT_TOOL_SEARCH_DEFAULTS);
assert.equal(migrated.bundleDefaultsVersion, 3);
for (const name of ["memory_search", "session_search", "memory_add", "memory_replace", "memory_remove", "skill_manage"]) {
assert.ok(migrated.alwaysEnabled.includes(name), `${name} must be pinned after migration`);
}
});
test("preserves custom settings that keep the previous capacities but pin extra tools", async (t) => {
const { root, settingsPath } = await fixture(t);
const custom = {
toolSearch: {
...PREVIOUS_TOOL_SEARCH_DEFAULTS,
alwaysEnabled: [...PREVIOUS_TOOL_SEARCH_DEFAULTS.alwaysEnabled, "custom_tool"],
},
};
const original = `${JSON.stringify(custom, null, 2)}\n`;
await writeFile(settingsPath, original, "utf8");
assert.deepEqual(await migrateToolSearchSettings(settingsPath), { status: "custom" });
assert.equal(await readFile(settingsPath, "utf8"), original);
assert.deepEqual(await readdir(root), ["settings.json"]);
});
test("fails safely for invalid JSON and reports a missing settings file", async (t) => { test("fails safely for invalid JSON and reports a missing settings file", async (t) => {
const invalid = await fixture(t); const invalid = await fixture(t);
await writeFile(invalid.settingsPath, "{ invalid\n", "utf8"); await writeFile(invalid.settingsPath, "{ invalid\n", "utf8");
@@ -130,8 +161,8 @@ async function updateFixture(t: test.TestContext, piExitStatus = 0) {
await mkdir(join(updatedBundle, "scripts"), { recursive: true }); await mkdir(join(updatedBundle, "scripts"), { recursive: true });
await copyFile(join(repositoryRoot, "config", "p10k.zsh"), join(updatedBundle, "config", "p10k.zsh")); await copyFile(join(repositoryRoot, "config", "p10k.zsh"), join(updatedBundle, "config", "p10k.zsh"));
await copyFile( await copyFile(
join(repositoryRoot, "config", "hippo-memory-version"), join(repositoryRoot, "config", "pi-hashline-edit.json"),
join(updatedBundle, "config", "hippo-memory-version"), join(updatedBundle, "config", "pi-hashline-edit.json"),
); );
await copyFile( await copyFile(
join(repositoryRoot, "scripts", "migrate-tool-search-settings.mjs"), join(repositoryRoot, "scripts", "migrate-tool-search-settings.mjs"),
+2 -2
View File
@@ -29,8 +29,8 @@ async function fixture(
await mkdir(join(target, "config"), { recursive: true }); await mkdir(join(target, "config"), { recursive: true });
await copyFile(join(repositoryRoot, "config", "p10k.zsh"), join(target, "config", "p10k.zsh")); await copyFile(join(repositoryRoot, "config", "p10k.zsh"), join(target, "config", "p10k.zsh"));
await copyFile( await copyFile(
join(repositoryRoot, "config", "hippo-memory-version"), join(repositoryRoot, "config", "pi-hashline-edit.json"),
join(target, "config", "hippo-memory-version"), join(target, "config", "pi-hashline-edit.json"),
); );
await mkdir(bin, { recursive: true }); await mkdir(bin, { recursive: true });
-36
View File
@@ -3,19 +3,6 @@
set -u set -u
PACKAGE_SOURCE=${PI_PACKAGE_SOURCE:-git:git@bitbucket.org:siakitem/my-pi.git} PACKAGE_SOURCE=${PI_PACKAGE_SOURCE:-git:git@bitbucket.org:siakitem/my-pi.git}
uninstall_failed=0
ask_yes_no() {
prompt=$1
printf '%s [y/N] ' "$prompt"
if ! IFS= read -r answer; then
answer=n
fi
case $answer in
y|Y|yes|YES|Yes) return 0 ;;
*) return 1 ;;
esac
}
if ! command -v pi >/dev/null 2>&1; then if ! command -v pi >/dev/null 2>&1; then
printf '错误: 找不到 pi,无法卸载组合包。请确保 pi 位于 PATH。\n' >&2 printf '错误: 找不到 pi,无法卸载组合包。请确保 pi 位于 PATH。\n' >&2
@@ -28,27 +15,4 @@ if ! pi remove "$PACKAGE_SOURCE"; then
exit 1 exit 1
fi fi
if command -v hippo >/dev/null 2>&1; then
hippo_version=$(hippo --version 2>/dev/null | sed -n 's/^[^0-9]*\([0-9][0-9.]*\).*$/\1/p' | sed -n '1p')
if ask_yes_no "检测到 Hippo Memory CLI ${hippo_version:-未知版本}。是否同时卸载 npm 全局 hippo-memory?项目与全局记忆数据会保留。"; then
if ! command -v npm >/dev/null 2>&1; then
printf '%s\n' '警告: 找不到 npm,无法卸载 Hippo Memory CLI。' >&2
uninstall_failed=1
elif ! npm list -g --depth=0 hippo-memory >/dev/null 2>&1; then
printf '%s\n' '警告: 当前 hippo 不属于 npm 全局 hippo-memory 安装,已保留。' >&2
uninstall_failed=1
elif npm uninstall -g hippo-memory; then
printf '%s\n' 'Hippo Memory CLI 已卸载;.hippo 与用户级记忆数据均已保留。'
else
printf '%s\n' '警告: Hippo Memory CLI 卸载失败。' >&2
uninstall_failed=1
fi
else
printf '%s\n' 'Hippo Memory CLI 已保留。'
fi
else
printf '%s\n' '未检测到 Hippo Memory CLI,无需额外卸载。'
fi
printf '%s\n' '组合包已移除。CodeGraph、Kotlin LSP、JDT LS、Java 和记忆数据均未卸载,因为它们可能仍被其他项目使用。' printf '%s\n' '组合包已移除。CodeGraph、Kotlin LSP、JDT LS、Java 和记忆数据均未卸载,因为它们可能仍被其他项目使用。'
exit "$uninstall_failed"
+1 -60
View File
@@ -13,7 +13,6 @@ fi
PACKAGE_SOURCE=${PI_PACKAGE_SOURCE:-git:git@bitbucket.org:siakitem/my-pi.git} PACKAGE_SOURCE=${PI_PACKAGE_SOURCE:-git:git@bitbucket.org:siakitem/my-pi.git}
POWERLEVEL10K_PRESET_FILE=$SCRIPT_DIR/config/p10k.zsh POWERLEVEL10K_PRESET_FILE=$SCRIPT_DIR/config/p10k.zsh
HIPPO_MEMORY_VERSION_FILE=$SCRIPT_DIR/config/hippo-memory-version
CODEGRAPH_INSTALL_URL=https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh CODEGRAPH_INSTALL_URL=https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh
CODEGRAPH_LATEST_RELEASE_URL=https://github.com/colbymchenry/codegraph/releases/latest CODEGRAPH_LATEST_RELEASE_URL=https://github.com/colbymchenry/codegraph/releases/latest
KITTY_INSTALL_URL=https://sw.kovidgoyal.net/kitty/installer.sh KITTY_INSTALL_URL=https://sw.kovidgoyal.net/kitty/installer.sh
@@ -50,7 +49,6 @@ select_updated_bundle_config() {
if [ -n "$updated_bundle_dir" ] && [ -f "$updated_bundle_dir/config/p10k.zsh" ]; then if [ -n "$updated_bundle_dir" ] && [ -f "$updated_bundle_dir/config/p10k.zsh" ]; then
POWERLEVEL10K_PRESET_FILE=$updated_bundle_dir/config/p10k.zsh POWERLEVEL10K_PRESET_FILE=$updated_bundle_dir/config/p10k.zsh
HIPPO_MEMORY_VERSION_FILE=$updated_bundle_dir/config/hippo-memory-version
elif [ ! -f "$POWERLEVEL10K_PRESET_FILE" ]; then elif [ ! -f "$POWERLEVEL10K_PRESET_FILE" ]; then
warn "无法定位升级后的组合包配置,找不到 ${POWERLEVEL10K_PRESET_FILE}。" warn "无法定位升级后的组合包配置,找不到 ${POWERLEVEL10K_PRESET_FILE}。"
return 1 return 1
@@ -122,7 +120,7 @@ migrate_tool_search_settings() {
migration_result=$(node "$migration_helper" "$settings_file") || return 1 migration_result=$(node "$migration_helper" "$settings_file") || return 1
case $migration_result in case $migration_result in
migrated:*) migrated:*)
say "✓ Tool Search 旧版默认配置已迁移到 5 个动态组 / 28 个动态工具。" say "✓ Tool Search 旧版默认配置已迁移到当前组合包默认值。"
say " 原配置备份:${migration_result#migrated:}" say " 原配置备份:${migration_result#migrated:}"
;; ;;
current) current)
@@ -396,62 +394,6 @@ update_codegraph() {
rm -f "$installer_file" rm -f "$installer_file"
} }
read_hippo_memory_version() {
if [ ! -f "$HIPPO_MEMORY_VERSION_FILE" ]; then
warn "找不到 Hippo Memory 版本文件:$HIPPO_MEMORY_VERSION_FILE"
return 1
fi
hippo_target_version=$(sed -n '1p' "$HIPPO_MEMORY_VERSION_FILE")
case $hippo_target_version in
''|*[!0-9.]*)
warn "Hippo Memory 版本文件内容无效:$hippo_target_version"
return 1
;;
esac
printf '%s\n' "$hippo_target_version"
}
installed_hippo_memory_version() {
hippo --version 2>/dev/null | sed -n 's/^[^0-9]*\([0-9][0-9.]*\).*$/\1/p' | sed -n '1p'
}
update_hippo_memory() {
if ! command -v hippo >/dev/null 2>&1; then
say "- Hippo Memory CLI 未安装,升级流程跳过。"
return 0
fi
if ! command -v npm >/dev/null 2>&1; then
warn "升级 Hippo Memory CLI 需要 npm。"
return 1
fi
hippo_target_version=$(read_hippo_memory_version) || return 1
hippo_published_version=$(npm view "hippo-memory@$hippo_target_version" version 2>/dev/null) || hippo_published_version=
if [ "$hippo_published_version" != "$hippo_target_version" ]; then
warn "无法确认 npm 上的 Hippo Memory 目标版本 $hippo_target_version,已保留现状。"
return 1
fi
hippo_installed_version=$(installed_hippo_memory_version)
if [ -z "$hippo_installed_version" ]; then
warn "无法读取 Hippo Memory CLI 本地版本,已保留现状。"
return 1
fi
if [ "$hippo_installed_version" = "$hippo_target_version" ]; then
say "✓ Hippo Memory CLI $hippo_installed_version 已是组合包目标版本。"
return 0
fi
if ! npm list -g --depth=0 hippo-memory >/dev/null 2>&1; then
warn "检测到 hippo,但它不属于 npm 全局 hippo-memory 安装;已保留现状。"
return 1
fi
say "发现 Hippo Memory CLI ${hippo_target_version}(当前 ${hippo_installed_version}),正在升级。"
npm install -g "hippo-memory@$hippo_target_version" || return 1
hippo_updated_version=$(installed_hippo_memory_version)
if [ "$hippo_updated_version" != "$hippo_target_version" ]; then
warn "Hippo Memory CLI 升级后版本校验失败:期望 $hippo_target_version,实际 ${hippo_updated_version:-未知}。"
return 1
fi
}
update_zsh_environment() { update_zsh_environment() {
resolve_zsh_paths resolve_zsh_paths
if [ ! -f "$oh_my_zsh_dir/oh-my-zsh.sh" ]; then if [ ! -f "$oh_my_zsh_dir/oh-my-zsh.sh" ]; then
@@ -548,7 +490,6 @@ say "组合包升级完成,开始升级已安装的终端环境和机器级 CL
update_kitty || record_update_failure "Kitty" update_kitty || record_update_failure "Kitty"
update_zsh_environment || record_update_failure "Zsh 环境" update_zsh_environment || record_update_failure "Zsh 环境"
update_codegraph || record_update_failure "CodeGraph" update_codegraph || record_update_failure "CodeGraph"
update_hippo_memory || record_update_failure "Hippo Memory CLI"
update_brew_formula_if_installed "kotlin-lsp" "kotlin-lsp" "kotlin-lsp" update_brew_formula_if_installed "kotlin-lsp" "kotlin-lsp" "kotlin-lsp"
update_brew_formula_if_installed "openjdk@21" "Java 21" "java" update_brew_formula_if_installed "openjdk@21" "Java 21" "java"
update_brew_formula_if_installed "jdtls" "jdtls" "jdtls" update_brew_formula_if_installed "jdtls" "jdtls" "jdtls"