+
+
{getStatusText()}
{getStatusIcon()}
@@ -118,7 +128,7 @@ export function InitializeStep({
] ?? (
)}
@@ -128,7 +138,7 @@ export function InitializeStep({
"font-normal",
step.status === "error"
? "text-red-500"
- : "text-gray-800",
+ : "text-gray-800 dark:text-gray-200",
)}
>
{step.name}
diff --git a/apps/web/src/components/github/repo-branch-selectors/index.tsx b/apps/web/src/components/github/repo-branch-selectors/index.tsx
index e4653cf9..3177247c 100644
--- a/apps/web/src/components/github/repo-branch-selectors/index.tsx
+++ b/apps/web/src/components/github/repo-branch-selectors/index.tsx
@@ -6,9 +6,9 @@ export function RepositoryBranchSelectors() {
const [threadId] = useQueryState("threadId");
const chatStarted = !!threadId;
const defaultButtonStyles =
- "bg-inherit border-none text-gray-500 hover:text-gray-300 text-xs p-0 h-fit hover:bg-inherit";
+ "bg-inherit border-none text-gray-500 hover:text-black dark:hover:text-gray-300 text-xs p-0 h-fit hover:bg-inherit";
const defaultStylesChatStarted =
- "hover:bg-inherit cursor-default hover:cursor-default hover:text-gray-300 hover:border-gray-300 hover:ring-inherit";
+ "hover:bg-inherit cursor-default hover:cursor-default hover:text-black dark:hover:text-gray-300 hover:border-gray-300 hover:ring-inherit";
return (
diff --git a/apps/web/src/components/v2/terminal-input.tsx b/apps/web/src/components/v2/terminal-input.tsx
index 77d7c144..1d8e3fa7 100644
--- a/apps/web/src/components/v2/terminal-input.tsx
+++ b/apps/web/src/components/v2/terminal-input.tsx
@@ -155,13 +155,13 @@ export function TerminalInput({
diff --git a/apps/web/src/components/v2/thread-view.tsx b/apps/web/src/components/v2/thread-view.tsx
index 4cc8f573..d2516c5c 100644
--- a/apps/web/src/components/v2/thread-view.tsx
+++ b/apps/web/src/components/v2/thread-view.tsx
@@ -1,14 +1,15 @@
"use client";
-
import { v4 as uuidv4 } from "uuid";
-import { useState } from "react";
+import { useState, useEffect, useRef } from "react";
import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Card, CardContent } from "@/components/ui/card";
import { Textarea } from "@/components/ui/textarea";
import {
ArrowLeft,
GitBranch,
Send,
+ Terminal,
+ Clock,
User,
Bot,
Copy,
@@ -23,10 +24,16 @@ import { ThreadDisplayInfo } from "./types";
import { useStream } from "@langchain/langgraph-sdk/react";
import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types";
import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types";
+import { GraphState } from "@open-swe/shared/open-swe/types";
import { ActionsRenderer } from "./actions-renderer";
import { ThemeToggle } from "../theme-toggle";
import { HumanMessage } from "@langchain/core/messages";
import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants";
+import { StickToBottom } from "use-stick-to-bottom";
+import {
+ StickyToBottomContent,
+ ScrollToBottom,
+} from "../../utils/scroll-utils";
const PROGRAMMER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PROGRAMMER_ASSISTANT_ID;
const PLANNER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PLANNER_ASSISTANT_ID;
@@ -100,9 +107,23 @@ export function ThreadView({
const [programmerSession, setProgrammerSession] =
useState
();
- if (!stream.messages?.length) {
- return null;
- }
+ const prevMessageLength = useRef(0);
+ useEffect(() => {
+ if (
+ stream.messages &&
+ stream.messages.length !== prevMessageLength.current
+ ) {
+ prevMessageLength.current = stream.messages.length;
+ }
+ }, [stream.messages]);
+
+ useEffect(() => {
+ // Auto-scroll to bottom when tab switches (requirement fulfilled)
+ // StickToBottom handles the actual scrolling via scrollToBottom()
+ }, [selectedTab]);
+
+ // Don't early return for empty messages - show loading/empty state instead
+ // This prevents blank pages when navigating to existing threads
const handleSendMessage = () => {
if (chatInput.trim()) {
@@ -179,40 +200,58 @@ export function ThreadView({
{/* Left Side - Chat Interface */}
{/* Chat Messages */}
-
- {filteredMessages.map((message) => (
-
-
- {message.type === "human" ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
-
-
- {message.type === "human" ? "You" : "Agent"}
-
+
+
+
+ {filteredMessages.map((message) => (
+
+
+ {message.type === "human" ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+ {message.type === "human" ? "You" : "Agent"}
+
+
+
+ {getMessageContentString(message.content)}
+
+
+
+
+
+
+ ))}
+ >
+ }
+ footer={
+
+
-
- {getMessageContentString(message.content)}
-
-
-
-
-
-
- ))}
+ }
+ />
+
{/* Chat Input - Fixed at bottom */}
@@ -233,10 +272,10 @@ export function ThreadView({
@@ -247,56 +286,87 @@ export function ThreadView({
{/* Right Side - Actions & Plan */}
-
-
- setSelectedTab(value as "planner" | "programmer")
- }
+
+
-
- Planner
- Programmer
-
-
-
-
- {plannerThreadId &&
- plannerRunId &&
- PLANNER_ASSISTANT_ID && (
-
- graphId={PLANNER_ASSISTANT_ID}
- threadId={plannerThreadId}
- runId={plannerRunId}
- setProgrammerSession={setProgrammerSession}
- programmerSession={programmerSession}
- setSelectedTab={setSelectedTab}
- />
- )}
-
-
-
-
-
-
- {programmerSession && PROGRAMMER_ASSISTANT_ID && (
-
- graphId={PROGRAMMER_ASSISTANT_ID}
- threadId={programmerSession.threadId}
- runId={programmerSession.runId}
- />
- )}
- {!programmerSession && (
-
- No programmer session
-
- )}
-
-
-
-
+
+ setSelectedTab(value as "planner" | "programmer")
+ }
+ >
+
+ Planner
+ Programmer
+
+
+
+
+ {plannerThreadId &&
+ plannerRunId &&
+ PLANNER_ASSISTANT_ID && (
+
+ graphId={PLANNER_ASSISTANT_ID}
+ threadId={plannerThreadId}
+ runId={plannerRunId}
+ setProgrammerSession={setProgrammerSession}
+ programmerSession={programmerSession}
+ setSelectedTab={setSelectedTab}
+ />
+ )}
+ {!(
+ plannerThreadId &&
+ plannerRunId &&
+ PLANNER_ASSISTANT_ID
+ ) && (
+
+
+
+ No planner session
+
+
+ )}
+
+
+
+
+
+
+ {programmerSession && PROGRAMMER_ASSISTANT_ID && (
+
+ graphId={PROGRAMMER_ASSISTANT_ID}
+ threadId={programmerSession.threadId}
+ runId={programmerSession.runId}
+ />
+ )}
+ {!programmerSession && (
+
+
+
+ No programmer session
+
+
+ )}
+
+
+
+
+ }
+ footer={
+
+
+
+ }
+ />
+
diff --git a/apps/web/src/utils/scroll-utils.tsx b/apps/web/src/utils/scroll-utils.tsx
new file mode 100644
index 00000000..91ccce4a
--- /dev/null
+++ b/apps/web/src/utils/scroll-utils.tsx
@@ -0,0 +1,45 @@
+import { ReactNode } from "react";
+import { Button } from "@/components/ui/button";
+import { ArrowDown } from "lucide-react";
+import { useStickToBottomContext } from "use-stick-to-bottom";
+
+export function StickyToBottomContent(props: {
+ content: ReactNode;
+ footer?: ReactNode;
+ className?: string;
+ contentClassName?: string;
+}) {
+ const context = useStickToBottomContext();
+ return (
+
+
+ {props.content}
+
+ {props.footer}
+
+ );
+}
+
+export function ScrollToBottom(props: { className?: string }) {
+ const { isAtBottom, scrollToBottom } = useStickToBottomContext();
+
+ if (isAtBottom) return null;
+ return (
+
+ );
+}
diff --git a/package.json b/package.json
index b9edb4c3..7ade16b3 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
"typescript": "^5"
},
"resolutions": {
- "@langchain/langgraph-sdk": "^0.0.83",
+ "@langchain/langgraph-sdk": "^0.0.85",
"@langchain/core": "^0.3.58"
},
"packageManager": "yarn@3.5.1"
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 1f16e400..7df1e825 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -19,7 +19,7 @@
"dependencies": {
"@langchain/core": "^0.3.56",
"@langchain/langgraph": "^0.3.3",
- "@langchain/langgraph-sdk": "^0.0.83",
+ "@langchain/langgraph-sdk": "^0.0.85",
"@octokit/rest": "^22.0.0",
"zod": "^3.25.32"
},
diff --git a/yarn.lock b/yarn.lock
index ab991d5c..ccefd315 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1803,9 +1803,9 @@ __metadata:
languageName: node
linkType: hard
-"@langchain/langgraph-sdk@npm:^0.0.83":
- version: 0.0.83
- resolution: "@langchain/langgraph-sdk@npm:0.0.83"
+"@langchain/langgraph-sdk@npm:^0.0.85":
+ version: 0.0.85
+ resolution: "@langchain/langgraph-sdk@npm:0.0.85"
dependencies:
"@types/json-schema": ^7.0.15
p-queue: ^6.6.2
@@ -1819,7 +1819,7 @@ __metadata:
optional: true
react:
optional: true
- checksum: c6d037b8b9486456a8180d892e8b05ad3c2332456d7d237597720ebe42dd6b126f1aed963a0d2a681405724b7b8e0eac0607994b8e527d2f0e756e4c9e30c19e
+ checksum: f6d206c718cf4cb5d69dd677f2afa38325fdd69b485310a6beb61f06f24b2b4a8c0446eacbe7911c02cedc8bd4b719e22894c6f9b93572e6665c6a609cc9f511
languageName: node
linkType: hard
@@ -2264,7 +2264,7 @@ __metadata:
"@langchain/google-genai": ^0.2.9
"@langchain/langgraph": ^0.3.3
"@langchain/langgraph-cli": latest
- "@langchain/langgraph-sdk": ^0.0.84
+ "@langchain/langgraph-sdk": ^0.0.85
"@langchain/openai": ^0.5.10
"@octokit/rest": ^22.0.0
"@open-swe/shared": "*"
@@ -2299,7 +2299,7 @@ __metadata:
"@eslint/js": ^9.19.0
"@langchain/core": ^0.3.56
"@langchain/langgraph": ^0.3.3
- "@langchain/langgraph-sdk": ^0.0.83
+ "@langchain/langgraph-sdk": ^0.0.85
"@octokit/rest": ^22.0.0
"@octokit/types": ^12.0.0
"@tsconfig/recommended": ^1.0.8
@@ -2324,7 +2324,7 @@ __metadata:
"@eslint/js": ^9.19.0
"@langchain/core": ^0.3.57
"@langchain/langgraph": ^0.3.3
- "@langchain/langgraph-sdk": ^0.0.83
+ "@langchain/langgraph-sdk": ^0.0.85
"@open-swe/shared": "*"
"@radix-ui/react-alert-dialog": ^1.1.14
"@radix-ui/react-avatar": ^1.1.3