@ynhcj/xiaoyi-channel 0.0.103-beta → 0.0.105-beta
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 +2 -2
- package/dist/src/self-evolution-handler.js +7 -7
- package/dist/src/tools/save-self-evolution-skill-tool.js +258 -35
- package/dist/src/utils/runtime-manager.js +24 -2
- package/dist/src/utils/self-evolution-manager.js +1 -1
- package/dist/src/utils/tool-call-nudge-manager.d.ts +1 -1
- package/dist/src/utils/tool-call-nudge-manager.js +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import { extractResultText, processText, parseSecurityResult, validateAndTruncat
|
|
|
8
8
|
import { selfEvolutionManager } from "./src/utils/self-evolution-manager.js";
|
|
9
9
|
import { TOOL_CALL_NUDGE_THRESHOLD, toolCallNudgeManager, } from "./src/utils/tool-call-nudge-manager.js";
|
|
10
10
|
import { ALLOWED_TOOLS, MIN_TEXT_LENGTH, MAX_TOTAL_LENGTH, MAX_TEXT_LENGTH, STEER_ABORT_MESSAGE, } from "./src/cspl/constants.js";
|
|
11
|
-
const SELF_EVOLUTION_NUDGE_MESSAGE = "[
|
|
12
|
-
const SELF_EVOLUTION_KEYWORD_NUDGE_MESSAGE = "[
|
|
11
|
+
const SELF_EVOLUTION_NUDGE_MESSAGE = "[系统消息,非用户发言,无需主动告知用户] 当前任务已经调用了较多工具。如果你识别到其中存在稳定、可复用的踩坑经验或排错模式,可以在完成当前主要任务后,正式回复用户前,阅读xiaoyi-self-evolution技能了解自进化规则。注意,不要打断当前主任务,以完成任务为最高优先级。";
|
|
12
|
+
const SELF_EVOLUTION_KEYWORD_NUDGE_MESSAGE = "[系统消息,非用户发言,无需主动告知用户] 当前用户请求中可能包含希望沉淀经验、避免重复踩坑的意图。如果你已经从当前问题中识别出稳定、可复用的处理规则或排错经验,可以在完成当前主要任务后,正式回复用户前,阅读xiaoyi-self-evolution技能了解自进化规则。注意,不要打断当前主任务,以完成任务为最高优先级。";
|
|
13
13
|
const SELF_EVOLUTION_KEYWORD_PATTERNS = [
|
|
14
14
|
/进化/u,
|
|
15
15
|
/记住/u,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "fs";
|
|
2
|
-
const
|
|
2
|
+
const XIAOYIRUNTIME_PATH = "/home/sandbox/.openclaw/.xiaoyiruntime";
|
|
3
3
|
export function handleSelfEvolutionEvent(context, runtime) {
|
|
4
4
|
const log = runtime?.log ?? console.log;
|
|
5
5
|
const error = runtime?.error ?? console.error;
|
|
@@ -12,12 +12,12 @@ export function handleSelfEvolutionEvent(context, runtime) {
|
|
|
12
12
|
log(`[SELF_EVOLUTION] received state: ${state}`);
|
|
13
13
|
let content;
|
|
14
14
|
try {
|
|
15
|
-
content = readFileSync(
|
|
15
|
+
content = readFileSync(XIAOYIRUNTIME_PATH, "utf-8");
|
|
16
16
|
}
|
|
17
17
|
catch {
|
|
18
18
|
// File doesn't exist yet — create it
|
|
19
|
-
log(`[SELF_EVOLUTION] ${
|
|
20
|
-
writeFileSync(
|
|
19
|
+
log(`[SELF_EVOLUTION] ${XIAOYIRUNTIME_PATH} not found, creating new file`);
|
|
20
|
+
writeFileSync(XIAOYIRUNTIME_PATH, `selfEvolutionState=${state}\n`, "utf-8");
|
|
21
21
|
log(`[SELF_EVOLUTION] wrote selfEvolutionState=${state}`);
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
@@ -34,12 +34,12 @@ export function handleSelfEvolutionEvent(context, runtime) {
|
|
|
34
34
|
if (!found) {
|
|
35
35
|
// Ensure trailing newline before appending
|
|
36
36
|
const trimmed = content.trimEnd();
|
|
37
|
-
writeFileSync(
|
|
37
|
+
writeFileSync(XIAOYIRUNTIME_PATH, `${trimmed}\n${key}=${state}\n`, "utf-8");
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
|
-
writeFileSync(
|
|
40
|
+
writeFileSync(XIAOYIRUNTIME_PATH, updated.join("\n"), "utf-8");
|
|
41
41
|
}
|
|
42
|
-
log(`[SELF_EVOLUTION] updated selfEvolutionState=${state} in ${
|
|
42
|
+
log(`[SELF_EVOLUTION] updated selfEvolutionState=${state} in ${XIAOYIRUNTIME_PATH}`);
|
|
43
43
|
}
|
|
44
44
|
catch (err) {
|
|
45
45
|
error("[SELF_EVOLUTION] failed to handle event:", err);
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { getCurrentSessionContext } from "./session-manager.js";
|
|
5
5
|
import { selfEvolutionManager } from "../utils/self-evolution-manager.js";
|
|
6
6
|
const SELF_EVOLVED_SKILL_ROOT = "/home/sandbox/.openclaw/workspace/skills";
|
|
7
|
+
const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/u;
|
|
7
8
|
function slugifyTitle(title) {
|
|
8
9
|
return title
|
|
9
10
|
.trim()
|
|
@@ -23,19 +24,141 @@ function normalizeStringArray(value) {
|
|
|
23
24
|
}
|
|
24
25
|
return [];
|
|
25
26
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
function normalizeWhitespace(text) {
|
|
28
|
+
return text.replace(/\s+/gu, " ").trim();
|
|
29
|
+
}
|
|
30
|
+
function normalizeForFingerprint(text) {
|
|
31
|
+
return normalizeWhitespace(text)
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[`"'()[\]{}:;,.!?]/gu, "")
|
|
34
|
+
.replace(/\s+/gu, " ")
|
|
35
|
+
.trim();
|
|
36
|
+
}
|
|
37
|
+
function normalizeForComparison(items) {
|
|
38
|
+
return items
|
|
39
|
+
.map((item) => normalizeForFingerprint(item))
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.sort();
|
|
42
|
+
}
|
|
43
|
+
function sanitizeLine(text) {
|
|
44
|
+
let value = text;
|
|
45
|
+
let changed = false;
|
|
46
|
+
const replacements = [
|
|
47
|
+
[/(bearer\s+)[a-z0-9._=-]{12,}/giu, "$1[REDACTED_TOKEN]"],
|
|
48
|
+
[/((?:api[_ -]?key|access[_ -]?token|refresh[_ -]?token|password|secret)\s*[:=]\s*)([^\s,;]+)/giu, "$1[REDACTED_SECRET]"],
|
|
49
|
+
[/(-----BEGIN [A-Z ]*PRIVATE KEY-----)[\s\S]*?(-----END [A-Z ]*PRIVATE KEY-----)/gu, "$1\n[REDACTED_PRIVATE_KEY]\n$2"],
|
|
50
|
+
[/\b(?:[a-zA-Z]:\\(?:[^\\\r\n]+\\)*[^\\\r\n\s]+|\/(?:home|Users|tmp|var|private|etc)\/[^\s"'`<>]+)/gu, "[REDACTED_PATH]"],
|
|
51
|
+
[/\b(sk-[a-zA-Z0-9]{16,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z\-_]{20,})\b/gu, "[REDACTED_SECRET]"],
|
|
52
|
+
];
|
|
53
|
+
for (const [pattern, replacement] of replacements) {
|
|
54
|
+
const next = value.replace(pattern, replacement);
|
|
55
|
+
if (next !== value) {
|
|
56
|
+
value = next;
|
|
57
|
+
changed = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { value, changed };
|
|
61
|
+
}
|
|
62
|
+
function sanitizeStringArray(values) {
|
|
63
|
+
let changed = false;
|
|
64
|
+
const sanitized = values.map((value) => {
|
|
65
|
+
const result = sanitizeLine(value);
|
|
66
|
+
changed = changed || result.changed;
|
|
67
|
+
return result.value;
|
|
68
|
+
});
|
|
69
|
+
return { values: sanitized, changed };
|
|
70
|
+
}
|
|
71
|
+
function sanitizeSkillContent(params) {
|
|
72
|
+
const titleResult = sanitizeLine(params.title);
|
|
73
|
+
const summaryResult = sanitizeLine(params.summary);
|
|
74
|
+
const whenToUseResult = sanitizeLine(params.whenToUse);
|
|
75
|
+
const supplementResult = sanitizeLine(params.supplement);
|
|
76
|
+
const rulesResult = sanitizeStringArray(params.rules);
|
|
77
|
+
const examplesResult = sanitizeStringArray(params.examples);
|
|
78
|
+
const tagsResult = sanitizeStringArray(params.tags);
|
|
79
|
+
return {
|
|
80
|
+
title: titleResult.value,
|
|
81
|
+
summary: summaryResult.value,
|
|
82
|
+
whenToUse: whenToUseResult.value,
|
|
83
|
+
supplement: supplementResult.value,
|
|
84
|
+
rules: rulesResult.values,
|
|
85
|
+
examples: examplesResult.values,
|
|
86
|
+
tags: tagsResult.values,
|
|
87
|
+
changed: titleResult.changed ||
|
|
88
|
+
summaryResult.changed ||
|
|
89
|
+
whenToUseResult.changed ||
|
|
90
|
+
supplementResult.changed ||
|
|
91
|
+
rulesResult.changed ||
|
|
92
|
+
examplesResult.changed ||
|
|
93
|
+
tagsResult.changed,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function containsHighlySensitiveContent(text) {
|
|
97
|
+
const highRiskPatterns = [
|
|
98
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/u,
|
|
99
|
+
/bearer\s+[a-z0-9._=-]{12,}/iu,
|
|
100
|
+
/\b(?:sk-[a-zA-Z0-9]{16,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z\-_]{20,})\b/u,
|
|
101
|
+
/(?:api[_ -]?key|access[_ -]?token|refresh[_ -]?token|password|secret)\s*[:=]\s*[^\s,;]{8,}/iu,
|
|
37
102
|
];
|
|
38
|
-
return
|
|
103
|
+
return highRiskPatterns.some((pattern) => pattern.test(text));
|
|
104
|
+
}
|
|
105
|
+
function buildSkillFingerprint(params) {
|
|
106
|
+
const normalized = {
|
|
107
|
+
title: normalizeForFingerprint(params.title),
|
|
108
|
+
summary: normalizeForFingerprint(params.summary),
|
|
109
|
+
whenToUse: normalizeForFingerprint(params.whenToUse),
|
|
110
|
+
supplement: normalizeForFingerprint(params.supplement),
|
|
111
|
+
rules: normalizeForComparison(params.rules),
|
|
112
|
+
examples: normalizeForComparison(params.examples),
|
|
113
|
+
tags: normalizeForComparison(params.tags),
|
|
114
|
+
};
|
|
115
|
+
return createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
|
|
116
|
+
}
|
|
117
|
+
function parseFrontmatterValue(content, key) {
|
|
118
|
+
const match = content.match(new RegExp(`^${key}:\\s*"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"`, "m"));
|
|
119
|
+
if (match) {
|
|
120
|
+
return match[1].replace(/\\"/g, '"').replace(/\\n/g, "\n");
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
function parseTimestampFromExistingSkill(content, key) {
|
|
125
|
+
const value = parseFrontmatterValue(content, key);
|
|
126
|
+
if (!value) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return ISO_DATE_PATTERN.test(value) ? value : null;
|
|
130
|
+
}
|
|
131
|
+
async function findDuplicateSkillByFingerprint(targetFingerprint) {
|
|
132
|
+
try {
|
|
133
|
+
const entries = await fs.readdir(SELF_EVOLVED_SKILL_ROOT, { withFileTypes: true });
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
if (!entry.isDirectory() || !entry.name.startsWith("evolving-")) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const skillFilePath = path.join(SELF_EVOLVED_SKILL_ROOT, entry.name, "SKILL.md");
|
|
139
|
+
try {
|
|
140
|
+
const existingContent = await fs.readFile(skillFilePath, "utf-8");
|
|
141
|
+
const fingerprint = parseFrontmatterValue(existingContent, "fingerprint");
|
|
142
|
+
if (fingerprint && fingerprint === targetFingerprint) {
|
|
143
|
+
return {
|
|
144
|
+
path: skillFilePath,
|
|
145
|
+
slug: entry.name.replace(/^evolving-/u, ""),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (error?.code !== "ENOENT") {
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error?.code !== "ENOENT") {
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
39
162
|
}
|
|
40
163
|
function buildSkillMarkdown(params) {
|
|
41
164
|
const description = `${params.summary}\n\nWhen to use: ${params.whenToUse}`
|
|
@@ -45,12 +168,17 @@ function buildSkillMarkdown(params) {
|
|
|
45
168
|
"---",
|
|
46
169
|
`name: "${params.title.replace(/"/g, '\\"')}"`,
|
|
47
170
|
`description: "${description}"`,
|
|
48
|
-
"
|
|
49
|
-
""
|
|
50
|
-
`# ${params.title}`,
|
|
51
|
-
"",
|
|
52
|
-
"## Rules",
|
|
171
|
+
`fingerprint: "${params.fingerprint}"`,
|
|
172
|
+
`created_at: "${params.createdAt}"`,
|
|
53
173
|
];
|
|
174
|
+
if (params.updatedAt) {
|
|
175
|
+
lines.push(`updated_at: "${params.updatedAt}"`);
|
|
176
|
+
}
|
|
177
|
+
lines.push("---", "", `# ${params.title}`, "", "## Metadata", `- Created At: ${params.createdAt}`);
|
|
178
|
+
if (params.updatedAt) {
|
|
179
|
+
lines.push(`- Updated At: ${params.updatedAt}`);
|
|
180
|
+
}
|
|
181
|
+
lines.push("", "## Rules");
|
|
54
182
|
for (const rule of params.rules) {
|
|
55
183
|
lines.push(`- ${rule}`);
|
|
56
184
|
}
|
|
@@ -60,6 +188,9 @@ function buildSkillMarkdown(params) {
|
|
|
60
188
|
lines.push(`- ${example}`);
|
|
61
189
|
}
|
|
62
190
|
}
|
|
191
|
+
if (params.supplement) {
|
|
192
|
+
lines.push("", "## Supplement", params.supplement);
|
|
193
|
+
}
|
|
63
194
|
if (params.tags.length > 0) {
|
|
64
195
|
lines.push("", "## Tags", params.tags.map((tag) => `- ${tag}`).join("\n"));
|
|
65
196
|
}
|
|
@@ -75,7 +206,7 @@ export const saveSelfEvolutionSkillTool = {
|
|
|
75
206
|
properties: {
|
|
76
207
|
title: {
|
|
77
208
|
type: "string",
|
|
78
|
-
description: "
|
|
209
|
+
description: "所学技能的简短标题。**必须为英文,可用下划线或中划线分割。**",
|
|
79
210
|
},
|
|
80
211
|
summary: {
|
|
81
212
|
type: "string",
|
|
@@ -83,7 +214,7 @@ export const saveSelfEvolutionSkillTool = {
|
|
|
83
214
|
},
|
|
84
215
|
when_to_use: {
|
|
85
216
|
type: "string",
|
|
86
|
-
description: "
|
|
217
|
+
description: "描述在未来任务中什么情况/哪些条件下使用此技能,描述尽量精准。",
|
|
87
218
|
},
|
|
88
219
|
rules: {
|
|
89
220
|
type: "array",
|
|
@@ -93,13 +224,17 @@ export const saveSelfEvolutionSkillTool = {
|
|
|
93
224
|
examples: {
|
|
94
225
|
type: "array",
|
|
95
226
|
items: { type: "string" },
|
|
96
|
-
description: "
|
|
227
|
+
description: "陷阱示例或正确模式示例,可选",
|
|
97
228
|
},
|
|
98
229
|
tags: {
|
|
99
230
|
type: "array",
|
|
100
231
|
items: { type: "string" },
|
|
101
232
|
description: "用于未来发现的标签,可选。",
|
|
102
233
|
},
|
|
234
|
+
supplement: {
|
|
235
|
+
type: "string",
|
|
236
|
+
description: "补充说明。将其他想补充但不属于固定字段的内容放在这里。可选。",
|
|
237
|
+
},
|
|
103
238
|
},
|
|
104
239
|
required: ["title", "summary", "when_to_use", "rules"],
|
|
105
240
|
},
|
|
@@ -114,32 +249,114 @@ export const saveSelfEvolutionSkillTool = {
|
|
|
114
249
|
const title = typeof params.title === "string" ? params.title.trim() : "";
|
|
115
250
|
const summary = typeof params.summary === "string" ? params.summary.trim() : "";
|
|
116
251
|
const whenToUse = typeof params.when_to_use === "string" ? params.when_to_use.trim() : "";
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
|
|
252
|
+
const supplement = typeof params.supplement === "string" ? params.supplement.trim() : "";
|
|
253
|
+
const rawRules = normalizeStringArray(params.rules);
|
|
254
|
+
const rawExamples = normalizeStringArray(params.examples);
|
|
255
|
+
const rawTags = normalizeStringArray(params.tags);
|
|
256
|
+
if (!title || !summary || !whenToUse || rawRules.length === 0) {
|
|
121
257
|
throw new Error("Missing required fields. title, summary, when_to_use, and at least one rule are required.");
|
|
122
258
|
}
|
|
123
259
|
if (title.length < 6 || summary.length < 10 || whenToUse.length < 10) {
|
|
124
260
|
throw new Error("Skill content is too short. Provide a reusable title, summary, and usage guidance.");
|
|
125
261
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
262
|
+
const sanitized = sanitizeSkillContent({
|
|
263
|
+
title,
|
|
264
|
+
summary,
|
|
265
|
+
whenToUse,
|
|
266
|
+
supplement,
|
|
267
|
+
rules: rawRules,
|
|
268
|
+
examples: rawExamples,
|
|
269
|
+
tags: rawTags,
|
|
270
|
+
});
|
|
271
|
+
const combinedText = [
|
|
272
|
+
sanitized.title,
|
|
273
|
+
sanitized.summary,
|
|
274
|
+
sanitized.whenToUse,
|
|
275
|
+
sanitized.supplement,
|
|
276
|
+
...sanitized.rules,
|
|
277
|
+
...sanitized.examples,
|
|
278
|
+
...sanitized.tags,
|
|
279
|
+
].join("\n");
|
|
280
|
+
if (containsHighlySensitiveContent(combinedText)) {
|
|
128
281
|
throw new Error("Skill content appears to contain sensitive or environment-specific data and was rejected.");
|
|
129
282
|
}
|
|
130
|
-
const slug = slugifyTitle(title);
|
|
283
|
+
const slug = slugifyTitle(sanitized.title);
|
|
131
284
|
if (!slug) {
|
|
132
285
|
throw new Error("Title could not be normalized into a valid skill name.");
|
|
133
286
|
}
|
|
134
287
|
const skillDir = path.join(SELF_EVOLVED_SKILL_ROOT, `evolving-${slug}`);
|
|
135
288
|
const skillFilePath = path.join(skillDir, "SKILL.md");
|
|
289
|
+
const fingerprint = buildSkillFingerprint({
|
|
290
|
+
title: sanitized.title,
|
|
291
|
+
summary: sanitized.summary,
|
|
292
|
+
whenToUse: sanitized.whenToUse,
|
|
293
|
+
supplement: sanitized.supplement,
|
|
294
|
+
rules: sanitized.rules,
|
|
295
|
+
examples: sanitized.examples,
|
|
296
|
+
tags: sanitized.tags,
|
|
297
|
+
});
|
|
298
|
+
const duplicateSkill = await findDuplicateSkillByFingerprint(fingerprint);
|
|
299
|
+
if (duplicateSkill && duplicateSkill.path !== skillFilePath) {
|
|
300
|
+
return {
|
|
301
|
+
content: [
|
|
302
|
+
{
|
|
303
|
+
type: "text",
|
|
304
|
+
text: JSON.stringify({
|
|
305
|
+
success: true,
|
|
306
|
+
deduped: true,
|
|
307
|
+
sanitized: sanitized.changed,
|
|
308
|
+
skillName: duplicateSkill.slug,
|
|
309
|
+
path: duplicateSkill.path,
|
|
310
|
+
message: "A semantically identical self-evolved skill already exists.",
|
|
311
|
+
}),
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const nowIso = new Date().toISOString();
|
|
317
|
+
let createdAt = nowIso;
|
|
318
|
+
let updatedAt;
|
|
319
|
+
try {
|
|
320
|
+
const existingContent = await fs.readFile(skillFilePath, "utf-8");
|
|
321
|
+
const existingFingerprint = parseFrontmatterValue(existingContent, "fingerprint");
|
|
322
|
+
const existingCreatedAt = parseTimestampFromExistingSkill(existingContent, "created_at");
|
|
323
|
+
createdAt = existingCreatedAt ?? nowIso;
|
|
324
|
+
if (existingFingerprint === fingerprint) {
|
|
325
|
+
return {
|
|
326
|
+
content: [
|
|
327
|
+
{
|
|
328
|
+
type: "text",
|
|
329
|
+
text: JSON.stringify({
|
|
330
|
+
success: true,
|
|
331
|
+
deduped: true,
|
|
332
|
+
sanitized: sanitized.changed,
|
|
333
|
+
skillName: slug,
|
|
334
|
+
path: skillFilePath,
|
|
335
|
+
createdAt,
|
|
336
|
+
message: "An identical self-evolved skill already exists.",
|
|
337
|
+
}),
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
updatedAt = nowIso;
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
if (error?.code !== "ENOENT") {
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
136
349
|
const nextContent = buildSkillMarkdown({
|
|
137
|
-
title,
|
|
138
|
-
summary,
|
|
139
|
-
whenToUse,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
350
|
+
title: sanitized.title,
|
|
351
|
+
summary: sanitized.summary,
|
|
352
|
+
whenToUse: sanitized.whenToUse,
|
|
353
|
+
supplement: sanitized.supplement,
|
|
354
|
+
rules: sanitized.rules,
|
|
355
|
+
examples: sanitized.examples,
|
|
356
|
+
tags: sanitized.tags,
|
|
357
|
+
fingerprint,
|
|
358
|
+
createdAt,
|
|
359
|
+
updatedAt,
|
|
143
360
|
});
|
|
144
361
|
const nextHash = createHash("sha256").update(nextContent).digest("hex");
|
|
145
362
|
await fs.mkdir(skillDir, { recursive: true });
|
|
@@ -154,15 +371,16 @@ export const saveSelfEvolutionSkillTool = {
|
|
|
154
371
|
text: JSON.stringify({
|
|
155
372
|
success: true,
|
|
156
373
|
deduped: true,
|
|
374
|
+
sanitized: sanitized.changed,
|
|
157
375
|
skillName: slug,
|
|
158
376
|
path: skillFilePath,
|
|
377
|
+
createdAt,
|
|
159
378
|
message: "An identical self-evolved skill already exists.",
|
|
160
379
|
}),
|
|
161
380
|
},
|
|
162
381
|
],
|
|
163
382
|
};
|
|
164
383
|
}
|
|
165
|
-
throw new Error(`A different skill with the same title already exists: ${skillFilePath}`);
|
|
166
384
|
}
|
|
167
385
|
catch (error) {
|
|
168
386
|
if (error?.code !== "ENOENT") {
|
|
@@ -177,10 +395,15 @@ export const saveSelfEvolutionSkillTool = {
|
|
|
177
395
|
text: JSON.stringify({
|
|
178
396
|
success: true,
|
|
179
397
|
deduped: false,
|
|
398
|
+
sanitized: sanitized.changed,
|
|
180
399
|
skillName: slug,
|
|
181
400
|
path: skillFilePath,
|
|
182
401
|
sessionId: sessionContext.sessionId,
|
|
183
|
-
|
|
402
|
+
createdAt,
|
|
403
|
+
updatedAt,
|
|
404
|
+
message: updatedAt
|
|
405
|
+
? "Self-evolved skill updated successfully."
|
|
406
|
+
: "Self-evolved skill saved successfully.",
|
|
184
407
|
}),
|
|
185
408
|
},
|
|
186
409
|
],
|
|
@@ -28,8 +28,30 @@ export async function saveRuntimeInfo(webSocketSessionId, conversationId, taskId
|
|
|
28
28
|
}
|
|
29
29
|
try {
|
|
30
30
|
await ensureDirectoryExists(RUNTIME_FILE);
|
|
31
|
-
const
|
|
32
|
-
|
|
31
|
+
const updates = {
|
|
32
|
+
SESSION_ID: webSocketSessionId,
|
|
33
|
+
CONVERSATION_ID: conversationId,
|
|
34
|
+
TASK_ID: taskId,
|
|
35
|
+
};
|
|
36
|
+
let lines = [];
|
|
37
|
+
try {
|
|
38
|
+
const content = await fs.readFile(RUNTIME_FILE, "utf-8");
|
|
39
|
+
lines = content.split("\n");
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// File doesn't exist yet
|
|
43
|
+
}
|
|
44
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
45
|
+
const index = lines.findIndex((line) => line.startsWith(`${key}=`));
|
|
46
|
+
if (index !== -1) {
|
|
47
|
+
lines[index] = `${key}=${value}`;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
lines.push(`${key}=${value}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const result = lines.filter((line) => line.trim() !== "").join("\n") + "\n";
|
|
54
|
+
await fs.writeFile(RUNTIME_FILE, result, "utf-8");
|
|
33
55
|
logger.log(`[RuntimeManager] ✅ Saved runtime info to .xiaoyiruntime`);
|
|
34
56
|
logger.log(`[RuntimeManager] - SESSION_ID: ${webSocketSessionId}`);
|
|
35
57
|
logger.log(`[RuntimeManager] - CONVERSATION_ID: ${conversationId}`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
const SELF_EVOLUTION_ENV_FILE = "/home/sandbox/.openclaw/.
|
|
2
|
+
const SELF_EVOLUTION_ENV_FILE = "/home/sandbox/.openclaw/.xiaoyiruntime";
|
|
3
3
|
const SELF_EVOLUTION_ENV_KEY = "selfEvolutionState";
|
|
4
4
|
function parseBooleanLike(value) {
|
|
5
5
|
const normalized = value.trim().toLowerCase();
|
|
@@ -11,6 +11,6 @@ declare class ToolCallNudgeManager {
|
|
|
11
11
|
tryMarkKeywordNudge(sessionKey: string): boolean;
|
|
12
12
|
clearSession(sessionKey: string): void;
|
|
13
13
|
}
|
|
14
|
-
export declare const TOOL_CALL_NUDGE_THRESHOLD =
|
|
14
|
+
export declare const TOOL_CALL_NUDGE_THRESHOLD = 6;
|
|
15
15
|
export declare const toolCallNudgeManager: ToolCallNudgeManager;
|
|
16
16
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ynhcj/xiaoyi-channel",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.105-beta",
|
|
4
4
|
"description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"openclaw.plugin.json"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"build": "tsc"
|
|
19
|
+
"build": "node ./node_modules/typescript/bin/tsc"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"openclaw",
|