Commit Graph

2079 Commits

Author SHA1 Message Date
1445043649 85a14f4c05 docs(impl): PR1 implementation note + acceptance checklist
记录 Stage 0 PR1 的实施落点、关键 LOCK 决策、fixture 用法示例、跟进项与
10 commit 列表。验收:3085 passed + 23 skipped + 0 failed(无 regression),
PG smoke 在 docker daemon 起来后实跑(CI workflow 已配)。

Stage 0 PR1 T1.10. PR1 完成。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:58:21 +08:00
1445043649 a33b46b4af ci(postgres): add Postgres workflow running @pytest.mark.postgres tests
新增 .github/workflows/backend-postgres-tests.yml,独立于现有
backend-unit-tests workflow:
  - GitHub runner (ubuntu-latest) 自带 docker daemon
  - testcontainers 在 runner 上起 postgres:16-alpine 容器
  - uv sync --extra postgres-test 拉 testcontainers + asyncpg + psycopg
  - pytest -m postgres 仅跑 PG 标签的 4 个 smoke test (+ 后续 PR 增量)

为什么独立 workflow 而不是合到 backend-unit-tests:
  - 现有 fast 测试不受 PG container 启动延迟影响
  - 可独立 fail-soft(早期 Stage 0 rollout 期间需要时加 continue-on-error)
  - fork 用户不强制承担 docker infra 成本

Stage 0 PR1 T1.9.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:55:53 +08:00
1445043649 eae0190184 feat(wizard): add Postgres backend question to setup wizard
setup_wizard.py 在 Step 3 (Execution) 之后新增一个 Database 问答:
"Use Postgres? (y = postgres, n = sqlite)",默认 y(Stage 0+ 推荐 PG)。
选 y 时引导填 DATABASE_URL(可留空稍后写 .env);DATABASE_URL 进 .env,
config.yaml 写入 database.backend=postgres + postgres_url=\$DATABASE_URL。

writer.py build_minimal_config 加 database_backend 参数,postgres 时
覆盖 base_config 的 database 段;默认 sqlite 时沿用 base_config 行为
(继承 config.example.yaml 的 sqlite_dir 等)。

minimal pattern:不新建 wizard/steps/database.py,inline 在 main 里加 1
问答 + writer 加 1 参数。后续如果需要更复杂数据库选项再升为完整 step 模块。

Stage 0 PR1 T1.8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:55:19 +08:00
1445043649 e6f5ba53bd feat(doctor): add Database section probing configured backend
scripts/doctor.py 新增 check_database():

  - sqlite → OK + 显示 sqlite_dir
  - memory → WARN(数据非持久化)
  - postgres → 解析 database.postgres_url 中的 \$DATABASE_URL,asyncpg
    实际连接 + SELECT version() → OK with server version;连接失败时
    FAIL 给出可执行 fix(启 docker compose postgres / 检查 DATABASE_URL)
  - 未知 backend → WARN

Database section 插在 LLM Provider 与 Sandbox 之间。SQLite 部署不会触发
PG 探测(不破坏现有 dev 体验)。

Stage 0 PR1 T1.7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:52:26 +08:00
1445043649 8f480cd76f test(persistence): postgres smoke tests for init_engine + ThreadMetaRepo
新建 backend/tests/test_postgres_smoke.py,4 个 test 用 postgres_url fixture:

  - test_postgres_url_creates_isolated_database (T1.4)
    asyncpg 直连验证 fixture 落到 test_<hex> DB
  - test_postgres_url_isolates_between_tests (T1.4)
    手动起第 2 个 DB 比对,证明每 test 隔离
  - test_init_engine_postgres_creates_tables (T1.5)
    init_engine('postgres', postgres_url) 跑完后 information_schema
    出现 5 张现有表(users/threads_meta/runs/feedback/run_events)
  - test_thread_meta_repo_postgres_round_trip (T1.6)
    ThreadMetaRepository.create + get 完整 round-trip,验证 PG 上仓储行为
    与 SQLite 对齐

全部 @pytest.mark.postgres 门控;Docker 不可用时由 fixture 跳过。本次本地
docker daemon 未起,4 测试 SKIP;CI workflow(T1.9)将带 docker service
触发实跑。

Regression:跑全套 `pytest tests/` 3085 passed + 23 skipped + 0 failed
(基线 3086 passed + 18 skipped;diff = 4 新 postgres skip + 1 env 相关
live test 偶发 skip)。

Stage 0 PR1 T1.4 + T1.5 + T1.6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:50:10 +08:00
1445043649 31361e2dcf test(fixtures): add testcontainers Postgres fixture for Stage 0
新建 backend/tests/fixtures/postgres.py,提供两层 fixture:
  - postgres_container (session 级):用 PostgresContainer 起 postgres:16-alpine
  - postgres_url (function 级):每 test 一个 ephemeral DB,teardown 时
    pg_terminate_backend 清掉残连后 DROP DATABASE

为什么 per-DB 而不是 per-schema:asyncpg + SQLAlchemy 不通过 URL 传 search_path,
per-DB 一次 ~50ms 开销可接受,让 test 代码不感知 schema。

Docker 不可用时 fixture 自动 pytest.skip 而非 error,dev 环境无 Docker
仍能跑其余 3086 个测试。

