Files
fusion/plugins/fusion-plugin-paperclip-runtime/README.md
Fusion 6e65786bc5 fix(plugins): re-export probe symbols + declare plugin deps in dashboard
- Hermes / OpenClaw plugin index.ts now re-export `probeHermesBinary` /
  `probeOpenClawBinary` and their status types so the dashboard's
  `runtime-provider-probes.ts` façade can import them via the public
  package entry instead of deep paths.
- Dashboard `package.json` adds `@fusion-plugin-examples/hermes-runtime`,
  `…/openclaw-runtime`, `…/paperclip-runtime` as workspace deps so
  pnpm symlinks them into `packages/dashboard/node_modules/`. Without
  these, the new probe imports failed with "Cannot find module" during
  `pnpm typecheck`.

This clears 6 of the 9 outstanding typecheck errors. The remaining 3 are
in the in-flight Hermes plugin rewrite (runtime-adapter still imports
from a deleted `./pi-module.js`; the new `index.ts` calls a factory
with the wrong arg type) and should be resolved by the same change set
that landed the rewrite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:17:54 -07:00

159 lines
8.0 KiB
Markdown

# Paperclip Runtime Plugin
Drives a [Paperclip](https://paperclip.ing/) 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:
```bash
npm install -g paperclipai
paperclipai onboard # interactive setup
paperclipai run # starts the server (default: http://localhost:3100)
```
Verify the server is up:
```bash
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
```ts
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 <agent>" 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
```bash
pnpm --filter @fusion-plugin-examples/paperclip-runtime test # 46 tests
pnpm --filter @fusion-plugin-examples/paperclip-runtime build
```