@openviking/dsh-memory-plugin 0.3.2 → 0.4.3

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.
@@ -0,0 +1,87 @@
1
+ // GENERATED FROM examples/memory-plugin-shared/lib. DO NOT EDIT.
2
+ /**
3
+ * The per-machine workspace registry, `~/.openviking/workspaces/`.
4
+ *
5
+ * One file per workspace rather than one file listing them all: hooks are many
6
+ * short-lived processes, and a shared JSON file loses writes whenever any two
7
+ * of them touch unrelated workspaces at once.
8
+ *
9
+ * The plugins only read this layer; nothing writes it today. A user who wants
10
+ * an entry creates the file by hand, and it then outranks both workspace files
11
+ * — so it is where they keep the last word over any repository they clone.
12
+ */
13
+
14
+ import { createHash } from "node:crypto";
15
+ import { homedir } from "node:os";
16
+ import { basename, join } from "node:path";
17
+
18
+ import { readWorkspaceFile } from "./workspace-config.mjs";
19
+
20
+ export function registryDir(env = process.env) {
21
+ const home = String(env.OPENVIKING_HOME || "").trim();
22
+ const base = home ? home.replace(/^~(?=$|\/)/, homedir()) : join(homedir(), ".openviking");
23
+ return join(base, "workspaces");
24
+ }
25
+
26
+ /**
27
+ * A readable name plus a hash, keyed on the workspace's identity rather than
28
+ * its path wherever git supplies one.
29
+ *
30
+ * Two linked worktrees of one repository are one workspace — the same peer and
31
+ * the same settings — and keying on the checkout path would silently split
32
+ * them in two. Outside a repository there is no
33
+ * identity but the path, so two `~/src/api` clones still get separate entries.
34
+ */
35
+ export function slotName(root, identity = null) {
36
+ const path = String(root || "");
37
+ const key = identity ? identityKey(identity) : "path";
38
+ const source = key === "path" ? path : key;
39
+ const label = key.startsWith("remote:") ? key.split("/").pop() : basename(path);
40
+ const readable = String(label || "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
41
+ const digest = createHash("sha256").update(source).digest("hex").slice(0, 12);
42
+ return `${readable ? `${readable}-` : ""}${digest}.json`;
43
+ }
44
+
45
+ export function entryPath(root, env = process.env, identity = null) {
46
+ return join(registryDir(env), slotName(root, identity));
47
+ }
48
+
49
+ /**
50
+ * The identity a stored entry is checked against. Path alone is not enough:
51
+ * a directory can be deleted and a different repository cloned in its place,
52
+ * and inheriting the old entry's peer would silently cross two projects.
53
+ */
54
+ export function identityKey(identity) {
55
+ // The normalized remote, so re-spelling origin (ssh ↔ https, or rotating an
56
+ // embedded token) is not mistaken for a different repository.
57
+ const remote = String(identity?.remote || "").trim();
58
+ if (remote) return `remote:${remote}`;
59
+ if (identity?.isGit) return `git:${identity.gitCommonDir || ""}`;
60
+ return "path";
61
+ }
62
+
63
+ /**
64
+ * Read this workspace's entry, or null.
65
+ *
66
+ * A stored entry whose identity contradicts the current one is treated as a
67
+ * miss — negative evidence. Nothing is inherited from it.
68
+ */
69
+ export function readEntry(root, { identity = null, env = process.env } = {}) {
70
+ const path = entryPath(root, env, identity);
71
+ const file = readWorkspaceFile(path, { layer: "registry" });
72
+ if (!file.data) return { path, entry: null, warnings: file.warnings, conflict: false };
73
+
74
+ const entry = file.data;
75
+ const warnings = [...file.warnings];
76
+ if (identity) {
77
+ const expected = identityKey(identity);
78
+ const stored = String(entry.identity || "");
79
+ if (stored && stored !== expected) {
80
+ warnings.push(
81
+ `${path} was recorded for a different repository (${stored}); starting a fresh entry for ${expected}`,
82
+ );
83
+ return { path, entry: null, warnings, conflict: true };
84
+ }
85
+ }
86
+ return { path, entry, warnings, conflict: false };
87
+ }
package/skills.mjs CHANGED
@@ -12,6 +12,8 @@ export function buildSkillsConfig() {
12
12
  providerName: SKILL_PROVIDER_NAME,
13
13
  includeDefaultRoots: false,
14
14
  bundledSkillDir: SKILLS_DIR,
15
+ // Packaged skills change only on upgrade; watchers can block replacement on Windows.
16
+ watch: false,
15
17
  };
16
18
  }
17
19
 
package/uri-guard.mjs CHANGED
@@ -1,5 +1,7 @@
1
+ import { boundContextSummary } from "@deepseek-ai/dsh-llm";
2
+ import { pluginMessage } from "./capture.mjs";
1
3
  import { MCP_SERVER_NAME } from "./config.mjs";
2
- import { buildGuardMessage, findVikingUri } from "./shared/uri-guard.mjs";
4
+ import { evaluateUriGuard, evaluateUriNotice } from "./shared/uri-guard.mjs";
3
5
 
4
6
  /** Model-facing name of a bridged OpenViking MCP tool. */
5
7
  const mcp = rawName => `mcp__${MCP_SERVER_NAME}__${rawName}`;
@@ -37,16 +39,24 @@ const GUARDED_TOOLS = {
37
39
  };
38
40
 
39
41
  export async function guardVikingUri(exec, next) {
40
- const hint = GUARDED_TOOLS[exec.name];
41
- if (!hint) return next();
42
- const uri = findVikingUri(exec.arguments);
43
- if (!uri) return next();
42
+ const decision = evaluateUriGuard(exec.name, exec.arguments, { hints: GUARDED_TOOLS });
43
+ if (!decision) return next();
44
+ return { kind: "deny", reason: decision.reason };
45
+ }
46
+
47
+ // Delegates first so a later listener's block or content replacement survives;
48
+ // the notice only rides along as one more context.
49
+ export async function noticeVikingUri(exec, _result, next) {
50
+ const notice = evaluateUriNotice(exec.name, exec.arguments, { hints: GUARDED_TOOLS });
51
+ if (!notice) return next();
52
+ const decision = await next();
53
+ const context = pluginMessage(notice.reason, {
54
+ form: "notice",
55
+ summary: boundContextSummary(`OpenViking URI guard: shell command contains ${notice.uri}`),
56
+ });
44
57
  return {
45
- kind: "deny",
46
- reason: buildGuardMessage(uri, {
47
- tool: hint.tool,
48
- example: hint.example(uri, exec.arguments),
49
- }),
58
+ ...decision,
59
+ additionalContexts: [...(decision.additionalContexts ?? []), context],
50
60
  };
51
61
  }
52
62