@ynhcj/xiaoyi-channel 0.0.113-beta → 0.0.113-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 (127) hide show
  1. package/dist/index.d.ts +4 -5
  2. package/dist/index.js +71 -82
  3. package/dist/provider-discovery.d.ts +2 -0
  4. package/dist/provider-discovery.js +4 -0
  5. package/dist/src/bot.d.ts +2 -0
  6. package/dist/src/bot.js +41 -30
  7. package/dist/src/channel.js +2 -21
  8. package/dist/src/client.js +31 -22
  9. package/dist/src/cspl/call-api.js +6 -5
  10. package/dist/src/file-download.js +4 -3
  11. package/dist/src/file-upload.js +19 -18
  12. package/dist/src/formatter.d.ts +2 -0
  13. package/dist/src/formatter.js +25 -35
  14. package/dist/src/heartbeat.js +1 -1
  15. package/dist/src/monitor.js +35 -10
  16. package/dist/src/outbound.js +19 -18
  17. package/dist/src/provider.d.ts +1 -1
  18. package/dist/src/provider.js +112 -23
  19. package/dist/src/push.js +16 -15
  20. package/dist/src/reply-dispatcher.js +12 -3
  21. package/dist/src/runtime.d.ts +3 -11
  22. package/dist/src/runtime.js +6 -18
  23. package/dist/src/self-evolution-tool-result-nudge.d.ts +3 -0
  24. package/dist/src/self-evolution-tool-result-nudge.js +96 -0
  25. package/dist/src/skill-retriever/hooks.js +7 -15
  26. package/dist/src/skill-retriever/tool-search.js +22 -8
  27. package/dist/src/skill-retriever/types.d.ts +2 -0
  28. package/dist/src/steer-injector.js +1 -1
  29. package/dist/src/task-manager.js +6 -1
  30. package/dist/src/tools/calendar-tool.d.ts +2 -1
  31. package/dist/src/tools/calendar-tool.js +112 -116
  32. package/dist/src/tools/call-device-tool.d.ts +2 -1
  33. package/dist/src/tools/call-device-tool.js +126 -103
  34. package/dist/src/tools/call-phone-tool.d.ts +2 -1
  35. package/dist/src/tools/call-phone-tool.js +109 -113
  36. package/dist/src/tools/create-alarm-tool.d.ts +2 -1
  37. package/dist/src/tools/create-alarm-tool.js +227 -231
  38. package/dist/src/tools/create-all-tools.d.ts +16 -0
  39. package/dist/src/tools/create-all-tools.js +50 -0
  40. package/dist/src/tools/delete-alarm-tool.d.ts +2 -1
  41. package/dist/src/tools/delete-alarm-tool.js +131 -135
  42. package/dist/src/tools/get-alarm-tool-schema.d.ts +2 -1
  43. package/dist/src/tools/get-alarm-tool-schema.js +16 -10
  44. package/dist/src/tools/get-calendar-tool-schema.d.ts +2 -1
  45. package/dist/src/tools/get-calendar-tool-schema.js +12 -8
  46. package/dist/src/tools/get-collection-tool-schema.d.ts +2 -1
  47. package/dist/src/tools/get-collection-tool-schema.js +11 -9
  48. package/dist/src/tools/get-contact-tool-schema.d.ts +2 -1
  49. package/dist/src/tools/get-contact-tool-schema.js +16 -10
  50. package/dist/src/tools/get-device-file-tool-schema.d.ts +2 -1
  51. package/dist/src/tools/get-device-file-tool-schema.js +13 -9
  52. package/dist/src/tools/get-email-tool-schema.d.ts +2 -1
  53. package/dist/src/tools/get-email-tool-schema.js +11 -8
  54. package/dist/src/tools/get-note-tool-schema.d.ts +2 -1
  55. package/dist/src/tools/get-note-tool-schema.js +14 -9
  56. package/dist/src/tools/get-photo-tool-schema.d.ts +2 -1
  57. package/dist/src/tools/get-photo-tool-schema.js +12 -9
  58. package/dist/src/tools/image-reading-tool.d.ts +3 -2
  59. package/dist/src/tools/image-reading-tool.js +82 -162
  60. package/dist/src/tools/location-tool.d.ts +2 -1
  61. package/dist/src/tools/location-tool.js +87 -91
  62. package/dist/src/tools/login-token-tool.d.ts +3 -2
  63. package/dist/src/tools/login-token-tool.js +114 -117
  64. package/dist/src/tools/modify-alarm-tool.d.ts +2 -1
  65. package/dist/src/tools/modify-alarm-tool.js +232 -236
  66. package/dist/src/tools/modify-note-tool.d.ts +2 -1
  67. package/dist/src/tools/modify-note-tool.js +104 -108
  68. package/dist/src/tools/note-tool.d.ts +2 -1
  69. package/dist/src/tools/note-tool.js +103 -107
  70. package/dist/src/tools/query-app-message-tool.d.ts +2 -1
  71. package/dist/src/tools/query-app-message-tool.js +108 -111
  72. package/dist/src/tools/query-memory-data-tool.d.ts +2 -1
  73. package/dist/src/tools/query-memory-data-tool.js +109 -112
  74. package/dist/src/tools/query-todo-task-tool.d.ts +2 -1
  75. package/dist/src/tools/query-todo-task-tool.js +103 -106
  76. package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -1
  77. package/dist/src/tools/save-file-to-phone-tool.js +127 -131
  78. package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -1
  79. package/dist/src/tools/save-media-to-gallery-tool.js +134 -138
  80. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -1
  81. package/dist/src/tools/save-self-evolution-skill-tool.js +195 -197
  82. package/dist/src/tools/search-alarm-tool.d.ts +2 -1
  83. package/dist/src/tools/search-alarm-tool.js +171 -175
  84. package/dist/src/tools/search-calendar-tool.d.ts +2 -1
  85. package/dist/src/tools/search-calendar-tool.js +145 -149
  86. package/dist/src/tools/search-contact-tool.d.ts +2 -1
  87. package/dist/src/tools/search-contact-tool.js +98 -102
  88. package/dist/src/tools/search-email-tool.d.ts +2 -1
  89. package/dist/src/tools/search-email-tool.js +107 -111
  90. package/dist/src/tools/search-file-tool.d.ts +2 -1
  91. package/dist/src/tools/search-file-tool.js +99 -103
  92. package/dist/src/tools/search-message-tool.d.ts +2 -1
  93. package/dist/src/tools/search-message-tool.js +100 -104
  94. package/dist/src/tools/search-note-tool.d.ts +2 -1
  95. package/dist/src/tools/search-note-tool.js +95 -99
  96. package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -1
  97. package/dist/src/tools/search-photo-gallery-tool.js +34 -38
  98. package/dist/src/tools/send-email-tool.d.ts +2 -1
  99. package/dist/src/tools/send-email-tool.js +105 -108
  100. package/dist/src/tools/send-file-to-user-tool.d.ts +2 -1
  101. package/dist/src/tools/send-file-to-user-tool.js +158 -155
  102. package/dist/src/tools/send-message-tool.d.ts +2 -1
  103. package/dist/src/tools/send-message-tool.js +119 -123
  104. package/dist/src/tools/session-helper.d.ts +24 -0
  105. package/dist/src/tools/session-helper.js +45 -0
  106. package/dist/src/tools/session-manager.d.ts +29 -6
  107. package/dist/src/tools/session-manager.js +146 -19
  108. package/dist/src/tools/upload-file-tool.d.ts +2 -1
  109. package/dist/src/tools/upload-file-tool.js +78 -82
  110. package/dist/src/tools/upload-photo-tool.d.ts +2 -1
  111. package/dist/src/tools/upload-photo-tool.js +69 -73
  112. package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -1
  113. package/dist/src/tools/xiaoyi-add-collection-tool.js +143 -147
  114. package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -1
  115. package/dist/src/tools/xiaoyi-collection-tool.js +111 -115
  116. package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -1
  117. package/dist/src/tools/xiaoyi-delete-collection-tool.js +124 -128
  118. package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -1
  119. package/dist/src/tools/xiaoyi-gui-tool.js +87 -88
  120. package/dist/src/utils/logger.js +20 -18
  121. package/dist/src/utils/self-evolution-manager.d.ts +5 -0
  122. package/dist/src/utils/self-evolution-manager.js +45 -23
  123. package/dist/src/websocket.js +31 -29
  124. package/dist/src/xy-session-store.d.ts +79 -0
  125. package/dist/src/xy-session-store.js +153 -0
  126. package/openclaw.plugin.json +22 -0
  127. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- declare const plugin: {
1
+ declare const _default: {
3
2
  id: string;
4
3
  name: string;
5
4
  description: string;
6
5
  configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
7
- register(api: OpenClawPluginApi): void;
8
- };
9
- export default plugin;
6
+ register: NonNullable<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition["register"]>;
7
+ } & Pick<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
8
+ export default _default;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/core";
2
2
  import { xiaoyiProvider } from "./src/provider.js";
