@helyx/bot 0.4.33 → 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.
@@ -1,4 +1,5 @@
1
1
  import { defineModule, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
2
+ import { isCustomerSelectableModule } from "@helyx/module-manifest";
2
3
  import { PermissionFlagsBits } from "discord.js";
3
4
  import { canMutatePermissionPolicy, permissionMutationDenialMessage, } from "@helyx/core";
4
5
  const MODULE_SELECT_ID = "helyx.permissions.module";
@@ -17,7 +18,9 @@ const MODULE_PAGE_SIZE = 25;
17
18
  const TARGET_PAGE_SIZE = 24;
18
19
  const VIOLET = 0x7c3aed;
19
20
  export function createPermissionsModule(configurableModules) {
20
- const modules = new Map(configurableModules.map((module) => [module.manifest.id, module]));
21
+ const modules = new Map(configurableModules
22
+ .filter((module) => isCustomerSelectableModule(module.manifest))
23
+ .map((module) => [module.manifest.id, module]));
21
24
  const commandModules = [...modules.values()]
22
25
  .filter((module) => commandTargets(module).length > 0)
23
26
  .sort((left, right) => left.manifest.name.localeCompare(right.manifest.name, "en-GB"));
@@ -46,6 +49,7 @@ export function createPermissionsModule(configurableModules) {
46
49
  {
47
50
  name: "permissions",
48
51
  description: "Configure access to Helyx modules and commands",
52
+ acknowledgement: "deferred-ephemeral",
49
53
  registration: "global",
50
54
  contexts: ["guild"],
51
55
  integrationTypes: ["guild_install"],
@@ -359,9 +363,17 @@ function restrictedPolicy() {
359
363
  };
360
364
  }
361
365
  function commandTargets(module) {
362
- return (module.interactions?.commands ?? []).flatMap((command) => command.subcommands?.length
363
- ? command.subcommands.map((subcommand) => `${command.name}.${subcommand.name}`)
364
- : [command.name]);
366
+ const targets = (module.interactions?.commands ?? []).flatMap((command) => {
367
+ const nested = [
368
+ ...(command.subcommands ?? []).map((subcommand) => `${command.name}.${subcommand.name}`),
369
+ ...(command.subcommandGroups ?? []).flatMap((group) => group.subcommands.map((subcommand) => `${command.name}.${group.name}.${subcommand.name}`)),
370
+ ];
371
+ if (nested.length > 0)
372
+ return nested;
373
+ return [command.name];
374
+ });
375
+ targets.push(...(module.nexusCommands ?? []).map((command) => `nexus.${command.path}`));
376
+ return [...new Set(targets)];
365
377
  }
366
378
  function modulePickerResponse(modules, requestedPage) {
367
379
  const pages = pageCount(modules.length, MODULE_PAGE_SIZE);
@@ -321,6 +321,13 @@ export function createLocalScheduledTaskRoute(input) {
321
321
  const module = modules.get(task.moduleId);
322
322
  if (!module)
323
323
  return deferred("module_unavailable", DEFAULT_POLL_INTERVAL_MS * 30);
324
+ if (typeof input.services.has === "function" &&
325
+ input.services.has(HELYX_SERVICE_NAMES.moduleAvailability)) {
326
+ const availability = input.services.get(HELYX_SERVICE_NAMES.moduleAvailability);
327
+ if (typeof availability?.isAvailable === "function" &&
328
+ !(await availability.isAvailable(task.moduleId)))
329
+ return deferred("module_globally_disabled", DEFAULT_POLL_INTERVAL_MS * 30);
330
+ }
324
331
  if (task.guildId) {
325
332
  const enabled = await input.services
326
333
  .get(HELYX_SERVICE_NAMES.installations)
@@ -11,11 +11,13 @@ export declare class ShardControlRouter implements BotControlHandler {
11
11
  localLeases: ReadonlyMap<number, ShardLease>;
12
12
  dispatchLocal: LocalDispatcher;
13
13
  privacy?: Pick<BotControlHandler, "listPrivacyProviders" | "capturePrivacy" | "erasePrivacy">;
14
+ availability?: Pick<BotControlHandler, "listAvailabilityModules">;
14
15
  localLeaderLease?: ShardLeaderLease;
15
16
  coordinator?: ShardCoordinator;
16
17
  notificationGuildId?: string;
17
18
  });
18
19
  cleanupOAuthConsumer(input: Parameters<BotControlHandler["cleanupOAuthConsumer"]>[0]): ReturnType<BotControlHandler["cleanupOAuthConsumer"]>;
20
+ listAvailabilityModules(): ReturnType<BotControlHandler["listAvailabilityModules"]>;
19
21
  getGuildState(guildId: string): ReturnType<BotControlHandler["getGuildState"]>;
20
22
  canAccessDashboard(input: Parameters<BotControlHandler["canAccessDashboard"]>[0]): ReturnType<BotControlHandler["canAccessDashboard"]>;
21
23
  getGuildAccessPolicy(input: Parameters<BotControlHandler["getGuildAccessPolicy"]>[0]): ReturnType<BotControlHandler["getGuildAccessPolicy"]>;
@@ -15,6 +15,11 @@ export class ShardControlRouter {
15
15
  cleanupOAuthConsumer(input) {
16
16
  return this.#route({ kind: "oauth.consumer.cleanup", ...input });
17
17
  }
18
+ listAvailabilityModules() {
19
+ if (!this.input.availability)
20
+ return Promise.reject(new BotControlTransportError(503, "Module availability catalogue is unavailable"));
21
+ return this.input.availability.listAvailabilityModules();
22
+ }
18
23
  getGuildState(guildId) {
19
24
  return this.#route({ kind: "guild.state.get", guildId });
20
25
  }
@@ -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";
@@ -229,6 +230,10 @@ export async function startShardSupervisor(environment, processEnvironment, work
229
230
  ...publicModules,
230
231
  ...(operationsModule ? [operationsModule] : []),
231
232
  ];
233
+ const availabilityModules = availabilityCatalogueDefinitions(modules);
234
+ const availabilityService = {
235
+ listAvailabilityModules: () => availabilityCatalogue(availabilityModules),
236
+ };
232
237
  const initialiseLeader = async () => {
233
238
  await runLeaderStartup(database, environment, modulePackages, modules, logger);
234
239
  assertStartupLeaseHealthy();
@@ -258,6 +263,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
258
263
  localLeases,
259
264
  dispatchLocal,
260
265
  privacy: privacyService,
266
+ availability: availabilityService,
261
267
  localLeaderLease: leaderLease,
262
268
  ...(coordinator ? { coordinator } : {}),
263
269
  ...(environment.DISCORD_INTERNAL_GUILD_ID
@@ -351,6 +357,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
351
357
  localLeases,
352
358
  dispatchLocal,
353
359
  ...(privacyService ? { privacy: privacyService } : {}),
360
+ availability: availabilityService,
354
361
  ...(leaderLease ? { localLeaderLease: leaderLease } : {}),
355
362
  ...(coordinator ? { coordinator } : {}),
356
363
  ...(environment.DISCORD_INTERNAL_GUILD_ID
@@ -9,7 +9,7 @@ export declare function createLeaseHeartbeatWatchdog(onExpired: () => void, time
9
9
  };
10
10
  export declare function startShardWorker(processEnvironment?: NodeJS.ProcessEnv, identity?: {
11
11
  version: 1;
12
- mode: "auto" | "fixed";
12
+ mode: "fixed" | "auto";
13
13
  scope: "local" | "coordinated";
14
14
  generation: string;
15
15
  supervisorId: string;
@@ -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: "off" | "auto" | "fixed";
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;
@@ -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) {
@@ -28,6 +28,7 @@ export function createSupportModule(transport) {
28
28
  {
29
29
  name: "bug",
30
30
  description: "Report a Helyx bug to the support team",
31
+ acknowledgement: "immediate-modal",
31
32
  registration: "global",
32
33
  contexts: ["guild"],
33
34
  integrationTypes: ["guild_install"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helyx/bot",
3
- "version": "0.4.33",
3
+ "version": "0.4.35",
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.6.0 <0.7.0",
57
- "@helyx/platform": ">=0.5.0 <0.6.0",
58
- "@helyx/sdk": ">=0.7.0 <0.8.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",