architecture-refactor-tasklist.md 200 KB

BettaFish 架构重构任务清单

目标:将当前 BettaFish 从“运行时重入口 + 局部全局状态驱动”的模块化单体,逐步重构为“任务驱动 + 应用层编排 + 统一运行时模型”的可持续架构。

原则:不推倒重来;先拆运行时,再统一模型,再补应用层,最后做测试、可观测性与后台执行体系。


0. 使用说明

  • 本文档是整个项目后续优化开发的执行基线。
  • 开发时严格按阶段推进,避免跨阶段大范围同时改动。
  • 每完成一个任务,必须更新状态与备注。
  • 状态标记:
    • [ ] 未开始
    • [-] 进行中
    • [x] 已完成
    • [!] 阻塞/需决策

1. 总体重构目标

1.1 需要解决的核心问题

  • apps/web_api/app.py 过度中心化,承担了 app 初始化、蓝图注册、Socket.IO、系统状态、进程管理、Forum 启停、引擎构造、任务分发等多重职责。
  • 系统主链路缺乏统一任务模型,研究任务、采集任务、分析任务、报告任务没有形成标准化状态对象与状态迁移机制。
  • API 层与业务编排层耦合,HTTP 请求处理逻辑直接碰运行时状态与引擎对象。
  • services/shared/ 尚未完成 shared config / dto / errors / events / observability 的真正收口。
  • 前端虽然已经形成“任务准备 → 采集 → 分析 → 报告”主流程,但状态来源仍分散,页面需要自行拼装多个接口结果。
  • 当前测试偏局部模块,系统主链路缺少稳定的集成与冒烟测试。

1.2 最终目标架构

apps/
├─ web_api/
│  ├─ app.py
│  ├─ factory.py
│  ├─ bootstrap/
│  ├─ runtime/
│  └─ interfaces/
├─ web_ui/
└─ engine_console/

backend/
├─ api/
│  ├─ research_tasks.py
│  ├─ crawler.py
│  ├─ analysis.py
│  ├─ reports.py
│  └─ system.py

services/
├─ application/
│  ├─ research/
│  ├─ crawler/
│  ├─ analysis/
│  └─ report/
├─ engines/
│  ├─ query/
│  ├─ media/
│  ├─ insight/
│  ├─ forum/
│  └─ report/
├─ crawler/
│  ├─ domain/
│  ├─ application/
│  └─ adapters/
└─ shared/
   ├─ config/
   ├─ dto/
   ├─ errors/
   ├─ events/
   ├─ logging/
   ├─ models/
   ├─ llm/
   └─ observability/

2. 执行策略与阶段划分

P0:结构止血期

目标:先把运行时中心化问题拆开,建立统一模型基础。

P1:业务收口期

目标:建立应用服务层,统一 API / 前端 / 引擎协作方式。

P2:系统增强期

目标:补系统级测试、可观测性、持久化恢复与后台任务执行体系。


3. P0:结构止血期

3.1 拆分 apps/web_api/app.py

目标

将当前单文件重入口改造成“入口壳 + bootstrap + runtime”结构。

当前进展快照(2026-04-17 16:05)

  • 已完成 runtime/system_state.pyapp.py 已切换为导入 get_system_state() / set_system_state() / prepare_system_start() / mark_shutdown_requested()
  • 已完成 runtime/process_manager.pyapp.py 已切换为依赖 Streamlit 启停/状态检查/清理逻辑;process_manager.py 中遗留的 processes 兼容出口也已移除。
  • runtime/log_stream.py 文件已落地,process_manager.py 已改为依赖其中的 write_log_to_file()read_process_output()
  • 已完成 runtime/forum_runtime.pyruntime/search_dispatch.py,Forum 启停 / 日志读取、搜索分发主链路已从 app.py 移出。
  • 已完成 runtime/system_lifecycle.pyapp.py 的系统启动、异步关机、Socket.IO 停止调度已改为委托 SystemLifecycleService
  • 已新增 bootstrap/runtime.py,把 runtime service 构造、route dependency 拼装从 app.py 迁出;同时已新增 bootstrap/http_config.py 承接前端 dev URL 解析、bootstrap/cleanup.py 承接 reload-safe cleanup 注册、bootstrap/import_path.py 承接项目根路径注入。http_routes.py 中 Forum/日志相关操作以及剩余的 Streamlit/process-manager 行为(STREAMLIT_SCRIPTS、状态刷新、启动/停止、启动等待)也已切到通过 HttpRouteDependencies 注入,路由层不再直接 import process_manager.py
  • interfaces/socket_events.py 也已切换为通过 SocketEventDependencies 接收 process_registry 和状态刷新 callable,默认装配由 bootstrap/runtime.py 承接,Socket.IO handlers 不再依赖 process_manager 的模块级默认 import。
  • 已新增 bootstrap/search_hooks.py,承接 app.py 原先保留的 search monkeypatch 兼容逻辑;当前入口只保留 _APP_MODULE 装配结果、app / socketio / main(),兼容逻辑已迁出到独立 helper。
  • 已新增 bootstrap/app_module.py,统一承接 app module 级别的 Flask/Socket.IO/runtime services/cleanup/search hooks/route wiring 装配;app.py 当前只保留 import-path 入口壳、bootstrap_app_module(...) 结果导出与 main()。helper 内部对 runtime builders 改为经 apps.web_api.bootstrap.runtime 模块属性读取,避免测试里的 monkeypatch 因导入时 capture 符号而失效。
  • SystemLifecycleService 已新增 SystemLifecycleDependencies,启动与关机两条链路都改为消费同一组显式 runtime callables;默认装配也已迁到 bootstrap/runtime.py,不再在 service 方法体里直接调用模块级 runtime 函数。
  • process_manager.py 的 cleanup 链路已新增 ProcessCleanupPort,串行/并发清理都改为消费同一 cleanup port;bootstrap/runtime.py 的 reload-safe cleanup handler 与 SystemLifecycleService.start_async_shutdown() 已同步切换到该显式端口,并补了直接 cleanup 单测覆盖 linger/二次 kill 分支。
  • services/application/analysis/analysis_service.py 已落地,/api/search 的 query 解析、运行中引擎判定、任务状态推进与后台分发接单逻辑已开始迁入 application 层;runtime/search_dispatch.py 当前主要保留本地引擎构造/执行 adapter,以及 SearchDispatchRuntime / submitter builder 等 runtime 装配入口。
  • build_runtime_services() 已开始显式构造 analysis_serviceapp.py 的 search alias 当前改为指向 RUNTIME_SERVICES.analysis_service 的 bound methods;同时 check_app_status() 已支持绑定指定 process_registryHttpRouteDependenciesSocketEventDependenciesSystemLifecycleDependencies 现在都优先使用注入 registry,而不是隐式落回全局单例。
  • bootstrap/runtime.py 的 search route contract 已进一步收敛为单一 submit_search_request submitter;process_registrycheck_app_statuslog_dirwrite_log 等运行时协作依赖的预绑定,当前统一由 runtime/search_dispatch.py 的 builder 承接,build_http_route_dependencies() 只接收最终 submitter 并注入路由依赖。
  • 2026-04-20:bootstrap/runtime.py / bootstrap/app_module.py 本轮继续把 search route contract 再收一层:route/bootstrap 公共契约当前只暴露单一 submit_search_request submitter,build_http_route_dependencies() 不再直接接收 resolve_search_query / dispatch_search_request 两段式依赖;后续轮次中,search_hooks 的动态 binding 装配也已继续从 app_module 收回到 bootstrap/runtime.py,同时仍保持 search_hooks.dispatch_search_request monkeypatch 兼容链路。
  • 2026-04-20:runtime/search_dispatch.py 本轮继续接管 submitter 最终装配:新增 build_search_request_submitter() 后,bootstrap/runtime.py 当前不再自己手搓 execution_context + dispatch closure,而是转为委托 runtime adapter builder;bootstrap/app_module.py 也不再手动构造 AnalysisExecutionContext。至此 /api/search 的 route submitter 绑定知识已进一步集中到 runtime/search adapter 层,app_module 只负责把 search hooks 与 route wiring 接起来。
  • 2026-04-20:runtime/system_lifecycle.py 本轮继续接管 cleanup port 的装配:新增共享 build_process_cleanup_port() 后,bootstrap/runtime.py 的 reload-safe cleanup handler 与 SystemLifecycleService.start_async_shutdown() 当前都改为消费同一 cleanup builder,不再各自重复手搓 ProcessCleanupPort。这样 system shutdown / reload cleanup 的状态回写约定已进一步收口到 lifecycle runtime 单点来源。
  • 2026-04-20:bootstrap/runtime.py 本轮也继续移除了只作中转的 lifecycle dependency builder 壳;build_runtime_services() 现在会直接委托 runtime/system_lifecycle.pybuild_default_system_lifecycle_dependencies(...),不再在 bootstrap 侧保留平行的 build_system_lifecycle_dependencies() 转发层。
  • AnalysisService 对任务服务的依赖已收敛为显式 ResearchTaskAnalysisPort_update_task_status() / _get_task_query() 中的 legacy hasattr fallback 已移除;当前 production wiring 统一走 ResearchTaskService
  • 已新增 bootstrap/search_hooks.py,search monkeypatch 兼容面当前通过 _APP_MODULE.search_hooks 承接;resolve_search_query / dispatch_search_request 不再直接以 app-module 级全局 alias 暴露。
  • 2026-04-21:本轮继续把这条 search seam 再压薄一层:bootstrap/app_module.py 当前已不再额外构造 SearchHookContainer,而是让 _APP_MODULE.search_hooks 直接暴露同一个 search_dispatch_runtime 对象;随后 bootstrap/runtime.py 也已继续接管动态 binding 的装配,app_module 不再手工创建 search hook bindings,build_search_hook_bindings() 当前只保留“对任意可变 hook source 做调用时解析”的最小 monkeypatch helper。
  • 已新增 runtime/process_manager.pyProcessManager 类,build_runtime_services() / RuntimeServices 当前会显式注入该对象;HttpRouteDependenciesSocketEventDependenciesSystemLifecycleDependencies 已开始优先消费注入的 ProcessManager bound methods,而模块级函数仅保留兼容壳。
  • 已新增 runtime/forum_runtime.pyForumRuntime / build_forum_runtime()build_runtime_services() / RuntimeServices 现已显式持有 forum runtime 对象,bootstrap/runtime.pySystemLifecycleService 与 HTTP route dependency 装配当前都优先消费同一 forum runtime 实例的 bound methods,而不再在主链路上直接依赖模块级 start_forum_engine() / stop_forum_engine() 默认函数。
  • ForumRuntime.get_log_history() 已修复文本迭代后调用 file.tell() 触发 OSError 的问题,Forum 日志 helper 的 cursor contract 已重新纳入单测。
  • 2026-04-20:app.py 本轮已进一步收口为更纯的入口壳,不再导出 _REGISTERED_CLEANUP_HANDLERSEARCH_HOOKS;热重载时改为从上一轮 _APP_MODULE.cleanup_handler 续接 reload-safe cleanup,测试侧 monkeypatch 也已同步改走 _APP_MODULE.search_hooks
  • 2026-04-20:forum_runtime.py 曾先把模块级 wrapper 改为在调用时经 build_forum_runtime() 解析默认 runtime;search_dispatch.py 也补了 build_analysis_execution_context()_resolve_analysis_service(),先把 execution context / analysis service 的缺省解析收口到明确 helper。
  • 2026-04-21:本轮继续把这两条兼容壳彻底收掉:forum_runtime.py 当前仅保留 ForumRuntime / build_forum_runtime() / parse_forum_log_line()search_dispatch.py 也已移除 get_default_search_dispatch_runtime()、模块级 execute_search_dispatch_async()dispatch_search_request()resolve_search_query();搜索侧剩余兼容面当前主要转移到 bootstrap/search_hooks.py 的 app/bootstrap monkeypatch seam,而不是 runtime 模块级入口。

任务清单

  • [-] 盘点 app.py 当前职责边界,按以下类别拆分:
    • app/factory 初始化
    • blueprint 注册
    • socketio 初始化
    • system state 管理
    • process 管理
    • forum runtime 管理
    • log/output 管理
    • search dispatch 管理
    • system start/shutdown 管理
    • 备注(2026-04-15 14:23):当前已明确三块已落地 runtime:system_stateprocess_managerlog_stream(部分接线);Forum、搜索分发、系统生命周期仍集中在 app.py
  • 新建 apps/web_api/factory.py,提供 create_app()
    • 备注(2026-04-15 14:35):已落地 create_app(),收拢 Flask app 构造、SECRET_KEY、Socket.IO 创建与 blueprint 注册;后续轮次中 app.py 也已稳定改为通过 factory 获取 app, socketio
  • 新建 apps/web_api/bootstrap/blueprints.py,集中注册 blueprint。
    • 备注(2026-04-15 14:35):已迁出 crawler/research/config/report blueprint 注册;ReportEngine 仍保持惰性导入与降级日志,但 blueprint 可用性判断已从 app.py 移除。
  • 新建 apps/web_api/bootstrap/socketio.py,集中创建/配置 Socket.IO。
    • 备注(2026-04-15 14:35):已抽出 create_socketio(app);当前虽然仍只封装 cors_allowed_origins,但 Socket.IO 创建/绑定职责已稳定离开入口文件。
  • 新建 apps/web_api/bootstrap/logging.py,迁移 eventlet 断连补丁与日志初始化。
    • 备注(2026-04-15 14:35):已迁出 Python 运行时环境变量设置与 eventlet 断连补丁;ensure_runtime_dirs() 与业务日志/生命周期日志初始化仍在 runtime/bootstrap 其他模块侧,但本条 bootstrap logging 拆分任务已完成。
  • 新建 apps/web_api/runtime/system_state.py,迁移 _system_state_get_system_state()_set_system_state()_prepare_system_start()_mark_shutdown_requested()
    • 备注(2026-04-15 14:23):当前模块导出为 get_system_state() / set_system_state() / prepare_system_start() / mark_shutdown_requested()app.py 已完成接线。
  • 新建 apps/web_api/runtime/process_manager.py,迁移:
    • processes(历史兼容别名,现已移除)
    • STREAMLIT_SCRIPTS
    • start_streamlit_app()
    • stop_streamlit_app()
    • check_app_status()
    • wait_for_app_startup()
    • cleanup_processes()
    • cleanup_processes_concurrent()
    • 备注(2026-04-16 13:52):process_manager.py 已直接依赖 runtime/log_stream.py,并已移除 processes 兼容 alias;cleanup 链路中的 forum 停止与 system state 标记已收敛为 ProcessCleanupPort,当前剩余较明显的 callback-style 依赖主要是启动链路上的 emit_output
  • 新建 apps/web_api/runtime/log_stream.py,迁移:
    • write_log_to_file()
    • read_log_from_file()
    • read_process_output()
    • parse_forum_log_line() 相关依赖梳理
    • 备注(2026-04-15 17:55):log_stream.py 已承接日志写入、日志读取、子进程输出转发,app.py 中的重复日志实现已移除;Forum 日志解析仍可继续独立清理,但主链路接线已完成。
  • 新建 apps/web_api/runtime/forum_runtime.py,迁移:
    • start_forum_engine()
    • stop_forum_engine()
    • forum status 维护
    • 备注(2026-04-17):runtime/forum_runtime.py 已进一步对象化为 ForumRuntime / build_forum_runtime(),通过显式注入的 ProcessRuntimeRegistry 管理 forum 状态;build_runtime_services() / RuntimeServices、route dependency 与 SystemLifecycleDependencies 当前都优先消费同一个 forum runtime 对象的 bound methods。
    • 备注(2026-04-20):本轮继续把旧兼容壳往对象路径压缩,forum 启停与日志读取相关 contract 已补到 tests/unit/web_api/test_forum_runtime.py / tests/unit/web_api/test_forum_runtime_helpers.py
    • 备注(2026-04-21):本轮已移除 FORUM_RUNTIMEget_default_forum_runtime() 以及剩余模块级 forum wrapper;forum 侧公开面当前收敛为 ForumRuntime / build_forum_runtime() / parse_forum_log_line()
  • 新建 apps/web_api/runtime/search_dispatch.py,迁移:
    • _build_query_agent()
    • _build_media_agent()
    • _build_insight_agent()
    • _run_local_engine_research()
    • _execute_search_dispatch_async()
    • 备注(2026-04-16 14:54):runtime/search_dispatch.py 已退化为本地引擎构造/执行 adapter 与 search runtime builder;query 解析与接单分发编排已开始委托给 services/application/analysis/AnalysisService
    • 备注(2026-04-20):本轮继续把 search runtime 默认依赖集中化:新增 build_analysis_execution_context()_resolve_analysis_service(),先把 execution context / analysis service 的缺省解析收口到显式 helper;build_search_request_submitter() 也已接管 route submitter 的最终装配。
    • 备注(2026-04-21):本轮已新增 SearchDispatchRuntime / build_search_dispatch_runtime() 并移除剩余模块级 search wrapper;搜索侧 runtime 公开面当前收敛为 runtime object、本地 agent builder、execution-context builder 与 submitter builder,兼容 monkeypatch seam 则保留在 bootstrap/search_hooks.py
  • 新建 apps/web_api/runtime/system_lifecycle.py,迁移:
    • _schedule_server_shutdown()
    • _start_async_shutdown()
    • initialize_system_components() 调用协作逻辑
    • 备注(2026-04-15 14:35):已新增 SystemLifecycleService,承接系统启动、异步关机、Socket.IO 停止调度;app.py/api/system/start/api/system/shutdown 已改为委托 runtime 模块,但 Flask/Socket.IO 初始化仍在入口文件中。
  • app.py 收缩为:
    • 导入 create_app()
    • 暴露 app / socketio / main
    • 少量根级启动逻辑
    • 备注(2026-04-17):app.py 已进一步收缩为 import-path 入口壳、bootstrap_app_module(...) 装配结果消费,以及 main()create_app()、runtime services、cleanup handler、search hook seam、route/socket dependency wiring 与 handler 注册现统一由 apps/web_api/bootstrap/app_module.py 承接。后续轮次已继续把 SEARCH_HOOKSSEARCH_HOOK_BINDINGS_REGISTERED_CLEANUP_HANDLER 等旧模块级兼容出口收回到私有 _APP_MODULE 之下。
    • 备注(2026-04-20):本轮已继续移除 RUNTIME_SERVICESSEARCH_HOOK_BINDINGSROUTE_DEPENDENCIESSOCKET_EVENT_DEPENDENCIES 四个未再被消费的兼容导出;入口壳当前公开面已进一步收缩到 appsocketiomain
    • 备注(2026-04-20):本轮继续把 bootstrap 内部装配结果从入口契约中收回:AppModuleBootstrapResult 当前只暴露 appsocketiocleanup_handlersearch_hooksruntime_servicessearch_hook_bindingsroute_dependenciessocket_event_dependencies 等内部装配产物不再作为 app-module 结果对象向外暴露。
    • 备注(2026-04-20):本轮继续移除入口级测试型兼容出口:app.py 已不再导出 _REGISTERED_CLEANUP_HANDLERSEARCH_HOOKS,相关 reload-safe cleanup 与 search monkeypatch 夹具已统一改为通过私有 _APP_MODULE 访问 cleanup_handlersearch_hooks
  • 更新 apps/web_api/__init__.py,确保导出路径稳定。
    • 备注(2026-04-16 09:10):包级入口已稳定导出 create_app,未再依赖旧入口文件侧效应。
  • 启动验证:确保主服务可正常启动,关键接口不报错。
    • 备注(2026-04-16 16:00):当前已通过定向 unit tests、tests/unit/application/test_analysis_run_store.pytests/unit/application/test_analysis_service.pytests/unit/web_api/test_process_manager_cleanup.pytests/unit/web_api/test_http_routes_runtime_dependencies.pytests/unit/web_api/test_runtime_bootstrap.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/integration/test_web_api_system_controls.py 验证 / 首页 dev redirect、/api/status/api/start/<app_name>/api/stop/<app_name>/api/system/status/api/system/start/api/system/shutdown 成功/失败透传、/api/search/api/research/tasks/<task_id>/analysis-run、factory/bootstrap 装配、Forum/日志/Streamlit 路由依赖注入、route-ready search wrappers、Socket.IO handlers 关键链路、app-module search monkeypatch 兼容链路、process-manager cleanup 串行/并发分支,以及 AnalysisService/AnalysisRun 的 application-layer contract;当前定向回归为 64 条测试通过。现存噪音主要是 eventlet deprecation warning 与 .pytest_cache 权限告警。
    • 备注(2026-04-16):analysis query 链路已从 route 侧临时拼装的 dummy AnalysisService 中拆出,改为独立 AnalysisRunQueryService;同时补齐 AnalysisRunStore.list_runs_for_task(...)GET /api/research/tasks/<task_id>/analysis-runs,并继续把 analysis 查询输出标准化到 AnalysisRunDTO/AnalysisRunListDTO。本轮还补了更资源化的 GET /api/research/tasks/<task_id>/analysis 视图,以及 crawler 侧的 CrawlerJobDTO/CrawlerJobListDTOCrawlerJobQueryServiceGET /api/crawler/jobs / GET /api/crawler/jobs/{id} 只读查询边界,当前相关定向测试已更新为 89 条通过,warning 仍仅为 eventlet deprecation 与 .pytest_cache 权限告警。
    • 备注(2026-04-16):已继续沿 task-scoped analysis 资源化方向推进,新增 GET /api/research/tasks/<task_id>/analysis,由 AnalysisRunQueryService 复用现有 run/history payload helper 输出嵌套 analysis 资源视图;本轮相关回归为 tests/unit/application/test_analysis_query_service.py + tests/integration/test_web_api_smoke.py 共 20 条通过。
    • 备注(2026-04-17):已把 task-scoped analysis 资源视图进一步收敛到共享 TaskAnalysisResourceDTO,将 summary / history / stats 的派生逻辑从 AnalysisRunQueryService 中抽离,同时保持 /api/research/tasks/<task_id>/analysis 外部 payload 不变;crawler 读侧则继续补强 tests/unit/application/test_crawler_query_service.pytests/unit/backend/test_crawler_routes.pytests/integration/test_web_api_factory_smoke.py,锁定 idle + empty history、单 job 成功查询和 factory app 下 /api/crawler/jobs / /api/crawler/jobs/{id} 的注册与最小 smoke。当前扩展后的定向回归为 97 条测试通过,warning 仍仅为 eventlet deprecation 与 .pytest_cache 权限告警。
    • 备注(2026-04-17):已新增 services/application/crawler/crawler_service.pyCrawlerStateDTO,将 /api/crawler/state/api/crawler/jobs/api/crawler/jobs/{id} 以及 login/crawler start-stop 路由统一收口到 CRAWLER_APP_SERVICE;route 层当前只保留请求解析、异常映射与响应封装。相关回归已扩展到 tests/unit/shared/test_crawler_state_dto.pytests/unit/application/test_crawler_service.py,并补了 /api/crawler/start/api/crawler/stop 的 app smoke,当前组合回归为 118 条测试通过,warning 仍仅为 eventlet deprecation 与 .pytest_cache 权限告警。
    • 备注(2026-04-17):已继续打通 task-scoped crawler 闭环:ResearchTaskService.set_task_status()backend/research_tasks.pymap_legacy_research_task() 现已支持 crawler_job_id round-trip,CrawlerService.start_crawler() 会在携带 research_task_id 时把 history_id -> crawler_job_id 回写到任务;同时 backend/research_routes.py 已新增 GET /api/research/tasks/<task_id>/crawler,以共享 TaskCrawlerResourceDTO 输出“仅 linked job 可见”的 task-scoped crawler 资源视图,不再回退到全局 current job。围绕这条链路补齐 tests/unit/shared/test_task_crawler_resource_dto.pytests/unit/application/test_research_task_service.pytests/unit/application/test_crawler_service.pytests/integration/test_web_api_smoke.py 后,已重新执行主定向回归共 126 条测试通过;warning 仍仅为 eventlet deprecation 与 .pytest_cache 权限告警。
    • 备注(2026-04-17):Forum runtime 对象化后,已新增 tests/unit/web_api/test_forum_runtime.py、恢复 tests/unit/web_api/test_forum_runtime_helpers.py 的 log history cursor contract,并补齐 tests/unit/web_api/test_runtime_bootstrap.py 对 “同一 forum runtime 对象贯穿 runtime services / route wiring” 的断言;相关定向回归 31 passed33 passed,warning 仍仅包含既有 eventlet deprecation、datetime.utcnow() deprecation 与 .pytest_cache 权限提示。
    • 备注(2026-04-17):已新增 tests/unit/web_api/test_app_module_bootstrap.py,覆盖 bootstrap_app_module(...) 对 app/socketio/runtime services/cleanup/search hooks/route wiring 的装配 contract;同时修复 helper 直接导入 runtime builders 导致 tests/integration/test_web_api_system_controls.py 中 monkeypatch 失效的问题。相关回归已补跑 tests/unit/web_api/test_app_module_bootstrap.pytests/unit/web_api/test_search_hooks.pytests/unit/web_api/test_cleanup.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/integration/test_web_api_system_controls.py34 passed,以及 tests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_socket_events.py 加同一组 integration suites 共 35 passed;warning 仍仅包含既有 eventlet deprecation、datetime.utcnow() deprecation 与 .pytest_cache 权限提示。
    • 备注(2026-04-20):本轮继续补跑 tests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_routes.pytests/unit/backend/test_crawler_runtime.pytests/unit/backend/test_crawler_runtime_service.pytests/unit/shared/test_config_access.py,组合回归为 50 passed;其中 tests/integration/test_web_api_smoke.py 已补充断言,锁定 app.py 不再导出 RUNTIME_SERVICESSEARCH_HOOK_BINDINGSROUTE_DEPENDENCIESSOCKET_EVENT_DEPENDENCIES 这四个历史兼容对象。
    • 备注(2026-04-20):本轮又补跑 tests/unit/web_api/test_app_module_bootstrap.pytests/unit/web_api/test_process_manager_cleanup.pytests/unit/web_api/test_forum_runtime.pytests/unit/web_api/test_forum_runtime_helpers.pytests/unit/web_api/test_runtime_bootstrap.pytests/unit/backend/test_crawler_runtime.pytests/unit/backend/test_crawler_runtime_service.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.py,组合回归为 58 passed;其中 test_app_module_bootstrap.py 已同步锁定 bootstrap 结果对象只保留入口真正需要的公开字段。
    • 备注(2026-04-20):本轮继续补跑 tests/unit/web_api/test_process_manager_cleanup.pytests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_app_module_bootstrap.pytests/unit/web_api/test_system_lifecycle.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_runtime_service.py,组合回归为 57 passed;新增断言锁定 app.py 不再导出 _REGISTERED_CLEANUP_HANDLER / SEARCH_HOOKSSTREAMLIT_SCRIPTS 改为实时兼容视图,以及 build_system_lifecycle_dependencies() 统一委托 runtime lifecycle 默认 builder。
    • 备注(2026-04-20):本轮又补跑 tests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_forum_runtime.pytests/unit/web_api/test_forum_runtime_helpers.pytests/unit/web_api/test_runtime_bootstrap.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_runtime_service.py,组合回归为 64 passed;新增断言锁定 forum wrapper 改为调用时解析默认 runtime、search dispatch wrapper 统一通过 helper 解析 execution context / analysis service,以及 crawler manager 继续通过 build_execution_context() 透传 runtime cwd/env
    • 备注(2026-04-20):本轮继续把 web_api search bootstrap contract 收口为单一 submitter:tests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_app_module_bootstrap.pytests/unit/web_api/test_http_routes_runtime_dependencies.pytests/integration/test_web_api_smoke.py 已联合回归 41 passed,并额外通过 python -m py_compile apps/web_api/bootstrap/runtime.py apps/web_api/bootstrap/app_module.py 语法检查;新增断言锁定 HttpRouteDependencies 只消费 submit_search_request,而 search_hooks 的动态 monkeypatch smoke 继续保持兼容。
    • 备注(2026-04-20):本轮继续把 search submitter 的最终装配从 bootstrap 收回 runtime/search adapter:tests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_app_module_bootstrap.pytests/unit/web_api/test_http_routes_runtime_dependencies.pytests/integration/test_web_api_smoke.py 已联合回归 49 passed,并额外通过 python -m py_compile apps/web_api/runtime/search_dispatch.py apps/web_api/bootstrap/runtime.py apps/web_api/bootstrap/app_module.py 语法检查;新增断言锁定 build_search_request_submitter() 统一绑定 AnalysisExecutionContext、research task service 与动态 dispatch hook,且 bootstrap_app_module() 不再手工拼 execution context。
    • 备注(2026-04-20):本轮继续把 shutdown/cleanup wiring 的重复装配收回 lifecycle runtime:tests/unit/web_api/test_system_lifecycle.pytests/unit/web_api/test_runtime_bootstrap.pytests/integration/test_web_api_system_controls.pytests/integration/test_web_api_smoke.py 已联合回归 45 passed,并额外通过 python -m py_compile apps/web_api/runtime/system_lifecycle.py apps/web_api/bootstrap/runtime.py 语法检查;新增断言锁定 build_process_cleanup_port() 同时服务 bootstrap cleanup handler 与 async shutdown,不再在两处平行维护 ProcessCleanupPort 约定。
    • 备注(2026-04-20):本轮继续收掉 bootstrap 侧仅作转发的 lifecycle dependency builder,并与前述 cleanup/search 收口一起补跑 tests/unit/web_api/test_system_lifecycle.pytests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_app_module_bootstrap.pytests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_http_routes_runtime_dependencies.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_system_controls.py;联合 crawler 最小 compat 壳回归后,当前组合回归为 79 passed

