@memtensor/memos-local-openclaw-plugin 1.0.4-beta.6 → 1.0.4-beta.7

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 (153) hide show
  1. package/README.md +39 -22
  2. package/dist/capture/index.d.ts +1 -1
  3. package/dist/capture/index.d.ts.map +1 -1
  4. package/dist/capture/index.js +27 -7
  5. package/dist/capture/index.js.map +1 -1
  6. package/dist/client/connector.d.ts +29 -0
  7. package/dist/client/connector.d.ts.map +1 -0
  8. package/dist/client/connector.js +218 -0
  9. package/dist/client/connector.js.map +1 -0
  10. package/dist/client/hub.d.ts +61 -0
  11. package/dist/client/hub.d.ts.map +1 -0
  12. package/dist/client/hub.js +170 -0
  13. package/dist/client/hub.js.map +1 -0
  14. package/dist/client/skill-sync.d.ts +36 -0
  15. package/dist/client/skill-sync.d.ts.map +1 -0
  16. package/dist/client/skill-sync.js +226 -0
  17. package/dist/client/skill-sync.js.map +1 -0
  18. package/dist/config.d.ts +2 -1
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +72 -3
  21. package/dist/config.js.map +1 -1
  22. package/dist/embedding/index.d.ts +4 -2
  23. package/dist/embedding/index.d.ts.map +1 -1
  24. package/dist/embedding/index.js +17 -1
  25. package/dist/embedding/index.js.map +1 -1
  26. package/dist/hub/auth.d.ts +19 -0
  27. package/dist/hub/auth.d.ts.map +1 -0
  28. package/dist/hub/auth.js +70 -0
  29. package/dist/hub/auth.js.map +1 -0
  30. package/dist/hub/server.d.ts +41 -0
  31. package/dist/hub/server.d.ts.map +1 -0
  32. package/dist/hub/server.js +767 -0
  33. package/dist/hub/server.js.map +1 -0
  34. package/dist/hub/user-manager.d.ts +31 -0
  35. package/dist/hub/user-manager.d.ts.map +1 -0
  36. package/dist/hub/user-manager.js +129 -0
  37. package/dist/hub/user-manager.js.map +1 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +8 -4
  41. package/dist/index.js.map +1 -1
  42. package/dist/ingest/providers/index.d.ts +10 -2
  43. package/dist/ingest/providers/index.d.ts.map +1 -1
  44. package/dist/ingest/providers/index.js +209 -43
  45. package/dist/ingest/providers/index.js.map +1 -1
  46. package/dist/ingest/providers/openai.d.ts +1 -0
  47. package/dist/ingest/providers/openai.d.ts.map +1 -1
  48. package/dist/ingest/providers/openai.js +1 -0
  49. package/dist/ingest/providers/openai.js.map +1 -1
  50. package/dist/ingest/task-processor.js +1 -1
  51. package/dist/ingest/task-processor.js.map +1 -1
  52. package/dist/openclaw-api.d.ts +53 -0
  53. package/dist/openclaw-api.d.ts.map +1 -0
  54. package/dist/openclaw-api.js +189 -0
  55. package/dist/openclaw-api.js.map +1 -0
  56. package/dist/recall/engine.js +1 -1
  57. package/dist/recall/engine.js.map +1 -1
  58. package/dist/shared/llm-call.d.ts +4 -2
  59. package/dist/shared/llm-call.d.ts.map +1 -1
  60. package/dist/shared/llm-call.js +20 -81
  61. package/dist/shared/llm-call.js.map +1 -1
  62. package/dist/sharing/types.contract.d.ts +2 -0
  63. package/dist/sharing/types.contract.d.ts.map +1 -0
  64. package/dist/sharing/types.contract.js +3 -0
  65. package/dist/sharing/types.contract.js.map +1 -0
  66. package/dist/sharing/types.d.ts +80 -0
  67. package/dist/sharing/types.d.ts.map +1 -0
  68. package/dist/sharing/types.js +3 -0
  69. package/dist/sharing/types.js.map +1 -0
  70. package/dist/skill/evaluator.d.ts.map +1 -1
  71. package/dist/skill/evaluator.js +2 -2
  72. package/dist/skill/evaluator.js.map +1 -1
  73. package/dist/skill/evolver.d.ts +0 -2
  74. package/dist/skill/evolver.d.ts.map +1 -1
  75. package/dist/skill/evolver.js +0 -3
  76. package/dist/skill/evolver.js.map +1 -1
  77. package/dist/skill/generator.d.ts.map +1 -1
  78. package/dist/skill/generator.js +4 -4
  79. package/dist/skill/generator.js.map +1 -1
  80. package/dist/skill/upgrader.js +1 -1
  81. package/dist/skill/upgrader.js.map +1 -1
  82. package/dist/skill/validator.js +1 -1
  83. package/dist/skill/validator.js.map +1 -1
  84. package/dist/storage/ensure-binding.d.ts.map +1 -1
  85. package/dist/storage/ensure-binding.js +3 -1
  86. package/dist/storage/ensure-binding.js.map +1 -1
  87. package/dist/storage/sqlite.d.ts +329 -1
  88. package/dist/storage/sqlite.d.ts.map +1 -1
  89. package/dist/storage/sqlite.js +909 -4
  90. package/dist/storage/sqlite.js.map +1 -1
  91. package/dist/telemetry.d.ts +5 -12
  92. package/dist/telemetry.d.ts.map +1 -1
  93. package/dist/telemetry.js +38 -135
  94. package/dist/telemetry.js.map +1 -1
  95. package/dist/tools/index.d.ts +1 -0
  96. package/dist/tools/index.d.ts.map +1 -1
  97. package/dist/tools/index.js +3 -1
  98. package/dist/tools/index.js.map +1 -1
  99. package/dist/tools/memory-search.d.ts +5 -2
  100. package/dist/tools/memory-search.d.ts.map +1 -1
  101. package/dist/tools/memory-search.js +50 -7
  102. package/dist/tools/memory-search.js.map +1 -1
  103. package/dist/tools/network-memory-detail.d.ts +4 -0
  104. package/dist/tools/network-memory-detail.d.ts.map +1 -0
  105. package/dist/tools/network-memory-detail.js +34 -0
  106. package/dist/tools/network-memory-detail.js.map +1 -0
  107. package/dist/types.d.ts +49 -2
  108. package/dist/types.d.ts.map +1 -1
  109. package/dist/types.js.map +1 -1
  110. package/dist/viewer/html.d.ts.map +1 -1
  111. package/dist/viewer/html.js +3965 -459
  112. package/dist/viewer/html.js.map +1 -1
  113. package/dist/viewer/server.d.ts +51 -0
  114. package/dist/viewer/server.d.ts.map +1 -1
  115. package/dist/viewer/server.js +1564 -23
  116. package/dist/viewer/server.js.map +1 -1
  117. package/index.ts +769 -67
  118. package/openclaw.plugin.json +2 -1
  119. package/package.json +4 -3
  120. package/scripts/postinstall.cjs +283 -46
  121. package/skill/memos-memory-guide/SKILL.md +82 -20
  122. package/src/capture/index.ts +27 -7
  123. package/src/client/connector.ts +212 -0
  124. package/src/client/hub.ts +207 -0
  125. package/src/client/skill-sync.ts +216 -0
  126. package/src/config.ts +94 -3
  127. package/src/embedding/index.ts +21 -1
  128. package/src/hub/auth.ts +78 -0
  129. package/src/hub/server.ts +754 -0
  130. package/src/hub/user-manager.ts +143 -0
  131. package/src/index.ts +13 -5
  132. package/src/ingest/providers/index.ts +246 -46
  133. package/src/ingest/providers/openai.ts +1 -1
  134. package/src/ingest/task-processor.ts +1 -1
  135. package/src/openclaw-api.ts +287 -0
  136. package/src/recall/engine.ts +1 -1
  137. package/src/shared/llm-call.ts +23 -95
  138. package/src/sharing/types.contract.ts +40 -0
  139. package/src/sharing/types.ts +102 -0
  140. package/src/skill/evaluator.ts +3 -2
  141. package/src/skill/evolver.ts +0 -5
  142. package/src/skill/generator.ts +6 -4
  143. package/src/skill/upgrader.ts +1 -1
  144. package/src/skill/validator.ts +1 -1
  145. package/src/storage/ensure-binding.ts +3 -1
  146. package/src/storage/sqlite.ts +1159 -4
  147. package/src/telemetry.ts +39 -152
  148. package/src/tools/index.ts +1 -0
  149. package/src/tools/memory-search.ts +58 -8
  150. package/src/tools/network-memory-detail.ts +34 -0
  151. package/src/types.ts +44 -2
  152. package/src/viewer/html.ts +3965 -459
  153. package/src/viewer/server.ts +1452 -25
