foxclaw 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/index.js +1 -8
  2. package/package.json +1 -1
  3. package/python/.env.example +8 -0
  4. package/python/.github/workflows/ci.yml +47 -2
  5. package/python/CHANGELOG.md +57 -0
  6. package/python/CHANGELOG_CN.md +57 -0
  7. package/python/README.md +11 -2
  8. package/python/README_CN.md +32 -20
  9. package/python/bench/bm25_audit.py +1 -14
  10. package/python/bench/bm25_regression.py +0 -1
  11. package/python/bench/bm25_stress.py +0 -4
  12. package/python/bench/cd_audit.py +0 -23
  13. package/python/bench/chaos.py +6 -8
  14. package/python/bench/e2e_real_models.py +0 -1
  15. package/python/bench/llm_retry_test.py +2 -9
  16. package/python/bench/load_test.py +5 -8
  17. package/python/bench/vector_ivf_upgrade.py +1 -7
  18. package/python/docs/RELEASE.md +4 -3
  19. package/python/docs/RUN_CN.md +2 -2
  20. package/python/evals/governance/cases.py +0 -37
  21. package/python/evals/governance/run_evals.py +5 -37
  22. package/python/examples/governance_middleware_demo.py +1 -6
  23. package/python/examples/langgraph_governance_demo.py +7 -11
  24. package/python/foxclaw/__init__.py +34 -152
  25. package/python/foxclaw/agents/base.py +65 -136
  26. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  27. package/python/foxclaw/agents/impl/finance.py +4 -21
  28. package/python/foxclaw/agents/impl/office.py +7 -24
  29. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  30. package/python/foxclaw/agents/mixins/_context.py +0 -23
  31. package/python/foxclaw/agents/mixins/augment.py +10 -16
  32. package/python/foxclaw/agents/mixins/llm.py +7 -6
  33. package/python/foxclaw/agents/mixins/memory.py +7 -1
  34. package/python/foxclaw/agents/mixins/preference.py +58 -8
  35. package/python/foxclaw/agents/mixins/prompt.py +0 -6
  36. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  37. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  38. package/python/foxclaw/api/__init__.py +18 -77
  39. package/python/foxclaw/api/_state.py +43 -0
  40. package/python/foxclaw/api/{app.py → bootstrap.py} +5 -32
  41. package/python/foxclaw/api/middleware.py +5 -28
  42. package/python/foxclaw/api/routes/ahs.py +60 -73
  43. package/python/foxclaw/api/routes/attachments.py +5 -6
  44. package/python/foxclaw/api/routes/browser.py +7 -11
  45. package/python/foxclaw/api/routes/domain.py +18 -26
  46. package/python/foxclaw/api/routes/knowledge.py +3 -21
  47. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  48. package/python/foxclaw/api/routes/memory.py +28 -12
  49. package/python/foxclaw/api/routes/orchestration.py +3 -5
  50. package/python/foxclaw/api/routes/recovery.py +4 -3
  51. package/python/foxclaw/api/routes/review.py +2 -11
  52. package/python/foxclaw/api/routes/search.py +3 -7
  53. package/python/foxclaw/api/routes/system.py +33 -49
  54. package/python/foxclaw/api/routes/task.py +31 -49
  55. package/python/foxclaw/api/routes/ws.py +2 -6
  56. package/python/foxclaw/api/spine.py +1 -4
  57. package/python/foxclaw/browser/action/action_executor.py +1 -5
  58. package/python/foxclaw/browser/action/action_types.py +0 -1
  59. package/python/foxclaw/browser/action/url_policy.py +1 -5
  60. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  61. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  62. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  63. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  64. package/python/foxclaw/browser/service.py +7 -11
  65. package/python/foxclaw/capability/embedding_service.py +4 -6
  66. package/python/foxclaw/channels/__init__.py +9 -11
  67. package/python/foxclaw/channels/discord.py +4 -13
  68. package/python/foxclaw/channels/dispatcher.py +2 -27
  69. package/python/foxclaw/channels/feishu.py +10 -20
  70. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  71. package/python/foxclaw/channels/signing.py +9 -9
  72. package/python/foxclaw/channels/telegram.py +3 -4
  73. package/python/foxclaw/channels/weixin.py +7 -17
  74. package/python/foxclaw/cli.py +42 -13
  75. package/python/foxclaw/cognitive/reasoner.py +9 -10
  76. package/python/foxclaw/cognitive/state.py +18 -24
  77. package/python/foxclaw/core/audit_chain.py +1 -3
  78. package/python/foxclaw/core/audit_sink.py +3 -3
  79. package/python/foxclaw/core/compaction.py +8 -26
  80. package/python/foxclaw/core/config.py +2 -2
  81. package/python/foxclaw/core/confirm_nonce.py +6 -12
  82. package/python/foxclaw/core/envfile.py +2 -2
  83. package/python/foxclaw/core/failure_policy.py +17 -26
  84. package/python/foxclaw/core/gateway.py +6 -10
  85. package/python/foxclaw/core/governance_audit.py +3 -7
  86. package/python/foxclaw/core/idempotency.py +15 -22
  87. package/python/foxclaw/core/injection_guard.py +10 -29
  88. package/python/foxclaw/core/memory_adapter.py +30 -57
  89. package/python/foxclaw/core/metrics.py +4 -5
  90. package/python/foxclaw/core/model_router.py +36 -148
  91. package/python/foxclaw/core/paths.py +1 -1
  92. package/python/foxclaw/core/recovery.py +21 -34
  93. package/python/foxclaw/core/scheduler.py +23 -26
  94. package/python/foxclaw/core/secrets.py +0 -3
  95. package/python/foxclaw/core/sessions.py +4 -6
  96. package/python/foxclaw/core/settings.py +16 -32
  97. package/python/foxclaw/core/shared_write.py +11 -23
  98. package/python/foxclaw/core/steering_queue.py +12 -11
  99. package/python/foxclaw/core/system_bus.py +1 -1
  100. package/python/foxclaw/core/thought_chain.py +30 -32
  101. package/python/foxclaw/core/token_meter.py +9 -13
  102. package/python/foxclaw/core/tools.py +4 -12
  103. package/python/foxclaw/core/tracing.py +25 -40
  104. package/python/foxclaw/core/truth_pipeline.py +5 -9
  105. package/python/foxclaw/core/update_check.py +149 -0
  106. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  107. package/python/foxclaw/core/verify.py +6 -14
  108. package/python/foxclaw/core/web_search.py +4 -28
  109. package/python/foxclaw/finance/evidence.py +14 -18
  110. package/python/foxclaw/finance/fx.py +1 -3
  111. package/python/foxclaw/finance/graph.py +9 -18
  112. package/python/foxclaw/finance/models.py +20 -24
  113. package/python/foxclaw/finance/nlparse.py +12 -72
  114. package/python/foxclaw/finance/policy.py +8 -36
  115. package/python/foxclaw/finance/reasoner.py +3 -11
  116. package/python/foxclaw/finance/reasoning.py +13 -34
  117. package/python/foxclaw/finance/reconcile.py +5 -10
  118. package/python/foxclaw/finance/runtime.py +19 -46
  119. package/python/foxclaw/governance/middleware.py +13 -21
  120. package/python/foxclaw/harness/_base.py +2 -8
  121. package/python/foxclaw/harness/_policy.py +19 -38
  122. package/python/foxclaw/harness/drift.py +1 -13
  123. package/python/foxclaw/harness/event_bus.py +2 -5
  124. package/python/foxclaw/harness/harness.py +9 -97
  125. package/python/foxclaw/harness/loop_detection.py +11 -18
  126. package/python/foxclaw/itinerary/amap_client.py +2 -5
  127. package/python/foxclaw/itinerary/models.py +7 -7
  128. package/python/foxclaw/itinerary/reasoner.py +1 -6
  129. package/python/foxclaw/itinerary/reasoning.py +15 -21
  130. package/python/foxclaw/itinerary/runtime.py +8 -12
  131. package/python/foxclaw/kernel/capabilities.py +5 -6
  132. package/python/foxclaw/kernel/policy_gate.py +4 -10
  133. package/python/foxclaw/kernel/process_manager.py +17 -36
  134. package/python/foxclaw/kernel/scheduler.py +3 -9
  135. package/python/foxclaw/kernel/sentinel.py +13 -23
  136. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  137. package/python/foxclaw/kernel/spine.py +9 -9
  138. package/python/foxclaw/knowledge/__init__.py +4 -20
  139. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  140. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  141. package/python/foxclaw/learning/models.py +13 -13
  142. package/python/foxclaw/learning/reasoner.py +1 -4
  143. package/python/foxclaw/learning/runtime.py +19 -31
  144. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  145. package/python/foxclaw/mcp_server.py +2 -3
  146. package/python/foxclaw/orchestration/__init__.py +16 -33
  147. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  148. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  149. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  150. package/python/foxclaw/planning/llm_planner.py +3 -3
  151. package/python/foxclaw/planning/planner.py +6 -11
  152. package/python/foxclaw/search/agent_port.py +7 -11
  153. package/python/foxclaw/search/evidence.py +0 -4
  154. package/python/foxclaw/search/internal_search.py +3 -5
  155. package/python/foxclaw/search/models.py +11 -13
  156. package/python/foxclaw/search/planner.py +3 -3
  157. package/python/foxclaw/search/retriever.py +1 -1
  158. package/python/foxclaw/search/runtime.py +1 -8
  159. package/python/foxclaw/skills/__init__.py +46 -100
  160. package/python/foxclaw/skills/archive.py +111 -0
  161. package/python/foxclaw/skills/fetch.py +226 -0
  162. package/python/foxclaw/skills/hooks_spec.py +21 -25
  163. package/python/foxclaw/skills/install_cmd.py +216 -0
  164. package/python/foxclaw/skills/lifecycle.py +16 -42
  165. package/python/foxclaw/skills/markdown_skill.py +10 -9
  166. package/python/foxclaw/skills/mcp_client.py +5 -11
  167. package/python/foxclaw/skills/mcp_store.py +0 -3
  168. package/python/foxclaw/skills/permission.py +13 -48
  169. package/python/foxclaw/skills/pipeline.py +21 -53
  170. package/python/foxclaw/skills/policy.py +4 -10
  171. package/python/foxclaw/skills/review.py +3 -3
  172. package/python/frontend/deck/auth.js +0 -7
  173. package/python/frontend/deck/chat_model_picker.js +0 -54
  174. package/python/frontend/deck/domain.js +0 -20
  175. package/python/frontend/deck/main.js +0 -144
  176. package/python/frontend/deck/markdown.js +0 -4
  177. package/python/frontend/deck/settings.js +1 -18
  178. package/python/frontend/deck/state.js +0 -1
  179. package/python/frontend/deck/util.js +0 -2
  180. package/python/frontend/deck/visuals.js +0 -22
  181. package/python/frontend/planet/planet.js +0 -201
  182. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  183. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  184. package/python/frontend/v3/index.html +3 -3
  185. package/python/frontend/widget_runtime.js +0 -43
  186. package/python/frontend/widgets.js +0 -57
  187. package/python/frontend_src/index.html +1 -1
  188. package/python/frontend_src/package-lock.json +2 -2
  189. package/python/frontend_src/package.json +1 -1
  190. package/python/frontend_src/src/App.vue +3 -7
  191. package/python/frontend_src/src/api.js +0 -7
  192. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  193. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  194. package/python/frontend_src/src/components/Inspector.vue +0 -6
  195. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  196. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  197. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  198. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  199. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  200. package/python/frontend_src/src/components/Stage.vue +71 -25
  201. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  202. package/python/frontend_src/src/components/TopBar.vue +38 -10
  203. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  204. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  205. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  206. package/python/frontend_src/src/components/panels/MemoryPanel.vue +0 -1
  207. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  208. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  209. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  210. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  211. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  212. package/python/frontend_src/src/lib/fmt.js +0 -4
  213. package/python/frontend_src/src/lib/i18n.js +53 -0
  214. package/python/frontend_src/src/lib/locales/en.js +173 -0
  215. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  216. package/python/frontend_src/src/lib/md.js +0 -14
  217. package/python/frontend_src/src/main.js +2 -0
  218. package/python/frontend_src/src/stores/app.js +17 -32
  219. package/python/frontend_src/src/stores/chat.js +0 -34
  220. package/python/frontend_src/src/stores/model.js +0 -8
  221. package/python/frontend_src/src/stores/panels.js +0 -41
  222. package/python/frontend_src/src/stores/profile.js +31 -0
  223. package/python/frontend_src/src/stores/system.js +25 -0
  224. package/python/frontend_src/vite.config.js +0 -7
  225. package/python/pyproject.toml +6 -3
  226. package/python/requirements.txt +311 -0
  227. package/python/tests/conftest.py +0 -22
  228. package/python/tests/run_all.py +3 -66
  229. package/python/tests/test_agent_lifecycle.py +4 -38
  230. package/python/tests/test_ahs_p2.py +4 -25
  231. package/python/tests/test_anthropic_tools.py +1 -15
  232. package/python/tests/test_api.py +19 -9
  233. package/python/tests/test_api_auth.py +1 -5
  234. package/python/tests/test_attachments.py +9 -25
  235. package/python/tests/test_audit_chain.py +2 -7
  236. package/python/tests/test_audit_sink.py +0 -6
  237. package/python/tests/test_browser.py +2 -6
  238. package/python/tests/test_browser_request_guard.py +0 -4
  239. package/python/tests/test_capabilities.py +0 -6
  240. package/python/tests/test_channel_signing.py +2 -9
  241. package/python/tests/test_channels.py +1 -11
  242. package/python/tests/test_config_layering.py +0 -4
  243. package/python/tests/test_confirm_nonce.py +1 -12
  244. package/python/tests/test_context_compaction_wiring.py +1 -7
  245. package/python/tests/test_dispatch.py +0 -6
  246. package/python/tests/test_domain_evals.py +11 -80
  247. package/python/tests/test_domain_import.py +0 -6
  248. package/python/tests/test_domain_reasoners.py +0 -14
  249. package/python/tests/test_e2e.py +0 -17
  250. package/python/tests/test_emb_cache.py +7 -17
  251. package/python/tests/test_embedding_singleton.py +1 -3
  252. package/python/tests/test_evals_smoke.py +0 -24
  253. package/python/tests/test_event_spine.py +10 -18
  254. package/python/tests/test_evidence_semantic.py +1 -14
  255. package/python/tests/test_evidence_sse.py +3 -6
  256. package/python/tests/test_file_ingest.py +0 -11
  257. package/python/tests/test_finance.py +0 -9
  258. package/python/tests/test_finance_ledger.py +5 -12
  259. package/python/tests/test_finance_persist.py +8 -12
  260. package/python/tests/test_gateway.py +3 -22
  261. package/python/tests/test_governance.py +5 -52
  262. package/python/tests/test_governance_audit.py +4 -39
  263. package/python/tests/test_governance_middleware.py +1 -7
  264. package/python/tests/test_hkc.py +6 -24
  265. package/python/tests/test_hkc_index_build_manager.py +0 -33
  266. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  267. package/python/tests/test_hkc_search_event_sync.py +3 -54
  268. package/python/tests/test_import_path_guard.py +0 -6
  269. package/python/tests/test_injection_guard.py +18 -36
  270. package/python/tests/test_integration.py +5 -29
  271. package/python/tests/test_itinerary.py +0 -3
  272. package/python/tests/test_kee_semantic.py +0 -5
  273. package/python/tests/test_kernel.py +1 -11
  274. package/python/tests/test_learning.py +3 -12
  275. package/python/tests/test_llm_admin.py +4 -19
  276. package/python/tests/test_llm_planner.py +1 -23
  277. package/python/tests/test_markdown_skills.py +3 -14
  278. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  279. package/python/tests/test_mcp_connectors.py +3 -15
  280. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  281. package/python/tests/test_mcp_no_memory.py +3 -3
  282. package/python/tests/test_mcp_policy_gate.py +0 -1
  283. package/python/tests/test_mcp_server.py +6 -9
  284. package/python/tests/test_memory_recall.py +1 -4
  285. package/python/tests/test_metrics.py +0 -8
  286. package/python/tests/test_multi_chain.py +1 -13
  287. package/python/tests/test_multimodal.py +5 -18
  288. package/python/tests/test_negotiation.py +3 -13
  289. package/python/tests/test_orchestration.py +1 -11
  290. package/python/tests/test_orchestration_decoupled.py +0 -6
  291. package/python/tests/test_p3_p5_hardening.py +3 -17
  292. package/python/tests/test_parallel_dispatch.py +4 -12
  293. package/python/tests/test_pipeline.py +17 -58
  294. package/python/tests/test_plan_visible.py +4 -4
  295. package/python/tests/test_planner.py +0 -7
  296. package/python/tests/test_policy_gate.py +2 -3
  297. package/python/tests/test_profile.py +67 -0
  298. package/python/tests/test_readme_claims.py +163 -0
  299. package/python/tests/test_reasoner.py +2 -17
  300. package/python/tests/test_release_check.py +2 -13
  301. package/python/tests/test_resume_checkpoint.py +7 -15
  302. package/python/tests/test_routing.py +2 -12
  303. package/python/tests/test_scheduler.py +7 -10
  304. package/python/tests/test_search.py +1 -11
  305. package/python/tests/test_secrets.py +4 -10
  306. package/python/tests/test_semantic.py +1 -10
  307. package/python/tests/test_sessions.py +3 -4
  308. package/python/tests/test_settings.py +2 -18
  309. package/python/tests/test_shadow_learning.py +3 -43
  310. package/python/tests/test_shared_write.py +0 -7
  311. package/python/tests/test_skill_taint_gate.py +1 -10
  312. package/python/tests/test_skills.py +2 -18
  313. package/python/tests/test_skills_fetch.py +282 -0
  314. package/python/tests/test_stats_and_events.py +2 -5
  315. package/python/tests/test_streaming.py +2 -7
  316. package/python/tests/test_supervisor_pipeline.py +4 -5
  317. package/python/tests/test_supervisor_triage.py +4 -10
  318. package/python/tests/test_taint.py +2 -7
  319. package/python/tests/test_task_stream.py +1 -12
  320. package/python/tests/test_thinking_sse.py +0 -1
  321. package/python/tests/test_tool_center.py +1 -17
  322. package/python/tests/test_tool_parallel.py +2 -2
  323. package/python/tests/test_tool_params.py +1 -5
  324. package/python/tests/test_tool_use.py +0 -13
  325. package/python/tests/test_toplevel_concurrency.py +3 -4
  326. package/python/tests/test_tracing.py +7 -41
  327. package/python/tests/test_truth_crystallizer.py +4 -6
  328. package/python/tests/test_truth_flow.py +2 -7
  329. package/python/tests/test_update_check.py +95 -0
  330. package/python/tests/test_verify_evidence.py +8 -21
  331. package/python/tests/test_version_sync.py +35 -6
  332. package/python/tests/test_web_trigger.py +0 -17
  333. package/python/tools/check_faiss_type.py +0 -1
  334. package/python/tools/check_log.ps1 +0 -8
  335. package/python/tools/mypy_baseline.txt +12 -4
  336. package/python/tools/mypy_ratchet.py +52 -6
  337. package/python/tools/pack.py +0 -5
  338. package/python/tools/release_check.py +7 -7
  339. package/python/tools/restart_foxclaw.ps1 +5 -8
  340. package/python/tools/shared_write_audit.py +1 -15
  341. package/python/tools/size_check.py +1 -1
  342. package/python/tools/strip_comments.py +228 -0
  343. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  344. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -1,7 +1,7 @@
