78 KiB
AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
Project Overview
This is a customized offline deployment of DeerFlow — a LangGraph-based AI super agent platform. The repo contains two sub-projects:
offline-backend-20260512/backend/— Python backend (FastAPI Gateway + LangGraph agent runtime)frontend-web/— Vite + React 19 frontend (TypeScript, pnpm)
Managed together via shell scripts in scripts/.
Offline Release Repository
Linux AMD64 离线发版脚本、wheelhouse、deploy.env 模板和部署文档统一维护在
knowledge-base-bj/deerflow-offline-deployment,不要把离线镜像或发版资产提交到
本业务源码仓库。正式发版从该仓库执行 scripts/build-latest-master.sh,脚本会读取
本仓库最新 master 的干净源码快照。
Commands
Full Application (from repo root)
./scripts/start-all.sh # Start backend + frontend as background daemons
./scripts/stop-all.sh # Stop both
./scripts/restart-all.sh # Restart both
./scripts/status.sh # Check running status
Logs and PID files are written to .runtime/logs/ and .runtime/pids/.
Backend Only (from offline-backend-20260512/backend/)
make install # Install Python dependencies (uv)
make dev # Run Gateway API with hot-reload on port 8001
make gateway # Run Gateway API without reload
make test # Run all tests: PYTHONPATH=. uv run pytest tests/ -v
make lint # Lint with ruff
make format # Format with ruff
Run a single test file:
PYTHONPATH=. uv run pytest tests/test_<feature>.py -v
Frontend Only (from frontend-web/)
pnpm install # Install dependencies
pnpm dev # Start Vite dev server on port 5174
pnpm build # Production build
pnpm typecheck # TypeScript type checking (tsc --noEmit)
Service Ports
| Service | Port | Notes |
|---|---|---|
| Backend (Gateway API) | 8001 | FastAPI + LangGraph runtime |
| Frontend (Vite) | 5174 | React SPA |
Frontend connects to backend via env vars VITE_BACKEND_BASE_URL and VITE_LANGGRAPH_BASE_URL (injected by scripts/start-frontend.sh).
Architecture
Backend
The backend (offline-backend-20260512/backend/) is split into two layers:
packages/harness/deerflow/— publishable agent framework (deerflow.*imports): LangGraph lead agent, middleware chain, sandbox, tools, MCP, memory, models, skills. Never imports fromapp.*.app/— application layer (app.*imports): FastAPI Gateway routers, IM channel integrations (Feishu, Slack, Telegram, DingTalk).
The Lead Agent (deerflow/agents/lead_agent/) is the single LangGraph graph entry point. It wraps a middleware chain (~18 middlewares assembled in strict order) around LLM calls, handling: thread isolation, sandbox lifecycle, uploads, memory, plan mode, vision, subagent delegation, and loop detection.
Middleware order matters — middlewares are appended in a fixed sequence; ClarificationMiddleware must always be last. See offline-backend-20260512/backend/AGENTS.md for the full ordered list.
Harness → App import firewall is enforced by tests/test_harness_boundary.py (runs in CI).
Workflow Studio (deerflow/workflows/ + /api/workflows*) is a second, independent execution path beside the lead agent: a persisted DAG scheduler that runs published workflow graphs, streams a durable event log over SSE, and supports human-input pauses, cancellation, and crash recovery via lease reclaim. Coze Studio supplies the canvas UI only; LangGraph is used only inside an agent/skill node. Master switch and all limits live in config.yaml → workflows. See the backend AGENTS.md/CLAUDE.md「Workflow Studio」and docs/WORKFLOW_STUDIO_BACKEND_DEV_ZH.md.
Frontend
The frontend (frontend-web/) is a React 19 + Vite SPA:
-
Routing: React Router v7; top-level routes in
src/pages/PageRoutes.tsx, workspace sub-routes insrc/pages/WorkspaceRoutes.tsx -
Pages:
ChatPage(thread chat),AgentChatPage(custom agent chat),AgentsPage/NewAgentPage(agent management),ScheduledTasksPage/ScheduledTaskRunDetailPage(scheduled tasks),LightAppManagementPage/LightAppListPage/LightAppIframeView(轻应用中心 — register/list/embed mini-apps) -
智能体对话跳转: 创建页、智能体管理编辑框和问答页详情面板都可以给智能体配置「对话跳转」链接、打开方式(新窗口 / 当前窗口)和路由参数(登录参数、主题参数,解析方式与应用管理相同)。配置保存在该智能体
config.yaml的chat_link,并镜像到agent_extras.chat_link供列表读取。留空时智能体管理的「对话」按钮仍进入本系统问答;填了地址后,按钮按解析后的链接跳转。 -
模型流完整性检测 + 截断记录 (stream truncation): 模型配置页(
/page/workspace/admin/models,ModelManagementPage.tsx)每张模型卡片新增「流完整性检测」开关 —PUT /api/admin/models/strict-stream-integrity/{name}写 config.yaml 的strict_stream_integrity: true/false(app/gateway/routers/model_management.py,仅 OpenAI 兼容 Chat Completions 模型可开;显式false也会覆盖 qwen3.8-flash-next 的自动开启;卡片显示值 = 显式配置 ?? qwen3.8-flash-next 自动规则,前端isAutoStrictIntegrityModel复刻后端判定)。开启后上游流未发终止帧就结束时抛可重试IncompleteChatStreamError(chat_stream_state.py),LLM 错误中间件按 upstream 重试,避免半截回答静默入库。同时每次流异常结束都由patched_openai.py的_ToolCallStreamDiagnostics._record_truncation_event落库stream_truncation_events(分类:silent_no_terminal静默截断 /incomplete_stream_error完整性拦截 /length_truncated/content_filter/midstream_error;write_file 兼容桥故意提前收流与用户取消不计;经fire_and_forget_db_write从任意 loop/thread 安全写入,store 在deerflow/persistence/stream_truncation/,每次写入顺手删除 30 天前的行,硬上限一个月)。查看页pages/StreamTruncationLogPage.tsx(路由/page/workspace/admin/stream-truncations,admin gate 同模型配置页;简洁模式「设置和更多」下拉的「模型截断记录」入口在workspace-nav-menu.tsx),clientcore/stream-truncation-log/api.ts;后端 APIGET /api/admin/stream-truncations(分页 + 模型/类型过滤)与/breakdown(按模型汇总,app/gateway/routers/stream_truncations.py)。测试tests/test_stream_truncation.py、tests/test_model_management.py。配套「保留中间正文」(管理员设置弹窗同名分区,GET/PUT /api/system-settings/retry-keep-partial,默认开启,关闭即恢复旧行为):①正常路径——原先「消息带可见工具调用就不给正文气泡」的分组规则会把步骤条执行中已流出的正文清掉;现groupMessages新增keepIntermediateText选项(MessageList 按开关传入):本轮全部过程正文合并成一个气泡挂在步骤条正下方(合成消息剥思考/工具调用,组 id 加:keep-text后缀避免 React key 冲突),步骤条保持一条不被拆开(正文气泡对工具消息/后续轮次的累积「透明」,lastOpenGroup/lastNonKeepBubbleGroup向前跳过),分组完成后把合并气泡重排到 present-files/subagent/document-rewrite 附属组之后(正文块与最终回答相邻、不被文件卡片隔开),仅本轮「最终正文消息」(回扫到的最后一条有正文且无可见工具调用的 ai 消息)保留独立气泡;②重试路径——LLM 错误中间件静默重试时,上一次尝试已流出的半截正文从未进 checkpoint、会被前端 values 对账清掉,core/threads/hooks.ts在收到llm_retry事件时快照在途消息,被丢弃后按原位插回(纯函数core/threads/retry-kept-messages.ts,快照只在本轮流式期间有效,刷新即失)。测试tests/test_strategy_project_settings.py尾部、src/core/messages/utils-group.test.ts、src/core/threads/retry-kept-messages.test.ts(pnpm test:retry-kept)。 -
知识库深度研究(普通知识库限定问答): 每个可读知识库从卡片或详情页进入
/page/workspace/knowledge/:noteId/research。它不复用原“深度研究”流程,而是把普通 Lead Agent 问答在服务端强制绑定到单个知识库、经典检索并关闭工具/记忆;私有会话与草稿仅创建者可见。首个完整回答自动成为 Markdown 草稿,后续回答须显式“应用为文档”。可写用户保存时在该知识库 Wiki 下创建保留根目录「深度研究」,用独立 publication id 的稳定 slug 创建/覆盖文章;文件名独立于问答标题。书架文章随知识库读权限共享,但“更多”及原问答仅创建者可见;只读用户可问答和整理草稿,不能发布。前端核心页pages/KnowledgeResearchPage.tsx,clientstrategy-components/api/knowledge-research.ts;后端 REST 在app/gateway/routers/llmwiki.py,运行时约束app/gateway/knowledge_research.py,持久化deerflow/persistence/knowledge_research.py。 -
AI 写作·对话版(对话驱动写作台): a parallel new entry beside the classic AI-writing page (classic page/routes untouched) — route
/page/canvas/ai-writing-chat, sidebar 「问答管理 → 课题研究(对话版)」. Pageopen-canvas/pages/AIWritingChatPage.tsxreuses the engine (AIWritingContext+useStream+AIWritingDraftPanel); idle keeps the classic setup layout (WritingSetupChat+ collapsible form) and switches to the chat workbench once writing starts; left panelcomponents/ai-writing-chat/WritingChatPanel.tsx= unified chronological feed (ChatFlowTimeline.tsx, chat bubbles anchor-interleaved into the progress timeline so triggered events render below the user's message) + button-only intervention cards (ChatInterventionCards.tsx, inline textareas removed) + bottom unified smart composer. Intent resolution: frontend fast path +POST /api/ai-writing/sessions/{id}/intent→deerflow/agents/ai_writing/intent_router.py(per-pause action whitelist, high-cost confidence gates, payload cleaning); supports QA bypass, clarify follow-ups, control intents, and material-stage auto re-search + pending-intent resume. Teststests/test_ai_writing_intent.py; plan docfrontend-web/docs/ai-writing-对话驱动写作台-实现方案.md. 素材检索技能池限定白名单:researcher_node通用检索的候选池 = ai-writing-researcher 智能体 config.yamlskills白名单里的启用技能(管理员在智能体管理页增删,内置knowledge-base-search保底且始终排最前),不再列全部启用技能——300+ 技能部署把选择提示词撑爆导致素材检索不可用;容错兜底_fallback_candidate_skills同样限定白名单;白名单未配置/加载失败回退仅内置知识库检索。实现deerflow/agents/ai_writing/nodes/researcher.py::_load_candidate_skills/_researcher_skill_whitelist,测试tests/test_ai_writing_knowledge_base.py。 -
轻应用中心 (Light App Center): a sibling of 任务管理 in the sidebar with two pages — 应用管理 (admin registers
window_openoriframemini-apps with a route-param builder: login params resolved fromlocalStorage.userInfo, theme params literal) and 应用列表 (card grid ofwindow_openapps,window.openlaunch).iframeapps are mounted under a chosen parent menu (XK/PG/TY/综合管理) and dynamically injected into the sidebar bycore/page-layout/use-sidebar-menu.ts(useSidebarMenuItems), opening in theLightAppIframeViewhost. Client + React Query hooks instrategy-components/api/light-apps.ts; URL builder/constants instrategy-components/lib/light-app.ts. Backend:/api/light-apps(global/shared, admin-gated writes). -
菜单管理 (Menu Management, admin defaults + personal layouts):
MenuManagementPage(pages/MenuManagementPage.tsx) is available from「设置和更多」and the global Header account dropdown. Administrators edit the shared default menu; ordinary users edit only their own full/compact menu snapshot. For users with a saved snapshot, known node ids keep the personallabel/disabled/parentId/sortOrder(or compact placement/order), while administrator defaults for node ids absent from that snapshot flow through—so newly-added code menus and iframe light apps follow the current default without overwriting older personal choices. Temporarily absent dynamic nodes are preserved when an unrelated personal edit is saved;「恢复默认」deletes only that user's snapshot. The static tree (core/page-layout/sidebar-menu.ts) remains the source of icons/routes and administrator defaults remain sparse per-node deltas. Merge/snapshot helpers live incore/page-layout/menu-overrides.tsandcompact-menu-overrides.ts; full-menu writes carry the complete effective parent hierarchy for cycle/strict three-level validation while persisting only the sparse admin delta or full personal snapshot. Backend/api/menu-overridesusesmenu_overrides(default) +user_menu_override_configs(per-user snapshot);/api/compact-menu-overridesusescompact_menu_overrides(typeA/Bdefaults) +user_compact_menu_override_configs(per-user/type snapshots). Each website selects its compact type viaruntime-config.jsVITE_COMPACT_MENU_TYPE;CompactToolboxrenderstoolboxitems. -
生成网页标识 (Generated Page Branding, admin-only write): 管理员在设置弹窗「生成网页标识」中填写网页展示名称、上传 PNG/JPG/WebP Logo(≤10MB)、单独移除 Logo 或整体清除配置。配置保存在 runtime
system_settings.json -> generated_page_branding,图片保存在 runtimegenerated-page-branding/,GET /api/system-settings/generated-page-branding全员可读,PUT/DELETE仅管理员;生成 HTML 通过公开只读/api/public/html-runtime/generated-page-logo获取图片。只有网页展示名称非空且 Logo 文件真实存在时功能才启用;缺少任意一项(含单独移除 Logo)都不注入任何内容,完全保持原有网页生成逻辑。该配置只约束后续生成的 HTML 网页内容,不修改 DeerFlow 自身的系统名称、主导航、欢迎区、工作区标题或 Logo。启用时,HTML 写入与page_report_template validate会由html_runtime/generated_page_chrome.py确定性注入平台套壳:页头通栏全宽、贴顶、无圆角,背景直接用全量模式导航栏设计图(html_runtime/assets/topbar-light.svg浅色红调 /topbar-dark.svg深色深蓝,已注册进网关html_runtime.py的_STATIC_ASSETS白名单以/api/public/html-runtime/topbar-*.svg提供;页头运行时按 body 明暗 tone 自动切图),只注入页头、不注入页脚;设置弹窗不再提供「品牌位置」选择,新配置默认使用center:「Logo+系统名称」居中锁孔(26px 白色金属渐变字 + 深浅辉光,落在设计图中央发光基座上)+ 页面导航分列两侧白字页签(当前项白色加粗 + 白色墨条)。generated_page_branding.header_layout字段继续兼容存量配置,既有left仍按品牌居左 + 导航居中单行渲染。页签均为通栏等高下划线式(当前项主色 + 底部渐变墨条),多页强制首个逻辑页初始可见。开关chrome_enabled默认开启;明确关闭时完全回归无注入:不注入页头、也不对页面提任何品牌要求(校验不检查品牌,模型不会被迫自写)。页脚与「网页页脚文字」设置项均已按需求移除(FOOTER_MARKER常量仅为旧页面兼容保留)。模板套件的兜底导航条同款(品牌左 + 导航中、通栏贴顶),平台注入时经body:has([data-df-generated-header])+ 兜底脚本双保险自动隐藏。后处理会隐藏旧的多页导航、移除模型写入的data-df-brand重复标识且保持幂等;lead prompt 只让模型生成正文和逻辑页。注入点共四处且全部账号无关(admin 配置对全部账号生效,非 admin 仅无配置入口):page_report_template prepare(工作副本创建时即预注入页头,模型从第一秒起编辑的就是带品牌导航的页面,brief 的platform_branding.pre_injected标记此状态)、write_file/str_replace(sandbox/tools.py的maybe_fix_generated_html)、page_report_template validate、以及present_files交付兜底(present_file_tool._ensure_generated_html_chrome,覆盖模型经 bash 等旁路写出的 HTML;幂等,>4MB 跳过);注入时还会归一化 body/html 顶部 padding/margin(清零)(防弱模型为自想页头留白)并隐藏模型自写的顶部 nav/header(仅正文首个内容容器之前的,正文内导航不受影响);品牌配置每次实时读取system_settings.json,无缓存无账号隔离。 -
生成网页默认明暗模式 (Generated Page Theme):
ChatPage与AgentChatPage每次发送只把当前界面的明暗模式作为presentation_theme=light|dark上下文传入。用户未明确指定网页主题时,自由生成 HTML、显式模板的auto选择以及平台注入的品牌页眉/页脚按该明暗模式生成;用户明确指定浅色、深色或其他配色时以用户要求为准。不得跟随系统的红、蓝、绿等主题主色或自定义主色,网页强调色由页面内容与可读性决定;科技、数据、看板、驾驶舱或地图题材本身不得再触发深色默认。 -
生成网页离线下载包 (Generated HTML Offline Bundle): 产物面板下载
.htm/.html/.xhtml时不再只保存单个 HTML,而是请求GET /api/threads/{thread_id}/artifacts/{path}?download=true&offline_bundle=true(公开只读分享的文件端点同样支持),下载{原名}-离线包.zip。纯引擎deerflow/html_runtime/offline_bundle.py把入口统一为index.html,只将平台信任的/api/public/html-runtime/*JS/CSS/GeoJSON/品牌资源、认知态势图运行目录和签名/api/public/html-images/*图片复制到assets/并改写为相对地址;不得联网抓取任意第三方 URL。包内还带manifest.json、中文README.txt和实际使用库的许可证。Leaflet 在线瓦片及用户主动引用的公网图片/API 不复制,保留原地址并列入external_urls。非 HTML 下载、HTML 预览/新窗口打开及流式未落盘的临时下载行为不变。 -
系统名称与 Logo(与生成网页标识分开): 管理员在设置弹窗「系统名称」里填写本系统导航、侧栏和欢迎区使用的名称,并可上传一张 PNG/JPG/WebP Logo。顶部导航名称字号可选
adaptive(默认,30px 上限、24px 下限)或fixed(16–40px);配置字段为app_identity.header_title_font_mode/header_title_font_size,固定模式不再按分辨率缩放,超长名称仍单行完整显示。红色主题还可分别上传导航栏背景图与菜单选中样式图(PNG/JPG/WebP/SVG,不设应用层文件大小限制),字段为light_red_header_background_*/light_red_nav_active_*;选择文件或恢复默认时顶部导航即时预览,未配置时继续使用top-bg-light-red.svg/nav-active-light-red.svg。其余配置在system_settings.json -> app_identity,图片统一放在运行目录app-identity/,接口GET/PUT/DELETE /api/system-settings/app-identity(写仅管理员),图片通过/api/public/app-identity/*读取。名称留空继续用「CM业务中心」/「CM 助手」;未上传 Logo 继续用各主题静态图片和既有runtime-config.js覆盖。不读写generated_page_branding,也不改已生成的 HTML。 -
平台模板套件 (Bootstrap page-template-suite 技能): 内置技能
skills/public/page-template-suite(public 类,缺省启用)让「画页面」走平台预制 Bootstrap 模板而不是从零手写。套件只提供多页面门户(最低 2 个逻辑页),不做单页面 HTML(单页请求自动回落共享响应式文档模板):5 套设计 × 明暗两版共 10 个deerflow/page_reports/assets/report-bootstrap-*-template.html(图表门户 3 页 / 甘特双页「进度总览+里程碑风险」/ 瓦片地图门户 3 页 / 文字杂志双页「正文论述+脉络结论」/ 研判简报双页「核心结论+风险行动」),profile 注册于page_reports/templates.py(idbootstrap-charts-portal-{light,dark}、bootstrap-gantt-{light,dark}、bootstrap-map-portal-{light,dark}、bootstrap-text-magazine-{light,dark}、bootstrap-text-briefing-{light,dark})。显式template_id时页数以模板为准(prepare 会把 brief 的页数对齐到模板页数,技能无需依赖用户说出「多页」)。模板源文件用__DF_HTML_RUNTIME_BASE__占位符引用 Gateway 运行库(Bootstrap/Alpine/ECharts/Leaflet/deerflow-runtime.js),page_report_template prepare在会话副本中替换为设置里保存的 HTML 运行库地址,部署无关;图表走data-df-chart严格 JSON(甘特 = 时间轴 + 阶段重叠柱barGap:-100%),地图门户走data-df-mapLeaflet 瓦片契约(瓦片地址来自设置里保存的地图瓦片地址,需管理员在「外观设置 → HTML 地图」选择「地图网页」引擎,引擎不符时 prepare 的 brief 会带map_engine_warning)。prepare 的 brief 还带platform_branding(管理员「生成网页标识」的名称/Logo/页脚文字与注入规则,正文禁止自写品牌)和runtime_base_url;_choose_page_profile现在显式template_id优先(单页文档模式也能选中套件模板,此前单页一律回落共享文档模板),未显式指定时旧选择行为不变。多页门户模板自带锚点切页兜底脚本(检测data-df-generated-header让位平台套壳,勿用data-df-generated-chrome字面量——会被注入幂等检查误判)。测试tests/test_page_template_suite.py(10 模板 prepare→validate 全绿 + 品牌注入 + 居左/居中布局 + 引擎门控)。 -
Docker 内置技能同步: Linux Docker 启动时不能让旧的持久化
/app/skills遮住当前版本的backend/skills/public。共享实现app/builtin_skill_sync.py会把 bundled public 覆盖同步到 live public 根、保留custom/,并强制校验 Facebook/X/TikTok/YouTube/Threads/CNN/BBC/新华社/白宫官网/马尼拉时报/GMA News/雅虎日本/读卖新闻共 13 个硬路由页面技能;它使用跨进程文件锁和必需包内容摘要,多 worker 首次只复制一次,后续相同版本只校验跳过。两个受支持启动脚本仍在 Gateway 前调用backend/scripts/sync_builtin_skills.py,把当前源码backend/skills只读挂载为/app/bundled-skills、live skills 读写挂载到/app/skills;同时 Gateway lifespan 在加载配置后、技能 ownership 迁移/MySQL 目录投影/提示词缓存之前再次调用同一共享实现,所以镜像默认入口、Compose 或外部发版脚本绕过 Shell 时也会自动补齐。任一硬路由技能缺失会 fail-fast 并打印名单,不能静默启动成“技能不可用”。 -
自定义技能迭代与版本切换: 管理员设置弹窗的「自定义技能迭代」总开关默认关闭(
GET/PUT /api/system-settings/skill-versioning);关闭时保持原同名覆盖逻辑。开启后仅skills/custom参与:本人上传同名技能可选「迭代为新版本」(保留完整历史包并激活新版)或「直接覆盖」(清除旧版本链,以本次内容重新作为 v1);本人技能详情顶部也提供「迭代新版本」安装包入口,详情弹窗「版本」页可切换任一快照。历史包保存在 runtimeskill-versions/<name>/vNNNNNN,位于技能扫描/沙箱挂载目录之外;skills/custom/<name>永远只保留一个活动包,所以大模型只能发现当前激活版本。版本元数据与调用统计解耦并继续以稳定技能名计数,上传迭代和版本切换都不清零外部调用次数。实现deerflow/skills/versioning.py、app/gateway/routers/skills.py的/versions接口,测试tests/test_skill_versioning.py与tests/test_strategy_project_settings.py。 -
Facebook 页面生成技能: 内置公共技能
skills/public/facebook-page-generator只在用户明确要求 Facebook/Facebook 风格网页时触发;lead_agent/prompt.py的普通问答 HTML 分流会强制读取该技能,并排除通用page-report-generation、模板套件和从零手写 HTML。单账号单动态场景强制只写小型中文规格 JSON,再调用一次scripts/fast_single_post.py --spec;脚本可将总评论数与实际展示数分离,按commentTopics自动补齐最多 100 条中文热评,也接受用户指定的逐条评论,并在一次进程内完成七模块 JSON、内部校验和 HTML 渲染。用户已提供素材或明确不搜索时禁止联网、图像生成、读模板/脚本源码和临时构建脚本;动态与评论正文必须为简体中文。多帖子场景仍使用scripts/render_facebook_page.py的init → validate → render流程确定性注入assets/facebook-template.html,模型不得重写模板 DOM/CSS;渲染器拒绝覆盖已有文件并对</script>注入做转义。最终单文件 HTML 保留右上角设置按钮、分模块 JSON 编辑弹窗、实时校验与「应用并渲染」,以及响应式、深浅主题、发布/点赞/评论/搜索交互;浏览器缓存键包含初始 JSON 的 FNV-1a 指纹,同一制品刷新保留手动编辑,不同新制品不会再被沙箱同源旧 JSON 覆盖。测试tests/test_facebook_page_generator_skill.py。 -
X 页面生成技能: 内置公共技能
skills/public/x-page-generator只在用户明确要求 X 平台、Twitter/推特或 X 风格社交时间线网页时触发;专用路由会强制读取技能并绕开通用 HTML 生成器。单账号单推文场景强制只写小型中文规格 JSON,再调用一次scripts/fast_single_post.py --spec;脚本可将总回复数与实际展示数分离,按replyTopics自动补齐最多 100 条中文热评,也接受用户指定的逐条回复,并在一次进程内完成六模块 JSON、内部校验和 HTML 渲染。用户已提供素材或明确不搜索时禁止联网、图像生成、读模板/脚本源码和临时构建脚本;推文与回复正文必须为简体中文。多帖子场景仍通过scripts/render_x_page.py的init → validate → render流程注入固定三栏模板与内联运行时。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、实时校验和「应用并渲染」,保留深浅主题、发布、回复、点赞、转发、收藏、搜索、关注与响应式移动布局;缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_x_page_generator_skill.py。 -
TikTok 页面生成技能: 内置公共技能
skills/public/tiktok-page-generator只在用户明确要求 TikTok、抖音国际版或 TikTok 风格短视频信息流网页时触发。默认只写小型六模块补丁规格 JSON,并调用一次scripts/fast_page.py --spec在内部合并、校验和渲染;只有结构改造才回退scripts/render_tiktok_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、实时校验和「应用并渲染」,保留滚动吸附、播放/暂停、双击点赞、静音、收藏、关注、评论、分享、搜索、方向键切换和移动端全屏布局;缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_tiktok_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
YouTube 页面生成技能: 内置公共技能
skills/public/youtube-page-generator只在用户明确要求 YouTube 或 YouTube 风格视频门户、视频首页、频道页时触发。默认只写小型七模块补丁规格 JSON,并调用一次scripts/fast_page.py --spec在内部合并、校验和渲染;只有结构改造才回退scripts/render_youtube_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、分类筛选、搜索、响应式视频网格、Shorts 横向列表、观看弹窗、点赞、收藏、订阅和评论;缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_youtube_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
Threads 页面生成技能: 内置公共技能
skills/public/threads-page-generator只在用户明确要求 Threads 或 Threads 风格串文首页、文本社交信息流时触发。默认只写accountName、postText、topic、displayReplyCount等高层规格 JSON,并调用一次专用scripts/fast_page.py --spec;脚本自动映射六模块、补齐作者/统计/中文热评、保留示例视觉并一次完成校验与渲染,禁止模型猜测$set深层路径或读取默认数据。只有结构改造才回退scripts/render_threads_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、深浅主题、发帖、搜索、点赞、回复、转发、分享、关注、动态面板和移动端导航;缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_threads_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
CNN 页面生成技能: 内置公共技能
skills/public/cnn-page-generator只在用户明确要求 CNN 或 CNN 风格全球新闻门户、新闻首页时触发。默认只写以topic为核心、可选headline/breaking/栏目标题数组的高层规格 JSON,并调用一次专用scripts/fast_page.py --spec;脚本自动生成完整中文十模块与明确标注的非实时示意数据,禁止模型猜测$set深层路径、读取生成后的合并数据或探路后二次渲染。只有结构改造才回退scripts/render_cnn_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、突发条、搜索、直播弹窗、字幕切换、收藏、分享、移动菜单、市场快照和订阅表单;缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_cnn_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
BBC 页面生成技能: 内置公共技能
skills/public/bbc-page-generator只在用户明确要求 BBC 或 BBC 风格国际公共新闻门户、新闻首页时触发。默认只写小型十模块补丁规格 JSON,并调用一次scripts/fast_page.py --spec在内部合并、校验和渲染;只有结构改造才回退scripts/render_bbc_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、搜索、快讯、直播弹窗、字幕切换、音频进度、移动抽屉、新闻排行、地区天气和订阅反馈;缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_bbc_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
新华社页面生成技能: 内置公共技能
skills/public/xinhua-page-generator只在用户明确要求新华社、新华网或新华社风格中文通讯社门户、新闻首页时触发。默认只写小型十模块中文补丁规格 JSON,并调用一次scripts/fast_page.py --spec在内部合并、校验和渲染;只有结构改造才回退scripts/render_xinhua_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、多语种顶栏、报道专集、焦点图、要闻分类、名专栏、专题、记者工作室、地方切换、行业频道、媒体矩阵、直播字幕和订阅反馈;缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_xinhua_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
白宫官网页面生成技能: 内置公共技能
skills/public/white-house-page-generator只在用户明确要求白宫官网、White House 风格政府首页或美国行政机构门户时触发。技能把材料整理为site/navigation/hero/notice/updates/priorities/administration/calendar/participation/footer十模块 JSON;键名、id、锚点和视觉枚举保留英文技术值,所有读者可见内容强制使用简体中文,英文来源先翻译,site.language固定为zh-CN。默认只写小型中文补丁规格 JSON,并调用一次scripts/fast_page.py --spec在内部合并、校验和渲染;只有结构改造才回退scripts/render_white_house_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、透明转吸顶导航、建筑主视觉、公告、官方动态、优先事项、行政团队、公共日历、全屏菜单搜索、直播字幕和订阅反馈;默认内容明确为概念模板,不冒充政府官网,缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_white_house_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
马尼拉时报页面生成技能: 内置公共技能
skills/public/manila-times-page-generator只在用户明确要求马尼拉时报、The Manila Times 或其风格的菲律宾大报门户时触发。技能把材料整理为site/navigation/ticker/lead/pulse/regions/opinion/sections/media/footer十模块 JSON;键名、id、锚点、视觉和涨跌枚举保留英文技术值,所有读者可见内容强制使用简体中文,英文或菲律宾语来源先翻译,site.language固定为zh-CN。默认只写小型中文补丁规格 JSON,并调用一次scripts/fast_page.py --spec在内部合并、校验和渲染;只有结构改造才回退scripts/render_manila_times_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、菲律宾旗色带、衬线报头、三栏头条、地区切换、评论、商业体育、视频弹窗、数字报翻页和订阅反馈;默认内容明确为概念模板,不冒充官方网站,缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_manila_times_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
GMA News 页面生成技能: 内置公共技能
skills/public/gma-news-page-generator只在用户明确要求 GMA News、GMA 新闻或菲律宾广播新闻门户风格页面时触发。技能把材料整理为site/navigation/justIn/lead/news/services/broadcast/channels/media/footer十模块 JSON;键名、id、锚点和视觉枚举保留英文技术值,品牌缩写GMA可保留,其余读者可见内容强制使用简体中文,英文或菲律宾语来源先翻译,site.language固定为zh-CN。默认只写小型中文补丁规格 JSON,并调用一次scripts/fast_page.py --spec在内部合并、校验和渲染;只有结构改造才回退scripts/render_gma_news_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、即时新闻条、天气切换、吸顶导航、头条视频、最新/热门/地方切换、公共服务、电视直播字幕、频道卡片、播客、彩票开奖和订阅反馈;默认内容明确为概念模板,不冒充官方网站,缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_gma_news_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
雅虎日本页面生成技能: 内置公共技能
skills/public/yahoo-japan-page-generator只在用户明确要求雅虎日本、Yahoo! JAPAN、Yahoo Japan 或日本综合门户风格页面时触发。技能把材料整理为site/search/services/news/account/weather/utilities/shopping/pulse/footer十模块 JSON;键名、id、锚点和视觉枚举保留英文技术值,品牌Yahoo! JAPAN可保留,其余读者可见内容强制使用简体中文,日文或英文来源先翻译,site.language固定为zh-CN。默认只写小型中文补丁规格 JSON,并调用一次scripts/fast_page.py --spec在内部合并、校验和渲染;只有结构改造才回退scripts/render_yahoo_japan_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、多类型搜索、服务展开、分类新闻、推荐轮换、演示登录、城市天气、路线查询、市场切换、商品收藏、体育比分、实时热榜、移动抽屉和返回顶部;默认内容明确为概念模板,不冒充官方网站,缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_yahoo_japan_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
读卖新闻页面生成技能: 内置公共技能
skills/public/yomiuri-news-page-generator只在用户明确要求读卖新闻、读买新闻、Yomiuri、Yomiuri Shimbun 或日本全国大报风格页面时触发。技能把材料整理为site/navigation/breaking/lead/edition/features/news/opinion/media/footer十模块 JSON;键名、id、锚点和视觉枚举保留英文技术值,品牌罗马字YOMIURI ONLINE可保留,其余读者可见内容强制使用简体中文,日文或英文来源先翻译,site.language固定为zh-CN。默认只写小型中文补丁规格 JSON,并调用一次scripts/fast_page.py --spec在内部合并、校验和渲染;只有结构改造才回退scripts/render_yomiuri_news_page.py的init → validate → render。最终单文件 HTML 自带右上角设置按钮、按模块 JSON 编辑弹窗、字号切换、速报展开、三栏头条、文章收藏、吸顶导航、全屏菜单、搜索、演示登录、数字报翻页、专题与分类切换、社论、视频字幕和邮件订阅;默认内容明确为概念模板,不冒充官方网站,缓存键包含初始 JSON 指纹并兼容禁止 localStorage 的沙箱。测试tests/test_yomiuri_news_page_generator_skill.py与tests/test_fast_page_generator_specs.py。 -
舆情分析智能体(前端虚拟智能体): 智能体管理页新增「舆情分析」类型 tab,固定一张虚拟智能体卡片(名称/简介可编辑,存 localStorage)。对话页
pages/SentimentAgentChatPage.tsx(路由agents/sentiment-analysis/chats/:thread_id)复用普通问答布局 +MessageList,输入框只有发送按钮;问答经 GatewayPOST /api/sentiment-agent/stream转发外部 AG-UI 网关(不经过 LangGraph),会话仅存 sessionStorage。接口地址 + Basic Auth 在runtime-config.js配置(VITE_SENTIMENT_AGENT_API_URL/_AUTH_USERNAME/_AUTH_PASSWORD)并随请求传给后台;后台按传入地址转发、关闭 TLS 校验,失败时把完整错误回传。核心模块src/core/sentiment-agent/。 -
简洁模式用户引导 (Compact-mode onboarding tour): 简洁模式(外观设置的「全量/简洁」= compact,无全局导航栏 + 页面侧边栏)新增 TDesign React
Guide组件驱动的分步引导。组件components/workspace/compact-mode-guide.tsx(挂在ChatPage):9 步 —— 欢迎弹窗(dialog 模式)→ 左侧功能导航 → 聊天记录 → 设置和更多 → 换肤 → 界面模式(切回全量)→ 常用提示词栏 → 提示词管理弹窗 → 输入框;高亮目标由各组件上的data-compact-guide标记定位(workspace-nav-chat-list/recent-chat-list/workspace-nav-menu/appearance-controls/custom-prompt-menu.tsx的CustomPromptBar(提示词栏,仅新对话落地态渲染)与其ManagePromptsDialog(prompt-dialog锚点)/ ChatPage 输入框容器),启动时按目标是否存在过滤步骤(如新用户无聊天记录、侧栏收起时自动剔除该步 —— Guide 对缺失元素会抛错,不能盲传)。提示词管理弹窗步骤是动态目标:弹窗平时不渲染,引导切到该步前先广播PROMPT_DIALOG_GUIDE_EVENT(custom-prompt-menu.tsx导出)让CustomPromptBar打开ManagePromptsDialog并接管(owner: true期间 ESC/点遮罩不关闭,防止高亮目标消失导致 Guide 报错),轮询锚点真正挂载后才切步;离开该步 / 跳过 / 完成 / 中途卸载时以owner: false收回弹窗,4 秒打不开则跳过该步防卡死。TDesign Guide × Radix 模态互操作:Radix 模态弹窗打开时会把body置pointer-events:none(滚动锁定),Guide 直接挂在 body 下的遮罩/高亮/弹层会一并继承失活(引导按钮点不动),styles/tdesign-theme.css末尾对.t-guide__overlay/__highlight/__popup/__wrapper显式pointer-events:auto兜底。触发:① 自动 —— 简洁模式新对话落地页自动演示,直到用户显式点「完成」或「跳过」才写 localStoragedeerflow.compact-guide-finished记忆(浏览器级);中途刷新/离开不记忆,下次进入还会再演示。② 手动 —— 「设置和更多」下拉新增「界面使用引导」(该下拉仅存在于简洁模式工作区侧栏 footer):已在问答页时广播compact-guide:start窗口事件直接启动,否则写 sessionStorage 请求并导航/chats/new由挂载的引导消费(消费在定时器真正启动时才执行,规避 StrictMode effect 双跑吞请求)。eligible排除 embed=1 / hideChrome=1 / chats_iframe / FORCE_COLLAPSED_SIDEBAR(无界)等同样无全局 chrome 的特殊环境;启动延迟 800ms 等侧栏宽度过渡稳定后再测量高亮框。 -
智能体使用引导 (Agent onboarding tour, 三段式): 智能体「浏览管理 → 创建与配置 → 发起问答」全流程分步引导,组件
components/workspace/agents/agent-guide.tsx(同一 TDesignGuide封装模式,与简洁模式引导共享记忆语义:仅显式点「完成/跳过」才写 localStoragedeerflow.agent-guide.<stage>.finished,中途刷新/离开不记忆)。自动演示仅 create / chat 两段(进智能体列表页不弹引导,点「创建智能体」进入新建页时才自动演示;list 段的AgentGuide传autoStart={false},只能由头部「使用引导」按钮手动触发)。三段各自挂载、各自记忆:①list(agent-gallery.tsx,/page/workspace/agents)——分区 Tabs、搜索排序、智能体卡片操作、右上「创建智能体」按钮;完成/跳过就地结束(不跳转创建页,与 create 段无联动),最后一步仅提示进入创建页后会有配置引导;②create(NewAgentPage.tsx,/page/workspace/agents/new)——左侧对话式创建输入区、基本信息、运行模型+入库模板名、知识空间、SOUL.md、技能穿梭框、保存按钮(9 步);③chat(AgentChatPage.tsx,agents/:id/chats)——左栏切换与历史、常用问题 chips、输入框(落地态/会话态两个挂点同一chat-input标记)、右侧详情面板;eligible限定普通智能体对话页(showAgentList && !isEmbedded && !taskCtxOn,即排除通用问答原地嵌入与任务深链工作区),三段共用isAgentGuideEnvironment()排除 embed/hideChrome/无界环境。高亮锚点data-agent-guide:列表页tabs/search/agent-card(卡片+列表两种布局的根元素)/new-agent-btn,创建页create-chat-input/create-basic/create-model/create-knowledge/create-soul/create-skills/create-save-btn,对话页chat-sidebar/chat-questions/chat-input/chat-detail;同样启动时按目标存在性过滤步骤(新用户无卡片/无预设问题时自动剔除)。手动重放:三个页面头部各有「使用引导」按钮(列表页创建按钮左侧、创建页 header 右侧、对话页 header),广播agent-guide:start:<stage>窗口事件强制重放(忽略已看过记忆)。 -
敏感词管理 (Sensitive Word Management): configures the 展示层脱敏映射 (原始词 → 替换词). Gated on the username
lqq(not the admin role) —getAccountDisplayName().toLowerCase() === "lqq"(username = email@prefix, elseuser_id, same as the profile page); the dropdown entry, the route guard, and the page-level guard all use this check.src/lib/desensitize.tsships a frozen seed mapSEED_DESENSITIZE_MAP(the deployment seed) plus a live mutableDESENSITIZE_MAP(initialized from the seed sodesensitize()works before any fetch).DesensitizeWordsLoader(mounted inApp.tsxAppProviders) fetches/api/sensitive-wordson startup and, if non-empty, callsreplaceDesensitizeMap(...)to make the backend config the source of truth (empty/failed → keep seed). PageSensitiveWordsPage.tsx(route/page/strategy/admin/sensitive-words, opened from the 「敏感词管理」 item in the bottom-left settings dropdown ofcomponents/page-sidebar.tsx, present in both full + collapsed sidebar modes) lists/adds/edits/启停/deletes words and saves the whole set atomically (replace-all); a 「导入种子词」 button merges any missingSEED_DESENSITIZE_MAPentries into the editor. Client + hooks instrategy-components/api/sensitive-words.ts(wordsToMapfolds enabled rows into the runtime map). Backend:/api/sensitive-words(global/shared; open read, write allowed for admin or thelqqaccount), mirroring 菜单管理's store shape. 侧边栏 + 导航栏 menu labels are desensitized at the data source:useSidebarMenuItems/useActiveThirdLevel(core/page-layout/use-sidebar-menu.ts) map every node'slabelthrough a desensitizer built directly from the/api/sensitive-wordsquery (makeDesensitizer(wordsToMap(words)), falling back to the global seeddesensitizewhile empty) — this sidesteps thereplaceDesensitizeMapeffect-timing race. Onlylabelis rewritten;path/id/iconare untouched so routing/highlight are unaffected. All nav surfaces (page-sidebar.tsx,page-sidebar-dark.tsx,page-sidebar-v1.tsx,strategy-components/Header.tsx,PageRoutes.tsx) consume this hook and are covered;MenuManagementPageuses the raw tree so admins still edit real labels. -
技能上下线 (Skill Availability, owner/admin): the global
Skill.enabledflag is the online/offline switch. Taking a skill offline persistsskills.<name>.enabled=falseinextensions_config.jsonbut does not delete its directory or remove it from existing agent/workflow configurations; the row remains visible and bringing it online restores those references. Administrators may toggle every skill from 「技能管理」; ordinary users may toggle only their own non-built-incustomskills from 「技能 → 技能列表 → 我的」. Offline skills must be absent from every model-facing skill catalogue (leadprompt,search_skills,skill_list,skill_view, subagents, AI writing/deep research/roundtable and Workflow run-start validation), from agent skill pickers, and from direct sandbox access:read_file/ls/glob/grep/bashreject offline, unknown, root-wide and category-wide/mnt/skillspaths. The backend mutation isPUT /api/skills/{name}, authorizes either an administrator or the persisted owner of the target custom skill, patches the raw document so MCP placeholders and unrelated extension fields survive, uses atomic replacement when supported plus a Docker single-file-bind fallback, and refreshes the skill prompt cache. Launchers keep the runtime extensions file in a writable persisted directory. Existing agent/strategy-agent references stay visible as 「已下线」 and may be removed but not newly selected. -
技能地址管理 (Skill Address Management, admin-only): 扫描运行中的技能(
skills/{public,custom}下所有.md/.py)里的 http(s) URL 与 IP/IP:端口,按唯一地址聚合全部出现位置(跨 .md/.py),支持批量替换(如把 UAT 地址一次性改成生产)。主体抽成可复用组件SkillAddressesPanel(pages/SkillAddressesPage.tsx导出,默认导出SkillAddressesPage仍保留旧路由/page/strategy/admin/skill-addresses作直链回退),作为「技能地址管理」标签页嵌入管理员「技能管理」页pages/AdminSkillsPage.tsx(该页加 Tabs:「技能列表」+「技能地址管理」;入口在「设置和更多」下拉的「技能管理」workspace-nav-menu.tsx→/page/workspace/admin/skills,不再单列于components/page-sidebar.tsx左下角设置下拉):打开自动扫描并带进度条(扫描逐技能 / 应用逐文件 X/N,应对 150+ 技能),URL/IP 类型过滤 + 搜索 + 「片段批量套用」(如所有uat.4.cn→prod.4.cn)、每地址「替换为」输入框(可展开看file:line片段)、内联预览、应用、多关键词检索(空格分隔全部命中),以及修改记录面板——每次应用生成一条记录(显示时间/改了哪些地址 from→to/文件数/处数),可针对单次记录回退(二次确认)。client + NDJSON 流式解析在strategy-components/api/skill-addresses.ts。后端/api/skill-addresses(仅管理员):所有文件 IO 走asyncio.to_thread不阻塞事件循环;GET /scan(+/scan/stream)、POST /preview、POST /apply(+/apply/stream)、GET /backups、POST /rollback/{id};子串安全替换(基于 token span,不会误伤子串)、expected_total乐观锁(409)、写前自动磁盘备份到{skills_root}/.address-edits/(同时写一份{id}.manifest.json记录本次替换明细,供修改记录展示)、应用后刷新技能系统提示缓存。纯引擎deerflow/skills/address_scan.py,routerapp/gateway/routers/skill_addresses.py,teststests/test_skill_addresses.py。范围仅运行中技能,不动deploy/minimal/skills/。 -
按钮管理 (Task Button Management, admin-only): configures the 可配置跳转按钮 rendered in the left task panel of the task deep-link workspaces (rwfx / 3qfx / xdfx, see
TaskFxWorkspace). Each button ={business, label, linkType, target, appendTaskId, idParam, idKind, enabled, sortOrder};linkTypeisurl(外链window.open, optional append of?{idParam}={id}—idParamis the editable query-param name, e.g.id/task_id),business(内部切换到另一业务, reusesresolveTaskDeeplinkTarget, always task id), orpurpose(弹出当前任务的「任务目的」表格 via the sharedTaskPurposeDialog, no target — configurable for any page incl. xdfx).host(宿主路由) asks the embedding parent to navigate:runTaskButtonsendswindow.parent.postMessage({ type:'HOST_NAVIGATE', path, queryParams:{ [idParam]: Number(taskId) } }, hostOrigin)—targetis the host route,appendTaskId/idParambecome numericqueryParams(task id, or action id whenidKind:"action"— same selector as url buttons, xdfx only), origin fromruntime-config.jsVITE_HOST_NAVIGATE_ORIGIN(else ancestorOrigins/referrer; never*).idKind(task/action) only matters for xdfx: its deep-link?taskId=is an 行动id and the 任务id is resolved at login (fetchTaskIdFromAction) —TaskContextnow also stashes the rawactionId, so a url or host button can append either (defaulttask). PageTaskButtonsPage.tsx(route/page/strategy/admin/task-buttons, admin-gated, entry「按钮管理」in the bottom-left settings dropdown ofcomponents/page-sidebar.tsx, both full + collapsed modes) has per-business tabs, edits name/type/target/启停/排序/增删, saves the whole set atomically (replace-all), plus 「导入默认」 to merge code-seed defaults. Seed/fallback (like 敏感词管理): only when the whole table is empty (fresh deploy, never saved) do the workspace + page fall back todefaultTaskButtons(cfg)— rwfx's url buttons (编辑/下一步/3q详情) built from the live/api/public/config/task-deeplinkplus cross-business switch buttons for all three businesses (each switches to the other two — rwfx→三情/行动, 3qfx→任务/行动, xdfx→任务/三情). Once saved, the store is the sole source of truth. The old hardcoded jump buttons were migrated out:TaskFxWorkspaceno longer hardcodes 切换到三情分析,TaskActionsPanelkeeps only the non-jump 清除记录, andTaskPurposePaneldropped its 3q详情 button (now configurable) and defaults the 目的树 table to collapsed (chevron toggle). Helperscore/auth/task-buttons.ts(TaskButton,defaultTaskButtons,resolveTaskButtons,buttonsForBusiness,runTaskButton), client+hooksstrategy-components/api/task-buttons.ts. Backend:/api/task-buttons(global/shared; open read, admin replace-all), storedeerflow.persistence.task_buttons(tabletask_buttons, auto-created bycreate_all), wiredapp.state.task_button_store. Tests:tests/test_task_buttons.py. -
HTML 模板管理 (HTML Template Management, admin-only): 定时任务「HTML 模板」库的可视化管理——列表/预览(iframe srcDoc sandbox)/在线编辑/上传 .html 覆盖/新建/删除/重置内置,改完即时生效(调度器每次执行直接读文件,无需重启)。模板从纯代码注册表升级为文件型存储:harness 纯引擎
deerflow/runtime/scheduler/template_store.py({base_dir}/html-templates/<id>/{template.html,meta.json},gitignored),app.py lifespan 把随包scheduler/templates/的全部内置模板(目前 27 个)seed 进存储——目录缺失才复制,绝不覆盖管理页/线上修改;template_fill.py的get_template/list_templates/read_template_text改为存储优先、随包注册表兜底,写操作后invalidate_cache()清引擎 lru_cache。convertible 自动探测:上传/编辑正文时检测单行const DATA={…};注入行(当前引擎唯一注入点,const MANIFEST=同理),带注入行即「可转换填充」(定时任务 JSON 转换管线零代码接入),否则「只展示」;数据存<script type="application/json">元素或let DATA/带空格写法的模板仍探测为只展示。内置模板可编辑/覆盖/重置回随包原版但不可删除(防引用它的任务挂掉),自建模板可删。页面pages/HtmlTemplatesPage.tsx(入口仅官方 DeerFlow 入口:OfficialDeerFlowEntry.tsx侧栏「模板管理」→#/official/workspace/task-analysis/html-templates,页面 idhtml-templates的路径判断必须先于task-analysis-detail的/:id正则,仅 admin 可见;主应用的简洁/全量模式不加侧栏入口,仅保留直链路由/page/strategy/admin/html-templates作回退,admin-gated),client+hooksstrategy-components/api/html-templates.ts。后端/api/html-templates(读开放、写仅 admin;app/gateway/routers/html_templates.py),旧端点GET /api/scheduled-tasks/html-templates不变(内部同样读存储),定时任务表单的下拉自动看到新建模板。测试tests/test_html_templates_admin.py。 -
单智能体任务深链聊天记录(2026-10-19):
3qfx/rwfx/xdfx的会话历史使用独立task_deeplink_threads索引表及GET /api/task-deeplink-threads?task_id=&go_path=,前端由useTaskDeeplinkThreads读取,不再筛选通用聊天列表。LangGraph thread/checkpoint 仍是消息权威数据;专用表仅按(task_id, go_path)定位 thread。3qfx/rwfx按任务 id,xdfx按 URL 原始行动 id;新 thread 写metadata.{taskRecordId,taskGoPath,thread_type:"task_deeplink"}并自动登记,通用最近对话默认隐藏。迁移20261019_01只建表、不迁移/回填旧数据。 -
上下文与对话轮数(管理员全局设置): 「设置 → 上下文管理」可开启
system_settings.json -> conversation_context.recent_turn_window_enabled(默认关闭),并调整max_conversation_rounds(单个普通对话的真实用户提问上限,默认 20,范围 1–50);统一走GET/PUT /api/system-settings/conversation-context,PUT 仅管理员。轮数上限由ChatPage读取,达到后禁止继续发送并引导新建对话;旧配置缺字段时仍按 20。近 3 轮模式开启后,RecentTurnsMiddleware只改变单次模型请求,保留含当前轮在内的最近 3 个真实用户回合及其 assistant/tool 链;checkpoint 与前端完整聊天记录不删。该模式优先于聊天框「上下文压缩」,且不会调用摘要模型;关闭后沿用原压缩行为。实现deerflow/agents/middlewares/recent_turns_middleware.py,回归tests/test_summarization_multiturn.py/tests/test_prompt_prefix.py。 -
输入框选项按窗口隔离(per-tab composer options): 输入框工具条的「写作 / 文件 / 参考文献 / 上下文压缩」开关与「模型 / 快速(模式) / 思考强度」选择器原本存
localStorage(deerflow.local-settings)且 store.ts 监听storage事件跨窗口同步——一个窗口切换、所有窗口生效。现这 7 个字段拆入sessionStorage(deerflow.composer-session):纯函数拆分/合并/解析在core/settings/composer-scope.ts,core/settings/local.ts的getLocalSettings/saveLocalSettings负责路由(全局文档读写时强制剥离这 7 个字段,历史残留值失效)。每个浏览器窗口独立——复制链接新开的窗口从默认值开始、互不影响;同一窗口刷新保留;useLocalSettingsAPI 与全部使用方(input-box / chat-box / message-list / hooks / IframeChatPage 等)零改动。「选择 Agent」的新对话预选deerflow.last-agent-id同样改 sessionStorage。其余 LocalSettings(字号 / 布局模式 / composer 宽度等外观偏好)保持浏览器全局并继续跨窗口同步;一次性迁移标志deerflow.composer-session.migrated把升级前旧值搬进升级后第一个打开的窗口。「经典检索/智能检索」与「知识空间」选择本来就是页面 state + 线程 metadata,天然按窗口隔离,未改动。测试pnpm test:composer-session(core/settings/composer-scope.test.ts)。 -
State: TanStack Query for server state, context providers (
SubtasksProvider,ArtifactsProvider,PromptInputProvider) scoped toWorkspaceRoutes -
LLM streaming:
@langchain/langgraph-sdkstreams messages from the backend via SSE -
UI: Radix UI primitives + TailwindCSS v4 + shadcn-style components in
src/components/ui/ -
Shims:
src/shims/providesnext/linkandnext/navigationpolyfills (original code was Next.js) -
Login URL params (
LoginPage.tsx, frontend-only — the backend never sees these): besides the auth params (ticket/userId/env/theme/yUserId/token/access_token/authToken/password), the page supports a task deep-link via?taskId=&goPath=. After login it calls the consumer APIGET {VITE_AIPI_CONSUMER_URL}/taskAnalyseSearch/cop-task-detail?taskId=(headerAuthorization: Bearer admin, auto-injected byutils/request.ts), stashes the fulldata(id/overview/taskDirection/taskName/taskContent/taskType; opening text mapstaskType1/2/3 to 打击/塑势/防御) intosessionStorage["login.copTaskDetail"], then routes bygoPath: base codes3qfx/rwfx/xdfx→ that business's configured single agent Q&A inside a 任务工作区 (resolved via/api/business-mapping;resolveTaskDeeplinkTargetcallspersistTaskContext({taskId, agentId, openingText})and navigates to the…/chatsindex, which opens the latest task conversation or 新建 — a 1:N task↔对话 workspace, NOTpendingAgentMessage); the-Asuffix (3qfx-A/…) → multi-agent roundtable withroundtablePrefill.businessCodeso the page auto-selects the configured 业务链条. xdfx's deep-link?taskId=is actually an 行动id, not a 任务id —resolveDeeplinkTaskId(goPath, rawId)(called inLoginPagebefore everything) detectsxdfx/xdfx-Aand resolves it viaGET {action_detail_url}/{actionId}→data.taskId(fetchTaskIdFromAction;action_test+mock.action_detailfor offline testing); the resolved 任务id then drivescop-task-detail, the purpose tree, and the 1:N binding (other businesses pass the id through unchanged). xdfx additionally partitions its chat records by 行动id (action_id), NOT by task_id:resolveTaskDeeplinkTargetsetspartitionByAction:true+ keeps the raw action_id asTaskContext.actionId, so new threads are taggedmetadata.{taskId,actionId}(taskId kept only to preserve the backend「任务深链对话全局共享」semantics, which keys offmetadata.taskId) and the conversation list filters byactionId(+agent_id) — different actions of the same task get separate lists. xdfx's first-question opening text also appends行动id(action_id)为…(seebuildOpeningText/taskThreadMetadata). goPath→businessCode:3qfx→3Q,rwfx→6BF,xdfx→7BF. The roundtable target also threads the URLtaskIdintoroundtablePrefill.taskId(resolveTaskDeeplinkTarget(goPath, detail, taskId)), which turns on two taskId-scoped behaviors: (1) separate, unpartitioned per-task storage — taskId 会商聊天记录 live in their own store/table (roundtable_task_drafts), keyed only bytask_id(one taskId = many records, shared across users, no user 分权 at all;created_byis audit-only), kept entirely out of the per-user personal store (roundtable_drafts, now strictly per-user) so the two never mix. Backend router/api/roundtable-task-drafts(GET /by-task/{id}+/by-task/{id}/latest,POST,GET/PUT/DELETE /{id}— all user-agnostic); frontend clientroundtable-planning/api/task-drafts.ts, anduseDraftPersistence/useRoundtableDraftsroute all CRUD to it when ataskIdis present (else the personaldrafts.ts). The matching background jobs are shared by task too:roundtable_jobs.task_idmarks a task job; reads/dedup/stream/resume/cancel pass?task_id=so any user opening the task sees the same running 研讨 (progress/flowchart), and the executor writes the final report back to the task-draft store. Existing rows are moved over byscripts/migrate_roundtable_task_drafts.py(migration20260618_01). The history dropdown filters to the task and 新建 keeps the binding; (2) Step-3 auto structured output — after theroundtable-summaryagent writes the Markdown report it automatically runs a second round emitting aflow-jsonflowchart (useStep3SummaryautoFlow+chainLevels), whose hierarchy follows the business chain's stage goals (任务 → 目的 → 行为体 → …; coordinatorPrompt-specified hierarchy wins) and whose nodes never carry 阶段编号/阶段名 (the prior bug). Both targets render embedded under the 任务管理 layout — nested routes undertask-management/<business-path>/{roundtable,agent/:agent_id/chats}inStrategyRoutes.tsx(<business-path>=plan-task-sentiment/plan-task/plan-task-fx/plan-task/plan-task-action, matching the COH中任务 menu paths insidebar-menu.ts) so the global sidebar keeps 任务管理 → COH中任务 → the matching business highlighted. All three single-agent businesses shareTaskFxWorkspace(task-components/TaskFxWorkspace.tsx): left = a「当前任务」card (readCopTaskDetail()任务名称 + 简介, desensitized, 留存 above 新建对话) + per-task 1:N conversation list (useTaskThreads(taskId, agentId, actionId?)— filtered by bothmetadata.taskIdANDmetadata.agent_idso the three businesses' conversations stay isolated even though they share one taskId; xdfx passes a thirdactionIdso it filters bymetadata.actionIdtoo, partitioning chat records per 行动 —useChatsBasederives it fromTaskContext.partitionByAction) + 新建对话; the…/chatsindex (TaskFxIndexResolver) opens the latest task thread or 新建. The right:thread_idhost differs by business: rwfx →TaskFxChat(AgentChatPage taskGate) = full workspace (non-lite): 目的树闸门 panel + 清除记录 (placed under 新建对话); xdfx → alsoTaskFxChat(same 目的树闸门 query+display, samepurpose_detail_url) but workspace passedlite; 3qfx →TaskBindChat(AgentChatPage taskBind,lite) = 1:N list only, no gate, and 新建对话 auto-sends the task opening text (taskAutoSendText; gate businesses send it via the gate's 开始问答 instead). rwfx auto-starts the first Q&A when the 任务目的 query returns empty (TaskPurposePanel autoStartWhenEmpty, threaded from the rwfxTaskFxChatonly). The 目的树闸门 (TaskPurposePanel) defaults collapsed and now keeps only 弹框查看 + 开始问答; the former jump buttons (编辑/下一步/3q详情/切换三情/弹框查看) all moved to the configurable bottom 快捷跳转 bar (TaskJumpBar, red 红底白字 buttons, right-aligned) driven by 按钮管理 — see the 按钮管理 bullet.TaskPurposeDialog(exported fromTaskPurposePanel) backs both the gate zoom and the bottom bar'spurpose-type button.chatBasePathkeeps the post-send URL rewrite + new-chat button in the task-management context. Backend configtask_deeplink(config.yaml, surfaced byGET /api/public/config/task-deeplink, mirrored incore/auth/task-deeplink-config.ts) addsaction_test/action_detail_url/detail_3q_url+mock.action_detail. Any failure (no consumer URL / fetch error / unmapped goPath / unresolvable action id / no configured agent) silently falls back to the default home. Helpers:core/auth/task-deeplink.ts,core/auth/task-purpose.ts,core/auth/task-deeplink-config.ts,core/threads/use-task-threads.ts. -
agentfx 任务深链(新系统 iframe 嵌入的智能体问答,3qfx 单智能体 lite 模式的独立复制): goPath=
agentfx(${loginBase}?password=…&taskId=…&goPath=agentfx&embed=1&hideJump=1&theme=dark-blue)。与 3qfx 的差异:①任务详情不走外部 consumer,改调本系统 GatewayGET {backend}/taskAnalyseSearch/cop-task-detail?taskId=(apiFetch带刚登录的 DeerFlow 态;任务数据来自 TaskCOP 任务表,与任务列表/报告导入同一份);②固定使用内置智能体agentfx-analyst(不走业务映射);③路由task-management/plan-task-agentfx/agent/:agent_id/chats,页面为独立复制的AgentFxWorkspace.tsx(AgentFxWorkspace/AgentFxIndexResolver/AgentFxChat—— 左「当前任务」卡 + 1:N 对话列表 + 新建对话,AgentChatPage taskBind自动发开场白;无 TaskJumpBar/清除记录/闸门/xdfx 行动卡)。模块core/auth/agentfx-deeplink.ts(isAgentfxGoPath/fetchAgentfxTaskDetail/resolveAgentfxDeeplinkTarget);任务详情整包仍存共享键login.copTaskDetail(按标签页隔离),AgentChatPage的 taskBind 自恢复对 agentfx 生效。LoginPage在既有深链分支前用isAgentfxGoPath分流。内置「任务研判报告助手」:后端 seederapp/gateway/routers/_agentfx_seed.py+_agentfx_seed_assets/(config.yaml + SOUL.md,目录存在不覆盖;app.py lifespan 在_sync_legacy_agents之前调用,自动进 agents 表 user_id IS NULL)——SOUL 规定工作流「搜集信息(web_search,首步不可跳过)→ 每次检索 JSON 原样落盘/mnt/user-data/workspace/hits/qN.json→ 一条命令task-report-build(groupall)技能函数转换成 14 类task-reports.json(模型禁止手写该 JSON;分页技能task-report-{enemy,our,env,judge}仅用于事后补跑单页;五者都是共享引擎task-report-lib/convert_lib.py的瘦 CLI,resolve_hit_files对虚拟绝对路径读空的情况自动回退到相对写法并在warnings里列出尝试过的路径,免得智能体自己写脚本排查)→write_file生成/mnt/user-data/outputs/report.md→ 聊天总结 → 引导入库」,硬性约束:categoryType 逐字用 14 个全称(禁缩写自造如「DQ关键事件」)、JSON 直接写汉字禁\uXXXX转义、每类必须有实质内容不许空数组/纯占位、文件一律用/mnt/user-data/...虚拟路径(防宿主路径泄漏进 tool args)。检索依赖config.yaml tools.web_search(DDG,enabled 已开;内网需换 configurable_search 时改回并填 endpoint)。任务工作区输入框在taskCtxOn时隐藏「写作/记忆/参考文献」(InputBoxhideContextToolsprop)与「知识空间」(不传 knowledgeSelector);消息流里 write_file 文件卡片只显示文件名(artifactDisplayName收敛宿主/虚拟路径)。入库两条路径:(a) 前端协助卡 ——AgentChatPage(taskBind 且非 taskGate)检测线程 artifacts 出现task-reports.json(按文件名后缀匹配,兼容本地沙箱记录的宿主路径)且流式结束时,向 displayThread 尾部注入additional_kwargs.task_report_import_approval虚拟 ai 消息(core/messages/utils.ts分组assistant:task-report-import+MessageList的taskReportImportSlot插槽渲染TaskReportImportCard),点「开始入库」前端校验(strategy-components/lib/agentfx-report-schema.ts,14 类 + contentJson 合法 JSON + \u 转义归一化还原为汉字,与技能脚本同规则)后importTaskReport(strategy-components/api/task-reports.ts)POST 导入接口整体替换;任务已有详情不抑制卡片(入库即整体替换,卡片文案说明覆盖语义),导入成功按 thread 记 sessionStorage(刷新不重复询问)、「暂不入库」为会话内记忆;(b) 入库技能task-report-import(seeder 一并复制到skills/public/,public 类技能 extensions_config 缺省即启用)—— 用户聊天里说「入库」时智能体按 SKILL.md 用 bash 跑scripts/task_report_import.py(纯标准库:同样的校验规则 + 转义归一化;默认关闭 SSL 证书校验ssl._create_unverified_context+ 忽略代理,适配内网自签证书;鉴权--token/envAGENTFX_IMPORT_TOKEN→DEERFLOW_AUTH_TOKEN→TOOLS_TOKEN优先,缺省经公共端点POST /api/parallel-agents/auth/token以agentfx-import用户现签 token——内网既有鉴权旁路;gateway 地址 envAGENTFX_GATEWAY_URL或http://127.0.0.1:{DEER_FLOW_GATEWAY_PORT});--strict可要求 14 类齐全,默认允许部分导入并在 stdout JSON 的missing_categories里列缺。入库成功后任务 TaskCOP 状态自动置 25(已完成分析)。入库成功通知宿主(iframe 嵌入场景):两条路径成功后都会window.top.postMessage({source: "magent-web", type: "task-report:saved", taskId: String(taskId)}, "*")(notifyTaskReportSaved,agentfx-report-schema.ts)——卡片路径在TaskReportImportCard导入成功回调里直发;技能路径由AgentChatPage从消息流检测task_report_import.py的成功 stdout("success": true+task_id)代发——仅在回合结束后检测(!thread.isLoading才扫描;流式过程中 messages 每增量全量扫描会卡死页面),只看线程内最后一次成功入库(新一次入库触发新通知;刷新旧对话按 thread 记 sessionStorage 已通知的消息 id 不重复发)。 -
iframe 嵌入模式 (外部系统用 iframe 套用深链问答/会商) — 三个按标签页开关,全部由深链 URL 参数开启、存进同一份
sessionStorage会话(embed.session,键名常量在core/embed/embed-session.ts),跨登录跳转/刷新/页内导航存活,绝不跨标签污染同浏览器另开的正常系统页。核心隔离手法:用sessionStorage(按标签页/浏览上下文隔离,iframe 即便同源也有独立一份)而非localStorage(跨标签共享)。开关:(1)?embed=1(可带&theme=) — 去全局 chrome(PageLayout的pageSidebarAllowed && !isEmbedActive()→ 隐藏 Header + PageSidebar)+ 按外部主题色强制明/暗:走 next-themes 的forcedTheme(App.tsxAppProviders调resolveForcedEmbedTheme,不写 localStoragestrategy-theme),离散映射dark-blue/dark→深、其余→浅;嵌入侧绝不调setTheme/persistSidebarEnabled(那俩落 localStorage 跨标签污染,LoginPage据readEmbedParam跳过)。(2)?hideJump=1— 隐藏深链底部「快捷跳转」栏TaskJumpBar的两部分(底部「下一步」等按钮 + 右侧垂直居中悬浮菜单),单智能体问答 + 会商共用该栏,isJumpBarHidden()为真时整条return null。(3) 登录用户隔离 — 嵌入态下鉴权/身份键(deerflow.authtoken、userInfo、login.*、tools_token、maxkey/userDetail token)全部经core/auth/scoped-storage.ts的authStorage()(嵌入→sessionStorage / 正常→localStorage)读写,使 iframe 里登录的用户与正常登录用户完全隔离、互不覆盖;所有 token 读取都过getStoredAuth()/getAuthorizationHeaderValue()(API 客户端core/api/*全走这俩),改中心函数即让全体调用方自动隔离;跨标签账号变更监听AuthSyncWatcher(core/auth/sync.tsx)在嵌入态禁用。嵌入会话在main.tsx渲染前syncEmbedSessionFromCurrentUrl()同步启用(早于任何鉴权写入,避免首帧 effect 顺序漏写 localStorage);AppProviders的 effect 再按 SPA 导航同步。embed=0/hideJump=0可显式关闭。正常(非嵌入)标签页所有isXxx()恒 false,行为零变化。第三个开关?isolate=1(独立登录会话)把同一套登录用户隔离单独提供给普通(带 chrome)标签页——只切换鉴权存储、不去 chrome / 不强制主题 / 不动跳转栏:authStorage()的条件从「仅嵌入」放宽为isEmbedActive() || isIsolatedSession(),AuthSyncWatcher的跨标签监听对嵌入态或独立态都即时忽略(handler 内实时判,运行时开关也即刻生效)。除 URL 参数外,设置 → 外观 →「登录会话」 有一个 Switch「独立登录会话(本标签页)」运行时切换(setIsolatedSession,appearance-settings-page.tsx):开启时先migrateAuthToSession()(scoped-storage.ts,把 localStorage 的受保护键deerflow.auth/userInfo/userId/tools_token+login.*前缀快照搬进 sessionStorage)再翻标志,故无感不登出;此后本标签页登录态独立——别处登录/换号影响不到它,它换号也影响不到别人,但关掉该标签即结束(需重登)。开启给 toast 提示。 -
Web App 产物模式(多页面站点 / 流程图 / 地图,默认关闭、全链路可降级): 让智能体产出完整站点而非单个 xxx.html。沙箱技能
skills/public/web-app用模板(backend/templates/web-app-template,经config.yaml sandbox.mounts只读挂载到/mnt/web-app-template,目录缺失自动跳过)脚手架到outputs/<site>/(site.json 清单 +src/pages/*.tsx文件名即路由 +public/数据),再跑scripts/build_site.py build用预置 node_modules(symlink/junction 链接,离线永不 npm install)执行 vite build 出dist/;内置组件FlowCanvas(@xyflow/react)/GeoMap(MapLibre,默认无底图 GeoJSON 模式,site.json 可配 tilesUrl/PMTiles)/ChartPanel(ECharts)/MermaidBlock。平台侧app/gateway/routers/web_app_sites.py+ 纯引擎deerflow/webapp/(发现/子进程构建/HMAC 签名):GET /api/web-app/capability(enabled/模板/依赖/node 四项探测)、GET|POST /api/threads/{tid}/sites[...]{build,build-status,preview-url},静态托管走GET /api/public/site-preview/{token}/{path}(/api/public/免鉴权命名空间,唯一允许 HTML inline 的出口——签名令牌 + dist/ 根限定 + 按请求真实 origin 显式声明的 CSP(iframe sandbox 不透明 origin 下 'self' 失效)+ nosniff;artifacts 接口既有的强制 attachment 不变)。前端:独立风格预览工作台pages/site-studio/SiteStudioPage.tsx(顶层路由#/sites/:threadId/:siteName,App.tsx注册,不进 PageLayout/WorkspaceLayout;自带 site-studio.css 深色设计体系,零全局样式污染;iframesandbox="allow-scripts allow-forms allow-popups allow-modals"无 allow-same-origin)+ 产物面板artifact-file-detail.tsx里 site.json 的「站点预览」按钮(capability 可用才渲染,唯一既有文件改动)。降级铁律:默认完全关闭(sandbox.mounts的模板挂载条目与文件末尾的web_app:段在 config.yaml 里均为注释状态);开启需同时放开两处(挂载让沙箱技能可探测/mnt/web-app-template,web_app.enabled: true开平台 API——只开一处会得到"生成了看不了"或"看不了也不生成"的中间态)。build_site.py check在宿主侧会读 config.yaml 的web_app.enabled(envDEERFLOW_WEBAPP_ENABLED/DEERFLOW_WEBAPP_CONFIG可覆盖;沙箱内读不到配置时"挂载即视为开")。任何一环(配置/模板/node_modules/node)缺失 → capability 不可用 + 技能第 0 步探测失败即回退单页 HTML 模式 + 入口按钮不渲染 + 构建失败只收进任务状态,现有功能零影响。配置web_app:(enabled/template_dir/build_timeout_seconds/preview_token_ttl_seconds/node_path);测试tests/test_web_app_sites.py;开发机make webapp-template预置依赖,离线发版由 deerflow-offline-deployment 仓库在 CI 预置 node_modules 进镜像。
Configuration
Backend config lives in offline-backend-20260512/backend/config.yaml. Values starting with $ resolve as environment variables (e.g., $OPENAI_API_KEY). MCP servers and skills are configured in extensions_config.json in the same directory.
Detailed Backend Reference
The backend has its own comprehensive documentation at:
offline-backend-20260512/backend/AGENTS.md— architecture, middleware chain, all API routes, config schema, sandbox/memory/subagent systemsoffline-backend-20260512/backend/README.md— setup guide, technology stack