Add ACP-backed role sessions

This commit is contained in:
zenord
2026-08-16 10:08:03 +08:00
parent d59c4a654f
commit a80fe68d57
34 changed files with 1683 additions and 887 deletions
+46 -106
View File
@@ -1,145 +1,85 @@
#!/usr/bin/env node
import process from "node:process";
import { discoverAgents } from "./agents/discovery.js";
import { discoverBackends } from "./acp/discovery.js";
import { loadConfigFile } from "./cli/config-file.js";
import { runDoctor } from "./cli/doctor.js";
import { localBaseUrl } from "./cli/net.js";
import { printFeishu } from "./cli/print.js";
import { runSetup } from "./cli/setup.js";
import { localBaseUrl } from "./cli/net.js";
import { defaultStateFile } from "./config.js";
import { startServer } from "./server.js";
interface ParsedArgs {
command?: string;
rest: string[];
configPath?: string;
json: boolean;
help: boolean;
}
interface ParsedArgs { command?: string; rest: string[]; configPath?: string; json: boolean; help: boolean }
async function main(argv: string[]): Promise<number> {
const parsed = parseArgs(argv);
if (parsed.help || !parsed.command) {
printHelp();
return 0;
}
if (parsed.command === "setup") {
await runSetup(parsed.configPath);
return 0;
}
if (parsed.command === "discover-agents") {
const agents = await discoverAgents();
if (parsed.json) {
console.log(JSON.stringify(agents, null, 2));
} else {
for (const agent of agents) {
const version = agent.version ? ` (${agent.version})` : "";
const reason = agent.reason ? ` - ${agent.reason}` : "";
console.log(`${agent.name}\t${agent.status}\t${agent.command} ${agent.args.join(" ")}${version}${reason}`.trim());
}
if (parsed.help || !parsed.command) { printHelp(); return 0; }
if (parsed.command === "setup") { await runSetup(parsed.configPath); return 0; }
if (parsed.command === "discover-backends" || parsed.command === "discover-agents") {
const backends = await discoverBackends();
if (parsed.json) console.log(JSON.stringify(backends, null, 2));
else for (const backend of backends) {
const version = backend.version ? ` (${backend.version})` : "";
const reason = backend.reason ? ` - ${backend.reason}` : "";
console.log(`${backend.id}\t${backend.status}\t${backend.command} ${backend.args.join(" ")}${version}${reason}`.trim());
}
return 0;
}
if (parsed.command === "start") {
const loaded = loadConfigFile(parsed.configPath);
startServer(loaded.config);
return await new Promise(() => undefined);
const running = await startServer(loaded.config);
return await new Promise<number>((resolve) => {
let stopping = false;
const stop = (signal: string): void => {
if (stopping) return;
stopping = true;
console.log(`Received ${signal}; shutting down...`);
void running.shutdown().then(() => resolve(0), (error) => { console.error(error); resolve(1); });
};
process.once("SIGINT", () => stop("SIGINT"));
process.once("SIGTERM", () => stop("SIGTERM"));
});
}
if (parsed.command === "status") {
await printStatus(parsed.configPath);
return 0;
}
if (parsed.command === "doctor") {
const loaded = loadConfigFile(parsed.configPath);
return runDoctor(loaded.config, loaded.path);
}
if (parsed.command === "status") { await printStatus(parsed.configPath); return 0; }
if (parsed.command === "doctor") { const loaded = loadConfigFile(parsed.configPath); return runDoctor(loaded.config, loaded.path); }
if (parsed.command === "print") {
const topic = parsed.rest[0];
const loaded = loadConfigFile(parsed.configPath);
if (topic === "feishu") {
await printFeishu(loaded.config);
return 0;
}
console.error(`Unknown print topic: ${topic || "(missing)"}`);
console.error("Available: feishu");
return 1;
if (parsed.rest[0] === "feishu") { await printFeishu(loaded.config); return 0; }
console.error(`Unknown print topic: ${parsed.rest[0] || "(missing)"}`); return 1;
}
console.error(`Unknown command: ${parsed.command}`);
printHelp();
return 1;
console.error(`Unknown command: ${parsed.command}`); printHelp(); return 1;
}
function parseArgs(argv: string[]): ParsedArgs {
const rest: string[] = [];
let command: string | undefined;
let configPath: string | undefined;
let json = false;
let help = false;
const rest: string[] = []; let command: string | undefined; let configPath: string | undefined; let json = false; let help = false;
for (let index = 0; index < argv.length; index++) {
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);
}
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);
}
return { command, rest, configPath, json, help };
}
async function printStatus(configPath?: string): Promise<void> {
const loaded = loadConfigFile(configPath);
const baseUrl = localBaseUrl(loaded.config);
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 agent: ${loaded.config.defaultAgent}`);
console.log(`Agents: ${loaded.config.agents.map((agent) => agent.name).join(", ")}`);
console.log(`Enabled platforms: ${Object.entries(loaded.config.platforms).filter(([, value]) => value.enabled).map(([name]) => name).join(", ") || "none"}`);
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(`State file: ${defaultStateFile(loaded.config)}`);
try {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 1_000);
const response = await fetch(`${baseUrl}/health`, { signal: controller.signal });
clearTimeout(timer);
console.log(`Health probe: HTTP ${response.status}`);
} catch {
console.log("Health probe: not reachable on local URL");
}
const response = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(1_000) });
console.log(`Health probe: HTTP ${response.status} ${await response.text()}`);
} catch { console.log("Health probe: not reachable on local URL"); }
}
function printHelp(): void {
console.log(`gori-agent - local CLI-agent gateway setup and operations
Usage:
gori-agent setup [--config path]
gori-agent discover-agents [--json]
gori-agent start [--config path]
gori-agent status [--config path]
gori-agent doctor [--config path]
gori-agent print feishu [--config path]
gori-agent --help
No-link fallback:
npm run gori-agent -- setup`);
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`);
}
main(process.argv.slice(2)).then((code) => {
if (Number.isInteger(code)) process.exitCode = code;
}).catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
main(process.argv.slice(2)).then((code) => { if (Number.isInteger(code)) process.exitCode = code; }).catch((error) => {
console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1;
});