From 7a281ad4c148ccd1dd5c19a8c4cbeb6baf000566 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 23 Jun 2025 15:06:26 -0700 Subject: [PATCH] feat: Custom rules (#304) --- .../planner/nodes/generate-message/index.ts | 4 +- .../planner/nodes/generate-message/prompt.ts | 2 + .../src/graphs/planner/nodes/generate-plan.ts | 6 +- .../src/graphs/planner/nodes/notetaker.ts | 10 +- .../nodes/generate-message/index.ts | 4 +- .../nodes/generate-message/prompt.ts | 4 +- .../graphs/programmer/nodes/update-plan.ts | 8 +- .../src/graphs/shared/initialize-sandbox.ts | 11 +- apps/open-swe/src/utils/custom-rules.ts | 164 ++++++++++++++++++ apps/web/src/components/v2/default-view.tsx | 46 +---- apps/web/src/components/v2/quick-actions.tsx | 54 ++++++ apps/web/src/components/v2/terminal-input.tsx | 14 +- packages/shared/src/open-swe/planner/types.ts | 37 ++-- packages/shared/src/open-swe/types.ts | 16 ++ 14 files changed, 318 insertions(+), 62 deletions(-) create mode 100644 apps/open-swe/src/utils/custom-rules.ts create mode 100644 apps/web/src/components/v2/quick-actions.tsx diff --git a/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts b/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts index 8395386e..eaabc42e 100644 --- a/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts +++ b/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts @@ -17,6 +17,7 @@ import { getMissingMessages } from "../../../../utils/github/issue-messages.js"; import { filterHiddenMessages } from "../../../../utils/message/filter-hidden.js"; import { getTaskPlanFromIssue } from "../../../../utils/github/issue-task.js"; import { createRgTool } from "../../../../tools/rg.js"; +import { formatCustomRulesPrompt } from "../../../../utils/custom-rules.js"; const logger = createLogger(LogLevel.INFO, "GeneratePlanningMessageNode"); @@ -36,7 +37,8 @@ function formatSystemPrompt(state: PlannerGraphState): string { .replaceAll( "{CURRENT_WORKING_DIRECTORY}", getRepoAbsolutePath(state.targetRepository), - ); + ) + .replaceAll("{CUSTOM_RULES}", formatCustomRulesPrompt(state.customRules)); } export async function generateAction( diff --git a/apps/open-swe/src/graphs/planner/nodes/generate-message/prompt.ts b/apps/open-swe/src/graphs/planner/nodes/generate-message/prompt.ts index c8f2d8df..9870cada 100644 --- a/apps/open-swe/src/graphs/planner/nodes/generate-message/prompt.ts +++ b/apps/open-swe/src/graphs/planner/nodes/generate-message/prompt.ts @@ -41,6 +41,8 @@ Generated via: \`git ls-files | tree --fromfile -L 3\` +{CUSTOM_RULES} + The user's request appears as the first message in the conversation below. Your context gathering should specifically target information needed to address this request effectively. `; diff --git a/apps/open-swe/src/graphs/planner/nodes/generate-plan.ts b/apps/open-swe/src/graphs/planner/nodes/generate-plan.ts index 8ebd31be..6fab293e 100644 --- a/apps/open-swe/src/graphs/planner/nodes/generate-plan.ts +++ b/apps/open-swe/src/graphs/planner/nodes/generate-plan.ts @@ -14,6 +14,7 @@ import { import { stopSandbox } from "../../../utils/sandbox.js"; import { filterHiddenMessages } from "../../../utils/message/filter-hidden.js"; import { z } from "zod"; +import { formatCustomRulesPrompt } from "../../../utils/custom-rules.js"; const systemPrompt = `You are a terminal-based agentic coding assistant built by LangChain, designed to enable natural language interaction with local codebases through wrapped LLM models. @@ -62,6 +63,8 @@ Structure your plan items as clear directives, for example: - "Modify the authentication middleware in /src/auth.js to add rate limiting using the Express rate-limit package" +{CUSTOM_RULES} + Remember: Your goal is to create a focused, executable plan that efficiently accomplishes the user's request using the context you've already gathered.`; function formatSystemPrompt(state: PlannerGraphState): string { @@ -78,7 +81,8 @@ function formatSystemPrompt(state: PlannerGraphState): string { "\n\n" : "", ) - .replace("{USER_REQUEST}", userRequest); + .replace("{USER_REQUEST}", userRequest) + .replaceAll("{CUSTOM_RULES}", formatCustomRulesPrompt(state.customRules)); } export async function generatePlan( diff --git a/apps/open-swe/src/graphs/planner/nodes/notetaker.ts b/apps/open-swe/src/graphs/planner/nodes/notetaker.ts index 72e4e1c3..cee40cdd 100644 --- a/apps/open-swe/src/graphs/planner/nodes/notetaker.ts +++ b/apps/open-swe/src/graphs/planner/nodes/notetaker.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { GraphConfig } from "@open-swe/shared/open-swe/types"; +import { CustomRules, GraphConfig } from "@open-swe/shared/open-swe/types"; import { PlannerGraphState, PlannerGraphUpdate, @@ -8,6 +8,7 @@ import { loadModel, Task } from "../../../utils/load-model.js"; import { getMessageString } from "../../../utils/message/content.js"; import { getUserRequest } from "../../../utils/user-request.js"; import { BaseMessage } from "@langchain/core/messages"; +import { formatCustomRulesPrompt } from "../../../utils/custom-rules.js"; const systemPrompt = `You are operating as a terminal-based agentic coding assistant built by LangChain. It wraps LLM models to enable natural language interaction with a local codebase. You are expected to be precise, safe, and helpful. @@ -23,6 +24,8 @@ You MUST adhere to the following criteria when generating your notes: - Do not retain any full file contents. - Only take notes on the context provided below, and do not make up, or attempt to infer any information/context which is not explicitly provided. +{CUSTOM_RULES} + Here is the user's request ## User request: {USER_REQUEST} @@ -42,6 +45,7 @@ const formatPrompt = ( userRequest: string, conversationHistory: BaseMessage[], proposedPlan: string[], + customRules?: CustomRules, ): string => systemPrompt .replace("{USER_REQUEST}", userRequest) @@ -49,7 +53,8 @@ const formatPrompt = ( "{CONVERSATION_HISTORY}", conversationHistory.map(getMessageString).join("\n"), ) - .replace("{PROPOSED_PLAN}", ` - ${proposedPlan.join("\n - ")}`); + .replace("{PROPOSED_PLAN}", ` - ${proposedPlan.join("\n - ")}`) + .replaceAll("{CUSTOM_RULES}", formatCustomRulesPrompt(customRules)); const condenseContextToolSchema = z.object({ notes: z @@ -85,6 +90,7 @@ ${state.messages.map(getMessageString).join("\n")}`; userRequest || "No user request provided.", state.messages, state.proposedPlan, + state.customRules, ), }, { diff --git a/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts b/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts index 0845be72..d61b0b1c 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts @@ -26,6 +26,7 @@ import { getMissingMessages } from "../../../../utils/github/issue-messages.js"; import { getTaskPlanFromIssue } from "../../../../utils/github/issue-task.js"; import { createRgTool } from "../../../../tools/rg.js"; import { createInstallDependenciesTool } from "../../../../tools/install-dependencies.js"; +import { formatCustomRulesPrompt } from "../../../../utils/custom-rules.js"; const logger = createLogger(LogLevel.INFO, "GenerateMessageNode"); @@ -61,7 +62,8 @@ const formatPrompt = (state: GraphState): string => { !state.dependenciesInstalled ? INSTALL_DEPENDENCIES_TOOL_PROMPT : DEPENDENCIES_INSTALLED_PROMPT, - ); + ) + .replaceAll("{CUSTOM_RULES}", formatCustomRulesPrompt(state.customRules)); }; export async function generateAction( diff --git a/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts b/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts index b4f826ef..66da0723 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts @@ -87,4 +87,6 @@ Generated via: \`git ls-files | tree --fromfile -L 3\` Location: {REPO_DIRECTORY} {CODEBASE_TREE} -`; + + +{CUSTOM_RULES}`; diff --git a/apps/open-swe/src/graphs/programmer/nodes/update-plan.ts b/apps/open-swe/src/graphs/programmer/nodes/update-plan.ts index ba465ce8..47f1268f 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/update-plan.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/update-plan.ts @@ -3,6 +3,7 @@ import { GraphConfig, PlanItem, GraphUpdate, + CustomRules, } from "@open-swe/shared/open-swe/types"; import { loadModel, Task } from "../../../utils/load-model.js"; import { z } from "zod"; @@ -19,6 +20,7 @@ import { getMessageString } from "../../../utils/message/content.js"; import { formatPlanPrompt } from "../../../utils/plan-prompt.js"; import { createLogger, LogLevel } from "../../../utils/logger.js"; import { createUpdatePlanToolFields } from "@open-swe/shared/open-swe/tools"; +import { formatCustomRulesPrompt } from "../../../utils/custom-rules.js"; const logger = createLogger(LogLevel.INFO, "UpdatePlanNode"); @@ -50,6 +52,8 @@ You MUST adhere to the following criteria when generating the plan: - You should call the \`update_plan\` tool, passing in each plan item in the order they should be executed in. - To remove an item from the plan, you should not include it in the \`update_plan\` tool call. +{CUSTOM_RULES} + With all of this in mind, please call the \`update_plan\` tool with the updated plan. `; @@ -72,11 +76,13 @@ const formatSystemPrompt = ( userRequest: string, reasoning: string, planItems: PlanItem[], + customRules?: CustomRules, ) => { return systemPrompt .replace("{USER_REQUEST}", userRequest) .replace("{PLAN}", formatPlanPrompt(planItems, { includeSummaries: true })) - .replace("{REASONING}", reasoning); + .replace("{REASONING}", reasoning) + .replaceAll("{CUSTOM_RULES}", formatCustomRulesPrompt(customRules)); }; const formatUserMessage = (messages: BaseMessage[]): string => { diff --git a/apps/open-swe/src/graphs/shared/initialize-sandbox.ts b/apps/open-swe/src/graphs/shared/initialize-sandbox.ts index 58173fd8..d63ecfc3 100644 --- a/apps/open-swe/src/graphs/shared/initialize-sandbox.ts +++ b/apps/open-swe/src/graphs/shared/initialize-sandbox.ts @@ -1,7 +1,11 @@ import { v4 as uuidv4 } from "uuid"; import { getRepoAbsolutePath } from "@open-swe/shared/git"; import { getGitHubTokensFromConfig } from "../../utils/github-tokens.js"; -import { GraphConfig, TargetRepository } from "@open-swe/shared/open-swe/types"; +import { + CustomRules, + GraphConfig, + TargetRepository, +} from "@open-swe/shared/open-swe/types"; import { createLogger, LogLevel } from "../../utils/logger.js"; import { daytonaClient } from "../../utils/sandbox.js"; import { @@ -19,6 +23,7 @@ import { import { Sandbox } from "@daytonaio/sdk"; import { AIMessage, BaseMessage } from "@langchain/core/messages"; import { DEFAULT_SANDBOX_CREATE_PARAMS } from "../../constants.js"; +import { getCustomRules } from "../../utils/custom-rules.js"; const logger = createLogger(LogLevel.INFO, "InitializeSandbox"); @@ -29,6 +34,7 @@ type InitializeSandboxState = { codebaseTree?: string; messages?: BaseMessage[]; dependenciesInstalled?: boolean; + customRules?: CustomRules; }; export async function initializeSandbox( @@ -166,10 +172,12 @@ export async function initializeSandbox( try { const codebaseTree = await getCodebaseTree(existingSandbox.id); emitStepEvent(baseGenerateCodebaseTreeAction, "success"); + return { sandboxSessionId: existingSandbox.id, codebaseTree, messages: createEventsMessage(), + customRules: await getCustomRules(existingSandbox, absoluteRepoDir), }; } catch { emitStepEvent( @@ -332,5 +340,6 @@ export async function initializeSandbox( codebaseTree, messages: createEventsMessage(), dependenciesInstalled: false, + customRules: await getCustomRules(sandbox, absoluteRepoDir), }; } diff --git a/apps/open-swe/src/utils/custom-rules.ts b/apps/open-swe/src/utils/custom-rules.ts new file mode 100644 index 00000000..e3b8ba52 --- /dev/null +++ b/apps/open-swe/src/utils/custom-rules.ts @@ -0,0 +1,164 @@ +import { CustomRules } from "@open-swe/shared/open-swe/types"; +import { Sandbox } from "@daytonaio/sdk"; +import { createLogger, LogLevel } from "./logger.js"; +import { getSandboxErrorFields } from "./sandbox-error-fields.js"; + +const logger = createLogger(LogLevel.INFO, "CustomRules"); + +const GENERAL_RULES_OPEN_TAG = ""; +const GENERAL_RULES_CLOSE_TAG = ""; +const REPOSITORY_STRUCTURE_OPEN_TAG = ""; +const REPOSITORY_STRUCTURE_CLOSE_TAG = ""; +const DEPENDENCIES_AND_INSTALLATION_OPEN_TAG = + ""; +const DEPENDENCIES_AND_INSTALLATION_CLOSE_TAG = + ""; +const TESTING_INSTRUCTIONS_OPEN_TAG = ""; +const TESTING_INSTRUCTIONS_CLOSE_TAG = ""; +const ALL_TAGS = [ + GENERAL_RULES_OPEN_TAG, + GENERAL_RULES_CLOSE_TAG, + REPOSITORY_STRUCTURE_OPEN_TAG, + REPOSITORY_STRUCTURE_CLOSE_TAG, + DEPENDENCIES_AND_INSTALLATION_OPEN_TAG, + DEPENDENCIES_AND_INSTALLATION_CLOSE_TAG, + TESTING_INSTRUCTIONS_OPEN_TAG, + TESTING_INSTRUCTIONS_CLOSE_TAG, +]; + +export function parseCustomRulesFromString(contents: string): CustomRules { + if (ALL_TAGS.every((tag) => !contents.includes(tag))) { + // Text file has no custom rules. Return all as general rules + return { + generalRules: contents, + }; + } + let generalRules = ""; + let repositoryStructure = ""; + let dependenciesAndInstallation = ""; + let testingInstructions = ""; + + if ( + contents.includes(GENERAL_RULES_OPEN_TAG) && + contents.includes(GENERAL_RULES_CLOSE_TAG) + ) { + generalRules = contents.substring( + contents.indexOf(GENERAL_RULES_OPEN_TAG) + GENERAL_RULES_OPEN_TAG.length, + contents.indexOf(GENERAL_RULES_CLOSE_TAG), + ); + } + if ( + contents.includes(REPOSITORY_STRUCTURE_OPEN_TAG) && + contents.includes(REPOSITORY_STRUCTURE_CLOSE_TAG) + ) { + repositoryStructure = contents.substring( + contents.indexOf(REPOSITORY_STRUCTURE_OPEN_TAG) + + REPOSITORY_STRUCTURE_OPEN_TAG.length, + contents.indexOf(REPOSITORY_STRUCTURE_CLOSE_TAG), + ); + } + if ( + contents.includes(DEPENDENCIES_AND_INSTALLATION_OPEN_TAG) && + contents.includes(DEPENDENCIES_AND_INSTALLATION_CLOSE_TAG) + ) { + dependenciesAndInstallation = contents.substring( + contents.indexOf(DEPENDENCIES_AND_INSTALLATION_OPEN_TAG) + + DEPENDENCIES_AND_INSTALLATION_OPEN_TAG.length, + contents.indexOf(DEPENDENCIES_AND_INSTALLATION_CLOSE_TAG), + ); + } + if ( + contents.includes(TESTING_INSTRUCTIONS_OPEN_TAG) && + contents.includes(TESTING_INSTRUCTIONS_CLOSE_TAG) + ) { + testingInstructions = contents.substring( + contents.indexOf(TESTING_INSTRUCTIONS_OPEN_TAG) + + TESTING_INSTRUCTIONS_OPEN_TAG.length, + contents.indexOf(TESTING_INSTRUCTIONS_CLOSE_TAG), + ); + } + + return { + generalRules, + repositoryStructure, + dependenciesAndInstallation, + testingInstructions, + }; +} + +export async function getCustomRules( + sandbox: Sandbox, + rootDir: string, +): Promise { + try { + const catAgentsMdFileCommand = ["cat", "AGENTS.md"]; + const agentsMdRes = await sandbox.process.executeCommand( + catAgentsMdFileCommand.join(" "), + rootDir, + ); + if (agentsMdRes.exitCode === 0 && agentsMdRes.result?.length > 0) { + return parseCustomRulesFromString(agentsMdRes.result); + } + + const catAgentMdFileCommand = ["cat", "AGENT.md"]; + const catClaudeMdFileCommand = ["cat", "CLAUDE.md"]; + const catCursorMdFileCommand = ["cat", "CURSOR.md"]; + const [agentMdRes, claudeMdRes, cursorMdRes] = await Promise.all([ + sandbox.process.executeCommand(catAgentMdFileCommand.join(" "), rootDir), + sandbox.process.executeCommand(catClaudeMdFileCommand.join(" "), rootDir), + sandbox.process.executeCommand(catCursorMdFileCommand.join(" "), rootDir), + ]); + if (agentMdRes.exitCode === 0 && agentMdRes.result?.length > 0) { + return parseCustomRulesFromString(agentMdRes.result); + } + if (claudeMdRes.exitCode === 0 && claudeMdRes.result?.length > 0) { + return parseCustomRulesFromString(claudeMdRes.result); + } + if (cursorMdRes.exitCode === 0 && cursorMdRes.result?.length > 0) { + return parseCustomRulesFromString(cursorMdRes.result); + } + } catch (error) { + const sandboxErrorFields = getSandboxErrorFields(error); + logger.error("Failed to get custom rules", { + ...(sandboxErrorFields ? { ...sandboxErrorFields } : { error }), + }); + } + + return undefined; +} + +export const CUSTOM_RULES_PROMPT = ` +The following are custom rules provided by the user. +{GENERAL_RULES} +{REPOSITORY_STRUCTURE} +{TESTING_INSTRUCTIONS} +{DEPENDENCIES_AND_INSTALLATION} +`; + +export function formatCustomRulesPrompt(customRules?: CustomRules): string { + if (!customRules) return ""; + return CUSTOM_RULES_PROMPT.replace( + "{GENERAL_RULES}", + customRules.generalRules + ? `\n${customRules.generalRules}\n` + : "", + ) + .replace( + "{REPOSITORY_STRUCTURE}", + customRules.repositoryStructure + ? `\n${customRules.repositoryStructure}\n` + : "", + ) + .replace( + "{TESTING_INSTRUCTIONS}", + customRules.testingInstructions + ? `\n${customRules.testingInstructions}\n` + : "", + ) + .replace( + "{DEPENDENCIES_AND_INSTALLATION}", + customRules.dependenciesAndInstallation + ? `\n${customRules.dependenciesAndInstallation}\n` + : "", + ); +} diff --git a/apps/web/src/components/v2/default-view.tsx b/apps/web/src/components/v2/default-view.tsx index 925a2351..419345fe 100644 --- a/apps/web/src/components/v2/default-view.tsx +++ b/apps/web/src/components/v2/default-view.tsx @@ -32,9 +32,10 @@ import { } from "../ui/tooltip"; import { Label } from "../ui/label"; import { ContentBlocksPreview } from "../thread/ContentBlocksPreview"; -import { TooltipIconButton } from "../ui/tooltip-icon-button"; import { ThemeToggle } from "../theme-toggle"; import { ThreadCard, ThreadCardLoading } from "./thread-card"; +import { QuickActions } from "./quick-actions"; +import { useState } from "react"; interface DefaultViewProps { threads: ThreadDisplayInfo[]; @@ -43,6 +44,7 @@ interface DefaultViewProps { export function DefaultView({ threads, threadsLoading }: DefaultViewProps) { const router = useRouter(); + const [quickActionPrompt, setQuickActionPrompt] = useState(""); const apiUrl: string | undefined = process.env.NEXT_PUBLIC_API_URL ?? ""; const assistantId: string | undefined = process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID ?? ""; @@ -115,6 +117,8 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) { contentBlocks={contentBlocks} setContentBlocks={setContentBlocks} onPaste={handlePaste} + quickActionPrompt={quickActionPrompt} + setQuickActionPrompt={setQuickActionPrompt} />
@@ -177,45 +181,7 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
)} - - {/* Quick Actions */} -
-

