@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
@@ -0,0 +1,335 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Type, type Static } from "@sinclair/typebox";
6
+ import { Value } from "@sinclair/typebox/value";
7
+
8
+ export const OPENVIKING_260610_FEATURE = "openviking.260610";
9
+ export const OPENVIKING_FEATURE_GATES_RPC = "openviking.feature.gates";
10
+
11
+ const versionPattern = String.raw`^\d+\.\d+\.\d+(?:-(?:\d+|beta(?:\.\d+)?))?$`;
12
+
13
+ const FeatureGateSchema = Type.Object(
14
+ {
15
+ enable: Type.Boolean(),
16
+ minPluginVersion: Type.String({ pattern: versionPattern }),
17
+ minOpenclawVersion: Type.String({ minLength: 1 }),
18
+ editions: Type.Array(Type.String({ minLength: 1 }), {
19
+ minItems: 1,
20
+ uniqueItems: true,
21
+ }),
22
+ },
23
+ { additionalProperties: false },
24
+ );
25
+
26
+ const FeatureGatesConfigSchema = Type.Object(
27
+ {
28
+ features: Type.Record(Type.String({ minLength: 1 }), FeatureGateSchema),
29
+ },
30
+ { additionalProperties: false },
31
+ );
32
+
33
+ type FeatureGatesConfig = Static<typeof FeatureGatesConfigSchema>;
34
+
35
+ interface ParsedVersion {
36
+ year: number;
37
+ month: number;
38
+ day: number;
39
+ channelRank: number;
40
+ sequence: number;
41
+ }
42
+
43
+ export interface OpenVikingFeatureGateServiceOptions {
44
+ configPath?: string;
45
+ getPluginVersion?: () => string;
46
+ getOpenClawVersion?: () => Promise<string>;
47
+ getOpenClawVersionSync?: () => string;
48
+ }
49
+
50
+ export interface OpenVikingFeatureGateService {
51
+ getEnabledFeatureGates(edition?: string): Promise<string[]>;
52
+ isFeatureGateEnabled(featureName: string, edition?: string): Promise<boolean>;
53
+ isFeatureGateEnabledSync(featureName: string, edition?: string): boolean;
54
+ }
55
+
56
+ export interface OpenVikingFeatureGatesGatewayApi {
57
+ registerGatewayMethod?: (
58
+ name: string,
59
+ handler: (input: {
60
+ params?: unknown;
61
+ respond: (success: boolean, data: unknown) => void;
62
+ }) => void | Promise<void>,
63
+ ) => void;
64
+ }
65
+
66
+ function parseVersion(version: string): ParsedVersion | null {
67
+ const stableMatch = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(\d+))?$/);
68
+ if (stableMatch) {
69
+ return {
70
+ year: Number(stableMatch[1]),
71
+ month: Number(stableMatch[2]),
72
+ day: Number(stableMatch[3]),
73
+ channelRank: 1,
74
+ sequence: stableMatch[4] ? Number(stableMatch[4]) : 0,
75
+ };
76
+ }
77
+
78
+ const betaMatch = version.match(/^(\d+)\.(\d+)\.(\d+)-beta(?:\.(\d+))?$/);
79
+ if (betaMatch) {
80
+ return {
81
+ year: Number(betaMatch[1]),
82
+ month: Number(betaMatch[2]),
83
+ day: Number(betaMatch[3]),
84
+ channelRank: 0,
85
+ sequence: betaMatch[4] ? Number(betaMatch[4]) : 0,
86
+ };
87
+ }
88
+
89
+ return null;
90
+ }
91
+
92
+ export function isPluginVersionAtLeast(currentVersion: string, minVersion: string): boolean {
93
+ const current = parseVersion(currentVersion);
94
+ const minimum = parseVersion(minVersion);
95
+ if (!current || !minimum) {
96
+ return false;
97
+ }
98
+
99
+ if (current.year !== minimum.year) return current.year > minimum.year;
100
+ if (current.month !== minimum.month) return current.month > minimum.month;
101
+ if (current.day !== minimum.day) return current.day > minimum.day;
102
+ if (current.channelRank !== minimum.channelRank) {
103
+ return current.channelRank > minimum.channelRank;
104
+ }
105
+ return current.sequence >= minimum.sequence;
106
+ }
107
+
108
+ export function isOpenClawVersionAtLeast(currentVersion: string, minVersion: string): boolean {
109
+ return isPluginVersionAtLeast(currentVersion, minVersion);
110
+ }
111
+
112
+ function formatSchemaError(path: string, message: string): string {
113
+ const normalizedPath = path ? path.replace(/\//g, ".").replace(/^\./, "") : "$";
114
+ return `${normalizedPath}: ${message}`;
115
+ }
116
+
117
+ function parseFeatureGatesConfig(raw: string): FeatureGatesConfig {
118
+ const parsed = JSON.parse(raw) as unknown;
119
+ if (Value.Check(FeatureGatesConfigSchema, parsed)) {
120
+ return parsed;
121
+ }
122
+
123
+ const validationError = Value.Errors(FeatureGatesConfigSchema, parsed).First();
124
+ const errorMessage = validationError
125
+ ? formatSchemaError(validationError.path, validationError.message)
126
+ : "unknown validation error";
127
+ throw new Error(`Invalid feature-gates.json: ${errorMessage}`);
128
+ }
129
+
130
+ function normalizeFeatures(
131
+ features: FeatureGatesConfig["features"],
132
+ pluginVersion: string,
133
+ openclawVersion: string,
134
+ edition?: string,
135
+ ): string[] {
136
+ const requestedEdition = edition?.trim();
137
+ const businessCarriers = (process.env.BUSINESS_CARRIER ?? "")
138
+ .split(",")
139
+ .map((carrier) => carrier.trim())
140
+ .filter(Boolean);
141
+ const effectiveEditions = requestedEdition ? [requestedEdition] : businessCarriers;
142
+
143
+ return Object.entries(features).flatMap(([featureName, featureConfig]) => {
144
+ const editionOk =
145
+ effectiveEditions.length > 0 &&
146
+ effectiveEditions.some((carrier) => featureConfig.editions.includes(carrier));
147
+
148
+ if (
149
+ featureConfig.enable &&
150
+ editionOk &&
151
+ isPluginVersionAtLeast(pluginVersion, featureConfig.minPluginVersion) &&
152
+ isOpenClawVersionAtLeast(openclawVersion, featureConfig.minOpenclawVersion)
153
+ ) {
154
+ return [featureName];
155
+ }
156
+ return [];
157
+ });
158
+ }
159
+
160
+ let cachedPackageRoot: string | undefined;
161
+ let cachedDefaultPluginVersion: string | undefined;
162
+
163
+ function findPackageRoot(startDir: string): string {
164
+ let current = startDir;
165
+ for (;;) {
166
+ if (existsSync(join(current, "package.json"))) {
167
+ return current;
168
+ }
169
+ const parent = dirname(current);
170
+ if (parent === current) {
171
+ return startDir;
172
+ }
173
+ current = parent;
174
+ }
175
+ }
176
+
177
+ function getPackageRoot(): string {
178
+ cachedPackageRoot ??= findPackageRoot(dirname(fileURLToPath(import.meta.url)));
179
+ return cachedPackageRoot;
180
+ }
181
+
182
+ function getDefaultFeatureGatesPath(): string {
183
+ return join(getPackageRoot(), "config", "feature-gates.json");
184
+ }
185
+
186
+ function getDefaultPluginVersion(): string {
187
+ if (cachedDefaultPluginVersion) {
188
+ return cachedDefaultPluginVersion;
189
+ }
190
+ try {
191
+ const raw = readFileSync(join(getPackageRoot(), "package.json"), "utf8");
192
+ const parsed = JSON.parse(raw) as { version?: unknown };
193
+ cachedDefaultPluginVersion = typeof parsed.version === "string" ? parsed.version : "0.0.0";
194
+ } catch {
195
+ cachedDefaultPluginVersion = "0.0.0";
196
+ }
197
+ return cachedDefaultPluginVersion;
198
+ }
199
+
200
+ function getDefaultOpenClawVersionSync(): string {
201
+ const envVersion = process.env.OPENCLAW_VERSION?.trim();
202
+ return envVersion || "2026.4.8";
203
+ }
204
+
205
+ export function createOpenVikingFeatureGateService(
206
+ options: OpenVikingFeatureGateServiceOptions = {},
207
+ ): OpenVikingFeatureGateService {
208
+ const configPath = options.configPath ?? getDefaultFeatureGatesPath();
209
+ const getPluginVersion = options.getPluginVersion ?? getDefaultPluginVersion;
210
+ const getOpenClawVersion = options.getOpenClawVersion ?? (async () => getDefaultOpenClawVersionSync());
211
+ const getOpenClawVersionSync = options.getOpenClawVersionSync ?? getDefaultOpenClawVersionSync;
212
+ let cachedConfig: FeatureGatesConfig | undefined;
213
+ let cachedConfigPromise: Promise<FeatureGatesConfig> | undefined;
214
+ let cachedOpenClawVersion: string | undefined;
215
+ let cachedOpenClawVersionPromise: Promise<string> | undefined;
216
+
217
+ async function loadFeatureGatesConfig(): Promise<FeatureGatesConfig> {
218
+ if (cachedConfig) {
219
+ return cachedConfig;
220
+ }
221
+ cachedConfigPromise ??= readFile(configPath, "utf8")
222
+ .then((raw) => {
223
+ cachedConfig = parseFeatureGatesConfig(raw);
224
+ return cachedConfig;
225
+ })
226
+ .catch((error: unknown) => {
227
+ cachedConfigPromise = undefined;
228
+ throw error;
229
+ });
230
+ return cachedConfigPromise;
231
+ }
232
+
233
+ function loadFeatureGatesConfigSync(): FeatureGatesConfig {
234
+ if (cachedConfig) {
235
+ return cachedConfig;
236
+ }
237
+ const raw = readFileSync(configPath, "utf8");
238
+ cachedConfig = parseFeatureGatesConfig(raw);
239
+ return cachedConfig;
240
+ }
241
+
242
+ async function loadOpenClawVersion(): Promise<string> {
243
+ if (cachedOpenClawVersion) {
244
+ return cachedOpenClawVersion;
245
+ }
246
+ cachedOpenClawVersionPromise ??= getOpenClawVersion()
247
+ .then((version) => {
248
+ cachedOpenClawVersion = version;
249
+ return version;
250
+ })
251
+ .catch((error: unknown) => {
252
+ cachedOpenClawVersionPromise = undefined;
253
+ throw error;
254
+ });
255
+ return cachedOpenClawVersionPromise;
256
+ }
257
+
258
+ function loadOpenClawVersionSync(): string {
259
+ cachedOpenClawVersion ??= getOpenClawVersionSync();
260
+ return cachedOpenClawVersion;
261
+ }
262
+
263
+ function loadAndNormalizeFeaturesSync(edition?: string): string[] {
264
+ const parsed = loadFeatureGatesConfigSync();
265
+ return normalizeFeatures(parsed.features, getPluginVersion(), loadOpenClawVersionSync(), edition);
266
+ }
267
+
268
+ async function loadAndNormalizeFeatures(edition?: string): Promise<string[]> {
269
+ const [parsed, openclawVersion] = await Promise.all([
270
+ loadFeatureGatesConfig(),
271
+ loadOpenClawVersion(),
272
+ ]);
273
+ return normalizeFeatures(parsed.features, getPluginVersion(), openclawVersion, edition);
274
+ }
275
+
276
+ return {
277
+ async getEnabledFeatureGates(edition?: string): Promise<string[]> {
278
+ return loadAndNormalizeFeatures(edition);
279
+ },
280
+
281
+ async isFeatureGateEnabled(featureName: string, edition?: string): Promise<boolean> {
282
+ try {
283
+ const features = await loadAndNormalizeFeatures(edition);
284
+ return features.includes(featureName);
285
+ } catch {
286
+ return false;
287
+ }
288
+ },
289
+
290
+ isFeatureGateEnabledSync(featureName: string, edition?: string): boolean {
291
+ try {
292
+ const features = loadAndNormalizeFeaturesSync(edition);
293
+ return features.includes(featureName);
294
+ } catch {
295
+ return false;
296
+ }
297
+ },
298
+ };
299
+ }
300
+
301
+ function readEditionParam(params: unknown): string | undefined {
302
+ if (!params || typeof params !== "object") {
303
+ return undefined;
304
+ }
305
+ const value = (params as { edition?: unknown }).edition;
306
+ return typeof value === "string" ? value : undefined;
307
+ }
308
+
309
+ const defaultFeatureGateService = createOpenVikingFeatureGateService();
310
+
311
+ export const getEnabledFeatureGates = (edition?: string): Promise<string[]> =>
312
+ defaultFeatureGateService.getEnabledFeatureGates(edition);
313
+ export const isFeatureGateEnabled = (featureName: string, edition?: string): Promise<boolean> =>
314
+ defaultFeatureGateService.isFeatureGateEnabled(featureName, edition);
315
+ export const isFeatureGateEnabledSync = (featureName: string, edition?: string): boolean =>
316
+ defaultFeatureGateService.isFeatureGateEnabledSync(featureName, edition);
317
+
318
+ export function registerOpenVikingFeatureGatesMethod(
319
+ api: OpenVikingFeatureGatesGatewayApi,
320
+ service: OpenVikingFeatureGateService = defaultFeatureGateService,
321
+ ): void {
322
+ if (typeof api.registerGatewayMethod !== "function") {
323
+ return;
324
+ }
325
+
326
+ api.registerGatewayMethod(OPENVIKING_FEATURE_GATES_RPC, async ({ params, respond }) => {
327
+ try {
328
+ const edition = readEditionParam(params);
329
+ const features = await service.getEnabledFeatureGates(edition);
330
+ respond(true, { features });
331
+ } catch (error) {
332
+ respond(false, error instanceof Error ? error.message : String(error));
333
+ }
334
+ });
335
+ }
@@ -0,0 +1,105 @@
1
+ import type {
2
+ AddResourceInput,
3
+ AddResourceResult,
4
+ AddSkillInput,
5
+ AddSkillResult,
6
+ } from "../client.js";
7
+
8
+ export type OpenVikingImportRuntimeResourceInput = {
9
+ source?: string;
10
+ to?: string;
11
+ parent?: string;
12
+ reason?: string;
13
+ instruction?: string;
14
+ wait?: boolean;
15
+ timeout?: number;
16
+ };
17
+
18
+ export type OpenVikingImportRuntimeSkillInput = {
19
+ source?: string;
20
+ data?: unknown;
21
+ wait?: boolean;
22
+ timeout?: number;
23
+ };
24
+
25
+ export type OpenVikingImportRuntimeToolResult = {
26
+ content: Array<{ type: "text"; text: string }>;
27
+ details?: Record<string, unknown>;
28
+ };
29
+
30
+ export type OpenVikingImportRuntimeClient = {
31
+ addResource: (input: AddResourceInput, agentId?: string) => Promise<AddResourceResult>;
32
+ addSkill: (input: AddSkillInput, agentId?: string) => Promise<AddSkillResult>;
33
+ };
34
+
35
+ export type OpenVikingImportRuntimeDeps = {
36
+ getClient: () => Promise<OpenVikingImportRuntimeClient>;
37
+ };
38
+
39
+ function formatResourceImportText(result: AddResourceResult): string {
40
+ const root = result.root_uri ? ` ${result.root_uri}` : "";
41
+ const warnings = result.warnings?.length ? ` Warnings: ${result.warnings.join("; ")}` : "";
42
+ return `Imported OpenViking resource.${root}${warnings}`.trim();
43
+ }
44
+
45
+ function formatSkillImportText(result: AddSkillResult): string {
46
+ const uri = result.uri ? ` ${result.uri}` : "";
47
+ const name = result.name ? ` (${result.name})` : "";
48
+ return `Imported OpenViking skill${name}.${uri}`.trim();
49
+ }
50
+
51
+ export function createOpenVikingImportRuntime(deps: OpenVikingImportRuntimeDeps): {
52
+ addResourceOpenViking: (
53
+ input: OpenVikingImportRuntimeResourceInput,
54
+ agentId?: string,
55
+ ) => Promise<OpenVikingImportRuntimeToolResult>;
56
+ addSkillOpenViking: (
57
+ input: OpenVikingImportRuntimeSkillInput,
58
+ agentId?: string,
59
+ ) => Promise<OpenVikingImportRuntimeToolResult>;
60
+ } {
61
+ const importResource = async (input: AddResourceInput, agentId?: string): Promise<OpenVikingImportRuntimeToolResult> => {
62
+ const client = await deps.getClient();
63
+ const result = await client.addResource(input, agentId);
64
+ return {
65
+ content: [{ type: "text", text: formatResourceImportText(result) }],
66
+ details: {
67
+ action: "resource_imported",
68
+ ...result,
69
+ },
70
+ };
71
+ };
72
+
73
+ const importSkill = async (input: AddSkillInput, agentId?: string): Promise<OpenVikingImportRuntimeToolResult> => {
74
+ const client = await deps.getClient();
75
+ const result = await client.addSkill(input, agentId);
76
+ return {
77
+ content: [{ type: "text", text: formatSkillImportText(result) }],
78
+ details: {
79
+ action: "skill_imported",
80
+ ...result,
81
+ },
82
+ };
83
+ };
84
+
85
+ const addResourceOpenViking = (input: OpenVikingImportRuntimeResourceInput, agentId?: string) =>
86
+ importResource({
87
+ pathOrUrl: input.source ?? "",
88
+ to: input.to,
89
+ parent: input.parent,
90
+ reason: input.reason,
91
+ instruction: input.instruction,
92
+ wait: input.wait,
93
+ timeout: input.timeout,
94
+ }, agentId);
95
+
96
+ const addSkillOpenViking = (input: OpenVikingImportRuntimeSkillInput, agentId?: string) =>
97
+ importSkill({
98
+ path: input.source,
99
+ data: input.data,
100
+ wait: input.wait,
101
+ timeout: input.timeout,
102
+ }, agentId);
103
+
104
+ return { addResourceOpenViking, addSkillOpenViking };
105
+ }
@@ -0,0 +1,133 @@
1
+ import { Type } from "@sinclair/typebox";
2
+
3
+ import type {
4
+ AddResourceInput,
5
+ AddResourceResult,
6
+ AddSkillInput,
7
+ AddSkillResult,
8
+ } from "../client.js";
9
+
10
+ export type OpenVikingImportToolContext = {
11
+ sessionKey?: string;
12
+ sessionId?: string;
13
+ agentId?: string;
14
+ senderId?: string;
15
+ };
16
+
17
+ export type OpenVikingImportSession = {
18
+ sessionId?: string;
19
+ sessionKey?: string;
20
+ ovSessionId?: string;
21
+ agentId: string;
22
+ };
23
+
24
+ export type OpenVikingImportClient = {
25
+ addResource: (input: AddResourceInput, agentId?: string) => Promise<AddResourceResult>;
26
+ addSkill: (input: AddSkillInput, agentId?: string) => Promise<AddSkillResult>;
27
+ };
28
+
29
+ export type OpenVikingImportToolsDeps = {
30
+ registerTool: (toolOrFactory: unknown, opts: { name: string }) => void;
31
+ getClient: () => Promise<OpenVikingImportClient>;
32
+ resolvePluginSessionRouting: (ctx?: OpenVikingImportToolContext) => OpenVikingImportSession;
33
+ isBypassedSession: (ctx?: OpenVikingImportToolContext) => boolean;
34
+ makeBypassedToolResult: (toolName: string) => unknown;
35
+ enableAddResourceTool: boolean;
36
+ };
37
+
38
+ function formatResourceImportText(result: AddResourceResult): string {
39
+ const root = result.root_uri ? ` ${result.root_uri}` : "";
40
+ const warnings = result.warnings?.length ? ` Warnings: ${result.warnings.join("; ")}` : "";
41
+ return `Imported OpenViking resource.${root}${warnings}`.trim();
42
+ }
43
+
44
+ function formatSkillImportText(result: AddSkillResult): string {
45
+ const uri = result.uri ? ` ${result.uri}` : "";
46
+ const name = result.name ? ` (${result.name})` : "";
47
+ return `Imported OpenViking skill${name}.${uri}`.trim();
48
+ }
49
+
50
+ export function registerOpenVikingImportTools(deps: OpenVikingImportToolsDeps): void {
51
+ if (deps.enableAddResourceTool) {
52
+ deps.registerTool(
53
+ (ctx: OpenVikingImportToolContext) => ({
54
+ name: "add_resource",
55
+ label: "Add Resource (OpenViking)",
56
+ description:
57
+ "Use only when the user explicitly asks to import, add, upload, save, or index a document, directory, URL, Git repository, or OpenClaw media attachment into OpenViking resources. " +
58
+ "Never use this during search, retrieval, URI reading, or search-result optimization; use ov_search and ov_read for those flows. " +
59
+ "For a '[media attached: /path ...]' document, set source to that exact local media path. Do not invent OpenViking upload REST endpoints.",
60
+ parameters: Type.Object({
61
+ source: Type.String({ description: "Local path, OpenClaw media attachment path, directory path, public URL, or Git URL" }),
62
+ to: Type.Optional(Type.String({ description: "Exact target URI, e.g. viking://resources/project-docs" })),
63
+ parent: Type.Optional(Type.String({ description: "Parent URI under viking://resources" })),
64
+ reason: Type.Optional(Type.String({ description: "Reason or note for adding this resource" })),
65
+ instruction: Type.Optional(Type.String({ description: "Processing instruction for semantic extraction" })),
66
+ wait: Type.Optional(Type.Boolean({ description: "Wait for processing to complete" })),
67
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds when wait is true" })),
68
+ }),
69
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
70
+ if (deps.isBypassedSession(ctx)) {
71
+ return deps.makeBypassedToolResult("add_resource");
72
+ }
73
+ const session = deps.resolvePluginSessionRouting(ctx);
74
+ const client = await deps.getClient();
75
+ const result = await client.addResource({
76
+ pathOrUrl: typeof params.source === "string" ? params.source : "",
77
+ to: typeof params.to === "string" ? params.to : undefined,
78
+ parent: typeof params.parent === "string" ? params.parent : undefined,
79
+ reason: typeof params.reason === "string" ? params.reason : undefined,
80
+ instruction: typeof params.instruction === "string" ? params.instruction : undefined,
81
+ wait: typeof params.wait === "boolean" ? params.wait : undefined,
82
+ timeout: typeof params.timeout === "number" ? params.timeout : undefined,
83
+ }, session.agentId);
84
+ return {
85
+ content: [{ type: "text" as const, text: formatResourceImportText(result) }],
86
+ details: {
87
+ action: "resource_imported",
88
+ ...result,
89
+ },
90
+ };
91
+ },
92
+ }),
93
+ { name: "add_resource" },
94
+ );
95
+ }
96
+
97
+ deps.registerTool(
98
+ (ctx: OpenVikingImportToolContext) => ({
99
+ name: "add_skill",
100
+ label: "Add Skill (OpenViking)",
101
+ description:
102
+ "Use only when the user explicitly asks to import, add, install, or register a skill into OpenViking. " +
103
+ "Set source to a local SKILL.md file or skill directory, or data to raw SKILL.md content or an MCP tool dict.",
104
+ parameters: Type.Object({
105
+ source: Type.Optional(Type.String({ description: "Local SKILL.md path or skill directory path" })),
106
+ data: Type.Optional(Type.Any({ description: "Raw SKILL.md content or MCP tool dict" })),
107
+ wait: Type.Optional(Type.Boolean({ description: "Wait for processing to complete" })),
108
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds when wait is true" })),
109
+ }),
110
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
111
+ if (deps.isBypassedSession(ctx)) {
112
+ return deps.makeBypassedToolResult("add_skill");
113
+ }
114
+ const session = deps.resolvePluginSessionRouting(ctx);
115
+ const client = await deps.getClient();
116
+ const result = await client.addSkill({
117
+ path: typeof params.source === "string" ? params.source : undefined,
118
+ data: params.data,
119
+ wait: typeof params.wait === "boolean" ? params.wait : undefined,
120
+ timeout: typeof params.timeout === "number" ? params.timeout : undefined,
121
+ }, session.agentId);
122
+ return {
123
+ content: [{ type: "text" as const, text: formatSkillImportText(result) }],
124
+ details: {
125
+ action: "skill_imported",
126
+ ...result,
127
+ },
128
+ };
129
+ },
130
+ }),
131
+ { name: "add_skill" },
132
+ );
133
+ }
@@ -0,0 +1,65 @@
1
+ export type OpenVikingHookContext = {
2
+ agentId?: string;
3
+ sessionId?: string;
4
+ sessionKey?: string;
5
+ ovSessionId?: string;
6
+ };
7
+
8
+ export type ContextEngineCommitPort = {
9
+ commitOVSession: (ctx: { sessionId: string; sessionKey?: string }) => Promise<boolean>;
10
+ };
11
+
12
+ export type OpenVikingLifecycleHookApi = {
13
+ on: (
14
+ hookName: string,
15
+ handler: (event: unknown, ctx?: OpenVikingHookContext) => unknown,
16
+ opts?: { priority?: number },
17
+ ) => void;
18
+ };
19
+
20
+ export type OpenVikingLifecycleHooksDeps = {
21
+ api: OpenVikingLifecycleHookApi;
22
+ rememberSessionAgentId: (ctx: OpenVikingHookContext) => void;
23
+ isBypassedSession: (ctx?: OpenVikingHookContext) => boolean;
24
+ verboseRoutingInfo: (message: string) => void;
25
+ getContextEngine: () => ContextEngineCommitPort | null;
26
+ logger: {
27
+ info: (message: string) => void;
28
+ warn: (message: string) => void;
29
+ };
30
+ };
31
+
32
+ export function registerOpenVikingLifecycleHooks(deps: OpenVikingLifecycleHooksDeps): void {
33
+ deps.api.on("session_start", async (_event: unknown, ctx?: OpenVikingHookContext) => {
34
+ deps.rememberSessionAgentId(ctx ?? {});
35
+ });
36
+ deps.api.on("session_end", async (_event: unknown, ctx?: OpenVikingHookContext) => {
37
+ deps.rememberSessionAgentId(ctx ?? {});
38
+ });
39
+ deps.api.on("before_reset", async (_event: unknown, ctx?: OpenVikingHookContext) => {
40
+ if (deps.isBypassedSession(ctx)) {
41
+ deps.verboseRoutingInfo(
42
+ `openviking: bypassing before_reset due to session pattern match (sessionKey=${ctx?.sessionKey ?? "none"}, sessionId=${ctx?.sessionId ?? "none"})`,
43
+ );
44
+ return;
45
+ }
46
+ const sessionId = ctx?.sessionId;
47
+ const contextEngine = deps.getContextEngine();
48
+ if (sessionId && contextEngine) {
49
+ try {
50
+ const ok = await contextEngine.commitOVSession({
51
+ sessionId,
52
+ sessionKey: ctx?.sessionKey,
53
+ });
54
+ if (ok) {
55
+ deps.logger.info(`openviking: committed OV session on reset for session=${sessionId}`);
56
+ }
57
+ } catch (err) {
58
+ deps.logger.warn(`openviking: failed to commit OV session on reset: ${String(err)}`);
59
+ }
60
+ }
61
+ });
62
+ deps.api.on("after_compaction", async (_event: unknown, _ctx?: OpenVikingHookContext) => {
63
+ // Reserved hook registration for future post-compaction memory integration.
64
+ });
65
+ }