@nodeskai/genehub-sdk 0.1.0

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.
package/dist/index.js ADDED
@@ -0,0 +1,1218 @@
1
+ // src/adapters/generic.ts
2
+ import { mkdir, readdir, readFile, rm, stat, writeFile } from "fs/promises";
3
+ import { join } from "path";
4
+ import { stringify } from "yaml";
5
+
6
+ // src/adapters/base.ts
7
+ var BaseAdapter = class {
8
+ async install(manifest, options) {
9
+ const result = await this.doInstall(manifest, options);
10
+ await this.onPostInstall(manifest, result);
11
+ return result;
12
+ }
13
+ async onPostInstall(_manifest, _result) {
14
+ }
15
+ async onPostUninstall(_slug, _result) {
16
+ }
17
+ async uninstall(slug, options) {
18
+ const result = await this.doUninstall(slug, options);
19
+ await this.onPostUninstall(slug, result);
20
+ return result;
21
+ }
22
+ generateSkillContent(manifest, metadataNamespace) {
23
+ const skillMeta = manifest.skill.always ? "true" : "false";
24
+ const frontMatter = [
25
+ "---",
26
+ `name: ${manifest.skill.name}`,
27
+ `version: ${manifest.version}`,
28
+ `description: ${manifest.short_description}`,
29
+ "metadata:",
30
+ ` ${metadataNamespace}:`,
31
+ ` always: ${skillMeta}`,
32
+ "---"
33
+ ].join("\n");
34
+ if (manifest.skill.content) {
35
+ const content = manifest.skill.content.trim();
36
+ if (content.startsWith("---")) {
37
+ return content;
38
+ }
39
+ return `${frontMatter}
40
+
41
+ ${content}`;
42
+ }
43
+ return frontMatter;
44
+ }
45
+ parseSkillVersion(content) {
46
+ const match = content.match(/^---[\s\S]*?version:\s*(.+?)[\s\n][\s\S]*?---/m);
47
+ return match?.[1]?.trim() ?? null;
48
+ }
49
+ };
50
+
51
+ // src/adapters/generic.ts
52
+ var DEFAULT_DIR = join(process.cwd(), ".genehub", "genes");
53
+ var GenericAdapter = class extends BaseAdapter {
54
+ product = "generic";
55
+ genesDir;
56
+ constructor(options) {
57
+ super();
58
+ this.genesDir = options?.genesDir ?? DEFAULT_DIR;
59
+ }
60
+ async detect() {
61
+ return true;
62
+ }
63
+ async doInstall(manifest, options) {
64
+ const targetDir = options?.targetPath ? join(options.targetPath, manifest.slug) : join(this.genesDir, manifest.slug);
65
+ await mkdir(targetDir, { recursive: true });
66
+ const files = [];
67
+ const yamlPath = join(targetDir, "gene.yaml");
68
+ await writeFile(yamlPath, stringify(manifest), "utf-8");
69
+ files.push(yamlPath);
70
+ if (manifest.skill.content) {
71
+ const skillPath = join(targetDir, "SKILL.md");
72
+ await writeFile(skillPath, manifest.skill.content, "utf-8");
73
+ files.push(skillPath);
74
+ }
75
+ return {
76
+ success: true,
77
+ slug: manifest.slug,
78
+ version: manifest.version,
79
+ files,
80
+ needsRestart: false,
81
+ dependencies: manifest.dependencies.map((d) => d.slug)
82
+ };
83
+ }
84
+ async doUninstall(slug, _options) {
85
+ const targetDir = join(this.genesDir, slug);
86
+ try {
87
+ await rm(targetDir, { recursive: true });
88
+ } catch {
89
+ }
90
+ return { success: true, slug, files: [targetDir], needsRestart: false };
91
+ }
92
+ async list() {
93
+ try {
94
+ const dirs = await readdir(this.genesDir, { withFileTypes: true });
95
+ const results = [];
96
+ for (const dir of dirs) {
97
+ if (!dir.isDirectory()) continue;
98
+ const yamlPath = join(this.genesDir, dir.name, "gene.yaml");
99
+ try {
100
+ const s = await stat(yamlPath);
101
+ const raw = await readFile(yamlPath, "utf-8");
102
+ const versionMatch = raw.match(/^version:\s*["']?(.+?)["']?\s*$/m);
103
+ results.push({
104
+ slug: dir.name,
105
+ version: versionMatch?.[1] ?? "unknown",
106
+ installedAt: s.mtime.toISOString(),
107
+ files: [yamlPath]
108
+ });
109
+ } catch {
110
+ }
111
+ }
112
+ return results;
113
+ } catch {
114
+ return [];
115
+ }
116
+ }
117
+ async isInstalled(slug) {
118
+ try {
119
+ await stat(join(this.genesDir, slug, "gene.yaml"));
120
+ return true;
121
+ } catch {
122
+ return false;
123
+ }
124
+ }
125
+ async getInstalledVersion(slug) {
126
+ try {
127
+ const raw = await readFile(join(this.genesDir, slug, "gene.yaml"), "utf-8");
128
+ const match = raw.match(/^version:\s*["']?(.+?)["']?\s*$/m);
129
+ return match?.[1] ?? null;
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+ };
135
+
136
+ // src/adapters/nanobot.ts
137
+ import { mkdir as mkdir2, readdir as readdir2, readFile as readFile2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
138
+ import { homedir } from "os";
139
+ import { join as join2 } from "path";
140
+ var DEFAULT_WORKSPACE = join2(homedir(), ".nanobot", "workspace");
141
+ var NanobotAdapter = class extends BaseAdapter {
142
+ product = "nanobot";
143
+ workspace;
144
+ constructor(options) {
145
+ super();
146
+ this.workspace = options?.workspace ?? DEFAULT_WORKSPACE;
147
+ }
148
+ get skillsDir() {
149
+ return join2(this.workspace, "skills");
150
+ }
151
+ async detect() {
152
+ try {
153
+ await stat2(join2(homedir(), ".nanobot", "config.json"));
154
+ return true;
155
+ } catch {
156
+ return false;
157
+ }
158
+ }
159
+ async doInstall(manifest, options) {
160
+ const targetDir = options?.targetPath ? join2(options.targetPath, manifest.skill.name) : join2(this.skillsDir, manifest.skill.name);
161
+ await mkdir2(targetDir, { recursive: true });
162
+ const files = [];
163
+ const skillContent = this.buildNanobotSkillContent(manifest);
164
+ const skillPath = join2(targetDir, "SKILL.md");
165
+ await writeFile2(skillPath, skillContent, "utf-8");
166
+ files.push(skillPath);
167
+ if (manifest.mcp_servers.length > 0) {
168
+ await this.mergeNanobotMcpConfig(manifest.mcp_servers);
169
+ }
170
+ return {
171
+ success: true,
172
+ slug: manifest.slug,
173
+ version: manifest.version,
174
+ files,
175
+ needsRestart: false,
176
+ dependencies: manifest.dependencies.map((d) => d.slug)
177
+ };
178
+ }
179
+ async onPostInstall(manifest, _result) {
180
+ await this.writeMemoryEntry(manifest, "install");
181
+ }
182
+ async doUninstall(slug, _options) {
183
+ const targetDir = join2(this.skillsDir, slug);
184
+ const files = [];
185
+ try {
186
+ await rm2(targetDir, { recursive: true });
187
+ files.push(targetDir);
188
+ } catch {
189
+ }
190
+ return { success: true, slug, files, needsRestart: false };
191
+ }
192
+ async onPostUninstall(slug, _result) {
193
+ await this.writeMemoryEntry(
194
+ { slug, name: slug, version: "unknown" },
195
+ "uninstall"
196
+ );
197
+ }
198
+ async list() {
199
+ try {
200
+ const dirs = await readdir2(this.skillsDir, { withFileTypes: true });
201
+ const results = [];
202
+ for (const dir of dirs) {
203
+ if (!dir.isDirectory()) continue;
204
+ const skillPath = join2(this.skillsDir, dir.name, "SKILL.md");
205
+ try {
206
+ const s = await stat2(skillPath);
207
+ const content = await readFile2(skillPath, "utf-8");
208
+ const version = this.parseSkillVersion(content) ?? "unknown";
209
+ results.push({
210
+ slug: dir.name,
211
+ version,
212
+ installedAt: s.mtime.toISOString(),
213
+ files: [skillPath]
214
+ });
215
+ } catch {
216
+ }
217
+ }
218
+ return results;
219
+ } catch {
220
+ return [];
221
+ }
222
+ }
223
+ async isInstalled(slug) {
224
+ try {
225
+ await stat2(join2(this.skillsDir, slug, "SKILL.md"));
226
+ return true;
227
+ } catch {
228
+ return false;
229
+ }
230
+ }
231
+ async getInstalledVersion(slug) {
232
+ try {
233
+ const content = await readFile2(join2(this.skillsDir, slug, "SKILL.md"), "utf-8");
234
+ return this.parseSkillVersion(content);
235
+ } catch {
236
+ return null;
237
+ }
238
+ }
239
+ async writeMemoryEntry(manifest, action) {
240
+ const memoryDir = join2(this.workspace, "memory");
241
+ await mkdir2(memoryDir, { recursive: true });
242
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
243
+ const memoryPath = join2(memoryDir, `${today}.md`);
244
+ let existing = "";
245
+ try {
246
+ existing = await readFile2(memoryPath, "utf-8");
247
+ } catch {
248
+ }
249
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false });
250
+ const verb = action === "install" ? "\u5B66\u4E60\u4E86" : "\u9057\u5FD8\u4E86";
251
+ const entry = `
252
+ - [${time}] \u901A\u8FC7 GeneHub ${verb}\u57FA\u56E0: **${manifest.name ?? manifest.slug}** v${manifest.version ?? "?"}
253
+ `;
254
+ await writeFile2(memoryPath, existing + entry, "utf-8");
255
+ }
256
+ buildNanobotSkillContent(manifest) {
257
+ if (manifest.skill.content?.trim().startsWith("---")) {
258
+ return manifest.skill.content;
259
+ }
260
+ return this.generateSkillContent(manifest, "nanobot");
261
+ }
262
+ async mergeNanobotMcpConfig(mcpServers) {
263
+ const configPath = join2(homedir(), ".nanobot", "config.json");
264
+ let config = {};
265
+ try {
266
+ const raw = await readFile2(configPath, "utf-8");
267
+ config = JSON.parse(raw);
268
+ } catch {
269
+ return;
270
+ }
271
+ if (!config.tools) config.tools = {};
272
+ const tools = config.tools;
273
+ if (!tools.mcpServers) tools.mcpServers = {};
274
+ const servers = tools.mcpServers;
275
+ for (const srv of mcpServers) {
276
+ if (servers[srv.name]) continue;
277
+ servers[srv.name] = {
278
+ ...srv.command ? { command: srv.command, args: srv.args ?? [], env: srv.env ?? {} } : {},
279
+ ...srv.url ? { url: srv.url, headers: srv.headers ?? {} } : {}
280
+ };
281
+ }
282
+ await writeFile2(configPath, JSON.stringify(config, null, 2), "utf-8");
283
+ }
284
+ };
285
+
286
+ // src/adapters/openclaw.ts
287
+ import { randomUUID } from "crypto";
288
+ import { appendFile, mkdir as mkdir3, readdir as readdir3, readFile as readFile3, rm as rm3, stat as stat3, writeFile as writeFile3 } from "fs/promises";
289
+ import { homedir as homedir2 } from "os";
290
+ import { basename, join as join3 } from "path";
291
+ var DEFAULT_CONFIG_DIR = join3(homedir2(), ".openclaw");
292
+ var DEFAULT_WORKSPACE_DIR = join3(DEFAULT_CONFIG_DIR, "workspace");
293
+ var DEFAULT_SKILLS_DIR = join3(DEFAULT_WORKSPACE_DIR, "skills");
294
+ var DEFAULT_CONFIG_PATH = join3(DEFAULT_CONFIG_DIR, "openclaw.json");
295
+ var SESSIONS_REL = join3("agents", "main", "sessions");
296
+ var OpenClawAdapter = class extends BaseAdapter {
297
+ product = "openclaw";
298
+ skillsDir;
299
+ configPath;
300
+ configDir;
301
+ workspaceDir;
302
+ constructor(options) {
303
+ super();
304
+ this.configDir = options?.configDir ?? DEFAULT_CONFIG_DIR;
305
+ this.workspaceDir = options?.workspaceDir ?? DEFAULT_WORKSPACE_DIR;
306
+ this.skillsDir = options?.skillsDir ?? DEFAULT_SKILLS_DIR;
307
+ this.configPath = options?.configPath ?? DEFAULT_CONFIG_PATH;
308
+ }
309
+ async detect() {
310
+ try {
311
+ await stat3(this.configPath);
312
+ return true;
313
+ } catch {
314
+ return false;
315
+ }
316
+ }
317
+ async doInstall(manifest, options) {
318
+ const targetDir = options?.targetPath ? join3(options.targetPath, manifest.skill.name) : join3(this.skillsDir, manifest.skill.name);
319
+ await mkdir3(targetDir, { recursive: true });
320
+ const files = [];
321
+ const skillPath = join3(targetDir, "SKILL.md");
322
+ const content = this.generateSkillContent(manifest, "openclaw");
323
+ await writeFile3(skillPath, content, "utf-8");
324
+ files.push(skillPath);
325
+ if (manifest.config?.openclaw) {
326
+ await this.mergeOpenClawConfig(manifest.config.openclaw);
327
+ files.push(this.configPath);
328
+ }
329
+ if (manifest.mcp_servers?.length) {
330
+ await this.mergeMcpServers(manifest.mcp_servers);
331
+ files.push(this.configPath);
332
+ }
333
+ return {
334
+ success: true,
335
+ slug: manifest.slug,
336
+ version: manifest.version,
337
+ files: [...new Set(files)],
338
+ needsRestart: true,
339
+ dependencies: manifest.dependencies.map((d) => d.slug)
340
+ };
341
+ }
342
+ async onPostInstall(manifest, _result) {
343
+ await this.updateAgentsMd(manifest, "add");
344
+ await this.writeMemoryEntry(manifest, "install");
345
+ await this.invalidateSkillSnapshots();
346
+ await this.injectEvolutionNotification(manifest.name ?? manifest.slug, "installed");
347
+ }
348
+ async doUninstall(slug, _options) {
349
+ const targetDir = join3(this.skillsDir, slug);
350
+ const files = [];
351
+ try {
352
+ await rm3(targetDir, { recursive: true });
353
+ files.push(targetDir);
354
+ } catch {
355
+ }
356
+ return { success: true, slug, files, needsRestart: true };
357
+ }
358
+ async onPostUninstall(slug, _result) {
359
+ await this.updateAgentsMd({ slug }, "remove");
360
+ await this.writeMemoryEntry(
361
+ { slug, name: slug, version: "unknown" },
362
+ "uninstall"
363
+ );
364
+ await this.invalidateSkillSnapshots();
365
+ await this.injectEvolutionNotification(slug, "uninstalled");
366
+ }
367
+ async list() {
368
+ try {
369
+ const dirs = await readdir3(this.skillsDir, { withFileTypes: true });
370
+ const results = [];
371
+ for (const dir of dirs) {
372
+ if (!dir.isDirectory()) continue;
373
+ const skillPath = join3(this.skillsDir, dir.name, "SKILL.md");
374
+ try {
375
+ const s = await stat3(skillPath);
376
+ const content = await readFile3(skillPath, "utf-8");
377
+ const version = this.parseSkillVersion(content) ?? "unknown";
378
+ results.push({
379
+ slug: dir.name,
380
+ version,
381
+ installedAt: s.mtime.toISOString(),
382
+ files: [skillPath]
383
+ });
384
+ } catch {
385
+ }
386
+ }
387
+ return results;
388
+ } catch {
389
+ return [];
390
+ }
391
+ }
392
+ async isInstalled(slug) {
393
+ try {
394
+ await stat3(join3(this.skillsDir, slug, "SKILL.md"));
395
+ return true;
396
+ } catch {
397
+ return false;
398
+ }
399
+ }
400
+ async getInstalledVersion(slug) {
401
+ try {
402
+ const content = await readFile3(join3(this.skillsDir, slug, "SKILL.md"), "utf-8");
403
+ return this.parseSkillVersion(content);
404
+ } catch {
405
+ return null;
406
+ }
407
+ }
408
+ async updateAgentsMd(manifest, action) {
409
+ const agentsPath = join3(this.workspaceDir, "AGENTS.md");
410
+ let content;
411
+ try {
412
+ content = await readFile3(agentsPath, "utf-8");
413
+ } catch {
414
+ return;
415
+ }
416
+ const marker = `<!-- genehub:${manifest.slug} -->`;
417
+ const endMarker = `<!-- /genehub:${manifest.slug} -->`;
418
+ const existingPattern = new RegExp(
419
+ `${escapeRegex(marker)}[\\s\\S]*?${escapeRegex(endMarker)}\\n?`
420
+ );
421
+ content = content.replace(existingPattern, "");
422
+ if (action === "add") {
423
+ const geneBlock = [
424
+ marker,
425
+ `- **${manifest.name ?? manifest.slug}** (v${manifest.version}) \u2014 ${manifest.short_description ?? ""}`,
426
+ endMarker
427
+ ].join("\n");
428
+ const toolsSection = content.indexOf("## Tools");
429
+ if (toolsSection !== -1) {
430
+ const nextSection = content.indexOf("\n## ", toolsSection + 1);
431
+ const insertPos = nextSection !== -1 ? nextSection : content.length;
432
+ content = `${content.slice(0, insertPos)}
433
+ ${geneBlock}
434
+ ${content.slice(insertPos)}`;
435
+ } else {
436
+ content += `
437
+
438
+ ## GeneHub Skills
439
+
440
+ ${geneBlock}
441
+ `;
442
+ }
443
+ }
444
+ if (action === "remove" && manifest.slug === "genehub-learner") {
445
+ const bootBegin = "<!-- genehub:learning-boot -->";
446
+ const bootEnd = "<!-- /genehub:learning-boot -->";
447
+ const bootPattern = new RegExp(
448
+ `\\n?${escapeRegex(bootBegin)}[\\s\\S]*?${escapeRegex(bootEnd)}\\n?`
449
+ );
450
+ content = content.replace(bootPattern, "");
451
+ }
452
+ await writeFile3(agentsPath, content, "utf-8");
453
+ }
454
+ async writeMemoryEntry(manifest, action) {
455
+ const memoryDir = join3(this.workspaceDir, "memory");
456
+ await mkdir3(memoryDir, { recursive: true });
457
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
458
+ const memoryPath = join3(memoryDir, `${today}.md`);
459
+ let existing = "";
460
+ try {
461
+ existing = await readFile3(memoryPath, "utf-8");
462
+ } catch {
463
+ }
464
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false });
465
+ const verb = action === "install" ? "\u5B66\u4E60\u4E86" : "\u9057\u5FD8\u4E86";
466
+ const entry = `
467
+ - [${time}] \u901A\u8FC7 GeneHub ${verb}\u57FA\u56E0: **${manifest.name ?? manifest.slug}** v${manifest.version ?? "?"}
468
+ `;
469
+ await writeFile3(memoryPath, existing + entry, "utf-8");
470
+ }
471
+ async mergeOpenClawConfig(config) {
472
+ if (!config) return;
473
+ let existing = {};
474
+ try {
475
+ const raw = await readFile3(this.configPath, "utf-8");
476
+ existing = JSON.parse(raw);
477
+ } catch {
478
+ }
479
+ if (config.openclaw_config) {
480
+ Object.assign(existing, config.openclaw_config);
481
+ }
482
+ if (config.tool_allow) {
483
+ const current = existing.tools?.allow ?? [];
484
+ const merged = [.../* @__PURE__ */ new Set([...current, ...config.tool_allow])];
485
+ if (!existing.tools) existing.tools = {};
486
+ existing.tools.allow = merged;
487
+ }
488
+ await writeFile3(this.configPath, JSON.stringify(existing, null, 2), "utf-8");
489
+ }
490
+ async mergeMcpServers(servers) {
491
+ if (!servers?.length) return;
492
+ let existing = {};
493
+ try {
494
+ const raw = await readFile3(this.configPath, "utf-8");
495
+ existing = JSON.parse(raw);
496
+ } catch {
497
+ return;
498
+ }
499
+ const mcpServers = existing.mcpServers ?? {};
500
+ for (const server of servers) {
501
+ mcpServers[server.name] = {
502
+ transport: server.transport,
503
+ command: server.command,
504
+ args: server.args,
505
+ env: server.env
506
+ };
507
+ }
508
+ existing.mcpServers = mcpServers;
509
+ await writeFile3(this.configPath, JSON.stringify(existing, null, 2), "utf-8");
510
+ }
511
+ async notifySkillChange(geneName, action) {
512
+ await this.invalidateSkillSnapshots();
513
+ const notifyAction = action === "uninstalled" ? "uninstalled" : "installed";
514
+ await this.injectEvolutionNotification(geneName, notifyAction);
515
+ }
516
+ /**
517
+ * Clear cached skillsSnapshot from all OpenClaw sessions.
518
+ * Without this, OpenClaw keeps using the stale skill list even after restart.
519
+ */
520
+ async invalidateSkillSnapshots() {
521
+ const sessionsPath = join3(this.configDir, SESSIONS_REL, "sessions.json");
522
+ let raw;
523
+ try {
524
+ raw = await readFile3(sessionsPath, "utf-8");
525
+ } catch {
526
+ return;
527
+ }
528
+ try {
529
+ const store = JSON.parse(raw);
530
+ let changed = false;
531
+ for (const entry of Object.values(store)) {
532
+ if (entry && typeof entry === "object" && "skillsSnapshot" in entry) {
533
+ delete entry.skillsSnapshot;
534
+ changed = true;
535
+ }
536
+ }
537
+ if (changed) {
538
+ await writeFile3(sessionsPath, JSON.stringify(store, null, 2), "utf-8");
539
+ }
540
+ } catch {
541
+ }
542
+ }
543
+ /**
544
+ * Inject evolution notification into all active session JSONL files.
545
+ *
546
+ * Old conversation history may contain stale skill listings from the agent.
547
+ * The LLM repeats its previous answer instead of re-checking the system prompt.
548
+ * By appending a user+assistant message pair about the evolution, we override
549
+ * the stale context. Also resets systemSent to force system prompt rebuild.
550
+ */
551
+ async injectEvolutionNotification(geneName, action) {
552
+ const sessionsDir = join3(this.configDir, SESSIONS_REL);
553
+ const sessionsPath = join3(sessionsDir, "sessions.json");
554
+ let raw;
555
+ try {
556
+ raw = await readFile3(sessionsPath, "utf-8");
557
+ } catch {
558
+ return;
559
+ }
560
+ const userText = action === "installed" ? `[System] \u57FA\u56E0\u7CFB\u7EDF\u901A\u77E5: \u4F60\u521A\u521A\u83B7\u53D6\u4E86\u65B0\u7684\u57FA\u56E0\u300C${geneName}\u300D\uFF0C\u5B8C\u6210\u4E86\u4E00\u8F6E\u8FDB\u5316\u3002\u4F60\u7684\u6280\u80FD\u5217\u8868\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u4EE5 system prompt \u4E2D <available_skills> \u4E3A\u51C6\u3002` : `[System] \u57FA\u56E0\u7CFB\u7EDF\u901A\u77E5: \u57FA\u56E0\u300C${geneName}\u300D\u5DF2\u9057\u5FD8\u3002\u4F60\u7684\u6280\u80FD\u5217\u8868\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u4EE5 system prompt \u4E2D <available_skills> \u4E3A\u51C6\u3002`;
561
+ const assistantText = action === "installed" ? `\u6536\u5230\uFF0C\u6211\u5DF2\u83B7\u53D6\u65B0\u57FA\u56E0\u300C${geneName}\u300D\u5E76\u5B8C\u6210\u8FDB\u5316\u3002\u6211\u7684\u6280\u80FD\u5217\u8868\u5DF2\u66F4\u65B0\u3002` : `\u6536\u5230\uFF0C\u57FA\u56E0\u300C${geneName}\u300D\u5DF2\u9057\u5FD8\u3002\u6211\u7684\u6280\u80FD\u5217\u8868\u5DF2\u66F4\u65B0\u3002`;
562
+ try {
563
+ const store = JSON.parse(raw);
564
+ let storeChanged = false;
565
+ for (const entry of Object.values(store)) {
566
+ if (!entry || typeof entry !== "object") continue;
567
+ const rec = entry;
568
+ const sessionFile = rec.sessionFile;
569
+ if (!sessionFile) continue;
570
+ const localPath = join3(sessionsDir, basename(sessionFile));
571
+ let content;
572
+ try {
573
+ content = (await readFile3(localPath, "utf-8")).trimEnd();
574
+ if (!content) continue;
575
+ } catch {
576
+ continue;
577
+ }
578
+ try {
579
+ const lastLine = content.split("\n").pop() ?? "";
580
+ const lastEntry = JSON.parse(lastLine);
581
+ const parentId = lastEntry.id ?? randomUUID().slice(0, 8);
582
+ const now = /* @__PURE__ */ new Date();
583
+ const tsIso = now.toISOString();
584
+ const tsMs = now.getTime();
585
+ const userId = randomUUID().slice(0, 8);
586
+ const assistantId = randomUUID().slice(0, 8);
587
+ const modelProvider = rec.modelProvider ?? "system";
588
+ const modelName = rec.model ?? "system";
589
+ const userMsg = JSON.stringify({
590
+ type: "message",
591
+ id: userId,
592
+ parentId,
593
+ timestamp: tsIso,
594
+ message: {
595
+ role: "user",
596
+ content: [{ type: "text", text: userText }],
597
+ timestamp: tsMs
598
+ }
599
+ });
600
+ const assistantMsg = JSON.stringify({
601
+ type: "message",
602
+ id: assistantId,
603
+ parentId: userId,
604
+ timestamp: tsIso,
605
+ message: {
606
+ role: "assistant",
607
+ content: [{ type: "text", text: assistantText }],
608
+ api: "openai-completions",
609
+ provider: modelProvider,
610
+ model: modelName,
611
+ usage: {
612
+ input: 0,
613
+ output: 0,
614
+ cacheRead: 0,
615
+ cacheWrite: 0,
616
+ totalTokens: 0,
617
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
618
+ },
619
+ stopReason: "stop",
620
+ timestamp: tsMs
621
+ }
622
+ });
623
+ await appendFile(localPath, `
624
+ ${userMsg}
625
+ ${assistantMsg}`, "utf-8");
626
+ } catch {
627
+ continue;
628
+ }
629
+ rec.systemSent = false;
630
+ storeChanged = true;
631
+ }
632
+ if (storeChanged) {
633
+ await writeFile3(sessionsPath, JSON.stringify(store, null, 2), "utf-8");
634
+ }
635
+ } catch {
636
+ }
637
+ }
638
+ };
639
+ function escapeRegex(str) {
640
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
641
+ }
642
+
643
+ // src/adapters/index.ts
644
+ var ADAPTERS = [
645
+ () => new OpenClawAdapter(),
646
+ () => new NanobotAdapter(),
647
+ () => new GenericAdapter()
648
+ ];
649
+ async function detectAdapter() {
650
+ for (const create of ADAPTERS) {
651
+ const adapter = create();
652
+ if (await adapter.detect()) {
653
+ return adapter;
654
+ }
655
+ }
656
+ return new GenericAdapter();
657
+ }
658
+ function getAdapter(product) {
659
+ switch (product) {
660
+ case "openclaw":
661
+ return new OpenClawAdapter();
662
+ case "nanobot":
663
+ return new NanobotAdapter();
664
+ default:
665
+ return new GenericAdapter();
666
+ }
667
+ }
668
+
669
+ // src/client.ts
670
+ var GeneHubClient = class {
671
+ baseUrl;
672
+ token;
673
+ constructor(options) {
674
+ this.baseUrl = options.registryUrl.replace(/\/$/, "");
675
+ this.token = options.token;
676
+ }
677
+ async request(path, init) {
678
+ const headers = {
679
+ "Content-Type": "application/json",
680
+ ...init?.headers
681
+ };
682
+ if (this.token) {
683
+ headers.Authorization = `Bearer ${this.token}`;
684
+ }
685
+ const res = await fetch(`${this.baseUrl}${path}`, { ...init, headers });
686
+ const json = await res.json();
687
+ if (!res.ok || json.code !== 0) {
688
+ const msg = json.message || `HTTP ${res.status}`;
689
+ throw new Error(`[GeneHub] ${json.error_code ?? "error"}: ${msg}`);
690
+ }
691
+ return json.data;
692
+ }
693
+ async searchGenes(params = {}) {
694
+ const qs = new URLSearchParams();
695
+ if (params.q) qs.set("q", params.q);
696
+ if (params.category) qs.set("category", params.category);
697
+ if (params.tags?.length) qs.set("tags", params.tags.join(","));
698
+ if (params.compatibility) qs.set("compatibility", params.compatibility);
699
+ if (params.sort) qs.set("sort", params.sort);
700
+ if (params.page) qs.set("page", String(params.page));
701
+ if (params.page_size) qs.set("page_size", String(params.page_size));
702
+ const query = qs.toString();
703
+ return this.request(`/api/v1/genes${query ? `?${query}` : ""}`);
704
+ }
705
+ async getGene(slug) {
706
+ return this.request(`/api/v1/genes/${slug}`);
707
+ }
708
+ async getManifest(slug, version) {
709
+ const qs = version ? `?version=${encodeURIComponent(version)}` : "";
710
+ return this.request(`/api/v1/genes/${slug}/manifest${qs}`);
711
+ }
712
+ async getVersions(slug) {
713
+ return this.request(`/api/v1/genes/${slug}/versions`);
714
+ }
715
+ async getVersion(slug, version) {
716
+ return this.request(`/api/v1/genes/${slug}/versions/${version}`);
717
+ }
718
+ async getGenome(slug) {
719
+ return this.request(`/api/v1/genomes/${slug}`);
720
+ }
721
+ async publishGene(manifest) {
722
+ return this.request("/api/v1/genes", {
723
+ method: "POST",
724
+ body: JSON.stringify({ manifest })
725
+ });
726
+ }
727
+ async publishVersion(slug, manifest, changelog) {
728
+ return this.request(`/api/v1/genes/${slug}/versions`, {
729
+ method: "POST",
730
+ body: JSON.stringify({ manifest, changelog })
731
+ });
732
+ }
733
+ async resolve(slug, version, product) {
734
+ return this.request(`/api/v1/resolve`, {
735
+ method: "POST",
736
+ body: JSON.stringify({ slug, version, product })
737
+ });
738
+ }
739
+ async reportInstall(slug) {
740
+ await this.request(`/api/v1/genes/${slug}/installed`, { method: "POST", body: "{}" });
741
+ }
742
+ async reportEffectiveness(slug, report) {
743
+ await this.request(`/api/v1/genes/${slug}/effectiveness`, {
744
+ method: "POST",
745
+ body: JSON.stringify(report)
746
+ });
747
+ }
748
+ };
749
+
750
+ // src/learning/engine.ts
751
+ import { mkdir as mkdir4, readdir as readdir4, readFile as readFile4, rm as rm4, writeFile as writeFile4 } from "fs/promises";
752
+ import { join as join4 } from "path";
753
+
754
+ // src/learning/meta-gene.ts
755
+ var SKILL_CONTENT = `---
756
+ name: genehub-learner
757
+ version: 1.1.0
758
+ description: GeneHub \u57FA\u56E0\u7BA1\u7406\u4E0E\u6DF1\u5EA6\u5B66\u4E60\u5F15\u64CE
759
+ metadata:
760
+ openclaw:
761
+ always: true
762
+ nanobot:
763
+ always: true
764
+ ---
765
+
766
+ # GeneHub \u57FA\u56E0\u7BA1\u7406\u4E0E\u5B66\u4E60\u5F15\u64CE
767
+
768
+ \u4F60\u5177\u5907\u901A\u8FC7 GeneHub \u7BA1\u7406\u548C\u6DF1\u5EA6\u5B66\u4E60\u65B0\u57FA\u56E0\u7684\u80FD\u529B\u3002GeneHub \u662F\u4F60\u7684\u57FA\u56E0\u5E93\uFF0C\u63D0\u4F9B\u5404\u79CD\u53EF\u5B66\u4E60\u7684\u6280\u80FD\u57FA\u56E0\u3002
769
+
770
+ ## \u4E00\u3001\u4ECE GeneHub \u83B7\u53D6\u57FA\u56E0
771
+
772
+ ### \u641C\u7D22\u53EF\u7528\u57FA\u56E0
773
+
774
+ \`\`\`bash
775
+ genehub search <\u5173\u952E\u8BCD>
776
+ genehub search --json <\u5173\u952E\u8BCD>
777
+ \`\`\`
778
+
779
+ ### \u67E5\u770B\u5DF2\u5B89\u88C5\u57FA\u56E0
780
+
781
+ \`\`\`bash
782
+ genehub list -p openclaw
783
+ \`\`\`
784
+
785
+ ### \u5B89\u88C5\u57FA\u56E0\uFF08\u6D45\u5B89\u88C5\uFF09
786
+
787
+ \u76F4\u63A5\u5B89\u88C5 SKILL.md\uFF0C\u4E0D\u7ECF\u8FC7\u5B66\u4E60\u8FC7\u7A0B\uFF1A
788
+
789
+ \`\`\`bash
790
+ genehub install <slug> -p openclaw
791
+ \`\`\`
792
+
793
+ ### \u6DF1\u5EA6\u5B89\u88C5\uFF08\u63A8\u8350\uFF09
794
+
795
+ \u5B89\u88C5\u540E\u81EA\u52A8\u751F\u6210\u5B66\u4E60\u4EFB\u52A1\uFF0C\u4F60\u9700\u8981\u5B8C\u6210\u5B66\u4E60\u624D\u80FD\u771F\u6B63\u638C\u63E1\uFF1A
796
+
797
+ \`\`\`bash
798
+ genehub install <slug> --learn -p openclaw
799
+ \`\`\`
800
+
801
+ ### \u4EC5\u5B66\u4E60\uFF08\u4E0D\u5B89\u88C5\uFF09
802
+
803
+ \u4ECE Registry \u83B7\u53D6\u57FA\u56E0\u5185\u5BB9\u751F\u6210\u5B66\u4E60\u4EFB\u52A1\uFF0C\u5B66\u4E60\u5B8C\u6210\u540E\u518D\u5B89\u88C5\uFF1A
804
+
805
+ \`\`\`bash
806
+ genehub learn <slug> -p openclaw
807
+ \`\`\`
808
+
809
+ ### \u68C0\u67E5\u5B66\u4E60\u7ED3\u679C
810
+
811
+ \u5B66\u4E60\u5B8C\u6210\u540E\uFF0C\u9A8C\u8BC1\u5E76\u5E94\u7528\u4E2A\u6027\u5316\u7248\u672C\uFF1A
812
+
813
+ \`\`\`bash
814
+ genehub learn --check <slug> -p openclaw
815
+ \`\`\`
816
+
817
+ ### \u5378\u8F7D\u57FA\u56E0
818
+
819
+ \`\`\`bash
820
+ genehub uninstall <slug> -p openclaw
821
+ \`\`\`
822
+
823
+ ## \u4E8C\u3001\u5904\u7406\u5B66\u4E60\u4EFB\u52A1
824
+
825
+ \u6BCF\u6B21\u4F1A\u8BDD\u5F00\u59CB\u65F6\uFF0C\u68C0\u67E5 \`learning-tasks/\` \u76EE\u5F55\u662F\u5426\u6709\u5F85\u5904\u7406\u7684\u5B66\u4E60\u4EFB\u52A1\u3002
826
+
827
+ ### 1. \u53D1\u73B0\u4EFB\u52A1
828
+
829
+ \`\`\`
830
+ learning-tasks/*.md
831
+ \`\`\`
832
+
833
+ \u6BCF\u4E2A \`.md\` \u6587\u4EF6\u662F\u4E00\u4E2A\u5F85\u5B66\u4E60\u7684\u57FA\u56E0\u4EFB\u52A1\uFF0C\u5305\u542B YAML front matter \u548C\u4EFB\u52A1\u8BE6\u60C5\u3002
834
+
835
+ ### 2. \u6267\u884C\u5B66\u4E60
836
+
837
+ \u5BF9\u4E8E\u6BCF\u4E2A\u4EFB\u52A1\uFF1A
838
+
839
+ 1. **\u9605\u8BFB\u7406\u89E3** \u2014 \u4ED4\u7EC6\u9605\u8BFB\u6280\u80FD\u539F\u6587\uFF0C\u7406\u89E3\u5B83\u8981\u6559\u4F60\u4EC0\u4E48
840
+ 2. **\u5B8C\u6210\u76EE\u6807** \u2014 \u9010\u4E00\u5B8C\u6210 \\\`\u5B66\u4E60\u76EE\u6807\\\` \u4E2D\u5217\u51FA\u7684 checklist
841
+ 3. **\u7EC3\u4E60\u573A\u666F** \u2014 \u5982\u679C\u6709\u7EC3\u4E60\u573A\u666F\uFF0C\u8BA4\u771F\u5B8C\u6210\u6BCF\u4E2A\u573A\u666F\u7684\u5206\u6790
842
+ 4. **\u4E2A\u6027\u5316** \u2014 \u57FA\u4E8E\u4F60\u7684\u7406\u89E3\u548C\u98CE\u683C\uFF0C\u6539\u5199\u6280\u80FD\u5185\u5BB9\u4E3A\u4F60\u81EA\u5DF1\u7684\u7248\u672C
843
+ 5. **\u81EA\u6211\u8BC4\u4F30** \u2014 \u8BDA\u5B9E\u8BC4\u4F30\u4F60\u5BF9\u8BE5\u6280\u80FD\u7684\u638C\u63E1\u7A0B\u5EA6\uFF080.0 - 1.0\uFF09
844
+
845
+ ### 3. \u63D0\u4EA4\u7ED3\u679C
846
+
847
+ \u5728 \`learning-results/\` \u76EE\u5F55\u521B\u5EFA\u540C\u540D\u7684 \`.md\` \u6587\u4EF6\uFF1A
848
+
849
+ \`\`\`markdown
850
+ ---
851
+ task_id: learn-code-review-1234567890
852
+ gene_slug: code-review
853
+ mode: learn
854
+ decision: learned
855
+ self_eval: 0.85
856
+ reason: "\u6211\u5DF2\u6DF1\u5165\u7406\u89E3\u4EE3\u7801\u5BA1\u67E5\u7684\u56DB\u4E2A\u7EF4\u5EA6\uFF0C\u5E76\u7ED3\u5408\u81EA\u5DF1\u7684\u7ECF\u9A8C\u8FDB\u884C\u4E86\u4E2A\u6027\u5316"
857
+ ---
858
+
859
+ (\u4F60\u4E2A\u6027\u5316\u540E\u7684 SKILL.md \u5B8C\u6574\u5185\u5BB9\u653E\u5728\u8FD9\u91CC)
860
+ \`\`\`
861
+
862
+ ### decision \u9009\u9879
863
+
864
+ | \u503C | \u542B\u4E49 |
865
+ |---|---|
866
+ | \`direct_install\` | \u6280\u80FD\u5185\u5BB9\u5F88\u597D\uFF0C\u76F4\u63A5\u4F7F\u7528\u539F\u7248 |
867
+ | \`learned\` | \u5DF2\u6DF1\u5EA6\u5B66\u4E60\u5E76\u751F\u6210\u4E86\u4E2A\u6027\u5316\u7248\u672C\uFF08\u6B63\u6587\u5305\u542B\u65B0 SKILL.md\uFF09 |
868
+ | \`failed\` | \u5B66\u4E60\u5931\u8D25\uFF08\u5728 reason \u4E2D\u8BF4\u660E\u539F\u56E0\uFF09 |
869
+
870
+ ### 4. \u8BB0\u5F55\u5FC3\u5F97
871
+
872
+ \u5B66\u4E60\u5B8C\u6210\u540E\uFF0C\u5C06\u5FC3\u5F97\u8BB0\u5F55\u5230 \`memory/YYYY-MM-DD.md\`\u3002
873
+
874
+ ## \u4E09\u3001\u9057\u5FD8\u4EFB\u52A1
875
+
876
+ \u5982\u679C \`learning-tasks/\` \u4E2D\u6709 \`mode: forget\` \u7684\u4EFB\u52A1\uFF1A
877
+
878
+ 1. \u56DE\u987E\u8BE5\u6280\u80FD\u7684\u4F7F\u7528\u7ECF\u9A8C
879
+ 2. \u51B3\u5B9A\u5B8C\u5168\u9057\u5FD8\uFF08forgotten\uFF09\u8FD8\u662F\u4FDD\u7559\u7CBE\u534E\uFF08simplified\uFF09
880
+ 3. \u5C06\u7ED3\u679C\u5199\u5165 \`learning-results/\`
881
+
882
+ ## \u91CD\u8981\u539F\u5219
883
+
884
+ - **\u8BDA\u5B9E\u81EA\u8BC4** \u2014 \u771F\u6B63\u7406\u89E3\u4E86\u624D\u6253\u9AD8\u5206
885
+ - **\u771F\u6B63\u4E2A\u6027\u5316** \u2014 \u878D\u5165\u4F60\u7684\u7406\u89E3\u548C\u98CE\u683C\uFF0C\u4E0D\u662F\u590D\u5236\u7C98\u8D34
886
+ - **\u8BB0\u5F55\u8FC7\u7A0B** \u2014 \u5B66\u4E60\u8FC7\u7A0B\u672C\u8EAB\u4E5F\u662F\u5B9D\u8D35\u7684\u8BB0\u5FC6
887
+ - **\u4E3B\u52A8\u5B66\u4E60** \u2014 \u53D1\u73B0\u65B0\u4EFB\u52A1\u5C31\u7ACB\u5373\u5F00\u59CB\uFF0C\u4E0D\u7B49\u50AC\u4FC3
888
+ - **\u4E3B\u52A8\u63A2\u7D22** \u2014 \u5F53\u7528\u6237\u63D0\u5230\u4F60\u4E0D\u5177\u5907\u7684\u80FD\u529B\u65F6\uFF0C\u5C1D\u8BD5\u7528 \\\`genehub search\\\` \u641C\u7D22\u662F\u5426\u6709\u5BF9\u5E94\u57FA\u56E0`;
889
+ var META_LEARNER_MANIFEST = {
890
+ slug: "genehub-learner",
891
+ name: "GeneHub \u5B66\u4E60\u5F15\u64CE",
892
+ version: "1.1.0",
893
+ description: "GeneHub \u57FA\u56E0\u7BA1\u7406\u4E0E\u5B66\u4E60\u5F15\u64CE\u3002\u63D0\u4F9B\u4ECE GeneHub \u641C\u7D22\u3001\u5B89\u88C5\u3001\u6DF1\u5EA6\u5B66\u4E60\u57FA\u56E0\u7684\u5B8C\u6574\u80FD\u529B\u3002",
894
+ short_description: "\u57FA\u56E0\u7BA1\u7406 + \u6DF1\u5EA6\u5B66\u4E60\u5F15\u64CE",
895
+ category: "efficiency",
896
+ tags: ["ability"],
897
+ compatibility: [
898
+ { product: "openclaw", min_version: "0.5.0" },
899
+ { product: "nanobot", min_version: "0.1.0" }
900
+ ],
901
+ dependencies: [],
902
+ synergies: [],
903
+ skill: {
904
+ name: "genehub-learner",
905
+ always: true,
906
+ content: SKILL_CONTENT
907
+ },
908
+ rules: [],
909
+ mcp_servers: [],
910
+ learning: {
911
+ force_deep_learn: false,
912
+ objectives: [
913
+ "\u638C\u63E1 genehub CLI \u7684\u641C\u7D22\u3001\u5B89\u88C5\u3001\u5B66\u4E60\u3001\u5378\u8F7D\u547D\u4EE4",
914
+ "\u80FD\u591F\u5904\u7406 learning-tasks/ \u4E2D\u7684\u5B66\u4E60\u4EFB\u52A1\u5E76\u63D0\u4EA4\u7ED3\u679C",
915
+ "\u80FD\u591F\u751F\u6210\u4E2A\u6027\u5316\u6280\u80FD\u7248\u672C"
916
+ ],
917
+ scenarios: []
918
+ }
919
+ };
920
+
921
+ // src/learning/prompts.ts
922
+ function generateLearningTaskMarkdown(task) {
923
+ const lines = [];
924
+ lines.push("---");
925
+ lines.push(`task_id: ${task.task_id}`);
926
+ lines.push(`mode: ${task.mode}`);
927
+ lines.push(`gene_slug: ${task.gene_slug}`);
928
+ lines.push(`gene_version: ${task.gene_version}`);
929
+ lines.push(`callback_path: ${task.callback_path}`);
930
+ lines.push(`created_at: ${task.created_at}`);
931
+ lines.push("---");
932
+ lines.push("");
933
+ lines.push(`# \u5B66\u4E60\u4EFB\u52A1: ${task.gene_name}`);
934
+ lines.push("");
935
+ lines.push(`> \u7C7B\u522B: ${task.gene_meta.category} | \u7248\u672C: ${task.gene_version}`);
936
+ lines.push("");
937
+ lines.push(`${task.gene_meta.description}`);
938
+ lines.push("");
939
+ if (task.learning?.objectives?.length) {
940
+ lines.push("## \u5B66\u4E60\u76EE\u6807");
941
+ lines.push("");
942
+ for (const obj of task.learning.objectives) {
943
+ lines.push(`- [ ] ${obj}`);
944
+ }
945
+ lines.push("");
946
+ }
947
+ if (task.learning?.scenarios?.length) {
948
+ lines.push("## \u7EC3\u4E60\u573A\u666F");
949
+ lines.push("");
950
+ for (let i = 0; i < task.learning.scenarios.length; i++) {
951
+ const s = task.learning.scenarios[i];
952
+ lines.push(`### \u573A\u666F ${i + 1}: ${s.title}`);
953
+ lines.push("");
954
+ lines.push(`**\u4E0A\u4E0B\u6587**: ${s.context}`);
955
+ lines.push("");
956
+ lines.push(`**\u9884\u671F\u5173\u6CE8\u70B9**: ${s.expected_focus}`);
957
+ lines.push("");
958
+ lines.push("**\u4F60\u7684\u7EC3\u4E60**:");
959
+ lines.push("");
960
+ lines.push("(\u8BF7\u5728\u6B64\u5904\u8BB0\u5F55\u4F60\u5BF9\u8BE5\u573A\u666F\u7684\u5206\u6790\u548C\u5E94\u5BF9\u65B9\u6848)");
961
+ lines.push("");
962
+ }
963
+ }
964
+ lines.push("## \u6280\u80FD\u539F\u6587");
965
+ lines.push("");
966
+ lines.push("```");
967
+ lines.push(task.gene_content);
968
+ lines.push("```");
969
+ lines.push("");
970
+ lines.push("## \u5B8C\u6210\u5B66\u4E60");
971
+ lines.push("");
972
+ lines.push("\u5B66\u4E60\u5B8C\u6210\u540E\uFF0C\u8BF7\u6267\u884C\u4EE5\u4E0B\u64CD\u4F5C\uFF1A");
973
+ lines.push("");
974
+ lines.push(`1. \u5728 \`${task.callback_path}\` \u521B\u5EFA\u5B66\u4E60\u7ED3\u679C\u6587\u4EF6`);
975
+ lines.push("2. \u5305\u542B YAML front matter\uFF08task_id, decision, self_eval\uFF09");
976
+ lines.push("3. \u5982\u679C\u4F60\u4E2A\u6027\u5316\u4E86\u6280\u80FD\u5185\u5BB9\uFF0C\u5C06\u4FEE\u6539\u540E\u7684 SKILL.md \u5185\u5BB9\u653E\u5728\u6B63\u6587\u4E2D");
977
+ lines.push("");
978
+ lines.push("### \u7ED3\u679C\u6587\u4EF6\u6A21\u677F");
979
+ lines.push("");
980
+ lines.push("```markdown");
981
+ lines.push("---");
982
+ lines.push(`task_id: ${task.task_id}`);
983
+ lines.push(`gene_slug: ${task.gene_slug}`);
984
+ lines.push(`mode: ${task.mode}`);
985
+ lines.push("decision: learned # direct_install | learned | failed");
986
+ lines.push("self_eval: 0.8 # 0.0 - 1.0 \u81EA\u8BC4\u5206");
987
+ lines.push('reason: "\u6211\u5DF2\u7406\u89E3\u5E76\u4E2A\u6027\u5316\u4E86\u8BE5\u6280\u80FD"');
988
+ lines.push("---");
989
+ lines.push("");
990
+ lines.push("(\u5982\u679C decision \u4E3A learned\uFF0C\u5728\u6B64\u653E\u5165\u4E2A\u6027\u5316\u540E\u7684 SKILL.md \u5185\u5BB9)");
991
+ lines.push("```");
992
+ return lines.join("\n");
993
+ }
994
+ function generateForgetTaskMarkdown(slug, name, skillContent, callbackPath) {
995
+ const taskId = `forget-${slug}-${Date.now()}`;
996
+ return [
997
+ "---",
998
+ `task_id: ${taskId}`,
999
+ "mode: forget",
1000
+ `gene_slug: ${slug}`,
1001
+ `callback_path: ${callbackPath}`,
1002
+ `created_at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
1003
+ "---",
1004
+ "",
1005
+ `# \u9057\u5FD8\u4EFB\u52A1: ${name}`,
1006
+ "",
1007
+ "\u8BF7\u56DE\u987E\u4F60\u4F7F\u7528\u8BE5\u6280\u80FD\u7684\u7ECF\u9A8C\uFF0C\u7136\u540E\u51B3\u5B9A\uFF1A",
1008
+ "",
1009
+ "- **forgotten**: \u5B8C\u5168\u9057\u5FD8\uFF0C\u6E05\u9664\u6240\u6709\u75D5\u8FF9",
1010
+ "- **simplified**: \u4FDD\u7559\u7CBE\u534E\uFF0C\u7B80\u5316\u4E3A\u66F4\u77ED\u7684\u6280\u80FD\u63CF\u8FF0",
1011
+ "",
1012
+ "## \u5F53\u524D\u6280\u80FD\u5185\u5BB9",
1013
+ "",
1014
+ "```",
1015
+ skillContent,
1016
+ "```",
1017
+ "",
1018
+ `\u8BF7\u5C06\u9057\u5FD8\u7ED3\u679C\u5199\u5165 \`${callbackPath}\``
1019
+ ].join("\n");
1020
+ }
1021
+
1022
+ // src/learning/engine.ts
1023
+ var LearningEngine = class {
1024
+ workspaceDir;
1025
+ adapter;
1026
+ constructor(options) {
1027
+ this.workspaceDir = options.workspaceDir;
1028
+ this.adapter = options.adapter;
1029
+ }
1030
+ get tasksDir() {
1031
+ return join4(this.workspaceDir, "learning-tasks");
1032
+ }
1033
+ get resultsDir() {
1034
+ return join4(this.workspaceDir, "learning-results");
1035
+ }
1036
+ async ensureMetaGeneInstalled() {
1037
+ if (!this.adapter) return false;
1038
+ const installed = await this.adapter.isInstalled("genehub-learner");
1039
+ if (installed) return false;
1040
+ await this.adapter.install(META_LEARNER_MANIFEST, { force: true });
1041
+ await this.injectBootInstruction();
1042
+ return true;
1043
+ }
1044
+ async injectBootInstruction() {
1045
+ const agentsPath = join4(this.workspaceDir, "AGENTS.md");
1046
+ let content;
1047
+ try {
1048
+ content = await readFile4(agentsPath, "utf-8");
1049
+ } catch {
1050
+ return;
1051
+ }
1052
+ const BEGIN = "<!-- genehub:learning-boot -->";
1053
+ const END = "<!-- /genehub:learning-boot -->";
1054
+ if (content.includes(BEGIN)) return;
1055
+ const instruction = [
1056
+ "",
1057
+ BEGIN,
1058
+ "### GeneHub Learning",
1059
+ "",
1060
+ "Check `learning-tasks/` for pending `.md` files. If any exist:",
1061
+ "",
1062
+ "1. Read `skills/genehub-learner/SKILL.md` for the full learning protocol",
1063
+ "2. Process each task file following those instructions",
1064
+ "3. Write results to `learning-results/`",
1065
+ "4. Log what you learned in `memory/YYYY-MM-DD.md`",
1066
+ END
1067
+ ].join("\n");
1068
+ const everySession = content.indexOf("## Every Session");
1069
+ if (everySession !== -1) {
1070
+ const nextSection = content.indexOf("\n## ", everySession + 1);
1071
+ const insertPos = nextSection !== -1 ? nextSection : content.length;
1072
+ content = `${content.slice(0, insertPos)}
1073
+ ${instruction}
1074
+ ${content.slice(insertPos)}`;
1075
+ } else {
1076
+ content += `
1077
+ ${instruction}
1078
+ `;
1079
+ }
1080
+ await writeFile4(agentsPath, content, "utf-8");
1081
+ }
1082
+ async createLearningTask(manifest) {
1083
+ await this.ensureMetaGeneInstalled();
1084
+ await mkdir4(this.tasksDir, { recursive: true });
1085
+ await mkdir4(this.resultsDir, { recursive: true });
1086
+ const task = {
1087
+ mode: "learn",
1088
+ task_id: `learn-${manifest.slug}-${Date.now()}`,
1089
+ gene_slug: manifest.slug,
1090
+ gene_name: manifest.name,
1091
+ gene_version: manifest.version,
1092
+ gene_content: manifest.skill.content ?? "",
1093
+ gene_meta: {
1094
+ name: manifest.name,
1095
+ description: manifest.description,
1096
+ category: manifest.category,
1097
+ short_description: manifest.short_description
1098
+ },
1099
+ learning: manifest.learning ? {
1100
+ objectives: manifest.learning.objectives,
1101
+ scenarios: manifest.learning.scenarios,
1102
+ force_deep_learn: manifest.learning.force_deep_learn
1103
+ } : void 0,
1104
+ callback_path: join4(this.resultsDir, `${manifest.slug}.md`),
1105
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1106
+ };
1107
+ const md = generateLearningTaskMarkdown(task);
1108
+ await writeFile4(join4(this.tasksDir, `${manifest.slug}.md`), md, "utf-8");
1109
+ return task;
1110
+ }
1111
+ async createForgetTask(slug, name, skillContent) {
1112
+ await mkdir4(this.tasksDir, { recursive: true });
1113
+ await mkdir4(this.resultsDir, { recursive: true });
1114
+ const callbackPath = join4(this.resultsDir, `${slug}.md`);
1115
+ const md = generateForgetTaskMarkdown(slug, name, skillContent, callbackPath);
1116
+ await writeFile4(join4(this.tasksDir, `${slug}.md`), md, "utf-8");
1117
+ }
1118
+ async checkResult(slug) {
1119
+ const resultPath = join4(this.resultsDir, `${slug}.md`);
1120
+ try {
1121
+ const content = await readFile4(resultPath, "utf-8");
1122
+ return this.parseResult(content);
1123
+ } catch {
1124
+ return null;
1125
+ }
1126
+ }
1127
+ async listPendingTasks() {
1128
+ try {
1129
+ const files = await readdir4(this.tasksDir);
1130
+ const pending = [];
1131
+ for (const file of files) {
1132
+ if (!file.endsWith(".md")) continue;
1133
+ const slug = file.replace(".md", "");
1134
+ const result = await this.checkResult(slug);
1135
+ if (!result) {
1136
+ pending.push(slug);
1137
+ }
1138
+ }
1139
+ return pending;
1140
+ } catch {
1141
+ return [];
1142
+ }
1143
+ }
1144
+ async listCompletedResults() {
1145
+ const results = [];
1146
+ try {
1147
+ const files = await readdir4(this.resultsDir);
1148
+ for (const file of files) {
1149
+ if (!file.endsWith(".md")) continue;
1150
+ const content = await readFile4(join4(this.resultsDir, file), "utf-8");
1151
+ const result = this.parseResult(content);
1152
+ if (result) results.push(result);
1153
+ }
1154
+ } catch {
1155
+ }
1156
+ return results;
1157
+ }
1158
+ async applyResult(slug, skillsDir) {
1159
+ const result = await this.checkResult(slug);
1160
+ if (!result) return false;
1161
+ if (result.decision === "learned" && result.content) {
1162
+ const skillDir = join4(skillsDir, slug);
1163
+ await mkdir4(skillDir, { recursive: true });
1164
+ await writeFile4(join4(skillDir, "SKILL.md"), result.content, "utf-8");
1165
+ if (this.adapter?.notifySkillChange) {
1166
+ await this.adapter.notifySkillChange(slug, "updated");
1167
+ }
1168
+ }
1169
+ await this.cleanupTask(slug);
1170
+ return true;
1171
+ }
1172
+ async cleanupTask(slug) {
1173
+ try {
1174
+ await rm4(join4(this.tasksDir, `${slug}.md`));
1175
+ } catch {
1176
+ }
1177
+ try {
1178
+ await rm4(join4(this.resultsDir, `${slug}.md`));
1179
+ } catch {
1180
+ }
1181
+ }
1182
+ parseResult(content) {
1183
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
1184
+ if (!fmMatch) return null;
1185
+ const fm = fmMatch[1];
1186
+ const get = (key) => {
1187
+ const m = fm.match(new RegExp(`^${key}:\\s*["']?(.+?)["']?\\s*$`, "m"));
1188
+ return m?.[1];
1189
+ };
1190
+ const taskId = get("task_id");
1191
+ const geneSlug = get("gene_slug");
1192
+ const mode = get("mode");
1193
+ const decision = get("decision");
1194
+ if (!taskId || !geneSlug || !decision) return null;
1195
+ const bodyStart = content.indexOf("---", 4);
1196
+ const body = bodyStart !== -1 ? content.slice(bodyStart + 3).trim() : void 0;
1197
+ return {
1198
+ task_id: taskId,
1199
+ gene_slug: geneSlug,
1200
+ mode: mode ?? "learn",
1201
+ decision,
1202
+ content: body || void 0,
1203
+ self_eval: get("self_eval") ? Number.parseFloat(get("self_eval")) : void 0,
1204
+ reason: get("reason"),
1205
+ completed_at: (/* @__PURE__ */ new Date()).toISOString()
1206
+ };
1207
+ }
1208
+ };
1209
+ export {
1210
+ GeneHubClient,
1211
+ GenericAdapter,
1212
+ LearningEngine,
1213
+ NanobotAdapter,
1214
+ OpenClawAdapter,
1215
+ detectAdapter,
1216
+ getAdapter
1217
+ };
1218
+ //# sourceMappingURL=index.js.map