3
3
  import { xyPlugin } from "./src/channel.js";
4
4
  import { callCsplApi } from "./src/cspl/call-api.js";
@@ -6,93 +6,82 @@ import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH, STEE
6
6
  import { extractResultText, parseSecurityResult, processText, validateAndTruncateText, } from "./src/cspl/utils.js";
7
7
  import { setXYRuntime } from "./src/runtime.js";
8
8
  import { tryInjectSteer } from "./src/steer-injector.js";
9
- import { selfEvolutionManager } from "./src/utils/self-evolution-manager.js";
10
- import { TOOL_CALL_NUDGE_THRESHOLD, toolCallNudgeManager, } from "./src/utils/tool-call-nudge-manager.js";
9
+ import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
11
10
  import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
12
11
  import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
13
- const SELF_EVOLUTION_NUDGE_MESSAGE = "[系统消息,非用户发言,无需主动告知用户] 当前任务已经调用了较多次数的工具。请先完成用户主要任务。完成后,如果这些工具调用暴露出稳定、通用、可复用的流程、排错经验或踩坑点,再考虑阅读 xiaoyi-self-evolution 技能进行评估;如果没有高价值候选经验,请忽略本提醒。";
14
- function shouldCountToolCall(toolName) {
15
- if (toolName === "save_self_evolution_skill") {
16
- return false;
17
- }
18
- if (toolName === "call_device_tool") {
19
- return false;
20
- }
21
- if (toolName.endsWith("_tool_schema")) {
22
- return false;
23
- }
24
- return true;
12
+ function registerFullHooks(api) {
13
+ // SKILL RETRIEVER HOOK: before_prompt_build hook
14
+ const pluginConfig = api.pluginConfig || {};
15
+ const skillRetrieverConfig = normalizeToolRetrieverConfig({
16
+ enabled: pluginConfig.skillRetrieverEnabled ?? true,
17
+ maxTools: pluginConfig.skillRetrieverMaxTools ?? 2,
18
+ includeUninstalledOnly: true,
19
+ envFilePath: "~/.openclaw/.xiaoyienv",
20
+ timeoutMs: pluginConfig.skillRetrieverTimeoutMs ?? 1000,
21
+ });
22
+ const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
23
+ api.on("before_prompt_build", beforePromptBuildHandler);
24
+ registerSelfEvolutionToolResultNudge(api);
25
+ api.on("after_tool_call", async (event, ctx) => {
26
+ if (!ALLOWED_TOOLS.includes(event.toolName)) {
27
+ return;
28
+ }
29
+ console.log(`[SENTINEL HOOK] after_tool_call triggered: toolName=${event.toolName}, sessionKey=${ctx.sessionKey ?? "none"}`);
30
+ try {
31
+ const resultText = extractResultText(event, event.toolName);
32
+ const resultLength = resultText.length;
33
+ if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
34
+ return;
35
+ }
36
+ const questionText = {
37
+ subSceneID: "TOOL_OUTPUT",
38
+ tool: event.toolName,
39
+ output: [{ content: "" }],
40
+ };
41
+ const originText = processText(resultText);
42
+ questionText.output[0].content = originText;
43
+ let finalJson = JSON.stringify(questionText);
44
+ if (finalJson.length > MAX_TEXT_LENGTH) {
45
+ const diff = finalJson.length - MAX_TEXT_LENGTH;
46
+ const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
47
+ questionText.output[0].content = trimmed;
48
+ finalJson = JSON.stringify(questionText);
49
+ }
50
+ const response = await callCsplApi(finalJson, api.config);
51
+ const result = parseSecurityResult(response);
52
+ console.log(`[SENTINEL HOOK] Security result: status=${result.status}`);
53
+ if (result.status === "REJECT") {
54
+ await tryInjectSteer(ctx.sessionKey, STEER_ABORT_MESSAGE);
55
+ }
56
+ }
57
+ catch (err) {
58
+ api.logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
59
+ }
60
+ });
25
61
  }
