mirror of
https://github.com/paperclipai/paperclip
synced 2026-04-25 17:25:15 +02:00
fix: honor Hermes local command override (#3503)
## Summary This fixes the Hermes local adapter so that a configured command override is respected during both environment tests and execution. ## Problem The Hermes adapter expects `adapterConfig.hermesCommand`, but the generic local command path in the UI was storing `adapterConfig.command`. As a result, changing the command in the UI did not reliably affect runtime behavior. In real use, the adapter could still fall back to the default `hermes` binary. This showed up clearly in setups where Hermes is launched through a wrapper command rather than installed directly on the host. ## What changed - switched the Hermes local UI adapter to the Hermes-specific config builder - updated the configuration form to read and write `hermesCommand` for `hermes_local` - preserved the override correctly in the test-environment path - added server-side normalization from legacy `command` to `hermesCommand` ## Compatibility The server-side normalization keeps older saved agent configs working, including configs that still store the value under `command`. ## Validation Validated against a Docker-based Hermes workflow using a local wrapper exposed through a symlinked command: - `Command = hermes-docker` - environment test respects the override - runs no longer fall back to `hermes` Typecheck also passed for both UI and server. Co-authored-by: NoronhaH <NoronhaH@users.noreply.github.com>
This commit is contained in:
@@ -86,6 +86,37 @@ import { getDisabledAdapterTypes } from "../services/adapter-plugin-store.js";
|
|||||||
import { processAdapter } from "./process/index.js";
|
import { processAdapter } from "./process/index.js";
|
||||||
import { httpAdapter } from "./http/index.js";
|
import { httpAdapter } from "./http/index.js";
|
||||||
|
|
||||||
|
function normalizeHermesConfig<T extends { config?: unknown; agent?: unknown }>(ctx: T): T {
|
||||||
|
const config =
|
||||||
|
ctx && typeof ctx === "object" && "config" in ctx && ctx.config && typeof ctx.config === "object"
|
||||||
|
? (ctx.config as Record<string, unknown>)
|
||||||
|
: null;
|
||||||
|
const agent =
|
||||||
|
ctx && typeof ctx === "object" && "agent" in ctx && ctx.agent && typeof ctx.agent === "object"
|
||||||
|
? (ctx.agent as Record<string, unknown>)
|
||||||
|
: null;
|
||||||
|
const agentAdapterConfig =
|
||||||
|
agent?.adapterConfig && typeof agent.adapterConfig === "object"
|
||||||
|
? (agent.adapterConfig as Record<string, unknown>)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const configCommand =
|
||||||
|
typeof config?.command === "string" && config.command.length > 0 ? config.command : undefined;
|
||||||
|
const agentCommand =
|
||||||
|
typeof agentAdapterConfig?.command === "string" && agentAdapterConfig.command.length > 0
|
||||||
|
? agentAdapterConfig.command
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (config && !config.hermesCommand && configCommand) {
|
||||||
|
config.hermesCommand = configCommand;
|
||||||
|
}
|
||||||
|
if (agentAdapterConfig && !agentAdapterConfig.hermesCommand && agentCommand) {
|
||||||
|
agentAdapterConfig.hermesCommand = agentCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
const claudeLocalAdapter: ServerAdapterModule = {
|
const claudeLocalAdapter: ServerAdapterModule = {
|
||||||
type: "claude_local",
|
type: "claude_local",
|
||||||
execute: claudeExecute,
|
execute: claudeExecute,
|
||||||
@@ -202,8 +233,8 @@ const piLocalAdapter: ServerAdapterModule = {
|
|||||||
|
|
||||||
const hermesLocalAdapter: ServerAdapterModule = {
|
const hermesLocalAdapter: ServerAdapterModule = {
|
||||||
type: "hermes_local",
|
type: "hermes_local",
|
||||||
execute: hermesExecute,
|
execute: (ctx) => hermesExecute(normalizeHermesConfig(ctx) as never),
|
||||||
testEnvironment: hermesTestEnvironment,
|
testEnvironment: (ctx) => hermesTestEnvironment(normalizeHermesConfig(ctx) as never),
|
||||||
sessionCodec: hermesSessionCodec,
|
sessionCodec: hermesSessionCodec,
|
||||||
listSkills: hermesListSkills,
|
listSkills: hermesListSkills,
|
||||||
syncSkills: hermesSyncSkills,
|
syncSkills: hermesSyncSkills,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { UIAdapterModule } from "../types";
|
import type { UIAdapterModule } from "../types";
|
||||||
import { parseHermesStdoutLine } from "hermes-paperclip-adapter/ui";
|
import { parseHermesStdoutLine } from "hermes-paperclip-adapter/ui";
|
||||||
import { SchemaConfigFields, buildSchemaAdapterConfig } from "../schema-config-fields";
|
|
||||||
import { buildHermesConfig } from "hermes-paperclip-adapter/ui";
|
import { buildHermesConfig } from "hermes-paperclip-adapter/ui";
|
||||||
|
import { SchemaConfigFields } from "../schema-config-fields";
|
||||||
|
|
||||||
export const hermesLocalUIAdapter: UIAdapterModule = {
|
export const hermesLocalUIAdapter: UIAdapterModule = {
|
||||||
type: "hermes_local",
|
type: "hermes_local",
|
||||||
label: "Hermes Agent",
|
label: "Hermes Agent",
|
||||||
parseStdoutLine: parseHermesStdoutLine,
|
parseStdoutLine: parseHermesStdoutLine,
|
||||||
ConfigFields: SchemaConfigFields,
|
ConfigFields: SchemaConfigFields,
|
||||||
buildAdapterConfig: buildSchemaAdapterConfig,
|
buildAdapterConfig: buildHermesConfig,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -291,6 +291,8 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||||||
enabled: Boolean(selectedCompanyId),
|
enabled: Boolean(selectedCompanyId),
|
||||||
});
|
});
|
||||||
const models = fetchedModels ?? externalModels ?? [];
|
const models = fetchedModels ?? externalModels ?? [];
|
||||||
|
const adapterCommandField =
|
||||||
|
adapterType === "hermes_local" ? "hermesCommand" : "command";
|
||||||
const {
|
const {
|
||||||
data: detectedModelData,
|
data: detectedModelData,
|
||||||
refetch: refetchDetectedModel,
|
refetch: refetchDetectedModel,
|
||||||
@@ -346,7 +348,19 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||||||
return uiAdapter.buildAdapterConfig(val!);
|
return uiAdapter.buildAdapterConfig(val!);
|
||||||
}
|
}
|
||||||
const base = config as Record<string, unknown>;
|
const base = config as Record<string, unknown>;
|
||||||
return { ...base, ...overlay.adapterConfig };
|
const next = { ...base, ...overlay.adapterConfig };
|
||||||
|
if (adapterType === "hermes_local") {
|
||||||
|
const hermesCommand =
|
||||||
|
typeof next.hermesCommand === "string" && next.hermesCommand.length > 0
|
||||||
|
? next.hermesCommand
|
||||||
|
: typeof next.command === "string" && next.command.length > 0
|
||||||
|
? next.command
|
||||||
|
: undefined;
|
||||||
|
if (hermesCommand) {
|
||||||
|
next.hermesCommand = hermesCommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
const testEnvironment = useMutation({
|
const testEnvironment = useMutation({
|
||||||
@@ -667,12 +681,20 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||||||
value={
|
value={
|
||||||
isCreate
|
isCreate
|
||||||
? val!.command
|
? val!.command
|
||||||
: eff("adapterConfig", "command", String(config.command ?? ""))
|
: eff(
|
||||||
|
"adapterConfig",
|
||||||
|
adapterCommandField,
|
||||||
|
String(
|
||||||
|
(adapterType === "hermes_local"
|
||||||
|
? config.hermesCommand ?? config.command
|
||||||
|
: config.command) ?? "",
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onCommit={(v) =>
|
onCommit={(v) =>
|
||||||
isCreate
|
isCreate
|
||||||
? set!({ command: v })
|
? set!({ command: v })
|
||||||
: mark("adapterConfig", "command", v || null)
|
: mark("adapterConfig", adapterCommandField, v || null)
|
||||||
}
|
}
|
||||||
immediate
|
immediate
|
||||||
className={inputClass}
|
className={inputClass}
|
||||||
|
|||||||
Reference in New Issue
Block a user