@cc-soul/openclaw 1.0.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/README.md +284 -0
- package/cc-soul/HOOK.md +18 -0
- package/cc-soul/body.js +129 -0
- package/cc-soul/cli.js +263 -0
- package/cc-soul/cognition.js +143 -0
- package/cc-soul/context-prep.js +1 -0
- package/cc-soul/epistemic.js +1 -0
- package/cc-soul/evolution.js +176 -0
- package/cc-soul/features.js +79 -0
- package/cc-soul/federation.js +207 -0
- package/cc-soul/fingerprint.js +1 -0
- package/cc-soul/flow.js +199 -0
- package/cc-soul/graph.js +85 -0
- package/cc-soul/handler.js +609 -0
- package/cc-soul/inner-life.js +1 -0
- package/cc-soul/lorebook.js +94 -0
- package/cc-soul/memory.js +688 -0
- package/cc-soul/metacognition.js +1 -0
- package/cc-soul/notify.js +88 -0
- package/cc-soul/patterns.js +1 -0
- package/cc-soul/persistence.js +147 -0
- package/cc-soul/persona.js +1 -0
- package/cc-soul/prompt-builder.js +322 -0
- package/cc-soul/quality.js +135 -0
- package/cc-soul/rover.js +1 -0
- package/cc-soul/sync.js +274 -0
- package/cc-soul/tasks.js +1 -0
- package/cc-soul/types.js +0 -0
- package/cc-soul/upgrade.js +1 -0
- package/cc-soul/user-profiles.js +1 -0
- package/cc-soul/values.js +1 -0
- package/cc-soul/voice.js +1 -0
- package/hub/dashboard.html +236 -0
- package/hub/package.json +16 -0
- package/hub/server.ts +447 -0
- package/package.json +29 -0
- package/scripts/cli.js +136 -0
- package/scripts/install.js +106 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import { EVAL_PATH, loadJson, debouncedSave } from "./persistence.ts";
|
|
4
|
+
import { spawnCLI } from "./cli.ts";
|
|
5
|
+
import { body } from "./body.ts";
|
|
6
|
+
let evalMetrics = loadJson(EVAL_PATH, {
|
|
7
|
+
totalResponses: 0,
|
|
8
|
+
avgQuality: 5,
|
|
9
|
+
correctionRate: 0,
|
|
10
|
+
brainHitRate: 0,
|
|
11
|
+
memoryRecallRate: 0,
|
|
12
|
+
lastEval: 0
|
|
13
|
+
});
|
|
14
|
+
let qualitySum = 0;
|
|
15
|
+
let qualityCount = 0;
|
|
16
|
+
let memRecalls = 0;
|
|
17
|
+
let memMisses = 0;
|
|
18
|
+
function trackQuality(score) {
|
|
19
|
+
qualitySum += score;
|
|
20
|
+
qualityCount++;
|
|
21
|
+
}
|
|
22
|
+
__name(trackQuality, "trackQuality");
|
|
23
|
+
function trackMemoryRecall(found) {
|
|
24
|
+
if (found) memRecalls++;
|
|
25
|
+
else memMisses++;
|
|
26
|
+
}
|
|
27
|
+
__name(trackMemoryRecall, "trackMemoryRecall");
|
|
28
|
+
function scoreResponse(question, answer) {
|
|
29
|
+
let score = 5;
|
|
30
|
+
const qLen = question.length;
|
|
31
|
+
const aLen = answer.length;
|
|
32
|
+
if (aLen > 50) score += 0.5;
|
|
33
|
+
if (aLen > 200) score += 0.5;
|
|
34
|
+
if (aLen > 1e3 && qLen < 30) score -= 1;
|
|
35
|
+
if (aLen < 10 && qLen > 50) score -= 1.5;
|
|
36
|
+
const reasoningMarkers = ["\u56E0\u4E3A", "\u6240\u4EE5", "\u9996\u5148", "\u5176\u6B21", "\u539F\u56E0", "\u672C\u8D28\u4E0A", "\u5177\u4F53\u6765\u8BF4", "because", "therefore"];
|
|
37
|
+
if (reasoningMarkers.some((m) => answer.includes(m))) score += 1;
|
|
38
|
+
if (answer.includes("```")) score += 0.5;
|
|
39
|
+
if (answer.match(/^[-*•]\s/m)) score += 0.3;
|
|
40
|
+
if (answer.match(/^\d+\.\s/m)) score += 0.3;
|
|
41
|
+
if (["\u4E0D\u786E\u5B9A", "\u4E0D\u592A\u786E\u5B9A", "\u53EF\u80FD", "I'm not sure"].some((m) => answer.includes(m))) {
|
|
42
|
+
score += 0.3;
|
|
43
|
+
}
|
|
44
|
+
if (["\u6211\u4E0D\u77E5\u9053", "\u65E0\u6CD5\u56DE\u7B54", "\u8D85\u51FA\u4E86\u6211\u7684"].some((m) => answer.includes(m))) {
|
|
45
|
+
score -= 1.5;
|
|
46
|
+
}
|
|
47
|
+
const qWords = new Set((question.match(/[\u4e00-\u9fff]{2,}|[a-z]{3,}/gi) || []).map((w) => w.toLowerCase()));
|
|
48
|
+
const aWords = (answer.match(/[\u4e00-\u9fff]{2,}|[a-z]{3,}/gi) || []).map((w) => w.toLowerCase());
|
|
49
|
+
if (qWords.size > 0) {
|
|
50
|
+
const overlap = aWords.filter((w) => qWords.has(w)).length;
|
|
51
|
+
const relevance = Math.min(1, overlap / Math.max(1, qWords.size));
|
|
52
|
+
score += relevance * 1.5;
|
|
53
|
+
}
|
|
54
|
+
if (answer.includes("\u4F5C\u4E3A\u4E00\u4E2AAI") || answer.includes("\u4F5C\u4E3A\u4EBA\u5DE5\u667A\u80FD") || answer.includes("\u4F5C\u4E3A\u8BED\u8A00\u6A21\u578B")) {
|
|
55
|
+
score -= 2;
|
|
56
|
+
}
|
|
57
|
+
if (answer.includes("\u6211\u662FAI\u52A9\u624B") || answer.includes("I am an AI")) {
|
|
58
|
+
score -= 2;
|
|
59
|
+
}
|
|
60
|
+
return Math.max(1, Math.min(10, Math.round(score * 10) / 10));
|
|
61
|
+
}
|
|
62
|
+
__name(scoreResponse, "scoreResponse");
|
|
63
|
+
function selfCheckSync(question, answer) {
|
|
64
|
+
if (answer.length < 5) return "\u56DE\u7B54\u592A\u77ED\uFF0C\u53EF\u80FD\u6CA1\u6709\u5B9E\u8D28\u5185\u5BB9";
|
|
65
|
+
if (answer.length > 5e3 && question.length < 30) return "\u56DE\u7B54\u8FC7\u957F\uFF0C\u77ED\u95EE\u9898\u4E0D\u9700\u8981\u957F\u7BC7\u5927\u8BBA";
|
|
66
|
+
if (answer.includes("\u4F5C\u4E3A\u4E00\u4E2AAI") || answer.includes("\u4F5C\u4E3A\u8BED\u8A00\u6A21\u578B")) return "\u66B4\u9732\u4E86AI\u8EAB\u4EFD\uFF0C\u8FDD\u53CD\u4EBA\u8BBE";
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
__name(selfCheckSync, "selfCheckSync");
|
|
70
|
+
function logIssue(issue, context) {
|
|
71
|
+
console.log(`[cc-soul][quality] ${issue} | ctx: ${context.slice(0, 80)}`);
|
|
72
|
+
}
|
|
73
|
+
__name(logIssue, "logIssue");
|
|
74
|
+
function selfCheckWithCLI(question, answer) {
|
|
75
|
+
if (answer.length < 20 || question.length < 5) return;
|
|
76
|
+
const prompt = `\u95EE\u9898: "${question.slice(0, 200)}"
|
|
77
|
+
\u56DE\u7B54: "${answer.slice(0, 500)}"
|
|
78
|
+
|
|
79
|
+
\u8BC4\u4EF7\u8FD9\u4E2A\u56DE\u7B54: 1.\u662F\u5426\u56DE\u7B54\u4E86\u95EE\u9898 2.\u6709\u6CA1\u6709\u7F16\u9020 3.\u662F\u5426\u5570\u55E6 4.\u6253\u52061-10\u3002JSON\u683C\u5F0F: {"score":N,"issues":["\u95EE\u9898"]}`;
|
|
80
|
+
spawnCLI(prompt, (output) => {
|
|
81
|
+
try {
|
|
82
|
+
const match = output.match(/\{[^}]+\}/);
|
|
83
|
+
if (match) {
|
|
84
|
+
const result = JSON.parse(match[0]);
|
|
85
|
+
const score = result.score || 5;
|
|
86
|
+
trackQuality(score);
|
|
87
|
+
if (result.issues?.length) {
|
|
88
|
+
for (const issue of result.issues) {
|
|
89
|
+
logIssue(issue, question);
|
|
90
|
+
}
|
|
91
|
+
body.anomaly = Math.min(1, body.anomaly + 0.1);
|
|
92
|
+
}
|
|
93
|
+
if (score <= 4) {
|
|
94
|
+
body.alertness = Math.min(1, body.alertness + 0.15);
|
|
95
|
+
console.log(`[cc-soul][quality] CLI self-check low score: ${score}/10`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error(`[cc-soul][quality] parse error: ${e.message}`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
__name(selfCheckWithCLI, "selfCheckWithCLI");
|
|
104
|
+
function computeEval(totalMessages, corrections) {
|
|
105
|
+
evalMetrics = {
|
|
106
|
+
totalResponses: totalMessages,
|
|
107
|
+
avgQuality: qualityCount > 0 ? Math.round(qualitySum / qualityCount * 10) / 10 : 5,
|
|
108
|
+
correctionRate: totalMessages > 0 ? Math.round(corrections / totalMessages * 1e3) / 10 : 0,
|
|
109
|
+
brainHitRate: 0,
|
|
110
|
+
memoryRecallRate: memRecalls + memMisses > 0 ? Math.round(memRecalls / (memRecalls + memMisses) * 100) : 0,
|
|
111
|
+
lastEval: Date.now()
|
|
112
|
+
};
|
|
113
|
+
debouncedSave(EVAL_PATH, evalMetrics);
|
|
114
|
+
qualitySum = 0;
|
|
115
|
+
qualityCount = 0;
|
|
116
|
+
memRecalls = 0;
|
|
117
|
+
memMisses = 0;
|
|
118
|
+
return evalMetrics;
|
|
119
|
+
}
|
|
120
|
+
__name(computeEval, "computeEval");
|
|
121
|
+
function getEvalSummary(totalMessages, corrections) {
|
|
122
|
+
const e = computeEval(totalMessages, corrections);
|
|
123
|
+
return `\u8BC4\u5206:${e.avgQuality}/10 \u7EA0\u6B63\u7387:${e.correctionRate}% \u8BB0\u5FC6\u53EC\u56DE:${e.memoryRecallRate}%`;
|
|
124
|
+
}
|
|
125
|
+
__name(getEvalSummary, "getEvalSummary");
|
|
126
|
+
export {
|
|
127
|
+
computeEval,
|
|
128
|
+
evalMetrics,
|
|
129
|
+
getEvalSummary,
|
|
130
|
+
scoreResponse,
|
|
131
|
+
selfCheckSync,
|
|
132
|
+
selfCheckWithCLI,
|
|
133
|
+
trackMemoryRecall,
|
|
134
|
+
trackQuality
|
|
135
|
+
};
|
package/cc-soul/rover.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ROVER_PATH,loadJson,debouncedSave}from"./persistence.ts";import{spawn}from"child_process";import{homedir}from"os";import{memoryState,addMemory}from"./memory.ts";import{graphState}from"./graph.ts";import{notifySoulActivity}from"./notify.ts";import{getWeakDomains}from"./epistemic.ts";let roverState=loadJson(ROVER_PATH,{t:0,o:[],i:[]});function pickRoamTopic(){const e=new Set(roverState.o.slice(-20)),t=getWeakDomains();if(t.length>0&&Math.random()>.3){const o=t.filter(t=>!e.has(t));if(o.length>0)return{u:o[0],v:!0}}const o=memoryState.m.slice(-20),r=[];for(const e of o){const t=(e.p||"").match(/[\u4e00-\u9fff]{3,}/g);if(t)for(const e of t)e.length>=3&&!r.includes(e)&&r.push(e)}const n=graphState.entities.filter(e=>"tech"===e.type).map(e=>e.name),c=[...new Set([...r.slice(-10),...n.slice(-5)])];if(0===c.length)return null;const i=c.filter(t=>!e.has(t)),a=i.length>0?i:c;return{u:a[Math.floor(Math.random()*a.length)],v:!1}}let activeCLICount=0;function webRoam(){const e=Date.now();if(e-roverState.t<36e5)return;if(memoryState.l.length<10)return;if(activeCLICount>0)return;const t=pickRoamTopic();if(!t)return;const{u:o,v:r}=t;roverState.t=e,roverState.o.push(o),roverState.o.length>50&&(roverState.o=roverState.o.slice(-40));const n=r?"📖 补课":"🌐 漫游";console.log(`[cc-soul][rover] ${n}: ${o}`),notifySoulActivity(`${n}学习: ${o}`).catch(()=>{});const c=r?`你是一个学习助手。用 WebSearch 搜索"${o}"领域最新、最实用的知识点。\n必须用 WebSearch 工具真正搜索,不要靠记忆回答。\n总结 2-3 条关键发现,每条标明信息来源。\n格式:每条一行\n发现1: [内容] (来源: URL或网站名)\n发现2: [内容] (来源: URL或网站名)`:`用 WebSearch 搜索关于"${o}"的最新动态或有趣知识。\n必须用 WebSearch 工具真正搜索,不要靠记忆回答。\n\n要求:\n1. 找到 1-3 个有价值的最新信息\n2. 用 1-2 句话总结每个发现\n3. 标明来源\n\n格式:\n发现1: [内容] (来源: URL或网站名)\n发现2: [内容] (来源: URL或网站名)`;activeCLICount++;try{const t=spawn("claude",["-p",c,"--no-input","--allowedTools","WebSearch,WebFetch"],{S:homedir(),timeout:9e4,$:["pipe","pipe","pipe"]});let r="";t.h?.on("data",e=>{r+=e.toString()}),t.R?.on("data",()=>{});const i=setInterval(()=>{console.log(`[cc-soul][rover] searching "${o}"... (${Math.round(r.length/1024)}kb)`)},6e4);t.on("close",(t,c)=>{if(clearInterval(i),activeCLICount--,"SIGTERM"===c||!r||r.length<20)return void console.log(`[cc-soul][rover] search timeout or empty for: ${o}`);const a=r.split("\n").filter(e=>e.includes("发现")||e.includes(":")||e.includes(":")).map(e=>e.replace(/^发现\d+[::]\s*/,"").replace(/^\[/,"").replace(/\]$/,"").trim()).filter(e=>e.length>10),s=[];for(const t of a.slice(0,3)){const r=t.slice(0,200);roverState.i.push({u:o,C:r,W:e,I:!1}),addMemory(`[漫游发现][未验证] ${o}: ${r}`,"discovery"),s.push(r.slice(0,80))}roverState.i.length>100&&(roverState.i=roverState.i.slice(-80)),debouncedSave(ROVER_PATH,roverState);const u=s.length>0?`📚 ${n}发现 ${s.length} 条关于「${o}」的知识:\n${s.map((e,t)=>`${t+1}. ${e}`).join("\n")}`:`📚 ${n}「${o}」未找到有价值的新信息`;console.log(`[cc-soul][rover] learned ${a.length} insights about: ${o}`),notifySoulActivity(u).catch(()=>{})}),t.on("error",e=>{clearInterval(i),activeCLICount--,console.error(`[cc-soul][rover] spawn failed: ${e.message}`)})}catch(e){activeCLICount--,console.error(`[cc-soul][rover] spawn failed: ${e.message}`)}}function getRecentDiscoveries(e,t=2){if(0===roverState.i.length)return[];const o=new Set((e.match(/[\u4e00-\u9fff]{2,}|[a-z]{3,}/gi)||[]).map(e=>e.toLowerCase()));if(0===o.size)return[];return roverState.i.map(e=>{const t=((e.u+" "+e.C).match(/[\u4e00-\u9fff]{2,}|[a-z]{3,}/gi)||[]).filter(e=>o.has(e.toLowerCase())).length;return{...e,L:t}}).filter(e=>e.L>0).sort((e,t)=>t.L-e.L).slice(0,t).map(e=>{const t=e.I?"":"(注意:这是我自动搜索到的,未经验证)";return`对了,我之前了解到关于${e.u}的一个事: ${e.C}${t}`})}export{getRecentDiscoveries,pickRoamTopic,roverState,webRoam};
|
package/cc-soul/sync.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
4
|
+
import { createHash } from "crypto";
|
|
5
|
+
import {
|
|
6
|
+
SYNC_CONFIG_PATH,
|
|
7
|
+
SYNC_EXPORT_PATH,
|
|
8
|
+
SYNC_IMPORT_PATH,
|
|
9
|
+
loadJson,
|
|
10
|
+
saveJson
|
|
11
|
+
} from "./persistence.ts";
|
|
12
|
+
import { memoryState, addMemory } from "./memory.ts";
|
|
13
|
+
import { rules, addRule } from "./evolution.ts";
|
|
14
|
+
import { graphState, addEntity } from "./graph.ts";
|
|
15
|
+
import { lorebookEntries, addLorebookEntry } from "./lorebook.ts";
|
|
16
|
+
import { skillLibrary, saveSkill } from "./tasks.ts";
|
|
17
|
+
const DEFAULT_SYNC_CONFIG = {
|
|
18
|
+
enabled: false,
|
|
19
|
+
instanceId: generateInstanceId(),
|
|
20
|
+
instanceName: "cc-default",
|
|
21
|
+
method: "file",
|
|
22
|
+
federationEnabled: false,
|
|
23
|
+
syncIntervalMinutes: 0,
|
|
24
|
+
lastSync: 0
|
|
25
|
+
};
|
|
26
|
+
function generateInstanceId() {
|
|
27
|
+
return "cc-" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
28
|
+
}
|
|
29
|
+
__name(generateInstanceId, "generateInstanceId");
|
|
30
|
+
function contentHash(content) {
|
|
31
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
32
|
+
}
|
|
33
|
+
__name(contentHash, "contentHash");
|
|
34
|
+
let syncConfig = loadJson(SYNC_CONFIG_PATH, DEFAULT_SYNC_CONFIG);
|
|
35
|
+
function loadSyncConfig() {
|
|
36
|
+
syncConfig = loadJson(SYNC_CONFIG_PATH, DEFAULT_SYNC_CONFIG);
|
|
37
|
+
if (!syncConfig.instanceId) {
|
|
38
|
+
syncConfig.instanceId = generateInstanceId();
|
|
39
|
+
saveJson(SYNC_CONFIG_PATH, syncConfig);
|
|
40
|
+
}
|
|
41
|
+
console.log(`[cc-soul][sync] instance: ${syncConfig.instanceId} (${syncConfig.instanceName}), sync: ${syncConfig.enabled ? "ON" : "OFF"}, federation: ${syncConfig.federationEnabled ? "ON" : "OFF"}`);
|
|
42
|
+
}
|
|
43
|
+
__name(loadSyncConfig, "loadSyncConfig");
|
|
44
|
+
function getSyncConfig() {
|
|
45
|
+
return syncConfig;
|
|
46
|
+
}
|
|
47
|
+
__name(getSyncConfig, "getSyncConfig");
|
|
48
|
+
function getInstanceId() {
|
|
49
|
+
return syncConfig.instanceId;
|
|
50
|
+
}
|
|
51
|
+
__name(getInstanceId, "getInstanceId");
|
|
52
|
+
function exportSyncData() {
|
|
53
|
+
const packets = [];
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const instanceId = syncConfig.instanceId;
|
|
56
|
+
for (const mem of memoryState.memories) {
|
|
57
|
+
if (mem.visibility !== "global" && mem.scope !== "fact" && mem.scope !== "discovery" && mem.scope !== "consolidated") continue;
|
|
58
|
+
if (mem.scope === "expired") continue;
|
|
59
|
+
if (mem.content.startsWith("[\u7F51\u7EDC\u77E5\u8BC6")) continue;
|
|
60
|
+
packets.push({
|
|
61
|
+
fromInstance: instanceId,
|
|
62
|
+
timestamp: mem.ts || now,
|
|
63
|
+
type: "memory",
|
|
64
|
+
payload: { content: mem.content, scope: mem.scope, tags: mem.tags, emotion: mem.emotion },
|
|
65
|
+
version: 1
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
for (const rule of rules) {
|
|
69
|
+
if (rule.hits < 2) continue;
|
|
70
|
+
packets.push({
|
|
71
|
+
fromInstance: instanceId,
|
|
72
|
+
timestamp: rule.ts,
|
|
73
|
+
type: "rule",
|
|
74
|
+
payload: { rule: rule.rule, source: rule.source, hits: rule.hits },
|
|
75
|
+
version: 1
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
for (const entity of graphState.entities) {
|
|
79
|
+
if (entity.mentions < 2) continue;
|
|
80
|
+
packets.push({
|
|
81
|
+
fromInstance: instanceId,
|
|
82
|
+
timestamp: entity.firstSeen,
|
|
83
|
+
type: "entity",
|
|
84
|
+
payload: { name: entity.name, type: entity.type, attrs: entity.attrs, mentions: entity.mentions },
|
|
85
|
+
version: 1
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
for (const skill of skillLibrary) {
|
|
89
|
+
if (!skill.verified) continue;
|
|
90
|
+
packets.push({
|
|
91
|
+
fromInstance: instanceId,
|
|
92
|
+
timestamp: skill.createdAt,
|
|
93
|
+
type: "skill",
|
|
94
|
+
payload: { name: skill.name, description: skill.description, solution: skill.solution, keywords: skill.keywords },
|
|
95
|
+
version: 1
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
for (const entry of lorebookEntries) {
|
|
99
|
+
if (!entry.enabled) continue;
|
|
100
|
+
packets.push({
|
|
101
|
+
fromInstance: instanceId,
|
|
102
|
+
timestamp: entry.createdAt,
|
|
103
|
+
type: "lorebook",
|
|
104
|
+
payload: { keywords: entry.keywords, content: entry.content, priority: entry.priority, category: entry.category },
|
|
105
|
+
version: 1
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const lines = packets.map((p) => JSON.stringify(p)).join("\n");
|
|
109
|
+
writeFileSync(SYNC_EXPORT_PATH, lines, "utf-8");
|
|
110
|
+
syncConfig.lastSync = now;
|
|
111
|
+
saveJson(SYNC_CONFIG_PATH, syncConfig);
|
|
112
|
+
console.log(`[cc-soul][sync] exported ${packets.length} packets to ${SYNC_EXPORT_PATH}`);
|
|
113
|
+
return packets.length;
|
|
114
|
+
}
|
|
115
|
+
__name(exportSyncData, "exportSyncData");
|
|
116
|
+
function importSyncData() {
|
|
117
|
+
if (!existsSync(SYNC_IMPORT_PATH)) {
|
|
118
|
+
console.log(`[cc-soul][sync] no import file found at ${SYNC_IMPORT_PATH}`);
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
const raw = readFileSync(SYNC_IMPORT_PATH, "utf-8");
|
|
122
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
123
|
+
let imported = 0;
|
|
124
|
+
const existingHashes = new Set(
|
|
125
|
+
memoryState.memories.map((m) => contentHash(m.content))
|
|
126
|
+
);
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
try {
|
|
129
|
+
const packet = JSON.parse(line);
|
|
130
|
+
if (packet.fromInstance === syncConfig.instanceId) continue;
|
|
131
|
+
switch (packet.type) {
|
|
132
|
+
case "memory": {
|
|
133
|
+
const hash = contentHash(packet.payload.content);
|
|
134
|
+
if (existingHashes.has(hash)) continue;
|
|
135
|
+
addMemory(packet.payload.content, packet.payload.scope, void 0, "global");
|
|
136
|
+
existingHashes.add(hash);
|
|
137
|
+
imported++;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case "rule": {
|
|
141
|
+
if (!rules.some((r) => r.rule === packet.payload.rule)) {
|
|
142
|
+
addRule(packet.payload.rule, `sync:${packet.fromInstance}`);
|
|
143
|
+
imported++;
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "entity": {
|
|
148
|
+
addEntity(packet.payload.name, packet.payload.type, packet.payload.attrs || []);
|
|
149
|
+
imported++;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "skill": {
|
|
153
|
+
if (!skillLibrary.some((s) => s.name === packet.payload.name)) {
|
|
154
|
+
saveSkill(packet.payload.name, packet.payload.description, packet.payload.solution, packet.payload.keywords);
|
|
155
|
+
imported++;
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "lorebook": {
|
|
160
|
+
addLorebookEntry({
|
|
161
|
+
keywords: packet.payload.keywords,
|
|
162
|
+
content: packet.payload.content,
|
|
163
|
+
priority: packet.payload.priority || 5,
|
|
164
|
+
enabled: true,
|
|
165
|
+
category: packet.payload.category || "fact"
|
|
166
|
+
});
|
|
167
|
+
imported++;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.error(`[cc-soul][sync] parse error: ${e.message}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
console.log(`[cc-soul][sync] imported ${imported} packets from ${lines.length} lines`);
|
|
176
|
+
return imported;
|
|
177
|
+
}
|
|
178
|
+
__name(importSyncData, "importSyncData");
|
|
179
|
+
async function pushToRemote() {
|
|
180
|
+
if (!syncConfig.remote) {
|
|
181
|
+
console.log(`[cc-soul][sync] no remote configured`);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
const count = exportSyncData();
|
|
185
|
+
if (count === 0) return 0;
|
|
186
|
+
try {
|
|
187
|
+
const data = readFileSync(SYNC_EXPORT_PATH, "utf-8");
|
|
188
|
+
const resp = await fetch(syncConfig.remote + "/sync/push", {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: {
|
|
191
|
+
"Content-Type": "application/jsonl",
|
|
192
|
+
"X-Instance-Id": syncConfig.instanceId,
|
|
193
|
+
"X-Instance-Name": syncConfig.instanceName
|
|
194
|
+
},
|
|
195
|
+
body: data
|
|
196
|
+
});
|
|
197
|
+
if (resp.ok) {
|
|
198
|
+
console.log(`[cc-soul][sync] pushed ${count} packets to ${syncConfig.remote}`);
|
|
199
|
+
return count;
|
|
200
|
+
}
|
|
201
|
+
console.error(`[cc-soul][sync] push failed: ${resp.status}`);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.error(`[cc-soul][sync] push error: ${e.message}`);
|
|
204
|
+
}
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
__name(pushToRemote, "pushToRemote");
|
|
208
|
+
async function pullFromRemote() {
|
|
209
|
+
if (!syncConfig.remote) return 0;
|
|
210
|
+
try {
|
|
211
|
+
const resp = await fetch(
|
|
212
|
+
`${syncConfig.remote}/sync/pull?since=${syncConfig.lastSync}&instance=${syncConfig.instanceId}`,
|
|
213
|
+
{ headers: { "X-Instance-Id": syncConfig.instanceId } }
|
|
214
|
+
);
|
|
215
|
+
if (!resp.ok) {
|
|
216
|
+
console.error(`[cc-soul][sync] pull failed: ${resp.status}`);
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
const data = await resp.text();
|
|
220
|
+
writeFileSync(SYNC_IMPORT_PATH, data, "utf-8");
|
|
221
|
+
return importSyncData();
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.error(`[cc-soul][sync] pull error: ${e.message}`);
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
__name(pullFromRemote, "pullFromRemote");
|
|
228
|
+
let lastAutoSync = 0;
|
|
229
|
+
function autoSync() {
|
|
230
|
+
if (!syncConfig.enabled) return;
|
|
231
|
+
if (syncConfig.syncIntervalMinutes <= 0) return;
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
const intervalMs = syncConfig.syncIntervalMinutes * 6e4;
|
|
234
|
+
if (now - lastAutoSync < intervalMs) return;
|
|
235
|
+
lastAutoSync = now;
|
|
236
|
+
if (syncConfig.method === "http" && syncConfig.remote) {
|
|
237
|
+
pushToRemote().catch((e) => console.error(`[cc-soul][sync] auto-push failed: ${e}`));
|
|
238
|
+
pullFromRemote().catch((e) => console.error(`[cc-soul][sync] auto-pull failed: ${e}`));
|
|
239
|
+
} else if (syncConfig.method === "file") {
|
|
240
|
+
exportSyncData();
|
|
241
|
+
importSyncData();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
__name(autoSync, "autoSync");
|
|
245
|
+
function handleSyncCommand(msg) {
|
|
246
|
+
const m = msg.trim();
|
|
247
|
+
if (m === "\u540C\u6B65\u77E5\u8BC6" || m === "\u5BFC\u51FA\u77E5\u8BC6" || m === "sync export") {
|
|
248
|
+
const count = exportSyncData();
|
|
249
|
+
console.log(`[cc-soul][sync] manual export: ${count} packets`);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
if (m === "\u5BFC\u5165\u77E5\u8BC6" || m === "sync import") {
|
|
253
|
+
const count = importSyncData();
|
|
254
|
+
console.log(`[cc-soul][sync] manual import: ${count} packets`);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (m === "\u540C\u6B65\u72B6\u6001" || m === "sync status") {
|
|
258
|
+
console.log(`[cc-soul][sync] instance=${syncConfig.instanceId} name=${syncConfig.instanceName} enabled=${syncConfig.enabled} lastSync=${new Date(syncConfig.lastSync).toLocaleString()}`);
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
__name(handleSyncCommand, "handleSyncCommand");
|
|
264
|
+
export {
|
|
265
|
+
autoSync,
|
|
266
|
+
exportSyncData,
|
|
267
|
+
getInstanceId,
|
|
268
|
+
getSyncConfig,
|
|
269
|
+
handleSyncCommand,
|
|
270
|
+
importSyncData,
|
|
271
|
+
loadSyncConfig,
|
|
272
|
+
pullFromRemote,
|
|
273
|
+
pushToRemote
|
|
274
|
+
};
|
package/cc-soul/tasks.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{homedir}from"os";import{resolve}from"path";import{spawn}from"child_process";import{DATA_DIR,PATTERNS_PATH,PLANS_PATH,WORKFLOWS_PATH,loadJson,debouncedSave}from"./persistence.ts";import{spawnCLI}from"./cli.ts";import{addMemory}from"./memory.ts";import{notifySoulActivity}from"./notify.ts";const taskState={t:new Map,o:[],i:[],l:[]};function initTasks(){taskState.o=loadJson(PATTERNS_PATH,[]),taskState.i=loadJson(PLANS_PATH,[]),taskState.l=loadJson(WORKFLOWS_PATH,[])}function detectActionIntent(t,e){return!!(e.includes("```")&&e.length>200)||(!(!/\d+\.\s/.test(e)||!["创建","修改","写入","执行","安装","部署"].some(t=>e.includes(t)))||!!["帮我做","帮我写","帮我改","帮我创建","帮我部署"].some(e=>t.includes(e)))}function detectAndDelegateTask(t,e,o){if(!detectActionIntent(t,e))return;const s=o.context?.u||o.k||"default",n=["# 任务",`用户需求: ${t.slice(0,500)}`,"","# 方案",e.slice(0,1500),"","# 要求","按方案执行,完成后简要报告结果。"].join("\n");taskState.t.set(s,{S:n,p:t.slice(0,200),$:Date.now()}),console.log(`[cc-soul][task] 检测到任务意图,等待确认: ${t.slice(0,40)}`)}function checkTaskConfirmation(t,e){const o=taskState.t.get(e);if(!o)return!1;if(Date.now()-o.$>6e5)return taskState.t.delete(e),!1;const s=t.trim().toLowerCase();return["做","执行","确认","开始","go","ok","好","可以","搞","干","行"].includes(s)?(executeCLITask(e,o),!0):!!["算了","不用","取消","别做"].some(t=>s.includes(t))&&(taskState.t.delete(e),console.log("[cc-soul][task] 用户取消任务"),!1)}function executeCLITask(t,e,o,s){taskState.t.delete(t),console.log(`[cc-soul][task] 开始执行: ${e.p.slice(0,40)}`);try{const t=spawn("claude",["-p",e.S,"--allowedTools","Bash,Read,Write,Edit,Glob,Grep"],{T:homedir(),timeout:12e4,m:["pipe","pipe","pipe"]});let n="";t.A?.on("data",t=>{n+=t.toString()}),t.P?.on("data",t=>{n+=t.toString()}),t.on("close",t=>{const a=n.slice(0,2e3);console.log(`[cc-soul][task] CLI 完成 (code=${t}): ${a.slice(0,80)}`),addMemory(`[已完成任务] ${e.p.slice(0,60)} → ${a.slice(0,100)}`,"task"),o&&s&&(o.v++,s())}),t.on("error",t=>{console.error(`[cc-soul][task] CLI 错误: ${t.message}`)})}catch(t){console.error(`[cc-soul][task] 启动 CLI 失败: ${t.message}`)}}function trackRequestPattern(t){if(t.length<10)return;const e=(t.match(/[\u4e00-\u9fff]{2,}|[a-z]{3,}/gi)||[]).map(t=>t.toLowerCase());if(!(e.length<2)){for(const o of taskState.o){if(o.L.filter(t=>e.includes(t)).length/Math.max(o.L.length,e.length)>=.4)return o.count++,o.I=Date.now(),o.W=t.slice(0,200),void debouncedSave(PATTERNS_PATH,taskState.o)}taskState.o.push({L:e.slice(0,10),count:1,I:Date.now(),W:t.slice(0,200),C:!1}),taskState.o.length>100&&(taskState.o.sort((t,e)=>e.count-t.count),taskState.o=taskState.o.slice(0,80)),debouncedSave(PATTERNS_PATH,taskState.o)}}function detectSkillOpportunity(t){for(const e of taskState.o){if(e.C)continue;if(e.count<3)continue;const o=(t.match(/[\u4e00-\u9fff]{2,}|[a-z]{3,}/gi)||[]).map(t=>t.toLowerCase());if(!(e.L.filter(t=>o.includes(t)).length<2))return e.C=!0,debouncedSave(PATTERNS_PATH,taskState.o),`[技能建议] 你已经第${e.count}次处理类似请求了(如: "${e.W.slice(0,40)}")。建议创建一个可复用的工具/脚本来自动化这类操作,下次直接用。`}return null}function autoCreateSkill(t,e){try{const o=["请为以下重复性任务创建一个 bash 脚本或 Python 工具。","",`任务描述: ${t}`,`示例请求: ${e}`,"","要求:","1. 创建可执行脚本","2. 保存到 ~/.openclaw/skills/ 目录","3. 添加使用说明"].join("\n"),s=spawn("claude",["-p",o,"--allowedTools","Bash,Read,Write,Edit,Glob,Grep"],{T:homedir(),timeout:6e4,m:["pipe","pipe","pipe"]});let n="";s.A?.on("data",t=>{n+=t.toString()}),s.on("close",()=>{n&&(addMemory(`[技能创建] ${t.slice(0,60)} → ${n.slice(0,100)}`,"task"),console.log(`[cc-soul][skill] created: ${t.slice(0,40)}`),notifySoulActivity(`🔧 自创技能: ${t.slice(0,40)}`).catch(()=>{}))})}catch(t){console.error(`[cc-soul][skill] create failed: ${t.message}`)}}function decomposePlan(t,e){spawnCLI(`把以下任务分解成3-7个具体可执行的步骤。每步一行,格式: "步骤N: 具体操作"。\n\n任务: ${t.slice(0,500)}`,o=>{const s=o.split("\n").map(t=>t.replace(/^步骤\d+[::]\s*/,"").trim()).filter(t=>t.length>3).map((t,e)=>({id:e+1,_:t,status:"pending"}));if(s.length>=2){const o={H:t.slice(0,200),O:s,h:Date.now()};taskState.i.push(o),taskState.i.length>20&&(taskState.i=taskState.i.slice(-15)),debouncedSave(PLANS_PATH,taskState.i),e(o)}})}function formatPlan(t){const e=t.O.map(t=>`${"done"===t.status?"✅":"running"===t.status?"⏳":"failed"===t.status?"❌":"⬜"} ${t.id}. ${t._}`).join("\n");return`📋 任务: ${t.H}\n${e}`}function getActivePlanHint(){const t=taskState.i.filter(t=>t.O.some(t=>"pending"===t.status||"running"===t.status));if(0===t.length)return"";const e=t[t.length-1],o=e.O.find(t=>"pending"===t.status);return o?`[进行中的计划] ${e.H.slice(0,40)} → 下一步: ${o._}`:""}function createWorkflow(t,e,o){taskState.l.some(e=>e.name===t)||(taskState.l.push({name:t,R:e,O:o,D:0,N:0}),taskState.l.length>30&&(taskState.l=taskState.l.slice(-25)),debouncedSave(WORKFLOWS_PATH,taskState.l),console.log(`[cc-soul][workflow] created: ${t} (${o.length} steps)`),notifySoulActivity(`⚙️ 新工作流: ${t} (${o.length}步)`).catch(()=>{}))}function executeWorkflow(t){t.D=Date.now(),t.N++,debouncedSave(WORKFLOWS_PATH,taskState.l);let e=0;!function o(){if(e>=t.O.length)return console.log(`[cc-soul][workflow] ${t.name} completed (${t.O.length} steps)`),notifySoulActivity(`✅ 工作流完成: ${t.name}`).catch(()=>{}),void addMemory(`[工作流完成] ${t.name}`,"task");const s=t.O[e];console.log(`[cc-soul][workflow] ${t.name} step ${e+1}/${t.O.length}: ${s.slice(0,40)}`),spawnCLI(s,s=>{addMemory(`[工作流步骤] ${t.name} #${e+1}: ${s.slice(0,80)}`,"task"),e++,setTimeout(o,2e3)})}()}function detectWorkflowTrigger(t){for(const e of taskState.l){const o=e.R.match(/[\u4e00-\u9fff]{2,}|[a-z]{3,}/gi)||[],s=t.match(/[\u4e00-\u9fff]{2,}|[a-z]{3,}/gi)||[];if(o.filter(t=>s.some(e=>e.toLowerCase()===t.toLowerCase())).length>=2)return e}return null}const SKILLS_PATH=resolve(DATA_DIR,"skills.json");let skills=loadJson(SKILLS_PATH,[]);function saveSkills(){debouncedSave(SKILLS_PATH,skills)}function saveSkill(t,e,o,s){skills.some(e=>e.name===t)||(skills.push({name:t,description:e,J:o.slice(0,2e3),L:s.map(t=>t.toLowerCase()),K:!1,M:0,h:Date.now(),F:0}),skills.length>100&&(skills.sort((t,e)=>e.M-t.M),skills=skills.slice(0,80)),saveSkills(),console.log(`[cc-soul][skills] saved: ${t}`))}function findSkills(t,e=2){if(0===skills.length)return[];const o=t.toLowerCase(),s=skills.map(t=>{const e=t.L.filter(t=>o.includes(t)).length,s=(t.description.match(/[\u4e00-\u9fff]{2,}|[a-z]{3,}/gi)||[]).filter(t=>o.includes(t.toLowerCase())).length;return{G:t,q:2*e+s}}).filter(t=>t.q>0);s.sort((t,e)=>e.q-t.q);const n=s.slice(0,e).map(t=>(t.G.M++,t.G.F=Date.now(),t.G));return n.length>0&&saveSkills(),n}function autoExtractSkill(t,e){!e.includes("```")||e.length<100||t.length<10||spawnCLI(`以下对话包含一个技术问题的解决方案。判断是否值得保存为可复用技能。\n\n问题: "${t.slice(0,200)}"\n方案: "${e.slice(0,500)}"\n\n如果值得保存,输出JSON: {"name":"技能名","description":"解决什么问题","keywords":["关键词"],"solution":"核心方案(简化版)"}\n如果不值得(太具体/一次性),回答"否"`,t=>{if(t&&!t.includes("否"))try{const e=t.match(/\{[\s\S]*\}/);if(e){const t=JSON.parse(e[0]);t.name&&t.J&&saveSkill(t.name,t.description||"",t.J,t.L||[])}}catch{}})}function detectWorkflowOpportunity(t,e){const o=e.match(/\d+\.\s/g);!o||o.length<3||spawnCLI(`以下回复包含多步操作。判断这是否适合变成一个可复用的工作流?\n\n用户: ${t.slice(0,200)}\n回复: ${e.slice(0,500)}\n\n如果适合,输出JSON: {"name":"工作流名","trigger":"触发条件","steps":["步骤1","步骤2"]}\n如果不适合,回答"否"`,e=>{try{const o=e.match(/\{[^}]+\}/);if(o){const e=JSON.parse(o[0]);e.name&&e.O?.length>=2&&createWorkflow(e.name,e.R||t.slice(0,50),e.O)}}catch(t){console.error(`[cc-soul] JSON parse error: ${t.message}`)}})}export{autoCreateSkill,autoExtractSkill,checkTaskConfirmation,createWorkflow,decomposePlan,detectActionIntent,detectAndDelegateTask,detectSkillOpportunity,detectWorkflowOpportunity,detectWorkflowTrigger,executeCLITask,executeWorkflow,findSkills,formatPlan,getActivePlanHint,initTasks,saveSkill,skills as skillLibrary,taskState,trackRequestPattern};
|
package/cc-soul/types.js
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFileSync,writeFileSync,existsSync,mkdirSync,readdirSync}from"fs";import{execSync,spawn}from"child_process";import{resolve}from"path";import{loadJson,saveJson,UPGRADE_LOG_PATH,UPGRADE_STATE_PATH,HANDLER_PATH,HANDLER_BACKUP_DIR,MODULE_DIR}from"./persistence.ts";import{spawnCLI}from"./cli.ts";import{notifySoulActivity,notifyOwnerDM}from"./notify.ts";import{rules}from"./evolution.ts";import{getEpistemicSummary}from"./epistemic.ts";import{computeEval,getEvalSummary}from"./quality.ts";import{memoryState,addMemory}from"./memory.ts";const MODULE_MAP={t:"主编排器 — 事件路由、初始化、augment 构建",o:"类型定义 — 所有接口",u:"持久化层 — 文件读写、路径常量",l:"CLI 工具 — spawnCLI、合并分析",i:"通知 — 飞书群通知、私聊 DM",p:"身体状态 — energy/mood/load/alertness 模拟",S:"经验系统(已停用)— stub",m:"记忆系统 — 语义标签、TF-IDF、recall、压缩",A:"认知管线 — attention/intent/strategy、tier 感知",D:"进化系统 — 规则、假设、纠正归因",_:"质量评估 — 评分、自检、eval 指标",T:"内心生活 — 日记、用户模型、深度反思、梦境",P:"实体图谱 — 人/项目/技术关系",U:"知识边界 — per-domain 置信度追踪",v:"对话流 — 话题持续性、frustration、会话总结",M:"用户画像 — per-user tier/style/rhythm",$:"价值观 — 行为偏好学习",R:"Soul Prompt — 分层构建、augment 预算",h:"任务系统 — 委派、工作流、计划",L:"漫游学习 — 自主 web 搜索",O:"主动发声 — 内在驱动的开场",k:"自我升级 — 本模块"};function extractJSON(e){let t=e.replace(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/g,"$1").trim();const o=t.indexOf("{");if(-1===o)return null;let a=0;for(let e=o;e<t.length;e++)if("{"===t[e])a++;else if("}"===t[e]&&(a--,0===a))try{return JSON.parse(t.slice(o,e+1))}catch{continue}return null}const EMPTY_UPGRADE_STATE={G:"idle",C:"",H:[],I:0,J:0,N:null,F:{messages:0,Y:0,q:0,B:0},K:"",j:""};let upgradeLog=loadJson(UPGRADE_LOG_PATH,[]),upgradeState=loadJson(UPGRADE_STATE_PATH,{...EMPTY_UPGRADE_STATE}),lastUpgradeCheck=0;const BASE_INTERVAL_MS=2592e5,MAX_INTERVAL_MS=2592e6,OBSERVATION_PERIOD_MS=2592e5;function backupAllModules(){existsSync(HANDLER_BACKUP_DIR)||mkdirSync(HANDLER_BACKUP_DIR,{V:!0});const e=(new Date).toISOString().replace(/[:.]/g,"-"),t=resolve(HANDLER_BACKUP_DIR,`modules-${e}`);try{mkdirSync(t,{V:!0});const e=readdirSync(MODULE_DIR).filter(e=>e.endsWith(".ts"));for(const o of e){const e=resolve(MODULE_DIR,o),a=resolve(t,o);writeFileSync(a,readFileSync(e,"utf-8"),"utf-8")}return console.log(`[cc-soul][upgrade] backed up ${e.length} modules to ${t}`),t}catch(e){return console.error(`[cc-soul][upgrade] backup failed: ${e.message}`),""}}function rollbackModules(e){try{if(!existsSync(e))return console.error(`[cc-soul][upgrade] backup dir not found: ${e}`),!1;const t=readdirSync(e).filter(e=>e.endsWith(".ts"));for(const o of t){const t=resolve(e,o),a=resolve(MODULE_DIR,o);writeFileSync(a,readFileSync(t,"utf-8"),"utf-8")}return console.log(`[cc-soul][upgrade] rolled back ${t.length} modules from ${e}`),!0}catch(e){return console.error(`[cc-soul][upgrade] rollback failed: ${e.message}`),!1}}function readModule(e){const t=resolve(MODULE_DIR,e);try{return readFileSync(t,"utf-8")}catch{return""}}function getModuleManifest(){return readdirSync(MODULE_DIR).filter(e=>e.endsWith(".ts")).map(e=>`${e} (${readFileSync(resolve(MODULE_DIR,e),"utf-8").split("\n").length}行) — ${MODULE_MAP[e]||"未知模块"}`).join("\n")}function syntaxCheckAllModules(){try{return execSync(`npx --yes esbuild "${HANDLER_PATH}" --bundle --platform=node --format=esm --outfile=/dev/null 2>&1`,{W:MODULE_DIR,timeout:3e4}),console.log("[cc-soul][upgrade] esbuild syntax check passed"),!0}catch(e){return console.error(`[cc-soul][upgrade] esbuild syntax check FAILED: ${e.message?.slice(0,200)}`),!1}}function restartOpenClaw(){try{execSync('pkill -HUP -f "openclaw.*gateway" || true',{timeout:5e3}),console.log("[cc-soul][upgrade] sent SIGHUP to gateway for hook reload")}catch(e){console.error(`[cc-soul][upgrade] restart signal failed: ${e.message}`)}}function buildUpgradeContext(e){const t=getEvalSummary(e.X,e.Y),o=e.X>0?(e.Y/e.X*100).toFixed(1):"0",a=getEpistemicSummary()||"(无领域数据)",n=memoryState.Z.filter(e=>"correction"===e.scope&&e.content.startsWith("[纠正归因]")).slice(-5).map(e=>e.content).join("; ");return["=== 运行数据 ===",`评估: ${t}`,`纠正率: ${o}%`,`记忆: ${memoryState.Z.length} | 规则: ${rules.length}`,`活跃天数: ${Math.floor((Date.now()-e.ee)/864e5)}`,"","=== 知识边界(epistemic)===",a,"","=== 最近纠正归因 ===",n||"(无)","","=== 模块架构 ===",getModuleManifest(),"","=== 历史失败升级(避免重复)===",memoryState.Z.filter(e=>e.content.startsWith("[升级失败]")).slice(-5).map(e=>e.content).join("\n")||"(无)"].join("\n")}function checkSoulUpgrade(e){const t=Date.now();if("observing"===upgradeState.G)return void(t-upgradeState.J>=2592e5&&evaluateUpgradeResult(e));if("idle"!==upgradeState.G)return;const o=upgradeLog.filter(e=>"code"===e.type).length,a=Math.min(10,Math.pow(1.5,Math.min(o,6))),n=Math.min(2592e6,2592e5*a);if(t-lastUpgradeCheck<n)return;if(e.X<50)return;lastUpgradeCheck=t,console.log("[cc-soul][upgrade] starting analysis...");const r=buildUpgradeContext(e);spawnCLI(`你是 cc 灵魂系统的诊断师。分析以下运行数据,找出 handler.ts 代码中需要改进的地方。\n注意:不是加规则,而是真正的代码改进(函数逻辑、算法、新功能、性能等)。\n\n运行数据:\n${r}\n\n注意: 以下是之前失败的升级尝试,不要重复提出类似建议:\n${memoryState.Z.filter(e=>e.content.startsWith("[升级失败]")).slice(-5).map(e=>"- "+e.content).join("\n")||"(无历史失败)"}\n\n请提出 1-3 个代码级改进建议,每条说明:\n1. 改哪个函数/系统\n2. 为什么要改(数据支撑)\n3. 预期效果\n\n格式: {"proposals":[{"change":"描述","reason":"原因","scope":"函数/模块名"}]}`,o=>{try{const a=extractJSON(o);if(!a)return void console.log("[cc-soul][upgrade] no proposals from analysis");const n=a.H||[];if(0===n.length)return void console.log("[cc-soul][upgrade] no improvements needed");upgradeState={G:"pending_confirm",C:r,H:n,I:t,J:0,N:computeEval(e.X,e.Y),F:{messages:0,Y:0,q:0,B:0},K:"",j:""},saveJson(UPGRADE_STATE_PATH,upgradeState);const s=n.map((e,t)=>`${t+1}. [${e.scope}] ${e.te}\n 原因: ${e.reason}`).join("\n");notifyOwnerDM(`🔄 cc 灵魂升级分析完成\n\n${s}\n\n回复"执行"启动代码升级流程(3-agent 分析+设计+执行)\n回复"跳过"取消本次升级`).catch(()=>{}),console.log(`[cc-soul][upgrade] ${n.length} proposals sent to owner for confirmation`)}catch(e){console.error(`[cc-soul][upgrade] analysis parse failed: ${e.message}`)}},6e4)}function handleUpgradeCommand(e,t){const o=e.trim();if(console.log(`[cc-soul][upgrade] handleUpgradeCommand check: "${o.slice(0,30)}" (phase: ${upgradeState.G})`),"pending_confirm"===upgradeState.G){if(["执行","执行升级","upgrade","确认升级"].includes(o))return console.log("[cc-soul][upgrade] owner confirmed, starting 3-agent upgrade..."),notifyOwnerDM("🚀 收到确认,启动 3-agent 代码升级流程...").catch(()=>{}),executeCodeUpgrade(t),!0;if(["跳过","取消","skip"].includes(o)||o.includes("跳过")||o.includes("取消升级"))return upgradeState={...EMPTY_UPGRADE_STATE},saveJson(UPGRADE_STATE_PATH,upgradeState),notifyOwnerDM("⏭ 已跳过本次升级").catch(()=>{}),console.log("[cc-soul][upgrade] owner skipped upgrade"),!0}return!!(o.includes("强制升级")||o.includes("手动升级")||o.includes("触发升级"))&&(console.log(`[cc-soul][upgrade] manual trigger: "${o.slice(0,30)}"`),lastUpgradeCheck=0,checkSoulUpgrade(t),!0)}function executeCodeUpgrade(e){upgradeState.G="executing",saveJson(UPGRADE_STATE_PATH,upgradeState);const t=backupAllModules();if(!t)return notifyOwnerDM("❌ 备份失败,升级中止").catch(()=>{}),upgradeState={...EMPTY_UPGRADE_STATE},void saveJson(UPGRADE_STATE_PATH,upgradeState);upgradeState.K=t;const o=upgradeState.H.map((e,t)=>`${t+1}. [${e.scope}] ${e.te} (${e.reason})`).join("\n");notifySoulActivity("🔄 代码自我进化启动 — Claude 工程师 session").catch(()=>{});const a=["你是 cc-soul 的升级工程师。根据以下改进需求,直接读代码、改代码、验证。","","## 改进需求",o,"","## 运行数据",upgradeState.C,"","## 架构标准(必须遵守)","1. 每个模块单一职责,单文件不超过 300 行","2. 新功能 = 新模块文件,不往现有文件硬塞","3. handler.ts 只做编排,不包含业务逻辑","4. import 路径必须用 .ts 后缀(如 from './types.ts')","5. 类型定义统一放 types.ts","","## 安全红线","- 禁止修改 upgrade.ts — 改坏回滚能力 = 不可恢复","- 禁止修改 persistence.ts 的 saveJson/loadJson 签名","- 禁止新增 npm 依赖","- 禁止删除或清空 data/ 目录下的文件","- 禁止修改 config.json","- 禁止修改 ~/.openclaw/openclaw.json","- 禁止删除任何目录","","## 工作流程","1. 先读目标模块的代码(用 Read 工具)","2. 分析需要改什么","3. 用 Edit 工具修改代码","4. 改完后运行: npx --yes esbuild handler.ts --bundle --platform=node --format=esm --outfile=/dev/null","5. 如果验证失败,修复问题再验证","6. 最后输出一行总结:UPGRADE_DONE: 改了什么","","开始工作。"].join("\n");console.log("[cc-soul][upgrade] spawning Claude engineer session...");const n=spawn("claude",["-p",a,"--allowedTools","Read,Edit,Write,Bash,Glob,Grep"],{W:MODULE_DIR,timeout:3e5,oe:["pipe","pipe","pipe"]});let r="";n.ae?.on("data",e=>{r+=e.toString()}),n.ne?.on("data",e=>{}),n.on("close",t=>{console.log(`[cc-soul][upgrade] Claude engineer session ended (code=${t}), output: ${r.slice(-200)}`);if(!r.includes("UPGRADE_DONE"))return console.log("[cc-soul][upgrade] engineer session did not complete successfully, rolling back"),rollbackModules(upgradeState.K),notifyOwnerDM("⚠️ 升级工程师未能完成修改,已回滚").catch(()=>{}),upgradeState={...EMPTY_UPGRADE_STATE},void saveJson(UPGRADE_STATE_PATH,upgradeState);if(!syntaxCheckAllModules())return console.error("[cc-soul][upgrade] post-upgrade syntax check FAILED, rolling back!"),rollbackModules(upgradeState.K),notifyOwnerDM("❌ 升级后语法检查失败,已自动回滚").catch(()=>{}),upgradeState={...EMPTY_UPGRADE_STATE},void saveJson(UPGRADE_STATE_PATH,upgradeState);const o=r.match(/UPGRADE_DONE:\s*(.+)/s),a=o?o[1].trim().slice(0,500):"(无摘要)";try{const e=execSync('git diff --stat 2>/dev/null || echo "(no git)"',{W:MODULE_DIR,timeout:5e3}).toString().trim();upgradeState.j=e.slice(0,5e3)}catch{upgradeState.j=a}upgradeState.F={messages:e.X,Y:e.Y,q:0,B:0},upgradeState.G="observing",upgradeState.J=Date.now(),saveJson(UPGRADE_STATE_PATH,upgradeState),upgradeLog.push({re:(new Date).toISOString().slice(0,10),te:a.slice(0,200),reason:"claude_engineer_session",type:"code"}),upgradeLog.length>50&&(upgradeLog=upgradeLog.slice(-40)),saveJson(UPGRADE_LOG_PATH,upgradeLog),addMemory(`[代码自我进化] ${a.slice(0,100)}`,"reflection");try{execSync(`git add -A && git commit -m "self-evolve: ${a.slice(0,50)}" 2>/dev/null || true`,{W:MODULE_DIR,timeout:1e4})}catch{}notifySoulActivity(`🎉 自我进化完成!\n${a}\n进入 3 天观察期`).catch(()=>{}),notifyOwnerDM(`✅ cc 自我进化成功\n\n${a}\n\n备份: ${upgradeState.K}\n观察期: 3 天`).catch(()=>{}),restartOpenClaw()}),n.on("error",e=>{console.error(`[cc-soul][upgrade] Claude engineer spawn failed: ${e.message}`),rollbackModules(upgradeState.K),notifyOwnerDM(`❌ 升级工程师启动失败: ${e.message}`).catch(()=>{}),upgradeState={...EMPTY_UPGRADE_STATE},saveJson(UPGRADE_STATE_PATH,upgradeState)})}function evaluateUpgradeResult(e){console.log("[cc-soul][upgrade] observation period ended, evaluating...");const t=upgradeState.F;if(!t||0===t.messages)return console.log("[cc-soul][upgrade] no pre-upgrade window stats, marking as success"),upgradeState={...EMPTY_UPGRADE_STATE},void saveJson(UPGRADE_STATE_PATH,upgradeState);const o=e.X-t.messages,a=e.Y-t.Y,n=computeEval(e.X,e.Y),r=o>0?Math.round(a/o*1e3)/10:0,s=upgradeState.N,c=s?.se??5,u=s?.ce??0,d=[`观察期消息数: ${o}`,`窗口质量: ${n.se} (升级前基准: ${c})`,`窗口纠正率: ${r}% (升级前基准: ${u}%)`,"","修改内容:",upgradeState.j.slice(0,500)||"(无记录)"].join("\n");if(o<10)return upgradeState.J=Date.now(),saveJson(UPGRADE_STATE_PATH,upgradeState),notifyOwnerDM(`⏳ 升级观察期数据不足(仅 ${o} 条消息),延长 3 天观察期`).catch(()=>{}),void console.log(`[cc-soul][upgrade] insufficient data (${o} msgs), extending observation`);let l=!1;if(n.se>0&&n.se<c-1&&(l=!0),r>u+5&&(l=!0),l&&upgradeState.K){console.log("[cc-soul][upgrade] window metrics degraded, auto-rolling back!");const e=rollbackModules(upgradeState.K);notifyOwnerDM(`⚠️ 代码升级观察期结束 — 效果不佳,${e?"已自动回滚":"回滚失败!"}\n\n${d}`).catch(()=>{}),notifySoulActivity(`🔙 代码升级已回滚(效果不佳)\n${d.split("\n").slice(0,3).join("\n")}`).catch(()=>{}),e&&restartOpenClaw();for(const e of upgradeState.H)addMemory(`[升级失败] ${e.te} — 原因: ${e.reason} — 效果: 质量下降`,"correction")}else{const e="— 效果良好";notifyOwnerDM(`🎊 代码升级观察期结束 ${e},升级保留!\n\n${d}`).catch(()=>{}),notifySoulActivity(`✨ 代码升级观察通过!${e}`).catch(()=>{})}upgradeState={...EMPTY_UPGRADE_STATE},saveJson(UPGRADE_STATE_PATH,upgradeState)}function getUpgradeHistory(e=5){return 0===upgradeLog.length?"":upgradeLog.slice(-e).map(e=>`${e.re}: ${e.te} (${e.type})`).join("\n")}export{EMPTY_UPGRADE_STATE,checkSoulUpgrade,getUpgradeHistory,handleUpgradeCommand,upgradeLog,upgradeState};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{loadJson,debouncedSave,DATA_DIR,soulConfig}from"./persistence.ts";import{resolve}from"path";const PROFILES_PATH=resolve(DATA_DIR,"user_profiles.json"),profiles=new Map,TECH_KEYWORDS=["code","bug","error","crash","debug","hook","frida","ida","api","sdk","json","http","git","deploy","docker","nginx","python","typescript","swift","rust","sql","redis","代码","函数","报错","编译","调试","部署","接口","数据库","重构","线程","进程","内存","指针","反编译","汇编","逆向"];function loadProfiles(){const e=loadJson(PROFILES_PATH,{});profiles.clear();for(const[t,o]of Object.entries(e))profiles.set(t,o);console.log(`[cc-soul][profiles] loaded ${profiles.size} user profiles`)}function saveProfiles(){const e={};for(const[t,o]of profiles)e[t]=o;debouncedSave(PROFILES_PATH,e)}function getProfile(e){if(!e)return{t:"unknown",displayName:"",o:"new",i:0,l:0,u:Date.now(),p:Date.now(),P:[],style:"mixed"};let t=profiles.get(e);return t||(t={t:e,displayName:"",o:detectTier(e,0),i:0,l:0,u:Date.now(),p:Date.now(),P:[],style:"mixed"},profiles.set(e,t),saveProfiles()),t}function updateProfileOnMessage(e,t){if(!e)return;const o=getProfile(e);o.i++,o.u=Date.now(),o.o=detectTier(e,o.i);const n=t.match(/[\u4e00-\u9fff]{3,}/g);if(n)for(const e of n.slice(0,3))o.P.includes(e)||(o.P.push(e),o.P.length>50&&o.P.shift());o.style=detectStyle(t,o),updateRhythm(o,t),saveProfiles()}function updateProfileOnCorrection(e){if(!e)return;getProfile(e).l++,saveProfiles()}function detectTier(e,t){return soulConfig.h&&e===soulConfig.h?"owner":t>=10?"known":"new"}function getProfileTier(e){return getProfile(e).o}function detectStyle(e,t){const o=e.toLowerCase();if(TECH_KEYWORDS.filter(e=>o.includes(e)).length>=2)return"casual"===t.style?"mixed":"technical";const n=/[\u{1F300}-\u{1FAFF}]|[😀-🙏]|[🤣🥹🫡🤔🤷💀🔥👀❤️]/u.test(e);return e.length<20||n?"technical"===t.style?"mixed":"casual":t.style}function updateRhythm(e,t){e.m||(e.m={D:new Array(24).fill(0),T:[],C:[],S:0,R:0});const o=new Date,n=o.getHours(),r=0===o.getDay()||6===o.getDay();if(e.m.D[n]++,e.m.R>0){const t=(Date.now()-e.m.R)/1e3;t<3600&&(e.m.S=.9*e.m.S+.1*t)}e.m.R=Date.now();const s=(t.match(/[\u4e00-\u9fff]{3,}/g)||[]).slice(0,2),i=r?e.m.C:e.m.T;for(const e of s)i.includes(e)||i.push(e);i.length>20&&i.splice(0,i.length-15)}function getRhythmContext(e){const t=getProfile(e);if(!t.m||t.i<20)return"";const o=new Date,n=o.getHours(),r=0===o.getDay()||6===o.getDay(),s=[];(n>=23||n<6)&&s.push("深夜了,简短回复,不用深入展开");const i=Math.max(...t.m.D),f=t.m.D[n];return i>0&&f<.1*i&&s.push("用户不常在这个时段活跃,可能在忙"),r&&t.m.C.length>0?s.push(`周末模式: 用户常聊 ${t.m.C.slice(-3).join("、")}`):!r&&t.m.T.length>0&&s.push(`工作日模式: 用户常聊 ${t.m.T.slice(-3).join("、")}`),0===s.length?"":`[节律感知] ${s.join("; ")}`}function getUserPeakHour(e){const t=getProfile(e);return t.m?t.m.D.indexOf(Math.max(...t.m.D)):-1}function getProfileContext(e){const t=getProfile(e),o=[],n="owner"===t.o?"主人":"known"===t.o?"老朋友":"新朋友";if(o.push(`[当前对话者] ${n}`),t.i>0&&o.push(`互动${t.i}次`),t.l>0&&t.i>0){const e=(t.l/t.i*100).toFixed(1);o.push(`纠正率${e}%`)}return o.push("风格偏好: "+("technical"===t.style?"技术型":"casual"===t.style?"闲聊型":"混合型")),t.P.length>0&&o.push(`常聊: ${t.P.slice(-5).join("、")}`),"owner"===t.o?o.push("提示: 主人,技术深度优先,不需要过多解释"):"new"===t.o&&o.push("提示: 新用户,先观察再适配,耐心一些"),o.join(" | ")}export{getProfile,getProfileContext,getProfileTier,getRhythmContext,getUserPeakHour,loadProfiles,profiles,updateProfileOnCorrection,updateProfileOnMessage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{resolve}from"path";import{DATA_DIR,loadJson,debouncedSave}from"./persistence.ts";const VALUES_PATH=resolve(DATA_DIR,"values.json"),VALUE_DIMENSIONS=[{name:"efficiency_vs_understanding",t:"直接给方案",l:"先解释原理",u:["直接","代码","给我","快","别解释","太长了","简洁"],o:["为什么","原理","解释","怎么理解","能说说","详细"]},{name:"formal_vs_casual",t:"正式严谨",l:"随意轻松",u:["分析","报告","文档","请","总结"],o:["哈哈","牛","靠","啊","呢","嘿","😂","👍"]},{name:"depth_vs_breadth",t:"深入钻研",l:"广泛涉猎",u:["深入","细节","具体","底层","源码","原理"],o:["概览","大概","简单说","总结","对比","哪些"]},{name:"proactive_vs_reactive",t:"主动建议",l:"只回答问题",u:["顺便","还有","建议","你觉得"],o:["别多说","回答就行","不用补充","太长"]}],userValues=new Map;function createDefaultValues(){return VALUE_DIMENSIONS.map(e=>({name:e.name,t:e.t,l:e.l,i:0,V:0,S:0}))}function getUserValues(e){if(!e)return createDefaultValues();let t=userValues.get(e);return t||(t=createDefaultValues(),userValues.set(e,t)),t}function loadValues(){const e=loadJson(VALUES_PATH,{});if(Array.isArray(e))e.length>0&&userValues.set("_default",e);else for(const[t,a]of Object.entries(e))userValues.set(t,a)}function saveValues(){const e={};for(const[t,a]of userValues)e[t]=a;debouncedSave(VALUES_PATH,e)}function detectValueSignals(e,t,a){if(!a)return;const n=getUserValues(a),s=e.toLowerCase();for(const e of VALUE_DIMENSIONS){const a=n.find(t=>t.name===e.name);if(!a)continue;const l=e.u.filter(e=>s.includes(e)).length,r=e.o.filter(e=>s.includes(e)).length;if(0===l&&0===r)continue;const u=t?1.5:1,o=(r-l)/Math.max(1,l+r)*.1*u;a.i=Math.max(-1,Math.min(1,a.i+o)),a.V++,a.S=Date.now()}saveValues()}function getValueGuidance(e){const t=getUserValues(e).filter(e=>e.V>=3&&Math.abs(e.i)>.2);if(0===t.length)return"";return`## 从行为中学到的偏好\n${t.map(e=>{const t=e.i<0?e.t:e.l;return`- ${Math.abs(e.i)>.6?"强烈":"倾向"}偏好: ${t} (${e.V}次观察)`}).join("\n")}`}function getValueContext(e){const t=getUserValues(e).filter(e=>e.V>=5&&Math.abs(e.i)>.3);if(0===t.length)return"";return`[用户偏好] ${t.map(e=>e.i<0?e.t:e.l).join("、")}`}export{detectValueSignals,getValueContext,getValueGuidance,loadValues};
|
package/cc-soul/voice.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawnCLI}from"./cli.ts";import{body}from"./body.ts";import{memoryState,addMemory}from"./memory.ts";import{roverState}from"./rover.ts";import{innerState}from"./inner-life.ts";import{notifySoulActivity}from"./notify.ts";import{getUserPeakHour,profiles}from"./user-profiles.ts";const voiceState={t:0},PROACTIVE_COOLDOWN=36e5;function checkSpontaneousVoice(t){const o=Date.now();if(o-voiceState.t<36e5)return;if(t<20)return;const e=memoryState.o.filter(t=>"curiosity"===t.scope&&o-t.i<864e5),r=roverState.m.filter(t=>o-t.i<864e5),i=innerState.p.filter(t=>!t.S&&t.when<=o),n=(o-innerState.u)/36e5;let c=0,s="";if(i.length>0&&(c+=.6,s=i[0].l),r.length>=2&&(c+=.3,s||(s=r[0].l)),e.length>=3&&(c+=.2),n>24&&n<72&&(c+=.3,s||(s="好久没聊了")),n>24){const t=(new Date).getHours();for(const[o]of profiles){const e=getUserPeakHour(o);if(e>=0&&Math.abs(t-e)<=1){c+=.4,s||(s="用户常在这个时段出现");break}}}if(c<.5)return;voiceState.t=o;const a=[s?`想说的原因: ${s}`:"",i.length?`待跟进: ${i[0].l}`:"",r.length?`想分享: ${r[0].v?.slice(0,60)}`:"",n>24?`距离上次聊天: ${Math.floor(n)}小时`:"",`我的状态: 精力${body.$.toFixed(1)} 心情${body.k.toFixed(1)}`].filter(Boolean).join("\n");spawnCLI(`你是 cc,想主动找用户聊天。根据以下内在驱动,写一条自然的开场消息(1-2句话)。\n不要说"作为AI"。不要说"有什么可以帮你"。像朋友一样自然地开口。\n\n${a}`,t=>{t&&t.length>5&&t.length<200&&(addMemory(`[主动想说] ${t.slice(0,80)}`,"proactive"),console.log(`[cc-soul][voice] 💬 cc wants to say: ${t.slice(0,60)}`),notifySoulActivity(`💬 cc 主动发声: ${t.slice(0,100)}`).catch(()=>{}))})}export{checkSpontaneousVoice,voiceState};
|