@trim21/personal-pi-extensions
Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more
Package details
Install @trim21/personal-pi-extensions from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@trim21/personal-pi-extensions- Package
@trim21/personal-pi-extensions- Version
0.0.206- Published
- Aug 13, 2026
- Downloads
- 5,258/mo · 4,355/wk
- Author
- trim21
- License
- unknown
- Types
- extension, skill
- Size
- 279.8 KB
- Dependencies
- 0 dependencies · 5 peers
Pi manifest JSON
{
"extensions": [
"src/vision-agent.ts",
"src/bwrap/index.ts",
"src/workspace-guard.ts",
"src/opencode-edit.ts",
"src/opencode-read.ts",
"src/opencode-write.ts",
"src/bash-default-timeout.ts",
"src/gh-readonly.ts",
"src/spawn-agent.ts",
"src/opencode-todo.ts",
"src/question.ts",
"src/talk/index.ts"
],
"skills": [
"src/talk/skills"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-extensions
pi coding-agent 自定义扩展集合。
扩展概览
| 扩展 | 描述 |
|---|---|
| bwrap | 基于 bubblewrap 的 OS 级沙箱,提供文件系统和网络隔离 |
| workspace-guard | 限制文件写入在 workspace 内,外部写入需用户审批 |
| opencode-edit | 替换内置 edit 工具,使用 opencode 的 schema 和匹配引擎 |
| bash-default-timeout | 为 bash 工具设置默认超时(180 秒) |
| vision-agent | 视觉代理:主模型不支持视觉时,spawn 子 agent 识别图片 |
| todowrite | opencode 风格的任务列表工具,完整列表替换语义 |
| question | opencode 风格的提问工具,阻塞式询问用户选择 |
| talk | session 间消息传递,SQLite 邮箱 + 双向 ask 时间戳仲裁 |
bwrap
基于 bubblewrap 的 OS 级沙箱,为所有 bash 命令提供文件系统和网络隔离。
前置条件: 安装 bubblewrap(apt install bubblewrap / pacman -S bubblewrap / dnf install bubblewrap)。
模式
可在运行时切换:
| 模式 | 沙箱 | 网络 | 可写文件系统 | 提权方式 |
|---|---|---|---|---|
allow-all |
关 | 开 | 完整 | 无需 |
workspace-write |
开 | 关 | workspace + /tmp |
用户审批 |
readonly |
开 | 关 | 无 | 用户审批 |
提权机制
bash 工具注册了 request_full_access 和 request_full_access_reason 参数。模型需要全权限时须说明原因(如需要网络、写入 workspace 外部路径)。
建议模型不确定时先尝试沙箱模式,若因沙箱限制失败,再以完整权限重试。
保护目录
.git、.pi、.agent 即使在 workspace-write 模式下也始终只读。
运行时命令
/bwrap— 显示当前模式和路径配置/bwrap-allow-all— 切换到 allow-all 模式/bwrap-workspace-write— 切换到 workspace-write 模式/bwrap-readonly— 切换到 readonly 模式
配置
配置文件(项目优先于全局):
~/.pi/agent/extensions/bwrap.json(全局).pi/bwrap.json(项目)
{
// "allow-all" | "workspace-write" | "readonly"
"mode": "workspace-write",
// 自定义 bwrap 路径(可选)
"bwrapPath": "/usr/local/bin/bwrap",
// 可写路径列表,~ 展开为 $HOME,覆盖默认值
"writablePaths": [".", "/tmp", "~/my-projects"],
// 额外可写路径,与默认值合并(ro-bind)
"extraWritablePaths": ["~/.config"],
// tmpfs 挂载路径(避免写入磁盘)
"tmpfsPaths": [],
// 额外 bwrap 参数
"extraArgs": ["--die-with-parent"],
}
使用
pi -e ./src/bwrap/index.ts
# 或通过配置文件注册后自动加载
workspace-guard
阻止 write 和 edit 工具写入 workspace 外部的路径。读取工具(read、ls、find、grep)不受限制。
- workspace 内或
/tmp下的路径自动放行 - 外部路径需通过确认对话框由用户审批,对话框内以 ```diff 代码块展示将要发生的变更预览(与 opencode-edit 共享匹配引擎,能定位时显示带行号的真实 patch,否则退化为参数 diff)
- 无需配置
使用
pi -e ./src/workspace-guard.ts
opencode-edit
替换内置 edit 工具,使用 opencode 的 schema 和模糊匹配引擎。核心 replacer 和 replace() 函数直接复制自 opencode,行为与原版完全一致。匹配引擎位于 src/opencode-edit-engine.ts,与 workspace-guard 的审批弹窗 diff 预览共享。
支持的匹配策略:
- 精确匹配(SimpleReplacer)
- 行尾空白容差(LineTrimmedReplacer)
- 块首尾锚定(BlockAnchorReplacer)
- 空白规范化(WhitespaceNormalizedReplacer)
- 缩进灵活匹配(IndentationFlexibleReplacer)
- 转义规范化(EscapeNormalizedReplacer)
- 首尾空白修剪(TrimmedBoundaryReplacer)
- 上下文感知匹配(ContextAwareReplacer)
- 多次出现替换(MultiOccurrenceReplacer)
所有匹配策略按顺序尝试,第一个匹配成功即返回。同时自动处理 BOM、CRLF/LF 行尾转换和文件写入队列。
使用
pi -e ./src/opencode-edit.ts
bash-default-timeout
为所有 bash 工具调用设置 180 秒默认超时。仅在模型未显式指定 timeout 时生效,避免长时间运行的命令无限挂起。
使用
pi -e ./src/bash-default-timeout.ts
vision-agent
视觉代理扩展。主模型不支持视觉(如 DeepSeek)时自动启用 describe_image 工具;主模型支持视觉时自动隐藏,图片由 pi 原生透传。
describe_image 工具只接收本地图片路径(path,单个或数组,一次可识别多张),图片直接以 base64 data URL 放进请求体,由视觉模型按顺序逐张描述,中间不经过任何 read 工具或 agent。内置默认 system prompt(图像识别助手),并支持 prompt 参数追加具体描述要求(如「图中验证码是什么」「逐字翻译图中的文字」),缺省时自动生成通用描述指令。功能与 pi-vlm-proxy 一致,但配置不单独维护。
配置
不需要独立配置文件,直接复用 pi 已有的配置:
// ~/.pi/agent/settings.json —— 指定视觉模型
{
"defaultProvider": "axonhub",
"visionConfig": {
"provider": "axonhub", // 可选,缺省回退到 defaultProvider
"model": "mimo-v2.5",
},
}
provider 的 baseUrl / apiKey 从 ~/.pi/agent/models.json(pi 自定义 provider 配置)解析,认证、代理、网络全部复用 pi 自身配置。
未配置 visionConfig(或 provider 缺失)时扩展不会注册 describe_image 工具,agent 看不到也调不到,避免一个必然失败的僵尸工具;配置好后 /reload 即可生效。
使用
pi -e ./src/vision-agent.ts
注意: 本扩展与 pi-vlm-proxy 都注册同名 describe_image 工具,启用前请先从 ~/.pi/agent/settings.json 的 packages 中移除 pi-vlm-proxy,避免工具注册冲突。
todowrite
opencode 风格的任务列表工具,参数与语义和 opencode 的 todowrite 工具一致。取代原 todo-pendant.ts 的 widget 输出方式,改用 details.pendant.markdown 渲染(与 vision-agent 相同的 pendant 约定)。
- 完整列表替换语义:模型每次调用都传完整的 todo 列表,工具整体替换当前列表
- 参数:
todos: Array<{ content, status, priority }>status:pending|in_progress|completed|cancelledpriority:high|medium|low
- 持久化:列表存进工具结果
details.todos,跟随会话分支自动恢复 - 渲染:每次调用用完整 markdown 列表输出对应的任务(pendant 面板自动展开)
与 pi 内置 todo 工具(create/update/list/… 单条动作)不同,本工具没有单条增删改动作,模型必须每次都传完整列表。
使用
pi -e ./src/opencode-todo.ts
question
opencode 风格的提问工具,参数与语义和 opencode 的 question 工具一致。阻塞式执行:工具调用挂起,等用户作答后才把答案返回给模型。
- 参数:
questions: Array<{ question, header, options, multiple? }>options每项为{ label, description }multiple缺省为单选,true时循环用ui.select逐个勾选直到「✓ Done」
- 自定义答案:每个问题自动追加
Type your own answer.选项,选中后走ui.input自由输入 - 返回值:每个问题一个 label 数组(
Answer = string[]),跳过的为空数组 - 输出:与 opencode 一致 ——
User has answered your questions: "q"="a", "q2"="Unanswered"...
交互全部走 pi 内置的 ctx.ui.select / ctx.ui.input,不写自定义 TUI 渲染;option.description 不显示在对话框里,仅保留在 details 中。
使用
pi -e ./src/question.ts
talk
session 间消息传递:不同 pi session(同一台机器)通过一个共享的 SQLite 邮箱互相发送消息、提问并等待回复。
架构(三层)
storage.ts —— 存储层:TalkStorage 接口 + SqliteTalkStorage 实现(node:sqlite,零 npm 依赖)
core.ts —— talk 核心:registry/mailbox/policy/format + TalkCore 协调器,只依赖存储层,通过回调 yield 投递/通知
index.ts —— pi adapter:把 core 接到 pi 的 sendMessage / 生命周期事件 / 工具注册
存储层抽象成接口是为了后续可换成 HTTP / remote 后端,talk 核心无需改动。
工具(LLM 可见)
| 工具 | 作用 |
|---|---|
talk-list-sessions |
列出会话,返回 JSON 数组(status / work_dir / id / name,自己带 self: true);始终列出所有可见会话,status 区分 live(idle / working / waiting-talk-message)与 offline |
talk-ask |
向某个 session 提问并阻塞等待回复(默认 30 分钟超时) |
talk-send |
发送纯文本消息到单个 session(to 只接受明确的 session id,不支持广播) |
talk-reply |
回复一个 ask(replyTo 为 ask id,显式关联、不推断) |
对端消息自动投递(无需主动拉取):投递方式由 talk.deliver 配置,steer 在模型工作过程中打断/唤醒,queue 排队到 session 下一轮自然 turn 时注入。
定位只认 session id:talk-send / talk-ask / talk-watch 的 to 只接受 talk-list-sessions 返回的 id(pi 的 session uuid)精确匹配,不做 name/路径/前缀匹配。
标记废弃 session:/talk-dead 给 session 打 offline 标志并把 lastSeenAt 置 0(列表显示为 offline,下次 sweep 无 mail 即回收):无参标记当前 session,/talk-dead <sessionId> 标记指定 session,/talk-dead --all 标记所有其他可见 session。
关键设计
- presence 不靠心跳:presence 由
offline标志 + 进程 pid 存活判定;pid 存活时还校验进程启动时间(/proc/<pid>/stat),排除 pid 回卷复用造成的误判。未标记 offline 且进程存活即 live,否则 offline;没有心跳,进程挂死(wedged)与健康空闲不可区分。status在 live 时显示working/waiting-talk-message(talk-ask阻塞等待回复中)/idle。 - 定期清理:进程仍存活的记录永不回收;进程已死且最后活跃超过 24h 且无未投递 mail 的记录会被定期 sweep(30 分钟一次)回收;有 mail 的保留 30 天。resume 后 session 会自动重新注册,无 mail 即无损失。
- 投递成功才消费:信件只在成功交给
sendMessage后才从 inbox 删除,投递失败留在 inbox 下次重试——不会因sendMessage吞异常而静默丢信。 - 双向 ask 仲裁:
talk-ask发起前先检查收件箱(有对方消息就先读/先回);阻塞等待期间若收到对方的 ask(而非 reply),按两个 ask 的ts字段仲裁——先 ask 者主导继续等,后 ask 者让位并先回复对方。ts是信件内固定字段,双方读到同一对值,结论天然对称;同毫秒碰撞用session dir + session id字符串比较兜底。 - typebox runtime 验证:所有从存储读出的值经 TypeBox schema 校验,损坏/伪造数据被拒绝,不做
as T强转。 - 安全:纯文本 ≤32KB;10s 去重 / 30s 限速 8 条 / 50 积压上限(防环);每条投递标注来源(来自另一个 pi session,非用户)。
- workspace 可见性:每个 workspace 通过
<cwd>/.pi/talk.json的allowed控制自己能看到哪些 session,见下方配置。
配置
sqlite 文件路径按优先级取第一个可用值:
- 环境变量
PI_TALK_DB - global
~/.pi/agent/settings.json里的talk.db_path - 默认
~/.pi/agent/talk.db
db_path 支持 ~ 展开(~/… → 用户主目录),相对路径相对 ~/.pi/agent 解析;绝对路径原样使用。
// ~/.pi/agent/settings.json
{
"talk": { "db_path": "~/data/talk.db", "deliver": "queue" },
}
talk.deliver 控制对端消息的投递方式:
"steer":消息到达时打断当前工作(工具调用间隙注入),空闲 session 被唤醒;"queue":消息排队,在 session 下一轮自然 turn(如用户发消息)时注入,不主动唤醒。
默认 "queue"。
workspace 可见性
每个 workspace 通过 <cwd>/.pi/talk.json 的 allowed 决定自己能看到哪些 session:
// ~/projects/company1/.pi/talk.json
{
"allowed": ["~/projects/company1/"],
}
allowed是路径前缀列表:能看到前缀本身及其子目录下的 session(~/projects/company1/*),看不到~/projects/company2/下的 session;company1不会误匹配company12- 路径支持
~展开,相对路径相对该 workspace 的 cwd 解析 - 无
allowed字段 → 全部可见;"allowed": []→ 谁都看不到 - 可见性单向生效:A 的配置只决定 A 能看到谁,不影响 B
- 不可见的 session 不仅 list 不到,也无法
talk-send/talk-ask寻址(即使知道 id 也会被拒绝)
| 变量 | 默认 | 含义 |
|---|---|---|
PI_TALK_DB |
settings 或 ~/.pi/agent/talk.db |
SQLite 邮箱数据库路径 |
PI_TALK_INBOUND |
accept |
refuse 时丢弃所有 peer 消息 |
talk.deliver |
queue |
消息投递方式:steer / queue |
使用
pi -e ./src/talk/index.ts
安装
通过 npm/git 包
// ~/.pi/agent/settings.json
{
"packages": ["github:trim21/pi-extensions"],
}
命令行加载单个扩展
pi -e ./src/bwrap/index.ts
pi -e ./src/workspace-guard.ts
开发
pnpm install # 安装依赖
pnpm run check # tsc --noEmit + prettier --check
pnpm run format # prettier --write
新增扩展
- 在
src/下创建扩展文件 - 在
package.json的pi.extensions数组中注册