注册 pytest mark `postgres`、把 tests/ 加 sys.path 让 pytest_plugins
按 `fixtures.postgres` 路径解析(不加 tests/__init__.py 避免干扰
现有 pytest 发现行为)。

Stage 0 PR1 T1.3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:44:02 +08:00
1445043649 ae4ea46be2 feat(docker): add postgres service to dev compose
新增 postgres:16-alpine service + named volume + healthcheck,作为 Stage 0+
默认 DB backend。生产可通过 DATABASE_URL 指向远程 RDS 时跳过此 service。
端口 / 用户 / 密码 / 库名都走 env var 覆盖(POSTGRES_USER/PORT/...
默认 deerflow/5432/deerflow_dev)。

YAML 通过 `docker compose config` 校验;live healthcheck 待 docker daemon 起后由
T1.3 testcontainers 路径覆盖。

Stage 0 PR1 T1.2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:40:12 +08:00
1445043649 fab85b14a6 build(deps): add postgres-test optional extra for testcontainers
新增 postgres-test extra(harness + backend),引入 testcontainers[postgres]
4.14.2 用于 Stage 0 PR1 的 PG fixture。复用现有 postgres extra 的
asyncpg/psycopg/langgraph-checkpoint-postgres,pytest-asyncio 沿用 dev group。

Stage 0 PR1 T1.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:38:50 +08:00
1445043649 a74b88a45c docs(superpowers): add Stage 0 multi-tenant foundation master plan
8 PR 串成的 stage-level 拆解(Postgres 切换 + workspaces schema + auth
扩字段 + 入口路由强校验 + CI boundary + headless schema 预留)。每个 PR
含 commit-sized TDD task list 和验收清单。执行时各 PR 独立 review
checkpoint。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:33:25 +08:00
1445043649 89fe54cc07 docs(multi-tenant): 加入汇总索引、跨文档一致性修订、Postgres 切换前移到 Stage 0
* 新增 README.zh-CN.md 汇总索引:ADR 状态表 + Stage 0-4 业务目标 / 技术路径 /
  验证方式 + Stage↔ADR 对照矩阵 + 不可逆决策一览 + 用语映射 + FAQ + 阅读路径
* 新增 workspace-schema-design.zh-CN.md(Stage 0 schema 锁定版)一并入库
* 7 份 ADR 顶部加"代码命名"映射行(tenant_id ↔ workspace_id)
* ADR-002 §1 加分期落地提示,明确 K8s 推迟到 Stage 3
* ADR-005 §5"第 1/2 阶段"补出与 rollout Stage 2/3 的映射
* ADR-007 §4 加 /api/v1/ 反向链接;§8 加 tid → wid 字段名映射
* headless-api §0/§7 把"SaaS + on-prem 双主线"改为"SaaS 主线、schema 兼容 on-prem"
* phased-rollout 去除重复的"Go/No-Go 进入 Stage 2"段

Postgres 切换从 Stage 1 提前到 Stage 0:Stage 0 已要 ALTER 4 张表加
workspace_id,先 SQLite 再 PG 是纯返工;Stage 0 没有生产数据,迁移阻力最小。
同步调整 phased-rollout / headless-api / phase-0-plan / workspace-schema-design /
README 中的时间盒(Stage 0: 3-4→4-5 周;Stage 1: 10-15→8-13 周)、不可逆决策
清单、PR 顺序、轨道前置依赖。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:39:58 +08:00
1445043649 9ff790554d docs(persistence): 给 ORM 表/字段补充中文注释
通过 SQLAlchemy 的 comment= 给 5 张持久化表(users / threads_meta / runs /
run_events / feedback)的所有字段以及表本身加上中文注释,便于读代码、
生成文档与未来切到 Postgres 时直接落库为 COMMENT ON。

SQLite 引擎本身不支持 COMMENT ON,运行时不会改变 .schema 输出。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 18:50:17 +08:00
1445043649 ecc9339ede docs(multi-tenant): 加入 Headless API 改造轨道(Pattern A/B)+ Stage 1 双轨修订
新增 headless-api-track 文档,把 DeerFlow 作为后端服务的两种集成
pattern 全部纳入设计:

- Pattern A(业务 backend 代理):业务系统 backend 用 API key 调,
  浏览器走他们的 backend。覆盖 IM channels + server-to-server 集成。
- Pattern B(自研 web 浏览器直连):业务 backend 调
  /api/v1/auth/exchange-token 换 5-15 min 短期 JWT → 浏览器拿 JWT 直连
  含 SSE。核心 4 件事:exchange-token endpoint、ServiceTokenAuthBackend
  (AuthMiddleware 第三条路径)、workspaces.allowed_origins + CORS
  中间件、SSE 跨域验证。
- 不做 widget / iframe(纯 API)。

数据模型:service_accounts / api_keys / external_users 表(schema 在
Stage 0 末加上不阻塞);身份模式三态(collapsed / external_passthrough
/ both)按 endpoint 分支。

