@openviking/openclaw-plugin 2026.9.1 → 2026.9.4-1
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 +25 -4
- package/INSTALL-ZH.md +22 -4
- package/INSTALL.md +22 -4
- package/README.md +19 -15
- package/auto-recall.ts +126 -135
- package/client.ts +95 -0
- package/commands/setup.ts +22 -8
- package/config.ts +31 -16
- package/context-engine.ts +2 -2
- package/dist/auto-recall.js +113 -114
- package/dist/client.js +54 -0
- package/dist/commands/setup.js +15 -6
- package/dist/config.js +18 -11
- package/dist/plugin/openviking-memory-tools.js +1 -1
- package/dist/plugin/openviking-session-routing-runtime.js +2 -2
- package/dist/routing/identity-routing.js +5 -5
- package/dist/services/context-lifecycle-service.js +4 -4
- package/dist/shared/recall-compress-core.mjs +188 -0
- package/dist/shared/recall-core.mjs +645 -0
- package/install-manifest.json +1 -0
- package/openclaw.plugin.json +9 -8
- package/package.json +2 -1
- package/plugin/openviking-client-runtime.ts +1 -1
- package/plugin/openviking-memory-tools.ts +1 -1
- package/plugin/openviking-session-routing-runtime.ts +2 -2
- package/routing/identity-routing.ts +8 -8
- package/services/context-lifecycle-service.ts +4 -4
- package/services/setup/setup-flow.ts +8 -6
- package/shared/recall-compress-core.mjs +195 -0
- package/shared/recall-core.d.mts +39 -0
- package/shared/recall-core.mjs +678 -0
- package/skills/install-openviking-memory/SKILL.md +33 -10
- package/skills/openviking-context-database/SKILL.md +2 -2
package/openclaw.plugin.json
CHANGED
|
@@ -80,14 +80,14 @@
|
|
|
80
80
|
"help": "HTTP URL when mode is remote (or ${OPENVIKING_BASE_URL})"
|
|
81
81
|
},
|
|
82
82
|
"peer_role": {
|
|
83
|
-
"label": "
|
|
84
|
-
"placeholder": "
|
|
85
|
-
"help": "
|
|
83
|
+
"label": "Memory Scope (peer_role)",
|
|
84
|
+
"placeholder": "none",
|
|
85
|
+
"help": "Where peer-scoped memories are stored. none (default): viking://user/<user_id>/memories. assistant: viking://user/<user_id>/peers/<assistant_id>/memories. sender: viking://user/<user_id>/peers/<sender_id>/memories. Legacy person is accepted as sender."
|
|
86
86
|
},
|
|
87
87
|
"peer_prefix": {
|
|
88
88
|
"label": "Peer Prefix",
|
|
89
89
|
"placeholder": "optional-prefix",
|
|
90
|
-
"help": "
|
|
90
|
+
"help": "Only used when Memory Scope is assistant. Prefix added to the peer id derived from the OpenClaw agent id."
|
|
91
91
|
},
|
|
92
92
|
"apiKey": {
|
|
93
93
|
"label": "OpenViking API Key",
|
|
@@ -144,9 +144,9 @@
|
|
|
144
144
|
},
|
|
145
145
|
"autoRecallTimeoutMs": {
|
|
146
146
|
"label": "Auto-Recall Timeout (ms)",
|
|
147
|
-
"placeholder": "
|
|
147
|
+
"placeholder": "15000",
|
|
148
148
|
"advanced": true,
|
|
149
|
-
"help": "Outer time budget for the
|
|
149
|
+
"help": "Outer time budget for the server-assembled context search, including session-aware query expansion."
|
|
150
150
|
},
|
|
151
151
|
"recallResources": {
|
|
152
152
|
"label": "Recall Resources",
|
|
@@ -279,10 +279,11 @@
|
|
|
279
279
|
"enum": [
|
|
280
280
|
"none",
|
|
281
281
|
"assistant",
|
|
282
|
+
"sender",
|
|
282
283
|
"person"
|
|
283
284
|
],
|
|
284
|
-
"default": "
|
|
285
|
-
"description": "
|
|
285
|
+
"default": "none",
|
|
286
|
+
"description": "Memory scope. none: shared user memory. assistant: peer memory keyed by OpenClaw assistant id. sender: peer memory keyed by sender id. person is a legacy alias for sender. Also controls which session messages include peer_id."
|
|
286
287
|
},
|
|
287
288
|
"peer_prefix": {
|
|
288
289
|
"type": "string",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openviking/openclaw-plugin",
|
|
3
|
-
"version": "2026.9.1",
|
|
3
|
+
"version": "2026.9.4-1",
|
|
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",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"config/feature-gates.json",
|
|
27
27
|
"registries/",
|
|
28
28
|
"routing/",
|
|
29
|
+
"shared/",
|
|
29
30
|
"plugin/",
|
|
30
31
|
"services/",
|
|
31
32
|
"!vitest.config.ts",
|
|
@@ -114,7 +114,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
|
|
|
114
114
|
const peerId = resolveOpenVikingMessagePeerId({
|
|
115
115
|
peerRole: deps.peerRole,
|
|
116
116
|
role,
|
|
117
|
-
|
|
117
|
+
senderPeerId: session.actorPeerId,
|
|
118
118
|
assistantPeerId: session.actorPeerId,
|
|
119
119
|
});
|
|
120
120
|
await client.addSessionMessage(
|
|
@@ -34,7 +34,7 @@ export function createOpenVikingSessionRoutingRuntime(options: {
|
|
|
34
34
|
logFindRequests: boolean;
|
|
35
35
|
logger: Logger;
|
|
36
36
|
}) {
|
|
37
|
-
const peerRole = options.peerRole ?? "
|
|
37
|
+
const peerRole = options.peerRole ?? "none";
|
|
38
38
|
const sessionAgentResolver = createSessionAgentResolver(options.peerPrefix);
|
|
39
39
|
|
|
40
40
|
const rememberSessionAgentId = (ctx: SessionAgentLookup) => {
|
|
@@ -97,7 +97,7 @@ export function createOpenVikingSessionRoutingRuntime(options: {
|
|
|
97
97
|
agentId,
|
|
98
98
|
actorPeerId: resolveOpenVikingActorPeerId({
|
|
99
99
|
peerRole,
|
|
100
|
-
|
|
100
|
+
senderPeerId: sanitizeOpenVikingPeerId(ctx?.requesterSenderId ?? ctx?.senderId),
|
|
101
101
|
assistantPeerId: agentId,
|
|
102
102
|
}),
|
|
103
103
|
};
|
|
@@ -15,7 +15,7 @@ export type SessionAgentLookup = {
|
|
|
15
15
|
ovSessionId?: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
export type OpenVikingPeerRole = "none" | "assistant" | "
|
|
18
|
+
export type OpenVikingPeerRole = "none" | "assistant" | "sender";
|
|
19
19
|
|
|
20
20
|
export function sanitizeOpenVikingPeerId(raw?: string): string | undefined {
|
|
21
21
|
const normalized = raw
|
|
@@ -29,11 +29,11 @@ export function sanitizeOpenVikingPeerId(raw?: string): string | undefined {
|
|
|
29
29
|
export function resolveOpenVikingMessagePeerId(params: {
|
|
30
30
|
peerRole: OpenVikingPeerRole;
|
|
31
31
|
role?: string;
|
|
32
|
-
|
|
32
|
+
senderPeerId?: string;
|
|
33
33
|
assistantPeerId?: string;
|
|
34
34
|
}): string | undefined {
|
|
35
|
-
if (params.peerRole === "
|
|
36
|
-
return params.
|
|
35
|
+
if (params.peerRole === "sender" && params.role === "user") {
|
|
36
|
+
return params.senderPeerId;
|
|
37
37
|
}
|
|
38
38
|
if (params.peerRole === "assistant" && params.role === "assistant") {
|
|
39
39
|
return params.assistantPeerId;
|
|
@@ -43,15 +43,15 @@ export function resolveOpenVikingMessagePeerId(params: {
|
|
|
43
43
|
|
|
44
44
|
export function resolveOpenVikingActorPeerId(params: {
|
|
45
45
|
peerRole: OpenVikingPeerRole;
|
|
46
|
-
|
|
46
|
+
senderPeerId?: string;
|
|
47
47
|
assistantPeerId?: string;
|
|
48
48
|
}): string | undefined {
|
|
49
49
|
const actorPeerId = resolveOpenVikingMessagePeerId({
|
|
50
50
|
...params,
|
|
51
|
-
role: params.peerRole === "
|
|
51
|
+
role: params.peerRole === "sender" ? "user" : "assistant",
|
|
52
52
|
});
|
|
53
|
-
if (params.peerRole === "
|
|
54
|
-
throw new Error("openviking: peer_role=
|
|
53
|
+
if (params.peerRole === "sender" && !actorPeerId) {
|
|
54
|
+
throw new Error("openviking: peer_role=sender requires a sender identity");
|
|
55
55
|
}
|
|
56
56
|
return actorPeerId;
|
|
57
57
|
}
|
|
@@ -497,8 +497,8 @@ export async function assembleOpenVikingSession({
|
|
|
497
497
|
const routingRef = sessionId ?? sessionKey ?? ovSessionId;
|
|
498
498
|
const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
|
|
499
499
|
const actorPeerId = resolveOpenVikingActorPeerId({
|
|
500
|
-
peerRole: cfg.peer_role ?? "
|
|
501
|
-
|
|
500
|
+
peerRole: cfg.peer_role ?? "none",
|
|
501
|
+
senderPeerId: sanitizeOpenVikingPeerId(sender.senderId),
|
|
502
502
|
assistantPeerId: agentId,
|
|
503
503
|
});
|
|
504
504
|
const queryConfig = await queryConfigStore?.getEffective({
|
|
@@ -910,9 +910,9 @@ export async function afterTurnOpenVikingSession({
|
|
|
910
910
|
undefined,
|
|
911
911
|
createdAt,
|
|
912
912
|
resolveOpenVikingMessagePeerId({
|
|
913
|
-
peerRole: cfg.peer_role ?? "
|
|
913
|
+
peerRole: cfg.peer_role ?? "none",
|
|
914
914
|
role: msg.role,
|
|
915
|
-
|
|
915
|
+
senderPeerId: senderRoleId,
|
|
916
916
|
assistantPeerId: agentId,
|
|
917
917
|
}),
|
|
918
918
|
);
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
|
|
14
14
|
export type { SetupIO, SlotActivationResult };
|
|
15
15
|
|
|
16
|
+
type SetupPeerRole = "none" | "assistant" | "sender" | "person";
|
|
17
|
+
|
|
16
18
|
export type VersionCompatibility = "compatible" | "server_too_old" | "server_too_new" | "unknown";
|
|
17
19
|
|
|
18
20
|
export type HealthResult = {
|
|
@@ -38,7 +40,7 @@ export type SetupResult = {
|
|
|
38
40
|
mode: string;
|
|
39
41
|
baseUrl: string;
|
|
40
42
|
apiKey?: string;
|
|
41
|
-
peer_role?:
|
|
43
|
+
peer_role?: SetupPeerRole;
|
|
42
44
|
peer_prefix?: string;
|
|
43
45
|
accountId?: string;
|
|
44
46
|
userId?: string;
|
|
@@ -57,7 +59,7 @@ export type StatusResult = {
|
|
|
57
59
|
mode: string;
|
|
58
60
|
baseUrl: string;
|
|
59
61
|
hasApiKey: boolean;
|
|
60
|
-
peer_role?:
|
|
62
|
+
peer_role?: SetupPeerRole;
|
|
61
63
|
peer_prefix?: string;
|
|
62
64
|
hasAccountId: boolean;
|
|
63
65
|
hasUserId: boolean;
|
|
@@ -71,7 +73,7 @@ export type SetupParams = {
|
|
|
71
73
|
baseUrl: string;
|
|
72
74
|
apiKey?: string;
|
|
73
75
|
headers?: OpenVikingRequestHeaders;
|
|
74
|
-
peerRole?:
|
|
76
|
+
peerRole?: SetupPeerRole;
|
|
75
77
|
peerPrefix?: string;
|
|
76
78
|
accountId?: string;
|
|
77
79
|
userId?: string;
|
|
@@ -85,7 +87,7 @@ export type InteractiveRemoteConfigParams = {
|
|
|
85
87
|
baseUrl: string;
|
|
86
88
|
apiKey?: string;
|
|
87
89
|
headers?: OpenVikingRequestHeaders;
|
|
88
|
-
peerRole?:
|
|
90
|
+
peerRole?: SetupPeerRole;
|
|
89
91
|
peerPrefix?: string;
|
|
90
92
|
accountId?: string;
|
|
91
93
|
userId?: string;
|
|
@@ -315,7 +317,7 @@ export function createOpenVikingSetupService({
|
|
|
315
317
|
baseUrl,
|
|
316
318
|
...(apiKey ? { apiKey: maskKey(apiKey) } : {}),
|
|
317
319
|
...(headers ? { headers } : {}),
|
|
318
|
-
...(existing.peer_role ? { peer_role: String(existing.peer_role) as
|
|
320
|
+
...(existing.peer_role ? { peer_role: String(existing.peer_role) as SetupPeerRole } : {}),
|
|
319
321
|
...(existing.peer_prefix ? { peer_prefix: String(existing.peer_prefix) } : {}),
|
|
320
322
|
...(existing.accountId ? { accountId: String(existing.accountId) } : {}),
|
|
321
323
|
...(existing.userId ? { userId: String(existing.userId) } : {}),
|
|
@@ -346,7 +348,7 @@ export function createOpenVikingSetupService({
|
|
|
346
348
|
mode: String(existing.mode ?? "remote"),
|
|
347
349
|
baseUrl,
|
|
348
350
|
hasApiKey: !!existing.apiKey,
|
|
349
|
-
...(existing.peer_role ? { peer_role: String(existing.peer_role) as
|
|
351
|
+
...(existing.peer_role ? { peer_role: String(existing.peer_role) as SetupPeerRole } : {}),
|
|
350
352
|
...(existing.peer_prefix ? { peer_prefix: String(existing.peer_prefix) } : {}),
|
|
351
353
|
hasAccountId: !!existing.accountId,
|
|
352
354
|
hasUserId: !!existing.userId,
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// GENERATED FROM examples/memory-plugin-shared/lib. DO NOT EDIT.
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
|
|
6
|
+
export const NO_RELEVANT_MEMORY = "NO_RELEVANT_MEMORY";
|
|
7
|
+
export const DIGEST_HEADER = "OpenViking memory digest:";
|
|
8
|
+
const COMPRESS_OK = "ok";
|
|
9
|
+
const COMPRESS_EMPTY = "empty";
|
|
10
|
+
const COMPRESS_FAILED = "failed";
|
|
11
|
+
|
|
12
|
+
// Medium-constraint prompt: state the goal and two structural floors, but no hard
|
|
13
|
+
// bullet-length contract. Hard per-bullet limits pin the digest to headline
|
|
14
|
+
// density; leaving it unconstrained lets small models rewrite long URIs into dead
|
|
15
|
+
// links, which the URI repair below cleans up.
|
|
16
|
+
export function buildRecallCompressionPrompt({ query, rendered, maxBullets = 6 }) {
|
|
17
|
+
return `You are a memory relevance compressor utility.
|
|
18
|
+
Do not use any tools. Do not investigate. Only transform the given text.
|
|
19
|
+
|
|
20
|
+
User query:
|
|
21
|
+
${query}
|
|
22
|
+
|
|
23
|
+
Retrieved OpenViking context fragments:
|
|
24
|
+
${rendered}
|
|
25
|
+
|
|
26
|
+
Write a memory digest for a coding agent about to answer that query. Keep the
|
|
27
|
+
concrete facts (paths, identifiers, decisions, constraints); drop pleasantries
|
|
28
|
+
and conversational filler.
|
|
29
|
+
|
|
30
|
+
Format rules:
|
|
31
|
+
- Group related facts by topic, one bullet per topic, at most ${maxBullets} bullets.
|
|
32
|
+
- Start every bullet with "- ".
|
|
33
|
+
- End every bullet with its source, copied verbatim from the fragments above:
|
|
34
|
+
"来源:viking://..." or "source: viking://...". Never edit, shorten, or invent a URI.
|
|
35
|
+
- Output the digest body only. No preamble, no closing remark.
|
|
36
|
+
|
|
37
|
+
If nothing above is relevant to the query, output exactly: ${NO_RELEVANT_MEMORY}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function editDistance(a, b) {
|
|
41
|
+
if (a === b) return 0;
|
|
42
|
+
const rows = a.length + 1;
|
|
43
|
+
const cols = b.length + 1;
|
|
44
|
+
let prev = Array.from({ length: cols }, (_, i) => i);
|
|
45
|
+
for (let i = 1; i < rows; i += 1) {
|
|
46
|
+
const cur = [i];
|
|
47
|
+
for (let j = 1; j < cols; j += 1) {
|
|
48
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
49
|
+
cur[j] = Math.min(cur[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
50
|
+
}
|
|
51
|
+
prev = cur;
|
|
52
|
+
}
|
|
53
|
+
return prev[cols - 1];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function nearestUri(candidate, validUris) {
|
|
57
|
+
let best = "";
|
|
58
|
+
let bestDistance = Infinity;
|
|
59
|
+
for (const uri of validUris) {
|
|
60
|
+
const distance = editDistance(candidate, uri);
|
|
61
|
+
if (distance < bestDistance) {
|
|
62
|
+
best = uri;
|
|
63
|
+
bestDistance = distance;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Only repair near-misses; an unrelated hallucination is dropped instead.
|
|
67
|
+
const tolerance = Math.max(4, Math.floor(candidate.length * 0.25));
|
|
68
|
+
return bestDistance <= tolerance ? best : "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Small models occasionally mangle long URIs. Snap every cited URI back onto the
|
|
73
|
+
* set the server actually returned, and drop bullets whose citation cannot be
|
|
74
|
+
* recovered so the digest never carries a dead link.
|
|
75
|
+
*/
|
|
76
|
+
export function repairDigestUris(digest, validUris = []) {
|
|
77
|
+
const text = String(digest || "");
|
|
78
|
+
if (!text) return "";
|
|
79
|
+
const valid = validUris.map((uri) => String(uri || "").trim()).filter(Boolean);
|
|
80
|
+
if (!valid.length) return text;
|
|
81
|
+
const validSet = new Set(valid);
|
|
82
|
+
|
|
83
|
+
const lines = [];
|
|
84
|
+
for (const line of text.split(/\r?\n/)) {
|
|
85
|
+
if (!line.trim().startsWith("- ")) {
|
|
86
|
+
lines.push(line);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
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);
|
|
93
|
+
if (nearest) return nearest;
|
|
94
|
+
dropped = true;
|
|
95
|
+
return uri;
|
|
96
|
+
});
|
|
97
|
+
if (!dropped) lines.push(repaired);
|
|
98
|
+
}
|
|
99
|
+
return lines.join("\n").trim();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function normalizeCompressedContext(raw, maxChars = 4000, maxBullets = 6) {
|
|
103
|
+
const text = String(raw || "").trim();
|
|
104
|
+
if (!text) return null;
|
|
105
|
+
if (text.toUpperCase() === NO_RELEVANT_MEMORY) return "";
|
|
106
|
+
const bullets = text.split(/\r?\n/)
|
|
107
|
+
.map((line) => line.trim())
|
|
108
|
+
.filter((line) => /^[-*]\s+/.test(line) && line.includes("viking://"))
|
|
109
|
+
.slice(0, Math.max(1, maxBullets))
|
|
110
|
+
.map((line) => `- ${line.replace(/^[-*]\s+/, "").slice(0, 500).trim()}`);
|
|
111
|
+
if (!bullets.length) return null;
|
|
112
|
+
return (`${DIGEST_HEADER}\n${bullets.join("\n")}`).slice(0, Math.max(100, maxChars));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function recallDigestCacheKey({
|
|
116
|
+
query = "",
|
|
117
|
+
rendered = "",
|
|
118
|
+
entries = [],
|
|
119
|
+
maxInputChars = 18000,
|
|
120
|
+
maxBullets = 6,
|
|
121
|
+
} = {}) {
|
|
122
|
+
const uris = entries.map((entry) => String(entry?.uri || "").trim()).filter(Boolean).sort();
|
|
123
|
+
const source = JSON.stringify({
|
|
124
|
+
version: 2,
|
|
125
|
+
query: String(query),
|
|
126
|
+
rendered: String(rendered).slice(0, maxInputChars),
|
|
127
|
+
uris,
|
|
128
|
+
maxInputChars,
|
|
129
|
+
maxBullets,
|
|
130
|
+
});
|
|
131
|
+
return createHash("sha256").update(source).digest("hex");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function readCache(path) {
|
|
135
|
+
if (!path) return null;
|
|
136
|
+
try { return JSON.parse(await readFile(path, "utf8")); } catch { return null; }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function writeCache(path, value) {
|
|
140
|
+
if (!path) return;
|
|
141
|
+
try {
|
|
142
|
+
await mkdir(dirname(path), { recursive: true });
|
|
143
|
+
const tmp = `${path}.tmp`;
|
|
144
|
+
await writeFile(tmp, JSON.stringify(value));
|
|
145
|
+
await rename(tmp, path);
|
|
146
|
+
} catch { /* best effort */ }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function compressRecallContext({
|
|
150
|
+
query,
|
|
151
|
+
rendered,
|
|
152
|
+
entries = [],
|
|
153
|
+
cfg = {},
|
|
154
|
+
runCompressor,
|
|
155
|
+
cachePath = "",
|
|
156
|
+
now = 0,
|
|
157
|
+
}) {
|
|
158
|
+
const input = String(rendered || "").trim();
|
|
159
|
+
if (!input) return { status: COMPRESS_EMPTY, context: "" };
|
|
160
|
+
const minChars = Math.max(0, Number(cfg.recallCompressMinInputChars ?? 1500));
|
|
161
|
+
if (input.length < minChars) return { status: COMPRESS_OK, context: input };
|
|
162
|
+
|
|
163
|
+
const maxInputChars = Math.max(1000, Number(cfg.recallCompressMaxInputChars || 18000));
|
|
164
|
+
const maxBullets = Math.max(1, Number(cfg.recallCompressMaxBullets || 6));
|
|
165
|
+
const key = recallDigestCacheKey({
|
|
166
|
+
query,
|
|
167
|
+
rendered: input,
|
|
168
|
+
entries,
|
|
169
|
+
maxInputChars,
|
|
170
|
+
maxBullets,
|
|
171
|
+
});
|
|
172
|
+
const cached = await readCache(cachePath);
|
|
173
|
+
if (cached?.key === key && typeof cached.digest === "string") {
|
|
174
|
+
return { status: COMPRESS_OK, context: cached.digest };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const prompt = buildRecallCompressionPrompt({
|
|
178
|
+
query,
|
|
179
|
+
rendered: input.slice(0, maxInputChars),
|
|
180
|
+
maxBullets,
|
|
181
|
+
});
|
|
182
|
+
const raw = await runCompressor(prompt);
|
|
183
|
+
const normalized = normalizeCompressedContext(raw, 4000, maxBullets);
|
|
184
|
+
if (normalized === null) return { status: COMPRESS_FAILED, context: "" };
|
|
185
|
+
if (!normalized) return { status: COMPRESS_EMPTY, context: "" };
|
|
186
|
+
|
|
187
|
+
const validUris = entries.map((entry) => entry?.uri).filter(Boolean);
|
|
188
|
+
const digest = repairDigestUris(normalized, validUris.length
|
|
189
|
+
? validUris
|
|
190
|
+
: (input.match(/viking:\/\/[^\s<>"']+/g) || []));
|
|
191
|
+
if (!digest) return { status: COMPRESS_FAILED, context: "" };
|
|
192
|
+
|
|
193
|
+
await writeCache(cachePath, { key, digest, updatedAt: now || 0 });
|
|
194
|
+
return { status: COMPRESS_OK, context: digest };
|
|
195
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type ContextSearchBody = {
|
|
2
|
+
query: string;
|
|
3
|
+
mode: "context";
|
|
4
|
+
purpose: "coding";
|
|
5
|
+
score_threshold: number;
|
|
6
|
+
quotas?: Record<string, number>;
|
|
7
|
+
max_tokens?: number;
|
|
8
|
+
peer_scope?: "actor";
|
|
9
|
+
session_id?: string;
|
|
10
|
+
query_expansion?: "off" | "auto";
|
|
11
|
+
dedup_turns?: number;
|
|
12
|
+
exclude_uris?: string[];
|
|
13
|
+
rewrite?: boolean | "auto";
|
|
14
|
+
rewrite_max_bullets?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type NormalizedContextEntry = {
|
|
18
|
+
uri: string;
|
|
19
|
+
category: string;
|
|
20
|
+
detail: string;
|
|
21
|
+
score: number;
|
|
22
|
+
text: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function buildContextSearchBody(
|
|
26
|
+
cfg?: Record<string, unknown>,
|
|
27
|
+
options?: {
|
|
28
|
+
sessionId?: string;
|
|
29
|
+
excludeUris?: string[];
|
|
30
|
+
localCompressorAvailable?: boolean;
|
|
31
|
+
},
|
|
32
|
+
): ContextSearchBody;
|
|
33
|
+
|
|
34
|
+
export function contextRequestTimeoutMs(
|
|
35
|
+
cfg?: Record<string, unknown>,
|
|
36
|
+
body?: Record<string, unknown>,
|
|
37
|
+
): number | undefined;
|
|
38
|
+
|
|
39
|
+
export function normalizeContextEntry(entry?: unknown): NormalizedContextEntry;
|