# 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) ```bash ./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/`) ```bash 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: ```bash PYTHONPATH=. uv run pytest tests/test_.py -v ``` ### Frontend Only (from `frontend-web/`) ```bash 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 from `app.*`.** - **`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 in `src/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`),client `core/stream-truncation-log/api.ts`;后端 API `GET /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`,client `strategy-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 「问答管理 → 课题研究(对话版)」. Page `open-canvas/pages/AIWritingChatPage.tsx` reuses the engine (`AIWritingContext` + `useStream` + `AIWritingDraftPanel`); **idle keeps the classic setup layout** (`WritingSetupChat` + collapsible form) and switches to the chat workbench once writing starts; left panel `components/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. Tests `tests/test_ai_writing_intent.py`; plan doc `frontend-web/docs/ai-writing-对话驱动写作台-实现方案.md`. **素材检索技能池限定白名单**:`researcher_node` 通用检索的候选池 = **ai-writing-researcher 智能体 config.yaml `skills` 白名单里的启用技能**(管理员在智能体管理页增删,内置 `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_open` or `iframe` mini-apps with a route-param builder: login params resolved from `localStorage.userInfo`, theme params literal) and 应用列表 (card grid of `window_open` apps, `window.open` launch). `iframe` apps are mounted under a chosen parent menu (XK/PG/TY/综合管理) and **dynamically injected into the sidebar** by `core/page-layout/use-sidebar-menu.ts` (`useSidebarMenuItems`), opening in the `LightAppIframeView` host. Client + React Query hooks in `strategy-components/api/light-apps.ts`; URL builder/constants in `strategy-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 personal `label`/`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 in `core/page-layout/menu-overrides.ts` and `compact-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-overrides` uses `menu_overrides` (default) + `user_menu_override_configs` (per-user snapshot); `/api/compact-menu-overrides` uses `compact_menu_overrides` (type `A` / `B` defaults) + `user_compact_menu_override_configs` (per-user/type snapshots). Each website selects its compact type via `runtime-config.js` `VITE_COMPACT_MENU_TYPE`; `CompactToolbox` renders `toolbox` items. - **舆情分析智能体(前端虚拟智能体)**: 智能体管理页新增「舆情分析」类型 tab,固定一张虚拟智能体卡片(名称/简介可编辑,存 localStorage)。对话页 `pages/SentimentAgentChatPage.tsx`(路由 `agents/sentiment-analysis/chats/:thread_id`)复用普通问答布局 + `MessageList`,**输入框只有发送按钮**;问答经 Gateway `POST /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` 兜底。触发:① 自动 —— 简洁模式新对话落地页自动演示,直到用户显式点「完成」或「跳过」才写 localStorage `deerflow.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`(同一 TDesign `Guide` 封装模式,与简洁模式引导共享记忆语义:**仅显式点「完成/跳过」才写 localStorage** `deerflow.agent-guide..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:` 窗口事件强制重放(忽略已看过记忆)。 - **敏感词管理 (Sensitive Word Management)**: configures the **展示层脱敏映射** (原始词 → 替换词). **Gated on the username `lqq`** (not the admin role) — `getAccountDisplayName().toLowerCase() === "lqq"` (username = email `@` prefix, else `user_id`, same as the profile page); the dropdown entry, the route guard, and the page-level guard all use this check. `src/lib/desensitize.ts` ships a frozen seed map `SEED_DESENSITIZE_MAP` (the deployment seed) plus a live mutable `DESENSITIZE_MAP` (initialized from the seed so `desensitize()` works before any fetch). `DesensitizeWordsLoader` (mounted in `App.tsx` `AppProviders`) fetches `/api/sensitive-words` on startup and, if non-empty, calls `replaceDesensitizeMap(...)` to make the backend config the source of truth (empty/failed → keep seed). Page `SensitiveWordsPage.tsx` (route `/page/strategy/admin/sensitive-words`, opened from the 「敏感词管理」 item in the **bottom-left settings dropdown** of `components/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 missing `SEED_DESENSITIZE_MAP` entries into the editor. Client + hooks in `strategy-components/api/sensitive-words.ts` (`wordsToMap` folds enabled rows into the runtime map). Backend: `/api/sensitive-words` (global/shared; open read, write allowed for admin **or** the `lqq` account), mirroring 菜单管理's store shape. **侧边栏 + 导航栏 menu labels are desensitized** at the data source: `useSidebarMenuItems`/`useActiveThirdLevel` (`core/page-layout/use-sidebar-menu.ts`) map every node's `label` through a desensitizer built **directly from the `/api/sensitive-words` query** (`makeDesensitizer(wordsToMap(words))`, falling back to the global seed `desensitize` while empty) — this sidesteps the `replaceDesensitizeMap` effect-timing race. Only `label` is rewritten; `path`/`id`/`icon` are 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; `MenuManagementPage` uses the raw tree so admins still edit real labels. - **技能上下线 (Skill Availability, owner/admin)**: the global `Skill.enabled` flag is the online/offline switch. Taking a skill offline persists `skills..enabled=false` in `extensions_config.json` but 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-in `custom` skills from 「技能 → 技能列表 → 我的」. Offline skills must be absent from every model-facing skill catalogue (`lead` prompt, `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`/`bash` reject offline, unknown, root-wide and category-wide `/mnt/skills` paths. The backend mutation is `PUT /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`,router `app/gateway/routers/skill_addresses.py`,tests `tests/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}`; `linkType` is `url` (外链 `window.open`, optional append of `?{idParam}={id}` — **`idParam` is the editable query-param name**, e.g. `id`/`task_id`), `business` (内部切换到另一业务, reuses `resolveTaskDeeplinkTarget`, always task id), or `purpose` (弹出当前任务的「任务目的」表格 via the shared `TaskPurposeDialog`, no target — configurable for any page incl. xdfx). **`host`(宿主路由)** asks the embedding parent to navigate: `runTaskButton` sends `window.parent.postMessage({ type:'HOST_NAVIGATE', path, queryParams:{ [idParam]: Number(taskId) } }, hostOrigin)` — `target` is the host route, `appendTaskId`/`idParam` become numeric `queryParams` (task id, or action id when `idKind:"action"` — same selector as url buttons, xdfx only), origin from `runtime-config.js` `VITE_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`) — `TaskContext` now also stashes the raw `actionId`, so a url **or host** button can append either (default `task`). Page `TaskButtonsPage.tsx` (route `/page/strategy/admin/task-buttons`, **admin-gated**, entry「按钮管理」in the bottom-left settings dropdown of `components/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 to `defaultTaskButtons(cfg)` — rwfx's url buttons (编辑/下一步/3q详情) built from the **live** `/api/public/config/task-deeplink` plus 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: `TaskFxWorkspace` no longer hardcodes 切换到三情分析, `TaskActionsPanel` keeps only the non-jump 清除记录, and `TaskPurposePanel` dropped its 3q详情 button (now configurable) **and defaults the 目的树 table to collapsed** (chevron toggle). Helpers `core/auth/task-buttons.ts` (`TaskButton`, `defaultTaskButtons`, `resolveTaskButtons`, `buttonsForBusiness`, `runTaskButton`), client+hooks `strategy-components/api/task-buttons.ts`. Backend: `/api/task-buttons` (global/shared; open read, admin replace-all), store `deerflow.persistence.task_buttons` (table `task_buttons`, auto-created by `create_all`), wired `app.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//{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 转换管线零代码接入),否则「只展示」;数据存 `