* fix(memory-plugin): split the viking:// URI guard into deny and notice A shell command that carries a viking:// URI is not necessarily trying to open it: ov CLI arguments, HTTP payloads and grep patterns all mention one. The guard used to deny every such command, and models learned to split the URI to get past it. evaluateUriGuard now denies only file tools whose path is a viking:// URI. evaluateUriNotice returns a notice for shell tools instead, naming the plugin, the replacement tool and telling the model to ignore it when the URI is intentional. preToolUseOutput wraps both for the PreToolUse hosts. * fix(memory-plugin): stop treating grep's pattern as a path pattern was one of the path keys, so Grep(pattern="viking://", path="/repo") was denied on every host that guards grep, although it only searches local files for the text. It stays a location for glob, which the generic sweep still reaches. * fix(dsh): run shell commands that carry viking:// and attach a notice bash is no longer denied by tools/pre-execute. A tools/post-execute listener delegates to the rest of the chain first, then appends a plugin context with form "notice" when the command carried a viking:// URI, so a later listener's block or content replacement survives. pluginMessage moves to capture.mjs and takes the whole source, since a notice needs a summary as well as a form. * fix(pi): notice on tool_result instead of blocking bash tool_call now denies only read/grep/find/ls on a viking:// path. A bash command that carries a URI runs, and tool_result appends the notice after the result's own content blocks. The shared plugin-config test reads pi's version from its package.json, like the other harnesses, instead of a literal. * fix(agent-hook-plugin): trae notices shell commands, cursor stops guarding the shell TRAE and ZCode use the shared preToolUseOutput, so Bash and RunCommand on TRAE get additionalContext instead of a deny. ZCode's matcher still names no shell tool, because its strict output schema is not verified to accept that envelope. Cursor has no channel that shows the model a note after a shell command, so beforeShellExecution is dropped. The installer prunes an entry an older install left behind, and the guard ignores a shell event that still arrives. * feat(opencode): notice on tool.execute.after bash had no guard on opencode. A command that carries a viking:// URI now gets the notice appended to its output; read/glob/grep keep their deny in tool.execute.before. * feat(claude-code): guard Edit/Write and notice on Bash The PreToolUse matcher grows from Read|Glob|Grep to Read|Glob|Grep|Edit|Write|Bash. Edit and Write on a viking:// path are denied like the read tools, and a Bash command that carries one gets additionalContext. The script is now just preToolUseOutput, so the shared library drops the guarded option that only this script used. * feat(codex): add the PreToolUse URI guard Codex gets the same uri-guard script as claude-code on a Bash matcher. Its Edit and Write matchers are aliases for apply_patch, whose input is a patch body with no path to deny, so the hook only ever adds a notice. The doctor expects the sixth hook trust record, and users approve it once in /hooks. * docs: capability reference rows for the deny/notice guard
9.9 KiB
安装 OpenViking OpenCode 统一插件
这个插件新增了一个面向 OpenCode 的统一 OpenViking 插件:
- 面向 memory、resources 和 code context 的 OpenViking MCP 工具
- 长期记忆、session 同步、生命周期边界 commit、自动 recall
这是仓库中唯一继续维护的 OpenCode 插件示例。这个插件不再安装 skills/openviking/SKILL.md,也不要求 agent 使用 ov 命令。模型工具由 Claude Code 和 Codex 记忆插件同款的 stdio MCP proxy 提供。
前置条件
需要先准备:
- OpenCode
- OpenViking HTTP Server
- Node.js 18+
- 如果服务端启用了认证,需要可用的 OpenViking API Key
建议先启动 OpenViking:
openviking-server --config ~/.openviking/ov.conf
检查服务:
curl http://localhost:1933/health
安装方式一:发布包安装
普通用户推荐通过 OpenCode 的 package plugin 机制启用:
{
"plugin": ["@openviking/opencode-plugin"]
}
安装方式二:源码安装
用于开发调试或 PR 测试。OpenCode 推荐插件目录:
~/.config/opencode/plugins
在仓库根目录执行:
node examples/memory-plugin-shared/sync.mjs
mkdir -p ~/.config/opencode/plugins/openviking
cp examples/opencode-plugin/wrappers/openviking.js ~/.config/opencode/plugins/openviking.js
cp examples/opencode-plugin/index.mjs examples/opencode-plugin/package.json ~/.config/opencode/plugins/openviking/
cp -r examples/opencode-plugin/lib ~/.config/opencode/plugins/openviking/
cp -r examples/opencode-plugin/servers ~/.config/opencode/plugins/openviking/
sync.mjs 会生成 lib/shared/,插件和 MCP 代理都从这里 import 共享模块。这个目录不在 git 里,所以复制前要先运行;之后每次 git pull 也要重新运行再复制。
安装后结构应类似:
~/.config/opencode/plugins/
├── openviking.js
└── openviking/
├── index.mjs
├── package.json
├── lib/
└── servers/
顶层 openviking.js 只负责把 OpenCode 能发现的一级 .js 入口转发到插件目录:
export { OpenVikingPlugin, default } from "./openviking/index.mjs"
这个 wrapper 只用于上面这种源码安装目录结构。npm 包安装会通过 package.json 直接加载 index.mjs。
源码安装请使用 .js wrapper;OpenCode 的本地插件扫描器会发现 JavaScript/TypeScript 插件文件。
如果你使用 npm 包方式安装,也可以将 examples/opencode-plugin 作为一个普通 OpenCode 插件包使用。
配置
行为旋钮写在共享的客户端配置文件里:
~/.openviking/ovcli.conf
示例配置:
{
"url": "http://127.0.0.1:1933",
"api_key": "your-api-key-here",
"plugin": {
"recallLimit": 6,
"opencode": {
"enabled": true,
"mcpEnabled": true,
"timeoutMs": 30000,
"repoContext": true,
"repoContextCacheTtlMs": 60000,
"autoRecall": true,
"scoreThreshold": 0.35,
"recallMaxContentChars": 500,
"recallPreferAbstract": true,
"recallTokenBudget": 2000,
"minQueryLength": 3,
"commitTokenThreshold": 20000,
"commitKeepRecentCount": 10,
"profileTokenBudget": 10000,
"resumeContextBudget": 32000
}
}
}
plugin 里的键对所有 harness 生效;plugin.opencode 里的键只对本插件生效,并覆盖前者。解析顺序是 OPENVIKING_* 环境变量 → 工作区的 .openviking/config.json、.openviking/config.local.json 和本机 registry 条目 → plugin.opencode → plugin → 内置默认值。每个旋钮的类型、默认值、取值范围、环境变量名和兼容的旧拼写都声明在 examples/memory-plugin-shared/lib/config-schema.mjs。
recallLimit 是遗留的配额缩放输入,不是最终结果上限。显式设置为
1 到 5 时,有效总配额仍为 6,因为六个 coding 分类会各保留一个检索槽位。
推荐通过环境变量提供 API Key,而不是写入配置文件:
export OPENVIKING_API_KEY="your-api-key-here"
API key 会从环境变量或 ~/.openviking/ovcli.conf 读取,并由 hooks 和 MCP proxy 作为 Authorization: Bearer ... 发送。account 和 user 是 trusted mode
身份头,会作为 X-OpenViking-Account、X-OpenViking-User 发送;api_key
模式的服务端从 key 里取身份,插件在那里不发这两个头。
peerId 会作为 X-OpenViking-Actor-Peer 用于数据面的 memory/resource 请求;捕获 session message 时仍写入 body peer_id。需要 peer 维度路由时请显式配置。
OPENVIKING_API_KEY、OPENVIKING_ACCOUNT、OPENVIKING_USER、
OPENVIKING_PEER_ID
优先级高于 ovcli.conf 里的同名配置。
高级场景可以用 OPENVIKING_CLI_CONFIG_FILE 指向其他路径的 ovcli.conf。
仅 Hooks 模式
如果其他 MCP server 已经提供 OpenViking,可以关闭本插件附带的 MCP 注册,同时保留生命周期 hooks:
{
"plugin": {
"opencode": { "mcpEnabled": false }
}
}
repository context、自动 recall、消息 capture 和生命周期 commit 会继续工作,也不会添加或覆盖
OpenCode 的 mcp.openviking 配置。
验证
修改插件或 OpenViking 配置后,需要重启 OpenCode。
进入新的 OpenCode session 后,可以让 agent 浏览 OpenViking memory,或搜索一个已索引的资源。插件应暴露 OpenViking MCP server,OpenCode 中的工具名会带 openviking_ 前缀:
openviking_search、openviking_findopenviking_read、openviking_list、openviking_tree、openviking_grep、openviking_globopenviking_remember、openviking_write、openviking_edit、openviking_add_resourceopenviking_list_watches、openviking_cancel_watch、openviking_forget、openviking_healthopenviking_list_watches、openviking_cancel_watch
如果行为异常,先查看运行时文件:
ls ~/.config/opencode/openviking/
tail -n 100 ~/.config/opencode/openviking/openviking-memory.log
如果使用本地 server,也确认 OpenViking 可访问:
curl http://localhost:1933/health
可用 MCP 工具
插件会通过 OpenCode config 注册 OpenViking stdio MCP proxy。服务端实际返回的 tools/list 是最终工具清单;当前 OpenViking server 暴露:
openviking_search:跨 memories/resources/skills 的深度语义检索;使用mode="context"获取面向当前任务、可直接注入的平衡上下文openviking_find:快速语义检索openviking_remember:存储重要事实或决策,供记忆提取openviking_read:读取一个或多个viking://文件openviking_list:列出viking://目录openviking_tree:展示viking://目录树openviking_grep:精确文本或正则搜索openviking_glob:glob 文件匹配openviking_write:创建、覆盖或追加viking://文件openviking_edit:对viking://文件做精确字符串替换openviking_add_resource:添加 URL、本地文件、sitemap 或 feedopenviking_forget:在用户明确确认后删除viking://URIopenviking_list_watches/openviking_cancel_watch:查看或取消资源 watchopenviking_health:检查 OpenViking server 健康状态
使用建议:
- 概念性问题用
openviking_search - 精确符号、函数名、类名、报错字符串用
openviking_grep - 枚举文件用
openviking_glob - 读取内容用
openviking_read - 探索目录结构用
openviking_list - 删除前必须先获得用户明确确认,再调用
openviking_forget - 如果 agent 误用 OpenCode 本地
read、glob、grep工具访问viking://URI,插件会阻止这次本地文件系统调用,并提示改用 MCP 工具。 bash命令里带viking://URI 时照常执行,插件会在输出末尾附一段提示,建议改用 MCP 工具;URI 本来就是命令参数时,agent 可以忽略这段提示。
openviking_add_resource 本地文件
openviking_add_resource 支持三类输入:
- 远端
http(s)URL:直接调用/api/v1/resources - 本地文件路径:先调用
/api/v1/resources/temp_upload,再用返回的temp_file_id添加资源 file://URL:按本地文件处理
相对路径会按 OpenCode 当前项目目录解析。示例:
openviking_add_resource(path="https://example.com/spec.md", to="viking://resources/spec")
openviking_add_resource(path="./docs/notes.md", to="viking://resources/notes.md")
openviking_add_resource(path="file:///home/alice/project/notes.md", description="project notes")
当前仍不支持本地目录自动打 zip 上传;传入目录时会返回明确错误。
运行时文件
插件默认会把运行时文件写入:
~/.config/opencode/openviking/
可能包含:
openviking-memory.logopenviking-session-state.json
可以通过 plugin.opencode 里的 dataDir 修改这个目录。
这些是本地运行时文件,不建议提交到版本库。
故障排查
| 问题 | 排查方向 |
|---|---|
| 插件没有加载 | package 安装检查 ~/.config/opencode/opencode.json 是否包含 @openviking/opencode-plugin;源码安装检查 ~/.config/opencode/plugins/openviking.js 是否存在 |
加载时报找不到 lib/shared/*.mjs |
源码复制前没有运行 sync.mjs。在仓库根目录运行 node examples/memory-plugin-shared/sync.mjs 后重新复制 lib/ |
| MCP tools 连到了错误的 server | 检查 ~/.openviking/ovcli.conf,或用 OPENVIKING_* 环境变量 / OPENVIKING_CLI_CONFIG_FILE 指向正确配置 |
| OpenViking 返回 401 / 403 | 检查 OPENVIKING_API_KEY;trusted-mode 部署还要检查 OPENVIKING_ACCOUNT 和 OPENVIKING_USER |
| recall 为空 | 确认 OpenViking 中已有 memories/resources,并且 autoRecall 为 true |
本地 openviking_add_resource 失败 |
传入文件路径而不是目录;目前还不支持自动上传本地目录 |