@openviking/opencode-plugin 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -135,9 +135,23 @@ Create `~/.config/opencode/openviking-config.json`:
135
135
  API keys are resolved from environment variables or `~/.openviking/ovcli.conf` and sent as `Authorization: Bearer ...` by both hooks and the MCP proxy. `account` and `user` are trusted-mode identity
136
136
  headers sent as `X-OpenViking-Account` and `X-OpenViking-User`; leave them empty
137
137
  when using API-key mode with user/admin API keys.
138
- `peerId` is sent as `X-OpenViking-Actor-Peer` on data-plane memory/resource
139
- requests; captured session messages store it as body `peer_id`. Configure
140
- `peerId` explicitly when peer-scoped memory routing is needed.
138
+ By default the plugin derives a peer from the project directory using Claude's
139
+ project-directory naming rule: every non-letter-or-digit character becomes `-`,
140
+ with no path normalization. For example, `/Users/x/Dev/OpenViking` becomes
141
+ `-Users-x-Dev-OpenViking`. Data-plane memory/resource requests send the
142
+ effective peer as `X-OpenViking-Actor-Peer`; captured session messages store it
143
+ as body `peer_id`. Configure `peerId` or `OPENVIKING_PEER_ID` to override the
144
+ workspace-derived peer, or set `workspacePeer=false` /
145
+ `OPENVIKING_WORKSPACE_PEER=0` to turn workspace-derived peers off.
146
+
147
+ Recall defaults to the broad mode: global memory, the current workspace, and
148
+ other workspace memories can all be recalled, with other workspaces penalized
149
+ and rendered later. Set `recallPeerScope="actor"` or
150
+ `OPENVIKING_RECALL_PEER_SCOPE=actor` for the isolation mode, which only sees
151
+ global memory plus the current workspace. In deployments where one bot serves
152
+ multiple real people, such as zouk, vikingbot, or AstrBot, use the isolation mode
153
+ with an explicit actor peer so one person's memories are not recalled into
154
+ another person's session.
141
155
 
142
156
  `OPENVIKING_API_KEY`, `OPENVIKING_ACCOUNT`, `OPENVIKING_USER`,
143
157
  and `OPENVIKING_PEER_ID` take precedence over values in this file.
package/lib/config.mjs CHANGED
@@ -2,6 +2,7 @@ import fs from "fs"
2
2
  import path from "path"
3
3
  import { homedir } from "os"
4
4
  import { resolveOpenVikingCredentials } from "./shared/credentials.mjs"
5
+ import { resolveEffectivePeerId } from "./shared/workspace-peer.mjs"
5
6
 
