@ynhcj/xiaoyi-channel 0.0.192-beta → 0.0.192-next
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 +56 -3
- package/dist/src/acp-session-binding.d.ts +37 -0
- package/dist/src/acp-session-binding.js +237 -0
- package/dist/src/bot.js +101 -34
- package/dist/src/channel.js +68 -64
- package/dist/src/client.d.ts +5 -0
- package/dist/src/client.js +10 -0
- package/dist/src/cspl/call_api.js +2 -0
- package/dist/src/cspl/sentinel_hook.js +9 -8
- package/dist/src/formatter.d.ts +5 -0
- package/dist/src/formatter.js +24 -34
- package/dist/src/log-reporter/config-loader.d.ts +11 -0
- package/dist/src/log-reporter/config-loader.js +68 -0
- package/dist/src/log-reporter/cursor-store.d.ts +5 -0
- package/dist/src/log-reporter/cursor-store.js +26 -0
- package/dist/src/log-reporter/index.d.ts +10 -0
- package/dist/src/log-reporter/index.js +77 -0
- package/dist/src/log-reporter/reporter.d.ts +6 -0
- package/dist/src/log-reporter/reporter.js +17 -0
- package/dist/src/log-reporter/scanner.d.ts +6 -0
- package/dist/src/log-reporter/scanner.js +82 -0
- package/dist/src/log-reporter/types.d.ts +59 -0
- package/dist/src/log-reporter/types.js +2 -0
- package/dist/src/log-reporter/uploader.d.ts +6 -0
- package/dist/src/log-reporter/uploader.js +32 -0
- package/dist/src/memory-query-handler.js +123 -4
- package/dist/src/monitor.js +20 -9
- package/dist/src/outbound.d.ts +7 -0
- package/dist/src/outbound.js +77 -5
- package/dist/src/parser.d.ts +12 -0
- package/dist/src/parser.js +32 -0
- package/dist/src/provider.js +69 -15
- package/dist/src/reply-dispatcher.d.ts +3 -0
- package/dist/src/reply-dispatcher.js +184 -154
- package/dist/src/self-evolution-handler.js +2 -1
- package/dist/src/subagent-wait-state.d.ts +66 -0
- package/dist/src/subagent-wait-state.js +226 -0
- package/dist/src/task-manager.d.ts +14 -7
- package/dist/src/task-manager.js +54 -15
- package/dist/src/tools/agent-as-skill-tool.d.ts +41 -2
- package/dist/src/tools/agent-as-skill-tool.js +144 -151
- package/dist/src/tools/calendar-tool.d.ts +24 -2
- package/dist/src/tools/calendar-tool.js +115 -117
- package/dist/src/tools/call-device-tool.d.ts +20 -6
- package/dist/src/tools/call-device-tool.js +116 -138
- package/dist/src/tools/call-phone-tool.d.ts +21 -2
- package/dist/src/tools/call-phone-tool.js +112 -114
- package/dist/src/tools/check-plugin-privilege-tool.d.ts +16 -2
- package/dist/src/tools/check-plugin-privilege-tool.js +141 -143
- package/dist/src/tools/create-alarm-tool.d.ts +39 -2
- package/dist/src/tools/create-alarm-tool.js +229 -231
- package/dist/src/tools/create-all-tools.js +2 -2
- package/dist/src/tools/delete-alarm-tool.d.ts +15 -2
- package/dist/src/tools/delete-alarm-tool.js +134 -136
- package/dist/src/tools/device-tool-map.js +1 -1
- package/dist/src/tools/discover-cross-devices-tool.d.ts +16 -2
- package/dist/src/tools/discover-cross-devices-tool.js +121 -124
- package/dist/src/tools/display-a2ui-card-bypath-tool.d.ts +21 -0
- package/dist/src/tools/display-a2ui-card-bypath-tool.js +65 -0
- package/dist/src/tools/get-alarm-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-alarm-tool-schema.js +10 -16
- package/dist/src/tools/get-calendar-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-calendar-tool-schema.js +8 -12
- package/dist/src/tools/get-collection-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-collection-tool-schema.js +9 -11
- package/dist/src/tools/get-contact-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-contact-tool-schema.js +10 -16
- package/dist/src/tools/get-device-file-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-device-file-tool-schema.js +9 -13
- package/dist/src/tools/get-email-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-email-tool-schema.js +8 -11
- package/dist/src/tools/get-note-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-note-tool-schema.js +9 -14
- package/dist/src/tools/get-photo-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-photo-tool-schema.js +9 -12
- package/dist/src/tools/image-reading-tool.d.ts +28 -2
- package/dist/src/tools/image-reading-tool.js +76 -76
- package/dist/src/tools/location-tool.d.ts +11 -2
- package/dist/src/tools/location-tool.js +93 -92
- package/dist/src/tools/login-token-tool.d.ts +20 -2
- package/dist/src/tools/login-token-tool.js +123 -125
- package/dist/src/tools/modify-alarm-tool.d.ts +47 -2
- package/dist/src/tools/modify-alarm-tool.js +250 -252
- package/dist/src/tools/modify-note-tool.d.ts +20 -2
- package/dist/src/tools/modify-note-tool.js +107 -109
- package/dist/src/tools/note-tool.d.ts +20 -2
- package/dist/src/tools/note-tool.js +106 -108
- package/dist/src/tools/query-app-message-tool.d.ts +28 -2
- package/dist/src/tools/query-app-message-tool.js +111 -113
- package/dist/src/tools/query-memory-data-tool.d.ts +28 -2
- package/dist/src/tools/query-memory-data-tool.js +112 -114
- package/dist/src/tools/query-todo-task-tool.d.ts +24 -2
- package/dist/src/tools/query-todo-task-tool.js +106 -108
- package/dist/src/tools/save-file-to-phone-tool.d.ts +24 -2
- package/dist/src/tools/save-file-to-phone-tool.js +130 -132
- package/dist/src/tools/save-media-to-gallery-tool.d.ts +24 -2
- package/dist/src/tools/save-media-to-gallery-tool.js +137 -139
- package/dist/src/tools/save-self-evolution-skill-tool.d.ts +54 -2
- package/dist/src/tools/save-self-evolution-skill-tool.js +194 -194
- package/dist/src/tools/search-alarm-tool.d.ts +34 -2
- package/dist/src/tools/search-alarm-tool.js +174 -176
- package/dist/src/tools/search-calendar-tool.d.ts +24 -2
- package/dist/src/tools/search-calendar-tool.js +148 -150
- package/dist/src/tools/search-contact-tool.d.ts +16 -2
- package/dist/src/tools/search-contact-tool.js +101 -103
- package/dist/src/tools/search-email-tool.d.ts +21 -2
- package/dist/src/tools/search-email-tool.js +110 -112
- package/dist/src/tools/search-file-tool.d.ts +16 -2
- package/dist/src/tools/search-file-tool.js +104 -106
- package/dist/src/tools/search-message-tool.d.ts +16 -2
- package/dist/src/tools/search-message-tool.js +103 -105
- package/dist/src/tools/search-note-tool.d.ts +16 -2
- package/dist/src/tools/search-note-tool.js +98 -100
- package/dist/src/tools/search-photo-gallery-tool.d.ts +21 -2
- package/dist/src/tools/search-photo-gallery-tool.js +35 -37
- package/dist/src/tools/send-cross-device-task-tool.d.ts +35 -2
- package/dist/src/tools/send-cross-device-task-tool.js +140 -143
- package/dist/src/tools/send-email-tool.d.ts +24 -2
- package/dist/src/tools/send-email-tool.js +108 -110
- package/dist/src/tools/send-file-to-user-tool.d.ts +20 -2
- package/dist/src/tools/send-file-to-user-tool.js +174 -176
- package/dist/src/tools/send-html-card-tool.d.ts +20 -2
- package/dist/src/tools/send-html-card-tool.js +85 -87
- package/dist/src/tools/send-message-tool.d.ts +20 -2
- package/dist/src/tools/send-message-tool.js +122 -124
- package/dist/src/tools/session-manager.d.ts +11 -50
- package/dist/src/tools/session-manager.js +40 -231
- package/dist/src/tools/upload-file-tool.d.ts +20 -2
- package/dist/src/tools/upload-file-tool.js +80 -82
- package/dist/src/tools/upload-photo-tool.d.ts +20 -2
- package/dist/src/tools/upload-photo-tool.js +68 -70
- package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +32 -2
- package/dist/src/tools/xiaoyi-add-collection-tool.js +146 -148
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +20 -2
- package/dist/src/tools/xiaoyi-collection-tool.js +114 -116
- package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +15 -2
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +127 -129
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +16 -2
- package/dist/src/tools/xiaoyi-gui-tool.js +92 -95
- package/dist/src/utils/logger.js +3 -14
- package/dist/src/websocket.d.ts +1 -1
- package/dist/src/websocket.js +4 -4
- package/package.json +2 -2
package/dist/src/channel.js
CHANGED
|
@@ -2,15 +2,55 @@ import { resolveXYConfig, listXYAccountIds, getDefaultXYAccountId } from "./conf
|
|
|
2
2
|
import { xyConfigSchema } from "./config-schema.js";
|
|
3
3
|
import { xyOutbound } from "./outbound.js";
|
|
4
4
|
import { filterToolsByDevice } from "./tools/device-tool-map.js";
|
|
5
|
-
import { getCurrentSessionContext
|
|
6
|
-
import { createAllTools } from "./tools/create-all-tools.js";
|
|
5
|
+
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
7
6
|
import { logger } from "./utils/logger.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
// Static tool imports (3.24 pattern)
|
|
8
|
+
import { locationTool } from "./tools/location-tool.js";
|
|
9
|
+
import { xiaoyiGuiTool } from "./tools/xiaoyi-gui-tool.js";
|
|
10
|
+
import { sendFileToUserTool } from "./tools/send-file-to-user-tool.js";
|
|
11
|
+
import { sendHtmlCardTool } from "./tools/send-html-card-tool.js";
|
|
12
|
+
import { viewPushResultTool } from "./tools/view-push-result-tool.js";
|
|
13
|
+
import { imageReadingTool } from "./tools/image-reading-tool.js";
|
|
14
|
+
import { timestampToUtc8Tool } from "./tools/timestamp-to-utc8-tool.js";
|
|
15
|
+
import { saveSelfEvolutionSkillTool } from "./tools/save-self-evolution-skill-tool.js";
|
|
16
|
+
import { callDeviceTool } from "./tools/call-device-tool.js";
|
|
17
|
+
import { getNoteToolSchemaTool } from "./tools/get-note-tool-schema.js";
|
|
18
|
+
import { getCalendarToolSchemaTool } from "./tools/get-calendar-tool-schema.js";
|
|
19
|
+
import { getContactToolSchemaTool } from "./tools/get-contact-tool-schema.js";
|
|
20
|
+
import { getPhotoToolSchemaTool } from "./tools/get-photo-tool-schema.js";
|
|
21
|
+
import { getDeviceFileToolSchemaTool } from "./tools/get-device-file-tool-schema.js";
|
|
22
|
+
import { getAlarmToolSchemaTool } from "./tools/get-alarm-tool-schema.js";
|
|
23
|
+
import { getCollectionToolSchemaTool } from "./tools/get-collection-tool-schema.js";
|
|
24
|
+
import { loginTokenTool } from "./tools/login-token-tool.js";
|
|
25
|
+
import { agentAsSkillTool } from "./tools/agent-as-skill-tool.js";
|
|
26
|
+
import { discoverCrossDevicesTool } from "./tools/discover-cross-devices-tool.js";
|
|
27
|
+
import { sendCrossDeviceTaskTool } from "./tools/send-cross-device-task-tool.js";
|
|
28
|
+
import { displayA2UICardByPathTool } from "./tools/display-a2ui-card-bypath-tool.js";
|
|
29
|
+
import { checkPluginPrivilegeTool } from "./tools/check-plugin-privilege-tool.js";
|
|
30
|
+
const ALL_TOOLS = [
|
|
31
|
+
locationTool,
|
|
32
|
+
discoverCrossDevicesTool,
|
|
33
|
+
sendCrossDeviceTaskTool,
|
|
34
|
+
displayA2UICardByPathTool,
|
|
35
|
+
callDeviceTool,
|
|
36
|
+
getNoteToolSchemaTool,
|
|
37
|
+
getCalendarToolSchemaTool,
|
|
38
|
+
getContactToolSchemaTool,
|
|
39
|
+
getPhotoToolSchemaTool,
|
|
40
|
+
xiaoyiGuiTool,
|
|
41
|
+
getDeviceFileToolSchemaTool,
|
|
42
|
+
getAlarmToolSchemaTool,
|
|
43
|
+
getCollectionToolSchemaTool,
|
|
44
|
+
sendFileToUserTool,
|
|
45
|
+
sendHtmlCardTool,
|
|
46
|
+
viewPushResultTool,
|
|
47
|
+
imageReadingTool,
|
|
48
|
+
timestampToUtc8Tool,
|
|
49
|
+
saveSelfEvolutionSkillTool,
|
|
50
|
+
loginTokenTool,
|
|
51
|
+
agentAsSkillTool,
|
|
52
|
+
checkPluginPrivilegeTool,
|
|
53
|
+
];
|
|
14
54
|
/**
|
|
15
55
|
* Xiaoyi Channel Plugin for OpenClaw.
|
|
16
56
|
* Implements Xiaoyi A2A protocol with dual WebSocket connections.
|
|
@@ -32,7 +72,7 @@ export const xyPlugin = {
|
|
|
32
72
|
],
|
|
33
73
|
},
|
|
34
74
|
capabilities: {
|
|
35
|
-
chatTypes: ["direct"],
|
|
75
|
+
chatTypes: ["direct"],
|
|
36
76
|
polls: false,
|
|
37
77
|
threads: false,
|
|
38
78
|
media: true,
|
|
@@ -49,59 +89,11 @@ export const xyPlugin = {
|
|
|
49
89
|
schema: xyConfigSchema,
|
|
50
90
|
},
|
|
51
91
|
outbound: xyOutbound,
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
* 1. **Normal (WebSocket) session** – `getCurrentSessionContext()` returns
|
|
58
|
-
* a context that was registered by bot.ts during message processing.
|
|
59
|
-
* Tools send commands through the WebSocket and listen for responses.
|
|
60
|
-
*
|
|
61
|
-
* 2. **Cron / scheduled-task session** – openclaw's cron runner calls
|
|
62
|
-
* `agentTools({ cfg })` without an active WebSocket session. When no
|
|
63
|
-
* session context exists but `cfg` is provided, we create a synthetic
|
|
64
|
-
* "cron session" with `isCron: true` and a `cron-`-prefixed sessionId.
|
|
65
|
-
* `sendCommand()` detects this prefix and routes commands through the
|
|
66
|
-
* push channel. Response listening (WebSocket events) works unchanged
|
|
67
|
-
* because the gateway WebSocket connection is always active.
|
|
68
|
-
*/
|
|
69
|
-
agentTools: (params) => {
|
|
70
|
-
let ctx = getCurrentSessionContext();
|
|
71
|
-
// ── Cron / non-session fallback ──────────────────────────────
|
|
72
|
-
// When no active xy WebSocket session exists but the openclaw cfg
|
|
73
|
-
// is provided (framework calls agentTools({ cfg })), create a
|
|
74
|
-
// synthetic "cron session". This enables cron-triggered agent
|
|
75
|
-
// turns and cross-channel tool calls to use xiaoyi tools via the
|
|
76
|
-
// push channel. sendCommand() detects the "cron-" sessionId
|
|
77
|
-
// prefix and routes commands through push instead of WebSocket.
|
|
78
|
-
if (!ctx && params?.cfg) {
|
|
79
|
-
try {
|
|
80
|
-
const config = resolveXYConfig(params.cfg);
|
|
81
|
-
const cronId = `${CRON_SESSION_PREFIX}${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
82
|
-
ctx = {
|
|
83
|
-
config,
|
|
84
|
-
sessionId: cronId,
|
|
85
|
-
taskId: cronId,
|
|
86
|
-
messageId: cronId,
|
|
87
|
-
agentId: "default",
|
|
88
|
-
isCron: true,
|
|
89
|
-
};
|
|
90
|
-
// Register so getCurrentSessionContext() fallback can find it
|
|
91
|
-
registerSession(`__cron__${cronId}`, ctx);
|
|
92
|
-
logger.log(`[CRON-TOOLS] Created cron session context: ${cronId}`);
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
logger.error("[CRON-TOOLS] Failed to create cron context:", err);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (!ctx) {
|
|
99
|
-
logger.log("[CREATE-ALL-TOOLS] no session context, returning empty tools list");
|
|
100
|
-
return [];
|
|
101
|
-
}
|
|
102
|
-
const allTools = createAllTools(ctx);
|
|
103
|
-
const filtered = filterToolsByDevice(allTools, ctx.deviceType);
|
|
104
|
-
logger.log(`[DEVICE-FILTER] deviceType=${ctx.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
|
|
92
|
+
/** Static tool list (3.24 pattern). Tools read SessionContext at execute time via ALS. */
|
|
93
|
+
agentTools: () => {
|
|
94
|
+
const ctx = getCurrentSessionContext();
|
|
95
|
+
const filtered = filterToolsByDevice(ALL_TOOLS, ctx?.deviceType);
|
|
96
|
+
logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${ALL_TOOLS.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
|
|
105
97
|
return filtered;
|
|
106
98
|
},
|
|
107
99
|
messaging: {
|
|
@@ -113,7 +105,6 @@ export const xyPlugin = {
|
|
|
113
105
|
},
|
|
114
106
|
targetResolver: {
|
|
115
107
|
looksLikeId: (raw) => {
|
|
116
|
-
// 信任所有非空字符串作为有效的 sessionId
|
|
117
108
|
const trimmed = raw.trim();
|
|
118
109
|
return trimmed.length > 0;
|
|
119
110
|
},
|
|
@@ -135,20 +126,33 @@ export const xyPlugin = {
|
|
|
135
126
|
? { conversationId, matchPriority: 2 }
|
|
136
127
|
: null;
|
|
137
128
|
},
|
|
129
|
+
resolveCommandConversation: ({ accountId }) => {
|
|
130
|
+
// xiaoyi-channel: the A2A sessionId IS the conversationId.
|
|
131
|
+
// Read from the current ALS session context.
|
|
132
|
+
const ctx = getCurrentSessionContext();
|
|
133
|
+
const sessionId = ctx?.sessionId?.trim();
|
|
134
|
+
if (!sessionId)
|
|
135
|
+
return null;
|
|
136
|
+
return { conversationId: sessionId };
|
|
137
|
+
},
|
|
138
138
|
},
|
|
139
139
|
reload: {
|
|
140
140
|
configPrefixes: ["channels.xiaoyi-channel"],
|
|
141
141
|
},
|
|
142
|
-
// Gateway adapter for receiving messages
|
|
143
142
|
gateway: {
|
|
144
143
|
async startAccount(context) {
|
|
145
144
|
const { monitorXYProvider } = await import("./monitor.js");
|
|
145
|
+
const { createXyAcpBindingManager } = await import("./acp-session-binding.js");
|
|
146
146
|
const account = resolveXYConfig(context.cfg);
|
|
147
147
|
context.setStatus?.({
|
|
148
148
|
accountId: context.accountId,
|
|
149
149
|
wsUrl: account.wsUrl,
|
|
150
150
|
});
|
|
151
151
|
context.log?.info(`[${context.accountId}] starting xiaoyi channel (wsUrl: ${account.wsUrl})`);
|
|
152
|
+
// Register ACP session binding adapter for this account.
|
|
153
|
+
// Enables sessions_spawn(runtime="acp") to bind subagent sessions
|
|
154
|
+
// to the current A2A conversation.
|
|
155
|
+
createXyAcpBindingManager({ accountId: context.accountId, cfg: context.cfg });
|
|
152
156
|
return monitorXYProvider({
|
|
153
157
|
config: context.cfg,
|
|
154
158
|
runtime: context.runtime,
|
package/dist/src/client.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { XYWebSocketManager } from "./websocket.js";
|
|
2
2
|
import type { XYChannelConfig } from "./types.js";
|
|
3
3
|
import type { RuntimeEnv } from "openclaw/plugin-sdk";
|
|
4
|
+
/**
|
|
5
|
+
* Get a cached WebSocket manager without requiring config.
|
|
6
|
+
* Returns the first available manager. Use when ALS has no SessionContext.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getCachedXYWebSocketManager(): XYWebSocketManager;
|
|
4
9
|
/**
|
|
5
10
|
* Get or create a WebSocket manager for the given configuration.
|
|
6
11
|
* Reuses existing managers if config matches.
|
package/dist/src/client.js
CHANGED
|
@@ -13,6 +13,16 @@ if (!_g.__xyWsManagerCache) {
|
|
|
13
13
|
_g.__xyWsManagerCache = new Map();
|
|
14
14
|
}
|
|
15
15
|
const wsManagerCache = _g.__xyWsManagerCache;
|
|
16
|
+
/**
|
|
17
|
+
* Get a cached WebSocket manager without requiring config.
|
|
18
|
+
* Returns the first available manager. Use when ALS has no SessionContext.
|
|
19
|
+
*/
|
|
20
|
+
export function getCachedXYWebSocketManager() {
|
|
21
|
+
if (wsManagerCache.size === 0) {
|
|
22
|
+
throw new Error("No WebSocket manager available in cache");
|
|
23
|
+
}
|
|
24
|
+
return wsManagerCache.values().next().value;
|
|
25
|
+
}
|
|
16
26
|
/**
|
|
17
27
|
* Get or create a WebSocket manager for the given configuration.
|
|
18
28
|
* Reuses existing managers if config matches.
|
|
@@ -5,6 +5,7 @@ import https from 'https';
|
|
|
5
5
|
import { URL } from 'url';
|
|
6
6
|
import { getConfig } from './config.js';
|
|
7
7
|
import { DEFAULT_HTTPS_PORT, HTTP_STATUS_BAD_REQUEST, API_URL_SUFFIX } from './constants.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
8
9
|
function buildHeadersForCelia(config, sessionId) {
|
|
9
10
|
if (!config.uid || !config.apiKey || !config.skillId || !config.requestFrom) {
|
|
10
11
|
throw new Error('[SENTINEL HOOK] Missing required configuration: uid, apiKey, skillId, or requestFrom is not defined');
|
|
@@ -89,6 +90,7 @@ export async function callApi(questionText, api, sessionId, action) {
|
|
|
89
90
|
};
|
|
90
91
|
const httpBody = JSON.stringify(payload);
|
|
91
92
|
const apiUrl = `${config.api.url}${API_URL_SUFFIX}`;
|
|
93
|
+
logger.log(`[SENTINEL HOOK] callApi: action=${action}, x-hag-trace-id=${sessionId}, url=${apiUrl}`);
|
|
92
94
|
return new Promise((resolve, reject) => {
|
|
93
95
|
const options = buildRequestOptions(apiUrl, headersForCelia, config.api.timeout);
|
|
94
96
|
const req = https.request(options, (res) => {
|
|
@@ -6,15 +6,16 @@ import { callApi } from './call_api.js';
|
|
|
6
6
|
import { processText, extractResultText, validateAndTruncateText, parseSecurityResult, handleExecToolInput, handleMessageToolInput, handleOtherToolInput } from './utils.js';
|
|
7
7
|
import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH, STEER_ABORT_MESSAGE, TOOL_OUTPUT_ACTION } from './constants.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
-
import {
|
|
9
|
+
import { getCurrentSessionContext } from '../tools/session-manager.js';
|
|
10
10
|
import { tryInjectSteer } from './steer-context.js';
|
|
11
11
|
// 主入口模块
|
|
12
12
|
export default function register(api) {
|
|
13
13
|
api.on("before_tool_call", async (event, ctx) => {
|
|
14
14
|
logger.log(`[SENTINEL HOOK] before_tool_call_event toolName: ${event.toolName}`);
|
|
15
|
-
//
|
|
16
|
-
const
|
|
17
|
-
|
|
15
|
+
// 获取真实sessionID:优先使用ALS中的A2A sessionId,降级到OpenClaw runId或随机值
|
|
16
|
+
const sessionCtx = getCurrentSessionContext();
|
|
17
|
+
const sessionId = sessionCtx?.sessionId || (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
|
|
18
|
+
logger.log(`[SENTINEL HOOK] Session ID: ${sessionId} (fromALS: ${!!sessionCtx?.sessionId})`);
|
|
18
19
|
// 处理 TOOL_INPUT 数据采集、发送数据,根据扫描结果决定是否阻塞
|
|
19
20
|
try {
|
|
20
21
|
let scanResult = null;
|
|
@@ -43,9 +44,10 @@ export default function register(api) {
|
|
|
43
44
|
}
|
|
44
45
|
try {
|
|
45
46
|
logger.log(`[SENTINEL HOOK] after_tool_call_event toolName: ${event.toolName}`);
|
|
46
|
-
//
|
|
47
|
-
const
|
|
48
|
-
|
|
47
|
+
// 获取真实sessionID:优先使用ALS中的A2A sessionId,降级到OpenClaw runId或随机值
|
|
48
|
+
const sessionCtx = getCurrentSessionContext();
|
|
49
|
+
const sessionId = sessionCtx?.sessionId || (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
|
|
50
|
+
logger.log(`[SENTINEL HOOK] Session ID: ${sessionId} (fromALS: ${!!sessionCtx?.sessionId})`);
|
|
49
51
|
// 处理TOOL_OUTPUT数据采集(保持现有逻辑)
|
|
50
52
|
const resultText = extractResultText(event, event.toolName);
|
|
51
53
|
const resultTextLength = resultText.length;
|
|
@@ -78,7 +80,6 @@ export default function register(api) {
|
|
|
78
80
|
logger.log(`[SENTINEL HOOK] TOOL_OUTPUT response: status=${result.status}.`);
|
|
79
81
|
if (result.status === 'REJECT') {
|
|
80
82
|
logger.warn('[SENTINEL HOOK] REJECT detected, attempting steer injection');
|
|
81
|
-
const sessionCtx = ctx.sessionKey ? getSessionContext(ctx.sessionKey) : null;
|
|
82
83
|
if (sessionCtx?.sessionId && sessionCtx?.taskId) {
|
|
83
84
|
await tryInjectSteer({
|
|
84
85
|
sessionId: sessionCtx.sessionId,
|
package/dist/src/formatter.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export interface SendA2AResponseParams {
|
|
|
10
10
|
text?: string;
|
|
11
11
|
append: boolean;
|
|
12
12
|
final: boolean;
|
|
13
|
+
/** Optional: reuse an existing artifactId instead of generating a new one.
|
|
14
|
+
* Used by subagent completion to link results to the original artifact. */
|
|
15
|
+
artifactId?: string;
|
|
13
16
|
files?: Array<{
|
|
14
17
|
fileName: string;
|
|
15
18
|
fileType: string;
|
|
@@ -50,6 +53,8 @@ export interface SendStatusUpdateParams {
|
|
|
50
53
|
messageId: string;
|
|
51
54
|
text: string;
|
|
52
55
|
state: "submitted" | "working" | "input-required" | "completed" | "canceled" | "failed" | "unknown";
|
|
56
|
+
/** When false, use the exact taskId/messageId passed in. Default true (dynamic lookup). */
|
|
57
|
+
useLatestTask?: boolean;
|
|
53
58
|
}
|
|
54
59
|
/**
|
|
55
60
|
* Send an A2A task status update.
|
package/dist/src/formatter.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// OpenClaw → A2A format conversion
|
|
2
|
+
import os from "os";
|
|
2
3
|
import { v4 as uuidv4 } from "uuid";
|
|
3
4
|
import { getXYWebSocketManager } from "./client.js";
|
|
4
5
|
import { logger } from "./utils/logger.js";
|
|
5
|
-
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
6
6
|
import { redactSensitiveText, containsSensitiveInfo } from "./sensitive-redactor.js";
|
|
7
7
|
import { rewriteOutboundApprovalText } from "./approval-bridge.js";
|
|
8
8
|
import { isCronToolCall, getCurrentCronJobId } from "./tools/session-manager.js";
|
|
@@ -45,7 +45,7 @@ function buildTextPreview(text) {
|
|
|
45
45
|
* Send an A2A artifact update response.
|
|
46
46
|
*/
|
|
47
47
|
export async function sendA2AResponse(params) {
|
|
48
|
-
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage, log: shouldLog = true } = params;
|
|
48
|
+
const { config, sessionId, taskId, messageId, text, append, final, artifactId, files, errorCode, errorMessage, log: shouldLog = true } = params;
|
|
49
49
|
const log = logger.withContext(sessionId, taskId);
|
|
50
50
|
// 审批桥接:将 OpenClaw 的审批提示翻译成用户友好的确认文案
|
|
51
51
|
const bridgedText = text === undefined ? text : rewriteOutboundApprovalText(sessionId, text);
|
|
@@ -57,7 +57,7 @@ export async function sendA2AResponse(params) {
|
|
|
57
57
|
lastChunk: true,
|
|
58
58
|
final,
|
|
59
59
|
artifact: {
|
|
60
|
-
artifactId: uuidv4(),
|
|
60
|
+
artifactId: artifactId ?? uuidv4(),
|
|
61
61
|
parts: [],
|
|
62
62
|
},
|
|
63
63
|
};
|
|
@@ -98,7 +98,7 @@ export async function sendA2AResponse(params) {
|
|
|
98
98
|
agentId: config.agentId,
|
|
99
99
|
sessionId,
|
|
100
100
|
taskId,
|
|
101
|
-
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
101
|
+
msgDetail: JSON.stringify({ ...jsonRpcResponse, hostname: os.hostname() }),
|
|
102
102
|
};
|
|
103
103
|
if (shouldLog) {
|
|
104
104
|
const redactedText = redactSensitiveText(bridgedText ?? "");
|
|
@@ -148,7 +148,7 @@ export async function sendReasoningTextUpdate(params) {
|
|
|
148
148
|
agentId: config.agentId,
|
|
149
149
|
sessionId,
|
|
150
150
|
taskId,
|
|
151
|
-
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
151
|
+
msgDetail: JSON.stringify({ ...jsonRpcResponse, hostname: os.hostname() }),
|
|
152
152
|
};
|
|
153
153
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
154
154
|
}
|
|
@@ -157,12 +157,8 @@ export async function sendReasoningTextUpdate(params) {
|
|
|
157
157
|
* Follows A2A protocol standard format with nested status object.
|
|
158
158
|
*/
|
|
159
159
|
export async function sendStatusUpdate(params) {
|
|
160
|
-
const { config, sessionId, taskId, messageId, text, state } = params;
|
|
161
|
-
|
|
162
|
-
// fall back to closure-captured values
|
|
163
|
-
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
164
|
-
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
165
|
-
const log = logger.withContext(sessionId, currentTaskId);
|
|
160
|
+
const { config, sessionId, taskId, messageId, text, state, useLatestTask = true } = params;
|
|
161
|
+
const log = logger.withContext(sessionId, taskId);
|
|
166
162
|
// 审批桥接和脱敏
|
|
167
163
|
const bridgedText = rewriteOutboundApprovalText(sessionId, text);
|
|
168
164
|
const redactedText = redactSensitiveText(bridgedText);
|
|
@@ -177,7 +173,7 @@ export async function sendStatusUpdate(params) {
|
|
|
177
173
|
],
|
|
178
174
|
});
|
|
179
175
|
const statusUpdate = {
|
|
180
|
-
taskId
|
|
176
|
+
taskId,
|
|
181
177
|
kind: "status-update",
|
|
182
178
|
final: false, // Status updates should not end the stream
|
|
183
179
|
status: {
|
|
@@ -188,7 +184,7 @@ export async function sendStatusUpdate(params) {
|
|
|
188
184
|
// Build JSON-RPC response
|
|
189
185
|
const jsonRpcResponse = {
|
|
190
186
|
jsonrpc: "2.0",
|
|
191
|
-
id:
|
|
187
|
+
id: messageId,
|
|
192
188
|
result: statusUpdate,
|
|
193
189
|
};
|
|
194
190
|
// Send via WebSocket
|
|
@@ -197,8 +193,8 @@ export async function sendStatusUpdate(params) {
|
|
|
197
193
|
msgType: "agent_response",
|
|
198
194
|
agentId: config.agentId,
|
|
199
195
|
sessionId,
|
|
200
|
-
taskId
|
|
201
|
-
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
196
|
+
taskId,
|
|
197
|
+
msgDetail: JSON.stringify({ ...jsonRpcResponse, hostname: os.hostname() }),
|
|
202
198
|
};
|
|
203
199
|
// Log complete response body
|
|
204
200
|
log.log(`[A2A_STATUS] Sending status-update, text="${redactedText}"`);
|
|
@@ -270,15 +266,11 @@ export async function sendCommand(params) {
|
|
|
270
266
|
return sendCommandViaPush({ config, command: commands[0], pushId });
|
|
271
267
|
}
|
|
272
268
|
// ── Normal mode: WebSocket ─────────────────────────────────────
|
|
273
|
-
|
|
274
|
-
// fall back to closure-captured values
|
|
275
|
-
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
276
|
-
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
277
|
-
const log = logger.withContext(sessionId, currentTaskId);
|
|
269
|
+
const log = logger.withContext(sessionId, taskId);
|
|
278
270
|
// Build artifact update with command as data
|
|
279
271
|
// Wrap command in commands array as per protocol requirement
|
|
280
272
|
const artifact = {
|
|
281
|
-
taskId
|
|
273
|
+
taskId,
|
|
282
274
|
kind: "artifact-update",
|
|
283
275
|
append: false,
|
|
284
276
|
lastChunk: true,
|
|
@@ -300,7 +292,7 @@ export async function sendCommand(params) {
|
|
|
300
292
|
// Build JSON-RPC response
|
|
301
293
|
const jsonRpcResponse = {
|
|
302
294
|
jsonrpc: "2.0",
|
|
303
|
-
id:
|
|
295
|
+
id: messageId,
|
|
304
296
|
result: artifact,
|
|
305
297
|
};
|
|
306
298
|
// Send via WebSocket
|
|
@@ -309,8 +301,8 @@ export async function sendCommand(params) {
|
|
|
309
301
|
msgType: "agent_response",
|
|
310
302
|
agentId: config.agentId,
|
|
311
303
|
sessionId,
|
|
312
|
-
taskId
|
|
313
|
-
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
304
|
+
taskId,
|
|
305
|
+
msgDetail: JSON.stringify({ ...jsonRpcResponse, hostname: os.hostname() }),
|
|
314
306
|
};
|
|
315
307
|
// Log complete response body
|
|
316
308
|
log.log(`[A2A_COMMAND] Sending command`);
|
|
@@ -329,12 +321,10 @@ export async function sendCard(params) {
|
|
|
329
321
|
throw new Error("sendCard does not support cron mode");
|
|
330
322
|
}
|
|
331
323
|
// ── Normal mode: WebSocket ─────────────────────────────────────
|
|
332
|
-
const
|
|
333
|
-
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
334
|
-
const log = logger.withContext(sessionId, currentTaskId);
|
|
324
|
+
const log = logger.withContext(sessionId, taskId);
|
|
335
325
|
// Build artifact update with cardsInfo as data
|
|
336
326
|
const artifact = {
|
|
337
|
-
taskId
|
|
327
|
+
taskId,
|
|
338
328
|
kind: "artifact-update",
|
|
339
329
|
append: false,
|
|
340
330
|
lastChunk: true,
|
|
@@ -354,7 +344,7 @@ export async function sendCard(params) {
|
|
|
354
344
|
// Build JSON-RPC response
|
|
355
345
|
const jsonRpcResponse = {
|
|
356
346
|
jsonrpc: "2.0",
|
|
357
|
-
id:
|
|
347
|
+
id: messageId,
|
|
358
348
|
result: artifact,
|
|
359
349
|
};
|
|
360
350
|
// Send via WebSocket
|
|
@@ -363,8 +353,8 @@ export async function sendCard(params) {
|
|
|
363
353
|
msgType: "agent_response",
|
|
364
354
|
agentId: config.agentId,
|
|
365
355
|
sessionId,
|
|
366
|
-
taskId
|
|
367
|
-
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
356
|
+
taskId,
|
|
357
|
+
msgDetail: JSON.stringify({ ...jsonRpcResponse, hostname: os.hostname() }),
|
|
368
358
|
};
|
|
369
359
|
log.log(`[A2A_CARD] Sending card`);
|
|
370
360
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
@@ -398,7 +388,7 @@ export async function sendClearContextResponse(params) {
|
|
|
398
388
|
agentId: config.agentId,
|
|
399
389
|
sessionId,
|
|
400
390
|
taskId: sessionId, // Use sessionId as taskId for clearContext
|
|
401
|
-
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
391
|
+
msgDetail: JSON.stringify({ ...jsonRpcResponse, hostname: os.hostname() }),
|
|
402
392
|
};
|
|
403
393
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
404
394
|
log.log(`[CLEAR_CONTEXT] Sent clearContext response`);
|
|
@@ -432,7 +422,7 @@ export async function sendTasksCancelResponse(params) {
|
|
|
432
422
|
agentId: config.agentId,
|
|
433
423
|
sessionId,
|
|
434
424
|
taskId,
|
|
435
|
-
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
425
|
+
msgDetail: JSON.stringify({ ...jsonRpcResponse, hostname: os.hostname() }),
|
|
436
426
|
};
|
|
437
427
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
438
428
|
log.log(`[TASKS_CANCEL] Sent tasks/cancel response`);
|
|
@@ -480,7 +470,7 @@ export async function sendTriggerResponse(params) {
|
|
|
480
470
|
agentId: config.agentId,
|
|
481
471
|
sessionId,
|
|
482
472
|
taskId,
|
|
483
|
-
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
473
|
+
msgDetail: JSON.stringify({ ...jsonRpcResponse, hostname: os.hostname() }),
|
|
484
474
|
};
|
|
485
475
|
log.log(`[TRIGGER_RESPONSE] Sending Trigger response, text=${buildTextPreview(redactedContent)}`);
|
|
486
476
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LogReporterConfig } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Load and validate the JSON config file.
|
|
4
|
+
* Falls back to defaults for optional fields.
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadConfig(configPath: string): LogReporterConfig;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a path template with date wildcards to actual file paths on disk.
|
|
9
|
+
* Scans the directory and returns all files matching the pattern.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveLogFiles(templatePath: string): string[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Config loader: reads JSON config file, validates fields, resolves date wildcards to actual files
|
|
2
|
+
import { readFileSync, readdirSync } from "fs";
|
|
3
|
+
import { dirname, basename, join } from "path";
|
|
4
|
+
// Replace longer tokens first to avoid partial matches
|
|
5
|
+
const WILDCARD_TOKENS = [
|
|
6
|
+
["{year-month-day}", "\\d{4}-\\d{2}-\\d{2}"],
|
|
7
|
+
["{year}{month}{day}", "\\d{8}"],
|
|
8
|
+
["{year}", "\\d{4}"],
|
|
9
|
+
["{month}", "\\d{2}"],
|
|
10
|
+
["{day}", "\\d{2}"],
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Load and validate the JSON config file.
|
|
14
|
+
* Falls back to defaults for optional fields.
|
|
15
|
+
*/
|
|
16
|
+
export function loadConfig(configPath) {
|
|
17
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
18
|
+
const parsed = JSON.parse(raw);
|
|
19
|
+
if (!parsed.logFiles || !Array.isArray(parsed.logFiles) || parsed.logFiles.length === 0) {
|
|
20
|
+
throw new Error("log-reporter config: 'logFiles' must be a non-empty array");
|
|
21
|
+
}
|
|
22
|
+
for (const lf of parsed.logFiles) {
|
|
23
|
+
if (!lf.path || !lf.name) {
|
|
24
|
+
throw new Error(`log-reporter config: each logFile must have 'path' and 'name', got ${JSON.stringify(lf)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
scanIntervalMs: parsed.scanIntervalMs ?? 600000,
|
|
29
|
+
bakDir: parsed.bakDir ?? "/tmp/openclaw",
|
|
30
|
+
reportUrl: parsed.reportUrl ?? "",
|
|
31
|
+
logFiles: parsed.logFiles,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Convert a path with date wildcards into a RegExp that matches the filename part only.
|
|
36
|
+
* Returns { dir, regex } where dir is the directory portion and regex matches filenames.
|
|
37
|
+
*/
|
|
38
|
+
function pathToPattern(templatePath) {
|
|
39
|
+
const dir = dirname(templatePath);
|
|
40
|
+
let pattern = basename(templatePath);
|
|
41
|
+
// Escape regex special chars in the literal parts, then replace tokens
|
|
42
|
+
pattern = escapeRegex(pattern);
|
|
43
|
+
for (const [token, replacement] of WILDCARD_TOKENS) {
|
|
44
|
+
pattern = pattern.replaceAll(escapeRegex(token), replacement);
|
|
45
|
+
}
|
|
46
|
+
return { dir, regex: new RegExp(`^${pattern}$`) };
|
|
47
|
+
}
|
|
48
|
+
function escapeRegex(s) {
|
|
49
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve a path template with date wildcards to actual file paths on disk.
|
|
53
|
+
* Scans the directory and returns all files matching the pattern.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveLogFiles(templatePath) {
|
|
56
|
+
const { dir, regex } = pathToPattern(templatePath);
|
|
57
|
+
let entries;
|
|
58
|
+
try {
|
|
59
|
+
entries = readdirSync(dir);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
return entries
|
|
65
|
+
.filter((f) => regex.test(f))
|
|
66
|
+
.map((f) => join(dir, f))
|
|
67
|
+
.sort(); // chronological order for date-named logs
|
|
68
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CursorStore, FileCursor } from "./types.js";
|
|
2
|
+
export declare function loadCursorStore(storePath: string): CursorStore;
|
|
3
|
+
export declare function saveCursorStore(storePath: string, store: CursorStore): void;
|
|
4
|
+
export declare function getCursor(store: CursorStore, filePath: string): FileCursor | undefined;
|
|
5
|
+
export declare function setCursor(store: CursorStore, filePath: string, cursor: FileCursor): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Cursor state persistence — reads/writes FileCursor entries keyed by file path
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { dirname } from "path";
|
|
4
|
+
export function loadCursorStore(storePath) {
|
|
5
|
+
try {
|
|
6
|
+
const raw = readFileSync(storePath, "utf-8");
|
|
7
|
+
const parsed = JSON.parse(raw);
|
|
8
|
+
return { files: parsed.files ?? {} };
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return { files: {} };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function saveCursorStore(storePath, store) {
|
|
15
|
+
try {
|
|
16
|
+
mkdirSync(dirname(storePath), { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
catch { }
|
|
19
|
+
writeFileSync(storePath, JSON.stringify(store, null, 2), "utf-8");
|
|
20
|
+
}
|
|
21
|
+
export function getCursor(store, filePath) {
|
|
22
|
+
return store.files[filePath];
|
|
23
|
+
}
|
|
24
|
+
export function setCursor(store, filePath, cursor) {
|
|
25
|
+
store.files[filePath] = cursor;
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LogReporterOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Start the log reporter. Runs the first scan immediately, then on the configured interval.
|
|
4
|
+
* Returns a stop function.
|
|
5
|
+
*/
|
|
6
|
+
export declare function startLogReporter(options: LogReporterOptions): Promise<() => void>;
|
|
7
|
+
/**
|
|
8
|
+
* Stop the log reporter timer.
|
|
9
|
+
*/
|
|
10
|
+
export declare function stopLogReporter(): void;
|