foxclaw 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (514) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +4 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +173 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +167 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/CHANGELOG.md +57 -0
  11. package/python/CHANGELOG_CN.md +57 -0
  12. package/python/Dockerfile +64 -0
  13. package/python/LICENSE +21 -0
  14. package/python/MANIFEST.in +14 -0
  15. package/python/README.md +201 -0
  16. package/python/README_CN.md +336 -0
  17. package/python/bench/bm25_audit.py +186 -0
  18. package/python/bench/bm25_regression.py +177 -0
  19. package/python/bench/bm25_stress.py +155 -0
  20. package/python/bench/cd_audit.py +215 -0
  21. package/python/bench/chaos.py +140 -0
  22. package/python/bench/concurrency.py +103 -0
  23. package/python/bench/e2e_real_models.py +94 -0
  24. package/python/bench/llm_retry_test.py +241 -0
  25. package/python/bench/load_test.py +193 -0
  26. package/python/bench/vector_ivf_upgrade.py +128 -0
  27. package/python/docs/CHANNELS.md +195 -0
  28. package/python/docs/GOVERNANCE.md +164 -0
  29. package/python/docs/RELEASE.md +110 -0
  30. package/python/docs/RUN.md +75 -0
  31. package/python/docs/RUN_CN.md +121 -0
  32. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  33. package/python/evals/__init__.py +0 -0
  34. package/python/evals/governance/README.md +61 -0
  35. package/python/evals/governance/__init__.py +0 -0
  36. package/python/evals/governance/cases.py +115 -0
  37. package/python/evals/governance/compare_embeddings.py +94 -0
  38. package/python/evals/governance/convergence.svg +11 -0
  39. package/python/evals/governance/report.json +371 -0
  40. package/python/evals/governance/run_evals.py +368 -0
  41. package/python/examples/governance_middleware_demo.py +72 -0
  42. package/python/examples/langgraph_governance_demo.py +104 -0
  43. package/python/examples/sample_itinerary.csv +6 -0
  44. package/python/examples/sample_knowledge.csv +9 -0
  45. package/python/examples/sample_transactions.csv +14 -0
  46. package/python/foxclaw/__init__.py +408 -0
  47. package/python/foxclaw/agents/__init__.py +12 -0
  48. package/python/foxclaw/agents/agents.py +35 -0
  49. package/python/foxclaw/agents/base.py +794 -0
  50. package/python/foxclaw/agents/impl/__init__.py +1 -0
  51. package/python/foxclaw/agents/impl/_helpers.py +70 -0
  52. package/python/foxclaw/agents/impl/finance.py +231 -0
  53. package/python/foxclaw/agents/impl/office.py +350 -0
  54. package/python/foxclaw/agents/impl/supervisor.py +592 -0
  55. package/python/foxclaw/agents/mixins/_context.py +128 -0
  56. package/python/foxclaw/agents/mixins/augment.py +143 -0
  57. package/python/foxclaw/agents/mixins/llm.py +67 -0
  58. package/python/foxclaw/agents/mixins/memory.py +41 -0
  59. package/python/foxclaw/agents/mixins/preference.py +131 -0
  60. package/python/foxclaw/agents/mixins/prompt.py +71 -0
  61. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  62. package/python/foxclaw/agents/mixins/tool_exec.py +417 -0
  63. package/python/foxclaw/api/__init__.py +224 -0
  64. package/python/foxclaw/api/_state.py +43 -0
  65. package/python/foxclaw/api/bootstrap.py +96 -0
  66. package/python/foxclaw/api/middleware.py +294 -0
  67. package/python/foxclaw/api/routes/ahs.py +404 -0
  68. package/python/foxclaw/api/routes/attachments.py +116 -0
  69. package/python/foxclaw/api/routes/browser.py +35 -0
  70. package/python/foxclaw/api/routes/domain.py +306 -0
  71. package/python/foxclaw/api/routes/knowledge.py +145 -0
  72. package/python/foxclaw/api/routes/llm_admin.py +449 -0
  73. package/python/foxclaw/api/routes/memory.py +105 -0
  74. package/python/foxclaw/api/routes/orchestration.py +39 -0
  75. package/python/foxclaw/api/routes/recovery.py +44 -0
  76. package/python/foxclaw/api/routes/review.py +266 -0
  77. package/python/foxclaw/api/routes/search.py +33 -0
  78. package/python/foxclaw/api/routes/system.py +311 -0
  79. package/python/foxclaw/api/routes/task.py +263 -0
  80. package/python/foxclaw/api/routes/ws.py +64 -0
  81. package/python/foxclaw/api/spine.py +52 -0
  82. package/python/foxclaw/browser/__init__.py +13 -0
  83. package/python/foxclaw/browser/action/__init__.py +1 -0
  84. package/python/foxclaw/browser/action/action_executor.py +50 -0
  85. package/python/foxclaw/browser/action/action_types.py +44 -0
  86. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  87. package/python/foxclaw/browser/action/url_policy.py +143 -0
  88. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  89. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  90. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  91. package/python/foxclaw/browser/driver/__init__.py +1 -0
  92. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  93. package/python/foxclaw/browser/driver/playwright_driver.py +170 -0
  94. package/python/foxclaw/browser/perception/__init__.py +1 -0
  95. package/python/foxclaw/browser/perception/dom_extractor.py +42 -0
  96. package/python/foxclaw/browser/perception/element_indexer.py +56 -0
  97. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  98. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  99. package/python/foxclaw/browser/service.py +144 -0
  100. package/python/foxclaw/browser/session/__init__.py +1 -0
  101. package/python/foxclaw/browser/session/session_manager.py +57 -0
  102. package/python/foxclaw/capability/__init__.py +12 -0
  103. package/python/foxclaw/capability/embedding_service.py +65 -0
  104. package/python/foxclaw/channels/__init__.py +138 -0
  105. package/python/foxclaw/channels/discord.py +175 -0
  106. package/python/foxclaw/channels/dispatcher.py +179 -0
  107. package/python/foxclaw/channels/feishu.py +215 -0
  108. package/python/foxclaw/channels/openclaw_bridge.py +151 -0
  109. package/python/foxclaw/channels/signing.py +187 -0
  110. package/python/foxclaw/channels/telegram.py +120 -0
  111. package/python/foxclaw/channels/weixin.py +215 -0
  112. package/python/foxclaw/cli.py +183 -0
  113. package/python/foxclaw/cognitive/__init__.py +4 -0
  114. package/python/foxclaw/cognitive/reasoner.py +81 -0
  115. package/python/foxclaw/cognitive/state.py +141 -0
  116. package/python/foxclaw/core/__init__.py +9 -0
  117. package/python/foxclaw/core/audit_chain.py +131 -0
  118. package/python/foxclaw/core/audit_sink.py +100 -0
  119. package/python/foxclaw/core/compaction.py +265 -0
  120. package/python/foxclaw/core/config.py +65 -0
  121. package/python/foxclaw/core/confirm_nonce.py +151 -0
  122. package/python/foxclaw/core/dispatch.py +27 -0
  123. package/python/foxclaw/core/envfile.py +48 -0
  124. package/python/foxclaw/core/failure_policy.py +142 -0
  125. package/python/foxclaw/core/gateway.py +81 -0
  126. package/python/foxclaw/core/governance_audit.py +171 -0
  127. package/python/foxclaw/core/idempotency.py +282 -0
  128. package/python/foxclaw/core/injection_guard.py +202 -0
  129. package/python/foxclaw/core/memory_adapter.py +489 -0
  130. package/python/foxclaw/core/metrics.py +88 -0
  131. package/python/foxclaw/core/model_router.py +985 -0
  132. package/python/foxclaw/core/paths.py +103 -0
  133. package/python/foxclaw/core/recovery.py +349 -0
  134. package/python/foxclaw/core/scheduler.py +141 -0
  135. package/python/foxclaw/core/secrets.py +131 -0
  136. package/python/foxclaw/core/sessions.py +155 -0
  137. package/python/foxclaw/core/settings.py +414 -0
  138. package/python/foxclaw/core/shared_write.py +186 -0
  139. package/python/foxclaw/core/steering_queue.py +154 -0
  140. package/python/foxclaw/core/system_bus.py +84 -0
  141. package/python/foxclaw/core/thought_chain.py +316 -0
  142. package/python/foxclaw/core/token_meter.py +231 -0
  143. package/python/foxclaw/core/tools.py +164 -0
  144. package/python/foxclaw/core/tracing.py +375 -0
  145. package/python/foxclaw/core/truth_pipeline.py +110 -0
  146. package/python/foxclaw/core/update_check.py +149 -0
  147. package/python/foxclaw/core/vec_recall_adapter.py +477 -0
  148. package/python/foxclaw/core/verify.py +173 -0
  149. package/python/foxclaw/core/web_search.py +218 -0
  150. package/python/foxclaw/finance/__init__.py +15 -0
  151. package/python/foxclaw/finance/evidence.py +149 -0
  152. package/python/foxclaw/finance/fx.py +98 -0
  153. package/python/foxclaw/finance/graph.py +179 -0
  154. package/python/foxclaw/finance/models.py +146 -0
  155. package/python/foxclaw/finance/nlparse.py +515 -0
  156. package/python/foxclaw/finance/policy.py +301 -0
  157. package/python/foxclaw/finance/reasoner.py +121 -0
  158. package/python/foxclaw/finance/reasoning.py +319 -0
  159. package/python/foxclaw/finance/reconcile.py +175 -0
  160. package/python/foxclaw/finance/runtime.py +500 -0
  161. package/python/foxclaw/governance/__init__.py +24 -0
  162. package/python/foxclaw/governance/middleware.py +176 -0
  163. package/python/foxclaw/harness/__init__.py +17 -0
  164. package/python/foxclaw/harness/_base.py +66 -0
  165. package/python/foxclaw/harness/_policy.py +340 -0
  166. package/python/foxclaw/harness/drift.py +77 -0
  167. package/python/foxclaw/harness/event_bus.py +28 -0
  168. package/python/foxclaw/harness/harness.py +358 -0
  169. package/python/foxclaw/harness/loop_detection.py +145 -0
  170. package/python/foxclaw/itinerary/__init__.py +10 -0
  171. package/python/foxclaw/itinerary/amap_client.py +114 -0
  172. package/python/foxclaw/itinerary/models.py +80 -0
  173. package/python/foxclaw/itinerary/reasoner.py +96 -0
  174. package/python/foxclaw/itinerary/reasoning.py +168 -0
  175. package/python/foxclaw/itinerary/runtime.py +144 -0
  176. package/python/foxclaw/kernel/__init__.py +13 -0
  177. package/python/foxclaw/kernel/capabilities.py +72 -0
  178. package/python/foxclaw/kernel/policy_gate.py +108 -0
  179. package/python/foxclaw/kernel/process_manager.py +186 -0
  180. package/python/foxclaw/kernel/scheduler.py +125 -0
  181. package/python/foxclaw/kernel/sentinel.py +443 -0
  182. package/python/foxclaw/kernel/sentinel_client.py +243 -0
  183. package/python/foxclaw/kernel/spine.py +97 -0
  184. package/python/foxclaw/knowledge/__init__.py +360 -0
  185. package/python/foxclaw/knowledge/truth_crystallizer.py +89 -0
  186. package/python/foxclaw/learning/__init__.py +12 -0
  187. package/python/foxclaw/learning/knowledge_graph.py +144 -0
  188. package/python/foxclaw/learning/models.py +93 -0
  189. package/python/foxclaw/learning/reasoner.py +67 -0
  190. package/python/foxclaw/learning/runtime.py +351 -0
  191. package/python/foxclaw/learning/spaced_repetition.py +91 -0
  192. package/python/foxclaw/mcp_server.py +188 -0
  193. package/python/foxclaw/orchestration/__init__.py +167 -0
  194. package/python/foxclaw/pipeline/__init__.py +23 -0
  195. package/python/foxclaw/pipeline/cognitive_pipeline.py +429 -0
  196. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  197. package/python/foxclaw/pipeline/supervisor_stages.py +201 -0
  198. package/python/foxclaw/planning/__init__.py +17 -0
  199. package/python/foxclaw/planning/llm_planner.py +232 -0
  200. package/python/foxclaw/planning/planner.py +94 -0
  201. package/python/foxclaw/search/__init__.py +24 -0
  202. package/python/foxclaw/search/agent_port.py +190 -0
  203. package/python/foxclaw/search/evidence.py +109 -0
  204. package/python/foxclaw/search/external_search.py +50 -0
  205. package/python/foxclaw/search/hkc_read_port.py +60 -0
  206. package/python/foxclaw/search/internal_search.py +71 -0
  207. package/python/foxclaw/search/models.py +127 -0
  208. package/python/foxclaw/search/planner.py +44 -0
  209. package/python/foxclaw/search/retriever.py +39 -0
  210. package/python/foxclaw/search/runtime.py +127 -0
  211. package/python/foxclaw/search/strategy.py +44 -0
  212. package/python/foxclaw/skills/__init__.py +559 -0
  213. package/python/foxclaw/skills/archive.py +111 -0
  214. package/python/foxclaw/skills/fetch.py +226 -0
  215. package/python/foxclaw/skills/hooks_spec.py +69 -0
  216. package/python/foxclaw/skills/install_cmd.py +216 -0
  217. package/python/foxclaw/skills/lifecycle.py +252 -0
  218. package/python/foxclaw/skills/markdown_skill.py +175 -0
  219. package/python/foxclaw/skills/markdown_store.py +39 -0
  220. package/python/foxclaw/skills/mcp_client.py +184 -0
  221. package/python/foxclaw/skills/mcp_store.py +40 -0
  222. package/python/foxclaw/skills/permission.py +145 -0
  223. package/python/foxclaw/skills/pipeline.py +354 -0
  224. package/python/foxclaw/skills/policy.py +74 -0
  225. package/python/foxclaw/skills/review.py +72 -0
  226. package/python/foxclaw/skills/store.py +57 -0
  227. package/python/frontend/assets/core_bg.jpg +0 -0
  228. package/python/frontend/deck/auth.js +25 -0
  229. package/python/frontend/deck/chat_model_picker.js +483 -0
  230. package/python/frontend/deck/domain.js +276 -0
  231. package/python/frontend/deck/main.js +1806 -0
  232. package/python/frontend/deck/markdown.js +44 -0
  233. package/python/frontend/deck/settings.js +307 -0
  234. package/python/frontend/deck/state.js +5 -0
  235. package/python/frontend/deck/util.js +4 -0
  236. package/python/frontend/deck/visuals.js +190 -0
  237. package/python/frontend/deck.css +855 -0
  238. package/python/frontend/deck.html +686 -0
  239. package/python/frontend/deck_v2.html +341 -0
  240. package/python/frontend/planet/index.html +266 -0
  241. package/python/frontend/planet/planet.css +579 -0
  242. package/python/frontend/planet/planet.js +1436 -0
  243. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  244. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  245. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  246. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  247. package/python/frontend/v3/index.html +15 -0
  248. package/python/frontend/widget_runtime.js +252 -0
  249. package/python/frontend/widgets.js +639 -0
  250. package/python/frontend_src/index.html +14 -0
  251. package/python/frontend_src/package-lock.json +1376 -0
  252. package/python/frontend_src/package.json +19 -0
  253. package/python/frontend_src/src/App.vue +58 -0
  254. package/python/frontend_src/src/api.js +82 -0
  255. package/python/frontend_src/src/components/ChatDock.vue +365 -0
  256. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  257. package/python/frontend_src/src/components/DocViewer.vue +162 -0
  258. package/python/frontend_src/src/components/Inspector.vue +235 -0
  259. package/python/frontend_src/src/components/LeftRail.vue +415 -0
  260. package/python/frontend_src/src/components/MenuPopover.vue +128 -0
  261. package/python/frontend_src/src/components/MessageStream.vue +389 -0
  262. package/python/frontend_src/src/components/ModelPicker.vue +350 -0
  263. package/python/frontend_src/src/components/QuickIngest.vue +112 -0
  264. package/python/frontend_src/src/components/Stage.vue +272 -0
  265. package/python/frontend_src/src/components/ToolMenu.vue +141 -0
  266. package/python/frontend_src/src/components/TopBar.vue +118 -0
  267. package/python/frontend_src/src/components/panels/ActivityPanel.vue +82 -0
  268. package/python/frontend_src/src/components/panels/AgentsPanel.vue +83 -0
  269. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +425 -0
  270. package/python/frontend_src/src/components/panels/MemoryPanel.vue +117 -0
  271. package/python/frontend_src/src/components/panels/PanelBox.vue +50 -0
  272. package/python/frontend_src/src/components/panels/ReviewPanel.vue +292 -0
  273. package/python/frontend_src/src/components/panels/SettingsPanel.vue +358 -0
  274. package/python/frontend_src/src/components/panels/TasksPanel.vue +73 -0
  275. package/python/frontend_src/src/components/panels/ToolsPanel.vue +746 -0
  276. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  277. package/python/frontend_src/src/lib/fmt.js +44 -0
  278. package/python/frontend_src/src/lib/i18n.js +53 -0
  279. package/python/frontend_src/src/lib/locales/en.js +173 -0
  280. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  281. package/python/frontend_src/src/lib/md.js +190 -0
  282. package/python/frontend_src/src/main.js +9 -0
  283. package/python/frontend_src/src/stores/app.js +91 -0
  284. package/python/frontend_src/src/stores/chat.js +450 -0
  285. package/python/frontend_src/src/stores/model.js +96 -0
  286. package/python/frontend_src/src/stores/panels.js +289 -0
  287. package/python/frontend_src/src/stores/profile.js +31 -0
  288. package/python/frontend_src/src/stores/system.js +25 -0
  289. package/python/frontend_src/src/styles/base.css +138 -0
  290. package/python/frontend_src/src/styles/panel.css +117 -0
  291. package/python/frontend_src/src/styles/tokens.css +78 -0
  292. package/python/frontend_src/vite.config.js +39 -0
  293. package/python/pyproject.toml +268 -0
  294. package/python/requirements.txt +311 -0
  295. package/python/skills/README.md +123 -0
  296. package/python/skills/execution-contract/SKILL.md +30 -0
  297. package/python/skills/general-assistant/SKILL.md +34 -0
  298. package/python/skills/planner/SKILL.md +33 -0
  299. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  300. package/python/skills/skill-composition/SKILL.md +31 -0
  301. package/python/skills/skill-guard/SKILL.md +67 -0
  302. package/python/skills/skill-memory/SKILL.md +50 -0
  303. package/python/skills/skill-router/SKILL.md +35 -0
  304. package/python/tests/conftest.py +54 -0
  305. package/python/tests/run_all.py +200 -0
  306. package/python/tests/test_agent_lifecycle.py +326 -0
  307. package/python/tests/test_ahs_p2.py +393 -0
  308. package/python/tests/test_anthropic_tools.py +192 -0
  309. package/python/tests/test_api.py +97 -0
  310. package/python/tests/test_api_auth.py +79 -0
  311. package/python/tests/test_api_host_guard.py +69 -0
  312. package/python/tests/test_attachments.py +151 -0
  313. package/python/tests/test_audit_chain.py +116 -0
  314. package/python/tests/test_audit_sink.py +130 -0
  315. package/python/tests/test_browser.py +149 -0
  316. package/python/tests/test_browser_request_guard.py +180 -0
  317. package/python/tests/test_capabilities.py +152 -0
  318. package/python/tests/test_channel_signing.py +185 -0
  319. package/python/tests/test_channel_trust.py +109 -0
  320. package/python/tests/test_channels.py +174 -0
  321. package/python/tests/test_cli.py +46 -0
  322. package/python/tests/test_compaction_toolpair.py +119 -0
  323. package/python/tests/test_config_layering.py +61 -0
  324. package/python/tests/test_confirm_nonce.py +186 -0
  325. package/python/tests/test_context_compaction_wiring.py +124 -0
  326. package/python/tests/test_dispatch.py +89 -0
  327. package/python/tests/test_domain_evals.py +334 -0
  328. package/python/tests/test_domain_import.py +72 -0
  329. package/python/tests/test_domain_reasoners.py +134 -0
  330. package/python/tests/test_e2e.py +161 -0
  331. package/python/tests/test_emb_cache.py +97 -0
  332. package/python/tests/test_embedding_singleton.py +87 -0
  333. package/python/tests/test_envfile.py +83 -0
  334. package/python/tests/test_evals_smoke.py +90 -0
  335. package/python/tests/test_event_spine.py +174 -0
  336. package/python/tests/test_evidence_semantic.py +88 -0
  337. package/python/tests/test_evidence_sse.py +94 -0
  338. package/python/tests/test_file_ingest.py +135 -0
  339. package/python/tests/test_finance.py +144 -0
  340. package/python/tests/test_finance_ledger.py +245 -0
  341. package/python/tests/test_finance_persist.py +174 -0
  342. package/python/tests/test_gateway.py +125 -0
  343. package/python/tests/test_governance.py +421 -0
  344. package/python/tests/test_governance_audit.py +324 -0
  345. package/python/tests/test_governance_middleware.py +137 -0
  346. package/python/tests/test_hkc.py +268 -0
  347. package/python/tests/test_hkc_index_build_manager.py +306 -0
  348. package/python/tests/test_hkc_manifest_reuse.py +294 -0
  349. package/python/tests/test_hkc_search_event_sync.py +482 -0
  350. package/python/tests/test_import_path_guard.py +155 -0
  351. package/python/tests/test_injection_guard.py +193 -0
  352. package/python/tests/test_integration.py +117 -0
  353. package/python/tests/test_itinerary.py +100 -0
  354. package/python/tests/test_kee_semantic.py +84 -0
  355. package/python/tests/test_kernel.py +212 -0
  356. package/python/tests/test_learning.py +101 -0
  357. package/python/tests/test_llm_admin.py +145 -0
  358. package/python/tests/test_llm_planner.py +344 -0
  359. package/python/tests/test_markdown_skills.py +62 -0
  360. package/python/tests/test_markdown_skills_openclaw.py +66 -0
  361. package/python/tests/test_mcp_connectors.py +97 -0
  362. package/python/tests/test_mcp_guardian_discovery.py +87 -0
  363. package/python/tests/test_mcp_no_memory.py +43 -0
  364. package/python/tests/test_mcp_policy_gate.py +76 -0
  365. package/python/tests/test_mcp_server.py +92 -0
  366. package/python/tests/test_memory_recall.py +92 -0
  367. package/python/tests/test_metrics.py +58 -0
  368. package/python/tests/test_multi_chain.py +90 -0
  369. package/python/tests/test_multimodal.py +122 -0
  370. package/python/tests/test_negotiation.py +136 -0
  371. package/python/tests/test_orchestration.py +109 -0
  372. package/python/tests/test_orchestration_decoupled.py +103 -0
  373. package/python/tests/test_p3_p5_hardening.py +324 -0
  374. package/python/tests/test_parallel_dispatch.py +207 -0
  375. package/python/tests/test_pipeline.py +492 -0
  376. package/python/tests/test_plan_visible.py +86 -0
  377. package/python/tests/test_planner.py +88 -0
  378. package/python/tests/test_policy_gate.py +108 -0
  379. package/python/tests/test_profile.py +67 -0
  380. package/python/tests/test_readme_claims.py +163 -0
  381. package/python/tests/test_reasoner.py +170 -0
  382. package/python/tests/test_release_check.py +252 -0
  383. package/python/tests/test_resume_checkpoint.py +148 -0
  384. package/python/tests/test_routing.py +85 -0
  385. package/python/tests/test_scheduler.py +138 -0
  386. package/python/tests/test_search.py +194 -0
  387. package/python/tests/test_secrets.py +153 -0
  388. package/python/tests/test_semantic.py +69 -0
  389. package/python/tests/test_sessions.py +90 -0
  390. package/python/tests/test_settings.py +265 -0
  391. package/python/tests/test_shadow_learning.py +290 -0
  392. package/python/tests/test_shared_write.py +200 -0
  393. package/python/tests/test_skill_taint_gate.py +145 -0
  394. package/python/tests/test_skills.py +117 -0
  395. package/python/tests/test_skills_fetch.py +282 -0
  396. package/python/tests/test_stats_and_events.py +162 -0
  397. package/python/tests/test_streaming.py +55 -0
  398. package/python/tests/test_supervisor_pipeline.py +176 -0
  399. package/python/tests/test_supervisor_triage.py +102 -0
  400. package/python/tests/test_taint.py +112 -0
  401. package/python/tests/test_task_stream.py +74 -0
  402. package/python/tests/test_thinking_sse.py +66 -0
  403. package/python/tests/test_thinking_stream.py +76 -0
  404. package/python/tests/test_tool_center.py +113 -0
  405. package/python/tests/test_tool_parallel.py +113 -0
  406. package/python/tests/test_tool_params.py +99 -0
  407. package/python/tests/test_tool_use.py +229 -0
  408. package/python/tests/test_toplevel_concurrency.py +116 -0
  409. package/python/tests/test_tracing.py +256 -0
  410. package/python/tests/test_truth_crystallizer.py +125 -0
  411. package/python/tests/test_truth_flow.py +88 -0
  412. package/python/tests/test_update_check.py +95 -0
  413. package/python/tests/test_url_policy.py +53 -0
  414. package/python/tests/test_verify_evidence.py +212 -0
  415. package/python/tests/test_version_sync.py +100 -0
  416. package/python/tests/test_web_trigger.py +82 -0
  417. package/python/tools/check_faiss_type.py +30 -0
  418. package/python/tools/check_log.ps1 +38 -0
  419. package/python/tools/ivf_status.py +72 -0
  420. package/python/tools/mypy_baseline.txt +13 -0
  421. package/python/tools/mypy_ratchet.py +143 -0
  422. package/python/tools/pack.py +173 -0
  423. package/python/tools/release_check.py +349 -0
  424. package/python/tools/restart_foxclaw.ps1 +71 -0
  425. package/python/tools/shared_write_audit.py +190 -0
  426. package/python/tools/size_baseline.txt +28 -0
  427. package/python/tools/size_check.py +102 -0
  428. package/python/tools/strip_comments.py +228 -0
  429. package/python/tools/verify_env.bat +2 -0
  430. package/python/tools/verify_env.py +25 -0
  431. package/python/vendor/HMR_LICENSE +37 -0
  432. package/python/vendor/HMR_README.md +65 -0
  433. package/python/vendor/hkc/LICENSE +37 -0
  434. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  435. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  436. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  437. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  438. package/python/vendor/hkc/hkc_api/container.py +416 -0
  439. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  440. package/python/vendor/hkc/hkc_api/main.py +157 -0
  441. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  442. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  443. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  444. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  445. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  446. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  447. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  448. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  449. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  450. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  451. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  452. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  453. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  454. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  455. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  456. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  457. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  458. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  459. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  460. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  461. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  462. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  463. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  464. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  465. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  466. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  467. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  468. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  469. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  470. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  471. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  472. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  473. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  474. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  475. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  476. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  477. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  478. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  479. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  480. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  481. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  482. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  483. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  484. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  485. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  486. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  487. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  488. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  489. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  490. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  491. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  492. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  493. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  494. package/python/vendor/hmr/__init__.py +0 -0
  495. package/python/vendor/hmr/core/__init__.py +0 -0
  496. package/python/vendor/hmr/core/hmr.py +684 -0
  497. package/python/vendor/hmr/core/models.py +310 -0
  498. package/python/vendor/hmr/engines/__init__.py +0 -0
  499. package/python/vendor/hmr/engines/evolution.py +710 -0
  500. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  501. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  502. package/python/vendor/hmr/engines/policy.py +625 -0
  503. package/python/vendor/hmr/engines/recall.py +260 -0
  504. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  505. package/python/vendor/hmr/engines/scheduler.py +323 -0
  506. package/python/vendor/hmr/engines/semantic.py +150 -0
  507. package/python/vendor/hmr/engines/temporal.py +261 -0
  508. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  509. package/python/vendor/hmr/graph/__init__.py +0 -0
  510. package/python/vendor/hmr/graph/cwg.py +338 -0
  511. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  512. package/python/vendor/hmr/storage/__init__.py +0 -0
  513. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  514. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,108 @@
