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,182 @@
1
+ """
2
+ 统一事件脊柱(EventSpine, P0 第二项)测试 —— 全离线。
3
+
4
+ 覆盖:
5
+ 1. EventSpine:发布/订阅(前缀匹配) + 投影(收到每条) + 有界历史
6
+ 2. SystemBus 门面:退化为脊柱门面后,既有消费者零改;系统事件汇入脊柱
7
+ 3. Harness → 脊柱转发:harness 事件汇入脊柱;高频内部信号(goal_registered)不上脊柱
8
+ 4. Foxclaw 统一投影:harness 事件与系统事件都被**同一个投影**观测(核心统一验证)
9
+
10
+ 为避免加载 7s 重模型,harness embedding 用不存在的模型名 → 降级 hash(不影响事件验证)。
11
+ """
12
+ import os, sys, asyncio, tempfile, shutil
13
+ from pathlib import Path
14
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
15
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
16
+ os.environ["FOXCLAW_EMBEDDING_MODEL"] = "foxclaw-nonexistent-fast" # harness 走 hash,快
17
+ sys.path.insert(0, str(Path(__file__).parent.parent))
18
+
19
+ from foxclaw.kernel import EventSpine
20
+
21
+
22
+ def test_spine_pubsub_prefix_projection():
23
+ print("\n【1】EventSpine:前缀订阅 + 投影 + 历史")
24
+ spine = EventSpine()
25
+ got = []
26
+ spine.subscribe("harness", lambda e: got.append(e.topic)) # 前缀订阅
27
+ allseen = []
28
+ spine.project(lambda e: allseen.append(e.topic)) # 投影:收到每条
29
+ spine.publish("harness.drift.blocked", {"x": 1})
30
+ spine.publish("memory.ingested", {"y": 2})
31
+ assert got == ["harness.drift.blocked"], "前缀 'harness' 应只命中 harness.*"
32
+ assert allseen == ["harness.drift.blocked", "memory.ingested"], "投影应收到每一条"
33
+ assert len(spine.history()) == 2 and len(spine.history("harness")) == 1
34
+ print(" ✓ 前缀匹配正确;投影全量;历史可按前缀过滤")
35
+
36
+
37
+ def test_systembus_facade():
38
+ print("\n【2】SystemBus 门面:消费者零改 + 汇入脊柱")
39
+ from foxclaw.core.system_bus import SystemBus
40
+ spine = EventSpine()
41
+ seen = []
42
+ spine.project(lambda e: seen.append((e.topic, e.source)))
43
+ bus = SystemBus(spine=spine)
44
+ got = []
45
+ bus.subscribe("execution.completed", lambda m: got.append(m.payload)) # 旧式 handler(msg)
46
+ bus.publish("execution.completed", {"task": "t"})
47
+ assert got == [{"task": "t"}], "旧消费者 handler(msg) 仍收到 .payload"
48
+ assert ("execution.completed", "system_bus") in seen, "系统事件应汇入同一脊柱"
49
+ assert bus.history("execution.completed"), "history 门面可用"
50
+ print(" ✓ handler(msg) 兼容;系统事件经脊柱;history 正常")
51
+
52
+
53
+ def test_harness_forwards_to_spine():
54
+ print("\n【3】Harness → 脊柱转发(goal_registered 不上脊柱)")
55
+ from foxclaw.harness.harness import Harness
56
+ from foxclaw.core.thought_chain import ThoughtChain
57
+ d = tempfile.mkdtemp(prefix="spine_h_")
58
+ try:
59
+ spine = EventSpine()
60
+ seen = []
61
+ spine.project(lambda e: seen.append(e.topic))
62
+ h = Harness("tester", ThoughtChain(str(Path(d) / "tc.db")),
63
+ policy_path=str(Path(d) / "p.yaml"), spine=spine)
64
+ local = []
65
+ h.bus.on("harness_warn", lambda data: local.append(data)) # 本地订阅仍工作
66
+ h.bus.emit("harness_warn", {"a": 1}) # harness_warn 无内部自订阅,输出干净
67
+ h.bus.emit("goal_registered", {"g": 1})
68
+ assert local == [{"a": 1}], "本地订阅(向后兼容)仍收到"
69
+ assert "harness_warn" in seen, "harness 事件应汇入脊柱"
70
+ assert "goal_registered" not in seen, "高频内部信号不应上脊柱(降噪)"
71
+ finally:
72
+ shutil.rmtree(d, ignore_errors=True)
73
+ print(" ✓ 本地订阅兼容 + harness 汇入脊柱 + goal_registered 被过滤")
74
+
75
+
76
+ def test_hkc_events_bridge_to_spine():
77
+ print("\n【3b】HKC EventBus → 脊柱桥接(知识事件不再是孤岛;memory.* 刻意不桥)")
78
+ from foxclaw.knowledge import bridge_hkc_events
79
+
80
+ class _FakeHKCBus:
81
+ def __init__(self): self._h = {}
82
+ def subscribe(self, ev, h): self._h.setdefault(ev, []).append(h)
83
+ def publish(self, d):
84
+ for h in self._h.get(d.get("event"), []): h(d)
85
+
86
+ spine = EventSpine()
87
+ seen = []
88
+ spine.project(lambda e: seen.append((e.topic, e.source, e.payload)))
89
+ bus = _FakeHKCBus()
90
+ assert bridge_hkc_events(bus, spine) is True
91
+ bus.publish({"event": "knowledge.merged", "payload": {"ku_id": "K1"}, "event_id": "EVT_1"})
92
+ bus.publish({"event": "conflict.detected", "payload": {"card": "C1"}})
93
+ bus.publish({"event": "ability.created", "payload": {"key": "A1"}})
94
+ assert ("knowledge.merged", "hkc", {"ku_id": "K1"}) in seen, seen
95
+ assert any(t == "conflict.detected" and s == "hkc" for t, s, _ in seen)
96
+ assert any(t == "ability.created" for t, _, _ in seen)
97
+ # memory.* 刻意不桥(HMR 域,转发会重复/混淆)
98
+ bus.publish({"event": "memory.created", "payload": {}})
99
+ assert not any(t == "memory.created" for t, _, _ in seen), "memory.* 不应被桥接"
100
+ # 降级:bus/spine 为 None → 不接线、不崩
101
+ assert bridge_hkc_events(None, spine) is False
102
+ print(" ✓ knowledge.merged/conflict/ability 桥到脊柱(source=hkc);memory.* 不桥;None 优雅降级")
103
+
104
+
105
+ def test_unified_projection_via_foxclawos():
106
+ print("\n【4】Foxclaw:harness 事件与系统事件被同一投影统一观测")
107
+ d = tempfile.mkdtemp(prefix="spine_os_")
108
+ try:
109
+ from foxclaw import Foxclaw
110
+ os_ = Foxclaw(data_dir=d, shadow_rounds=50)
111
+ seen = []
112
+ os_.event_spine.project(lambda e: seen.append(e.topic))
113
+ # 两类来源:harness 总线 与 系统总线,都汇入 os_.event_spine
114
+ os_.finance.harness.bus.emit("harness_warn", {"agent": "finance"})
115
+ os_.system_bus.publish("execution.completed", {"task": "demo"})
116
+ assert "harness_warn" in seen, "harness 事件应到统一投影"
117
+ assert "execution.completed" in seen, "系统事件应到统一投影(修复:此前到不了)"
118
+ # 且二者用的是同一条脊柱实例
119
+ assert os_.system_bus.spine is os_.event_spine
120
+ asyncio.run(os_.aclose())
121
+ finally:
122
+ shutil.rmtree(d, ignore_errors=True)
123
+ print(" ✓ harness + 系统事件汇入同一脊柱、被同一投影观测(4→1 统一成立)")
124
+
125
+
126
+ def test_ws_projection_safe_and_bounded():
127
+ print("\n【5】WS 投影净化:脏/大 payload 不丢连接、不泛洪(深审修复)")
128
+ import json as _json
129
+ import foxclaw.api as api
130
+ # a) 净化器:非 JSON 对象 → 可序列化;超大 → 截断;正常 → 透传
131
+ class Weird: # 非 JSON 可序列化
132
+ def __repr__(self): return "<Weird>"
133
+ safe = api._ws_safe_payload({"obj": Weird(), "n": 1})
134
+ _json.dumps(safe) # 不应抛
135
+ big = api._ws_safe_payload({"blob": "x" * 10000})
136
+ assert big.get("_truncated") is True, "超大 payload 应被截断"
137
+ assert api._ws_safe_payload({"a": 1, "b": "ok"}) == {"a": 1, "b": "ok"}, "正常 payload 透传"
138
+ print(" ✓ 非 JSON 对象被 str 化;超大被截断;正常透传")
139
+
140
+ # b) 端到端:投影把脏事件放进队列后,队列项必须 JSON 可序列化(否则 send_json 会丢连接)
141
+ d = tempfile.mkdtemp(prefix="spine_ws_")
142
+ try:
143
+ from foxclaw import Foxclaw
144
+ os_ = Foxclaw(data_dir=d, shadow_rounds=50)
145
+ # 清空可能的既有队列
146
+ while not api._event_queue.empty():
147
+ api._event_queue.get_nowait()
148
+ api._register_spine_projection(os_)
149
+ os_.system_bus.publish("execution.completed",
150
+ {"agent_id": "x", "task": "t", "result": Weird()}) # 脏 result
151
+ # 真值链会连带产生 trace/truth/memory 事件(再入 publish),全部经投影入队。
152
+ items = []
153
+ while not api._event_queue.empty():
154
+ items.append(api._event_queue.get_nowait())
155
+ assert items, "投影应把事件放进队列"
156
+ for it in items:
157
+ _json.dumps(it) # 关键:每一项都必须能 send_json 而不抛 → 不会丢连接
158
+ topics = {it["event"] for it in items}
159
+ assert "execution.completed" in topics, f"应含 execution.completed,实得 {topics}"
160
+ asyncio.run(os_.aclose())
161
+ finally:
162
+ shutil.rmtree(d, ignore_errors=True)
163
+ print(" ✓ 脏 result 事件经投影后队列项 JSON 安全(WS 不会静默丢连接)")
164
+
165
+
166
+ def main():
167
+ print("=" * 60)
168
+ print("统一事件脊柱 · EventSpine(P0 第二项)")
169
+ print("=" * 60)
170
+ test_spine_pubsub_prefix_projection()
171
+ test_systembus_facade()
172
+ test_harness_forwards_to_spine()
173
+ test_hkc_events_bridge_to_spine()
174
+ test_unified_projection_via_foxclawos()
175
+ test_ws_projection_safe_and_bounded()
176
+ print("\n" + "=" * 60)
177
+ print("✓ P0-2 验证完成:脊柱 + SystemBus门面 + Harness转发 + 统一投影")
178
+ print("=" * 60)
179
+
180
+
181
+ if __name__ == "__main__":
182
+ main()
@@ -0,0 +1,101 @@
1
+ """证据融合语义化 —— 原始复盘点名的 `search/evidence.py "规则占位"`(此前一直漏修)。
2
+
3
+ 修掉两个真问题:
4
+ ① 否定词**子串**误命中:原 ("不是","并非","no longer","not") 按子串匹配 →
5
+ "another"/"notebook" 里的 "not" 被命中 → 无关证据被判**矛盾**;而 confidence()
6
+ 按矛盾数扣分(0.1/条)→ 搜索置信被**幻影矛盾**无端拉低。
7
+ 这与 hkc_kee/kee.py 早已修过的 not⊂nothing / no⊂know 是**同一 bug 家族**,证据层漏了同步。
8
+ ② 相似度是**字符级**(SequenceMatcher)而非语义 → 语义等价的证据识别不出互相支持。
9
+
10
+ 无 ST 模型时优雅降级回字符级(不崩),故本测试对语义部分做"可用才断言"。
11
+ """
12
+ import os, sys
13
+ from pathlib import Path
14
+ sys.path.insert(0, str(Path(__file__).parent.parent))
15
+ sys.path.insert(0, str(Path(__file__).parent.parent / "vendor" / "hkc"))
16
+
17
+
18
+ def test_evidence_semantic():
19
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
20
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
21
+ os.environ["FOXCLAW_EVIDENCE_MODEL"] = "all-MiniLM-L6-v2" # 进程缓存里通常有,保证离线可测
22
+
23
+ from foxclaw.search.evidence import EvidenceFusion, _has_neg, _semantic_sim
24
+ from foxclaw.search.models import RawEvidence
25
+
26
+ print("=" * 60); print("证据融合语义化验证"); print("=" * 60)
27
+ f = EvidenceFusion()
28
+
29
+ # [1] 否定词词边界:不依赖模型,总能测(这是幻影矛盾的根因)
30
+ assert _has_neg("this is another way") is False, "another 里的 not 不该命中"
31
+ assert _has_neg("the notebook is useful") is False, "notebook 里的 not 不该命中"
32
+ assert _has_neg("I know this") is False, "know 里的 no 不该命中"
33
+ assert _has_neg("cannot proceed") is True
34
+ assert _has_neg("this is not good") is True
35
+ assert _has_neg("这个方案并非最优") is True
36
+ print("[1] 否定词词边界(another/notebook/know 不误命中)OK")
37
+
38
+ # [2] 幻影矛盾消失:无关句子不再被判矛盾
39
+ assert f._contradicts("this is another way", "this is a way") is False, "幻影矛盾"
40
+ assert f._contradicts("the notebook is useful", "the book is useful") is False, "幻影矛盾"
41
+ print("[2] 幻影矛盾消失(another/notebook 不再被判矛盾)OK")
42
+
43
+ # [3] 真矛盾仍能识别(不能因为修 bug 把功能砍了)
44
+ assert f._contradicts("cannot proceed", "can proceed") is True
45
+ assert f._contradicts("这个方案并非最优", "这个方案是最优") is True
46
+ print("[3] 真矛盾仍识别(cannot/并非)OK")
47
+
48
+ # [4] 幻影矛盾会拉低置信 —— 端到端证明它真的有害
49
+ g = f.build([
50
+ RawEvidence(id="e1", fact="this is another way", source="s1", origin="internal", confidence=0.9),
51
+ RawEvidence(id="e2", fact="this is a way", source="s2", origin="internal", confidence=0.9),
52
+ ])
53
+ assert len(g.contradictions()) == 0, f"不该有矛盾边: {g.contradictions()}"
54
+ assert f.confidence(g) >= 0.85, f"置信不该被幻影矛盾扣分: {f.confidence(g)}"
55
+ print(f"[4] 端到端:无幻影矛盾边,置信未被误扣({f.confidence(g):.2f})OK")
56
+
57
+ # ── 语义部分:模型可用才断言,否则优雅跳过(降级路径本身已由 [1]-[4] 覆盖)──
58
+ if _semantic_sim("a", "a") is None:
59
+ print("[skip] ST 模型未缓存 → 语义部分跳过(已回退字符级,不崩)")
60
+ print("\n证据融合语义化验证通过(降级路径)")
61
+ # 两种运行方式都要正确退出(M7):
62
+ # · 被 pytest 收集 → pytest.skip 是标准做法;让测试显式"跳过"而非崩溃。
63
+ # · 被 run_all.py 当脚本直跑 → pytest.skip 抛 Skipped 会让脚本非零退出,
64
+ # 反而把「跳过」变成「失败」。故按 __name__ 区分。
65
+ if __name__ != "__main__":
66
+ import pytest
67
+ pytest.skip("ST 模型未缓存 → 语义部分跳过(降级路径已由 [1]-[4] 覆盖)",
68
+ allow_module_level=True)
69
+ import sys as _sys
70
+ _sys.exit(0)
71
+
72
+ # [5] 语义相似:近义高、无关低(字符级做不到这点)
73
+ close = f._similarity("the plan is good", "the plan is nice")
74
+ far = f._similarity("the plan is good", "the stock market crashed")
75
+ assert close > 0.6 > far, f"近义应>0.6>无关: {close:.3f} vs {far:.3f}"
76
+ assert f._similarity("hello world", "hello world") >= 0.99
77
+ print(f"[5] 语义相似(近义 {close:.2f} > 0.6 > 无关 {far:.2f})OK")
78
+
79
+ # [6] 语义等价的证据 → 建立 supports 边(原字符级识别不出)
80
+ g2 = f.build([
81
+ RawEvidence(id="a", fact="it will rain today", source="s1", origin="internal", confidence=0.8),
82
+ RawEvidence(id="b", fact="rain is expected today", source="s2", origin="external", confidence=0.8),
83
+ ])
84
+ sup = [e for e in g2.edges if e.relation == "supports"]
85
+ assert sup, "语义等价的证据应互相支持(字符级识别不出)"
86
+ print("[6] 语义等价证据建立 supports 边 OK")
87
+
88
+ # [7] 阈值仍成立(换成余弦后 support=0.6 / dup=0.92 依然分得开——实测:近义 0.80~0.91、
89
+ # 无关 0.02、相同 1.00),且完全相同的证据仍被去重
90
+ g3 = f.build([
91
+ RawEvidence(id="x", fact="hello world", source="s1", origin="internal", confidence=0.8),
92
+ RawEvidence(id="y", fact="hello world", source="s2", origin="internal", confidence=0.8),
93
+ ])
94
+ assert len(g3.nodes) == 1, f"完全相同应去重: {list(g3.nodes)}"
95
+ print("[7] 阈值在余弦下仍成立 + 相同证据去重 OK")
96
+
97
+ print("\n证据融合语义化验证全部通过")
98
+
99
+
100
+ if __name__ == "__main__":
101
+ test_evidence_semantic()
@@ -0,0 +1,97 @@
1
+ """/api/chat/stream 的命名 SSE 事件:`event: evidence`(执行凭据)与正文 delta 分离。
2
+
3
+ 锁住的契约:
4
+ · 工具轮每步的核验结论 + 凭据以**命名事件**整块送出,不拼进正文(否则复制/落库被污染);
5
+ · 没有工具步的纯对话不产生 evidence 事件(不能凭空生成"凭据",前端也不闪空面板);
6
+ · 前端拿到的是一个数组:`[{tool,status,reason,evidence:[{kind,ref,label,...}]}]`。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import sys
14
+ import tempfile
15
+ from pathlib import Path
16
+
17
+ sys.path.insert(0, str(Path(__file__).parent.parent))
18
+
19
+ EVIDENCE = [
20
+ {"tool": "web_search", "kind": "ok", "status": "verified", "reason": "",
21
+ "evidence": [{"kind": "url", "ref": "https://example.com/a", "label": "example.com",
22
+ "source": "web_search", "at": 0}]},
23
+ {"tool": "send_mail", "kind": "ok", "status": "unverified",
24
+ "reason": "该工具未声明可观察的后置条件", "evidence": []},
25
+ ]
26
+
27
+
28
+ def _parse(body: str) -> tuple[list, str]:
29
+ """拆 SSE:返回 (evidence 事件载荷列表, 正文 delta 拼接)。"""
30
+ ev_payloads, delta_text = [], ""
31
+ for b in [x for x in body.split("\n\n") if x.strip()]:
32
+ name, data = "message", None
33
+ for line in b.splitlines():
34
+ if line.startswith("event: "):
35
+ name = line[7:].strip()
36
+ elif line.startswith("data: "):
37
+ data = line[6:].strip()
38
+ if data is None:
39
+ continue
40
+ ev = json.loads(data)
41
+ if name == "evidence":
42
+ ev_payloads.append(ev)
43
+ elif "delta" in ev:
44
+ delta_text += ev["delta"]
45
+ return ev_payloads, delta_text
46
+
47
+
48
+ def test_chat_stream_evidence_event_contract():
49
+ # ISOLATION_GUARD:依赖 import 期固化的单例(路由配置/限流器),须独立进程跑。
50
+ if "foxclaw.api" in sys.modules:
51
+ import pytest
52
+ pytest.skip("需独立进程运行(模块级单例已固化),请用 tests/run_all.py")
53
+ os.environ.setdefault("FOXCLAW_DATA_DIR", tempfile.mkdtemp(prefix="foxclaw_ev_"))
54
+
55
+ from fastapi.testclient import TestClient
56
+ from foxclaw.agents.base import CognitiveAgent
57
+ from foxclaw.api import app
58
+
59
+ seen: dict = {}
60
+
61
+ def _fake_stream(self, task, stop_event=None, images=None, history=None,
62
+ attachments=None, on_thinking=None, on_evidence=None):
63
+ seen["has_cb"] = on_evidence is not None
64
+ if task == "plain":
65
+ yield "纯对话,没有动用工具。" # 无工具步:不上报任何证据
66
+ return
67
+ yield "已完成。" # 正文先出
68
+ if on_evidence is not None: # 证据后出(与真实实现一致:答案之后上报)
69
+ on_evidence(EVIDENCE)
70
+
71
+ original = CognitiveAgent.stream_reply
72
+ CognitiveAgent.stream_reply = _fake_stream
73
+ try:
74
+ with TestClient(app) as c:
75
+ with_tools = c.post("/api/chat/stream", json={"task": "use-tools", "agent_id": "supervisor"})
76
+ plain = c.post("/api/chat/stream", json={"task": "plain", "agent_id": "supervisor"})
77
+ assert with_tools.status_code == 200 and plain.status_code == 200
78
+ body, plain_body = with_tools.text, plain.text
79
+ finally:
80
+ CognitiveAgent.stream_reply = original
81
+
82
+ assert seen.get("has_cb") is True, "端点必须把 on_evidence 传给 Agent"
83
+
84
+ ev_payloads, delta_text = _parse(body)
85
+ assert "event: evidence" in body, "证据必须以命名事件送出"
86
+ assert len(ev_payloads) == 1, "一轮只上报一次"
87
+ items = ev_payloads[0]["evidence"]
88
+ assert [i["tool"] for i in items] == ["web_search", "send_mail"]
89
+ assert [i["status"] for i in items] == ["verified", "unverified"]
90
+ assert items[0]["evidence"][0]["ref"] == "https://example.com/a"
91
+ # 凭据不得混进正文——正文是答案,不是证据仓库
92
+ assert "example.com" not in delta_text
93
+ assert delta_text.strip() == "已完成。"
94
+
95
+ # 纯对话:没有 evidence 事件,正文照常
96
+ assert "event: evidence" not in plain_body
97
+ assert "纯对话" in plain_body
@@ -0,0 +1,146 @@
1
+ """
2
+ 文件摄入打通交互层:docx/xlsx 加载器 + KDE 后缀分派 + 上传路由格式门。
3
+
4
+ 离线可跑:加载器是确定性纯解析(无 LLM);KDE 分派用假抽取器验证
5
+ 「新后缀 → loader → chunker → packager → KU」链路,不调真实模型。
6
+ 真实 LLM 抽取质量不在离线回归内(与 test_hkc 一致哲学)。
7
+ """
8
+ import os, sys, tempfile
9
+ from pathlib import Path
10
+ sys.path.insert(0, str(Path(__file__).parent.parent))
11
+ sys.path.insert(0, str(Path(__file__).parent.parent / "vendor" / "hkc"))
12
+
13
+
14
+ def test_file_ingest():
15
+ # 离线 + stub,确定性
16
+ for k in ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "DEEPSEEK_API_KEY"):
17
+ os.environ.pop(k, None)
18
+ os.environ["HKC_EMBEDDING"] = "stub"
19
+ os.environ["HF_HUB_OFFLINE"] = "1"
20
+ os.environ["TRANSFORMERS_OFFLINE"] = "1"
21
+
22
+ from docx import Document
23
+ from openpyxl import Workbook
24
+ from hkc_kde.loaders.office_loader import DocxLoader, XlsxLoader
25
+ from hkc_kde.chunker import Chunker
26
+ from hkc_kde.models import ExtractionResult, RawExtraction
27
+
28
+ print("=" * 60); print("文件摄入(docx/xlsx)打通验证"); print("=" * 60)
29
+
30
+ TMP = Path(tempfile.mkdtemp(prefix="foxclaw_fileingest_"))
31
+
32
+ def _make_docx() -> Path:
33
+ p = TMP / "sample.docx"
34
+ doc = Document()
35
+ doc.add_heading("第一章 引言", level=1)
36
+ doc.add_paragraph("Foxclaw 是一个认知代理操作系统,由五个 Agent 组成。")
37
+ # 表格夹在第一章内、第二章之前——用于验证按 body 顺序归章(而非全塞末章)
38
+ t = doc.add_table(rows=2, cols=2)
39
+ t.rows[0].cells[0].text = "字段"; t.rows[0].cells[1].text = "取值"
40
+ t.rows[1].cells[0].text = "版本"; t.rows[1].cells[1].text = "v1.6"
41
+ doc.add_heading("第二章 架构", level=1)
42
+ doc.add_paragraph("知识核心负责长期记忆的沉淀与演化。")
43
+ doc.save(str(p))
44
+ return p
45
+
46
+ def _make_xlsx() -> Path:
47
+ p = TMP / "sample.xlsx"
48
+ wb = Workbook()
49
+ ws = wb.active; ws.title = "财务"
50
+ ws.append(["科目", "金额"]); ws.append(["收入", 100]); ws.append(["支出", 40])
51
+ ws2 = wb.create_sheet("备注")
52
+ ws2.append(["说明", "季度汇总"])
53
+ wb.save(str(p)); wb.close()
54
+ return p
55
+
56
+ # [1] DocxLoader:标题切章 + 表格行入文
57
+ pd = DocxLoader().load(str(_make_docx()))
58
+ assert pd.doc_type == "DOCX", pd.doc_type
59
+ titles = [c.title for c in pd.chapters]
60
+ assert "第一章 引言" in titles and "第二章 架构" in titles, titles
61
+ alltext = "\n".join(s.content for c in pd.chapters for s in c.sections)
62
+ assert "五个 Agent" in alltext, "正文段落丢失"
63
+ assert "版本 | v1.6" in alltext, "表格行未转文本"
64
+ # 表格按 body 顺序归入它出现的章节(第一章),不该被塞到末章
65
+ ch1 = [c for c in pd.chapters if c.title == "第一章 引言"][0]
66
+ ch2 = [c for c in pd.chapters if c.title == "第二章 架构"][0]
67
+ assert any("版本 | v1.6" in s.content for s in ch1.sections), "表格未归到出现位置的第一章"
68
+ assert not any("版本" in s.content for s in ch2.sections), "表格错误归到末章第二章"
69
+ print(f"[1] DocxLoader({len(pd.chapters)} 章,标题切分 + 表格按序归章)OK")
70
+
71
+ # [2] XlsxLoader:每表一章 + 行合并
72
+ px = XlsxLoader().load(str(_make_xlsx()))
73
+ assert px.doc_type == "XLSX", px.doc_type
74
+ sheet_titles = [c.title for c in px.chapters]
75
+ assert "财务" in sheet_titles and "备注" in sheet_titles, sheet_titles
76
+ xtext = "\n".join(s.content for c in px.chapters for s in c.sections)
77
+ assert "收入 | 100" in xtext and "季度汇总" in xtext, xtext
78
+ print(f"[2] XlsxLoader({len(px.chapters)} 表→章,行合并)OK")
79
+
80
+ # [3] 加载器输出可被 Chunker 消费(loader→chunker 接线)
81
+ chunks = Chunker().chunk(pd)
82
+ assert chunks and any("Agent" in ch.content for ch in chunks), "docx 未切出有效 chunk"
83
+ print(f"[3] Chunker 消费 docx({len(chunks)} chunk)OK")
84
+
85
+ # [4] KDE 后缀分派端到端:假抽取器把 chunk 变 KU,证明 .docx/.xlsx 走通全链路
86
+ def _build_kde():
87
+ from hkc_core.utils.id_gen import IDGenerator
88
+ from hkc_core.graph.sqlite_store import SQLiteGraphStore
89
+ from hkc_kep.event_bus import EventBus
90
+ from hkc_kde.kde import KnowledgeDigestEngine
91
+ d = Path(tempfile.mkdtemp(prefix="foxclaw_kde_"))
92
+ store = SQLiteGraphStore(str(d / "hkc.db"))
93
+ bus = EventBus(str(d / "events.db"))
94
+ idg = IDGenerator(str(d / "id.db"))
95
+ return KnowledgeDigestEngine(store, bus, idg, provider="anthropic")
96
+
97
+ class _FakeExtractor:
98
+ """每个 chunk 抽出一个实体,捕获收到的 chunk 供断言。"""
99
+ def __init__(self): self.seen = []
100
+ def extract(self, chunks, progress_cb=None):
101
+ self.seen = list(chunks)
102
+ doc_id = chunks[0].doc_id if chunks else ""
103
+ raw = RawExtraction(entities=[{"name": "Foxclaw", "type": "Person"}])
104
+ return ExtractionResult(doc_id=doc_id, source=doc_id, items=[raw])
105
+
106
+ for suffix, maker in ((".docx", _make_docx), (".xlsx", _make_xlsx)):
107
+ kde = _build_kde()
108
+ fake = _FakeExtractor(); kde.extractor = fake
109
+ kus = kde.ingest_file(str(maker()), source=f"user{suffix}")
110
+ assert fake.seen, f"{suffix}: 抽取器未收到 chunk(分派断裂)"
111
+ assert kus, f"{suffix}: 未写入任何 KU"
112
+ print("[4] KDE 分派 .docx/.xlsx → loader → chunker → KU 全链路 OK")
113
+
114
+ # [5] 不支持的格式明确报错(不静默产空)
115
+ kde = _build_kde()
116
+ bad = TMP / "x.pptx"; bad.write_bytes(b"PK\x03\x04not really")
117
+ try:
118
+ kde.ingest_file(str(bad))
119
+ raise AssertionError("应对 .pptx 抛不支持错误")
120
+ except ValueError as e:
121
+ assert ".pptx" in str(e) or "不支持" in str(e), str(e)
122
+ print("[5] 不支持格式(.pptx)明确 ValueError OK")
123
+
124
+ # [6] 上传路由格式门:TestClient 走 Foxclaw app 挂载的 /knowledge/ingest/file
125
+ from fastapi.testclient import TestClient
126
+ from foxclaw.api import app
127
+ with TestClient(app) as c:
128
+ # 不支持后缀 → 415(后缀检查在读文件前,无需 LLM)
129
+ r = c.post("/knowledge/ingest/file",
130
+ files={"file": ("bad.pptx", b"x", "application/octet-stream")})
131
+ assert r.status_code == 415, (r.status_code, r.text)
132
+ assert ".docx" in r.text and ".xlsx" in r.text, "415 提示未含新格式"
133
+ # 支持的 docx 上传 → 200(stub 抽取 0 KU,但格式门放行、链路不崩)
134
+ with open(_make_docx(), "rb") as fh:
135
+ r2 = c.post("/knowledge/ingest/file",
136
+ files={"file": ("sample.docx", fh.read(),
137
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document")},
138
+ data={"domain": "test"})
139
+ assert r2.status_code == 200, (r2.status_code, r2.text)
140
+ print("[6] 上传路由格式门(.pptx→415;.docx→200)OK")
141
+
142
+ print("\n文件摄入(docx/xlsx)验证全部通过")
143
+
144
+
145
+ if __name__ == "__main__":
146
+ test_file_ingest()