@workclaw/openclaw-workclaw 1.0.16 → 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 +12 -5
  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
@@ -15,13 +15,21 @@ always: true
15
15
 
16
16
  你没有内存或后台线程,口头承诺"到时候提醒"是无效的——只有调用工具才能真正注册定时任务。
17
17
 
18
- **🔒 参数强制规则**:
19
- - ❌ **严禁**将 `kind: "cron"` 转换为 `--at` 参数
20
- - **必须**保持 `kind: "cron"` 不变
21
- - **严禁**将 cron 表达式转换为绝对时间(--at)
22
- - ✅ **必须**使用 cron 表达式格式(--cron)
23
- - **必须**使用 `--session isolated + --message`(agentTurn),**禁止**使用 `--system-event`
24
- - ✅ **必须**使用 `--channel "openclaw-workclaw"`
18
+ **🔒 时间格式强制规则(核心!)**:
19
+
20
+ 1. **所有时间都必须转换为 cron 表达式**
21
+ - 相对时间(如 `5m`、`1h`、`1h30m`、`2d`)会自动转换为 cron 表达式
22
+ - 你只需要传入用户说的原始时间,工具会自动转换
23
+ - 示例:`"5分钟后提醒"` 传入 `time: "5m"` 工具转换为 cron 表达式
24
+
25
+ 2. **严禁使用 `--at` 参数**
26
+ - ❌ 禁止将 cron 表达式转换为 `--at` 绝对时间参数
27
+ - ✅ 必须使用 `--cron` 参数传递 cron 表达式
28
+ - ❌ 禁止使用任何绝对时间格式(如 ISO 时间戳)
29
+
30
+ 3. **禁止使用其他定时方式**
31
+ - ❌ 禁止使用 `--at`、`--every`、`--deliver` 等参数
32
+ - ✅ 只能使用 `--cron` 参数
25
33
 
26
34
  ***
27
35
 
@@ -68,24 +76,26 @@ always: true
68
76
 
69
77
  | 参数 | 说明 | 示例 |
70
78
  |------|------|------|
71
- | `time` | 时间描述,支持相对时间(5m、1h)和 cron 表达式(0 8 * * *) | `"5m"` / `"0 8 * * *"` |
79
+ | `time` | **时间(核心!)**,支持相对时间和 cron 表达式,工具会自动统一转换为 cron 表达式 | `"5m"` / `"0 8 * * *"` |
72
80
  | `name` | 任务名称 | `"喝水提醒"` |
73
81
  | `message` | 提醒内容 | `"该喝水了"` |
74
82
 
75
- ### ⏰ 时间格式
83
+ ### ⏰ 时间格式说明
76
84
 
77
- **相对时间**:
78
- - `5m` - 5分钟后
79
- - `1h` - 1小时后
80
- - `1h30m` - 1.5小时后
81
- - `2d` - 2天后
85
+ **⚠️ 重要:所有时间都会被自动转换为 cron 表达式**
82
86
 
83
- **注意:提醒时间不能少于 2 分钟**
87
+ **支持的输入格式**:
88
+ | 输入格式 | 示例 | 说明 |
89
+ |---------|------|------|
90
+ | 相对时间 | `5m` | 5分钟后 |
91
+ | 相对时间 | `1h` | 1小时后 |
92
+ | 相对时间 | `1h30m` | 1.5小时后 |
93
+ | 相对时间 | `2d` | 2天后 |
94
+ | cron 表达式 | `0 8 * * *` | 每天早上8点 |
95
+ | cron 表达式 | `0 22 * * *` | 每天晚上10点 |
96
+ | cron 表达式 | `0 9 * * 1-5` | 工作日早上9点 |
84
97
 
85
- **cron 表达式**:
86
- - `0 8 * * *` - 每天早上8点
87
- - `0 22 * * *` - 每天晚上10点
88
- - `0 9 * * 1-5` - 工作日早上9点
98
+ **⚠️ 提醒时间不能少于 2 分钟**
89
99
 
90
100
  ### 🔍 OpenClaw cron 工具返回值格式(重要!)
91
101
 
