@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.
Files changed (51) hide show
  1. package/README.md +21 -1
  2. package/index.ts +210 -210
  3. package/openclaw.plugin.json +1 -0
  4. package/package.json +11 -4
  5. package/setup-entry.ts +6 -0
  6. package/skills/openclaw-workclaw-cron/SKILL.md +45 -28
  7. package/src/accounts.ts +62 -37
  8. package/src/api/accounts-api.ts +88 -89
  9. package/src/api/prompts-api.ts +70 -77
  10. package/src/api/session-api.ts +99 -108
  11. package/src/api/skills-api.ts +35 -37
  12. package/src/api/workspace.ts +27 -29
  13. package/src/channel.ts +200 -202
  14. package/src/config-schema.ts +9 -9
  15. package/src/connection/workclaw-client.ts +554 -567
  16. package/src/gateway/agent-handlers.ts +392 -426
  17. package/src/gateway/config-writer.ts +228 -243
  18. package/src/gateway/message-context.ts +534 -362
  19. package/src/gateway/message-dispatcher.ts +529 -489
  20. package/src/gateway/reconnect.ts +217 -113
  21. package/src/gateway/skills-handler.ts +408 -472
  22. package/src/gateway/skills-list-handler.ts +9 -9
  23. package/src/gateway/tools-list-handler.ts +70 -72
  24. package/src/gateway/workclaw-gateway.ts +328 -486
  25. package/src/media/upload.ts +83 -94
  26. package/src/outbound/index.ts +57 -55
  27. package/src/outbound/workclaw-sender.ts +134 -133
  28. package/src/runtime.ts +291 -194
  29. package/src/send.ts +1 -1
  30. package/src/tools/openclaw-workclaw-cron/api/index.ts +6 -6
  31. package/src/tools/openclaw-workclaw-cron/src/add/params.ts +20 -19
  32. package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +2 -2
  33. package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +1 -1
  34. package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +3 -3
  35. package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +1 -1
  36. package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +3 -3
  37. package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +2 -2
  38. package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +1 -1
  39. package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +3 -3
  40. package/src/tools/openclaw-workclaw-cron/src/update/params.ts +195 -197
  41. package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +4 -4
  42. package/src/tools/openclaw-workclaw-system/src/get/index.ts +2 -2
  43. package/src/tools/openclaw-workclaw-system/src/token/index.ts +4 -4
  44. package/src/types.ts +38 -40
  45. package/src/utils/content.ts +16 -21
  46. package/tests/accounts.test.ts +285 -0
  47. package/tests/message-context.test.ts +313 -0
  48. package/tests/reconnect.test.ts +257 -0
  49. package/tests/workclaw-client.test.ts +112 -0
  50. package/tsconfig.json +8 -5
  51. package/vitest.config.ts +8 -0
package/src/runtime.ts CHANGED
@@ -1,400 +1,497 @@
1
- import type { PluginRuntime } from 'openclaw/plugin-sdk'
2
- import type { WebSocket } from 'undici'
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
- const connectingApps = new Set<string>() // 正在连接中的 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
+ // 正在连接中的 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 setOpenclawWorkclawRuntime(next: PluginRuntime): void {
17
- runtime = next
20
+ export function setWorkclawRuntime(next: PluginRuntime) {
21
+ runtime = next;
18
22
  }
19
23
 
