@openviking/openclaw-plugin 2026.6.2 → 2026.6.17-dev.2
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/INSTALL-AGENT.md +4 -4
- package/INSTALL-ZH.md +68 -8
- package/INSTALL.md +12 -8
- package/README.md +45 -35
- package/adapters/http-transport.ts +3 -0
- package/adapters/resource-packager.ts +141 -0
- package/auto-recall.ts +258 -27
- package/client.ts +201 -177
- package/commands/setup.ts +200 -33
- package/config/feature-gates.json +10 -0
- package/config.ts +428 -85
- package/context-engine.ts +71 -1402
- package/dist/adapters/http-transport.js +1 -0
- package/dist/adapters/resource-packager.js +114 -0
- package/dist/auto-recall.js +201 -25
- package/dist/client.js +125 -161
- package/dist/commands/setup.js +170 -32
- package/dist/config.js +285 -64
- package/dist/context-engine.js +62 -1173
- package/dist/index.js +209 -1489
- package/dist/memory-ranking.js +35 -12
- package/dist/plugin/command-registration.js +5 -0
- package/dist/plugin/openviking-archive-tools.js +210 -0
- package/dist/plugin/openviking-bypass-runtime.js +8 -0
- package/dist/plugin/openviking-client-runtime.js +28 -0
- package/dist/plugin/openviking-command-args.js +146 -0
- package/dist/plugin/openviking-command-definitions.js +206 -0
- package/dist/plugin/openviking-context-engine-ref.js +11 -0
- package/dist/plugin/openviking-context-engine-registration.js +23 -0
- package/dist/plugin/openviking-feature-gates.js +250 -0
- package/dist/plugin/openviking-import-runtime.js +50 -0
- package/dist/plugin/openviking-import-tools.js +86 -0
- package/dist/plugin/openviking-lifecycle-hooks.js +33 -0
- package/dist/plugin/openviking-memory-recall-tools.js +233 -0
- package/dist/plugin/openviking-memory-tools.js +199 -0
- package/dist/plugin/openviking-query-config-command.js +203 -0
- package/dist/plugin/openviking-query-runtime.js +367 -0
- package/dist/plugin/openviking-query-tools.js +91 -0
- package/dist/plugin/openviking-recall-trace-runtime.js +387 -0
- package/dist/plugin/openviking-recall-trace-tools.js +38 -0
- package/dist/plugin/openviking-runtime-state.js +26 -0
- package/dist/plugin/openviking-runtime-utils.js +62 -0
- package/dist/plugin/openviking-services.js +20 -0
- package/dist/plugin/openviking-session-routing-runtime.js +61 -0
- package/dist/plugin/openviking-tool-result-tools.js +268 -0
- package/dist/plugin/recall-trace-routes.js +77 -0
- package/dist/plugin/tool-registration.js +23 -0
- package/dist/process-manager.js +1 -1
- package/dist/query-config.js +336 -0
- package/dist/recall-trace.js +309 -0
- package/dist/registries/recall-resource-types.js +58 -0
- package/dist/routing/identity-routing.js +152 -0
- package/dist/routing/memory-uri.js +7 -0
- package/dist/services/context-lifecycle-service.js +847 -0
- package/dist/services/context-message-adapter.js +340 -0
- package/dist/session-transcript-repair.js +0 -1
- package/dist/text-utils.js +14 -31
- package/index.ts +271 -1873
- package/install-manifest.json +10 -1
- package/memory-ranking.ts +46 -10
- package/openclaw.plugin.json +198 -36
- package/package.json +14 -4
- package/plugin/command-registration.ts +34 -0
- package/plugin/openviking-archive-tools.ts +301 -0
- package/plugin/openviking-bypass-runtime.ts +26 -0
- package/plugin/openviking-client-runtime.ts +74 -0
- package/plugin/openviking-command-args.ts +187 -0
- package/plugin/openviking-command-definitions.ts +294 -0
- package/plugin/openviking-context-engine-ref.ts +16 -0
- package/plugin/openviking-context-engine-registration.ts +97 -0
- package/plugin/openviking-feature-gates.ts +335 -0
- package/plugin/openviking-import-runtime.ts +105 -0
- package/plugin/openviking-import-tools.ts +133 -0
- package/plugin/openviking-lifecycle-hooks.ts +65 -0
- package/plugin/openviking-memory-recall-tools.ts +368 -0
- package/plugin/openviking-memory-tools.ts +299 -0
- package/plugin/openviking-query-config-command.ts +230 -0
- package/plugin/openviking-query-formatters.ts +108 -0
- package/plugin/openviking-query-runtime.ts +476 -0
- package/plugin/openviking-query-tools.ts +161 -0
- package/plugin/openviking-recall-trace-runtime.ts +482 -0
- package/plugin/openviking-recall-trace-tools.ts +99 -0
- package/plugin/openviking-runtime-state.ts +52 -0
- package/plugin/openviking-runtime-utils.ts +67 -0
- package/plugin/openviking-services.ts +49 -0
- package/plugin/openviking-session-routing-runtime.ts +105 -0
- package/plugin/openviking-tool-result-tools.ts +397 -0
- package/plugin/recall-trace-routes.ts +133 -0
- package/plugin/tool-registration.ts +51 -0
- package/process-manager.ts +1 -1
- package/query-config.ts +400 -0
- package/recall-trace.ts +442 -0
- package/registries/openviking-tools.ts +84 -0
- package/registries/recall-resource-types.ts +74 -0
- package/routing/identity-routing.ts +210 -0
- package/routing/memory-uri.ts +8 -0
- package/services/context-lifecycle-service.ts +1209 -0
- package/services/context-message-adapter.ts +375 -0
- package/services/setup/config-writer.ts +125 -0
- package/services/setup/exit-utils.ts +5 -0
- package/services/setup/package-metadata.ts +56 -0
- package/services/setup/probe-service.ts +143 -0
- package/services/setup/setup-flow.ts +306 -0
- package/services/setup/version-compatibility.ts +41 -0
- package/session-transcript-repair.ts +0 -2
- package/skills/install-openviking-memory/SKILL.md +123 -50
- package/skills/openviking-context-database/SKILL.md +286 -0
- package/text-utils.ts +15 -34
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
function toTraceResult(item, resultType, deps) {
|
|
3
|
+
return {
|
|
4
|
+
uri: item.uri,
|
|
5
|
+
resourceType: deps.inferRecallResourceType(item.uri),
|
|
6
|
+
category: item.category,
|
|
7
|
+
score: item.score,
|
|
8
|
+
level: item.level,
|
|
9
|
+
abstractPreview: deps.previewText(item.abstract || item.overview, deps.cfg.traceRecallPreviewChars),
|
|
10
|
+
resultType,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function registerOpenVikingMemoryRecallTools(deps) {
|
|
14
|
+
deps.registerTool((ctx) => ({
|
|
15
|
+
name: "memory_recall",
|
|
16
|
+
label: "Memory Recall (OpenViking)",
|
|
17
|
+
description: "Search long-term memories from OpenViking. Use when you need past user preferences, facts, or decisions.",
|
|
18
|
+
parameters: Type.Object({
|
|
19
|
+
query: Type.String({ description: "Search query" }),
|
|
20
|
+
limit: Type.Optional(Type.Number({ description: "Max results (default: plugin config)" })),
|
|
21
|
+
scoreThreshold: Type.Optional(Type.Number({ description: "Minimum score (0-1, default: plugin config)" })),
|
|
22
|
+
targetUri: Type.Optional(Type.String({ description: "Search scope URI (default: plugin config)" })),
|
|
23
|
+
resourceTypes: Type.Optional(Type.Array(Type.String({ description: "resource, user, or agent; used when targetUri is omitted" }))),
|
|
24
|
+
}),
|
|
25
|
+
async execute(_toolCallId, params) {
|
|
26
|
+
if (deps.isBypassedSession(ctx)) {
|
|
27
|
+
return deps.makeBypassedToolResult("memory_recall");
|
|
28
|
+
}
|
|
29
|
+
const session = deps.resolvePluginSessionRouting(ctx);
|
|
30
|
+
const { query } = params;
|
|
31
|
+
const queryConfig = await deps.queryConfigStore.getEffective(deps.toQueryConfigContext(session), {
|
|
32
|
+
recallLimit: typeof params.limit === "number" ? params.limit : undefined,
|
|
33
|
+
scoreThreshold: typeof params.scoreThreshold === "number" ? params.scoreThreshold : undefined,
|
|
34
|
+
targetUri: typeof params.targetUri === "string" ? params.targetUri : undefined,
|
|
35
|
+
resourceTypes: Object.prototype.hasOwnProperty.call(params, "resourceTypes")
|
|
36
|
+
? params.resourceTypes
|
|
37
|
+
: undefined,
|
|
38
|
+
});
|
|
39
|
+
const limit = queryConfig.recallLimit;
|
|
40
|
+
const scoreThreshold = queryConfig.scoreThreshold;
|
|
41
|
+
const targetUri = typeof params.targetUri === "string"
|
|
42
|
+
? params.targetUri
|
|
43
|
+
: queryConfig.targetUri;
|
|
44
|
+
const requestedResourceTypes = Object.prototype.hasOwnProperty.call(params, "resourceTypes")
|
|
45
|
+
? params.resourceTypes
|
|
46
|
+
: queryConfig.resourceTypes;
|
|
47
|
+
const requestLimit = queryConfig.candidateLimit;
|
|
48
|
+
const recallClient = await deps.getClient();
|
|
49
|
+
if (deps.cfg.logFindRequests) {
|
|
50
|
+
deps.logger.info?.(`openviking: memory_recall X-OpenViking-Actor-Peer="${session.agentId}" ` +
|
|
51
|
+
`(plugin defaultAgentId="${recallClient.getDefaultAgentId()}" is unused when session context is present)`);
|
|
52
|
+
}
|
|
53
|
+
let result;
|
|
54
|
+
let memoryRecallSearches = [];
|
|
55
|
+
if (targetUri) {
|
|
56
|
+
result = await recallClient.find(query, {
|
|
57
|
+
targetUri,
|
|
58
|
+
limit: requestLimit,
|
|
59
|
+
scoreThreshold: 0,
|
|
60
|
+
actorPeerId: session.agentId,
|
|
61
|
+
});
|
|
62
|
+
const traceResults = [
|
|
63
|
+
...(result.memories ?? []).map((item) => toTraceResult(item, "memory", deps)),
|
|
64
|
+
...(result.resources ?? []).map((item) => toTraceResult(item, "resource", deps)),
|
|
65
|
+
].slice(0, deps.cfg.traceRecallMaxResultsPerSearch);
|
|
66
|
+
memoryRecallSearches = [{
|
|
67
|
+
resourceType: deps.inferRecallResourceType(targetUri) ?? "resource",
|
|
68
|
+
targetUriInput: targetUri,
|
|
69
|
+
targetUriResolved: targetUri,
|
|
70
|
+
limit: requestLimit,
|
|
71
|
+
scoreThreshold,
|
|
72
|
+
durationMs: 0,
|
|
73
|
+
total: result.total ?? traceResults.length,
|
|
74
|
+
results: traceResults,
|
|
75
|
+
}];
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const searchPlan = deps.resolveRecallSearchPlan(requestedResourceTypes ?? deps.cfg.recallTargetTypes, {
|
|
79
|
+
ovSessionId: session.ovSessionId,
|
|
80
|
+
agentId: session.agentId,
|
|
81
|
+
});
|
|
82
|
+
const settled = await Promise.allSettled(searchPlan.searches.map((search) => recallClient.find(query, {
|
|
83
|
+
targetUri: search.targetUri,
|
|
84
|
+
limit: requestLimit,
|
|
85
|
+
scoreThreshold: 0,
|
|
86
|
+
contextType: search.contextType,
|
|
87
|
+
actorPeerId: session.agentId,
|
|
88
|
+
})));
|
|
89
|
+
const allMemories = [];
|
|
90
|
+
for (let index = 0; index < settled.length; index += 1) {
|
|
91
|
+
const s = settled[index];
|
|
92
|
+
const search = searchPlan.searches[index];
|
|
93
|
+
if (s.status === "fulfilled") {
|
|
94
|
+
allMemories.push(...(s.value.memories ?? []), ...(s.value.resources ?? []));
|
|
95
|
+
memoryRecallSearches.push({
|
|
96
|
+
resourceType: search.resourceType,
|
|
97
|
+
targetUriInput: search.targetUri,
|
|
98
|
+
targetUriResolved: search.targetUri,
|
|
99
|
+
limit: requestLimit,
|
|
100
|
+
scoreThreshold,
|
|
101
|
+
durationMs: 0,
|
|
102
|
+
total: s.value.total ?? ((s.value.memories ?? []).length + (s.value.resources ?? []).length),
|
|
103
|
+
results: [
|
|
104
|
+
...(s.value.memories ?? []).map((item) => toTraceResult(item, "memory", deps)),
|
|
105
|
+
...(s.value.resources ?? []).map((item) => toTraceResult(item, "resource", deps)),
|
|
106
|
+
].slice(0, deps.cfg.traceRecallMaxResultsPerSearch),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
memoryRecallSearches.push({
|
|
111
|
+
resourceType: search.resourceType,
|
|
112
|
+
targetUriInput: search.targetUri,
|
|
113
|
+
targetUriResolved: search.targetUri,
|
|
114
|
+
limit: requestLimit,
|
|
115
|
+
scoreThreshold,
|
|
116
|
+
durationMs: 0,
|
|
117
|
+
total: 0,
|
|
118
|
+
results: [],
|
|
119
|
+
error: s.reason instanceof Error ? s.reason.message : String(s.reason),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const uniqueMemories = allMemories.filter((memory, index, self) => index === self.findIndex((m) => m.uri === memory.uri));
|
|
124
|
+
const leafOnly = uniqueMemories.filter((m) => !m.level || m.level === 2);
|
|
125
|
+
result = {
|
|
126
|
+
memories: leafOnly,
|
|
127
|
+
total: leafOnly.length,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const leafOnly = (result.memories ?? []).filter((m) => !m.level || m.level === 2);
|
|
131
|
+
const processed = deps.postProcessMemories(leafOnly, {
|
|
132
|
+
limit: requestLimit,
|
|
133
|
+
scoreThreshold,
|
|
134
|
+
});
|
|
135
|
+
const memories = deps.pickMemoriesForInjection(processed, limit, query, scoreThreshold, {
|
|
136
|
+
weights: queryConfig.rankingWeights,
|
|
137
|
+
categoryWeights: queryConfig.categoryWeights,
|
|
138
|
+
resourceTypeWeights: queryConfig.resourceTypeWeights,
|
|
139
|
+
});
|
|
140
|
+
const candidateTraceResults = leafOnly
|
|
141
|
+
.map((item) => toTraceResult(item, deps.inferRecallResourceType(item.uri) === "resource" ? "resource" : "memory", deps))
|
|
142
|
+
.slice(0, deps.cfg.traceRecallMaxResultsPerSearch);
|
|
143
|
+
const traceResourceTypes = [...new Set((targetUri ? [deps.inferRecallResourceType(targetUri)] : memoryRecallSearches.map((search) => search.resourceType))
|
|
144
|
+
.filter((resourceType) => Boolean(resourceType)))];
|
|
145
|
+
const recordMemoryRecallTrace = async (injectedUris) => {
|
|
146
|
+
await deps.traceRecorder?.recordAndFlush?.({
|
|
147
|
+
schemaVersion: "1.0",
|
|
148
|
+
traceId: deps.createTraceId("memory_recall"),
|
|
149
|
+
ts: Date.now(),
|
|
150
|
+
sessionId: session.sessionId,
|
|
151
|
+
sessionKey: session.sessionKey,
|
|
152
|
+
ovSessionId: session.ovSessionId,
|
|
153
|
+
agentId: session.agentId,
|
|
154
|
+
source: "memory_recall",
|
|
155
|
+
operationType: "semantic_find",
|
|
156
|
+
resourceTypes: traceResourceTypes.length > 0 ? traceResourceTypes : ["resource"],
|
|
157
|
+
trigger: deps.boundTraceQuery(query, deps.cfg.traceRecallQueryMaxChars),
|
|
158
|
+
searches: memoryRecallSearches.length > 0 ? memoryRecallSearches : [{
|
|
159
|
+
resourceType: "resource",
|
|
160
|
+
targetUriInput: targetUri,
|
|
161
|
+
targetUriResolved: targetUri ?? "viking://resources",
|
|
162
|
+
limit: requestLimit,
|
|
163
|
+
scoreThreshold,
|
|
164
|
+
durationMs: 0,
|
|
165
|
+
total: result.total ?? leafOnly.length,
|
|
166
|
+
results: candidateTraceResults,
|
|
167
|
+
}],
|
|
168
|
+
selected: memories.map((item) => ({
|
|
169
|
+
uri: item.uri,
|
|
170
|
+
resourceType: deps.inferRecallResourceType(item.uri),
|
|
171
|
+
category: item.category,
|
|
172
|
+
score: item.score,
|
|
173
|
+
abstractPreview: deps.previewText(item.abstract || item.overview, deps.cfg.traceRecallPreviewChars),
|
|
174
|
+
injected: injectedUris.has(item.uri),
|
|
175
|
+
displayed: injectedUris.has(item.uri),
|
|
176
|
+
})),
|
|
177
|
+
stats: {
|
|
178
|
+
candidateCount: leafOnly.length,
|
|
179
|
+
selectedCount: memories.length,
|
|
180
|
+
injectedCount: injectedUris.size,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
if (memories.length === 0) {
|
|
185
|
+
await recordMemoryRecallTrace(new Set());
|
|
186
|
+
return {
|
|
187
|
+
content: [{ type: "text", text: "No relevant OpenViking memories found." }],
|
|
188
|
+
details: { count: 0, total: result.total ?? 0, scoreThreshold },
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const { lines: memoryLines } = await deps.buildMemoryLinesWithBudget(memories, (uri) => recallClient.read(uri, session.agentId), {
|
|
192
|
+
recallPreferAbstract: false,
|
|
193
|
+
recallMaxInjectedChars: queryConfig.maxInjectedChars,
|
|
194
|
+
});
|
|
195
|
+
if (memoryLines.length === 0) {
|
|
196
|
+
await recordMemoryRecallTrace(new Set());
|
|
197
|
+
return {
|
|
198
|
+
content: [
|
|
199
|
+
{
|
|
200
|
+
type: "text",
|
|
201
|
+
text: `No complete OpenViking memories fit recallMaxInjectedChars=${queryConfig.maxInjectedChars}.`,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
details: {
|
|
205
|
+
count: 0,
|
|
206
|
+
memories,
|
|
207
|
+
total: result.total ?? memories.length,
|
|
208
|
+
scoreThreshold,
|
|
209
|
+
requestLimit,
|
|
210
|
+
recallMaxInjectedChars: queryConfig.maxInjectedChars,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
await recordMemoryRecallTrace(new Set(memories.slice(0, memoryLines.length).map((item) => item.uri)));
|
|
215
|
+
return {
|
|
216
|
+
content: [
|
|
217
|
+
{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: `Found ${memoryLines.length} memories:\n\n${memoryLines.join("\n")}`,
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
details: {
|
|
223
|
+
count: memoryLines.length,
|
|
224
|
+
memories,
|
|
225
|
+
total: result.total ?? memories.length,
|
|
226
|
+
scoreThreshold,
|
|
227
|
+
requestLimit,
|
|
228
|
+
recallMaxInjectedChars: queryConfig.maxInjectedChars,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
}), { name: "memory_recall" });
|
|
233
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { clampScore, postProcessMemories } from "../memory-ranking.js";
|
|
3
|
+
import { isMemoryUri } from "../routing/memory-uri.js";
|
|
4
|
+
function totalCommitMemories(r) {
|
|
5
|
+
const m = r.memories_extracted;
|
|
6
|
+
if (!m || typeof m !== "object")
|
|
7
|
+
return 0;
|
|
8
|
+
return Object.values(m).reduce((sum, n) => sum + (n ?? 0), 0);
|
|
9
|
+
}
|
|
10
|
+
export function registerOpenVikingMemoryTools(deps) {
|
|
11
|
+
deps.registerTool((ctx) => ({
|
|
12
|
+
name: "memory_store",
|
|
13
|
+
label: "Memory Store (OpenViking)",
|
|
14
|
+
description: "Store text in OpenViking memory pipeline by writing to a session and running memory extraction.",
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
text: Type.String({ description: "Information to store as memory source text" }),
|
|
17
|
+
role: Type.Optional(Type.String({ description: "Session role, default user" })),
|
|
18
|
+
sessionId: Type.Optional(Type.String({ description: "Existing OpenViking session ID" })),
|
|
19
|
+
}),
|
|
20
|
+
async execute(_toolCallId, params) {
|
|
21
|
+
if (deps.isBypassedSession(ctx)) {
|
|
22
|
+
return deps.makeBypassedToolResult("memory_store");
|
|
23
|
+
}
|
|
24
|
+
const session = deps.resolvePluginSessionRouting(ctx);
|
|
25
|
+
const { text } = params;
|
|
26
|
+
const role = typeof params.role === "string"
|
|
27
|
+
? params.role
|
|
28
|
+
: "user";
|
|
29
|
+
const explicitSessionId = typeof params.sessionId === "string" &&
|
|
30
|
+
params.sessionId.trim()
|
|
31
|
+
? deps.normalizeSessionId(params.sessionId)
|
|
32
|
+
: undefined;
|
|
33
|
+
if (deps.logFindRequests) {
|
|
34
|
+
deps.logger.info?.(`openviking: memory_store invoked (textLength=${text?.length ?? 0}, sessionId=${explicitSessionId ?? "auto"})`);
|
|
35
|
+
}
|
|
36
|
+
let sessionId = explicitSessionId;
|
|
37
|
+
let usedTempSession = false;
|
|
38
|
+
try {
|
|
39
|
+
const client = await deps.getClient();
|
|
40
|
+
if (!sessionId) {
|
|
41
|
+
sessionId = deps.createTempSessionId();
|
|
42
|
+
usedTempSession = true;
|
|
43
|
+
}
|
|
44
|
+
const roleId = role === "user" ? deps.toRoleId(deps.extractSenderId(ctx)) : undefined;
|
|
45
|
+
await client.addSessionMessage(sessionId, role, [{ type: "text", text }], session.agentId, undefined, roleId);
|
|
46
|
+
const commitResult = await client.commitSession(sessionId, {
|
|
47
|
+
wait: true,
|
|
48
|
+
agentId: session.agentId,
|
|
49
|
+
keepRecentCount: 0,
|
|
50
|
+
});
|
|
51
|
+
const memoriesCount = totalCommitMemories(commitResult);
|
|
52
|
+
if (commitResult.status === "failed") {
|
|
53
|
+
deps.logger.warn(`openviking: memory_store commit failed (sessionId=${sessionId}): ${commitResult.error ?? "unknown"}`);
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: `Memory extraction failed for session ${sessionId}: ${commitResult.error ?? "unknown"}` }],
|
|
56
|
+
details: {
|
|
57
|
+
action: "failed",
|
|
58
|
+
sessionId,
|
|
59
|
+
status: "failed",
|
|
60
|
+
error: commitResult.error,
|
|
61
|
+
usedTempSession,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (commitResult.status === "timeout") {
|
|
66
|
+
deps.logger.warn(`openviking: memory_store commit timed out (sessionId=${sessionId}), task_id=${commitResult.task_id ?? "none"}. Memories may still be extracting in background.`);
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: `Memory extraction timed out for session ${sessionId}. It may still complete in the background (task_id=${commitResult.task_id ?? "none"}).` }],
|
|
69
|
+
details: {
|
|
70
|
+
action: "timeout",
|
|
71
|
+
sessionId,
|
|
72
|
+
status: "timeout",
|
|
73
|
+
taskId: commitResult.task_id,
|
|
74
|
+
usedTempSession,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (memoriesCount === 0) {
|
|
79
|
+
deps.logger.warn(`openviking: memory_store committed but 0 memories extracted (sessionId=${sessionId}). ` +
|
|
80
|
+
"Check OpenViking server logs for embedding/extract errors (e.g. 401 API key, or extraction pipeline).");
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
deps.logger.info?.(`openviking: memory_store committed, memories=${memoriesCount}`);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: `Stored in OpenViking session ${sessionId} and committed ${memoriesCount} memories.`,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
details: {
|
|
93
|
+
action: "stored",
|
|
94
|
+
sessionId,
|
|
95
|
+
memoriesCount,
|
|
96
|
+
status: commitResult.status,
|
|
97
|
+
archived: commitResult.archived ?? false,
|
|
98
|
+
usedTempSession,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
deps.logger.warn(`openviking: memory_store failed: ${String(err)}`);
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
}), { name: "memory_store" });
|
|
108
|
+
deps.registerTool((ctx) => ({
|
|
109
|
+
name: "memory_forget",
|
|
110
|
+
label: "Memory Forget (OpenViking)",
|
|
111
|
+
description: "Forget memory by URI, or search then delete when a strong single match is found.",
|
|
112
|
+
parameters: Type.Object({
|
|
113
|
+
uri: Type.Optional(Type.String({ description: "Exact memory URI to delete" })),
|
|
114
|
+
query: Type.Optional(Type.String({ description: "Search query to find memory URI" })),
|
|
115
|
+
targetUri: Type.Optional(Type.String({ description: "Search scope URI (default: plugin config)" })),
|
|
116
|
+
limit: Type.Optional(Type.Number({ description: "Search limit (default: 5)" })),
|
|
117
|
+
scoreThreshold: Type.Optional(Type.Number({ description: "Minimum score (0-1, default: plugin config)" })),
|
|
118
|
+
}),
|
|
119
|
+
async execute(_toolCallId, params) {
|
|
120
|
+
if (deps.isBypassedSession(ctx)) {
|
|
121
|
+
return deps.makeBypassedToolResult("memory_forget");
|
|
122
|
+
}
|
|
123
|
+
const session = deps.resolvePluginSessionRouting(ctx);
|
|
124
|
+
const client = await deps.getClient();
|
|
125
|
+
const uri = params.uri;
|
|
126
|
+
if (uri) {
|
|
127
|
+
if (!isMemoryUri(uri)) {
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: "text", text: `Refusing to delete non-memory URI: ${uri}` }],
|
|
130
|
+
details: { action: "rejected", uri },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
await client.deleteUri(uri, session.agentId);
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: `Forgotten: ${uri}` }],
|
|
136
|
+
details: { action: "deleted", uri },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const query = params.query;
|
|
140
|
+
if (!query) {
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: "text", text: "Provide uri or query." }],
|
|
143
|
+
details: { error: "missing_param" },
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const limit = typeof params.limit === "number"
|
|
147
|
+
? Math.max(1, Math.floor(params.limit))
|
|
148
|
+
: 5;
|
|
149
|
+
const scoreThreshold = typeof params.scoreThreshold === "number"
|
|
150
|
+
? Math.max(0, Math.min(1, params.scoreThreshold))
|
|
151
|
+
: deps.defaultRecallScoreThreshold;
|
|
152
|
+
const targetUri = typeof params.targetUri === "string"
|
|
153
|
+
? params.targetUri
|
|
154
|
+
: deps.defaultTargetUri;
|
|
155
|
+
const requestLimit = Math.max(limit * 4, 20);
|
|
156
|
+
const result = await client.find(query, {
|
|
157
|
+
targetUri,
|
|
158
|
+
limit: requestLimit,
|
|
159
|
+
scoreThreshold: 0,
|
|
160
|
+
}, session.agentId);
|
|
161
|
+
const candidates = postProcessMemories(result.memories ?? [], {
|
|
162
|
+
limit: requestLimit,
|
|
163
|
+
scoreThreshold,
|
|
164
|
+
leafOnly: true,
|
|
165
|
+
}).filter((item) => isMemoryUri(item.uri));
|
|
166
|
+
if (candidates.length === 0) {
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: "No matching leaf memory candidates found. Try a more specific query.",
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
details: { action: "none", scoreThreshold },
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const top = candidates[0];
|
|
178
|
+
if (candidates.length === 1 && clampScore(top.score) >= 0.85) {
|
|
179
|
+
await client.deleteUri(top.uri, session.agentId);
|
|
180
|
+
return {
|
|
181
|
+
content: [{ type: "text", text: `Forgotten: ${top.uri}` }],
|
|
182
|
+
details: { action: "deleted", uri: top.uri, score: top.score ?? 0 },
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const list = candidates
|
|
186
|
+
.map((item) => `- ${item.uri} (${(clampScore(item.score) * 100).toFixed(0)}%)`)
|
|
187
|
+
.join("\n");
|
|
188
|
+
return {
|
|
189
|
+
content: [
|
|
190
|
+
{
|
|
191
|
+
type: "text",
|
|
192
|
+
text: `Found ${candidates.length} candidates. Specify uri:\n${list}`,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
details: { action: "candidates", candidates, scoreThreshold, requestLimit },
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
}), { name: "memory_forget" });
|
|
199
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
function tokenizeCommandArgs(args) {
|
|
2
|
+
const tokens = [];
|
|
3
|
+
let current = "";
|
|
4
|
+
let quote = null;
|
|
5
|
+
let escaping = false;
|
|
6
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
7
|
+
const ch = args[i];
|
|
8
|
+
const next = args[i + 1];
|
|
9
|
+
if (escaping) {
|
|
10
|
+
current += ch;
|
|
11
|
+
escaping = false;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (ch === "\\") {
|
|
15
|
+
const shouldEscape = quote === '"'
|
|
16
|
+
? next === '"' || next === "\\"
|
|
17
|
+
: !quote && Boolean(next && (/\s/.test(next) || next === '"' || next === "'"));
|
|
18
|
+
if (shouldEscape) {
|
|
19
|
+
escaping = true;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
current += ch;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if ((ch === '"' || ch === "'") && (!quote || quote === ch)) {
|
|
26
|
+
quote = quote ? null : ch;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!quote && /\s/.test(ch)) {
|
|
30
|
+
if (current) {
|
|
31
|
+
tokens.push(current);
|
|
32
|
+
current = "";
|
|
33
|
+
}
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
current += ch;
|
|
37
|
+
}
|
|
38
|
+
if (escaping) {
|
|
39
|
+
current += "\\";
|
|
40
|
+
}
|
|
41
|
+
if (quote) {
|
|
42
|
+
throw new Error("Unterminated quoted argument");
|
|
43
|
+
}
|
|
44
|
+
if (current) {
|
|
45
|
+
tokens.push(current);
|
|
46
|
+
}
|
|
47
|
+
return tokens;
|
|
48
|
+
}
|
|
49
|
+
function parseFlagArgs(args) {
|
|
50
|
+
const tokens = tokenizeCommandArgs(args);
|
|
51
|
+
const positionals = [];
|
|
52
|
+
const flags = new Map();
|
|
53
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
54
|
+
const token = tokens[i];
|
|
55
|
+
if (!token.startsWith("--")) {
|
|
56
|
+
positionals.push(token);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const raw = token.slice(2);
|
|
60
|
+
if (!raw) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const eqIndex = raw.indexOf("=");
|
|
64
|
+
if (eqIndex >= 0) {
|
|
65
|
+
flags.set(raw.slice(0, eqIndex), raw.slice(eqIndex + 1));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const next = tokens[i + 1];
|
|
69
|
+
if (next && !next.startsWith("--")) {
|
|
70
|
+
flags.set(raw, next);
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
flags.set(raw, true);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { positionals, flags };
|
|
78
|
+
}
|
|
79
|
+
function getStringFlag(flags, name) {
|
|
80
|
+
const value = flags.get(name);
|
|
81
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
82
|
+
}
|
|
83
|
+
function getNumberFlag(flags, name) {
|
|
84
|
+
const raw = getStringFlag(flags, name);
|
|
85
|
+
if (!raw) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const value = Number(raw);
|
|
89
|
+
if (!Number.isFinite(value)) {
|
|
90
|
+
throw new Error(`--${name} must be a number`);
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
function getOptionalBoolFlag(flags, name) {
|
|
95
|
+
const value = flags.get(name);
|
|
96
|
+
if (value === undefined)
|
|
97
|
+
return undefined;
|
|
98
|
+
if (value === true)
|
|
99
|
+
return true;
|
|
100
|
+
if (value === false)
|
|
101
|
+
return false;
|
|
102
|
+
const normalized = value.trim().toLowerCase();
|
|
103
|
+
if (["1", "true", "yes", "on"].includes(normalized))
|
|
104
|
+
return true;
|
|
105
|
+
if (["0", "false", "no", "off"].includes(normalized))
|
|
106
|
+
return false;
|
|
107
|
+
throw new Error(`--${name} must be a boolean`);
|
|
108
|
+
}
|
|
109
|
+
function parseNumberMapFlag(flags, name) {
|
|
110
|
+
const raw = getStringFlag(flags, name);
|
|
111
|
+
if (!raw)
|
|
112
|
+
return undefined;
|
|
113
|
+
const result = {};
|
|
114
|
+
for (const item of raw.split(",")) {
|
|
115
|
+
const [key, rawValue, ...rest] = item.split("=");
|
|
116
|
+
const trimmedKey = key?.trim();
|
|
117
|
+
const trimmedValue = rawValue?.trim();
|
|
118
|
+
if (!trimmedKey || !trimmedValue || rest.length > 0) {
|
|
119
|
+
throw new Error(`--${name} must use key=value pairs separated by commas`);
|
|
120
|
+
}
|
|
121
|
+
const value = Number(trimmedValue);
|
|
122
|
+
if (!Number.isFinite(value)) {
|
|
123
|
+
throw new Error(`--${name} value for ${trimmedKey} must be a number`);
|
|
124
|
+
}
|
|
125
|
+
result[trimmedKey] = value;
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
function parseQueryConfigPatch(flags) {
|
|
130
|
+
const patch = {};
|
|
131
|
+
const numberFields = [
|
|
132
|
+
["recallLimit", "recallLimit"],
|
|
133
|
+
["candidateLimit", "candidateLimit"],
|
|
134
|
+
["candidateMultiplier", "candidateMultiplier"],
|
|
135
|
+
["scoreThreshold", "scoreThreshold"],
|
|
136
|
+
["maxInjectedChars", "maxInjectedChars"],
|
|
137
|
+
["ovSearchLimit", "ovSearchLimit"],
|
|
138
|
+
];
|
|
139
|
+
for (const [flag, field] of numberFields) {
|
|
140
|
+
if (flags.has(flag)) {
|
|
141
|
+
patch[field] = getNumberFlag(flags, flag);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const resourceTypes = getStringFlag(flags, "resourceTypes");
|
|
145
|
+
if (resourceTypes)
|
|
146
|
+
patch.resourceTypes = resourceTypes;
|
|
147
|
+
const targetUri = getStringFlag(flags, "targetUri");
|
|
148
|
+
if (targetUri)
|
|
149
|
+
patch.targetUri = targetUri;
|
|
150
|
+
const recallPreferAbstract = getOptionalBoolFlag(flags, "recallPreferAbstract");
|
|
151
|
+
if (recallPreferAbstract !== undefined)
|
|
152
|
+
patch.recallPreferAbstract = recallPreferAbstract;
|
|
153
|
+
const rankingWeights = parseNumberMapFlag(flags, "weight") ?? parseNumberMapFlag(flags, "rankingWeights");
|
|
154
|
+
if (rankingWeights)
|
|
155
|
+
patch.rankingWeights = rankingWeights;
|
|
156
|
+
const categoryWeights = parseNumberMapFlag(flags, "categoryWeight") ?? parseNumberMapFlag(flags, "categoryWeights");
|
|
157
|
+
if (categoryWeights)
|
|
158
|
+
patch.categoryWeights = categoryWeights;
|
|
159
|
+
const resourceTypeWeights = parseNumberMapFlag(flags, "resourceTypeWeight") ?? parseNumberMapFlag(flags, "resourceTypeWeights");
|
|
160
|
+
if (resourceTypeWeights)
|
|
161
|
+
patch.resourceTypeWeights = resourceTypeWeights;
|
|
162
|
+
return patch;
|
|
163
|
+
}
|
|
164
|
+
export function createOpenVikingQueryConfigCommandHandler(deps) {
|
|
165
|
+
return async (ctx) => {
|
|
166
|
+
const parsed = parseFlagArgs(ctx.args ?? "");
|
|
167
|
+
const action = parsed.positionals[0] ?? "get";
|
|
168
|
+
const session = deps.resolvePluginSessionRouting(ctx);
|
|
169
|
+
const scope = getStringFlag(parsed.flags, "scope") === "claw" ? "claw" : "session";
|
|
170
|
+
const queryCtx = deps.toQueryConfigContext(session);
|
|
171
|
+
if (action === "get") {
|
|
172
|
+
const effective = await deps.queryConfigStore.getEffective(queryCtx);
|
|
173
|
+
return { text: JSON.stringify({ scope, effective }, null, 2), details: { scope, effective } };
|
|
174
|
+
}
|
|
175
|
+
if (action === "set") {
|
|
176
|
+
const patch = parseQueryConfigPatch(parsed.flags);
|
|
177
|
+
const { params, warnings } = deps.normalizeRuntimeQueryParams(patch);
|
|
178
|
+
if (Object.keys(params).length === 0) {
|
|
179
|
+
throw new Error("No query config parameters provided for /ov-query-config set");
|
|
180
|
+
}
|
|
181
|
+
await deps.queryConfigStore.set(scope, queryCtx, params);
|
|
182
|
+
const effective = await deps.queryConfigStore.getEffective(queryCtx);
|
|
183
|
+
return {
|
|
184
|
+
text: `Updated OpenViking query config (${scope}).${warnings.length ? ` Warnings: ${warnings.join("; ")}` : ""}`,
|
|
185
|
+
details: { scope, params, warnings, effective },
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (action === "unset") {
|
|
189
|
+
const fields = parsed.positionals.slice(1);
|
|
190
|
+
if (fields.length === 0)
|
|
191
|
+
throw new Error("Usage: /ov-query-config unset <field...> [--scope session|claw]");
|
|
192
|
+
await deps.queryConfigStore.unset(scope, queryCtx, fields);
|
|
193
|
+
const effective = await deps.queryConfigStore.getEffective(queryCtx);
|
|
194
|
+
return { text: `Unset OpenViking query config fields (${scope}): ${fields.join(", ")}`, details: { scope, fields, effective } };
|
|
195
|
+
}
|
|
196
|
+
if (action === "reset") {
|
|
197
|
+
await deps.queryConfigStore.reset(scope, queryCtx);
|
|
198
|
+
const effective = await deps.queryConfigStore.getEffective(queryCtx);
|
|
199
|
+
return { text: `Reset OpenViking query config (${scope}).`, details: { scope, effective } };
|
|
200
|
+
}
|
|
201
|
+
throw new Error("Usage: /ov-query-config get|set|unset|reset [--scope session|claw] [--recallLimit N] [--candidateLimit N] [--scoreThreshold N] [--resourceTypes user,agent]");
|
|
202
|
+
};
|
|
203
|
+
}
|