@openviking/openclaw-plugin 2026.6.18 → 2026.7.15

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 (61) hide show
  1. package/INSTALL-AGENT.md +2 -2
  2. package/INSTALL-ZH.md +2 -2
  3. package/INSTALL.md +2 -2
  4. package/README.md +1 -1
  5. package/auto-recall.ts +6 -6
  6. package/client.ts +14 -6
  7. package/commands/setup.ts +40 -10
  8. package/config.ts +44 -8
  9. package/context-engine.ts +2 -1
  10. package/dist/auto-recall.js +5 -6
  11. package/dist/client.js +11 -4
  12. package/dist/commands/setup.js +26 -10
  13. package/dist/config.js +31 -6
  14. package/dist/context-engine.js +2 -1
  15. package/dist/index.js +4 -4
  16. package/dist/plugin/openviking-archive-tools.js +1 -2
  17. package/dist/plugin/openviking-client-runtime.js +7 -1
  18. package/dist/plugin/openviking-command-definitions.js +3 -3
  19. package/dist/plugin/openviking-import-tools.js +2 -2
  20. package/dist/plugin/openviking-memory-recall-tools.js +4 -4
  21. package/dist/plugin/openviking-memory-tools.js +12 -7
  22. package/dist/plugin/openviking-query-runtime.js +1 -1
  23. package/dist/plugin/openviking-query-tools.js +4 -4
  24. package/dist/plugin/openviking-recall-trace-runtime.js +3 -3
  25. package/dist/plugin/openviking-session-routing-runtime.js +9 -2
  26. package/dist/plugin/openviking-tool-result-tools.js +3 -3
  27. package/dist/query-config.js +7 -1
  28. package/dist/request-headers.js +19 -0
  29. package/dist/routing/identity-routing.js +27 -0
  30. package/dist/routing/memory-uri.js +1 -0
  31. package/dist/services/context-lifecycle-service.js +56 -23
  32. package/dist/services/context-message-adapter.js +2 -9
  33. package/dist/text-utils.js +1 -6
  34. package/index.ts +3 -3
  35. package/install-manifest.json +2 -1
  36. package/openclaw.plugin.json +20 -4
  37. package/package.json +6 -6
  38. package/plugin/command-registration.ts +2 -0
  39. package/plugin/openviking-archive-tools.ts +1 -4
  40. package/plugin/openviking-client-runtime.ts +8 -1
  41. package/plugin/openviking-command-definitions.ts +4 -3
  42. package/plugin/openviking-import-tools.ts +4 -2
  43. package/plugin/openviking-memory-recall-tools.ts +6 -4
  44. package/plugin/openviking-memory-tools.ts +19 -11
  45. package/plugin/openviking-query-runtime.ts +1 -1
  46. package/plugin/openviking-query-tools.ts +6 -4
  47. package/plugin/openviking-recall-trace-runtime.ts +4 -3
  48. package/plugin/openviking-recall-trace-tools.ts +2 -0
  49. package/plugin/openviking-session-routing-runtime.ts +15 -1
  50. package/plugin/openviking-tool-result-tools.ts +0 -6
  51. package/query-config.ts +7 -1
  52. package/request-headers.ts +29 -0
  53. package/routing/identity-routing.ts +41 -0
  54. package/routing/memory-uri.ts +1 -0
  55. package/services/context-lifecycle-service.ts +69 -25
  56. package/services/context-message-adapter.ts +2 -9
  57. package/services/setup/probe-service.ts +35 -6
  58. package/services/setup/setup-flow.ts +61 -7
  59. package/skills/install-openviking-memory/SKILL.md +29 -15
  60. package/skills/openviking-context-database/SKILL.md +3 -3
  61. package/text-utils.ts +1 -6
@@ -2,6 +2,10 @@ import { Type } from "@sinclair/typebox";
2
2
 
3
3
  import type { CommitSessionResult, FindResultItem } from "../client.js";
