@ynhcj/xiaoyi-channel 0.0.127-beta → 0.0.129-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -6
- package/dist/index.js +71 -53
- package/dist/provider-discovery.d.ts +2 -0
- package/dist/provider-discovery.js +4 -0
- package/dist/src/bot.js +3 -0
- package/dist/src/formatter.d.ts +2 -0
- package/dist/src/formatter.js +30 -19
- package/dist/src/monitor.js +1 -0
- package/dist/src/provider.js +21 -17
- package/dist/src/reply-dispatcher.js +8 -0
- package/dist/src/tools/send-file-to-user-tool.js +8 -4
- package/dist/src/tools/xiaoyi-gui-tool.js +4 -1
- package/dist/src/utils/logger.js +20 -18
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
1
|
declare const _default: {
|
|
3
2
|
id: string;
|
|
4
3
|
name: string;
|
|
5
4
|
description: string;
|
|
6
|
-
configSchema: import("openclaw/plugin-sdk").
|
|
7
|
-
register: (
|
|
8
|
-
|
|
9
|
-
setChannelRuntime?: (runtime: import("openclaw/plugin-sdk").PluginRuntime) => void;
|
|
10
|
-
};
|
|
5
|
+
configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
|
|
6
|
+
register: NonNullable<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition["register"]>;
|
|
7
|
+
} & Pick<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
|
|
11
8
|
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
|
2
2
|
import { xiaoyiProvider } from "./src/provider.js";
|
|
3
3
|
import { xyPlugin } from "./src/channel.js";
|
|
4
4
|
import { callCsplApi } from "./src/cspl/call-api.js";
|
|
@@ -9,61 +9,79 @@ import { tryInjectSteer } from "./src/steer-injector.js";
|
|
|
9
9
|
import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
|
|
10
10
|
import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
|
|
11
11
|
import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
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
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
questionText.output[0].content = originText;
|
|
50
|
-
let finalJson = JSON.stringify(questionText);
|
|
51
|
-
if (finalJson.length > MAX_TEXT_LENGTH) {
|
|
52
|
-
const diff = finalJson.length - MAX_TEXT_LENGTH;
|
|
53
|
-
const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
|
|
54
|
-
questionText.output[0].content = trimmed;
|
|
55
|
-
finalJson = JSON.stringify(questionText);
|
|
56
|
-
}
|
|
57
|
-
const response = await callCsplApi(finalJson, api.config);
|
|
58
|
-
const result = parseSecurityResult(response);
|
|
59
|
-
console.log(`[SENTINEL HOOK] Security result: status=${result.status}`);
|
|
60
|
-
if (result.status === "REJECT") {
|
|
61
|
-
await tryInjectSteer(ctx.sessionKey, STEER_ABORT_MESSAGE);
|
|
62
|
-
}
|
|
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);
|
|
63
49
|
}
|
|
64
|
-
|
|
65
|
-
|
|
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);
|
|
66
55
|
}
|
|
67
|
-
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
api.logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export default definePluginEntry({
|
|
63
|
+
id: "xiaoyi-channel",
|
|
64
|
+
name: "Xiaoyi Channel",
|
|
65
|
+
description: "Xiaoyi channel plugin - Xiaoyi A2A protocol integration",
|
|
66
|
+
register(api) {
|
|
67
|
+
// Always register the provider so wrapStreamFn/prepareExtraParams work
|
|
68
|
+
// in ALL registration modes (not just "full").
|
|
69
|
+
api.registerProvider(xiaoyiProvider);
|
|
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
|
+
}
|
|
68
86
|
},
|
|
69
87
|
});
|
package/dist/src/bot.js
CHANGED
|
@@ -90,6 +90,7 @@ export async function handleXYMessage(params) {
|
|
|
90
90
|
text: pushDataItem.dataDetail,
|
|
91
91
|
append: false,
|
|
92
92
|
final: true,
|
|
93
|
+
runtime,
|
|
93
94
|
});
|
|
94
95
|
log(`[BOT] ✅ Trigger response sent successfully, exiting early`);
|
|
95
96
|
return; // 提前返回,不继续处理
|
|
@@ -162,6 +163,7 @@ export async function handleXYMessage(params) {
|
|
|
162
163
|
taskId: parsed.taskId,
|
|
163
164
|
messageId: parsed.messageId,
|
|
164
165
|
agentId: route.accountId,
|
|
166
|
+
deviceType,
|
|
165
167
|
});
|
|
166
168
|
// 🔑 发送初始状态更新(第二条消息也要发,用新taskId)
|
|
167
169
|
log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
|
|
@@ -172,6 +174,7 @@ export async function handleXYMessage(params) {
|
|
|
172
174
|
messageId: parsed.messageId,
|
|
173
175
|
text: isSecondMessage ? "新消息已接收,正在处理..." : "任务正在处理中,请稍候~",
|
|
174
176
|
state: "working",
|
|
177
|
+
runtime,
|
|
175
178
|
}).catch((err) => {
|
|
176
179
|
error(`Failed to send initial status update:`, err);
|
|
177
180
|
});
|
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.
|
package/dist/src/formatter.js
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
3
|
import { getXYWebSocketManager } from "./client.js";
|
|
4
4
|
import { logger } from "./utils/logger.js";
|
|
5
|
+
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
5
6
|
/**
|
|
6
7
|
* Send an A2A artifact update response.
|
|
7
8
|
*/
|
|
8
9
|
export async function sendA2AResponse(params) {
|
|
9
|
-
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
|
|
10
|
+
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage, runtime } = params;
|
|
11
|
+
const log = runtime?.log ?? console.log;
|
|
10
12
|
// Build artifact update event
|
|
11
13
|
const artifact = {
|
|
12
14
|
taskId,
|
|
@@ -45,7 +47,7 @@ export async function sendA2AResponse(params) {
|
|
|
45
47
|
code: errorCode,
|
|
46
48
|
message: errorMessage ?? "任务执行异常,请重试",
|
|
47
49
|
};
|
|
48
|
-
|
|
50
|
+
log(`[A2A_RESPONSE] ⚠️ Including error code: ${errorCode}`);
|
|
49
51
|
}
|
|
50
52
|
// Send via WebSocket
|
|
51
53
|
const wsManager = getXYWebSocketManager(config);
|
|
@@ -57,13 +59,13 @@ export async function sendA2AResponse(params) {
|
|
|
57
59
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
58
60
|
};
|
|
59
61
|
// 📋 Log complete response body
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
log(`[A2A_RESPONSE] 📤 Sending A2A artifact-update response: taskId: ${taskId}`);
|
|
63
|
+
log(`[A2A_RESPONSE] - append: ${append}`);
|
|
64
|
+
log(`[A2A_RESPONSE] - final: ${final}`);
|
|
65
|
+
log(`[A2A_RESPONSE] - text: ${text.length <= 10 ? text : text.slice(0, 5) + '***' + text.slice(-5)}`);
|
|
66
|
+
log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
|
|
65
67
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
66
|
-
|
|
68
|
+
log(`[A2A_RESPONSE] ✅ Message sent successfully`);
|
|
67
69
|
}
|
|
68
70
|
/**
|
|
69
71
|
* Send an A2A artifact-update with reasoningText part.
|
|
@@ -108,10 +110,15 @@ export async function sendReasoningTextUpdate(params) {
|
|
|
108
110
|
* Follows A2A protocol standard format with nested status object.
|
|
109
111
|
*/
|
|
110
112
|
export async function sendStatusUpdate(params) {
|
|
111
|
-
const { config, sessionId, taskId, messageId, text, state } = params;
|
|
113
|
+
const { config, sessionId, taskId, messageId, text, state, runtime } = params;
|
|
114
|
+
const log = runtime?.log ?? console.log;
|
|
115
|
+
// Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
|
|
116
|
+
// fall back to closure-captured values
|
|
117
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
118
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
112
119
|
// Build status update event following A2A protocol standard
|
|
113
120
|
const statusUpdate = {
|
|
114
|
-
taskId,
|
|
121
|
+
taskId: currentTaskId,
|
|
115
122
|
kind: "status-update",
|
|
116
123
|
final: false, // Status updates should not end the stream
|
|
117
124
|
status: {
|
|
@@ -130,7 +137,7 @@ export async function sendStatusUpdate(params) {
|
|
|
130
137
|
// Build JSON-RPC response
|
|
131
138
|
const jsonRpcResponse = {
|
|
132
139
|
jsonrpc: "2.0",
|
|
133
|
-
id:
|
|
140
|
+
id: currentMessageId,
|
|
134
141
|
result: statusUpdate,
|
|
135
142
|
};
|
|
136
143
|
// Send via WebSocket
|
|
@@ -139,13 +146,13 @@ export async function sendStatusUpdate(params) {
|
|
|
139
146
|
msgType: "agent_response",
|
|
140
147
|
agentId: config.agentId,
|
|
141
148
|
sessionId,
|
|
142
|
-
taskId,
|
|
149
|
+
taskId: currentTaskId,
|
|
143
150
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
144
151
|
};
|
|
145
152
|
// 📋 Log complete response body
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
log(`[A2A_STATUS] 📤 Sending A2A status-update:`);
|
|
154
|
+
log(`[A2A_STATUS] - taskId: ${currentTaskId}`);
|
|
155
|
+
log(`[A2A_STATUS] - text: "${text}"`);
|
|
149
156
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
150
157
|
}
|
|
151
158
|
/**
|
|
@@ -153,10 +160,14 @@ export async function sendStatusUpdate(params) {
|
|
|
153
160
|
*/
|
|
154
161
|
export async function sendCommand(params) {
|
|
155
162
|
const { config, sessionId, taskId, messageId, command } = params;
|
|
163
|
+
// Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
|
|
164
|
+
// fall back to closure-captured values
|
|
165
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
166
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
156
167
|
// Build artifact update with command as data
|
|
157
168
|
// Wrap command in commands array as per protocol requirement
|
|
158
169
|
const artifact = {
|
|
159
|
-
taskId,
|
|
170
|
+
taskId: currentTaskId,
|
|
160
171
|
kind: "artifact-update",
|
|
161
172
|
append: false,
|
|
162
173
|
lastChunk: true,
|
|
@@ -176,7 +187,7 @@ export async function sendCommand(params) {
|
|
|
176
187
|
// Build JSON-RPC response
|
|
177
188
|
const jsonRpcResponse = {
|
|
178
189
|
jsonrpc: "2.0",
|
|
179
|
-
id:
|
|
190
|
+
id: currentMessageId,
|
|
180
191
|
result: artifact,
|
|
181
192
|
};
|
|
182
193
|
// Send via WebSocket
|
|
@@ -185,11 +196,11 @@ export async function sendCommand(params) {
|
|
|
185
196
|
msgType: "agent_response",
|
|
186
197
|
agentId: config.agentId,
|
|
187
198
|
sessionId,
|
|
188
|
-
taskId,
|
|
199
|
+
taskId: currentTaskId,
|
|
189
200
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
190
201
|
};
|
|
191
202
|
// 📋 Log complete response body
|
|
192
|
-
logger.log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${
|
|
203
|
+
logger.log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${currentTaskId}`);
|
|
193
204
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
194
205
|
logger.log(`[A2A_COMMAND] ✅ Command sent successfully`);
|
|
195
206
|
}
|
package/dist/src/monitor.js
CHANGED
|
@@ -227,6 +227,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
227
227
|
text: notificationText,
|
|
228
228
|
append: false,
|
|
229
229
|
final: true,
|
|
230
|
+
runtime,
|
|
230
231
|
}).catch(err => {
|
|
231
232
|
error(`[MONITOR] Failed to send restart notification to session ${binding.sessionId}: ${String(err)}`);
|
|
232
233
|
}));
|
package/dist/src/provider.js
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
import { createHash } from "crypto";
|
|
11
11
|
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
12
12
|
import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
|
|
13
|
-
import { logger } from "./utils/logger.js";
|
|
14
13
|
// ── Retry config ──────────────────────────────────────────────
|
|
15
14
|
const RETRY_DELAYS_MS = [10_000, 20_000, 40_000, 60_000, 60_000];
|
|
16
15
|
const MAX_RETRY_ATTEMPTS = 5;
|
|
@@ -127,7 +126,7 @@ function createRetryingStream(createStream, cronJob) {
|
|
|
127
126
|
if (!hasContent && !isContent) {
|
|
128
127
|
// ── Buffer phase (no content yet) ──
|
|
129
128
|
if (event.type === "done") {
|
|
130
|
-
|
|
129
|
+
console.log(`[xiaoyiprovider] stream completed (no content), usage: input=${event.message?.usage?.input} output=${event.message?.usage?.output}`);
|
|
131
130
|
for (const b of buffer)
|
|
132
131
|
yield b;
|
|
133
132
|
resultResolve(event.message);
|
|
@@ -142,7 +141,7 @@ function createRetryingStream(createStream, cronJob) {
|
|
|
142
141
|
else {
|
|
143
142
|
// ── Streaming phase ──
|
|
144
143
|
if (!hasContent) {
|
|
145
|
-
|
|
144
|
+
console.log("[xiaoyiprovider] first content event received, switching to streaming mode");
|
|
146
145
|
hasContent = true;
|
|
147
146
|
for (const b of buffer)
|
|
148
147
|
yield b;
|
|
@@ -151,13 +150,13 @@ function createRetryingStream(createStream, cronJob) {
|
|
|
151
150
|
// The SDK calls result() when it sees done/error — if we yield first, the generator
|
|
152
151
|
// suspends and can never reach resolve, causing a permanent deadlock.
|
|
153
152
|
if (event.type === "done") {
|
|
154
|
-
|
|
153
|
+
console.log(`[xiaoyiprovider] stream completed, usage: input=${event.message?.usage?.input} output=${event.message?.usage?.output}`);
|
|
155
154
|
resultResolve(event.message);
|
|
156
155
|
yield event;
|
|
157
156
|
return;
|
|
158
157
|
}
|
|
159
158
|
if (event.type === "error") {
|
|
160
|
-
|
|
159
|
+
console.log(`[xiaoyiprovider] stream error after content: ${event.error?.errorMessage}`);
|
|
161
160
|
errorResult = event.error;
|
|
162
161
|
break; // break inner loop, proceed to retry decision
|
|
163
162
|
}
|
|
@@ -168,15 +167,15 @@ function createRetryingStream(createStream, cronJob) {
|
|
|
168
167
|
if (errorResult?.stopReason === "error" && isRetryableProviderError(errorResult.errorMessage)) {
|
|
169
168
|
if (attempt < MAX_RETRY_ATTEMPTS - 1) {
|
|
170
169
|
const delayMs = getRetryDelayMs(attempt + 1, cronJob);
|
|
171
|
-
|
|
170
|
+
console.log(`[xiaoyiprovider] retryable error (attempt ${attempt + 1}/${MAX_RETRY_ATTEMPTS}): ` +
|
|
172
171
|
`${errorResult.errorMessage} — retrying in ${delayMs}ms`);
|
|
173
172
|
await sleep(delayMs);
|
|
174
173
|
continue; // discard buffer, retry with a new stream
|
|
175
174
|
}
|
|
176
|
-
|
|
175
|
+
console.log(`[xiaoyiprovider] all ${MAX_RETRY_ATTEMPTS} retries exhausted, surfacing last error`);
|
|
177
176
|
}
|
|
178
177
|
else if (errorResult) {
|
|
179
|
-
|
|
178
|
+
console.log(`[xiaoyiprovider] non-retryable error: ${errorResult.errorMessage}`);
|
|
180
179
|
}
|
|
181
180
|
// Non-retryable or retries exhausted — yield buffered events.
|
|
182
181
|
// Resolve before yielding the terminal event to avoid the same deadlock.
|
|
@@ -196,7 +195,7 @@ function createRetryingStream(createStream, cronJob) {
|
|
|
196
195
|
return;
|
|
197
196
|
}
|
|
198
197
|
// Safety: final fallback attempt
|
|
199
|
-
|
|
198
|
+
console.log("[xiaoyiprovider] entering final fallback attempt");
|
|
200
199
|
const lastStream = await createStream();
|
|
201
200
|
for await (const event of lastStream) {
|
|
202
201
|
if (event.type === "done") {
|
|
@@ -439,6 +438,7 @@ export const xiaoyiProvider = {
|
|
|
439
438
|
* since the default agent timeout is 48 hours).
|
|
440
439
|
*/
|
|
441
440
|
wrapStreamFn: (ctx) => {
|
|
441
|
+
console.log("[xiaoyiprovider] wrapStreamFn CALLED — provider resolved by openclaw");
|
|
442
442
|
const underlying = ctx.streamFn;
|
|
443
443
|
if (!underlying)
|
|
444
444
|
return underlying;
|
|
@@ -485,13 +485,17 @@ export const xiaoyiProvider = {
|
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
487
|
// 记录输入
|
|
488
|
-
|
|
488
|
+
console.log(`[xiaoyiprovider] input messages count: ${context.messages?.length ?? 0}`);
|
|
489
489
|
if (context.systemPrompt) {
|
|
490
|
-
|
|
490
|
+
console.log(`[xiaoyiprovider] system prompt length: ${context.systemPrompt.length}`);
|
|
491
491
|
}
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
|
|
492
|
+
// Prefer deviceType from extraParams (set by prepareExtraParams).
|
|
493
|
+
// Fall back to getCurrentSessionContext() because OpenClaw caches
|
|
494
|
+
// resolvePreparedExtraParams by provider/modelId – the cache key does
|
|
495
|
+
// not include session-specific data, so deviceType may be missing
|
|
496
|
+
// from the cached extraParams even when a session is active.
|
|
497
|
+
const extraParamsDeviceType = ctx.extraParams?.[DEVICE_TYPE_KEY] || undefined;
|
|
498
|
+
const deviceType = extraParamsDeviceType ?? getCurrentSessionContext()?.deviceType;
|
|
495
499
|
// 在发送给模型前,优化 systemPrompt 结构
|
|
496
500
|
if (context.systemPrompt) {
|
|
497
501
|
let sp = context.systemPrompt;
|
|
@@ -518,11 +522,11 @@ export const xiaoyiProvider = {
|
|
|
518
522
|
sp = sp.replace('## Runtime', combined + '\n\n## Runtime');
|
|
519
523
|
}
|
|
520
524
|
}
|
|
521
|
-
|
|
525
|
+
console.log(`[xiaoyiprovider] system prompt optimized: ${beforeLen} -> ${sp.length}`);
|
|
522
526
|
context.systemPrompt = sp;
|
|
523
527
|
}
|
|
524
528
|
const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
|
|
525
|
-
|
|
529
|
+
console.log(`[selfEvolution] selfEvolution flag: ${selfEvolutionEnabled}`);
|
|
526
530
|
context.systemPrompt = applySelfEvolutionPrompt(context.systemPrompt, selfEvolutionEnabled);
|
|
527
531
|
// Append device context to systemPrompt (using pre-captured deviceType from prepareExtraParams)
|
|
528
532
|
if (deviceType) {
|
|
@@ -550,7 +554,7 @@ export const xiaoyiProvider = {
|
|
|
550
554
|
// ── Retry-capable streaming ──────────────────────────────
|
|
551
555
|
const cronJob = isCronTriggered(context.messages);
|
|
552
556
|
if (cronJob)
|
|
553
|
-
|
|
557
|
+
console.log("[xiaoyiprovider] detected cron-triggered request, using extended retry delays");
|
|
554
558
|
const makeStream = () => underlying(model, context, {
|
|
555
559
|
...options,
|
|
556
560
|
headers: {
|
|
@@ -95,6 +95,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
95
95
|
messageId: currentMessageId, // 🔑 动态messageId
|
|
96
96
|
text: "任务正在处理中,请稍候~",
|
|
97
97
|
state: "working",
|
|
98
|
+
runtime,
|
|
98
99
|
}).catch((err) => {
|
|
99
100
|
error(`Failed to send status update:`, err);
|
|
100
101
|
});
|
|
@@ -161,6 +162,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
161
162
|
messageId: currentMessageId,
|
|
162
163
|
text: "处理失败,请稍后重试",
|
|
163
164
|
state: "failed",
|
|
165
|
+
runtime,
|
|
164
166
|
});
|
|
165
167
|
}
|
|
166
168
|
catch (statusError) {
|
|
@@ -196,6 +198,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
196
198
|
messageId: currentMessageId,
|
|
197
199
|
text: "任务处理已完成~",
|
|
198
200
|
state: "completed",
|
|
201
|
+
runtime,
|
|
199
202
|
});
|
|
200
203
|
log(`[ON_IDLE] ✅ Sent completion status update`);
|
|
201
204
|
// 🔑 使用动态taskId发送最终响应
|
|
@@ -207,6 +210,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
207
210
|
text: accumulatedText,
|
|
208
211
|
append: false,
|
|
209
212
|
final: true,
|
|
213
|
+
runtime,
|
|
210
214
|
});
|
|
211
215
|
finalSent = true;
|
|
212
216
|
log(`[ON_IDLE] ✅ Sent final response with taskId=${currentTaskId}`);
|
|
@@ -226,6 +230,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
226
230
|
messageId: currentMessageId,
|
|
227
231
|
text: "任务处理中断了~",
|
|
228
232
|
state: "failed",
|
|
233
|
+
runtime,
|
|
229
234
|
});
|
|
230
235
|
log(`[ON_IDLE] ✅ Sent failure status update`);
|
|
231
236
|
await sendA2AResponse({
|
|
@@ -238,6 +243,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
238
243
|
final: true,
|
|
239
244
|
errorCode: 99921111,
|
|
240
245
|
errorMessage: "任务执行异常,请重试",
|
|
246
|
+
runtime,
|
|
241
247
|
});
|
|
242
248
|
finalSent = true;
|
|
243
249
|
log(`[ON_IDLE] ✅ Sent error response with code: 99921111`);
|
|
@@ -282,6 +288,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
282
288
|
messageId: currentMessageId,
|
|
283
289
|
text: `正在使用工具: ${toolName}...`,
|
|
284
290
|
state: "working",
|
|
291
|
+
runtime,
|
|
285
292
|
});
|
|
286
293
|
log(`[TOOL START] ✅ Sent status update for tool start: ${toolName}`);
|
|
287
294
|
}
|
|
@@ -310,6 +317,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
310
317
|
messageId: currentMessageId,
|
|
311
318
|
text: resultText,
|
|
312
319
|
state: "working",
|
|
320
|
+
runtime,
|
|
313
321
|
});
|
|
314
322
|
log(`[TOOL RESULT] ✅ Sent tool result as status update`);
|
|
315
323
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { XYFileUploadService } from "../file-upload.js";
|
|
3
3
|
import { logger } from "../utils/logger.js";
|
|
4
|
+
import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
|
|
4
5
|
import fetch from "node-fetch";
|
|
5
6
|
import fs from "fs/promises";
|
|
6
7
|
import path from "path";
|
|
@@ -122,6 +123,9 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
122
123
|
},
|
|
123
124
|
},
|
|
124
125
|
async execute(toolCallId, params) {
|
|
126
|
+
// Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt)
|
|
127
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
128
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
125
129
|
// Set timeout for the entire operation (2 minutes)
|
|
126
130
|
const TOOL_TIMEOUT = 120000; // 2 minutes in milliseconds
|
|
127
131
|
let timeoutHandle = null;
|
|
@@ -211,17 +215,17 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
211
215
|
msgType: "agent_response",
|
|
212
216
|
agentId: config.agentId,
|
|
213
217
|
sessionId: sessionId,
|
|
214
|
-
taskId:
|
|
218
|
+
taskId: currentTaskId,
|
|
215
219
|
msgDetail: JSON.stringify({
|
|
216
220
|
jsonrpc: "2.0",
|
|
217
|
-
id:
|
|
221
|
+
id: currentMessageId,
|
|
218
222
|
result: {
|
|
219
223
|
kind: "artifact-update",
|
|
220
224
|
append: true,
|
|
221
225
|
lastChunk: false,
|
|
222
226
|
final: false,
|
|
223
227
|
artifact: {
|
|
224
|
-
artifactId:
|
|
228
|
+
artifactId: currentTaskId,
|
|
225
229
|
parts: [
|
|
226
230
|
{
|
|
227
231
|
kind: "file",
|
|
@@ -237,7 +241,7 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
237
241
|
error: { code: 0 },
|
|
238
242
|
}),
|
|
239
243
|
};
|
|
240
|
-
logger.log(`[SEND-FILE-TO-USER] 🚀 EXEC sending: sessionId=${sessionId} taskId=${
|
|
244
|
+
logger.log(`[SEND-FILE-TO-USER] 🚀 EXEC sending: sessionId=${sessionId} taskId=${currentTaskId} fileName=${fileName}`);
|
|
241
245
|
// Send WebSocket message
|
|
242
246
|
await wsManager.sendMessage(sessionId, agentResponse);
|
|
243
247
|
logger.log(`send ${fileName} file to user success`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// XiaoYi GUI tool implementation - simulates phone screen interactions
|
|
2
2
|
import { getXYWebSocketManager } from "../client.js";
|
|
3
3
|
import { sendCommand } from "../formatter.js";
|
|
4
|
+
import { getCurrentTaskId } from "../task-manager.js";
|
|
4
5
|
/**
|
|
5
6
|
* XiaoYi GUI tool - executes phone app interactions through GUI agent.
|
|
6
7
|
* Simulates user interactions on phone screen (click, swipe, input, navigation, etc.)
|
|
@@ -38,6 +39,8 @@ export function createXiaoyiGuiTool(ctx) {
|
|
|
38
39
|
required: ["query"],
|
|
39
40
|
},
|
|
40
41
|
async execute(toolCallId, params) {
|
|
42
|
+
// Dynamic lookup: use latest taskId from task-manager (handles steer/interrupt)
|
|
43
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
41
44
|
// Validate parameters
|
|
42
45
|
if (!params.query || typeof params.query !== "string") {
|
|
43
46
|
throw new Error("Missing or invalid required parameter: query must be a non-empty string");
|
|
@@ -53,7 +56,7 @@ export function createXiaoyiGuiTool(ctx) {
|
|
|
53
56
|
payload: {
|
|
54
57
|
query: params.query,
|
|
55
58
|
sessionId: sessionId,
|
|
56
|
-
interactionId:
|
|
59
|
+
interactionId: currentTaskId, // taskId corresponds to interactionId; use dynamic lookup for steer safety
|
|
57
60
|
},
|
|
58
61
|
};
|
|
59
62
|
// Send command and wait for response (5 minute timeout)
|
package/dist/src/utils/logger.js
CHANGED
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
// Logging utilities for XY channel
|
|
2
2
|
import { getXYRuntime } from "../runtime.js";
|
|
3
|
-
|
|
4
|
-
* Log a message using the OpenClaw runtime logger.
|
|
5
|
-
*/
|
|
6
|
-
function logMessage(level, message, ...args) {
|
|
3
|
+
function getRuntime() {
|
|
7
4
|
try {
|
|
8
|
-
|
|
9
|
-
const logFn = runtime[level];
|
|
10
|
-
if (logFn) {
|
|
11
|
-
const formattedMessage = `[XY] ${message}`;
|
|
12
|
-
logFn(formattedMessage, ...args);
|
|
13
|
-
}
|
|
5
|
+
return getXYRuntime();
|
|
14
6
|
}
|
|
15
|
-
catch
|
|
16
|
-
|
|
17
|
-
console[level](`[XY] ${message}`, ...args);
|
|
7
|
+
catch {
|
|
8
|
+
return undefined;
|
|
18
9
|
}
|
|
19
10
|
}
|
|
11
|
+
function getLog() {
|
|
12
|
+
const runtime = getRuntime();
|
|
13
|
+
return runtime?.log ?? console.log;
|
|
14
|
+
}
|
|
15
|
+
function getWarn() {
|
|
16
|
+
const runtime = getRuntime();
|
|
17
|
+
return runtime?.warn ?? console.warn;
|
|
18
|
+
}
|
|
19
|
+
function getError() {
|
|
20
|
+
const runtime = getRuntime();
|
|
21
|
+
return runtime?.error ?? console.error;
|
|
22
|
+
}
|
|
20
23
|
export const logger = {
|
|
21
24
|
log(message, ...args) {
|
|
22
|
-
|
|
25
|
+
getLog()(message, ...args);
|
|
23
26
|
},
|
|
24
27
|
warn(message, ...args) {
|
|
25
|
-
|
|
28
|
+
getWarn()(message, ...args);
|
|
26
29
|
},
|
|
27
30
|
error(message, ...args) {
|
|
28
|
-
|
|
31
|
+
getError()(message, ...args);
|
|
29
32
|
},
|
|
30
33
|
debug(message, ...args) {
|
|
31
|
-
|
|
32
|
-
logMessage("log", `[DEBUG] ${message}`, ...args);
|
|
34
|
+
getLog()(`[DEBUG] ${message}`, ...args);
|
|
33
35
|
},
|
|
34
36
|
};
|
package/openclaw.plugin.json
CHANGED