1
1
  """系统状态 / 模型路由 / 认知调度 / 事件总线 / 真值 / 进程 / 思考链 / Harness / 探活 / 监控与追踪。
2
2
 
3
- 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
4
- 晚绑定读取(保持测试可重绑定 `api._os_instance`);路由用 `@app` 装饰器在 import 时注册。
3
+ 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
4
+ 晚绑定读取(状态在 `_state`,测试可重绑 `_state._os_instance`);路由用 `@app` 装饰器在 import 时注册。
5
5
  """
6
6
 
7
7
  import os
@@ -16,21 +16,19 @@ from foxclaw import __version__
16
16
  import foxclaw.api as _api
17
17
  from .. import app
18
18
  from ..middleware import _gateway_on, _GATEWAY
19
- from ...core import settings
19
+ from ...core import settings, update_check
20
20
 
21
21
 
22
- # ── 系统状态 ───────────────────────────────────────────────
23
-
24
22
  @app.get("/api/status")
25
23
  async def get_system_status():
26
24
  """获取所有 Agent 的状态"""
27
- return _api._os_instance.get_all_status()
25
+ return _api.os_instance().get_all_status()
28
26
 
29
27
 
30
28
  @app.get("/api/tokens")
31
29
  async def get_token_summary():
32
30
  """Token 消耗摘要(成本可观测性):总量、按 Agent、按模型。"""
