@workclaw/openclaw-workclaw 1.0.0 → 1.0.11

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.
@@ -1,326 +1,326 @@
1
- /**
2
- * 智小途定时任务工具 - 后端接口封装
3
- */
4
-
5
- import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
6
- import type { OpenclawWorkclawConnectionConfig } from '../../../connection/workclaw-client.js'
7
- import { resolveOpenclawWorkclawAccount } from '../../../accounts.js'
8
- import { doFetchJson, getOpenclawWorkclawAccessToken, normalizeBaseUrl } from '../../../connection/workclaw-client.js'
9
-
10
- const DEFAULT_MAX_RETRIES = 3
11
- const DEFAULT_RETRY_DELAY_MS = 1000
12
-
13
- /**
14
- * 睡眠函数
15
- */
16
- function sleep(ms: number): Promise<void> {
17
- return new Promise(resolve => setTimeout(resolve, ms))
18
- }
19
-
20
- /**
21
- * 带重试机制的 fetch 调用
22
- */
23
- async function fetchWithRetry<T>(
24
- fetchFn: () => Promise<T>,
25
- options: {
26
- maxRetries?: number
27
- retryDelayMs?: number
28
- retryableErrors?: string[]
29
- } = {},
30
- ): Promise<{ data?: T, error?: string, attempts: number }> {
31
- const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES
32
- const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS
33
- const retryableErrors = options.retryableErrors ?? [
34
- 'ECONNREFUSED',
35
- 'ETIMEDOUT',
36
- 'ENOTFOUND',
37
- 'ENETUNREACH',
38
- 'EAI_AGAIN',
39
- ]
40
-
41
- let lastError: string = ''
42
- let attempts = 0
43
-
44
- for (let i = 0; i <= maxRetries; i++) {
45
- attempts = i + 1
46
- try {
47
- const data = await fetchFn()
48
- return { data, attempts }
1
+ /**
2
+ * 智小途定时任务工具 - 后端接口封装
3
+ */
4
+
5
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
6
+ import type { OpenclawWorkclawConnectionConfig } from '../../../connection/workclaw-client.js'
7
+ import { resolveOpenclawWorkclawAccount } from '../../../accounts.js'
8
+ import { doFetchJson, getOpenclawWorkclawAccessToken, normalizeBaseUrl } from '../../../connection/workclaw-client.js'
9
+
10
+ const DEFAULT_MAX_RETRIES = 3
11
+ const DEFAULT_RETRY_DELAY_MS = 1000
12
+
13
+ /**
14
+ * 睡眠函数
15
+ */
16
+ function sleep(ms: number): Promise<void> {
17
+ return new Promise(resolve => setTimeout(resolve, ms))
18
+ }
19
+
20
+ /**
21
+ * 带重试机制的 fetch 调用
22
+ */
23
+ async function fetchWithRetry<T>(
24
+ fetchFn: () => Promise<T>,
25
+ options: {
26
+ maxRetries?: number
27
+ retryDelayMs?: number
28
+ retryableErrors?: string[]
29
+ } = {},
30
+ ): Promise<{ data?: T, error?: string, attempts: number }> {
31
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES
32
+ const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS
33
+ const retryableErrors = options.retryableErrors ?? [
34
+ 'ECONNREFUSED',
35
+ 'ETIMEDOUT',
36
+ 'ENOTFOUND',
37
+ 'ENETUNREACH',
38
+ 'EAI_AGAIN',
39
+ ]
40
+
41
+ let lastError: string = ''
42
+ let attempts = 0
43
+
44
+ for (let i = 0; i <= maxRetries; i++) {
45
+ attempts = i + 1
46
+ try {
47
+ const data = await fetchFn()
48
+ return { data, attempts }
49
49
  }
50
- catch (error: any) {
51
- lastError = error.message || String(error)
52
-
53
- const isRetryable = retryableErrors.some(
54
- e => lastError.toLowerCase().includes(e.toLowerCase()),
55
- )
56
-
57
- if (i < maxRetries && isRetryable) {
58
- const delay = retryDelayMs * 2 ** i
59
- await sleep(delay)
60
- continue
61
- }
62
-
63
- break
64
- }
65
- }
66
-
67
- return { error: lastError, attempts }
68
- }
69
-
70
- /**
71
- * 脱敏日志输出(隐藏敏感字段)
72
- */
73
- function sanitizePayload(payload: Record<string, any>): Record<string, any> {
74
- const sensitiveFields = [
75
- 'password',
76
- 'secret',
77
- 'token',
78
- 'authorization',
79
- 'appSecret',
80
- 'appSecret',
81
- ]
82
-
83
- const sanitized: Record<string, any> = {}
84
- for (const [key, value] of Object.entries(payload)) {
85
- const lowerKey = key.toLowerCase()
86
- if (sensitiveFields.some(f => lowerKey.includes(f))) {
87
- sanitized[key] = '***'
50
+ catch (error: any) {
51
+ lastError = error.message || String(error)
52
+
53
+ const isRetryable = retryableErrors.some(
54
+ e => lastError.toLowerCase().includes(e.toLowerCase()),
55
+ )
56
+
57
+ if (i < maxRetries && isRetryable) {
58
+ const delay = retryDelayMs * 2 ** i
59
+ await sleep(delay)
60
+ continue
61
+ }
62
+
63
+ break
88
64
  }
89
- else if (typeof value === 'object' && value !== null) {
90
- sanitized[key] = sanitizePayload(value as Record<string, any>)
65
+ }
66
+
67
+ return { error: lastError, attempts }
68
+ }
69
+
70
+ /**
71
+ * 脱敏日志输出(隐藏敏感字段)
72
+ */
73
+ function sanitizePayload(payload: Record<string, any>): Record<string, any> {
74
+ const sensitiveFields = [
75
+ 'password',
76
+ 'secret',
77
+ 'token',
78
+ 'authorization',
79
+ 'appSecret',
80
+ 'appSecret',
81
+ ]
82
+
83
+ const sanitized: Record<string, any> = {}
84
+ for (const [key, value] of Object.entries(payload)) {
85
+ const lowerKey = key.toLowerCase()
86
+ if (sensitiveFields.some(f => lowerKey.includes(f))) {
87
+ sanitized[key] = '***'
88
+ }
89
+ else if (typeof value === 'object' && value !== null) {
90
+ sanitized[key] = sanitizePayload(value as Record<string, any>)
91
+ }
92
+ else {
93
+ sanitized[key] = value
94
+ }
95
+ }
96
+ return sanitized
97
+ }
98
+
99
+ /**
100
+ * 调用后端 API 的通用方法
101
+ * @param api - OpenClaw API 实例
102
+ * @param accountId - 账户 ID
103
+ * @param path - API 路径
104
+ * @param payload - 请求体
105
+ * @param options - 可选配置
106
+ * @returns 接口调用结果
107
+ */
108
+ export async function callBackendApi(
109
+ api: OpenClawPluginApi,
110
+ accountId: string,
111
+ path: string,
112
+ payload: Record<string, any>,
113
+ options: {
114
+ maxRetries?: number
115
+ } = {},
116
+ ): Promise<{ success: boolean, data?: any, error?: string, attempts?: number }> {
117
+ try {
118
+ const account = resolveOpenclawWorkclawAccount({
119
+ cfg: api.config,
120
+ accountId,
121
+ })
122
+
123
+ if (!account.configured) {
124
+ api.logger.error(`[智小途-定时任务] 账户未配置 accountId=${accountId}`)
125
+ return { success: false, error: 'Account not configured' }
126
+ }
127
+
128
+ const accConfig = account.config as any
129
+ const tokenCacheKey = accConfig.appKey || accountId
130
+ const baseUrl = normalizeBaseUrl(accConfig.baseUrl)
131
+
132
+ api.logger.info(`[智小途-定时任务] 正在获取认证 token,accountId=${accountId}`)
133
+
134
+ const token = await getOpenclawWorkclawAccessToken(tokenCacheKey, {
135
+ baseUrl: accConfig.baseUrl,
136
+ appKey: accConfig.appKey,
137
+ appSecret: accConfig.appSecret,
138
+ allowInsecureTls: accConfig.allowInsecureTls,
139
+ requestTimeout: accConfig.requestTimeout,
140
+ } as OpenclawWorkclawConnectionConfig)
141
+
142
+ const requestUrl = `${baseUrl}/open-apis${path}`
143
+ const sanitizedPayload = sanitizePayload(payload)
144
+ api.logger.info(`[智小途-定时任务] 调用接口 ${requestUrl}`)
145
+ api.logger.info(`[智小途-定时任务] 请求参数 ${JSON.stringify(sanitizedPayload)}`)
146
+
147
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES
148
+
149
+ const fetchResult = await fetchWithRetry(
150
+ () =>
151
+ doFetchJson(
152
+ requestUrl,
153
+ {
154
+ method: 'POST',
155
+ headers: {
156
+ 'Content-Type': 'application/json',
157
+ 'Authorization': `Bearer ${token}`,
158
+ },
159
+ body: JSON.stringify(payload),
160
+ },
161
+ accConfig.allowInsecureTls,
162
+ accConfig.requestTimeout,
163
+ ),
164
+ { maxRetries },
165
+ )
166
+
167
+ if (fetchResult.error) {
168
+ api.logger.error(`[智小途-定时任务] 接口调用失败(已重试 ${fetchResult.attempts} 次): ${fetchResult.error}`)
169
+ return { success: false, error: fetchResult.error, attempts: fetchResult.attempts }
91
170
  }
92
- else {
93
- sanitized[key] = value
94
- }
95
- }
96
- return sanitized
97
- }
98
-
99
- /**
100
- * 调用后端 API 的通用方法
101
- * @param api - OpenClaw API 实例
102
- * @param accountId - 账户 ID
103
- * @param path - API 路径
104
- * @param payload - 请求体
105
- * @param options - 可选配置
106
- * @returns 接口调用结果
107
- */
108
- export async function callBackendApi(
109
- api: OpenClawPluginApi,
110
- accountId: string,
111
- path: string,
112
- payload: Record<string, any>,
113
- options: {
114
- maxRetries?: number
115
- } = {},
116
- ): Promise<{ success: boolean, data?: any, error?: string, attempts?: number }> {
117
- try {
118
- const account = resolveOpenclawWorkclawAccount({
119
- cfg: api.config,
120
- accountId,
121
- })
122
-
123
- if (!account.configured) {
124
- api.logger.error(`[智小途-定时任务] 账户未配置 accountId=${accountId}`)
125
- return { success: false, error: 'Account not configured' }
126
- }
127
-
128
- const accConfig = account.config as any
129
- const tokenCacheKey = accConfig.appKey || accountId
130
- const baseUrl = normalizeBaseUrl(accConfig.baseUrl)
131
-
132
- api.logger.info(`[智小途-定时任务] 正在获取认证 token,accountId=${accountId}`)
133
-
134
- const token = await getOpenclawWorkclawAccessToken(tokenCacheKey, {
135
- baseUrl: accConfig.baseUrl,
136
- appKey: accConfig.appKey,
137
- appSecret: accConfig.appSecret,
138
- allowInsecureTls: accConfig.allowInsecureTls,
139
- requestTimeout: accConfig.requestTimeout,
140
- } as OpenclawWorkclawConnectionConfig)
141
-
142
- const requestUrl = `${baseUrl}/open-apis${path}`
143
- const sanitizedPayload = sanitizePayload(payload)
144
- api.logger.info(`[智小途-定时任务] 调用接口 ${requestUrl}`)
145
- api.logger.info(`[智小途-定时任务] 请求参数 ${JSON.stringify(sanitizedPayload)}`)
146
-
147
- const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES
148
-
149
- const fetchResult = await fetchWithRetry(
150
- () =>
151
- doFetchJson(
152
- requestUrl,
153
- {
154
- method: 'POST',
155
- headers: {
156
- 'Content-Type': 'application/json',
157
- 'Authorization': `Bearer ${token}`,
158
- },
159
- body: JSON.stringify(payload),
160
- },
161
- accConfig.allowInsecureTls,
162
- accConfig.requestTimeout,
163
- ),
164
- { maxRetries },
165
- )
166
-
167
- if (fetchResult.error) {
168
- api.logger.error(`[智小途-定时任务] 接口调用失败(已重试 ${fetchResult.attempts} 次): ${fetchResult.error}`)
169
- return { success: false, error: fetchResult.error, attempts: fetchResult.attempts }
170
- }
171
-
172
- const data = fetchResult.data
173
- api.logger.info(`[智小途-定时任务] 接口响应(尝试 ${fetchResult.attempts} 次): ${JSON.stringify(data)}`)
174
-
175
- if (data?.code === 200 || data?.code === 0) {
176
- return { success: true, data, attempts: fetchResult.attempts }
171
+
172
+ const data = fetchResult.data
173
+ api.logger.info(`[智小途-定时任务] 接口响应(尝试 ${fetchResult.attempts} 次): ${JSON.stringify(data)}`)
174
+
175
+ if (data?.code === 200 || data?.code === 0) {
176
+ return { success: true, data, attempts: fetchResult.attempts }
177
177
  }
178
- else {
179
- const errorMsg = data?.message || data?.msg || 'Unknown error'
180
- api.logger.error(`[智小途-定时任务] 接口返回错误 ${errorMsg}`)
181
- return { success: false, error: errorMsg }
182
- }
178
+ else {
179
+ const errorMsg = data?.message || data?.msg || 'Unknown error'
180
+ api.logger.error(`[智小途-定时任务] 接口返回错误 ${errorMsg}`)
181
+ return { success: false, error: errorMsg }
182
+ }
183
+ }
184
+ catch (error: any) {
185
+ api.logger.error(`[智小途-定时任务] 接口调用异常 ${error.message}`)
186
+ return { success: false, error: error.message }
183
187
  }
184
- catch (error: any) {
185
- api.logger.error(`[智小途-定时任务] 接口调用异常 ${error.message}`)
186
- return { success: false, error: error.message }
187
- }
188
- }
189
-
190
- /**
191
- * 调用新增定时任务接口
192
- */
193
- export async function callCronJobAdd(
194
- api: OpenClawPluginApi,
195
- accountId: string,
196
- payload: {
197
- jobId: string
198
- name: string
199
- kind: 'cron'
200
- expr?: string
201
- message: string
202
- messageId?: string
203
- },
204
- ): Promise<{ success: boolean, data?: any, error?: string }> {
205
- api.logger.info(`[智小途-定时任务] 新增定时任务 jobId=${payload.jobId} name=${payload.name}`)
206
-
207
- const result = await callBackendApi(api, accountId, '/cron/job/add', {
208
- clawJobId: payload.jobId,
209
- name: payload.name,
210
- kind: payload.kind,
211
- expr: payload.expr || '',
212
- message: payload.message,
213
- agentId: payload.messageId || '0',
214
- })
215
-
216
- return { success: result.success, data: result.data, error: result.error }
217
- }
218
-
219
- /**
220
- * 调用删除定时任务接口
221
- */
222
- export async function callCronJobRemove(
223
- api: OpenClawPluginApi,
224
- accountId: string,
225
- payload: {
226
- jobId: string
227
- },
228
- ): Promise<{ success: boolean, data?: any, error?: string }> {
229
- api.logger.info(`[智小途-定时任务] 删除定时任务 jobId=${payload.jobId}`)
230
-
231
- const result = await callBackendApi(api, accountId, '/cron/job/del', {
232
- clawJobId: payload.jobId,
233
- })
234
-
235
- return { success: result.success, data: result.data, error: result.error }
236
- }
237
-
238
- /**
239
- * 调用更新定时任务接口
240
- */
241
- export async function callCronJobUpdate(
242
- api: OpenClawPluginApi,
243
- accountId: string,
244
- payload: {
245
- jobId: string
246
- name: string
247
- kind: 'cron'
248
- expr?: string
249
- message: string
250
- agentId?: string
251
- },
252
- ): Promise<{ success: boolean, data?: any, error?: string }> {
253
- api.logger.info(`[智小途-定时任务] 更新定时任务 jobId=${payload.jobId} name=${payload.name} agentId=${payload.agentId}`)
254
-
255
- const result = await callBackendApi(api, accountId, '/cron/job/update', {
256
- clawJobId: payload.jobId,
257
- name: payload.name,
258
- kind: payload.kind,
259
- expr: payload.expr || '',
260
- message: payload.message,
261
- agentId: payload.agentId || '0',
262
- })
263
-
264
- return { success: result.success, data: result.data, error: result.error }
265
- }
266
-
267
- /**
268
- * 调用启用定时任务接口
269
- */
270
- export async function callCronJobEnabled(
271
- api: OpenClawPluginApi,
272
- accountId: string,
273
- payload: {
274
- jobId: string
275
- },
276
- ): Promise<{ success: boolean, data?: any, error?: string }> {
277
- api.logger.info(`[智小途-定时任务] 启用定时任务 jobId=${payload.jobId}`)
278
-
279
- const result = await callBackendApi(api, accountId, '/cron/job/enabled', {
280
- clawJobId: payload.jobId,
281
- })
282
-
283
- return { success: result.success, data: result.data, error: result.error }
284
- }
285
-
286
- /**
287
- * 调用禁用定时任务接口
288
- */
289
- export async function callCronJobDisabled(
290
- api: OpenClawPluginApi,
291
- accountId: string,
292
- payload: {
293
- jobId: string
294
- },
295
- ): Promise<{ success: boolean, data?: any, error?: string }> {
296
- api.logger.info(`[智小途-定时任务] 禁用定时任务 jobId=${payload.jobId}`)
297
-
298
- const result = await callBackendApi(api, accountId, '/cron/job/disabled', {
299
- clawJobId: payload.jobId,
300
- })
301
-
302
- return { success: result.success, data: result.data, error: result.error }
303
- }
304
-
305
- /**
306
- * 调用定时任务触发通知接口
307
- */
308
- export async function callCronJobMessage(
309
- api: OpenClawPluginApi,
310
- accountId: string,
311
- payload: {
312
- jobId: string
313
- message: string
314
- agentId?: string
315
- },
316
- ): Promise<{ success: boolean, data?: any, error?: string }> {
317
- api.logger.info(`[智小途-定时任务] 定时任务触发通知 jobId=${payload.jobId} message=${payload.message} agentId=${payload.agentId}`)
318
-
319
- const result = await callBackendApi(api, accountId, '/cron/job/message', {
320
- clawJobId: payload.jobId,
321
- message: payload.message,
322
- agentId: payload.agentId || '0',
323
- })
324
-
325
- return { success: result.success, data: result.data, error: result.error }
326
- }
188
+ }
189
+
190
+ /**
191
+ * 调用新增定时任务接口
192
+ */
193
+ export async function callCronJobAdd(
194
+ api: OpenClawPluginApi,
195
+ accountId: string,
196
+ payload: {
197
+ jobId: string
198
+ name: string
199
+ kind: 'cron'
200
+ expr?: string
201
+ message: string
202
+ messageId?: string
203
+ },
204
+ ): Promise<{ success: boolean, data?: any, error?: string }> {
205
+ api.logger.info(`[智小途-定时任务] 新增定时任务 jobId=${payload.jobId} name=${payload.name}`)
206
+
207
+ const result = await callBackendApi(api, accountId, '/cron/job/add', {
208
+ clawJobId: payload.jobId,
209
+ name: payload.name,
210
+ kind: payload.kind,
211
+ expr: payload.expr || '',
212
+ message: payload.message,
213
+ agentId: payload.messageId || '0',
214
+ })
215
+
216
+ return { success: result.success, data: result.data, error: result.error }
217
+ }
218
+
219
+ /**
220
+ * 调用删除定时任务接口
221
+ */
222
+ export async function callCronJobRemove(
223
+ api: OpenClawPluginApi,
224
+ accountId: string,
225
+ payload: {
226
+ jobId: string
227
+ },
228
+ ): Promise<{ success: boolean, data?: any, error?: string }> {
229
+ api.logger.info(`[智小途-定时任务] 删除定时任务 jobId=${payload.jobId}`)
230
+
231
+ const result = await callBackendApi(api, accountId, '/cron/job/del', {
232
+ clawJobId: payload.jobId,
233
+ })
234
+
235
+ return { success: result.success, data: result.data, error: result.error }
236
+ }
237
+
238
+ /**
239
+ * 调用更新定时任务接口
240
+ */
241
+ export async function callCronJobUpdate(
242
+ api: OpenClawPluginApi,
243
+ accountId: string,
244
+ payload: {
245
+ jobId: string
246
+ name: string
247
+ kind: 'cron'
248
+ expr?: string
249
+ message: string
250
+ agentId?: string
251
+ },
252
+ ): Promise<{ success: boolean, data?: any, error?: string }> {
253
+ api.logger.info(`[智小途-定时任务] 更新定时任务 jobId=${payload.jobId} name=${payload.name} agentId=${payload.agentId}`)
254
+
255
+ const result = await callBackendApi(api, accountId, '/cron/job/update', {
256
+ clawJobId: payload.jobId,
257
+ name: payload.name,
258
+ kind: payload.kind,
259
+ expr: payload.expr || '',
260
+ message: payload.message,
261
+ agentId: payload.agentId || '0',
262
+ })
263
+
264
+ return { success: result.success, data: result.data, error: result.error }
265
+ }
266
+
267
+ /**
268
+ * 调用启用定时任务接口
269
+ */
270
+ export async function callCronJobEnabled(
271
+ api: OpenClawPluginApi,
272
+ accountId: string,
273
+ payload: {
274
+ jobId: string
275
+ },
276
+ ): Promise<{ success: boolean, data?: any, error?: string }> {
277
+ api.logger.info(`[智小途-定时任务] 启用定时任务 jobId=${payload.jobId}`)
278
+
279
+ const result = await callBackendApi(api, accountId, '/cron/job/enabled', {
280
+ clawJobId: payload.jobId,
281
+ })
282
+
283
+ return { success: result.success, data: result.data, error: result.error }
284
+ }
285
+
286
+ /**
287
+ * 调用禁用定时任务接口
288
+ */
289
+ export async function callCronJobDisabled(
290
+ api: OpenClawPluginApi,
291
+ accountId: string,
292
+ payload: {
293
+ jobId: string
294
+ },
295
+ ): Promise<{ success: boolean, data?: any, error?: string }> {
296
+ api.logger.info(`[智小途-定时任务] 禁用定时任务 jobId=${payload.jobId}`)
297
+
298
+ const result = await callBackendApi(api, accountId, '/cron/job/disabled', {
299
+ clawJobId: payload.jobId,
300
+ })
301
+
302
+ return { success: result.success, data: result.data, error: result.error }
303
+ }
304
+
305
+ /**
306
+ * 调用定时任务触发通知接口
307
+ */
308
+ export async function callCronJobMessage(
309
+ api: OpenClawPluginApi,
310
+ accountId: string,
311
+ payload: {
312
+ jobId: string
313
+ message: string
314
+ agentId?: string
315
+ },
316
+ ): Promise<{ success: boolean, data?: any, error?: string }> {
317
+ api.logger.info(`[智小途-定时任务] 定时任务触发通知 jobId=${payload.jobId} message=${payload.message} agentId=${payload.agentId}`)
318
+
319
+ const result = await callBackendApi(api, accountId, '/cron/job/message', {
320
+ clawJobId: payload.jobId,
321
+ message: payload.message,
322
+ agentId: payload.agentId || '0',
323
+ })
324
+
325
+ return { success: result.success, data: result.data, error: result.error }
326
+ }
package/src/types.ts CHANGED
@@ -50,9 +50,9 @@ export interface WorkClawAccountConfig {
50
50
 
51
51
  export type WorkClawConfig = WorkClawBaseConfig & WorkClawAccountConfig
52
52
 
53
- /**
53
+ /**
54
54
  * account.config is typed as OpenclawWorkclawConfig (top-level) but at runtime
55
- * also contains all OpenclawWorkclawAccountConfig fields (merged).
55
+ * also contains all OpenclawWorkclawAccountConfig fields (merged).
56
56
  */
57
57
  export type OpenclawWorkclawFullAccountConfig = OpenclawWorkclawConfig & Partial<OpenclawWorkclawAccountConfig>
58
58