@ynhcj/xiaoyi-channel 0.0.117-beta → 0.0.117-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 (132) hide show
  1. package/dist/index.d.ts +4 -5
  2. package/dist/index.js +72 -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 +80 -72
  7. package/dist/src/channel.js +2 -17
  8. package/dist/src/client.d.ts +1 -5
  9. package/dist/src/client.js +32 -37
  10. package/dist/src/cspl/call-api.js +6 -5
  11. package/dist/src/file-download.js +4 -3
  12. package/dist/src/file-upload.js +19 -18
  13. package/dist/src/formatter.js +32 -44
  14. package/dist/src/heartbeat.js +4 -3
  15. package/dist/src/login-token-handler.js +7 -8
  16. package/dist/src/message-queue.js +2 -1
  17. package/dist/src/monitor.js +62 -41
  18. package/dist/src/outbound.js +22 -18
  19. package/dist/src/provider.d.ts +1 -1
  20. package/dist/src/provider.js +120 -30
  21. package/dist/src/push.js +16 -15
  22. package/dist/src/reply-dispatcher.js +47 -48
  23. package/dist/src/runtime.d.ts +3 -11
  24. package/dist/src/runtime.js +6 -18
  25. package/dist/src/self-evolution-handler.js +11 -14
  26. package/dist/src/self-evolution-tool-result-nudge.d.ts +3 -0
  27. package/dist/src/self-evolution-tool-result-nudge.js +96 -0
  28. package/dist/src/skill-retriever/hooks.js +7 -16
  29. package/dist/src/skill-retriever/tool-search.js +16 -17
  30. package/dist/src/steer-injector.js +1 -1
  31. package/dist/src/task-manager.js +6 -1
  32. package/dist/src/tools/calendar-tool.d.ts +2 -1
  33. package/dist/src/tools/calendar-tool.js +116 -116
  34. package/dist/src/tools/call-device-tool.d.ts +2 -1
  35. package/dist/src/tools/call-device-tool.js +126 -103
  36. package/dist/src/tools/call-phone-tool.d.ts +2 -1
  37. package/dist/src/tools/call-phone-tool.js +113 -113
  38. package/dist/src/tools/create-alarm-tool.d.ts +2 -1
  39. package/dist/src/tools/create-alarm-tool.js +231 -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 +135 -135
  44. package/dist/src/tools/get-alarm-tool-schema.d.ts +2 -1
  45. package/dist/src/tools/get-alarm-tool-schema.js +16 -10
  46. package/dist/src/tools/get-calendar-tool-schema.d.ts +2 -1
  47. package/dist/src/tools/get-calendar-tool-schema.js +12 -8
  48. package/dist/src/tools/get-collection-tool-schema.d.ts +2 -1
  49. package/dist/src/tools/get-collection-tool-schema.js +11 -9
  50. package/dist/src/tools/get-contact-tool-schema.d.ts +2 -1
  51. package/dist/src/tools/get-contact-tool-schema.js +16 -10
  52. package/dist/src/tools/get-device-file-tool-schema.d.ts +2 -1
  53. package/dist/src/tools/get-device-file-tool-schema.js +13 -9
  54. package/dist/src/tools/get-email-tool-schema.d.ts +2 -1
  55. package/dist/src/tools/get-email-tool-schema.js +11 -8
  56. package/dist/src/tools/get-note-tool-schema.d.ts +2 -1
  57. package/dist/src/tools/get-note-tool-schema.js +14 -9
  58. package/dist/src/tools/get-photo-tool-schema.d.ts +2 -1
  59. package/dist/src/tools/get-photo-tool-schema.js +12 -9
  60. package/dist/src/tools/image-reading-tool.d.ts +3 -2
  61. package/dist/src/tools/image-reading-tool.js +82 -162
  62. package/dist/src/tools/location-tool.d.ts +2 -1
  63. package/dist/src/tools/location-tool.js +91 -91
  64. package/dist/src/tools/login-token-tool.d.ts +2 -1
  65. package/dist/src/tools/login-token-tool.js +113 -116
  66. package/dist/src/tools/modify-alarm-tool.d.ts +2 -1
  67. package/dist/src/tools/modify-alarm-tool.js +236 -236
  68. package/dist/src/tools/modify-note-tool.d.ts +2 -1
  69. package/dist/src/tools/modify-note-tool.js +108 -108
  70. package/dist/src/tools/note-tool.d.ts +2 -1
  71. package/dist/src/tools/note-tool.js +107 -107
  72. package/dist/src/tools/query-app-message-tool.d.ts +2 -1
  73. package/dist/src/tools/query-app-message-tool.js +112 -111
  74. package/dist/src/tools/query-memory-data-tool.d.ts +2 -1
  75. package/dist/src/tools/query-memory-data-tool.js +113 -112
  76. package/dist/src/tools/query-todo-task-tool.d.ts +2 -1
  77. package/dist/src/tools/query-todo-task-tool.js +107 -106
  78. package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -1
  79. package/dist/src/tools/save-file-to-phone-tool.js +131 -131
  80. package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -1
  81. package/dist/src/tools/save-media-to-gallery-tool.js +138 -138
  82. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -1
  83. package/dist/src/tools/save-self-evolution-skill-tool.js +194 -196
  84. package/dist/src/tools/search-alarm-tool.d.ts +2 -1
  85. package/dist/src/tools/search-alarm-tool.js +175 -175
  86. package/dist/src/tools/search-calendar-tool.d.ts +2 -1
  87. package/dist/src/tools/search-calendar-tool.js +149 -149
  88. package/dist/src/tools/search-contact-tool.d.ts +2 -1
  89. package/dist/src/tools/search-contact-tool.js +102 -102
  90. package/dist/src/tools/search-email-tool.d.ts +2 -1
  91. package/dist/src/tools/search-email-tool.js +111 -111
  92. package/dist/src/tools/search-file-tool.d.ts +2 -1
  93. package/dist/src/tools/search-file-tool.js +103 -103
  94. package/dist/src/tools/search-message-tool.d.ts +2 -1
  95. package/dist/src/tools/search-message-tool.js +104 -104
  96. package/dist/src/tools/search-note-tool.d.ts +2 -1
  97. package/dist/src/tools/search-note-tool.js +99 -99
  98. package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -1
  99. package/dist/src/tools/search-photo-gallery-tool.js +38 -38
  100. package/dist/src/tools/send-email-tool.d.ts +2 -1
  101. package/dist/src/tools/send-email-tool.js +109 -108
  102. package/dist/src/tools/send-file-to-user-tool.d.ts +2 -1
  103. package/dist/src/tools/send-file-to-user-tool.js +157 -155
  104. package/dist/src/tools/send-message-tool.d.ts +2 -1
  105. package/dist/src/tools/send-message-tool.js +123 -123
  106. package/dist/src/tools/session-helper.d.ts +24 -0
  107. package/dist/src/tools/session-helper.js +45 -0
  108. package/dist/src/tools/session-manager.d.ts +29 -6
  109. package/dist/src/tools/session-manager.js +134 -19
  110. package/dist/src/tools/upload-file-tool.d.ts +2 -1
  111. package/dist/src/tools/upload-file-tool.js +82 -82
  112. package/dist/src/tools/upload-photo-tool.d.ts +2 -1
  113. package/dist/src/tools/upload-photo-tool.js +73 -73
  114. package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -1
  115. package/dist/src/tools/xiaoyi-add-collection-tool.js +147 -147
  116. package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -1
  117. package/dist/src/tools/xiaoyi-collection-tool.js +115 -115
  118. package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -1
  119. package/dist/src/tools/xiaoyi-delete-collection-tool.js +128 -128
  120. package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -1
  121. package/dist/src/tools/xiaoyi-gui-tool.js +89 -88
  122. package/dist/src/trigger-handler.js +8 -9
  123. package/dist/src/utils/logger.js +105 -19
  124. package/dist/src/utils/self-evolution-manager.d.ts +5 -0
  125. package/dist/src/utils/self-evolution-manager.js +45 -23
  126. package/dist/src/utils/throw.d.ts +5 -0
  127. package/dist/src/utils/throw.js +10 -0
  128. package/dist/src/websocket.js +35 -31
  129. package/dist/src/xy-session-store.d.ts +79 -0
  130. package/dist/src/xy-session-store.js +153 -0
  131. package/openclaw.plugin.json +2 -2
  132. package/package.json +7 -6
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,98 +1,88 @@
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";
5
5
  import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH, STEER_ABORT_MESSAGE, } from "./src/cspl/constants.js";
