@dsbot-channel/core 0.7.0 → 0.7.1
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/dist/adapter-plugin.js +9 -3
- package/dist/plugin.js +118 -10
- package/package.json +1 -1
package/dist/adapter-plugin.js
CHANGED
|
@@ -46,10 +46,16 @@ export function makeAdapterPlugin(spec) {
|
|
|
46
46
|
source: `${spec.name} plugin`,
|
|
47
47
|
};
|
|
48
48
|
const mount = ctx.get(MOUNT_SERVICE);
|
|
49
|
-
if (mount === undefined)
|
|
49
|
+
if (mount === undefined) {
|
|
50
50
|
registry.addInstance(instance);
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
mount.add(instance);
|
|
54
|
+
const remove = mount.remove;
|
|
55
|
+
if (remove === undefined)
|
|
56
|
+
return;
|
|
57
|
+
// A reload disposes this plugin first, so the instance leaves the gateway before it is applied again.
|
|
58
|
+
ctx.effect(() => () => Promise.resolve(remove.call(mount, instance.channel, instance.source)).catch(() => undefined), `${spec.name}: adapter instance`);
|
|
53
59
|
};
|
|
54
60
|
const plugin = { name: spec.name, inject: [REGISTRY_SERVICE], Config: AdapterPluginConfig, apply };
|
|
55
61
|
return { Config: AdapterPluginConfig, apply, plugin };
|
package/dist/plugin.js
CHANGED
|
@@ -685,6 +685,14 @@ async function applyWith(ctx, raw, options = {}, skillFile) {
|
|
|
685
685
|
const bots = botScopes(effective, written, rawProfile.policy);
|
|
686
686
|
const pipelines = new SessionPipelines({ bots });
|
|
687
687
|
const channels = mountAdapters(effective, settings, warn, registry, (index) => writtenRaw[index], rawProfile.policy, roster);
|
|
688
|
+
// One live view: the push router and the capability gate read these, so a late mount or an unmount is
|
|
689
|
+
// visible to them at once without rebuilding anything per send.
|
|
690
|
+
const byChannel = new Map(channels.map((channel) => [channel.config.channel, channel]));
|
|
691
|
+
const pushChannels = channels.map((channel) => ({
|
|
692
|
+
channel: channel.config.channel,
|
|
693
|
+
botName: channel.config.botName,
|
|
694
|
+
}));
|
|
695
|
+
let refreshSendText = () => undefined;
|
|
688
696
|
const scoreService = ctx.get('dsbot-channel/score');
|
|
689
697
|
const judgeService = ctx.get('dsbot-channel/judge');
|
|
690
698
|
const declaredPipelines = pipelineScopesOf(effective).map((scopes) => resolvePipeline(scopes).nodes);
|
|
@@ -1100,6 +1108,8 @@ async function applyWith(ctx, raw, options = {}, skillFile) {
|
|
|
1100
1108
|
const chanceCounts = new Map();
|
|
1101
1109
|
const selections = new Map();
|
|
1102
1110
|
const started = [];
|
|
1111
|
+
const handles = new Map();
|
|
1112
|
+
const windowsByChannel = new Map();
|
|
1103
1113
|
const runtimes = [];
|
|
1104
1114
|
const configLayers = [];
|
|
1105
1115
|
ctx.effect(() => async () => {
|
|
@@ -1143,6 +1153,7 @@ async function applyWith(ctx, raw, options = {}, skillFile) {
|
|
|
1143
1153
|
const context = new ContextBuffer(turnSettings.context);
|
|
1144
1154
|
const windows = new TurnWindows({ now: clock, setTimer, clearTimer }, { limits: { maxBatch: turnSettings.maxBatch, maxChars: turnSettings.maxChars }, windowMs: turnSettings.coalesceMs });
|
|
1145
1155
|
windowBuffers.push(windows);
|
|
1156
|
+
windowsByChannel.set(adapter.channel, windows);
|
|
1146
1157
|
const mergedPolicy = effectivePolicy(rawProfile.policy, writtenBlock?.policy);
|
|
1147
1158
|
const compiled = compilePolicy(mergedPolicy);
|
|
1148
1159
|
const policyBlock = mergedPolicy;
|
|
@@ -1401,7 +1412,9 @@ async function applyWith(ctx, raw, options = {}, skillFile) {
|
|
|
1401
1412
|
windows.offer(`${adapter.botName}|${message.sessionKey}`, { eventKey: message.eventKey, chars: message.body.text.length, value: message }, (batch) => runBatch(batch));
|
|
1402
1413
|
return { ok: true };
|
|
1403
1414
|
};
|
|
1404
|
-
|
|
1415
|
+
const handle = await transport.start({ onMessage, resolveSecret });
|
|
1416
|
+
started.push(handle);
|
|
1417
|
+
handles.set(adapter.channel, handle);
|
|
1405
1418
|
}
|
|
1406
1419
|
};
|
|
1407
1420
|
try {
|
|
@@ -1451,15 +1464,23 @@ async function applyWith(ctx, raw, options = {}, skillFile) {
|
|
|
1451
1464
|
writtenRaw.push(raw);
|
|
1452
1465
|
channels.push(...fresh);
|
|
1453
1466
|
mountedBy.set(config.channel, source);
|
|
1454
|
-
for (const mounted of fresh)
|
|
1467
|
+
for (const mounted of fresh) {
|
|
1455
1468
|
reporting.add(mounted.adapter);
|
|
1469
|
+
byChannel.set(mounted.config.channel, mounted);
|
|
1470
|
+
pushChannels.push({ channel: mounted.config.channel, botName: mounted.config.botName });
|
|
1471
|
+
}
|
|
1456
1472
|
try {
|
|
1457
1473
|
checkReplyModes(fresh);
|
|
1458
1474
|
refreshBots();
|
|
1459
1475
|
}
|
|
1460
1476
|
catch (error) {
|
|
1461
|
-
for (const mounted of fresh)
|
|
1477
|
+
for (const mounted of fresh) {
|
|
1462
1478
|
reporting.delete(mounted.adapter);
|
|
1479
|
+
byChannel.delete(mounted.config.channel);
|
|
1480
|
+
const pushed = pushChannels.findIndex((one) => one.channel === mounted.config.channel);
|
|
1481
|
+
if (pushed >= 0)
|
|
1482
|
+
pushChannels.splice(pushed, 1);
|
|
1483
|
+
}
|
|
1463
1484
|
mountedBy.delete(config.channel);
|
|
1464
1485
|
channels.splice(channels.length - fresh.length, fresh.length);
|
|
1465
1486
|
writtenRaw.pop();
|
|
@@ -1471,10 +1492,89 @@ async function applyWith(ctx, raw, options = {}, skillFile) {
|
|
|
1471
1492
|
identity = readIdentity(true);
|
|
1472
1493
|
readImportTarget();
|
|
1473
1494
|
}
|
|
1495
|
+
refreshSendText();
|
|
1474
1496
|
startQueued(fresh);
|
|
1475
1497
|
};
|
|
1498
|
+
/** Drops one mounted channel: every live view forgets it, and its listener and sessions are released. */
|
|
1499
|
+
const removeChannel = (channel, source) => {
|
|
1500
|
+
const run = async () => {
|
|
1501
|
+
const mounted = byChannel.get(channel);
|
|
1502
|
+
if (mounted === undefined)
|
|
1503
|
+
return;
|
|
1504
|
+
const declared = mountedBy.get(channel);
|
|
1505
|
+
if (source !== undefined && declared !== undefined && declared !== source)
|
|
1506
|
+
return;
|
|
1507
|
+
const windows = windowsByChannel.get(channel);
|
|
1508
|
+
if (windows !== undefined) {
|
|
1509
|
+
const at = windowBuffers.indexOf(windows);
|
|
1510
|
+
if (at >= 0)
|
|
1511
|
+
windowBuffers.splice(at, 1);
|
|
1512
|
+
windows.clear();
|
|
1513
|
+
windowsByChannel.delete(channel);
|
|
1514
|
+
}
|
|
1515
|
+
const sessions = selections.get(channel);
|
|
1516
|
+
if (sessions !== undefined) {
|
|
1517
|
+
const at = runtimes.indexOf(sessions);
|
|
1518
|
+
if (at >= 0)
|
|
1519
|
+
runtimes.splice(at, 1);
|
|
1520
|
+
selections.delete(channel);
|
|
1521
|
+
sessions.dispose();
|
|
1522
|
+
}
|
|
1523
|
+
const handle = handles.get(channel);
|
|
1524
|
+
handles.delete(channel);
|
|
1525
|
+
if (handle !== undefined) {
|
|
1526
|
+
const at = started.indexOf(handle);
|
|
1527
|
+
if (at >= 0)
|
|
1528
|
+
started.splice(at, 1);
|
|
1529
|
+
try {
|
|
1530
|
+
await handle.stop();
|
|
1531
|
+
}
|
|
1532
|
+
catch (error) {
|
|
1533
|
+
warn(`dsbot-channel: stopping "${channel}" failed: ${String(error)}`);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
const configAt = mounts.indexOf(mounted.config);
|
|
1537
|
+
if (configAt >= 0) {
|
|
1538
|
+
mounts.splice(configAt, 1);
|
|
1539
|
+
writtenRaw.splice(configAt, 1);
|
|
1540
|
+
}
|
|
1541
|
+
const at = channels.indexOf(mounted);
|
|
1542
|
+
if (at >= 0)
|
|
1543
|
+
channels.splice(at, 1);
|
|
1544
|
+
const pushed = pushChannels.findIndex((one) => one.channel === channel);
|
|
1545
|
+
if (pushed >= 0)
|
|
1546
|
+
pushChannels.splice(pushed, 1);
|
|
1547
|
+
byChannel.delete(channel);
|
|
1548
|
+
mountedBy.delete(channel);
|
|
1549
|
+
reporting.delete(mounted.adapter);
|
|
1550
|
+
chanceCounts.delete(channel);
|
|
1551
|
+
const layerAt = configLayers.findIndex((entry) => entry.channel === channel);
|
|
1552
|
+
if (layerAt >= 0)
|
|
1553
|
+
configLayers.splice(layerAt, 1);
|
|
1554
|
+
if (!channels.some((one) => one.config.botName === mounted.config.botName)) {
|
|
1555
|
+
const ledgerAt = ledgers.findIndex((one) => one.bot === mounted.config.botName);
|
|
1556
|
+
if (ledgerAt >= 0)
|
|
1557
|
+
ledgers.splice(ledgerAt, 1);
|
|
1558
|
+
}
|
|
1559
|
+
try {
|
|
1560
|
+
refreshBots();
|
|
1561
|
+
identity = readIdentity(true);
|
|
1562
|
+
readImportTarget();
|
|
1563
|
+
const dangling = [...identityPending.values(), ...importPending.values()].filter((error) => error.message.includes(channel));
|
|
1564
|
+
if (dangling.length > 0)
|
|
1565
|
+
warn(`dsbot-channel: after unmounting "${channel}": ${dangling[0].message}`);
|
|
1566
|
+
}
|
|
1567
|
+
catch (error) {
|
|
1568
|
+
warn(`dsbot-channel: re-checking the profile after unmounting "${channel}" failed: ${String(error)}`);
|
|
1569
|
+
}
|
|
1570
|
+
refreshSendText();
|
|
1571
|
+
};
|
|
1572
|
+
const queued = starting.then(run);
|
|
1573
|
+
starting = queued.then(() => undefined, () => undefined);
|
|
1574
|
+
return queued;
|
|
1575
|
+
};
|
|
1476
1576
|
ctx.provide(MOUNT_SERVICE);
|
|
1477
|
-
ctx.set(MOUNT_SERVICE, { add: addInstance });
|
|
1577
|
+
ctx.set(MOUNT_SERVICE, { add: addInstance, remove: removeChannel });
|
|
1478
1578
|
// An instance queued while the gateway was still applying is mounted here instead of being lost.
|
|
1479
1579
|
for (const one of registry.takeInstances())
|
|
1480
1580
|
addInstance(one);
|
|
@@ -1507,12 +1607,8 @@ async function applyWith(ctx, raw, options = {}, skillFile) {
|
|
|
1507
1607
|
ctx.provide('dsbot-channel.peers');
|
|
1508
1608
|
ctx.set('dsbot-channel.peers', started);
|
|
1509
1609
|
if (tools !== undefined && typeof tools.register === 'function') {
|
|
1510
|
-
const byChannel = new Map(channels.map((channel) => [channel.config.channel, channel]));
|
|
1511
1610
|
const router = new PushRouter({
|
|
1512
|
-
channels:
|
|
1513
|
-
channel: channel.config.channel,
|
|
1514
|
-
botName: channel.config.botName,
|
|
1515
|
-
})),
|
|
1611
|
+
channels: pushChannels,
|
|
1516
1612
|
source: (sessionId) => (sessionId === undefined ? undefined : sources.get(sessionId)),
|
|
1517
1613
|
checkTarget: (channel, target) => byChannel.get(channel)?.adapter.checkTarget(target),
|
|
1518
1614
|
describeTargets: (channel) => byChannel.get(channel)?.adapter.describeTargets() ?? '',
|
|
@@ -1556,7 +1652,19 @@ async function applyWith(ctx, raw, options = {}, skillFile) {
|
|
|
1556
1652
|
return sent;
|
|
1557
1653
|
},
|
|
1558
1654
|
});
|
|
1559
|
-
|
|
1655
|
+
let releaseSendText;
|
|
1656
|
+
// The description lists the live channels, so a mount or an unmount republished it.
|
|
1657
|
+
refreshSendText = () => {
|
|
1658
|
+
releaseSendText?.();
|
|
1659
|
+
releaseSendText = tools.register(createSendText(router));
|
|
1660
|
+
};
|
|
1661
|
+
ctx.effect(() => {
|
|
1662
|
+
refreshSendText();
|
|
1663
|
+
return () => {
|
|
1664
|
+
releaseSendText?.();
|
|
1665
|
+
releaseSendText = undefined;
|
|
1666
|
+
};
|
|
1667
|
+
}, 'dsbot-channel: send_text tool');
|
|
1560
1668
|
const gate = new CapabilityGate({
|
|
1561
1669
|
router,
|
|
1562
1670
|
capabilities: (channel) => byChannel.get(channel)?.adapter.capabilities,
|