4
4
  import { clampScore, postProcessMemories } from "../memory-ranking.js";
5
+ import {
6
+ resolveOpenVikingMessagePeerId,
7
+ type OpenVikingPeerRole,
8
+ } from "../routing/identity-routing.js";
5
9
  import { isMemoryUri } from "../routing/memory-uri.js";
6
10
 
7
11
  export type OpenVikingMemoryToolContext = {
@@ -17,6 +21,7 @@ export type OpenVikingMemorySession = {
17
21
  sessionKey?: string;
18
22
  ovSessionId?: string;
19
23
  agentId: string;
24
+ actorPeerId?: string;
20
25
  };
21
26
 
22
27
  export type OpenVikingMemoryClient = {
@@ -30,7 +35,7 @@ export type OpenVikingMemoryClient = {
30
35
  ) => Promise<void>;
31
36
  commitSession: (
32
37
  sessionId: string,
33
- options: { wait: true; agentId: string; keepRecentCount: number },
38
+ options: { wait: true; agentId?: string; keepRecentCount: number },
34
39
  ) => Promise<CommitSessionResult>;
35
40
  deleteUri: (uri: string, agentId?: string) => Promise<void>;
36
41
  find: (
@@ -45,8 +50,7 @@ export type OpenVikingMemoryToolsDeps = {
45
50
  getClient: () => Promise<OpenVikingMemoryClient>;
46
51
  normalizeSessionId: (sessionId: string) => string;
47
52
  createTempSessionId: () => string;
48
- extractSenderId: (ctx?: OpenVikingMemoryToolContext) => string | undefined;
49
- toRoleId: (senderId?: string) => string | undefined;
53
+ peerRole: OpenVikingPeerRole;
50
54
  resolvePluginSessionRouting: (ctx?: OpenVikingMemoryToolContext) => OpenVikingMemorySession;
51
55
  isBypassedSession: (ctx?: OpenVikingMemoryToolContext) => boolean;
52
56
  makeBypassedToolResult: (toolName: string) => unknown;
@@ -71,7 +75,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
71
75
  name: "memory_store",
72
76
  label: "Memory Store (OpenViking)",
73
77
  description:
74
- "Store text in OpenViking memory pipeline by writing to a session and running memory extraction.",
78
+ "Store text in OpenViking memory pipeline by writing to a session and running memory extraction. Use when the user explicitly asks to remember, save, or store an important long-term fact, preference, project, or decision; automatic capture is threshold/commit dependent.",
75
79
  parameters: Type.Object({
76
80
  text: Type.String({ description: "Information to store as memory source text" }),
77
81
  role: Type.Optional(Type.String({ description: "Session role, default user" })),
@@ -107,18 +111,22 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
107
111
  sessionId = deps.createTempSessionId();
108
112
  usedTempSession = true;
109
113
  }
110
- const roleId = role === "user" ? deps.toRoleId(deps.extractSenderId(ctx)) : undefined;
114
+ const peerId = resolveOpenVikingMessagePeerId({
115
+ peerRole: deps.peerRole,
116
+ role,
117
+ personPeerId: session.actorPeerId,
118
+ assistantPeerId: session.actorPeerId,
119
+ });
111
120
  await client.addSessionMessage(
112
121
  sessionId,
113
122
  role,
114
123
  [{ type: "text", text }],
115
- session.agentId,
116
124
  undefined,
117
- roleId,
125
+ undefined,
126
+ peerId,
118
127
  );
119
128
  const commitResult = await client.commitSession(sessionId, {
120
129
  wait: true,
121
- agentId: session.agentId,
122
130
  keepRecentCount: 0,
123
131
  });
124
132
  const memoriesCount = totalCommitMemories(commitResult);
@@ -216,7 +224,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
216
224
  details: { action: "rejected", uri },
217
225
  };
218
226
  }
219
- await client.deleteUri(uri, session.agentId);
227
+ await client.deleteUri(uri, session.actorPeerId);
220
228
  return {
221
229
  content: [{ type: "text", text: `Forgotten: ${uri}` }],
222
230
  details: { action: "deleted", uri },
@@ -252,7 +260,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
252
260
  limit: requestLimit,
253
261
  scoreThreshold: 0,
254
262
  },
255
- session.agentId,
263
+ session.actorPeerId,
256
264
  );
257
265
  const candidates = postProcessMemories(result.memories ?? [], {
258
266
  limit: requestLimit,
@@ -272,7 +280,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
272
280
  }
273
281
  const top = candidates[0];
274
282
  if (candidates.length === 1 && clampScore(top.score) >= 0.85) {
275
- await client.deleteUri(top.uri, session.agentId);
283
+ await client.deleteUri(top.uri, session.actorPeerId);
276
284
  return {
277
285
  content: [{ type: "text", text: `Forgotten: ${top.uri}` }],
278
286
  details: { action: "deleted", uri: top.uri, score: top.score ?? 0 },
@@ -445,7 +445,7 @@ export function createOpenVikingQueryRuntime<TQueryConfigContext>(deps: OpenViki
445
445
  sessionId: traceCtx?.sessionId,
446
446
  sessionKey: traceCtx?.sessionKey,
447
447
  ovSessionId: traceCtx?.ovSessionId,
448
- agentId,
448
+ agentId: traceCtx?.agentId,
449
449
  source: "ov_search",
450
450
  operationType: "semantic_find",
451
451
  resourceTypes: [...new Set(searches.map((search) => search.resourceType).filter((resourceType): resourceType is RecallResourceType => resourceType !== "archive"))],
@@ -5,6 +5,7 @@ export type OpenVikingQueryToolContext = {
5
5
  sessionId?: string;
6
6
  agentId?: string;
7
7
  senderId?: string;
8
+ requesterSenderId?: string;
8
9
  };
9
10
 
10
11
  export type OpenVikingQuerySession = {
@@ -12,6 +13,7 @@ export type OpenVikingQuerySession = {
12
13
  sessionKey?: string;
13
14
  ovSessionId?: string;
14
15
  agentId: string;
16
+ actorPeerId?: string;
15
17
  };
16
18
 
17
19
  export type OVSearchInput = {
@@ -74,7 +76,7 @@ export function registerOpenVikingQueryTools(deps: OpenVikingQueryToolsDeps): vo
74
76
  query: String((params as { query?: unknown }).query ?? ""),
75
77
  uri: typeof params.uri === "string" ? params.uri : undefined,
76
78
  limit: typeof params.limit === "number" ? params.limit : undefined,
77
- }, session.agentId, session);
79
+ }, session.actorPeerId, session);
78
80
  },
79
81
  }),
80
82
  { name: "ov_search" },
@@ -99,7 +101,7 @@ export function registerOpenVikingQueryTools(deps: OpenVikingQueryToolsDeps): vo
99
101
  const session = deps.resolvePluginSessionRouting(ctx);
100
102
  return deps.readOpenVikingContent({
101
103
  uri: String((params as { uri?: unknown }).uri ?? ""),
102
- }, session.agentId);
104
+ }, session.actorPeerId);
103
105
  },
