* 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
4.3 KiB
OpenViking Memory for the config-driven hook hosts
Cursor, TRAE, TRAE CN and ZCode all install the same way: the shared installer writes lifecycle hooks and an MCP server entry into the host's own configuration files, and assembles the OpenViking runtime beside the integration. None of them has a marketplace listing to register or a separate MCP setup to do.
bash examples/memory-plugin-shared/install.sh --harness cursor
bash examples/memory-plugin-shared/install.sh --harness trae,trae-cn
bash examples/memory-plugin-shared/install.sh --harness zcode
Requires an OpenViking server with
viking://~home-alias support. Recall targets the caller's own context space throughviking://~/memoriesandviking://~/skills; the uid-lessviking://user/memoriesshorthand is rejected by newer servers.
What the hooks do
- Session start — injects the user profile and preferences into context, and replays anything an offline session queued.
- Prompt submit — searches OpenViking for memories relevant to the prompt and injects them, deduplicated by event id and a 500ms window.
- Tool use — denies local file tools a
viking://virtual path and points the agent back at the OpenViking MCP tools. On TRAE a shell command that carries aviking://URI still runs, with a notice pointing at the same tools. - Stop — captures the finished turn and commits the OpenViking session. Cursor also runs this before a compaction and at session end; ZCode answers first and finishes the writes in a detached worker.
Layout
scripts/hook.mjs is the single entry every hook command runs. It owns the state machine all four clients share — the debounce, the prompt dedup, the recall cache, the cross-process lock — and asks the adapter under hosts/ for the four things that differ: the event vocabulary, the response envelope, how a prompt is read out of the payload, and how a finished turn is captured. scripts/uri-guard.mjs and servers/mcp-proxy.mjs are likewise one file each, with the host chosen from the client id the installer passes.
The root plugin.json is host-neutral package metadata used for version checks and diagnostics. It is not a Claude Code, Cursor, TRAE, or ZCode native plugin manifest.
hosts/<host>/ holds only what a host reads as configuration — hooks.json, .mcp.json, openviking.integration.json, plus Cursor's rule and skill. Everything executable stays one level up, because ../../memory-plugin-shared/lib is the path that resolves both in this repository and in an installed ~/.openviking/agent-integrations/<client>/.
The memory logic itself is not here: recall, batching, the pending queue, credential resolution and the MCP proxy all come from examples/memory-plugin-shared/lib, which the installer copies to ~/.openviking/agent-integrations/memory-plugin-shared/lib.
Host notes
- Cursor — six events, including the
preCompactandsessionEndno other host in this plugin has. Commits on Stop oncecapturedSinceCommitreaches the threshold, and unconditionally before a compaction. Sessions arecu-. See the Cursor guide. - TRAE / TRAE CN — capture reads
prompt,text_contentandlast_assistant_messageoff the Stop event rather than parsing a transcript. Every Stop that carries content commits. Sessions aretr-andtrcn-. See the TRAE guide. - ZCode — the rollout file is the authoritative incremental transcript: stable host
turnIdvalues drive deduplication and let a later Stop recover missed turns, and hook stdin is only the fallback. ZCode supports neitherPreCompactnorSessionEnd, so committing on every Stop stands in for both. Its output schema is strict, so a pass-through writes nothing at all. Sessions arezc-. DESIGN.md records the verified extension surface.
Diagnostics
node ~/.openviking/agent-integrations/<client>/scripts/ov-memory-doctor.mjs --offline
The client defaults to the one this copy was installed for; pass cursor, trae, trae-cn or zcode as an argument to override it, drop --offline to probe the server as well, and add --json for a machine-readable report.
Tests
node --test examples/agent-hook-plugin/tests/*.test.mjs