@paperclipai/server 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/app.d.ts.map +1 -1
  2. package/dist/app.js +15 -6
  3. package/dist/app.js.map +1 -1
  4. package/dist/routes/access.d.ts.map +1 -1
  5. package/dist/routes/access.js +3 -2
  6. package/dist/routes/access.js.map +1 -1
  7. package/package.json +10 -8
  8. package/skills/create-agent-adapter/SKILL.md +718 -0
  9. package/skills/paperclip/SKILL.md +213 -0
  10. package/skills/paperclip/references/api-reference.md +540 -0
  11. package/skills/paperclip-create-agent/SKILL.md +139 -0
  12. package/skills/paperclip-create-agent/references/api-reference.md +95 -0
  13. package/skills/para-memory-files/SKILL.md +104 -0
  14. package/skills/para-memory-files/references/schemas.md +35 -0
  15. package/ui-dist/android-chrome-192x192.png +0 -0
  16. package/ui-dist/android-chrome-512x512.png +0 -0
  17. package/ui-dist/apple-touch-icon.png +0 -0
  18. package/ui-dist/assets/apl-B4CMkyY2.js +1 -0
  19. package/ui-dist/assets/asciiarmor-Df11BRmG.js +1 -0
  20. package/ui-dist/assets/asn1-EdZsLKOL.js +1 -0
  21. package/ui-dist/assets/asterisk-B-8jnY81.js +1 -0
  22. package/ui-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  23. package/ui-dist/assets/clike-B9uivgTg.js +1 -0
  24. package/ui-dist/assets/clojure-BMjYHr_A.js +1 -0
  25. package/ui-dist/assets/cmake-BQqOBYOt.js +1 -0
  26. package/ui-dist/assets/cobol-CWcv1MsR.js +1 -0
  27. package/ui-dist/assets/coffeescript-S37ZYGWr.js +1 -0
  28. package/ui-dist/assets/commonlisp-DBKNyK5s.js +1 -0
  29. package/ui-dist/assets/crystal-SjHAIU92.js +1 -0
  30. package/ui-dist/assets/css-BnMrqG3P.js +1 -0
  31. package/ui-dist/assets/cypher-C_CwsFkJ.js +1 -0
  32. package/ui-dist/assets/d-pRatUO7H.js +1 -0
  33. package/ui-dist/assets/diff-DbItnlRl.js +1 -0
  34. package/ui-dist/assets/dockerfile-BKs6k2Af.js +1 -0
  35. package/ui-dist/assets/dtd-DF_7sFjM.js +1 -0
  36. package/ui-dist/assets/dylan-DwRh75JA.js +1 -0
  37. package/ui-dist/assets/ebnf-CDyGwa7X.js +1 -0
  38. package/ui-dist/assets/ecl-Cabwm37j.js +1 -0
  39. package/ui-dist/assets/eiffel-CnydiIhH.js +1 -0
  40. package/ui-dist/assets/elm-vLlmbW-K.js +1 -0
  41. package/ui-dist/assets/erlang-BNw1qcRV.js +1 -0
  42. package/ui-dist/assets/factor-kuTfRLto.js +1 -0
  43. package/ui-dist/assets/fcl-Kvtd6kyn.js +1 -0
  44. package/ui-dist/assets/forth-Ffai-XNe.js +1 -0
  45. package/ui-dist/assets/fortran-DYz_wnZ1.js +1 -0
  46. package/ui-dist/assets/gas-Bneqetm1.js +1 -0
  47. package/ui-dist/assets/gherkin-heZmZLOM.js +1 -0
  48. package/ui-dist/assets/groovy-D9Dt4D0W.js +1 -0
  49. package/ui-dist/assets/haskell-Cw1EW3IL.js +1 -0
  50. package/ui-dist/assets/haxe-H-WmDvRZ.js +1 -0
  51. package/ui-dist/assets/http-DBlCnlav.js +1 -0
  52. package/ui-dist/assets/idl-BEugSyMb.js +1 -0
  53. package/ui-dist/assets/index-B2dK2ySs.js +1 -0
  54. package/ui-dist/assets/index-BEMuI8M5.js +1 -0
  55. package/ui-dist/assets/index-BGLYk1vA.css +1 -0
  56. package/ui-dist/assets/index-BO1f7tXb.js +2 -0
  57. package/ui-dist/assets/index-Bu_mSzdo.js +1 -0
  58. package/ui-dist/assets/index-CGaEmHQp.js +1 -0
  59. package/ui-dist/assets/index-CPMrn6B_.js +1 -0
  60. package/ui-dist/assets/index-Cbiiu7n-.js +13 -0
  61. package/ui-dist/assets/index-CiTr7s1M.js +1 -0
  62. package/ui-dist/assets/index-D-wHRcSa.js +1 -0
  63. package/ui-dist/assets/index-D2ePBvbp.js +7 -0
  64. package/ui-dist/assets/index-D6hplTiT.js +1 -0
  65. package/ui-dist/assets/index-DLOEShXw.js +6 -0
  66. package/ui-dist/assets/index-DWQXbuog.js +3 -0
  67. package/ui-dist/assets/index-DX4_5XJS.js +1 -0
  68. package/ui-dist/assets/index-DYDNDnEW.js +1 -0
  69. package/ui-dist/assets/index-DZGJY7BE.js +1 -0
  70. package/ui-dist/assets/index-DjVA9sNG.js +1 -0
  71. package/ui-dist/assets/index-DkA0X4Hn.js +856 -0
  72. package/ui-dist/assets/index-DsE_dmbp.js +1 -0
  73. package/ui-dist/assets/index-WgytcSxP.js +1 -0
  74. package/ui-dist/assets/index-oqPcx6xl.js +1 -0
  75. package/ui-dist/assets/index-q3FDV08c.js +1 -0
  76. package/ui-dist/assets/index-vd8vTg9y.js +1 -0
  77. package/ui-dist/assets/javascript-iXu5QeM3.js +1 -0
  78. package/ui-dist/assets/julia-DuME0IfC.js +1 -0
  79. package/ui-dist/assets/livescript-BwQOo05w.js +1 -0
  80. package/ui-dist/assets/lua-BgMRiT3U.js +1 -0
  81. package/ui-dist/assets/mathematica-DTrFuWx2.js +1 -0
  82. package/ui-dist/assets/mbox-CNhZ1qSd.js +1 -0
  83. package/ui-dist/assets/mirc-CjQqDB4T.js +1 -0
  84. package/ui-dist/assets/mllike-CXdrOF99.js +1 -0
  85. package/ui-dist/assets/modelica-Dc1JOy9r.js +1 -0
  86. package/ui-dist/assets/mscgen-BA5vi2Kp.js +1 -0
  87. package/ui-dist/assets/mumps-BT43cFF4.js +1 -0
  88. package/ui-dist/assets/nginx-DdIZxoE0.js +1 -0
  89. package/ui-dist/assets/nsis-LdVXkNf5.js +1 -0
  90. package/ui-dist/assets/ntriples-BfvgReVJ.js +1 -0
  91. package/ui-dist/assets/octave-Ck1zUtKM.js +1 -0
  92. package/ui-dist/assets/oz-BzwKVEFT.js +1 -0
  93. package/ui-dist/assets/pascal--L3eBynH.js +1 -0
  94. package/ui-dist/assets/perl-CdXCOZ3F.js +1 -0
  95. package/ui-dist/assets/pig-CevX1Tat.js +1 -0
  96. package/ui-dist/assets/powershell-CFHJl5sT.js +1 -0
  97. package/ui-dist/assets/properties-C78fOPTZ.js +1 -0
  98. package/ui-dist/assets/protobuf-ChK-085T.js +1 -0
  99. package/ui-dist/assets/pug-DeIclll2.js +1 -0
  100. package/ui-dist/assets/puppet-DMA9R1ak.js +1 -0
  101. package/ui-dist/assets/python-BuPzkPfP.js +1 -0
  102. package/ui-dist/assets/q-pXgVlZs6.js +1 -0
  103. package/ui-dist/assets/r-B6wPVr8A.js +1 -0
  104. package/ui-dist/assets/rpm-CTu-6PCP.js +1 -0
  105. package/ui-dist/assets/ruby-B2Rjki9n.js +1 -0
  106. package/ui-dist/assets/sas-B4kiWyti.js +1 -0
  107. package/ui-dist/assets/scheme-C41bIUwD.js +1 -0
  108. package/ui-dist/assets/shell-CjFT_Tl9.js +1 -0
  109. package/ui-dist/assets/sieve-C3Gn_uJK.js +1 -0
  110. package/ui-dist/assets/simple-mode-GW_nhZxv.js +1 -0
  111. package/ui-dist/assets/smalltalk-CnHTOXQT.js +1 -0
  112. package/ui-dist/assets/solr-DehyRSwq.js +1 -0
  113. package/ui-dist/assets/sparql-DkYu6x3z.js +1 -0
  114. package/ui-dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  115. package/ui-dist/assets/sql-D0XecflT.js +1 -0
  116. package/ui-dist/assets/stex-C3f8Ysf7.js +1 -0
  117. package/ui-dist/assets/stylus-B533Al4x.js +1 -0
  118. package/ui-dist/assets/swift-BzpIVaGY.js +1 -0
  119. package/ui-dist/assets/tcl-DVfN8rqt.js +1 -0
  120. package/ui-dist/assets/textile-CnDTJFAw.js +1 -0
  121. package/ui-dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  122. package/ui-dist/assets/tiki-DGYXhP31.js +1 -0
  123. package/ui-dist/assets/toml-Bm5Em-hy.js +1 -0
  124. package/ui-dist/assets/troff-wAsdV37c.js +1 -0
  125. package/ui-dist/assets/ttcn-CfJYG6tj.js +1 -0
  126. package/ui-dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  127. package/ui-dist/assets/turtle-B1tBg_DP.js +1 -0
  128. package/ui-dist/assets/vb-CmGdzxic.js +1 -0
  129. package/ui-dist/assets/vbscript-BuJXcnF6.js +1 -0
  130. package/ui-dist/assets/velocity-D8B20fx6.js +1 -0
  131. package/ui-dist/assets/verilog-C6RDOZhf.js +1 -0
  132. package/ui-dist/assets/vhdl-lSbBsy5d.js +1 -0
  133. package/ui-dist/assets/webidl-ZXfAyPTL.js +1 -0
  134. package/ui-dist/assets/xquery-DzFWVndE.js +1 -0
  135. package/ui-dist/assets/yacas-BJ4BC0dw.js +1 -0
  136. package/ui-dist/assets/z80-Hz9HOZM7.js +1 -0
  137. package/ui-dist/favicon-16x16.png +0 -0
  138. package/ui-dist/favicon-32x32.png +0 -0
  139. package/ui-dist/favicon.ico +0 -0
  140. package/ui-dist/favicon.svg +9 -0
  141. package/ui-dist/index.html +44 -0
  142. package/ui-dist/site.webmanifest +19 -0
