@openviking/openclaw-plugin 2026.9.8-1 → 2026.9.8-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/dist/recall-trace.js
CHANGED
|
@@ -1,63 +1,5 @@
|
|
|
1
1
|
import { appendFile, mkdir, readFile, readdir, unlink } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
const ALLOWED_RESOURCE_TYPES = ["resource", "user", "agent"];
|
|
4
|
-
const DEFAULT_RESOURCE_TYPES = ["user", "agent"];
|
|
5
|
-
function toResourceTypeEntries(value) {
|
|
6
|
-
if (Array.isArray(value)) {
|
|
7
|
-
return value
|
|
8
|
-
.filter((entry) => typeof entry === "string")
|
|
9
|
-
.map((entry) => entry.trim())
|
|
10
|
-
.filter(Boolean);
|
|
11
|
-
}
|
|
12
|
-
if (typeof value === "string") {
|
|
13
|
-
return value
|
|
14
|
-
.split(/[,\n]/)
|
|
15
|
-
.map((entry) => entry.trim())
|
|
16
|
-
.filter(Boolean);
|
|
17
|
-
}
|
|
18
|
-
return [];
|
|
19
|
-
}
|
|
20
|
-
export function normalizeResourceTypes(value) {
|
|
21
|
-
const entries = toResourceTypeEntries(value);
|
|
22
|
-
if (entries.length === 0) {
|
|
23
|
-
return [...DEFAULT_RESOURCE_TYPES];
|
|
24
|
-
}
|
|
25
|
-
const seen = new Set();
|
|
26
|
-
const normalized = [];
|
|
27
|
-
const invalid = [];
|
|
28
|
-
for (const entry of entries) {
|
|
29
|
-
if (ALLOWED_RESOURCE_TYPES.includes(entry)) {
|
|
30
|
-
const typed = entry;
|
|
31
|
-
if (!seen.has(typed)) {
|
|
32
|
-
seen.add(typed);
|
|
33
|
-
normalized.push(typed);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
invalid.push(entry);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (invalid.length > 0) {
|
|
41
|
-
throw new Error(`invalid resourceTypes: ${invalid.join(", ")}`);
|
|
42
|
-
}
|
|
43
|
-
return normalized.length > 0 ? normalized : [...DEFAULT_RESOURCE_TYPES];
|
|
44
|
-
}
|
|
45
|
-
export function resolveRecallSearchPlan(resourceTypes, _ctx) {
|
|
46
|
-
const normalized = normalizeResourceTypes(resourceTypes);
|
|
47
|
-
const searches = [];
|
|
48
|
-
const skipped = [];
|
|
49
|
-
let addedMemorySearch = false;
|
|
50
|
-
for (const resourceType of normalized) {
|
|
51
|
-
if (resourceType === "resource") {
|
|
52
|
-
searches.push({ resourceType, contextType: "resource" });
|
|
53
|
-
}
|
|
54
|
-
else if ((resourceType === "user" || resourceType === "agent") && !addedMemorySearch) {
|
|
55
|
-
searches.push({ resourceType: "user", contextType: "memory" });
|
|
56
|
-
addedMemorySearch = true;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return { resourceTypes: normalized, searches, skipped };
|
|
60
|
-
}
|
|
61
3
|
export class RecallTraceMemoryStore {
|
|
62
4
|
maxEntries;
|
|
63
5
|
entries = [];
|
|
@@ -83,11 +83,21 @@ export function repairDigestUris(digest, validUris = []) {
|
|
|
83
83
|
lines.push(line);
|
|
84
84
|
continue;
|
|
85
85
|
}
|
|
86
|
+
let tokenized = line;
|
|
87
|
+
for (const uri of [...valid].sort((a, b) => b.length - a.length)) {
|
|
88
|
+
if (/\s/.test(uri))
|
|
89
|
+
tokenized = tokenized.split(uri).join(encodeURI(uri));
|
|
90
|
+
}
|
|
86
91
|
let dropped = false;
|
|
87
|
-
const repaired =
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
const repaired = tokenized.replace(/viking:\/\/[^\s<>"')\]]+/g, (uri) => {
|
|
93
|
+
let decoded = uri;
|
|
94
|
+
try {
|
|
95
|
+
decoded = decodeURI(uri);
|
|
96
|
+
}
|
|
97
|
+
catch { /* keep the original candidate */ }
|
|
98
|
+
if (validSet.has(decoded))
|
|
99
|
+
return decoded;
|
|
100
|
+
const nearest = nearestUri(decoded, valid);
|
|
91
101
|
if (nearest)
|
|
92
102
|
return nearest;
|
|
93
103
|
dropped = true;
|
|
@@ -146,13 +156,14 @@ async function writeCache(path, value) {
|
|
|
146
156
|
}
|
|
147
157
|
catch { /* best effort */ }
|
|
148
158
|
}
|
|
149
|
-
export async function compressRecallContext({ query, rendered, entries = [], cfg = {}, runCompressor, cachePath = "", now = 0, }) {
|
|
159
|
+
export async function compressRecallContext({ query, rendered, shortContext = rendered, entries = [], cfg = {}, runCompressor, cachePath = "", now = 0, }) {
|
|
150
160
|
const input = String(rendered || "").trim();
|
|
151
161
|
if (!input)
|
|
152
162
|
return { status: COMPRESS_EMPTY, context: "" };
|
|
153
163
|
const minChars = Math.max(0, Number(cfg.recallCompressMinInputChars ?? 1500));
|
|
154
|
-
if (input.length < minChars)
|
|
155
|
-
return { status: COMPRESS_OK, context: input };
|
|
164
|
+
if (input.length < minChars) {
|
|
165
|
+
return { status: COMPRESS_OK, context: String(shortContext ?? input).trim() };
|
|
166
|
+
}
|
|
156
167
|
const maxInputChars = Math.max(1000, Number(cfg.recallCompressMaxInputChars || 18000));
|
|
157
168
|
const maxBullets = Math.max(1, Number(cfg.recallCompressMaxBullets || 6));
|
|
158
169
|
const key = recallDigestCacheKey({
|
|
@@ -164,7 +175,9 @@ export async function compressRecallContext({ query, rendered, entries = [], cfg
|
|
|
164
175
|
});
|
|
165
176
|
const cached = await readCache(cachePath);
|
|
166
177
|
if (cached?.key === key && typeof cached.digest === "string") {
|
|
167
|
-
|
|
178
|
+
const digest = normalizeCompressedContext(cached.digest, 4000, maxBullets);
|
|
179
|
+
if (digest)
|
|
180
|
+
return { status: COMPRESS_OK, context: digest };
|
|
168
181
|
}
|
|
169
182
|
const prompt = buildRecallCompressionPrompt({
|
|
170
183
|
query,
|
|
@@ -178,9 +191,10 @@ export async function compressRecallContext({ query, rendered, entries = [], cfg
|
|
|
178
191
|
if (!normalized)
|
|
179
192
|
return { status: COMPRESS_EMPTY, context: "" };
|
|
180
193
|
const validUris = entries.map((entry) => entry?.uri).filter(Boolean);
|
|
181
|
-
const
|
|
194
|
+
const repaired = repairDigestUris(normalized, validUris.length
|
|
182
195
|
? validUris
|
|
183
196
|
: (input.match(/viking:\/\/[^\s<>"']+/g) || []));
|
|
197
|
+
const digest = normalizeCompressedContext(repaired, 4000, maxBullets);
|
|
184
198
|
if (!digest)
|
|
185
199
|
return { status: COMPRESS_FAILED, context: "" };
|
|
186
200
|
await writeCache(cachePath, { key, digest, updatedAt: now || 0 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openviking/openclaw-plugin",
|
|
3
|
-
"version": "2026.9.8-
|
|
3
|
+
"version": "2026.9.8-3",
|
|
4
4
|
"displayName": "OpenViking",
|
|
5
5
|
"description": "OpenClaw context-engine plugin — OpenViking AI memory, long-term memory, knowledge base, semantic search, RAG, context engine. 长期记忆 · 知识库 · 语义检索 · 上下文引擎",
|
|
6
6
|
"type": "module",
|
package/recall-trace.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appendFile, mkdir, readFile, readdir, unlink } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import type { RecallResourceType } from "./registries/recall-resource-types.js";
|
|
5
5
|
|
|
6
6
|
export type RecallTraceSource = "auto_recall" | "memory_recall" | "ov_search" | "ov_archive_search";
|
|
7
7
|
|
|
@@ -27,7 +27,9 @@ export type RecallTraceEntry = {
|
|
|
27
27
|
agentId?: string;
|
|
28
28
|
source: RecallTraceSource;
|
|
29
29
|
operationType: RecallTraceOperationType;
|
|
30
|
-
|
|
30
|
+
// "session" labels archive-grep traces over session history; recall targets
|
|
31
|
+
// stay in the registry's narrower union and never include it.
|
|
32
|
+
resourceTypes: Array<RecallResourceType | "session">;
|
|
31
33
|
trigger: {
|
|
32
34
|
rawUserTextPreview?: string;
|
|
33
35
|
query: string;
|
|
@@ -92,78 +94,6 @@ export type RecallTraceFlushResult = {
|
|
|
92
94
|
warnings: string[];
|
|
93
95
|
};
|
|
94
96
|
|
|
95
|
-
const ALLOWED_RESOURCE_TYPES: RecallResourceType[] = ["resource", "user", "agent"];
|
|
96
|
-
const DEFAULT_RESOURCE_TYPES: RecallResourceType[] = ["user", "agent"];
|
|
97
|
-
|
|
98
|
-
function toResourceTypeEntries(value: unknown): string[] {
|
|
99
|
-
if (Array.isArray(value)) {
|
|
100
|
-
return value
|
|
101
|
-
.filter((entry): entry is string => typeof entry === "string")
|
|
102
|
-
.map((entry) => entry.trim())
|
|
103
|
-
.filter(Boolean);
|
|
104
|
-
}
|
|
105
|
-
if (typeof value === "string") {
|
|
106
|
-
return value
|
|
107
|
-
.split(/[,\n]/)
|
|
108
|
-
.map((entry) => entry.trim())
|
|
109
|
-
.filter(Boolean);
|
|
110
|
-
}
|
|
111
|
-
return [];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function normalizeResourceTypes(value: unknown): RecallResourceType[] {
|
|
115
|
-
const entries = toResourceTypeEntries(value);
|
|
116
|
-
if (entries.length === 0) {
|
|
117
|
-
return [...DEFAULT_RESOURCE_TYPES];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const seen = new Set<RecallResourceType>();
|
|
121
|
-
const normalized: RecallResourceType[] = [];
|
|
122
|
-
const invalid: string[] = [];
|
|
123
|
-
for (const entry of entries) {
|
|
124
|
-
if ((ALLOWED_RESOURCE_TYPES as string[]).includes(entry)) {
|
|
125
|
-
const typed = entry as RecallResourceType;
|
|
126
|
-
if (!seen.has(typed)) {
|
|
127
|
-
seen.add(typed);
|
|
128
|
-
normalized.push(typed);
|
|
129
|
-
}
|
|
130
|
-
} else {
|
|
131
|
-
invalid.push(entry);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (invalid.length > 0) {
|
|
136
|
-
throw new Error(`invalid resourceTypes: ${invalid.join(", ")}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return normalized.length > 0 ? normalized : [...DEFAULT_RESOURCE_TYPES];
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function resolveRecallSearchPlan(
|
|
143
|
-
resourceTypes: unknown,
|
|
144
|
-
_ctx: { ovSessionId?: string; agentId?: string },
|
|
145
|
-
): {
|
|
146
|
-
resourceTypes: RecallResourceType[];
|
|
147
|
-
searches: Array<{ resourceType: RecallResourceType; targetUri?: string; contextType: "memory" | "resource" }>;
|
|
148
|
-
skipped: Array<{ resourceType: RecallResourceType; reason: "missing_session" }>;
|
|
149
|
-
} {
|
|
150
|
-
const normalized = normalizeResourceTypes(resourceTypes);
|
|
151
|
-
const searches: Array<{ resourceType: RecallResourceType; targetUri?: string; contextType: "memory" | "resource" }> = [];
|
|
152
|
-
const skipped: Array<{ resourceType: RecallResourceType; reason: "missing_session" }> = [];
|
|
153
|
-
let addedMemorySearch = false;
|
|
154
|
-
|
|
155
|
-
for (const resourceType of normalized) {
|
|
156
|
-
if (resourceType === "resource") {
|
|
157
|
-
searches.push({ resourceType, contextType: "resource" });
|
|
158
|
-
} else if ((resourceType === "user" || resourceType === "agent") && !addedMemorySearch) {
|
|
159
|
-
searches.push({ resourceType: "user", contextType: "memory" });
|
|
160
|
-
addedMemorySearch = true;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return { resourceTypes: normalized, searches, skipped };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
97
|
export class RecallTraceMemoryStore {
|
|
168
98
|
private readonly maxEntries: number;
|
|
169
99
|
private readonly entries: RecallTraceEntry[] = [];
|
|
@@ -183,7 +113,7 @@ export class RecallTraceMemoryStore {
|
|
|
183
113
|
const limit = Math.max(1, Math.floor(query.limit ?? 20));
|
|
184
114
|
const turn = query.turn ?? "latest";
|
|
185
115
|
const resourceTypes = query.resourceTypes && query.resourceTypes.length > 0
|
|
186
|
-
? new Set(query.resourceTypes)
|
|
116
|
+
? new Set<string>(query.resourceTypes)
|
|
187
117
|
: undefined;
|
|
188
118
|
|
|
189
119
|
const filtered = this.entries
|
|
@@ -86,10 +86,16 @@ export function repairDigestUris(digest, validUris = []) {
|
|
|
86
86
|
lines.push(line);
|
|
87
87
|
continue;
|
|
88
88
|
}
|
|
89
|
+
let tokenized = line;
|
|
90
|
+
for (const uri of [...valid].sort((a, b) => b.length - a.length)) {
|
|
91
|
+
if (/\s/.test(uri)) tokenized = tokenized.split(uri).join(encodeURI(uri));
|
|
92
|
+
}
|
|
89
93
|
let dropped = false;
|
|
90
|
-
const repaired =
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
const repaired = tokenized.replace(/viking:\/\/[^\s<>"')\]]+/g, (uri) => {
|
|
95
|
+
let decoded = uri;
|
|
96
|
+
try { decoded = decodeURI(uri); } catch { /* keep the original candidate */ }
|
|
97
|
+
if (validSet.has(decoded)) return decoded;
|
|
98
|
+
const nearest = nearestUri(decoded, valid);
|
|
93
99
|
if (nearest) return nearest;
|
|
94
100
|
dropped = true;
|
|
95
101
|
return uri;
|
|
@@ -149,6 +155,7 @@ async function writeCache(path, value) {
|
|
|
149
155
|
export async function compressRecallContext({
|
|
150
156
|
query,
|
|
151
157
|
rendered,
|
|
158
|
+
shortContext = rendered,
|
|
152
159
|
entries = [],
|
|
153
160
|
cfg = {},
|
|
154
161
|
runCompressor,
|
|
@@ -158,7 +165,9 @@ export async function compressRecallContext({
|
|
|
158
165
|
const input = String(rendered || "").trim();
|
|
159
166
|
if (!input) return { status: COMPRESS_EMPTY, context: "" };
|
|
160
167
|
const minChars = Math.max(0, Number(cfg.recallCompressMinInputChars ?? 1500));
|
|
161
|
-
if (input.length < minChars)
|
|
168
|
+
if (input.length < minChars) {
|
|
169
|
+
return { status: COMPRESS_OK, context: String(shortContext ?? input).trim() };
|
|
170
|
+
}
|
|
162
171
|
|
|
163
172
|
const maxInputChars = Math.max(1000, Number(cfg.recallCompressMaxInputChars || 18000));
|
|
164
173
|
const maxBullets = Math.max(1, Number(cfg.recallCompressMaxBullets || 6));
|
|
@@ -171,7 +180,8 @@ export async function compressRecallContext({
|
|
|
171
180
|
});
|
|
172
181
|
const cached = await readCache(cachePath);
|
|
173
182
|
if (cached?.key === key && typeof cached.digest === "string") {
|
|
174
|
-
|
|
183
|
+
const digest = normalizeCompressedContext(cached.digest, 4000, maxBullets);
|
|
184
|
+
if (digest) return { status: COMPRESS_OK, context: digest };
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
const prompt = buildRecallCompressionPrompt({
|
|
@@ -185,9 +195,10 @@ export async function compressRecallContext({
|
|
|
185
195
|
if (!normalized) return { status: COMPRESS_EMPTY, context: "" };
|
|
186
196
|
|
|
187
197
|
const validUris = entries.map((entry) => entry?.uri).filter(Boolean);
|
|
188
|
-
const
|
|
198
|
+
const repaired = repairDigestUris(normalized, validUris.length
|
|
189
199
|
? validUris
|
|
190
200
|
: (input.match(/viking:\/\/[^\s<>"']+/g) || []));
|
|
201
|
+
const digest = normalizeCompressedContext(repaired, 4000, maxBullets);
|
|
191
202
|
if (!digest) return { status: COMPRESS_FAILED, context: "" };
|
|
192
203
|
|
|
193
204
|
await writeCache(cachePath, { key, digest, updatedAt: now || 0 });
|