@ynhcj/xiaoyi-channel 0.0.16-beta → 0.0.16-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/src/bot.js +120 -41
- package/dist/src/channel.js +17 -4
- package/dist/src/client.js +11 -24
- package/dist/src/config.js +2 -2
- package/dist/src/file-upload.d.ts +5 -0
- package/dist/src/file-upload.js +92 -0
- package/dist/src/formatter.d.ts +14 -0
- package/dist/src/formatter.js +46 -2
- package/dist/src/monitor.js +47 -5
- package/dist/src/outbound.js +52 -6
- package/dist/src/parser.d.ts +7 -0
- package/dist/src/parser.js +22 -0
- package/dist/src/push.d.ts +8 -1
- package/dist/src/push.js +30 -22
- package/dist/src/reply-dispatcher.d.ts +1 -0
- package/dist/src/reply-dispatcher.js +115 -100
- package/dist/src/task-manager.d.ts +55 -0
- package/dist/src/task-manager.js +136 -0
- package/dist/src/tools/calendar-tool.js +5 -3
- package/dist/src/tools/call-phone-tool.d.ts +5 -0
- package/dist/src/tools/call-phone-tool.js +183 -0
- package/dist/src/tools/create-alarm-tool.d.ts +7 -0
- package/dist/src/tools/create-alarm-tool.js +430 -0
- package/dist/src/tools/delete-alarm-tool.d.ts +11 -0
- package/dist/src/tools/delete-alarm-tool.js +227 -0
- package/dist/src/tools/image-reading-tool.d.ts +5 -0
- package/dist/src/tools/image-reading-tool.js +353 -0
- package/dist/src/tools/location-tool.js +8 -11
- package/dist/src/tools/modify-alarm-tool.d.ts +9 -0
- package/dist/src/tools/modify-alarm-tool.js +449 -0
- package/dist/src/tools/modify-note-tool.js +4 -9
- package/dist/src/tools/note-tool.js +32 -21
- package/dist/src/tools/search-alarm-tool.d.ts +8 -0
- package/dist/src/tools/search-alarm-tool.js +391 -0
- package/dist/src/tools/search-calendar-tool.js +6 -3
- package/dist/src/tools/search-contact-tool.js +2 -2
- package/dist/src/tools/search-file-tool.d.ts +5 -0
- package/dist/src/tools/search-file-tool.js +185 -0
- package/dist/src/tools/search-message-tool.d.ts +5 -0
- package/dist/src/tools/search-message-tool.js +173 -0
- package/dist/src/tools/search-note-tool.js +2 -2
- package/dist/src/tools/search-photo-gallery-tool.js +54 -17
- package/dist/src/tools/send-file-to-user-tool.d.ts +5 -0
- package/dist/src/tools/send-file-to-user-tool.js +318 -0
- package/dist/src/tools/send-message-tool.d.ts +5 -0
- package/dist/src/tools/send-message-tool.js +189 -0
- package/dist/src/tools/session-manager.d.ts +15 -0
- package/dist/src/tools/session-manager.js +99 -18
- package/dist/src/tools/upload-file-tool.d.ts +13 -0
- package/dist/src/tools/upload-file-tool.js +265 -0
- package/dist/src/tools/upload-photo-tool.js +14 -4
- package/dist/src/tools/view-push-result-tool.d.ts +5 -0
- package/dist/src/tools/view-push-result-tool.js +118 -0
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +5 -0
- package/dist/src/tools/xiaoyi-collection-tool.js +190 -0
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +6 -0
- package/dist/src/tools/xiaoyi-gui-tool.js +151 -0
- package/dist/src/trigger-handler.d.ts +22 -0
- package/dist/src/trigger-handler.js +59 -0
- package/dist/src/types.d.ts +1 -8
- package/dist/src/types.js +4 -0
- package/dist/src/utils/pushdata-manager.d.ts +28 -0
- package/dist/src/utils/pushdata-manager.js +171 -0
- package/dist/src/utils/pushid-manager.d.ts +12 -0
- package/dist/src/utils/pushid-manager.js +105 -0
- package/dist/src/websocket.d.ts +26 -31
- package/dist/src/websocket.js +227 -267
- package/package.json +1 -1
package/dist/src/outbound.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { resolveXYConfig } from "./config.js";
|
|
2
2
|
import { XYFileUploadService } from "./file-upload.js";
|
|
3
3
|
import { XYPushService } from "./push.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
5
|
+
import { savePushData } from "./utils/pushdata-manager.js";
|
|
6
|
+
import { getAllPushIds } from "./utils/pushid-manager.js";
|
|
5
7
|
// Special marker for default push delivery when no target is specified
|
|
6
8
|
const DEFAULT_PUSH_MARKER = "default";
|
|
7
9
|
// File extension to MIME type mapping
|
|
@@ -65,8 +67,8 @@ export const xyOutbound = {
|
|
|
65
67
|
// If the target doesn't contain "::", try to enhance it with taskId from session context
|
|
66
68
|
if (!trimmedTo.includes("::")) {
|
|
67
69
|
console.log(`[xyOutbound.resolveTarget] Target "${trimmedTo}" missing taskId, looking up session context`);
|
|
68
|
-
// Try to get the
|
|
69
|
-
const sessionContext =
|
|
70
|
+
// Try to get the current session context
|
|
71
|
+
const sessionContext = getCurrentSessionContext();
|
|
70
72
|
if (sessionContext && sessionContext.sessionId === trimmedTo) {
|
|
71
73
|
const enhancedTarget = `${trimmedTo}::${sessionContext.taskId}`;
|
|
72
74
|
console.log(`[xyOutbound.resolveTarget] Enhanced target: ${enhancedTarget}`);
|
|
@@ -105,19 +107,63 @@ export const xyOutbound = {
|
|
|
105
107
|
// The push service will handle it based on config
|
|
106
108
|
actualTo = config.defaultSessionId || "";
|
|
107
109
|
}
|
|
110
|
+
// 1. 持久化推送消息内容,获取 pushDataId
|
|
111
|
+
console.log(`[xyOutbound.sendText] Saving push data to local storage...`);
|
|
112
|
+
let pushDataId;
|
|
113
|
+
try {
|
|
114
|
+
pushDataId = await savePushData(text);
|
|
115
|
+
console.log(`[xyOutbound.sendText] ✅ Push data saved with ID: ${pushDataId}`);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error(`[xyOutbound.sendText] ❌ Failed to save push data:`, error);
|
|
119
|
+
// 如果持久化失败,仍然继续发送(不阻塞主流程)
|
|
120
|
+
pushDataId = "";
|
|
121
|
+
}
|
|
122
|
+
// 2. 读取所有 pushId
|
|
123
|
+
console.log(`[xyOutbound.sendText] Loading all pushIds...`);
|
|
124
|
+
let pushIdList = [];
|
|
125
|
+
try {
|
|
126
|
+
pushIdList = await getAllPushIds();
|
|
127
|
+
console.log(`[xyOutbound.sendText] ✅ Loaded ${pushIdList.length} pushIds`);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
console.error(`[xyOutbound.sendText] ❌ Failed to load pushIds:`, error);
|
|
131
|
+
}
|
|
132
|
+
// 3. 如果 pushIdList 为空,回退到原有逻辑(使用 config pushId)
|
|
133
|
+
if (pushIdList.length === 0) {
|
|
134
|
+
console.log(`[xyOutbound.sendText] ⚠️ No pushIds found, falling back to config pushId`);
|
|
135
|
+
pushIdList = [config.pushId];
|
|
136
|
+
}
|
|
108
137
|
// Create push service
|
|
109
138
|
const pushService = new XYPushService(config);
|
|
110
139
|
// Extract title (first 57 chars or first line)
|
|
111
140
|
const title = text.split("\n")[0].slice(0, 57);
|
|
112
141
|
// Truncate push content to max length 1000
|
|
113
142
|
const pushText = text.length > 1000 ? text.slice(0, 1000) : text;
|
|
114
|
-
//
|
|
115
|
-
|
|
143
|
+
// 4. 遍历所有 pushId,依次发送推送通知
|
|
144
|
+
console.log(`[xyOutbound.sendText] 📤 Broadcasting to ${pushIdList.length} pushId(s)...`);
|
|
145
|
+
let successCount = 0;
|
|
146
|
+
let failureCount = 0;
|
|
147
|
+
for (const pushId of pushIdList) {
|
|
148
|
+
try {
|
|
149
|
+
console.log(`[xyOutbound.sendText] Sending to pushId: ${pushId.substring(0, 20)}...`);
|
|
150
|
+
// 传入 pushId 和 pushDataId,使用 kind="data" 格式
|
|
151
|
+
await pushService.sendPush(pushText, title, undefined, actualTo, pushDataId, pushId);
|
|
152
|
+
successCount++;
|
|
153
|
+
console.log(`[xyOutbound.sendText] ✅ Sent successfully to pushId: ${pushId.substring(0, 20)}...`);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
failureCount++;
|
|
157
|
+
console.error(`[xyOutbound.sendText] ❌ Failed to send to pushId: ${pushId.substring(0, 20)}...`, error);
|
|
158
|
+
// 单个 pushId 发送失败不影响其他,继续处理下一个
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
console.log(`[xyOutbound.sendText] 📊 Broadcast summary: ${successCount} success, ${failureCount} failures`);
|
|
116
162
|
console.log(`[xyOutbound.sendText] Completed successfully`);
|
|
117
163
|
// Return message info
|
|
118
164
|
return {
|
|
119
165
|
channel: "xiaoyi-channel",
|
|
120
|
-
messageId: Date.now().toString(),
|
|
166
|
+
messageId: pushDataId || Date.now().toString(),
|
|
121
167
|
chatId: actualTo,
|
|
122
168
|
};
|
|
123
169
|
},
|
package/dist/src/parser.d.ts
CHANGED
|
@@ -43,6 +43,13 @@ export declare function isTasksCancelMessage(method: string): boolean;
|
|
|
43
43
|
* Looks for push_id in data parts under variables.systemVariables.push_id
|
|
44
44
|
*/
|
|
45
45
|
export declare function extractPushId(parts: A2AMessagePart[]): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Extract Trigger event data from message parts.
|
|
48
|
+
* Looks for Trigger events with pushDataId in data parts.
|
|
49
|
+
*/
|
|
50
|
+
export declare function extractTriggerData(parts: A2AMessagePart[]): {
|
|
51
|
+
pushDataId: string;
|
|
52
|
+
} | null;
|
|
46
53
|
/**
|
|
47
54
|
* Validate A2A request structure.
|
|
48
55
|
*/
|
package/dist/src/parser.js
CHANGED
|
@@ -72,6 +72,28 @@ export function extractPushId(parts) {
|
|
|
72
72
|
}
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Extract Trigger event data from message parts.
|
|
77
|
+
* Looks for Trigger events with pushDataId in data parts.
|
|
78
|
+
*/
|
|
79
|
+
export function extractTriggerData(parts) {
|
|
80
|
+
for (const part of parts) {
|
|
81
|
+
if (part.kind === "data" && part.data) {
|
|
82
|
+
const events = part.data.events;
|
|
83
|
+
if (Array.isArray(events)) {
|
|
84
|
+
for (const event of events) {
|
|
85
|
+
if (event.header?.namespace === "Common" && event.header?.name === "Trigger") {
|
|
86
|
+
const pushDataId = event.payload?.dataMap?.pushDataId;
|
|
87
|
+
if (pushDataId && typeof pushDataId === "string") {
|
|
88
|
+
return { pushDataId };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
75
97
|
/**
|
|
76
98
|
* Validate A2A request structure.
|
|
77
99
|
*/
|
package/dist/src/push.d.ts
CHANGED
|
@@ -14,8 +14,15 @@ export declare class XYPushService {
|
|
|
14
14
|
private generateTraceId;
|
|
15
15
|
/**
|
|
16
16
|
* Send a push message to a user session.
|
|
17
|
+
*
|
|
18
|
+
* @param content - Push message content
|
|
19
|
+
* @param title - Push message title
|
|
20
|
+
* @param data - Optional additional data
|
|
21
|
+
* @param sessionId - Optional session ID
|
|
22
|
+
* @param pushDataId - Optional pushDataId for kind="data" format
|
|
23
|
+
* @param pushId - Push ID to use (required)
|
|
17
24
|
*/
|
|
18
|
-
sendPush(content: string, title: string, data?: Record<string, any>, sessionId?: string): Promise<void>;
|
|
25
|
+
sendPush(content: string, title: string, data?: Record<string, any>, sessionId?: string, pushDataId?: string, pushId?: string): Promise<void>;
|
|
19
26
|
/**
|
|
20
27
|
* Send a push message with file attachments.
|
|
21
28
|
*/
|
package/dist/src/push.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Push message service for scheduled tasks
|
|
2
2
|
import fetch from "node-fetch";
|
|
3
3
|
import { randomUUID } from "crypto";
|
|
4
|
-
import { configManager } from "./utils/config-manager.js";
|
|
5
4
|
/**
|
|
6
5
|
* Service for sending push messages to users.
|
|
7
6
|
* Used for outbound messages and scheduled tasks.
|
|
@@ -21,27 +20,27 @@ export class XYPushService {
|
|
|
21
20
|
}
|
|
22
21
|
/**
|
|
23
22
|
* Send a push message to a user session.
|
|
23
|
+
*
|
|
24
|
+
* @param content - Push message content
|
|
25
|
+
* @param title - Push message title
|
|
26
|
+
* @param data - Optional additional data
|
|
27
|
+
* @param sessionId - Optional session ID
|
|
28
|
+
* @param pushDataId - Optional pushDataId for kind="data" format
|
|
29
|
+
* @param pushId - Push ID to use (required)
|
|
24
30
|
*/
|
|
25
|
-
async sendPush(content, title, data, sessionId) {
|
|
31
|
+
async sendPush(content, title, data, sessionId, pushDataId, pushId) {
|
|
26
32
|
const pushUrl = this.config.pushUrl || this.DEFAULT_PUSH_URL;
|
|
27
33
|
const traceId = this.generateTraceId();
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
const pushId = dynamicPushId || this.config.pushId;
|
|
34
|
+
// Use provided pushId or fall back to config pushId
|
|
35
|
+
const actualPushId = pushId || this.config.pushId;
|
|
31
36
|
console.log(`[PUSH] 📤 Preparing to send push message`);
|
|
32
37
|
console.log(`[PUSH] - Title: "${title}"`);
|
|
33
38
|
console.log(`[PUSH] - Content length: ${content.length} chars`);
|
|
34
39
|
console.log(`[PUSH] - Session ID: ${sessionId || 'none'}`);
|
|
35
40
|
console.log(`[PUSH] - Trace ID: ${traceId}`);
|
|
36
41
|
console.log(`[PUSH] - Push URL: ${pushUrl}`);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
console.log(`[PUSH] - Full dynamic pushId: ${pushId}`);
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
console.log(`[PUSH] - Using config pushId (fallback): ${pushId.substring(0, 20)}...`);
|
|
43
|
-
console.log(`[PUSH] - Full config pushId: ${pushId}`);
|
|
44
|
-
}
|
|
42
|
+
console.log(`[PUSH] - Using pushId: ${actualPushId.substring(0, 20)}...`);
|
|
43
|
+
console.log(`[PUSH] - Full pushId: ${actualPushId}`);
|
|
45
44
|
console.log(`[PUSH] - API ID: ${this.config.apiId}`);
|
|
46
45
|
console.log(`[PUSH] - UID: ${this.config.uid}`);
|
|
47
46
|
try {
|
|
@@ -51,18 +50,27 @@ export class XYPushService {
|
|
|
51
50
|
result: {
|
|
52
51
|
id: randomUUID(),
|
|
53
52
|
apiId: this.config.apiId,
|
|
54
|
-
pushId:
|
|
53
|
+
pushId: actualPushId,
|
|
55
54
|
pushText: title,
|
|
56
55
|
kind: "task",
|
|
57
56
|
artifacts: [
|
|
58
57
|
{
|
|
59
58
|
artifactId: randomUUID(),
|
|
60
|
-
parts:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
parts: pushDataId
|
|
60
|
+
? [
|
|
61
|
+
{
|
|
62
|
+
kind: "data",
|
|
63
|
+
data: {
|
|
64
|
+
pushDataId: pushDataId,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
: [
|
|
69
|
+
{
|
|
70
|
+
kind: "text",
|
|
71
|
+
text: content,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
66
74
|
},
|
|
67
75
|
],
|
|
68
76
|
},
|
|
@@ -114,14 +122,14 @@ export class XYPushService {
|
|
|
114
122
|
console.log(`[PUSH] ✅ Push message sent successfully`);
|
|
115
123
|
console.log(`[PUSH] - Title: "${title}"`);
|
|
116
124
|
console.log(`[PUSH] - Trace ID: ${traceId}`);
|
|
117
|
-
console.log(`[PUSH] - Used pushId: ${
|
|
125
|
+
console.log(`[PUSH] - Used pushId: ${actualPushId.substring(0, 20)}...`);
|
|
118
126
|
console.log(`[PUSH] - Response:`, result);
|
|
119
127
|
}
|
|
120
128
|
catch (error) {
|
|
121
129
|
console.log(`[PUSH] ❌ Failed to send push message`);
|
|
122
130
|
console.log(`[PUSH] - Trace ID: ${traceId}`);
|
|
123
131
|
console.log(`[PUSH] - Target URL: ${pushUrl}`);
|
|
124
|
-
console.log(`[PUSH] - Push ID: ${
|
|
132
|
+
console.log(`[PUSH] - Push ID: ${actualPushId.substring(0, 20)}...`);
|
|
125
133
|
if (error instanceof Error) {
|
|
126
134
|
console.log(`[PUSH] - Error name: ${error.name}`);
|
|
127
135
|
console.log(`[PUSH] - Error message: ${error.message}`);
|
|
@@ -2,47 +2,58 @@ import { createReplyPrefixContext } from "openclaw/plugin-sdk";
|
|
|
2
2
|
import { getXYRuntime } from "./runtime.js";
|
|
3
3
|
import { sendA2AResponse, sendStatusUpdate, sendReasoningTextUpdate } from "./formatter.js";
|
|
4
4
|
import { resolveXYConfig } from "./config.js";
|
|
5
|
+
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
5
6
|
/**
|
|
6
7
|
* Create a reply dispatcher for XY channel messages.
|
|
7
8
|
* Follows feishu pattern with status updates and streaming support.
|
|
8
9
|
* Runtime is expected to be validated before calling this function.
|
|
9
10
|
*/
|
|
10
11
|
export function createXYReplyDispatcher(params) {
|
|
11
|
-
const { cfg, runtime, sessionId, taskId, messageId, accountId } = params;
|
|
12
|
+
const { cfg, runtime, sessionId, taskId, messageId, accountId, isSteerFollower } = params;
|
|
12
13
|
const log = runtime?.log ?? console.log;
|
|
13
14
|
const error = runtime?.error ?? console.error;
|
|
14
|
-
log(`[DISPATCHER-CREATE] ******* Creating dispatcher
|
|
15
|
-
log(`[DISPATCHER-CREATE]
|
|
16
|
-
log(`[DISPATCHER-CREATE]
|
|
17
|
-
log(`[DISPATCHER-CREATE]
|
|
18
|
-
log(`[DISPATCHER-CREATE]
|
|
19
|
-
//
|
|
15
|
+
log(`[DISPATCHER-CREATE] ******* Creating dispatcher *******`);
|
|
16
|
+
log(`[DISPATCHER-CREATE] - sessionId: ${sessionId}`);
|
|
17
|
+
log(`[DISPATCHER-CREATE] - taskId: ${taskId}`);
|
|
18
|
+
log(`[DISPATCHER-CREATE] - messageId: ${messageId}`);
|
|
19
|
+
log(`[DISPATCHER-CREATE] - isSteerFollower: ${isSteerFollower ?? false}`);
|
|
20
|
+
// 初始taskId和messageId(作为fallback)
|
|
21
|
+
const initialTaskId = taskId;
|
|
22
|
+
const initialMessageId = messageId;
|
|
23
|
+
/**
|
|
24
|
+
* 🔑 核心改造:动态获取当前活跃的taskId和messageId
|
|
25
|
+
* 每次需要taskId时,都从TaskManager获取最新值
|
|
26
|
+
*/
|
|
27
|
+
const getActiveTaskId = () => {
|
|
28
|
+
return getCurrentTaskId(sessionId) ?? initialTaskId;
|
|
29
|
+
};
|
|
30
|
+
const getActiveMessageId = () => {
|
|
31
|
+
return getCurrentMessageId(sessionId) ?? initialMessageId;
|
|
32
|
+
};
|
|
20
33
|
const core = getXYRuntime();
|
|
21
|
-
// Resolve configuration
|
|
22
34
|
const config = resolveXYConfig(cfg);
|
|
23
|
-
// Create reply prefix context (for model selection, etc.)
|
|
24
35
|
const prefixContext = createReplyPrefixContext({ cfg, agentId: accountId });
|
|
25
|
-
// Status update interval (every 60 seconds)
|
|
26
36
|
let statusUpdateInterval = null;
|
|
27
|
-
// Track if we've sent any response
|
|
28
37
|
let hasSentResponse = false;
|
|
29
|
-
// Track if we've sent the final empty message
|
|
30
38
|
let finalSent = false;
|
|
31
|
-
// Accumulate all text from deliver calls
|
|
32
39
|
let accumulatedText = "";
|
|
33
40
|
/**
|
|
34
41
|
* Start the status update interval
|
|
35
|
-
* Call this immediately after creating the dispatcher
|
|
36
42
|
*/
|
|
37
43
|
const startStatusInterval = () => {
|
|
38
|
-
log(`[STATUS INTERVAL] Starting interval for session ${sessionId}
|
|
44
|
+
log(`[STATUS INTERVAL] Starting interval for session ${sessionId}`);
|
|
39
45
|
statusUpdateInterval = setInterval(() => {
|
|
40
|
-
|
|
46
|
+
// 🔑 使用动态taskId
|
|
47
|
+
const currentTaskId = getActiveTaskId();
|
|
48
|
+
const currentMessageId = getActiveMessageId();
|
|
49
|
+
log(`[STATUS INTERVAL] Triggering status update`);
|
|
50
|
+
log(`[STATUS INTERVAL] - sessionId: ${sessionId}`);
|
|
51
|
+
log(`[STATUS INTERVAL] - currentTaskId: ${currentTaskId}`);
|
|
41
52
|
void sendStatusUpdate({
|
|
42
53
|
config,
|
|
43
54
|
sessionId,
|
|
44
|
-
taskId,
|
|
45
|
-
messageId,
|
|
55
|
+
taskId: currentTaskId, // 🔑 动态taskId
|
|
56
|
+
messageId: currentMessageId, // 🔑 动态messageId
|
|
46
57
|
text: "任务正在处理中,请稍后~",
|
|
47
58
|
state: "working",
|
|
48
59
|
}).catch((err) => {
|
|
@@ -50,15 +61,11 @@ export function createXYReplyDispatcher(params) {
|
|
|
50
61
|
});
|
|
51
62
|
}, 30000); // 30 seconds
|
|
52
63
|
};
|
|
53
|
-
/**
|
|
54
|
-
* Stop the status update interval
|
|
55
|
-
*/
|
|
56
64
|
const stopStatusInterval = () => {
|
|
57
65
|
if (statusUpdateInterval) {
|
|
58
|
-
log(`[STATUS INTERVAL] Stopping interval for session ${sessionId}
|
|
66
|
+
log(`[STATUS INTERVAL] Stopping interval for session ${sessionId}`);
|
|
59
67
|
clearInterval(statusUpdateInterval);
|
|
60
68
|
statusUpdateInterval = null;
|
|
61
|
-
log(`[STATUS INTERVAL] Stopped interval for session ${sessionId}, taskId=${taskId}`);
|
|
62
69
|
}
|
|
63
70
|
};
|
|
64
71
|
const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
|
|
@@ -66,33 +73,28 @@ export function createXYReplyDispatcher(params) {
|
|
|
66
73
|
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
|
67
74
|
humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, accountId),
|
|
68
75
|
onReplyStart: () => {
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
const currentTaskId = getActiveTaskId();
|
|
77
|
+
log(`[REPLY START] Reply started for session ${sessionId}, taskId=${currentTaskId}, isSteerFollower=${isSteerFollower}`);
|
|
71
78
|
},
|
|
72
79
|
deliver: async (payload, info) => {
|
|
73
80
|
const text = payload.text ?? "";
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
log(`[DELIVER]
|
|
77
|
-
if (payload.mediaUrls) {
|
|
78
|
-
log(`[DELIVER] mediaUrls: ${payload.mediaUrls.length} files`);
|
|
79
|
-
}
|
|
81
|
+
const currentTaskId = getActiveTaskId();
|
|
82
|
+
const currentMessageId = getActiveMessageId();
|
|
83
|
+
log(`[DELIVER] sessionId=${sessionId}, taskId=${currentTaskId}, info.kind=${info?.kind}, text.length=${text.length}`);
|
|
80
84
|
try {
|
|
81
|
-
// Skip empty messages
|
|
82
85
|
if (!text.trim()) {
|
|
83
86
|
log(`[DELIVER SKIP] Empty text, skipping`);
|
|
84
87
|
return;
|
|
85
88
|
}
|
|
86
|
-
// Accumulate text instead of sending immediately
|
|
87
89
|
accumulatedText += text;
|
|
88
90
|
hasSentResponse = true;
|
|
89
91
|
log(`[DELIVER ACCUMULATE] Accumulated text, current length=${accumulatedText.length}`);
|
|
90
|
-
//
|
|
92
|
+
// 🔑 使用动态taskId发送reasoningText更新
|
|
91
93
|
await sendReasoningTextUpdate({
|
|
92
94
|
config,
|
|
93
95
|
sessionId,
|
|
94
|
-
taskId,
|
|
95
|
-
messageId,
|
|
96
|
+
taskId: currentTaskId,
|
|
97
|
+
messageId: currentMessageId,
|
|
96
98
|
text,
|
|
97
99
|
});
|
|
98
100
|
log(`[DELIVER] ✅ Sent deliver text as reasoningText update`);
|
|
@@ -103,16 +105,21 @@ export function createXYReplyDispatcher(params) {
|
|
|
103
105
|
},
|
|
104
106
|
onError: async (err, info) => {
|
|
105
107
|
runtime.error?.(`xy: ${info.kind} reply failed: ${String(err)}`);
|
|
106
|
-
// Stop status updates
|
|
107
108
|
stopStatusInterval();
|
|
108
|
-
//
|
|
109
|
+
// 🔑 steer follower不发送错误状态(让主dispatcher处理)
|
|
110
|
+
if (isSteerFollower) {
|
|
111
|
+
log(`[ON_ERROR] Steer follower - skipping error response`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
109
114
|
if (!hasSentResponse) {
|
|
115
|
+
const currentTaskId = getActiveTaskId();
|
|
116
|
+
const currentMessageId = getActiveMessageId();
|
|
110
117
|
try {
|
|
111
118
|
await sendStatusUpdate({
|
|
112
119
|
config,
|
|
113
120
|
sessionId,
|
|
114
|
-
taskId,
|
|
115
|
-
messageId,
|
|
121
|
+
taskId: currentTaskId,
|
|
122
|
+
messageId: currentMessageId,
|
|
116
123
|
text: "处理失败,请稍后重试",
|
|
117
124
|
state: "failed",
|
|
118
125
|
});
|
|
@@ -123,46 +130,61 @@ export function createXYReplyDispatcher(params) {
|
|
|
123
130
|
}
|
|
124
131
|
},
|
|
125
132
|
onIdle: async () => {
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
const currentTaskId = getActiveTaskId();
|
|
134
|
+
const currentMessageId = getActiveMessageId();
|
|
135
|
+
log(`[ON_IDLE] Reply idle`);
|
|
136
|
+
log(`[ON_IDLE] - sessionId: ${sessionId}`);
|
|
137
|
+
log(`[ON_IDLE] - taskId: ${currentTaskId}`);
|
|
138
|
+
log(`[ON_IDLE] - isSteerFollower: ${isSteerFollower}`);
|
|
139
|
+
log(`[ON_IDLE] - hasSentResponse: ${hasSentResponse}`);
|
|
140
|
+
log(`[ON_IDLE] - finalSent: ${finalSent}`);
|
|
141
|
+
// 🔑 核心改动:steer follower不发送final响应
|
|
142
|
+
if (isSteerFollower) {
|
|
143
|
+
log(`[ON_IDLE] Steer follower - skipping final response`);
|
|
144
|
+
log(`[ON_IDLE] - Message queued successfully, waiting for primary dispatcher`);
|
|
145
|
+
stopStatusInterval();
|
|
146
|
+
return; // ← 直接返回,不发送任何东西!
|
|
147
|
+
}
|
|
148
|
+
// 正常模式(或steer的第一条消息)
|
|
128
149
|
if (hasSentResponse && !finalSent) {
|
|
129
150
|
log(`[ON_IDLE] Sending accumulated text, length=${accumulatedText.length}`);
|
|
130
151
|
try {
|
|
131
|
-
//
|
|
152
|
+
// 🔑 使用动态taskId发送完成状态
|
|
132
153
|
await sendStatusUpdate({
|
|
133
154
|
config,
|
|
134
155
|
sessionId,
|
|
135
|
-
taskId,
|
|
136
|
-
messageId,
|
|
156
|
+
taskId: currentTaskId,
|
|
157
|
+
messageId: currentMessageId,
|
|
137
158
|
text: "任务处理已完成~",
|
|
138
159
|
state: "completed",
|
|
139
160
|
});
|
|
140
161
|
log(`[ON_IDLE] ✅ Sent completion status update`);
|
|
162
|
+
// 🔑 使用动态taskId发送最终响应
|
|
141
163
|
await sendA2AResponse({
|
|
142
164
|
config,
|
|
143
165
|
sessionId,
|
|
144
|
-
taskId,
|
|
145
|
-
messageId,
|
|
166
|
+
taskId: currentTaskId,
|
|
167
|
+
messageId: currentMessageId,
|
|
146
168
|
text: accumulatedText,
|
|
147
169
|
append: false,
|
|
148
170
|
final: true,
|
|
149
171
|
});
|
|
150
172
|
finalSent = true;
|
|
151
|
-
log(`[ON_IDLE] Sent
|
|
173
|
+
log(`[ON_IDLE] ✅ Sent final response with taskId=${currentTaskId}`);
|
|
152
174
|
}
|
|
153
175
|
catch (err) {
|
|
154
|
-
error(`[ON_IDLE] Failed to send
|
|
176
|
+
error(`[ON_IDLE] Failed to send final response:`, err);
|
|
155
177
|
}
|
|
156
178
|
}
|
|
157
179
|
else {
|
|
180
|
+
// 正常失败场景(非steer follower)
|
|
158
181
|
log(`[ON_IDLE] Skipping final message: hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
|
|
159
|
-
// Task was interrupted - send failure status and error response
|
|
160
182
|
try {
|
|
161
183
|
await sendStatusUpdate({
|
|
162
184
|
config,
|
|
163
185
|
sessionId,
|
|
164
|
-
taskId,
|
|
165
|
-
messageId,
|
|
186
|
+
taskId: currentTaskId,
|
|
187
|
+
messageId: currentMessageId,
|
|
166
188
|
text: "任务处理中断了~",
|
|
167
189
|
state: "failed",
|
|
168
190
|
});
|
|
@@ -170,8 +192,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
170
192
|
await sendA2AResponse({
|
|
171
193
|
config,
|
|
172
194
|
sessionId,
|
|
173
|
-
taskId,
|
|
174
|
-
messageId,
|
|
195
|
+
taskId: currentTaskId,
|
|
196
|
+
messageId: currentMessageId,
|
|
175
197
|
text: "任务执行异常,请重试~",
|
|
176
198
|
append: false,
|
|
177
199
|
final: true,
|
|
@@ -180,14 +202,14 @@ export function createXYReplyDispatcher(params) {
|
|
|
180
202
|
log(`[ON_IDLE] ✅ Sent error response`);
|
|
181
203
|
}
|
|
182
204
|
catch (err) {
|
|
183
|
-
error(`[ON_IDLE] Failed to send
|
|
205
|
+
error(`[ON_IDLE] Failed to send error response:`, err);
|
|
184
206
|
}
|
|
185
207
|
}
|
|
186
|
-
// Stop status updates
|
|
187
208
|
stopStatusInterval();
|
|
188
209
|
},
|
|
189
210
|
onCleanup: () => {
|
|
190
|
-
|
|
211
|
+
const currentTaskId = getActiveTaskId();
|
|
212
|
+
log(`[ON_CLEANUP] Reply cleanup, taskId=${currentTaskId}, isSteerFollower=${isSteerFollower}`);
|
|
191
213
|
},
|
|
192
214
|
});
|
|
193
215
|
return {
|
|
@@ -195,17 +217,22 @@ export function createXYReplyDispatcher(params) {
|
|
|
195
217
|
replyOptions: {
|
|
196
218
|
...replyOptions,
|
|
197
219
|
onModelSelected: prefixContext.onModelSelected,
|
|
198
|
-
// 🔧 Tool execution start callback
|
|
199
220
|
onToolStart: async ({ name, phase }) => {
|
|
200
|
-
|
|
221
|
+
// 🔑 steer follower不发送tool状态(让主dispatcher处理)
|
|
222
|
+
if (isSteerFollower) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const currentTaskId = getActiveTaskId();
|
|
226
|
+
const currentMessageId = getActiveMessageId();
|
|
227
|
+
log(`[TOOL START] Tool: ${name}, phase: ${phase}, taskId: ${currentTaskId}`);
|
|
201
228
|
if (phase === "start") {
|
|
202
229
|
const toolName = name || "unknown";
|
|
203
230
|
try {
|
|
204
231
|
await sendStatusUpdate({
|
|
205
232
|
config,
|
|
206
233
|
sessionId,
|
|
207
|
-
taskId,
|
|
208
|
-
messageId,
|
|
234
|
+
taskId: currentTaskId,
|
|
235
|
+
messageId: currentMessageId,
|
|
209
236
|
text: `正在使用工具: ${toolName}...`,
|
|
210
237
|
state: "working",
|
|
211
238
|
});
|
|
@@ -216,25 +243,24 @@ export function createXYReplyDispatcher(params) {
|
|
|
216
243
|
}
|
|
217
244
|
}
|
|
218
245
|
},
|
|
219
|
-
// 🔧 Tool execution result callback
|
|
220
246
|
onToolResult: async (payload) => {
|
|
247
|
+
// 🔑 steer follower不发送tool结果(让主dispatcher处理)
|
|
248
|
+
if (isSteerFollower) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const currentTaskId = getActiveTaskId();
|
|
252
|
+
const currentMessageId = getActiveMessageId();
|
|
221
253
|
const text = payload.text ?? "";
|
|
222
254
|
const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
|
|
223
|
-
log(`[TOOL RESULT]
|
|
224
|
-
log(`[TOOL RESULT] - text.length=${text.length}`);
|
|
225
|
-
log(`[TOOL RESULT] - hasMedia=${hasMedia}`);
|
|
226
|
-
log(`[TOOL RESULT] - isError=${payload.isError}`);
|
|
227
|
-
if (text.length > 0) {
|
|
228
|
-
log(`[TOOL RESULT] - text preview: "${text.slice(0, 200)}"`);
|
|
229
|
-
}
|
|
255
|
+
log(`[TOOL RESULT] Tool result, taskId: ${currentTaskId}, text.length: ${text.length}`);
|
|
230
256
|
try {
|
|
231
257
|
if (text.length > 0 || hasMedia) {
|
|
232
258
|
const resultText = text.length > 0 ? text : "工具执行完成";
|
|
233
259
|
await sendStatusUpdate({
|
|
234
260
|
config,
|
|
235
261
|
sessionId,
|
|
236
|
-
taskId,
|
|
237
|
-
messageId,
|
|
262
|
+
taskId: currentTaskId,
|
|
263
|
+
messageId: currentMessageId,
|
|
238
264
|
text: resultText,
|
|
239
265
|
state: "working",
|
|
240
266
|
});
|
|
@@ -245,54 +271,43 @@ export function createXYReplyDispatcher(params) {
|
|
|
245
271
|
error(`[TOOL RESULT] ❌ Failed to send tool result status:`, err);
|
|
246
272
|
}
|
|
247
273
|
},
|
|
248
|
-
// 🧠 Reasoning/thinking process streaming callback
|
|
249
274
|
onReasoningStream: async (payload) => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (text.length > 0) {
|
|
254
|
-
log(`[REASONING STREAM] - text preview: "${text.slice(0, 200)}"`);
|
|
275
|
+
// 🔑 steer follower不发送reasoning stream
|
|
276
|
+
if (isSteerFollower) {
|
|
277
|
+
return;
|
|
255
278
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
// sessionId,
|
|
261
|
-
// taskId,
|
|
262
|
-
// messageId,
|
|
263
|
-
// text,
|
|
264
|
-
// });
|
|
265
|
-
// log(`[REASONING STREAM] ✅ Sent reasoning chunk as reasoningText update`);
|
|
266
|
-
// }
|
|
267
|
-
// } catch (err) {
|
|
268
|
-
// error(`[REASONING STREAM] ❌ Failed to send reasoning chunk reasoningText:`, err);
|
|
269
|
-
// }
|
|
279
|
+
const text = payload.text ?? "";
|
|
280
|
+
log(`[REASONING STREAM] Reasoning chunk received, text.length: ${text.length}`);
|
|
281
|
+
// Reasoning stream 目前被注释掉
|
|
282
|
+
// 如果需要可以启用
|
|
270
283
|
},
|
|
271
|
-
// 📝 Partial reply streaming callback (real-time preview)
|
|
272
284
|
onPartialReply: async (payload) => {
|
|
285
|
+
// 🔑 steer follower不发送partial reply(让主dispatcher处理)
|
|
286
|
+
if (isSteerFollower) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const currentTaskId = getActiveTaskId();
|
|
290
|
+
const currentMessageId = getActiveMessageId();
|
|
273
291
|
const text = payload.text ?? "";
|
|
274
|
-
const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
|
|
275
|
-
log(`[PARTIAL REPLY] 📝 Partial reply chunk received: session=${sessionId}, taskId=${taskId}`);
|
|
276
292
|
try {
|
|
277
293
|
if (text.length > 0) {
|
|
278
294
|
await sendReasoningTextUpdate({
|
|
279
295
|
config,
|
|
280
296
|
sessionId,
|
|
281
|
-
taskId,
|
|
282
|
-
messageId,
|
|
297
|
+
taskId: currentTaskId,
|
|
298
|
+
messageId: currentMessageId,
|
|
283
299
|
text,
|
|
284
300
|
append: false,
|
|
285
301
|
});
|
|
286
|
-
log(`[PARTIAL REPLY] ✅ Sent partial reply as reasoningText update (append=false)`);
|
|
287
302
|
}
|
|
288
303
|
}
|
|
289
304
|
catch (err) {
|
|
290
|
-
error(`[PARTIAL REPLY] ❌ Failed to send partial reply
|
|
305
|
+
error(`[PARTIAL REPLY] ❌ Failed to send partial reply:`, err);
|
|
291
306
|
}
|
|
292
307
|
},
|
|
293
308
|
},
|
|
294
309
|
markDispatchIdle,
|
|
295
|
-
startStatusInterval,
|
|
296
|
-
stopStatusInterval,
|
|
310
|
+
startStatusInterval,
|
|
311
|
+
stopStatusInterval,
|
|
297
312
|
};
|
|
298
313
|
}
|