建议下一步顺序(按当前依赖收敛)

  1. 继续检查 bootstrap/search_hooks.py_APP_MODULE.search_hooks 这条 monkeypatch seam 是否还能进一步缩薄;当前 forum/search 的 runtime 模块级 wrapper 已清理完成,下一步更适合评估 app/bootstrap 侧兼容面是否还能继续收口而不破坏 smoke contract。
  2. 在 crawler/runtime path 侧继续评估 browser data / writable path 的归属,优先检查 utils/runtime_paths.pyservices/crawler/adapters/mediacrawler_adapter.py 是否仍存在双轨策略,等 backend/runtime 兼容壳进一步变薄后再决定是否收口行为。
  3. 在 P1 中继续沿 application service 主线补剩余编排入口,优先看 crawler/report 还有哪些 route/runtime 仍在做非 HTTP 适配之外的工作,避免在 P0 已收紧后又把 orchestration 逻辑回流到 backend/runtime。

验收标准

  • apps/web_api/app.py 体积显著下降,不再承载大部分实现。
  • 所有运行时状态来源可追溯到 runtime/
  • 系统启动、状态查询、引擎启动/停止、搜索分发仍然可用。

风险与注意事项

  • 注意避免循环导入,尤其是 app.pysocketioruntime/* 之间的双向依赖。
  • 注意 Flask app / socketio 初始化顺序;system_lifecyclelog_stream 后续若依赖 emit 能力,需要继续采用回调或显式注入,避免 runtime 反向 import Flask 入口。
  • 注意旧代码仍依赖模块级全局变量与函数名兼容;当前 search hook 兼容面已收敛到 _APP_MODULE.search_hooks,若后续继续收缩入口或替换 hook 实现,需要先锁定现有 monkeypatch 出口。
  • 当前 process_manager.py 的 cleanup 路径已收敛为 ProcessCleanupPort,但启动链路仍通过回调接收 emit_output;后续若继续对象化 process runtime,需要注意不要把 Socket.IO/Flask 入口反向耦合回 runtime。

3.2 建立统一任务模型

目标

统一研究任务主链路中的核心对象与状态机。

当前进展快照(2026-04-15 14:35)

  • 已在 services/shared/models/ 下新增 common.pyresearch_task.pycrawler_job.pyanalysis_run.pyreport_job.pymappers.py 以及 __init__.py
  • 当前统一模型采用 Pydantic v2,包含 ProgressInfoErrorInfoTaskMetadata 等公共基元,四类任务模型均保留 legacy_payload 以兼容旧结构渐进迁移。
  • 已补 map_legacy_research_task()map_crawler_history_entry() mapper 骨架,可将 backend/research_tasks.pybackend/crawler/state_store.py 的现有 JSON 结构投影到统一模型;AnalysisRun / ReportJob 先提供 stub mapper,为 P1 应用层接线预留入口。
  • 2026-04-20:services/shared/models/mappers.py 已继续补齐真实 map_analysis_run_record()map_report_task_record(),开始把 analysis persisted payload / report runtime payload 直接投影到统一模型;ReportJobQueryService 也已能在只有 runtime dict、尚无 unified_task 字段时回退走共享 mapper,再输出 ReportJobDTO
  • 2026-04-20:ResearchTaskService 本轮已开始直接消费 services/shared/events/services/shared/timeline/ 的最小骨架:create_task()set_task_status()sync_analysis_runtime_status()sync_report_runtime_status()transition_task() 现在都会产出标准 task lifecycle/timeline 记录,build_runtime_services() 也已默认注入 TaskTimelineTracker
  • 2026-04-21:map_legacy_research_task() 已继续把 crawler_keywords_text 映射进统一 metadata.extrasReportJobQueryService 也已把 /api/report/status|tasks|progress|result 的 runtime payload 统一经 ReportJobDTO/shared mapper 归一后再输出,同时保留顶层 status/progress/report_file_* 等兼容字段,避免 report/crawler 主链继续直接扩散裸 runtime dict / legacy_payload 读取。
  • 暂未改动现有 API 返回与前端消费路径,本次属于“模型骨架已落地、接线尚未开始”。

任务清单

  • services/shared/models/ 下新增:
    • research_task.py
    • crawler_job.py
    • analysis_run.py
    • report_job.py
    • 备注(2026-04-15 14:35):同时补充了 common.pymappers.py 与包级 __init__.py,统一承载公共字段和旧结构映射入口。
  • ResearchTask 定义统一字段:
    • id
    • title/brief
    • generated_query
    • status
    • progress
    • created_at
    • updated_at
    • crawler_job_id
    • analysis_run_id
    • report_job_id
    • metadata
    • last_action
    • error
    • 备注(2026-04-15 14:35):额外加入 legacy_payload 作为过渡期兼容字段,避免旧 JSON store 字段立即丢失。
  • CrawlerJob 定义统一字段:
    • id
    • research_task_id
    • platform
    • keywords
    • status
    • progress
    • started_at
    • finished_at
    • error
    • 备注(2026-04-15 14:35):当前额外保留 configresult_summarylegacy_payload,用于覆盖 crawler runtime/UI state 的现有结构。
  • AnalysisRun 定义统一字段:
    • id
    • research_task_id
    • engines
    • status
    • progress
    • partial_results
    • metrics
    • started_at
    • finished_at
    • error
    • 备注(2026-04-15 14:35):当前只落模型与 stub mapper,尚未绑定真实运行态来源。
  • ReportJob 定义统一字段:
    • id
    • research_task_id
    • status
    • output_path
    • output_format
    • started_at
    • finished_at
    • error
    • 备注(2026-04-15 14:35):当前额外补了 progressartifactslegacy_payload,便于后续报告引擎产物收口。
  • 统一状态枚举:
    • ResearchTaskStatus
    • CrawlerJobStatus
    • AnalysisRunStatus
    • ReportJobStatus
    • 备注(2026-04-15 14:35):枚举以“渐进兼容”为目标,已覆盖 draft/ready/running/completed/failed/cancelled 等主链路状态,并对旧状态值建立初版映射。
  • 梳理当前研究任务接口与状态存储结构,建立旧结构到新模型的 mapper。
    • 备注(2026-04-15 14:35):ResearchTaskStoreCrawlerUiStateStore 已有首版 mapper;分析与报告链路仍缺真实旧结构梳理,目前仅提供 stub。
    • 备注(2026-04-15 15:21):services/engines/report/flask_interface.py 已为旧 ReportTask 增加 to_model()/to_dto() 投影,并引入 ReportJobDTO/ReportJobListDTO 适配 /status/tasks/progress、SSE 事件载荷等边界输出;当前仍以 task_id 回填 research_task_id 做兼容占位,尚未与真实 research task 建立稳定关联。
    • 备注(2026-04-20):analysis/report 两条链路的共享 mapper 已不再停留在 stub:map_analysis_run_record()map_report_task_record() 已补齐状态、进度、错误、产物字段映射,并新增 tests/unit/shared/test_task_model_mappers.py 锁定 unified model 投影 contract。
    • 备注(2026-04-20):本轮又把 unified task model 的“状态变化事件”也接到了主链路上:ResearchTaskService 现已通过 TaskTimelineTracker 为 create/set_status/transition/sync_* 入口记录标准 lifecycle entry,避免 shared event/timeline 骨架继续只停留在未接线状态。
  • 将研究任务相关 API 返回逐步过渡为统一 task 视角。

    • 备注(2026-04-15 15:13):backend/research_routes.py 已切换为通过 services.application.research.ResearchTaskService 输出 ResearchTaskDTO/ResearchTaskListDTO/api/research/tasks 的读写响应开始携带 unified_task 与统一 progress/metadata/error 字段,同时继续保留旧 task_id/status_label/crawler_defaults 等兼容字段;任务激活入口仍暂时复用旧 store 的 activate(),后续可继续收敛为 application service 专用用例。
    • 备注(2026-04-20):ReportJobQueryService 当前已支持把 raw runtime dict 通过共享 mapper 回退转换为 ReportJobDTO,减少 report 链路继续扩散“裸 dict / 裸 runtime object”输出的情况;相关断言已补到 tests/unit/application/test_report_query_service.py
    • 备注(2026-04-21):本轮继续把 report query 读侧的“DTO 视图是否可用”条件分支收平:/api/report/status/api/report/tasks/api/report/progress/<task_id>/api/report/result/<task_id>/json 当前都会优先经过 ReportJobDTO/shared mapper 归一,再输出兼容 payload,并在响应中稳定附带 unified_task 供新链路消费。
    • 备注(2026-04-21):research task 资源接口这条主读链路也已基本切换到统一 task 视角:backend/research_routes.py 当前会通过 ResearchTaskService / ResearchTaskDTO 输出 /api/research/tasks/api/research-tasks/api/research-tasks/<id>,analysis/crawler/report 的 task-scoped 资源接口也统一先解析 ResearchTaskDTO 再委托各 query service 构建 payload。
  • [-] 前端核心流程页先兼容读取统一任务字段。

    • 备注(2026-04-15 15:13):已在 apps/web_ui/src/composables/useTaskViewModel.ts 新增最小 task-centered view model 骨架,聚合 ResearchTask / crawler / system-analysis / report 四段状态;当前 TaskCenterView.vue 已接入只读摘要卡作为统一入口,AnalyzeView.vueDeliverView.vue 仍保留现有直接 controller/composable 读取方式,但后续可直接切换到该 view model。

验收标准

  • 主链路可通过统一 task model 表达。
  • 旧状态结构不再继续扩散。
  • 新接口/新逻辑默认使用统一模型。

3.3 收口 shared 基础设施

目标

将 config / dto / errors / events / logging 的标准入口集中到 services/shared/

任务清单

  • [-] 梳理当前各引擎与 backend 中重复的配置读取方式。
    • 备注(2026-04-20):backend/crawler/managers.pyapps/engine_console/query_engine_streamlit_app.pymedia_engine_streamlit_app.pyinsight_engine_streamlit_app.py 已开始把直接 settings / reload_settings() 读取迁到 get_settings() / get_database_runtime_settings();当前剩余工作主要是继续盘点更深层 engine/runtime 内部的直接配置访问。
    • 备注(2026-04-21):本轮继续沿主线做小步收口:backend/crawler/routes.py/api/crawler/options 默认值读取已不再由 route 直接触碰 shared config,而是改由 CrawlerService.get_default_save_option() 统一暴露;同时 services/engines/forum/llm_host.py 也已从直接 import 模块级 settings 切到构造时调用 get_settings(),减少 engine 在模块导入阶段冻结 shared config 单例。
  • services/shared/config/ 中补齐统一 settings 访问边界。
    • 备注(2026-04-20):已新增 services/shared/config/access.py 与包级导出,补齐 get_settings()get_setting_value()read_settings_values()resolve_settings_write_env_file()DatabaseRuntimeSettings / get_database_runtime_settings() 等 helper;backend/config_admin.pybackend/crawler/routes.pybackend/crawler/runtime.py 已开始改走这条 shared config 边界。
    • 备注(2026-04-20):本轮继续把 shared config 边界落到 crawler / 单引擎入口:backend/crawler/managers.py 现已不再自己读取 shared config,而是通过 runtime/application helper 解析默认 save_option;三个 apps/engine_console/*_streamlit_app.py 入口也已改为在运行时按需调用 get_settings() / get_database_runtime_settings(),避免模块导入阶段继续 capture 全局 settings 单例。
    • 备注(2026-04-20):本轮也继续把这条 shared config 边界落到 Web API runtime adapter:apps/web_api/runtime/search_dispatch.py 中 Query/Media/Insight 的本地 agent builder 当前已全部改为调用时读取 get_settings() / get_database_runtime_settings(),不再直接 import 模块级 settings 单例。
    • 备注(2026-04-21):本轮又继续把 shared config access 往 crawler/forum 主链推了一步:CrawlerService 已新增 get_default_save_option() 作为 application 边界,route 侧只负责透传;ForumHost 也已切换到在实例初始化时读取 get_settings(),并补了定向单测锁定这条 access contract。
  • services/shared/errors/ 中新增标准错误模型:

    • AppError
    • EngineError
    • ExternalServiceError
    • ValidationError
    • 备注(2026-04-15 15:13):已先落 AppErrorExternalServiceError 与错误分类/上下文骨架,采用 Pydantic 共享模型以便渐进迁移;EngineErrorValidationError 先不提前细化,避免在现有错误语义尚未统一前制造重复类型。
  • services/shared/dto/ 中新增主链路 DTO:

    • ResearchTaskDTO
    • CrawlerJobDTO
    • AnalysisRunDTO
    • ReportJobDTO
    • 备注(2026-04-16):ResearchTaskDTOReportJobDTOAnalysisRunDTOCrawlerJobDTO 已落地;当前 backend/research_routes.py 上的 analysis 查询输出已切到 AnalysisRunDTO/AnalysisRunListDTO 组装 response payload,crawler 共享边界也已具备 CrawlerJobListDTO。剩余工作主要是让更多 route/resource 持续改用这些 DTO,而不是继续直接暴露 model/store 结构。
    • 备注(2026-04-21):ResearchTaskDTO 已补 get_crawler_defaults() / get_crawler_keywords_text() 轻量 accessor,把 crawler 默认参数与关键词文本的 legacy fallback 收回 DTO 边界;ReportJobDTO.to_response_item() 也已补齐 report_file_* / state_file_* / markdown_file_* 等 artifact flattening,并将顶层 progress 维持为兼容数值视图,便于旧前端继续消费。
  • services/shared/events/ 中新增统一事件 envelope。

    • 备注(2026-04-15 15:13):已新增传输无关的 EventEnvelopeTaskLifecycleEvent 骨架,统一 event_type/task_id/source/timestamp/payload 并补 trace/correlation/version 字段,为后续 SSE/Socket.IO/日志桥接和统一任务状态流转预留契约。
  • 建立最小可用的 task timeline 聚合入口。

    • 备注(2026-04-15 15:21):已新增 services/shared/timeline/,提供 TaskTimelineStore / InMemoryTaskTimelineStoreTaskTimelineTracker;当前先把 TaskLifecycleEvent 聚合为可查询 timeline entry,并同步镜像到 observability recorder,runtime/application 后续可通过依赖注入挂载,暂未直接接入现有 API 或持久化存储。
    • 备注(2026-04-20):本轮已把这条 shared timeline 骨架正式挂到 ResearchTaskServicebuild_runtime_services() 当前会默认创建 TaskTimelineTracker 并注入 application service,研究任务创建、显式状态回写、analysis/report runtime 同步与合法状态迁移都已开始产出标准 lifecycle/timeline 记录。
  • 统一事件字段:

    • event_type
    • task_id
    • source
    • timestamp
    • payload
    • 备注(2026-04-15 15:13):上述核心字段已在 EventEnvelope 固化,同时补充 event_idtrace_idcorrelation_idversion 以兼容后续 observability 与跨边界事件传输。
  • [-] 统一主链路日志上下文格式,至少包含:

    • task_id
    • engine
    • stage
    • action
    • 备注(2026-04-20):已新增 services/shared/logging/context.py 与包级导出,提供 build_log_context() / bind_logger();当前 apps/web_api/app.pyapps/web_api/factory.py 已开始通过 shared helper 绑定 source 与启动参数,后续仍需继续把 task/engine/stage/action 逐步挂到主链路日志上。
    • 备注(2026-04-21):本轮已先把研究任务主链的统一日志上下文接到最小闭环上:services/application/research/task_service.py 现在会在 lifecycle/timeline 记录时统一绑定 task_id/stage/action/operationbackend/research_routes.py 也已在 create/activate/task-scoped resource 的错误路径补齐 task_id/action 上下文;这条 shared logging contract 已从入口启动日志延伸到一条真实业务主链。
  • [-] 识别并迁移各 engine 中的重复 shared 能力实现。

    • 备注(2026-04-20):本轮已先从最浅层入口着手,把三个 engine_console Streamlit 入口的重复 settings 读取收口到 shared config access helper;后续可继续沿 engine 内部 runtime/config helper 往下推进,避免再次在各引擎里复制 settings 解析逻辑。
    • 备注(2026-04-21):本轮已继续把 forum engine 的 shared config 读取往统一 helper 迁移:services/engines/forum/llm_host.py 不再直接依赖模块级 settings 单例,而是与其它主链入口一样改走 services.shared.config.access.get_settings()
    • 备注(2026-04-20):本轮已补跑 tests/unit/shared/test_config_access.pytests/unit/shared/test_logging_context.pytests/unit/shared/test_task_model_mappers.pytests/unit/backend/test_config_admin.pytests/unit/backend/test_crawler_runtime.pytests/unit/backend/test_crawler_runtime_service.pytests/unit/backend/test_crawler_routes.pytests/unit/application/test_crawler_service.pytests/unit/application/test_report_query_service.pytests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_system_lifecycle.pytests/integration/test_web_api_factory_smoke.pytests/integration/test_web_api_smoke.py,当前组合回归为 102 passed;warning 仍仅包含既有 eventlet deprecation、datetime.utcnow() deprecation 与 .pytest_cache 权限提示。
    • 备注(2026-04-20):本轮另补跑 tests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_routes.pytests/unit/backend/test_crawler_runtime.pytests/unit/backend/test_crawler_runtime_service.pytests/unit/shared/test_config_access.py,组合回归为 50 passed;三个 engine_console 入口也已额外通过 py_compile 语法检查。
    • 备注(2026-04-20):本轮围绕 shared timeline 接线与 crawler writable path contract 又补跑了 tests/unit/application/test_research_task_service.pytests/unit/web_api/test_runtime_bootstrap.pytests/integration/test_web_api_system_controls.pytests/unit/shared/test_task_timeline_tracker.pytests/unit/backend/test_mediacrawler_adapter.pytests/unit/backend/test_crawler_runtime_service.py,当前组合回归为 47 passed;warning 仍仅包含既有 eventlet deprecation 与 .pytest_cache 权限提示。
    • 备注(2026-04-21):本轮围绕 crawler options 收口与 forum config access 又补跑 tests/unit/application/test_crawler_service.pytests/unit/backend/test_crawler_routes.pytests/unit/shared/test_forum_llm_host.pytests/integration/test_web_api_smoke.py,组合回归为 59 passed;同时 backend/crawler/routes.pyservices/application/crawler/crawler_service.pyservices/engines/forum/llm_host.py 已额外通过 py_compile 语法检查。

验收标准

  • 新代码不再新增重复 config/dto/error/event 结构。
  • 主链路已有统一 shared 类型可复用。

3.4 建立运行时 registry/store

目标

将局部全局变量式的状态管理改为显式运行时对象。

当前进展快照(2026-04-17 16:05)

  • 已新增 apps/web_api/runtime/process_registry.py,落地 ProcessRuntimeRegistry 与默认 PROCESS_RUNTIME_REGISTRY,统一承载 insight/media/query/forum 的进程、端口、状态与输出快照。
  • apps/web_api/runtime/system_state.py 已从函数式模块状态升级为 SystemStateRegistry,并保留 get_system_state() / set_system_state() / prepare_system_start() / mark_shutdown_requested() 兼容出口。
  • http_routes.pysocket_events.py 已改为通过注入的 registry 读取 /api/status/api/system/status 与 Socket 状态快照;system_lifecycle.pyforum_runtime.py 也已开始通过 registry 读写运行态。
  • bootstrap/runtime.py 现已将 Forum 启停、Forum 日志读取以及普通 app 日志读写以 callable 形式注入 HttpRouteDependencieshttp_routes.py 不再直接耦合这些 runtime 模块实现。
  • SystemLifecycleService 已补齐 SystemLifecycleDependencies,启动/关机关联的 streamlit_scripts、Forum 启停、状态刷新、清理编排、关机日志与子进程探测都改为经依赖 bundle 消费,默认 wiring 由 bootstrap/runtime.py 承接。
  • 已新增 apps/web_api/runtime/task_runtime_store.py,落地 TaskRuntimeStore 与默认 TASK_RUNTIME_STORE,统一承载 task-scoped 的运行期投影(status/generated_query/analysis_run_id/last_action/progress/error/partial_results/metrics)。
  • build_runtime_services() / RuntimeServices 已开始显式注入 task_runtime_storeAnalysisService 当前会在“无可用引擎 / 接单 / 运行中 / 部分成功 / 失败”各阶段同步回写 TaskRuntimeStore,形成独立于 durable ResearchTaskStore / AnalysisRunStore 的运行态快照。
  • 已新增 ProcessManager 类,当前 bootstrap/runtime.py 已会为 RuntimeServices 显式构造并注入该对象;route/socket/system lifecycle 这三条 wiring 已开始消费 ProcessManager 实例方法,而模块级 start_streamlit_app() / stop_streamlit_app() / check_app_status() / cleanup_processes*() 仅作为兼容 wrapper 保留。
  • 已新增 apps/web_api/runtime/engine_registry.py,落地 EngineRuntimeRegistry 与默认 ENGINE_RUNTIME_REGISTRY,统一承载引擎静态运行元数据(launch_modescript_pathlocal_runneravailable);search_dispatch.py 的本地引擎分发现已改为只从该 registry 读取 runner 元数据。
  • bootstrap/runtime.py / RuntimeServices / build_system_lifecycle_dependencies() 现已显式注入 engine_registryProcessManager 默认使用的 Streamlit 脚本清单也改为从 EngineRuntimeRegistry 派生,避免再维护平行引擎表。
  • tests/conftest.py 现在会在 reload_web_api_app() 前重置 EngineRuntimeRegistryProcessRuntimeRegistrySystemStateRegistryTaskRuntimeStore,避免组合回归时 runtime 单例状态互相污染。
  • 已新增 ForumRuntime / build_forum_runtime()build_runtime_services() 现会在未显式注入 forum runtime 时,根据当前 process_registry 构造同源 forum runtime,避免 forum 状态继续写回到默认单例 registry。RuntimeServices 当前也已显式持有 forum_runtime 字段,route / lifecycle 装配使用的是同一对象的 bound methods。
  • forum_runtime.py 的 log history helper 也已修复 file.tell() 游标读取问题,并恢复对应 helper contract test。
  • 2026-04-20:system_lifecycle.pysearch_dispatch.pybootstrap/runtime.py 已继续把默认依赖从“定义时绑定全局单例”改为“运行时显式解析/注入优先”;cleanup_handler 现在也会回写当前注入的 SystemStateRegistry,不再偷偷落回全局 set_system_state(...)
  • 2026-04-20:SystemLifecycleService 当前已不再依赖导入时冻结的 DEFAULT_SYSTEM_LIFECYCLE_DEPENDENCIES 常量,改为在构造时通过 build_default_system_lifecycle_dependencies(...) 基于当前注入的 process_registry 解析默认 bundle;search_dispatch.py 中 Query/Media/Insight 的本地 agent builder 也已切到在调用时读取 get_settings() / get_database_runtime_settings(),避免 runtime 模块在 import 阶段继续 capture 全局 settings。
  • 2026-04-20:process_manager.py 中的 STREAMLIT_SCRIPTS 兼容导出已改为实时视图,不再在模块导入阶段冻结 EngineRuntimeRegistry 的 script snapshot;ProcessManager 在未显式传入 streamlit_scripts 时也统一复用这条运行时解析 helper。
  • 2026-04-20:bootstrap/runtime.py 中的 build_system_lifecycle_dependencies() 已改为直接委托 runtime/system_lifecycle.py 的默认 builder,避免 lifecycle 默认 wiring 继续维持两份平行实现。
  • 2026-04-20:forum_runtime.py 曾先把旧模块级 wrapper 改为调用时通过 build_forum_runtime() 解析默认 runtime;search_dispatch.py 则新增 build_analysis_execution_context()_resolve_analysis_service(),把 search runtime 的默认依赖解析进一步集中到 helper,为后续彻底移除模块级 wrapper 做准备。
  • 2026-04-20:SystemStateRegistry 本轮已新增 clear_shutdown_request()tests/conftest.pyreload_web_api_app() 夹具也已改为通过公有 API 清理 shutdown 标记,不再直接写私有 _state["shutdown_requested"]
  • 2026-04-21:本轮继续把状态查询 contract 从 route/socket 侧收口到 runtime facade:已新增 apps/web_api/runtime/status_service.pyRuntimeStatusService/api/status/api/system/status 与 Socket.IO request_status 现都改为通过同一状态服务查询 payload,不再分别在接口层拼 check_status + registry.snapshot() 或直接读取 system_state_registryHttpRouteDependencies 里未再使用的 research_task_service 也已移除。
  • 2026-04-21:本轮继续把 search_dispatch.py 朝与 forum_runtime.py 对称的对象化模式推进:已新增 SearchDispatchRuntimebuild_search_dispatch_runtime(),并在后续轮次移除剩余模块级 search wrapper;原先分散在 wrapper 内部的 _resolve_analysis_service(...) + execution-context 兜底逻辑现已沉到 runtime object / helper builder。
  • 2026-04-21:本轮又继续把这层 search runtime 真正接进主链路 wiring:RuntimeServices 已显式持有 search_dispatch_runtimebootstrap/app_module.pysearch_hooks 当前也直接暴露同一个 search runtime 对象;这样 app-module search hook、route submitter 与 search runtime object 现在共享同一 service graph,而不是“一条走 analysis service、一条走 wrapper/runtime builder”。

任务清单

  • 新建 SystemStateRegistry
    • 备注(2026-04-15 16:05):runtime/system_state.py 已落地 SystemStateRegistry,并由 app.pysystem_lifecycle.pyhttp_routes.py 通过显式注入或兼容函数接线。
  • 新建 TaskRuntimeStore
    • 备注(2026-04-17):已新增 runtime/task_runtime_store.py,提供 clone-safe 的 task runtime snapshot / upsert / clear 能力;当前已先接入 AnalysisService 写侧,用于同步 analysis_run_id、运行阶段、进度、错误与 partial results,不改动现有 API 读模型。
  • 新建 ProcessManager 类封装进程状态与操作。
    • 备注(2026-04-17):已在 runtime/process_manager.py 中补 ProcessManager,统一封装 describe_running_children()、Streamlit 启停、健康检查、启动等待以及串行/并发 cleanup;bootstrap/runtime.py 现已改为优先将 ProcessManager 实例注入 route/socket/system lifecycle wiring,相关对象级单测与 Web API smoke 已回归通过。
  • 新建 EngineRegistryEngineRuntimeRegistry,统一记录引擎可用性与运行状态。
    • 备注(2026-04-17):已新增 runtime/engine_registry.py,提供 EngineRuntimeRegistry 的 clone-safe snapshot、可用性切换与 reset 能力;search_dispatch.py 的本地 runner 选择、bootstrap/runtime.py 的 runtime service 装配以及 process_manager.py 的默认 Streamlit 脚本来源都已切到从该 registry 派生。引擎 live process status 仍继续由 ProcessRuntimeRegistry 负责,避免双写。
  • /api/status/api/system/status 等状态接口改为从 registry/store 读取。
    • 备注(2026-04-15 16:05):http_routes.py 已通过 process_registry.status_snapshot()system_state_registry.snapshot() 返回状态;socket_events.py 也已通过 registry 推送状态快照。
  • 清理入口文件中散落的状态读写逻辑。
    • 备注(2026-04-17):runtime service 构造与 route dependency 拼装已迁入 apps/web_api/bootstrap/runtime.pysystem_lifecycle.py 也已切到依赖 bundle;当前 ProcessManagerTaskRuntimeStoreEngineRuntimeRegistryForumRuntime 都已通过 runtime services 显式注入。现阶段更明显的残余已经转向 search monkeypatch seam 与少量默认注入兜底,而不是大块入口实现仍滞留在 app.py
    • 备注(2026-04-20):SystemLifecycleServicebuild_runtime_services()build_system_lifecycle_dependencies() 与 search dispatch 相关 builder 当前都已调整为 None -> resolve default 模式,避免模块导入阶段 capture 默认单例;相关断言已补到 tests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_system_lifecycle.py
    • 备注(2026-04-20):本轮进一步移除了 SystemLifecycleService 对导入时默认依赖 bundle 的隐式绑定,并让 search_dispatch.py 的本地 agent builder 改为运行时读取 shared config helper;同时新增单测锁定“构造时解析默认 lifecycle bundle”与“Insight/Query builder 使用 shared settings/database helper”两条 contract。
    • 备注(2026-04-20):本轮继续把同类尾巴从 process_manager.pyforum_runtime.py 中收掉:ProcessManager / build_process_manager() / wrapper helpers 以及 ForumRuntime / build_forum_runtime() 现在都不再通过默认参数冻结 PROCESS_RUNTIME_REGISTRY,而是改为运行时读取当前模块上的 registry;FORUM_RUNTIME 这类死兼容导出也已在后续轮次移除。
    • 备注(2026-04-20):本轮继续清理运行时默认来源的散点:process_manager.pySTREAMLIT_SCRIPTS 已改为实时兼容视图,不再冻结 engine registry snapshot;bootstrap/runtime.py 里的 lifecycle dependency 装配也已统一委托给 runtime/system_lifecycle.py 的默认 builder,避免同一套默认 wiring 在 bootstrap 与 runtime 侧各维护一份。
    • 备注(2026-04-20):本轮继续把 forum/search 这两块 runtime 的默认兜底压成单点:forum 与 search 都先经历了一轮“调用时解析默认对象/依赖”的收口,避免 wrapper 内部继续散落多份默认单例逻辑。
    • 备注(2026-04-20):本轮又把 runtime state reset 的最后一处私有字段写入收回到 registry API:SystemStateRegistry.clear_shutdown_request() 已落地并接入 tests/conftest.py,reload fixture 不再越过封装直接操作 _state["shutdown_requested"]
    • 备注(2026-04-21):本轮又收掉一层 route/socket 侧的散落状态读取:http_routes.pysocket_events.py 现统一依赖 RuntimeStatusServiceget_process_status_snapshot() / get_system_status_payload(),不再直接持有 ProcessRuntimeRegistrySystemStateRegistry 或额外的 check_app_status callback;相关 wiring 已同步收回 bootstrap/runtime.py
    • 备注(2026-04-21):本轮又把 search runtime 这一层再对象化了一步:search_dispatch.py 当前已新增 SearchDispatchRuntime,并移除了剩余模块级 search wrapper;这样 search 侧兼容面已不再停留在 runtime 模块级入口,而是转移到 app/bootstrap 的 monkeypatch seam。
    • 备注(2026-04-21):本轮继续把 search runtime 从“模块里有对象”推进到“bootstrap 真正复用同一个对象”:RuntimeServices 当前已显式持有 search_dispatch_runtimebootstrap_app_module()search_hooks 也直接暴露这同一个 runtime 对象;主链路上的 search wiring 已与 forum/status 一样优先消费显式 runtime object。
    • 备注(2026-04-21):本轮又继续把 app/bootstrap 侧这条 monkeypatch seam 收薄:SearchHookContainer / build_search_hook_container() 已移除,tests/unit/web_api/test_search_hooks.py 当前改为直接针对通用 hook source 验证动态 binding 行为;兼容 seam 现已更明确地收敛为 _APP_MODULE.search_hooks + build_search_hook_bindings(...)

验收标准

  • 系统运行态来源唯一。
    • 备注(2026-04-17):EngineRuntimeRegistryProcessRuntimeRegistrySystemStateRegistryTaskRuntimeStoreForumRuntime 都已落地并接入 runtime wiring;search_dispatch.py 现只读 engine/process registry,process_manager.pyprocesses 兼容出口也已移除,bootstrap/runtime.py / SystemLifecycleService / route / socket wiring 现也优先通过显式注入的 ProcessManagerEngineRuntimeRegistryForumRuntimeTaskRuntimeStore 工作。
    • 备注(2026-04-20):runtime 读写路径已进一步避免“定义时绑定默认单例”;SystemLifecycleService 的默认 lifecycle dependencies、search_dispatch.py 的 shared settings 读取,以及 forum/search 缺省依赖解析都已改为调用时或 builder 侧解析。
    • 备注(2026-04-21):本轮已把 HTTP route / Socket.IO 的状态查询统一压到 RuntimeStatusService,并把 forum/search 的模块级 runtime wrapper 彻底移除;当前生产代码里的运行态读写来源已经统一可追溯到 apps/web_api/runtime/*,剩余尾项主要是 app/bootstrap monkeypatch seam 和少量 builder 兼容注入形式,而不再是状态来源分散。
  • 状态查询接口不再拼接多个散变量。
    • 备注(2026-04-15 16:05):/api/status/api/system/status 与 Socket 状态推送已完成切换,但其他运行态读取路径仍有渐进兼容逻辑。
    • 备注(2026-04-21):本轮已新增 RuntimeStatusService 并接入 http_routes.pysocket_events.pybootstrap/runtime.py/api/status/api/system/status、Socket request_status 现在都会统一通过同一 facade 生成状态 payload;接口层已不再自行拼接多个散落状态变量。
  • 运行时对象可被单独测试。
    • 备注(2026-04-17):仓库已补 TaskRuntimeStoreEngineRuntimeRegistryProcessManagerForumRuntime、registry、search dispatch、system lifecycle、bootstrap/runtime 单测,并重新跑通 Web API 最小冒烟测试;SystemLifecycleServiceTaskRuntimeStoreEngineRuntimeRegistryForumRuntimeProcessManager 现在都可通过 fake dependencies / isolated store 独立测试启动、幂等关机、异步清理协作与 analysis runtime 投影,后续仍可继续补更多运行时协作场景的 contract test。
    • 预备断言(待主线程接口落地后补):build_runtime_services() 应显式构造 forum runtime service;build_http_route_dependencies()build_system_lifecycle_dependencies() 应消费同一对象的 bound methods;隔离 process_registry 的 factory/integration smoke 应能覆盖 forum start/stop 后状态只回写注入 registry、而非默认单例。
    • 备注(2026-04-20):本轮继续补跑 tests/unit/web_api/test_system_lifecycle.pytests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_runtime_bootstrap.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.py,组合回归为 45 passed;新增断言覆盖 runtime 默认依赖的延迟解析和 search builder 对 shared helper 的调用路径。
    • 备注(2026-04-20):本轮又新增 tests/unit/web_api/test_process_manager_cleanup.pytests/unit/web_api/test_forum_runtime.py 中对“未显式注入时跟随模块当前 registry”的断言,并联合 app bootstrap / crawler runtime / integration smoke 重新回归为 58 passed
    • 备注(2026-04-20):本轮又新增 tests/unit/web_api/test_process_manager_cleanup.pytests/unit/web_api/test_runtime_bootstrap.py 与 integration smoke 中对“STREAMLIT_SCRIPTS 跟随当前 engine registry”“bootstrap lifecycle dependency builder 统一委托 runtime 默认 builder”“app.py 不再导出旧兼容符号”的断言,相关定向回归为 57 passed
    • 备注(2026-04-20):本轮继续补 tests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_forum_runtime.py,先锁定 forum/search 旧兼容壳向 runtime object / helper 收口的 contract;这些断言已在后续轮次随着模块级 wrapper 移除而同步收敛。联合 smoke/crawler 相关回归后组合结果为 64 passed
    • 备注(2026-04-20):本轮又补 tests/unit/web_api/test_system_lifecycle.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.py,锁定 SystemStateRegistry.clear_shutdown_request() 的 public contract 与 reload fixture 不再直改私有 _state;相关定向回归为 37 passed
    • 备注(2026-04-21):本轮新增 tests/unit/web_api/test_http_routes_runtime_dependencies.pytests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_socket_events.pytests/integration/test_web_api_system_controls.py / tests/integration/test_web_api_smoke.pyRuntimeStatusService 接线路径的断言,并联合回归为 49 passed;同时已通过 python -m py_compile apps/web_api/runtime/status_service.py apps/web_api/bootstrap/runtime.py apps/web_api/interfaces/http_routes.py apps/web_api/interfaces/socket_events.py ... 的语法检查。
    • 备注(2026-04-21):本轮又补充并收敛了 tests/unit/web_api/test_search_dispatch.py 中对“dispatch_search_request() / resolve_search_query() 在调用时解析默认 search runtime”的断言,并联合 tests/unit/web_api/test_forum_runtime.pytests/unit/web_api/test_forum_runtime_helpers.pytests/unit/web_api/test_runtime_bootstrap.pytests/integration/test_web_api_system_controls.py 回归为 30 passed;同时已通过 python -m py_compile apps/web_api/runtime/forum_runtime.py apps/web_api/runtime/search_dispatch.py tests/unit/web_api/test_forum_runtime.py tests/unit/web_api/test_search_dispatch.py 的语法检查。
    • 备注(2026-04-21):本轮又补 tests/unit/web_api/test_app_module_bootstrap.pytests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_search_dispatch.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_system_controls.py,锁定 RuntimeServices / bootstrap_app_module() 开始显式复用同一个 search_dispatch_runtime;相关组合回归为 49 passed,并额外通过 python -m py_compile apps/web_api/bootstrap/runtime.py apps/web_api/bootstrap/app_module.py apps/web_api/runtime/search_dispatch.py ...

3.3 提前收敛 services/crawler/ 边界

目标

在不破坏现有 mindspider / backend/crawler 调用的前提下,先建立 crawler 域接口与第三方适配层骨架,为后续 application 层收口铺路。

当前进展快照(2026-04-15 14:41)

  • 已新增 services/crawler/domain/,落地 entities.pycontracts.pyservices.py 与包级导出,明确 CrawlerProviderCrawlerRuntimePathsMediaCrawlerContractCrawlerProviderLocator 等边界类型。
  • 已将 services/crawler/adapters/mediacrawler_adapter.py 升级为 MediaCrawlerAdapter,使其实现 domain contract,同时继续兼容 get_mediacrawler_paths() / ensure_mediacrawler_available() 这些旧调用入口。
  • 已更新 services/crawler/README.mdservices/crawler/adapters/README.md,明确 vendor/mediacrawler/ 只能经 adapters 暴露,不应被业务层直接引用。
  • 当前 services/crawler/mindspider/backend/crawler/runtime.py 已通过 adapter 访问 vendor 路径,但配置文件改写、依赖安装、运行时环境拼装仍分散在 mindspider / backend 中,尚未收敛为 application service。
  • 2026-04-20:已新增 services/crawler/application/mediacrawler_runtime.py 与包级导出,补齐 MediaCrawlerRuntimeService 最小骨架,开始统一承接 runtime env 组装、依赖安装与 db_config.py / base_config.py 写入;backend/crawler/runtime.pybackend/crawler/managers.pyservices/crawler/mindspider/main.pyservices/crawler/mindspider/DeepSentimentCrawling/platform_crawler.py 已开始改走该 application boundary。
  • 2026-04-20:MediaCrawlerRuntimeService 本轮继续补了 resolve_save_data_option()backend/crawler/managers.py 已不再直接从 shared config 读取数据库运行时配置来推导默认 save_option,而是统一通过 backend/crawler/runtime.py -> services/crawler/application 这条 runtime/application 边界解析。
  • 2026-04-20:MediaCrawlerRuntimeService 本轮继续新增 MediaCrawlerCommandSpec / build_crawl_command_spec()CrawlTaskManager.start() 的长串 crawl CLI 参数拼装与 canonical save_option 解析现已进一步收回到 runtime service,经 backend/crawler/runtime.py 兼容壳调用,manager 仅保留 payload 归一化、history/UI state 与子进程生命周期管理。
  • 2026-04-20:MediaCrawlerRuntimeService 本轮继续新增 build_login_status_command_spec()build_login_command_spec()LoginTaskManager.check_login() / start_login() 当前也已改为消费 runtime service 返回的 canonical command spec,manager 侧不再自己展开 status/login CLI 参数。
  • 2026-04-20:MediaCrawlerRuntimeService 本轮继续新增 build_completed_process_kwargs()build_streaming_process_kwargs()backend/crawler/managers.py 当前已统一通过这些 helper 构建 login status / login / crawl 三条链路的 subprocess kwargs,manager 侧剩余的 runtime 细节已进一步集中到 execution context 消费、进程生命周期控制与事件/UI state 解析。
  • 2026-04-20:backend/crawler/runtime.py 本轮继续压成最小 compat 壳:PROJECT_ROOTMEDIACRAWLER_PATHSRUNTIME_PATHRUNTIME_CWDbuild_runtime_env()build_runtime_command()resolve_save_data_option()build_execution_context() 这些仅剩历史兼容或测试用途的门面当前已移除,只保留 managers 仍在消费的 build_*command_spec()build_*process_kwargs() helper,以及 EVENT_PREFIX / MAX_LOG_LINES / MEDIACRAWLER_RUNTIME_SERVICE 三个最小导出。
  • 2026-04-20:MediaCrawlerAdapter 本轮继续统一了 crawler writable path contract:browser_data 默认已改为走 utils/runtime_paths.py 中的 CRAWLER_BROWSER_DATA_DIR,不再默认落到 vendor 根目录;MediaCrawlerRuntimeService 也已显式暴露 get_browser_data_dir(),相关默认路径/override contract 已补到 tests/unit/backend/test_mediacrawler_adapter.pytests/unit/backend/test_crawler_runtime_service.py
  • 2026-04-21:MediaCrawlerRuntimeService 本轮继续把这条 writable path contract 往真实运行时推进:build_runtime_env() 现已显式导出 BETTAFISH_CRAWLER_BROWSER_DATA_DIRMEDIACRAWLER_USER_DATA_DIR_PATTERNwrite_base_config() 也会同步把 vendor USER_DATA_DIR 改写为 runtime-managed absolute path pattern。这样 MediaCrawler 各平台里通过 config.USER_DATA_DIR % platform 派生的持久化浏览器 profile 路径,当前已开始默认落到 var/crawler/browser_data/...,而不是继续回到 vendor 工作目录。
  • 2026-04-21:本轮继续把这条 contract 打到 vendor 实际消费层:vendor/mediacrawler/tools/utils.py 已新增统一的 browser user-data-dir helper,tools/cdp_browser.pymedia_platform/xhs/core.py 现都改为通过该 helper 解析 profile 目录,不再各自硬编码 cwd/browser_data/...。这样普通持久化上下文与 CDP 分支现在都会优先消费 runtime/application 写回的绝对 USER_DATA_DIR pattern,并仅在未提供 runtime-managed 路径时回退到 vendor 侧相对目录。

任务清单

  • 建立 services/crawler/domain/ 骨架:
    • entities.py
    • contracts.py
    • services.py
    • __init__.py
    • 备注(2026-04-15 14:41):当前仅承载 provider 边界与路径值对象,不引入行为性编排,避免过早牵动现有 crawler 流程。
  • 收敛 services/crawler/adapters/
    • vendor/mediacrawler/ 提供稳定 adapter 入口
    • 保持旧函数式调用兼容
    • 备注(2026-04-15 14:41):MediaCrawlerAdapter 已实现 MediaCrawlerContract,现有模块仍可继续使用 get_mediacrawler_paths() / ensure_mediacrawler_available()
  • [-] 盘点并记录当前耦合点:
    • services/crawler/mindspider/DeepSentimentCrawling/platform_crawler.py 仍负责直接改写 vendor 配置文件
    • services/crawler/mindspider/main.py 仍负责依赖安装与环境准备
    • backend/crawler/runtime.py 仍直接以 adapter 返回路径组装运行时环境
    • 备注(2026-04-15 14:41):目前是“路径解析已隔离,运行时行为未隔离”的状态,适合下一步补 application/
  • [-] 新建 services/crawler/application/,把以下能力从 mindspider/ / backend/crawler/ 迁出:
    • vendor 配置写入
    • 依赖安装
    • 运行时环境变量组装
    • crawl job 启停编排
    • 备注(2026-04-20):MediaCrawlerRuntimeService 已落地并接管运行时环境变量组装、依赖安装、db_config.pybase_config.py 写入;crawler job 启停编排仍主要在 manager/service 层,后续可继续向 application service 收口。
    • 备注(2026-04-20):本轮继续沿 application boundary 补齐了可复用 runtime helper:MediaCrawlerRuntimeService 当前已显式提供 get_database_runtime_settings()get_default_save_data_option()build_async_database_url()build_python_command()mindspider/main.pyDeepSentimentCrawling/platform_crawler.pybackend/crawler/runtime.py 已开始消费这组 helper,而不是继续各自重复推导 DB/save option/runtime 命令细节。
    • 备注(2026-04-20):本轮又把 CrawlTaskManager.start() 中默认 save_option 的推导收回到 application/runtime 边界:MediaCrawlerRuntimeService.resolve_save_data_option() 统一处理“显式值优先,否则回退默认值”,backend/crawler/managers.py 已不再直接 import shared config helper。
    • 备注(2026-04-20):本轮也补强了 manager/runtime contract 测试:tests/unit/backend/test_crawler_managers.py 当前已锁定 LoginTaskManager / CrawlTaskManager 通过 build_execution_context() 透传 runtime cwd/env,避免 manager 侧再回退到自己拼接运行时上下文。
    • 备注(2026-04-20):本轮继续把 CrawlTaskManager.start() 的运行时命令细节收回到 MediaCrawlerRuntimeService:新增 build_crawl_command_spec() 后,backend/crawler/managers.py 不再自己展开 crawl CLI 参数或默认 save_option,而是消费 runtime service 返回的 canonical command spec;tests/unit/backend/test_crawler_runtime_service.pytests/unit/backend/test_crawler_managers.py 也同步改为锁定这条委托链路。
    • 备注(2026-04-20):本轮继续把 login 相关 runtime 命令细节收回到 MediaCrawlerRuntimeService:新增 build_login_status_command_spec()build_login_command_spec() 后,LoginTaskManager.check_login() / start_login() 不再直接手写 status/login CLI 参数;相关 contract 已补到 tests/unit/backend/test_crawler_runtime_service.pytests/unit/backend/test_crawler_managers.py
    • 备注(2026-04-20):本轮也开始把 crawler 的 runtime writable directory 语义收回到 application/adaptor 边界:browser_data 默认现已统一走 CRAWLER_BROWSER_DATA_DIR,并保留显式 override 入口供测试/特殊部署使用;当前仍待后续进一步确认 vendor 真实运行时是否完全消费这一路径。
  • [-] 进一步压缩 mindspider/ 对 vendor 文件结构的认知:
    • 避免业务代码直接拼接 config/*.pyrequirements.txt 等路径
    • 通过 adapter / application 返回显式能力对象
    • 备注(2026-04-20):mindspider/main.pyplatform_crawler.py 当前已开始复用 MediaCrawlerRuntimeService,不再各自重复维护依赖安装与 vendor 配置写入逻辑;仍有部分 vendor 目录认知散落在 legacy 调用路径中,尚未完全去除。
    • 备注(2026-04-20):本轮 platform_crawler.py 已进一步把默认 save_data_option 判断收口到 MediaCrawlerRuntimeService.get_default_save_data_option()backend/crawler/runtime.py 也继续通过 runtime service 暴露 execution context / runtime command,不再在 backend 侧展开 DB/save option 推导逻辑。
    • 备注(2026-04-20):本轮 backend/crawler/managers.py 也开始通过 resolve_save_data_option() 使用同一条 runtime/application 边界,不再在 manager 侧自己 reload shared settings。
    • 备注(2026-04-20):本轮 backend/crawler/runtime.py 又进一步退化为兼容壳:crawl 启动命令当前也改为通过 build_crawl_command_spec() 暴露给 manager,而不是让 manager 继续手写 vendor runtime 参数序列。
    • 备注(2026-04-20):本轮 backend/crawler/runtime.py 继续沿同一方向退化为兼容壳:login/status 命令当前也改为通过 build_login_command_spec() / build_login_status_command_spec() 暴露给 manager,而不是继续在 manager 侧手写 runtime 参数序列。

验收标准

  • services/crawler/domain/services/crawler/adapters/ 已具备可持续扩展的最小骨架。
  • 现有 crawler 相关调用点未被破坏,旧入口仍可使用。
  • [-] mindspider/backend/crawler/ 不再直接承担第三方 vendor 运行时行为。
    • 备注(2026-04-20):backend/mindspider 已开始改为经 services/crawler/application 访问 MediaCrawler runtime 能力,但 crawl job orchestration 仍未完全从 legacy 模块抽离。
    • 备注(2026-04-20):本轮已补跑 tests/unit/backend/test_crawler_runtime.pytests/unit/backend/test_crawler_runtime_service.py10 passed,并额外通过 py_compile 校验 backend/crawler/runtime.pyservices/crawler/application/mediacrawler_runtime.pyservices/crawler/mindspider/main.pyservices/crawler/mindspider/DeepSentimentCrawling/platform_crawler.py;说明这条 application-boundary 收口在当前工作区内已保持兼容。
    • 备注(2026-04-20):本轮 CrawlTaskManager.start() 的 crawl command 构建已继续迁入 MediaCrawlerRuntimeService,当前 manager 侧残留的运行时细节已进一步收缩到 LoginTaskManager.check_login() / start_login() 的 status/login 命令拼装与真正的进程生命周期管理;相关定向回归为 tests/unit/backend/test_crawler_runtime_service.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_routes.pytests/integration/test_web_api_factory_smoke.py -k crawler22 passed
    • 备注(2026-04-20):本轮又补跑 tests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_runtime_service.py,锁定 manager 默认 save_option 解析也已通过 runtime/application 边界完成;相关两组定向回归为 1 passed + 9 passed,并额外通过 py_compile 校验 backend/crawler/managers.pybackend/crawler/runtime.pyservices/crawler/application/mediacrawler_runtime.py
    • 备注(2026-04-20):本轮继续把 LoginTaskManager.check_login() / start_login() 的 status/login command 构建迁入 MediaCrawlerRuntimeService 后,manager 侧残留的 runtime 细节已进一步收缩到 execution context 消费、子进程生命周期与事件/UI state 解析;相关定向回归为 tests/unit/backend/test_crawler_runtime_service.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_routes.pytests/integration/test_web_api_factory_smoke.py -k crawler25 passed
    • 备注(2026-04-20):本轮继续把 manager 侧的 subprocess kwargs 构建也收回到 MediaCrawlerRuntimeService:新增 build_completed_process_kwargs()build_streaming_process_kwargs() 后,LoginTaskManager.check_login()LoginTaskManager.start_login()CrawlTaskManager.start() 当前都改为经 backend/crawler/runtime.py 兼容壳消费 runtime/application helper 生成 canonical subprocess.run() / subprocess.Popen() kwargs。至此 manager 侧剩余的 runtime 细节已主要集中在 execution context 消费、进程生命周期控制与事件/UI state 解析;相关定向回归为 tests/unit/backend/test_crawler_runtime_service.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_routes.py26 passed,另补跑 tests/integration/test_web_api_factory_smoke.py -k crawler1 passed
    • 备注(2026-04-20):本轮继续把 backend/crawler/runtime.py 收薄为仅服务 managers 的最小 compat 壳后,tests/unit/backend/test_crawler_runtime.py 已改为锁定“旧 env/path/save_option/execution_context 门面已移除、剩余 helper 继续委托 MEDIACRAWLER_RUNTIME_SERVICE”这条 contract;相关定向回归为 tests/unit/backend/test_crawler_runtime.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_routes.py17 passed,并额外通过 python -m py_compile backend/crawler/runtime.py tests/unit/backend/test_crawler_runtime.py
    • 备注(2026-04-20):本轮围绕 browser_data writable path contract 又补跑了 tests/unit/backend/test_mediacrawler_adapter.pytests/unit/backend/test_crawler_runtime_service.pytests/unit/backend/test_crawler_runtime.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_routes.py,当前组合回归为 35 passed;并额外通过 python -m py_compile services/crawler/adapters/mediacrawler_adapter.py services/crawler/application/mediacrawler_runtime.py tests/unit/backend/test_mediacrawler_adapter.py tests/unit/backend/test_crawler_runtime_service.py
    • 备注(2026-04-21):本轮继续把 browser_data contract 落到 runtime env / vendor config 写入层后,又补跑了 tests/unit/backend/test_crawler_runtime_service.pytests/unit/backend/test_mediacrawler_adapter.pytests/unit/backend/test_crawler_runtime.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_routes.py,当前组合回归仍为 35 passed;并额外通过 python -m py_compile services/crawler/application/mediacrawler_runtime.py tests/unit/backend/test_crawler_runtime_service.py
    • 备注(2026-04-21):本轮继续把 vendor 真实运行时的 browser_data 消费收口到同一 helper 后,又新增 tests/unit/backend/test_mediacrawler_vendor_paths.py,并联合 tests/unit/backend/test_crawler_runtime_service.pytests/unit/backend/test_mediacrawler_adapter.pytests/unit/backend/test_crawler_runtime.pytests/unit/backend/test_crawler_managers.pytests/unit/backend/test_crawler_routes.py 回归为 38 passed;同时已通过 python -m py_compile vendor/mediacrawler/tools/utils.py vendor/mediacrawler/tools/cdp_browser.py vendor/mediacrawler/media_platform/xhs/core.py tests/unit/backend/test_mediacrawler_vendor_paths.py

风险与注意事项

  • 当前仅做边界骨架与适配器重构,尚未处理 vendor 配置文件写入副作用;后续切 application 层时需要额外测试。
  • browser_data 的默认路径已统一到 runtime-managed var/crawler/browser_dataUSER_DATA_DIR 也已经 runtime/application 边界写回 vendor config;tools/cdp_browser.pymedia_platform/xhs/core.py 这两条剩余 vendor 分支现已改为消费同一 user-data-dir helper,不再直接按 cwd/browser_data/... 推导 profile,CDP 分支与普通持久化上下文分支的目录来源已重新对齐。
  • vendor 侧仍存在少量依赖当前工作目录的相对资源路径约定(例如部分 libs/docs/ 资源访问);这不再阻塞当前 browser_data 主线,但后续若继续收口 runtime cwd 语义,仍需要单独盘点。
  • runtime_paths.py 目前仍作为兼容转发层存在,后续若应用层稳定,可考虑删除或降级为 deprecated shim。
  • MediaCrawlerPaths 目前继承 CrawlerRuntimePaths 以兼容旧类型名,后续若外部调用完全切到 domain 层,可进一步收口命名。

4. P1:业务收口期

4.1 建立 services/application/ 应用服务层

目标

在 API 与 engine 之间补齐用例编排层。

任务清单

  • 新建 services/application/research/task_service.py
    • 备注(2026-04-15 14:41):已落地 ResearchTaskService 骨架与 ResearchTaskCreateInput,当前只封装创建、列表查询、单任务查询、状态迁移四类接口;底层仍复用 backend.research_tasks.ResearchTaskStore 与 mapper,将统一 ResearchTaskStatus 渐进映射回旧 JSON store,暂不接入 crawler / analysis / report 编排逻辑。
    • 备注(2026-04-15 15:13):已补 ResearchTaskDTO/ResearchTaskListDTO 输出边界,研究任务创建、列表快照、单任务查询、状态迁移现在都可直接返回统一 DTO;backend/research_routes.py 已开始退化为“参数解析 + 调 service + 返回 DTO”的适配层。
  • 备注(2026-04-16 13:33):已补 tests/unit/application/test_research_task_service.py,使用 fake store 锁定 set_task_status()activate_task()、非法 transition_task() 的 application-layer contract;同时修复 transition_task() 在当前状态为字符串时可能抛出 AttributeError 的问题,统一先走 _coerce_status(...)
  • 备注(2026-04-16 15:44):为承接 AnalysisRun 最小闭环,ResearchTaskService.set_task_status() 与底层 ResearchTaskStore.update_status() 已补可选 analysis_run_id 写回,map_legacy_research_task() 也已开始把该字段映射到统一模型;当前 analysis_run_id 已能在 legacy JSON store、统一 ResearchTask、DTO 响应之间 round-trip。
  • 备注(2026-04-17):本轮已补齐 crawler_job_id 的同路径回写:ResearchTaskService.set_task_status()backend/research_tasks.pymap_legacy_research_task() 现已支持在不覆盖 analysis_run_id 的前提下单独写回 crawler_job_id,并由 tests/unit/application/test_research_task_service.py 锁定双链接字段可并存的 round-trip contract。
  • 备注(2026-04-17):已继续补齐 report_job_id 的同路径回写:ResearchTaskService.set_task_status()backend/research_tasks.pymap_legacy_research_task() 现已支持在不覆盖 crawler_job_id / analysis_run_id 的前提下单独写回 report_job_idservices/engines/report/flask_interface.py 在创建 report task 时也会在携带 research_task_id 的情况下把该关联回写到研究任务,并在 report 状态变更时同步研究任务阶段。
  • 备注(2026-04-17):本轮继续沿“更新任务阶段状态”主线补了 application 层统一入口:ResearchTaskService 新增 sync_report_runtime_status(...),把 report runtime 的 pending/running/completed/error/cancelled 状态映射统一收回 research application service,再复用 set_task_status(...) 落库;report_job_id 与既有 crawler_job_id / analysis_run_id 共存语义保持不变。相关断言已补到 tests/unit/application/test_research_task_service.py,当前该文件回归为 9 passed
  • 备注(2026-04-17):继续沿“更新任务阶段状态”主线推进 analysis 收口:ResearchTaskService 已新增 sync_analysis_runtime_status(...),统一承接 pending/queued/running/partial/completed/failed/cancelled 到 research task unified status 的映射,并保持现有“analysis partial/completed 仍停留在 analyzing、failed 回落 ready 以便重试”的兼容语义;AnalysisService 也已优先改为通过该入口推进任务阶段,不再在 orchestrator 内部散落维护 analyzing/ready 映射。

  • 新建 services/application/crawler/crawler_service.py

    • 备注(2026-04-16):虽然完整 crawler_service.py 尚未开始承接启动/停止编排,但已新增只读 CrawlerJobQueryService,当前通过 backend.crawler.state_store 的 history 快照和 map_crawler_history_entry() 输出统一 CrawlerJobDTObackend/crawler/routes.py 已开始提供 /api/crawler/jobs/api/crawler/jobs/{id} 以及在 /api/crawler/state 中附带 crawler_jobs 的 DTO 化查询出口,为后续真正的 crawler application service 铺路。
    • 备注(2026-04-17):读侧 contract 已继续补强,tests/unit/application/test_crawler_query_service.py 现已覆盖 idle/空历史时 current_job is None、history 命中与 live job 命中三条主要分支;tests/unit/backend/test_crawler_routes.py 已补 /api/crawler/jobs/{id} 成功路径,tests/integration/test_web_api_factory_smoke.py 也已锁定 factory app 中 crawler jobs 路由注册与最小 GET smoke。下一步仍建议优先保持 /api/crawler/jobs + current_job 这一现有 contract,而不是立刻再加 /api/crawler/jobs/current
    • 备注(2026-04-17):本轮已落地 CrawlerService,统一承接 crawler read/write application boundary:读侧通过 CrawlerStateDTO + CrawlerJobQueryService 输出 /api/crawler/state / /api/crawler/jobs / /api/crawler/jobs/{id},写侧则封装 check_login()start_login()cancel_login()start_crawler()stop_crawler() 并以 CrawlerValidationError / CrawlerOperationConflictError 向 route 暴露稳定错误边界。backend/crawler/routes.py 现已改为主要依赖 CRAWLER_APP_SERVICE,相关单测/冒烟测试已覆盖 service 委托、校验失败、冲突失败与最小 app-level start/stop contract。
    • 备注(2026-04-17):已继续把 crawler 读写与 task 资源接起来:CrawlerService.start_crawler() 现在会在 payload 中存在 research_task_id 时把 crawl snapshot 的 history_id 回写为任务的 crawler_job_id;同时新增 build_task_crawler_resource_payload() / get_task_crawler_resource_dto(),由 backend/research_routes.py 暴露 GET /api/research/tasks/<task_id>/crawler。当前 task-scoped crawler 资源刻意只返回该任务 linked job,不回退展示与任务无关的全局 crawler history。
    • 备注(2026-04-17):CrawlerService.start_crawler() 现已在 application 层补齐“研究任务默认采集参数同步”这条主线:当 payload 携带 research_task_id 时,会先读取统一 ResearchTaskDTO,将 crawler_defaults / crawler_keywords_text 中的默认 keywordscrawler_typelogin_type、分页与数量限制补齐到 crawler payload,但不会覆盖显式传入的字段;同时会把研究任务的 generated_query 一并带入状态回写。相关回归已补 tests/unit/application/test_crawler_service.py 对“默认参数同步 / 显式参数优先 / 缺失任务报错”的断言,并联合 tests/unit/backend/test_crawler_routes.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.py 重新验证 crawler route / app smoke,当前组合回归为 57 passed
    • 备注(2026-04-21):本轮已把 CrawlerServicelinked_task.legacy_payload 的直接读取收回到 DTO/mapper 边界:crawler 默认参数和 crawler_keywords_text 当前统一经 ResearchTaskDTO accessor + metadata.extras 解析,application 层不再直接依赖 legacy_payload["crawler_defaults"] / legacy_payload["crawler_keywords_text"] 字段名;相关定向回归为 tests/unit/application/test_crawler_service.pytests/unit/shared/test_task_model_mappers.py20 passed
  • 新建 services/application/analysis/analysis_service.py

    • 备注(2026-04-16 16:00):AnalysisService 已进一步收口 AnalysisRun 聚合逻辑,当前在接单时会创建 AnalysisRun、把 analysis_run_id 回写到研究任务,并在异步执行过程中持续更新 partial_resultsmetrics、运行中/部分成功/完成/失败状态;默认 store 已切到 JSON-backed AnalysisRunStore,同时仍保留 InMemoryAnalysisRunStore 供单测/隔离场景使用。已补 tests/unit/application/test_analysis_service.pytests/unit/application/test_analysis_run_store.py,锁定 query 回填、无引擎回退、接单后启动后台线程、analysis_run_id 关联回写、异步执行成功/异常分支下的聚合结果,以及 JSON store 的 round-trip。
    • 备注(2026-04-16):已新增只读 AnalysisRunQueryService,把 /api/research/tasks/<task_id>/analysis-run 的查询责任从 dummy AnalysisService 中拆出;同时 AnalysisRunStorePort 已补 list_runs_for_task(...)backend/research_routes.py 现在同时暴露单条最新 run 查询、GET /api/research/tasks/<task_id>/analysis-runs 历史列表查询,以及更资源化的 GET /api/research/tasks/<task_id>/analysis 视图,相关单测/冒烟测试已覆盖 linked run 命中、无 analysis_run_id 时回落到最新持久化 run、task-scoped history 返回,以及嵌套 analysis 资源返回。
    • 备注(2026-04-16):analysis query service 已进一步补 AnalysisRunDTO/payload helper,route 不再直接 model_dump(...) 拼 analysis 返回体;当前 analysis 查询接口已统一返回 run_idunified_run 等 DTO 视角字段,便于后续继续资源化。
    • 备注(2026-04-17):analysis 读侧资源视图已继续收敛为共享 TaskAnalysisResourceDTOAnalysisRunQueryService 现在只负责解析 current_run / runs 并委托 DTO 统一输出 summaryhistorystats;相关测试已补 tests/unit/shared/test_task_analysis_resource_dto.py,并在 tests/unit/application/test_analysis_query_service.pytests/integration/test_web_api_smoke.py 中锁定 linked-run 缺失时的 fallback、聚合统计和资源视图关键字段,便于下一步继续围绕 task-scoped analysis API 做字段收口或筛选能力扩展。
    • 备注(2026-04-17):本轮已继续把 /api/search 的用例入口往 application 层收口:AnalysisService 新增 submit_search_request(...) façade,在 service 内部串起 resolve_search_query()、空 query 判定、dispatch_search_request() 与 HTTP status 映射;http_routes.py 当前只保留 JSON payload 读取和 submit_search_request() 返回体透传,不再显式编排两段式 search choreography。bootstrap/runtime.py 也新增了 route-ready submit_search_request wiring,同时继续通过传入的 search hook callables 保持 _APP_MODULE.search_hooks 这条 monkeypatch 兼容面。相关回归已补 tests/unit/application/test_analysis_service.pytests/unit/web_api/test_http_routes_runtime_dependencies.pytests/unit/web_api/test_runtime_bootstrap.py,并联合 tests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/integration/test_web_api_system_controls.py 重新验证 search/app bootstrap 主链路,当前组合回归为 51 passed
  • 备注(2026-04-17):已继续完成上一轮提出的 runtime 协作收口:AnalysisService 现新增 AnalysisExecutionContext,把 process_registrycheck_app_statuslog_dirwrite_log 四类运行态依赖收束为单一 execution port;submit_search_request(...)dispatch_search_request(...) 当前优先消费该 bundle,apps/web_api/bootstrap/runtime.pyapps/web_api/runtime/search_dispatch.py 也已改为预先装配该 context,再通过 route-ready dispatcher 保持 _APP_MODULE.search_hooks 这条 monkeypatch 链路可替换。旧的显式 kwargs 入口仍暂时保留为兼容层。相关回归已扩展 tests/unit/application/test_analysis_service.pytests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_search_dispatch.py,并联合 tests/unit/web_api/test_http_routes_runtime_dependencies.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/integration/test_web_api_system_controls.py 重新验证,当前组合回归为 52 passed

  • 备注(2026-04-24):本轮继续把 /api/search 的 transport 语义从 application 层往外收:AnalysisService.submit_search_request(...) 当前已改为只返回 application submission 结果对象,不再直接产出 (payload, status_code) HTTP tuple;对应 200/400 状态码映射现在统一回退到 apps/web_api/runtime/search_dispatch.py 的 route/runtime adapter 边界,外部 /api/search contract 保持不变。相关回归已补到 tests/unit/application/test_analysis_service.pytests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_http_routes_runtime_dependencies.pytests/unit/web_api/test_runtime_bootstrap.pytests/integration/test_web_api_smoke.py

  • 备注(2026-04-28):本轮继续沿 4.2 主线把 analysis application 内部编排从裸结果结构收口到统一结果对象:AnalysisService.execute_search_dispatch_async(...) 当前已改为在 application 层先把 engine runner 输出归一成 EngineResult,随后统一基于 EngineResult.success/summary/error 生成状态文案、记录 partial result、统计 success/failure 与收尾消息,不再在 service 内部继续混用裸 dict.get(\"success\"/\"message\")。同时 _record_analysis_run_result(...) 也已改为直接消费 EngineResult,并补了“engine runner 直接返回 EngineResult 实例”定向单测,锁定这条 typed contract。

  • 备注(2026-04-28):同一条 typed-contract 主线本轮也继续往 runtime 边界推了一步:apps/web_api/runtime/search_dispatch.pyrun_local_engine_research(...) 当前已直接返回 EngineResult / EngineExecutionError,不再只回 raw dict;apps/web_api/runtime/task_runtime_store.py 也已开始在写入时统一把 partial_results 归一成 canonical engine payload,避免 task runtime snapshot 再扩散混杂的 legacy string/dict 结构。相关回归已补到 tests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_task_runtime_store.py,并联合 analysis 读写单测重新验证。

  • 备注(2026-04-28):本轮继续把这条 contract 收口到底:services/application/analysis/analysis_service.py 中的 EngineRunner 现已正式收紧为只返回 EngineResult,过渡性的 _run_engine(...) 兼容层已经移除,execute_search_dispatch_async(...) 直接消费统一结果对象;与之对应,tests/unit/application/test_analysis_service.py 里的 engine-runner 场景也已全部切到 EngineResult / EngineExecutionError 语义,当前定向回归为 12 passed

  • 备注(2026-04-28):本轮继续沿 task-centered application façade 收口 analysis 读侧:ResearchTaskViewService 当前已开始直接消费 AnalysisRunQueryService.get_task_analysis_run_dto(...) / list_task_analysis_run_dtos(...) 两条 typed 入口,并在 application 层组装 /analysis-run/analysis-runs 的 task-scoped payload;原先只作中转的 query-service payload helpers 已移除。相关回归已补跑 tests/unit/application/test_research_task_view_service.pytests/unit/application/test_analysis_query_service.pytests/unit/web_api/test_runtime_bootstrap.pytests/integration/test_web_api_smoke.py,组合为 36 passed;叠加本轮 analysis/search/factory 主线回归后,最新组合结果为 79 passed, 21 deselected

  • 备注(2026-04-22):本轮继续收口 research route 的跨服务聚合:新增 services/application/research/task_view_service.py,将 GET /api/research-tasks/task-views 原先留在 backend/research_routes.py 的 task/crawler/report 三段聚合视图拼装迁入 application facade;当前 route 已退化为“取 snapshot -> 调 task view service -> 返回 payload”的薄适配器。相关覆盖已补到 tests/unit/application/test_research_task_view_service.py,并联合 tests/integration/test_web_api_smoke.py 重新验证 task-views 资源输出。

  • 备注(2026-04-24):ResearchTaskViewService 本轮继续沿 task-centered 主线补齐了 analysis 聚合;GET /api/research-tasks/task-views 当前已从“task + crawler + report”扩展为统一返回 task + analysis + crawler + report 四段 task-scoped 摘要,便于后续前端页面不再额外拼接 analysis 资源。相关覆盖已同步扩展到 tests/unit/application/test_research_task_view_service.pytests/integration/test_web_api_smoke.py

  • 备注(2026-04-24):本轮又继续把 task-scoped resource orchestration 从 backend/research_routes.py 收回 ResearchTaskViewService/api/research/tasks/<task_id>/analysis-run/analysis/analysis-runs 当前都改为经 application facade 统一解析任务与 linked analysis_run_id,route 侧只保留 404 + jsonify 映射;同时为避免测试/运行时动态替换 RESEARCH_TASK_APP_SERVICE 时 capture 旧实例,research route wiring 也已切到通过惰性 task-service proxy 解析当前 task service。相关定向回归 pytest -q tests/unit/application/test_research_task_view_service.py tests/integration/test_web_api_smoke.py -k "analysis or task_view or resource_alias"12 passed, 34 deselected

  • 备注(2026-04-24):同一条主线本轮也已补齐 crawler/report 两条 task-scoped resource façade:ResearchTaskViewService 新增 build_task_crawler_resource_payload(...)build_task_report_resource_payload(...)backend/research_routes.py/api/research/tasks/<task_id>/crawler|report 现都已退化为“调 application facade -> 映射 404/503 -> jsonify”的薄路由,旧 _build_task_resource_response(...) 中转壳也已移除。相关回归 pytest -q tests/unit/application/test_research_task_view_service.py tests/integration/test_web_api_smoke.py tests/unit/engines/report/test_flask_interface_runtime_wiring.py -k "task_view or analysis or crawler_resource or report_resource or resource_alias or report_service"18 passed, 34 deselected

  • 备注(2026-04-28):本轮又继续把 analysis 读侧的 task-view 聚合留在 application 内部 typed 化:ResearchTaskViewService 当前已支持消费带 to_response_payload() 的 analysis resource DTO,而不是要求 analysis builder 直接返回裸 payload dict;backend/research_routes.pyRESEARCH_TASK_VIEW_APP_SERVICE 现已改为向 AnalysisRunQueryService 注入 get_task_analysis_resource_dto(...),从而让 task-views 聚合在 application 层内部优先围绕 TaskAnalysisResourceDTO 运行,最终只在最外层 materialize 成 response payload。相关回归已补到 tests/unit/application/test_research_task_view_service.pytests/integration/test_web_api_smoke.py

  • 备注(2026-04-28):本轮继续沿 P1.1/P1.4 瘦 route 主线收掉 research route 里的 task-view 模块级装配:backend/research_routes.py 当前只保留 configure_research_task_view_app_service(...) + getter/proxy,真正的 ResearchTaskViewService(...) 实例化已迁到 apps/web_api/bootstrap/runtime.py 的 runtime wiring 中统一注入;同时继续刻意复用 RESEARCH_TASK_APP_SERVICE 与 analysis/crawler/report 几条 lazy seam,避免 task-view facade capture 旧 store/service。相关回归已补到 tests/unit/web_api/test_runtime_bootstrap.py,并联合 tests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.py 重新验证 task-views / analysis resource / factory bootstrap 路径。

  • 新建 services/application/report/report_service.py

  • 备注(2026-04-17):虽然完整写侧 report_service.py 仍未落地,但本轮已先补只读 ReportJobQueryService 与 task-scoped GET /api/research/tasks/<task_id>/report,并新增共享 TaskReportResourceDTO 统一 report 资源输出;当前 route 层会先解析 research task,再把 linked report_job_id 显式交给 report query service,保持“仅 linked job 可见、不回退全局最近任务”的保守语义。

  • 备注(2026-04-17):已继续落地写侧 ReportService,统一封装 /api/report/generate 的任务冲突检查、引擎就绪校验、report task 创建、研究任务 report_job_id/status 回写以及后台线程启动;services/engines/report/flask_interface.pygenerate_report() 现已退化为“解析请求 -> 调 REPORT_APP_SERVICE.generate_report(...) -> 映射 service error”的薄路由。当前仍保留其余 report status/download/SSE 接口在 runtime 模块内,后续可继续向 application 层收口。

  • 备注(2026-04-22):本轮继续沿 report 写侧主线收口:ReportService 已新增 cancel_report(...),统一承接 /api/report/cancel/<task_id> 的任务解析、可取消状态判定、取消事件发布与 current-task 清理;services/engines/report/flask_interface.py 中对应 route 已退化为“参数透传 -> 调 REPORT_APP_SERVICE.cancel_report(...) -> 统一映射 service error”的薄适配器。相关覆盖已补到 tests/unit/application/test_report_service.pytests/integration/test_web_api_smoke.py

  • 备注(2026-04-17):report 读侧本轮继续向 application/query 层收口:ReportJobQueryService 现已新增 build_report_status_payload()build_report_tasks_payload()build_report_progress_payload() 三个 façade,统一承接 /api/report/status/api/report/tasks/api/report/progress/<task_id> 的 payload 编排,并保留“任务缺失时返回 completed fallback”兼容语义。services/engines/report/flask_interface.py 当前已退化为 jsonify(REPORT_QUERY_SERVICE....) 的薄适配器。相关回归已补 tests/unit/application/test_report_query_service.pytests/integration/test_web_api_smoke.py

  • 备注(2026-04-17):report 读侧继续沿相同模式收口,ReportJobQueryService 本轮已新增 build_report_result_payload()build_report_html_payload()build_report_download_descriptor()build_report_export_source(),统一承接 /api/report/result/<task_id>/api/report/result/<task_id>/json/api/report/download/<task_id> 以及 export/md|pdf/<task_id> 的任务解析、完成态校验、HTML 延迟加载、文件定位与 IR 读取。services/engines/report/flask_interface.py 中对应 route 已收缩为“HTTP 参数解析 -> 调 query façade -> 做 Flask response / 异常映射”的薄适配器,SSE 未改动。相关回归已扩展到 tests/unit/application/test_report_query_service.pytests/integration/test_web_api_smoke.py

  • 备注(2026-04-21):本轮继续把 report read-side 的 raw runtime dict fallback 收口到 shared DTO/mapper:ReportJobQueryService 当前对 snapshot/progress/result 几条只读链路都会优先解析为 ReportJobDTO,并统一产出带 unified_task 的兼容 payload;_FakeRuntimeReportTask.to_dict() 这类 runtime object 也已纳入同一 DTO coercion 链。相关定向回归为 tests/unit/application/test_report_query_service.py21 passed

  • 备注(2026-04-22):本轮继续把 report query application 边界从 runtime 私有实现里抽离:services/application/report/query_service.py 已移除默认反向 import services.engines.report.flask_interface 的 fallback,改为仅消费显式注入的 runtime/query callables;services/engines/report/flask_interface.py 当前会在构造 REPORT_QUERY_SERVICE 时注入 report_job_getter/check_engines_ready/snapshot/result_loaderbackend/research_routes.py 也改为复用同一个 runtime-wired query service,而不是自行 new 一个默认实例。这样 report read-side 的 application 层不再默认反向依赖 engine/runtime 模块。

  • 备注(2026-04-23):本轮继续沿同一主线把 report 辅助读接口收口到 application 层:新增 services/application/report/export_service.py/api/report/export/md/<task_id>/api/report/export/pdf/<task_id>/api/report/export/pdf-from-ir 已统一委托 ReportExportService;同时 ReportJobQueryService 已新增 build_report_log_payload()build_report_templates_payload(),统一承接 /api/report/log/api/report/templates 的日志/模板目录读取,ReportService 也已新增 clear_report_log() 承接 /api/report/log/clear。当前 services/engines/report/flask_interface.py 中这些 route 均已退化为“HTTP 参数解析 -> 调 application/query façade -> 异常映射”的薄适配器。

  • 备注(2026-04-28):backend/research_routes.py 的 report 读侧 wiring 本轮继续收口到 bootstrap 主线:apps/web_api/bootstrap/runtime.py 当前会在 build_runtime_services() 中解析 runtime-wired REPORT_QUERY_SERVICE,并通过 configure_research_route_services(..., report_query_service=...) 统一注入给 research routes;services/engines/report/flask_interface.py 中原先“模块导入时反向注入 research routes”的 side effect 已移除。相关回归已补到 tests/unit/web_api/test_runtime_bootstrap.pytests/unit/engines/report/test_flask_interface_runtime_wiring.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.py,report/runtime 定向组合为 26 passed,叠加 application/task-view/report 宽回归后本轮最新组合结果为 92 passed, 13 deselected

  • 备注(2026-04-23):同一条 wiring 主线本轮也继续收紧了失败语义:backend/research_routes.py 中 task-scoped report 资源与 task view 聚合当前不再在 report service 未注入时静默 new 一个裸 ReportJobQueryService() 退化为空能力,而是显式返回 503 配置错误,便于尽早暴露 bootstrap/runtime 接线回归。

  • 备注(2026-04-23):report runtime 本轮也开始沿 P0/P1 主线拆基础设施:已新增 services/engines/report/stream_runtime.pyservices/engines/report/task_runtime.py,分别承接 SSE subscriber/event-history helper 以及 current-task/task-registry snapshot helper;services/engines/report/flask_interface.py 当前已切到复用 StreamSubscriberRegistryTaskEventStreamReportTaskRuntimeStore,并把相关 smoke 改为经 REPORT_TASK_RUNTIME 访问运行时状态,而不再直接依赖散落的模块级全局变量。

  • 备注(2026-04-24):本轮继续把 report runtime 启动期的过渡 shim 收掉:services/engines/report/flask_interface.py 当前已不再先把恢复结果写入模块级 current_task / tasks_registry 再回灌给 REPORT_TASK_RUNTIME,而是改为由 _restore_report_tasks_from_disk() 直接返回恢复任务列表并初始化 REPORT_TASK_RUNTIME;旧全局别名已从主链路移除,相关 wiring smoke 仍保持通过。

  • 备注(2026-04-23):本轮继续把 report stream 主线往 application 层收口:新增 services/application/report/stream_service.py,将 /api/report/stream/<task_id> 的任务解析、Last-Event-ID 解析、历史事件回放、heartbeat 与 subscriber 注册/释放编排迁入 ReportStreamServiceservices/engines/report/flask_interface.py 中对应 route 当前已退化为“HTTP/SSE 外壳 + response headers + disconnect 检测”的薄适配器。同时,report runtime 当前生效的 stream/task helper 也已统一改为经 REPORT_STREAM_SUBSCRIBERSREPORT_TASK_RUNTIMEserialize_sse_event(...) 收口,减少 route/runtime 主文件里的平行实现。

  • 备注(2026-04-23):本轮继续收口 report runtime 主文件的遗留实现:services/engines/report/flask_interface.py 中已删除一整段被 stream_runtime.py / task_runtime.py 覆盖的旧 stream/task helper,并清理 /api/report/stream/<task_id> route 中 yield from REPORT_STREAM_SERVICE... 之后不再可达的旧 SSE 循环 body;同时 run_report_generation(...) 已进一步拆成“stream handler 构造 / 输入准备 / 带重试的 agent 调用 / 结果持久化”四个 helper,后续再继续把剩余编排迁出 runtime 时切口会更清晰。

  • 备注(2026-04-23):本轮又继续把 report runtime wiring 压薄了一层:REPORT_APP_SERVICE / REPORT_QUERY_SERVICE / REPORT_STREAM_SERVICE 当前已直接复用 REPORT_TASK_RUNTIMEREPORT_STREAM_SUBSCRIBERSserialize_sse_event(...) 的显式注入,不再在 flask_interface.py 内维护平行 task/stream store wrapper;同时保留 check_engines_ready() 的动态解析 seam,以兼容现有 smoke/unit tests 的 monkeypatch 入口。

  • 备注(2026-04-24):report runtime 本轮又补了一处并发边界收口:run_report_generation(...) 在取消分支和通用异常分支里,当前都改为通过 REPORT_TASK_RUNTIME.clear_current_task(task_id=...) 按任务 ID 条件清理 current task,而不再无条件清空。这样旧任务晚到的失败/取消不会误清掉已经切换成 current 的新任务。

  • 将以下核心用例迁入 application 层:

    • 创建/更新研究任务
    • 从研究任务同步爬虫参数
    • 启动采集
    • 启动分析
    • 汇总分析结果
    • 触发报告生成
    • [-] 更新任务阶段状态
  • backend 层改为仅做:

    • 请求参数解析
    • 校验
    • 调用 service
    • 输出响应 DTO
  • 备注(2026-04-23):report 侧又有一批 route 完成了这条收口:/api/report/log/api/report/log/clear/api/report/templates/api/report/export/md/<task_id>/api/report/export/pdf/<task_id> 当前都已通过 application/query service delegation 输出响应,route 本身不再直接读取日志文件、扫描模板目录或构造导出描述符。

  • [-] 将跨引擎编排逻辑从 API 或 runtime 中迁移到 application 层。

    • 备注(2026-04-16 16:00):/api/search 当前已通过 AnalysisService 执行 query 解析、接单分发与 AnalysisRun 聚合编排,runtime search_dispatch.py 主要保留本地引擎构造/执行 adapter 与兼容 wrapper;当前已形成 analysis_run_id -> partial_results/metrics/status -> task-scoped query endpoint 的最小闭环,并已在 backend/research_routes.py 暴露 GET /api/research/tasks/<task_id>/analysis-run
    • 备注(2026-04-16):analysis 查询职责已进一步沉淀为独立 AnalysisRunQueryService,并补齐 task-scoped history 边界 GET /api/research/tasks/<task_id>/analysis-runs;下一步更适合继续把“最新 run 选择 / 历史列表 / 未来筛选”抽成更稳定的资源化 analysis API,而不是继续把查询逻辑留在 route 适配层里。
    • 备注(2026-04-17):/api/search route 现已不再显式执行 resolve -> dispatch 两段编排,而是统一委托给 AnalysisService.submit_search_request(...);runtime bootstrap/runtime.py 侧只保留 route-ready dependency binding。下一步更适合继续把 process_registry / check_app_status / log_dir / write_log 这类 runtime 协作依赖收敛为更明确的 analysis execution port,进一步减少 service 入口暴露的运行时细节。
    • 备注(2026-04-17):上一条建议已完成,analysis dispatch 入口现在统一通过 AnalysisExecutionContext 吸收 runtime 协作依赖;route/bootstrap 与 runtime/search_dispatch.py 已不再把 process_registry / check_app_status / log_dir / write_log 四元组逐个摊开传入 AnalysisService。下一步更适合继续沿主线把“任务阶段状态更新”或 report 读写剩余用例继续收口到 application service,而不是回头扩张 route/runtime 侧逻辑。
  • 备注(2026-04-17):report 主链本轮也继续往 application 层收口了两步:一是研究任务的 report 阶段同步已不再由 runtime helper 自行维护映射,而是统一委托 ResearchTaskService.sync_report_runtime_status(...);二是 /api/report/status/api/report/tasks/api/report/progress/<task_id> 的读侧编排已迁到 ReportJobQueryService,runtime route 仅保留 HTTP 适配与异常映射。后续可继续按相同模式收口 report 的 result/download/export 读侧。

  • 备注(2026-04-17):上述 report 读侧收口已进一步延伸到 /api/report/result/<task_id>/api/report/result/<task_id>/json/api/report/download/<task_id>export/md|pdf/<task_id>;当前 route 不再直接自己查 runtime task / 判定完成态 / 定位 HTML/IR 文件,而是统一通过 ReportJobQueryService façade 获取 payload / descriptor,并集中映射 not found / not ready / artifact missing 三类读侧异常。

  • 备注(2026-04-22):除 route 变薄外,本轮还补了 report 生成线程的最小协作取消:run_report_generation(...) 当前会在启动、输入检查、载入数据、调用 agent、持久化与 completed 前检查任务是否已是 cancelledstream_handler 也不再把已取消任务重新推进为 running。这样 cancel_report(...) 不再只是表面改状态,而能阻止后续 running/completed 覆盖取消结果。

  • 备注(2026-04-23):report runtime/API 的跨层编排又继续减少了一批:模板列表、日志读取、日志清空、task-scoped Markdown/PDF 导出以及 /api/report/stream/<task_id> 的流式编排现在都已迁入 ReportJobQueryService / ReportService / ReportExportService / ReportStreamService;当前 report route 中仍然相对“重”的主线残留主要收缩为 runtime 侧的任务仓库/生成编排,以及 flask_interface.py 内尚未彻底移除的部分 legacy helper 痕迹。

验收标准

  • 主链路关键接口全部通过 application service 执行。
  • API 层代码显著变薄。
  • 引擎层不直接处理 HTTP 语义。

4.2 统一引擎输入输出契约

目标

减少 Query / Media / Insight / Forum / Report 之间的结果结构差异。

任务清单

  • 定义统一 EngineContext
  • 定义统一 EngineResult
  • 定义统一 EngineExecutionError
    • 备注(2026-04-22):已在 services/shared/models/engine_contract.py 落地最小共享契约:EngineContextEngineResultEngineExecutionError 当前已具备 engine_name/status/summary/artifacts/metrics/logs_ref/raw_data_ref/error 基本字段,并补了 EngineResult.from_raw(...)/to_runtime_payload() 归一化 helper。AnalysisService._record_analysis_run_result(...) 现已开始用该统一结果模型对 Query/Media/Insight 的原始执行结果做标准化后再写入 partial_results,同时保持现有 task runtime / DTO 输出兼容。相关回归已补 tests/unit/shared/test_engine_contract_models.pytests/unit/application/test_analysis_service.py,本轮定向验证为 13 passed
    • 备注(2026-04-22):本轮继续沿最小路径推进 P1.2:apps/web_api/runtime/search_dispatch.py 的 Query/Media/Insight 本地 adapter 现已开始直接输出 canonical raw shape(status/summary/artifacts/metrics/error),不再只返回 {success,message,report_preview}EngineResult.from_raw(...) 也同步补齐了 string error、report_preview/results/themes/sources artifact 映射以及常见统计字段 metrics 推断。相关回归已扩展到 tests/unit/web_api/test_search_dispatch.pytests/unit/shared/test_engine_contract_models.py,并联合 tests/unit/application/test_analysis_service.py 重新验证为 25 passed
  • 备注(2026-04-28):本轮继续把 typed input contract 贯通到 runtime adapter:AnalysisService.execute_search_dispatch_async(...) 当前会为每个 engine 显式构造 EngineContext(engine_name, research_task_id, query, trace_id, metadata)apps/web_api/runtime/search_dispatch.py 的本地 runner 也已统一改为接收 EngineContext;同时 services.application.analysis 包级出口现已补导出 EngineContext,方便 application/runtime/test 共享同一 typed contract。相关定向回归为 tests/unit/application/test_analysis_service.pytests/unit/web_api/test_search_dispatch.pytests/unit/shared/test_engine_contract_models.py29 passed
  • EngineResult 至少包含:
    • engine_name
    • status
    • summary
    • artifacts
    • metrics
    • logs_ref
    • raw_data_ref
    • error
  • 为 Query/Media/Insight 先增加 adapter/mapper,输出统一结果。
  • 备注(2026-04-22):本轮已先沿最小 adapter 路线把 apps/web_api/runtime/search_dispatch.py 的 Query/Media/Insight 本地执行结果收口到 canonical raw shape;同时 services/shared/dto/analysis_run.py 也新增了 partial_results 读模型归一化,task-scoped analysis run/resource 接口当前会把 legacy message/themes/sources 等旧结构投影成统一的 engine_name/status/success/summary/artifacts/metrics/error。相关回归已补到 tests/unit/shared/test_analysis_run_dto.pytests/unit/shared/test_task_analysis_resource_dto.pytests/unit/application/test_analysis_query_service.pytests/integration/test_web_api_smoke.py,本轮组合验证为 46 passed
  • 为 Forum/Report 增补统一结果表示。
  • 备注(2026-04-22):apps/web_api/runtime/forum_runtime.py 当前已为 get_output()get_log_payload()get_log_history() 统一附带 engine_result 字段,基于 parsed forum messages / host summary / runtime status 投影成 canonical EngineResult payload;services/shared/dto/report_job.pyservices/shared/dto/task_report.pyservices/application/report/query_service.py 也已为 report 读侧统一补齐 engine_result,覆盖 /api/report/status/api/report/tasks/api/report/progress/<id>/api/report/result/<id>/json 以及 task-scoped /api/research-tasks/<task_id>/report 的输出,同时保持旧字段完全兼容。相关定向回归已补到 tests/unit/web_api/test_forum_runtime.pytests/unit/web_api/test_forum_runtime_helpers.pytests/unit/application/test_report_query_service.py
  • [-] application 层只消费统一结果对象。
  • 备注(2026-04-22):本轮继续把 analysis application/read-model 链路往统一结果对象收口:services/application/analysis/analysis_service.py 当前在统计 success/failure 时已不再直接假设 partial_results 是裸 dict,而是统一经 EngineResult.from_raw(...) 归一后计算;services/shared/dto/analysis_run.py 也新增了 get_engine_results()services/shared/dto/task_analysis.py 当前的 summary/stats 派生改为优先消费统一 EngineResult 视角,而不是直接读原始 payload 结构。相关定向回归已补到 tests/unit/application/test_analysis_service.pytests/unit/shared/test_analysis_run_dto.pytests/unit/shared/test_task_analysis_resource_dto.py
  • 备注(2026-04-28):本轮又继续把这条边界往前推进了一步:analysis application 层当前不只在“统计/读模型”阶段消费 EngineResult,连异步执行编排本身也已经先统一归一为 EngineResult 再生成 status message / final summary / partial result 写回。相关回归 pytest -q tests/unit/application/test_analysis_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/application/test_analysis_query_service.py tests/unit/web_api/test_search_dispatch.py -k "analysis or engine_result or search"39 passed
  • 备注(2026-04-28):本轮继续补强 typed contract 的 integration coverage:tests/integration/test_web_api_smoke.py 现在额外锁定了 GET /api/research/tasks/<task_id>/analysis-runs 在“canonical + legacy partial result 混合 history”下仍统一输出标准 engine_name/status/success/summary/error 字段,以及 GET /api/research/tasks/<task_id>/analysis 在 linked analysis_run_id 失效时,仍会回退到最新 persisted run 并保留 summary.linked_run_idhistory.linked_run_available=falsehistory.linked_run_is_current=false 这条资源语义。
  • 备注(2026-04-28):本轮继续把 contract 从“可兼容 raw dict”推进到“只消费 typed result object”:AnalysisServiceEngineRunner 现已不再声明 dict | EngineResult 联合返回,而是完全收紧为 EngineResult;与此同时,tests/unit/application/test_analysis_query_service.pytests/integration/test_web_api_smoke.py 也新增了 current_run.partial_results 的 legacy/string/missing-field 归一化断言,进一步锁定 task-scoped analysis resource 在 current run 视图上的 canonical engine_name/status/success/summary/error 输出。
  • 备注(2026-04-22):本轮继续沿 report 读链推进同一目标:ReportJobQueryService._build_missing_progress_task_payload() 当前也已改为统一通过 ReportJobDTO.to_response_item() 产出兼容 payload,并补回 legacy report_file_* / state_file_* / error_message 空字段;缺省 progress fallback 现同样携带 unified_task 与 canonical engine_result,减少 report 侧手写 raw dict 分叉。

验收标准

  • 主链路中的核心引擎具备统一输出结构。
  • 汇总逻辑明显简化。

4.3 前端围绕 ResearchTaskViewModel 收口

目标

前端不再在页面里手工拼多个接口结果,而是围绕统一任务视图模型渲染。

当前进展快照(2026-04-21)

  • apps/web_ui/src/composables/useTaskViewModel.ts 已收敛为真正的组合层,不再各自实例化 useResearchWorkspace() / useCrawlerController() / useSystemController() / useReportStudio(),而是改为复用 WorkbenchController 提供的统一状态源。
  • useResearchWorkspace() 已新增 activeTaskCrawlerResourcerefreshActiveTaskCrawlerResource(),由 useWorkbenchController.ts 在初始化、任务保存/切换、研究触发、crawler start-stop 以及 4 秒轮询中统一刷新 task-scoped crawler 资源。
  • 前端启动 crawler 时现在会显式携带 research_task_id,因此后端 CrawlerService.start_crawler() 写回的 crawler_job_id 已能被 UI 主链路稳定消费;TaskViewModel 的 crawler 卡片也已切换为优先展示 /api/research-tasks/<task_id>/crawler 返回的 linked job,而不是全局 /api/crawler/state 的 current job。
  • useResearchWorkspace() 本轮已继续新增 activeTaskReportResourcerefreshActiveTaskReportResource()useTaskViewModel.ts 的 report 卡片已切换为优先消费 /api/research-tasks/<task_id>/report 返回的 task-scoped report 资源,不再依赖全局 report 列表按 query/task_id 做启发式匹配。
  • useReportStudio.generate() 现已支持透传 research_task_id(默认会回退到当前持久化的 active research task),因此报告生成链路已能把 task-scoped report 资源和研究任务主链路关联起来;useWorkbenchController.ts 在初始化、任务保存/切换与 4 秒轮询中也已统一刷新 report 资源。
  • useWorkbenchController.ts 本轮已继续把“刷新报告 / 生成报告”两条动作显式收口到当前 active research task:generateReport() 现在会直接传入 task_id 调用 reports.generate(query, taskId),随后刷新 research.refreshTasks(taskId)research.refreshActiveTaskReportResource(taskId)refreshReports() 也会同步刷新 research task snapshot 与 task-scoped report 资源,避免 UI 继续依赖持久化 fallback 才能看到最新 report link/status。
  • useReportStudio.generate() 本轮还补了失败分支的 generating=false 复位,避免 /api/report/generate 请求失败后前端一直卡在 “Generating...” 状态。
  • useResearchWorkspace.ts 已在 2026-04-20 切到资源化别名路径:任务集合读写、任务激活,以及 active task 的 crawler/report 资源刷新当前统一走 /api/research-tasks 前缀,减少前端同时混用两套 task URL 的情况。
  • 当前 apps/web_uivue-tsc --noEmit 已恢复通过:AnalyzeView.vue 依赖的 refreshSystemStatus() / refreshAppStatus() 已通过 useSystemController() 正式暴露,views/modes/AnalyzeView.vue / DeliverView.vue 中的旧图标导入也已替换为当前 @element-plus/icons-vue 可用成员。

任务清单

  • 设计 ResearchTaskViewModel
  • 聚合字段至少包含:
    • task base info
    • crawler status
    • analysis status
    • report status
    • recent action
    • errors
    • next available actions
  • 在 store/composable 中建立统一 task vm 映射层。
  • 备注(2026-04-20):useTaskViewModel.ts 已开始围绕 WorkbenchController 的 research/crawler/system/report 四类状态构建统一任务视图,并已把 crawler/report 数据源切换到 task-scoped /api/research-tasks/<task_id>/crawler|report 资源;下一步仍需继续把 analysis 的细粒度状态和 next available actions 明确纳入统一 VM。
  • 备注(2026-04-21):useTaskViewModel.ts 本轮已补齐 lastActionerrorsnextActions 三组主线字段;TaskCenterView.vue 现已直接消费统一 VM 渲染 recent action、open issues 与 next actions,并完成旧 activeTaskView 零散引用收口,页面不再自行拼 crawler/report/analysis 的局部展示字段。
  • 逐页迁移:
    • TasksView
    • ObserveView
    • AnalyzeView
    • DeliverView
    • 备注(2026-04-17):TaskCenterView 当前已消费 useTaskViewModel() 输出的统一任务视图,其中 crawler 展示已切换到 task-scoped resource;其余工作台页面仍有部分直接读取局部 composable 状态的逻辑,需后续继续迁移。
    • 备注(2026-04-17):TasksView.vueDeliverView.vue 本轮已先完成页面层接线收口,改为复用 useWorkbenchControllerContext() / useTaskViewModel(),不再各自新建 useResearchWorkspace() / useReportStudio() 实例。DeliverView 现已在选中任务时同步激活对应 research task,并优先消费 active task 的 task-scoped report VM;TasksView 也已优先按 report_job_id 关联全局 report 列表,仅在非 active task 且缺少稳定 link 时保留按 query 的兜底匹配。剩余待迁页面主要集中在 ObserveView / AnalyzeView 的细粒度交互状态收口,以及列表页对“非当前任务”的真正 task-scoped report/crawler 批量视图能力。
    • 备注(2026-04-21):ObserveView.vue 本轮已改为复用 WorkbenchControlleruseTaskViewModel(),不再自行实例化 useResearchWorkspace() / useCrawlerController() 或直接调用 /api/searchAnalyzeView.vue 也已切到消费统一 controller/system 状态与 active task VM,移除独立 useSystemController() 生命周期。当前 P1.3 剩余最主要的页面级拼装仍集中在 TasksView.vue 的列表聚合逻辑。
    • 备注(2026-04-21):useTaskViewModel.ts 本轮已继续补出 taskViews 列表投影视图,并把列表态 report fallback 关联逻辑收回共享 VM;TasksView.vue 现已改为直接消费 taskViews 渲染,不再在页面内混合 researchTasks + reportStatus + activeTaskReport 三段状态,也移除了按 report_job_id/generated_query 的页面级手工匹配。
    • 备注(2026-04-21):本轮继续把页面壳再压薄一层:CanvasView.vue 已改为以 activeTaskView + selectedTask 派生 workflow stage/tip/notice,不再用 watch 手工回填阶段状态;DeliverView.vue 也已进一步切到优先消费 taskViews/activeTaskViewtaskView.report.taskId,移除页面内按 query 手工匹配 report 的逻辑;ObserveView.vue 则开始优先以 active task 的 task-scoped crawler 资源构建关键词、平台、recent action 和分析移交流程,减少继续回退到全局 crawler snapshot/form 的范围。
    • 备注(2026-04-22):本轮继续沿同一主线收缩剩余启发式逻辑:useTaskViewModel.ts 当前对非 active task 的列表态 report 关联已不再按 generated_query 做 query heuristic,而是只认稳定的 report_job_id link;ObserveView.vue 的 recent crawl 展示也开始优先消费 active task 的 linked crawler jobs,而不是默认回退到全局 crawler logs。
    • 备注(2026-04-22):本轮继续收口页面层状态来源:AnalyzeView.vue 当前已按 targetTaskId -> taskViews/activeTaskView 解析目标任务,并由共享 TaskViewModel 统一构建 selectedTaskContextgo-deliver、forum 消息、engine 状态和资源刷新也开始优先跟随同一个 task vm;ObserveView.vue 则进一步移除了展示链路上对全局 crawler.snapshot/logs 的默认依赖,recent crawl 回退改为 task-scoped summary,页面挂载时也不再主动刷新全局 crawler state。
    • 备注(2026-04-22):本轮又继续把 ObserveView.vue 的目标任务解析收回统一 vm:页面当前已新增 targetTaskId -> observeTaskView(taskViews/activeTaskView) 解析链,关键词、平台、crawler 状态、recent action、recent crawl、handoff query 与 buildTaskContext 等展示逻辑都开始优先跟随目标任务 vm,而不是默认混用 active task / props fallback。
    • 备注(2026-04-22):本轮继续把“目标任务解析 + 页面上下文构建”收回共享 VM:useTaskViewModel.ts 新增 resolveTaskView() / resolveTaskContext()AnalyzeView.vueDeliverView.vueCanvasView.vue 当前都已改为优先复用这组 helper 来解析 targetTaskId 对应的 task vm 和页面上下文,不再各自维护 taskViewById + buildTaskContext(...) 的重复拼装。相关前端 npm run typecheck 已再次通过。
    • 备注(2026-04-22):本轮又继续把 ObserveView.vue 并入同一收口路径:当前页面已改为优先复用 resolveTaskView() / resolveTaskContext() 解析当前观察任务与页面上下文,currentTaskId/currentTaskQuery、crawler platform/keywords/status/recentAction、draft hydration 与 emit 出去的 task context 现在都尽量跟随共享 task vm,而不是继续分散混用 activeTaskactiveTaskCrawlerResourceprops.selectedTask。相关前端 npm run typecheck 已再次通过。
  • 备注(2026-04-22):本轮继续收掉 TasksView.vue 的最后一段旧链路:后端已新增 /api/research-tasks/task-views 列表态 task-scoped 摘要接口,useResearchWorkspace.tsuseTaskViewModel.ts 当前会为非 active task 注入对应的 task-scoped crawler/report 资源;TasksView.vue 也已改为直接消费这组列表态资源,不再为了展开卡片详情而先 activateTask() 再刷新 active task report 资源。相关回归为 tests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.py 与前端 npm run typecheck
  • 备注(2026-04-22):本轮又继续把 useTaskViewModel.tsAnalyzeView.vue 里剩余的 report/task-context 启发式 fallback 再压薄了一层:active task 的 report 关联当前也已只认稳定的 report_job_id link,不再回退到全局 report 列表按 query 匹配;AnalyzeView.vue 也已去掉页面层本地重建 task context 的平行逻辑,统一复用共享 resolveTaskContext()。结合前面几轮收口,TaskCenterViewTasksViewObserveViewAnalyzeViewDeliverViewCanvasView 当前主路径都已切到统一 task vm / task context 解析链。
  • 页面层去除跨多个接口的分散状态拼装逻辑。

验收标准

  • 核心页面统一消费 task vm。
  • 页面逻辑显著收敛。
  • 状态展示与后端主模型一致。

4.4 API 资源化收口

目标

围绕核心业务对象组织接口。

当前进展快照(2026-04-21)

  • 已先沿现有兼容前缀 /api/research/tasks/<task_id>/... 落地一组 task-scoped 资源接口:/analysis-run/analysis-runs/analysis/crawler
  • 其中 analysis 资源已收敛到共享 TaskAnalysisResourceDTO,crawler 资源已收敛到共享 TaskCrawlerResourceDTO,report 资源本轮也已收敛到共享 TaskReportResourceDTO;三者都由 route 层统一先解析任务,再把 linked resource id 显式传给各自 application service。
  • 2026-04-24:task-scoped analysis-run/analysis-runs/analysis/crawler/report 五条 research resource 主读链已继续收口到 services/application/research/task_view_service.pybackend/research_routes.py 当前不再自己解析 task 再手搓 resource payload,而是统一委托 application facade,仅保留 404/503 异常映射与 jsonify
  • 当前 /api/research/tasks/<task_id>/crawler 采取保守策略:仅当任务存在 crawler_job_id 且可解析到对应 job 时返回 linked job 视图,不回退到全局 current_job,避免错误串联其他任务的 crawler 状态。
  • 当前 /api/research/tasks/<task_id>/report 同样采取保守策略:仅当任务存在 report_job_id 且可解析到对应 report job 时返回 linked job 视图,不回退到全局最近 report task,避免错误串联其他任务的交付状态。
  • 前端 ResearchTaskViewModel 已开始切换到 task-scoped crawler/report 资源,同时 crawler start 与 report generate 请求都已补传 research_task_id,使 crawler_job_id / report_job_id 写回与 UI 展示形成闭环;下一步更适合继续把更多页面逻辑从局部 snapshot 迁移到统一 task VM,并考虑是否把 URL 前缀统一切到 /api/research-tasks/{id}/...
  • 已新增资源化兼容别名蓝图 /api/research-tasks:当前已落地 GET/POST /api/research-tasksGET /api/research-tasks/{id}GET /api/research-tasks/{id}/analysis|crawler|report,统一复用现有 application service / task-scoped resource payload,不影响旧 /api/research/tasks 前缀继续工作。该路径目前作为兼容别名存在,便于后续前端与接口文档渐进切换。
  • 2026-04-20:/api/research-tasks 兼容层已继续补齐 POST /api/research-tasks/{id}/activateGET /api/research-tasks/{id}/analysis-runGET /api/research-tasks/{id}/analysis-runs;同时 useResearchWorkspace.ts 已把任务集合读写、激活与 active task 的 crawler/report 刷新统一切到新前缀,避免 Research workspace 继续混用旧 /api/research/tasks 路径。
  • 2026-04-20:已补跑 tests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.py30 passed,并重新执行 apps/web_uivue-tsc --noEmit 通过;warning 仍仅包含既有 eventlet deprecation、datetime.utcnow() deprecation 与 .pytest_cache 权限提示。
  • 2026-04-22:本轮已继续补上面向 TasksView 的列表态任务摘要接口 GET /api/research-tasks/task-views,以 task 为中心聚合 research task 基本信息以及 task-scoped crawler/report 读资源,前端列表页已切到直接消费该接口返回的非 active task 摘要,不再依赖先切 active task 才能拿到准确详情。

任务清单

  • 盘点当前与任务流相关的 API。
  • 设计新的资源化路径:
    • /api/research-tasks
    • /api/research-tasks/task-views
    • /api/research-tasks/{id}
    • /api/research-tasks/{id}/crawler
    • /api/research-tasks/{id}/analysis
    • /api/research-tasks/{id}/report
    • 备注(2026-04-22):当前资源化别名已覆盖 collection、list-oriented task-views、detail、activate 以及 task-scoped analysis-run/analysis-runs/analysis/crawler/report,并继续复用既有 application service / DTO;这一轮新增的 task-views 已用于收口 TasksView 对非 active task 的 task-scoped 摘要读取。
  • 提供兼容层,避免一次性废弃旧接口。
    • 备注(2026-04-20):/api/research-tasks 现已覆盖研究任务主链路中的集合、详情、激活以及分析/采集/报告读侧资源,旧 /api/research/tasks 路径仍保持兼容可用,核心 task flow 已具备渐进迁移所需的双轨接口。
  • 前端逐步切换到资源化接口。
    • 备注(2026-04-20):useResearchWorkspace.ts 当前已把任务集合读写、激活以及 active task 的 crawler/report 资源刷新切到 /api/research-tasks;后续仍需继续检查 Analyze/Observe 等页面是否存在 task-related 旧路径或局部 snapshot 依赖。
    • 备注(2026-04-21):本轮再次扫描 apps/web_ui/src,未再发现前端直接请求旧 /api/research/tasks/... 路径;当前剩余尾项主要是页面语义上继续减少对全局 snapshot 的回退,而不再是 research-task 资源路径的切换问题。
  • 文档化接口契约与字段说明。
    • 备注(2026-04-21):已新增 docs/api-research-task-resources.md,集中说明 /api/research-tasks collection/detail/activate、task-scoped analysis-run/analysis-runs/analysis/crawler/report 资源,以及与旧 /api/research/tasks/... 前缀的兼容映射。

验收标准

  • 新增主链路接口以 task 为中心。
  • 旧接口只作为兼容层存在。

5. P2:系统增强期

当前进展快照(2026-04-15 14:41)

  • 已在 services/shared/observability/ 下新增 context.pyevents.pymetrics.py 与包级导出,先落地 trace_id/task_id/span_id 关联上下文、事件 envelope 与指标采集接口骨架。
  • 已在 services/shared/timeline/ 下新增 timeline.pytracker.py 与包级导出,提供生命周期事件到 timeline entry 的聚合与查询入口,并兼容 observability recorder 镜像,供 runtime/application 后续直接挂载。
  • 当前 observability 仅提供无后端依赖的端口与内存实现,尚未接入 apps/web_api/runtime/services/application/ 或结构化日志链路。
  • 共享包入口 services/shared/__init__.py 已补充 observabilitytimeline 导出,后续 runtime / application 可直接按统一路径引用。

5.1 crawler 域隔离

目标

隔离 vendor crawler 实现,避免业务层直接依赖第三方源码。

任务清单

  • 建立 services/crawler/domain/
  • 建立 services/crawler/application/
  • 建立 services/crawler/adapters/mediacrawler_adapter.py
  • 统一 crawler 参数模型与错误映射。
  • 业务层改为只依赖 crawler domain/application。

验收标准

  • 上层代码不直接 import vendor/mediacrawler
  • crawler 替换成本可控。

5.2 补系统级测试

目标

让主链路具备稳定回归保障。

当前进展快照(2026-04-16 13:33)

  • 已新增 tests/integration/test_web_api_smoke.py,通过 Flask test_client() 建立 Web API 最小冒烟测试入口。
  • 已新增 tests/unit/web_api/test_cleanup.py,覆盖 reload-safe cleanup 注册 helper 的“替换旧 handler / 忽略反注册异常”行为。
  • 已新增 tests/unit/web_api/test_import_path.py,覆盖项目根路径注入 helper 的“插入到 sys.path[0] / 重复调用不重复插入”行为。
  • 已新增 tests/unit/web_api/test_runtime_bootstrap.py,覆盖 runtime service 构造、cleanup handler 委托和 route dependency 装配映射,补齐 bootstrap/runtime.py 的 contract test。
  • 已新增 tests/unit/web_api/test_forum_runtime.py,覆盖 ForumRuntime 对注入 ProcessRuntimeRegistry 的状态写入与输出委托;tests/unit/web_api/test_forum_runtime_helpers.py 也已恢复 Forum log history cursor contract,锁定 helper 不再因为 file.tell() 使用方式回退为 OSError
  • 已新增 tests/unit/web_api/test_http_routes_runtime_dependencies.py,覆盖 Forum 启停、Forum 日志读取、/api/status 状态刷新,以及普通 app 启停/日志路由对注入依赖的委托行为,避免 http_routes.py 回退到模块级直接 import。
  • 已新增 tests/unit/web_api/test_socket_events.py,覆盖 connectrequest_status 事件对注入依赖的使用顺序,锁定 Socket.IO handlers 不回退到模块级 runtime 默认 import。
  • 已新增 tests/unit/web_api/test_search_hooks.py,覆盖 search hook helper 对 app 模块命名空间的动态解析行为,锁定 monkeypatch 兼容链路不因 wrapper 迁出 app.py 而回归。
  • 已新增 tests/unit/web_api/test_app_module_bootstrap.py,覆盖 bootstrap/app_module.py 的 app module 级装配 contract,并锁定 helper 通过 apps.web_api.bootstrap.runtime 模块属性读取 runtime builders,避免 reload/monkeypatch 测试因导入时 capture 符号而回归。
  • 已新增 tests/unit/application/test_research_task_service.py,使用 fake store 锁定 ResearchTaskService 的状态回写、任务激活与非法状态迁移 contract,避免测试被本地文件系统权限噪音干扰。
  • 已新增 tests/unit/web_api/test_http_config.py,覆盖前端 dev URL helper 的空值/去尾斜杠行为。
  • 已新增 tests/integration/test_web_api_factory_smoke.py,覆盖 create_app() 的 factory/bootstrap 装配、关键 blueprint/runtime 路由挂载和 Socket.IO handler 注册的最小 smoke。
  • tests/unit/web_api/test_runtime_bootstrap.py 现已进一步锁定 RuntimeServices 对同一 forum_runtime 对象的装配,以及 route dependency 对该对象 bound methods 的委托;tests/integration/test_web_api_factory_smoke.py 也已补 /api/status 读取注入 forum 状态的 smoke,避免 factory app 回退到测试外的默认 runtime 状态。
  • 已新增 tests/integration/test_web_api_system_controls.py,覆盖 /api/system/start/api/system/shutdown 的无子进程模式 contract test,并验证 shutdown 默认透传 cleanup_timeout=6.0
  • 当前通过 monkeypatch 将 backend.research_tasks.research_task_service 重定向到临时 store,并对测试用 store 的 _save_locked() 做 no-op 处理,避免当前 Windows 环境的文件写权限问题和真实日志目录污染。
  • tests/conftest.py 已新增共享 fixture,统一收敛隔离 ResearchTaskStoreapps.web_api.app reload 与 _APP_MODULE.cleanup_handler 注销逻辑,减少多份 integration test 的重复接线。
  • 已覆盖 /api/system/status happy path,验证系统未启动时的最小返回结构。
  • 已覆盖 / 首页在 BETTAFISH_FRONTEND_DEV_URL 存在时的 redirect/URL 规范化行为。
  • 已覆盖 /api/research/tasks POST + GET happy path,验证 ResearchTask 创建、active_task 快照与任务列表响应主链路。
  • 已覆盖运行时路由/Socket 注册检查、/api/system/start 成功/失败透传、/api/system/shutdown 成功/失败透传以及 /api/search 的 app-module monkeypatch 链路,确保入口重构后关键 hook 仍可用。
  • 已覆盖 /api/research/tasks/<task_id>/analysis-run 的 null/linked-run/fallback-to-latest persisted run 三种查询分支、/api/research/tasks/<task_id>/analysis-runs 的 task-scoped history 返回,以及 /api/research/tasks/<task_id>/analysis 的资源化 analysis 视图返回。
  • 已新增 TaskAnalysisResourceDTO 及其 shared/unit coverage,锁定 summary / history / stats 的派生语义,并在 /api/research/tasks/<task_id>/analysis 的 query/integration tests 中继续覆盖 linked-run 缺失时的 fallback、聚合计数和关键资源字段。
  • 已新增 CrawlerStateDTO shared/unit coverage,并继续覆盖 AnalysisRunDTOCrawlerJobDTO 的 response payload contract,确保 run_id/job_idunified_run/unified_job 与 legacy payload 合并行为稳定。
  • 已覆盖 /api/crawler/jobs/api/crawler/jobs/{id}/api/crawler/state 以及 /api/crawler/start / /api/crawler/stop 的 application-service 委托链路,锁定 crawler history 到 unified DTO payload 的读侧输出,以及 CRAWLER_APP_SERVICE 的写侧异常映射行为,并额外补了 Web API app 级别的 /api/crawler/jobs/api/crawler/start/api/crawler/stop smoke、factory app 中 crawler jobs 路由注册与最小 GET smoke。
  • 已新增 TaskCrawlerResourceDTO shared/unit coverage,并继续覆盖 /api/research/tasks/<task_id>/crawler 在“无 linked job”与“命中 linked job”两种分支下的 task-scoped crawler 资源输出;同时 tests/unit/application/test_research_task_service.pytests/unit/application/test_crawler_service.py 已锁定 crawler_job_id 的 round-trip 与 start_crawler(research_task_id=...) 回写行为。
  • 已新增 TaskReportResourceDTO shared/query coverage,并继续覆盖 /api/research/tasks/<task_id>/report 在“无 linked job”与“命中 linked job”两种分支下的 task-scoped report 资源输出;tests/unit/application/test_report_query_service.py 现已锁定 report query service 的 linked-only 读侧语义,tests/integration/test_web_api_smoke.py 也已补齐 report resource 路由 smoke 与 /api/report/generate 携带 research_task_idreport_job_id 回写研究任务的主链路 contract。
  • 已继续补强 report-task linkage 的边界测试:tests/unit/application/test_report_query_service.py 现在额外覆盖“task 挂着 report_job_id,但 linked report job 实际缺失”时的保守返回语义;tests/unit/application/test_research_task_service.py 也已锁定 report_job_id=None 清链路时不会误伤既有 crawler_job_id / analysis_run_id。此外 tests/integration/test_web_api_smoke.py 已新增闭环 smoke,验证 /api/report/generate(research_task_id=...) -> GET /api/research/tasks/<task_id>/report 会返回同一个 linked runtime report job,current_job_id/linked_job_idresearch_task_id 都能正确对齐。
  • 已新增 tests/unit/application/test_report_service.py,锁定 ReportService.generate_report(...) 的 application-layer contract:覆盖“已有运行中任务冲突”、“Report Engine 未初始化”、“输入文件缺失”与“成功创建任务并启动后台线程”四条主分支,同时验证 queued 事件发布与 report_job_id 回写触发时机。
  • 当前测试刻意不触发真实 Streamlit / Forum / 搜索分发 / Socket 运行流程,优先保证重构期间入口稳定。
  • 已执行相关定向回归:tests/unit/shared/test_analysis_run_dto.pytests/unit/shared/test_crawler_job_dto.pytests/unit/shared/test_crawler_state_dto.pytests/unit/shared/test_task_analysis_resource_dto.pytests/unit/shared/test_task_crawler_resource_dto.pytests/unit/application/test_analysis_run_store.pytests/unit/application/test_analysis_query_service.pytests/unit/application/test_analysis_service.pytests/unit/application/test_crawler_query_service.pytests/unit/application/test_crawler_service.pytests/unit/application/test_research_task_service.pytests/unit/backend/test_crawler_routes.pytests/unit/web_api/test_cleanup.pytests/unit/web_api/test_http_config.pytests/unit/web_api/test_http_routes_runtime_dependencies.pytests/unit/web_api/test_import_path.pytests/unit/web_api/test_process_manager_cleanup.pytests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_socket_events.pytests/unit/web_api/test_search_hooks.pytests/unit/web_api/test_search_dispatch.pytests/unit/web_api/test_system_lifecycle.pytests/unit/web_api/test_process_registry.pytests/unit/web_api/test_system_state_registry.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/integration/test_web_api_system_controls.py,当前共 126 条测试通过;其中 test_runtime_bootstrap.py 已额外校验 Forum/日志/Streamlit-process-manager、SystemLifecycleDependencies 以及 Socket.IO 依赖通过 bootstrap wiring 正确映射,test_http_routes_runtime_dependencies.py 进一步锁定 /api/status/api/start/<app_name>/api/stop/<app_name> 以及 Forum/日志路由对这些注入 callable 的委托行为,test_socket_events.py 锁定 Socket.IO handlers 对显式注入依赖的调用顺序,test_search_hooks.py 锁定 app-module search monkeypatch 兼容链路的动态解析行为,test_research_task_service.py 则锁定应用层任务状态边界,新增的 analysis/crawler task-resource DTO tests、crawler state/service tests 与 crawler route/app smoke tests 则进一步补齐 task-scoped analysis/crawler 资源视图和 crawler 读写 contract;共享 fixture teardown 仍会显式注销 app 模块注册的 cleanup handler,避免退出阶段的旧版清理异常。
  • 本轮又补跑了 tests/unit/application/test_report_query_service.pytests/unit/application/test_research_task_service.pytests/integration/test_web_api_smoke.py 三组与 report-task linkage 直接相关的定向回归,当前共 30 passed;warning 仍仅包含既有 eventlet deprecation、datetime.utcnow() deprecation 与 .pytest_cache 权限提示。
  • 本轮围绕 report write-side application service 又补跑了 tests/unit/application/test_report_service.pytests/unit/application/test_report_query_service.pytests/unit/application/test_research_task_service.pytests/integration/test_web_api_smoke.py,当前共 34 passed;warning 仍仅包含既有 eventlet deprecation、datetime.utcnow() deprecation 与 .pytest_cache 权限提示。

任务清单

  • 为 application service 补测试:
    • 创建任务/状态回写
    • 状态迁移
    • 分析触发
    • 报告生成触发
  • [-] 为 API 补集成测试:
    • 创建任务
    • 启动爬虫
    • 触发分析
    • 查询报告状态
    • 备注(2026-04-16 10:35):已落地 / 首页 redirect、/api/system/status/api/system/start(成功/失败透传)、/api/system/shutdown(成功/失败透传)与 /api/research/tasks 的最小 happy path/contract coverage,后续按 runtime/application 稳定度继续扩充 crawler、analysis、report 主链路。
    • 备注(2026-04-21):tests/integration/test_web_api_smoke.py 当前已覆盖 /api/crawler/start|stop/api/search/api/report/status|tasks|progress、task-scoped analysis/crawler/report 资源,以及 /api/report/generate(research_task_id=...) 到 task-scoped report 资源的闭环链路;本轮再次执行 smoke 为 27 passed
    • 备注(2026-04-22):本轮又继续补了 /api/research-tasks/task-views/api/forum/log|historyengine_result 断言,以及 report task-scoped/result/status/tasks/progress 对 engine_result 的兼容透传断言;结合 analysis 读侧与 factory smoke 回归后,本轮组合验证 tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/application/test_report_query_service.py tests/unit/web_api/test_forum_runtime.py tests/unit/web_api/test_forum_runtime_helpers.py tests/integration/test_web_api_factory_smoke.py tests/integration/test_web_api_smoke.py90 passed
  • [-] 增加一条主链路冒烟测试:
    • 建立 Flask app/test_client 冒烟测试入口
    • 校验 system status 基础返回
    • 校验 ResearchTask 创建与快照读取
    • mock crawler 完成
    • mock query/media/insight 返回
    • forum 汇总
    • report 生成
    • 校验 completed 状态与输出文件
    • 备注(2026-04-15 15:21):当前骨架聚焦低依赖入口验证,故意不耦合未稳定的引擎与后台流程。
    • 备注(2026-04-22):tests/integration/test_web_api_smoke.py 本轮继续沿同一主线补齐低依赖 smoke:新增 task-scoped analysis 资源对 query/media/insight 多引擎 partial results 的保留断言、/api/crawler/start -> task-scoped /crawler 的 mock completed crawler 输出断言,以及 /api/forum/log|history 的 forum host summary 读取断言,并重新回归整个 smoke 文件 32 passed;同时已确认既有 report smoke 继续覆盖 /api/report/generate -> completed -> task-scoped report resource/result/download 的输出链路。

后续接入建议

  1. services/application/research 真正承接搜索编排后,在同一测试文件追加 /api/search 的 fake dispatcher happy path,用 stub 代替真实引擎。
  2. 当前 /api/system/start/api/system/shutdown 已有无子进程模式的 contract test,后续可继续补启动失败、重复关机、部分引擎启动异常等失败路径。
  3. bootstrap/runtime.py 装配层 contract test 与 app factory/bootstrap smoke 已补齐;当前公共 fixture 已先收敛到 tests/conftest.py,后续若 Web API 测试继续增多,可再拆分到 tests/fixtures 子模块。
  4. 后续若引入统一任务仓储接口,可继续把当前 monkeypatch store fixture 升级为更明确的仓储/应用服务 fixture,减少测试对全局 patch 的依赖。

验收标准

  • 主链路改动后可自动回归。
  • 关键阶段状态迁移有测试覆盖。

5.3 建设可观测性

目标

从“日志可看”升级到“任务全链路可观测”。

任务清单

  • [-] 在 services/shared/observability/ 下建立:
    • tracing.py
    • metrics.py
    • event_log.py
    • correlation.py
    • 备注(2026-04-15 14:41):已先以 context.pyevents.pymetrics.py 落地最小骨架,统一 trace_id/task_id/span_id 关联上下文、事件 envelope、指标采集端口,并提供内存实现供测试/迁移使用;真实 exporter 与 runtime/application 接线尚未开始。
  • 为所有主链路动作补 task_id/trace_id
  • 为每个引擎记录执行耗时。
  • 为状态迁移记录标准事件日志。
  • 为主链路失败增加阶段级错误记录。
  • 如条件允许,补一个最小 metrics 面板或可查询接口。

验收标准

  • 任意任务可追踪完整生命周期。
  • 可定位慢点与失败点。

5.4 任务持久化与恢复

目标

将主链路任务状态从内存状态升级为可恢复状态。

任务清单

  • 评估使用 PostgreSQL 还是轻量过渡存储。
  • 持久化以下信息:
    • task metadata
    • status timeline
    • engine result refs
    • report artifact refs
    • error history
  • 启动时恢复未完成任务的状态快照。
  • 定义失败任务重试/恢复策略。

验收标准

  • 系统重启后任务状态可恢复。
  • 报告产物与任务关系明确。

5.5 后台任务执行体系

目标

将重任务从 Flask 请求生命周期中解耦。

任务清单

  • 评估 Celery / RQ / Dramatiq / 自研轻量 job runner。
  • 选型时对比:
    • 部署复杂度
    • 重试能力
    • 状态回写便利性
    • 与现有 Flask 架构兼容性
  • 为分析任务与报告任务建立异步 job 执行原型。
  • 保证异步状态回写 task model。
  • 补失败重试与超时控制。

验收标准

  • 主分析/报告任务可异步执行。
  • 状态一致且可追踪。

6. 推荐执行顺序

第 1 批(必须先做)

  • P0.1 拆 app.py
  • P0.4 建 runtime registry/store
  • [-] P0.2 建统一任务模型

第 2 批(完成主语义收口)

  • [-] P0.3 收口 shared 基础设施
  • [-] P1.1 建 application service

  • [-] P1.2 统一引擎输出

第 3 批(产品流稳定化)

  • [-] P1.3 前端 task view model 化
  • [-] P1.4 API 资源化收口

第 4 批(系统增强)

  • P2.1 crawler 域隔离
  • [-] P2.2 系统级测试
  • P2.3 可观测性
  • P2.4 持久化与恢复
  • P2.5 后台任务体系

7. 当前立即执行项(本轮开发起点)

当前第一刀

  • 拆分 apps/web_api/app.py 为 factory / bootstrap / runtime 结构

子任务

  • 提取 system state 模块
  • 提取 process manager 模块
  • 提取 forum runtime 模块
  • 提取 log stream 模块
  • 提取 search dispatch 模块
  • 提取 bootstrap blueprints / socketio / logging
  • 收缩 app.py 到最小入口
  • 验证现有接口不回归

8. 更新记录

2026-04-21

  • 继续推进 P1.3 前端 task view model 主线:apps/web_ui/src/composables/useTaskViewModel.ts 本轮已补齐 lastActionerrorsnextActions 聚合字段,apps/web_ui/src/views/TaskCenterView.vue 也已切到直接消费统一 activeTaskView,新增 recent action、open issues、next actions 展示并清理旧的零散字段引用;npm run typecheck 已重新通过。
  • 继续推进 P1.3 剩余页面迁移:apps/web_ui/src/views/ObserveView.vue 现已改为复用 WorkbenchControlleruseTaskViewModel(),不再自行 new useResearchWorkspace() / useCrawlerController() 或直接请求 /api/searchapps/web_ui/src/views/AnalyzeView.vue 也已切到消费共享 controller/system 状态与 active task VM,不再维护独立 useSystemController() 生命周期。相关前端 npm run typecheck 已再次通过。
  • 继续推进 P1.3 列表页收口:apps/web_ui/src/composables/useTaskViewModel.ts 本轮已新增 taskViews 列表 VM,把 report fallback 关联与列表态任务摘要统一收回共享映射层;apps/web_ui/src/views/TasksView.vue 也已重建为直接消费 taskViews 的页面壳,不再在页面内部混合 research/report/active-task 三段状态。相关前端 npm run typecheck 已再次通过。
  • 继续推进 P1.3 页面壳收口:apps/web_ui/src/views/CanvasView.vue 本轮已改为基于 activeTaskView + selectedTask 派生 workflow stage、tip 与 notice,不再用 watch 手工回填阶段状态;apps/web_ui/src/views/DeliverView.vue 也进一步切到优先消费 taskViews/activeTaskViewtaskView.report.taskId,移除页面内按 query 手工匹配 report 的逻辑;apps/web_ui/src/views/ObserveView.vue 则开始优先使用 active task 的 task-scoped crawler 资源来构建关键词、平台、recent action 与 handoff 上下文。相关前端 npm run typecheck 已再次通过。
  • 继续推进 P1.4 API 资源化收口:新增 docs/api-research-task-resources.md,明确 /api/research-tasks collection/detail/activate 与 task-scoped analysis-run/analysis-runs/analysis/crawler/report 资源契约,并记录与旧 /api/research/tasks/... 的兼容映射。
  • 继续推进 P1.4 前端路径切换收尾:本轮再次扫描 apps/web_ui/src,未再发现前端直接调用旧 /api/research/tasks/... 的代码;当前剩余问题已主要转为页面语义上的 task-scoped / task-vm 收口,而不是 research-task 资源路径迁移。
  • 继续推进 P2.2 系统级测试收口:本轮重新执行 tests/integration/test_web_api_smoke.py 得到 27 passed,并补跑 tests/unit/application/test_analysis_service.pytests/unit/application/test_report_service.py 得到 14 passed,进一步锁定 analysis trigger、report generation trigger 与 research-task 主链路 smoke contract。

2026-04-22

  • 继续推进 P1.1 report 写侧主线:services/application/report/report_service.py 本轮已新增 cancel_report(...),统一承接 report task 的取消用例;services/engines/report/flask_interface.py/api/report/cancel/<task_id> 当前已改为复用同一个 REPORT_APP_SERVICE,不再在 route 里直接操作 current_task/tasks_registry/task_lock。相关覆盖已补 tests/unit/application/test_report_service.pytests/integration/test_web_api_smoke.py
  • 继续推进 P1.1 report 运行时语义收口:report runtime 现已补 _ensure_task_not_cancelled(...)ReportTaskCancelledrun_report_generation(...) 在关键阶段和流式 progress 更新时都会尊重 cancelled 状态,避免取消后又被后续 running/completed 回写覆盖。
  • 继续推进 P1.1/P1.4 主线:新增 services/application/research/task_view_service.py,把 backend/research_routes.py 中原本由 route 手工完成的 task -> crawler/report resource 聚合视图拼装迁入 application facade;GET /api/research-tasks/task-views 当前已退化为直接委托该 service。相关回归已补 tests/unit/application/test_research_task_view_service.py,并与 tests/integration/test_web_api_smoke.py 重新通过。
  • 继续推进 P1.1/P1.2 主线:services/application/report/query_service.py 本轮已去除默认反向 import report runtime 的 fallback,改为只消费显式注入的 runtime/query callables;services/engines/report/flask_interface.py 当前在构造 REPORT_QUERY_SERVICE 时统一注入 report job getter / runtime status / snapshot / result loader,backend/research_routes.py 也改为复用这一个 runtime-wired query service。与此同时,report progress 的 missing fallback 已统一改走 ReportJobDTO.to_response_item(),补齐 unified_task 与 canonical engine_result
  • 本轮重新执行 pytest -q tests/unit/application/test_report_service.py tests/unit/application/test_report_query_service.py tests/integration/test_web_api_smoke.py -k "report",结果为 33 passed;同时 apps/web_uinpm run typecheck 继续通过。
  • 本轮重新执行 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/application/test_report_query_service.py tests/unit/application/test_research_task_view_service.py tests/unit/web_api/test_forum_runtime.py tests/unit/web_api/test_forum_runtime_helpers.py tests/integration/test_web_api_factory_smoke.py tests/integration/test_web_api_smoke.py,结果为 91 passed;同时 apps/web_uinpm run typecheck 继续通过。
  • 继续推进 P1.3 前端 task view model 主线:apps/web_ui/src/composables/useTaskViewModel.ts 本轮继续收掉非 active task 的 report query heuristic;列表态 taskViews 现在对非当前任务只认稳定的 report_job_id link,不再按 generated_query 去启发式匹配全局 report 任务。
  • 继续推进 P1.3 Observe 页面收口:apps/web_ui/src/views/ObserveView.vue 的 recent crawl 展示当前已开始优先消费 active task 的 linked crawler jobs,并继续让关键词、平台、recent action 与 handoff query 优先跟随 task-scoped crawler 资源,而不是默认回退到全局 crawler snapshot/logs。
  • 继续推进 P1.3 页面层目标任务收口:apps/web_ui/src/views/ObserveView.vue 本轮已新增 targetTaskId -> observeTaskView(taskViews/activeTaskView) 解析链,crawler 平台/关键词/状态、recent action、recent crawl、handoff query 与 buildTaskContext 等展示字段都开始优先跟随目标任务 vm,而不是默认混用 active task 与 props fallback。
  • 继续推进 P1.2 统一引擎输入输出契约:apps/web_api/runtime/search_dispatch.py 本轮已把 Query/Media/Insight 本地执行 adapter 收口到 canonical raw shape,成功结果现统一返回 status/summary/artifacts/metrics,失败结果补齐标准 error 对象;services/shared/models/engine_contract.py 也同步补了 string error、artifact/metrics 推断逻辑。相关定向回归 pytest -q tests/unit/shared/test_engine_contract_models.py tests/unit/application/test_analysis_service.py tests/unit/web_api/test_search_dispatch.py25 passed

2026-04-23

  • 继续推进 P1.1 report route thinning 主线:新增 services/application/report/export_service.py,把 task-scoped /api/report/export/md/<task_id>/api/report/export/pdf/<task_id> 的 IR 读取、Markdown/PDF renderer 调用、导出文件命名与响应描述符构造统一迁入 ReportExportServiceservices/engines/report/flask_interface.py 中对应 route 当前已退化为“参数解析 -> 调 service -> Flask response / 异常映射”的薄适配器。
  • 继续推进 P1.1 report export 主线:ReportExportService 本轮继续接管 /api/report/export/pdf-from-ir 的 payload 校验、document_ir 必填校验、optimize 归一化、PDF renderer 调用与下载文件名构造;services/engines/report/flask_interface.py 中对应 route 当前只保留 JSON 读取与 400/503 异常映射。
  • 继续推进 P1.1/P1.2 report query façade:ReportJobQueryService 本轮已新增 build_report_log_payload()build_report_templates_payload(),统一承接 /api/report/log/api/report/templates 的日志文件定位/截断读取/权限错误映射以及模板目录扫描;同时 ReportService.clear_report_log() 也已承接 /api/report/log/clear。这样 report 侧又减少了一批 route 直接碰文件系统与 renderer 的逻辑。
  • 继续推进 P1.1/P1.4 wiring 收口:backend/research_routes.py 当前已去掉对 services.engines.report.flask_interface 的反向 lazy import,改为通过 configure_report_app_service(...) 接收 runtime-wired REPORT_QUERY_SERVICE;这让 task-scoped report 资源 route 对 runtime 的依赖路径与 bootstrap/application 主线更一致。
  • 继续推进 P0/P1 report runtime 基础设施拆分:新增 services/engines/report/stream_runtime.pyservices/engines/report/task_runtime.py,将 flask_interface.py 中原先散落的 SSE subscriber 管理、事件历史以及 current-task/task-registry snapshot helper 抽成独立 runtime helper;ReportTask 当前已改为复用 TaskEventStream 维护事件历史,REPORT_TASK_RUNTIME 也已开始承接 runtime 任务仓库与 snapshot 读侧。
  • 当前 report application 边界已形成读/写/导出三段公开面:services/application/report/query_service.pyservices/application/report/report_service.pyservices/application/report/export_service.pyservices/application/report/__init__.py 已统一导出对应 façade 与错误类型,便于后续继续从 runtime route 中抽走剩余 report 主线逻辑。
  • 继续推进 P1.1 report stream 主线:新增 services/application/report/stream_service.py,把 /api/report/stream/<task_id> 的任务解析、Last-Event-ID 解析、历史回放、heartbeat 与终态收口迁入 application façade;services/engines/report/flask_interface.py 中对应 route 已进一步退化为 REPORT_STREAM_SERVICE 调用 + Flask Response 壳。与此同时,report runtime 当前生效的 stream/task helper 也已统一改走 REPORT_STREAM_SUBSCRIBERSREPORT_TASK_RUNTIMEserialize_sse_event(...),不再继续扩大旧的模块级平行实现。
  • 本轮重新执行 pytest -q tests/unit/engines/report/test_stream_runtime.py tests/unit/engines/report/test_task_runtime.py tests/unit/application/test_report_stream_service.py tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/integration/test_web_api_smoke.py -k "report",结果为 69 passed;随后补跑 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/application/test_report_stream_service.py tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/unit/application/test_research_task_view_service.py tests/unit/engines/report/test_stream_runtime.py tests/unit/engines/report/test_task_runtime.py tests/unit/web_api/test_forum_runtime.py tests/unit/web_api/test_forum_runtime_helpers.py tests/integration/test_web_api_factory_smoke.py tests/integration/test_web_api_smoke.py,结果为 135 passedapps/web_uinpm run typecheck 继续通过。
  • 本轮重新执行 pytest -q tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/integration/test_web_api_smoke.py -k "report",结果为 44 passed;随后补跑 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/unit/application/test_research_task_view_service.py tests/unit/web_api/test_forum_runtime.py tests/unit/web_api/test_forum_runtime_helpers.py tests/integration/test_web_api_factory_smoke.py tests/integration/test_web_api_smoke.py,结果为 112 passedapps/web_uinpm run typecheck 继续通过。
  • 本轮还额外执行 pytest -q tests/unit/application/test_report_query_service.py tests/unit/application/test_research_task_view_service.py tests/integration/test_web_api_smoke.py -k "report or task_view",结果为 34 passed;用于锁定 report query wiring 与 research task-scoped report 资源链路在去除反向 import 后仍保持稳定。
  • 本轮继续补跑 pytest -q tests/unit/engines/report/test_stream_runtime.py tests/unit/engines/report/test_task_runtime.py tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/integration/test_web_api_smoke.py -k "report",结果为 55 passed;随后把这两组 runtime helper 一并纳入宽回归 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/unit/application/test_research_task_view_service.py tests/unit/engines/report/test_stream_runtime.py tests/unit/engines/report/test_task_runtime.py tests/unit/web_api/test_forum_runtime.py tests/unit/web_api/test_forum_runtime_helpers.py tests/integration/test_web_api_factory_smoke.py tests/integration/test_web_api_smoke.py,结果为 121 passedapps/web_uinpm run typecheck 继续通过。
  • 继续推进 P1.3 共享 task vm 收口:apps/web_ui/src/composables/useTaskViewModel.ts 本轮新增 resolveTaskView() / resolveTaskContext()apps/web_ui/src/views/AnalyzeView.vueDeliverView.vueCanvasView.vue 当前都已开始直接复用共享 helper 来解析目标任务上下文,进一步减少页面层重复的 taskViewById + buildTaskContext(...) 拼装;前端 npm run typecheck 继续通过。
  • 继续推进 P1.2 analysis 读模型收口:services/shared/dto/analysis_run.py 本轮已补上 partial_results 读模型归一化,analysis run / task-scoped analysis resource 接口当前会把 legacy message/themes/sources 结构投影成统一的 engine_name/status/success/summary/artifacts/metrics/error;相关样例和断言也已同步更新到 tests/unit/shared/test_analysis_run_dto.pytests/unit/shared/test_task_analysis_resource_dto.pytests/unit/application/test_analysis_query_service.pytests/integration/test_web_api_smoke.py,组合验证为 46 passed
  • 继续推进 P1.3 Observe 页面状态来源收口:apps/web_ui/src/views/ObserveView.vue 本轮也已切到优先复用 resolveTaskView() / resolveTaskContext() 解析观察任务与页面上下文,currentTaskId/currentTaskQuery、crawler 平台/关键词/状态、recent action、draft hydration 与 emit 出去的 task context 现在都尽量跟随共享 task vm,而不是继续分散混用 activeTaskactiveTaskCrawlerResourceprops.selectedTask;前端 npm run typecheck 已再次通过。
  • 继续推进 P2.2 主链路 smoke:tests/integration/test_web_api_smoke.py 本轮新增 /api/crawler/start -> /api/research/tasks/<task_id>/crawler 的 mock completed crawler smoke,验证 crawler_job_id、task-scoped crawler status、result summary 与 linked current job 输出;连同前一轮 analysis/report smoke 重新回归后,当前 pytest -q tests/integration/test_web_api_smoke.py31 passed
  • 继续推进 P2.2 forum 汇总 smoke:tests/integration/test_web_api_smoke.py 本轮新增 /api/forum/log/api/forum/log/history 的低依赖 smoke,直接向 runtime forum.log 注入 Query/Insight/HOST 三段日志,并校验 host summary 的 parsed message 与 history 分页读取;整组 smoke 现为 32 passed
  • 本轮再次执行 apps/web_ui 下的 npm run typecheckvue-tsc --noEmit 通过。
  • 本轮修复了一处 report runtime wiring 回归:REPORT_APP_SERVICE 恢复通过动态 check_engines_ready() seam 兼容测试侧 monkeypatch,随后重新执行 pytest -q tests/unit/engines/report/test_stream_runtime.py tests/unit/engines/report/test_task_runtime.py tests/unit/engines/report/test_flask_interface_runtime_wiring.py tests/unit/application/test_report_stream_service.py tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/integration/test_web_api_smoke.py -k "report",结果为 81 passed, 24 deselected
  • 本轮还继续收紧了 backend/research_routes.py 的 report wiring fallback:task-scoped /api/research/tasks/<task_id>/report 以及 task-views 聚合在 report service 未配置时当前会显式返回 503,不再静默退化为空 ReportJobQueryService()。相关 smoke pytest -q tests/integration/test_web_api_smoke.py -k "report_resource or task_views or report"19 passed
  • 本轮又补跑 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/application/test_report_stream_service.py tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/unit/application/test_research_task_view_service.py tests/unit/engines/report/test_stream_runtime.py tests/unit/engines/report/test_task_runtime.py tests/unit/engines/report/test_flask_interface_runtime_wiring.py tests/unit/web_api/test_forum_runtime.py tests/unit/web_api/test_forum_runtime_helpers.py tests/integration/test_web_api_factory_smoke.py tests/integration/test_web_api_smoke.py,结果为 148 passedapps/web_uinpm run typecheck 继续通过。

2026-04-28

  • 继续推进 P1.2 analysis typed contract 主线:services/application/analysis/analysis_service.py 本轮已把 execute_search_dispatch_async(...) 内部剩余的裸 dict 编排收口到统一 EngineResult 对象;当前 EngineRunner 也已正式收紧为只返回 EngineResult,过渡性的 _run_engine(...) 兼容层已经移除,随后统一基于 EngineResult.success/summary/error 生成状态文案、写回 partial_results 和统计 success/failure,_record_analysis_run_result(...) 也同步改为直接消费 EngineResult
  • 同一轮已新增/扩展 tests/unit/application/test_analysis_service.py,补上“engine runner 直接返回 EngineResult 实例时仍能正确落库与聚合”的定向断言,进一步锁定 application 层只消费统一结果对象的 contract。
  • 本轮重新执行 pytest -q tests/unit/application/test_analysis_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/application/test_analysis_query_service.py tests/unit/web_api/test_search_dispatch.py -k "analysis or engine_result or search",结果为 39 passed
  • 本轮又补跑 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/application/test_research_task_view_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/web_api/test_search_dispatch.py tests/unit/web_api/test_http_routes_runtime_dependencies.py tests/unit/web_api/test_runtime_bootstrap.py tests/integration/test_web_api_smoke.py -k "search or analysis or task_view or resource_alias",结果为 64 passed, 35 deselected
  • 继续推进 P1.2 typed contract 到 runtime snapshot:apps/web_api/runtime/search_dispatch.py 中 Query/Media/Insight 的本地 adapter 当前已直接返回 EngineResult / EngineExecutionErrorapps/web_api/runtime/task_runtime_store.py 也已在写入 partial_results 时统一经过 EngineResult.from_raw(...).to_runtime_payload() 归一,避免 task runtime snapshot 再扩散 legacy string/dict 混合结构。
  • 继续推进 P1.1/P1.4 analysis task-view 聚合 typed 化:services/application/research/task_view_service.py 当前已支持在 application 层内部直接聚合带 to_response_payload() 的 analysis resource DTO,backend/research_routes.pyRESEARCH_TASK_VIEW_APP_SERVICE 也已切到注入 AnalysisRunQueryService.get_task_analysis_resource_dto(...),因此 /api/research-tasks/task-views 的 analysis 聚合现在会优先围绕 TaskAnalysisResourceDTO 运转,再在最外层 materialize 成 response payload。
  • 本轮重新执行 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/web_api/test_search_dispatch.py tests/unit/web_api/test_task_runtime_store.py -k "analysis or engine_result or task_runtime or search",结果为 43 passed
  • 本轮又补跑 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/application/test_research_task_view_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/web_api/test_search_dispatch.py tests/unit/web_api/test_task_runtime_store.py tests/unit/web_api/test_http_routes_runtime_dependencies.py tests/unit/web_api/test_runtime_bootstrap.py tests/integration/test_web_api_smoke.py -k "search or analysis or task_view or task_runtime or resource_alias",结果为 68 passed, 35 deselected
  • 本轮还补跑 pytest -q tests/unit/application/test_research_task_view_service.py tests/unit/application/test_analysis_query_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/web_api/test_search_dispatch.py tests/unit/web_api/test_task_runtime_store.py tests/integration/test_web_api_smoke.py -k "task_view or analysis or task_runtime or resource_alias or task_views",结果为 34 passed, 44 deselected
  • 本轮最终又补跑 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/application/test_research_task_view_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/web_api/test_search_dispatch.py tests/unit/web_api/test_task_runtime_store.py tests/unit/web_api/test_http_routes_runtime_dependencies.py tests/unit/web_api/test_runtime_bootstrap.py tests/integration/test_web_api_smoke.py -k "search or analysis or task_view or task_runtime or resource_alias or task_views",结果为 69 passed, 35 deselected
  • 继续推进 P1.1/P1.4 research route thinning 主线:backend/research_routes.py 本轮已去掉 ResearchTaskViewService(...) 的模块级装配,改为只保留 configure_research_task_view_app_service(...) + getter/proxy;真正的 task-view facade 现在统一由 apps/web_api/bootstrap/runtime.pybuild_runtime_services() 阶段注入,同时继续复用 task/analysis/crawler/report 几条 lazy seam,避免 capture 旧 store/service。
  • 本轮还补了一处 report runtime import-order wiring 缺口:services/engines/report/flask_interface.pyREPORT_APP_SERVICEclear_report_log 的绑定已改成调用时解析,避免 factory/bootstrap 路径在未预先 monkeypatch builtins 时因定义顺序触发 NameError
  • 本轮重新执行 pytest -q tests\\integration\\test_web_api_factory_smoke.py,结果为 4 passed;并补跑 pytest -q tests\\unit\\engines\\report\\test_flask_interface_runtime_wiring.py -k "research_report_proxy or services_bind_directly",结果为 2 passed, 4 deselected
  • 本轮最终又补跑 pytest -q tests\\unit\\application\\test_analysis_service.py tests\\unit\\application\\test_analysis_query_service.py tests\\unit\\application\\test_research_task_view_service.py tests\\unit\\web_api\\test_runtime_bootstrap.py tests\\unit\\web_api\\test_app_module_bootstrap.py tests\\unit\\web_api\\test_search_dispatch.py tests\\unit\\web_api\\test_task_runtime_store.py tests\\unit\\engines\\report\\test_flask_interface_runtime_wiring.py tests\\integration\\test_web_api_smoke.py tests\\integration\\test_web_api_factory_smoke.py -k "analysis or task_view or runtime or search or task_runtime or resource_alias or task_views or factory_app or research_report_proxy",结果为 84 passed, 21 deselected

2026-04-24

  • 继续推进 P1.1/P1.4 task-centered 聚合主线:services/application/research/task_view_service.py 本轮已补上 analysis 聚合,backend/research_routes.py 中的 RESEARCH_TASK_VIEW_APP_SERVICE 也开始统一注入 analysis/crawler/report 三条 task-scoped resource builder;GET /api/research-tasks/task-views 当前已能稳定输出 task + analysis + crawler + report 四段摘要,而不再只覆盖 crawler/report。
  • 继续推进 P1.1 analysis service 边界收口:services/application/analysis/analysis_service.py 本轮已去掉 submit_search_request(...) 里的 HTTP tuple 语义,改为只返回 application submission 结果对象;apps/web_api/runtime/search_dispatch.py 当前再负责把 submission 映射回 route 侧 (payload, status_code),因此 /api/search 的 200/400 行为保持兼容,但 service 本身不再直接处理 transport 语义。
  • 继续推进 P1.1/P1.4 research route thinning 主线:ResearchTaskViewService 本轮已继续接管 task-scoped analysis-run / analysis / analysis-runs 三条 research resource 的任务解析与 linked analysis_run_id 绑定,backend/research_routes.py 中对应路由已退化为“调 façade + 404 映射 + jsonify”;同一轮里 research route wiring 也切到通过惰性 task-service proxy 解析 RESEARCH_TASK_APP_SERVICE,避免测试/运行时切换 store 后 RESEARCH_TASK_VIEW_APP_SERVICE 捕获旧 service 实例。
  • 继续推进 P1.1/P1.4 crawler/report task-scoped resource 收口:ResearchTaskViewService 本轮又补上 build_task_crawler_resource_payload(...)build_task_report_resource_payload(...)backend/research_routes.py/api/research/tasks/<task_id>/crawler|report 当前也已改为复用同一个 application facade;route 层旧 _build_task_resource_response(...) 中转壳已删除,report service 未配置时仍显式返回 503,不再静默退化。
  • 继续推进 P1.1 report runtime 语义收口:services/engines/report/flask_interface.py 本轮把 run_report_generation(...)ReportTaskCancelled 与通用异常分支中的 current-task 清理改为 REPORT_TASK_RUNTIME.clear_current_task(task_id=...) 条件清理,避免旧任务晚到的失败/取消把新 current task 一并清掉。
  • 继续推进 P1.1 report runtime 启动收口:services/engines/report/flask_interface.py 本轮也已移除启动恢复阶段的 current_task/tasks_registry 过渡 shim;磁盘恢复结果当前会直接初始化 REPORT_TASK_RUNTIME,不再先写一层模块级旧全局再回灌。
  • 本轮重新执行 pytest -q tests/unit/application/test_analysis_service.py tests/unit/web_api/test_search_dispatch.py tests/unit/web_api/test_http_routes_runtime_dependencies.py tests/unit/web_api/test_runtime_bootstrap.py tests/integration/test_web_api_smoke.py -k "search or submit_search_request or task_view",结果为 43 passed, 37 deselected
  • 本轮重新执行 pytest -q tests/unit/application/test_research_task_view_service.py tests/integration/test_web_api_smoke.py -k "analysis or task_view or resource_alias",结果为 12 passed, 34 deselected
  • 本轮重新执行 pytest -q tests/unit/application/test_research_task_view_service.py tests/integration/test_web_api_smoke.py tests/unit/engines/report/test_flask_interface_runtime_wiring.py -k "task_view or analysis or crawler_resource or report_resource or resource_alias or report_service",结果为 18 passed, 34 deselected
  • 本轮还补跑 pytest -q tests/unit/application/test_research_task_view_service.py tests/unit/engines/report/test_flask_interface_runtime_wiring.py tests/unit/engines/report/test_task_runtime.py tests/integration/test_web_api_smoke.py -k "analysis or task_view or report or runtime_wiring or resource_alias",结果为 41 passed, 15 deselected
  • 本轮重新执行 pytest -q tests/unit/application/test_research_task_view_service.py tests/unit/engines/report/test_task_runtime.py tests/unit/engines/report/test_flask_interface_runtime_wiring.py tests/integration/test_web_api_smoke.py -k "task_view or runtime_wiring or clear_current_task or report_generation_error or report_resource or report",结果为 30 passed, 23 deselected
  • 本轮重新执行 pytest -q tests/unit/application/test_analysis_service.py tests/unit/web_api/test_search_dispatch.py tests/unit/web_api/test_http_routes_runtime_dependencies.py tests/unit/web_api/test_runtime_bootstrap.py tests/unit/engines/report/test_task_runtime.py tests/unit/engines/report/test_flask_interface_runtime_wiring.py tests/unit/application/test_report_stream_service.py tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/integration/test_web_api_smoke.py -k "search or runtime_wiring or report or clear_current_task or report_generation_error or task_view",结果为 118 passed, 22 deselected
  • 本轮又补跑 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/application/test_research_task_view_service.py tests/unit/application/test_report_stream_service.py tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/unit/engines/report/test_stream_runtime.py tests/unit/engines/report/test_task_runtime.py tests/unit/engines/report/test_flask_interface_runtime_wiring.py tests/unit/web_api/test_forum_runtime.py tests/unit/web_api/test_forum_runtime_helpers.py tests/integration/test_web_api_factory_smoke.py tests/integration/test_web_api_smoke.py,结果为 151 passedapps/web_uinpm run typecheck 继续通过。
  • 本轮最终又补跑 pytest -q tests/unit/application/test_analysis_service.py tests/unit/application/test_analysis_query_service.py tests/unit/shared/test_analysis_run_dto.py tests/unit/shared/test_task_analysis_resource_dto.py tests/unit/application/test_research_task_view_service.py tests/unit/application/test_report_stream_service.py tests/unit/application/test_report_export_service.py tests/unit/application/test_report_query_service.py tests/unit/application/test_report_service.py tests/unit/engines/report/test_stream_runtime.py tests/unit/engines/report/test_task_runtime.py tests/unit/engines/report/test_flask_interface_runtime_wiring.py tests/unit/web_api/test_forum_runtime.py tests/unit/web_api/test_forum_runtime_helpers.py tests/unit/web_api/test_search_dispatch.py tests/unit/web_api/test_http_routes_runtime_dependencies.py tests/unit/web_api/test_runtime_bootstrap.py tests/integration/test_web_api_factory_smoke.py tests/integration/test_web_api_smoke.py,结果为 177 passedapps/web_uinpm run typecheck 继续通过。

2026-04-17

  • 新增 apps/web_api/bootstrap/app_module.py,统一承接 app module 级别的 Flask/Socket.IO/runtime services/cleanup/search hooks/route wiring 装配;apps/web_api/app.py 现仅保留 import-path 入口壳、装配结果导出与 main()
  • 新增 tests/unit/web_api/test_app_module_bootstrap.py,覆盖 bootstrap_app_module(...) 的装配 contract。
  • 修复 bootstrap/app_module.py 因直接导入 runtime builders 导致 tests/integration/test_web_api_system_controls.py monkeypatch 失效的问题;本轮相关定向回归为 34 passed,扩大回归为 35 passed
  • 继续推进 P1.1 crawler application service:services/application/crawler/crawler_service.py 现已在 start_crawler() 中补齐研究任务默认采集参数同步,会在携带 research_task_id 时从统一 ResearchTaskDTO 读取 crawler_defaults / crawler_keywords_text 填充缺省 crawler payload,同时保持显式传入参数优先,并在状态回写时同步 generated_query
  • 新增/扩展 tests/unit/application/test_crawler_service.py,锁定“研究任务默认 crawler 参数同步 / 显式参数优先 / 缺失任务报错”的 application-layer contract;并补跑 tests/unit/application/test_research_task_service.pytests/unit/application/test_crawler_service.pytests/unit/backend/test_crawler_routes.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.py,当前共 57 passed
  • 继续推进 P0/P1 主线的 analysis 收口:services/application/analysis/analysis_service.py 现已新增 submit_search_request(...) façade,把 /api/search 原本留在 route 的 resolve_search_query -> dispatch_search_request 两段式 choreography 收进 application service;apps/web_api/interfaces/http_routes.py 当前只保留 payload 读取与响应透传。
  • apps/web_api/bootstrap/runtime.py 已同步新增 route-ready submit_search_request wiring,并继续通过传入的 search hook callables 保持 _APP_MODULE.search_hooks 这条 monkeypatch 兼容链路不回退。
  • 新增/扩展 tests/unit/application/test_analysis_service.pytests/unit/web_api/test_http_routes_runtime_dependencies.pytests/unit/web_api/test_runtime_bootstrap.py,锁定新的 analysis façade 行为与 bootstrap wiring;并补跑 tests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/integration/test_web_api_system_controls.py,当前相关组合回归为 51 passed
  • 继续推进 P0/P1 主线的 analysis runtime 收口:新增 AnalysisExecutionContext,把 process_registrycheck_app_statuslog_dirwrite_log 统一封装为 application 层 execution port;AnalysisService.submit_search_request(...) / dispatch_search_request(...) 已优先消费该 context,同时保留旧 kwargs 入口作为兼容层。
  • apps/web_api/bootstrap/runtime.pyapps/web_api/runtime/search_dispatch.py 已同步切换为预先装配 AnalysisExecutionContext,并把 route-ready dispatcher 传给 submit_search_request(...),从而继续保持 _APP_MODULE.search_hooks 这条 monkeypatch 链路可替换,但不再把四元 runtime 依赖逐个暴露到 route-level submit façade。
  • 新增/扩展 tests/unit/application/test_analysis_service.pytests/unit/web_api/test_runtime_bootstrap.pytests/unit/web_api/test_search_dispatch.py,并联合 tests/unit/web_api/test_http_routes_runtime_dependencies.pytests/integration/test_web_api_smoke.pytests/integration/test_web_api_factory_smoke.pytests/integration/test_web_api_system_controls.py 重新回归,当前相关组合回归为 52 passed
  • 继续推进 P0/P1 主线的 report 收口:ResearchTaskService 新增 sync_report_runtime_status(...),把 report runtime 阶段到 research task unified status 的映射收回 application 层;services/engines/report/flask_interface.py 中的 report 状态同步桥接现已改为调用这一统一入口,不再在 runtime 侧维护独立状态映射表。
  • ReportJobQueryService 已继续新增 build_report_status_payload()build_report_tasks_payload()build_report_progress_payload()/api/report/status/api/report/tasks/api/report/progress/<task_id> 三个路由当前都已退化为 query façade 的薄适配器,同时保持 progress 在任务缺失时返回 completed fallback 的兼容语义。
  • 新增/扩展 tests/unit/application/test_research_task_service.pytests/unit/application/test_report_query_service.pytests/unit/application/test_report_service.pytests/integration/test_web_api_smoke.py,锁定 report 状态同步入口、report runtime query façade 以及对应 route 适配行为;并补跑 tests/integration/test_web_api_factory_smoke.py,当前相关组合回归为 47 passed
  • 继续推进 P0/P1 主线的 analysis 状态同步收口:ResearchTaskService 新增 sync_analysis_runtime_status(...)AnalysisService 当前已改为通过 runtime 状态 queued/running/partial/failed 驱动 research task 阶段回写,保持既有 analysis_run_id 透传、runtime store 行为以及“部分成功仍保持 analyzing、失败回落 ready”的兼容语义;相关覆盖已补到 tests/unit/application/test_research_task_service.pytests/unit/application/test_analysis_service.pytests/unit/web_api/test_search_dispatch.py

2026-04-16

  • 继续推进 apps/web_api/app.py 瘦身:cleanup 注册已直接复用 RuntimeServices.cleanup_handler,去掉仅作中转的 _cleanup_runtime 包装层。
  • 继续收缩入口壳:新增 apps/web_api/bootstrap/http_config.py 承接前端 dev URL 配置解析,app.py 不再保留该 helper。
  • 继续收缩入口壳:新增 apps/web_api/bootstrap/cleanup.py 承接 reload-safe cleanup 注册,app.py 不再直接持有 atexit.unregister/register 细节。
  • 继续收缩入口壳:新增 apps/web_api/bootstrap/import_path.py 承接项目根路径注入,app.py 不再直接持有 sys.path 变更实现细节。
  • 新增 tests/integration/test_web_api_factory_smoke.py,补齐 create_app()、route wiring 与 Socket.IO handler 注册的最小 factory/bootstrap smoke coverage。
  • 新增 tests/integration/test_web_api_system_controls.py,补齐 /api/system/start/api/system/shutdown 的无子进程模式 contract test。
  • 补齐首页 dev redirect contract 与 /api/system/start 失败透传 contract。
  • 补齐 /api/system/shutdown 失败透传 contract,以及 tests/unit/web_api/test_import_path.py 对导入路径 helper 的行为锁定。
  • 收敛 tests/conftest.py 中的共享 fixture,统一隔离 ResearchTaskStore、app reload 与 cleanup handler 注销逻辑。
  • 新增 tests/unit/web_api/test_cleanup.py,固定 cleanup helper 的注册行为。
  • 将 Forum 启停、Forum 日志读取与普通 app 日志读写 callable 纳入 HttpRouteDependencies / bootstrap.runtime 装配,并新增 tests/unit/web_api/test_http_routes_runtime_dependencies.py 锁定路由层委托。
  • 联合既有 unit/integration 测试完成定向回归,当前相关 36 条测试通过。

2026-04-15

  • 建立本文档,作为项目整体架构优化与开发执行基线。
  • 确定先从 apps/web_api/app.py 拆分开始,作为整个重构的第一阶段入口。

  • 继续推进 apps/web_api/app.py 瘦身:新增 apps/web_api/interfaces/http_routes.pyapps/web_api/interfaces/socket_events.py,将运行时 HTTP 路由和 Socket.IO 事件注册从入口文件中抽离。

  • apps/web_api/app.py 现已收缩为应用创建、依赖装配、注册调用、清理钩子和 main() 启动入口;同时修复 SystemLifecycleService.start_async_shutdown() 漏传 stop_forum_engine / set_system_state 导致关机清理失败的问题。

  • 继续推进运行态收口:新增 apps/web_api/runtime/process_registry.pySystemStateRegistry,把进程状态快照与系统启动状态封装为显式 registry,并保留 get_system_state() 等兼容出口;processes 兼容出口已在后续轮次移除。

  • http_routessocket_eventsforum_runtimesystem_lifecycle 已开始通过 registry 读取或写入运行态;同时补充了 registry 单测与 /api/status 冒烟验证,手工校验确认 /api/status/api/system/status/api/search monkeypatch 链路保持可用。