@ynhcj/xiaoyi-channel 0.0.149-beta → 0.0.151-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 +3 -69
- package/dist/src/approval-bridge.d.ts +48 -0
- package/dist/src/approval-bridge.js +382 -0
- package/dist/src/bot.js +64 -68
- package/dist/src/client.js +13 -23
- package/dist/src/cspl/call_api.d.ts +2 -0
- package/dist/src/cspl/call_api.js +107 -0
- package/dist/src/cspl/config.d.ts +4 -17
- package/dist/src/cspl/config.js +80 -70
- package/dist/src/cspl/configs.json +10 -0
- package/dist/src/cspl/constants.d.ts +46 -24
- package/dist/src/cspl/constants.js +41 -16
- package/dist/src/cspl/sentinel_hook.d.ts +2 -0
- package/dist/src/cspl/sentinel_hook.js +84 -0
- package/dist/src/cspl/steer-context.js +1 -1
- package/dist/src/cspl/upload_file.d.ts +1 -0
- package/dist/src/cspl/upload_file.js +211 -0
- package/dist/src/cspl/utils.d.ts +11 -2
- package/dist/src/cspl/utils.js +265 -15
- package/dist/src/formatter.js +92 -37
- package/dist/src/monitor.js +18 -21
- package/dist/src/outbound.js +8 -9
- package/dist/src/push.js +8 -15
- package/dist/src/reply-dispatcher.js +39 -48
- package/dist/src/self-evolution-handler.js +1 -1
- package/dist/src/sensitive-redactor.d.ts +4 -0
- package/dist/src/sensitive-redactor.js +364 -0
- package/dist/src/task-manager.js +6 -10
- package/dist/src/tools/agent-as-skill-tool.d.ts +7 -0
- package/dist/src/tools/agent-as-skill-tool.js +138 -0
- package/dist/src/tools/calendar-tool.js +1 -1
- package/dist/src/tools/call-device-tool.js +3 -0
- package/dist/src/tools/call-phone-tool.js +1 -1
- package/dist/src/tools/create-alarm-tool.js +1 -1
- package/dist/src/tools/create-all-tools.js +5 -1
- package/dist/src/tools/delete-alarm-tool.js +1 -1
- package/dist/src/tools/find-pc-devices-tool.d.ts +2 -1
- package/dist/src/tools/find-pc-devices-tool.js +84 -88
- package/dist/src/tools/get-device-file-tool-schema.js +3 -2
- package/dist/src/tools/image-reading-tool.js +4 -4
- package/dist/src/tools/location-tool.js +1 -1
- package/dist/src/tools/modify-alarm-tool.js +1 -1
- package/dist/src/tools/modify-note-tool.js +1 -1
- package/dist/src/tools/note-tool.js +1 -1
- package/dist/src/tools/query-app-message-tool.js +1 -1
- package/dist/src/tools/query-memory-data-tool.js +1 -1
- package/dist/src/tools/query-todo-task-tool.js +1 -1
- package/dist/src/tools/save-file-to-phone-tool.js +1 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +1 -1
- package/dist/src/tools/search-alarm-tool.js +1 -1
- package/dist/src/tools/search-calendar-tool.js +1 -1
- package/dist/src/tools/search-contact-tool.js +1 -1
- package/dist/src/tools/search-email-tool.js +1 -1
- package/dist/src/tools/search-file-tool.js +11 -8
- package/dist/src/tools/search-message-tool.js +1 -1
- package/dist/src/tools/search-note-tool.js +1 -1
- package/dist/src/tools/search-photo-gallery-tool.js +1 -1
- package/dist/src/tools/send-email-tool.js +1 -1
- package/dist/src/tools/send-file-to-user-tool.js +2 -2
- package/dist/src/tools/send-message-tool.js +1 -1
- package/dist/src/tools/session-manager.js +5 -0
- package/dist/src/tools/upload-file-tool.js +15 -5
- package/dist/src/tools/upload-photo-tool.js +1 -1
- package/dist/src/tools/xiaoyi-add-collection-tool.js +1 -1
- package/dist/src/tools/xiaoyi-collection-tool.js +1 -1
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +1 -1
- package/dist/src/tools/xiaoyi-gui-tool.js +1 -1
- package/dist/src/trigger-handler.js +4 -7
- package/dist/src/utils/config-manager.js +3 -6
- package/dist/src/utils/logger.d.ts +8 -0
- package/dist/src/utils/logger.js +69 -34
- package/dist/src/utils/pushdata-manager.js +1 -5
- package/dist/src/utils/pushid-manager.js +1 -2
- package/dist/src/utils/runtime-manager.js +1 -4
- package/dist/src/websocket.js +37 -25
- package/package.json +1 -1
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
// 一键开关 Xiaoyi channel 的敏感信息脱敏与检测能力。
|
|
4
|
+
// false 时:
|
|
5
|
+
// 1. 不再读取 openclaw.json / .xiaoyienv 提取敏感值;
|
|
6
|
+
// 2. 不再对消息内容做脱敏替换;
|
|
7
|
+
// 3. 不再做 Base64 绕过检测;
|
|
8
|
+
// 4. containsSensitiveInfo 一律返回 false。
|
|
9
|
+
const ENABLE_SENSITIVE_REDACTION = true;
|
|
10
|
+
let sensitiveValues = null;
|
|
11
|
+
let lastLoadTime = 0;
|
|
12
|
+
const CACHE_TTL_MS = 60 * 1000;
|
|
13
|
+
const REDACTED_MARKER = "[execution-validator已脱敏]";
|
|
14
|
+
const BASE64_CANDIDATE_RE = /(?<![A-Za-z0-9+/_-])([A-Za-z0-9+/]{12,}={0,2}|[A-Za-z0-9_-]{12,})(?![A-Za-z0-9+/_-])/g;
|
|
15
|
+
const SECRET_KEYWORDS = [
|
|
16
|
+
"apikey",
|
|
17
|
+
"token",
|
|
18
|
+
"secret",
|
|
19
|
+
"password",
|
|
20
|
+
"passwd",
|
|
21
|
+
"pwd",
|
|
22
|
+
"authorization",
|
|
23
|
+
"cookie",
|
|
24
|
+
"session",
|
|
25
|
+
"signature",
|
|
26
|
+
"sign",
|
|
27
|
+
];
|
|
28
|
+
const ID_KEYWORDS = [
|
|
29
|
+
"agentid",
|
|
30
|
+
"apiid",
|
|
31
|
+
"uid",
|
|
32
|
+
"pushid",
|
|
33
|
+
"userid",
|
|
34
|
+
"accountid",
|
|
35
|
+
"clientid",
|
|
36
|
+
"appid",
|
|
37
|
+
];
|
|
38
|
+
const URL_KEYWORDS = [
|
|
39
|
+
"url",
|
|
40
|
+
"endpoint",
|
|
41
|
+
];
|
|
42
|
+
const PLACEHOLDER_VALUES = new Set([
|
|
43
|
+
"apikey",
|
|
44
|
+
"api-key",
|
|
45
|
+
"api_key",
|
|
46
|
+
"token",
|
|
47
|
+
"secret",
|
|
48
|
+
"password",
|
|
49
|
+
"passwd",
|
|
50
|
+
"pwd",
|
|
51
|
+
"authorization",
|
|
52
|
+
"bearer",
|
|
53
|
+
"openclaw",
|
|
54
|
+
"text/event-stream",
|
|
55
|
+
]);
|
|
56
|
+
function getConfigRoot() {
|
|
57
|
+
return process.env.HOME || "/home/sandbox";
|
|
58
|
+
}
|
|
59
|
+
function normalizeKeyName(key) {
|
|
60
|
+
return String(key ?? "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
61
|
+
}
|
|
62
|
+
function normalizeValue(value) {
|
|
63
|
+
return String(value ?? "").trim();
|
|
64
|
+
}
|
|
65
|
+
function matchesKeyword(normalizedKey, keywords) {
|
|
66
|
+
return keywords.some(keyword => normalizedKey.includes(keyword));
|
|
67
|
+
}
|
|
68
|
+
function isPlaceholderValue(value) {
|
|
69
|
+
const normalized = normalizeValue(value).toLowerCase();
|
|
70
|
+
if (!normalized) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (PLACEHOLDER_VALUES.has(normalized)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (/^(my|your|demo|test)?(apikey|token|secret|password)$/.test(normalized)) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
function looksLikeToken(value) {
|
|
82
|
+
const trimmed = normalizeValue(value);
|
|
83
|
+
if (trimmed.length < 8 || isPlaceholderValue(trimmed)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (/^(sk-|sk_)[a-z0-9]/i.test(trimmed)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (/^[a-f0-9]{24,}$/i.test(trimmed)) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (/^[A-Za-z0-9._/+\\=-]{16,}$/.test(trimmed) && /[A-Za-z]/.test(trimmed) && /\d/.test(trimmed)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
function looksLikeIdentifier(value) {
|
|
98
|
+
const trimmed = normalizeValue(value);
|
|
99
|
+
if (trimmed.length < 6 || isPlaceholderValue(trimmed)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (/^agent[a-z0-9]{8,}$/i.test(trimmed)) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (/^webhook[a-z0-9]{6,}$/i.test(trimmed)) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (/^\d{6,}$/.test(trimmed)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (/^[A-Za-z][A-Za-z0-9_-]{10,}$/.test(trimmed) && /\d/.test(trimmed)) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
function looksLikeSensitiveUrl(value) {
|
|
117
|
+
const trimmed = normalizeValue(value);
|
|
118
|
+
try {
|
|
119
|
+
const parsed = new URL(trimmed);
|
|
120
|
+
if (parsed.username || parsed.password) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
for (const [name, paramValue] of parsed.searchParams.entries()) {
|
|
124
|
+
const normalizedName = normalizeKeyName(name);
|
|
125
|
+
if ((matchesKeyword(normalizedName, SECRET_KEYWORDS) || matchesKeyword(normalizedName, ID_KEYWORDS))
|
|
126
|
+
&& normalizeValue(paramValue).length > 5) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
131
|
+
return segments.some(segment => looksLikeToken(segment));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function shouldKeepSensitiveValue(key, value) {
|
|
138
|
+
const trimmed = normalizeValue(value);
|
|
139
|
+
if (trimmed.length <= 5 || isPlaceholderValue(trimmed)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const normalizedKey = normalizeKeyName(key);
|
|
143
|
+
if (matchesKeyword(normalizedKey, SECRET_KEYWORDS)) {
|
|
144
|
+
return looksLikeToken(trimmed) || looksLikeSensitiveUrl(trimmed);
|
|
145
|
+
}
|
|
146
|
+
if (matchesKeyword(normalizedKey, ID_KEYWORDS)) {
|
|
147
|
+
return looksLikeIdentifier(trimmed) || looksLikeToken(trimmed);
|
|
148
|
+
}
|
|
149
|
+
if (matchesKeyword(normalizedKey, URL_KEYWORDS)) {
|
|
150
|
+
return looksLikeSensitiveUrl(trimmed);
|
|
151
|
+
}
|
|
152
|
+
return looksLikeToken(trimmed);
|
|
153
|
+
}
|
|
154
|
+
function addSensitiveValue(values, key, value) {
|
|
155
|
+
if (shouldKeepSensitiveValue(key, value)) {
|
|
156
|
+
values.push(String(value));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function extractSensitiveValues() {
|
|
160
|
+
const values = [];
|
|
161
|
+
try {
|
|
162
|
+
const openclawJsonPath = path.join(getConfigRoot(), ".openclaw", "openclaw.json");
|
|
163
|
+
if (fs.existsSync(openclawJsonPath)) {
|
|
164
|
+
const content = fs.readFileSync(openclawJsonPath, "utf-8");
|
|
165
|
+
const config = JSON.parse(content);
|
|
166
|
+
if (config.channels) {
|
|
167
|
+
for (const channelName of Object.keys(config.channels)) {
|
|
168
|
+
const channel = config.channels[channelName];
|
|
169
|
+
addSensitiveValue(values, "apiKey", channel.apiKey);
|
|
170
|
+
addSensitiveValue(values, "agentId", channel.agentId);
|
|
171
|
+
addSensitiveValue(values, "apiId", channel.apiId);
|
|
172
|
+
addSensitiveValue(values, "uid", channel.uid);
|
|
173
|
+
addSensitiveValue(values, "pushId", channel.pushId);
|
|
174
|
+
addSensitiveValue(values, "wsUrl1", channel.wsUrl1);
|
|
175
|
+
addSensitiveValue(values, "wsUrl2", channel.wsUrl2);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (config.models?.providers) {
|
|
179
|
+
for (const providerName of Object.keys(config.models.providers)) {
|
|
180
|
+
const provider = config.models.providers[providerName];
|
|
181
|
+
addSensitiveValue(values, "apiKey", provider.apiKey);
|
|
182
|
+
addSensitiveValue(values, "baseUrl", provider.baseUrl);
|
|
183
|
+
if (provider.headers) {
|
|
184
|
+
for (const headerKey of Object.keys(provider.headers)) {
|
|
185
|
+
addSensitiveValue(values, headerKey, provider.headers[headerKey]);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
addSensitiveValue(values, "token", config.gateway?.auth?.token);
|
|
191
|
+
console.log(`[SENSITIVE_REDACTOR] Extracted ${values.length} sensitive values from openclaw.json`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
console.error("[SENSITIVE_REDACTOR] Failed to read openclaw.json:", err);
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const xiaoyienvPath = path.join(getConfigRoot(), ".openclaw", ".xiaoyienv");
|
|
199
|
+
if (fs.existsSync(xiaoyienvPath)) {
|
|
200
|
+
const content = fs.readFileSync(xiaoyienvPath, "utf-8");
|
|
201
|
+
try {
|
|
202
|
+
const envConfig = JSON.parse(content);
|
|
203
|
+
for (const key of Object.keys(envConfig)) {
|
|
204
|
+
addSensitiveValue(values, key, envConfig[key]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
const lines = content.split("\n");
|
|
209
|
+
for (const line of lines) {
|
|
210
|
+
const trimmed = line.trim();
|
|
211
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
212
|
+
const eqIndex = trimmed.indexOf("=");
|
|
213
|
+
if (eqIndex > 0) {
|
|
214
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
215
|
+
const value = trimmed.slice(eqIndex + 1).trim();
|
|
216
|
+
addSensitiveValue(values, key, value);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
console.log(`[SENSITIVE_REDACTOR] Total sensitive values after .xiaoyienv: ${values.length}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
console.error("[SENSITIVE_REDACTOR] Failed to read .xiaoyienv:", err);
|
|
226
|
+
}
|
|
227
|
+
const uniqueValues = [...new Set(values)].filter(value => typeof value === "string" && value.length > 5);
|
|
228
|
+
uniqueValues.sort((a, b) => b.length - a.length);
|
|
229
|
+
return uniqueValues;
|
|
230
|
+
}
|
|
231
|
+
function getSensitiveValues() {
|
|
232
|
+
if (!ENABLE_SENSITIVE_REDACTION) {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
const now = Date.now();
|
|
236
|
+
if (sensitiveValues === null || now - lastLoadTime > CACHE_TTL_MS) {
|
|
237
|
+
sensitiveValues = extractSensitiveValues();
|
|
238
|
+
lastLoadTime = now;
|
|
239
|
+
}
|
|
240
|
+
return sensitiveValues;
|
|
241
|
+
}
|
|
242
|
+
export function refreshSensitivePatterns() {
|
|
243
|
+
sensitiveValues = null;
|
|
244
|
+
lastLoadTime = 0;
|
|
245
|
+
if (ENABLE_SENSITIVE_REDACTION) {
|
|
246
|
+
getSensitiveValues();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function maskSensitiveValue(value) {
|
|
250
|
+
return REDACTED_MARKER;
|
|
251
|
+
}
|
|
252
|
+
function escapeRegExp(value) {
|
|
253
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
254
|
+
}
|
|
255
|
+
function containsSensitiveValue(text, values) {
|
|
256
|
+
const normalizedText = String(text).toLowerCase();
|
|
257
|
+
for (const sensitiveValue of values) {
|
|
258
|
+
if (sensitiveValue && normalizedText.includes(String(sensitiveValue).toLowerCase())) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
function replaceSensitiveValues(text, values) {
|
|
265
|
+
let result = text;
|
|
266
|
+
for (const sensitiveValue of values) {
|
|
267
|
+
if (sensitiveValue && containsSensitiveValue(result, [sensitiveValue])) {
|
|
268
|
+
const masked = maskSensitiveValue(sensitiveValue);
|
|
269
|
+
result = result.replace(new RegExp(escapeRegExp(sensitiveValue), "gi"), masked);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
// 从文本里提取“看起来像 Base64”的连续片段。
|
|
275
|
+
// 这里只做候选收集,是否真的需要脱敏由解码后的匹配结果决定。
|
|
276
|
+
function extractBase64Candidates(text) {
|
|
277
|
+
return [...text.matchAll(BASE64_CANDIDATE_RE)].map(match => match[1]);
|
|
278
|
+
}
|
|
279
|
+
// 尝试把候选片段按 Base64 / Base64URL 解码。
|
|
280
|
+
// 解码失败时返回 null,交给上层继续处理其他候选。
|
|
281
|
+
function tryDecodeBase64(input) {
|
|
282
|
+
try {
|
|
283
|
+
let normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
284
|
+
const mod = normalized.length % 4;
|
|
285
|
+
if (mod) {
|
|
286
|
+
normalized += "=".repeat(4 - mod);
|
|
287
|
+
}
|
|
288
|
+
const decoded = Buffer.from(normalized, "base64").toString("utf8");
|
|
289
|
+
if (!decoded) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
return decoded;
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
export function redactSensitiveText(text) {
|
|
299
|
+
if (!ENABLE_SENSITIVE_REDACTION) {
|
|
300
|
+
return text;
|
|
301
|
+
}
|
|
302
|
+
if (!text || typeof text !== "string") {
|
|
303
|
+
return text;
|
|
304
|
+
}
|
|
305
|
+
const values = getSensitiveValues();
|
|
306
|
+
let result = replaceSensitiveValues(text, values);
|
|
307
|
+
// 先按原文直接匹配;只有没命中时才继续尝试 Base64 绕过检测。
|
|
308
|
+
if (result !== text) {
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
const candidates = extractBase64Candidates(text);
|
|
312
|
+
for (const candidate of candidates) {
|
|
313
|
+
const decoded = tryDecodeBase64(candidate);
|
|
314
|
+
if (!decoded) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (containsSensitiveValue(decoded, values)) {
|
|
318
|
+
result = result.split(candidate).join(REDACTED_MARKER);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
export function redactSensitiveObject(obj) {
|
|
324
|
+
if (!ENABLE_SENSITIVE_REDACTION) {
|
|
325
|
+
return obj;
|
|
326
|
+
}
|
|
327
|
+
if (obj === null || obj === undefined) {
|
|
328
|
+
return obj;
|
|
329
|
+
}
|
|
330
|
+
if (typeof obj === "string") {
|
|
331
|
+
return redactSensitiveText(obj);
|
|
332
|
+
}
|
|
333
|
+
if (Array.isArray(obj)) {
|
|
334
|
+
return obj.map(item => redactSensitiveObject(item));
|
|
335
|
+
}
|
|
336
|
+
if (typeof obj === "object") {
|
|
337
|
+
const result = {};
|
|
338
|
+
for (const key of Object.keys(obj)) {
|
|
339
|
+
result[key] = redactSensitiveObject(obj[key]);
|
|
340
|
+
}
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
return obj;
|
|
344
|
+
}
|
|
345
|
+
export function containsSensitiveInfo(text) {
|
|
346
|
+
if (!ENABLE_SENSITIVE_REDACTION) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
if (!text || typeof text !== "string") {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
const values = getSensitiveValues();
|
|
353
|
+
if (containsSensitiveValue(text, values)) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
const candidates = extractBase64Candidates(text);
|
|
357
|
+
for (const candidate of candidates) {
|
|
358
|
+
const decoded = tryDecodeBase64(candidate);
|
|
359
|
+
if (decoded && containsSensitiveValue(decoded, values)) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return false;
|
|
364
|
+
}
|
package/dist/src/task-manager.js
CHANGED
|
@@ -17,26 +17,22 @@ const activeTaskIds = _g.__xyActiveTaskIds;
|
|
|
17
17
|
* Returns true if this was an update (session already had an active task).
|
|
18
18
|
*/
|
|
19
19
|
export function registerTaskId(sessionId, taskId, messageId) {
|
|
20
|
-
logger.log(`[TASK_MANAGER] 📝 Registering/Updating taskId for session: ${sessionId}`);
|
|
21
|
-
logger.log(`[TASK_MANAGER] - taskId: ${taskId}`);
|
|
22
20
|
const existing = activeTaskIds.get(sessionId);
|
|
23
21
|
if (existing) {
|
|
24
|
-
logger.log(`[TASK_MANAGER]
|
|
25
|
-
logger.log(`[TASK_MANAGER] - 🔄 Updating taskId`);
|
|
22
|
+
logger.log(`[TASK_MANAGER] Updating taskId: ${existing.currentTaskId} → ${taskId}`);
|
|
26
23
|
existing.currentTaskId = taskId;
|
|
27
24
|
existing.currentMessageId = messageId;
|
|
28
25
|
existing.updatedAt = Date.now();
|
|
29
26
|
return true; // isUpdate
|
|
30
27
|
}
|
|
31
28
|
else {
|
|
32
|
-
|
|
29
|
+
activeTaskIds.set(sessionId, {
|
|
33
30
|
sessionId,
|
|
34
31
|
currentTaskId: taskId,
|
|
35
32
|
currentMessageId: messageId,
|
|
36
33
|
updatedAt: Date.now(),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
logger.log(`[TASK_MANAGER] - ✅ TaskId registered (new)`);
|
|
34
|
+
});
|
|
35
|
+
logger.log(`[TASK_MANAGER] Registered new taskId: ${taskId}`);
|
|
40
36
|
return false;
|
|
41
37
|
}
|
|
42
38
|
}
|
|
@@ -44,7 +40,7 @@ export function registerTaskId(sessionId, taskId, messageId) {
|
|
|
44
40
|
* 移除session的活跃taskId(消息处理完成时调用)。
|
|
45
41
|
*/
|
|
46
42
|
export function decrementTaskIdRef(sessionId) {
|
|
47
|
-
logger.log(`[TASK_MANAGER]
|
|
43
|
+
logger.log(`[TASK_MANAGER] Removing taskId`);
|
|
48
44
|
activeTaskIds.delete(sessionId);
|
|
49
45
|
}
|
|
50
46
|
/**
|
|
@@ -77,6 +73,6 @@ export function getAllActiveTaskBindings() {
|
|
|
77
73
|
* 强制清理(错误恢复用)
|
|
78
74
|
*/
|
|
79
75
|
export function forceCleanTaskId(sessionId) {
|
|
80
|
-
logger.log(`[TASK_MANAGER]
|
|
76
|
+
logger.log(`[TASK_MANAGER] Force clearing taskId`);
|
|
81
77
|
activeTaskIds.delete(sessionId);
|
|
82
78
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SessionContext } from "./session-manager.js";
|
|
2
|
+
/**
|
|
3
|
+
* Agent-as-skill tool - invokes a registered agent by agentId as a skill.
|
|
4
|
+
* The tool receives the agentId, query, and optional file attachments,
|
|
5
|
+
* forwards the request to the target agent via WebSocket, and returns the result.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createAgentAsSkillTool(ctx: SessionContext): any;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Agent-as-skill tool implementation - invokes another agent as a skill
|
|
2
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
3
|
+
import { sendCommand } from "../formatter.js";
|
|
4
|
+
import { getCurrentTaskId } from "../task-manager.js";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Agent-as-skill tool - invokes a registered agent by agentId as a skill.
|
|
8
|
+
* The tool receives the agentId, query, and optional file attachments,
|
|
9
|
+
* forwards the request to the target agent via WebSocket, and returns the result.
|
|
10
|
+
*/
|
|
11
|
+
export function createAgentAsSkillTool(ctx) {
|
|
12
|
+
const { config, sessionId, taskId, messageId } = ctx;
|
|
13
|
+
return {
|
|
14
|
+
name: "agent-as-skill-tool",
|
|
15
|
+
label: "Agent as Skill Tool",
|
|
16
|
+
description: `智能体作为skill的执行元工具。当需要调用其他已注册的Agent来执行特定任务时使用此工具。
|
|
17
|
+
该工具会将用户请求和可选的附件文件转发给目标Agent执行,并返回执行结果。
|
|
18
|
+
|
|
19
|
+
使用场景:
|
|
20
|
+
- 需要调用其他Agent完成特定领域的任务
|
|
21
|
+
- 需要将文件/图片交给专门的Agent处理
|
|
22
|
+
- 需要组合多个Agent的能力来完成复杂任务
|
|
23
|
+
|
|
24
|
+
注意事项:
|
|
25
|
+
- 操作超时时间为5分钟
|
|
26
|
+
- 该工具执行期间必须严格等待结果返回,不要执行其他操作
|
|
27
|
+
- 如果超时或失败,最多重试一次`,
|
|
28
|
+
parameters: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
agentId: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "待执行的AgentId,精准匹配系统注册的AgentId",
|
|
34
|
+
},
|
|
35
|
+
query: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "用户原始请求文本,原样转发给目标Agent执行",
|
|
38
|
+
},
|
|
39
|
+
filesInfo: {
|
|
40
|
+
type: "array",
|
|
41
|
+
description: "附件文件/图片信息列表,无文件时可传null或空数组",
|
|
42
|
+
items: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
fileType: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "文件类型:file 或 image",
|
|
48
|
+
enum: ["file", "image"],
|
|
49
|
+
},
|
|
50
|
+
fileId: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: "文件全局唯一标识",
|
|
53
|
+
},
|
|
54
|
+
fileUrl: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "文件可访问下载链接(完整HTTP/HTTPS地址)",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ["agentId", "query"],
|
|
63
|
+
},
|
|
64
|
+
async execute(toolCallId, params) {
|
|
65
|
+
// Dynamic lookup: use latest taskId from task-manager (handles steer/interrupt)
|
|
66
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
67
|
+
// Validate parameters
|
|
68
|
+
if (!params.agentId || typeof params.agentId !== "string") {
|
|
69
|
+
throw new Error("Missing or invalid required parameter: agentId must be a non-empty string");
|
|
70
|
+
}
|
|
71
|
+
if (!params.query || typeof params.query !== "string") {
|
|
72
|
+
throw new Error("Missing or invalid required parameter: query must be a non-empty string");
|
|
73
|
+
}
|
|
74
|
+
// Get WebSocket manager
|
|
75
|
+
const wsManager = getXYWebSocketManager(config);
|
|
76
|
+
// Build ExecuteAgentAsSkill command
|
|
77
|
+
const command = {
|
|
78
|
+
header: {
|
|
79
|
+
namespace: "System",
|
|
80
|
+
name: "ExecuteAgentAsSkill",
|
|
81
|
+
},
|
|
82
|
+
payload: {
|
|
83
|
+
agentId: params.agentId,
|
|
84
|
+
query: params.query,
|
|
85
|
+
filesInfo: params.filesInfo || null,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
// Send command and wait for response (5 minute timeout)
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
const timeout = setTimeout(() => {
|
|
91
|
+
wsManager.off("agent-as-skill-response", handler);
|
|
92
|
+
logger.error("超时: Agent-as-Skill 操作超时(5分钟)", { agentId: params.agentId, toolCallId });
|
|
93
|
+
reject(new Error("Agent-as-Skill 操作超时(5分钟)"));
|
|
94
|
+
}, 300000); // 5 minutes timeout
|
|
95
|
+
// Listen for Agent-as-Skill response events
|
|
96
|
+
const handler = (event) => {
|
|
97
|
+
// Check if this is the ExecuteAgentAsSkillResponse we're waiting for
|
|
98
|
+
if (event.header?.namespace === "System" &&
|
|
99
|
+
event.header?.name === "ExecuteAgentAsSkillResponse") {
|
|
100
|
+
clearTimeout(timeout);
|
|
101
|
+
wsManager.off("agent-as-skill-response", handler);
|
|
102
|
+
// Return the payload directly as the tool result
|
|
103
|
+
const payload = event.payload;
|
|
104
|
+
if (payload) {
|
|
105
|
+
resolve({
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: typeof payload === "string" ? payload : JSON.stringify(payload),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
reject(new Error("Agent-as-Skill 响应格式错误:缺少 payload"));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
// Register event handler
|
|
120
|
+
wsManager.on("agent-as-skill-response", handler);
|
|
121
|
+
// Send the command
|
|
122
|
+
sendCommand({
|
|
123
|
+
config,
|
|
124
|
+
sessionId,
|
|
125
|
+
taskId: currentTaskId,
|
|
126
|
+
messageId,
|
|
127
|
+
command,
|
|
128
|
+
}).then(() => {
|
|
129
|
+
logger.log("[AGENT-AS-SKILL] Command sent successfully", { agentId: params.agentId });
|
|
130
|
+
}).catch((error) => {
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
wsManager.off("agent-as-skill-response", handler);
|
|
133
|
+
reject(error);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -87,7 +87,7 @@ export function createCalendarTool(ctx) {
|
|
|
87
87
|
return new Promise((resolve, reject) => {
|
|
88
88
|
const timeout = setTimeout(() => {
|
|
89
89
|
wsManager.off("data-event", handler);
|
|
90
|
-
logger.error("超时: 创建日程超时(60秒)", {
|
|
90
|
+
logger.error("超时: 创建日程超时(60秒)", { toolCallId });
|
|
91
91
|
reject(new Error("创建日程超时(60秒)"));
|
|
92
92
|
}, 60000);
|
|
93
93
|
// Listen for data events from WebSocket
|
|
@@ -22,6 +22,7 @@ import { createUploadFileTool } from "./upload-file-tool.js";
|
|
|
22
22
|
import { createSaveFileToPhoneTool } from "./save-file-to-phone-tool.js";
|
|
23
23
|
import { createSendEmailTool } from "./send-email-tool.js";
|
|
24
24
|
import { createSearchEmailTool } from "./search-email-tool.js";
|
|
25
|
+
import { createFindPcDevicesTool } from "./find-pc-devices-tool.js";
|
|
25
26
|
import { sendStatusUpdate } from "../formatter.js";
|
|
26
27
|
import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
|
|
27
28
|
/**
|
|
@@ -54,6 +55,7 @@ export function createCallDeviceTool(ctx) {
|
|
|
54
55
|
const searchFileTool = createSearchFileTool(ctx);
|
|
55
56
|
const saveFileToPhoneTool = createSaveFileToPhoneTool(ctx);
|
|
56
57
|
const searchEmailTool = createSearchEmailTool(ctx);
|
|
58
|
+
const findPcDevicesTool = createFindPcDevicesTool(ctx);
|
|
57
59
|
/**
|
|
58
60
|
* 端工具注册表 —— 按 name 索引所有可通过 call_device_tool 调度的工具。
|
|
59
61
|
*/
|
|
@@ -82,6 +84,7 @@ export function createCallDeviceTool(ctx) {
|
|
|
82
84
|
[saveFileToPhoneTool.name, saveFileToPhoneTool],
|
|
83
85
|
[sendEmailTool.name, sendEmailTool],
|
|
84
86
|
[searchEmailTool.name, searchEmailTool],
|
|
87
|
+
[findPcDevicesTool.name, findPcDevicesTool],
|
|
85
88
|
]);
|
|
86
89
|
return {
|
|
87
90
|
name: "call_device_tool",
|
|
@@ -79,7 +79,7 @@ export function createCallPhoneTool(ctx) {
|
|
|
79
79
|
return new Promise((resolve, reject) => {
|
|
80
80
|
const timeout = setTimeout(() => {
|
|
81
81
|
wsManager.off("data-event", handler);
|
|
82
|
-
logger.error("超时: 拨打电话超时(60秒)", {
|
|
82
|
+
logger.error("超时: 拨打电话超时(60秒)", { toolCallId });
|
|
83
83
|
reject(new Error("拨打电话超时(60秒)"));
|
|
84
84
|
}, 60000);
|
|
85
85
|
// Listen for data events from WebSocket
|
|
@@ -224,7 +224,7 @@ b. 使用该工具之前需获取当前真实时间
|
|
|
224
224
|
return new Promise((resolve, reject) => {
|
|
225
225
|
const timeout = setTimeout(() => {
|
|
226
226
|
wsManager.off("data-event", handler);
|
|
227
|
-
logger.error("超时: 创建闹钟超时(60秒)", {
|
|
227
|
+
logger.error("超时: 创建闹钟超时(60秒)", { toolCallId });
|
|
228
228
|
reject(new Error("创建闹钟超时(60秒)"));
|
|
229
229
|
}, 60000);
|
|
230
230
|
// Listen for data events from WebSocket
|
|
@@ -15,6 +15,8 @@ import { createGetAlarmToolSchemaTool } from "./get-alarm-tool-schema.js";
|
|
|
15
15
|
import { createGetCollectionToolSchemaTool } from "./get-collection-tool-schema.js";
|
|
16
16
|
import { createGetEmailToolSchemaTool } from "./get-email-tool-schema.js";
|
|
17
17
|
import { createLoginTokenTool } from "./login-token-tool.js";
|
|
18
|
+
import { createAgentAsSkillTool } from "./agent-as-skill-tool.js";
|
|
19
|
+
import { createFindPcDevicesTool } from "./find-pc-devices-tool.js";
|
|
18
20
|
import { logger } from "../utils/logger.js";
|
|
19
21
|
/**
|
|
20
22
|
* Create all XY channel tools for the given session context.
|
|
@@ -27,7 +29,7 @@ export function createAllTools(ctx) {
|
|
|
27
29
|
logger.log("[CREATE-ALL-TOOLS] no session context, returning empty tools list");
|
|
28
30
|
return [];
|
|
29
31
|
}
|
|
30
|
-
logger.log(`[CREATE-ALL-TOOLS] creating tools
|
|
32
|
+
logger.log(`[CREATE-ALL-TOOLS] creating tools`);
|
|
31
33
|
return [
|
|
32
34
|
createLocationTool(ctx),
|
|
33
35
|
createCallDeviceTool(ctx),
|
|
@@ -46,5 +48,7 @@ export function createAllTools(ctx) {
|
|
|
46
48
|
timestampToUtc8Tool,
|
|
47
49
|
createSaveSelfEvolutionSkillTool(ctx),
|
|
48
50
|
createLoginTokenTool(ctx),
|
|
51
|
+
createAgentAsSkillTool(ctx),
|
|
52
|
+
createFindPcDevicesTool(ctx),
|
|
49
53
|
];
|
|
50
54
|
}
|
|
@@ -123,7 +123,7 @@ export function createDeleteAlarmTool(ctx) {
|
|
|
123
123
|
return new Promise((resolve, reject) => {
|
|
124
124
|
const timeout = setTimeout(() => {
|
|
125
125
|
wsManager.off("data-event", handler);
|
|
126
|
-
logger.error("超时: 删除闹钟超时(60秒)", {
|
|
126
|
+
logger.error("超时: 删除闹钟超时(60秒)", { toolCallId });
|
|
127
127
|
reject(new Error("删除闹钟超时(60秒)"));
|
|
128
128
|
}, 60000);
|
|
129
129
|
// Listen for data events from WebSocket
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { SessionContext } from "./session-manager.js";
|
|
1
2
|
/**
|
|
2
3
|
* XY find PC devices tool - finds all PC devices associated with the user.
|
|
3
4
|
* Returns device IDs for use in subsequent file search operations.
|
|
4
5
|
*/
|
|
5
|
-
export declare
|
|
6
|
+
export declare function createFindPcDevicesTool(ctx: SessionContext): any;
|