@ynhcj/xiaoyi-channel 0.0.88-beta → 0.0.88-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 (131) hide show
  1. package/dist/index.d.ts +6 -9
  2. package/dist/index.js +26 -21
  3. package/dist/src/bot.js +26 -3
  4. package/dist/src/channel.js +2 -30
  5. package/dist/src/client.js +7 -1
  6. package/dist/src/login-token-handler.d.ts +8 -0
  7. package/dist/src/login-token-handler.js +60 -0
  8. package/dist/src/message-queue.d.ts +17 -0
  9. package/dist/src/message-queue.js +51 -0
  10. package/dist/src/monitor.js +64 -4
  11. package/dist/src/provider.d.ts +2 -1
  12. package/dist/src/provider.js +440 -31
  13. package/dist/src/reply-dispatcher.js +6 -0
  14. package/dist/src/runtime.d.ts +3 -11
  15. package/dist/src/runtime.js +6 -18
  16. package/dist/src/self-evolution-handler.d.ts +7 -0
  17. package/dist/src/self-evolution-handler.js +140 -0
  18. package/dist/src/self-evolution-keyword.d.ts +9 -0
  19. package/dist/src/self-evolution-keyword.js +147 -0
  20. package/dist/src/self-evolution-tool-result-nudge.d.ts +3 -0
  21. package/dist/src/self-evolution-tool-result-nudge.js +96 -0
  22. package/dist/src/skill-retriever/config.d.ts +4 -0
  23. package/dist/src/skill-retriever/config.js +23 -0
  24. package/dist/src/skill-retriever/hooks.d.ts +22 -0
  25. package/dist/src/skill-retriever/hooks.js +82 -0
  26. package/dist/src/skill-retriever/tool-search.d.ts +16 -0
  27. package/dist/src/skill-retriever/tool-search.js +172 -0
  28. package/dist/src/skill-retriever/types.d.ts +36 -0
  29. package/dist/src/skill-retriever/types.js +1 -0
  30. package/dist/src/task-manager.d.ts +4 -0
  31. package/dist/src/task-manager.js +12 -1
  32. package/dist/src/tools/calendar-tool.d.ts +2 -1
  33. package/dist/src/tools/calendar-tool.js +112 -116
  34. package/dist/src/tools/call-device-tool.d.ts +6 -0
  35. package/dist/src/tools/call-device-tool.js +153 -0
  36. package/dist/src/tools/call-phone-tool.d.ts +2 -1
  37. package/dist/src/tools/call-phone-tool.js +109 -113
  38. package/dist/src/tools/create-alarm-tool.d.ts +2 -1
  39. package/dist/src/tools/create-alarm-tool.js +227 -231
  40. package/dist/src/tools/create-all-tools.d.ts +16 -0
  41. package/dist/src/tools/create-all-tools.js +50 -0
  42. package/dist/src/tools/delete-alarm-tool.d.ts +2 -1
  43. package/dist/src/tools/delete-alarm-tool.js +131 -135
  44. package/dist/src/tools/device-tool-map.js +2 -0
  45. package/dist/src/tools/get-alarm-tool-schema.d.ts +17 -0
  46. package/dist/src/tools/get-alarm-tool-schema.js +17 -0
  47. package/dist/src/tools/get-calendar-tool-schema.d.ts +17 -0
  48. package/dist/src/tools/get-calendar-tool-schema.js +13 -0
  49. package/dist/src/tools/get-collection-tool-schema.d.ts +17 -0
  50. package/dist/src/tools/get-collection-tool-schema.js +12 -0
  51. package/dist/src/tools/get-contact-tool-schema.d.ts +17 -0
  52. package/dist/src/tools/get-contact-tool-schema.js +17 -0
  53. package/dist/src/tools/get-device-file-tool-schema.d.ts +17 -0
  54. package/dist/src/tools/get-device-file-tool-schema.js +14 -0
  55. package/dist/src/tools/get-email-tool-schema.d.ts +17 -0
  56. package/dist/src/tools/get-email-tool-schema.js +12 -0
  57. package/dist/src/tools/get-note-tool-schema.d.ts +17 -0
  58. package/dist/src/tools/get-note-tool-schema.js +15 -0
  59. package/dist/src/tools/get-photo-tool-schema.d.ts +17 -0
  60. package/dist/src/tools/get-photo-tool-schema.js +13 -0
  61. package/dist/src/tools/image-reading-tool.d.ts +2 -1
  62. package/dist/src/tools/image-reading-tool.js +86 -90
  63. package/dist/src/tools/location-tool.d.ts +2 -1
  64. package/dist/src/tools/location-tool.js +87 -91
  65. package/dist/src/tools/login-token-tool.d.ts +6 -0
  66. package/dist/src/tools/login-token-tool.js +133 -0
  67. package/dist/src/tools/modify-alarm-tool.d.ts +2 -1
  68. package/dist/src/tools/modify-alarm-tool.js +232 -236
  69. package/dist/src/tools/modify-note-tool.d.ts +2 -1
  70. package/dist/src/tools/modify-note-tool.js +104 -108
  71. package/dist/src/tools/note-tool.d.ts +2 -1
  72. package/dist/src/tools/note-tool.js +103 -107
  73. package/dist/src/tools/query-app-message-tool.d.ts +5 -0
  74. package/dist/src/tools/query-app-message-tool.js +135 -0
  75. package/dist/src/tools/query-memory-data-tool.d.ts +5 -0
  76. package/dist/src/tools/query-memory-data-tool.js +151 -0
  77. package/dist/src/tools/query-todo-task-tool.d.ts +5 -0
  78. package/dist/src/tools/query-todo-task-tool.js +130 -0
  79. package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -1
  80. package/dist/src/tools/save-file-to-phone-tool.js +127 -131
  81. package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -1
  82. package/dist/src/tools/save-media-to-gallery-tool.js +134 -138
  83. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -0
  84. package/dist/src/tools/save-self-evolution-skill-tool.js +410 -0
  85. package/dist/src/tools/schema-tool-factory.d.ts +27 -0
  86. package/dist/src/tools/schema-tool-factory.js +32 -0
  87. package/dist/src/tools/search-alarm-tool.d.ts +2 -1
  88. package/dist/src/tools/search-alarm-tool.js +171 -175
  89. package/dist/src/tools/search-calendar-tool.d.ts +2 -1
  90. package/dist/src/tools/search-calendar-tool.js +145 -149
  91. package/dist/src/tools/search-contact-tool.d.ts +2 -1
  92. package/dist/src/tools/search-contact-tool.js +98 -102
  93. package/dist/src/tools/search-email-tool.d.ts +2 -1
  94. package/dist/src/tools/search-email-tool.js +107 -111
  95. package/dist/src/tools/search-file-tool.d.ts +2 -1
  96. package/dist/src/tools/search-file-tool.js +99 -103
  97. package/dist/src/tools/search-message-tool.d.ts +2 -1
  98. package/dist/src/tools/search-message-tool.js +100 -104
  99. package/dist/src/tools/search-note-tool.d.ts +2 -1
  100. package/dist/src/tools/search-note-tool.js +95 -99
  101. package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -1
  102. package/dist/src/tools/search-photo-gallery-tool.js +34 -38
  103. package/dist/src/tools/send-email-tool.d.ts +5 -0
  104. package/dist/src/tools/send-email-tool.js +131 -0
  105. package/dist/src/tools/send-file-to-user-tool.d.ts +2 -1
  106. package/dist/src/tools/send-file-to-user-tool.js +153 -155
  107. package/dist/src/tools/send-message-tool.d.ts +2 -1
  108. package/dist/src/tools/send-message-tool.js +119 -123
  109. package/dist/src/tools/session-manager.d.ts +21 -6
  110. package/dist/src/tools/session-manager.js +147 -18
  111. package/dist/src/tools/upload-file-tool.d.ts +2 -1
  112. package/dist/src/tools/upload-file-tool.js +79 -83
  113. package/dist/src/tools/upload-photo-tool.d.ts +2 -1
  114. package/dist/src/tools/upload-photo-tool.js +70 -74
  115. package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -1
  116. package/dist/src/tools/xiaoyi-add-collection-tool.js +143 -147
  117. package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -1
  118. package/dist/src/tools/xiaoyi-collection-tool.js +112 -116
  119. package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -1
  120. package/dist/src/tools/xiaoyi-delete-collection-tool.js +124 -128
  121. package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -1
  122. package/dist/src/tools/xiaoyi-gui-tool.js +84 -88
  123. package/dist/src/utils/runtime-manager.js +24 -2
  124. package/dist/src/utils/self-evolution-manager.d.ts +10 -0
  125. package/dist/src/utils/self-evolution-manager.js +68 -0
  126. package/dist/src/utils/tool-call-nudge-manager.d.ts +16 -0
  127. package/dist/src/utils/tool-call-nudge-manager.js +47 -0
  128. package/dist/src/websocket.d.ts +3 -0
  129. package/dist/src/websocket.js +94 -24
  130. package/openclaw.plugin.json +21 -0
  131. package/package.json +3 -3
