@openviking/openclaw-plugin 2026.7.11 → 2026.8.6

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.
Files changed (48) hide show
  1. package/INSTALL-ZH.md +39 -0
  2. package/INSTALL.md +39 -0
  3. package/README.md +5 -0
  4. package/auto-recall.ts +5 -4
  5. package/config.ts +145 -6
  6. package/context-engine.ts +0 -1
  7. package/dist/auto-recall.js +4 -4
  8. package/dist/config.js +101 -4
  9. package/dist/context-engine.js +0 -1
  10. package/dist/index.js +13 -4
  11. package/dist/plugin/openviking-archive-tools.js +1 -2
  12. package/dist/plugin/openviking-command-definitions.js +3 -3
  13. package/dist/plugin/openviking-experience-tools.js +105 -0
  14. package/dist/plugin/openviking-import-tools.js +2 -2
  15. package/dist/plugin/openviking-memory-recall-tools.js +4 -4
  16. package/dist/plugin/openviking-memory-tools.js +11 -6
  17. package/dist/plugin/openviking-query-runtime.js +1 -1
  18. package/dist/plugin/openviking-query-tools.js +4 -4
  19. package/dist/plugin/openviking-recall-trace-runtime.js +3 -3
  20. package/dist/plugin/openviking-session-routing-runtime.js +9 -2
  21. package/dist/plugin/openviking-tool-result-tools.js +3 -3
  22. package/dist/routing/identity-routing.js +27 -0
  23. package/dist/routing/memory-uri.js +1 -0
  24. package/dist/services/context-lifecycle-service.js +21 -16
  25. package/dist/services/context-message-adapter.js +2 -9
  26. package/index.ts +13 -3
  27. package/install-manifest.json +1 -0
  28. package/openclaw.plugin.json +46 -3
  29. package/package.json +1 -1
  30. package/plugin/command-registration.ts +2 -0
  31. package/plugin/openviking-archive-tools.ts +1 -4
  32. package/plugin/openviking-command-definitions.ts +4 -3
  33. package/plugin/openviking-experience-tools.ts +160 -0
  34. package/plugin/openviking-import-tools.ts +4 -2
  35. package/plugin/openviking-memory-recall-tools.ts +6 -4
  36. package/plugin/openviking-memory-tools.ts +18 -10
  37. package/plugin/openviking-query-runtime.ts +1 -1
  38. package/plugin/openviking-query-tools.ts +6 -4
  39. package/plugin/openviking-recall-trace-runtime.ts +4 -3
  40. package/plugin/openviking-recall-trace-tools.ts +2 -0
  41. package/plugin/openviking-session-routing-runtime.ts +15 -1
  42. package/plugin/openviking-tool-result-tools.ts +0 -6
  43. package/registries/openviking-tools.ts +6 -0
  44. package/routing/identity-routing.ts +41 -0
  45. package/routing/memory-uri.ts +1 -0
  46. package/services/context-lifecycle-service.ts +27 -18
  47. package/services/context-message-adapter.ts +2 -9
  48. package/skills/ov-experience-memory/SKILL.md +156 -0
