Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .workflow/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Plan — Normalize Lionroot Paperclip Fork

**Status:** Approved
**Date:** 2026-03-09

## Steps
1. Inspect the current Paperclip fork state and identify Lionroot-owned deltas.
2. Commit the in-flight OpenClaw adapter changes with coverage.
3. Create `LIONROOT-PATCHES.md` documenting owned files, upstream patch files, and sync notes.
4. Normalize the fork to a canonical fork mainline branch.
5. Fetch and merge latest `upstream/master`, resolving conflicts by preserving documented Lionroot patches.
6. Run targeted verification for adapter behavior and affected server bootstrap surfaces.
7. Review the result and summarize the new durable state.

## Verification
- `pnpm exec vitest run packages/adapters/openclaw/src/server/execute.test.ts`
- relevant `vitest` coverage for affected bootstrap surfaces
- git status clean at the end
31 changes: 31 additions & 0 deletions .workflow/prd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# PRD — Normalize Lionroot Paperclip Fork

**Status:** Approved
**Date:** 2026-03-09

## Goal
Make the vendored Paperclip fork under `command-post/paperclip-server` durable and syncable: preserve Lionroot-owned OpenClaw integration, land any in-flight adapter changes, normalize the fork onto a clear fork mainline, and bring in the latest upstream Paperclip changes without losing local behavior.

## Problem
Paperclip is currently running from a vendor-sync branch with uncommitted local adapter changes. Lionroot-specific behavior exists, but it is not cleanly normalized onto the fork’s mainline. That makes future upstream syncs fragile and obscures which behavior is truly fork-owned.

## Requirements
1. Commit the current OpenClaw adapter working changes with tests.
2. Inventory Lionroot-owned Paperclip deltas versus upstream.
3. Add a maintained patch inventory document for future syncs.
4. Normalize the fork onto a canonical mainline branch.
5. Sync latest upstream Paperclip changes while preserving Lionroot patches.
6. Verify Paperclip still builds/tests for the affected surfaces.
7. Preserve the running OpenClaw adapter semantics used by Command Post.

## Non-Goals
- Refactor Command Post’s Paperclip client unless upstream drift forces it.
- Re-architect Paperclip’s plugin system.
- Upstream the OpenClaw adapter in this task.

## Success Criteria
- Paperclip fork has clean committed Lionroot changes.
- Fork has an explicit patch inventory doc.
- Canonical fork branch is current and deployable.
- Latest upstream changes are merged in.
- Targeted tests for OpenClaw adapter and affected bootstrap surfaces pass.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ WORKDIR /app
COPY --from=deps /app /app
COPY . .
RUN pnpm --filter @paperclipai/ui build
RUN pnpm --filter @paperclipai/server build
RUN test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1)
# Server dist is pre-built in repo; keep the Lionroot Docker workaround until
# the server build is confirmed stable in this image.

FROM base AS production
WORKDIR /app
Expand Down
80 changes: 80 additions & 0 deletions LIONROOT-PATCHES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# LIONROOT-PATCHES.md

Maintained Lionroot delta for the Paperclip fork under `command-post/paperclip-server`.

## 1. Lionroot-owned surface

These files implement the Lionroot OpenClaw webhook adapter and should be treated as Lionroot-owned. Upstream does not provide this adapter. This fork also carries upstream `openclaw_gateway` separately; both adapter types are intentionally supported.

- `packages/adapters/openclaw/package.json`
- `packages/adapters/openclaw/tsconfig.json`
- `packages/adapters/openclaw/src/index.ts`
- `packages/adapters/openclaw/src/cli/format-event.ts`
- `packages/adapters/openclaw/src/cli/index.ts`
- `packages/adapters/openclaw/src/server/execute.ts`
- `packages/adapters/openclaw/src/server/index.ts`
- `packages/adapters/openclaw/src/server/parse.ts`
- `packages/adapters/openclaw/src/server/test.ts`
- `packages/adapters/openclaw/src/server/execute.test.ts`
- `packages/adapters/openclaw/src/ui/build-config.ts`
- `packages/adapters/openclaw/src/ui/index.ts`
- `packages/adapters/openclaw/src/ui/parse-stdout.ts`

### Current adapter semantics to preserve

- Webhook payloads include a top-level `message` field.
- Webhook payloads include a top-level merged `context` object for hook consumers.
- Hook payload context includes `source: "paperclip"` and the computed wake metadata.
- `payloadTemplate.context` can extend the top-level `context`, but computed wake metadata wins on collisions.
- The original nested `paperclip` payload remains present for consumers that depend on the full Paperclip context envelope.

## 2. Structural wiring added by Lionroot

