@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.
@@ -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 = line.replace(/viking:\/\/[^\s<>"')\]]+/g, (uri) => {
88
- if (validSet.has(uri))
89
- return uri;
90
- const nearest = nearestUri(uri, valid);
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
- return { status: COMPRESS_OK, context: cached.digest };
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 digest = repairDigestUris(normalized, validUris.length
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-1",
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
- export type RecallResourceType = "resource" | "session" | "user" | "agent";
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
- resourceTypes: RecallResourceType[];
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 = line.replace(/viking:\/\/[^\s<>"')\]]+/g, (uri) => {
91
- if (validSet.has(uri)) return uri;
92
- const nearest = nearestUri(uri, valid);
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) return { status: COMPRESS_OK, context: input };
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
- return { status: COMPRESS_OK, context: cached.digest };
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 digest = repairDigestUris(normalized, validUris.length
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 });