26
- const plugin = {
62
+ export default definePluginEntry({
27
63
  id: "xiaoyi-channel",
28
64
  name: "Xiaoyi Channel",
29
65
  description: "Xiaoyi channel plugin - Xiaoyi A2A protocol integration",
30
- configSchema: emptyPluginConfigSchema(),
31
66
  register(api) {
32
- setXYRuntime(api.runtime);
33
- api.registerChannel({ plugin: xyPlugin });
67
+ // Always register the provider so wrapStreamFn/prepareExtraParams work
68
+ // in ALL registration modes (not just "full").
34
69
  api.registerProvider(xiaoyiProvider);
35
- // SKILL RETRIEVER HOOK: before_prompt_build hook
36
- const pluginConfig = api.pluginConfig || {};
37
- const skillRetrieverConfig = normalizeToolRetrieverConfig({
38
- enabled: pluginConfig.skillRetrieverEnabled ?? true,
39
- maxTools: pluginConfig.skillRetrieverMaxTools ?? 2,
40
- includeUninstalledOnly: true,
41
- envFilePath: "~/.openclaw/.xiaoyienv",
42
- timeoutMs: pluginConfig.skillRetrieverTimeoutMs ?? 1000,
43
- });
44
- const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
45
- api.on("before_prompt_build", beforePromptBuildHandler);
46
- api.on("after_tool_call", async (event, ctx) => {
47
- const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
48
- if (ctx.sessionKey && selfEvolutionEnabled && shouldCountToolCall(event.toolName)) {
49
- try {
50
- const { count, shouldNudge } = toolCallNudgeManager.recordToolCall(ctx.sessionKey);
51
- api.logger.debug?.(`[SELF_EVOLUTION] Tool call counted: tool=${event.toolName}, count=${count}, threshold=${TOOL_CALL_NUDGE_THRESHOLD}, sessionKey=${ctx.sessionKey}`);
52
- if (shouldNudge) {
53
- api.logger.info?.(`[SELF_EVOLUTION] Tool call threshold reached, injecting nudge: count=${count}, sessionKey=${ctx.sessionKey}`);
54
- await tryInjectSteer(ctx.sessionKey, SELF_EVOLUTION_NUDGE_MESSAGE);
55
- }
56
- }
57
- catch (err) {
58
- api.logger.error(`[SELF_EVOLUTION] after_tool_call nudge error: ${err}`);
59
- }
60
- }
61
- if (!ALLOWED_TOOLS.includes(event.toolName)) {
62
- return;
63
- }
64
- console.log(`[SENTINEL HOOK] after_tool_call triggered: toolName=${event.toolName}, sessionKey=${ctx.sessionKey ?? "none"}`);
65
- try {
66
- const resultText = extractResultText(event, event.toolName);
67
- const resultLength = resultText.length;
68
- if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
69
- return;
70
- }
71
- const questionText = {
72
- subSceneID: "TOOL_OUTPUT",
73
- tool: event.toolName,
74
- output: [{ content: "" }],
75
- };
76
- const originText = processText(resultText);
77
- questionText.output[0].content = originText;
78
- let finalJson = JSON.stringify(questionText);
79
- if (finalJson.length > MAX_TEXT_LENGTH) {
80
- const diff = finalJson.length - MAX_TEXT_LENGTH;
81
- const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
82
- questionText.output[0].content = trimmed;
83
- finalJson = JSON.stringify(questionText);
84
- }
85
- const response = await callCsplApi(finalJson, api.config);
86
- const result = parseSecurityResult(response);
87
- console.log(`[SENTINEL HOOK] Security result: status=${result.status}`);
88
- if (result.status === "REJECT") {
89
- await tryInjectSteer(ctx.sessionKey, STEER_ABORT_MESSAGE);
90
- }
91
- }
92
- catch (err) {
93
- api.logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
94
- }
95
- });
70
+ if (api.registrationMode === "cli-metadata") {
71
+ return;
72
+ }
73
+ if (api.registrationMode === "tool-discovery") {
74
+ registerFullHooks(api);
75
+ return;
76
+ }
77
+ // Register channel plugin and set runtime
78
+ api.registerChannel({ plugin: xyPlugin });
79
+ setXYRuntime(api.runtime);
80
+ if (api.registrationMode === "discovery") {
81
+ return;
82
+ }
83
+ if (api.registrationMode === "full") {
84
+ registerFullHooks(api);
85
+ }
96
86
  },
97
- };
98
- export default plugin;
87
+ });
@@ -0,0 +1,2 @@
1
+ import { xiaoyiProvider } from "./src/provider.js";
2
+ export default xiaoyiProvider;
@@ -0,0 +1,4 @@
1
+ // Provider discovery entry for fast-path provider resolution.
2
+ // Exported as default so normalizeDiscoveryModule can unwrap it via .default.
3
+ import { xiaoyiProvider } from "./src/provider.js";
4
+ export default xiaoyiProvider;
package/dist/src/bot.d.ts CHANGED
@@ -9,6 +9,8 @@ export interface HandleXYMessageParams {
9
9
  message: A2AJsonRpcRequest;
10
10
  accountId: string;
11
11
  webSocketSessionId?: string;
12
+ /** Called after dispatch init is complete (agentTools/wrapStreamFn done). */
13
+ onInitComplete?: () => void;
12
14
  }
