@ynhcj/xiaoyi-channel 0.0.185-beta → 0.0.185-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.
- package/dist/index.js +141 -1
- package/dist/src/acp-session-binding.d.ts +37 -0
- package/dist/src/acp-session-binding.js +237 -0
- package/dist/src/bot.js +62 -33
- package/dist/src/channel.js +68 -64
- package/dist/src/client.d.ts +5 -0
- package/dist/src/client.js +10 -0
- package/dist/src/cron-command.d.ts +2 -0
- package/dist/src/cron-command.js +14 -8
- package/dist/src/cron-query-handler.js +36 -0
- package/dist/src/cspl/call_api.js +4 -2
- package/dist/src/cspl/sentinel_hook.js +9 -8
- package/dist/src/cspl/upload_file.js +2 -2
- package/dist/src/formatter.js +72 -36
- package/dist/src/log-reporter/config-loader.d.ts +11 -0
- package/dist/src/log-reporter/config-loader.js +68 -0
- package/dist/src/log-reporter/cursor-store.d.ts +5 -0
- package/dist/src/log-reporter/cursor-store.js +26 -0
- package/dist/src/log-reporter/index.d.ts +10 -0
- package/dist/src/log-reporter/index.js +77 -0
- package/dist/src/log-reporter/reporter.d.ts +6 -0
- package/dist/src/log-reporter/reporter.js +17 -0
- package/dist/src/log-reporter/scanner.d.ts +6 -0
- package/dist/src/log-reporter/scanner.js +82 -0
- package/dist/src/log-reporter/types.d.ts +59 -0
- package/dist/src/log-reporter/types.js +2 -0
- package/dist/src/log-reporter/uploader.d.ts +6 -0
- package/dist/src/log-reporter/uploader.js +32 -0
- package/dist/src/memory-query-handler.d.ts +1 -0
- package/dist/src/memory-query-handler.js +283 -0
- package/dist/src/monitor.js +29 -9
- package/dist/src/outbound.js +5 -0
- package/dist/src/parser.d.ts +12 -0
- package/dist/src/parser.js +39 -13
- package/dist/src/provider.js +79 -16
- package/dist/src/reply-dispatcher.d.ts +3 -0
- package/dist/src/reply-dispatcher.js +166 -164
- package/dist/src/self-evolution-handler.js +2 -1
- package/dist/src/task-manager.d.ts +5 -6
- package/dist/src/task-manager.js +5 -9
- package/dist/src/tools/agent-as-skill-tool.d.ts +41 -2
- package/dist/src/tools/agent-as-skill-tool.js +144 -151
- package/dist/src/tools/calendar-tool.d.ts +24 -2
- package/dist/src/tools/calendar-tool.js +115 -117
- package/dist/src/tools/call-device-tool.d.ts +20 -6
- package/dist/src/tools/call-device-tool.js +116 -138
- package/dist/src/tools/call-phone-tool.d.ts +21 -2
- package/dist/src/tools/call-phone-tool.js +112 -114
- package/dist/src/tools/check-plugin-privilege-tool.d.ts +16 -2
- package/dist/src/tools/check-plugin-privilege-tool.js +141 -143
- package/dist/src/tools/create-alarm-tool.d.ts +39 -2
- package/dist/src/tools/create-alarm-tool.js +229 -231
- package/dist/src/tools/create-all-tools.js +8 -4
- package/dist/src/tools/delete-alarm-tool.d.ts +15 -2
- package/dist/src/tools/delete-alarm-tool.js +134 -136
- package/dist/src/tools/device-tool-map.d.ts +1 -1
- package/dist/src/tools/device-tool-map.js +9 -2
- package/dist/src/tools/discover-cross-devices-tool.d.ts +16 -2
- package/dist/src/tools/discover-cross-devices-tool.js +121 -124
- package/dist/src/tools/display-a2ui-card-bypath-tool.d.ts +21 -0
- package/dist/src/tools/display-a2ui-card-bypath-tool.js +65 -0
- package/dist/src/tools/get-alarm-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-alarm-tool-schema.js +10 -16
- package/dist/src/tools/get-calendar-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-calendar-tool-schema.js +8 -12
- package/dist/src/tools/get-collection-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-collection-tool-schema.js +9 -11
- package/dist/src/tools/get-contact-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-contact-tool-schema.js +10 -16
- package/dist/src/tools/get-device-file-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-device-file-tool-schema.js +9 -13
- package/dist/src/tools/get-email-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-email-tool-schema.js +8 -11
- package/dist/src/tools/get-note-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-note-tool-schema.js +9 -14
- package/dist/src/tools/get-photo-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-photo-tool-schema.js +9 -12
- package/dist/src/tools/image-reading-tool.d.ts +28 -2
- package/dist/src/tools/image-reading-tool.js +76 -76
- package/dist/src/tools/location-tool.d.ts +11 -2
- package/dist/src/tools/location-tool.js +93 -92
- package/dist/src/tools/login-token-tool.d.ts +20 -2
- package/dist/src/tools/login-token-tool.js +123 -125
- package/dist/src/tools/modify-alarm-tool.d.ts +47 -2
- package/dist/src/tools/modify-alarm-tool.js +250 -252
- package/dist/src/tools/modify-note-tool.d.ts +20 -2
- package/dist/src/tools/modify-note-tool.js +107 -109
- package/dist/src/tools/note-tool.d.ts +20 -2
- package/dist/src/tools/note-tool.js +106 -108
- package/dist/src/tools/query-app-message-tool.d.ts +28 -2
- package/dist/src/tools/query-app-message-tool.js +111 -113
- package/dist/src/tools/query-memory-data-tool.d.ts +28 -2
- package/dist/src/tools/query-memory-data-tool.js +112 -114
- package/dist/src/tools/query-todo-task-tool.d.ts +24 -2
- package/dist/src/tools/query-todo-task-tool.js +106 -108
- package/dist/src/tools/save-file-to-phone-tool.d.ts +24 -2
- package/dist/src/tools/save-file-to-phone-tool.js +130 -132
- package/dist/src/tools/save-media-to-gallery-tool.d.ts +24 -2
- package/dist/src/tools/save-media-to-gallery-tool.js +137 -139
- package/dist/src/tools/save-self-evolution-skill-tool.d.ts +54 -2
- package/dist/src/tools/save-self-evolution-skill-tool.js +194 -194
- package/dist/src/tools/search-alarm-tool.d.ts +34 -2
- package/dist/src/tools/search-alarm-tool.js +174 -176
- package/dist/src/tools/search-calendar-tool.d.ts +24 -2
- package/dist/src/tools/search-calendar-tool.js +148 -150
- package/dist/src/tools/search-contact-tool.d.ts +16 -2
- package/dist/src/tools/search-contact-tool.js +101 -103
- package/dist/src/tools/search-email-tool.d.ts +21 -2
- package/dist/src/tools/search-email-tool.js +110 -112
- package/dist/src/tools/search-file-tool.d.ts +16 -2
- package/dist/src/tools/search-file-tool.js +104 -106
- package/dist/src/tools/search-message-tool.d.ts +16 -2
- package/dist/src/tools/search-message-tool.js +103 -105
- package/dist/src/tools/search-note-tool.d.ts +16 -2
- package/dist/src/tools/search-note-tool.js +98 -100
- package/dist/src/tools/search-photo-gallery-tool.d.ts +21 -2
- package/dist/src/tools/search-photo-gallery-tool.js +35 -37
- package/dist/src/tools/send-cross-device-task-tool.d.ts +35 -2
- package/dist/src/tools/send-cross-device-task-tool.js +216 -150
- package/dist/src/tools/send-email-tool.d.ts +24 -2
- package/dist/src/tools/send-email-tool.js +108 -110
- package/dist/src/tools/send-file-to-user-tool.d.ts +20 -2
- package/dist/src/tools/send-file-to-user-tool.js +174 -178
- package/dist/src/tools/send-html-card-tool.d.ts +20 -2
- package/dist/src/tools/send-html-card-tool.js +85 -87
- package/dist/src/tools/send-message-tool.d.ts +20 -2
- package/dist/src/tools/send-message-tool.js +122 -124
- package/dist/src/tools/session-manager.d.ts +19 -52
- package/dist/src/tools/session-manager.js +95 -247
- package/dist/src/tools/upload-file-tool.d.ts +20 -2
- package/dist/src/tools/upload-file-tool.js +80 -82
- package/dist/src/tools/upload-photo-tool.d.ts +20 -2
- package/dist/src/tools/upload-photo-tool.js +68 -70
- package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +32 -2
- package/dist/src/tools/xiaoyi-add-collection-tool.js +146 -148
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +20 -2
- package/dist/src/tools/xiaoyi-collection-tool.js +114 -116
- package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +15 -2
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +127 -129
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +16 -2
- package/dist/src/tools/xiaoyi-gui-tool.js +92 -95
- package/dist/src/types.d.ts +6 -6
- package/dist/src/utils/config-manager.d.ts +3 -2
- package/dist/src/utils/config-manager.js +22 -2
- package/dist/src/utils/cron-push-map.d.ts +26 -0
- package/dist/src/utils/cron-push-map.js +131 -0
- package/dist/src/utils/logger.js +3 -14
- package/dist/src/websocket.d.ts +1 -1
- package/dist/src/websocket.js +29 -13
- package/package.json +1 -1
|
@@ -9,6 +9,9 @@ export interface CreateXYReplyDispatcherParams {
|
|
|
9
9
|
steerState: {
|
|
10
10
|
steered: boolean;
|
|
11
11
|
};
|
|
12
|
+
/** Called at end of onIdle, after final frame is sent. openclaw's waitForIdle() does
|
|
13
|
+
* not await the async onIdle, so cleanup must happen inside onIdle itself. */
|
|
14
|
+
onIdleComplete?: () => void | Promise<void>;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { getXYRuntime } from "./runtime.js";
|
|
2
2
|
import { sendA2AResponse, sendStatusUpdate, sendReasoningTextUpdate, sendCommand } from "./formatter.js";
|
|
3
3
|
import { resolveXYConfig } from "./config.js";
|
|
4
|
-
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
5
4
|
import { clearRunCrossTaskSentFiles, getCurrentSessionContext } from "./tools/session-manager.js";
|
|
6
5
|
import fs from "fs/promises";
|
|
7
6
|
import path from "path";
|
|
8
7
|
import { logger } from "./utils/logger.js";
|
|
8
|
+
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
9
9
|
const TEMP_FILE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
10
10
|
const RUN_CROSS_TASK_LOG_TAG = "[RunCrossTask]";
|
|
11
11
|
function buildDistributionStatusCommand(context) {
|
|
@@ -39,17 +39,23 @@ function buildCrossTaskExecuteResultCommand(code, message, sentFiles = []) {
|
|
|
39
39
|
async function sendRunCrossTaskResult(params) {
|
|
40
40
|
const { config, sessionId, taskId, messageId, context, resultCode, resultMessage } = params;
|
|
41
41
|
const sentFiles = Array.isArray(context.sentFiles) ? context.sentFiles : [];
|
|
42
|
+
const fileCardCount = sentFiles.length;
|
|
42
43
|
const statusCommand = buildDistributionStatusCommand(context);
|
|
43
44
|
const resultCommand = buildCrossTaskExecuteResultCommand(resultCode, resultMessage, sentFiles);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
try {
|
|
46
|
+
await sendCommand({
|
|
47
|
+
config,
|
|
48
|
+
sessionId,
|
|
49
|
+
taskId,
|
|
50
|
+
messageId,
|
|
51
|
+
commands: [statusCommand, resultCommand],
|
|
52
|
+
});
|
|
53
|
+
logger.log(`${RUN_CROSS_TASK_LOG_TAG} sent cross-task result, sessionId=${sessionId}, taskId=${taskId}, code=${resultCode}, fileCardCount=${fileCardCount}, messageLength=${resultMessage.length}`);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearRunCrossTaskSentFiles(context);
|
|
57
|
+
logger.log(`${RUN_CROSS_TASK_LOG_TAG} cleared cross-task sentFiles, sessionId=${sessionId}, taskId=${taskId}, clearedFileCardCount=${fileCardCount}`);
|
|
58
|
+
}
|
|
53
59
|
}
|
|
54
60
|
/**
|
|
55
61
|
* 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
|
|
@@ -90,15 +96,23 @@ export async function cleanupStaleTempFiles(tempDir = "/tmp/xy_channel") {
|
|
|
90
96
|
* Runtime is expected to be validated before calling this function.
|
|
91
97
|
*/
|
|
92
98
|
export function createXYReplyDispatcher(params) {
|
|
93
|
-
const { cfg, runtime, sessionId, taskId, messageId, accountId, steerState } = params;
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
const { cfg, runtime, sessionId, taskId, messageId, accountId, steerState, onIdleComplete } = params;
|
|
100
|
+
// fallback taskId/messageId(当 task-manager 返回 null 时使用)
|
|
101
|
+
// steer 触发时会通过 updateFallbackTaskId() 更新为最新的 taskId
|
|
102
|
+
let currentFallbackTaskId = taskId;
|
|
103
|
+
let currentFallbackMessageId = messageId;
|
|
104
|
+
// 跨链读取:steer 消息通过 registerTaskId 更新 Map,这里读取最新 taskId
|
|
97
105
|
const getActiveTaskId = () => {
|
|
98
|
-
return getCurrentTaskId(sessionId) ??
|
|
106
|
+
return getCurrentTaskId(sessionId) ?? currentFallbackTaskId;
|
|
99
107
|
};
|
|
100
108
|
const getActiveMessageId = () => {
|
|
101
|
-
return getCurrentMessageId(sessionId) ??
|
|
109
|
+
return getCurrentMessageId(sessionId) ?? currentFallbackMessageId;
|
|
110
|
+
};
|
|
111
|
+
/** steer 触发时调用,同步 fallback taskId/messageId 到最新值 */
|
|
112
|
+
const updateFallbackTaskId = (newTaskId, newMessageId) => {
|
|
113
|
+
currentFallbackTaskId = newTaskId;
|
|
114
|
+
currentFallbackMessageId = newMessageId;
|
|
115
|
+
logger.log(`[DISPATCHER-UPDATE] Updated fallback taskId: ${newTaskId}`);
|
|
102
116
|
};
|
|
103
117
|
// Create a scoped logger that always uses this session's sessionId
|
|
104
118
|
// and dynamically resolves the latest taskId
|
|
@@ -116,6 +130,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
116
130
|
let hasSentResponse = false;
|
|
117
131
|
let finalSent = false;
|
|
118
132
|
let accumulatedText = "";
|
|
133
|
+
let finalReplyText = "";
|
|
119
134
|
const initialRunCrossTaskContext = getCurrentSessionContext()?.runCrossTaskContext;
|
|
120
135
|
const getRunCrossTaskContext = () => {
|
|
121
136
|
return getCurrentSessionContext()?.runCrossTaskContext ?? initialRunCrossTaskContext;
|
|
@@ -128,13 +143,12 @@ export function createXYReplyDispatcher(params) {
|
|
|
128
143
|
statusUpdateInterval = setInterval(() => {
|
|
129
144
|
// 🔑 使用动态taskId
|
|
130
145
|
const currentTaskId = getActiveTaskId();
|
|
131
|
-
const currentMessageId = getActiveMessageId();
|
|
132
146
|
scopedLog().log(`[STATUS-INTERVAL] Triggering status update, taskId=${currentTaskId}`);
|
|
133
147
|
void sendStatusUpdate({
|
|
134
148
|
config,
|
|
135
149
|
sessionId,
|
|
136
|
-
taskId: currentTaskId,
|
|
137
|
-
messageId:
|
|
150
|
+
taskId: currentTaskId,
|
|
151
|
+
messageId: getActiveMessageId(),
|
|
138
152
|
text: "任务正在处理中,请稍候~",
|
|
139
153
|
state: "working",
|
|
140
154
|
}).catch((err) => {
|
|
@@ -154,24 +168,20 @@ export function createXYReplyDispatcher(params) {
|
|
|
154
168
|
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
|
155
169
|
humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, accountId),
|
|
156
170
|
onReplyStart: () => {
|
|
157
|
-
|
|
158
|
-
scopedLog().log(`[REPLY-START] Reply started, taskId=${currentTaskId}, steered=${steerState.steered}`);
|
|
171
|
+
scopedLog().log(`[REPLY-START] Reply started, taskId=${taskId}, steered=${steerState.steered}`);
|
|
159
172
|
},
|
|
160
173
|
deliver: async (payload, info) => {
|
|
161
|
-
// 🔑 steered dispatch不发送内容(让主dispatcher处理)
|
|
162
|
-
if (steerState.steered) {
|
|
163
|
-
scopedLog().log(`[DELIVER] Steered dispatch, skipping, kind=${info?.kind}`);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
174
|
const text = payload.text ?? "";
|
|
167
|
-
const currentTaskId = getActiveTaskId();
|
|
168
|
-
const currentMessageId = getActiveMessageId();
|
|
169
175
|
scopedLog().log(`[DELIVER] kind=${info?.kind}, text.length=${text.length}`);
|
|
170
176
|
try {
|
|
171
177
|
if (!text.trim()) {
|
|
172
178
|
scopedLog().log(`[DELIVER SKIP] Empty text, skipping`);
|
|
173
179
|
return;
|
|
174
180
|
}
|
|
181
|
+
if (info?.kind === "final") {
|
|
182
|
+
finalReplyText = text;
|
|
183
|
+
scopedLog().log(`[DELIVER] Captured final reply text, length=${finalReplyText.length}`);
|
|
184
|
+
}
|
|
175
185
|
// 🔑 如果 onPartialReply 已经流式发送过文本,deliver 不再重复发送
|
|
176
186
|
if (hasSentResponse) {
|
|
177
187
|
scopedLog().log(`[DELIVER SKIP] Already sent via onPartialReply`);
|
|
@@ -183,8 +193,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
183
193
|
await sendA2AResponse({
|
|
184
194
|
config,
|
|
185
195
|
sessionId,
|
|
186
|
-
taskId:
|
|
187
|
-
messageId:
|
|
196
|
+
taskId: getActiveTaskId(),
|
|
197
|
+
messageId: getActiveMessageId(),
|
|
188
198
|
text,
|
|
189
199
|
append: true,
|
|
190
200
|
final: false,
|
|
@@ -197,20 +207,13 @@ export function createXYReplyDispatcher(params) {
|
|
|
197
207
|
onError: async (err, info) => {
|
|
198
208
|
runtime.error?.(`xy: ${info.kind} reply failed: ${String(err)}`);
|
|
199
209
|
stopStatusInterval();
|
|
200
|
-
// 🔑 steered dispatcher不发送错误状态(让主dispatcher处理)
|
|
201
|
-
if (steerState.steered) {
|
|
202
|
-
scopedLog().log(`[ON-ERROR] Steered dispatch, skipping error response`);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
210
|
if (!hasSentResponse) {
|
|
206
|
-
const currentTaskId = getActiveTaskId();
|
|
207
|
-
const currentMessageId = getActiveMessageId();
|
|
208
211
|
try {
|
|
209
212
|
await sendStatusUpdate({
|
|
210
213
|
config,
|
|
211
214
|
sessionId,
|
|
212
|
-
taskId:
|
|
213
|
-
messageId:
|
|
215
|
+
taskId: getActiveTaskId(),
|
|
216
|
+
messageId: getActiveMessageId(),
|
|
214
217
|
text: "处理失败,请稍后重试",
|
|
215
218
|
state: "failed",
|
|
216
219
|
});
|
|
@@ -221,105 +224,119 @@ export function createXYReplyDispatcher(params) {
|
|
|
221
224
|
}
|
|
222
225
|
},
|
|
223
226
|
onIdle: async () => {
|
|
224
|
-
const currentTaskId = getActiveTaskId();
|
|
225
|
-
const currentMessageId = getActiveMessageId();
|
|
226
227
|
scopedLog().log(`[ON-IDLE] Reply idle, steered=${steerState.steered}, hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
|
|
227
|
-
// 🔑 steered dispatch
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
// 🔑 steered dispatch without response — steer was injected into active run,
|
|
229
|
+
// no reply generated. Skip final response without sending error.
|
|
230
|
+
if (steerState.steered && !hasSentResponse) {
|
|
231
|
+
scopedLog().log(`[ON-IDLE] Steered dispatch, no response generated, skipping`);
|
|
230
232
|
stopStatusInterval();
|
|
231
|
-
return;
|
|
233
|
+
return;
|
|
232
234
|
}
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
// 🔑 用 try/finally 确保 cleanup 在 onIdle 的 async 工作全部完成后才执行。
|
|
236
|
+
// openclaw 的 waitForIdle() 以 void options.onIdle?.() 调用 onIdle,
|
|
237
|
+
// 不会 await 返回的 Promise,因此 onSettled 可能在 onIdle 中途触发。
|
|
238
|
+
// 所有清理逻辑必须放在 finally 块中,不要依赖 onSettled。
|
|
239
|
+
try {
|
|
240
|
+
// 正常模式(或未被steer的dispatch)
|
|
241
|
+
if (hasSentResponse && !finalSent) {
|
|
242
|
+
const trimmedFinalReplyText = finalReplyText.trim();
|
|
243
|
+
const trimmedAccumulatedText = accumulatedText.trim();
|
|
244
|
+
const crossTaskResultMessage = trimmedFinalReplyText || trimmedAccumulatedText;
|
|
245
|
+
const crossTaskResultSource = trimmedFinalReplyText ? "final" : "accumulated";
|
|
246
|
+
scopedLog().log(`[ON-IDLE] [SendCrossResult]Sending cross-task result, source=${crossTaskResultSource}, resultMessage.length=${crossTaskResultMessage.length}`);
|
|
247
|
+
try {
|
|
248
|
+
const runCrossTaskContext = getRunCrossTaskContext();
|
|
249
|
+
if (runCrossTaskContext) {
|
|
250
|
+
await sendRunCrossTaskResult({
|
|
251
|
+
config,
|
|
252
|
+
sessionId,
|
|
253
|
+
taskId: getActiveTaskId(),
|
|
254
|
+
messageId: getActiveMessageId(),
|
|
255
|
+
context: runCrossTaskContext,
|
|
256
|
+
resultCode: "0",
|
|
257
|
+
resultMessage: crossTaskResultMessage,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
// 🔑 使用动态taskId发送完成状态
|
|
261
|
+
await sendStatusUpdate({
|
|
262
|
+
config,
|
|
263
|
+
sessionId,
|
|
264
|
+
taskId: getActiveTaskId(),
|
|
265
|
+
messageId: getActiveMessageId(),
|
|
266
|
+
text: "任务处理已完成~",
|
|
267
|
+
state: "completed",
|
|
268
|
+
});
|
|
269
|
+
scopedLog().log(`[ON-IDLE] Sent completion status update`);
|
|
270
|
+
// 🔑 使用动态taskId发送最终响应(空字符串表示流结束)
|
|
271
|
+
await sendA2AResponse({
|
|
240
272
|
config,
|
|
241
273
|
sessionId,
|
|
242
|
-
taskId:
|
|
243
|
-
messageId:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
274
|
+
taskId: getActiveTaskId(),
|
|
275
|
+
messageId: getActiveMessageId(),
|
|
276
|
+
text: "",
|
|
277
|
+
append: true,
|
|
278
|
+
final: true,
|
|
247
279
|
});
|
|
280
|
+
finalSent = true;
|
|
281
|
+
scopedLog().log(`[ON-IDLE] Sent final response (empty, stream end)`);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
scopedLog().error(`[ON-IDLE] Failed to send final response:`, err);
|
|
248
285
|
}
|
|
249
|
-
// 🔑 使用动态taskId发送完成状态
|
|
250
|
-
await sendStatusUpdate({
|
|
251
|
-
config,
|
|
252
|
-
sessionId,
|
|
253
|
-
taskId: currentTaskId,
|
|
254
|
-
messageId: currentMessageId,
|
|
255
|
-
text: "任务处理已完成~",
|
|
256
|
-
state: "completed",
|
|
257
|
-
});
|
|
258
|
-
scopedLog().log(`[ON-IDLE] Sent completion status update`);
|
|
259
|
-
// 🔑 使用动态taskId发送最终响应(空字符串表示流结束)
|
|
260
|
-
await sendA2AResponse({
|
|
261
|
-
config,
|
|
262
|
-
sessionId,
|
|
263
|
-
taskId: currentTaskId,
|
|
264
|
-
messageId: currentMessageId,
|
|
265
|
-
text: "",
|
|
266
|
-
append: true,
|
|
267
|
-
final: true,
|
|
268
|
-
});
|
|
269
|
-
finalSent = true;
|
|
270
|
-
scopedLog().log(`[ON-IDLE] Sent final response (empty, stream end)`);
|
|
271
|
-
}
|
|
272
|
-
catch (err) {
|
|
273
|
-
scopedLog().error(`[ON-IDLE] Failed to send final response:`, err);
|
|
274
286
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
287
|
+
else {
|
|
288
|
+
// 正常失败场景(非steered)
|
|
289
|
+
scopedLog().log(`[ON-IDLE] Skipping final message: hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
|
|
290
|
+
try {
|
|
291
|
+
const runCrossTaskContext = getRunCrossTaskContext();
|
|
292
|
+
if (runCrossTaskContext) {
|
|
293
|
+
await sendRunCrossTaskResult({
|
|
294
|
+
config,
|
|
295
|
+
sessionId,
|
|
296
|
+
taskId: getActiveTaskId(),
|
|
297
|
+
messageId: getActiveMessageId(),
|
|
298
|
+
context: runCrossTaskContext,
|
|
299
|
+
resultCode: "1",
|
|
300
|
+
resultMessage: "任务执行异常,请重试",
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
await sendStatusUpdate({
|
|
304
|
+
config,
|
|
305
|
+
sessionId,
|
|
306
|
+
taskId: getActiveTaskId(),
|
|
307
|
+
messageId: getActiveMessageId(),
|
|
308
|
+
text: "任务处理中断了~",
|
|
309
|
+
state: "failed",
|
|
310
|
+
});
|
|
311
|
+
scopedLog().log(`[ON-IDLE] Sent failure status update`);
|
|
312
|
+
await sendA2AResponse({
|
|
283
313
|
config,
|
|
284
314
|
sessionId,
|
|
285
|
-
taskId:
|
|
286
|
-
messageId:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
315
|
+
taskId: getActiveTaskId(),
|
|
316
|
+
messageId: getActiveMessageId(),
|
|
317
|
+
text: "任务执行异常,请重试~",
|
|
318
|
+
append: true,
|
|
319
|
+
final: true,
|
|
320
|
+
errorCode: 99921111,
|
|
321
|
+
errorMessage: "任务执行异常,请重试",
|
|
290
322
|
});
|
|
323
|
+
finalSent = true;
|
|
324
|
+
scopedLog().log(`[ON-IDLE] Sent error response, code=99921111`);
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
scopedLog().error(`[ON-IDLE] Failed to send error response:`, err);
|
|
291
328
|
}
|
|
292
|
-
await sendStatusUpdate({
|
|
293
|
-
config,
|
|
294
|
-
sessionId,
|
|
295
|
-
taskId: currentTaskId,
|
|
296
|
-
messageId: currentMessageId,
|
|
297
|
-
text: "任务处理中断了~",
|
|
298
|
-
state: "failed",
|
|
299
|
-
});
|
|
300
|
-
scopedLog().log(`[ON-IDLE] Sent failure status update`);
|
|
301
|
-
await sendA2AResponse({
|
|
302
|
-
config,
|
|
303
|
-
sessionId,
|
|
304
|
-
taskId: currentTaskId,
|
|
305
|
-
messageId: currentMessageId,
|
|
306
|
-
text: "任务执行异常,请重试~",
|
|
307
|
-
append: true,
|
|
308
|
-
final: true,
|
|
309
|
-
errorCode: 99921111,
|
|
310
|
-
errorMessage: "任务执行异常,请重试",
|
|
311
|
-
});
|
|
312
|
-
finalSent = true;
|
|
313
|
-
scopedLog().log(`[ON-IDLE] Sent error response, code=99921111`);
|
|
314
|
-
}
|
|
315
|
-
catch (err) {
|
|
316
|
-
scopedLog().error(`[ON-IDLE] Failed to send error response:`, err);
|
|
317
329
|
}
|
|
318
330
|
}
|
|
319
|
-
|
|
331
|
+
finally {
|
|
332
|
+
stopStatusInterval();
|
|
333
|
+
// 🔑 清理必须在 onIdle 内部完成,因为 openclaw 的 waitForIdle() 不会
|
|
334
|
+
// await onIdle 返回的 Promise(源码中为 void options.onIdle?.()),
|
|
335
|
+
// 导致 onSettled 在 onIdle 的 async 工作完成之前就执行。
|
|
336
|
+
await onIdleComplete?.();
|
|
337
|
+
}
|
|
320
338
|
},
|
|
321
339
|
onCleanup: () => {
|
|
322
|
-
const currentTaskId = getActiveTaskId();
|
|
323
340
|
scopedLog().log(`[ON-CLEANUP] Reply cleanup, steered=${steerState.steered}`);
|
|
324
341
|
},
|
|
325
342
|
});
|
|
@@ -330,12 +347,6 @@ export function createXYReplyDispatcher(params) {
|
|
|
330
347
|
suppressToolErrorWarnings: true,
|
|
331
348
|
onModelSelected: prefixContext.onModelSelected,
|
|
332
349
|
onToolStart: async ({ name, phase }) => {
|
|
333
|
-
// 🔑 steered dispatch不发送tool状态(让主dispatcher处理)
|
|
334
|
-
if (steerState.steered) {
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
const currentTaskId = getActiveTaskId();
|
|
338
|
-
const currentMessageId = getActiveMessageId();
|
|
339
350
|
scopedLog().log(`[TOOL-START] Tool: ${name}, phase: ${phase}`);
|
|
340
351
|
if (phase === "start") {
|
|
341
352
|
const toolName = name || "unknown";
|
|
@@ -349,8 +360,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
349
360
|
await sendStatusUpdate({
|
|
350
361
|
config,
|
|
351
362
|
sessionId,
|
|
352
|
-
taskId:
|
|
353
|
-
messageId:
|
|
363
|
+
taskId: getActiveTaskId(),
|
|
364
|
+
messageId: getActiveMessageId(),
|
|
354
365
|
text: `正在使用工具: ${toolName}...`,
|
|
355
366
|
state: "working",
|
|
356
367
|
});
|
|
@@ -362,12 +373,6 @@ export function createXYReplyDispatcher(params) {
|
|
|
362
373
|
}
|
|
363
374
|
},
|
|
364
375
|
onToolResult: async (payload) => {
|
|
365
|
-
// 🔑 steered dispatch不发送tool结果(让主dispatcher处理)
|
|
366
|
-
if (steerState.steered) {
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
const currentTaskId = getActiveTaskId();
|
|
370
|
-
const currentMessageId = getActiveMessageId();
|
|
371
376
|
const text = payload.text ?? "";
|
|
372
377
|
const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
|
|
373
378
|
scopedLog().log(`[TOOL-RESULT] Tool result, text.length: ${text.length}`);
|
|
@@ -377,8 +382,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
377
382
|
await sendStatusUpdate({
|
|
378
383
|
config,
|
|
379
384
|
sessionId,
|
|
380
|
-
taskId:
|
|
381
|
-
messageId:
|
|
385
|
+
taskId: getActiveTaskId(),
|
|
386
|
+
messageId: getActiveMessageId(),
|
|
382
387
|
text: resultText,
|
|
383
388
|
state: "working",
|
|
384
389
|
});
|
|
@@ -390,12 +395,6 @@ export function createXYReplyDispatcher(params) {
|
|
|
390
395
|
}
|
|
391
396
|
},
|
|
392
397
|
onReasoningStream: async (payload) => {
|
|
393
|
-
// 🔑 steered dispatch不发送reasoning stream
|
|
394
|
-
if (steerState.steered) {
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
const currentTaskId = getActiveTaskId();
|
|
398
|
-
const currentMessageId = getActiveMessageId();
|
|
399
398
|
let text = payload.text ?? "";
|
|
400
399
|
// Strip "Reasoning:" prefix that some reasoning models add to their thinking output
|
|
401
400
|
const lines = text.split(/\r?\n/);
|
|
@@ -407,8 +406,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
407
406
|
await sendReasoningTextUpdate({
|
|
408
407
|
config,
|
|
409
408
|
sessionId,
|
|
410
|
-
taskId:
|
|
411
|
-
messageId:
|
|
409
|
+
taskId: getActiveTaskId(),
|
|
410
|
+
messageId: getActiveMessageId(),
|
|
412
411
|
text,
|
|
413
412
|
append: false,
|
|
414
413
|
});
|
|
@@ -419,27 +418,29 @@ export function createXYReplyDispatcher(params) {
|
|
|
419
418
|
}
|
|
420
419
|
},
|
|
421
420
|
onPartialReply: async (payload) => {
|
|
422
|
-
|
|
423
|
-
if (steerState.steered) {
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
const currentTaskId = getActiveTaskId();
|
|
427
|
-
const currentMessageId = getActiveMessageId();
|
|
428
|
-
const text = payload.text ?? "";
|
|
421
|
+
let text = payload.text ?? "";
|
|
429
422
|
try {
|
|
430
423
|
if (text.length > 0) {
|
|
431
|
-
|
|
424
|
+
// 模型可能返回完整文本(非纯增量),如果新文本以已累积内容开头,
|
|
425
|
+
// 则只取新增的后缀部分,避免 append 模式下重复拼接。
|
|
426
|
+
let sendText = text;
|
|
427
|
+
if (accumulatedText.length > 0 && text.startsWith(accumulatedText)) {
|
|
428
|
+
sendText = text.slice(accumulatedText.length);
|
|
429
|
+
}
|
|
430
|
+
accumulatedText += sendText;
|
|
432
431
|
hasSentResponse = true;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
432
|
+
if (sendText.length > 0) {
|
|
433
|
+
await sendA2AResponse({
|
|
434
|
+
config,
|
|
435
|
+
sessionId,
|
|
436
|
+
taskId: getActiveTaskId(),
|
|
437
|
+
messageId: getActiveMessageId(),
|
|
438
|
+
text: sendText,
|
|
439
|
+
append: true,
|
|
440
|
+
final: false,
|
|
441
|
+
log: false,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
443
444
|
}
|
|
444
445
|
}
|
|
445
446
|
catch (err) {
|
|
@@ -450,5 +451,6 @@ export function createXYReplyDispatcher(params) {
|
|
|
450
451
|
markDispatchIdle,
|
|
451
452
|
startStatusInterval,
|
|
452
453
|
stopStatusInterval,
|
|
454
|
+
updateFallbackTaskId,
|
|
453
455
|
};
|
|
454
456
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os from "os";
|
|
1
2
|
import { readFileSync, writeFileSync } from "fs";
|
|
2
3
|
import { v4 as uuidv4 } from "uuid";
|
|
3
4
|
import { resolveXYConfig } from "./config.js";
|
|
@@ -136,7 +137,7 @@ export async function handleSelfEvolutionStateGetEvent(context, cfg, runtime, ws
|
|
|
136
137
|
agentId: cfg.agentId,
|
|
137
138
|
sessionId,
|
|
138
139
|
taskId,
|
|
139
|
-
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
140
|
+
msgDetail: JSON.stringify({ ...jsonRpcResponse, hostname: os.hostname() }),
|
|
140
141
|
};
|
|
141
142
|
logger.log(`[A2A_COMMAND] Sending A2A command, taskId: ${taskId}`);
|
|
142
143
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
@@ -14,11 +14,14 @@ export declare function registerTaskId(sessionId: string, taskId: string, messag
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function decrementTaskIdRef(sessionId: string): void;
|
|
16
16
|
/**
|
|
17
|
-
* 获取session的当前活跃taskId
|
|
17
|
+
* 获取session的当前活跃taskId。
|
|
18
|
+
* 仅供 reply-dispatcher 跨链读取 steer 更新后的 taskId。
|
|
19
|
+
* 工具和 sendCommand 应使用 ALS SessionContext.taskId。
|
|
18
20
|
*/
|
|
19
21
|
export declare function getCurrentTaskId(sessionId: string): string | null;
|
|
20
22
|
/**
|
|
21
|
-
* 获取session的当前活跃messageId
|
|
23
|
+
* 获取session的当前活跃messageId。
|
|
24
|
+
* 仅供 reply-dispatcher 跨链读取 steer 更新后的 messageId。
|
|
22
25
|
*/
|
|
23
26
|
export declare function getCurrentMessageId(sessionId: string): string | null;
|
|
24
27
|
/**
|
|
@@ -29,8 +32,4 @@ export declare function hasActiveTask(sessionId: string): boolean;
|
|
|
29
32
|
* 获取所有活跃的 task bindings(用于 gateway_stop 通知等场景)
|
|
30
33
|
*/
|
|
31
34
|
export declare function getAllActiveTaskBindings(): TaskIdBinding[];
|
|
32
|
-
/**
|
|
33
|
-
* 强制清理(错误恢复用)
|
|
34
|
-
*/
|
|
35
|
-
export declare function forceCleanTaskId(sessionId: string): void;
|
|
36
35
|
export {};
|
package/dist/src/task-manager.js
CHANGED
|
@@ -44,14 +44,17 @@ export function decrementTaskIdRef(sessionId) {
|
|
|
44
44
|
activeTaskIds.delete(sessionId);
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* 获取session的当前活跃taskId
|
|
47
|
+
* 获取session的当前活跃taskId。
|
|
48
|
+
* 仅供 reply-dispatcher 跨链读取 steer 更新后的 taskId。
|
|
49
|
+
* 工具和 sendCommand 应使用 ALS SessionContext.taskId。
|
|
48
50
|
*/
|
|
49
51
|
export function getCurrentTaskId(sessionId) {
|
|
50
52
|
const binding = activeTaskIds.get(sessionId);
|
|
51
53
|
return binding?.currentTaskId ?? null;
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
|
-
* 获取session的当前活跃messageId
|
|
56
|
+
* 获取session的当前活跃messageId。
|
|
57
|
+
* 仅供 reply-dispatcher 跨链读取 steer 更新后的 messageId。
|
|
55
58
|
*/
|
|
56
59
|
export function getCurrentMessageId(sessionId) {
|
|
57
60
|
const binding = activeTaskIds.get(sessionId);
|
|
@@ -69,10 +72,3 @@ export function hasActiveTask(sessionId) {
|
|
|
69
72
|
export function getAllActiveTaskBindings() {
|
|
70
73
|
return Array.from(activeTaskIds.values());
|
|
71
74
|
}
|
|
72
|
-
/**
|
|
73
|
-
* 强制清理(错误恢复用)
|
|
74
|
-
*/
|
|
75
|
-
export function forceCleanTaskId(sessionId) {
|
|
76
|
-
logger.log(`[TASK_MANAGER] Force clearing taskId`);
|
|
77
|
-
activeTaskIds.delete(sessionId);
|
|
78
|
-
}
|
|
@@ -1,7 +1,46 @@
|
|
|
1
|
-
import type { SessionContext } from "./session-manager.js";
|
|
2
1
|
/**
|
|
3
2
|
* Agent-as-skill tool - invokes a registered agent by agentId as a skill.
|
|
4
3
|
* The tool receives the agentId, query, and optional file attachments,
|
|
5
4
|
* forwards the request to the target agent via WebSocket, and returns the result.
|
|
6
5
|
*/
|
|
7
|
-
export declare
|
|
6
|
+
export declare const agentAsSkillTool: {
|
|
7
|
+
name: string;
|
|
8
|
+
label: string;
|
|
9
|
+
description: string;
|
|
10
|
+
parameters: {
|
|
11
|
+
type: string;
|
|
12
|
+
properties: {
|
|
13
|
+
agentId: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
query: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
filesInfo: {
|
|
22
|
+
description: string;
|
|
23
|
+
items: {
|
|
24
|
+
type: string;
|
|
25
|
+
properties: {
|
|
26
|
+
fileType: {
|
|
27
|
+
type: string;
|
|
28
|
+
description: string;
|
|
29
|
+
enum: string[];
|
|
30
|
+
};
|
|
31
|
+
fileId: {
|
|
32
|
+
type: string;
|
|
33
|
+
description: string;
|
|
34
|
+
};
|
|
35
|
+
fileUrl: {
|
|
36
|
+
type: string;
|
|
37
|
+
description: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
required: string[];
|
|
44
|
+
};
|
|
45
|
+
execute(toolCallId: string, params: any): Promise<unknown>;
|
|
46
|
+
};
|