@paperclipai/adapter-cursor-cloud 0.3.1

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 (57) hide show
  1. package/dist/cli/format-event.d.ts +2 -0
  2. package/dist/cli/format-event.d.ts.map +1 -0
  3. package/dist/cli/format-event.js +42 -0
  4. package/dist/cli/format-event.js.map +1 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.d.ts.map +1 -0
  7. package/dist/cli/index.js +2 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/index.d.ts +4 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +34 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/server/execute.d.ts +3 -0
  14. package/dist/server/execute.d.ts.map +1 -0
  15. package/dist/server/execute.js +524 -0
  16. package/dist/server/execute.js.map +1 -0
  17. package/dist/server/execute.test.d.ts +2 -0
  18. package/dist/server/execute.test.d.ts.map +1 -0
  19. package/dist/server/execute.test.js +305 -0
  20. package/dist/server/execute.test.js.map +1 -0
  21. package/dist/server/index.d.ts +6 -0
  22. package/dist/server/index.d.ts.map +1 -0
  23. package/dist/server/index.js +68 -0
  24. package/dist/server/index.js.map +1 -0
  25. package/dist/server/session.d.ts +3 -0
  26. package/dist/server/session.d.ts.map +1 -0
  27. package/dist/server/session.js +61 -0
  28. package/dist/server/session.js.map +1 -0
  29. package/dist/server/session.test.d.ts +2 -0
  30. package/dist/server/session.test.d.ts.map +1 -0
  31. package/dist/server/session.test.js +28 -0
  32. package/dist/server/session.test.js.map +1 -0
  33. package/dist/server/test.d.ts +3 -0
  34. package/dist/server/test.d.ts.map +1 -0
  35. package/dist/server/test.js +111 -0
  36. package/dist/server/test.js.map +1 -0
  37. package/dist/ui/build-config.d.ts +3 -0
  38. package/dist/ui/build-config.d.ts.map +1 -0
  39. package/dist/ui/build-config.js +72 -0
  40. package/dist/ui/build-config.js.map +1 -0
  41. package/dist/ui/build-config.test.d.ts +2 -0
  42. package/dist/ui/build-config.test.d.ts.map +1 -0
  43. package/dist/ui/build-config.test.js +76 -0
  44. package/dist/ui/build-config.test.js.map +1 -0
  45. package/dist/ui/index.d.ts +3 -0
  46. package/dist/ui/index.d.ts.map +1 -0
  47. package/dist/ui/index.js +3 -0
  48. package/dist/ui/index.js.map +1 -0
  49. package/dist/ui/parse-stdout.d.ts +3 -0
  50. package/dist/ui/parse-stdout.d.ts.map +1 -0
  51. package/dist/ui/parse-stdout.js +173 -0
  52. package/dist/ui/parse-stdout.js.map +1 -0
  53. package/dist/ui/parse-stdout.test.d.ts +2 -0
  54. package/dist/ui/parse-stdout.test.d.ts.map +1 -0
  55. package/dist/ui/parse-stdout.test.js +119 -0
  56. package/dist/ui/parse-stdout.test.js.map +1 -0
  57. package/package.json +61 -0