- Quick Actions -

-
- - - - Debug Code - - - Find and fix issues in your codebase - - - - - - - Add Feature - - - Implement new functionality - - - - - - - Refactor Code - - - Improve code structure and performance - - - -
-
+ diff --git a/apps/web/src/components/v2/quick-actions.tsx b/apps/web/src/components/v2/quick-actions.tsx new file mode 100644 index 00000000..17fc342d --- /dev/null +++ b/apps/web/src/components/v2/quick-actions.tsx @@ -0,0 +1,54 @@ +import { Dispatch, SetStateAction } from "react"; +import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card"; + +const GENERATE_RULES_PROMPT = `You're given a task to write a collection of rules, context and guidelines on the repository you're provided. Please gather context on the following categories, then write an \`AGENTS.md\` file in the root of the repository. + +- **General Rules**: These are general rules any developer/agent should follow when writing code. It should contain rules such as "When creating new XYZ functions, always first search in the XYZ/ directory to see if one exists, and if not, create it and place it in an existing or new file". Additionally, here is where you'd include context about scripts which are commonly executed, such as linter and formatter scripts. +- **Repository Structure**: This section is where you'll include high level context about how the repository is laid out, any highly useful and not overly obvious information about how the directories/files are structured, different apps/packages/services inside the repository, etc. Do not include every last detail about the repository contents, only a high level technical overview on the repository structure. +- **Dependencies and Installation**: This section should include high level context about how to install dependencies, where to install them, package managers, etc. Do not include overly verbose context in this section as most repositories typically have straightforward dependency management. +- **Testing Instructions**: A general guide on testing in this repository. It should include context such as testing frameworks, roughly what types of modules should be tested, how to run tests, and any other context a developer wouldn't be able to infer by looking at test files on how to write & run tests. Do not include every last detail about testing in this section, only the most used/important context necessary to run tests. + +Ensure each category is properly wrapped in opening and closing XML tags. The tags to use are as follows: + + + + + + + + + +It is incredibly important that you ALWAYS wrap your sections in the opening and closing XML tags. Failure to do so will result in an invalid file and will not be accepted. + +The file should not contain any other content besides those tags, and the rules/context/instructions they contain. Ensure your rules are thoughtful, concise and actually useful to a developer who's never contributed to the repository before. You can think of it as a more structured and directed \`CONTRIBUTING.md\` file. + +With all of this in mind, please explore the repository and write this single \`AGENTS.md\` file with the rules/context/instructions gathered!`; + +interface QuickActionsProps { + setQuickActionPrompt: Dispatch>; +} + +export function QuickActions({ setQuickActionPrompt }: QuickActionsProps) { + return ( +
+

