@tickernelz/paperclip-pro-adapter-cursor-local 2026.925.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.
Files changed (74) hide show
  1. package/LICENSE +22 -0
  2. package/dist/cli/format-event.d.ts +2 -0
  3. package/dist/cli/format-event.d.ts.map +1 -0
  4. package/dist/cli/format-event.js +296 -0
  5. package/dist/cli/format-event.js.map +1 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +2 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +102 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/server/execute.d.ts +15 -0
  15. package/dist/server/execute.d.ts.map +1 -0
  16. package/dist/server/execute.js +640 -0
  17. package/dist/server/execute.js.map +1 -0
  18. package/dist/server/execute.remote.test.d.ts +2 -0
  19. package/dist/server/execute.remote.test.d.ts.map +1 -0
  20. package/dist/server/execute.remote.test.js +284 -0
  21. package/dist/server/execute.remote.test.js.map +1 -0
  22. package/dist/server/execute.test.d.ts +2 -0
  23. package/dist/server/execute.test.d.ts.map +1 -0
  24. package/dist/server/execute.test.js +294 -0
  25. package/dist/server/execute.test.js.map +1 -0
  26. package/dist/server/index.d.ts +7 -0
  27. package/dist/server/index.d.ts.map +1 -0
  28. package/dist/server/index.js +62 -0
  29. package/dist/server/index.js.map +1 -0
  30. package/dist/server/parse.d.ts +13 -0
  31. package/dist/server/parse.d.ts.map +1 -0
  32. package/dist/server/parse.js +139 -0
  33. package/dist/server/parse.js.map +1 -0
  34. package/dist/server/remote-command.d.ts +20 -0
  35. package/dist/server/remote-command.d.ts.map +1 -0
  36. package/dist/server/remote-command.js +172 -0
  37. package/dist/server/remote-command.js.map +1 -0
  38. package/dist/server/remote-command.test.d.ts +2 -0
  39. package/dist/server/remote-command.test.d.ts.map +1 -0
  40. package/dist/server/remote-command.test.js +123 -0
  41. package/dist/server/remote-command.test.js.map +1 -0
  42. package/dist/server/skills.d.ts +8 -0
  43. package/dist/server/skills.d.ts.map +1 -0
  44. package/dist/server/skills.js +69 -0
  45. package/dist/server/skills.js.map +1 -0
  46. package/dist/server/test.d.ts +10 -0
  47. package/dist/server/test.d.ts.map +1 -0
  48. package/dist/server/test.js +334 -0
  49. package/dist/server/test.js.map +1 -0
  50. package/dist/server/test.test.d.ts +2 -0
  51. package/dist/server/test.test.d.ts.map +1 -0
  52. package/dist/server/test.test.js +116 -0
  53. package/dist/server/test.test.js.map +1 -0
  54. package/dist/shared/stream.d.ts +5 -0
  55. package/dist/shared/stream.d.ts.map +1 -0
  56. package/dist/shared/stream.js +13 -0
  57. package/dist/shared/stream.js.map +1 -0
  58. package/dist/shared/trust.d.ts +2 -0
  59. package/dist/shared/trust.d.ts.map +1 -0
  60. package/dist/shared/trust.js +7 -0
  61. package/dist/shared/trust.js.map +1 -0
  62. package/dist/ui/build-config.d.ts +3 -0
  63. package/dist/ui/build-config.d.ts.map +1 -0
  64. package/dist/ui/build-config.js +36 -0
  65. package/dist/ui/build-config.js.map +1 -0
  66. package/dist/ui/index.d.ts +3 -0
  67. package/dist/ui/index.d.ts.map +1 -0
  68. package/dist/ui/index.js +3 -0
  69. package/dist/ui/index.js.map +1 -0
  70. package/dist/ui/parse-stdout.d.ts +3 -0
  71. package/dist/ui/parse-stdout.d.ts.map +1 -0
  72. package/dist/ui/parse-stdout.js +378 -0
  73. package/dist/ui/parse-stdout.js.map +1 -0
  74. package/package.json +58 -0
