@workclaw/openclaw-workclaw 1.0.17 → 1.0.18
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/README.md +21 -1
- package/index.ts +210 -210
- package/openclaw.plugin.json +1 -0
- package/package.json +11 -4
- package/setup-entry.ts +6 -0
- package/skills/openclaw-workclaw-cron/SKILL.md +45 -28
- package/src/accounts.ts +62 -37
- package/src/api/accounts-api.ts +88 -89
- package/src/api/prompts-api.ts +70 -77
- package/src/api/session-api.ts +99 -108
- package/src/api/skills-api.ts +35 -37
- package/src/api/workspace.ts +27 -29
- package/src/channel.ts +200 -202
- package/src/config-schema.ts +9 -9
- package/src/connection/workclaw-client.ts +554 -567
- package/src/gateway/agent-handlers.ts +392 -426
- package/src/gateway/config-writer.ts +228 -243
- package/src/gateway/message-context.ts +534 -362
- package/src/gateway/message-dispatcher.ts +529 -489
- package/src/gateway/reconnect.ts +217 -113
- package/src/gateway/skills-handler.ts +408 -472
- package/src/gateway/skills-list-handler.ts +9 -9
- package/src/gateway/tools-list-handler.ts +70 -72
- package/src/gateway/workclaw-gateway.ts +328 -486
- package/src/media/upload.ts +83 -94
- package/src/outbound/index.ts +57 -55
- package/src/outbound/workclaw-sender.ts +134 -133
- package/src/runtime.ts +291 -194
- package/src/send.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/api/index.ts +6 -6
- package/src/tools/openclaw-workclaw-cron/src/add/params.ts +20 -19
- package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +2 -2
- package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +2 -2
- package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/update/params.ts +195 -197
- package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +4 -4
- package/src/tools/openclaw-workclaw-system/src/get/index.ts +2 -2
- package/src/tools/openclaw-workclaw-system/src/token/index.ts +4 -4
- package/src/types.ts +38 -40
- package/src/utils/content.ts +16 -21
- package/tests/accounts.test.ts +285 -0
- package/tests/message-context.test.ts +313 -0
- package/tests/reconnect.test.ts +257 -0
- package/tests/workclaw-client.test.ts +112 -0
- package/tsconfig.json +8 -5
- package/vitest.config.ts +8 -0
package/src/runtime.ts
CHANGED
|
@@ -1,400 +1,497 @@
|
|
|
1
|
-
import type { PluginRuntime } from
|
|
2
|
-
import type { WebSocket } from
|
|
1
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { WebSocket } from "undici";
|
|
3
3
|
|
|
4
|
-
let runtime: PluginRuntime | null = null
|
|
4
|
+
let runtime: PluginRuntime | null = null;
|
|
5
5
|
|
|
6
6
|
// 统一存储: key 可能是 accountId (per-account) 或 appKey (per-appKey)
|
|
7
|
-
const wsByKey = new Map<string, WebSocket>()
|
|
7
|
+
const wsByKey = new Map<string, WebSocket>();
|
|
8
8
|
|
|
9
9
|
// per-appKey 模式额外需要的映射
|
|
10
|
-
const accountIdsByAppKey = new Map<string, Set<string>>()
|
|
11
|
-
const appKeyByAccountId = new Map<string, string>()
|
|
12
|
-
const reconnectSchedulers = new Map<string, any>()
|
|
13
|
-
const dispatchersByAppKey = new Map<string, Map<string, { ctx: any }>>()
|
|
14
|
-
|
|
10
|
+
const accountIdsByAppKey = new Map<string, Set<string>>();
|
|
11
|
+
const appKeyByAccountId = new Map<string, string>();
|
|
12
|
+
const reconnectSchedulers = new Map<string, any>();
|
|
13
|
+
const dispatchersByAppKey = new Map<string, Map<string, { ctx: any }>>();
|
|
14
|
+
// 正在连接中的 appKey → Promise<ws>(成功后 resolve(ws),失败 resolve(null))
|
|
15
|
+
const connectingPromises = new Map<string, Promise<WebSocket | null>>();
|
|
16
|
+
// 仅用于 tryStartConnecting 返回 existingWs:ws 已建立成功后,从这里读取
|
|
17
|
+
const wsByAppKey = new Map<string, WebSocket>();
|
|
18
|
+
const lastMessageAtByKey = new Map<string, number>(); // 上次消息时间,key 为 accountId 或 appKey
|
|
15
19
|
|
|
16
|
-
export function
|
|
17
|
-
runtime = next
|
|
20
|
+
export function setWorkclawRuntime(next: PluginRuntime) {
|
|
21
|
+
runtime = next;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
export function
|
|
24
|
+
export function getWorkclawRuntime(): PluginRuntime {
|
|
21
25
|
if (!runtime) {
|
|
22
|
-
throw new Error(
|
|
26
|
+
throw new Error("Workclaw runtime not initialized");
|
|
23
27
|
}
|
|
24
|
-
return runtime
|
|
28
|
+
return runtime;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
export
|
|
28
|
-
info
|
|
29
|
-
warn
|
|
30
|
-
error
|
|
31
|
-
debug
|
|
32
|
-
}
|
|
31
|
+
export type WorkclawLogger = {
|
|
32
|
+
info?: (msg: string, ...args: any[]) => void;
|
|
33
|
+
warn?: (msg: string, ...args: any[]) => void;
|
|
34
|
+
error?: (msg: string, ...args: any[]) => void;
|
|
35
|
+
debug?: (msg: string, ...args: any[]) => void;
|
|
36
|
+
};
|
|
33
37
|
|
|
34
|
-
let _logger:
|
|
38
|
+
let _logger: WorkclawLogger | null = null;
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
const _noop = () => {};
|
|
37
41
|
|
|
38
|
-
function _fallbackLogger():
|
|
42
|
+
function _fallbackLogger(): WorkclawLogger {
|
|
39
43
|
return {
|
|
40
|
-
// eslint-disable-next-line no-console
|
|
41
44
|
info: console.log.bind(console),
|
|
42
45
|
warn: console.warn.bind(console),
|
|
43
46
|
error: console.error.bind(console),
|
|
44
47
|
debug: _noop,
|
|
45
|
-
}
|
|
48
|
+
};
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
/** Store the gateway's log into runtime. Called from startAccount. */
|
|
49
|
-
export function
|
|
50
|
-
_logger = log
|
|
52
|
+
export function setWorkclawLoggerFromContext(log: WorkclawLogger): void {
|
|
53
|
+
_logger = log;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
/** Get the shared logger. Returns ctx.log from gateway if set, otherwise a console fallback. */
|
|
54
|
-
export function
|
|
57
|
+
export function getWorkclawLogger(): WorkclawLogger {
|
|
55
58
|
if (!_logger) {
|
|
56
|
-
return _fallbackLogger()
|
|
59
|
+
return _fallbackLogger();
|
|
57
60
|
}
|
|
58
61
|
// If the stored logger's info is a noop, fall back to console
|
|
59
|
-
const l = _logger
|
|
62
|
+
const l = _logger;
|
|
60
63
|
if (l.info === _noop) {
|
|
61
|
-
return _fallbackLogger()
|
|
64
|
+
return _fallbackLogger();
|
|
62
65
|
}
|
|
63
|
-
return _logger
|
|
66
|
+
return _logger;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/**
|
|
67
70
|
* Wrap a logger with a prefix automatically prepended to every message.
|
|
68
71
|
* All messages logged via the returned logger will have the prefix attached.
|
|
69
72
|
*/
|
|
70
|
-
export function createLogger(prefix: string, log?:
|
|
71
|
-
const fallback = _fallbackLogger()
|
|
73
|
+
export function createLogger(prefix: string, log?: WorkclawLogger): WorkclawLogger {
|
|
74
|
+
const fallback = _fallbackLogger();
|
|
72
75
|
return {
|
|
73
|
-
info: log?.info ? (msg: string, ...args: any[]) => log.info(`${prefix} ${msg}`, ...args) : fallback.info,
|
|
74
|
-
warn: log?.warn ? (msg: string, ...args: any[]) => log.warn(`${prefix} ${msg}`, ...args) : fallback.warn,
|
|
75
|
-
error: log?.error ? (msg: string, ...args: any[]) => log.error(`${prefix} ${msg}`, ...args) : fallback.error,
|
|
76
|
-
debug: log?.debug ? (msg: string, ...args: any[]) => log.debug(`${prefix} ${msg}`, ...args) : fallback.debug,
|
|
77
|
-
}
|
|
76
|
+
info: log?.info ? (msg: string, ...args: any[]) => log.info!(`${prefix} ${msg}`, ...args) : fallback.info,
|
|
77
|
+
warn: log?.warn ? (msg: string, ...args: any[]) => log.warn!(`${prefix} ${msg}`, ...args) : fallback.warn,
|
|
78
|
+
error: log?.error ? (msg: string, ...args: any[]) => log.error!(`${prefix} ${msg}`, ...args) : fallback.error,
|
|
79
|
+
debug: log?.debug ? (msg: string, ...args: any[]) => log.debug!(`${prefix} ${msg}`, ...args) : fallback.debug,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function setWorkclawWsConnection(key: string, ws: WebSocket) {
|
|
84
|
+
wsByKey.set(key, ws);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function clearWorkclawWsConnection(key: string) {
|
|
88
|
+
wsByKey.delete(key);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getWorkclawWsConnection(key: string): WebSocket | undefined {
|
|
92
|
+
return wsByKey.get(key);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function setLastInboundAt(key: string, timestamp: number) {
|
|
96
|
+
lastMessageAtByKey.set(key, timestamp);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getLastInboundAt(key: string): number | undefined {
|
|
100
|
+
return lastMessageAtByKey.get(key);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const lastOutboundAtByKey = new Map<string, number>();
|
|
104
|
+
|
|
105
|
+
// Cached connection config: accountId -> connection config with stable appKey/appSecret
|
|
106
|
+
// This prevents re-resolving from cfg which may have been mutated
|
|
107
|
+
const connectionConfigCache = new Map<string, {
|
|
108
|
+
appKey: string;
|
|
109
|
+
appSecret: string;
|
|
110
|
+
baseUrl: string;
|
|
111
|
+
websocketUrl?: string;
|
|
112
|
+
localIp?: string;
|
|
113
|
+
allowInsecureTls?: boolean;
|
|
114
|
+
requestTimeout?: number;
|
|
115
|
+
}>();
|
|
116
|
+
|
|
117
|
+
export function setWorkclawConnectionConfig(accountId: string, config: {
|
|
118
|
+
appKey: string;
|
|
119
|
+
appSecret: string;
|
|
120
|
+
baseUrl?: string;
|
|
121
|
+
websocketUrl?: string;
|
|
122
|
+
localIp?: string;
|
|
123
|
+
allowInsecureTls?: boolean;
|
|
124
|
+
requestTimeout?: number;
|
|
125
|
+
}) {
|
|
126
|
+
connectionConfigCache.set(accountId, {
|
|
127
|
+
appKey: config.appKey,
|
|
128
|
+
appSecret: config.appSecret,
|
|
129
|
+
baseUrl: config.baseUrl ?? "",
|
|
130
|
+
websocketUrl: config.websocketUrl,
|
|
131
|
+
localIp: config.localIp,
|
|
132
|
+
allowInsecureTls: config.allowInsecureTls,
|
|
133
|
+
requestTimeout: config.requestTimeout,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function getWorkclawConnectionConfig(accountId: string): {
|
|
138
|
+
appKey: string;
|
|
139
|
+
appSecret: string;
|
|
140
|
+
baseUrl: string;
|
|
141
|
+
websocketUrl?: string;
|
|
142
|
+
localIp?: string;
|
|
143
|
+
allowInsecureTls?: boolean;
|
|
144
|
+
requestTimeout?: number;
|
|
145
|
+
} | undefined {
|
|
146
|
+
return connectionConfigCache.get(accountId);
|
|
78
147
|
}
|
|
79
148
|
|
|
80
|
-
export function
|
|
81
|
-
|
|
149
|
+
export function clearWorkclawConnectionConfig(accountId: string) {
|
|
150
|
+
connectionConfigCache.delete(accountId);
|
|
82
151
|
}
|
|
83
152
|
|
|
84
|
-
export function
|
|
85
|
-
|
|
153
|
+
export function setLastOutboundAt(key: string, timestamp: number) {
|
|
154
|
+
lastOutboundAtByKey.set(key, timestamp);
|
|
86
155
|
}
|
|
87
156
|
|
|
88
|
-
export function
|
|
89
|
-
return
|
|
157
|
+
export function getLastOutboundAt(key: string): number | undefined {
|
|
158
|
+
return lastOutboundAtByKey.get(key);
|
|
90
159
|
}
|
|
91
160
|
|
|
92
161
|
// per-appKey 模式专用
|
|
93
|
-
export function registerAccountContext(appKey: string, accountId: string, ctx: any)
|
|
162
|
+
export function registerAccountContext(appKey: string, accountId: string, ctx: any) {
|
|
94
163
|
if (!accountIdsByAppKey.has(appKey)) {
|
|
95
|
-
accountIdsByAppKey.set(appKey, new Set())
|
|
164
|
+
accountIdsByAppKey.set(appKey, new Set());
|
|
96
165
|
}
|
|
97
|
-
accountIdsByAppKey.get(appKey)!.add(accountId)
|
|
98
|
-
appKeyByAccountId.set(accountId, appKey)
|
|
166
|
+
accountIdsByAppKey.get(appKey)!.add(accountId);
|
|
167
|
+
appKeyByAccountId.set(accountId, appKey);
|
|
99
168
|
|
|
100
169
|
if (!dispatchersByAppKey.has(appKey)) {
|
|
101
|
-
dispatchersByAppKey.set(appKey, new Map())
|
|
170
|
+
dispatchersByAppKey.set(appKey, new Map());
|
|
102
171
|
}
|
|
103
|
-
dispatchersByAppKey.get(appKey)!.set(accountId, { ctx })
|
|
172
|
+
dispatchersByAppKey.get(appKey)!.set(accountId, { ctx });
|
|
104
173
|
}
|
|
105
174
|
|
|
106
|
-
export function unregisterAccountContext(appKey: string, accountId: string)
|
|
107
|
-
accountIdsByAppKey.get(appKey)?.delete(accountId)
|
|
108
|
-
appKeyByAccountId.delete(accountId)
|
|
109
|
-
dispatchersByAppKey.get(appKey)?.delete(accountId)
|
|
175
|
+
export function unregisterAccountContext(appKey: string, accountId: string) {
|
|
176
|
+
accountIdsByAppKey.get(appKey)?.delete(accountId);
|
|
177
|
+
appKeyByAccountId.delete(accountId);
|
|
178
|
+
dispatchersByAppKey.get(appKey)?.delete(accountId);
|
|
110
179
|
}
|
|
111
180
|
|
|
112
181
|
export function getAccountIdsByAppKey(appKey: string): string[] {
|
|
113
|
-
return Array.from(accountIdsByAppKey.get(appKey) ?? [])
|
|
182
|
+
return Array.from(accountIdsByAppKey.get(appKey) ?? []);
|
|
114
183
|
}
|
|
115
184
|
|
|
116
185
|
export function getAppKeyByAccountId(accountId: string): string | undefined {
|
|
117
|
-
return appKeyByAccountId.get(accountId)
|
|
186
|
+
return appKeyByAccountId.get(accountId);
|
|
118
187
|
}
|
|
119
188
|
|
|
120
189
|
export function getDispatcherByAppKeyAndAccountId(appKey: string, accountId: string): { ctx: any } | undefined {
|
|
121
|
-
return dispatchersByAppKey.get(appKey)?.get(accountId)
|
|
190
|
+
return dispatchersByAppKey.get(appKey)?.get(accountId);
|
|
122
191
|
}
|
|
123
192
|
|
|
124
193
|
export function getAllDispatchersByAppKey(appKey: string): Map<string, { ctx: any }> | undefined {
|
|
125
|
-
return dispatchersByAppKey.get(appKey)
|
|
194
|
+
return dispatchersByAppKey.get(appKey);
|
|
126
195
|
}
|
|
127
196
|
|
|
128
|
-
export function setReconnectScheduler(appKey: string, scheduler: any)
|
|
129
|
-
reconnectSchedulers.set(appKey, scheduler)
|
|
197
|
+
export function setReconnectScheduler(appKey: string, scheduler: any) {
|
|
198
|
+
reconnectSchedulers.set(appKey, scheduler);
|
|
130
199
|
}
|
|
131
200
|
|
|
132
201
|
export function getReconnectScheduler(appKey: string): any | undefined {
|
|
133
|
-
return reconnectSchedulers.get(appKey)
|
|
202
|
+
return reconnectSchedulers.get(appKey);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function clearReconnectScheduler(appKey: string) {
|
|
206
|
+
reconnectSchedulers.delete(appKey);
|
|
134
207
|
}
|
|
135
208
|
|
|
136
|
-
|
|
137
|
-
|
|
209
|
+
// 尝试开始连接,返回是否需要自己创建连接
|
|
210
|
+
// exported for workclaw-gateway.ts
|
|
211
|
+
export interface TryStartConnectingResult {
|
|
212
|
+
isConnector: boolean;
|
|
213
|
+
/** 如果 ws 已建立完成(非连接中),返回该 ws;否则 undefined */
|
|
214
|
+
existingWs: WebSocket | undefined;
|
|
215
|
+
/** 等待连接完成的 Promise:成功时 resolve(ws),失败时 resolve(null) */
|
|
216
|
+
connectingPromise: Promise<WebSocket | null>;
|
|
138
217
|
}
|
|
139
218
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
219
|
+
// 尝试开始连接
|
|
220
|
+
// 关键:所有并发调用者必须共享同一个 Promise,而不是各自创建新的导致重复建连
|
|
221
|
+
export function tryStartConnecting(appKey: string): TryStartConnectingResult {
|
|
222
|
+
// 优先查 connectingPromises,确保所有并发调用者共享同一个 Promise
|
|
223
|
+
const existingPromise = connectingPromises.get(appKey);
|
|
224
|
+
if (existingPromise !== undefined) {
|
|
225
|
+
// 已有并发账号在连接,等待同一个 Promise
|
|
226
|
+
return {
|
|
227
|
+
isConnector: false,
|
|
228
|
+
existingWs: wsByAppKey.get(appKey),
|
|
229
|
+
connectingPromise: existingPromise,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
// Map 里没有 entry,自己是新 connector
|
|
233
|
+
let resolvePromise: (ws: WebSocket | null) => void;
|
|
234
|
+
const promise = new Promise<WebSocket | null>((resolve) => { resolvePromise = resolve; });
|
|
235
|
+
(promise as any)._resolve = resolvePromise;
|
|
236
|
+
connectingPromises.set(appKey, promise);
|
|
237
|
+
return { isConnector: true, existingWs: undefined, connectingPromise: promise };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 标记连接完成(成功或失败都需要调用)
|
|
241
|
+
// ws 不传或传 undefined 表示失败
|
|
242
|
+
export function finishConnecting(appKey: string, ws?: WebSocket) {
|
|
243
|
+
const promise = connectingPromises.get(appKey);
|
|
244
|
+
if (promise) {
|
|
245
|
+
(promise as any)._resolve?.(ws ?? null);
|
|
246
|
+
connectingPromises.delete(appKey);
|
|
247
|
+
}
|
|
248
|
+
// ws 已建立成功时存入 wsByAppKey,供后续 tryStartConnecting 返回 existingWs
|
|
249
|
+
if (ws) {
|
|
250
|
+
wsByAppKey.set(appKey, ws);
|
|
144
251
|
}
|
|
145
|
-
connectingApps.add(appKey)
|
|
146
|
-
return true
|
|
147
252
|
}
|
|
148
253
|
|
|
149
254
|
// 工具执行上下文信息,用于关联 runId 和发送目标
|
|
150
255
|
export interface ToolContext {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
256
|
+
target: string;
|
|
257
|
+
replyToMessageId: string;
|
|
258
|
+
openConversationId: string;
|
|
259
|
+
accountId: string;
|
|
260
|
+
agentId: string;
|
|
261
|
+
sessionKey: string;
|
|
157
262
|
}
|
|
158
263
|
|
|
159
264
|
// runId -> ToolContext 映射表
|
|
160
|
-
const runIdToToolContext = new Map<string, ToolContext>()
|
|
265
|
+
const runIdToToolContext = new Map<string, ToolContext>();
|
|
161
266
|
// toolCallId -> runId 映射表(用于 tool_result_persist 通过 toolCallId 找到 runId,再找到 ctx)
|
|
162
|
-
const toolCallIdToRunId = new Map<string, string>()
|
|
267
|
+
const toolCallIdToRunId = new Map<string, string>();
|
|
163
268
|
|
|
164
269
|
// 获取工具上下文(通过 runId)
|
|
165
270
|
export function getToolContext(runId: string): ToolContext | undefined {
|
|
166
|
-
|
|
271
|
+
return runIdToToolContext.get(runId);
|
|
167
272
|
}
|
|
168
273
|
|
|
169
274
|
// 设置工具上下文(仅通过 runId)
|
|
170
275
|
export function setToolContext(runId: string, ctx: ToolContext): void {
|
|
171
|
-
|
|
276
|
+
runIdToToolContext.set(runId, ctx);
|
|
172
277
|
}
|
|
173
278
|
|
|
174
279
|
// 建立 toolCallId -> runId 的映射(用于 after_tool_call 中)
|
|
175
280
|
export function setToolCallIdToRunIdMapping(toolCallId: string, runId: string): void {
|
|
176
|
-
|
|
281
|
+
toolCallIdToRunId.set(toolCallId, runId);
|
|
177
282
|
}
|
|
178
283
|
|
|
179
284
|
// 通过 toolCallId 获取 runId
|
|
180
285
|
export function getRunIdByToolCallId(toolCallId: string): string | undefined {
|
|
181
|
-
|
|
286
|
+
return toolCallIdToRunId.get(toolCallId);
|
|
182
287
|
}
|
|
183
288
|
|
|
184
289
|
// 删除工具上下文
|
|
185
290
|
export function deleteToolContext(runId: string): void {
|
|
186
|
-
|
|
291
|
+
runIdToToolContext.delete(runId);
|
|
187
292
|
}
|
|
188
293
|
|
|
189
294
|
// 清空所有工具上下文(用于清理)
|
|
190
295
|
export function clearAllToolContexts(): void {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// 标记连接完成(成功或失败都需要调用)
|
|
196
|
-
export function finishConnecting(appKey: string): void {
|
|
197
|
-
connectingApps.delete(appKey)
|
|
296
|
+
runIdToToolContext.clear();
|
|
297
|
+
toolCallIdToRunId.clear();
|
|
198
298
|
}
|
|
199
299
|
|
|
200
300
|
// 调用sub-agent进行协作
|
|
201
301
|
export async function spawnSubAgent(params: {
|
|
202
|
-
agentId: string
|
|
203
|
-
task: string
|
|
204
|
-
label?: string
|
|
205
|
-
model?: string
|
|
206
|
-
}): Promise<{ ok: boolean
|
|
302
|
+
agentId: string;
|
|
303
|
+
task: string;
|
|
304
|
+
label?: string;
|
|
305
|
+
model?: string;
|
|
306
|
+
}): Promise<{ ok: boolean; sessionKey?: string; summary?: string; error?: string }> {
|
|
207
307
|
if (!runtime) {
|
|
208
|
-
throw new Error(
|
|
308
|
+
throw new Error("Workclaw runtime not initialized");
|
|
209
309
|
}
|
|
210
310
|
|
|
211
311
|
try {
|
|
212
312
|
// 通过runtime调用sessions_spawn
|
|
213
|
-
const sessionsSpawn = (runtime as any)?.sessions_spawn
|
|
214
|
-
if (!sessionsSpawn || typeof sessionsSpawn !==
|
|
313
|
+
const sessionsSpawn = (runtime as any)?.sessions_spawn;
|
|
314
|
+
if (!sessionsSpawn || typeof sessionsSpawn !== "function") {
|
|
215
315
|
// 如果runtime没有提供sessions_spawn,返回模拟数据
|
|
216
|
-
|
|
316
|
+
getWorkclawLogger().warn(`Runtime does not provide sessions_spawn, returning mock response for agent ${params.agentId}`);
|
|
217
317
|
return {
|
|
218
318
|
ok: true,
|
|
219
|
-
summary: `[模拟] Agent ${params.agentId} 完成任务: ${params.task.substring(0, 50)}
|
|
220
|
-
}
|
|
319
|
+
summary: `[模拟] Agent ${params.agentId} 完成任务: ${params.task.substring(0, 50)}...`
|
|
320
|
+
};
|
|
221
321
|
}
|
|
222
322
|
|
|
223
323
|
const result = await sessionsSpawn({
|
|
224
324
|
agentId: params.agentId,
|
|
225
325
|
task: params.task,
|
|
226
326
|
label: params.label,
|
|
227
|
-
model: params.model
|
|
228
|
-
})
|
|
327
|
+
model: params.model
|
|
328
|
+
});
|
|
229
329
|
|
|
230
330
|
return {
|
|
231
331
|
ok: true,
|
|
232
332
|
sessionKey: result.sessionKey,
|
|
233
|
-
summary: result.summary
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
getOpenclawWorkclawLogger().error(`Failed to spawn sub-agent: ${error.message}`)
|
|
333
|
+
summary: result.summary
|
|
334
|
+
};
|
|
335
|
+
} catch (error: any) {
|
|
336
|
+
getWorkclawLogger().error(`Failed to spawn sub-agent: ${error.message}`);
|
|
238
337
|
return {
|
|
239
338
|
ok: false,
|
|
240
|
-
error: error.message
|
|
241
|
-
}
|
|
339
|
+
error: error.message
|
|
340
|
+
};
|
|
242
341
|
}
|
|
243
342
|
}
|
|
244
343
|
|
|
245
|
-
export const __openclaw_workclaw_runtime_emitted = true
|
|
344
|
+
export const __openclaw_workclaw_runtime_emitted = true;
|
|
246
345
|
|
|
247
346
|
export interface ToolInfo {
|
|
248
|
-
emoji: string
|
|
249
|
-
title: string
|
|
250
|
-
hint: string
|
|
251
|
-
aliases: string[]
|
|
347
|
+
emoji: string;
|
|
348
|
+
title: string;
|
|
349
|
+
hint: string;
|
|
350
|
+
aliases: string[];
|
|
252
351
|
}
|
|
253
352
|
|
|
254
353
|
/**
|
|
255
354
|
* 获取工具执行开始的提示(用于 onToolStart)
|
|
256
355
|
*/
|
|
257
356
|
export function getToolStartHint(toolName: string | undefined): string {
|
|
258
|
-
if (!toolName)
|
|
259
|
-
return '🔄 正在执行工具...'
|
|
357
|
+
if (!toolName) return "🔄 正在执行工具...";
|
|
260
358
|
|
|
261
|
-
const normalizedToolName = toolName.toLowerCase().trim()
|
|
359
|
+
const normalizedToolName = toolName.toLowerCase().trim();
|
|
262
360
|
|
|
263
361
|
const toolMap: Record<string, ToolInfo> = {
|
|
264
362
|
// 📁 文件操作
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
363
|
+
read: { emoji: "📖", title: "读取文件", hint: "正在读取文件", aliases: ["file_read", "read_file"] },
|
|
364
|
+
write: { emoji: "✍️", title: "写入文件", hint: "正在写入文件", aliases: ["file_write", "write_file"] },
|
|
365
|
+
edit: { emoji: "✏️", title: "编辑文件", hint: "正在编辑文件", aliases: ["file_edit", "modify"] },
|
|
268
366
|
|
|
269
367
|
// 💻 系统命令
|
|
270
|
-
|
|
271
|
-
|
|
368
|
+
exec: { emoji: "⚡", title: "执行命令", hint: "正在执行命令", aliases: ["bash", "shell", "cmd", "command"] },
|
|
369
|
+
process: { emoji: "🔄", title: "进程管理", hint: "正在处理进程", aliases: ["process_manager"] },
|
|
272
370
|
|
|
273
371
|
// 🌐 网络/浏览器
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
372
|
+
web_fetch: { emoji: "🕸️", title: "获取网页", hint: "正在获取网页内容", aliases: ["fetch", "http_get", "http_request", "curl"] },
|
|
373
|
+
web_search: { emoji: "🔍", title: "搜索网页", hint: "正在搜索网页", aliases: ["search", "google_search", "brave_search"] },
|
|
374
|
+
browser: { emoji: "🌐", title: "浏览器控制", hint: "正在控制浏览器", aliases: ["browser_control", "playwright"] },
|
|
277
375
|
|
|
278
376
|
// 💬 消息/通信
|
|
279
|
-
|
|
280
|
-
|
|
377
|
+
message: { emoji: "💌", title: "发送消息", hint: "正在发送消息", aliases: ["send_message", "mcp_deliver", "deliver", "notify"] },
|
|
378
|
+
tts: { emoji: "🔊", title: "文字转语音", hint: "正在转换语音", aliases: ["text_to_speech", "speak"] },
|
|
281
379
|
|
|
282
380
|
// 🎭 会话管理
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
381
|
+
sessions_spawn: { emoji: "🧬", title: "创建子代理", hint: "正在创建子代理", aliases: ["spawn", "create_agent", "new_session"] },
|
|
382
|
+
sessions_send: { emoji: "📤", title: "跨会话发消息", hint: "正在发送跨会话消息", aliases: ["send_to_session", "forward"] },
|
|
383
|
+
sessions_list: { emoji: "📋", title: "列出会话", hint: "正在列出会话", aliases: ["list_sessions"] },
|
|
384
|
+
sessions_history: { emoji: "📜", title: "获取历史", hint: "正在获取历史消息", aliases: ["get_history", "history"] },
|
|
385
|
+
sessions_yield: { emoji: "⏸️", title: "暂停让出", hint: "正在暂停等待", aliases: ["yield", "pause"] },
|
|
386
|
+
subagents: { emoji: "🐛", title: "子代理管理", hint: "正在管理子代理", aliases: ["subagent_manager"] },
|
|
387
|
+
agents_list: { emoji: "🤖", title: "列出代理", hint: "正在列出可用代理", aliases: ["list_agents"] },
|
|
290
388
|
|
|
291
389
|
// 🧠 记忆/知识
|
|
292
|
-
|
|
293
|
-
|
|
390
|
+
memory_search: { emoji: "🔎", title: "搜索记忆", hint: "正在搜索记忆", aliases: ["search_memory", "recall"] },
|
|
391
|
+
memory_get: { emoji: "📝", title: "读取记忆", hint: "正在读取记忆片段", aliases: ["get_memory", "read_memory"] },
|
|
294
392
|
|
|
295
393
|
// ⏰ 定时任务
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
394
|
+
"openclaw-workclaw-cron-add-params": { emoji: "⏱️", title: "创建定时任务", hint: "正在创建定时任务", aliases: ["cron_add", "add_timer"] },
|
|
395
|
+
"openclaw-workclaw-cron-update-params": { emoji: "🔧", title: "修改定时任务", hint: "正在修改定时任务", aliases: ["cron_update", "update_timer"] },
|
|
396
|
+
"openclaw-workclaw-cron-remove-params": { emoji: "🗑️", title: "删除定时任务", hint: "正在删除定时任务", aliases: ["cron_remove", "remove_timer"] },
|
|
397
|
+
"openclaw-workclaw-cron-notify-sync": { emoji: "🔔", title: "定时触发通知", hint: "定时任务已触发", aliases: ["cron_trigger", "notify"] },
|
|
398
|
+
"openclaw-workclaw-cron-add-sync": { emoji: "✅", title: "同步定时任务", hint: "正在同步到后端", aliases: [] },
|
|
399
|
+
"openclaw-workclaw-cron-update-sync": { emoji: "✅", title: "同步定时更新", hint: "正在同步更新", aliases: [] },
|
|
400
|
+
"openclaw-workclaw-cron-remove-sync": { emoji: "✅", title: "同步定时删除", hint: "正在同步删除", aliases: [] },
|
|
401
|
+
"openclaw-workclaw-cron-disable-params": { emoji: "⏸️", title: "禁用定时任务", hint: "正在禁用定时任务", aliases: ["cron_disable"] },
|
|
402
|
+
"openclaw-workclaw-cron-enable-params": { emoji: "▶️", title: "启用定时任务", hint: "正在启用定时任务", aliases: ["cron_enable"] },
|
|
305
403
|
|
|
306
404
|
// 📊 其他
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
405
|
+
session_status: { emoji: "📊", title: "会话状态", hint: "正在获取会话状态", aliases: ["status", "get_status"] },
|
|
406
|
+
canvas: { emoji: "🎨", title: "画布控制", hint: "正在控制画布", aliases: ["canvas_control", "draw"] },
|
|
407
|
+
};
|
|
310
408
|
|
|
311
409
|
// 精确匹配
|
|
312
410
|
if (toolMap[normalizedToolName]) {
|
|
313
|
-
const tool = toolMap[normalizedToolName]
|
|
314
|
-
return `${tool.emoji} ${tool.hint}
|
|
411
|
+
const tool = toolMap[normalizedToolName];
|
|
412
|
+
return `${tool.emoji} ${tool.hint}...`;
|
|
315
413
|
}
|
|
316
414
|
|
|
317
415
|
// 别名匹配
|
|
318
|
-
for (const [
|
|
416
|
+
for (const [key, tool] of Object.entries(toolMap)) {
|
|
319
417
|
if (tool.aliases.includes(normalizedToolName)) {
|
|
320
|
-
return `${tool.emoji} ${tool.hint}
|
|
418
|
+
return `${tool.emoji} ${tool.hint}...`;
|
|
321
419
|
}
|
|
322
420
|
}
|
|
323
421
|
|
|
324
422
|
// 默认
|
|
325
|
-
return `🔄 正在执行 ${toolName}
|
|
423
|
+
return `🔄 正在执行 ${toolName}...`;
|
|
326
424
|
}
|
|
327
425
|
|
|
328
426
|
/**
|
|
329
427
|
* 获取工具执行完成的提示(用于 after_tool_call)
|
|
330
428
|
*/
|
|
331
429
|
export function getToolResultHint(toolName: string | undefined): string {
|
|
332
|
-
if (!toolName)
|
|
333
|
-
return '✅ 工具执行完成'
|
|
430
|
+
if (!toolName) return "✅ 工具执行完成";
|
|
334
431
|
|
|
335
|
-
const normalizedToolName = toolName.toLowerCase().trim()
|
|
432
|
+
const normalizedToolName = toolName.toLowerCase().trim();
|
|
336
433
|
|
|
337
434
|
const toolMap: Record<string, ToolInfo> = {
|
|
338
435
|
// 📁 文件操作
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
436
|
+
read: { emoji: "📖", title: "读取文件", hint: "已读取文件", aliases: ["file_read", "read_file"] },
|
|
437
|
+
write: { emoji: "✍️", title: "写入文件", hint: "已写入文件", aliases: ["file_write", "write_file"] },
|
|
438
|
+
edit: { emoji: "✏️", title: "编辑文件", hint: "已编辑文件", aliases: ["file_edit", "modify"] },
|
|
342
439
|
|
|
343
440
|
// 💻 系统命令
|
|
344
|
-
|
|
345
|
-
|
|
441
|
+
exec: { emoji: "⚡", title: "执行命令", hint: "已执行命令", aliases: ["bash", "shell", "cmd", "command"] },
|
|
442
|
+
process: { emoji: "🔄", title: "进程管理", hint: "已处理进程", aliases: ["process_manager"] },
|
|
346
443
|
|
|
347
444
|
// 🌐 网络/浏览器
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
445
|
+
web_fetch: { emoji: "🕸️", title: "获取网页", hint: "已获取网页", aliases: ["fetch", "http_get", "http_request", "curl"] },
|
|
446
|
+
web_search: { emoji: "🔍", title: "搜索网页", hint: "已搜索网页", aliases: ["search", "google_search", "brave_search"] },
|
|
447
|
+
browser: { emoji: "🌐", title: "浏览器控制", hint: "已控制浏览器", aliases: ["browser_control", "playwright"] },
|
|
351
448
|
|
|
352
449
|
// 💬 消息/通信
|
|
353
|
-
|
|
354
|
-
|
|
450
|
+
message: { emoji: "💌", title: "发送消息", hint: "已发送消息", aliases: ["send_message", "mcp_deliver", "deliver", "notify"] },
|
|
451
|
+
tts: { emoji: "🔊", title: "文字转语音", hint: "已转换语音", aliases: ["text_to_speech", "speak"] },
|
|
355
452
|
|
|
356
453
|
// 🎭 会话管理
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
454
|
+
sessions_spawn: { emoji: "🧬", title: "创建子代理", hint: "已创建子代理", aliases: ["spawn", "create_agent", "new_session"] },
|
|
455
|
+
sessions_send: { emoji: "📤", title: "跨会话发消息", hint: "已发送跨会话消息", aliases: ["send_to_session", "forward"] },
|
|
456
|
+
sessions_list: { emoji: "📋", title: "列出会话", hint: "已列出会话", aliases: ["list_sessions"] },
|
|
457
|
+
sessions_history: { emoji: "📜", title: "获取历史", hint: "已获取历史消息", aliases: ["get_history", "history"] },
|
|
458
|
+
sessions_yield: { emoji: "⏸️", title: "暂停让出", hint: "已暂停等待", aliases: ["yield", "pause"] },
|
|
459
|
+
subagents: { emoji: "🐛", title: "子代理管理", hint: "已管理子代理", aliases: ["subagent_manager"] },
|
|
460
|
+
agents_list: { emoji: "🤖", title: "列出代理", hint: "已列出代理", aliases: ["list_agents"] },
|
|
364
461
|
|
|
365
462
|
// 🧠 记忆/知识
|
|
366
|
-
|
|
367
|
-
|
|
463
|
+
memory_search: { emoji: "🔎", title: "搜索记忆", hint: "已搜索记忆", aliases: ["search_memory", "recall"] },
|
|
464
|
+
memory_get: { emoji: "📝", title: "读取记忆", hint: "已读取记忆", aliases: ["get_memory", "read_memory"] },
|
|
368
465
|
|
|
369
466
|
// ⏰ 定时任务
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
467
|
+
"openclaw-workclaw-cron-add-params": { emoji: "⏱️", title: "创建定时任务", hint: "已创建定时任务", aliases: ["cron_add", "add_timer"] },
|
|
468
|
+
"openclaw-workclaw-cron-update-params": { emoji: "🔧", title: "修改定时任务", hint: "已修改定时任务", aliases: ["cron_update", "update_timer"] },
|
|
469
|
+
"openclaw-workclaw-cron-remove-params": { emoji: "🗑️", title: "删除定时任务", hint: "已删除定时任务", aliases: ["cron_remove", "remove_timer"] },
|
|
470
|
+
"openclaw-workclaw-cron-notify-sync": { emoji: "🔔", title: "定时触发通知", hint: "定时任务已触发", aliases: ["cron_trigger", "notify"] },
|
|
471
|
+
"openclaw-workclaw-cron-add-sync": { emoji: "✅", title: "同步定时任务", hint: "已同步定时任务", aliases: [] },
|
|
472
|
+
"openclaw-workclaw-cron-update-sync": { emoji: "✅", title: "同步定时更新", hint: "已同步更新", aliases: [] },
|
|
473
|
+
"openclaw-workclaw-cron-remove-sync": { emoji: "✅", title: "同步定时删除", hint: "已同步删除", aliases: [] },
|
|
474
|
+
"openclaw-workclaw-cron-disable-params": { emoji: "⏸️", title: "禁用定时任务", hint: "已禁用定时任务", aliases: ["cron_disable"] },
|
|
475
|
+
"openclaw-workclaw-cron-enable-params": { emoji: "▶️", title: "启用定时任务", hint: "已启用定时任务", aliases: ["cron_enable"] },
|
|
379
476
|
|
|
380
477
|
// 📊 其他
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
478
|
+
session_status: { emoji: "📊", title: "会话状态", hint: "已获取会话状态", aliases: ["status", "get_status"] },
|
|
479
|
+
canvas: { emoji: "🎨", title: "画布控制", hint: "已控制画布", aliases: ["canvas_control", "draw"] },
|
|
480
|
+
};
|
|
384
481
|
|
|
385
482
|
// 精确匹配
|
|
386
483
|
if (toolMap[normalizedToolName]) {
|
|
387
|
-
const tool = toolMap[normalizedToolName]
|
|
388
|
-
return `${tool.emoji} ${tool.hint}
|
|
484
|
+
const tool = toolMap[normalizedToolName];
|
|
485
|
+
return `${tool.emoji} ${tool.hint}`;
|
|
389
486
|
}
|
|
390
487
|
|
|
391
488
|
// 别名匹配
|
|
392
|
-
for (const [
|
|
489
|
+
for (const [key, tool] of Object.entries(toolMap)) {
|
|
393
490
|
if (tool.aliases.includes(normalizedToolName)) {
|
|
394
|
-
return `${tool.emoji} ${tool.hint}
|
|
491
|
+
return `${tool.emoji} ${tool.hint}`;
|
|
395
492
|
}
|
|
396
493
|
}
|
|
397
494
|
|
|
398
495
|
// 默认
|
|
399
|
-
return `✅ 已执行 ${toolName}
|
|
400
|
-
}
|
|
496
|
+
return `✅ 已执行 ${toolName}`;
|
|
497
|
+
}
|