@@ -220,7 +230,7 @@ always: true
220
230
  | 参数 | 说明 | 示例 |
221
231
  |------|------|------|
222
232
  | `jobId` | OpenClaw 任务 ID | `"123456"` |
223
- | `time` | 时间描述,支持相对时间(5m、1h)和 cron 表达式 | `"10m"` / `"0 9 * * *"` |
233
+ | `time` | **时间(核心!)**,支持相对时间和 cron 表达式,工具会自动统一转换为 cron 表达式 | `"10m"` / `"0 9 * * *"` |
224
234
  | `name` | 任务名称 | `"新喝水提醒"` |
225
235
  | `message` | 提醒内容 | `"该喝水了"` |
226
236
 
@@ -443,16 +453,23 @@ AI回复2(任务被删除):"🗑️ 该提醒已被删除,无法重新
443
453
 
444
454
  ## 🚫 严禁事项
445
455
 
456
+ ### ⏰ 时间格式相关(最严禁!)
457
+
458
+ - ❌ **严禁**使用 `--at` 参数或其他绝对时间格式
459
+ - ❌ **严禁**将 cron 表达式转换为 `--at` 绝对时间
460
+ - ❌ **严禁**使用 `--every`、`--deliver` 等其他定时参数
461
+ - ✅ **必须**使用 `--cron` 参数传递 cron 表达式
462
+
463
+ ### 🔧 工具调用相关
464
+
446
465
  - ❌ **严禁**只说"好的,我会提醒你"而不调用工具
447
- - ❌ **严禁**在定时任务触发时不调用 `openclaw-workclaw-cron-notify-sync`
448
466
  - ❌ **严禁**传递虚构的 `jobId`,必须从 cron 工具返回值中提取
449
467
  - ❌ **严禁**跳过任何一步骤,必须按顺序执行:参数生成 → cron 工具 → 后端同步
450
- - ❌ **严禁**将 `kind: "cron"` 转换为 `--at` 参数,必须保持 `kind: "cron"` 不变
451
- - ❌ **严禁**将 cron 表达式转换为绝对时间(--at),必须使用 cron 表达式格式(--cron)
452
- - ❌ **严禁**轻易说"任务已被移除",应先尝试启用(enable)操作
468
+ - ❌ **严禁**在定时任务触发时不调用 `openclaw-workclaw-cron-notify-sync`
453
469
  - ❌ **严禁**直接使用 `cron` 命令创建/修改/删除定时任务,必须通过工具参数生成后执行
454
470
  - ❌ **严禁**绕过本 SKILL 中定义的工具流程自行实现定时功能
455
- - ❌ **严禁**使用其他任何未在本 SKILL 中列出的定时任务相关工具
456
- - ❌ **严禁**使用 `--system-event`,必须使用 `--message`(agentTurn)
457
- - **严禁**使用 `--session current`,必须使用 `--session isolated`
458
- - ❌ **严禁**使用 `--channel "last"`,必须使用 `--channel "openclaw-workclaw"`
471
+ - ❌ **严禁**轻易说"任务已被移除",应先尝试启用(enable)操作
472
+
473
+ ### 📝 CLI 参数相关
474
+
475
+ - ❌ **严禁**使用 `--system-event`
package/src/accounts.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import type { OpenClawConfig } from 'openclaw/plugin-sdk'
2
2
  import type {
3
- OpenclawWorkclawAccountConfig,
4
- OpenclawWorkclawConfig,
5
- ResolvedOpenclawWorkclawAccount,
3
+ ResolvedWorkclawAccount,
4
+ WorkclawAccountConfig,
5
+ WorkclawConfig,
6
6
  } from './types.js'
7
- import { getOpenclawWorkclawLogger } from './runtime.js'
8
-
9
- const DEFAULT_ACCOUNT_ID = 'default'
7
+ import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk/account-id'
8
+ import { getWorkclawLogger, getWorkclawConnectionConfig } from './runtime.js'
10
9
 
11
10
  // =============================================================================
12
11
  // In-memory lookup cache for (userId, agentId) → workspace-N accountId
