* feat(frontend): add structured human input cards for ask_clarification Implement a reusable Human Input Card flow for ask_clarification while keeping the existing text fallback for older clients and IM channels. Backend: - Add structured ToolMessage.artifact.human_input payloads for clarification requests. - Preserve ToolMessage.content as the readable Markdown/text fallback. - Normalize clarification options from native lists, JSON strings, plain strings, mixed scalar values, None, and missing options. - Derive input_mode as choice_with_other when options exist, otherwise free_text. - Keep disable_clarification non-interactive behavior as a plain ToolMessage with no human_input artifact. - Cover artifact persistence and Gateway message metadata preservation in tests. Frontend: - Add human input protocol types, runtime guards, extractors, response builders, and thread-state helpers. - Add reusable HumanInputCard with option buttons, free-text input, pending, read-only, disabled, and answered states. - Render structured clarification cards from artifact.human_input, with Markdown fallback for malformed or legacy tool messages. - Preserve line breaks in structured question/context/option text. - Hide submitted clarification bridge messages from the chat UI via additional_kwargs.hide_from_ui. - Send structured human_input_response metadata through the fourth sendMessage options argument, preserving run context in the third argument. - Wire submissions for normal chats, custom agent chats, agent bootstrap chats, and sidecar chats. - Derive answered state from raw thread.messages so hidden replies still update the original card. - Clear pending state when the hidden reply arrives, dispatch is dropped, or a later async stream failure appears on thread.error. * perf(frontend): optimize HumanInputCard UI interactions - Support Enter key to submit text input (Shift+Enter for newline) - Render question and context fields as Markdown instead of plain text - Replace deprecated FormEventHandler type with structural typing * test(frontend): add unit test cover optimize HumanInputCard UI interactions * feat(frontend): disabled chatbox when has new human-input-card * fix(style): lint error fix * fix: sanitize hidden human input replies - Preserve IME composition safety for human input card Enter submits - Treat hidden human input responses as genuine user messages for sanitization - Keep hidden card replies in memory filtering while excluding malformed/internal hidden messages - Add regression coverage for card IME handling and hidden reply sanitization * fix: tighten human input response validation - Reject empty hidden human input response values - Remove invalid list ARIA role from human input card options - Add backend coverage for empty response payloads --------- 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/,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
- User input → 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 is a built-in composer command, not a skill activation. 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 requests are tied to the current threadId with an AbortController, so switching threads or unmounting the composer aborts in-flight goal requests and stale responses cannot update the new thread's goal state. The chat pages render GoalStatus above the composer from AgentThreadState.goal, with local optimistic state until the next stream values update arrives.
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