@ynhcj/xiaoyi-channel 0.0.124-beta โ 0.0.124-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.d.ts +3 -6
- package/dist/index.js +85 -54
- package/dist/provider-discovery.d.ts +2 -0
- package/dist/provider-discovery.js +4 -0
- package/dist/src/bot.d.ts +2 -0
- package/dist/src/bot.js +104 -103
- package/dist/src/client.d.ts +1 -5
- package/dist/src/client.js +25 -36
- package/dist/src/cspl/call-api.d.ts +6 -0
- package/dist/src/cspl/call-api.js +37 -16
- package/dist/src/cspl/config.d.ts +11 -1
- package/dist/src/cspl/config.js +30 -0
- package/dist/src/cspl/middleware.d.ts +8 -0
- package/dist/src/cspl/middleware.js +90 -0
- package/dist/src/cspl/steer-context.d.ts +7 -0
- package/dist/src/cspl/steer-context.js +47 -0
- package/dist/src/file-download.js +4 -3
- package/dist/src/file-upload.js +19 -18
- package/dist/src/formatter.js +32 -44
- package/dist/src/heartbeat.js +4 -3
- package/dist/src/login-token-handler.js +13 -10
- package/dist/src/message-queue.js +2 -1
- package/dist/src/monitor.js +54 -42
- package/dist/src/outbound.js +22 -18
- package/dist/src/provider.js +82 -30
- package/dist/src/push.js +16 -15
- package/dist/src/reply-dispatcher.d.ts +3 -1
- package/dist/src/reply-dispatcher.js +64 -62
- package/dist/src/self-evolution-handler.js +11 -14
- package/dist/src/skill-retriever/hooks.js +4 -4
- package/dist/src/skill-retriever/tool-search.js +13 -17
- package/dist/src/steer-injector.js +1 -1
- package/dist/src/task-manager.d.ts +4 -27
- package/dist/src/task-manager.js +13 -78
- package/dist/src/tools/calendar-tool.js +5 -1
- package/dist/src/tools/call-phone-tool.js +5 -1
- package/dist/src/tools/create-alarm-tool.js +5 -1
- package/dist/src/tools/delete-alarm-tool.js +5 -1
- package/dist/src/tools/image-reading-tool.d.ts +1 -1
- package/dist/src/tools/image-reading-tool.js +38 -114
- package/dist/src/tools/location-tool.js +5 -1
- package/dist/src/tools/login-token-tool.js +13 -2
- package/dist/src/tools/modify-alarm-tool.js +5 -1
- package/dist/src/tools/modify-note-tool.js +5 -1
- package/dist/src/tools/note-tool.js +5 -1
- package/dist/src/tools/query-app-message-tool.js +5 -1
- package/dist/src/tools/query-memory-data-tool.js +5 -1
- package/dist/src/tools/query-todo-task-tool.js +5 -1
- package/dist/src/tools/save-file-to-phone-tool.js +5 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +5 -1
- package/dist/src/tools/search-alarm-tool.js +5 -1
- package/dist/src/tools/search-calendar-tool.js +5 -1
- package/dist/src/tools/search-contact-tool.js +5 -1
- package/dist/src/tools/search-email-tool.js +5 -1
- package/dist/src/tools/search-file-tool.js +5 -1
- package/dist/src/tools/search-message-tool.js +5 -1
- package/dist/src/tools/search-note-tool.js +5 -1
- package/dist/src/tools/search-photo-gallery-tool.js +5 -1
- package/dist/src/tools/send-email-tool.js +5 -1
- package/dist/src/tools/send-file-to-user-tool.js +10 -4
- package/dist/src/tools/send-message-tool.js +5 -1
- package/dist/src/tools/session-helper.d.ts +24 -0
- package/dist/src/tools/session-helper.js +45 -0
- package/dist/src/tools/session-manager.d.ts +8 -0
- package/dist/src/tools/session-manager.js +28 -24
- package/dist/src/tools/upload-file-tool.js +5 -1
- package/dist/src/tools/upload-photo-tool.js +5 -1
- package/dist/src/tools/xiaoyi-add-collection-tool.js +5 -1
- package/dist/src/tools/xiaoyi-collection-tool.js +5 -1
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +5 -1
- package/dist/src/tools/xiaoyi-gui-tool.js +7 -2
- package/dist/src/trigger-handler.js +8 -9
- package/dist/src/utils/logger.js +105 -19
- package/dist/src/utils/self-evolution-manager.js +3 -2
- package/dist/src/utils/throw.d.ts +5 -0
- package/dist/src/utils/throw.js +10 -0
- package/dist/src/websocket.js +35 -31
- package/dist/src/xy-session-store.d.ts +79 -0
- package/dist/src/xy-session-store.js +153 -0
- package/openclaw.plugin.json +4 -0
- package/package.json +6 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
1
|
declare const _default: {
|
|
3
2
|
id: string;
|
|
4
3
|
name: string;
|
|
5
4
|
description: string;
|
|
6
|
-
configSchema: import("openclaw/plugin-sdk").
|
|
7
|
-
register: (
|
|
8
|
-
|
|
9
|
-
setChannelRuntime?: (runtime: import("openclaw/plugin-sdk").PluginRuntime) => void;
|
|
10
|
-
};
|
|
5
|
+
configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
|
|
6
|
+
register: NonNullable<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition["register"]>;
|
|
7
|
+
} & Pick<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
|
|
11
8
|
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -1,69 +1,100 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
|
2
2
|
import { xiaoyiProvider } from "./src/provider.js";
|
|
3
3
|
import { xyPlugin } from "./src/channel.js";
|
|
4
|
-
import {
|
|
4
|
+
import { callCsplApiWithConfig } from "./src/cspl/call-api.js";
|
|
5
|
+
import { getCsplConfig, initCsplConfigFromXYConfig } from "./src/cspl/config.js";
|
|
5
6
|
import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH, STEER_ABORT_MESSAGE, } from "./src/cspl/constants.js";
|
|
6
7
|
import { extractResultText, parseSecurityResult, processText, validateAndTruncateText, } from "./src/cspl/utils.js";
|
|
8
|
+
import { injectCsplSteer } from "./src/cspl/steer-context.js";
|
|
9
|
+
import { getSessionContext } from "./src/tools/session-manager.js";
|
|
10
|
+
import { logger } from "./src/utils/logger.js";
|
|
7
11
|
import { setXYRuntime } from "./src/runtime.js";
|
|
8
|
-
import { tryInjectSteer } from "./src/steer-injector.js";
|
|
9
12
|
import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
|
|
10
13
|
import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
|
|
11
14
|
import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
function registerFullHooks(api) {
|
|
16
|
+
// SKILL RETRIEVER HOOK: before_prompt_build hook
|
|
17
|
+
const pluginConfig = api.pluginConfig || {};
|
|
18
|
+
const skillRetrieverConfig = normalizeToolRetrieverConfig({
|
|
19
|
+
enabled: pluginConfig.skillRetrieverEnabled ?? true,
|
|
20
|
+
maxTools: pluginConfig.skillRetrieverMaxTools ?? 2,
|
|
21
|
+
includeUninstalledOnly: true,
|
|
22
|
+
envFilePath: "~/.openclaw/.xiaoyienv",
|
|
23
|
+
timeoutMs: pluginConfig.skillRetrieverTimeoutMs ?? 1000,
|
|
24
|
+
});
|
|
25
|
+
const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
|
|
26
|
+
api.on("before_prompt_build", beforePromptBuildHandler);
|
|
27
|
+
registerSelfEvolutionToolResultNudge(api);
|
|
28
|
+
}
|
|
29
|
+
function registerCsplHook(api) {
|
|
30
|
+
// CSPL security scanning via after_tool_call hook.
|
|
31
|
+
// When CSPL returns REJECT, injects a steer message (with /steer prefix)
|
|
32
|
+
// into the active Pi run to interrupt the agent.
|
|
33
|
+
// Only registered in "full" mode because it depends on handleXYMessage
|
|
34
|
+
// having cached cfg/runtime via setCsplSteerContext.
|
|
35
|
+
api.on("after_tool_call", async (event, ctx) => {
|
|
36
|
+
if (!ALLOWED_TOOLS.includes(event.toolName)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const resultText = extractResultText(event, event.toolName);
|
|
41
|
+
const resultLength = resultText.length;
|
|
42
|
+
if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
|
|
34
43
|
return;
|
|
35
44
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
logger.log(`[SENTINEL HOOK] after_tool_call: toolName=${event.toolName}, textLength=${resultLength}`);
|
|
46
|
+
const questionText = {
|
|
47
|
+
subSceneID: "TOOL_OUTPUT",
|
|
48
|
+
tool: event.toolName,
|
|
49
|
+
output: [{ content: "" }],
|
|
50
|
+
};
|
|
51
|
+
const originText = processText(resultText);
|
|
52
|
+
questionText.output[0].content = originText;
|
|
53
|
+
let finalJson = JSON.stringify(questionText);
|
|
54
|
+
if (finalJson.length > MAX_TEXT_LENGTH) {
|
|
55
|
+
const diff = finalJson.length - MAX_TEXT_LENGTH;
|
|
56
|
+
const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
|
|
57
|
+
questionText.output[0].content = trimmed;
|
|
58
|
+
finalJson = JSON.stringify(questionText);
|
|
59
|
+
}
|
|
60
|
+
const sessionCtx = getSessionContext(ctx.sessionKey ?? "");
|
|
61
|
+
const csplConfig = sessionCtx
|
|
62
|
+
? initCsplConfigFromXYConfig(sessionCtx.config)
|
|
63
|
+
: getCsplConfig();
|
|
64
|
+
const csplStartTime = Date.now();
|
|
65
|
+
const response = await callCsplApiWithConfig(finalJson, csplConfig);
|
|
66
|
+
const csplElapsed = Date.now() - csplStartTime;
|
|
67
|
+
const result = parseSecurityResult(response);
|
|
68
|
+
logger.log(`[SENTINEL HOOK] Security result: status=${result.status}, toolName=${event.toolName}, elapsed=${csplElapsed}ms`);
|
|
69
|
+
if (result.status === "REJECT") {
|
|
70
|
+
logger.log(`[SENTINEL HOOK] REJECT - injecting steer message`);
|
|
71
|
+
if (sessionCtx) {
|
|
72
|
+
await injectCsplSteer(sessionCtx.sessionId, sessionCtx.taskId, STEER_ABORT_MESSAGE);
|
|
56
73
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
console.log(`[SENTINEL HOOK] Security result: status=${result.status}`);
|
|
60
|
-
if (result.status === "REJECT") {
|
|
61
|
-
await tryInjectSteer(ctx.sessionKey, STEER_ABORT_MESSAGE);
|
|
74
|
+
else {
|
|
75
|
+
logger.error("[SENTINEL HOOK] No session context, cannot inject steer");
|
|
62
76
|
}
|
|
63
77
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export default definePluginEntry({
|
|
85
|
+
id: "xiaoyi-channel",
|
|
86
|
+
name: "Xiaoyi Channel",
|
|
87
|
+
description: "Xiaoyi channel plugin - Xiaoyi A2A protocol integration",
|
|
88
|
+
register(api) {
|
|
89
|
+
// Always register the provider so wrapStreamFn/prepareExtraParams work
|
|
90
|
+
// in ALL registration modes (not just "full").
|
|
91
|
+
api.registerProvider(xiaoyiProvider);
|
|
92
|
+
// Register channel plugin and set runtime
|
|
93
|
+
api.registerChannel({ plugin: xyPlugin });
|
|
94
|
+
setXYRuntime(api.runtime);
|
|
95
|
+
if (api.registrationMode === "full") {
|
|
96
|
+
registerFullHooks(api);
|
|
97
|
+
registerCsplHook(api);
|
|
98
|
+
}
|
|
68
99
|
},
|
|
69
100
|
});
|
package/dist/src/bot.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface HandleXYMessageParams {
|
|
|
9
9
|
message: A2AJsonRpcRequest;
|
|
10
10
|
accountId: string;
|
|
11
11
|
webSocketSessionId?: string;
|
|
12
|
+
/** Called after dispatch init is complete (agentTools/wrapStreamFn done). */
|
|
13
|
+
onInitComplete?: () => void;
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
16
|
* Handle an incoming A2A message.
|
package/dist/src/bot.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getXYRuntime } from "./runtime.js";
|
|
2
|
-
import { setCachedContext } from "./steer-injector.js";
|
|
3
2
|
import { createXYReplyDispatcher } from "./reply-dispatcher.js";
|
|
4
3
|
import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId, extractDeviceType, extractTriggerData } from "./parser.js";
|
|
5
4
|
import { downloadFilesFromParts } from "./file-download.js";
|
|
@@ -13,7 +12,9 @@ import { getPushDataById } from "./utils/pushdata-manager.js";
|
|
|
13
12
|
import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
|
|
14
13
|
import { saveRuntimeInfo } from "./utils/runtime-manager.js";
|
|
15
14
|
import { toolCallNudgeManager } from "./utils/tool-call-nudge-manager.js";
|
|
16
|
-
import {
|
|
15
|
+
import { setCsplSteerContext } from "./cspl/steer-context.js";
|
|
16
|
+
import { registerTaskId, decrementTaskIdRef, hasActiveTask, } from "./task-manager.js";
|
|
17
|
+
import { logger } from "./utils/logger.js";
|
|
17
18
|
/**
|
|
18
19
|
* Handle an incoming A2A message.
|
|
19
20
|
* This is the main entry point for message processing.
|
|
@@ -21,10 +22,8 @@ import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActive
|
|
|
21
22
|
*/
|
|
22
23
|
export async function handleXYMessage(params) {
|
|
23
24
|
const { cfg, runtime, message, accountId, webSocketSessionId } = params;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// ๆฏๆฌกๆถๅฐๆถๆฏๆถๆดๆฐ็ผๅญ๏ผไพ steer ๆณจๅ
ฅไฝฟ็จ
|
|
27
|
-
setCachedContext(cfg, runtime, accountId);
|
|
25
|
+
// Cache context for CSPL steer injection (after_tool_call hook)
|
|
26
|
+
setCsplSteerContext(cfg, runtime);
|
|
28
27
|
// Get runtime (already validated in monitor.ts, but get reference for use)
|
|
29
28
|
const core = getXYRuntime();
|
|
30
29
|
try {
|
|
@@ -36,7 +35,7 @@ export async function handleXYMessage(params) {
|
|
|
36
35
|
if (!sessionId) {
|
|
37
36
|
throw new Error("clearContext request missing sessionId in params");
|
|
38
37
|
}
|
|
39
|
-
log(`Clear context request for session ${sessionId}`);
|
|
38
|
+
logger.log(`Clear context request for session ${sessionId}`);
|
|
40
39
|
const config = resolveXYConfig(cfg);
|
|
41
40
|
await sendClearContextResponse({
|
|
42
41
|
config,
|
|
@@ -52,7 +51,7 @@ export async function handleXYMessage(params) {
|
|
|
52
51
|
if (!sessionId) {
|
|
53
52
|
throw new Error("tasks/cancel request missing sessionId in params");
|
|
54
53
|
}
|
|
55
|
-
log(`Tasks cancel request for session ${sessionId}, task ${taskId}`);
|
|
54
|
+
logger.log(`Tasks cancel request for session ${sessionId}, task ${taskId}`);
|
|
56
55
|
const config = resolveXYConfig(cfg);
|
|
57
56
|
await sendTasksCancelResponse({
|
|
58
57
|
config,
|
|
@@ -68,18 +67,18 @@ export async function handleXYMessage(params) {
|
|
|
68
67
|
// ๅฆๆๆถๆฏไธญๅ
ๅซ Trigger ไบไปถๆฐๆฎ๏ผ็ดๆฅ่ฟๅ pushData ๅ
ๅฎน๏ผไธ่ตฐๆญฃๅธธๆต็จ
|
|
69
68
|
const triggerData = extractTriggerData(parsed.parts);
|
|
70
69
|
if (triggerData) {
|
|
71
|
-
log(`[BOT] ๐ Detected Trigger message with pushDataId: ${triggerData.pushDataId}`);
|
|
72
|
-
log(`[BOT] - Session ID: ${parsed.sessionId}`);
|
|
73
|
-
log(`[BOT] - Task ID: ${parsed.taskId}`);
|
|
70
|
+
logger.log(`[BOT] ๐ Detected Trigger message with pushDataId: ${triggerData.pushDataId}`);
|
|
71
|
+
logger.log(`[BOT] - Session ID: ${parsed.sessionId}`);
|
|
72
|
+
logger.log(`[BOT] - Task ID: ${parsed.taskId}`);
|
|
74
73
|
try {
|
|
75
74
|
// ่ฏปๅ pushData
|
|
76
75
|
const pushDataItem = await getPushDataById(triggerData.pushDataId);
|
|
77
76
|
if (!pushDataItem) {
|
|
78
|
-
error(`[BOT] โ pushData not found for ID: ${triggerData.pushDataId}`);
|
|
77
|
+
logger.error(`[BOT] โ pushData not found for ID: ${triggerData.pushDataId}`);
|
|
79
78
|
return;
|
|
80
79
|
}
|
|
81
|
-
log(`[BOT] โ
Found pushData, sending direct response`);
|
|
82
|
-
log(`[BOT] - pushDataId: ${pushDataItem.pushDataId}`);
|
|
80
|
+
logger.log(`[BOT] โ
Found pushData, sending direct response`);
|
|
81
|
+
logger.log(`[BOT] - pushDataId: ${pushDataItem.pushDataId}`);
|
|
83
82
|
const config = resolveXYConfig(cfg);
|
|
84
83
|
// ็ดๆฅๅ้ๅๅบ๏ผfinal=true๏ผไธ่ตฐ openclaw ๆต็จ๏ผ
|
|
85
84
|
await sendA2AResponse({
|
|
@@ -91,55 +90,47 @@ export async function handleXYMessage(params) {
|
|
|
91
90
|
append: false,
|
|
92
91
|
final: true,
|
|
93
92
|
});
|
|
94
|
-
log(`[BOT] โ
Trigger response sent successfully, exiting early`);
|
|
93
|
+
logger.log(`[BOT] โ
Trigger response sent successfully, exiting early`);
|
|
95
94
|
return; // ๆๅ่ฟๅ๏ผไธ็ปง็ปญๅค็
|
|
96
95
|
}
|
|
97
96
|
catch (err) {
|
|
98
|
-
error(`[BOT] โ Failed to handle Trigger message:`, err);
|
|
97
|
+
logger.error(`[BOT] โ Failed to handle Trigger message:`, err);
|
|
99
98
|
return;
|
|
100
99
|
}
|
|
101
100
|
}
|
|
102
101
|
// ========================================
|
|
103
|
-
// ๐
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
log(`[BOT]
|
|
108
|
-
log(`[BOT] -
|
|
109
|
-
log(`[BOT] - New taskId: ${parsed.taskId} (will replace current)`);
|
|
110
|
-
}
|
|
111
|
-
// ๐ ๆณจๅtaskId๏ผ็ฌฌไบๆกๆถๆฏไผ่ฆ็็ฌฌไธๆก็taskId๏ผ
|
|
112
|
-
const { isUpdate, refCount } = registerTaskId(parsed.sessionId, parsed.taskId, parsed.messageId, { incrementRef: true } // ๅขๅ ๅผ็จ่ฎกๆฐ
|
|
113
|
-
);
|
|
114
|
-
// ๐ ๅฆๆๆฏ็ฌฌไธๆกๆถๆฏ๏ผ้ๅฎtaskId้ฒๆญข่ขซ่ฟๆฉๆธ
็
|
|
115
|
-
if (!isUpdate) {
|
|
116
|
-
lockTaskId(parsed.sessionId);
|
|
117
|
-
log(`[BOT] ๐ Locked taskId for first message`);
|
|
102
|
+
// ๐ ๆณจๅtaskId๏ผๆฃๆตๆฏๅฆๆฏๅทฒๆๆดป่ทไปปๅก็ session๏ผ
|
|
103
|
+
const isUpdate = hasActiveTask(parsed.sessionId);
|
|
104
|
+
if (isUpdate) {
|
|
105
|
+
logger.log(`[BOT] ๐ STEER MODE - Second message detected (core will handle steer)`);
|
|
106
|
+
logger.log(`[BOT] - Session: ${parsed.sessionId}`);
|
|
107
|
+
logger.log(`[BOT] - New taskId: ${parsed.taskId}`);
|
|
118
108
|
}
|
|
109
|
+
registerTaskId(parsed.sessionId, parsed.taskId, parsed.messageId);
|
|
119
110
|
// Extract and update push_id if present
|
|
120
111
|
const pushId = extractPushId(parsed.parts);
|
|
121
112
|
if (pushId) {
|
|
122
|
-
log(`[BOT] ๐ Extracted push_id from user message`);
|
|
113
|
+
logger.log(`[BOT] ๐ Extracted push_id from user message`);
|
|
123
114
|
configManager.updatePushId(parsed.sessionId, pushId);
|
|
124
115
|
// ๆไน
ๅ pushId ๅฐๆฌๅฐๆไปถ๏ผๅผๆญฅ๏ผไธ้ปๅกไธปๆต็จ๏ผ
|
|
125
116
|
addPushId(pushId).catch((err) => {
|
|
126
|
-
error(`[BOT] Failed to persist pushId:`, err);
|
|
117
|
+
logger.error(`[BOT] Failed to persist pushId:`, err);
|
|
127
118
|
});
|
|
128
119
|
}
|
|
129
120
|
else {
|
|
130
|
-
log(`[BOT] โน๏ธ No push_id found in message, will use config default`);
|
|
121
|
+
logger.log(`[BOT] โน๏ธ No push_id found in message, will use config default`);
|
|
131
122
|
}
|
|
132
123
|
// Extract deviceType if present (same level as push_id in systemVariables)
|
|
133
124
|
const deviceType = extractDeviceType(parsed.parts);
|
|
134
125
|
if (deviceType) {
|
|
135
|
-
log(`[BOT] ๐ฑ Extracted deviceType from user message: ${deviceType}`);
|
|
126
|
+
logger.log(`[BOT] ๐ฑ Extracted deviceType from user message: ${deviceType}`);
|
|
136
127
|
}
|
|
137
128
|
// ไฟๅญ runtime ไฟกๆฏๅฐ .xiaoyiruntime ๆไปถ๏ผๅผๆญฅ๏ผไธ้ปๅกไธปๆต็จ๏ผ
|
|
138
129
|
saveRuntimeInfo(webSocketSessionId || parsed.sessionId, // SESSION_ID (WebSocket ๅฑ็บง๏ผๅฆๆๆฒกๆๅ fallback)
|
|
139
130
|
parsed.sessionId, // CONVERSATION_ID (param ้็ sessionId)
|
|
140
131
|
parsed.taskId // TASK_ID (param.id)
|
|
141
132
|
).catch((err) => {
|
|
142
|
-
error(`[BOT] Failed to save runtime info:`, err);
|
|
133
|
+
logger.error(`[BOT] Failed to save runtime info:`, err);
|
|
143
134
|
});
|
|
144
135
|
// Resolve configuration (needed for status updates)
|
|
145
136
|
const config = resolveXYConfig(cfg);
|
|
@@ -155,25 +146,26 @@ export async function handleXYMessage(params) {
|
|
|
155
146
|
id: parsed.sessionId, // โ
Use sessionId to share context within the same conversation session
|
|
156
147
|
},
|
|
157
148
|
});
|
|
158
|
-
log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
|
|
149
|
+
logger.log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
|
|
159
150
|
registerSession(route.sessionKey, {
|
|
160
151
|
config,
|
|
161
152
|
sessionId: parsed.sessionId,
|
|
162
153
|
taskId: parsed.taskId,
|
|
163
154
|
messageId: parsed.messageId,
|
|
164
155
|
agentId: route.accountId,
|
|
156
|
+
deviceType,
|
|
165
157
|
});
|
|
166
|
-
// ๐
|
|
167
|
-
log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
|
|
158
|
+
// ๐ ๅ้ๅๅง็ถๆๆดๆฐ
|
|
159
|
+
logger.log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
|
|
168
160
|
void sendStatusUpdate({
|
|
169
161
|
config,
|
|
170
162
|
sessionId: parsed.sessionId,
|
|
171
163
|
taskId: parsed.taskId,
|
|
172
164
|
messageId: parsed.messageId,
|
|
173
|
-
text:
|
|
165
|
+
text: "ไปปๅกๆญฃๅจๅค็ไธญ๏ผ่ฏท็จๅ~",
|
|
174
166
|
state: "working",
|
|
175
167
|
}).catch((err) => {
|
|
176
|
-
error(`Failed to send initial status update:`, err);
|
|
168
|
+
logger.error(`Failed to send initial status update:`, err);
|
|
177
169
|
});
|
|
178
170
|
// Extract text and files from parts
|
|
179
171
|
const text = extractTextFromParts(parsed.parts);
|
|
@@ -183,24 +175,29 @@ export async function handleXYMessage(params) {
|
|
|
183
175
|
const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
|
|
184
176
|
if (selfEvolutionEnabled && shouldNudgeForSelfEvolutionKeyword(textForAgent)) {
|
|
185
177
|
const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(route.sessionKey);
|
|
186
|
-
log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
|
|
178
|
+
logger.log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
|
|
187
179
|
if (shouldNudge) {
|
|
188
180
|
const augmented = appendSelfEvolutionKeywordNudge(textForAgent);
|
|
189
181
|
textForAgent = augmented.text;
|
|
190
182
|
if (augmented.appended) {
|
|
191
|
-
log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
|
|
183
|
+
logger.log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
|
|
192
184
|
}
|
|
193
185
|
}
|
|
194
186
|
}
|
|
195
187
|
}
|
|
196
188
|
catch (selfEvolutionError) {
|
|
197
|
-
error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
|
|
189
|
+
logger.error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
|
|
198
190
|
}
|
|
199
191
|
}
|
|
192
|
+
// ๐ Steerๆถๆฏๅ /steer ๅ็ผ๏ผ่งฆๅcore็ queueEmbeddedPiMessage
|
|
193
|
+
if (isUpdate && textForAgent) {
|
|
194
|
+
textForAgent = `/steer ${textForAgent}`;
|
|
195
|
+
logger.log(`[BOT] ๐ Prepended /steer for steer injection`);
|
|
196
|
+
}
|
|
200
197
|
const fileParts = extractFileParts(parsed.parts);
|
|
201
198
|
// Download files to local disk
|
|
202
199
|
const downloadedFiles = await downloadFilesFromParts(fileParts);
|
|
203
|
-
|
|
200
|
+
logger.log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
|
|
204
201
|
const mediaPayload = buildXYMediaPayload(downloadedFiles);
|
|
205
202
|
// Resolve envelope format options (following feishu pattern)
|
|
206
203
|
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
@@ -233,7 +230,7 @@ export async function handleXYMessage(params) {
|
|
|
233
230
|
SenderId: parsed.sessionId,
|
|
234
231
|
Provider: "xiaoyi-channel",
|
|
235
232
|
Surface: "xiaoyi-channel",
|
|
236
|
-
MessageSid: parsed.
|
|
233
|
+
MessageSid: `${parsed.taskId}_${deviceType}`,
|
|
237
234
|
Timestamp: Date.now(),
|
|
238
235
|
WasMentioned: false,
|
|
239
236
|
CommandAuthorized: true,
|
|
@@ -242,9 +239,13 @@ export async function handleXYMessage(params) {
|
|
|
242
239
|
ReplyToBody: undefined, // A2A protocol doesn't support reply/quote
|
|
243
240
|
...mediaPayload,
|
|
244
241
|
});
|
|
245
|
-
// ๐
|
|
246
|
-
|
|
247
|
-
|
|
242
|
+
// ๐ Dynamic steer state: when isUpdate (second message), start as steered=true
|
|
243
|
+
// so the dispatcher skips all user-facing callbacks (deliver, onIdle, etc.)
|
|
244
|
+
// and onSettled skips cleanup.
|
|
245
|
+
const steerState = { steered: isUpdate };
|
|
246
|
+
// ๐ ๅๅปบdispatcher
|
|
247
|
+
logger.log(`[BOT-DISPATCHER] ๐ฏ Creating reply dispatcher`);
|
|
248
|
+
logger.log(`[BOT-DISPATCHER] - taskId: ${parsed.taskId}`);
|
|
248
249
|
const { dispatcher, replyOptions, markDispatchIdle, startStatusInterval } = createXYReplyDispatcher({
|
|
249
250
|
cfg,
|
|
250
251
|
runtime,
|
|
@@ -252,14 +253,9 @@ export async function handleXYMessage(params) {
|
|
|
252
253
|
taskId: parsed.taskId,
|
|
253
254
|
messageId: parsed.messageId,
|
|
254
255
|
accountId: route.accountId,
|
|
255
|
-
|
|
256
|
+
steerState,
|
|
256
257
|
});
|
|
257
|
-
|
|
258
|
-
// ็ฌฌไบๆกๆถๆฏไผๅพๅฟซ่ฟๅ๏ผไธ้่ฆๅฎๆถๅจ
|
|
259
|
-
if (!isSecondMessage) {
|
|
260
|
-
startStatusInterval();
|
|
261
|
-
log(`[BOT-DISPATCHER] โ
Status interval started for first message`);
|
|
262
|
-
}
|
|
258
|
+
startStatusInterval();
|
|
263
259
|
// Build session context for AsyncLocalStorage
|
|
264
260
|
const sessionContext = {
|
|
265
261
|
config,
|
|
@@ -269,69 +265,74 @@ export async function handleXYMessage(params) {
|
|
|
269
265
|
agentId: route.accountId,
|
|
270
266
|
deviceType,
|
|
271
267
|
};
|
|
272
|
-
log(`[BOT-DISPATCH] โณ withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
|
|
268
|
+
logger.log(`[BOT-DISPATCH] โณ withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
|
|
273
269
|
await core.channel.reply.withReplyDispatcher({
|
|
274
270
|
dispatcher,
|
|
275
271
|
onSettled: () => {
|
|
276
|
-
log(`[BOT] ๐ onSettled called for session: ${route.sessionKey}`);
|
|
277
|
-
log(`[BOT] -
|
|
278
|
-
// ๐
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
unlockTaskId(parsed.sessionId);
|
|
283
|
-
log(`[BOT] ๐ Unlocked taskId (first message completed)`);
|
|
272
|
+
logger.log(`[BOT] ๐ onSettled called for session: ${route.sessionKey}`);
|
|
273
|
+
logger.log(`[BOT] - steered: ${steerState.steered}`);
|
|
274
|
+
// ๐ When steered, skip heavy cleanup โ the first message's dispatcher is still running
|
|
275
|
+
if (steerState.steered) {
|
|
276
|
+
logger.log(`[BOT] โ
Steered dispatch settled (skipping cleanup)`);
|
|
277
|
+
return;
|
|
284
278
|
}
|
|
285
|
-
|
|
279
|
+
decrementTaskIdRef(parsed.sessionId);
|
|
286
280
|
unregisterSession(route.sessionKey);
|
|
287
|
-
log(`[BOT] โ
Cleanup completed`);
|
|
281
|
+
logger.log(`[BOT] โ
Cleanup completed`);
|
|
282
|
+
},
|
|
283
|
+
run: () => {
|
|
284
|
+
// ๐ Use AsyncLocalStorage to provide session context to tools.
|
|
285
|
+
// runWithSessionContext returns after the sync part of dispatch
|
|
286
|
+
// (including agentTools + wrapStreamFn) has executed, so we
|
|
287
|
+
// signal init complete to release the global dispatch gate
|
|
288
|
+
// for the next session.
|
|
289
|
+
const dispatchPromise = runWithSessionContext(sessionContext, async () => {
|
|
290
|
+
logger.log(`[BOT-DISPATCH] โณ dispatchReplyFromConfig starting...`);
|
|
291
|
+
logger.log(`[BOT-DISPATCH] - sessionKey: ${ctxPayload.SessionKey}`);
|
|
292
|
+
logger.log(`[BOT-DISPATCH] - provider: ${ctxPayload.Provider}`);
|
|
293
|
+
logger.log(`[BOT-DISPATCH] - surface: ${ctxPayload.Surface}`);
|
|
294
|
+
logger.log(`[BOT-DISPATCH] - from: ${ctxPayload.From}`);
|
|
295
|
+
logger.log(`[BOT-DISPATCH] - body length: ${ctxPayload.Body?.length ?? 0}`);
|
|
296
|
+
try {
|
|
297
|
+
const result = await core.channel.reply.dispatchReplyFromConfig({
|
|
298
|
+
ctx: ctxPayload,
|
|
299
|
+
cfg,
|
|
300
|
+
dispatcher,
|
|
301
|
+
replyOptions,
|
|
302
|
+
});
|
|
303
|
+
logger.log(`[BOT-DISPATCH] โ
dispatchReplyFromConfig returned`);
|
|
304
|
+
logger.log(`[BOT-DISPATCH] - result: ${JSON.stringify(result)}`);
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
catch (dispatchErr) {
|
|
308
|
+
logger.error(`[BOT-DISPATCH] โ dispatchReplyFromConfig threw`);
|
|
309
|
+
logger.error(`[BOT-DISPATCH] - error name: ${dispatchErr instanceof Error ? dispatchErr.name : "unknown"}`);
|
|
310
|
+
logger.error(`[BOT-DISPATCH] - error message: ${String(dispatchErr)}`);
|
|
311
|
+
logger.error(`[BOT-DISPATCH] - error stack: ${dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : "N/A"}`);
|
|
312
|
+
throw dispatchErr;
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
// Signal init complete โ sync part (agentTools, wrapStreamFn) is done
|
|
316
|
+
params.onInitComplete?.();
|
|
317
|
+
return dispatchPromise;
|
|
288
318
|
},
|
|
289
|
-
run: () =>
|
|
290
|
-
// ๐ Use AsyncLocalStorage to provide session context to tools
|
|
291
|
-
runWithSessionContext(sessionContext, async () => {
|
|
292
|
-
log(`[BOT-DISPATCH] โณ dispatchReplyFromConfig starting...`);
|
|
293
|
-
log(`[BOT-DISPATCH] - sessionKey: ${ctxPayload.SessionKey}`);
|
|
294
|
-
log(`[BOT-DISPATCH] - provider: ${ctxPayload.Provider}`);
|
|
295
|
-
log(`[BOT-DISPATCH] - surface: ${ctxPayload.Surface}`);
|
|
296
|
-
log(`[BOT-DISPATCH] - from: ${ctxPayload.From}`);
|
|
297
|
-
log(`[BOT-DISPATCH] - body length: ${ctxPayload.Body?.length ?? 0}`);
|
|
298
|
-
try {
|
|
299
|
-
const result = await core.channel.reply.dispatchReplyFromConfig({
|
|
300
|
-
ctx: ctxPayload,
|
|
301
|
-
cfg,
|
|
302
|
-
dispatcher,
|
|
303
|
-
replyOptions,
|
|
304
|
-
});
|
|
305
|
-
log(`[BOT-DISPATCH] โ
dispatchReplyFromConfig returned`);
|
|
306
|
-
log(`[BOT-DISPATCH] - result: ${JSON.stringify(result)}`);
|
|
307
|
-
return result;
|
|
308
|
-
}
|
|
309
|
-
catch (dispatchErr) {
|
|
310
|
-
error(`[BOT-DISPATCH] โ dispatchReplyFromConfig threw`);
|
|
311
|
-
error(`[BOT-DISPATCH] - error name: ${dispatchErr instanceof Error ? dispatchErr.name : "unknown"}`);
|
|
312
|
-
error(`[BOT-DISPATCH] - error message: ${String(dispatchErr)}`);
|
|
313
|
-
error(`[BOT-DISPATCH] - error stack: ${dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : "N/A"}`);
|
|
314
|
-
throw dispatchErr;
|
|
315
|
-
}
|
|
316
|
-
}),
|
|
317
319
|
});
|
|
318
|
-
log(`[BOT] โ
Dispatcher completed for session: ${parsed.sessionId}`);
|
|
319
|
-
log(`xy: dispatch complete (session=${parsed.sessionId})`);
|
|
320
|
+
logger.log(`[BOT] โ
Dispatcher completed for session: ${parsed.sessionId}`);
|
|
321
|
+
logger.log(`xy: dispatch complete (session=${parsed.sessionId})`);
|
|
320
322
|
}
|
|
321
323
|
catch (err) {
|
|
322
324
|
// โ
Only log error, don't re-throw to prevent gateway restart
|
|
323
|
-
error("Failed to handle XY message:", err);
|
|
325
|
+
logger.error("Failed to handle XY message:", err);
|
|
324
326
|
runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
|
|
325
|
-
log(`[BOT] โ Error occurred, attempting cleanup...`);
|
|
327
|
+
logger.log(`[BOT] โ Error occurred, attempting cleanup...`);
|
|
326
328
|
// ๐ ้่ฏฏๆถไน่ฆๆธ
็taskIdๅsession
|
|
327
329
|
try {
|
|
328
330
|
const params = message.params;
|
|
329
331
|
const sessionId = params?.sessionId;
|
|
330
332
|
if (sessionId) {
|
|
331
|
-
log(`[BOT] ๐งน Cleaning up after error: ${sessionId}`);
|
|
333
|
+
logger.log(`[BOT] ๐งน Cleaning up after error: ${sessionId}`);
|
|
332
334
|
// ๆธ
็ taskId
|
|
333
335
|
decrementTaskIdRef(sessionId);
|
|
334
|
-
unlockTaskId(sessionId);
|
|
335
336
|
// ๆธ
็ session
|
|
336
337
|
const core = getXYRuntime();
|
|
337
338
|
const route = core.channel.routing.resolveAgentRoute({
|
|
@@ -344,11 +345,11 @@ export async function handleXYMessage(params) {
|
|
|
344
345
|
},
|
|
345
346
|
});
|
|
346
347
|
unregisterSession(route.sessionKey);
|
|
347
|
-
log(`[BOT] โ
Cleanup completed after error`);
|
|
348
|
+
logger.log(`[BOT] โ
Cleanup completed after error`);
|
|
348
349
|
}
|
|
349
350
|
}
|
|
350
351
|
catch (cleanupErr) {
|
|
351
|
-
log(`[BOT] โ ๏ธ Cleanup failed:`, cleanupErr);
|
|
352
|
+
logger.log(`[BOT] โ ๏ธ Cleanup failed:`, cleanupErr);
|
|
352
353
|
// Ignore cleanup errors
|
|
353
354
|
}
|
|
354
355
|
// โ Don't re-throw: message processing error should not affect gateway stability
|
package/dist/src/client.d.ts
CHANGED
|
@@ -1,15 +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
|
-
* Set the runtime for logging in client module.
|
|
6
|
-
*/
|
|
7
|
-
export declare function setClientRuntime(rt: RuntimeEnv | undefined): void;
|
|
8
4
|
/**
|
|
9
5
|
* Get or create a WebSocket manager for the given configuration.
|
|
10
6
|
* Reuses existing managers if config matches.
|
|
11
7
|
*/
|
|
12
|
-
export declare function getXYWebSocketManager(config: XYChannelConfig): XYWebSocketManager;
|
|
8
|
+
export declare function getXYWebSocketManager(config: XYChannelConfig, runtime?: RuntimeEnv): XYWebSocketManager;
|
|
13
9
|
/**
|
|
14
10
|
* Remove a specific WebSocket manager from cache.
|
|
15
11
|
* Disconnects the manager and removes it from the cache.
|