@openviking/dsh-memory-plugin 0.5.8 → 0.5.10

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
@@ -216,7 +216,7 @@ The older size settings apply to fallback recall, not the primary context reques
216
216
 
217
217
  ## Behavior
218
218
 
219
- - `agent/session-start` injects the OpenViking profile, the available-memory index, and the `<available-skills>` catalog through `agent.inject()`.
219
+ - Agent startup injects the OpenViking profile, the available-memory index, and the `<available-skills>` catalog through `agent.inject()`, and registers the per-session disposal commit. On DSH `0.1.7` and later this runs from the serial `agent/created` event (`{ agent, source, signal? }`), which holds queued input until it settles; DSH `0.1.0-rc.6` and `0.1.5` emit `agent/created` without `source` and start the session through `agent/session-start` instead. Startup runs once per agent, and `agent/pre-step` delivers the profile only if startup did not.
220
220
  - `agent/pre-step` retrieves with the current step input and appends a durable plugin message to that same step.
221
221
  - `session/event` captures user, assistant, and optionally tool-result messages without scraping a transcript.
222
222
  - `turn/end` checks the OpenViking pending-token threshold and commits when required.
package/config.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { buildPluginConfig } from "./shared/plugin-config.mjs";
2
2
  import { loadCredentialFiles } from "./shared/credentials.mjs";
3
3
 
4
- export const PLUGIN_VERSION = "0.5.8";
4
+ export const PLUGIN_VERSION = "0.5.10";
5
5
 
6
6
  /**
7
7
  * Namespace for the bridged OpenViking MCP tools. DSH publishes every MCP tool
package/index.mjs CHANGED
@@ -34,14 +34,42 @@ export function apply(ctx, input = {}) {
34
34
  "openvikingMemory.stopDrainer()",
35
35
  );
36
36
 
37
- ctx.on("agent/session-start", ({ agent }) => {
38
- if (skipMemory(agent.session)) return false;
37
+ // Per-agent startup: register the session's disposal commit, initialize the
38
+ // OpenViking session, and inject the profile while the agent is still idle.
39
+ // Runs at most once per agent, whichever DSH event reaches it.
40
+ const started = new WeakSet();
41
+ const startAgent = async (agent, signal) => {
42
+ if (skipMemory(agent.session) || started.has(agent)) return false;
43
+ started.add(agent);
39
44
  agent.ctx.effect(
40
45
  () => () => runtime.dispose(agent.session),
41
46
  "openvikingMemory.disposeSession()",
42
47
  );
43
- return injectStartupProfile(agent, runtime);
48
+ try {
49
+ return await injectStartupProfile(agent, runtime, signal);
50
+ } catch (error) {
51
+ // A throw from a serial `agent/created` listener fails agent creation;
52
+ // memory startup must never do that. pre-step retries profile delivery.
53
+ runtime.log("startup_error", {
54
+ error: error instanceof Error ? error.message : String(error),
55
+ });
56
+ return false;
57
+ }
58
+ };
59
+
60
+ // DSH 0.1.7+ has no `agent/session-start`; startup is the serial
61
+ // `agent/created` event with `{ agent, source, signal? }`, and AgentLoop holds
62
+ // queued input until its listeners settle. DSH 0.1.0-rc.6 and 0.1.5 still
63
+ // emit a composition-only `agent/created` with `{ agent }` only, followed by
64
+ // `agent/session-start`, so a missing `source` leaves startup to that event.
65
+ // The listener must resolve to undefined: any other value bails the serial
66
+ // dispatch and skips the listeners registered after this one.
67
+ ctx.on("agent/created", async ({ agent, source, signal }) => {
68
+ if (source === undefined) return undefined;
69
+ await untilAborted(startAgent(agent, signal), signal);
70
+ return undefined;
44
71
  });
72
+ ctx.on("agent/session-start", ({ agent }) => startAgent(agent));
45
73
 
46
74
  // prepend: downstream waterfall listeners run first, so this plugin sees
47
75
  // the final claimed batch and appends after every other contributor.
@@ -83,3 +111,18 @@ export function apply(ctx, input = {}) {
83
111
  mountOpenVikingMcp(ctx, config);
84
112
  mountOpenVikingSkills(ctx);
85
113
  }
114
+
115
+ // Resolve when `promise` settles or `signal` aborts, whichever comes first. An
116
+ // abandoned initialization keeps running and is reused by the pre-step path.
117
+ function untilAborted(promise, signal) {
118
+ if (!signal) return promise;
119
+ if (signal.aborted) return Promise.resolve();
120
+ return new Promise(resolve => {
121
+ const onAbort = () => resolve();
122
+ signal.addEventListener("abort", onAbort, { once: true });
123
+ promise.finally(() => {
124
+ signal.removeEventListener("abort", onAbort);
125
+ resolve();
126
+ });
127
+ });
128
+ }
package/lifecycle.mjs CHANGED
@@ -1,6 +1,6 @@
1
- export async function injectStartupProfile(agent, runtime) {
1
+ export async function injectStartupProfile(agent, runtime, signal) {
2
2
  await runtime.initialize(agent);
3
- if (agent.status !== "idle") return false;
3
+ if (signal?.aborted || agent.status !== "idle") return false;
4
4
  const profile = await runtime.profileMessage(agent);
5
5
  if (!profile) return false;
6
6
  agent.inject(profile);
package/mcp-env.mjs CHANGED
@@ -12,15 +12,20 @@ export const PROXY_PATH = fileURLToPath(new URL("./servers/mcp-proxy.mjs", impor
12
12
  * DSH scrubs credential-shaped names out of the environment a subprocess
13
13
  * inherits, and the Cordis patch is invisible to one, so the proxy cannot
14
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.
15
+ * the resolved connection, diagnostics, and this session's peer, forwarded
16
+ * with the forced `env` source so the child reads no file and lands on exactly
17
+ * these values — the empty ones included.
18
18
  */
