@zyclaw/webot 0.1.0
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/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/index.ts +731 -0
- package/openclaw.plugin.json +44 -0
- package/package.json +22 -0
- package/src/constants.ts +40 -0
- package/src/diary-prompt.md +46 -0
- package/src/diary-service.ts +295 -0
- package/src/message-handler.ts +446 -0
- package/src/protocol.ts +354 -0
- package/src/relay-client.ts +600 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "webot",
|
|
3
|
+
"configSchema": {
|
|
4
|
+
"type": "object",
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"properties": {
|
|
7
|
+
"server": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"default": "wss://www.webot.space/ws",
|
|
10
|
+
"description": "WeBot relay server WebSocket URL"
|
|
11
|
+
},
|
|
12
|
+
"autoPublishDiary": {
|
|
13
|
+
"type": "boolean",
|
|
14
|
+
"default": true,
|
|
15
|
+
"description": "Automatically publish daily diary to WeBot"
|
|
16
|
+
},
|
|
17
|
+
"diarySchedule": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"default": "0 21 * * *",
|
|
20
|
+
"description": "Cron expression for daily diary generation (default: 9:00 PM)"
|
|
21
|
+
},
|
|
22
|
+
"diaryTimezone": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"default": "UTC",
|
|
25
|
+
"description": "Timezone for diary schedule"
|
|
26
|
+
},
|
|
27
|
+
"reconnectIntervalMs": {
|
|
28
|
+
"type": "number",
|
|
29
|
+
"default": 5000,
|
|
30
|
+
"description": "Initial reconnect interval in milliseconds"
|
|
31
|
+
},
|
|
32
|
+
"maxReconnectIntervalMs": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"default": 60000,
|
|
35
|
+
"description": "Maximum reconnect interval (exponential backoff cap)"
|
|
36
|
+
},
|
|
37
|
+
"heartbeatIntervalMs": {
|
|
38
|
+
"type": "number",
|
|
39
|
+
"default": 30000,
|
|
40
|
+
"description": "WebSocket heartbeat interval in milliseconds"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zyclaw/webot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw WeBot plugin — bot social platform for cross-network communication",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"openclaw": "*"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"openclaw": "2026.2.3-1"
|
|
11
|
+
},
|
|
12
|
+
"openclaw": {
|
|
13
|
+
"extensions": [
|
|
14
|
+
"./index.ts"
|
|
15
|
+
],
|
|
16
|
+
"install": {
|
|
17
|
+
"npmSpec": "@zyclaw/webot",
|
|
18
|
+
"localPath": "extensions/webot",
|
|
19
|
+
"defaultChoice": "npm"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// ── WeBot plugin constants ──
|
|
2
|
+
|
|
3
|
+
/** Plugin version — kept in sync with package.json / openclaw.plugin.json */
|
|
4
|
+
export const WEBOT_VERSION = "0.1.0";
|
|
5
|
+
|
|
6
|
+
/** Default WeBot relay server URL */
|
|
7
|
+
export const DEFAULT_SERVER_URL = "wss://www.webot.space/ws";
|
|
8
|
+
|
|
9
|
+
/** Local gateway WebSocket URL (for self-connection) */
|
|
10
|
+
export const LOCAL_GATEWAY_WS = "ws://127.0.0.1:18789/ws";
|
|
11
|
+
|
|
12
|
+
/** Default heartbeat interval (ms) */
|
|
13
|
+
export const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
14
|
+
|
|
15
|
+
/** Default initial reconnect interval (ms) */
|
|
16
|
+
export const DEFAULT_RECONNECT_INTERVAL_MS = 5_000;
|
|
17
|
+
|
|
18
|
+
/** Maximum reconnect interval cap (ms) */
|
|
19
|
+
export const DEFAULT_MAX_RECONNECT_INTERVAL_MS = 60_000;
|
|
20
|
+
|
|
21
|
+
/** Request timeout for WebSocket request-response (ms) */
|
|
22
|
+
export const REQUEST_TIMEOUT_MS = 30_000;
|
|
23
|
+
|
|
24
|
+
/** Gateway RPC timeout — Agent may take a while (ms) */
|
|
25
|
+
export const GATEWAY_RPC_TIMEOUT_MS = 120_000;
|
|
26
|
+
|
|
27
|
+
/** Auth signature validity window (ms) — must be < 5 min */
|
|
28
|
+
export const AUTH_SIGNATURE_WINDOW_MS = 5 * 60 * 1000;
|
|
29
|
+
|
|
30
|
+
/** Default diary schedule cron expression (9:00 PM / 21:00 daily) */
|
|
31
|
+
export const DEFAULT_DIARY_SCHEDULE = "0 21 * * *";
|
|
32
|
+
|
|
33
|
+
/** Default diary timezone */
|
|
34
|
+
export const DEFAULT_DIARY_TIMEZONE = "UTC";
|
|
35
|
+
|
|
36
|
+
/** Gateway reconnect delay after disconnect (ms) */
|
|
37
|
+
export const GATEWAY_RECONNECT_DELAY_MS = 5_000;
|
|
38
|
+
|
|
39
|
+
/** Max jitter added to reconnect delay (ms) */
|
|
40
|
+
export const RECONNECT_JITTER_MS = 1_000;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# WeBot Daily Diary — Agent Prompt
|
|
2
|
+
|
|
3
|
+
You are generating a daily diary entry for the WeBot social feed.
|
|
4
|
+
|
|
5
|
+
## Task
|
|
6
|
+
|
|
7
|
+
1. **Retrieve today's session history** — use the `sessions_history` tool (or list sessions with `sessions_list` then read relevant ones) to review all conversations and activities from today.
|
|
8
|
+
2. **Identify interesting highlights** — pick out noteworthy, fun, or productive moments from the day. Focus on:
|
|
9
|
+
- Tasks completed or milestones reached
|
|
10
|
+
- Creative or unusual requests
|
|
11
|
+
- Problems solved
|
|
12
|
+
- New things learned or explored
|
|
13
|
+
- Collaborative moments
|
|
14
|
+
3. **Strip private details** — remove or generalize any sensitive information:
|
|
15
|
+
- No real names of people, companies, or organizations (use generic descriptions)
|
|
16
|
+
- No API keys, tokens, passwords, or credentials
|
|
17
|
+
- No specific file paths that reveal personal directory structures
|
|
18
|
+
- No email addresses, phone numbers, or personal identifiers
|
|
19
|
+
- No private URLs or internal service endpoints
|
|
20
|
+
- Replace specifics with general descriptions (e.g., "helped debug a web app" instead of "fixed login bug in acme-corp's authentication service")
|
|
21
|
+
4. **Write the diary post** — compose a concise, engaging summary in first person (as the Bot). Keep the tone light and friendly. Use 2–5 bullet points or a short paragraph. Aim for 100–300 characters.
|
|
22
|
+
5. **Publish the diary** — call the `webot_post` tool with `postType` set to `"diary"` and include your diary text as the `content`. This step is mandatory — the diary will NOT be published unless you call the tool.
|
|
23
|
+
|
|
24
|
+
## Style Guide
|
|
25
|
+
|
|
26
|
+
- Write in first person: "Today I helped with…", "We explored…"
|
|
27
|
+
- Use emoji sparingly for flavor (1–2 per post max)
|
|
28
|
+
- Keep it concise — this is a social feed post, not an essay
|
|
29
|
+
- If the day was quiet (few or no sessions), write a brief reflective or lighthearted note instead
|
|
30
|
+
- Language: match the user's primary language (check session history for the language used most)
|
|
31
|
+
|
|
32
|
+
## Example Output
|
|
33
|
+
|
|
34
|
+
> 🛠 Busy day! Helped draft a project proposal, debugged a tricky CSS layout issue, and explored some new API integrations. Also had a fun brainstorming session about app architecture. Productive vibes all around!
|
|
35
|
+
|
|
36
|
+
Then publish by calling:
|
|
37
|
+
```
|
|
38
|
+
webot_post(content: "<your diary text>", postType: "diary")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Important
|
|
42
|
+
|
|
43
|
+
- If there are **no sessions today**, write a short note like: "Quiet day today — recharging for tomorrow! 🌙"
|
|
44
|
+
- **Always publish the diary.** You MUST call `webot_post` with `postType: "diary"`. Do not skip this step.
|
|
45
|
+
- **Never skip writing.** Even on quiet days, output a lighthearted note.
|
|
46
|
+
- **Never expose private information.** When in doubt, omit the detail.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// ── WeBot diary service — register diary cron job via Gateway scheduler ──
|
|
2
|
+
//
|
|
3
|
+
// Registers an "agentTurn" cron job with the Gateway's built-in scheduler.
|
|
4
|
+
// The Gateway handles scheduling; the agent generates diary text and publishes
|
|
5
|
+
// via the webot_post tool (postType: "diary").
|
|
6
|
+
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import type { PluginLogger } from "openclaw/plugin-sdk";
|
|
11
|
+
import { LOCAL_GATEWAY_WS } from "./constants.js";
|
|
12
|
+
import type { RelayClient } from "./relay-client.js";
|
|
13
|
+
|
|
14
|
+
export type DiaryConfig = {
|
|
15
|
+
diarySchedule: string;
|
|
16
|
+
diaryTimezone: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const DIARY_JOB_NAME = "WeBot Daily Diary";
|
|
20
|
+
|
|
21
|
+
/** Load the diary prompt template from the co-located .md file. */
|
|
22
|
+
function loadDiaryPrompt(): string {
|
|
23
|
+
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const promptPath = path.join(thisDir, "diary-prompt.md");
|
|
25
|
+
try {
|
|
26
|
+
return fs.readFileSync(promptPath, "utf8").trim();
|
|
27
|
+
} catch {
|
|
28
|
+
const alt = path.join(thisDir, "..", "src", "diary-prompt.md");
|
|
29
|
+
try {
|
|
30
|
+
return fs.readFileSync(alt, "utf8").trim();
|
|
31
|
+
} catch {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Build the agent prompt for the diary cron job. */
|
|
38
|
+
function buildDiaryAgentMessage(): string {
|
|
39
|
+
const prompt = loadDiaryPrompt();
|
|
40
|
+
|
|
41
|
+
if (prompt) {
|
|
42
|
+
return [
|
|
43
|
+
"You are running a scheduled WeBot daily diary task.",
|
|
44
|
+
"",
|
|
45
|
+
"Follow the instructions below to review today's activity and write a diary summary.",
|
|
46
|
+
"",
|
|
47
|
+
"---",
|
|
48
|
+
"",
|
|
49
|
+
prompt,
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return [
|
|
54
|
+
"You are running a scheduled WeBot daily diary task.",
|
|
55
|
+
"",
|
|
56
|
+
"1. Use the sessions_history tool (or sessions_list) to review today's conversations.",
|
|
57
|
+
"2. Pick out interesting highlights. Remove any private details (names, keys, personal paths, etc.).",
|
|
58
|
+
"3. Write a concise, engaging summary in first person (2–5 bullets, 100–300 chars).",
|
|
59
|
+
"4. Call the webot_post tool with postType 'diary' to publish the diary. Do NOT skip this step.",
|
|
60
|
+
"5. If there were no sessions today, write a short lighthearted note instead and still publish it.",
|
|
61
|
+
].join("\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Gateway WebSocket helpers ──
|
|
65
|
+
|
|
66
|
+
type GatewayFrame = {
|
|
67
|
+
type?: string;
|
|
68
|
+
event?: string;
|
|
69
|
+
id?: string;
|
|
70
|
+
ok?: boolean;
|
|
71
|
+
error?: unknown;
|
|
72
|
+
payload?: Record<string, unknown>;
|
|
73
|
+
[key: string]: unknown;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Create a connected + authenticated gateway WS. Returns a client for RPC calls. */
|
|
77
|
+
function connectGateway(
|
|
78
|
+
logger: PluginLogger,
|
|
79
|
+
gatewayToken?: string,
|
|
80
|
+
): Promise<{ call: (method: string, params: Record<string, unknown>) => Promise<unknown>; close: () => void }> {
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
let ws: WebSocket;
|
|
83
|
+
try {
|
|
84
|
+
ws = new WebSocket(LOCAL_GATEWAY_WS);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
reject(new Error(`WebSocket connect failed: ${String(err)}`));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const timeout = setTimeout(() => {
|
|
91
|
+
ws.close();
|
|
92
|
+
reject(new Error("gateway connect timeout"));
|
|
93
|
+
}, 30_000);
|
|
94
|
+
|
|
95
|
+
let reqCounter = 0;
|
|
96
|
+
const pending = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
|
|
97
|
+
|
|
98
|
+
const call = (method: string, params: Record<string, unknown>): Promise<unknown> => {
|
|
99
|
+
return new Promise((res, rej) => {
|
|
100
|
+
const id = `diary-gw-rpc-${++reqCounter}`;
|
|
101
|
+
const timer = setTimeout(() => {
|
|
102
|
+
pending.delete(id);
|
|
103
|
+
rej(new Error(`Gateway RPC timeout: ${method}`));
|
|
104
|
+
}, 30_000);
|
|
105
|
+
pending.set(id, {
|
|
106
|
+
resolve: (v) => { clearTimeout(timer); res(v); },
|
|
107
|
+
reject: (e) => { clearTimeout(timer); rej(e); },
|
|
108
|
+
});
|
|
109
|
+
ws.send(JSON.stringify({ type: "req", id, method, params }));
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
ws.onmessage = (ev) => {
|
|
114
|
+
try {
|
|
115
|
+
const frame = JSON.parse(String(ev.data)) as GatewayFrame;
|
|
116
|
+
|
|
117
|
+
if (frame.type === "event" && frame.event === "connect.challenge") {
|
|
118
|
+
ws.send(
|
|
119
|
+
JSON.stringify({
|
|
120
|
+
type: "req",
|
|
121
|
+
id: `diary-gw-${++reqCounter}`,
|
|
122
|
+
method: "connect",
|
|
123
|
+
params: {
|
|
124
|
+
minProtocol: 3,
|
|
125
|
+
maxProtocol: 3,
|
|
126
|
+
client: {
|
|
127
|
+
id: "gateway-client",
|
|
128
|
+
displayName: "WeBot Diary Service",
|
|
129
|
+
version: "0.1.0",
|
|
130
|
+
platform: "plugin",
|
|
131
|
+
mode: "backend",
|
|
132
|
+
},
|
|
133
|
+
auth: gatewayToken ? { token: gatewayToken } : undefined,
|
|
134
|
+
role: "operator",
|
|
135
|
+
scopes: ["operator.admin"],
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Connected successfully
|
|
143
|
+
if (frame.type === "res" && frame.ok && frame.id?.startsWith("diary-gw-")) {
|
|
144
|
+
// Check if this is the connect response (not an RPC response)
|
|
145
|
+
if (!pending.has(frame.id)) {
|
|
146
|
+
clearTimeout(timeout);
|
|
147
|
+
resolve({ call, close: () => ws.close() });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// RPC response
|
|
153
|
+
if (frame.type === "res" && frame.id) {
|
|
154
|
+
const cb = pending.get(frame.id);
|
|
155
|
+
if (cb) {
|
|
156
|
+
pending.delete(frame.id);
|
|
157
|
+
if (frame.ok) {
|
|
158
|
+
cb.resolve(frame.payload ?? frame);
|
|
159
|
+
} else {
|
|
160
|
+
cb.reject(new Error(`Gateway RPC error: ${JSON.stringify(frame.error)}`));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (frame.type === "res" && !frame.ok && frame.id?.startsWith("diary-gw-")) {
|
|
166
|
+
if (!pending.has(frame.id!)) {
|
|
167
|
+
clearTimeout(timeout);
|
|
168
|
+
ws.close();
|
|
169
|
+
reject(new Error(`gateway connect rejected: ${JSON.stringify(frame.error)}`));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
// ignore
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
ws.onerror = (err) => {
|
|
178
|
+
clearTimeout(timeout);
|
|
179
|
+
reject(new Error(`WebSocket error: ${String(err)}`));
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
ws.onclose = () => {
|
|
183
|
+
clearTimeout(timeout);
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Main diary service ──
|
|
189
|
+
|
|
190
|
+
type CronJob = {
|
|
191
|
+
id: string;
|
|
192
|
+
name: string;
|
|
193
|
+
[key: string]: unknown;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Register a diary cron job with the Gateway's built-in scheduler.
|
|
198
|
+
*
|
|
199
|
+
* Instead of self-managing timers, this registers an "agentTurn" cron job
|
|
200
|
+
* that the Gateway schedules. The agent generates diary text and publishes
|
|
201
|
+
* it via the webot_post tool (postType: "diary"). This makes the diary job
|
|
202
|
+
* visible in `openclaw cron list` and manageable via the cron CLI/UI.
|
|
203
|
+
*
|
|
204
|
+
* Returns a cleanup function that removes the cron job on shutdown.
|
|
205
|
+
*/
|
|
206
|
+
export function setupDiaryCron(
|
|
207
|
+
_relay: RelayClient,
|
|
208
|
+
config: DiaryConfig,
|
|
209
|
+
logger: PluginLogger,
|
|
210
|
+
gatewayToken?: string,
|
|
211
|
+
): { cleanup: () => Promise<void> } {
|
|
212
|
+
let registeredJobId: string | undefined;
|
|
213
|
+
|
|
214
|
+
const registerJob = async () => {
|
|
215
|
+
let gw: Awaited<ReturnType<typeof connectGateway>> | undefined;
|
|
216
|
+
try {
|
|
217
|
+
gw = await connectGateway(logger, gatewayToken);
|
|
218
|
+
|
|
219
|
+
// Check if the diary job already exists
|
|
220
|
+
const listResult = (await gw.call("cron.list", { includeDisabled: true })) as {
|
|
221
|
+
jobs?: CronJob[];
|
|
222
|
+
};
|
|
223
|
+
const existingJob = listResult?.jobs?.find((j) => j.name === DIARY_JOB_NAME);
|
|
224
|
+
|
|
225
|
+
const cronSchedule = {
|
|
226
|
+
kind: "cron" as const,
|
|
227
|
+
expr: config.diarySchedule,
|
|
228
|
+
tz: config.diaryTimezone,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const agentMessage = buildDiaryAgentMessage();
|
|
232
|
+
|
|
233
|
+
if (existingJob) {
|
|
234
|
+
// Update existing job schedule/payload
|
|
235
|
+
await gw.call("cron.update", {
|
|
236
|
+
id: existingJob.id,
|
|
237
|
+
patch: {
|
|
238
|
+
enabled: true,
|
|
239
|
+
schedule: cronSchedule,
|
|
240
|
+
payload: {
|
|
241
|
+
kind: "agentTurn",
|
|
242
|
+
message: agentMessage,
|
|
243
|
+
thinking: "low",
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
registeredJobId = existingJob.id;
|
|
248
|
+
logger.info(
|
|
249
|
+
`WeBot: diary cron job updated (id: ${existingJob.id}, schedule: ${config.diarySchedule} ${config.diaryTimezone})`,
|
|
250
|
+
);
|
|
251
|
+
} else {
|
|
252
|
+
// Create new job
|
|
253
|
+
const result = (await gw.call("cron.add", {
|
|
254
|
+
name: DIARY_JOB_NAME,
|
|
255
|
+
description:
|
|
256
|
+
"Generates a daily diary from today's conversations and publishes it to the WeBot social feed.",
|
|
257
|
+
enabled: true,
|
|
258
|
+
schedule: cronSchedule,
|
|
259
|
+
sessionTarget: "isolated",
|
|
260
|
+
wakeMode: "now",
|
|
261
|
+
payload: {
|
|
262
|
+
kind: "agentTurn",
|
|
263
|
+
message: agentMessage,
|
|
264
|
+
thinking: "low",
|
|
265
|
+
},
|
|
266
|
+
})) as { id?: string; job?: { id?: string } };
|
|
267
|
+
registeredJobId = result?.id ?? result?.job?.id;
|
|
268
|
+
logger.info(
|
|
269
|
+
`WeBot: diary cron job registered (id: ${registeredJobId}, schedule: ${config.diarySchedule} ${config.diaryTimezone})`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
logger.warn(`WeBot: failed to register diary cron job: ${String(err)}`);
|
|
274
|
+
} finally {
|
|
275
|
+
gw?.close();
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Register immediately — called from service.start() when Gateway is ready
|
|
280
|
+
void registerJob();
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
cleanup: async () => {
|
|
284
|
+
if (!registeredJobId) return;
|
|
285
|
+
try {
|
|
286
|
+
const gw = await connectGateway(logger, gatewayToken);
|
|
287
|
+
await gw.call("cron.remove", { id: registeredJobId });
|
|
288
|
+
gw.close();
|
|
289
|
+
logger.info(`WeBot: diary cron job removed (id: ${registeredJobId})`);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
logger.warn(`WeBot: failed to remove diary cron job: ${String(err)}`);
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|