From ba012d98bcd9a5a21647d3a2d65e73df035a4561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Mon, 18 May 2026 09:22:12 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20add=20@uncaged/uwf-agent-hermes=20?= =?UTF-8?q?=E2=80=94=20Hermes=20agent=20CLI=20adapter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spawns 'hermes chat' with assembled prompt from agent-kit context. Agent-kit handles extract, StepNode write, and stdout output. Refs #309, #316 --- packages/uwf-agent-hermes/package.json | 32 +++++++++ packages/uwf-agent-hermes/src/cli.ts | 6 ++ packages/uwf-agent-hermes/src/hermes.ts | 90 +++++++++++++++++++++++++ packages/uwf-agent-hermes/src/index.ts | 1 + packages/uwf-agent-hermes/tsconfig.json | 9 +++ 5 files changed, 138 insertions(+) create mode 100644 packages/uwf-agent-hermes/package.json create mode 100644 packages/uwf-agent-hermes/src/cli.ts create mode 100644 packages/uwf-agent-hermes/src/hermes.ts create mode 100644 packages/uwf-agent-hermes/src/index.ts create mode 100644 packages/uwf-agent-hermes/tsconfig.json diff --git a/packages/uwf-agent-hermes/package.json b/packages/uwf-agent-hermes/package.json new file mode 100644 index 0000000..c20f7ba --- /dev/null +++ b/packages/uwf-agent-hermes/package.json @@ -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" + } +} diff --git a/packages/uwf-agent-hermes/src/cli.ts b/packages/uwf-agent-hermes/src/cli.ts new file mode 100644 index 0000000..39941d1 --- /dev/null +++ b/packages/uwf-agent-hermes/src/cli.ts @@ -0,0 +1,6 @@ +#!/usr/bin/env bun + +import { createHermesAgent } from "./hermes.js"; + +const main = createHermesAgent(); +void main(); diff --git a/packages/uwf-agent-hermes/src/hermes.ts b/packages/uwf-agent-hermes/src/hermes.ts new file mode 100644 index 0000000..6851928 --- /dev/null +++ b/packages/uwf-agent-hermes/src/hermes.ts @@ -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 { + 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 { + const fullPrompt = buildHermesPrompt(ctx); + return spawnHermesChat(fullPrompt); +} + +/** Agent CLI factory: parses argv, runs Hermes, extracts output, writes StepNode. */ +export function createHermesAgent(): () => Promise { + return createAgent({ + name: "hermes", + run: runHermes, + }); +} diff --git a/packages/uwf-agent-hermes/src/index.ts b/packages/uwf-agent-hermes/src/index.ts new file mode 100644 index 0000000..b27f12a --- /dev/null +++ b/packages/uwf-agent-hermes/src/index.ts @@ -0,0 +1 @@ +export { buildHermesPrompt, createHermesAgent } from "./hermes.js"; diff --git a/packages/uwf-agent-hermes/tsconfig.json b/packages/uwf-agent-hermes/tsconfig.json new file mode 100644 index 0000000..c00cc38 --- /dev/null +++ b/packages/uwf-agent-hermes/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"], + "references": [{ "path": "../uwf-agent-kit" }] +}