104
106
  }),
105
107
  { name: "ov_read" },
@@ -125,7 +127,7 @@ export function registerOpenVikingQueryTools(deps: OpenVikingQueryToolsDeps): vo
125
127
  const uris = Array.isArray((params as { uris?: unknown }).uris)
126
128
  ? (params as { uris: unknown[] }).uris.map((uri) => String(uri))
127
129
  : [];
128
- return deps.multiReadOpenVikingContent({ uris }, session.agentId);
130
+ return deps.multiReadOpenVikingContent({ uris }, session.actorPeerId);
129
131
  },
130
132
  }),
131
133
  { name: "ov_multi_read" },
@@ -153,7 +155,7 @@ export function registerOpenVikingQueryTools(deps: OpenVikingQueryToolsDeps): vo
153
155
  recursive: typeof params.recursive === "boolean" ? params.recursive : undefined,
154
156
  simple: typeof params.simple === "boolean" ? params.simple : undefined,
155
157
  limit: typeof params.limit === "number" ? params.limit : undefined,
156
- }, session.agentId);
158
+ }, session.actorPeerId);
157
159
  },
158
160
  }),
159
161
  { name: "ov_list" },
@@ -26,6 +26,7 @@ export type OpenVikingRecallTraceSession = {
26
26
  sessionKey?: string;
27
27
  ovSessionId?: string;
28
28
  agentId: string;
29
+ actorPeerId?: string;
29
30
  };
