Repository navigation
Description
现象
OpenCode 2.2.6 起,本插件的自动注入在模型侧完全失效,但链路前半段一切正常,也没有任何报错。
实测(opencode 2.2.7 + @openviking/opencode-plugin 2026.11.10,本地 OpenViking 服务正常):
| 环节 | 结果 |
|---|---|
prompt hook 查询 OpenViking |
正常,日志有 Injected OpenViking session context、recall_context_assembled |
注入块写入 event.metadata |
正常,且宿主把 metadata 持久化到了 session 消息里 |
context hook 读 message.metadata.openviking |
恒为 undefined,静默跳过 |
模型是否看到 <openviking-context> |
看不到(新会话直接问模型,回答 CANARY=no) |
影响:session-start 的 profile 注入和每轮的 auto-recall 全部失效,模型不再"默认访问"OpenViking,只能靠自己想起来调 MCP 工具。对下游用户表现为"记忆突然不管用了",且没有任何提示。
根因
上游 anomalyco/opencode#61018("refactor(ai): remove unused metadata fields",2026-11-02 合并,2.2.6 首次发版)删掉了 @opencode/ai 里 Message.metadata,并在 packages/core/src/session/runner/to-llm-message.ts 里不再把 session metadata 带到 hook 消息上:
case "user":
return [Message.make({
id: message.id,
role: "user",
content,
- metadata: {
- ...message.metadata,
- ...(message.agents?.length ? { agents: message.agents } : {}),
- },
})]lib/v2-plugin.mjs 的 injectV2Context 恰好依赖这一字段:
const blocks = message?.metadata?.[METADATA_KEY]?.context
if (message?.role !== "user" || !Array.isArray(blocks) || blocks.length === 0) continue所以 prepareV2Prompt 写进去的 event.metadata[openviking] 永远读不回来。
注意这不是"数据丢了":metadata 仍然存在于存储层,ctx.session.context() 返回的存储消息里 metadata.openviking.context 完好。只是 hook 收到的消息不带它。
建议修复
不建议只把 injectV2Context 改成 async 完事,需要两条路配合(本地验证过的改法):
prepareV2Prompt写event.metadata的同时,按messageID在进程内存里留一份(有界 Map)。contexthook 优先查内存;未命中(插件重启、resume、fork)时,每个 session 最多一次调用ctx.session.context({ sessionID }),把存储消息里的metadata.openviking.context灌进同一张 Map,再注入。
这样每轮不额外请求,注入文本也可重复(prompt cache 稳定)。
示意:
export async function injectV2Context(event, repoContext, { getContext } = {}) {
const repoPrompt = repoContext.getRepoSystemPrompt()
if (repoPrompt) pushSystem(event, repoPrompt)
if (!Array.isArray(event?.messages)) return
const pending = event.messages.filter((m) =>
m?.role === "user" && typeof m?.id === "string" && !hasInjectedContext(m))
if (pending.length === 0) return
if (pending.some((m) => !promptContexts.has(m.id))) await hydratePromptContexts(event, getContext)
for (const message of pending) {
const blocks = promptContexts.get(message.id) ?? message?.metadata?.[METADATA_KEY]?.context
if (!Array.isArray(blocks) || blocks.length === 0) continue
message.content.unshift({ type: "text", text: blocks.filter(Boolean).join("\n\n"),
metadata: { [METADATA_KEY]: true } })
}
}同时建议保留对 message.metadata 的读取作为第一条分支,兼容 2.2.6 之前的 OpenCode。
验证
本地打了上述补丁后,用两个 canary 会话实测:
- 新会话问"你的上下文里是否有一个以
<openviking-context开头的块",模型回答CANARY=yes并引出viking://user/default/memories/profile.md; - 第二轮问是否能额外看到召回块,回答
RECALL=yes并引出一条记忆 URI; - 插件重载后(内存清空)再次对话,日志出现
Hydrated prompt contexts from stored session { messages: 9, injectable: 3 },说明回读路径可用。
node --test tests/*.test.mjs 57 项全过,新增了一项覆盖"hook 消息无 metadata"的场景。
关联
上游 issue(英文,已附最小复现插件):anomalyco/opencode#63252
顺带一提,本仓库 #6736 反映的"OpenCode 2.x 双实例加载"在本地也一直存在(每次初始化会打印两条 Registered OpenViking MCP server,dispose 时对全部会话做一次 flushAll),与本 issue 无关,但会放大 metadata/state 的时序问题。
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
r266-tech commentedon Nov 11, 2026
There is a useful overlap with the existing v2 latency PR #5496, but its current implementation does not cover the stored-message fallback described here.
I exercised the registered production
prompt/contexthooks with metadata-free{ id, role, content }messages, comparing current main9879a78d01e7with #5496 atce66cadcb947:session.context()can return its storedmetadata.openviking.contextThe last case makes zero
session.context()calls on both heads. #5496's legacy branch still readsmessage.metadata.openviking.contextdirectly, and its existing legacy regression supplies that metadata on the hook message. That fixture passes, but it does not exercise the post-2.2.6 message shape.So the stored-message hydration fallback remains necessary even if #5496's new-message/ledger path is adopted. In particular, a metadata-free old message with the blocks available only through
ctx.session.context({ sessionID })would make a useful migration/resume regression.The existing focused tests pass: main
tests/v2-plugin.test.mjs9/9; #5496's v2 plugin + recall files 16/16. AI-only local Node hook verification on macOS / Node 26.13.0, with deterministic recall and host-storage fixtures; no live OpenCode UI, provider request, or model call.AtaraxiaZ commentedon Nov 13, 2026
OpenCode那边修复了这个问题,是他们的锅,这条issue就不需要了,anomalyco/opencode#63252