@ynhcj/xiaoyi-channel 0.0.99-beta → 0.0.99-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 -10
- package/dist/index.js +76 -66
- package/dist/provider-discovery.d.ts +2 -0
- package/dist/provider-discovery.js +4 -0
- package/dist/src/bot.js +29 -4
- package/dist/src/channel.js +2 -20
- 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 +9 -28
- 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 +63 -14
- package/dist/src/outbound.js +19 -18
- package/dist/src/provider.d.ts +2 -1
- package/dist/src/provider.js +211 -41
- package/dist/src/push.js +16 -15
- package/dist/src/reply-dispatcher.js +12 -3
- 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 +100 -7
- 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 +8 -12
- 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 +1 -1
- 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 +2 -1
- package/dist/src/tools/calendar-tool.js +112 -116
- package/dist/src/tools/call-device-tool.d.ts +2 -1
- package/dist/src/tools/call-device-tool.js +126 -103
- package/dist/src/tools/call-phone-tool.d.ts +2 -1
- package/dist/src/tools/call-phone-tool.js +109 -113
- package/dist/src/tools/create-alarm-tool.d.ts +2 -1
- package/dist/src/tools/create-alarm-tool.js +227 -231
- package/dist/src/tools/create-all-tools.d.ts +16 -0
- package/dist/src/tools/create-all-tools.js +50 -0
- package/dist/src/tools/delete-alarm-tool.d.ts +2 -1
- package/dist/src/tools/delete-alarm-tool.js +131 -135
- package/dist/src/tools/get-alarm-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-alarm-tool-schema.js +16 -10
- package/dist/src/tools/get-calendar-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-calendar-tool-schema.js +12 -8
- package/dist/src/tools/get-collection-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-collection-tool-schema.js +11 -9
- package/dist/src/tools/get-contact-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-contact-tool-schema.js +16 -10
- package/dist/src/tools/get-device-file-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-device-file-tool-schema.js +13 -9
- package/dist/src/tools/get-email-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-email-tool-schema.js +11 -8
- package/dist/src/tools/get-note-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-note-tool-schema.js +14 -9
- package/dist/src/tools/get-photo-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-photo-tool-schema.js +12 -9
- package/dist/src/tools/image-reading-tool.d.ts +2 -1
- package/dist/src/tools/image-reading-tool.js +86 -90
- package/dist/src/tools/location-tool.d.ts +2 -1
- package/dist/src/tools/location-tool.js +87 -91
- package/dist/src/tools/login-token-tool.d.ts +3 -2
- package/dist/src/tools/login-token-tool.js +114 -117
- package/dist/src/tools/modify-alarm-tool.d.ts +2 -1
- package/dist/src/tools/modify-alarm-tool.js +232 -236
- package/dist/src/tools/modify-note-tool.d.ts +2 -1
- package/dist/src/tools/modify-note-tool.js +104 -108
- package/dist/src/tools/note-tool.d.ts +2 -1
- package/dist/src/tools/note-tool.js +103 -107
- package/dist/src/tools/query-app-message-tool.d.ts +2 -1
- package/dist/src/tools/query-app-message-tool.js +108 -111
- package/dist/src/tools/query-memory-data-tool.d.ts +2 -1
- package/dist/src/tools/query-memory-data-tool.js +109 -112
- package/dist/src/tools/query-todo-task-tool.d.ts +2 -1
- package/dist/src/tools/query-todo-task-tool.js +103 -106
- package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -1
- package/dist/src/tools/save-file-to-phone-tool.js +127 -131
- package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +134 -138
- package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -0
- package/dist/src/tools/save-self-evolution-skill-tool.js +410 -0
- package/dist/src/tools/search-alarm-tool.d.ts +2 -1
- package/dist/src/tools/search-alarm-tool.js +171 -175
- package/dist/src/tools/search-calendar-tool.d.ts +2 -1
- package/dist/src/tools/search-calendar-tool.js +145 -149
- package/dist/src/tools/search-contact-tool.d.ts +2 -1
- package/dist/src/tools/search-contact-tool.js +98 -102
- package/dist/src/tools/search-email-tool.d.ts +2 -1
- package/dist/src/tools/search-email-tool.js +107 -111
- package/dist/src/tools/search-file-tool.d.ts +2 -1
- package/dist/src/tools/search-file-tool.js +99 -103
- package/dist/src/tools/search-message-tool.d.ts +2 -1
- package/dist/src/tools/search-message-tool.js +100 -104
- package/dist/src/tools/search-note-tool.d.ts +2 -1
- package/dist/src/tools/search-note-tool.js +95 -99
- package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -1
- package/dist/src/tools/search-photo-gallery-tool.js +34 -38
- package/dist/src/tools/send-email-tool.d.ts +2 -1
- package/dist/src/tools/send-email-tool.js +105 -108
- package/dist/src/tools/send-file-to-user-tool.d.ts +2 -1
- package/dist/src/tools/send-file-to-user-tool.js +154 -155
- package/dist/src/tools/send-message-tool.d.ts +2 -1
- package/dist/src/tools/send-message-tool.js +119 -123
- package/dist/src/tools/session-manager.d.ts +21 -6
- package/dist/src/tools/session-manager.js +147 -18
- package/dist/src/tools/upload-file-tool.d.ts +2 -1
- package/dist/src/tools/upload-file-tool.js +78 -82
- package/dist/src/tools/upload-photo-tool.d.ts +2 -1
- package/dist/src/tools/upload-photo-tool.js +69 -73
- package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-add-collection-tool.js +143 -147
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-collection-tool.js +111 -115
- package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +124 -128
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-gui-tool.js +84 -88
- package/dist/src/utils/logger.js +20 -18
- package/dist/src/utils/runtime-manager.js +24 -2
- package/dist/src/utils/self-evolution-manager.d.ts +10 -0
- package/dist/src/utils/self-evolution-manager.js +69 -0
- package/dist/src/utils/tool-call-nudge-manager.d.ts +16 -0
- package/dist/src/utils/tool-call-nudge-manager.js +47 -0
- package/dist/src/websocket.d.ts +3 -0
- package/dist/src/websocket.js +81 -28
- package/openclaw.plugin.json +22 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Xiaoyi Channel Plugin Entry Point.
|
|
4
|
-
* Exports the plugin for OpenClaw to load.
|
|
5
|
-
* Located at root level following feishu pattern for proper plugin registration.
|
|
6
|
-
*/
|
|
7
|
-
declare const plugin: {
|
|
1
|
+
declare const _default: {
|
|
8
2
|
id: string;
|
|
9
3
|
name: string;
|
|
10
4
|
description: string;
|
|
11
5
|
configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
|
|
12
|
-
register
|
|
13
|
-
}
|
|
14
|
-
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,77 +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 { extractResultText, processText, parseSecurityResult, validateAndTruncateText } from "./src/cspl/utils.js";
|
|
8
|
-
import { ALLOWED_TOOLS, MIN_TEXT_LENGTH, MAX_TOTAL_LENGTH, MAX_TEXT_LENGTH, STEER_ABORT_MESSAGE, } from "./src/cspl/constants.js";
|
|
9
|
+
import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
|
|
9
10
|
import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
|
|
10
11
|
import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
}
|
|
62
|
+
export default definePluginEntry({
|
|
17
63
|
id: "xiaoyi-channel",
|
|
18
64
|
name: "Xiaoyi Channel",
|
|
19
65
|
description: "Xiaoyi channel plugin - Xiaoyi A2A protocol integration",
|
|
20
|
-
configSchema: emptyPluginConfigSchema(),
|
|
21
66
|
register(api) {
|
|
22
|
-
|
|
23
|
-
|
|
67
|
+
// Always register the provider so wrapStreamFn/prepareExtraParams work
|
|
68
|
+
// in ALL registration modes (not just "full").
|
|
24
69
|
api.registerProvider(xiaoyiProvider);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
tool: event.toolName,
|
|
42
|
-
output: [{ content: "" }],
|
|
43
|
-
};
|
|
44
|
-
const originText = processText(resultText);
|
|
45
|
-
questionText.output[0].content = originText;
|
|
46
|
-
let finalJson = JSON.stringify(questionText);
|
|
47
|
-
if (finalJson.length > MAX_TEXT_LENGTH) {
|
|
48
|
-
const diff = finalJson.length - MAX_TEXT_LENGTH;
|
|
49
|
-
const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
|
|
50
|
-
questionText.output[0].content = trimmed;
|
|
51
|
-
finalJson = JSON.stringify(questionText);
|
|
52
|
-
}
|
|
53
|
-
const response = await callCsplApi(finalJson, api.config);
|
|
54
|
-
const result = parseSecurityResult(response);
|
|
55
|
-
console.log(`[SENTINEL HOOK] Security result: status=${result.status}`);
|
|
56
|
-
if (result.status === "REJECT") {
|
|
57
|
-
await tryInjectSteer(ctx.sessionKey, STEER_ABORT_MESSAGE);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
api.logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
// SKILL RETRIEVER HOOK: before_prompt_build hook
|
|
65
|
-
const pluginConfig = api.pluginConfig || {};
|
|
66
|
-
const skillRetrieverConfig = normalizeToolRetrieverConfig({
|
|
67
|
-
enabled: pluginConfig.skillRetrieverEnabled ?? true,
|
|
68
|
-
maxTools: pluginConfig.skillRetrieverMaxTools ?? 2,
|
|
69
|
-
includeUninstalledOnly: true,
|
|
70
|
-
envFilePath: "~/.openclaw/.xiaoyienv",
|
|
71
|
-
timeoutMs: pluginConfig.skillRetrieverTimeoutMs ?? 1000,
|
|
72
|
-
});
|
|
73
|
-
const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
|
|
74
|
-
api.on("before_prompt_build", beforePromptBuildHandler);
|
|
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
|
+
}
|
|
75
86
|
},
|
|
76
|
-
};
|
|
77
|
-
export default plugin;
|
|
87
|
+
});
|
package/dist/src/bot.js
CHANGED
|
@@ -5,11 +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 { appendSelfEvolutionKeywordNudge, shouldNudgeForSelfEvolutionKeyword, } from "./self-evolution-keyword.js";
|
|
8
9
|
import { registerSession, unregisterSession, runWithSessionContext } from "./tools/session-manager.js";
|
|
9
10
|
import { configManager } from "./utils/config-manager.js";
|
|
10
11
|
import { addPushId } from "./utils/pushid-manager.js";
|
|
11
12
|
import { getPushDataById } from "./utils/pushdata-manager.js";
|
|
13
|
+
import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
|
|
12
14
|
import { saveRuntimeInfo } from "./utils/runtime-manager.js";
|
|
15
|
+
import { toolCallNudgeManager } from "./utils/tool-call-nudge-manager.js";
|
|
13
16
|
import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActiveTask, } from "./task-manager.js";
|
|
14
17
|
/**
|
|
15
18
|
* Handle an incoming A2A message.
|
|
@@ -87,6 +90,7 @@ export async function handleXYMessage(params) {
|
|
|
87
90
|
text: pushDataItem.dataDetail,
|
|
88
91
|
append: false,
|
|
89
92
|
final: true,
|
|
93
|
+
runtime,
|
|
90
94
|
});
|
|
91
95
|
log(`[BOT] ✅ Trigger response sent successfully, exiting early`);
|
|
92
96
|
return; // 提前返回,不继续处理
|
|
@@ -169,20 +173,41 @@ export async function handleXYMessage(params) {
|
|
|
169
173
|
messageId: parsed.messageId,
|
|
170
174
|
text: isSecondMessage ? "新消息已接收,正在处理..." : "任务正在处理中,请稍候~",
|
|
171
175
|
state: "working",
|
|
176
|
+
runtime,
|
|
172
177
|
}).catch((err) => {
|
|
173
178
|
error(`Failed to send initial status update:`, err);
|
|
174
179
|
});
|
|
175
180
|
// Extract text and files from parts
|
|
176
181
|
const text = extractTextFromParts(parsed.parts);
|
|
182
|
+
let textForAgent = text || "";
|
|
183
|
+
if (route.sessionKey && textForAgent) {
|
|
184
|
+
try {
|
|
185
|
+
const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
|
|
186
|
+
if (selfEvolutionEnabled && shouldNudgeForSelfEvolutionKeyword(textForAgent)) {
|
|
187
|
+
const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(route.sessionKey);
|
|
188
|
+
log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
|
|
189
|
+
if (shouldNudge) {
|
|
190
|
+
const augmented = appendSelfEvolutionKeywordNudge(textForAgent);
|
|
191
|
+
textForAgent = augmented.text;
|
|
192
|
+
if (augmented.appended) {
|
|
193
|
+
log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (selfEvolutionError) {
|
|
199
|
+
error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
177
202
|
const fileParts = extractFileParts(parsed.parts);
|
|
178
203
|
// Download files to local disk
|
|
179
204
|
const downloadedFiles = await downloadFilesFromParts(fileParts);
|
|
180
|
-
|
|
205
|
+
log("Downloaded files:", JSON.stringify(downloadedFiles, null, 2));
|
|
181
206
|
const mediaPayload = buildXYMediaPayload(downloadedFiles);
|
|
182
207
|
// Resolve envelope format options (following feishu pattern)
|
|
183
208
|
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
184
209
|
// Build message body with speaker prefix (following feishu pattern)
|
|
185
|
-
let messageBody =
|
|
210
|
+
let messageBody = textForAgent;
|
|
186
211
|
// Add speaker prefix for clarity
|
|
187
212
|
const speaker = parsed.sessionId;
|
|
188
213
|
messageBody = `${speaker}: ${messageBody}`;
|
|
@@ -198,8 +223,8 @@ export async function handleXYMessage(params) {
|
|
|
198
223
|
// Use route.accountId and route.sessionKey instead of parsed fields
|
|
199
224
|
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
200
225
|
Body: body,
|
|
201
|
-
RawBody:
|
|
202
|
-
CommandBody:
|
|
226
|
+
RawBody: textForAgent,
|
|
227
|
+
CommandBody: textForAgent,
|
|
203
228
|
From: parsed.sessionId,
|
|
204
229
|
To: parsed.sessionId, // ✅ Simplified: use sessionId as target (context is managed by SessionKey)
|
|
205
230
|
SessionKey: route.sessionKey, // ✅ Use route.sessionKey
|
package/dist/src/channel.js
CHANGED
|
@@ -1,27 +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 { getEmailToolSchemaTool } from "./tools/get-email-tool-schema.js";
|
|
11
|
-
import { callDeviceTool } from "./tools/call-device-tool.js";
|
|
12
|
-
import { getNoteToolSchemaTool } from "./tools/get-note-tool-schema.js";
|
|
13
|
-
import { getCalendarToolSchemaTool } from "./tools/get-calendar-tool-schema.js";
|
|
14
|
-
import { getContactToolSchemaTool } from "./tools/get-contact-tool-schema.js";
|
|
15
|
-
import { getPhotoToolSchemaTool } from "./tools/get-photo-tool-schema.js";
|
|
16
|
-
import { getDeviceFileToolSchemaTool } from "./tools/get-device-file-tool-schema.js";
|
|
17
|
-
import { getAlarmToolSchemaTool } from "./tools/get-alarm-tool-schema.js";
|
|
18
|
-
import { getCollectionToolSchemaTool } from "./tools/get-collection-tool-schema.js";
|
|
19
|
-
import { queryAppMessageTool } from "./tools/query-app-message-tool.js";
|
|
20
|
-
import { queryMemoryDataTool } from "./tools/query-memory-data-tool.js";
|
|
21
|
-
import { queryTodoTaskTool } from "./tools/query-todo-task-tool.js";
|
|
22
|
-
import { loginTokenTool } from "./tools/login-token-tool.js";
|
|
23
4
|
import { filterToolsByDevice } from "./tools/device-tool-map.js";
|
|
24
5
|
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
6
|
+
import { createAllTools } from "./tools/create-all-tools.js";
|
|
25
7
|
import { logger } from "./utils/logger.js";
|
|
26
8
|
/**
|
|
27
9
|
* Xiaoyi Channel Plugin for OpenClaw.
|
|
@@ -62,8 +44,8 @@ export const xyPlugin = {
|
|
|
62
44
|
},
|
|
63
45
|
outbound: xyOutbound,
|
|
64
46
|
agentTools: () => {
|
|
65
|
-
const allTools = [locationTool, callDeviceTool, getNoteToolSchemaTool, getCalendarToolSchemaTool, getContactToolSchemaTool, getPhotoToolSchemaTool, xiaoyiGuiTool, getDeviceFileToolSchemaTool, getAlarmToolSchemaTool, getCollectionToolSchemaTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, getEmailToolSchemaTool, queryAppMessageTool, queryMemoryDataTool, queryTodoTaskTool, loginTokenTool];
|
|
66
47
|
const ctx = getCurrentSessionContext();
|
|
48
|
+
const allTools = createAllTools(ctx);
|
|
67
49
|
const filtered = filterToolsByDevice(allTools, ctx?.deviceType);
|
|
68
50
|
logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
|
|
69
51
|
return filtered;
|
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
|
}
|
|
@@ -4,12 +4,13 @@ import { URL } from "node:url";
|
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
5
|
import { getCsplConfig } from "./config.js";
|
|
6
6
|
import { DEFAULT_HTTP_PORT, HTTP_STATUS_BAD_REQUEST } from "./constants.js";
|
|
7
|
+
import { logger } from "../utils/logger.js";
|
|
7
8
|
function generateTraceId() {
|
|
8
9
|
return randomBytes(16).toString("hex");
|
|
9
10
|
}
|
|
10
11
|
function buildHeaders(config) {
|
|
11
12
|
const traceId = generateTraceId();
|
|
12
|
-
|
|
13
|
+
logger.log(`[SENTINEL HOOK] trace-id: ${traceId}`);
|
|
13
14
|
return {
|
|
14
15
|
"x-hag-trace-id": traceId,
|
|
15
16
|
"x-uid": config.uid,
|
|
@@ -65,21 +66,21 @@ export async function callCsplApi(questionText, cfg) {
|
|
|
65
66
|
res.on("end", () => {
|
|
66
67
|
try {
|
|
67
68
|
const result = parseResponse(data);
|
|
68
|
-
|
|
69
|
+
logger.log(`[SENTINEL HOOK] ✅ 请求成功`);
|
|
69
70
|
resolve(result);
|
|
70
71
|
}
|
|
71
72
|
catch (e) {
|
|
72
|
-
|
|
73
|
+
logger.error(`[SENTINEL HOOK] ❌ 请求失败: ${e instanceof Error ? e.message : String(e)}`);
|
|
73
74
|
reject(e);
|
|
74
75
|
}
|
|
75
76
|
});
|
|
76
77
|
});
|
|
77
78
|
req.on("error", (error) => {
|
|
78
|
-
|
|
79
|
+
logger.error(`[SENTINEL HOOK] ❌ 请求错误: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
80
|
reject(error);
|
|
80
81
|
});
|
|
81
82
|
req.on("timeout", () => {
|
|
82
|
-
|
|
83
|
+
logger.error(`[SENTINEL HOOK] ⏰ 请求超时 (${config.api.timeout}ms)`);
|
|
83
84
|
req.destroy();
|
|
84
85
|
reject(new Error("[SENTINEL HOOK] Request timeout"));
|
|
85
86
|
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import fetch from "node-fetch";
|
|
3
3
|
import fs from "fs/promises";
|
|
4
4
|
import path from "path";
|
|
5
|
+
import { logger } from "./utils/logger.js";
|
|
5
6
|
/**
|
|
6
7
|
* Download a file from URL to local path.
|
|
7
8
|
*/
|
|
@@ -19,10 +20,10 @@ export async function downloadFile(url, destPath) {
|
|
|
19
20
|
}
|
|
20
21
|
catch (error) {
|
|
21
22
|
if (error.name === 'AbortError') {
|
|
22
|
-
|
|
23
|
+
logger.log(`Download timeout (30s) for ${url}`);
|
|
23
24
|
throw new Error(`Download timeout after 30 seconds`);
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
+
logger.log(`Failed to download file from ${url}:`);
|
|
26
27
|
throw error;
|
|
27
28
|
}
|
|
28
29
|
finally {
|
|
@@ -51,7 +52,7 @@ export async function downloadFilesFromParts(fileParts, tempDir = "/tmp/xy_chann
|
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
54
|
catch (error) {
|
|
54
|
-
|
|
55
|
+
logger.log(`Failed to download file ${name}:`);
|
|
55
56
|
// Continue with other files
|
|
56
57
|
}
|
|
57
58
|
}
|
package/dist/src/file-upload.js
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
import fetch from "node-fetch";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
import os from "os";
|
|
6
|
+
import { logger } from "./utils/logger.js";
|
|
6
7
|
import path from "path";
|
|
7
8
|
import { calculateSHA256 } from "./utils/crypto.js";
|
|
8
9
|
function isRemoteUrl(filePath) {
|
|
9
10
|
return filePath.startsWith("http://") || filePath.startsWith("https://");
|
|
10
11
|
}
|
|
11
12
|
async function downloadToTempFile(url) {
|
|
12
|
-
|
|
13
|
+
logger.log(`[XY File Upload] Downloading remote file: ${url}`);
|
|
13
14
|
const response = await fetch(url);
|
|
14
15
|
if (!response.ok) {
|
|
15
16
|
throw new Error(`Failed to download remote file: HTTP ${response.status}`);
|
|
@@ -18,7 +19,7 @@ async function downloadToTempFile(url) {
|
|
|
18
19
|
const urlFileName = path.basename(new URL(url).pathname) || "download";
|
|
19
20
|
const tempPath = path.join(os.tmpdir(), `xy-upload-${Date.now()}-${urlFileName}`);
|
|
20
21
|
await fs.writeFile(tempPath, buffer);
|
|
21
|
-
|
|
22
|
+
logger.log(`[XY File Upload] Downloaded to temp file: ${tempPath}`);
|
|
22
23
|
return tempPath;
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
@@ -39,7 +40,7 @@ export class XYFileUploadService {
|
|
|
39
40
|
* Returns the objectId (as fileId) for use in A2A messages.
|
|
40
41
|
*/
|
|
41
42
|
async uploadFile(filePath, objectType = "TEMPORARY_MATERIAL_DOC") {
|
|
42
|
-
|
|
43
|
+
logger.log(`[XY File Upload] Starting file upload: ${filePath}`);
|
|
43
44
|
let localFilePath = filePath;
|
|
44
45
|
let isTempFile = false;
|
|
45
46
|
try {
|
|
@@ -54,7 +55,7 @@ export class XYFileUploadService {
|
|
|
54
55
|
const fileSha256 = calculateSHA256(fileBuffer);
|
|
55
56
|
const fileSize = fileBuffer.length;
|
|
56
57
|
// Phase 1: Prepare
|
|
57
|
-
|
|
58
|
+
logger.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
|
|
58
59
|
const prepareResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/prepare`, {
|
|
59
60
|
method: "POST",
|
|
60
61
|
headers: {
|
|
@@ -84,7 +85,7 @@ export class XYFileUploadService {
|
|
|
84
85
|
}
|
|
85
86
|
const { objectId, draftId, uploadInfos } = prepareData;
|
|
86
87
|
// Phase 2: Upload
|
|
87
|
-
|
|
88
|
+
logger.log(`[XY File Upload] Phase 2: Upload file data`);
|
|
88
89
|
const uploadInfo = uploadInfos[0]; // Single-part upload
|
|
89
90
|
const uploadResp = await fetch(uploadInfo.url, {
|
|
90
91
|
method: uploadInfo.method,
|
|
@@ -95,9 +96,9 @@ export class XYFileUploadService {
|
|
|
95
96
|
const uploadErrorText = await uploadResp.text();
|
|
96
97
|
throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
+
logger.log(`[XY File Upload] Upload complete`);
|
|
99
100
|
// Phase 3: Complete
|
|
100
|
-
|
|
101
|
+
logger.log(`[XY File Upload] Phase 3: Complete upload`);
|
|
101
102
|
const completeResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/complete`, {
|
|
102
103
|
method: "POST",
|
|
103
104
|
headers: {
|
|
@@ -115,11 +116,11 @@ export class XYFileUploadService {
|
|
|
115
116
|
throw new Error(`Complete failed: HTTP ${completeResp.status}`);
|
|
116
117
|
}
|
|
117
118
|
const completeData = await completeResp.json();
|
|
118
|
-
|
|
119
|
+
logger.log(`[XY File Upload] File upload successful: ${fileName} → objectId=${objectId}`);
|
|
119
120
|
return objectId;
|
|
120
121
|
}
|
|
121
122
|
catch (error) {
|
|
122
|
-
|
|
123
|
+
logger.error(`[XY File Upload] File upload failed for ${filePath}:`, error);
|
|
123
124
|
throw error;
|
|
124
125
|
}
|
|
125
126
|
finally {
|
|
@@ -150,7 +151,7 @@ export class XYFileUploadService {
|
|
|
150
151
|
const fileSha256 = calculateSHA256(fileBuffer);
|
|
151
152
|
const fileSize = fileBuffer.length;
|
|
152
153
|
// Phase 1: Prepare
|
|
153
|
-
|
|
154
|
+
logger.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
|
|
154
155
|
const prepareResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/prepare`, {
|
|
155
156
|
method: "POST",
|
|
156
157
|
headers: {
|
|
@@ -179,23 +180,23 @@ export class XYFileUploadService {
|
|
|
179
180
|
throw new Error(`Prepare failed: ${prepareData.desc}`);
|
|
180
181
|
}
|
|
181
182
|
const { objectId, draftId, uploadInfos } = prepareData;
|
|
182
|
-
|
|
183
|
+
logger.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
|
|
183
184
|
// Phase 2: Upload
|
|
184
|
-
|
|
185
|
+
logger.log(`[XY File Upload] Phase 2: Upload file data`);
|
|
185
186
|
const uploadInfo = uploadInfos[0]; // Single-part upload
|
|
186
187
|
const uploadResp = await fetch(uploadInfo.url, {
|
|
187
188
|
method: uploadInfo.method,
|
|
188
189
|
headers: uploadInfo.headers,
|
|
189
190
|
body: fileBuffer,
|
|
190
191
|
});
|
|
191
|
-
|
|
192
|
+
logger.log(`[XY File Upload] Upload response status: ${uploadResp.status}`);
|
|
192
193
|
if (!uploadResp.ok) {
|
|
193
194
|
const uploadErrorText = await uploadResp.text();
|
|
194
195
|
throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
|
|
195
196
|
}
|
|
196
|
-
|
|
197
|
+
logger.log(`[XY File Upload] Upload complete`);
|
|
197
198
|
// Phase 3: CompleteAndQuery - get file URL
|
|
198
|
-
|
|
199
|
+
logger.log(`[XY File Upload] Phase 3: CompleteAndQuery to get file URL`);
|
|
199
200
|
const completeResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/completeAndQuery`, {
|
|
200
201
|
method: "POST",
|
|
201
202
|
headers: {
|
|
@@ -218,11 +219,11 @@ export class XYFileUploadService {
|
|
|
218
219
|
if (!fileUrl) {
|
|
219
220
|
throw new Error("No file URL returned from completeAndQuery");
|
|
220
221
|
}
|
|
221
|
-
|
|
222
|
+
logger.log(`[XY File Upload] File upload successful`);
|
|
222
223
|
return fileUrl;
|
|
223
224
|
}
|
|
224
225
|
catch (error) {
|
|
225
|
-
|
|
226
|
+
logger.error(`[XY File Upload] File upload with URL retrieval failed for ${filePath}:`, error);
|
|
226
227
|
throw error;
|
|
227
228
|
}
|
|
228
229
|
finally {
|
|
@@ -249,7 +250,7 @@ export class XYFileUploadService {
|
|
|
249
250
|
});
|
|
250
251
|
}
|
|
251
252
|
catch (error) {
|
|
252
|
-
|
|
253
|
+
logger.error(`[XY File Upload] Failed to upload ${filePath}, skipping:`, error);
|
|
253
254
|
// Continue with other files
|
|
254
255
|
}
|
|
255
256
|
}
|
package/dist/src/formatter.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface SendA2AResponseParams {
|
|
|
17
17
|
}>;
|
|
18
18
|
errorCode?: number | string;
|
|
19
19
|
errorMessage?: string;
|
|
20
|
+
runtime?: any;
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
22
23
|
* Send an A2A artifact update response.
|
|
@@ -49,6 +50,7 @@ export interface SendStatusUpdateParams {
|
|
|
49
50
|
messageId: string;
|
|
50
51
|
text: string;
|
|
51
52
|
state: "submitted" | "working" | "input-required" | "completed" | "canceled" | "failed" | "unknown";
|
|
53
|
+
runtime?: any;
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
56
|
* Send an A2A task status update.
|