@ynhcj/xiaoyi-channel 0.0.196-beta → 0.0.197-beta
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 +1 -141
- package/dist/src/bot.js +1 -12
- package/dist/src/cron-command.d.ts +0 -2
- package/dist/src/cron-command.js +8 -14
- package/dist/src/cron-query-handler.js +0 -36
- package/dist/src/formatter.js +5 -51
- package/dist/src/parser.d.ts +0 -12
- package/dist/src/parser.js +13 -39
- package/dist/src/provider.js +3 -30
- package/dist/src/reply-dispatcher.js +11 -26
- package/dist/src/tools/create-all-tools.js +5 -10
- package/dist/src/tools/send-cross-device-task-tool.js +15 -84
- package/dist/src/tools/send-file-to-user-tool.js +11 -9
- package/dist/src/tools/session-manager.d.ts +2 -12
- package/dist/src/tools/session-manager.js +17 -56
- package/dist/src/types.d.ts +6 -6
- package/dist/src/utils/config-manager.d.ts +2 -3
- package/dist/src/utils/config-manager.js +2 -22
- package/dist/src/websocket.js +13 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,10 +3,7 @@ import { xiaoyiProvider } from "./src/provider.js";
|
|
|
3
3
|
import { xyPlugin } from "./src/channel.js";
|
|
4
4
|
import registerSentinelHook from "./src/cspl/sentinel_hook.js";
|
|
5
5
|
import { setXYRuntime } from "./src/runtime.js";
|
|
6
|
-
import { markCronToolCall, clearCronToolCall
|
|
7
|
-
import { configManager } from "./src/utils/config-manager.js";
|
|
8
|
-
import { setJobPushId } from "./src/utils/cron-push-map.js";
|
|
9
|
-
import { getAllPushIds } from "./src/utils/pushid-manager.js";
|
|
6
|
+
import { markCronToolCall, clearCronToolCall } from "./src/tools/session-manager.js";
|
|
10
7
|
import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
|
|
11
8
|
import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
|
|
12
9
|
import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
|
|
@@ -28,145 +25,8 @@ function registerCronDetectionHook(api) {
|
|
|
28
25
|
if (event.toolCallId) {
|
|
29
26
|
clearCronToolCall(event.toolCallId);
|
|
30
27
|
}
|
|
31
|
-
// 捕获对话创建的 cron job:agent 调 cron(add) 后,从 result 拿 jobId,
|
|
32
|
-
// 配合当前会话的 pushId,写入 jobId↔pushId 映射,供 fire 时反查设备。
|
|
33
|
-
await captureCronAddMapping(event, ctx).catch((err) => {
|
|
34
|
-
// 捕获失败不影响工具结果
|
|
35
|
-
console.error("[xy] captureCronAddMapping failed:", err);
|
|
36
|
-
});
|
|
37
28
|
});
|
|
38
29
|
}
|
|
39
|
-
/** 从 cron add 工具结果中提取 jobId 并写入 pushId 映射。 */
|
|
40
|
-
async function captureCronAddMapping(event, ctx) {
|
|
41
|
-
// 两条创建路径都要捕获:
|
|
42
|
-
// 1) cron agent 工具:toolName==="cron", params.action==="add"
|
|
43
|
-
// 2) exec 跑 CLI:toolName==="exec", params.command 含 "cron add"
|
|
44
|
-
// (agent 实际用的是这条:openclaw cron add --name ... --cron ... --message ...)
|
|
45
|
-
const isCronAddTool = event.toolName === "cron" &&
|
|
46
|
-
(event.params?.action === "add" || event.params?.action === "create");
|
|
47
|
-
const isExecCronAdd = event.toolName === "exec" && isExecCronAddCommand(event.params?.command);
|
|
48
|
-
if (!isCronAddTool && !isExecCronAdd)
|
|
49
|
-
return;
|
|
50
|
-
console.log(`[CRONMAP] after_tool_call path=${event.toolName}, resultType=${typeof event.result}`);
|
|
51
|
-
const jobId = readJobIdFromResult(event.result);
|
|
52
|
-
if (!jobId) {
|
|
53
|
-
console.log(`[CRONMAP] skip: could not extract jobId. preview=${preview(event.result)}`);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
console.log(`[CRONMAP] extracted jobId=${jobId}`);
|
|
57
|
-
const sessionCtx = ctx.sessionKey ? getSessionContext(ctx.sessionKey) : null;
|
|
58
|
-
const sessionId = sessionCtx?.sessionId;
|
|
59
|
-
if (!sessionId) {
|
|
60
|
-
console.log(`[CRONMAP] skip: no sessionId (sessionKey=${ctx.sessionKey}, ctxFound=${!!sessionCtx})`);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const pushId = await resolvePushId(sessionId);
|
|
64
|
-
if (!pushId) {
|
|
65
|
-
console.log(`[CRONMAP] skip: no pushId available for sessionId=${sessionId} (no session match, no global, no file)`);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
console.log(`[CRONMAP] writing map: jobId=${jobId}, sessionId=${sessionId}, pushId=${pushId.substring(0, 16)}...`);
|
|
69
|
-
await setJobPushId(jobId, {
|
|
70
|
-
pushId,
|
|
71
|
-
sessionId,
|
|
72
|
-
deviceType: sessionCtx?.deviceType,
|
|
73
|
-
source: event.toolName === "exec" ? "exec-cli" : "conversation",
|
|
74
|
-
});
|
|
75
|
-
console.log(`[CRONMAP] map written OK`);
|
|
76
|
-
}
|
|
77
|
-
/** 回退链取 pushId:当前会话 → 全局兜底 → 本地文件首个(保底)。 */
|
|
78
|
-
async function resolvePushId(sessionId) {
|
|
79
|
-
// 1. 同会话
|
|
80
|
-
const session = configManager.getPushId(sessionId);
|
|
81
|
-
if (session)
|
|
82
|
-
return session;
|
|
83
|
-
// 2. 全局(任何会话注册过的)
|
|
84
|
-
const global = configManager.getPushId();
|
|
85
|
-
if (global)
|
|
86
|
-
return global;
|
|
87
|
-
// 3. 文件兜底
|
|
88
|
-
try {
|
|
89
|
-
const all = await getAllPushIds();
|
|
90
|
-
if (all.length > 0)
|
|
91
|
-
return all[0];
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// ignore
|
|
95
|
-
}
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
/** 判断 exec 命令是否为 cron add(匹配 "openclaw cron add" 或裸 "cron add",排除 list/remove 等)。 */
|
|
99
|
-
function isExecCronAddCommand(command) {
|
|
100
|
-
if (typeof command !== "string")
|
|
101
|
-
return false;
|
|
102
|
-
return /\bcron\s+add\b/.test(command);
|
|
103
|
-
}
|
|
104
|
-
/** 取结果的短预览,用于诊断。 */
|
|
105
|
-
function preview(value) {
|
|
106
|
-
if (value == null)
|
|
107
|
-
return String(value);
|
|
108
|
-
const s = typeof value === "string" ? value : JSON.stringify(value);
|
|
109
|
-
return s.length > 200 ? s.slice(0, 200) + "…" : s;
|
|
110
|
-
}
|
|
111
|
-
/** 防御性地从 cron add 结果中取 job id。
|
|
112
|
-
* 覆盖:裸 job 对象、JSON 字符串、exec 输出文本、
|
|
113
|
-
* {content:[{text}]} / {stdout} / data/result/job 嵌套。 */
|
|
114
|
-
function readJobIdFromResult(result) {
|
|
115
|
-
if (!result)
|
|
116
|
-
return undefined;
|
|
117
|
-
// {content: [{type:"text", text: "..."}]} — exec 工具的输出信封
|
|
118
|
-
if (result && typeof result === "object") {
|
|
119
|
-
const contentArr = result.content;
|
|
120
|
-
if (Array.isArray(contentArr)) {
|
|
121
|
-
for (const item of contentArr) {
|
|
122
|
-
if (item && typeof item === "object") {
|
|
123
|
-
const text = item.text;
|
|
124
|
-
if (typeof text === "string" && text.trim()) {
|
|
125
|
-
const fromContent = readJobIdFromResult(text);
|
|
126
|
-
if (fromContent)
|
|
127
|
-
return fromContent;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// {stdout} — 备选 exec 输出信封
|
|
134
|
-
if (result && typeof result === "object") {
|
|
135
|
-
const stdout = result.stdout;
|
|
136
|
-
if (typeof stdout === "string" && stdout.trim()) {
|
|
137
|
-
const fromStdout = readJobIdFromResult(stdout);
|
|
138
|
-
if (fromStdout)
|
|
139
|
-
return fromStdout;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
let obj = result;
|
|
143
|
-
if (typeof result === "string") {
|
|
144
|
-
try {
|
|
145
|
-
obj = JSON.parse(result);
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
// 纯文本:可能含 stderr 前缀行 + JSON。用正则抓 "id":"..."。
|
|
149
|
-
const m = result.match(/"id"\s*:\s*"([^"]+)"/);
|
|
150
|
-
if (m)
|
|
151
|
-
return m[1];
|
|
152
|
-
return undefined;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (obj && typeof obj === "object") {
|
|
156
|
-
const id = obj.id;
|
|
157
|
-
if (typeof id === "string" && id.trim())
|
|
158
|
-
return id.trim();
|
|
159
|
-
for (const k of ["data", "result", "job"]) {
|
|
160
|
-
const inner = obj[k];
|
|
161
|
-
if (inner && typeof inner === "object") {
|
|
162
|
-
const innerId = inner.id;
|
|
163
|
-
if (typeof innerId === "string" && innerId.trim())
|
|
164
|
-
return innerId.trim();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return undefined;
|
|
169
|
-
}
|
|
170
30
|
function registerFullHooks(api) {
|
|
171
31
|
// SKILL RETRIEVER HOOK: before_prompt_build hook
|
|
172
32
|
const pluginConfig = api.pluginConfig || {};
|
package/dist/src/bot.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { updateSessionStoreEntry, updateSessionStore, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
2
2
|
import { getXYRuntime } from "./runtime.js";
|
|
3
3
|
import { createXYReplyDispatcher } from "./reply-dispatcher.js";
|
|
4
|
-
import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId, extractDeviceType,
|
|
4
|
+
import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId, extractDeviceType, extractModelName, extractTriggerData, extractRunCrossTaskContext } from "./parser.js";
|
|
5
5
|
import { downloadFilesFromParts } from "./file-download.js";
|
|
6
6
|
import { resolveXYConfig } from "./config.js";
|
|
7
7
|
import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
|
|
@@ -139,15 +139,6 @@ export async function handleXYMessage(params) {
|
|
|
139
139
|
if (deviceType) {
|
|
140
140
|
log.log(`[BOT] Extracted deviceType: ${deviceType}`);
|
|
141
141
|
}
|
|
142
|
-
// Extract app_ver and display_version if present
|
|
143
|
-
const appVer = extractAppVer(parsed.parts);
|
|
144
|
-
if (appVer) {
|
|
145
|
-
log.log(`[BOT] Extracted app_ver: ${appVer}`);
|
|
146
|
-
}
|
|
147
|
-
const displayVersion = extractDisplayVersion(parsed.parts);
|
|
148
|
-
if (displayVersion) {
|
|
149
|
-
log.log(`[BOT] Extracted display_version: ${displayVersion}`);
|
|
150
|
-
}
|
|
151
142
|
// Extract modelName if present (used by provider.ts to override model.id)
|
|
152
143
|
const modelName = extractModelName(parsed.parts);
|
|
153
144
|
if (modelName) {
|
|
@@ -179,8 +170,6 @@ export async function handleXYMessage(params) {
|
|
|
179
170
|
messageId: parsed.messageId,
|
|
180
171
|
agentId: route.accountId,
|
|
181
172
|
deviceType,
|
|
182
|
-
appVer: appVer ?? undefined,
|
|
183
|
-
displayVersion: displayVersion ?? undefined,
|
|
184
173
|
modelName,
|
|
185
174
|
runCrossTaskContext: runCrossTaskContext ?? undefined,
|
|
186
175
|
});
|
|
@@ -2,8 +2,6 @@ import type { XYChannelConfig, A2ACommand } from "./types.js";
|
|
|
2
2
|
export interface SendCommandViaPushParams {
|
|
3
3
|
config: XYChannelConfig;
|
|
4
4
|
command: A2ACommand;
|
|
5
|
-
/** 指定设备的 pushId(多设备路由)。未传时回退到 getAllPushIds()[0]。 */
|
|
6
|
-
pushId?: string;
|
|
7
5
|
}
|
|
8
6
|
/**
|
|
9
7
|
* Send a tool command through the push channel (for cron-triggered tool calls).
|
package/dist/src/cron-command.js
CHANGED
|
@@ -24,23 +24,17 @@ export async function sendCommandViaPush(params) {
|
|
|
24
24
|
command.header?.name ??
|
|
25
25
|
"Command";
|
|
26
26
|
logger.log(`[CRON-CMD] Sending command via push, intent=${intentName}`);
|
|
27
|
-
// 1.
|
|
28
|
-
// 未提供时回退到 getAllPushIds()[0](单设备兼容旧行为)。
|
|
27
|
+
// 1. Load push IDs, use first one
|
|
29
28
|
let pushId = config.pushId;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const pushIdList = await getAllPushIds();
|
|
36
|
-
if (pushIdList.length > 0) {
|
|
37
|
-
pushId = pushIdList[0];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
logger.error("[CRON-CMD] Failed to load pushIds:", error);
|
|
29
|
+
try {
|
|
30
|
+
const pushIdList = await getAllPushIds();
|
|
31
|
+
if (pushIdList.length > 0) {
|
|
32
|
+
pushId = pushIdList[0];
|
|
42
33
|
}
|
|
43
34
|
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.error("[CRON-CMD] Failed to load pushIds:", error);
|
|
37
|
+
}
|
|
44
38
|
// 2. Build and send push notification with command in directives
|
|
45
39
|
const pushService = new XYPushService(config);
|
|
46
40
|
const sessionId = randomUUID();
|
|
@@ -7,8 +7,6 @@ import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
|
7
7
|
import * as os from "os";
|
|
8
8
|
import { sendCommand } from "./formatter.js";
|
|
9
9
|
import { resolveXYConfig } from "./config.js";
|
|
10
|
-
import { configManager } from "./utils/config-manager.js";
|
|
11
|
-
import { setJobPushId } from "./utils/cron-push-map.js";
|
|
12
10
|
import { logger } from "./utils/logger.js";
|
|
13
11
|
import { readFileSync, readdirSync } from "fs";
|
|
14
12
|
import { join } from "path";
|
|
@@ -41,11 +39,6 @@ export async function handleCronQueryEvent(context, cfg) {
|
|
|
41
39
|
break;
|
|
42
40
|
case "add":
|
|
43
41
|
result = await callGatewayTool("cron.add", { timeoutMs: GATEWAY_TIMEOUT_MS }, params ?? {});
|
|
44
|
-
// 捕获 jobId↔pushId:cron-query 路径由 channel 自己建 job,
|
|
45
|
-
// 此处 context 握着 sessionId,configManager 有对应设备 pushId。
|
|
46
|
-
await persistCronPushMap(context.sessionId, result).catch((err) => {
|
|
47
|
-
logger.error(`[CRON-QUERY] Failed to persist cron-push-map:`, err);
|
|
48
|
-
});
|
|
49
42
|
break;
|
|
50
43
|
case "update":
|
|
51
44
|
result = await callGatewayTool("cron.update", { timeoutMs: GATEWAY_TIMEOUT_MS }, {
|
|
@@ -113,35 +106,6 @@ export async function handleCronQueryEvent(context, cfg) {
|
|
|
113
106
|
log.warn(`[CRON-QUERY] Missing cfg/sessionId/taskId/messageId, skipping sendCommand`);
|
|
114
107
|
}
|
|
115
108
|
}
|
|
116
|
-
/**
|
|
117
|
-
* 从 cron.add 结果中提取 jobId,配合 sessionId 对应的 pushId 写入映射。
|
|
118
|
-
*/
|
|
119
|
-
async function persistCronPushMap(sessionId, result) {
|
|
120
|
-
logger.log(`[CRONMAP] cron-query persist: sessionId=${sessionId ?? "(none)"}, resultType=${typeof result}`);
|
|
121
|
-
if (!sessionId) {
|
|
122
|
-
logger.log(`[CRONMAP] cron-query skip: no sessionId in context`);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
let jobId;
|
|
126
|
-
if (result && typeof result === "object") {
|
|
127
|
-
const id = result.id;
|
|
128
|
-
if (typeof id === "string" && id.trim())
|
|
129
|
-
jobId = id.trim();
|
|
130
|
-
}
|
|
131
|
-
if (!jobId) {
|
|
132
|
-
const preview = typeof result === "string" ? result.slice(0, 200) : JSON.stringify(result)?.slice(0, 200);
|
|
133
|
-
logger.log(`[CRONMAP] cron-query skip: no jobId in result. preview=${preview ?? "(empty)"}`);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const pushId = configManager.getPushId(sessionId);
|
|
137
|
-
if (!pushId) {
|
|
138
|
-
logger.log(`[CRONMAP] cron-query skip: configManager has no pushId for sessionId=${sessionId}`);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
logger.log(`[CRONMAP] cron-query writing map: jobId=${jobId}, pushId=${pushId.substring(0, 16)}...`);
|
|
142
|
-
await setJobPushId(jobId, { pushId, sessionId, source: "cron-query" });
|
|
143
|
-
logger.log(`[CRONMAP] cron-query map written OK`);
|
|
144
|
-
}
|
|
145
109
|
/**
|
|
146
110
|
* Read local cron folder directly (bypassing openclaw RPC) and return
|
|
147
111
|
* run records from the last 7 days, grouped by date and sorted by time.
|
package/dist/src/formatter.js
CHANGED
|
@@ -5,10 +5,7 @@ import { logger } from "./utils/logger.js";
|
|
|
5
5
|
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
6
6
|
import { redactSensitiveText, containsSensitiveInfo } from "./sensitive-redactor.js";
|
|
7
7
|
import { rewriteOutboundApprovalText } from "./approval-bridge.js";
|
|
8
|
-
import { isCronToolCall
|
|
9
|
-
import { configManager } from "./utils/config-manager.js";
|
|
10
|
-
import { getPushIdByJobId } from "./utils/cron-push-map.js";
|
|
11
|
-
import { getAllPushIds } from "./utils/pushid-manager.js";
|
|
8
|
+
import { isCronToolCall } from "./tools/session-manager.js";
|
|
12
9
|
// ─────────────────────────────────────────────────────────────
|
|
13
10
|
// 敏感信息脱敏辅助函数
|
|
14
11
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -204,45 +201,6 @@ export async function sendStatusUpdate(params) {
|
|
|
204
201
|
log.log(`[A2A_STATUS] Sending status-update, text="${redactedText}"`);
|
|
205
202
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
206
203
|
}
|
|
207
|
-
/**
|
|
208
|
-
* 解析 cron fire 时应使用的 pushId(多设备路由)。
|
|
209
|
-
*
|
|
210
|
-
* 查询链(逐级回退):
|
|
211
|
-
* 1. 合成 sessionId → jobId → cron-push-map.json → 创建时记录的设备 pushId
|
|
212
|
-
* 2. configManager 同进程的 sessionId→pushId(进程未重启时兜底)
|
|
213
|
-
* 3. getAllPushIds()[0](单设备兼容旧行为)
|
|
214
|
-
* 返回 undefined 表示走兜底(由 sendCommandViaPush 内部处理)。
|
|
215
|
-
*/
|
|
216
|
-
async function resolveCronPushId(sessionId, config) {
|
|
217
|
-
// 1. jobId → 持久化映射
|
|
218
|
-
const jobId = getCurrentCronJobId(sessionId);
|
|
219
|
-
if (jobId) {
|
|
220
|
-
const hit = await getPushIdByJobId(jobId);
|
|
221
|
-
if (hit?.pushId) {
|
|
222
|
-
logger.log(`[CRON-PUSH] Resolved pushId via map, jobId=${jobId}`);
|
|
223
|
-
return hit.pushId;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
// 2. 同进程 configManager 兜底
|
|
227
|
-
const sessionPushId = configManager.getPushId(sessionId);
|
|
228
|
-
if (sessionPushId) {
|
|
229
|
-
logger.log(`[CRON-PUSH] Resolved pushId via configManager (fallback)`);
|
|
230
|
-
return sessionPushId;
|
|
231
|
-
}
|
|
232
|
-
// 3. config.pushId / getAllPushIds()[0] 交给 sendCommandViaPush 内部处理
|
|
233
|
-
void config;
|
|
234
|
-
try {
|
|
235
|
-
const all = await getAllPushIds();
|
|
236
|
-
if (all.length > 0) {
|
|
237
|
-
logger.log(`[CRON-PUSH] Resolved pushId via getAllPushIds[0] (legacy fallback)`);
|
|
238
|
-
return all[0];
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
242
|
-
logger.error(`[CRON-PUSH] getAllPushIds failed:`, error);
|
|
243
|
-
}
|
|
244
|
-
return undefined;
|
|
245
|
-
}
|
|
246
204
|
/**
|
|
247
205
|
* Send a command as an artifact update (final=false).
|
|
248
206
|
*
|
|
@@ -259,15 +217,11 @@ export async function sendCommand(params) {
|
|
|
259
217
|
if (commands.length === 0) {
|
|
260
218
|
throw new Error("sendCommand requires command or commands.");
|
|
261
219
|
}
|
|
262
|
-
// ── Cron mode:
|
|
263
|
-
//
|
|
264
|
-
//
|
|
220
|
+
// ── Cron mode: disabled ────────────────────────────────────────
|
|
221
|
+
// sendCommandViaPush is disabled in this version. Cron-triggered
|
|
222
|
+
// tool calls that try to send commands will be rejected.
|
|
265
223
|
if (sessionId.startsWith("cron-") || isCronToolCall(toolCallId)) {
|
|
266
|
-
|
|
267
|
-
// 解析正确设备的 pushId:合成 sessionId → jobId → cron-push-map。
|
|
268
|
-
// provider.ts 在 isCron 分支已把 jobId 绑定到该 sessionId。
|
|
269
|
-
const pushId = await resolveCronPushId(sessionId, config);
|
|
270
|
-
return sendCommandViaPush({ config, command: commands[0], pushId });
|
|
224
|
+
throw new Error("sendCommandViaPush is disabled in this version");
|
|
271
225
|
}
|
|
272
226
|
// ── Normal mode: WebSocket ─────────────────────────────────────
|
|
273
227
|
// Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
|
package/dist/src/parser.d.ts
CHANGED
|
@@ -44,18 +44,6 @@ export declare function isTasksCancelMessage(method: string): boolean;
|
|
|
44
44
|
* Looks for push_id in data parts under variables.systemVariables.push_id
|
|
45
45
|
*/
|
|
46
46
|
export declare function extractPushId(parts: A2AMessagePart[]): string | null;
|
|
47
|
-
/**
|
|
48
|
-
* Extract app_ver from message parts.
|
|
49
|
-
* Looks for app_ver in data parts under variables.systemVariables.app_ver
|
|
50
|
-
* (same level as push_id).
|
|
51
|
-
*/
|
|
52
|
-
export declare function extractAppVer(parts: A2AMessagePart[]): string | null;
|
|
53
|
-
/**
|
|
54
|
-
* Extract display_version from message parts.
|
|
55
|
-
* Looks for display_version in data parts under variables.systemVariables.display_version
|
|
56
|
-
* (same level as push_id).
|
|
57
|
-
*/
|
|
58
|
-
export declare function extractDisplayVersion(parts: A2AMessagePart[]): string | null;
|
|
59
47
|
/**
|
|
60
48
|
* Extract deviceType from message parts.
|
|
61
49
|
* Looks for deviceType in data parts under variables.systemVariables.deviceType
|
package/dist/src/parser.js
CHANGED
|
@@ -56,16 +56,22 @@ export function extractRunCrossTaskContext(parts) {
|
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
58
|
const candidate = item;
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const fileLocalUrls = Array.isArray(candidate.fileLocalUrls)
|
|
60
|
+
? candidate.fileLocalUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
61
|
+
: [];
|
|
62
|
+
const fileRemoteUrls = Array.isArray(candidate.fileRemoteUrls)
|
|
63
|
+
? candidate.fileRemoteUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
64
|
+
: [];
|
|
65
|
+
const fileNames = Array.isArray(candidate.fileNames)
|
|
66
|
+
? candidate.fileNames.filter((name) => typeof name === "string" && name.length > 0)
|
|
67
|
+
: [];
|
|
68
|
+
if (fileLocalUrls.length === 0 && fileRemoteUrls.length === 0) {
|
|
63
69
|
return null;
|
|
64
70
|
}
|
|
65
71
|
return {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
...(
|
|
72
|
+
...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
|
|
73
|
+
...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
|
|
74
|
+
...(fileNames.length > 0 && fileNames.length === fileRemoteUrls.length ? { fileNames } : {}),
|
|
69
75
|
};
|
|
70
76
|
})
|
|
71
77
|
.filter((item) => item !== null);
|
|
@@ -121,38 +127,6 @@ export function extractPushId(parts) {
|
|
|
121
127
|
}
|
|
122
128
|
return null;
|
|
123
129
|
}
|
|
124
|
-
/**
|
|
125
|
-
* Extract app_ver from message parts.
|
|
126
|
-
* Looks for app_ver in data parts under variables.systemVariables.app_ver
|
|
127
|
-
* (same level as push_id).
|
|
128
|
-
*/
|
|
129
|
-
export function extractAppVer(parts) {
|
|
130
|
-
for (const part of parts) {
|
|
131
|
-
if (part.kind === "data" && part.data) {
|
|
132
|
-
const appVer = part.data.variables?.systemVariables?.app_ver;
|
|
133
|
-
if (appVer && typeof appVer === "string") {
|
|
134
|
-
return appVer;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Extract display_version from message parts.
|
|
142
|
-
* Looks for display_version in data parts under variables.systemVariables.display_version
|
|
143
|
-
* (same level as push_id).
|
|
144
|
-
*/
|
|
145
|
-
export function extractDisplayVersion(parts) {
|
|
146
|
-
for (const part of parts) {
|
|
147
|
-
if (part.kind === "data" && part.data) {
|
|
148
|
-
const displayVersion = part.data.variables?.systemVariables?.display_version;
|
|
149
|
-
if (displayVersion && typeof displayVersion === "string") {
|
|
150
|
-
return displayVersion;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
130
|
/**
|
|
157
131
|
* Extract deviceType from message parts.
|
|
158
132
|
* Looks for deviceType in data parts under variables.systemVariables.deviceType
|
package/dist/src/provider.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// models.providers.xiaoyiprovider.models = [...]
|
|
10
10
|
import { createHash } from "crypto";
|
|
11
11
|
import { logger } from "./utils/logger.js";
|
|
12
|
-
import { getCurrentSessionContext
|
|
12
|
+
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
13
13
|
import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
|
|
14
14
|
import { notifyModelStreaming } from "./bot.js";
|
|
15
15
|
// ── Retry config ──────────────────────────────────────────────
|
|
@@ -506,14 +506,6 @@ export const xiaoyiProvider = {
|
|
|
506
506
|
// 3. UID-based fallback: sha256(uid).hex[:32]_timestamp
|
|
507
507
|
const isCron = isCronTriggered(context.messages);
|
|
508
508
|
if (isCron) {
|
|
509
|
-
// fire 期 jobId 桥:把首条消息 `[cron:<jobId> ...]` 解析出的真实 jobId
|
|
510
|
-
// 绑定到本次 cron run 的合成 sessionId。sendCommand 凭同一 sessionId
|
|
511
|
-
// 反查 jobId → cron-push-map → 正确设备的 pushId(多设备路由)。
|
|
512
|
-
const cronJobId = extractCronUuid(context.messages);
|
|
513
|
-
const cronCtx = getCurrentSessionContext();
|
|
514
|
-
if (cronJobId && cronCtx?.sessionId) {
|
|
515
|
-
setCurrentCronJobId(cronCtx.sessionId, cronJobId);
|
|
516
|
-
}
|
|
517
509
|
const fallbackPrefix = ctx.extraParams?.[FALLBACK_PREFIX_KEY];
|
|
518
510
|
if (typeof fallbackPrefix === "string") {
|
|
519
511
|
const fallbackValue = `${fallbackPrefix}_${Date.now()}`;
|
|
@@ -564,15 +556,6 @@ export const xiaoyiProvider = {
|
|
|
564
556
|
// then ALS fallback.
|
|
565
557
|
const deviceType = extractedDeviceType
|
|
566
558
|
?? getCurrentSessionContext()?.deviceType;
|
|
567
|
-
// app_ver and display_version from session context (ALS)
|
|
568
|
-
const appVer = sessionCtx?.appVer;
|
|
569
|
-
const displayVersion = sessionCtx?.displayVersion;
|
|
570
|
-
if (appVer) {
|
|
571
|
-
logger.log(`[xiaoyiprovider] app_ver: ${appVer}`);
|
|
572
|
-
}
|
|
573
|
-
if (displayVersion) {
|
|
574
|
-
logger.log(`[xiaoyiprovider] display_version: ${displayVersion}`);
|
|
575
|
-
}
|
|
576
559
|
// 在发送给模型前,优化 systemPrompt 结构
|
|
577
560
|
if (context.systemPrompt) {
|
|
578
561
|
let sp = context.systemPrompt;
|
|
@@ -608,19 +591,9 @@ export const xiaoyiProvider = {
|
|
|
608
591
|
logger.log(`[selfEvolution] selfEvolution flag: ${selfEvolutionEnabled}`);
|
|
609
592
|
context.systemPrompt = applySelfEvolutionPrompt(context.systemPrompt, selfEvolutionEnabled);
|
|
610
593
|
// Append device context to systemPrompt
|
|
611
|
-
if (deviceType
|
|
594
|
+
if (deviceType) {
|
|
612
595
|
const displayDevice = (deviceType === "2in1") ? "鸿蒙PC" : deviceType;
|
|
613
|
-
|
|
614
|
-
if (deviceType) {
|
|
615
|
-
deviceSection += `The current user is using the following device: ${displayDevice}\n`;
|
|
616
|
-
}
|
|
617
|
-
if (appVer) {
|
|
618
|
-
deviceSection += `当前用户小艺APP版本是${appVer}\n`;
|
|
619
|
-
}
|
|
620
|
-
if (displayVersion) {
|
|
621
|
-
deviceSection += `当前用户系统Rom版本是${displayVersion}\n`;
|
|
622
|
-
}
|
|
623
|
-
deviceSection += `You need to be aware of the user's current device and provide guidance accordingly. If the response involves device-related tools or actions, you must tailor the reply based on the user's current device, using device-specific references such as "saved to the Notes/Calendar on your {deviceType}.\n"`;
|
|
596
|
+
const deviceSection = `\n\n## Current User Device Context\nThe current user is using the following device: ${displayDevice}\nYou need to be aware of the user's current device and provide guidance accordingly. If the response involves device-related tools or actions, you must tailor the reply based on the user's current device, using device-specific references such as "saved to the Notes/Calendar on your {deviceType}.\n"`;
|
|
624
597
|
context.systemPrompt = (context.systemPrompt ?? "") + deviceSection;
|
|
625
598
|
}
|
|
626
599
|
// ── Trim user message metadata ──────────────────────
|
|
@@ -39,23 +39,17 @@ 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;
|
|
43
42
|
const statusCommand = buildDistributionStatusCommand(context);
|
|
44
43
|
const resultCommand = buildCrossTaskExecuteResultCommand(resultCode, resultMessage, sentFiles);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
}
|
|
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}`);
|
|
59
53
|
}
|
|
60
54
|
/**
|
|
61
55
|
* 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
|
|
@@ -122,7 +116,6 @@ export function createXYReplyDispatcher(params) {
|
|
|
122
116
|
let hasSentResponse = false;
|
|
123
117
|
let finalSent = false;
|
|
124
118
|
let accumulatedText = "";
|
|
125
|
-
let finalReplyText = "";
|
|
126
119
|
const initialRunCrossTaskContext = getCurrentSessionContext()?.runCrossTaskContext;
|
|
127
120
|
const getRunCrossTaskContext = () => {
|
|
128
121
|
return getCurrentSessionContext()?.runCrossTaskContext ?? initialRunCrossTaskContext;
|
|
@@ -179,10 +172,6 @@ export function createXYReplyDispatcher(params) {
|
|
|
179
172
|
scopedLog().log(`[DELIVER SKIP] Empty text, skipping`);
|
|
180
173
|
return;
|
|
181
174
|
}
|
|
182
|
-
if (info?.kind === "final") {
|
|
183
|
-
finalReplyText = text;
|
|
184
|
-
scopedLog().log(`[DELIVER] Captured final reply text, length=${finalReplyText.length}`);
|
|
185
|
-
}
|
|
186
175
|
// 🔑 如果 onPartialReply 已经流式发送过文本,deliver 不再重复发送
|
|
187
176
|
if (hasSentResponse) {
|
|
188
177
|
scopedLog().log(`[DELIVER SKIP] Already sent via onPartialReply`);
|
|
@@ -243,11 +232,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
243
232
|
}
|
|
244
233
|
// 正常模式(或未被steer的dispatch)
|
|
245
234
|
if (hasSentResponse && !finalSent) {
|
|
246
|
-
|
|
247
|
-
const trimmedAccumulatedText = accumulatedText.trim();
|
|
248
|
-
const crossTaskResultMessage = trimmedFinalReplyText || trimmedAccumulatedText;
|
|
249
|
-
const crossTaskResultSource = trimmedFinalReplyText ? "final" : "accumulated";
|
|
250
|
-
scopedLog().log(`[ON-IDLE] [SendCrossResult]Sending cross-task result, source=${crossTaskResultSource}, resultMessage.length=${crossTaskResultMessage.length}`);
|
|
235
|
+
scopedLog().log(`[ON-IDLE] Sending accumulated text, length=${accumulatedText.length}`);
|
|
251
236
|
try {
|
|
252
237
|
const runCrossTaskContext = getRunCrossTaskContext();
|
|
253
238
|
if (runCrossTaskContext) {
|
|
@@ -258,7 +243,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
258
243
|
messageId: currentMessageId,
|
|
259
244
|
context: runCrossTaskContext,
|
|
260
245
|
resultCode: "0",
|
|
261
|
-
resultMessage:
|
|
246
|
+
resultMessage: accumulatedText,
|
|
262
247
|
});
|
|
263
248
|
}
|
|
264
249
|
// 🔑 使用动态taskId发送完成状态
|
|
@@ -16,11 +16,6 @@ import { createGetAlarmToolSchemaTool } from "./get-alarm-tool-schema.js";
|
|
|
16
16
|
import { createGetCollectionToolSchemaTool } from "./get-collection-tool-schema.js";
|
|
17
17
|
// import { createGetEmailToolSchemaTool } from "./get-email-tool-schema.js";
|
|
18
18
|
import { createLoginTokenTool } from "./login-token-tool.js";
|
|
19
|
-
import { createAgentAsSkillTool } from "./agent-as-skill-tool.js";
|
|
20
|
-
import { createDiscoverCrossDevicesTool } from "./discover-cross-devices-tool.js";
|
|
21
|
-
import { createSendCrossDeviceTaskTool } from "./send-cross-device-task-tool.js";
|
|
22
|
-
import { createDisplayA2UICardTool } from "./display-a2ui-card-tool.js";
|
|
23
|
-
import { createCheckPluginPrivilegeTool } from "./check-plugin-privilege-tool.js";
|
|
24
19
|
import { logger } from "../utils/logger.js";
|
|
25
20
|
/**
|
|
26
21
|
* Create all XY channel tools for the given session context.
|
|
@@ -36,9 +31,9 @@ export function createAllTools(ctx) {
|
|
|
36
31
|
logger.log(`[CREATE-ALL-TOOLS] creating tools`);
|
|
37
32
|
return [
|
|
38
33
|
createLocationTool(ctx),
|
|
39
|
-
createDiscoverCrossDevicesTool(ctx),
|
|
40
|
-
createSendCrossDeviceTaskTool(ctx),
|
|
41
|
-
createDisplayA2UICardTool(ctx),
|
|
34
|
+
// createDiscoverCrossDevicesTool(ctx),
|
|
35
|
+
// createSendCrossDeviceTaskTool(ctx),
|
|
36
|
+
// createDisplayA2UICardTool(ctx),
|
|
42
37
|
createCallDeviceTool(ctx),
|
|
43
38
|
createGetNoteToolSchemaTool(ctx),
|
|
44
39
|
createGetCalendarToolSchemaTool(ctx),
|
|
@@ -56,7 +51,7 @@ export function createAllTools(ctx) {
|
|
|
56
51
|
timestampToUtc8Tool,
|
|
57
52
|
createSaveSelfEvolutionSkillTool(ctx),
|
|
58
53
|
createLoginTokenTool(ctx),
|
|
59
|
-
createAgentAsSkillTool(ctx),
|
|
60
|
-
createCheckPluginPrivilegeTool(ctx),
|
|
54
|
+
// createAgentAsSkillTool(ctx),
|
|
55
|
+
// createCheckPluginPrivilegeTool(ctx),
|
|
61
56
|
];
|
|
62
57
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sendCommand, sendStatusUpdate } from "../formatter.js";
|
|
2
2
|
import { getXYWebSocketManager } from "../client.js";
|
|
3
3
|
import { getCurrentMessageId, getCurrentTaskId } from "../task-manager.js";
|
|
4
|
+
import { createSendFileToUserTool } from "./send-file-to-user-tool.js";
|
|
4
5
|
import { logger } from "../utils/logger.js";
|
|
5
6
|
const LOG_TAG = "[SendPcDeviceTask]";
|
|
6
7
|
const SEND_CROSS_RESULT_LOG_TAG = "[SendCrossResult]";
|
|
@@ -27,7 +28,7 @@ function buildModelToolResult(result) {
|
|
|
27
28
|
let message = `跨端任务执行结果:${baseMessage}`;
|
|
28
29
|
if (resultStatus === "对端设备执行任务成功且返回有文件") {
|
|
29
30
|
if (result.autoSendFileToUser?.success) {
|
|
30
|
-
message += "\n\n
|
|
31
|
+
message += "\n\n对端设备返回了文件,系统已自动通过 send_file_to_user 将文件卡片发送给用户。请你基于跨端任务结果生成最终回复,告知用户任务已完成且文件已发送。";
|
|
31
32
|
}
|
|
32
33
|
else {
|
|
33
34
|
const errorMessage = result.autoSendFileToUser?.error || "未知错误";
|
|
@@ -55,92 +56,22 @@ function buildCrossDeviceResult(params) {
|
|
|
55
56
|
};
|
|
56
57
|
return result;
|
|
57
58
|
}
|
|
58
|
-
function collectSentFileCards(sentFiles) {
|
|
59
|
-
const cardsByFileId = new Map();
|
|
60
|
-
for (const card of sentFiles) {
|
|
61
|
-
const fileId = typeof card.fileId === "string" ? card.fileId.trim() : "";
|
|
62
|
-
const fileName = typeof card.fileName === "string" ? card.fileName.trim() : "";
|
|
63
|
-
const mimeType = typeof card.mimeType === "string" ? card.mimeType.trim() : "";
|
|
64
|
-
if (!fileId || !fileName || cardsByFileId.has(fileId)) {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
cardsByFileId.set(fileId, {
|
|
68
|
-
fileId,
|
|
69
|
-
fileName,
|
|
70
|
-
...(mimeType ? { mimeType } : {}),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
return Array.from(cardsByFileId.values());
|
|
74
|
-
}
|
|
75
|
-
function countSentFileCards(sentFiles) {
|
|
76
|
-
return collectSentFileCards(sentFiles).length;
|
|
77
|
-
}
|
|
78
|
-
async function sendFileCardsToUser(ctx, fileCards) {
|
|
79
|
-
const { config, sessionId, taskId, messageId } = ctx;
|
|
80
|
-
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
81
|
-
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
82
|
-
const wsManager = getXYWebSocketManager(config);
|
|
83
|
-
const sentFileCards = [];
|
|
84
|
-
for (const card of fileCards) {
|
|
85
|
-
const agentResponse = {
|
|
86
|
-
msgType: "agent_response",
|
|
87
|
-
agentId: config.agentId,
|
|
88
|
-
sessionId,
|
|
89
|
-
taskId: currentTaskId,
|
|
90
|
-
msgDetail: JSON.stringify({
|
|
91
|
-
jsonrpc: "2.0",
|
|
92
|
-
id: currentMessageId,
|
|
93
|
-
result: {
|
|
94
|
-
kind: "artifact-update",
|
|
95
|
-
append: true,
|
|
96
|
-
lastChunk: false,
|
|
97
|
-
final: false,
|
|
98
|
-
artifact: {
|
|
99
|
-
artifactId: currentTaskId,
|
|
100
|
-
parts: [
|
|
101
|
-
{
|
|
102
|
-
kind: "file",
|
|
103
|
-
file: {
|
|
104
|
-
name: card.fileName,
|
|
105
|
-
mimeType: card.mimeType,
|
|
106
|
-
fileId: card.fileId,
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
error: { code: 0 },
|
|
113
|
-
}),
|
|
114
|
-
};
|
|
115
|
-
logger.log(`${SEND_CROSS_RESULT_LOG_TAG} sending file card by fileId, fileName=${card.fileName}`);
|
|
116
|
-
await wsManager.sendMessage(sessionId, agentResponse);
|
|
117
|
-
sentFileCards.push({ fileName: card.fileName, fileId: card.fileId });
|
|
118
|
-
}
|
|
119
|
-
return sentFileCards;
|
|
120
|
-
}
|
|
121
59
|
async function autoSendFileToUserIfNeeded(result, ctx) {
|
|
122
60
|
const sentFiles = Array.isArray(result.sentFiles) ? result.sentFiles : [];
|
|
123
61
|
if (sentFiles.length === 0) {
|
|
124
62
|
return result;
|
|
125
63
|
}
|
|
126
|
-
|
|
127
|
-
if (fileCards.length === 0) {
|
|
128
|
-
const errorMessage = "Cross-device result contains no valid fileCards.";
|
|
129
|
-
logger.error(`${SEND_CROSS_RESULT_LOG_TAG} auto file card send skipped, error=${errorMessage}`);
|
|
130
|
-
return {
|
|
131
|
-
...result,
|
|
132
|
-
autoSendFileToUser: {
|
|
133
|
-
success: false,
|
|
134
|
-
error: errorMessage,
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
logger.log(`${SEND_CROSS_RESULT_LOG_TAG} auto sending cross-device file cards, fileCardCount=${fileCards.length}`);
|
|
64
|
+
logger.log(`${SEND_CROSS_RESULT_LOG_TAG} auto sending ${sentFiles.length} cross-device file(s) to user`);
|
|
139
65
|
try {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
66
|
+
const sendFileTool = createSendFileToUserTool(ctx);
|
|
67
|
+
const sendFileResult = await (async () => {
|
|
68
|
+
const results = [];
|
|
69
|
+
for (const sentFileParams of sentFiles) {
|
|
70
|
+
results.push(await sendFileTool.execute("auto_send_cross_device_file", sentFileParams));
|
|
71
|
+
}
|
|
72
|
+
return results;
|
|
73
|
+
})();
|
|
74
|
+
logger.log(`${SEND_CROSS_RESULT_LOG_TAG} auto send_file_to_user completed`);
|
|
144
75
|
return {
|
|
145
76
|
...result,
|
|
146
77
|
autoSendFileToUser: {
|
|
@@ -151,7 +82,7 @@ async function autoSendFileToUserIfNeeded(result, ctx) {
|
|
|
151
82
|
}
|
|
152
83
|
catch (error) {
|
|
153
84
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
154
|
-
logger.error(`${SEND_CROSS_RESULT_LOG_TAG} auto
|
|
85
|
+
logger.error(`${SEND_CROSS_RESULT_LOG_TAG} auto send_file_to_user failed, error=${errorMessage}`);
|
|
155
86
|
return {
|
|
156
87
|
...result,
|
|
157
88
|
autoSendFileToUser: {
|
|
@@ -287,7 +218,7 @@ export function createSendCrossDeviceTaskTool(ctx) {
|
|
|
287
218
|
}
|
|
288
219
|
settled = true;
|
|
289
220
|
const modelResult = buildModelToolResult(result);
|
|
290
|
-
logger.log(`${LOG_TAG} completed, success=${result.success}, code=${result.code},
|
|
221
|
+
logger.log(`${LOG_TAG} completed, success=${result.success}, code=${result.code}, sentFileCount=${result.sentFiles.length}`);
|
|
291
222
|
cleanup();
|
|
292
223
|
resolve(buildResultText(modelResult));
|
|
293
224
|
};
|
|
@@ -295,7 +226,7 @@ export function createSendCrossDeviceTaskTool(ctx) {
|
|
|
295
226
|
if (event.sessionId && event.sessionId !== sessionId && event.sessionId !== distributionSessionId) {
|
|
296
227
|
return;
|
|
297
228
|
}
|
|
298
|
-
logger.log(`${SEND_CROSS_RESULT_LOG_TAG} received result, status=${event.status}, code=${event.code},
|
|
229
|
+
logger.log(`${SEND_CROSS_RESULT_LOG_TAG} received result, status=${event.status}, code=${event.code}, sentFileCount=${event.sentFiles.length}`);
|
|
299
230
|
void (async () => {
|
|
300
231
|
if (resultHandlingStarted) {
|
|
301
232
|
return;
|
|
@@ -174,6 +174,16 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
174
174
|
throw new Error(`fileNames length (${fileNames.length}) must match fileRemoteUrls length (${fileRemoteUrls.length})`);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
+
if (ctx.runCrossTaskContext && (fileLocalUrls.length > 0 || fileRemoteUrls.length > 0)) {
|
|
178
|
+
const cachedSentFiles = appendRunCrossTaskSentFiles([
|
|
179
|
+
{
|
|
180
|
+
...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
|
|
181
|
+
...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
|
|
182
|
+
...(fileNames.length > 0 ? { fileNames } : {}),
|
|
183
|
+
},
|
|
184
|
+
], ctx.runCrossTaskContext);
|
|
185
|
+
logger.log(`[RunCrossTask] cached ${cachedSentFiles.length} send_file_to_user input(s) for cross-task result`);
|
|
186
|
+
}
|
|
177
187
|
// Get WebSocket manager
|
|
178
188
|
const wsManager = getXYWebSocketManager(config);
|
|
179
189
|
// Create upload service
|
|
@@ -227,7 +237,6 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
227
237
|
}
|
|
228
238
|
// Build and send agent_response messages for each file
|
|
229
239
|
const sentFiles = [];
|
|
230
|
-
let cachedSentFilesForReturn = [];
|
|
231
240
|
for (const uploadedFile of uploadedFiles) {
|
|
232
241
|
const { fileName, fileId, mimeType } = uploadedFile;
|
|
233
242
|
const agentResponse = {
|
|
@@ -264,12 +273,6 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
264
273
|
// Send WebSocket message
|
|
265
274
|
await wsManager.sendMessage(sessionId, agentResponse);
|
|
266
275
|
logger.log(`[SEND-FILE-TO-USER] send ${fileName} file to user success`);
|
|
267
|
-
if (ctx.runCrossTaskContext) {
|
|
268
|
-
const sentFileCard = { fileName, fileId, mimeType };
|
|
269
|
-
const cachedSentFiles = appendRunCrossTaskSentFiles([sentFileCard], ctx.runCrossTaskContext);
|
|
270
|
-
cachedSentFilesForReturn = cachedSentFiles;
|
|
271
|
-
logger.log(`[RunCrossTask] cached file card for cross-task result, fileName=${fileName}, cachedFileCardCount=${cachedSentFiles.length}`);
|
|
272
|
-
}
|
|
273
276
|
sentFiles.push({ fileName, fileId });
|
|
274
277
|
}
|
|
275
278
|
return {
|
|
@@ -279,8 +282,7 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
279
282
|
text: JSON.stringify({
|
|
280
283
|
sentFiles,
|
|
281
284
|
count: sentFiles.length,
|
|
282
|
-
message: `成功发送 ${sentFiles.length}
|
|
283
|
-
cachedSentFiles: cachedSentFilesForReturn
|
|
285
|
+
message: `成功发送 ${sentFiles.length} 个文件到用户设备`
|
|
284
286
|
}),
|
|
285
287
|
},
|
|
286
288
|
],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
-
import type { RunCrossTaskContext,
|
|
2
|
+
import type { RunCrossTaskContext, SentFileParams, XYChannelConfig } from "../types.js";
|
|
3
3
|
export interface SessionContext {
|
|
4
4
|
config: XYChannelConfig;
|
|
5
5
|
sessionId: string;
|
|
@@ -8,10 +8,6 @@ export interface SessionContext {
|
|
|
8
8
|
messageId: string;
|
|
9
9
|
agentId: string;
|
|
10
10
|
deviceType?: string;
|
|
11
|
-
/** App version extracted from A2A systemVariables (variables.systemVariables.app_ver). */
|
|
12
|
-
appVer?: string;
|
|
13
|
-
/** Display version (ROM version) extracted from A2A systemVariables (variables.systemVariables.display_version). */
|
|
14
|
-
displayVersion?: string;
|
|
15
11
|
/** Model name extracted from A2A user variables (variables.clientVariables.modelName).
|
|
16
12
|
* When set, provider.ts replaces model.id in the OpenAI request body. */
|
|
17
13
|
modelName?: string;
|
|
@@ -31,12 +27,6 @@ export declare function markCronToolCall(toolCallId: string): void;
|
|
|
31
27
|
export declare function isCronToolCall(toolCallId?: string): boolean;
|
|
32
28
|
/** Clean up a cron tool call marker after use. */
|
|
33
29
|
export declare function clearCronToolCall(toolCallId: string): void;
|
|
34
|
-
/** 把 fire 期解析出的 jobId 绑定到当前 cron run 的合成 sessionId。 */
|
|
35
|
-
export declare function setCurrentCronJobId(cronSessionId: string, jobId: string): void;
|
|
36
|
-
/** 凭合成 cron sessionId 取本次 run 的 jobId(供 sendCommand 反查 pushId)。 */
|
|
37
|
-
export declare function getCurrentCronJobId(cronSessionId?: string): string | undefined;
|
|
38
|
-
/** cron run 结束后清理。 */
|
|
39
|
-
export declare function clearCronJobId(cronSessionId: string): void;
|
|
40
30
|
export declare const asyncLocalStorage: AsyncLocalStorage<SessionContext>;
|
|
41
31
|
/**
|
|
42
32
|
* Register a session context for tool access.
|
|
@@ -89,6 +79,6 @@ export declare function cleanupStaleSessions(): number;
|
|
|
89
79
|
* Get the current number of active sessions (for diagnostics).
|
|
90
80
|
*/
|
|
91
81
|
export declare function getActiveSessionCount(): number;
|
|
92
|
-
export declare function appendRunCrossTaskSentFiles(sentFiles:
|
|
82
|
+
export declare function appendRunCrossTaskSentFiles(sentFiles: SentFileParams[], explicitRunCrossTaskContext?: RunCrossTaskContext): SentFileParams[];
|
|
93
83
|
export declare function clearRunCrossTaskSentFiles(explicitRunCrossTaskContext?: RunCrossTaskContext): void;
|
|
94
84
|
export {};
|
|
@@ -48,32 +48,6 @@ export function isCronToolCall(toolCallId) {
|
|
|
48
48
|
export function clearCronToolCall(toolCallId) {
|
|
49
49
|
cronToolCallMap.delete(toolCallId);
|
|
50
50
|
}
|
|
51
|
-
// ── Cron session ↔ jobId bridge ────────────────────────────────────
|
|
52
|
-
// fire 期 jobId 传递桥。provider.ts 在 isCron 分支从首条消息
|
|
53
|
-
// `[cron:<jobId> ...]` 解析出真实 jobId,写入合成 cron sessionId;
|
|
54
|
-
// sendCommand/formatter 凭同一合成 sessionId 反查 jobId,再去
|
|
55
|
-
// cron-push-map 取对应设备的 pushId。同一 cron run 内 ALS 上下文共享,
|
|
56
|
-
// 合成 sessionId 在 provider 与 sendCommand 之间一致。
|
|
57
|
-
if (!_g.__xyCronSessionJobId) {
|
|
58
|
-
_g.__xyCronSessionJobId = new Map();
|
|
59
|
-
}
|
|
60
|
-
const cronSessionJobIdMap = _g.__xyCronSessionJobId;
|
|
61
|
-
/** 把 fire 期解析出的 jobId 绑定到当前 cron run 的合成 sessionId。 */
|
|
62
|
-
export function setCurrentCronJobId(cronSessionId, jobId) {
|
|
63
|
-
if (cronSessionId && jobId) {
|
|
64
|
-
cronSessionJobIdMap.set(cronSessionId, jobId);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
/** 凭合成 cron sessionId 取本次 run 的 jobId(供 sendCommand 反查 pushId)。 */
|
|
68
|
-
export function getCurrentCronJobId(cronSessionId) {
|
|
69
|
-
if (!cronSessionId)
|
|
70
|
-
return undefined;
|
|
71
|
-
return cronSessionJobIdMap.get(cronSessionId);
|
|
72
|
-
}
|
|
73
|
-
/** cron run 结束后清理。 */
|
|
74
|
-
export function clearCronJobId(cronSessionId) {
|
|
75
|
-
cronSessionJobIdMap.delete(cronSessionId);
|
|
76
|
-
}
|
|
77
51
|
// AsyncLocalStorage for thread-safe session context isolation
|
|
78
52
|
export const asyncLocalStorage = new AsyncLocalStorage();
|
|
79
53
|
// Export AsyncLocalStorage to globalThis so logger.ts can access it
|
|
@@ -271,49 +245,36 @@ export function cleanupStaleSessions() {
|
|
|
271
245
|
export function getActiveSessionCount() {
|
|
272
246
|
return activeSessions.size;
|
|
273
247
|
}
|
|
274
|
-
function
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
248
|
+
function normalizeSentFileParams(params) {
|
|
249
|
+
const fileLocalUrls = Array.isArray(params.fileLocalUrls)
|
|
250
|
+
? params.fileLocalUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
251
|
+
: [];
|
|
252
|
+
const fileRemoteUrls = Array.isArray(params.fileRemoteUrls)
|
|
253
|
+
? params.fileRemoteUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
254
|
+
: [];
|
|
255
|
+
const fileNames = Array.isArray(params.fileNames)
|
|
256
|
+
? params.fileNames.filter((name) => typeof name === "string" && name.length > 0)
|
|
257
|
+
: [];
|
|
258
|
+
if (fileLocalUrls.length === 0 && fileRemoteUrls.length === 0) {
|
|
282
259
|
return null;
|
|
283
260
|
}
|
|
284
261
|
return {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
...(
|
|
262
|
+
...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
|
|
263
|
+
...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
|
|
264
|
+
...(fileNames.length > 0 && fileNames.length === fileRemoteUrls.length ? { fileNames } : {}),
|
|
288
265
|
};
|
|
289
266
|
}
|
|
290
|
-
function dedupeSentFilesByFileId(existing, incoming) {
|
|
291
|
-
const knownFileIds = new Set();
|
|
292
|
-
for (const card of existing) {
|
|
293
|
-
if (card.fileId) {
|
|
294
|
-
knownFileIds.add(card.fileId);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return incoming.filter((card) => {
|
|
298
|
-
if (knownFileIds.has(card.fileId)) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
knownFileIds.add(card.fileId);
|
|
302
|
-
return true;
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
267
|
export function appendRunCrossTaskSentFiles(sentFiles, explicitRunCrossTaskContext) {
|
|
306
268
|
const context = asyncLocalStorage.getStore() ?? null;
|
|
307
269
|
const runCrossTaskContext = explicitRunCrossTaskContext ?? context?.runCrossTaskContext;
|
|
308
270
|
const normalizedSentFiles = sentFiles
|
|
309
|
-
.map((
|
|
310
|
-
.filter((
|
|
271
|
+
.map((params) => normalizeSentFileParams(params))
|
|
272
|
+
.filter((params) => params !== null);
|
|
311
273
|
if (!runCrossTaskContext || normalizedSentFiles.length === 0) {
|
|
312
274
|
return runCrossTaskContext?.sentFiles ?? [];
|
|
313
275
|
}
|
|
314
276
|
const existing = Array.isArray(runCrossTaskContext.sentFiles) ? runCrossTaskContext.sentFiles : [];
|
|
315
|
-
const
|
|
316
|
-
const merged = [...existing, ...dedupedSentFiles];
|
|
277
|
+
const merged = [...existing, ...normalizedSentFiles];
|
|
317
278
|
runCrossTaskContext.sentFiles = merged;
|
|
318
279
|
const sessionWithRef = Array.from(activeSessions.values()).find((session) => session.runCrossTaskContext === runCrossTaskContext);
|
|
319
280
|
if (sessionWithRef?.runCrossTaskContext) {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -68,7 +68,7 @@ export interface CrossDeviceTaskResultEvent {
|
|
|
68
68
|
sessionId: string;
|
|
69
69
|
code: string;
|
|
70
70
|
message: string;
|
|
71
|
-
sentFiles:
|
|
71
|
+
sentFiles: SentFileParams[];
|
|
72
72
|
status: "success" | "failed";
|
|
73
73
|
rawEvent: any;
|
|
74
74
|
}
|
|
@@ -78,13 +78,13 @@ export interface RunCrossTaskContext {
|
|
|
78
78
|
isDistributed: boolean;
|
|
79
79
|
networkId: string;
|
|
80
80
|
isSupportAgent: boolean;
|
|
81
|
-
sentFiles:
|
|
81
|
+
sentFiles: SentFileParams[];
|
|
82
82
|
rawContext: any;
|
|
83
83
|
}
|
|
84
|
-
export interface
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
export interface SentFileParams {
|
|
85
|
+
fileLocalUrls?: string[];
|
|
86
|
+
fileRemoteUrls?: string[];
|
|
87
|
+
fileNames?: string[];
|
|
88
88
|
}
|
|
89
89
|
export interface A2ATaskArtifactUpdateEvent {
|
|
90
90
|
taskId: string;
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* Specifically handles pushId which can be updated per-session.
|
|
4
4
|
*/
|
|
5
5
|
declare class ConfigManager {
|
|
6
|
-
private
|
|
7
|
-
private
|
|
8
|
-
private set globalPushId(value);
|
|
6
|
+
private sessionPushIds;
|
|
7
|
+
private globalPushId;
|
|
9
8
|
/**
|
|
10
9
|
* Update push ID for a specific session.
|
|
11
10
|
*/
|
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
// Dynamic configuration manager for runtime updates
|
|
2
|
-
//
|
|
3
|
-
// NOTE: xy_channel is loaded from multiple module resolution paths
|
|
4
|
-
// (plugin entry vs tool registration), which duplicates class instances.
|
|
5
|
-
// sessionPushIds and globalPushId must live on globalThis so all copies
|
|
6
|
-
// share the same Map — same reason activeSessions is on globalThis in
|
|
7
|
-
// session-manager.ts.
|
|
8
2
|
import { logger } from "./logger.js";
|
|
9
|
-
const _g = globalThis;
|
|
10
|
-
if (!_g.__xyConfigSessionPushIds) {
|
|
11
|
-
_g.__xyConfigSessionPushIds = new Map();
|
|
12
|
-
}
|
|
13
|
-
if (!_g.__xyConfigGlobalPushId) {
|
|
14
|
-
_g.__xyConfigGlobalPushId = null;
|
|
15
|
-
}
|
|
16
3
|
/**
|
|
17
4
|
* Manages dynamic configuration updates that can change at runtime.
|
|
18
5
|
* Specifically handles pushId which can be updated per-session.
|
|
19
6
|
*/
|
|
20
7
|
class ConfigManager {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
get globalPushId() {
|
|
25
|
-
return _g.__xyConfigGlobalPushId;
|
|
26
|
-
}
|
|
27
|
-
set globalPushId(value) {
|
|
28
|
-
_g.__xyConfigGlobalPushId = value;
|
|
29
|
-
}
|
|
8
|
+
sessionPushIds = new Map();
|
|
9
|
+
globalPushId = null;
|
|
30
10
|
/**
|
|
31
11
|
* Update push ID for a specific session.
|
|
32
12
|
*/
|
package/dist/src/websocket.js
CHANGED
|
@@ -7,7 +7,6 @@ import { HeartbeatManager } from "./heartbeat.js";
|
|
|
7
7
|
import { MessageQueue } from "./message-queue.js";
|
|
8
8
|
import { v4 as uuidv4 } from "uuid";
|
|
9
9
|
const RUN_CROSS_TASK_LOG_TAG = "[RunCrossTask]";
|
|
10
|
-
const SEND_CROSS_RESULT_LOG_TAG = "[SendCrossResult]";
|
|
11
10
|
const RUN_CROSS_TASK_QUERY_PREFIX = `# 跨设备协作接收模式<br/><br/>你当前正在接收来自其他设备的协作请求。请注意以下角色转换规则:<br/><br/>## 角色转换规则<br/><br/>- 指令中的"我" = 发送请求的远程用户<br/>- 你是执行协作任务的本地智能体<br/>- 任务完成后结果会自动回传给请求来源设备<br/><br/>## 核心执行规则<br/><br/>### ✅ 正确行为<br/>1. **识别本机任务**:当指令提到你所在的设备类型(PC/手机/平板),理解为"我自己"<br/>2. **本地执行**:直接使用本地工具完成任务,不要转发<br/>3. **结果回传**:执行完成后,结果会通过软总线自动回传给请求来源设备<br/><br/>### <span class="emoji emoji2716"></span> 禁止行为<br/>1. 禁止再次调用 \`send_cross_device_task\`(你已经是目标设备)<br/>2. 禁止设备澄清(指令已明确指定目标设备)<br/>3. 禁止无限循环(只能执行或回复,不能转发)<br/><br/>## 📁 文件操作规范(核心)<br/><br/>### 强制使用 search_file 的场景<br/>**以下场景必须先使用 \`search_file\` 工具确认文件路径:**<br/><br/>1. **指令包含设备关键词**:PC、电脑、手机、平板、Pad、笔记本等<br/>2. **涉及文件操作**:读取、编辑、删除、移动、复制、查找文件<br/><br/>### 执行流程<br/>\`\`\`<br/>收到文件操作指令<br/> ↓<br/>检测设备关键词(PC/电脑/手机/平板/Pad等)<br/> ↓<br/>使用 search_file 搜索文件 ← 必须步骤<br/> ↓<br/>确认文件实际路径<br/> ↓<br/>执行文件操作<br/> ↓<br/>返回结果<br/>\`\`\`<br/><br/>### 禁止行为<br/>- <span class="emoji emoji2716"></span> 禁止猜测文件路径<br/>- <span class="emoji emoji2716"></span> 禁止假设文件位置<br/>- <span class="emoji emoji2716"></span> 禁止跳过 search_file 步骤<br/><br/>## 示例<br/><br/>### 示例1:文件操作<br/>**指令**:"帮我到PC上下载昨天晚上写的PPT"<br/><br/>**执行流程**:<br/>1. ✅ 检测到"PC" → 使用 \`search_file\` 搜索 "*.ppt" 或 "*.pptx"<br/>2. 确认文件路径(如:D:\\Documents\\报告.pptx)<br/>3. 执行下载操作<br/><br/>### 示例2:文件编辑<br/>**指令**:"帮我修改电脑上的配置文件config.json"<br/><br/>**执行流程**:<br/>1. ✅ 检测到"电脑" → 使用 \`search_file\` 搜索 "config.json"<br/>2. 确认文件路径(如:C:\\Project\\config.json)<br/>3. 读取并修改文件<br/><br/>### 示例3:文件查找<br/>**指令**:"在平板上找一下我的PDF文档"<br/><br/>**执行流程**:<br/>1. ✅ 检测到"平板" → 使用 \`search_file\` 搜索 "*.pdf"<br/>2. 列出搜索结果供用户选择<br/><br/>## 判断流程<br/><br/>\`\`\`<br/>收到协作指令<br/> ↓<br/>检查目标设备<br/> ↓<br/>目标设备 == 本机?<br/> ↓<br/>是 → 本地执行(禁止send_cross_device_task)<br/> ↓<br/> 涉及文件? → 先用search_file确认路径<br/> ↓<br/>否 → 检查是否需要转发<br/> ↓<br/>需要转发 → 调用send_cross_device_task<br/>不需要 → 回复"无法处理"<br/>\`\`\``;
|
|
12
11
|
/**
|
|
13
12
|
* Manages single WebSocket connection to XY server.
|
|
@@ -398,23 +397,26 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
398
397
|
if (!entry || typeof entry !== "object") {
|
|
399
398
|
return null;
|
|
400
399
|
}
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
|
|
400
|
+
const fileLocalUrls = Array.isArray(entry.fileLocalUrls)
|
|
401
|
+
? entry.fileLocalUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
402
|
+
: [];
|
|
403
|
+
const fileRemoteUrls = Array.isArray(entry.fileRemoteUrls)
|
|
404
|
+
? entry.fileRemoteUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
405
|
+
: [];
|
|
406
|
+
const fileNames = Array.isArray(entry.fileNames)
|
|
407
|
+
? entry.fileNames.filter((name) => typeof name === "string" && name.length > 0)
|
|
408
|
+
: [];
|
|
409
|
+
if (fileLocalUrls.length === 0 && fileRemoteUrls.length === 0) {
|
|
406
410
|
return null;
|
|
407
411
|
}
|
|
408
412
|
return {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
...(
|
|
413
|
+
...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
|
|
414
|
+
...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
|
|
415
|
+
...(fileNames.length > 0 && fileNames.length === fileRemoteUrls.length ? { fileNames } : {}),
|
|
412
416
|
};
|
|
413
417
|
}).filter((entry) => entry !== null)
|
|
414
418
|
: [];
|
|
415
419
|
const status = code === "0" ? "success" : "failed";
|
|
416
|
-
const fileCardCount = sentFiles.length;
|
|
417
|
-
this.log(`${SEND_CROSS_RESULT_LOG_TAG} normalized CrossTaskExecuteResult, sessionId=${sessionId}, status=${status}, code=${code}, fileCardCount=${fileCardCount}, messageLength=${message.length}`);
|
|
418
420
|
const event = {
|
|
419
421
|
sessionId,
|
|
420
422
|
code,
|
|
@@ -512,7 +514,6 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
512
514
|
? logger.withContext(sessionId, taskId)
|
|
513
515
|
: { log: (msg, ...args) => logger.log(msg, ...args) };
|
|
514
516
|
log.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
|
|
515
|
-
log.log(`[WS-RECV] Full message: ${messageStr}`);
|
|
516
517
|
// Handle direct cross-task requests (top-level networkId)
|
|
517
518
|
const directRunCrossTaskRequest = this.toRunCrossTaskA2ARequest(parsed);
|
|
518
519
|
if (directRunCrossTaskRequest) {
|