6
6
  import { extractResultText, parseSecurityResult, processText, validateAndTruncateText, } from "./src/cspl/utils.js";
7
7
  import { setXYRuntime } from "./src/runtime.js";
8
+ import { logger } from "./src/utils/logger.js";
8
9
  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";
10
+ import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
11
11
  import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
12
12
  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;
13
+ function registerFullHooks(api) {
14
+ // SKILL RETRIEVER HOOK: before_prompt_build hook
15
+ const pluginConfig = api.pluginConfig || {};
16
+ const skillRetrieverConfig = normalizeToolRetrieverConfig({
17
+ enabled: pluginConfig.skillRetrieverEnabled ?? true,
18
+ maxTools: pluginConfig.skillRetrieverMaxTools ?? 2,
19
+ includeUninstalledOnly: true,
20
+ envFilePath: "~/.openclaw/.xiaoyienv",
21
+ timeoutMs: pluginConfig.skillRetrieverTimeoutMs ?? 1000,
22
+ });
23
+ const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
24
+ api.on("before_prompt_build", beforePromptBuildHandler);
25
+ registerSelfEvolutionToolResultNudge(api);
26
+ api.on("after_tool_call", async (event, ctx) => {
27
+ if (!ALLOWED_TOOLS.includes(event.toolName)) {
28
+ return;
29
+ }
30
+ logger.log(`[SENTINEL HOOK] after_tool_call triggered: toolName=${event.toolName}, sessionKey=${ctx.sessionKey ?? "none"}`);
31
+ try {
32
+ const resultText = extractResultText(event, event.toolName);
33
+ const resultLength = resultText.length;
34
+ if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
35
+ return;
36
+ }
37
+ const questionText = {
38
+ subSceneID: "TOOL_OUTPUT",
39
+ tool: event.toolName,
40
+ output: [{ content: "" }],
41
+ };
42
+ const originText = processText(resultText);
43
+ questionText.output[0].content = originText;
44
+ let finalJson = JSON.stringify(questionText);
45
+ if (finalJson.length > MAX_TEXT_LENGTH) {
46
+ const diff = finalJson.length - MAX_TEXT_LENGTH;
47
+ const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
48
+ questionText.output[0].content = trimmed;
49
+ finalJson = JSON.stringify(questionText);
50
+ }
51
+ const response = await callCsplApi(finalJson, api.config);
52
+ const result = parseSecurityResult(response);
53
+ logger.log(`[SENTINEL HOOK] Security result: status=${result.status}`);
54
+ if (result.status === "REJECT") {
55
+ await tryInjectSteer(ctx.sessionKey, STEER_ABORT_MESSAGE);
56
+ }
57
+ }
58
+ catch (err) {
59
+ logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
60
+ }
61
+ });
25
62
  }
