@ynhcj/xiaoyi-channel 0.0.105-beta → 0.0.105-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 +4 -5
- package/dist/index.js +77 -136
- package/dist/provider-discovery.d.ts +2 -0
- package/dist/provider-discovery.js +4 -0
- package/dist/src/bot.js +61 -68
- package/dist/src/channel.js +14 -25
- package/dist/src/client.js +31 -22
- package/dist/src/cspl/call-api.js +6 -5
- package/dist/src/file-download.js +4 -3
- package/dist/src/file-upload.js +19 -18
- package/dist/src/formatter.d.ts +2 -0
- package/dist/src/formatter.js +25 -35
- package/dist/src/heartbeat.js +1 -1
- package/dist/src/message-queue.d.ts +17 -0
- package/dist/src/message-queue.js +51 -0
- package/dist/src/monitor.js +64 -15
- package/dist/src/outbound.js +24 -23
- package/dist/src/provider.d.ts +2 -1
- package/dist/src/provider.js +230 -71
- package/dist/src/push.js +16 -15
- package/dist/src/reply-dispatcher.js +17 -6
- package/dist/src/runtime.d.ts +3 -11
- package/dist/src/runtime.js +6 -18
- package/dist/src/self-evolution-handler.d.ts +6 -0
- package/dist/src/self-evolution-handler.js +93 -0
- package/dist/src/self-evolution-keyword.d.ts +9 -0
- package/dist/src/self-evolution-keyword.js +147 -0
- package/dist/src/self-evolution-tool-result-nudge.d.ts +3 -0
- package/dist/src/self-evolution-tool-result-nudge.js +96 -0
- package/dist/src/skill-retriever/hooks.js +7 -15
- package/dist/src/skill-retriever/tool-search.js +22 -8
- package/dist/src/skill-retriever/types.d.ts +2 -0
- package/dist/src/steer-injector.js +6 -8
- package/dist/src/task-manager.d.ts +4 -0
- package/dist/src/task-manager.js +12 -1
- package/dist/src/tools/calendar-tool.d.ts +1 -1
- package/dist/src/tools/calendar-tool.js +113 -116
- package/dist/src/tools/call-device-tool.d.ts +1 -1
- package/dist/src/tools/call-device-tool.js +127 -104
- package/dist/src/tools/call-phone-tool.d.ts +1 -1
- package/dist/src/tools/call-phone-tool.js +110 -113
- package/dist/src/tools/create-alarm-tool.d.ts +1 -1
- package/dist/src/tools/create-alarm-tool.js +228 -231
- package/dist/src/tools/create-all-tools.d.ts +15 -0
- package/dist/src/tools/create-all-tools.js +46 -0
- package/dist/src/tools/delete-alarm-tool.d.ts +1 -1
- package/dist/src/tools/delete-alarm-tool.js +132 -135
- package/dist/src/tools/get-alarm-tool-schema.d.ts +1 -1
- package/dist/src/tools/get-alarm-tool-schema.js +16 -10
- package/dist/src/tools/get-calendar-tool-schema.d.ts +1 -1
- package/dist/src/tools/get-calendar-tool-schema.js +12 -8
- package/dist/src/tools/get-collection-tool-schema.d.ts +1 -1
- package/dist/src/tools/get-collection-tool-schema.js +11 -9
- package/dist/src/tools/get-contact-tool-schema.d.ts +1 -1
- package/dist/src/tools/get-contact-tool-schema.js +16 -10
- package/dist/src/tools/get-device-file-tool-schema.d.ts +1 -1
- package/dist/src/tools/get-device-file-tool-schema.js +13 -9
- package/dist/src/tools/get-email-tool-schema.d.ts +1 -1
- package/dist/src/tools/get-email-tool-schema.js +11 -8
- package/dist/src/tools/get-note-tool-schema.d.ts +1 -1
- package/dist/src/tools/get-note-tool-schema.js +14 -9
- package/dist/src/tools/get-photo-tool-schema.d.ts +1 -1
- package/dist/src/tools/get-photo-tool-schema.js +12 -9
- package/dist/src/tools/image-reading-tool.d.ts +2 -2
- package/dist/src/tools/image-reading-tool.js +83 -162
- package/dist/src/tools/location-tool.d.ts +1 -1
- package/dist/src/tools/location-tool.js +88 -91
- package/dist/src/tools/login-token-tool.d.ts +2 -2
- package/dist/src/tools/login-token-tool.js +115 -118
- package/dist/src/tools/modify-alarm-tool.d.ts +1 -1
- package/dist/src/tools/modify-alarm-tool.js +233 -236
- package/dist/src/tools/modify-note-tool.d.ts +1 -1
- package/dist/src/tools/modify-note-tool.js +105 -108
- package/dist/src/tools/note-tool.d.ts +1 -1
- package/dist/src/tools/note-tool.js +104 -107
- package/dist/src/tools/query-app-message-tool.d.ts +1 -1
- package/dist/src/tools/query-app-message-tool.js +109 -111
- package/dist/src/tools/query-memory-data-tool.d.ts +1 -1
- package/dist/src/tools/query-memory-data-tool.js +110 -112
- package/dist/src/tools/query-todo-task-tool.d.ts +1 -1
- package/dist/src/tools/query-todo-task-tool.js +104 -106
- package/dist/src/tools/save-file-to-phone-tool.d.ts +1 -1
- package/dist/src/tools/save-file-to-phone-tool.js +128 -131
- package/dist/src/tools/save-media-to-gallery-tool.d.ts +1 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +135 -138
- package/dist/src/tools/save-self-evolution-skill-tool.d.ts +1 -1
- package/dist/src/tools/save-self-evolution-skill-tool.js +196 -197
- package/dist/src/tools/search-alarm-tool.d.ts +1 -1
- package/dist/src/tools/search-alarm-tool.js +172 -175
- package/dist/src/tools/search-calendar-tool.d.ts +1 -1
- package/dist/src/tools/search-calendar-tool.js +146 -149
- package/dist/src/tools/search-contact-tool.d.ts +1 -1
- package/dist/src/tools/search-contact-tool.js +99 -102
- package/dist/src/tools/search-email-tool.d.ts +1 -1
- package/dist/src/tools/search-email-tool.js +108 -111
- package/dist/src/tools/search-file-tool.d.ts +1 -1
- package/dist/src/tools/search-file-tool.js +100 -103
- package/dist/src/tools/search-message-tool.d.ts +1 -1
- package/dist/src/tools/search-message-tool.js +101 -104
- package/dist/src/tools/search-note-tool.d.ts +1 -1
- package/dist/src/tools/search-note-tool.js +96 -99
- package/dist/src/tools/search-photo-gallery-tool.d.ts +1 -1
- package/dist/src/tools/search-photo-gallery-tool.js +35 -38
- package/dist/src/tools/send-email-tool.d.ts +1 -1
- package/dist/src/tools/send-email-tool.js +106 -108
- package/dist/src/tools/send-file-to-user-tool.d.ts +1 -1
- package/dist/src/tools/send-file-to-user-tool.js +157 -155
- package/dist/src/tools/send-message-tool.d.ts +1 -1
- package/dist/src/tools/send-message-tool.js +120 -123
- package/dist/src/tools/session-helper.d.ts +13 -0
- package/dist/src/tools/session-helper.js +19 -0
- package/dist/src/tools/session-manager.d.ts +21 -6
- package/dist/src/tools/session-manager.js +145 -18
- package/dist/src/tools/upload-file-tool.d.ts +1 -1
- package/dist/src/tools/upload-file-tool.js +79 -82
- package/dist/src/tools/upload-photo-tool.d.ts +1 -1
- package/dist/src/tools/upload-photo-tool.js +70 -73
- package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +1 -1
- package/dist/src/tools/xiaoyi-add-collection-tool.js +144 -147
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +1 -1
- package/dist/src/tools/xiaoyi-collection-tool.js +112 -115
- package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +1 -1
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +125 -128
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +1 -1
- package/dist/src/tools/xiaoyi-gui-tool.js +87 -88
- package/dist/src/utils/logger.js +20 -18
- package/dist/src/utils/self-evolution-manager.d.ts +5 -0
- package/dist/src/utils/self-evolution-manager.js +45 -23
- package/dist/src/websocket.d.ts +3 -0
- package/dist/src/websocket.js +81 -28
- package/dist/src/xy-session-store.d.ts +79 -0
- package/dist/src/xy-session-store.js +153 -0
- package/openclaw.plugin.json +22 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
declare const plugin: {
|
|
1
|
+
declare const _default: {
|
|
3
2
|
id: string;
|
|
4
3
|
name: string;
|
|
5
4
|
description: string;
|
|
6
5
|
configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
|
|
7
|
-
register
|
|
8
|
-
}
|
|
9
|
-
export default
|
|
6
|
+
register: NonNullable<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition["register"]>;
|
|
7
|
+
} & Pick<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
|
|
8
|
+
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -1,146 +1,87 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { xyPlugin } from "./src/channel.js";
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
|
3
2
|
import { xiaoyiProvider } from "./src/provider.js";
|
|
3
|
+
import { xyPlugin } from "./src/channel.js";
|
|
4
|
+
import { callCsplApi } from "./src/cspl/call-api.js";
|
|
5
|
+
import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH, STEER_ABORT_MESSAGE, } from "./src/cspl/constants.js";
|
|
6
|
+
import { extractResultText, parseSecurityResult, processText, validateAndTruncateText, } from "./src/cspl/utils.js";
|
|
4
7
|
import { setXYRuntime } from "./src/runtime.js";
|
|
5
8
|
import { tryInjectSteer } from "./src/steer-injector.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
return SELF_EVOLUTION_KEYWORD_PATTERNS.some((pattern) => pattern.test(text));
|
|
9
|
+
import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
|
|
10
|
+
import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
|
|
11
|
+
import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
|
|
12
|
+
function registerFullHooks(api) {
|
|
13
|
+
// SKILL RETRIEVER HOOK: before_prompt_build hook
|
|
14
|
+
const pluginConfig = api.pluginConfig || {};
|
|
15
|
+
const skillRetrieverConfig = normalizeToolRetrieverConfig({
|
|
16
|
+
enabled: pluginConfig.skillRetrieverEnabled ?? true,
|
|
17
|
+
maxTools: pluginConfig.skillRetrieverMaxTools ?? 2,
|
|
18
|
+
includeUninstalledOnly: true,
|
|
19
|
+
envFilePath: "~/.openclaw/.xiaoyienv",
|
|
20
|
+
timeoutMs: pluginConfig.skillRetrieverTimeoutMs ?? 1000,
|
|
21
|
+
});
|
|
22
|
+
const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
|
|
23
|
+
api.on("before_prompt_build", beforePromptBuildHandler);
|
|
24
|
+
registerSelfEvolutionToolResultNudge(api);
|
|
25
|
+
api.on("after_tool_call", async (event, ctx) => {
|
|
26
|
+
if (!ALLOWED_TOOLS.includes(event.toolName)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(`[SENTINEL HOOK] after_tool_call triggered: toolName=${event.toolName}, sessionKey=${ctx.sessionKey ?? "none"}`);
|
|
30
|
+
try {
|
|
31
|
+
const resultText = extractResultText(event, event.toolName);
|
|
32
|
+
const resultLength = resultText.length;
|
|
33
|
+
if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const questionText = {
|
|
37
|
+
subSceneID: "TOOL_OUTPUT",
|
|
38
|
+
tool: event.toolName,
|
|
39
|
+
output: [{ content: "" }],
|
|
40
|
+
};
|
|
41
|
+
const originText = processText(resultText);
|
|
42
|
+
questionText.output[0].content = originText;
|
|
43
|
+
let finalJson = JSON.stringify(questionText);
|
|
44
|
+
if (finalJson.length > MAX_TEXT_LENGTH) {
|
|
45
|
+
const diff = finalJson.length - MAX_TEXT_LENGTH;
|
|
46
|
+
const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
|
|
47
|
+
questionText.output[0].content = trimmed;
|
|
48
|
+
finalJson = JSON.stringify(questionText);
|
|
49
|
+
}
|
|
50
|
+
const response = await callCsplApi(finalJson, api.config);
|
|
51
|
+
const result = parseSecurityResult(response);
|
|
52
|
+
console.log(`[SENTINEL HOOK] Security result: status=${result.status}`);
|
|
53
|
+
if (result.status === "REJECT") {
|
|
54
|
+
await tryInjectSteer(ctx.sessionKey, STEER_ABORT_MESSAGE);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
api.logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
export default definePluginEntry({
|
|
63
63
|
id: "xiaoyi-channel",
|
|
64
64
|
name: "Xiaoyi Channel",
|
|
65
65
|
description: "Xiaoyi channel plugin - Xiaoyi A2A protocol integration",
|
|
66
|
-
configSchema: emptyPluginConfigSchema(),
|
|
67
66
|
register(api) {
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
// Always register the provider so wrapStreamFn/prepareExtraParams work
|
|
68
|
+
// in ALL registration modes (not just "full").
|
|
70
69
|
api.registerProvider(xiaoyiProvider);
|
|
71
|
-
api.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
api.logger.error(`[SELF_EVOLUTION] before_dispatch keyword nudge error: ${err}`);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
api.on("after_tool_call", async (event, ctx) => {
|
|
93
|
-
const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
|
|
94
|
-
if (ctx.sessionKey &&
|
|
95
|
-
selfEvolutionEnabled &&
|
|
96
|
-
shouldCountToolCall(event.toolName)) {
|
|
97
|
-
try {
|
|
98
|
-
const { count, shouldNudge } = toolCallNudgeManager.recordToolCall(ctx.sessionKey);
|
|
99
|
-
api.logger.debug?.(`[SELF_EVOLUTION] Tool call counted: tool=${event.toolName}, count=${count}, threshold=${TOOL_CALL_NUDGE_THRESHOLD}, sessionKey=${ctx.sessionKey}`);
|
|
100
|
-
if (shouldNudge) {
|
|
101
|
-
api.logger.info?.(`[SELF_EVOLUTION] Tool call threshold reached, injecting nudge: count=${count}, sessionKey=${ctx.sessionKey}`);
|
|
102
|
-
await tryInjectSteer(ctx.sessionKey, SELF_EVOLUTION_NUDGE_MESSAGE);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
api.logger.error(`[SELF_EVOLUTION] after_tool_call nudge error: ${err}`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (!ALLOWED_TOOLS.includes(event.toolName)) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
console.log(`[SENTINEL HOOK] after_tool_call triggered: toolName=${event.toolName}, sessionKey=${ctx.sessionKey ?? "none"}`);
|
|
113
|
-
try {
|
|
114
|
-
const resultText = extractResultText(event, event.toolName);
|
|
115
|
-
const resultLength = resultText.length;
|
|
116
|
-
if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
const questionText = {
|
|
120
|
-
subSceneID: "TOOL_OUTPUT",
|
|
121
|
-
tool: event.toolName,
|
|
122
|
-
output: [{ content: "" }],
|
|
123
|
-
};
|
|
124
|
-
const originText = processText(resultText);
|
|
125
|
-
questionText.output[0].content = originText;
|
|
126
|
-
let finalJson = JSON.stringify(questionText);
|
|
127
|
-
if (finalJson.length > MAX_TEXT_LENGTH) {
|
|
128
|
-
const diff = finalJson.length - MAX_TEXT_LENGTH;
|
|
129
|
-
const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
|
|
130
|
-
questionText.output[0].content = trimmed;
|
|
131
|
-
finalJson = JSON.stringify(questionText);
|
|
132
|
-
}
|
|
133
|
-
const response = await callCsplApi(finalJson, api.config);
|
|
134
|
-
const result = parseSecurityResult(response);
|
|
135
|
-
console.log(`[SENTINEL HOOK] Security result: status=${result.status}`);
|
|
136
|
-
if (result.status === "REJECT") {
|
|
137
|
-
await tryInjectSteer(ctx.sessionKey, STEER_ABORT_MESSAGE);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
api.logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
70
|
+
if (api.registrationMode === "cli-metadata") {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (api.registrationMode === "tool-discovery") {
|
|
74
|
+
registerFullHooks(api);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Register channel plugin and set runtime
|
|
78
|
+
api.registerChannel({ plugin: xyPlugin });
|
|
79
|
+
setXYRuntime(api.runtime);
|
|
80
|
+
if (api.registrationMode === "discovery") {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (api.registrationMode === "full") {
|
|
84
|
+
registerFullHooks(api);
|
|
85
|
+
}
|
|
144
86
|
},
|
|
145
|
-
};
|
|
146
|
-
export default plugin;
|
|
87
|
+
});
|
package/dist/src/bot.js
CHANGED
|
@@ -5,12 +5,14 @@ import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId,
|
|
|
5
5
|
import { downloadFilesFromParts } from "./file-download.js";
|
|
6
6
|
import { resolveXYConfig } from "./config.js";
|
|
7
7
|
import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
|
|
8
|
-
import {
|
|
8
|
+
import { appendSelfEvolutionKeywordNudge, shouldNudgeForSelfEvolutionKeyword, } from "./self-evolution-keyword.js";
|
|
9
9
|
import { configManager } from "./utils/config-manager.js";
|
|
10
10
|
import { addPushId } from "./utils/pushid-manager.js";
|
|
11
11
|
import { getPushDataById } from "./utils/pushdata-manager.js";
|
|
12
|
+
import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
|
|
12
13
|
import { saveRuntimeInfo } from "./utils/runtime-manager.js";
|
|
13
|
-
import {
|
|
14
|
+
import { toolCallNudgeManager } from "./utils/tool-call-nudge-manager.js";
|
|
15
|
+
import { registerSession, unregisterSession, hasActiveSession, xyAsyncLocalStorage, } from "./xy-session-store.js";
|
|
14
16
|
/**
|
|
15
17
|
* Handle an incoming A2A message.
|
|
16
18
|
* This is the main entry point for message processing.
|
|
@@ -87,6 +89,7 @@ export async function handleXYMessage(params) {
|
|
|
87
89
|
text: pushDataItem.dataDetail,
|
|
88
90
|
append: false,
|
|
89
91
|
final: true,
|
|
92
|
+
runtime,
|
|
90
93
|
});
|
|
91
94
|
log(`[BOT] ✅ Trigger response sent successfully, exiting early`);
|
|
92
95
|
return; // 提前返回,不继续处理
|
|
@@ -97,22 +100,26 @@ export async function handleXYMessage(params) {
|
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
// ========================================
|
|
100
|
-
// 🔑 检测steer
|
|
103
|
+
// 🔑 检测steer模式
|
|
104
|
+
// Resolve route early to determine steer state
|
|
105
|
+
const config = resolveXYConfig(cfg);
|
|
106
|
+
let route = core.channel.routing.resolveAgentRoute({
|
|
107
|
+
cfg,
|
|
108
|
+
channel: "xiaoyi-channel",
|
|
109
|
+
accountId,
|
|
110
|
+
peer: {
|
|
111
|
+
kind: "direct",
|
|
112
|
+
id: parsed.sessionId,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
|
|
101
116
|
const isSteerMode = cfg.messages?.queue?.mode === "steer";
|
|
102
|
-
const isSecondMessage = isSteerMode &&
|
|
117
|
+
const isSecondMessage = isSteerMode && hasActiveSession(route.sessionKey);
|
|
103
118
|
if (isSecondMessage) {
|
|
104
119
|
log(`[BOT] 🔄 STEER MODE - Second message detected (will be follower)`);
|
|
105
120
|
log(`[BOT] - Session: ${parsed.sessionId}`);
|
|
106
121
|
log(`[BOT] - New taskId: ${parsed.taskId} (will replace current)`);
|
|
107
122
|
}
|
|
108
|
-
// 🔑 注册taskId(第二条消息会覆盖第一条的taskId)
|
|
109
|
-
const { isUpdate, refCount } = registerTaskId(parsed.sessionId, parsed.taskId, parsed.messageId, { incrementRef: true } // 增加引用计数
|
|
110
|
-
);
|
|
111
|
-
// 🔑 如果是第一条消息,锁定taskId防止被过早清理
|
|
112
|
-
if (!isUpdate) {
|
|
113
|
-
lockTaskId(parsed.sessionId);
|
|
114
|
-
log(`[BOT] 🔒 Locked taskId for first message`);
|
|
115
|
-
}
|
|
116
123
|
// Extract and update push_id if present
|
|
117
124
|
const pushId = extractPushId(parsed.parts);
|
|
118
125
|
if (pushId) {
|
|
@@ -138,27 +145,14 @@ export async function handleXYMessage(params) {
|
|
|
138
145
|
).catch((err) => {
|
|
139
146
|
error(`[BOT] Failed to save runtime info:`, err);
|
|
140
147
|
});
|
|
141
|
-
//
|
|
142
|
-
const config = resolveXYConfig(cfg);
|
|
143
|
-
// ✅ Resolve agent route (following feishu pattern)
|
|
144
|
-
// accountId is "default" for XY (single account mode)
|
|
145
|
-
// Use sessionId as peer.id to ensure all messages in the same session share context
|
|
146
|
-
let route = core.channel.routing.resolveAgentRoute({
|
|
147
|
-
cfg,
|
|
148
|
-
channel: "xiaoyi-channel",
|
|
149
|
-
accountId, // "default"
|
|
150
|
-
peer: {
|
|
151
|
-
kind: "direct",
|
|
152
|
-
id: parsed.sessionId, // ✅ Use sessionId to share context within the same conversation session
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
|
|
148
|
+
// 🔑 Register / update session in unified store
|
|
156
149
|
registerSession(route.sessionKey, {
|
|
157
150
|
config,
|
|
158
|
-
|
|
151
|
+
a2aSessionId: parsed.sessionId,
|
|
159
152
|
taskId: parsed.taskId,
|
|
160
153
|
messageId: parsed.messageId,
|
|
161
|
-
|
|
154
|
+
accountId: route.accountId,
|
|
155
|
+
deviceType,
|
|
162
156
|
});
|
|
163
157
|
// 🔑 发送初始状态更新(第二条消息也要发,用新taskId)
|
|
164
158
|
log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
|
|
@@ -169,20 +163,41 @@ export async function handleXYMessage(params) {
|
|
|
169
163
|
messageId: parsed.messageId,
|
|
170
164
|
text: isSecondMessage ? "新消息已接收,正在处理..." : "任务正在处理中,请稍候~",
|
|
171
165
|
state: "working",
|
|
166
|
+
runtime,
|
|
172
167
|
}).catch((err) => {
|
|
173
168
|
error(`Failed to send initial status update:`, err);
|
|
174
169
|
});
|
|
175
170
|
// Extract text and files from parts
|
|
176
171
|
const text = extractTextFromParts(parsed.parts);
|
|
172
|
+
let textForAgent = text || "";
|
|
173
|
+
if (route.sessionKey && textForAgent) {
|
|
174
|
+
try {
|
|
175
|
+
const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
|
|
176
|
+
if (selfEvolutionEnabled && shouldNudgeForSelfEvolutionKeyword(textForAgent)) {
|
|
177
|
+
const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(route.sessionKey);
|
|
178
|
+
log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
|
|
179
|
+
if (shouldNudge) {
|
|
180
|
+
const augmented = appendSelfEvolutionKeywordNudge(textForAgent);
|
|
181
|
+
textForAgent = augmented.text;
|
|
182
|
+
if (augmented.appended) {
|
|
183
|
+
log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (selfEvolutionError) {
|
|
189
|
+
error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
177
192
|
const fileParts = extractFileParts(parsed.parts);
|
|
178
193
|
// Download files to local disk
|
|
179
194
|
const downloadedFiles = await downloadFilesFromParts(fileParts);
|
|
180
|
-
|
|
195
|
+
log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
|
|
181
196
|
const mediaPayload = buildXYMediaPayload(downloadedFiles);
|
|
182
197
|
// Resolve envelope format options (following feishu pattern)
|
|
183
198
|
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
184
199
|
// Build message body with speaker prefix (following feishu pattern)
|
|
185
|
-
let messageBody =
|
|
200
|
+
let messageBody = textForAgent;
|
|
186
201
|
// Add speaker prefix for clarity
|
|
187
202
|
const speaker = parsed.sessionId;
|
|
188
203
|
messageBody = `${speaker}: ${messageBody}`;
|
|
@@ -198,8 +213,8 @@ export async function handleXYMessage(params) {
|
|
|
198
213
|
// Use route.accountId and route.sessionKey instead of parsed fields
|
|
199
214
|
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
200
215
|
Body: body,
|
|
201
|
-
RawBody:
|
|
202
|
-
CommandBody:
|
|
216
|
+
RawBody: textForAgent,
|
|
217
|
+
CommandBody: textForAgent,
|
|
203
218
|
From: parsed.sessionId,
|
|
204
219
|
To: parsed.sessionId, // ✅ Simplified: use sessionId as target (context is managed by SessionKey)
|
|
205
220
|
SessionKey: route.sessionKey, // ✅ Use route.sessionKey
|
|
@@ -237,35 +252,18 @@ export async function handleXYMessage(params) {
|
|
|
237
252
|
startStatusInterval();
|
|
238
253
|
log(`[BOT-DISPATCHER] ✅ Status interval started for first message`);
|
|
239
254
|
}
|
|
240
|
-
// Build session context for AsyncLocalStorage
|
|
241
|
-
const sessionContext = {
|
|
242
|
-
config,
|
|
243
|
-
sessionId: parsed.sessionId,
|
|
244
|
-
taskId: parsed.taskId,
|
|
245
|
-
messageId: parsed.messageId,
|
|
246
|
-
agentId: route.accountId,
|
|
247
|
-
deviceType,
|
|
248
|
-
};
|
|
249
255
|
log(`[BOT-DISPATCH] ⏳ withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
|
|
250
|
-
|
|
256
|
+
// 🔐 Set ALS BEFORE withReplyDispatcher so agentTools factory can read it
|
|
257
|
+
// during dispatcher setup (which happens before run() is called).
|
|
258
|
+
await xyAsyncLocalStorage.run({ openclawSessionKey: route.sessionKey }, () => core.channel.reply.withReplyDispatcher({
|
|
251
259
|
dispatcher,
|
|
252
260
|
onSettled: () => {
|
|
253
261
|
log(`[BOT] 🏁 onSettled called for session: ${route.sessionKey}`);
|
|
254
|
-
|
|
255
|
-
// 🔑 减少引用计数
|
|
256
|
-
decrementTaskIdRef(parsed.sessionId);
|
|
257
|
-
// 🔑 如果是第一条消息完成,解锁
|
|
258
|
-
if (!isSecondMessage) {
|
|
259
|
-
unlockTaskId(parsed.sessionId);
|
|
260
|
-
log(`[BOT] 🔓 Unlocked taskId (first message completed)`);
|
|
261
|
-
}
|
|
262
|
-
// 减少session引用计数
|
|
262
|
+
// Cleanup session from store
|
|
263
263
|
unregisterSession(route.sessionKey);
|
|
264
264
|
log(`[BOT] ✅ Cleanup completed`);
|
|
265
265
|
},
|
|
266
|
-
run: () =>
|
|
267
|
-
// 🔐 Use AsyncLocalStorage to provide session context to tools
|
|
268
|
-
runWithSessionContext(sessionContext, async () => {
|
|
266
|
+
run: async () => {
|
|
269
267
|
log(`[BOT-DISPATCH] ⏳ dispatchReplyFromConfig starting...`);
|
|
270
268
|
log(`[BOT-DISPATCH] - sessionKey: ${ctxPayload.SessionKey}`);
|
|
271
269
|
log(`[BOT-DISPATCH] - provider: ${ctxPayload.Provider}`);
|
|
@@ -290,8 +288,8 @@ export async function handleXYMessage(params) {
|
|
|
290
288
|
error(`[BOT-DISPATCH] - error stack: ${dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : "N/A"}`);
|
|
291
289
|
throw dispatchErr;
|
|
292
290
|
}
|
|
293
|
-
}
|
|
294
|
-
});
|
|
291
|
+
},
|
|
292
|
+
}));
|
|
295
293
|
log(`[BOT] ✅ Dispatcher completed for session: ${parsed.sessionId}`);
|
|
296
294
|
log(`xy: dispatch complete (session=${parsed.sessionId})`);
|
|
297
295
|
}
|
|
@@ -300,18 +298,14 @@ export async function handleXYMessage(params) {
|
|
|
300
298
|
error("Failed to handle XY message:", err);
|
|
301
299
|
runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
|
|
302
300
|
log(`[BOT] ❌ Error occurred, attempting cleanup...`);
|
|
303
|
-
// 🔑 错误时也要清理
|
|
301
|
+
// 🔑 错误时也要清理session
|
|
304
302
|
try {
|
|
305
|
-
const
|
|
306
|
-
const sessionId =
|
|
303
|
+
const msgParams = message.params;
|
|
304
|
+
const sessionId = msgParams?.sessionId;
|
|
307
305
|
if (sessionId) {
|
|
308
306
|
log(`[BOT] 🧹 Cleaning up after error: ${sessionId}`);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
unlockTaskId(sessionId);
|
|
312
|
-
// 清理 session
|
|
313
|
-
const core = getXYRuntime();
|
|
314
|
-
const route = core.channel.routing.resolveAgentRoute({
|
|
307
|
+
const core2 = getXYRuntime();
|
|
308
|
+
const errorRoute = core2.channel.routing.resolveAgentRoute({
|
|
315
309
|
cfg,
|
|
316
310
|
channel: "xiaoyi-channel",
|
|
317
311
|
accountId,
|
|
@@ -320,13 +314,12 @@ export async function handleXYMessage(params) {
|
|
|
320
314
|
id: sessionId,
|
|
321
315
|
},
|
|
322
316
|
});
|
|
323
|
-
unregisterSession(
|
|
317
|
+
unregisterSession(errorRoute.sessionKey);
|
|
324
318
|
log(`[BOT] ✅ Cleanup completed after error`);
|
|
325
319
|
}
|
|
326
320
|
}
|
|
327
321
|
catch (cleanupErr) {
|
|
328
322
|
log(`[BOT] ⚠️ Cleanup failed:`, cleanupErr);
|
|
329
|
-
// Ignore cleanup errors
|
|
330
323
|
}
|
|
331
324
|
// ❌ Don't re-throw: message processing error should not affect gateway stability
|
|
332
325
|
}
|
package/dist/src/channel.js
CHANGED
|
@@ -1,28 +1,9 @@
|
|
|
1
1
|
import { resolveXYConfig, listXYAccountIds, getDefaultXYAccountId } from "./config.js";
|
|
2
2
|
import { xyConfigSchema } from "./config-schema.js";
|
|
3
3
|
import { xyOutbound } from "./outbound.js";
|
|
4
|
-
import { locationTool } from "./tools/location-tool.js";
|
|
5
|
-
import { xiaoyiGuiTool } from "./tools/xiaoyi-gui-tool.js";
|
|
6
|
-
import { sendFileToUserTool } from "./tools/send-file-to-user-tool.js";
|
|
7
|
-
import { viewPushResultTool } from "./tools/view-push-result-tool.js";
|
|
8
|
-
import { imageReadingTool } from "./tools/image-reading-tool.js";
|
|
9
|
-
import { timestampToUtc8Tool } from "./tools/timestamp-to-utc8-tool.js";
|
|
10
|
-
import { saveSelfEvolutionSkillTool } from "./tools/save-self-evolution-skill-tool.js";
|
|
11
|
-
import { getEmailToolSchemaTool } from "./tools/get-email-tool-schema.js";
|
|
12
|
-
import { callDeviceTool } from "./tools/call-device-tool.js";
|
|
13
|
-
import { getNoteToolSchemaTool } from "./tools/get-note-tool-schema.js";
|
|
14
|
-
import { getCalendarToolSchemaTool } from "./tools/get-calendar-tool-schema.js";
|
|
15
|
-
import { getContactToolSchemaTool } from "./tools/get-contact-tool-schema.js";
|
|
16
|
-
import { getPhotoToolSchemaTool } from "./tools/get-photo-tool-schema.js";
|
|
17
|
-
import { getDeviceFileToolSchemaTool } from "./tools/get-device-file-tool-schema.js";
|
|
18
|
-
import { getAlarmToolSchemaTool } from "./tools/get-alarm-tool-schema.js";
|
|
19
|
-
import { getCollectionToolSchemaTool } from "./tools/get-collection-tool-schema.js";
|
|
20
|
-
import { queryAppMessageTool } from "./tools/query-app-message-tool.js";
|
|
21
|
-
import { queryMemoryDataTool } from "./tools/query-memory-data-tool.js";
|
|
22
|
-
import { queryTodoTaskTool } from "./tools/query-todo-task-tool.js";
|
|
23
|
-
import { loginTokenTool } from "./tools/login-token-tool.js";
|
|
24
4
|
import { filterToolsByDevice } from "./tools/device-tool-map.js";
|
|
25
|
-
import {
|
|
5
|
+
import { getSession, xyAsyncLocalStorage } from "./xy-session-store.js";
|
|
6
|
+
import { createAllTools } from "./tools/create-all-tools.js";
|
|
26
7
|
import { logger } from "./utils/logger.js";
|
|
27
8
|
/**
|
|
28
9
|
* Xiaoyi Channel Plugin for OpenClaw.
|
|
@@ -63,10 +44,18 @@ export const xyPlugin = {
|
|
|
63
44
|
},
|
|
64
45
|
outbound: xyOutbound,
|
|
65
46
|
agentTools: () => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
47
|
+
// agentTools factory is called during agent run setup.
|
|
48
|
+
// ALS is still valid at this point — read sessionKey from it.
|
|
49
|
+
const alsContext = xyAsyncLocalStorage.getStore();
|
|
50
|
+
const sessionKey = alsContext?.openclawSessionKey;
|
|
51
|
+
if (!sessionKey) {
|
|
52
|
+
logger.log("[CHANNEL-TOOLS] no sessionKey in ALS, returning empty tools");
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const session = getSession(sessionKey);
|
|
56
|
+
const allTools = createAllTools(sessionKey);
|
|
57
|
+
const filtered = filterToolsByDevice(allTools, session?.deviceType);
|
|
58
|
+
logger.log(`[CHANNEL-TOOLS] sessionKey=${sessionKey} deviceType=${session?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
|
|
70
59
|
return filtered;
|
|
71
60
|
},
|
|
72
61
|
messaging: {
|