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,541 @@
1
+ """
2
+ hkc-kee / kee.py
3
+ Knowledge Evolution Engine
4
+
5
+ 职责:
6
+ 1. 监听 knowledge.created 事件
7
+ 2. 新 Claim 进入时与已有 Claim 做冲突检测
8
+ 3. 生成 Conflict Card,运行裁决策略
9
+ 4. 更新 Claim 状态(pending → active / disputed / superseded)
10
+ """
11
+ # TODO: Replace with proper package install (pyproject.toml) before v1 release
12
+
13
+ import threading
14
+ from datetime import datetime, timezone
15
+
16
+ from hkc_core.graph.base import GraphStore
17
+ from hkc_core.models.ku import ClaimKU, ConflictCard
18
+ from hkc_core.models.enums import (
19
+ ClaimStatus, ConflictStatus, ResolutionStrategy, KUType
20
+ )
21
+ from hkc_core.utils.id_gen import IDGenerator
22
+ from hkc_kep.event_bus import EventBus, KEPEvents
23
+ from .dedup import KnowledgeDeduplicator
24
+
25
+
26
+ # ── 语义相似度(轻量版,不依赖 GPU)────────────────────────
27
+
28
+ import re as _re
29
+ # 否定词:ASCII 按词边界匹配(避免 bad⊂badge / no⊂know 的子串误命中),中文按子串。
30
+ _NEG_ASCII = {"not", "no", "never", "false", "bad", "harmful", "worse", "cannot"}
31
+ _NEG_CJK = {"不", "非", "无", "差", "不好", "没有", "并非", "并不", "错误"}
32
+ # 含否定字却实为正面/中性的高频复合词。单字否定(不/非/无/差)按子串匹配会把
33
+ # "无论/非常/不断/不仅"误判为否定,进而把互补陈述判成"矛盾"(实测假阳性主因)。
34
+ # 判否定前先剔除这些;真否定(无法/并非/不好/没有…)不在此列,仍被检出。
35
+ _NEG_CJK_FALSE = (
36
+ "无论", "无数", "无比", "无穷", "无限", "无非", "无疑", "无关", "无外乎",
37
+ "非常", "非凡",
38
+ "不过", "不仅", "不但", "不断", "不只", "不妨", "不禁", "不由", "不时", "不外乎", "不失为",
39
+ "差不多", "差点",
40
+ )
41
+ _NEG_ASCII_RE = _re.compile(r"\b(" + "|".join(sorted(_NEG_ASCII, key=len, reverse=True)) + r")\b")
42
+ _NEGATION = _NEG_ASCII | _NEG_CJK # 供 _score_keyword 的词集匹配(word-set,ASCII 本就安全)
43
+
44
+ def _encode_one(model, model_name, text):
45
+ """单文本编码。走 HKC 的**共享向量缓存**(与检索/去重同一份、LRU 有界、锁外编码)。
46
+ 此前这里有自己的 _EMB_CACHE,各层各缓存各的、互不共享;现已收敛到编码咽喉一处。"""
47
+ from hkc_search.embedding_backends import encode_cached
48
+ return encode_cached(model, model_name, [text])[0]
49
+
50
+
51
+ class _SimpleEmbedder:
52
+ """语义方向分数 score() ∈ [-1, 1]:
53
+ > thr → 同向(互相支持) -thr~thr → 无关 < -thr → 反向(矛盾)
54
+ 阈值 thr 由 KnowledgeEvolutionEngine.similarity_threshold 判定(默认 0.30);
55
+ 本类只负责给分,不内嵌阈值。
56
+ v2:优先 sentence-transformers 余弦(真语义,经 HKC 共享缓存);
57
+ 无 ST/加载失败 → 回退 v1 关键词重叠 + 否定词。接口不变。
58
+ """
59
+ _MODEL_NAME = "paraphrase-multilingual-MiniLM-L12-v2" # 与 HKC LocalSTBackend 默认同名 → 共享一份
60
+
61
+ @staticmethod
62
+ def _has_neg(text: str) -> bool:
63
+ t = (text or "").lower()
64
+ if _NEG_ASCII_RE.search(t):
65
+ return True
66
+ # 先剔除"伪否定"复合词(无论/非常/不断…),再看是否残留单字否定
67
+ for w in _NEG_CJK_FALSE:
68
+ t = t.replace(w, "")
69
+ return any(n in t for n in _NEG_CJK)
70
+
71
+ def score(self, text_a: str, text_b: str) -> float:
72
+ model = None
73
+ try:
74
+ from hkc_search.embedding_backends import get_st_model
75
+ model = get_st_model(self._MODEL_NAME)
76
+ except Exception:
77
+ model = None
78
+ if model is not None:
79
+ try:
80
+ import numpy as np
81
+ va = _encode_one(model, self._MODEL_NAME, text_a) # 缓存,不重复编码
82
+ vb = _encode_one(model, self._MODEL_NAME, text_b)
83
+ cos = float(np.dot(va, vb)) # 归一化后点积=余弦 ∈ [-1,1]
84
+ # 否定方向:主题高度相似但一方否定另一方不否定 → 立场相反 → 反号(矛盾)
85
+ if self._has_neg(text_a) != self._has_neg(text_b) and cos > 0.35:
86
+ return -abs(cos)
87
+ return max(-1.0, min(1.0, cos))
88
+ except Exception:
89
+ pass
90
+ return self._score_keyword(text_a, text_b)
91
+
92
+ def _score_keyword(self, text_a: str, text_b: str) -> float:
93
+ """v1 回退:关键词重叠 + 否定词(无 ST 时用)。"""
94
+ a_words = set(text_a.lower().split())
95
+ b_words = set(text_b.lower().split())
96
+ a_neg = bool(a_words & _NEGATION)
97
+ b_neg = bool(b_words & _NEGATION)
98
+ a_topic = a_words - _NEGATION
99
+ b_topic = b_words - _NEGATION
100
+ overlap = len(a_topic & b_topic)
101
+ if overlap == 0:
102
+ return 0.0
103
+ if a_neg != b_neg:
104
+ return -0.5
105
+ return min(0.8, overlap * 0.15)
106
+
107
+
108
+ # ── Claim 状态机 ─────────────────────────────────────────────
109
+
110
+ class ClaimStateMachine:
111
+ """
112
+ 管理 Claim 的五种状态流转。
113
+
114
+ pending → active : 找到支持 Evidence,confidence > 0.7
115
+ pending → disputed : 对立 Claim,置信度相近
116
+ pending → rejected : Evidence 明确反驳,confidence < 0.3
117
+ active → disputed : 出现对立 Claim,置信度差 < 0.15
118
+ active → superseded: 新研究明确推翻
119
+ disputed → active : 新 Evidence 倾向一方,差值 > 0.2
120
+ """
121
+
122
+ def transition(
123
+ self,
124
+ claim: ClaimKU,
125
+ new_confidence: float,
126
+ has_conflict: bool,
127
+ conflict_gap: float = 0.0,
128
+ ) -> ClaimStatus:
129
+ current = claim.claim_status
130
+ conf = new_confidence
131
+
132
+ if current == ClaimStatus.PENDING:
133
+ if conf > 0.7 and not has_conflict:
134
+ return ClaimStatus.ACTIVE
135
+ if has_conflict and conflict_gap < 0.15:
136
+ return ClaimStatus.DISPUTED
137
+ if conf < 0.3:
138
+ return ClaimStatus.REJECTED
139
+
140
+ elif current == ClaimStatus.ACTIVE:
141
+ if has_conflict and conflict_gap < 0.15:
142
+ return ClaimStatus.DISPUTED
143
+ if conf < 0.3:
144
+ return ClaimStatus.SUPERSEDED
145
+
146
+ elif current == ClaimStatus.DISPUTED:
147
+ if conflict_gap > 0.2:
148
+ return ClaimStatus.ACTIVE
149
+
150
+ return current # 无变化
151
+
152
+
153
+ # ── KEE 主引擎 ───────────────────────────────────────────────
154
+
155
+ class KnowledgeEvolutionEngine:
156
+
157
+ def __init__(
158
+ self,
159
+ graph_store: GraphStore,
160
+ event_bus: EventBus,
161
+ id_gen: IDGenerator,
162
+ similarity_threshold: float = 0.30,
163
+ auto_resolve_gap: float = 0.20,
164
+ ):
165
+ self.store = graph_store
166
+ self.bus = event_bus
167
+ self.id_gen = id_gen
168
+ self.embedder = _SimpleEmbedder()
169
+ self.state_machine = ClaimStateMachine()
170
+ # 语义方向阈值(score∈[-1,1]):|score|>threshold 判同向(合并)/反向(冲突)。
171
+ # 默认 0.30 与既有硬编码一致(behavior-preserving);可经构造参数调严/调宽。
172
+ self.similarity_threshold = similarity_threshold
173
+ self.auto_resolve_gap = auto_resolve_gap
174
+
175
+ # open 冲突卡的 (claim_a, claim_b) 对索引:替代 _handle_conflict 里每次
176
+ # list_conflicts 全扫去重(冲突多时 O(冲突²))。并发摄入会跨线程碰它 → 加锁。
177
+ # 惰性构建;锁获取顺序恒为「本组件锁 → graph_store 锁」,与全系统一致,无死锁。
178
+ self._conflict_lock = threading.RLock()
179
+ self._open_pairs = None # None=未构建;构建后为 set[frozenset({a,b})]
180
+ # Evidence id → "分组键"缓存。分组键 = 所属合集(文件夹)id(若 Evidence 带
181
+ # _incol tag),否则为 Evidence 自身 id。按分组判同源:同一文件夹的多个文件
182
+ # 视为同源、互不判矛盾(比按书名更稳,单文件/文件夹都覆盖)。幂等填充,无需锁。
183
+ self._evd_group_cache: dict = {}
184
+
185
+ # 注册监听
186
+ self.bus.subscribe(KEPEvents.KNOWLEDGE_CREATED, self._on_knowledge_created)
187
+
188
+ # 知识级去重器(KEE 是知识身份唯一权威)
189
+ self.dedup = KnowledgeDeduplicator(graph_store)
190
+
191
+ def reset_conflict_index(self):
192
+ """清库后调用:失效 open 冲突对索引,下次使用时按(空)库重建。"""
193
+ with self._conflict_lock:
194
+ self._open_pairs = None
195
+ self._evd_group_cache.clear()
196
+
197
+ def _source_groups(self, claim) -> set:
198
+ """claim 的来源分组集合。每个支持 Evidence 的"分组键":属于某合集(文件夹)
199
+ 则用合集 id(Evidence 的 _incol:<id> tag),否则用 Evidence 自身 id。
200
+ 按分组判同源——同一文件夹的多个文件(章节)视为同源、互不判矛盾。缓存。"""
201
+ out = set()
202
+ for eid in (getattr(claim, "supports", None) or []):
203
+ if eid not in self._evd_group_cache:
204
+ ku = self.store.get(eid)
205
+ g = eid # 默认:Evidence 自身即一个来源(单文件上传)
206
+ if ku is not None:
207
+ for t in (getattr(ku, "tags", None) or []):
208
+ if t.startswith("_incol:"):
209
+ g = t[len("_incol:"):] # 属于合集 → 用合集 id
210
+ break
211
+ self._evd_group_cache[eid] = g
212
+ out.add(self._evd_group_cache[eid])
213
+ return out
214
+
215
+ # ── 事件入口 ──────────────────────────────────────────────
216
+
217
+ def _on_knowledge_created(self, event: dict):
218
+ ku_ids = event.get("payload", {}).get("ku_ids", [])
219
+ for ku_id in ku_ids:
220
+ ku = self.store.get(ku_id)
221
+ if ku is None:
222
+ continue
223
+
224
+ # ── 知识级去重(所有 KU 类型,先于一切处理)──
225
+ # 命中已有同一知识 → Evidence 已合并入旧 KU → 删除这个重复新 KU,不再处理
226
+ existing_id = self.dedup.check_and_merge(ku)
227
+ if existing_id is not None:
228
+ try:
229
+ self.store.delete(ku_id)
230
+ except Exception:
231
+ pass
232
+ continue
233
+
234
+ # 未命中(是新知识)→ 按类型走后续演化
235
+ if ku.ku_type == KUType.CLAIM:
236
+ self._process_new_claim(ku)
237
+
238
+ # ── 核心流程 ──────────────────────────────────────────────
239
+
240
+ def _process_new_claim(self, new_claim: ClaimKU):
241
+ """
242
+ 新 Claim 进入时的完整处理流程:
243
+ 1. 搜索同领域、**不同来源**的已有 Claim
244
+ 2. 计算语义方向
245
+ 3. 冲突 → 生成 Conflict Card → 尝试裁决
246
+ 4. 非冲突 → 融合 Evidence,更新置信度
247
+
248
+ 来源感知(Principle 07:身份/演化按证据裁决):同一来源(共享 Evidence,
249
+ 即同一本书/文档)内部的 Claim 是一份连贯材料,不互判矛盾、不互相合并证据——
250
+ 真正值得裁决的是**跨来源**的分歧。这既符合"按证据裁决"的语义,也把摄入单本
251
+ 书时最大的开销项(书内 Claim 两两比对的 N²)整块砍掉。
252
+ """
253
+ existing_claims = self.store.find_claims_by_domain(
254
+ new_claim.domain or ""
255
+ )
256
+ new_groups = self._source_groups(new_claim)
257
+ # 排除自身 + 排除同源(同一分组/合集;同文件夹的多个文件按合集算同源)
258
+ existing_claims = [
259
+ c for c in existing_claims
260
+ if c.ku_id != new_claim.ku_id
261
+ and (not new_groups
262
+ or new_groups.isdisjoint(self._source_groups(c)))
263
+ ]
264
+
265
+ conflict_found = False
266
+
267
+ for old_claim in existing_claims:
268
+ score = self.embedder.score(
269
+ new_claim.statement,
270
+ old_claim.statement,
271
+ )
272
+
273
+ if score < -self.similarity_threshold:
274
+ # 语义方向相反 → 冲突
275
+ conflict_found = True
276
+ self._handle_conflict(new_claim, old_claim)
277
+
278
+ elif score > self.similarity_threshold:
279
+ # 同向 → 融合 Evidence,强化置信度
280
+ self._merge_evidence(new_claim, old_claim)
281
+
282
+ # 更新 new_claim 自身状态
283
+ has_conflict = conflict_found
284
+ conflict_gap = 0.0
285
+ if conflict_found:
286
+ # 找到最强对立 Claim 的置信度差
287
+ conflict_gap = self._max_conflict_gap(new_claim, existing_claims)
288
+
289
+ new_status = self.state_machine.transition(
290
+ new_claim,
291
+ new_confidence=new_claim.confidence,
292
+ has_conflict=has_conflict,
293
+ conflict_gap=conflict_gap,
294
+ )
295
+
296
+ if new_status != new_claim.claim_status:
297
+ old_status = new_claim.claim_status
298
+ new_claim.claim_status = new_status
299
+ # claim_status 落在 extra 里 → 只合并这一个键,别整行回写(会冲掉并发的
300
+ # extra 写入,如复习进度)。行已不在则退回 put 的插入语义。
301
+ if self.store.patch_ku(
302
+ new_claim.ku_id,
303
+ extra={"claim_status": new_claim.claim_status.value},
304
+ ) == 0:
305
+ self.store.put(new_claim)
306
+
307
+ self.bus.publish({
308
+ "event": KEPEvents.CLAIM_STATUS_CHANGED,
309
+ "source": "KEE",
310
+ "payload": {
311
+ "ku_id": new_claim.ku_id,
312
+ "old_status": old_status.value,
313
+ "new_status": new_status.value,
314
+ }
315
+ })
316
+ # 阶段 B:KEE 内部状态机变化必须 publish UPDATED,否则 _sync_ku 不会重新
317
+ # 检查该 KU,vector 里仍是旧状态(superseded/disputed 仍被检索召回)。
318
+ self.bus.publish({
319
+ "event": KEPEvents.KNOWLEDGE_UPDATED,
320
+ "source": "KEE",
321
+ "payload": {
322
+ "ku_id": new_claim.ku_id,
323
+ "reason": "claim_status_changed",
324
+ }
325
+ })
326
+
327
+ def _ensure_open_pairs(self):
328
+ """惰性构建 open 冲突对索引(frozenset({a,b}))。仅在持 _conflict_lock 时调用。"""
329
+ if self._open_pairs is None:
330
+ pairs = set()
331
+ try:
332
+ for card in self.store.list_conflicts(status="open"):
333
+ pairs.add(frozenset((card.claim_a_id, card.claim_b_id)))
334
+ except Exception:
335
+ pass
336
+ self._open_pairs = pairs
337
+
338
+ def _forget_open_pair(self, a_id: str, b_id: str):
339
+ """冲突卡关闭后从 open 对索引移除。所有裁决路径(自动/人工)统一调用,
340
+ 保证内存索引与库中 open 卡一致——否则残留旧对会误跳过该对的新冲突。"""
341
+ with self._conflict_lock:
342
+ if self._open_pairs is not None:
343
+ self._open_pairs.discard(frozenset((a_id, b_id)))
344
+
345
+ def _handle_conflict(self, new_claim: ClaimKU, old_claim: ClaimKU):
346
+ """生成 Conflict Card 并尝试自动裁决。"""
347
+ pair = frozenset((new_claim.ku_id, old_claim.ku_id))
348
+ # 用内存索引避免同一对重复建卡(旧实现每次 list_conflicts 全扫 = O(冲突²))
349
+ with self._conflict_lock:
350
+ self._ensure_open_pairs()
351
+ if pair in self._open_pairs:
352
+ return # 已存在同一对的 open 冲突卡
353
+ card = ConflictCard(
354
+ conflict_id=self.id_gen.next("Conflict"),
355
+ claim_a_id=old_claim.ku_id,
356
+ claim_b_id=new_claim.ku_id,
357
+ domain=new_claim.domain or old_claim.domain,
358
+ status=ConflictStatus.OPEN,
359
+ resolution_strategy=ResolutionStrategy.EVIDENCE_WEIGHT,
360
+ )
361
+ self.store.save_conflict(card)
362
+ self._open_pairs.add(pair)
363
+
364
+ self.bus.publish({
365
+ "event": KEPEvents.CONFLICT_DETECTED,
366
+ "source": "KEE",
367
+ "payload": {
368
+ "conflict_id": card.conflict_id,
369
+ "claim_a": old_claim.ku_id,
370
+ "claim_b": new_claim.ku_id,
371
+ "domain": card.domain,
372
+ }
373
+ })
374
+
375
+ # 尝试自动裁决
376
+ self._try_auto_resolve(card, old_claim, new_claim)
377
+
378
+ def _try_auto_resolve(
379
+ self,
380
+ card: ConflictCard,
381
+ claim_a: ClaimKU,
382
+ claim_b: ClaimKU,
383
+ ):
384
+ """
385
+ evidence_weight 策略:
386
+ 置信度差 > auto_resolve_gap (0.20) 才自动裁决。
387
+ 差距不足 → 保持 open,等待人工介入。
388
+ """
389
+ gap = abs(claim_a.confidence - claim_b.confidence)
390
+ if gap < self.auto_resolve_gap:
391
+ return # 差距不足,不自动裁决
392
+
393
+ winner = claim_a if claim_a.confidence >= claim_b.confidence else claim_b
394
+ loser = claim_b if winner.ku_id == claim_a.ku_id else claim_a
395
+
396
+ # 更新状态(只合并 extra.claim_status,不整行回写)
397
+ winner.claim_status = ClaimStatus.ACTIVE
398
+ loser.claim_status = ClaimStatus.SUPERSEDED
399
+ for c in (winner, loser):
400
+ if self.store.patch_ku(
401
+ c.ku_id, extra={"claim_status": c.claim_status.value}
402
+ ) == 0:
403
+ self.store.put(c)
404
+ # 阶段 B:winner(→active)/loser(→superseded)状态都变了,发 UPDATED 让 _sync_ku
405
+ # 同步 vector:loser 必须从 vector 移除,否则检索仍召回已废弃 Claim。
406
+ self.bus.publish({
407
+ "event": KEPEvents.KNOWLEDGE_UPDATED,
408
+ "source": "KEE",
409
+ "payload": {
410
+ "ku_ids": [winner.ku_id, loser.ku_id],
411
+ "reason": "conflict_auto_resolved",
412
+ }
413
+ })
414
+
415
+ # 关闭冲突卡
416
+ card.status = ConflictStatus.RESOLVED
417
+ card.resolved_at = datetime.now(timezone.utc).isoformat()
418
+ card.resolution_note = (
419
+ f"自动裁决:{winner.ku_id} 置信度更高 "
420
+ f"({winner.confidence:.2f} vs {loser.confidence:.2f})"
421
+ )
422
+ self.store.save_conflict(card)
423
+ self._forget_open_pair(card.claim_a_id, card.claim_b_id) # 关闭→移出 open 索引
424
+
425
+ self.bus.publish({
426
+ "event": KEPEvents.CONFLICT_RESOLVED,
427
+ "source": "KEE",
428
+ "payload": {
429
+ "conflict_id": card.conflict_id,
430
+ "winner": winner.ku_id,
431
+ "loser": loser.ku_id,
432
+ }
433
+ })
434
+
435
+ def _merge_evidence(self, new_claim: ClaimKU, old_claim: ClaimKU):
436
+ """
437
+ 新 Claim 与旧 Claim 同向时:
438
+ 把新 Claim 的 Evidence 合并到旧 Claim,提升其置信度。
439
+ """
440
+ if not new_claim.supports:
441
+ return
442
+ added = [e for e in new_claim.supports if e not in old_claim.supports]
443
+ if not added:
444
+ return
445
+ old_claim.supports.extend(added)
446
+ # 每多一条 Evidence,置信度 +0.02,上限 0.98;只能是「抬高」,不能把本来
447
+ # 1.0 的置信度因为多一条证据降到 0.98。
448
+ old_claim.confidence = max(
449
+ old_claim.confidence,
450
+ min(0.98, old_claim.confidence + len(added) * 0.02),
451
+ )
452
+ # supports 在 extra、confidence 是普通列 → 只写这两处,别整行回写
453
+ if self.store.patch_ku(
454
+ old_claim.ku_id,
455
+ columns={"confidence": old_claim.confidence},
456
+ extra={"supports": list(old_claim.supports)},
457
+ ) == 0:
458
+ self.store.put(old_claim)
459
+ # 阶段 B:置信度/supports 变了,虽然向量文本没变(text_hash 同),仍 publish UPDATED
460
+ # 让 _sync_ku 走 (5b) 同 hash 跳过 encode,只是保证检索侧能拿到最新 confidence
461
+ # 注:_sync_ku 五分支不返回 confidence;但显式发 UPDATED 让审计日志完整。
462
+ self.bus.publish({
463
+ "event": KEPEvents.KNOWLEDGE_UPDATED,
464
+ "source": "KEE",
465
+ "payload": {
466
+ "ku_id": old_claim.ku_id,
467
+ "reason": "evidence_merged",
468
+ }
469
+ })
470
+
471
+ def _max_conflict_gap(
472
+ self,
473
+ claim: ClaimKU,
474
+ others: list[ClaimKU],
475
+ ) -> float:
476
+ """找到与 claim 最接近(最危险)的对立 Claim 的置信度差。"""
477
+ gaps = []
478
+ for other in others:
479
+ score = self.embedder.score(claim.statement, other.statement)
480
+ if score < -self.similarity_threshold:
481
+ gaps.append(abs(claim.confidence - other.confidence))
482
+ return min(gaps) if gaps else 1.0
483
+
484
+ # ── 手动裁决接口(供 API 层调用)─────────────────────────
485
+
486
+ def manual_resolve(
487
+ self,
488
+ conflict_id: str,
489
+ winner_id: str,
490
+ note: str = "",
491
+ resolved_by: str = "human",
492
+ ) -> bool:
493
+ card = self.store.get_conflict(conflict_id)
494
+ if not card or card.status != ConflictStatus.OPEN:
495
+ return False
496
+
497
+ loser_id = (
498
+ card.claim_b_id
499
+ if winner_id == card.claim_a_id
500
+ else card.claim_a_id
501
+ )
502
+ winner = self.store.get(winner_id)
503
+ loser = self.store.get(loser_id)
504
+ if not winner or not loser:
505
+ return False
506
+
507
+ winner.claim_status = ClaimStatus.ACTIVE
508
+ loser.claim_status = ClaimStatus.SUPERSEDED
509
+ for c in (winner, loser):
510
+ if self.store.patch_ku(
511
+ c.ku_id, extra={"claim_status": c.claim_status.value}
512
+ ) == 0:
513
+ self.store.put(c)
514
+ # 阶段 B:同自动裁决,loser 变 superseded 必须从 vector 移除。
515
+ self.bus.publish({
516
+ "event": KEPEvents.KNOWLEDGE_UPDATED,
517
+ "source": "KEE",
518
+ "payload": {
519
+ "ku_ids": [winner.ku_id, loser.ku_id],
520
+ "reason": "conflict_manual_resolved",
521
+ }
522
+ })
523
+
524
+ card.status = ConflictStatus.RESOLVED
525
+ card.resolved_by = resolved_by
526
+ card.resolved_at = datetime.now(timezone.utc).isoformat()
527
+ card.resolution_note = note or f"人工裁决:{winner_id} 胜出"
528
+ self.store.save_conflict(card)
529
+ self._forget_open_pair(card.claim_a_id, card.claim_b_id) # 关闭→移出 open 索引
530
+
531
+ self.bus.publish({
532
+ "event": KEPEvents.CONFLICT_RESOLVED,
533
+ "source": "KEE",
534
+ "payload": {
535
+ "conflict_id": conflict_id,
536
+ "winner": winner_id,
537
+ "loser": loser_id,
538
+ "by": resolved_by,
539
+ }
540
+ })
541
+ return True
File without changes