13
15
  /**
14
16
  * Handle an incoming A2A message.
package/dist/src/bot.js CHANGED
@@ -90,6 +90,7 @@ export async function handleXYMessage(params) {
90
90
  text: pushDataItem.dataDetail,
91
91
  append: false,
92
92
  final: true,
93
+ runtime,
93
94
  });
94
95
  log(`[BOT] ✅ Trigger response sent successfully, exiting early`);
95
96
  return; // 提前返回,不继续处理
@@ -162,6 +163,7 @@ export async function handleXYMessage(params) {
162
163
  taskId: parsed.taskId,
163
164
  messageId: parsed.messageId,
164
165
  agentId: route.accountId,
166
+ deviceType,
165
167
  });
166
168
  // 🔑 发送初始状态更新(第二条消息也要发,用新taskId)
167
169
  log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
@@ -172,6 +174,7 @@ export async function handleXYMessage(params) {
172
174
  messageId: parsed.messageId,
173
175
  text: isSecondMessage ? "新消息已接收,正在处理..." : "任务正在处理中,请稍候~",
174
176
  state: "working",
177
+ runtime,
175
178
  }).catch((err) => {
176
179
  error(`Failed to send initial status update:`, err);
177
180
  });
@@ -200,7 +203,7 @@ export async function handleXYMessage(params) {
200
203
  const fileParts = extractFileParts(parsed.parts);
201
204
  // Download files to local disk
202
205
  const downloadedFiles = await downloadFilesFromParts(fileParts);
203
- console.log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
206
+ log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
204
207
  const mediaPayload = buildXYMediaPayload(downloadedFiles);
205
208
  // Resolve envelope format options (following feishu pattern)
206
209
  const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
@@ -233,7 +236,7 @@ export async function handleXYMessage(params) {
233
236
  SenderId: parsed.sessionId,
234
237
  Provider: "xiaoyi-channel",
235
238
  Surface: "xiaoyi-channel",
236
- MessageSid: parsed.messageId,
239
+ MessageSid: `${parsed.taskId}_${deviceType}`,
237
240
  Timestamp: Date.now(),
238
241
  WasMentioned: false,
239
242
  CommandAuthorized: true,
@@ -286,34 +289,42 @@ export async function handleXYMessage(params) {
286
289
  unregisterSession(route.sessionKey);
287
290
  log(`[BOT] ✅ Cleanup completed`);
288
291
  },
289
- run: () =>
290
- // 🔐 Use AsyncLocalStorage to provide session context to tools
291
- runWithSessionContext(sessionContext, async () => {
292
- log(`[BOT-DISPATCH] dispatchReplyFromConfig starting...`);
293
- log(`[BOT-DISPATCH] - sessionKey: ${ctxPayload.SessionKey}`);
294
- log(`[BOT-DISPATCH] - provider: ${ctxPayload.Provider}`);
295
- log(`[BOT-DISPATCH] - surface: ${ctxPayload.Surface}`);
296
- log(`[BOT-DISPATCH] - from: ${ctxPayload.From}`);
297
- log(`[BOT-DISPATCH] - body length: ${ctxPayload.Body?.length ?? 0}`);
298
- try {
299
- const result = await core.channel.reply.dispatchReplyFromConfig({
300
- ctx: ctxPayload,
301
- cfg,
302
- dispatcher,
303
- replyOptions,
304
- });
305
- log(`[BOT-DISPATCH] ✅ dispatchReplyFromConfig returned`);
306
- log(`[BOT-DISPATCH] - result: ${JSON.stringify(result)}`);
307
- return result;
308
- }
309
- catch (dispatchErr) {
310
- error(`[BOT-DISPATCH] dispatchReplyFromConfig threw`);
311
- error(`[BOT-DISPATCH] - error name: ${dispatchErr instanceof Error ? dispatchErr.name : "unknown"}`);
312
- error(`[BOT-DISPATCH] - error message: ${String(dispatchErr)}`);
313
- error(`[BOT-DISPATCH] - error stack: ${dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : "N/A"}`);
314
- throw dispatchErr;
315
- }
316
- }),
292
+ run: () => {
293
+ // 🔐 Use AsyncLocalStorage to provide session context to tools.
294
+ // runWithSessionContext returns after the sync part of dispatch
295
+ // (including agentTools + wrapStreamFn) has executed, so we
296
+ // signal init complete to release the global dispatch gate
297
+ // for the next session.
298
+ const dispatchPromise = runWithSessionContext(sessionContext, async () => {
299
+ log(`[BOT-DISPATCH] dispatchReplyFromConfig starting...`);
300
+ log(`[BOT-DISPATCH] - sessionKey: ${ctxPayload.SessionKey}`);
301
+ log(`[BOT-DISPATCH] - provider: ${ctxPayload.Provider}`);
302
+ log(`[BOT-DISPATCH] - surface: ${ctxPayload.Surface}`);
303
+ log(`[BOT-DISPATCH] - from: ${ctxPayload.From}`);
304
+ log(`[BOT-DISPATCH] - body length: ${ctxPayload.Body?.length ?? 0}`);
305
+ try {
306
+ const result = await core.channel.reply.dispatchReplyFromConfig({
307
+ ctx: ctxPayload,
308
+ cfg,
309
+ dispatcher,
310
+ replyOptions,
311
+ });
312
+ log(`[BOT-DISPATCH] ✅ dispatchReplyFromConfig returned`);
313
+ log(`[BOT-DISPATCH] - result: ${JSON.stringify(result)}`);
314
+ return result;
315
+ }
316
+ catch (dispatchErr) {
317
+ error(`[BOT-DISPATCH] ❌ dispatchReplyFromConfig threw`);
318
+ error(`[BOT-DISPATCH] - error name: ${dispatchErr instanceof Error ? dispatchErr.name : "unknown"}`);
319
+ error(`[BOT-DISPATCH] - error message: ${String(dispatchErr)}`);
320
+ error(`[BOT-DISPATCH] - error stack: ${dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : "N/A"}`);
321
+ throw dispatchErr;
322
+ }
323
+ });
324
+ // Signal init complete — sync part (agentTools, wrapStreamFn) is done
325
+ params.onInitComplete?.();
326
+ return dispatchPromise;
327
+ },
317
328
  });
318
329
  log(`[BOT] ✅ Dispatcher completed for session: ${parsed.sessionId}`);
319
330
  log(`xy: dispatch complete (session=${parsed.sessionId})`);
@@ -1,28 +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 { xiaoyiGuiTool } from "./tools/xiaoyi-gui-tool.js";
6
- import { sendFileToUserTool } from "./tools/send-file-to-user-tool.js";
7
- import { viewPushResultTool } from "./tools/view-push-result-tool.js";
8
- import { imageReadingTool } from "./tools/image-reading-tool.js";
9
- import { timestampToUtc8Tool } from "./tools/timestamp-to-utc8-tool.js";
10
- import { saveSelfEvolutionSkillTool } from "./tools/save-self-evolution-skill-tool.js";
11
- import { getEmailToolSchemaTool } from "./tools/get-email-tool-schema.js";
12
- import { callDeviceTool } from "./tools/call-device-tool.js";
13
- import { getNoteToolSchemaTool } from "./tools/get-note-tool-schema.js";
14
- import { getCalendarToolSchemaTool } from "./tools/get-calendar-tool-schema.js";
15
- import { getContactToolSchemaTool } from "./tools/get-contact-tool-schema.js";
16
- import { getPhotoToolSchemaTool } from "./tools/get-photo-tool-schema.js";
17
- import { getDeviceFileToolSchemaTool } from "./tools/get-device-file-tool-schema.js";
18
- import { getAlarmToolSchemaTool } from "./tools/get-alarm-tool-schema.js";
19
- import { getCollectionToolSchemaTool } from "./tools/get-collection-tool-schema.js";
20
- import { queryAppMessageTool } from "./tools/query-app-message-tool.js";
21
- import { queryMemoryDataTool } from "./tools/query-memory-data-tool.js";
22
- import { queryTodoTaskTool } from "./tools/query-todo-task-tool.js";
23
- import { loginTokenTool } from "./tools/login-token-tool.js";
24
4
  import { filterToolsByDevice } from "./tools/device-tool-map.js";
25
5
  import { getCurrentSessionContext } from "./tools/session-manager.js";
6
+ import { createAllTools } from "./tools/create-all-tools.js";
26
7
  import { logger } from "./utils/logger.js";
27
8
  /**
28
9
  * Xiaoyi Channel Plugin for OpenClaw.
@@ -63,8 +44,8 @@ export const xyPlugin = {
63
44
  },
64
45
  outbound: xyOutbound,
65
46
  agentTools: () => {
66
- const allTools = [locationTool, callDeviceTool, getNoteToolSchemaTool, getCalendarToolSchemaTool, getContactToolSchemaTool, getPhotoToolSchemaTool, xiaoyiGuiTool, getDeviceFileToolSchemaTool, getAlarmToolSchemaTool, getCollectionToolSchemaTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, saveSelfEvolutionSkillTool, getEmailToolSchemaTool, queryAppMessageTool, queryMemoryDataTool, queryTodoTaskTool, loginTokenTool];
67
47
  const ctx = getCurrentSessionContext();
48
+ const allTools = createAllTools(ctx);
68
49
  const filtered = filterToolsByDevice(allTools, ctx?.deviceType);
69
50
  logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
70
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.
@@ -38,16 +44,17 @@ export function getXYWebSocketManager(config) {
38
44
  * Disconnects the manager and removes it from the cache.
39
45
  */
