agy-paperclip-adapter 1.0.0
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.
- package/README.md +43 -0
- package/dist/icon.d.ts +2 -0
- package/dist/icon.js +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/models.d.ts +5 -0
- package/dist/models.js +18 -0
- package/dist/server/execute.d.ts +2 -0
- package/dist/server/execute.js +238 -0
- package/dist/server/index.d.ts +35 -0
- package/dist/server/index.js +111 -0
- package/dist/server/skills-sync.d.ts +13 -0
- package/dist/server/skills-sync.js +39 -0
- package/dist/server/test.d.ts +2 -0
- package/dist/server/test.js +42 -0
- package/dist/ui-parser.d.ts +11 -0
- package/dist/ui-parser.js +42 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Antigravity Paperclip Adapter (`agy-paperclip-adapter`)
|
|
2
|
+
|
|
3
|
+
An external adapter plugin for [Paperclip](https://github.com/paperclipai/paperclip) that integrates the Google Antigravity (`agy`) local CLI agent runtime.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Full Model Selection**: Supports all Antigravity models (`gemini-3.8-flash-high`, `gemini-3.7-flash-high`, `gemini-3.6-flash-high`, `gemini-3.1-pro-high`, `claude-sonnet-4-6`, `claude-opus-4-6-thinking`, `gpt-oss-120b-medium`, `auto`).
|
|
8
|
+
- **Adapter Icon**: Includes native SVG branding icon for Paperclip's UI.
|
|
9
|
+
- **Paperclip Skills to AGY Sync**: Automatically materializes skills assigned in Paperclip into `${workspace}/.agents/skills/<skill-name>/SKILL.md` for AGY agent discovery.
|
|
10
|
+
- **Multi-Agent Isolation**: Allows hiring multiple agents with distinct roles, instructions, and skills on a single local `agy` runtime via isolated conversation sessions (`--conversation`) and working directories.
|
|
11
|
+
- **UI Transcript Parser**: Self-contained streaming parser for tool calls, thinking steps, system logs, and assistant markdown output.
|
|
12
|
+
- **Package Import Ready (`npm run pack`)**: Easily build and package into `dist/agy-paperclip-adapter-1.0.0.tgz` for importing via Paperclip UI or CLI.
|
|
13
|
+
|
|
14
|
+
## Quick Start & Packing
|
|
15
|
+
|
|
16
|
+
To build and generate the importable tarball in `dist/`:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm run pack
|
|
20
|
+
# Output: dist/agy-paperclip-adapter-1.0.0.tgz
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Installation into Paperclip
|
|
24
|
+
|
|
25
|
+
### Option 1: Via UI (Custom Adapter Import)
|
|
26
|
+
Upload `dist/agy-paperclip-adapter-1.0.0.tgz` in the Paperclip UI under **Settings → Adapters → Install Custom Adapter**.
|
|
27
|
+
|
|
28
|
+
### Option 2: Via Paperclip CLI
|
|
29
|
+
```bash
|
|
30
|
+
paperclipai adapter install --payload-json "{\"packageName\":\"$PWD/dist/agy-paperclip-adapter-1.0.0.tgz\"}"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Option 3: Local Symlink Development
|
|
34
|
+
```bash
|
|
35
|
+
curl -X POST http://localhost:3100/api/adapters \
|
|
36
|
+
-H "Authorization: Bearer <PAPERCLIP_API_KEY>" \
|
|
37
|
+
-H "Content-Type: application/json" \
|
|
38
|
+
-d "{\"localPath\":\"$PWD\"}"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
MIT
|
package/dist/icon.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const iconSvg = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 2L2 19h20L12 2z\" stroke=\"#4285F4\" stroke-width=\"2\"/>\n <ellipse cx=\"12\" cy=\"14\" rx=\"8\" ry=\"3\" stroke=\"#A142F4\" stroke-width=\"1.5\" stroke-dasharray=\"3 2\"/>\n <circle cx=\"12\" cy=\"10\" r=\"2\" fill=\"#4285F4\"/>\n</svg>";
|
|
2
|
+
export declare const icon = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 2L2 19h20L12 2z\" stroke=\"#4285F4\" stroke-width=\"2\"/>\n <ellipse cx=\"12\" cy=\"14\" rx=\"8\" ry=\"3\" stroke=\"#A142F4\" stroke-width=\"1.5\" stroke-dasharray=\"3 2\"/>\n <circle cx=\"12\" cy=\"10\" r=\"2\" fill=\"#4285F4\"/>\n</svg>";
|
package/dist/icon.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const iconSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
2
|
+
<path d="M12 2L2 19h20L12 2z" stroke="#4285F4" stroke-width="2"/>
|
|
3
|
+
<ellipse cx="12" cy="14" rx="8" ry="3" stroke="#A142F4" stroke-width="1.5" stroke-dasharray="3 2"/>
|
|
4
|
+
<circle cx="12" cy="10" r="2" fill="#4285F4"/>
|
|
5
|
+
</svg>`;
|
|
6
|
+
export const icon = iconSvg;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const type = "antigravity";
|
|
2
|
+
export declare const label = "Antigravity";
|
|
3
|
+
export { icon, iconSvg } from "./icon.js";
|
|
4
|
+
export { models, DEFAULT_ANTIGRAVITY_MODEL } from "./models.js";
|
|
5
|
+
export { parseStdoutLine } from "./ui-parser.js";
|
|
6
|
+
export { agentConfigurationDoc, createServerAdapter, sessionCodec, getConfigSchema, } from "./server/index.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const type = "antigravity";
|
|
2
|
+
export const label = "Antigravity";
|
|
3
|
+
export { icon, iconSvg } from "./icon.js";
|
|
4
|
+
export { models, DEFAULT_ANTIGRAVITY_MODEL } from "./models.js";
|
|
5
|
+
export { parseStdoutLine } from "./ui-parser.js";
|
|
6
|
+
export { agentConfigurationDoc, createServerAdapter, sessionCodec, getConfigSchema, } from "./server/index.js";
|
package/dist/models.d.ts
ADDED
package/dist/models.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const DEFAULT_ANTIGRAVITY_MODEL = "gemini-3.8-flash-high";
|
|
2
|
+
export const models = [
|
|
3
|
+
{ id: "gemini-3.8-flash-high", label: "Gemini 3.8 Flash (High)" },
|
|
4
|
+
{ id: "gemini-3.8-flash-medium", label: "Gemini 3.8 Flash (Medium)" },
|
|
5
|
+
{ id: "gemini-3.8-flash-low", label: "Gemini 3.8 Flash (Low)" },
|
|
6
|
+
{ id: "gemini-3.7-flash-high", label: "Gemini 3.7 Flash (High)" },
|
|
7
|
+
{ id: "gemini-3.7-flash-medium", label: "Gemini 3.7 Flash (Medium)" },
|
|
8
|
+
{ id: "gemini-3.7-flash-low", label: "Gemini 3.7 Flash (Low)" },
|
|
9
|
+
{ id: "gemini-3.6-flash-high", label: "Gemini 3.6 Flash (High)" },
|
|
10
|
+
{ id: "gemini-3.6-flash-medium", label: "Gemini 3.6 Flash (Medium)" },
|
|
11
|
+
{ id: "gemini-3.6-flash-low", label: "Gemini 3.6 Flash (Low)" },
|
|
12
|
+
{ id: "gemini-3.1-pro-high", label: "Gemini 3.1 Pro (High)" },
|
|
13
|
+
{ id: "gemini-3.1-pro-low", label: "Gemini 3.1 Pro (Low)" },
|
|
14
|
+
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6 (Thinking)" },
|
|
15
|
+
{ id: "claude-opus-4-6-thinking", label: "Claude Opus 4.6 (Thinking)" },
|
|
16
|
+
{ id: "gpt-oss-120b-medium", label: "GPT-OSS 120B (Medium)" },
|
|
17
|
+
{ id: "auto", label: "Auto" },
|
|
18
|
+
];
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import { runChildProcess, buildPaperclipEnv, applyPaperclipWorkspaceEnv, renderPaperclipWakePrompt, renderTemplate, joinPromptSections, stringifyPaperclipWakePayload, parseObject, asString, DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE, } from "@paperclipai/adapter-utils/server-utils";
|
|
4
|
+
import { DEFAULT_ANTIGRAVITY_MODEL } from "../models.js";
|
|
5
|
+
import { syncSkillsToWorkspace } from "./skills-sync.js";
|
|
6
|
+
function parseSkills(raw) {
|
|
7
|
+
if (!Array.isArray(raw))
|
|
8
|
+
return [];
|
|
9
|
+
const items = [];
|
|
10
|
+
for (const item of raw) {
|
|
11
|
+
if (typeof item === "object" && item !== null) {
|
|
12
|
+
const obj = item;
|
|
13
|
+
if (typeof obj.name === "string" && obj.name.trim().length > 0) {
|
|
14
|
+
const files = [];
|
|
15
|
+
if (Array.isArray(obj.files)) {
|
|
16
|
+
for (const f of obj.files) {
|
|
17
|
+
if (typeof f === "object" && f !== null) {
|
|
18
|
+
const fileObj = f;
|
|
19
|
+
if (typeof fileObj.path === "string" && fileObj.path.trim().length > 0) {
|
|
20
|
+
files.push({
|
|
21
|
+
path: fileObj.path,
|
|
22
|
+
content: typeof fileObj.content === "string" ? fileObj.content : "",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
items.push({
|
|
29
|
+
id: typeof obj.id === "string" ? obj.id : undefined,
|
|
30
|
+
name: obj.name,
|
|
31
|
+
description: typeof obj.description === "string" ? obj.description : undefined,
|
|
32
|
+
instructions: typeof obj.instructions === "string" ? obj.instructions : undefined,
|
|
33
|
+
content: typeof obj.content === "string" ? obj.content : undefined,
|
|
34
|
+
files: files.length > 0 ? files : undefined,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return items;
|
|
40
|
+
}
|
|
41
|
+
function buildPrompt(ctx) {
|
|
42
|
+
const { config, agent, context } = ctx;
|
|
43
|
+
const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, {
|
|
44
|
+
conversationMode: context.conversationMode === true,
|
|
45
|
+
resumedSession: Boolean(ctx.runtime.sessionId),
|
|
46
|
+
suppressIssueDescription: false,
|
|
47
|
+
});
|
|
48
|
+
const template = typeof config.promptTemplate === "string" && config.promptTemplate.trim().length > 0
|
|
49
|
+
? config.promptTemplate
|
|
50
|
+
: DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE;
|
|
51
|
+
const data = {
|
|
52
|
+
agentId: agent.id,
|
|
53
|
+
agentName: agent.name,
|
|
54
|
+
companyId: agent.companyId,
|
|
55
|
+
runId: ctx.runId,
|
|
56
|
+
taskId: asString(context.taskId, ""),
|
|
57
|
+
taskTitle: asString(context.taskTitle, ""),
|
|
58
|
+
wakeReason: asString(context.wakeReason, ""),
|
|
59
|
+
};
|
|
60
|
+
const renderedTemplate = renderTemplate(template, data);
|
|
61
|
+
const sections = [];
|
|
62
|
+
const agentInstructions = asString(context.agentInstructions, "");
|
|
63
|
+
if (agentInstructions.trim().length > 0) {
|
|
64
|
+
sections.push(`## Agent Role & Instructions\n${agentInstructions.trim()}`);
|
|
65
|
+
}
|
|
66
|
+
if (wakePrompt && wakePrompt.trim().length > 0) {
|
|
67
|
+
sections.push(wakePrompt);
|
|
68
|
+
}
|
|
69
|
+
sections.push(renderedTemplate);
|
|
70
|
+
const taskDescription = asString(context.taskDescription, "");
|
|
71
|
+
if (taskDescription.trim().length > 0) {
|
|
72
|
+
sections.push(`## Task Details\n${taskDescription.trim()}`);
|
|
73
|
+
}
|
|
74
|
+
return joinPromptSections(sections);
|
|
75
|
+
}
|
|
76
|
+
export async function execute(ctx) {
|
|
77
|
+
const { config, agent, runId, onLog, context } = ctx;
|
|
78
|
+
const command = String(config.command ?? "agy");
|
|
79
|
+
const model = String(config.model ?? DEFAULT_ANTIGRAVITY_MODEL);
|
|
80
|
+
const effort = config.effort ? String(config.effort) : null;
|
|
81
|
+
const mode = config.mode ? String(config.mode) : null;
|
|
82
|
+
const timeoutSec = Number(config.timeoutSec ?? 600);
|
|
83
|
+
const graceSec = Number(config.graceSec ?? 15);
|
|
84
|
+
const prevConversationId = typeof ctx.runtime.sessionParams?.conversationId === "string"
|
|
85
|
+
? ctx.runtime.sessionParams.conversationId
|
|
86
|
+
: typeof ctx.runtime.sessionId === "string"
|
|
87
|
+
? ctx.runtime.sessionId
|
|
88
|
+
: null;
|
|
89
|
+
const conversationId = prevConversationId ?? `${agent.id}-${crypto.randomUUID()}`;
|
|
90
|
+
const workspaceContext = parseObject(context.paperclipWorkspace);
|
|
91
|
+
const workspaceCwd = asString(workspaceContext.cwd, "");
|
|
92
|
+
const effectiveCwd = workspaceCwd || String(config.cwd ?? process.cwd());
|
|
93
|
+
try {
|
|
94
|
+
await fs.mkdir(effectiveCwd, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Ignore directory creation error
|
|
98
|
+
}
|
|
99
|
+
const skillsList = parseSkills(context.skills);
|
|
100
|
+
const syncedSkills = await syncSkillsToWorkspace(effectiveCwd, skillsList);
|
|
101
|
+
const paperclipEnv = buildPaperclipEnv(agent);
|
|
102
|
+
const env = {
|
|
103
|
+
...process.env,
|
|
104
|
+
...paperclipEnv,
|
|
105
|
+
HOME: process.env.HOME || "/root",
|
|
106
|
+
PATH: `/root/.local/bin:${process.env.PATH ?? ""}`,
|
|
107
|
+
PAPERCLIP_RUN_ID: runId,
|
|
108
|
+
};
|
|
109
|
+
if (ctx.authToken) {
|
|
110
|
+
env.PAPERCLIP_API_KEY = ctx.authToken;
|
|
111
|
+
}
|
|
112
|
+
const wakeTaskId = asString(context.taskId, "") || asString(context.issueId, "");
|
|
113
|
+
if (wakeTaskId.trim().length > 0) {
|
|
114
|
+
env.PAPERCLIP_TASK_ID = wakeTaskId.trim();
|
|
115
|
+
}
|
|
116
|
+
const wakeReason = asString(context.wakeReason, "");
|
|
117
|
+
if (wakeReason.trim().length > 0) {
|
|
118
|
+
env.PAPERCLIP_WAKE_REASON = wakeReason.trim();
|
|
119
|
+
}
|
|
120
|
+
const wakeCommentId = asString(context.wakeCommentId, "") || asString(context.commentId, "");
|
|
121
|
+
if (wakeCommentId.trim().length > 0) {
|
|
122
|
+
env.PAPERCLIP_WAKE_COMMENT_ID = wakeCommentId.trim();
|
|
123
|
+
}
|
|
124
|
+
const approvalId = asString(context.approvalId, "");
|
|
125
|
+
if (approvalId.trim().length > 0) {
|
|
126
|
+
env.PAPERCLIP_APPROVAL_ID = approvalId.trim();
|
|
127
|
+
}
|
|
128
|
+
const approvalStatus = asString(context.approvalStatus, "");
|
|
129
|
+
if (approvalStatus.trim().length > 0) {
|
|
130
|
+
env.PAPERCLIP_APPROVAL_STATUS = approvalStatus.trim();
|
|
131
|
+
}
|
|
132
|
+
const linkedIssueIds = [];
|
|
133
|
+
if (Array.isArray(context.issueIds)) {
|
|
134
|
+
for (const item of context.issueIds) {
|
|
135
|
+
if (typeof item === "string" && item.trim().length > 0) {
|
|
136
|
+
linkedIssueIds.push(item.trim());
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (linkedIssueIds.length > 0) {
|
|
141
|
+
env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(",");
|
|
142
|
+
}
|
|
143
|
+
const wakePayloadJson = stringifyPaperclipWakePayload(context.paperclipWake);
|
|
144
|
+
if (wakePayloadJson) {
|
|
145
|
+
env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayloadJson;
|
|
146
|
+
}
|
|
147
|
+
applyPaperclipWorkspaceEnv(env, {
|
|
148
|
+
workspaceCwd: effectiveCwd,
|
|
149
|
+
workspaceSource: asString(workspaceContext.source, ""),
|
|
150
|
+
workspaceStrategy: asString(workspaceContext.strategy, ""),
|
|
151
|
+
workspaceId: asString(workspaceContext.workspaceId, "") || null,
|
|
152
|
+
workspaceRepoUrl: asString(workspaceContext.repoUrl, "") || null,
|
|
153
|
+
workspaceRepoRef: asString(workspaceContext.repoRef, "") || null,
|
|
154
|
+
workspaceBranch: asString(workspaceContext.branchName, "") || null,
|
|
155
|
+
workspaceWorktreePath: asString(workspaceContext.worktreePath, "") || null,
|
|
156
|
+
agentHome: asString(workspaceContext.agentHome, "") || null,
|
|
157
|
+
});
|
|
158
|
+
if (typeof config.env === "object" && config.env !== null) {
|
|
159
|
+
for (const [k, v] of Object.entries(config.env)) {
|
|
160
|
+
if (typeof v === "string")
|
|
161
|
+
env[k] = v;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
let promptText = buildPrompt(ctx);
|
|
165
|
+
const instructionsPath = config.instructionsFilePath
|
|
166
|
+
? String(config.instructionsFilePath)
|
|
167
|
+
: null;
|
|
168
|
+
if (instructionsPath) {
|
|
169
|
+
try {
|
|
170
|
+
const instructions = await fs.readFile(instructionsPath, "utf-8");
|
|
171
|
+
promptText = `${instructions}\n\n---\n\n${promptText}`;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Ignore missing instructions file
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (syncedSkills.length > 0) {
|
|
178
|
+
await onLog("stdout", `[antigravity] Synchronized ${syncedSkills.length} skill(s) for agent "${agent.name}": ${syncedSkills.join(", ")}\n`);
|
|
179
|
+
}
|
|
180
|
+
const args = ["--dangerously-skip-permissions"];
|
|
181
|
+
if (model && model !== "auto") {
|
|
182
|
+
args.push(`--model=${model}`);
|
|
183
|
+
}
|
|
184
|
+
if (effort) {
|
|
185
|
+
args.push(`--effort=${effort}`);
|
|
186
|
+
}
|
|
187
|
+
if (mode) {
|
|
188
|
+
args.push(`--mode=${mode}`);
|
|
189
|
+
}
|
|
190
|
+
if (prevConversationId) {
|
|
191
|
+
args.push(`--conversation=${prevConversationId}`);
|
|
192
|
+
}
|
|
193
|
+
args.push(`--print=${promptText}`);
|
|
194
|
+
await onLog("stdout", `[antigravity] Starting agy session (agent: ${agent.name}, model: ${model}, conversation: ${conversationId})...\n`);
|
|
195
|
+
try {
|
|
196
|
+
const processResult = await runChildProcess(runId, command, args, {
|
|
197
|
+
cwd: effectiveCwd,
|
|
198
|
+
env,
|
|
199
|
+
timeoutSec,
|
|
200
|
+
graceSec,
|
|
201
|
+
onLog: async (stream, chunk) => {
|
|
202
|
+
await onLog(stream, chunk);
|
|
203
|
+
},
|
|
204
|
+
onSpawn: ctx.onSpawn,
|
|
205
|
+
});
|
|
206
|
+
const isSuccess = processResult.exitCode === 0;
|
|
207
|
+
return {
|
|
208
|
+
exitCode: processResult.exitCode,
|
|
209
|
+
signal: processResult.signal,
|
|
210
|
+
timedOut: processResult.timedOut,
|
|
211
|
+
provider: "antigravity",
|
|
212
|
+
model,
|
|
213
|
+
sessionId: conversationId,
|
|
214
|
+
sessionDisplayId: conversationId.slice(0, 8),
|
|
215
|
+
sessionParams: {
|
|
216
|
+
conversationId,
|
|
217
|
+
cwd: effectiveCwd,
|
|
218
|
+
model,
|
|
219
|
+
agentId: agent.id,
|
|
220
|
+
},
|
|
221
|
+
errorMessage: isSuccess ? null : `agy exited with code ${processResult.exitCode}`,
|
|
222
|
+
summary: isSuccess ? "Completed Antigravity run" : `Run failed with exit code ${processResult.exitCode}`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
227
|
+
await onLog("stderr", `[antigravity] Error executing agy: ${errorMsg}\n`);
|
|
228
|
+
return {
|
|
229
|
+
exitCode: 1,
|
|
230
|
+
signal: null,
|
|
231
|
+
timedOut: false,
|
|
232
|
+
provider: "antigravity",
|
|
233
|
+
model,
|
|
234
|
+
errorMessage: errorMsg,
|
|
235
|
+
summary: `Antigravity execution failed: ${errorMsg}`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ServerAdapterModule, AdapterSessionCodec } from "@paperclipai/adapter-utils";
|
|
2
|
+
import { icon, iconSvg } from "../icon.js";
|
|
3
|
+
export declare const type = "antigravity";
|
|
4
|
+
export declare const label = "Antigravity";
|
|
5
|
+
export { icon, iconSvg };
|
|
6
|
+
export declare const sessionCodec: AdapterSessionCodec;
|
|
7
|
+
export declare const agentConfigurationDoc = "# Antigravity Agent Configuration\n\nAdapter: antigravity\n\nUse when:\n- You want Paperclip to orchestrate Google Antigravity (agy) local CLI agents.\n- You want model selection across Gemini 3.8 Flash, Gemini 3.7 Flash, Gemini 3.6 Flash, Gemini 3.1 Pro, Claude Sonnet/Opus, or GPT-OSS models.\n- You want to hire multiple local agents with individual instructions and Paperclip skills.\n- You want persistent conversation session state across heartbeats.\n\nCore fields:\n- model (string): Antigravity model ID (e.g. gemini-3.8-flash-high, gemini-3.7-flash-high, claude-sonnet-4-6).\n- effort (string, optional): Reasoning effort (low, medium, high, max).\n- mode (string, optional): accept-edits or plan.\n- command (string, optional): agy binary command path (defaults to \"agy\").\n- cwd (string, optional): Working directory fallback.\n- instructionsFilePath (string, optional): Path to instructions markdown file (e.g. AGENTS.md).\n- timeoutSec (number, optional): Execution timeout in seconds (default: 600).\n";
|
|
8
|
+
export declare function getConfigSchema(): {
|
|
9
|
+
fields: ({
|
|
10
|
+
key: string;
|
|
11
|
+
label: string;
|
|
12
|
+
type: "select";
|
|
13
|
+
default: string;
|
|
14
|
+
options: {
|
|
15
|
+
value: string;
|
|
16
|
+
label: string;
|
|
17
|
+
}[];
|
|
18
|
+
hint: string;
|
|
19
|
+
} | {
|
|
20
|
+
key: string;
|
|
21
|
+
label: string;
|
|
22
|
+
type: "text";
|
|
23
|
+
default: string;
|
|
24
|
+
hint: string;
|
|
25
|
+
options?: undefined;
|
|
26
|
+
} | {
|
|
27
|
+
key: string;
|
|
28
|
+
label: string;
|
|
29
|
+
type: "number";
|
|
30
|
+
default: number;
|
|
31
|
+
hint: string;
|
|
32
|
+
options?: undefined;
|
|
33
|
+
})[];
|
|
34
|
+
};
|
|
35
|
+
export declare function createServerAdapter(): ServerAdapterModule;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { models, DEFAULT_ANTIGRAVITY_MODEL } from "../models.js";
|
|
2
|
+
import { icon, iconSvg } from "../icon.js";
|
|
3
|
+
import { execute } from "./execute.js";
|
|
4
|
+
import { testEnvironment } from "./test.js";
|
|
5
|
+
export const type = "antigravity";
|
|
6
|
+
export const label = "Antigravity";
|
|
7
|
+
export { icon, iconSvg };
|
|
8
|
+
export const sessionCodec = {
|
|
9
|
+
deserialize(raw) {
|
|
10
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
11
|
+
return null;
|
|
12
|
+
const r = raw;
|
|
13
|
+
const conversationId = r.conversationId ?? r.sessionId;
|
|
14
|
+
return conversationId ? { conversationId: String(conversationId) } : null;
|
|
15
|
+
},
|
|
16
|
+
serialize(params) {
|
|
17
|
+
if (!params?.conversationId && !params?.sessionId)
|
|
18
|
+
return null;
|
|
19
|
+
return { conversationId: String(params.conversationId ?? params.sessionId) };
|
|
20
|
+
},
|
|
21
|
+
getDisplayId(params) {
|
|
22
|
+
const id = params?.conversationId ?? params?.sessionId;
|
|
23
|
+
return id ? String(id).slice(0, 8) : null;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
export const agentConfigurationDoc = `# Antigravity Agent Configuration
|
|
27
|
+
|
|
28
|
+
Adapter: antigravity
|
|
29
|
+
|
|
30
|
+
Use when:
|
|
31
|
+
- You want Paperclip to orchestrate Google Antigravity (agy) local CLI agents.
|
|
32
|
+
- You want model selection across Gemini 3.8 Flash, Gemini 3.7 Flash, Gemini 3.6 Flash, Gemini 3.1 Pro, Claude Sonnet/Opus, or GPT-OSS models.
|
|
33
|
+
- You want to hire multiple local agents with individual instructions and Paperclip skills.
|
|
34
|
+
- You want persistent conversation session state across heartbeats.
|
|
35
|
+
|
|
36
|
+
Core fields:
|
|
37
|
+
- model (string): Antigravity model ID (e.g. gemini-3.8-flash-high, gemini-3.7-flash-high, claude-sonnet-4-6).
|
|
38
|
+
- effort (string, optional): Reasoning effort (low, medium, high, max).
|
|
39
|
+
- mode (string, optional): accept-edits or plan.
|
|
40
|
+
- command (string, optional): agy binary command path (defaults to "agy").
|
|
41
|
+
- cwd (string, optional): Working directory fallback.
|
|
42
|
+
- instructionsFilePath (string, optional): Path to instructions markdown file (e.g. AGENTS.md).
|
|
43
|
+
- timeoutSec (number, optional): Execution timeout in seconds (default: 600).
|
|
44
|
+
`;
|
|
45
|
+
export function getConfigSchema() {
|
|
46
|
+
return {
|
|
47
|
+
fields: [
|
|
48
|
+
{
|
|
49
|
+
key: "model",
|
|
50
|
+
label: "Model",
|
|
51
|
+
type: "select",
|
|
52
|
+
default: DEFAULT_ANTIGRAVITY_MODEL,
|
|
53
|
+
options: models.map((m) => ({ value: m.id, label: m.label })),
|
|
54
|
+
hint: "Select the AI model for this Antigravity agent.",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
key: "effort",
|
|
58
|
+
label: "Reasoning Effort",
|
|
59
|
+
type: "select",
|
|
60
|
+
default: "high",
|
|
61
|
+
options: [
|
|
62
|
+
{ value: "low", label: "Low" },
|
|
63
|
+
{ value: "medium", label: "Medium" },
|
|
64
|
+
{ value: "high", label: "High" },
|
|
65
|
+
{ value: "max", label: "Max" },
|
|
66
|
+
],
|
|
67
|
+
hint: "Reasoning effort level for thinking models.",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: "mode",
|
|
71
|
+
label: "Execution Mode",
|
|
72
|
+
type: "select",
|
|
73
|
+
default: "accept-edits",
|
|
74
|
+
options: [
|
|
75
|
+
{ value: "accept-edits", label: "Accept Edits" },
|
|
76
|
+
{ value: "plan", label: "Plan" },
|
|
77
|
+
],
|
|
78
|
+
hint: "Execution mode for the Antigravity session.",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: "command",
|
|
82
|
+
label: "CLI Command",
|
|
83
|
+
type: "text",
|
|
84
|
+
default: "agy",
|
|
85
|
+
hint: "Override command or binary path for Antigravity CLI.",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
key: "timeoutSec",
|
|
89
|
+
label: "Timeout (seconds)",
|
|
90
|
+
type: "number",
|
|
91
|
+
default: 600,
|
|
92
|
+
hint: "Execution timeout in seconds.",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function createServerAdapter() {
|
|
98
|
+
return {
|
|
99
|
+
type,
|
|
100
|
+
execute,
|
|
101
|
+
testEnvironment,
|
|
102
|
+
sessionCodec,
|
|
103
|
+
models,
|
|
104
|
+
agentConfigurationDoc,
|
|
105
|
+
getConfigSchema,
|
|
106
|
+
supportsSkills: true,
|
|
107
|
+
supportsInstructionsBundle: true,
|
|
108
|
+
instructionsPathKey: "instructionsFilePath",
|
|
109
|
+
supportsLocalAgentJwt: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface SkillFile {
|
|
2
|
+
path: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SkillItem {
|
|
6
|
+
id?: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
instructions?: string;
|
|
10
|
+
content?: string;
|
|
11
|
+
files?: SkillFile[];
|
|
12
|
+
}
|
|
13
|
+
export declare function syncSkillsToWorkspace(effectiveCwd: string, skills?: SkillItem[]): Promise<string[]>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function syncSkillsToWorkspace(effectiveCwd, skills) {
|
|
4
|
+
if (!Array.isArray(skills) || skills.length === 0) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
const baseSkillsDir = path.join(effectiveCwd, ".agents", "skills");
|
|
8
|
+
await fs.mkdir(baseSkillsDir, { recursive: true });
|
|
9
|
+
const syncedSkillNames = [];
|
|
10
|
+
for (const skill of skills) {
|
|
11
|
+
if (!skill || !skill.name)
|
|
12
|
+
continue;
|
|
13
|
+
const skillName = skill.name.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-");
|
|
14
|
+
const skillDir = path.join(baseSkillsDir, skillName);
|
|
15
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
16
|
+
const description = skill.description ? skill.description.trim() : `Skill: ${skill.name}`;
|
|
17
|
+
const body = skill.instructions ?? skill.content ?? "";
|
|
18
|
+
const skillMdContent = `---
|
|
19
|
+
name: ${skillName}
|
|
20
|
+
description: ${description.replace(/\n/g, " ")}
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
${body}
|
|
24
|
+
`;
|
|
25
|
+
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
26
|
+
await fs.writeFile(skillMdPath, skillMdContent, "utf-8");
|
|
27
|
+
if (Array.isArray(skill.files)) {
|
|
28
|
+
for (const file of skill.files) {
|
|
29
|
+
if (!file || !file.path)
|
|
30
|
+
continue;
|
|
31
|
+
const filePath = path.join(skillDir, file.path);
|
|
32
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
33
|
+
await fs.writeFile(filePath, file.content ?? "", "utf-8");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
syncedSkillNames.push(skillName);
|
|
37
|
+
}
|
|
38
|
+
return syncedSkillNames;
|
|
39
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export async function testEnvironment(ctx) {
|
|
5
|
+
const checks = [];
|
|
6
|
+
const command = String(ctx.config.command ?? "agy");
|
|
7
|
+
try {
|
|
8
|
+
const { stdout } = await execFileAsync(command, ["--version"], {
|
|
9
|
+
timeout: 5000,
|
|
10
|
+
});
|
|
11
|
+
const version = stdout.trim();
|
|
12
|
+
checks.push({
|
|
13
|
+
level: "info",
|
|
14
|
+
message: `Antigravity CLI detected: ${version}`,
|
|
15
|
+
code: "agy_cli_detected",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
checks.push({
|
|
20
|
+
level: "error",
|
|
21
|
+
message: `Failed to execute Antigravity CLI command "${command}": ${err instanceof Error ? err.message : String(err)}`,
|
|
22
|
+
hint: "Ensure 'agy' is installed in PATH (e.g. /root/.local/bin/agy) or specify adapterConfig.command.",
|
|
23
|
+
code: "agy_cli_missing",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const cwd = ctx.config.cwd ? String(ctx.config.cwd) : null;
|
|
27
|
+
if (cwd && !cwd.startsWith("/")) {
|
|
28
|
+
checks.push({
|
|
29
|
+
level: "error",
|
|
30
|
+
message: `Working directory must be an absolute path: "${cwd}"`,
|
|
31
|
+
hint: "Specify an absolute path such as /root or a workspace directory.",
|
|
32
|
+
code: "invalid_cwd",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const hasError = checks.some((c) => c.level === "error");
|
|
36
|
+
return {
|
|
37
|
+
adapterType: ctx.adapterType,
|
|
38
|
+
status: hasError ? "fail" : "pass",
|
|
39
|
+
checks,
|
|
40
|
+
testedAt: new Date().toISOString(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface TranscriptEntry {
|
|
2
|
+
kind: "assistant" | "thinking" | "system" | "tool_call" | "tool_result" | "stderr" | "stdout";
|
|
3
|
+
ts: string;
|
|
4
|
+
text?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
input?: unknown;
|
|
7
|
+
toolUseId?: string;
|
|
8
|
+
content?: string;
|
|
9
|
+
isError?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseStdoutLine(line: string, ts: string): TranscriptEntry[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Self-contained UI transcript parser for Antigravity (zero runtime imports)
|
|
2
|
+
export function parseStdoutLine(line, ts) {
|
|
3
|
+
const trimmed = line.trim();
|
|
4
|
+
if (!trimmed)
|
|
5
|
+
return [];
|
|
6
|
+
// Antigravity adapter log messages
|
|
7
|
+
if (trimmed.startsWith("[antigravity]")) {
|
|
8
|
+
return [{ kind: "system", ts, text: trimmed }];
|
|
9
|
+
}
|
|
10
|
+
// Spinner / progress indicators from CLI
|
|
11
|
+
if (/^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/.test(trimmed)) {
|
|
12
|
+
return [{ kind: "thinking", ts, text: trimmed.replace(/^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\s*/, "") }];
|
|
13
|
+
}
|
|
14
|
+
// Thinking markers
|
|
15
|
+
if (trimmed.startsWith("Thinking:") || trimmed.startsWith("Thought:")) {
|
|
16
|
+
return [{ kind: "thinking", ts, text: trimmed }];
|
|
17
|
+
}
|
|
18
|
+
// Tool calls (e.g. [tool] call: name)
|
|
19
|
+
const toolMatch = trimmed.match(/^\[(?:tool|call)\]\s+([a-zA-Z0-9_-]+)(?:\s+(.*))?$/);
|
|
20
|
+
if (toolMatch) {
|
|
21
|
+
const name = toolMatch[1];
|
|
22
|
+
let input = {};
|
|
23
|
+
if (toolMatch[2]) {
|
|
24
|
+
try {
|
|
25
|
+
input = JSON.parse(toolMatch[2]);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
input = { raw: toolMatch[2] };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
kind: "tool_call",
|
|
34
|
+
ts,
|
|
35
|
+
name,
|
|
36
|
+
input,
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
// Default to assistant output
|
|
41
|
+
return [{ kind: "assistant", ts, text: line }];
|
|
42
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agy-paperclip-adapter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Google Antigravity (agy) local agent adapter for Paperclip",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"paperclip",
|
|
12
|
+
"paperclip-adapter",
|
|
13
|
+
"antigravity",
|
|
14
|
+
"agy",
|
|
15
|
+
"ai-agent",
|
|
16
|
+
"gemini"
|
|
17
|
+
],
|
|
18
|
+
"paperclip": {
|
|
19
|
+
"adapterUiParser": "1.0.0"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./server": {
|
|
28
|
+
"types": "./dist/server/index.d.ts",
|
|
29
|
+
"import": "./dist/server/index.js",
|
|
30
|
+
"default": "./dist/server/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./ui-parser": {
|
|
33
|
+
"types": "./dist/ui-parser.d.ts",
|
|
34
|
+
"import": "./dist/ui-parser.js",
|
|
35
|
+
"default": "./dist/ui-parser.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc",
|
|
44
|
+
"clean": "rm -rf dist *.tgz",
|
|
45
|
+
"pack": "npm run clean && npm run build && npm pack --pack-destination=dist",
|
|
46
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@paperclipai/adapter-utils": "^2026.325.0",
|
|
50
|
+
"picocolors": "^1.1.1"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^24.0.0",
|
|
54
|
+
"typescript": "^5.7.0"
|
|
55
|
+
}
|
|
56
|
+
}
|