@workclaw/openclaw-workclaw 1.0.17 → 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 +11 -4
  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
@@ -2,310 +2,295 @@
2
2
  * Config Writer - handles writing account config to openclaw.json
3
3
  */
4
4
 
5
- import { existsSync } from 'node:fs'
6
- import { readFile, writeFile } from 'node:fs/promises'
7
- import { homedir } from 'node:os'
8
- import { join } from 'node:path'
9
- import process from 'node:process'
5
+ import { writeFile, readFile } from "fs/promises";
6
+ import { existsSync } from "fs";
7
+ import { join } from "path";
8
+ import { homedir } from "os";
10
9
 
11
- interface ConfigLogger {
12
- info?: (msg: string) => void
13
- error?: (msg: string) => void
14
- }
10
+ type ConfigLogger = {
11
+ info?: (msg: string) => void;
12
+ error?: (msg: string) => void;
13
+ };
15
14
 
16
15
  /**
17
16
  * Find the actual openclaw.json config path.
18
17
  */
19
18
  function findConfigPath(): string {
20
- // Prefer homedir config over cwd, since cwd might be the project directory
21
- // which could have a stale or unrelated openclaw.json
22
- const configPaths = [
23
- join(homedir(), '.openclaw', 'openclaw.json'),
24
- join(homedir(), '.openclaw', 'config.json'),
25
- join(process.cwd(), 'openclaw.json'),
26
- join(process.cwd(), 'config.json'),
27
- ]
19
+ // Prefer homedir config over cwd, since cwd might be the project directory
20
+ // which could have a stale or unrelated openclaw.json
21
+ const configPaths = [
22
+ join(homedir(), ".openclaw", "openclaw.json"),
23
+ join(homedir(), ".openclaw", "config.json"),
24
+ join(process.cwd(), "openclaw.json"),
25
+ join(process.cwd(), "config.json"),
26
+ ];
28
27
 
29
- const found = configPaths.find(p => existsSync(p))
30
- return found ?? join(homedir(), '.openclaw', 'openclaw.json')
28
+ const found = configPaths.find((p) => existsSync(p));
29
+ return found ?? join(homedir(), ".openclaw", "openclaw.json");
31
30
  }
32
31
 
33
32
  /**
34
33
  * Write a new config to the openclaw.json file.
35
34
  */
