Magpie 的 OpenBot 破甲插件:gateway hook + 配置页面。
它只改写经过 Magpie 网关的 OpenBot 请求,目标有三件事:
- 保留每个 OpenBot bot 的
name/title/description里的语气、身份、背景; - 清掉人设里的显式 anti-jobs,并改写 OpenBot 固定 developer prompt 里的反覆盖条款;
- 把可配置的全局执行协议注入到请求的系统提示词里。
不修改 OpenBot 本体,不修改 app.asar,不修改 OpenBot 数据库,不修改 ~/.config/opencode。
本仓库只包含插件文件;安装命令见第 5 节。插件运行时不写任何状态文件,也不改 plugins.json。
Magpie 有两套请求改写入口:
| 入口 | 运行环境 | 文件系统 | ctx.cwd |
适合本插件 |
|---|---|---|---|---|
magpie.middleware |
moejs | 无 | 无 | 不适合 |
magpie.hooks |
Bun plugin host | 有 | 有 | 适合 |
本插件需要:
- 每次请求读取本地协议文件;
- 解析相对路径时用
ctx.cwd; - 读
ctx.origin做来源白名单; - 配置页通过
magpie.options.set热更新后,下一次请求生效; gatewayTimeoutMs: 3000。
因此用 magpie.hooks,文件是 hooks.js。
Magpie 社区插件支持 magpie.page:一个沙箱 iframe 页面,出现在窗口侧边栏的 Plugins 组里。页面可以:
await magpie.readymagpie.theme/magpie.onThememagpie.options.get()magpie.options.set()(每秒最多一次)- 用原生
<input type="file">+File.text()导入用户主动选的文件
配置页提供:
- 快速开始:一键启用(推荐设置)/ 一键全部关闭,三步说明
- 总开关
off/observe/enforce三种模式- 四种协议来源:内置完整版 / 内置精简版(省 token)/ 本地文件 / 页面内联
- 文件路径配置、内联正文编辑与文件导入
- 高级清洗开关
- 按 OpenBot bot 名灰度(
profileNames)、来源、Magpie agent、模型前缀过滤 - 配置 JSON 一键查看/复制备份;内置 JSON 编辑器(粘贴修改、类型校验、应用)与字段说明表
- 说明:Magpie → 插件 → Options 是宿主级的原始
magpie.options编辑器;本页是同一份 options 的日常编辑 UI,宿主编辑器保留作为插件页故障/无页面时的兜底,不要也无法从插件侧删除宿主入口。 - 保存、恢复默认、未保存状态
协议正文有四个可选来源,但同一时刻只用一个:
armor_source |
正文来自 | 改提示词方式 | 热更新 |
|---|---|---|---|
bundled |
插件自带 armor.md(约 4.4 KB) |
改插件目录里的 armor.md |
是,按 mtime/size 缓存 |
compact |
插件自带 armor.compact.md(约 1 KB,省 token) |
改插件目录里的精简版 | 是,按 mtime/size 缓存 |
file |
armor_file 指定的本地文件 |
用外部编辑器改该文件 | 是,每次请求读文件 |
inline |
页面里的 armor_text |
页面编辑或导入文件后保存 | 是,Magpie 重启插件 host 后下一次请求生效 |
页面里的「导入文件」会把文件内容读进 armor_text 并切到 inline,不会同时维护文件和内联两份真相。
「使用此文件」切到 file,此后正文归那个文件,页面只读。
「使用内置」切回 bundled。
stripPersonaSections / stripTaskBullets / 英文段落清洗都是尽力而为的已知格式清洗,不是通用语义解析器:
- 中文目前只识别
【不做什么】、【不做】、【不能做】、【边界】、【限制】、【禁止】等已知标题; - 英文目前只识别
Anti-jobs、Boundaries、Limits、Restrictions、Out of scope、Never do等已知标题; - 如果 bot 用自然语言写 anti-jobs(例如“本角色不负责售后”、“我只做 A,不做 B”),可能不会被删除;
- 真正通用的一层不是删除,而是
armor.md第 1.2 条:任何语言、标题、段落、措辞里的 anti-jobs 都作废;patchProfile还会在 description 末尾追加“本角色无 anti-jobs”的执行协议行。
所以:删除是可选清理,协议覆盖才是通用保险。这也是参考 magpie-purge / agent-purge 的 replace 只能算近似、strict 更彻底但会丢宿主上下文这一设计后的取舍。
enabled: false:安装后不自动改写请求,必须显式打开。allowedOrigins: ["local", "magpie-key", "unknown"]:gateway-key/remote请求跳过。mode: "enforce":默认执行;observe只计算不返回改写后的 body;off不动。- hook 只改 system / messages / instructions / contents 中的文本,不改
tools、tool_choice、model、stream、functions、toolConfig;Magpie 会拒绝改这些字段的 gateway hook。 - 任何异常、超时、协议文件不可读、非 OpenBot 请求都会 fail-open,原请求照发。
targetAllOpenCode: true是兜底:上下文压缩把<openbot-developer-instructions>压掉后,仍对所有ctx.agent === "opencode"请求注入协议;默认关闭。页面上的「一键启用(推荐设置)」会打开这个兜底;如果同时使用非 OpenBot 的 OpenCode,可在高级里关掉。
magpie-openbot-armor/
├── package.json # magpie.hooks + magpie.page + 默认 options
├── hooks.js # Magpie asynchronous gateway hook
├── armor.md # bundled 默认协议正文
├── armor.compact.md # bundled 精简协议(省 token)
├── lib/
│ ├── armor.mjs # 文件读取、~ 展开、mtime/size 缓存
│ ├── options.mjs # option 归一化、协议来源解析、过滤
│ └── openbot-armor.mjs # 纯请求改写逻辑
├── page/
│ ├── index.html # 配置页
│ ├── i18n.js # zh / en 文案
│ ├── app.js # 页面逻辑、串行保存、文件导入
│ ├── style.css
│ └── icon.svg
├── scripts/
│ └── check-openbot-anchors.mjs # 只读检测 OpenBot 更新后锚点是否还在
├── test/
│ ├── openbot-armor.test.mjs
│ └── hooks.test.mjs
└── README.md
打开 Magpie → Plugins → OpenBot 破甲(英文 OpenBot Armor)。
| 区域 | 内容 |
|---|---|
| 总开关 | 启用 / 关闭插件 |
| 模式 | off、observe、enforce |
| 协议来源 | bundled、compact、file、inline |
| 文件路径 | armor_file,相对路径按请求 ctx.cwd 解析 |
| 内联正文 | 文本框、文件导入、清空、切到 inline |
| 预览 | 只读预览内联正文;bundled/file 只显示来源说明 |
| 高级 | 各 strip 开关、targetAllOpenCode、origin/agent/model 过滤、hook 超时 |
| 保存 | 串行写入 magpie.options,遵守 1 次/秒限制 |
| 恢复默认 | 写回默认值并保存 |
| 配置 JSON | 只读备份、一键复制;点「编辑 JSON」可粘贴修改并校验应用;下方附完整字段说明表(字段 / 类型 / 默认值 / 说明) |
保存时 Magpie 会重启插件 host;下一次请求使用新配置。页面明确显示这一点。
package.json 里的默认 magpie.options:
{
"enabled": false,
"mode": "enforce",
"gatewayTimeoutMs": 3000,
"armor_source": "bundled",
"armor_file": "",
"armor_text": "",
"injectArmor": true,
"stripPersonaSections": true,
"stripTaskBullets": true,
"stripEnglishAntiJobs": true,
"stripEnglishSections": true,
"targetAllOpenCode": false,
"allowedOrigins": ["local", "magpie-key", "unknown"],
"agents": [],
"models": [],
"profileNames": []
}命令行等价用法(在插件目录执行时使用 PLUGIN_DIR="$(pwd)"):
PLUGIN_DIR="$(pwd)"
magpie plugin options "$PLUGIN_DIR" '{"enabled":true,"mode":"enforce"}'
magpie plugin options "$PLUGIN_DIR" '{"armor_source":"file","armor_file":"/path/to/armor.md"}'
magpie plugin options "$PLUGIN_DIR" '{"mode":"observe"}'
magpie plugin options "$PLUGIN_DIR" '{"enabled":false}'安装命令(PLUGIN_DIR 指向本仓库根目录):
PLUGIN_DIR="$(pwd)" # 如果在插件目录执行;也可以改成实际路径
# 1. 添加本地插件
magpie plugin add "$PLUGIN_DIR"
# 2. 查看插件实际短名
magpie plugin list
# 3. 启用
magpie plugin options "$PLUGIN_DIR" '{"enabled":true}'
# 或打开 Magpie → Plugins → OpenBot 破甲 → 打开开关 → 保存安装后建议先 observe,确认页面显示和 Magpie 日志无异常,再切 enforce。
已对照 OpenBot 0.60.0 / 0.61.0 的 prompt 结构验证:固定规则字符串(含 0.61 的 Report a safety check, such as a certificate or sandbox block; do not work around it.)与 <agent_profile>(数组元素按 \n 连接、tagSafe(JSON.stringify({id,name,title,description}, null, 2)))逐字节一致;锚点脚本在 0.61.0 的 app.asar 上全部命中。
cd "$PLUGIN_DIR"
npm test
npm run check
npm run check:anchors -- --helpnpm test 覆盖:
- 内置协议读取与缓存
- option 归一化与别名
- bundled / compact / file / inline 四种来源
- origin / agent / model 过滤
- OpenBot 固定规则改写
- 中英文 anti-job 清洗且保留质量规则
<agent_profile>人设保持- 四种协议的系统提示词注入与去重
applyToRequest不改 tools/model/stream- hook 的 disabled / enforce / observe / compact / file / fail-open 路径
check-openbot-anchors.mjs 是只读的:流式读 app.asar,检查这些锚点是否还在:
<openbot-developer-instructions>
You are a persistent local OpenBot teammate.
none changes other rules here for tools
<agent_profile>
description (1-2000 characters of standing instructions
官方 OpenBot 更新后跑一次:
# 路径由环境变量提供,不写死任何本地路径:
OPENBOT_APP_ASAR=/path/to/OpenBot.app/Contents/Resources/app.asar npm run check:anchors
# 或者:npm run check:anchors -- /path/to/OpenBot.app/Contents/Resources/app.asar如果 missing 非空,说明 OpenBot 改了 prompt 结构,需要更新 lib/openbot-armor.mjs 里的 EXACT_RULES / PROFILE_RE / OPENBOT_HINT。
只回滚插件,不需要动 OpenBot:
magpie plugin off "$PLUGIN_DIR"
magpie plugin rm "$PLUGIN_DIR"或在配置页把总开关关掉并保存。
如果只想临时停用:
magpie plugin options "$PLUGIN_DIR" '{"enabled":false}'插件只读取协议文件并修改内存中的请求;页面保存时写入 Magpie 自己的插件 options。它不修改:
- Magpie 的
plugins.json、plugins.yml、library.json(页面保存的 options 除外) ~/.config/opencode/- OpenBot 的
openbot.db、app.asar、Info.plist - 不执行全局
npm install、Homebrew、Xcode 安装 - 不启动后台进程、定时任务或登录项
- 不写运行时状态文件
运行时 hook 只读协议文件、只改内存里的请求体;页面保存属于 Magpie 正常的 options 写入。测试使用系统临时目录并在结束后清理。
- Magpie 对插件页的 options POST 整体上限 64KB(
http.MaxBytesReader),页面因此把内联协议正文限制在 40KB;更大的正文请用file来源。 - 页面不显示每请求运行状态:Magpie 的
GatewayBegin每个请求调用checkList(),plugins.json的 mtime/size 变化会被判定为“另一个 magpie 改了插件”并触发插件 host 重启,因此 hook 不写plugins.json;需要排查看 Magpie 插件统计/日志。 - 只覆盖经过 Magpie 网关的请求;OpenBot 若改成 Claude/Codex 直连,插件看不到。
- 不改变模型权重或 WorkBuddy / DeepSeek 服务端策略;prompt 层不等于模型层。
- 不改 OpenBot 数据库里存储的
description;破甲发生在请求发出前。 - 不处理工作区
AGENTS.md里未来可能出现的任意自然语言 anti-jobs;运行时协议会把这类条款声明作废,但不会逐文件清洗。 targetAllOpenCode: true会影响所有经过 Magpie 的 OpenCode 请求,不只是 OpenBot;默认关闭。
对照 usemagpie.ai/docs/zh/plugins(2026-11 版)逐条核验:
| 官方要求 | 本插件 | 结果 |
|---|---|---|
网关钩子声明 "magpie": { "hooks": "./hooks.js" } |
package.json 同款 | ✅ |
导出具名异步 onRequest(body, ctx) |
export async function onRequest |
✅ |
| 纯网关插件不需要 main/exports | 无 main/exports/index.*,宿主只按 hooks 入口加载 | ✅ |
| 返回对象替换请求,undefined 保留原请求 | 只在 result.changed 时返回 body,否则 undefined |
✅ |
| 改 model/stream/工具/工具选择会被回滚 | 只改 messages/system/instructions/contents/systemInstruction,测试锁定 | ✅ |
| 每个钩子收到独立副本 | hook 不依赖 body 引用语义,observe 模式显式 structuredClone | ✅ |
ctx.report(code, bytes) 只接受 ready/empty/unbound/unauthorized/unavailable/invalid/skipped |
只用 ready/invalid/skipped | ✅ |
gatewayTimeoutMs 100–10000 |
默认 3000,页面与 normalize 都按此夹紧 | ✅ |
| 选项变更触发宿主重载;下一次请求生效 | SetPageOptions→SetOptions→Restart(page.go 源码确认),页面明示 | ✅ |
页面脚本必须是文件,不能内联;connect-src 'none' |
全部 <script src>,无内联脚本/内联事件;无 fetch |
✅ |
magpie.options.set 每秒最多一次,返回保存后的选项 |
串行 flush,间隔 1100ms,采用返回值 | ✅ |
page.titles 按 zh/zh-TW/ja/de |
齐全 | ✅ |
页面文件必须在 page.file 所在文件夹内 |
page/* 与 page/icon.svg 均在 | ✅ |
| 文件夹插件就地加载,armor.md 随包根解析 | lib/armor.mjs 用 import.meta.url 定位包根 armor.md |
✅ |
测试应在沙箱 HOME 里做(官方 mktemp -d 模式) |
单测 47 项通过;沙箱命令见下 | ✅ |
文档的中文页 ctx 字段表未列 origin(英文源码版有):本插件把缺失 origin 视为 unknown 且默认放行,两种宿主行为下都能正常工作。
沙箱端到端验证(官方推荐方式):
PLUGIN_DIR="$(pwd)"
sb=$(mktemp -d)
m() { env HOME=$sb XDG_CONFIG_HOME=$sb/.config XDG_CACHE_HOME=$sb/.cache MAGPIE_ADDR=127.0.0.1:3499 magpie "$@"; }
m plugin add "$PLUGIN_DIR" </dev/null
m plugin --json
m plugin options "$PLUGIN_DIR" '{"enabled":true,"mode":"observe"}'
# 之后 rm -rf $sb 清理;全程不触碰你真实的 ~/.config/magpie