@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.
Files changed (104) hide show
  1. package/dist/index.js +26 -69
  2. package/dist/src/approval-bridge.d.ts +48 -0
  3. package/dist/src/approval-bridge.js +382 -0
  4. package/dist/src/bot.js +132 -73
  5. package/dist/src/channel.js +59 -5
  6. package/dist/src/client.js +13 -23
  7. package/dist/src/cron-command.d.ts +15 -0
  8. package/dist/src/cron-command.js +49 -0
  9. package/dist/src/cron-query-handler.d.ts +7 -0
  10. package/dist/src/cron-query-handler.js +189 -0
  11. package/dist/src/cspl/call_api.d.ts +2 -0
  12. package/dist/src/cspl/call_api.js +107 -0
  13. package/dist/src/cspl/config.d.ts +4 -17
  14. package/dist/src/cspl/config.js +100 -70
  15. package/dist/src/cspl/configs.json +10 -0
  16. package/dist/src/cspl/constants.d.ts +49 -24
  17. package/dist/src/cspl/constants.js +46 -16
  18. package/dist/src/cspl/sentinel_hook.d.ts +2 -0
  19. package/dist/src/cspl/sentinel_hook.js +103 -0
  20. package/dist/src/cspl/steer-context.js +1 -1
  21. package/dist/src/cspl/upload_file.d.ts +1 -0
  22. package/dist/src/cspl/upload_file.js +211 -0
  23. package/dist/src/cspl/utils.d.ts +17 -2
  24. package/dist/src/cspl/utils.js +271 -15
  25. package/dist/src/file-upload.d.ts +5 -0
  26. package/dist/src/file-upload.js +102 -0
  27. package/dist/src/formatter.d.ts +43 -1
  28. package/dist/src/formatter.js +171 -41
  29. package/dist/src/monitor.js +64 -43
  30. package/dist/src/outbound.js +8 -9
  31. package/dist/src/parser.d.ts +8 -1
  32. package/dist/src/parser.js +71 -0
  33. package/dist/src/provider.js +51 -17
  34. package/dist/src/push.d.ts +11 -1
  35. package/dist/src/push.js +101 -17
  36. package/dist/src/reply-dispatcher.js +152 -59
  37. package/dist/src/self-evolution-handler.d.ts +1 -1
  38. package/dist/src/self-evolution-handler.js +14 -3
  39. package/dist/src/sensitive-redactor.d.ts +4 -0
  40. package/dist/src/sensitive-redactor.js +364 -0
  41. package/dist/src/task-manager.js +6 -10
  42. package/dist/src/tools/agent-as-skill-tool.d.ts +7 -0
  43. package/dist/src/tools/agent-as-skill-tool.js +190 -0
  44. package/dist/src/tools/calendar-tool.js +3 -2
  45. package/dist/src/tools/call-phone-tool.js +3 -2
  46. package/dist/src/tools/check-plugin-privilege-tool.d.ts +6 -0
  47. package/dist/src/tools/check-plugin-privilege-tool.js +182 -0
  48. package/dist/src/tools/create-alarm-tool.js +3 -2
  49. package/dist/src/tools/create-all-tools.js +11 -3
  50. package/dist/src/tools/delete-alarm-tool.js +3 -2
  51. package/dist/src/tools/device-tool-map.d.ts +1 -1
  52. package/dist/src/tools/device-tool-map.js +12 -5
  53. package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -0
  54. package/dist/src/tools/discover-cross-devices-tool.js +235 -0
  55. package/dist/src/tools/display-a2ui-card-tool.d.ts +2 -0
  56. package/dist/src/tools/display-a2ui-card-tool.js +85 -0
  57. package/dist/src/tools/find-pc-devices-tool.d.ts +2 -1
  58. package/dist/src/tools/find-pc-devices-tool.js +85 -88
  59. package/dist/src/tools/get-collection-tool-schema.js +1 -1
  60. package/dist/src/tools/location-tool.js +3 -2
  61. package/dist/src/tools/modify-alarm-tool.js +3 -2
  62. package/dist/src/tools/modify-note-tool.js +3 -2
  63. package/dist/src/tools/note-tool.js +3 -2
  64. package/dist/src/tools/query-app-message-tool.js +4 -3
  65. package/dist/src/tools/query-memory-data-tool.js +4 -3
  66. package/dist/src/tools/query-todo-task-tool.js +4 -3
  67. package/dist/src/tools/save-file-to-phone-tool.js +3 -2
  68. package/dist/src/tools/save-media-to-gallery-tool.js +3 -2
  69. package/dist/src/tools/schema-tool-factory.js +1 -1
  70. package/dist/src/tools/search-alarm-tool.js +3 -2
  71. package/dist/src/tools/search-calendar-tool.js +3 -2
  72. package/dist/src/tools/search-contact-tool.js +3 -2
  73. package/dist/src/tools/search-email-tool.js +4 -3
  74. package/dist/src/tools/search-file-tool.js +8 -9
  75. package/dist/src/tools/search-message-tool.js +2 -1
  76. package/dist/src/tools/search-note-tool.js +3 -2
  77. package/dist/src/tools/search-photo-gallery-tool.js +5 -4
  78. package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -0
  79. package/dist/src/tools/send-cross-device-task-tool.js +299 -0
  80. package/dist/src/tools/send-email-tool.js +4 -3
  81. package/dist/src/tools/send-file-to-user-tool.d.ts +1 -1
  82. package/dist/src/tools/send-file-to-user-tool.js +37 -8
  83. package/dist/src/tools/send-html-card-tool.d.ts +7 -0
  84. package/dist/src/tools/send-html-card-tool.js +113 -0
  85. package/dist/src/tools/send-message-tool.js +2 -1
  86. package/dist/src/tools/session-manager.d.ts +17 -1
  87. package/dist/src/tools/session-manager.js +87 -1
  88. package/dist/src/tools/upload-file-tool.js +9 -7
  89. package/dist/src/tools/upload-photo-tool.js +5 -4
  90. package/dist/src/tools/xiaoyi-add-collection-tool.js +5 -3
  91. package/dist/src/tools/xiaoyi-collection-tool.js +4 -3
  92. package/dist/src/tools/xiaoyi-delete-collection-tool.js +4 -3
  93. package/dist/src/tools/xiaoyi-gui-tool.js +8 -2
  94. package/dist/src/trigger-handler.js +4 -7
  95. package/dist/src/types.d.ts +25 -1
  96. package/dist/src/utils/config-manager.js +3 -6
  97. package/dist/src/utils/logger.d.ts +8 -0
  98. package/dist/src/utils/logger.js +69 -34
  99. package/dist/src/utils/pushdata-manager.js +1 -5
  100. package/dist/src/utils/pushid-manager.js +1 -2
  101. package/dist/src/utils/runtime-manager.js +1 -4
  102. package/dist/src/websocket.d.ts +3 -0
  103. package/dist/src/websocket.js +242 -38
  104. package/package.json +1 -1
