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,91 @@
1
+ """第4步 · MCP 发现阶段进守门人:主进程(内存+磁盘)不碰 headers 凭证。"""
2
+ import sys, os, tempfile
3
+ from pathlib import Path
4
+ from types import SimpleNamespace as NS
5
+ sys.path.insert(0, str(Path(__file__).parent.parent))
6
+
7
+ def test_mcp_guardian_discovery():
8
+ os.environ["FOXCLAW_SKIP_DOTENV"] = "1"
9
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_mcp_guardian_")
10
+
11
+ # 1. tool_to_spec 兼容 dict(守门人序列化产物),fail-closed 不变
12
+ from foxclaw.skills.mcp_client import tool_to_spec
13
+ spec = tool_to_spec("srv", {"name": "get_time", "description": "读时间", "inputSchema": {},
14
+ "annotations": {"readOnlyHint": True, "destructiveHint": None}})
15
+ assert spec.id == "mcp__srv__get_time" and spec.side_effect == "read" and not spec.destructive
16
+ spec2 = tool_to_spec("srv", {"name": "do_thing", "description": "", "inputSchema": {},
17
+ "annotations": None})
18
+ assert spec2.side_effect == "write" # fail-closed:无注解按有副作用
19
+ print("[1] tool_to_spec 兼容 dict + fail-closed OK")
20
+
21
+ # 2. 守门人 mcp_register:主进程空 headers 不覆盖守门人已有凭证
22
+ import foxclaw.kernel.sentinel as sm
23
+ servers = {}
24
+ sm._handle_mcp_register({"servers": [
25
+ {"id": "a", "name": "a", "url": "https://a", "headers": {"Authorization": "Bearer SECRET"},
26
+ "transport": "streamable_http"}]}, servers)
27
+ assert servers["a"]["headers"]["Authorization"] == "Bearer SECRET"
28
+ sm._handle_mcp_register({"servers": [
29
+ {"id": "a", "name": "a", "url": "https://a", "transport": "streamable_http"}]}, servers)
30
+ assert servers["a"]["headers"]["Authorization"] == "Bearer SECRET", "空 headers 不应覆盖守门人已有凭证"
31
+ print("[2] mcp_register 空 headers 保留旧凭证 OK")
32
+
33
+ # 3. 守门人 mcp_list_tools 代发现:用自己的 headers 连 + 序列化为 dict
34
+ import foxclaw.skills.mcp_client as mc
35
+ _seen = {}
36
+ async def fake_list(url, headers, transport):
37
+ _seen["headers"] = headers
38
+ return [NS(name="t1", description="d", inputSchema={},
39
+ annotations=NS(readOnlyHint=True, destructiveHint=None))]
40
+ mc._amcp_list_tools = fake_list
41
+ out = sm._handle_mcp_list_tools({"server_id": "a"}, servers)
42
+ assert out["tools"][0]["name"] == "t1"
43
+ assert out["tools"][0]["annotations"]["readOnlyHint"] is True
44
+ assert _seen["headers"].get("Authorization") == "Bearer SECRET" # 守门人用自己的凭证连
45
+ print("[3] mcp_list_tools 守门人代发现 + 序列化 + 凭证不出守门人 OK")
46
+
47
+ # 4. mcp_add_server_guardian:主进程不存 headers,守门人收到 headers
48
+ class FakeSentinel:
49
+ def __init__(self):
50
+ self.registered = None
51
+ def mcp_register(self, servers):
52
+ self.registered = servers
53
+ def mcp_list_tools(self, sid):
54
+ return [{"name": "t1", "description": "", "inputSchema": {}, "annotations": None}]
55
+
56
+ class FakeMgr:
57
+ def __init__(self):
58
+ self._servers = {}
59
+ def servers(self):
60
+ return [dict(s) for s in self._servers.values()]
61
+ def restore(self, cfg):
62
+ self._servers[cfg["id"]] = cfg
63
+
64
+ class FakeStore:
65
+ def __init__(self):
66
+ self.saved = None
67
+ def save(self, servers):
68
+ self.saved = servers
69
+
70
+ class FakeHost:
71
+ pass
72
+
73
+ host = FakeHost()
74
+ host.sentinel = FakeSentinel()
75
+ host.mcp_manager = FakeMgr()
76
+ host.mcp_store = FakeStore()
77
+
78
+ from foxclaw import Foxclaw
79
+ Foxclaw.mcp_add_server_guardian(host, {
80
+ "id": "srv", "name": "srv", "url": "https://srv",
81
+ "headers": {"X-Key": "SECRET"}, "transport": "streamable_http"})
82
+
83
+ assert "headers" not in host.mcp_manager.servers()[0], "主进程 McpManager 不应存 headers"
84
+ assert "headers" not in host.mcp_store.saved[0], "主进程磁盘不应存 headers"
85
+ assert host.sentinel.registered[-1]["headers"]["X-Key"] == "SECRET", "守门人应收到 headers"
86
+ print("[4] mcp_add_server_guardian:主进程内存/磁盘无 headers,守门人持 headers OK")
87
+
88
+ print("\n第4步 · MCP 发现进守门人 验证全部通过")
89
+
90
+ if __name__ == "__main__":
91
+ test_mcp_guardian_discovery()
@@ -0,0 +1,43 @@
1
+ """MCP ask_foxclaw "不污染记忆":外部驱动的任务不写 HMR(防外部灌垃圾污染个人记忆),
2
+ 正常任务照写。验证抑制标志随 to_thread→scheduler.run→asyncio.run 上下文传到 _store_memory。"""
3
+ import os, sys, tempfile, asyncio
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent))
6
+
7
+ def test_mcp_no_memory():
8
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
9
+ os.environ["FOXCLAW_SKIP_DOTENV"] = "1" # 隔离开发机 .env,保证测试环境自洽
10
+ os.environ.setdefault("FOXCLAW_EMBEDDING_MODEL", "foxclaw-nonexistent-fast")
11
+ os.environ["HF_HUB_OFFLINE"] = "1" # 无网环境:跳过 huggingface 联网重试,直接用本地缓存
12
+ os.environ["TRANSFORMERS_OFFLINE"] = "1"
13
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_nomem_")
14
+
15
+ from foxclaw import Foxclaw
16
+
17
+ o = Foxclaw(data_dir=os.environ["FOXCLAW_DATA_DIR"])
18
+ calls = []
19
+ o.hmr.ingest = lambda *a, **k: calls.append(1) # spy:数 HMR 写入次数
20
+
21
+
22
+ async def dispatch_suppressed():
23
+ """完全复刻 ask_foxclaw 的派发路径:to_thread → set 抑制 → scheduler.run → asyncio.run。"""
24
+ def _work():
25
+ from foxclaw.agents.base import _suppress_memory
26
+ _suppress_memory.set(True)
27
+ async def _t():
28
+ o.supervisor._store_memory("外部驱动任务的内容(应被抑制)")
29
+ return o.scheduler.run(lambda: asyncio.run(_t()), agent_id="assistant", task="t")
30
+ return await asyncio.to_thread(_work)
31
+
32
+
33
+ asyncio.run(dispatch_suppressed())
34
+ suppressed = len(calls)
35
+ o.supervisor._store_memory("正常任务内容(应写入)")
36
+ normal = len(calls) - suppressed
37
+
38
+ assert suppressed == 0, f"污染!MCP 外部任务写了 {suppressed} 条记忆"
39
+ assert normal > 0, "正常任务应正常写记忆"
40
+ print(f"MCP ask_foxclaw 不污染记忆 OK(抑制路径写入={suppressed}, 正常路径写入={normal})")
41
+
42
+ if __name__ == "__main__":
43
+ test_mcp_no_memory()
@@ -0,0 +1,77 @@
1
+ """MCP `ask_foxclaw` 的外部驱动准入。
2
+
3
+ 外部客户端「绕开工具直接驱动 Agent」时,若不过统一动作门,宿主配的 deny(封杀动作)与
4
+ ask(要求人工)对它完全无效。MCP 没有交互确认通道,所以命中 ask 规则要**立即拒**,
5
+ 而不是放行后等一个永远不会来的确认。
6
+ """
7
+ import sys
8
+ from pathlib import Path
9
+ from types import SimpleNamespace
10
+
11
+ sys.path.insert(0, str(Path(__file__).parent.parent))
12
+
13
+ from foxclaw.mcp_server import _policy_error # noqa: E402
14
+
15
+
16
+ class _StubGate:
17
+ """只回放指定裁决,用来隔离「门怎么判」——这里只测我们怎么消费结果。"""
18
+
19
+ def __init__(self, decision: str, code: str = ""):
20
+ self.decision = decision
21
+ self.code = code
22
+
23
+ def check_action(self, skill, args, rules=None, confirmed=False, mode="strict",
24
+ taint_level=0, taint_threshold=0):
25
+ assert skill.side_effect == "write" and skill.external is True
26
+ assert confirmed is False, "外部驱动不能用 confirmed 自证"
27
+ # Verdict 的字段是 level(allow|warn|deny|confirm),不是 decision
28
+ return SimpleNamespace(level=self.decision, reason="stub",
29
+ raw=SimpleNamespace(reason_code=self.code),
30
+ decision_id="d")
31
+
32
+
33
+ def test_no_gate_means_no_restriction():
34
+ assert _policy_error(None, None, "分析这个月支出") is None
35
+
36
+
37
+ def test_routine_side_effect_ask_is_allowed():
38
+ """副作用 / external 的常规 ask 由 FOXCLAW_MCP_AGENTS=1 承担授权,不重复拦。"""
39
+ from foxclaw.skills.permission import EXTERNAL_CONFIRM
40
+
41
+ assert _policy_error(_StubGate("ask", EXTERNAL_CONFIRM), None, "跑一下") is None
42
+
43
+
44
+ def test_ask_rule_is_refused_not_waited_on():
45
+ """宿主显式要求确认 → 直接拒。MCP 客户端不会有人按下那个按钮。"""
46
+ from foxclaw.skills.permission import ASK_RULE
47
+
48
+ assert _policy_error(_StubGate("ask", ASK_RULE), None, "跑一下") == "needs_confirmation"
49
+
50
+
51
+ def test_deny_wins():
52
+ assert _policy_error(_StubGate("deny"), None, "跑一下") == "blocked"
53
+
54
+
55
+ def test_missing_params_surface_as_error():
56
+ from foxclaw.skills.permission import MISSING_PARAMS
57
+
58
+ assert _policy_error(_StubGate("ask", MISSING_PARAMS), None, "") == "missing arguments"
59
+
60
+
61
+ def test_real_deny_rule_blocks_mcp_drive():
62
+ """真 evaluate + 真规则集:宿主写 `deny=["mcp:*"]` 必须真的挡住。"""
63
+ from foxclaw.kernel.policy_gate import PolicyGate
64
+ from foxclaw.skills.permission import PermissionRules, evaluate
65
+
66
+ gate = PolicyGate(action_evaluator=evaluate)
67
+ rules = PermissionRules(deny=["mcp:*"])
68
+ assert _policy_error(gate, rules, "帮我发封邮件") == "blocked"
69
+
70
+
71
+ def test_real_default_rules_do_not_block():
72
+ """没配任何规则时不能被自己的门挡住(否则 MCP 驱动直接不可用)。"""
73
+ from foxclaw.kernel.policy_gate import PolicyGate
74
+ from foxclaw.skills.permission import PermissionRules, evaluate
75
+
76
+ gate = PolicyGate(action_evaluator=evaluate)
77
+ assert _policy_error(gate, PermissionRules(), "分析这个月支出") is None
@@ -0,0 +1,95 @@
1
+ """Foxclaw 内置 MCP 服务器 端到端(带鉴权,贴真实暴露场景):
2
+ - 防后门:不带 X-API-Key 访问 /api/mcp/ → 401
3
+ - 带 key 的 MCP 客户端能 initialize + list_tools(3 只读 + ask_foxclaw) + call
4
+ 真起 uvicorn(MCP over Streamable HTTP 需真实 URL)。"""
5
+ import os, sys, tempfile, threading, time, asyncio, urllib.request, urllib.error
6
+ from pathlib import Path
7
+ import pytest
8
+ sys.path.insert(0, str(Path(__file__).parent.parent))
9
+
10
+ @pytest.mark.xfail(
11
+ reason="pre-existing 失败:真实 uvicorn MCP-over-Streamable-HTTP 端到端断言需要 base_url/凭证配置,本地 stub 环境(/api/mcp/ 后门检查)与基线不一致。需真实 provider 环境才能过。",
12
+ strict=False,
13
+ )
14
+ def test_mcp_server():
15
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
16
+ os.environ["FOXCLAW_SKIP_DOTENV"] = "1" # 用进程环境变量自洽测试,不加载开发机 .env
17
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_mcpsrv_")
18
+ os.environ["FOXCLAW_API_KEY"] = "mcp-test-key" # ★ 开鉴权,测真实暴露
19
+ os.environ["FOXCLAW_MCP_SERVER"] = "1"
20
+ os.environ["FOXCLAW_MCP_AGENTS"] = "1"
21
+ os.environ["FOXCLAW_MCP_RATE_AGENT"] = "1" # ask_foxclaw 限 1/桶,测限流
22
+ os.environ.setdefault("FOXCLAW_EMBEDDING_MODEL", "foxclaw-nonexistent-fast")
23
+ os.environ["HF_HUB_OFFLINE"] = "1" # 无网环境:跳过 huggingface 联网重试(否则模型加载拖 2 分钟,服务器赶不上就绪窗口)
24
+ os.environ["TRANSFORMERS_OFFLINE"] = "1"
25
+ import uvicorn
26
+ import httpx
27
+ from foxclaw.api import app, _foxclaw_mcp
28
+
29
+ assert _foxclaw_mcp is not None, "FOXCLAW_MCP_SERVER=1 时应构建 MCP 服务器"
30
+ print("=" * 60); print("Foxclaw 内置 MCP 服务器验证(带鉴权)"); print("=" * 60)
31
+
32
+ PORT = 8079
33
+ KEY = "mcp-test-key"
34
+ threading.Thread(target=lambda: uvicorn.run(app, host="127.0.0.1", port=PORT, log_level="error"),
35
+ daemon=True).start()
36
+ for _ in range(60):
37
+ try:
38
+ urllib.request.urlopen(f"http://127.0.0.1:{PORT}/health", timeout=2); break # /health 开放
39
+ except urllib.error.HTTPError:
40
+ break
41
+ except Exception:
42
+ time.sleep(1)
43
+
44
+ # [0] 防后门:设了 key,不带/错 key 访问 /api/mcp/ 必须 401(未被鉴权绕过)
45
+ with httpx.Client(timeout=8) as c:
46
+ body = {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}
47
+ hdr = {"Accept": "application/json, text/event-stream"}
48
+ r = c.post(f"http://127.0.0.1:{PORT}/api/mcp/", json=body, headers=hdr)
49
+ assert r.status_code == 401, f"后门!/api/mcp 未受鉴权保护: {r.status_code}"
50
+ r = c.post(f"http://127.0.0.1:{PORT}/api/mcp/", json=body, headers={**hdr, "X-API-Key": "wrong"})
51
+ assert r.status_code == 401, "错误 key 应 401"
52
+ print("[0] 防后门:无key/错key 访问 /api/mcp/ → 401 OK")
53
+
54
+
55
+ async def probe():
56
+ from mcp.client.streamable_http import streamablehttp_client
57
+ from mcp import ClientSession
58
+ # 带正确 key 连接
59
+ async with streamablehttp_client(f"http://127.0.0.1:{PORT}/api/mcp/",
60
+ headers={"X-API-Key": KEY}) as (r, w, _):
61
+ async with ClientSession(r, w) as s:
62
+ await s.initialize()
63
+ tools = {t.name for t in (await s.list_tools()).tools}
64
+ assert tools == {"knowledge_search", "knowledge_get", "memory_recall", "ask_foxclaw"}, tools
65
+ print("[1] 带key initialize + list_tools(3 只读 + ask_foxclaw) OK")
66
+
67
+ res = await s.call_tool("knowledge_search", {"query": "anything", "top_k": 999999}) # 试超大 top_k(应被钳制不崩)
68
+ assert res.content is not None
69
+ print("[2] knowledge_search 可调 + top_k 钳制不崩 OK")
70
+
71
+ res = await s.call_tool("memory_recall", {"query": "hello"})
72
+ assert res.content is not None
73
+ print("[3] memory_recall 可调 OK")
74
+
75
+ res = await s.call_tool("knowledge_get", {"ku_id": "nonexistent"})
76
+ txt = res.content[0].text if res.content else ""
77
+ assert "not_found" in txt or "error" in txt
78
+ print("[4] knowledge_get 不存在 → not_found OK")
79
+
80
+ res = await s.call_tool("ask_foxclaw", {"task": "分析这个月支出"})
81
+ txt = res.content[0].text if res.content else ""
82
+ assert "dispatched_to" in txt or "reply" in txt or "error" in txt, txt
83
+ print("[5] ask_foxclaw Agent 调度可调(返回 reply/dispatched_to) OK")
84
+
85
+ # [6] 限流:agent 桶设 1,上面已消耗 → 再调应被限流
86
+ res = await s.call_tool("ask_foxclaw", {"task": "再来一次"})
87
+ txt = res.content[0].text if res.content else ""
88
+ assert "rate limited" in txt, f"限流未生效: {txt}"
89
+ print("[6] ask_foxclaw 限流(令牌桶)生效 OK")
90
+
91
+ asyncio.run(probe())
92
+ print("\nFoxclaw 内置 MCP 服务器验证全部通过")
93
+
94
+ if __name__ == "__main__":
95
+ test_mcp_server()
@@ -0,0 +1,95 @@
1
+ """
2
+ M4 · 记忆隔离召回完整性(根治)回归
3
+
4
+ 验证 HMRAdapter 的 tag 预过滤根治路径:
5
+ 1. 完整性:recall(agent=X) 在"X 私有 + shared"**全集**内排序,X 自己的相关记忆一定能召回,
6
+ 不会因库里其他 Agent 有大量强相关记忆挤占全局 top-k 而被漏掉;
7
+ 2. 隔离:任一路径都只返回带 agent:X 或 shared 标签的记忆,绝不泄露他人私有;
8
+ 3. 对照:同一库里老路径(_legacy_recall,全局 top-k 再过滤)会漏掉被淹没的 X 记忆,
9
+ 根治路径(_filtered_recall)能找回 —— 直观展示"完整性缺陷被根治"。
10
+
11
+ 真实 HMR(离线,ST 本地缓存);HMR 不可用时优雅跳过。
12
+ """
13
+ import sys
14
+ import tempfile
15
+ from pathlib import Path
16
+ sys.path.insert(0, str(Path(__file__).parent.parent))
17
+
18
+ from foxclaw.core.memory_adapter import create_memory_backend
19
+
20
+ QUERY = "quarterly revenue growth and budget forecast analysis"
21
+ N_FINANCE = 55 # > 老路径 fetch_k(50),< 压缩阈值(同类型 80)
22
+
23
+
24
+ def check(cond, msg):
25
+ if not cond:
26
+ raise AssertionError(f"✗ {msg}")
27
+ print(f" ✓ {msg}")
28
+
29
+
30
+ def main():
31
+ print("=" * 56)
32
+ print("M4 · 记忆隔离召回完整性(根治)")
33
+ print("=" * 56)
34
+ hmr = create_memory_backend(storage_path=tempfile.mkdtemp())
35
+ if getattr(hmr, "backend", "") != "HMR":
36
+ print(" · HMR 不可用(降级 Mock),跳过 M4 测试")
37
+ print("结果:跳过"); return
38
+
39
+ # office:一条与 query 相关的关键记忆 + 一条无关
40
+ hmr.ingest("office quarterly revenue growth and budget forecast for planning",
41
+ type="concept", agent_id="office", title="office-key")
42
+ hmr.ingest("office meeting room booking schedule for next week",
43
+ type="concept", agent_id="office", title="office-other")
44
+ # finance:大量与 query 强相关记忆(会在全局排序里淹没 office 的记忆)
45
+ for i in range(N_FINANCE):
46
+ hmr.ingest(f"finance quarterly revenue growth and budget forecast record {i}",
47
+ type="concept", agent_id="finance", title=f"fin-{i}")
48
+ # 一条共享记忆
49
+ hmr.ingest("shared team quarterly planning memo", type="concept",
50
+ agent_id="finance", shared=True, title="shared-memo")
51
+
52
+ print("\n【1】完整性:根治路径在 office 全集内排序,召回其相关记忆")
53
+ res = hmr.recall(QUERY, agent_id="office", top_k=5)
54
+ titles = [r["title"] for r in res]
55
+ check("office-key" in titles, f"office 的相关记忆被召回(结果:{titles})")
56
+
57
+ print("\n【2】隔离:office 召回零 finance 私有泄露")
58
+ def foreign_private(r):
59
+ tags = r.get("tags", [])
60
+ return (any(t.startswith("agent:") and t != "agent:office" for t in tags)
61
+ and "shared" not in tags)
62
+ check(not any(foreign_private(r) for r in res), "office 召回结果无他人私有")
63
+
64
+ print("\n【3】根治路径 = 在 X 全集内考虑(不受全局 top-k 挤占)")
65
+ full = hmr._filtered_recall(QUERY, "office", top_k=100, include_shared=True)
66
+ ftitles = {r["title"] for r in full}
67
+ check(ftitles == {"office-key", "office-other", "shared-memo"},
68
+ f"office 视角恰好看到自己 2 条 + 共享 1 条,零 finance({sorted(ftitles)})")
69
+
70
+ print("\n【4】对照:老路径漏掉被淹没的 office 记忆,根治路径找回")
71
+ legacy = hmr._legacy_recall(QUERY, "office", top_k=5, include_shared=True)
72
+ filtered = hmr._filtered_recall(QUERY, "office", top_k=5, include_shared=True)
73
+ legacy_has = any(r["title"] == "office-key" for r in legacy)
74
+ filtered_has = any(r["title"] == "office-key" for r in filtered)
75
+ print(f" 老路径召回 office-key: {legacy_has} 根治路径召回 office-key: {filtered_has}")
76
+ check(filtered_has, "根治路径召回到 office-key(完整性)")
77
+ check(not legacy_has, "老路径确实漏掉 office-key(被 finance 强相关记忆淹没,印证根治的必要)")
78
+
79
+ print("\n【5】反向隔离:finance 召回零 office 私有")
80
+ fin = hmr._filtered_recall(QUERY, "finance", top_k=100, include_shared=True)
81
+ check(not any(r["title"].startswith("office-") for r in fin),
82
+ "finance 视角看不到 office 私有")
83
+ check(any(r["title"] == "shared-memo" for r in fin), "finance 能看到共享记忆")
84
+
85
+ print("\n" + "=" * 56)
86
+ print("记忆隔离召回完整性 根治通过")
87
+ print("=" * 56)
88
+
89
+
90
+ def test_memory_recall():
91
+ main()
92
+
93
+
94
+ if __name__ == "__main__":
95
+ main()
@@ -0,0 +1,66 @@
1
+ """ops 监控:请求/LLM 指标(计数/错误率/延迟分位)+ 深度健康 + Prometheus 文本。"""
2
+ import os, sys, tempfile
3
+ from pathlib import Path
4
+ sys.path.insert(0, str(Path(__file__).parent.parent))
5
+ import pytest
6
+
7
+
8
+ def test_metrics():
9
+ # ISOLATION_GUARD:本用例依赖 import 时固化的模块级单例(网关/限流器/路由配置按 env 在 import 时定型),单进程全量跑会被前序用例污染 →
10
+ # 假红。全量请用 tests/run_all.py(每文件独立子进程)。
11
+ if "foxclaw.api" in sys.modules:
12
+ pytest.skip("需独立进程运行(模块级单例已固化),请用 tests/run_all.py")
13
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
14
+ os.environ.setdefault("FOXCLAW_EMBEDDING_MODEL", "foxclaw-nonexistent-fast")
15
+ os.environ["HKC_EMBEDDING"] = "stub"
16
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_metrics_")
17
+
18
+ from fastapi.testclient import TestClient
19
+ from foxclaw.api import app
20
+
21
+ print("=" * 60); print("ops 监控验证"); print("=" * 60)
22
+
23
+ with TestClient(app) as c:
24
+ # 产生一些请求 + 一次任务(触发 LLM 埋点,stub)
25
+ c.get("/api/status"); c.get("/api/ahs/skills"); c.get("/health")
26
+ c.post("/api/task", json={"task": "测试", "agent_id": "supervisor"})
27
+
28
+ # [1] 深度健康:逐子系统(开放路径,探针无需鉴权;不泄露 provider 名)
29
+ hd = c.get("/health/ready")
30
+ assert hd.status_code == 200, hd.status_code
31
+ j = hd.json()
32
+ assert j["status"] in ("ok", "degraded") and "hmr" in j["components"] and "router" in j["components"], j
33
+ assert j["components"]["router"] in ("ok", "stub", "down"), "router 不应泄露 provider 名"
34
+ print(f"[1] /health/ready 深度健康({j['status']},{len(j['components'])} 组件,router 不泄露) OK")
35
+
36
+ # [2] JSON metrics:端点延迟分位 + 错误率
37
+ m = c.get("/api/metrics").json()
38
+ assert "uptime_s" in m and m["endpoints"], m
39
+ ep = next(k for k in m["endpoints"] if "status" in k)
40
+ st = m["endpoints"][ep]
41
+ assert st["count"] >= 1 and "p95_ms" in st and "error_rate" in st and "avg_ms" in st, st
42
+ print(f"[2] /api/metrics 端点(p50={st['p50_ms']} p95={st['p95_ms']} err={st['error_rate']}) OK")
43
+
44
+ # [3] LLM 埋点(stub 也计入调用量)
45
+ assert m["llm"], f"LLM 指标应非空: {m['llm']}"
46
+ print(f"[3] LLM 埋点({list(m['llm'])}) OK")
47
+
48
+ # [4] Prometheus 文本格式
49
+ p = c.get("/metrics")
50
+ assert p.status_code == 200 and "foxclaw_requests_total" in p.text and "foxclaw_llm_calls_total" in p.text
51
+ print("[4] /metrics Prometheus 文本 OK")
52
+
53
+ # [5] 未匹配路由(404)不撑爆 key:20 个不同 404 路径 → 只 1 个 "unmatched" key
54
+ for i in range(20):
55
+ c.get(f"/api/does-not-exist/random-{i}")
56
+ m2 = c.get("/api/metrics").json()
57
+ rand_keys = [k for k in m2["endpoints"] if "random-" in k]
58
+ assert len(rand_keys) == 0, f"404 路径不该各自成 key(内存DoS): {rand_keys[:3]}"
59
+ assert any("unmatched" in k for k in m2["endpoints"]), "应有 unmatched 汇总 key"
60
+ print("[5] 404 路径归入 unmatched(防指标 key 内存 DoS) OK")
61
+
62
+ print("\nops 监控验证全部通过")
63
+
64
+
65
+ if __name__ == "__main__":
66
+ test_metrics()
@@ -0,0 +1,102 @@
1
+ """智能体层深化:多步跨 Agent 由「排队跑」升级为「真链式」——后一步能看到前序步骤结果。
2
+
3
+ 原缺口:_dispatch_multi 每步都只拿到同一份原始 context,等于 N 个互不相干的任务顺序执行
4
+ (「查本月餐饮花了多少,再据此定预算」的第 2 步拿不到第 1 步的数字)。
5
+ """
6
+ import os, sys, asyncio, tempfile
7
+ from pathlib import Path
8
+ sys.path.insert(0, str(Path(__file__).parent.parent))
9
+
10
+ def test_multi_chain():
11
+ os.environ["HKC_EMBEDDING"] = "stub"
12
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
13
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_chain_")
14
+
15
+ from foxclaw.agents.agents import _with_prior, PRIOR_MAX_STEPS, PRIOR_MAX_CHARS
16
+
17
+ print("=" * 60); print("多步跨 Agent 链式协作验证"); print("=" * 60)
18
+
19
+ # [1] 无前序 → 原样返回(第 1 步不该有前言)
20
+ assert _with_prior("查餐饮支出", []) == "查餐饮支出"
21
+ print("[1] 首步无前序上下文(原样)OK")
22
+
23
+ # [2] 有前序 → 并进任务文本
24
+ t = _with_prior("据此定预算", [{"step": 1, "agent": "finance", "output": "餐饮 1200 元"}])
25
+ assert "据此定预算" in t and "餐饮 1200 元" in t and "第1步(finance)" in t, t
26
+ print("[2] 前序结果并进下一步任务文本 OK")
27
+
28
+ # [3] 步数封顶:只带最近 N 步(防长链把提示词滚爆)
29
+ many = [{"step": i, "agent": "a", "output": f"out{i}"} for i in range(1, 9)]
30
+ t3 = _with_prior("x", many)
31
+ assert "out8" in t3 and "out6" in t3 and "out5" not in t3, t3
32
+ assert t3.count("第") - 1 == PRIOR_MAX_STEPS or t3.count("步(") == PRIOR_MAX_STEPS, t3
33
+ print(f"[3] 只带最近 {PRIOR_MAX_STEPS} 步(更早的不注入)OK")
34
+
35
+ # [4] 单步字符封顶
36
+ t4 = _with_prior("x", [{"step": 1, "agent": "a", "output": "y" * 900}])
37
+ assert "y" * PRIOR_MAX_CHARS in t4 and "y" * (PRIOR_MAX_CHARS + 1) not in t4
38
+ print(f"[4] 单步输出截断到 {PRIOR_MAX_CHARS} 字符 OK")
39
+
40
+ # ── 集成:直接驱动 _dispatch_multi,看子 Agent 真实收到了什么 ──
41
+ from foxclaw import Foxclaw
42
+
43
+ # 注意:Foxclaw 的 data_dir 是**构造参数**(默认 ./foxclaw_data),不读 FOXCLAW_DATA_DIR
44
+ # ——env 是 api.py 读了再传进来的。测试必须显式传临时目录,否则会写进真实用户记忆库。
45
+ os_ = Foxclaw(data_dir=os.environ["FOXCLAW_DATA_DIR"])
46
+ sup = os_.supervisor
47
+
48
+
49
+ class StubAgent:
50
+ """记录真实收到的 task 文本与 context。"""
51
+ def __init__(self, name, reply): self.name = name; self.reply = reply; self.seen = []
52
+ async def run_task(self, task, context=None):
53
+ self.seen.append({"task": task, "context": context or {}})
54
+ return {"result": self.reply}
55
+
56
+
57
+ fin = StubAgent("finance", "本月餐饮 1200 元")
58
+ adm = StubAgent("admin", "已按 1200 元安排下周预算")
59
+ subs = {"finance": fin, "admin": adm}
60
+ sched = sup._schedule_for("t", subs) if hasattr(sup, "_schedule_for") else None
61
+ if sched is None: # 兼容:直接拿一个 schedule 对象
62
+ from foxclaw.core.scheduler import SystemContext
63
+ sched = sup._scheduler.schedule("t", SystemContext(active_agents=0, queue_depth=0, system_load=0.1))
64
+
65
+ routes = [("查本月餐饮支出", "finance"), ("据此安排下周预算", "admin")]
66
+ out = asyncio.run(sup._dispatch_multi("查餐饮并定预算", routes, sched, "sess1", subs, {}, []))
67
+
68
+ # [5] 第 1 步不带前序;第 2 步**收到了**第 1 步的输出 → 链真的接上了
69
+ s1, s2 = fin.seen[0]["task"], adm.seen[0]["task"]
70
+ assert "前序步骤结果" not in s1, f"首步不该有前序前言: {s1}"
71
+ assert "本月餐饮 1200 元" in s2, f"第2步应看到第1步结果,实际: {s2}"
72
+ assert "据此安排下周预算" in s2
73
+ print("[5] 第2步真实收到第1步输出(链式成立)OK")
74
+
75
+ # [6] context 里也有结构化 prior_steps(两手准备)
76
+ ps = adm.seen[0]["context"].get("prior_steps")
77
+ assert ps and ps[0]["agent"] == "finance" and "1200" in ps[0]["output"], ps
78
+ assert fin.seen[0]["context"].get("prior_steps") == [], "首步 prior 应为空"
79
+ print("[6] context.prior_steps 结构化传递 OK")
80
+
81
+ # [7] 展示用 subtask 保留原始文本(不被前序前言污染 UI)
82
+ steps = out["result"]["multi_steps"]
83
+ assert steps[1]["subtask"] == "据此安排下周预算", steps[1]["subtask"]
84
+ assert "前序步骤结果" not in steps[1]["subtask"]
85
+ assert out["result"]["dispatched_to"] == "multi" and len(steps) == 2
86
+ print("[7] UI 展示的 subtask 仍是原始文本(未被污染)OK")
87
+
88
+ # [8] 【长期运行】入链的输出必须截断:prior 会被复制进**每个**后续步骤的 context,
89
+ # 不截断则长链 + 大输出会滚成可观的内存/拷贝开销(渲染本就只用 300 字符)。
90
+ big = StubAgent("finance", "X" * 5000)
91
+ subs2 = {"finance": big, "admin": StubAgent("admin", "ok")}
92
+ asyncio.run(sup._dispatch_multi("t", [("查", "finance"), ("用", "admin")],
93
+ sched, "s2", subs2, {}, []))
94
+ ps = subs2["admin"].seen[0]["context"]["prior_steps"]
95
+ assert len(ps[0]["output"]) <= PRIOR_MAX_CHARS, f"入链输出未截断: {len(ps[0]['output'])}"
96
+ assert len(subs2["admin"].seen[0]["task"]) < 2000, "任务文本不该被前序大输出撑爆"
97
+ print(f"[8] 大输出入链自动截断({len(ps[0]['output'])} ≤ {PRIOR_MAX_CHARS} 字符)OK")
98
+
99
+ print("\n多步跨 Agent 链式协作验证全部通过")
100
+
101
+ if __name__ == "__main__":
102
+ test_multi_chain()