Files
fusion/plugins/fusion-plugin-paperclip-runtime
gsxdsm a3102080c4 feat(paperclip-runtime): route Paperclip calls through paperclipai CLI in Local CLI mode
The Paperclip runtime card's Local CLI tab previously only derived an apiUrl
from the local config and then made HTTP calls itself, so the Test action and
the company/agent pickers ignored the user's onboarded CLI auth context.

Add CLI-backed variants for every Paperclip call that has a `paperclipai`
counterpart, and route through them when transport=cli — both from the
settings card and from the runtime adapter's prompt path.

- New plugin functions spawning `paperclipai … --json`:
  * probePaperclipViaCli, listCompaniesViaCli, listCompanyAgentsViaCli
    (settings card test + pickers)
  * createIssueViaCli, getIssueViaCli, agentsMeViaCli (runtime hot path)
- New dashboard routes: /providers/paperclip/cli-status, /cli-companies,
  /cli-agents (read-only façades over the plugin's CLI helpers)
- PaperclipRuntimeCard: branches on transport=cli to use the cli-* fetchers
- PaperclipRuntimeAdapter: stores transport on the session and routes
  createIssue/getIssue + identity derivation through CLI variants in CLI mode;
  raises a clear error when agentId is unset in CLI mode (paperclipai has no
  /agents/me equivalent)
- getIssueComments / wakeAgent / getRunEvents stay on HTTP (no matching
  paperclipai subcommands) and continue to use the apiKey discovered from
  the local paperclipai config, so CLI mode still works end-to-end
- Tests: 9 new paperclip-client tests covering each CLI variant + 5 new
  adapter tests for the Local-CLI transport branch (64/64 plugin tests pass)
- Update routes.test for the existing BUNDLED_PLUGIN_RUNTIMES fallback so
  the bundled hermes/openclaw/paperclip entries are expected alongside
  installed plugins

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:06:20 -07:00
..
2026-04-27 22:34:32 -07:00
2026-04-27 22:34:32 -07:00

Paperclip Runtime Plugin

Drives a Paperclip agent (an "employee" in a Paperclip company) via the wakeup + heartbeat-run API. Each Fusion prompt becomes a Paperclip task, not a chat completion.

Mental model — read this first

Paperclip is a control plane for AI labor. Agents are long-lived employees with budgets, chains of command, and approval gates; Paperclip itself does not run models — it dispatches work to adapters (claude_local, codex_local, openclaw, http, …) which run the actual LLM call inside their own heartbeat.

This plugin proxies a Fusion conversation through one of those Paperclip agents:

  1. (Optionally) creates a Paperclip issue with the prompt as its body, assigned to your chosen agent.
  2. Calls POST /api/agents/{id}/wakeup with payload: { prompt, fusionSessionId, issueId } and an idempotency key.
  3. Streams GET /api/heartbeat-runs/{runId}/events, forwarding heartbeat.run.log chunks to the chat UI.
  4. On terminal status (succeeded | failed | cancelled | timed_out), reads the issue's final state and the agent's closing comment.

Implications:

  • Latency is task-shaped (seconds to minutes), not chat-shaped.
  • Governance applies: budget caps, approval requirements, audit log all come from Paperclip.
  • A single Paperclip agent can be proxied from many Fusion sessions concurrently.

Prerequisites

A running Paperclip server you can reach. For local development:

npm install -g paperclipai
paperclipai onboard      # interactive setup
paperclipai run          # starts the server (default: http://localhost:3100)

Verify the server is up:

curl http://localhost:3100/api/health

Connection modes

The dashboard settings card lets you pick API or CLI mode:

API mode (default)

Paste an apiUrl and an agent apiKey. Get a key from the Paperclip UI's agent detail page → "Create API Key" (the full value is shown once). For local-trusted deployments, the key may be omitted.

CLI mode

Auto-derive the apiUrl from the local paperclipai install. The plugin reads ~/.paperclip/instances/default/config.json and uses that host:port as the apiUrl. No token needs to be pasted into Fusion. For non-local-trusted deployments, you can still set an override apiKey.

CLI key bootstrap

For authenticated Paperclip deployments (e.g. paperclip-dev or any non-local_trusted instance), a bearer token is required. The dashboard offers a one-click " Mint API key via paperclipai" button that appears in CLI mode when:

  • A connection has been attempted but available === false (typically "API key rejected"), AND
  • The user has already selected an agent in the agent picker.

The button calls POST /api/providers/paperclip/cli-mint-key on the Fusion backend, which spawns:

paperclipai agent local-cli <agentRef> --json --no-install-skills --key-name fusion-runtime

On success the returned apiKey is written into the API key field and a save-prompt toast is shown. On failure (e.g. CLI not authenticated) the toast shows the error and instructs the user to run paperclipai onboard.

Requirement: The local paperclipai CLI must be authenticated (~/.paperclip/context.json must have a valid profile). Run paperclipai onboard to authenticate if the mint fails.

Conversation modes

Independent of transport, the mode setting controls how prompts map to Paperclip issues:

Mode Behavior
rolling-issue (default) Creates one Paperclip issue per Fusion session; subsequent prompts add comments. Closest to chat.
issue-per-prompt Each prompt creates a new top-level issue. Maximally explicit; clutters the board.
wakeup-only No issue side-effects; the prompt is delivered via the wakeup payload only. Requires the agent's prompt template to handle payload-driven wakes.

Settings

Key Env var Default Notes
transport PAPERCLIP_TRANSPORT api api or cli.
apiUrl PAPERCLIP_API_URL http://localhost:3100 API mode only.
apiKey PAPERCLIP_API_KEY (none) API mode (and as a CLI-mode override).
cliBinaryPath PAPERCLIPAI_BIN paperclipai CLI mode only.
cliConfigPath PAPERCLIP_CLI_CONFIG ~/.paperclip/instances/default/config.json CLI mode only.
agentId PAPERCLIP_AGENT_ID auto-derived from /api/agents/me The Paperclip agent this Fusion runtime proxies.
companyId PAPERCLIP_COMPANY_ID auto-derived from /api/agents/me The Paperclip company.
mode PAPERCLIP_RUNTIME_MODE rolling-issue One of the conversation modes above.
parentIssueId PAPERCLIP_PARENT_ISSUE_ID (none) Optional issue scoping.
projectId PAPERCLIP_PROJECT_ID (none) Optional.
goalId PAPERCLIP_GOAL_ID (none) Optional.
runTimeoutMs PAPERCLIP_RUN_TIMEOUT_MS 600000 Local cap before Fusion stops polling. The run continues server-side.
pollIntervalMs PAPERCLIP_POLL_INTERVAL_MS 500 Initial poll interval.
pollIntervalMaxMs PAPERCLIP_POLL_INTERVAL_MAX_MS 2000 Max poll interval after exponential backoff.

Settings precedence: plugin settings → env var → default.

Public API

import {
  PaperclipRuntimeAdapter,
  // REST helpers
  agentsMe,
  listCompanies,
  listCompanyAgents,
  // Probes
  probePaperclipConnection,
  discoverPaperclipCliConfig,
  // CLI key minting
  mintAgentApiKeyViaCli,
  // Types
  type PaperclipAgentSummary,
  type PaperclipCompanySummary,
  type PaperclipConnectionStatus,
  type PaperclipCliDiscoveryResult,
  type MintCliKeyOptions,
  type MintedApiKey,
} from "@fusion-plugin-examples/paperclip-runtime";
  • probePaperclipConnection({ apiUrl, apiKey?, timeoutMs? }){ available, identity?, reason? }. Powers the dashboard's "✓ Connected as " badge.
  • listCompanyAgents(apiUrl, apiKey, companyId) → list of agents in a company. Drives the agent picker.
  • discoverPaperclipCliConfig({ configPath? }){ ok, apiUrl, deploymentMode? } from the local paperclipai config. Drives CLI-mode auth discovery.
  • mintAgentApiKeyViaCli(opts: MintCliKeyOptions)Promise<MintedApiKey>. Spawns paperclipai agent local-cli <agentRef> --json --no-install-skills to mint a fresh agent API key. Throws on ENOENT, non-zero exit, or malformed JSON; includes a hint to run paperclipai onboard on auth failures.

Endpoints used (Paperclip side)

Method Path Purpose
GET /api/agents/me Identity + auto-derive agentId/companyId.
GET /api/companies Company list (board access).
GET /api/companies/{companyId}/agents Agent list (agent-key sees its own company).
POST /api/companies/{companyId}/issues Issue creation (issue-per-prompt / rolling-issue modes).
POST /api/agents/{agentId}/wakeup Trigger a heartbeat run with the Fusion prompt as payload.
GET /api/heartbeat-runs/{runId}/events Streaming run log + status.
GET /api/issues/{issueId} Final issue state.
GET /api/issues/{issueId}/comments Final comment fallback.

The adapter does not call /api/issues/{id}/checkout — checkout is the agent's job during its own heartbeat. The adapter does not call the legacy /api/agents/{id}/heartbeat/invoke.

Limitations

  • Latency. Heartbeat runs can take minutes; not a chat-completion drop-in.
  • Single-agent identity per connection. A Paperclip agent API key is scoped to one agent in one company. To proxy several agents, configure several Fusion connections.
  • Run-events schema is partially inferred. The events endpoint payload shape is documented but not formally schema'd; the client accepts both bare-array and { events: [...] } envelopes defensively.

Metadata

  • Plugin ID: fusion-plugin-paperclip-runtime
  • Runtime ID: paperclip
  • Package: @fusion-plugin-examples/paperclip-runtime

Development

pnpm --filter @fusion-plugin-examples/paperclip-runtime test    # 46 tests
pnpm --filter @fusion-plugin-examples/paperclip-runtime build