@@ -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] Push data saved with ID: ${pushDataId.substring(0, 20)}`);
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] Failed to save push data:`, error);
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] Loaded ${pushIdList.length} pushIds`);
121
+ logger.log(`[xyOutbound.sendText] Loaded ${pushIdList.length} pushIds`);
122
122
  }
123
123
  catch (error) {
124
- logger.error(`[xyOutbound.sendText] Failed to load pushIds:`, error);
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] ⚠️ No pushIds found, falling back to config pushId`);
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] 📤 Broadcasting to ${pushIdList.length} pushId(s)...`);
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] Sent successfully to pushId: ${pushId.substring(0, 20)}...`);
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] Failed to send to pushId: ${pushId.substring(0, 20)}...`, error);
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
@@ -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.
@@ -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.
@@ -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) 提取 ## Skills (mandatory) </available_skills> 作为第一部分
540
- const skillsMatch = sp.match(/(## Skills \(mandatory\)[\s\S]*?<\/available_skills>)/);
541
- const part1 = skillsMatch ? skillsMatch[0] : '';
542
- // (2) 提取 ## /home/sandbox/.openclaw/workspace/SOUL.md 到 ## /home/sandbox/.openclaw/workspace/TOOLS.md 之前的内容作为第二部分
543
- const soulMatch = sp.match(/(## \/home\/sandbox\/\.openclaw\/workspace\/SOUL\.md[\s\S]*?)(?=## \/home\/sandbox\/\.openclaw\/workspace\/TOOLS\.md)/);
544
- const part2 = soulMatch ? soulMatch[1].trim() : '';
545
- if (part1 || part2) {
546
- // 从原始位置删除已提取的部分
547
- if (skillsMatch)
548
- sp = sp.replace(skillsMatch[0], '');
549
- if (soulMatch)
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)
@@ -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 DEFAULT_PUSH_URL;
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
- DEFAULT_PUSH_URL = "https://hag.cloud.huawei.com/open-ability-agent/v1/agent-webhook";
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.config.pushUrl || this.DEFAULT_PUSH_URL;
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] 📤 Preparing to send push message`);
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] 📥 Response received`);
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] Push request failed`);
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] ⚠️ Received empty response body`);
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] Failed to parse JSON response`);
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] Push message sent successfully`);
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] - Error name: ${error.name}`);
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] - Error:`, error);
203
+ logger.error(`[PUSH] Failed to send push message:`, error);
120
204
  }
121
205
  throw error;
122
206
  }