20
- export function getOpenclawWorkclawRuntime(): PluginRuntime {
24
+ export function getWorkclawRuntime(): PluginRuntime {
21
25
  if (!runtime) {
22
- throw new Error('OpenclawWorkclaw runtime not initialized')
26
+ throw new Error("Workclaw runtime not initialized");
23
27
  }
24
- return runtime
28
+ return runtime;
25
29
  }
26
30
 
27
- export interface OpenclawWorkclawLogger {
28
- info: (msg: string, ...args: any[]) => void
29
- warn: (msg: string, ...args: any[]) => void
30
- error: (msg: string, ...args: any[]) => void
31
- debug: (msg: string, ...args: any[]) => void
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: OpenclawWorkclawLogger | null = null
38
+ let _logger: WorkclawLogger | null = null;
35
39
 
36
- function _noop(): void { }
40
+ const _noop = () => {};
37
41
 
38
- function _fallbackLogger(): OpenclawWorkclawLogger {
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 setOpenclawWorkclawLoggerFromContext(log: OpenclawWorkclawLogger): void {
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 getOpenclawWorkclawLogger(): OpenclawWorkclawLogger {
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?: any): OpenclawWorkclawLogger {
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 setOpenclawWorkclawWsConnection(key: string, ws: WebSocket): void {
81
- wsByKey.set(key, ws)
149
+ export function clearWorkclawConnectionConfig(accountId: string) {
150
+ connectionConfigCache.delete(accountId);
82
151
  }
83
152
 
84
- export function clearOpenclawWorkclawWsConnection(key: string): void {
85
- wsByKey.delete(key)
153
+ export function setLastOutboundAt(key: string, timestamp: number) {
154
+ lastOutboundAtByKey.set(key, timestamp);
86
155
  }
87
156
 
88
- export function getOpenclawWorkclawWsConnection(key: string): WebSocket | undefined {
89
- return wsByKey.get(key)
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): void {
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): void {
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): void {
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
- export function clearReconnectScheduler(appKey: string): void {
137
- reconnectSchedulers.delete(appKey)
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
- // 尝试开始连接,如果已经在连接中则返回 false
141
- export function tryStartConnecting(appKey: string): boolean {
142
- if (connectingApps.has(appKey)) {
143
- return false
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
- target: string
152
- replyToMessageId: string
153
- openConversationId: string
154
- accountId: string
155
- agentId: string
156
- sessionKey: string
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
- return runIdToToolContext.get(runId)
271
+ return runIdToToolContext.get(runId);
167
272
  }
168
273
 
169
274
  // 设置工具上下文(仅通过 runId)
170
275
  export function setToolContext(runId: string, ctx: ToolContext): void {
171
- runIdToToolContext.set(runId, ctx)
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
- toolCallIdToRunId.set(toolCallId, runId)
281
+ toolCallIdToRunId.set(toolCallId, runId);
177
282
  }
178
283
 
179
284
  // 通过 toolCallId 获取 runId
180
285
  export function getRunIdByToolCallId(toolCallId: string): string | undefined {
181
- return toolCallIdToRunId.get(toolCallId)
286
+ return toolCallIdToRunId.get(toolCallId);
182
287
  }
183
288
 
184
289
  // 删除工具上下文
185
290
  export function deleteToolContext(runId: string): void {
186
- runIdToToolContext.delete(runId)
291
+ runIdToToolContext.delete(runId);
187
292
  }
188
293
 
189
294
  // 清空所有工具上下文(用于清理)
190
295
  export function clearAllToolContexts(): void {
191
- runIdToToolContext.clear()
192
- toolCallIdToRunId.clear()
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, sessionKey?: string, summary?: string, error?: string }> {
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('OpenclawWorkclaw runtime not initialized')
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 !== 'function') {
313
+ const sessionsSpawn = (runtime as any)?.sessions_spawn;
314
+ if (!sessionsSpawn || typeof sessionsSpawn !== "function") {
215
315
  // 如果runtime没有提供sessions_spawn,返回模拟数据
216
- getOpenclawWorkclawLogger().warn(`Runtime does not provide sessions_spawn, returning mock response for agent ${params.agentId}`)
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
- catch (error: any) {
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
- 'read': { emoji: '📖', title: '读取文件', hint: '正在读取文件', aliases: ['file_read', 'read_file'] },
266
- 'write': { emoji: '✍️', title: '写入文件', hint: '正在写入文件', aliases: ['file_write', 'write_file'] },
267
- 'edit': { emoji: '✏️', title: '编辑文件', hint: '正在编辑文件', aliases: ['file_edit', 'modify'] },
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
- 'exec': { emoji: '', title: '执行命令', hint: '正在执行命令', aliases: ['bash', 'shell', 'cmd', 'command'] },
271
- 'process': { emoji: '🔄', title: '进程管理', hint: '正在处理进程', aliases: ['process_manager'] },
368
+ exec: { emoji: "", title: "执行命令", hint: "正在执行命令", aliases: ["bash", "shell", "cmd", "command"] },
369
+ process: { emoji: "🔄", title: "进程管理", hint: "正在处理进程", aliases: ["process_manager"] },
272
370
 
273
371
  // 🌐 网络/浏览器
274
- 'web_fetch': { emoji: '🕸️', title: '获取网页', hint: '正在获取网页内容', aliases: ['fetch', 'http_get', 'http_request', 'curl'] },
275
- 'web_search': { emoji: '🔍', title: '搜索网页', hint: '正在搜索网页', aliases: ['search', 'google_search', 'brave_search'] },
276
- 'browser': { emoji: '🌐', title: '浏览器控制', hint: '正在控制浏览器', aliases: ['browser_control', 'playwright'] },
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
- 'message': { emoji: '💌', title: '发送消息', hint: '正在发送消息', aliases: ['send_message', 'mcp_deliver', 'deliver', 'notify'] },
280
- 'tts': { emoji: '🔊', title: '文字转语音', hint: '正在转换语音', aliases: ['text_to_speech', 'speak'] },
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
- 'sessions_spawn': { emoji: '🧬', title: '创建子代理', hint: '正在创建子代理', aliases: ['spawn', 'create_agent', 'new_session'] },
284
- 'sessions_send': { emoji: '📤', title: '跨会话发消息', hint: '正在发送跨会话消息', aliases: ['send_to_session', 'forward'] },
285
- 'sessions_list': { emoji: '📋', title: '列出会话', hint: '正在列出会话', aliases: ['list_sessions'] },
286
- 'sessions_history': { emoji: '📜', title: '获取历史', hint: '正在获取历史消息', aliases: ['get_history', 'history'] },
287
- 'sessions_yield': { emoji: '⏸️', title: '暂停让出', hint: '正在暂停等待', aliases: ['yield', 'pause'] },
288
- 'subagents': { emoji: '🐛', title: '子代理管理', hint: '正在管理子代理', aliases: ['subagent_manager'] },
289
- 'agents_list': { emoji: '🤖', title: '列出代理', hint: '正在列出可用代理', aliases: ['list_agents'] },
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
- 'memory_search': { emoji: '🔎', title: '搜索记忆', hint: '正在搜索记忆', aliases: ['search_memory', 'recall'] },
293
- 'memory_get': { emoji: '📝', title: '读取记忆', hint: '正在读取记忆片段', aliases: ['get_memory', 'read_memory'] },
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
- 'openclaw-workclaw-cron-add-params': { emoji: '⏱️', title: '创建定时任务', hint: '正在创建定时任务', aliases: ['cron_add', 'add_timer'] },
297
- 'openclaw-workclaw-cron-update-params': { emoji: '🔧', title: '修改定时任务', hint: '正在修改定时任务', aliases: ['cron_update', 'update_timer'] },
298
- 'openclaw-workclaw-cron-remove-params': { emoji: '🗑️', title: '删除定时任务', hint: '正在删除定时任务', aliases: ['cron_remove', 'remove_timer'] },
299
- 'openclaw-workclaw-cron-notify-sync': { emoji: '🔔', title: '定时触发通知', hint: '定时任务已触发', aliases: ['cron_trigger', 'notify'] },
300
- 'openclaw-workclaw-cron-add-sync': { emoji: '', title: '同步定时任务', hint: '正在同步到后端', aliases: [] },
301
- 'openclaw-workclaw-cron-update-sync': { emoji: '', title: '同步定时更新', hint: '正在同步更新', aliases: [] },
302
- 'openclaw-workclaw-cron-remove-sync': { emoji: '', title: '同步定时删除', hint: '正在同步删除', aliases: [] },
303
- 'openclaw-workclaw-cron-disable-params': { emoji: '⏸️', title: '禁用定时任务', hint: '正在禁用定时任务', aliases: ['cron_disable'] },
304
- 'openclaw-workclaw-cron-enable-params': { emoji: '▶️', title: '启用定时任务', hint: '正在启用定时任务', aliases: ['cron_enable'] },
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
- 'session_status': { emoji: '📊', title: '会话状态', hint: '正在获取会话状态', aliases: ['status', 'get_status'] },
308
- 'canvas': { emoji: '🎨', title: '画布控制', hint: '正在控制画布', aliases: ['canvas_control', 'draw'] },
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 [_, tool] of Object.entries(toolMap)) {
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
- 'read': { emoji: '📖', title: '读取文件', hint: '已读取文件', aliases: ['file_read', 'read_file'] },
340
- 'write': { emoji: '✍️', title: '写入文件', hint: '已写入文件', aliases: ['file_write', 'write_file'] },
341
- 'edit': { emoji: '✏️', title: '编辑文件', hint: '已编辑文件', aliases: ['file_edit', 'modify'] },
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
- 'exec': { emoji: '', title: '执行命令', hint: '已执行命令', aliases: ['bash', 'shell', 'cmd', 'command'] },
345
- 'process': { emoji: '🔄', title: '进程管理', hint: '已处理进程', aliases: ['process_manager'] },
441
+ exec: { emoji: "", title: "执行命令", hint: "已执行命令", aliases: ["bash", "shell", "cmd", "command"] },
442
+ process: { emoji: "🔄", title: "进程管理", hint: "已处理进程", aliases: ["process_manager"] },
346
443
 
347
444
  // 🌐 网络/浏览器
348
- 'web_fetch': { emoji: '🕸️', title: '获取网页', hint: '已获取网页', aliases: ['fetch', 'http_get', 'http_request', 'curl'] },
349
- 'web_search': { emoji: '🔍', title: '搜索网页', hint: '已搜索网页', aliases: ['search', 'google_search', 'brave_search'] },
350
- 'browser': { emoji: '🌐', title: '浏览器控制', hint: '已控制浏览器', aliases: ['browser_control', 'playwright'] },
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
- 'message': { emoji: '💌', title: '发送消息', hint: '已发送消息', aliases: ['send_message', 'mcp_deliver', 'deliver', 'notify'] },
354
- 'tts': { emoji: '🔊', title: '文字转语音', hint: '已转换语音', aliases: ['text_to_speech', 'speak'] },
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
- 'sessions_spawn': { emoji: '🧬', title: '创建子代理', hint: '已创建子代理', aliases: ['spawn', 'create_agent', 'new_session'] },
358
- 'sessions_send': { emoji: '📤', title: '跨会话发消息', hint: '已发送跨会话消息', aliases: ['send_to_session', 'forward'] },
359
- 'sessions_list': { emoji: '📋', title: '列出会话', hint: '已列出会话', aliases: ['list_sessions'] },
360
- 'sessions_history': { emoji: '📜', title: '获取历史', hint: '已获取历史消息', aliases: ['get_history', 'history'] },
361
- 'sessions_yield': { emoji: '⏸️', title: '暂停让出', hint: '已暂停等待', aliases: ['yield', 'pause'] },
362
- 'subagents': { emoji: '🐛', title: '子代理管理', hint: '已管理子代理', aliases: ['subagent_manager'] },
363
- 'agents_list': { emoji: '🤖', title: '列出代理', hint: '已列出代理', aliases: ['list_agents'] },
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
- 'memory_search': { emoji: '🔎', title: '搜索记忆', hint: '已搜索记忆', aliases: ['search_memory', 'recall'] },
367
- 'memory_get': { emoji: '📝', title: '读取记忆', hint: '已读取记忆', aliases: ['get_memory', 'read_memory'] },
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
- 'openclaw-workclaw-cron-add-params': { emoji: '⏱️', title: '创建定时任务', hint: '已创建定时任务', aliases: ['cron_add', 'add_timer'] },
371
- 'openclaw-workclaw-cron-update-params': { emoji: '🔧', title: '修改定时任务', hint: '已修改定时任务', aliases: ['cron_update', 'update_timer'] },
372
- 'openclaw-workclaw-cron-remove-params': { emoji: '🗑️', title: '删除定时任务', hint: '已删除定时任务', aliases: ['cron_remove', 'remove_timer'] },
373
- 'openclaw-workclaw-cron-notify-sync': { emoji: '🔔', title: '定时触发通知', hint: '定时任务已触发', aliases: ['cron_trigger', 'notify'] },
374
- 'openclaw-workclaw-cron-add-sync': { emoji: '', title: '同步定时任务', hint: '已同步定时任务', aliases: [] },
375
- 'openclaw-workclaw-cron-update-sync': { emoji: '', title: '同步定时更新', hint: '已同步更新', aliases: [] },
376
- 'openclaw-workclaw-cron-remove-sync': { emoji: '', title: '同步定时删除', hint: '已同步删除', aliases: [] },
377
- 'openclaw-workclaw-cron-disable-params': { emoji: '⏸️', title: '禁用定时任务', hint: '已禁用定时任务', aliases: ['cron_disable'] },
378
- 'openclaw-workclaw-cron-enable-params': { emoji: '▶️', title: '启用定时任务', hint: '已启用定时任务', aliases: ['cron_enable'] },
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
- 'session_status': { emoji: '📊', title: '会话状态', hint: '已获取会话状态', aliases: ['status', 'get_status'] },
382
- 'canvas': { emoji: '🎨', title: '画布控制', hint: '已控制画布', aliases: ['canvas_control', 'draw'] },
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 [_, tool] of Object.entries(toolMap)) {
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
+ }