import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { inferOpenAiCompatibleBiller, type AdapterExecutionContext, type AdapterExecutionResult } from "@paperclipai/adapter-utils"; import { adapterExecutionTargetIsRemote, adapterExecutionTargetRemoteCwd, adapterExecutionTargetSessionIdentity, adapterExecutionTargetSessionMatches, adapterExecutionTargetUsesManagedHome, adapterExecutionTargetUsesPaperclipBridge, describeAdapterExecutionTarget, ensureAdapterExecutionTargetCommandResolvable, ensureAdapterExecutionTargetRuntimeCommandInstalled, prepareAdapterExecutionTargetRuntime, readAdapterExecutionTarget, readAdapterExecutionTargetHomeDir, resolveAdapterExecutionTargetCommandForLogs, runAdapterExecutionTargetProcess, runAdapterExecutionTargetShellCommand, startAdapterExecutionTargetPaperclipBridge, } from "@paperclipai/adapter-utils/execution-target"; import { asString, asNumber, asStringArray, parseObject, applyPaperclipWorkspaceEnv, buildPaperclipEnv, buildInvocationEnvForLogs, ensureAbsoluteDirectory, ensurePaperclipSkillSymlink, ensurePathInEnv, readPaperclipRuntimeSkillEntries, readPaperclipIssueWorkModeFromContext, resolvePaperclipDesiredSkillNames, removeMaintainerOnlySkillSymlinks, renderTemplate, renderPaperclipWakePrompt, shapePaperclipWorkspaceEnvForExecution, stringifyPaperclipWakePayload, DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE, joinPromptSections, } from "@paperclipai/adapter-utils/server-utils"; import { DEFAULT_CURSOR_LOCAL_MODEL, SANDBOX_INSTALL_COMMAND } from "../index.js"; import { parseCursorJsonl, isCursorUnknownSessionError } from "./parse.js"; import { prepareCursorSandboxCommand } from "./remote-command.js"; import { normalizeCursorStreamLine } from "../shared/stream.js"; import { hasCursorTrustBypassArg } from "../shared/trust.js"; const __moduleDir = path.dirname(fileURLToPath(import.meta.url)); function firstNonEmptyLine(text: string): string { return ( text .split(/\r?\n/) .map((line) => line.trim()) .find(Boolean) ?? "" ); } function hasNonEmptyEnvValue(env: Record, key: string): boolean { const raw = env[key]; return typeof raw === "string" && raw.trim().length > 0; } function resolveCursorBillingType(env: Record): "api" | "subscription" { return hasNonEmptyEnvValue(env, "CURSOR_API_KEY") || hasNonEmptyEnvValue(env, "OPENAI_API_KEY") ? "api" : "subscription"; } function resolveCursorBiller( env: Record, billingType: "api" | "subscription", provider: string | null, ): string { const openAiCompatibleBiller = inferOpenAiCompatibleBiller(env, null); if (openAiCompatibleBiller === "openrouter") return "openrouter"; if (billingType === "subscription") return "cursor"; return provider ?? "cursor"; } function resolveProviderFromModel(model: string): string | null { const trimmed = model.trim().toLowerCase(); if (!trimmed) return null; const slash = trimmed.indexOf("/"); if (slash > 0) return trimmed.slice(0, slash); if (trimmed.includes("sonnet") || trimmed.includes("claude")) return "anthropic"; if (trimmed.startsWith("gpt") || trimmed.startsWith("o")) return "openai"; return null; } function normalizeMode(rawMode: string): "plan" | "ask" | null { const mode = rawMode.trim().toLowerCase(); if (mode === "plan" || mode === "ask") return mode; return null; } function renderPaperclipEnvNote(env: Record): string { const paperclipKeys = Object.keys(env) .filter((key) => key.startsWith("PAPERCLIP_")) .sort(); if (paperclipKeys.length === 0) return ""; return [ "Paperclip runtime note:", `The following PAPERCLIP_* environment variables are available in this run: ${paperclipKeys.join(", ")}`, "Do not assume these variables are missing without checking your shell environment.", "", "", ].join("\n"); } function cursorSkillsHome(): string { return path.join(os.homedir(), ".cursor", "skills"); } async function buildCursorSkillsDir(config: Record): Promise { const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-cursor-skills-")); const target = path.join(tmp, "skills"); await fs.mkdir(target, { recursive: true }); const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir); const desiredNames = new Set(resolvePaperclipDesiredSkillNames(config, availableEntries)); for (const entry of availableEntries) { if (!desiredNames.has(entry.key)) continue; await fs.symlink(entry.source, path.join(target, entry.runtimeName)); } return target; } type EnsureCursorSkillsInjectedOptions = { skillsDir?: string | null; skillsEntries?: Array<{ key: string; runtimeName: string; source: string }>; skillsHome?: string; linkSkill?: (source: string, target: string) => Promise; }; export async function ensureCursorSkillsInjected( onLog: AdapterExecutionContext["onLog"], options: EnsureCursorSkillsInjectedOptions = {}, ) { const skillsEntries = options.skillsEntries ?? (options.skillsDir ? (await fs.readdir(options.skillsDir, { withFileTypes: true })) .filter((entry) => entry.isDirectory()) .map((entry) => ({ key: entry.name, runtimeName: entry.name, source: path.join(options.skillsDir!, entry.name), })) : await readPaperclipRuntimeSkillEntries({}, __moduleDir)); if (skillsEntries.length === 0) return; const skillsHome = options.skillsHome ?? cursorSkillsHome(); try { await fs.mkdir(skillsHome, { recursive: true }); } catch (err) { await onLog( "stderr", `[paperclip] Failed to prepare Cursor skills directory ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, ); return; } const removedSkills = await removeMaintainerOnlySkillSymlinks( skillsHome, skillsEntries.map((entry) => entry.runtimeName), ); for (const skillName of removedSkills) { await onLog( "stderr", `[paperclip] Removed maintainer-only Cursor skill "${skillName}" from ${skillsHome}\n`, ); } const linkSkill = options.linkSkill ?? ((source: string, target: string) => fs.symlink(source, target)); for (const entry of skillsEntries) { const target = path.join(skillsHome, entry.runtimeName); try { const result = await ensurePaperclipSkillSymlink(entry.source, target, linkSkill); if (result === "skipped") continue; await onLog( "stderr", `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Cursor skill "${entry.key}" into ${skillsHome}\n`, ); } catch (err) { await onLog( "stderr", `[paperclip] Failed to inject Cursor skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, ); } } } export async function execute(ctx: AdapterExecutionContext): Promise { const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx; const executionTarget = readAdapterExecutionTarget({ executionTarget: ctx.executionTarget, legacyRemoteExecution: ctx.executionTransport?.remoteExecution, }); const executionTargetIsRemote = adapterExecutionTargetIsRemote(executionTarget); const promptTemplate = asString( config.promptTemplate, DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE, ); let command = asString(config.command, "agent"); const model = asString(config.model, DEFAULT_CURSOR_LOCAL_MODEL).trim(); const mode = normalizeMode(asString(config.mode, "")); const workspaceContext = parseObject(context.paperclipWorkspace); const workspaceCwd = asString(workspaceContext.cwd, ""); const workspaceSource = asString(workspaceContext.source, ""); const workspaceId = asString(workspaceContext.workspaceId, ""); const workspaceRepoUrl = asString(workspaceContext.repoUrl, ""); const workspaceRepoRef = asString(workspaceContext.repoRef, ""); const agentHome = asString(workspaceContext.agentHome, ""); const workspaceHints = Array.isArray(context.paperclipWorkspaces) ? context.paperclipWorkspaces.filter( (value): value is Record => typeof value === "object" && value !== null, ) : []; const configuredCwd = asString(config.cwd, ""); const useConfiguredInsteadOfAgentHome = workspaceSource === "agent_home" && configuredCwd.length > 0; const effectiveWorkspaceCwd = useConfiguredInsteadOfAgentHome ? "" : workspaceCwd; const cwd = effectiveWorkspaceCwd || configuredCwd || process.cwd(); const effectiveExecutionCwd = adapterExecutionTargetRemoteCwd(executionTarget, cwd); const shapedWorkspaceEnv = shapePaperclipWorkspaceEnvForExecution({ workspaceCwd: effectiveWorkspaceCwd, workspaceHints, executionTargetIsRemote, executionCwd: effectiveExecutionCwd, }); await ensureAbsoluteDirectory(cwd, { createIfMissing: true }); const cursorSkillEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir); const desiredCursorSkillNames = resolvePaperclipDesiredSkillNames(config, cursorSkillEntries); if (!executionTargetIsRemote) { await ensureCursorSkillsInjected(onLog, { skillsEntries: cursorSkillEntries.filter((entry) => desiredCursorSkillNames.includes(entry.key)), }); } const envConfig = parseObject(config.env); const hasExplicitApiKey = typeof envConfig.PAPERCLIP_API_KEY === "string" && envConfig.PAPERCLIP_API_KEY.trim().length > 0; let env: Record = { ...buildPaperclipEnv(agent) }; env.PAPERCLIP_RUN_ID = runId; const wakeTaskId = (typeof context.taskId === "string" && context.taskId.trim().length > 0 && context.taskId.trim()) || (typeof context.issueId === "string" && context.issueId.trim().length > 0 && context.issueId.trim()) || null; const wakeReason = typeof context.wakeReason === "string" && context.wakeReason.trim().length > 0 ? context.wakeReason.trim() : null; const wakeCommentId = (typeof context.wakeCommentId === "string" && context.wakeCommentId.trim().length > 0 && context.wakeCommentId.trim()) || (typeof context.commentId === "string" && context.commentId.trim().length > 0 && context.commentId.trim()) || null; const approvalId = typeof context.approvalId === "string" && context.approvalId.trim().length > 0 ? context.approvalId.trim() : null; const approvalStatus = typeof context.approvalStatus === "string" && context.approvalStatus.trim().length > 0 ? context.approvalStatus.trim() : null; const linkedIssueIds = Array.isArray(context.issueIds) ? context.issueIds.filter((value): value is string => typeof value === "string" && value.trim().length > 0) : []; const wakePayloadJson = stringifyPaperclipWakePayload(context.paperclipWake); const issueWorkMode = readPaperclipIssueWorkModeFromContext(context); if (wakeTaskId) { env.PAPERCLIP_TASK_ID = wakeTaskId; } if (issueWorkMode) { env.PAPERCLIP_ISSUE_WORK_MODE = issueWorkMode; } if (wakeReason) { env.PAPERCLIP_WAKE_REASON = wakeReason; } if (wakeCommentId) { env.PAPERCLIP_WAKE_COMMENT_ID = wakeCommentId; } if (approvalId) { env.PAPERCLIP_APPROVAL_ID = approvalId; } if (approvalStatus) { env.PAPERCLIP_APPROVAL_STATUS = approvalStatus; } if (linkedIssueIds.length > 0) { env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(","); } if (wakePayloadJson) { env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayloadJson; } applyPaperclipWorkspaceEnv(env, { workspaceCwd: shapedWorkspaceEnv.workspaceCwd, workspaceSource, workspaceId, workspaceRepoUrl, workspaceRepoRef, agentHome, }); if (shapedWorkspaceEnv.workspaceHints.length > 0) { env.PAPERCLIP_WORKSPACES_JSON = JSON.stringify(shapedWorkspaceEnv.workspaceHints); } for (const [k, v] of Object.entries(envConfig)) { if (typeof v === "string") env[k] = v; } if (!hasExplicitApiKey && authToken) { env.PAPERCLIP_API_KEY = authToken; } const timeoutSec = asNumber(config.timeoutSec, 0); const graceSec = asNumber(config.graceSec, 20); await ensureAdapterExecutionTargetRuntimeCommandInstalled({ runId, target: executionTarget, installCommand: ctx.runtimeCommandSpec?.installCommand, detectCommand: ctx.runtimeCommandSpec?.detectCommand, cwd, env, timeoutSec, graceSec, onLog, }); // Probe the sandbox before the managed-home override so we discover // cursor-agent from the real system HOME (e.g. ~/.local/bin/cursor-agent). // The managed HOME set later is for runtime isolation, not for finding the CLI. const sandboxCommand = await prepareCursorSandboxCommand({ runId, target: executionTarget, command, cwd, env, timeoutSec, graceSec, }); command = sandboxCommand.command; env = sandboxCommand.env; const effectiveEnv = Object.fromEntries( Object.entries({ ...process.env, ...env }).filter( (entry): entry is [string, string] => typeof entry[1] === "string", ), ); const billingType = resolveCursorBillingType(effectiveEnv); const runtimeEnv = ensurePathInEnv(effectiveEnv); await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv, { installCommand: SANDBOX_INSTALL_COMMAND }); const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv); let loggedEnv = buildInvocationEnvForLogs(env, { runtimeEnv, includeRuntimeKeys: ["HOME"], resolvedCommand, }); const extraArgs = (() => { const fromExtraArgs = asStringArray(config.extraArgs); if (fromExtraArgs.length > 0) return fromExtraArgs; return asStringArray(config.args); })(); const autoTrustEnabled = !hasCursorTrustBypassArg(extraArgs); let restoreRemoteWorkspace: (() => Promise) | null = null; let localSkillsDir: string | null = null; let remoteRuntimeRootDir: string | null = null; let paperclipBridge: Awaited> = null; if (executionTargetIsRemote) { try { localSkillsDir = await buildCursorSkillsDir(config); await onLog( "stdout", `[paperclip] Syncing workspace and Cursor runtime assets to ${describeAdapterExecutionTarget(executionTarget)}.\n`, ); const preparedExecutionTargetRuntime = await prepareAdapterExecutionTargetRuntime({ target: executionTarget, adapterKey: "cursor", workspaceLocalDir: cwd, installCommand: SANDBOX_INSTALL_COMMAND, detectCommand: command, assets: [{ key: "skills", localDir: localSkillsDir, followSymlinks: true, }], }); restoreRemoteWorkspace = () => preparedExecutionTargetRuntime.restoreWorkspace(); remoteRuntimeRootDir = preparedExecutionTargetRuntime.runtimeRootDir; const managedHome = adapterExecutionTargetUsesManagedHome(executionTarget); if (managedHome && preparedExecutionTargetRuntime.runtimeRootDir) { env.HOME = preparedExecutionTargetRuntime.runtimeRootDir; } const remoteHomeDir = managedHome && preparedExecutionTargetRuntime.runtimeRootDir ? preparedExecutionTargetRuntime.runtimeRootDir : await readAdapterExecutionTargetHomeDir(runId, executionTarget, { cwd, env, timeoutSec, graceSec, onLog, }); if (remoteHomeDir && preparedExecutionTargetRuntime.assetDirs.skills) { const remoteSkillsDir = path.posix.join(remoteHomeDir, ".cursor", "skills"); 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 }, ); } } catch (error) { await Promise.allSettled([ restoreRemoteWorkspace?.(), localSkillsDir ? fs.rm(localSkillsDir, { recursive: true, force: true }).catch(() => undefined) : Promise.resolve(), ]); throw error; } } if (executionTargetIsRemote && adapterExecutionTargetUsesPaperclipBridge(executionTarget)) { paperclipBridge = await startAdapterExecutionTargetPaperclipBridge({ runId, target: executionTarget, runtimeRootDir: remoteRuntimeRootDir, adapterKey: "cursor", hostApiToken: env.PAPERCLIP_API_KEY, onLog, }); if (paperclipBridge) { Object.assign(env, paperclipBridge.env); loggedEnv = buildInvocationEnvForLogs(env, { runtimeEnv: ensurePathInEnv({ ...process.env, ...env }), includeRuntimeKeys: ["HOME"], resolvedCommand, }); } } const runtimeSessionParams = parseObject(runtime.sessionParams); const runtimeSessionId = asString(runtimeSessionParams.sessionId, runtime.sessionId ?? ""); const runtimeSessionCwd = asString(runtimeSessionParams.cwd, ""); const runtimeRemoteExecution = parseObject(runtimeSessionParams.remoteExecution); const canResumeSession = runtimeSessionId.length > 0 && (runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(effectiveExecutionCwd)) && adapterExecutionTargetSessionMatches(runtimeRemoteExecution, executionTarget); const sessionId = canResumeSession ? runtimeSessionId : null; if (executionTargetIsRemote && runtimeSessionId && !canResumeSession) { 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`, ); } else if (runtimeSessionId && !canResumeSession) { await onLog( "stdout", `[paperclip] Cursor session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${effectiveExecutionCwd}".\n`, ); } const instructionsFilePath = asString(config.instructionsFilePath, "").trim(); const instructionsDir = instructionsFilePath ? `${path.dirname(instructionsFilePath)}/` : ""; let instructionsPrefix = ""; let instructionsChars = 0; if (instructionsFilePath) { try { const instructionsContents = await fs.readFile(instructionsFilePath, "utf8"); instructionsPrefix = `${instructionsContents}\n\n` + `The above agent instructions were loaded from ${instructionsFilePath}. ` + `Resolve any relative file references from ${instructionsDir}.\n\n`; instructionsChars = instructionsPrefix.length; } catch (err) { const reason = err instanceof Error ? err.message : String(err); await onLog( "stdout", `[paperclip] Warning: could not read agent instructions file "${instructionsFilePath}": ${reason}\n`, ); } } const commandNotes = (() => { const notes: string[] = []; if (autoTrustEnabled) { notes.push("Auto-added --yolo to bypass interactive prompts."); } notes.push("Prompt is piped to Cursor via stdin."); if (sandboxCommand.addedPathEntry) { notes.push(`Remote sandbox runs prepend ${sandboxCommand.addedPathEntry} to PATH.`); } if (sandboxCommand.preferredCommandPath) { notes.push(`Remote sandbox runs prefer ${sandboxCommand.preferredCommandPath} when using the default Cursor entrypoint.`); } if (!instructionsFilePath) return notes; if (instructionsPrefix.length > 0) { notes.push( `Loaded agent instructions from ${instructionsFilePath}`, `Prepended instructions + path directive to prompt (relative references from ${instructionsDir}).`, ); return notes; } notes.push( `Configured instructionsFilePath ${instructionsFilePath}, but file could not be read; continuing without injected instructions.`, ); return notes; })(); const bootstrapPromptTemplate = asString(config.bootstrapPromptTemplate, ""); const templateData = { agentId: agent.id, companyId: agent.companyId, runId, company: { id: agent.companyId }, agent, run: { id: runId, source: "on_demand" }, context, }; const renderedBootstrapPrompt = !sessionId && bootstrapPromptTemplate.trim().length > 0 ? renderTemplate(bootstrapPromptTemplate, templateData).trim() : ""; const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, { resumedSession: Boolean(sessionId) }); const shouldUseResumeDeltaPrompt = Boolean(sessionId) && wakePrompt.length > 0; const renderedPrompt = shouldUseResumeDeltaPrompt ? "" : renderTemplate(promptTemplate, templateData); const sessionHandoffNote = asString(context.paperclipSessionHandoffMarkdown, "").trim(); const paperclipEnvNote = renderPaperclipEnvNote(env); const prompt = joinPromptSections([ instructionsPrefix, renderedBootstrapPrompt, wakePrompt, sessionHandoffNote, paperclipEnvNote, renderedPrompt, ]); const promptMetrics = { promptChars: prompt.length, instructionsChars, bootstrapPromptChars: renderedBootstrapPrompt.length, wakePromptChars: wakePrompt.length, sessionHandoffChars: sessionHandoffNote.length, runtimeNoteChars: paperclipEnvNote.length, heartbeatPromptChars: renderedPrompt.length, }; const buildArgs = (resumeSessionId: string | null) => { const args = ["-p", "--output-format", "stream-json", "--workspace", effectiveExecutionCwd]; if (resumeSessionId) args.push("--resume", resumeSessionId); if (model) args.push("--model", model); if (mode) args.push("--mode", mode); if (autoTrustEnabled) args.push("--yolo"); if (extraArgs.length > 0) args.push(...extraArgs); return args; }; const runAttempt = async (resumeSessionId: string | null) => { const args = buildArgs(resumeSessionId); if (onMeta) { await onMeta({ adapterType: "cursor", command: resolvedCommand, cwd: effectiveExecutionCwd, commandNotes, commandArgs: args, env: loggedEnv, prompt, promptMetrics, context, }); } let stdoutLineBuffer = ""; const emitNormalizedStdoutLine = async (rawLine: string) => { const normalized = normalizeCursorStreamLine(rawLine); if (!normalized.line) return; await onLog(normalized.stream ?? "stdout", `${normalized.line}\n`); }; const flushStdoutChunk = async (chunk: string, finalize = false) => { const combined = `${stdoutLineBuffer}${chunk}`; const lines = combined.split(/\r?\n/); stdoutLineBuffer = lines.pop() ?? ""; for (const line of lines) { await emitNormalizedStdoutLine(line); } if (finalize) { const trailing = stdoutLineBuffer.trim(); stdoutLineBuffer = ""; if (trailing) { await emitNormalizedStdoutLine(trailing); } } }; const proc = await runAdapterExecutionTargetProcess(runId, executionTarget, command, args, { cwd, env, timeoutSec, graceSec, stdin: prompt, onSpawn, onLog: async (stream, chunk) => { if (stream !== "stdout") { await onLog(stream, chunk); return; } await flushStdoutChunk(chunk); }, }); await flushStdoutChunk("", true); return { proc, parsed: parseCursorJsonl(proc.stdout), }; }; const providerFromModel = resolveProviderFromModel(model); const toResult = ( attempt: { proc: { exitCode: number | null; signal: string | null; timedOut: boolean; stdout: string; stderr: string; }; parsed: ReturnType; }, clearSessionOnMissingSession = false, ): AdapterExecutionResult => { if (attempt.proc.timedOut) { return { exitCode: attempt.proc.exitCode, signal: attempt.proc.signal, timedOut: true, errorMessage: `Timed out after ${timeoutSec}s`, clearSession: clearSessionOnMissingSession, }; } const resolvedSessionId = attempt.parsed.sessionId ?? runtimeSessionId ?? runtime.sessionId ?? null; const resolvedSessionParams = resolvedSessionId ? ({ sessionId: resolvedSessionId, cwd: effectiveExecutionCwd, ...(workspaceId ? { workspaceId } : {}), ...(workspaceRepoUrl ? { repoUrl: workspaceRepoUrl } : {}), ...(workspaceRepoRef ? { repoRef: workspaceRepoRef } : {}), ...(executionTargetIsRemote ? { remoteExecution: adapterExecutionTargetSessionIdentity(executionTarget), } : {}), } as Record) : null; const parsedError = typeof attempt.parsed.errorMessage === "string" ? attempt.parsed.errorMessage.trim() : ""; const stderrLine = firstNonEmptyLine(attempt.proc.stderr); const fallbackErrorMessage = parsedError || stderrLine || `Cursor exited with code ${attempt.proc.exitCode ?? -1}`; return { exitCode: attempt.proc.exitCode, signal: attempt.proc.signal, timedOut: false, errorMessage: (attempt.proc.exitCode ?? 0) === 0 ? null : fallbackErrorMessage, usage: attempt.parsed.usage, sessionId: resolvedSessionId, sessionParams: resolvedSessionParams, sessionDisplayId: resolvedSessionId, provider: providerFromModel, biller: resolveCursorBiller(effectiveEnv, billingType, providerFromModel), model, billingType, costUsd: attempt.parsed.costUsd, resultJson: { stdout: attempt.proc.stdout, stderr: attempt.proc.stderr, }, summary: attempt.parsed.summary, clearSession: Boolean(clearSessionOnMissingSession && !resolvedSessionId), }; }; try { const initial = await runAttempt(sessionId); if ( sessionId && !initial.proc.timedOut && (initial.proc.exitCode ?? 0) !== 0 && isCursorUnknownSessionError(initial.proc.stdout, initial.proc.stderr) ) { await onLog( "stdout", `[paperclip] Cursor resume session "${sessionId}" is unavailable; retrying with a fresh session.\n`, ); const retry = await runAttempt(null); return toResult(retry, true); } return toResult(initial); } finally { if (paperclipBridge) { await paperclipBridge.stop(); } if (restoreRemoteWorkspace) { await onLog( "stdout", `[paperclip] Restoring workspace changes from ${describeAdapterExecutionTarget(executionTarget)}.\n`, ); await restoreRemoteWorkspace(); } if (localSkillsDir) { await fs.rm(localSkillsDir, { recursive: true, force: true }).catch(() => undefined); } } }