@@ -52,16 +51,16 @@ export function buildAccountMap(cfg: OpenClawConfig): void {
52
51
  accountIndex.clear()
53
52
  nextWorkspaceNum = 1
54
53
 
55
- const _OpenclawWorkclawConfig = (cfg.channels?.['openclaw-workclaw'] || {}) as OpenclawWorkclawConfig
54
+ const _WorkclawConfig = (cfg.channels?.['openclaw-workclaw'] || {}) as WorkclawConfig
56
55
 
57
- const accounts = _OpenclawWorkclawConfig?.accounts
56
+ const accounts = _WorkclawConfig?.accounts
58
57
  if (!accounts || typeof accounts !== 'object')
59
58
  return
60
59
 
61
60
  for (const [accountId, accountCfg] of Object.entries(accounts)) {
62
61
  if (!accountCfg)
63
62
  continue
64
- const userId = accountCfg.userId || _OpenclawWorkclawConfig.userId
63
+ const userId = accountCfg.userId || _WorkclawConfig.userId
65
64
  const agentId = accountCfg.agentId
66
65
  if (!agentId)
67
66
  continue
@@ -82,7 +81,7 @@ export function buildAccountMap(cfg: OpenClawConfig): void {
82
81
  }
83
82
  }
84
83
 
85
- getOpenclawWorkclawLogger().info(`[AccountManager] Built account map with ${lookupCache.size} entries, nextWorkspaceNum=${nextWorkspaceNum}`)
84
+ getWorkclawLogger().info(`[AccountManager] Built account map with ${lookupCache.size} entries, nextWorkspaceNum=${nextWorkspaceNum}`)
86
85
  }
87
86
 
88
87
  /**
@@ -100,13 +99,13 @@ export function resolveAccountByUserIdAndAgentId(
100
99
  return lookupCache.get(key)!
101
100
  }
102
101
 
103
- const _OpenclawWorkclawConfig = (cfg.channels?.['openclaw-workclaw'] || {}) as OpenclawWorkclawConfig
104
- const accounts = _OpenclawWorkclawConfig?.accounts
102
+ const _WorkclawConfig = (cfg.channels?.['openclaw-workclaw'] || {}) as WorkclawConfig
103
+ const accounts = _WorkclawConfig?.accounts
105
104
  if (accounts && typeof accounts === 'object') {
106
105
  for (const [accountId, accountCfg] of Object.entries(accounts)) {
107
106
  if (!accountCfg)
108
107
  continue
109
- const accountUserId = accountCfg.userId || _OpenclawWorkclawConfig.userId
108
+ const accountUserId = accountCfg.userId || _WorkclawConfig.userId
110
109
  if (String(accountUserId) === userId && String(accountCfg.agentId) === agentId) {
111
110
  lookupCache.set(key, accountId)
112
111
  accountIndex.set(accountId, { userId, agentId })
@@ -133,7 +132,7 @@ export function allocateWorkerAccountId(
133
132
  lookupCache.set(key, accountId)
134
133
  accountIndex.set(accountId, { userId, agentId })
135
134
 
136
- getOpenclawWorkclawLogger().info(`[AccountManager] Allocated accountId=${accountId} for userId=${userId} agentId=${agentId}`)
135
+ getWorkclawLogger().info(`[AccountManager] Allocated accountId=${accountId} for userId=${userId} agentId=${agentId}`)
137
136
  return accountId
138
137
  }
139
138
 
@@ -152,7 +151,7 @@ export function refreshAccountCache(cfg: OpenClawConfig): void {
152
151
  * List all configured account IDs from the accounts field.
153
152
  */
