mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 07:23:06 +00:00
feat: vendor official hippo memory extension
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
.pi/npm/
|
.pi/npm/
|
||||||
|
.hippo/
|
||||||
.pi-debug/
|
.pi-debug/
|
||||||
|
|
||||||
# Compiled Java class files
|
# Compiled Java class files
|
||||||
|
|||||||
@@ -31,14 +31,17 @@
|
|||||||
- 初始导入快照:`ddfb23646fd3957b791214de278e23aa393c9b13`(`v0.3.6`)。
|
- 初始导入快照:`ddfb23646fd3957b791214de278e23aa393c9b13`(`v0.3.6`)。
|
||||||
- 该目录不是 submodule,不保留嵌套 `.git`、上游 `.pi` 状态、`node_modules` 或构建产物。
|
- 该目录不是 submodule,不保留嵌套 `.git`、上游 `.pi` 状态、`node_modules` 或构建产物。
|
||||||
- `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`。
|
||||||
- `pi-hermes-memory@0.9.6`:提供持久记忆、会话搜索、后台学习和 secret scanning,默认使用 policy-only 模式。
|
- `hippo-memory-pi/`:从官方 `hippo-memory` 仓库的 `extensions/pi-extension/` 导入并由本仓库直接维护;提供 session start 项目记忆注入、工具错误过滤捕获、session shutdown sleep 和 5 个 `hippo_*` 工具。
|
||||||
|
- 上游来源:<https://github.com/kitfunso/hippo-memory>
|
||||||
|
- 初始导入快照:`e928179a3b35e8fe5837878aed071d6025ced45c`(`v1.33.0`)。
|
||||||
|
- 官方 npm tarball 不包含 Pi Extension;仓库保留官方 README、LICENSE 和来源说明,匹配 CLI 版本由 `config/hippo-memory-version` 声明。
|
||||||
- `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程 compaction,默认阈值为 90%。
|
- `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程 compaction,默认阈值为 90%。
|
||||||
- `@ogulcancelik/pi-minimal-footer@0.1.10`:用紧凑的上下文仪表和订阅用量条替换默认 footer。
|
- `@ogulcancelik/pi-minimal-footer@0.1.10`:用紧凑的上下文仪表和订阅用量条替换默认 footer。
|
||||||
- `pi-condense@2.9.1`:总结已完成的工具调用批次,以短 stub 替换历史原始输出,并通过 `context_tree_query` 按需恢复;组合包在用户尚未配置 `contextPrune.enabled` 时默认开启。
|
- `pi-condense@2.9.1`:总结已完成的工具调用批次,以短 stub 替换历史原始输出,并通过 `context_tree_query` 按需恢复;组合包在用户尚未配置 `contextPrune.enabled` 时默认开启。
|
||||||
- 根包还固定安装 `@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`、`pi-context-view` 和 `@firstpick/pi-extension-codex-fast-mode`;`@gotgenes/pi-permission-system` 与 `pi-tool-search` 分别指向仓库内对应目录的本地 `file:` 依赖。
|
- 根包还固定安装 `@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`、`pi-context-view` 和 `@firstpick/pi-extension-codex-fast-mode`;`@gotgenes/pi-permission-system` 与 `pi-tool-search` 分别指向仓库内对应目录的本地 `file:` 依赖。
|
||||||
- `install.sh`:先安装根组合包,再交互检查 Kitty/Solarized Dark、Oh My Zsh/Powerlevel10k/Zsh 插件、CodeGraph、Kotlin LSP、Java 21+ 和 JDT LS;缺失项目只在用户明确选择 `Y` 后安装或配置。Powerlevel10k 默认配置来自仓库内置的 `config/p10k.zsh`(当前 Rainbow/ASCII 单行紧凑主题)。
|
- `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 单行紧凑主题)。
|
||||||
- `update.sh`:先通过 `pi update` 升级根组合包,再只升级当前已安装的终端环境和机器级依赖;未安装项直接跳过。升级前先查询并比较本地与远端版本,只有版本不同时才下载或替换;Powerlevel10k 配置优先从 `pi update` 后的已安装组合包读取,并与 `.zshrc` 受管块一起按内容比较后增量同步。
|
- `update.sh`:先通过 `pi update` 升级根组合包,再只升级当前已安装的终端环境和机器级依赖;未安装项直接跳过。升级前先查询并比较本地与远端版本,只有版本不同时才下载或替换;Powerlevel10k 配置优先从 `pi update` 后的已安装组合包读取,并与 `.zshrc` 受管块一起按内容比较后增量同步。
|
||||||
- `uninstall.sh`:只移除根组合包,不卸载或还原可能被其他项目共享的终端环境、CodeGraph、Kotlin LSP、JDT LS 或 Java。
|
- `uninstall.sh`:先移除根组合包;若检测到 Hippo Memory CLI,再明确询问是否卸载确认属于 npm 全局安装的 `hippo-memory`,默认保留且始终不删除 `.hippo/` 或用户记忆数据。其他共享终端环境和机器工具不卸载。
|
||||||
|
|
||||||
## 当前职责与默认行为
|
## 当前职责与默认行为
|
||||||
|
|
||||||
@@ -69,10 +72,13 @@
|
|||||||
- `config/pi-permission-system.json` 必须显式配置 `authorizerChain: ["auto-review"]`,并把需要自动复核的 Git 非只读操作、包管理及其他类别声明为 `ask`;硬 `deny` 不得改成可由模型覆盖的 `ask`。
|
- `config/pi-permission-system.json` 必须显式配置 `authorizerChain: ["auto-review"]`,并把需要自动复核的 Git 非只读操作、包管理及其他类别声明为 `ask`;硬 `deny` 不得改成可由模型覆盖的 `ask`。
|
||||||
- `pi-lsp` 的 TypeScript/JavaScript 后端由根包固定依赖提供,`extensions/lsp.ts` 使用当前 Node 可执行文件直接启动包内 `typescript-language-server` CLI,不得依赖或调用 VS Code GUI;Kotlin/JDT LS 仍使用组合包部署的 `kotlin-lsp --stdio` 与 `jdtls` 配置,根包不安装这两个系统可执行文件,便捷脚本可在用户逐项明确确认后通过 Homebrew 安装 Kotlin LSP、Java 21 和 JDT LS。
|
- `pi-lsp` 的 TypeScript/JavaScript 后端由根包固定依赖提供,`extensions/lsp.ts` 使用当前 Node 可执行文件直接启动包内 `typescript-language-server` CLI,不得依赖或调用 VS Code GUI;Kotlin/JDT LS 仍使用组合包部署的 `kotlin-lsp --stdio` 与 `jdtls` 配置,根包不安装这两个系统可执行文件,便捷脚本可在用户逐项明确确认后通过 Homebrew 安装 Kotlin LSP、Java 21 和 JDT LS。
|
||||||
- 终端环境配置只属于便捷脚本: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` 在安装流程中逐项询问后才安装或启用。
|
||||||
- Codex 远程压缩只对 `openai-codex` 生效,默认在 turn boundary 达到 90% 时触发;Hermes Memory 默认使用 policy-only 模式。
|
- Codex 远程压缩只对 `openai-codex` 生效,默认在 turn boundary 达到 90% 时触发;官方 Hippo Extension 在已初始化项目的 session start 注入上下文、过滤捕获工具错误,并在 session shutdown 执行 `hippo sleep`。
|
||||||
|
- Hippo Extension 直接使用 PATH 中的官方全局 `hippo` CLI;`install.sh` 只在 CLI 缺失且用户明确确认后安装 `config/hippo-memory-version` 指定的 npm 版本,不得扫描项目、选择初始化目录或执行 `hippo init`。用户必须在自己选择的项目目录中手动初始化。
|
||||||
|
- `update.sh` 只升级已经安装且属于 npm 全局包的 Hippo CLI;必须从 `pi update` 后的组合包读取目标版本,先用 `npm view` 确认,再比较本地版本,版本相同不得重新安装,CLI 缺失或来源未知时保留现状。
|
||||||
|
- `uninstall.sh` 只在根包移除成功后询问是否卸载 npm 全局 Hippo CLI,默认拒绝;不得删除项目 `.hippo/`、全局记忆或旧 Hermes 数据。
|
||||||
- `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` 视为明确授权升级已安装组件及同步已有受管配置,但不得安装缺失组件或改写受管块之外的用户配置;卸载仍不改动共享机器级工具。
|
- `install.sh` 不检查默认不需要的 `rtk` CLI;`update.sh` 和 `uninstall.sh` 使用同一个 `PI_PACKAGE_SOURCE` 规则分别调用 `pi update` 和 `pi remove`。运行 `update.sh` 视为明确授权升级已安装组件及同步已有受管配置,但不得安装缺失组件或改写受管块之外的用户配置;卸载仅可在用户当次明确确认后移除 npm 全局 Hippo CLI,其他共享机器级工具保持不变。
|
||||||
|
|
||||||
## 修改边界
|
## 修改边界
|
||||||
|
|
||||||
@@ -80,7 +86,7 @@
|
|||||||
- 保留原项目的 `LICENSE`、版权信息和必要的来源说明。
|
- 保留原项目的 `LICENSE`、版权信息和必要的来源说明。
|
||||||
- 扩展运行目录中的 `config.json`、日志、构建产物、覆盖率目录和依赖目录属于本地状态,不应提交;`config/pi-permission-system.json`、`config/pi-hashline-edit.json` 与 `config/lsp.json` 分别是权限链、Hashline 默认行为和 LSP 后端的组合包权威源配置,必须提交并维护。
|
- 扩展运行目录中的 `config.json`、日志、构建产物、覆盖率目录和依赖目录属于本地状态,不应提交;`config/pi-permission-system.json`、`config/pi-hashline-edit.json` 与 `config/lsp.json` 分别是权限链、Hashline 默认行为和 LSP 后端的组合包权威源配置,必须提交并维护。
|
||||||
- 外部 Pi 扩展依赖必须在根 `package.json` 中使用精确版本,并更新根 `package-lock.json`;不要用仓库级 `.pi/settings.json` 代替组合包依赖。
|
- 外部 Pi 扩展依赖必须在根 `package.json` 中使用精确版本,并更新根 `package-lock.json`;不要用仓库级 `.pi/settings.json` 代替组合包依赖。
|
||||||
- 需要原生构建的依赖只按锁定版本加入根 `allowScripts`;当前仅允许 Hermes Memory 所需的 `better-sqlite3`,不得批量批准其他 install scripts。
|
- 需要原生构建的依赖只按锁定版本加入根 `allowScripts`;当前 `better-sqlite3@12.11.1` 仍由 Context Mode 使用并保持精确放行。官方 Hippo CLI 是独立机器级 npm 包,不得因此向根包批量批准其他 install scripts。
|
||||||
- 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 入口或上游旧宿主实现直接覆盖。
|
||||||
- 上游仅作为参考来源。同步上游改动时先核对本仓库已有修改,再按明确范围移植;不要直接覆盖本地实现。
|
- 上游仅作为参考来源。同步上游改动时先核对本仓库已有修改,再按明确范围移植;不要直接覆盖本地实现。
|
||||||
@@ -125,6 +131,14 @@
|
|||||||
- reviewer 只能处理 `pi-permission-system` 已判定为 `ask` 的请求;不得绕过硬 `deny`,不得另加并行 `tool_call` 审批层,也不得扩大 `pi-permission-system` 明确授予的 delegation envelope。
|
- reviewer 只能处理 `pi-permission-system` 已判定为 `ask` 的请求;不得绕过硬 `deny`,不得另加并行 `tool_call` 审批层,也不得扩大 `pi-permission-system` 明确授予的 delegation envelope。
|
||||||
- 修改 reviewer 策略、可信证据边界、配置结构、模型解析或 authorizer 注册时,必须同步更新 README、schema 和相应测试。
|
- 修改 reviewer 策略、可信证据边界、配置结构、模型解析或 authorizer 注册时,必须同步更新 README、schema 和相应测试。
|
||||||
|
|
||||||
|
## `hippo-memory-pi` 开发约定
|
||||||
|
|
||||||
|
- 扩展入口是 `hippo-memory-pi/index.ts`,来源记录是 `hippo-memory-pi/UPSTREAM.md`;只从官方 `kitfunso/hippo-memory` 的明确 tag/commit 移植 `extensions/pi-extension/`,保留官方 README、LICENSE 和版权。
|
||||||
|
- 官方 npm tarball 当前不包含 Pi Extension,因此根包直接打包并加载本地源码;不得同时加载第三方 `@the-forge-flow/hippo-memory-pi` 或第二个 Hippo Pi Extension。
|
||||||
|
- CLI 版本权威源是 `config/hippo-memory-version`;修改它时必须同步上游源码快照、README、AGENTS、安装/升级 mock 测试和扩展加载验证。
|
||||||
|
- 扩展依赖 PATH 中的全局 `hippo`。CLI 缺失时必须 fail-safe,不得阻止其他扩展加载;项目未执行 `hippo init` 时不得擅自创建 `.hippo/`。
|
||||||
|
|
||||||
|
|
||||||
## `pi-tool-search` 开发约定
|
## `pi-tool-search` 开发约定
|
||||||
|
|
||||||
- 扩展入口是 `pi-tool-search/extensions/index.ts`,组合包权威分组位于 `pi-tool-search/extensions/bundle-groups.ts`,目录生成/校验/缓存位于 `pi-tool-search/extensions/catalog.ts`,配置部署位于 `pi-tool-search/extensions/config.ts`,测试保留在 `pi-tool-search/test/`。
|
- 扩展入口是 `pi-tool-search/extensions/index.ts`,组合包权威分组位于 `pi-tool-search/extensions/bundle-groups.ts`,目录生成/校验/缓存位于 `pi-tool-search/extensions/catalog.ts`,配置部署位于 `pi-tool-search/extensions/config.ts`,测试保留在 `pi-tool-search/test/`。
|
||||||
@@ -140,10 +154,13 @@
|
|||||||
|
|
||||||
安装、升级、卸载或搜索配置脚本变化时,至少运行 `sh -n install.sh`、`sh -n update.sh`、`sh -n uninstall.sh`、`sh -n search_config.sh` 和 ShellCheck,并核对脚本仍具有可执行权限、仍包含在根 `package.json` 的 `files` 中、README 描述与实际流程一致。搜索配置测试只能使用虚拟 key 和隔离 HOME,不得把真实 key 写入测试输出。涉及真实 `pi install`、`pi update`、`pi remove`、Homebrew、远程安装器、Git 克隆或真实用户终端配置的端到端验证属于外部写操作,未经明确要求不得执行;可以使用隔离的临时 HOME 和 mock 命令验证分支行为。
|
安装、升级、卸载或搜索配置脚本变化时,至少运行 `sh -n install.sh`、`sh -n update.sh`、`sh -n uninstall.sh`、`sh -n search_config.sh` 和 ShellCheck,并核对脚本仍具有可执行权限、仍包含在根 `package.json` 的 `files` 中、README 描述与实际流程一致。搜索配置测试只能使用虚拟 key 和隔离 HOME,不得把真实 key 写入测试输出。涉及真实 `pi install`、`pi update`、`pi remove`、Homebrew、远程安装器、Git 克隆或真实用户终端配置的端到端验证属于外部写操作,未经明确要求不得执行;可以使用隔离的临时 HOME 和 mock 命令验证分支行为。
|
||||||
|
|
||||||
|
Hippo 脚本变化必须额外用隔离 HOME/PATH 和 mock `pi`、`npm`、`hippo` 验证:安装缺失 CLI 但不调用 `hippo init`、相同版本不升级、不同版本精确升级、未知来源 CLI 不替换、卸载默认保留、明确确认后只卸载 npm 全局包且保留数据。不得在测试中执行真实全局 npm 写入或修改真实 `.hippo/`。
|
||||||
|
|
||||||
在 `pi-permission-system/` 内至少运行 `npm run typecheck`、`npm run test` 和 `npm run build`。在 `pi-permission-auto-review/` 内至少运行同样三项;涉及权限集成时还要使用仓库内 `pi-permission-system` 验证 authorizer 注册、`allow` / `deny` / `defer` 与 delegation envelope。
|
在 `pi-permission-system/` 内至少运行 `npm run typecheck`、`npm run test` 和 `npm run build`。在 `pi-permission-auto-review/` 内至少运行同样三项;涉及权限集成时还要使用仓库内 `pi-permission-system` 验证 authorizer 注册、`allow` / `deny` / `defer` 与 delegation envelope。
|
||||||
|
|
||||||
在 `pi-tool-search/` 内至少运行 `npm run typecheck`、`npm run test` 和 `npm run build`;涉及加载入口或 active-tool 行为时还要运行根扩展联合加载测试和实际 packed tarball 的隔离安装验证。
|
在 `pi-tool-search/` 内至少运行 `npm run typecheck`、`npm run test` 和 `npm run build`;涉及加载入口或 active-tool 行为时还要运行根扩展联合加载测试和实际 packed tarball 的隔离安装验证。
|
||||||
|
|
||||||
|
|
||||||
在 `pi-rtk-optimizer/` 内按改动范围选择最小充分验证:
|
在 `pi-rtk-optimizer/` 内按改动范围选择最小充分验证:
|
||||||
|
|
||||||
- `npm run build`:TypeScript 转译检查。
|
- `npm run build`:TypeScript 转译检查。
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
- `pi-context-view@0.4.2`:查看上下文占用。
|
- `pi-context-view@0.4.2`:查看上下文占用。
|
||||||
- `@firstpick/pi-extension-codex-fast-mode@0.1.1`:为 Codex provider 提供会话级 `/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 后端由组合包内置。
|
||||||
- `pi-hermes-memory@0.9.6`:提供持久记忆、会话搜索和 secret scanning。
|
- `hippo-memory-pi/`:从官方 `hippo-memory v1.33.0` 源码导入的 Pi Extension,提供项目记忆注入、错误捕获和 sleep consolidation。
|
||||||
- `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程压缩。
|
- `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程压缩。
|
||||||
- `@ogulcancelik/pi-minimal-footer@0.1.10`:用紧凑的上下文仪表和订阅用量条替换默认 footer。
|
- `@ogulcancelik/pi-minimal-footer@0.1.10`:用紧凑的上下文仪表和订阅用量条替换默认 footer。
|
||||||
- `pi-condense@2.9.1`:把已完成的工具调用批次总结为可恢复的短摘要,并通过 `context_tree_query` 按需取回原始输出;组合包首次加载时默认开启。
|
- `pi-condense@2.9.1`:把已完成的工具调用批次总结为可恢复的短摘要,并通过 `context_tree_query` 按需取回原始输出;组合包首次加载时默认开启。
|
||||||
@@ -104,9 +104,12 @@ PI_PACKAGE_SOURCE="git:git@bitbucket.org:siakitem/my-pi.git" ./update.sh
|
|||||||
|
|
||||||
根 `package.json` 对外部依赖使用精确版本,对本仓库维护的包使用 `file:` 路径;`package-lock.json` 固定完整依赖树。不要再用项目级 `.pi/settings.json` 重复安装这些扩展,否则同一扩展可能被加载两次。
|
根 `package.json` 对外部依赖使用精确版本,对本仓库维护的包使用 `file:` 路径;`package-lock.json` 固定完整依赖树。不要再用项目级 `.pi/settings.json` 重复安装这些扩展,否则同一扩展可能被加载两次。
|
||||||
|
|
||||||
Hermes Memory 的 SQLite search 依赖 `better-sqlite3` 原生模块;根包只在 `allowScripts` 中
|
Hippo 官方 Pi Extension 存在于上游 Git 仓库的 `extensions/pi-extension/`,但没有包含在
|
||||||
精确放行当前锁定版本的构建脚本。若升级 Hermes 或 `better-sqlite3`,必须同步核对并更新该精确
|
`hippo-memory@1.33.0` npm tarball 中。本仓库从官方 `v1.33.0` /
|
||||||
版本,不能把其他依赖的 install scripts 一并放行。
|
`e928179a3b35e8fe5837878aed071d6025ced45c` 导入源码到 `hippo-memory-pi/` 并直接加载;
|
||||||
|
匹配的 CLI 版本由 `config/hippo-memory-version` 声明。`install.sh` 会在 CLI 缺失时询问是否执行
|
||||||
|
`npm install -g hippo-memory@<固定版本>`,但不会选择项目或执行 `hippo init`。用户应进入需要记忆的
|
||||||
|
项目目录自行运行 `hippo init`。
|
||||||
|
|
||||||
默认配置不需要系统 `rtk` CLI。只有以后在 `/rtk` 中主动开启
|
默认配置不需要系统 `rtk` CLI。只有以后在 `/rtk` 中主动开启
|
||||||
`RTK command rewriting` 时,才需要另外安装 `rtk` 可执行文件。
|
`RTK command rewriting` 时,才需要另外安装 `rtk` 可执行文件。
|
||||||
@@ -275,11 +278,23 @@ brew install JetBrains/utils/kotlin-lsp
|
|||||||
|
|
||||||
JDT LS 需要 Java 21 或更高版本;可使用带 `jdtls` wrapper 的官方发行包或系统包。
|
JDT LS 需要 Java 21 或更高版本;可使用带 `jdtls` wrapper 的官方发行包或系统包。
|
||||||
|
|
||||||
### Hermes Memory
|
### Hippo Memory
|
||||||
|
|
||||||
Hermes Memory 使用上游默认的 `policy-only` 模式:不把完整记忆直接注入每轮上下文,由工具按需
|
官方扩展要求全局 `hippo` CLI,并要求目标项目存在 `.hippo/`。组合包加载仓库内官方扩展源码,
|
||||||
搜索;同时启用后台复核、自动 consolidation、session search 和 secret scanning。首次需要检索
|
注册 `hippo_recall`、`hippo_remember`、`hippo_outcome`、`hippo_status` 和 `hippo_context` 五个工具。
|
||||||
旧会话时运行 `/memory-index-sessions`。
|
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/` 中的旧数据。需要保留的
|
||||||
|
旧记忆应另行审核后导入,不在安装、升级或卸载流程中自动转换或删除。
|
||||||
|
|
||||||
|
`install.sh` 只对缺失的 Hippo CLI 询问一次,默认拒绝;缺少 npm 时保留已完成的组合包安装并报告失败。
|
||||||
|
脚本不会扫描仓库、选择目录或执行 `hippo init`。`update.sh` 只处理已经存在且确认属于 npm 全局安装的
|
||||||
|
Hippo CLI:从升级后的组合包读取固定版本,先通过 npm 确认版本存在,再比较本地版本,只有不同时才升级。
|
||||||
|
`uninstall.sh` 移除组合包后询问是否一并执行 `npm uninstall -g hippo-memory`,默认保留;无论选择什么,
|
||||||
|
`.hippo/` 和用户记忆数据都不会被脚本删除。
|
||||||
|
|
||||||
### Codex 原生远程压缩
|
### Codex 原生远程压缩
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
1.33.0
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# 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).
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Upstream provenance
|
||||||
|
|
||||||
|
- Upstream repository: <https://github.com/kitfunso/hippo-memory>
|
||||||
|
- Imported release: `v1.33.0`
|
||||||
|
- Imported commit: `e928179a3b35e8fe5837878aed071d6025ced45c`
|
||||||
|
- Imported source: `extensions/pi-extension/`
|
||||||
|
- Initial import date: 2026-08-19
|
||||||
|
|
||||||
|
This directory contains the official Hippo Memory Pi extension source, which is
|
||||||
|
present in the upstream Git repository but excluded from the
|
||||||
|
`hippo-memory@1.33.0` npm tarball. The extension is maintained directly in
|
||||||
|
`my-pi`, is not a submodule, and does not retain a nested `.git` directory.
|
||||||
|
|
||||||
|
The matching CLI version is declared in `config/hippo-memory-version` and is
|
||||||
|
managed as an optional machine-level dependency by the bundle helper scripts.
|
||||||
|
Future upstream updates must be ported from an exact tag/commit and reviewed
|
||||||
|
against local changes rather than overwriting this directory wholesale.
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
/**
|
||||||
|
* 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
@@ -13,6 +13,7 @@ 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
|
||||||
@@ -91,6 +92,43 @@ 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
|
||||||
@@ -335,6 +373,28 @@ 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
|
||||||
|
|||||||
Generated
+2
-60
@@ -19,7 +19,6 @@
|
|||||||
"pi-condense": "2.9.1",
|
"pi-condense": "2.9.1",
|
||||||
"pi-context-view": "0.4.2",
|
"pi-context-view": "0.4.2",
|
||||||
"pi-hashline-edit": "0.8.3",
|
"pi-hashline-edit": "0.8.3",
|
||||||
"pi-hermes-memory": "0.9.6",
|
|
||||||
"pi-lsp": "0.1.7",
|
"pi-lsp": "0.1.7",
|
||||||
"pi-mcp-adapter": "2.26.0",
|
"pi-mcp-adapter": "2.26.0",
|
||||||
"pi-tool-search": "file:./pi-tool-search",
|
"pi-tool-search": "file:./pi-tool-search",
|
||||||
@@ -34,7 +33,6 @@
|
|||||||
"@earendil-works/pi-ai": "*",
|
"@earendil-works/pi-ai": "*",
|
||||||
"@earendil-works/pi-coding-agent": "*",
|
"@earendil-works/pi-coding-agent": "*",
|
||||||
"@earendil-works/pi-tui": "*",
|
"@earendil-works/pi-tui": "*",
|
||||||
"@sinclair/typebox": "*",
|
|
||||||
"typebox": "*"
|
"typebox": "*"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
@@ -47,9 +45,6 @@
|
|||||||
"@earendil-works/pi-tui": {
|
"@earendil-works/pi-tui": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@sinclair/typebox": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"typebox": {
|
"typebox": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
@@ -2072,19 +2067,6 @@
|
|||||||
"url": "https://github.com/sponsors/eemeli"
|
"url": "https://github.com/sponsors/eemeli"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@earendil-works/pi-tui": {
|
|
||||||
"version": "0.80.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.10.tgz",
|
|
||||||
"integrity": "sha512-c2JO29PbhKPEQ6fgHQKAl0WhwuFqzWfzspMmP+8B5tpDuP+0mvarRbKKg8gq4b+pQx/QX+6aVS4ko7deoyjQjg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"get-east-asian-width": "1.6.0",
|
|
||||||
"marked": "18.0.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=22.19.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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",
|
||||||
@@ -3822,18 +3804,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ansi-regex": {
|
|
||||||
"version": "6.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz",
|
|
||||||
"integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/assertion-error": {
|
"node_modules/assertion-error": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||||
@@ -4770,6 +4740,7 @@
|
|||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
|
||||||
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
|
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -5365,6 +5336,7 @@
|
|||||||
"version": "18.0.5",
|
"version": "18.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz",
|
||||||
"integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==",
|
"integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"marked": "bin/marked.js"
|
"marked": "bin/marked.js"
|
||||||
@@ -5689,21 +5661,6 @@
|
|||||||
"@sinclair/typebox": "*"
|
"@sinclair/typebox": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pi-hermes-memory": {
|
|
||||||
"version": "0.9.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/pi-hermes-memory/-/pi-hermes-memory-0.9.6.tgz",
|
|
||||||
"integrity": "sha512-tCPGzRk5Wcsb9LgBEcd5QYKZXLmI4N+9BUwC0JyHzHSMV6uCO73058Bk8+BRzvR2Lbpk2oUlg6NylueCegURLw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@earendil-works/pi-tui": "^0.80.2",
|
|
||||||
"better-sqlite3": "^12.9.0",
|
|
||||||
"strip-ansi": "7.2.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@earendil-works/pi-ai": ">=0.80.1",
|
|
||||||
"@earendil-works/pi-coding-agent": ">=0.80.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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",
|
||||||
@@ -6480,21 +6437,6 @@
|
|||||||
"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",
|
||||||
|
|||||||
+2
-6
@@ -10,6 +10,7 @@
|
|||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"extensions",
|
"extensions",
|
||||||
|
"hippo-memory-pi",
|
||||||
"pi-rtk-optimizer",
|
"pi-rtk-optimizer",
|
||||||
"pi-tool-search/extensions",
|
"pi-tool-search/extensions",
|
||||||
"pi-tool-search/docs",
|
"pi-tool-search/docs",
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
"./extensions/lsp.ts",
|
"./extensions/lsp.ts",
|
||||||
"./node_modules/pi-context-view/src/index.ts",
|
"./node_modules/pi-context-view/src/index.ts",
|
||||||
"./node_modules/@firstpick/pi-extension-codex-fast-mode/index.ts",
|
"./node_modules/@firstpick/pi-extension-codex-fast-mode/index.ts",
|
||||||
"./node_modules/pi-hermes-memory/src/index.ts",
|
"./hippo-memory-pi/index.ts",
|
||||||
"./node_modules/@ogulcancelik/pi-codex-compaction/index.ts",
|
"./node_modules/@ogulcancelik/pi-codex-compaction/index.ts",
|
||||||
"./node_modules/@ogulcancelik/pi-minimal-footer/index.ts",
|
"./node_modules/@ogulcancelik/pi-minimal-footer/index.ts",
|
||||||
"./extensions/condense.ts",
|
"./extensions/condense.ts",
|
||||||
@@ -74,7 +75,6 @@
|
|||||||
"pi-condense": "2.9.1",
|
"pi-condense": "2.9.1",
|
||||||
"pi-context-view": "0.4.2",
|
"pi-context-view": "0.4.2",
|
||||||
"pi-hashline-edit": "0.8.3",
|
"pi-hashline-edit": "0.8.3",
|
||||||
"pi-hermes-memory": "0.9.6",
|
|
||||||
"pi-lsp": "0.1.7",
|
"pi-lsp": "0.1.7",
|
||||||
"pi-mcp-adapter": "2.26.0",
|
"pi-mcp-adapter": "2.26.0",
|
||||||
"pi-tool-search": "file:./pi-tool-search",
|
"pi-tool-search": "file:./pi-tool-search",
|
||||||
@@ -86,7 +86,6 @@
|
|||||||
"@earendil-works/pi-ai": "*",
|
"@earendil-works/pi-ai": "*",
|
||||||
"@earendil-works/pi-coding-agent": "*",
|
"@earendil-works/pi-coding-agent": "*",
|
||||||
"@earendil-works/pi-tui": "*",
|
"@earendil-works/pi-tui": "*",
|
||||||
"@sinclair/typebox": "*",
|
|
||||||
"typebox": "*"
|
"typebox": "*"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
@@ -99,9 +98,6 @@
|
|||||||
"@earendil-works/pi-tui": {
|
"@earendil-works/pi-tui": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@sinclair/typebox": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"typebox": {
|
"typebox": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
- Added deterministic no-model fallback grouping, private manifest-hash caching, exact group overrides, lazy `/tool-search-rebuild`, and nested model usage accounting.
|
- Added deterministic no-model fallback grouping, private manifest-hash caching, exact group overrides, lazy `/tool-search-rebuild`, and nested model usage accounting.
|
||||||
- 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.
|
||||||
|
|
||||||
## [0.3.6] - 2026-04-24
|
## [0.3.6] - 2026-04-24
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,8 @@ Run `/tool-search-rebuild` to remove model enrichment. Standard bundle tools imm
|
|||||||
| `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` | Memory/session search and condensed-output recovery |
|
| `memory-recall` | Official Hippo recall/context/status and condensed-output recovery |
|
||||||
| `memory-management` | Durable memory add/replace/remove |
|
| `memory-management` | Official Hippo remember/outcome tools |
|
||||||
| `skill-management` | Pi-native skill management |
|
|
||||||
| `mcp-management` | Shared MCP adapter management |
|
| `mcp-management` | Shared MCP adapter management |
|
||||||
|
|
||||||
Pinned tools are removed from these dynamic groups at runtime. For example, the default-pinned `codegraph_explore` and `lsp_diagnostics` do not consume the `code-intelligence` group budget.
|
Pinned tools are removed from these dynamic groups at runtime. For example, the default-pinned `codegraph_explore` and `lsp_diagnostics` do not consume the `code-intelligence` group budget.
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
8. Group load and member execution update an in-session LRU counter. Loading past `maxActiveGroups` or `maxDynamicTools` removes the least-recently-used dynamic groups.
|
8. Group load and member execution update an in-session LRU counter. Loading past `maxActiveGroups` or `maxDynamicTools` removes the least-recently-used dynamic groups.
|
||||||
9. `turn_start` notices tool or grouping-configuration changes by hash, resets stale dynamic groups, and reconstructs the checked-in/hybrid catalog.
|
9. `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. `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.
|
||||||
|
|
||||||
A purely additive first load receives Pi's native dynamic-tool result propagation. A replacement that evicts one group while adding another is intentionally non-additive; current Pi detects the removal and uses its safe next-request fallback instead of attaching an invalid additive-only hint.
|
A purely additive first load receives Pi's native dynamic-tool result propagation. A replacement that evicts one group while adding another is intentionally non-additive; current Pi detects the removal and uses its safe next-request fallback instead of attaching an invalid additive-only hint.
|
||||||
|
|
||||||
A model cannot call a schema it had not received in an already-generated assistant response. `tool_search` therefore returns normally, and the next model request sees the activated group's complete schemas. No provider payload rewriting, proxy dispatch, hidden steer message, old package alias, or duplicate Pi runtime is used.
|
A model cannot call a schema it had not received in an already-generated assistant response. `tool_search` therefore returns normally, and the next model request sees the activated group's complete schemas. No provider payload rewriting, proxy dispatch, hidden steer message, old package alias, or duplicate Pi runtime is used.
|
||||||
|
|||||||
@@ -105,30 +105,21 @@ export const BUNDLE_GROUP_DEFINITIONS: BundleGroupDefinition[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "memory-recall",
|
id: "memory-recall",
|
||||||
title: "Memory and session recall",
|
title: "Official Hippo memory recall",
|
||||||
summary: "Search durable memories, prior sessions, or recover pruned tool-call output.",
|
summary: "Recall project memories, inspect Hippo status/context, or recover pruned tool-call output.",
|
||||||
useWhen: ["Past decisions, preferences, failures, conversations, or condensed outputs may answer the question"],
|
useWhen: ["Past project lessons, errors, decisions, or condensed outputs may answer the question"],
|
||||||
avoidWhen: ["The required evidence is already in the current visible context"],
|
avoidWhen: ["The required evidence is already in the current visible context"],
|
||||||
keywords: ["memory", "session", "history", "recover", "记忆", "历史会话", "恢复输出"],
|
keywords: ["memory", "recall", "context", "status", "recover", "记忆", "召回", "恢复输出"],
|
||||||
tools: ["memory_search", "session_search", "context_tree_query"],
|
tools: ["hippo_recall", "hippo_context", "hippo_status", "context_tree_query"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "memory-management",
|
id: "memory-management",
|
||||||
title: "Memory management",
|
title: "Official Hippo memory management",
|
||||||
summary: "Add, replace, or remove durable user, project, global, or failure memories.",
|
summary: "Store durable project lessons or report whether recalled Hippo memories were useful.",
|
||||||
useWhen: ["A stable preference, correction, environment fact, convention, or durable lesson should change"],
|
useWhen: ["A non-obvious project lesson, error, or decision should persist, or recalled memories need feedback"],
|
||||||
avoidWhen: ["The information is temporary task progress"],
|
avoidWhen: ["The information is temporary task progress or derivable from the repository"],
|
||||||
keywords: ["remember", "add memory", "replace memory", "forget", "记住", "更新记忆", "删除记忆"],
|
keywords: ["remember", "outcome", "lesson", "error", "记住", "反馈", "项目经验"],
|
||||||
tools: ["memory_add", "memory_replace", "memory_remove"],
|
tools: ["hippo_remember", "hippo_outcome"],
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "skill-management",
|
|
||||||
title: "Procedural skill management",
|
|
||||||
summary: "Create, inspect, patch, update, or remove reusable Pi-native procedural skills.",
|
|
||||||
useWhen: ["A reusable workflow or non-obvious procedure should persist across sessions"],
|
|
||||||
avoidWhen: ["Saving one-off task state or a generic summary"],
|
|
||||||
keywords: ["skill", "procedure", "workflow", "技能", "流程", "工作流"],
|
|
||||||
tools: ["skill_manage"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "mcp-management",
|
id: "mcp-management",
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ 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 === "hippo_remember")?.primaryGroup, "memory-management");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user overrides take priority and unknown tools retain deterministic fallback groups", () => {
|
test("user overrides take priority and unknown tools retain deterministic fallback groups", () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { mkdtemp, readFile } from "node:fs/promises";
|
import { chmod, mkdir, mkdtemp, readFile, 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";
|
||||||
@@ -11,9 +11,26 @@ const hasPi = spawnSync("pi", ["--version"], { encoding: "utf8" }).status === 0;
|
|||||||
|
|
||||||
test("all package extensions load together without global registration conflicts", { skip: !hasPi }, async () => {
|
test("all package extensions load together without global registration conflicts", { skip: !hasPi }, async () => {
|
||||||
const packageJson = JSON.parse(await readFile(join(repositoryRoot, "package.json"), "utf8")) as {
|
const packageJson = JSON.parse(await readFile(join(repositoryRoot, "package.json"), "utf8")) as {
|
||||||
|
dependencies: Record<string, string>;
|
||||||
pi: { extensions: string[] };
|
pi: { extensions: string[] };
|
||||||
};
|
};
|
||||||
|
assert.ok(
|
||||||
|
packageJson.pi.extensions.includes("./hippo-memory-pi/index.ts"),
|
||||||
|
"the bundle must load the vendored official Hippo Memory extension",
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
packageJson.pi.extensions.some((entry) => entry.includes("pi-hermes-memory") || entry.includes("the-forge-flow")),
|
||||||
|
false,
|
||||||
|
"retired Hermes and third-party Hippo extensions must not be loaded",
|
||||||
|
);
|
||||||
|
assert.equal(packageJson.dependencies["@the-forge-flow/hippo-memory-pi"], 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");
|
||||||
|
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("--help");
|
args.push("--help");
|
||||||
@@ -27,6 +44,7 @@ test("all package extensions load together without global registration conflicts
|
|||||||
XDG_CONFIG_HOME: join(home, ".config"),
|
XDG_CONFIG_HOME: join(home, ".config"),
|
||||||
PI_CODING_AGENT_DIR: join(home, ".pi-agent"),
|
PI_CODING_AGENT_DIR: join(home, ".pi-agent"),
|
||||||
MY_PI_SEARCH_CONFIG: join(home, "missing-search.env"),
|
MY_PI_SEARCH_CONFIG: join(home, "missing-search.env"),
|
||||||
|
PATH: `${mockBin}:${process.env.PATH ?? ""}`,
|
||||||
PI_OFFLINE: "1",
|
PI_OFFLINE: "1",
|
||||||
TAVILY_API_KEY: "",
|
TAVILY_API_KEY: "",
|
||||||
EXA_API_KEY: "",
|
EXA_API_KEY: "",
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
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");
|
||||||
|
});
|
||||||
+42
-2
@@ -3,6 +3,19 @@
|
|||||||
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
|
||||||
@@ -10,5 +23,32 @@ if ! command -v pi >/dev/null 2>&1; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
printf '正在从 Pi 中移除组合包:%s\n' "$PACKAGE_SOURCE"
|
printf '正在从 Pi 中移除组合包:%s\n' "$PACKAGE_SOURCE"
|
||||||
pi remove "$PACKAGE_SOURCE"
|
if ! pi remove "$PACKAGE_SOURCE"; then
|
||||||
printf '%s\n' '组合包已移除。CodeGraph、Kotlin LSP、JDT LS 和 Java 均未卸载,因为它们可能仍被其他项目使用。'
|
printf '%s\n' '错误: 组合包移除失败。' >&2
|
||||||
|
exit 1
|
||||||
|
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 和记忆数据均未卸载,因为它们可能仍被其他项目使用。'
|
||||||
|
exit "$uninstall_failed"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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
|
||||||
@@ -49,6 +50,7 @@ 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
|
||||||
@@ -264,6 +266,62 @@ 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
|
||||||
@@ -340,6 +398,7 @@ 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"
|
||||||
|
|||||||
Reference in New Issue
Block a user