+ Quick Actions +

+
+ setQuickActionPrompt(GENERATE_RULES_PROMPT)} + className="border-border bg-card hover:bg-muted cursor-pointer py-3 transition-shadow hover:shadow-lg dark:bg-gray-950" + > + + + Generate Agent Rules + + + Generate an AGENTS.md file for the repository. + + + +
+
+ ); +} diff --git a/apps/web/src/components/v2/terminal-input.tsx b/apps/web/src/components/v2/terminal-input.tsx index 6d149f48..77d7c144 100644 --- a/apps/web/src/components/v2/terminal-input.tsx +++ b/apps/web/src/components/v2/terminal-input.tsx @@ -2,7 +2,7 @@ import type React from "react"; import { v4 as uuidv4 } from "uuid"; -import { Dispatch, SetStateAction, useState } from "react"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { Textarea } from "@/components/ui/textarea"; import { Loader2, Send } from "lucide-react"; import { RepositoryBranchSelectors } from "../github/repo-branch-selectors"; @@ -23,6 +23,8 @@ interface TerminalInputProps { contentBlocks: Base64ContentBlock[]; setContentBlocks: Dispatch>; onPaste?: (e: React.ClipboardEvent) => void; + quickActionPrompt?: string; + setQuickActionPrompt?: Dispatch>; } export function TerminalInput({ @@ -33,6 +35,8 @@ export function TerminalInput({ contentBlocks, setContentBlocks, onPaste, + quickActionPrompt, + setQuickActionPrompt, }: TerminalInputProps) { const { push } = useRouter(); const [message, setMessage] = useState(""); @@ -113,6 +117,14 @@ export function TerminalInput({ } }; + useEffect(() => { + if (quickActionPrompt && message !== quickActionPrompt) { + setMessage(quickActionPrompt); + // Clear quick action prompt + setQuickActionPrompt?.(""); + } + }, [quickActionPrompt]); + return (
diff --git a/packages/shared/src/open-swe/planner/types.ts b/packages/shared/src/open-swe/planner/types.ts index 8a152681..3e48d150 100644 --- a/packages/shared/src/open-swe/planner/types.ts +++ b/packages/shared/src/open-swe/planner/types.ts @@ -1,13 +1,18 @@ import "@langchain/langgraph/zod"; import { z } from "zod"; import { MessagesZodState } from "@langchain/langgraph"; -import { AgentSession, TargetRepository, TaskPlan } from "../types.js"; +import { + AgentSession, + CustomRules, + TargetRepository, + TaskPlan, +} from "../types.js"; import { withLangGraph } from "@langchain/langgraph/zod"; export const PlannerGraphStateObj = MessagesZodState.extend({ - sandboxSessionId: withLangGraph(z.custom(), { + sandboxSessionId: withLangGraph(z.string(), { reducer: { - schema: z.custom(), + schema: z.string(), fn: (_state, update) => update, }, }), @@ -23,9 +28,9 @@ export const PlannerGraphStateObj = MessagesZodState.extend({ fn: (_state, update) => update, }, }), - codebaseTree: withLangGraph(z.custom(), { + codebaseTree: withLangGraph(z.string(), { reducer: { - schema: z.custom(), + schema: z.string(), fn: (_state, update) => update, }, }), @@ -42,22 +47,22 @@ export const PlannerGraphStateObj = MessagesZodState.extend({ }, default: (): string[] => [], }), - contextGatheringNotes: withLangGraph(z.custom(), { + contextGatheringNotes: withLangGraph(z.string(), { reducer: { - schema: z.custom(), + schema: z.string(), fn: (_state, update) => update, }, default: () => "", }), - branchName: withLangGraph(z.custom(), { + branchName: withLangGraph(z.string(), { reducer: { - schema: z.custom(), + schema: z.string(), fn: (_state, update) => update, }, }), - planChangeRequest: withLangGraph(z.custom(), { + planChangeRequest: withLangGraph(z.string(), { reducer: { - schema: z.custom(), + schema: z.string(), fn: (_state, update) => update, }, }), @@ -67,13 +72,19 @@ export const PlannerGraphStateObj = MessagesZodState.extend({ fn: (_state, update) => update, }, }), - proposedPlanTitle: withLangGraph(z.custom(), { + proposedPlanTitle: withLangGraph(z.string(), { reducer: { - schema: z.custom(), + schema: z.string(), fn: (_state, update) => update, }, default: () => "", }), + customRules: withLangGraph(z.custom().optional(), { + reducer: { + schema: z.custom().optional(), + fn: (_state, update) => update, + }, + }), }); export type PlannerGraphState = z.infer; diff --git a/packages/shared/src/open-swe/types.ts b/packages/shared/src/open-swe/types.ts index 722e311f..e227e77e 100644 --- a/packages/shared/src/open-swe/types.ts +++ b/packages/shared/src/open-swe/types.ts @@ -126,6 +126,13 @@ export type TargetRepository = { baseCommit?: string; }; +export type CustomRules = { + generalRules?: string; + repositoryStructure?: string; + dependenciesAndInstallation?: string; + testingInstructions?: string; +}; + export const GraphAnnotation = MessagesZodState.extend({ /** * The internal messages. These are the messages which are @@ -219,6 +226,15 @@ export const GraphAnnotation = MessagesZodState.extend({ }, default: () => false, }), + /** + * User defined rules. + */ + customRules: withLangGraph(z.custom().optional(), { + reducer: { + schema: z.custom().optional(), + fn: (_state, update) => update, + }, + }), // ---NOT USED--- ui: z