feat: add @uncaged/uwf-agent-hermes — Hermes agent CLI adapter

Spawns 'hermes chat' with assembled prompt from agent-kit context.
Agent-kit handles extract, StepNode write, and stdout output.

Refs #309, #316
This commit is contained in:
2026-05-18 09:22:12 +00:00
parent b165049a13
commit ba012d98bc
5 changed files with 138 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
{
"name": "@uncaged/uwf-agent-hermes",
"version": "0.1.0",
"files": [
"src",
"dist",
"package.json"
],
"type": "module",
"bin": {
"uwf-hermes": "./src/cli.ts"
},
"exports": {
".": {
"bun": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"test": "bun test"
},
"dependencies": {
"@uncaged/uwf-agent-kit": "workspace:^"
},
"devDependencies": {
"typescript": "^5.8.3"
},
"publishConfig": {
"access": "public"
}
}
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bun
import { createHermesAgent } from "./hermes.js";
const main = createHermesAgent();
void main();
+90
View File
@@ -0,0 +1,90 @@
import { spawn } from "node:child_process";
import { type AgentContext, createAgent } from "@uncaged/uwf-agent-kit";
const HERMES_COMMAND = "hermes";
const HERMES_MAX_TURNS = 90;
function buildHistorySummary(history: AgentContext["history"]): string {
if (history.length === 0) {
return "";
}
const lines: string[] = ["## Previous Steps"];
for (let i = 0; i < history.length; i++) {
const step = history[i];
if (step === undefined) {
continue;
}
lines.push("");
lines.push(`### Step ${i + 1}: ${step.role}`);
lines.push(`Output: ${JSON.stringify(step.output)}`);
lines.push(`Agent: ${step.agent}`);
}
return lines.join("\n");
}
/** Assemble system prompt, task, and prior step outputs for Hermes. */
export function buildHermesPrompt(ctx: AgentContext): string {
const parts: string[] = [ctx.systemPrompt, "", "## Task", ctx.prompt];
const historyBlock = buildHistorySummary(ctx.history);
if (historyBlock !== "") {
parts.push("", historyBlock);
}
return parts.join("\n");
}
function spawnHermesChat(prompt: string): Promise<string> {
return new Promise((resolve, reject) => {
const args = [
"chat",
"-q",
prompt,
"--yolo",
"--max-turns",
String(HERMES_MAX_TURNS),
"--quiet",
];
const child = spawn(HERMES_COMMAND, args, {
env: process.env,
shell: false,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout?.on("data", (chunk: Buffer) => {
stdout += chunk.toString();
});
child.stderr?.on("data", (chunk: Buffer) => {
stderr += chunk.toString();
});
child.on("error", (cause) => {
const message = cause instanceof Error ? cause.message : String(cause);
reject(new Error(`hermes spawn failed: ${message}`));
});
child.on("close", (code) => {
if (code === 0) {
resolve(stdout);
return;
}
const detail = stderr.trim() !== "" ? ` stderr=${stderr.trim()}` : "";
reject(new Error(`hermes exited with code ${code ?? "null"}${detail}`));
});
});
}
async function runHermes(ctx: AgentContext): Promise<string> {
const fullPrompt = buildHermesPrompt(ctx);
return spawnHermesChat(fullPrompt);
}
/** Agent CLI factory: parses argv, runs Hermes, extracts output, writes StepNode. */
export function createHermesAgent(): () => Promise<void> {
return createAgent({
name: "hermes",
run: runHermes,
});
}
+1
View File
@@ -0,0 +1 @@
export { buildHermesPrompt, createHermesAgent } from "./hermes.js";
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"],
"references": [{ "path": "../uwf-agent-kit" }]
}