@openviking/openclaw-plugin 2026.9.4 → 2026.9.8-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 +16 -5
- package/client.ts +0 -91
- package/commands/setup.ts +41 -150
- package/config.ts +24 -11
- package/context-engine.ts +7 -2
- package/dist/client.js +0 -82
- package/dist/commands/setup.js +29 -127
- package/dist/config.js +13 -8
- package/dist/context-engine.js +5 -0
- package/dist/plugin/openclaw-runtime-compaction.js +20 -0
- 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 +17 -5
- package/dist/services/setup/probe-service.js +112 -0
- package/openclaw.plugin.json +8 -7
- package/package.json +1 -7
- package/plugin/openclaw-runtime-compaction.ts +41 -0
- 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 +19 -4
- package/services/setup/setup-flow.ts +8 -6
- package/skills/install-openviking-memory/SKILL.md +33 -10
- package/skills/openviking-context-database/SKILL.md +2 -2
|
@@ -243,8 +243,8 @@ export async function assembleOpenVikingSession({ sessionId, sessionKey, message
|
|
|
243
243
|
const routingRef = sessionId ?? sessionKey ?? ovSessionId;
|
|
244
244
|
const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
|
|
245
245
|
const actorPeerId = resolveOpenVikingActorPeerId({
|
|
246
|
-
peerRole: cfg.peer_role ?? "
|
|
247
|
-
|
|
246
|
+
peerRole: cfg.peer_role ?? "none",
|
|
247
|
+
senderPeerId: sanitizeOpenVikingPeerId(sender.senderId),
|
|
248
248
|
assistantPeerId: agentId,
|
|
249
249
|
});
|
|
250
250
|
const queryConfig = await queryConfigStore?.getEffective({
|
|
@@ -595,9 +595,9 @@ export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messag
|
|
|
595
595
|
});
|
|
596
596
|
if (ovParts.length > 0) {
|
|
597
597
|
await client.addSessionMessage(ovSessionId, msg.role, ovParts, undefined, createdAt, resolveOpenVikingMessagePeerId({
|
|
598
|
-
peerRole: cfg.peer_role ?? "
|
|
598
|
+
peerRole: cfg.peer_role ?? "none",
|
|
599
599
|
role: msg.role,
|
|
600
|
-
|
|
600
|
+
senderPeerId: senderRoleId,
|
|
601
601
|
assistantPeerId: agentId,
|
|
602
602
|
}));
|
|
603
603
|
}
|
|
@@ -671,7 +671,7 @@ function compactFailureResult(reason, tokensBefore, details) {
|
|
|
671
671
|
},
|
|
672
672
|
};
|
|
673
673
|
}
|
|
674
|
-
export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBudget, currentTokenCount, force, compactionTarget, customInstructions, getClient, logger, resolveAgentId, isBypassedSession, diag, }) {
|
|
674
|
+
export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBudget, currentTokenCount, force, compactionTarget, customInstructions, getClient, logger, resolveAgentId, isBypassedSession, runtimeCompact, diag, }) {
|
|
675
675
|
const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
|
|
676
676
|
diag("compact_entry", ovSessionId, {
|
|
677
677
|
tokenBudget,
|
|
@@ -682,6 +682,18 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
|
|
|
682
682
|
customInstructions.trim().length > 0,
|
|
683
683
|
});
|
|
684
684
|
if (isBypassedSession({ sessionId, sessionKey })) {
|
|
685
|
+
// Bypassed sessions store nothing in OV, but the host still needs a real
|
|
686
|
+
// compaction (preflight hard-fails on an unknown skip reason), so hand the
|
|
687
|
+
// request to OpenClaw's native compactor.
|
|
688
|
+
const delegated = await runtimeCompact?.();
|
|
689
|
+
if (delegated) {
|
|
690
|
+
diag("compact_result", ovSessionId, {
|
|
691
|
+
ok: delegated.ok,
|
|
692
|
+
compacted: delegated.compacted,
|
|
693
|
+
reason: delegated.reason ?? "session_bypassed_runtime_compaction",
|
|
694
|
+
});
|
|
695
|
+
return delegated;
|
|
696
|
+
}
|
|
685
697
|
diag("compact_result", ovSessionId, {
|
|
686
698
|
ok: true,
|
|
687
699
|
compacted: false,
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { resolveOpenVikingRequestHeaders, } from "../../request-headers.js";
|
|
2
|
+
export const defaultSetupNetworkTransport = (url, init) => fetch(url, init);
|
|
3
|
+
export function createSetupNetworkProbes({ pluginVersion, compatRange, checkVersionCompatibility, transport = defaultSetupNetworkTransport, timeoutMs = 10_000, }) {
|
|
4
|
+
return {
|
|
5
|
+
async probeApiKeyType(baseUrl, apiKey, configuredHeaders) {
|
|
6
|
+
if (!apiKey) {
|
|
7
|
+
return { keyType: "no_key", needsAccountId: false, needsUserId: false, detail: "No API key configured" };
|
|
8
|
+
}
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
11
|
+
const sessionsUrl = `${baseUrl.replace(/\/+$/, "")}/api/v1/sessions?limit=1`;
|
|
12
|
+
try {
|
|
13
|
+
const configuredRequestHeaders = resolveOpenVikingRequestHeaders({ headers: configuredHeaders });
|
|
14
|
+
const headers = {};
|
|
15
|
+
headers["X-API-Key"] = apiKey;
|
|
16
|
+
Object.assign(headers, configuredRequestHeaders);
|
|
17
|
+
const response = await transport(sessionsUrl, {
|
|
18
|
+
headers,
|
|
19
|
+
signal: controller.signal,
|
|
20
|
+
});
|
|
21
|
+
if (response.ok) {
|
|
22
|
+
return { keyType: "user_key", needsAccountId: false, needsUserId: false, detail: "API key has full user context" };
|
|
23
|
+
}
|
|
24
|
+
if ([400, 401, 403, 422].includes(response.status)) {
|
|
25
|
+
let body = "";
|
|
26
|
+
try {
|
|
27
|
+
body = await response.text();
|
|
28
|
+
}
|
|
29
|
+
catch { /* ignore parse errors */ }
|
|
30
|
+
const lower = body.toLowerCase();
|
|
31
|
+
const needsAccount = /x-openviking-account|account[_ ]?id|account context|tenant/.test(lower);
|
|
32
|
+
const needsUser = /x-openviking-user|user[_ ]?id|user context|user key/.test(lower);
|
|
33
|
+
if (needsAccount || needsUser) {
|
|
34
|
+
return {
|
|
35
|
+
keyType: "root_key",
|
|
36
|
+
needsAccountId: needsAccount,
|
|
37
|
+
needsUserId: needsUser,
|
|
38
|
+
detail: body.slice(0, 200),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const probeHeaders = {
|
|
43
|
+
"X-API-Key": apiKey,
|
|
44
|
+
"X-OpenViking-Account": "__probe__",
|
|
45
|
+
"X-OpenViking-User": "__probe__",
|
|
46
|
+
...configuredRequestHeaders,
|
|
47
|
+
};
|
|
48
|
+
const probe2 = await transport(sessionsUrl, {
|
|
49
|
+
headers: probeHeaders,
|
|
50
|
+
signal: controller.signal,
|
|
51
|
+
});
|
|
52
|
+
if (probe2.status !== response.status) {
|
|
53
|
+
return {
|
|
54
|
+
keyType: "root_key",
|
|
55
|
+
needsAccountId: true,
|
|
56
|
+
needsUserId: true,
|
|
57
|
+
detail: body.slice(0, 200) || `HTTP ${response.status} -> ${probe2.status} after adding tenant headers`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch { /* ignore probe errors, fall through to unknown */ }
|
|
62
|
+
if (response.status === 401 || response.status === 403) {
|
|
63
|
+
return { keyType: "unknown", needsAccountId: false, needsUserId: false, detail: `HTTP ${response.status} - authentication failed, verify your API key` };
|
|
64
|
+
}
|
|
65
|
+
return { keyType: "unknown", needsAccountId: false, needsUserId: false, detail: `HTTP ${response.status}${body ? ` - ${body.slice(0, 160)}` : ""}` };
|
|
66
|
+
}
|
|
67
|
+
return { keyType: "unknown", needsAccountId: false, needsUserId: false, detail: `HTTP ${response.status}` };
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return { keyType: "unknown", needsAccountId: false, needsUserId: false, detail: String(err instanceof Error ? err.message : err) };
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
clearTimeout(timeoutId);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
async checkServiceHealth(baseUrl, apiKey, configuredHeaders) {
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
79
|
+
try {
|
|
80
|
+
const configuredRequestHeaders = resolveOpenVikingRequestHeaders({ headers: configuredHeaders });
|
|
81
|
+
const headers = {};
|
|
82
|
+
if (apiKey) {
|
|
83
|
+
headers["X-API-Key"] = apiKey;
|
|
84
|
+
}
|
|
85
|
+
Object.assign(headers, configuredRequestHeaders);
|
|
86
|
+
const response = await transport(`${baseUrl.replace(/\/+$/, "")}/health`, {
|
|
87
|
+
headers,
|
|
88
|
+
signal: controller.signal,
|
|
89
|
+
});
|
|
90
|
+
if (response.ok) {
|
|
91
|
+
try {
|
|
92
|
+
const data = await response.json();
|
|
93
|
+
const result = (data.result ?? data);
|
|
94
|
+
const version = String(result.version ?? data.version ?? "");
|
|
95
|
+
const compatibility = checkVersionCompatibility(version);
|
|
96
|
+
return { ok: true, version, error: "", compatibility, pluginVersion, compatRange };
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return { ok: true, version: "", error: "", compatibility: "unknown", pluginVersion, compatRange };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { ok: false, version: "", error: `HTTP ${response.status}`, compatibility: "unknown", pluginVersion, compatRange };
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
return { ok: false, version: "", error: String(err instanceof Error ? err.message : err), compatibility: "unknown", pluginVersion, compatRange };
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
clearTimeout(timeoutId);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
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",
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
"bypassSessionPatterns": {
|
|
196
196
|
"label": "Bypass Session Patterns",
|
|
197
197
|
"placeholder": "agent:*:cron:**",
|
|
198
|
-
"help": "Completely bypass OpenViking for matching session keys. Use * within one segment and ** across segments.",
|
|
198
|
+
"help": "Completely bypass OpenViking for matching session keys (no capture, recall, or commit; compaction falls back to OpenClaw's native compactor). Use * within one segment and ** across segments.",
|
|
199
199
|
"advanced": true
|
|
200
200
|
},
|
|
201
201
|
"commitTokenThresholdRatio": {
|
|
@@ -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.
|
|
3
|
+
"version": "2026.9.8-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",
|
|
@@ -77,11 +77,5 @@
|
|
|
77
77
|
"openclawVersion": "2026.5.27",
|
|
78
78
|
"pluginSdkVersion": "2026.5.27"
|
|
79
79
|
}
|
|
80
|
-
},
|
|
81
|
-
"devDependencies": {
|
|
82
|
-
"@types/node": "^25.3.5",
|
|
83
|
-
"@volcengine/tos-sdk": "^2.9.1",
|
|
84
|
-
"typescript": "^5.8.0",
|
|
85
|
-
"vitest": "^4.1.0"
|
|
86
80
|
}
|
|
87
81
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Bridge to OpenClaw's built-in compactor for sessions that bypass OpenViking.
|
|
2
|
+
// The host exports `delegateCompactionToRuntime` from `openclaw/plugin-sdk/core`
|
|
3
|
+
// (present since the earliest supported host); it is resolved lazily because the
|
|
4
|
+
// plugin builds without the host SDK installed.
|
|
5
|
+
|
|
6
|
+
export type RuntimeCompactionResult = {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
compacted: boolean;
|
|
9
|
+
reason?: string;
|
|
10
|
+
result?: {
|
|
11
|
+
summary?: string;
|
|
12
|
+
firstKeptEntryId?: string;
|
|
13
|
+
tokensBefore: number;
|
|
14
|
+
tokensAfter?: number;
|
|
15
|
+
details?: unknown;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type RuntimeCompactionDelegate = (
|
|
20
|
+
params: Record<string, unknown>,
|
|
21
|
+
) => Promise<RuntimeCompactionResult>;
|
|
22
|
+
|
|
23
|
+
const PLUGIN_SDK_CORE_SPECIFIER = "openclaw/plugin-sdk/core";
|
|
24
|
+
|
|
25
|
+
let cached: Promise<RuntimeCompactionDelegate | undefined> | undefined;
|
|
26
|
+
|
|
27
|
+
export function loadRuntimeCompactionDelegate(): Promise<RuntimeCompactionDelegate | undefined> {
|
|
28
|
+
cached ??= (async () => {
|
|
29
|
+
try {
|
|
30
|
+
const mod = (await import(PLUGIN_SDK_CORE_SPECIFIER)) as {
|
|
31
|
+
delegateCompactionToRuntime?: unknown;
|
|
32
|
+
};
|
|
33
|
+
return typeof mod?.delegateCompactionToRuntime === "function"
|
|
34
|
+
? (mod.delegateCompactionToRuntime as RuntimeCompactionDelegate)
|
|
35
|
+
: undefined;
|
|
36
|
+
} catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
return cached;
|
|
41
|
+
}
|
|
@@ -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
|
}
|
|
@@ -117,6 +117,8 @@ export type CompactOpenVikingSessionParams = {
|
|
|
117
117
|
logger: ContextEngineLifecycleLogger;
|
|
118
118
|
resolveAgentId: (sessionId: string, sessionKey?: string, ovSessionId?: string) => string;
|
|
119
119
|
isBypassedSession: (params: { sessionId?: string; sessionKey?: string }) => boolean;
|
|
120
|
+
/** Host-native compaction for bypassed sessions; resolves undefined when unavailable. */
|
|
121
|
+
runtimeCompact?: () => Promise<CompactOpenVikingSessionResult | undefined>;
|
|
120
122
|
diag: (stage: string, sessionId: string, data: Record<string, unknown>) => void;
|
|
121
123
|
};
|
|
122
124
|
|
|
@@ -497,8 +499,8 @@ export async function assembleOpenVikingSession({
|
|
|
497
499
|
const routingRef = sessionId ?? sessionKey ?? ovSessionId;
|
|
498
500
|
const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
|
|
499
501
|
const actorPeerId = resolveOpenVikingActorPeerId({
|
|
500
|
-
peerRole: cfg.peer_role ?? "
|
|
501
|
-
|
|
502
|
+
peerRole: cfg.peer_role ?? "none",
|
|
503
|
+
senderPeerId: sanitizeOpenVikingPeerId(sender.senderId),
|
|
502
504
|
assistantPeerId: agentId,
|
|
503
505
|
});
|
|
504
506
|
const queryConfig = await queryConfigStore?.getEffective({
|
|
@@ -910,9 +912,9 @@ export async function afterTurnOpenVikingSession({
|
|
|
910
912
|
undefined,
|
|
911
913
|
createdAt,
|
|
912
914
|
resolveOpenVikingMessagePeerId({
|
|
913
|
-
peerRole: cfg.peer_role ?? "
|
|
915
|
+
peerRole: cfg.peer_role ?? "none",
|
|
914
916
|
role: msg.role,
|
|
915
|
-
|
|
917
|
+
senderPeerId: senderRoleId,
|
|
916
918
|
assistantPeerId: agentId,
|
|
917
919
|
}),
|
|
918
920
|
);
|
|
@@ -1014,6 +1016,7 @@ export async function compactOpenVikingSession({
|
|
|
1014
1016
|
logger,
|
|
1015
1017
|
resolveAgentId,
|
|
1016
1018
|
isBypassedSession,
|
|
1019
|
+
runtimeCompact,
|
|
1017
1020
|
diag,
|
|
1018
1021
|
}: CompactOpenVikingSessionParams): Promise<CompactOpenVikingSessionResult> {
|
|
1019
1022
|
const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
|
|
@@ -1027,6 +1030,18 @@ export async function compactOpenVikingSession({
|
|
|
1027
1030
|
});
|
|
1028
1031
|
|
|
1029
1032
|
if (isBypassedSession({ sessionId, sessionKey })) {
|
|
1033
|
+
// Bypassed sessions store nothing in OV, but the host still needs a real
|
|
1034
|
+
// compaction (preflight hard-fails on an unknown skip reason), so hand the
|
|
1035
|
+
// request to OpenClaw's native compactor.
|
|
1036
|
+
const delegated = await runtimeCompact?.();
|
|
1037
|
+
if (delegated) {
|
|
1038
|
+
diag("compact_result", ovSessionId, {
|
|
1039
|
+
ok: delegated.ok,
|
|
1040
|
+
compacted: delegated.compacted,
|
|
1041
|
+
reason: delegated.reason ?? "session_bypassed_runtime_compaction",
|
|
1042
|
+
});
|
|
1043
|
+
return delegated;
|
|
1044
|
+
}
|
|
1030
1045
|
diag("compact_result", ovSessionId, {
|
|
1031
1046
|
ok: true,
|
|
1032
1047
|
compacted: false,
|
|
@@ -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,
|
|
@@ -106,7 +106,7 @@ Send this message:
|
|
|
106
106
|
> 我需要 3 条信息,不知道的可以问你的管理员:
|
|
107
107
|
> 1. **OpenViking 服务地址** —— 例如 `https://ov.example.com` 或 `http://192.168.1.100:1933`,本机服务可以直接说"本机"
|
|
108
108
|
> 2. **API Key** —— 用来鉴权;服务没开认证可以说"没有"
|
|
109
|
-
> 3.
|
|
109
|
+
> 3. **记忆归属**(可选) —— 所有对话共用 user 记忆(默认),还是在 `peers/<assistant_id>` 或 `peers/<sender_id>` 下分开存放
|
|
110
110
|
>
|
|
111
111
|
> 先告诉我服务地址吧?
|
|
112
112
|
|
|
@@ -115,7 +115,7 @@ Send this message:
|
|
|
115
115
|
> I need 3 things (ask your admin if unsure):
|
|
116
116
|
> 1. **OpenViking server URL** — e.g. `https://ov.example.com` or `http://192.168.1.100:1933`. For a local server, just say "local".
|
|
117
117
|
> 2. **API Key** — for auth. Say "none" if the server has no auth.
|
|
118
|
-
> 3. **
|
|
118
|
+
> 3. **Memory scope** (optional) — shared user memory (default), or peer memory under `peers/<assistant_id>` / `peers/<sender_id>`.
|
|
119
119
|
>
|
|
120
120
|
> What's the server URL?
|
|
121
121
|
|
|
@@ -141,15 +141,36 @@ Collect 3 values through natural conversation. Be flexible: if the user gives se
|
|
|
141
141
|
- "no" / "none" / "没有" / "空" / empty → `API_KEY=""` (you will skip the flag later).
|
|
142
142
|
- Otherwise store as-is.
|
|
143
143
|
|
|
144
|
-
### 4c. `
|
|
144
|
+
### 4c. `PEER_ROLE` (OPTIONAL)
|
|
145
|
+
|
|
146
|
+
> (CN) 记忆要存在哪一层?默认共享在 `viking://user/<user_id>/memories`(`none`)。也可以按 OpenClaw 助手存到 `.../peers/<assistant_id>/memories`(`assistant`),或按发送者存到 `.../peers/<sender_id>/memories`(`sender`)。
|
|
147
|
+
> (EN) Where should memory live? The default is shared `viking://user/<user_id>/memories` (`none`). You can instead use `.../peers/<assistant_id>/memories` (`assistant`) or `.../peers/<sender_id>/memories` (`sender`).
|
|
148
|
+
|
|
149
|
+
Default to `none`. **Never silently pick another value** — the three options mean different storage layouts and are awkward to change later:
|
|
150
|
+
|
|
151
|
+
| Value | Concrete layout | Choose it when |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| `none` (default) | Shared memory under `viking://user/<user_id>/memories/...`; no concrete `peers/<peer_id>/memories` subtree is used. | General case. All conversations for this OpenViking user share user-level memory. |
|
|
154
|
+
| `assistant` | Assistant-attributed peer memory under `viking://user/<user_id>/peers/<assistant_id>/memories/...`, keyed by the resolved OpenClaw agent id (fallback `main`; optional prefix supported). | **Human as OpenViking user**: one human uses multiple assistants and wants their peer memories separated. Example: Alice's `main` and `research` assistants use `.../peers/main/...` and `.../peers/research/...`. |
|
|
155
|
+
| `sender` | Sender-attributed peer memory under `viking://user/<user_id>/peers/<sender_id>/memories/...`, keyed by the sender identity supplied by OpenClaw. | **Agent as OpenViking user**: one agent talks to multiple humans and wants their peer memories separated. Example: `support-agent` stores `customer-42` and `customer-99` under different peer subtrees. |
|
|
156
|
+
|
|
157
|
+
`person` is a legacy alias for `sender`. Accept it from an existing config or explicit user input, but normalize/write new setup values as `sender`.
|
|
158
|
+
|
|
159
|
+
OpenViking initializes the managed `peers/` container for every user. Do not tell the user that `none` removes that container: it only avoids a concrete peer memory subtree. With `assistant` or `sender`, actor-peer recall includes shared user memory plus the current peer memory; it hides other peer subtrees. Changing this setting does not move existing memories.
|
|
160
|
+
|
|
161
|
+
- Empty / "default" / "默认" / "共用" → leave unset (plugin defaults to `none`).
|
|
162
|
+
- Otherwise pass `--peer-role none|assistant|sender`. Treat explicit legacy `person` as `sender`. Any other value → ask again.
|
|
163
|
+
|
|
164
|
+
### 4d. `PEER_PREFIX` (OPTIONAL, only when `PEER_ROLE=assistant`)
|
|
145
165
|
|
|
146
166
|
> (CN) 想给这个 agent 一个记忆前缀吗?留空就用默认。只能用字母、数字、`_`、`-`。
|
|
147
167
|
> (EN) Want to set an peer prefix? Leave blank for the default. Letters, digits, `_`, `-` only.
|
|
148
168
|
|
|
169
|
+
- Only meaningful when `PEER_ROLE=assistant`; skip the question otherwise.
|
|
149
170
|
- Empty / "default" / "默认" → leave unset (plugin defaults to `""`).
|
|
150
171
|
- Otherwise validate against `/^[A-Za-z0-9_-]+$/`. If invalid, ask again.
|
|
151
172
|
|
|
152
|
-
###
|
|
173
|
+
### 4e. (Conditional) Multi-Tenant Root-Key Fields
|
|
153
174
|
|
|
154
175
|
Only ask for these if STEP 7 detects a root key (`Root API key detected. Missing: --account-id, --user-id`). Don't ask up front.
|
|
155
176
|
|
|
@@ -259,14 +280,15 @@ Tell the user:
|
|
|
259
280
|
Run the installer with `npx` (no global install needed):
|
|
260
281
|
|
|
261
282
|
```bash
|
|
262
|
-
npx -y openclaw-openviking-setup-helper@latest --base-url BASE_URL [--api-key API_KEY] [--peer-prefix PEER_PREFIX] [--account-id ACCOUNT_ID] [--user-id USER_ID]
|
|
283
|
+
npx -y openclaw-openviking-setup-helper@latest --base-url BASE_URL [--api-key API_KEY] [--peer-role PEER_ROLE] [--peer-prefix PEER_PREFIX] [--account-id ACCOUNT_ID] [--user-id USER_ID]
|
|
263
284
|
```
|
|
264
285
|
|
|
265
286
|
Build the flag list according to what the user gave you:
|
|
266
287
|
|
|
267
288
|
- Always pass `--base-url BASE_URL`.
|
|
268
289
|
- Pass `--api-key API_KEY` only if `API_KEY` is non-empty.
|
|
269
|
-
- Pass `--peer-
|
|
290
|
+
- Pass `--peer-role PEER_ROLE` only if the user chose `assistant` or `sender`; omit it for the `none` default. Normalize legacy `person` to `sender` first.
|
|
291
|
+
- Pass `--peer-prefix PEER_PREFIX` only if the user gave one and `PEER_ROLE=assistant`.
|
|
270
292
|
- `--account-id` / `--user-id` only if the root-key path requires them.
|
|
271
293
|
|
|
272
294
|
`ov-install` will, in one shot:
|
|
@@ -287,14 +309,15 @@ If `ov-install` exits non-zero, capture the last 30 lines of its output, show th
|
|
|
287
309
|
Run the setup wizard non-interactively. Build flags from collected values:
|
|
288
310
|
|
|
289
311
|
```bash
|
|
290
|
-
openclaw openviking setup --base-url BASE_URL --json [--api-key API_KEY] [--peer-prefix PEER_PREFIX] [--account-id ACCOUNT_ID] [--user-id USER_ID] [--allow-offline] [--force-slot]
|
|
312
|
+
openclaw openviking setup --base-url BASE_URL --json [--api-key API_KEY] [--peer-role PEER_ROLE] [--peer-prefix PEER_PREFIX] [--account-id ACCOUNT_ID] [--user-id USER_ID] [--allow-offline] [--force-slot]
|
|
291
313
|
```
|
|
292
314
|
|
|
293
315
|
Rules:
|
|
294
316
|
|
|
295
317
|
- `--base-url BASE_URL` is **required** under `--json`. Without it, the wizard prints `--json requires --base-url for non-interactive mode`.
|
|
296
318
|
- `--api-key` only if `API_KEY` is non-empty.
|
|
297
|
-
- `--peer-
|
|
319
|
+
- `--peer-role` only if the user chose `assistant` or `sender`; omit it for the `none` default. Normalize legacy `person` to `sender` first.
|
|
320
|
+
- `--peer-prefix` only if the user gave one and `--peer-role assistant` is in play. Use **`--peer-prefix`** for assistant peer prefixes; the old ID-style setup flag is no longer supported.
|
|
298
321
|
- `--account-id` / `--user-id` only after STEP 7 root-key detection (see below).
|
|
299
322
|
- `--allow-offline` only if the user explicitly approved it in STEP 5.
|
|
300
323
|
- `--force-slot` **never** in the first attempt. Add only after the user confirms (see slot_blocked handling below).
|
|
@@ -583,8 +606,8 @@ These are the keys under `plugins.entries.openviking.config` in `openclaw.json`.
|
|
|
583
606
|
| `mode` | `"remote"` (forced by plugin) | Always remote in this skill. Don't set manually. |
|
|
584
607
|
| `baseUrl` | `http://127.0.0.1:1933` | OpenViking server URL. |
|
|
585
608
|
| `apiKey` | — | API key. Optional if server has no auth. |
|
|
586
|
-
| `peer_role` | `
|
|
587
|
-
| `peer_prefix` | `""` | Optional prefix for assistant `peer_id` / actor peer values when `peer_role=assistant`. Letters / digits / `_` / `-`. |
|
|
609
|
+
| `peer_role` | `none` | Memory scope: `none` (shared `viking://user/<user_id>/memories`), `assistant` (`.../peers/<assistant_id>/memories`), or `sender` (`.../peers/<sender_id>/memories`). Legacy `person` is accepted as `sender`. Session messages use body `peer_id`; data-plane recall/search uses `X-OpenViking-Actor-Peer`. |
|
|
610
|
+
| `peer_prefix` | `""` | Optional prefix for assistant `peer_id` / actor peer values. Only used when `peer_role=assistant`. Letters / digits / `_` / `-`. |
|
|
588
611
|
| `accountId` | — | Required when `apiKey` is a root key. |
|
|
589
612
|
| `userId` | — | Required when `apiKey` is a root key. |
|
|
590
613
|
| `targetUri` | `viking://~/memories` | Default search scope URI. |
|
|
@@ -64,7 +64,7 @@ Core config lives under `plugins.entries.openviking.config`:
|
|
|
64
64
|
|---|---:|---|
|
|
65
65
|
| `baseUrl` | `http://127.0.0.1:1933` | OpenViking HTTP endpoint. Can also come from `OPENVIKING_BASE_URL` / `OPENVIKING_URL`. |
|
|
66
66
|
| `apiKey` | empty | Optional API key. Can also come from `OPENVIKING_API_KEY`. |
|
|
67
|
-
| `peer_role` | `
|
|
67
|
+
| `peer_role` | `none` | Memory scope: `none` (shared `viking://user/<user_id>/memories`), `assistant` (assistant-attributed memory under `.../peers/<assistant_id>/memories`), or `sender` (sender-attributed memory under `.../peers/<sender_id>/memories`). Legacy `person` is accepted as `sender`. |
|
|
68
68
|
| `peer_prefix` | empty | Optional prefix for assistant `peer_id` / actor peer values when `peer_role=assistant`. |
|
|
69
69
|
| `accountId` / `userId` | empty | Advanced tenant identity headers for root-key or trusted deployments. |
|
|
70
70
|
| `targetUri` | `viking://~/memories` | Default search scope for legacy targeted memory search. |
|
|
@@ -95,7 +95,7 @@ openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --json
|
|
|
95
95
|
Useful variants:
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
|
-
openclaw openviking setup --base-url <URL> --api-key <KEY> --peer-prefix openclaw-prod --json
|
|
98
|
+
openclaw openviking setup --base-url <URL> --api-key <KEY> --peer-role assistant --peer-prefix openclaw-prod --json
|
|
99
99
|
openclaw openviking setup --base-url <URL> --api-key <ROOT_KEY> --account-id <ACCOUNT_ID> --user-id <USER_ID> --json
|
|
100
100
|
openclaw openviking setup --base-url <URL> --api-key <KEY> --recall-target-types resource --json
|
|
101
101
|
openclaw openviking setup --base-url <URL> --api-key <KEY> --allow-offline --json
|