diff --git a/apps/open-swe/src/constants.ts b/apps/open-swe/src/constants.ts index b1121d6d..dd89268c 100644 --- a/apps/open-swe/src/constants.ts +++ b/apps/open-swe/src/constants.ts @@ -23,3 +23,7 @@ export const LANGGRAPH_USER_PERMISSIONS = [ "deployments:search", "store:access", ]; + +export enum RequestSource { + GITHUB_ISSUE_WEBHOOK = "github_issue_webhook", +} diff --git a/apps/open-swe/src/graphs/manager/nodes/classify-message/index.ts b/apps/open-swe/src/graphs/manager/nodes/classify-message/index.ts index 83507030..f25850ee 100644 --- a/apps/open-swe/src/graphs/manager/nodes/classify-message/index.ts +++ b/apps/open-swe/src/graphs/manager/nodes/classify-message/index.ts @@ -37,6 +37,7 @@ import { PLANNER_GRAPH_ID } from "@open-swe/shared/constants"; import { createLogger, LogLevel } from "../../../../utils/logger.js"; import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types"; import { createClassificationPromptAndToolSchema } from "./utils.js"; +import { RequestSource } from "../../../../constants.js"; const logger = createLogger(LogLevel.INFO, "ClassifyMessage"); @@ -86,6 +87,9 @@ export async function classifyMessage( messages: state.messages, taskPlan, proposedPlan: issuePlans?.proposedPlan ?? undefined, + requestSource: userMessage.additional_kwargs?.requestSource as + | RequestSource + | undefined, }); const respondAndRouteTool = { name: "respond_and_route", diff --git a/apps/open-swe/src/graphs/manager/nodes/classify-message/prompts.ts b/apps/open-swe/src/graphs/manager/nodes/classify-message/prompts.ts index 58030b44..a74b90c8 100644 --- a/apps/open-swe/src/graphs/manager/nodes/classify-message/prompts.ts +++ b/apps/open-swe/src/graphs/manager/nodes/classify-message/prompts.ts @@ -1,3 +1,5 @@ +import { RequestSource } from "../../../../constants.js"; + export const UPDATE_PROGRAMMER_ROUTING_OPTION = `- update_programmer: You should call this route if the user's message should be added to the programmer's currently running session. This should be called if you determine the user is trying to provide extra context to the programmer's current session.\n`; export const START_PLANNER_ROUTING_OPTION = `- start_planner: You should call this route if the user's message is a complete request you can send to the planner, which it can use to generate a plan. This route may be called when the planner has not started yet.\n`; @@ -41,6 +43,9 @@ Ensure your response is clear, and concise. Although you're only supposed to classify & respond to the latest message, this does not mean you should look at it in isolation. You should consider the conversation history as a whole, and the current status of your two AI assistants (programmer and planner) to determine how to respond & route the user's new message. +If the source is from a '${RequestSource.GITHUB_ISSUE_WEBHOOK}', you should ALWAYS classify it as a full request which should be routed to the planner. +The instances where the source will be '${RequestSource.GITHUB_ISSUE_WEBHOOK}' are when the user labels a GitHub issue as a task to be completed by the AI coding agent system. + # Context Although it's not shown here, you do have access to the full repository contents the user is referencing. Because of this, you should always assume you'll have access to any/all files or folders the user is referencing. @@ -48,6 +53,9 @@ Although it's not shown here, you do have access to the full repository contents The planner's current status is: {PLANNER_STATUS} The programmer's current status is: {PROGRAMMER_STATUS} +# Source +The source of the request is: {REQUEST_SOURCE} + {TASK_PLAN_PROMPT} {CONVERSATION_HISTORY_PROMPT} diff --git a/apps/open-swe/src/graphs/manager/nodes/classify-message/utils.ts b/apps/open-swe/src/graphs/manager/nodes/classify-message/utils.ts index aeebcb07..f6eb404d 100644 --- a/apps/open-swe/src/graphs/manager/nodes/classify-message/utils.ts +++ b/apps/open-swe/src/graphs/manager/nodes/classify-message/utils.ts @@ -31,6 +31,7 @@ import { START_PLANNER_FOR_FOLLOWUP_ROUTING_OPTION, } from "./prompts.js"; import { createClassificationSchema } from "./schemas.js"; +import { RequestSource } from "../../../../constants.js"; const THREAD_STATUS_READABLE_STRING_MAP = { not_started: "not started", @@ -72,6 +73,7 @@ export function createClassificationPromptAndToolSchema(inputs: { messages: BaseMessage[]; taskPlan: TaskPlan; proposedPlan?: string[]; + requestSource?: RequestSource; }): { prompt: string; schema: z.ZodTypeAny; @@ -166,6 +168,10 @@ export function createClassificationPromptAndToolSchema(inputs: { .replaceAll( "{CONVERSATION_HISTORY_PROMPT}", formattedConversationHistoryPrompt ?? "", + ) + .replaceAll( + "{REQUEST_SOURCE}", + inputs.requestSource ?? "no source provided", ); const schema = createClassificationSchema( diff --git a/apps/open-swe/src/routes/github/issue-webhook.ts b/apps/open-swe/src/routes/github/issue-webhook.ts index 4785ac04..e629505b 100644 --- a/apps/open-swe/src/routes/github/issue-webhook.ts +++ b/apps/open-swe/src/routes/github/issue-webhook.ts @@ -19,6 +19,7 @@ import { getOpenSWELabel, } from "../../utils/github/label.js"; import { ManagerGraphUpdate } from "@open-swe/shared/open-swe/manager/types"; +import { RequestSource } from "../../constants.js"; const logger = createLogger(LogLevel.INFO, "GitHubIssueWebhook"); @@ -149,6 +150,7 @@ webhooks.on("issues.labeled", async ({ payload }) => { additional_kwargs: { isOriginalIssue: true, githubIssueId: issueData.issueNumber, + requestSource: RequestSource.GITHUB_ISSUE_WEBHOOK, }, }), ], diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 3882f6f7..e06aab7c 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -148,4 +148,25 @@ .shadow-inner-left { box-shadow: inset 9px 0 6px -1px rgb(0 0 0 / 0.02); } + + .scrollbar-pretty-auto { + overflow-y: auto; + } + + .scrollbar-pretty-auto::-webkit-scrollbar { + width: 8px; + } + + .scrollbar-pretty-auto::-webkit-scrollbar-thumb { + border-radius: 9999px; + background-color: rgb(209 213 219); + } + + .scrollbar-pretty-auto::-webkit-scrollbar-track { + background-color: transparent; + } + + .dark .scrollbar-pretty-auto::-webkit-scrollbar-thumb { + background-color: rgb(75 85 99); + } } diff --git a/apps/web/src/components/gen-ui/action-step.tsx b/apps/web/src/components/gen-ui/action-step.tsx index 40d41f12..63d8adc5 100644 --- a/apps/web/src/components/gen-ui/action-step.tsx +++ b/apps/web/src/components/gen-ui/action-step.tsx @@ -145,47 +145,16 @@ function MatchCaseIcon({ matchCase }: { matchCase: boolean }) { ); } -function MatchWholeWordIcon({ matchWholeWord }: { matchWholeWord: boolean }) { - return ( - - - - -

