@ynhcj/xiaoyi-channel 0.0.95-beta → 0.0.95-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 (138) hide show
  1. package/dist/index.d.ts +6 -9
  2. package/dist/index.js +26 -21
  3. package/dist/src/bot.js +29 -4
  4. package/dist/src/channel.js +2 -19
  5. package/dist/src/client.js +33 -22
  6. package/dist/src/cspl/call-api.js +6 -5
  7. package/dist/src/file-download.js +4 -3
  8. package/dist/src/file-upload.js +19 -18
  9. package/dist/src/formatter.d.ts +2 -0
  10. package/dist/src/formatter.js +9 -28
  11. package/dist/src/heartbeat.js +1 -1
  12. package/dist/src/login-token-handler.d.ts +8 -0
  13. package/dist/src/login-token-handler.js +60 -0
  14. package/dist/src/message-queue.d.ts +17 -0
  15. package/dist/src/message-queue.js +51 -0
  16. package/dist/src/monitor.js +76 -13
  17. package/dist/src/outbound.js +19 -18
  18. package/dist/src/provider.d.ts +2 -1
  19. package/dist/src/provider.js +246 -38
  20. package/dist/src/push.js +16 -15
  21. package/dist/src/reply-dispatcher.js +12 -3
  22. package/dist/src/runtime.d.ts +3 -11
  23. package/dist/src/runtime.js +6 -18
  24. package/dist/src/self-evolution-handler.d.ts +7 -0
  25. package/dist/src/self-evolution-handler.js +140 -0
  26. package/dist/src/self-evolution-keyword.d.ts +9 -0
  27. package/dist/src/self-evolution-keyword.js +147 -0
  28. package/dist/src/self-evolution-tool-result-nudge.d.ts +3 -0
  29. package/dist/src/self-evolution-tool-result-nudge.js +96 -0
  30. package/dist/src/skill-retriever/config.d.ts +4 -0
  31. package/dist/src/skill-retriever/config.js +23 -0
  32. package/dist/src/skill-retriever/hooks.d.ts +22 -0
  33. package/dist/src/skill-retriever/hooks.js +83 -0
  34. package/dist/src/skill-retriever/tool-search.d.ts +16 -0
  35. package/dist/src/skill-retriever/tool-search.js +173 -0
  36. package/dist/src/skill-retriever/types.d.ts +36 -0
  37. package/dist/src/skill-retriever/types.js +1 -0
  38. package/dist/src/steer-injector.js +1 -1
  39. package/dist/src/task-manager.d.ts +4 -0
  40. package/dist/src/task-manager.js +12 -1
  41. package/dist/src/tools/calendar-tool.d.ts +2 -1
  42. package/dist/src/tools/calendar-tool.js +112 -116
  43. package/dist/src/tools/call-device-tool.d.ts +2 -1
  44. package/dist/src/tools/call-device-tool.js +126 -103
  45. package/dist/src/tools/call-phone-tool.d.ts +2 -1
  46. package/dist/src/tools/call-phone-tool.js +109 -113
  47. package/dist/src/tools/create-alarm-tool.d.ts +2 -1
  48. package/dist/src/tools/create-alarm-tool.js +227 -231
  49. package/dist/src/tools/create-all-tools.d.ts +16 -0
  50. package/dist/src/tools/create-all-tools.js +50 -0
  51. package/dist/src/tools/delete-alarm-tool.d.ts +2 -1
  52. package/dist/src/tools/delete-alarm-tool.js +131 -135
  53. package/dist/src/tools/get-alarm-tool-schema.d.ts +2 -1
  54. package/dist/src/tools/get-alarm-tool-schema.js +16 -10
  55. package/dist/src/tools/get-calendar-tool-schema.d.ts +2 -1
  56. package/dist/src/tools/get-calendar-tool-schema.js +12 -8
  57. package/dist/src/tools/get-collection-tool-schema.d.ts +2 -1
  58. package/dist/src/tools/get-collection-tool-schema.js +11 -9
  59. package/dist/src/tools/get-contact-tool-schema.d.ts +2 -1
  60. package/dist/src/tools/get-contact-tool-schema.js +16 -10
  61. package/dist/src/tools/get-device-file-tool-schema.d.ts +2 -1
  62. package/dist/src/tools/get-device-file-tool-schema.js +13 -9
  63. package/dist/src/tools/get-email-tool-schema.d.ts +2 -1
  64. package/dist/src/tools/get-email-tool-schema.js +11 -8
  65. package/dist/src/tools/get-note-tool-schema.d.ts +2 -1
  66. package/dist/src/tools/get-note-tool-schema.js +14 -9
  67. package/dist/src/tools/get-photo-tool-schema.d.ts +2 -1
  68. package/dist/src/tools/get-photo-tool-schema.js +12 -9
  69. package/dist/src/tools/image-reading-tool.d.ts +2 -1
  70. package/dist/src/tools/image-reading-tool.js +86 -90
  71. package/dist/src/tools/location-tool.d.ts +2 -1
  72. package/dist/src/tools/location-tool.js +87 -91
  73. package/dist/src/tools/login-token-tool.d.ts +6 -0
  74. package/dist/src/tools/login-token-tool.js +133 -0
  75. package/dist/src/tools/modify-alarm-tool.d.ts +2 -1
  76. package/dist/src/tools/modify-alarm-tool.js +232 -236
  77. package/dist/src/tools/modify-note-tool.d.ts +2 -1
  78. package/dist/src/tools/modify-note-tool.js +104 -108
  79. package/dist/src/tools/note-tool.d.ts +2 -1
  80. package/dist/src/tools/note-tool.js +103 -107
  81. package/dist/src/tools/query-app-message-tool.d.ts +2 -1
  82. package/dist/src/tools/query-app-message-tool.js +108 -111
  83. package/dist/src/tools/query-memory-data-tool.d.ts +2 -1
  84. package/dist/src/tools/query-memory-data-tool.js +109 -112
  85. package/dist/src/tools/query-todo-task-tool.d.ts +2 -1
  86. package/dist/src/tools/query-todo-task-tool.js +103 -106
  87. package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -1
  88. package/dist/src/tools/save-file-to-phone-tool.js +127 -131
  89. package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -1
  90. package/dist/src/tools/save-media-to-gallery-tool.js +134 -138
  91. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -0
  92. package/dist/src/tools/save-self-evolution-skill-tool.js +410 -0
  93. package/dist/src/tools/search-alarm-tool.d.ts +2 -1
  94. package/dist/src/tools/search-alarm-tool.js +171 -175
  95. package/dist/src/tools/search-calendar-tool.d.ts +2 -1
  96. package/dist/src/tools/search-calendar-tool.js +145 -149
  97. package/dist/src/tools/search-contact-tool.d.ts +2 -1
  98. package/dist/src/tools/search-contact-tool.js +98 -102
  99. package/dist/src/tools/search-email-tool.d.ts +2 -1
  100. package/dist/src/tools/search-email-tool.js +107 -111
  101. package/dist/src/tools/search-file-tool.d.ts +2 -1
  102. package/dist/src/tools/search-file-tool.js +99 -103
  103. package/dist/src/tools/search-message-tool.d.ts +2 -1
  104. package/dist/src/tools/search-message-tool.js +100 -104
  105. package/dist/src/tools/search-note-tool.d.ts +2 -1
  106. package/dist/src/tools/search-note-tool.js +95 -99
  107. package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -1
  108. package/dist/src/tools/search-photo-gallery-tool.js +34 -38
  109. package/dist/src/tools/send-email-tool.d.ts +2 -1
  110. package/dist/src/tools/send-email-tool.js +105 -108
  111. package/dist/src/tools/send-file-to-user-tool.d.ts +2 -1
  112. package/dist/src/tools/send-file-to-user-tool.js +154 -155
  113. package/dist/src/tools/send-message-tool.d.ts +2 -1
  114. package/dist/src/tools/send-message-tool.js +119 -123
  115. package/dist/src/tools/session-manager.d.ts +21 -6
  116. package/dist/src/tools/session-manager.js +147 -18
  117. package/dist/src/tools/upload-file-tool.d.ts +2 -1
  118. package/dist/src/tools/upload-file-tool.js +78 -82
  119. package/dist/src/tools/upload-photo-tool.d.ts +2 -1
  120. package/dist/src/tools/upload-photo-tool.js +69 -73
  121. package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -1
  122. package/dist/src/tools/xiaoyi-add-collection-tool.js +143 -147
  123. package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -1
  124. package/dist/src/tools/xiaoyi-collection-tool.js +111 -115
  125. package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -1
  126. package/dist/src/tools/xiaoyi-delete-collection-tool.js +124 -128
  127. package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -1
  128. package/dist/src/tools/xiaoyi-gui-tool.js +84 -88
  129. package/dist/src/utils/logger.js +20 -18
  130. package/dist/src/utils/runtime-manager.js +24 -2
  131. package/dist/src/utils/self-evolution-manager.d.ts +10 -0
  132. package/dist/src/utils/self-evolution-manager.js +69 -0
  133. package/dist/src/utils/tool-call-nudge-manager.d.ts +16 -0
  134. package/dist/src/utils/tool-call-nudge-manager.js +47 -0
  135. package/dist/src/websocket.d.ts +3 -0
  136. package/dist/src/websocket.js +96 -25
  137. package/openclaw.plugin.json +21 -0
  138. 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.
