mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
fix(adapters): restore built-in Hermes and sync lockfile with server
Re-align phase1 with upstream: hermes_local ships via hermes-paperclip-adapter on the server and UI (hermes-local module). Fixes ERR_PNPM_OUTDATED_LOCKFILE from server/package.json missing a dep still present in the lockfile. Add shared BUILTIN_ADAPTER_TYPES and skip external plugin registration when it would override a built-in type. Docs list Hermes as built-in; Droid remains the primary external example. Made-with: Cursor
This commit is contained in:
parent
14d59da316
commit
f884cbab78
11 changed files with 128 additions and 25 deletions
|
|
@ -24,6 +24,7 @@ When a heartbeat fires, Paperclip:
|
||||||
| OpenCode Local | `opencode_local` | Runs OpenCode CLI locally (multi-provider `provider/model`) |
|
| OpenCode Local | `opencode_local` | Runs OpenCode CLI locally (multi-provider `provider/model`) |
|
||||||
| Cursor | `cursor` | Runs Cursor in background mode |
|
| Cursor | `cursor` | Runs Cursor in background mode |
|
||||||
| Pi Local | `pi_local` | Runs an embedded Pi agent locally |
|
| Pi Local | `pi_local` | Runs an embedded Pi agent locally |
|
||||||
|
| Hermes Local | `hermes_local` | Runs Hermes CLI locally (`hermes-paperclip-adapter`) |
|
||||||
| OpenClaw Gateway | `openclaw_gateway` | Connects to an OpenClaw gateway endpoint |
|
| OpenClaw Gateway | `openclaw_gateway` | Connects to an OpenClaw gateway endpoint |
|
||||||
| [Process](/adapters/process) | `process` | Executes arbitrary shell commands |
|
| [Process](/adapters/process) | `process` | Executes arbitrary shell commands |
|
||||||
| [HTTP](/adapters/http) | `http` | Sends webhooks to external agents |
|
| [HTTP](/adapters/http) | `http` | Sends webhooks to external agents |
|
||||||
|
|
@ -35,7 +36,6 @@ These adapters ship as standalone npm packages and are installed via the plugin
|
||||||
| Adapter | Package | Type Key | Description |
|
| Adapter | Package | Type Key | Description |
|
||||||
|---------|---------|----------|-------------|
|
|---------|---------|----------|-------------|
|
||||||
| Droid Local | `@henkey/droid-paperclip-adapter` | `droid_local` | Runs Factory Droid locally |
|
| Droid Local | `@henkey/droid-paperclip-adapter` | `droid_local` | Runs Factory Droid locally |
|
||||||
| Hermes Local | `@henkey/hermes-paperclip-adapter` | `hermes_local` | Runs Hermes CLI locally |
|
|
||||||
|
|
||||||
## External Adapters
|
## External Adapters
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ my-adapter/
|
||||||
|
|
||||||
## Choosing an Adapter
|
## Choosing an Adapter
|
||||||
|
|
||||||
- **Need a coding agent?** Use `claude_local`, `codex_local`, `opencode_local`, or install `droid_local` / `hermes_local` as external plugins
|
- **Need a coding agent?** Use `claude_local`, `codex_local`, `opencode_local`, `hermes_local`, or install `droid_local` as an external plugin
|
||||||
- **Need to run a script or command?** Use `process`
|
- **Need to run a script or command?** Use `process`
|
||||||
- **Need to call an external service?** Use `http`
|
- **Need to call an external service?** Use `http`
|
||||||
- **Need something custom?** [Create your own adapter](/adapters/creating-an-adapter) or [build an external adapter plugin](/adapters/external-adapters)
|
- **Need something custom?** [Create your own adapter](/adapters/creating-an-adapter) or [build an external adapter plugin](/adapters/external-adapters)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ Built-in adapters:
|
||||||
- `opencode_local`: runs your local `opencode` CLI
|
- `opencode_local`: runs your local `opencode` CLI
|
||||||
- `cursor`: runs Cursor in background mode
|
- `cursor`: runs Cursor in background mode
|
||||||
- `pi_local`: runs an embedded Pi agent locally
|
- `pi_local`: runs an embedded Pi agent locally
|
||||||
|
- `hermes_local`: runs your local `hermes` CLI (`hermes-paperclip-adapter`)
|
||||||
- `openclaw_gateway`: connects to an OpenClaw gateway endpoint
|
- `openclaw_gateway`: connects to an OpenClaw gateway endpoint
|
||||||
- `process`: generic shell command adapter
|
- `process`: generic shell command adapter
|
||||||
- `http`: calls an external HTTP endpoint
|
- `http`: calls an external HTTP endpoint
|
||||||
|
|
@ -46,9 +47,8 @@ Built-in adapters:
|
||||||
External plugin adapters (install via the adapter manager or API):
|
External plugin adapters (install via the adapter manager or API):
|
||||||
|
|
||||||
- `droid_local`: runs your local Factory Droid CLI (`@henkey/droid-paperclip-adapter`)
|
- `droid_local`: runs your local Factory Droid CLI (`@henkey/droid-paperclip-adapter`)
|
||||||
- `hermes_local`: runs your local `hermes` CLI (`@henkey/hermes-paperclip-adapter`)
|
|
||||||
|
|
||||||
For local CLI adapters (`claude_local`, `codex_local`, `opencode_local`, `droid_local`, `hermes_local`), Paperclip assumes the CLI is already installed and authenticated on the host machine.
|
For local CLI adapters (`claude_local`, `codex_local`, `opencode_local`, `hermes_local`, `droid_local`), Paperclip assumes the CLI is already installed and authenticated on the host machine.
|
||||||
|
|
||||||
## 3.2 Runtime behavior
|
## 3.2 Runtime behavior
|
||||||
|
|
||||||
|
|
@ -177,7 +177,7 @@ Start with least privilege where possible, and avoid exposing secrets in broad r
|
||||||
|
|
||||||
## 10. Minimal setup checklist
|
## 10. Minimal setup checklist
|
||||||
|
|
||||||
1. Choose adapter (e.g. `claude_local`, `codex_local`, `opencode_local`, `cursor`, or `openclaw_gateway`). External plugins like `droid_local` and `hermes_local` are also available via the adapter manager.
|
1. Choose adapter (e.g. `claude_local`, `codex_local`, `opencode_local`, `hermes_local`, `cursor`, or `openclaw_gateway`). External plugins like `droid_local` are also available via the adapter manager.
|
||||||
2. Set `cwd` to the target workspace (for local adapters).
|
2. Set `cwd` to the target workspace (for local adapters).
|
||||||
3. Optionally add a prompt template (`promptTemplate`) or use the managed instructions bundle.
|
3. Optionally add a prompt template (`promptTemplate`) or use the managed instructions bundle.
|
||||||
4. Configure heartbeat policy (timer and/or assignment wakeups).
|
4. Configure heartbeat policy (timer and/or assignment wakeups).
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@
|
||||||
"drizzle-orm": "^0.38.4",
|
"drizzle-orm": "^0.38.4",
|
||||||
"embedded-postgres": "^18.1.0-beta.16",
|
"embedded-postgres": "^18.1.0-beta.16",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"hermes-paperclip-adapter": "^0.2.0",
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^28.1.0",
|
||||||
"multer": "^2.0.2",
|
"multer": "^2.0.2",
|
||||||
"open": "^11.0.0",
|
"open": "^11.0.0",
|
||||||
|
|
|
||||||
15
server/src/adapters/builtin-adapter-types.ts
Normal file
15
server/src/adapters/builtin-adapter-types.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Adapter types shipped with Paperclip. External plugins must not replace these.
|
||||||
|
*/
|
||||||
|
export const BUILTIN_ADAPTER_TYPES = new Set([
|
||||||
|
"claude_local",
|
||||||
|
"codex_local",
|
||||||
|
"cursor",
|
||||||
|
"gemini_local",
|
||||||
|
"openclaw_gateway",
|
||||||
|
"opencode_local",
|
||||||
|
"pi_local",
|
||||||
|
"hermes_local",
|
||||||
|
"process",
|
||||||
|
"http",
|
||||||
|
]);
|
||||||
|
|
@ -67,6 +67,21 @@ import {
|
||||||
import {
|
import {
|
||||||
agentConfigurationDoc as piAgentConfigurationDoc,
|
agentConfigurationDoc as piAgentConfigurationDoc,
|
||||||
} from "@paperclipai/adapter-pi-local";
|
} from "@paperclipai/adapter-pi-local";
|
||||||
|
import {
|
||||||
|
execute as hermesExecute,
|
||||||
|
testEnvironment as hermesTestEnvironment,
|
||||||
|
sessionCodec as hermesSessionCodec,
|
||||||
|
listSkills as hermesListSkills,
|
||||||
|
syncSkills as hermesSyncSkills,
|
||||||
|
detectModel as detectModelFromHermes,
|
||||||
|
} from "hermes-paperclip-adapter/server";
|
||||||
|
import {
|
||||||
|
agentConfigurationDoc as hermesAgentConfigurationDoc,
|
||||||
|
models as hermesModels,
|
||||||
|
} from "hermes-paperclip-adapter";
|
||||||
|
import { BUILTIN_ADAPTER_TYPES } from "./builtin-adapter-types.js";
|
||||||
|
import { buildExternalAdapters } from "./plugin-loader.js";
|
||||||
|
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";
|
||||||
|
|
||||||
|
|
@ -163,6 +178,19 @@ const piLocalAdapter: ServerAdapterModule = {
|
||||||
agentConfigurationDoc: piAgentConfigurationDoc,
|
agentConfigurationDoc: piAgentConfigurationDoc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hermesLocalAdapter: ServerAdapterModule = {
|
||||||
|
type: "hermes_local",
|
||||||
|
execute: hermesExecute,
|
||||||
|
testEnvironment: hermesTestEnvironment,
|
||||||
|
sessionCodec: hermesSessionCodec,
|
||||||
|
listSkills: hermesListSkills,
|
||||||
|
syncSkills: hermesSyncSkills,
|
||||||
|
models: hermesModels,
|
||||||
|
supportsLocalAgentJwt: true,
|
||||||
|
agentConfigurationDoc: hermesAgentConfigurationDoc,
|
||||||
|
detectModel: () => detectModelFromHermes(),
|
||||||
|
};
|
||||||
|
|
||||||
const adaptersByType = new Map<string, ServerAdapterModule>();
|
const adaptersByType = new Map<string, ServerAdapterModule>();
|
||||||
|
|
||||||
function registerBuiltInAdapters() {
|
function registerBuiltInAdapters() {
|
||||||
|
|
@ -174,6 +202,7 @@ function registerBuiltInAdapters() {
|
||||||
cursorLocalAdapter,
|
cursorLocalAdapter,
|
||||||
geminiLocalAdapter,
|
geminiLocalAdapter,
|
||||||
openclawGatewayAdapter,
|
openclawGatewayAdapter,
|
||||||
|
hermesLocalAdapter,
|
||||||
processAdapter,
|
processAdapter,
|
||||||
httpAdapter,
|
httpAdapter,
|
||||||
]) {
|
]) {
|
||||||
|
|
@ -184,15 +213,12 @@ function registerBuiltInAdapters() {
|
||||||
registerBuiltInAdapters();
|
registerBuiltInAdapters();
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Load external adapter plugins (droid, hermes, etc.)
|
// Load external adapter plugins (e.g. droid_local)
|
||||||
//
|
//
|
||||||
// External adapter packages export createServerAdapter() which returns a
|
// External adapter packages export createServerAdapter() which returns a
|
||||||
// ServerAdapterModule. The host fills in sessionManagement.
|
// ServerAdapterModule. The host fills in sessionManagement.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
import { buildExternalAdapters } from "./plugin-loader.js";
|
|
||||||
import { getDisabledAdapterTypes } from "../services/adapter-plugin-store.js";
|
|
||||||
|
|
||||||
/** Cached sync wrapper — the store is a simple JSON file read, safe to call frequently. */
|
/** Cached sync wrapper — the store is a simple JSON file read, safe to call frequently. */
|
||||||
function getDisabledAdapterTypesFromStore(): string[] {
|
function getDisabledAdapterTypesFromStore(): string[] {
|
||||||
return getDisabledAdapterTypes();
|
return getDisabledAdapterTypes();
|
||||||
|
|
@ -208,6 +234,12 @@ const externalAdaptersReady: Promise<void> = (async () => {
|
||||||
try {
|
try {
|
||||||
const externalAdapters = await buildExternalAdapters();
|
const externalAdapters = await buildExternalAdapters();
|
||||||
for (const externalAdapter of externalAdapters) {
|
for (const externalAdapter of externalAdapters) {
|
||||||
|
if (BUILTIN_ADAPTER_TYPES.has(externalAdapter.type)) {
|
||||||
|
console.warn(
|
||||||
|
`[paperclip] Skipping external adapter "${externalAdapter.type}" — conflicts with built-in adapter`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
adaptersByType.set(
|
adaptersByType.set(
|
||||||
externalAdapter.type,
|
externalAdapter.type,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -39,25 +39,10 @@ import type { ServerAdapterModule } from "../adapters/types.js";
|
||||||
import { loadExternalAdapterPackage, getUiParserSource, getOrExtractUiParserSource, reloadExternalAdapter } from "../adapters/plugin-loader.js";
|
import { loadExternalAdapterPackage, getUiParserSource, getOrExtractUiParserSource, reloadExternalAdapter } from "../adapters/plugin-loader.js";
|
||||||
import { logger } from "../middleware/logger.js";
|
import { logger } from "../middleware/logger.js";
|
||||||
import { assertBoard } from "./authz.js";
|
import { assertBoard } from "./authz.js";
|
||||||
|
import { BUILTIN_ADAPTER_TYPES } from "../adapters/builtin-adapter-types.js";
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
const execFileAsync = promisify(execFile);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Known built-in adapter types (cannot be removed via the API)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const BUILTIN_ADAPTER_TYPES = new Set([
|
|
||||||
"claude_local",
|
|
||||||
"codex_local",
|
|
||||||
"cursor",
|
|
||||||
"gemini_local",
|
|
||||||
"openclaw_gateway",
|
|
||||||
"opencode_local",
|
|
||||||
"pi_local",
|
|
||||||
"process",
|
|
||||||
"http",
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Request / Response types
|
// Request / Response types
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ export function agentRoutes(db: Db) {
|
||||||
codex_local: "instructionsFilePath",
|
codex_local: "instructionsFilePath",
|
||||||
droid_local: "instructionsFilePath",
|
droid_local: "instructionsFilePath",
|
||||||
gemini_local: "instructionsFilePath",
|
gemini_local: "instructionsFilePath",
|
||||||
|
hermes_local: "instructionsFilePath",
|
||||||
opencode_local: "instructionsFilePath",
|
opencode_local: "instructionsFilePath",
|
||||||
cursor: "instructionsFilePath",
|
cursor: "instructionsFilePath",
|
||||||
pi_local: "instructionsFilePath",
|
pi_local: "instructionsFilePath",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
Cpu,
|
Cpu,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { OpenCodeLogoIcon } from "@/components/OpenCodeLogoIcon";
|
import { OpenCodeLogoIcon } from "@/components/OpenCodeLogoIcon";
|
||||||
|
import { HermesIcon } from "@/components/HermesIcon";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Type suffix parsing
|
// Type suffix parsing
|
||||||
|
|
@ -73,6 +74,11 @@ const adapterDisplayMap: Record<string, AdapterDisplayInfo> = {
|
||||||
description: "Local multi-provider agent",
|
description: "Local multi-provider agent",
|
||||||
icon: OpenCodeLogoIcon,
|
icon: OpenCodeLogoIcon,
|
||||||
},
|
},
|
||||||
|
hermes_local: {
|
||||||
|
label: "Hermes Agent",
|
||||||
|
description: "Local Hermes CLI agent",
|
||||||
|
icon: HermesIcon,
|
||||||
|
},
|
||||||
pi_local: {
|
pi_local: {
|
||||||
label: "Pi",
|
label: "Pi",
|
||||||
description: "Local Pi agent",
|
description: "Local Pi agent",
|
||||||
|
|
|
||||||
49
ui/src/adapters/hermes-local/config-fields.tsx
Normal file
49
ui/src/adapters/hermes-local/config-fields.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import type { AdapterConfigFieldsProps } from "../types";
|
||||||
|
import {
|
||||||
|
Field,
|
||||||
|
DraftInput,
|
||||||
|
} from "../../components/agent-config-primitives";
|
||||||
|
import { ChoosePathButton } from "../../components/PathInstructionsModal";
|
||||||
|
|
||||||
|
const inputClass =
|
||||||
|
"w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40";
|
||||||
|
const instructionsFileHint =
|
||||||
|
"Absolute path to a markdown file (e.g. AGENTS.md) that defines this agent's behavior. Injected into the system prompt at runtime.";
|
||||||
|
|
||||||
|
export function HermesLocalConfigFields({
|
||||||
|
isCreate,
|
||||||
|
values,
|
||||||
|
set,
|
||||||
|
config,
|
||||||
|
eff,
|
||||||
|
mark,
|
||||||
|
hideInstructionsFile,
|
||||||
|
}: AdapterConfigFieldsProps) {
|
||||||
|
if (hideInstructionsFile) return null;
|
||||||
|
return (
|
||||||
|
<Field label="Agent instructions file" hint={instructionsFileHint}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<DraftInput
|
||||||
|
value={
|
||||||
|
isCreate
|
||||||
|
? values!.instructionsFilePath ?? ""
|
||||||
|
: eff(
|
||||||
|
"adapterConfig",
|
||||||
|
"instructionsFilePath",
|
||||||
|
String(config.instructionsFilePath ?? ""),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onCommit={(v) =>
|
||||||
|
isCreate
|
||||||
|
? set!({ instructionsFilePath: v })
|
||||||
|
: mark("adapterConfig", "instructionsFilePath", v || undefined)
|
||||||
|
}
|
||||||
|
immediate
|
||||||
|
className={inputClass}
|
||||||
|
placeholder="/absolute/path/to/AGENTS.md"
|
||||||
|
/>
|
||||||
|
<ChoosePathButton />
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
ui/src/adapters/hermes-local/index.ts
Normal file
12
ui/src/adapters/hermes-local/index.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import type { UIAdapterModule } from "../types";
|
||||||
|
import { parseHermesStdoutLine } from "hermes-paperclip-adapter/ui";
|
||||||
|
import { HermesLocalConfigFields } from "./config-fields";
|
||||||
|
import { buildHermesConfig } from "hermes-paperclip-adapter/ui";
|
||||||
|
|
||||||
|
export const hermesLocalUIAdapter: UIAdapterModule = {
|
||||||
|
type: "hermes_local",
|
||||||
|
label: "Hermes Agent",
|
||||||
|
parseStdoutLine: parseHermesStdoutLine,
|
||||||
|
ConfigFields: HermesLocalConfigFields,
|
||||||
|
buildAdapterConfig: buildHermesConfig,
|
||||||
|
};
|
||||||
|
|
@ -6,6 +6,7 @@ import { geminiLocalUIAdapter } from "./gemini-local";
|
||||||
import { openCodeLocalUIAdapter } from "./opencode-local";
|
import { openCodeLocalUIAdapter } from "./opencode-local";
|
||||||
import { piLocalUIAdapter } from "./pi-local";
|
import { piLocalUIAdapter } from "./pi-local";
|
||||||
import { openClawGatewayUIAdapter } from "./openclaw-gateway";
|
import { openClawGatewayUIAdapter } from "./openclaw-gateway";
|
||||||
|
import { hermesLocalUIAdapter } from "./hermes-local";
|
||||||
import { processUIAdapter } from "./process";
|
import { processUIAdapter } from "./process";
|
||||||
import { httpUIAdapter } from "./http";
|
import { httpUIAdapter } from "./http";
|
||||||
import { loadDynamicParser } from "./dynamic-loader";
|
import { loadDynamicParser } from "./dynamic-loader";
|
||||||
|
|
@ -18,6 +19,7 @@ function registerBuiltInUIAdapters() {
|
||||||
claudeLocalUIAdapter,
|
claudeLocalUIAdapter,
|
||||||
codexLocalUIAdapter,
|
codexLocalUIAdapter,
|
||||||
geminiLocalUIAdapter,
|
geminiLocalUIAdapter,
|
||||||
|
hermesLocalUIAdapter,
|
||||||
openCodeLocalUIAdapter,
|
openCodeLocalUIAdapter,
|
||||||
piLocalUIAdapter,
|
piLocalUIAdapter,
|
||||||
cursorLocalUIAdapter,
|
cursorLocalUIAdapter,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue