@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/cloud-management-module.js +7 -1
  3. package/dist/control-server.d.ts +6 -7
  4. package/dist/control-server.js +38 -45
  5. package/dist/dashboard-commands.d.ts +10 -0
  6. package/dist/dashboard-commands.js +47 -0
  7. package/dist/developer-operations-module.js +5 -1
  8. package/dist/discord-guild-health.d.ts +4 -3
  9. package/dist/discord-guild-health.js +6 -3
  10. package/dist/events.js +38 -16
  11. package/dist/index.js +43 -3
  12. package/dist/interactions.d.ts +3 -2
  13. package/dist/interactions.js +132 -45
  14. package/dist/internal-module-manifest.d.ts +13 -0
  15. package/dist/internal-module-manifest.js +14 -0
  16. package/dist/management-module.js +7 -2
  17. package/dist/module-availability-control.d.ts +24 -0
  18. package/dist/module-availability-control.js +61 -0
  19. package/dist/modules.js +24 -1
  20. package/dist/nexus-commands.d.ts +13 -0
  21. package/dist/nexus-commands.js +100 -0
  22. package/dist/numeric-barriers.d.ts +11 -0
  23. package/dist/numeric-barriers.js +14 -0
  24. package/dist/numeric-write-behind-proxy.d.ts +4 -4
  25. package/dist/numeric-write-behind-proxy.js +45 -24
  26. package/dist/numeric-write-behind-redis.d.ts +4 -4
  27. package/dist/numeric-write-behind-redis.js +132 -70
  28. package/dist/oauth-module.js +3 -1
  29. package/dist/permissions-module.js +18 -5
  30. package/dist/renewable-lease.d.ts +16 -0
  31. package/dist/renewable-lease.js +66 -0
  32. package/dist/scheduled-task-runner.js +15 -1
  33. package/dist/shard-router.d.ts +2 -0
  34. package/dist/shard-router.js +5 -0
  35. package/dist/shard-supervisor.d.ts +5 -0
  36. package/dist/shard-supervisor.js +63 -4
  37. package/dist/shard-worker.d.ts +7 -1
  38. package/dist/shard-worker.js +4 -1
  39. package/dist/support-module.js +3 -1
  40. 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
- manifestVersion: "0.1",
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)))
@@ -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 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>;
@@ -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;
@@ -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, emptyPolicy, fromDashboardPolicy, managedResourcePermissionId, permissionPoliciesEqual, permissionPolicyKey, safeOptionalVersion, safeVersion, toDashboardPolicy, } from "./control-permissions.js";
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
- const runBoundary = async (index) => {
663
- const scope = scopes[index];
664
- if (!scope) {
665
- await setEnabled();
666
- return;
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
- manifestVersion: "0.1",
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(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;
@@ -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 ("message-content" | "guild-members")[];
166
- missingCapabilities: ("message-content" | "guild-members")[];
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(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);