From ea501ccef8ff744488248baf29593e0d1696cca3 Mon Sep 17 00:00:00 2001 From: orion Date: Sun, 8 Mar 2026 05:39:27 +0000 Subject: [PATCH] refactor(plugin): extract no-reply child process lifecycle module --- plugin/core/no-reply-process.ts | 51 +++++++++++++++++++++++++++++++++ plugin/index.ts | 47 +----------------------------- 2 files changed, 52 insertions(+), 46 deletions(-) create mode 100644 plugin/core/no-reply-process.ts diff --git a/plugin/core/no-reply-process.ts b/plugin/core/no-reply-process.ts new file mode 100644 index 0000000..cfd41ff --- /dev/null +++ b/plugin/core/no-reply-process.ts @@ -0,0 +1,51 @@ +import fs from "node:fs"; +import path from "node:path"; +import { spawn, type ChildProcess } from "node:child_process"; + +let noReplyProcess: ChildProcess | null = null; + +export function startNoReplyApi( + logger: { info: (m: string) => void; warn: (m: string) => void }, + pluginDir: string, + port = 8787, +): void { + logger.info(`dirigent: startNoReplyApi called, pluginDir=${pluginDir}`); + + if (noReplyProcess) { + logger.info("dirigent: no-reply API already running, skipping"); + return; + } + + const serverPath = path.resolve(pluginDir, "..", "no-reply-api", "server.mjs"); + logger.info(`dirigent: resolved serverPath=${serverPath}`); + + if (!fs.existsSync(serverPath)) { + logger.warn(`dirigent: no-reply API server not found at ${serverPath}, skipping`); + return; + } + + logger.info("dirigent: no-reply API server found, spawning process..."); + + noReplyProcess = spawn(process.execPath, [serverPath], { + env: { ...process.env, PORT: String(port) }, + stdio: ["ignore", "pipe", "pipe"], + detached: false, + }); + + noReplyProcess.stdout?.on("data", (d: Buffer) => logger.info(`dirigent: no-reply-api: ${d.toString().trim()}`)); + noReplyProcess.stderr?.on("data", (d: Buffer) => logger.warn(`dirigent: no-reply-api: ${d.toString().trim()}`)); + + noReplyProcess.on("exit", (code, signal) => { + logger.info(`dirigent: no-reply API exited (code=${code}, signal=${signal})`); + noReplyProcess = null; + }); + + logger.info(`dirigent: no-reply API started (pid=${noReplyProcess.pid}, port=${port})`); +} + +export function stopNoReplyApi(logger: { info: (m: string) => void }): void { + if (!noReplyProcess) return; + logger.info("dirigent: stopping no-reply API"); + noReplyProcess.kill("SIGTERM"); + noReplyProcess = null; +} diff --git a/plugin/index.ts b/plugin/index.ts index 75a6d1e..0e94d59 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -1,6 +1,5 @@ import fs from "node:fs"; import path from "node:path"; -import { spawn, type ChildProcess } from "node:child_process"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import type { Decision, DirigentConfig } from "./rules.js"; import { startModeratorPresence, stopModeratorPresence } from "./moderator-presence.js"; @@ -18,51 +17,7 @@ import { extractMentionedUserIds, getModeratorUserId } from "./core/mentions.js" import { ensureTurnOrder, recordChannelAccount } from "./core/turn-bootstrap.js"; import { debugCtxSummary, pickDefined, shouldDebugLog } from "./core/utils.js"; import { resolveDiscordUserId, sendModeratorMessage } from "./core/moderator-discord.js"; - -// ── No-Reply API child process lifecycle ────────────────────────────── -let noReplyProcess: ChildProcess | null = null; - -function startNoReplyApi(logger: { info: (m: string) => void; warn: (m: string) => void }, pluginDir: string, port = 8787): void { - logger.info(`dirigent: startNoReplyApi called, pluginDir=${pluginDir}`); - - if (noReplyProcess) { - logger.info("dirigent: no-reply API already running, skipping"); - return; - } - - const serverPath = path.resolve(pluginDir, "..", "no-reply-api", "server.mjs"); - logger.info(`dirigent: resolved serverPath=${serverPath}`); - - if (!fs.existsSync(serverPath)) { - logger.warn(`dirigent: no-reply API server not found at ${serverPath}, skipping`); - return; - } - - logger.info(`dirigent: no-reply API server found, spawning process...`); - - noReplyProcess = spawn(process.execPath, [serverPath], { - env: { ...process.env, PORT: String(port) }, - stdio: ["ignore", "pipe", "pipe"], - detached: false, - }); - - noReplyProcess.stdout?.on("data", (d: Buffer) => logger.info(`dirigent: no-reply-api: ${d.toString().trim()}`)); - noReplyProcess.stderr?.on("data", (d: Buffer) => logger.warn(`dirigent: no-reply-api: ${d.toString().trim()}`)); - - noReplyProcess.on("exit", (code, signal) => { - logger.info(`dirigent: no-reply API exited (code=${code}, signal=${signal})`); - noReplyProcess = null; - }); - - logger.info(`dirigent: no-reply API started (pid=${noReplyProcess.pid}, port=${port})`); -} - -function stopNoReplyApi(logger: { info: (m: string) => void }): void { - if (!noReplyProcess) return; - logger.info("dirigent: stopping no-reply API"); - noReplyProcess.kill("SIGTERM"); - noReplyProcess = null; -} +import { startNoReplyApi, stopNoReplyApi } from "./core/no-reply-process.js"; type DecisionRecord = { decision: Decision;