foxclaw 0.1.0 → 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 (514) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +4 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +173 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +167 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/CHANGELOG.md +57 -0
  11. package/python/CHANGELOG_CN.md +57 -0
  12. package/python/Dockerfile +64 -0
  13. package/python/LICENSE +21 -0
  14. package/python/MANIFEST.in +14 -0
  15. package/python/README.md +201 -0
  16. package/python/README_CN.md +336 -0
  17. package/python/bench/bm25_audit.py +186 -0
  18. package/python/bench/bm25_regression.py +177 -0
  19. package/python/bench/bm25_stress.py +155 -0
  20. package/python/bench/cd_audit.py +215 -0
  21. package/python/bench/chaos.py +140 -0
  22. package/python/bench/concurrency.py +103 -0
  23. package/python/bench/e2e_real_models.py +94 -0
  24. package/python/bench/llm_retry_test.py +241 -0
  25. package/python/bench/load_test.py +193 -0
  26. package/python/bench/vector_ivf_upgrade.py +128 -0
  27. package/python/docs/CHANNELS.md +195 -0
  28. package/python/docs/GOVERNANCE.md +164 -0
  29. package/python/docs/RELEASE.md +110 -0
  30. package/python/docs/RUN.md +75 -0
  31. package/python/docs/RUN_CN.md +121 -0
  32. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  33. package/python/evals/__init__.py +0 -0
  34. package/python/evals/governance/README.md +61 -0
  35. package/python/evals/governance/__init__.py +0 -0
  36. package/python/evals/governance/cases.py +115 -0
  37. package/python/evals/governance/compare_embeddings.py +94 -0
  38. package/python/evals/governance/convergence.svg +11 -0
  39. package/python/evals/governance/report.json +371 -0
  40. package/python/evals/governance/run_evals.py +368 -0
  41. package/python/examples/governance_middleware_demo.py +72 -0
  42. package/python/examples/langgraph_governance_demo.py +104 -0
  43. package/python/examples/sample_itinerary.csv +6 -0
  44. package/python/examples/sample_knowledge.csv +9 -0
  45. package/python/examples/sample_transactions.csv +14 -0
  46. package/python/foxclaw/__init__.py +408 -0
  47. package/python/foxclaw/agents/__init__.py +12 -0
  48. package/python/foxclaw/agents/agents.py +35 -0
  49. package/python/foxclaw/agents/base.py +794 -0
  50. package/python/foxclaw/agents/impl/__init__.py +1 -0
  51. package/python/foxclaw/agents/impl/_helpers.py +70 -0
  52. package/python/foxclaw/agents/impl/finance.py +231 -0
  53. package/python/foxclaw/agents/impl/office.py +350 -0
  54. package/python/foxclaw/agents/impl/supervisor.py +592 -0
  55. package/python/foxclaw/agents/mixins/_context.py +128 -0
  56. package/python/foxclaw/agents/mixins/augment.py +143 -0
  57. package/python/foxclaw/agents/mixins/llm.py +67 -0
  58. package/python/foxclaw/agents/mixins/memory.py +41 -0
  59. package/python/foxclaw/agents/mixins/preference.py +131 -0
  60. package/python/foxclaw/agents/mixins/prompt.py +71 -0
  61. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  62. package/python/foxclaw/agents/mixins/tool_exec.py +417 -0
  63. package/python/foxclaw/api/__init__.py +224 -0
  64. package/python/foxclaw/api/_state.py +43 -0
  65. package/python/foxclaw/api/bootstrap.py +96 -0
  66. package/python/foxclaw/api/middleware.py +294 -0
  67. package/python/foxclaw/api/routes/ahs.py +404 -0
  68. package/python/foxclaw/api/routes/attachments.py +116 -0
  69. package/python/foxclaw/api/routes/browser.py +35 -0
  70. package/python/foxclaw/api/routes/domain.py +306 -0
  71. package/python/foxclaw/api/routes/knowledge.py +145 -0
  72. package/python/foxclaw/api/routes/llm_admin.py +449 -0
  73. package/python/foxclaw/api/routes/memory.py +105 -0
  74. package/python/foxclaw/api/routes/orchestration.py +39 -0
  75. package/python/foxclaw/api/routes/recovery.py +44 -0
  76. package/python/foxclaw/api/routes/review.py +266 -0
  77. package/python/foxclaw/api/routes/search.py +33 -0
  78. package/python/foxclaw/api/routes/system.py +311 -0
  79. package/python/foxclaw/api/routes/task.py +263 -0
  80. package/python/foxclaw/api/routes/ws.py +64 -0
  81. package/python/foxclaw/api/spine.py +52 -0
  82. package/python/foxclaw/browser/__init__.py +13 -0
  83. package/python/foxclaw/browser/action/__init__.py +1 -0
  84. package/python/foxclaw/browser/action/action_executor.py +50 -0
  85. package/python/foxclaw/browser/action/action_types.py +44 -0
  86. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  87. package/python/foxclaw/browser/action/url_policy.py +143 -0
  88. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  89. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  90. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  91. package/python/foxclaw/browser/driver/__init__.py +1 -0
  92. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  93. package/python/foxclaw/browser/driver/playwright_driver.py +170 -0
  94. package/python/foxclaw/browser/perception/__init__.py +1 -0
  95. package/python/foxclaw/browser/perception/dom_extractor.py +42 -0
  96. package/python/foxclaw/browser/perception/element_indexer.py +56 -0
  97. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  98. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  99. package/python/foxclaw/browser/service.py +144 -0
  100. package/python/foxclaw/browser/session/__init__.py +1 -0
  101. package/python/foxclaw/browser/session/session_manager.py +57 -0
  102. package/python/foxclaw/capability/__init__.py +12 -0
  103. package/python/foxclaw/capability/embedding_service.py +65 -0
  104. package/python/foxclaw/channels/__init__.py +138 -0
  105. package/python/foxclaw/channels/discord.py +175 -0
  106. package/python/foxclaw/channels/dispatcher.py +179 -0
  107. package/python/foxclaw/channels/feishu.py +215 -0
  108. package/python/foxclaw/channels/openclaw_bridge.py +151 -0
  109. package/python/foxclaw/channels/signing.py +187 -0
  110. package/python/foxclaw/channels/telegram.py +120 -0
  111. package/python/foxclaw/channels/weixin.py +215 -0
  112. package/python/foxclaw/cli.py +183 -0
  113. package/python/foxclaw/cognitive/__init__.py +4 -0
  114. package/python/foxclaw/cognitive/reasoner.py +81 -0
  115. package/python/foxclaw/cognitive/state.py +141 -0
  116. package/python/foxclaw/core/__init__.py +9 -0
  117. package/python/foxclaw/core/audit_chain.py +131 -0
  118. package/python/foxclaw/core/audit_sink.py +100 -0
  119. package/python/foxclaw/core/compaction.py +265 -0
  120. package/python/foxclaw/core/config.py +65 -0
  121. package/python/foxclaw/core/confirm_nonce.py +151 -0
  122. package/python/foxclaw/core/dispatch.py +27 -0
  123. package/python/foxclaw/core/envfile.py +48 -0
  124. package/python/foxclaw/core/failure_policy.py +142 -0
  125. package/python/foxclaw/core/gateway.py +81 -0
  126. package/python/foxclaw/core/governance_audit.py +171 -0
  127. package/python/foxclaw/core/idempotency.py +282 -0
  128. package/python/foxclaw/core/injection_guard.py +202 -0
  129. package/python/foxclaw/core/memory_adapter.py +489 -0
  130. package/python/foxclaw/core/metrics.py +88 -0
  131. package/python/foxclaw/core/model_router.py +985 -0
  132. package/python/foxclaw/core/paths.py +103 -0
  133. package/python/foxclaw/core/recovery.py +349 -0
  134. package/python/foxclaw/core/scheduler.py +141 -0
  135. package/python/foxclaw/core/secrets.py +131 -0
  136. package/python/foxclaw/core/sessions.py +155 -0
  137. package/python/foxclaw/core/settings.py +414 -0
  138. package/python/foxclaw/core/shared_write.py +186 -0
  139. package/python/foxclaw/core/steering_queue.py +154 -0
  140. package/python/foxclaw/core/system_bus.py +84 -0
  141. package/python/foxclaw/core/thought_chain.py +316 -0
  142. package/python/foxclaw/core/token_meter.py +231 -0
  143. package/python/foxclaw/core/tools.py +164 -0
  144. package/python/foxclaw/core/tracing.py +375 -0
  145. package/python/foxclaw/core/truth_pipeline.py +110 -0
  146. package/python/foxclaw/core/update_check.py +149 -0
  147. package/python/foxclaw/core/vec_recall_adapter.py +477 -0
  148. package/python/foxclaw/core/verify.py +173 -0
  149. package/python/foxclaw/core/web_search.py +218 -0
  150. package/python/foxclaw/finance/__init__.py +15 -0
  151. package/python/foxclaw/finance/evidence.py +149 -0
  152. package/python/foxclaw/finance/fx.py +98 -0
  153. package/python/foxclaw/finance/graph.py +179 -0
  154. package/python/foxclaw/finance/models.py +146 -0
  155. package/python/foxclaw/finance/nlparse.py +515 -0
  156. package/python/foxclaw/finance/policy.py +301 -0
  157. package/python/foxclaw/finance/reasoner.py +121 -0
  158. package/python/foxclaw/finance/reasoning.py +319 -0
  159. package/python/foxclaw/finance/reconcile.py +175 -0
  160. package/python/foxclaw/finance/runtime.py +500 -0
  161. package/python/foxclaw/governance/__init__.py +24 -0
  162. package/python/foxclaw/governance/middleware.py +176 -0
  163. package/python/foxclaw/harness/__init__.py +17 -0
  164. package/python/foxclaw/harness/_base.py +66 -0
  165. package/python/foxclaw/harness/_policy.py +340 -0
  166. package/python/foxclaw/harness/drift.py +77 -0
  167. package/python/foxclaw/harness/event_bus.py +28 -0
  168. package/python/foxclaw/harness/harness.py +358 -0
  169. package/python/foxclaw/harness/loop_detection.py +145 -0
  170. package/python/foxclaw/itinerary/__init__.py +10 -0
  171. package/python/foxclaw/itinerary/amap_client.py +114 -0
  172. package/python/foxclaw/itinerary/models.py +80 -0
  173. package/python/foxclaw/itinerary/reasoner.py +96 -0
  174. package/python/foxclaw/itinerary/reasoning.py +168 -0
  175. package/python/foxclaw/itinerary/runtime.py +144 -0
  176. package/python/foxclaw/kernel/__init__.py +13 -0
  177. package/python/foxclaw/kernel/capabilities.py +72 -0
  178. package/python/foxclaw/kernel/policy_gate.py +108 -0
  179. package/python/foxclaw/kernel/process_manager.py +186 -0
  180. package/python/foxclaw/kernel/scheduler.py +125 -0
  181. package/python/foxclaw/kernel/sentinel.py +443 -0
  182. package/python/foxclaw/kernel/sentinel_client.py +243 -0
  183. package/python/foxclaw/kernel/spine.py +97 -0
  184. package/python/foxclaw/knowledge/__init__.py +360 -0
  185. package/python/foxclaw/knowledge/truth_crystallizer.py +89 -0
  186. package/python/foxclaw/learning/__init__.py +12 -0
  187. package/python/foxclaw/learning/knowledge_graph.py +144 -0
  188. package/python/foxclaw/learning/models.py +93 -0
  189. package/python/foxclaw/learning/reasoner.py +67 -0
  190. package/python/foxclaw/learning/runtime.py +351 -0
  191. package/python/foxclaw/learning/spaced_repetition.py +91 -0
  192. package/python/foxclaw/mcp_server.py +188 -0
  193. package/python/foxclaw/orchestration/__init__.py +167 -0
  194. package/python/foxclaw/pipeline/__init__.py +23 -0
  195. package/python/foxclaw/pipeline/cognitive_pipeline.py +429 -0
  196. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  197. package/python/foxclaw/pipeline/supervisor_stages.py +201 -0
  198. package/python/foxclaw/planning/__init__.py +17 -0
  199. package/python/foxclaw/planning/llm_planner.py +232 -0
  200. package/python/foxclaw/planning/planner.py +94 -0
  201. package/python/foxclaw/search/__init__.py +24 -0
  202. package/python/foxclaw/search/agent_port.py +190 -0
  203. package/python/foxclaw/search/evidence.py +109 -0
  204. package/python/foxclaw/search/external_search.py +50 -0
  205. package/python/foxclaw/search/hkc_read_port.py +60 -0
  206. package/python/foxclaw/search/internal_search.py +71 -0
  207. package/python/foxclaw/search/models.py +127 -0
  208. package/python/foxclaw/search/planner.py +44 -0
  209. package/python/foxclaw/search/retriever.py +39 -0
  210. package/python/foxclaw/search/runtime.py +127 -0
  211. package/python/foxclaw/search/strategy.py +44 -0
  212. package/python/foxclaw/skills/__init__.py +559 -0
  213. package/python/foxclaw/skills/archive.py +111 -0
  214. package/python/foxclaw/skills/fetch.py +226 -0
  215. package/python/foxclaw/skills/hooks_spec.py +69 -0
  216. package/python/foxclaw/skills/install_cmd.py +216 -0
  217. package/python/foxclaw/skills/lifecycle.py +252 -0
  218. package/python/foxclaw/skills/markdown_skill.py +175 -0
  219. package/python/foxclaw/skills/markdown_store.py +39 -0
  220. package/python/foxclaw/skills/mcp_client.py +184 -0
  221. package/python/foxclaw/skills/mcp_store.py +40 -0
  222. package/python/foxclaw/skills/permission.py +145 -0
  223. package/python/foxclaw/skills/pipeline.py +354 -0
  224. package/python/foxclaw/skills/policy.py +74 -0
  225. package/python/foxclaw/skills/review.py +72 -0
  226. package/python/foxclaw/skills/store.py +57 -0
  227. package/python/frontend/assets/core_bg.jpg +0 -0
  228. package/python/frontend/deck/auth.js +25 -0
  229. package/python/frontend/deck/chat_model_picker.js +483 -0
  230. package/python/frontend/deck/domain.js +276 -0
  231. package/python/frontend/deck/main.js +1806 -0
  232. package/python/frontend/deck/markdown.js +44 -0
  233. package/python/frontend/deck/settings.js +307 -0
  234. package/python/frontend/deck/state.js +5 -0
  235. package/python/frontend/deck/util.js +4 -0
  236. package/python/frontend/deck/visuals.js +190 -0
  237. package/python/frontend/deck.css +855 -0
  238. package/python/frontend/deck.html +686 -0
  239. package/python/frontend/deck_v2.html +341 -0
  240. package/python/frontend/planet/index.html +266 -0
  241. package/python/frontend/planet/planet.css +579 -0
  242. package/python/frontend/planet/planet.js +1436 -0
  243. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  244. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  245. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  246. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  247. package/python/frontend/v3/index.html +15 -0
  248. package/python/frontend/widget_runtime.js +252 -0
  249. package/python/frontend/widgets.js +639 -0
  250. package/python/frontend_src/index.html +14 -0
  251. package/python/frontend_src/package-lock.json +1376 -0
  252. package/python/frontend_src/package.json +19 -0
  253. package/python/frontend_src/src/App.vue +58 -0
  254. package/python/frontend_src/src/api.js +82 -0
  255. package/python/frontend_src/src/components/ChatDock.vue +365 -0
  256. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  257. package/python/frontend_src/src/components/DocViewer.vue +162 -0
  258. package/python/frontend_src/src/components/Inspector.vue +235 -0
  259. package/python/frontend_src/src/components/LeftRail.vue +415 -0
  260. package/python/frontend_src/src/components/MenuPopover.vue +128 -0
  261. package/python/frontend_src/src/components/MessageStream.vue +389 -0
  262. package/python/frontend_src/src/components/ModelPicker.vue +350 -0
  263. package/python/frontend_src/src/components/QuickIngest.vue +112 -0
  264. package/python/frontend_src/src/components/Stage.vue +272 -0
  265. package/python/frontend_src/src/components/ToolMenu.vue +141 -0
  266. package/python/frontend_src/src/components/TopBar.vue +118 -0
  267. package/python/frontend_src/src/components/panels/ActivityPanel.vue +82 -0
  268. package/python/frontend_src/src/components/panels/AgentsPanel.vue +83 -0
  269. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +425 -0
  270. package/python/frontend_src/src/components/panels/MemoryPanel.vue +117 -0
  271. package/python/frontend_src/src/components/panels/PanelBox.vue +50 -0
  272. package/python/frontend_src/src/components/panels/ReviewPanel.vue +292 -0
  273. package/python/frontend_src/src/components/panels/SettingsPanel.vue +358 -0
  274. package/python/frontend_src/src/components/panels/TasksPanel.vue +73 -0
  275. package/python/frontend_src/src/components/panels/ToolsPanel.vue +746 -0
  276. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  277. package/python/frontend_src/src/lib/fmt.js +44 -0
  278. package/python/frontend_src/src/lib/i18n.js +53 -0
  279. package/python/frontend_src/src/lib/locales/en.js +173 -0
  280. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  281. package/python/frontend_src/src/lib/md.js +190 -0
  282. package/python/frontend_src/src/main.js +9 -0
  283. package/python/frontend_src/src/stores/app.js +91 -0
  284. package/python/frontend_src/src/stores/chat.js +450 -0
  285. package/python/frontend_src/src/stores/model.js +96 -0
  286. package/python/frontend_src/src/stores/panels.js +289 -0
  287. package/python/frontend_src/src/stores/profile.js +31 -0
  288. package/python/frontend_src/src/stores/system.js +25 -0
  289. package/python/frontend_src/src/styles/base.css +138 -0
  290. package/python/frontend_src/src/styles/panel.css +117 -0
  291. package/python/frontend_src/src/styles/tokens.css +78 -0
  292. package/python/frontend_src/vite.config.js +39 -0
  293. package/python/pyproject.toml +268 -0
  294. package/python/requirements.txt +311 -0
  295. package/python/skills/README.md +123 -0
  296. package/python/skills/execution-contract/SKILL.md +30 -0
  297. package/python/skills/general-assistant/SKILL.md +34 -0
  298. package/python/skills/planner/SKILL.md +33 -0
  299. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  300. package/python/skills/skill-composition/SKILL.md +31 -0
  301. package/python/skills/skill-guard/SKILL.md +67 -0
  302. package/python/skills/skill-memory/SKILL.md +50 -0
  303. package/python/skills/skill-router/SKILL.md +35 -0
  304. package/python/tests/conftest.py +54 -0
  305. package/python/tests/run_all.py +200 -0
  306. package/python/tests/test_agent_lifecycle.py +326 -0
  307. package/python/tests/test_ahs_p2.py +393 -0
  308. package/python/tests/test_anthropic_tools.py +192 -0
  309. package/python/tests/test_api.py +97 -0
  310. package/python/tests/test_api_auth.py +79 -0
  311. package/python/tests/test_api_host_guard.py +69 -0
  312. package/python/tests/test_attachments.py +151 -0
  313. package/python/tests/test_audit_chain.py +116 -0
  314. package/python/tests/test_audit_sink.py +130 -0
  315. package/python/tests/test_browser.py +149 -0
  316. package/python/tests/test_browser_request_guard.py +180 -0
  317. package/python/tests/test_capabilities.py +152 -0
  318. package/python/tests/test_channel_signing.py +185 -0
  319. package/python/tests/test_channel_trust.py +109 -0
  320. package/python/tests/test_channels.py +174 -0
  321. package/python/tests/test_cli.py +46 -0
  322. package/python/tests/test_compaction_toolpair.py +119 -0
  323. package/python/tests/test_config_layering.py +61 -0
  324. package/python/tests/test_confirm_nonce.py +186 -0
  325. package/python/tests/test_context_compaction_wiring.py +124 -0
  326. package/python/tests/test_dispatch.py +89 -0
  327. package/python/tests/test_domain_evals.py +334 -0
  328. package/python/tests/test_domain_import.py +72 -0
  329. package/python/tests/test_domain_reasoners.py +134 -0
  330. package/python/tests/test_e2e.py +161 -0
  331. package/python/tests/test_emb_cache.py +97 -0
  332. package/python/tests/test_embedding_singleton.py +87 -0
  333. package/python/tests/test_envfile.py +83 -0
  334. package/python/tests/test_evals_smoke.py +90 -0
  335. package/python/tests/test_event_spine.py +174 -0
  336. package/python/tests/test_evidence_semantic.py +88 -0
  337. package/python/tests/test_evidence_sse.py +94 -0
  338. package/python/tests/test_file_ingest.py +135 -0
  339. package/python/tests/test_finance.py +144 -0
  340. package/python/tests/test_finance_ledger.py +245 -0
  341. package/python/tests/test_finance_persist.py +174 -0
  342. package/python/tests/test_gateway.py +125 -0
  343. package/python/tests/test_governance.py +421 -0
  344. package/python/tests/test_governance_audit.py +324 -0
  345. package/python/tests/test_governance_middleware.py +137 -0
  346. package/python/tests/test_hkc.py +268 -0
  347. package/python/tests/test_hkc_index_build_manager.py +306 -0
  348. package/python/tests/test_hkc_manifest_reuse.py +294 -0
  349. package/python/tests/test_hkc_search_event_sync.py +482 -0
  350. package/python/tests/test_import_path_guard.py +155 -0
  351. package/python/tests/test_injection_guard.py +193 -0
  352. package/python/tests/test_integration.py +117 -0
  353. package/python/tests/test_itinerary.py +100 -0
  354. package/python/tests/test_kee_semantic.py +84 -0
  355. package/python/tests/test_kernel.py +212 -0
  356. package/python/tests/test_learning.py +101 -0
  357. package/python/tests/test_llm_admin.py +145 -0
  358. package/python/tests/test_llm_planner.py +344 -0
  359. package/python/tests/test_markdown_skills.py +62 -0
  360. package/python/tests/test_markdown_skills_openclaw.py +66 -0
  361. package/python/tests/test_mcp_connectors.py +97 -0
  362. package/python/tests/test_mcp_guardian_discovery.py +87 -0
  363. package/python/tests/test_mcp_no_memory.py +43 -0
  364. package/python/tests/test_mcp_policy_gate.py +76 -0
  365. package/python/tests/test_mcp_server.py +92 -0
  366. package/python/tests/test_memory_recall.py +92 -0
  367. package/python/tests/test_metrics.py +58 -0
  368. package/python/tests/test_multi_chain.py +90 -0
  369. package/python/tests/test_multimodal.py +122 -0
  370. package/python/tests/test_negotiation.py +136 -0
  371. package/python/tests/test_orchestration.py +109 -0
  372. package/python/tests/test_orchestration_decoupled.py +103 -0
  373. package/python/tests/test_p3_p5_hardening.py +324 -0
  374. package/python/tests/test_parallel_dispatch.py +207 -0
  375. package/python/tests/test_pipeline.py +492 -0
  376. package/python/tests/test_plan_visible.py +86 -0
  377. package/python/tests/test_planner.py +88 -0
  378. package/python/tests/test_policy_gate.py +108 -0
  379. package/python/tests/test_profile.py +67 -0
  380. package/python/tests/test_readme_claims.py +163 -0
  381. package/python/tests/test_reasoner.py +170 -0
  382. package/python/tests/test_release_check.py +252 -0
  383. package/python/tests/test_resume_checkpoint.py +148 -0
  384. package/python/tests/test_routing.py +85 -0
  385. package/python/tests/test_scheduler.py +138 -0
  386. package/python/tests/test_search.py +194 -0
  387. package/python/tests/test_secrets.py +153 -0
  388. package/python/tests/test_semantic.py +69 -0
  389. package/python/tests/test_sessions.py +90 -0
  390. package/python/tests/test_settings.py +265 -0
  391. package/python/tests/test_shadow_learning.py +290 -0
  392. package/python/tests/test_shared_write.py +200 -0
  393. package/python/tests/test_skill_taint_gate.py +145 -0
  394. package/python/tests/test_skills.py +117 -0
  395. package/python/tests/test_skills_fetch.py +282 -0
  396. package/python/tests/test_stats_and_events.py +162 -0
  397. package/python/tests/test_streaming.py +55 -0
  398. package/python/tests/test_supervisor_pipeline.py +176 -0
  399. package/python/tests/test_supervisor_triage.py +102 -0
  400. package/python/tests/test_taint.py +112 -0
  401. package/python/tests/test_task_stream.py +74 -0
  402. package/python/tests/test_thinking_sse.py +66 -0
  403. package/python/tests/test_thinking_stream.py +76 -0
  404. package/python/tests/test_tool_center.py +113 -0
  405. package/python/tests/test_tool_parallel.py +113 -0
  406. package/python/tests/test_tool_params.py +99 -0
  407. package/python/tests/test_tool_use.py +229 -0
  408. package/python/tests/test_toplevel_concurrency.py +116 -0
  409. package/python/tests/test_tracing.py +256 -0
  410. package/python/tests/test_truth_crystallizer.py +125 -0
  411. package/python/tests/test_truth_flow.py +88 -0
  412. package/python/tests/test_update_check.py +95 -0
  413. package/python/tests/test_url_policy.py +53 -0
  414. package/python/tests/test_verify_evidence.py +212 -0
  415. package/python/tests/test_version_sync.py +100 -0
  416. package/python/tests/test_web_trigger.py +82 -0
  417. package/python/tools/check_faiss_type.py +30 -0
  418. package/python/tools/check_log.ps1 +38 -0
  419. package/python/tools/ivf_status.py +72 -0
  420. package/python/tools/mypy_baseline.txt +13 -0
  421. package/python/tools/mypy_ratchet.py +143 -0
  422. package/python/tools/pack.py +173 -0
  423. package/python/tools/release_check.py +349 -0
  424. package/python/tools/restart_foxclaw.ps1 +71 -0
  425. package/python/tools/shared_write_audit.py +190 -0
  426. package/python/tools/size_baseline.txt +28 -0
  427. package/python/tools/size_check.py +102 -0
  428. package/python/tools/strip_comments.py +228 -0
  429. package/python/tools/verify_env.bat +2 -0
  430. package/python/tools/verify_env.py +25 -0
  431. package/python/vendor/HMR_LICENSE +37 -0
  432. package/python/vendor/HMR_README.md +65 -0
  433. package/python/vendor/hkc/LICENSE +37 -0
  434. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  435. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  436. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  437. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  438. package/python/vendor/hkc/hkc_api/container.py +416 -0
  439. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  440. package/python/vendor/hkc/hkc_api/main.py +157 -0
  441. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  442. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  443. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  444. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  445. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  446. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  447. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  448. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  449. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  450. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  451. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  452. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  453. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  454. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  455. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  456. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  457. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  458. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  459. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  460. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  461. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  462. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  463. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  464. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  465. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  466. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  467. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  468. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  469. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  470. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  471. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  472. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  473. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  474. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  475. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  476. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  477. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  478. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  479. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  480. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  481. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  482. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  483. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  484. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  485. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  486. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  487. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  488. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  489. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  490. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  491. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  492. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  493. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  494. package/python/vendor/hmr/__init__.py +0 -0
  495. package/python/vendor/hmr/core/__init__.py +0 -0
  496. package/python/vendor/hmr/core/hmr.py +684 -0
  497. package/python/vendor/hmr/core/models.py +310 -0
  498. package/python/vendor/hmr/engines/__init__.py +0 -0
  499. package/python/vendor/hmr/engines/evolution.py +710 -0
  500. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  501. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  502. package/python/vendor/hmr/engines/policy.py +625 -0
  503. package/python/vendor/hmr/engines/recall.py +260 -0
  504. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  505. package/python/vendor/hmr/engines/scheduler.py +323 -0
  506. package/python/vendor/hmr/engines/semantic.py +150 -0
  507. package/python/vendor/hmr/engines/temporal.py +261 -0
  508. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  509. package/python/vendor/hmr/graph/__init__.py +0 -0
  510. package/python/vendor/hmr/graph/cwg.py +338 -0
  511. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  512. package/python/vendor/hmr/storage/__init__.py +0 -0
  513. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  514. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,96 @@