Stage 1 改为三轨并行:付费 SaaS / Pattern A / Pattern B;时间盒从
6-10 周延到 10-15 周。Pattern B 依赖 Pattern A 完成,建议 Stage 1
末 1-2 周做。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:29:46 +08:00
1445043649 fd8d0d637d docs(multi-tenant): 加入 Stage 0 代码地图
把 Stage 0 必做项落到具体文件 + 行号,覆盖 7 个子系统:
auth、user 数据模型、thread 入口路由、ThreadDataMiddleware + 路径系统、
仓储访问模式、setup / 注册流程、CSRF + ContextVar 注入。

文档结构:每节有【关键文件 + 行号】、【关键函数 / 类】、【当前数据流】、
【Stage 0 改动锚点】;外加改动影响面总览(每条 Stage 0 必做项映射到
具体代码位置)、不可逆决策落点、推荐阅读顺序(11 个文件,1.5-2.5h
粗读)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:29:27 +08:00
1445043649 35ae97d0a6 docs(multi-tenant): Stage 2 加入 per-user skill 覆盖与 config 设计
补充团队 workspace 内不同成员的 skill 区分模型:

- 启用状态:workspace 级默认 + per-user 覆盖
  (final_enabled = user_override ?? workspace_default)
- skill 私有配置(API key 等):per-user 强制隔离 + KMS 加密
- 上传权限:仅 owner / admin,成员只能 enable/disable + 填自己 config

PR 顺序新增第 8 步,依赖 RBAC + KMS 已就位。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:47:05 +08:00
1445043649 e78ed687a6 docs(multi-tenant): 加入按规模分期的落地方案(02-rollout)
基于已收敛的目标客户画像(个人用户为主 + 小团队 / 中心化 SaaS /
freemium)把 7 份 ADR 拆成 5 期落地:

- Stage 0:workspace 模型立起来 + auth 收紧(不可逆决策集中在此)
- Stage 1:Postgres + Quota 必落(freemium 不上 quota = 信用卡递给攻击者)
- Stage 2:DeerFlow 表 RLS、KMS、ObjectStorage S3、付费分层
- Stage 3:K8s sandbox + BYO key + audit DB 拆分
- Stage 4:SSO / custom domain / per-tenant DB(按 enterprise 客户合同驱动)

关键取舍:① Stage 0 末就把 workspace_id 列加到 SQLite,避免 Stage 1
切 Postgres 时再补;② Quota 比 ADR-003 原稿提前一档到 Stage 1;③ K8s
sandbox 推到 Stage 3,AioSandbox + 出网白名单 + 资源限额撑到几千用户。

时间盒:最小可付费 4 个月(Stage 0+1)/ 风险可控增长 8 个月(+Stage 2)
/ 全功能 14 个月(+Stage 3)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:28:55 +08:00
1445043649 8bdd308ea0 docs(multi-tenant): 据审计对齐 ADR-002/003/004/005 与 phase-0 计划
非 spike 驱动的对齐改动:
- ADR-002/004/005:头部加现状提示 + 链接审计报告(沙箱出网/资源缺位、
  token_version 已存在 MembershipCache 全新建、ObjectStorage/7 表/KMS
  全部从 0 起)
- ADR-003 LLM 计费:修正 TokenUsageMiddleware 当前只 log 不持久化的描述;
  补充 create_chat_model sync→async 改造的连带影响说明
- phase-0 计划:新增 §3.5 底座先行(Postgres 测试夹具 / ObjectStorage
  Protocol / KMS 抽象 3 件并行做),时间盒 2 → 3 周;ADR-006/007 摘要
  对齐;DoD 加底座骨架检查项

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:03:32 +08:00
1445043649 27c4f14233 docs(multi-tenant): 加入 ADR 审计 + spike,并据其修订 ADR-001/006/007
新增两份评审产出物:
- adr-vs-code-audit:7 份 ADR 与现状代码的差异核对,标注每条假设是否成立
- adr-spike-langgraph-postgres:实测 langgraph-checkpoint-postgres==3.0.5
  注入能力,确认不存在 connection_factory 参数,且 psycopg_pool 自带的
  configure callback 不是 per-acquire hook

据 spike 与审计修订三份 ADR:
- ADR-001 数据隔离:LangGraph 表改为应用层强校验 + threads_meta unique
  约束兜底(不再挂 RLS、不 ALTER 表);hook 点从 AssistantsCompat 修正
  为 threads.py + thread_runs.py
- ADR-006 运行时与渠道:§2.1 完全重写为应用层强校验;MCP OAuth token
  从"无持久化进程内存"直接做加密 DB;channel store binding 改为新建
  channel_bindings 表
- ADR-007 路由与前端:删除 Better Auth 假设(前端实际无此依赖),改为
  扩展现有 auth/jwt.py TokenPayload 加 tid/role 字段

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:03:16 +08:00
1445043649 dce5e9598b docs(multi-tenant): add ADRs and phase-0 plan for multi-tenant redesign
Add 7 ADRs and a phase-0 plan covering the multi-tenant redesign of
DeerFlow, plus an architecture-overview snapshot of the current state.

ADRs:
- 001 data isolation: row-level tenant_id + Postgres RLS, including
  LangGraph-owned checkpoint tables (subquery RLS or column upgrade path).
- 002 sandbox isolation: K8s namespace + gVisor + NetworkPolicy default-
  deny, threat model and pod spec defaults.
