@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/CHANGELOG.md +38 -0
- package/client/src/layouts/dashboard.tsx +1 -1
- package/client/src/main.tsx +24 -6
- package/client/src/pages/cron.tsx +529 -0
- package/client/src/pages/dashboard.tsx +60 -1
- package/client/src/pages/marketplace.tsx +464 -0
- package/dist/assets/index-BMkFI-uN.js +124 -0
- package/dist/assets/style-CtySe6_R.css +3 -0
- package/dist/client.js +10 -2
- package/dist/index.html +2 -2
- package/dist/style.css +1 -1
- package/lib/index.js +146 -6
- package/lib/websocket.js +146 -6
- package/package.json +7 -6
- package/src/bot-hub.ts +18 -4
- package/src/websocket.ts +151 -2
- package/dist/assets/index-B1ihXBk4.js +0 -124
- package/dist/assets/style-kkLO-vsa.css +0 -3
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
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.
|
|
68
|
+
"zhin.js": "1.0.60"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@types/ws": "^8.18.1",
|
|
72
|
-
"@zhin.js/
|
|
73
|
-
"@zhin.js/
|
|
74
|
-
"@zhin.js/
|
|
75
|
-
"zhin.js": "1.
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
}
|