From c5339abe1a9c6231ef165968db68651b16e35802 Mon Sep 17 00:00:00 2001 From: yiekheng Date: Sun, 10 May 2026 14:35:26 +0800 Subject: [PATCH] feat(bot): fan-out tuning env vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BOT_FIRE_CONCURRENCY (8) — pg-boss worker pool size, gates max accounts firing fan-outs in parallel. BOT_GROUP_CONCURRENCY (3) — per-account parallel group sends; parts within a group stay serial so chat order is preserved. BOT_MAX_SEND_PER_MINUTE (40) — per-account token-bucket rate. Defaults are tuned for an established WhatsApp account (~30-60 msg/min safe band). Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/bot/src/env.test.ts | 30 ++++++++++++++++++++++++++++++ apps/bot/src/env.ts | 9 +++++++++ 2 files changed, 39 insertions(+) diff --git a/apps/bot/src/env.test.ts b/apps/bot/src/env.test.ts index 96c3aca..cd39346 100644 --- a/apps/bot/src/env.test.ts +++ b/apps/bot/src/env.test.ts @@ -24,4 +24,34 @@ describe("parseEnv", () => { it("rejects malformed port", () => { expect(() => parseEnv({ ...valid, BOT_HEALTH_PORT: "notanumber" })).toThrow(); }); + + it("defaults BOT_FIRE_CONCURRENCY to 8 when unset", () => { + expect(parseEnv(valid).BOT_FIRE_CONCURRENCY).toBe(8); + }); + + it("defaults BOT_GROUP_CONCURRENCY to 3 when unset", () => { + expect(parseEnv(valid).BOT_GROUP_CONCURRENCY).toBe(3); + }); + + it("defaults BOT_MAX_SEND_PER_MINUTE to 40 when unset", () => { + expect(parseEnv(valid).BOT_MAX_SEND_PER_MINUTE).toBe(40); + }); + + it("parses overrides for the fan-out tuning vars as integers", () => { + const env = parseEnv({ + ...valid, + BOT_FIRE_CONCURRENCY: "16", + BOT_GROUP_CONCURRENCY: "5", + BOT_MAX_SEND_PER_MINUTE: "60", + }); + expect(env.BOT_FIRE_CONCURRENCY).toBe(16); + expect(env.BOT_GROUP_CONCURRENCY).toBe(5); + expect(env.BOT_MAX_SEND_PER_MINUTE).toBe(60); + }); + + it("rejects non-numeric values for the fan-out tuning vars", () => { + expect(() => parseEnv({ ...valid, BOT_FIRE_CONCURRENCY: "many" })).toThrow(); + expect(() => parseEnv({ ...valid, BOT_GROUP_CONCURRENCY: "-1" })).toThrow(); + expect(() => parseEnv({ ...valid, BOT_MAX_SEND_PER_MINUTE: "40.5" })).toThrow(); + }); }); diff --git a/apps/bot/src/env.ts b/apps/bot/src/env.ts index 917b2ce..db5de3f 100644 --- a/apps/bot/src/env.ts +++ b/apps/bot/src/env.ts @@ -9,6 +9,15 @@ const envSchema = z.object({ MEDIA_DIR: z.string().min(1), BOT_HEALTH_PORT: numberFromString, BOT_LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info"), + + // Reminder fan-out tuning. Defaults aim for an established WhatsApp + // account (~30-60 msg/min safe band). + // BOT_FIRE_CONCURRENCY — pg-boss worker pool: max accounts firing in parallel. + // BOT_GROUP_CONCURRENCY — per-account parallel group sends; parts within a group stay serial. + // BOT_MAX_SEND_PER_MINUTE — per-account token-bucket rate. + BOT_FIRE_CONCURRENCY: numberFromString.default("8"), + BOT_GROUP_CONCURRENCY: numberFromString.default("3"), + BOT_MAX_SEND_PER_MINUTE: numberFromString.default("40"), }); export type Env = z.infer;