30
31
 
31
32
  export type OpenVikingRecallTraceQueryResult = {
@@ -248,11 +249,11 @@ export function createOpenVikingRecallTraceRuntime(deps: OpenVikingRecallTraceRu
248
249
  for (const fallbackQuery of fallbackIdentities) {
249
250
  const fallback = await deps.traceRecorder.queryWithFallback(fallbackQuery);
250
251
  if (fallback.entries.length > 0) {
251
- return enrichTraceEntriesWithContent(fallback, shouldIncludeTraceContent(input), session.agentId);
252
+ return enrichTraceEntriesWithContent(fallback, shouldIncludeTraceContent(input), session.actorPeerId);
252
253
  }
253
254
  }
254
255
  }
255
- return enrichTraceEntriesWithContent(base, shouldIncludeTraceContent(input), session.agentId);
256
+ return enrichTraceEntriesWithContent(base, shouldIncludeTraceContent(input), session.actorPeerId);
256
257
  };
257
258
 
258
259
  const handleUriDetail = async (request?: RecallTraceRouteRequest) => {
@@ -298,7 +299,7 @@ export function createOpenVikingRecallTraceRuntime(deps: OpenVikingRecallTraceRu
298
299
  }
299
300
  try {
300
301
  const client = await deps.getClient();
301
- const content = await client.read(uri, toString(query.agentId) ?? session.agentId);
302
+ const content = await client.read(uri, session.actorPeerId);
302
303
  const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
303
304
  const chars = Array.from(text);
304
305
  const slice = chars.slice(offset, offset + contentLimit).join("");
@@ -11,6 +11,7 @@ export type OpenVikingRecallTraceToolContext = {
11
11
  sessionId?: string;
12
12
  agentId?: string;
13
13
  senderId?: string;
14
+ requesterSenderId?: string;
14
15
  };
15
16
 
16
17
  export type OpenVikingRecallTraceSession = {
@@ -18,6 +19,7 @@ export type OpenVikingRecallTraceSession = {
18
19
  sessionKey?: string;
19
20
  ovSessionId?: string;
20
21
  agentId: string;
22
+ actorPeerId?: string;
21
23
  };
22
24
 
23
25
  export type RecallTraceToolInput = {
@@ -2,6 +2,9 @@ import type { QueryConfigContext } from "../query-config.js";
2
2
  import {
3
3
  createSessionAgentResolver,
4
4
  openClawSessionToOvStorageId,
5
+ resolveOpenVikingActorPeerId,
6
+ sanitizeOpenVikingPeerId,
7
+ type OpenVikingPeerRole,
5
8
  } from "../routing/identity-routing.js";
6
9
 
7
10
  type Logger = {
@@ -10,6 +13,8 @@ type Logger = {
10
13
 
11
14
  export type SessionAgentLookup = {
12
15
  agentId?: string;
16
+ senderId?: string;
17
+ requesterSenderId?: string;
13
18
  sessionId?: string;
14
19
  sessionKey?: string;
15
20
  ovSessionId?: string;
@@ -20,13 +25,16 @@ export type PluginSessionRouting = {
20
25
  sessionKey?: string;
21
26
  ovSessionId?: string;
22
27
  agentId: string;
28
+ actorPeerId?: string;
23
29
  };
24
30
 
25
31
  export function createOpenVikingSessionRoutingRuntime(options: {
32
+ peerRole?: OpenVikingPeerRole;
26
33
  peerPrefix: string;
27
34
  logFindRequests: boolean;
28
35
  logger: Logger;
29
36
  }) {
37
+ const peerRole = options.peerRole ?? "assistant";
30
38
  const sessionAgentResolver = createSessionAgentResolver(options.peerPrefix);
31
39
 
32
40
  const rememberSessionAgentId = (ctx: SessionAgentLookup) => {
@@ -81,11 +89,17 @@ export function createOpenVikingSessionRoutingRuntime(options: {
81
89
  };
82
90
  rememberSessionAgentId(session);
83
91
 
92
+ const agentId = resolveAgentId(session.sessionId, session.sessionKey, session.ovSessionId);
84
93
  return {
85
94
  sessionId: session.sessionId,
86
95
  sessionKey: session.sessionKey,
87
96
  ovSessionId: session.ovSessionId,
88
- agentId: resolveAgentId(session.sessionId, session.sessionKey, session.ovSessionId),
97
+ agentId,
98
+ actorPeerId: resolveOpenVikingActorPeerId({
99
+ peerRole,
100
+ personPeerId: sanitizeOpenVikingPeerId(ctx?.requesterSenderId ?? ctx?.senderId),
101
+ assistantPeerId: agentId,
102
+ }),
89
103
  };
90
104
  };
91
105
 
@@ -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
package/query-config.ts CHANGED
@@ -231,7 +231,13 @@ export class RuntimeQueryConfigStore {
231
231
  const { params, warnings } = normalizeRuntimeQueryParams(patch);
232
232
  const key = this.keyForScope(scope, ctx);
233
233
  const bucket = scope === "claw" ? this.data.claws : this.data.sessions;
234
- bucket[key] = { params, updatedAt: Date.now(), updatedBy: "command", peerId: ctx.peerId };
234
+ const previous = bucket[key];
235
+ bucket[key] = {
236
+ params: { ...(previous?.params ?? {}), ...params },
237
+ updatedAt: Date.now(),
238
+ updatedBy: "command",
239
+ peerId: ctx.peerId,
240
+ };
235
241
  this.data.updatedAt = Date.now();
236
242
  await this.persist();
237
243
  return { warnings };
@@ -0,0 +1,29 @@
1
+ export type OpenVikingRequestHeaders = Record<string, string>;
2
+
3
+ export type ResolveOpenVikingRequestHeadersOptions = {
4
+ headers?: unknown;
5
+ };
6
+
7
+ export function resolveOpenVikingRequestHeaders(
8
+ options: ResolveOpenVikingRequestHeadersOptions = {},
9
+ ): OpenVikingRequestHeaders {
10
+ return cleanOpenVikingRequestHeaders(options.headers);
11
+ }
12
+
13
+ export function cleanOpenVikingRequestHeaders(headers: unknown): OpenVikingRequestHeaders {
14
+ if (headers === undefined || headers === null) {
15
+ return {};
16
+ }
17
+ if (typeof headers !== "object" || Array.isArray(headers)) {
18
+ throw new Error("openviking request headers must be an object");
19
+ }
20
+
21
+ const out: OpenVikingRequestHeaders = {};
22
+ for (const [key, value] of Object.entries(headers as Record<string, unknown>)) {
23
+ if (typeof value !== "string") {
24
+ throw new Error(`openviking request header ${key} must be a string`);
25
+ }
26
+ out[key] = value;
27
+ }
28
+ return out;
29
+ }
@@ -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;
@@ -123,12 +128,15 @@ export type AfterTurnOpenVikingSessionParams = {
123
128
  messages?: AgentMessage[];
124
129
  prePromptMessageCount?: number;
125
130
  isHeartbeat?: boolean;
131
+ /** Model context window in tokens; the auto-commit threshold is a fraction of this. */
132
+ tokenBudget: number;
126
133
  runtimeContext?: Record<string, unknown>;
127
134
  cfg: {
128
135
  autoCapture: boolean;
129
- commitTokenThreshold: number;
136
+ commitTokenThresholdRatio: number;
130
137
  commitKeepRecentCount: number;
131
138
  logFindRequests: boolean;
139
+ peer_role?: OpenVikingPeerRole;
132
140
  };
133
141
  getClient: () => Promise<AfterTurnClient>;
134
142
  logger: ContextEngineLifecycleLogger;
@@ -175,7 +183,6 @@ function sleep(ms: number): Promise<void> {
175
183
  async function pollPhase2ExtractionOutcome(
176
184
  client: AfterTurnClient,
177
185
  taskId: string,
178
- agentId: string,
179
186
  logger: ContextEngineLifecycleLogger,
180
187
  sessionLabel: string,
181
188
  ): Promise<void> {
@@ -183,7 +190,7 @@ async function pollPhase2ExtractionOutcome(
183
190
  try {
184
191
  while (Date.now() < deadline) {
185
192
  await sleep(PHASE2_POLL_INTERVAL_MS);
186
- const task = await client.getTask(taskId, agentId).catch((e) => {
193
+ const task = await client.getTask(taskId).catch((e) => {
187
194
  logger.warn?.(`openviking: phase2 getTask failed task_id=${taskId}: ${String(e)}`);
188
195
  return null;
189
196
  });
@@ -338,7 +345,6 @@ export async function commitOpenVikingSession({
338
345
  sessionKey,
339
346
  getClient,
340
347
  logger,
341
- resolveAgentId,
342
348
  rememberSessionAgentId,
343
349
  isBypassedSession,
344
350
  }: CommitOpenVikingSessionParams): Promise<boolean> {
@@ -356,10 +362,8 @@ export async function commitOpenVikingSession({
356
362
  sessionKey,
357
363
  ovSessionId: ovId,
358
364
  });
359
- const agentId = resolveAgentId(sessionId, sessionKey, ovId);
360
365
  const commitResult = await client.commitSession(ovId, {
361
366
  wait: true,
362
- agentId,
363
367
  keepRecentCount: 0,
364
368
  });
365
369
  const memCount = totalExtractedMemories(commitResult.memories_extracted);
@@ -404,6 +408,11 @@ function assemblePassthrough(
404
408
  return { messages: liveMessages, estimatedTokens: originalTokens };
405
409
  }
406
410
 
411
+ function isSessionNotFoundError(err: unknown): boolean {
412
+ const errorMessage = String(err);
413
+ return errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found");
414
+ }
415
+
407
416
  export async function assembleOpenVikingSession({
408
417
  sessionId,
409
418
  sessionKey,
@@ -485,6 +494,11 @@ export async function assembleOpenVikingSession({
485
494
  const client = await getClient();
486
495
  const routingRef = sessionId ?? sessionKey ?? ovSessionId;
487
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
+ });
488
502
  const queryConfig = await queryConfigStore?.getEffective({
489
503
  agentId,
490
504
  sessionId,
@@ -496,6 +510,7 @@ export async function assembleOpenVikingSession({
496
510
  queryConfig,
497
511
  client,
498
512
  agentId,
513
+ actorPeerId,
499
514
  queryText: recallQuery.query,
500
515
  logger,
501
516
  verbose: (message) => logger.info(message),
@@ -546,9 +561,7 @@ export async function assembleOpenVikingSession({
546
561
 
547
562
  try {
548
563
  const client = await getClient();
549
- const routingRef = sessionId ?? sessionKey ?? ovSessionId;
550
- const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
551
- const ctx = await client.getSessionContext(ovSessionId, tokenBudget, agentId);
564
+ const ctx = await client.getSessionContext(ovSessionId, tokenBudget);
552
565
 
553
566
  const preAbstracts = ctx?.pre_archive_abstracts ?? [];
554
567
  const hasArchives = !!ctx?.latest_archive_overview || preAbstracts.length > 0;
@@ -625,6 +638,27 @@ export async function assembleOpenVikingSession({
625
638
  ...(instruction.text ? { systemPromptAddition: instruction.text } : {}),
626
639
  };
627
640
  } catch (err) {
641
+ if (isSessionNotFoundError(err)) {
642
+ const errorMessage = String(err);
643
+ logger.info(
644
+ `openviking: assemble skipped because OV session does not exist ` +
645
+ `(session=${ovSessionId}, tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)})`,
646
+ );
647
+ return assemblePassthrough({
648
+ diag,
649
+ ovSessionId,
650
+ reason: "session_not_found",
651
+ liveMessages: messages,
652
+ originalTokens,
653
+ extra: {
654
+ error: errorMessage,
655
+ tokenBudget,
656
+ agentId: resolveAgentId(ovSessionId),
657
+ senderIdFound: sender.found,
658
+ senderId: sender.senderId ?? null,
659
+ },
660
+ });
661
+ }
628
662
  logger.warn?.(
629
663
  `openviking: assemble failed for session=${ovSessionId}, ` +
630
664
  `tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)}: ${String(err)}`,
@@ -688,7 +722,7 @@ function extractRuntimeAgentId(runtimeContext: Record<string, unknown> | undefin
688
722
  }
689
723
 
690
724
  function isToolOnlyMessage(msg: ExtractedTurnMessage): boolean {
691
- return msg.role === "user" && msg.parts.length > 0 && msg.parts.every((part) => part.type === "tool");
725
+ return msg.role === "assistant" && msg.parts.length > 0 && msg.parts.every((part) => part.type === "tool");
692
726
  }
693
727
 
694
728
  function coalesceConsecutiveToolMessages(messages: ExtractedTurnMessage[]): ExtractedTurnMessage[] {
@@ -705,7 +739,7 @@ function coalesceConsecutiveToolMessages(messages: ExtractedTurnMessage[]): Extr
705
739
  for (const msg of messages) {
706
740
  if (isToolOnlyMessage(msg)) {
707
741
  if (!pendingTools) {
708
- pendingTools = { role: "user", parts: [] };
742
+ pendingTools = { role: "assistant", parts: [] };
709
743
  }
710
744
  pendingTools.parts.push(...msg.parts);
711
745
  continue;
@@ -753,6 +787,7 @@ export async function afterTurnOpenVikingSession({
753
787
  messages: rawMessages,
754
788
  prePromptMessageCount,
755
789
  isHeartbeat,
790
+ tokenBudget,
756
791
  runtimeContext,
757
792
  cfg,
758
793
  getClient,
@@ -870,21 +905,30 @@ export async function afterTurnOpenVikingSession({
870
905
  ovSessionId,
871
906
  msg.role,
872
907
  ovParts,
873
- agentId,
908
+ undefined,
874
909
  createdAt,
875
- msg.role === "user" ? senderRoleId : undefined,
910
+ resolveOpenVikingMessagePeerId({
911
+ peerRole: cfg.peer_role ?? "assistant",
912
+ role: msg.role,
913
+ personPeerId: senderRoleId,
914
+ assistantPeerId: agentId,
915
+ }),
876
916
  );
877
917
  }
878
918
  }
879
919
 
880
- const session = await client.getSession(ovSessionId, agentId);
920
+ const session = await client.getSession(ovSessionId);
881
921
  const pendingTokens = session.pending_tokens ?? 0;
882
922
 
883
- if (pendingTokens < cfg.commitTokenThreshold) {
923
+ const commitTokenThreshold = Math.floor(tokenBudget * cfg.commitTokenThresholdRatio);
924
+
925
+ if (pendingTokens < commitTokenThreshold) {
884
926
  diag("afterTurn_skip", ovSessionId, {
885
927
  reason: "below_threshold",
886
928
  pendingTokens,
887
- commitTokenThreshold: cfg.commitTokenThreshold,
929
+ commitTokenThreshold,
930
+ commitTokenThresholdRatio: cfg.commitTokenThresholdRatio,
931
+ tokenBudget,
888
932
  senderIdFound: sender.found,
889
933
  senderId: sender.senderId ?? null,
890
934
  });
@@ -893,7 +937,6 @@ export async function afterTurnOpenVikingSession({
893
937
 
894
938
  const commitResult = await client.commitSession(ovSessionId, {
895
939
  wait: false,
896
- agentId,
897
940
  keepRecentCount: cfg.commitKeepRecentCount,
898
941
  });
899
942
  logger.info(
@@ -904,7 +947,9 @@ export async function afterTurnOpenVikingSession({
904
947
 
905
948
  diag("afterTurn_commit", ovSessionId, {
906
949
  pendingTokens,
907
- commitTokenThreshold: cfg.commitTokenThreshold,
950
+ commitTokenThreshold,
951
+ commitTokenThresholdRatio: cfg.commitTokenThresholdRatio,
952
+ tokenBudget,
908
953
  status: commitResult.status,
909
954
  archived: commitResult.archived ?? false,
910
955
  taskId: commitResult.task_id ?? null,
@@ -917,7 +962,7 @@ export async function afterTurnOpenVikingSession({
917
962
  `openviking: Phase2 memory extraction runs asynchronously on the server (task_id=${commitResult.task_id}). ` +
918
963
  "memories_extracted appears only after that task completes — not in this immediate response.",
919
964
  );
920
- void pollPhase2ExtractionOutcome(client, commitResult.task_id, agentId, logger, ovSessionId);
965
+ void pollPhase2ExtractionOutcome(client, commitResult.task_id, logger, ovSessionId);
921
966
  }
922
967
  } catch (err) {
923
968
  logger.warn?.(`openviking: afterTurn failed: ${String(err)}`);
@@ -998,7 +1043,7 @@ export async function compactOpenVikingSession({
998
1043
  let preCommitEstimatedTokens: number | undefined;
999
1044
  if (typeof tokensBeforeOriginal !== "number") {
1000
1045
  try {
1001
- const preCtx = await client.getSessionContext(ovSessionId, tokenBudget, agentId);
1046
+ const preCtx = await client.getSessionContext(ovSessionId, tokenBudget);
1002
1047
  if (typeof preCtx.estimatedTokens === "number" && Number.isFinite(preCtx.estimatedTokens)) {
1003
1048
  preCommitEstimatedTokens = preCtx.estimatedTokens;
1004
1049
  }
@@ -1018,7 +1063,6 @@ export async function compactOpenVikingSession({
1018
1063
  );
1019
1064
  const commitResult = await client.commitSession(ovSessionId, {
1020
1065
  wait: true,
1021
- agentId,
1022
1066
  keepRecentCount: 0,
1023
1067
  });
1024
1068
  const memCount = totalExtractedMemories(commitResult.memories_extracted);
@@ -1094,7 +1138,7 @@ export async function compactOpenVikingSession({
1094
1138
  let contextFetchError: string | undefined;
1095
1139
 
1096
1140
  try {
1097
- const ctx = await client.getSessionContext(ovSessionId, tokenBudget, agentId);
1141
+ const ctx = await client.getSessionContext(ovSessionId, tokenBudget);
1098
1142
  logger.info(
1099
1143
  `openviking: compact getSessionContext raw result for ${ovSessionId}: ` +
1100
1144
  JSON.stringify(ctx, null, 2),
@@ -1183,7 +1227,7 @@ export async function compactOpenVikingSession({
1183
1227
  };
1184
1228
  } catch (err) {
1185
1229
  const errorMessage = String(err);
1186
- if (errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found")) {
1230
+ if (isSessionNotFoundError(err)) {
1187
1231
  logger.info(
1188
1232
  `openviking: compact skipped because OV session does not exist ` +
1189
1233
  `(session=${ovSessionId}, agentId=${agentId})`,