foxclaw 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +11 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +165 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +122 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/Dockerfile +64 -0
  11. package/python/LICENSE +21 -0
  12. package/python/MANIFEST.in +14 -0
  13. package/python/README.md +192 -0
  14. package/python/README_CN.md +324 -0
  15. package/python/bench/bm25_audit.py +199 -0
  16. package/python/bench/bm25_regression.py +178 -0
  17. package/python/bench/bm25_stress.py +159 -0
  18. package/python/bench/cd_audit.py +238 -0
  19. package/python/bench/chaos.py +142 -0
  20. package/python/bench/concurrency.py +103 -0
  21. package/python/bench/e2e_real_models.py +95 -0
  22. package/python/bench/llm_retry_test.py +248 -0
  23. package/python/bench/load_test.py +196 -0
  24. package/python/bench/vector_ivf_upgrade.py +134 -0
  25. package/python/docs/CHANNELS.md +195 -0
  26. package/python/docs/GOVERNANCE.md +164 -0
  27. package/python/docs/RELEASE.md +109 -0
  28. package/python/docs/RUN.md +75 -0
  29. package/python/docs/RUN_CN.md +121 -0
  30. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  31. package/python/evals/__init__.py +0 -0
  32. package/python/evals/governance/README.md +61 -0
  33. package/python/evals/governance/__init__.py +0 -0
  34. package/python/evals/governance/cases.py +152 -0
  35. package/python/evals/governance/compare_embeddings.py +94 -0
  36. package/python/evals/governance/convergence.svg +11 -0
  37. package/python/evals/governance/report.json +371 -0
  38. package/python/evals/governance/run_evals.py +400 -0
  39. package/python/examples/governance_middleware_demo.py +77 -0
  40. package/python/examples/langgraph_governance_demo.py +108 -0
  41. package/python/examples/sample_itinerary.csv +6 -0
  42. package/python/examples/sample_knowledge.csv +9 -0
  43. package/python/examples/sample_transactions.csv +14 -0
  44. package/python/foxclaw/__init__.py +526 -0
  45. package/python/foxclaw/agents/__init__.py +12 -0
  46. package/python/foxclaw/agents/agents.py +35 -0
  47. package/python/foxclaw/agents/base.py +865 -0
  48. package/python/foxclaw/agents/impl/__init__.py +1 -0
  49. package/python/foxclaw/agents/impl/_helpers.py +78 -0
  50. package/python/foxclaw/agents/impl/finance.py +248 -0
  51. package/python/foxclaw/agents/impl/office.py +367 -0
  52. package/python/foxclaw/agents/impl/supervisor.py +659 -0
  53. package/python/foxclaw/agents/mixins/_context.py +151 -0
  54. package/python/foxclaw/agents/mixins/augment.py +149 -0
  55. package/python/foxclaw/agents/mixins/llm.py +66 -0
  56. package/python/foxclaw/agents/mixins/memory.py +35 -0
  57. package/python/foxclaw/agents/mixins/preference.py +81 -0
  58. package/python/foxclaw/agents/mixins/prompt.py +77 -0
  59. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  60. package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
  61. package/python/foxclaw/api/__init__.py +283 -0
  62. package/python/foxclaw/api/app.py +123 -0
  63. package/python/foxclaw/api/middleware.py +317 -0
  64. package/python/foxclaw/api/routes/ahs.py +417 -0
  65. package/python/foxclaw/api/routes/attachments.py +117 -0
  66. package/python/foxclaw/api/routes/browser.py +39 -0
  67. package/python/foxclaw/api/routes/domain.py +314 -0
  68. package/python/foxclaw/api/routes/knowledge.py +163 -0
  69. package/python/foxclaw/api/routes/llm_admin.py +461 -0
  70. package/python/foxclaw/api/routes/memory.py +89 -0
  71. package/python/foxclaw/api/routes/orchestration.py +41 -0
  72. package/python/foxclaw/api/routes/recovery.py +43 -0
  73. package/python/foxclaw/api/routes/review.py +275 -0
  74. package/python/foxclaw/api/routes/search.py +37 -0
  75. package/python/foxclaw/api/routes/system.py +327 -0
  76. package/python/foxclaw/api/routes/task.py +281 -0
  77. package/python/foxclaw/api/routes/ws.py +68 -0
  78. package/python/foxclaw/api/spine.py +55 -0
  79. package/python/foxclaw/browser/__init__.py +13 -0
  80. package/python/foxclaw/browser/action/__init__.py +1 -0
  81. package/python/foxclaw/browser/action/action_executor.py +54 -0
  82. package/python/foxclaw/browser/action/action_types.py +45 -0
  83. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  84. package/python/foxclaw/browser/action/url_policy.py +147 -0
  85. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  86. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  87. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  88. package/python/foxclaw/browser/driver/__init__.py +1 -0
  89. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  90. package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
  91. package/python/foxclaw/browser/perception/__init__.py +1 -0
  92. package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
  93. package/python/foxclaw/browser/perception/element_indexer.py +57 -0
  94. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  95. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  96. package/python/foxclaw/browser/service.py +148 -0
  97. package/python/foxclaw/browser/session/__init__.py +1 -0
  98. package/python/foxclaw/browser/session/session_manager.py +57 -0
  99. package/python/foxclaw/capability/__init__.py +12 -0
  100. package/python/foxclaw/capability/embedding_service.py +67 -0
  101. package/python/foxclaw/channels/__init__.py +140 -0
  102. package/python/foxclaw/channels/discord.py +184 -0
  103. package/python/foxclaw/channels/dispatcher.py +204 -0
  104. package/python/foxclaw/channels/feishu.py +225 -0
  105. package/python/foxclaw/channels/openclaw_bridge.py +156 -0
  106. package/python/foxclaw/channels/signing.py +187 -0
  107. package/python/foxclaw/channels/telegram.py +121 -0
  108. package/python/foxclaw/channels/weixin.py +225 -0
  109. package/python/foxclaw/cli.py +154 -0
  110. package/python/foxclaw/cognitive/__init__.py +4 -0
  111. package/python/foxclaw/cognitive/reasoner.py +82 -0
  112. package/python/foxclaw/cognitive/state.py +147 -0
  113. package/python/foxclaw/core/__init__.py +9 -0
  114. package/python/foxclaw/core/audit_chain.py +133 -0
  115. package/python/foxclaw/core/audit_sink.py +100 -0
  116. package/python/foxclaw/core/compaction.py +283 -0
  117. package/python/foxclaw/core/config.py +65 -0
  118. package/python/foxclaw/core/confirm_nonce.py +157 -0
  119. package/python/foxclaw/core/dispatch.py +27 -0
  120. package/python/foxclaw/core/envfile.py +48 -0
  121. package/python/foxclaw/core/failure_policy.py +151 -0
  122. package/python/foxclaw/core/gateway.py +85 -0
  123. package/python/foxclaw/core/governance_audit.py +175 -0
  124. package/python/foxclaw/core/idempotency.py +289 -0
  125. package/python/foxclaw/core/injection_guard.py +221 -0
  126. package/python/foxclaw/core/memory_adapter.py +516 -0
  127. package/python/foxclaw/core/metrics.py +89 -0
  128. package/python/foxclaw/core/model_router.py +1097 -0
  129. package/python/foxclaw/core/paths.py +103 -0
  130. package/python/foxclaw/core/recovery.py +362 -0
  131. package/python/foxclaw/core/scheduler.py +144 -0
  132. package/python/foxclaw/core/secrets.py +134 -0
  133. package/python/foxclaw/core/sessions.py +157 -0
  134. package/python/foxclaw/core/settings.py +430 -0
  135. package/python/foxclaw/core/shared_write.py +198 -0
  136. package/python/foxclaw/core/steering_queue.py +153 -0
  137. package/python/foxclaw/core/system_bus.py +84 -0
  138. package/python/foxclaw/core/thought_chain.py +318 -0
  139. package/python/foxclaw/core/token_meter.py +235 -0
  140. package/python/foxclaw/core/tools.py +172 -0
  141. package/python/foxclaw/core/tracing.py +390 -0
  142. package/python/foxclaw/core/truth_pipeline.py +114 -0
  143. package/python/foxclaw/core/vec_recall_adapter.py +503 -0
  144. package/python/foxclaw/core/verify.py +181 -0
  145. package/python/foxclaw/core/web_search.py +242 -0
  146. package/python/foxclaw/finance/__init__.py +15 -0
  147. package/python/foxclaw/finance/evidence.py +153 -0
  148. package/python/foxclaw/finance/fx.py +100 -0
  149. package/python/foxclaw/finance/graph.py +188 -0
  150. package/python/foxclaw/finance/models.py +150 -0
  151. package/python/foxclaw/finance/nlparse.py +575 -0
  152. package/python/foxclaw/finance/policy.py +329 -0
  153. package/python/foxclaw/finance/reasoner.py +129 -0
  154. package/python/foxclaw/finance/reasoning.py +340 -0
  155. package/python/foxclaw/finance/reconcile.py +180 -0
  156. package/python/foxclaw/finance/runtime.py +527 -0
  157. package/python/foxclaw/governance/__init__.py +24 -0
  158. package/python/foxclaw/governance/middleware.py +184 -0
  159. package/python/foxclaw/harness/__init__.py +17 -0
  160. package/python/foxclaw/harness/_base.py +72 -0
  161. package/python/foxclaw/harness/_policy.py +359 -0
  162. package/python/foxclaw/harness/drift.py +89 -0
  163. package/python/foxclaw/harness/event_bus.py +31 -0
  164. package/python/foxclaw/harness/harness.py +446 -0
  165. package/python/foxclaw/harness/loop_detection.py +152 -0
  166. package/python/foxclaw/itinerary/__init__.py +10 -0
  167. package/python/foxclaw/itinerary/amap_client.py +117 -0
  168. package/python/foxclaw/itinerary/models.py +80 -0
  169. package/python/foxclaw/itinerary/reasoner.py +101 -0
  170. package/python/foxclaw/itinerary/reasoning.py +174 -0
  171. package/python/foxclaw/itinerary/runtime.py +148 -0
  172. package/python/foxclaw/kernel/__init__.py +13 -0
  173. package/python/foxclaw/kernel/capabilities.py +73 -0
  174. package/python/foxclaw/kernel/policy_gate.py +114 -0
  175. package/python/foxclaw/kernel/process_manager.py +205 -0
  176. package/python/foxclaw/kernel/scheduler.py +131 -0
  177. package/python/foxclaw/kernel/sentinel.py +453 -0
  178. package/python/foxclaw/kernel/sentinel_client.py +246 -0
  179. package/python/foxclaw/kernel/spine.py +97 -0
  180. package/python/foxclaw/knowledge/__init__.py +376 -0
  181. package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
  182. package/python/foxclaw/learning/__init__.py +12 -0
  183. package/python/foxclaw/learning/knowledge_graph.py +149 -0
  184. package/python/foxclaw/learning/models.py +93 -0
  185. package/python/foxclaw/learning/reasoner.py +70 -0
  186. package/python/foxclaw/learning/runtime.py +363 -0
  187. package/python/foxclaw/learning/spaced_repetition.py +96 -0
  188. package/python/foxclaw/mcp_server.py +189 -0
  189. package/python/foxclaw/orchestration/__init__.py +184 -0
  190. package/python/foxclaw/pipeline/__init__.py +23 -0
  191. package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
  192. package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
  193. package/python/foxclaw/planning/__init__.py +17 -0
  194. package/python/foxclaw/planning/llm_planner.py +232 -0
  195. package/python/foxclaw/planning/planner.py +99 -0
  196. package/python/foxclaw/search/__init__.py +24 -0
  197. package/python/foxclaw/search/agent_port.py +194 -0
  198. package/python/foxclaw/search/evidence.py +113 -0
  199. package/python/foxclaw/search/external_search.py +50 -0
  200. package/python/foxclaw/search/hkc_read_port.py +60 -0
  201. package/python/foxclaw/search/internal_search.py +73 -0
  202. package/python/foxclaw/search/models.py +129 -0
  203. package/python/foxclaw/search/planner.py +44 -0
  204. package/python/foxclaw/search/retriever.py +39 -0
  205. package/python/foxclaw/search/runtime.py +134 -0
  206. package/python/foxclaw/search/strategy.py +44 -0
  207. package/python/foxclaw/skills/__init__.py +613 -0
  208. package/python/foxclaw/skills/hooks_spec.py +73 -0
  209. package/python/foxclaw/skills/lifecycle.py +278 -0
  210. package/python/foxclaw/skills/markdown_skill.py +174 -0
  211. package/python/foxclaw/skills/markdown_store.py +39 -0
  212. package/python/foxclaw/skills/mcp_client.py +190 -0
  213. package/python/foxclaw/skills/mcp_store.py +43 -0
  214. package/python/foxclaw/skills/permission.py +180 -0
  215. package/python/foxclaw/skills/pipeline.py +386 -0
  216. package/python/foxclaw/skills/policy.py +80 -0
  217. package/python/foxclaw/skills/review.py +72 -0
  218. package/python/foxclaw/skills/store.py +57 -0
  219. package/python/frontend/assets/core_bg.jpg +0 -0
  220. package/python/frontend/deck/auth.js +32 -0
  221. package/python/frontend/deck/chat_model_picker.js +537 -0
  222. package/python/frontend/deck/domain.js +296 -0
  223. package/python/frontend/deck/main.js +1950 -0
  224. package/python/frontend/deck/markdown.js +48 -0
  225. package/python/frontend/deck/settings.js +324 -0
  226. package/python/frontend/deck/state.js +6 -0
  227. package/python/frontend/deck/util.js +6 -0
  228. package/python/frontend/deck/visuals.js +212 -0
  229. package/python/frontend/deck.css +855 -0
  230. package/python/frontend/deck.html +686 -0
  231. package/python/frontend/deck_v2.html +341 -0
  232. package/python/frontend/planet/index.html +266 -0
  233. package/python/frontend/planet/planet.css +579 -0
  234. package/python/frontend/planet/planet.js +1637 -0
  235. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  236. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  237. package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
  238. package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
  239. package/python/frontend/v3/index.html +15 -0
  240. package/python/frontend/widget_runtime.js +295 -0
  241. package/python/frontend/widgets.js +696 -0
  242. package/python/frontend_src/index.html +14 -0
  243. package/python/frontend_src/package-lock.json +1376 -0
  244. package/python/frontend_src/package.json +19 -0
  245. package/python/frontend_src/src/App.vue +62 -0
  246. package/python/frontend_src/src/api.js +89 -0
  247. package/python/frontend_src/src/components/ChatDock.vue +377 -0
  248. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  249. package/python/frontend_src/src/components/DocViewer.vue +171 -0
  250. package/python/frontend_src/src/components/Inspector.vue +241 -0
  251. package/python/frontend_src/src/components/LeftRail.vue +394 -0
  252. package/python/frontend_src/src/components/MenuPopover.vue +130 -0
  253. package/python/frontend_src/src/components/MessageStream.vue +401 -0
  254. package/python/frontend_src/src/components/ModelPicker.vue +355 -0
  255. package/python/frontend_src/src/components/QuickIngest.vue +118 -0
  256. package/python/frontend_src/src/components/Stage.vue +226 -0
  257. package/python/frontend_src/src/components/ToolMenu.vue +144 -0
  258. package/python/frontend_src/src/components/TopBar.vue +90 -0
  259. package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
  260. package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
  261. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
  262. package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
  263. package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
  264. package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
  265. package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
  266. package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
  267. package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
  268. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  269. package/python/frontend_src/src/lib/fmt.js +48 -0
  270. package/python/frontend_src/src/lib/md.js +204 -0
  271. package/python/frontend_src/src/main.js +7 -0
  272. package/python/frontend_src/src/stores/app.js +106 -0
  273. package/python/frontend_src/src/stores/chat.js +484 -0
  274. package/python/frontend_src/src/stores/model.js +104 -0
  275. package/python/frontend_src/src/stores/panels.js +330 -0
  276. package/python/frontend_src/src/styles/base.css +138 -0
  277. package/python/frontend_src/src/styles/panel.css +117 -0
  278. package/python/frontend_src/src/styles/tokens.css +78 -0
  279. package/python/frontend_src/vite.config.js +46 -0
  280. package/python/pyproject.toml +265 -0
  281. package/python/skills/README.md +123 -0
  282. package/python/skills/execution-contract/SKILL.md +30 -0
  283. package/python/skills/general-assistant/SKILL.md +34 -0
  284. package/python/skills/planner/SKILL.md +33 -0
  285. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  286. package/python/skills/skill-composition/SKILL.md +31 -0
  287. package/python/skills/skill-guard/SKILL.md +67 -0
  288. package/python/skills/skill-memory/SKILL.md +50 -0
  289. package/python/skills/skill-router/SKILL.md +35 -0
  290. package/python/tests/conftest.py +76 -0
  291. package/python/tests/run_all.py +263 -0
  292. package/python/tests/test_agent_lifecycle.py +360 -0
  293. package/python/tests/test_ahs_p2.py +414 -0
  294. package/python/tests/test_anthropic_tools.py +206 -0
  295. package/python/tests/test_api.py +87 -0
  296. package/python/tests/test_api_auth.py +83 -0
  297. package/python/tests/test_api_host_guard.py +69 -0
  298. package/python/tests/test_attachments.py +167 -0
  299. package/python/tests/test_audit_chain.py +121 -0
  300. package/python/tests/test_audit_sink.py +136 -0
  301. package/python/tests/test_browser.py +153 -0
  302. package/python/tests/test_browser_request_guard.py +184 -0
  303. package/python/tests/test_capabilities.py +158 -0
  304. package/python/tests/test_channel_signing.py +192 -0
  305. package/python/tests/test_channel_trust.py +109 -0
  306. package/python/tests/test_channels.py +184 -0
  307. package/python/tests/test_cli.py +46 -0
  308. package/python/tests/test_compaction_toolpair.py +119 -0
  309. package/python/tests/test_config_layering.py +65 -0
  310. package/python/tests/test_confirm_nonce.py +197 -0
  311. package/python/tests/test_context_compaction_wiring.py +130 -0
  312. package/python/tests/test_dispatch.py +95 -0
  313. package/python/tests/test_domain_evals.py +403 -0
  314. package/python/tests/test_domain_import.py +78 -0
  315. package/python/tests/test_domain_reasoners.py +148 -0
  316. package/python/tests/test_e2e.py +178 -0
  317. package/python/tests/test_emb_cache.py +107 -0
  318. package/python/tests/test_embedding_singleton.py +89 -0
  319. package/python/tests/test_envfile.py +83 -0
  320. package/python/tests/test_evals_smoke.py +114 -0
  321. package/python/tests/test_event_spine.py +182 -0
  322. package/python/tests/test_evidence_semantic.py +101 -0
  323. package/python/tests/test_evidence_sse.py +97 -0
  324. package/python/tests/test_file_ingest.py +146 -0
  325. package/python/tests/test_finance.py +153 -0
  326. package/python/tests/test_finance_ledger.py +252 -0
  327. package/python/tests/test_finance_persist.py +178 -0
  328. package/python/tests/test_gateway.py +144 -0
  329. package/python/tests/test_governance.py +468 -0
  330. package/python/tests/test_governance_audit.py +359 -0
  331. package/python/tests/test_governance_middleware.py +143 -0
  332. package/python/tests/test_hkc.py +286 -0
  333. package/python/tests/test_hkc_index_build_manager.py +339 -0
  334. package/python/tests/test_hkc_manifest_reuse.py +317 -0
  335. package/python/tests/test_hkc_search_event_sync.py +533 -0
  336. package/python/tests/test_import_path_guard.py +161 -0
  337. package/python/tests/test_injection_guard.py +211 -0
  338. package/python/tests/test_integration.py +141 -0
  339. package/python/tests/test_itinerary.py +103 -0
  340. package/python/tests/test_kee_semantic.py +89 -0
  341. package/python/tests/test_kernel.py +222 -0
  342. package/python/tests/test_learning.py +110 -0
  343. package/python/tests/test_llm_admin.py +160 -0
  344. package/python/tests/test_llm_planner.py +366 -0
  345. package/python/tests/test_markdown_skills.py +73 -0
  346. package/python/tests/test_markdown_skills_openclaw.py +71 -0
  347. package/python/tests/test_mcp_connectors.py +109 -0
  348. package/python/tests/test_mcp_guardian_discovery.py +91 -0
  349. package/python/tests/test_mcp_no_memory.py +43 -0
  350. package/python/tests/test_mcp_policy_gate.py +77 -0
  351. package/python/tests/test_mcp_server.py +95 -0
  352. package/python/tests/test_memory_recall.py +95 -0
  353. package/python/tests/test_metrics.py +66 -0
  354. package/python/tests/test_multi_chain.py +102 -0
  355. package/python/tests/test_multimodal.py +135 -0
  356. package/python/tests/test_negotiation.py +146 -0
  357. package/python/tests/test_orchestration.py +119 -0
  358. package/python/tests/test_orchestration_decoupled.py +109 -0
  359. package/python/tests/test_p3_p5_hardening.py +338 -0
  360. package/python/tests/test_parallel_dispatch.py +215 -0
  361. package/python/tests/test_pipeline.py +533 -0
  362. package/python/tests/test_plan_visible.py +86 -0
  363. package/python/tests/test_planner.py +95 -0
  364. package/python/tests/test_policy_gate.py +109 -0
  365. package/python/tests/test_reasoner.py +185 -0
  366. package/python/tests/test_release_check.py +263 -0
  367. package/python/tests/test_resume_checkpoint.py +156 -0
  368. package/python/tests/test_routing.py +95 -0
  369. package/python/tests/test_scheduler.py +141 -0
  370. package/python/tests/test_search.py +204 -0
  371. package/python/tests/test_secrets.py +159 -0
  372. package/python/tests/test_semantic.py +78 -0
  373. package/python/tests/test_sessions.py +91 -0
  374. package/python/tests/test_settings.py +281 -0
  375. package/python/tests/test_shadow_learning.py +330 -0
  376. package/python/tests/test_shared_write.py +207 -0
  377. package/python/tests/test_skill_taint_gate.py +154 -0
  378. package/python/tests/test_skills.py +133 -0
  379. package/python/tests/test_stats_and_events.py +165 -0
  380. package/python/tests/test_streaming.py +60 -0
  381. package/python/tests/test_supervisor_pipeline.py +177 -0
  382. package/python/tests/test_supervisor_triage.py +108 -0
  383. package/python/tests/test_taint.py +117 -0
  384. package/python/tests/test_task_stream.py +85 -0
  385. package/python/tests/test_thinking_sse.py +67 -0
  386. package/python/tests/test_thinking_stream.py +76 -0
  387. package/python/tests/test_tool_center.py +129 -0
  388. package/python/tests/test_tool_parallel.py +113 -0
  389. package/python/tests/test_tool_params.py +103 -0
  390. package/python/tests/test_tool_use.py +242 -0
  391. package/python/tests/test_toplevel_concurrency.py +117 -0
  392. package/python/tests/test_tracing.py +290 -0
  393. package/python/tests/test_truth_crystallizer.py +127 -0
  394. package/python/tests/test_truth_flow.py +93 -0
  395. package/python/tests/test_url_policy.py +53 -0
  396. package/python/tests/test_verify_evidence.py +225 -0
  397. package/python/tests/test_version_sync.py +71 -0
  398. package/python/tests/test_web_trigger.py +99 -0
  399. package/python/tools/check_faiss_type.py +31 -0
  400. package/python/tools/check_log.ps1 +46 -0
  401. package/python/tools/ivf_status.py +72 -0
  402. package/python/tools/mypy_baseline.txt +5 -0
  403. package/python/tools/mypy_ratchet.py +97 -0
  404. package/python/tools/pack.py +178 -0
  405. package/python/tools/release_check.py +349 -0
  406. package/python/tools/restart_foxclaw.ps1 +74 -0
  407. package/python/tools/shared_write_audit.py +204 -0
  408. package/python/tools/size_baseline.txt +28 -0
  409. package/python/tools/size_check.py +102 -0
  410. package/python/tools/verify_env.bat +2 -0
  411. package/python/tools/verify_env.py +25 -0
  412. package/python/vendor/HMR_LICENSE +37 -0
  413. package/python/vendor/HMR_README.md +65 -0
  414. package/python/vendor/hkc/LICENSE +37 -0
  415. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  416. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  417. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  418. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  419. package/python/vendor/hkc/hkc_api/container.py +416 -0
  420. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  421. package/python/vendor/hkc/hkc_api/main.py +157 -0
  422. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  423. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  424. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  425. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  426. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  427. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  428. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  429. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  430. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  431. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  432. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  433. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  434. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  435. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  436. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  437. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  438. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  439. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  440. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  441. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  442. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  443. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  444. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  445. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  446. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  447. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  448. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  449. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  450. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  451. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  452. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  453. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  454. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  455. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  456. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  457. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  458. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  459. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  460. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  461. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  462. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  463. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  464. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  465. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  466. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  467. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  468. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  469. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  470. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  471. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  472. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  473. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  474. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  475. package/python/vendor/hmr/__init__.py +0 -0
  476. package/python/vendor/hmr/core/__init__.py +0 -0
  477. package/python/vendor/hmr/core/hmr.py +684 -0
  478. package/python/vendor/hmr/core/models.py +310 -0
  479. package/python/vendor/hmr/engines/__init__.py +0 -0
  480. package/python/vendor/hmr/engines/evolution.py +710 -0
  481. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  482. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  483. package/python/vendor/hmr/engines/policy.py +625 -0
  484. package/python/vendor/hmr/engines/recall.py +260 -0
  485. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  486. package/python/vendor/hmr/engines/scheduler.py +323 -0
  487. package/python/vendor/hmr/engines/semantic.py +150 -0
  488. package/python/vendor/hmr/engines/temporal.py +261 -0
  489. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  490. package/python/vendor/hmr/graph/__init__.py +0 -0
  491. package/python/vendor/hmr/graph/cwg.py +338 -0
  492. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  493. package/python/vendor/hmr/storage/__init__.py +0 -0
  494. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  495. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,89 @@
