@ynhcj/xiaoyi-channel 0.0.104-beta → 0.0.104-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 +57 -64
- 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 +214 -72
- 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 +347 -125
- 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/utils/tool-call-nudge-manager.d.ts +1 -1
- package/dist/src/utils/tool-call-nudge-manager.js +1 -1
- 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 +3 -3
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
|
await core.channel.reply.withReplyDispatcher({
|
|
251
257
|
dispatcher,
|
|
252
258
|
onSettled: () => {
|
|
253
259
|
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引用计数
|
|
260
|
+
// Cleanup session from store
|
|
263
261
|
unregisterSession(route.sessionKey);
|
|
264
262
|
log(`[BOT] ✅ Cleanup completed`);
|
|
265
263
|
},
|
|
266
264
|
run: () =>
|
|
267
|
-
// 🔐
|
|
268
|
-
|
|
265
|
+
// 🔐 Run inside ALS with openclawSessionKey so agentTools factory can read it
|
|
266
|
+
xyAsyncLocalStorage.run({ openclawSessionKey: route.sessionKey }, async () => {
|
|
269
267
|
log(`[BOT-DISPATCH] ⏳ dispatchReplyFromConfig starting...`);
|
|
270
268
|
log(`[BOT-DISPATCH] - sessionKey: ${ctxPayload.SessionKey}`);
|
|
271
269
|
log(`[BOT-DISPATCH] - provider: ${ctxPayload.Provider}`);
|
|
@@ -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: {
|
package/dist/src/client.js
CHANGED
|
@@ -12,8 +12,14 @@ export function setClientRuntime(rt) {
|
|
|
12
12
|
/**
|
|
13
13
|
* Global cache for WebSocket managers.
|
|
14
14
|
* Key format: `${apiKey}-${agentId}`
|
|
15
|
+
* Uses globalThis to ensure a single cache across all module copies
|
|
16
|
+
* (same fix as session-manager.ts for openclaw multi-instance loading).
|
|
15
17
|
*/
|
|
16
|
-
const
|
|
18
|
+
const _g = globalThis;
|
|
19
|
+
if (!_g.__xyWsManagerCache) {
|
|
20
|
+
_g.__xyWsManagerCache = new Map();
|
|
21
|
+
}
|
|
22
|
+
const wsManagerCache = _g.__xyWsManagerCache;
|
|
17
23
|
/**
|
|
18
24
|
* Get or create a WebSocket manager for the given configuration.
|
|
19
25
|
* Reuses existing managers if config matches.
|
|
@@ -38,16 +44,17 @@ export function getXYWebSocketManager(config) {
|
|
|
38
44
|
* Disconnects the manager and removes it from the cache.
|
|
39
45
|
*/
|
|
40
46
|
export function removeXYWebSocketManager(config) {
|
|
47
|
+
const log = runtime?.log ?? console.log;
|
|
41
48
|
const cacheKey = `${config.apiKey}-${config.agentId}`;
|
|
42
49
|
const manager = wsManagerCache.get(cacheKey);
|
|
43
50
|
if (manager) {
|
|
44
|
-
|
|
51
|
+
log(`🗑️ [WS-MANAGER-CACHE] Removing manager from cache: ${cacheKey}`);
|
|
45
52
|
manager.disconnect();
|
|
46
53
|
wsManagerCache.delete(cacheKey);
|
|
47
|
-
|
|
54
|
+
log(`🗑️ [WS-MANAGER-CACHE] Manager removed, remaining managers: ${wsManagerCache.size}`);
|
|
48
55
|
}
|
|
49
56
|
else {
|
|
50
|
-
|
|
57
|
+
log(`⚠️ [WS-MANAGER-CACHE] Manager not found in cache: ${cacheKey}`);
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
60
|
/**
|
|
@@ -72,36 +79,37 @@ export function getCachedManagerCount() {
|
|
|
72
79
|
* Helps identify connection issues and orphan connections.
|
|
73
80
|
*/
|
|
74
81
|
export function diagnoseAllManagers() {
|
|
75
|
-
|
|
82
|
+
const log = runtime?.log ?? console.log;
|
|
83
|
+
log(`Total cached managers: ${wsManagerCache.size}`);
|
|
76
84
|
if (wsManagerCache.size === 0) {
|
|
77
|
-
|
|
85
|
+
log("ℹ️ No managers in cache");
|
|
78
86
|
return;
|
|
79
87
|
}
|
|
80
88
|
let orphanCount = 0;
|
|
81
89
|
wsManagerCache.forEach((manager, key) => {
|
|
82
90
|
const diag = manager.getConnectionDiagnostics();
|
|
83
|
-
|
|
91
|
+
log(` Total event listeners on manager: ${diag.totalEventListeners}`);
|
|
84
92
|
// Connection
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
log(` 🔌 Connection:`);
|
|
94
|
+
log(` - Exists: ${diag.connection.exists}`);
|
|
95
|
+
log(` - ReadyState: ${diag.connection.readyState}`);
|
|
96
|
+
log(` - State connected/ready: ${diag.connection.stateConnected}/${diag.connection.stateReady}`);
|
|
97
|
+
log(` - Reconnect attempts: ${diag.connection.reconnectAttempts}`);
|
|
98
|
+
log(` - Listeners on WebSocket: ${diag.connection.listenerCount}`);
|
|
99
|
+
log(` - Heartbeat active: ${diag.connection.heartbeatActive}`);
|
|
100
|
+
log(` - Has reconnect timer: ${diag.connection.hasReconnectTimer}`);
|
|
93
101
|
if (diag.connection.isOrphan) {
|
|
94
|
-
|
|
102
|
+
log(` ⚠️ ORPHAN CONNECTION DETECTED!`);
|
|
95
103
|
orphanCount++;
|
|
96
104
|
}
|
|
97
|
-
|
|
105
|
+
log("");
|
|
98
106
|
});
|
|
99
107
|
if (orphanCount > 0) {
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
log(`⚠️ Total orphan connections found: ${orphanCount}`);
|
|
109
|
+
log(`💡 Suggestion: These connections should be cleaned up`);
|
|
102
110
|
}
|
|
103
111
|
else {
|
|
104
|
-
|
|
112
|
+
log(`✅ No orphan connections found`);
|
|
105
113
|
}
|
|
106
114
|
}
|
|
107
115
|
/**
|
|
@@ -109,17 +117,18 @@ export function diagnoseAllManagers() {
|
|
|
109
117
|
* Returns the number of managers that had orphan connections.
|
|
110
118
|
*/
|
|
111
119
|
export function cleanupOrphanConnections() {
|
|
120
|
+
const log = runtime?.log ?? console.log;
|
|
112
121
|
let cleanedCount = 0;
|
|
113
122
|
wsManagerCache.forEach((manager, key) => {
|
|
114
123
|
const diag = manager.getConnectionDiagnostics();
|
|
115
124
|
if (diag.connection.isOrphan) {
|
|
116
|
-
|
|
125
|
+
log(`🧹 Cleaning up orphan connections in manager: ${key}`);
|
|
117
126
|
manager.disconnect();
|
|
118
127
|
cleanedCount++;
|
|
119
128
|
}
|
|
120
129
|
});
|
|
121
130
|
if (cleanedCount > 0) {
|
|
122
|
-
|
|
131
|
+
log(`🧹 Cleaned up ${cleanedCount} manager(s) with orphan connections`);
|
|
123
132
|
}
|
|
124
133
|
return cleanedCount;
|
|
125
134
|
}
|