@@ -87,6 +90,7 @@ export async function handleXYMessage(params) {
87
90
  text: pushDataItem.dataDetail,
88
91
  append: false,
89
92
  final: true,
93
+ runtime,
90
94
  });
91
95
  log(`[BOT] ✅ Trigger response sent successfully, exiting early`);
92
96
  return; // 提前返回,不继续处理
@@ -169,20 +173,41 @@ export async function handleXYMessage(params) {
169
173
  messageId: parsed.messageId,
170
174
  text: isSecondMessage ? "新消息已接收,正在处理..." : "任务正在处理中,请稍候~",
171
175
  state: "working",
176
+ runtime,
172
177
  }).catch((err) => {
173
178
  error(`Failed to send initial status update:`, err);
174
179
  });
175
180
  // Extract text and files from parts
176
181
  const text = extractTextFromParts(parsed.parts);
182
+ let textForAgent = text || "";
183
+ if (route.sessionKey && textForAgent) {
184
+ try {
185
+ const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
186
+ if (selfEvolutionEnabled && shouldNudgeForSelfEvolutionKeyword(textForAgent)) {
187
+ const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(route.sessionKey);
188
+ log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
189
+ if (shouldNudge) {
190
+ const augmented = appendSelfEvolutionKeywordNudge(textForAgent);
191
+ textForAgent = augmented.text;
192
+ if (augmented.appended) {
193
+ log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
194
+ }
195
+ }
196
+ }
197
+ }
198
+ catch (selfEvolutionError) {
199
+ error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
200
+ }
201
+ }
177
202
  const fileParts = extractFileParts(parsed.parts);