@@ -27,7 +27,6 @@ export type OpenVikingToolResultClient = {
27
27
  sessionId: string,
28
28
  toolResultId: string,
29
29
  options: { offset: number; limit: number; includeMetadata: boolean },
30
- agentId?: string,
31
30
  ) => Promise<{
32
31
  content: string;
33
32
  offset: number;
@@ -42,7 +41,6 @@ export type OpenVikingToolResultClient = {
42
41
  toolResultId: string,
43
42
  query: string,
44
43
  options: { limit: number; contextChars: number },
45
- agentId?: string,
46
44
  ) => Promise<{
47
45
  tool_result_id: string;
48
46
  matches?: Array<{ offset: number; snippet: string }>;
@@ -50,7 +48,6 @@ export type OpenVikingToolResultClient = {
50
48
  listToolResults: (
51
49
  sessionId: string,
52
50
  options: { toolName?: string; limit: number },
53
- agentId?: string,
54
51
  ) => Promise<{
55
52
  tool_results?: Array<{
56
53
  storage_uri?: string;
@@ -180,7 +177,6 @@ export function registerOpenVikingToolResultTools({
180
177
  session.ovSessionId,
181
178
  parsed.toolResultId,
182
179
  { offset, limit, includeMetadata: true },
183
- session.agentId,
184
180
  );
185
181
  const returnedChars = result.content.length;
186
182
  const nextOffset = result.offset + returnedChars;
@@ -281,7 +277,6 @@ export function registerOpenVikingToolResultTools({
281
277
  parsed.toolResultId,
282
278
  query,
283
279
  { limit, contextChars },
284
- session.agentId,
285
280
  );
286
281
  const matches = result.matches ?? [];
287
282
  const text = matches.length
@@ -354,7 +349,6 @@ export function registerOpenVikingToolResultTools({
354
349
  const result = await client.listToolResults(
355
350
  session.ovSessionId,
356
351
  { toolName, limit },
357
- session.agentId,
358
352
  );
359
353
  const items = result.tool_results ?? [];
360
354
  const text = items.length
@@ -1,6 +1,7 @@
1
1
  export type OpenVikingToolGroup =
2
2
  | "memory"
3
3
  | "resource_query"
4
+ | "experience"
4
5
  | "import"
5
6
  | "recall_trace"
6
7
  | "archive"
@@ -13,6 +14,8 @@ export type OpenVikingToolName =
13
14
  | "ov_read"
14
15
  | "ov_multi_read"
15
16
  | "ov_list"
17
+ | "search_experience"
18
+ | "read_experience"
16
19
  | "memory_recall"
17
20
  | "ov_recall_trace"
18
21
  | "memory_store"
@@ -44,6 +47,8 @@ export const OPENVIKING_TOOL_SPECS = [
44
47
  { name: "ov_read", group: "resource_query", defaultEnabled: true },
45
48
  { name: "ov_multi_read", group: "resource_query", defaultEnabled: true },
46
49
  { name: "ov_list", group: "resource_query", defaultEnabled: true },
50
+ { name: "search_experience", group: "experience", defaultEnabled: true },
51
+ { name: "read_experience", group: "experience", defaultEnabled: true },
47
52
  { name: "memory_recall", group: "memory", defaultEnabled: true },
48
53
  { name: "ov_recall_trace", group: "recall_trace", defaultEnabled: true },
49
54
  { name: "memory_store", group: "memory", defaultEnabled: true },
@@ -64,6 +69,7 @@ export const OPENVIKING_DEFAULT_ENABLED_TOOL_NAMES = OPENVIKING_TOOL_SPECS
64
69
  const OPENVIKING_TOOL_GROUP_ORDER: readonly OpenVikingToolGroup[] = [
65
70
  "memory",
66
71
  "resource_query",
72
+ "experience",
67
73
  "import",
68
74
  "recall_trace",
69
75
  "archive",
@@ -15,6 +15,47 @@ export type SessionAgentLookup = {
15
15
  ovSessionId?: string;
16
16
  };
17
17
 
18
+ export type OpenVikingPeerRole = "none" | "assistant" | "person";
19
+
20
+ export function sanitizeOpenVikingPeerId(raw?: string): string | undefined {
21
+ const normalized = raw
22
+ ?.trim()
23
+ .replace(/[^a-zA-Z0-9_-]+/g, "_")
24
+ .replace(/^_+|_+$/g, "")
25
+ .replace(/_+/g, "_");
26
+ return normalized || undefined;
27
+ }
28
+
29
+ export function resolveOpenVikingMessagePeerId(params: {
30
+ peerRole: OpenVikingPeerRole;
31
+ role?: string;
32
+ personPeerId?: string;
33
+ assistantPeerId?: string;
34
+ }): string | undefined {
35
+ if (params.peerRole === "person" && params.role === "user") {
36
+ return params.personPeerId;
37
+ }
38
+ if (params.peerRole === "assistant" && params.role === "assistant") {
39
+ return params.assistantPeerId;
40
+ }
41
+ return undefined;
42
+ }
43
+
44
+ export function resolveOpenVikingActorPeerId(params: {
45
+ peerRole: OpenVikingPeerRole;
46
+ personPeerId?: string;
47
+ assistantPeerId?: string;
48
+ }): string | undefined {
49
+ const actorPeerId = resolveOpenVikingMessagePeerId({
50
+ ...params,
51
+ role: params.peerRole === "person" ? "user" : "assistant",
52
+ });
53
+ if (params.peerRole === "person" && !actorPeerId) {
54
+ throw new Error("openviking: peer_role=person requires a sender identity");
55
+ }
56
+ return actorPeerId;
57
+ }
58
+
18
59
  export type SessionAgentResolveBranch =
19
60
  | "session_resolved"
20
61
  | "config_only_fallback"
@@ -1,5 +1,6 @@
1
1
  const MEMORY_URI_PATTERNS = [
2
2
  /^viking:\/\/user\/(?:[^/]+(?:\/agent\/[^/]+)?\/)?memories(?:\/|$)/,
3
+ /^viking:\/\/user\/[^/]+\/peers\/[^/]+\/memories(?:\/|$)/,
3
4
  /^viking:\/\/agent\/(?:[^/]+(?:\/user\/[^/]+)?\/)?memories(?:\/|$)/,
4
5
  ];
5
6
 
@@ -3,7 +3,13 @@ import { DEFAULT_PHASE2_POLL_TIMEOUT_MS, type OpenVikingClient, type OVMessage }
3
3
  import type { EffectiveQueryConfig } from "../query-config.js";
4
4
  import { buildAutoRecallContext, prepareRecallQuery } from "../auto-recall.js";
5
5
  import { toJsonLog } from "../memory-ranking.js";
6
- import { openClawSessionToOvStorageId } from "../routing/identity-routing.js";
6
+ import {
7
+ openClawSessionToOvStorageId,
8
+ resolveOpenVikingActorPeerId,
9
+ resolveOpenVikingMessagePeerId,
10
+ sanitizeOpenVikingPeerId,
11
+ type OpenVikingPeerRole,
12
+ } from "../routing/identity-routing.js";
7
13
  import { extractNewTurnMessages } from "../text-utils.js";
8
14
  import { estimateAgentMessageTokens, estimateTextTokens } from "../token-estimator.js";
9
15
  import {
@@ -26,7 +32,6 @@ export type CommitOpenVikingSessionParams = {
26
32
  sessionKey?: string;
27
33
  getClient: () => Promise<Pick<OpenVikingClient, "commitSession">>;
28
34
  logger: ContextEngineLifecycleLogger;
29
- resolveAgentId: (sessionId: string, sessionKey?: string, ovSessionId?: string) => string;
30
35
  rememberSessionAgentId?: (ctx: {
31
36
  agentId?: string;
32
37
  sessionId?: string;
@@ -131,6 +136,7 @@ export type AfterTurnOpenVikingSessionParams = {
131
136
  commitTokenThresholdRatio: number;
132
137
  commitKeepRecentCount: number;
133
138
  logFindRequests: boolean;
139
+ peer_role?: OpenVikingPeerRole;
134
140
  };
135
141
  getClient: () => Promise<AfterTurnClient>;
136
142
  logger: ContextEngineLifecycleLogger;
@@ -177,7 +183,6 @@ function sleep(ms: number): Promise<void> {
177
183
  async function pollPhase2ExtractionOutcome(
178
184
  client: AfterTurnClient,
179
185
  taskId: string,
180
- agentId: string,
181
186
  logger: ContextEngineLifecycleLogger,
182
187
  sessionLabel: string,
183
188
  ): Promise<void> {
@@ -185,7 +190,7 @@ async function pollPhase2ExtractionOutcome(
185
190
  try {
186
191
  while (Date.now() < deadline) {
187
192
  await sleep(PHASE2_POLL_INTERVAL_MS);
188
- const task = await client.getTask(taskId, agentId).catch((e) => {
193
+ const task = await client.getTask(taskId).catch((e) => {
189
194
  logger.warn?.(`openviking: phase2 getTask failed task_id=${taskId}: ${String(e)}`);
190
195
  return null;
191
196
  });
@@ -340,7 +345,6 @@ export async function commitOpenVikingSession({
340
345
  sessionKey,
341
346
  getClient,
342
347
  logger,
343
- resolveAgentId,
344
348
  rememberSessionAgentId,
345
349
  isBypassedSession,
346
350
  }: CommitOpenVikingSessionParams): Promise<boolean> {
@@ -358,10 +362,8 @@ export async function commitOpenVikingSession({
358
362
  sessionKey,
359
363
  ovSessionId: ovId,
360
364
  });
361
- const agentId = resolveAgentId(sessionId, sessionKey, ovId);
362
365
  const commitResult = await client.commitSession(ovId, {
363
366
  wait: true,
364
- agentId,
365
367
  keepRecentCount: 0,
366
368
  });
367
369
  const memCount = totalExtractedMemories(commitResult.memories_extracted);
@@ -492,6 +494,11 @@ export async function assembleOpenVikingSession({
492
494
  const client = await getClient();
493
495
  const routingRef = sessionId ?? sessionKey ?? ovSessionId;
494
496
  const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
497
+ const actorPeerId = resolveOpenVikingActorPeerId({
498
+ peerRole: cfg.peer_role ?? "assistant",
499
+ personPeerId: sanitizeOpenVikingPeerId(sender.senderId),
500
+ assistantPeerId: agentId,
501
+ });
495
502
  const queryConfig = await queryConfigStore?.getEffective({
496
503
  agentId,
497
504
  sessionId,
@@ -503,6 +510,7 @@ export async function assembleOpenVikingSession({
503
510
  queryConfig,
504
511
  client,
505
512
  agentId,
513
+ actorPeerId,
506
514
  queryText: recallQuery.query,
507
515
  logger,
508
516
  verbose: (message) => logger.info(message),
@@ -553,9 +561,7 @@ export async function assembleOpenVikingSession({
553
561
 
554
562
  try {
555
563
  const client = await getClient();
556
- const routingRef = sessionId ?? sessionKey ?? ovSessionId;
557
- const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
558
- const ctx = await client.getSessionContext(ovSessionId, tokenBudget, agentId);
564
+ const ctx = await client.getSessionContext(ovSessionId, tokenBudget);
559
565
 
560
566
  const preAbstracts = ctx?.pre_archive_abstracts ?? [];
561
567
  const hasArchives = !!ctx?.latest_archive_overview || preAbstracts.length > 0;
@@ -899,14 +905,19 @@ export async function afterTurnOpenVikingSession({
899
905
  ovSessionId,
900
906
  msg.role,
901
907
  ovParts,
902
- agentId,
908
+ undefined,
903
909
  createdAt,
904
- msg.role === "user" ? senderRoleId : undefined,
910
+ resolveOpenVikingMessagePeerId({
911
+ peerRole: cfg.peer_role ?? "assistant",
912
+ role: msg.role,
913
+ personPeerId: senderRoleId,
914
+ assistantPeerId: agentId,
915
+ }),
905
916
  );
906
917
  }
907
918
  }
908
919
 
909
- const session = await client.getSession(ovSessionId, agentId);
920
+ const session = await client.getSession(ovSessionId);
910
921
  const pendingTokens = session.pending_tokens ?? 0;
911
922
 
912
923
  const commitTokenThreshold = Math.floor(tokenBudget * cfg.commitTokenThresholdRatio);
@@ -926,7 +937,6 @@ export async function afterTurnOpenVikingSession({
926
937
 
927
938
  const commitResult = await client.commitSession(ovSessionId, {
928
939
  wait: false,
929
- agentId,
930
940
  keepRecentCount: cfg.commitKeepRecentCount,
931
941
  });
932
942
  logger.info(
@@ -952,7 +962,7 @@ export async function afterTurnOpenVikingSession({
952
962
  `openviking: Phase2 memory extraction runs asynchronously on the server (task_id=${commitResult.task_id}). ` +
953
963
  "memories_extracted appears only after that task completes — not in this immediate response.",
954
964
  );
955
- void pollPhase2ExtractionOutcome(client, commitResult.task_id, agentId, logger, ovSessionId);
965
+ void pollPhase2ExtractionOutcome(client, commitResult.task_id, logger, ovSessionId);
956
966
  }
957
967
  } catch (err) {
958
968
  logger.warn?.(`openviking: afterTurn failed: ${String(err)}`);
@@ -1033,7 +1043,7 @@ export async function compactOpenVikingSession({
1033
1043
  let preCommitEstimatedTokens: number | undefined;
1034
1044
  if (typeof tokensBeforeOriginal !== "number") {
1035
1045
  try {
1036
- const preCtx = await client.getSessionContext(ovSessionId, tokenBudget, agentId);
1046
+ const preCtx = await client.getSessionContext(ovSessionId, tokenBudget);
1037
1047
  if (typeof preCtx.estimatedTokens === "number" && Number.isFinite(preCtx.estimatedTokens)) {
1038
1048
  preCommitEstimatedTokens = preCtx.estimatedTokens;
1039
1049
  }
@@ -1053,7 +1063,6 @@ export async function compactOpenVikingSession({
1053
1063
  );
1054
1064
  const commitResult = await client.commitSession(ovSessionId, {
1055
1065
  wait: true,
1056
- agentId,
1057
1066
  keepRecentCount: 0,
1058
1067
  });
1059
1068
  const memCount = totalExtractedMemories(commitResult.memories_extracted);
@@ -1129,7 +1138,7 @@ export async function compactOpenVikingSession({
1129
1138
  let contextFetchError: string | undefined;
1130
1139
 
1131
1140
  try {
1132
- const ctx = await client.getSessionContext(ovSessionId, tokenBudget, agentId);
1141
+ const ctx = await client.getSessionContext(ovSessionId, tokenBudget);
1133
1142
  logger.info(
1134
1143
  `openviking: compact getSessionContext raw result for ${ovSessionId}: ` +
1135
1144
  JSON.stringify(ctx, null, 2),
@@ -1,4 +1,5 @@
1
1
  import type { OVMessage } from "../client.js";
2
+ import { sanitizeOpenVikingPeerId } from "../routing/identity-routing.js";
2
3
  import { sanitizeToolUseResultPairing } from "../session-transcript-repair.js";
3
4
 
4
5
  export type AgentMessage = {
@@ -8,15 +9,7 @@ export type AgentMessage = {
8
9
  };
9
10
 
10
11
  export function toRoleId(senderId: string | undefined): string | undefined {
11
- if (!senderId) {
12
- return undefined;
13
- }
14
- const normalized = senderId
15
- .trim()
16
- .replace(/[^a-zA-Z0-9_-]+/g, "_")
17
- .replace(/^_+|_+$/g, "")
18
- .replace(/_+/g, "_");
19
- return normalized || undefined;
12
+ return sanitizeOpenVikingPeerId(senderId);
20
13
  }
21
14
 
22
15
  /**
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: ov-experience-memory
3
+ description: >
4
+ Use OpenViking experience memories during task execution. Search relevant
5
+ experiences with search_experience, read selected experiences with
6
+ read_experience, and leave standard tool parts in the committed session so
7
+ OpenViking can report recall and injection usage.
8
+ version: 2026.7.9
9
+ tags:
10
+ - openviking
11
+ - experience-memory
12
+ - agent-memory
13
+ - usage-reporting
14
+ ---
15
+
16
+ # OpenViking Experience Memory
17
+
18
+ Use this skill when the current user request starts or continues an executable
19
+ task, especially tasks involving tools, files, code changes, data operations,
20
+ workflow decisions, or multi-step actions.
21
+
22
+ Do not use this skill for casual chat, pure explanation, or one-off factual Q&A
23
+ that does not require operational guidance.
24
+
25
+ ## Runtime Contract
26
+
27
+ The agent runtime must expose two tools with these exact names:
28
+
29
+ - `search_experience`
30
+ - `read_experience`
31
+
32
+ OpenViking usage reporting recognizes only completed tool parts with these exact
33
+ tool names. Calls to generic `find`, `search`, `read`, `ov_search`, or `ov_read`
34
+ do not count as experience recall or injection events.
35
+
36
+ ## Tool: search_experience
37
+
38
+ Purpose: search reusable execution experiences from the OpenViking experience
39
+ library before assembling task context.
40
+
41
+ Input schema:
42
+
43
+ ```json
44
+ {
45
+ "query": "string",
46
+ "limit": 5
47
+ }
48
+ ```
49
+
50
+ Output schema:
51
+
52
+ ```json
53
+ {
54
+ "results": [
55
+ {
56
+ "uri": "viking://user/<current_user_id>/memories/experiences/example.md",
57
+ "title": "example",
58
+ "score": 0.82,
59
+ "snippet": "Short summary or matched situation"
60
+ }
61
+ ]
62
+ }
63
+ ```
64
+
65
+ Implementation:
66
+
67
+ The runtime tool calls OpenViking `POST /api/v1/search/find` with `target_uri`
68
+ fixed to the current-user shorthand `viking://user/memories/experiences/`.
69
+ Callers provide only `query` and optional `limit`; they cannot override or pass
70
+ `target_uri`. OpenViking resolves the fixed shorthand against the authenticated
71
+ request user. Return only canonical experience memory URIs for that user; never
72
+ hardcode `default` or another user ID.
73
+
74
+ Usage reporting:
75
+
76
+ A completed `search_experience` tool part is counted as an experience recall
77
+ event for every `results[].uri` value.
78
+
79
+ ## Tool: read_experience
80
+
81
+ Purpose: read the full Markdown body of a selected experience and inject it into
82
+ the agent prompt as task execution guidance.
83
+
84
+ Input schema:
85
+
86
+ ```json
87
+ {
88
+ "uri": "viking://user/<current_user_id>/memories/experiences/example.md"
89
+ }
90
+ ```
91
+
92
+ Output schema:
93
+
94
+ ```json
95
+ {
96
+ "uri": "viking://user/<current_user_id>/memories/experiences/example.md",
97
+ "content": "Experience Markdown body"
98
+ }
99
+ ```
100
+
101
+ Implementation:
102
+
103
+ Call OpenViking `GET /api/v1/content/read?uri=<encoded_uri>` for the selected
104
+ experience URI. Always pass the canonical URI returned by `search_experience`;
105
+ do not construct a URI with a hardcoded user ID. The returned content should be
106
+ inserted into the prompt as operational guidance, not as user profile facts.
107
+
108
+ Usage reporting:
109
+
110
+ A completed `read_experience` tool part is counted as an experience injection
111
+ event for `tool_input.uri` or `tool_output.uri`. In this design, reading an
112
+ experience through `read_experience` means the experience was injected into the
113
+ prompt.
114
+
115
+ ## Recommended Flow
116
+
117
+ 1. When a task begins, build a short query from the latest user instruction,
118
+ current plan, active skill name, and important tool/environment context.
119
+ 2. Call `search_experience` before final prompt assembly.
120
+ 3. Review returned titles/snippets and select only experiences likely to affect
121
+ execution.
122
+ 4. Call `read_experience` for selected experience URIs.
123
+ 5. Inject the returned Markdown into the prompt under an explicit experience
124
+ section.
125
+ 6. Continue task execution.
126
+ 7. Commit the session normally. The committed session must include the
127
+ `search_experience` and `read_experience` tool parts so OpenViking can report
128
+ usage.
129
+
130
+ ## Prompt Injection Format
131
+
132
+ Use a compact and explicit block:
133
+
134
+ ```text
135
+ <openviking-experience-memory>
136
+ The following guidance was retrieved from prior task execution experience.
137
+ Use it as operational guidance. Do not treat it as user identity or preference.
138
+
139
+ <experience uri="viking://user/<current_user_id>/memories/experiences/example.md">
140
+ ...experience markdown...
141
+ </experience>
142
+ </openviking-experience-memory>
143
+ ```
144
+
145
+ ## Commit Requirements
146
+
147
+ The session committed to OpenViking must preserve tool parts with:
148
+
149
+ - `tool_name`
150
+ - `tool_status`
151
+ - `tool_input`
152
+ - `tool_output`
153
+ - `tool_id`
154
+
155
+ Only `tool_status == "completed"` is counted. Failed, cancelled, or skipped tool
156
+ parts are ignored by usage reporting.