These upstream-owned files must continue to register the Lionroot webhook adapter while also preserving upstream `openclaw_gateway` support.

- `cli/package.json` — keep both workspace deps: `@paperclipai/adapter-openclaw` and `@paperclipai/adapter-openclaw-gateway`
- `cli/src/adapters/registry.ts` — register both adapter types: `openclaw` and `openclaw_gateway`
- `packages/shared/src/constants.ts` — adapter union must include both `"openclaw"` and upstream `"openclaw_gateway"`
- `server/package.json` — keep both server-side workspace deps
- `server/src/adapters/registry.ts` — register both adapter types in server adapter lookup
- `tsconfig.json` — keep both adapter package references in the monorepo project refs
- `ui/package.json` — keep both UI-side workspace deps
- `ui/src/adapters/openclaw/config-fields.tsx`
- `ui/src/adapters/openclaw/index.ts`
- `ui/src/adapters/registry.ts` — register both UI adapters
- `ui/src/components/AgentProperties.tsx` — keep distinct labels for webhook vs gateway adapters
- `ui/src/components/agent-config-primitives.tsx` — document both adapter modes in operator help text
- `ui/src/pages/Agents.tsx` — keep distinct adapter labels in lists
- `ui/src/pages/OrgChart.tsx` — keep distinct adapter labels in org chart cards
- `vitest.config.ts` — include `packages/adapters/openclaw` in the project list so webhook adapter tests run
- `pnpm-lock.yaml` — regenerate from install if conflicts occur

## 3. Upstream patch files

These are small targeted patches in upstream-owned files and are the most likely merge-conflict points during sync.

### `Dockerfile`
Keep the Lionroot Docker workaround that skips the server image build step and relies on pre-built `server/dist` output.

Replay patch:
```diff
-RUN pnpm --filter @paperclipai/server build
-RUN test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1)
+# Server dist is pre-built in repo; keep the Lionroot Docker workaround until
+# the server build is confirmed stable in this image.
```

### `server/src/index.ts`
Preserve the Lionroot loopback behavior that treats `0.0.0.0` as loopback in `isLoopbackHost(...)` so private/local deployment mode works when Paperclip binds all interfaces.

Replay patch:
```diff
-return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1";
+return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1" || normalized === "0.0.0.0";
```

## 4. Mainline policy

- Fork mainline is `master` and should remain the deployable Lionroot branch.
- Future upstream syncs should merge `upstream/master` into fork `master` and resolve only the documented files above.
- If `pnpm-lock.yaml` conflicts, regenerate it from a clean install with the repo's pinned package manager (`pnpm@9.15.4`).
1 change: 1 addition & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@paperclipai/adapter-codex-local": "workspace:*",
"@paperclipai/adapter-cursor-local": "workspace:*",
"@paperclipai/adapter-opencode-local": "workspace:*",
"@paperclipai/adapter-openclaw": "workspace:*",
"@paperclipai/adapter-pi-local": "workspace:*",
"@paperclipai/adapter-openclaw-gateway": "workspace:*",
"@paperclipai/adapter-utils": "workspace:*",
Expand Down
7 changes: 7 additions & 0 deletions cli/src/adapters/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { printClaudeStreamEvent } from "@paperclipai/adapter-claude-local/cli";
import { printCodexStreamEvent } from "@paperclipai/adapter-codex-local/cli";
import { printCursorStreamEvent } from "@paperclipai/adapter-cursor-local/cli";
import { printOpenCodeStreamEvent } from "@paperclipai/adapter-opencode-local/cli";
import { printOpenClawStreamEvent } from "@paperclipai/adapter-openclaw/cli";
import { printPiStreamEvent } from "@paperclipai/adapter-pi-local/cli";
import { printOpenClawGatewayStreamEvent } from "@paperclipai/adapter-openclaw-gateway/cli";
import { processCLIAdapter } from "./process/index.js";
Expand All @@ -23,6 +24,11 @@ const openCodeLocalCLIAdapter: CLIAdapterModule = {
formatStdoutEvent: printOpenCodeStreamEvent,
};

const openclawCLIAdapter: CLIAdapterModule = {
type: "openclaw",
formatStdoutEvent: printOpenClawStreamEvent,
};

