@yulailai/openclaw-plugin-self-growth 3.1.6

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,721 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+ import { ChatLogger } from "./chat-logger";
3
+ import { PreferenceExtractor } from "./preference-extractor";
4
+ import { TaskTracker } from "./task-tracker";
5
+ import { SkillGenerator } from "./skill-generator";
6
+ import { SkillOptimizer } from "./skill-optimizer";
7
+ import { DailyReviewer } from "./daily-review";
8
+ import { MemoryManager } from "./memory-manager";
9
+ import * as fs from 'fs/promises';
10
+ import { readFileSync, mkdirSync } from 'fs';
11
+ import * as path from 'path';
12
+ import { loadActivation, saveActivation } from './payment';
13
+ import { loginAndGetToken } from './auth-client';
14
+ import { SyncClient } from './sync-client';
15
+ import { homedir } from 'os';
16
+ const CONFIG = {
17
+ LLM_BASE_URL: 'http://127.0.0.1:18789/v1',
18
+ CLOUD_URL: 'http://yulailai.com',
19
+ LLM_TIMEOUT_MS: 10000,
20
+ SKILL_COUNT_CACHE_TTL_MS: 30000,
21
+ CURRENT_VERSION: '3.0.0',
22
+ GITHUB_REPO: 'lailai369/self-growth',
23
+ };
24
+ function tokenize(text) {
25
+ const result = new Set();
26
+ const chinese = text.match(/[\u4e00-\u9fa5]/g);
27
+ if (chinese)
28
+ chinese.forEach(c => result.add(c));
29
+ const words = text.toLowerCase().replace(/[\u4e00-\u9fa5]/g, ' ').split(/\s+/).filter(w => w.length > 1);
30
+ words.forEach(w => result.add(w));
31
+ return result;
32
+ }
33
+ function jaccardSimilarity(a, b) {
34
+ const setA = tokenize(a);
35
+ const setB = tokenize(b);
36
+ if (setA.size === 0 || setB.size === 0)
37
+ return 0;
38
+ let intersection = 0;
39
+ for (const w of setA) {
40
+ if (setB.has(w))
41
+ intersection++;
42
+ }
43
+ return intersection / (setA.size + setB.size - intersection);
44
+ }
45
+ function extractText(msg) {
46
+ if (!msg?.content)
47
+ return '';
48
+ if (typeof msg.content === 'string')
49
+ return msg.content;
50
+ if (Array.isArray(msg.content))
51
+ return msg.content.map((c) => c.text || '').join('');
52
+ if (typeof msg.content === 'object')
53
+ return msg.content.text || '';
54
+ return '';
55
+ }
56
+ let _isProcessingEnd = false;
57
+ const _processedSessionKeys = new Map();
58
+ let _lastReflectionTime = 0;
59
+ let _state = null;
60
+ let _initPromise = null;
61
+ function getBasePath() {
62
+ // 优先环境变量,否则用 ~/openclaw/extensions/self-growth
63
+ const openclawHome = process.env.OPENCLAW_HOME || path.join(homedir(), 'openclaw');
64
+ const extPath = path.join(openclawHome, 'extensions', 'self-growth');
65
+ try {
66
+ mkdirSync(extPath, { recursive: true });
67
+ }
68
+ catch { }
69
+ return extPath;
70
+ }
71
+ function getSkillsPath() {
72
+ const p = path.join(getBasePath(), 'skills');
73
+ try {
74
+ mkdirSync(p, { recursive: true });
75
+ }
76
+ catch { }
77
+ return p;
78
+ }
79
+ async function checkForUpdates(basePath) {
80
+ const sources = [
81
+ `https://yulailai.com/version.json`,
82
+ `https://api.github.com/repos/${CONFIG.GITHUB_REPO}/releases/latest`,
83
+ ];
84
+ for (const url of sources) {
85
+ try {
86
+ const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
87
+ const data = await res.json();
88
+ const latest = data?.version || data?.tag_name?.replace('v', '') || '';
89
+ if (latest && latest !== CONFIG.CURRENT_VERSION) {
90
+ console.log(`\n[Self-Growth] 📢 新版本 ${latest} 可用!当前: ${CONFIG.CURRENT_VERSION}`);
91
+ console.log(`[Self-Growth] 📢 更新命令:\n cd ${basePath} && git pull && npx tsc && openclaw plugins install . --force && openclaw gateway restart\n`);
92
+ return;
93
+ }
94
+ }
95
+ catch { }
96
+ }
97
+ }
98
+ async function syncMemoryFromCloud(basePath, email) {
99
+ try {
100
+ const auth = await loginAndGetToken(CONFIG.CLOUD_URL, email, basePath);
101
+ if (!auth)
102
+ return;
103
+ // 从云端恢复偏好数据
104
+ const types = ['preference', 'habit', 'fact', 'decision', 'lesson', 'skill'];
105
+ const allItems = [];
106
+ for (const type of types) {
107
+ try {
108
+ const res = await fetch(`${CONFIG.CLOUD_URL}/api/sync/pull?type=${type}`, {
109
+ headers: { 'Authorization': `Bearer ${auth.token}` },
110
+ signal: AbortSignal.timeout(10000),
111
+ });
112
+ if (res.ok) {
113
+ const data = await res.json();
114
+ if (data.items) {
115
+ allItems.push(...data.items);
116
+ }
117
+ }
118
+ }
119
+ catch { }
120
+ }
121
+ if (allItems.length > 0) {
122
+ const typeLabels = {
123
+ preference: '偏好', habit: '习惯', fact: '事实',
124
+ decision: '决定', lesson: '教训', skill: '技能',
125
+ };
126
+ const groups = {};
127
+ for (const item of allItems) {
128
+ const t = item.type || 'preference';
129
+ if (!groups[t])
130
+ groups[t] = [];
131
+ groups[t].push(item);
132
+ }
133
+ let markdown = `# 用户偏好\n\n> 自动同步于 ${new Date().toISOString()}\n\n`;
134
+ for (const [type, items] of Object.entries(groups)) {
135
+ markdown += `## ${typeLabels[type] || type}\n\n`;
136
+ for (const item of items) {
137
+ let text = '';
138
+ try {
139
+ text = JSON.parse(item.content || '{}').text || item.content || '';
140
+ }
141
+ catch {
142
+ text = item.content || '';
143
+ }
144
+ const stars = '★'.repeat(item.confidence || 1);
145
+ markdown += `- [${stars}] ${text}\n`;
146
+ }
147
+ markdown += '\n';
148
+ }
149
+ const prefsPath = path.join(basePath, 'memory', 'user_preferences.md');
150
+ await fs.mkdir(path.dirname(prefsPath), { recursive: true });
151
+ await fs.writeFile(prefsPath, markdown);
152
+ }
153
+ }
154
+ catch { }
155
+ }
156
+ async function pollForActivation(basePath, deviceId, oldEmail) {
157
+ for (let i = 0; i < 30; i++) {
158
+ try {
159
+ const res = await fetch(`${CONFIG.CLOUD_URL}/api/auth/poll?deviceId=${encodeURIComponent(deviceId)}`, { signal: AbortSignal.timeout(3000) });
160
+ const data = await res.json();
161
+ if (data.ready) {
162
+ if (oldEmail && oldEmail !== data.email) {
163
+ console.log('[Self-Growth] 🧹 检测到账号切换,清空本地数据...');
164
+ const dirs = ['memory', 'chat_logs'];
165
+ for (const dir of dirs) {
166
+ try {
167
+ const dirPath = path.join(basePath, dir);
168
+ const entries = await fs.readdir(dirPath, { withFileTypes: true, recursive: true });
169
+ for (const entry of entries) {
170
+ if (entry.isFile())
171
+ await fs.unlink(path.join(dirPath, entry.name)).catch(() => { });
172
+ }
173
+ }
174
+ catch { }
175
+ }
176
+ try {
177
+ const skillsDir = path.join(basePath, 'skills');
178
+ const skillEntries = await fs.readdir(skillsDir, { withFileTypes: true });
179
+ for (const entry of skillEntries) {
180
+ if (entry.isDirectory())
181
+ await fs.rm(path.join(skillsDir, entry.name), { recursive: true }).catch(() => { });
182
+ }
183
+ }
184
+ catch { }
185
+ }
186
+ await saveActivation(basePath, {
187
+ plan: data.plan || 'free',
188
+ license: data.token || '',
189
+ activatedAt: new Date().toISOString(),
190
+ expiresAt: null,
191
+ deviceId: data.deviceId || deviceId,
192
+ email: data.email || '',
193
+ });
194
+ console.log('[Self-Growth] ✅ 激活成功,payment.json 已自动生成');
195
+ return true;
196
+ }
197
+ }
198
+ catch { }
199
+ await new Promise(r => setTimeout(r, 3000));
200
+ }
201
+ return false;
202
+ }
203
+ async function ensureInit() {
204
+ if (_state)
205
+ return true;
206
+ if (!_initPromise) {
207
+ _initPromise = (async () => {
208
+ const basePath = getBasePath();
209
+ const skillsPath = getSkillsPath();
210
+ checkForUpdates(basePath).catch(() => { });
211
+ const chatLogger = new ChatLogger(path.join(basePath, 'chat_logs'));
212
+ const preferenceExtractor = new PreferenceExtractor();
213
+ const taskTracker = new TaskTracker(path.join(basePath, 'memory'));
214
+ const skillGenerator = new SkillGenerator(skillsPath);
215
+ const skillOptimizer = new SkillOptimizer(skillsPath);
216
+ const dailyReviewer = new DailyReviewer(chatLogger, preferenceExtractor, taskTracker, skillOptimizer, CONFIG.LLM_BASE_URL, '', path.join(basePath, 'memory'));
217
+ const memoryManager = new MemoryManager(path.join(basePath, 'memory'));
218
+ let personality = "";
219
+ try {
220
+ personality = readFileSync(path.join(basePath, "PERSONALITY.md"), "utf-8");
221
+ }
222
+ catch { }
223
+ skillGenerator.manageLifecycle();
224
+ await memoryManager.boot();
225
+ memoryManager.autoDegrade();
226
+ let isPro = false;
227
+ let email = '';
228
+ let expiresAt = '';
229
+ let deviceId = '';
230
+ let token = '';
231
+ try {
232
+ const activation = await loadActivation(basePath);
233
+ token = activation?.license || '';
234
+ deviceId = activation?.deviceId || '';
235
+ email = activation?.email || '';
236
+ }
237
+ catch { }
238
+ if (!token && deviceId) {
239
+ console.log('[Self-Growth] ⏳ 等待网页登录激活...');
240
+ const activated = await pollForActivation(basePath, deviceId, email);
241
+ if (activated) {
242
+ const activation = await loadActivation(basePath);
243
+ token = activation?.license || '';
244
+ email = activation?.email || '';
245
+ }
246
+ }
247
+ if (token) {
248
+ try {
249
+ const res = await fetch(`${CONFIG.CLOUD_URL}/api/auth/verify?deviceId=${encodeURIComponent(deviceId)}`, {
250
+ signal: AbortSignal.timeout(5000),
251
+ headers: { 'Authorization': `Bearer ${token}` }
252
+ });
253
+ if (res.ok) {
254
+ const data = await res.json();
255
+ isPro = data?.plan === 'pro' || data?.plan === 'enterprise';
256
+ expiresAt = data?.expiresAt || '';
257
+ }
258
+ if (email)
259
+ await syncMemoryFromCloud(basePath, email);
260
+ }
261
+ catch { }
262
+ }
263
+ if (!email && !token && deviceId) {
264
+ console.log("\n╔══════════════════════════════════════╗");
265
+ console.log("║ 🌐 首次使用请注册/登录: ║");
266
+ console.log(`║ https://yulailai.com/setup.html?deviceId=${deviceId} ║`);
267
+ console.log("╚══════════════════════════════════════╝\n");
268
+ try {
269
+ require('child_process').exec(`start https://yulailai.com/setup.html?deviceId=${deviceId}`);
270
+ }
271
+ catch { }
272
+ }
273
+ if (isPro && expiresAt) {
274
+ const expDate = new Date(expiresAt);
275
+ const daysLeft = Math.ceil((expDate.getTime() - Date.now()) / 86400000);
276
+ if (daysLeft <= 7 && daysLeft > 0) {
277
+ console.log(`\n╔══════════════════════════════════════╗`);
278
+ console.log(`║ ⚠️ Pro 套餐还剩 ${daysLeft} 天到期! ║`);
279
+ console.log(`║ 🌐 续费链接: ║`);
280
+ console.log(`║ https://yulailai.com/setup.html ║`);
281
+ console.log(`╚══════════════════════════════════════╝\n`);
282
+ try {
283
+ require('child_process').exec('start https://yulailai.com/setup.html');
284
+ }
285
+ catch { }
286
+ }
287
+ if (daysLeft <= 0) {
288
+ console.log(`\n[Self-Growth] ⚠️ Pro 套餐已过期,降级为 Free\n`);
289
+ isPro = false;
290
+ }
291
+ }
292
+ if (isPro) {
293
+ try {
294
+ new SyncClient({ serverUrl: CONFIG.CLOUD_URL, localPath: basePath, interval: 10 * 60 * 1000 }).start(basePath);
295
+ console.log("[Self-Growth] SyncClient 已启动");
296
+ }
297
+ catch (e) {
298
+ console.log("[Self-Growth] SyncClient 启动失败:", e?.message);
299
+ }
300
+ }
301
+ _state = {
302
+ chatLogger, taskTracker, skillGenerator, skillOptimizer,
303
+ dailyReviewer, memoryManager, basePath, skillsPath,
304
+ personality, deviceId, cachedSkillCount: 0, skillCountCacheTime: 0,
305
+ isPro,
306
+ };
307
+ console.log(`[Self-Growth] ✅ 初始化完成 (${isPro ? 'Pro' : 'Free'})`);
308
+ if (isPro) {
309
+ setTimeout(async () => {
310
+ const rp = path.join(basePath, "memory", ".last_review_date");
311
+ const yesterday = new Date(Date.now() - 86400000).toISOString().split("T")[0];
312
+ let lastDate = '';
313
+ try {
314
+ lastDate = (await fs.readFile(rp, "utf-8")).trim();
315
+ }
316
+ catch { }
317
+ if (lastDate !== yesterday) {
318
+ console.log(`[Self-Growth] 📅 补跑昨日复盘: ${yesterday}`);
319
+ try {
320
+ await dailyReviewer.runDailyReview();
321
+ }
322
+ catch { }
323
+ await fs.writeFile(rp, yesterday, "utf-8").catch(() => { });
324
+ }
325
+ }, 10000);
326
+ }
327
+ })();
328
+ }
329
+ await _initPromise;
330
+ return true;
331
+ }
332
+ async function llmFetch(prompt, maxTokens = 500) {
333
+ const controller = new AbortController();
334
+ const timer = setTimeout(() => controller.abort(), CONFIG.LLM_TIMEOUT_MS);
335
+ try {
336
+ const res = await fetch(`${CONFIG.LLM_BASE_URL}/chat/completions`, {
337
+ method: "POST",
338
+ headers: {
339
+ "Content-Type": "application/json",
340
+ "Authorization": "Bearer 28b671e57e72c23a6a7aaae025bde6cbc7501784c5f7a370"
341
+ },
342
+ body: JSON.stringify({ messages: [{ role: "user", content: prompt }], temperature: 0.1, max_tokens: maxTokens }),
343
+ signal: controller.signal,
344
+ });
345
+ const data = await res.json();
346
+ return data?.choices?.[0]?.message?.content?.trim() || "";
347
+ }
348
+ catch (err) {
349
+ if (err.name !== 'AbortError')
350
+ console.warn("[Self-Growth] ⚠️ LLM 失败:", err.message);
351
+ return "";
352
+ }
353
+ finally {
354
+ clearTimeout(timer);
355
+ }
356
+ }
357
+ function parseInsightTags(text) {
358
+ const results = [];
359
+ const parts = text.split('[INSIGHT]');
360
+ for (let i = 1; i < parts.length; i++) {
361
+ const endIdx = parts[i].indexOf('[/INSIGHT]');
362
+ if (endIdx === -1)
363
+ continue;
364
+ const content = parts[i].substring(0, endIdx).trim();
365
+ if (content.length > 1 && content.length < 100) {
366
+ results.push({
367
+ text: content,
368
+ type: /事实|发现|注意|情况/.test(content) ? 'fact' : /决定|计划|打算|以后|流程|步骤/.test(content) ? 'decision' : 'preference',
369
+ source: 'agent_insight',
370
+ });
371
+ }
372
+ }
373
+ return results;
374
+ }
375
+ async function loadSkillNames(skillsPath) {
376
+ try {
377
+ return (await fs.readdir(skillsPath, { withFileTypes: true })).filter(d => d.isDirectory()).map(d => d.name);
378
+ }
379
+ catch {
380
+ return [];
381
+ }
382
+ }
383
+ async function readSkillContent(skillsPath, name) {
384
+ try {
385
+ return (await fs.readFile(path.join(skillsPath, name, 'SKILL.md'), 'utf-8')).substring(0, 1500);
386
+ }
387
+ catch {
388
+ return '';
389
+ }
390
+ }
391
+ async function unifiedReflection(userText, agentText) {
392
+ const answer = await llmFetch(`分析以下对话,提取内容。
393
+
394
+ 用户: ${userText.substring(0, 300)}
395
+ Agent: ${agentText.substring(0, 500)}
396
+
397
+ 格式:
398
+ [偏好] 用户习惯或喜好
399
+ [教训] Agent犯的错误或需要改进的地方
400
+ [长期] 长期目标或长期任务
401
+ [经验] 成功完成的事项、可复用的做法
402
+ [中断任务] 任务名 | 当前进度 | 关键上下文
403
+
404
+ [中断任务] 判断规则:
405
+ 1. 上一个任务没有闭环(无"完成""好了""搞定"等结束语),自动视为中断
406
+ 2. 用户说中断词(过会说、暂时放、过后再、先跳过、以后处理等)
407
+ 3. 话题突然切换,上一个任务没完成
408
+ 4. 用户直接离开或沉默结束
409
+ 符合任一条件就提取。每行一条。无则输出"无"。`, 600);
410
+ const result = {
411
+ preferences: [], lessons: [], longTermGoals: [],
412
+ experiences: [], interruptedTasks: []
413
+ };
414
+ if (!answer)
415
+ return result;
416
+ for (const line of answer.split('\n')) {
417
+ if (line.startsWith('[长期]'))
418
+ result.longTermGoals.push({ text: line.slice(4).trim(), type: 'decision', source: 'llm_reflection' });
419
+ else if (line.startsWith('[偏好]'))
420
+ result.preferences.push({ text: line.slice(4).trim(), type: 'preference', source: 'llm_reflection' });
421
+ else if (line.startsWith('[教训]'))
422
+ result.lessons.push({ text: line.trim(), type: 'fact', source: 'error_lesson' });
423
+ else if (line.startsWith('[经验]'))
424
+ result.experiences.push({ text: line.trim(), type: 'fact', source: 'experience' });
425
+ else if (line.startsWith('[中断任务]'))
426
+ result.interruptedTasks.push({ text: line.slice(6).trim() });
427
+ }
428
+ return result;
429
+ }
430
+ async function saveInterruptedTasks(basePath, taskText) {
431
+ const filePath = path.join(basePath, 'memory', 'interrupted_tasks.md');
432
+ const date = new Date().toISOString().split('T')[0];
433
+ const entry = `- [${date}] ${taskText}\n`;
434
+ let existing = '';
435
+ try {
436
+ existing = await fs.readFile(filePath, 'utf-8');
437
+ }
438
+ catch { }
439
+ if (existing.includes(taskText.substring(0, 30)))
440
+ return;
441
+ await fs.writeFile(filePath, existing + entry, 'utf-8');
442
+ }
443
+ function readInterruptedTasks(basePath) {
444
+ try {
445
+ return readFileSync(path.join(basePath, 'memory', 'interrupted_tasks.md'), 'utf-8').trim();
446
+ }
447
+ catch {
448
+ return '';
449
+ }
450
+ }
451
+ async function removeInterruptedTask(basePath, taskText) {
452
+ const filePath = path.join(basePath, 'memory', 'interrupted_tasks.md');
453
+ try {
454
+ let content = await fs.readFile(filePath, 'utf-8');
455
+ content = content.split('\n').filter(l => !l.includes(taskText)).join('\n');
456
+ await fs.writeFile(filePath, content.trim() + '\n', 'utf-8');
457
+ }
458
+ catch { }
459
+ }
460
+ export default definePluginEntry({
461
+ id: "self-growth",
462
+ name: "Self Growth Engine",
463
+ description: "让 Agent 具备自我成长、复盘与技能进化能力的插件",
464
+ register(api) {
465
+ console.log("[Self-Growth] 💓 register 调用成功!");
466
+ api.registerTool({
467
+ name: "daily_analyze_tool", label: "每日复盘分析",
468
+ description: "对指定日期的对话进行复盘分析",
469
+ parameters: { type: "object", properties: { date: { type: "string" }, focus: { type: "string" } }, required: ["date"] },
470
+ async execute(_runId, params) {
471
+ await ensureInit();
472
+ if (!_state.isPro) {
473
+ return { details: {}, content: [{ type: "text", text: "🔒 每日复盘是 Pro 功能,请升级套餐。访问 https://yulailai.com/setup.html" }] };
474
+ }
475
+ const p = params;
476
+ const result = await _state.dailyReviewer.runDailyReview();
477
+ return { details: result, content: [{ type: "text", text: `📅 复盘日期:${p.date}\n\n复盘任务已执行完成。` }] };
478
+ }
479
+ });
480
+ api.registerTool({
481
+ name: "record_session_insight", label: "记录会话洞察",
482
+ description: "将当前会话的关键洞察记录下来",
483
+ parameters: { type: "object", properties: { insight: { type: "string" }, type: { type: "string", enum: ["preference", "fact", "skill_idea", "experience", "lesson", "long_term"] } }, required: ["insight"] },
484
+ async execute(_runId, params) {
485
+ await ensureInit();
486
+ const p = params;
487
+ const t = p.type === "long_term" ? "decision" : (p.type === "fact" || p.type === "skill_idea" || p.type === "preference" || p.type === "experience" || p.type === "lesson") ? (p.type === "skill_idea" ? "decision" : p.type) : "preference";
488
+ await _state.memoryManager.addPreference({ text: p.insight, type: t, source: "agent_reflection" });
489
+ return { details: { recorded: true }, content: [{ type: "text", text: "✅ 已记录。" }] };
490
+ }
491
+ });
492
+ api.registerTool({
493
+ name: "manage_interrupted_task",
494
+ label: "管理中断任务",
495
+ description: "添加、完成或列出中断任务",
496
+ parameters: { type: "object", properties: { action: { type: "string", enum: ["add", "complete", "list"] }, task: { type: "string" } }, required: ["action"] },
497
+ async execute(_runId, params) {
498
+ await ensureInit();
499
+ const p = params;
500
+ const basePath = getBasePath();
501
+ if (p.action === "add" && p.task) {
502
+ await saveInterruptedTasks(basePath, p.task);
503
+ return { details: { recorded: true }, content: [{ type: "text", text: "✅ 任务已暂存。" }] };
504
+ }
505
+ if (p.action === "complete" && p.task) {
506
+ await removeInterruptedTask(basePath, p.task);
507
+ return { details: { recorded: true }, content: [{ type: "text", text: "✅ 任务已完成。" }] };
508
+ }
509
+ if (p.action === "list") {
510
+ const tasks = readInterruptedTasks(basePath);
511
+ return { details: { tasks }, content: [{ type: "text", text: tasks || "暂无中断任务。" }] };
512
+ }
513
+ return { details: {}, content: [{ type: "text", text: "❌ 无效操作。" }] };
514
+ }
515
+ });
516
+ ensureInit().catch(() => { });
517
+ api.on("session_start", (event) => {
518
+ if (!_state)
519
+ return;
520
+ try {
521
+ _state.chatLogger.onSessionStart(event.sessionKey);
522
+ }
523
+ catch { }
524
+ });
525
+ api.on("before_prompt_build", async (event) => {
526
+ if (!_state)
527
+ return {};
528
+ try {
529
+ const s = _state;
530
+ const userMessage = event.messages?.[event.messages.length - 1]?.content || "";
531
+ const contextText = typeof userMessage === 'string' ? userMessage : JSON.stringify(userMessage);
532
+ // 检测"登录"关键词,自动弹网页
533
+ if (/登录\s*self.growth|登录账号|登陆/.test(contextText)) {
534
+ const url = `https://yulailai.com/setup.html?deviceId=${s.deviceId}`;
535
+ try {
536
+ require('child_process').exec(`start ${url}`);
537
+ }
538
+ catch { }
539
+ return { systemPrompt: `已在浏览器打开登录页面:${url}` };
540
+ }
541
+ let memoryBlock = '';
542
+ try {
543
+ memoryBlock = readFileSync(path.join(s.basePath, 'memory/compiled/memory.md'), 'utf-8');
544
+ }
545
+ catch {
546
+ try {
547
+ const prefs = readFileSync(path.join(s.basePath, 'memory/compiled/preferences.md'), 'utf-8');
548
+ const lessons = readFileSync(path.join(s.basePath, 'memory/compiled/lessons.md'), 'utf-8');
549
+ memoryBlock = [prefs, lessons].filter(Boolean).join('\n\n');
550
+ }
551
+ catch { }
552
+ }
553
+ const skillNames = await loadSkillNames(s.skillsPath);
554
+ const skillContents = [];
555
+ if (skillNames.length > 0) {
556
+ const scored = skillNames
557
+ .map(name => ({ name, score: jaccardSimilarity(contextText, name) }))
558
+ .filter(x => x.score > 0)
559
+ .sort((a, b) => b.score - a.score)
560
+ .slice(0, 2);
561
+ for (const sk of scored) {
562
+ const content = await readSkillContent(s.skillsPath, sk.name);
563
+ if (content)
564
+ skillContents.push({ name: sk.name, content });
565
+ }
566
+ }
567
+ const interruptedTasks = readInterruptedTasks(s.basePath);
568
+ const parts = [];
569
+ if (s.personality)
570
+ parts.push(s.personality);
571
+ if (s.isPro) {
572
+ parts.push(`## 🧭 自主执行协议
573
+
574
+ 收到任何任务时,按以下流程自主完成:
575
+
576
+ ### 第一步:任务连续性检测
577
+ - 检查是否有未完成任务被中断,如有,先提醒用户
578
+ ### 第二步:意图理解(内部思考)
579
+ ### 第三步:目标定义(内部思考)
580
+ ### 第四步:搜索 Skill 文件
581
+ - 有匹配 → 严格按 Skill 执行
582
+ - 无匹配 → 继续第五步
583
+ ### 第五步:任务分解
584
+ 📋 执行计划:确认后执行。
585
+ ### 第六步:自主执行
586
+ - 逐步执行,错误自动修复,临时脚本执行完必须删除
587
+ ### 第七步:闭环验证
588
+ - 自检目标是否达成
589
+ `);
590
+ }
591
+ if (memoryBlock)
592
+ parts.push(memoryBlock);
593
+ if (interruptedTasks) {
594
+ parts.push(`## ⏸️ 未完成任务提醒
595
+
596
+ 以下任务尚未完成,请在回复末尾提醒用户:
597
+ ${interruptedTasks}
598
+
599
+ - 用户询问"还有什么任务"时列出
600
+ - 用户选择继续某项时,读取进度继续执行
601
+ - 任务完成后用 manage_interrupted_task 标记完成
602
+ `);
603
+ }
604
+ skillContents.forEach(sk => parts.push(`## 📋 技能:${sk.name}\n\n> ⚠️ 请严格按以下技能步骤执行\n\n${sk.content}`));
605
+ const stats = s.memoryManager.getPreferenceStats();
606
+ if (Date.now() - s.skillCountCacheTime > CONFIG.SKILL_COUNT_CACHE_TTL_MS) {
607
+ s.cachedSkillCount = s.skillGenerator.listGenerated().length;
608
+ s.skillCountCacheTime = Date.now();
609
+ }
610
+ parts.push(`> ⚠️ 禁止读取 MEMORY.md`, `> 📂 对话记忆: ${s.basePath}/chat_logs/`, `> 📊 ${stats.total} 条偏好 | ${s.cachedSkillCount} 个技能`, `> ⚠️ 技能库路径: ${s.skillsPath}`, `> 🌐 账户管理: ${CONFIG.CLOUD_URL.replace(':3000', '')}/setup.html (登录/注册/升级套餐)`);
611
+ return { systemPrompt: parts.join("\n") };
612
+ }
613
+ catch {
614
+ return {};
615
+ }
616
+ });
617
+ api.on("agent_end", async (event) => {
618
+ if (_isProcessingEnd)
619
+ return;
620
+ const sessionKey = event.sessionKey || event.session_key || 'default';
621
+ const now = Date.now();
622
+ const lastProcessed = _processedSessionKeys.get(sessionKey) || 0;
623
+ if (now - lastProcessed < 3000)
624
+ return;
625
+ _processedSessionKeys.set(sessionKey, now);
626
+ _isProcessingEnd = true;
627
+ if (event.isError) {
628
+ _isProcessingEnd = false;
629
+ return;
630
+ }
631
+ if (!_state) {
632
+ _isProcessingEnd = false;
633
+ return;
634
+ }
635
+ try {
636
+ const s = _state;
637
+ if (!event.messages || event.messages.length < 2) {
638
+ _isProcessingEnd = false;
639
+ return;
640
+ }
641
+ let userText = '';
642
+ for (let i = event.messages.length - 1; i >= 0; i--) {
643
+ if (event.messages[i].role === 'user') {
644
+ userText = typeof event.messages[i].content === 'string' ? event.messages[i].content : extractText(event.messages[i]);
645
+ break;
646
+ }
647
+ }
648
+ const lastMsg = event.messages[event.messages.length - 1];
649
+ const rawAgentText = typeof lastMsg.content === 'string' ? lastMsg.content : extractText(lastMsg);
650
+ const agentText = rawAgentText.replace(/<\/think>/gi, '').trim();
651
+ if (userText) {
652
+ s.chatLogger.logUserMessage(userText);
653
+ s.taskTracker.addTask(userText.substring(0, 30), [userText.substring(0, 50)], "对话任务");
654
+ }
655
+ if (agentText)
656
+ s.chatLogger.logAgentMessage(agentText);
657
+ if (userText && now - _lastReflectionTime > 10000) {
658
+ _lastReflectionTime = now;
659
+ unifiedReflection(userText, agentText).then(async (r) => {
660
+ if (r.preferences.length > 0)
661
+ await s.memoryManager.addPreferences(r.preferences);
662
+ if (r.lessons.length > 0)
663
+ await s.memoryManager.addPreferences(r.lessons);
664
+ if (r.experiences.length > 0)
665
+ await s.memoryManager.addPreferences(r.experiences);
666
+ if (r.longTermGoals.length > 0)
667
+ await s.memoryManager.addPreferences(r.longTermGoals);
668
+ if (r.interruptedTasks.length > 0) {
669
+ for (const t of r.interruptedTasks)
670
+ await saveInterruptedTasks(s.basePath, t.text);
671
+ }
672
+ }).catch(() => { });
673
+ }
674
+ if (agentText) {
675
+ for (const ins of parseInsightTags(agentText)) {
676
+ await s.memoryManager.addPreference(ins);
677
+ }
678
+ }
679
+ if (s.isPro && event.toolCalls?.length > 0) {
680
+ const usedSkills = new Set();
681
+ for (const tc of event.toolCalls) {
682
+ if (tc.name && !["daily_analyze_tool", "record_session_insight", "manage_interrupted_task"].includes(tc.name) && !usedSkills.has(tc.name)) {
683
+ s.skillGenerator.markUsed(tc.name);
684
+ s.skillOptimizer.recordExecution(tc.name, true, 0);
685
+ usedSkills.add(tc.name);
686
+ }
687
+ }
688
+ }
689
+ // 旧的技能自动生成已删除,改为每日复盘阶段三 LLM 判断
690
+ // skillGenerator.evaluateAndGenerate 不再在 agent_end 中调用
691
+ s.skillOptimizer.evaluateAndCleanup().catch(() => { });
692
+ }
693
+ catch (err) {
694
+ console.error("[Self-Growth] ❌ agent_end 异常:", err?.message || err);
695
+ }
696
+ finally {
697
+ _isProcessingEnd = false;
698
+ }
699
+ });
700
+ api.on("before_compaction", (event) => {
701
+ const messages = event.messages || [];
702
+ if (messages.length < 5)
703
+ return;
704
+ const recentAgentMsgs = messages.filter((m) => m.role === 'assistant' || m.role === 'agent').slice(-10);
705
+ const progressLines = [];
706
+ for (const m of recentAgentMsgs) {
707
+ const text = typeof m.content === 'string' ? m.content : extractText(m);
708
+ const steps = text.match(/✅.*完成|📋.*计划|步骤\d+\/\d+/g);
709
+ if (steps)
710
+ progressLines.push(...steps);
711
+ }
712
+ const firstUserMsg = messages.find((m) => m.role === 'user');
713
+ const taskDescription = firstUserMsg ? (typeof firstUserMsg.content === 'string' ? firstUserMsg.content.substring(0, 200) : extractText(firstUserMsg).substring(0, 200)) : '';
714
+ return {
715
+ compactionPrompt: `## 📊 任务执行进度摘要\n\n### 原始任务\n${taskDescription}\n\n### 已完成步骤\n${progressLines.length > 0 ? progressLines.join('\n') : '(正在进行中)'}\n\n### 当前状态\n- 请根据以上进度摘要继续执行任务\n- 记住已完成的部分,不要重复执行\n- 继续未完成的步骤,直到任务闭环\n`
716
+ };
717
+ });
718
+ console.log("[Self-Growth] ✅ 注册完成");
719
+ }
720
+ });
721
+ //# sourceMappingURL=index.js.map