feat: lifecycle management - no-reply API + moderator bot follow gateway start/stop
- Added gateway_start hook: spawns no-reply API as child process, then starts moderator bot - Added gateway_stop hook: kills no-reply API process, stops moderator bot - No-reply API server.mjs is located relative to plugin dir via import.meta.url - Moderator presence moved from register() to gateway_start for proper lifecycle
This commit is contained in:
@@ -1,10 +1,50 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { spawn, type ChildProcess } from "node:child_process";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||||
import { evaluateDecision, resolvePolicy, type ChannelPolicy, type Decision, type DirigentConfig } from "./rules.js";
|
import { evaluateDecision, resolvePolicy, type ChannelPolicy, type Decision, type DirigentConfig } from "./rules.js";
|
||||||
import { checkTurn, advanceTurn, resetTurn, onNewMessage, onSpeakerDone, initTurnOrder, getTurnDebugInfo } from "./turn-manager.js";
|
import { checkTurn, advanceTurn, resetTurn, onNewMessage, onSpeakerDone, initTurnOrder, getTurnDebugInfo } from "./turn-manager.js";
|
||||||
import { startModeratorPresence, stopModeratorPresence } from "./moderator-presence.js";
|
import { startModeratorPresence, stopModeratorPresence } from "./moderator-presence.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 {
|
||||||
|
if (noReplyProcess) {
|
||||||
|
logger.info("dirigent: no-reply API already running, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverPath = path.resolve(pluginDir, "..", "no-reply-api", "server.mjs");
|
||||||
|
if (!fs.existsSync(serverPath)) {
|
||||||
|
logger.warn(`dirigent: no-reply API server not found at ${serverPath}, skipping`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
type DiscordControlAction = "channel-private-create" | "channel-private-update" | "member-list";
|
type DiscordControlAction = "channel-private-create" | "channel-private-update" | "member-list";
|
||||||
|
|
||||||
type DecisionRecord = {
|
type DecisionRecord = {
|
||||||
@@ -467,11 +507,25 @@ export default {
|
|||||||
const liveAtRegister = getLivePluginConfig(api, baseConfig as DirigentConfig);
|
const liveAtRegister = getLivePluginConfig(api, baseConfig as DirigentConfig);
|
||||||
ensurePolicyStateLoaded(api, liveAtRegister);
|
ensurePolicyStateLoaded(api, liveAtRegister);
|
||||||
|
|
||||||
// Start moderator bot presence (keep it "online" on Discord)
|
// Resolve plugin directory for locating sibling modules (no-reply-api/)
|
||||||
if (liveAtRegister.moderatorBotToken) {
|
const pluginDir = path.dirname(new URL(import.meta.url).pathname);
|
||||||
startModeratorPresence(liveAtRegister.moderatorBotToken, api.logger);
|
|
||||||
api.logger.info("dirigent: moderator bot presence starting");
|
// Gateway lifecycle: start/stop no-reply API and moderator bot with the gateway
|
||||||
}
|
api.on("gateway_start", () => {
|
||||||
|
startNoReplyApi(api.logger, pluginDir);
|
||||||
|
|
||||||
|
const live = getLivePluginConfig(api, baseConfig as DirigentConfig);
|
||||||
|
if (live.moderatorBotToken) {
|
||||||
|
startModeratorPresence(live.moderatorBotToken, api.logger);
|
||||||
|
api.logger.info("dirigent: moderator bot presence starting");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.on("gateway_stop", () => {
|
||||||
|
stopNoReplyApi(api.logger);
|
||||||
|
stopModeratorPresence();
|
||||||
|
api.logger.info("dirigent: gateway stopping, services shut down");
|
||||||
|
});
|
||||||
|
|
||||||
api.registerTool(
|
api.registerTool(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user