diff --git a/packages/adapters/hermes/src/server/command-resolution.test.ts b/packages/adapters/hermes/src/server/command-resolution.test.ts index 2b8b1a97ed3..9512b78fcea 100644 --- a/packages/adapters/hermes/src/server/command-resolution.test.ts +++ b/packages/adapters/hermes/src/server/command-resolution.test.ts @@ -1,10 +1,10 @@ import os from "node:os"; import path from "node:path"; -import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { expect, test } from "vitest"; import { HERMES_CLI } from "../shared/constants.js"; -import { resolveHermesCommand } from "./execute.js"; +import { execute, resolveHermesCommand } from "./execute.js"; import { testEnvironment } from "./test.js"; test("resolveHermesCommand prefers hermesCommand over command", () => { @@ -47,6 +47,56 @@ test("testEnvironment accepts config.command when hermesCommand is absent", asyn } }); +test("execute renders prompt API base from adapter env PAPERCLIP_API_URL", async () => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), "hermes-prompt-api-url-")); + const cliPath = path.join(tempDir, "fake-hermes"); + const argvPath = path.join(tempDir, "argv.txt"); + const originalApiUrl = process.env.PAPERCLIP_API_URL; + + try { + process.env.PAPERCLIP_API_URL = "https://public.example.com/api"; + await writeFile( + cliPath, + [ + "#!/bin/sh", + `printf '%s\\n' "$@" > ${JSON.stringify(argvPath)}`, + "printf 'done\\n\\nsession_id: test-session\\n'", + "", + ].join("\n"), + "utf8", + ); + await chmod(cliPath, 0o755); + + await execute({ + runId: "run-test", + agent: { + id: "agent-test", + name: "Hermes Test Agent", + companyId: "company-test", + }, + config: { + hermesCommand: cliPath, + env: { + PAPERCLIP_API_URL: { type: "plain", value: "http://127.0.0.1:3101" }, + }, + }, + runtime: {}, + onLog: async () => {}, + } as any); + + const argv = await readFile(argvPath, "utf8"); + expect(argv).toContain("- API base: http://127.0.0.1:3101/api"); + expect(argv).not.toContain("https://public.example.com/api"); + } finally { + if (originalApiUrl === undefined) { + delete process.env.PAPERCLIP_API_URL; + } else { + process.env.PAPERCLIP_API_URL = originalApiUrl; + } + await rm(tempDir, { recursive: true, force: true }); + } +}); + test("managed connections probe the selected environment without falling back to host keys", async () => { const tempDir = await mkdtemp(path.join(os.tmpdir(), "hermes-managed-probe-")); const cliPath = path.join(tempDir, "fake-hermes"); diff --git a/packages/adapters/hermes/src/server/execute.ts b/packages/adapters/hermes/src/server/execute.ts index 98a5d85846e..b018acab243 100644 --- a/packages/adapters/hermes/src/server/execute.ts +++ b/packages/adapters/hermes/src/server/execute.ts @@ -72,6 +72,14 @@ function cfgStringArray(v: unknown): string[] | undefined { ? (v as string[]) : undefined; } +function cfgEnvString(v: unknown): string | undefined { + if (typeof v === "string" && v.length > 0) return v; + if (v && typeof v === "object" && "value" in v) { + const value = (v as { value?: unknown }).value; + return typeof value === "string" && value.length > 0 ? value : undefined; + } + return undefined; +} export function resolveHermesCommand(config: Record): string { return cfgString(config.hermesCommand) || cfgString(config.command) || HERMES_CLI; @@ -432,8 +440,17 @@ export async function execute( } // ── Build prompt ─────────────────────────────────────────────────────── + const userEnvForPrompt = config.env as Record | undefined; + const promptConfig: Record = { ...config }; + if (!cfgString(promptConfig.paperclipApiUrl) && userEnvForPrompt && typeof userEnvForPrompt === "object") { + const configuredPaperclipApiUrl = cfgEnvString(userEnvForPrompt.PAPERCLIP_API_URL); + if (configuredPaperclipApiUrl) { + promptConfig.paperclipApiUrl = configuredPaperclipApiUrl; + } + } + const sessionId = persistSession ? prevSessionId : undefined; - const prompt = buildPrompt(ctx, config, { resumedSession: Boolean(sessionId) }); + const prompt = buildPrompt(ctx, promptConfig, { resumedSession: Boolean(sessionId) }); // ── Build command args ───────────────────────────────────────────────── // Use -Q (quiet) to get clean output: just response + session_id line