refactor: new design — sidecar services, moderator Gateway client, tool execute API
- Replace standalone no-reply-api Docker service with unified sidecar (services/main.mjs) that routes /no-reply/* and /moderator/* and starts/stops with openclaw-gateway - Add moderator Discord Gateway client (services/moderator/index.mjs) for real-time MESSAGE_CREATE push instead of polling; notifies plugin via HTTP callback - Add plugin HTTP routes (plugin/web/dirigent-api.ts) for moderator → plugin callbacks (wake-from-dormant, interrupt tail-match) - Fix tool registration format: AgentTool requires execute: not handler:; factory form for tools needing ctx - Rename no-reply-process.ts → sidecar-process.ts, startNoReplyApi → startSideCar - Remove dead config fields from openclaw.plugin.json (humanList, agentList, listMode, channelPoliciesFile, endSymbols, waitIdentifier, multiMessage*, bypassUserIds, etc.) - Rename noReplyPort → sideCarPort - Remove docker-compose.yml, dev-up/down scripts, package-plugin.mjs, test-no-reply-api.mjs - Update install.mjs: clean dist before build, copy services/, drop dead config writes - Update README, Makefile, smoke script for new architecture Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
111
services/main.mjs
Normal file
111
services/main.mjs
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Unified entry point for Dirigent services.
|
||||
*
|
||||
* Routes:
|
||||
* /no-reply/* → no-reply API (strips /no-reply prefix)
|
||||
* /moderator/* → moderator bot service (strips /moderator prefix)
|
||||
* otherwise → 404
|
||||
*
|
||||
* Env vars:
|
||||
* SERVICES_PORT (default 8787)
|
||||
* MODERATOR_TOKEN Discord bot token (required for moderator)
|
||||
* PLUGIN_API_URL (default http://127.0.0.1:18789)
|
||||
* PLUGIN_API_TOKEN auth token for plugin API calls
|
||||
* SCHEDULE_IDENTIFIER (default ➡️)
|
||||
* DEBUG_MODE (default false)
|
||||
*/
|
||||
|
||||
import http from "node:http";
|
||||
import { createNoReplyHandler } from "./no-reply-api/server.mjs";
|
||||
import { createModeratorService } from "./moderator/index.mjs";
|
||||
|
||||
const PORT = Number(process.env.SERVICES_PORT || 8787);
|
||||
const MODERATOR_TOKEN = process.env.MODERATOR_TOKEN || "";
|
||||
const PLUGIN_API_URL = process.env.PLUGIN_API_URL || "http://127.0.0.1:18789";
|
||||
const PLUGIN_API_TOKEN = process.env.PLUGIN_API_TOKEN || "";
|
||||
const SCHEDULE_IDENTIFIER = process.env.SCHEDULE_IDENTIFIER || "➡️";
|
||||
const DEBUG_MODE = process.env.DEBUG_MODE === "true" || process.env.DEBUG_MODE === "1";
|
||||
|
||||
function sendJson(res, status, payload) {
|
||||
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
||||
res.end(JSON.stringify(payload));
|
||||
}
|
||||
|
||||
// ── Initialize services ────────────────────────────────────────────────────────
|
||||
|
||||
const noReplyHandler = createNoReplyHandler();
|
||||
|
||||
let moderatorService = null;
|
||||
if (MODERATOR_TOKEN) {
|
||||
console.log("[dirigent-services] moderator bot enabled");
|
||||
moderatorService = createModeratorService({
|
||||
token: MODERATOR_TOKEN,
|
||||
pluginApiUrl: PLUGIN_API_URL,
|
||||
pluginApiToken: PLUGIN_API_TOKEN,
|
||||
scheduleIdentifier: SCHEDULE_IDENTIFIER,
|
||||
debugMode: DEBUG_MODE,
|
||||
});
|
||||
} else {
|
||||
console.log("[dirigent-services] MODERATOR_TOKEN not set — moderator disabled");
|
||||
}
|
||||
|
||||
// ── HTTP server ────────────────────────────────────────────────────────────────
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const url = req.url ?? "/";
|
||||
|
||||
if (url === "/health") {
|
||||
return sendJson(res, 200, {
|
||||
ok: true,
|
||||
services: {
|
||||
noReply: true,
|
||||
moderator: !!moderatorService,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (url.startsWith("/no-reply")) {
|
||||
req.url = url.slice("/no-reply".length) || "/";
|
||||
return noReplyHandler(req, res);
|
||||
}
|
||||
|
||||
if (url.startsWith("/moderator")) {
|
||||
if (!moderatorService) {
|
||||
return sendJson(res, 503, { error: "moderator service not configured" });
|
||||
}
|
||||
req.url = url.slice("/moderator".length) || "/";
|
||||
return moderatorService.httpHandler(req, res);
|
||||
}
|
||||
|
||||
return sendJson(res, 404, { error: "not_found" });
|
||||
});
|
||||
|
||||
server.listen(PORT, "127.0.0.1", () => {
|
||||
console.log(`[dirigent-services] listening on 127.0.0.1:${PORT}`);
|
||||
console.log(`[dirigent-services] /no-reply → no-reply API`);
|
||||
if (moderatorService) {
|
||||
console.log(`[dirigent-services] /moderator → moderator bot`);
|
||||
console.log(`[dirigent-services] plugin API: ${PLUGIN_API_URL}`);
|
||||
}
|
||||
if (DEBUG_MODE) {
|
||||
console.log(`[dirigent-services] debug mode ON`);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Graceful shutdown ──────────────────────────────────────────────────────────
|
||||
|
||||
function shutdown(signal) {
|
||||
console.log(`[dirigent-services] received ${signal}, shutting down`);
|
||||
if (moderatorService) {
|
||||
moderatorService.stop();
|
||||
}
|
||||
server.close(() => {
|
||||
console.log("[dirigent-services] server closed");
|
||||
process.exit(0);
|
||||
});
|
||||
// Force-exit after 5s
|
||||
setTimeout(() => process.exit(1), 5000).unref();
|
||||
}
|
||||
|
||||
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
||||
process.on("SIGINT", () => shutdown("SIGINT"));
|
||||
Reference in New Issue
Block a user