1
+ """
2
+ 统一准入门(PolicyGate, v2 ① Step4)测试 —— 全离线。
3
+
4
+ 覆盖:
5
+ 1. 推理准入归一化:Harness PASS/WARN/BLOCK → Verdict allow/warn/deny(raw 透传同一 HarnessResult)
6
+ 2. 动作准入归一化:permission allow/ask/deny → Verdict allow/confirm/deny(raw 透传);
7
+ 非 allow 发统一 policy.decision.* 事件,allow 不发(降噪)
8
+ 3. 真实集成:经 Foxclaw,技能路径(副作用 skill 需确认)发出 policy.decision 事件
9
+ """
10
+ import os, sys, asyncio, tempfile, shutil
11
+ from pathlib import Path
12
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
13
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
14
+ os.environ["FOXCLAW_EMBEDDING_MODEL"] = "foxclaw-nonexistent-fast"
15
+ sys.path.insert(0, str(Path(__file__).parent.parent))
16
+
17
+ from foxclaw.kernel import PolicyGate
18
+
19
+
20
+ class _HR:
21
+ def __init__(self, blocked=False, warned=False, reason=""):
22
+ self._b, self._w, self.reason = blocked, warned, reason
23
+ @property
24
+ def is_blocked(self): return self._b
25
+ @property
26
+ def is_warned(self): return self._w
27
+
28
+
29
+ class _Harness:
30
+ def __init__(self, hr): self.hr = hr; self.calls = 0
31
+ def check(self, node): self.calls += 1; return self.hr
32
+
33
+
34
+ class _PD:
35
+ def __init__(self, decision, reason=""):
36
+ self.decision, self.stage, self.reason, self.trust_downgrade = decision, 2, reason, False
37
+
38
+
39
+ class _CapSpine:
40
+ def __init__(self): self.events = []
41
+ def publish(self, topic, payload=None, source="", **kw): self.events.append(topic)
42
+
43
+
44
+ class _Skill:
45
+ id = "gmail_send"
46
+
47
+
48
+ def test_reasoning_normalization():
49
+ print("\n【1】推理准入归一化(Harness → Verdict;raw 透传)")
50
+ gate = PolicyGate()
51
+ h = _Harness(_HR())
52
+ v = gate.check_reasoning(h, None)
53
+ assert v.level == "allow" and v.allowed and v.raw is h.hr and h.calls == 1
54
+ v = gate.check_reasoning(_Harness(_HR(warned=True)), None)
55
+ assert v.level == "warn" and v.allowed
56
+ v = gate.check_reasoning(_Harness(_HR(blocked=True, reason="越界")), None)
57
+ assert v.level == "deny" and not v.allowed and v.reason == "越界"
58
+ print(" ✓ PASS→allow / WARN→warn / BLOCK→deny;raw 仍是同一 HarnessResult(行为一致)")
59
+
60
+
61
+ def test_action_normalization_and_event():
62
+ print("\n【2】动作准入归一化 + 非 allow 发 policy.decision")
63
+ for decision, level, event in [("allow", "allow", None),
64
+ ("ask", "confirm", "policy.decision.confirm"),
65
+ ("deny", "deny", "policy.decision.deny")]:
66
+ spine = _CapSpine()
67
+ pd = _PD(decision, reason=f"r-{decision}")
68
+ gate = PolicyGate(spine=spine, action_evaluator=lambda *a, _pd=pd, **k: _pd)
69
+ v = gate.check_action(_Skill(), {})
70
+ assert v.level == level and v.raw is pd, f"{decision}→{v.level}"
71
+ assert v.allowed == (decision == "allow")
72
+ assert spine.events == ([event] if event else []), (decision, spine.events)
73
+ print(" ✓ allow(不发) / ask→confirm(发) / deny(发);raw 透传 PermissionDecision")
74
+
75
+
76
+ def test_integration_skill_path():
77
+ print("\n【3】真实集成:技能路径经统一门,副作用 skill 发 policy.decision")
78
+ d = tempfile.mkdtemp(prefix="pg_")
79
+ try:
80
+ from foxclaw import Foxclaw
81
+ os_ = Foxclaw(data_dir=d, shadow_rounds=50)
82
+ seen = []
83
+ os_.event_spine.project(lambda e: seen.append(e.topic))
84
+ os_.supervisor.use_skill("帮我发邮件给客户",
85
+ args={"to": "a@b.com", "subject": "x", "body": "y"})
86
+ assert any(t.startswith("policy.decision") for t in seen), \
87
+ f"技能准入应发 policy.decision,实得 {[t for t in seen if 'policy' in t]}"
88
+ assert os_.capabilities.get("policy_gate") is os_.policy_gate
89
+ print(f" ✓ use_skill(副作用)→ 发 {[t for t in seen if t.startswith('policy')]}")
90
+ asyncio.run(os_.aclose())
91
+ finally:
92
+ shutil.rmtree(d, ignore_errors=True)
93
+
94
+
95
+ def main():
96
+ print("=" * 60)
97
+ print("统一准入门 · PolicyGate(v2 ① Step4)")
98
+ print("=" * 60)
99
+ test_reasoning_normalization()
100
+ test_action_normalization_and_event()
101
+ test_integration_skill_path()
102
+ print("\n" + "=" * 60)
103
+ print("✓ Step4 验证完成:两个门统一到 Verdict 词汇 + 一个接口 + 统一审计")
104
+ print("=" * 60)
105
+
106
+
107
+ if __name__ == "__main__":
108
+ main()
@@ -0,0 +1,67 @@
1
+ """称呼(user profile)存取:round-trip、改一次不留旧条目、可清空、不误删别的偏好。
2
+
3
+ 用 stub 宿主直接测 mixin,不初始化 Agent / API(那条路径由 test_api 覆盖)。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from foxclaw.agents.mixins.preference import PreferenceMixin
9
+
10
+
11
+ class _Stub(PreferenceMixin):
12
+ AGENT_ID = "stub"
13
+
14
+ def __init__(self):
15
+ self.items: list[dict] = []
16
+ self._seq = 0
17
+
18
+ def _store_memory(self, content, mem_type="concept", **kwargs):
19
+ self._seq += 1
20
+ self.items.insert(0, {"id": f"m{self._seq}", "content": content})
21
+
22
+ def list_preferences(self, limit=50):
23
+ return self.items[:limit]
24
+
25
+ def delete_preference(self, pref_id):
26
+ before = len(self.items)
27
+ self.items = [p for p in self.items if p["id"] != pref_id]
28
+ return len(self.items) < before
29
+
30
+
31
+ def test_nickname_roundtrip():
32
+ s = _Stub()
33
+ assert s.get_nickname() == ""
34
+ assert s.set_nickname(" snow ") == "snow"
35
+ assert s.get_nickname() == "snow"
36
+
37
+
38
+ def test_set_nickname_replaces_old():
39
+ """改称呼不能留下两条互相矛盾的偏好。"""
40
+ s = _Stub()
41
+ s.set_nickname("snow")
42
+ s.set_nickname("snow2")
43
+ assert [s._nick_of(p["content"]) for p in s.items] == ["snow2"]
44
+ assert s.get_nickname() == "snow2"
45
+
46
+
47
+ def test_clear_nickname():
48
+ s = _Stub()
49
+ s.set_nickname("snow")
50
+ assert s.set_nickname("") == ""
51
+ assert s.get_nickname() == ""
52
+ assert s.items == []
53
+
54
+
55
+ def test_other_preferences_untouched():
56
+ s = _Stub()
57
+ s.remember_preference("回答尽量简短")
58
+ s.set_nickname("snow")
59
+ contents = [p["content"] for p in s.items]
60
+ assert "用户偏好:回答尽量简短" in contents
61
+ assert "用户偏好:称呼我为 snow" in contents
62
+
63
+
64
+ def test_nickname_truncated():
65
+ s = _Stub()
66
+ long = "x" * 60
67
+ assert s.set_nickname(long) == long[:24]
@@ -0,0 +1,163 @@
1
+ """README 与仓库实际的一致性巡检。
2
+
3
+ README 里的结构和数字曾经长期漂移:目录注释写「五智能体」而实际三个、
4
+ 写「76 个测试文件」而实际 110、写「77 个依赖」而实际 96 条。
5
+ 这类不一致第一眼就被识破,所以把可机器核对的部分钉在这里:
6
+ 改了代码不改文档,这个文件会红。
7
+
8
+ 刻意不钉的东西:测试文件个数、代码行数、依赖条目数——它们每次提交都在变,
9
+ 写进 README 就是下一个「76」。需要真实数字时用命令现取,不抄文档。
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from pathlib import Path
15
+
16
+ import pytest
17
+
18
+ ROOT = Path(__file__).resolve().parent.parent
19
+ README_EN = ROOT / "README.md"
20
+ README_CN = ROOT / "README_CN.md"
21
+
22
+ HKC_ROUTES = ROOT / "vendor" / "hkc" / "hkc_api" / "routes" / "knowledge.py"
23
+ KDE = ROOT / "vendor" / "hkc" / "hkc_kde" / "kde.py"
24
+ CHUNKER = ROOT / "vendor" / "hkc" / "hkc_kde" / "chunker.py"
25
+ EXTRACTOR = ROOT / "vendor" / "hkc" / "hkc_kde" / "extractor.py"
26
+
27
+
28
+ @pytest.fixture(scope="module")
29
+ def docs() -> dict[str, str]:
30
+ return {p.name: p.read_text(encoding="utf-8") for p in (README_EN, README_CN)}
31
+
32
+
33
+ def _num(path: Path, pattern: str) -> int:
34
+ m = re.search(pattern, path.read_text(encoding="utf-8"), re.M)
35
+ assert m, f"{path.relative_to(ROOT)} 中找不到模式 {pattern!r}"
36
+ return int(m.group(1))
37
+
38
+
39
+ def test_agents_are_three(docs: dict[str, str]) -> None:
40
+ impl = sorted(
41
+ p.stem
42
+ for p in (ROOT / "foxclaw" / "agents" / "impl").glob("*.py")
43
+ if not p.stem.startswith("_")
44
+ )
45
+ assert impl == ["finance", "office", "supervisor"], f"实际智能体实现: {impl}"
46
+
47
+ for name, text in docs.items():
48
+ assert "五智能体" not in text, f"{name} 仍写着五智能体"
49
+ assert "五个智能体" not in text, f"{name} 仍写着五个智能体"
50
+ assert "five agents" not in text.lower(), f"{name} 仍写着 five agents"
51
+
52
+ assert "Three agents" in docs["README.md"]
53
+ assert "三个智能体" in docs["README_CN.md"]
54
+
55
+
56
+ def test_channels_are_four() -> None:
57
+ for adapter in ("weixin", "feishu", "telegram", "discord"):
58
+ assert (ROOT / "foxclaw" / "channels" / f"{adapter}.py").exists(), adapter
59
+ assert (ROOT / "foxclaw" / "channels" / "dispatcher.py").exists()
60
+
61
+ text = README_EN.read_text(encoding="utf-8")
62
+ for brand in ("WeChat", "Feishu", "Telegram", "Discord"):
63
+ assert brand in text, brand
64
+
65
+
66
+ def test_runtime_governance_modules_exist() -> None:
67
+ for rel in (
68
+ "foxclaw/core/idempotency.py",
69
+ "foxclaw/core/recovery.py",
70
+ "foxclaw/harness/loop_detection.py",
71
+ "foxclaw/core/compaction.py",
72
+ "foxclaw/core/steering_queue.py",
73
+ "docs/GOVERNANCE.md",
74
+ ):
75
+ assert (ROOT / rel).exists(), rel
76
+
77
+
78
+ def test_ingest_limits_match_docs(docs: dict[str, str]) -> None:
79
+ upload_mb = _num(HKC_ROUTES, r"MAX_UPLOAD_BYTES\s*=\s*(\d+)\s*\*\s*1024\s*\*\s*1024")
80
+ assert upload_mb == 20, f"单文件上传上限已变为 {upload_mb}MB,README 写的 20MB 要改"
81
+
82
+ assert _num(HKC_ROUTES, r"MAX_INGEST_CHARS\s*=\s*([\d_]+)") == 2_000_000
83
+ assert _num(KDE, r"DEFAULT_MAX_CHUNKS_PER_DOC\s*=\s*(\d+)") == 800
84
+ assert _num(EXTRACTOR, r'"HKC_EXTRACT_MAX_TOKENS",\s*"(\d+)"') == 8192
85
+
86
+ for name, text in docs.items():
87
+ assert "20 MB" in text or "20MB" in text, name
88
+ assert "2,000,000" in text, name
89
+ assert "800" in text, name
90
+ assert "8192" in text, name
91
+
92
+
93
+ def test_chunk_geometry_matches_docs(docs: dict[str, str]) -> None:
94
+ max_tokens = _num(CHUNKER, r"^MAX_TOKENS\s*=\s*(\d+)")
95
+ overlap_tokens = _num(CHUNKER, r"^OVERLAP_TOKENS\s*=\s*(\d+)")
96
+ m = re.search(r"^CHARS_PER_TOKEN\s*=\s*([\d.]+)", CHUNKER.read_text(encoding="utf-8"), re.M)
97
+ assert m, "chunker.py 中找不到 CHARS_PER_TOKEN"
98
+ chars_per_token = float(m.group(1))
99
+
100
+ max_chars = int(max_tokens * chars_per_token)
101
+ overlap_chars = int(overlap_tokens * chars_per_token)
102
+ step = max_chars - overlap_chars
103
+
104
+ assert (max_chars, overlap_chars, step) == (1792, 224, 1568), (
105
+ f"分块几何已变为 {max_chars}/{overlap_chars}/{step},README 写的 1792/224/1568 要改"
106
+ )
107
+ for name, text in docs.items():
108
+ for value in ("1792", "224", "1568"):
109
+ assert value in text, f"{name} 未出现 {value}"
110
+
111
+
112
+ def test_supported_formats_match_docs(docs: dict[str, str]) -> None:
113
+ src = HKC_ROUTES.read_text(encoding="utf-8")
114
+ m = re.search(r"ALLOWED_SUFFIXES\s*=\s*\((.*?)\)", src, re.S)
115
+ assert m, "knowledge.py 中找不到 ALLOWED_SUFFIXES"
116
+ suffixes = re.findall(r'"(\.\w+)"', m.group(1))
117
+ assert len(suffixes) == 10, f"支持格式已变为 {suffixes}"
118
+
119
+ for name, text in docs.items():
120
+ for suffix in suffixes:
121
+ assert suffix in text, f"{name} 未列出支持格式 {suffix}"
122
+
123
+
124
+ def test_relative_links_exist(docs: dict[str, str]) -> None:
125
+ for name, text in docs.items():
126
+ for target in re.findall(r"\]\(([^)#:]+)\)", text):
127
+ if target.startswith(("http", "mailto")):
128
+ continue
129
+ assert (ROOT / target).exists(), f"{name} 的链接指向不存在的 {target}"
130
+
131
+
132
+ def test_directory_tree_paths_exist() -> None:
133
+ for rel in (
134
+ "foxclaw/core",
135
+ "foxclaw/kernel",
136
+ "foxclaw/pipeline",
137
+ "foxclaw/planning",
138
+ "foxclaw/cognitive",
139
+ "foxclaw/knowledge",
140
+ "foxclaw/agents",
141
+ "foxclaw/harness",
142
+ "foxclaw/finance",
143
+ "foxclaw/learning",
144
+ "foxclaw/itinerary",
145
+ "foxclaw/channels",
146
+ "foxclaw/skills",
147
+ "foxclaw/search",
148
+ "foxclaw/browser",
149
+ "foxclaw/capability",
150
+ "foxclaw/governance",
151
+ "vendor/hmr",
152
+ "vendor/hkc",
153
+ "examples",
154
+ "tests",
155
+ "docs",
156
+ ):
157
+ assert (ROOT / rel).is_dir(), f"目录树列出的 {rel} 不存在"
158
+
159
+
160
+ def test_referenced_skills_exist(docs: dict[str, str]) -> None:
161
+ for skill in ("general-assistant", "schedule-itinerary"):
162
+ assert (ROOT / "skills" / skill).is_dir(), skill
163
+ assert skill in docs["README_CN.md"], skill
@@ -0,0 +1,170 @@
1
+ """
2
+ 领域认知化(v2 ③ Step1:DomainReasoner + 财务守门人形式化)测试 —— 全离线。
3
+
4
+ 覆盖:
5
+ 1. DomainReasoner 基座:reason() 按序 observe→hypothesize→gather_evidence→arbitrate
6
+ 2. FinanceGatekeeperReasoner:小额→approve、大额→confirm、低余额→reject(freeze);
7
+ 裁决与原守门人逐字一致 + 显式证据链(source=rules) + 假设
8
+ 3. 真实路径:orchestrator.route_request 走认知化守门人,req 附证据链(可审计)
9
+ """
10
+ import os, sys, asyncio, tempfile, shutil
11
+ from pathlib import Path
12
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
13
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
14
+ os.environ["FOXCLAW_EMBEDDING_MODEL"] = "foxclaw-nonexistent-fast"
15
+ sys.path.insert(0, str(Path(__file__).parent.parent))
16
+
17
+ from foxclaw.cognitive.reasoner import DomainReasoner, ReasonedVerdict, Evidence
18
+
19
+
20
+ def test_reasoner_base_order():
21
+ print("\n【1】DomainReasoner 基座:四步按序执行")
22
+ order = []
23
+ class R(DomainReasoner):
24
+ def observe(self, s): order.append("observe"); return {"x": s}
25
+ def hypothesize(self, o): order.append("hypothesize"); return ["h"]
26
+ def gather_evidence(self, s, o): order.append("evidence"); return [Evidence("f", "d")]
27
+ def arbitrate(self, s, o, h, e):
28
+ order.append("arbitrate")
29
+ return ReasonedVerdict("approve", "ok", e, h, "rules")
30
+ rv = R().reason(42)
31
+ assert order == ["observe", "hypothesize", "evidence", "arbitrate"], order
32
+ assert rv.decision == "approve" and rv.hypotheses == ["h"] and len(rv.evidence) == 1
33
+ print(" ✓ observe→hypothesize→gather_evidence→arbitrate;证据链随裁决返回")
34
+
35
+
36
+ def _policy():
37
+ from foxclaw.finance import FinanceRuntime
38
+ p = FinanceRuntime(policy_path=tempfile.mktemp(suffix=".yaml"),
39
+ data_path=tempfile.mktemp(suffix=".json")).policy
40
+ for r in p.rules:
41
+ if r.id == "large_purchase_check":
42
+ r.threshold = 200.0
43
+ r.condition = "单笔支出 > 200"
44
+ return p
45
+
46
+
47
+ def test_finance_gatekeeper_reasoner():
48
+ print("\n【2】财务守门人认知化:裁决逐字一致 + 证据链 + 假设")
49
+ from foxclaw.finance.reasoner import FinanceGatekeeperReasoner
50
+ from foxclaw.finance.models import Transaction
51
+ r = FinanceGatekeeperReasoner(_policy())
52
+ rv = r.reason((Transaction(amount=-150, category="travel", entity="高铁", account="default"), 5000))
53
+ assert rv.decision == "approve" and rv.rationale == "财务批准:travel 预算 150 在规则内"
54
+ assert rv.source == "rules" and any(e.factor == "rules_clear" for e in rv.evidence)
55
+ assert rv.hypotheses, "应有假设"
56
+ rv2 = r.reason((Transaction(amount=-680, category="travel", entity="酒店", account="default"), 5000))
57
+ assert rv2.decision == "confirm" and any(e.factor == "large_purchase_check" for e in rv2.evidence)
58
+ print(f" ✓ 小额→approve(证据 rules_clear);大额→confirm(证据 large_purchase_check;假设={rv2.hypotheses})")
59
+
60
+
61
+ def test_context_evidence_no_decision_change():
62
+ print("\n【2b】Step2:情境证据(历史/预算/频率)丰满证据链,但**决策不变**")
63
+ from foxclaw.finance import FinanceRuntime
64
+ from foxclaw.finance.reasoner import FinanceGatekeeperReasoner
65
+ from foxclaw.finance.models import Transaction
66
+ rt = FinanceRuntime(policy_path=tempfile.mktemp(suffix=".yaml"),
67
+ data_path=tempfile.mktemp(suffix=".json"))
68
+ rt.set_budget("travel", 3000); rt.set_balance("default", 5000)
69
+ for _ in range(3):
70
+ rt.add_transaction(Transaction(amount=-100, category="travel", entity="高铁",
71
+ account="default"), run_gatekeeper=False)
72
+ r = FinanceGatekeeperReasoner(rt.policy, runtime=rt)
73
+ rv = r.reason((Transaction(amount=-150, category="travel", entity="高铁",
74
+ account="default"), 5000))
75
+ assert rv.decision == "approve", "规则内 → 决策仍是 approve(情境证据不改裁决)"
76
+ factors = {e.factor for e in rv.evidence}
77
+ assert {"history_category", "budget_remaining", "entity_frequency"} <= factors, factors
78
+ rv0 = FinanceGatekeeperReasoner(rt.policy).reason(
79
+ (Transaction(amount=-150, category="travel", entity="高铁", account="default"), 5000))
80
+ assert not ({"history_category", "budget_remaining", "entity_frequency"}
81
+ & {e.factor for e in rv0.evidence})
82
+ assert rv0.decision == rv.decision == "approve"
83
+ print(f" ✓ 决策 approve 不变;证据链含 {sorted(factors & {'history_category','budget_remaining','entity_frequency'})}")
84
+
85
+
86
+ class _MockLLM:
87
+ def __init__(self, resp="APPROVE", raises=False):
88
+ self.resp, self.raises, self.calls, self.is_stub = resp, raises, 0, False
89
+ def complete(self, system, messages, max_tokens=2048, agent_id=""):
90
+ self.calls += 1
91
+ if self.raises:
92
+ raise RuntimeError("llm down")
93
+ return self.resp
94
+
95
+
96
+ def test_llm_arbitration_safety():
97
+ print("\n【2c】Step3:LLM 裁决的安全不变量(只增强模糊、只能 approve→confirm、永不 reject)")
98
+ from foxclaw.finance.reasoner import FinanceGatekeeperReasoner
99
+ from foxclaw.finance.models import Transaction
100
+ policy = _policy()
101
+ def txn(amt): return Transaction(amount=-amt, category="travel", entity="高铁", account="default")
102
+ def mk(llm, enabled=True): return FinanceGatekeeperReasoner(policy, llm=llm, llm_enabled=enabled)
103
+
104
+ llm = _MockLLM("APPROVE"); rv = mk(llm).reason((txn(680), 5000))
105
+ assert rv.decision == "confirm" and rv.source == "rules" and llm.calls == 0
106
+
107
+ llm = _MockLLM("CONFIRM"); rv = mk(llm).reason((txn(150), 5000))
108
+ assert rv.decision == "confirm" and rv.source == "llm" and llm.calls == 1
109
+
110
+ llm = _MockLLM("APPROVE"); rv = mk(llm).reason((txn(150), 5000))
111
+ assert rv.decision == "approve" and rv.source == "llm"
112
+
113
+ llm = _MockLLM(raises=True); rv = mk(llm).reason((txn(150), 5000))
114
+ assert rv.decision == "confirm" and rv.source == "llm_failsafe"
115
+
116
+ for bad in ["REJECT", "让我拒绝这笔", "不确定", "yes maybe"]:
117
+ llm = _MockLLM(bad); rv = mk(llm).reason((txn(150), 5000))
118
+ assert rv.decision == "confirm" and rv.source == "llm_failsafe", (bad, rv.decision)
119
+ assert rv.decision != "reject", "LLM 永远不能产生 reject"
120
+
121
+ llm = _MockLLM("CONFIRM"); rv = mk(llm).reason((txn(50), 5000))
122
+ assert rv.decision == "approve" and rv.source == "rules" and llm.calls == 0
123
+
124
+ llm = _MockLLM("CONFIRM"); rv = mk(llm, enabled=False).reason((txn(150), 5000))
125
+ assert rv.decision == "approve" and rv.source == "rules" and llm.calls == 0
126
+
127
+ stub = _MockLLM("CONFIRM"); stub.is_stub = True
128
+ rv = FinanceGatekeeperReasoner(policy, llm=stub, llm_enabled=True).reason((txn(150), 5000))
129
+ assert rv.source == "rules" and stub.calls == 0
130
+ print(" ✓ 规则权威不问LLM · 模糊只 approve/confirm · 出错/乱回 fail-safe confirm · "
131
+ "永不 reject · 明显安全/默认关/stub 不调")
132
+
133
+
134
+ def test_real_path_evidence():
135
+ print("\n【3】真实路径:认知化守门人 + req 附证据链")
136
+ d = tempfile.mkdtemp(prefix="reasoner_")
137
+ try:
138
+ from foxclaw import Foxclaw
139
+ from foxclaw.orchestration import CrossAgentRequest
140
+ os_ = Foxclaw(data_dir=d, shadow_rounds=50)
141
+ os_.finance_runtime.set_balance("default", 5000)
142
+ req = os_.orchestrator.route_request(CrossAgentRequest(
143
+ from_agent="admin", to_agent="finance", request_type="budget_request",
144
+ payload={"amount": 680, "category": "travel", "entity": "酒店", "account": "default"},
145
+ reason="大额"))
146
+ assert req.status == "needs_confirmation", req.status
147
+ assert hasattr(req, "evidence") and req.evidence, "守门人应附证据链"
148
+ assert req.reason_source == "rules"
149
+ print(f" ✓ status={req.status}(裁决不变);evidence={req.evidence};source={req.reason_source}")
150
+ asyncio.run(os_.aclose())
151
+ finally:
152
+ shutil.rmtree(d, ignore_errors=True)
153
+
154
+
155
+ def main():
156
+ print("=" * 60)
157
+ print("领域认知化 · DomainReasoner + 财务守门人(v2 ③ Step1)")
158
+ print("=" * 60)
159
+ test_reasoner_base_order()
160
+ test_finance_gatekeeper_reasoner()
161
+ test_context_evidence_no_decision_change()
162
+ test_llm_arbitration_safety()
163
+ test_real_path_evidence()
164
+ print("\n" + "=" * 60)
165
+ print("✓ Step1 验证完成:观察→假设→证据→裁决 形式化;规则实现、裁决不变、证据链可审计")
166
+ print("=" * 60)
167
+
168
+
169
+ if __name__ == "__main__":
170
+ main()