@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.
- package/index.js +305 -0
- package/openclaw.plugin.json +41 -12
- package/package.json +8 -35
- package/README.md +0 -167
- package/index.ts +0 -35
- package/src/agent.ts +0 -568
- package/src/bot.ts +0 -646
- package/src/channel/index.ts +0 -329
- package/src/channel/outboundMedia.ts +0 -144
- package/src/channel/outboundTarget.ts +0 -62
- package/src/channel/uploadMediaUrl.ts +0 -76
- package/src/cron/message.ts +0 -114
- package/src/cron/params.ts +0 -164
- package/src/cron/toolGuard.ts +0 -466
- package/src/cron/types.ts +0 -15
- package/src/gateway/index.ts +0 -430
- package/src/gateway/security.ts +0 -95
- package/src/gateway/socket.ts +0 -256
- package/src/libs/ali-oss-6.23.0.tgz +0 -0
- package/src/libs/axios-1.13.6.tgz +0 -0
- package/src/libs/md5-2.3.0.tgz +0 -0
- package/src/libs/mime-types-3.0.2.tgz +0 -0
- package/src/libs/unzipper-0.12.3.tgz +0 -0
- package/src/libs/ws-8.19.0.tgz +0 -0
- package/src/monitor.ts +0 -269
- package/src/request/api.ts +0 -80
- package/src/request/oss.ts +0 -200
- package/src/request/request.ts +0 -191
- package/src/request/userInfo.ts +0 -44
- package/src/session.ts +0 -28
- package/src/skill.ts +0 -114
- package/src/tool.ts +0 -603
- package/src/tools/messageTool.ts +0 -334
- package/src/tools/toolCallGuard.ts +0 -327
- package/src/transport.ts +0 -217
- package/src/types.ts +0 -139
- package/src/utils/agentErrors.ts +0 -116
- package/src/utils/constant.ts +0 -60
- package/src/utils/env-config.ts +0 -19
- package/src/utils/formatLlmInputEvent.ts +0 -48
- package/src/utils/gatewayMsgHandler.ts +0 -147
- package/src/utils/global.ts +0 -309
- package/src/utils/inboundTurnState.ts +0 -66
- package/src/utils/log.ts +0 -77
- package/src/utils/mediaAttached.ts +0 -244
- package/src/utils/mediaEmitter.ts +0 -54
- package/src/utils/outboundAssistantText.ts +0 -117
- package/src/utils/params.ts +0 -104
- package/src/utils/passTxt.ts +0 -4
- package/src/utils/resolveRegisterConfig.ts +0 -33
- package/src/utils/searchFile.ts +0 -228
- package/src/utils/sessionState.ts +0 -137
- package/src/utils/sessionTermination.ts +0 -228
- package/src/utils/streamMerge.ts +0 -150
- package/src/utils/subagentRunMap.ts +0 -71
- package/src/utils/undiciFetchInterceptor.ts +0 -346
- package/src/utils/workspaceFilePaths.ts +0 -18
- package/src/utils/wsMessageHandler.ts +0 -124
- package/src/utils/zipExtract.ts +0 -97
- package/src/utils/zipPath.ts +0 -24
package/src/cron/toolGuard.ts
DELETED
|
@@ -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
|
-
}
|