40
46
  export function removeXYWebSocketManager(config) {
47
+ const log = runtime?.log ?? console.log;
41
48
  const cacheKey = `${config.apiKey}-${config.agentId}`;
42
49
  const manager = wsManagerCache.get(cacheKey);
43
50
  if (manager) {
44
- console.log(`🗑️ [WS-MANAGER-CACHE] Removing manager from cache: ${cacheKey}`);
51
+ log(`🗑️ [WS-MANAGER-CACHE] Removing manager from cache: ${cacheKey}`);
45
52
  manager.disconnect();
46
53
  wsManagerCache.delete(cacheKey);
47
- console.log(`🗑️ [WS-MANAGER-CACHE] Manager removed, remaining managers: ${wsManagerCache.size}`);
54
+ log(`🗑️ [WS-MANAGER-CACHE] Manager removed, remaining managers: ${wsManagerCache.size}`);
48
55
  }
49
56
  else {
50
- console.log(`⚠️ [WS-MANAGER-CACHE] Manager not found in cache: ${cacheKey}`);
57
+ log(`⚠️ [WS-MANAGER-CACHE] Manager not found in cache: ${cacheKey}`);
51
58
  }
52
59
  }
53
60
  /**
@@ -72,36 +79,37 @@ export function getCachedManagerCount() {
72
79
  * Helps identify connection issues and orphan connections.
73
80
  */