33
- meter = getattr(_api._os_instance, "token_meter", None)
31
+ meter = getattr(_api.current_os(), "token_meter", None)
34
32
  if meter is None:
35
33
  return {"error": "token meter 未启用"}
36
34
  return {
@@ -43,18 +41,16 @@ async def get_token_summary():
43
41
  @app.get("/api/tokens/report")
44
42
  async def get_token_report():
45
43
  """Token 消耗的人类可读文本报告。"""
46
- meter = getattr(_api._os_instance, "token_meter", None)
44
+ meter = getattr(_api.current_os(), "token_meter", None)
47
45
  if meter is None:
48
46
  return {"error": "token meter 未启用"}
49
47
  return {"report": meter.report()}
50
48
 
51
49
 
52
- # ── 模型路由与系统信息 ─────────────────────────────────────
53
-
54
50
  @app.get("/api/model")
55
51
  async def get_model():
56
52
  """当前模型路由状态 + 可用 provider 列表。"""
57
- return _api._os_instance.router.info()
53
+ return _api.os_instance().router.info()
58
54
 
59
55
 
60
56
  @app.get("/api/config/amap")
@@ -69,24 +65,24 @@ async def get_amap_config():
69
65
  @app.get("/api/system/info")
70
66
  async def get_system_info():
71
67
  """系统级只读信息:版本、运行参数、各 Agent、模块状态(供设置面板展示)。"""
68
+ update_check.maybe_refresh()
72
69
  agents = {}
73
- for aid, ag in _api._os_instance._agents.items():
70
+ for aid, ag in _api.os_instance()._agents.items():
74
71
  h = ag.harness
75
72
  agents[aid] = {
76
73
  "name": getattr(ag, "AGENT_NAME", aid),
77
74
  "shadow_mode": h.is_shadow_mode,
78
75
  "shadow_rounds": h.shadow_rounds,
79
76
  "run_count": getattr(h, "_run_count", 0),
80
- # 降级可见性(B 类修复):语义维度停用时,漂移检测只剩 tool/scope 两维,
81
- # 跑题不会被拦截 —— 前端需据此提示,不能只在服务端日志里静默。
82
77
  "semantic_degraded": getattr(h, "semantic_degraded", False),
83
78
  "embedding_mode": getattr(getattr(h, "sem_drift", None), "mode", "unknown"),
84
79
  }
85
80
  return {
86
81
  "version": __version__,
82
+ "update": update_check.status(),
87
83
  "data_dir": os.environ.get("FOXCLAW_DATA_DIR", "./foxclaw_data"),
88
84
  "shadow_rounds": settings.get_int("FOXCLAW_SHADOW_ROUNDS"),
89
- "model": _api._os_instance.router.info(),
85
+ "model": _api.os_instance().router.info(),
90
86
  "amap": {
91
87
  "web_key": bool(os.environ.get("AMAP_MAPS_API_KEY")),
92
88
  "js_key": bool(os.environ.get("AMAP_JS_KEY")),
@@ -105,21 +101,19 @@ class ModelSwitchPayload(BaseModel):
105
101
  async def switch_model(p: ModelSwitchPayload):
106
102
  """运行时切换模型 provider(多模型路由核心能力)。"""
107
103
  try:
108
- return _api._os_instance.router.switch(p.provider, p.model)
104
+ return _api.os_instance().router.switch(p.provider, p.model)
109
105
  except Exception as e:
110
106
  raise HTTPException(400, f"切换失败: {e}")
111
107
 
112
108
 
113
109
  @app.get("/api/agents/{agent_id}/status")
114
110
  async def get_agent_status(agent_id: str):
115
- agent = _api._os_instance.get_agent(agent_id)
111
+ agent = _api.os_instance().get_agent(agent_id)
116
112
  if not agent:
117
113
  raise HTTPException(404, f"Agent {agent_id} 不存在")
118
114
  return agent.get_status()
119
115
 
120
116
 
121
- # ── 认知调度 / 事件总线 / 真值 / 进程 / 认知状态 ─────────────
122
-
123
117
  class SchedulePreviewPayload(BaseModel):
124
118
  task: str
125
119
 
@@ -136,14 +130,14 @@ async def scheduler_preview(payload: SchedulePreviewPayload):
136
130
  @app.get("/api/scheduler/stats")
137
131
  async def scheduler_stats():
138
132
  """调度器记账:并发配额 + 提交/运行/完成/失败/峰值(可观测性)。"""
139
- return _api._os_instance.scheduler.stats()
133
+ return _api.os_instance().scheduler.stats()
140
134
 
141
135
 
142
136
  @app.get("/api/bus/history")
143
- async def bus_history(channel: str = None, limit: int = 30):
137
+ async def bus_history(channel: str | None = None, limit: int = 30):
144
138
  """系统事件总线历史:观测执行→审计→记忆的真值流转。"""
145
139
  try:
146
- return {"events": _api._os_instance.system_bus.history(channel, limit)}
140
+ return {"events": _api.os_instance().system_bus.history(channel, limit)}
147
141
  except Exception:
148
142
  return {"events": []}
149
143
 
@@ -151,16 +145,16 @@ async def bus_history(channel: str = None, limit: int = 30):
151
145
  @app.get("/api/truth/last")
152
146
  async def truth_last():
153
147
  """真值管线最近一次的 trace 与记忆更新。"""
154
- tp = _api._os_instance.truth_pipeline
148
+ tp = _api.os_instance().truth_pipeline
155
149
  return {"last_trace": tp.last_trace, "last_memory_update": tp.last_memory_update}
156
150
 
157
151
 
158
152
  @app.get("/api/processes")
159
- async def list_processes(state: str = None, limit: int = 30):
153
+ async def list_processes(state: str | None = None, limit: int = 30):
160
154
  """进程列表:观测任务作为"进程"的生命周期状态。"""
161
155
  try:
162
- return {"processes": _api._os_instance.process_manager.list(state, limit),
163
- "stats": _api._os_instance.process_manager.stats()}
156
+ return {"processes": _api.os_instance().process_manager.list(state, limit),
157
+ "stats": _api.os_instance().process_manager.stats()}
164
158
  except Exception:
165
159
  return {"processes": [], "stats": {}}
166
160
 
@@ -168,7 +162,7 @@ async def list_processes(state: str = None, limit: int = 30):
168
162
  @app.get("/api/processes/{process_id}")
169
163
  async def get_process(process_id: str):
170
164
  """单个进程详情:含完整状态历史(state_history)。"""
171
- rec = _api._os_instance.process_manager.get(process_id)
165
+ rec = _api.os_instance().process_manager.get(process_id)
172
166
  return rec.to_dict() if rec else {"error": "not_found"}
173
167
 
174
168
 
@@ -176,21 +170,18 @@ async def get_process(process_id: str):
176
170
  async def cognitive_state():
177
171
  """认知状态快照:系统此刻在想什么(目标/焦点/注意力/激活记忆/活跃进程)。"""
178
172
  try:
179
- return _api._os_instance.cognitive_state.snapshot()
173
+ return _api.os_instance().cognitive_state.snapshot()
180
174
  except Exception as e:
181
175
  return {"error": str(e)}
182
176
 
183
177
 
184
- # ── ThoughtChain 查询 ──────────────────────────────────────
185
-
186
178
  @app.get("/api/thoughtchain/{agent_id}/chain")
187
- async def get_thought_chain(agent_id: str, session_id: str = None):
179
+ async def get_thought_chain(agent_id: str, session_id: str | None = None):
188
180
  """获取 Agent 的推理链"""
189
- tc = _api._os_instance.thought_chain
181
+ tc = _api.os_instance().thought_chain
190
182
  if session_id:
191
183
  nodes = tc.get_chain(session_id, agent_id)
192
184
  else:
193
- # 返回最近 20 个节点
194
185
  nodes = tc.get_blocked_nodes(agent_id, limit=20)
195
186
  return {"nodes": [n.to_dict() for n in nodes]}
196
187
 
@@ -198,7 +189,7 @@ async def get_thought_chain(agent_id: str, session_id: str = None):
198
189
  @app.get("/api/thoughtchain/{agent_id}/blocked")
199
190
  async def get_blocked_nodes(agent_id: str, limit: int = 20):
200
191
  """获取被 Harness 截断的节点(审计用)"""
201
- tc = _api._os_instance.thought_chain
192
+ tc = _api.os_instance().thought_chain
202
193
  nodes = tc.get_blocked_nodes(agent_id, limit=limit)
203
194
  return {"blocked": [n.to_dict() for n in nodes], "count": len(nodes)}
204
195
 
@@ -206,7 +197,7 @@ async def get_blocked_nodes(agent_id: str, limit: int = 20):
206
197
  @app.get("/api/thoughtchain/{agent_id}/trend")
207
198
  async def get_drift_trend(agent_id: str, limit: int = 100):
208
199
  """获取漂移趋势数据(前端可视化用)"""
209
- tc = _api._os_instance.thought_chain
200
+ tc = _api.os_instance().thought_chain
210
201
  trend = tc.get_delta_trend(agent_id, limit=limit)
211
202
  return {"trend": trend, "agent_id": agent_id}
212
203
 
@@ -214,15 +205,13 @@ async def get_drift_trend(agent_id: str, limit: int = 100):
214
205
  @app.get("/api/thoughtchain/{agent_id}/summary")
215
206
  async def get_thought_summary(agent_id: str):
216
207
  """获取 ThoughtChain 健康摘要"""
217
- return _api._os_instance.thought_chain.get_summary(agent_id)
218
-
208
+ return _api.os_instance().thought_chain.get_summary(agent_id)
219
209
 
220
- # ── Harness 状态 ───────────────────────────────────────────
221
210
 
222
211
  @app.get("/api/harness/{agent_id}/status")
223
212
  async def get_harness_status(agent_id: str):
224
213
  """获取 Harness 状态:阈值、shadow mode、policy 摘要"""
225
- agent = _api._os_instance.get_agent(agent_id)
214
+ agent = _api.os_instance().get_agent(agent_id)
226
215
  if not agent:
227
216
  raise HTTPException(404, f"Agent {agent_id} 不存在")
228
217
  return {
@@ -236,15 +225,12 @@ async def get_harness_status(agent_id: str):
236
225
  @app.get("/api/harness/{agent_id}/policy")
237
226
  async def get_policy(agent_id: str):
238
227
  """获取 Agent 的 Policy YAML(完整内容)"""
239
- agent = _api._os_instance.get_agent(agent_id)
228
+ agent = _api.os_instance().get_agent(agent_id)
240
229
  if not agent:
241
230
  raise HTTPException(404, f"Agent {agent_id} 不存在")
242
231
  return agent.harness._policy
243
232
 
244
233
 
245
- # ── 健康探活 ───────────────────────────────────────────────
246
- # 标准探活端点(开放,不在鉴权前缀内、不触发 HKC 装配)。星球 UI 的 connect() 用它探活,
247
- # 也供部署/监控用。放在静态挂载("/")之前,否则会被 StaticFiles 的 catch-all 吞成 404。
248
234
  @app.get("/health")
249
235
  async def health():
250
236
  """存活探针(liveness):浅、快,不触发子系统。"""
@@ -255,7 +241,7 @@ async def health():
255
241
  async def health_ready():
256
242
  """就绪探针(readiness):逐子系统深度检查。**开放**(探针无需鉴权),
257
243
  组件只回 ok/stub/down,不泄露 provider 名等细节。不触发 HKC 惰性初始化。"""
258
- o = _api._os_instance
244
+ o = _api.current_os()
259
245
  comps = {}
260
246
  comps["hmr"] = "ok" if getattr(o, "hmr", None) is not None else "down"
261
247
  _router = getattr(o, "router", None)
@@ -264,9 +250,9 @@ async def health_ready():
264
250
  elif getattr(_router, "is_stub", True):
265
251
  comps["router"] = "stub"
266
252
  else:
267
- comps["router"] = "ok" # 不回传 provider 名(开放端点,防泄露)
253
+ comps["router"] = "ok"
268
254
  comps["scheduler"] = "ok" if getattr(o, "scheduler", None) is not None else "down"
269
- hkc_svc = getattr(o, "_hkc_service", None) # 只看已初始化的,不触发惰性 init
255
+ hkc_svc = getattr(o, "_hkc_service", None)
270
256
  comps["hkc"] = ("ok" if (hkc_svc is not None and getattr(hkc_svc, "available", False))
271
257
  else ("uninitialized" if hkc_svc is None else "degraded"))
272
258
  comps["mcp_server"] = "on" if getattr(o, "mcp_manager", None) is not None else "off"
@@ -274,8 +260,6 @@ async def health_ready():
274
260
  return {"status": "degraded" if down else "ok", "components": comps, "version": __version__}
275
261
 
276
262
 
277
- # ── 监控 / 追踪 / 网关 ──────────────────────────────────────
278
-
279
263
  @app.get("/api/metrics")
280
264
  async def metrics_json():
281
265
  """可观测:各端点/LLM 的计数、错误率、延迟分位(p50/p95/avg)+ uptime。"""
@@ -285,7 +269,7 @@ async def metrics_json():
285
269
 
286
270
  @app.get("/api/traces")
287
271
  async def list_traces(limit: int = 30, persisted: bool = False,
288
- slow_ms: float = None, errors_only: bool = False):
272
+ slow_ms: float | None = None, errors_only: bool = False):
289
273
  """请求链路。默认看内存里最近若干条;persisted=1 查**已落库的历史**
290
274
  (可加 slow_ms / errors_only 过滤)——「找出昨天那次慢请求」走这里。"""
291
275
  from ...core.tracing import TRACES
@@ -1,6 +1,6 @@
1
1
  """任务执行 / 流式直答 / 流式任务 路由与多模态护栏。
2
2
 
3
- 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
3
+ 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
4
4
  晚绑定读取;路由用 `@app` 装饰器在 import 时注册。流式限流器 `_STREAM_LIMITER` 为
5
5
  模块级单例,`__init__.py` 会 re-export(测试复位其桶容量)。
6
6
  """
@@ -19,18 +19,14 @@ from ...mcp_server import _RateLimiter as _RL
19
19
  from ...core import settings
20
20
 
21
21
 
22
- # ── 请求模型 ───────────────────────────────────────────────
23
-
24
22
  class TaskRequest(BaseModel):
25
23
  task: str
26
24
  agent_id: str = "supervisor"
27
25
  context: dict = {}
28
- images: list = [] # 多模态对话:[{media_type,data(base64)}];仅 /chat/stream 消费
29
- history: list = [] # 多轮上下文:[{role:'user'/'assistant',content}];前端带最近几轮,
30
- # 后端拼进 LLM messages,让对话连贯(否则每轮无状态、记不住上一句)
31
- session_id: str = "" # 会话归属:非空则本轮问答落盘到该会话(供"会话历史"回看)
32
- attachments: list = [] # 聊天附件:[{name,size,mime,url,text}];text 为提取正文(可为空),
33
- # 仅 /chat/stream 消费;随问题注入 LLM + 落盘会话供回看
26
+ images: list = []
27
+ history: list = []
28
+ session_id: str = ""
29
+ attachments: list = []
34
30
 
35
31
 
36
32
  class GoalRequest(BaseModel):
@@ -40,8 +36,6 @@ class GoalRequest(BaseModel):
40
36
  entities: list[str] = []
41
37
 
42
38
 
43
- # ── 任务执行 ───────────────────────────────────────────────
44
-
45
39
  @app.post("/api/task")
46
40
  async def run_task(req: TaskRequest):
47
41
  """执行任务,返回结果 + ThoughtChain 摘要。
@@ -53,33 +47,31 @@ async def run_task(req: TaskRequest):
53
47
  同一线程池,故无嵌套线程池死锁;
54
48
  · harness 累积态已在 harness.py 用 RLock 保护(嵌入在锁外,故并行不受锁影响)。"""
55
49
  def _work():
56
- # 经微内核调度器准入(默认无上限=直通;设了 FOXCLAW_MAX_CONCURRENCY 才背压)+ 生命周期事件
57
- return _api._os_instance.scheduler.run(
58
- lambda: asyncio.run(_api._os_instance.run(req.task, req.agent_id,
50
+ return _api.os_instance().scheduler.run(
51
+ lambda: asyncio.run(_api.os_instance().run(req.task, req.agent_id,
59
52
  context=req.context)),
60
53
  agent_id=req.agent_id, task=req.task)
61
54
  return await asyncio.to_thread(_work)
62
55
 
63
56
 
64
- # 流式限流 + 并发上限(防刷爆/资源耗尽;env 可配)
65
- _STREAM_LIMITER = _RL(settings.get_int("FOXCLAW_STREAM_RATE")) # 次/分
57
+ _STREAM_LIMITER = _RL(settings.get_int("FOXCLAW_STREAM_RATE"))
66
58
  _STREAM_SEM = _threading.Semaphore(settings.get_int("FOXCLAW_STREAM_CONCURRENCY"))
67
59
 
68
60
 
69
61
  _MAX_IMAGES = 6
70
- _MAX_IMG_B64 = 10 * 1024 * 1024 # 单图 base64 上限 ~10MB(≈7.5MB 原图)
71
- _MAX_IMG_TOTAL = 20 * 1024 * 1024 # 所有图 base64 累计上限(防 6×10MB=60MB 聚合 payload)
62
+ _MAX_IMG_B64 = 10 * 1024 * 1024
63
+ _MAX_IMG_TOTAL = 20 * 1024 * 1024
72
64
  _ALLOWED_IMG_MT = {"image/png", "image/jpeg", "image/webp", "image/gif"}
73
65
 
74
- _MAX_ATTACHMENTS = 6 # 单次问答最多附件数
75
- _ATTACH_TEXT_MAX = 20000 # 单附件提取正文上限
76
- _ATTACH_TEXT_TOTAL = 60000 # 全部附件正文累计上限(≈1.5 万 token,防撑爆上下文)
66
+ _MAX_ATTACHMENTS = 6
67
+ _ATTACH_TEXT_MAX = 20000
68
+ _ATTACH_TEXT_TOTAL = 60000
77
69
 
78
70
 
79
71
  def _sanitize_images(images):
80
72
  """校验/裁剪前端传来的多模态图片:限数量、限单图大小、限累计总量、白名单类型。
81
73
  防超大 payload/OOM(前端已缩放,这是对直连/恶意客户端的后端自保)。"""
82
- out = []
74
+ out: list[dict] = []
83
75
  total = 0
84
76
  if not isinstance(images, list):
85
77
  return out
@@ -92,7 +84,7 @@ def _sanitize_images(images):
92
84
  continue
93
85
  if not isinstance(d, str) or not d or len(d) > _MAX_IMG_B64:
94
86
  continue
95
- if total + len(d) > _MAX_IMG_TOTAL: # 累计超总上限 → 停(丢弃其余)
87
+ if total + len(d) > _MAX_IMG_TOTAL:
96
88
  break
97
89
  total += len(d)
98
90
  out.append({"media_type": mt, "data": d})
@@ -103,7 +95,7 @@ def _sanitize_attachments(attachments):
103
95
  """校验/裁剪前端传来的聊天附件:限数量、字段清洗、正文长度上限 + 累计上限。
104
96
  附件是前端经 /api/upload 上传后带回的引用({name,size,mime,url,text});这里只做
105
97
  直连/恶意客户端的自保,不信任 text 之外任何字段用于文件系统操作(下载按 url 里的 id 白名单)。"""
106
- out = []
98
+ out: list[dict] = []
107
99
  total = 0
108
100
  if not isinstance(attachments, list):
109
101
  return out
@@ -114,11 +106,12 @@ def _sanitize_attachments(attachments):
114
106
  text = str(a.get("text") or "")[:_ATTACH_TEXT_MAX]
115
107
  if not name and not text:
116
108
  continue
117
- if total + len(text) > _ATTACH_TEXT_TOTAL: # 累计正文超总上限 → 截断(丢弃后续正文)
109
+ if total + len(text) > _ATTACH_TEXT_TOTAL:
118
110
  text = text[:_ATTACH_TEXT_TOTAL - total]
119
111
  total += len(text)
120
- try: # size 可能是脏值("big"/None),护栏绝不因此抛异常
121
- size = int(a.get("size")) if a.get("size") else 0
112
+ try:
113
+ raw_size = a.get("size")
114
+ size = int(raw_size) if raw_size else 0
122
115
  except (TypeError, ValueError):
123
116
  size = 0
124
117
  out.append({
@@ -142,12 +135,12 @@ async def chat_stream(req: TaskRequest, request: Request):
142
135
  yield "data: " + json.dumps({"error": "rate limited"}) + "\n\n"
143
136
  return StreamingResponse(_rl(), media_type="text/event-stream")
144
137
 
145
- agent = _api._os_instance._agents.get(req.agent_id) or _api._os_instance.supervisor
138
+ agent = _api.os_instance()._agents.get(req.agent_id) or _api.os_instance().supervisor
146
139
  imgs = _sanitize_images(req.images)
147
140
  atts = _sanitize_attachments(req.attachments)
148
141
 
149
142
  async def async_sse():
150
- if not _STREAM_SEM.acquire(blocking=False): # 超并发上限 → 拒(不排队)
143
+ if not _STREAM_SEM.acquire(blocking=False):
151
144
  yield "data: " + json.dumps({"error": "busy, try again later"}) + "\n\n"
152
145
  return
153
146
  q: asyncio.Queue = asyncio.Queue()
@@ -158,12 +151,9 @@ async def chat_stream(req: TaskRequest, request: Request):
158
151
  _parts = []
159
152
 
160
153
  def _on_thinking(delta: str) -> None:
161
- # 推理走独立事件,不混进正文:前端折叠展示,正文仍只收 {"delta"}。
162
154
  loop.call_soon_threadsafe(q.put_nowait, {"thinking": delta})
163
155
 
164
156
  def _on_evidence(evs: list) -> None:
165
- # 执行证据(核验结论 + 来源链接/文件)整块送:前端挂在答案下方供核对,
166
- # 不进正文(正文是给用户读的答案,证据是给人查的凭据)。
167
157
  loop.call_soon_threadsafe(q.put_nowait, {"evidence": evs})
168
158
 
169
159
  try:
@@ -177,22 +167,21 @@ async def chat_stream(req: TaskRequest, request: Request):
177
167
  except Exception as e:
178
168
  loop.call_soon_threadsafe(q.put_nowait, {"error": str(e)})
179
169
  finally:
180
- # 落盘会话历史(问 + 答),供"会话/所有会话"回看。断连中止也保留已生成部分。
181
170
  if req.session_id:
182
171
  try:
183
- _api._os_instance.sessions.append(req.session_id, "user", req.task, attachments=atts)
172
+ _api.os_instance().sessions.append(req.session_id, "user", req.task, attachments=atts)
184
173
  _reply = "".join(_parts)
185
174
  if _reply:
186
- _api._os_instance.sessions.append(req.session_id, "assistant", _reply)
175
+ _api.os_instance().sessions.append(req.session_id, "assistant", _reply)
187
176
  except Exception:
188
177
  pass
189
- loop.call_soon_threadsafe(q.put_nowait, None) # 结束哨兵
178
+ loop.call_soon_threadsafe(q.put_nowait, None)
190
179
 
191
180
  _threading.Thread(target=_producer, daemon=True, name="chat-stream").start()
192
181
  try:
193
182
  while True:
194
183
  if await request.is_disconnected():
195
- stop.set(); break # 断连 → 终止 LLM 流
184
+ stop.set(); break
196
185
  try:
197
186
  item = await asyncio.wait_for(q.get(), timeout=0.5)
198
187
  except asyncio.TimeoutError:
@@ -200,15 +189,11 @@ async def chat_stream(req: TaskRequest, request: Request):
200
189
  if item is None:
201
190
  break
202
191
  if "thinking" in item:
203
- # 命名事件(SSE 规范):event: thinking + data。老客户端只认 data: 行也不会被
204
- # 误导成正文(delta 字段不存在);新前端按事件名折叠展示。
205
192
  yield ("event: thinking\ndata: "
206
193
  + json.dumps({"thinking": item["thinking"]}, ensure_ascii=False)
207
194
  + "\n\n")
208
195
  continue
209
196
  if "evidence" in item:
210
- # 与 thinking 同理走命名事件:老客户端拿不到这个事件名也不会把凭据
211
- # 当成正文拼进回答。
212
197
  yield ("event: evidence\ndata: "
213
198
  + json.dumps({"evidence": item["evidence"]}, ensure_ascii=False)
214
199
  + "\n\n")
@@ -235,16 +220,13 @@ async def task_stream(req: TaskRequest, request: Request):
235
220
  yield "data: " + json.dumps({"error": "rate limited"}) + "\n\n"
236
221
  return StreamingResponse(_rl(), media_type="text/event-stream")
237
222
 
238
- agent = _api._os_instance._agents.get(req.agent_id)
239
- # 【P3-3】P1-1 落地后主管走统一 pipeline(supervisor_stages),支持 run_task_stream,
240
- # 不再回落 assistant(助手已下放为技能)。未知 agent_id 仍回落主管兼容旧客户端
241
- # (旧前端可能还传 "assistant"/"admin"),但打 warning 不静默。
223
+ agent = _api.os_instance()._agents.get(req.agent_id)
242
224
  if agent is None:
243
225
  print(f"[Foxclaw API] ⚠ /api/task/stream 收到未知 agent_id={req.agent_id},回落 supervisor")
244
- agent = _api._os_instance.supervisor
226
+ agent = _api.os_instance().supervisor
245
227
 
246
228
  async def async_sse():
247
- if not _STREAM_SEM.acquire(blocking=False): # 超并发上限 → 拒(不排队)
229
+ if not _STREAM_SEM.acquire(blocking=False):
248
230
  yield "data: " + json.dumps({"error": "busy, try again later"}) + "\n\n"
249
231
  return
250
232
  q: asyncio.Queue = asyncio.Queue()
@@ -259,13 +241,13 @@ async def task_stream(req: TaskRequest, request: Request):
259
241
  except Exception as e:
260
242
  loop.call_soon_threadsafe(q.put_nowait, {"error": str(e)})
261
243
  finally:
262
- loop.call_soon_threadsafe(q.put_nowait, None) # 结束哨兵
244
+ loop.call_soon_threadsafe(q.put_nowait, None)
263
245
 
264
246
  _threading.Thread(target=_producer, daemon=True, name="task-stream-sse").start()
265
247
  try:
266
248
  while True:
267
249
  if await request.is_disconnected():
268
- stop.set(); break # 断连 → 停管线 + 停 act LLM
250
+ stop.set(); break
269
251
  try:
270
252
  item = await asyncio.wait_for(q.get(), timeout=0.5)
271
253
  except asyncio.TimeoutError:
@@ -45,21 +45,17 @@ async def harness_websocket(websocket: WebSocket):
45
45
  """
46
46
  api_key = os.environ.get("FOXCLAW_API_KEY", "").strip()
47
47
  if api_key:
48
- # 【P3-loopback】与 HTTP 中间件同口径:本机(127.0.0.1 / ::1)WS 客户端免 ?key=
49
- # 设 FOXCLAW_LOOPBACK_BYPASS=0 可关。详见 foxclaw.api.middleware._loopback_bypass_on
50
48
  ws_host = websocket.client.host if websocket.client else ""
51
49
  if not (_loopback_bypass_on() and _is_loopback_client(ws_host)):
52
50
  provided = (websocket.query_params.get("key")
53
51
  or websocket.headers.get("x-api-key"))
54
- # 【P3-1】常量时间比较(与 HTTP 中间件同一辅助函数);None 由 or "" 兜底——
55
- # api_key 非空时 not eq("", api_key) 必为 True,与原 None != api_key 行为一致
56
52
  if not _constant_time_eq(provided or "", api_key):
57
- await websocket.close(code=1008) # policy violation
53
+ await websocket.close(code=1008)
58
54
  return
59
55
  await manager.connect(websocket)
60
56
  try:
61
57
  while True:
62
- await websocket.receive_text() # 保持连接
58
+ await websocket.receive_text()
63
59
  except WebSocketDisconnect:
64
60
  manager.disconnect(websocket)
65
61
 
@@ -35,11 +35,9 @@ def _prewarm_hkc(osi):
35
35
  def _run():
36
36
  t0 = _t.time()
37
37
  try:
38
- svc = getattr(osi, "hkc", None) # 访问即触发 HKC 惰性装配
38
+ svc = getattr(osi, "hkc", None)
39
39
  if not svc or not getattr(svc, "available", False):
40
40
  return
41
- # 搜索索引已在 assemble() 内构建(向量索引持久化加载优先,避免每次启动全量
42
- # embedding 把 CPU 打满、启动卡几分钟),这里不再重复 build_index。
43
41
  try:
44
42
  lr = getattr(osi, "learning_runtime", None)
45
43
  if lr is not None:
@@ -50,6 +48,5 @@ def _prewarm_hkc(osi):
50
48
  except Exception as e:
51
49
  print(f"[Foxclaw API] · HKC 预热未启用(可选): {e}")
52
50
 
53
- # 晚绑定:把守护线程挂到主模块 `__init__.py` 的 `_prewarm_thread`(lifespan 关闭前需 join)。
54
51
  _api._prewarm_thread = threading.Thread(target=_run, name="hkc-prewarm", daemon=True)
55
52
  _api._prewarm_thread.start()
@@ -21,10 +21,8 @@ class ActionExecutor:
21
21
  return handler(action)
22
22
 
23
23
  def _do_navigate(self, action: WebAction):
24
- # URL 准入:此前直接把模型给的字符串交给 driver → file:// 读本机文件、
25
- # javascript: 执行脚本、内网/云元数据 SSRF。ValueError 由 service.act 转成错误返回。
26
24
  from .url_policy import validate_navigate_url
27
- target = validate_navigate_url(action.target)
25
+ target = validate_navigate_url(action.target or "")
28
26
  self.driver.navigate(target)
29
27
  return {"navigated_to": target}
30
28
 
@@ -44,8 +42,6 @@ class ActionExecutor:
44
42
  return {"scrolled": direction}
45
43
 
46
44
  def _do_extract(self, action: WebAction):
47
- # extract 本身不改变页面状态,只是触发一次重新语义化,
48
- # 具体的语义化交给上层 WebAdapter 统一处理,这里返回空占位即可
49
45
  return {"extract_requested": True}
50
46
 
51
47
  def _do_wait(self, action: WebAction):
@@ -9,7 +9,6 @@ from dataclasses import dataclass, field
9
9
  from typing import Optional, Any
10
10
 
11
11
 
12
- # HIR-Browser 支持的动作类型,故意保持极简,不做扩展性设计
13
12
  SUPPORTED_ACTIONS = ("navigate", "click", "type", "scroll", "extract", "wait")
14
13
 
15
14
 
@@ -30,12 +30,8 @@ from urllib.parse import urlparse
30
30
 
31
31
  ALLOWED_SCHEMES = ("http", "https")
32
32
 
33
- # 页面内部 scheme:内容由页面自身生成(data:/blob:),或压根不是网络请求(about:)。
34
- # 它们不经过网络栈,构不成 SSRF;拦下只会让内联图片 / 媒体源加载失败。
35
33
  INTERNAL_SCHEMES = ("data", "blob", "about")
36
34
 
37
- # 解析结果缓存:一个页面动辄几十个子资源,每次都 getaddrinfo 会让加载慢一个量级。
38
- # 带 TTL 而不是永久缓存 —— 永久缓存会让「先公网后内网」的切换永远不被重查。
39
35
  _RESOLVE_TTL = 60.0
40
36
  _MAX_CACHE = 512
41
37
  _resolve_cache: dict[str, tuple[bool, float]] = {}
@@ -67,7 +63,7 @@ def _resolve_internal(host: str) -> bool:
67
63
  return False
68
64
  internal = False
69
65
  for info in infos:
70
- addr = str(info[4][0]).split("%", 1)[0] # IPv6 可能带 scope id
66
+ addr = str(info[4][0]).split("%", 1)[0]
71
67
  try:
72
68
  parsed = ipaddress.ip_address(addr)
73
69
  except ValueError:
@@ -18,7 +18,7 @@ from ..action.action_types import WebAction, SUPPORTED_ACTIONS
18
18
  class WebAdapter(BaseAdapter):
19
19
 
20
20
  def __init__(self, headless: bool = True):
21
- self._session = None
21
+ self._session: "SessionManager | None" = None
22
22
  self._headless = headless
23
23
 
24
24
  def _ensure_session(self):