26
- const plugin = {
63
+ export default definePluginEntry({
27
64
  id: "xiaoyi-channel",
28
65
  name: "Xiaoyi Channel",
29
66
  description: "Xiaoyi channel plugin - Xiaoyi A2A protocol integration",
30
- configSchema: emptyPluginConfigSchema(),
31
67
  register(api) {
32
- setXYRuntime(api.runtime);
33
- api.registerChannel({ plugin: xyPlugin });
68
+ // Always register the provider so wrapStreamFn/prepareExtraParams work
69
+ // in ALL registration modes (not just "full").
34
70
  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
- });
71
+ if (api.registrationMode === "cli-metadata") {
72
+ return;
73
+ }
74
+ if (api.registrationMode === "tool-discovery") {
75
+ registerFullHooks(api);
76
+ return;
77
+ }
78
+ // Register channel plugin and set runtime
79
+ api.registerChannel({ plugin: xyPlugin });
80
+ setXYRuntime(api.runtime);
81
+ if (api.registrationMode === "discovery") {
82
+ return;
83
+ }
84
+ if (api.registrationMode === "full") {
85
+ registerFullHooks(api);
86
+ }
96
87
  },
97
- };
98
- export default plugin;
88
+ });
@@ -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
@@ -14,6 +14,7 @@ import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
14
14
  import { saveRuntimeInfo } from "./utils/runtime-manager.js";