19
19
  export function buildMcpConfig(config) {
20
20
  // In DSH Desktop, process.execPath is Electron's executable rather than a
21
21
  // standalone Node binary. This tells Electron to run the proxy script as
22
22
  // Node instead of attempting to launch a second Desktop instance.
23
- const env = { ELECTRON_RUN_AS_NODE: "1", ...forwardConnectionEnv(config) };
23
+ const env = {
24
+ ELECTRON_RUN_AS_NODE: "1",
25
+ ...forwardConnectionEnv(config),
26
+ OPENVIKING_DEBUG: config.debug ? "1" : "0",
27
+ OPENVIKING_DEBUG_LOG: config.debugLogPath || "",
28
+ };
24
29
  if (config.recallPeerScope) env.OPENVIKING_RECALL_PEER_SCOPE = config.recallPeerScope;
25
30
  if (config.timeoutMs) env.OPENVIKING_TIMEOUT_MS = String(config.timeoutMs);
26
31
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openviking/dsh-memory-plugin",
3
- "version": "0.5.8",
3
+ "version": "0.5.10",
4
4
  "description": "OpenViking memory and context bundle for DeepSeek Harness",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",
@@ -4,12 +4,13 @@
4
4
  * stdio -> streamable-HTTP MCP proxy for the OpenViking DSH bundle.
5
5
  *
6
6
  * DSH's MCP bridge starts this process as a local stdio MCP server. The proxy
7
- * resolves its connection through the same `resolveConfig()` as the in-process
8
- * runtime, from the child environment the bundle builds in `mcp.mjs`: DSH
7
+ * resolves its connection and diagnostics through the same `resolveConfig()`
8
+ * as the in-process runtime, from the child environment built in `mcp-env.mjs`: DSH
9
9
  * scrubs credential-shaped names out of what it inherits, and values that came
10
10
  * from the Cordis patch are invisible to a subprocess otherwise.
11
11
  */
12
12
 
13
+ import { realpathSync } from "node:fs";
13
14
  import { resolve as resolvePath } from "node:path";
14
15
  import { fileURLToPath } from "node:url";
15
16
  import { resolveConfig } from "../config.mjs";
@@ -25,11 +26,20 @@ export function readProxyConfig(env = process.env, cwd = process.cwd()) {
25
26
  // still decides whether the proxy may send it. In broad recall mode the
26
27
  // actor header must stay unset; otherwise a process launched in workspace A
27
28
  // narrows MCP searches for a session whose runtime is serving workspace B.
28
- debug: Boolean(env.OV_DEBUG_LOG),
29
- debugLogPath: env.OV_DEBUG_LOG || "",
29
+ debug: env.OV_DEBUG_LOG ? true : cfg.debug,
30
+ debugLogPath: env.OV_DEBUG_LOG || cfg.debugLogPath,
30
31
  });
31
32
  }
32
33
 
33
- if (process.argv[1] && fileURLToPath(import.meta.url) === resolvePath(process.argv[1])) {
34
+ function isDirectRun() {
35
+ if (!process.argv[1]) return false;
36
+ try {
37
+ return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
38
+ } catch {
39
+ return resolvePath(process.argv[1]) === fileURLToPath(import.meta.url);
40
+ }
41
+ }
42
+
43
+ if (isDirectRun()) {
34
44
  createOpenVikingMcpProxy({ readConfig: readProxyConfig, loggerFactory: createLogger }).start();
35
45
  }