@helyx/bot 0.4.32 → 0.4.35

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 CHANGED
@@ -1,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.35
4
+
5
+ ### Patch Changes
6
+
7
+ - 26b4c3e: Add versioned hidden module dependencies, automatic installation lifecycle,
8
+ delegated customer health, and transitive self-host build composition for the
9
+ Nexus foundation while preserving legacy manifest and catalogue behavior. Add
10
+ closed, owner-aware composition for the shared `/nexus` command namespace and
11
+ bounded Core reads for terminology, member history and confirmed member value
12
+ changes. Add resumable, idempotent role and server bulk-award jobs.
13
+ Add bounded Nexus privacy capture, a durable hashed erasure fence and atomic
14
+ member detachment that journals any remaining balance as a privacy burn before
15
+ removing account and bulk-recipient identifiers.
16
+ Generalise the durable Achievement contribution bus as member contributions
17
+ while retaining the existing service names as compatibility aliases.
18
+ Add the private Nexus Rewards Testing package, complete initial record schema
19
+ and an atomic timezone-aware Daily Claim path backed by the shared ledger.
20
+ Add reviewed, default-off Nexus eligibility metadata to existing contribution
21
+ catalogues and atomically apply configured fixed rewards with source, role,
22
+ rate-cap, replay, balance-cap and privacy safeguards.
23
+ Add definition-owned Achievement reward amounts and live repeatable-award
24
+ high-water processing so retries credit only newly committed occurrences.
25
+ Add the private Nexus Economy Testing package and its complete initial Exchange,
26
+ item, redemption, inventory, role-grant and durable fulfilment record schema.
27
+ Add a typed atomic Economy redemption path with exact replay, finite stock,
28
+ member limits, role eligibility, collectible inventory and durable fulfilment.
29
+ Add coalesced leased role fulfilment with reviewed Discord safety, observed
30
+ delivery reconciliation, bounded retries and Core-linked automatic refunds.
31
+ - Updated dependencies [26b4c3e]
32
+ - @helyx/module-manifest@0.7.0
33
+ - @helyx/platform@0.6.0
34
+ - @helyx/sdk@0.8.0
35
+
36
+ ## 0.4.34
37
+
38
+ ### Patch Changes
39
+
40
+ - Add fail-open production status heartbeat configuration and reporting support.
41
+
42
+ ## 0.4.33
43
+
44
+ ### Patch Changes
45
+
46
+ - Add versioned OAuth consumer cleanup contracts, durable and resumable
47
+ revocation state, least-privilege provider scopes, and Creator Connections
48
+ cleanup dispatch. Promote the hosted-only Creator Connections module to live
49
+ availability with clear OAuth activation validation. Ignore shard responses
50
+ that finish after shutdown has already detached their worker.
51
+
3
52
  ## 0.4.32
4
53
 
5
54
  ### Patch Changes