ab

-
-
-
-
-
- - Match whole word {matchWholeWord ? "on" : "off"} - -
-
- ); -} +const coerceStringToArray = (str: string | string[]) => { + if (Array.isArray(str)) { + return str; + } + try { + return JSON.parse(str); + } catch { + return [str]; + } +}; function ActionItem(props: ActionItemProps) { const [expanded, setExpanded] = useState(false); @@ -379,7 +348,10 @@ function ActionItem(props: ActionItemProps) { Max results: {castProps.max_results} )} {castProps.file_types && castProps.file_types.length > 0 && ( - File types: {castProps.file_types.join(", ")} + + File types:{" "} + {coerceStringToArray(castProps.file_types).join(", ")} + )} {castProps.follow_symlinks && Follow symlinks} diff --git a/apps/web/src/components/thread/markdown-text.tsx b/apps/web/src/components/thread/markdown-text.tsx index be0f7a17..44d97c1e 100644 --- a/apps/web/src/components/thread/markdown-text.tsx +++ b/apps/web/src/components/thread/markdown-text.tsx @@ -196,7 +196,7 @@ const defaultComponents: any = { pre: ({ className, ...props }: { className?: string }) => (
+        
{code} - +
); } diff --git a/apps/web/src/components/thread/syntax-highlighter.tsx b/apps/web/src/components/thread/syntax-highlighter.tsx index f7c8b665..a7cc8c63 100644 --- a/apps/web/src/components/thread/syntax-highlighter.tsx +++ b/apps/web/src/components/thread/syntax-highlighter.tsx @@ -29,8 +29,10 @@ export const SyntaxHighlighter: FC = ({ customStyle={{ margin: 0, width: "100%", + maxWidth: "100%", background: "transparent", padding: "1.5rem 1rem", + overflow: "auto", }} className={className} > diff --git a/apps/web/src/components/v2/manager-chat.tsx b/apps/web/src/components/v2/manager-chat.tsx index 728dbf8f..0c3fb6a9 100644 --- a/apps/web/src/components/v2/manager-chat.tsx +++ b/apps/web/src/components/v2/manager-chat.tsx @@ -8,11 +8,10 @@ import { AnimatePresence, motion } from "framer-motion"; import { Bot, Copy, CopyCheck, Send, User, Loader2 } from "lucide-react"; import { Textarea } from "../ui/textarea"; import { Button } from "../ui/button"; -import { useStream } from "@langchain/langgraph-sdk/react"; -import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types"; import { cn } from "@/lib/utils"; import { isAIMessageSDK } from "@/lib/langchain-messages"; import { BasicMarkdownText } from "../thread/markdown-text"; + function MessageCopyButton({ content }: { content: string }) { const [copied, setCopied] = useState(false); @@ -98,7 +97,7 @@ export function ManagerChat({ initial={true} > @@ -121,7 +120,7 @@ export function ManagerChat({ )} -
+
{message.type === "human" ? "You" : "Agent"} @@ -130,7 +129,7 @@ export function ManagerChat({
- + {messageContentString}
diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js index 8b8f360d..a017ee72 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.js @@ -16,10 +16,6 @@ module.exports = { md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, - components: { - ".scrollbar-pretty": - "overflow-y-scroll [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-track]:bg-transparent", - }, colors: { background: "hsl(var(--background))", foreground: "hsl(var(--foreground))",