@ynhcj/xiaoyi-channel 0.0.171-next → 0.0.172-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 +3 -3
- package/dist/src/bot.js +29 -7
- package/dist/src/channel.js +64 -59
- package/dist/src/client.d.ts +0 -5
- package/dist/src/client.js +0 -10
- package/dist/src/cspl/call_api.js +0 -2
- package/dist/src/cspl/sentinel_hook.js +8 -9
- package/dist/src/formatter.js +23 -12
- package/dist/src/memory-query-handler.js +33 -1
- package/dist/src/monitor.js +9 -20
- package/dist/src/provider.js +62 -33
- package/dist/src/reply-dispatcher.js +45 -29
- package/dist/src/task-manager.d.ts +6 -5
- package/dist/src/task-manager.js +9 -5
- package/dist/src/tools/agent-as-skill-tool.d.ts +2 -45
- package/dist/src/tools/agent-as-skill-tool.js +146 -149
- package/dist/src/tools/calendar-tool.d.ts +2 -24
- package/dist/src/tools/calendar-tool.js +117 -115
- package/dist/src/tools/call-device-tool.d.ts +6 -20
- package/dist/src/tools/call-device-tool.js +138 -116
- package/dist/src/tools/call-phone-tool.d.ts +2 -21
- package/dist/src/tools/call-phone-tool.js +114 -112
- package/dist/src/tools/check-plugin-privilege-tool.d.ts +2 -16
- package/dist/src/tools/check-plugin-privilege-tool.js +143 -141
- package/dist/src/tools/create-alarm-tool.d.ts +2 -39
- package/dist/src/tools/create-alarm-tool.js +231 -229
- package/dist/src/tools/create-all-tools.js +10 -5
- package/dist/src/tools/delete-alarm-tool.d.ts +2 -15
- package/dist/src/tools/delete-alarm-tool.js +136 -134
- package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -16
- package/dist/src/tools/discover-cross-devices-tool.js +124 -121
- package/dist/src/tools/display-a2ui-card-tool.d.ts +2 -27
- package/dist/src/tools/display-a2ui-card-tool.js +68 -65
- package/dist/src/tools/get-alarm-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-alarm-tool-schema.js +16 -10
- package/dist/src/tools/get-calendar-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-calendar-tool-schema.js +12 -8
- package/dist/src/tools/get-collection-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-collection-tool-schema.js +11 -9
- package/dist/src/tools/get-contact-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-contact-tool-schema.js +16 -10
- package/dist/src/tools/get-device-file-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-device-file-tool-schema.js +13 -9
- package/dist/src/tools/get-email-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-email-tool-schema.js +11 -8
- package/dist/src/tools/get-note-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-note-tool-schema.js +14 -9
- package/dist/src/tools/get-photo-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-photo-tool-schema.js +12 -9
- package/dist/src/tools/image-reading-tool.d.ts +2 -28
- package/dist/src/tools/image-reading-tool.js +76 -76
- package/dist/src/tools/location-tool.d.ts +2 -11
- package/dist/src/tools/location-tool.js +92 -93
- package/dist/src/tools/login-token-tool.d.ts +2 -20
- package/dist/src/tools/login-token-tool.js +125 -122
- package/dist/src/tools/modify-alarm-tool.d.ts +2 -47
- package/dist/src/tools/modify-alarm-tool.js +252 -250
- package/dist/src/tools/modify-note-tool.d.ts +2 -20
- package/dist/src/tools/modify-note-tool.js +109 -107
- package/dist/src/tools/note-tool.d.ts +2 -20
- package/dist/src/tools/note-tool.js +108 -106
- package/dist/src/tools/query-app-message-tool.d.ts +2 -28
- package/dist/src/tools/query-app-message-tool.js +113 -111
- package/dist/src/tools/query-memory-data-tool.d.ts +2 -28
- package/dist/src/tools/query-memory-data-tool.js +114 -112
- package/dist/src/tools/query-todo-task-tool.d.ts +2 -24
- package/dist/src/tools/query-todo-task-tool.js +108 -106
- package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -24
- package/dist/src/tools/save-file-to-phone-tool.js +132 -130
- package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -24
- package/dist/src/tools/save-media-to-gallery-tool.js +139 -137
- package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -54
- package/dist/src/tools/save-self-evolution-skill-tool.js +194 -194
- package/dist/src/tools/search-alarm-tool.d.ts +2 -34
- package/dist/src/tools/search-alarm-tool.js +176 -174
- package/dist/src/tools/search-calendar-tool.d.ts +2 -24
- package/dist/src/tools/search-calendar-tool.js +150 -148
- package/dist/src/tools/search-contact-tool.d.ts +2 -16
- package/dist/src/tools/search-contact-tool.js +103 -101
- package/dist/src/tools/search-email-tool.d.ts +2 -21
- package/dist/src/tools/search-email-tool.js +112 -110
- package/dist/src/tools/search-file-tool.d.ts +2 -16
- package/dist/src/tools/search-file-tool.js +106 -104
- package/dist/src/tools/search-message-tool.d.ts +2 -16
- package/dist/src/tools/search-message-tool.js +105 -103
- package/dist/src/tools/search-note-tool.d.ts +2 -16
- package/dist/src/tools/search-note-tool.js +100 -98
- package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -21
- package/dist/src/tools/search-photo-gallery-tool.js +37 -35
- package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -35
- package/dist/src/tools/send-cross-device-task-tool.js +143 -138
- package/dist/src/tools/send-email-tool.d.ts +2 -24
- package/dist/src/tools/send-email-tool.js +110 -108
- package/dist/src/tools/send-file-to-user-tool.d.ts +2 -20
- package/dist/src/tools/send-file-to-user-tool.js +176 -172
- package/dist/src/tools/send-html-card-tool.d.ts +2 -20
- package/dist/src/tools/send-html-card-tool.js +87 -85
- package/dist/src/tools/send-message-tool.d.ts +2 -20
- package/dist/src/tools/send-message-tool.js +124 -122
- package/dist/src/tools/session-manager.d.ts +50 -7
- package/dist/src/tools/session-manager.js +231 -40
- package/dist/src/tools/upload-file-tool.d.ts +2 -20
- package/dist/src/tools/upload-file-tool.js +82 -80
- package/dist/src/tools/upload-photo-tool.d.ts +2 -20
- package/dist/src/tools/upload-photo-tool.js +70 -68
- package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -32
- package/dist/src/tools/xiaoyi-add-collection-tool.js +148 -146
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -20
- package/dist/src/tools/xiaoyi-collection-tool.js +116 -114
- package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -15
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +129 -127
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -16
- package/dist/src/tools/xiaoyi-gui-tool.js +95 -92
- package/dist/src/utils/logger.js +14 -3
- package/dist/src/websocket.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { xiaoyiProvider } from "./src/provider.js";
|
|
|
3
3
|
import { xyPlugin } from "./src/channel.js";
|
|
4
4
|
import registerSentinelHook from "./src/cspl/sentinel_hook.js";
|
|
5
5
|
import { setXYRuntime } from "./src/runtime.js";
|
|
6
|
-
import { markCronToolCall, clearCronToolCall,
|
|
6
|
+
import { markCronToolCall, clearCronToolCall, getSessionContext } from "./src/tools/session-manager.js";
|
|
7
7
|
import { configManager } from "./src/utils/config-manager.js";
|
|
8
8
|
import { setJobPushId } from "./src/utils/cron-push-map.js";
|
|
9
9
|
import { getAllPushIds } from "./src/utils/pushid-manager.js";
|
|
@@ -54,10 +54,10 @@ async function captureCronAddMapping(event, ctx) {
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
console.log(`[CRONMAP] extracted jobId=${jobId}`);
|
|
57
|
-
const sessionCtx =
|
|
57
|
+
const sessionCtx = ctx.sessionKey ? getSessionContext(ctx.sessionKey) : null;
|
|
58
58
|
const sessionId = sessionCtx?.sessionId;
|
|
59
59
|
if (!sessionId) {
|
|
60
|
-
console.log(`[CRONMAP] skip: no sessionId
|
|
60
|
+
console.log(`[CRONMAP] skip: no sessionId (sessionKey=${ctx.sessionKey}, ctxFound=${!!sessionCtx})`);
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
const pushId = await resolvePushId(sessionId);
|
package/dist/src/bot.js
CHANGED
|
@@ -6,7 +6,7 @@ import { downloadFilesFromParts } from "./file-download.js";
|
|
|
6
6
|
import { resolveXYConfig } from "./config.js";
|
|
7
7
|
import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
|
|
8
8
|
import { appendSelfEvolutionKeywordNudge, shouldNudgeForSelfEvolutionKeyword, } from "./self-evolution-keyword.js";
|
|
9
|
-
import { runWithSessionContext } from "./tools/session-manager.js";
|
|
9
|
+
import { registerSession, unregisterSession, runWithSessionContext } from "./tools/session-manager.js";
|
|
10
10
|
import { configManager } from "./utils/config-manager.js";
|
|
11
11
|
import { addPushId } from "./utils/pushid-manager.js";
|
|
12
12
|
import { getPushDataById } from "./utils/pushdata-manager.js";
|
|
@@ -169,10 +169,21 @@ export async function handleXYMessage(params) {
|
|
|
169
169
|
},
|
|
170
170
|
});
|
|
171
171
|
log.log(`[BOT] Resolved route, sessionKey=${route.sessionKey}`);
|
|
172
|
-
//
|
|
173
|
-
// to runWithSessionContext() inside withReplyDispatcher.run, which is the
|
|
174
|
-
// single wrap point for the whole agent turn.
|
|
172
|
+
// Steer injections skip session registration to avoid refCount leaks
|
|
175
173
|
if (!skipReg) {
|
|
174
|
+
registerSession(route.sessionKey, {
|
|
175
|
+
config,
|
|
176
|
+
sessionId: parsed.sessionId,
|
|
177
|
+
distributionSessionId,
|
|
178
|
+
taskId: parsed.taskId,
|
|
179
|
+
messageId: parsed.messageId,
|
|
180
|
+
agentId: route.accountId,
|
|
181
|
+
deviceType,
|
|
182
|
+
appVer: appVer ?? undefined,
|
|
183
|
+
displayVersion: displayVersion ?? undefined,
|
|
184
|
+
modelName,
|
|
185
|
+
runCrossTaskContext: runCrossTaskContext ?? undefined,
|
|
186
|
+
});
|
|
176
187
|
// 🔑 Sync A2A modelName to OpenClaw session store so that session_status
|
|
177
188
|
// reports the correct model. Without this, session_status returns the
|
|
178
189
|
// configured default model instead of the A2A-specified one.
|
|
@@ -380,6 +391,7 @@ export async function handleXYMessage(params) {
|
|
|
380
391
|
}
|
|
381
392
|
streamingSignals.delete(parsed.sessionId);
|
|
382
393
|
decrementTaskIdRef(parsed.sessionId);
|
|
394
|
+
unregisterSession(route.sessionKey);
|
|
383
395
|
log.log(`[BOT] Cleanup completed`);
|
|
384
396
|
},
|
|
385
397
|
run: () => {
|
|
@@ -389,7 +401,6 @@ export async function handleXYMessage(params) {
|
|
|
389
401
|
// signal init complete to release the global dispatch gate
|
|
390
402
|
// for the next session.
|
|
391
403
|
const dispatchPromise = runWithSessionContext(sessionContext, async () => {
|
|
392
|
-
log.log(`[ALS-PROOF] bot entered dispatch scope sessionId=${sessionContext.sessionId} taskId=${sessionContext.taskId} isSteer=false`);
|
|
393
404
|
log.log(`[BOT-DISPATCH] dispatchReplyFromConfig starting, body.length=${ctxPayload.Body?.length ?? 0}`);
|
|
394
405
|
try {
|
|
395
406
|
const result = await core.channel.reply.dispatchReplyFromConfig({
|
|
@@ -422,7 +433,7 @@ export async function handleXYMessage(params) {
|
|
|
422
433
|
errLog.error("Failed to handle XY message:", err);
|
|
423
434
|
runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
|
|
424
435
|
errLog.log(`[BOT] Error occurred, attempting cleanup`);
|
|
425
|
-
// 🔑 错误时也要清理taskId
|
|
436
|
+
// 🔑 错误时也要清理taskId和session
|
|
426
437
|
try {
|
|
427
438
|
const params = message.params;
|
|
428
439
|
const sessionId = params?.sessionId;
|
|
@@ -430,6 +441,18 @@ export async function handleXYMessage(params) {
|
|
|
430
441
|
errLog.log(`[BOT] Cleaning up after error`);
|
|
431
442
|
// 清理 taskId
|
|
432
443
|
decrementTaskIdRef(sessionId);
|
|
444
|
+
// 清理 session
|
|
445
|
+
const core = getXYRuntime();
|
|
446
|
+
const route = core.channel.routing.resolveAgentRoute({
|
|
447
|
+
cfg,
|
|
448
|
+
channel: "xiaoyi-channel",
|
|
449
|
+
accountId,
|
|
450
|
+
peer: {
|
|
451
|
+
kind: "direct",
|
|
452
|
+
id: sessionId,
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
unregisterSession(route.sessionKey);
|
|
433
456
|
errLog.log(`[BOT] Cleanup completed after error`);
|
|
434
457
|
}
|
|
435
458
|
}
|
|
@@ -614,7 +637,6 @@ async function dispatchSteerWhenReady(params) {
|
|
|
614
637
|
},
|
|
615
638
|
run: () => {
|
|
616
639
|
return runWithSessionContext(sessionContext, async () => {
|
|
617
|
-
log.log(`[ALS-PROOF] bot entered steer dispatch scope sessionId=${sessionContext.sessionId} taskId=${sessionContext.taskId} isSteer=true`);
|
|
618
640
|
const result = await core.channel.reply.dispatchReplyFromConfig({
|
|
619
641
|
ctx: ctxPayload,
|
|
620
642
|
cfg: params.cfg,
|
package/dist/src/channel.js
CHANGED
|
@@ -2,55 +2,15 @@ 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 } from "./tools/session-manager.js";
|
|
5
|
+
import { getCurrentSessionContext, registerSession } from "./tools/session-manager.js";
|
|
6
|
+
import { createAllTools } from "./tools/create-all-tools.js";
|
|
6
7
|
import { logger } from "./utils/logger.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 { displayA2UICardTool } from "./tools/display-a2ui-card-tool.js";
|
|
29
|
-
import { checkPluginPrivilegeTool } from "./tools/check-plugin-privilege-tool.js";
|
|
30
|
-
const ALL_TOOLS = [
|
|
31
|
-
locationTool,
|
|
32
|
-
discoverCrossDevicesTool,
|
|
33
|
-
sendCrossDeviceTaskTool,
|
|
34
|
-
displayA2UICardTool,
|
|
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
|
-
];
|
|
8
|
+
/**
|
|
9
|
+
* Prefix used for synthetic sessionIds created during cron-triggered tool
|
|
10
|
+
* execution. `sendCommand()` checks this prefix to route commands through
|
|
11
|
+
* the push channel instead of the (non-existent) WebSocket session.
|
|
12
|
+
*/
|
|
13
|
+
const CRON_SESSION_PREFIX = "cron-";
|
|
54
14
|
/**
|
|
55
15
|
* Xiaoyi Channel Plugin for OpenClaw.
|
|
56
16
|
* Implements Xiaoyi A2A protocol with dual WebSocket connections.
|
|
@@ -72,7 +32,7 @@ export const xyPlugin = {
|
|
|
72
32
|
],
|
|
73
33
|
},
|
|
74
34
|
capabilities: {
|
|
75
|
-
chatTypes: ["direct"],
|
|
35
|
+
chatTypes: ["direct"], // Only private chat (no group support)
|
|
76
36
|
polls: false,
|
|
77
37
|
threads: false,
|
|
78
38
|
media: true,
|
|
@@ -89,11 +49,59 @@ export const xyPlugin = {
|
|
|
89
49
|
schema: xyConfigSchema,
|
|
90
50
|
},
|
|
91
51
|
outbound: xyOutbound,
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Provide channel-specific agent tools.
|
|
54
|
+
*
|
|
55
|
+
* Two execution contexts are supported:
|
|
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(", ")})`);
|
|
97
105
|
return filtered;
|
|
98
106
|
},
|
|
99
107
|
messaging: {
|
|
@@ -105,6 +113,7 @@ export const xyPlugin = {
|
|
|
105
113
|
},
|
|
106
114
|
targetResolver: {
|
|
107
115
|
looksLikeId: (raw) => {
|
|
116
|
+
// 信任所有非空字符串作为有效的 sessionId
|
|
108
117
|
const trimmed = raw.trim();
|
|
109
118
|
return trimmed.length > 0;
|
|
110
119
|
},
|
|
@@ -130,20 +139,16 @@ export const xyPlugin = {
|
|
|
130
139
|
reload: {
|
|
131
140
|
configPrefixes: ["channels.xiaoyi-channel"],
|
|
132
141
|
},
|
|
142
|
+
// Gateway adapter for receiving messages
|
|
133
143
|
gateway: {
|
|
134
144
|
async startAccount(context) {
|
|
135
145
|
const { monitorXYProvider } = await import("./monitor.js");
|
|
136
|
-
const { createXyAcpBindingManager } = await import("./acp-session-binding.js");
|
|
137
146
|
const account = resolveXYConfig(context.cfg);
|
|
138
147
|
context.setStatus?.({
|
|
139
148
|
accountId: context.accountId,
|
|
140
149
|
wsUrl: account.wsUrl,
|
|
141
150
|
});
|
|
142
151
|
context.log?.info(`[${context.accountId}] starting xiaoyi channel (wsUrl: ${account.wsUrl})`);
|
|
143
|
-
// Register ACP session binding adapter for this account.
|
|
144
|
-
// Enables sessions_spawn(runtime="acp") to bind subagent sessions
|
|
145
|
-
// to the current A2A conversation.
|
|
146
|
-
createXyAcpBindingManager({ accountId: context.accountId, cfg: context.cfg });
|
|
147
152
|
return monitorXYProvider({
|
|
148
153
|
config: context.cfg,
|
|
149
154
|
runtime: context.runtime,
|
package/dist/src/client.d.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
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;
|
|
9
4
|
/**
|
|
10
5
|
* Get or create a WebSocket manager for the given configuration.
|
|
11
6
|
* Reuses existing managers if config matches.
|
package/dist/src/client.js
CHANGED
|
@@ -13,16 +13,6 @@ 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
|
-
}
|
|
26
16
|
/**
|
|
27
17
|
* Get or create a WebSocket manager for the given configuration.
|
|
28
18
|
* Reuses existing managers if config matches.
|
|
@@ -5,7 +5,6 @@ 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';
|
|
9
8
|
function buildHeadersForCelia(config, sessionId) {
|
|
10
9
|
if (!config.uid || !config.apiKey || !config.skillId || !config.requestFrom) {
|
|
11
10
|
throw new Error('[SENTINEL HOOK] Missing required configuration: uid, apiKey, skillId, or requestFrom is not defined');
|
|
@@ -90,7 +89,6 @@ export async function callApi(questionText, api, sessionId, action) {
|
|
|
90
89
|
};
|
|
91
90
|
const httpBody = JSON.stringify(payload);
|
|
92
91
|
const apiUrl = `${config.api.url}${API_URL_SUFFIX}`;
|
|
93
|
-
logger.log(`[SENTINEL HOOK] callApi: action=${action}, x-hag-trace-id=${sessionId}, url=${apiUrl}`);
|
|
94
92
|
return new Promise((resolve, reject) => {
|
|
95
93
|
const options = buildRequestOptions(apiUrl, headersForCelia, config.api.timeout);
|
|
96
94
|
const req = https.request(options, (res) => {
|
|
@@ -6,16 +6,15 @@ 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 { getSessionContext } 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
|
-
|
|
18
|
-
logger.log(`[SENTINEL HOOK] Session ID: ${sessionId} (fromALS: ${!!sessionCtx?.sessionId})`);
|
|
15
|
+
// 生成sessionID
|
|
16
|
+
const sessionId = (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
|
|
17
|
+
logger.log(`[SENTINEL HOOK] Generated Session ID: ${sessionId}`);
|
|
19
18
|
// 处理 TOOL_INPUT 数据采集、发送数据,根据扫描结果决定是否阻塞
|
|
20
19
|
try {
|
|
21
20
|
let scanResult = null;
|
|
@@ -44,10 +43,9 @@ export default function register(api) {
|
|
|
44
43
|
}
|
|
45
44
|
try {
|
|
46
45
|
logger.log(`[SENTINEL HOOK] after_tool_call_event toolName: ${event.toolName}`);
|
|
47
|
-
//
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
logger.log(`[SENTINEL HOOK] Session ID: ${sessionId} (fromALS: ${!!sessionCtx?.sessionId})`);
|
|
46
|
+
// 生成sessionID
|
|
47
|
+
const sessionId = (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
|
|
48
|
+
logger.log(`[SENTINEL HOOK] Generated Session ID: ${sessionId}`);
|
|
51
49
|
// 处理TOOL_OUTPUT数据采集(保持现有逻辑)
|
|
52
50
|
const resultText = extractResultText(event, event.toolName);
|
|
53
51
|
const resultTextLength = resultText.length;
|
|
@@ -80,6 +78,7 @@ export default function register(api) {
|
|
|
80
78
|
logger.log(`[SENTINEL HOOK] TOOL_OUTPUT response: status=${result.status}.`);
|
|
81
79
|
if (result.status === 'REJECT') {
|
|
82
80
|
logger.warn('[SENTINEL HOOK] REJECT detected, attempting steer injection');
|
|
81
|
+
const sessionCtx = ctx.sessionKey ? getSessionContext(ctx.sessionKey) : null;
|
|
83
82
|
if (sessionCtx?.sessionId && sessionCtx?.taskId) {
|
|
84
83
|
await tryInjectSteer({
|
|
85
84
|
sessionId: sessionCtx.sessionId,
|
package/dist/src/formatter.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
3
|
import { getXYWebSocketManager } from "./client.js";
|
|
4
4
|
import { logger } from "./utils/logger.js";
|
|
5
|
+
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
5
6
|
import { redactSensitiveText, containsSensitiveInfo } from "./sensitive-redactor.js";
|
|
6
7
|
import { rewriteOutboundApprovalText } from "./approval-bridge.js";
|
|
7
8
|
import { isCronToolCall, getCurrentCronJobId } from "./tools/session-manager.js";
|
|
@@ -157,7 +158,11 @@ export async function sendReasoningTextUpdate(params) {
|
|
|
157
158
|
*/
|
|
158
159
|
export async function sendStatusUpdate(params) {
|
|
159
160
|
const { config, sessionId, taskId, messageId, text, state } = params;
|
|
160
|
-
|
|
161
|
+
// Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
|
|
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);
|
|
161
166
|
// 审批桥接和脱敏
|
|
162
167
|
const bridgedText = rewriteOutboundApprovalText(sessionId, text);
|
|
163
168
|
const redactedText = redactSensitiveText(bridgedText);
|
|
@@ -172,7 +177,7 @@ export async function sendStatusUpdate(params) {
|
|
|
172
177
|
],
|
|
173
178
|
});
|
|
174
179
|
const statusUpdate = {
|
|
175
|
-
taskId,
|
|
180
|
+
taskId: currentTaskId,
|
|
176
181
|
kind: "status-update",
|
|
177
182
|
final: false, // Status updates should not end the stream
|
|
178
183
|
status: {
|
|
@@ -183,7 +188,7 @@ export async function sendStatusUpdate(params) {
|
|
|
183
188
|
// Build JSON-RPC response
|
|
184
189
|
const jsonRpcResponse = {
|
|
185
190
|
jsonrpc: "2.0",
|
|
186
|
-
id:
|
|
191
|
+
id: currentMessageId,
|
|
187
192
|
result: statusUpdate,
|
|
188
193
|
};
|
|
189
194
|
// Send via WebSocket
|
|
@@ -192,7 +197,7 @@ export async function sendStatusUpdate(params) {
|
|
|
192
197
|
msgType: "agent_response",
|
|
193
198
|
agentId: config.agentId,
|
|
194
199
|
sessionId,
|
|
195
|
-
taskId,
|
|
200
|
+
taskId: currentTaskId,
|
|
196
201
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
197
202
|
};
|
|
198
203
|
// Log complete response body
|
|
@@ -265,11 +270,15 @@ export async function sendCommand(params) {
|
|
|
265
270
|
return sendCommandViaPush({ config, command: commands[0], pushId });
|
|
266
271
|
}
|
|
267
272
|
// ── Normal mode: WebSocket ─────────────────────────────────────
|
|
268
|
-
|
|
273
|
+
// Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
|
|
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
278
|
// Build artifact update with command as data
|
|
270
279
|
// Wrap command in commands array as per protocol requirement
|
|
271
280
|
const artifact = {
|
|
272
|
-
taskId,
|
|
281
|
+
taskId: currentTaskId,
|
|
273
282
|
kind: "artifact-update",
|
|
274
283
|
append: false,
|
|
275
284
|
lastChunk: true,
|
|
@@ -291,7 +300,7 @@ export async function sendCommand(params) {
|
|
|
291
300
|
// Build JSON-RPC response
|
|
292
301
|
const jsonRpcResponse = {
|
|
293
302
|
jsonrpc: "2.0",
|
|
294
|
-
id:
|
|
303
|
+
id: currentMessageId,
|
|
295
304
|
result: artifact,
|
|
296
305
|
};
|
|
297
306
|
// Send via WebSocket
|
|
@@ -300,7 +309,7 @@ export async function sendCommand(params) {
|
|
|
300
309
|
msgType: "agent_response",
|
|
301
310
|
agentId: config.agentId,
|
|
302
311
|
sessionId,
|
|
303
|
-
taskId,
|
|
312
|
+
taskId: currentTaskId,
|
|
304
313
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
305
314
|
};
|
|
306
315
|
// Log complete response body
|
|
@@ -320,10 +329,12 @@ export async function sendCard(params) {
|
|
|
320
329
|
throw new Error("sendCard does not support cron mode");
|
|
321
330
|
}
|
|
322
331
|
// ── Normal mode: WebSocket ─────────────────────────────────────
|
|
323
|
-
const
|
|
332
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
333
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
334
|
+
const log = logger.withContext(sessionId, currentTaskId);
|
|
324
335
|
// Build artifact update with cardsInfo as data
|
|
325
336
|
const artifact = {
|
|
326
|
-
taskId,
|
|
337
|
+
taskId: currentTaskId,
|
|
327
338
|
kind: "artifact-update",
|
|
328
339
|
append: false,
|
|
329
340
|
lastChunk: true,
|
|
@@ -343,7 +354,7 @@ export async function sendCard(params) {
|
|
|
343
354
|
// Build JSON-RPC response
|
|
344
355
|
const jsonRpcResponse = {
|
|
345
356
|
jsonrpc: "2.0",
|
|
346
|
-
id:
|
|
357
|
+
id: currentMessageId,
|
|
347
358
|
result: artifact,
|
|
348
359
|
};
|
|
349
360
|
// Send via WebSocket
|
|
@@ -352,7 +363,7 @@ export async function sendCard(params) {
|
|
|
352
363
|
msgType: "agent_response",
|
|
353
364
|
agentId: config.agentId,
|
|
354
365
|
sessionId,
|
|
355
|
-
taskId,
|
|
366
|
+
taskId: currentTaskId,
|
|
356
367
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
357
368
|
};
|
|
358
369
|
log.log(`[A2A_CARD] Sending card`);
|
|
@@ -42,6 +42,9 @@ export async function handleMemoryQueryEvent(context, cfg) {
|
|
|
42
42
|
case "MemoryStateSet":
|
|
43
43
|
result = handleMemoryStateSet(params);
|
|
44
44
|
break;
|
|
45
|
+
case "MemoryStateGet":
|
|
46
|
+
result = handleMemoryStateGet();
|
|
47
|
+
break;
|
|
45
48
|
case "UserMdQuery":
|
|
46
49
|
result = handleUserMdQuery();
|
|
47
50
|
break;
|
|
@@ -135,6 +138,35 @@ function handleMemoryStateSet(params) {
|
|
|
135
138
|
logger.log(`[MEMORY-QUERY] updated ${MEMORY_STATE_KEY}=${value} in ${filePath}`);
|
|
136
139
|
return { code: 0 };
|
|
137
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Read MEMORYSTATE from .xiaoyiruntime and return its boolean value.
|
|
143
|
+
* Missing file or key defaults to false.
|
|
144
|
+
*/
|
|
145
|
+
function handleMemoryStateGet() {
|
|
146
|
+
const filePath = resolveXiaoyiRuntimePath();
|
|
147
|
+
let content;
|
|
148
|
+
try {
|
|
149
|
+
content = readFileSync(filePath, "utf-8");
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
if (err.code === "ENOENT") {
|
|
153
|
+
logger.log(`[MEMORY-QUERY] ${filePath} not found`);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
logger.error(`[MEMORY-QUERY] Failed to read ${filePath}:`, err);
|
|
157
|
+
}
|
|
158
|
+
return { memoryState: false };
|
|
159
|
+
}
|
|
160
|
+
for (const line of content.split("\n")) {
|
|
161
|
+
if (line.startsWith(`${MEMORY_STATE_KEY}=`)) {
|
|
162
|
+
const value = line.slice(`${MEMORY_STATE_KEY}=`.length).trim();
|
|
163
|
+
logger.log(`[MEMORY-QUERY] read ${MEMORY_STATE_KEY}=${value} from ${filePath}`);
|
|
164
|
+
return { memoryState: value === "true" };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
logger.log(`[MEMORY-QUERY] ${MEMORY_STATE_KEY} not found in ${filePath}`);
|
|
168
|
+
return { memoryState: false };
|
|
169
|
+
}
|
|
138
170
|
/**
|
|
139
171
|
* Read ~/.openclaw/workspace/USER.md and return content in fileDetail.
|
|
140
172
|
*/
|
|
@@ -235,7 +267,7 @@ function handleMemoryHistory() {
|
|
|
235
267
|
// Build ans array sorted by date ascending, each entry is { <date>: [...] }.
|
|
236
268
|
const ans = Array.from(byDate.keys())
|
|
237
269
|
.sort()
|
|
238
|
-
.map((dateStr) => ({ [dateStr]: byDate.get(dateStr) }));
|
|
270
|
+
.map((dateStr) => ({ [dateStr]: byDate.get(dateStr).reverse() }));
|
|
239
271
|
// Prune memory.log: keep only the last 30 days.
|
|
240
272
|
try {
|
|
241
273
|
const newContent = keptLines.length > 0 ? `${keptLines.join("\n")}\n` : "";
|
package/dist/src/monitor.js
CHANGED
|
@@ -10,9 +10,8 @@ import { handleLoginTokenEvent } from "./login-token-handler.js";
|
|
|
10
10
|
import { handleCronQueryEvent } from "./cron-query-handler.js";
|
|
11
11
|
import { handleMemoryQueryEvent } from "./memory-query-handler.js";
|
|
12
12
|
import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
|
|
13
|
+
import { cleanupStaleSessions, getActiveSessionCount, cleanupAllSessions } from "./tools/session-manager.js";
|
|
13
14
|
import { logger } from "./utils/logger.js";
|
|
14
|
-
import { XYFileUploadService } from "./file-upload.js";
|
|
15
|
-
import { startLogReporter } from "./log-reporter/index.js";
|
|
16
15
|
/**
|
|
17
16
|
* Per-session serial queue that ensures messages from the same session are processed
|
|
18
17
|
* in arrival order while allowing different sessions to run concurrently.
|
|
@@ -76,8 +75,6 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
76
75
|
let globalDispatchInitGate = Promise.resolve();
|
|
77
76
|
// Health check interval
|
|
78
77
|
let healthCheckInterval = null;
|
|
79
|
-
// Log reporter stop handle
|
|
80
|
-
let stopLogReporter = null;
|
|
81
78
|
return new Promise((resolve, reject) => {
|
|
82
79
|
// Event handlers (defined early so they can be referenced in cleanup)
|
|
83
80
|
const messageHandler = (message, sessionId, serverId) => {
|
|
@@ -217,11 +214,6 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
217
214
|
};
|
|
218
215
|
const cleanup = () => {
|
|
219
216
|
logger.log("XY gateway: cleaning up...");
|
|
220
|
-
// Stop log reporter
|
|
221
|
-
if (stopLogReporter) {
|
|
222
|
-
stopLogReporter();
|
|
223
|
-
stopLogReporter = null;
|
|
224
|
-
}
|
|
225
217
|
// 🔍 Diagnose before cleanup
|
|
226
218
|
logger.log("[DIAGNOSTICS] Checking WebSocket managers before cleanup...");
|
|
227
219
|
diagnoseAllManagers();
|
|
@@ -247,7 +239,8 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
247
239
|
wsManager.disconnect();
|
|
248
240
|
// ✅ Remove manager from cache to prevent reusing dirty state
|
|
249
241
|
removeXYWebSocketManager(account);
|
|
250
|
-
//
|
|
242
|
+
// Clean up all active sessions
|
|
243
|
+
cleanupAllSessions();
|
|
251
244
|
loggedServers.clear();
|
|
252
245
|
activeMessages.clear();
|
|
253
246
|
logger.log(`[MONITOR-HANDLER] Cleanup complete, cleared active messages and sessions`);
|
|
@@ -316,7 +309,12 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
316
309
|
if (cleaned > 0) {
|
|
317
310
|
logger.log(`[HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
|
|
318
311
|
}
|
|
319
|
-
//
|
|
312
|
+
// Cleanup stale sessions (older than 10min TTL)
|
|
313
|
+
const cleanedSessions = cleanupStaleSessions();
|
|
314
|
+
const remainingSessions = getActiveSessionCount();
|
|
315
|
+
if (cleanedSessions > 0 || remainingSessions > 0) {
|
|
316
|
+
logger.log(`[HEALTH CHECK] Sessions: cleaned=${cleanedSessions}, active=${remainingSessions}`);
|
|
317
|
+
}
|
|
320
318
|
// Cleanup stale temp files (older than 24 hours)
|
|
321
319
|
void cleanupStaleTempFiles();
|
|
322
320
|
}, 6 * 60 * 60 * 1000); // 6 hours
|
|
@@ -324,15 +322,6 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
324
322
|
wsManager.connect()
|
|
325
323
|
.then(() => {
|
|
326
324
|
logger.log("XY gateway: started successfully");
|
|
327
|
-
// Start log reporter (independent periodic scanner + uploader)
|
|
328
|
-
startLogReporter({
|
|
329
|
-
configPath: "/home/ynhcj/.openclaw/log-reporter-config.json",
|
|
330
|
-
uploadService: new XYFileUploadService(account.fileUploadUrl, account.apiKey, account.uid),
|
|
331
|
-
}).then((stop) => {
|
|
332
|
-
stopLogReporter = stop;
|
|
333
|
-
}).catch((err) => {
|
|
334
|
-
logger.warn(`Log reporter not started: ${String(err)}`);
|
|
335
|
-
});
|
|
336
325
|
})
|
|
337
326
|
.catch((err) => {
|
|
338
327
|
// Connection failed but don't reject - continue monitoring for reconnection
|