* feat: add composer input polishing * Revert "Merge branch 'main' into feat/input-polish" This reverts commit 5b6ceccf0db3092bc62fde3b05e7816829601756, reversing changes made to 45fbc57fef5fa5fd878cf0176c37f3e3bc7ebef6. * Merge main into feat/input-polish * style(frontend): format input helper polish guard * fix(input-polish): address composer polish review findings Frontend - Add a cancel affordance to the in-flight polish status pill that calls abortInputPolishRequest(), so a slow/hung provider no longer hard-locks the composer for up to stream_chunk_timeout with a page reload (and draft loss) as the only escape. - Reset promptHistoryIndexRef/promptHistoryDraftRef when a rewrite is applied (and on undo), so a stale history-browse index can no longer let the next ArrowDown silently overwrite the polished draft. - Disable polishing while an open human-input card is present, matching the frontend/AGENTS.md rule that composer entry points defer to the card so card-reply metadata is preserved. - canPolishInput now reuses parseGoalCommand/parseCompactCommand instead of a third hardcoded reserved-command regex, and drops the phantom /help entry (no /help parser exists in the composer), so future builtins only need to be taught to the existing parsers. Backend - Extract the non-graph one-shot LLM path (build model + inject Langfuse metadata + system/user invoke + text extract) into deerflow.utils.oneshot_llm.run_oneshot_llm, shared by the input-polish and suggestions routers so tracing-metadata and invocation shape cannot drift between the two copies. - strip_think_blocks gains truncate_unclosed (default True, preserving the suggestions/goal JSON-prep behavior); input polish passes False so a draft that legitimately contains a literal <think> substring is no longer truncated into a partial rewrite or a spurious 503. - Validate the empty-check and max_chars boundary against the same stripped view of the draft that is sent to the model, so the user-facing length boundary and the model input can no longer disagree. Tests / docs - Backend: literal-<think> preservation, whitespace-only rejection, and normalized-length/model-input agreement cases; suggestions tests repoint the create_chat_model patch to the shared helper module. - Frontend: helper unit tests updated for the /help/reserved-command change; a new Playwright case covers cancelling an in-flight polish request. - backend/AGENTS.md documents the shared one-shot helper and the polish normalization/think-tag behavior. --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
11 KiB
AGENTS.md
This file provides guidance to AI coding agents (Claude Code, Codex, and others) when working with the DeerFlow frontend. It is the source of truth; the sibling CLAUDE.md imports it via @AGENTS.md.
Project Overview
DeerFlow Frontend is a Next.js 16 web interface for an AI agent system. It communicates with a LangGraph-based backend to provide thread-based AI conversations with streaming responses, artifacts, and a skills/tools system.
Stack: Next.js 16, React 19, TypeScript 5.8, Tailwind CSS 4, pnpm 10.26.2. Requires Node.js 22+ and pnpm 10.26.2+.
Core dependencies
- LangGraph SDK (
@langchain/langgraph-sdk^1.5.3) — Agent orchestration and streaming - LangChain Core (
@langchain/core^1.1.15) — Fundamental AI building blocks - TanStack Query (
@tanstack/react-query^5.90.17) — Server state management - UI: Shadcn UI, MagicUI, React Bits, and Vercel AI SDK elements (generated from registries — see Code Style)
Commands
| Command | Purpose |
|---|---|
pnpm dev |
Dev server with Turbopack (http://localhost:3000) |
pnpm build |
Production build |
pnpm check |
Lint + type check (run before committing) |
pnpm lint |
ESLint only |
pnpm lint:fix |
ESLint with auto-fix |
pnpm format |
Prettier check (pnpm format:write to apply) |
pnpm test |
Run unit tests with Rstest |
pnpm test:e2e |
Run E2E tests with Playwright (Chromium) |
pnpm typecheck |
TypeScript type check (tsc --noEmit) |
pnpm start |
Start production server |
Unit tests live under tests/unit/ and mirror the src/ layout (e.g., tests/unit/core/api/stream-mode.test.ts tests src/core/api/stream-mode.ts). Powered by Rstest; import source modules via the @/ path alias.
E2E tests live under tests/e2e/ and use Playwright with Chromium. They mock all backend APIs via page.route() network interception and test real page interactions (navigation, chat input, streaming responses). Config: playwright.config.ts.
Architecture
Frontend (Next.js) ──▶ LangGraph SDK ──▶ LangGraph Backend (lead_agent)
├── Sub-Agents
└── Tools & Skills
The frontend is a stateful chat application. Users create threads (conversations), send messages, set thread-scoped /goal completion conditions, and receive streamed AI responses. The backend orchestrates agents that can produce artifacts (files/code), todos, and goal state updates.
Source Layout (src/)
app/— Next.js App Router. Routes include/(landing),/workspace/chats/[thread_id](chat),/workspace/agents/[agent_name]and/workspace/agents/new(custom agents),/blog/…, the(auth)/{login,setup,auth/callback}flow,/[lang]/docs/…, and/api/…route handlers (e.g./api/memory).components/— React components:ui/— Shadcn UI primitives (auto-generated, ESLint-ignored)ai-elements/— Vercel AI SDK elements (auto-generated, ESLint-ignored)workspace/— Chat page components (messages, artifacts, settings)landing/— Landing page sectionsdocs/— Docs / MDX rendering components
core/— Business logic, the heart of the app. Domains includethreads/(creation, streaming, state),api/(LangGraph client singleton),agents/(custom agents),auth/(authentication),artifacts/,channels/(IM connections),i18n/(en-US, zh-CN),settings/,memory/,skills/,messages/,mcp/,models/,input-polish/(pre-send draft rewrite API),suggestions/,tasks/,todos/,tools/,workspace-changes/(run-scoped changed-file summaries and diff fetching),config/,notification/,blog/, plus rendering helpers (rehype/,streamdown/) andutils/.hooks/— Shared React hookslib/— Utilities (cn()from clsx + tailwind-merge)content/— MDX content (blog posts, docs) rendered by the appstyles/— Global CSS with Tailwind v4@importsyntax and CSS variables for themingtypings/— Ambient TypeScript declarations- Root files:
env.js(env validation),mdx-components.ts(MDX component map)
Data Flow
- Optional composer helpers such as
core/input-polishcan rewrite the local draft before submission; confirmed user input then flows to thread hooks (core/threads/hooks.ts) → LangGraph SDK streaming - Stream events update thread state (messages, artifacts, todos, goal)
- Stop actions call the LangGraph SDK stream stop path;
core/threads/hooks.tsinvalidates current-thread, token-usage, and sidebar/search caches immediately and schedules one follow-up refetch because SDK stop may finish via abort + fire-and-forget cancel before backend title finalization commits - TanStack Query manages server state; localStorage stores user settings
- Components subscribe to thread state and render updates
/goal and /compact are built-in composer commands, not skill activations. src/components/workspace/input-box.tsx intercepts /goal, /goal clear, and /goal <condition> before normal chat submission, calling Gateway GET/PUT/DELETE /api/threads/{thread_id}/goal. Setting /goal <condition> also submits the condition text as the next user task so the agent starts running immediately; status and clear do not start a run. Goal and compact requests are tied to the current threadId with an AbortController, so switching threads or unmounting the composer aborts in-flight requests and stale responses cannot update the new thread's composer state. The chat pages render GoalStatus above the composer from AgentThreadState.goal, with local optimistic state until the next stream values update arrives. /compact calls POST /api/threads/{thread_id}/compact to summarize older active context while leaving the full visible chat history intact; it is skipped on new/empty threads and blocked server-side while a run is in flight.
Human input requests are a structured message protocol layered on normal chat history. The backend writes request payloads to ToolMessage.artifact.human_input, src/core/messages/human-input.ts owns the runtime validators/types, and src/components/workspace/messages/human-input-card.tsx renders the reusable card. MessageList owns answered/latest/pending state for visible cards, but derives answered responses from raw thread.messages because replies are hidden; pending cards clear when the hidden reply appears, when dispatch is dropped, or when a new thread.error reports an async stream failure. Page-level submit callbacks must send a normal human message and put hide_from_ui: true plus the response payload in the fourth sendMessage(..., options) argument as options.additionalKwargs; the third argument remains run context such as { agent_name }. Composer entry points should disable normal bottom input while hasOpenHumanInputRequest(...) is true so users answer through the card and preserve response metadata.
Key Patterns
- Server Components by default,
"use client"only for interactive components - Thread hooks (
useThreadStream,useSubmitThread,useThreads) are the primary API interface - LangGraph client is a singleton obtained via
getAPIClient()incore/api/ - Environment validation uses
@t3-oss/env-nextjswith Zod schemas (src/env.js). Skip withSKIP_ENV_VALIDATION=1 - Subtask step history (
core/tasks/) — the subtask card shows a subagent's full step timeline (#3779): its assistant reasoning turns interleaved with the tools it ran.Subtask.steps[]is accumulated live fromtask_runningevents (appended viamergeSteps, not overwritten) and backfilled on expand for historical runs byfetchSubtaskSteps, which pages the events endpoint scoped to one task (GET/runs/{runId}/events?event_types=subagent.step&task_id=…&after_seq=…) until a short page, so the run-wide limit can't truncate the timeline.core/tasks/steps.tsis the pure model:messageToStep(live),eventsToSteps(reload),mergeSteps(dedup bymessage_index), andstepsForDisplay(what the card renders — keeps tool steps + AI steps with text, drops the trailing final-answer AI step when completed since it's shown asresult).core/tasks/subtask-update.ts::computeNextSubtaskis the pure per-subtask state transition (merge step deltas, keep terminal status stable);core/tasks/context.tsx'suseUpdateSubtaskapplies it against atasksRefmirroring the latest state (not a closure snapshot), so a late-resolvingfetchSubtaskStepsbackfill merges into current state instead of clobbering SSE steps or sibling subtasks that arrived meanwhile. The owningrun_idis carried onto history content messages inbuildVisibleHistoryMessagesso the card can resolve the events endpoint.
Interaction Ownership
src/app/workspace/chats/[thread_id]/page.tsxowns composer busy-state wiring.src/app/workspace/chats/[thread_id]/page.tsxowns branch-from-turn submission and navigation; sidecarMessageListinstances do not receive the branch action.src/app/workspace/chats/[thread_id]/page.tsxandsrc/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsxown active-goal display state for their composer overlays.src/components/workspace/messages/message-list.tsxowns human-input card answered/latest/pending gating; entry pages only translate a submitted card response intosendMessagecalls.src/core/threads/hooks.tsowns pre-submit upload state and thread submission.
Code Style
- Imports: Enforced ordering (builtin → external → internal → parent → sibling), alphabetized, newlines between groups. Use inline type imports:
import { type Foo }. - Unused variables: Prefix with
_. - Class names: Use
cn()from@/lib/utilsfor conditional Tailwind classes. - Path alias:
@/*maps tosrc/*. - Components:
ui/andai-elements/are generated from registries (Shadcn, MagicUI, React Bits, Vercel AI SDK) — don't manually edit these.
Environment
Backend API URLs are optional; an nginx proxy is used by default:
NEXT_PUBLIC_BACKEND_BASE_URL=http://localhost:8001
NEXT_PUBLIC_LANGGRAPH_BASE_URL=http://localhost:8001/api
Leave these unset for the standard make dev / Docker flow, where nginx serves the public /api/langgraph/* prefix and rewrites it to Gateway's native /api/* routes.
Resources
Contributing
When adding features:
- Follow the established
src/structure - Add TypeScript types and proper error handling
- Write unit tests under
tests/unit/(pnpm test) and E2E tests undertests/e2e/(pnpm test:e2e) - Run
pnpm checkbefore committing - Update this
AGENTS.mdwhen architecture, commands, or conventions change