@ynhcj/xiaoyi-channel 1.1.26 → 1.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +26 -69
- package/dist/src/approval-bridge.d.ts +48 -0
- package/dist/src/approval-bridge.js +382 -0
- package/dist/src/bot.js +132 -73
- package/dist/src/channel.js +59 -5
- package/dist/src/client.js +13 -23
- package/dist/src/cron-command.d.ts +15 -0
- package/dist/src/cron-command.js +49 -0
- package/dist/src/cron-query-handler.d.ts +7 -0
- package/dist/src/cron-query-handler.js +189 -0
- 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 +100 -70
- package/dist/src/cspl/configs.json +10 -0
- package/dist/src/cspl/constants.d.ts +49 -24
- package/dist/src/cspl/constants.js +46 -16
- package/dist/src/cspl/sentinel_hook.d.ts +2 -0
- package/dist/src/cspl/sentinel_hook.js +103 -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 +17 -2
- package/dist/src/cspl/utils.js +271 -15
- package/dist/src/file-upload.d.ts +5 -0
- package/dist/src/file-upload.js +102 -0
- package/dist/src/formatter.d.ts +43 -1
- package/dist/src/formatter.js +171 -41
- package/dist/src/monitor.js +64 -43
- package/dist/src/outbound.js +8 -9
- package/dist/src/parser.d.ts +8 -1
- package/dist/src/parser.js +71 -0
- package/dist/src/provider.js +51 -17
- package/dist/src/push.d.ts +11 -1
- package/dist/src/push.js +101 -17
- package/dist/src/reply-dispatcher.js +152 -59
- package/dist/src/self-evolution-handler.d.ts +1 -1
- package/dist/src/self-evolution-handler.js +14 -3
- 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 +190 -0
- package/dist/src/tools/calendar-tool.js +3 -2
- package/dist/src/tools/call-phone-tool.js +3 -2
- package/dist/src/tools/check-plugin-privilege-tool.d.ts +6 -0
- package/dist/src/tools/check-plugin-privilege-tool.js +182 -0
- package/dist/src/tools/create-alarm-tool.js +3 -2
- package/dist/src/tools/create-all-tools.js +11 -3
- package/dist/src/tools/delete-alarm-tool.js +3 -2
- package/dist/src/tools/device-tool-map.d.ts +1 -1
- package/dist/src/tools/device-tool-map.js +12 -5
- package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -0
- package/dist/src/tools/discover-cross-devices-tool.js +235 -0
- package/dist/src/tools/display-a2ui-card-tool.d.ts +2 -0
- package/dist/src/tools/display-a2ui-card-tool.js +85 -0
- package/dist/src/tools/find-pc-devices-tool.d.ts +2 -1
- package/dist/src/tools/find-pc-devices-tool.js +85 -88
- package/dist/src/tools/get-collection-tool-schema.js +1 -1
- package/dist/src/tools/location-tool.js +3 -2
- package/dist/src/tools/modify-alarm-tool.js +3 -2
- package/dist/src/tools/modify-note-tool.js +3 -2
- package/dist/src/tools/note-tool.js +3 -2
- package/dist/src/tools/query-app-message-tool.js +4 -3
- package/dist/src/tools/query-memory-data-tool.js +4 -3
- package/dist/src/tools/query-todo-task-tool.js +4 -3
- package/dist/src/tools/save-file-to-phone-tool.js +3 -2
- package/dist/src/tools/save-media-to-gallery-tool.js +3 -2
- package/dist/src/tools/schema-tool-factory.js +1 -1
- package/dist/src/tools/search-alarm-tool.js +3 -2
- package/dist/src/tools/search-calendar-tool.js +3 -2
- package/dist/src/tools/search-contact-tool.js +3 -2
- package/dist/src/tools/search-email-tool.js +4 -3
- package/dist/src/tools/search-file-tool.js +8 -9
- package/dist/src/tools/search-message-tool.js +2 -1
- package/dist/src/tools/search-note-tool.js +3 -2
- package/dist/src/tools/search-photo-gallery-tool.js +5 -4
- package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -0
- package/dist/src/tools/send-cross-device-task-tool.js +299 -0
- package/dist/src/tools/send-email-tool.js +4 -3
- package/dist/src/tools/send-file-to-user-tool.d.ts +1 -1
- package/dist/src/tools/send-file-to-user-tool.js +37 -8
- package/dist/src/tools/send-html-card-tool.d.ts +7 -0
- package/dist/src/tools/send-html-card-tool.js +113 -0
- package/dist/src/tools/send-message-tool.js +2 -1
- package/dist/src/tools/session-manager.d.ts +17 -1
- package/dist/src/tools/session-manager.js +87 -1
- package/dist/src/tools/upload-file-tool.js +9 -7
- package/dist/src/tools/upload-photo-tool.js +5 -4
- package/dist/src/tools/xiaoyi-add-collection-tool.js +5 -3
- package/dist/src/tools/xiaoyi-collection-tool.js +4 -3
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +4 -3
- package/dist/src/tools/xiaoyi-gui-tool.js +8 -2
- package/dist/src/trigger-handler.js +4 -7
- package/dist/src/types.d.ts +25 -1
- 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.d.ts +3 -0
- package/dist/src/websocket.js +242 -38
- package/package.json +1 -1
package/dist/src/outbound.js
CHANGED
|
@@ -106,10 +106,10 @@ export const xyOutbound = {
|
|
|
106
106
|
let pushDataId;
|
|
107
107
|
try {
|
|
108
108
|
pushDataId = await savePushData(text);
|
|
109
|
-
logger.log(`[xyOutbound.sendText]
|
|
109
|
+
logger.log(`[xyOutbound.sendText] Push data saved with ID: ${pushDataId.substring(0, 20)}`);
|
|
110
110
|
}
|
|
111
111
|
catch (error) {
|
|
112
|
-
logger.error(`[xyOutbound.sendText]
|
|
112
|
+
logger.error(`[xyOutbound.sendText] Failed to save push data:`, error);
|
|
113
113
|
// 如果持久化失败,仍然继续发送(不阻塞主流程)
|
|
114
114
|
pushDataId = "";
|
|
115
115
|
}
|
|
@@ -118,14 +118,14 @@ export const xyOutbound = {
|
|
|
118
118
|
let pushIdList = [];
|
|
119
119
|
try {
|
|
120
120
|
pushIdList = await getAllPushIds();
|
|
121
|
-
logger.log(`[xyOutbound.sendText]
|
|
121
|
+
logger.log(`[xyOutbound.sendText] Loaded ${pushIdList.length} pushIds`);
|
|
122
122
|
}
|
|
123
123
|
catch (error) {
|
|
124
|
-
logger.error(`[xyOutbound.sendText]
|
|
124
|
+
logger.error(`[xyOutbound.sendText] Failed to load pushIds:`, error);
|
|
125
125
|
}
|
|
126
126
|
// 3. 如果 pushIdList 为空,回退到原有逻辑(使用 config pushId)
|
|
127
127
|
if (pushIdList.length === 0) {
|
|
128
|
-
logger.log(`[xyOutbound.sendText]
|
|
128
|
+
logger.log(`[xyOutbound.sendText] No pushIds found, falling back to config pushId`);
|
|
129
129
|
pushIdList = [config.pushId];
|
|
130
130
|
}
|
|
131
131
|
// Create push service
|
|
@@ -135,7 +135,7 @@ export const xyOutbound = {
|
|
|
135
135
|
// Truncate push content to max length 1000
|
|
136
136
|
const pushText = text.length > 1000 ? text.slice(0, 1000) : text;
|
|
137
137
|
// 4. 遍历所有 pushId,依次发送推送通知
|
|
138
|
-
logger.log(`[xyOutbound.sendText]
|
|
138
|
+
logger.log(`[xyOutbound.sendText] Broadcasting to ${pushIdList.length} pushId(s)...`);
|
|
139
139
|
let successCount = 0;
|
|
140
140
|
let failureCount = 0;
|
|
141
141
|
for (const pushId of pushIdList) {
|
|
@@ -143,11 +143,11 @@ export const xyOutbound = {
|
|
|
143
143
|
// 传入 pushId 和 pushDataId,使用 kind="data" 格式
|
|
144
144
|
await pushService.sendPush(pushText, title, undefined, actualTo, pushDataId, pushId);
|
|
145
145
|
successCount++;
|
|
146
|
-
logger.log(`[xyOutbound.sendText]
|
|
146
|
+
logger.log(`[xyOutbound.sendText] Sent successfully to pushId: ${pushId.substring(0, 20)}...`);
|
|
147
147
|
}
|
|
148
148
|
catch (error) {
|
|
149
149
|
failureCount++;
|
|
150
|
-
logger.error(`[xyOutbound.sendText]
|
|
150
|
+
logger.error(`[xyOutbound.sendText] Failed to send to pushId: ${pushId.substring(0, 20)}...`, error);
|
|
151
151
|
// 单个 pushId 发送失败不影响其他,继续处理下一个
|
|
152
152
|
}
|
|
153
153
|
}
|
|
@@ -184,7 +184,6 @@ export const xyOutbound = {
|
|
|
184
184
|
}
|
|
185
185
|
logger.log(`[xyOutbound.sendMedia] File uploaded:`, {
|
|
186
186
|
fileId,
|
|
187
|
-
sessionId,
|
|
188
187
|
taskId,
|
|
189
188
|
});
|
|
190
189
|
// Get filename and mime type from mediaUrl
|
package/dist/src/parser.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { A2AJsonRpcRequest, A2AMessagePart, A2ADataEvent } from "./types.js";
|
|
1
|
+
import type { A2AJsonRpcRequest, A2AMessagePart, A2ADataEvent, RunCrossTaskContext } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Parsed message information extracted from A2A request.
|
|
4
4
|
* Note: agentId is not extracted from message - it should come from config.
|
|
@@ -30,6 +30,7 @@ export declare function extractFileParts(parts: A2AMessagePart[]): Array<{
|
|
|
30
30
|
* Extract data events from message parts (for tool responses).
|
|
31
31
|
*/
|
|
32
32
|
export declare function extractDataEvents(parts: A2AMessagePart[]): A2ADataEvent[];
|
|
33
|
+
export declare function extractRunCrossTaskContext(parts: A2AMessagePart[]): RunCrossTaskContext | null;
|
|
33
34
|
/**
|
|
34
35
|
* Check if message is a clearContext request.
|
|
35
36
|
*/
|
|
@@ -49,6 +50,12 @@ export declare function extractPushId(parts: A2AMessagePart[]): string | null;
|
|
|
49
50
|
* (same level as push_id).
|
|
50
51
|
*/
|
|
51
52
|
export declare function extractDeviceType(parts: A2AMessagePart[]): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Extract modelName from message parts.
|
|
55
|
+
* Looks for modelName in data parts under variables.clientVariables.modelName
|
|
56
|
+
* (same level as systemVariables).
|
|
57
|
+
*/
|
|
58
|
+
export declare function extractModelName(parts: A2AMessagePart[]): string | null;
|
|
52
59
|
/**
|
|
53
60
|
* Extract Trigger event data from message parts.
|
|
54
61
|
* Looks for Trigger events with pushDataId in data parts.
|
package/dist/src/parser.js
CHANGED
|
@@ -45,6 +45,61 @@ export function extractDataEvents(parts) {
|
|
|
45
45
|
.map((part) => part.data.event)
|
|
46
46
|
.filter((event) => event !== undefined);
|
|
47
47
|
}
|
|
48
|
+
export function extractRunCrossTaskContext(parts) {
|
|
49
|
+
const normalizeSentFiles = (value) => {
|
|
50
|
+
if (!Array.isArray(value)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return value
|
|
54
|
+
.map((item) => {
|
|
55
|
+
if (!item || typeof item !== "object") {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const candidate = item;
|
|
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) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
|
|
73
|
+
...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
|
|
74
|
+
...(fileNames.length > 0 && fileNames.length === fileRemoteUrls.length ? { fileNames } : {}),
|
|
75
|
+
};
|
|
76
|
+
})
|
|
77
|
+
.filter((item) => item !== null);
|
|
78
|
+
};
|
|
79
|
+
for (const part of parts) {
|
|
80
|
+
if (part.kind !== "data" || !part.data) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const context = part.data.runCrossTaskContext;
|
|
84
|
+
if (!context || typeof context !== "object") {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const networkId = typeof context.networkId === "string" ? context.networkId : "";
|
|
88
|
+
if (!networkId) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
agentId: typeof context.agentId === "string" ? context.agentId : "",
|
|
93
|
+
sessionId: typeof context.sessionId === "string" ? context.sessionId : "",
|
|
94
|
+
isDistributed: context.isDistributed === true,
|
|
95
|
+
networkId,
|
|
96
|
+
isSupportAgent: context.isSupportAgent !== false,
|
|
97
|
+
sentFiles: normalizeSentFiles(context.sentFiles),
|
|
98
|
+
rawContext: context,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
48
103
|
/**
|
|
49
104
|
* Check if message is a clearContext request.
|
|
50
105
|
*/
|
|
@@ -88,6 +143,22 @@ export function extractDeviceType(parts) {
|
|
|
88
143
|
}
|
|
89
144
|
return null;
|
|
90
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Extract modelName from message parts.
|
|
148
|
+
* Looks for modelName in data parts under variables.clientVariables.modelName
|
|
149
|
+
* (same level as systemVariables).
|
|
150
|
+
*/
|
|
151
|
+
export function extractModelName(parts) {
|
|
152
|
+
for (const part of parts) {
|
|
153
|
+
if (part.kind === "data" && part.data) {
|
|
154
|
+
const modelName = part.data.variables?.clientVariables?.modelName;
|
|
155
|
+
if (modelName && typeof modelName === "string" && modelName.trim() !== "" && modelName.toLowerCase() !== "none") {
|
|
156
|
+
return modelName;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
91
162
|
/**
|
|
92
163
|
* Extract Trigger event data from message parts.
|
|
93
164
|
* Looks for Trigger events with pushDataId in data parts.
|
package/dist/src/provider.js
CHANGED
|
@@ -417,6 +417,32 @@ export const xiaoyiProvider = {
|
|
|
417
417
|
docsPath: "/providers/models",
|
|
418
418
|
auth: [],
|
|
419
419
|
isCacheTtlEligible: () => true,
|
|
420
|
+
/**
|
|
421
|
+
* Dynamic model resolution for A2A-specified model names.
|
|
422
|
+
* A2A messages carry a dynamic modelName that isn't in any static catalog.
|
|
423
|
+
* This hook lets OpenClaw's resolveModelAsync accept any model ID under
|
|
424
|
+
* xiaoyiprovider as long as the provider has a configured baseUrl.
|
|
425
|
+
*/
|
|
426
|
+
resolveDynamicModel: (ctx) => {
|
|
427
|
+
const baseUrl = ctx.providerConfig?.baseUrl;
|
|
428
|
+
if (!baseUrl || typeof baseUrl !== "string")
|
|
429
|
+
return null;
|
|
430
|
+
return {
|
|
431
|
+
id: ctx.modelId,
|
|
432
|
+
name: ctx.modelId,
|
|
433
|
+
api: ctx.providerConfig?.api ?? "openai-completions",
|
|
434
|
+
provider: "xiaoyiprovider",
|
|
435
|
+
baseUrl,
|
|
436
|
+
reasoning: false,
|
|
437
|
+
input: ["text"],
|
|
438
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
439
|
+
contextWindow: 256_000,
|
|
440
|
+
maxTokens: 8192,
|
|
441
|
+
...(ctx.providerConfig?.headers && typeof ctx.providerConfig.headers === "object"
|
|
442
|
+
? { headers: ctx.providerConfig.headers }
|
|
443
|
+
: {}),
|
|
444
|
+
};
|
|
445
|
+
},
|
|
420
446
|
/**
|
|
421
447
|
* Store uid-based fallback prefix for lazy timestamp generation in wrapStreamFn.
|
|
422
448
|
* Session-level headers (traceId / sessionId / interactionId) are resolved
|
|
@@ -536,26 +562,28 @@ export const xiaoyiProvider = {
|
|
|
536
562
|
const beforeLen = sp.length;
|
|
537
563
|
// 删除 ## Tooling 与 TOOLS.md 声明之间的内容
|
|
538
564
|
sp = sp.replace(/(## Tooling)[\s\S]*?(TOOLS\.md does not control tool availability; it is user guidance for how to use external tools\.)/, "$1\n\n$2");
|
|
539
|
-
// (1)
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
565
|
+
// (1) Skills 部分:移动到 ## Runtime 之前
|
|
566
|
+
if (sp.includes('## Runtime')) {
|
|
567
|
+
// 提取 ## Skills (mandatory) 到 </available_skills> 作为第一部分
|
|
568
|
+
const skillsMatch = sp.match(/(## Skills \(mandatory\)[\s\S]*?<\/available_skills>)/);
|
|
569
|
+
if (skillsMatch) {
|
|
570
|
+
const part1 = skillsMatch[0];
|
|
571
|
+
sp = sp.replace(part1, '');
|
|
572
|
+
sp = sp.replace('## Runtime', part1 + '\n\n## Runtime');
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// (2) SOUL.md 部分:移动到 ## Silent Replies 之前
|
|
576
|
+
if (sp.includes('## Silent Replies')) {
|
|
577
|
+
// 提取 ## /home/sandbox/.openclaw/workspace/SOUL.md 到 其特定脚注结束标志 的内容作为第二部分
|
|
578
|
+
const soulMatch = sp.match(/(## \/home\/sandbox\/\.openclaw\/workspace\/SOUL\.md[\s\S]*?_This file is yours to evolve\. As you learn who you are, update it\._)/);
|
|
579
|
+
if (soulMatch) {
|
|
580
|
+
const part2 = soulMatch[1].trim();
|
|
550
581
|
sp = sp.replace(soulMatch[1], '');
|
|
551
|
-
|
|
552
|
-
sp = sp.replace(/\n{3,}/g, '\n\n');
|
|
553
|
-
// (3) 将 第二部分 + 第一部分 插入到 ## Runtime 上面
|
|
554
|
-
const combined = (part2 + '\n\n' + part1).trim();
|
|
555
|
-
if (combined && sp.includes('## Runtime')) {
|
|
556
|
-
sp = sp.replace('## Runtime', combined + '\n\n## Runtime');
|
|
582
|
+
sp = sp.replace('## Silent Replies', part2 + '\n\n## Silent Replies');
|
|
557
583
|
}
|
|
558
584
|
}
|
|
585
|
+
// 清理多余空行
|
|
586
|
+
sp = sp.replace(/\n{3,}/g, '\n\n');
|
|
559
587
|
logger.log(`[xiaoyiprovider] system prompt optimized: ${beforeLen} -> ${sp.length}`);
|
|
560
588
|
context.systemPrompt = sp;
|
|
561
589
|
}
|
|
@@ -585,6 +613,12 @@ export const xiaoyiProvider = {
|
|
|
585
613
|
}
|
|
586
614
|
}
|
|
587
615
|
}
|
|
616
|
+
// ── Override model.id if A2A message specified modelName ──
|
|
617
|
+
const modelNameOverride = getCurrentSessionContext()?.modelName;
|
|
618
|
+
if (modelNameOverride && modelNameOverride.trim() !== "" && modelNameOverride.toLowerCase() !== "none") {
|
|
619
|
+
logger.log(`[xiaoyiprovider] overriding model.id: ${model.id} → ${modelNameOverride}`);
|
|
620
|
+
model = { ...model, id: modelNameOverride };
|
|
621
|
+
}
|
|
588
622
|
// ── Retry-capable streaming ──────────────────────────────
|
|
589
623
|
const cronJob = isCronTriggered(context.messages);
|
|
590
624
|
if (cronJob)
|
package/dist/src/push.d.ts
CHANGED
|
@@ -5,9 +5,14 @@ import type { XYChannelConfig } from "./types.js";
|
|
|
5
5
|
*/
|
|
6
6
|
export declare class XYPushService {
|
|
7
7
|
private config;
|
|
8
|
-
private readonly
|
|
8
|
+
private readonly PROD_PUSH_URL;
|
|
9
|
+
private readonly TEST_PUSH_URL;
|
|
9
10
|
private readonly REQUEST_FROM;
|
|
10
11
|
constructor(config: XYChannelConfig);
|
|
12
|
+
/**
|
|
13
|
+
* Resolve push URL: config.pushUrl > inferred from fileUploadUrl > production default.
|
|
14
|
+
*/
|
|
15
|
+
private resolvePushUrl;
|
|
11
16
|
/**
|
|
12
17
|
* Generate a random trace ID for request tracking.
|
|
13
18
|
*/
|
|
@@ -23,6 +28,11 @@ export declare class XYPushService {
|
|
|
23
28
|
* @param pushId - Push ID to use (required)
|
|
24
29
|
*/
|
|
25
30
|
sendPush(content: string, title: string, data?: Record<string, any>, sessionId?: string, pushDataId?: string, pushId?: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Send a push message with command directives embedded directly.
|
|
33
|
+
* Used for cron-triggered commands where pushText is empty and pushType=101.
|
|
34
|
+
*/
|
|
35
|
+
sendPushWithDirectives(pushId: string, sessionId: string, directives: any[]): Promise<void>;
|
|
26
36
|
/**
|
|
27
37
|
* Send a push message with file attachments.
|
|
28
38
|
*/
|
package/dist/src/push.js
CHANGED
|
@@ -8,11 +8,24 @@ import { logger } from "./utils/logger.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export class XYPushService {
|
|
10
10
|
config;
|
|
11
|
-
|
|
11
|
+
PROD_PUSH_URL = "https://hag.cloud.huawei.com/open-ability-agent/v1/agent-webhook";
|
|
12
|
+
TEST_PUSH_URL = "https://lfhagcp.hwcloudtest.cn:58447/open-ability-agent/v1/agent-webhook";
|
|
12
13
|
REQUEST_FROM = "openclaw";
|
|
13
14
|
constructor(config) {
|
|
14
15
|
this.config = config;
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve push URL: config.pushUrl > inferred from fileUploadUrl > production default.
|
|
19
|
+
*/
|
|
20
|
+
resolvePushUrl() {
|
|
21
|
+
if (this.config.pushUrl) {
|
|
22
|
+
return this.config.pushUrl;
|
|
23
|
+
}
|
|
24
|
+
if (this.config.fileUploadUrl?.includes("lfhagmirror")) {
|
|
25
|
+
return this.TEST_PUSH_URL;
|
|
26
|
+
}
|
|
27
|
+
return this.PROD_PUSH_URL;
|
|
28
|
+
}
|
|
16
29
|
/**
|
|
17
30
|
* Generate a random trace ID for request tracking.
|
|
18
31
|
*/
|
|
@@ -30,12 +43,11 @@ export class XYPushService {
|
|
|
30
43
|
* @param pushId - Push ID to use (required)
|
|
31
44
|
*/
|
|
32
45
|
async sendPush(content, title, data, sessionId, pushDataId, pushId) {
|
|
33
|
-
const pushUrl = this.
|
|
46
|
+
const pushUrl = this.resolvePushUrl();
|
|
34
47
|
const traceId = this.generateTraceId();
|
|
35
48
|
// Use provided pushId or fall back to config pushId
|
|
36
49
|
const actualPushId = pushId || this.config.pushId;
|
|
37
|
-
logger.log(`[PUSH]
|
|
38
|
-
logger.log(`[PUSH] - Using pushId: ${actualPushId.substring(0, 20)}...`);
|
|
50
|
+
logger.log(`[PUSH] Preparing to send push message with pushId: ${actualPushId.substring(0, 20)}...`);
|
|
39
51
|
try {
|
|
40
52
|
const requestBody = {
|
|
41
53
|
jsonrpc: "2.0",
|
|
@@ -81,12 +93,10 @@ export class XYPushService {
|
|
|
81
93
|
body: JSON.stringify(requestBody),
|
|
82
94
|
});
|
|
83
95
|
// Log response status and headers
|
|
84
|
-
logger.log(`[PUSH]
|
|
85
|
-
logger.log(`[PUSH] - HTTP Status: ${response.status} ${response.statusText}`);
|
|
96
|
+
logger.log(`[PUSH] Response received, HTTP Status: ${response.status} ${response.statusText}`);
|
|
86
97
|
if (!response.ok) {
|
|
87
98
|
const errorText = await response.text();
|
|
88
|
-
logger.error(`[PUSH]
|
|
89
|
-
logger.error(`[PUSH] - HTTP Status: ${response.status}`);
|
|
99
|
+
logger.error(`[PUSH] Push request failed, HTTP Status: ${response.status}`);
|
|
90
100
|
throw new Error(`Push failed: HTTP ${response.status} - ${errorText}`);
|
|
91
101
|
}
|
|
92
102
|
// Try to parse JSON response with detailed error handling
|
|
@@ -94,7 +104,85 @@ export class XYPushService {
|
|
|
94
104
|
try {
|
|
95
105
|
const responseText = await response.text();
|
|
96
106
|
if (!responseText || responseText.trim() === '') {
|
|
97
|
-
logger.error(`[PUSH]
|
|
107
|
+
logger.error(`[PUSH] Received empty response body`);
|
|
108
|
+
result = {};
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
result = JSON.parse(responseText);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (parseError) {
|
|
115
|
+
logger.error(`[PUSH] Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
116
|
+
throw new Error(`Invalid JSON response from push service: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
117
|
+
}
|
|
118
|
+
logger.log(`[PUSH] Push message sent successfully, Trace ID: ${traceId}`);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof Error) {
|
|
122
|
+
logger.error(`[PUSH] Failed to send push message: ${error.name} - ${error.message}`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
logger.error(`[PUSH] Failed to send push message:`, error);
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Send a push message with command directives embedded directly.
|
|
132
|
+
* Used for cron-triggered commands where pushText is empty and pushType=101.
|
|
133
|
+
*/
|
|
134
|
+
async sendPushWithDirectives(pushId, sessionId, directives) {
|
|
135
|
+
const pushUrl = this.resolvePushUrl();
|
|
136
|
+
const traceId = this.generateTraceId();
|
|
137
|
+
logger.log(`[PUSH] Preparing to send push with directives, pushId: ${pushId.substring(0, 20)}...`);
|
|
138
|
+
const requestBody = {
|
|
139
|
+
jsonrpc: "2.0",
|
|
140
|
+
id: randomUUID(),
|
|
141
|
+
result: {
|
|
142
|
+
id: randomUUID(),
|
|
143
|
+
apiId: this.config.apiId,
|
|
144
|
+
pushId,
|
|
145
|
+
pushText: "",
|
|
146
|
+
pushType: 101,
|
|
147
|
+
kind: "task",
|
|
148
|
+
sessionId,
|
|
149
|
+
artifacts: [
|
|
150
|
+
{
|
|
151
|
+
artifactId: randomUUID(),
|
|
152
|
+
parts: [
|
|
153
|
+
{
|
|
154
|
+
kind: "data",
|
|
155
|
+
data: { directives },
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
try {
|
|
163
|
+
const response = await fetch(pushUrl, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: {
|
|
166
|
+
"Content-Type": "application/json",
|
|
167
|
+
"Accept": "application/json",
|
|
168
|
+
"x-hag-trace-id": traceId,
|
|
169
|
+
"x-uid": this.config.uid,
|
|
170
|
+
"x-api-key": this.config.apiKey,
|
|
171
|
+
"x-request-from": this.REQUEST_FROM,
|
|
172
|
+
},
|
|
173
|
+
body: JSON.stringify(requestBody),
|
|
174
|
+
});
|
|
175
|
+
logger.log(`[PUSH] Response received, HTTP Status: ${response.status} ${response.statusText}`);
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
const errorText = await response.text();
|
|
178
|
+
logger.error(`[PUSH] Push request failed, HTTP Status: ${response.status}`);
|
|
179
|
+
throw new Error(`Push failed: HTTP ${response.status} - ${errorText}`);
|
|
180
|
+
}
|
|
181
|
+
let result;
|
|
182
|
+
try {
|
|
183
|
+
const responseText = await response.text();
|
|
184
|
+
if (!responseText || responseText.trim() === '') {
|
|
185
|
+
logger.error(`[PUSH] Received empty response body`);
|
|
98
186
|
result = {};
|
|
99
187
|
}
|
|
100
188
|
else {
|
|
@@ -102,21 +190,17 @@ export class XYPushService {
|
|
|
102
190
|
}
|
|
103
191
|
}
|
|
104
192
|
catch (parseError) {
|
|
105
|
-
logger.error(`[PUSH]
|
|
106
|
-
logger.error(`[PUSH] - Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
193
|
+
logger.error(`[PUSH] Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
107
194
|
throw new Error(`Invalid JSON response from push service: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
108
195
|
}
|
|
109
|
-
logger.log(`[PUSH]
|
|
110
|
-
logger.log(`[PUSH] - Trace ID: ${traceId}`);
|
|
196
|
+
logger.log(`[PUSH] Push message sent successfully, Trace ID: ${traceId}`);
|
|
111
197
|
}
|
|
112
198
|
catch (error) {
|
|
113
|
-
logger.error(`[PUSH] ❌ Failed to send push message`);
|
|
114
199
|
if (error instanceof Error) {
|
|
115
|
-
logger.error(`[PUSH]
|
|
116
|
-
logger.error(`[PUSH] - Error message: ${error.message}`);
|
|
200
|
+
logger.error(`[PUSH] Failed to send push message: ${error.name} - ${error.message}`);
|
|
117
201
|
}
|
|
118
202
|
else {
|
|
119
|
-
logger.error(`[PUSH]
|
|
203
|
+
logger.error(`[PUSH] Failed to send push message:`, error);
|
|
120
204
|
}
|
|
121
205
|
throw error;
|
|
122
206
|
}
|