@ynhcj/xiaoyi-channel 0.0.117-next → 0.0.118-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.js +6 -40
- package/dist/src/bot.js +30 -39
- package/dist/src/cspl/call-api.d.ts +6 -0
- package/dist/src/cspl/call-api.js +48 -0
- package/dist/src/cspl/config.d.ts +11 -1
- package/dist/src/cspl/config.js +30 -0
- package/dist/src/cspl/middleware.d.ts +8 -0
- package/dist/src/cspl/middleware.js +87 -0
- package/dist/src/login-token-handler.js +8 -4
- package/dist/src/reply-dispatcher.d.ts +3 -1
- package/dist/src/reply-dispatcher.js +20 -22
- package/dist/src/task-manager.d.ts +4 -27
- package/dist/src/task-manager.js +13 -78
- package/dist/src/tools/login-token-tool.js +13 -2
- package/openclaw.plugin.json +3 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
|
2
2
|
import { xiaoyiProvider } from "./src/provider.js";
|
|
3
3
|
import { xyPlugin } from "./src/channel.js";
|
|
4
|
-
import {
|
|
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
|
+
import { createCsplMiddleware } from "./src/cspl/middleware.js";
|
|
7
5
|
import { setXYRuntime } from "./src/runtime.js";
|
|
8
|
-
import { logger } from "./src/utils/logger.js";
|
|
9
|
-
import { tryInjectSteer } from "./src/steer-injector.js";
|
|
10
6
|
import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
|
|
11
7
|
import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
|
|
12
8
|
import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
|
|
@@ -23,41 +19,11 @@ function registerFullHooks(api) {
|
|
|
23
19
|
const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
|
|
24
20
|
api.on("before_prompt_build", beforePromptBuildHandler);
|
|
25
21
|
registerSelfEvolutionToolResultNudge(api);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const resultText = extractResultText(event, event.toolName);
|
|
33
|
-
const resultLength = resultText.length;
|
|
34
|
-
if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const questionText = {
|
|
38
|
-
subSceneID: "TOOL_OUTPUT",
|
|
39
|
-
tool: event.toolName,
|
|
40
|
-
output: [{ content: "" }],
|
|
41
|
-
};
|
|
42
|
-
const originText = processText(resultText);
|
|
43
|
-
questionText.output[0].content = originText;
|
|
44
|
-
let finalJson = JSON.stringify(questionText);
|
|
45
|
-
if (finalJson.length > MAX_TEXT_LENGTH) {
|
|
46
|
-
const diff = finalJson.length - MAX_TEXT_LENGTH;
|
|
47
|
-
const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
|
|
48
|
-
questionText.output[0].content = trimmed;
|
|
49
|
-
finalJson = JSON.stringify(questionText);
|
|
50
|
-
}
|
|
51
|
-
const response = await callCsplApi(finalJson, api.config);
|
|
52
|
-
const result = parseSecurityResult(response);
|
|
53
|
-
logger.log(`[SENTINEL HOOK] Security result: status=${result.status}`);
|
|
54
|
-
if (result.status === "REJECT") {
|
|
55
|
-
await tryInjectSteer(ctx.sessionKey, STEER_ABORT_MESSAGE);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
|
|
60
|
-
}
|
|
22
|
+
// CSPL security scanning via AgentToolResultMiddleware.
|
|
23
|
+
// Intercepts tool results BEFORE they reach the LLM, replacing them
|
|
24
|
+
// with a security rejection message when CSPL returns REJECT.
|
|
25
|
+
api.registerAgentToolResultMiddleware(createCsplMiddleware(), {
|
|
26
|
+
runtimes: ["pi"],
|
|
61
27
|
});
|
|
62
28
|
}
|
|
63
29
|
export default definePluginEntry({
|
package/dist/src/bot.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getXYRuntime } from "./runtime.js";
|
|
2
|
-
import { setCachedContext } from "./steer-injector.js";
|
|
3
2
|
import { createXYReplyDispatcher } from "./reply-dispatcher.js";
|
|
4
3
|
import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId, extractDeviceType, extractTriggerData } from "./parser.js";
|
|
5
4
|
import { downloadFilesFromParts } from "./file-download.js";
|
|
@@ -13,7 +12,7 @@ import { getPushDataById } from "./utils/pushdata-manager.js";
|
|
|
13
12
|
import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
|
|
14
13
|
import { saveRuntimeInfo } from "./utils/runtime-manager.js";
|
|
15
14
|
import { toolCallNudgeManager } from "./utils/tool-call-nudge-manager.js";
|
|
16
|
-
import { registerTaskId, decrementTaskIdRef,
|
|
15
|
+
import { registerTaskId, decrementTaskIdRef, hasActiveTask, } from "./task-manager.js";
|
|
17
16
|
import { logger } from "./utils/logger.js";
|
|
18
17
|
/**
|
|
19
18
|
* Handle an incoming A2A message.
|
|
@@ -22,8 +21,6 @@ import { logger } from "./utils/logger.js";
|
|
|
22
21
|
*/
|
|
23
22
|
export async function handleXYMessage(params) {
|
|
24
23
|
const { cfg, runtime, message, accountId, webSocketSessionId } = params;
|
|
25
|
-
// 每次收到消息时更新缓存,供 steer 注入使用
|
|
26
|
-
setCachedContext(cfg, runtime, accountId);
|
|
27
24
|
// Get runtime (already validated in monitor.ts, but get reference for use)
|
|
28
25
|
const core = getXYRuntime();
|
|
29
26
|
try {
|
|
@@ -99,22 +96,14 @@ export async function handleXYMessage(params) {
|
|
|
99
96
|
}
|
|
100
97
|
}
|
|
101
98
|
// ========================================
|
|
102
|
-
// 🔑
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
logger.log(`[BOT] 🔄 STEER MODE - Second message detected (will be follower)`);
|
|
99
|
+
// 🔑 注册taskId(检测是否是已有活跃任务的 session)
|
|
100
|
+
const isUpdate = hasActiveTask(parsed.sessionId);
|
|
101
|
+
if (isUpdate) {
|
|
102
|
+
logger.log(`[BOT] 🔄 STEER MODE - Second message detected (core will handle steer)`);
|
|
107
103
|
logger.log(`[BOT] - Session: ${parsed.sessionId}`);
|
|
108
|
-
logger.log(`[BOT] - New taskId: ${parsed.taskId}
|
|
109
|
-
}
|
|
110
|
-
// 🔑 注册taskId(第二条消息会覆盖第一条的taskId)
|
|
111
|
-
const { isUpdate, refCount } = registerTaskId(parsed.sessionId, parsed.taskId, parsed.messageId, { incrementRef: true } // 增加引用计数
|
|
112
|
-
);
|
|
113
|
-
// 🔑 如果是第一条消息,锁定taskId防止被过早清理
|
|
114
|
-
if (!isUpdate) {
|
|
115
|
-
lockTaskId(parsed.sessionId);
|
|
116
|
-
logger.log(`[BOT] 🔒 Locked taskId for first message`);
|
|
104
|
+
logger.log(`[BOT] - New taskId: ${parsed.taskId}`);
|
|
117
105
|
}
|
|
106
|
+
registerTaskId(parsed.sessionId, parsed.taskId, parsed.messageId);
|
|
118
107
|
// Extract and update push_id if present
|
|
119
108
|
const pushId = extractPushId(parsed.parts);
|
|
120
109
|
if (pushId) {
|
|
@@ -163,14 +152,14 @@ export async function handleXYMessage(params) {
|
|
|
163
152
|
agentId: route.accountId,
|
|
164
153
|
deviceType,
|
|
165
154
|
});
|
|
166
|
-
// 🔑
|
|
155
|
+
// 🔑 发送初始状态更新
|
|
167
156
|
logger.log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
|
|
168
157
|
void sendStatusUpdate({
|
|
169
158
|
config,
|
|
170
159
|
sessionId: parsed.sessionId,
|
|
171
160
|
taskId: parsed.taskId,
|
|
172
161
|
messageId: parsed.messageId,
|
|
173
|
-
text:
|
|
162
|
+
text: "任务正在处理中,请稍候~",
|
|
174
163
|
state: "working",
|
|
175
164
|
}).catch((err) => {
|
|
176
165
|
logger.error(`Failed to send initial status update:`, err);
|
|
@@ -242,7 +231,10 @@ export async function handleXYMessage(params) {
|
|
|
242
231
|
ReplyToBody: undefined, // A2A protocol doesn't support reply/quote
|
|
243
232
|
...mediaPayload,
|
|
244
233
|
});
|
|
245
|
-
// 🔑
|
|
234
|
+
// 🔑 Dynamic steer state: set to true when dispatchReplyFromConfig
|
|
235
|
+
// returns undefined (meaning the core steered the message into the active Pi run).
|
|
236
|
+
const steerState = { steered: false };
|
|
237
|
+
// 🔑 创建dispatcher
|
|
246
238
|
logger.log(`[BOT-DISPATCHER] 🎯 Creating reply dispatcher`);
|
|
247
239
|
logger.log(`[BOT-DISPATCHER] - taskId: ${parsed.taskId}`);
|
|
248
240
|
const { dispatcher, replyOptions, markDispatchIdle, startStatusInterval } = createXYReplyDispatcher({
|
|
@@ -252,14 +244,9 @@ export async function handleXYMessage(params) {
|
|
|
252
244
|
taskId: parsed.taskId,
|
|
253
245
|
messageId: parsed.messageId,
|
|
254
246
|
accountId: route.accountId,
|
|
255
|
-
|
|
247
|
+
steerState,
|
|
256
248
|
});
|
|
257
|
-
|
|
258
|
-
// 第二条消息会很快返回,不需要定时器
|
|
259
|
-
if (!isSecondMessage) {
|
|
260
|
-
startStatusInterval();
|
|
261
|
-
logger.log(`[BOT-DISPATCHER] ✅ Status interval started for first message`);
|
|
262
|
-
}
|
|
249
|
+
startStatusInterval();
|
|
263
250
|
// Build session context for AsyncLocalStorage
|
|
264
251
|
const sessionContext = {
|
|
265
252
|
config,
|
|
@@ -274,15 +261,13 @@ export async function handleXYMessage(params) {
|
|
|
274
261
|
dispatcher,
|
|
275
262
|
onSettled: () => {
|
|
276
263
|
logger.log(`[BOT] 🏁 onSettled called for session: ${route.sessionKey}`);
|
|
277
|
-
logger.log(`[BOT] -
|
|
278
|
-
// 🔑
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
unlockTaskId(parsed.sessionId);
|
|
283
|
-
logger.log(`[BOT] 🔓 Unlocked taskId (first message completed)`);
|
|
264
|
+
logger.log(`[BOT] - steered: ${steerState.steered}`);
|
|
265
|
+
// 🔑 When steered, skip heavy cleanup — the first message's dispatcher is still running
|
|
266
|
+
if (steerState.steered) {
|
|
267
|
+
logger.log(`[BOT] ✅ Steered dispatch settled (skipping cleanup)`);
|
|
268
|
+
return;
|
|
284
269
|
}
|
|
285
|
-
|
|
270
|
+
decrementTaskIdRef(parsed.sessionId);
|
|
286
271
|
unregisterSession(route.sessionKey);
|
|
287
272
|
logger.log(`[BOT] ✅ Cleanup completed`);
|
|
288
273
|
},
|
|
@@ -306,8 +291,15 @@ export async function handleXYMessage(params) {
|
|
|
306
291
|
dispatcher,
|
|
307
292
|
replyOptions,
|
|
308
293
|
});
|
|
309
|
-
|
|
310
|
-
|
|
294
|
+
// 🔑 Core returned undefined = message was steered into the active Pi run
|
|
295
|
+
if (result === undefined) {
|
|
296
|
+
steerState.steered = true;
|
|
297
|
+
logger.log(`[BOT-DISPATCH] ✅ Message steered into active Pi run`);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
logger.log(`[BOT-DISPATCH] ✅ dispatchReplyFromConfig returned`);
|
|
301
|
+
logger.log(`[BOT-DISPATCH] - result: ${JSON.stringify(result)}`);
|
|
302
|
+
}
|
|
311
303
|
return result;
|
|
312
304
|
}
|
|
313
305
|
catch (dispatchErr) {
|
|
@@ -339,7 +331,6 @@ export async function handleXYMessage(params) {
|
|
|
339
331
|
logger.log(`[BOT] 🧹 Cleaning up after error: ${sessionId}`);
|
|
340
332
|
// 清理 taskId
|
|
341
333
|
decrementTaskIdRef(sessionId);
|
|
342
|
-
unlockTaskId(sessionId);
|
|
343
334
|
// 清理 session
|
|
344
335
|
const core = getXYRuntime();
|
|
345
336
|
const route = core.channel.routing.resolveAgentRoute({
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import { type CsplConfig } from "./config.js";
|
|
2
3
|
import type { ApiResponse } from "./constants.js";
|
|
3
4
|
export declare function callCsplApi(questionText: string, cfg: ClawdbotConfig): Promise<ApiResponse>;
|
|
5
|
+
/**
|
|
6
|
+
* Call CSPL API with a pre-resolved CsplConfig.
|
|
7
|
+
* Used by AgentToolResultMiddleware which doesn't have ClawdbotConfig.
|
|
8
|
+
*/
|
|
9
|
+
export declare function callCsplApiWithConfig(questionText: string, config: CsplConfig): Promise<ApiResponse>;
|
|
@@ -88,3 +88,51 @@ export async function callCsplApi(questionText, cfg) {
|
|
|
88
88
|
req.end();
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Call CSPL API with a pre-resolved CsplConfig.
|
|
93
|
+
* Used by AgentToolResultMiddleware which doesn't have ClawdbotConfig.
|
|
94
|
+
*/
|
|
95
|
+
export async function callCsplApiWithConfig(questionText, config) {
|
|
96
|
+
const headers = buildHeaders(config);
|
|
97
|
+
const payload = {
|
|
98
|
+
questionText,
|
|
99
|
+
textSource: config.textSource,
|
|
100
|
+
action: config.action,
|
|
101
|
+
extra: JSON.stringify({ userId: config.uid }),
|
|
102
|
+
};
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const options = buildRequestOptions(config.api.url, headers, config.api.timeout);
|
|
105
|
+
const req = https.request(options, (res) => {
|
|
106
|
+
if (res.statusCode && res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
107
|
+
reject(new Error(`[SENTINEL HOOK] HTTP error: ${res.statusCode}`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
let data = "";
|
|
111
|
+
res.on("data", (chunk) => {
|
|
112
|
+
data += chunk;
|
|
113
|
+
});
|
|
114
|
+
res.on("end", () => {
|
|
115
|
+
try {
|
|
116
|
+
const result = parseResponse(data);
|
|
117
|
+
logger.log(`[SENTINEL HOOK] ✅ 请求成功`);
|
|
118
|
+
resolve(result);
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
logger.error(`[SENTINEL HOOK] ❌ 请求失败: ${e instanceof Error ? e.message : String(e)}`);
|
|
122
|
+
reject(e);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
req.on("error", (error) => {
|
|
127
|
+
logger.error(`[SENTINEL HOOK] ❌ 请求错误: ${error instanceof Error ? error.message : String(error)}`);
|
|
128
|
+
reject(error);
|
|
129
|
+
});
|
|
130
|
+
req.on("timeout", () => {
|
|
131
|
+
logger.error(`[SENTINEL HOOK] ⏰ 请求超时 (${config.api.timeout}ms)`);
|
|
132
|
+
req.destroy();
|
|
133
|
+
reject(new Error("[SENTINEL HOOK] Request timeout"));
|
|
134
|
+
});
|
|
135
|
+
req.write(JSON.stringify(payload));
|
|
136
|
+
req.end();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { XYChannelConfig } from "../types.js";
|
|
2
3
|
export interface ApiConfig {
|
|
3
4
|
url: string;
|
|
4
5
|
timeout: number;
|
|
@@ -15,5 +16,14 @@ export interface CsplConfig {
|
|
|
15
16
|
/**
|
|
16
17
|
* 构建 CSPL 配置。uid 和 apiKey 复用 XYChannelConfig,避免重复配置。
|
|
17
18
|
* serviceUrl 从 .xiaoyienv 文件读取,skillId 写死在常量中。
|
|
19
|
+
*
|
|
20
|
+
* Accepts either ClawdbotConfig (legacy after_tool_call path) or
|
|
21
|
+
* XYChannelConfig (AgentToolResultMiddleware path). Config is cached
|
|
22
|
+
* after the first successful call so subsequent calls can omit the arg.
|
|
18
23
|
*/
|
|
19
|
-
export declare function getCsplConfig(cfg
|
|
24
|
+
export declare function getCsplConfig(cfg?: ClawdbotConfig): CsplConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Initialize CSPL config from an already-resolved XYChannelConfig.
|
|
27
|
+
* Used by AgentToolResultMiddleware which has session context but not ClawdbotConfig.
|
|
28
|
+
*/
|
|
29
|
+
export declare function initCsplConfigFromXYConfig(xyConfig: XYChannelConfig): CsplConfig;
|
package/dist/src/cspl/config.js
CHANGED
|
@@ -27,10 +27,17 @@ function readServiceUrl() {
|
|
|
27
27
|
/**
|
|
28
28
|
* 构建 CSPL 配置。uid 和 apiKey 复用 XYChannelConfig,避免重复配置。
|
|
29
29
|
* serviceUrl 从 .xiaoyienv 文件读取,skillId 写死在常量中。
|
|
30
|
+
*
|
|
31
|
+
* Accepts either ClawdbotConfig (legacy after_tool_call path) or
|
|
32
|
+
* XYChannelConfig (AgentToolResultMiddleware path). Config is cached
|
|
33
|
+
* after the first successful call so subsequent calls can omit the arg.
|
|
30
34
|
*/
|
|
31
35
|
export function getCsplConfig(cfg) {
|
|
32
36
|
if (cachedConfig)
|
|
33
37
|
return cachedConfig;
|
|
38
|
+
if (!cfg) {
|
|
39
|
+
throw new Error("[SENTINEL HOOK] CSPL config not initialized: pass ClawdbotConfig on first call");
|
|
40
|
+
}
|
|
34
41
|
const xyConfig = resolveXYConfig(cfg);
|
|
35
42
|
const serviceUrl = readServiceUrl();
|
|
36
43
|
cachedConfig = {
|
|
@@ -48,3 +55,26 @@ export function getCsplConfig(cfg) {
|
|
|
48
55
|
logger.log("[SENTINEL HOOK] Config loaded (uid/apiKey from XYChannelConfig)");
|
|
49
56
|
return cachedConfig;
|
|
50
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Initialize CSPL config from an already-resolved XYChannelConfig.
|
|
60
|
+
* Used by AgentToolResultMiddleware which has session context but not ClawdbotConfig.
|
|
61
|
+
*/
|
|
62
|
+
export function initCsplConfigFromXYConfig(xyConfig) {
|
|
63
|
+
if (cachedConfig)
|
|
64
|
+
return cachedConfig;
|
|
65
|
+
const serviceUrl = readServiceUrl();
|
|
66
|
+
cachedConfig = {
|
|
67
|
+
api: {
|
|
68
|
+
url: `${serviceUrl}${API_URL_SUFFIX}`,
|
|
69
|
+
timeout: CSPL_STATIC_CONFIG.api.timeout,
|
|
70
|
+
},
|
|
71
|
+
uid: xyConfig.uid,
|
|
72
|
+
apiKey: xyConfig.apiKey,
|
|
73
|
+
skillId: CSPL_STATIC_CONFIG.skillId,
|
|
74
|
+
requestFrom: CSPL_STATIC_CONFIG.requestFrom,
|
|
75
|
+
textSource: CSPL_STATIC_CONFIG.textSource,
|
|
76
|
+
action: CSPL_STATIC_CONFIG.action,
|
|
77
|
+
};
|
|
78
|
+
logger.log("[SENTINEL HOOK] Config loaded via XYChannelConfig");
|
|
79
|
+
return cachedConfig;
|
|
80
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AgentToolResultMiddleware } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Create the CSPL AgentToolResultMiddleware.
|
|
4
|
+
*
|
|
5
|
+
* Gets XYChannelConfig from session context (via sessionKey) to initialize
|
|
6
|
+
* the CSPL API config on first call, then caches it for subsequent calls.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createCsplMiddleware(): AgentToolResultMiddleware;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// CSPL AgentToolResultMiddleware
|
|
2
|
+
// Replaces the after_tool_call hook with a middleware that intercepts tool results
|
|
3
|
+
// BEFORE they reach the LLM, enabling true security interruption.
|
|
4
|
+
import { callCsplApiWithConfig } from "./call-api.js";
|
|
5
|
+
import { getCsplConfig, initCsplConfigFromXYConfig } from "./config.js";
|
|
6
|
+
import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH, STEER_ABORT_MESSAGE, } from "./constants.js";
|
|
7
|
+
import { parseSecurityResult, processText, validateAndTruncateText, } from "./utils.js";
|
|
8
|
+
import { getSessionContext } from "../tools/session-manager.js";
|
|
9
|
+
import { logger } from "../utils/logger.js";
|
|
10
|
+
/**
|
|
11
|
+
* Extract text content from an OpenClawAgentToolResult.
|
|
12
|
+
*/
|
|
13
|
+
function extractMiddlewareResultText(event) {
|
|
14
|
+
const result = event.result;
|
|
15
|
+
if (!result?.content || !Array.isArray(result.content)) {
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
const texts = [];
|
|
19
|
+
// Special handling for web_fetch: text is in details.text
|
|
20
|
+
if (event.toolName === "web_fetch" && result.details?.text) {
|
|
21
|
+
texts.push(String(result.details.text));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
for (const item of result.content) {
|
|
25
|
+
if (item?.type === "text" && typeof item.text === "string") {
|
|
26
|
+
texts.push(item.text);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return texts.length > 0 ? texts.join("; ") : "";
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create the CSPL AgentToolResultMiddleware.
|
|
34
|
+
*
|
|
35
|
+
* Gets XYChannelConfig from session context (via sessionKey) to initialize
|
|
36
|
+
* the CSPL API config on first call, then caches it for subsequent calls.
|
|
37
|
+
*/
|
|
38
|
+
export function createCsplMiddleware() {
|
|
39
|
+
return async (event, ctx) => {
|
|
40
|
+
if (!ALLOWED_TOOLS.includes(event.toolName)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const resultText = extractMiddlewareResultText(event);
|
|
45
|
+
const resultLength = resultText.length;
|
|
46
|
+
if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Build CSPL request payload
|
|
50
|
+
const questionText = {
|
|
51
|
+
subSceneID: "TOOL_OUTPUT",
|
|
52
|
+
tool: event.toolName,
|
|
53
|
+
output: [{ content: "" }],
|
|
54
|
+
};
|
|
55
|
+
const originText = processText(resultText);
|
|
56
|
+
questionText.output[0].content = originText;
|
|
57
|
+
let finalJson = JSON.stringify(questionText);
|
|
58
|
+
if (finalJson.length > MAX_TEXT_LENGTH) {
|
|
59
|
+
const diff = finalJson.length - MAX_TEXT_LENGTH;
|
|
60
|
+
const { text: trimmed } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff);
|
|
61
|
+
questionText.output[0].content = trimmed;
|
|
62
|
+
finalJson = JSON.stringify(questionText);
|
|
63
|
+
}
|
|
64
|
+
// Get CSPL config (cached after first call)
|
|
65
|
+
// Try session context first (XYChannelConfig), then fall back to cached config
|
|
66
|
+
const sessionCtx = getSessionContext(ctx.sessionKey ?? "");
|
|
67
|
+
const csplConfig = sessionCtx
|
|
68
|
+
? initCsplConfigFromXYConfig(sessionCtx.config)
|
|
69
|
+
: getCsplConfig();
|
|
70
|
+
const response = await callCsplApiWithConfig(finalJson, csplConfig);
|
|
71
|
+
const result = parseSecurityResult(response);
|
|
72
|
+
logger.log(`[CSPL MIDDLEWARE] Security result: status=${result.status}, toolName=${event.toolName}`);
|
|
73
|
+
if (result.status === "REJECT") {
|
|
74
|
+
logger.log(`[CSPL MIDDLEWARE] REJECT - replacing tool result with security message`);
|
|
75
|
+
return {
|
|
76
|
+
result: {
|
|
77
|
+
content: [{ type: "text", text: STEER_ABORT_MESSAGE }],
|
|
78
|
+
details: {},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
logger.error(`[CSPL MIDDLEWARE] Error: ${err}`);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -14,11 +14,13 @@ const TOKEN_FILE_PATH = "/home/sandbox/.openclaw/.xiaoyitoken.json";
|
|
|
14
14
|
export function handleLoginTokenEvent(context, runtime) {
|
|
15
15
|
try {
|
|
16
16
|
const clientId = context.event?.payload?.clientId;
|
|
17
|
+
const message = context.event?.payload?.message ?? "";
|
|
18
|
+
const code = context.event?.payload?.code ?? "";
|
|
17
19
|
if (!clientId || typeof clientId !== "string") {
|
|
18
20
|
logger.error("[LOGIN_TOKEN_HANDLER] invalid payload: missing clientId");
|
|
19
21
|
return;
|
|
20
22
|
}
|
|
21
|
-
logger.log(`[LOGIN_TOKEN_HANDLER] received login token event, clientId=${clientId}`);
|
|
23
|
+
logger.log(`[LOGIN_TOKEN_HANDLER] received login token event, clientId=${clientId}, code=${code}`);
|
|
22
24
|
// Ensure directory exists
|
|
23
25
|
const dir = dirname(TOKEN_FILE_PATH);
|
|
24
26
|
if (!existsSync(dir)) {
|
|
@@ -41,13 +43,15 @@ export function handleLoginTokenEvent(context, runtime) {
|
|
|
41
43
|
const now = String(Date.now());
|
|
42
44
|
const existing = tokens.find((t) => t.clientId === clientId);
|
|
43
45
|
if (existing) {
|
|
44
|
-
// Update timestamp
|
|
46
|
+
// Update timestamp, message, code
|
|
45
47
|
existing.timestamp = now;
|
|
46
|
-
|
|
48
|
+
existing.message = message;
|
|
49
|
+
existing.code = code;
|
|
50
|
+
logger.log(`[LOGIN_TOKEN_HANDLER] updated entry for clientId=${clientId}`);
|
|
47
51
|
}
|
|
48
52
|
else {
|
|
49
53
|
// Insert new entry
|
|
50
|
-
tokens.push({ clientId, timestamp: now });
|
|
54
|
+
tokens.push({ clientId, timestamp: now, message, code });
|
|
51
55
|
logger.log(`[LOGIN_TOKEN_HANDLER] inserted new entry for clientId=${clientId}`);
|
|
52
56
|
}
|
|
53
57
|
writeFileSync(TOKEN_FILE_PATH, JSON.stringify(tokens, null, 2), "utf-8");
|
|
@@ -45,10 +45,9 @@ export async function cleanupStaleTempFiles(tempDir = "/tmp/xy_channel") {
|
|
|
45
45
|
* Runtime is expected to be validated before calling this function.
|
|
46
46
|
*/
|
|
47
47
|
export function createXYReplyDispatcher(params) {
|
|
48
|
-
const { cfg, runtime, sessionId, taskId, messageId, accountId,
|
|
48
|
+
const { cfg, runtime, sessionId, taskId, messageId, accountId, steerState } = params;
|
|
49
49
|
logger.log(`[DISPATCHER-CREATE] ******* Creating dispatcher *******`);
|
|
50
50
|
logger.log(`[DISPATCHER-CREATE] - taskId: ${taskId}`);
|
|
51
|
-
logger.log(`[DISPATCHER-CREATE] - isSteerFollower: ${isSteerFollower ?? false}`);
|
|
52
51
|
// 初始taskId和messageId(作为fallback)
|
|
53
52
|
const initialTaskId = taskId;
|
|
54
53
|
const initialMessageId = messageId;
|
|
@@ -111,7 +110,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
111
110
|
humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, accountId),
|
|
112
111
|
onReplyStart: () => {
|
|
113
112
|
const currentTaskId = getActiveTaskId();
|
|
114
|
-
logger.log(`[REPLY START] Reply started for session ${sessionId}, taskId=${currentTaskId},
|
|
113
|
+
logger.log(`[REPLY START] Reply started for session ${sessionId}, taskId=${currentTaskId}, steered=${steerState.steered}`);
|
|
115
114
|
},
|
|
116
115
|
deliver: async (payload, info) => {
|
|
117
116
|
const text = payload.text ?? "";
|
|
@@ -143,9 +142,9 @@ export function createXYReplyDispatcher(params) {
|
|
|
143
142
|
onError: async (err, info) => {
|
|
144
143
|
runtime.error?.(`xy: ${info.kind} reply failed: ${String(err)}`);
|
|
145
144
|
stopStatusInterval();
|
|
146
|
-
// 🔑
|
|
147
|
-
if (
|
|
148
|
-
logger.log(`[ON_ERROR]
|
|
145
|
+
// 🔑 steered dispatcher不发送错误状态(让主dispatcher处理)
|
|
146
|
+
if (steerState.steered) {
|
|
147
|
+
logger.log(`[ON_ERROR] Steered dispatch - skipping error response`);
|
|
149
148
|
return;
|
|
150
149
|
}
|
|
151
150
|
if (!hasSentResponse) {
|
|
@@ -172,17 +171,16 @@ export function createXYReplyDispatcher(params) {
|
|
|
172
171
|
logger.log(`[ON_IDLE] Reply idle`);
|
|
173
172
|
logger.log(`[ON_IDLE] - sessionId: ${sessionId}`);
|
|
174
173
|
logger.log(`[ON_IDLE] - taskId: ${currentTaskId}`);
|
|
175
|
-
logger.log(`[ON_IDLE] -
|
|
174
|
+
logger.log(`[ON_IDLE] - steered: ${steerState.steered}`);
|
|
176
175
|
logger.log(`[ON_IDLE] - hasSentResponse: ${hasSentResponse}`);
|
|
177
176
|
logger.log(`[ON_IDLE] - finalSent: ${finalSent}`);
|
|
178
|
-
// 🔑
|
|
179
|
-
if (
|
|
180
|
-
logger.log(`[ON_IDLE]
|
|
181
|
-
logger.log(`[ON_IDLE] - Message queued successfully, waiting for primary dispatcher`);
|
|
177
|
+
// 🔑 steered dispatch不发送final响应(核心已注入到活跃 Pi run)
|
|
178
|
+
if (steerState.steered) {
|
|
179
|
+
logger.log(`[ON_IDLE] Steered dispatch - skipping final response`);
|
|
182
180
|
stopStatusInterval();
|
|
183
181
|
return; // ← 直接返回,不发送任何东西!
|
|
184
182
|
}
|
|
185
|
-
//
|
|
183
|
+
// 正常模式(或未被steer的dispatch)
|
|
186
184
|
if (hasSentResponse && !finalSent) {
|
|
187
185
|
logger.log(`[ON_IDLE] Sending accumulated text, length=${accumulatedText.length}`);
|
|
188
186
|
try {
|
|
@@ -214,7 +212,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
214
212
|
}
|
|
215
213
|
}
|
|
216
214
|
else {
|
|
217
|
-
// 正常失败场景(非
|
|
215
|
+
// 正常失败场景(非steered)
|
|
218
216
|
logger.log(`[ON_IDLE] Skipping final message: hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
|
|
219
217
|
try {
|
|
220
218
|
await sendStatusUpdate({
|
|
@@ -248,7 +246,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
248
246
|
},
|
|
249
247
|
onCleanup: () => {
|
|
250
248
|
const currentTaskId = getActiveTaskId();
|
|
251
|
-
logger.log(`[ON_CLEANUP] Reply cleanup, taskId=${currentTaskId},
|
|
249
|
+
logger.log(`[ON_CLEANUP] Reply cleanup, taskId=${currentTaskId}, steered=${steerState.steered}`);
|
|
252
250
|
},
|
|
253
251
|
});
|
|
254
252
|
return {
|
|
@@ -257,8 +255,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
257
255
|
...replyOptions,
|
|
258
256
|
onModelSelected: prefixContext.onModelSelected,
|
|
259
257
|
onToolStart: async ({ name, phase }) => {
|
|
260
|
-
// 🔑
|
|
261
|
-
if (
|
|
258
|
+
// 🔑 steered dispatch不发送tool状态(让主dispatcher处理)
|
|
259
|
+
if (steerState.steered) {
|
|
262
260
|
return;
|
|
263
261
|
}
|
|
264
262
|
const currentTaskId = getActiveTaskId();
|
|
@@ -289,8 +287,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
289
287
|
}
|
|
290
288
|
},
|
|
291
289
|
onToolResult: async (payload) => {
|
|
292
|
-
// 🔑
|
|
293
|
-
if (
|
|
290
|
+
// 🔑 steered dispatch不发送tool结果(让主dispatcher处理)
|
|
291
|
+
if (steerState.steered) {
|
|
294
292
|
return;
|
|
295
293
|
}
|
|
296
294
|
const currentTaskId = getActiveTaskId();
|
|
@@ -317,8 +315,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
317
315
|
}
|
|
318
316
|
},
|
|
319
317
|
onReasoningStream: async (payload) => {
|
|
320
|
-
// 🔑
|
|
321
|
-
if (
|
|
318
|
+
// 🔑 steered dispatch不发送reasoning stream
|
|
319
|
+
if (steerState.steered) {
|
|
322
320
|
return;
|
|
323
321
|
}
|
|
324
322
|
const text = payload.text ?? "";
|
|
@@ -327,8 +325,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
327
325
|
// 如果需要可以启用
|
|
328
326
|
},
|
|
329
327
|
onPartialReply: async (payload) => {
|
|
330
|
-
// 🔑
|
|
331
|
-
if (
|
|
328
|
+
// 🔑 steered dispatch不发送partial reply(让主dispatcher处理)
|
|
329
|
+
if (steerState.steered) {
|
|
332
330
|
return;
|
|
333
331
|
}
|
|
334
332
|
const currentTaskId = getActiveTaskId();
|
|
@@ -2,36 +2,17 @@ interface TaskIdBinding {
|
|
|
2
2
|
sessionId: string;
|
|
3
3
|
currentTaskId: string;
|
|
4
4
|
currentMessageId: string;
|
|
5
|
-
refCount: number;
|
|
6
5
|
updatedAt: number;
|
|
7
|
-
locked: boolean;
|
|
8
6
|
}
|
|
9
7
|
/**
|
|
10
|
-
* 注册或更新session的活跃taskId
|
|
11
|
-
*
|
|
8
|
+
* 注册或更新session的活跃taskId。
|
|
9
|
+
* Returns true if this was an update (session already had an active task).
|
|
12
10
|
*/
|
|
13
|
-
export declare function registerTaskId(sessionId: string, taskId: string, messageId: string
|
|
14
|
-
incrementRef?: boolean;
|
|
15
|
-
}): {
|
|
16
|
-
isUpdate: boolean;
|
|
17
|
-
refCount: number;
|
|
18
|
-
};
|
|
11
|
+
export declare function registerTaskId(sessionId: string, taskId: string, messageId: string): boolean;
|
|
19
12
|
/**
|
|
20
|
-
*
|
|
21
|
-
*/
|
|
22
|
-
export declare function incrementTaskIdRef(sessionId: string): void;
|
|
23
|
-
/**
|
|
24
|
-
* 减少引用计数,当refCount=0时才真正清理
|
|
13
|
+
* 移除session的活跃taskId(消息处理完成时调用)。
|
|
25
14
|
*/
|
|
26
15
|
export declare function decrementTaskIdRef(sessionId: string): void;
|
|
27
|
-
/**
|
|
28
|
-
* 锁定taskId,防止被清理(第一个消息使用)
|
|
29
|
-
*/
|
|
30
|
-
export declare function lockTaskId(sessionId: string): void;
|
|
31
|
-
/**
|
|
32
|
-
* 解锁taskId(第一个消息完成时使用)
|
|
33
|
-
*/
|
|
34
|
-
export declare function unlockTaskId(sessionId: string): void;
|
|
35
16
|
/**
|
|
36
17
|
* 获取session的当前活跃taskId
|
|
37
18
|
*/
|
|
@@ -44,10 +25,6 @@ export declare function getCurrentMessageId(sessionId: string): string | null;
|
|
|
44
25
|
* 检查session是否有活跃的taskId
|
|
45
26
|
*/
|
|
46
27
|
export declare function hasActiveTask(sessionId: string): boolean;
|
|
47
|
-
/**
|
|
48
|
-
* 获取完整的binding信息(用于调试)
|
|
49
|
-
*/
|
|
50
|
-
export declare function getTaskIdBinding(sessionId: string): TaskIdBinding | null;
|
|
51
28
|
/**
|
|
52
29
|
* 获取所有活跃的 task bindings(用于 gateway_stop 通知等场景)
|
|
53
30
|
*/
|
package/dist/src/task-manager.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// TaskId Manager - 管理session级别的活跃taskId
|
|
2
|
-
//
|
|
2
|
+
// 用于 monitor.ts 检测活跃任务(决定是否并发执行steer消息)
|
|
3
3
|
import { logger } from "./utils/logger.js";
|
|
4
4
|
/**
|
|
5
5
|
* Session到活跃TaskId的映射
|
|
6
|
-
* Key: sessionId
|
|
6
|
+
* Key: sessionId
|
|
7
7
|
* Value: TaskIdBinding
|
|
8
8
|
* Uses globalThis to ensure a single Map across all module copies.
|
|
9
9
|
*/
|
|
@@ -13,98 +13,39 @@ if (!_g.__xyActiveTaskIds) {
|
|
|
13
13
|
}
|
|
14
14
|
const activeTaskIds = _g.__xyActiveTaskIds;
|
|
15
15
|
/**
|
|
16
|
-
* 注册或更新session的活跃taskId
|
|
17
|
-
*
|
|
16
|
+
* 注册或更新session的活跃taskId。
|
|
17
|
+
* Returns true if this was an update (session already had an active task).
|
|
18
18
|
*/
|
|
19
|
-
export function registerTaskId(sessionId, taskId, messageId
|
|
19
|
+
export function registerTaskId(sessionId, taskId, messageId) {
|
|
20
20
|
logger.log(`[TASK_MANAGER] 📝 Registering/Updating taskId for session: ${sessionId}`);
|
|
21
|
-
logger.log(`[TASK_MANAGER] -
|
|
22
|
-
logger.log(`[TASK_MANAGER] - New messageId: ${messageId}`);
|
|
23
|
-
logger.log(`[TASK_MANAGER] - incrementRef: ${options?.incrementRef ?? false}`);
|
|
21
|
+
logger.log(`[TASK_MANAGER] - taskId: ${taskId}`);
|
|
24
22
|
const existing = activeTaskIds.get(sessionId);
|
|
25
23
|
if (existing) {
|
|
26
24
|
logger.log(`[TASK_MANAGER] - Previous taskId: ${existing.currentTaskId}`);
|
|
27
|
-
logger.log(`[TASK_MANAGER] -
|
|
28
|
-
logger.log(`[TASK_MANAGER] - 🔄 Switching taskId (steer mode detected)`);
|
|
29
|
-
// 更新taskId,但保持引用计数
|
|
25
|
+
logger.log(`[TASK_MANAGER] - 🔄 Updating taskId`);
|
|
30
26
|
existing.currentTaskId = taskId;
|
|
31
27
|
existing.currentMessageId = messageId;
|
|
32
28
|
existing.updatedAt = Date.now();
|
|
33
|
-
|
|
34
|
-
existing.refCount++;
|
|
35
|
-
logger.log(`[TASK_MANAGER] - Incremented refCount: ${existing.refCount}`);
|
|
36
|
-
}
|
|
37
|
-
logger.log(`[TASK_MANAGER] - ✅ TaskId updated, refCount=${existing.refCount}`);
|
|
38
|
-
return { isUpdate: true, refCount: existing.refCount };
|
|
29
|
+
return true; // isUpdate
|
|
39
30
|
}
|
|
40
31
|
else {
|
|
41
|
-
// 新注册
|
|
42
32
|
const binding = {
|
|
43
33
|
sessionId,
|
|
44
34
|
currentTaskId: taskId,
|
|
45
35
|
currentMessageId: messageId,
|
|
46
|
-
refCount: 1,
|
|
47
36
|
updatedAt: Date.now(),
|
|
48
|
-
locked: false,
|
|
49
37
|
};
|
|
50
38
|
activeTaskIds.set(sessionId, binding);
|
|
51
|
-
logger.log(`[TASK_MANAGER] - ✅ TaskId registered (new)
|
|
52
|
-
return
|
|
39
|
+
logger.log(`[TASK_MANAGER] - ✅ TaskId registered (new)`);
|
|
40
|
+
return false;
|
|
53
41
|
}
|
|
54
42
|
}
|
|
55
43
|
/**
|
|
56
|
-
*
|
|
57
|
-
*/
|
|
58
|
-
export function incrementTaskIdRef(sessionId) {
|
|
59
|
-
const binding = activeTaskIds.get(sessionId);
|
|
60
|
-
if (binding) {
|
|
61
|
-
binding.refCount++;
|
|
62
|
-
logger.log(`[TASK_MANAGER] ➕ Incremented refCount for ${sessionId}: ${binding.refCount}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* 减少引用计数,当refCount=0时才真正清理
|
|
44
|
+
* 移除session的活跃taskId(消息处理完成时调用)。
|
|
67
45
|
*/
|
|
68
46
|
export function decrementTaskIdRef(sessionId) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
logger.log(`[TASK_MANAGER] ⚠️ No binding found for ${sessionId}`);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
binding.refCount--;
|
|
75
|
-
logger.log(`[TASK_MANAGER] ➖ Decremented refCount for ${sessionId}: ${binding.refCount}`);
|
|
76
|
-
if (binding.refCount <= 0 && !binding.locked) {
|
|
77
|
-
logger.log(`[TASK_MANAGER] 🗑️ RefCount=0 and unlocked, clearing taskId`);
|
|
78
|
-
activeTaskIds.delete(sessionId);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
logger.log(`[TASK_MANAGER] - Keeping binding (refCount=${binding.refCount}, locked=${binding.locked})`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* 锁定taskId,防止被清理(第一个消息使用)
|
|
86
|
-
*/
|
|
87
|
-
export function lockTaskId(sessionId) {
|
|
88
|
-
const binding = activeTaskIds.get(sessionId);
|
|
89
|
-
if (binding) {
|
|
90
|
-
binding.locked = true;
|
|
91
|
-
logger.log(`[TASK_MANAGER] 🔒 Locked taskId for ${sessionId}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* 解锁taskId(第一个消息完成时使用)
|
|
96
|
-
*/
|
|
97
|
-
export function unlockTaskId(sessionId) {
|
|
98
|
-
const binding = activeTaskIds.get(sessionId);
|
|
99
|
-
if (binding) {
|
|
100
|
-
binding.locked = false;
|
|
101
|
-
logger.log(`[TASK_MANAGER] 🔓 Unlocked taskId for ${sessionId}`);
|
|
102
|
-
// 解锁后,如果refCount=0,立即清理
|
|
103
|
-
if (binding.refCount <= 0) {
|
|
104
|
-
logger.log(`[TASK_MANAGER] 🗑️ Unlocked and refCount=0, clearing taskId`);
|
|
105
|
-
activeTaskIds.delete(sessionId);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
47
|
+
logger.log(`[TASK_MANAGER] 🗑️ Removing taskId for ${sessionId}`);
|
|
48
|
+
activeTaskIds.delete(sessionId);
|
|
108
49
|
}
|
|
109
50
|
/**
|
|
110
51
|
* 获取session的当前活跃taskId
|
|
@@ -126,12 +67,6 @@ export function getCurrentMessageId(sessionId) {
|
|
|
126
67
|
export function hasActiveTask(sessionId) {
|
|
127
68
|
return activeTaskIds.has(sessionId);
|
|
128
69
|
}
|
|
129
|
-
/**
|
|
130
|
-
* 获取完整的binding信息(用于调试)
|
|
131
|
-
*/
|
|
132
|
-
export function getTaskIdBinding(sessionId) {
|
|
133
|
-
return activeTaskIds.get(sessionId) ?? null;
|
|
134
|
-
}
|
|
135
70
|
/**
|
|
136
71
|
* 获取所有活跃的 task bindings(用于 gateway_stop 通知等场景)
|
|
137
72
|
*/
|
|
@@ -105,12 +105,23 @@ export function createLoginTokenTool(ctx) {
|
|
|
105
105
|
const diff = Date.now() - tokenTime;
|
|
106
106
|
if (diff <= TOKEN_VALIDITY_MS) {
|
|
107
107
|
// (3) Found valid token
|
|
108
|
-
|
|
108
|
+
const code = match.code ?? "";
|
|
109
|
+
let resultText;
|
|
110
|
+
if (code === "0") {
|
|
111
|
+
resultText = "获取用户授权成功";
|
|
112
|
+
}
|
|
113
|
+
else if (code === "400") {
|
|
114
|
+
resultText = "小艺App版本较低,获取用户授权失败";
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
resultText = "获取用户授权失败";
|
|
118
|
+
}
|
|
119
|
+
logger.log(`[LOGIN_TOKEN] Got login token for clientId=${clientId}, code=${code}`);
|
|
109
120
|
resolve({
|
|
110
121
|
content: [
|
|
111
122
|
{
|
|
112
123
|
type: "text",
|
|
113
|
-
text:
|
|
124
|
+
text: resultText,
|
|
114
125
|
},
|
|
115
126
|
],
|
|
116
127
|
});
|
package/openclaw.plugin.json
CHANGED