Files
OpenViking/examples/opencode-plugin
t0saki a83b81715b feat(uri)!: remove uid-less current-user shorthand in favor of viking://~ (#4196)
* feat(uri)!: reject uid-less current-user shorthand in favor of viking://~

viking://user/<segment> (memories/resources/skills/peers/privacy/sessions
without a user id) was ambiguous with a user literally named after the
segment, and a user actually named e.g. "memories" was unreachable for
USER/ADMIN callers. Now that the viking://~ home alias (#4167) covers the
same need unambiguously, the shorthand fails closed at the request
boundary instead of expanding:

- resolve_current_user_uri raises NamespaceShapeError with a corrective
  hint naming both viking://~/<rest> and the explicit-uid form. Silently
  parsing the reserved segment as a peer user id would misdirect reads
  and writes, so rejection is the only safe removal.
- Bare viking://user falls through to the canonical parser and keeps
  container semantics (a user key listing it sees only its own space).
- The self-id escape stays: a caller whose user_id equals a reserved
  name keeps viking://user/<own-id> as their canonical root. ROOT-role
  literal parsing and the legacy viking://session alias are unchanged.
- AddTargetsConfig normalizes stored legacy config spellings
  (viking://user/resources|skills) to the viking://~ form at validation
  so existing ov.conf/user_config deployments keep working; the accepted
  per-user spelling is now viking://~/resources and viking://~/skills.
- usage_reporter keeps canonicalizing the historical shorthand found in
  old transcripts and additionally recognizes viking://~/memories/.

BREAKING CHANGE: requests using the uid-less viking://user/<segment>
spelling now fail with 400; use viking://~/<segment> or an explicit
viking://user/{user_id}/<segment> URI.

* refactor(clients): migrate first-party emitters to the viking://~ home alias

Every in-repo client that emitted the removed uid-less current-user
shorthand now sends viking://~/... instead: vikingbot fallbacks and
default sentinels, the LangChain store/tools defaults, the shared
recall-core.mjs (all synced plugin copies), the codex/claude-code/
openclaw/openwebui/dsh/zcode/pi plugin emitters, quick-app examples,
Go SDK example, tau2 benchmark targets, and the eval golden dataset.

Compat kept where legacy strings live in stored user configs: bot and
ov_dream sentinels accept both spellings while emitting only ~, and
recall-core still rewrites legacy viking://user/<reserved> config values
client-side. langchain_openviking._uri now classifies viking://~ with
the explicit-user shape so canonicalized server responses keep matching
a ~ root. Plugin READMEs note the server requirement for the alias.

* docs: replace current-user shorthand guidance with the viking://~ home alias

Rewrite every EN/ZH doc and model-facing prompt that advertised the
uid-less viking://user/<segment> spelling: URI concept catalogue,
context-types/storage/extraction/retrieval/session/privacy concepts,
configuration guide (with the legacy add_targets auto-normalization
note), resources/skills/sessions/retrieval/admin API references, FAQ,
capability reference, and the openviking-memory / ov-experience-memory /
openclaw / ov-resources skills. The stale MCP viking://user/<path>
dialect passage in the MCP guide is replaced by ~ guidance, and bare
viking://user is documented as the container of user spaces.

* test(api): migrate live API session-used tests off the removed shorthand

tests/api_test/sessions sent uid-less viking://user/skills/... URIs to
record_used, which the request boundary now rejects with 400 (caught by
the API & CLI Integration Tests CI job; these tests need a live server
and are not part of the local suites). The api_test client authenticates
as an admin-role user key, so the viking://~ home alias expands for it.
tests/api_test/common/test_edge_cases.py is left as is: it asserts a 400
for a non-resource add target, which still holds.
2026-08-21 19:00:19 +08:00
..

OpenViking OpenCode Plugin

A unified OpenCode plugin for OpenViking repository retrieval and long-term memory.

Requires an OpenViking server with viking://~ home-alias support. Recall targets the caller's own context space through viking://~/memories and viking://~/skills; the uid-less viking://user/memories shorthand is rejected by newer servers.

This is the only OpenCode plugin example maintained in this repository. It supersedes the former split examples for indexed repository prompt injection and long-term memory.

The plugin uses OpenCode hooks for lifecycle behavior and registers OpenViking's standard stdio MCP proxy for model tools. It does not install or require an OpenCode skill, and agents do not need to run ov shell commands.

What It Does

  • Injects indexed viking://resources/ repositories into the system prompt.
  • Exposes the same OpenViking MCP tools used by the Claude Code and Codex memory plugins.
  • Maps each OpenCode session to an OpenViking session.
  • Captures user and assistant text messages into OpenViking.
  • Commits sessions at lifecycle boundaries for memory extraction.
  • Automatically recalls relevant memories and injects them as hidden synthetic context for the current user message.
  • Blocks accidental local filesystem reads of viking:// URIs and points the agent back to openviking_read, openviking_glob, or openviking_search.

Files

examples/opencode-plugin/
├── index.mjs
├── package.json
├── README.md
├── INSTALL-ZH.md
├── lib/
│   ├── config.mjs
│   ├── mcp-config.mjs
│   ├── runtime.mjs
│   ├── repo-context.mjs
│   ├── memory-session.mjs
│   ├── memory-recall.mjs
│   ├── session-inject.mjs
│   ├── viking-uri-guard.mjs
│   └── utils.mjs
├── servers/
│   └── mcp-proxy.mjs
├── tests/
└── wrappers/
    └── openviking.js

There is intentionally no skills/openviking/SKILL.md. The tool surface comes from OpenViking's MCP endpoint.

Requirements

  • OpenCode
  • OpenViking HTTP server
  • Node.js 18+
  • An OpenViking API key if your server requires authentication

Start OpenViking first:

openviking-server --config ~/.openviking/ov.conf

Installation

Published Package

Normal users should enable it through OpenCode's package plugin mechanism:

The published npm package is @openviking/opencode-plugin; verify availability with:

npm view @openviking/opencode-plugin version
{
  "plugin": ["@openviking/opencode-plugin"]
}

Source Install

For development or PR testing, copy the package into OpenCode's plugin directory with a top-level wrapper:

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/

This creates a stable OpenCode plugin layout:

~/.config/opencode/plugins/
├── openviking.js
└── openviking/
    ├── index.mjs
    ├── package.json
    ├── lib/
    └── servers/

The top-level openviking.js is only a wrapper:

export { OpenVikingPlugin, default } from "./openviking/index.mjs"

This wrapper is only for source installs with the directory layout shown above. npm package installs load index.mjs directly through package.json. Use the .js wrapper for source installs; OpenCode's local plugin scanner discovers JavaScript/TypeScript plugin files.

Configuration

Create ~/.config/opencode/openviking-config.json:

{
  "enabled": true,
  "mcp": { "enabled": true },
  "timeoutMs": 30000,
  "repoContext": { "enabled": true, "cacheTtlMs": 60000 },
  "autoRecall": {
    "enabled": true,
    "limit": 6,
    "scoreThreshold": 0.35,
    "maxContentChars": 500,
    "preferAbstract": true,
    "tokenBudget": 2000,
    "minQueryLength": 3
  },
  "commitTokenThreshold": 20000,
  "commitKeepRecentCount": 10,
  "profileTokenBudget": 10000,
  "resumeContextBudget": 32000
}

autoRecall.limit is a legacy quota-scaling input, not a final result cap. Explicit values from 1 through 5 produce an effective total quota of 6 because each coding category keeps one retrieval slot. Use Context quotas directly when exact category ceilings are required.

API keys are resolved from environment variables or ~/.openviking/ovcli.conf and sent as Authorization: Bearer ... by both hooks and the MCP proxy. Recall goes through the server-side context face (POST /api/v1/search/search with mode="context"), falling back to the deprecated /api/v1/search/recall on older deployments. account and user are trusted-mode identity headers sent as X-OpenViking-Account and X-OpenViking-User; leave them empty when using API-key mode with user/admin API keys. By default the plugin derives a peer from the project directory using Claude's project-directory naming rule: every non-letter-or-digit character becomes -, with no path normalization. For example, /Users/x/Dev/OpenViking becomes -Users-x-Dev-OpenViking. Data-plane memory/resource requests send the effective peer as X-OpenViking-Actor-Peer; captured session messages store it as body peer_id. Configure peerId or OPENVIKING_PEER_ID to override the workspace-derived peer, or set workspacePeer=false / OPENVIKING_WORKSPACE_PEER=0 to turn workspace-derived peers off.

Recall defaults to the broad mode: global memory, the current workspace, and other workspace memories can all be recalled, with other workspaces penalized and rendered later. Set recallPeerScope="actor" or OPENVIKING_RECALL_PEER_SCOPE=actor for the isolation mode, which only sees global memory plus the current workspace. In deployments where one bot serves multiple real people, such as zouk, vikingbot, or AstrBot, use the isolation mode with an explicit actor peer so one person's memories are not recalled into another person's session.

OPENVIKING_API_KEY, OPENVIKING_ACCOUNT, OPENVIKING_USER, and OPENVIKING_PEER_ID take precedence over values in this file.

For advanced setups, OPENVIKING_PLUGIN_CONFIG can point to another config file path.

Hook-only mode

When OpenViking is already exposed through another MCP server, retain the lifecycle hooks while skipping this plugin's bundled MCP registration:

{
  "mcp": { "enabled": false }
}

This leaves repository context, automatic recall, message capture, and lifecycle commits enabled. It does not add or overwrite OpenCode's mcp.openviking entry.

OpenCode's local read, glob, and grep tools cannot read viking:// URIs. When the agent accidentally tries that, the plugin blocks the filesystem tool call and points it to the OpenViking MCP tools.

MCP Tools

OpenCode sees the OpenViking MCP server as openviking, so tool names are namespaced with openviking_.

  • openviking_search: deep semantic retrieval across memories, resources, and skills; use mode="context" for balanced, injection-ready context.
  • openviking_find: fast semantic retrieval.
  • openviking_remember: store important facts or decisions for memory extraction.
  • openviking_read: read one or more viking:// files.
  • openviking_list: list a viking:// directory.
  • openviking_tree: show a viking:// directory tree.
  • openviking_grep: exact text or regex search.
  • openviking_glob: glob file matching.
  • openviking_write: create, overwrite, or append to a viking:// file.
  • openviking_edit: exact string replacement in a viking:// file.
  • openviking_add_resource: add a URL, local file, sitemap, or feed.
  • openviking_forget: delete a viking:// URI after explicit user confirmation.
  • openviking_list_watches / openviking_cancel_watch: inspect or cancel resource watches.
  • openviking_health: check OpenViking server health.

The proxy forwards the server's real tools/list response; the plugin does not maintain a separate native tool list.

Runtime Files

The plugin writes runtime files to ~/.config/opencode/openviking/ by default:

  • openviking-memory.log
  • openviking-session-state.json

Set runtime.dataDir in config to override this directory.