- 003 LLM key & billing: hybrid platform/BYO with pessimistic reservation
  to handle the "ghost token" overflow on the last call, plus a usage
  category split for memory/title/summarization charges.
- 004 RBAC: two-level (owner/admin/member), JWT-with-role + 30s LRU
  cache for reads, strict DB lookup for sensitive writes, token_version
  bump as the single revocation path.
- 005 storage topology: Postgres (structured) + S3-compatible object
  store (large objects) + emptyDir (ephemeral); explicit treatment of
  the extensions_config.json migration's downstream effects.
- 006 runtime & channel tenancy: per-tenant MCP cache, dual-track skills
  loader, sandbox provider routing by namespace, internal LLM call
  billing, IM channel-to-tenant binding model.
- 007 routing & frontend: path-slug URL form, JWT-only API auth,
  TenantProvider, hard-reload tenant switch, Better Auth integration.

These docs are decision records; no code changes are included.
2026-05-08 23:45:33 +08:00
DanielWalnut 2b1fcb3e43 fix(task): remove max_turns parameter from task tool interface (#2783)
Unit Tests / backend-unit-tests (push) Has been cancelled
Frontend Unit Tests / frontend-unit-tests (push) Has been cancelled
Lint Check / lint (push) Has been cancelled
Lint Check / lint-frontend (push) Has been cancelled
* fix(task): remove max_turns parameter from task tool interface

Subagents should always use their configured max_turns value. Exposing
this parameter allowed callers to override the admin-configured limit,
which is undesirable. The value is now exclusively driven by subagent
config (per-agent overrides and global defaults in config.yaml).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-08 15:05:24 +08:00
He Wang 7de9b5828b fix(tools): introduce Runtime type alias to eliminate Pydantic serialization warning (#2774)
* fix(tools): introduce Runtime type alias to eliminate Pydantic serialization warning

Add deerflow/tools/types.py with:

    Runtime = ToolRuntime[dict[str, Any], ThreadState]

Replace every runtime: ToolRuntime[ContextT, ThreadState] and
runtime: ToolRuntime[dict[str, Any], ThreadState] annotation in
sandbox/tools.py, present_file_tool.py, task_tool.py, view_image_tool.py,
and skill_manage_tool.py with the new Runtime alias.

The unbound ContextT TypeVar (default None) caused
PydanticSerializationUnexpectedValue warnings on every tool call because
LangChain's BaseTool._parse_input calls model_dump() on the auto-generated
args_schema while DeerFlow passes a dict as runtime context.
Binding the context to dict[str, Any] aligns Pydantic's serialization
expectations with reality and removes the noise from all run modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(tools): extend Runtime alias to setup_agent and update_agent tools

Replace bare ToolRuntime annotations in setup_agent_tool.py and
update_agent_tool.py with the shared Runtime alias introduced in the
previous commit, and add both tools to the Pydantic serialization
warning regression test (13 cases total).

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(tools): loosen Pydantic warning filter to avoid version-specific format

Replace the brittle "field_name='context'" substring check with a looser
"context" match so the assertion stays valid if Pydantic changes its
internal warning format across versions.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(tools): simplify warning filter and clean up docstring

Remove the "context" substring condition from the Pydantic warning
filter — asserting that no PydanticSerializationUnexpectedValue fires
at all is both simpler and more comprehensive, since the test payload
contains only the tool's own args plus runtime.

Also update the module docstring to remove the version-specific warning
format example that was inconsistent with the looser filter.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 14:50:33 +08:00
Eilen Shin 37db689349 fix(events): serialize structured db event content (#2762) 2026-05-08 10:17:17 +08:00
Eilen Shin bd45cb2846 fix(sandbox): disable msys path conversion (#2766) 2026-05-08 10:13:11 +08:00
Eilen Shin 5fd0e6ac89 fix(middleware): sync raw tool call metadata (#2757) 2026-05-08 10:08:53 +08:00
YuJitang 530bda7107 fix: dedupe token usage aggregation by message id (#2770) 2026-05-08 09:54:20 +08:00
Willem Jiang 6c220a9aef fix(chat): prevent first user message from being swallowed in new conversations (#2731)
* fix(chat): prevent first user message from being swallowed in new conversations

  The optimistic message clearing effect cleared too eagerly — any stream
  message (including AI messages from messages-tuple events) triggered the
  clear before the server's human message had arrived via values events.
  For new threads this caused the user's first prompt to disappear permanently.

  Only clear optimistic messages once the server's human message has been
  confirmed to arrive in thread.messages, not just when any message arrives.

  Fixes #2730

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-07 17:31:48 +08:00
Tao Liu daa3ffc29b feat(loop-detection): make loop detection configurable with per-tool frequency overrides (#2711)
* Make loop detection configurable

Expose LoopDetectionMiddleware thresholds through config.yaml while preserving existing defaults and allowing the middleware to be disabled.

Refs bytedance/deer-flow#2517

* feat(loop-detection): add per-tool tool_freq_overrides to Phase 1

Adds ToolFreqOverride model and tool_freq_overrides field to
LoopDetectionConfig, wires it through LoopDetectionMiddleware, and
documents the option in config.example.yaml.

Resolves the gap flagged in the #2586 review: without per-tool overrides,
users hit by #2510/#2511 (RNA-seq workflows exceeding the bash hard limit)
had no way to raise thresholds for one tool without loosening the global
limit for every tool.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* docs(loop-detection): document tool_freq_overrides in LoopDetectionMiddleware docstring

Add the missing Args entry for tool_freq_overrides, explaining the
(warn, hard_limit) tuple structure and how per-tool thresholds supersede
the global tool_freq_warn / tool_freq_hard_limit for named tools.
Also run ruff format on the three files flagged by the lint check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(loop-detection): validate LoopDetectionMiddleware __init__ params eagerly

Raise clear ValueError at construction time instead of crashing at
unpack-time inside _track_and_check when bad values are passed:
- tool_freq_overrides: must be 2-tuples of positive ints with hard_limit >= warn
- scalar thresholds: warn_threshold, hard_limit, tool_freq_warn,
  tool_freq_hard_limit must be >= 1 and hard limits must >= their warn pairs
- window_size, max_tracked_threads must be >= 1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): isolate credential loader directory-path test from real ~/.claude

The test didn't monkeypatch HOME, so on any machine with real Claude Code
credentials at ~/.claude/.credentials.json the function fell through to
those credentials and the assertion failed. Adding HOME redirect ensures
the default credential path doesn't exist during the test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style(test): add blank lines after import pytest in TestInitValidation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(loop-detection): collapse dual validation to LoopDetectionConfig

Modifications
  - LoopDetectionMiddleware.__init__: stripped of all ValueError raises;
    becomes a plain field-assignment constructor.
  - LoopDetectionMiddleware.from_config: classmethod that builds the
    middleware from a Pydantic-validated LoopDetectionConfig and handles
    the ToolFreqOverride -> tuple[int, int] conversion.
  - agents/factory.py: SDK construction routed through
    LoopDetectionMiddleware.from_config(LoopDetectionConfig()) so the
    defaults path is Pydantic-validated too.
  - agents/lead_agent/agent.py: uses from_config instead of unpacking
    config fields by hand.
  - tests/test_loop_detection_middleware.py: deleted TestInitValidation
    (16 methods exercising the removed __init__ checks); added
    TestFromConfig (4 tests: scalar field mapping, override tuple
    conversion, empty overrides, behavioral smoke test).

Result: one validation layer (Pydantic), zero duplication, no __new__
hacks. Both production construction sites flow through LoopDetectionConfig.

Test results
  make test   -> 2977 passed, 18 skipped, 0 failed (137s)
  make format -> All checks passed; 411 files left unchanged

* feat(agents): make loop_detection configurable in create_deerflow_agent

Adds a `loop_detection: bool | AgentMiddleware = True` field to
RuntimeFeatures, mirroring the existing pattern used by `sandbox`,
`memory`, and `vision`. SDK users can now disable LoopDetectionMiddleware
or replace it with a custom instance built from their own
LoopDetectionConfig — e.g.
`LoopDetectionMiddleware.from_config(my_cfg)` — instead of being stuck
with the hardcoded defaults previously installed by the SDK factory.

The lead-agent path (which already reads AppConfig.loop_detection) is
unchanged, and the default `True` preserves prior always-on behavior for
all existing callers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: knight0940 <631532668@qq.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Amorend <142649913+knight0940@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-05-07 16:15:15 +08:00
Xinmin Zeng 27559f3675 fix(frontend): defer thread id to onStart to avoid 404 on new chat (#2749)
* fix(frontend): defer thread id to onStart to avoid 404 on new chat

The LangGraph SDK's useStream eagerly fetches /threads/{id}/history the
moment it receives a thread id, and the local useThreadRuns issues
GET /threads/{id}/runs for the same reason. The chats page used to flip
isNewThread=false (and forward the client-generated thread id) inside
the synchronous onSend callback, before thread.submit had created the
thread on the backend. The two queries therefore raced ahead of
POST /runs/stream and returned 404 on the very first send.

Drop the onSend handler so isNewThread stays true until onStart fires
from useStream's onCreated — by then the backend has the thread, and
the SDK's submittingRef guard naturally suppresses the redundant
history fetch. The agent chat page already uses this pattern, so this
also unifies the two flows.

Adds an E2E regression that records request ordering and asserts
GET /history and GET /runs are never issued before POST /runs/stream
on the first send from /chats/new.

Closes #2746

* fix(frontend): split welcome layout from backend thread state

Removing onSend kept GET /history and GET /runs from racing ahead of
POST /runs/stream, but it also coupled the welcome layout (centered
input, hero, quick actions) to backend thread creation.  Until onCreated
returned, the user's optimistic message and the welcome hero rendered on
top of each other.

Introduce a dedicated `isWelcomeMode` UI flag, separate from
`isNewThread`:
- `isNewThread` still tracks "backend has no thread yet" and gates the
  thread id forwarded to useStream.
- `isWelcomeMode` drives the visual layout (header background, input
  box position, max width, hero, quick actions, autoFocus) and flips to
  false inside onSend so the layout animates immediately.

`isWelcomeMode` is kept in sync with `isNewThread` via an effect so
sidebar navigation and "new chat" still behave correctly.  All 15 E2E
tests pass, including the ordering regression added in the previous
commit.

* test(e2e): use monotonic sequence for thread-init ordering check

Date.now() is millisecond-resolution, so two requests emitted within
the same tick would share a timestamp and slip past the strict `<`
ordering assertions. Replace the timestamp with a monotonic counter
that increments on every observed request/requestfinished event so the
ordering check is robust regardless of scheduling.

Per PR #2749 review feedback from copilot-pull-request-reviewer.

* refactor(input-box): rename isNewThread prop to isWelcomeMode

Inside InputBox, the prop named `isNewThread` is only ever consulted
for visual layout decisions — gating follow-up suggestions, the bottom
background strip, and the welcome-mode quick-action SuggestionList. It
never reflects "the backend has created the thread", which after #2746
is tracked separately via `isNewThread` in the chat pages themselves.

Rename the prop to `isWelcomeMode` and update both call sites
(workspace chats page and agent chats page) so the prop name matches
its actual semantics. No behavior change.

Per PR #2749 review feedback from @WillemJiang.
2026-05-07 16:11:44 +08:00
AochenShen99 cef4224381 fix(skills): enforce allowed-tools metadata (#2626)
* fix(skills): parse allowed-tools frontmatter

* fix(skills): validate allowed-tools metadata

* fix(skills): add shared allowed-tools policy

* fix(subagents): enforce skill allowed-tools

* fix(agent): enforce skill allowed-tools

* refactor(skills): dedupe TypeVar and reuse cached enabled skills

- Drop redundant module-level TypeVar in tool_policy; rely on PEP 695 syntax.
- Expose get_cached_enabled_skills() and have the lead agent reuse it
  instead of synchronously rescanning skills on every request.

* fix(agent): expose config-scoped skill cache

* fix(subagents): pass filtered tools explicitly

* fix(skills): clean allowed-tools policy feedback
2026-05-07 08:34:43 +08:00
Hinotobi 2b0e62f679 [security] fix(auth): reject cross-site auth POSTs (#2740)
* fix(security): reject cross-site auth posts

* fix(auth): align secure cookie proxy scheme handling

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-05-07 07:58:06 +08:00
Eilen Shin 1336872b15 fix(channels): authenticate gateway command requests (#2742) 2026-05-06 15:27:34 +08:00
KiteEater 4ead2c6b19 fix(config): reset config-backed singletons on hot reload (#2588)
* Fix stale config singletons on reload

* fix(config): update checkpointer imports after runtime move

* Fix config reload singleton mutation on validation failure

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-05-06 10:17:55 +08:00
yangzheli 59c4a3f0a4 feat(agent): add custom-agent self-updates with user isolation (#2713)
* feat(agent): add update_agent tool for in-chat custom-agent self-updates (#2616)

Custom agents had no built-in way to persist updates to their own SOUL.md /
config.yaml from a normal chat — `setup_agent` was only bound during the
bootstrap flow, so when the user asked the agent to refine its description
or personality, the agent would shell out via bash/write_file and the edits
landed in a temporary sandbox/tool workspace instead of
`{base_dir}/agents/{agent_name}/`.

Changes:
- New `update_agent` builtin tool with partial-update semantics (only the
  fields you pass are written) and atomic temp-file + os.replace writes so
  a failed update never corrupts existing SOUL.md / config.yaml.
- Lead agent now binds `update_agent` in the non-bootstrap path whenever
  `agent_name` is set in the runtime context. Default agent (no
  agent_name) and bootstrap flow are unchanged.
- New `<self_update>` system-prompt section is injected for custom agents,
  instructing them to use `update_agent` — and explicitly NOT bash /
  write_file — to persist self-updates.
- Tests: 11 new cases in `tests/test_update_agent_tool.py` covering
  validation (missing/invalid agent_name, unknown agent, no fields),
  partial updates (soul-only, description-only, skills=[] vs omitted),
  no-op detection, atomic-write safety, and AgentConfig round-tripping;
  plus 2 new cases in `tests/test_lead_agent_prompt.py` covering the
  self-update prompt section.
- Docs: updated backend/CLAUDE.md builtin tools list and tools.mdx
  (en/zh) with the new tool description.

* feat(agent): isolate custom agents per user

Store custom agent definitions under the effective user, keep legacy agents readable until migration, and cover API/tool/migration behavior with tests.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: consistent write/delete targets & add --user-id to migration

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 23:17:42 +08:00
Nan Gao e8675f266d fix(loop-detection): keep tool-call pairing on warn injection (#2724) (#2725)
* fix(loop-detection): keep tool-call pairing on warn injection (#2724)

* make format

* fix(loop-detection): avoid IMMessage leak to downstream consumer

* fix(channels): filter loop warning text from IM replies
2026-05-05 18:53:49 +08:00
Xun 680187ddc2 fix: Supplement list_running in RemoteSandboxBackend (#2716)
* fix: Supplement list_running in RemoteSandboxBackend

* fix

* except requests.RequestException as exc:

* fix
2026-05-05 18:53:10 +08:00
Xinmin Zeng aded753de3 fix(frontend): restore localhost fallback for getGatewayConfig in prod mode (#2705) (#2718)
* fix(frontend): unify gateway-config localhost fallback for prod (#2705)

`getGatewayConfig()` only fell back to localhost defaults when
`NODE_ENV === "development"`, while `next.config.js` always falls back
to `127.0.0.1:8001`. Running `make start` (which sets NODE_ENV=production
via `next start`) without `DEER_FLOW_INTERNAL_GATEWAY_BASE_URL` /
`DEER_FLOW_TRUSTED_ORIGINS` therefore caused zod to throw inside SSR
layouts and surfaced as a 500.

Drop the NODE_ENV gating and use localhost defaults everywhere — the
"force explicit config in prod" intent should be enforced by deployment
templates (docker-compose already sets both vars), not by request-time
crashes. Document the two vars in both .env.example files and add unit
coverage for the dev/prod env-unset paths.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Update internalGatewayUrl in gateway config tests

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-05 16:27:29 +08:00
Willem Jiang 028493bfd8 fix(docker):force ngix to resolve upstream names at request time (#2717)
* fix(docker):force ngix to resolve upstream names at request time

* fix(docker): set resolver valid=0s to eliminate DNS cache window for request-time re-resolution

Agent-Logs-Url: https://github.com/bytedance/deer-flow/sessions/07bdb872-022f-4fd2-9fa8-d800a4ce34a7

Co-authored-by: WillemJiang <219644+WillemJiang@users.noreply.github.com>

* Update DNS resolver valid time and add upstreams

* fix the unit test error

* Remove upstream server configurations from nginx.conf

Removed upstream server configurations for gateway and frontend.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-05 14:35:55 +08:00
Willem Jiang 8e48b7e85c fix(channels): preserve clarification conversation history across follow-up turns (#2444)
* fix(channels): preserve clarification conversation history across follow-up turns

Pin channel-triggered runs to the root checkpoint namespace and ensure thread_id is always present in configurable run config so follow-up replies resume the same conversation state.

Add regression coverage to channel tests:

assert checkpoint_ns/thread_id are passed in wait and stream paths
add an integration-style clarification flow test that verifies the second user reply continues prior context instead of starting a new session
This addresses history loss after ask_clarification interruptions (issue #2425).

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(channels): copy configurable dict before injecting run-scoped fields

  When configurable was already a plain dict, _resolve_run_params mutated
  it in place, leaking checkpoint_ns and thread_id back into the shared
  session config. Always copy via dict() before mutating to prevent
  cross-user or cross-channel config pollution.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-04 16:14:07 +08:00
Willem Jiang af6e48ccaa fix(i18n): add Chinese translations for account settings page (#2712)
The account settings page had all user-facing strings (profile labels,
  password form placeholders, validation messages, button text) hardcoded
  in English. Replace them with i18n translation keys so the page renders
  correctly when the locale is set to Chinese.

 Fixed #2710
2026-05-04 11:15:16 +08:00
Willem Jiang b10eb7bafc feat(github): Added container push workflow (#2709)
* feat(github):Added container push workflow

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-04 11:14:34 +08:00
YuJitang d02f762ab0 feat: refine token usage display modes (#2329)
* feat: refine token usage display modes

* docs: clarify token usage accounting semantics

* fix: avoid duplicate subtask debug keys

* style: format token usage tests

* chore: address token attribution review feedback

* Update test_token_usage_middleware.py

* Update test_token_usage_middleware.py

* chore: simplify token attribution fallback

* fix token usage metadata follow-up handling

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-05-04 09:56:16 +08:00
Willem Jiang 82e7936d36 fix(docker): set UTF-8 locale to prevent ASCII encoding errors in minimal containers (#2707)
* fix(docker): set UTF-8 locale to prevent ASCII encoding errors in minimal containers

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-04 09:41:10 +08:00
Nan Gao 222a7773cb fix(frontend): avoid misleading error message when agent api is disable (#2697) (#2698) 2026-05-04 09:38:05 +08:00
Nan Gao f80ac961ec fix(harness): restore legacy skills path fallback (#2694) (#2696)
* fix(harness): restore legacy skills path fallback (#2694)

* fix(format): make format

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-03 23:40:59 +08:00
wanxsb 44ab21fc44 feat(community): add Serper web search provider (#2630)
* feat(community): add Serper web search provider

Add a new community search provider backed by the Serper Google Search
API (https://serper.dev). Serper returns real-time Google results via a
simple JSON API and requires only an API key — no extra Python package.

Changes:
- backend/packages/harness/deerflow/community/serper/__init__.py
- backend/packages/harness/deerflow/community/serper/tools.py
  Implements web_search_tool using httpx (already a project dependency).
  API key is read from config.yaml `api_key` field or SERPER_API_KEY env var.
  Follows the same interface / output shape as the existing ddg_search provider.
  Exposes max_results parameter (default 5) with config override logic.
- backend/tests/test_serper_tools.py
  Unit tests covering API key resolution, config overrides, HTTP errors,
  empty results, and parameter passing.
- config.example.yaml: add commented-out Serper example alongside other providers
- .env.example: add SERPER_API_KEY placeholder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix the lint error

* Fix the lint error

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-05-02 16:22:35 +08:00
Hinotobi e543bbf5d6 [security] fix(upload): reject symlinked upload destinations (#2623)
* fix: reject symlinked upload destinations

* test: harden upload destination checks

* fix: address PR feedback for #2623

* test: cover safe upload re-uploads

* fix: preserve upload limit checks after rebase

* fix(upload): stream safe HTTP upload writes
2026-05-02 15:19:28 +08:00
Xinmin Zeng ca3332f8bf fix(gateway): return ISO 8601 timestamps from threads endpoints (#2599)
* fix(gateway): return ISO 8601 timestamps from threads endpoints (#2594)

ThreadResponse documents created_at / updated_at as ISO timestamps,
matching the LangGraph Platform schema (langgraph_sdk.schema.Thread
exposes them as datetime, JSON-encoded as ISO 8601). The gateway
threads router was instead emitting str(time.time()) — unix-second
floats — breaking frontend new Date() parsing and producing a mixed
ISO/unix wire format that also corrupted the search sort order.

Centralize timestamp generation in deerflow.utils.time:
- now_iso()       — datetime.now(UTC).isoformat()
- coerce_iso(x)   — heals legacy unix-timestamp strings on read so the
                    store converges to ISO without a one-shot migration

threads.py: replace 6 time.time() call sites with now_iso(); wrap all
read paths and Phase-2 checkpoint metadata with coerce_iso(); _store_upsert
opportunistically heals legacy created_at on update; drop unused time import.

thread_runs.py: reuse now_iso() instead of a private duplicate _now_iso(),
preventing future drift between the two timestamp call sites.

Tests: 9 unit tests for the helper; 5 integration tests pinning the ISO
contract for create/get/patch/search and the legacy-healing path on the
internal store upsert. Full suite: 2144 passed, 15 skipped, 0 failed.

Closes #2594

* fix(gateway): coerce checkpoint metadata timestamps to ISO on read

After the merge with main, three additional read paths in ``threads.py``
were still emitting raw ``str(metadata.get("created_at", ""))`` —
``get_thread_state``, ``update_thread_state``, and ``get_thread_history``.

Same root cause as #2594: when the checkpoint metadata's ``created_at``
is a unix-second float (legacy data, or a checkpoint written by an older
Gateway version), ``str(float)`` produces ``"1777252410.411327"`` and the
frontend's ``new Date(...)`` returns ``Invalid Date``. The fix on the
``/threads/{id}`` GET path was already in place; these three sibling
endpoints needed the same treatment.

All four call sites now flow through ``coerce_iso``, so:
- legacy float metadata heals to ISO on the way out,
- ISO metadata passes through unchanged,
- ``datetime`` instances (which the new ``coerce_iso`` branch handles
  explicitly) emit with the ``T`` separator instead of falling through
  to the space-separated ``str(datetime)`` form.

Coverage added for the two endpoints not already pinned by the merge:
- ``test_get_thread_state_returns_iso_for_legacy_checkpoint_metadata``
- ``test_get_thread_history_returns_iso_for_legacy_checkpoint_metadata``

Both pre-seed a checkpoint whose metadata carries the literal float
from the issue body and assert the wire format is ISO.
2026-05-02 15:16:16 +08:00
Willem Jiang bb8b234d85 chroe(2585): keep polishing the code of codex token usage (#2689) 2026-05-02 15:04:11 +08:00
KiteEater 17447fccbe fix(runtime): make rollback restore checkpoint supersede newer checkpoints (#2582)
* Restore rollback checkpoints with fresh ids

* Tighten rollback checkpoint tests and imports

* Update test_run_worker_rollback.py

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-05-02 11:25:45 +08:00
KiteEater 866d1ca409 Populate Codex usage metadata for token accounting (#2585) 2026-05-02 11:16:03 +08:00
greatmengqi 8ba01dfd83 refactor: thread app_config through lead and subagent task path (#2666)
* refactor: thread app config through lead prompt

* fix: honor explicit app config across runtime paths

* style: format subagent executor tests

* fix: thread resolved app config and guard subagents-only fallback

Address two PR review findings:

1. _create_summarization_middleware passed the original (possibly None)
   app_config into create_chat_model, forcing the model factory back to
   ambient get_app_config() and risking config drift between the
   middleware's resolved view and the model's view. Pass the resolved
   AppConfig instance through end-to-end.

2. get_available_subagent_names accepted Any-typed config and forwarded
   it to is_host_bash_allowed, which reads ``.sandbox``. A
   SubagentsAppConfig (also accepted upstream as a sum-type input) has
   no ``.sandbox`` attribute and would be silently treated as "no
   sandbox configured", incorrectly disabling the bash subagent. Guard
   on hasattr and fall back to ambient lookup otherwise.

Adds regression tests for both paths.

* chore: simplify hasattr guard and tighten regression tests

- Collapse if/else into ternary in get_available_subagent_names; hasattr(None, ...) is False so the explicit None check was redundant.
- Drop comments that narrate the change rather than explain non-obvious WHY (test names already convey intent).
- Replace stringly-typed sentinel "no-arg" in regression test with direct args tuple comparison.

---------

Co-authored-by: greatmengqi <chenmengqi.0376@bytedance.com>
2026-05-02 06:37:49 +08:00