@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.
- package/README.md +21 -1
- package/index.ts +210 -210
- package/openclaw.plugin.json +1 -0
- package/package.json +11 -4
- package/setup-entry.ts +6 -0
- package/skills/openclaw-workclaw-cron/SKILL.md +45 -28
- package/src/accounts.ts +62 -37
- package/src/api/accounts-api.ts +88 -89
- package/src/api/prompts-api.ts +70 -77
- package/src/api/session-api.ts +99 -108
- package/src/api/skills-api.ts +35 -37
- package/src/api/workspace.ts +27 -29
- package/src/channel.ts +200 -202
- package/src/config-schema.ts +9 -9
- package/src/connection/workclaw-client.ts +554 -567
- package/src/gateway/agent-handlers.ts +392 -426
- package/src/gateway/config-writer.ts +228 -243
- package/src/gateway/message-context.ts +534 -362
- package/src/gateway/message-dispatcher.ts +529 -489
- package/src/gateway/reconnect.ts +217 -113
- package/src/gateway/skills-handler.ts +408 -472
- package/src/gateway/skills-list-handler.ts +9 -9
- package/src/gateway/tools-list-handler.ts +70 -72
- package/src/gateway/workclaw-gateway.ts +328 -486
- package/src/media/upload.ts +83 -94
- package/src/outbound/index.ts +57 -55
- package/src/outbound/workclaw-sender.ts +134 -133
- package/src/runtime.ts +291 -194
- package/src/send.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/api/index.ts +6 -6
- package/src/tools/openclaw-workclaw-cron/src/add/params.ts +20 -19
- package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +2 -2
- package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +2 -2
- package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/update/params.ts +195 -197
- package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +4 -4
- package/src/tools/openclaw-workclaw-system/src/get/index.ts +2 -2
- package/src/tools/openclaw-workclaw-system/src/token/index.ts +4 -4
- package/src/types.ts +38 -40
- package/src/utils/content.ts +16 -21
- package/tests/accounts.test.ts +285 -0
- package/tests/message-context.test.ts +313 -0
- package/tests/reconnect.test.ts +257 -0
- package/tests/workclaw-client.test.ts +112 -0
- package/tsconfig.json +8 -5
- 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 {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
newConfig: any,
|
|
37
|
+
cfg: any,
|
|
38
|
+
log?: ConfigLogger,
|
|
40
39
|
): Promise<void> {
|
|
41
|
-
|
|
40
|
+
const configPath = findConfigPath();
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
accountId: string,
|
|
69
|
+
openConversationId: string,
|
|
70
|
+
cfg: any,
|
|
71
|
+
log?: ConfigLogger,
|
|
75
72
|
): Promise<void> {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
91
|
+
let savedToAccount: string | null = null;
|
|
96
92
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
if (!savedToAccount) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
119
113
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
138
|
+
accountId: string,
|
|
139
|
+
userId: string | number,
|
|
140
|
+
cfg: any,
|
|
141
|
+
log?: ConfigLogger,
|
|
149
142
|
): Promise<void> {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
183
|
+
accountId: string,
|
|
184
|
+
agentId: string | number,
|
|
185
|
+
cfg: any,
|
|
186
|
+
log?: ConfigLogger,
|
|
196
187
|
): Promise<void> {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
206
|
+
let savedToAccount: string | null = null;
|
|
217
207
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
227
|
+
if (!savedToAccount) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
242
230
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
255
|
+
apiKey: string,
|
|
256
|
+
cfg: any,
|
|
257
|
+
log?: ConfigLogger,
|
|
271
258
|
): Promise<void> {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
271
|
+
const existingModels = existingConfig.models || {};
|
|
272
|
+
const existingProviders = existingModels.providers || {};
|
|
273
|
+
const existingSophnetMinimax = existingProviders["sophnet-minimax"] || {};
|
|
288
274
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
}
|