@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/client.js
CHANGED
|
@@ -24,10 +24,10 @@ export function getXYWebSocketManager(config, runtime) {
|
|
|
24
24
|
return cached;
|
|
25
25
|
}
|
|
26
26
|
// Create new manager
|
|
27
|
-
logger.log(`[WS-MANAGER-CACHE]
|
|
27
|
+
logger.log(`[WS-MANAGER-CACHE] Creating new WebSocket manager: ${cacheKey}, total managers before: ${wsManagerCache.size}`);
|
|
28
28
|
cached = new XYWebSocketManager(config, runtime);
|
|
29
29
|
wsManagerCache.set(cacheKey, cached);
|
|
30
|
-
logger.log(`[WS-MANAGER-CACHE]
|
|
30
|
+
logger.log(`[WS-MANAGER-CACHE] Total managers after creation: ${wsManagerCache.size}`);
|
|
31
31
|
return cached;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
@@ -38,13 +38,13 @@ export function removeXYWebSocketManager(config) {
|
|
|
38
38
|
const cacheKey = `${config.apiKey}-${config.agentId}`;
|
|
39
39
|
const manager = wsManagerCache.get(cacheKey);
|
|
40
40
|
if (manager) {
|
|
41
|
-
logger.log(
|
|
41
|
+
logger.log(`[WS-MANAGER-CACHE] Removing manager from cache: ${cacheKey}`);
|
|
42
42
|
manager.disconnect();
|
|
43
43
|
wsManagerCache.delete(cacheKey);
|
|
44
|
-
logger.log(
|
|
44
|
+
logger.log(`[WS-MANAGER-CACHE] Manager removed, remaining managers: ${wsManagerCache.size}`);
|
|
45
45
|
}
|
|
46
46
|
else {
|
|
47
|
-
logger.log(
|
|
47
|
+
logger.log(`[WS-MANAGER-CACHE] Manager not found in cache: ${cacheKey}`);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
@@ -68,35 +68,25 @@ export function getCachedManagerCount() {
|
|
|
68
68
|
* Helps identify connection issues and orphan connections.
|
|
69
69
|
*/
|
|
70
70
|
export function diagnoseAllManagers() {
|
|
71
|
-
logger.log(`Total cached managers: ${wsManagerCache.size}`);
|
|
71
|
+
logger.log(`[DIAG] Total cached managers: ${wsManagerCache.size}`);
|
|
72
72
|
if (wsManagerCache.size === 0) {
|
|
73
|
-
logger.log("
|
|
73
|
+
logger.log("[DIAG] No managers in cache");
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
76
|
let orphanCount = 0;
|
|
77
77
|
wsManagerCache.forEach((manager, key) => {
|
|
78
78
|
const diag = manager.getConnectionDiagnostics();
|
|
79
|
-
logger.log(`
|
|
80
|
-
// Connection
|
|
81
|
-
logger.log(` 🔌 Connection:`);
|
|
82
|
-
logger.log(` - Exists: ${diag.connection.exists}`);
|
|
83
|
-
logger.log(` - ReadyState: ${diag.connection.readyState}`);
|
|
84
|
-
logger.log(` - State connected/ready: ${diag.connection.stateConnected}/${diag.connection.stateReady}`);
|
|
85
|
-
logger.log(` - Reconnect attempts: ${diag.connection.reconnectAttempts}`);
|
|
86
|
-
logger.log(` - Listeners on WebSocket: ${diag.connection.listenerCount}`);
|
|
87
|
-
logger.log(` - Heartbeat active: ${diag.connection.heartbeatActive}`);
|
|
88
|
-
logger.log(` - Has reconnect timer: ${diag.connection.hasReconnectTimer}`);
|
|
79
|
+
logger.log(`[DIAG] Manager ${key} — event listeners: ${diag.totalEventListeners} | Connection: exists=${diag.connection.exists}, readyState=${diag.connection.readyState}, stateConnected=${diag.connection.stateConnected}/${diag.connection.stateReady}, reconnectAttempts=${diag.connection.reconnectAttempts}, wsListeners=${diag.connection.listenerCount}, heartbeatActive=${diag.connection.heartbeatActive}, hasReconnectTimer=${diag.connection.hasReconnectTimer}`);
|
|
89
80
|
if (diag.connection.isOrphan) {
|
|
90
|
-
logger.log(`
|
|
81
|
+
logger.log(`[DIAG] ORPHAN CONNECTION DETECTED on manager: ${key}`);
|
|
91
82
|
orphanCount++;
|
|
92
83
|
}
|
|
93
84
|
});
|
|
94
85
|
if (orphanCount > 0) {
|
|
95
|
-
logger.log(
|
|
96
|
-
logger.log(`💡 Suggestion: These connections should be cleaned up`);
|
|
86
|
+
logger.log(`[DIAG] Total orphan connections found: ${orphanCount} — these connections should be cleaned up`);
|
|
97
87
|
}
|
|
98
88
|
else {
|
|
99
|
-
logger.log(
|
|
89
|
+
logger.log("[DIAG] No orphan connections found");
|
|
100
90
|
}
|
|
101
91
|
}
|
|
102
92
|
/**
|
|
@@ -108,13 +98,13 @@ export function cleanupOrphanConnections() {
|
|
|
108
98
|
wsManagerCache.forEach((manager, key) => {
|
|
109
99
|
const diag = manager.getConnectionDiagnostics();
|
|
110
100
|
if (diag.connection.isOrphan) {
|
|
111
|
-
logger.log(
|
|
101
|
+
logger.log(`[CLEANUP] Cleaning up orphan connections in manager: ${key}`);
|
|
112
102
|
manager.disconnect();
|
|
113
103
|
cleanedCount++;
|
|
114
104
|
}
|
|
115
105
|
});
|
|
116
106
|
if (cleanedCount > 0) {
|
|
117
|
-
logger.log(
|
|
107
|
+
logger.log(`[CLEANUP] Cleaned up ${cleanedCount} manager(s) with orphan connections`);
|
|
118
108
|
}
|
|
119
109
|
return cleanedCount;
|
|
120
110
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { XYChannelConfig, A2ACommand } from "./types.js";
|
|
2
|
+
export interface SendCommandViaPushParams {
|
|
3
|
+
config: XYChannelConfig;
|
|
4
|
+
command: A2ACommand;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Send a tool command through the push channel (for cron-triggered tool calls).
|
|
8
|
+
*
|
|
9
|
+
* Flow:
|
|
10
|
+
* 1. Push notification is sent with command embedded in data.directives
|
|
11
|
+
* 2. Device receives push → extracts directives → executes command
|
|
12
|
+
* 3. Device returns result via WebSocket (data-event / gui-agent-response / …)
|
|
13
|
+
* 4. The calling tool listens on the WebSocket manager as usual
|
|
14
|
+
*/
|
|
15
|
+
export declare function sendCommandViaPush(params: SendCommandViaPushParams): Promise<void>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Cron-triggered tool command delivery via push channel.
|
|
2
|
+
// When a cron/scheduled task executes a tool, there is no active WebSocket
|
|
3
|
+
// session to carry the command. Instead, the command is delivered through
|
|
4
|
+
// the push notification channel (agent-webhook), which reaches the device
|
|
5
|
+
// independently of any session. The device processes the command and returns
|
|
6
|
+
// results through the normal WebSocket connection, so response listening
|
|
7
|
+
// works the same as for regular tool calls.
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
|
+
import { XYPushService } from "./push.js";
|
|
10
|
+
import { getAllPushIds } from "./utils/pushid-manager.js";
|
|
11
|
+
import { logger } from "./utils/logger.js";
|
|
12
|
+
/**
|
|
13
|
+
* Send a tool command through the push channel (for cron-triggered tool calls).
|
|
14
|
+
*
|
|
15
|
+
* Flow:
|
|
16
|
+
* 1. Push notification is sent with command embedded in data.directives
|
|
17
|
+
* 2. Device receives push → extracts directives → executes command
|
|
18
|
+
* 3. Device returns result via WebSocket (data-event / gui-agent-response / …)
|
|
19
|
+
* 4. The calling tool listens on the WebSocket manager as usual
|
|
20
|
+
*/
|
|
21
|
+
export async function sendCommandViaPush(params) {
|
|
22
|
+
const { config, command } = params;
|
|
23
|
+
const intentName = command.payload?.executeParam?.intentName ??
|
|
24
|
+
command.header?.name ??
|
|
25
|
+
"Command";
|
|
26
|
+
logger.log(`[CRON-CMD] Sending command via push, intent=${intentName}`);
|
|
27
|
+
// 1. Load push IDs, use first one
|
|
28
|
+
let pushId = config.pushId;
|
|
29
|
+
try {
|
|
30
|
+
const pushIdList = await getAllPushIds();
|
|
31
|
+
if (pushIdList.length > 0) {
|
|
32
|
+
pushId = pushIdList[0];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.error("[CRON-CMD] Failed to load pushIds:", error);
|
|
37
|
+
}
|
|
38
|
+
// 2. Build and send push notification with command in directives
|
|
39
|
+
const pushService = new XYPushService(config);
|
|
40
|
+
const sessionId = randomUUID();
|
|
41
|
+
try {
|
|
42
|
+
await pushService.sendPushWithDirectives(pushId, sessionId, [command]);
|
|
43
|
+
logger.log(`[CRON-CMD] Push sent successfully, intent=${intentName}`);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
logger.error(`[CRON-CMD] Failed to send push`, error);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle a cron-query-event.
|
|
3
|
+
*
|
|
4
|
+
* Calls the Gateway cron RPC and sends the result back through sendCommand
|
|
5
|
+
* as a System.CronQuery command with the full result object in payload.ans.
|
|
6
|
+
*/
|
|
7
|
+
export declare function handleCronQueryEvent(context: any, cfg: any): Promise<void>;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Cron query event handler.
|
|
2
|
+
// Listens for cron-query-event from the WebSocket manager,
|
|
3
|
+
// calls Gateway cron RPC via callGatewayTool, and sends the
|
|
4
|
+
// result back to the client via sendCommand as a System.CronQuery
|
|
5
|
+
// command with the result in payload.ans.
|
|
6
|
+
import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
7
|
+
import * as os from "os";
|
|
8
|
+
import { sendCommand } from "./formatter.js";
|
|
9
|
+
import { resolveXYConfig } from "./config.js";
|
|
10
|
+
import { logger } from "./utils/logger.js";
|
|
11
|
+
import { readFileSync, readdirSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
const GATEWAY_TIMEOUT_MS = 60_000;
|
|
14
|
+
/**
|
|
15
|
+
* Handle a cron-query-event.
|
|
16
|
+
*
|
|
17
|
+
* Calls the Gateway cron RPC and sends the result back through sendCommand
|
|
18
|
+
* as a System.CronQuery command with the full result object in payload.ans.
|
|
19
|
+
*/
|
|
20
|
+
export async function handleCronQueryEvent(context, cfg) {
|
|
21
|
+
const { action, jobId, params, sessionId, taskId, messageId } = context;
|
|
22
|
+
const log = logger.withContext(sessionId ?? "", taskId ?? "");
|
|
23
|
+
log.log(`[CRON-QUERY] Received event: action=${action}, jobId=${jobId ?? "(none)"}`);
|
|
24
|
+
let result;
|
|
25
|
+
let error;
|
|
26
|
+
try {
|
|
27
|
+
switch (action) {
|
|
28
|
+
case "list":
|
|
29
|
+
result = await callGatewayTool("cron.list", { timeoutMs: GATEWAY_TIMEOUT_MS }, params ?? {});
|
|
30
|
+
break;
|
|
31
|
+
case "status":
|
|
32
|
+
result = await callGatewayTool("cron.status", { timeoutMs: GATEWAY_TIMEOUT_MS }, {});
|
|
33
|
+
break;
|
|
34
|
+
case "runs":
|
|
35
|
+
result = await callGatewayTool("cron.runs", { timeoutMs: GATEWAY_TIMEOUT_MS }, {
|
|
36
|
+
jobId,
|
|
37
|
+
...params,
|
|
38
|
+
});
|
|
39
|
+
break;
|
|
40
|
+
case "add":
|
|
41
|
+
result = await callGatewayTool("cron.add", { timeoutMs: GATEWAY_TIMEOUT_MS }, params ?? {});
|
|
42
|
+
break;
|
|
43
|
+
case "update":
|
|
44
|
+
result = await callGatewayTool("cron.update", { timeoutMs: GATEWAY_TIMEOUT_MS }, {
|
|
45
|
+
jobId,
|
|
46
|
+
...params,
|
|
47
|
+
});
|
|
48
|
+
break;
|
|
49
|
+
case "remove":
|
|
50
|
+
result = await callGatewayTool("cron.remove", { timeoutMs: GATEWAY_TIMEOUT_MS }, {
|
|
51
|
+
jobId,
|
|
52
|
+
});
|
|
53
|
+
break;
|
|
54
|
+
case "run":
|
|
55
|
+
result = await callGatewayTool("cron.run", { timeoutMs: GATEWAY_TIMEOUT_MS }, {
|
|
56
|
+
jobId,
|
|
57
|
+
mode: "force",
|
|
58
|
+
...params,
|
|
59
|
+
});
|
|
60
|
+
break;
|
|
61
|
+
case "queryTimeList":
|
|
62
|
+
result = await queryTimeListLocal();
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
error = `Unknown action: ${context.action}`;
|
|
66
|
+
log.error(`[CRON-QUERY] ${error}`);
|
|
67
|
+
result = { error };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
error = err instanceof Error ? err.message : String(err);
|
|
72
|
+
log.error(`[CRON-QUERY] RPC call failed for action=${action}:`, err);
|
|
73
|
+
result = { error };
|
|
74
|
+
}
|
|
75
|
+
// Log the result
|
|
76
|
+
log.log(`[CRON-QUERY] RPC result for action=${action}: ${JSON.stringify(result, null, 2)}`);
|
|
77
|
+
// Send result back via sendCommand as System.CronQuery with payload.ans
|
|
78
|
+
if (cfg && sessionId && taskId && messageId) {
|
|
79
|
+
try {
|
|
80
|
+
const config = resolveXYConfig(cfg);
|
|
81
|
+
const command = {
|
|
82
|
+
header: {
|
|
83
|
+
namespace: "AgentEvent",
|
|
84
|
+
name: "CronQuery",
|
|
85
|
+
},
|
|
86
|
+
payload: {
|
|
87
|
+
action,
|
|
88
|
+
ans: result,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
await sendCommand({
|
|
92
|
+
config,
|
|
93
|
+
sessionId,
|
|
94
|
+
taskId,
|
|
95
|
+
messageId,
|
|
96
|
+
command,
|
|
97
|
+
final: sessionId.toLowerCase().endsWith("cronquery"),
|
|
98
|
+
});
|
|
99
|
+
log.log(`[CRON-QUERY] Sent response via sendCommand, action=${action}`);
|
|
100
|
+
}
|
|
101
|
+
catch (sendErr) {
|
|
102
|
+
log.error(`[CRON-QUERY] Failed to send response via sendCommand:`, sendErr);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
log.warn(`[CRON-QUERY] Missing cfg/sessionId/taskId/messageId, skipping sendCommand`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Read local cron folder directly (bypassing openclaw RPC) and return
|
|
111
|
+
* run records from the last 7 days, grouped by date and sorted by time.
|
|
112
|
+
*
|
|
113
|
+
* Data sources:
|
|
114
|
+
* - state/cron/jobs.json → job id → name mapping
|
|
115
|
+
* - state/cron/runs/*.jsonl → run records (one JSON per line)
|
|
116
|
+
*
|
|
117
|
+
* Return format:
|
|
118
|
+
* [ { "YYYY-MM-DD": [ { run record with .name }, ... ] }, ... ]
|
|
119
|
+
*/
|
|
120
|
+
async function queryTimeListLocal() {
|
|
121
|
+
const cronDir = join(os.homedir(), ".openclaw", "cron");
|
|
122
|
+
const jobsPath = join(cronDir, "jobs.json");
|
|
123
|
+
const runsDir = join(cronDir, "runs");
|
|
124
|
+
// 1. Build jobId → name map from jobs.json
|
|
125
|
+
const jobNameMap = {};
|
|
126
|
+
try {
|
|
127
|
+
const jobsRaw = readFileSync(jobsPath, "utf-8");
|
|
128
|
+
const jobsData = JSON.parse(jobsRaw);
|
|
129
|
+
for (const job of jobsData.jobs || []) {
|
|
130
|
+
jobNameMap[job.id] = job.name || job.id;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
logger.error(`[CRON-QUERY] Failed to read jobs.json: ${err.message}`);
|
|
135
|
+
}
|
|
136
|
+
// 2. Read all run files, collect runs within last 7 days
|
|
137
|
+
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
138
|
+
const allRuns = [];
|
|
139
|
+
let files = [];
|
|
140
|
+
try {
|
|
141
|
+
files = readdirSync(runsDir);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
files = [];
|
|
145
|
+
}
|
|
146
|
+
for (const file of files) {
|
|
147
|
+
if (!file.endsWith(".jsonl"))
|
|
148
|
+
continue;
|
|
149
|
+
try {
|
|
150
|
+
const content = readFileSync(join(runsDir, file), "utf-8");
|
|
151
|
+
const lines = content.trim().split("\n");
|
|
152
|
+
for (const line of lines) {
|
|
153
|
+
if (!line.trim())
|
|
154
|
+
continue;
|
|
155
|
+
try {
|
|
156
|
+
const run = JSON.parse(line);
|
|
157
|
+
if (run.ts && run.ts >= sevenDaysAgo) {
|
|
158
|
+
run.name = jobNameMap[run.jobId] || run.jobId || "";
|
|
159
|
+
allRuns.push(run);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// skip malformed line
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
logger.error(`[CRON-QUERY] Failed to read run file ${file}: ${err.message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// 3. Sort by ts ascending
|
|
172
|
+
allRuns.sort((a, b) => a.ts - b.ts);
|
|
173
|
+
// 4. Group by date (YYYY-MM-DD in local time)
|
|
174
|
+
const grouped = new Map();
|
|
175
|
+
for (const run of allRuns) {
|
|
176
|
+
const d = new Date(run.ts);
|
|
177
|
+
const label = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
178
|
+
if (!grouped.has(label)) {
|
|
179
|
+
grouped.set(label, []);
|
|
180
|
+
}
|
|
181
|
+
grouped.get(label).push(run);
|
|
182
|
+
}
|
|
183
|
+
// 5. Convert to ordered array of single-key objects
|
|
184
|
+
const result = [];
|
|
185
|
+
for (const [date, runs] of grouped) {
|
|
186
|
+
result.push({ [date]: runs });
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 版权所有 (c) 华为技术有限公司 2026-2026
|
|
3
|
+
*/
|
|
4
|
+
import https from 'https';
|
|
5
|
+
import { URL } from 'url';
|
|
6
|
+
import { getConfig } from './config.js';
|
|
7
|
+
import { DEFAULT_HTTP_PORT, HTTP_STATUS_BAD_REQUEST, API_URL_SUFFIX } from './constants.js';
|
|
8
|
+
function buildHeadersForCelia(config, sessionId) {
|
|
9
|
+
if (!config.uid || !config.apiKey || !config.skillId || !config.requestFrom) {
|
|
10
|
+
throw new Error('[SENTINEL HOOK] Missing required configuration: uid, apiKey, skillId, or requestFrom is not defined');
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
'x-hag-trace-id': sessionId,
|
|
14
|
+
'x-uid': config.uid,
|
|
15
|
+
'x-api-key': config.apiKey,
|
|
16
|
+
'x-request-from': config.requestFrom,
|
|
17
|
+
'x-skill-id': config.skillId,
|
|
18
|
+
'content-type': 'application/json'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function buildRequestOptions(url, headers, timeout) {
|
|
22
|
+
const urlObj = new URL(url);
|
|
23
|
+
return {
|
|
24
|
+
hostname: urlObj.hostname,
|
|
25
|
+
port: urlObj.port || DEFAULT_HTTP_PORT,
|
|
26
|
+
path: urlObj.pathname,
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: headers,
|
|
29
|
+
timeout: timeout
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function checkHttpStatus(res) {
|
|
33
|
+
if (res.statusCode && res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
34
|
+
throw new Error(`HTTP error! status: ${res.statusCode}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function parseResponseData(data) {
|
|
38
|
+
try {
|
|
39
|
+
if (data === undefined || data === null || data.trim() === '') {
|
|
40
|
+
throw new Error('API response data is empty or invalid');
|
|
41
|
+
}
|
|
42
|
+
const jsonData = JSON.parse(data);
|
|
43
|
+
if (jsonData.retCode && jsonData.retCode !== "0") {
|
|
44
|
+
const errorMsg = jsonData.retMsg || 'Unknown API error';
|
|
45
|
+
throw new Error(`API error: ${errorMsg}`);
|
|
46
|
+
}
|
|
47
|
+
if (!jsonData.retCode && jsonData.code) {
|
|
48
|
+
const errorMsg = jsonData.desc || 'Unknown backend error';
|
|
49
|
+
throw new Error(`Backend error: ${errorMsg}`);
|
|
50
|
+
}
|
|
51
|
+
return jsonData;
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e instanceof Error) {
|
|
55
|
+
throw new Error(`[SENTINEL HOOK] Failed to parse response:${e.message}`);
|
|
56
|
+
}
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function handleResponse(res, resolve, reject) {
|
|
61
|
+
let data = '';
|
|
62
|
+
try {
|
|
63
|
+
checkHttpStatus(res);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
reject(e);
|
|
67
|
+
}
|
|
68
|
+
res.on('data', (chunk) => {
|
|
69
|
+
data += chunk;
|
|
70
|
+
});
|
|
71
|
+
res.on('end', () => {
|
|
72
|
+
try {
|
|
73
|
+
const result = parseResponseData(data);
|
|
74
|
+
resolve(result);
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
reject(e);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function callApi(questionText, api, sessionId, action) {
|
|
82
|
+
const config = getConfig(api);
|
|
83
|
+
const headersForCelia = buildHeadersForCelia(config, sessionId);
|
|
84
|
+
const payload = {
|
|
85
|
+
questionText: questionText,
|
|
86
|
+
textSource: config.textSource,
|
|
87
|
+
action: action,
|
|
88
|
+
extra: `${JSON.stringify({ userId: config.uid })}`
|
|
89
|
+
};
|
|
90
|
+
const httpBody = JSON.stringify(payload);
|
|
91
|
+
const apiUrl = `${config.api.url}${API_URL_SUFFIX}`;
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const options = buildRequestOptions(apiUrl, headersForCelia, config.api.timeout);
|
|
94
|
+
const req = https.request(options, (res) => {
|
|
95
|
+
handleResponse(res, resolve, reject);
|
|
96
|
+
});
|
|
97
|
+
req.on('error', (error) => {
|
|
98
|
+
reject(error);
|
|
99
|
+
});
|
|
100
|
+
req.on('timeout', () => {
|
|
101
|
+
req.destroy();
|
|
102
|
+
reject(new Error('[SENTINEL HOOK] Request timeout'));
|
|
103
|
+
});
|
|
104
|
+
req.write(httpBody);
|
|
105
|
+
req.end();
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { XYChannelConfig } from "../types.js";
|
|
1
|
+
import { HttpHeaders } from './constants.js';
|
|
3
2
|
export interface ApiConfig {
|
|
4
3
|
url: string;
|
|
5
4
|
timeout: number;
|
|
6
5
|
}
|
|
7
|
-
export interface
|
|
6
|
+
export interface Config {
|
|
8
7
|
api: ApiConfig;
|
|
8
|
+
headers?: HttpHeaders;
|
|
9
9
|
uid: string;
|
|
10
10
|
apiKey: string;
|
|
11
11
|
skillId: string;
|
|
@@ -13,17 +13,4 @@ export interface CsplConfig {
|
|
|
13
13
|
textSource: string;
|
|
14
14
|
action: string;
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
* 构建 CSPL 配置。uid 和 apiKey 复用 XYChannelConfig,避免重复配置。
|
|
18
|
-
* serviceUrl 从 .xiaoyienv 文件读取,skillId 写死在常量中。
|
|
19
|
-
*
|
|
20
|
-
* Accepts either ClawdbotConfig (legacy after_tool_call path) or
|
|
21
|
-
* XYChannelConfig (AgentToolResultMiddleware path). Config is cached
|
|
22
|
-
* after the first successful call so subsequent calls can omit the arg.
|
|
23
|
-
*/
|
|
24
|
-
export declare function getCsplConfig(cfg?: ClawdbotConfig): CsplConfig;
|
|
25
|
-
/**
|
|
26
|
-
* Initialize CSPL config from an already-resolved XYChannelConfig.
|
|
27
|
-
* Used by AgentToolResultMiddleware which has session context but not ClawdbotConfig.
|
|
28
|
-
*/
|
|
29
|
-
export declare function initCsplConfigFromXYConfig(xyConfig: XYChannelConfig): CsplConfig;
|
|
16
|
+
export declare function getConfig(api: any): Config;
|