1
+ """KEE 身份/去重:别名确定性匹配(「北平」aliases=['北京'] ↔「北京」归并)。
2
+ 别名替代向量语义近重:零模型依赖、零余弦误合并;Fact/Claim 用完整 statement 守 exact。
3
+ 无别名时退化为纯 exact,与旧版默认(关)行为一致。"""
4
+ import sys, types
5
+ from pathlib import Path
6
+ sys.path.insert(0, str(Path(__file__).parent.parent))
7
+ sys.path.insert(0, str(Path(__file__).parent.parent / "vendor" / "hkc"))
8
+
9
+
10
+ def test_kee_alias_dedup():
11
+ from hkc_kee.dedup import KnowledgeDeduplicator
12
+
13
+ class FakeKU:
14
+ def __init__(self, ku_id, ku_type, name, domain="", sources=None,
15
+ aliases=None, statement=None):
16
+ self.ku_id = ku_id
17
+ self.ku_type = types.SimpleNamespace(value=ku_type)
18
+ self.name = name
19
+ self.domain = domain
20
+ self.sources = list(sources or [])
21
+ self.aliases = list(aliases or [])
22
+ self.statement = statement
23
+ self.confidence = 0.5
24
+
25
+ class FakeStore:
26
+ def __init__(self): self.kus = {}
27
+ def put(self, ku): self.kus[ku.ku_id] = ku
28
+ def get(self, i): return self.kus.get(i)
29
+ def query_all(self, limit=100000): return list(self.kus.values())
30
+ def redirect_relations(self, a, b): return 0
31
+
32
+ # [1] 别名正向归并:「北平」(aliases=['北京']) → 命中已有「北京」
33
+ store = FakeStore(); d = KnowledgeDeduplicator(store)
34
+ beijing = FakeKU("E1", "Entity", "北京", "", aliases=[])
35
+ store.put(beijing)
36
+ assert d.check_and_merge(beijing) is None, "首个应为新知识"
37
+ beiping = FakeKU("E2", "Entity", "北平", "", aliases=["北京"], sources=["EVD_A"])
38
+ store.put(beiping)
39
+ assert d.check_and_merge(beiping) == "E1", "别名应命中 canonical「北京」"
40
+ assert "EVD_A" in beijing.sources, "归并应把新证据并入存活 KU"
41
+ print("[1] 别名正向归并 OK")
42
+
43
+ # [2] 别名反向归并:已有「北平」(aliases=['北京']),新「北京」无别名 → 命中别名
44
+ store2 = FakeStore(); d2 = KnowledgeDeduplicator(store2)
45
+ beiping2 = FakeKU("E1", "Entity", "北平", "", aliases=["北京"])
46
+ store2.put(beiping2)
47
+ assert d2.check_and_merge(beiping2) is None
48
+ beijing2 = FakeKU("E2", "Entity", "北京", "", aliases=[])
49
+ store2.put(beijing2)
50
+ assert d2.check_and_merge(beijing2) == "E1", "新 KU 主名应命中已有 KU 的别名"
51
+ print("[2] 别名反向归并 OK")
52
+
53
+ # [3] 无别名 → 纯 exact:不同名不并
54
+ store3 = FakeStore(); d3 = KnowledgeDeduplicator(store3)
55
+ a = FakeKU("C1", "Concept", "value investing", "finance")
56
+ store3.put(a)
57
+ assert d3.check_and_merge(a) is None
58
+ b = FakeKU("C2", "Concept", "value investing strategy", "finance")
59
+ store3.put(b)
60
+ assert d3.check_and_merge(b) is None, "无别名时不同名不应归并(纯 exact)"
61
+ print("[3] 无别名纯 exact OK")
62
+
63
+ # [4] Fact 用完整 statement 守 exact:前 80 字符相同但完整陈述不同 → 不误并
64
+ store4 = FakeStore(); d4 = KnowledgeDeduplicator(store4)
65
+ stmt1 = "A" * 80 + "X"
66
+ stmt2 = "A" * 80 + "Y"
67
+ f1 = FakeKU("F1", "Fact", stmt1[:80], statement=stmt1)
68
+ store4.put(f1)
69
+ assert d4.check_and_merge(f1) is None
70
+ f2 = FakeKU("F2", "Fact", stmt2[:80], statement=stmt2)
71
+ store4.put(f2)
72
+ assert d4.check_and_merge(f2) is None, "前 80 字符相同但完整陈述不同,不应误并"
73
+ print("[4] Fact 完整 statement 守 exact OK")
74
+
75
+ # [5] 类型隔离:Entity 与 Concept 同名不并
76
+ store5 = FakeStore(); d5 = KnowledgeDeduplicator(store5)
77
+ ent = FakeKU("E", "Entity", "北京", "", aliases=[])
78
+ store5.put(ent)
79
+ assert d5.check_and_merge(ent) is None
80
+ con = FakeKU("C", "Concept", "北京", "地理")
81
+ store5.put(con)
82
+ assert d5.check_and_merge(con) is None, "Entity 与 Concept 同名不应归并(类型隔离)"
83
+ print("[5] 类型隔离 OK")
84
+
85
+ print("\nKEE 别名确定性去重验证通过")
86
+
87
+
88
+ if __name__ == "__main__":
89
+ test_kee_alias_dedup()
@@ -0,0 +1,222 @@
1
+ """
2
+ 内核新模块回归测试 — 覆盖 Codex 融合 + AHS 落地的全部新能力
3
+
4
+ 验证:
5
+ 1. 认知调度器(意图/优先级/负载/抢占)
6
+ 2. 进程状态机(生命周期/非法流转拦截/幂等)
7
+ 3. 系统事件总线(发布订阅/历史)
8
+ 4. 真值管线(执行→审计→记忆事件链)
9
+ 5. 认知状态快照(聚合/去重/None防护)
10
+ 6. 权限管线(四阶段 deny>ask>allow)
11
+ 7. 断路器(连续失败开闸)+ 钩子
12
+ 8. AHS 执行链(router→planner→composition 端到端)
13
+ """
14
+ import sys
15
+ from pathlib import Path
16
+ sys.path.insert(0, str(Path(__file__).parent.parent))
17
+
18
+
19
+ def check(cond, msg):
20
+ if not cond:
21
+ raise AssertionError(f"✗ {msg}")
22
+ print(f" ✓ {msg}")
23
+
24
+
25
+ def test_scheduler():
26
+ print("\n【1】认知调度器")
27
+ from foxclaw.core.scheduler import CognitiveScheduler, SystemContext
28
+ sch = CognitiveScheduler()
29
+ d_reason = sch.schedule("帮我规划设计方案")
30
+ d_tool = sch.schedule("算一下 5+5")
31
+ check(d_reason.intent.category == "reasoning", "推理类意图识别")
32
+ check(d_tool.intent.category == "tool", "工具类意图识别")
33
+ check(d_reason.priority > d_tool.priority, "推理类优先级 > 工具类")
34
+ idle = sch.schedule("规划项目", SystemContext(active_agents=0, system_load=0.1))
35
+ busy = sch.schedule("规划项目", SystemContext(active_agents=4, queue_depth=3, system_load=0.8))
36
+ check(idle.priority > busy.priority, "高负载下优先级被压低")
37
+ preempt = sch.schedule("紧急规划重大项目", SystemContext(active_agents=3))
38
+ check(preempt.preemptible, "高优先级+Agent压力触发抢占")
39
+
40
+
41
+ def test_process_manager():
42
+ print("\n【2】进程状态机")
43
+ from foxclaw.kernel import ProcessManager
44
+ pm = ProcessManager()
45
+ p = pm.create("任务", agent_id="finance", schedule={"priority": 0.5})
46
+ check(p.state == "scheduled", "创建后进入 scheduled")
47
+ check(pm.active_count() == 1, "活跃计数=1(create 后)")
48
+ pm.mark_running(p.process_id)
49
+ check(pm.active_count() == 1, "running 仍算活跃")
50
+ pm.mark_completed(p.process_id)
51
+ check(pm.active_count() == 0, "completed 后活跃计数归零(O(1) 增量)")
52
+ check(pm.get(p.process_id).state == "completed", "完整生命周期到 completed")
53
+ check(pm.get(p.process_id).state_history == ["created", "queued", "scheduled", "running", "completed"],
54
+ "状态历史完整")
55
+ # 幂等:已终态再标记不崩溃,且**不重复扣减**活跃计数
56
+ r = pm.mark_failed(p.process_id, "二次")
57
+ check(r is not None and r.state == "completed", "终态重复标记幂等不崩溃")
58
+ check(pm.active_count() == 0, "终态重复标记不重复扣减活跃计数(仍为0,不为负)")
59
+ # 非法流转拦截
60
+ p2 = pm.create("x", schedule={"priority": 0.1})
61
+ p2.state = "created"
62
+ try:
63
+ pm._transition(p2, "completed")
64
+ check(False, "非法流转应被拦")
65
+ except ValueError:
66
+ check(True, "非法流转(created→completed)被拦截")
67
+
68
+
69
+ def test_system_bus():
70
+ print("\n【3】系统事件总线")
71
+ from foxclaw.core.system_bus import SystemBus
72
+ bus = SystemBus()
73
+ received = []
74
+ bus.subscribe("test.ch", lambda m: received.append(m.payload))
75
+ bus.publish("test.ch", {"x": 1})
76
+ check(received == [{"x": 1}], "发布订阅传递正确")
77
+ check(len(bus.history("test.ch")) == 1, "事件历史记录")
78
+
79
+
80
+ def test_truth_pipeline():
81
+ print("\n【4】真值管线")
82
+ from foxclaw.core.system_bus import SystemBus
83
+ from foxclaw.core.truth_pipeline import TruthPipeline
84
+
85
+ class FakeHMR:
86
+ def __init__(self): self.items = []
87
+ def ingest(self, content, **kw): self.items.append(content)
88
+
89
+ bus = SystemBus()
90
+ hmr = FakeHMR()
91
+ tp = TruthPipeline(bus, hmr=hmr)
92
+ # 【P3-9】TruthPipeline.emit_execution 已删除:事件名即契约,直接经 bus 入队。
93
+ bus.publish("execution.completed", {"agent_id": "finance", "task": "分析预算",
94
+ "result": "预算正常", "blocked": False})
95
+ check(tp.last_trace is not None, "执行→审计 trace 生成")
96
+ check(len(hmr.items) == 1, "真值沉淀到记忆")
97
+ bus.publish("execution.completed", {"agent_id": "finance", "task": "危险",
98
+ "result": "x", "blocked": True})
99
+ check(not tp.last_memory_update.get("persisted"), "被拦截任务不沉淀记忆")
100
+
101
+
102
+ def test_cognitive_state():
103
+ print("\n【5】认知状态快照")
104
+ from foxclaw.core.system_bus import SystemBus
105
+ from foxclaw.cognitive import CognitiveStateTracker
106
+ bus = SystemBus()
107
+ t = CognitiveStateTracker(bus)
108
+ bus.publish("execution.completed", {"task": "分析财务", "agent_id": "finance",
109
+ "schedule": {"intent": "reasoning", "route_hint": "agent", "priority": 0.4}})
110
+ s = t.snapshot()
111
+ check(s["active_goal"] == "分析财务", "活跃目标聚合")
112
+ check(s["semantic_focus"] == "reasoning", "语义焦点聚合")
113
+ # 去重
114
+ for _ in range(5):
115
+ bus.publish("memory.updated", {"memory_update": {"content": "同样"}})
116
+ check(len(t.snapshot()["activated_memories"]) == 1, "重复记忆去重")
117
+ # None 防护
118
+ class M: payload = None
119
+ t._on_exec(M())
120
+ check(True, "None payload 不崩溃")
121
+
122
+
123
+ def test_permission():
124
+ print("\n【6】权限管线")
125
+ from foxclaw.skills import build_default_registry, SkillAdapter
126
+ from foxclaw.skills.permission import PermissionRules
127
+ reg = build_default_registry()
128
+ ad = SkillAdapter(reg)
129
+ calc = reg.get("calculator")
130
+ r = ad.invoke(calc, {"expression": "1+1"})
131
+ check(r.success, "只读 skill 放行")
132
+ gmail = reg.get("gmail_send")
133
+ r2 = ad.invoke(gmail, {"to": "x", "subject": "y", "body": "z"})
134
+ check(r2.status == "needs_confirmation", "发邮件未确认被拦")
135
+ rules = PermissionRules(deny=["gmail_send"])
136
+ ad2 = SkillAdapter(reg, rules=rules)
137
+ r3 = ad2.invoke(gmail, {"to": "x", "subject": "y", "body": "z"}, confirmed=True)
138
+ check(r3.status == "blocked", "deny 铁律压倒已确认")
139
+
140
+
141
+ def test_trust_single_source():
142
+ """【P6】信任分级单一真源:permission 复用 SkillSpec.trust_level(),不再有第二套 dict。
143
+ 此前 permission.TRUST_LEVEL 与 trust_level() 重复(且含死 key bundled/remote)——对 3 个合法
144
+ SkillKind 二者 downgrade 行为本就一致(latent,非 live bug),本次收敛为单一真源、行为不变。"""
145
+ print("\n【6b】信任分级单一真源(P6):permission 复用 trust_level(),删死字典")
146
+ from foxclaw.skills import SkillSpec
147
+ from foxclaw.skills import permission as perm
148
+ from foxclaw.skills.permission import evaluate
149
+ check(not hasattr(perm, "TRUST_LEVEL"), "permission.TRUST_LEVEL 死字典已删(不再双真源)")
150
+ # 3 个合法 kind × 两种 source:permission 的 downgrade 恒等于 (trust_level()=='low'),且行为不变
151
+ expect = {"local": False, "web_api": False, "mcp": True} # behavior-preserving 基线
152
+ for kind, want_dg in expect.items():
153
+ for source in ("builtin", "user"):
154
+ s = SkillSpec(id=f"s_{kind}_{source}", name="t", description="d",
155
+ kind=kind, side_effect="read", source=source)
156
+ d = evaluate(s, {})
157
+ check(d.trust_downgrade == (s.trust_level() == "low"),
158
+ f"{kind}/{source}: downgrade 严格跟随 trust_level()(单一真源)")
159
+ check(d.trust_downgrade == want_dg,
160
+ f"{kind}: downgrade={want_dg}(重构前后一致)")
161
+ print(" 结论:全部合法 kind 行为不变 + 双真源分歧点已消除")
162
+
163
+
164
+ def test_breaker_hooks():
165
+ print("\n【7】断路器 + 钩子")
166
+ from foxclaw.skills import SkillRegistry, SkillSpec, SkillAdapter
167
+ from foxclaw.skills.lifecycle import CircuitBreaker, HookManager
168
+ reg = SkillRegistry()
169
+ reg.register(SkillSpec(id="flaky", name="易碎", description="总失败",
170
+ kind="local", side_effect="read", handler=lambda **k: (_ for _ in ()).throw(RuntimeError("boom"))))
171
+ ad = SkillAdapter(reg, breaker=CircuitBreaker(threshold=3))
172
+ for _ in range(3):
173
+ ad.invoke(reg.get("flaky"), {})
174
+ check(ad.breaker.status("flaky")["state"] == "open", "连续失败3次断路器开闸")
175
+ r = ad.invoke(reg.get("flaky"), {})
176
+ check(r.status == "circuit_open", "开闸后停止调用")
177
+ # 钩子
178
+ log = []
179
+ hm = HookManager()
180
+ hm.register("pre_skill", lambda ctx: log.append("pre"))
181
+ hm.register("post_skill", lambda ctx: log.append("post"))
182
+ reg.register(SkillSpec(id="good", name="好", description="成功",
183
+ kind="local", side_effect="read", handler=lambda **k: {"ok": 1}))
184
+ ad2 = SkillAdapter(reg, hooks=hm)
185
+ ad2.invoke(reg.get("good"), {})
186
+ check(log == ["pre", "post"], "钩子 pre→post 顺序")
187
+
188
+
189
+ def test_ahs_pipeline():
190
+ print("\n【8】AHS 执行链")
191
+ from foxclaw.skills import build_default_registry, SkillAdapter
192
+ from foxclaw.skills.pipeline import AHSPipeline, route, plan
193
+ reg = build_default_registry()
194
+ ad = SkillAdapter(reg)
195
+ pipe = AHSPipeline(reg, ad)
196
+ # router 否定词
197
+ r = route("不要计算", reg)
198
+ check(r.primary != "calculator", "否定词检测:不要计算→不选calculator")
199
+ # planner 拆解
200
+ p = plan("先算 5+5 然后再算 9*9", reg)
201
+ check(len(p.tasks) == 2, "planner 多步拆解")
202
+ # 端到端
203
+ res = pipe.run("帮我算一下 12*12")
204
+ check(res.success, "AHS 执行链端到端成功")
205
+
206
+
207
+ def main():
208
+ print("=" * 60)
209
+ print("内核新模块回归测试")
210
+ print("=" * 60)
211
+ tests = [test_scheduler, test_process_manager, test_system_bus,
212
+ test_truth_pipeline, test_cognitive_state, test_permission,
213
+ test_breaker_hooks, test_ahs_pipeline, test_trust_single_source]
214
+ for t in tests:
215
+ t()
216
+ print("\n" + "=" * 60)
217
+ print("✓ 内核新模块全部通过")
218
+ print("=" * 60)
219
+
220
+
221
+ if __name__ == "__main__":
222
+ main()
@@ -0,0 +1,110 @@
1
+ """
2
+ B 阶段测试 — office Agent 学习内核(学习行为操作系统)
3
+ 验证:采集 → 间隔重复(固化) → 知识图谱(连接) → 学习策略
4
+ """
5
+ import sys
6
+ from pathlib import Path
7
+ sys.path.insert(0, str(Path(__file__).parent.parent))
8
+
9
+ from foxclaw.learning import LearningRuntime
10
+
11
+ CSV = str(Path(__file__).parent.parent / "examples" / "sample_knowledge.csv")
12
+
13
+
14
+ def check(cond, msg):
15
+ if not cond:
16
+ raise AssertionError(f"✗ {msg}")
17
+ print(f" ✓ {msg}")
18
+
19
+
20
+ def main():
21
+ print("=" * 60)
22
+ print("B 阶段 · office Agent 学习内核(学习行为操作系统)")
23
+ print("=" * 60)
24
+
25
+ rt = LearningRuntime()
26
+ n = rt.import_csv(CSV)
27
+ print(f"\n采集 {n} 个知识点")
28
+ check(n == 8, f"采集 8 个知识点(实为 {n})")
29
+
30
+ # 1. 间隔重复:模拟几次复习
31
+ print("\n【固化:间隔重复】")
32
+ ids = list(rt.items.keys())
33
+ check(len(ids) >= 4, f"至少 4 个知识点可供复习(实为 {len(ids)})")
34
+ # 模拟:前两个记得好,第三个记不住
35
+ rt.review(ids[0], grade=5) # 完美
36
+ rt.review(ids[0], grade=4)
37
+ rt.review(ids[1], grade=4)
38
+ rt.review(ids[2], grade=1) # 没记住
39
+ rt.review(ids[2], grade=2) # 又没记住
40
+ for iid in ids[:4]:
41
+ st = rt.review_states[iid]
42
+ item = rt.items[iid]
43
+ print(f" 掌握度 {st.mastery:.2f} | 间隔 {st.interval}天 | "
44
+ f"复习{st.total_reviews}次 遗忘{st.lapses}次 | {item.content[:24]}...")
45
+ # SM-2 的语义后果:连续答不好应累积 lapses,且掌握度被压低。
46
+ # 这是"间隔重复真的在起作用"的核心断言 —— 只打印 0.60/0.11 两个数字不算验证。
47
+ st_good, st_bad = rt.review_states[ids[0]], rt.review_states[ids[2]]
48
+ check(st_bad.lapses == 2, f"连错两次的知识点累积 lapses=2(实为 {st_bad.lapses})")
49
+ check(st_bad.mastery < st_good.mastery,
50
+ f"记得差的掌握度低于记得好的({st_bad.mastery:.2f} < {st_good.mastery:.2f})")
51
+ check(st_good.total_reviews == 2 and st_bad.total_reviews == 2,
52
+ "复习次数被正确累计(各 2 次)")
53
+
54
+ # 2. 知识图谱:构建连接
55
+ print("\n【连接:知识图谱】")
56
+ conn = rt.build_connections(semantic_threshold=0.5)
57
+ print(f" 同主题边: {conn['topic_edges']} · 语义边: {conn['semantic_edges']}")
58
+ g = conn["graph"]
59
+ print(f" 图谱: {g['items']} 节点, {g['edges']} 边, "
60
+ f"{g['clusters']} 个知识簇")
61
+ print(f" 边类型: {g['edge_types']}")
62
+ check(conn["topic_edges"] > 0, f"同主题连接已建立({conn['topic_edges']} 条)")
63
+ check(g["items"] == n, f"图谱节点数 = 知识点数({g['items']} == {n})")
64
+ # 结构不变式:总边数必须等于各类边之和,否则说明有一类边被漏计/重复计
65
+ check(g["edges"] == conn["topic_edges"] + conn["semantic_edges"],
66
+ f"总边数 = 同主题边 + 语义边({g['edges']} == {conn['topic_edges']}+{conn['semantic_edges']})")
67
+ check(g["clusters"] > 0, f"聚类非空({g['clusters']} 个知识簇)")
68
+
69
+ # 3. 连接发现:某知识点的关联
70
+ print("\n【连接发现】")
71
+ related = rt.related(ids[0])
72
+ base = rt.items[ids[0]].content[:20]
73
+ print(f" 与「{base}...」相关的知识:")
74
+ for r in related[:4]:
75
+ print(f" [{r['edge_type']}/{r['weight']}] {r['item']['content'][:24]}... ({r['reason']})")
76
+ check(len(related) >= 1, f"「{base}…」找到 {len(related)} 条关联知识")
77
+ check(all(("edge_type" in x and "weight" in x and "item" in x) for x in related),
78
+ "每条关联都带 edge_type / weight / item 结构")
79
+
80
+ # 4. 综合分析
81
+ print("\n【综合分析】")
82
+ rep = rt.analyze()
83
+ print(f" 知识点 {rep['total_items']} · 平均掌握度 {rep['avg_mastery']}")
84
+ print(f" 主题掌握度: {rep['topic_mastery']}")
85
+ print(f" 最薄弱主题: {rep['weakest_topic']}")
86
+ print(f" 遗忘风险: {len(rep['forgetting_risk'])} 个高风险知识点")
87
+ check(rep["total_items"] == n, f"报告覆盖全部知识点({rep['total_items']} == {n})")
88
+ check(0 < rep["avg_mastery"] <= 1, f"平均掌握度落在 (0,1](实为 {rep['avg_mastery']})")
89
+ check(rep["weakest_topic"] in rep["topic_mastery"],
90
+ f"最薄弱主题来自主题掌握度表(weakest_topic={rep['weakest_topic']})")
91
+
92
+ # 5. 学习策略裁决
93
+ print("\n【学习策略裁决】")
94
+ for v in rep["strategy"]:
95
+ print(f" [{v['action']}] {v['message']}")
96
+ check(len(rep["strategy"]) >= 1, f"产出 {len(rep['strategy'])} 条学习策略")
97
+ check(all(("action" in v and "message" in v) for v in rep["strategy"]),
98
+ "每条策略都含 action / message")
99
+
100
+ print("\n" + "=" * 60)
101
+ print("✓ B 阶段验证完成:学习内核(间隔重复 + 知识图谱)成立")
102
+ print("=" * 60)
103
+
104
+
105
+ def test_learning():
106
+ main()
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()
@@ -0,0 +1,160 @@
1
+ """LLM 模型管理「自定义模型填不上」回归测试。
2
+
3
+ 覆盖三条曾经真实踩过的坑:
4
+ 1. BASE URL 没写 /v1 → SDK 拼出 /chat/completions → 网关 404/401(用户以为"填不上")
5
+ 2. 保存了自定义模型但**没切换** → 聊天仍走 .env 旧 provider → AuthenticationError + stub
6
+ 3. provider="custom" 不在 ``_OPENAI_FAMILY`` → stream() 退化为一次性输出、工具能力静默关闭
7
+
8
+ 全离线:不联网、不加载嵌入模型(provider=stub 起 router)。
9
+ """
10
+ import os, sys, json, tempfile
11
+ from pathlib import Path
12
+ sys.path.insert(0, str(Path(__file__).parent.parent))
13
+
14
+ # ── 环境隔离:必须在 import foxclaw 之前 ──────────────────────────────
15
+ for _k in ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "DEEPSEEK_API_KEY", "GOOGLE_API_KEY",
16
+ "DASHSCOPE_API_KEY", "MINIMAX_API_KEY", "LLM_API_KEY", "FOXCLAW_API_KEY"):
17
+ os.environ.pop(_k, None)
18
+ os.environ["HKC_EMBEDDING"] = "stub"
19
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_llmadmin_")
20
+ os.environ["FOXCLAW_SKIP_DOTENV"] = "1"
21
+ os.environ["FOXCLAW_PROVIDER"] = ""
22
+ os.environ["FOXCLAW_MODEL"] = ""
23
+ os.environ["FOXCLAW_MCP_SERVER"] = "0"
24
+ os.environ["HF_HUB_OFFLINE"] = "1"
25
+ os.environ["TRANSFORMERS_OFFLINE"] = "1"
26
+
27
+ import pytest
28
+ from fastapi import HTTPException
29
+
30
+ from foxclaw.core.model_router import ModelRouter, normalize_base_url
31
+ import foxclaw.api as _api
32
+ from foxclaw.api.routes import llm_admin
33
+
34
+
35
+ # ── 1. BASE URL 规范化 ───────────────────────────────────────────────
36
+
37
+ @pytest.mark.parametrize("raw,expected", [
38
+ ("https://api.deepseek.com", "https://api.deepseek.com/v1"),
39
+ ("https://api.deepseek.com/", "https://api.deepseek.com/v1"),
40
+ ("https://api.deepseek.com/v1", "https://api.deepseek.com/v1"),
41
+ ("https://api.deepseek.com/v1/", "https://api.deepseek.com/v1"),
42
+ (" https://api.openai.com/v1 ", "https://api.openai.com/v1"),
43
+ ("api.deepseek.com", "https://api.deepseek.com/v1"), # 漏 scheme
44
+ ("api.deepseek.com/v1", "https://api.deepseek.com/v1"),
45
+ # 已有非 /v1 的 path 必须原样保留(通义兼容模式 / 自建网关 /openai/v1)
46
+ ("https://dashscope.aliyuncs.com/compatible-mode/v1",
47
+ "https://dashscope.aliyuncs.com/compatible-mode/v1"),
48
+ ("http://localhost:11434/v1", "http://localhost:11434/v1"),
49
+ ("http://127.0.0.1:8080/openai/v1", "http://127.0.0.1:8080/openai/v1"),
50
+ ])
51
+ def test_normalize_base_url(raw, expected):
52
+ assert normalize_base_url(raw) == expected
53
+ # 幂等:规范化两次结果不变(前端 blur + 后端保存 + router 构造都会过一遍)
54
+ assert normalize_base_url(expected) == expected
55
+
56
+
57
+ @pytest.mark.parametrize("bad", ["", " ", "ftp://example.com", "https://"])
58
+ def test_normalize_base_url_rejects(bad):
59
+ with pytest.raises(ValueError):
60
+ normalize_base_url(bad)
61
+
62
+
63
+ # ── 2. custom 必须被当作 openai-compatible 家族成员 ──────────────────
64
+
65
+ def test_custom_is_openai_family():
66
+ assert "custom" in ModelRouter._OPENAI_FAMILY, \
67
+ "custom 漏了会让流式退化为一次性输出、工具能力静默关闭"
68
+ assert "custom" in ModelRouter._TOOL_PROVIDERS
69
+
70
+
71
+ def test_custom_provider_supports_tools():
72
+ r = ModelRouter(provider="stub")
73
+ r.provider = "custom"
74
+ r._client = object() # 假装已构造好(不联网)
75
+ assert r.supports_tools() is True
76
+
77
+
78
+ # ── 3. stream() 必须 reload 选择(聊天主路径)─────────────────────────
79
+
80
+ def test_stream_reloads_selection():
81
+ r = ModelRouter(provider="stub")
82
+ hits = []
83
+ r._maybe_reload_selection = lambda: hits.append(True) # 不打真实文件
84
+ list(r.stream("sys", [{"role": "user", "content": "hi"}]))
85
+ assert hits, "stream() 未调用 _maybe_reload_selection → 切模型后流式对话不生效"
86
+
87
+
88
+ # ── 4. 保存 / 切换端点 ───────────────────────────────────────────────
89
+
90
+ @pytest.fixture()
91
+ def fresh_store(monkeypatch):
92
+ """每个用例一份干净的 custom_models.json + .current_model。"""
93
+ d = Path(tempfile.mkdtemp(prefix="llm_admin_store_"))
94
+ # router 侧按 env 现算路径(_custom_models_path / _selection_path),必须一起指过来,
95
+ # 否则端点写 A 目录、router 读 B 目录 → 预检假失败。
96
+ monkeypatch.setenv("FOXCLAW_DATA_DIR", str(d))
97
+ monkeypatch.setattr(llm_admin, "CUSTOM_MODELS_PATH", d / "custom_models.json")
98
+ monkeypatch.setattr(llm_admin, "CURRENT_MODEL_PATH", d / ".current_model")
99
+ yield d
100
+
101
+
102
+ def _add(name="deepseek", base="https://api.deepseek.com", key="sk-test", model="deepseek-chat"):
103
+ import asyncio
104
+ return asyncio.run(llm_admin.add_custom_model(
105
+ llm_admin._CustomAddReq(name=name, base_url=base, api_key=key, model=model)))
106
+
107
+
108
+ def test_add_normalizes_base_url(fresh_store):
109
+ _add(base="https://api.deepseek.com")
110
+ saved = json.loads((fresh_store / "custom_models.json").read_text(encoding="utf-8"))
111
+ assert saved[0]["base_url"] == "https://api.deepseek.com/v1"
112
+
113
+ # 更新时同样规范化
114
+ import asyncio
115
+ asyncio.run(llm_admin.update_custom_model("deepseek", llm_admin._CustomUpdateReq(
116
+ base_url="https://api.deepseek.com")))
117
+ saved = json.loads((fresh_store / "custom_models.json").read_text(encoding="utf-8"))
118
+ assert saved[0]["base_url"] == "https://api.deepseek.com/v1"
119
+
120
+
121
+ def test_add_rejects_bad_base_url(fresh_store):
122
+ with pytest.raises(HTTPException) as ei:
123
+ _add(base="ftp://example.com")
124
+ assert ei.value.status_code == 400
125
+
126
+
127
+ def test_select_custom_takes_effect_immediately(fresh_store, monkeypatch):
128
+ """核心回归:保存后 point 到它必须真的把 router 切过去。"""
129
+ _add()
130
+ router = ModelRouter(provider="stub")
131
+
132
+ class _FakeOS:
133
+ pass
134
+ fake = _FakeOS()
135
+ fake.router = router
136
+ monkeypatch.setattr(_api, "_os_instance", fake, raising=False)
137
+
138
+ import asyncio
139
+ out = asyncio.run(llm_admin.select_model(
140
+ llm_admin._SelectReq(source="custom", name="deepseek")))
141
+
142
+ assert out["current"]["name"] == "deepseek"
143
+ assert out["runtime"]["provider"] == "custom"
144
+ assert out["runtime"]["model"] == "deepseek-chat"
145
+ assert router.provider == "custom" and router.model == "deepseek-chat"
146
+ # .current_model 落盘,重启后 lazy reload 仍能读到
147
+ cur = json.loads((fresh_store / ".current_model").read_text(encoding="utf-8"))
148
+ assert cur["source"] == "custom" and cur["name"] == "deepseek"
149
+
150
+
151
+ def test_select_missing_custom_404(fresh_store):
152
+ import asyncio
153
+ with pytest.raises(HTTPException) as ei:
154
+ asyncio.run(llm_admin.select_model(
155
+ llm_admin._SelectReq(source="custom", name="nope")))
156
+ assert ei.value.status_code == 404
157
+
158
+
159
+ if __name__ == "__main__":
160
+ sys.exit(pytest.main([__file__, "-q"]))