@openviking/openclaw-plugin 2026.6.2 → 2026.6.17-dev.2

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 (108) hide show
  1. package/INSTALL-AGENT.md +4 -4
  2. package/INSTALL-ZH.md +68 -8
  3. package/INSTALL.md +12 -8
  4. package/README.md +45 -35
  5. package/adapters/http-transport.ts +3 -0
  6. package/adapters/resource-packager.ts +141 -0
  7. package/auto-recall.ts +258 -27
  8. package/client.ts +201 -177
  9. package/commands/setup.ts +200 -33
  10. package/config/feature-gates.json +10 -0
  11. package/config.ts +428 -85
  12. package/context-engine.ts +71 -1402
  13. package/dist/adapters/http-transport.js +1 -0
  14. package/dist/adapters/resource-packager.js +114 -0
  15. package/dist/auto-recall.js +201 -25
  16. package/dist/client.js +125 -161
  17. package/dist/commands/setup.js +170 -32
  18. package/dist/config.js +285 -64
  19. package/dist/context-engine.js +62 -1173
  20. package/dist/index.js +209 -1489
  21. package/dist/memory-ranking.js +35 -12
  22. package/dist/plugin/command-registration.js +5 -0
  23. package/dist/plugin/openviking-archive-tools.js +210 -0
  24. package/dist/plugin/openviking-bypass-runtime.js +8 -0
  25. package/dist/plugin/openviking-client-runtime.js +28 -0
  26. package/dist/plugin/openviking-command-args.js +146 -0
  27. package/dist/plugin/openviking-command-definitions.js +206 -0
  28. package/dist/plugin/openviking-context-engine-ref.js +11 -0
  29. package/dist/plugin/openviking-context-engine-registration.js +23 -0
  30. package/dist/plugin/openviking-feature-gates.js +250 -0
  31. package/dist/plugin/openviking-import-runtime.js +50 -0
  32. package/dist/plugin/openviking-import-tools.js +86 -0
  33. package/dist/plugin/openviking-lifecycle-hooks.js +33 -0
  34. package/dist/plugin/openviking-memory-recall-tools.js +233 -0
  35. package/dist/plugin/openviking-memory-tools.js +199 -0
  36. package/dist/plugin/openviking-query-config-command.js +203 -0
  37. package/dist/plugin/openviking-query-runtime.js +367 -0
  38. package/dist/plugin/openviking-query-tools.js +91 -0
  39. package/dist/plugin/openviking-recall-trace-runtime.js +387 -0
  40. package/dist/plugin/openviking-recall-trace-tools.js +38 -0
  41. package/dist/plugin/openviking-runtime-state.js +26 -0
  42. package/dist/plugin/openviking-runtime-utils.js +62 -0
  43. package/dist/plugin/openviking-services.js +20 -0
  44. package/dist/plugin/openviking-session-routing-runtime.js +61 -0
  45. package/dist/plugin/openviking-tool-result-tools.js +268 -0
  46. package/dist/plugin/recall-trace-routes.js +77 -0
  47. package/dist/plugin/tool-registration.js +23 -0
  48. package/dist/process-manager.js +1 -1
  49. package/dist/query-config.js +336 -0
  50. package/dist/recall-trace.js +309 -0
  51. package/dist/registries/recall-resource-types.js +58 -0
  52. package/dist/routing/identity-routing.js +152 -0
  53. package/dist/routing/memory-uri.js +7 -0
  54. package/dist/services/context-lifecycle-service.js +847 -0
  55. package/dist/services/context-message-adapter.js +340 -0
  56. package/dist/session-transcript-repair.js +0 -1
  57. package/dist/text-utils.js +14 -31
  58. package/index.ts +271 -1873
  59. package/install-manifest.json +10 -1
  60. package/memory-ranking.ts +46 -10
  61. package/openclaw.plugin.json +198 -36
  62. package/package.json +14 -4
  63. package/plugin/command-registration.ts +34 -0
  64. package/plugin/openviking-archive-tools.ts +301 -0
  65. package/plugin/openviking-bypass-runtime.ts +26 -0
  66. package/plugin/openviking-client-runtime.ts +74 -0
  67. package/plugin/openviking-command-args.ts +187 -0
  68. package/plugin/openviking-command-definitions.ts +294 -0
  69. package/plugin/openviking-context-engine-ref.ts +16 -0
  70. package/plugin/openviking-context-engine-registration.ts +97 -0
  71. package/plugin/openviking-feature-gates.ts +335 -0
  72. package/plugin/openviking-import-runtime.ts +105 -0
  73. package/plugin/openviking-import-tools.ts +133 -0
  74. package/plugin/openviking-lifecycle-hooks.ts +65 -0
  75. package/plugin/openviking-memory-recall-tools.ts +368 -0
  76. package/plugin/openviking-memory-tools.ts +299 -0
  77. package/plugin/openviking-query-config-command.ts +230 -0
  78. package/plugin/openviking-query-formatters.ts +108 -0
  79. package/plugin/openviking-query-runtime.ts +476 -0
  80. package/plugin/openviking-query-tools.ts +161 -0
  81. package/plugin/openviking-recall-trace-runtime.ts +482 -0
  82. package/plugin/openviking-recall-trace-tools.ts +99 -0
  83. package/plugin/openviking-runtime-state.ts +52 -0
  84. package/plugin/openviking-runtime-utils.ts +67 -0
  85. package/plugin/openviking-services.ts +49 -0
  86. package/plugin/openviking-session-routing-runtime.ts +105 -0
  87. package/plugin/openviking-tool-result-tools.ts +397 -0
  88. package/plugin/recall-trace-routes.ts +133 -0
  89. package/plugin/tool-registration.ts +51 -0
  90. package/process-manager.ts +1 -1
  91. package/query-config.ts +400 -0
  92. package/recall-trace.ts +442 -0
  93. package/registries/openviking-tools.ts +84 -0
  94. package/registries/recall-resource-types.ts +74 -0
  95. package/routing/identity-routing.ts +210 -0
  96. package/routing/memory-uri.ts +8 -0
  97. package/services/context-lifecycle-service.ts +1209 -0
  98. package/services/context-message-adapter.ts +375 -0
  99. package/services/setup/config-writer.ts +125 -0
  100. package/services/setup/exit-utils.ts +5 -0
  101. package/services/setup/package-metadata.ts +56 -0
  102. package/services/setup/probe-service.ts +143 -0
  103. package/services/setup/setup-flow.ts +306 -0
  104. package/services/setup/version-compatibility.ts +41 -0
  105. package/session-transcript-repair.ts +0 -2
  106. package/skills/install-openviking-memory/SKILL.md +123 -50
  107. package/skills/openviking-context-database/SKILL.md +286 -0
  108. package/text-utils.ts +15 -34
