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,334 @@
1
+ """领域推理评测框架 —— 让"推理深度"从感觉变成**数字**。
2
+
3
+ 为什么需要它:守门人的价值不取决于"规则多不多",而取决于
4
+ · **漏报**(该抓没抓)→ 规则缺 → 补规则
5
+ · **误报**(拦了但没事)→ 规则太粗/阈值不合 → **必须能定位是哪条规则干的**,才改得动
6
+ 误报率一高,用户就会把守门人关掉 —— 那它就形同虚设。所以误报是一等公民指标。
7
+
8
+ 怎么用(给未来的你/我):
9
+ · 往 CASES 里加**你真实遇到的情形** + 期望裁决,跑一次就知道漏报/误报和**归因**。
10
+ · 加一条新规则后重跑:立刻看到它抓住了什么、误伤了什么。
11
+ · 与 test_evals_smoke.py(治理层拦截率/误报)同款方法,搬到领域推理层。
12
+
13
+ 裁决语义:approve=放行 / confirm=要你确认 / reject=拒。
14
+ "标注"= expect_flag:True 表示"这笔该被拦下来(confirm 或 reject)"。
15
+ """
16
+ import sys, tempfile
17
+ from pathlib import Path
18
+ sys.path.insert(0, str(Path(__file__).parent.parent))
19
+
20
+ def test_domain_evals():
21
+ from foxclaw.finance import FinanceRuntime
22
+ from foxclaw.finance.models import Transaction
23
+ from foxclaw.finance.reasoner import FinanceGatekeeperReasoner
24
+
25
+ print("=" * 60); print("领域推理评测(财务守门人)"); print("=" * 60)
26
+
27
+
28
+ def _policy():
29
+ return FinanceRuntime(policy_path=tempfile.mktemp(suffix=".yaml"),
30
+ data_path=tempfile.mktemp(suffix=".json")).policy
31
+
32
+
33
+ CASES = [
34
+ ("买咖啡", -35, "food", 8000, False),
35
+ ("午饭", -60, "food", 8000, False),
36
+ ("打车", -80, "transport", 8000, False),
37
+ ("买菜", -150, "food", 8000, False),
38
+ ("买书", -220, "book", 8000, False),
39
+ ("聚餐 AA", -320, "food", 8000, False),
40
+ ("买鞋", -450, "shopping", 8000, False),
41
+ ("买手机", -4500, "digital", 8000, True),
42
+ ("交房租", -3000, "housing", 8000, True),
43
+ ("买笔记本", -8000, "digital", 20000, True),
44
+ ("低余额时消费", -100, "food", 300, True),
45
+ ("发工资", +15000, "salary", 8000, False),
46
+ ]
47
+
48
+
49
+ def evaluate(cases, policy):
50
+ """跑评测,返回 (漏报, 误报) —— 误报带**归因**(是哪条规则拦的)。"""
51
+ misses, false_pos = [], []
52
+ for name, amount, category, balance, expect_flag in cases:
53
+ txn = Transaction(amount=amount, category=category, entity=name, account="default")
54
+ rv = FinanceGatekeeperReasoner(policy).reason((txn, balance))
55
+ flagged = rv.decision in ("confirm", "reject")
56
+ if expect_flag and not flagged:
57
+ misses.append((name, amount, rv.decision))
58
+ elif not expect_flag and flagged:
59
+ rules = [e.factor for e in rv.evidence if e.raw is not None]
60
+ false_pos.append((name, amount, rv.decision, rules))
61
+ return misses, false_pos
62
+
63
+
64
+ pol = _policy()
65
+ misses, fps = evaluate(CASES, pol)
66
+ n = len(CASES)
67
+ n_pos = sum(1 for c in CASES if c[4])
68
+ n_neg = n - n_pos
69
+ miss_rate = len(misses) / n_pos if n_pos else 0.0
70
+ fp_rate = len(fps) / n_neg if n_neg else 0.0
71
+
72
+ print(f"\n案例 {n}(该拦 {n_pos} / 不该拦 {n_neg})")
73
+ print(f" 漏报率 = {miss_rate:.0%} ({len(misses)}/{n_pos})")
74
+ print(f" 误报率 = {fp_rate:.0%} ({len(fps)}/{n_neg})")
75
+ if misses:
76
+ print(" 【漏报】该抓没抓 → 缺规则:")
77
+ for nm, amt, d in misses:
78
+ print(f" {nm}({amt}) → {d}")
79
+ if fps:
80
+ print(" 【误报】拦了但没事 → 归因到规则(据此改阈值/规则):")
81
+ for nm, amt, d, rules in fps:
82
+ print(f" {nm}({amt}) → {d} ← 触发规则: {rules or '(未归因)'}")
83
+
84
+ assert not misses, f"存在漏报(该抓没抓): {misses}"
85
+ print("\n[1] 无漏报(>500 与低余额保护都抓住了)OK")
86
+
87
+ assert fp_rate <= 0.15, f"误报率过高({fp_rate:.0%}),用户会关掉守门人: {fps}"
88
+ print(f"[2] 误报率 {fp_rate:.0%} ≤ 15%(沿用 evals_smoke 同款红线)OK")
89
+
90
+ pol_bad = _policy()
91
+ for r in pol_bad.rules:
92
+ if r.id == "large_purchase_check":
93
+ r.threshold = 100.0
94
+ _m2, fps2 = evaluate(CASES, pol_bad)
95
+ assert fps2, "阈值压到 100 应制造出误报(用于验证归因)"
96
+ assert all("large_purchase_check" in rules for _, _, _, rules in fps2), \
97
+ f"误报必须能归因到具体规则,否则无从改起: {fps2}"
98
+ print(f"[3] 误报可归因(阈值压到100 → {len(fps2)} 条误报,全部指向 large_purchase_check)OK")
99
+
100
+ from foxclaw.finance.policy import DEFAULT_RULES
101
+ p1, p2 = _policy(), _policy()
102
+ r1 = [r for r in p1.rules if r.id == "large_purchase_check"][0]
103
+ r2 = [r for r in p2.rules if r.id == "large_purchase_check"][0]
104
+ rd = [r for r in DEFAULT_RULES if r.id == "large_purchase_check"][0]
105
+ assert r1 is not r2, "不同 runtime 不得共享同一规则对象"
106
+ r1.threshold = 99.0
107
+ assert r2.threshold != 99.0, "改一个 runtime 的阈值污染了另一个(共享可变状态)"
108
+ assert rd.threshold != 99.0, "改 runtime 的阈值污染了模块级 DEFAULT_RULES"
109
+ print("[4] 规则实例隔离(改一处不污染其它 runtime / 模块级默认)OK")
110
+
111
+ assert rd.threshold == 500.0, f"默认单笔阈值应为 500,实际 {rd.threshold}"
112
+ print("[5] 阈值为可调项(finance_policy.yaml 可改,默认 500)OK")
113
+
114
+ from datetime import datetime, timedelta
115
+ from foxclaw.finance.models import Budget
116
+ from foxclaw.finance.reasoning import FinanceReasoning as FR
117
+
118
+ def _txn(amount, entity, days_ago=0, category="misc", tags=None):
119
+ return Transaction(amount=amount, category=category, entity=entity, account="default",
120
+ timestamp=(datetime.now() - timedelta(days=days_ago)).isoformat(),
121
+ tags=tags or [])
122
+
123
+ def _fire(**kw):
124
+ """跑状态裁决,返回触发的 rule_id 集合。"""
125
+ return {v.rule_id for v in _policy().evaluate_state(**kw)}
126
+
127
+ dups = FR.detect_duplicate_charges([
128
+ _txn(-99, "SomeApp", days_ago=1), _txn(-99, "SomeApp", days_ago=1),
129
+ ])
130
+ assert dups, "重复扣款应被检测到"
131
+ assert "duplicate_charge_alert" in _fire(duplicates=dups), "检测到却不裁决(老毛病)"
132
+ assert "duplicate_charge_alert" not in _fire(duplicates=[]), "无重复扣款不该报警"
133
+ print(f"[6] 同一商家高频扣款 → 触发裁决({dups[0]['reason']})OK")
134
+
135
+ f_trend = {"category": "food", "spent_so_far": 800, "monthly_limit": 1000,
136
+ "projected_month_end": 1600, "projected_overage": 600, "will_overspend": True}
137
+ f_safe = {"category": "food", "spent_so_far": 200, "monthly_limit": 1000,
138
+ "projected_month_end": 400, "projected_overage": 0, "will_overspend": False}
139
+ assert "overspend_trend_alert" in _fire(forecasts=[f_trend]), "超支趋势应提醒"
140
+ assert "overspend_trend_alert" not in _fire(forecasts=[f_safe]), "不会超不该提醒"
141
+ print("[7] 跨月超支趋势(还没超但会超 → 提醒;不会超 → 不吵)OK")
142
+
143
+ annual = [_txn(-598, "某会员", days_ago=362 + 365, tags=["recurring"]),
144
+ _txn(-598, "某会员", days_ago=362, tags=["recurring"])]
145
+ ren = FR.detect_upcoming_renewals(annual, horizon_days=7)
146
+ assert ren and ren[0]["cadence"] == "年订" and ren[0]["days_until"] <= 7, ren
147
+ assert "renewal_reminder" in _fire(renewals=ren)
148
+ messy = [_txn(-50, "乱扣", days_ago=200, tags=["recurring"]),
149
+ _txn(-50, "乱扣", days_ago=3, tags=["recurring"])]
150
+ assert FR.detect_upcoming_renewals(messy) == [], "间隔紊乱不该预测续订日"
151
+ assert FR.detect_upcoming_renewals([_txn(-50, "新订阅", days_ago=1, tags=["recurring"])]) == []
152
+ print(f"[8] 年费续订前提醒({ren[0]['entity']} {ren[0]['days_until']} 天后;紊乱/单次不预测)OK")
153
+
154
+ overdue = FR.detect_pending_refunds([_txn(-300, "某电商", days_ago=20, tags=["refund_expected"])])
155
+ assert overdue and overdue[0]["days_waiting"] >= 14, overdue
156
+ assert "refund_overdue_alert" in _fire(pending_refunds=overdue)
157
+ done = FR.detect_pending_refunds([_txn(-300, "某电商", days_ago=20, tags=["refund_expected"]),
158
+ _txn(+300, "某电商", days_ago=2)])
159
+ assert done == [], "退款已到账不该再报"
160
+ assert FR.detect_pending_refunds([_txn(-300, "某电商", days_ago=20)]) == [], \
161
+ "未标记的普通消费不得触发退款报警(灾难性误报)"
162
+ print("[9] 退款未到账(需标记;已到账不报;未标记的普通消费不误报)OK")
163
+
164
+ assert _fire() == set(), f"无信号时不该有任何裁决: {_fire()}"
165
+ print("[10] 无信号 → 零裁决(新规则不制造背景噪音)OK")
166
+
167
+ def _stream(each, n=60, entity="日常"):
168
+ """造 n 笔支出,跨**正好** n-1 天(满足校准的样本门槛)。
169
+
170
+ 基准时刻只取一次:若每笔各调 datetime.now(),60 次调用间的微秒漂移会把跨度
171
+ 啃掉不到一天,而 .days 向下取整 → span 变成 58,校准值随机器快慢漂移。
172
+ """
173
+ base = datetime.now()
174
+ return [Transaction(amount=-each, category="misc", entity=entity, account="default",
175
+ timestamp=(base - timedelta(days=i)).isoformat())
176
+ for i in range(n)]
177
+
178
+ assert FR.calibrate_thresholds([]) == {}, "空数据不该校准"
179
+ assert FR.calibrate_thresholds(_stream(300, n=5)) == {}, "5 笔就校准 = 用垃圾数据冒充个性化"
180
+ assert FR.calibrate_thresholds(_stream(300, n=30)) == {}, \
181
+ "笔数够但跨度只有 29 天(差一天到门槛)→ 日均不可信,不该校准"
182
+ print("[11] 样本不足(空/笔数少/跨度短)→ 不校准,沿用出厂默认 OK")
183
+
184
+ _PAT = [30, 45, 60, 80, 120, 55, 90, 40, 150, 70]
185
+ def _real(scale=1.0, n=80, spike=None):
186
+ base = datetime.now()
187
+ txns = [Transaction(amount=-_PAT[i % len(_PAT)] * scale, category="misc",
188
+ entity="日常", account="default",
189
+ timestamp=(base - timedelta(days=i)).isoformat())
190
+ for i in range(n)]
191
+ if spike:
192
+ txns.append(Transaction(amount=-spike, category="misc", entity="大额",
193
+ account="default", timestamp=(base - timedelta(days=1)).isoformat()))
194
+ return txns
195
+
196
+ rich = FR.calibrate_thresholds(_real(scale=2.0))
197
+ poor = FR.calibrate_thresholds(_real(scale=0.5))
198
+ lr, lp = rich["large_purchase_check"][0], poor["large_purchase_check"][0]
199
+ assert lr > lp, f"高消费者的大额门槛应更高: 富{lr} vs 穷{lp}"
200
+ assert rich["low_balance_protection"][0] > poor["low_balance_protection"][0]
201
+ print(f"[12] 阈值随消费水平伸缩(富→大额{lr:.0f} / 穷→大额{lp:.0f};固定 500 对两者都错)OK")
202
+
203
+ base_large = FR.calibrate_thresholds(_real())["large_purchase_check"][0]
204
+ spiked_large = FR.calibrate_thresholds(_real(spike=8000))["large_purchase_check"][0]
205
+ assert abs(spiked_large - base_large) <= 100, \
206
+ f"一笔大额离群把门槛从 {base_large} 顶到 {spiked_large}(均值锚的老毛病,MAD 应免疫)"
207
+ print(f"[12b] 抗离群:加一笔 8000 大额,门槛 {base_large:.0f}→{spiked_large:.0f} 几乎不动 OK")
208
+
209
+ assert set(rich) == {"large_purchase_check", "low_balance_protection"}, \
210
+ f"只有金额类阈值有分布依据;时间类不得假装校准: {set(rich)}"
211
+ print("[13] 时间类阈值(48h/7d/14d)不参与校准(无分布依据,不给猜测套数学外衣)OK")
212
+
213
+ cal = FR.calibrate_thresholds(_real())
214
+ cal_large = cal["large_purchase_check"][0]
215
+ p14 = _policy()
216
+ ch = p14.apply_calibration(cal)
217
+ r14 = [r for r in p14.rules if r.id == "large_purchase_check"][0]
218
+ assert r14.threshold == cal_large and r14.source == "learned" and r14.calibrated_value == cal_large
219
+ assert "median" in (r14.calibrated_from or ""), "阈值必须可解释,用户才敢信"
220
+ assert "large_purchase_check" in {c["rule_id"] for c in ch}, ch
221
+ assert p14.apply_calibration(cal) == [], "值没变还写盘 = 每次 analyze 都刷一遍 yaml"
222
+ print(f"[14] 校准落盘 + 可解释({r14.calibrated_from})+ 幂等 OK")
223
+
224
+ p_user = _policy()
225
+ for r in p_user.rules:
226
+ if r.id == "large_purchase_check": r.source = "user"
227
+ p_user.apply_calibration(rich)
228
+ assert [r for r in p_user.rules if r.id == "large_purchase_check"][0].threshold == 500.0, \
229
+ "source=user 是显式锁定,校准不得覆盖"
230
+
231
+ p_edit = _policy()
232
+ for r in p_edit.rules:
233
+ if r.id == "large_purchase_check": r.threshold = 1234.0
234
+ p_edit.apply_calibration(rich)
235
+ assert [r for r in p_edit.rules if r.id == "large_purchase_check"][0].threshold == 1234.0, \
236
+ "阈值偏离出厂值 = 有人有意改过,校准必须收手"
237
+
238
+ p_re = _policy()
239
+ p_re.apply_calibration(rich)
240
+ for r in p_re.rules:
241
+ if r.id == "large_purchase_check": r.threshold = 777.0
242
+ p_re.apply_calibration(poor)
243
+ assert [r for r in p_re.rules if r.id == "large_purchase_check"][0].threshold == 777.0, \
244
+ "校准值被人改过 = 人的决定,不得再次覆盖"
245
+ print("[15] 校准不踩人的决定(user 锁定 / 手改出厂值 / 手改校准值 → 三种都收手)OK")
246
+
247
+ assert rd.threshold == 500.0, f"校准污染了模块级 DEFAULT_RULES(阈值变成 {rd.threshold})"
248
+ from foxclaw.finance.policy import FACTORY_THRESHOLDS
249
+ assert FACTORY_THRESHOLDS["large_purchase_check"] == 500.0, "出厂快照被污染 → 判定基准失效"
250
+ print("[16] 校准不污染模块级出厂默认/出厂快照 OK")
251
+
252
+ rt = FinanceRuntime(policy_path=tempfile.mktemp(suffix=".yaml"),
253
+ data_path=tempfile.mktemp(suffix=".json"))
254
+ for t in _real():
255
+ rt.add_transaction(t, run_gatekeeper=False)
256
+ rep = rt.analyze()
257
+ assert rep["threshold_calibration"], "analyze 必须把阈值变更报出来"
258
+ assert any(c["rule_id"] == "large_purchase_check" and c["from"] == 500.0
259
+ and c["to"] == cal_large for c in rep["threshold_calibration"]), \
260
+ rep["threshold_calibration"]
261
+ assert rt.analyze()["threshold_calibration"] == [], "已校准后重复 analyze 不该再报变更"
262
+ print(f"[17] 阈值变更对用户可见(analyze 报出 500→{cal_large:.0f}),且不重复打扰 OK")
263
+
264
+
265
+ from foxclaw.finance.models import Transaction as _T
266
+ def _jul(day, amt):
267
+ return _T(amount=-amt, category="food", entity="午饭", account="default",
268
+ timestamp=f"2026-07-{day:02d}T12:00:00")
269
+ food_budget = Budget(category="food", monthly_limit=1000)
270
+ early = FR.overspend_forecast([_jul(1, 100)], food_budget,
271
+ now=datetime(2026, 7, 2))
272
+ assert early["will_overspend"] and not early["reliable"], early
273
+ assert "overspend_trend_alert" not in _fire(forecasts=[early]), "月初噪音不得报警(A 修复)"
274
+ late = FR.overspend_forecast([_jul(d, 100) for d in range(1, 16)], food_budget,
275
+ now=datetime(2026, 7, 20))
276
+ assert late["reliable"], late
277
+ print("[18]【A】月初(day<7)超支外推 reliable=False → 不误报;7 天后才裁决 OK")
278
+
279
+ monthly_sub = [_txn(-30, "月刊会员", days_ago=10 + 30, tags=["recurring"]),
280
+ _txn(-30, "月刊会员", days_ago=10, tags=["recurring"])]
281
+ ren = FR.detect_upcoming_renewals(monthly_sub)
282
+ assert ren and ren[0]["cadence"] == "月订" and 18 <= ren[0]["days_until"] <= 22, ren
283
+ assert "renewal_reminder" not in _fire(renewals=ren), "默认提前 7 天 → 20 天后的续订还太远,不报"
284
+ p19 = _policy()
285
+ for r in p19.rules:
286
+ if r.id == "renewal_reminder": r.threshold = 25.0
287
+ v19 = {x.rule_id for x in p19.evaluate_state(renewals=ren)}
288
+ assert "renewal_reminder" in v19, "阈值改到 25 应生效(旧代码被检测层硬封 7 天 → 静默失效)"
289
+ print("[19]【C】续订阈值真正生效(默认7天不报20天后的;改25天则报)OK")
290
+
291
+ two_expected = [
292
+ _txn(-300, "某电商", days_ago=40, tags=["refund_expected"]),
293
+ _txn(-300, "某电商", days_ago=30, tags=["refund_expected"]),
294
+ _txn(+300, "某电商", days_ago=5),
295
+ ]
296
+ pend = FR.detect_pending_refunds(two_expected)
297
+ assert len(pend) == 1, f"两笔标记只退一笔 → 应恰好报 1 笔未到账,实得 {len(pend)}: {pend}"
298
+ both_back = two_expected[:2] + [_txn(+300, "某电商", days_ago=5),
299
+ _txn(+300, "某电商", days_ago=4)]
300
+ assert FR.detect_pending_refunds(both_back) == [], "两笔退款都到账不该报"
301
+ print("[20]【D】退款消耗式匹配(两笔同额退一笔→报第二笔;都退→不报)OK")
302
+
303
+ import threading as _th
304
+ from foxclaw.finance.policy import FinancePolicyEngine
305
+ rt21 = FinanceRuntime(policy_path=tempfile.mktemp(suffix=".yaml"),
306
+ data_path=tempfile.mktemp(suffix=".json"))
307
+ for t in _real():
308
+ rt21.add_transaction(t, run_gatekeeper=False)
309
+ errs21, seen_to = [], []
310
+ _lk = _th.Lock()
311
+ def _hammer():
312
+ try:
313
+ rep = rt21.analyze()
314
+ with _lk:
315
+ seen_to.append(tuple(sorted((c["rule_id"], c["to"])
316
+ for c in rep["threshold_calibration"])))
317
+ except Exception as e:
318
+ with _lk: errs21.append(repr(e))
319
+ _ts = [_th.Thread(target=_hammer) for _ in range(16)]
320
+ for t in _ts: t.start()
321
+ for t in _ts: t.join()
322
+ assert not errs21, f"并发 analyze/校准抛异常: {errs21[:2]}"
323
+ assert sum(1 for s in seen_to if s) == 1, \
324
+ f"首次校准应只发生一次(其余幂等空),实得 {sum(1 for s in seen_to if s)} 次非空"
325
+ reload21 = FinancePolicyEngine(str(rt21.policy.policy_path))
326
+ r21 = [r for r in reload21.rules if r.id == "large_purchase_check"][0]
327
+ assert r21.threshold == cal_large and r21.source == "learned", \
328
+ f"并发后 yaml 落盘损坏或值不对: threshold={r21.threshold}"
329
+ print(f"[21]【E】16 线程并发校准:无异常 · 首次只一次 · yaml 完好(large={r21.threshold:.0f})OK")
330
+
331
+ print("\n领域推理评测通过")
332
+
333
+ if __name__ == "__main__":
334
+ test_domain_evals()
@@ -0,0 +1,72 @@
1
+ """OS 领域导入超越 csv:三领域(finance/learning/itinerary)支持上传 csv / xlsx / json。
2
+ 多格式统一解析成 rows → 桥接临时 CSV → 复用各运行时 import_csv(唯一转换真源)。"""
3
+ import os, sys, io, json, tempfile
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent))
6
+
7
+
8
+ def test_domain_import():
9
+ os.environ["HKC_EMBEDDING"] = "stub"
10
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
11
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_import_")
12
+
13
+ import openpyxl
14
+
15
+ print("=" * 60); print("OS 领域导入(csv/xlsx/json)验证"); print("=" * 60)
16
+
17
+ from foxclaw.api import _parse_import_rows
18
+
19
+ csv_bytes = b"amount,category,entity\n-50,food,cafe\n-20,transport,metro\n"
20
+ rows = _parse_import_rows("t.csv", csv_bytes)
21
+ assert len(rows) == 2 and rows[0]["amount"] == "-50" and rows[0]["category"] == "food", rows
22
+ json_bytes = json.dumps([{"amount": -30, "category": "book"}]).encode()
23
+ assert _parse_import_rows("t.json", json_bytes)[0]["amount"] == -30
24
+ assert _parse_import_rows("t.json", b'{"rows":[{"amount":1}]}')[0]["amount"] == 1
25
+
26
+ wb = openpyxl.Workbook(); ws = wb.active
27
+ ws.append(["amount", "category", "entity"]); ws.append([-15, "coffee", "cafe"]); ws.append([-8, "snack", "shop"])
28
+ buf = io.BytesIO(); wb.save(buf); xlsx_bytes = buf.getvalue()
29
+ xr = _parse_import_rows("t.xlsx", xlsx_bytes)
30
+ assert len(xr) == 2 and str(xr[0]["amount"]) == "-15" and xr[0]["category"] == "coffee", xr
31
+ try:
32
+ _parse_import_rows("t.pptx", b"x"); raise AssertionError("应对未支持格式报错")
33
+ except ValueError:
34
+ pass
35
+ print("[1] 解析器 csv/json/xlsx + 未支持格式报错 OK")
36
+
37
+ from fastapi.testclient import TestClient
38
+ from foxclaw.api import app
39
+
40
+ with TestClient(app) as c:
41
+ r = c.post("/api/finance/import/file", files={"file": ("f.csv", csv_bytes, "text/csv")})
42
+ assert r.status_code == 200 and r.json()["imported"] == 2, r.text
43
+ r = c.post("/api/finance/import/file",
44
+ files={"file": ("f.xlsx", xlsx_bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")})
45
+ assert r.status_code == 200 and r.json()["imported"] == 2, r.text
46
+ r = c.post("/api/finance/import/file",
47
+ files={"file": ("f.json", json.dumps([{"amount": -30, "category": "book", "entity": "store"}]).encode(), "application/json")})
48
+ assert r.status_code == 200 and r.json()["imported"] == 1, r.text
49
+ print("[2] finance 导入 csv/xlsx/json(2+2+1)OK")
50
+
51
+ lc = "content,topic,tags\n间隔重复提升记忆,学习方法,记忆|复习\n费曼技巧,学习方法,输出\n".encode()
52
+ r = c.post("/api/learning/import/file", files={"file": ("l.csv", lc, "text/csv")})
53
+ assert r.status_code == 200 and r.json()["imported"] == 2, r.text
54
+ print("[3] learning 导入 csv(含 tags 管道分隔)OK")
55
+
56
+ ij = json.dumps([{"name": "故宫", "city": "北京", "duration_min": 120},
57
+ {"name": "颐和园", "city": "北京", "duration_min": 90}]).encode()
58
+ r = c.post("/api/itinerary/import/file", files={"file": ("i.json", ij, "application/json")})
59
+ assert r.status_code == 200 and r.json()["imported"] == 2, r.text
60
+ print("[4] itinerary 导入 json OK")
61
+
62
+ r = c.post("/api/finance/import/file", files={"file": ("x.pptx", b"x", "application/octet-stream")})
63
+ assert r.status_code == 400, r.status_code
64
+ r = c.post("/api/finance/import/file", files={"file": ("e.csv", b"amount,category\n", "text/csv")})
65
+ assert r.status_code == 200 and r.json()["imported"] == 0, r.text
66
+ print("[5] 未支持格式→400;空数据→imported 0 OK")
67
+
68
+ print("\nOS 领域导入(csv/xlsx/json)验证通过")
69
+
70
+
71
+ if __name__ == "__main__":
72
+ test_domain_import()
@@ -0,0 +1,134 @@
1
+ """
2
+ 领域认知化推广(v2 ③ Step4:itinerary / learning 复用 DomainReasoner)—— 全离线。
3
+
4
+ 同款双层安全不变量(与 finance 一致):
5
+ · 规则永远权威:检出硬冲突/高遗忘风险 → confirm,**绝不被 LLM 推翻/隐藏**,且不问 LLM;
6
+ · 可选 LLM 层默认关,只对"无硬问题、但紧凑/负荷重"的微妙情形**升级** approve→confirm;
7
+ · LLM 结构上无从产生"推翻规则"结果(helper 只返回 escalate/keep);
8
+ · 顾问域 fail-safe:LLM 出错/stub/默认关 → 维持规则裁决(approve),不制造确认噪声;
9
+ · 每个裁决带证据链(可审计)。
10
+ """
11
+ import os, sys
12
+ from pathlib import Path
13
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
14
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
15
+ os.environ["FOXCLAW_EMBEDDING_MODEL"] = "foxclaw-nonexistent-fast"
16
+ sys.path.insert(0, str(Path(__file__).parent.parent))
17
+
18
+
19
+ class _MockLLM:
20
+ """可配置回答的假 LLM(is_stub=False 表示"有真实模型")。"""
21
+ def __init__(self, resp="OK", raises=False):
22
+ self.resp, self.raises, self.calls, self.is_stub = resp, raises, 0, False
23
+ def complete(self, system, messages, max_tokens=2048, agent_id=""):
24
+ self.calls += 1
25
+ if self.raises:
26
+ raise RuntimeError("llm down")
27
+ return self.resp
28
+
29
+
30
+ def _wp(name, start=None, dur=0, lng=None, lat=None):
31
+ from foxclaw.itinerary.models import Waypoint
32
+ return Waypoint(name=name, start_time=start, duration_min=dur, lng=lng, lat=lat)
33
+
34
+
35
+ def test_itinerary_reasoner():
36
+ print("\n【1】行程冲突推理:规则权威 + 可选 LLM 只升级")
37
+ from foxclaw.itinerary.reasoner import ItineraryConflictReasoner
38
+ conflict = [_wp("会议A", "2026-07-08T10:00:00", 120), _wp("会议B", "2026-07-08T11:00:00", 60)]
39
+ llm = _MockLLM("OK")
40
+ rv = ItineraryConflictReasoner(llm=llm, llm_enabled=True).reason((conflict, "driving"))
41
+ assert rv.decision == "confirm" and rv.source == "rules" and llm.calls == 0
42
+ assert any(e.factor == "time_conflict" for e in rv.evidence)
43
+
44
+ loose = [_wp("A", "2026-07-08T10:00:00", 60, 116.40, 39.90),
45
+ _wp("B", "2026-07-08T12:00:00", 60, 116.50, 39.90)]
46
+ llm = _MockLLM("CONFIRM")
47
+ rv = ItineraryConflictReasoner(llm=llm, llm_enabled=True).reason((loose, "driving"))
48
+ assert rv.decision == "approve" and rv.source == "rules" and llm.calls == 0
49
+
50
+ tight = [_wp("A", "2026-07-08T10:00:00", 60, 116.40, 39.90),
51
+ _wp("B", "2026-07-08T11:25:00", 60, 116.50, 39.90)]
52
+ llm = _MockLLM("CONFIRM")
53
+ rv = ItineraryConflictReasoner(llm=llm, llm_enabled=True).reason((tight, "driving"))
54
+ assert rv.decision == "confirm" and rv.source == "llm" and llm.calls == 1
55
+
56
+ llm = _MockLLM("OK")
57
+ rv = ItineraryConflictReasoner(llm=llm, llm_enabled=True).reason((tight, "driving"))
58
+ assert rv.decision == "approve" and rv.source == "rules"
59
+
60
+ llm = _MockLLM(raises=True)
61
+ rv = ItineraryConflictReasoner(llm=llm, llm_enabled=True).reason((tight, "driving"))
62
+ assert rv.decision == "approve" and rv.source == "rules"
63
+
64
+ rv = ItineraryConflictReasoner().reason((tight, "driving"))
65
+ assert rv.decision == "approve" and rv.source == "rules"
66
+ print(" ✓ 冲突→confirm(规则,不问LLM);紧凑+LLM升级→confirm(llm);OK/出错/默认关→approve(规则)")
67
+
68
+
69
+ def _rs(item_id, rep, ease, due_days):
70
+ from datetime import datetime, timedelta
71
+ from foxclaw.learning.models import ReviewState
72
+ return ReviewState(item_id=item_id, repetitions=rep, ease_factor=ease,
73
+ due_date=(datetime.now() + timedelta(days=due_days)).isoformat())
74
+
75
+
76
+ def test_learning_reasoner():
77
+ print("\n【2】学习复习推理:规则权威 + 可选 LLM 只升级")
78
+ from foxclaw.learning.reasoner import LearningReviewReasoner
79
+ risky = [_rs("weak", 0, 1.3, 0)]
80
+ llm = _MockLLM("OK")
81
+ rv = LearningReviewReasoner(llm=llm, llm_enabled=True).reason(risky)
82
+ assert rv.decision == "confirm" and rv.source == "rules" and llm.calls == 0
83
+ assert any(e.factor == "forgetting_risk" for e in rv.evidence)
84
+
85
+ healthy = [_rs("strong", 8, 2.5, 30)]
86
+ llm = _MockLLM("CONFIRM")
87
+ rv = LearningReviewReasoner(llm=llm, llm_enabled=True).reason(healthy)
88
+ assert rv.decision == "approve" and rv.source == "rules" and llm.calls == 0
89
+
90
+ heavy = [_rs(f"h{i}", 8, 2.5, 3) for i in range(150)]
91
+ llm = _MockLLM("CONFIRM")
92
+ rv = LearningReviewReasoner(llm=llm, llm_enabled=True).reason(heavy)
93
+ assert rv.decision == "confirm" and rv.source == "llm" and llm.calls == 1
94
+
95
+ rv = LearningReviewReasoner(llm=_MockLLM("OK"), llm_enabled=True).reason(heavy)
96
+ assert rv.decision == "approve" and rv.source == "rules"
97
+ rv = LearningReviewReasoner(llm=_MockLLM(raises=True), llm_enabled=True).reason(heavy)
98
+ assert rv.decision == "approve" and rv.source == "rules"
99
+
100
+ rv = LearningReviewReasoner().reason(heavy)
101
+ assert rv.decision == "approve" and rv.source == "rules"
102
+ stub = _MockLLM("CONFIRM"); stub.is_stub = True
103
+ rv = LearningReviewReasoner(llm=stub, llm_enabled=True).reason(heavy)
104
+ assert rv.source == "rules" and stub.calls == 0
105
+ print(" ✓ 高风险→confirm(规则,不问LLM);负荷重+LLM升级→confirm(llm);OK/出错/默认/stub→approve(规则)")
106
+
107
+
108
+ def test_llm_cannot_hide_hard_problems():
109
+ print("\n【3】核心不变量:LLM 永远无法隐藏规则已检出的硬问题")
110
+ from foxclaw.itinerary.reasoner import ItineraryConflictReasoner
111
+ from foxclaw.learning.reasoner import LearningReviewReasoner
112
+ for bad in ["OK", "APPROVE", "一切正常没问题", ""]:
113
+ conflict = [_wp("A", "2026-07-08T10:00:00", 120), _wp("B", "2026-07-08T11:00:00", 60)]
114
+ rv = ItineraryConflictReasoner(llm=_MockLLM(bad), llm_enabled=True).reason((conflict, "driving"))
115
+ assert rv.decision == "confirm" and rv.source == "rules", (bad, rv.decision)
116
+ rv = LearningReviewReasoner(llm=_MockLLM(bad), llm_enabled=True).reason([_rs("w", 0, 1.3, 0)])
117
+ assert rv.decision == "confirm" and rv.source == "rules", (bad, rv.decision)
118
+ print(" ✓ 任何 LLM 回答都无法把已检出的冲突/高风险降级为 approve")
119
+
120
+
121
+ def main():
122
+ print("=" * 60)
123
+ print("领域认知化推广 · itinerary + learning(v2 ③ Step4)")
124
+ print("=" * 60)
125
+ test_itinerary_reasoner()
126
+ test_learning_reasoner()
127
+ test_llm_cannot_hide_hard_problems()
128
+ print("\n" + "=" * 60)
129
+ print("✓ Step4:两域复用 DomainReasoner;规则权威、LLM 只增强、证据链可审计")
130
+ print("=" * 60)
131
+
132
+
133
+ if __name__ == "__main__":
134
+ main()