@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.
- package/README.md +11 -6
- package/capture.mjs +14 -0
- package/client.mjs +6 -155
- package/config.mjs +36 -170
- package/index.mjs +2 -1
- package/mcp-env.mjs +33 -0
- package/mcp.mjs +2 -33
- package/package.json +4 -2
- package/runtime.mjs +10 -22
- package/servers/mcp-proxy.mjs +11 -21
- package/shared/capture-utils.mjs +77 -1
- package/shared/config-schema.mjs +359 -0
- package/shared/credentials.mjs +238 -102
- package/shared/mcp-proxy-config.mjs +154 -2
- package/shared/mcp-proxy-core.mjs +23 -15
- package/shared/ov-http.mjs +112 -0
- package/shared/plugin-config.mjs +300 -0
- package/shared/recall-core.mjs +66 -13
- package/shared/uri-guard.mjs +173 -1
- package/shared/workspace-config.mjs +433 -0
- package/shared/workspace-peer.mjs +63 -0
- package/shared/workspace-registry.mjs +87 -0
- package/skills.mjs +2 -0
- package/uri-guard.mjs +20 -10
|
@@ -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
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 {
|
|
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
|
|
41
|
-
if (!
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
|