6
7
  const DEFAULT_CONFIG = {
7
8
  endpoint: "http://127.0.0.1:1933",
@@ -9,6 +10,8 @@ const DEFAULT_CONFIG = {
9
10
  account: "",
10
11
  user: "",
11
12
  peerId: "",
13
+ workspacePeer: true,
14
+ recallPeerScope: "all",
12
15
  enabled: true,
13
16
  timeoutMs: 30000,
14
17
  runtime: {
@@ -147,6 +150,8 @@ function applyBehaviorConfig(config, fileConfig = {}) {
147
150
  "bypassSessionPatterns",
148
151
  "debug",
149
152
  "debugLogPath",
153
+ "workspacePeer",
154
+ "recallPeerScope",
150
155
  ]) {
151
156
  if (fileConfig[key] !== undefined) config[key] = fileConfig[key]
152
157
  }
@@ -166,6 +171,10 @@ function applyEnv(config) {
166
171
  if (process.env.OPENVIKING_RECALL_PREFER_ABSTRACT !== undefined) {
167
172
  config.autoRecall.preferAbstract = envBool("OPENVIKING_RECALL_PREFER_ABSTRACT") ?? config.autoRecall.preferAbstract
168
173
  }
174
+ if (process.env.OPENVIKING_RECALL_PEER_SCOPE) config.recallPeerScope = process.env.OPENVIKING_RECALL_PEER_SCOPE
175
+ if (process.env.OPENVIKING_WORKSPACE_PEER !== undefined) {
176
+ config.workspacePeer = envBool("OPENVIKING_WORKSPACE_PEER") ?? config.workspacePeer
177
+ }
169
178
  if (process.env.OPENVIKING_MIN_QUERY_LENGTH) config.autoRecall.minQueryLength = process.env.OPENVIKING_MIN_QUERY_LENGTH
170
179
  if (process.env.OPENVIKING_AUTO_CAPTURE !== undefined) {
171
180
  config.autoCapture = envBool("OPENVIKING_AUTO_CAPTURE") ?? config.autoCapture
@@ -220,6 +229,7 @@ function normalizeConfig(config) {
220
229
  config.autoRecall.tokenBudget = Math.max(200, Math.min(50000, Math.round(Number(config.autoRecall.tokenBudget) || 2000)))
221
230
  config.autoRecall.minQueryLength = Math.max(1, Math.min(64, Math.round(Number(config.autoRecall.minQueryLength) || 3)))
222
231
  config.captureMode = config.captureMode === "keyword" ? "keyword" : "semantic"
232
+ config.recallPeerScope = config.recallPeerScope === "actor" ? "actor" : "all"
223
233
  config.captureMaxLength = Math.max(200, Math.min(100000, Math.round(Number(config.captureMaxLength) || 24000)))
224
234
  config.captureToolMaxChars = Math.max(200, Math.min(20000, Math.round(Number(config.captureToolMaxChars) || 2000)))
225
235
  config.commitTokenThreshold = Math.max(1000, Math.round(Number(config.commitTokenThreshold) || 20000))
@@ -262,7 +272,9 @@ export function loadConfig(pluginRoot, projectDirectory) {
262
272
  applyEnv(config)
263
273
  config.configPath = configPath
264
274
  config.legacyCredentialsUsed = legacyUsed && creds.credentialSource !== "env"
265
- return normalizeConfig(config)
275
+ const normalized = normalizeConfig(config)
276
+ normalized.effectivePeer = resolveEffectivePeerId({ cfg: normalized, cwd: projectDirectory })
277
+ return normalized
266
278
  }
267
279
 
268
280
  export function resolveDataDir(pluginRoot, config) {
@@ -20,7 +20,7 @@ export function estimateTokens(text) {
20
20
 
21
21
  export function buildRecallEndpointBody(cfg = {}) {
22
22
  const limit = Math.max(Number(cfg.recallLimit || 0), 1);
23
- return {
23
+ const body = {
24
24
  query: "",
25
25
  quotas: {
26
26
  events: limit,
@@ -32,6 +32,8 @@ export function buildRecallEndpointBody(cfg = {}) {
32
32
  min_score: Number.isFinite(Number(cfg.scoreThreshold)) ? Number(cfg.scoreThreshold) : 0.35,
33
33
  render: true,
34
34
  };
35
+ if (cfg.recallPeerScope === "actor") body.peer_scope = "actor";
36
+ return body;
35
37
  }
36
38
 
37
39
  function clampScore(v) {
@@ -233,10 +235,7 @@ async function buildFallbackInjectionBlock(fetchJSON, items, cfg, actorPeerId =
233
235
  async function recallViaEndpoint(fetchJSON, cfg, query, actorPeerId = "", log = () => {}) {
234
236
  const body = buildRecallEndpointBody(cfg);
235
237
  body.query = query;
236
- const res = await fetchJSON("/api/v1/search/recall", {
237
- method: "POST",
238
- body: JSON.stringify(body),
239
- }, { actorPeerId });
238
+ const res = await postRecall(fetchJSON, body, { actorPeerId, log });
240
239
  if (!res.ok) {
241
240
  log("recall_endpoint_fallback", { status: res.status || 0 });
242
241
  return null;
@@ -251,6 +250,27 @@ async function recallViaEndpoint(fetchJSON, cfg, query, actorPeerId = "", log =
251
250
  ].join("\n");
252
251
  }
253
252
 
253
+ export async function postRecall(fetchJSON, body, opts = {}) {
254
+ const actorPeerId = opts.actorPeerId || "";
255
+ const log = opts.log || (() => {});
256
+ const request = { ...body };
257
+ const res = await fetchJSON("/api/v1/search/recall", {
258
+ method: "POST",
259
+ body: JSON.stringify(request),
260
+ }, { actorPeerId });
261
+ if (!request.peer_scope || (res.status !== 400 && res.status !== 422)) {
262
+ return res;
263
+ }
264
+
265
+ const downgraded = { ...request };
266
+ delete downgraded.peer_scope;
267
+ log("recall_peer_scope_downgrade", { status: res.status || 0 });
268
+ return fetchJSON("/api/v1/search/recall", {
269
+ method: "POST",
270
+ body: JSON.stringify(downgraded),
271
+ }, { actorPeerId });
272
+ }
273
+
254
274
  export async function buildRecallBlock(fetchJSON, cfg, query, options = {}) {
255
275
  const actorPeerId = options.actorPeerId ?? cfg.peerId ?? "";
256
276
  const log = options.log || (() => {});
@@ -0,0 +1,16 @@
1
+ // GENERATED FROM examples/memory-plugin-shared/lib. DO NOT EDIT.
2
+ export function deriveWorkspacePeerId(cwd) {
3
+ return String(cwd || "").replace(/[^A-Za-z0-9]/g, "-");
4
+ }
5
+
6
+ export function resolveEffectivePeerId({ cfg = {}, cwd = "" } = {}) {
7
+ const explicit = String(cfg.peerId || "").trim();
8
+ if (explicit) return { peerId: explicit, source: "explicit" };
9
+
10
+ if (cfg.workspacePeer !== false) {
11
+ const peerId = deriveWorkspacePeerId(cwd);
12
+ if (peerId) return { peerId, source: "workspace" };
13
+ }
14
+
15
+ return { peerId: "", source: "none" };
16
+ }
package/lib/utils.mjs CHANGED
@@ -65,7 +65,7 @@ export function normalizeEndpoint(endpoint) {
65
65
  }
66
66
 
67
67
  export function effectivePeerId(config) {
68
- return String(config.peerId || "").trim() || null
68
+ return String(config.effectivePeer?.peerId || config.peerId || "").trim() || null
69
69
  }
70
70
 
71
71
  export async function fetchJSON(config, endpoint, init = {}, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openviking/opencode-plugin",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "author": "@openviking",
5
5
  "description": "Unified OpenCode plugin for OpenViking repository retrieval and long-term memory",
6
6
  "type": "module",
@@ -14,6 +14,7 @@ import { fileURLToPath } from "node:url"
14
14
  import { loadConfig } from "../lib/config.mjs"
15
15
  import { createLogger } from "../lib/shared/debug-log.mjs"
16
16
  import { createOpenVikingMcpProxy } from "../lib/shared/mcp-proxy-core.mjs"
17
+ import { resolveEffectivePeerId } from "../lib/shared/workspace-peer.mjs"
17
18
 
18
19
  export { createOpenVikingMcpProxy } from "../lib/shared/mcp-proxy-core.mjs"
19
20
 
@@ -37,13 +38,14 @@ function uniq(values) {
37
38
 
38
39
  function readProxyConfig() {
39
40
  const cfg = loadConfig(resolvePath(fileURLToPath(import.meta.url), "..", ".."))
41
+ const effectivePeer = resolveEffectivePeerId({ cfg, cwd: process.cwd() })
40
42
  const mcpUrl = cfg.mcpUrl || `${trimSlash(cfg.endpoint)}/mcp`
41
43
  return {
42
44
  mcpUrl,
43
45
  apiKey: cfg.apiKey || "",
44
46
  account: cfg.account || "",
45
47
  user: cfg.user || "",
46
- peerId: cfg.peerId || "",
48
+ peerId: effectivePeer.peerId,
47
49
  timeoutMs: Math.max(1000, Number(cfg.timeoutMs) || DEFAULT_TIMEOUT_MS),
48
50
  debug: cfg.debug === true,
49
51
  debugLogPath: cfg.debugLogPath,