@@ -0,0 +1,640 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { inferOpenAiCompatibleBiller } from "@tickernelz/paperclip-pro-adapter-utils";
6
+ import { adapterExecutionTargetIsRemote, adapterExecutionTargetRemoteCwd, overrideAdapterExecutionTargetRemoteCwd, adapterExecutionTargetSessionIdentity, adapterExecutionTargetSessionMatches, adapterExecutionTargetUsesManagedHome, adapterExecutionTargetUsesPaperclipBridge, describeAdapterExecutionTarget, ensureAdapterExecutionTargetCommandResolvable, ensureAdapterExecutionTargetRuntimeCommandInstalled, prepareAdapterExecutionTargetRuntime, adapterExecutionTargetDuplexObservabilityRecorder, adapterExecutionTargetEnablesSandboxDuplexBridge, readAdapterExecutionTarget, readAdapterExecutionTargetHomeDir, resolveAdapterExecutionTargetTimeoutSec, resolveAdapterExecutionTargetCommandForLogs, runAdapterExecutionTargetProcess, runAdapterExecutionTargetShellCommand, startAdapterExecutionTargetPaperclipBridge, } from "@tickernelz/paperclip-pro-adapter-utils/execution-target";
7
+ import { asString, asNumber, asStringArray, parseObject, buildPaperclipEnv, buildRuntimeToolsEnv, buildInvocationEnvForLogs, ensureAbsoluteDirectory, ensurePaperclipSkillSymlink, ensurePathInEnv, refreshPaperclipWorkspaceEnvForExecution, isPaperclipSkillSourceMissing, readPaperclipRuntimeSkillEntries, readPaperclipIssueWorkModeFromContext, resolveLegacyPaperclipDesiredSkillNames, removeMaintainerOnlySkillSymlinks, renderTemplate, renderPaperclipWakePrompt, selectPaperclipTaskMarkdown, selectInitialCommunicationGuidance, isPaperclipRecoveryWakePayload, DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE, DEFAULT_PAPERCLIP_CONVERSATION_PROMPT_TEMPLATE, joinPromptSections, } from "@tickernelz/paperclip-pro-adapter-utils/server-utils";
8
+ import { DEFAULT_CURSOR_LOCAL_MODEL, SANDBOX_INSTALL_COMMAND } from "../index.js";
9
+ import { parseCursorJsonl, isCursorUnknownSessionError } from "./parse.js";
10
+ import { prepareCursorSandboxCommand } from "./remote-command.js";
11
+ import { normalizeCursorStreamLine } from "../shared/stream.js";
12
+ import { hasCursorTrustBypassArg } from "../shared/trust.js";
13
+ import { resolveCursorSkillsHome } from "./skills.js";
14
+ const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
15
+ function firstNonEmptyLine(text) {
16
+ return (text
17
+ .split(/\r?\n/)
18
+ .map((line) => line.trim())
19
+ .find(Boolean) ?? "");
20
+ }
21
+ function hasNonEmptyEnvValue(env, key) {
22
+ const raw = env[key];
23
+ return typeof raw === "string" && raw.trim().length > 0;
24
+ }
25
+ function resolveCursorBillingType(env) {
26
+ return hasNonEmptyEnvValue(env, "CURSOR_API_KEY") || hasNonEmptyEnvValue(env, "OPENAI_API_KEY")
27
+ ? "api"
28
+ : "subscription";
29
+ }
30
+ function resolveCursorBiller(env, billingType, provider) {
31
+ const openAiCompatibleBiller = inferOpenAiCompatibleBiller(env, null);
32
+ if (openAiCompatibleBiller === "openrouter")
33
+ return "openrouter";
34
+ if (billingType === "subscription")
35
+ return "cursor";
36
+ return provider ?? "cursor";
37
+ }
38
+ function resolveProviderFromModel(model) {
39
+ const trimmed = model.trim().toLowerCase();
40
+ if (!trimmed)
41
+ return null;
42
+ const slash = trimmed.indexOf("/");
43
+ if (slash > 0)
44
+ return trimmed.slice(0, slash);
45
+ if (trimmed.includes("sonnet") || trimmed.includes("claude"))
46
+ return "anthropic";
47
+ if (trimmed.startsWith("gpt") || trimmed.startsWith("o"))
48
+ return "openai";
49
+ return null;
50
+ }
51
+ function normalizeMode(rawMode) {
52
+ const mode = rawMode.trim().toLowerCase();
53
+ if (mode === "plan" || mode === "ask")
54
+ return mode;
55
+ return null;
56
+ }
57
+ function renderPaperclipEnvNote(env) {
58
+ const paperclipKeys = Object.keys(env)
59
+ .filter((key) => key.startsWith("PAPERCLIP_"))
60
+ .sort();
61
+ if (paperclipKeys.length === 0)
62
+ return "";
63
+ return [
64
+ "Paperclip runtime note:",
65
+ `The following PAPERCLIP_* environment variables are available in this run: ${paperclipKeys.join(", ")}`,
66
+ "Do not assume these variables are missing without checking your shell environment.",
67
+ "",
68
+ "",
69
+ ].join("\n");
70
+ }
71
+ async function buildCursorSkillsDir(config) {
72
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-cursor-skills-"));
73
+ const target = path.join(tmp, "skills");
74
+ await fs.mkdir(target, { recursive: true });
75
+ const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir);
76
+ const desiredNames = new Set(resolveLegacyPaperclipDesiredSkillNames(config, availableEntries));
77
+ for (const entry of availableEntries) {
78
+ if (!desiredNames.has(entry.key))
79
+ continue;
80
+ if (isPaperclipSkillSourceMissing(entry))
81
+ continue;
82
+ await fs.symlink(entry.source, path.join(target, entry.runtimeName));
83
+ }
84
+ return target;
85
+ }
86
+ export async function ensureCursorSkillsInjected(onLog, options = {}) {
87
+ const skillsEntries = options.skillsEntries
88
+ ?? (options.skillsDir
89
+ ? (await fs.readdir(options.skillsDir, { withFileTypes: true }))
90
+ .filter((entry) => entry.isDirectory())
91
+ .map((entry) => ({
92
+ key: entry.name,
93
+ runtimeName: entry.name,
94
+ source: path.join(options.skillsDir, entry.name),
95
+ }))
96
+ : await readPaperclipRuntimeSkillEntries({}, __moduleDir));
97
+ if (skillsEntries.length === 0)
98
+ return;
99
+ const skillsHome = options.skillsHome ?? resolveCursorSkillsHome({});
100
+ try {
101
+ await fs.mkdir(skillsHome, { recursive: true });
102
+ }
103
+ catch (err) {
104
+ await onLog("stderr", `[paperclip] Failed to prepare Cursor skills directory ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`);
105
+ return;
106
+ }
107
+ const removedSkills = await removeMaintainerOnlySkillSymlinks(skillsHome, skillsEntries.map((entry) => entry.runtimeName));
108
+ for (const skillName of removedSkills) {
109
+ await onLog("stderr", `[paperclip] Removed maintainer-only Cursor skill "${skillName}" from ${skillsHome}\n`);
110
+ }
111
+ const linkSkill = options.linkSkill ?? ((source, target) => fs.symlink(source, target));
112
+ for (const entry of skillsEntries) {
113
+ const target = path.join(skillsHome, entry.runtimeName);
114
+ try {
115
+ const result = await ensurePaperclipSkillSymlink(entry.source, target, linkSkill);
116
+ if (result === "skipped")
117
+ continue;
118
+ await onLog("stderr", `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Cursor skill "${entry.key}" into ${skillsHome}\n`);
119
+ }
120
+ catch (err) {
121
+ await onLog("stderr", `[paperclip] Failed to inject Cursor skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`);
122
+ }
123
+ }
124
+ }
125
+ export async function execute(ctx) {
126
+ const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx;
127
+ const executionTarget = readAdapterExecutionTarget({
128
+ executionTarget: ctx.executionTarget,
129
+ legacyRemoteExecution: ctx.executionTransport?.remoteExecution,
130
+ });
131
+ const executionTargetIsRemote = adapterExecutionTargetIsRemote(executionTarget);
132
+ const promptTemplate = asString(config.promptTemplate, context.conversationMode === true
133
+ ? DEFAULT_PAPERCLIP_CONVERSATION_PROMPT_TEMPLATE
134
+ : DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE);
135
+ let command = asString(config.command, "agent");
136
+ const model = asString(config.model, DEFAULT_CURSOR_LOCAL_MODEL).trim();
137
+ const mode = normalizeMode(asString(config.mode, ""));
138
+ const workspaceContext = parseObject(context.paperclipWorkspace);
139
+ const workspaceCwd = asString(workspaceContext.cwd, "");
140
+ const workspaceSource = asString(workspaceContext.source, "");
141
+ const workspaceId = asString(workspaceContext.workspaceId, "");
142
+ const workspaceRepoUrl = asString(workspaceContext.repoUrl, "");
143
+ const workspaceRepoRef = asString(workspaceContext.repoRef, "");
144
+ const agentHome = asString(workspaceContext.agentHome, "");
145
+ const workspaceHints = Array.isArray(context.paperclipWorkspaces)
146
+ ? context.paperclipWorkspaces.filter((value) => typeof value === "object" && value !== null)
147
+ : [];
148
+ const configuredCwd = asString(config.cwd, "");
149
+ const useConfiguredInsteadOfAgentHome = workspaceSource === "agent_home" && configuredCwd.length > 0;
150
+ const effectiveWorkspaceCwd = useConfiguredInsteadOfAgentHome ? "" : workspaceCwd;
151
+ const cwd = effectiveWorkspaceCwd || configuredCwd || process.cwd();
152
+ let effectiveExecutionCwd = adapterExecutionTargetRemoteCwd(executionTarget, cwd);
153
+ await ensureAbsoluteDirectory(cwd, { createIfMissing: true });
154
+ const cursorSkillEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir);
155
+ const desiredCursorSkillNames = resolveLegacyPaperclipDesiredSkillNames(config, cursorSkillEntries);
156
+ if (!executionTargetIsRemote) {
157
+ await ensureCursorSkillsInjected(onLog, {
158
+ skillsEntries: cursorSkillEntries.filter((entry) => desiredCursorSkillNames.includes(entry.key) && !isPaperclipSkillSourceMissing(entry)),
159
+ skillsHome: resolveCursorSkillsHome(config),
160
+ });
161
+ }
162
+ const envConfig = parseObject(config.env);
163
+ let env = {
164
+ ...buildPaperclipEnv(agent),
165
+ ...buildRuntimeToolsEnv(ctx.runtimeTools),
166
+ };
167
+ env.PAPERCLIP_RUN_ID = runId;
168
+ const wakeTaskId = (typeof context.taskId === "string" && context.taskId.trim().length > 0 && context.taskId.trim()) ||
169
+ (typeof context.issueId === "string" && context.issueId.trim().length > 0 && context.issueId.trim()) ||
170
+ null;
171
+ const wakeReason = typeof context.wakeReason === "string" && context.wakeReason.trim().length > 0
172
+ ? context.wakeReason.trim()
173
+ : null;
174
+ const wakeCommentId = (typeof context.wakeCommentId === "string" && context.wakeCommentId.trim().length > 0 && context.wakeCommentId.trim()) ||
175
+ (typeof context.commentId === "string" && context.commentId.trim().length > 0 && context.commentId.trim()) ||
176
+ null;
177
+ const approvalId = typeof context.approvalId === "string" && context.approvalId.trim().length > 0
178
+ ? context.approvalId.trim()
179
+ : null;
180
+ const approvalStatus = typeof context.approvalStatus === "string" && context.approvalStatus.trim().length > 0
181
+ ? context.approvalStatus.trim()
182
+ : null;
183
+ const linkedIssueIds = Array.isArray(context.issueIds)
184
+ ? context.issueIds.filter((value) => typeof value === "string" && value.trim().length > 0)
185
+ : [];
186
+ const issueWorkMode = readPaperclipIssueWorkModeFromContext(context);
187
+ if (wakeTaskId) {
188
+ env.PAPERCLIP_TASK_ID = wakeTaskId;
189
+ }
190
+ if (issueWorkMode) {
191
+ env.PAPERCLIP_ISSUE_WORK_MODE = issueWorkMode;
192
+ }
193
+ if (wakeReason) {
194
+ env.PAPERCLIP_WAKE_REASON = wakeReason;
195
+ }
196
+ if (wakeCommentId) {
197
+ env.PAPERCLIP_WAKE_COMMENT_ID = wakeCommentId;
198
+ }
199
+ if (approvalId) {
200
+ env.PAPERCLIP_APPROVAL_ID = approvalId;
201
+ }
202
+ if (approvalStatus) {
203
+ env.PAPERCLIP_APPROVAL_STATUS = approvalStatus;
204
+ }
205
+ if (linkedIssueIds.length > 0) {
206
+ env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(",");
207
+ }
208
+ refreshPaperclipWorkspaceEnvForExecution({
209
+ env,
210
+ envConfig,
211
+ workspaceCwd: effectiveWorkspaceCwd,
212
+ workspaceSource,
213
+ workspaceId,
214
+ workspaceRepoUrl,
215
+ workspaceRepoRef,
216
+ workspaceHints,
217
+ agentHome,
218
+ executionTargetIsRemote,
219
+ executionCwd: effectiveExecutionCwd,
220
+ });
221
+ if (authToken) {
222
+ env.PAPERCLIP_API_KEY = authToken;
223
+ }
224
+ const timeoutSec = resolveAdapterExecutionTargetTimeoutSec(executionTarget, asNumber(config.timeoutSec, 0));
225
+ const graceSec = asNumber(config.graceSec, 20);
226
+ await ensureAdapterExecutionTargetRuntimeCommandInstalled({
227
+ runId,
228
+ target: executionTarget,
229
+ installCommand: ctx.runtimeCommandSpec?.installCommand,
230
+ detectCommand: ctx.runtimeCommandSpec?.detectCommand,
231
+ cwd,
232
+ env,
233
+ timeoutSec,
234
+ graceSec,
235
+ onLog,
236
+ });
237
+ // Probe the sandbox before the managed-home override so we discover the
238
+ // installer-managed agent symlinks from the real system HOME (for example
239
+ // ~/.local/bin/agent). The managed HOME set later is for runtime isolation,
240
+ // not for finding the CLI.
241
+ const initialSandboxCommand = await prepareCursorSandboxCommand({
242
+ runId,
243
+ target: executionTarget,
244
+ command,
245
+ cwd,
246
+ env,
247
+ timeoutSec,
248
+ graceSec,
249
+ });
250
+ const sandboxSystemHomeDir = initialSandboxCommand.remoteSystemHomeDir;
251
+ command = initialSandboxCommand.command;
252
+ env = initialSandboxCommand.env;
253
+ const extraArgs = (() => {
254
+ const fromExtraArgs = asStringArray(config.extraArgs);
255
+ if (fromExtraArgs.length > 0)
256
+ return fromExtraArgs;
257
+ return asStringArray(config.args);
258
+ })();
259
+ const autoTrustEnabled = !hasCursorTrustBypassArg(extraArgs);
260
+ let restoreRemoteWorkspace = null;
261
+ let localSkillsDir = null;
262
+ let remoteRuntimeRootDir = null;
263
+ let paperclipBridge = null;
264
+ if (executionTargetIsRemote) {
265
+ try {
266
+ localSkillsDir = await buildCursorSkillsDir(config);
267
+ await onLog("stdout", `[paperclip] Syncing workspace and Cursor runtime assets to ${describeAdapterExecutionTarget(executionTarget)}.\n`);
268
+ const preparedExecutionTargetRuntime = await prepareAdapterExecutionTargetRuntime({
269
+ runId,
270
+ target: executionTarget,
271
+ adapterKey: "cursor",
272
+ timeoutSec,
273
+ workspaceLocalDir: cwd,
274
+ installCommand: SANDBOX_INSTALL_COMMAND,
275
+ detectCommand: command,
276
+ onProgress: (line) => onLog("stdout", line),
277
+ onRuntimeProgress: ctx.onRuntimeProgress,
278
+ assets: [{
279
+ key: "skills",
280
+ localDir: localSkillsDir,
281
+ followSymlinks: true,
282
+ }],
283
+ });
284
+ restoreRemoteWorkspace = () => preparedExecutionTargetRuntime.restoreWorkspace((line) => onLog("stdout", line));
285
+ effectiveExecutionCwd = preparedExecutionTargetRuntime.workspaceRemoteDir ?? effectiveExecutionCwd;
286
+ refreshPaperclipWorkspaceEnvForExecution({
287
+ env,
288
+ envConfig,
289
+ workspaceCwd: effectiveWorkspaceCwd,
290
+ workspaceSource,
291
+ workspaceId,
292
+ workspaceRepoUrl,
293
+ workspaceRepoRef,
294
+ workspaceHints,
295
+ agentHome,
296
+ executionTargetIsRemote,
297
+ executionCwd: effectiveExecutionCwd,
298
+ });
299
+ remoteRuntimeRootDir = preparedExecutionTargetRuntime.runtimeRootDir;
300
+ const managedHome = adapterExecutionTargetUsesManagedHome(executionTarget);
301
+ if (managedHome && preparedExecutionTargetRuntime.runtimeRootDir) {
302
+ env.HOME = preparedExecutionTargetRuntime.runtimeRootDir;
303
+ }
304
+ const remoteHomeDir = managedHome && preparedExecutionTargetRuntime.runtimeRootDir
305
+ ? preparedExecutionTargetRuntime.runtimeRootDir
306
+ : await readAdapterExecutionTargetHomeDir(runId, executionTarget, {
307
+ cwd,
308
+ env,
309
+ timeoutSec,
310
+ graceSec,
311
+ onLog,
312
+ });
313
+ if (remoteHomeDir && preparedExecutionTargetRuntime.assetDirs.skills) {
314
+ const remoteSkillsDir = path.posix.join(remoteHomeDir, ".cursor", "skills");
315
+ await runAdapterExecutionTargetShellCommand(runId, executionTarget, `mkdir -p ${JSON.stringify(path.posix.dirname(remoteSkillsDir))} && rm -rf ${JSON.stringify(remoteSkillsDir)} && cp -a ${JSON.stringify(preparedExecutionTargetRuntime.assetDirs.skills)} ${JSON.stringify(remoteSkillsDir)}`, { cwd, env, timeoutSec, graceSec, onLog });
316
+ }
317
+ }
318
+ catch (error) {
319
+ await Promise.allSettled([
320
+ restoreRemoteWorkspace?.(),
321
+ localSkillsDir ? fs.rm(localSkillsDir, { recursive: true, force: true }).catch(() => undefined) : Promise.resolve(),
322
+ ]);
323
+ throw error;
324
+ }
325
+ }
326
+ const finalSandboxCommand = executionTarget?.kind === "remote" && executionTarget.transport === "sandbox"
327
+ ? await prepareCursorSandboxCommand({
328
+ runId,
329
+ target: executionTarget,
330
+ command,
331
+ cwd,
332
+ env,
333
+ remoteSystemHomeDirHint: sandboxSystemHomeDir,
334
+ timeoutSec,
335
+ graceSec,
336
+ })
337
+ : null;
338
+ if (finalSandboxCommand) {
339
+ command = finalSandboxCommand.command;
340
+ env = finalSandboxCommand.env;
341
+ }
342
+ const runtimeExecutionTarget = overrideAdapterExecutionTargetRemoteCwd(executionTarget, effectiveExecutionCwd);
343
+ const effectiveEnv = Object.fromEntries(Object.entries({ ...process.env, ...env }).filter((entry) => typeof entry[1] === "string"));
344
+ const billingType = resolveCursorBillingType(effectiveEnv);
345
+ const runtimeEnv = ensurePathInEnv(effectiveEnv);
346
+ await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv, {
347
+ installCommand: SANDBOX_INSTALL_COMMAND,
348
+ timeoutSec,
349
+ });
350
+ const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
351
+ let loggedEnv = buildInvocationEnvForLogs(env, {
352
+ runtimeEnv,
353
+ includeRuntimeKeys: ["HOME"],
354
+ resolvedCommand,
355
+ });
356
+ if (executionTargetIsRemote && adapterExecutionTargetUsesPaperclipBridge(runtimeExecutionTarget)) {
357
+ paperclipBridge = await startAdapterExecutionTargetPaperclipBridge({
358
+ runId,
359
+ target: runtimeExecutionTarget,
360
+ enableSandboxDuplexBridge: adapterExecutionTargetEnablesSandboxDuplexBridge(runtimeExecutionTarget),
361
+ duplexObservabilityRecorder: adapterExecutionTargetDuplexObservabilityRecorder(runtimeExecutionTarget),
362
+ runtimeRootDir: remoteRuntimeRootDir,
363
+ adapterKey: "cursor",
364
+ timeoutSec,
365
+ hostApiToken: env.PAPERCLIP_API_KEY,
366
+ onLog,
367
+ });
368
+ if (paperclipBridge) {
369
+ Object.assign(env, paperclipBridge.env);
370
+ loggedEnv = buildInvocationEnvForLogs(env, {
371
+ runtimeEnv: ensurePathInEnv({ ...process.env, ...env }),
372
+ includeRuntimeKeys: ["HOME"],
373
+ resolvedCommand,
374
+ });
375
+ }
376
+ }
377
+ const runtimeSessionParams = parseObject(runtime.sessionParams);
378
+ const runtimeSessionId = asString(runtimeSessionParams.sessionId, runtime.sessionId ?? "");
379
+ const runtimeSessionCwd = asString(runtimeSessionParams.cwd, "");
380
+ const runtimeRemoteExecution = parseObject(runtimeSessionParams.remoteExecution);
381
+ const canResumeSession = runtimeSessionId.length > 0 &&
382
+ (runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(effectiveExecutionCwd)) &&
383
+ adapterExecutionTargetSessionMatches(runtimeRemoteExecution, runtimeExecutionTarget);
384
+ const sessionId = canResumeSession ? runtimeSessionId : null;
385
+ if (executionTargetIsRemote && runtimeSessionId && !canResumeSession) {
386
+ await onLog("stdout", `[paperclip] Cursor session "${runtimeSessionId}" does not match the current remote execution identity and will not be resumed in "${effectiveExecutionCwd}". Starting a fresh remote session.\n`);
387
+ }
388
+ else if (runtimeSessionId && !canResumeSession) {
389
+ await onLog("stdout", `[paperclip] Cursor session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${effectiveExecutionCwd}".\n`);
390
+ }
391
+ const instructionsFilePath = asString(config.instructionsFilePath, "").trim();
392
+ const instructionsDir = instructionsFilePath ? `${path.dirname(instructionsFilePath)}/` : "";
393
+ let instructionsPrefix = "";
394
+ let instructionsChars = 0;
395
+ if (instructionsFilePath) {
396
+ try {
397
+ const instructionsContents = await fs.readFile(instructionsFilePath, "utf8");
398
+ instructionsPrefix =
399
+ `${instructionsContents}\n\n` +
400
+ `The above agent instructions were loaded from ${instructionsFilePath}. ` +
401
+ `Resolve any relative file references from ${instructionsDir}.\n\n`;
402
+ instructionsChars = instructionsPrefix.length;
403
+ }
404
+ catch (err) {
405
+ const reason = err instanceof Error ? err.message : String(err);
406
+ await onLog("stdout", `[paperclip] Warning: could not read agent instructions file "${instructionsFilePath}": ${reason}\n`);
407
+ }
408
+ }
409
+ const commandNotes = (() => {
410
+ const notes = [];
411
+ if (autoTrustEnabled) {
412
+ notes.push("Auto-added --yolo to bypass interactive prompts.");
413
+ }
414
+ notes.push("Prompt is piped to Cursor via stdin.");
415
+ const sandboxCommand = finalSandboxCommand ?? initialSandboxCommand;
416
+ if (sandboxCommand?.addedPathEntry) {
417
+ notes.push(`Remote sandbox runs prepend ${sandboxCommand.addedPathEntry} to PATH.`);
418
+ }
419
+ if (sandboxCommand?.preferredCommandPath) {
420
+ notes.push(`Remote sandbox runs prefer ${sandboxCommand.preferredCommandPath} when using the default Cursor entrypoint.`);
421
+ }
422
+ if (!instructionsFilePath)
423
+ return notes;
424
+ if (instructionsPrefix.length > 0) {
425
+ notes.push(`Loaded agent instructions from ${instructionsFilePath}`, `Prepended instructions + path directive to prompt (relative references from ${instructionsDir}).`);
426
+ return notes;
427
+ }
428
+ notes.push(`Configured instructionsFilePath ${instructionsFilePath}, but file could not be read; continuing without injected instructions.`);
429
+ return notes;
430
+ })();
431
+ const bootstrapPromptTemplate = asString(config.bootstrapPromptTemplate, "");
432
+ const templateData = {
433
+ agentId: agent.id,
434
+ companyId: agent.companyId,
435
+ runId,
436
+ company: { id: agent.companyId },
437
+ agent,
438
+ run: { id: runId, source: "on_demand" },
439
+ context,
440
+ };
441
+ const renderedBootstrapPrompt = !sessionId && bootstrapPromptTemplate.trim().length > 0
442
+ ? renderTemplate(bootstrapPromptTemplate, templateData).trim()
443
+ : "";
444
+ const taskContextNote = context.conversationMode === true
445
+ ? selectPaperclipTaskMarkdown(context, { resumedSession: Boolean(sessionId), includeCommunicationGuidance: false })
446
+ : "";
447
+ const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, {
448
+ conversationMode: context.conversationMode === true,
449
+ resumedSession: Boolean(sessionId),
450
+ suppressIssueDescription: taskContextNote.length > 0,
451
+ });
452
+ const shouldUseResumeDeltaPrompt = Boolean(sessionId) && wakePrompt.length > 0;
453
+ const renderedPrompt = shouldUseResumeDeltaPrompt || isPaperclipRecoveryWakePayload(context.paperclipWake)
454
+ ? ""
455
+ : renderTemplate(promptTemplate, templateData);
456
+ const sessionHandoffNote = asString(context.paperclipSessionHandoffMarkdown, "").trim();
457
+ const paperclipEnvNote = renderPaperclipEnvNote(env);
458
+ const basePrompt = joinPromptSections([
459
+ instructionsPrefix,
460
+ renderedBootstrapPrompt,
461
+ wakePrompt,
462
+ taskContextNote,
463
+ sessionHandoffNote,
464
+ paperclipEnvNote,
465
+ renderedPrompt,
466
+ ]);
467
+ const promptMetrics = {
468
+ promptChars: basePrompt.length,
469
+ instructionsChars,
470
+ bootstrapPromptChars: renderedBootstrapPrompt.length,
471
+ wakePromptChars: wakePrompt.length,
472
+ taskContextChars: taskContextNote.length,
473
+ sessionHandoffChars: sessionHandoffNote.length,
474
+ runtimeNoteChars: paperclipEnvNote.length,
475
+ heartbeatPromptChars: renderedPrompt.length,
476
+ };
477
+ const buildArgs = (resumeSessionId) => {
478
+ const args = ["-p", "--output-format", "stream-json", "--workspace", effectiveExecutionCwd];
479
+ if (resumeSessionId)
480
+ args.push("--resume", resumeSessionId);
481
+ if (model)
482
+ args.push("--model", model);
483
+ if (mode)
484
+ args.push("--mode", mode);
485
+ if (autoTrustEnabled)
486
+ args.push("--yolo");
487
+ if (extraArgs.length > 0)
488
+ args.push(...extraArgs);
489
+ return args;
490
+ };
491
+ const runAttempt = async (resumeSessionId) => {
492
+ const prompt = joinPromptSections([
493
+ selectInitialCommunicationGuidance(context, { resumedSession: Boolean(resumeSessionId) }),
494
+ basePrompt,
495
+ ]);
496
+ const args = buildArgs(resumeSessionId);
497
+ if (onMeta) {
498
+ await onMeta({
499
+ adapterType: "cursor",
500
+ command: resolvedCommand,
501
+ cwd: effectiveExecutionCwd,
502
+ commandNotes,
503
+ commandArgs: args,
504
+ env: loggedEnv,
505
+ prompt,
506
+ promptMetrics: { ...promptMetrics, promptChars: prompt.length },
507
+ context,
508
+ });
509
+ }
510
+ let stdoutLineBuffer = "";
511
+ const emitNormalizedStdoutLine = async (rawLine) => {
512
+ const normalized = normalizeCursorStreamLine(rawLine);
513
+ if (!normalized.line)
514
+ return;
515
+ await onLog(normalized.stream ?? "stdout", `${normalized.line}\n`);
516
+ };
517
+ const flushStdoutChunk = async (chunk, finalize = false) => {
518
+ const combined = `${stdoutLineBuffer}${chunk}`;
519
+ const lines = combined.split(/\r?\n/);
520
+ stdoutLineBuffer = lines.pop() ?? "";
521
+ for (const line of lines) {
522
+ await emitNormalizedStdoutLine(line);
523
+ }
524
+ if (finalize) {
525
+ const trailing = stdoutLineBuffer.trim();
526
+ stdoutLineBuffer = "";
527
+ if (trailing) {
528
+ await emitNormalizedStdoutLine(trailing);
529
+ }
530
+ }
531
+ };
532
+ const proc = await runAdapterExecutionTargetProcess(runId, runtimeExecutionTarget, command, args, {
533
+ cwd,
534
+ env,
535
+ timeoutSec,
536
+ graceSec,
537
+ stdin: prompt,
538
+ onSpawn,
539
+ onRuntimeProgress: ctx.onRuntimeProgress,
540
+ onLog: async (stream, chunk) => {
541
+ if (stream !== "stdout") {
542
+ await onLog(stream, chunk);
543
+ return;
544
+ }
545
+ await flushStdoutChunk(chunk);
546
+ },
547
+ runLogTail: paperclipBridge?.runLogTail,
548
+ settleRunDisposition: paperclipBridge?.settleRunDisposition,
549
+ });
550
+ await flushStdoutChunk("", true);
551
+ return {
552
+ proc,
553
+ parsed: parseCursorJsonl(proc.stdout),
554
+ };
555
+ };
556
+ const providerFromModel = resolveProviderFromModel(model);
557
+ const toResult = (attempt, clearSessionOnMissingSession = false) => {
558
+ if (attempt.proc.timedOut) {
559
+ return {
560
+ exitCode: attempt.proc.exitCode,
561
+ signal: attempt.proc.signal,
562
+ timedOut: true,
563
+ errorMessage: `Timed out after ${timeoutSec}s`,
564
+ clearSession: clearSessionOnMissingSession,
565
+ };
566
+ }
567
+ const resolvedSessionId = attempt.parsed.sessionId ?? runtimeSessionId ?? runtime.sessionId ?? null;
568
+ const resolvedSessionParams = resolvedSessionId
569
+ ? {
570
+ sessionId: resolvedSessionId,
571
+ cwd: effectiveExecutionCwd,
572
+ ...(workspaceId ? { workspaceId } : {}),
573
+ ...(workspaceRepoUrl ? { repoUrl: workspaceRepoUrl } : {}),
574
+ ...(workspaceRepoRef ? { repoRef: workspaceRepoRef } : {}),
575
+ ...(executionTargetIsRemote
576
+ ? {
577
+ remoteExecution: adapterExecutionTargetSessionIdentity(runtimeExecutionTarget),
578
+ }
579
+ : {}),
580
+ }
581
+ : null;
582
+ const parsedError = typeof attempt.parsed.errorMessage === "string" ? attempt.parsed.errorMessage.trim() : "";
583
+ const stderrLine = firstNonEmptyLine(attempt.proc.stderr);
584
+ const fallbackErrorMessage = parsedError ||
585
+ stderrLine ||
586
+ `Cursor exited with code ${attempt.proc.exitCode ?? -1}`;
587
+ return {
588
+ exitCode: attempt.proc.exitCode,
589
+ signal: attempt.proc.signal,
590
+ timedOut: false,
591
+ errorMessage: (attempt.proc.exitCode ?? 0) === 0
592
+ ? null
593
+ : fallbackErrorMessage,
594
+ // Forward the transport-level error code from the run-disposition seam. A
595
+ // lost duplex control channel surfaces the typed `duplex_channel_lost`
596
+ // code; every other result carries no code here.
597
+ errorCode: attempt.proc.errorCode ?? null,
598
+ usage: attempt.parsed.usage,
599
+ sessionId: resolvedSessionId,
600
+ sessionParams: resolvedSessionParams,
601
+ sessionDisplayId: resolvedSessionId,
602
+ provider: providerFromModel,
603
+ biller: resolveCursorBiller(effectiveEnv, billingType, providerFromModel),
604
+ model,
605
+ billingType,
606
+ costUsd: attempt.parsed.costUsd,
607
+ resultJson: {
608
+ stdout: attempt.proc.stdout,
609
+ stderr: attempt.proc.stderr,
610
+ },
611
+ summary: attempt.parsed.summary,
612
+ clearSession: Boolean(clearSessionOnMissingSession && !resolvedSessionId),
613
+ };
614
+ };
615
+ try {
616
+ const initial = await runAttempt(sessionId);
617
+ if (sessionId &&
618
+ !initial.proc.timedOut &&
619
+ (initial.proc.exitCode ?? 0) !== 0 &&
620
+ isCursorUnknownSessionError(initial.proc.stdout, initial.proc.stderr)) {
621
+ await onLog("stdout", `[paperclip] Cursor resume session "${sessionId}" is unavailable; retrying with a fresh session.\n`);
622
+ const retry = await runAttempt(null);
623
+ return toResult(retry, true);
624
+ }
625
+ return toResult(initial);
626
+ }
627
+ finally {
628
+ if (paperclipBridge) {
629
+ await paperclipBridge.stop();
630
+ }
631
+ if (restoreRemoteWorkspace) {
632
+ await onLog("stdout", `[paperclip] Restoring workspace changes from ${describeAdapterExecutionTarget(executionTarget)}.\n`);
633
+ await restoreRemoteWorkspace();
634
+ }
635
+ if (localSkillsDir) {
636
+ await fs.rm(localSkillsDir, { recursive: true, force: true }).catch(() => undefined);
637
+ }
638
+ }
639
+ }
640
+ //# sourceMappingURL=execute.js.map