@ynhcj/xiaoyi-channel 1.1.26 → 1.1.28

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 (104) hide show
  1. package/dist/index.js +26 -69
  2. package/dist/src/approval-bridge.d.ts +48 -0
  3. package/dist/src/approval-bridge.js +382 -0
  4. package/dist/src/bot.js +132 -73
  5. package/dist/src/channel.js +59 -5
  6. package/dist/src/client.js +13 -23
  7. package/dist/src/cron-command.d.ts +15 -0
  8. package/dist/src/cron-command.js +49 -0
  9. package/dist/src/cron-query-handler.d.ts +7 -0
  10. package/dist/src/cron-query-handler.js +189 -0
  11. package/dist/src/cspl/call_api.d.ts +2 -0
  12. package/dist/src/cspl/call_api.js +107 -0
  13. package/dist/src/cspl/config.d.ts +4 -17
  14. package/dist/src/cspl/config.js +100 -70
  15. package/dist/src/cspl/configs.json +10 -0
  16. package/dist/src/cspl/constants.d.ts +49 -24
  17. package/dist/src/cspl/constants.js +46 -16
  18. package/dist/src/cspl/sentinel_hook.d.ts +2 -0
  19. package/dist/src/cspl/sentinel_hook.js +103 -0
  20. package/dist/src/cspl/steer-context.js +1 -1
  21. package/dist/src/cspl/upload_file.d.ts +1 -0
  22. package/dist/src/cspl/upload_file.js +211 -0
  23. package/dist/src/cspl/utils.d.ts +17 -2
  24. package/dist/src/cspl/utils.js +271 -15
  25. package/dist/src/file-upload.d.ts +5 -0
  26. package/dist/src/file-upload.js +102 -0
  27. package/dist/src/formatter.d.ts +43 -1
  28. package/dist/src/formatter.js +171 -41
  29. package/dist/src/monitor.js +64 -43
  30. package/dist/src/outbound.js +8 -9
  31. package/dist/src/parser.d.ts +8 -1
  32. package/dist/src/parser.js +71 -0
  33. package/dist/src/provider.js +51 -17
  34. package/dist/src/push.d.ts +11 -1
  35. package/dist/src/push.js +101 -17
  36. package/dist/src/reply-dispatcher.js +152 -59
  37. package/dist/src/self-evolution-handler.d.ts +1 -1
  38. package/dist/src/self-evolution-handler.js +14 -3
  39. package/dist/src/sensitive-redactor.d.ts +4 -0
  40. package/dist/src/sensitive-redactor.js +364 -0
  41. package/dist/src/task-manager.js +6 -10
  42. package/dist/src/tools/agent-as-skill-tool.d.ts +7 -0
  43. package/dist/src/tools/agent-as-skill-tool.js +190 -0
  44. package/dist/src/tools/calendar-tool.js +3 -2
  45. package/dist/src/tools/call-phone-tool.js +3 -2
  46. package/dist/src/tools/check-plugin-privilege-tool.d.ts +6 -0
  47. package/dist/src/tools/check-plugin-privilege-tool.js +182 -0
  48. package/dist/src/tools/create-alarm-tool.js +3 -2
  49. package/dist/src/tools/create-all-tools.js +11 -3
  50. package/dist/src/tools/delete-alarm-tool.js +3 -2
  51. package/dist/src/tools/device-tool-map.d.ts +1 -1
  52. package/dist/src/tools/device-tool-map.js +12 -5
  53. package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -0
  54. package/dist/src/tools/discover-cross-devices-tool.js +235 -0
  55. package/dist/src/tools/display-a2ui-card-tool.d.ts +2 -0
  56. package/dist/src/tools/display-a2ui-card-tool.js +85 -0
  57. package/dist/src/tools/find-pc-devices-tool.d.ts +2 -1
  58. package/dist/src/tools/find-pc-devices-tool.js +85 -88
  59. package/dist/src/tools/get-collection-tool-schema.js +1 -1
  60. package/dist/src/tools/location-tool.js +3 -2
  61. package/dist/src/tools/modify-alarm-tool.js +3 -2
  62. package/dist/src/tools/modify-note-tool.js +3 -2
  63. package/dist/src/tools/note-tool.js +3 -2
  64. package/dist/src/tools/query-app-message-tool.js +4 -3
  65. package/dist/src/tools/query-memory-data-tool.js +4 -3
  66. package/dist/src/tools/query-todo-task-tool.js +4 -3
  67. package/dist/src/tools/save-file-to-phone-tool.js +3 -2
  68. package/dist/src/tools/save-media-to-gallery-tool.js +3 -2
  69. package/dist/src/tools/schema-tool-factory.js +1 -1
  70. package/dist/src/tools/search-alarm-tool.js +3 -2
  71. package/dist/src/tools/search-calendar-tool.js +3 -2
  72. package/dist/src/tools/search-contact-tool.js +3 -2
  73. package/dist/src/tools/search-email-tool.js +4 -3
  74. package/dist/src/tools/search-file-tool.js +8 -9
  75. package/dist/src/tools/search-message-tool.js +2 -1
  76. package/dist/src/tools/search-note-tool.js +3 -2
  77. package/dist/src/tools/search-photo-gallery-tool.js +5 -4
  78. package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -0
  79. package/dist/src/tools/send-cross-device-task-tool.js +299 -0
  80. package/dist/src/tools/send-email-tool.js +4 -3
  81. package/dist/src/tools/send-file-to-user-tool.d.ts +1 -1
  82. package/dist/src/tools/send-file-to-user-tool.js +37 -8
  83. package/dist/src/tools/send-html-card-tool.d.ts +7 -0
  84. package/dist/src/tools/send-html-card-tool.js +113 -0
  85. package/dist/src/tools/send-message-tool.js +2 -1
  86. package/dist/src/tools/session-manager.d.ts +17 -1
  87. package/dist/src/tools/session-manager.js +87 -1
  88. package/dist/src/tools/upload-file-tool.js +9 -7
  89. package/dist/src/tools/upload-photo-tool.js +5 -4
  90. package/dist/src/tools/xiaoyi-add-collection-tool.js +5 -3
  91. package/dist/src/tools/xiaoyi-collection-tool.js +4 -3
  92. package/dist/src/tools/xiaoyi-delete-collection-tool.js +4 -3
  93. package/dist/src/tools/xiaoyi-gui-tool.js +8 -2
  94. package/dist/src/trigger-handler.js +4 -7
  95. package/dist/src/types.d.ts +25 -1
  96. package/dist/src/utils/config-manager.js +3 -6
  97. package/dist/src/utils/logger.d.ts +8 -0
  98. package/dist/src/utils/logger.js +69 -34
  99. package/dist/src/utils/pushdata-manager.js +1 -5
  100. package/dist/src/utils/pushid-manager.js +1 -2
  101. package/dist/src/utils/runtime-manager.js +1 -4
  102. package/dist/src/websocket.d.ts +3 -0
  103. package/dist/src/websocket.js +242 -38
  104. package/package.json +1 -1
