open-swe/ui/src/components/agents/AgentsHome.tsx

138 lines
5 KiB
TypeScript
Raw Normal View History

import { useEffect, useRef, useState } from "react"
import { useStreamContext as useAgentThreadStream } from "@langchain/react"
import { useQueryClient } from "@tanstack/react-query"
import { useNavigate } from "@tanstack/react-router"
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
import type { ImageChunk } from "@/lib/agents/types"
import type { CreateAgentThreadVariables } from "@/lib/agents/queries"
import type { ModelSelection } from "@/lib/agents/provider/useModelOptions"
fix: reliable, safe Slack account-connect prompt + first-login Slack dialog (#1383) * fix: deliver Slack account-link prompt as a visible threaded reply Blocked Slack users got no prompt at all. Prod logs show chat.postEphemeral returns ok, but ephemeral messages are silently dropped in Slack's assistant threads (where Open SWE runs), so the user sees nothing. Post the prompt as a normal threaded reply instead — the same channel the agent uses to reply. * fix: deliver Slack auth-failure prompt as a visible threaded reply leave_failure_comment() tried an ephemeral message first and only fell back to a thread reply on failure. Ephemeral messages succeed (ok) but are dropped in Slack's assistant threads, so the fallback never fired and the user saw no auth-failure prompt. Post the visible threaded reply directly, matching the account-link prompt fix. * fix: prompt blocked Slack users with a generic, token-free dashboard link Addresses the review findings that posting the per-user account-link token / auth URL in a visible thread lets any channel member bind their GitHub account to the triggering user's Slack identity. Drop the per-user signed link entirely. Both the account-link prompt (_post_account_link_prompt) and the runtime auth-failure prompt (leave_failure_comment) now post a plain dashboard settings link (build_settings_url) as a visible threaded reply. The user signs in with GitHub from their own session and connects Slack via verified OIDC on the settings page — no secret in the thread, nothing to hijack, and no DM machinery. * feat: nudge first-time users to connect Slack from the dashboard home Show a Connect Slack banner on the agents landing page whenever Slack OAuth is enabled and the user hasn't linked Slack yet. A first-time user (no Slack mapping) sees it immediately after signing in; it disappears once connected. * feat: prompt first-time users to connect Slack via a dialog Replace the inline Connect Slack card on the agents home with a modal dialog (Base UI). It opens automatically once the mapping query resolves to "not connected" and closes itself once Slack is linked; "Maybe later" dismisses it for the session. No new dependency — uses the design system's Base UI. * copy: frame Slack connect as resolving the user's GitHub account Drop 'act/reply on your behalf' wording across the connect-Slack dialog, the Slack thread prompts (blocked + auth-failure), and the settings description. Connecting Slack lets Open SWE resolve the user's GitHub account when they tag it in Slack.
2026-06-02 20:55:07 -07:00
import { AgentPromptBar } from "@/components/agents/AgentPromptBar"
import { OnboardingDialog } from "@/components/agents/OnboardingDialog"
fix: reliable, safe Slack account-connect prompt + first-login Slack dialog (#1383) * fix: deliver Slack account-link prompt as a visible threaded reply Blocked Slack users got no prompt at all. Prod logs show chat.postEphemeral returns ok, but ephemeral messages are silently dropped in Slack's assistant threads (where Open SWE runs), so the user sees nothing. Post the prompt as a normal threaded reply instead — the same channel the agent uses to reply. * fix: deliver Slack auth-failure prompt as a visible threaded reply leave_failure_comment() tried an ephemeral message first and only fell back to a thread reply on failure. Ephemeral messages succeed (ok) but are dropped in Slack's assistant threads, so the fallback never fired and the user saw no auth-failure prompt. Post the visible threaded reply directly, matching the account-link prompt fix. * fix: prompt blocked Slack users with a generic, token-free dashboard link Addresses the review findings that posting the per-user account-link token / auth URL in a visible thread lets any channel member bind their GitHub account to the triggering user's Slack identity. Drop the per-user signed link entirely. Both the account-link prompt (_post_account_link_prompt) and the runtime auth-failure prompt (leave_failure_comment) now post a plain dashboard settings link (build_settings_url) as a visible threaded reply. The user signs in with GitHub from their own session and connects Slack via verified OIDC on the settings page — no secret in the thread, nothing to hijack, and no DM machinery. * feat: nudge first-time users to connect Slack from the dashboard home Show a Connect Slack banner on the agents landing page whenever Slack OAuth is enabled and the user hasn't linked Slack yet. A first-time user (no Slack mapping) sees it immediately after signing in; it disappears once connected. * feat: prompt first-time users to connect Slack via a dialog Replace the inline Connect Slack card on the agents home with a modal dialog (Base UI). It opens automatically once the mapping query resolves to "not connected" and closes itself once Slack is linked; "Maybe later" dismisses it for the session. No new dependency — uses the design system's Base UI. * copy: frame Slack connect as resolving the user's GitHub account Drop 'act/reply on your behalf' wording across the connect-Slack dialog, the Slack thread prompts (blocked + auth-failure), and the settings description. Connecting Slack lets Open SWE resolve the user's GitHub account when they tag it in Slack.
2026-06-02 20:55:07 -07:00
import { Logo } from "@/components/agents/ported/Logo"
import {
agentThreadKeys,
invalidateAgentThreadLists,
optimisticThread,
seedAgentThreadLists,
} from "@/lib/agents/queries"
import { useModelOptions } from "@/lib/agents/provider/useModelOptions"
2026-06-03 14:24:08 -07:00
import { useProfile, useRepos } from "@/lib/profile"
import {
requestNotificationPermission,
setNotificationsPref,
} from "@/lib/notifications"
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
function promptContent(text: string, images: Array<ImageChunk>) {
const trimmed = text.trim()
const imageBlocks = images.map((image) => ({
type: "image",
base64: image.base64,
mime_type: image.mimeType,
...(image.fileName ? { file_name: image.fileName } : {}),
}))
return [...imageBlocks, ...(trimmed ? [{ type: "text", text: trimmed }] : [])]
}
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
export function AgentsHome() {
// Submit straight through the layout's persistent stream. The SDK mints the
// thread id (no client-minted id, no `getState` 404), fires the first
// `run.start` — which lazily creates + stamps + owns the thread server-side
// — and keeps streaming after we navigate to the minted thread below.
const stream = useAgentThreadStream()
const queryClient = useQueryClient()
const navigate = useNavigate()
fix: reliable, safe Slack account-connect prompt + first-login Slack dialog (#1383) * fix: deliver Slack account-link prompt as a visible threaded reply Blocked Slack users got no prompt at all. Prod logs show chat.postEphemeral returns ok, but ephemeral messages are silently dropped in Slack's assistant threads (where Open SWE runs), so the user sees nothing. Post the prompt as a normal threaded reply instead — the same channel the agent uses to reply. * fix: deliver Slack auth-failure prompt as a visible threaded reply leave_failure_comment() tried an ephemeral message first and only fell back to a thread reply on failure. Ephemeral messages succeed (ok) but are dropped in Slack's assistant threads, so the fallback never fired and the user saw no auth-failure prompt. Post the visible threaded reply directly, matching the account-link prompt fix. * fix: prompt blocked Slack users with a generic, token-free dashboard link Addresses the review findings that posting the per-user account-link token / auth URL in a visible thread lets any channel member bind their GitHub account to the triggering user's Slack identity. Drop the per-user signed link entirely. Both the account-link prompt (_post_account_link_prompt) and the runtime auth-failure prompt (leave_failure_comment) now post a plain dashboard settings link (build_settings_url) as a visible threaded reply. The user signs in with GitHub from their own session and connects Slack via verified OIDC on the settings page — no secret in the thread, nothing to hijack, and no DM machinery. * feat: nudge first-time users to connect Slack from the dashboard home Show a Connect Slack banner on the agents landing page whenever Slack OAuth is enabled and the user hasn't linked Slack yet. A first-time user (no Slack mapping) sees it immediately after signing in; it disappears once connected. * feat: prompt first-time users to connect Slack via a dialog Replace the inline Connect Slack card on the agents home with a modal dialog (Base UI). It opens automatically once the mapping query resolves to "not connected" and closes itself once Slack is linked; "Maybe later" dismisses it for the session. No new dependency — uses the design system's Base UI. * copy: frame Slack connect as resolving the user's GitHub account Drop 'act/reply on your behalf' wording across the connect-Slack dialog, the Slack thread prompts (blocked + auth-failure), and the settings description. Connecting Slack lets Open SWE resolve the user's GitHub account when they tag it in Slack.
2026-06-02 20:55:07 -07:00
const { models, defaultSelection } = useModelOptions()
const [selection, setSelection] = useState<ModelSelection | null>(null)
const activeSelection = selection ?? defaultSelection
feat: plan mode with model-driven entry and collaborative review (#1580) * feat: add plan mode for read-only research and planning Adds a per-run plan_mode flag that puts the agent in a read-only research phase: a strong prompt section is injected and mutating tools are stripped via ExcludeToolsMiddleware so the agent proposes a reviewable implementation plan before any edits. Surfaced in the dashboard UI with a Plan toggle (Shift+Tab) wired through the thread API. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: enforce plan-mode read-only at tool layer and disable subagents Addresses PR review: plan mode previously relied on prompt text to keep the shell read-only and left the task subagent (built with its own write/PR/Linear tools) unrestricted. Now `task` is excluded so research cannot be delegated to a mutating subagent, and a new PlanModeShellGuardMiddleware enforces a read-only command allowlist on `execute`, blocking writes, git state changes, installs, redirection, and command substitution regardless of model/prompt-injection compliance. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: harden plan-mode shell guard against wrapped mutations Block git global options that take values (-C, --git-dir, ...) from being misread as the subcommand, reject config-injection options (-c, --config-env, --exec-path), and drop the env command wrapper that could run arbitrary commands. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * feat: add plan mode with enter_plan_mode tool, profile/team defaults, Slack commands and approval flow - enter_plan_mode tool: agent self-activates plan mode via Command(update={'plan_mode': True}) - Plan mode resolution: per-thread > profile default > team default > False - PLAN_MODE_GUIDANCE_SECTION: always-present prompt section telling agent about the tool - profile_plan_mode_default and team plan_mode_default settings - Slack plan on/off/status commands with thread metadata persistence - slack_thread_reply plan_approval=True renders Approve/Revise/Cancel buttons - Interactivity handler: approve triggers implementation run, cancel posts confirmation - Frontend: plan_mode_default in Profile/ProfileUpdate/TeamSettings types and UI toggles Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * test: add tests for enter_plan_mode tool, profile/team defaults, Slack plan commands, approval blocks Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * refactor(plan-mode): drop shell guard, rely on prompt for read-only discipline Remove PlanModeShellGuardMiddleware and its enforcement of read-only shell commands during plan mode. Plan mode now relies on the system prompt to instruct the agent not to run mutating commands; the mutating-tool exclusion (ExcludeToolsMiddleware) is retained. * test(open-swe): add Playwright E2E for the Slack → PR → web handoff Local, secrets-free end-to-end suite that drives the full happy path through mock Slack/GitHub control panels and the real dashboard UI. Only the LLM and external SaaS HTTP boundaries (GitHub/Slack APIs, OAuth token mint) are faked — the real process_slack_mention, get_agent, deepagents loop, tools, middleware, and dashboard authorization all run under `langgraph dev` with a scripted fake chat model and a local temp-dir sandbox. - full_flow: a Slack mention runs the agent, which implements a change in the sandbox, opens a PR against a fake GitHub remote, and replies with the PR link in the same thread. - dashboard: clicking the bot's real "Open in Web" link loads the built ui/ app (served same-origin); the thread owner can continue the conversation, while a different user sees the same thread read-only (no composer). Wired into Agent CI as a `Playwright E2E` job that runs on pull requests. * fix(open-swe): serve E2E UI assets via explicit route; pin Playwright The dashboard E2E served the built ui/ SPA's /assets via app.mount(StaticFiles), but LangGraph's custom-app loader serves APIRoutes and drops sub-app Mounts, so /assets 404'd under `langgraph dev` in CI — the React app never booted and the composer/transcript never rendered. Serve assets via an explicit route instead. Also pin @playwright/test to the latest (1.61.0) for reproducible runs, and make the owner composer assertion tolerant of either hydration state. * test(open-swe): record Playwright trace + video on every E2E run Capture a replayable trace (DOM snapshots, network, console, source) and a screen recording for every test, not just retries, plus a screenshot on failure. The CI job already uploads playwright-report/ and test-results/, so each run now has a downloadable replay; documented how to open it. * feat(plan-mode): collaborative plan review with BlockNote + Yjs When the agent enters plan mode it writes the plan as a markdown file in the sandbox (save_plan tool), publishes it, and posts a review link to the source channel. Reviewers open the plan inside the dashboard (under the /agents shell), read it rendered in a BlockNote editor, and leave inline comments synced live over Yjs. Only the thread owner can approve; any reviewer can request changes. On approve/reject the comments are harvested and handed to the agent for the follow-up run; the agent never sees comments mid-review. - agent: enter_plan_mode persists plan state; new save_plan tool; prompt shares the plan-review link. - dashboard: Yjs WebSocket collab server (pycrdt-websocket) with store-backed snapshots; plan content/status store; plan REST API (get/approve/reject, owner-only approve, client-harvested comments); planStatus on thread summaries. - ui: BlockNote native comments (CommentsExtension + YjsThreadStore) plan page mounted under the agents shell, with a "Review plan" banner in the thread view and a back-link; theme-aware (dark mode) using the dashboard tokens. - e2e: Playwright coverage of the full Slack -> plan -> review -> approve -> PR flow, including cross-user comment sync and owner-only approval. * fix(plan-mode): address review feedback (authz, overrides, leaks, deps) - plan-collab WS: authorize per-thread before joining a room (same read gate as the REST API) — previously any logged-in user could join any thread (IDOR). - plan-collab: tie the snapshot flusher to active connections (refcount) so each opened plan no longer leaks a permanent 1.5s task on the shared event loop. - plan decisions: include thread_id in the follow-up run configurable so the run resumes the existing thread; set plan_mode explicitly so approve forces it off. - get_agent: an explicit per-thread plan_mode (Slack `plan off`, approved plan, dashboard toggle) now overrides profile/team defaults instead of falling back. - plan mode tool gating moved to a state-aware PlanModeMiddleware installed unconditionally, so a mid-run enter_plan_mode restricts the next model turn; before_agent resets stale plan_mode so a later run isn't forced back into it. - exclude write-capable http_request from plan mode. - pin pycrdt / pycrdt-websocket with upper bounds. Includes the latest base (#1583): E2E UI assets served via explicit route (fixes the Playwright CI failure — LangGraph's app loader drops sub-app mounts). * style: ruff format plan_collab.py * fix(plan-mode): owner-gate Slack approval + same-origin check on collab WS - Slack "Approve & Implement" now verifies the clicking user is the plan requester (owner, via the stored triggering_user_id) before implementing — matching the dashboard API's owner-only approval. Non-owners are pointed to Revise / feedback. - The plan-collab WebSocket validates the handshake Origin against the dashboard allowlist before accept() (no-op when unconfigured, e.g. local/dev), mirroring the REST require_same_origin CSRF defense. * fix(plan-mode): enter plan mode only via the model + local mock dev harness Plan mode is now entered solely when the model calls enter_plan_mode. Removed the per-user and team plan_mode_default settings (backend + UI) and the Slack `plan on/off/status` toggle. - enter_plan_mode returns a terminating ToolMessage, fixing the missing ToolMessage error that silently dropped plan mode mid-run. - PlanReview: defer Yjs provider/doc teardown so React StrictMode's dev remount doesn't destroy and then reuse the collaboration provider. - e2e plan_review spec asserts plan_mode actually engages. - LangSmith trace-url resolution is best-effort: bail before any API call when the tenant is unset, cache failures, log at debug. - Add `pnpm run dev:mock`: same-origin Vite HMR harness with a real LLM, Alice/Bob mock users, and a GitHub login picker. * docs(plan-mode): drop stale references to removed profile/team defaults The plan_mode middleware docstring and the approve/reject dispatch comment still described the profile/team plan_mode_default resolution that no longer exists; reword to match model-driven entry + the per-thread carry. * feat(plan-mode): let any reviewer edit the plan, not just comment Drop the owner/commenter split for the plan document: everyone with read access edits and comments alike (DefaultThreadStoreAuth "editor" for all, editor always editable until a decision, anyone seeds the empty doc). This matches the collab WS, which already relays frames to every readable user. Plan approval stays owner-gated. * test(plan-mode): assert plan-mode entry via the tool's success message plan_mode lives only in run state for tool gating; it is not a persisted thread-state channel, so the previous `values.plan_mode === true` poll could never pass. Assert instead that enter_plan_mode's success ToolMessage ("Plan mode is active …") lands in the thread — which only happens when the tool's Command applies cleanly, the exact regression this guards. --------- Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-23 15:06:58 -04:00
const [planMode, setPlanMode] = useState(false)
const [submitting, setSubmitting] = useState(false)
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
2026-06-03 14:24:08 -07:00
const reposQuery = useRepos()
const profileQuery = useProfile()
// undefined = untouched (fall back to the profile default); null = explicitly "no repo".
const [repoOverride, setRepoOverride] = useState<string | null | undefined>(
undefined
)
2026-06-03 14:24:08 -07:00
const repo =
repoOverride === undefined
? (profileQuery.data?.default_repo ?? null)
: repoOverride
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
// Holds the just-submitted prompt until the SDK mints the thread id; the
// effect then seeds the optimistic summary and navigates exactly once.
const draftRef = useRef<CreateAgentThreadVariables | null>(null)
useEffect(() => {
const id = stream.threadId
const draft = draftRef.current
if (!id || !draft) return
draftRef.current = null
const thread = optimisticThread(id, draft)
queryClient.setQueryData(agentThreadKeys.detail(id), thread)
seedAgentThreadLists(queryClient, thread)
invalidateAgentThreadLists(queryClient)
void navigate({ to: "/agents/$threadId", params: { threadId: id } })
}, [stream.threadId, queryClient, navigate])
const handleSubmit = (prompt: string, images: Array<ImageChunk>) => {
void requestNotificationPermission().then((perm) => {
if (perm === "granted") setNotificationsPref(true)
})
draftRef.current = {
prompt,
images,
repo,
repo_explicitly_none: repoOverride === null,
model_id: activeSelection?.modelId ?? null,
effort: activeSelection?.effort ?? null,
}
setSubmitting(true)
const configurable: Record<string, unknown> = {}
if (activeSelection?.modelId && activeSelection.effort) {
configurable.agent_model_id = activeSelection.modelId
configurable.agent_effort = activeSelection.effort
}
if (repo) configurable.repo = repo
if (repoOverride === null) configurable.repo_explicitly_none = true
feat: plan mode with model-driven entry and collaborative review (#1580) * feat: add plan mode for read-only research and planning Adds a per-run plan_mode flag that puts the agent in a read-only research phase: a strong prompt section is injected and mutating tools are stripped via ExcludeToolsMiddleware so the agent proposes a reviewable implementation plan before any edits. Surfaced in the dashboard UI with a Plan toggle (Shift+Tab) wired through the thread API. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: enforce plan-mode read-only at tool layer and disable subagents Addresses PR review: plan mode previously relied on prompt text to keep the shell read-only and left the task subagent (built with its own write/PR/Linear tools) unrestricted. Now `task` is excluded so research cannot be delegated to a mutating subagent, and a new PlanModeShellGuardMiddleware enforces a read-only command allowlist on `execute`, blocking writes, git state changes, installs, redirection, and command substitution regardless of model/prompt-injection compliance. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: harden plan-mode shell guard against wrapped mutations Block git global options that take values (-C, --git-dir, ...) from being misread as the subcommand, reject config-injection options (-c, --config-env, --exec-path), and drop the env command wrapper that could run arbitrary commands. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * feat: add plan mode with enter_plan_mode tool, profile/team defaults, Slack commands and approval flow - enter_plan_mode tool: agent self-activates plan mode via Command(update={'plan_mode': True}) - Plan mode resolution: per-thread > profile default > team default > False - PLAN_MODE_GUIDANCE_SECTION: always-present prompt section telling agent about the tool - profile_plan_mode_default and team plan_mode_default settings - Slack plan on/off/status commands with thread metadata persistence - slack_thread_reply plan_approval=True renders Approve/Revise/Cancel buttons - Interactivity handler: approve triggers implementation run, cancel posts confirmation - Frontend: plan_mode_default in Profile/ProfileUpdate/TeamSettings types and UI toggles Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * test: add tests for enter_plan_mode tool, profile/team defaults, Slack plan commands, approval blocks Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * refactor(plan-mode): drop shell guard, rely on prompt for read-only discipline Remove PlanModeShellGuardMiddleware and its enforcement of read-only shell commands during plan mode. Plan mode now relies on the system prompt to instruct the agent not to run mutating commands; the mutating-tool exclusion (ExcludeToolsMiddleware) is retained. * test(open-swe): add Playwright E2E for the Slack → PR → web handoff Local, secrets-free end-to-end suite that drives the full happy path through mock Slack/GitHub control panels and the real dashboard UI. Only the LLM and external SaaS HTTP boundaries (GitHub/Slack APIs, OAuth token mint) are faked — the real process_slack_mention, get_agent, deepagents loop, tools, middleware, and dashboard authorization all run under `langgraph dev` with a scripted fake chat model and a local temp-dir sandbox. - full_flow: a Slack mention runs the agent, which implements a change in the sandbox, opens a PR against a fake GitHub remote, and replies with the PR link in the same thread. - dashboard: clicking the bot's real "Open in Web" link loads the built ui/ app (served same-origin); the thread owner can continue the conversation, while a different user sees the same thread read-only (no composer). Wired into Agent CI as a `Playwright E2E` job that runs on pull requests. * fix(open-swe): serve E2E UI assets via explicit route; pin Playwright The dashboard E2E served the built ui/ SPA's /assets via app.mount(StaticFiles), but LangGraph's custom-app loader serves APIRoutes and drops sub-app Mounts, so /assets 404'd under `langgraph dev` in CI — the React app never booted and the composer/transcript never rendered. Serve assets via an explicit route instead. Also pin @playwright/test to the latest (1.61.0) for reproducible runs, and make the owner composer assertion tolerant of either hydration state. * test(open-swe): record Playwright trace + video on every E2E run Capture a replayable trace (DOM snapshots, network, console, source) and a screen recording for every test, not just retries, plus a screenshot on failure. The CI job already uploads playwright-report/ and test-results/, so each run now has a downloadable replay; documented how to open it. * feat(plan-mode): collaborative plan review with BlockNote + Yjs When the agent enters plan mode it writes the plan as a markdown file in the sandbox (save_plan tool), publishes it, and posts a review link to the source channel. Reviewers open the plan inside the dashboard (under the /agents shell), read it rendered in a BlockNote editor, and leave inline comments synced live over Yjs. Only the thread owner can approve; any reviewer can request changes. On approve/reject the comments are harvested and handed to the agent for the follow-up run; the agent never sees comments mid-review. - agent: enter_plan_mode persists plan state; new save_plan tool; prompt shares the plan-review link. - dashboard: Yjs WebSocket collab server (pycrdt-websocket) with store-backed snapshots; plan content/status store; plan REST API (get/approve/reject, owner-only approve, client-harvested comments); planStatus on thread summaries. - ui: BlockNote native comments (CommentsExtension + YjsThreadStore) plan page mounted under the agents shell, with a "Review plan" banner in the thread view and a back-link; theme-aware (dark mode) using the dashboard tokens. - e2e: Playwright coverage of the full Slack -> plan -> review -> approve -> PR flow, including cross-user comment sync and owner-only approval. * fix(plan-mode): address review feedback (authz, overrides, leaks, deps) - plan-collab WS: authorize per-thread before joining a room (same read gate as the REST API) — previously any logged-in user could join any thread (IDOR). - plan-collab: tie the snapshot flusher to active connections (refcount) so each opened plan no longer leaks a permanent 1.5s task on the shared event loop. - plan decisions: include thread_id in the follow-up run configurable so the run resumes the existing thread; set plan_mode explicitly so approve forces it off. - get_agent: an explicit per-thread plan_mode (Slack `plan off`, approved plan, dashboard toggle) now overrides profile/team defaults instead of falling back. - plan mode tool gating moved to a state-aware PlanModeMiddleware installed unconditionally, so a mid-run enter_plan_mode restricts the next model turn; before_agent resets stale plan_mode so a later run isn't forced back into it. - exclude write-capable http_request from plan mode. - pin pycrdt / pycrdt-websocket with upper bounds. Includes the latest base (#1583): E2E UI assets served via explicit route (fixes the Playwright CI failure — LangGraph's app loader drops sub-app mounts). * style: ruff format plan_collab.py * fix(plan-mode): owner-gate Slack approval + same-origin check on collab WS - Slack "Approve & Implement" now verifies the clicking user is the plan requester (owner, via the stored triggering_user_id) before implementing — matching the dashboard API's owner-only approval. Non-owners are pointed to Revise / feedback. - The plan-collab WebSocket validates the handshake Origin against the dashboard allowlist before accept() (no-op when unconfigured, e.g. local/dev), mirroring the REST require_same_origin CSRF defense. * fix(plan-mode): enter plan mode only via the model + local mock dev harness Plan mode is now entered solely when the model calls enter_plan_mode. Removed the per-user and team plan_mode_default settings (backend + UI) and the Slack `plan on/off/status` toggle. - enter_plan_mode returns a terminating ToolMessage, fixing the missing ToolMessage error that silently dropped plan mode mid-run. - PlanReview: defer Yjs provider/doc teardown so React StrictMode's dev remount doesn't destroy and then reuse the collaboration provider. - e2e plan_review spec asserts plan_mode actually engages. - LangSmith trace-url resolution is best-effort: bail before any API call when the tenant is unset, cache failures, log at debug. - Add `pnpm run dev:mock`: same-origin Vite HMR harness with a real LLM, Alice/Bob mock users, and a GitHub login picker. * docs(plan-mode): drop stale references to removed profile/team defaults The plan_mode middleware docstring and the approve/reject dispatch comment still described the profile/team plan_mode_default resolution that no longer exists; reword to match model-driven entry + the per-thread carry. * feat(plan-mode): let any reviewer edit the plan, not just comment Drop the owner/commenter split for the plan document: everyone with read access edits and comments alike (DefaultThreadStoreAuth "editor" for all, editor always editable until a decision, anyone seeds the empty doc). This matches the collab WS, which already relays frames to every readable user. Plan approval stays owner-gated. * test(plan-mode): assert plan-mode entry via the tool's success message plan_mode lives only in run state for tool gating; it is not a persisted thread-state channel, so the previous `values.plan_mode === true` poll could never pass. Assert instead that enter_plan_mode's success ToolMessage ("Plan mode is active …") lands in the thread — which only happens when the tool's Command applies cleanly, the exact regression this guards. --------- Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-23 15:06:58 -04:00
if (planMode) configurable.plan_mode = true
stream
.submit(
{
messages: [{ type: "human", content: promptContent(prompt, images) }],
},
{ config: { configurable } }
)
.catch(() => {
// Submit failed before the SDK minted a thread id — re-enable the
// prompt instead of leaving it disabled until a reload.
draftRef.current = null
setSubmitting(false)
})
}
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
return (
<div className="flex min-w-0 flex-1 flex-col overflow-y-auto px-6 py-8">
<OnboardingDialog />
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
<div className="mx-auto flex w-full max-w-2xl flex-1 flex-col items-center justify-center">
<div className="flex w-full flex-col items-center gap-6">
<Logo />
<AgentPromptBar
onSubmit={handleSubmit}
disabled={submitting}
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
models={models}
selection={activeSelection}
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
onSelectionChange={setSelection}
2026-06-03 14:24:08 -07:00
repos={reposQuery.data?.repositories}
selectedRepo={repo}
onRepoChange={setRepoOverride}
feat: plan mode with model-driven entry and collaborative review (#1580) * feat: add plan mode for read-only research and planning Adds a per-run plan_mode flag that puts the agent in a read-only research phase: a strong prompt section is injected and mutating tools are stripped via ExcludeToolsMiddleware so the agent proposes a reviewable implementation plan before any edits. Surfaced in the dashboard UI with a Plan toggle (Shift+Tab) wired through the thread API. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: enforce plan-mode read-only at tool layer and disable subagents Addresses PR review: plan mode previously relied on prompt text to keep the shell read-only and left the task subagent (built with its own write/PR/Linear tools) unrestricted. Now `task` is excluded so research cannot be delegated to a mutating subagent, and a new PlanModeShellGuardMiddleware enforces a read-only command allowlist on `execute`, blocking writes, git state changes, installs, redirection, and command substitution regardless of model/prompt-injection compliance. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: harden plan-mode shell guard against wrapped mutations Block git global options that take values (-C, --git-dir, ...) from being misread as the subcommand, reject config-injection options (-c, --config-env, --exec-path), and drop the env command wrapper that could run arbitrary commands. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * feat: add plan mode with enter_plan_mode tool, profile/team defaults, Slack commands and approval flow - enter_plan_mode tool: agent self-activates plan mode via Command(update={'plan_mode': True}) - Plan mode resolution: per-thread > profile default > team default > False - PLAN_MODE_GUIDANCE_SECTION: always-present prompt section telling agent about the tool - profile_plan_mode_default and team plan_mode_default settings - Slack plan on/off/status commands with thread metadata persistence - slack_thread_reply plan_approval=True renders Approve/Revise/Cancel buttons - Interactivity handler: approve triggers implementation run, cancel posts confirmation - Frontend: plan_mode_default in Profile/ProfileUpdate/TeamSettings types and UI toggles Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * test: add tests for enter_plan_mode tool, profile/team defaults, Slack plan commands, approval blocks Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * refactor(plan-mode): drop shell guard, rely on prompt for read-only discipline Remove PlanModeShellGuardMiddleware and its enforcement of read-only shell commands during plan mode. Plan mode now relies on the system prompt to instruct the agent not to run mutating commands; the mutating-tool exclusion (ExcludeToolsMiddleware) is retained. * test(open-swe): add Playwright E2E for the Slack → PR → web handoff Local, secrets-free end-to-end suite that drives the full happy path through mock Slack/GitHub control panels and the real dashboard UI. Only the LLM and external SaaS HTTP boundaries (GitHub/Slack APIs, OAuth token mint) are faked — the real process_slack_mention, get_agent, deepagents loop, tools, middleware, and dashboard authorization all run under `langgraph dev` with a scripted fake chat model and a local temp-dir sandbox. - full_flow: a Slack mention runs the agent, which implements a change in the sandbox, opens a PR against a fake GitHub remote, and replies with the PR link in the same thread. - dashboard: clicking the bot's real "Open in Web" link loads the built ui/ app (served same-origin); the thread owner can continue the conversation, while a different user sees the same thread read-only (no composer). Wired into Agent CI as a `Playwright E2E` job that runs on pull requests. * fix(open-swe): serve E2E UI assets via explicit route; pin Playwright The dashboard E2E served the built ui/ SPA's /assets via app.mount(StaticFiles), but LangGraph's custom-app loader serves APIRoutes and drops sub-app Mounts, so /assets 404'd under `langgraph dev` in CI — the React app never booted and the composer/transcript never rendered. Serve assets via an explicit route instead. Also pin @playwright/test to the latest (1.61.0) for reproducible runs, and make the owner composer assertion tolerant of either hydration state. * test(open-swe): record Playwright trace + video on every E2E run Capture a replayable trace (DOM snapshots, network, console, source) and a screen recording for every test, not just retries, plus a screenshot on failure. The CI job already uploads playwright-report/ and test-results/, so each run now has a downloadable replay; documented how to open it. * feat(plan-mode): collaborative plan review with BlockNote + Yjs When the agent enters plan mode it writes the plan as a markdown file in the sandbox (save_plan tool), publishes it, and posts a review link to the source channel. Reviewers open the plan inside the dashboard (under the /agents shell), read it rendered in a BlockNote editor, and leave inline comments synced live over Yjs. Only the thread owner can approve; any reviewer can request changes. On approve/reject the comments are harvested and handed to the agent for the follow-up run; the agent never sees comments mid-review. - agent: enter_plan_mode persists plan state; new save_plan tool; prompt shares the plan-review link. - dashboard: Yjs WebSocket collab server (pycrdt-websocket) with store-backed snapshots; plan content/status store; plan REST API (get/approve/reject, owner-only approve, client-harvested comments); planStatus on thread summaries. - ui: BlockNote native comments (CommentsExtension + YjsThreadStore) plan page mounted under the agents shell, with a "Review plan" banner in the thread view and a back-link; theme-aware (dark mode) using the dashboard tokens. - e2e: Playwright coverage of the full Slack -> plan -> review -> approve -> PR flow, including cross-user comment sync and owner-only approval. * fix(plan-mode): address review feedback (authz, overrides, leaks, deps) - plan-collab WS: authorize per-thread before joining a room (same read gate as the REST API) — previously any logged-in user could join any thread (IDOR). - plan-collab: tie the snapshot flusher to active connections (refcount) so each opened plan no longer leaks a permanent 1.5s task on the shared event loop. - plan decisions: include thread_id in the follow-up run configurable so the run resumes the existing thread; set plan_mode explicitly so approve forces it off. - get_agent: an explicit per-thread plan_mode (Slack `plan off`, approved plan, dashboard toggle) now overrides profile/team defaults instead of falling back. - plan mode tool gating moved to a state-aware PlanModeMiddleware installed unconditionally, so a mid-run enter_plan_mode restricts the next model turn; before_agent resets stale plan_mode so a later run isn't forced back into it. - exclude write-capable http_request from plan mode. - pin pycrdt / pycrdt-websocket with upper bounds. Includes the latest base (#1583): E2E UI assets served via explicit route (fixes the Playwright CI failure — LangGraph's app loader drops sub-app mounts). * style: ruff format plan_collab.py * fix(plan-mode): owner-gate Slack approval + same-origin check on collab WS - Slack "Approve & Implement" now verifies the clicking user is the plan requester (owner, via the stored triggering_user_id) before implementing — matching the dashboard API's owner-only approval. Non-owners are pointed to Revise / feedback. - The plan-collab WebSocket validates the handshake Origin against the dashboard allowlist before accept() (no-op when unconfigured, e.g. local/dev), mirroring the REST require_same_origin CSRF defense. * fix(plan-mode): enter plan mode only via the model + local mock dev harness Plan mode is now entered solely when the model calls enter_plan_mode. Removed the per-user and team plan_mode_default settings (backend + UI) and the Slack `plan on/off/status` toggle. - enter_plan_mode returns a terminating ToolMessage, fixing the missing ToolMessage error that silently dropped plan mode mid-run. - PlanReview: defer Yjs provider/doc teardown so React StrictMode's dev remount doesn't destroy and then reuse the collaboration provider. - e2e plan_review spec asserts plan_mode actually engages. - LangSmith trace-url resolution is best-effort: bail before any API call when the tenant is unset, cache failures, log at debug. - Add `pnpm run dev:mock`: same-origin Vite HMR harness with a real LLM, Alice/Bob mock users, and a GitHub login picker. * docs(plan-mode): drop stale references to removed profile/team defaults The plan_mode middleware docstring and the approve/reject dispatch comment still described the profile/team plan_mode_default resolution that no longer exists; reword to match model-driven entry + the per-thread carry. * feat(plan-mode): let any reviewer edit the plan, not just comment Drop the owner/commenter split for the plan document: everyone with read access edits and comments alike (DefaultThreadStoreAuth "editor" for all, editor always editable until a decision, anyone seeds the empty doc). This matches the collab WS, which already relays frames to every readable user. Plan approval stays owner-gated. * test(plan-mode): assert plan-mode entry via the tool's success message plan_mode lives only in run state for tool gating; it is not a persisted thread-state channel, so the previous `values.plan_mode === true` poll could never pass. Assert instead that enter_plan_mode's success ToolMessage ("Plan mode is active …") lands in the thread — which only happens when the tool's Command applies cleanly, the exact regression this guards. --------- Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-23 15:06:58 -04:00
planMode={planMode}
onPlanModeChange={setPlanMode}
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
/>
</div>
</div>
</div>
fix: reliable, safe Slack account-connect prompt + first-login Slack dialog (#1383) * fix: deliver Slack account-link prompt as a visible threaded reply Blocked Slack users got no prompt at all. Prod logs show chat.postEphemeral returns ok, but ephemeral messages are silently dropped in Slack's assistant threads (where Open SWE runs), so the user sees nothing. Post the prompt as a normal threaded reply instead — the same channel the agent uses to reply. * fix: deliver Slack auth-failure prompt as a visible threaded reply leave_failure_comment() tried an ephemeral message first and only fell back to a thread reply on failure. Ephemeral messages succeed (ok) but are dropped in Slack's assistant threads, so the fallback never fired and the user saw no auth-failure prompt. Post the visible threaded reply directly, matching the account-link prompt fix. * fix: prompt blocked Slack users with a generic, token-free dashboard link Addresses the review findings that posting the per-user account-link token / auth URL in a visible thread lets any channel member bind their GitHub account to the triggering user's Slack identity. Drop the per-user signed link entirely. Both the account-link prompt (_post_account_link_prompt) and the runtime auth-failure prompt (leave_failure_comment) now post a plain dashboard settings link (build_settings_url) as a visible threaded reply. The user signs in with GitHub from their own session and connects Slack via verified OIDC on the settings page — no secret in the thread, nothing to hijack, and no DM machinery. * feat: nudge first-time users to connect Slack from the dashboard home Show a Connect Slack banner on the agents landing page whenever Slack OAuth is enabled and the user hasn't linked Slack yet. A first-time user (no Slack mapping) sees it immediately after signing in; it disappears once connected. * feat: prompt first-time users to connect Slack via a dialog Replace the inline Connect Slack card on the agents home with a modal dialog (Base UI). It opens automatically once the mapping query resolves to "not connected" and closes itself once Slack is linked; "Maybe later" dismisses it for the session. No new dependency — uses the design system's Base UI. * copy: frame Slack connect as resolving the user's GitHub account Drop 'act/reply on your behalf' wording across the connect-Slack dialog, the Slack thread prompts (blocked + auth-failure), and the settings description. Connecting Slack lets Open SWE resolve the user's GitHub account when they tag it in Slack.
2026-06-02 20:55:07 -07:00
)
feat: add Agents chat UI for cloud threads (#1323) * feat(ui): add Agents chat UI ported from open-swe-app Introduce a Cursor-style Agents surface separate from the dashboard, with ported chat/diff components and mock thread data until LangGraph APIs land. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): wire Agents UI to LangGraph thread APIs Add dashboard thread list/detail/run/message/stream endpoints with a LangGraph message adapter, dashboard OAuth auth for runs, and TanStack Query hooks replacing mock data. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dashboard): single agent reply per turn in Agents UI Use UUID thread IDs LangGraph accepts, skip confirming_completion for dashboard threads, and merge adapter agent messages so duplicate bubbles do not render. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): polish Agents UI with floating prompt and layout cleanup Remove no-op chrome (git panel, headers, sidebar search), port CloudPromptBar from open-swe-app, and refine chat layout so messages scroll behind the input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent): patch deepagents reducer for None messages on checkpoint replay LangGraph thread state could 500 when cancelled runs left messages as None. Apply the reducer guard before graph import, fall back to metadata in the dashboard API, and adjust Agents prompt bar layout. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): unify sidebar user menu and clean up Agents UI navigation Extract SidebarUserMenu so the dashboard and Agents sidebars render the same profile button, drop the redundant Agents nav row in favor of the existing Back to Agents link, add the open-swe logo header to the Agents sidebar, flatten the New Agent button, and cap the home screen run list to keep the prompt input in view. * feat(ui): resizable/collapsible sidebar shared across dashboard and Agents Add a useSidebarLayout hook + SidebarFrame wrapper so both sidebars share a persisted width (default 260px, drag to resize, 200-420 range) and a collapse toggle that hides the panel and surfaces a floating reopen button. Also adds a DELETE /threads/{id} endpoint and an X-on- hover thread delete control in the Agents sidebar. * feat(ui): instant user message and busy indicator on Agents transition Stash submitted prompts in sessionStorage, pre-populate the new thread detail cache, and merge pending prompts into the rendered message list so the Agents page renders the user bubble plus the existing thinking spinner immediately instead of flashing a skeleton and "Agent is starting" while the run boots. * feat(ui): token-stream agent replies in the Agents thread view Opt the LangGraph runs into messages-tuple streaming and forward those events through the existing SSE channel. The frontend now applies AIMessageChunk deltas directly to the cached thread (cancelling any in-flight refetch first so optimistic tokens are not clobbered) and keeps positional pending prompts so the user bubble stays in the right place while the agent streams its reply. * fix(dashboard): await threads.join_stream before iterating threads.join_stream is async def returning an AsyncIterator, so it must be awaited before async for. The SSE endpoint was raising TypeError: 'async for' requires an object with __aiter__ method, got coroutine on every connection. * fix(dashboard): drop messages-tuple stream_mode that broke thinking-mode tool turns Setting stream_mode=["values","messages-tuple","updates"] on runs.create forces langchain_anthropic into streaming, and on the second model call (after tool execution) its serialized thinking blocks come back malformed, so Anthropic rejects the request with 'messages.1.content.0.thinking.thinking: Field required'. Revert to the default stream_mode so claude-opus thinking + tool use runs to completion. The frontend keeps the messages-event handler in place as a no-op fallback for when streaming is re-enabled. * feat(agents): per-thread model picker wired through to the run Add optional model_id/effort to the create-thread and send-message request bodies, forward them as agent_model_id/agent_effort in the LangGraph run configurable, and record the resolved choice in thread metadata so the UI can show the model the run is actually using. get_agent now picks the per-thread override last (highest priority over team default + profile override) and falls back gracefully when it is absent or unsupported. The frontend prompt bar becomes a controlled component fed by a shared useModelOptions hook (options + profile -> defaultSelection). AgentsHome seeds the picker from the user's profile default; the thread view seeds from the thread's recorded model/effort and lets each follow-up retarget the run. * refactor(ui): align Agents prompt bar layout with open-swe-app PromptBar Drop the absolute-positioned send button, restore the original px-4 py-3.5 min-h-[106px] flex-col container, and move the model picker into a mt-auto pt-2 footer row so the placeholder text and the model selector share the same horizontal padding. * chore: fix lint/format CI failures Remove unused imports and reformat two files flagged by ruff. * fix(tests): stop messages-reducer patch tests from polluting the suite Restore agent modules after reducer patch tests and import LangSmithSandbox from agent.server in proxy refresh tests so isinstance checks stay valid. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:15:59 -07:00
}