15
15
  import { toolCallNudgeManager } from "./utils/tool-call-nudge-manager.js";
16
16
  import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActiveTask, } from "./task-manager.js";
17
+ import { logger } from "./utils/logger.js";
17
18
  /**
18
19
  * Handle an incoming A2A message.
19
20
  * This is the main entry point for message processing.
@@ -21,8 +22,6 @@ import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActive
21
22
  */
22
23
  export async function handleXYMessage(params) {
23
24
  const { cfg, runtime, message, accountId, webSocketSessionId } = params;
24
- const log = runtime?.log ?? console.log;
25
- const error = runtime?.error ?? console.error;
26
25
  // 每次收到消息时更新缓存,供 steer 注入使用
27
26
  setCachedContext(cfg, runtime, accountId);
28
27
  // Get runtime (already validated in monitor.ts, but get reference for use)
@@ -36,7 +35,7 @@ export async function handleXYMessage(params) {
36
35
  if (!sessionId) {
37
36
  throw new Error("clearContext request missing sessionId in params");
38
37
  }
39
- log(`Clear context request for session ${sessionId}`);
38
+ logger.log(`Clear context request for session ${sessionId}`);
40
39
  const config = resolveXYConfig(cfg);
41
40
  await sendClearContextResponse({
42
41
  config,
@@ -52,7 +51,7 @@ export async function handleXYMessage(params) {
52
51
  if (!sessionId) {
53
52
  throw new Error("tasks/cancel request missing sessionId in params");
54
53
  }
55
- log(`Tasks cancel request for session ${sessionId}, task ${taskId}`);
54
+ logger.log(`Tasks cancel request for session ${sessionId}, task ${taskId}`);
56
55
  const config = resolveXYConfig(cfg);
57
56
  await sendTasksCancelResponse({
58
57
  config,
@@ -68,18 +67,18 @@ export async function handleXYMessage(params) {
68
67
  // 如果消息中包含 Trigger 事件数据,直接返回 pushData 内容,不走正常流程
69
68
  const triggerData = extractTriggerData(parsed.parts);
70
69
  if (triggerData) {
71
- log(`[BOT] 📌 Detected Trigger message with pushDataId: ${triggerData.pushDataId}`);
72
- log(`[BOT] - Session ID: ${parsed.sessionId}`);
73
- log(`[BOT] - Task ID: ${parsed.taskId}`);
70
+ logger.log(`[BOT] 📌 Detected Trigger message with pushDataId: ${triggerData.pushDataId}`);
71
+ logger.log(`[BOT] - Session ID: ${parsed.sessionId}`);
72
+ logger.log(`[BOT] - Task ID: ${parsed.taskId}`);
74
73
  try {
75
74
  // 读取 pushData
76
75
  const pushDataItem = await getPushDataById(triggerData.pushDataId);
77
76
  if (!pushDataItem) {
78
- error(`[BOT] ❌ pushData not found for ID: ${triggerData.pushDataId}`);
77
+ logger.error(`[BOT] ❌ pushData not found for ID: ${triggerData.pushDataId}`);
79
78
  return;
80
79
  }
81
- log(`[BOT] ✅ Found pushData, sending direct response`);
82
- log(`[BOT] - pushDataId: ${pushDataItem.pushDataId}`);
80
+ logger.log(`[BOT] ✅ Found pushData, sending direct response`);
81
+ logger.log(`[BOT] - pushDataId: ${pushDataItem.pushDataId}`);
83
82
  const config = resolveXYConfig(cfg);
84
83
  // 直接发送响应(final=true,不走 openclaw 流程)
85
84
  await sendA2AResponse({
@@ -91,11 +90,11 @@ export async function handleXYMessage(params) {
91
90
  append: false,
92
91
  final: true,
93
92
  });
94
- log(`[BOT] ✅ Trigger response sent successfully, exiting early`);
93
+ logger.log(`[BOT] ✅ Trigger response sent successfully, exiting early`);
95
94
  return; // 提前返回,不继续处理
96
95
  }
97
96
  catch (err) {
98
- error(`[BOT] ❌ Failed to handle Trigger message:`, err);
97
+ logger.error(`[BOT] ❌ Failed to handle Trigger message:`, err);
99
98
  return;
100
99
  }
101
100
  }
@@ -104,9 +103,9 @@ export async function handleXYMessage(params) {
104
103
  const isSteerMode = cfg.messages?.queue?.mode === "steer";
105
104
  const isSecondMessage = isSteerMode && hasActiveTask(parsed.sessionId);
106
105
  if (isSecondMessage) {
107
- log(`[BOT] 🔄 STEER MODE - Second message detected (will be follower)`);
108
- log(`[BOT] - Session: ${parsed.sessionId}`);
109
- log(`[BOT] - New taskId: ${parsed.taskId} (will replace current)`);
106
+ logger.log(`[BOT] 🔄 STEER MODE - Second message detected (will be follower)`);
107
+ logger.log(`[BOT] - Session: ${parsed.sessionId}`);
108
+ logger.log(`[BOT] - New taskId: ${parsed.taskId} (will replace current)`);
110
109
  }
111
110
  // 🔑 注册taskId(第二条消息会覆盖第一条的taskId)
112
111
  const { isUpdate, refCount } = registerTaskId(parsed.sessionId, parsed.taskId, parsed.messageId, { incrementRef: true } // 增加引用计数
@@ -114,32 +113,32 @@ export async function handleXYMessage(params) {
114
113
  // 🔑 如果是第一条消息,锁定taskId防止被过早清理
115
114
  if (!isUpdate) {
116
115
  lockTaskId(parsed.sessionId);
117
- log(`[BOT] 🔒 Locked taskId for first message`);
116
+ logger.log(`[BOT] 🔒 Locked taskId for first message`);
118
117
  }
119
118
  // Extract and update push_id if present
120
119
  const pushId = extractPushId(parsed.parts);
121
120
  if (pushId) {
122
- log(`[BOT] 📌 Extracted push_id from user message`);
121
+ logger.log(`[BOT] 📌 Extracted push_id from user message`);
123
122
  configManager.updatePushId(parsed.sessionId, pushId);
124
123
  // 持久化 pushId 到本地文件(异步,不阻塞主流程)
125
124
  addPushId(pushId).catch((err) => {
126
- error(`[BOT] Failed to persist pushId:`, err);
125
+ logger.error(`[BOT] Failed to persist pushId:`, err);
127
126
  });
128
127
  }
129
128
  else {
130
- log(`[BOT] ℹ️ No push_id found in message, will use config default`);
129
+ logger.log(`[BOT] ℹ️ No push_id found in message, will use config default`);
131
130
  }
132
131
  // Extract deviceType if present (same level as push_id in systemVariables)
133
132
  const deviceType = extractDeviceType(parsed.parts);
134
133
  if (deviceType) {
135
- log(`[BOT] 📱 Extracted deviceType from user message: ${deviceType}`);
134
+ logger.log(`[BOT] 📱 Extracted deviceType from user message: ${deviceType}`);
136
135
  }
137
136
  // 保存 runtime 信息到 .xiaoyiruntime 文件(异步,不阻塞主流程)
138
137
  saveRuntimeInfo(webSocketSessionId || parsed.sessionId, // SESSION_ID (WebSocket 层级,如果没有则 fallback)
139
138
  parsed.sessionId, // CONVERSATION_ID (param 里的 sessionId)
140
139
  parsed.taskId // TASK_ID (param.id)
141
140
  ).catch((err) => {
142
- error(`[BOT] Failed to save runtime info:`, err);
141
+ logger.error(`[BOT] Failed to save runtime info:`, err);
143
142
  });
144
143
  // Resolve configuration (needed for status updates)
145
144
  const config = resolveXYConfig(cfg);
@@ -155,16 +154,17 @@ export async function handleXYMessage(params) {
155
154
  id: parsed.sessionId, // ✅ Use sessionId to share context within the same conversation session
156
155
  },
157
156
  });
158
- log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
157
+ logger.log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
159
158
  registerSession(route.sessionKey, {
160
159
  config,
161
160
  sessionId: parsed.sessionId,
162
161
  taskId: parsed.taskId,
163
162
  messageId: parsed.messageId,
164
163
  agentId: route.accountId,
164
+ deviceType,
165
165
  });
166
166
  // 🔑 发送初始状态更新(第二条消息也要发,用新taskId)
167
- log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
167
+ logger.log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
168
168
  void sendStatusUpdate({
169
169
  config,
170
170
  sessionId: parsed.sessionId,
@@ -173,7 +173,7 @@ export async function handleXYMessage(params) {
173
173
  text: isSecondMessage ? "新消息已接收,正在处理..." : "任务正在处理中,请稍候~",
174
174
  state: "working",
175
175
  }).catch((err) => {
176
- error(`Failed to send initial status update:`, err);
176
+ logger.error(`Failed to send initial status update:`, err);
177
177
  });
178
178
  // Extract text and files from parts
179
179
  const text = extractTextFromParts(parsed.parts);
@@ -183,24 +183,24 @@ export async function handleXYMessage(params) {
183
183
  const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
184
184
  if (selfEvolutionEnabled && shouldNudgeForSelfEvolutionKeyword(textForAgent)) {
185
185
  const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(route.sessionKey);
186
- log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
186
+ logger.log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
187
187
  if (shouldNudge) {
188
188
  const augmented = appendSelfEvolutionKeywordNudge(textForAgent);
189
189
  textForAgent = augmented.text;
190
190
  if (augmented.appended) {
191
- log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
191
+ logger.log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
192
192
  }
193
193
  }
194
194
  }
195
195
  }
196
196
  catch (selfEvolutionError) {
197
- error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
197
+ logger.error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
198
198
  }
199
199
  }
200
200
  const fileParts = extractFileParts(parsed.parts);
201
201
  // Download files to local disk
202
202
  const downloadedFiles = await downloadFilesFromParts(fileParts);
203
- console.log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
203
+ logger.log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
204
204
  const mediaPayload = buildXYMediaPayload(downloadedFiles);
205
205
  // Resolve envelope format options (following feishu pattern)
206
206
  const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
@@ -233,7 +233,7 @@ export async function handleXYMessage(params) {
233
233
  SenderId: parsed.sessionId,
234
234
  Provider: "xiaoyi-channel",
235
235
  Surface: "xiaoyi-channel",
236
- MessageSid: parsed.messageId,
236
+ MessageSid: `${parsed.taskId}_${deviceType}`,
237
237
  Timestamp: Date.now(),
238
238
  WasMentioned: false,
239
239
  CommandAuthorized: true,
@@ -243,8 +243,8 @@ export async function handleXYMessage(params) {
243
243
  ...mediaPayload,
244
244
  });
245
245
  // 🔑 创建dispatcher(dispatcher会自动使用动态taskId)
246
- log(`[BOT-DISPATCHER] 🎯 Creating reply dispatcher`);
247
- log(`[BOT-DISPATCHER] - taskId: ${parsed.taskId}`);
246
+ logger.log(`[BOT-DISPATCHER] 🎯 Creating reply dispatcher`);
247
+ logger.log(`[BOT-DISPATCHER] - taskId: ${parsed.taskId}`);
248
248
  const { dispatcher, replyOptions, markDispatchIdle, startStatusInterval } = createXYReplyDispatcher({
249
249
  cfg,
250
250
  runtime,
@@ -258,7 +258,7 @@ export async function handleXYMessage(params) {
258
258
  // 第二条消息会很快返回,不需要定时器
259
259
  if (!isSecondMessage) {
260
260
  startStatusInterval();
261
- log(`[BOT-DISPATCHER] ✅ Status interval started for first message`);
261
+ logger.log(`[BOT-DISPATCHER] ✅ Status interval started for first message`);
262
262
  }
263
263
  // Build session context for AsyncLocalStorage
264
264
  const sessionContext = {
@@ -269,66 +269,74 @@ export async function handleXYMessage(params) {
269
269
  agentId: route.accountId,
270
270
  deviceType,
271
271
  };
272
- log(`[BOT-DISPATCH] ⏳ withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
272
+ logger.log(`[BOT-DISPATCH] ⏳ withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
273
273
  await core.channel.reply.withReplyDispatcher({
274
274
  dispatcher,
275
275
  onSettled: () => {
276
- log(`[BOT] 🏁 onSettled called for session: ${route.sessionKey}`);
277
- log(`[BOT] - isSecondMessage: ${isSecondMessage}`);
276
+ logger.log(`[BOT] 🏁 onSettled called for session: ${route.sessionKey}`);
277
+ logger.log(`[BOT] - isSecondMessage: ${isSecondMessage}`);
278
278
  // 🔑 减少引用计数
279
279
  decrementTaskIdRef(parsed.sessionId);
280
280
  // 🔑 如果是第一条消息完成,解锁
281
281
  if (!isSecondMessage) {
282
282
  unlockTaskId(parsed.sessionId);
283
- log(`[BOT] 🔓 Unlocked taskId (first message completed)`);
283
+ logger.log(`[BOT] 🔓 Unlocked taskId (first message completed)`);
284
284
  }
285
285
  // 减少session引用计数
286
286
  unregisterSession(route.sessionKey);
287
- log(`[BOT] ✅ Cleanup completed`);
287
+ logger.log(`[BOT] ✅ Cleanup completed`);
288
+ },
289
+ run: () => {
290
+ // 🔐 Use AsyncLocalStorage to provide session context to tools.
291
+ // runWithSessionContext returns after the sync part of dispatch
292
+ // (including agentTools + wrapStreamFn) has executed, so we
293
+ // signal init complete to release the global dispatch gate
294
+ // for the next session.
295
+ const dispatchPromise = runWithSessionContext(sessionContext, async () => {
296
+ logger.log(`[BOT-DISPATCH] ⏳ dispatchReplyFromConfig starting...`);
297
+ logger.log(`[BOT-DISPATCH] - sessionKey: ${ctxPayload.SessionKey}`);
298
+ logger.log(`[BOT-DISPATCH] - provider: ${ctxPayload.Provider}`);
299
+ logger.log(`[BOT-DISPATCH] - surface: ${ctxPayload.Surface}`);
300
+ logger.log(`[BOT-DISPATCH] - from: ${ctxPayload.From}`);
301
+ logger.log(`[BOT-DISPATCH] - body length: ${ctxPayload.Body?.length ?? 0}`);
302
+ try {
303
+ const result = await core.channel.reply.dispatchReplyFromConfig({
304
+ ctx: ctxPayload,
305
+ cfg,
306
+ dispatcher,
307
+ replyOptions,
308
+ });
309
+ logger.log(`[BOT-DISPATCH] ✅ dispatchReplyFromConfig returned`);
310
+ logger.log(`[BOT-DISPATCH] - result: ${JSON.stringify(result)}`);
311
+ return result;
312
+ }
313
+ catch (dispatchErr) {
314
+ logger.error(`[BOT-DISPATCH] ❌ dispatchReplyFromConfig threw`);
315
+ logger.error(`[BOT-DISPATCH] - error name: ${dispatchErr instanceof Error ? dispatchErr.name : "unknown"}`);
316
+ logger.error(`[BOT-DISPATCH] - error message: ${String(dispatchErr)}`);
317
+ logger.error(`[BOT-DISPATCH] - error stack: ${dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : "N/A"}`);
318
+ throw dispatchErr;
319
+ }
320
+ });
321
+ // Signal init complete — sync part (agentTools, wrapStreamFn) is done
322
+ params.onInitComplete?.();
323
+ return dispatchPromise;
288
324
  },
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
- }),
317
325
  });
318
- log(`[BOT] ✅ Dispatcher completed for session: ${parsed.sessionId}`);
319
- log(`xy: dispatch complete (session=${parsed.sessionId})`);
326
+ logger.log(`[BOT] ✅ Dispatcher completed for session: ${parsed.sessionId}`);
327
+ logger.log(`xy: dispatch complete (session=${parsed.sessionId})`);
320
328
  }
321
329
  catch (err) {
322
330
  // ✅ Only log error, don't re-throw to prevent gateway restart
323
- error("Failed to handle XY message:", err);
331
+ logger.error("Failed to handle XY message:", err);
324
332
  runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
325
- log(`[BOT] ❌ Error occurred, attempting cleanup...`);
333
+ logger.log(`[BOT] ❌ Error occurred, attempting cleanup...`);
326
334
  // 🔑 错误时也要清理taskId和session
327
335
  try {
328
336
  const params = message.params;
329
337
  const sessionId = params?.sessionId;
330
338
  if (sessionId) {
331
- log(`[BOT] 🧹 Cleaning up after error: ${sessionId}`);
339
+ logger.log(`[BOT] 🧹 Cleaning up after error: ${sessionId}`);
332
340
  // 清理 taskId
333
341
  decrementTaskIdRef(sessionId);
334
342
  unlockTaskId(sessionId);
@@ -344,11 +352,11 @@ export async function handleXYMessage(params) {
344
352
  },
345
353
  });
346
354
  unregisterSession(route.sessionKey);
347
- log(`[BOT] ✅ Cleanup completed after error`);
355
+ logger.log(`[BOT] ✅ Cleanup completed after error`);
348
356
  }
349
357
  }
350
358
  catch (cleanupErr) {
351
- log(`[BOT] ⚠️ Cleanup failed:`, cleanupErr);
359
+ logger.log(`[BOT] ⚠️ Cleanup failed:`, cleanupErr);
352
360
  // Ignore cleanup errors
353
361
  }
354
362
  // ❌ Don't re-throw: message processing error should not affect gateway stability
@@ -1,24 +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 { callDeviceTool } from "./tools/call-device-tool.js";
12
- import { getNoteToolSchemaTool } from "./tools/get-note-tool-schema.js";
13
- import { getCalendarToolSchemaTool } from "./tools/get-calendar-tool-schema.js";
14
- import { getContactToolSchemaTool } from "./tools/get-contact-tool-schema.js";
15
- import { getPhotoToolSchemaTool } from "./tools/get-photo-tool-schema.js";
16
- import { getDeviceFileToolSchemaTool } from "./tools/get-device-file-tool-schema.js";
17
- import { getAlarmToolSchemaTool } from "./tools/get-alarm-tool-schema.js";
18
- import { getCollectionToolSchemaTool } from "./tools/get-collection-tool-schema.js";
19
- import { loginTokenTool } from "./tools/login-token-tool.js";
20
4
  import { filterToolsByDevice } from "./tools/device-tool-map.js";
21
5
  import { getCurrentSessionContext } from "./tools/session-manager.js";
6
+ import { createAllTools } from "./tools/create-all-tools.js";
22
7
  import { logger } from "./utils/logger.js";
23
8
  /**
24
9
  * Xiaoyi Channel Plugin for OpenClaw.
@@ -59,8 +44,8 @@ export const xyPlugin = {
59
44
  },
60
45
  outbound: xyOutbound,
61
46
  agentTools: () => {
62
- const allTools = [locationTool, callDeviceTool, getNoteToolSchemaTool, getCalendarToolSchemaTool, getContactToolSchemaTool, getPhotoToolSchemaTool, xiaoyiGuiTool, getDeviceFileToolSchemaTool, getAlarmToolSchemaTool, getCollectionToolSchemaTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, saveSelfEvolutionSkillTool, loginTokenTool];
63
47
  const ctx = getCurrentSessionContext();
48
+ const allTools = createAllTools(ctx);
64
49
  const filtered = filterToolsByDevice(allTools, ctx?.deviceType);
65
50
  logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
66
51
  return filtered;
@@ -1,15 +1,11 @@
1
1
  import { XYWebSocketManager } from "./websocket.js";
2
2
  import type { XYChannelConfig } from "./types.js";
3
3
  import type { RuntimeEnv } from "openclaw/plugin-sdk";
4
- /**
5
- * Set the runtime for logging in client module.
6
- */
7
- export declare function setClientRuntime(rt: RuntimeEnv | undefined): void;
8
4
  /**
9
5
  * Get or create a WebSocket manager for the given configuration.
10
6
  * Reuses existing managers if config matches.
11
7
  */
12
- export declare function getXYWebSocketManager(config: XYChannelConfig): XYWebSocketManager;
8
+ export declare function getXYWebSocketManager(config: XYChannelConfig, runtime?: RuntimeEnv): XYWebSocketManager;
13
9
  /**
14
10
  * Remove a specific WebSocket manager from cache.
15
11
  * Disconnects the manager and removes it from the cache.