/** 独立节点进程入口(供 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(); } }