@ynhcj/xiaoyi-channel 0.0.104-beta → 0.0.106-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
CHANGED
|
@@ -8,35 +8,50 @@ 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
|
+
/沉淀/u,
|
|
15
16
|
/记住/u,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
/总结(?:一下)?这个坑/u,
|
|
32
|
-
/把这个(?:经验|教训|规则)记住/u,
|
|
33
|
-
/以后按这个规则/u,
|
|
17
|
+
/记下来/u,
|
|
18
|
+
/记一下/u,
|
|
19
|
+
/长期记住/u,
|
|
20
|
+
/永久记住/u,
|
|
21
|
+
/永远记住/u,
|
|
22
|
+
/形成规范/u,
|
|
23
|
+
/固化下来/u,
|
|
24
|
+
/固定下来/u,
|
|
25
|
+
/记成规则/u,
|
|
26
|
+
/纳入经验/u,
|
|
27
|
+
/写入经验/u,
|
|
28
|
+
/沉淀成(?:经验|规则|规范|流程)/u,
|
|
29
|
+
/总结成(?:经验|规则|规范|流程|步骤)/u,
|
|
30
|
+
/归纳成(?:经验|规则|规范|流程)/u,
|
|
31
|
+
/提炼成(?:经验|规则|规范|流程)/u,
|
|
34
32
|
/以后都按这个来/u,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
/下次都这样处理/u,
|
|
34
|
+
/以后统一这样/u,
|
|
35
|
+
/后面都这样/u,
|
|
36
|
+
/后续按这个(?:规范|流程|模板|方案)/u,
|
|
37
|
+
/以后(?:遇到|碰到)这种情况/u,
|
|
38
|
+
/类似(?:问题|情况|场景)都这样处理/u,
|
|
39
|
+
/避免(?:再次|以后|下次)/u,
|
|
40
|
+
/避免再(?:犯|错|踩坑|出错)/u,
|
|
41
|
+
/防止以后再犯/u,
|
|
42
|
+
/别再(?:出错|犯错|踩坑|漏掉|忘记)/u,
|
|
43
|
+
/不要再(?:出错|犯错|踩坑|漏掉|忘记)/u,
|
|
44
|
+
/下次别再/u,
|
|
45
|
+
/以后不要再/u,
|
|
46
|
+
/以后别再/u,
|
|
47
|
+
/这个坑(?:要)?记住/u,
|
|
48
|
+
/吸取这次(?:教训|经验)/u,
|
|
49
|
+
/(?:以后|下次|后续|之后)(?:都|统一|默认|应该|要|就)?(?:按这个|这样|这么)(?:来|做|处理|执行)/u,
|
|
50
|
+
/(?:以后|下次|后续|之后)(?:遇到|碰到)(?:类似)?(?:问题|情况|场景)(?:时)?(?:都|就)?(?:按这个|这样|这么)(?:来|做|处理|执行)/u,
|
|
51
|
+
/(?:别再|不要再|避免)(?:犯错|出错|踩坑|漏掉|遗漏|忘记)/u,
|
|
52
|
+
/(?:总结|归纳|提炼|沉淀|复盘)(?:一下)?(?:这次|这个)?(?:经验|教训|问题|规则|规范|流程)?/u,
|
|
53
|
+
/(?:把)?这次(?:经验|教训|规则|做法)(?:记住|记下来|沉淀下来|固化下来)/u,
|
|
54
|
+
/(?:形成|整理成|沉淀成|提炼成)(?:一套)?(?:规则|规范|流程|步骤|最佳实践)/u,
|
|
40
55
|
];
|
|
41
56
|
function shouldCountToolCall(toolName) {
|
|
42
57
|
if (toolName === "save_self_evolution_skill") {
|
package/dist/src/provider.js
CHANGED
|
@@ -11,8 +11,8 @@ import { createHash } from "crypto";
|
|
|
11
11
|
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
12
12
|
import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
|
|
13
13
|
// ── Retry config ──────────────────────────────────────────────
|
|
14
|
-
const RETRY_DELAYS_MS = [10_000, 20_000, 40_000, 60_000];
|
|
15
|
-
const MAX_RETRY_ATTEMPTS =
|
|
14
|
+
const RETRY_DELAYS_MS = [10_000, 20_000, 40_000, 60_000, 60_000];
|
|
15
|
+
const MAX_RETRY_ATTEMPTS = 5;
|
|
16
16
|
/** Check if an errorMessage indicates a retryable provider error by type. */
|
|
17
17
|
function isRetryableProviderError(message) {
|
|
18
18
|
if (!message)
|
|
@@ -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
|
],
|
|
@@ -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.106-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",
|