36
35
  export async function writeConfigFile(
37
- newConfig: any,
38
- cfg: any,
39
- log?: ConfigLogger,
36
+ newConfig: any,
37
+ cfg: any,
38
+ log?: ConfigLogger,
40
39
  ): Promise<void> {
41
- const configPath = findConfigPath()
40
+ const configPath = findConfigPath();
42
41
 
43
- try {
44
- let existingConfig: any = {}
45
- if (existsSync(configPath)) {
46
- try {
47
- const content = await readFile(configPath, 'utf-8')
48
- existingConfig = JSON.parse(content)
49
- }
50
- catch (parseErr) {
51
- log?.error?.(`[WriteConfig] Failed to parse existing config: ${String(parseErr)}`)
52
- }
53
- }
42
+ try {
43
+ let existingConfig: any = {};
44
+ if (existsSync(configPath)) {
45
+ try {
46
+ const content = await readFile(configPath, "utf-8");
47
+ existingConfig = JSON.parse(content);
48
+ } catch (parseErr) {
49
+ log?.error?.(`[WriteConfig] Failed to parse existing config: ${String(parseErr)}`);
50
+ }
51
+ }
54
52
 
55
- const mergedConfig = { ...existingConfig, ...newConfig }
56
- await writeFile(configPath, JSON.stringify(mergedConfig, null, 2), 'utf-8')
57
- log?.info?.(`[WriteConfig] Config written to ${configPath}`)
58
- Object.assign(cfg, newConfig)
59
- }
60
- catch (err) {
61
- log?.error?.(`[WriteConfig] Failed to write config: ${String(err)}`)
62
- Object.assign(cfg, newConfig)
63
- throw err
64
- }
53
+ const mergedConfig = { ...existingConfig, ...newConfig };
54
+ await writeFile(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
55
+ log?.info?.(`[WriteConfig] Config written to ${configPath}`);
56
+ Object.assign(cfg, newConfig);
57
+ } catch (err) {
58
+ log?.error?.(`[WriteConfig] Failed to write config: ${String(err)}`);
59
+ Object.assign(cfg, newConfig);
60
+ throw err;
61
+ }
65
62
  }
66
63
 
67
64
  /**
68
65
  * Save openConversationId to account config.
69
66
  */
70
67
  export async function saveOpenConversationId(
71
- accountId: string,
72
- openConversationId: string,
73
- cfg: any,
74
- log?: ConfigLogger,
68
+ accountId: string,
69
+ openConversationId: string,
70
+ cfg: any,
71
+ log?: ConfigLogger,
75
72
  ): Promise<void> {
76
- try {
77
- const configPath = findConfigPath()
78
- let existingConfig: any = {}
79
- if (existsSync(configPath)) {
80
- try {
81
- const content = await readFile(configPath, 'utf-8')
82
- existingConfig = JSON.parse(content)
83
- }
84
- catch (parseErr) {
85
- log?.error?.(`SaveConversation: Failed to parse existing config: ${String(parseErr)}`)
86
- }
87
- }
73
+ try {
74
+ const configPath = findConfigPath();
75
+ let existingConfig: any = {};
76
+ if (existsSync(configPath)) {
77
+ try {
78
+ const content = await readFile(configPath, "utf-8");
79
+ existingConfig = JSON.parse(content);
80
+ } catch (parseErr) {
81
+ log?.error?.(`SaveConversation: Failed to parse existing config: ${String(parseErr)}`);
82
+ }
83
+ }
88
84
 
89
- const channels = existingConfig?.channels && typeof existingConfig.channels === 'object' ? existingConfig.channels : {}
90
- const openclawWorkclaw
91
- = channels['openclaw-workclaw'] && typeof channels['openclaw-workclaw'] === 'object' ? channels['openclaw-workclaw'] : {}
92
- const accounts
93
- = openclawWorkclaw.accounts && typeof openclawWorkclaw.accounts === 'object' ? { ...openclawWorkclaw.accounts } : {}
85
+ const channels = existingConfig?.channels && typeof existingConfig.channels === "object" ? existingConfig.channels : {};
86
+ const openclawWorkclaw =
87
+ channels['openclaw-workclaw'] && typeof channels['openclaw-workclaw'] === "object" ? channels['openclaw-workclaw'] : {};
88
+ const accounts =
89
+ openclawWorkclaw.accounts && typeof openclawWorkclaw.accounts === "object" ? { ...openclawWorkclaw.accounts } : {};
94
90
 
95
- let savedToAccount: string | null = null
91
+ let savedToAccount: string | null = null;
96
92
 
97
- if (accounts[accountId]) {
98
- accounts[accountId] = {
99
- ...accounts[accountId],
100
- openConversationId,
101
- }
102
- savedToAccount = accountId
103
- }
104
- else {
105
- const defaultAccount = accounts.default
106
- if (defaultAccount) {
107
- defaultAccount.openConversationId = openConversationId
108
- savedToAccount = 'default'
109
- log?.info?.(`SaveConversation: Saved openConversationId ${openConversationId} to default account`)
110
- }
111
- else {
112
- log?.info?.(`SaveConversation: Account ${accountId} not found, default account also missing`)
113
- }
114
- }
93
+ if (accounts[accountId]) {
94
+ accounts[accountId] = {
95
+ ...accounts[accountId],
96
+ openConversationId,
97
+ };
98
+ savedToAccount = accountId;
99
+ } else {
100
+ const defaultAccount = accounts["default"];
101
+ if (defaultAccount) {
102
+ defaultAccount.openConversationId = openConversationId;
103
+ savedToAccount = "default";
104
+ log?.info?.(`SaveConversation: Saved openConversationId ${openConversationId} to default account`);
105
+ } else {
106
+ log?.info?.(`SaveConversation: Account ${accountId} not found, default account also missing`);
107
+ }
108
+ }
115
109
 
116
- if (!savedToAccount) {
117
- return
118
- }
110
+ if (!savedToAccount) {
111
+ return;
112
+ }
119
113
 
120
- const mergedConfig = {
121
- ...existingConfig,
122
- channels: {
123
- ...channels,
124
- 'openclaw-workclaw': {
125
- ...openclawWorkclaw,
126
- accounts,
127
- },
128
- },
129
- }
114
+ const mergedConfig = {
115
+ ...existingConfig,
116
+ channels: {
117
+ ...channels,
118
+ 'openclaw-workclaw': {
119
+ ...openclawWorkclaw,
120
+ accounts,
121
+ },
122
+ },
123
+ };
130
124
 
131
- await writeFile(configPath, JSON.stringify(mergedConfig, null, 2), 'utf-8')
132
- log?.info?.(`SaveConversation: Saved openConversationId to ${configPath} for account ${savedToAccount}`)
133
- Object.assign(cfg, mergedConfig)
134
- }
135
- catch (err) {
136
- log?.error?.(`SaveConversation: Failed to write config: ${String(err)}`)
137
- throw err
138
- }
125
+ await writeFile(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
126
+ log?.info?.(`SaveConversation: Saved openConversationId to ${configPath} for account ${savedToAccount}`);
127
+ Object.assign(cfg, mergedConfig);
128
+ } catch (err) {
129
+ log?.error?.(`SaveConversation: Failed to write config: ${String(err)}`);
130
+ throw err;
131
+ }
139
132
  }
140
133
 
141
134
  /**
142
135
  * Save userId to openclawWorkclaw config.
143
136
  */
144
137
  export async function saveWorkClawUserId(
145
- accountId: string,
146
- userId: string | number,
147
- cfg: any,
148
- log?: ConfigLogger,
138
+ accountId: string,
139
+ userId: string | number,
140
+ cfg: any,
141
+ log?: ConfigLogger,
149
142
  ): Promise<void> {
150
- try {
151
- const configPath = findConfigPath()
152
- let existingConfig: any = {}
153
- if (existsSync(configPath)) {
154
- try {
155
- const content = await readFile(configPath, 'utf-8')
156
- existingConfig = JSON.parse(content)
157
- }
158
- catch (parseErr) {
159
- log?.error?.(`SaveUserId: Failed to parse existing config: ${String(parseErr)}`)
160
- }
161
- }
143
+ try {
144
+ const configPath = findConfigPath();
145
+ let existingConfig: any = {};
146
+ if (existsSync(configPath)) {
147
+ try {
148
+ const content = await readFile(configPath, "utf-8");
149
+ existingConfig = JSON.parse(content);
150
+ } catch (parseErr) {
151
+ log?.error?.(`SaveUserId: Failed to parse existing config: ${String(parseErr)}`);
152
+ }
153
+ }
162
154
 
163
- const channels = existingConfig?.channels && typeof existingConfig.channels === 'object' ? existingConfig.channels : {}
164
- const openclawWorkclaw
165
- = channels['openclaw-workclaw'] && typeof channels['openclaw-workclaw'] === 'object' ? channels['openclaw-workclaw'] : {}
155
+ const channels = existingConfig?.channels && typeof existingConfig.channels === "object" ? existingConfig.channels : {};
156
+ const openclawWorkclaw =
157
+ channels['openclaw-workclaw'] && typeof channels['openclaw-workclaw'] === "object" ? channels['openclaw-workclaw'] : {};
166
158
 
167
- const mergedConfig = {
168
- ...existingConfig,
169
- channels: {
170
- ...channels,
171
- 'openclaw-workclaw': {
172
- ...openclawWorkclaw,
173
- userId,
174
- },
175
- },
176
- }
159
+ const mergedConfig = {
160
+ ...existingConfig,
161
+ channels: {
162
+ ...channels,
163
+ 'openclaw-workclaw': {
164
+ ...openclawWorkclaw,
165
+ userId: userId,
166
+ },
167
+ },
168
+ };
177
169
 
178
- await writeFile(configPath, JSON.stringify(mergedConfig, null, 2), 'utf-8')
179
- log?.info?.(`SaveUserId: Saved userId ${userId} to ${configPath} for account ${accountId}`)
180
- Object.assign(cfg, mergedConfig)
181
- }
182
- catch (err) {
183
- log?.error?.(`SaveUserId: Failed to write config: ${String(err)}`)
184
- throw err
185
- }
170
+ await writeFile(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
171
+ log?.info?.(`SaveUserId: Saved userId ${userId} to ${configPath} for account ${accountId}`);
172
+ Object.assign(cfg, mergedConfig);
173
+ } catch (err) {
174
+ log?.error?.(`SaveUserId: Failed to write config: ${String(err)}`);
175
+ throw err;
176
+ }
186
177
  }
187
178
 
188
179
  /**
189
180
  * Save agentId to account config.
190
181
  */
191
182
  export async function saveWorkClawAgentId(
192
- accountId: string,
193
- agentId: string | number,
194
- cfg: any,
195
- log?: ConfigLogger,
183
+ accountId: string,
184
+ agentId: string | number,
185
+ cfg: any,
186
+ log?: ConfigLogger,
196
187
  ): Promise<void> {
197
- try {
198
- const configPath = findConfigPath()
199
- let existingConfig: any = {}
200
- if (existsSync(configPath)) {
201
- try {
202
- const content = await readFile(configPath, 'utf-8')
203
- existingConfig = JSON.parse(content)
204
- }
205
- catch (parseErr) {
206
- log?.error?.(`SaveAgentId: Failed to parse existing config: ${String(parseErr)}`)
207
- }
208
- }
188
+ try {
189
+ const configPath = findConfigPath();
190
+ let existingConfig: any = {};
191
+ if (existsSync(configPath)) {
192
+ try {
193
+ const content = await readFile(configPath, "utf-8");
194
+ existingConfig = JSON.parse(content);
195
+ } catch (parseErr) {
196
+ log?.error?.(`SaveAgentId: Failed to parse existing config: ${String(parseErr)}`);
197
+ }
198
+ }
209
199
 
210
- const channels = existingConfig?.channels && typeof existingConfig.channels === 'object' ? existingConfig.channels : {}
211
- const openclawWorkclaw
212
- = channels['openclaw-workclaw'] && typeof channels['openclaw-workclaw'] === 'object' ? channels['openclaw-workclaw'] : {}
213
- const accounts
214
- = openclawWorkclaw.accounts && typeof openclawWorkclaw.accounts === 'object' ? { ...openclawWorkclaw.accounts } : {}
200
+ const channels = existingConfig?.channels && typeof existingConfig.channels === "object" ? existingConfig.channels : {};
201
+ const openclawWorkclaw =
202
+ channels['openclaw-workclaw'] && typeof channels['openclaw-workclaw'] === "object" ? channels['openclaw-workclaw'] : {};
203
+ const accounts =
204
+ openclawWorkclaw.accounts && typeof openclawWorkclaw.accounts === "object" ? { ...openclawWorkclaw.accounts } : {};
215
205
 
216
- let savedToAccount: string | null = null
206
+ let savedToAccount: string | null = null;
217
207
 
218
- if (accounts[accountId]) {
219
- accounts[accountId] = {
220
- ...accounts[accountId],
221
- agentId,
222
- }
223
- savedToAccount = accountId
224
- }
225
- else {
226
- const defaultAccount = accounts.default
227
- if (defaultAccount) {
228
- if (!defaultAccount.agentId) {
229
- defaultAccount.agentId = agentId
230
- savedToAccount = 'default'
231
- log?.info?.(`SaveAgentId: Saved agentId ${agentId} to default account`)
208
+ if (accounts[accountId]) {
209
+ accounts[accountId] = {
210
+ ...accounts[accountId],
211
+ agentId: agentId,
212
+ };
213
+ savedToAccount = accountId;
214
+ } else {
215
+ const defaultAccount = accounts["default"];
216
+ if (defaultAccount) {
217
+ if (!defaultAccount.agentId) {
218
+ defaultAccount.agentId = agentId;
219
+ savedToAccount = "default";
220
+ log?.info?.(`SaveAgentId: Saved agentId ${agentId} to default account`);
221
+ }
222
+ } else {
223
+ log?.info?.(`SaveAgentId: Account ${accountId} not found, default account also missing`);
224
+ }
232
225
  }
233
- }
234
- else {
235
- log?.info?.(`SaveAgentId: Account ${accountId} not found, default account also missing`)
236
- }
237
- }
238
226
 
239
- if (!savedToAccount) {
240
- return
241
- }
227
+ if (!savedToAccount) {
228
+ return;
229
+ }
242
230
 
243
- const mergedConfig = {
244
- ...existingConfig,
245
- channels: {
246
- ...channels,
247
- 'openclaw-workclaw': {
248
- ...openclawWorkclaw,
249
- accounts,
250
- },
251
- },
252
- }
231
+ const mergedConfig = {
232
+ ...existingConfig,
233
+ channels: {
234
+ ...channels,
235
+ 'openclaw-workclaw': {
236
+ ...openclawWorkclaw,
237
+ accounts,
238
+ },
239
+ },
240
+ };
253
241
 
254
- await writeFile(configPath, JSON.stringify(mergedConfig, null, 2), 'utf-8')
255
- log?.info?.(`SaveAgentId: Saved agentId ${agentId} to ${configPath} for account ${savedToAccount}`)
256
- Object.assign(cfg, mergedConfig)
257
- }
258
- catch (err) {
259
- log?.error?.(`SaveAgentId: Failed to write config: ${String(err)}`)
260
- throw err
261
- }
242
+ await writeFile(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
243
+ log?.info?.(`SaveAgentId: Saved agentId ${agentId} to ${configPath} for account ${savedToAccount}`);
244
+ Object.assign(cfg, mergedConfig);
245
+ } catch (err) {
246
+ log?.error?.(`SaveAgentId: Failed to write config: ${String(err)}`);
247
+ throw err;
248
+ }
262
249
  }
263
250
 
264
251
  /**
265
252
  * Save apiKey to models configuration
266
253
  */
267
254
  export async function saveWorkClawApiKey(
268
- apiKey: string,
269
- cfg: any,
270
- log?: ConfigLogger,
255
+ apiKey: string,
256
+ cfg: any,
257
+ log?: ConfigLogger,
271
258
  ): Promise<void> {
272
- try {
273
- const configPath = findConfigPath()
274
- let existingConfig: any = {}
275
- if (existsSync(configPath)) {
276
- try {
277
- const content = await readFile(configPath, 'utf-8')
278
- existingConfig = JSON.parse(content)
279
- }
280
- catch (parseErr) {
281
- log?.error?.(`SaveApiKey: Failed to parse existing config: ${String(parseErr)}`)
282
- }
283
- }
259
+ try {
260
+ const configPath = findConfigPath();
261
+ let existingConfig: any = {};
262
+ if (existsSync(configPath)) {
263
+ try {
264
+ const content = await readFile(configPath, "utf-8");
265
+ existingConfig = JSON.parse(content);
266
+ } catch (parseErr) {
267
+ log?.error?.(`SaveApiKey: Failed to parse existing config: ${String(parseErr)}`);
268
+ }
269
+ }
284
270
 
285
- const existingModels = existingConfig.models || {}
286
- const existingProviders = existingModels.providers || {}
287
- const existingSophnetMinimax = existingProviders['sophnet-minimax'] || {}
271
+ const existingModels = existingConfig.models || {};
272
+ const existingProviders = existingModels.providers || {};
273
+ const existingSophnetMinimax = existingProviders["sophnet-minimax"] || {};
288
274
 
289
- const mergedConfig = {
290
- ...existingConfig,
291
- models: {
292
- ...existingModels,
293
- providers: {
294
- ...existingProviders,
295
- 'sophnet-minimax': {
296
- ...existingSophnetMinimax,
297
- apiKey,
298
- },
299
- },
300
- },
301
- }
275
+ const mergedConfig = {
276
+ ...existingConfig,
277
+ models: {
278
+ ...existingModels,
279
+ providers: {
280
+ ...existingProviders,
281
+ "sophnet-minimax": {
282
+ ...existingSophnetMinimax,
283
+ apiKey: apiKey,
284
+ },
285
+ },
286
+ },
287
+ };
302
288
 
303
- await writeFile(configPath, JSON.stringify(mergedConfig, null, 2), 'utf-8')
304
- log?.info?.(`SaveApiKey: Saved apiKey to ${configPath}`)
305
- Object.assign(cfg, mergedConfig)
306
- }
307
- catch (err) {
308
- log?.error?.(`SaveApiKey: Failed to write config: ${String(err)}`)
309
- throw err
310
- }
289
+ await writeFile(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
290
+ log?.info?.(`SaveApiKey: Saved apiKey to ${configPath}`);
291
+ Object.assign(cfg, mergedConfig);
292
+ } catch (err) {
293
+ log?.error?.(`SaveApiKey: Failed to write config: ${String(err)}`);
294
+ throw err;
295
+ }
311
296
  }