const piLocalCLIAdapter: CLIAdapterModule = {
type: "pi_local",
formatStdoutEvent: printPiStreamEvent,
Expand All @@ -43,6 +49,7 @@ const adaptersByType = new Map<string, CLIAdapterModule>(
claudeLocalCLIAdapter,
codexLocalCLIAdapter,
openCodeLocalCLIAdapter,
openclawCLIAdapter,
piLocalCLIAdapter,
cursorLocalCLIAdapter,
openclawGatewayCLIAdapter,
Expand Down
16 changes: 4 additions & 12 deletions packages/adapters/codex-local/src/server/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,10 @@ const CODEX_ROLLOUT_NOISE_RE =
/^\d{4}-\d{2}-\d{2}T[^\s]+\s+ERROR\s+codex_core::rollout::list:\s+state db missing rollout path for thread\s+[a-z0-9-]+$/i;

function stripCodexRolloutNoise(text: string): string {
const parts = text.split(/\r?\n/);
const kept: string[] = [];
for (const part of parts) {
const trimmed = part.trim();
if (!trimmed) {
kept.push(part);
continue;
}
if (CODEX_ROLLOUT_NOISE_RE.test(trimmed)) continue;
kept.push(part);
}
return kept.join("\n");
return text
.split(/\r?\n/)
.filter((line) => !line.trim() || !CODEX_ROLLOUT_NOISE_RE.test(line.trim()))
.join("\n");
}

function firstNonEmptyLine(text: string): string {
Expand Down
57 changes: 57 additions & 0 deletions packages/adapters/openclaw/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# @paperclipai/adapter-openclaw

## 0.2.7

### Patch Changes

- Version bump (patch)
- Updated dependencies
- @paperclipai/adapter-utils@0.2.7

## 0.2.6

### Patch Changes

- Version bump (patch)
- Updated dependencies
- @paperclipai/adapter-utils@0.2.6

## 0.2.5

### Patch Changes

- Version bump (patch)
- Updated dependencies
- @paperclipai/adapter-utils@0.2.5

## 0.2.4

### Patch Changes

- Version bump (patch)
- Updated dependencies
- @paperclipai/adapter-utils@0.2.4

## 0.2.3

### Patch Changes

- Version bump (patch)
- Updated dependencies
- @paperclipai/adapter-utils@0.2.3

## 0.2.2

### Patch Changes

- Version bump (patch)
- Updated dependencies
- @paperclipai/adapter-utils@0.2.2

## 0.2.1

### Patch Changes

- Version bump (patch)
- Updated dependencies
- @paperclipai/adapter-utils@0.2.1
50 changes: 50 additions & 0 deletions packages/adapters/openclaw/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@paperclipai/adapter-openclaw",
"version": "0.2.7",
"type": "module",
"exports": {
".": "./src/index.ts",
"./server": "./src/server/index.ts",
"./ui": "./src/ui/index.ts",
"./cli": "./src/cli/index.ts"
},
"publishConfig": {
"access": "public",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./server": {
"types": "./dist/server/index.d.ts",
"import": "./dist/server/index.js"
},
"./ui": {
"types": "./dist/ui/index.d.ts",
"import": "./dist/ui/index.js"
},
"./cli": {
"types": "./dist/cli/index.d.ts",
"import": "./dist/cli/index.js"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@paperclipai/adapter-utils": "workspace:*",
"picocolors": "^1.1.1"
},
"devDependencies": {
"@types/node": "^24.6.0",
"typescript": "^5.7.3"
}
}
18 changes: 18 additions & 0 deletions packages/adapters/openclaw/src/cli/format-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pc from "picocolors";

export function printOpenClawStreamEvent(raw: string, debug: boolean): void {
const line = raw.trim();
if (!line) return;

if (!debug) {
console.log(line);
return;
}

if (line.startsWith("[openclaw]")) {
console.log(pc.cyan(line));
return;
}

console.log(pc.gray(line));
}
1 change: 1 addition & 0 deletions packages/adapters/openclaw/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { printOpenClawStreamEvent } from "./format-event.js";
27 changes: 27 additions & 0 deletions packages/adapters/openclaw/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const type = "openclaw";
export const label = "OpenClaw";

export const models: { id: string; label: string }[] = [];

export const agentConfigurationDoc = `# openclaw agent configuration

Adapter: openclaw

Use when:
- You run an OpenClaw agent remotely and wake it via webhook.
- You want Paperclip heartbeat/task events delivered over HTTP.

Don't use when:
- You need local CLI execution inside Paperclip (use claude_local/codex_local/opencode_local/process).
- The OpenClaw endpoint is not reachable from the Paperclip server.

Core fields:
- url (string, required): OpenClaw webhook endpoint URL
- method (string, optional): HTTP method, default POST
- headers (object, optional): extra HTTP headers for webhook calls
- webhookAuthHeader (string, optional): Authorization header value if your endpoint requires auth
- payloadTemplate (object, optional): additional JSON payload fields merged into each wake payload

Operational fields:
- timeoutSec (number, optional): request timeout in seconds (default 30)
`;
Loading