74
81
  export function diagnoseAllManagers() {
75
- console.log(`Total cached managers: ${wsManagerCache.size}`);
82
+ const log = runtime?.log ?? console.log;
83
+ log(`Total cached managers: ${wsManagerCache.size}`);
76
84
  if (wsManagerCache.size === 0) {
77
- console.log("ℹ️ No managers in cache");
85
+ log("ℹ️ No managers in cache");
78
86
  return;
79
87
  }
80
88
  let orphanCount = 0;
81
89
  wsManagerCache.forEach((manager, key) => {
82
90
  const diag = manager.getConnectionDiagnostics();
83
- console.log(` Total event listeners on manager: ${diag.totalEventListeners}`);
91
+ log(` Total event listeners on manager: ${diag.totalEventListeners}`);
84
92
  // Connection
85
- console.log(` 🔌 Connection:`);
86
- console.log(` - Exists: ${diag.connection.exists}`);
87
- console.log(` - ReadyState: ${diag.connection.readyState}`);
88
- console.log(` - State connected/ready: ${diag.connection.stateConnected}/${diag.connection.stateReady}`);
89
- console.log(` - Reconnect attempts: ${diag.connection.reconnectAttempts}`);
90
- console.log(` - Listeners on WebSocket: ${diag.connection.listenerCount}`);
91
- console.log(` - Heartbeat active: ${diag.connection.heartbeatActive}`);
92
- console.log(` - Has reconnect timer: ${diag.connection.hasReconnectTimer}`);
93
+ log(` 🔌 Connection:`);
94
+ log(` - Exists: ${diag.connection.exists}`);
95
+ log(` - ReadyState: ${diag.connection.readyState}`);
96
+ log(` - State connected/ready: ${diag.connection.stateConnected}/${diag.connection.stateReady}`);
97
+ log(` - Reconnect attempts: ${diag.connection.reconnectAttempts}`);
98
+ log(` - Listeners on WebSocket: ${diag.connection.listenerCount}`);
99
+ log(` - Heartbeat active: ${diag.connection.heartbeatActive}`);
100
+ log(` - Has reconnect timer: ${diag.connection.hasReconnectTimer}`);
93
101
  if (diag.connection.isOrphan) {
94
- console.log(` ⚠️ ORPHAN CONNECTION DETECTED!`);
102
+ log(` ⚠️ ORPHAN CONNECTION DETECTED!`);
95
103
  orphanCount++;
96
104
  }
97
- console.log("");
105
+ log("");
98
106
  });
99
107
  if (orphanCount > 0) {
100
- console.log(`⚠️ Total orphan connections found: ${orphanCount}`);
101
- console.log(`💡 Suggestion: These connections should be cleaned up`);
108
+ log(`⚠️ Total orphan connections found: ${orphanCount}`);
109
+ log(`💡 Suggestion: These connections should be cleaned up`);
102
110
  }
103
111
  else {
104
- console.log(`✅ No orphan connections found`);
112
+ log(`✅ No orphan connections found`);
105
113
  }
106
114
  }