@@ -1,11 +1,56 @@
1
1
  import { getXYRuntime } from "./runtime.js";
2
- import { sendA2AResponse, sendStatusUpdate, sendReasoningTextUpdate } from "./formatter.js";
2
+ import { sendA2AResponse, sendStatusUpdate, sendReasoningTextUpdate, sendCommand } from "./formatter.js";
3
3
  import { resolveXYConfig } from "./config.js";
4
4
  import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
5
+ import { clearRunCrossTaskSentFiles, getCurrentSessionContext } from "./tools/session-manager.js";
5
6
  import fs from "fs/promises";
6
7
  import path from "path";
7
8
  import { logger } from "./utils/logger.js";
8
9
  const TEMP_FILE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
10
+ const RUN_CROSS_TASK_LOG_TAG = "[RunCrossTask]";
11
+ function buildDistributionStatusCommand(context) {
12
+ return {
13
+ header: {
14
+ namespace: "DistributionInteraction",
15
+ name: "DistributionStatus",
16
+ },
17
+ payload: {
18
+ agentId: context.agentId,
19
+ isDistributed: true,
20
+ networkId: context.networkId,
21
+ distributionType: "softbus",
22
+ distributionExecutePolicy: "backgroundExecution",
23
+ },
24
+ };
25
+ }
26
+ function buildCrossTaskExecuteResultCommand(code, message, sentFiles = []) {
27
+ return {
28
+ header: {
29
+ namespace: "DistributionInteraction",
30
+ name: "CrossTaskExecuteResult",
31
+ },
32
+ payload: {
33
+ code,
34
+ message,
35
+ sentFiles,
36
+ },
37
+ };
38
+ }
39
+ async function sendRunCrossTaskResult(params) {
40
+ const { config, sessionId, taskId, messageId, context, resultCode, resultMessage } = params;
41
+ const sentFiles = Array.isArray(context.sentFiles) ? context.sentFiles : [];
42
+ const statusCommand = buildDistributionStatusCommand(context);
43
+ const resultCommand = buildCrossTaskExecuteResultCommand(resultCode, resultMessage, sentFiles);
44
+ await sendCommand({
45
+ config,
46
+ sessionId,
47
+ taskId,
48
+ messageId,
49
+ commands: [statusCommand, resultCommand],
50
+ });
51
+ clearRunCrossTaskSentFiles(context);
52
+ logger.log(`${RUN_CROSS_TASK_LOG_TAG} sent cross-task result, sessionId=${sessionId}, taskId=${taskId}, code=${resultCode}, sentFileCount=${sentFiles.length}, clearedSentFileCount=${sentFiles.length}, messageLength=${resultMessage.length}`);
53
+ }
9
54
  /**
10
55
  * 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
11
56
  */
