diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts index ef0b35ac..515ef2f8 100644 --- a/apps/cli/src/utils.ts +++ b/apps/cli/src/utils.ts @@ -25,7 +25,7 @@ export async function submitFeedback({ plannerFeedback: string; plannerThreadId: string; setLogs: (updater: (prev: string[]) => string[]) => void; // eslint-disable-line no-unused-vars - setPlannerFeedback: () => void; // eslint-disable-line no-unused-vars + setPlannerFeedback: () => void; setStreamingPhase: (phase: "streaming" | "awaitingFeedback" | "done") => void; // eslint-disable-line no-unused-vars }) { try { diff --git a/apps/docs/setup/development.mdx b/apps/docs/setup/development.mdx index 94b3e8cb..1daf6c88 100644 --- a/apps/docs/setup/development.mdx +++ b/apps/docs/setup/development.mdx @@ -74,7 +74,7 @@ Before starting, ensure you have the following installed: GITHUB_APP_REDIRECT_URI="http://localhost:3000/api/auth/github/callback" # GitHub App details (will be filled after creating GitHub App) - GITHUB_APP_NAME="open-swe-dev" + GITHUB_APP_NAME="open-swe-dev" # this must match the name of your GitHub app, excluding spaces GITHUB_APP_ID="" GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- ...add your private key here... @@ -105,7 +105,7 @@ Before starting, ensure you have the following installed: FIRECRAWL_API_KEY="" # For URL content extraction # GitHub App settings (same as web app) - GITHUB_APP_NAME="open-swe-dev" + GITHUB_APP_NAME="open-swe-dev" # this must match the name of your GitHub app, excluding spaces GITHUB_APP_ID="" GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- ...add your private key here... diff --git a/apps/open-swe/.env.example b/apps/open-swe/.env.example index ae060a01..04f0209e 100644 --- a/apps/open-swe/.env.example +++ b/apps/open-swe/.env.example @@ -25,9 +25,7 @@ FIRECRAWL_API_KEY="" # ------------------Github App Secrets----------------- -# Used for setting the git user name & email for commits. -# Can modify to whatever string you want. -GITHUB_APP_NAME="open-swe-dev" +GITHUB_APP_NAME="open-swe-dev" # this must match the name of your GitHub app, excluding spaces GITHUB_APP_ID="" # App secret key. Should be multi-line. GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- @@ -54,5 +52,6 @@ SECRETS_ENCRYPTION_KEY="" # See the documentation for how to set this up: docs.langchain.com/labs/swe/setup/ci#skip-ci-until-last-commit SKIP_CI_UNTIL_LAST_COMMIT="true" -OPEN_SWE_LOCAL_MODE=true -OPEN_SWE_LOCAL_PROJECT_PATH="" \ No newline at end of file +# For the CLI to work, you need to set these variables. +# OPEN_SWE_LOCAL_MODE=false +# OPEN_SWE_LOCAL_PROJECT_PATH="" diff --git a/apps/open-swe/package.json b/apps/open-swe/package.json index 378eb9cb..11aea978 100644 --- a/apps/open-swe/package.json +++ b/apps/open-swe/package.json @@ -24,7 +24,7 @@ "postinstall": "turbo build" }, "dependencies": { - "@daytonaio/sdk": "^0.25.4", + "@daytonaio/sdk": "^0.25.5", "@langchain/anthropic": "^0.3.26", "@langchain/community": "^0.3.47", "@langchain/core": "^0.3.65", diff --git a/apps/open-swe/src/graphs/shared/initialize-sandbox.ts b/apps/open-swe/src/graphs/shared/initialize-sandbox.ts index c1d675ac..31e0cd2c 100644 --- a/apps/open-swe/src/graphs/shared/initialize-sandbox.ts +++ b/apps/open-swe/src/graphs/shared/initialize-sandbox.ts @@ -286,10 +286,13 @@ export async function initializeSandbox( stateBranchName: branchName, }); }, - { retries: 3, delay: 0 }, + { retries: 0, delay: 0 }, ); - if (cloneRepoRes instanceof Error) { + if ( + cloneRepoRes instanceof Error && + !cloneRepoRes.message.includes("repository already exists") + ) { emitStepEvent( baseCloneRepoAction, "error", diff --git a/apps/open-swe/src/utils/github/api.ts b/apps/open-swe/src/utils/github/api.ts index 36defb9b..aaf0a3c5 100644 --- a/apps/open-swe/src/utils/github/api.ts +++ b/apps/open-swe/src/utils/github/api.ts @@ -1,6 +1,7 @@ import { Octokit } from "@octokit/rest"; import { createLogger, LogLevel } from "../logger.js"; import { + GitHubBranch, GitHubIssue, GitHubIssueComment, GitHubPullRequest, @@ -555,3 +556,35 @@ export async function updateIssueComment({ numRetries, ); } + +export async function getBranch({ + owner, + repo, + branchName, + githubInstallationToken, +}: { + owner: string; + repo: string; + branchName: string; + githubInstallationToken: string; +}): Promise { + return withGitHubRetry( + async (token: string) => { + const octokit = new Octokit({ + auth: token, + }); + + const { data: branch } = await octokit.repos.getBranch({ + owner, + repo, + branch: branchName, + }); + + return branch; + }, + githubInstallationToken, + "Failed to get branch", + undefined, + 1, + ); +} diff --git a/apps/open-swe/src/utils/github/git.ts b/apps/open-swe/src/utils/github/git.ts index eb5fc1c5..6dc995d8 100644 --- a/apps/open-swe/src/utils/github/git.ts +++ b/apps/open-swe/src/utils/github/git.ts @@ -15,7 +15,7 @@ import { getActiveTask, getPullRequestNumberFromActiveTask, } from "@open-swe/shared/open-swe/tasks"; -import { createPullRequest } from "./api.js"; +import { createPullRequest, getBranch } from "./api.js"; import { addTaskPlanToIssue } from "./issue-task.js"; import { DEFAULT_EXCLUDED_PATTERNS } from "./constants.js"; import { escapeRegExp } from "../string-utils.js"; @@ -523,11 +523,32 @@ async function performClone( baseCommit: targetRepository.baseCommit, }); + if (!branchName && !targetRepository.baseCommit) { + throw new Error( + "Can not create new branch or checkout existing branch without branch name", + ); + } + + const branchExists = branchName + ? !!(await getBranch({ + owner: targetRepository.owner, + repo: targetRepository.repo, + branchName, + githubInstallationToken, + })) + : false; + + if (branchExists) { + logger.info("Branch already exists on remote. Cloning existing branch.", { + branch: branchName, + }); + } + await sandbox.git.clone( cloneUrl, absoluteRepoDir, - targetRepository.branch, - targetRepository.baseCommit, + branchExists ? branchName : targetRepository.branch, + branchExists ? undefined : targetRepository.baseCommit, "git", githubInstallationToken, ); @@ -543,45 +564,51 @@ async function performClone( } if (!branchName) { - throw new Error( - "Can not create new branch or checkout existing branch without branch name", - ); + throw new Error("Branch name is required"); + } + + if (branchExists) { + return branchName; } try { + logger.info("Creating branch", { + branch: branchName, + }); + await sandbox.git.createBranch(absoluteRepoDir, branchName); + logger.info("Created branch", { branch: branchName, }); - - // push an empty commit so that the branch exists in the remote - await sandbox.git.push(absoluteRepoDir, "git", githubInstallationToken); - logger.info("Pushed empty commit to remote", { - branch: branchName, - }); - - return branchName; - } catch { - logger.info("Failed to create branch, checking out branch", { + } catch (error) { + logger.error("Failed to create branch, checking out branch", { branch: branchName, + error: + error instanceof Error + ? { name: error.name, message: error.message } + : String(error), }); } - await sandbox.git.checkoutBranch(absoluteRepoDir, branchName); - logger.info("Checked out branch", { - branch: branchName, - }); - - const setUpstreamBranchRes = await sandbox.process.executeCommand( - `git branch --set-upstream-to=origin/${branchName}`, - absoluteRepoDir, - ); - if (setUpstreamBranchRes.exitCode !== 0) { - logger.error("Failed to set upstream branch", { - setUpstreamBranchRes, + try { + // push an empty commit so that the branch exists in the remote + logger.info("Pushing empty commit to remote", { + branch: branchName, + }); + await sandbox.git.push(absoluteRepoDir, "git", githubInstallationToken); + + logger.info("Pushed empty commit to remote", { + branch: branchName, + }); + } catch (error) { + logger.error("Failed to push an empty commit to branch", { + branch: branchName, + error: + error instanceof Error + ? { name: error.name, message: error.message } + : String(error), }); - } else { - logger.info("Set upstream branch"); } return branchName; diff --git a/apps/open-swe/src/utils/github/types.ts b/apps/open-swe/src/utils/github/types.ts index 2f889530..e093cf2c 100644 --- a/apps/open-swe/src/utils/github/types.ts +++ b/apps/open-swe/src/utils/github/types.ts @@ -14,3 +14,6 @@ export type GitHubPullRequestUpdate = export type GitHubPullRequestList = RestEndpointMethodTypes["pulls"]["list"]["response"]["data"]; + +export type GitHubBranch = + RestEndpointMethodTypes["repos"]["getBranch"]["response"]["data"]; diff --git a/apps/web/.env.example b/apps/web/.env.example index 1b5e74d1..9ba4e23d 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -7,9 +7,7 @@ GITHUB_APP_CLIENT_SECRET="" # your GitHub app settings. GITHUB_APP_REDIRECT_URI="http://localhost:3000/api/auth/github/callback" -# Used for setting the git user name & email for commits. -# Can modify to whatever string you want. -GITHUB_APP_NAME="open-swe-dev" +GITHUB_APP_NAME="open-swe-dev" # this must match the name of your GitHub app, excluding spaces GITHUB_APP_ID="" # App secret key. Should be multi-line. GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- diff --git a/apps/web/src/features/settings-page/config-manager.tsx b/apps/web/src/features/settings-page/config-manager.tsx index 2de2236d..8dbee3ac 100644 --- a/apps/web/src/features/settings-page/config-manager.tsx +++ b/apps/web/src/features/settings-page/config-manager.tsx @@ -208,45 +208,6 @@ export function ConfigManager() { updateConfig(DEFAULT_CONFIG_KEY, config.label, value); }} /> - -
- {(() => { - const currentValue = - configs[DEFAULT_CONFIG_KEY]?.[config.label]; - const defaultValue = defaultConfig.find( - (c) => c.label === config.label, - )?.default; - const isModified = - currentValue !== undefined && - currentValue !== defaultValue; - - return ( - isModified && ( - - ) - ); - })()} -
), diff --git a/packages/shared/src/messages.ts b/packages/shared/src/messages.ts index 566ab0af..60c6914d 100644 --- a/packages/shared/src/messages.ts +++ b/packages/shared/src/messages.ts @@ -1,10 +1,16 @@ import { MessageContent } from "@langchain/core/messages"; export function getMessageContentString(content: MessageContent): string { - if (typeof content === "string") return content; + try { + if (typeof content === "string") return content; - return content - .filter((c): c is { type: "text"; text: string } => c.type === "text") - .map((c) => c.text) - .join(" "); + return content + .filter((c): c is { type: "text"; text: string } => c.type === "text") + .map((c) => c.text) + .join(" "); + } catch (error) { + // eslint-disable-next-line no-console + console.error("Failed to get message content string", error); + return ""; + } } diff --git a/packages/shared/src/open-swe/models.ts b/packages/shared/src/open-swe/models.ts index 2656f6e7..965c5760 100644 --- a/packages/shared/src/open-swe/models.ts +++ b/packages/shared/src/open-swe/models.ts @@ -1,12 +1,13 @@ export const MODEL_OPTIONS = [ - { - label: "Claude Sonnet 4 (Extended Thinking)", - value: "anthropic:extended-thinking:claude-sonnet-4-0", - }, - { - label: "Claude Opus 4 (Extended Thinking)", - value: "anthropic:extended-thinking:claude-opus-4-0", - }, + // TODO: Test these then re-enable + // { + // label: "Claude Sonnet 4 (Extended Thinking)", + // value: "anthropic:extended-thinking:claude-sonnet-4-0", + // }, + // { + // label: "Claude Opus 4 (Extended Thinking)", + // value: "anthropic:extended-thinking:claude-opus-4-0", + // }, { label: "Claude Sonnet 4", value: "anthropic:claude-sonnet-4-0", diff --git a/yarn.lock b/yarn.lock index f3163e28..235e1bc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1282,33 +1282,32 @@ __metadata: languageName: node linkType: hard -"@daytonaio/api-client@npm:0.25.4": - version: 0.25.4 - resolution: "@daytonaio/api-client@npm:0.25.4" +"@daytonaio/api-client@npm:0.25.5": + version: 0.25.5 + resolution: "@daytonaio/api-client@npm:0.25.5" dependencies: axios: ^1.6.1 - checksum: c3ff09da2af437620bada5c6161e52104ec32a2ef0f3a890d4da5458015e78e9de7ee28954e423477dc5fd7a8a07f58e5fd1fc919d9f382e86b6ad2da8b421c6 + checksum: 208fd54526f9f3b40ef8ca137e6f6d68ac4cef0e63b51cda1cb7a91ffa23dc3875a49bfc4eda271f0f1cb86ba21e33234b68b5a51591882cc13508ccba29f8d2 languageName: node linkType: hard -"@daytonaio/sdk@npm:^0.25.4": - version: 0.25.4 - resolution: "@daytonaio/sdk@npm:0.25.4" +"@daytonaio/sdk@npm:^0.25.5": + version: 0.25.5 + resolution: "@daytonaio/sdk@npm:0.25.5" dependencies: "@aws-sdk/client-s3": ^3.787.0 "@aws-sdk/lib-storage": ^3.798.0 - "@daytonaio/api-client": 0.25.4 + "@daytonaio/api-client": 0.25.5 "@iarna/toml": ^2.2.5 axios: ^1.6.1 dotenv: ^17.0.1 expand-tilde: ^2.0.2 fast-glob: ^3.3.0 - form-data: ^4.0.0 - isomorphic-ws: 5.0.0 + form-data: ^4.0.4 pathe: ^2.0.3 shell-quote: ^1.8.2 tar: ^6.2.0 - checksum: d3d6e35c8c27a69c4cd8e241b9e1e4ac2171136718dc0ec21bf20e63e0d027f29a37ab16909025f14f891428055c9493382d72fe368d8db391249f59b4f00e21 + checksum: 692edc134c56bb83037870a750b81792b5592fdc5cf13d9b2d6ecfd522866d433bfb9e8943a3d4caf169b1d69969048079b0e876354e83035840fed4173b0c37 languageName: node linkType: hard @@ -4093,7 +4092,7 @@ __metadata: version: 0.0.0-use.local resolution: "@open-swe/agent@workspace:apps/open-swe" dependencies: - "@daytonaio/sdk": ^0.25.4 + "@daytonaio/sdk": ^0.25.5 "@eslint/eslintrc": ^3.1.0 "@eslint/js": ^9.19.0 "@jest/globals": ^29.7.0 @@ -11348,7 +11347,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0, form-data@npm:^4.0.4": +"form-data@npm:^4.0.4": version: 4.0.4 resolution: "form-data@npm:4.0.4" dependencies: @@ -13103,15 +13102,6 @@ __metadata: languageName: node linkType: hard -"isomorphic-ws@npm:5.0.0": - version: 5.0.0 - resolution: "isomorphic-ws@npm:5.0.0" - peerDependencies: - ws: "*" - checksum: e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 - languageName: node - linkType: hard - "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2"