scheduler-gateway/src/cli-nodes.js
Liuxinyu176 bbb364cd69 feat: DSH 调度网关(互联网关)v6.0.0 — 四模型合并版 R1+R2+R3
- 零第三方依赖,Node >=20 原生 ESM
- 团队式编排引擎:拆解/路由/执行/审查/合并全真实 LLM
- 阶段心跳、单一权威清单守卫、all-keys-failed 如实上报
- H4 会话视图/amend/watchdog 有界重试/产物区 artifacts.json
- H5 零依赖三栏控制台
2026-10-09 23:20:27 +08:00

128 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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