@helyx/bot 0.4.33 → 0.4.36
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 +45 -0
- package/dist/cloud-management-module.js +7 -1
- package/dist/control-server.d.ts +6 -7
- package/dist/control-server.js +38 -45
- package/dist/dashboard-commands.d.ts +10 -0
- package/dist/dashboard-commands.js +47 -0
- package/dist/developer-operations-module.js +5 -1
- package/dist/discord-guild-health.d.ts +4 -3
- package/dist/discord-guild-health.js +6 -3
- package/dist/events.js +38 -16
- package/dist/index.js +43 -3
- package/dist/interactions.d.ts +3 -2
- package/dist/interactions.js +132 -45
- package/dist/internal-module-manifest.d.ts +13 -0
- package/dist/internal-module-manifest.js +14 -0
- package/dist/management-module.js +7 -2
- package/dist/module-availability-control.d.ts +24 -0
- package/dist/module-availability-control.js +61 -0
- package/dist/modules.js +24 -1
- package/dist/nexus-commands.d.ts +13 -0
- package/dist/nexus-commands.js +100 -0
- package/dist/numeric-barriers.d.ts +11 -0
- package/dist/numeric-barriers.js +14 -0
- package/dist/numeric-write-behind-proxy.d.ts +4 -4
- package/dist/numeric-write-behind-proxy.js +45 -24
- package/dist/numeric-write-behind-redis.d.ts +4 -4
- package/dist/numeric-write-behind-redis.js +132 -70
- package/dist/oauth-module.js +3 -1
- package/dist/permissions-module.js +18 -5
- package/dist/renewable-lease.d.ts +16 -0
- package/dist/renewable-lease.js +66 -0
- package/dist/scheduled-task-runner.js +15 -1
- package/dist/shard-router.d.ts +2 -0
- package/dist/shard-router.js +5 -0
- package/dist/shard-supervisor.d.ts +5 -0
- package/dist/shard-supervisor.js +63 -4
- package/dist/shard-worker.d.ts +7 -1
- package/dist/shard-worker.js +4 -1
- package/dist/support-module.js +3 -1
- package/package.json +5 -4
package/dist/shard-supervisor.js
CHANGED
|
@@ -23,6 +23,7 @@ import { createDeveloperOperationsModule } from "./developer-operations-module.j
|
|
|
23
23
|
import { SupervisorCloudDiscordDirectory } from "./supervisor-cloud-directory.js";
|
|
24
24
|
import { createSupportModule } from "./support-module.js";
|
|
25
25
|
import { createOAuthModule } from "./oauth-module.js";
|
|
26
|
+
import { availabilityCatalogue, availabilityCatalogueDefinitions, } from "./module-availability-control.js";
|
|
26
27
|
import { createScheduledTaskRunner, schedulerOperationalHealth, } from "./scheduled-task-runner.js";
|
|
27
28
|
import { numericWriteBehindRegistrations } from "./numeric-write-behind.js";
|
|
28
29
|
import { executeRedisNumericOperation, RedisNumericWriteBehindService, } from "./numeric-write-behind-redis.js";
|
|
@@ -56,6 +57,13 @@ export function createStartupWorkerHeartbeatMaintenance(heartbeat, intervalMs =
|
|
|
56
57
|
stop: () => clearInterval(timer),
|
|
57
58
|
};
|
|
58
59
|
}
|
|
60
|
+
export function relinquishRestartCircuitOwnership(input) {
|
|
61
|
+
const lease = input.localLeases.get(input.shardId);
|
|
62
|
+
if (!lease)
|
|
63
|
+
return Promise.resolve(false);
|
|
64
|
+
input.localLeases.delete(input.shardId);
|
|
65
|
+
return input.release(lease);
|
|
66
|
+
}
|
|
59
67
|
export async function deferScheduledRoutingFailures(route) {
|
|
60
68
|
try {
|
|
61
69
|
return await route();
|
|
@@ -119,6 +127,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
119
127
|
const pendingNumericCommitted = new Map();
|
|
120
128
|
const restartHistory = new Map();
|
|
121
129
|
const restartCounts = new Map();
|
|
130
|
+
const openRestartCircuits = new Set();
|
|
122
131
|
const workers = new Map();
|
|
123
132
|
let sessionBudget;
|
|
124
133
|
let databaseHealthy = false;
|
|
@@ -229,6 +238,10 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
229
238
|
...publicModules,
|
|
230
239
|
...(operationsModule ? [operationsModule] : []),
|
|
231
240
|
];
|
|
241
|
+
const availabilityModules = availabilityCatalogueDefinitions(modules);
|
|
242
|
+
const availabilityService = {
|
|
243
|
+
listAvailabilityModules: () => availabilityCatalogue(availabilityModules),
|
|
244
|
+
};
|
|
232
245
|
const initialiseLeader = async () => {
|
|
233
246
|
await runLeaderStartup(database, environment, modulePackages, modules, logger);
|
|
234
247
|
assertStartupLeaseHealthy();
|
|
@@ -258,6 +271,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
258
271
|
localLeases,
|
|
259
272
|
dispatchLocal,
|
|
260
273
|
privacy: privacyService,
|
|
274
|
+
availability: availabilityService,
|
|
261
275
|
localLeaderLease: leaderLease,
|
|
262
276
|
...(coordinator ? { coordinator } : {}),
|
|
263
277
|
...(environment.DISCORD_INTERNAL_GUILD_ID
|
|
@@ -351,6 +365,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
351
365
|
localLeases,
|
|
352
366
|
dispatchLocal,
|
|
353
367
|
...(privacyService ? { privacy: privacyService } : {}),
|
|
368
|
+
availability: availabilityService,
|
|
354
369
|
...(leaderLease ? { localLeaderLease: leaderLease } : {}),
|
|
355
370
|
...(coordinator ? { coordinator } : {}),
|
|
356
371
|
...(environment.DISCORD_INTERNAL_GUILD_ID
|
|
@@ -526,8 +541,9 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
526
541
|
}
|
|
527
542
|
function installNumericCommitRouting(service) {
|
|
528
543
|
for (const registration of numericRegistrations) {
|
|
529
|
-
service.subscribeCommitted(registration, async (batch) => {
|
|
544
|
+
service.subscribeCommitted(registration, async (batch, signal) => {
|
|
530
545
|
for (let index = 0; index < batch.mutations.length; index += 1) {
|
|
546
|
+
signal.throwIfAborted();
|
|
531
547
|
const mutation = batch.mutations[index];
|
|
532
548
|
const result = batch.results[index];
|
|
533
549
|
const guildId = mutation?.identity.guild_id;
|
|
@@ -563,32 +579,53 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
563
579
|
payload: encodeNumericIpcValue(committedItem),
|
|
564
580
|
};
|
|
565
581
|
assertShardMessageSize(notification);
|
|
566
|
-
const response = await dispatchNumericCommitted(shardId, identity, notification);
|
|
582
|
+
const response = await dispatchNumericCommitted(shardId, identity, notification, signal);
|
|
567
583
|
if (!response.ok)
|
|
568
584
|
throw new Error(`Committed numeric batch was not applied: ${response.safeCode}`);
|
|
569
585
|
}
|
|
570
586
|
});
|
|
571
587
|
}
|
|
572
588
|
}
|
|
573
|
-
function dispatchNumericCommitted(shardId, identity, notification) {
|
|
589
|
+
function dispatchNumericCommitted(shardId, identity, notification, signal) {
|
|
574
590
|
const worker = workers.get(shardId);
|
|
575
591
|
if (!worker?.process)
|
|
576
592
|
return Promise.reject(new Error("Committed numeric batch owner is unavailable"));
|
|
577
593
|
return new Promise((resolve, reject) => {
|
|
594
|
+
const onAbort = () => {
|
|
595
|
+
const pending = pendingNumericCommitted.get(notification.requestId);
|
|
596
|
+
if (!pending)
|
|
597
|
+
return;
|
|
598
|
+
pendingNumericCommitted.delete(notification.requestId);
|
|
599
|
+
clearTimeout(pending.timer);
|
|
600
|
+
pending.removeAbortListener();
|
|
601
|
+
reject(signal.reason instanceof Error
|
|
602
|
+
? signal.reason
|
|
603
|
+
: new Error("Numeric claim ownership was lost"));
|
|
604
|
+
};
|
|
578
605
|
const timer = setTimeout(() => {
|
|
606
|
+
const pending = pendingNumericCommitted.get(notification.requestId);
|
|
579
607
|
pendingNumericCommitted.delete(notification.requestId);
|
|
608
|
+
pending?.removeAbortListener();
|
|
580
609
|
reject(new Error("Committed numeric batch acknowledgement timed out"));
|
|
581
610
|
}, SHARD_CONTROL_TIMEOUT_MS);
|
|
582
611
|
timer.unref();
|
|
612
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
583
613
|
pendingNumericCommitted.set(notification.requestId, {
|
|
584
614
|
shardId,
|
|
585
615
|
identity,
|
|
586
616
|
resolve,
|
|
587
617
|
timer,
|
|
618
|
+
removeAbortListener: () => signal.removeEventListener("abort", onAbort),
|
|
588
619
|
});
|
|
620
|
+
if (signal.aborted) {
|
|
621
|
+
onAbort();
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
589
624
|
void worker.send(notification).catch((error) => {
|
|
590
625
|
clearTimeout(timer);
|
|
626
|
+
const pending = pendingNumericCommitted.get(notification.requestId);
|
|
591
627
|
pendingNumericCommitted.delete(notification.requestId);
|
|
628
|
+
pending?.removeAbortListener();
|
|
592
629
|
reject(error instanceof Error
|
|
593
630
|
? error
|
|
594
631
|
: new Error("Committed numeric delivery failed"));
|
|
@@ -713,6 +750,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
713
750
|
return;
|
|
714
751
|
pendingNumericCommitted.delete(message.data.requestId);
|
|
715
752
|
clearTimeout(pending.timer);
|
|
753
|
+
pending.removeAbortListener();
|
|
716
754
|
pending.resolve(message.data);
|
|
717
755
|
return;
|
|
718
756
|
}
|
|
@@ -902,6 +940,25 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
902
940
|
const now = Date.now();
|
|
903
941
|
const recent = (restartHistory.get(shardId) ?? []).filter((time) => now - time < RESTART_WINDOW_MS);
|
|
904
942
|
if (recent.length >= MAX_RESTARTS_PER_WINDOW) {
|
|
943
|
+
openRestartCircuits.add(shardId);
|
|
944
|
+
const circuitCoordinator = coordinator;
|
|
945
|
+
if (circuitCoordinator)
|
|
946
|
+
void relinquishRestartCircuitOwnership({
|
|
947
|
+
shardId,
|
|
948
|
+
localLeases,
|
|
949
|
+
release: (lease) => circuitCoordinator.releaseShard(lease),
|
|
950
|
+
})
|
|
951
|
+
.then((released) => {
|
|
952
|
+
if (!released)
|
|
953
|
+
logger.warn({ event: "shard.circuit_lease_release_stale", shardId }, "Shard restart circuit lease was already reassigned");
|
|
954
|
+
})
|
|
955
|
+
.catch((error) => logger.warn({
|
|
956
|
+
event: "shard.circuit_lease_release_failed",
|
|
957
|
+
shardId,
|
|
958
|
+
error: safeError(error),
|
|
959
|
+
}, "Shard restart circuit lease will expire without renewal"));
|
|
960
|
+
else
|
|
961
|
+
localLeases.delete(shardId);
|
|
905
962
|
logger.error({ event: "shard.circuit_open", shardId, restartCount: recent.length }, "Shard worker restart circuit opened");
|
|
906
963
|
return;
|
|
907
964
|
}
|
|
@@ -991,7 +1048,9 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
991
1048
|
try {
|
|
992
1049
|
const occupied = new Set((await withTimeout(coordinator.getShardLeases(shardCount), LEASE_VALIDATION_TIMEOUT_MS).catch(() => [])).map(({ shardId }) => shardId));
|
|
993
1050
|
const candidates = Array.from({ length: shardCount }, (_, shardId) => shardId)
|
|
994
|
-
.filter((shardId) => !localLeases.has(shardId) &&
|
|
1051
|
+
.filter((shardId) => !localLeases.has(shardId) &&
|
|
1052
|
+
!occupied.has(shardId) &&
|
|
1053
|
+
!openRestartCircuits.has(shardId))
|
|
995
1054
|
.slice(0, capacity);
|
|
996
1055
|
const results = await Promise.all(candidates.map(async (shardId) => {
|
|
997
1056
|
const lease = await withTimeout(coordinator.claimShard(generationDescriptor, shardId, deploymentOrder), LEASE_VALIDATION_TIMEOUT_MS).catch(() => undefined);
|
package/dist/shard-worker.d.ts
CHANGED
|
@@ -50,12 +50,15 @@ export declare function parseShardWorkerEnvironment(processEnvironment: NodeJS.P
|
|
|
50
50
|
HELYX_OAUTH_ENABLED: boolean;
|
|
51
51
|
HELYX_CLOUD_ENDPOINT: string;
|
|
52
52
|
HELYX_OWNER_USER_IDS: string[];
|
|
53
|
-
HELYX_SHARD_MODE: "
|
|
53
|
+
HELYX_SHARD_MODE: "fixed" | "off" | "auto";
|
|
54
54
|
HELYX_SHARD_SCOPE: "local" | "coordinated";
|
|
55
55
|
HELYX_CLOUD_AUDIT_RETENTION_DAYS: number;
|
|
56
56
|
HELYX_CLOUD_CREDENTIAL_AUDIT_RETENTION_DAYS: number;
|
|
57
57
|
LOG_LEVEL: "error" | "fatal" | "warn" | "info" | "debug" | "trace" | "silent";
|
|
58
58
|
NODE_ENV: "development" | "test" | "production";
|
|
59
|
+
HELYX_STATUS_ENABLED: boolean;
|
|
60
|
+
HELYX_STATUS_URL: string;
|
|
61
|
+
HELYX_STATUS_HEARTBEAT_INTERVAL_SECONDS: number;
|
|
59
62
|
HELYX_BOT_CONTROL_SECRET?: string | undefined;
|
|
60
63
|
HELYX_SUPPORT_SERVICE_SECRET?: string | undefined;
|
|
61
64
|
HELYX_VERIFICATION_INSTALLATION_ID?: string | undefined;
|
|
@@ -69,6 +72,9 @@ export declare function parseShardWorkerEnvironment(processEnvironment: NodeJS.P
|
|
|
69
72
|
HELYX_SHARD_REDIS_NAMESPACE?: string | undefined;
|
|
70
73
|
HELYX_SHARD_GENERATION?: string | undefined;
|
|
71
74
|
HELYX_SHARD_DATABASE_CONNECTION_BUDGET?: number | undefined;
|
|
75
|
+
HELYX_STATUS_HEARTBEAT_SECRET?: string | undefined;
|
|
76
|
+
CF_ACCESS_CLIENT_ID?: string | undefined;
|
|
77
|
+
CF_ACCESS_CLIENT_SECRET?: string | undefined;
|
|
72
78
|
};
|
|
73
79
|
export declare function shardWorkerCommandRegistration(input: {
|
|
74
80
|
internalGuildId?: string;
|
package/dist/shard-worker.js
CHANGED
|
@@ -3,7 +3,7 @@ import { safeError, ServiceRegistry } from "@helyx/core";
|
|
|
3
3
|
import { checkDatabaseConnection, createDatabasePool } from "@helyx/database";
|
|
4
4
|
import { createRuntime } from "@helyx/framework";
|
|
5
5
|
import { createPlatformServices } from "@helyx/platform";
|
|
6
|
-
import { HELYX_SERVICE_NAMES } from "@helyx/sdk";
|
|
6
|
+
import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
7
7
|
import { Client, Events, Partials } from "discord.js";
|
|
8
8
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
9
9
|
import pino from "pino";
|
|
@@ -179,6 +179,9 @@ export async function startShardWorker(processEnvironment = process.env, identit
|
|
|
179
179
|
})) {
|
|
180
180
|
platformServices.set(name, service);
|
|
181
181
|
}
|
|
182
|
+
await serviceAccess
|
|
183
|
+
.get(HELYX_SERVICE_NAMES.moduleAvailability)
|
|
184
|
+
.refresh();
|
|
182
185
|
if (environment.HELYX_VERIFICATION_ENABLED &&
|
|
183
186
|
environment.HELYX_VERIFICATION_URL &&
|
|
184
187
|
environment.HELYX_VERIFICATION_INTERNAL_SECRET) {
|
package/dist/support-module.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineModule, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { INTERNAL_MODULE_MANIFEST_FIELDS } from "./internal-module-manifest.js";
|
|
3
4
|
const MODAL_ID = "helyx.support.bug";
|
|
4
5
|
const MODULE_FIELD_ID = "helyx.support.bug.module";
|
|
5
6
|
const SUMMARY_FIELD_ID = "helyx.support.bug.summary";
|
|
@@ -11,7 +12,7 @@ const HOUR_SECONDS = 60 * 60;
|
|
|
11
12
|
export function createSupportModule(transport) {
|
|
12
13
|
return defineModule({
|
|
13
14
|
manifest: {
|
|
14
|
-
|
|
15
|
+
...INTERNAL_MODULE_MANIFEST_FIELDS,
|
|
15
16
|
releaseStatus: "live",
|
|
16
17
|
id: MODULE_ID,
|
|
17
18
|
name: "Helyx Support",
|
|
@@ -28,6 +29,7 @@ export function createSupportModule(transport) {
|
|
|
28
29
|
{
|
|
29
30
|
name: "bug",
|
|
30
31
|
description: "Report a Helyx bug to the support team",
|
|
32
|
+
acknowledgement: "immediate-modal",
|
|
31
33
|
registration: "global",
|
|
32
34
|
contexts: ["guild"],
|
|
33
35
|
integrationTypes: ["guild_install"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helyx/bot",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.36",
|
|
4
4
|
"description": "Production Discord runtime for the modular Helyx platform.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"helyx",
|
|
@@ -53,9 +53,10 @@
|
|
|
53
53
|
"@helyx/core": ">=0.1.0 <0.2.0",
|
|
54
54
|
"@helyx/database": ">=0.8.0 <0.9.0",
|
|
55
55
|
"@helyx/framework": ">=0.1.0 <0.2.0",
|
|
56
|
-
"@helyx/module-manifest": ">=0.
|
|
57
|
-
"@helyx/platform": ">=0.
|
|
58
|
-
"@helyx/sdk": ">=0.
|
|
56
|
+
"@helyx/module-manifest": ">=0.7.0 <0.8.0",
|
|
57
|
+
"@helyx/platform": ">=0.6.0 <0.7.0",
|
|
58
|
+
"@helyx/sdk": ">=0.8.0 <0.9.0",
|
|
59
|
+
"@helyx/status-client": ">=0.1.0 <0.2.0",
|
|
59
60
|
"@helyx/verification-contracts": ">=0.3.0 <0.4.0",
|
|
60
61
|
"discord.js": "14.27.0",
|
|
61
62
|
"fastify": "5.10.0",
|