154
153
  function listConfiguredAccountIds(cfg: OpenClawConfig): string[] {
155
- const accounts = (cfg.channels?.['openclaw-workclaw'] as OpenclawWorkclawConfig)?.accounts
154
+ const accounts = (cfg.channels?.['openclaw-workclaw'] as WorkclawConfig)?.accounts
156
155
  if (!accounts || typeof accounts !== 'object') {
157
156
  return []
158
157
  }
@@ -163,9 +162,9 @@ function listConfiguredAccountIds(cfg: OpenClawConfig): string[] {
163
162
  * Check if plugin-level workclaw config exists (appKey + appSecret)
164
163
  */
165
164
  function hasPluginLevelWorkClawConfig(cfg: OpenClawConfig): boolean {
166
- const openclawWorkclawCfg = cfg.channels?.['openclaw-workclaw'] as OpenclawWorkclawConfig | undefined
167
- const key = typeof openclawWorkclawCfg?.appKey === 'string' ? openclawWorkclawCfg.appKey.trim() : ''
168
- const secret = typeof openclawWorkclawCfg?.appSecret === 'string' ? openclawWorkclawCfg.appSecret.trim() : ''
165
+ const workclawCfg = cfg.channels?.['openclaw-workclaw'] as WorkclawConfig | undefined
166
+ const key = typeof workclawCfg?.appKey === 'string' ? workclawCfg.appKey.trim() : ''
167
+ const secret = typeof workclawCfg?.appSecret === 'string' ? workclawCfg.appSecret.trim() : ''
169
168
  return Boolean(key && secret)
170
169
  }
171
170
 
@@ -173,7 +172,7 @@ function hasPluginLevelWorkClawConfig(cfg: OpenClawConfig): boolean {
173
172
  * List all account IDs.
174
173
  * If no accounts are configured but plugin has workclaw config, returns [DEFAULT_ACCOUNT_ID].
175
174
  */
176
- export function listOpenclawWorkclawAccountIds(cfg: OpenClawConfig): string[] {
175
+ export function listWorkclawAccountIds(cfg: OpenClawConfig): string[] {
177
176
  const ids = listConfiguredAccountIds(cfg)
178
177
  if (ids.length === 0) {
179
178
  // 如果没有配置账户,但插件级别有 workclaw 配置,返回默认账户
@@ -189,8 +188,8 @@ export function listOpenclawWorkclawAccountIds(cfg: OpenClawConfig): string[] {
189
188
  /**
190
189
  * Resolve the default account ID.
191
190
  */
192
- export function resolveDefaultOpenclawWorkclawAccountId(cfg: OpenClawConfig): string {
193
- const ids = listOpenclawWorkclawAccountIds(cfg)
191
+ export function resolveDefaultWorkclawAccountId(cfg: OpenClawConfig): string {
192
+ const ids = listWorkclawAccountIds(cfg)
194
193
  if (ids.includes(DEFAULT_ACCOUNT_ID)) {
195
194
  return DEFAULT_ACCOUNT_ID
196
195
  }
@@ -203,8 +202,8 @@ export function resolveDefaultOpenclawWorkclawAccountId(cfg: OpenClawConfig): st
203
202
  function resolveAccountConfig(
204
203
  cfg: OpenClawConfig,
205
204
  accountId: string,
206
- ): OpenclawWorkclawAccountConfig | undefined {
207
- const accounts = (cfg.channels?.['openclaw-workclaw'] as OpenclawWorkclawConfig)?.accounts
205
+ ): WorkclawAccountConfig | undefined {
206
+ const accounts = (cfg.channels?.['openclaw-workclaw'] as WorkclawConfig)?.accounts
208
207
  if (!accounts || typeof accounts !== 'object') {
209
208
  return undefined
210
209
  }
@@ -223,7 +222,7 @@ function hasWorkClawConfig(config: {
223
222
  return Boolean(key && secret)
224
223
  }
225
224
 
226
- export function isOpenclawWorkclawAccountConfigured(config: {
225
+ export function isWorkclawAccountConfigured(config: {
227
226
  appKey?: string
228
227
  appSecret?: string
229
228
  agentId?: string | number
@@ -235,34 +234,34 @@ export function isOpenclawWorkclawAccountConfigured(config: {
235
234
  * Merge top-level config with account-specific config.
236
235
  * Account-specific fields override top-level fields.
237
236
  */
238
- function mergeOpenclawWorkclawAccountConfig(cfg: OpenClawConfig, accountId: string): OpenclawWorkclawConfig {
239
- const openclawWorkclawCfg = cfg.channels?.['openclaw-workclaw'] as OpenclawWorkclawConfig | undefined
237
+ function mergeWorkclawAccountConfig(cfg: OpenClawConfig, accountId: string): WorkclawConfig {
238
+ const workclawCfg = cfg.channels?.['openclaw-workclaw'] as WorkclawConfig | undefined
240
239
 
241
240
  // Extract base config (exclude accounts field to avoid recursion)
242
- const { accounts: _ignored, ...base } = openclawWorkclawCfg ?? {}
241
+ const { accounts: _ignored, ...base } = workclawCfg ?? {}
243
242
 
244
243
  // Get account-specific overrides
245
244
  const account = resolveAccountConfig(cfg, accountId) ?? {}
246
245
 
247
246
  // Merge: account config overrides base config
248
- return { ...base, ...account } as OpenclawWorkclawConfig
247
+ return { ...base, ...account } as WorkclawConfig
249
248
  }
250
249
 
251
250
  /**
252
251
  * Resolve a complete account with merged config.
253
252
  */
254
- export function resolveOpenclawWorkclawAccount(params: {
253
+ export function resolveWorkclawAccount(params: {
255
254
  cfg: OpenClawConfig
256
255
  accountId?: string | null
257
- }): ResolvedOpenclawWorkclawAccount {
256
+ }): ResolvedWorkclawAccount {
258
257
  const accountId = normalizeAccountId(params.accountId)
259
- const openclawWorkclawCfg = params.cfg.channels?.['openclaw-workclaw'] as OpenclawWorkclawConfig | undefined
258
+ const workclawCfg = params.cfg.channels?.['openclaw-workclaw'] as WorkclawConfig | undefined
260
259
 
261
260
  // Base enabled state (top-level)
262
- const baseEnabled = openclawWorkclawCfg?.enabled !== false
261
+ const baseEnabled = workclawCfg?.enabled !== false
263
262
 
264
263
  // Merge configs
265
- const merged = mergeOpenclawWorkclawAccountConfig(params.cfg, accountId)
264
+ const merged = mergeWorkclawAccountConfig(params.cfg, accountId)
266
265
 
267
266
  // Account-level enabled state
268
267
  const accountEnabled = merged.enabled !== false
@@ -271,8 +270,8 @@ export function resolveOpenclawWorkclawAccount(params: {
271
270
  return {
272
271
  accountId,
273
272
  enabled,
274
- configured: isOpenclawWorkclawAccountConfigured(merged),
275
- name: (merged as OpenclawWorkclawAccountConfig).name?.trim() || undefined,
273
+ configured: isWorkclawAccountConfigured(merged),
274
+ name: (merged as WorkclawAccountConfig).name?.trim() || undefined,
276
275
  config: merged,
277
276
  }
278
277
  }
@@ -280,8 +279,34 @@ export function resolveOpenclawWorkclawAccount(params: {
280
279
  /**
281
280
  * List all enabled and configured accounts.
282
281
  */
283
- export function listEnabledOpenclawWorkclawAccounts(cfg: OpenClawConfig): ResolvedOpenclawWorkclawAccount[] {
282
+ export function listEnabledWorkclawAccounts(cfg: OpenClawConfig): ResolvedWorkclawAccount[] {
284
283
  return listConfiguredAccountIds(cfg)
285
- .map(accountId => resolveOpenclawWorkclawAccount({ cfg, accountId }))
284
+ .map(accountId => resolveWorkclawAccount({ cfg, accountId }))
286
285
  .filter(account => account.enabled && account.configured)
287
286
  }
287
+
288
+ /**
289
+ * Resolve account and apply cached connection config if available.
290
+ * This ensures that appKey/appSecret etc. use the stable values captured at connection time,
291
+ * not potentially mutated values from cfg at resolution time.
292
+ */
293
+ export function resolveWorkclawAccountWithCache(params: {
294
+ cfg: OpenClawConfig
295
+ accountId?: string | null
296
+ }): ResolvedWorkclawAccount {
297
+ const account = resolveWorkclawAccount(params);
298
+ const cachedConfig = getWorkclawConnectionConfig(account.accountId);
299
+
300
+ if (cachedConfig) {
301
+ // Apply cached values to ensure stability
302
+ account.config.appKey = cachedConfig.appKey || account.config.appKey;
303
+ account.config.appSecret = cachedConfig.appSecret || account.config.appSecret;
304
+ if (cachedConfig.baseUrl) account.config.baseUrl = cachedConfig.baseUrl;
305
+ if (cachedConfig.websocketUrl !== undefined) account.config.websocketUrl = cachedConfig.websocketUrl;
306
+ if (cachedConfig.localIp !== undefined) account.config.localIp = cachedConfig.localIp;
307
+ if (cachedConfig.allowInsecureTls !== undefined) account.config.allowInsecureTls = cachedConfig.allowInsecureTls;
308
+ if (cachedConfig.requestTimeout !== undefined) account.config.requestTimeout = cachedConfig.requestTimeout;
309
+ }
310
+
311
+ return account;
312
+ }
@@ -1,55 +1,55 @@
1
- import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
2
- import { Buffer } from 'node:buffer'
3
- import { normalizeAccountId } from '../accounts.js'
4
-
5
- function sendJson(res: any, statusCode: number, payload: unknown): void {
6
- res.statusCode = statusCode
7
- res.setHeader('Content-Type', 'application/json')
8
- res.end(JSON.stringify(payload))
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { normalizeAccountId } from "../accounts.js";
3
+
4
+
5
+ function sendJson(res: any, statusCode: number, payload: unknown) {
6
+ res.statusCode = statusCode;
7
+ res.setHeader("Content-Type", "application/json");
8
+ res.end(JSON.stringify(payload));
9
9
  }
10
10
 
11
11
  async function readRequestBody(req: any): Promise<string> {
12
- const chunks: Buffer[] = []
12
+ const chunks: Buffer[] = [];
13
13
  await new Promise<void>((resolve, reject) => {
14
- req.on('data', (chunk: any) => {
15
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
16
- })
17
- req.on('end', () => resolve())
18
- req.on('error', (err: unknown) => reject(err))
19
- })
20
- return Buffer.concat(chunks).toString('utf-8')
14
+ req.on("data", (chunk: any) => {
15
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
16
+ });
17
+ req.on("end", () => resolve());
18
+ req.on("error", (err: unknown) => reject(err));
19
+ });
20
+ return Buffer.concat(chunks).toString("utf-8");
21
21
  }
22
22
 
23
23
  async function loadRuntimeConfig(api: OpenClawPluginApi): Promise<any> {
24
- const runtimeConfig = (api.runtime as any)?.config
24
+ const runtimeConfig = (api.runtime as any)?.config;
25
25
  if (runtimeConfig?.loadConfig) {
26
- return runtimeConfig.loadConfig()
26
+ return runtimeConfig.loadConfig();
27
27
  }
28
- return api.config
28
+ return api.config;
29
29
  }
30
30
 
31
31
  async function writeRuntimeConfig(api: OpenClawPluginApi, config: any): Promise<void> {
32
- const runtimeConfig = (api.runtime as any)?.config
32
+ const runtimeConfig = (api.runtime as any)?.config;
33
33
  if (runtimeConfig?.writeConfigFile) {
34
- await runtimeConfig.writeConfigFile(config)
35
- return
34
+ await runtimeConfig.writeConfigFile(config);
35
+ return;
36
36
  }
37
- throw new Error('Config write not supported')
37
+ throw new Error("Config write not supported");
38
38
  }
39
39
 
40
40
  function getAccountMap(config: any): Record<string, any> {
41
- const channels = config?.channels && typeof config.channels === 'object' ? config.channels : {}
42
- const openclawWorkclaw
43
- = channels['openclaw-workclaw'] && typeof channels['openclaw-workclaw'] === 'object' ? channels['openclaw-workclaw'] : {}
44
- const accounts
45
- = openclawWorkclaw.accounts && typeof openclawWorkclaw.accounts === 'object' ? openclawWorkclaw.accounts : {}
46
- return { ...accounts }
41
+ const channels = config?.channels && typeof config.channels === "object" ? config.channels : {};
42
+ const openclawWorkclaw =
43
+ channels['openclaw-workclaw'] && typeof channels['openclaw-workclaw'] === "object" ? channels['openclaw-workclaw'] : {};
44
+ const accounts =
45
+ openclawWorkclaw.accounts && typeof openclawWorkclaw.accounts === "object" ? openclawWorkclaw.accounts : {};
46
+ return { ...accounts };
47
47
  }
48
48
 
49
49
  function withAccounts(config: any, accounts: Record<string, any>): any {
50
- const channels = config?.channels && typeof config.channels === 'object' ? config.channels : {}
51
- const openclawWorkclaw
52
- = channels['openclaw-workclaw'] && typeof channels['openclaw-workclaw'] === 'object' ? channels['openclaw-workclaw'] : {}
50
+ const channels = config?.channels && typeof config.channels === "object" ? config.channels : {};
51
+ const openclawWorkclaw =
52
+ channels['openclaw-workclaw'] && typeof channels['openclaw-workclaw'] === "object" ? channels['openclaw-workclaw'] : {};
53
53
  return {
54
54
  ...config,
55
55
  channels: {
@@ -59,99 +59,98 @@ function withAccounts(config: any, accounts: Record<string, any>): any {
59
59
  accounts,
60
60
  },
61
61
  },
62
- }
62
+ };
63
63
  }
64
64
 
65
65
  export function createAccountsApiHandler(api: OpenClawPluginApi) {
66
66
  return async (req: any, res: any) => {
67
- const method = String(req.method ?? 'GET').toUpperCase()
68
- const url = new URL(req.url ?? '', 'http://localhost')
69
- const idRaw = url.searchParams.get('id') ?? url.searchParams.get('accountId')
70
- const accountId = idRaw ? normalizeAccountId(idRaw) : null
67
+ const method = String(req.method ?? "GET").toUpperCase();
68
+ const url = new URL(req.url ?? "", "http://localhost");
69
+ const idRaw = url.searchParams.get("id") ?? url.searchParams.get("accountId");
70
+ const accountId = idRaw ? normalizeAccountId(idRaw) : null;
71
71
 
72
- if (method === 'GET') {
73
- const cfg = await loadRuntimeConfig(api)
74
- const accounts = getAccountMap(cfg)
72
+ if (method === "GET") {
73
+ const cfg = await loadRuntimeConfig(api);
74
+ const accounts = getAccountMap(cfg);
75
75
 
76
76
  if (accountId) {
77
- const account = accounts[accountId]
77
+ const account = accounts[accountId];
78
78
  if (!account) {
79
- sendJson(res, 404, { ok: false, error: 'Not Found' })
80
- return
79
+ sendJson(res, 404, { ok: false, error: "Not Found" });
80
+ return;
81
81
  }
82
- sendJson(res, 200, { ok: true, accountId, config: account })
83
- return
82
+ sendJson(res, 200, { ok: true, accountId, config: account });
83
+ return;
84
84
  }
85
85
 
86
86
  const entries = Object.entries(accounts).map(([id, config]) => ({
87
87
  accountId: id,
88
88
  config,
89
- }))
90
- sendJson(res, 200, { ok: true, accounts: entries })
91
- return
89
+ }));
90
+ sendJson(res, 200, { ok: true, accounts: entries });
91
+ return;
92
92
  }
93
93
 
94
- if (method === 'POST' || method === 'PUT') {
95
- const raw = await readRequestBody(req)
96
- let input: any = {}
94
+ if (method === "POST" || method === "PUT") {
95
+ const raw = await readRequestBody(req);
96
+ let input: any = {};
97
97
  try {
98
- input = raw ? JSON.parse(raw) : {}
99
- }
100
- catch {
101
- sendJson(res, 400, { ok: false, error: 'Invalid JSON' })
102
- return
98
+ input = raw ? JSON.parse(raw) : {};
99
+ } catch {
100
+ sendJson(res, 400, { ok: false, error: "Invalid JSON" });
101
+ return;
103
102
  }
104
103
 
105
- const inputId = input?.accountId ?? input?.id
106
- const normalizedId = inputId ? normalizeAccountId(String(inputId)) : ''
107
- const patch = input?.config
104
+ const inputId = input?.accountId ?? input?.id;
105
+ const normalizedId = inputId ? normalizeAccountId(String(inputId)) : "";
106
+ const patch = input?.config;
108
107
 
109
- if (!normalizedId || !patch || typeof patch !== 'object') {
110
- sendJson(res, 400, { ok: false, error: 'Missing accountId or config' })
111
- return
108
+ if (!normalizedId || !patch || typeof patch !== "object") {
109
+ sendJson(res, 400, { ok: false, error: "Missing accountId or config" });
110
+ return;
112
111
  }
113
112
 
114
- const cfg = await loadRuntimeConfig(api)
115
- const accounts = getAccountMap(cfg)
113
+ const cfg = await loadRuntimeConfig(api);
114
+ const accounts = getAccountMap(cfg);
116
115
 
117
- if (method === 'POST' && accounts[normalizedId]) {
118
- sendJson(res, 409, { ok: false, error: 'Account already exists' })
119
- return
116
+ if (method === "POST" && accounts[normalizedId]) {
117
+ sendJson(res, 409, { ok: false, error: "Account already exists" });
118
+ return;
120
119
  }
121
120
 
122
- if (method === 'PUT' && !accounts[normalizedId]) {
123
- sendJson(res, 404, { ok: false, error: 'Not Found' })
124
- return
121
+ if (method === "PUT" && !accounts[normalizedId]) {
122
+ sendJson(res, 404, { ok: false, error: "Not Found" });
123
+ return;
125
124
  }
126
125
 
127
- const next = method === 'PUT' ? { ...accounts[normalizedId], ...patch } : patch
128
- const updated = withAccounts(cfg, { ...accounts, [normalizedId]: next })
129
- await writeRuntimeConfig(api, updated)
130
- sendJson(res, 200, { ok: true, accountId: normalizedId })
131
- return
126
+ const next = method === "PUT" ? { ...accounts[normalizedId], ...patch } : patch;
127
+ const updated = withAccounts(cfg, { ...accounts, [normalizedId]: next });
128
+ await writeRuntimeConfig(api, updated);
129
+ sendJson(res, 200, { ok: true, accountId: normalizedId });
130
+ return;
132
131
  }
133
132
 
134
- if (method === 'DELETE') {
133
+ if (method === "DELETE") {
135
134
  if (!accountId) {
136
- sendJson(res, 400, { ok: false, error: 'Missing accountId' })
137
- return
135
+ sendJson(res, 400, { ok: false, error: "Missing accountId" });
136
+ return;
138
137
  }
139
138
 
140
- const cfg = await loadRuntimeConfig(api)
141
- const accounts = getAccountMap(cfg)
139
+ const cfg = await loadRuntimeConfig(api);
140
+ const accounts = getAccountMap(cfg);
142
141
 
143
142
  if (!accounts[accountId]) {
144
- sendJson(res, 404, { ok: false, error: 'Not Found' })
145
- return
143
+ sendJson(res, 404, { ok: false, error: "Not Found" });
144
+ return;
146
145
  }
147
146
 
148
- const { [accountId]: _ignored, ...rest } = accounts
149
- const updated = withAccounts(cfg, rest)
150
- await writeRuntimeConfig(api, updated)
151
- sendJson(res, 200, { ok: true, accountId, deleted: true })
152
- return
147
+ const { [accountId]: _ignored, ...rest } = accounts;
148
+ const updated = withAccounts(cfg, rest);
149
+ await writeRuntimeConfig(api, updated);
150
+ sendJson(res, 200, { ok: true, accountId, deleted: true });
151
+ return;
153
152
  }
154
153
 
155
- sendJson(res, 405, { ok: false, error: 'Method Not Allowed' })
156
- }
154
+ sendJson(res, 405, { ok: false, error: "Method Not Allowed" });
155
+ };
157
156
  }