@@ -32,11 +77,11 @@ export async function cleanupStaleTempFiles(tempDir = "/tmp/xy_channel") {
32
77
  }
33
78
  }
34
79
  if (cleanedCount > 0) {
35
- logger.log(`[CLEANUP] 🧹 Cleaned ${cleanedCount} stale files (>${TEMP_FILE_TTL_MS / 1000 / 3600}h) from ${tempDir}`);
80
+ logger.log(`[CLEANUP] Cleaned ${cleanedCount} stale files (>${TEMP_FILE_TTL_MS / 1000 / 3600}h) from ${tempDir}`);
36
81
  }
37
82
  }
38
83
  catch (err) {
39
- logger.error(`[CLEANUP] Failed to cleanup temp dir:`, err);
84
+ logger.error(`[CLEANUP] Failed to cleanup temp dir:`, err);
40
85
  }
41
86
  }
42
87
  /**
@@ -46,21 +91,19 @@ export async function cleanupStaleTempFiles(tempDir = "/tmp/xy_channel") {
46
91
  */
47
92
  export function createXYReplyDispatcher(params) {
48
93
  const { cfg, runtime, sessionId, taskId, messageId, accountId, steerState } = params;
49
- logger.log(`[DISPATCHER-CREATE] ******* Creating dispatcher *******`);
50
- logger.log(`[DISPATCHER-CREATE] - taskId: ${taskId}`);
51
94
  // 初始taskId和messageId(作为fallback)
52
95
  const initialTaskId = taskId;
53
96
  const initialMessageId = messageId;
54
- /**
55
- * 🔑 核心改造:动态获取当前活跃的taskId和messageId
56
- * 每次需要taskId时,都从TaskManager获取最新值
57
- */
58
97
  const getActiveTaskId = () => {
59
98
  return getCurrentTaskId(sessionId) ?? initialTaskId;
60
99
  };
61
100
  const getActiveMessageId = () => {
62
101
  return getCurrentMessageId(sessionId) ?? initialMessageId;
63
102
  };
103
+ // Create a scoped logger that always uses this session's sessionId
104
+ // and dynamically resolves the latest taskId
105
+ const scopedLog = () => logger.withContext(sessionId, getActiveTaskId());
106
+ scopedLog().log(`[DISPATCHER-CREATE] Creating dispatcher`);
64
107
  const core = getXYRuntime();
65
108
  const config = resolveXYConfig(cfg);
66
109
  // Simplified prefix context for single-account Xiaoyi channel
@@ -73,18 +116,20 @@ export function createXYReplyDispatcher(params) {
73
116
  let hasSentResponse = false;
74
117
  let finalSent = false;
75
118
  let accumulatedText = "";
119
+ const initialRunCrossTaskContext = getCurrentSessionContext()?.runCrossTaskContext;
120
+ const getRunCrossTaskContext = () => {
121
+ return getCurrentSessionContext()?.runCrossTaskContext ?? initialRunCrossTaskContext;
122
+ };
76
123
  /**
77
124
  * Start the status update interval
78
125
  */
79
126
  const startStatusInterval = () => {
80
- logger.log(`[STATUS INTERVAL] Starting interval for session ${sessionId}`);
127
+ scopedLog().log(`[STATUS-INTERVAL] Starting interval`);
81
128
  statusUpdateInterval = setInterval(() => {
82
129
  // 🔑 使用动态taskId
83
130
  const currentTaskId = getActiveTaskId();
84
131
  const currentMessageId = getActiveMessageId();
85
- logger.log(`[STATUS INTERVAL] Triggering status update`);
86
- logger.log(`[STATUS INTERVAL] - sessionId: ${sessionId}`);
87
- logger.log(`[STATUS INTERVAL] - currentTaskId: ${currentTaskId}`);
132
+ scopedLog().log(`[STATUS-INTERVAL] Triggering status update, taskId=${currentTaskId}`);
88
133
  void sendStatusUpdate({
89
134
  config,
90
135
  sessionId,
@@ -93,13 +138,13 @@ export function createXYReplyDispatcher(params) {
93
138
  text: "任务正在处理中,请稍候~",
94
139
  state: "working",
95
140
  }).catch((err) => {
96
- logger.error(`Failed to send status update:`, err);
141
+ scopedLog().error(`Failed to send status update:`, err);
97
142
  });
98
143
  }, 30000); // 30 seconds
99
144
  };
100
145
  const stopStatusInterval = () => {
101
146
  if (statusUpdateInterval) {
102
- logger.log(`[STATUS INTERVAL] Stopping interval for session ${sessionId}`);
147
+ scopedLog().log(`[STATUS-INTERVAL] Stopping interval`);
103
148
  clearInterval(statusUpdateInterval);
104
149
  statusUpdateInterval = null;
105
150
  }
@@ -110,38 +155,43 @@ export function createXYReplyDispatcher(params) {
110
155
  humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, accountId),
111
156
  onReplyStart: () => {
112
157
  const currentTaskId = getActiveTaskId();
113
- logger.log(`[REPLY START] Reply started for session ${sessionId}, taskId=${currentTaskId}, steered=${steerState.steered}`);
158
+ scopedLog().log(`[REPLY-START] Reply started, taskId=${currentTaskId}, steered=${steerState.steered}`);
114
159
  },
115
160
  deliver: async (payload, info) => {
116
161
  // 🔑 steered dispatch不发送内容(让主dispatcher处理)
117
162
  if (steerState.steered) {
118
- logger.log(`[DELIVER] Steered dispatch - skipping deliver, info.kind=${info?.kind}`);
163
+ scopedLog().log(`[DELIVER] Steered dispatch, skipping, kind=${info?.kind}`);
119
164
  return;
120
165
  }
121
166
  const text = payload.text ?? "";
122
167
  const currentTaskId = getActiveTaskId();
123
168
  const currentMessageId = getActiveMessageId();
124
- logger.log(`[DELIVER] sessionId=${sessionId}, taskId=${currentTaskId}, info.kind=${info?.kind}, text.length=${text.length}`);
169
+ scopedLog().log(`[DELIVER] kind=${info?.kind}, text.length=${text.length}`);
125
170
  try {
126
171
  if (!text.trim()) {
127
- logger.log(`[DELIVER SKIP] Empty text, skipping`);
172
+ scopedLog().log(`[DELIVER SKIP] Empty text, skipping`);
173
+ return;
174
+ }
175
+ // 🔑 如果 onPartialReply 已经流式发送过文本,deliver 不再重复发送
176
+ if (hasSentResponse) {
177
+ scopedLog().log(`[DELIVER SKIP] Already sent via onPartialReply`);
128
178
  return;
129
179
  }
130
180
  accumulatedText += text;
131
181
  hasSentResponse = true;
132
- logger.log(`[DELIVER ACCUMULATE] Accumulated text, current length=${accumulatedText.length}`);
133
- // 🔑 使用动态taskId发送reasoningText更新
134
- await sendReasoningTextUpdate({
182
+ // 🔑 使用动态taskId发送A2A响应(流式append)
183
+ await sendA2AResponse({
135
184
  config,
136
185
  sessionId,
137
186
  taskId: currentTaskId,
138
187
  messageId: currentMessageId,
139
188
  text,
189
+ append: true,
190
+ final: false,
140
191
  });
141
- logger.log(`[DELIVER] ✅ Sent deliver text as reasoningText update`);
142
192
  }
143
193
  catch (deliverError) {
144
- logger.error(`Failed to deliver message:`, deliverError);
194
+ scopedLog().error(`Failed to deliver message:`, deliverError);
145
195
  }
146
196
  },
147
197
  onError: async (err, info) => {
@@ -149,7 +199,7 @@ export function createXYReplyDispatcher(params) {
149
199
  stopStatusInterval();
150
200
  // 🔑 steered dispatcher不发送错误状态(让主dispatcher处理)
151
201
  if (steerState.steered) {
152
- logger.log(`[ON_ERROR] Steered dispatch - skipping error response`);
202
+ scopedLog().log(`[ON-ERROR] Steered dispatch, skipping error response`);
153
203
  return;
154
204
  }
155
205
  if (!hasSentResponse) {
@@ -166,29 +216,36 @@ export function createXYReplyDispatcher(params) {
166
216
  });
167
217
  }
168
218
  catch (statusError) {
169
- logger.error(`Failed to send error status:`, statusError);
219
+ scopedLog().error(`Failed to send error status:`, statusError);
170
220
  }
171
221
  }
172
222
  },
173
223
  onIdle: async () => {
174
224
  const currentTaskId = getActiveTaskId();
175
225
  const currentMessageId = getActiveMessageId();
176
- logger.log(`[ON_IDLE] Reply idle`);
177
- logger.log(`[ON_IDLE] - sessionId: ${sessionId}`);
178
- logger.log(`[ON_IDLE] - taskId: ${currentTaskId}`);
179
- logger.log(`[ON_IDLE] - steered: ${steerState.steered}`);
180
- logger.log(`[ON_IDLE] - hasSentResponse: ${hasSentResponse}`);
181
- logger.log(`[ON_IDLE] - finalSent: ${finalSent}`);
226
+ scopedLog().log(`[ON-IDLE] Reply idle, steered=${steerState.steered}, hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
182
227
  // 🔑 steered dispatch不发送final响应(核心已注入到活跃 Pi run)
183
228
  if (steerState.steered) {
184
- logger.log(`[ON_IDLE] Steered dispatch - skipping final response`);
229
+ scopedLog().log(`[ON-IDLE] Steered dispatch, skipping final response`);
185
230
  stopStatusInterval();
186
231
  return; // ← 直接返回,不发送任何东西!
187
232
  }
188
233
  // 正常模式(或未被steer的dispatch)
189
234
  if (hasSentResponse && !finalSent) {
190
- logger.log(`[ON_IDLE] Sending accumulated text, length=${accumulatedText.length}`);
235
+ scopedLog().log(`[ON-IDLE] Sending accumulated text, length=${accumulatedText.length}`);
191
236
  try {
237
+ const runCrossTaskContext = getRunCrossTaskContext();
238
+ if (runCrossTaskContext) {
239
+ await sendRunCrossTaskResult({
240
+ config,
241
+ sessionId,
242
+ taskId: currentTaskId,
243
+ messageId: currentMessageId,
244
+ context: runCrossTaskContext,
245
+ resultCode: "0",
246
+ resultMessage: accumulatedText,
247
+ });
248
+ }
192
249
  // 🔑 使用动态taskId发送完成状态
193
250
  await sendStatusUpdate({
194
251
  config,
@@ -198,28 +255,40 @@ export function createXYReplyDispatcher(params) {
198
255
  text: "任务处理已完成~",
199
256
  state: "completed",
200
257
  });
201
- logger.log(`[ON_IDLE] Sent completion status update`);
202
- // 🔑 使用动态taskId发送最终响应
258
+ scopedLog().log(`[ON-IDLE] Sent completion status update`);
259
+ // 🔑 使用动态taskId发送最终响应(空字符串表示流结束)
203
260
  await sendA2AResponse({
204
261
  config,
205
262
  sessionId,
206
263
  taskId: currentTaskId,
207
264
  messageId: currentMessageId,
208
- text: accumulatedText,
209
- append: false,
265
+ text: "",
266
+ append: true,
210
267
  final: true,
211
268
  });
212
269
  finalSent = true;
213
- logger.log(`[ON_IDLE] Sent final response with taskId=${currentTaskId}`);
270
+ scopedLog().log(`[ON-IDLE] Sent final response (empty, stream end)`);
214
271
  }
215
272
  catch (err) {
216
- logger.error(`[ON_IDLE] Failed to send final response:`, err);
273
+ scopedLog().error(`[ON-IDLE] Failed to send final response:`, err);
217
274
  }
218
275
  }
219
276
  else {
220
277
  // 正常失败场景(非steered)
221
- logger.log(`[ON_IDLE] Skipping final message: hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
278
+ scopedLog().log(`[ON-IDLE] Skipping final message: hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
222
279
  try {
280
+ const runCrossTaskContext = getRunCrossTaskContext();
281
+ if (runCrossTaskContext) {
282
+ await sendRunCrossTaskResult({
283
+ config,
284
+ sessionId,
285
+ taskId: currentTaskId,
286
+ messageId: currentMessageId,
287
+ context: runCrossTaskContext,
288
+ resultCode: "1",
289
+ resultMessage: "任务执行异常,请重试",
290
+ });
291
+ }
223
292
  await sendStatusUpdate({
224
293
  config,
225
294
  sessionId,
@@ -228,36 +297,37 @@ export function createXYReplyDispatcher(params) {
228
297
  text: "任务处理中断了~",
229
298
  state: "failed",
230
299
  });
231
- logger.log(`[ON_IDLE] Sent failure status update`);
300
+ scopedLog().log(`[ON-IDLE] Sent failure status update`);
232
301
  await sendA2AResponse({
233
302
  config,
234
303
  sessionId,
235
304
  taskId: currentTaskId,
236
305
  messageId: currentMessageId,
237
306
  text: "任务执行异常,请重试~",
238
- append: false,
307
+ append: true,
239
308
  final: true,
240
309
  errorCode: 99921111,
241
310
  errorMessage: "任务执行异常,请重试",
242
311
  });
243
312
  finalSent = true;
244
- logger.log(`[ON_IDLE] Sent error response with code: 99921111`);
313
+ scopedLog().log(`[ON-IDLE] Sent error response, code=99921111`);
245
314
  }
246
315
  catch (err) {
247
- logger.error(`[ON_IDLE] Failed to send error response:`, err);
316
+ scopedLog().error(`[ON-IDLE] Failed to send error response:`, err);
248
317
  }
249
318
  }
250
319
  stopStatusInterval();
251
320
  },
252
321
  onCleanup: () => {
253
322
  const currentTaskId = getActiveTaskId();
254
- logger.log(`[ON_CLEANUP] Reply cleanup, taskId=${currentTaskId}, steered=${steerState.steered}`);
323
+ scopedLog().log(`[ON-CLEANUP] Reply cleanup, steered=${steerState.steered}`);
255
324
  },
256
325
  });
257
326
  return {
258
327
  dispatcher,
259
328
  replyOptions: {
260
329
  ...replyOptions,
330
+ suppressToolErrorWarnings: true,
261
331
  onModelSelected: prefixContext.onModelSelected,
262
332
  onToolStart: async ({ name, phase }) => {
263
333
  // 🔑 steered dispatch不发送tool状态(让主dispatcher处理)
@@ -266,13 +336,13 @@ export function createXYReplyDispatcher(params) {
266
336
  }
267
337
  const currentTaskId = getActiveTaskId();
268
338
  const currentMessageId = getActiveMessageId();
269
- logger.log(`[TOOL START] Tool: ${name}, phase: ${phase}, taskId: ${currentTaskId}`);
339
+ scopedLog().log(`[TOOL-START] Tool: ${name}, phase: ${phase}`);
270
340
  if (phase === "start") {
271
341
  const toolName = name || "unknown";
272
342
  // call_device_tool 由自身 execute() 内部发送具体子工具名的状态更新
273
343
  // get_xxx_tool_schema 是给 LLM 查 schema 用的,无需向用户展示
274
344
  if (toolName === "call_device_tool" || toolName.endsWith("_tool_schema") || toolName === "huawei_id_tool") {
275
- logger.log(`[TOOL START] Skipping generic status for ${toolName}`);
345
+ scopedLog().log(`[TOOL-START] Skipping generic status for ${toolName}`);
276
346
  return;
277
347
  }
278
348
  try {
@@ -284,10 +354,10 @@ export function createXYReplyDispatcher(params) {
284
354
  text: `正在使用工具: ${toolName}...`,
285
355
  state: "working",
286
356
  });
287
- logger.log(`[TOOL START] Sent status update for tool start: ${toolName}`);
357
+ scopedLog().log(`[TOOL-START] Sent status update for tool start: ${toolName}`);
288
358
  }
289
359
  catch (err) {
290
- logger.error(`[TOOL START] Failed to send tool start status:`, err);
360
+ scopedLog().error(`[TOOL-START] Failed to send tool start status:`, err);
291
361
  }
292
362
  }
293
363
  },
@@ -300,7 +370,7 @@ export function createXYReplyDispatcher(params) {
300
370
  const currentMessageId = getActiveMessageId();
301
371
  const text = payload.text ?? "";
302
372
  const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
303
- logger.log(`[TOOL RESULT] Tool result, taskId: ${currentTaskId}, text.length: ${text.length}`);
373
+ scopedLog().log(`[TOOL-RESULT] Tool result, text.length: ${text.length}`);
304
374
  try {
305
375
  if (text.length > 0 || hasMedia) {
306
376
  const resultText = text.length > 0 ? text : "工具执行完成";
@@ -312,11 +382,11 @@ export function createXYReplyDispatcher(params) {
312
382
  text: resultText,
313
383
  state: "working",
314
384
  });
315
- logger.log(`[TOOL RESULT] Sent tool result as status update`);
385
+ scopedLog().log(`[TOOL-RESULT] Sent tool result as status update`);
316
386
  }
317
387
  }
318
388
  catch (err) {
319
- logger.error(`[TOOL RESULT] Failed to send tool result status:`, err);
389
+ scopedLog().error(`[TOOL-RESULT] Failed to send tool result status:`, err);
320
390
  }
321
391
  },
322
392
  onReasoningStream: async (payload) => {
@@ -324,10 +394,29 @@ export function createXYReplyDispatcher(params) {
324
394
  if (steerState.steered) {
325
395
  return;
326
396
  }
327
- const text = payload.text ?? "";
328
- logger.log(`[REASONING STREAM] Reasoning chunk received, text.length: ${text.length}`);
329
- // Reasoning stream 目前被注释掉
330
- // 如果需要可以启用
397
+ const currentTaskId = getActiveTaskId();
398
+ const currentMessageId = getActiveMessageId();
399
+ let text = payload.text ?? "";
400
+ // Strip "Reasoning:" prefix that some reasoning models add to their thinking output
401
+ const lines = text.split(/\r?\n/);
402
+ if (lines[0]?.trim() === "Reasoning:") {
403
+ text = lines.slice(1).join("\n").trim();
404
+ }
405
+ try {
406
+ if (text.length > 0) {
407
+ await sendReasoningTextUpdate({
408
+ config,
409
+ sessionId,
410
+ taskId: currentTaskId,
411
+ messageId: currentMessageId,
412
+ text,
413
+ append: false,
414
+ });
415
+ }
416
+ }
417
+ catch (err) {
418
+ scopedLog().error(`[REASONING-STREAM] Failed to send reasoning text:`, err);
419
+ }
331
420
  },
332
421
  onPartialReply: async (payload) => {
333
422
  // 🔑 steered dispatch不发送partial reply(让主dispatcher处理)
@@ -339,18 +428,22 @@ export function createXYReplyDispatcher(params) {
339
428
  const text = payload.text ?? "";
340
429
  try {
341
430
  if (text.length > 0) {
342
- await sendReasoningTextUpdate({
431
+ accumulatedText += text;
432
+ hasSentResponse = true;
433
+ await sendA2AResponse({
343
434
  config,
344
435
  sessionId,
345
436
  taskId: currentTaskId,
346
437
  messageId: currentMessageId,
347
438
  text,
348
439
  append: false,
440
+ final: false,
441
+ log: false,
349
442
  });
350
443
  }
351
444
  }
352
445
  catch (err) {
353
- logger.error(`[PARTIAL REPLY] Failed to send partial reply:`, err);
446
+ scopedLog().error(`[PARTIAL-REPLY] Failed to send partial reply:`, err);
354
447
  }
355
448
  },
356
449
  },
@@ -1,5 +1,5 @@
1
1
  import type { XYWebSocketManager } from "./websocket.js";
2
- export declare function handleSelfEvolutionEvent(context: any, runtime: any): void;
2
+ export declare function handleSelfEvolutionEvent(context: any, cfg: any): Promise<void>;
3
3
  /**
4
4
  * 读取 .xiaoyiruntime 中的 selfEvolutionState 并直接通过 wsManager 下发指令回复设备
5
5
  * 参考trigger实现:直接使用当前已连接的 wsManager 发送消息,避免 getXYWebSocketManager 返回未连接实例
@@ -1,14 +1,20 @@
1
1
  import { readFileSync, writeFileSync } from "fs";
2
2
  import { v4 as uuidv4 } from "uuid";
3
+ import { resolveXYConfig } from "./config.js";
4
+ import { sendA2AResponse } from "./formatter.js";
3
5
  import { logger } from "./utils/logger.js";
4
6
  const XIAOYIRUNTIME_PATH = "/home/sandbox/.openclaw/.xiaoyiruntime";
5
- export function handleSelfEvolutionEvent(context, runtime) {
7
+ export async function handleSelfEvolutionEvent(context, cfg) {
6
8
  try {
7
9
  const state = context.event?.payload?.selfEvolutionState;
8
10
  if (typeof state !== "string") {
9
11
  logger.error("[SELF_EVOLUTION] invalid payload: missing selfEvolutionState");
10
12
  return;
11
13
  }
14
+ const sessionId = context.sessionId ?? "";
15
+ const taskId = context.taskId ?? sessionId;
16
+ const messageId = context.messageId ?? uuidv4();
17
+ const config = resolveXYConfig(cfg);
12
18
  logger.log(`[SELF_EVOLUTION] received state: ${state}`);
13
19
  let content;
14
20
  try {
@@ -19,6 +25,8 @@ export function handleSelfEvolutionEvent(context, runtime) {
19
25
  logger.log(`[SELF_EVOLUTION] ${XIAOYIRUNTIME_PATH} not found, creating new file`);
20
26
  writeFileSync(XIAOYIRUNTIME_PATH, `selfEvolutionState=${state}\n`, "utf-8");
21
27
  logger.log(`[SELF_EVOLUTION] wrote selfEvolutionState=${state}`);
28
+ await sendA2AResponse({ config, sessionId, taskId, messageId, text: "", append: false, final: true });
29
+ logger.log(`[SELF_EVOLUTION] Sent final response (empty, stream end)`);
22
30
  return;
23
31
  }
24
32
  const lines = content.split("\n");
@@ -40,6 +48,9 @@ export function handleSelfEvolutionEvent(context, runtime) {
40
48
  writeFileSync(XIAOYIRUNTIME_PATH, updated.join("\n"), "utf-8");
41
49
  }
42
50
  logger.log(`[SELF_EVOLUTION] updated selfEvolutionState=${state} in ${XIAOYIRUNTIME_PATH}`);
51
+ // Reply with final empty response to acknowledge the state update
52
+ await sendA2AResponse({ config, sessionId, taskId, messageId, text: "", append: false, final: true });
53
+ logger.log(`[SELF_EVOLUTION] Sent final response (empty, stream end)`);
43
54
  }
44
55
  catch (err) {
45
56
  logger.error("[SELF_EVOLUTION] failed to handle event:", err);
@@ -108,7 +119,7 @@ export async function handleSelfEvolutionStateGetEvent(context, cfg, runtime, ws
108
119
  kind: "artifact-update",
109
120
  append: false,
110
121
  lastChunk: true,
111
- final: false,
122
+ final: true,
112
123
  artifact: {
113
124
  artifactId: uuidv4(),
114
125
  parts: [{
@@ -127,7 +138,7 @@ export async function handleSelfEvolutionStateGetEvent(context, cfg, runtime, ws
127
138
  taskId,
128
139
  msgDetail: JSON.stringify(jsonRpcResponse),
129
140
  };
130
- logger.log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${taskId}`);
141
+ logger.log(`[A2A_COMMAND] Sending A2A command, taskId: ${taskId}`);
131
142
  await wsManager.sendMessage(sessionId, outboundMessage);
132
143
  logger.log(`[SELF_EVOLUTION_GET] command sent successfully`);
133
144
  }
@@ -0,0 +1,4 @@
1
+ export declare function refreshSensitivePatterns(): void;
2
+ export declare function redactSensitiveText(text: any): any;
3
+ export declare function redactSensitiveObject(obj: any): any;
4
+ export declare function containsSensitiveInfo(text: any): boolean;