Refactor runtime around isolated bot instances

This commit is contained in:
zenord
2026-08-16 23:45:09 +08:00
parent 914579a94c
commit f9fb4ef776
51 changed files with 2355 additions and 1459 deletions
+49 -12
View File
@@ -1,8 +1,9 @@
#!/usr/bin/env node
import process from "node:process";
import { discoverBackends } from "./acp/discovery.js";
import { loadConfigFile } from "./cli/config-file.js";
import { assertOperationalConfig, loadConfigFile } from "./cli/config-file.js";
import { runDoctor } from "./cli/doctor.js";
import { runInstanceCommand } from "./cli/instance.js";
import { localBaseUrl } from "./cli/net.js";
import { printFeishu } from "./cli/print.js";
import { runSetup } from "./cli/setup.js";
@@ -14,6 +15,8 @@ interface ParsedArgs { command?: string; rest: string[]; configPath?: string; js
async function main(argv: string[]): Promise<number> {
const parsed = parseArgs(argv);
if (parsed.help || !parsed.command) { printHelp(); return 0; }
validateCommandArgs(parsed);
if (parsed.command === "instance") return runInstanceCommand(parsed.rest[0], parsed.rest.slice(1));
if (parsed.command === "setup") { await runSetup(parsed.configPath); return 0; }
if (parsed.command === "discover-backends" || parsed.command === "discover-agents") {
const backends = await discoverBackends();
@@ -27,6 +30,7 @@ async function main(argv: string[]): Promise<number> {
}
if (parsed.command === "start") {
const loaded = loadConfigFile(parsed.configPath);
assertOperationalConfig(loaded.config, loaded.path);
const running = await startServer(loaded.config);
return await new Promise<number>((resolve) => {
let stopping = false;
@@ -44,10 +48,10 @@ async function main(argv: string[]): Promise<number> {
if (parsed.command === "doctor") { const loaded = loadConfigFile(parsed.configPath); return runDoctor(loaded.config, loaded.path); }
if (parsed.command === "print") {
const loaded = loadConfigFile(parsed.configPath);
if (parsed.rest[0] === "feishu") { await printFeishu(loaded.config); return 0; }
console.error(`Unknown print topic: ${parsed.rest[0] || "(missing)"}`); return 1;
await printFeishu(loaded.config);
return 0;
}
console.error(`Unknown command: ${parsed.command}`); printHelp(); return 1;
throw new Error(`Unknown command: ${parsed.command}`);
}
function parseArgs(argv: string[]): ParsedArgs {
@@ -56,19 +60,52 @@ function parseArgs(argv: string[]): ParsedArgs {
const arg = argv[index];
if (arg === "--help" || arg === "-h") help = true;
else if (arg === "--json") json = true;
else if (arg === "--config") { configPath = argv[++index]; if (!configPath) throw new Error("--config requires a path"); }
else if (!command) command = arg; else rest.push(arg);
else if (arg === "--config") {
if (configPath !== undefined) throw new Error("--config may only be specified once");
const value = argv[++index];
if (!value || value.startsWith("-")) throw new Error("--config requires a path");
configPath = value;
} else if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
else if (!command) command = arg;
else rest.push(arg);
}
return { command, rest, configPath, json, help };
}
function validateCommandArgs(parsed: ParsedArgs): void {
const { command, rest, configPath, json } = parsed;
if (command === "instance") {
if (configPath || json) throw new Error("instance commands do not accept --config or --json");
return;
}
if (command === "setup") {
if (json || rest.length > 0) throw new Error("setup accepts only --config <path>");
return;
}
if (command === "discover-backends" || command === "discover-agents") {
if (configPath || rest.length > 0) throw new Error(`${command} accepts only --json`);
return;
}
if (["start", "status", "doctor"].includes(command || "")) {
if (json || rest.length > 0) throw new Error(`${command} accepts only --config <path>`);
return;
}
if (command === "print") {
if (json || rest.length !== 1 || rest[0] !== "feishu") throw new Error("print requires exactly the topic 'feishu'");
return;
}
throw new Error(`Unknown command: ${command}`);
}
async function printStatus(configPath?: string): Promise<void> {
const loaded = loadConfigFile(configPath); const baseUrl = localBaseUrl(loaded.config);
console.log(`Config: ${loaded.path}${loaded.exists ? "" : " (seeded from config.example.json)"}`);
console.log(`Server: ${loaded.config.server.host}:${loaded.config.server.port}`);
console.log(`Default role: ${loaded.config.defaultRole}`);
console.log(`Roles: ${loaded.config.roles.map(({ id }) => id).join(", ")}`);
console.log(`Backends: ${loaded.config.backends.map(({ id }) => id).join(", ")}`);
console.log(`Config: ${loaded.path}`);
console.log(`Config version: ${loaded.config.configVersion}`);
console.log(`Bot: ${loaded.config.bot.id}`);
console.log(`Agent: ${loaded.config.bot.agent.id}`);
console.log(`Platform: ${loaded.config.gateway.platform.type}`);
console.log(`Server: ${loaded.config.gateway.server.host}:${loaded.config.gateway.server.port}`);
console.log(`Workspace: ${loaded.config.bot.workspace}`);
console.log(`State file: ${defaultStateFile(loaded.config)}`);
try {
const response = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(1_000) });
@@ -77,7 +114,7 @@ async function printStatus(configPath?: string): Promise<void> {
}
function printHelp(): void {
console.log(`gori-agent - multi-IM ACP agent gateway\n\nUsage:\n gori-agent setup [--config path]\n gori-agent discover-backends [--json]\n gori-agent start [--config path]\n gori-agent status [--config path]\n gori-agent doctor [--config path]\n gori-agent print feishu [--config path]\n\nDeprecated alias: discover-agents`);
console.log(`gori-agent - single-Bot ACP gateway\n\nUsage:\n gori-agent instance init <bot-id>\n gori-agent instance start <bot-id>\n gori-agent instance stop <bot-id>\n gori-agent instance restart <bot-id>\n gori-agent instance status <bot-id>\n gori-agent instance logs <bot-id>\n gori-agent instance doctor <bot-id>\n gori-agent instance list\n\nDebug commands:\n gori-agent setup [--config path]\n gori-agent discover-backends [--json]\n gori-agent start --config path\n gori-agent status --config path\n gori-agent doctor --config path\n gori-agent print feishu --config path\n\nInstances root: \${GORI_AGENT_ROOT:-$HOME/.gori-agent}/instances`);
}
main(process.argv.slice(2)).then((code) => { if (Number.isInteger(code)) process.exitCode = code; }).catch((error) => {