1
+ """app 配置与挂载:CORS、HKC 星球数据面、前端静态文件。
2
+
3
+ 从 `foxclaw/api/__init__.py` 搬移出来的「挂载类」逻辑,均为接收 `app` 参数的纯函数,
4
+ 在 `__init__.py` 创建 `app` 之后按原顺序调用,保持行为不变。
5
+ """
6
+ import os
7
+ from pathlib import Path
8
+
9
+ from fastapi import HTTPException
10
+ from fastapi.middleware.cors import CORSMiddleware
11
+ from fastapi.staticfiles import StaticFiles
12
+ from fastapi.responses import FileResponse
13
+
14
+ import foxclaw.api as _api
15
+
16
+
17
+ def _configure_cors(app):
18
+ _cors_env = os.environ.get("FOXCLAW_CORS_ORIGINS", "").strip()
19
+ if _cors_env == "*":
20
+ _CORS_ORIGINS = ["*"]
21
+ _CORS_ORIGIN_REGEX = None
22
+ elif _cors_env:
23
+ _CORS_ORIGINS = [o.strip() for o in _cors_env.split(",") if o.strip()]
24
+ _CORS_ORIGIN_REGEX = None
25
+ else:
26
+ _CORS_ORIGINS = []
27
+ _CORS_ORIGIN_REGEX = r"^https?://(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$"
28
+
29
+ app.add_middleware(
30
+ CORSMiddleware,
31
+ allow_origins=_CORS_ORIGINS,
32
+ allow_origin_regex=_CORS_ORIGIN_REGEX,
33
+ allow_methods=["*"],
34
+ allow_headers=["*"],
35
+ )
36
+
37
+
38
+ def _mount_hkc(app):
39
+ try:
40
+ from foxclaw.knowledge import _ensure_hkc_importable as _ensure_hkc
41
+ _ensure_hkc()
42
+ from hkc_api.routes.knowledge import router as _hkc_knowledge_router
43
+ from hkc_api.container import get_container as _hkc_get_container
44
+
45
+ def _resolve_hkc_container():
46
+ os_inst = _api.current_os()
47
+ svc = os_inst.hkc if os_inst is not None else None
48
+ if svc is None or not getattr(svc, "available", False):
49
+ raise HTTPException(503, "HKC 未启用(未 vendor 或缺依赖)")
50
+ return svc.container
51
+
52
+ app.dependency_overrides[_hkc_get_container] = _resolve_hkc_container
53
+ app.include_router(_hkc_knowledge_router)
54
+ @app.get("/stats", tags=["system"])
55
+ def _hkc_stats():
56
+ return _resolve_hkc_container().stats()
57
+ from hkc_api.routes.config import router as _hkc_config_router
58
+ app.include_router(_hkc_config_router)
59
+ try:
60
+ from hkc_api.routes.ability import router as _hkc_ability_router
61
+ from hkc_api.routes.search import router as _hkc_search_router
62
+ app.include_router(_hkc_ability_router)
63
+ app.include_router(_hkc_search_router)
64
+ _sub = "/abilities, /conflicts, /search/*"
65
+ except Exception as _e2:
66
+ _sub = f"(能力/搜索路由未挂: {_e2})"
67
+ print(f"[Foxclaw API] ✓ HKC 星球数据面已挂载 (/knowledge/*, /config/*, {_sub})")
68
+ except Exception as _e:
69
+ print(f"[Foxclaw API] · HKC 星球数据面未挂载(可选): {_e}")
70
+
71
+
72
+ def _mount_frontend(app):
73
+ frontend_dir = Path(__file__).parent.parent.parent / "frontend"
74
+ if not frontend_dir.exists():
75
+ return
76
+
77
+ v3_index = frontend_dir / "v3" / "index.html"
78
+
79
+ @app.get("/")
80
+ async def serve_root():
81
+ """主界面。v3 未构建(没跑 npm run build)时退回旧 deck_v2,不让根路径 404。"""
82
+ if v3_index.exists():
83
+ return FileResponse(str(v3_index))
84
+ legacy = frontend_dir / "deck_v2.html"
85
+ return FileResponse(str(legacy if legacy.exists() else frontend_dir / "deck.html"))
86
+
87
+ @app.get("/classic")
88
+ async def serve_classic():
89
+ """旧界面(deck_v2,v5.34):新版出问题时的手动回退入口,也留着它的二级面板历史。"""
90
+ return FileResponse(str(frontend_dir / "deck_v2.html"))
91
+
92
+ @app.get("/old")
93
+ async def serve_deck_old():
94
+ return FileResponse(str(frontend_dir / "deck.html"))
95
+
96
+ app.mount("/", StaticFiles(directory=str(frontend_dir), html=True), name="frontend")
@@ -0,0 +1,294 @@
1
+ """API 中间件层:鉴权 / 统一网关限流 / 链路追踪 / 指标埋点。
2
+
3
+ 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。原先用 `@app.middleware("http")`
4
+ 装饰器在 api 模块内直接注册;现集中到 `install_middleware(app)`,按**原注册顺序**
5
+ 把 4 个 http 中间件挂到 app(注册顺序即执行顺序,保持不变)。
6
+ """
7
+
8
+ import os
9
+
10
+ from ..core.gateway import Gateway
11
+ from ..core import settings
12
+
13
+
14
+ def _constant_time_eq(a: str, b: str) -> bool:
15
+ """【P3-1】常量时间字符串比较,防计时攻击。
16
+
17
+ 攻击者可通过普通 == 的逐字符短路耗时差异逐位猜出 API Key(猜对前缀越长、
18
+ 响应越慢),从而把请求从 ip:* 身份伪造成 k:* 身份绕过 IP 限流。
19
+ compare_digest 接受 bytes 或 ASCII str;API Key 可能含非 ASCII,故统一先
20
+ encode("utf-8")。None 由调用方用 or "" 兜底。
21
+ """
22
+ import hmac
23
+ return hmac.compare_digest((a or "").encode("utf-8"), (b or "").encode("utf-8"))
24
+
25
+
26
+ def _is_loopback_client(host: str) -> bool:
27
+ """【P3-loopback】本机回环判定:127.0.0.0/8(RFC 3330) + ::1(RFC 4291)。
28
+
29
+ FastAPI/Starlette 的 `request.client.host` 已是 TCP 层解析后的远端 IP,
30
+ 不识别 'localhost' 字符串(那是浏览器层主机名,握手时已解析为 IP)。
31
+ 空值 / 解析失败 → False(默认走鉴权,偏安全)。
32
+ """
33
+ if not host:
34
+ return False
35
+ h = host.strip()
36
+ if h == "::1":
37
+ return True
38
+ if h.startswith("127."):
39
+ parts = h.split(".")
40
+ if len(parts) == 4:
41
+ try:
42
+ if all(0 <= int(p) <= 255 for p in parts):
43
+ return True
44
+ except ValueError:
45
+ return False
46
+ return False
47
+
48
+
49
+ def _loopback_bypass_on() -> bool:
50
+ """【P3-loopback】本机免密开关。默认开(本机开发体验优先);
51
+ FOXCLAW_LOOPBACK_BYPASS=0 显式关(强迫症/特殊场景)。
52
+ 与 _gateway_on 同模式:每请求读 env(运行时可配 + 便于测试)。
53
+
54
+ 设计依据:RUN_CN.md:113「不设 FOXCLAW_API_KEY = 无鉴权,仅回环地址安全」。
55
+ → 回环本身就安全,鉴权的目的是挡非回环访问,本机不该被一刀切。
56
+ → 真正对外场景(0.0.0.0 监听)非 127.0.0.1 客户端仍要 key,锁门能力不变。
57
+ """
58
+ v = os.environ.get("FOXCLAW_LOOPBACK_BYPASS", "").strip()
59
+ if v == "0":
60
+ return False
61
+ if v == "1":
62
+ return True
63
+ return True
64
+
65
+
66
+ def _host_header_allowed(host_header: str) -> bool:
67
+ """【P3-rebind】Host 头校验,防 DNS rebinding。
68
+
69
+ 攻击模型(本机默认部署即中招):用户在本机跑 `foxclaw serve`(回环监听、无 key),
70
+ 随后浏览器打开 evil.com;evil.com 的 DNS 解析到 127.0.0.1,于是页面内的
71
+ `fetch("http://evil.com:8000/api/...")` 实际打到本机 API,且与页面同源 →
72
+ 恶意页可读写本地数据面(会话历史、记忆、触发执行链)。
73
+
74
+ 本函数卡住的是「Host 头是公网域名」这一环:浏览器发请求时 Host 必须与被访问
75
+ 域名一致,所以改解析也绕不过。规则:
76
+ - 无点主机名(localhost / testserver / 机器名)→ 放行;
77
+ - IP 字面量:回环与私网放行(家庭/公司局域网用法不受影响);
78
+ - 带点公网域名 → 默认拒绝,可用 FOXCLAW_ALLOWED_HOSTS 显式放行
79
+ (逗号分隔,支持 `*.example.com`)。
80
+ """
81
+ raw = (host_header or "").strip().lower()
82
+ if not raw:
83
+ return True
84
+ if raw.startswith("["):
85
+ hostname = raw.split("]")[0].lstrip("[")
86
+ else:
87
+ hostname = raw.rsplit(":", 1)[0] if raw.count(":") == 1 else raw
88
+ hostname = hostname.rstrip(".")
89
+ if not hostname:
90
+ return True
91
+
92
+ import ipaddress
93
+ try:
94
+ ip = ipaddress.ip_address(hostname)
95
+ except ValueError:
96
+ ip = None
97
+ if ip is not None:
98
+ return ip.is_loopback or ip.is_private
99
+
100
+ allowed = [h.strip().lower() for h in
101
+ os.environ.get("FOXCLAW_ALLOWED_HOSTS", "").split(",") if h.strip()]
102
+ for pat in allowed:
103
+ if pat.startswith("*.") and hostname.endswith(pat[1:]):
104
+ return True
105
+ if hostname == pat:
106
+ return True
107
+ return "." not in hostname
108
+
109
+
110
+ def _is_guarded_path(path: str) -> bool:
111
+ """【P3-8】需要鉴权/限流/追踪的数据面路径:/api/*、/knowledge/*、/config/*。
112
+
113
+ 抽成辅助函数:此前 _api_key_guard / _gateway_mw / _trace_mw 各自重复
114
+ `startswith("/api") or startswith("/knowledge")`,未来加新前缀(如 /ws 之外的
115
+ 新数据面)只改一处,也避免三处漂移出不一致的防护口径。
116
+ /knowledge/* 是 HKC 数据面(graph/ingest/reset),必须与 /api 同一防护口径。
117
+ /config/* 是 HKC 运行时 LLM 配置(连接设置),属可变状态,与数据面同口径保护。
118
+ /stats 是 HKC 统计(真实 data_dir + 全库 KU 总数),虽挂在应用根路径上,内容同属
119
+ 数据面 —— 此前遗漏,设了 FOXCLAW_API_KEY 仍可匿名读取。
120
+ """
121
+ return (path.startswith("/api") or path.startswith("/knowledge")
122
+ or path.startswith("/config")
123
+ or path == "/stats" or path.startswith("/stats/"))
124
+
125
+
126
+ async def _api_key_guard(request, call_next):
127
+ """设了 FOXCLAW_API_KEY 时,保护 /api/* 与 /ws/*。未设则放行(本地默认)。
128
+ 每请求读环境变量:可运行时配置,也便于测试。
129
+
130
+ 【P3-loopback】本机客户端(127.0.0.1 / ::1)自动免密:仅对非 loopback 客户端
131
+ 校验 X-API-Key。详见 _loopback_bypass_on。"""
132
+ api_key = os.environ.get("FOXCLAW_API_KEY", "").strip()
133
+ path = request.url.path
134
+ if _is_guarded_path(path):
135
+ _client = request.client.host if request.client else ""
136
+ if _is_loopback_client(_client) and not _host_header_allowed(
137
+ request.headers.get("host", "")):
138
+ from fastapi.responses import JSONResponse
139
+ return JSONResponse(
140
+ {"error": "forbidden: Host 头不是本机地址(已启用 DNS rebinding 防护)。"
141
+ "自建域名请加入 FOXCLAW_ALLOWED_HOSTS。"}, status_code=403)
142
+ if api_key:
143
+ if _is_guarded_path(path):
144
+ if request.method == "OPTIONS" and request.headers.get("access-control-request-method"):
145
+ return await call_next(request)
146
+ if _loopback_bypass_on():
147
+ client_host = request.client.host if request.client else ""
148
+ if _is_loopback_client(client_host):
149
+ return await call_next(request)
150
+ if not _constant_time_eq(request.headers.get("X-API-Key", "") or "", api_key):
151
+ from fastapi.responses import JSONResponse
152
+ return JSONResponse(
153
+ {"error": "unauthorized: 缺少或无效的 X-API-Key"}, status_code=401)
154
+ return await call_next(request)
155
+
156
+
157
+ _GATEWAY = Gateway(
158
+ rate_per_min=settings.get_int("FOXCLAW_GATEWAY_RATE"),
159
+ quota_per_hour=settings.get_int("FOXCLAW_GATEWAY_QUOTA"),
160
+ )
161
+ _MAX_BODY = settings.get_int("FOXCLAW_MAX_BODY_MB") * 1024 * 1024
162
+
163
+
164
+ class _PayloadTooLarge(Exception):
165
+ """请求体在**流上**超限:Content-Length 缺失或撒谎(如 chunked 传输)。"""
166
+
167
+ def __init__(self, size: int):
168
+ super().__init__(size)
169
+ self.size = size
170
+
171
+
172
+ def _wrap_body_limit(request, max_bytes: int) -> None:
173
+ """给 `request.receive` 套一层累计计数。
174
+
175
+ `Content-Length` 只是**声明值**:chunked 或不带该头时上游可以无限发,只查头等于没拦。
176
+ 头检查保留(不读流就能拒掉绝大多数),这里对流兜底,两道都要。
177
+ """
178
+ original = request.receive
179
+ received = 0
180
+
181
+ async def _receive():
182
+ nonlocal received
183
+ msg = await original()
184
+ if msg.get("type") == "http.request":
185
+ received += len(msg.get("body") or b"")
186
+ if received > max_bytes:
187
+ raise _PayloadTooLarge(received)
188
+ return msg
189
+
190
+ request._receive = _receive
191
+
192
+
193
+ def _gateway_on() -> bool:
194
+ """网关是否启用。沿用鉴权哲学:配了 FOXCLAW_API_KEY(=对外暴露)才自动开;
195
+ FOXCLAW_GATEWAY=1 强制开、=0 强制关。每请求读环境变量(运行时可配 + 便于测试)。"""
196
+ v = os.environ.get("FOXCLAW_GATEWAY", "").strip()
197
+ if v == "1":
198
+ return True
199
+ if v == "0":
200
+ return False
201
+ return bool(os.environ.get("FOXCLAW_API_KEY", "").strip())
202
+
203
+
204
+ async def _gateway_mw(request, call_next):
205
+ """统一网关:只管 /api 与 /knowledge 数据面(探针 /health、/metrics、/ 不在其下)。
206
+ client 身份:持有效 key → 按 key 计;否则按来源 IP 计(故轮换错误 key 无法绕过 IP 限流)。
207
+ 超限 → 429 + Retry-After。默认关(本地);配鉴权或显式开启才生效。"""
208
+ path = request.url.path
209
+ guarded = _is_guarded_path(path)
210
+ if guarded:
211
+ cl = request.headers.get("content-length")
212
+ if cl and cl.isdigit() and int(cl) > _MAX_BODY:
213
+ from fastapi.responses import JSONResponse
214
+ return JSONResponse(
215
+ {"error": f"payload too large (>{_MAX_BODY // 1048576}MB)"}, status_code=413)
216
+ _wrap_body_limit(request, _MAX_BODY)
217
+ if guarded and _gateway_on():
218
+ valid = os.environ.get("FOXCLAW_API_KEY", "").strip()
219
+ hdr = (request.headers.get("X-API-Key") or "").strip()
220
+ client = ("k:" + hdr) if (valid and _constant_time_eq(hdr, valid)) \
221
+ else ("ip:" + (request.client.host if request.client else "anon"))
222
+ allowed, retry, reason = _GATEWAY.check(client)
223
+ if not allowed:
224
+ from fastapi.responses import JSONResponse
225
+ return JSONResponse(
226
+ {"error": f"rate limited ({reason})", "retry_after": retry},
227
+ status_code=429, headers={"Retry-After": str(retry)})
228
+ try:
229
+ return await call_next(request)
230
+ except _PayloadTooLarge:
231
+ from fastapi.responses import JSONResponse
232
+ return JSONResponse(
233
+ {"error": f"payload too large (>{_MAX_BODY // 1048576}MB)"}, status_code=413)
234
+
235
+
236
+ async def _trace_mw(request, call_next):
237
+ """按请求开一条 trace(contextvars 会随 asyncio.to_thread 传到后台线程,故 Agent/LLM/
238
+ skill 里的 span 仍归属本次请求)。响应带 X-Trace-Id 便于定位。只追数据面,不污染静态/探针。"""
239
+ from ..core.tracing import start_trace, clear_trace, TRACES, parse_traceparent
240
+ path = request.url.path
241
+ if not _is_guarded_path(path):
242
+ return await call_next(request)
243
+ inbound = parse_traceparent(request.headers.get("traceparent"))
244
+ tr = start_trace(f"{request.method} {path}", trace_id=inbound)
245
+ try:
246
+ resp = await call_next(request)
247
+ try:
248
+ resp.headers["X-Trace-Id"] = tr.trace_id
249
+ except Exception:
250
+ pass
251
+ return resp
252
+ finally:
253
+ tr.finish()
254
+ TRACES.put(tr)
255
+ clear_trace()
256
+
257
+
258
+ async def _metrics_mw(request, call_next):
259
+ """埋点:记录每个请求的延迟 + 状态(按路由模板分组,ID 不炸表)。"""
260
+ import time as _t
261
+ t0 = _t.perf_counter()
262
+ status = 500
263
+ try:
264
+ resp = await call_next(request)
265
+ status = resp.status_code
266
+ return resp
267
+ finally:
268
+ ms = (_t.perf_counter() - t0) * 1000.0
269
+ route = request.scope.get("route")
270
+ ep = getattr(route, "path", None) or "unmatched"
271
+ try:
272
+ from ..core.metrics import METRICS
273
+ METRICS.record_request(f"{request.method} {ep}", status, ms)
274
+ except Exception:
275
+ pass
276
+
277
+
278
+ def install_middleware(app):
279
+ """按原注册顺序挂载 4 个 http 中间件(等价于原先的 @app.middleware 装饰)。
280
+
281
+ 顺序即执行顺序(Starlette 中间件后加先执行),必须与搬移前完全一致:
282
+ _api_key_guard → _gateway_mw → _trace_mw → _metrics_mw。
283
+ """
284
+ if os.environ.get("FOXCLAW_API_KEY", "").strip() and _loopback_bypass_on():
285
+ import logging
286
+ logging.getLogger("foxclaw").info(
287
+ "[LoopbackBypass] 已启用:本机(127.0.0.1 / ::1)请求自动免密,"
288
+ "鉴权仅作用于非 loopback 客户端。"
289
+ "设 FOXCLAW_LOOPBACK_BYPASS=0 可关闭。"
290
+ )
291
+ app.middleware("http")(_api_key_guard)
292
+ app.middleware("http")(_gateway_mw)
293
+ app.middleware("http")(_trace_mw)
294
+ app.middleware("http")(_metrics_mw)