@@ -106,6 +106,7 @@ export function createCloudManagementModule(input) {
106
106
  {
107
107
  name: "connect",
108
108
  description: "Connect this deployment using a one-use code",
109
+ acknowledgement: "immediate-modal",
109
110
  rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
110
111
  async execute(context) {
111
112
  if (!(await requireOwner(context)))
@@ -136,6 +137,7 @@ export function createCloudManagementModule(input) {
136
137
  {
137
138
  name: "status",
138
139
  description: "Show this deployment's Cloud Connect status",
140
+ acknowledgement: "deferred-ephemeral",
139
141
  rateLimit: { scope: "user", limit: 20, windowSeconds: 60 },
140
142
  async execute(context) {
141
143
  if (!(await requireOwner(context)))
@@ -146,6 +148,7 @@ export function createCloudManagementModule(input) {
146
148
  {
147
149
  name: "disconnect",
148
150
  description: "Revoke this deployment's Cloud Connect identity",
151
+ acknowledgement: "immediate-modal",
149
152
  rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
150
153
  async execute(context) {
151
154
  if (!(await requireOwner(context)))
@@ -185,6 +188,7 @@ export function createCloudManagementModule(input) {
185
188
  {
186
189
  name: "rotate",
187
190
  description: "Replace the saved Cloud Connect credential",
191
+ acknowledgement: "immediate-modal",
188
192
  rateLimit: { scope: "user", limit: 3, windowSeconds: 60 },
189
193
  async execute(context) {
190
194
  if (!(await requireOwner(context)))
@@ -224,6 +228,7 @@ export function createCloudManagementModule(input) {
224
228
  {
225
229
  name: "support",
226
230
  description: "Choose what Helyx support may inspect",
231
+ acknowledgement: "immediate-modal",
227
232
  rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
228
233
  async execute(context) {
229
234
  if (!(await requireOwner(context)))
@@ -1,16 +1,12 @@
1
1
  import { type DatabasePool, type GuildShardOwnership } from "@helyx/database";
2
- import { type DashboardActionResult, type GuildAccessPolicy, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type PublicAchievementProjectionPage, type ServiceAccess } from "@helyx/sdk";
2
+ import { type DashboardActionResult, type GuildAccessPolicy, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type OAuthConsumerCleanupResult, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type PublicAchievementProjectionPage, type ServiceAccess } from "@helyx/sdk";
3
3
  import { type ActionReceipt, type SignedAssessmentEnvelope } from "@helyx/verification-contracts";
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 DashboardPermissionPolicy, type DashboardPermissionState } from "./control-permissions.js";
8
- interface DashboardCommandDefinition {
9
- id: string;
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>;
@@ -143,7 +140,19 @@ export interface DashboardGuildAccessState extends GuildAccessPolicy {
143
140
  updatedAt: string | null;
144
141
  }
145
142
  export interface BotControlHandler {
143
+ cleanupOAuthConsumer(input: {
144
+ consumerBindingId: string;
145
+ moduleId: string;
146
+ provider: "youtube" | "twitch";
147
+ guildId: string;
148
+ resourceType: string;
149
+ resourceId: string;
150
+ subjectUserId: string;
151
+ contractVersion: number;
152
+ correlationId: string;
153
+ }): Promise<OAuthConsumerCleanupResult>;
146
154
  sendSupportNotification(input: SupportNotificationInput): Promise<void>;
155
+ listAvailabilityModules(): Promise<AvailabilityModuleCatalogue>;
147
156
  listGuildPresence(input: {
148
157
  guildIds: readonly string[];
149
158
  correlationId: string;
@@ -339,6 +348,17 @@ export declare class BotControlService implements BotControlHandler {
339
348
  guildId: string;
340
349
  channelId: string;
341
350
  } | undefined, presenceOwnership?: GuildShardOwnership | undefined, presenceShardCount?: number | undefined);
351
+ cleanupOAuthConsumer(input: {
352
+ consumerBindingId: string;
353
+ moduleId: string;
354
+ provider: "youtube" | "twitch";
355
+ guildId: string;
356
+ resourceType: string;
357
+ resourceId: string;
358
+ subjectUserId: string;
359
+ contractVersion: number;
360
+ correlationId: string;
361
+ }): Promise<OAuthConsumerCleanupResult>;
342
362
  listGuildPresence(input: {
343
363
  guildIds: readonly string[];
344
364
  correlationId: string;
@@ -519,6 +539,7 @@ export declare class BotControlService implements BotControlHandler {
519
539
  correlationId: string;
520
540
  actorAuthenticatedAt?: string;
521
541
  }): Promise<ManagedResourceDetail | null>;
542
+ listAvailabilityModules(): Promise<AvailabilityModuleCatalogue>;
522
543
  }
523
544
  export declare function createBotControlServer(input: {
524
545
  service: BotControlHandler;
@@ -9,7 +9,9 @@ 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
10
  import { SHARD_HEALTH_STALE_MS, } from "./shard-contracts.js";
11
11
  import { dashboardCategoryLabel } from "./dashboard-category.js";
12
- import { assertVersion, dashboardActionPermissionId, declaredManagedResourcePermission, emptyPolicy, fromDashboardPolicy, managedResourcePermissionId, permissionPoliciesEqual, permissionPolicyKey, safeOptionalVersion, safeVersion, toDashboardPolicy, } from "./control-permissions.js";
12
+ import { assertVersion, dashboardActionPermissionId, declaredManagedResourcePermission, fromDashboardPolicy, managedResourcePermissionId, permissionPoliciesEqual, permissionPolicyKey, safeOptionalVersion, safeVersion, toDashboardPolicy, } from "./control-permissions.js";
13
+ import { dashboardCommands, } from "./dashboard-commands.js";
14
+ import { availabilityCatalogueDefinitions, availabilityCatalogue, dashboardModuleStatus, disabledModuleSummary, moduleAvailabilityService, moduleAvailabilityStates, } from "./module-availability-control.js";
13
15
  const snowflake = z.string().regex(/^\d{17,20}$/u);
14
16
  const moduleId = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/u);
15
17
  const MAX_DISCORD_SETTING_SELECTIONS = 25;
@@ -258,6 +260,7 @@ export class BotControlService {
258
260
  presenceShardCount;
259
261
  #modules;
260
262
  #dashboardModules;
263
+ #availabilityModules;
261
264
  #configurations;
262
265
  #installations;
263
266
  #permissions;
@@ -273,6 +276,7 @@ export class BotControlService {
273
276
  this.#dashboardModules = modules
274
277
  .filter((module) => module.manifest.dashboard !== undefined)
275
278
  .map(mapDashboardModule);
279
+ this.#availabilityModules = availabilityCatalogueDefinitions(modules);
276
280
  this.#configurations = services.get(HELYX_SERVICE_NAMES.configuration);
277
281
  this.#installations = services.get(HELYX_SERVICE_NAMES.installations);
278
282
  this.#permissions = services.get(HELYX_SERVICE_NAMES.permissions);
@@ -286,6 +290,26 @@ export class BotControlService {
286
290
  })
287
291
  : []);
288
292
  }
293
+ async cleanupOAuthConsumer(input) {
294
+ const module = this.#modules.get(input.moduleId);
295
+ if (!module)
296
+ throw new BotControlNotFoundError("OAuth consumer module is unavailable");
297
+ const consumer = module.oauthConsumers?.find((candidate) => candidate.provider === input.provider &&
298
+ candidate.resourceType === input.resourceType &&
299
+ candidate.version === input.contractVersion);
300
+ if (!consumer)
301
+ throw new BotControlNotFoundError("OAuth consumer cleanup contract is unavailable");
302
+ return consumer.cleanup({
303
+ consumerBindingId: input.consumerBindingId,
304
+ provider: input.provider,
305
+ guildId: input.guildId,
306
+ resourceType: input.resourceType,
307
+ resourceId: input.resourceId,
308
+ subjectUserId: input.subjectUserId,
309
+ correlationId: input.correlationId,
310
+ services: this.#services,
311
+ });
312
+ }
289
313
  async listGuildPresence(input) {
290
314
  const guildIds = [...new Set(input.guildIds)];
291
315
  if (guildIds.length > 200)
@@ -403,14 +427,16 @@ export class BotControlService {
403
427
  commandId: id,
404
428
  })),
405
429
  ]);
406
- const [resources, installedModules, storedLoggingPolicies, runtime, storedConfigurations, storedPermissions,] = await Promise.all([
430
+ const [resources, installedModules, storedLoggingPolicies, runtime, storedConfigurations, storedPermissions, availabilityStates,] = await Promise.all([
407
431
  this.resources.getGuildResources(guildId),
408
432
  new InstallationRepository(this.database).listModules(guildId),
409
433
  new ModuleLoggingPolicyRepository(this.database).listForGuild(guildId),
410
434
  this.#inspectRuntimeCapabilities(),
411
435
  this.#readConfigurations(guildId, moduleIds),
412
436
  this.#readPermissionPolicies(guildId, permissionTargets),
437
+ moduleAvailabilityStates(this.#services, moduleIds),
413
438
  ]);
439
+ const disabledModules = disabledModuleSummary(availabilityStates);
414
440
  const installed = new Map(installedModules.map((module) => [module.moduleId, module]));
415
441
  const moduleStatuses = {};
416
442
  const configurations = {};
@@ -422,11 +448,7 @@ export class BotControlService {
422
448
  ]));
423
449
  for (const { dashboardModule, module } of dashboardEntries) {
424
450
  const installation = installed.get(dashboardModule.id);
425
- moduleStatuses[dashboardModule.id] = installation
426
- ? installation.enabled
427
- ? "enabled"
428
- : "disabled"
429
- : "unavailable";
451
+ moduleStatuses[dashboardModule.id] = dashboardModuleStatus(disabledModules.set, dashboardModule.id, installation);
430
452
  const configuration = storedConfigurations.get(dashboardModule.id);
431
453
  if (configuration)
432
454
  configurations[dashboardModule.id] = {
@@ -450,6 +472,7 @@ export class BotControlService {
450
472
  ...resources,
451
473
  modules: structuredClone(this.#dashboardModules),
452
474
  moduleStatuses,
475
+ globallyDisabledModuleIds: disabledModules.ids,
453
476
  configurations,
454
477
  permissions,
455
478
  logging,
@@ -604,6 +627,7 @@ export class BotControlService {
604
627
  }
605
628
  async setModuleStatus(input) {
606
629
  const module = this.#requireDashboardModule(input.moduleId);
630
+ await this.#requireModuleAvailable(input.moduleId);
607
631
  const scopes = Object.entries(module.records ?? {})
608
632
  .filter(([, collection]) => (collection.incrementableFields?.length ?? 0) > 0)
609
633
  .map(([collection]) => ({
@@ -657,6 +681,7 @@ export class BotControlService {
657
681
  }
658
682
  async updateConfiguration(input) {
659
683
  const module = this.#requireDashboardModule(input.moduleId);
684
+ await this.#requireModuleAvailable(input.moduleId);
660
685
  await this.#validateDiscordSettings(input.guildId, module, input.value);
661
686
  if (module.configuration?.validateUpdate) {
662
687
  try {
@@ -690,6 +715,7 @@ export class BotControlService {
690
715
  }
691
716
  async updatePermissions(input) {
692
717
  const module = this.#requireDashboardModule(input.moduleId);
718
+ await this.#requireModuleAvailable(input.moduleId);
693
719
  const commands = dashboardCommands(module);
694
720
  const supplied = new Map(input.state.commands.map((command) => [command.commandId, command]));
695
721
  if (supplied.size !== input.state.commands.length ||
@@ -813,6 +839,10 @@ export class BotControlService {
813
839
  return this.#getPermissionState(input.guildId, module);
814
840
  }
815
841
  async updateLoggingPolicy(input) {
842
+ await Promise.all([
843
+ this.#requireModuleAvailable(input.moduleId),
844
+ this.#requireModuleAvailable("helyx.logging"),
845
+ ]);
816
846
  if (!this.#modules.has("helyx.logging")) {
817
847
  throw new BotControlNotFoundError("Logging is not installed");
818
848
  }
@@ -857,6 +887,7 @@ export class BotControlService {
857
887
  }
858
888
  async executeDashboardAction(input) {
859
889
  const module = this.#requireDashboardModule(input.moduleId);
890
+ await this.#requireModuleAvailable(input.moduleId);
860
891
  const declared = module.manifest.dashboard?.actions?.find(({ id }) => id === input.actionId);
861
892
  const action = module.dashboardActions?.find(({ id }) => id === input.actionId);
862
893
  if (!declared || !action) {
@@ -915,11 +946,13 @@ export class BotControlService {
915
946
  })), relation.columns.map(({ key }) => key));
916
947
  }
917
948
  async previewManagedResource(input) {
949
+ await this.#requireModuleAvailable(input.moduleId);
918
950
  const { module, resource } = this.#prepareManagedResource(input);
919
951
  this.#requireManagedResourceOperation(module, input.resourceType, "preview");
920
952
  return resource.preview(await this.#managedResourceContext(input, module, "preview"), input.value);
921
953
  }
922
954
  async createManagedResource(input) {
955
+ await this.#requireModuleAvailable(input.moduleId);
923
956
  const { module, resource } = this.#prepareManagedResource(input);
924
957
  this.#requireManagedResourceOperation(module, input.resourceType, "create");
925
958
  const context = await this.#managedResourceContext(input, module, "create");
@@ -929,6 +962,7 @@ export class BotControlService {
929
962
  return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.create(context, input.value, input.intent)), managedResourceColumnKeys(module, input.resourceType));
930
963
  }
931
964
  async updateManagedResource(input) {
965
+ await this.#requireModuleAvailable(input.moduleId);
932
966
  const { module, resource } = this.#prepareManagedResource(input);
933
967
  this.#requireManagedResourceOperation(module, input.resourceType, "update");
934
968
  return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.update(await this.#managedResourceContext(input, module, "update"), {
@@ -938,6 +972,7 @@ export class BotControlService {
938
972
  })), managedResourceColumnKeys(module, input.resourceType));
939
973
  }
940
974
  async deleteManagedResource(input) {
975
+ await this.#requireModuleAvailable(input.moduleId);
941
976
  const { module, resource } = this.#prepareManagedResource(input);
942
977
  this.#requireManagedResourceOperation(module, input.resourceType, "delete");
943
978
  return managedResourceDeleteResultSchema.parse(await resource.delete(await this.#managedResourceContext(input, module, "delete"), {
@@ -946,6 +981,7 @@ export class BotControlService {
946
981
  }));
947
982
  }
948
983
  async executeManagedResourceAction(input) {
984
+ await this.#requireModuleAvailable(input.moduleId);
949
985
  const { module, resource } = this.#prepareManagedResource(input);
950
986
  const declared = module.manifest.dashboard?.resources
951
987
  ?.find(({ id }) => id === input.resourceType)
@@ -1041,6 +1077,14 @@ export class BotControlService {
1041
1077
  throw new BotControlNotFoundError("Module is not installed");
1042
1078
  return module;
1043
1079
  }
1080
+ listAvailabilityModules() {
1081
+ return availabilityCatalogue(this.#availabilityModules);
1082
+ }
1083
+ async #requireModuleAvailable(moduleIdValue) {
1084
+ const availability = moduleAvailabilityService(this.#services);
1085
+ if (availability && !(await availability.isAvailable(moduleIdValue)))
1086
+ throw new BotControlValidationError("This module is temporarily unavailable for all hosted servers.");
1087
+ }
1044
1088
  #requireDashboardModule(moduleIdValue) {
1045
1089
  const module = this.#requireModule(moduleIdValue);
1046
1090
  if (!module.manifest.dashboard)
@@ -1213,6 +1257,23 @@ export async function createBotControlServer(input) {
1213
1257
  server.get("/health", (_request, reply) => readiness(reply));
1214
1258
  server.get("/v1/health", (_request, reply) => readiness(reply));
1215
1259
  server.post("/v1/verification/results", async (request) => input.service.acceptVerificationResult(signedAssessmentEnvelopeSchema.parse(request.body)));
1260
+ server.post("/v1/oauth/consumers/cleanup", async (request) => {
1261
+ const body = z
1262
+ .object({
1263
+ consumerBindingId: z.uuid(),
1264
+ moduleId,
1265
+ provider: z.enum(["youtube", "twitch"]),
1266
+ guildId: snowflake,
1267
+ resourceType: z.string().regex(/^[a-z][a-z0-9_-]*$/u),
1268
+ resourceId,
1269
+ subjectUserId: snowflake,
1270
+ contractVersion: z.number().int().min(1).max(32_767),
1271
+ correlationId: z.string().trim().min(1).max(200),
1272
+ })
1273
+ .strict()
1274
+ .parse(request.body);
1275
+ return input.service.cleanupOAuthConsumer(body);
1276
+ });
1216
1277
  server.get("/v1/shards/health", async (request, reply) => {
1217
1278
  reply.header("Cache-Control", "no-store");
1218
1279
  if (!input.shardHealth)
@@ -1331,6 +1392,7 @@ export async function createBotControlServer(input) {
1331
1392
  const { guildId } = z.object({ guildId: snowflake }).parse(request.params);
1332
1393
  return input.service.getGuildState(guildId);
1333
1394
  });
1395
+ server.get("/v1/modules/availability-catalogue", () => input.service.listAvailabilityModules());
1334
1396
  server.post("/v1/guilds/:guildId/dashboard-access", async (request) => {
1335
1397
  const { guildId } = z.object({ guildId: snowflake }).parse(request.params);
1336
1398
  const { actorUserId } = z
@@ -1860,35 +1922,6 @@ function mapDashboardModule(module) {
1860
1922
  : undefined,
1861
1923
  };
1862
1924
  }
1863
- function dashboardCommands(module) {
1864
- const commands = (module.interactions?.commands ?? []).flatMap((command) => command.subcommands?.length
1865
- ? command.subcommands.map((subcommand) => {
1866
- const id = subcommand.permissionId ??
1867
- command.permissionId ??
1868
- `${command.name}.${subcommand.name}`;
1869
- return {
1870
- id,
1871
- name: `/${command.name} ${subcommand.name}`,
1872
- description: subcommand.description,
1873
- defaultPreset: toDashboardPolicy(module.defaultPermissions?.commands[id] ??
1874
- module.defaultPermissions?.module ??
1875
- emptyPolicy()).preset,
1876
- };
1877
- })
1878
- : [
1879
- {
1880
- id: command.permissionId ?? command.name,
1881
- name: `/${command.name}`,
1882
- description: command.description,
1883
- defaultPreset: toDashboardPolicy(module.defaultPermissions?.commands[command.name] ??
1884
- module.defaultPermissions?.module ??
1885
- emptyPolicy()).preset,
1886
- },
1887
- ]);
1888
- return [
1889
- ...new Map(commands.map((command) => [command.id, command])).values(),
1890
- ];
1891
- }
1892
1925
  function assertPermissionMutationAllowed(decision) {
1893
1926
  if (decision.allowed)
1894
1927
  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
@@ -33,6 +33,7 @@ export function createDeveloperOperationsModule(input) {
33
33
  {
34
34
  name: "shards",
35
35
  description: "Show bounded read-only shard health",
36
+ acknowledgement: "deferred-ephemeral",
36
37
  rateLimit: { scope: "user", limit: 10, windowSeconds: 60 },
37
38
  async execute(context) {
38
39
  if (!(await authorizeDeveloperCommand(context)))
@@ -81,6 +82,7 @@ export function createDeveloperOperationsModule(input) {
81
82
  {
82
83
  name: "bot",
83
84
  description: "Show the bot build and installed module versions",
85
+ acknowledgement: "deferred-ephemeral",
84
86
  rateLimit: { scope: "user", limit: 10, windowSeconds: 60 },
85
87
  async execute(context) {
86
88
  if (!(await authorizeDeveloperCommand(context)))
@@ -134,6 +136,7 @@ export function createDeveloperOperationsModule(input) {
134
136
  {
135
137
  name: "proc",
136
138
  description: "Show safe process resource and uptime information",
139
+ acknowledgement: "deferred-ephemeral",
137
140
  rateLimit: { scope: "user", limit: 10, windowSeconds: 60 },
138
141
  async execute(context) {
139
142
  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(moduleId: string): boolean;
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;
@@ -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(moduleId) {
25
- return !INTERNAL_HELYX_MODULE_IDS.has(moduleId);
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.id));
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);