@ynhcj/xiaoyi-channel 0.0.127-beta โ 0.0.127-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 +101 -54
- package/dist/provider-discovery.d.ts +2 -0
- package/dist/provider-discovery.js +4 -0
- package/dist/src/bot.d.ts +8 -0
- package/dist/src/bot.js +126 -115
- package/dist/src/client.d.ts +1 -5
- package/dist/src/client.js +25 -39
- package/dist/src/cspl/call-api.d.ts +6 -0
- package/dist/src/cspl/call-api.js +33 -13
- 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 +21 -0
- package/dist/src/cspl/steer-context.js +78 -0
- package/dist/src/file-download.js +3 -3
- package/dist/src/formatter.js +17 -8
- package/dist/src/heartbeat.js +3 -2
- package/dist/src/login-token-handler.js +13 -10
- package/dist/src/message-queue.js +2 -1
- package/dist/src/monitor.js +55 -45
- package/dist/src/outbound.js +3 -0
- package/dist/src/provider.js +70 -18
- package/dist/src/push.js +9 -9
- package/dist/src/reply-dispatcher.d.ts +3 -1
- package/dist/src/reply-dispatcher.js +61 -60
- package/dist/src/self-evolution-handler.js +11 -14
- package/dist/src/skill-retriever/hooks.js +0 -1
- package/dist/src/skill-retriever/tool-search.js +7 -12
- 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 +8 -5
- 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 +2 -14
- 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/throw.d.ts +5 -0
- package/dist/src/utils/throw.js +10 -0
- package/dist/src/websocket.js +4 -2
- 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,116 @@
|
|
|
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 { tryInjectSteer } 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 via tryInjectSteer
|
|
32
|
+
// to interrupt the agent. Uses skipRegistration to avoid refCount leaks
|
|
33
|
+
// and taskId overwrites.
|
|
34
|
+
// Only registered in "full" mode because it depends on handleXYMessage
|
|
35
|
+
// having cached cfg/runtime via setCsplSteerContext.
|
|
36
|
+
api.on("after_tool_call", async (event, ctx) => {
|
|
37
|
+
if (!ALLOWED_TOOLS.includes(event.toolName)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const resultText = extractResultText(event, event.toolName);
|
|
42
|
+
const resultLength = resultText.length;
|
|
43
|
+
if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
|
|
34
44
|
return;
|
|
35
45
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
logger.log(`[SENTINEL HOOK] after_tool_call: toolName=${event.toolName}, textLength=${resultLength}`);
|
|
47
|
+
const questionText = {
|
|
48
|
+
subSceneID: "TOOL_OUTPUT",
|
|
49
|
+
tool: event.toolName,
|
|
50
|
+
output: [{ content: "" }],
|
|
51
|
+
};
|
|
52
|
+
const originText = processText(resultText);
|
|
53
|
+
questionText.output[0].content = originText;
|
|
54
|
+
let finalJson = JSON.stringify(questionText);
|
|
55
|
+
if (finalJson.length > MAX_TEXT_LENGTH) {
|
|
56
|
+
const diff = finalJson.length - MAX_TEXT_LENGTH;
|
|
57
|
+
const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
|
|
58
|
+
questionText.output[0].content = trimmed;
|
|
59
|
+
finalJson = JSON.stringify(questionText);
|
|
60
|
+
}
|
|
61
|
+
const sessionCtx = getSessionContext(ctx.sessionKey ?? "");
|
|
62
|
+
const csplConfig = sessionCtx
|
|
63
|
+
? initCsplConfigFromXYConfig(sessionCtx.config)
|
|
64
|
+
: getCsplConfig();
|
|
65
|
+
const csplStartTime = Date.now();
|
|
66
|
+
const response = await callCsplApiWithConfig(finalJson, csplConfig);
|
|
67
|
+
const csplElapsed = Date.now() - csplStartTime;
|
|
68
|
+
const result = parseSecurityResult(response);
|
|
69
|
+
logger.log(`[SENTINEL HOOK] Security result: status=${result.status}, toolName=${event.toolName}, elapsed=${csplElapsed}ms`);
|
|
70
|
+
if (result.status === "REJECT") {
|
|
71
|
+
logger.log(`[SENTINEL HOOK] REJECT - injecting steer via tryInjectSteer`);
|
|
72
|
+
if (sessionCtx) {
|
|
73
|
+
await tryInjectSteer({
|
|
74
|
+
sessionId: sessionCtx.sessionId,
|
|
75
|
+
taskId: sessionCtx.taskId,
|
|
76
|
+
message: STEER_ABORT_MESSAGE,
|
|
77
|
+
source: "cspl",
|
|
78
|
+
});
|
|
56
79
|
}
|
|
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);
|
|
80
|
+
else {
|
|
81
|
+
logger.error("[SENTINEL HOOK] No session context, cannot inject steer");
|
|
62
82
|
}
|
|
63
83
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
}
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export default definePluginEntry({
|
|
91
|
+
id: "xiaoyi-channel",
|
|
92
|
+
name: "Xiaoyi Channel",
|
|
93
|
+
description: "Xiaoyi channel plugin - Xiaoyi A2A protocol integration",
|
|
94
|
+
register(api) {
|
|
95
|
+
// Always register the provider so wrapStreamFn/prepareExtraParams work
|
|
96
|
+
// in ALL registration modes (not just "full").
|
|
97
|
+
api.registerProvider(xiaoyiProvider);
|
|
98
|
+
if (api.registrationMode === "cli-metadata") {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (api.registrationMode === "tool-discovery") {
|
|
102
|
+
registerFullHooks(api);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Register channel plugin and set runtime
|
|
106
|
+
api.registerChannel({ plugin: xyPlugin });
|
|
107
|
+
setXYRuntime(api.runtime);
|
|
108
|
+
if (api.registrationMode === "discovery") {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (api.registrationMode === "full") {
|
|
112
|
+
registerFullHooks(api);
|
|
113
|
+
registerCsplHook(api);
|
|
114
|
+
}
|
|
68
115
|
},
|
|
69
116
|
});
|
package/dist/src/bot.d.ts
CHANGED
|
@@ -9,6 +9,14 @@ 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;
|
|
14
|
+
/**
|
|
15
|
+
* When true, skip taskId/session registration. Used by tryInjectSteer to
|
|
16
|
+
* inject a steer message without overwriting the active taskId or leaking
|
|
17
|
+
* session refCount.
|
|
18
|
+
*/
|
|
19
|
+
skipRegistration?: boolean;
|
|
12
20
|
}
|
|
13
21
|
/**
|
|
14
22
|
* 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,51 @@ 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
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
log(`[BOT] ๐ STEER MODE - Second message detected (will
|
|
108
|
-
log(`[BOT] - Session: ${parsed.sessionId}`);
|
|
109
|
-
log(`[BOT] - New taskId: ${parsed.taskId}
|
|
102
|
+
// ๐ ๆณจๅtaskId๏ผๆฃๆตๆฏๅฆๆฏๅทฒๆๆดป่ทไปปๅก็ session๏ผ
|
|
103
|
+
const isUpdate = hasActiveTask(parsed.sessionId);
|
|
104
|
+
const skipReg = params.skipRegistration === true;
|
|
105
|
+
if (isUpdate) {
|
|
106
|
+
logger.log(`[BOT] ๐ STEER MODE - Second message detected (core will handle steer)`);
|
|
107
|
+
logger.log(`[BOT] - Session: ${parsed.sessionId}`);
|
|
108
|
+
logger.log(`[BOT] - New taskId: ${parsed.taskId}`);
|
|
110
109
|
}
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// ๐ ๅฆๆๆฏ็ฌฌไธๆกๆถๆฏ๏ผ้ๅฎtaskId้ฒๆญข่ขซ่ฟๆฉๆธ
็
|
|
115
|
-
if (!isUpdate) {
|
|
116
|
-
lockTaskId(parsed.sessionId);
|
|
117
|
-
log(`[BOT] ๐ Locked taskId for first message`);
|
|
110
|
+
// Steer injections skip taskId registration to avoid overwriting the active taskId
|
|
111
|
+
if (!skipReg) {
|
|
112
|
+
registerTaskId(parsed.sessionId, parsed.taskId, parsed.messageId);
|
|
118
113
|
}
|
|
119
114
|
// Extract and update push_id if present
|
|
120
115
|
const pushId = extractPushId(parsed.parts);
|
|
121
116
|
if (pushId) {
|
|
122
|
-
log(`[BOT] ๐ Extracted push_id from user message`);
|
|
117
|
+
logger.log(`[BOT] ๐ Extracted push_id from user message`);
|
|
123
118
|
configManager.updatePushId(parsed.sessionId, pushId);
|
|
124
119
|
// ๆไน
ๅ pushId ๅฐๆฌๅฐๆไปถ๏ผๅผๆญฅ๏ผไธ้ปๅกไธปๆต็จ๏ผ
|
|
125
120
|
addPushId(pushId).catch((err) => {
|
|
126
|
-
error(`[BOT] Failed to persist pushId:`, err);
|
|
121
|
+
logger.error(`[BOT] Failed to persist pushId:`, err);
|
|
127
122
|
});
|
|
128
123
|
}
|
|
129
124
|
else {
|
|
130
|
-
log(`[BOT] โน๏ธ No push_id found in message, will use config default`);
|
|
125
|
+
logger.log(`[BOT] โน๏ธ No push_id found in message, will use config default`);
|
|
131
126
|
}
|
|
132
127
|
// Extract deviceType if present (same level as push_id in systemVariables)
|
|
133
128
|
const deviceType = extractDeviceType(parsed.parts);
|
|
134
129
|
if (deviceType) {
|
|
135
|
-
log(`[BOT] ๐ฑ Extracted deviceType from user message: ${deviceType}`);
|
|
130
|
+
logger.log(`[BOT] ๐ฑ Extracted deviceType from user message: ${deviceType}`);
|
|
136
131
|
}
|
|
137
132
|
// ไฟๅญ runtime ไฟกๆฏๅฐ .xiaoyiruntime ๆไปถ๏ผๅผๆญฅ๏ผไธ้ปๅกไธปๆต็จ๏ผ
|
|
138
133
|
saveRuntimeInfo(webSocketSessionId || parsed.sessionId, // SESSION_ID (WebSocket ๅฑ็บง๏ผๅฆๆๆฒกๆๅ fallback)
|
|
139
134
|
parsed.sessionId, // CONVERSATION_ID (param ้็ sessionId)
|
|
140
135
|
parsed.taskId // TASK_ID (param.id)
|
|
141
136
|
).catch((err) => {
|
|
142
|
-
error(`[BOT] Failed to save runtime info:`, err);
|
|
137
|
+
logger.error(`[BOT] Failed to save runtime info:`, err);
|
|
143
138
|
});
|
|
144
139
|
// Resolve configuration (needed for status updates)
|
|
145
140
|
const config = resolveXYConfig(cfg);
|
|
@@ -155,26 +150,30 @@ export async function handleXYMessage(params) {
|
|
|
155
150
|
id: parsed.sessionId, // โ
Use sessionId to share context within the same conversation session
|
|
156
151
|
},
|
|
157
152
|
});
|
|
158
|
-
log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
153
|
+
logger.log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
|
|
154
|
+
// Steer injections skip session registration to avoid refCount leaks
|
|
155
|
+
if (!skipReg) {
|
|
156
|
+
registerSession(route.sessionKey, {
|
|
157
|
+
config,
|
|
158
|
+
sessionId: parsed.sessionId,
|
|
159
|
+
taskId: parsed.taskId,
|
|
160
|
+
messageId: parsed.messageId,
|
|
161
|
+
agentId: route.accountId,
|
|
162
|
+
deviceType,
|
|
163
|
+
});
|
|
164
|
+
// ๐ ๅ้ๅๅง็ถๆๆดๆฐ
|
|
165
|
+
logger.log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
|
|
166
|
+
void sendStatusUpdate({
|
|
167
|
+
config,
|
|
168
|
+
sessionId: parsed.sessionId,
|
|
169
|
+
taskId: parsed.taskId,
|
|
170
|
+
messageId: parsed.messageId,
|
|
171
|
+
text: "ไปปๅกๆญฃๅจๅค็ไธญ๏ผ่ฏท็จๅ~",
|
|
172
|
+
state: "working",
|
|
173
|
+
}).catch((err) => {
|
|
174
|
+
logger.error(`Failed to send initial status update:`, err);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
178
177
|
// Extract text and files from parts
|
|
179
178
|
const text = extractTextFromParts(parsed.parts);
|
|
180
179
|
let textForAgent = text || "";
|
|
@@ -183,24 +182,29 @@ export async function handleXYMessage(params) {
|
|
|
183
182
|
const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
|
|
184
183
|
if (selfEvolutionEnabled && shouldNudgeForSelfEvolutionKeyword(textForAgent)) {
|
|
185
184
|
const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(route.sessionKey);
|
|
186
|
-
log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
|
|
185
|
+
logger.log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
|
|
187
186
|
if (shouldNudge) {
|
|
188
187
|
const augmented = appendSelfEvolutionKeywordNudge(textForAgent);
|
|
189
188
|
textForAgent = augmented.text;
|
|
190
189
|
if (augmented.appended) {
|
|
191
|
-
log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
|
|
190
|
+
logger.log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
|
|
192
191
|
}
|
|
193
192
|
}
|
|
194
193
|
}
|
|
195
194
|
}
|
|
196
195
|
catch (selfEvolutionError) {
|
|
197
|
-
error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
|
|
196
|
+
logger.error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
|
|
198
197
|
}
|
|
199
198
|
}
|
|
199
|
+
// ๐ Steerๆถๆฏๅ /steer ๅ็ผ๏ผ่งฆๅcore็ queueEmbeddedPiMessage
|
|
200
|
+
if (isUpdate && textForAgent) {
|
|
201
|
+
textForAgent = `/steer ${textForAgent}`;
|
|
202
|
+
logger.log(`[BOT] ๐ Prepended /steer for steer injection`);
|
|
203
|
+
}
|
|
200
204
|
const fileParts = extractFileParts(parsed.parts);
|
|
201
205
|
// Download files to local disk
|
|
202
206
|
const downloadedFiles = await downloadFilesFromParts(fileParts);
|
|
203
|
-
log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
|
|
207
|
+
logger.log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
|
|
204
208
|
const mediaPayload = buildXYMediaPayload(downloadedFiles);
|
|
205
209
|
// Resolve envelope format options (following feishu pattern)
|
|
206
210
|
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
@@ -233,7 +237,7 @@ export async function handleXYMessage(params) {
|
|
|
233
237
|
SenderId: parsed.sessionId,
|
|
234
238
|
Provider: "xiaoyi-channel",
|
|
235
239
|
Surface: "xiaoyi-channel",
|
|
236
|
-
MessageSid: parsed.
|
|
240
|
+
MessageSid: `${parsed.taskId}_${deviceType}`,
|
|
237
241
|
Timestamp: Date.now(),
|
|
238
242
|
WasMentioned: false,
|
|
239
243
|
CommandAuthorized: true,
|
|
@@ -242,9 +246,13 @@ export async function handleXYMessage(params) {
|
|
|
242
246
|
ReplyToBody: undefined, // A2A protocol doesn't support reply/quote
|
|
243
247
|
...mediaPayload,
|
|
244
248
|
});
|
|
245
|
-
// ๐
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
// ๐ Dynamic steer state: when isUpdate (second message), start as steered=true
|
|
250
|
+
// so the dispatcher skips all user-facing callbacks (deliver, onIdle, etc.)
|
|
251
|
+
// and onSettled skips cleanup.
|
|
252
|
+
const steerState = { steered: isUpdate };
|
|
253
|
+
// ๐ ๅๅปบdispatcher
|
|
254
|
+
logger.log(`[BOT-DISPATCHER] ๐ฏ Creating reply dispatcher`);
|
|
255
|
+
logger.log(`[BOT-DISPATCHER] - taskId: ${parsed.taskId}`);
|
|
248
256
|
const { dispatcher, replyOptions, markDispatchIdle, startStatusInterval } = createXYReplyDispatcher({
|
|
249
257
|
cfg,
|
|
250
258
|
runtime,
|
|
@@ -252,13 +260,11 @@ export async function handleXYMessage(params) {
|
|
|
252
260
|
taskId: parsed.taskId,
|
|
253
261
|
messageId: parsed.messageId,
|
|
254
262
|
accountId: route.accountId,
|
|
255
|
-
|
|
263
|
+
steerState,
|
|
256
264
|
});
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
if (!isSecondMessage) {
|
|
265
|
+
// Steer injections don't need status intervals
|
|
266
|
+
if (!skipReg) {
|
|
260
267
|
startStatusInterval();
|
|
261
|
-
log(`[BOT-DISPATCHER] โ
Status interval started for first message`);
|
|
262
268
|
}
|
|
263
269
|
// Build session context for AsyncLocalStorage
|
|
264
270
|
const sessionContext = {
|
|
@@ -269,69 +275,74 @@ export async function handleXYMessage(params) {
|
|
|
269
275
|
agentId: route.accountId,
|
|
270
276
|
deviceType,
|
|
271
277
|
};
|
|
272
|
-
log(`[BOT-DISPATCH] โณ withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
|
|
278
|
+
logger.log(`[BOT-DISPATCH] โณ withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
|
|
273
279
|
await core.channel.reply.withReplyDispatcher({
|
|
274
280
|
dispatcher,
|
|
275
281
|
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)`);
|
|
282
|
+
logger.log(`[BOT] ๐ onSettled called for session: ${route.sessionKey}`);
|
|
283
|
+
logger.log(`[BOT] - steered: ${steerState.steered}`);
|
|
284
|
+
// ๐ When steered, skip heavy cleanup โ the first message's dispatcher is still running
|
|
285
|
+
if (steerState.steered) {
|
|
286
|
+
logger.log(`[BOT] โ
Steered dispatch settled (skipping cleanup)`);
|
|
287
|
+
return;
|
|
284
288
|
}
|
|
285
|
-
|
|
289
|
+
decrementTaskIdRef(parsed.sessionId);
|
|
286
290
|
unregisterSession(route.sessionKey);
|
|
287
|
-
log(`[BOT] โ
Cleanup completed`);
|
|
291
|
+
logger.log(`[BOT] โ
Cleanup completed`);
|
|
292
|
+
},
|
|
293
|
+
run: () => {
|
|
294
|
+
// ๐ Use AsyncLocalStorage to provide session context to tools.
|
|
295
|
+
// runWithSessionContext returns after the sync part of dispatch
|
|
296
|
+
// (including agentTools + wrapStreamFn) has executed, so we
|
|
297
|
+
// signal init complete to release the global dispatch gate
|
|
298
|
+
// for the next session.
|
|
299
|
+
const dispatchPromise = runWithSessionContext(sessionContext, async () => {
|
|
300
|
+
logger.log(`[BOT-DISPATCH] โณ dispatchReplyFromConfig starting...`);
|
|
301
|
+
logger.log(`[BOT-DISPATCH] - sessionKey: ${ctxPayload.SessionKey}`);
|
|
302
|
+
logger.log(`[BOT-DISPATCH] - provider: ${ctxPayload.Provider}`);
|
|
303
|
+
logger.log(`[BOT-DISPATCH] - surface: ${ctxPayload.Surface}`);
|
|
304
|
+
logger.log(`[BOT-DISPATCH] - from: ${ctxPayload.From}`);
|
|
305
|
+
logger.log(`[BOT-DISPATCH] - body length: ${ctxPayload.Body?.length ?? 0}`);
|
|
306
|
+
try {
|
|
307
|
+
const result = await core.channel.reply.dispatchReplyFromConfig({
|
|
308
|
+
ctx: ctxPayload,
|
|
309
|
+
cfg,
|
|
310
|
+
dispatcher,
|
|
311
|
+
replyOptions,
|
|
312
|
+
});
|
|
313
|
+
logger.log(`[BOT-DISPATCH] โ
dispatchReplyFromConfig returned`);
|
|
314
|
+
logger.log(`[BOT-DISPATCH] - result: ${JSON.stringify(result)}`);
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
catch (dispatchErr) {
|
|
318
|
+
logger.error(`[BOT-DISPATCH] โ dispatchReplyFromConfig threw`);
|
|
319
|
+
logger.error(`[BOT-DISPATCH] - error name: ${dispatchErr instanceof Error ? dispatchErr.name : "unknown"}`);
|
|
320
|
+
logger.error(`[BOT-DISPATCH] - error message: ${String(dispatchErr)}`);
|
|
321
|
+
logger.error(`[BOT-DISPATCH] - error stack: ${dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : "N/A"}`);
|
|
322
|
+
throw dispatchErr;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
// Signal init complete โ sync part (agentTools, wrapStreamFn) is done
|
|
326
|
+
params.onInitComplete?.();
|
|
327
|
+
return dispatchPromise;
|
|
288
328
|
},
|
|
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
329
|
});
|
|
318
|
-
log(`[BOT] โ
Dispatcher completed for session: ${parsed.sessionId}`);
|
|
319
|
-
log(`xy: dispatch complete (session=${parsed.sessionId})`);
|
|
330
|
+
logger.log(`[BOT] โ
Dispatcher completed for session: ${parsed.sessionId}`);
|
|
331
|
+
logger.log(`xy: dispatch complete (session=${parsed.sessionId})`);
|
|
320
332
|
}
|
|
321
333
|
catch (err) {
|
|
322
334
|
// โ
Only log error, don't re-throw to prevent gateway restart
|
|
323
|
-
error("Failed to handle XY message:", err);
|
|
335
|
+
logger.error("Failed to handle XY message:", err);
|
|
324
336
|
runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
|
|
325
|
-
log(`[BOT] โ Error occurred, attempting cleanup...`);
|
|
337
|
+
logger.log(`[BOT] โ Error occurred, attempting cleanup...`);
|
|
326
338
|
// ๐ ้่ฏฏๆถไน่ฆๆธ
็taskIdๅsession
|
|
327
339
|
try {
|
|
328
340
|
const params = message.params;
|
|
329
341
|
const sessionId = params?.sessionId;
|
|
330
342
|
if (sessionId) {
|
|
331
|
-
log(`[BOT] ๐งน Cleaning up after error: ${sessionId}`);
|
|
343
|
+
logger.log(`[BOT] ๐งน Cleaning up after error: ${sessionId}`);
|
|
332
344
|
// ๆธ
็ taskId
|
|
333
345
|
decrementTaskIdRef(sessionId);
|
|
334
|
-
unlockTaskId(sessionId);
|
|
335
346
|
// ๆธ
็ session
|
|
336
347
|
const core = getXYRuntime();
|
|
337
348
|
const route = core.channel.routing.resolveAgentRoute({
|
|
@@ -344,11 +355,11 @@ export async function handleXYMessage(params) {
|
|
|
344
355
|
},
|
|
345
356
|
});
|
|
346
357
|
unregisterSession(route.sessionKey);
|
|
347
|
-
log(`[BOT] โ
Cleanup completed after error`);
|
|
358
|
+
logger.log(`[BOT] โ
Cleanup completed after error`);
|
|
348
359
|
}
|
|
349
360
|
}
|
|
350
361
|
catch (cleanupErr) {
|
|
351
|
-
log(`[BOT] โ ๏ธ Cleanup failed:`, cleanupErr);
|
|
362
|
+
logger.log(`[BOT] โ ๏ธ Cleanup failed:`, cleanupErr);
|
|
352
363
|
// Ignore cleanup errors
|
|
353
364
|
}
|
|
354
365
|
// โ 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.
|