@zhin.js/console 1.0.59 → 2.0.2

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/lib/index.js CHANGED
@@ -6,6 +6,7 @@ import * as path2 from 'path';
6
6
  import path2__default from 'path';
7
7
  import * as crypto from 'crypto';
8
8
  import WebSocket from 'ws';
9
+ import { getCronManager, generateCronJobId } from '@zhin.js/agent';
9
10
  import { transform } from 'esbuild';
10
11
 
11
12
  // src/index.ts
@@ -388,12 +389,13 @@ function initBotHub(root3) {
388
389
  root3.on("notice.receive", handlerNotice);
389
390
  const adapterNames = root3.adapters ? [...root3.adapters] : [];
390
391
  const inject2 = root3.inject;
392
+ const adapterListeners = [];
391
393
  if (inject2 && typeof inject2 === "function" && adapterNames.length > 0) {
392
394
  for (const name of adapterNames) {
393
395
  try {
394
396
  const ad = inject2(name);
395
397
  if (ad && typeof ad.on === "function") {
396
- ad.on("message.receive", (msg) => {
398
+ const handler = (msg) => {
397
399
  const payload = {
398
400
  type: "bot:message",
399
401
  data: {
@@ -407,16 +409,25 @@ function initBotHub(root3) {
407
409
  }
408
410
  };
409
411
  broadcast(payload);
410
- });
412
+ };
413
+ ad.on("message.receive", handler);
414
+ adapterListeners.push({ adapter: ad, handler });
411
415
  }
412
416
  } catch {
413
417
  }
414
418
  }
415
419
  }
420
+ return () => {
421
+ root3.off?.("request.receive", handlerReq);
422
+ root3.off?.("notice.receive", handlerNotice);
423
+ for (const { adapter, handler } of adapterListeners) {
424
+ adapter.off?.("message.receive", handler);
425
+ }
426
+ hubInited = false;
427
+ };
416
428
  }
417
-
418
- // src/websocket.ts
419
- var { root, logger } = usePlugin();
429
+ var plugin = usePlugin();
430
+ var { root, logger } = plugin;
420
431
  function collectBotsList() {
421
432
  const bots = [];
422
433
  for (const name of root.adapters) {
@@ -503,7 +514,10 @@ function getConfigFilePath() {
503
514
  }
504
515
  function setupWebSocket(webServer) {
505
516
  setBotHubWss(webServer.ws);
506
- initBotHub(root);
517
+ const disposeBotHub = initBotHub(root);
518
+ if (disposeBotHub) {
519
+ plugin.onDispose(disposeBotHub);
520
+ }
507
521
  webServer.ws.on("connection", (ws) => {
508
522
  ws.send(JSON.stringify({
509
523
  type: "sync",
@@ -1406,6 +1420,132 @@ async function handleWebSocketMessage(ws, message, webServer) {
1406
1420
  }
1407
1421
  break;
1408
1422
  }
1423
+ // ================================================================
1424
+ // 系统管理
1425
+ // ================================================================
1426
+ case "system:restart": {
1427
+ try {
1428
+ logger.info("[console] \u6536\u5230\u91CD\u542F\u8BF7\u6C42\uFF0C\u51C6\u5907\u91CD\u542F\u8FDB\u7A0B...");
1429
+ ws.send(JSON.stringify({ requestId, data: { success: true, message: "\u6B63\u5728\u91CD\u542F..." } }));
1430
+ broadcastToAll(webServer, { type: "system:restarting" });
1431
+ setTimeout(() => {
1432
+ process.exit(51);
1433
+ }, 500);
1434
+ } catch (error) {
1435
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1436
+ }
1437
+ break;
1438
+ }
1439
+ // ================================================================
1440
+ // 定时任务管理
1441
+ // ================================================================
1442
+ case "cron:list": {
1443
+ try {
1444
+ const m = getCronManager();
1445
+ if (!m) {
1446
+ ws.send(JSON.stringify({ requestId, error: "\u5B9A\u65F6\u4EFB\u52A1\u670D\u52A1\u4E0D\u53EF\u7528\uFF08Agent \u672A\u521D\u59CB\u5316\uFF09" }));
1447
+ break;
1448
+ }
1449
+ const memory = m.cronFeature.getStatus().map((s) => ({
1450
+ type: "memory",
1451
+ expression: s.expression,
1452
+ running: s.running,
1453
+ nextExecution: s.nextExecution?.toISOString() ?? null,
1454
+ plugin: s.plugin
1455
+ }));
1456
+ const persistent = m.engine ? (await m.engine.listJobs()).map((j) => ({
1457
+ type: "persistent",
1458
+ ...j
1459
+ })) : [];
1460
+ ws.send(JSON.stringify({ requestId, data: { memory, persistent } }));
1461
+ } catch (error) {
1462
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1463
+ }
1464
+ break;
1465
+ }
1466
+ case "cron:add": {
1467
+ try {
1468
+ const m = getCronManager();
1469
+ if (!m?.engine) {
1470
+ ws.send(JSON.stringify({ requestId, error: "\u6301\u4E45\u5316\u5B9A\u65F6\u4EFB\u52A1\u5F15\u64CE\u4E0D\u53EF\u7528" }));
1471
+ break;
1472
+ }
1473
+ const { cronExpression, prompt, label, context: cronContext } = message;
1474
+ if (!cronExpression || !prompt) {
1475
+ ws.send(JSON.stringify({ requestId, error: "\u7F3A\u5C11 cronExpression \u6216 prompt" }));
1476
+ break;
1477
+ }
1478
+ const record = await m.engine.addJob({
1479
+ id: generateCronJobId(),
1480
+ cronExpression,
1481
+ prompt,
1482
+ label: label || void 0,
1483
+ enabled: true,
1484
+ context: cronContext || void 0
1485
+ });
1486
+ ws.send(JSON.stringify({ requestId, data: record }));
1487
+ } catch (error) {
1488
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1489
+ }
1490
+ break;
1491
+ }
1492
+ case "cron:remove": {
1493
+ try {
1494
+ const m = getCronManager();
1495
+ if (!m?.engine) {
1496
+ ws.send(JSON.stringify({ requestId, error: "\u6301\u4E45\u5316\u5B9A\u65F6\u4EFB\u52A1\u5F15\u64CE\u4E0D\u53EF\u7528" }));
1497
+ break;
1498
+ }
1499
+ const { id } = message;
1500
+ if (!id) {
1501
+ ws.send(JSON.stringify({ requestId, error: "\u7F3A\u5C11\u4EFB\u52A1 id" }));
1502
+ break;
1503
+ }
1504
+ const ok = await m.engine.removeJob(id);
1505
+ ws.send(JSON.stringify({ requestId, data: { success: ok } }));
1506
+ } catch (error) {
1507
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1508
+ }
1509
+ break;
1510
+ }
1511
+ case "cron:pause": {
1512
+ try {
1513
+ const m = getCronManager();
1514
+ if (!m?.engine) {
1515
+ ws.send(JSON.stringify({ requestId, error: "\u6301\u4E45\u5316\u5B9A\u65F6\u4EFB\u52A1\u5F15\u64CE\u4E0D\u53EF\u7528" }));
1516
+ break;
1517
+ }
1518
+ const { id } = message;
1519
+ if (!id) {
1520
+ ws.send(JSON.stringify({ requestId, error: "\u7F3A\u5C11\u4EFB\u52A1 id" }));
1521
+ break;
1522
+ }
1523
+ const ok = await m.engine.pauseJob(id);
1524
+ ws.send(JSON.stringify({ requestId, data: { success: ok } }));
1525
+ } catch (error) {
1526
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1527
+ }
1528
+ break;
1529
+ }
1530
+ case "cron:resume": {
1531
+ try {
1532
+ const m = getCronManager();
1533
+ if (!m?.engine) {
1534
+ ws.send(JSON.stringify({ requestId, error: "\u6301\u4E45\u5316\u5B9A\u65F6\u4EFB\u52A1\u5F15\u64CE\u4E0D\u53EF\u7528" }));
1535
+ break;
1536
+ }
1537
+ const { id } = message;
1538
+ if (!id) {
1539
+ ws.send(JSON.stringify({ requestId, error: "\u7F3A\u5C11\u4EFB\u52A1 id" }));
1540
+ break;
1541
+ }
1542
+ const ok = await m.engine.resumeJob(id);
1543
+ ws.send(JSON.stringify({ requestId, data: { success: ok } }));
1544
+ } catch (error) {
1545
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1546
+ }
1547
+ break;
1548
+ }
1409
1549
  default:
1410
1550
  ws.send(JSON.stringify({ requestId, error: `Unknown message type: ${type}` }));
1411
1551
  }
package/lib/websocket.js CHANGED
@@ -2,6 +2,7 @@ import fs2 from 'fs';
2
2
  import path2 from 'path';
3
3
  import WebSocket from 'ws';
4
4
  import { usePlugin, Adapter } from '@zhin.js/core';
5
+ import { getCronManager, generateCronJobId } from '@zhin.js/agent';
5
6
 
6
7
  // src/websocket.ts
7
8
 
@@ -261,12 +262,13 @@ function initBotHub(root2) {
261
262
  root2.on("notice.receive", handlerNotice);
262
263
  const adapterNames = root2.adapters ? [...root2.adapters] : [];
263
264
  const inject = root2.inject;
265
+ const adapterListeners = [];
264
266
  if (inject && typeof inject === "function" && adapterNames.length > 0) {
265
267
  for (const name of adapterNames) {
266
268
  try {
267
269
  const ad = inject(name);
268
270
  if (ad && typeof ad.on === "function") {
269
- ad.on("message.receive", (msg) => {
271
+ const handler = (msg) => {
270
272
  const payload = {
271
273
  type: "bot:message",
272
274
  data: {
@@ -280,16 +282,25 @@ function initBotHub(root2) {
280
282
  }
281
283
  };
282
284
  broadcast(payload);
283
- });
285
+ };
286
+ ad.on("message.receive", handler);
287
+ adapterListeners.push({ adapter: ad, handler });
284
288
  }
285
289
  } catch {
286
290
  }
287
291
  }
288
292
  }
293
+ return () => {
294
+ root2.off?.("request.receive", handlerReq);
295
+ root2.off?.("notice.receive", handlerNotice);
296
+ for (const { adapter, handler } of adapterListeners) {
297
+ adapter.off?.("message.receive", handler);
298
+ }
299
+ hubInited = false;
300
+ };
289
301
  }
290
-
291
- // src/websocket.ts
292
- var { root, logger } = usePlugin();
302
+ var plugin = usePlugin();
303
+ var { root, logger } = plugin;
293
304
  function collectBotsList() {
294
305
  const bots = [];
295
306
  for (const name of root.adapters) {
@@ -376,7 +387,10 @@ function getConfigFilePath() {
376
387
  }
377
388
  function setupWebSocket(webServer) {
378
389
  setBotHubWss(webServer.ws);
379
- initBotHub(root);
390
+ const disposeBotHub = initBotHub(root);
391
+ if (disposeBotHub) {
392
+ plugin.onDispose(disposeBotHub);
393
+ }
380
394
  webServer.ws.on("connection", (ws) => {
381
395
  ws.send(JSON.stringify({
382
396
  type: "sync",
@@ -1279,6 +1293,132 @@ async function handleWebSocketMessage(ws, message, webServer) {
1279
1293
  }
1280
1294
  break;
1281
1295
  }
1296
+ // ================================================================
1297
+ // 系统管理
1298
+ // ================================================================
1299
+ case "system:restart": {
1300
+ try {
1301
+ logger.info("[console] \u6536\u5230\u91CD\u542F\u8BF7\u6C42\uFF0C\u51C6\u5907\u91CD\u542F\u8FDB\u7A0B...");
1302
+ ws.send(JSON.stringify({ requestId, data: { success: true, message: "\u6B63\u5728\u91CD\u542F..." } }));
1303
+ broadcastToAll(webServer, { type: "system:restarting" });
1304
+ setTimeout(() => {
1305
+ process.exit(51);
1306
+ }, 500);
1307
+ } catch (error) {
1308
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1309
+ }
1310
+ break;
1311
+ }
1312
+ // ================================================================
1313
+ // 定时任务管理
1314
+ // ================================================================
1315
+ case "cron:list": {
1316
+ try {
1317
+ const m = getCronManager();
1318
+ if (!m) {
1319
+ ws.send(JSON.stringify({ requestId, error: "\u5B9A\u65F6\u4EFB\u52A1\u670D\u52A1\u4E0D\u53EF\u7528\uFF08Agent \u672A\u521D\u59CB\u5316\uFF09" }));
1320
+ break;
1321
+ }
1322
+ const memory = m.cronFeature.getStatus().map((s) => ({
1323
+ type: "memory",
1324
+ expression: s.expression,
1325
+ running: s.running,
1326
+ nextExecution: s.nextExecution?.toISOString() ?? null,
1327
+ plugin: s.plugin
1328
+ }));
1329
+ const persistent = m.engine ? (await m.engine.listJobs()).map((j) => ({
1330
+ type: "persistent",
1331
+ ...j
1332
+ })) : [];
1333
+ ws.send(JSON.stringify({ requestId, data: { memory, persistent } }));
1334
+ } catch (error) {
1335
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1336
+ }
1337
+ break;
1338
+ }
1339
+ case "cron:add": {
1340
+ try {
1341
+ const m = getCronManager();
1342
+ if (!m?.engine) {
1343
+ ws.send(JSON.stringify({ requestId, error: "\u6301\u4E45\u5316\u5B9A\u65F6\u4EFB\u52A1\u5F15\u64CE\u4E0D\u53EF\u7528" }));
1344
+ break;
1345
+ }
1346
+ const { cronExpression, prompt, label, context: cronContext } = message;
1347
+ if (!cronExpression || !prompt) {
1348
+ ws.send(JSON.stringify({ requestId, error: "\u7F3A\u5C11 cronExpression \u6216 prompt" }));
1349
+ break;
1350
+ }
1351
+ const record = await m.engine.addJob({
1352
+ id: generateCronJobId(),
1353
+ cronExpression,
1354
+ prompt,
1355
+ label: label || void 0,
1356
+ enabled: true,
1357
+ context: cronContext || void 0
1358
+ });
1359
+ ws.send(JSON.stringify({ requestId, data: record }));
1360
+ } catch (error) {
1361
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1362
+ }
1363
+ break;
1364
+ }
1365
+ case "cron:remove": {
1366
+ try {
1367
+ const m = getCronManager();
1368
+ if (!m?.engine) {
1369
+ ws.send(JSON.stringify({ requestId, error: "\u6301\u4E45\u5316\u5B9A\u65F6\u4EFB\u52A1\u5F15\u64CE\u4E0D\u53EF\u7528" }));
1370
+ break;
1371
+ }
1372
+ const { id } = message;
1373
+ if (!id) {
1374
+ ws.send(JSON.stringify({ requestId, error: "\u7F3A\u5C11\u4EFB\u52A1 id" }));
1375
+ break;
1376
+ }
1377
+ const ok = await m.engine.removeJob(id);
1378
+ ws.send(JSON.stringify({ requestId, data: { success: ok } }));
1379
+ } catch (error) {
1380
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1381
+ }
1382
+ break;
1383
+ }
1384
+ case "cron:pause": {
1385
+ try {
1386
+ const m = getCronManager();
1387
+ if (!m?.engine) {
1388
+ ws.send(JSON.stringify({ requestId, error: "\u6301\u4E45\u5316\u5B9A\u65F6\u4EFB\u52A1\u5F15\u64CE\u4E0D\u53EF\u7528" }));
1389
+ break;
1390
+ }
1391
+ const { id } = message;
1392
+ if (!id) {
1393
+ ws.send(JSON.stringify({ requestId, error: "\u7F3A\u5C11\u4EFB\u52A1 id" }));
1394
+ break;
1395
+ }
1396
+ const ok = await m.engine.pauseJob(id);
1397
+ ws.send(JSON.stringify({ requestId, data: { success: ok } }));
1398
+ } catch (error) {
1399
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1400
+ }
1401
+ break;
1402
+ }
1403
+ case "cron:resume": {
1404
+ try {
1405
+ const m = getCronManager();
1406
+ if (!m?.engine) {
1407
+ ws.send(JSON.stringify({ requestId, error: "\u6301\u4E45\u5316\u5B9A\u65F6\u4EFB\u52A1\u5F15\u64CE\u4E0D\u53EF\u7528" }));
1408
+ break;
1409
+ }
1410
+ const { id } = message;
1411
+ if (!id) {
1412
+ ws.send(JSON.stringify({ requestId, error: "\u7F3A\u5C11\u4EFB\u52A1 id" }));
1413
+ break;
1414
+ }
1415
+ const ok = await m.engine.resumeJob(id);
1416
+ ws.send(JSON.stringify({ requestId, data: { success: ok } }));
1417
+ } catch (error) {
1418
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1419
+ }
1420
+ break;
1421
+ }
1282
1422
  default:
1283
1423
  ws.send(JSON.stringify({ requestId, error: `Unknown message type: ${type}` }));
1284
1424
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/console",
3
- "version": "1.0.59",
3
+ "version": "2.0.2",
4
4
  "description": "Web console service for Zhin.js with real-time monitoring",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -65,14 +65,15 @@
65
65
  "rolldown": "^1.0.0-rc.9",
66
66
  "vite": "8.0.0",
67
67
  "yaml": "^2.8.2",
68
- "zhin.js": "1.0.57"
68
+ "zhin.js": "1.0.60"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@types/ws": "^8.18.1",
72
- "@zhin.js/client": "^1.0.14",
73
- "@zhin.js/core": "^1.0.57",
74
- "@zhin.js/http": "^1.0.52",
75
- "zhin.js": "1.0.57"
72
+ "@zhin.js/core": "^1.1.2",
73
+ "@zhin.js/http": "^1.0.55",
74
+ "@zhin.js/client": "^1.0.15",
75
+ "@zhin.js/agent": "^0.1.2",
76
+ "zhin.js": "1.0.60"
76
77
  },
77
78
  "files": [
78
79
  "src",
package/src/bot-hub.ts CHANGED
@@ -188,9 +188,10 @@ export async function sendCatchUpToClient(ws: WebSocket) {
188
188
 
189
189
  export function initBotHub(root: {
190
190
  on: (ev: string, fn: (...a: any[]) => void) => void;
191
+ off?: (ev: string, fn: (...a: any[]) => void) => void;
191
192
  adapters?: Iterable<string>;
192
193
  inject?: (key: string) => unknown;
193
- }) {
194
+ }): (() => void) | undefined {
194
195
  if (hubInited) return;
195
196
  hubInited = true;
196
197
 
@@ -211,12 +212,13 @@ export function initBotHub(root: {
211
212
  // 收消息推送:向控制台广播机器人收到的消息,供「收消息展示」使用
212
213
  const adapterNames = root.adapters ? [...(root.adapters as Iterable<string>)] : [];
213
214
  const inject = root.inject;
215
+ const adapterListeners: Array<{ adapter: any; handler: (...a: any[]) => void }> = [];
214
216
  if (inject && typeof inject === "function" && adapterNames.length > 0) {
215
217
  for (const name of adapterNames) {
216
218
  try {
217
- const ad = inject(name) as { on?: (ev: string, fn: (...a: any[]) => void) => void } | null;
219
+ const ad = inject(name) as { on?: (ev: string, fn: (...a: any[]) => void) => void; off?: (ev: string, fn: (...a: any[]) => void) => void } | null;
218
220
  if (ad && typeof ad.on === "function") {
219
- ad.on("message.receive", (msg: any) => {
221
+ const handler = (msg: any) => {
220
222
  const payload = {
221
223
  type: "bot:message",
222
224
  data: {
@@ -230,11 +232,23 @@ export function initBotHub(root: {
230
232
  },
231
233
  };
232
234
  broadcast(payload);
233
- });
235
+ };
236
+ ad.on("message.receive", handler);
237
+ adapterListeners.push({ adapter: ad, handler });
234
238
  }
235
239
  } catch {
236
240
  // 忽略单个适配器注册失败
237
241
  }
238
242
  }
239
243
  }
244
+
245
+ // 返回清理函数
246
+ return () => {
247
+ root.off?.("request.receive", handlerReq);
248
+ root.off?.("notice.receive", handlerNotice);
249
+ for (const { adapter, handler } of adapterListeners) {
250
+ adapter.off?.("message.receive", handler);
251
+ }
252
+ hubInited = false;
253
+ };
240
254
  }
package/src/websocket.ts CHANGED
@@ -20,8 +20,11 @@ import {
20
20
  getRequestRowById,
21
21
  } from "./bot-persistence.js";
22
22
  import { removePendingRequest } from "./bot-hub.js";
23
+ import { getCronManager, generateCronJobId } from "@zhin.js/agent";
24
+ import type { CronJobRecord } from "@zhin.js/agent";
23
25
 
24
- const { root, logger } = usePlugin();
26
+ const plugin = usePlugin();
27
+ const { root, logger } = plugin;
25
28
 
26
29
  function collectBotsList(): Array<{
27
30
  name: string;
@@ -143,7 +146,15 @@ function getConfigFilePath(): string {
143
146
 
144
147
  export function setupWebSocket(webServer: WebServer) {
145
148
  setBotHubWss(webServer.ws);
146
- initBotHub(root as { on: (ev: string, fn: (...a: unknown[]) => void) => void });
149
+ const disposeBotHub = initBotHub(root as {
150
+ on: (ev: string, fn: (...a: unknown[]) => void) => void;
151
+ off?: (ev: string, fn: (...a: unknown[]) => void) => void;
152
+ adapters?: Iterable<string>;
153
+ inject?: (key: string) => unknown;
154
+ });
155
+ if (disposeBotHub) {
156
+ plugin.onDispose(disposeBotHub);
157
+ }
147
158
 
148
159
  webServer.ws.on("connection", (ws: WebSocket) => {
149
160
  ws.send(JSON.stringify({
@@ -1081,6 +1092,144 @@ async function handleWebSocketMessage(
1081
1092
  break;
1082
1093
  }
1083
1094
 
1095
+ // ================================================================
1096
+ // 系统管理
1097
+ // ================================================================
1098
+
1099
+ case "system:restart": {
1100
+ try {
1101
+ logger.info("[console] 收到重启请求,准备重启进程...");
1102
+ ws.send(JSON.stringify({ requestId, data: { success: true, message: "正在重启..." } }));
1103
+ // 广播给所有客户端
1104
+ broadcastToAll(webServer, { type: "system:restarting" });
1105
+ // 延迟 500ms 让 WebSocket 消息发出,然后用 exit code 51 触发 CLI 守护进程重启
1106
+ setTimeout(() => {
1107
+ process.exit(51);
1108
+ }, 500);
1109
+ } catch (error) {
1110
+ ws.send(JSON.stringify({ requestId, error: (error as Error).message }));
1111
+ }
1112
+ break;
1113
+ }
1114
+
1115
+ // ================================================================
1116
+ // 定时任务管理
1117
+ // ================================================================
1118
+
1119
+ case "cron:list": {
1120
+ try {
1121
+ const m = getCronManager();
1122
+ if (!m) {
1123
+ ws.send(JSON.stringify({ requestId, error: "定时任务服务不可用(Agent 未初始化)" }));
1124
+ break;
1125
+ }
1126
+ const memory = m.cronFeature.getStatus().map((s) => ({
1127
+ type: "memory" as const,
1128
+ expression: s.expression,
1129
+ running: s.running,
1130
+ nextExecution: s.nextExecution?.toISOString() ?? null,
1131
+ plugin: s.plugin,
1132
+ }));
1133
+ const persistent = m.engine
1134
+ ? (await m.engine.listJobs()).map((j) => ({
1135
+ type: "persistent" as const,
1136
+ ...j,
1137
+ }))
1138
+ : [];
1139
+ ws.send(JSON.stringify({ requestId, data: { memory, persistent } }));
1140
+ } catch (error) {
1141
+ ws.send(JSON.stringify({ requestId, error: (error as Error).message }));
1142
+ }
1143
+ break;
1144
+ }
1145
+
1146
+ case "cron:add": {
1147
+ try {
1148
+ const m = getCronManager();
1149
+ if (!m?.engine) {
1150
+ ws.send(JSON.stringify({ requestId, error: "持久化定时任务引擎不可用" }));
1151
+ break;
1152
+ }
1153
+ const { cronExpression, prompt, label, context: cronContext } = message as any;
1154
+ if (!cronExpression || !prompt) {
1155
+ ws.send(JSON.stringify({ requestId, error: "缺少 cronExpression 或 prompt" }));
1156
+ break;
1157
+ }
1158
+ const record = await m.engine.addJob({
1159
+ id: generateCronJobId(),
1160
+ cronExpression,
1161
+ prompt,
1162
+ label: label || undefined,
1163
+ enabled: true,
1164
+ context: cronContext || undefined,
1165
+ });
1166
+ ws.send(JSON.stringify({ requestId, data: record }));
1167
+ } catch (error) {
1168
+ ws.send(JSON.stringify({ requestId, error: (error as Error).message }));
1169
+ }
1170
+ break;
1171
+ }
1172
+
1173
+ case "cron:remove": {
1174
+ try {
1175
+ const m = getCronManager();
1176
+ if (!m?.engine) {
1177
+ ws.send(JSON.stringify({ requestId, error: "持久化定时任务引擎不可用" }));
1178
+ break;
1179
+ }
1180
+ const { id } = message as any;
1181
+ if (!id) {
1182
+ ws.send(JSON.stringify({ requestId, error: "缺少任务 id" }));
1183
+ break;
1184
+ }
1185
+ const ok = await m.engine.removeJob(id);
1186
+ ws.send(JSON.stringify({ requestId, data: { success: ok } }));
1187
+ } catch (error) {
1188
+ ws.send(JSON.stringify({ requestId, error: (error as Error).message }));
1189
+ }
1190
+ break;
1191
+ }
1192
+
1193
+ case "cron:pause": {
1194
+ try {
1195
+ const m = getCronManager();
1196
+ if (!m?.engine) {
1197
+ ws.send(JSON.stringify({ requestId, error: "持久化定时任务引擎不可用" }));
1198
+ break;
1199
+ }
1200
+ const { id } = message as any;
1201
+ if (!id) {
1202
+ ws.send(JSON.stringify({ requestId, error: "缺少任务 id" }));
1203
+ break;
1204
+ }
1205
+ const ok = await m.engine.pauseJob(id);
1206
+ ws.send(JSON.stringify({ requestId, data: { success: ok } }));
1207
+ } catch (error) {
1208
+ ws.send(JSON.stringify({ requestId, error: (error as Error).message }));
1209
+ }
1210
+ break;
1211
+ }
1212
+
1213
+ case "cron:resume": {
1214
+ try {
1215
+ const m = getCronManager();
1216
+ if (!m?.engine) {
1217
+ ws.send(JSON.stringify({ requestId, error: "持久化定时任务引擎不可用" }));
1218
+ break;
1219
+ }
1220
+ const { id } = message as any;
1221
+ if (!id) {
1222
+ ws.send(JSON.stringify({ requestId, error: "缺少任务 id" }));
1223
+ break;
1224
+ }
1225
+ const ok = await m.engine.resumeJob(id);
1226
+ ws.send(JSON.stringify({ requestId, data: { success: ok } }));
1227
+ } catch (error) {
1228
+ ws.send(JSON.stringify({ requestId, error: (error as Error).message }));
1229
+ }
1230
+ break;
1231
+ }
1232
+
1084
1233
  default:
1085
1234
  ws.send(JSON.stringify({ requestId, error: `Unknown message type: ${type}` }));
1086
1235
  }