@dcrays/dcgchat-test 0.6.12 → 0.6.13

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 +305 -0
  2. package/openclaw.plugin.json +41 -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
@@ -1,466 +0,0 @@
1
- import { CHANNEL_ID } from '../utils/constant.js'
2
- import type { CronJobLike } from './types.js'
3
- /**
4
- * cron-delivery-guard — 定时任务投递守护插件
5
- *
6
- * 核心机制:通过 before_tool_call 钩子拦截 cron 工具调用,
7
- * 对 DCG 创建的 cron 统一注入会话、执行、payload 和 delivery 业务字段,
8
- * 并在模型生成不完整或冲突配置时强制规范化。
9
- *
10
- * 背景:
11
- * - 创建前尽量让 OpenClaw 收到完整合法的 cron 配置
12
- */
13
-
14
- import { dcgLogger } from '../utils/log.js'
15
-
16
- const LOG_TAG = 'cron-delivery-guard'
17
- const DCG_CRON_TZ = 'Asia/Shanghai'
18
- const CRON_PAYLOAD_TIMEOUT_SECONDS = 600
19
- const DEFAULT_CRON_MESSAGE = '请按定时任务要求提醒用户。'
20
-
21
- type CronExecSegment = { cronIndex: number; segmentStartIndex: number; segmentEndIndex: number }
22
-
23
- /**
24
- * 定位 exec 命令中合法的 `cron create/add` 片段。
25
- * 仅接受段首 `cron create/add`,或 `openclaw cron create/add`,避免误匹配 `echo cron create` 等。
26
- */
27
- function findCronCreateExecSegment(tokens: ShellToken[]): CronExecSegment | null {
28
- const cronIndex = tokens.findIndex(
29
- (token, index) =>
30
- !token.control &&
31
- token.value.toLowerCase() === 'cron' &&
32
- ['create', 'add'].includes(tokens[index + 1]?.value.toLowerCase() ?? '')
33
- )
34
- if (cronIndex < 0) return null
35
-
36
- const prev = tokens[cronIndex - 1]
37
- const atSegmentStart = cronIndex === 0 || prev?.control === true
38
- const hasOpenclawPrefix = !prev?.control && prev?.value.toLowerCase() === 'openclaw'
39
- if (!atSegmentStart && !hasOpenclawPrefix) return null
40
-
41
- const segmentStartIndex = hasOpenclawPrefix ? cronIndex - 1 : cronIndex
42
- let segmentEndIndex = cronIndex + 2
43
- while (segmentEndIndex < tokens.length && !tokens[segmentEndIndex]?.control) segmentEndIndex += 1
44
-
45
- return { cronIndex, segmentStartIndex, segmentEndIndex }
46
- }
47
-
48
- export function isCronCreateExecCommand(command: unknown): boolean {
49
- if (typeof command !== 'string') return false
50
- const tokens = tokenizeShell(command.trim())
51
- return tokens !== null && findCronCreateExecSegment(tokens) !== null
52
- }
53
-
54
- function trimCronString(value: unknown): string {
55
- return typeof value === 'string' ? value.trim() : ''
56
- }
57
-
58
- export function formatterSessionKey(sessionKey: string): { agentId: string; sessionId: string } {
59
- const parts = sessionKey.split(':').filter((part) => part.length > 0)
60
- const normalized = parts.map((part) => part.toLowerCase())
61
- if (parts.length >= 6 && normalized[0] === 'agent' && normalized[2] === 'mobook' && normalized[3] === 'direct') {
62
- return {
63
- agentId: parts[4] ?? '',
64
- sessionId: parts[5] ?? ''
65
- }
66
- }
67
- if (parts.length >= 2) {
68
- return {
69
- agentId: parts[parts.length - 2] ?? '',
70
- sessionId: parts[parts.length - 1] ?? ''
71
- }
72
- }
73
- return { agentId: '', sessionId: '' }
74
- }
75
-
76
- function resolveCronMessage(job: CronJobLike): string {
77
- const payload = job.payload
78
- return (
79
- trimCronString(payload?.message) ||
80
- trimCronString(payload?.text) ||
81
- trimCronString(job.message) ||
82
- trimCronString(job.text) ||
83
- trimCronString(job.description) ||
84
- trimCronString(job.name) ||
85
- DEFAULT_CRON_MESSAGE
86
- )
87
- }
88
-
89
- export function buildDcgCronJob(job: CronJobLike, sessionKey: string): CronJobLike {
90
- const sk = sessionKey.trim()
91
- const { agentId } = formatterSessionKey(sk)
92
- const delivery: Record<string, unknown> = {
93
- ...(job.delivery ?? {}),
94
- mode: 'announce',
95
- channel: CHANNEL_ID,
96
- to: `dcg-cron:${sk}`,
97
- bestEffort: true
98
- }
99
- if (agentId) delivery.accountId = agentId
100
-
101
- const sourcePayload = job.payload ?? {}
102
- const payload: Record<string, unknown> = { ...sourcePayload }
103
- const payloadKind = trimCronString(sourcePayload.kind)
104
- const hasPayloadText = trimCronString(payload.message) || trimCronString(payload.text)
105
- if (!hasPayloadText) {
106
- if (payloadKind === 'systemEvent') {
107
- payload.text = resolveCronMessage(job)
108
- } else {
109
- payload.message = resolveCronMessage(job)
110
- }
111
- }
112
- if (!payloadKind) payload.kind = 'agentTurn'
113
- if (payloadKind !== 'systemEvent') {
114
- const configured = Number(payload.timeoutSeconds)
115
- payload.timeoutSeconds =
116
- Number.isFinite(configured) && configured >= CRON_PAYLOAD_TIMEOUT_SECONDS
117
- ? configured
118
- : CRON_PAYLOAD_TIMEOUT_SECONDS
119
- }
120
- for (const field of ['model', 'fallbacks', 'thinking', 'allowUnsafeExternalContent', 'lightContext', 'toolsAllow']) {
121
- if (sourcePayload[field] !== undefined) payload[field] = sourcePayload[field]
122
- }
123
-
124
- return {
125
- ...job,
126
- sessionKey: sk,
127
- sessionTarget: 'current',
128
- wakeMode: 'now',
129
- delivery,
130
- schedule: job.schedule?.kind === 'cron' ? { ...job.schedule, tz: DCG_CRON_TZ } : job.schedule,
131
- payload
132
- }
133
- }
134
-
135
- export function quoteCliArg(value: string): string {
136
- if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value
137
- return `'${value.replace(/'/g, `'\\''`)}'`
138
- }
139
-
140
- type ShellToken = { start: number; end: number; value: string; control: boolean }
141
-
142
- /** 识别参数、引号/转义和常用控制运算符,同时保留原始字符串位置。 */
143
- function tokenizeShell(command: string): ShellToken[] | null {
144
- const tokens: ShellToken[] = []
145
- let i = 0
146
- while (i < command.length) {
147
- if (/[^\S\r\n]/.test(command[i] ?? '')) {
148
- i += 1
149
- continue
150
- }
151
- const start = i
152
- const ch = command[i] ?? ''
153
- if (';&|\r\n'.includes(ch)) {
154
- const two = command.slice(i, i + 2)
155
- const end = two === '&&' || two === '||' || two === '\r\n' ? i + 2 : i + 1
156
- tokens.push({ start, end, value: command.slice(start, end), control: true })
157
- i = end
158
- continue
159
- }
160
-
161
- let value = ''
162
- let quote: "'" | '"' | null = null
163
- while (i < command.length) {
164
- const current = command[i] ?? ''
165
- if (!quote && (/\s/.test(current) || ';&|'.includes(current))) break
166
- if (!quote && (current === "'" || current === '"')) {
167
- quote = current
168
- i += 1
169
- continue
170
- }
171
- if (current === quote) {
172
- quote = null
173
- i += 1
174
- continue
175
- }
176
- if (current === '\\' && quote !== "'") {
177
- if (i + 1 >= command.length) return null
178
- value += command[i + 1] ?? ''
179
- i += 2
180
- continue
181
- }
182
- value += current
183
- i += 1
184
- }
185
- if (quote) return null
186
- tokens.push({ start, end: i, value, control: false })
187
- }
188
- return tokens
189
- }
190
-
191
- const CRON_VALUE_OPTIONS = new Set([
192
- '--tz', '--timeout-seconds', '--session', '--session-key', '--wake', '--channel', '--to', '--account'
193
- ])
194
- const CRON_FLAG_OPTIONS = new Set([
195
- '--announce', '--deliver', '--no-deliver', '--best-effort-deliver', '--no-best-effort-deliver', '--json'
196
- ])
197
-
198
- /**
199
- * 规范化 `exec` 工具中的 `cron create/add` 命令字符串。
200
- *
201
- * 背景:模型可能通过自由文本的 `exec` 工具创建 cron,写出来的命令参数可能:
202
- * 1. 缺少 DCG 必须的投递字段(如 --to、--channel、--best-effort-deliver);
203
- * 2. 与 DCG 投递要求冲突(如 --channel last);
204
- *
205
- * 本函数先“清洗”掉这些不确定/冲突的投递参数,再注入一套 DCG 统一的投递参数,
206
- * 保证最终命令在投递路由、超时、输出格式等方面一致。
207
- */
208
- export function normalizeCronExecCommand(command: string, sessionKey: string): string {
209
- const tokens = tokenizeShell(command)
210
- if (!tokens) {
211
- dcgLogger(`[${LOG_TAG}] cannot safely parse cron exec command, skip normalization.`, 'error')
212
- return command
213
- }
214
-
215
- const segment = findCronCreateExecSegment(tokens)
216
- if (!segment) return command
217
-
218
- const { segmentStartIndex, segmentEndIndex } = segment
219
- const segmentTokens = tokens.slice(segmentStartIndex, segmentEndIndex)
220
- const segmentStart = segmentTokens[0]?.start ?? 0
221
- const segmentEnd = tokens[segmentEndIndex]?.start ?? command.length
222
- const removed: Array<{ start: number; end: number }> = []
223
-
224
- for (let index = 0; index < segmentTokens.length; index += 1) {
225
- const token = segmentTokens[index]
226
- if (!token) continue
227
- const equalAt = token.value.indexOf('=')
228
- const option = equalAt >= 0 ? token.value.slice(0, equalAt) : token.value
229
- if (CRON_FLAG_OPTIONS.has(option)) {
230
- removed.push({ start: token.start, end: token.end })
231
- } else if (CRON_VALUE_OPTIONS.has(option)) {
232
- const nextToken = equalAt < 0 ? segmentTokens[index + 1] : undefined
233
- removed.push({ start: token.start, end: nextToken?.end ?? token.end })
234
- if (nextToken) index += 1
235
- }
236
- }
237
-
238
- let cursor = segmentStart
239
- let cleanedSegment = ''
240
- for (const range of removed) {
241
- cleanedSegment += command.slice(cursor, range.start)
242
- cursor = range.end
243
- }
244
- cleanedSegment += command.slice(cursor, segmentEnd)
245
-
246
- const remainingTokens = segmentTokens.filter(
247
- (token) => !removed.some((range) => token.start >= range.start && token.end <= range.end)
248
- )
249
- const skipTz = remainingTokens.some(
250
- (token) => token.value === '--every' || token.value.startsWith('--every=') || token.value === '--at' || token.value.startsWith('--at=')
251
- )
252
- const sk = sessionKey.trim()
253
- const { agentId } = formatterSessionKey(sk)
254
-
255
- // 4. 组装 DCG 要求的一套投递参数
256
- const required = [
257
- // 仅当命令不含 --every/--at 时才注入默认时区,避免时区语义冲突
258
- skipTz ? '' : `--tz ${quoteCliArg(DCG_CRON_TZ)}`,
259
- '--session current',
260
- '--wake now',
261
- // 控制 payload 执行超时
262
- `--timeout-seconds ${CRON_PAYLOAD_TIMEOUT_SECONDS}`,
263
- // 把 sessionKey 带给 cron payload,用于后续路由和修复
264
- `--session-key ${quoteCliArg(sk)}`,
265
- // 要求 cron 创建完成后发送announce消息,便于 DCG 拦截处理
266
- '--announce',
267
- // 固定投递到 DCG 监控的频道
268
- `--channel ${quoteCliArg(CHANNEL_ID)}`,
269
- // 投递目标指向 DCG 专用队列,带上 sessionKey 以便路由
270
- `--to ${quoteCliArg(`dcg-cron:${sk}`)}`,
271
- // 带上 agentId,便于多租户/多 agent 场景定位来源
272
- agentId ? `--account ${quoteCliArg(agentId)}` : '',
273
- // 投递失败时尽量重试,不直接丢弃任务
274
- '--best-effort-deliver',
275
- // 输出 JSON,方便下游解析
276
- '--json'
277
- ].filter(Boolean)
278
-
279
- const normalizedSegment = `${cleanedSegment.trim()} ${required.join(' ')}`
280
- const prefix = command.slice(0, segmentStart)
281
- const suffix = command.slice(segmentEnd)
282
- return `${prefix}${normalizedSegment}${suffix ? ` ${suffix.trimStart()}` : ''}`
283
- }
284
-
285
- // ---- 辅助函数 ----
286
-
287
- /** 与 OpenClaw cron tool 的 CRON_RECOVERABLE_OBJECT_KEYS 保持一致。 */
288
- const CRON_FLAT_PAYLOAD_KEYS = [
289
- 'message',
290
- 'text',
291
- 'model',
292
- 'fallbacks',
293
- 'toolsAllow',
294
- 'thinking',
295
- 'timeoutSeconds',
296
- 'lightContext',
297
- 'allowUnsafeExternalContent'
298
- ] as const
299
-
300
- const CRON_FLAT_SCHEDULE_KEYS = [
301
- 'kind',
302
- 'at',
303
- 'atMs',
304
- 'every',
305
- 'everyMs',
306
- 'anchorMs',
307
- 'cron',
308
- 'expr',
309
- 'tz',
310
- 'stagger',
311
- 'staggerMs',
312
- 'exact'
313
- ] as const
314
-
315
- const CRON_RECOVERABLE_JOB_KEYS = [
316
- 'name',
317
- 'schedule',
318
- 'sessionTarget',
319
- 'wakeMode',
320
- 'payload',
321
- 'delivery',
322
- 'enabled',
323
- 'description',
324
- 'deleteAfterRun',
325
- 'agentId',
326
- 'sessionKey',
327
- 'failureAlert',
328
- 'namePayload',
329
- 'scheduleKind',
330
- 'sessionTargetName',
331
- ...CRON_FLAT_PAYLOAD_KEYS,
332
- ...CRON_FLAT_SCHEDULE_KEYS
333
- ] as const
334
-
335
- /**
336
- * 将任意值安全地收窄为普通对象类型。
337
- *
338
- * 主要用于处理来自模型/上游的 `params`:它可能是 `null`、数组、字符串等,
339
- * 本函数先排除这些非法形态,再返回 `Record<string, unknown>`,
340
- * 让调用方可以安全地访问 `.action` 等字段。
341
- */
342
- function asRecord(value: unknown): Record<string, unknown> | null {
343
- return value !== null && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : null
344
- }
345
-
346
- function cloneParams(params: Record<string, unknown>): Record<string, unknown> | null {
347
- try {
348
- return structuredClone(params) as Record<string, unknown>
349
- } catch (err) {
350
- dcgLogger(`[${LOG_TAG}] structuredClone failed: ${String(err)}`, 'error')
351
- return null
352
- }
353
- }
354
-
355
- /**
356
- * OpenClaw cron tool 的正式创建 action 是 `add`。
357
- * `create` 只是 `openclaw cron create` CLI 的 alias,不是 tool action。
358
- */
359
- function isCronAddParams(params: Record<string, unknown>): boolean {
360
- const action = typeof params.action === 'string' ? params.action.trim().toLowerCase() : ''
361
- return action === 'add' || action === 'create'
362
- }
363
-
364
- /**
365
- * 模型偶尔会把 job 字段错误地放到 params 顶层。
366
- * OpenClaw 执行 cron.add 前也会按同一组字段恢复 params.job;这里提前恢复,
367
- * 这样 before_tool_call 可以在真正执行前应用完整的 DCG 规范。
368
- */
369
- function recoverFlatCronJob(params: Record<string, unknown>): Record<string, unknown> | null {
370
- const recovered: Record<string, unknown> = {}
371
- for (const key of CRON_RECOVERABLE_JOB_KEYS) {
372
- if (params[key] !== undefined) recovered[key] = params[key]
373
- }
374
-
375
- // 与 OpenClaw hasCronCreateSignal 保持一致。
376
- const hasJobContent =
377
- recovered.schedule !== undefined ||
378
- recovered.at !== undefined ||
379
- recovered.atMs !== undefined ||
380
- recovered.everyMs !== undefined ||
381
- recovered.cron !== undefined ||
382
- recovered.expr !== undefined ||
383
- recovered.payload !== undefined ||
384
- recovered.message !== undefined ||
385
- recovered.text !== undefined
386
-
387
- return hasJobContent ? recovered : null
388
- }
389
-
390
- function resolveCronJob(params: Record<string, unknown>): Record<string, unknown> | null {
391
- const explicitJob = asRecord(params.job)
392
- const flatJob = recoverFlatCronJob(params)
393
- const hasExplicitJob = explicitJob !== null && Object.keys(explicitJob).length > 0
394
-
395
- if (hasExplicitJob && flatJob) {
396
- return { ...flatJob, ...explicitJob }
397
- }
398
- if (hasExplicitJob) {
399
- return explicitJob
400
- }
401
- return flatJob
402
- }
403
-
404
- /**
405
- * 规范化 cron.add 参数。
406
- *
407
- * 正式结构是 `{ action: "add", job: CronJobCreate }`,因此只修改 params.job。
408
- * 如果 job 缺失,则使用 OpenClaw 自身支持的 flat 参数兼容规则恢复 job。
409
- * update/remove/run 等 action 有各自的 patch/jobId 结构,不应在这里处理。
410
- */
411
- function patchCronAddParams(params: Record<string, unknown>, sk: string): Record<string, unknown> | null {
412
- const newParams = cloneParams(params)
413
- if (!newParams) return null
414
- const job = resolveCronJob(newParams)
415
- if (!job) return null
416
-
417
- // OpenClaw cron tool 的正式 action 是 add;create 只是 CLI alias。
418
- newParams.action = 'add'
419
- // buildDcgCronJob 保留 job 的其他合法字段,只强制覆盖 DCG 业务字段。
420
- newParams.job = buildDcgCronJob(job as CronJobLike, sk)
421
- return newParams
422
- }
423
-
424
- export function cronToolCall(event: { toolName: unknown; params: unknown; toolCallId?: unknown }, sk: string) {
425
- const { toolName, params, toolCallId } = event
426
- // 仅处理 cron 工具
427
- if (toolName === 'cron') {
428
- const paramsRecord = asRecord(params)
429
- if (!paramsRecord || !isCronAddParams(paramsRecord)) {
430
- return undefined
431
- }
432
-
433
- const newParams = patchCronAddParams(paramsRecord, sk)
434
-
435
- dcgLogger(`[${LOG_TAG}] cronToolCall params (${toolCallId}) ==> ${JSON.stringify(params)}`)
436
- dcgLogger(`[${LOG_TAG}] cronToolCall newParams (${toolCallId}) ==> ${JSON.stringify(newParams)}`)
437
-
438
- if (!newParams) {
439
- dcgLogger(`[${LOG_TAG}] cronToolCall (${toolCallId}) could not resolve cron.add job, skip.`)
440
- return undefined
441
- }
442
-
443
- return { params: newParams }
444
- } else if (toolName === 'exec') {
445
- const paramsRecord = asRecord(params)
446
- if (!paramsRecord) return undefined
447
- const cmd = paramsRecord.command
448
- if (typeof cmd !== 'string') {
449
- return undefined
450
- }
451
-
452
- // 保留原有逻辑(非 openclaw cron 命令的 cron create/add,或其他 exec)
453
- if (isCronCreateExecCommand(cmd)) {
454
- const newParams = cloneParams(paramsRecord)
455
- if (!newParams) return undefined
456
- newParams.command = normalizeCronExecCommand(cmd, sk)
457
-
458
- dcgLogger(`cron exec create or add params: ${JSON.stringify(newParams)}`)
459
-
460
- return { params: newParams }
461
- }
462
- return undefined
463
- }
464
-
465
- return undefined
466
- }
package/src/cron/types.ts DELETED
@@ -1,15 +0,0 @@
1
- /**
2
- * Cron 领域内创建前规范化与创建后修复共用的最小任务结构。
3
- * 保留索引签名,以兼容 OpenClaw 后续增加的合法 job 字段。
4
- */
5
- export interface CronJobLike {
6
- sessionKey?: string
7
- sessionTarget?: string
8
- wakeMode?: string
9
- name?: string
10
- description?: string
11
- delivery?: Record<string, unknown>
12
- schedule?: Record<string, unknown>
13
- payload?: Record<string, unknown>
14
- [key: string]: unknown
15
- }