@dcrays/dcgchat-test 0.6.12 → 0.7.0

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 (60) hide show
  1. package/index.js +306 -0
  2. package/openclaw.plugin.json +42 -12
  3. package/package.json +8 -35
  4. package/README.md +0 -167
  5. package/index.ts +0 -35
  6. package/src/agent.ts +0 -568
  7. package/src/bot.ts +0 -646
  8. package/src/channel/index.ts +0 -329
  9. package/src/channel/outboundMedia.ts +0 -144
  10. package/src/channel/outboundTarget.ts +0 -62
  11. package/src/channel/uploadMediaUrl.ts +0 -76
  12. package/src/cron/message.ts +0 -114
  13. package/src/cron/params.ts +0 -164
  14. package/src/cron/toolGuard.ts +0 -466
  15. package/src/cron/types.ts +0 -15
  16. package/src/gateway/index.ts +0 -430
  17. package/src/gateway/security.ts +0 -95
  18. package/src/gateway/socket.ts +0 -256
  19. package/src/libs/ali-oss-6.23.0.tgz +0 -0
  20. package/src/libs/axios-1.13.6.tgz +0 -0
  21. package/src/libs/md5-2.3.0.tgz +0 -0
  22. package/src/libs/mime-types-3.0.2.tgz +0 -0
  23. package/src/libs/unzipper-0.12.3.tgz +0 -0
  24. package/src/libs/ws-8.19.0.tgz +0 -0
  25. package/src/monitor.ts +0 -269
  26. package/src/request/api.ts +0 -80
  27. package/src/request/oss.ts +0 -200
  28. package/src/request/request.ts +0 -191
  29. package/src/request/userInfo.ts +0 -44
  30. package/src/session.ts +0 -28
  31. package/src/skill.ts +0 -114
  32. package/src/tool.ts +0 -603
  33. package/src/tools/messageTool.ts +0 -334
  34. package/src/tools/toolCallGuard.ts +0 -327
  35. package/src/transport.ts +0 -217
  36. package/src/types.ts +0 -139
  37. package/src/utils/agentErrors.ts +0 -116
  38. package/src/utils/constant.ts +0 -60
  39. package/src/utils/env-config.ts +0 -19
  40. package/src/utils/formatLlmInputEvent.ts +0 -48
  41. package/src/utils/gatewayMsgHandler.ts +0 -147
  42. package/src/utils/global.ts +0 -309
  43. package/src/utils/inboundTurnState.ts +0 -66
  44. package/src/utils/log.ts +0 -77
  45. package/src/utils/mediaAttached.ts +0 -244
  46. package/src/utils/mediaEmitter.ts +0 -54
  47. package/src/utils/outboundAssistantText.ts +0 -117
  48. package/src/utils/params.ts +0 -104
  49. package/src/utils/passTxt.ts +0 -4
  50. package/src/utils/resolveRegisterConfig.ts +0 -33
  51. package/src/utils/searchFile.ts +0 -228
  52. package/src/utils/sessionState.ts +0 -137
  53. package/src/utils/sessionTermination.ts +0 -228
  54. package/src/utils/streamMerge.ts +0 -150
  55. package/src/utils/subagentRunMap.ts +0 -71
  56. package/src/utils/undiciFetchInterceptor.ts +0 -346
  57. package/src/utils/workspaceFilePaths.ts +0 -18
  58. package/src/utils/wsMessageHandler.ts +0 -124
  59. package/src/utils/zipExtract.ts +0 -97
  60. package/src/utils/zipPath.ts +0 -24
