@openviking/dsh-memory-plugin 0.3.2 → 0.4.4

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
@@ -29,6 +29,8 @@ be a real package. Linking a source checkout (`dsh plugin --profile web add
29
29
  ./examples/dsh-memory-plugin`) only works when that checkout has its own
30
30
  `node_modules`, because Node resolves the bundle's dsh peers from the source
31
31
  tree's realpath rather than from the profile.
32
+ It also needs `node examples/memory-plugin-shared/sync.mjs` run first: the
33
+ `shared/` modules the bundle imports are generated, not committed.
32
34
 
33
35
  ## Requirements
34
36
 
@@ -122,11 +124,13 @@ SDK client opens that standalone stream it stops resolving POST responses, so
122
124
  `tools/list` never returns. The proxy owns the transport itself and is
123
125
  unaffected.
124
126
 
125
- The bundle's resolved credentials travel to the proxy through the child
126
- environment, because DSH scrubs credential-shaped names out of the inherited env
127
- and a subprocess cannot see the Cordis patch; the proxy then applies the usual
128
- `OPENVIKING_*` → `ovcli.conf` → `ov.conf` chain and reloads when those files
129
- change.
127
+ The bundle's resolved connection — url, MCP url, key, account, user and auth
128
+ mode — travels to the proxy through the child environment, because DSH scrubs
129
+ credential-shaped names out of the inherited env and a subprocess cannot see the
130
+ Cordis patch. It goes with `OPENVIKING_CREDENTIAL_SOURCE=env`, so the proxy reads
131
+ no config file and reaches the server exactly as the runtime does, including
132
+ with no key when the runtime has none. A changed `ovcli.conf` therefore takes
133
+ effect for the tools when DSH restarts the bundle, as it does for the runtime.
130
134
 
131
135
  Two consequences follow from the proxy being one process per profile:
132
136
 
@@ -199,7 +203,8 @@ The patch can also carry plugin config:
199
203
  - `skipSubagentSessions: true` excludes sessions marked with `header.origin: subagent` from automatic profile, recall, capture, and commit; it defaults to `false`.
200
204
  - `syncTurns: false` stops every new write: no captured messages, no threshold or shutdown commit. Writes queued while the toggle was on are still replayed by the background drainer once the server recovers — they were captured with the toggle on. Profile injection and recall are unaffected; it defaults to `true`.
201
205
  - Failed writes enter the shared OpenViking pending queue. A background drainer (default every 60s, `OPENVIKING_PENDING_DRAIN_INTERVAL_MS`) probes the server health and replays the queue in-process, so a transient write failure recovers without restarting dsh; it does not consume the session-start retry budget. Session-start replays keep consuming retries as before.
202
- - `tools/pre-execute` blocks DSH filesystem and shell tools from treating `viking://` URIs as local paths, pointing the model at the bridged `mcp__openviking__*` tools instead.
206
+ - `tools/pre-execute` denies a DSH filesystem tool (`read`, `glob`, `grep`, `edit`, `write`, `str_replace_editor`) whose path argument is a `viking://` URI, pointing the model at the bridged `mcp__openviking__*` tools instead. A `grep` whose pattern is `viking://` text still runs.
207
+ - `tools/post-execute` lets a `bash` command that carries a `viking://` URI run unchanged and attaches a notice for the model: use the bridged tools if it meant OpenViking content, or ignore the notice when the URI is intentional data such as an `ov` argument or an HTTP payload.
203
208
 
204
209
  Each DSH session maps to `dsh-<session-id>` in OpenViking. Workspace-derived actor peers are resolved per session and sent on every session-specific request: the peer is the git identity of the session's workspace — the normalized `origin` URL (`git@github.com:volcengine/OpenViking.git` becomes `github.com-volcengine-openviking`), else the repository root path, that fallback keeping the older rule where every non-letter-or-digit character becomes `-`. Outside a git repository no peer is sent at all, and what is remembered there goes to the user-level space `viking://user/<you>/memories`. One repository therefore keeps one peer across subdirectories, worktrees, clones and machines, while a fork's different origin keeps it separate. DSH does not read workspace `.openviking/config.json` files, so a `peer.id` written there has no effect; pin a peer with `OPENVIKING_PEER_ID` instead. Memories written under the older path-derived peer stay reachable: the default `recallPeerScope: all` sweeps every peer under the user.
