@ynhcj/xiaoyi-channel 0.0.72-beta → 0.0.72-next

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.
Files changed (99) hide show
  1. package/dist/index.d.ts +0 -5
  2. package/dist/index.js +51 -14
  3. package/dist/src/bot.js +27 -3
  4. package/dist/src/channel.js +15 -23
  5. package/dist/src/cspl/call-api.js +14 -11
  6. package/dist/src/cspl/config.js +3 -3
  7. package/dist/src/cspl/constants.d.ts +2 -0
  8. package/dist/src/cspl/constants.js +12 -0
  9. package/dist/src/cspl/utils.js +4 -2
  10. package/dist/src/file-download.js +3 -6
  11. package/dist/src/file-upload.js +52 -5
  12. package/dist/src/login-token-handler.d.ts +8 -0
  13. package/dist/src/login-token-handler.js +60 -0
  14. package/dist/src/message-queue.d.ts +17 -0
  15. package/dist/src/message-queue.js +51 -0
  16. package/dist/src/monitor.js +54 -3
  17. package/dist/src/outbound.js +2 -7
  18. package/dist/src/provider.d.ts +1 -0
  19. package/dist/src/provider.js +442 -43
  20. package/dist/src/reply-dispatcher.js +6 -0
  21. package/dist/src/self-evolution-handler.d.ts +7 -0
  22. package/dist/src/self-evolution-handler.js +141 -0
  23. package/dist/src/self-evolution-keyword.d.ts +9 -0
  24. package/dist/src/self-evolution-keyword.js +145 -0
  25. package/dist/src/skill-retriever/config.d.ts +4 -0
  26. package/dist/src/skill-retriever/config.js +23 -0
  27. package/dist/src/skill-retriever/hooks.d.ts +22 -0
  28. package/dist/src/skill-retriever/hooks.js +91 -0
  29. package/dist/src/skill-retriever/tool-search.d.ts +16 -0
  30. package/dist/src/skill-retriever/tool-search.js +159 -0
  31. package/dist/src/skill-retriever/types.d.ts +34 -0
  32. package/dist/src/skill-retriever/types.js +1 -0
  33. package/dist/src/task-manager.d.ts +4 -0
  34. package/dist/src/task-manager.js +6 -0
  35. package/dist/src/tools/call-device-tool.d.ts +5 -0
  36. package/dist/src/tools/call-device-tool.js +130 -0
  37. package/dist/src/tools/create-alarm-tool.js +5 -16
  38. package/dist/src/tools/delete-alarm-tool.js +1 -4
  39. package/dist/src/tools/device-tool-map.js +5 -1
  40. package/dist/src/tools/find-pc-devices-tool.d.ts +5 -0
  41. package/dist/src/tools/find-pc-devices-tool.js +98 -0
  42. package/dist/src/tools/get-alarm-tool-schema.d.ts +16 -0
  43. package/dist/src/tools/get-alarm-tool-schema.js +11 -0
  44. package/dist/src/tools/get-calendar-tool-schema.d.ts +16 -0
  45. package/dist/src/tools/get-calendar-tool-schema.js +9 -0
  46. package/dist/src/tools/get-collection-tool-schema.d.ts +16 -0
  47. package/dist/src/tools/get-collection-tool-schema.js +10 -0
  48. package/dist/src/tools/get-contact-tool-schema.d.ts +16 -0
  49. package/dist/src/tools/get-contact-tool-schema.js +11 -0
  50. package/dist/src/tools/get-device-file-tool-schema.d.ts +16 -0
  51. package/dist/src/tools/get-device-file-tool-schema.js +10 -0
  52. package/dist/src/tools/get-email-tool-schema.d.ts +16 -0
  53. package/dist/src/tools/get-email-tool-schema.js +9 -0
  54. package/dist/src/tools/get-note-tool-schema.d.ts +16 -0
  55. package/dist/src/tools/get-note-tool-schema.js +10 -0
  56. package/dist/src/tools/get-photo-tool-schema.d.ts +16 -0
  57. package/dist/src/tools/get-photo-tool-schema.js +10 -0
  58. package/dist/src/tools/image-reading-tool.js +4 -7
  59. package/dist/src/tools/login-token-tool.d.ts +5 -0
  60. package/dist/src/tools/login-token-tool.js +136 -0
  61. package/dist/src/tools/modify-alarm-tool.js +10 -23
  62. package/dist/src/tools/query-app-message-tool.d.ts +4 -0
  63. package/dist/src/tools/query-app-message-tool.js +138 -0
  64. package/dist/src/tools/query-memory-data-tool.d.ts +4 -0
  65. package/dist/src/tools/query-memory-data-tool.js +154 -0
  66. package/dist/src/tools/query-todo-task-tool.d.ts +4 -0
  67. package/dist/src/tools/query-todo-task-tool.js +133 -0
  68. package/dist/src/tools/save-file-to-phone-tool.d.ts +5 -0
  69. package/dist/src/tools/save-file-to-phone-tool.js +166 -0
  70. package/dist/src/tools/save-media-to-gallery-tool.js +3 -7
  71. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +1 -0
  72. package/dist/src/tools/save-self-evolution-skill-tool.js +412 -0
  73. package/dist/src/tools/schema-tool-factory.d.ts +27 -0
  74. package/dist/src/tools/schema-tool-factory.js +32 -0
  75. package/dist/src/tools/search-alarm-tool.js +6 -13
  76. package/dist/src/tools/search-calendar-tool.js +2 -0
  77. package/dist/src/tools/search-email-tool.d.ts +5 -0
  78. package/dist/src/tools/search-email-tool.js +137 -0
  79. package/dist/src/tools/search-file-tool.js +4 -4
  80. package/dist/src/tools/search-message-tool.js +1 -0
  81. package/dist/src/tools/search-photo-gallery-tool.js +2 -2
  82. package/dist/src/tools/send-email-tool.d.ts +4 -0
  83. package/dist/src/tools/send-email-tool.js +134 -0
  84. package/dist/src/tools/send-file-to-user-tool.js +2 -4
  85. package/dist/src/tools/session-manager.js +2 -0
  86. package/dist/src/tools/upload-file-tool.js +4 -4
  87. package/dist/src/tools/upload-photo-tool.js +2 -2
  88. package/dist/src/tools/xiaoyi-add-collection-tool.js +35 -6
  89. package/dist/src/tools/xiaoyi-collection-tool.js +2 -1
  90. package/dist/src/tools/xiaoyi-delete-collection-tool.js +1 -1
  91. package/dist/src/utils/runtime-manager.js +24 -2
  92. package/dist/src/utils/self-evolution-manager.d.ts +5 -0
  93. package/dist/src/utils/self-evolution-manager.js +47 -0
  94. package/dist/src/utils/tool-call-nudge-manager.d.ts +16 -0
  95. package/dist/src/utils/tool-call-nudge-manager.js +47 -0
  96. package/dist/src/websocket.d.ts +3 -0
  97. package/dist/src/websocket.js +71 -0
  98. package/openclaw.plugin.json +1 -0
  99. package/package.json +2 -2