107
115
  /**
@@ -109,17 +117,18 @@ export function diagnoseAllManagers() {
109
117
  * Returns the number of managers that had orphan connections.
110
118
  */
111
119
  export function cleanupOrphanConnections() {
120
+ const log = runtime?.log ?? console.log;
112
121
  let cleanedCount = 0;
113
122
  wsManagerCache.forEach((manager, key) => {
114
123
  const diag = manager.getConnectionDiagnostics();
115
124
  if (diag.connection.isOrphan) {
116
- console.log(`🧹 Cleaning up orphan connections in manager: ${key}`);
125
+ log(`🧹 Cleaning up orphan connections in manager: ${key}`);
117
126
  manager.disconnect();
118
127
  cleanedCount++;
119
128
  }
120
129
  });
121
130
  if (cleanedCount > 0) {
122
- console.log(`🧹 Cleaned up ${cleanedCount} manager(s) with orphan connections`);
131
+ log(`🧹 Cleaned up ${cleanedCount} manager(s) with orphan connections`);
123
132
  }
124
133
  return cleanedCount;
125
134
  }
@@ -4,12 +4,13 @@ import { URL } from "node:url";
4
4
  import { randomBytes } from "node:crypto";
5
5
  import { getCsplConfig } from "./config.js";
6
6
  import { DEFAULT_HTTP_PORT, HTTP_STATUS_BAD_REQUEST } from "./constants.js";
