- 零第三方依赖,Node >=20 原生 ESM - 团队式编排引擎:拆解/路由/执行/审查/合并全真实 LLM - 阶段心跳、单一权威清单守卫、all-keys-failed 如实上报 - H4 会话视图/amend/watchdog 有界重试/产物区 artifacts.json - H5 零依赖三栏控制台
128 lines
5.2 KiB
JavaScript
128 lines
5.2 KiB
JavaScript
/** 独立节点进程入口(供 cli-live 与 npm scripts 复用)。 */
|
||
import { GatewayNode } from "./node-runtime.js";
|
||
import { nativeExecute, makeAdapterExecute } from "./nodes/executors.js";
|
||
|
||
function arg(name, def) {
|
||
const i = process.argv.indexOf(name);
|
||
return i >= 0 ? process.argv[i + 1] : def;
|
||
}
|
||
/** 可重复出现的参数(如 --env K=V --env A=B)。 */
|
||
function argAll(name) {
|
||
const out = [];
|
||
for (let i = 0; i < process.argv.length; i++) if (process.argv[i] === name) out.push(process.argv[i + 1]);
|
||
return out.filter(Boolean);
|
||
}
|
||
function parseEnvs(list) {
|
||
const o = {};
|
||
for (const kv of list || []) {
|
||
const i = kv.indexOf("=");
|
||
if (i > 0) o[kv.slice(0, i).trim()] = kv.slice(i + 1).trim();
|
||
}
|
||
return o;
|
||
}
|
||
const base = () => ({
|
||
gateway: arg("--gateway", process.env.GATEWAY_URL || "http://127.0.0.1:4180"),
|
||
token: arg("--token", process.env.GW_NODE_TOKEN || "dev-token-change-me"),
|
||
name: arg("--name", "") || undefined,
|
||
});
|
||
|
||
export async function startNative() {
|
||
const node = new GatewayNode({
|
||
...base(),
|
||
nodeId: arg("--id", `native-${process.pid}`),
|
||
kind: "native",
|
||
capabilities: ["shell", "worker", "native"],
|
||
execute: nativeExecute,
|
||
maxConcurrency: Number(arg("--conc", "4")),
|
||
meta: base().name ? { name: base().name } : {},
|
||
});
|
||
await node.start();
|
||
console.log(`[native] ${node.nodeId} 在线`);
|
||
}
|
||
|
||
/** 已知 agent CLI 的人类可读标签(面板/节点名都用它)。 */
|
||
export function cliLabel(cli) {
|
||
const map = {
|
||
claude: "Claude Code", codex: "Codex CLI", gemini: "Gemini CLI",
|
||
pi: "Pi CLI", qwen: "Qwen Code", o3: "OpenAI o3 CLI", gpt: "OpenAI CLI",
|
||
};
|
||
const base = String(cli || "").split(/[\\/]/).pop().toLowerCase();
|
||
const short = String(cli || "").split(/[\\/]/).pop();
|
||
return map[base] || map[cli] || `CLI · ${short}`;
|
||
}
|
||
|
||
/** 通用 CLI 适配器节点:任意本地 agent CLI(--cli 指定,{prompt} 占位符)。 */
|
||
export async function startCliAdapter() {
|
||
const cli = arg("--cli", "");
|
||
const cliArgs = arg("--cli-args", "") || "";
|
||
const cliEnv = parseEnvs(argAll("--env"));
|
||
if (!cli) {
|
||
console.error("adapter-cli 需要 --cli <可执行名>,如 --cli claude");
|
||
process.exit(1);
|
||
}
|
||
const cliBase = cli.split(/[\\/]/).pop().toLowerCase(); // 能力标签用文件名,不带路径
|
||
const role = arg("--role", ""); // member | lead | supervisor | admin —— 同一工具可开出多个不同身份
|
||
const PRESETS = {
|
||
member: "",
|
||
lead: "你是组长。执行任务时保持工程判断:先给方案骨架再落实,产出末尾附『自检清单』。",
|
||
supervisor: "你是监管。只做判定与把关:严格依据验收标准审查,结论必须以 PASS 或 FAIL 开头并给出理由。",
|
||
admin: "你是管理员,拥有全局视角,输出需包含风险与影响面分析。",
|
||
};
|
||
const pre = arg("--pre", "") || PRESETS[role] || "";
|
||
const baseExec = makeAdapterExecute("cli", { cli, cliArgs, cliEnv });
|
||
const execute = pre
|
||
? async (task) => baseExec({ ...task, prompt: pre + "\n\n【本次任务】\n" + String(task.prompt || "") })
|
||
: baseExec;
|
||
const node = new GatewayNode({
|
||
...base(),
|
||
nodeId: arg("--id", `adapter-cli-${cli}-${process.pid}`),
|
||
kind: "adapter",
|
||
capabilities: ["adapter", "cli", "worker", "shell", cliBase],
|
||
execute,
|
||
maxConcurrency: Number(arg("--conc", "1")),
|
||
model: arg("--model", "") || null,
|
||
cost: 2,
|
||
meta: { adapt: "cli", cli, role: role || null, name: base().name || cliLabel(cli) }, // 没起名就用真身标签
|
||
});
|
||
await node.start();
|
||
console.log(`[adapter:cli] ${node.nodeId}(${cli})在线`);
|
||
}
|
||
|
||
export async function startAdapter() {
|
||
const kind = arg("--adapt", "cmd");
|
||
if (kind === "cli") return startCliAdapter();
|
||
const cliEnv = parseEnvs(argAll("--env")); // codex 也支持 --env 注入自定义模型配置
|
||
const CAPS = { cmd: ["adapter", "cmd", "worker"], http: ["adapter", "http", "llm", "worker"], codex: ["adapter", "codex", "cli", "worker"] };
|
||
const node = new GatewayNode({
|
||
...base(),
|
||
nodeId: arg("--id", `adapter-${kind}-${process.pid}`),
|
||
kind: "adapter",
|
||
capabilities: CAPS[kind] || CAPS.cmd,
|
||
execute: makeAdapterExecute(kind, { cliEnv }),
|
||
maxConcurrency: Number(arg("--conc", "2")),
|
||
model: kind === "http" ? "deepseek-v4-flash" : null,
|
||
cost: kind === "http" ? 3 : 2,
|
||
meta: kind === "http"
|
||
? { adapt: kind, name: base().name || "大模型节点" }
|
||
: { adapt: kind, name: base().name || (kind === "codex" ? "Codex CLI" : "适配器节点") },
|
||
});
|
||
await node.start();
|
||
console.log(`[adapter:${kind}] ${node.nodeId} 在线`);
|
||
}
|
||
|
||
// 直接运行:node src/cli-nodes.js [native|adapter-cmd|adapter-http|adapter-codex]
|
||
import { pathToFileURL } from "node:url";
|
||
const direct = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
||
if (direct) {
|
||
const which = process.argv[2] || "native";
|
||
if (which.startsWith("adapter")) {
|
||
if (!process.argv.includes("--adapt")) {
|
||
const k = which.replace("adapter-", "") || "cmd";
|
||
process.argv.push("--adapt", k);
|
||
}
|
||
await startAdapter();
|
||
} else {
|
||
await startNative();
|
||
}
|
||
}
|