diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index 11da41275..898ed1ca7 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -13,6 +13,7 @@ import { readConfigFileSnapshot, writeConfigFile, } from "../config/config.js"; +import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js"; import { clearAgentRunContext, onAgentEvent } from "../infra/agent-events.js"; import { onHeartbeatEvent } from "../infra/heartbeat-events.js"; import { startHeartbeatRunner } from "../infra/heartbeat-runner.js"; @@ -182,6 +183,20 @@ export async function startGatewayServer( ); } + const autoEnable = applyPluginAutoEnable({ config: configSnapshot.config, env: process.env }); + if (autoEnable.changes.length > 0) { + try { + await writeConfigFile(autoEnable.config); + log.info( + `gateway: auto-enabled plugins:\n${autoEnable.changes + .map((entry) => `- ${entry}`) + .join("\n")}`, + ); + } catch (err) { + log.warn(`gateway: failed to persist plugin auto-enable changes: ${String(err)}`); + } + } + const cfgAtStart = loadConfig(); setGatewaySigusr1RestartPolicy({ allowExternal: cfgAtStart.commands?.restart === true }); initSubagentRegistry(); diff --git a/src/gateway/server.misc.test.ts b/src/gateway/server.misc.test.ts index 0e705f8b5..c0be52135 100644 --- a/src/gateway/server.misc.test.ts +++ b/src/gateway/server.misc.test.ts @@ -1,4 +1,6 @@ +import fs from "node:fs/promises"; import { createServer } from "node:net"; +import path from "node:path"; import { describe, expect, test } from "vitest"; import { resolveCanvasHostUrl } from "../infra/canvas-host-url.js"; import { getChannelPlugin } from "../channels/plugins/index.js"; @@ -129,6 +131,40 @@ describe("gateway server misc", () => { } }); + test("auto-enables configured channel plugins on startup", async () => { + const configPath = process.env.CLAWDBOT_CONFIG_PATH; + if (!configPath) throw new Error("Missing CLAWDBOT_CONFIG_PATH"); + await fs.mkdir(path.dirname(configPath), { recursive: true }); + await fs.writeFile( + configPath, + JSON.stringify( + { + channels: { + discord: { + token: "token-123", + }, + }, + }, + null, + 2, + ), + "utf-8", + ); + + const port = await getFreePort(); + const server = await startGatewayServer(port); + await server.close(); + + const updated = JSON.parse(await fs.readFile(configPath, "utf-8")) as Record; + const plugins = updated.plugins as Record | undefined; + const entries = plugins?.entries as Record | undefined; + const discord = entries?.discord as Record | undefined; + expect(discord?.enabled).toBe(true); + expect((updated.channels as Record | undefined)?.discord).toMatchObject({ + token: "token-123", + }); + }); + test("refuses to start when port already bound", async () => { const { server: blocker, port } = await occupyPort(); await expect(startGatewayServer(port)).rejects.toBeInstanceOf(GatewayLockError);