7
+ import { logger } from "../utils/logger.js";
7
8
  function generateTraceId() {
8
9
  return randomBytes(16).toString("hex");
9
10
  }
10
11
  function buildHeaders(config) {
11
12
  const traceId = generateTraceId();
12
- console.log(`[SENTINEL HOOK] trace-id: ${traceId}`);
13
+ logger.log(`[SENTINEL HOOK] trace-id: ${traceId}`);
13
14
  return {
14
15
  "x-hag-trace-id": traceId,
15
16
  "x-uid": config.uid,
@@ -65,21 +66,21 @@ export async function callCsplApi(questionText, cfg) {
65
66
  res.on("end", () => {
66
67
  try {
67
68
  const result = parseResponse(data);
68
- console.log(`[SENTINEL HOOK] ✅ 请求成功`);
69
+ logger.log(`[SENTINEL HOOK] ✅ 请求成功`);
69
70
  resolve(result);
70
71
  }
71
72
  catch (e) {
72
- console.error(`[SENTINEL HOOK] ❌ 请求失败: ${e instanceof Error ? e.message : String(e)}`);
73
+ logger.error(`[SENTINEL HOOK] ❌ 请求失败: ${e instanceof Error ? e.message : String(e)}`);
73
74
  reject(e);
74
75
  }
75
76
  });
76
77
  });
77
78
  req.on("error", (error) => {
78
- console.error(`[SENTINEL HOOK] ❌ 请求错误: ${error instanceof Error ? error.message : String(error)}`);
79
+ logger.error(`[SENTINEL HOOK] ❌ 请求错误: ${error instanceof Error ? error.message : String(error)}`);
79
80
  reject(error);
80
81
  });
81
82
  req.on("timeout", () => {
82
- console.error(`[SENTINEL HOOK] ⏰ 请求超时 (${config.api.timeout}ms)`);
83
+ logger.error(`[SENTINEL HOOK] ⏰ 请求超时 (${config.api.timeout}ms)`);
83
84
  req.destroy();
84
85
  reject(new Error("[SENTINEL HOOK] Request timeout"));
85
86
  });
@@ -2,6 +2,7 @@
2
2
  import fetch from "node-fetch";
3
3
  import fs from "fs/promises";
4
4
  import path from "path";
5
+ import { logger } from "./utils/logger.js";
5
6
  /**
6
7
  * Download a file from URL to local path.
7
8
  */
@@ -19,10 +20,10 @@ export async function downloadFile(url, destPath) {
19
20
  }
20
21
  catch (error) {
21
22
  if (error.name === 'AbortError') {
22
- console.log(`Download timeout (30s) for ${url}`);
23
+ logger.log(`Download timeout (30s) for ${url}`);
23
24
  throw new Error(`Download timeout after 30 seconds`);
24
25
  }
25
- console.log(`Failed to download file from ${url}:`);
26
+ logger.log(`Failed to download file from ${url}:`);
26
27
  throw error;
27
28
  }
28
29
  finally {
@@ -51,7 +52,7 @@ export async function downloadFilesFromParts(fileParts, tempDir = "/tmp/xy_chann
51
52
  });
52
53
  }
53
54
  catch (error) {
54
- console.log(`Failed to download file ${name}:`);
55
+ logger.log(`Failed to download file ${name}:`);
55
56
  // Continue with other files
56
57
  }
57
58
  }