@ynhcj/xiaoyi-channel 1.1.19 → 1.1.20

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 (38) hide show
  1. package/dist/index.d.ts +0 -5
  2. package/dist/index.js +107 -10
  3. package/dist/src/channel.js +2 -1
  4. package/dist/src/login-token-handler.d.ts +8 -0
  5. package/dist/src/login-token-handler.js +60 -0
  6. package/dist/src/monitor.js +14 -0
  7. package/dist/src/provider.js +320 -27
  8. package/dist/src/self-evolution-handler.d.ts +1 -0
  9. package/dist/src/self-evolution-handler.js +47 -0
  10. package/dist/src/skill-retriever/config.d.ts +4 -0
  11. package/dist/src/skill-retriever/config.js +23 -0
  12. package/dist/src/skill-retriever/hooks.d.ts +22 -0
  13. package/dist/src/skill-retriever/hooks.js +91 -0
  14. package/dist/src/skill-retriever/tool-search.d.ts +16 -0
  15. package/dist/src/skill-retriever/tool-search.js +159 -0
  16. package/dist/src/skill-retriever/types.d.ts +34 -0
  17. package/dist/src/skill-retriever/types.js +1 -0
  18. package/dist/src/tools/call-device-tool.js +4 -0
  19. package/dist/src/tools/get-email-tool-schema.d.ts +16 -0
  20. package/dist/src/tools/get-email-tool-schema.js +9 -0
  21. package/dist/src/tools/login-token-tool.d.ts +5 -0
  22. package/dist/src/tools/login-token-tool.js +136 -0
  23. package/dist/src/tools/query-app-message-tool.d.ts +4 -0
  24. package/dist/src/tools/query-app-message-tool.js +138 -0
  25. package/dist/src/tools/query-memory-data-tool.d.ts +4 -0
  26. package/dist/src/tools/query-memory-data-tool.js +154 -0
  27. package/dist/src/tools/query-todo-task-tool.d.ts +4 -0
  28. package/dist/src/tools/query-todo-task-tool.js +133 -0
  29. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +1 -0
  30. package/dist/src/tools/save-self-evolution-skill-tool.js +412 -0
  31. package/dist/src/tools/session-manager.js +2 -0
  32. package/dist/src/utils/runtime-manager.js +24 -2
  33. package/dist/src/utils/self-evolution-manager.d.ts +5 -0
  34. package/dist/src/utils/self-evolution-manager.js +47 -0
  35. package/dist/src/utils/tool-call-nudge-manager.d.ts +16 -0
  36. package/dist/src/utils/tool-call-nudge-manager.js +47 -0
  37. package/dist/src/websocket.js +18 -0
  38. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -1,9 +1,4 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- /**
3
- * Xiaoyi Channel Plugin Entry Point.
4
- * Exports the plugin for OpenClaw to load.
5
- * Located at root level following feishu pattern for proper plugin registration.
6
- */
7
2
  declare const plugin: {
8
3
  id: string;
9
4
  name: string;
package/dist/index.js CHANGED
@@ -4,13 +4,76 @@ import { xiaoyiProvider } from "./src/provider.js";
4
4
  import { setXYRuntime } from "./src/runtime.js";
5
5
  import { tryInjectSteer } from "./src/steer-injector.js";
6
6
  import { callCsplApi } from "./src/cspl/call-api.js";
7
- import { extractResultText, processText, parseSecurityResult, validateAndTruncateText } from "./src/cspl/utils.js";
7
+ import { extractResultText, processText, parseSecurityResult, validateAndTruncateText, } from "./src/cspl/utils.js";
8
+ import { selfEvolutionManager } from "./src/utils/self-evolution-manager.js";
9
+ import { TOOL_CALL_NUDGE_THRESHOLD, toolCallNudgeManager, } from "./src/utils/tool-call-nudge-manager.js";
8
10
  import { ALLOWED_TOOLS, MIN_TEXT_LENGTH, MAX_TOTAL_LENGTH, MAX_TEXT_LENGTH, STEER_ABORT_MESSAGE, } from "./src/cspl/constants.js";
9
- /**
10
- * Xiaoyi Channel Plugin Entry Point.
11
- * Exports the plugin for OpenClaw to load.
12
- * Located at root level following feishu pattern for proper plugin registration.
13
- */
11
+ const SELF_EVOLUTION_NUDGE_MESSAGE = "[系统消息,非用户发言,无需主动告知用户] 当前任务已经调用了较多次数的工具。如果你识别到其中存在可复用的解决方法或踩坑经验等,可以在完成当前主要任务后,正式回复用户前,阅读xiaoyi-self-evolution技能了解自进化规则。注意,不要打断当前主任务,以完成任务为最高优先级。";
12
+ const SELF_EVOLUTION_KEYWORD_NUDGE_MESSAGE = "[系统消息,非用户发言,无需主动告知用户] 当前用户请求中可能包含用户长期偏好、行为纠正意图、经验沉淀需求。如果你已经从当前问题中识别出稳定、可复用的处理规则或排错经验,或者用户表达了明显的具备长期约束力的纠错与反馈,可以在完成当前主要任务后,正式回复用户前,阅读xiaoyi-self-evolution技能了解自进化规则。注意,不要打断当前主任务,以完成任务为最高优先级。";
13
+ const SELF_EVOLUTION_KEYWORD_PATTERNS = [
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
+ ];
56
+ function shouldCountToolCall(toolName) {
57
+ if (toolName === "save_self_evolution_skill") {
58
+ return false;
59
+ }
60
+ if (toolName === "call_device_tool") {
61
+ return false;
62
+ }
63
+ if (toolName.endsWith("_tool_schema")) {
64
+ return false;
65
+ }
66
+ return true;
67
+ }
68
+ function getUserMessageForKeywordDetection(event) {
69
+ return event.body?.trim() || event.content.trim();
70
+ }
71
+ function shouldNudgeForSelfEvolutionKeyword(text) {
72
+ if (!text) {
73
+ return false;
74
+ }
75
+ return SELF_EVOLUTION_KEYWORD_PATTERNS.some((pattern) => pattern.test(text));
76
+ }
14
77
  const plugin = {
15
78
  id: "xiaoyi-channel",
16
79
  name: "Xiaoyi Channel",
@@ -20,9 +83,44 @@ const plugin = {
20
83
  setXYRuntime(api.runtime);
21
84
  api.registerChannel({ plugin: xyPlugin });
22
85
  api.registerProvider(xiaoyiProvider);
23
- // SENTINEL HOOK after_tool_call hook: 监听工具结果,发送至安全检测 API 进行安全检测
24
- // 如果响应为 REJECT,注入 steer 消息中止当前对话
86
+ api.on("before_dispatch", async (event, ctx) => {
87
+ const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
88
+ if (!ctx.sessionKey || !selfEvolutionEnabled) {
89
+ return;
90
+ }
91
+ const userText = getUserMessageForKeywordDetection(event);
92
+ if (!shouldNudgeForSelfEvolutionKeyword(userText)) {
93
+ return;
94
+ }
95
+ try {
96
+ const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(ctx.sessionKey);
97
+ api.logger.debug?.(`[SELF_EVOLUTION] Keyword check hit: sessionKey=${ctx.sessionKey}, shouldNudge=${shouldNudge}`);
98
+ if (shouldNudge) {
99
+ api.logger.info?.(`[SELF_EVOLUTION] Keyword-triggered nudge injected: sessionKey=${ctx.sessionKey}`);
100
+ await tryInjectSteer(ctx.sessionKey, SELF_EVOLUTION_KEYWORD_NUDGE_MESSAGE);
101
+ }
102
+ }
103
+ catch (err) {
104
+ api.logger.error(`[SELF_EVOLUTION] before_dispatch keyword nudge error: ${err}`);
105
+ }
106
+ });
25
107
  api.on("after_tool_call", async (event, ctx) => {
108
+ const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
109
+ if (ctx.sessionKey &&
110
+ selfEvolutionEnabled &&
111
+ shouldCountToolCall(event.toolName)) {
112
+ try {
113
+ const { count, shouldNudge } = toolCallNudgeManager.recordToolCall(ctx.sessionKey);
114
+ api.logger.debug?.(`[SELF_EVOLUTION] Tool call counted: tool=${event.toolName}, count=${count}, threshold=${TOOL_CALL_NUDGE_THRESHOLD}, sessionKey=${ctx.sessionKey}`);
115
+ if (shouldNudge) {
116
+ api.logger.info?.(`[SELF_EVOLUTION] Tool call threshold reached, injecting nudge: count=${count}, sessionKey=${ctx.sessionKey}`);
117
+ await tryInjectSteer(ctx.sessionKey, SELF_EVOLUTION_NUDGE_MESSAGE);
118
+ }
119
+ }
120
+ catch (err) {
121
+ api.logger.error(`[SELF_EVOLUTION] after_tool_call nudge error: ${err}`);
122
+ }
123
+ }
26
124
  if (!ALLOWED_TOOLS.includes(event.toolName)) {
27
125
  return;
28
126
  }
@@ -33,9 +131,8 @@ const plugin = {
33
131
  if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
34
132
  return;
35
133
  }
36
- // 构造 sentinel_hook 格式的 payload: { tool, output: [{ content }] }
37
134
  const questionText = {
38
- subSceneID: 'TOOL_OUTPUT',
135
+ subSceneID: "TOOL_OUTPUT",
39
136
  tool: event.toolName,
40
137
  output: [{ content: "" }],
41
138
  };
@@ -7,6 +7,7 @@ import { sendFileToUserTool } from "./tools/send-file-to-user-tool.js";
7
7
  import { viewPushResultTool } from "./tools/view-push-result-tool.js";
8
8
  import { imageReadingTool } from "./tools/image-reading-tool.js";
9
9
  import { timestampToUtc8Tool } from "./tools/timestamp-to-utc8-tool.js";
10
+ import { saveSelfEvolutionSkillTool } from "./tools/save-self-evolution-skill-tool.js";
10
11
  import { callDeviceTool } from "./tools/call-device-tool.js";
11
12
  import { getNoteToolSchemaTool } from "./tools/get-note-tool-schema.js";
12
13
  import { getCalendarToolSchemaTool } from "./tools/get-calendar-tool-schema.js";
@@ -57,7 +58,7 @@ export const xyPlugin = {
57
58
  },
58
59
  outbound: xyOutbound,
59
60
  agentTools: () => {
60
- const allTools = [locationTool, callDeviceTool, getNoteToolSchemaTool, getCalendarToolSchemaTool, getContactToolSchemaTool, getPhotoToolSchemaTool, xiaoyiGuiTool, getDeviceFileToolSchemaTool, getAlarmToolSchemaTool, getCollectionToolSchemaTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool];
61
+ const allTools = [locationTool, callDeviceTool, getNoteToolSchemaTool, getCalendarToolSchemaTool, getContactToolSchemaTool, getPhotoToolSchemaTool, xiaoyiGuiTool, getDeviceFileToolSchemaTool, getAlarmToolSchemaTool, getCollectionToolSchemaTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, saveSelfEvolutionSkillTool];
61
62
  const ctx = getCurrentSessionContext();
62
63
  const filtered = filterToolsByDevice(allTools, ctx?.deviceType);
63
64
  logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 处理 LoginTokenEvent.ClawAutoLogin 事件
3
+ * 将 clientId 和当前时间戳写入 .xiaoyitoken.json 文件
4
+ *
5
+ * @param context - 事件上下文,包含 event 对象
6
+ * @param runtime - 运行时环境
7
+ */
8
+ export declare function handleLoginTokenEvent(context: any, runtime: any): void;
@@ -0,0 +1,60 @@
1
+ // Login Token 事件处理器
2
+ // 监听 LoginTokenEvent.ClawAutoLogin 事件,将 clientId 写入 .xiaoyitoken.json
3
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
4
+ import { dirname } from "path";
5
+ const TOKEN_FILE_PATH = "/home/sandbox/.openclaw/.xiaoyitoken.json";
6
+ /**
7
+ * 处理 LoginTokenEvent.ClawAutoLogin 事件
8
+ * 将 clientId 和当前时间戳写入 .xiaoyitoken.json 文件
9
+ *
10
+ * @param context - 事件上下文,包含 event 对象
11
+ * @param runtime - 运行时环境
12
+ */
13
+ export function handleLoginTokenEvent(context, runtime) {
14
+ const log = runtime?.log ?? console.log;
15
+ const error = runtime?.error ?? console.error;
16
+ try {
17
+ const clientId = context.event?.payload?.clientId;
18
+ if (!clientId || typeof clientId !== "string") {
19
+ error("[LOGIN_TOKEN_HANDLER] invalid payload: missing clientId");
20
+ return;
21
+ }
22
+ log(`[LOGIN_TOKEN_HANDLER] received login token event, clientId=${clientId}`);
23
+ // Ensure directory exists
24
+ const dir = dirname(TOKEN_FILE_PATH);
25
+ if (!existsSync(dir)) {
26
+ mkdirSync(dir, { recursive: true });
27
+ }
28
+ let tokens = [];
29
+ if (existsSync(TOKEN_FILE_PATH)) {
30
+ try {
31
+ const content = readFileSync(TOKEN_FILE_PATH, "utf-8");
32
+ tokens = JSON.parse(content);
33
+ if (!Array.isArray(tokens)) {
34
+ tokens = [];
35
+ }
36
+ }
37
+ catch {
38
+ tokens = [];
39
+ }
40
+ }
41
+ // Check if clientId already exists
42
+ const now = String(Date.now());
43
+ const existing = tokens.find((t) => t.clientId === clientId);
44
+ if (existing) {
45
+ // Update timestamp
46
+ existing.timestamp = now;
47
+ log(`[LOGIN_TOKEN_HANDLER] updated timestamp for clientId=${clientId}`);
48
+ }
49
+ else {
50
+ // Insert new entry
51
+ tokens.push({ clientId, timestamp: now });
52
+ log(`[LOGIN_TOKEN_HANDLER] inserted new entry for clientId=${clientId}`);
53
+ }
54
+ writeFileSync(TOKEN_FILE_PATH, JSON.stringify(tokens, null, 2), "utf-8");
55
+ log(`[LOGIN_TOKEN_HANDLER] wrote token file: ${TOKEN_FILE_PATH}`);
56
+ }
57
+ catch (err) {
58
+ error("[LOGIN_TOKEN_HANDLER] failed to handle event:", err);
59
+ }
60
+ }
@@ -4,6 +4,8 @@ import { handleXYMessage } from "./bot.js";
4
4
  import { parseA2AMessage } from "./parser.js";
5
5
  import { hasActiveTask } from "./task-manager.js";
6
6
  import { handleTriggerEvent } from "./trigger-handler.js";
7
+ import { handleSelfEvolutionEvent } from "./self-evolution-handler.js";
8
+ import { handleLoginTokenEvent } from "./login-token-handler.js";
7
9
  import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
8
10
  /**
9
11
  * Per-session serial queue that ensures messages from the same session are processed
@@ -156,6 +158,14 @@ export async function monitorXYProvider(opts = {}) {
156
158
  error(`[MONITOR] Failed to handle trigger-event:`, err);
157
159
  });
158
160
  };
161
+ const selfEvolutionHandler = (context) => {
162
+ log(`[MONITOR] Received self-evolution-event, dispatching to handler...`);
163
+ handleSelfEvolutionEvent(context, runtime);
164
+ };
165
+ const loginTokenEventHandler = (context) => {
166
+ log(`[MONITOR] Received login-token-event, dispatching to handler...`);
167
+ handleLoginTokenEvent(context, runtime);
168
+ };
159
169
  const cleanup = () => {
160
170
  log("XY gateway: cleaning up...");
161
171
  // 🔍 Diagnose before cleanup
@@ -173,6 +183,8 @@ export async function monitorXYProvider(opts = {}) {
173
183
  wsManager.off("disconnected", disconnectedHandler);
174
184
  wsManager.off("error", errorHandler);
175
185
  wsManager.off("trigger-event", triggerEventHandler);
186
+ wsManager.off("self-evolution-event", selfEvolutionHandler);
187
+ wsManager.off("login-token-event", loginTokenEventHandler);
176
188
  // ✅ Disconnect the wsManager to prevent connection leaks
177
189
  // This is safe because each gateway lifecycle should have clean connections
178
190
  wsManager.disconnect();
@@ -203,6 +215,8 @@ export async function monitorXYProvider(opts = {}) {
203
215
  wsManager.on("disconnected", disconnectedHandler);
204
216
  wsManager.on("error", errorHandler);
205
217
  wsManager.on("trigger-event", triggerEventHandler);
218
+ wsManager.on("self-evolution-event", selfEvolutionHandler);
219
+ wsManager.on("login-token-event", loginTokenEventHandler);
206
220
  // Start periodic health check (every 6 hours)
207
221
  console.log("🏥 Starting periodic health check (every 6 hours)...");
208
222
  healthCheckInterval = setInterval(() => {