foxclaw 0.1.0 → 0.1.2

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