@@ -0,0 +1,2 @@
1
+ export declare function printCursorCloudEvent(raw: string, _debug: boolean): void;
2
+ //# sourceMappingURL=format-event.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-event.d.ts","sourceRoot":"","sources":["../../src/cli/format-event.ts"],"names":[],"mappings":"AAGA,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAsCxE"}
@@ -0,0 +1,42 @@
1
+ import pc from "picocolors";
2
+ import { parseCursorCloudStdoutLine } from "../ui/parse-stdout.js";
3
+ export function printCursorCloudEvent(raw, _debug) {
4
+ const entries = parseCursorCloudStdoutLine(raw, new Date().toISOString());
5
+ for (const entry of entries) {
6
+ switch (entry.kind) {
7
+ case "assistant":
8
+ console.log(pc.green(`assistant: ${entry.text}`));
9
+ break;
10
+ case "thinking":
11
+ console.log(pc.gray(`thinking: ${entry.text}`));
12
+ break;
13
+ case "user":
14
+ console.log(pc.gray(`user: ${entry.text}`));
15
+ break;
16
+ case "tool_call":
17
+ console.log(pc.yellow(`tool_call: ${entry.name}`));
18
+ break;
19
+ case "tool_result":
20
+ console.log((entry.isError ? pc.red : pc.cyan)(entry.content || "tool result"));
21
+ break;
22
+ case "result":
23
+ console.log((entry.isError ? pc.red : pc.blue)(`result: ${entry.subtype}${entry.text ? ` - ${entry.text}` : ""}`));
24
+ break;
25
+ case "stderr":
26
+ console.error(pc.red(entry.text));
27
+ break;
28
+ case "system":
29
+ console.log(pc.blue(entry.text));
30
+ break;
31
+ case "init":
32
+ console.log(pc.blue(`Cursor Cloud init (${entry.sessionId})`));
33
+ break;
34
+ case "stdout":
35
+ console.log(entry.text);
36
+ break;
37
+ default:
38
+ console.log("text" in entry ? entry.text : JSON.stringify(entry));
39
+ }
40
+ }
41
+ }
42
+ //# sourceMappingURL=format-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-event.js","sourceRoot":"","sources":["../../src/cli/format-event.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAEnE,MAAM,UAAU,qBAAqB,CAAC,GAAW,EAAE,MAAe;IAChE,MAAM,OAAO,GAAG,0BAA0B,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1E,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,WAAW;gBACd,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAClD,MAAM;YACR,KAAK,UAAU;gBACb,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,aAAa,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAChD,MAAM;YACR,KAAK,MAAM;gBACT,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAC5C,MAAM;YACR,KAAK,WAAW;gBACd,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,cAAc,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBACnD,MAAM;YACR,KAAK,aAAa;gBAChB,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,aAAa,CAAC,CAAC,CAAC;gBAChF,MAAM;YACR,KAAK,QAAQ;gBACX,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,WAAW,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;gBACnH,MAAM;YACR,KAAK,QAAQ;gBACX,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBAClC,MAAM;YACR,KAAK,QAAQ;gBACX,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBACjC,MAAM;YACR,KAAK,MAAM;gBACT,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,sBAAsB,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;gBAC/D,MAAM;YACR,KAAK,QAAQ;gBACX,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACxB,MAAM;YACR;gBACE,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { printCursorCloudEvent } from "./format-event.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { printCursorCloudEvent } from "./format-event.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare const type = "cursor_cloud";
2
+ export declare const label = "Cursor Cloud";
3
+ export declare const agentConfigurationDoc = "# cursor_cloud agent configuration\n\nAdapter: cursor_cloud\n\nUse when:\n- You want Paperclip to run Cursor Cloud Agents through the official Cursor SDK\n- You want durable remote Cursor agent sessions across Paperclip heartbeats\n- You want Paperclip to keep task state while Cursor handles remote code execution\n\nCore fields:\n- repoUrl (string, required): Git repository URL Cursor should open\n- repoStartingRef (string, optional): starting ref for the repo\n- repoPullRequestUrl (string, optional): PR URL to attach the agent to\n- runtimeEnvType (string, optional): cloud | pool | machine\n- runtimeEnvName (string, optional): named cloud/pool/machine target\n- workOnCurrentBranch (boolean, optional): continue work on current branch\n- autoCreatePR (boolean, optional): let Cursor auto-create a PR\n- skipReviewerRequest (boolean, optional): suppress reviewer request on auto-created PRs\n- instructionsFilePath (string, optional): agent instructions file prepended to the prompt\n- promptTemplate (string, optional): heartbeat prompt template\n- bootstrapPromptTemplate (string, optional): first-run-only bootstrap prompt template\n- model (string, optional): Cursor model id; omit to use the account default\n- env.CURSOR_API_KEY (string, required): Cursor API key\n- env.* (optional): additional env vars injected into the cloud agent shell\n\nNotes:\n- Paperclip reuses the durable Cursor agent across heartbeats when the repo/runtime identity still matches.\n- Each Paperclip heartbeat maps to a Cursor run on that durable agent.\n- Paperclip injects PAPERCLIP_* runtime env vars into the cloud agent shell through Cursor SDK cloud envVars.\n- Paperclip remains the source of truth for issue/task state; Cursor provides the remote execution surface.\n";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,iBAAiB,CAAC;AACnC,eAAO,MAAM,KAAK,iBAAiB,CAAC;AAEpC,eAAO,MAAM,qBAAqB,4uDA8BjC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ export const type = "cursor_cloud";
2
+ export const label = "Cursor Cloud";
3
+ export const agentConfigurationDoc = `# cursor_cloud agent configuration
4
+
5
+ Adapter: cursor_cloud
6
+
7
+ Use when:
8
+ - You want Paperclip to run Cursor Cloud Agents through the official Cursor SDK
9
+ - You want durable remote Cursor agent sessions across Paperclip heartbeats
10
+ - You want Paperclip to keep task state while Cursor handles remote code execution
11
+
12
+ Core fields:
13
+ - repoUrl (string, required): Git repository URL Cursor should open
14
+ - repoStartingRef (string, optional): starting ref for the repo
15
+ - repoPullRequestUrl (string, optional): PR URL to attach the agent to
16
+ - runtimeEnvType (string, optional): cloud | pool | machine
17
+ - runtimeEnvName (string, optional): named cloud/pool/machine target
18
+ - workOnCurrentBranch (boolean, optional): continue work on current branch
19
+ - autoCreatePR (boolean, optional): let Cursor auto-create a PR
20
+ - skipReviewerRequest (boolean, optional): suppress reviewer request on auto-created PRs
21
+ - instructionsFilePath (string, optional): agent instructions file prepended to the prompt
22
+ - promptTemplate (string, optional): heartbeat prompt template
23
+ - bootstrapPromptTemplate (string, optional): first-run-only bootstrap prompt template
24
+ - model (string, optional): Cursor model id; omit to use the account default
25
+ - env.CURSOR_API_KEY (string, required): Cursor API key
26
+ - env.* (optional): additional env vars injected into the cloud agent shell
27
+
28
+ Notes:
29
+ - Paperclip reuses the durable Cursor agent across heartbeats when the repo/runtime identity still matches.
30
+ - Each Paperclip heartbeat maps to a Cursor run on that durable agent.
31
+ - Paperclip injects PAPERCLIP_* runtime env vars into the cloud agent shell through Cursor SDK cloud envVars.
32
+ - Paperclip remains the source of truth for issue/task state; Cursor provides the remote execution surface.
33
+ `;
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,IAAI,GAAG,cAAc,CAAC;AACnC,MAAM,CAAC,MAAM,KAAK,GAAG,cAAc,CAAC;AAEpC,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BpC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { AdapterExecutionContext, AdapterExecutionResult } from "@paperclipai/adapter-utils";
2
+ export declare function execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult>;
3
+ //# sourceMappingURL=execute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/server/execute.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAsB,EAAyB,MAAM,4BAA4B,CAAC;AAqTzH,wBAAsB,OAAO,CAAC,GAAG,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA8R3F"}
@@ -0,0 +1,524 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { Agent, } from "@cursor/sdk";
4
+ import { DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE, asBoolean, asString, buildPaperclipEnv, joinPromptSections, parseObject, readPaperclipIssueWorkModeFromContext, renderPaperclipWakePrompt, renderTemplate, stringifyPaperclipWakePayload, } from "@paperclipai/adapter-utils/server-utils";
5
+ function asRecord(value) {
6
+ if (typeof value !== "object" || value === null || Array.isArray(value))
7
+ return null;
8
+ return value;
9
+ }
10
+ function asStringEnvMap(value) {
11
+ const parsed = parseObject(value);
12
+ const env = {};
13
+ for (const [key, entry] of Object.entries(parsed)) {
14
+ if (typeof entry === "string") {
15
+ env[key] = entry;
16
+ }
17
+ else if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
18
+ const rec = entry;
19
+ if (rec.type === "plain" && typeof rec.value === "string")
20
+ env[key] = rec.value;
21
+ }
22
+ }
23
+ return env;
24
+ }
25
+ function normalizeEnvType(raw) {
26
+ const value = raw.trim().toLowerCase();
27
+ if (value === "pool" || value === "machine")
28
+ return value;
29
+ return "cloud";
30
+ }
31
+ function trimNullable(value) {
32
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
33
+ }
34
+ function firstNonEmptyLine(text) {
35
+ return (text
36
+ .split(/\r?\n/)
37
+ .map((line) => line.trim())
38
+ .find(Boolean) ?? "");
39
+ }
40
+ function toModelSelection(rawModel) {
41
+ const model = rawModel.trim();
42
+ return model ? { id: model } : undefined;
43
+ }
44
+ function toSummary(result) {
45
+ const direct = trimNullable(result.result);
46
+ if (direct)
47
+ return firstNonEmptyLine(direct);
48
+ return null;
49
+ }
50
+ function formatRunError(err) {
51
+ if (err instanceof Error && err.message.trim().length > 0)
52
+ return err.message.trim();
53
+ return String(err);
54
+ }
55
+ function buildWakeEnv(ctx, configEnv) {
56
+ const { runId, agent, context, authToken } = ctx;
57
+ const env = {
58
+ ...configEnv,
59
+ ...buildPaperclipEnv(agent),
60
+ PAPERCLIP_RUN_ID: runId,
61
+ };
62
+ const wakeTaskId = trimNullable(context.taskId) ?? trimNullable(context.issueId);
63
+ const wakeReason = trimNullable(context.wakeReason);
64
+ const wakeCommentId = trimNullable(context.wakeCommentId) ?? trimNullable(context.commentId);
65
+ const approvalId = trimNullable(context.approvalId);
66
+ const approvalStatus = trimNullable(context.approvalStatus);
67
+ const linkedIssueIds = Array.isArray(context.issueIds)
68
+ ? context.issueIds.filter((value) => typeof value === "string" && value.trim().length > 0)
69
+ : [];
70
+ const wakePayloadJson = stringifyPaperclipWakePayload(context.paperclipWake);
71
+ const issueWorkMode = readPaperclipIssueWorkModeFromContext(context);
72
+ if (wakeTaskId)
73
+ env.PAPERCLIP_TASK_ID = wakeTaskId;
74
+ if (wakeReason)
75
+ env.PAPERCLIP_WAKE_REASON = wakeReason;
76
+ if (wakeCommentId)
77
+ env.PAPERCLIP_WAKE_COMMENT_ID = wakeCommentId;
78
+ if (approvalId)
79
+ env.PAPERCLIP_APPROVAL_ID = approvalId;
80
+ if (approvalStatus)
81
+ env.PAPERCLIP_APPROVAL_STATUS = approvalStatus;
82
+ if (linkedIssueIds.length > 0)
83
+ env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(",");
84
+ if (wakePayloadJson)
85
+ env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayloadJson;
86
+ if (issueWorkMode)
87
+ env.PAPERCLIP_ISSUE_WORK_MODE = issueWorkMode;
88
+ if (!trimNullable(env.PAPERCLIP_API_KEY) && authToken) {
89
+ env.PAPERCLIP_API_KEY = authToken;
90
+ }
91
+ const workspace = parseObject(context.paperclipWorkspace);
92
+ const workspaceMappings = [
93
+ ["PAPERCLIP_WORKSPACE_CWD", workspace.cwd],
94
+ ["PAPERCLIP_WORKSPACE_SOURCE", workspace.source],
95
+ ["PAPERCLIP_WORKSPACE_ID", workspace.workspaceId],
96
+ ["PAPERCLIP_WORKSPACE_REPO_URL", workspace.repoUrl],
97
+ ["PAPERCLIP_WORKSPACE_REPO_REF", workspace.repoRef],
98
+ ["PAPERCLIP_WORKSPACE_BRANCH", workspace.branch],
99
+ ["PAPERCLIP_WORKSPACE_WORKTREE_PATH", workspace.worktreePath],
100
+ ["AGENT_HOME", workspace.agentHome],
101
+ ];
102
+ for (const [key, value] of workspaceMappings) {
103
+ const normalized = trimNullable(value);
104
+ if (normalized)
105
+ env[key] = normalized;
106
+ }
107
+ delete env.CURSOR_API_KEY;
108
+ return env;
109
+ }
110
+ async function buildInstructionsPrefix(config, onLog) {
111
+ const instructionsFilePath = asString(config.instructionsFilePath, "").trim();
112
+ if (!instructionsFilePath) {
113
+ return { prefix: "", notes: [], chars: 0 };
114
+ }
115
+ try {
116
+ const contents = await fs.readFile(instructionsFilePath, "utf8");
117
+ const instructionsDir = `${path.dirname(instructionsFilePath)}/`;
118
+ const prefix = `${contents.trim()}\n\nThe above agent instructions were loaded from ${instructionsFilePath}. Resolve any relative file references from ${instructionsDir}.\n`;
119
+ return {
120
+ prefix,
121
+ chars: prefix.length,
122
+ notes: [
123
+ `Loaded agent instructions from ${instructionsFilePath}`,
124
+ `Prepended instructions + path directive to prompt (relative references from ${instructionsDir}).`,
125
+ ],
126
+ };
127
+ }
128
+ catch (err) {
129
+ const reason = err instanceof Error ? err.message : String(err);
130
+ await onLog("stderr", `[paperclip] Warning: could not read agent instructions file "${instructionsFilePath}": ${reason}\n`);
131
+ return {
132
+ prefix: "",
133
+ chars: 0,
134
+ notes: [
135
+ `Configured instructionsFilePath ${instructionsFilePath}, but file could not be read; continuing without injected instructions.`,
136
+ ],
137
+ };
138
+ }
139
+ }
140
+ function renderPaperclipEnvNote(env) {
141
+ const keys = Object.keys(env)
142
+ .filter((key) => key.startsWith("PAPERCLIP_"))
143
+ .sort();
144
+ if (keys.length === 0)
145
+ return "";
146
+ return [
147
+ "Paperclip runtime note:",
148
+ `The following PAPERCLIP_* environment variables are available in the cloud agent shell: ${keys.join(", ")}`,
149
+ "Use them directly instead of assuming they are absent.",
150
+ ].join("\n");
151
+ }
152
+ function readSession(params) {
153
+ if (!params)
154
+ return null;
155
+ const record = asRecord(params);
156
+ if (!record)
157
+ return null;
158
+ const cursorAgentId = trimNullable(record.cursorAgentId) ??
159
+ trimNullable(record.agentId) ??
160
+ trimNullable(record.sessionId);
161
+ if (!cursorAgentId)
162
+ return null;
163
+ const latestRunId = trimNullable(record.latestRunId) ?? trimNullable(record.runId) ?? undefined;
164
+ const envType = trimNullable(record.envType);
165
+ const envName = trimNullable(record.envName);
166
+ const reposValue = Array.isArray(record.repos) ? record.repos : [];
167
+ const repos = reposValue
168
+ .map((entry) => asRecord(entry))
169
+ .filter((entry) => Boolean(entry))
170
+ .map((entry) => ({
171
+ url: asString(entry.url, "").trim(),
172
+ startingRef: trimNullable(entry.startingRef) ?? undefined,
173
+ prUrl: trimNullable(entry.prUrl) ?? undefined,
174
+ }))
175
+ .filter((entry) => entry.url.length > 0);
176
+ return {
177
+ cursorAgentId,
178
+ ...(latestRunId ? { latestRunId } : {}),
179
+ runtime: "cloud",
180
+ ...(envType ? { envType: normalizeEnvType(envType) } : {}),
181
+ ...(envName ? { envName } : {}),
182
+ repos,
183
+ };
184
+ }
185
+ function sessionMatches(session, envType, envName, repos) {
186
+ if (!session)
187
+ return false;
188
+ if ((session.envType ?? "cloud") !== envType)
189
+ return false;
190
+ if ((session.envName ?? null) !== envName)
191
+ return false;
192
+ if (session.repos.length !== repos.length)
193
+ return false;
194
+ return session.repos.every((repo, index) => {
195
+ const next = repos[index];
196
+ return repo.url === next.url
197
+ && (repo.startingRef ?? null) === (next.startingRef ?? null)
198
+ && (repo.prUrl ?? null) === (next.prUrl ?? null);
199
+ });
200
+ }
201
+ function buildAgentOptions(input) {
202
+ return {
203
+ apiKey: input.apiKey,
204
+ name: input.name,
205
+ ...(input.model ? { model: input.model } : {}),
206
+ cloud: {
207
+ env: {
208
+ type: input.envType,
209
+ ...(input.envName ? { name: input.envName } : {}),
210
+ },
211
+ repos: input.repos,
212
+ workOnCurrentBranch: input.workOnCurrentBranch,
213
+ autoCreatePR: input.autoCreatePR,
214
+ skipReviewerRequest: input.skipReviewerRequest,
215
+ envVars: input.envVars,
216
+ },
217
+ };
218
+ }
219
+ function eventLine(event) {
220
+ return `${JSON.stringify(event)}\n`;
221
+ }
222
+ async function emitMessage(onLog, message) {
223
+ await onLog("stdout", eventLine({ type: "cursor_cloud.message", message }));
224
+ }
225
+ async function emitStatus(onLog, status, message) {
226
+ await onLog("stdout", eventLine({ type: "cursor_cloud.status", status, ...(message ? { message } : {}) }));
227
+ }
228
+ async function streamRun(run, onLog) {
229
+ if (!run.supports("stream"))
230
+ return;
231
+ for await (const message of run.stream()) {
232
+ await emitMessage(onLog, message);
233
+ }
234
+ }
235
+ async function getAttachedRun(input) {
236
+ const latestRunId = input.session?.latestRunId;
237
+ const cursorAgentId = input.session?.cursorAgentId;
238
+ if (!latestRunId || !cursorAgentId)
239
+ return null;
240
+ try {
241
+ const run = await Agent.getRun(latestRunId, {
242
+ runtime: "cloud",
243
+ agentId: cursorAgentId,
244
+ apiKey: input.apiKey,
245
+ });
246
+ return run.status === "running" ? run : null;
247
+ }
248
+ catch {
249
+ return null;
250
+ }
251
+ }
252
+ export async function execute(ctx) {
253
+ const { runId, agent, runtime, config, context, onLog, onMeta } = ctx;
254
+ const envConfig = asStringEnvMap(config.env);
255
+ const apiKey = asString(envConfig.CURSOR_API_KEY, "").trim();
256
+ if (!apiKey) {
257
+ return {
258
+ exitCode: 1,
259
+ signal: null,
260
+ timedOut: false,
261
+ errorMessage: "CURSOR_API_KEY is required for cursor_cloud.",
262
+ provider: "cursor",
263
+ biller: "cursor",
264
+ billingType: "api",
265
+ clearSession: false,
266
+ };
267
+ }
268
+ const workspace = parseObject(context.paperclipWorkspace);
269
+ const repoUrl = asString(config.repoUrl, "").trim() ||
270
+ asString(workspace.repoUrl, "").trim();
271
+ if (!repoUrl) {
272
+ return {
273
+ exitCode: 1,
274
+ signal: null,
275
+ timedOut: false,
276
+ errorMessage: "cursor_cloud requires repoUrl in adapterConfig or workspace context.",
277
+ provider: "cursor",
278
+ biller: "cursor",
279
+ billingType: "api",
280
+ clearSession: false,
281
+ };
282
+ }
283
+ const repoStartingRef = trimNullable(config.repoStartingRef) ??
284
+ trimNullable(workspace.repoRef) ??
285
+ undefined;
286
+ const repoPullRequestUrl = trimNullable(config.repoPullRequestUrl) ?? undefined;
287
+ const envType = normalizeEnvType(asString(config.runtimeEnvType, "cloud"));
288
+ const envName = trimNullable(config.runtimeEnvName);
289
+ const workOnCurrentBranch = asBoolean(config.workOnCurrentBranch, false);
290
+ const autoCreatePR = asBoolean(config.autoCreatePR, false);
291
+ const skipReviewerRequest = asBoolean(config.skipReviewerRequest, false);
292
+ const model = toModelSelection(asString(config.model, ""));
293
+ const repos = [{
294
+ url: repoUrl,
295
+ ...(repoStartingRef ? { startingRef: repoStartingRef } : {}),
296
+ ...(repoPullRequestUrl ? { prUrl: repoPullRequestUrl } : {}),
297
+ }];
298
+ const remoteEnv = buildWakeEnv(ctx, envConfig);
299
+ const session = readSession(runtime.sessionParams) ?? (runtime.sessionId
300
+ ? {
301
+ cursorAgentId: runtime.sessionId,
302
+ runtime: "cloud",
303
+ repos,
304
+ }
305
+ : null);
306
+ const canReuseSession = sessionMatches(session, envType, envName, repos);
307
+ const promptTemplate = asString(config.promptTemplate, DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE);
308
+ const bootstrapPromptTemplate = asString(config.bootstrapPromptTemplate, "");
309
+ const templateData = {
310
+ agentId: agent.id,
311
+ companyId: agent.companyId,
312
+ runId,
313
+ company: { id: agent.companyId },
314
+ agent,
315
+ run: { id: runId, source: "on_demand" },
316
+ context,
317
+ };
318
+ const instructions = await buildInstructionsPrefix(config, onLog);
319
+ const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, { resumedSession: canReuseSession });
320
+ const renderedBootstrapPrompt = !canReuseSession && bootstrapPromptTemplate.trim().length > 0
321
+ ? renderTemplate(bootstrapPromptTemplate, templateData).trim()
322
+ : "";
323
+ const renderedPrompt = canReuseSession && wakePrompt.length > 0
324
+ ? ""
325
+ : renderTemplate(promptTemplate, templateData).trim();
326
+ const paperclipEnvNote = renderPaperclipEnvNote(remoteEnv);
327
+ const prompt = joinPromptSections([
328
+ instructions.prefix,
329
+ renderedBootstrapPrompt,
330
+ wakePrompt,
331
+ paperclipEnvNote,
332
+ renderedPrompt,
333
+ ]);
334
+ const sessionHandoffNote = asString(context.paperclipSessionHandoffMarkdown, "").trim();
335
+ const finalPrompt = joinPromptSections([prompt, sessionHandoffNote]);
336
+ const agentOptions = buildAgentOptions({
337
+ apiKey,
338
+ name: `Paperclip ${agent.name}`,
339
+ model,
340
+ envType,
341
+ envName,
342
+ repos,
343
+ workOnCurrentBranch,
344
+ autoCreatePR,
345
+ skipReviewerRequest,
346
+ envVars: remoteEnv,
347
+ });
348
+ const commandNotes = [
349
+ ...instructions.notes,
350
+ canReuseSession
351
+ ? `Reusing Cursor cloud agent session ${session?.cursorAgentId ?? "unknown"}`
352
+ : "Creating a new Cursor cloud agent session",
353
+ `Repository: ${repoUrl}${repoStartingRef ? ` @ ${repoStartingRef}` : ""}`,
354
+ `Runtime target: ${envType}${envName ? ` (${envName})` : ""}`,
355
+ ];
356
+ if (onMeta) {
357
+ const meta = {
358
+ adapterType: "cursor_cloud",
359
+ command: "@cursor/sdk",
360
+ commandNotes,
361
+ prompt: finalPrompt,
362
+ promptMetrics: {
363
+ promptChars: finalPrompt.length,
364
+ instructionsChars: instructions.chars,
365
+ bootstrapPromptChars: renderedBootstrapPrompt.length,
366
+ wakePromptChars: wakePrompt.length,
367
+ heartbeatPromptChars: renderedPrompt.length,
368
+ },
369
+ context: {
370
+ cursorCloud: {
371
+ envType,
372
+ envName,
373
+ repoUrl,
374
+ repoStartingRef,
375
+ repoPullRequestUrl,
376
+ canReuseSession,
377
+ },
378
+ },
379
+ };
380
+ await onMeta(meta);
381
+ }
382
+ let sdkAgent = null;
383
+ let run = null;
384
+ let streamError = null;
385
+ try {
386
+ const attachedRun = canReuseSession
387
+ ? await getAttachedRun({ apiKey, session })
388
+ : null;
389
+ if (attachedRun) {
390
+ await emitStatus(onLog, "running", `Reattached to existing Cursor run ${attachedRun.id}.`);
391
+ await onLog("stdout", eventLine({
392
+ type: "cursor_cloud.init",
393
+ sessionId: attachedRun.agentId,
394
+ agentId: attachedRun.agentId,
395
+ runId: attachedRun.id,
396
+ ...(model?.id ? { model: model.id } : {}),
397
+ }));
398
+ const priorStreamPromise = streamRun(attachedRun, onLog).catch((err) => {
399
+ streamError = formatRunError(err);
400
+ });
401
+ if (attachedRun.supports("wait"))
402
+ await attachedRun.wait();
403
+ await priorStreamPromise;
404
+ streamError = null;
405
+ await emitStatus(onLog, "running", `Prior Cursor run ${attachedRun.id} finished; sending heartbeat follow-up so this wake's context is not dropped.`);
406
+ }
407
+ sdkAgent = canReuseSession && session
408
+ ? await Agent.resume(session.cursorAgentId, agentOptions)
409
+ : await Agent.create(agentOptions);
410
+ run = await sdkAgent.send(finalPrompt, {
411
+ ...(model ? { model } : {}),
412
+ });
413
+ await onLog("stdout", eventLine({
414
+ type: "cursor_cloud.init",
415
+ sessionId: sdkAgent.agentId,
416
+ agentId: sdkAgent.agentId,
417
+ runId: run.id,
418
+ ...(model?.id ? { model: model.id } : {}),
419
+ }));
420
+ await emitStatus(onLog, "running", `Started Cursor run ${run.id}.`);
421
+ const streamPromise = streamRun(run, onLog).catch((err) => {
422
+ streamError = formatRunError(err);
423
+ });
424
+ const result = run.supports("wait")
425
+ ? await run.wait()
426
+ : {
427
+ id: run.id,
428
+ status: run.status === "running" ? "error" : run.status,
429
+ result: run.result,
430
+ model: run.model,
431
+ durationMs: run.durationMs,
432
+ git: run.git,
433
+ };
434
+ await streamPromise;
435
+ const modelId = result.model?.id ?? model?.id ?? null;
436
+ await onLog("stdout", eventLine({
437
+ type: "cursor_cloud.result",
438
+ status: result.status,
439
+ ...(result.result ? { result: result.result } : {}),
440
+ ...(modelId ? { model: modelId } : {}),
441
+ ...(typeof result.durationMs === "number" ? { durationMs: result.durationMs } : {}),
442
+ ...(result.git ? { git: result.git } : {}),
443
+ ...(streamError ? { error: streamError } : {}),
444
+ }));
445
+ const nextSession = {
446
+ cursorAgentId: run.agentId,
447
+ latestRunId: result.id,
448
+ runtime: "cloud",
449
+ envType,
450
+ ...(envName ? { envName } : {}),
451
+ repos,
452
+ };
453
+ const isError = result.status !== "finished";
454
+ return {
455
+ exitCode: isError ? 1 : 0,
456
+ signal: null,
457
+ timedOut: false,
458
+ errorMessage: isError ? (trimNullable(result.result) ?? streamError ?? `Cursor run ${result.status}`) : null,
459
+ sessionId: run.agentId,
460
+ sessionDisplayId: run.agentId,
461
+ sessionParams: nextSession,
462
+ provider: "cursor",
463
+ biller: "cursor",
464
+ billingType: "api",
465
+ model: modelId,
466
+ costUsd: null,
467
+ summary: toSummary(result),
468
+ resultJson: {
469
+ status: result.status,
470
+ cursorAgentId: run.agentId,
471
+ cursorRunId: result.id,
472
+ envType,
473
+ envName,
474
+ repos,
475
+ ...(result.result ? { result: result.result } : {}),
476
+ ...(result.git ? { git: result.git } : {}),
477
+ ...(typeof result.durationMs === "number" ? { durationMs: result.durationMs } : {}),
478
+ ...(streamError ? { streamError } : {}),
479
+ },
480
+ clearSession: false,
481
+ };
482
+ }
483
+ catch (err) {
484
+ const reason = formatRunError(err);
485
+ if (run) {
486
+ await onLog("stdout", eventLine({
487
+ type: "cursor_cloud.result",
488
+ status: "error",
489
+ error: reason,
490
+ }));
491
+ }
492
+ return {
493
+ exitCode: 1,
494
+ signal: null,
495
+ timedOut: false,
496
+ errorMessage: reason,
497
+ sessionId: session?.cursorAgentId ?? null,
498
+ sessionDisplayId: session?.cursorAgentId ?? null,
499
+ sessionParams: session,
500
+ provider: "cursor",
501
+ biller: "cursor",
502
+ billingType: "api",
503
+ costUsd: null,
504
+ clearSession: false,
505
+ resultJson: {
506
+ status: "error",
507
+ ...(run ? { cursorRunId: run.id } : {}),
508
+ ...(session?.cursorAgentId ? { cursorAgentId: session.cursorAgentId } : {}),
509
+ error: reason,
510
+ },
511
+ };
512
+ }
513
+ finally {
514
+ if (sdkAgent) {
515
+ try {
516
+ await sdkAgent[Symbol.asyncDispose]();
517
+ }
518
+ catch {
519
+ // Best effort only.
520
+ }
521
+ }
522
+ }
523
+ }
524
+ //# sourceMappingURL=execute.js.map