@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.36
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Harden startup cleanup, lease ownership, database transaction handling, worker shutdown, and stale-work fencing across the hosted and self-hosted runtime.
|
|
8
|
+
|
|
9
|
+
## 0.4.35
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 26b4c3e: Add versioned hidden module dependencies, automatic installation lifecycle,
|
|
14
|
+
delegated customer health, and transitive self-host build composition for the
|
|
15
|
+
Nexus foundation while preserving legacy manifest and catalogue behavior. Add
|
|
16
|
+
closed, owner-aware composition for the shared `/nexus` command namespace and
|
|
17
|
+
bounded Core reads for terminology, member history and confirmed member value
|
|
18
|
+
changes. Add resumable, idempotent role and server bulk-award jobs.
|
|
19
|
+
Add bounded Nexus privacy capture, a durable hashed erasure fence and atomic
|
|
20
|
+
member detachment that journals any remaining balance as a privacy burn before
|
|
21
|
+
removing account and bulk-recipient identifiers.
|
|
22
|
+
Generalise the durable Achievement contribution bus as member contributions
|
|
23
|
+
while retaining the existing service names as compatibility aliases.
|
|
24
|
+
Add the private Nexus Rewards Testing package, complete initial record schema
|
|
25
|
+
and an atomic timezone-aware Daily Claim path backed by the shared ledger.
|
|
26
|
+
Add reviewed, default-off Nexus eligibility metadata to existing contribution
|
|
27
|
+
catalogues and atomically apply configured fixed rewards with source, role,
|
|
28
|
+
rate-cap, replay, balance-cap and privacy safeguards.
|
|
29
|
+
Add definition-owned Achievement reward amounts and live repeatable-award
|
|
30
|
+
high-water processing so retries credit only newly committed occurrences.
|
|
31
|
+
Add the private Nexus Economy Testing package and its complete initial Exchange,
|
|
32
|
+
item, redemption, inventory, role-grant and durable fulfilment record schema.
|
|
33
|
+
Add a typed atomic Economy redemption path with exact replay, finite stock,
|
|
34
|
+
member limits, role eligibility, collectible inventory and durable fulfilment.
|
|
35
|
+
Add coalesced leased role fulfilment with reviewed Discord safety, observed
|
|
36
|
+
delivery reconciliation, bounded retries and Core-linked automatic refunds.
|
|
37
|
+
- Updated dependencies [26b4c3e]
|
|
38
|
+
- @helyx/module-manifest@0.7.0
|
|
39
|
+
- @helyx/platform@0.6.0
|
|
40
|
+
- @helyx/sdk@0.8.0
|
|
41
|
+
|
|
42
|
+
## 0.4.34
|
|
43
|
+
|
|
44
|
+
### Patch Changes
|
|
45
|
+
|
|
46
|
+
- Add fail-open production status heartbeat configuration and reporting support.
|
|
47
|
+
|
|
3
48
|
## 0.4.33
|
|
4
49
|
|
|
5
50
|
### Patch Changes
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineModule, } from "@helyx/sdk";
|
|
2
2
|
import { Events } from "discord.js";
|
|
3
3
|
import { ClientCloudDiscordDirectory, CloudConnectorUserError, } from "./cloud-connector.js";
|
|
4
|
+
import { INTERNAL_MODULE_MANIFEST_FIELDS } from "./internal-module-manifest.js";
|
|
4
5
|
const CONNECT_MODAL_ID = "helyx.cloud.connect";
|
|
5
6
|
const CONNECT_CODE_FIELD_ID = "helyx.cloud.connect.code";
|
|
6
7
|
const DISCONNECT_MODAL_ID = "helyx.cloud.disconnect";
|
|
@@ -72,7 +73,7 @@ export function createCloudManagementModule(input) {
|
|
|
72
73
|
};
|
|
73
74
|
return defineModule({
|
|
74
75
|
manifest: {
|
|
75
|
-
|
|
76
|
+
...INTERNAL_MODULE_MANIFEST_FIELDS,
|
|
76
77
|
releaseStatus: "live",
|
|
77
78
|
id: "helyx.cloud-connect",
|
|
78
79
|
name: "Helyx Cloud Connect",
|
|
@@ -106,6 +107,7 @@ export function createCloudManagementModule(input) {
|
|
|
106
107
|
{
|
|
107
108
|
name: "connect",
|
|
108
109
|
description: "Connect this deployment using a one-use code",
|
|
110
|
+
acknowledgement: "immediate-modal",
|
|
109
111
|
rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
|
|
110
112
|
async execute(context) {
|
|
111
113
|
if (!(await requireOwner(context)))
|
|
@@ -136,6 +138,7 @@ export function createCloudManagementModule(input) {
|
|
|
136
138
|
{
|
|
137
139
|
name: "status",
|
|
138
140
|
description: "Show this deployment's Cloud Connect status",
|
|
141
|
+
acknowledgement: "deferred-ephemeral",
|
|
139
142
|
rateLimit: { scope: "user", limit: 20, windowSeconds: 60 },
|
|
140
143
|
async execute(context) {
|
|
141
144
|
if (!(await requireOwner(context)))
|
|
@@ -146,6 +149,7 @@ export function createCloudManagementModule(input) {
|
|
|
146
149
|
{
|
|
147
150
|
name: "disconnect",
|
|
148
151
|
description: "Revoke this deployment's Cloud Connect identity",
|
|
152
|
+
acknowledgement: "immediate-modal",
|
|
149
153
|
rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
|
|
150
154
|
async execute(context) {
|
|
151
155
|
if (!(await requireOwner(context)))
|
|
@@ -185,6 +189,7 @@ export function createCloudManagementModule(input) {
|
|
|
185
189
|
{
|
|
186
190
|
name: "rotate",
|
|
187
191
|
description: "Replace the saved Cloud Connect credential",
|
|
192
|
+
acknowledgement: "immediate-modal",
|
|
188
193
|
rateLimit: { scope: "user", limit: 3, windowSeconds: 60 },
|
|
189
194
|
async execute(context) {
|
|
190
195
|
if (!(await requireOwner(context)))
|
|
@@ -224,6 +229,7 @@ export function createCloudManagementModule(input) {
|
|
|
224
229
|
{
|
|
225
230
|
name: "support",
|
|
226
231
|
description: "Choose what Helyx support may inspect",
|
|
232
|
+
acknowledgement: "immediate-modal",
|
|
227
233
|
rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
|
|
228
234
|
async execute(context) {
|
|
229
235
|
if (!(await requireOwner(context)))
|
package/dist/control-server.d.ts
CHANGED
|
@@ -4,13 +4,9 @@ import { type ActionReceipt, type SignedAssessmentEnvelope } from "@helyx/verifi
|
|
|
4
4
|
import { type Client } from "discord.js";
|
|
5
5
|
import { type FastifyInstance } from "fastify";
|
|
6
6
|
import { type ShardHealthSnapshot } from "./shard-contracts.js";
|
|
7
|
-
import { type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
name: string;
|
|
11
|
-
description: string;
|
|
12
|
-
defaultPreset: DashboardPermissionPolicy["preset"];
|
|
13
|
-
}
|
|
7
|
+
import { type DashboardPermissionState } from "./control-permissions.js";
|
|
8
|
+
import { type DashboardCommandDefinition } from "./dashboard-commands.js";
|
|
9
|
+
import { type AvailabilityModuleCatalogue } from "./module-availability-control.js";
|
|
14
10
|
interface DashboardLogEventDefinition {
|
|
15
11
|
id: string;
|
|
16
12
|
name: string;
|
|
@@ -127,6 +123,7 @@ export interface BotControlState {
|
|
|
127
123
|
roles: BotControlGuildResources["roles"];
|
|
128
124
|
modules: DashboardModuleDefinition[];
|
|
129
125
|
moduleStatuses: Record<string, "enabled" | "disabled" | "unavailable">;
|
|
126
|
+
globallyDisabledModuleIds: string[];
|
|
130
127
|
configurations: Record<string, {
|
|
131
128
|
version: number;
|
|
132
129
|
value: Record<string, unknown>;
|
|
@@ -155,6 +152,7 @@ export interface BotControlHandler {
|
|
|
155
152
|
correlationId: string;
|
|
156
153
|
}): Promise<OAuthConsumerCleanupResult>;
|
|
157
154
|
sendSupportNotification(input: SupportNotificationInput): Promise<void>;
|
|
155
|
+
listAvailabilityModules(): Promise<AvailabilityModuleCatalogue>;
|
|
158
156
|
listGuildPresence(input: {
|
|
159
157
|
guildIds: readonly string[];
|
|
160
158
|
correlationId: string;
|
|
@@ -541,6 +539,7 @@ export declare class BotControlService implements BotControlHandler {
|
|
|
541
539
|
correlationId: string;
|
|
542
540
|
actorAuthenticatedAt?: string;
|
|
543
541
|
}): Promise<ManagedResourceDetail | null>;
|
|
542
|
+
listAvailabilityModules(): Promise<AvailabilityModuleCatalogue>;
|
|
544
543
|
}
|
|
545
544
|
export declare function createBotControlServer(input: {
|
|
546
545
|
service: BotControlHandler;
|
package/dist/control-server.js
CHANGED
|
@@ -7,9 +7,12 @@ import Fastify, {} from "fastify";
|
|
|
7
7
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
8
8
|
import { z, ZodError } from "zod";
|
|
9
9
|
import { addChannelMessageReaction as addDiscordChannelMessageReaction, deleteChannelMessage as deleteDiscordChannelMessage, editChannelMessage as editDiscordChannelMessage, normalizeDiscordPermission, sendToChannel as sendDiscordMessageToChannel, } from "./interactions.js";
|
|
10
|
+
import { runNestedGuildBarriers } from "./numeric-barriers.js";
|
|
10
11
|
import { SHARD_HEALTH_STALE_MS, } from "./shard-contracts.js";
|
|
11
12
|
import { dashboardCategoryLabel } from "./dashboard-category.js";
|
|
12
|
-
import { assertVersion, dashboardActionPermissionId, declaredManagedResourcePermission,
|
|
13
|
+
import { assertVersion, dashboardActionPermissionId, declaredManagedResourcePermission, fromDashboardPolicy, managedResourcePermissionId, permissionPoliciesEqual, permissionPolicyKey, safeOptionalVersion, safeVersion, toDashboardPolicy, } from "./control-permissions.js";
|
|
14
|
+
import { dashboardCommands, } from "./dashboard-commands.js";
|
|
15
|
+
import { availabilityCatalogueDefinitions, availabilityCatalogue, dashboardModuleStatus, disabledModuleSummary, moduleAvailabilityService, moduleAvailabilityStates, } from "./module-availability-control.js";
|
|
13
16
|
const snowflake = z.string().regex(/^\d{17,20}$/u);
|
|
14
17
|
const moduleId = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/u);
|
|
15
18
|
const MAX_DISCORD_SETTING_SELECTIONS = 25;
|
|
@@ -258,6 +261,7 @@ export class BotControlService {
|
|
|
258
261
|
presenceShardCount;
|
|
259
262
|
#modules;
|
|
260
263
|
#dashboardModules;
|
|
264
|
+
#availabilityModules;
|
|
261
265
|
#configurations;
|
|
262
266
|
#installations;
|
|
263
267
|
#permissions;
|
|
@@ -273,6 +277,7 @@ export class BotControlService {
|
|
|
273
277
|
this.#dashboardModules = modules
|
|
274
278
|
.filter((module) => module.manifest.dashboard !== undefined)
|
|
275
279
|
.map(mapDashboardModule);
|
|
280
|
+
this.#availabilityModules = availabilityCatalogueDefinitions(modules);
|
|
276
281
|
this.#configurations = services.get(HELYX_SERVICE_NAMES.configuration);
|
|
277
282
|
this.#installations = services.get(HELYX_SERVICE_NAMES.installations);
|
|
278
283
|
this.#permissions = services.get(HELYX_SERVICE_NAMES.permissions);
|
|
@@ -423,14 +428,16 @@ export class BotControlService {
|
|
|
423
428
|
commandId: id,
|
|
424
429
|
})),
|
|
425
430
|
]);
|
|
426
|
-
const [resources, installedModules, storedLoggingPolicies, runtime, storedConfigurations, storedPermissions,] = await Promise.all([
|
|
431
|
+
const [resources, installedModules, storedLoggingPolicies, runtime, storedConfigurations, storedPermissions, availabilityStates,] = await Promise.all([
|
|
427
432
|
this.resources.getGuildResources(guildId),
|
|
428
433
|
new InstallationRepository(this.database).listModules(guildId),
|
|
429
434
|
new ModuleLoggingPolicyRepository(this.database).listForGuild(guildId),
|
|
430
435
|
this.#inspectRuntimeCapabilities(),
|
|
431
436
|
this.#readConfigurations(guildId, moduleIds),
|
|
432
437
|
this.#readPermissionPolicies(guildId, permissionTargets),
|
|
438
|
+
moduleAvailabilityStates(this.#services, moduleIds),
|
|
433
439
|
]);
|
|
440
|
+
const disabledModules = disabledModuleSummary(availabilityStates);
|
|
434
441
|
const installed = new Map(installedModules.map((module) => [module.moduleId, module]));
|
|
435
442
|
const moduleStatuses = {};
|
|
436
443
|
const configurations = {};
|
|
@@ -442,11 +449,7 @@ export class BotControlService {
|
|
|
442
449
|
]));
|
|
443
450
|
for (const { dashboardModule, module } of dashboardEntries) {
|
|
444
451
|
const installation = installed.get(dashboardModule.id);
|
|
445
|
-
moduleStatuses[dashboardModule.id] = installation
|
|
446
|
-
? installation.enabled
|
|
447
|
-
? "enabled"
|
|
448
|
-
: "disabled"
|
|
449
|
-
: "unavailable";
|
|
452
|
+
moduleStatuses[dashboardModule.id] = dashboardModuleStatus(disabledModules.set, dashboardModule.id, installation);
|
|
450
453
|
const configuration = storedConfigurations.get(dashboardModule.id);
|
|
451
454
|
if (configuration)
|
|
452
455
|
configurations[dashboardModule.id] = {
|
|
@@ -470,6 +473,7 @@ export class BotControlService {
|
|
|
470
473
|
...resources,
|
|
471
474
|
modules: structuredClone(this.#dashboardModules),
|
|
472
475
|
moduleStatuses,
|
|
476
|
+
globallyDisabledModuleIds: disabledModules.ids,
|
|
473
477
|
configurations,
|
|
474
478
|
permissions,
|
|
475
479
|
logging,
|
|
@@ -624,6 +628,7 @@ export class BotControlService {
|
|
|
624
628
|
}
|
|
625
629
|
async setModuleStatus(input) {
|
|
626
630
|
const module = this.#requireDashboardModule(input.moduleId);
|
|
631
|
+
await this.#requireModuleAvailable(input.moduleId);
|
|
627
632
|
const scopes = Object.entries(module.records ?? {})
|
|
628
633
|
.filter(([, collection]) => (collection.incrementableFields?.length ?? 0) > 0)
|
|
629
634
|
.map(([collection]) => ({
|
|
@@ -659,15 +664,11 @@ export class BotControlService {
|
|
|
659
664
|
await setEnabled();
|
|
660
665
|
}
|
|
661
666
|
else {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
await writeBehind.runGuildBarrier(scope, () => runBoundary(index + 1));
|
|
669
|
-
};
|
|
670
|
-
await runBoundary(0);
|
|
667
|
+
await runNestedGuildBarriers({
|
|
668
|
+
scopes,
|
|
669
|
+
writeBehind,
|
|
670
|
+
operation: setEnabled,
|
|
671
|
+
});
|
|
671
672
|
for (const scope of scopes)
|
|
672
673
|
await writeBehind.flushGuild(scope);
|
|
673
674
|
for (const scope of scopes)
|
|
@@ -677,6 +678,7 @@ export class BotControlService {
|
|
|
677
678
|
}
|
|
678
679
|
async updateConfiguration(input) {
|
|
679
680
|
const module = this.#requireDashboardModule(input.moduleId);
|
|
681
|
+
await this.#requireModuleAvailable(input.moduleId);
|
|
680
682
|
await this.#validateDiscordSettings(input.guildId, module, input.value);
|
|
681
683
|
if (module.configuration?.validateUpdate) {
|
|
682
684
|
try {
|
|
@@ -710,6 +712,7 @@ export class BotControlService {
|
|
|
710
712
|
}
|
|
711
713
|
async updatePermissions(input) {
|
|
712
714
|
const module = this.#requireDashboardModule(input.moduleId);
|
|
715
|
+
await this.#requireModuleAvailable(input.moduleId);
|
|
713
716
|
const commands = dashboardCommands(module);
|
|
714
717
|
const supplied = new Map(input.state.commands.map((command) => [command.commandId, command]));
|
|
715
718
|
if (supplied.size !== input.state.commands.length ||
|
|
@@ -833,6 +836,10 @@ export class BotControlService {
|
|
|
833
836
|
return this.#getPermissionState(input.guildId, module);
|
|
834
837
|
}
|
|
835
838
|
async updateLoggingPolicy(input) {
|
|
839
|
+
await Promise.all([
|
|
840
|
+
this.#requireModuleAvailable(input.moduleId),
|
|
841
|
+
this.#requireModuleAvailable("helyx.logging"),
|
|
842
|
+
]);
|
|
836
843
|
if (!this.#modules.has("helyx.logging")) {
|
|
837
844
|
throw new BotControlNotFoundError("Logging is not installed");
|
|
838
845
|
}
|
|
@@ -877,6 +884,7 @@ export class BotControlService {
|
|
|
877
884
|
}
|
|
878
885
|
async executeDashboardAction(input) {
|
|
879
886
|
const module = this.#requireDashboardModule(input.moduleId);
|
|
887
|
+
await this.#requireModuleAvailable(input.moduleId);
|
|
880
888
|
const declared = module.manifest.dashboard?.actions?.find(({ id }) => id === input.actionId);
|
|
881
889
|
const action = module.dashboardActions?.find(({ id }) => id === input.actionId);
|
|
882
890
|
if (!declared || !action) {
|
|
@@ -935,11 +943,13 @@ export class BotControlService {
|
|
|
935
943
|
})), relation.columns.map(({ key }) => key));
|
|
936
944
|
}
|
|
937
945
|
async previewManagedResource(input) {
|
|
946
|
+
await this.#requireModuleAvailable(input.moduleId);
|
|
938
947
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
939
948
|
this.#requireManagedResourceOperation(module, input.resourceType, "preview");
|
|
940
949
|
return resource.preview(await this.#managedResourceContext(input, module, "preview"), input.value);
|
|
941
950
|
}
|
|
942
951
|
async createManagedResource(input) {
|
|
952
|
+
await this.#requireModuleAvailable(input.moduleId);
|
|
943
953
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
944
954
|
this.#requireManagedResourceOperation(module, input.resourceType, "create");
|
|
945
955
|
const context = await this.#managedResourceContext(input, module, "create");
|
|
@@ -949,6 +959,7 @@ export class BotControlService {
|
|
|
949
959
|
return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.create(context, input.value, input.intent)), managedResourceColumnKeys(module, input.resourceType));
|
|
950
960
|
}
|
|
951
961
|
async updateManagedResource(input) {
|
|
962
|
+
await this.#requireModuleAvailable(input.moduleId);
|
|
952
963
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
953
964
|
this.#requireManagedResourceOperation(module, input.resourceType, "update");
|
|
954
965
|
return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.update(await this.#managedResourceContext(input, module, "update"), {
|
|
@@ -958,6 +969,7 @@ export class BotControlService {
|
|
|
958
969
|
})), managedResourceColumnKeys(module, input.resourceType));
|
|
959
970
|
}
|
|
960
971
|
async deleteManagedResource(input) {
|
|
972
|
+
await this.#requireModuleAvailable(input.moduleId);
|
|
961
973
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
962
974
|
this.#requireManagedResourceOperation(module, input.resourceType, "delete");
|
|
963
975
|
return managedResourceDeleteResultSchema.parse(await resource.delete(await this.#managedResourceContext(input, module, "delete"), {
|
|
@@ -966,6 +978,7 @@ export class BotControlService {
|
|
|
966
978
|
}));
|
|
967
979
|
}
|
|
968
980
|
async executeManagedResourceAction(input) {
|
|
981
|
+
await this.#requireModuleAvailable(input.moduleId);
|
|
969
982
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
970
983
|
const declared = module.manifest.dashboard?.resources
|
|
971
984
|
?.find(({ id }) => id === input.resourceType)
|
|
@@ -1061,6 +1074,14 @@ export class BotControlService {
|
|
|
1061
1074
|
throw new BotControlNotFoundError("Module is not installed");
|
|
1062
1075
|
return module;
|
|
1063
1076
|
}
|
|
1077
|
+
listAvailabilityModules() {
|
|
1078
|
+
return availabilityCatalogue(this.#availabilityModules);
|
|
1079
|
+
}
|
|
1080
|
+
async #requireModuleAvailable(moduleIdValue) {
|
|
1081
|
+
const availability = moduleAvailabilityService(this.#services);
|
|
1082
|
+
if (availability && !(await availability.isAvailable(moduleIdValue)))
|
|
1083
|
+
throw new BotControlValidationError("This module is temporarily unavailable for all hosted servers.");
|
|
1084
|
+
}
|
|
1064
1085
|
#requireDashboardModule(moduleIdValue) {
|
|
1065
1086
|
const module = this.#requireModule(moduleIdValue);
|
|
1066
1087
|
if (!module.manifest.dashboard)
|
|
@@ -1368,6 +1389,7 @@ export async function createBotControlServer(input) {
|
|
|
1368
1389
|
const { guildId } = z.object({ guildId: snowflake }).parse(request.params);
|
|
1369
1390
|
return input.service.getGuildState(guildId);
|
|
1370
1391
|
});
|
|
1392
|
+
server.get("/v1/modules/availability-catalogue", () => input.service.listAvailabilityModules());
|
|
1371
1393
|
server.post("/v1/guilds/:guildId/dashboard-access", async (request) => {
|
|
1372
1394
|
const { guildId } = z.object({ guildId: snowflake }).parse(request.params);
|
|
1373
1395
|
const { actorUserId } = z
|
|
@@ -1897,35 +1919,6 @@ function mapDashboardModule(module) {
|
|
|
1897
1919
|
: undefined,
|
|
1898
1920
|
};
|
|
1899
1921
|
}
|
|
1900
|
-
function dashboardCommands(module) {
|
|
1901
|
-
const commands = (module.interactions?.commands ?? []).flatMap((command) => command.subcommands?.length
|
|
1902
|
-
? command.subcommands.map((subcommand) => {
|
|
1903
|
-
const id = subcommand.permissionId ??
|
|
1904
|
-
command.permissionId ??
|
|
1905
|
-
`${command.name}.${subcommand.name}`;
|
|
1906
|
-
return {
|
|
1907
|
-
id,
|
|
1908
|
-
name: `/${command.name} ${subcommand.name}`,
|
|
1909
|
-
description: subcommand.description,
|
|
1910
|
-
defaultPreset: toDashboardPolicy(module.defaultPermissions?.commands[id] ??
|
|
1911
|
-
module.defaultPermissions?.module ??
|
|
1912
|
-
emptyPolicy()).preset,
|
|
1913
|
-
};
|
|
1914
|
-
})
|
|
1915
|
-
: [
|
|
1916
|
-
{
|
|
1917
|
-
id: command.permissionId ?? command.name,
|
|
1918
|
-
name: `/${command.name}`,
|
|
1919
|
-
description: command.description,
|
|
1920
|
-
defaultPreset: toDashboardPolicy(module.defaultPermissions?.commands[command.name] ??
|
|
1921
|
-
module.defaultPermissions?.module ??
|
|
1922
|
-
emptyPolicy()).preset,
|
|
1923
|
-
},
|
|
1924
|
-
]);
|
|
1925
|
-
return [
|
|
1926
|
-
...new Map(commands.map((command) => [command.id, command])).values(),
|
|
1927
|
-
];
|
|
1928
|
-
}
|
|
1929
1922
|
function assertPermissionMutationAllowed(decision) {
|
|
1930
1923
|
if (decision.allowed)
|
|
1931
1924
|
return;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ModuleDefinition } from "@helyx/sdk";
|
|
2
|
+
import { type DashboardPermissionPolicy } from "./control-permissions.js";
|
|
3
|
+
export interface DashboardCommandDefinition {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
defaultPreset: DashboardPermissionPolicy["preset"];
|
|
8
|
+
}
|
|
9
|
+
export declare function dashboardCommands(module: ModuleDefinition): DashboardCommandDefinition[];
|
|
10
|
+
//# sourceMappingURL=dashboard-commands.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { emptyPolicy, toDashboardPolicy, } from "./control-permissions.js";
|
|
2
|
+
export function dashboardCommands(module) {
|
|
3
|
+
const commands = (module.interactions?.commands ?? []).flatMap((command) => {
|
|
4
|
+
const nested = [
|
|
5
|
+
...(command.subcommands ?? []).map((subcommand) => dashboardCommand(module, {
|
|
6
|
+
id: subcommand.permissionId ??
|
|
7
|
+
command.permissionId ??
|
|
8
|
+
`${command.name}.${subcommand.name}`,
|
|
9
|
+
name: `/${command.name} ${subcommand.name}`,
|
|
10
|
+
description: subcommand.description,
|
|
11
|
+
})),
|
|
12
|
+
...(command.subcommandGroups ?? []).flatMap((group) => group.subcommands.map((subcommand) => dashboardCommand(module, {
|
|
13
|
+
id: subcommand.permissionId ??
|
|
14
|
+
command.permissionId ??
|
|
15
|
+
`${command.name}.${group.name}.${subcommand.name}`,
|
|
16
|
+
name: `/${command.name} ${group.name} ${subcommand.name}`,
|
|
17
|
+
description: subcommand.description,
|
|
18
|
+
}))),
|
|
19
|
+
];
|
|
20
|
+
if (nested.length > 0)
|
|
21
|
+
return nested;
|
|
22
|
+
return [
|
|
23
|
+
dashboardCommand(module, {
|
|
24
|
+
id: command.permissionId ?? command.name,
|
|
25
|
+
name: `/${command.name}`,
|
|
26
|
+
description: command.description,
|
|
27
|
+
}),
|
|
28
|
+
];
|
|
29
|
+
});
|
|
30
|
+
commands.push(...(module.nexusCommands ?? []).map((command) => dashboardCommand(module, {
|
|
31
|
+
id: `nexus.${command.path}`,
|
|
32
|
+
name: `/nexus ${command.path.replace(".", " ")}`,
|
|
33
|
+
description: command.description,
|
|
34
|
+
})));
|
|
35
|
+
return [
|
|
36
|
+
...new Map(commands.map((command) => [command.id, command])).values(),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
function dashboardCommand(module, command) {
|
|
40
|
+
return {
|
|
41
|
+
...command,
|
|
42
|
+
defaultPreset: toDashboardPolicy(module.defaultPermissions?.commands[command.id] ??
|
|
43
|
+
module.defaultPermissions?.module ??
|
|
44
|
+
emptyPolicy()).preset,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=dashboard-commands.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineModule, } from "@helyx/sdk";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { SHARD_CONTROL_TIMEOUT_MS, assertShardMessageSize, shardOperationsHealthResponseSchema, } from "./shard-contracts.js";
|
|
4
|
+
import { INTERNAL_MODULE_MANIFEST_FIELDS } from "./internal-module-manifest.js";
|
|
4
5
|
const DEVELOPER_MODULE_ID = "helyx.developer-operations";
|
|
5
6
|
const DEVELOPER_MODULE_NAME = "Helyx Developer Operations";
|
|
6
7
|
const DEVELOPER_MODULE_VERSION = "0.2.0";
|
|
@@ -9,7 +10,7 @@ export function createDeveloperOperationsModule(input) {
|
|
|
9
10
|
const approvedRoles = new Set(input.approvedRoleIds);
|
|
10
11
|
return defineModule({
|
|
11
12
|
manifest: {
|
|
12
|
-
|
|
13
|
+
...INTERNAL_MODULE_MANIFEST_FIELDS,
|
|
13
14
|
releaseStatus: "live",
|
|
14
15
|
id: DEVELOPER_MODULE_ID,
|
|
15
16
|
name: DEVELOPER_MODULE_NAME,
|
|
@@ -33,6 +34,7 @@ export function createDeveloperOperationsModule(input) {
|
|
|
33
34
|
{
|
|
34
35
|
name: "shards",
|
|
35
36
|
description: "Show bounded read-only shard health",
|
|
37
|
+
acknowledgement: "deferred-ephemeral",
|
|
36
38
|
rateLimit: { scope: "user", limit: 10, windowSeconds: 60 },
|
|
37
39
|
async execute(context) {
|
|
38
40
|
if (!(await authorizeDeveloperCommand(context)))
|
|
@@ -81,6 +83,7 @@ export function createDeveloperOperationsModule(input) {
|
|
|
81
83
|
{
|
|
82
84
|
name: "bot",
|
|
83
85
|
description: "Show the bot build and installed module versions",
|
|
86
|
+
acknowledgement: "deferred-ephemeral",
|
|
84
87
|
rateLimit: { scope: "user", limit: 10, windowSeconds: 60 },
|
|
85
88
|
async execute(context) {
|
|
86
89
|
if (!(await authorizeDeveloperCommand(context)))
|
|
@@ -134,6 +137,7 @@ export function createDeveloperOperationsModule(input) {
|
|
|
134
137
|
{
|
|
135
138
|
name: "proc",
|
|
136
139
|
description: "Show safe process resource and uptime information",
|
|
140
|
+
acknowledgement: "deferred-ephemeral",
|
|
137
141
|
rateLimit: { scope: "user", limit: 10, windowSeconds: 60 },
|
|
138
142
|
async execute(context) {
|
|
139
143
|
if (!(await authorizeDeveloperCommand(context)))
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { type ModuleManifest } from "@helyx/module-manifest";
|
|
1
2
|
import type { DiscordGuildHealthInspectionService, DiscordGuildHealthUnavailable, ModuleDefinition, ServiceAccess } from "@helyx/sdk";
|
|
2
3
|
import { type Client } from "discord.js";
|
|
3
4
|
export declare function moduleCatalogueMatches(packagedManifest: unknown, executableManifest: unknown): boolean;
|
|
4
5
|
export declare function missingRequiredPermissions(grantedPermissions: readonly string[], requiredPermissions: readonly string[]): string[];
|
|
5
6
|
export declare function hasRequiredModuleConfiguration(exposesConfiguration: boolean, savedConfiguration: unknown): boolean;
|
|
6
|
-
export declare function isHealthInspectableModule(
|
|
7
|
+
export declare function isHealthInspectableModule(module: ModuleManifest | string): boolean;
|
|
7
8
|
export declare class DiscordGuildHealthInspection implements DiscordGuildHealthInspectionService {
|
|
8
9
|
#private;
|
|
9
10
|
private readonly client;
|
|
@@ -162,8 +163,8 @@ export declare class DiscordGuildHealthInspection implements DiscordGuildHealthI
|
|
|
162
163
|
resourceValidationValid: boolean | null;
|
|
163
164
|
activationValid: boolean | null;
|
|
164
165
|
catalogueMatches: boolean;
|
|
165
|
-
requiredCapabilities: readonly ("
|
|
166
|
-
missingCapabilities: ("
|
|
166
|
+
requiredCapabilities: readonly ("guild-members" | "message-content")[];
|
|
167
|
+
missingCapabilities: ("guild-members" | "message-content")[];
|
|
167
168
|
missingPermissions: string[];
|
|
168
169
|
healthStatus: "healthy" | "degraded" | "unhealthy";
|
|
169
170
|
healthMessage: string | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasDirectModuleHealthExposure, } from "@helyx/module-manifest";
|
|
1
2
|
import { HELYX_SERVICE_NAMES } from "@helyx/sdk";
|
|
2
3
|
import { AuditLogEvent, GatewayIntentBits, } from "discord.js";
|
|
3
4
|
import { isDeepStrictEqual } from "node:util";
|
|
@@ -21,8 +22,10 @@ export function missingRequiredPermissions(grantedPermissions, requiredPermissio
|
|
|
21
22
|
export function hasRequiredModuleConfiguration(exposesConfiguration, savedConfiguration) {
|
|
22
23
|
return !exposesConfiguration || savedConfiguration !== null;
|
|
23
24
|
}
|
|
24
|
-
export function isHealthInspectableModule(
|
|
25
|
-
|
|
25
|
+
export function isHealthInspectableModule(module) {
|
|
26
|
+
const moduleId = typeof module === "string" ? module : module.id;
|
|
27
|
+
return (!INTERNAL_HELYX_MODULE_IDS.has(moduleId) &&
|
|
28
|
+
(typeof module === "string" || hasDirectModuleHealthExposure(module)));
|
|
26
29
|
}
|
|
27
30
|
export class DiscordGuildHealthInspection {
|
|
28
31
|
client;
|
|
@@ -270,7 +273,7 @@ export class DiscordGuildHealthInspection {
|
|
|
270
273
|
return overview;
|
|
271
274
|
const items = [];
|
|
272
275
|
const batchSize = Math.min(20, Math.max(1, input.batchSize ?? 20));
|
|
273
|
-
const inspectableModules = this.modules.filter((module) => isHealthInspectableModule(module.manifest
|
|
276
|
+
const inspectableModules = this.modules.filter((module) => isHealthInspectableModule(module.manifest));
|
|
274
277
|
for (let index = 0; index < inspectableModules.length; index += 1) {
|
|
275
278
|
const module = inspectableModules[index];
|
|
276
279
|
const state = await installations.getModuleState(input.guildId, module.manifest.id);
|