@openviking/dsh-memory-plugin 0.1.0 → 0.2.0
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 +94 -38
- package/config.mjs +15 -1
- package/index.mjs +8 -3
- package/mcp.mjs +43 -0
- package/package.json +31 -4
- package/servers/mcp-proxy.mjs +42 -0
- package/shared/mcp-proxy-config.mjs +90 -0
- package/shared/mcp-proxy-core.mjs +504 -0
- package/skills/openviking-memory/SKILL.md +81 -0
- package/skills.mjs +23 -0
- package/uri-guard.mjs +18 -14
- package/tools.mjs +0 -246
package/tools.mjs
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
2
|
-
|
|
3
|
-
export function registerOpenVikingTools(ctx, client, runtime) {
|
|
4
|
-
ctx.tools.register(textTool({
|
|
5
|
-
name: "viking_search",
|
|
6
|
-
description:
|
|
7
|
-
"Search OpenViking memories, resources, and skills. Use this for prior decisions, preferences, and project knowledge not present in the current context.",
|
|
8
|
-
parameters: {
|
|
9
|
-
query: { type: "string", required: true, description: "Semantic search query." },
|
|
10
|
-
scope: { type: "string", description: "Optional viking:// URI prefix." },
|
|
11
|
-
limit: { type: "integer", description: "Maximum results, from 1 to 50." },
|
|
12
|
-
},
|
|
13
|
-
async execute(args, exec) {
|
|
14
|
-
const actorPeerId = await peerFor(runtime, exec);
|
|
15
|
-
const results = await client.find(args.query, {
|
|
16
|
-
targetUri: args.scope,
|
|
17
|
-
limit: Math.max(1, Math.min(50, args.limit || 10)),
|
|
18
|
-
actorPeerId,
|
|
19
|
-
});
|
|
20
|
-
if (results.length === 0) return "No results found.";
|
|
21
|
-
return results.map(result => {
|
|
22
|
-
const abstract = result.abstract.length > client.config.recallMaxContentChars
|
|
23
|
-
? `${result.abstract.slice(0, client.config.recallMaxContentChars)}...`
|
|
24
|
-
: result.abstract;
|
|
25
|
-
return `[${result.score.toFixed(2)}] ${result.uri}\n ${abstract}`;
|
|
26
|
-
}).join("\n\n");
|
|
27
|
-
},
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
ctx.tools.register(textTool({
|
|
31
|
-
name: "viking_read",
|
|
32
|
-
description:
|
|
33
|
-
"Read OpenViking content by viking:// URI at abstract, overview, or full detail.",
|
|
34
|
-
parameters: {
|
|
35
|
-
uri: { type: "string", required: true, description: "The viking:// URI to read." },
|
|
36
|
-
level: {
|
|
37
|
-
type: "string",
|
|
38
|
-
required: true,
|
|
39
|
-
enum: ["abstract", "overview", "full"],
|
|
40
|
-
description: "Detail level.",
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
async execute(args, exec) {
|
|
44
|
-
const content = await client.read(
|
|
45
|
-
args.uri,
|
|
46
|
-
args.level,
|
|
47
|
-
await peerFor(runtime, exec),
|
|
48
|
-
);
|
|
49
|
-
return typeof content === "string" && content
|
|
50
|
-
? content
|
|
51
|
-
: `No content at ${args.uri}`;
|
|
52
|
-
},
|
|
53
|
-
}));
|
|
54
|
-
|
|
55
|
-
ctx.tools.register(textTool({
|
|
56
|
-
name: "viking_browse",
|
|
57
|
-
description: "List an OpenViking directory or inspect metadata for a viking:// URI.",
|
|
58
|
-
parameters: {
|
|
59
|
-
action: {
|
|
60
|
-
type: "string",
|
|
61
|
-
required: true,
|
|
62
|
-
enum: ["list", "stat"],
|
|
63
|
-
description: "Browse operation.",
|
|
64
|
-
},
|
|
65
|
-
uri: { type: "string", description: "URI to browse. Defaults to viking://." },
|
|
66
|
-
},
|
|
67
|
-
async execute(args, exec) {
|
|
68
|
-
const actorPeerId = await peerFor(runtime, exec);
|
|
69
|
-
const uri = args.uri || "viking://";
|
|
70
|
-
if (args.action === "stat") {
|
|
71
|
-
const info = await client.stat(uri, actorPeerId);
|
|
72
|
-
return info ? JSON.stringify(info, null, 2) : `Not found: ${uri}`;
|
|
73
|
-
}
|
|
74
|
-
const entries = await client.list(uri, actorPeerId);
|
|
75
|
-
if (entries.length === 0) return `Empty directory: ${uri}`;
|
|
76
|
-
return entries
|
|
77
|
-
.map(entry => `${entry.isDir ? "[dir]" : "[file]"} ${entry.name || entry.uri}`)
|
|
78
|
-
.join("\n");
|
|
79
|
-
},
|
|
80
|
-
}));
|
|
81
|
-
|
|
82
|
-
ctx.tools.register(textTool({
|
|
83
|
-
name: "viking_remember",
|
|
84
|
-
description:
|
|
85
|
-
"Store an important fact, preference, decision, or gotcha in the current OpenViking session.",
|
|
86
|
-
parameters: {
|
|
87
|
-
content: { type: "string", required: true, description: "Fact to remember." },
|
|
88
|
-
category: {
|
|
89
|
-
type: "string",
|
|
90
|
-
description: "Optional category such as preference, decision, entity, event, or pattern.",
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
async execute(args, exec) {
|
|
94
|
-
if (!exec.agent) throw new Error("viking_remember requires a calling agent");
|
|
95
|
-
const state = await runtime.initialize(exec.agent);
|
|
96
|
-
if (!state.ready) return "OpenViking server is not reachable.";
|
|
97
|
-
const category = args.category || "general";
|
|
98
|
-
const payload = {
|
|
99
|
-
role: "user",
|
|
100
|
-
content: `[Remember — ${category}] ${args.content}`,
|
|
101
|
-
...(state.config.peerId ? { peer_id: state.config.peerId } : {}),
|
|
102
|
-
};
|
|
103
|
-
const response = await client.addMessage(
|
|
104
|
-
state.ovSessionId,
|
|
105
|
-
payload,
|
|
106
|
-
state.config.peerId,
|
|
107
|
-
);
|
|
108
|
-
return response.ok
|
|
109
|
-
? `Remembered in OpenViking: "${args.content}" (${category})`
|
|
110
|
-
: `Failed to remember: ${response.error?.message || response.error?.code || "unknown error"}`;
|
|
111
|
-
},
|
|
112
|
-
}));
|
|
113
|
-
|
|
114
|
-
ctx.tools.register(textTool({
|
|
115
|
-
name: "viking_forget",
|
|
116
|
-
description:
|
|
117
|
-
"Permanently delete an OpenViking item only after the user explicitly asks to forget or delete it. Use an exact URI, or delete the strongest search match above score 0.8.",
|
|
118
|
-
parameters: {
|
|
119
|
-
uri: { type: "string", description: "Exact viking:// URI to delete." },
|
|
120
|
-
query: { type: "string", description: "Search query used when URI is omitted." },
|
|
121
|
-
},
|
|
122
|
-
async execute(args, exec) {
|
|
123
|
-
const actorPeerId = await peerFor(runtime, exec);
|
|
124
|
-
let uri = args.uri;
|
|
125
|
-
if (!uri && args.query) {
|
|
126
|
-
const results = await client.find(args.query, { limit: 1, actorPeerId });
|
|
127
|
-
if (!results[0] || results[0].score <= 0.8) {
|
|
128
|
-
return "No strong match found (score > 0.8 required).";
|
|
129
|
-
}
|
|
130
|
-
uri = results[0].uri;
|
|
131
|
-
}
|
|
132
|
-
if (!uri) return "Provide either uri or query.";
|
|
133
|
-
return await client.forget(uri, false, actorPeerId)
|
|
134
|
-
? `Deleted: ${uri}`
|
|
135
|
-
: `Failed to delete: ${uri}`;
|
|
136
|
-
},
|
|
137
|
-
}));
|
|
138
|
-
|
|
139
|
-
ctx.tools.register(textTool({
|
|
140
|
-
name: "viking_add_resource",
|
|
141
|
-
description: "Ingest an HTTP(S) or git URL into OpenViking for indexed retrieval.",
|
|
142
|
-
parameters: {
|
|
143
|
-
url: { type: "string", required: true, description: "Remote URL to ingest." },
|
|
144
|
-
reason: { type: "string", description: "Why this resource is relevant." },
|
|
145
|
-
},
|
|
146
|
-
async execute(args, exec) {
|
|
147
|
-
const result = await client.addResource(
|
|
148
|
-
args.url,
|
|
149
|
-
args.reason,
|
|
150
|
-
await peerFor(runtime, exec),
|
|
151
|
-
);
|
|
152
|
-
return result?.root_uri
|
|
153
|
-
? `Ingested: ${result.root_uri}`
|
|
154
|
-
: `Failed to ingest: ${args.url}`;
|
|
155
|
-
},
|
|
156
|
-
}));
|
|
157
|
-
|
|
158
|
-
ctx.tools.register(textTool({
|
|
159
|
-
name: "viking_archive_expand",
|
|
160
|
-
description: "Read original messages from one archive in the current DSH session.",
|
|
161
|
-
parameters: {
|
|
162
|
-
archive_id: { type: "string", required: true, description: "Archive id such as archive_001." },
|
|
163
|
-
},
|
|
164
|
-
async execute(args, exec) {
|
|
165
|
-
if (!exec.agent) throw new Error("viking_archive_expand requires a calling agent");
|
|
166
|
-
const state = await runtime.initialize(exec.agent);
|
|
167
|
-
const archive = await client.getSessionArchive(
|
|
168
|
-
state.ovSessionId,
|
|
169
|
-
args.archive_id,
|
|
170
|
-
state.config.peerId,
|
|
171
|
-
);
|
|
172
|
-
if (!archive) return `Archive not found: ${args.archive_id}`;
|
|
173
|
-
const messages = Array.isArray(archive.messages) ? archive.messages : [];
|
|
174
|
-
const header = [
|
|
175
|
-
`## ${archive.archive_id || args.archive_id}`,
|
|
176
|
-
archive.abstract ? `**Summary**: ${archive.abstract}` : "",
|
|
177
|
-
`**Messages**: ${messages.length}`,
|
|
178
|
-
].filter(Boolean).join("\n");
|
|
179
|
-
const body = messages.map(formatArchiveMessage).join("\n\n");
|
|
180
|
-
return body ? `${header}\n\n${body}` : header;
|
|
181
|
-
},
|
|
182
|
-
}));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** Presentation identity per tool: pending-card kind and title verb. */
|
|
186
|
-
const TOOL_PRESENTATION = {
|
|
187
|
-
viking_search: { kind: "read", title: args => `OpenViking search: ${args.query}` },
|
|
188
|
-
viking_read: { kind: "read", title: args => `OpenViking read: ${args.uri}` },
|
|
189
|
-
viking_browse: { kind: "read", title: args => `OpenViking browse: ${args.uri ?? "viking://"}` },
|
|
190
|
-
viking_remember: { kind: "other", title: () => "OpenViking remember" },
|
|
191
|
-
viking_forget: { kind: "other", title: args => `OpenViking forget: ${args.uri ?? args.query ?? ""}` },
|
|
192
|
-
viking_add_resource: { kind: "other", title: args => `OpenViking ingest: ${args.url}` },
|
|
193
|
-
viking_archive_expand: { kind: "read", title: () => "OpenViking archive expand" },
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* All seven tools return model-facing text; `defineTool` owns parameter and
|
|
198
|
-
* output JSON-schema conversion, validation wiring, and reserved-name checks,
|
|
199
|
-
* so the definitions here stay declarative and track dsh's ToolDefinition
|
|
200
|
-
* contract through the pinned peer instead of a hand-built structure.
|
|
201
|
-
*/
|
|
202
|
-
function textTool(definition) {
|
|
203
|
-
const presentation = TOOL_PRESENTATION[definition.name];
|
|
204
|
-
return defineTool({
|
|
205
|
-
...definition,
|
|
206
|
-
output: {
|
|
207
|
-
schema: { type: "string" },
|
|
208
|
-
render: (_args, value) => [{ type: "text", text: value }],
|
|
209
|
-
},
|
|
210
|
-
presentCall: args => ({
|
|
211
|
-
card: "generic",
|
|
212
|
-
kind: presentation.kind,
|
|
213
|
-
title: presentation.title(args),
|
|
214
|
-
rawInput: args,
|
|
215
|
-
}),
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async function peerFor(runtime, exec) {
|
|
220
|
-
if (!exec.agent) return undefined;
|
|
221
|
-
return (await runtime.initialize(exec.agent)).config.peerId;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function formatArchiveMessage(message) {
|
|
225
|
-
const role = String(message?.role || "unknown");
|
|
226
|
-
const parts = Array.isArray(message?.parts) ? message.parts : [];
|
|
227
|
-
const body = parts.map(formatArchivePart).filter(Boolean).join("\n");
|
|
228
|
-
return `[${role}]: ${body || "(empty)"}`;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function formatArchivePart(part) {
|
|
232
|
-
if (!part || typeof part !== "object") return "";
|
|
233
|
-
if (part.type === "text") return String(part.text || "");
|
|
234
|
-
if (part.type === "context") {
|
|
235
|
-
return `[Context: ${part.uri || "unknown"}]\n${part.abstract || ""}`.trim();
|
|
236
|
-
}
|
|
237
|
-
if (part.type === "tool") {
|
|
238
|
-
const lines = [`[Tool: ${part.tool_name || "unknown"}]`];
|
|
239
|
-
if (part.tool_input !== undefined) {
|
|
240
|
-
lines.push(`Input: ${JSON.stringify(part.tool_input)}`);
|
|
241
|
-
}
|
|
242
|
-
if (part.tool_output) lines.push(`Output: ${part.tool_output}`);
|
|
243
|
-
return lines.join("\n");
|
|
244
|
-
}
|
|
245
|
-
return `[${part.type || "unknown"}]: ${JSON.stringify(part)}`;
|
|
246
|
-
}
|