package/dist/index.d.ts CHANGED
@@ -1,14 +1,11 @@
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
- declare const plugin: {
2
+ declare const _default: {
8
3
  id: string;
9
4
  name: string;
10
5
  description: string;
11
- configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
12
- register(api: OpenClawPluginApi): void;
6
+ configSchema: import("openclaw/plugin-sdk").ChannelConfigSchema;
7
+ register: (api: OpenClawPluginApi) => void;
8
+ channelPlugin: import("openclaw/plugin-sdk").ChannelPlugin;
9
+ setChannelRuntime?: (runtime: import("openclaw/plugin-sdk").PluginRuntime) => void;
13
10
  };
14
- export default plugin;
11
+ export default _default;
package/dist/index.js CHANGED
@@ -1,27 +1,34 @@
1
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
2
- import { xyPlugin } from "./src/channel.js";
1
+ import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
3
2
  import { xiaoyiProvider } from "./src/provider.js";
3
+ import { xyPlugin } from "./src/channel.js";
4
+ import { callCsplApi } from "./src/cspl/call-api.js";
5
+ import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH, STEER_ABORT_MESSAGE, } from "./src/cspl/constants.js";
6
+ import { extractResultText, parseSecurityResult, processText, validateAndTruncateText, } from "./src/cspl/utils.js";
4
7
  import { setXYRuntime } from "./src/runtime.js";