@@ -0,0 +1,718 @@
1
+ ---
2
+ name: create-agent-adapter
3
+ description: >
4
+ Technical guide for creating a new Paperclip agent adapter. Use when building
5
+ a new adapter package, adding support for a new AI coding tool (e.g. a new
6
+ CLI agent, API-based agent, or custom process), or when modifying the adapter
7
+ system. Covers the required interfaces, module structure, registration points,
8
+ and conventions derived from the existing claude-local and codex-local adapters.
9
+ ---
10
+
11
+ # Creating a Paperclip Agent Adapter
12
+
13
+ An adapter bridges Paperclip's orchestration layer to a specific AI agent runtime (Claude Code, Codex CLI, a custom process, an HTTP endpoint, etc.). Each adapter is a self-contained package that provides implementations for **three consumers**: the server, the UI, and the CLI.
14
+
15
+ ---
16
+
17
+ ## 1. Architecture Overview
18
+
19
+ ```
20
+ packages/adapters/<name>/
21
+ src/
22
+ index.ts # Shared metadata (type, label, models, agentConfigurationDoc)
23
+ server/
24
+ index.ts # Server exports: execute, sessionCodec, parse helpers
25
+ execute.ts # Core execution logic (AdapterExecutionContext -> AdapterExecutionResult)
26
+ parse.ts # Stdout/result parsing for the agent's output format
27
+ ui/
28
+ index.ts # UI exports: parseStdoutLine, buildConfig
29
+ parse-stdout.ts # Line-by-line stdout -> TranscriptEntry[] for the run viewer
30
+ build-config.ts # CreateConfigValues -> adapterConfig JSON for agent creation form
31
+ cli/
32
+ index.ts # CLI exports: formatStdoutEvent
33
+ format-event.ts # Colored terminal output for `paperclipai run --watch`
34
+ package.json
35
+ tsconfig.json
36
+ ```
37
+
38
+ Three separate registries consume adapter modules:
39
+
40
+ | Registry | Location | Interface |
41
+ |----------|----------|-----------|
42
+ | Server | `server/src/adapters/registry.ts` | `ServerAdapterModule` |
43
+ | UI | `ui/src/adapters/registry.ts` | `UIAdapterModule` |
44
+ | CLI | `cli/src/adapters/registry.ts` | `CLIAdapterModule` |
45
+
46
+ ---
47
+
48
+ ## 2. Shared Types (`@paperclipai/adapter-utils`)
49
+
50
+ All adapter interfaces live in `packages/adapter-utils/src/types.ts`. Import from `@paperclipai/adapter-utils` (types) or `@paperclipai/adapter-utils/server-utils` (runtime helpers).
51
+
52
+ ### Core Interfaces
53
+
54
+ ```ts
55
+ // The execute function signature — every adapter must implement this
56
+ interface AdapterExecutionContext {
57
+ runId: string;
58
+ agent: AdapterAgent; // { id, companyId, name, adapterType, adapterConfig }
59
+ runtime: AdapterRuntime; // { sessionId, sessionParams, sessionDisplayId, taskKey }
60
+ config: Record<string, unknown>; // The agent's adapterConfig blob
61
+ context: Record<string, unknown>; // Runtime context (taskId, wakeReason, approvalId, etc.)
62
+ onLog: (stream: "stdout" | "stderr", chunk: string) => Promise<void>;
63
+ onMeta?: (meta: AdapterInvocationMeta) => Promise<void>;
64
+ authToken?: string;
65
+ }
66
+
67
+ interface AdapterExecutionResult {
68
+ exitCode: number | null;
69
+ signal: string | null;
70
+ timedOut: boolean;
71
+ errorMessage?: string | null;
72
+ usage?: UsageSummary; // { inputTokens, outputTokens, cachedInputTokens? }
73
+ sessionId?: string | null; // Legacy — prefer sessionParams
74
+ sessionParams?: Record<string, unknown> | null; // Opaque session state persisted between runs
75
+ sessionDisplayId?: string | null;
76
+ provider?: string | null; // "anthropic", "openai", etc.
77
+ model?: string | null;
78
+ costUsd?: number | null;
79
+ resultJson?: Record<string, unknown> | null;
80
+ summary?: string | null; // Human-readable summary of what the agent did
81
+ clearSession?: boolean; // true = tell Paperclip to forget the stored session
82
+ }
83
+
84
+ interface AdapterSessionCodec {
85
+ deserialize(raw: unknown): Record<string, unknown> | null;
86
+ serialize(params: Record<string, unknown> | null): Record<string, unknown> | null;
87
+ getDisplayId?(params: Record<string, unknown> | null): string | null;
88
+ }
89
+ ```
90
+
91
+ ### Module Interfaces
92
+
93
+ ```ts
94
+ // Server — registered in server/src/adapters/registry.ts
95
+ interface ServerAdapterModule {
96
+ type: string;
97
+ execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult>;
98
+ testEnvironment(ctx: AdapterEnvironmentTestContext): Promise<AdapterEnvironmentTestResult>;
99
+ sessionCodec?: AdapterSessionCodec;
100
+ supportsLocalAgentJwt?: boolean;
101
+ models?: { id: string; label: string }[];
102
+ agentConfigurationDoc?: string;
103
+ }
104
+
105
+ // UI — registered in ui/src/adapters/registry.ts
106
+ interface UIAdapterModule {
107
+ type: string;
108
+ label: string;
109
+ parseStdoutLine: (line: string, ts: string) => TranscriptEntry[];
110
+ ConfigFields: ComponentType<AdapterConfigFieldsProps>;
111
+ buildAdapterConfig: (values: CreateConfigValues) => Record<string, unknown>;
112
+ }
113
+
114
+ // CLI — registered in cli/src/adapters/registry.ts
115
+ interface CLIAdapterModule {
116
+ type: string;
117
+ formatStdoutEvent: (line: string, debug: boolean) => void;
118
+ }
119
+ ```
120
+
121
+ ---
122
+
123
+ ## 2.1 Adapter Environment Test Contract
124
+
125
+ Every server adapter must implement `testEnvironment(...)`. This powers the board UI "Test environment" button in agent configuration.
126
+
127
+ ```ts
128
+ type AdapterEnvironmentCheckLevel = "info" | "warn" | "error";
129
+ type AdapterEnvironmentTestStatus = "pass" | "warn" | "fail";
130
+
131
+ interface AdapterEnvironmentCheck {
132
+ code: string;
133
+ level: AdapterEnvironmentCheckLevel;
134
+ message: string;
135
+ detail?: string | null;
136
+ hint?: string | null;
137
+ }
138
+
139
+ interface AdapterEnvironmentTestResult {
140
+ adapterType: string;
141
+ status: AdapterEnvironmentTestStatus;
142
+ checks: AdapterEnvironmentCheck[];
143
+ testedAt: string; // ISO timestamp
144
+ }
145
+
146
+ interface AdapterEnvironmentTestContext {
147
+ companyId: string;
148
+ adapterType: string;
149
+ config: Record<string, unknown>; // runtime-resolved adapterConfig
150
+ }
151
+ ```
152
+
153
+ Guidelines:
154
+
155
+ - Return structured diagnostics, never throw for expected findings.
156
+ - Use `error` for invalid/unusable runtime setup (bad cwd, missing command, invalid URL).
157
+ - Use `warn` for non-blocking but important situations.
158
+ - Use `info` for successful checks and context.
159
+
160
+ Severity policy is product-critical: warnings are not save blockers.
161
+ Example: for `claude_local`, detected `ANTHROPIC_API_KEY` must be a `warn`, not an `error`, because Claude can still run (it just uses API-key auth instead of subscription auth).
162
+
163
+ ---
164
+
165
+ ## 3. Step-by-Step: Creating a New Adapter
166
+
167
+ ### 3.1 Create the Package
168
+
169
+ ```
170
+ packages/adapters/<name>/
171
+ package.json
172
+ tsconfig.json
173
+ src/
174
+ index.ts
175
+ server/index.ts
176
+ server/execute.ts
177
+ server/parse.ts
178
+ ui/index.ts
179
+ ui/parse-stdout.ts
180
+ ui/build-config.ts
181
+ cli/index.ts
182
+ cli/format-event.ts
183
+ ```
184
+
185
+ **package.json** — must use the four-export convention:
186
+
187
+ ```json
188
+ {
189
+ "name": "@paperclipai/adapter-<name>",
190
+ "version": "0.0.1",
191
+ "private": true,
192
+ "type": "module",
193
+ "exports": {
194
+ ".": "./src/index.ts",
195
+ "./server": "./src/server/index.ts",
196
+ "./ui": "./src/ui/index.ts",
197
+ "./cli": "./src/cli/index.ts"
198
+ },
199
+ "dependencies": {
200
+ "@paperclipai/adapter-utils": "workspace:*",
201
+ "picocolors": "^1.1.1"
202
+ },
203
+ "devDependencies": {
204
+ "typescript": "^5.7.3"
205
+ }
206
+ }
207
+ ```
208
+
209
+ ### 3.2 Root `index.ts` — Adapter Metadata
210
+
211
+ This file is imported by **all three** consumers (server, UI, CLI). Keep it dependency-free (no Node APIs, no React).
212
+
213
+ ```ts
214
+ export const type = "my_agent"; // snake_case, globally unique
215
+ export const label = "My Agent (local)";
216
+
217
+ export const models = [
218
+ { id: "model-a", label: "Model A" },
219
+ { id: "model-b", label: "Model B" },
220
+ ];
221
+
222
+ export const agentConfigurationDoc = `# my_agent agent configuration
223
+ ...document all config fields here...
224
+ `;
225
+ ```
226
+
227
+ **Required exports:**
228
+ - `type` — the adapter type key, stored in `agents.adapter_type`
229
+ - `label` — human-readable name for the UI
230
+ - `models` — available model options for the agent creation form
231
+ - `agentConfigurationDoc` — markdown describing all `adapterConfig` fields (used by LLM agents configuring other agents)
232
+
233
+ **Writing `agentConfigurationDoc` as routing logic:**
234
+
235
+ The `agentConfigurationDoc` is read by LLM agents (including Paperclip agents that create other agents). Write it as **routing logic**, not marketing copy. Include concrete "use when" and "don't use when" guidance so an LLM can decide whether this adapter is appropriate for a given task.
236
+
237
+ ```ts
238
+ export const agentConfigurationDoc = `# my_agent agent configuration
239
+
240
+ Adapter: my_agent
241
+
242
+ Use when:
243
+ - The agent needs to run MyAgent CLI locally on the host machine
244
+ - You need session persistence across runs (MyAgent supports thread resumption)
245
+ - The task requires MyAgent-specific tools (e.g. web search, code execution)
246
+
247
+ Don't use when:
248
+ - You need a simple one-shot script execution (use the "process" adapter instead)
249
+ - The agent doesn't need conversational context between runs (process adapter is simpler)
250
+ - MyAgent CLI is not installed on the host
251
+
252
+ Core fields:
253
+ - cwd (string, required): absolute working directory for the agent process
254
+ ...
255
+ `;
256
+ ```
257
+
258
+ Adding explicit negative cases improves adapter selection accuracy. One concrete anti-pattern is worth more than three paragraphs of description.
259
+
260
+ ### 3.3 Server Module
261
+
262
+ #### `server/execute.ts` — The Core
263
+
264
+ This is the most important file. It receives an `AdapterExecutionContext` and must return an `AdapterExecutionResult`.
265
+
266
+ **Required behavior:**
267
+
268
+ 1. **Read config** — extract typed values from `ctx.config` using helpers (`asString`, `asNumber`, `asBoolean`, `asStringArray`, `parseObject` from `@paperclipai/adapter-utils/server-utils`)
269
+ 2. **Build environment** — call `buildPaperclipEnv(agent)` then layer in `PAPERCLIP_RUN_ID`, context vars (`PAPERCLIP_TASK_ID`, `PAPERCLIP_WAKE_REASON`, `PAPERCLIP_WAKE_COMMENT_ID`, `PAPERCLIP_APPROVAL_ID`, `PAPERCLIP_APPROVAL_STATUS`, `PAPERCLIP_LINKED_ISSUE_IDS`), user env overrides, and auth token
270
+ 3. **Resolve session** — check `runtime.sessionParams` / `runtime.sessionId` for an existing session; validate it's compatible (e.g. same cwd); decide whether to resume or start fresh
271
+ 4. **Render prompt** — use `renderTemplate(template, data)` with the template variables: `agentId`, `companyId`, `runId`, `company`, `agent`, `run`, `context`
272
+ 5. **Call onMeta** — emit adapter invocation metadata before spawning the process
273
+ 6. **Spawn the process** — use `runChildProcess()` for CLI-based agents or `fetch()` for HTTP-based agents
274
+ 7. **Parse output** — convert the agent's stdout into structured data (session id, usage, summary, errors)
275
+ 8. **Handle session errors** — if resume fails with "unknown session", retry with a fresh session and set `clearSession: true`
276
+ 9. **Return AdapterExecutionResult** — populate all fields the agent runtime supports
277
+
278
+ **Environment variables the server always injects:**
279
+
280
+ | Variable | Source |
281
+ |----------|--------|
282
+ | `PAPERCLIP_AGENT_ID` | `agent.id` |
283
+ | `PAPERCLIP_COMPANY_ID` | `agent.companyId` |
284
+ | `PAPERCLIP_API_URL` | Server's own URL |
285
+ | `PAPERCLIP_RUN_ID` | Current run id |
286
+ | `PAPERCLIP_TASK_ID` | `context.taskId` or `context.issueId` |
287
+ | `PAPERCLIP_WAKE_REASON` | `context.wakeReason` |
288
+ | `PAPERCLIP_WAKE_COMMENT_ID` | `context.wakeCommentId` or `context.commentId` |
289
+ | `PAPERCLIP_APPROVAL_ID` | `context.approvalId` |
290
+ | `PAPERCLIP_APPROVAL_STATUS` | `context.approvalStatus` |
291
+ | `PAPERCLIP_LINKED_ISSUE_IDS` | `context.issueIds` (comma-separated) |
292
+ | `PAPERCLIP_API_KEY` | `authToken` (if no explicit key in config) |
293
+
294
+ #### `server/parse.ts` — Output Parser
295
+
296
+ Parse the agent's stdout format into structured data. Must handle:
297
+
298
+ - **Session identification** — extract session/thread ID from init events
299
+ - **Usage tracking** — extract token counts (input, output, cached)
300
+ - **Cost tracking** — extract cost if available
301
+ - **Summary extraction** — pull the agent's final text response
302
+ - **Error detection** — identify error states, extract error messages
303
+ - **Unknown session detection** — export an `is<Agent>UnknownSessionError()` function for retry logic
304
+
305
+ **Treat agent output as untrusted.** The stdout you're parsing comes from an LLM-driven process that may have executed arbitrary tool calls, fetched external content, or been influenced by prompt injection in the files it read. Parse defensively:
306
+ - Never `eval()` or dynamically execute anything from output
307
+ - Use safe extraction helpers (`asString`, `asNumber`, `parseJson`) — they return fallbacks on unexpected types
308
+ - Validate session IDs and other structured data before passing them through
309
+ - If output contains URLs, file paths, or commands, do not act on them in the adapter — just record them
310
+
311
+ #### `server/index.ts` — Server Exports
312
+
313
+ ```ts
314
+ export { execute } from "./execute.js";
315
+ export { testEnvironment } from "./test.js";
316
+ export { parseMyAgentOutput, isMyAgentUnknownSessionError } from "./parse.js";
317
+
318
+ // Session codec — required for session persistence
319
+ export const sessionCodec: AdapterSessionCodec = {
320
+ deserialize(raw) { /* raw DB JSON -> typed params or null */ },
321
+ serialize(params) { /* typed params -> JSON for DB storage */ },
322
+ getDisplayId(params) { /* -> human-readable session id string */ },
323
+ };
324
+ ```
325
+
326
+ #### `server/test.ts` — Environment Diagnostics
327
+
328
+ Implement adapter-specific preflight checks used by the UI test button.
329
+
330
+ Minimum expectations:
331
+
332
+ 1. Validate required config primitives (paths, commands, URLs, auth assumptions)
333
+ 2. Return check objects with deterministic `code` values
334
+ 3. Map severity consistently (`info` / `warn` / `error`)
335
+ 4. Compute final status:
336
+ - `fail` if any `error`
337
+ - `warn` if no errors and at least one warning
338
+ - `pass` otherwise
339
+
340
+ This operation should be lightweight and side-effect free.
341
+
342
+ ### 3.4 UI Module
343
+
344
+ #### `ui/parse-stdout.ts` — Transcript Parser
345
+
346
+ Converts individual stdout lines into `TranscriptEntry[]` for the run detail viewer. Must handle the agent's streaming output format and produce entries of these kinds:
347
+
348
+ - `init` — model/session initialization
349
+ - `assistant` — agent text responses
350
+ - `thinking` — agent thinking/reasoning (if supported)
351
+ - `tool_call` — tool invocations with name and input
352
+ - `tool_result` — tool results with content and error flag
353
+ - `user` — user messages in the conversation
354
+ - `result` — final result with usage stats
355
+ - `stdout` — fallback for unparseable lines
356
+
357
+ ```ts
358
+ export function parseMyAgentStdoutLine(line: string, ts: string): TranscriptEntry[] {
359
+ // Parse JSON line, map to appropriate TranscriptEntry kind(s)
360
+ // Return [{ kind: "stdout", ts, text: line }] as fallback
361
+ }
362
+ ```
363
+
364
+ #### `ui/build-config.ts` — Config Builder
365
+
366
+ Converts the UI form's `CreateConfigValues` into the `adapterConfig` JSON blob stored on the agent.
367
+
368
+ ```ts
369
+ export function buildMyAgentConfig(v: CreateConfigValues): Record<string, unknown> {
370
+ const ac: Record<string, unknown> = {};
371
+ if (v.cwd) ac.cwd = v.cwd;
372
+ if (v.promptTemplate) ac.promptTemplate = v.promptTemplate;
373
+ if (v.model) ac.model = v.model;
374
+ ac.timeoutSec = 0;
375
+ ac.graceSec = 15;
376
+ // ... adapter-specific fields
377
+ return ac;
378
+ }
379
+ ```
380
+
381
+ #### UI Config Fields Component
382
+
383
+ Create `ui/src/adapters/<name>/config-fields.tsx` with a React component implementing `AdapterConfigFieldsProps`. This renders adapter-specific form fields in the agent creation/edit form.
384
+
385
+ Use the shared primitives from `ui/src/components/agent-config-primitives`:
386
+ - `Field` — labeled form field wrapper
387
+ - `ToggleField` — boolean toggle with label and hint
388
+ - `DraftInput` — text input with draft/commit behavior
389
+ - `DraftNumberInput` — number input with draft/commit behavior
390
+ - `help` — standard hint text for common fields
391
+
392
+ The component must support both `create` mode (using `values`/`set`) and `edit` mode (using `config`/`eff`/`mark`).
393
+
394
+ ### 3.5 CLI Module
395
+
396
+ #### `cli/format-event.ts` — Terminal Formatter
397
+
398
+ Pretty-prints stdout lines for `paperclipai run --watch`. Use `picocolors` for coloring.
399
+
400
+ ```ts
401
+ import pc from "picocolors";
402
+
403
+ export function printMyAgentStreamEvent(raw: string, debug: boolean): void {
404
+ // Parse JSON line from agent stdout
405
+ // Print colored output: blue for system, green for assistant, yellow for tools
406
+ // In debug mode, print unrecognized lines in gray
407
+ }
408
+ ```
409
+
410
+ ---
411
+
412
+ ## 4. Registration Checklist
413
+
414
+ After creating the adapter package, register it in all three consumers:
415
+
416
+ ### 4.1 Server Registry (`server/src/adapters/registry.ts`)
417
+
418
+ ```ts
419
+ import { execute as myExecute, sessionCodec as mySessionCodec } from "@paperclipai/adapter-my-agent/server";
420
+ import { agentConfigurationDoc as myDoc, models as myModels } from "@paperclipai/adapter-my-agent";
421
+
422
+ const myAgentAdapter: ServerAdapterModule = {
423
+ type: "my_agent",
424
+ execute: myExecute,
425
+ sessionCodec: mySessionCodec,
426
+ models: myModels,
427
+ supportsLocalAgentJwt: true, // true if agent can use Paperclip API
428
+ agentConfigurationDoc: myDoc,
429
+ };
430
+
431
+ // Add to the adaptersByType map
432
+ const adaptersByType = new Map<string, ServerAdapterModule>(
433
+ [..., myAgentAdapter].map((a) => [a.type, a]),
434
+ );
435
+ ```
436
+
437
+ ### 4.2 UI Registry (`ui/src/adapters/registry.ts`)
438
+
439
+ ```ts
440
+ import { myAgentUIAdapter } from "./my-agent";
441
+
442
+ const adaptersByType = new Map<string, UIAdapterModule>(
443
+ [..., myAgentUIAdapter].map((a) => [a.type, a]),
444
+ );
445
+ ```
446
+
447
+ With `ui/src/adapters/my-agent/index.ts`:
448
+
449
+ ```ts
450
+ import type { UIAdapterModule } from "../types";
451
+ import { parseMyAgentStdoutLine } from "@paperclipai/adapter-my-agent/ui";
452
+ import { MyAgentConfigFields } from "./config-fields";
453
+ import { buildMyAgentConfig } from "@paperclipai/adapter-my-agent/ui";
454
+
455
+ export const myAgentUIAdapter: UIAdapterModule = {
456
+ type: "my_agent",
457
+ label: "My Agent",
458
+ parseStdoutLine: parseMyAgentStdoutLine,
459
+ ConfigFields: MyAgentConfigFields,
460
+ buildAdapterConfig: buildMyAgentConfig,
461
+ };
462
+ ```
463
+
464
+ ### 4.3 CLI Registry (`cli/src/adapters/registry.ts`)
465
+
466
+ ```ts
467
+ import { printMyAgentStreamEvent } from "@paperclipai/adapter-my-agent/cli";
468
+
469
+ const myAgentCLIAdapter: CLIAdapterModule = {
470
+ type: "my_agent",
471
+ formatStdoutEvent: printMyAgentStreamEvent,
472
+ };
473
+
474
+ // Add to the adaptersByType map
475
+ ```
476
+
477
+ ---
478
+
479
+ ## 5. Session Management — Designing for Long Runs
480
+
481
+ Sessions allow agents to maintain conversation context across runs. The system is **codec-based** — each adapter defines how to serialize/deserialize its session state.
482
+
483
+ **Design for long runs from the start.** Treat session reuse as the default primitive, not an optimization to add later. An agent working on an issue may be woken dozens of times — for the initial assignment, approval callbacks, re-assignments, manual nudges. Each wake should resume the existing conversation so the agent retains full context about what it has already done, what files it has read, and what decisions it has made. Starting fresh each time wastes tokens on re-reading the same files and risks contradictory decisions.
484
+
485
+ **Key concepts:**
486
+ - `sessionParams` is an opaque `Record<string, unknown>` stored in the DB per task
487
+ - The adapter's `sessionCodec.serialize()` converts execution result data to storable params
488
+ - `sessionCodec.deserialize()` converts stored params back for the next run
489
+ - `sessionCodec.getDisplayId()` extracts a human-readable session ID for the UI
490
+ - **cwd-aware resume**: if the session was created in a different cwd than the current config, skip resuming (prevents cross-project session contamination)
491
+ - **Unknown session retry**: if resume fails with a "session not found" error, retry with a fresh session and return `clearSession: true` so Paperclip wipes the stale session
492
+
493
+ If the agent runtime supports any form of context compaction or conversation compression (e.g. Claude Code's automatic context management, or Codex's `previous_response_id` chaining), lean on it. Adapters that support session resume get compaction for free — the agent runtime handles context window management internally across resumes.
494
+
495
+ **Pattern** (from both claude-local and codex-local):
496
+
497
+ ```ts
498
+ const canResumeSession =
499
+ runtimeSessionId.length > 0 &&
500
+ (runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(cwd));
501
+ const sessionId = canResumeSession ? runtimeSessionId : null;
502
+
503
+ // ... run attempt ...
504
+
505
+ // If resume failed with unknown session, retry fresh
506
+ if (sessionId && !proc.timedOut && exitCode !== 0 && isUnknownSessionError(output)) {
507
+ const retry = await runAttempt(null);
508
+ return toResult(retry, { clearSessionOnMissingSession: true });
509
+ }
510
+ ```
511
+
512
+ ---
513
+
514
+ ## 6. Server-Utils Helpers
515
+
516
+ Import from `@paperclipai/adapter-utils/server-utils`:
517
+
518
+ | Helper | Purpose |
519
+ |--------|---------|
520
+ | `asString(val, fallback)` | Safe string extraction |
521
+ | `asNumber(val, fallback)` | Safe number extraction |
522
+ | `asBoolean(val, fallback)` | Safe boolean extraction |
523
+ | `asStringArray(val)` | Safe string array extraction |
524
+ | `parseObject(val)` | Safe `Record<string, unknown>` extraction |
525
+ | `parseJson(str)` | Safe JSON.parse returning `Record` or null |
526
+ | `renderTemplate(tmpl, data)` | `{{path.to.value}}` template rendering |
527
+ | `buildPaperclipEnv(agent)` | Standard `PAPERCLIP_*` env vars |
528
+ | `redactEnvForLogs(env)` | Redact sensitive keys for onMeta |
529
+ | `ensureAbsoluteDirectory(cwd)` | Validate cwd exists and is absolute |
530
+ | `ensureCommandResolvable(cmd, cwd, env)` | Validate command is in PATH |
531
+ | `ensurePathInEnv(env)` | Ensure PATH exists in env |
532
+ | `runChildProcess(runId, cmd, args, opts)` | Spawn with timeout, logging, capture |
533
+
534
+ ---
535
+
536
+ ## 7. Conventions and Patterns
537
+
538
+ ### Naming
539
+ - Adapter type: `snake_case` (e.g. `claude_local`, `codex_local`)
540
+ - Package name: `@paperclipai/adapter-<kebab-name>`
541
+ - Package directory: `packages/adapters/<kebab-name>/`
542
+
543
+ ### Config Parsing
544
+ - Never trust `config` values directly — always use `asString`, `asNumber`, etc.
545
+ - Provide sensible defaults for every optional field
546
+ - Document all fields in `agentConfigurationDoc`
547
+
548
+ ### Prompt Templates
549
+ - Support `promptTemplate` for every run
550
+ - Use `renderTemplate()` with the standard variable set
551
+ - Default prompt: `"You are agent {{agent.id}} ({{agent.name}}). Continue your Paperclip work."`
552
+
553
+ ### Error Handling
554
+ - Differentiate timeout vs process error vs parse failure
555
+ - Always populate `errorMessage` on failure
556
+ - Include raw stdout/stderr in `resultJson` when parsing fails
557
+ - Handle the agent CLI not being installed (command not found)
558
+
559
+ ### Logging
560
+ - Call `onLog("stdout", ...)` and `onLog("stderr", ...)` for all process output — this feeds the real-time run viewer
561
+ - Call `onMeta(...)` before spawning to record invocation details
562
+ - Use `redactEnvForLogs()` when including env in meta
563
+
564
+ ### Paperclip Skills Injection
565
+
566
+ Paperclip ships shared skills (in the repo's top-level `skills/` directory) that agents need at runtime — things like the `paperclip` API skill and the `paperclip-create-agent` workflow skill. Each adapter is responsible for making these skills discoverable by its agent runtime **without polluting the agent's working directory**.
567
+
568
+ **The constraint:** never copy or symlink skills into the agent's `cwd`. The cwd is the user's project checkout — writing `.claude/skills/` or any other files into it would contaminate the repo with Paperclip internals, break git status, and potentially leak into commits.
569
+
570
+ **The pattern:** create a clean, isolated location for skills and tell the agent runtime to look there.
571
+
572
+ **How claude-local does it:**
573
+
574
+ 1. At execution time, create a fresh tmpdir: `mkdtemp("paperclip-skills-")`
575
+ 2. Inside it, create `.claude/skills/` (the directory structure Claude Code expects)
576
+ 3. Symlink each skill directory from the repo's `skills/` into the tmpdir's `.claude/skills/`
577
+ 4. Pass the tmpdir to Claude Code via `--add-dir <tmpdir>` — this makes Claude Code discover the skills as if they were registered in that directory, without touching the agent's actual cwd
578
+ 5. Clean up the tmpdir in a `finally` block after the run completes
579
+
580
+ ```ts
581
+ // From claude-local execute.ts
582
+ async function buildSkillsDir(): Promise<string> {
583
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-skills-"));
584
+ const target = path.join(tmp, ".claude", "skills");
585
+ await fs.mkdir(target, { recursive: true });
586
+ const entries = await fs.readdir(PAPERCLIP_SKILLS_DIR, { withFileTypes: true });
587
+ for (const entry of entries) {
588
+ if (entry.isDirectory()) {
589
+ await fs.symlink(
590
+ path.join(PAPERCLIP_SKILLS_DIR, entry.name),
591
+ path.join(target, entry.name),
592
+ );
593
+ }
594
+ }
595
+ return tmp;
596
+ }
597
+
598
+ // In execute(): pass --add-dir to Claude Code
599
+ const skillsDir = await buildSkillsDir();
600
+ args.push("--add-dir", skillsDir);
601
+ // ... run process ...
602
+ // In finally: fs.rm(skillsDir, { recursive: true, force: true })
603
+ ```
604
+
605
+ **How codex-local does it:**
606
+
607
+ Codex has a global personal skills directory (`$CODEX_HOME/skills` or `~/.codex/skills`). The adapter symlinks Paperclip skills there if they don't already exist. This is acceptable because it's the agent tool's own config directory, not the user's project.
608
+
609
+ ```ts
610
+ // From codex-local execute.ts
611
+ async function ensureCodexSkillsInjected(onLog) {
612
+ const skillsHome = path.join(codexHomeDir(), "skills");
613
+ await fs.mkdir(skillsHome, { recursive: true });
614
+ for (const entry of entries) {
615
+ const target = path.join(skillsHome, entry.name);
616
+ const existing = await fs.lstat(target).catch(() => null);
617
+ if (existing) continue; // Don't overwrite user's own skills
618
+ await fs.symlink(source, target);
619
+ }
620
+ }
621
+ ```
622
+
623
+ **For a new adapter:** figure out how your agent runtime discovers skills/plugins, then choose the cleanest injection path:
624
+
625
+ 1. **Best: tmpdir + flag** (like claude-local) — if the runtime supports an "additional directory" flag, create a tmpdir, symlink skills in, pass the flag, clean up after. Zero side effects.
626
+ 2. **Acceptable: global config dir** (like codex-local) — if the runtime has a global skills/plugins directory separate from the project, symlink there. Skip existing entries to avoid overwriting user customizations.
627
+ 3. **Acceptable: env var** — if the runtime reads a skills/plugin path from an environment variable, point it at the repo's `skills/` directory directly.
628
+ 4. **Last resort: prompt injection** — if the runtime has no plugin system, include skill content in the prompt template itself. This uses tokens but avoids filesystem side effects entirely.
629
+
630
+ **Skills as loaded procedures, not prompt bloat.** The Paperclip skills (like `paperclip` and `paperclip-create-agent`) are designed as on-demand procedures: the agent sees skill metadata (name + description) in its context, but only loads the full SKILL.md content when it decides to invoke a skill. This keeps the base prompt small. When writing `agentConfigurationDoc` or prompt templates for your adapter, do not inline skill content — let the agent runtime's skill discovery do the work. The descriptions in each SKILL.md frontmatter act as routing logic: they tell the agent when to load the full skill, not what the skill contains.
631
+
632
+ **Explicit vs. fuzzy skill invocation.** For production workflows where reliability matters (e.g. an agent that must always call the Paperclip API to report status), use explicit instructions in the prompt template: "Use the paperclip skill to report your progress." Fuzzy routing (letting the model decide based on description matching) is fine for exploratory tasks but unreliable for mandatory procedures.
633
+
634
+ ---
635
+
636
+ ## 8. Security Considerations
637
+
638
+ Adapters sit at the boundary between Paperclip's orchestration layer and arbitrary agent execution. This is a high-risk surface.
639
+
640
+ ### Treat Agent Output as Untrusted
641
+
642
+ The agent process runs LLM-driven code that reads external files, fetches URLs, and executes tools. Its output may be influenced by prompt injection from the content it processes. The adapter's parse layer is a trust boundary — validate everything, execute nothing.
643
+
644
+ ### Secret Injection via Environment, Not Prompts
645
+
646
+ Never put secrets (API keys, tokens) into prompt templates or config fields that flow through the LLM. Instead, inject them as environment variables that the agent's tools can read directly:
647
+
648
+ - `PAPERCLIP_API_KEY` is injected by the server into the process environment, not the prompt
649
+ - User-provided secrets in `config.env` are passed as env vars, redacted in `onMeta` logs
650
+ - The `redactEnvForLogs()` helper automatically masks any key matching `/(key|token|secret|password|authorization|cookie)/i`
651
+
652
+ This follows the "sidecar injection" pattern: the model never sees the real secret value, but the tools it invokes can read it from the environment.
653
+
654
+ ### Network Access
655
+
656
+ If your agent runtime supports network access controls (sandboxing, allowlists), configure them in the adapter:
657
+
658
+ - Prefer minimal allowlists over open internet access. An agent that only needs to call the Paperclip API and GitHub should not have access to arbitrary hosts.
659
+ - Skills + network = amplified risk. A skill that teaches the agent to make HTTP requests combined with unrestricted network access creates an exfiltration path. Constrain one or the other.
660
+ - If the runtime supports layered policies (org-level defaults + per-request overrides), wire the org-level policy into the adapter config and let per-agent config narrow further.
661
+
662
+ ### Process Isolation
663
+
664
+ - CLI-based adapters inherit the server's user permissions. The `cwd` and `env` config determine what the agent process can access on the filesystem.
665
+ - `dangerouslySkipPermissions` / `dangerouslyBypassApprovalsAndSandbox` flags exist for development convenience but must be documented as dangerous in `agentConfigurationDoc`. Production deployments should not use them.
666
+ - Timeout and grace period (`timeoutSec`, `graceSec`) are safety rails — always enforce them. A runaway agent process without a timeout can consume unbounded resources.
667
+
668
+ ---
669
+
670
+ ## 9. TranscriptEntry Kinds Reference
671
+
672
+ The UI run viewer displays these entry kinds:
673
+
674
+ | Kind | Fields | Usage |
675
+ |------|--------|-------|
676
+ | `init` | `model`, `sessionId` | Agent initialization |
677
+ | `assistant` | `text` | Agent text response |
678
+ | `thinking` | `text` | Agent reasoning/thinking |
679
+ | `user` | `text` | User message |
680
+ | `tool_call` | `name`, `input` | Tool invocation |
681
+ | `tool_result` | `toolUseId`, `content`, `isError` | Tool result |
682
+ | `result` | `text`, `inputTokens`, `outputTokens`, `cachedTokens`, `costUsd`, `subtype`, `isError`, `errors` | Final result with usage |
683
+ | `stderr` | `text` | Stderr output |
684
+ | `system` | `text` | System messages |
685
+ | `stdout` | `text` | Raw stdout fallback |
686
+
687
+ ---
688
+
689
+ ## 10. Testing
690
+
691
+ Create tests in `server/src/__tests__/<adapter-name>-adapter.test.ts`. Test:
692
+
693
+ 1. **Output parsing** — feed sample stdout through your parser, verify structured output
694
+ 2. **Unknown session detection** — verify the `is<Agent>UnknownSessionError` function
695
+ 3. **Config building** — verify `buildConfig` produces correct adapterConfig from form values
696
+ 4. **Session codec** — verify serialize/deserialize round-trips
697
+
698
+ ---
699
+
700
+ ## 11. Minimal Adapter Checklist
701
+
702
+ - [ ] `packages/adapters/<name>/package.json` with four exports (`.`, `./server`, `./ui`, `./cli`)
703
+ - [ ] Root `index.ts` with `type`, `label`, `models`, `agentConfigurationDoc`
704
+ - [ ] `server/execute.ts` implementing `AdapterExecutionContext -> AdapterExecutionResult`
705
+ - [ ] `server/test.ts` implementing `AdapterEnvironmentTestContext -> AdapterEnvironmentTestResult`
706
+ - [ ] `server/parse.ts` with output parser and unknown-session detector
707
+ - [ ] `server/index.ts` exporting `execute`, `testEnvironment`, `sessionCodec`, parse helpers
708
+ - [ ] `ui/parse-stdout.ts` with `StdoutLineParser` for the run viewer
709
+ - [ ] `ui/build-config.ts` with `CreateConfigValues -> adapterConfig` builder
710
+ - [ ] `ui/src/adapters/<name>/config-fields.tsx` React component for agent form
711
+ - [ ] `ui/src/adapters/<name>/index.ts` assembling the `UIAdapterModule`
712
+ - [ ] `cli/format-event.ts` with terminal formatter
713
+ - [ ] `cli/index.ts` exporting the formatter
714
+ - [ ] Registered in `server/src/adapters/registry.ts`
715
+ - [ ] Registered in `ui/src/adapters/registry.ts`
716
+ - [ ] Registered in `cli/src/adapters/registry.ts`
717
+ - [ ] Added to workspace in root `pnpm-workspace.yaml` (if not already covered by glob)
718
+ - [ ] Tests for parsing, session codec, and config building