deer-flow/frontend/AGENTS.md
Ryker_Feng 01dc067997
feat: add composer input polishing (#3986)
* 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>
2026-07-08 17:10:27 +08:00

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 sections
    • docs/ — Docs / MDX rendering components
  • core/ — Business logic, the heart of the app. Domains include threads/ (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/) and utils/.
  • hooks/ — Shared React hooks
  • lib/ — Utilities (cn() from clsx + tailwind-merge)
  • content/ — MDX content (blog posts, docs) rendered by the app
  • styles/ — Global CSS with Tailwind v4 @import syntax and CSS variables for theming
  • typings/ — Ambient TypeScript declarations
  • Root files: env.js (env validation), mdx-components.ts (MDX component map)

Data Flow

  1. Optional composer helpers such as core/input-polish can rewrite the local draft before submission; confirmed user input then flows to thread hooks (core/threads/hooks.ts) → LangGraph SDK streaming
  2. Stream events update thread state (messages, artifacts, todos, goal)
  3. Stop actions call the LangGraph SDK stream stop path; core/threads/hooks.ts invalidates 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
  4. TanStack Query manages server state; localStorage stores user settings
  5. 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() in core/api/
  • Environment validation uses @t3-oss/env-nextjs with Zod schemas (src/env.js). Skip with SKIP_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 from task_running events (appended via mergeSteps, not overwritten) and backfilled on expand for historical runs by fetchSubtaskSteps, 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.ts is the pure model: messageToStep (live), eventsToSteps (reload), mergeSteps (dedup by message_index), and stepsForDisplay (what the card renders — keeps tool steps + AI steps with text, drops the trailing final-answer AI step when completed since it's shown as result). core/tasks/subtask-update.ts::computeNextSubtask is the pure per-subtask state transition (merge step deltas, keep terminal status stable); core/tasks/context.tsx's useUpdateSubtask applies it against a tasksRef mirroring the latest state (not a closure snapshot), so a late-resolving fetchSubtaskSteps backfill merges into current state instead of clobbering SSE steps or sibling subtasks that arrived meanwhile. The owning run_id is carried onto history content messages in buildVisibleHistoryMessages so the card can resolve the events endpoint.

Interaction Ownership

  • src/app/workspace/chats/[thread_id]/page.tsx owns composer busy-state wiring.
  • src/app/workspace/chats/[thread_id]/page.tsx owns branch-from-turn submission and navigation; sidecar MessageList instances do not receive the branch action.
  • src/app/workspace/chats/[thread_id]/page.tsx and src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx own active-goal display state for their composer overlays.
  • src/components/workspace/messages/message-list.tsx owns human-input card answered/latest/pending gating; entry pages only translate a submitted card response into sendMessage calls.
  • src/core/threads/hooks.ts owns 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/utils for conditional Tailwind classes.
  • Path alias: @/* maps to src/*.
  • Components: ui/ and ai-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:

  1. Follow the established src/ structure
  2. Add TypeScript types and proper error handling
  3. Write unit tests under tests/unit/ (pnpm test) and E2E tests under tests/e2e/ (pnpm test:e2e)
  4. Run pnpm check before committing
  5. Update this AGENTS.md when architecture, commands, or conventions change