5
8
  import { tryInjectSteer } from "./src/steer-injector.js";
6
- import { callCsplApi } from "./src/cspl/call-api.js";
7
- import { extractResultText, processText, parseSecurityResult, validateAndTruncateText } from "./src/cspl/utils.js";
8
- 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
- */
14
- const plugin = {
9
+ import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
10
+ import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
11
+ import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
12
+ export default defineChannelPluginEntry({
15
13
  id: "xiaoyi-channel",
16
14
  name: "Xiaoyi Channel",
17
15
  description: "Xiaoyi channel plugin - Xiaoyi A2A protocol integration",
18
- configSchema: emptyPluginConfigSchema(),
19
- register(api) {
20
- setXYRuntime(api.runtime);
21
- api.registerChannel({ plugin: xyPlugin });
16
+ plugin: xyPlugin,
17
+ setRuntime: setXYRuntime,
18
+ registerFull(api) {
22
19
  api.registerProvider(xiaoyiProvider);
23
- // SENTINEL HOOK after_tool_call hook: 监听工具结果,发送至安全检测 API 进行安全检测
24
- // 如果响应为 REJECT,注入 steer 消息中止当前对话
20
+ // SKILL RETRIEVER HOOK: before_prompt_build hook
21
+ const pluginConfig = api.pluginConfig || {};
22
+ const skillRetrieverConfig = normalizeToolRetrieverConfig({
23
+ enabled: pluginConfig.skillRetrieverEnabled ?? true,
24
+ maxTools: pluginConfig.skillRetrieverMaxTools ?? 2,
25
+ includeUninstalledOnly: true,
26
+ envFilePath: "~/.openclaw/.xiaoyienv",
27
+ timeoutMs: pluginConfig.skillRetrieverTimeoutMs ?? 1000,
28
+ });
29
+ const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
30
+ api.on("before_prompt_build", beforePromptBuildHandler);
31
+ registerSelfEvolutionToolResultNudge(api);
25
32
  api.on("after_tool_call", async (event, ctx) => {
26
33
  if (!ALLOWED_TOOLS.includes(event.toolName)) {
27
34
  return;
@@ -33,9 +40,8 @@ const plugin = {
33
40
  if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
34
41
  return;
35
42
  }
36
- // 构造 sentinel_hook 格式的 payload: { tool, output: [{ content }] }
37
43
  const questionText = {
38
- subSceneID: 'TOOL_OUTPUT',
44
+ subSceneID: "TOOL_OUTPUT",
39
45
  tool: event.toolName,
40
46
  output: [{ content: "" }],
41
47
  };
@@ -60,5 +66,4 @@ const plugin = {
60
66
  }
61
67
  });
62
68
  },
63
- };
64
- export default plugin;
69
+ });
package/dist/src/bot.js CHANGED
@@ -5,11 +5,14 @@ import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId,
5
5
  import { downloadFilesFromParts } from "./file-download.js";
6
6
  import { resolveXYConfig } from "./config.js";
7
7
  import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
8
+ import { appendSelfEvolutionKeywordNudge, shouldNudgeForSelfEvolutionKeyword, } from "./self-evolution-keyword.js";
8
9
  import { registerSession, unregisterSession, runWithSessionContext } from "./tools/session-manager.js";
9
10
  import { configManager } from "./utils/config-manager.js";
10
11
  import { addPushId } from "./utils/pushid-manager.js";
11
12
  import { getPushDataById } from "./utils/pushdata-manager.js";
13
+ import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
12
14
  import { saveRuntimeInfo } from "./utils/runtime-manager.js";
15
+ import { toolCallNudgeManager } from "./utils/tool-call-nudge-manager.js";
13
16
  import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActiveTask, } from "./task-manager.js";
14
17
  /**
15
18
  * Handle an incoming A2A message.
@@ -174,6 +177,26 @@ export async function handleXYMessage(params) {
174
177
  });
175
178
  // Extract text and files from parts
176
179
  const text = extractTextFromParts(parsed.parts);
180
+ let textForAgent = text || "";
181
+ if (route.sessionKey && textForAgent) {
182
+ try {
183
+ const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
184
+ if (selfEvolutionEnabled && shouldNudgeForSelfEvolutionKeyword(textForAgent)) {
185
+ const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(route.sessionKey);
186
+ log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
187
+ if (shouldNudge) {
188
+ const augmented = appendSelfEvolutionKeywordNudge(textForAgent);
189
+ textForAgent = augmented.text;
190
+ if (augmented.appended) {
191
+ log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
192
+ }
193
+ }
194
+ }
195
+ }
196
+ catch (selfEvolutionError) {
197
+ error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
198
+ }
199
+ }
177
200
  const fileParts = extractFileParts(parsed.parts);
178
201
  // Download files to local disk
179
202
  const downloadedFiles = await downloadFilesFromParts(fileParts);
@@ -182,7 +205,7 @@ export async function handleXYMessage(params) {
182
205
  // Resolve envelope format options (following feishu pattern)
183
206
  const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
184
207
  // Build message body with speaker prefix (following feishu pattern)
185
- let messageBody = text || "";
208
+ let messageBody = textForAgent;
186
209
  // Add speaker prefix for clarity
187
210
  const speaker = parsed.sessionId;
188
211
  messageBody = `${speaker}: ${messageBody}`;
@@ -198,8 +221,8 @@ export async function handleXYMessage(params) {
198
221
  // Use route.accountId and route.sessionKey instead of parsed fields
199
222
  const ctxPayload = core.channel.reply.finalizeInboundContext({
200
223
  Body: body,
201
- RawBody: text || "",
202
- CommandBody: text || "",
224
+ RawBody: textForAgent,
225
+ CommandBody: textForAgent,
203
226
  From: parsed.sessionId,
204
227
  To: parsed.sessionId, // ✅ Simplified: use sessionId as target (context is managed by SessionKey)
205
228
  SessionKey: route.sessionKey, // ✅ Use route.sessionKey
@@ -1,37 +1,9 @@
1
1
  import { resolveXYConfig, listXYAccountIds, getDefaultXYAccountId } from "./config.js";
2
2
  import { xyConfigSchema } from "./config-schema.js";
3
3
  import { xyOutbound } from "./outbound.js";
4
- import { locationTool } from "./tools/location-tool.js";
5
- import { noteTool } from "./tools/note-tool.js";
6
- import { searchNoteTool } from "./tools/search-note-tool.js";
7
- import { modifyNoteTool } from "./tools/modify-note-tool.js";
8
- import { calendarTool } from "./tools/calendar-tool.js";
9
- import { searchCalendarTool } from "./tools/search-calendar-tool.js";
10
- import { searchContactTool } from "./tools/search-contact-tool.js";
11
- import { searchPhotoGalleryTool } from "./tools/search-photo-gallery-tool.js";
12
- import { uploadPhotoTool } from "./tools/upload-photo-tool.js";
13
- import { xiaoyiGuiTool } from "./tools/xiaoyi-gui-tool.js";
14
- import { callPhoneTool } from "./tools/call-phone-tool.js";
15
- import { searchMessageTool } from "./tools/search-message-tool.js";
16
- import { sendMessageTool } from "./tools/send-message-tool.js";
17
- import { searchFileTool } from "./tools/search-file-tool.js";
18
- import { uploadFileTool } from "./tools/upload-file-tool.js";
19
- import { createAlarmTool } from "./tools/create-alarm-tool.js";
20
- import { searchAlarmTool } from "./tools/search-alarm-tool.js";
21
- import { modifyAlarmTool } from "./tools/modify-alarm-tool.js";
22
- import { deleteAlarmTool } from "./tools/delete-alarm-tool.js";
23
- import { sendFileToUserTool } from "./tools/send-file-to-user-tool.js";
24
- import { viewPushResultTool } from "./tools/view-push-result-tool.js";
25
- import { imageReadingTool } from "./tools/image-reading-tool.js";
26
- import { timestampToUtc8Tool } from "./tools/timestamp-to-utc8-tool.js";
27
- import { xiaoyiCollectionTool } from "./tools/xiaoyi-collection-tool.js";
28
- import { xiaoyiAddCollectionTool } from "./tools/xiaoyi-add-collection-tool.js";
29
- import { xiaoyiDeleteCollectionTool } from "./tools/xiaoyi-delete-collection-tool.js";
30
- import { saveMediaToGalleryTool } from "./tools/save-media-to-gallery-tool.js";
31
- import { saveFileToPhoneTool } from "./tools/save-file-to-phone-tool.js";
32
- import { searchEmailTool } from "./tools/search-email-tool.js";
33
4
  import { filterToolsByDevice } from "./tools/device-tool-map.js";
34
5
  import { getCurrentSessionContext } from "./tools/session-manager.js";
6
+ import { createAllTools } from "./tools/create-all-tools.js";
35
7
  import { logger } from "./utils/logger.js";
36
8
  /**
37
9
  * Xiaoyi Channel Plugin for OpenClaw.
@@ -72,8 +44,8 @@ export const xyPlugin = {
72
44
  },
73
45
  outbound: xyOutbound,
74
46
  agentTools: () => {
75
- const allTools = [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchContactTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool, callPhoneTool, searchMessageTool, sendMessageTool, searchFileTool, uploadFileTool, createAlarmTool, searchAlarmTool, modifyAlarmTool, deleteAlarmTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, xiaoyiCollectionTool, xiaoyiAddCollectionTool, xiaoyiDeleteCollectionTool, saveMediaToGalleryTool, saveFileToPhoneTool, searchEmailTool];
76
47
  const ctx = getCurrentSessionContext();
48
+ const allTools = createAllTools(ctx);
77
49
  const filtered = filterToolsByDevice(allTools, ctx?.deviceType);
78
50
  logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
79
51
  return filtered;
@@ -12,8 +12,14 @@ export function setClientRuntime(rt) {
12
12
  /**
13
13
  * Global cache for WebSocket managers.
14
14
  * Key format: `${apiKey}-${agentId}`
15
+ * Uses globalThis to ensure a single cache across all module copies
16
+ * (same fix as session-manager.ts for openclaw multi-instance loading).
15
17
  */
16
- const wsManagerCache = new Map();
18
+ const _g = globalThis;
19
+ if (!_g.__xyWsManagerCache) {
20
+ _g.__xyWsManagerCache = new Map();
21
+ }
22
+ const wsManagerCache = _g.__xyWsManagerCache;
17
23
  /**
18
24
  * Get or create a WebSocket manager for the given configuration.
19
25
  * Reuses existing managers if config matches.
@@ -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
+ }
@@ -0,0 +1,17 @@
1
+ import type { OutboundWebSocketMessage } from "./types.js";
2
+ /**
3
+ * Simple message queue for buffering outbound WebSocket messages
4
+ * during disconnection and reconnection stabilization period.
5
+ */
6
+ export declare class MessageQueue {
7
+ private items;
8
+ private log;
9
+ constructor(log?: (msg: string, ...args: any[]) => void);
10
+ /** Enqueue a message. Drops oldest if over limit. */
11
+ enqueue(message: OutboundWebSocketMessage): void;
12
+ /** Flush all queued messages by calling sendFn for each, then clear. */
13
+ flush(sendFn: (message: OutboundWebSocketMessage) => void): void;
14
+ /** Clear all queued messages without sending. */
15
+ clear(): void;
16
+ get size(): number;
17
+ }
@@ -0,0 +1,51 @@
1
+ const MAX_QUEUE_SIZE = 1000;
2
+ /**
3
+ * Simple message queue for buffering outbound WebSocket messages
4
+ * during disconnection and reconnection stabilization period.
5
+ */
6
+ export class MessageQueue {
7
+ items = [];
8
+ log;
9
+ constructor(log) {
10
+ this.log = log ?? console.log;
11
+ }
12
+ /** Enqueue a message. Drops oldest if over limit. */
13
+ enqueue(message) {
14
+ if (this.items.length >= MAX_QUEUE_SIZE) {
15
+ this.log(`[MessageQueue] Queue full (${MAX_QUEUE_SIZE}), dropping oldest message`);
16
+ this.items.shift();
17
+ }
18
+ this.items.push(message);
19
+ this.log(`[MessageQueue] Enqueued message, queue size: ${this.items.length}`);
20
+ }
21
+ /** Flush all queued messages by calling sendFn for each, then clear. */
22
+ flush(sendFn) {
23
+ const count = this.items.length;
24
+ if (count === 0) {
25
+ this.log("[MessageQueue] Queue empty, nothing to flush");
26
+ return;
27
+ }
28
+ this.log(`[MessageQueue] Flushing ${count} queued messages`);
29
+ for (const msg of this.items) {
30
+ try {
31
+ sendFn(msg);
32
+ }
33
+ catch (err) {
34
+ this.log(`[MessageQueue] Error flushing message: ${err}`);
35
+ }
36
+ }
37
+ this.items = [];
38
+ this.log(`[MessageQueue] Flush complete`);
39
+ }
40
+ /** Clear all queued messages without sending. */
41
+ clear() {
42
+ const count = this.items.length;
43
+ this.items = [];
44
+ if (count > 0) {
45
+ this.log(`[MessageQueue] Cleared ${count} messages`);
46
+ }
47
+ }
48
+ get size() {
49
+ return this.items.length;
50
+ }
51
+ }
@@ -2,9 +2,13 @@ import { resolveXYConfig } from "./config.js";
2
2
  import { getXYWebSocketManager, diagnoseAllManagers, cleanupOrphanConnections, removeXYWebSocketManager } from "./client.js";
3
3
  import { handleXYMessage } from "./bot.js";
4
4
  import { parseA2AMessage } from "./parser.js";
5
- import { hasActiveTask } from "./task-manager.js";
5
+ import { hasActiveTask, getAllActiveTaskBindings } from "./task-manager.js";
6
+ import { sendA2AResponse } from "./formatter.js";
6
7
  import { handleTriggerEvent } from "./trigger-handler.js";
8
+ import { handleSelfEvolutionEvent, handleSelfEvolutionStateGetEvent } from "./self-evolution-handler.js";
9
+ import { handleLoginTokenEvent } from "./login-token-handler.js";
7
10
  import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
11
+ import { cleanupStaleSessions, getActiveSessionCount, cleanupAllSessions } from "./tools/session-manager.js";
8
12
  /**
9
13
  * Per-session serial queue that ensures messages from the same session are processed
10
14
  * in arrival order while allowing different sessions to run concurrently.
@@ -156,6 +160,20 @@ export async function monitorXYProvider(opts = {}) {
156
160
  error(`[MONITOR] Failed to handle trigger-event:`, err);
157
161
  });
158
162
  };
163
+ const selfEvolutionHandler = (context) => {
164
+ log(`[MONITOR] Received self-evolution-event, dispatching to handler...`);
165
+ handleSelfEvolutionEvent(context, runtime);
166
+ };
167
+ const selfEvolutionStateGetHandler = (context) => {
168
+ log(`[MONITOR] Received self-evolution-state-get-event, dispatching to handler...`);
169
+ handleSelfEvolutionStateGetEvent(context, account, runtime, wsManager).catch((err) => {
170
+ error(`[MONITOR] Failed to handle self-evolution-state-get-event:`, err);
171
+ });
172
+ };
173
+ const loginTokenEventHandler = (context) => {
174
+ log(`[MONITOR] Received login-token-event, dispatching to handler...`);
175
+ handleLoginTokenEvent(context, runtime);
176
+ };
159
177
  const cleanup = () => {
160
178
  log("XY gateway: cleaning up...");
161
179
  // 🔍 Diagnose before cleanup
@@ -173,20 +191,53 @@ export async function monitorXYProvider(opts = {}) {
173
191
  wsManager.off("disconnected", disconnectedHandler);
174
192
  wsManager.off("error", errorHandler);
175
193
  wsManager.off("trigger-event", triggerEventHandler);
194
+ wsManager.off("self-evolution-event", selfEvolutionHandler);
195
+ wsManager.off("self-evolution-state-get-event", selfEvolutionStateGetHandler);
196
+ wsManager.off("login-token-event", loginTokenEventHandler);
176
197
  // ✅ Disconnect the wsManager to prevent connection leaks
177
198
  // This is safe because each gateway lifecycle should have clean connections
178
199
  wsManager.disconnect();
179
200
  // ✅ Remove manager from cache to prevent reusing dirty state
180
201
  removeXYWebSocketManager(account);
202
+ // Clean up all active sessions
203
+ cleanupAllSessions();
181
204
  loggedServers.clear();
182
205
  activeMessages.clear();
183
- log(`[MONITOR-HANDLER] 🧹 Cleanup complete, cleared active messages`);
206
+ log(`[MONITOR-HANDLER] 🧹 Cleanup complete, cleared active messages and sessions`);
184
207
  // 🔍 Diagnose after cleanup
185
208
  console.log("🔍 [DIAGNOSTICS] Checking WebSocket managers after cleanup...");
186
209
  diagnoseAllManagers();
187
210
  };
188
- const handleAbort = () => {
189
- log("XY gateway: abort signal received, stopping");
211
+ const handleAbort = async () => {
212
+ log("XY gateway: abort signal received, sending notifications before stopping");
213
+ // 📤 Send restart notification to all active sessions before disconnecting
214
+ try {
215
+ const activeBindings = getAllActiveTaskBindings();
216
+ if (activeBindings.length > 0) {
217
+ const config = resolveXYConfig(cfg);
218
+ const notificationText = "Gateway即将重启,重启期间可能短暂出现\u201c环境异常\u201d提示,请稍候并耐心重试~";
219
+ log(`[MONITOR] 📤 Sending restart notifications to ${activeBindings.length} active session(s)`);
220
+ const sendPromises = activeBindings.map(binding => sendA2AResponse({
221
+ config,
222
+ sessionId: binding.sessionId,
223
+ taskId: binding.currentTaskId,
224
+ messageId: binding.currentMessageId,
225
+ text: notificationText,
226
+ append: false,
227
+ final: true,
228
+ }).catch(err => {
229
+ error(`[MONITOR] Failed to send restart notification to session ${binding.sessionId}: ${String(err)}`);
230
+ }));
231
+ await Promise.all(sendPromises);
232
+ log(`[MONITOR] ✅ Restart notifications sent to ${activeBindings.length} session(s)`);
233
+ }
234
+ else {
235
+ log(`[MONITOR] No active sessions, skipping restart notifications`);
236
+ }
237
+ }
238
+ catch (err) {
239
+ error(`[MONITOR] Error sending restart notifications: ${String(err)}`);
240
+ }
190
241
  cleanup();
191
242
  log("XY gateway stopped");
192
243
  resolve();
@@ -203,6 +254,9 @@ export async function monitorXYProvider(opts = {}) {
203
254
  wsManager.on("disconnected", disconnectedHandler);
204
255
  wsManager.on("error", errorHandler);
205
256
  wsManager.on("trigger-event", triggerEventHandler);
257
+ wsManager.on("self-evolution-event", selfEvolutionHandler);
258
+ wsManager.on("self-evolution-state-get-event", selfEvolutionStateGetHandler);
259
+ wsManager.on("login-token-event", loginTokenEventHandler);
206
260
  // Start periodic health check (every 6 hours)
207
261
  console.log("🏥 Starting periodic health check (every 6 hours)...");
208
262
  healthCheckInterval = setInterval(() => {
@@ -213,6 +267,12 @@ export async function monitorXYProvider(opts = {}) {
213
267
  if (cleaned > 0) {
214
268
  console.log(`🧹 [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
215
269
  }
270
+ // Cleanup stale sessions (older than 10min TTL)
271
+ const cleanedSessions = cleanupStaleSessions();
272
+ const remainingSessions = getActiveSessionCount();
273
+ if (cleanedSessions > 0 || remainingSessions > 0) {
274
+ console.log(`🧹 [HEALTH CHECK] Sessions: cleaned=${cleanedSessions}, active=${remainingSessions}`);
275
+ }
216
276
  // Cleanup stale temp files (older than 24 hours)
217
277
  void cleanupStaleTempFiles();
218
278
  }, 6 * 60 * 60 * 1000); // 6 hours
@@ -1,2 +1,3 @@
1
- import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-models";
1
+ import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared";
2
+ export declare function applySelfEvolutionPrompt(systemPrompt: string | undefined, enabled: boolean): string;
2
3
  export declare const xiaoyiProvider: ProviderPlugin;