package/src/agent.ts DELETED
@@ -1,568 +0,0 @@
1
- import axios from 'axios'
2
- import fs from 'fs'
3
- import fsPromise from 'fs/promises'
4
- import path from 'path'
5
- import type { OpenClawConfig } from 'openclaw/plugin-sdk'
6
- import { getDcgchatRuntime, getOpenClawConfig, getWorkspaceDir, getWsConnection, setOpenClawConfig } from './utils/global.js'
7
- import { dcgLogger } from './utils/log.js'
8
- import { isWsOpen } from './transport.js'
9
- import { sendGatewayRpc } from './gateway/socket.js'
10
- import { extractZipBufferToDirectory } from './utils/zipExtract.js'
11
- import { retryWithBackoff, ENV } from './utils/constant.js'
12
-
13
- interface AgentEventMessage {
14
- url?: string
15
- clone_code?: string
16
- clone_codes?: string[]
17
- code_list?: string[]
18
- remove_files?: string[]
19
- name?: string
20
- description?: string
21
- status?: string
22
- [key: string]: unknown
23
- }
24
-
25
- function sendEvent(msgContent: AgentEventMessage) {
26
- const ws = getWsConnection()
27
- if (isWsOpen()) {
28
- ws?.send(
29
- JSON.stringify({
30
- messageType: 'openclaw_bot_event',
31
- source: 'client',
32
- content: msgContent
33
- })
34
- )
35
- }
36
- }
37
-
38
- /**
39
- * 异步版本,避免阻塞事件循环
40
- * workspace-${clone_code}/agent 存在,则复制到 agents/${clone_code}/agent
41
- * @param clone_code
42
- * @returns
43
- */
44
- async function copyAgentsFilesAsync(clone_code: string) {
45
- const workspacePath = getWorkspaceDir()
46
-
47
- if (!workspacePath) return
48
- const workspaceDir = path.join(workspacePath, '../', `workspace-${clone_code}`)
49
- const agentDir = path.join(workspacePath, '../', `agents/${clone_code}`)
50
- const sourceAgent = path.join(workspaceDir, 'agent')
51
-
52
- try {
53
- const stat = await fsPromise.stat(sourceAgent).catch(() => null)
54
- if (!stat || !stat.isDirectory()) return
55
-
56
- await fsPromise.mkdir(agentDir, { recursive: true })
57
-
58
- const dest = path.join(agentDir, 'agent')
59
-
60
- await fsPromise.rm(dest, { recursive: true, force: true }).catch((e) => dcgLogger(`agent workspace cleanup failed: ${String(e)}`))
61
-
62
- await fsPromise.cp(sourceAgent, dest, { recursive: true })
63
- } catch (err: unknown) {
64
- dcgLogger(`copyAgentsFilesAsync failed: ${String(err)}`, 'error')
65
- }
66
- }
67
-
68
- /**
69
- * Gateway 已写盘后,把 Runtime 当前配置同步到插件全局,避免 getOpenClawConfig() 仍是 register 时的旧快照。
70
- * 专家会话若在缓存里没有 agents.list 条目,OpenClaw 会把 workspace 解析成 defaults.workspace/<id>,
71
- * 与插件实际的 sibling `workspace-<id>` 不一致,必须先刷新或通过 agents.update 纠正。
72
- */
73
- export function refreshCachedOpenClawConfigFromRuntime(): void {
74
- try {
75
- const cfg = getDcgchatRuntime().config.loadConfig()
76
- setOpenClawConfig(structuredClone(cfg) as OpenClawConfig)
77
- } catch (err: unknown) {
78
- dcgLogger(`refreshCachedOpenClawConfigFromRuntime failed: ${String(err)}`, 'error')
79
- }
80
- }
81
-
82
- function findAgentEntry(cfg: OpenClawConfig | null, agentId: string) {
83
- const needle = agentId.trim().toLowerCase()
84
- return cfg?.agents?.list?.find((a) => (a?.id ?? '').trim().toLowerCase() === needle)
85
- }
86
-
87
- function workspacePathMatches(expected: string, configured?: string): boolean {
88
- const w = configured?.trim()
89
- if (!w) return false
90
- return path.resolve(w) === expected
91
- }
92
-
93
- /**
94
- * 确保网关配置里该专家的 workspace 指向 sibling `workspace-<id>`(与 agents.create / 解压目录一致)。
95
- * 在首次会话消息早于插件缓存刷新、或 workspace 字段缺失/错误时修复路径。
96
- */
97
- export async function ensureExpertAgentWorkspaceBinding(agentId: string): Promise<void> {
98
- const id = typeof agentId === 'string' ? agentId.trim() : ''
99
- if (!id || id === 'main') return
100
-
101
- refreshCachedOpenClawConfigFromRuntime()
102
-
103
- const wsRoot = getWorkspaceDir()
104
- if (!wsRoot) return
105
-
106
- const expectedWorkspace = path.resolve(path.join(wsRoot, '..', `workspace-${id}`))
107
- try {
108
- await fsPromise.access(expectedWorkspace)
109
- } catch (err) {
110
- const errMsg = err instanceof Error ? err.message : String(err)
111
- dcgLogger(`ensureExpertAgentWorkspaceBinding workspace access failed: ${errMsg}`, 'error')
112
- return
113
- }
114
-
115
- let cfg = getOpenClawConfig()
116
- let entry = findAgentEntry(cfg, id)
117
-
118
- if (entry && workspacePathMatches(expectedWorkspace, entry.workspace)) return
119
-
120
- if (entry && !workspacePathMatches(expectedWorkspace, entry.workspace)) {
121
- try {
122
- await sendGatewayRpc({
123
- method: 'agents.update',
124
- params: { agentId: id, workspace: expectedWorkspace }
125
- })
126
- refreshCachedOpenClawConfigFromRuntime()
127
- } catch (err: unknown) {
128
- dcgLogger(`ensureExpertAgentWorkspaceBinding agents.update failed after retries: ${String(err)}`, 'error')
129
- }
130
- return
131
- }
132
-
133
- try {
134
- await sendGatewayRpc({
135
- method: 'agents.create',
136
- params: { name: id, workspace: expectedWorkspace }
137
- })
138
- refreshCachedOpenClawConfigFromRuntime()
139
- } catch (err: unknown) {
140
- const msg = String(err)
141
- if (/already exists/i.test(msg)) {
142
- refreshCachedOpenClawConfigFromRuntime()
143
- cfg = getOpenClawConfig()
144
- entry = findAgentEntry(cfg, id)
145
- if (entry && !workspacePathMatches(expectedWorkspace, entry.workspace)) {
146
- try {
147
- await sendGatewayRpc({
148
- method: 'agents.update',
149
- params: { agentId: id, workspace: expectedWorkspace }
150
- })
151
- refreshCachedOpenClawConfigFromRuntime()
152
- } catch (e2: unknown) {
153
- dcgLogger(`ensureExpertAgentWorkspaceBinding repair workspace: ${String(e2)}`, 'error')
154
- }
155
- }
156
- } else {
157
- dcgLogger(`ensureExpertAgentWorkspaceBinding agents.create: ${msg}`, 'error')
158
- }
159
- }
160
- }
161
-
162
- async function onCreateAgent(params: AgentEventMessage) {
163
- const { clone_code, name } = params
164
- if (!clone_code) {
165
- dcgLogger('onCreateAgent: clone_code is missing', 'error')
166
- return
167
- }
168
- let createOk = false
169
- try {
170
- const workspacePath = getWorkspaceDir()
171
- if (!workspacePath) return
172
- const workspaceDir = path.join(workspacePath, '../', `workspace-${clone_code}`)
173
-
174
- // 如果尝试创建一个已存在的 agent,会报错。 agents.create failed: Error: agent "reviser-expert-11-2-37-16" already exists
175
- await sendGatewayRpc({ method: 'agents.create', params: { name: clone_code, workspace: workspaceDir } })
176
-
177
- createOk = true
178
- } catch (err: unknown) {
179
- dcgLogger(`agents.create failed: ${String(err)}`, 'error')
180
- }
181
- // Update config.name to the user-supplied display name (may contain CJK, spaces, etc.)
182
- try {
183
- await sendGatewayRpc({ method: 'agents.update', params: { name: name, agentId: clone_code } })
184
- } catch (err: unknown) {
185
- dcgLogger(`agents.update failed: ${String(err)}`, 'error')
186
- }
187
- if (createOk) {
188
- refreshCachedOpenClawConfigFromRuntime()
189
- }
190
-
191
- await copyAgentsFilesAsync(clone_code)
192
- sendEvent({ ...params, status: 'ok' })
193
- }
194
-
195
- // 校验Agent id 对应的文件目录是否存在
196
- export function isAgentInstalled(agentId: string): boolean {
197
- const workspacePath = getWorkspaceDir()
198
- if (!workspacePath) return false
199
- const agentWorkspaceDir = path.join(workspacePath, '../', `workspace-${agentId}`)
200
- return fs.existsSync(agentWorkspaceDir)
201
- }
202
- export async function onRemoveAgent(msgContent: AgentEventMessage) {
203
- const { clone_code } = msgContent
204
- if (!clone_code) {
205
- dcgLogger('onRemoveAgent: clone_code is missing', 'error')
206
- return
207
- }
208
- clearAgentDownloadUrl(clone_code)
209
- await removeAgentDownloadUrlMeta(clone_code)
210
- await sendGatewayRpc({ method: 'agents.delete', params: { agentId: clone_code } })
211
- }
212
- export async function onUpdateAgent(msgContent: AgentEventMessage) {
213
- const { code_list, url, remove_files } = msgContent
214
- const list = (code_list || []).filter((item): item is string => typeof item === 'string' && Boolean(item.trim()))
215
- const removeFiles = (remove_files || []).filter((item): item is string => typeof item === 'string' && Boolean(item.trim()))
216
- if (!list?.length) {
217
- dcgLogger('onUpdateAgent: clone_codes is missing', 'error')
218
- return
219
- }
220
- if (!url) {
221
- dcgLogger('onUpdateAgent: url is missing', 'error')
222
- sendEvent({ ...msgContent, status: 'fail' })
223
- return
224
- }
225
- const workspacePath = getWorkspaceDir()
226
- const tempDir = path.join(workspacePath, '../', `-temp-${Date.now()}`)
227
- try {
228
- const response = await axios({ method: 'get', url, responseType: 'arraybuffer' })
229
- const zipBuffer = Buffer.from(response.data)
230
-
231
- if (fs.existsSync(tempDir)) {
232
- fs.rmSync(tempDir, { recursive: true, force: true })
233
- }
234
- fs.mkdirSync(tempDir, { recursive: true })
235
-
236
- await extractZipBufferToDirectory(zipBuffer, tempDir)
237
- const results = await Promise.all(
238
- list.map(async (clone_code) => {
239
- const workspaceDir = path.join(workspacePath, '../', `workspace-${clone_code}`)
240
- let installSucceeded = false
241
- markAgentInstallStarted(clone_code)
242
- try {
243
- if (!fs.existsSync(workspaceDir)) {
244
- return true
245
- }
246
- const overwriteFilesRecursively = (src: string, dest: string) => {
247
- const entries = fs.readdirSync(src, { withFileTypes: true })
248
- for (const entry of entries) {
249
- const srcPath = path.join(src, entry.name)
250
- const destPath = path.join(dest, entry.name)
251
- if (entry.isDirectory()) {
252
- if (!fs.existsSync(destPath)) {
253
- fs.mkdirSync(destPath, { recursive: true })
254
- }
255
- overwriteFilesRecursively(srcPath, destPath)
256
- } else {
257
- if (fs.existsSync(destPath)) {
258
- fs.rmSync(destPath, { force: true })
259
- }
260
- fs.copyFileSync(srcPath, destPath)
261
- }
262
- }
263
- }
264
- overwriteFilesRecursively(tempDir, workspaceDir)
265
- removeFiles.forEach((file) => {
266
- const filePath = path.join(workspaceDir, file)
267
- if (fs.existsSync(filePath)) {
268
- fs.rmSync(filePath, { recursive: true, force: true })
269
- }
270
- })
271
- await sendGatewayRpc({ method: 'agents.update', params: { agentId: clone_code } })
272
- installSucceeded = true
273
- return true
274
- } catch (error: unknown) {
275
- const errMsg = error instanceof Error ? error.message : String(error)
276
- dcgLogger(`onUpdateAgent error: clone_code=${clone_code}, workspaceDir=${workspaceDir}, error=${errMsg}`, 'error')
277
- return false
278
- } finally {
279
- markAgentInstallFinished(clone_code, installSucceeded)
280
- }
281
- })
282
- )
283
- sendEvent({ ...msgContent, status: results.every(Boolean) ? 'ok' : 'fail' })
284
- } catch (err: unknown) {
285
- dcgLogger(`onUpdateAgent download failed: ${String(err)}`, 'error')
286
- sendEvent({ ...msgContent, status: 'fail' })
287
- } finally {
288
- fs.rmSync(tempDir, { recursive: true, force: true })
289
- }
290
- }
291
- export async function createAgent(msgContent: AgentEventMessage) {
292
- const { url, clone_code, name, description } = msgContent
293
-
294
- if (!url || !clone_code) {
295
- dcgLogger(`createAgent failed empty url&clone_code: ${JSON.stringify(msgContent)}`, 'error')
296
- sendEvent({ ...msgContent, status: 'fail' })
297
- return
298
- }
299
- // 缓存下载 URL,供消息处理流程中检测到 agent 缺失时重试下载
300
- cacheAgentDownloadUrl(clone_code, url, name, description)
301
-
302
- const workspacePath = getWorkspaceDir()
303
- const workspaceDir = path.join(workspacePath, '../', `workspace-${clone_code}`)
304
-
305
- // 如果目标目录已存在,先删除(避免残留文件干扰新安装)
306
- if (fs.existsSync(workspaceDir)) {
307
- fs.rmSync(workspaceDir, { recursive: true, force: true })
308
- }
309
-
310
- markAgentInstallStarted(clone_code)
311
- let installSucceeded = false
312
- try {
313
- // 通过 retryWithBackoff 最多尝试 3 次下载+安装
314
- await retryWithBackoff(
315
- () => downloadAndSetupAgent(url, clone_code, name, description),
316
- 3
317
- )
318
- installSucceeded = true
319
- } catch (error) {
320
- dcgLogger(`createAgent error: ${error}`, 'error')
321
- sendEvent({ ...msgContent, status: 'fail' })
322
- } finally {
323
- markAgentInstallFinished(clone_code, installSucceeded)
324
- }
325
- }
326
-
327
- /** 正以 clone_code 安装中的专家 workspace(解压 + 网关注册整段流程) */
328
- const installingAgentCloneCodes = new Set<string>()
329
- const installCompleteWaitersByCloneCode = new Map<string, Array<(success: boolean) => void>>()
330
-
331
- function markAgentInstallStarted(cloneCode: string): void {
332
- const id = typeof cloneCode === 'string' ? cloneCode.trim() : ''
333
- if (!id) return
334
- installingAgentCloneCodes.add(id)
335
- }
336
-
337
- function notifyInstallWaiters(cloneCode: string, success: boolean): void {
338
- const waiters = installCompleteWaitersByCloneCode.get(cloneCode)
339
- if (waiters) {
340
- for (const r of waiters) {
341
- r(success)
342
- }
343
- installCompleteWaitersByCloneCode.delete(cloneCode)
344
- }
345
- }
346
-
347
- function markAgentInstallFinished(cloneCode: string, success: boolean): void {
348
- const id = typeof cloneCode === 'string' ? cloneCode.trim() : ''
349
- if (!id) return
350
- installingAgentCloneCodes.delete(id)
351
- notifyInstallWaiters(id, success)
352
- }
353
-
354
- export function isAgentInstalling(cloneCode: string): boolean {
355
- const id = typeof cloneCode === 'string' ? cloneCode.trim() : ''
356
- if (!id) return false
357
- return installingAgentCloneCodes.has(id)
358
- }
359
-
360
- /** 等待当前安装流程结束;若未在安装中则立即返回是否已安装成功 */
361
- export function waitForAgentInstall(cloneCode: string): Promise<boolean> {
362
- const id = typeof cloneCode === 'string' ? cloneCode.trim() : ''
363
- if (!id) return Promise.resolve(false)
364
- if (!installingAgentCloneCodes.has(id)) {
365
- return Promise.resolve(isAgentInstalled(id))
366
- }
367
- return new Promise((resolve) => {
368
- let list = installCompleteWaitersByCloneCode.get(id)
369
- if (!list) {
370
- list = []
371
- installCompleteWaitersByCloneCode.set(id, list)
372
- }
373
- list.push(resolve)
374
- })
375
- }
376
-
377
- /* ── Agent 下载 URL 缓存与消息触发重试 ── */
378
- // createAgent 收到 WebSocket 事件时缓存 url/cloneCode,消息处理流程检测到
379
- // agent 文件缺失时通过 retryDownloadAgent 利用缓存的 URL 重试一次下载。
380
- // agentRetryFailed 保证每个 cloneCode 在消息流程中只重试一次,避免无限重试。
381
-
382
- const agentDownloadUrls = new Map<string, { url: string; name?: string; description?: string }>()
383
- const agentRetryFailed = new Set<string>()
384
-
385
- function cacheAgentDownloadUrl(
386
- cloneCode: string,
387
- url: string,
388
- name?: string,
389
- description?: string
390
- ): void {
391
- agentDownloadUrls.set(cloneCode, { url, name, description })
392
- }
393
-
394
- function clearAgentDownloadUrl(cloneCode: string): void {
395
- agentDownloadUrls.delete(cloneCode)
396
- agentRetryFailed.delete(cloneCode)
397
- }
398
-
399
- /* ── 在 .mobook-agents-donwload-url-cache.json 文件中缓存 Agents 下载的 url,用于重新加载 */
400
- const AGENTS_DONWNLOAD_URL_CACHE_FILE = '.mobook-agents-donwload-url-cache.json'
401
-
402
- interface AgentMeta {
403
- url: string
404
- name?: string
405
- description?: string
406
- }
407
-
408
- interface AgentsCache {
409
- version: number
410
- agents: Record<string, Record<string, AgentMeta>>
411
- }
412
-
413
- let _agentsCache: AgentsCache | null = null
414
-
415
- function getAgentsDownloadUrlCachePath(): string | null {
416
- const workspacePath = getWorkspaceDir()
417
- if (!workspacePath) return null
418
- return path.join(workspacePath, AGENTS_DONWNLOAD_URL_CACHE_FILE)
419
- }
420
-
421
- async function loadAgentsDownloadUrlCache(): Promise<AgentsCache> {
422
- if (_agentsCache) return _agentsCache
423
-
424
- const cachePath = getAgentsDownloadUrlCachePath()
425
- if (!cachePath) return { version: 1, agents: {} }
426
-
427
- try {
428
- const raw = await fsPromise.readFile(cachePath, 'utf-8')
429
- const parsed = JSON.parse(raw) as AgentsCache
430
- if (typeof parsed.version === 'number' && parsed.agents && typeof parsed.agents === 'object') {
431
- _agentsCache = parsed
432
- return parsed
433
- }
434
- } catch(error) {
435
- if ((error as { code?: string })?.code !== 'ENOENT') {
436
- dcgLogger(`loadAgentsCache error: ${JSON.stringify(error)}`)
437
- }
438
- }
439
-
440
- /* 文件不存在或解析失败,返回空缓存 */
441
- return { version: 1, agents: {} }
442
- }
443
-
444
- async function saveAgentsDownloadUrl(cache: AgentsCache): Promise<void> {
445
- const cachePath = getAgentsDownloadUrlCachePath()
446
-
447
- if (!cachePath) return
448
-
449
- try {
450
- // 先写 .tmp 再 rename,避免写入中断导致文件损坏
451
- const tempPath = `${cachePath}.tmp`
452
- await fsPromise.writeFile(tempPath, JSON.stringify(cache, null, 2), 'utf-8')
453
- await fsPromise.rename(tempPath, cachePath)
454
- _agentsCache = cache
455
- } catch (err) {
456
- dcgLogger(`saveAgentsCache failed: ${String(err)}`, 'error')
457
- }
458
- }
459
-
460
- async function writeAgentsDownloadUrlMeta(cloneCode: string, meta: AgentMeta): Promise<void> {
461
- const cache = await loadAgentsDownloadUrlCache()
462
- if (!cache.agents[ENV]) {
463
- cache.agents[ENV] = {}
464
- }
465
- cache.agents[ENV][cloneCode] = meta
466
- await saveAgentsDownloadUrl(cache)
467
- }
468
-
469
- async function readAgentDownloadUrlMeta(cloneCode: string): Promise<AgentMeta | null> {
470
- const cache = await loadAgentsDownloadUrlCache()
471
- return cache.agents[ENV]?.[cloneCode] ?? null
472
- }
473
-
474
- async function removeAgentDownloadUrlMeta(cloneCode: string): Promise<void> {
475
- const cache = await loadAgentsDownloadUrlCache()
476
- const envAgents = cache.agents[ENV]
477
- if (!envAgents || !envAgents[cloneCode]) return
478
- delete envAgents[cloneCode]
479
- if (Object.keys(envAgents).length === 0) {
480
- delete cache.agents[ENV]
481
- }
482
- await saveAgentsDownloadUrl(cache)
483
- }
484
-
485
- /**
486
- * 核心下载+安装逻辑:单次 axios 请求,下载 zip、解压、调用 onCreateAgent 注册。
487
- * 失败时自动清理目录并抛出异常,供上层 retryWithBackoff 捕获重试。
488
- * @returns 下载的字节数
489
- */
490
- async function downloadAndSetupAgent(
491
- url: string,
492
- cloneCode: string,
493
- name?: string,
494
- description?: string
495
- ): Promise<number> {
496
- const workspacePath = getWorkspaceDir()
497
- const workspaceDir = path.join(workspacePath, '../', `workspace-${cloneCode}`)
498
-
499
- // 每次调用时先清理残留目录,保证重试时环境干净
500
- if (fs.existsSync(workspaceDir)) {
501
- fs.rmSync(workspaceDir, { recursive: true, force: true })
502
- }
503
-
504
- try {
505
- // 下载 agent file stream
506
- const response = await axios({ method: 'get', url, responseType: 'arraybuffer' })
507
-
508
- fs.mkdirSync(workspaceDir, { recursive: true })
509
- await extractZipBufferToDirectory(Buffer.from(response.data), workspaceDir)
510
-
511
- await onCreateAgent({ url, clone_code: cloneCode, name, description })
512
- await writeAgentsDownloadUrlMeta(cloneCode, { url, name, description })
513
- return response.data?.length ?? 0
514
- } catch (error) {
515
- dcgLogger(`downloadAndSetupAgent error: ${error}`);
516
- const resolvedCleanDir = path.resolve(workspaceDir)
517
- if (fs.existsSync(resolvedCleanDir) && resolvedCleanDir !== '/') {
518
- fs.rmSync(resolvedCleanDir, { recursive: true, force: true })
519
- }
520
- throw error
521
- }
522
- }
523
-
524
- /**
525
- * 在消息处理流程中,对之前下载失败的 agent 尝试一次重新下载。
526
- * 依赖 createAgent 缓存的 URL,每个 cloneCode 在生命周期内只尝试一次。
527
- * @returns 下载并注册成功返回 true;失败或已重试过返回 false
528
- */
529
- export async function retryDownloadAgent(cloneCode: string): Promise<boolean> {
530
- const id = typeof cloneCode === 'string' ? cloneCode.trim() : ''
531
- if (!id) return false
532
-
533
- let cached = agentDownloadUrls.get(id)
534
-
535
- // 内存未命中 → 尝试从 workspace 缓存文件中恢复
536
- if (!cached) {
537
- const meta = await readAgentDownloadUrlMeta(id)
538
- if (meta) {
539
- cached = meta
540
- agentDownloadUrls.set(id, cached)
541
- }
542
- }
543
-
544
- if (!cached) {
545
- return false
546
- }
547
-
548
- if (agentRetryFailed.has(id)) {
549
- return false
550
- }
551
-
552
- try {
553
-
554
- // 重新下载之前,先发送 "agents.delete" rpc, 避免下载成功之后, 发送 'agents.create' rpc 报错
555
- await sendGatewayRpc({ method: 'agents.delete', params: { agentId: cloneCode } })
556
-
557
- // 重新下载文件
558
- await downloadAndSetupAgent(cached.url, id, cached.name, cached.description)
559
-
560
- agentRetryFailed.delete(id)
561
- return true
562
- } catch (error) {
563
- const errMsg = error instanceof Error ? error.message : String(error)
564
- dcgLogger(`retryDownloadAgent error: ${id}, error=${errMsg}`, 'error')
565
- agentRetryFailed.add(id)
566
- return false
567
- }
568
- }