205
210
 
package/capture.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
1
2
  import {
2
3
  extractPartsFromPayload,
3
4
  extractTextFromPayload,
@@ -6,6 +7,19 @@ import {
6
7
 
7
8
  export const OPENVIKING_PLUGIN_SOURCE = "openviking-memory";
8
9
 
10
+ export function pluginMessage(content, source) {
11
+ // dsh's own constructor: identity, normalization, and any future Message
12
+ // invariants come from the pinned peer instead of a hand-built object.
13
+ return createUserMessage({
14
+ content: [{ type: "text", text: content }],
15
+ source: {
16
+ kind: "plugin",
17
+ plugin: OPENVIKING_PLUGIN_SOURCE,
18
+ ...source,
19
+ },
20
+ });
21
+ }
22
+
9
23
  export function captureEvent(event, config, toolNames = new Map()) {
10
24
  if (!event || typeof event !== "object") return null;
11
25
  if (event.type === "tool/call") {
package/client.mjs CHANGED
@@ -1,67 +1,13 @@
1
+ import { createOvHttp } from "./shared/ov-http.mjs";
2
+
1
3
  export class OpenVikingClient {
2
4
  constructor(config) {
3
5
  this.config = config;
4
6
  this.connected = false;
5
- }
6
-
7
- headers(options = {}) {
8
- const headers = { "Content-Type": "application/json" };
9
- if (this.config.apiKey) headers.Authorization = `Bearer ${this.config.apiKey}`;
10
- if (this.config.account) headers["X-OpenViking-Account"] = this.config.account;
11
- if (this.config.user) headers["X-OpenViking-User"] = this.config.user;
12
- const actorPeerId = options.actorPeerId ?? this.config.peerId;
13
- if (actorPeerId) headers["X-OpenViking-Actor-Peer"] = actorPeerId;
14
- if (this.config.userAgent) headers["User-Agent"] = this.config.userAgent;
15
- return headers;
16
- }
17
-
18
- async fetchJSON(path, init = {}, options = {}) {
19
- const timeoutMs = options.timeoutMs ?? this.config.requestTimeoutMs;
20
- const controller = new AbortController();
21
- const timer = setTimeout(() => controller.abort(), timeoutMs);
22
- try {
23
- const response = await fetch(`${this.config.endpoint}${path}`, {
24
- ...init,
25
- headers: {
26
- ...this.headers(options),
27
- ...(init.headers || {}),
28
- },
29
- signal: controller.signal,
30
- });
31
- const body = await response.json().catch(() => ({}));
32
- const traceId = body?.result?.trace_id
33
- || body?.error?.trace_id
34
- || body?.trace_id
35
- || undefined;
36
- if (!response.ok || body?.status === "error") {
37
- return {
38
- ok: false,
39
- result: null,
40
- status: response.status,
41
- error: body?.error || { message: `HTTP ${response.status}` },
42
- traceId,
43
- };
44
- }
45
- return {
46
- ok: true,
47
- result: body?.result ?? body,
48
- status: response.status,
49
- traceId,
50
- };
51
- } catch (error) {
52
- return {
53
- ok: false,
54
- result: null,
55
- status: 0,
56
- error: { message: error instanceof Error ? error.message : String(error) },
57
- };
58
- } finally {
59
- clearTimeout(timer);
60
- }
61
- }
62
-
63
- async health() {
64
- return (await this.healthResult()).ok;
7
+ this.fetchJSON = createOvHttp(config, {
8
+ defaultTimeoutMs: config.requestTimeoutMs,
9
+ resolveActorPeerId: () => this.config.peerId,
10
+ });
65
11
  }
66
12
 
67
13
  async healthResult() {
@@ -70,12 +16,6 @@ export class OpenVikingClient {
70
16
  return response;
71
17
  }
72
18
 
73
- async ensureSession(sessionId, actorPeerId) {
74
- const response = await this.ensureSessionResult(sessionId, actorPeerId);
75
- return response.ok
76
- || (response.status === 409 && response.error?.code === "ALREADY_EXISTS");
77
- }
78
-
79
19
  async ensureSessionResult(sessionId, actorPeerId) {
80
20
  const response = await this.fetchJSON("/api/v1/sessions", {
81
21
  method: "POST",
@@ -93,15 +33,6 @@ export class OpenVikingClient {
93
33
  return response.ok ? response.result : null;
94
34
  }
95
35
 
96
- async getSessionArchive(sessionId, archiveId, actorPeerId) {
97
- const response = await this.fetchJSON(
98
- `/api/v1/sessions/${encodeURIComponent(sessionId)}/archives/${encodeURIComponent(archiveId)}`,
99
- {},
100
- { actorPeerId },
101
- );
102
- return response.ok ? response.result : null;
103
- }
104
-
105
36
  async addMessage(sessionId, payload, actorPeerId) {
106
37
  return this.fetchJSON(
107
38
  `/api/v1/sessions/${encodeURIComponent(sessionId)}/messages`,
@@ -123,84 +54,4 @@ export class OpenVikingClient {
123
54
  { timeoutMs: options.timeoutMs ?? 30000, actorPeerId },
124
55
  );
125
56
  }
126
-
127
- async find(query, options = {}) {
128
- const body = { query };
129
- if (options.targetUri) body.target_uri = options.targetUri;
130
- if (options.limit) body.limit = options.limit;
131
- if (options.scoreThreshold !== undefined) body.score_threshold = options.scoreThreshold;
132
- const response = await this.fetchJSON("/api/v1/search/find", {
133
- method: "POST",
134
- body: JSON.stringify(body),
135
- }, { actorPeerId: options.actorPeerId });
136
- if (!response.ok || !response.result) return [];
137
-
138
- const results = [];
139
- for (const bucket of ["memories", "resources", "skills"]) {
140
- const entries = response.result[bucket];
141
- if (!Array.isArray(entries)) continue;
142
- for (const entry of entries) {
143
- results.push({
144
- uri: entry?.uri || "",
145
- contextType: entry?.context_type
146
- || (bucket === "memories" ? "memory" : bucket === "skills" ? "skill" : "resource"),
147
- score: Number(entry?.score || 0),
148
- abstract: entry?.abstract || "",
149
- overview: entry?.overview || null,
150
- });
151
- }
152
- }
153
- return results;
154
- }
155
-
156
- async read(uri, level, actorPeerId) {
157
- const endpoint = level === "abstract"
158
- ? "abstract"
159
- : level === "overview"
160
- ? "overview"
161
- : "read";
162
- const response = await this.fetchJSON(
163
- `/api/v1/content/${endpoint}?uri=${encodeURIComponent(uri)}`,
164
- {},
165
- { actorPeerId },
166
- );
167
- return response.ok ? response.result : null;
168
- }
169
-
170
- async list(uri, actorPeerId) {
171
- const response = await this.fetchJSON(
172
- `/api/v1/fs/ls?uri=${encodeURIComponent(uri)}&output=original`,
173
- {},
174
- { actorPeerId },
175
- );
176
- return response.ok && Array.isArray(response.result) ? response.result : [];
177
- }
178
-
179
- async stat(uri, actorPeerId) {
180
- const response = await this.fetchJSON(
181
- `/api/v1/fs/stat?uri=${encodeURIComponent(uri)}`,
182
- {},
183
- { actorPeerId },
184
- );
185
- return response.ok ? response.result : null;
186
- }
187
-
188
- async forget(uri, recursive = false, actorPeerId) {
189
- const response = await this.fetchJSON(
190
- `/api/v1/fs?uri=${encodeURIComponent(uri)}&recursive=${recursive}`,
191
- { method: "DELETE" },
192
- { actorPeerId },
193
- );
194
- return response.ok;
195
- }
196
-
197
- async addResource(path, reason, actorPeerId) {
198
- const body = { path };
199
- if (reason) body.reason = reason;
200
- const response = await this.fetchJSON("/api/v1/resources", {
201
- method: "POST",
202
- body: JSON.stringify(body),
203
- }, { timeoutMs: 30000, actorPeerId });
204
- return response.ok ? response.result : null;
205
- }
206
57
  }
package/config.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { buildUserAgent, resolveOpenVikingCredentials } from "./shared/credentials.mjs";
2
- import { resolveEffectivePeerId } from "./shared/workspace-peer.mjs";
1
+ import { buildPluginConfig } from "./shared/plugin-config.mjs";
2
+ import { loadCredentialFiles } from "./shared/credentials.mjs";
3
3
 
4
- export const PLUGIN_VERSION = "0.3.2";
4
+ export const PLUGIN_VERSION = "0.4.4";
5
5
 
6
6
  /**
7
7
  * Namespace for the bridged OpenViking MCP tools. DSH publishes every MCP tool
@@ -10,172 +10,38 @@ export const PLUGIN_VERSION = "0.3.2";
10
10
  */
11
11
  export const MCP_SERVER_NAME = "openviking";
12
12
 
13
- const DEFAULT_CONFIG = Object.freeze({
14
- endpoint: "http://127.0.0.1:1933",
15
- apiKey: "",
16
- account: "",
17
- user: "",
18
- peerId: "",
19
- userAgent: "",
20
- workspacePeer: true,
21
- peerSource: "",
22
- recallPeerScope: "all",
23
- recallQueryExpansion: "auto",
24
- recallQueryExpansionConfigured: false,
25
- syncTurns: true,
26
- recallTokenBudget: 2000,
27
- recallMaxContentChars: 500,
28
- recallPreferAbstract: true,
29
- recallLimit: 10,
30
- recallLimitConfigured: false,
31
- scoreThreshold: 0.35,
32
- minQueryLength: 3,
33
- profileTokenBudget: 10000,
34
- commitTokenThreshold: 20000,
35
- commitKeepRecentCount: 10,
36
- captureToolResults: false,
37
- captureMode: "semantic",
38
- captureMaxLength: 24000,
39
- captureToolMaxChars: 1000000,
40
- captureAssistantTurns: true,
41
- skipSubagentSessions: false,
42
- requestTimeoutMs: 10000,
43
- mcpToolCallTimeoutMs: 60000,
44
- });
45
-
13
+ /**
14
+ * Resolve the plugin's configuration.
15
+ *
16
+ * `input` is what the cordis host hands the plugin — this harness has no config
17
+ * file of its own. Every knob is declared in `shared/config-schema.mjs`, and
18
+ * `ovcli.conf`'s `plugin` section outranks the host's input so one file
19
+ * configures every harness and `ov config switch` moves them together.
20
+ * Connection fields are the exception: an endpoint, key, identity or auth mode
21
+ * named by the host is the more specific answer and stays ahead of the
22
+ * credential chain.
23
+ */
46
24
  export function resolveConfig(input = {}, env = process.env, cwd = process.cwd()) {
47
- const credentials = resolveOpenVikingCredentials(env);
48
- const explicitPeerId = input.peerId || credentials.peerId;
49
- const config = {
50
- ...DEFAULT_CONFIG,
51
- ...input,
52
- endpoint: input.endpoint || credentials.baseUrl || DEFAULT_CONFIG.endpoint,
53
- apiKey: input.apiKey || credentials.apiKey,
54
- account: input.account || credentials.account,
55
- user: input.user || credentials.user,
56
- peerId: explicitPeerId,
57
- explicitPeerId,
58
- userAgent: buildUserAgent("dsh", PLUGIN_VERSION),
59
- harness: "dsh",
60
- recallLimitConfigured: Object.prototype.hasOwnProperty.call(input, "recallLimit"),
61
- recallQueryExpansionConfigured: Object.prototype.hasOwnProperty.call(input, "recallQueryExpansion"),
62
- };
63
-
64
- if (env.OPENVIKING_WORKSPACE_PEER !== undefined) {
65
- config.workspacePeer = environmentBoolean(
66
- env.OPENVIKING_WORKSPACE_PEER,
67
- config.workspacePeer,
68
- );
69
- }
70
- if (env.OPENVIKING_RECALL_PEER_SCOPE) {
71
- config.recallPeerScope = env.OPENVIKING_RECALL_PEER_SCOPE;
72
- }
73
- if (env.OPENVIKING_RECALL_QUERY_EXPANSION) {
74
- config.recallQueryExpansion = env.OPENVIKING_RECALL_QUERY_EXPANSION;
75
- config.recallQueryExpansionConfigured = true;
76
- }
77
- if (env.OPENVIKING_RECALL_LIMIT) {
78
- config.recallLimit = env.OPENVIKING_RECALL_LIMIT;
79
- config.recallLimitConfigured = true;
80
- }
81
-
82
- config.endpoint = String(config.endpoint || DEFAULT_CONFIG.endpoint).replace(/\/+$/, "");
83
- config.workspacePeer = config.workspacePeer !== false;
84
- const effectivePeer = resolveEffectivePeerId({ cfg: config, cwd });
85
- config.peerId = effectivePeer.peerId;
86
- config.legacyPeerId = effectivePeer.legacyPeerId;
87
- config.recallPeerScope = config.recallPeerScope === "actor" ? "actor" : "all";
88
- config.recallQueryExpansion = config.recallQueryExpansion === "off" ? "off" : "auto";
89
- config.recallLimit = clampInteger(config.recallLimit, 1, 50, DEFAULT_CONFIG.recallLimit);
90
- config.recallMaxContentChars = clampInteger(
91
- config.recallMaxContentChars,
92
- 100,
93
- 5000,
94
- DEFAULT_CONFIG.recallMaxContentChars,
95
- );
96
- config.recallTokenBudget = clampInteger(
97
- config.recallTokenBudget,
98
- 200,
99
- 50000,
100
- DEFAULT_CONFIG.recallTokenBudget,
101
- );
102
- config.scoreThreshold = clampNumber(
103
- config.scoreThreshold,
104
- 0,
105
- 1,
106
- DEFAULT_CONFIG.scoreThreshold,
107
- );
108
- config.minQueryLength = clampInteger(
109
- config.minQueryLength,
110
- 1,
111
- 64,
112
- DEFAULT_CONFIG.minQueryLength,
113
- );
114
- config.profileTokenBudget = clampInteger(
115
- config.profileTokenBudget,
116
- 500,
117
- 50000,
118
- DEFAULT_CONFIG.profileTokenBudget,
119
- );
120
- config.commitTokenThreshold = clampInteger(
121
- config.commitTokenThreshold,
122
- 1000,
123
- 1000000,
124
- DEFAULT_CONFIG.commitTokenThreshold,
125
- );
126
- config.commitKeepRecentCount = clampInteger(
127
- config.commitKeepRecentCount,
128
- 0,
129
- 1000,
130
- DEFAULT_CONFIG.commitKeepRecentCount,
131
- );
132
- config.captureMaxLength = clampInteger(
133
- config.captureMaxLength,
134
- 200,
135
- 100000,
136
- DEFAULT_CONFIG.captureMaxLength,
137
- );
138
- config.captureToolMaxChars = clampInteger(
139
- config.captureToolMaxChars,
140
- 200,
141
- 1000000,
142
- DEFAULT_CONFIG.captureToolMaxChars,
143
- );
144
- config.requestTimeoutMs = clampInteger(
145
- config.requestTimeoutMs,
146
- 1000,
147
- 120000,
148
- DEFAULT_CONFIG.requestTimeoutMs,
149
- );
150
- config.mcpToolCallTimeoutMs = clampInteger(
151
- config.mcpToolCallTimeoutMs,
152
- 1000,
153
- 600000,
154
- DEFAULT_CONFIG.mcpToolCallTimeoutMs,
155
- );
156
- config.captureMode = config.captureMode === "keyword" ? "keyword" : "semantic";
157
- config.syncTurns = config.syncTurns !== false;
158
- config.captureAssistantTurns = config.captureAssistantTurns !== false;
159
- config.captureToolResults = config.captureToolResults === true;
160
- config.skipSubagentSessions = config.skipSubagentSessions === true;
161
- return config;
162
- }
163
-
164
- function environmentBoolean(value, fallback) {
165
- const normalized = String(value || "").trim().toLowerCase();
166
- if (["0", "false", "no", "off"].includes(normalized)) return false;
167
- if (["1", "true", "yes", "on"].includes(normalized)) return true;
168
- return fallback;
169
- }
170
-
171
- function clampInteger(value, minimum, maximum, fallback) {
172
- const number = Math.round(Number(value));
173
- if (!Number.isFinite(number)) return fallback;
174
- return Math.max(minimum, Math.min(maximum, number));
175
- }
176
-
177
- function clampNumber(value, minimum, maximum, fallback) {
178
- const number = Number(value);
179
- if (!Number.isFinite(number)) return fallback;
180
- return Math.max(minimum, Math.min(maximum, number));
25
+ // ov.conf's `dsh` section is the legacy layer here as everywhere else, but
26
+ // the cordis patch shares that slot; the host named this process's settings,
27
+ // so it wins the overlap.
28
+ const ovSection = loadCredentialFiles(env).ovFile.dsh;
29
+ const config = buildPluginConfig("dsh", {
30
+ env,
31
+ cwd,
32
+ legacy: { ...(ovSection && typeof ovSection === "object" ? ovSection : {}), ...input },
33
+ version: PLUGIN_VERSION,
34
+ hostInput: {
35
+ peerId: input.peerId,
36
+ account: input.account,
37
+ user: input.user,
38
+ apiKey: input.apiKey,
39
+ baseUrl: input.endpoint,
40
+ // The legacy knob layer this used to ride accepted both spellings.
41
+ authMode: input.authMode || input.auth_mode,
42
+ },
43
+ deriveEffectivePeer: true,
44
+ });
45
+
46
+ return { ...config, peerId: config.effectivePeer.peerId };
181
47
  }
package/index.mjs CHANGED
@@ -4,7 +4,7 @@ import { injectStartupProfile } from "./lifecycle.mjs";
4
4
  import { mountOpenVikingMcp } from "./mcp.mjs";
5
5
  import { OpenVikingRuntime } from "./runtime.mjs";
6
6
  import { mountOpenVikingSkills } from "./skills.mjs";
7
- import { guardVikingUri } from "./uri-guard.mjs";
7
+ import { guardVikingUri, noticeVikingUri } from "./uri-guard.mjs";
8
8
 
9
9
  export const name = "openviking-memory";
10
10
  export const inject = ["agents", "sessions", "tools"];
@@ -71,6 +71,7 @@ export function apply(ctx, input = {}) {
71
71
  });
72
72
 
73
73
  ctx.on("tools/pre-execute", guardVikingUri);
74
+ ctx.on("tools/post-execute", noticeVikingUri);
74
75
 
75
76
  // Mounted last, and deliberately not awaited: the bridge's apply blocks on
76
77
  // its first tools/list, so a server that accepts the connection but never
package/mcp-env.mjs ADDED
@@ -0,0 +1,33 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ import { MCP_SERVER_NAME } from "./config.mjs";
4
+ import { forwardConnectionEnv } from "./shared/mcp-proxy-config.mjs";
5
+
6
+ /** The same stdio proxy every other OpenViking memory integration starts. */
7
+ export const PROXY_PATH = fileURLToPath(new URL("./servers/mcp-proxy.mjs", import.meta.url));
8
+
9
+ /**
10
+ * Build the dsh-mcp-client config for the OpenViking stdio proxy.
11
+ *
12
+ * DSH scrubs credential-shaped names out of the environment a subprocess
13
+ * inherits, and the Cordis patch is invisible to one, so the proxy cannot
14
+ * resolve the connection the way this process did. It gets the answer instead:
15
+ * the resolved connection and this session's peer, forwarded with the forced
16
+ * `env` source so the child reads no file and lands on exactly this url, key,
17
+ * identity, auth mode and peer — the empty ones included.
18
+ */
19
+ export function buildMcpConfig(config) {
20
+ // In DSH Desktop, process.execPath is Electron's executable rather than a
21
+ // standalone Node binary. This tells Electron to run the proxy script as
22
+ // Node instead of attempting to launch a second Desktop instance.
23
+ const env = { ELECTRON_RUN_AS_NODE: "1", ...forwardConnectionEnv(config) };
24
+ if (config.timeoutMs) env.OPENVIKING_TIMEOUT_MS = String(config.timeoutMs);
25
+ return {
26
+ transport: "stdio",
27
+ serverName: MCP_SERVER_NAME,
28
+ command: process.execPath,
29
+ args: [PROXY_PATH],
30
+ env,
31
+ toolCallTimeoutMs: config.mcpToolCallTimeoutMs,
32
+ };
33
+ }
package/mcp.mjs CHANGED
@@ -1,39 +1,8 @@
1
- import { fileURLToPath } from "node:url";
2
1
  import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
3
2
 
4
- import { MCP_SERVER_NAME } from "./config.mjs";
3
+ import { buildMcpConfig } from "./mcp-env.mjs";
5
4
 
6
- /** The same stdio proxy every other OpenViking memory integration starts. */
7
- export const PROXY_PATH = fileURLToPath(new URL("./servers/mcp-proxy.mjs", import.meta.url));
8
-
9
- /**
10
- * Build the dsh-mcp-client config for the OpenViking stdio proxy.
11
- *
12
- * The bundle's own credential resolution (`OPENVIKING_*` → `ovcli.conf` →
13
- * `ov.conf`, plus anything set in the Cordis patch) is forwarded through the
14
- * child environment: DSH scrubs credential-shaped names out of the inherited
15
- * env, and the patch is invisible to a subprocess, so values the runtime
16
- * already resolved have to be passed explicitly.
17
- */
18
- export function buildMcpConfig(config) {
19
- // In DSH Desktop, process.execPath is Electron's executable rather than a
20
- // standalone Node binary. This tells Electron to run the proxy script as
21
- // Node instead of attempting to launch a second Desktop instance.
22
- const env = { ELECTRON_RUN_AS_NODE: "1" };
23
- if (config.endpoint) env.OPENVIKING_URL = config.endpoint;
24
- if (config.apiKey) env.OPENVIKING_API_KEY = config.apiKey;
25
- if (config.account) env.OPENVIKING_ACCOUNT = config.account;
26
- if (config.user) env.OPENVIKING_USER = config.user;
27
- if (config.peerId) env.OPENVIKING_PEER_ID = config.peerId;
28
- return {
29
- transport: "stdio",
30
- serverName: MCP_SERVER_NAME,
31
- command: process.execPath,
32
- args: [PROXY_PATH],
33
- env,
34
- toolCallTimeoutMs: config.mcpToolCallTimeoutMs,
35
- };
36
- }
5
+ export { buildMcpConfig, PROXY_PATH } from "./mcp-env.mjs";
37
6
 
38
7
  /**
39
8
  * Mount the tool surface. The bridge ships with dsh itself, so it resolves
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openviking/dsh-memory-plugin",
3
- "version": "0.3.2",
3
+ "version": "0.4.4",
4
4
  "description": "OpenViking memory and context bundle for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "index.mjs",
@@ -14,6 +14,7 @@
14
14
  "capture.mjs",
15
15
  "lifecycle.mjs",
16
16
  "mcp.mjs",
17
+ "mcp-env.mjs",
17
18
  "runtime.mjs",
18
19
  "skills.mjs",
19
20
  "uri-guard.mjs",
@@ -29,10 +30,11 @@
29
30
  }
30
31
  },
31
32
  "scripts": {
33
+ "prepack": "node ../memory-plugin-shared/sync.mjs",
32
34
  "check": "for f in *.mjs shared/*.mjs; do node --check \"$f\" || exit 1; done && npm run check:version",
33
35
  "check:version": "node --input-type=module -e \"import {readFileSync} from 'node:fs'; const {PLUGIN_VERSION} = await import('./config.mjs'); const v = JSON.parse(readFileSync('package.json','utf8')).version; if (PLUGIN_VERSION !== v) throw new Error('PLUGIN_VERSION ' + PLUGIN_VERSION + ' does not match package.json version ' + v);\"",
34
36
  "test": "node --test *.test.mjs",
35
- "prepublishOnly": "npm run check && npm test"
37
+ "prepublishOnly": "npm run prepack && npm run check && npm test"
36
38
  },
37
39
  "peerDependencies": {
38
40
  "@deepseek-ai/dsh-llm": ">=0.1.0-rc.6 <0.2.0 || ^0.1.5-rc.1",