178
203
  // Download files to local disk
179
204
  const downloadedFiles = await downloadFilesFromParts(fileParts);
180
- console.log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
205
+ log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
181
206
  const mediaPayload = buildXYMediaPayload(downloadedFiles);
182
207
  // Resolve envelope format options (following feishu pattern)
183
208
  const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
184
209
  // Build message body with speaker prefix (following feishu pattern)
185
- let messageBody = text || "";
210
+ let messageBody = textForAgent;
186
211
  // Add speaker prefix for clarity
187
212
  const speaker = parsed.sessionId;
188
213
  messageBody = `${speaker}: ${messageBody}`;
@@ -198,8 +223,8 @@ export async function handleXYMessage(params) {
198
223
  // Use route.accountId and route.sessionKey instead of parsed fields
199
224
  const ctxPayload = core.channel.reply.finalizeInboundContext({
200
225
  Body: body,
201
- RawBody: text || "",
202
- CommandBody: text || "",
226
+ RawBody: textForAgent,
227
+ CommandBody: textForAgent,
203
228
  From: parsed.sessionId,
204
229
  To: parsed.sessionId, // ✅ Simplified: use sessionId as target (context is managed by SessionKey)
205
230
  SessionKey: route.sessionKey, // ✅ Use route.sessionKey
@@ -1,26 +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 { getEmailToolSchemaTool } from "./tools/get-email-tool-schema.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 { queryAppMessageTool } from "./tools/query-app-message-tool.js";
20
- import { queryMemoryDataTool } from "./tools/query-memory-data-tool.js";
21
- import { queryTodoTaskTool } from "./tools/query-todo-task-tool.js";
22
4
  import { filterToolsByDevice } from "./tools/device-tool-map.js";
23
5
  import { getCurrentSessionContext } from "./tools/session-manager.js";
6
+ import { createAllTools } from "./tools/create-all-tools.js";
24
7
  import { logger } from "./utils/logger.js";
25
8
  /**
26
9
  * Xiaoyi Channel Plugin for OpenClaw.
@@ -61,8 +44,8 @@ export const xyPlugin = {
61
44
  },
62
45
  outbound: xyOutbound,
63
46
  agentTools: () => {
64
- const allTools = [locationTool, callDeviceTool, getNoteToolSchemaTool, getCalendarToolSchemaTool, getContactToolSchemaTool, getPhotoToolSchemaTool, xiaoyiGuiTool, getDeviceFileToolSchemaTool, getAlarmToolSchemaTool, getCollectionToolSchemaTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, getEmailToolSchemaTool, queryAppMessageTool, queryMemoryDataTool, queryTodoTaskTool];
65
47
  const ctx = getCurrentSessionContext();
48
+ const allTools = createAllTools(ctx);
66
49
  const filtered = filterToolsByDevice(allTools, ctx?.deviceType);
67
50
  logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
68
51
  return filtered;
@@ -1,6 +1,7 @@
1
1
  // WebSocket client cache management
2
2
  // Follows feishu/client.ts pattern for caching client instances
3
3
  import { XYWebSocketManager } from "./websocket.js";
4
+ import { setXYRuntime } from "./runtime.js";
4
5
  // Runtime reference for logging
5
6
  let runtime;
6
7
  /**
@@ -8,12 +9,19 @@ let runtime;
8
9
  */
9
10
  export function setClientRuntime(rt) {
10
11
  runtime = rt;
12
+ setXYRuntime(rt);
11
13
  }
12
14
  /**
13
15
  * Global cache for WebSocket managers.
14
16
  * Key format: `${apiKey}-${agentId}`
17
+ * Uses globalThis to ensure a single cache across all module copies
18
+ * (same fix as session-manager.ts for openclaw multi-instance loading).
15
19
  */
16
- const wsManagerCache = new Map();
20
+ const _g = globalThis;
21
+ if (!_g.__xyWsManagerCache) {
22
+ _g.__xyWsManagerCache = new Map();
23
+ }
24
+ const wsManagerCache = _g.__xyWsManagerCache;
17
25
  /**
18
26
  * Get or create a WebSocket manager for the given configuration.
19
27
  * Reuses existing managers if config matches.
@@ -38,16 +46,17 @@ export function getXYWebSocketManager(config) {
38
46
  * Disconnects the manager and removes it from the cache.
39
47
  */
40
48
  export function removeXYWebSocketManager(config) {
49
+ const log = runtime?.log ?? console.log;
41
50
  const cacheKey = `${config.apiKey}-${config.agentId}`;
42
51
  const manager = wsManagerCache.get(cacheKey);
43
52
  if (manager) {
44
- console.log(`🗑️ [WS-MANAGER-CACHE] Removing manager from cache: ${cacheKey}`);
53
+ log(`🗑️ [WS-MANAGER-CACHE] Removing manager from cache: ${cacheKey}`);
45
54
  manager.disconnect();
46
55
  wsManagerCache.delete(cacheKey);
47
- console.log(`🗑️ [WS-MANAGER-CACHE] Manager removed, remaining managers: ${wsManagerCache.size}`);
56
+ log(`🗑️ [WS-MANAGER-CACHE] Manager removed, remaining managers: ${wsManagerCache.size}`);
48
57
  }
49
58
  else {
50
- console.log(`⚠️ [WS-MANAGER-CACHE] Manager not found in cache: ${cacheKey}`);
59
+ log(`⚠️ [WS-MANAGER-CACHE] Manager not found in cache: ${cacheKey}`);
51
60
  }
52
61
  }
53
62
  /**
@@ -72,36 +81,37 @@ export function getCachedManagerCount() {
72
81
  * Helps identify connection issues and orphan connections.
73
82
  */
74
83
  export function diagnoseAllManagers() {
75
- console.log(`Total cached managers: ${wsManagerCache.size}`);
84
+ const log = runtime?.log ?? console.log;
85
+ log(`Total cached managers: ${wsManagerCache.size}`);
76
86
  if (wsManagerCache.size === 0) {
77
- console.log("ℹ️ No managers in cache");
87
+ log("ℹ️ No managers in cache");
78
88
  return;
79
89
  }
80
90
  let orphanCount = 0;
81
91
  wsManagerCache.forEach((manager, key) => {
82
92
  const diag = manager.getConnectionDiagnostics();
83
- console.log(` Total event listeners on manager: ${diag.totalEventListeners}`);
93
+ log(` Total event listeners on manager: ${diag.totalEventListeners}`);
84
94
  // 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}`);
95
+ log(` 🔌 Connection:`);
96
+ log(` - Exists: ${diag.connection.exists}`);
97
+ log(` - ReadyState: ${diag.connection.readyState}`);
98
+ log(` - State connected/ready: ${diag.connection.stateConnected}/${diag.connection.stateReady}`);
99
+ log(` - Reconnect attempts: ${diag.connection.reconnectAttempts}`);
100
+ log(` - Listeners on WebSocket: ${diag.connection.listenerCount}`);
101
+ log(` - Heartbeat active: ${diag.connection.heartbeatActive}`);
102
+ log(` - Has reconnect timer: ${diag.connection.hasReconnectTimer}`);
93
103
  if (diag.connection.isOrphan) {
94
- console.log(` ⚠️ ORPHAN CONNECTION DETECTED!`);
104
+ log(` ⚠️ ORPHAN CONNECTION DETECTED!`);
95
105
  orphanCount++;
96
106
  }
97
- console.log("");
107
+ log("");
98
108
  });
99
109
  if (orphanCount > 0) {
100
- console.log(`⚠️ Total orphan connections found: ${orphanCount}`);
101
- console.log(`💡 Suggestion: These connections should be cleaned up`);
110
+ log(`⚠️ Total orphan connections found: ${orphanCount}`);
111
+ log(`💡 Suggestion: These connections should be cleaned up`);
102
112
  }
103
113
  else {
104
- console.log(`✅ No orphan connections found`);
114
+ log(`✅ No orphan connections found`);
105
115
  }
106
116
  }
107
117
  /**
@@ -109,17 +119,18 @@ export function diagnoseAllManagers() {
109
119
  * Returns the number of managers that had orphan connections.
110
120
  */
111
121
  export function cleanupOrphanConnections() {
122
+ const log = runtime?.log ?? console.log;
112
123
  let cleanedCount = 0;
113
124
  wsManagerCache.forEach((manager, key) => {
114
125
  const diag = manager.getConnectionDiagnostics();
115
126
  if (diag.connection.isOrphan) {
116
- console.log(`🧹 Cleaning up orphan connections in manager: ${key}`);
127
+ log(`🧹 Cleaning up orphan connections in manager: ${key}`);
117
128
  manager.disconnect();
118
129
  cleanedCount++;
119
130
  }
120
131
  });
121
132
  if (cleanedCount > 0) {
122
- console.log(`🧹 Cleaned up ${cleanedCount} manager(s) with orphan connections`);
133
+ log(`🧹 Cleaned up ${cleanedCount} manager(s) with orphan connections`);
123
134
  }
124
135
  return cleanedCount;
125
136
  }
@@ -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
  }
@@ -3,13 +3,14 @@
3
3
  import fetch from "node-fetch";
4
4
  import fs from "fs/promises";
5
5
  import os from "os";
6
+ import { logger } from "./utils/logger.js";
6
7
  import path from "path";
7
8
  import { calculateSHA256 } from "./utils/crypto.js";
8
9
  function isRemoteUrl(filePath) {
9
10
  return filePath.startsWith("http://") || filePath.startsWith("https://");
10
11
  }
11
12
  async function downloadToTempFile(url) {
12
- console.log(`[XY File Upload] Downloading remote file: ${url}`);
13
+ logger.log(`[XY File Upload] Downloading remote file: ${url}`);
13
14
  const response = await fetch(url);
14
15
  if (!response.ok) {
15
16
  throw new Error(`Failed to download remote file: HTTP ${response.status}`);
@@ -18,7 +19,7 @@ async function downloadToTempFile(url) {
18
19
  const urlFileName = path.basename(new URL(url).pathname) || "download";
19
20
  const tempPath = path.join(os.tmpdir(), `xy-upload-${Date.now()}-${urlFileName}`);
20
21
  await fs.writeFile(tempPath, buffer);
21
- console.log(`[XY File Upload] Downloaded to temp file: ${tempPath}`);
22
+ logger.log(`[XY File Upload] Downloaded to temp file: ${tempPath}`);
22
23
  return tempPath;
23
24
  }
24
25
  /**
@@ -39,7 +40,7 @@ export class XYFileUploadService {
39
40
  * Returns the objectId (as fileId) for use in A2A messages.
40
41
  */
41
42
  async uploadFile(filePath, objectType = "TEMPORARY_MATERIAL_DOC") {
42
- console.log(`[XY File Upload] Starting file upload: ${filePath}`);
43
+ logger.log(`[XY File Upload] Starting file upload: ${filePath}`);
43
44
  let localFilePath = filePath;
44
45
  let isTempFile = false;
45
46
  try {
@@ -54,7 +55,7 @@ export class XYFileUploadService {
54
55
  const fileSha256 = calculateSHA256(fileBuffer);
55
56
  const fileSize = fileBuffer.length;
56
57
  // Phase 1: Prepare
57
- console.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
58
+ logger.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
58
59
  const prepareResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/prepare`, {
59
60
  method: "POST",
60
61
  headers: {
@@ -84,7 +85,7 @@ export class XYFileUploadService {
84
85
  }
85
86
  const { objectId, draftId, uploadInfos } = prepareData;
86
87
  // Phase 2: Upload
87
- console.log(`[XY File Upload] Phase 2: Upload file data`);
88
+ logger.log(`[XY File Upload] Phase 2: Upload file data`);
88
89
  const uploadInfo = uploadInfos[0]; // Single-part upload
89
90
  const uploadResp = await fetch(uploadInfo.url, {
90
91
  method: uploadInfo.method,
@@ -95,9 +96,9 @@ export class XYFileUploadService {
95
96
  const uploadErrorText = await uploadResp.text();
96
97
  throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
97
98
  }
98
- console.log(`[XY File Upload] Upload complete`);
99
+ logger.log(`[XY File Upload] Upload complete`);
99
100
  // Phase 3: Complete
100
- console.log(`[XY File Upload] Phase 3: Complete upload`);
101
+ logger.log(`[XY File Upload] Phase 3: Complete upload`);
101
102
  const completeResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/complete`, {
102
103
  method: "POST",
103
104
  headers: {
@@ -115,11 +116,11 @@ export class XYFileUploadService {
115
116
  throw new Error(`Complete failed: HTTP ${completeResp.status}`);
116
117
  }
117
118
  const completeData = await completeResp.json();
118
- console.log(`[XY File Upload] File upload successful: ${fileName} → objectId=${objectId}`);
119
+ logger.log(`[XY File Upload] File upload successful: ${fileName} → objectId=${objectId}`);
119
120
  return objectId;
120
121
  }
121
122
  catch (error) {
122
- console.error(`[XY File Upload] File upload failed for ${filePath}:`, error);
123
+ logger.error(`[XY File Upload] File upload failed for ${filePath}:`, error);
123
124
  throw error;
124
125
  }
125
126
  finally {
@@ -150,7 +151,7 @@ export class XYFileUploadService {
150
151
  const fileSha256 = calculateSHA256(fileBuffer);
151
152
  const fileSize = fileBuffer.length;
152
153
  // Phase 1: Prepare
153
- console.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
154
+ logger.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
154
155
  const prepareResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/prepare`, {
155
156
  method: "POST",
156
157
  headers: {
@@ -179,23 +180,23 @@ export class XYFileUploadService {
179
180
  throw new Error(`Prepare failed: ${prepareData.desc}`);
180
181
  }
181
182
  const { objectId, draftId, uploadInfos } = prepareData;
182
- console.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
183
+ logger.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
183
184
  // Phase 2: Upload
184
- console.log(`[XY File Upload] Phase 2: Upload file data`);
185
+ logger.log(`[XY File Upload] Phase 2: Upload file data`);
185
186
  const uploadInfo = uploadInfos[0]; // Single-part upload
186
187
  const uploadResp = await fetch(uploadInfo.url, {
187
188
  method: uploadInfo.method,
188
189
  headers: uploadInfo.headers,
189
190
  body: fileBuffer,
190
191
  });
191
- console.log(`[XY File Upload] Upload response status: ${uploadResp.status}`);
192
+ logger.log(`[XY File Upload] Upload response status: ${uploadResp.status}`);
192
193
  if (!uploadResp.ok) {
193
194
  const uploadErrorText = await uploadResp.text();
194
195
  throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
195
196
  }
196
- console.log(`[XY File Upload] Upload complete`);
197
+ logger.log(`[XY File Upload] Upload complete`);
197
198
  // Phase 3: CompleteAndQuery - get file URL
198
- console.log(`[XY File Upload] Phase 3: CompleteAndQuery to get file URL`);
199
+ logger.log(`[XY File Upload] Phase 3: CompleteAndQuery to get file URL`);
199
200
  const completeResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/completeAndQuery`, {
200
201
  method: "POST",
201
202
  headers: {
@@ -218,11 +219,11 @@ export class XYFileUploadService {
218
219
  if (!fileUrl) {
219
220
  throw new Error("No file URL returned from completeAndQuery");
220
221
  }
221
- console.log(`[XY File Upload] File upload successful`);
222
+ logger.log(`[XY File Upload] File upload successful`);
222
223
  return fileUrl;
223
224
  }
224
225
  catch (error) {
225
- console.error(`[XY File Upload] File upload with URL retrieval failed for ${filePath}:`, error);
226
+ logger.error(`[XY File Upload] File upload with URL retrieval failed for ${filePath}:`, error);
226
227
  throw error;
227
228
  }
228
229
  finally {
@@ -249,7 +250,7 @@ export class XYFileUploadService {
249
250
  });
250
251
  }
251
252
  catch (error) {
252
- console.error(`[XY File Upload] Failed to upload ${filePath}, skipping:`, error);
253
+ logger.error(`[XY File Upload] Failed to upload ${filePath}, skipping:`, error);
253
254
  // Continue with other files
254
255
  }
255
256
  }
@@ -17,6 +17,7 @@ export interface SendA2AResponseParams {
17
17
  }>;
18
18
  errorCode?: number | string;
19
19
  errorMessage?: string;
20
+ runtime?: any;
20
21
  }
21
22
  /**
22
23
  * Send an A2A artifact update response.
@@ -49,6 +50,7 @@ export interface SendStatusUpdateParams {
49
50
  messageId: string;
50
51
  text: string;
51
52
  state: "submitted" | "working" | "input-required" | "completed" | "canceled" | "failed" | "unknown";
53
+ runtime?: any;
52
54
  }
53
55
  /**
54
56
  * Send an A2A task status update.