@@ -0,0 +1,287 @@
1
+ /**
2
+ * OpenClaw Host Model Proxy
3
+ *
4
+ * Reads the host's configured model providers (api.config.models.providers)
5
+ * and proxies completion / embedding requests via OpenAI-compatible HTTP calls.
6
+ */
7
+
8
+ import type { Logger, OpenClawAPI } from "./types";
9
+
10
+ // ── Request / Response types ────────────────────────────────────────
11
+
12
+ export interface OpenClawEmbedRequest {
13
+ texts: string[];
14
+ model?: string;
15
+ }
16
+
17
+ export interface OpenClawEmbedResponse {
18
+ embeddings: number[][];
19
+ dimensions: number;
20
+ }
21
+
22
+ export interface OpenClawCompleteRequest {
23
+ prompt: string;
24
+ maxTokens?: number;
25
+ temperature?: number;
26
+ model?: string;
27
+ }
28
+
29
+ export interface OpenClawCompleteResponse {
30
+ text: string;
31
+ }
32
+
33
+ // ── Host model config (mirrors OpenClaw SDK types loosely) ──────────
34
+
35
+ export interface HostModelDefinition {
36
+ id: string;
37
+ name: string;
38
+ api?: string;
39
+ [key: string]: unknown;
40
+ }
41
+
42
+ export interface HostModelProvider {
43
+ baseUrl: string;
44
+ apiKey?: string | { source: string; provider: string; id: string };
45
+ api?: string;
46
+ headers?: Record<string, string>;
47
+ models: HostModelDefinition[];
48
+ }
49
+
50
+ export interface HostModelsConfig {
51
+ providers?: Record<string, HostModelProvider>;
52
+ }
53
+
54
+ // ── Resolved provider info (internal) ───────────────────────────────
55
+
56
+ interface ResolvedProvider {
57
+ name: string;
58
+ baseUrl: string;
59
+ apiKey?: string;
60
+ api?: string;
61
+ headers?: Record<string, string>;
62
+ model: string;
63
+ }
64
+
65
+ // ── Client ──────────────────────────────────────────────────────────
66
+
67
+ export class OpenClawAPIClient implements OpenClawAPI {
68
+ private completionProvider: ResolvedProvider | undefined;
69
+ private embeddingProvider: ResolvedProvider | undefined;
70
+
71
+ constructor(
72
+ private log: Logger,
73
+ hostModels?: HostModelsConfig,
74
+ ) {
75
+ if (hostModels?.providers) {
76
+ this.completionProvider = pickCompletionProvider(hostModels.providers, log);
77
+ this.embeddingProvider = pickEmbeddingProvider(hostModels.providers, log);
78
+ }
79
+ }
80
+
81
+ // ── Embedding ─────────────────────────────────────────────────────
82
+
83
+ async embed(request: OpenClawEmbedRequest): Promise<OpenClawEmbedResponse> {
84
+ const provider = this.embeddingProvider;
85
+ if (!provider) {
86
+ throw new Error(
87
+ "No host embedding provider available. " +
88
+ "Configure a model provider with an embedding model in OpenClaw, " +
89
+ "or use an explicit embedding provider (openai, gemini, cohere, etc.)."
90
+ );
91
+ }
92
+
93
+ const model = request.model ?? provider.model;
94
+ const endpoint = normalizeEndpoint(provider.baseUrl, "/embeddings");
95
+
96
+ this.log.debug(`OpenClawAPI.embed → ${provider.name} (${model}) [${request.texts.length} texts]`);
97
+
98
+ const resp = await fetch(endpoint, {
99
+ method: "POST",
100
+ headers: buildHeaders(provider),
101
+ body: JSON.stringify({ input: request.texts, model }),
102
+ signal: AbortSignal.timeout(30_000),
103
+ });
104
+
105
+ if (!resp.ok) {
106
+ const body = await resp.text();
107
+ throw new Error(`Host embedding failed (${provider.name} ${resp.status}): ${body}`);
108
+ }
109
+
110
+ const json = (await resp.json()) as {
111
+ data: Array<{ embedding: number[] }>;
112
+ };
113
+
114
+ const embeddings = json.data.map((d) => d.embedding);
115
+ const dimensions = embeddings[0]?.length ?? 0;
116
+
117
+ this.log.debug(`OpenClawAPI.embed ← ${embeddings.length} vectors, dim=${dimensions}`);
118
+ return { embeddings, dimensions };
119
+ }
120
+
121
+ // ── Completion ────────────────────────────────────────────────────
122
+
123
+ async complete(request: OpenClawCompleteRequest): Promise<OpenClawCompleteResponse> {
124
+ const provider = this.completionProvider;
125
+ if (!provider) {
126
+ throw new Error(
127
+ "No host completion provider available. " +
128
+ "Configure a model provider in OpenClaw, " +
129
+ "or use an explicit summarizer provider (openai, anthropic, gemini, etc.)."
130
+ );
131
+ }
132
+
133
+ const model = request.model ?? provider.model;
134
+ const endpoint = normalizeEndpoint(provider.baseUrl, "/chat/completions");
135
+
136
+ this.log.debug(`OpenClawAPI.complete → ${provider.name} (${model})`);
137
+
138
+ const body: Record<string, unknown> = {
139
+ model,
140
+ messages: [{ role: "user", content: request.prompt }],
141
+ temperature: request.temperature ?? 0,
142
+ };
143
+ if (request.maxTokens) {
144
+ body.max_tokens = request.maxTokens;
145
+ }
146
+
147
+ const resp = await fetch(endpoint, {
148
+ method: "POST",
149
+ headers: buildHeaders(provider),
150
+ body: JSON.stringify(body),
151
+ signal: AbortSignal.timeout(60_000),
152
+ });
153
+
154
+ if (!resp.ok) {
155
+ const respBody = await resp.text();
156
+ throw new Error(`Host completion failed (${provider.name} ${resp.status}): ${respBody}`);
157
+ }
158
+
159
+ const json = (await resp.json()) as {
160
+ choices: Array<{ message: { content: string } }>;
161
+ };
162
+
163
+ const text = json.choices?.[0]?.message?.content ?? "";
164
+ this.log.debug(`OpenClawAPI.complete ← ${text.length} chars`);
165
+ return { text };
166
+ }
167
+ }
168
+
169
+ // ── Helpers ─────────────────────────────────────────────────────────
170
+
171
+ /**
172
+ * Resolve a SecretInput (string | SecretRef) to a plain string.
173
+ * For env-sourced SecretRef, reads from process.env.
174
+ * Other sources are not supported — returns undefined.
175
+ */
176
+ function resolveApiKey(
177
+ input: string | { source: string; provider: string; id: string } | undefined,
178
+ ): string | undefined {
179
+ if (!input) return undefined;
180
+ if (typeof input === "string") return input;
181
+ // SecretRef — only env source is supported in plugin context
182
+ if (input.source === "env") return process.env[input.id];
183
+ return undefined;
184
+ }
185
+
186
+ function buildHeaders(provider: ResolvedProvider): Record<string, string> {
187
+ const headers: Record<string, string> = {
188
+ "Content-Type": "application/json",
189
+ ...provider.headers,
190
+ };
191
+ if (provider.apiKey) {
192
+ headers["Authorization"] = `Bearer ${provider.apiKey}`;
193
+ }
194
+ return headers;
195
+ }
196
+
197
+ /**
198
+ * Normalize base URL + path suffix.
199
+ * e.g. "https://api.openai.com/v1" + "/chat/completions"
200
+ * → "https://api.openai.com/v1/chat/completions"
201
+ */
202
+ function normalizeEndpoint(baseUrl: string, suffix: string): string {
203
+ const base = baseUrl.replace(/\/+$/, "");
204
+ // If baseUrl already ends with the suffix, don't double-append
205
+ if (base.endsWith(suffix)) return base;
206
+ return `${base}${suffix}`;
207
+ }
208
+
209
+ /**
210
+ * Pick the best provider for chat completions.
211
+ * Priority: openai-completions API > has apiKey > first with models.
212
+ */
213
+ function pickCompletionProvider(
214
+ providers: Record<string, HostModelProvider>,
215
+ log: Logger,
216
+ ): ResolvedProvider | undefined {
217
+ const entries = Object.entries(providers).filter(([, p]) => p.models?.length > 0);
218
+ if (entries.length === 0) return undefined;
219
+
220
+ // Sort: prefer openai-completions api, then providers with apiKey
221
+ entries.sort(([, a], [, b]) => {
222
+ const aScore = (a.api === "openai-completions" ? 2 : 0) + (resolveApiKey(a.apiKey) ? 1 : 0);
223
+ const bScore = (b.api === "openai-completions" ? 2 : 0) + (resolveApiKey(b.apiKey) ? 1 : 0);
224
+ return bScore - aScore;
225
+ });
226
+
227
+ const [name, provider] = entries[0];
228
+ const model = provider.models[0].id;
229
+ log.info(`Host completion provider: ${name} → ${model}`);
230
+
231
+ return {
232
+ name,
233
+ baseUrl: provider.baseUrl,
234
+ apiKey: resolveApiKey(provider.apiKey),
235
+ api: provider.api,
236
+ headers: provider.headers,
237
+ model,
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Pick the best provider for embeddings.
243
+ * Priority: model name contains "embed" > first provider with apiKey.
244
+ */
245
+ function pickEmbeddingProvider(
246
+ providers: Record<string, HostModelProvider>,
247
+ log: Logger,
248
+ ): ResolvedProvider | undefined {
249
+ const entries = Object.entries(providers).filter(([, p]) => p.models?.length > 0);
250
+ if (entries.length === 0) return undefined;
251
+
252
+ // Try to find a provider that has an embedding model
253
+ for (const [name, provider] of entries) {
254
+ const embedModel = provider.models.find((m) =>
255
+ m.id.toLowerCase().includes("embed") || m.name.toLowerCase().includes("embed"),
256
+ );
257
+ if (embedModel) {
258
+ log.info(`Host embedding provider: ${name} → ${embedModel.id}`);
259
+ return {
260
+ name,
261
+ baseUrl: provider.baseUrl,
262
+ apiKey: resolveApiKey(provider.apiKey),
263
+ api: provider.api,
264
+ headers: provider.headers,
265
+ model: embedModel.id,
266
+ };
267
+ }
268
+ }
269
+
270
+ // Fallback: use first provider with apiKey, pick first model
271
+ const withKey = entries.find(([, p]) => !!resolveApiKey(p.apiKey));
272
+ if (withKey) {
273
+ const [name, provider] = withKey;
274
+ const model = provider.models[0].id;
275
+ log.info(`Host embedding provider (fallback): ${name} → ${model}`);
276
+ return {
277
+ name,
278
+ baseUrl: provider.baseUrl,
279
+ apiKey: resolveApiKey(provider.apiKey),
280
+ api: provider.api,
281
+ headers: provider.headers,
282
+ model,
283
+ };
284
+ }
285
+
286
+ return undefined;
287
+ }
@@ -246,7 +246,7 @@ export class RecallEngine {
246
246
  if (candidateSkills.length === 0) return [];
247
247
 
248
248
  // LLM relevance judgment
249
- const summarizer = new Summarizer(this.ctx.config.summarizer, this.ctx.log);
249
+ const summarizer = new Summarizer(this.ctx.config.summarizer, this.ctx.log, this.ctx.openclawAPI);
250
250
  const relevantIndices = await this.judgeSkillRelevance(summarizer, query, candidateSkills);
251
251
 
252
252
  return relevantIndices.map((idx) => {
@@ -1,35 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import type { SummarizerConfig, SummaryProvider, Logger, PluginContext } from "../types";
4
-
5
- /**
6
- * Detect provider type from provider key name or base URL.
7
- */
8
- function detectProvider(providerKey: string | undefined, baseUrl: string): SummaryProvider {
9
- const key = providerKey?.toLowerCase() ?? "";
10
- const url = baseUrl.toLowerCase();
11
- if (key.includes("anthropic") || url.includes("anthropic")) return "anthropic";
12
- if (key.includes("gemini") || url.includes("generativelanguage.googleapis.com")) {
13
- return "gemini";
14
- }
15
- if (key.includes("bedrock") || url.includes("bedrock")) return "bedrock";
16
- return "openai_compatible";
17
- }
18
-
19
- /**
20
- * Return the correct default endpoint for a given provider.
21
- */
22
- function defaultEndpointForProvider(provider: SummaryProvider, baseUrl: string): string {
23
- const stripped = baseUrl.replace(/\/+$/, "");
24
- if (provider === "anthropic") {
25
- if (stripped.endsWith("/v1/messages")) return stripped;
26
- return `${stripped}/v1/messages`;
27
- }
28
- // OpenAI-compatible providers
29
- if (stripped.endsWith("/chat/completions")) return stripped;
30
- if (stripped.endsWith("/completions")) return stripped;
31
- return `${stripped}/chat/completions`;
32
- }
3
+ import type { SummarizerConfig, Logger, PluginContext, OpenClawAPI } from "../types";
33
4
 
34
5
  /**
35
6
  * Build a SummarizerConfig from OpenClaw's native model configuration (openclaw.json).
@@ -59,12 +30,13 @@ export function loadOpenClawFallbackConfig(log: Logger): SummarizerConfig | unde
59
30
  const apiKey: string | undefined = providerCfg.apiKey;
60
31
  if (!baseUrl || !apiKey) return undefined;
61
32
 
62
- const provider = detectProvider(providerKey, baseUrl);
63
- const endpoint = defaultEndpointForProvider(provider, baseUrl);
33
+ const endpoint = baseUrl.endsWith("/chat/completions")
34
+ ? baseUrl
35
+ : baseUrl.replace(/\/+$/, "") + "/chat/completions";
64
36
 
65
- log.debug(`OpenClaw fallback model: ${modelId} via ${baseUrl} (${provider})`);
37
+ log.debug(`OpenClaw fallback model: ${modelId} via ${baseUrl}`);
66
38
  return {
67
- provider,
39
+ provider: "openai_compatible",
68
40
  endpoint,
69
41
  apiKey,
70
42
  model: modelId,
@@ -94,86 +66,42 @@ export interface LLMCallOptions {
94
66
  maxTokens?: number;
95
67
  temperature?: number;
96
68
  timeoutMs?: number;
69
+ /** Pass ctx.openclawAPI so callLLMOnce can handle provider === "openclaw" */
70
+ openclawAPI?: OpenClawAPI;
97
71
  }
98
72
 
99
- function normalizeOpenAIEndpoint(url: string): string {
73
+ function normalizeEndpoint(url: string): string {
100
74
  const stripped = url.replace(/\/+$/, "");
101
75
  if (stripped.endsWith("/chat/completions")) return stripped;
102
76
  if (stripped.endsWith("/completions")) return stripped;
103
77
  return `${stripped}/chat/completions`;
104
78
  }
105
79
 
106
- function normalizeAnthropicEndpoint(url: string): string {
107
- const stripped = url.replace(/\/+$/, "");
108
- if (stripped.endsWith("/v1/messages")) return stripped;
109
- if (stripped.endsWith("/messages")) return stripped;
110
- return `${stripped}/v1/messages`;
111
- }
112
-
113
- function isAnthropicProvider(cfg: SummarizerConfig): boolean {
114
- return cfg.provider === "anthropic";
115
- }
116
-
117
80
  /**
118
81
  * Make a single LLM call with the given config. Throws on failure.
119
- * Dispatches to Anthropic or OpenAI-compatible format based on provider.
82
+ * When cfg.provider === "openclaw", delegates to the OpenClaw host completion API.
120
83
  */
121
84
  export async function callLLMOnce(
122
85
  cfg: SummarizerConfig,
123
86
  prompt: string,
124
87
  opts: LLMCallOptions = {},
125
88
  ): Promise<string> {
126
- if (isAnthropicProvider(cfg)) {
127
- return callLLMOnceAnthropic(cfg, prompt, opts);
128
- }
129
- return callLLMOnceOpenAI(cfg, prompt, opts);
130
- }
131
-
132
- async function callLLMOnceAnthropic(
133
- cfg: SummarizerConfig,
134
- prompt: string,
135
- opts: LLMCallOptions = {},
136
- ): Promise<string> {
137
- const endpoint = normalizeAnthropicEndpoint(
138
- cfg.endpoint ?? "https://api.anthropic.com/v1/messages",
139
- );
140
- const model = cfg.model ?? "claude-3-haiku-20240307";
141
- const headers: Record<string, string> = {
142
- "Content-Type": "application/json",
143
- "x-api-key": cfg.apiKey ?? "",
144
- "anthropic-version": "2023-06-01",
145
- ...cfg.headers,
146
- };
147
-
148
- const resp = await fetch(endpoint, {
149
- method: "POST",
150
- headers,
151
- body: JSON.stringify({
152
- model,
89
+ // Handle openclaw provider via host completion API
90
+ if (cfg.provider === "openclaw") {
91
+ const api = opts.openclawAPI;
92
+ if (!api) {
93
+ throw new Error("OpenClaw API not available. Ensure sharing.capabilities.hostCompletion is enabled.");
94
+ }
95
+ const response = await api.complete({
96
+ prompt,
97
+ maxTokens: opts.maxTokens ?? 1024,
153
98
  temperature: opts.temperature ?? 0.1,
154
- max_tokens: opts.maxTokens ?? 1024,
155
- messages: [{ role: "user", content: prompt }],
156
- }),
157
- signal: AbortSignal.timeout(opts.timeoutMs ?? 30_000),
158
- });
159
-
160
- if (!resp.ok) {
161
- const body = await resp.text();
162
- throw new Error(`LLM call failed (${resp.status}): ${body}`);
99
+ model: cfg.model,
100
+ });
101
+ return response.text.trim();
163
102
  }
164
103
 
165
- const json = (await resp.json()) as { content: Array<{ type: string; text: string }> };
166
- return json.content.find((c) => c.type === "text")?.text?.trim() ?? "";
167
- }
168
-
169
- async function callLLMOnceOpenAI(
170
- cfg: SummarizerConfig,
171
- prompt: string,
172
- opts: LLMCallOptions = {},
173
- ): Promise<string> {
174
- const endpoint = normalizeOpenAIEndpoint(
175
- cfg.endpoint ?? "https://api.openai.com/v1/chat/completions",
176
- );
104
+ const endpoint = normalizeEndpoint(cfg.endpoint ?? "https://api.openai.com/v1/chat/completions");
177
105
  const model = cfg.model ?? "gpt-4o-mini";
178
106
  const headers: Record<string, string> = {
179
107
  "Content-Type": "application/json",
@@ -0,0 +1,40 @@
1
+ import type {
2
+ ClientModeConfig as RootClientModeConfig,
3
+ HubModeConfig as RootHubModeConfig,
4
+ SharingCapabilities as RootSharingCapabilities,
5
+ SharingConfig as RootSharingConfig,
6
+ SharingRole as RootSharingRole,
7
+ } from "../types";
8
+ import type {
9
+ ClientModeConfig as SharingClientModeConfig,
10
+ GroupInfo,
11
+ HubModeConfig as SharingHubModeConfig,
12
+ HubSearchHit,
13
+ NetworkSearchResult,
14
+ SharingCapabilities as SharingSharingCapabilities,
15
+ SharingConfig as SharingSharingConfig,
16
+ SharingRole as SharingSharingRole,
17
+ SkillBundle,
18
+ UserInfo,
19
+ } from "./types";
20
+
21
+ type Assert<T extends true> = T;
22
+ type Equal<A, B> =
23
+ (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
24
+ ? ((<T>() => T extends B ? 1 : 2) extends (<T>() => T extends A ? 1 : 2) ? true : false)
25
+ : false;
26
+ type Extends<A, B> = A extends B ? true : false;
27
+
28
+ type _SharingRoleMatchesRoot = Assert<Equal<SharingSharingRole, RootSharingRole>>;
29
+ type _SharingCapabilitiesMatchRoot = Assert<Equal<SharingSharingCapabilities, RootSharingCapabilities>>;
30
+ type _HubModeConfigMatchesRoot = Assert<Equal<SharingHubModeConfig, RootHubModeConfig>>;
31
+ type _ClientModeConfigMatchesRoot = Assert<Equal<SharingClientModeConfig, RootClientModeConfig>>;
32
+ type _SharingConfigMatchesRoot = Assert<Equal<SharingSharingConfig, RootSharingConfig>>;
33
+
34
+ type _GroupInfoExists = Assert<Extends<GroupInfo, { id: string; name: string }>>;
35
+ type _UserInfoExists = Assert<Extends<UserInfo, { id: string; username: string }>>;
36
+ type _HubSearchHitExists = Assert<Extends<HubSearchHit, { remoteHitId: string; hubRank: number }>>;
37
+ type _NetworkSearchResultExists = Assert<Extends<NetworkSearchResult, { local: unknown; hub: unknown }>>;
38
+ type _SkillBundleExists = Assert<Extends<SkillBundle, { metadata: unknown; bundle: unknown }>>;
39
+
40
+ export {};
@@ -0,0 +1,102 @@
1
+ import type {
2
+ ClientModeConfig,
3
+ HubModeConfig,
4
+ Role,
5
+ SearchResult,
6
+ SharingCapabilities,
7
+ SharingConfig,
8
+ SharingRole,
9
+ SkillGenerateOutput,
10
+ } from "../types";
11
+
12
+ export type HubScope = "local" | "group" | "all";
13
+ export type SharedVisibility = "group" | "public";
14
+ export type UserRole = "admin" | "member";
15
+ export type UserStatus = "pending" | "active" | "blocked" | "rejected";
16
+
17
+ export type { ClientModeConfig, HubModeConfig, SharingCapabilities, SharingConfig, SharingRole };
18
+
19
+ export interface GroupInfo {
20
+ id: string;
21
+ name: string;
22
+ description?: string;
23
+ }
24
+
25
+ export interface UserInfo {
26
+ id: string;
27
+ username: string;
28
+ deviceName?: string;
29
+ role: UserRole;
30
+ status: UserStatus;
31
+ groups: GroupInfo[];
32
+ }
33
+
34
+ export interface HubSearchHit {
35
+ remoteHitId: string;
36
+ summary: string;
37
+ excerpt: string;
38
+ hubRank: number;
39
+ taskTitle: string | null;
40
+ ownerName: string;
41
+ groupName: string | null;
42
+ visibility: SharedVisibility;
43
+ source: {
44
+ ts: number;
45
+ role: Role;
46
+ };
47
+ }
48
+
49
+ export interface HubSearchMeta {
50
+ totalCandidates: number;
51
+ searchedGroups: string[];
52
+ includedPublic: boolean;
53
+ }
54
+
55
+ export interface HubSearchResult {
56
+ hits: HubSearchHit[];
57
+ meta: HubSearchMeta;
58
+ }
59
+
60
+ export interface HubMemoryDetail {
61
+ remoteHitId: string;
62
+ content: string;
63
+ summary: string;
64
+ source: {
65
+ ts: number;
66
+ role: Role;
67
+ };
68
+ }
69
+
70
+ export interface HubSkillHit {
71
+ skillId: string;
72
+ name: string;
73
+ description: string;
74
+ version: number;
75
+ visibility: SharedVisibility;
76
+ groupName: string | null;
77
+ ownerName: string;
78
+ qualityScore: number | null;
79
+ }
80
+
81
+ export interface HubSkillSearchResult {
82
+ hits: HubSkillHit[];
83
+ }
84
+
85
+ export interface NetworkSearchResult {
86
+ local: SearchResult;
87
+ hub: HubSearchResult;
88
+ }
89
+
90
+ export interface SkillBundleMetadata {
91
+ id: string;
92
+ name: string;
93
+ description: string;
94
+ version: number;
95
+ qualityScore: number | null;
96
+ }
97
+
98
+ export interface SkillBundle {
99
+ metadata: SkillBundleMetadata;
100
+ bundle: SkillGenerateOutput;
101
+ }
102
+
@@ -145,7 +145,7 @@ export class SkillEvaluator {
145
145
  .replace("{SUMMARY}", task.summary.slice(0, 3000));
146
146
 
147
147
  try {
148
- const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "SkillEvaluator.create");
148
+ const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "SkillEvaluator.create", { openclawAPI: this.ctx.openclawAPI });
149
149
  return this.parseJSON<CreateEvalResult>(raw, {
150
150
  shouldGenerate: false, reason: "parse failed", suggestedName: "", suggestedTags: [], confidence: 0,
151
151
  });
@@ -169,7 +169,7 @@ export class SkillEvaluator {
169
169
  .replace("{SUMMARY}", task.summary.slice(0, 3000));
170
170
 
171
171
  try {
172
- const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "SkillEvaluator.upgrade");
172
+ const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "SkillEvaluator.upgrade", { openclawAPI: this.ctx.openclawAPI });
173
173
  return this.parseJSON<UpgradeEvalResult>(raw, {
174
174
  shouldUpgrade: false, upgradeType: "refine", dimensions: [], reason: "parse failed", mergeStrategy: "", confidence: 0,
175
175
  });
@@ -179,6 +179,7 @@ export class SkillEvaluator {
179
179
  }
180
180
  }
181
181
 
182
+
182
183
  private parseJSON<T>(raw: string, fallback: T): T {
183
184
  const jsonMatch = raw.match(/\{[\s\S]*\}/);
184
185
  if (!jsonMatch) return fallback;
@@ -12,8 +12,6 @@ import { SkillUpgrader } from "./upgrader";
12
12
  import { SkillInstaller } from "./installer";
13
13
  import { buildSkillConfigChain, callLLMWithFallback } from "../shared/llm-call";
14
14
 
15
- export type SkillEvolvedCallback = (skillName: string, upgradeType: "created" | "upgraded") => void;
16
-
17
15
  export class SkillEvolver {
18
16
  private evaluator: SkillEvaluator;
19
17
  private generator: SkillGenerator;
@@ -21,7 +19,6 @@ export class SkillEvolver {
21
19
  private installer: SkillInstaller;
22
20
  private processing = false;
23
21
  private queue: Task[] = [];
24
- onSkillEvolved: SkillEvolvedCallback | null = null;
25
22
 
26
23
  constructor(
27
24
  private store: SqliteStore,
@@ -282,7 +279,6 @@ Use selectedIndex 0 when none is highly relevant.`;
282
279
  if (upgraded) {
283
280
  this.store.linkTaskSkill(task.id, freshSkill.id, "evolved_from", freshSkill.version + 1);
284
281
  this.installer.syncIfInstalled(freshSkill.name);
285
- this.onSkillEvolved?.(freshSkill.name, "upgraded");
286
282
  } else {
287
283
  this.store.linkTaskSkill(task.id, freshSkill.id, "applied_to", freshSkill.version);
288
284
  }
@@ -311,7 +307,6 @@ Use selectedIndex 0 when none is highly relevant.`;
311
307
  this.markChunksWithSkill(chunks, skill.id);
312
308
  this.store.linkTaskSkill(task.id, skill.id, "generated_from", 1);
313
309
  this.store.setTaskSkillMeta(task.id, { skillStatus: "generated", skillReason: evalResult.reason });
314
- this.onSkillEvolved?.(skill.name, "created");
315
310
 
316
311
  const autoInstall = this.ctx.config.skillEvolution?.autoInstall ?? DEFAULTS.skillAutoInstall;
317
312
  if (autoInstall && skill.status === "active") {