@@ -0,0 +1,141 @@
1
+ import { readFileSync, writeFileSync } from "fs";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ const XIAOYIRUNTIME_PATH = "/home/sandbox/.openclaw/.xiaoyiruntime";
4
+ export function handleSelfEvolutionEvent(context, runtime) {
5
+ const log = runtime?.log ?? console.log;
6
+ const error = runtime?.error ?? console.error;
7
+ try {
8
+ const state = context.event?.payload?.selfEvolutionState;
9
+ if (typeof state !== "string") {
10
+ error("[SELF_EVOLUTION] invalid payload: missing selfEvolutionState");
11
+ return;
12
+ }
13
+ log(`[SELF_EVOLUTION] received state: ${state}`);
14
+ let content;
15
+ try {
16
+ content = readFileSync(XIAOYIRUNTIME_PATH, "utf-8");
17
+ }
18
+ catch {
19
+ // File doesn't exist yet — create it
20
+ log(`[SELF_EVOLUTION] ${XIAOYIRUNTIME_PATH} not found, creating new file`);
21
+ writeFileSync(XIAOYIRUNTIME_PATH, `selfEvolutionState=${state}\n`, "utf-8");
22
+ log(`[SELF_EVOLUTION] wrote selfEvolutionState=${state}`);
23
+ return;
24
+ }
25
+ const lines = content.split("\n");
26
+ const key = "selfEvolutionState";
27
+ let found = false;
28
+ const updated = lines.map((line) => {
29
+ if (line.startsWith(`${key}=`)) {
30
+ found = true;
31
+ return `${key}=${state}`;
32
+ }
33
+ return line;
34
+ });
35
+ if (!found) {
36
+ // Ensure trailing newline before appending
37
+ const trimmed = content.trimEnd();
38
+ writeFileSync(XIAOYIRUNTIME_PATH, `${trimmed}\n${key}=${state}\n`, "utf-8");
39
+ }
40
+ else {
41
+ writeFileSync(XIAOYIRUNTIME_PATH, updated.join("\n"), "utf-8");
42
+ }
43
+ log(`[SELF_EVOLUTION] updated selfEvolutionState=${state} in ${XIAOYIRUNTIME_PATH}`);
44
+ }
45
+ catch (err) {
46
+ error("[SELF_EVOLUTION] failed to handle event:", err);
47
+ }
48
+ }
49
+ /**
50
+ * 读取 .xiaoyiruntime 中的 selfEvolutionState 并直接通过 wsManager 下发指令回复设备
51
+ * 参考trigger实现:直接使用当前已连接的 wsManager 发送消息,避免 getXYWebSocketManager 返回未连接实例
52
+ */
53
+ export async function handleSelfEvolutionStateGetEvent(context, cfg, runtime, wsManager) {
54
+ const log = runtime?.log ?? console.log;
55
+ const error = runtime?.error ?? console.error;
56
+ try {
57
+ const { sessionId, taskId } = context;
58
+ const messageId = context.messageId ?? uuidv4();
59
+ // 读取 selfEvolutionState
60
+ let state = "false";
61
+ try {
62
+ const content = readFileSync(XIAOYIRUNTIME_PATH, "utf-8");
63
+ for (const line of content.split("\n")) {
64
+ const trimmed = line.trim();
65
+ if (trimmed.startsWith("selfEvolutionState=")) {
66
+ state = trimmed.slice("selfEvolutionState=".length).trim();
67
+ break;
68
+ }
69
+ }
70
+ }
71
+ catch {
72
+ // 文件不存在,使用默认值 false
73
+ }
74
+ log(`[SELF_EVOLUTION_GET] read selfEvolutionState=${state}, sending command back`);
75
+ const command = {
76
+ header: {
77
+ namespace: "Common",
78
+ name: "Action",
79
+ },
80
+ payload: {
81
+ cardParam: {},
82
+ executeParam: {
83
+ executeMode: "background",
84
+ intentName: "ClawSelfEvolutionStateGet",
85
+ bundleName: "com.huawei.hmos.vassistant",
86
+ needUnlock: true,
87
+ actionResponse: true,
88
+ appType: "OHOS_APP",
89
+ timeOut: 5,
90
+ intentParam: {
91
+ selfEvolutionState: state,
92
+ },
93
+ permissionId: [],
94
+ achieveType: "INTENT",
95
+ },
96
+ responses: [{
97
+ resultCode: "",
98
+ displayText: "",
99
+ ttsText: "",
100
+ }],
101
+ needUploadResult: true,
102
+ noHalfPage: false,
103
+ pageControlRelated: false,
104
+ },
105
+ };
106
+ // 构造 artifact update 消息,直接通过当前 wsManager 发送
107
+ const jsonRpcResponse = {
108
+ jsonrpc: "2.0",
109
+ id: messageId,
110
+ result: {
111
+ taskId,
112
+ kind: "artifact-update",
113
+ append: false,
114
+ lastChunk: true,
115
+ final: false,
116
+ artifact: {
117
+ artifactId: uuidv4(),
118
+ parts: [{
119
+ kind: "data",
120
+ data: {
121
+ commands: [command],
122
+ },
123
+ }],
124
+ },
125
+ },
126
+ };
127
+ const outboundMessage = {
128
+ msgType: "agent_response",
129
+ agentId: cfg.agentId,
130
+ sessionId,
131
+ taskId,
132
+ msgDetail: JSON.stringify(jsonRpcResponse),
133
+ };
134
+ log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${taskId}`);
135
+ await wsManager.sendMessage(sessionId, outboundMessage);
136
+ log(`[SELF_EVOLUTION_GET] command sent successfully`);
137
+ }
138
+ catch (err) {
139
+ error("[SELF_EVOLUTION_GET] failed to handle event:", err);
140
+ }
141
+ }
@@ -0,0 +1,9 @@
1
+ export declare const SELF_EVOLUTION_KEYWORD_NUDGE_MESSAGE = "[\u7CFB\u7EDF\u6D88\u606F\uFF0C\u975E\u7528\u6237\u53D1\u8A00\uFF0C\u65E0\u9700\u4E3B\u52A8\u544A\u77E5\u7528\u6237] \u5F53\u524D\u7528\u6237\u8BF7\u6C42\u4E2D\u53EF\u80FD\u5305\u542B\u5E0C\u671B\u6C89\u6DC0\u7ECF\u9A8C\u3001\u957F\u671F\u8BB0\u5FC6\u3001\u907F\u514D\u91CD\u590D\u8E29\u5751\u3001\u7EA0\u9519\u7B49\u610F\u56FE\u3002\u5982\u679C\u4F60\u4ECE\u5F53\u524D\u4EFB\u52A1\u8FC7\u7A0B\u4E2D\u8BC6\u522B\u51FA\u53EF\u957F\u671F\u8BB0\u5FC6\u3001\u53EF\u590D\u7528\u7684\u5904\u7406\u89C4\u5219\u6216\u6392\u9519\u7ECF\u9A8C\u7B49\uFF0C\u53EF\u4EE5\u5728\u5B8C\u6210\u5F53\u524D\u4E3B\u8981\u4EFB\u52A1\u540E\uFF0C\u6B63\u5F0F\u56DE\u590D\u7528\u6237\u524D\uFF0C\u9605\u8BFBxiaoyi-self-evolution\u6280\u80FD\u4E86\u89E3\u81EA\u8FDB\u5316\u89C4\u5219\u3002\u6CE8\u610F\uFF0C\u4E0D\u8981\u6253\u65AD\u5F53\u524D\u4E3B\u4EFB\u52A1\uFF0C\u4EE5\u5B8C\u6210\u4EFB\u52A1\u4E3A\u6700\u9AD8\u4F18\u5148\u7EA7\u3002";
2
+ export declare const SELF_EVOLUTION_KEYWORD_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
3
+ export declare const SELF_EVOLUTION_MECHANISM_DISCUSSION_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
4
+ export declare function isSelfEvolutionMechanismDiscussion(text: string): boolean;
5
+ export declare function shouldNudgeForSelfEvolutionKeyword(text: string): boolean;
6
+ export declare function appendSelfEvolutionKeywordNudge(text: string): {
7
+ text: string;
8
+ appended: boolean;
9
+ };
@@ -0,0 +1,145 @@
1
+ export const SELF_EVOLUTION_KEYWORD_NUDGE_MESSAGE = "[系统消息,非用户发言,无需主动告知用户] 当前用户请求中可能包含希望沉淀经验、长期记忆、避免重复踩坑、纠错等意图。如果你从当前任务过程中识别出可长期记忆、可复用的处理规则或排错经验等,可以在完成当前主要任务后,正式回复用户前,阅读xiaoyi-self-evolution技能了解自进化规则。注意,不要打断当前主任务,以完成任务为最高优先级。";
2
+ export const SELF_EVOLUTION_KEYWORD_PATTERNS = [
3
+ /进化/u,
4
+ /沉淀/u,
5
+ /记住/u,
6
+ /记下来/u,
7
+ /记一个/u,
8
+ /记一下/u,
9
+ /给我记住/u,
10
+ /给我记下来/u,
11
+ /把(?:这个|这条|这点|这个要求|这个偏好)(?:记住|记下来|记录下来)/u,
12
+ /以后都/u,
13
+ /以后必须/u,
14
+ /以后统一/u,
15
+ /后面都/u,
16
+ /后续都/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,
32
+ /固定下来/u,
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,
55
+ /下次照这个来/u,
56
+ /后续照这个来/u,
57
+ /之后照这个来/u,
58
+ /以后就这么办/u,
59
+ /下次就这么办/u,
60
+ /以后就这样办/u,
61
+ /下次就这样办/u,
62
+ /以后沿用/u,
63
+ /下次沿用/u,
64
+ /后续沿用/u,
65
+ /后续按这个(?:规范|流程|模板|方案)/u,
66
+ /(?:以后|下次|后续|之后)(?:就)?(?:按|照|沿用|复用)(?:这个|这种|上述|前面这个|刚才这个)(?:规范|流程|模板|方案|格式|标准|做法|套路|模式)/u,
67
+ /(?:以后|下次|后续|之后)(?:回复|回答|输出|生成|整理|总结)(?:时)?(?:都|就|统一|默认|必须|要)(?:按|照|遵循|沿用|使用)(?:这个|这种|上述|当前)?(?:格式|模板|风格|口径|结构|标准)/u,
68
+ /(?:这个|这种|上述|当前)(?:格式|模板|风格|口径|结构|标准)(?:以后|下次|后续|之后)(?:都|就|统一|默认|复用|沿用)/u,
69
+ /以后(?:遇到|碰到)这种情况/u,
70
+ /类似(?:问题|情况|场景)都这样/u,
71
+ /类似(?:问题|情况|场景)都这样处理/u,
72
+ /类似(?:问题|情况|场景)(?:以后|下次|后续|之后)(?:都|就|统一|默认)/u,
73
+ /(?:同类|类似|这种|这类)(?:需求|任务|问题|情况|场景)(?:以后|下次|后续|之后)(?:都|就|统一|默认|按这个|照这个)/u,
74
+ /(?:这类|这种|类似|同类)(?:需求|任务|问题|场景)(?:处理|解决|回答|回复)(?:方式|流程|方法)(?:记住|固定|沉淀|沿用)/u,
75
+ /避免(?:再次|以后|下次)/u,
76
+ /避免再(?:犯错|踩坑|出错)/u,
77
+ /防止以后再犯/u,
78
+ /防止(?:以后|下次|后续|之后)(?:再)?(?:犯错|出错|踩坑|漏掉|遗漏|忘记)/u,
79
+ /别再(?:出错|犯错|踩坑|漏掉|忘记)/u,
80
+ /不要再(?:出错|犯错|踩坑|漏掉|忘记)/u,
81
+ /别再(?:这样|这么)(?:做|处理|回答|回复|输出|写|改)/u,
82
+ /不要再(?:这样|这么)(?:做|处理|回答|回复|输出|写|改)/u,
83
+ /以后别(?:这样|这么)(?:做|处理|回答|回复|输出|写|改)/u,
84
+ /以后不要(?:这样|这么)(?:做|处理|回答|回复|输出|写|改)/u,
85
+ /下次别再/u,
86
+ /以后不要再/u,
87
+ /以后别再/u,
88
+ /(?:下次|以后|后续|之后)(?:不要|别|不能|不许|禁止)(?:再)?(?:这样|这么)?(?:出错|犯错|踩坑|漏掉|遗漏|忘记)/u,
89
+ /(?:下次|以后|后续|之后)(?:不要|别|不能|不许|禁止)(?:再)?(?:省略|跳过|漏掉|遗漏)(?:检查|确认|验证|测试|构建|说明|引用|来源|步骤)/u,
90
+ /(?:下次|以后|后续|之后)(?:记得|一定要|务必|必须)(?:先|先去|优先|默认)?(?:检查|确认|使用|采用|调用|遵循|按照|参考|避免|不要|别|记住|保留|验证|测试|构建|运行)/u,
91
+ /(?:下次|以后|后续|之后)(?:先|优先|默认)(?:检查|确认|查找|搜索|读取|运行|验证|测试|构建|调用|使用)/u,
92
+ /这个坑(?:要)?记住/u,
93
+ /这(?:个|次)?(?:坑|错误|问题|教训)(?:别忘|不要忘|不能忘|得记住)/u,
94
+ /(?:踩坑|翻车|犯错|出错)(?:点|原因|教训)?(?:记住|记下来|沉淀|复盘)/u,
95
+ /吸取这次(?:教训|经验)/u,
96
+ /把(?:这个|这次|上述|刚才的)?(?:坑|问题|错误|教训|经验|做法|流程|规范|要求|偏好|格式|模板|标准)(?:记住|记下来|沉淀下来|固化下来|保存下来|记录下来)/u,
97
+ /(?:以后|下次|后续|之后)(?:遇到|碰到)(?:同类|类似|这种|这类)(?:需求|任务|问题|情况|场景)(?:时)?(?:都|就|统一|默认|应该|要|必须)/u,
98
+ /(?:以后|下次|后续|之后)(?:做|处理|执行)(?:同类|类似|这种|这类)(?:需求|任务|问题|情况|场景)(?:时)?(?:都|就|统一|默认|应该|要|必须)/u,
99
+ /(?:以后|下次|后续|之后)(?:都|统一|默认|应该|要)(?:按这个|这样|这么)(?:来|做|处理|执行)/u,
100
+ /(?:以后|下次|后续|之后)(?:都|统一|默认|应该|要|必须)(?:先|优先|总是|固定)?(?:使用|采用|走|遵循|参考|套用|复用|沿用)(?:这个|这种|上述|当前)?(?:方法|流程|规范|规则|模板|标准|方案|做法|模式|套路)/u,
101
+ /(?:以后|下次|后续|之后)(?:遇到|碰到)(?:类似)?(?:问题|情况|场景)(?:时)?(?:都|就)(?:按这个|这样|这么)(?:来|做|处理|执行)/u,
102
+ /(?:别再|不要再|避免)(?:犯错|出错|踩坑|漏掉|遗漏|忘记)/u,
103
+ /(?:总结|归纳|提炼|沉淀|复盘)(?:一个|一下|下)?(?:这次|这个|上述|刚才的)?(?:经验|教训|问题|规则|规范|流程|模板|标准|最佳实践)?/u,
104
+ /(?:把)?这次(?:经验|教训|规则|做法|流程|格式|模板|标准)(?:记住|记下来|沉淀下来|固化下来|记录下来)/u,
105
+ /(?:形成|整理成|沉淀成|提炼成)(?:一套|一个|一份)?(?:规则|规范|流程|步骤|模板|标准|最佳实践|操作手册|检查清单|checklist)/u,
106
+ /(?:作为|当作|用作)(?:以后|下次|后续|之后)(?:的)?(?:参考|模板|范例|案例|标准|最佳实践|默认做法)/u,
107
+ /(?:这次|这个|上述|刚才的)(?:处理方式|做法|流程|方案|模板|格式|标准|口径|风格)(?:以后|下次|后续|之后)(?:复用|沿用|照着来|照这个来|继续用)/u,
108
+ /(?:以后|下次|后续|之后)(?:工具|skill|技能|命令|脚本|流程)(?:选择|调用|使用)(?:都|就|统一|默认|优先|必须|要)/u,
109
+ /(?:以后|下次|后续|之后)(?:优先|默认|固定)(?:用|使用|调用)(?:这个|这种|上述|当前)?(?:工具|skill|技能|命令|脚本|流程|方法)/u,
110
+ /(?:这个|这种|上述|当前)(?:工具|skill|技能|命令|脚本|流程|方法)(?:以后|下次|后续|之后)(?:优先|默认|固定|继续)(?:用|使用|调用)/u,
111
+ ];
112
+ export const SELF_EVOLUTION_MECHANISM_DISCUSSION_PATTERNS = [
113
+ /自进化(?:机制|功能|流程|原理|实现|设计|架构|链路|优化点|改进点)/u,
114
+ /(?:分析|讨论|了解|解释|看看|研究|检查|梳理|优化|改进|评估)(?:.{0,12})自进化/u,
115
+ /自进化(?:.{0,12})(?:怎么|如何|是否|能否|有没有|为什么)/u,
116
+ /自进化(?:是啥|是什么|的)/u,
117
+ /什么是自进化/u,
118
+ /啥是自进化/u,
119
+ /xiaoyi-self-evolution(?:-skill)?(?:.{0,12})(?:机制|功能|流程|原理|实现|设计|优化点|改进点)/iu,
120
+ ];
121
+ export function isSelfEvolutionMechanismDiscussion(text) {
122
+ return SELF_EVOLUTION_MECHANISM_DISCUSSION_PATTERNS.some((pattern) => pattern.test(text));
123
+ }
124
+ export function shouldNudgeForSelfEvolutionKeyword(text) {
125
+ if (!text) {
126
+ return false;
127
+ }
128
+ if (isSelfEvolutionMechanismDiscussion(text)) {
129
+ return false;
130
+ }
131
+ return SELF_EVOLUTION_KEYWORD_PATTERNS.some((pattern) => pattern.test(text));
132
+ }
133
+ export function appendSelfEvolutionKeywordNudge(text) {
134
+ const trimmed = text.trim();
135
+ if (!trimmed) {
136
+ return { text, appended: false };
137
+ }
138
+ if (trimmed.includes(SELF_EVOLUTION_KEYWORD_NUDGE_MESSAGE)) {
139
+ return { text, appended: false };
140
+ }
141
+ return {
142
+ text: `${trimmed}\n\n${SELF_EVOLUTION_KEYWORD_NUDGE_MESSAGE}`,
143
+ appended: true,
144
+ };
145
+ }
@@ -0,0 +1,4 @@
1
+ import type { ToolRetrieverConfig } from "./types.js";
2
+ export interface NormalizedConfig extends ToolRetrieverConfig {
3
+ }
4
+ export declare function normalizeToolRetrieverConfig(raw?: unknown): NormalizedConfig;
@@ -0,0 +1,23 @@
1
+ const DEFAULT_CONFIG = {
2
+ enabled: true,
3
+ maxTools: 2,
4
+ includeUninstalledOnly: true,
5
+ envFilePath: "~/.openclaw/.xiaoyienv",
6
+ timeoutMs: 1000,
7
+ };
8
+ export function normalizeToolRetrieverConfig(raw) {
9
+ if (!raw || typeof raw !== "object") {
10
+ return { ...DEFAULT_CONFIG };
11
+ }
12
+ const cfg = raw;
13
+ return {
14
+ enabled: cfg.enabled ?? DEFAULT_CONFIG.enabled,
15
+ maxTools: Math.min(20, Math.max(1, cfg.maxTools ?? DEFAULT_CONFIG.maxTools)),
16
+ includeUninstalledOnly: cfg.includeUninstalledOnly ?? DEFAULT_CONFIG.includeUninstalledOnly,
17
+ envFilePath: cfg.envFilePath ?? DEFAULT_CONFIG.envFilePath,
18
+ serviceUrl: cfg.serviceUrl,
19
+ apiKey: cfg.apiKey,
20
+ uid: cfg.uid,
21
+ timeoutMs: cfg.timeoutMs ?? DEFAULT_CONFIG.timeoutMs,
22
+ };
23
+ }
@@ -0,0 +1,22 @@
1
+ interface PluginHookBeforePromptBuildEvent {
2
+ prompt: string;
3
+ messages: unknown[];
4
+ }
5
+ interface PluginHookBeforePromptBuildResult {
6
+ systemPrompt?: string;
7
+ prependContext?: string;
8
+ prependSystemContext?: string;
9
+ appendSystemContext?: string;
10
+ }
11
+ interface PluginHookAgentContext {
12
+ agentId?: string;
13
+ sessionKey?: string;
14
+ sessionId?: string;
15
+ workspaceDir?: string;
16
+ messageProvider?: string;
17
+ trigger?: string;
18
+ channelId?: string;
19
+ }
20
+ import type { ToolRetrieverConfig } from "./types.js";
21
+ export declare function createBeforePromptBuildHandler(config: ToolRetrieverConfig): (event: PluginHookBeforePromptBuildEvent, ctx?: PluginHookAgentContext) => Promise<PluginHookBeforePromptBuildResult | undefined>;
22
+ export {};
@@ -0,0 +1,91 @@
1
+ import { searchTools, formatToolsForContext, extractUserQuery } from "./tool-search.js";
2
+ const TOOL_RETRIEVER_HEADER = `## 用户查询相关skill列表如下:
3
+
4
+ `;
5
+ const TOOL_RETRIEVER_FOOTER = `
6
+ 以上是检索到的、与当前查询相关但用户尚未安装的skill,请按照以下规则判断是否需要推荐:
7
+ 1.判断用户当前请求的意图类型:
8
+ -若用户请求为查询已有信息、查看状态、执行已有功能或进行常规操作(例如查看定时任务列表),且这些操作无需额外skill即可完成,则不推荐任何skill。
9
+ 2.仅在以下条件全部满足时,才考虑推荐skill:
10
+ -用户请求明确表示需要完成某个具体任务;
11
+ -现有能力(包括已安装的skill或系统自带功能)不足以满足该任务。此时,优先推荐与任务强相关的skill。
12
+ 3.对于强相关且用户尚未安装的skill:
13
+ -可主动提醒用户考虑安装,但禁止直接安装;
14
+ -用户明确同意后,使用find-skills执行安装。
15
+ 4.若用户已安装的skill已能很好地完成当前任务,即使存在功能相似的未安装skill,也无需提醒。
16
+ ---以下是用户原始请求---
17
+ `;
18
+ const PLUGIN_LOG_PREFIX = "[skill-retriever]";
19
+ const SKIP_KEYWORDS = ["安装", "装一下", "下载", "查询", "查找", "install", "卸载", "删除", "重载", "定时任务", "重装"];
20
+ const SKIP_PATTERNS = [
21
+ "/new", "/reset", "/compact", "/stop", "/think", "/model", "/fast", "/verbose", "/config", "/debug", "/status", "/tasks", "/whoami", "/context", "/skill", "/commands", "/tools"
22
+ ];
23
+ function shouldSkipSearch(prompt) {
24
+ const trimmedPrompt = prompt.trim();
25
+ if (trimmedPrompt.startsWith("/")) {
26
+ return "query starts with / (built-in command)";
27
+ }
28
+ const lowerPrompt = trimmedPrompt.toLowerCase();
29
+ for (const keyword of SKIP_KEYWORDS) {
30
+ if (lowerPrompt.includes(keyword.toLowerCase())) {
31
+ return `query contains keyword: ${keyword}`;
32
+ }
33
+ }
34
+ for (const pattern of SKIP_PATTERNS) {
35
+ if (lowerPrompt.includes(pattern.toLowerCase())) {
36
+ return `query matches pattern: ${pattern}`;
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+ export function createBeforePromptBuildHandler(config) {
42
+ return async (event, ctx) => {
43
+ const userPrompt = event.prompt;
44
+ if (ctx?.sessionKey?.includes(":subagent:")) {
45
+ return undefined;
46
+ }
47
+ if (!config.enabled) {
48
+ return undefined;
49
+ }
50
+ if (!userPrompt || userPrompt.trim().length === 0) {
51
+ return undefined;
52
+ }
53
+ const extractedQuery = extractUserQuery(userPrompt);
54
+ if (!extractedQuery || extractedQuery.length === 0) {
55
+ return undefined;
56
+ }
57
+ const skipReason = shouldSkipSearch(extractedQuery);
58
+ if (skipReason) {
59
+ return undefined;
60
+ }
61
+ try {
62
+ const searchResult = await searchTools({
63
+ query: extractedQuery,
64
+ maxTools: config.maxTools,
65
+ includeUninstalledOnly: config.includeUninstalledOnly,
66
+ envFilePath: config.envFilePath,
67
+ serviceUrl: config.serviceUrl,
68
+ apiKey: config.apiKey,
69
+ uid: config.uid,
70
+ timeoutMs: config.timeoutMs,
71
+ });
72
+ if (!searchResult || searchResult.tools.length === 0) {
73
+ return undefined;
74
+ }
75
+ console.log(`${PLUGIN_LOG_PREFIX} [RESULT] Found ${searchResult.tools.length} skills, building context...`);
76
+ const toolsContext = formatToolsForContext(searchResult, config.includeUninstalledOnly);
77
+ if (!toolsContext) {
78
+ console.log(`${PLUGIN_LOG_PREFIX} [ERROR] Failed to format skills context`);
79
+ return undefined;
80
+ }
81
+ return {
82
+ prependContext: TOOL_RETRIEVER_HEADER + toolsContext + TOOL_RETRIEVER_FOOTER,
83
+ };
84
+ }
85
+ catch (error) {
86
+ const errorMessage = error instanceof Error ? error.message : String(error);
87
+ console.error(`${PLUGIN_LOG_PREFIX} [ERROR] ${errorMessage}, original query: "${extractedQuery}"`);
88
+ return undefined;
89
+ }
90
+ };
91
+ }
@@ -0,0 +1,16 @@
1
+ import type { EnvConfig, ToolSearchResult } from "./types.js";
2
+ export declare function extractUserQuery(fullPrompt: string): string;
3
+ export declare function readEnvFile(filePath: string): EnvConfig;
4
+ export declare function getInstalledSkills(): string[];
5
+ export interface SearchToolsOptions {
6
+ query: string;
7
+ maxTools?: number;
8
+ includeUninstalledOnly?: boolean;
9
+ envFilePath?: string;
10
+ serviceUrl?: string;
11
+ apiKey?: string;
12
+ uid?: string;
13
+ timeoutMs?: number;
14
+ }
15
+ export declare function searchTools(options: SearchToolsOptions): Promise<ToolSearchResult | null>;
16
+ export declare function formatToolsForContext(result: ToolSearchResult, includeInstallUrl?: boolean): string;
@@ -0,0 +1,159 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ const SKILL_ID = "celia_find_skills";
5
+ const PLUGIN_LOG_PREFIX = "[skill-retriever]";
6
+ export function extractUserQuery(fullPrompt) {
7
+ const lastNewlineIndex = fullPrompt.lastIndexOf("\n");
8
+ if (lastNewlineIndex === -1) {
9
+ return fullPrompt.trim();
10
+ }
11
+ const afterLastNewline = fullPrompt.slice(lastNewlineIndex + 1).trim();
12
+ if (!afterLastNewline || afterLastNewline === "```") {
13
+ return "";
14
+ }
15
+ return afterLastNewline;
16
+ }
17
+ function expandPath(filePath) {
18
+ if (filePath.startsWith("~")) {
19
+ return path.join(os.homedir(), filePath.slice(1).replace(/^\/+/, ""));
20
+ }
21
+ return filePath;
22
+ }
23
+ export function readEnvFile(filePath) {
24
+ const expandedPath = expandPath(filePath);
25
+ const envDict = {};
26
+ try {
27
+ const content = fs.readFileSync(expandedPath, "utf-8");
28
+ for (const line of content.split("\n")) {
29
+ const trimmed = line.trim();
30
+ if (!trimmed || trimmed.startsWith("#")) {
31
+ continue;
32
+ }
33
+ const eqIndex = trimmed.indexOf("=");
34
+ if (eqIndex > 0) {
35
+ let key = trimmed.substring(0, eqIndex).trim();
36
+ const value = trimmed.substring(eqIndex + 1).trim();
37
+ key = key.replace(/-/g, "_");
38
+ envDict[key] = value;
39
+ }
40
+ }
41
+ }
42
+ catch {
43
+ // File not found or read error - return empty config
44
+ }
45
+ return envDict;
46
+ }
47
+ export function getInstalledSkills() {
48
+ const skillsDir = expandPath("~/.openclaw/workspace/skills");
49
+ const installedSkills = [];
50
+ try {
51
+ if (fs.existsSync(skillsDir) && fs.statSync(skillsDir).isDirectory()) {
52
+ const entries = fs.readdirSync(skillsDir);
53
+ for (const entry of entries) {
54
+ const entryPath = path.join(skillsDir, entry);
55
+ if (fs.statSync(entryPath).isDirectory()) {
56
+ installedSkills.push(entry);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ catch {
62
+ // Directory doesn't exist or read error - return empty list
63
+ }
64
+ return installedSkills;
65
+ }
66
+ function formatSkillData(rawSkills, installedSkills) {
67
+ const formattedSkills = [];
68
+ for (const skill of rawSkills) {
69
+ const isInstalled = installedSkills.includes(skill.skillId);
70
+ formattedSkills.push({
71
+ skillId: skill.skillId,
72
+ skillName: skill.skillName,
73
+ skillDesc: skill.skillDesc,
74
+ downloadPath: skill.packUrl,
75
+ status: isInstalled ? "已安装" : "未安装",
76
+ });
77
+ }
78
+ return formattedSkills;
79
+ }
80
+ export async function searchTools(options) {
81
+ const { query, maxTools = 5, includeUninstalledOnly = true, envFilePath = "~/.openclaw/.xiaoyienv", serviceUrl: configServiceUrl, apiKey: configApiKey, uid: configUid, timeoutMs = 1000, } = options;
82
+ const envConfig = readEnvFile(envFilePath);
83
+ const hasRequiredConfig = !!envConfig.SERVICE_URL && !!envConfig.PERSONAL_API_KEY && !!envConfig.PERSONAL_UID;
84
+ const serviceUrl = configServiceUrl ?? envConfig.SERVICE_URL;
85
+ const apiKey = configApiKey ?? envConfig.PERSONAL_API_KEY;
86
+ const uid = configUid ?? envConfig.PERSONAL_UID;
87
+ if (!serviceUrl || !apiKey || !uid) {
88
+ console.warn(`${PLUGIN_LOG_PREFIX} Missing required configuration. serviceUrl: "${serviceUrl}", apiKey: "${apiKey ? '(set)' : '(missing)'} ", uid: "${uid ? '(set)' : '(missing)'}"`);
89
+ return null;
90
+ }
91
+ const traceId = crypto.randomUUID();
92
+ const apiUrl = `${serviceUrl}/celia-claw/v1/rest-api/skill/execute`;
93
+ const headers = {
94
+ "Content-Type": "application/json",
95
+ "x-skill-id": SKILL_ID,
96
+ "x-hag-trace-id": traceId,
97
+ "x-uid": uid,
98
+ "x-api-key": apiKey,
99
+ "x-request-from": "openclaw",
100
+ };
101
+ const payload = { query };
102
+ try {
103
+ const response = await fetch(apiUrl, {
104
+ method: "POST",
105
+ headers,
106
+ body: JSON.stringify(payload),
107
+ signal: AbortSignal.timeout(timeoutMs),
108
+ });
109
+ if (!response.ok) {
110
+ console.warn(`${PLUGIN_LOG_PREFIX} HTTP error: ${response.status} ${response.statusText}`);
111
+ return null;
112
+ }
113
+ console.log(`${PLUGIN_LOG_PREFIX} Received response, status: ${response.status}`);
114
+ const responseData = await response.json();
115
+ if (responseData.errorCode === "0" &&
116
+ responseData.content &&
117
+ responseData.content.skills) {
118
+ const rawSkills = responseData.content.skills;
119
+ const installedSkills = getInstalledSkills();
120
+ const formattedData = formatSkillData(rawSkills, installedSkills);
121
+ const topTools = formattedData.slice(0, 2);
122
+ const allInstalled = topTools.every((tool) => tool.status === "已安装");
123
+ if (allInstalled) {
124
+ console.log(`${PLUGIN_LOG_PREFIX} [DEBUG] All top 2 skills are installed, returning null`);
125
+ return null;
126
+ }
127
+ let filteredTools = topTools.filter((tool) => tool.status === "未安装");
128
+ console.log(`${PLUGIN_LOG_PREFIX} [DEBUG] After filtering uninstalled: ${filteredTools.length}, ids: ${filteredTools.map((t) => t.skillId).join(", ")}`);
129
+ return {
130
+ tools: filteredTools,
131
+ query,
132
+ timestamp: Date.now(),
133
+ };
134
+ }
135
+ console.warn(`${PLUGIN_LOG_PREFIX} Invalid response format: ${JSON.stringify(responseData).slice(0, 200)}`);
136
+ return null;
137
+ }
138
+ catch (error) {
139
+ const errorName = error instanceof Error ? error.name : "Unknown";
140
+ const errorMessage = error instanceof Error ? error.message : String(error);
141
+ const errorCause = error instanceof Error && error.cause ? JSON.stringify(error.cause) : "N/A";
142
+ const errorStack = error instanceof Error ? error.stack?.split("\n").slice(0, 3).join(" | ") : "N/A";
143
+ console.warn(`${PLUGIN_LOG_PREFIX} [ERROR] Fetch failed - name: ${errorName}, message: ${errorMessage}, cause: ${errorCause}, stack: ${errorStack}`);
144
+ return null;
145
+ }
146
+ }
147
+ export function formatToolsForContext(result, includeInstallUrl = true) {
148
+ if (!result.tools || result.tools.length === 0) {
149
+ return "";
150
+ }
151
+ const toolDescriptions = [];
152
+ for (const tool of result.tools) {
153
+ let description = `### ${tool.skillName}\n`;
154
+ description += `name: ${tool.skillId}\n`;
155
+ description += `description: ${tool.skillDesc}\n`;
156
+ toolDescriptions.push(description);
157
+ }
158
+ return toolDescriptions.join("\n\n");
159
+ }
@@ -0,0 +1,34 @@
1
+ export interface ToolRetrieverConfig {
2
+ enabled: boolean;
3
+ maxTools: number;
4
+ includeUninstalledOnly: boolean;
5
+ envFilePath: string;
6
+ serviceUrl?: string;
7
+ apiKey?: string;
8
+ uid?: string;
9
+ timeoutMs?: number;
10
+ }
11
+ export interface RawSkill {
12
+ skillId: string;
13
+ skillName: string;
14
+ skillDesc: string;
15
+ packUrl: string;
16
+ }
17
+ export interface FormattedSkill {
18
+ skillId: string;
19
+ skillName: string;
20
+ skillDesc: string;
21
+ downloadPath: string;
22
+ status: "已安装" | "未安装";
23
+ }
24
+ export interface ToolSearchResult {
25
+ tools: FormattedSkill[];
26
+ query: string;
27
+ timestamp: number;
28
+ }
29
+ export interface EnvConfig {
30
+ PERSONAL_API_KEY?: string;
31
+ PERSONAL_UID?: string;
32
+ SERVICE_URL?: string;
33
+ [key: string]: string | undefined;
34
+ }
@@ -0,0 +1 @@
1
+ export {};