package/client.ts CHANGED
@@ -7,6 +7,12 @@ import { basename, dirname, join, relative } from "node:path";
7
7
 
8
8
  import { Zip, ZipDeflate } from "fflate";
9
9
 
10
+ import { defaultHttpTransport, type HttpTransport } from "./adapters/http-transport.js";
11
+ import {
12
+ defaultResourcePackager,
13
+ type ResourcePackager,
14
+ } from "./adapters/resource-packager.js";
15
+
10
16
  export type FindResultItem = {
11
17
  uri: string;
12
18
  level?: number;
@@ -24,13 +30,22 @@ export type FindResult = {
24
30
  total?: number;
25
31
  };
26
32
 
33
+ export type FsListEntry = string | Record<string, unknown>;
34
+
35
+ export type FsListResult = FsListEntry[];
36
+
27
37
  export type CaptureMode = "semantic" | "keyword";
28
- export type ScopeName = "user" | "agent";
29
- export type AgentScopeMode = "user_agent" | "agent";
30
- export type RuntimeIdentity = {
31
- userId: string;
32
- agentId: string;
38
+ function userSessionUri(sessionId: string): string {
39
+ return `viking://user/sessions/${encodeURIComponent(sessionId)}`;
40
+ }
41
+
42
+ export type OpenVikingClientOptions = {
43
+ transport?: HttpTransport;
44
+ resourcePackager?: ResourcePackager;
45
+ now?: () => number;
46
+ sleep?: (ms: number) => Promise<void>;
33
47
  };
48
+
34
49
  export type CommitSessionResult = {
35
50
  session_id: string;
36
51
  /** "accepted" (async), "completed", "failed", or "timeout" (wait mode). */
@@ -44,6 +59,16 @@ export type CommitSessionResult = {
44
59
  trace_id?: string;
45
60
  };
46
61
 
62
+ export type OVMemoryPolicySwitch = {
63
+ enabled?: boolean;
64
+ };
65
+
66
+ export type OVMemoryPolicy = {
67
+ self?: OVMemoryPolicySwitch;
68
+ peer?: OVMemoryPolicySwitch;
69
+ memory_types?: string[];
70
+ };
71
+
47
72
  export type TaskResult = {
48
73
  task_id: string;
49
74
  task_type: string;
@@ -195,18 +220,8 @@ function sleep(ms: number): Promise<void> {
195
220
  }
196
221
 
197
222
  const MEMORY_URI_PATTERNS = [
198
- /^viking:\/\/user\/(?:[^/]+(?:\/agent\/[^/]+)?\/)?memories(?:\/|$)/,
199
- /^viking:\/\/agent\/(?:[^/]+(?:\/user\/[^/]+)?\/)?memories(?:\/|$)/,
223
+ /^viking:\/\/user\/(?:[^/]+\/)?memories(?:\/|$)/,
200
224
  ];
201
- const USER_STRUCTURE_DIRS = new Set(["memories", "profile.md", ".abstract.md", ".overview.md"]);
202
- const AGENT_STRUCTURE_DIRS = new Set([
203
- "memories",
204
- "skills",
205
- "instructions",
206
- "workspaces",
207
- ".abstract.md",
208
- ".overview.md",
209
- ]);
210
225
  const REMOTE_RESOURCE_PREFIXES = ["http://", "https://", "git@", "ssh://", "git://"];
211
226
 
212
227
  export function isMemoryUri(uri: string): boolean {
@@ -238,7 +253,10 @@ async function cleanupUploadTempPath(path?: string): Promise<void> {
238
253
  }
239
254
 
240
255
  export class OpenVikingClient {
241
- private identityCache = new Map<string, RuntimeIdentity>();
256
+ private readonly transport: HttpTransport;
257
+ private readonly now: () => number;
258
+ private readonly sleep: (ms: number) => Promise<void>;
259
+ private readonly resourcePackager: ResourcePackager;
242
260
 
243
261
  constructor(
244
262
  private readonly baseUrl: string,
@@ -250,27 +268,24 @@ export class OpenVikingClient {
250
268
  private readonly userId: string = "",
251
269
  /** When set, logs routing for find + session writes (tenant headers + paths; never apiKey). */
252
270
  private readonly routingDebugLog?: (message: string) => void,
253
- private readonly isolateUserScopeByAgent = false,
254
- private readonly isolateAgentScopeByUser = true,
255
- ) {}
271
+ optionsOrLegacyUserScope: OpenVikingClientOptions | boolean = {},
272
+ _legacyAgentScope?: boolean,
273
+ legacyOptions?: OpenVikingClientOptions,
274
+ ) {
275
+ const options =
276
+ typeof optionsOrLegacyUserScope === "object" && optionsOrLegacyUserScope !== null
277
+ ? optionsOrLegacyUserScope
278
+ : (legacyOptions ?? {});
279
+ this.transport = options.transport ?? defaultHttpTransport;
280
+ this.now = options.now ?? Date.now;
281
+ this.sleep = options.sleep ?? sleep;
282
+ this.resourcePackager = options.resourcePackager ?? defaultResourcePackager;
283
+ }
256
284
 
257
285
  getDefaultAgentId(): string {
258
286
  return this.defaultAgentId;
259
287
  }
260
288
 
261
- private resolveEffectiveAgentId(agentId?: string): string {
262
- const explicit = agentId?.trim();
263
- if (explicit) {
264
- return explicit;
265
- }
266
- const prefix = this.defaultAgentId.trim();
267
- return prefix ? `${prefix}_main` : "main";
268
- }
269
-
270
- async getResolvedIdentity(agentId?: string): Promise<RuntimeIdentity> {
271
- return this.getRuntimeIdentity(agentId);
272
- }
273
-
274
289
  private resolveTenantHeaders():
275
290
  | { apiKey?: string; accountId?: string; userId?: string }
276
291
  {
@@ -284,27 +299,35 @@ export class OpenVikingClient {
284
299
  };
285
300
  }
286
301
 
302
+ private resolveActorPeerHeader(actorPeerId?: string): string | undefined {
303
+ const value = actorPeerId?.trim();
304
+ return value || undefined;
305
+ }
306
+
307
+ private resolveDefaultActorPeerHeader(): string {
308
+ const peerPrefix = this.defaultAgentId.trim();
309
+ return peerPrefix ? `${peerPrefix}_main` : "main";
310
+ }
311
+
287
312
  private async emitRoutingDebug(
288
313
  label: string,
289
314
  detail: Record<string, unknown>,
290
- agentId?: string,
315
+ actorPeerId?: string,
291
316
  ): Promise<void> {
292
317
  if (!this.routingDebugLog) {
293
318
  return;
294
319
  }
295
- const effectiveAgentId = this.resolveEffectiveAgentId(agentId);
296
- const identity = await this.getRuntimeIdentity(agentId);
297
320
  const tenantHeaders = this.resolveTenantHeaders();
321
+ const actorPeerHeader = this.resolveActorPeerHeader(actorPeerId);
298
322
  this.routingDebugLog(
299
323
  `openviking: ${label} ` +
300
324
  JSON.stringify({
301
325
  ...detail,
302
- X_OpenViking_Agent: effectiveAgentId,
303
326
  X_OpenViking_Account: tenantHeaders.accountId ?? null,
304
327
  X_OpenViking_User: tenantHeaders.userId ?? null,
305
- resolved_user_id: identity.userId,
328
+ X_OpenViking_Actor_Peer: actorPeerHeader ?? null,
306
329
  session_vfs_hint: detail.sessionId
307
- ? `viking://session/${String(detail.sessionId)}`
330
+ ? userSessionUri(String(detail.sessionId))
308
331
  : undefined,
309
332
  }),
310
333
  );
@@ -313,10 +336,9 @@ export class OpenVikingClient {
313
336
  private async request<T>(
314
337
  path: string,
315
338
  init: RequestInit = {},
316
- agentId?: string,
317
339
  requestTimeoutMs?: number,
340
+ actorPeerId?: string,
318
341
  ): Promise<T> {
319
- const effectiveAgentId = this.resolveEffectiveAgentId(agentId);
320
342
  const controller = new AbortController();
321
343
  const timer = setTimeout(() => controller.abort(), requestTimeoutMs ?? this.timeoutMs);
322
344
  try {
@@ -331,14 +353,15 @@ export class OpenVikingClient {
331
353
  if (tenantHeaders.userId) {
332
354
  headers.set("X-OpenViking-User", tenantHeaders.userId);
333
355
  }
334
- if (effectiveAgentId) {
335
- headers.set("X-OpenViking-Agent", effectiveAgentId);
356
+ const actorPeerHeader = this.resolveActorPeerHeader(actorPeerId);
357
+ if (actorPeerHeader) {
358
+ headers.set("X-OpenViking-Actor-Peer", actorPeerHeader);
336
359
  }
337
360
  if (init.body && !(init.body instanceof FormData) && !headers.has("Content-Type")) {
338
361
  headers.set("Content-Type", "application/json");
339
362
  }
340
363
 
341
- const response = await fetch(`${this.baseUrl}${path}`, {
364
+ const response = await this.transport(`${this.baseUrl}${path}`, {
342
365
  ...init,
343
366
  headers,
344
367
  signal: controller.signal,
@@ -362,96 +385,81 @@ export class OpenVikingClient {
362
385
  }
363
386
  }
364
387
 
365
- async healthCheck(agentId?: string, requestTimeoutMs?: number): Promise<void> {
366
- await this.request<{ status: string }>("/health", {}, agentId, requestTimeoutMs);
367
- }
368
-
369
- private async getRuntimeIdentity(agentId?: string): Promise<RuntimeIdentity> {
370
- const effectiveAgentId = this.resolveEffectiveAgentId(agentId);
371
- const cached = this.identityCache.get(effectiveAgentId);
372
- if (cached) {
373
- return cached;
374
- }
375
- const fallback: RuntimeIdentity = { userId: "default", agentId: effectiveAgentId };
376
- try {
377
- const status = await this.request<{ user?: unknown }>("/api/v1/system/status", {}, agentId);
378
- const userId =
379
- typeof status.user === "string" && status.user.trim() ? status.user.trim() : "default";
380
- const identity: RuntimeIdentity = { userId, agentId: effectiveAgentId };
381
- this.identityCache.set(effectiveAgentId, identity);
382
- return identity;
383
- } catch {
384
- this.identityCache.set(effectiveAgentId, fallback);
385
- return fallback;
386
- }
388
+ async healthCheck(requestTimeoutMs?: number, actorPeerId?: string): Promise<void> {
389
+ await this.request<{ status: string }>(
390
+ "/health",
391
+ {},
392
+ requestTimeoutMs,
393
+ actorPeerId ?? this.resolveDefaultActorPeerHeader(),
394
+ );
387
395
  }
388
396
 
389
- private async buildCanonicalRoot(scope: ScopeName, agentId?: string): Promise<string> {
390
- const identity = await this.getRuntimeIdentity(agentId);
391
- if (scope === "user") {
392
- const root = this.isolateUserScopeByAgent
393
- ? `viking://user/${identity.userId}/agent/${identity.agentId}`
394
- : `viking://user/${identity.userId}`;
395
- return root;
397
+ async createSession(
398
+ sessionId: string,
399
+ options?: { memoryPolicy?: OVMemoryPolicy },
400
+ ): Promise<{ session_id: string; user?: unknown }> {
401
+ const body: Record<string, unknown> = { session_id: sessionId };
402
+ if (options?.memoryPolicy) {
403
+ body.memory_policy = options.memoryPolicy;
396
404
  }
397
- const root = this.isolateAgentScopeByUser
398
- ? `viking://agent/${identity.agentId}/user/${identity.userId}`
399
- : `viking://agent/${identity.agentId}`;
400
- return root;
405
+ return this.request<{ session_id: string; user?: unknown }>(
406
+ "/api/v1/sessions",
407
+ { method: "POST", body: JSON.stringify(body) },
408
+ );
401
409
  }
402
410
 
403
- private async normalizeTargetUri(targetUri: string, agentId?: string): Promise<string> {
404
- const trimmed = targetUri.trim().replace(/\/+$/, "");
405
- const match = trimmed.match(/^viking:\/\/(user|agent)(?:\/(.*))?$/);
406
- if (!match) {
407
- return trimmed;
408
- }
409
- const scope = match[1] as ScopeName;
410
- const rawRest = (match[2] ?? "").trim();
411
- if (!rawRest) {
412
- return trimmed;
413
- }
414
- const parts = rawRest.split("/").filter(Boolean);
415
- if (parts.length === 0) {
416
- return trimmed;
417
- }
418
-
419
- const reservedDirs = scope === "user" ? USER_STRUCTURE_DIRS : AGENT_STRUCTURE_DIRS;
420
- if (!reservedDirs.has(parts[0]!)) {
421
- return trimmed;
411
+ async ensureSession(
412
+ sessionId: string,
413
+ options?: { memoryPolicy?: OVMemoryPolicy },
414
+ ): Promise<boolean> {
415
+ try {
416
+ await this.createSession(sessionId, options);
417
+ return true;
418
+ } catch (err) {
419
+ const message = err instanceof Error ? err.message : String(err);
420
+ if (message.includes("[ALREADY_EXISTS]")) {
421
+ return false;
422
+ }
423
+ throw err;
422
424
  }
423
-
424
- const root = await this.buildCanonicalRoot(scope, agentId);
425
- return `${root}/${parts.join("/")}`;
426
425
  }
427
426
 
428
427
  async find(
429
428
  query: string,
430
429
  options: {
431
- targetUri: string;
432
- limit: number;
430
+ targetUri?: string;
431
+ limit?: number;
433
432
  scoreThreshold?: number;
433
+ contextType?: string | string[];
434
+ actorPeerId?: string;
434
435
  },
435
- agentId?: string,
436
+ legacyActorPeerId?: string,
436
437
  ): Promise<FindResult> {
437
- const normalizedTargetUri = await this.normalizeTargetUri(options.targetUri, agentId);
438
- const body = {
438
+ const targetUri = options.targetUri?.trim().replace(/\/+$/, "") ?? "";
439
+ const body: {
440
+ query: string;
441
+ target_uri?: string;
442
+ limit?: number;
443
+ score_threshold?: number;
444
+ context_type?: string | string[];
445
+ } = {
439
446
  query,
440
- target_uri: normalizedTargetUri,
441
447
  limit: options.limit,
442
448
  score_threshold: options.scoreThreshold,
449
+ context_type: options.contextType,
443
450
  };
444
- const effectiveAgentId = this.resolveEffectiveAgentId(agentId);
445
- const identity = await this.getRuntimeIdentity(agentId);
451
+ if (targetUri) {
452
+ body.target_uri = targetUri;
453
+ }
454
+ const actorPeerId = this.resolveActorPeerHeader(options.actorPeerId ?? legacyActorPeerId);
446
455
  const tenantHeaders = this.resolveTenantHeaders();
447
456
  this.routingDebugLog?.(
448
457
  `openviking: find POST ${this.baseUrl}/api/v1/search/find ` +
449
458
  JSON.stringify({
450
- X_OpenViking_Agent: effectiveAgentId,
451
459
  X_OpenViking_Account: tenantHeaders.accountId ?? null,
452
460
  X_OpenViking_User: tenantHeaders.userId ?? null,
453
- resolved_user_id: identity.userId,
454
- target_uri: normalizedTargetUri,
461
+ X_OpenViking_Actor_Peer: actorPeerId ?? null,
462
+ target_uri: targetUri || null,
455
463
  target_uri_input: options.targetUri,
456
464
  query:
457
465
  query.length > 4000
@@ -459,19 +467,51 @@ export class OpenVikingClient {
459
467
  : query,
460
468
  limit: body.limit,
461
469
  score_threshold: body.score_threshold ?? null,
470
+ context_type: body.context_type ?? null,
462
471
  }),
463
472
  );
464
473
  return this.request<FindResult>("/api/v1/search/find", {
465
474
  method: "POST",
466
475
  body: JSON.stringify(body),
467
- }, agentId);
476
+ }, undefined, actorPeerId);
468
477
  }
469
478
 
470
- async read(uri: string, agentId?: string): Promise<string> {
479
+ async read(uri: string, actorPeerId?: string): Promise<string> {
471
480
  return this.request<string>(
472
481
  `/api/v1/content/read?uri=${encodeURIComponent(uri)}`,
473
482
  {},
474
- agentId,
483
+ undefined,
484
+ actorPeerId,
485
+ );
486
+ }
487
+
488
+ async list(
489
+ uri: string,
490
+ options?: {
491
+ recursive?: boolean;
492
+ simple?: boolean;
493
+ output?: "agent" | "original";
494
+ absLimit?: number;
495
+ showAllHidden?: boolean;
496
+ nodeLimit?: number;
497
+ actorPeerId?: string;
498
+ },
499
+ ): Promise<FsListResult> {
500
+ const normalizedUri = uri.trim().replace(/\/+$/, "");
501
+ const params = new URLSearchParams({
502
+ uri: normalizedUri,
503
+ recursive: String(options?.recursive ?? false),
504
+ simple: String(options?.simple ?? false),
505
+ output: options?.output ?? "agent",
506
+ abs_limit: String(options?.absLimit ?? 256),
507
+ show_all_hidden: String(options?.showAllHidden ?? false),
508
+ node_limit: String(options?.nodeLimit ?? 1000),
509
+ });
510
+ return this.request<FsListResult>(
511
+ `/api/v1/fs/ls?${params.toString()}`,
512
+ {},
513
+ undefined,
514
+ options?.actorPeerId,
475
515
  );
476
516
  }
477
517
 
@@ -479,7 +519,6 @@ export class OpenVikingClient {
479
519
  sessionId: string,
480
520
  toolResultId: string,
481
521
  options?: { offset?: number; limit?: number; includeMetadata?: boolean },
482
- agentId?: string,
483
522
  ): Promise<ToolResultReadResult> {
484
523
  const params = new URLSearchParams();
485
524
  if (options?.offset !== undefined) params.set("offset", String(options.offset));
@@ -491,7 +530,6 @@ export class OpenVikingClient {
491
530
  return this.request<ToolResultReadResult>(
492
531
  `/api/v1/sessions/${encodeURIComponent(sessionId)}/tool-results/${encodeURIComponent(toolResultId)}${query ? `?${query}` : ""}`,
493
532
  {},
494
- agentId,
495
533
  );
496
534
  }
497
535
 
@@ -500,7 +538,6 @@ export class OpenVikingClient {
500
538
  toolResultId: string,
501
539
  queryText: string,
502
540
  options?: { limit?: number; contextChars?: number },
503
- agentId?: string,
504
541
  ): Promise<ToolResultSearchResult> {
505
542
  const params = new URLSearchParams({ q: queryText });
506
543
  if (options?.limit !== undefined) params.set("limit", String(options.limit));
@@ -510,14 +547,12 @@ export class OpenVikingClient {
510
547
  return this.request<ToolResultSearchResult>(
511
548
  `/api/v1/sessions/${encodeURIComponent(sessionId)}/tool-results/${encodeURIComponent(toolResultId)}/search?${params.toString()}`,
512
549
  {},
513
- agentId,
514
550
  );
515
551
  }
516
552
 
517
553
  async listToolResults(
518
554
  sessionId: string,
519
555
  options?: { toolName?: string; limit?: number },
520
- agentId?: string,
521
556
  ): Promise<ToolResultListResult> {
522
557
  const params = new URLSearchParams();
523
558
  if (options?.toolName) params.set("tool_name", options.toolName);
@@ -526,22 +561,16 @@ export class OpenVikingClient {
526
561
  return this.request<ToolResultListResult>(
527
562
  `/api/v1/sessions/${encodeURIComponent(sessionId)}/tool-results${query ? `?${query}` : ""}`,
528
563
  {},
529
- agentId,
530
564
  );
531
565
  }
532
566
 
533
- async uploadTempFile(filePath: string, agentId?: string): Promise<string> {
534
- const fileBytes = await readFile(filePath);
535
- const form = new FormData();
536
- form.append(
537
- "file",
538
- new Blob([toBlobPart(fileBytes)], { type: "application/octet-stream" }),
539
- basename(filePath),
540
- );
567
+ async uploadTempFile(filePath: string, actorPeerId?: string): Promise<string> {
568
+ const form = await this.resourcePackager.createTempUploadBody(filePath);
541
569
  const result = await this.request<{ temp_file_id: string }>(
542
570
  "/api/v1/resources/temp_upload",
543
571
  { method: "POST", body: form },
544
- agentId,
572
+ undefined,
573
+ actorPeerId,
545
574
  );
546
575
  if (!result.temp_file_id) {
547
576
  throw new Error("OpenViking temp upload did not return temp_file_id");
@@ -606,7 +635,7 @@ export class OpenVikingClient {
606
635
  return zipPath;
607
636
  }
608
637
 
609
- async addResource(input: AddResourceInput, agentId?: string): Promise<AddResourceResult> {
638
+ async addResource(input: AddResourceInput, actorPeerId?: string): Promise<AddResourceResult> {
610
639
  const pathOrUrl = input.pathOrUrl.trim();
611
640
  if (!pathOrUrl) {
612
641
  throw new Error("pathOrUrl is required");
@@ -631,36 +660,31 @@ export class OpenVikingClient {
631
660
  body.preserve_structure = input.preserveStructure;
632
661
  }
633
662
 
634
- let cleanupPath: string | undefined;
663
+ let packagedSource: Awaited<ReturnType<ResourcePackager["prepareResourceSource"]>> | undefined;
635
664
  const requestTimeoutMs =
636
665
  input.wait ? resolveWaitRequestTimeoutMs(this.timeoutMs, input.timeout) : undefined;
637
666
  try {
638
- if (isRemoteResourceSource(pathOrUrl)) {
639
- body.path = pathOrUrl;
667
+ packagedSource = await this.resourcePackager.prepareResourceSource(pathOrUrl);
668
+ if (packagedSource.kind === "remote") {
669
+ body.path = packagedSource.path;
640
670
  } else {
641
- const localStats = await stat(pathOrUrl);
642
- let uploadPath = pathOrUrl;
643
- if (localStats.isDirectory()) {
644
- uploadPath = await this.zipDirectoryForUpload(pathOrUrl);
645
- cleanupPath = uploadPath;
646
- body.source_name = basename(pathOrUrl);
647
- } else if (!localStats.isFile()) {
648
- throw new Error(`Path is not a file or directory: ${pathOrUrl}`);
671
+ if (packagedSource.sourceName) {
672
+ body.source_name = packagedSource.sourceName;
649
673
  }
650
- body.temp_file_id = await this.uploadTempFile(uploadPath, agentId);
674
+ body.temp_file_id = await this.uploadTempFile(packagedSource.uploadPath, actorPeerId);
651
675
  }
652
676
  return this.request<AddResourceResult>(
653
677
  "/api/v1/resources",
654
678
  { method: "POST", body: JSON.stringify(body) },
655
- agentId,
656
679
  requestTimeoutMs,
680
+ actorPeerId,
657
681
  );
658
682
  } finally {
659
- await cleanupUploadTempPath(cleanupPath);
683
+ await this.resourcePackager.cleanup(packagedSource);
660
684
  }
661
685
  }
662
686
 
663
- async addSkill(input: AddSkillInput, agentId?: string): Promise<AddSkillResult> {
687
+ async addSkill(input: AddSkillInput, actorPeerId?: string): Promise<AddSkillResult> {
664
688
  const hasPath = typeof input.path === "string" && input.path.trim().length > 0;
665
689
  const hasData = input.data !== undefined && input.data !== null;
666
690
  if (hasPath === hasData) {
@@ -671,32 +695,28 @@ export class OpenVikingClient {
671
695
  wait: input.wait ?? false,
672
696
  timeout: input.timeout,
673
697
  };
674
- let cleanupPath: string | undefined;
698
+ let packagedSource: Awaited<ReturnType<ResourcePackager["prepareLocalUploadSource"]>> | undefined;
675
699
  const requestTimeoutMs =
676
700
  input.wait ? resolveWaitRequestTimeoutMs(this.timeoutMs, input.timeout) : undefined;
677
701
  try {
678
702
  if (hasPath) {
679
703
  const skillPath = input.path!.trim();
680
- const localStats = await stat(skillPath);
681
- let uploadPath = skillPath;
682
- if (localStats.isDirectory()) {
683
- uploadPath = await this.zipDirectoryForUpload(skillPath);
684
- cleanupPath = uploadPath;
685
- } else if (!localStats.isFile()) {
704
+ packagedSource = await this.resourcePackager.prepareLocalUploadSource(skillPath);
705
+ if (packagedSource.kind !== "upload") {
686
706
  throw new Error(`Path is not a file or directory: ${skillPath}`);
687
707
  }
688
- body.temp_file_id = await this.uploadTempFile(uploadPath, agentId);
708
+ body.temp_file_id = await this.uploadTempFile(packagedSource.uploadPath, actorPeerId);
689
709
  } else {
690
710
  body.data = input.data;
691
711
  }
692
712
  return this.request<AddSkillResult>(
693
713
  "/api/v1/skills",
694
714
  { method: "POST", body: JSON.stringify(body) },
695
- agentId,
696
715
  requestTimeoutMs,
716
+ actorPeerId,
697
717
  );
698
718
  } finally {
699
- await cleanupUploadTempPath(cleanupPath);
719
+ await this.resourcePackager.cleanup(packagedSource);
700
720
  }
701
721
  }
702
722
 
@@ -729,7 +749,7 @@ export class OpenVikingClient {
729
749
  abstract?: string;
730
750
  context_type?: "memory" | "resource" | "skill";
731
751
  }>,
732
- agentId?: string,
752
+ actorPeerId?: string,
733
753
  createdAt?: string,
734
754
  roleId?: string,
735
755
  ): Promise<void> {
@@ -755,7 +775,7 @@ export class OpenVikingClient {
755
775
  partCount: parts.length,
756
776
  created_at: createdAt ?? null,
757
777
  },
758
- agentId,
778
+ actorPeerId,
759
779
  );
760
780
  await this.request<{ session_id: string }>(
761
781
  `/api/v1/sessions/${encodeURIComponent(sessionId)}/messages`,
@@ -763,12 +783,13 @@ export class OpenVikingClient {
763
783
  method: "POST",
764
784
  body: JSON.stringify(body),
765
785
  },
766
- agentId,
786
+ undefined,
787
+ actorPeerId,
767
788
  );
768
789
  }
769
790
 
770
791
  /** GET session — server auto-creates if absent; returns session meta including message stats and token usage. */
771
- async getSession(sessionId: string, agentId?: string): Promise<{
792
+ async getSession(sessionId: string, actorPeerId?: string): Promise<{
772
793
  message_count?: number;
773
794
  commit_count?: number;
774
795
  last_commit_at?: string;
@@ -784,7 +805,8 @@ export class OpenVikingClient {
784
805
  }>(
785
806
  `/api/v1/sessions/${encodeURIComponent(sessionId)}`,
786
807
  { method: "GET" },
787
- agentId,
808
+ undefined,
809
+ actorPeerId,
788
810
  );
789
811
  }
790
812
 
@@ -800,13 +822,13 @@ export class OpenVikingClient {
800
822
  options?: {
801
823
  wait?: boolean;
802
824
  timeoutMs?: number;
803
- agentId?: string;
804
825
  /**
805
826
  * WM v2: number of most-recent messages to keep live after commit.
806
827
  * Forwarded as `keep_recent_count` in the POST body. 0 (default)
807
828
  * preserves the pre-v2 "archive everything" behavior.
808
- */
829
+ */
809
830
  keepRecentCount?: number;
831
+ agentId?: string;
810
832
  },
811
833
  ): Promise<CommitSessionResult> {
812
834
  const keepRecentCount =
@@ -830,6 +852,7 @@ export class OpenVikingClient {
830
852
  const result = await this.request<CommitSessionResult>(
831
853
  `/api/v1/sessions/${encodeURIComponent(sessionId)}/commit`,
832
854
  { method: "POST", body: JSON.stringify(body) },
855
+ undefined,
833
856
  options?.agentId,
834
857
  );
835
858
 
@@ -838,10 +861,10 @@ export class OpenVikingClient {
838
861
  }
839
862
 
840
863
  // Client-side poll until Phase 2 finishes
841
- const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_PHASE2_POLL_TIMEOUT_MS);
864
+ const deadline = this.now() + (options.timeoutMs ?? DEFAULT_PHASE2_POLL_TIMEOUT_MS);
842
865
  const pollInterval = 500;
843
- while (Date.now() < deadline) {
844
- await sleep(pollInterval);
866
+ while (this.now() < deadline) {
867
+ await this.sleep(pollInterval);
845
868
  const task = await this.getTask(result.task_id, options.agentId).catch(() => null);
846
869
  if (!task) break;
847
870
  if (task.status === "completed") {
@@ -862,35 +885,38 @@ export class OpenVikingClient {
862
885
  }
863
886
 
864
887
  /** Poll a background task by ID. */
865
- async getTask(taskId: string, agentId?: string): Promise<TaskResult> {
888
+ async getTask(taskId: string, actorPeerId?: string): Promise<TaskResult> {
866
889
  return this.request<TaskResult>(
867
890
  `/api/v1/tasks/${encodeURIComponent(taskId)}`,
868
891
  { method: "GET" },
869
- agentId,
892
+ undefined,
893
+ actorPeerId,
870
894
  );
871
895
  }
872
896
 
873
897
  async getSessionContext(
874
898
  sessionId: string,
875
899
  tokenBudget: number = 128_000,
876
- agentId?: string,
900
+ actorPeerId?: string,
877
901
  ): Promise<SessionContextResult> {
878
902
  return this.request(
879
903
  `/api/v1/sessions/${encodeURIComponent(sessionId)}/context?token_budget=${tokenBudget}`,
880
904
  { method: "GET" },
881
- agentId,
905
+ undefined,
906
+ actorPeerId,
882
907
  );
883
908
  }
884
909
 
885
910
  async getSessionArchive(
886
911
  sessionId: string,
887
912
  archiveId: string,
888
- agentId?: string,
913
+ actorPeerId?: string,
889
914
  ): Promise<SessionArchiveResult> {
890
915
  return this.request(
891
916
  `/api/v1/sessions/${encodeURIComponent(sessionId)}/archives/${encodeURIComponent(archiveId)}`,
892
917
  { method: "GET" },
893
- agentId,
918
+ undefined,
919
+ actorPeerId,
894
920
  );
895
921
  }
896
922
 
@@ -902,7 +928,6 @@ export class OpenVikingClient {
902
928
  caseInsensitive?: boolean;
903
929
  nodeLimit?: number;
904
930
  levelLimit?: number;
905
- agentId?: string;
906
931
  } = {},
907
932
  ): Promise<{
908
933
  matches: Array<{ line: number; uri: string; content: string }>;
@@ -910,7 +935,7 @@ export class OpenVikingClient {
910
935
  match_count?: number;
911
936
  files_scanned?: number;
912
937
  }> {
913
- const baseUri = `viking://session/${sessionId}/history`;
938
+ const baseUri = `${userSessionUri(sessionId)}/history`;
914
939
  const uri = options.archiveId ? `${baseUri}/${options.archiveId}` : baseUri;
915
940
  return this.request(
916
941
  "/api/v1/search/grep",
@@ -924,16 +949,15 @@ export class OpenVikingClient {
924
949
  ...(options.levelLimit !== undefined ? { level_limit: options.levelLimit } : {}),
925
950
  }),
926
951
  },
927
- options.agentId,
928
952
  );
929
953
  }
930
954
 
931
- async deleteSession(sessionId: string, agentId?: string): Promise<void> {
932
- await this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" }, agentId);
955
+ async deleteSession(sessionId: string): Promise<void> {
956
+ await this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
933
957
  }
934
- async deleteUri(uri: string, agentId?: string): Promise<void> {
958
+ async deleteUri(uri: string, actorPeerId?: string): Promise<void> {
935
959
  await this.request(`/api/v1/fs?uri=${encodeURIComponent(uri)}&recursive=false`, {
936
960
  method: "DELETE",
937
- }, agentId);
961
+ }, undefined, actorPeerId);
938
962
  }
939
963
  }