Every role is self-contained (types.ts, prompt.ts, index.ts).
No shared.ts, no cross-role imports. All dependencies injected:
index.ts — wiring (resolve env, call buildSenseGenerator)
build.ts — buildSenseGenerator(deps) → WorkflowDefinition
moderator.ts — pure routing, composes meta from role types
roles/planner/ — buildPlannerRole(deps), self-contained
roles/coder/ — buildCoderRole(deps), self-contained
roles/tester/ — buildTesterRole(deps), self-contained
Workflow is now reusable: buildSenseGenerator() can be called with
any provider/paths, not hardcoded to this machine.
小橘 🍊(NEKO Team)
78 lines
2.4 KiB
TypeScript
78 lines
2.4 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { spawnSafe } from "@uncaged/nerve-workflow-utils";
|
|
import { buildSenseGenerator } from "./build.js";
|
|
|
|
// --- Environment ---
|
|
|
|
const HOME = process.env.HOME ?? "/home/azureuser";
|
|
const NERVE_ROOT = join(HOME, ".uncaged-nerve");
|
|
const SENSES_DIR = join(NERVE_ROOT, "senses");
|
|
|
|
// --- Resolve provider ---
|
|
|
|
async function cfgGet(key: string): Promise<string | null> {
|
|
const result = await spawnSafe("cfg", ["get", key], {
|
|
cwd: NERVE_ROOT,
|
|
env: null,
|
|
timeoutMs: 10_000,
|
|
});
|
|
if (!result.ok) return null;
|
|
return result.value.stdout.trim() || null;
|
|
}
|
|
|
|
const apiKey = process.env.DASHSCOPE_API_KEY ?? (await cfgGet("DASHSCOPE_API_KEY"));
|
|
const baseUrl = process.env.DASHSCOPE_BASE_URL ?? (await cfgGet("DASHSCOPE_BASE_URL"));
|
|
const model = process.env.DASHSCOPE_MODEL ?? (await cfgGet("DASHSCOPE_MODEL")) ?? "qwen-plus";
|
|
if (!apiKey || !baseUrl) {
|
|
throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL");
|
|
}
|
|
|
|
// --- Build context ---
|
|
|
|
function getNerveYaml(): string {
|
|
try {
|
|
return readFileSync(join(NERVE_ROOT, "nerve.yaml"), "utf-8");
|
|
} catch {
|
|
return "# nerve.yaml unavailable";
|
|
}
|
|
}
|
|
|
|
function buildSenseExamples(): string {
|
|
const examples: string[] = [];
|
|
for (const name of ["cpu-usage", "linux-system-health"]) {
|
|
const dir = join(SENSES_DIR, name);
|
|
if (!existsSync(dir)) continue;
|
|
const indexFile = existsSync(join(dir, "index.js"))
|
|
? readFileSync(join(dir, "index.js"), "utf-8")
|
|
: "";
|
|
const schema = existsSync(join(dir, "schema.ts"))
|
|
? readFileSync(join(dir, "schema.ts"), "utf-8")
|
|
: "";
|
|
const migrationDir = join(dir, "migrations");
|
|
let migration = "";
|
|
if (existsSync(join(migrationDir, "0001_init.sql"))) {
|
|
migration = readFileSync(join(migrationDir, "0001_init.sql"), "utf-8");
|
|
}
|
|
examples.push(
|
|
`### Example sense: ${name}\n\n` +
|
|
`**index.js:**\n\`\`\`js\n${indexFile}\n\`\`\`\n\n` +
|
|
`**schema.ts:**\n\`\`\`ts\n${schema}\n\`\`\`\n\n` +
|
|
`**migrations/0001_init.sql:**\n\`\`\`sql\n${migration}\n\`\`\``,
|
|
);
|
|
}
|
|
return examples.join("\n\n---\n\n");
|
|
}
|
|
|
|
// --- Wire up ---
|
|
|
|
const workflow = buildSenseGenerator({
|
|
provider: { apiKey, baseUrl, model },
|
|
nerveRoot: NERVE_ROOT,
|
|
sensesDir: SENSES_DIR,
|
|
senseExamples: buildSenseExamples(),
|
|
nerveYaml: getNerveYaml(),
|
|
});
|
|
|
|
export default workflow;
|