foxclaw 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (514) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +4 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +173 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +167 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/CHANGELOG.md +57 -0
  11. package/python/CHANGELOG_CN.md +57 -0
  12. package/python/Dockerfile +64 -0
  13. package/python/LICENSE +21 -0
  14. package/python/MANIFEST.in +14 -0
  15. package/python/README.md +201 -0
  16. package/python/README_CN.md +336 -0
  17. package/python/bench/bm25_audit.py +186 -0
  18. package/python/bench/bm25_regression.py +177 -0
  19. package/python/bench/bm25_stress.py +155 -0
  20. package/python/bench/cd_audit.py +215 -0
  21. package/python/bench/chaos.py +140 -0
  22. package/python/bench/concurrency.py +103 -0
  23. package/python/bench/e2e_real_models.py +94 -0
  24. package/python/bench/llm_retry_test.py +241 -0
  25. package/python/bench/load_test.py +193 -0
  26. package/python/bench/vector_ivf_upgrade.py +128 -0
  27. package/python/docs/CHANNELS.md +195 -0
  28. package/python/docs/GOVERNANCE.md +164 -0
  29. package/python/docs/RELEASE.md +110 -0
  30. package/python/docs/RUN.md +75 -0
  31. package/python/docs/RUN_CN.md +121 -0
  32. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  33. package/python/evals/__init__.py +0 -0
  34. package/python/evals/governance/README.md +61 -0
  35. package/python/evals/governance/__init__.py +0 -0
  36. package/python/evals/governance/cases.py +115 -0
  37. package/python/evals/governance/compare_embeddings.py +94 -0
  38. package/python/evals/governance/convergence.svg +11 -0
  39. package/python/evals/governance/report.json +371 -0
  40. package/python/evals/governance/run_evals.py +368 -0
  41. package/python/examples/governance_middleware_demo.py +72 -0
  42. package/python/examples/langgraph_governance_demo.py +104 -0
  43. package/python/examples/sample_itinerary.csv +6 -0
  44. package/python/examples/sample_knowledge.csv +9 -0
  45. package/python/examples/sample_transactions.csv +14 -0
  46. package/python/foxclaw/__init__.py +408 -0
  47. package/python/foxclaw/agents/__init__.py +12 -0
  48. package/python/foxclaw/agents/agents.py +35 -0
  49. package/python/foxclaw/agents/base.py +794 -0
  50. package/python/foxclaw/agents/impl/__init__.py +1 -0
  51. package/python/foxclaw/agents/impl/_helpers.py +70 -0
  52. package/python/foxclaw/agents/impl/finance.py +231 -0
  53. package/python/foxclaw/agents/impl/office.py +350 -0
  54. package/python/foxclaw/agents/impl/supervisor.py +592 -0
  55. package/python/foxclaw/agents/mixins/_context.py +128 -0
  56. package/python/foxclaw/agents/mixins/augment.py +143 -0
  57. package/python/foxclaw/agents/mixins/llm.py +67 -0
  58. package/python/foxclaw/agents/mixins/memory.py +41 -0
  59. package/python/foxclaw/agents/mixins/preference.py +131 -0
  60. package/python/foxclaw/agents/mixins/prompt.py +71 -0
  61. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  62. package/python/foxclaw/agents/mixins/tool_exec.py +417 -0
  63. package/python/foxclaw/api/__init__.py +224 -0
  64. package/python/foxclaw/api/_state.py +43 -0
  65. package/python/foxclaw/api/bootstrap.py +96 -0
  66. package/python/foxclaw/api/middleware.py +294 -0
  67. package/python/foxclaw/api/routes/ahs.py +404 -0
  68. package/python/foxclaw/api/routes/attachments.py +116 -0
  69. package/python/foxclaw/api/routes/browser.py +35 -0
  70. package/python/foxclaw/api/routes/domain.py +306 -0
  71. package/python/foxclaw/api/routes/knowledge.py +145 -0
  72. package/python/foxclaw/api/routes/llm_admin.py +449 -0
  73. package/python/foxclaw/api/routes/memory.py +105 -0
  74. package/python/foxclaw/api/routes/orchestration.py +39 -0
  75. package/python/foxclaw/api/routes/recovery.py +44 -0
  76. package/python/foxclaw/api/routes/review.py +266 -0
  77. package/python/foxclaw/api/routes/search.py +33 -0
  78. package/python/foxclaw/api/routes/system.py +311 -0
  79. package/python/foxclaw/api/routes/task.py +263 -0
  80. package/python/foxclaw/api/routes/ws.py +64 -0
  81. package/python/foxclaw/api/spine.py +52 -0
  82. package/python/foxclaw/browser/__init__.py +13 -0
  83. package/python/foxclaw/browser/action/__init__.py +1 -0
  84. package/python/foxclaw/browser/action/action_executor.py +50 -0
  85. package/python/foxclaw/browser/action/action_types.py +44 -0
  86. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  87. package/python/foxclaw/browser/action/url_policy.py +143 -0
  88. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  89. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  90. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  91. package/python/foxclaw/browser/driver/__init__.py +1 -0
  92. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  93. package/python/foxclaw/browser/driver/playwright_driver.py +170 -0
  94. package/python/foxclaw/browser/perception/__init__.py +1 -0
  95. package/python/foxclaw/browser/perception/dom_extractor.py +42 -0
  96. package/python/foxclaw/browser/perception/element_indexer.py +56 -0
  97. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  98. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  99. package/python/foxclaw/browser/service.py +144 -0
  100. package/python/foxclaw/browser/session/__init__.py +1 -0
  101. package/python/foxclaw/browser/session/session_manager.py +57 -0
  102. package/python/foxclaw/capability/__init__.py +12 -0
  103. package/python/foxclaw/capability/embedding_service.py +65 -0
  104. package/python/foxclaw/channels/__init__.py +138 -0
  105. package/python/foxclaw/channels/discord.py +175 -0
  106. package/python/foxclaw/channels/dispatcher.py +179 -0
  107. package/python/foxclaw/channels/feishu.py +215 -0
  108. package/python/foxclaw/channels/openclaw_bridge.py +151 -0
  109. package/python/foxclaw/channels/signing.py +187 -0
  110. package/python/foxclaw/channels/telegram.py +120 -0
  111. package/python/foxclaw/channels/weixin.py +215 -0
  112. package/python/foxclaw/cli.py +183 -0
  113. package/python/foxclaw/cognitive/__init__.py +4 -0
  114. package/python/foxclaw/cognitive/reasoner.py +81 -0
  115. package/python/foxclaw/cognitive/state.py +141 -0
  116. package/python/foxclaw/core/__init__.py +9 -0
  117. package/python/foxclaw/core/audit_chain.py +131 -0
  118. package/python/foxclaw/core/audit_sink.py +100 -0
  119. package/python/foxclaw/core/compaction.py +265 -0
  120. package/python/foxclaw/core/config.py +65 -0
  121. package/python/foxclaw/core/confirm_nonce.py +151 -0
  122. package/python/foxclaw/core/dispatch.py +27 -0
  123. package/python/foxclaw/core/envfile.py +48 -0
  124. package/python/foxclaw/core/failure_policy.py +142 -0
  125. package/python/foxclaw/core/gateway.py +81 -0
  126. package/python/foxclaw/core/governance_audit.py +171 -0
  127. package/python/foxclaw/core/idempotency.py +282 -0
  128. package/python/foxclaw/core/injection_guard.py +202 -0
  129. package/python/foxclaw/core/memory_adapter.py +489 -0
  130. package/python/foxclaw/core/metrics.py +88 -0
  131. package/python/foxclaw/core/model_router.py +985 -0
  132. package/python/foxclaw/core/paths.py +103 -0
  133. package/python/foxclaw/core/recovery.py +349 -0
  134. package/python/foxclaw/core/scheduler.py +141 -0
  135. package/python/foxclaw/core/secrets.py +131 -0
  136. package/python/foxclaw/core/sessions.py +155 -0
  137. package/python/foxclaw/core/settings.py +414 -0
  138. package/python/foxclaw/core/shared_write.py +186 -0
  139. package/python/foxclaw/core/steering_queue.py +154 -0
  140. package/python/foxclaw/core/system_bus.py +84 -0
  141. package/python/foxclaw/core/thought_chain.py +316 -0
  142. package/python/foxclaw/core/token_meter.py +231 -0
  143. package/python/foxclaw/core/tools.py +164 -0
  144. package/python/foxclaw/core/tracing.py +375 -0
  145. package/python/foxclaw/core/truth_pipeline.py +110 -0
  146. package/python/foxclaw/core/update_check.py +149 -0
  147. package/python/foxclaw/core/vec_recall_adapter.py +477 -0
  148. package/python/foxclaw/core/verify.py +173 -0
  149. package/python/foxclaw/core/web_search.py +218 -0
  150. package/python/foxclaw/finance/__init__.py +15 -0
  151. package/python/foxclaw/finance/evidence.py +149 -0
  152. package/python/foxclaw/finance/fx.py +98 -0
  153. package/python/foxclaw/finance/graph.py +179 -0
  154. package/python/foxclaw/finance/models.py +146 -0
  155. package/python/foxclaw/finance/nlparse.py +515 -0
  156. package/python/foxclaw/finance/policy.py +301 -0
  157. package/python/foxclaw/finance/reasoner.py +121 -0
  158. package/python/foxclaw/finance/reasoning.py +319 -0
  159. package/python/foxclaw/finance/reconcile.py +175 -0
  160. package/python/foxclaw/finance/runtime.py +500 -0
  161. package/python/foxclaw/governance/__init__.py +24 -0
  162. package/python/foxclaw/governance/middleware.py +176 -0
  163. package/python/foxclaw/harness/__init__.py +17 -0
  164. package/python/foxclaw/harness/_base.py +66 -0
  165. package/python/foxclaw/harness/_policy.py +340 -0
  166. package/python/foxclaw/harness/drift.py +77 -0
  167. package/python/foxclaw/harness/event_bus.py +28 -0
  168. package/python/foxclaw/harness/harness.py +358 -0
  169. package/python/foxclaw/harness/loop_detection.py +145 -0
  170. package/python/foxclaw/itinerary/__init__.py +10 -0
  171. package/python/foxclaw/itinerary/amap_client.py +114 -0
  172. package/python/foxclaw/itinerary/models.py +80 -0
  173. package/python/foxclaw/itinerary/reasoner.py +96 -0
  174. package/python/foxclaw/itinerary/reasoning.py +168 -0
  175. package/python/foxclaw/itinerary/runtime.py +144 -0
  176. package/python/foxclaw/kernel/__init__.py +13 -0
  177. package/python/foxclaw/kernel/capabilities.py +72 -0
  178. package/python/foxclaw/kernel/policy_gate.py +108 -0
  179. package/python/foxclaw/kernel/process_manager.py +186 -0
  180. package/python/foxclaw/kernel/scheduler.py +125 -0
  181. package/python/foxclaw/kernel/sentinel.py +443 -0
  182. package/python/foxclaw/kernel/sentinel_client.py +243 -0
  183. package/python/foxclaw/kernel/spine.py +97 -0
  184. package/python/foxclaw/knowledge/__init__.py +360 -0
  185. package/python/foxclaw/knowledge/truth_crystallizer.py +89 -0
  186. package/python/foxclaw/learning/__init__.py +12 -0
  187. package/python/foxclaw/learning/knowledge_graph.py +144 -0
  188. package/python/foxclaw/learning/models.py +93 -0
  189. package/python/foxclaw/learning/reasoner.py +67 -0
  190. package/python/foxclaw/learning/runtime.py +351 -0
  191. package/python/foxclaw/learning/spaced_repetition.py +91 -0
  192. package/python/foxclaw/mcp_server.py +188 -0
  193. package/python/foxclaw/orchestration/__init__.py +167 -0
  194. package/python/foxclaw/pipeline/__init__.py +23 -0
  195. package/python/foxclaw/pipeline/cognitive_pipeline.py +429 -0
  196. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  197. package/python/foxclaw/pipeline/supervisor_stages.py +201 -0
  198. package/python/foxclaw/planning/__init__.py +17 -0
  199. package/python/foxclaw/planning/llm_planner.py +232 -0
  200. package/python/foxclaw/planning/planner.py +94 -0
  201. package/python/foxclaw/search/__init__.py +24 -0
  202. package/python/foxclaw/search/agent_port.py +190 -0
  203. package/python/foxclaw/search/evidence.py +109 -0
  204. package/python/foxclaw/search/external_search.py +50 -0
  205. package/python/foxclaw/search/hkc_read_port.py +60 -0
  206. package/python/foxclaw/search/internal_search.py +71 -0
  207. package/python/foxclaw/search/models.py +127 -0
  208. package/python/foxclaw/search/planner.py +44 -0
  209. package/python/foxclaw/search/retriever.py +39 -0
  210. package/python/foxclaw/search/runtime.py +127 -0
  211. package/python/foxclaw/search/strategy.py +44 -0
  212. package/python/foxclaw/skills/__init__.py +559 -0
  213. package/python/foxclaw/skills/archive.py +111 -0
  214. package/python/foxclaw/skills/fetch.py +226 -0
  215. package/python/foxclaw/skills/hooks_spec.py +69 -0
  216. package/python/foxclaw/skills/install_cmd.py +216 -0
  217. package/python/foxclaw/skills/lifecycle.py +252 -0
  218. package/python/foxclaw/skills/markdown_skill.py +175 -0
  219. package/python/foxclaw/skills/markdown_store.py +39 -0
  220. package/python/foxclaw/skills/mcp_client.py +184 -0
  221. package/python/foxclaw/skills/mcp_store.py +40 -0
  222. package/python/foxclaw/skills/permission.py +145 -0
  223. package/python/foxclaw/skills/pipeline.py +354 -0
  224. package/python/foxclaw/skills/policy.py +74 -0
  225. package/python/foxclaw/skills/review.py +72 -0
  226. package/python/foxclaw/skills/store.py +57 -0
  227. package/python/frontend/assets/core_bg.jpg +0 -0
  228. package/python/frontend/deck/auth.js +25 -0
  229. package/python/frontend/deck/chat_model_picker.js +483 -0
  230. package/python/frontend/deck/domain.js +276 -0
  231. package/python/frontend/deck/main.js +1806 -0
  232. package/python/frontend/deck/markdown.js +44 -0
  233. package/python/frontend/deck/settings.js +307 -0
  234. package/python/frontend/deck/state.js +5 -0
  235. package/python/frontend/deck/util.js +4 -0
  236. package/python/frontend/deck/visuals.js +190 -0
  237. package/python/frontend/deck.css +855 -0
  238. package/python/frontend/deck.html +686 -0
  239. package/python/frontend/deck_v2.html +341 -0
  240. package/python/frontend/planet/index.html +266 -0
  241. package/python/frontend/planet/planet.css +579 -0
  242. package/python/frontend/planet/planet.js +1436 -0
  243. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  244. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  245. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  246. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  247. package/python/frontend/v3/index.html +15 -0
  248. package/python/frontend/widget_runtime.js +252 -0
  249. package/python/frontend/widgets.js +639 -0
  250. package/python/frontend_src/index.html +14 -0
  251. package/python/frontend_src/package-lock.json +1376 -0
  252. package/python/frontend_src/package.json +19 -0
  253. package/python/frontend_src/src/App.vue +58 -0
  254. package/python/frontend_src/src/api.js +82 -0
  255. package/python/frontend_src/src/components/ChatDock.vue +365 -0
  256. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  257. package/python/frontend_src/src/components/DocViewer.vue +162 -0
  258. package/python/frontend_src/src/components/Inspector.vue +235 -0
  259. package/python/frontend_src/src/components/LeftRail.vue +415 -0
  260. package/python/frontend_src/src/components/MenuPopover.vue +128 -0
  261. package/python/frontend_src/src/components/MessageStream.vue +389 -0
  262. package/python/frontend_src/src/components/ModelPicker.vue +350 -0
  263. package/python/frontend_src/src/components/QuickIngest.vue +112 -0
  264. package/python/frontend_src/src/components/Stage.vue +272 -0
  265. package/python/frontend_src/src/components/ToolMenu.vue +141 -0
  266. package/python/frontend_src/src/components/TopBar.vue +118 -0
  267. package/python/frontend_src/src/components/panels/ActivityPanel.vue +82 -0
  268. package/python/frontend_src/src/components/panels/AgentsPanel.vue +83 -0
  269. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +425 -0
  270. package/python/frontend_src/src/components/panels/MemoryPanel.vue +117 -0
  271. package/python/frontend_src/src/components/panels/PanelBox.vue +50 -0
  272. package/python/frontend_src/src/components/panels/ReviewPanel.vue +292 -0
  273. package/python/frontend_src/src/components/panels/SettingsPanel.vue +358 -0
  274. package/python/frontend_src/src/components/panels/TasksPanel.vue +73 -0
  275. package/python/frontend_src/src/components/panels/ToolsPanel.vue +746 -0
  276. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  277. package/python/frontend_src/src/lib/fmt.js +44 -0
  278. package/python/frontend_src/src/lib/i18n.js +53 -0
  279. package/python/frontend_src/src/lib/locales/en.js +173 -0
  280. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  281. package/python/frontend_src/src/lib/md.js +190 -0
  282. package/python/frontend_src/src/main.js +9 -0
  283. package/python/frontend_src/src/stores/app.js +91 -0
  284. package/python/frontend_src/src/stores/chat.js +450 -0
  285. package/python/frontend_src/src/stores/model.js +96 -0
  286. package/python/frontend_src/src/stores/panels.js +289 -0
  287. package/python/frontend_src/src/stores/profile.js +31 -0
  288. package/python/frontend_src/src/stores/system.js +25 -0
  289. package/python/frontend_src/src/styles/base.css +138 -0
  290. package/python/frontend_src/src/styles/panel.css +117 -0
  291. package/python/frontend_src/src/styles/tokens.css +78 -0
  292. package/python/frontend_src/vite.config.js +39 -0
  293. package/python/pyproject.toml +268 -0
  294. package/python/requirements.txt +311 -0
  295. package/python/skills/README.md +123 -0
  296. package/python/skills/execution-contract/SKILL.md +30 -0
  297. package/python/skills/general-assistant/SKILL.md +34 -0
  298. package/python/skills/planner/SKILL.md +33 -0
  299. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  300. package/python/skills/skill-composition/SKILL.md +31 -0
  301. package/python/skills/skill-guard/SKILL.md +67 -0
  302. package/python/skills/skill-memory/SKILL.md +50 -0
  303. package/python/skills/skill-router/SKILL.md +35 -0
  304. package/python/tests/conftest.py +54 -0
  305. package/python/tests/run_all.py +200 -0
  306. package/python/tests/test_agent_lifecycle.py +326 -0
  307. package/python/tests/test_ahs_p2.py +393 -0
  308. package/python/tests/test_anthropic_tools.py +192 -0
  309. package/python/tests/test_api.py +97 -0
  310. package/python/tests/test_api_auth.py +79 -0
  311. package/python/tests/test_api_host_guard.py +69 -0
  312. package/python/tests/test_attachments.py +151 -0
  313. package/python/tests/test_audit_chain.py +116 -0
  314. package/python/tests/test_audit_sink.py +130 -0
  315. package/python/tests/test_browser.py +149 -0
  316. package/python/tests/test_browser_request_guard.py +180 -0
  317. package/python/tests/test_capabilities.py +152 -0
  318. package/python/tests/test_channel_signing.py +185 -0
  319. package/python/tests/test_channel_trust.py +109 -0
  320. package/python/tests/test_channels.py +174 -0
  321. package/python/tests/test_cli.py +46 -0
  322. package/python/tests/test_compaction_toolpair.py +119 -0
  323. package/python/tests/test_config_layering.py +61 -0
  324. package/python/tests/test_confirm_nonce.py +186 -0
  325. package/python/tests/test_context_compaction_wiring.py +124 -0
  326. package/python/tests/test_dispatch.py +89 -0
  327. package/python/tests/test_domain_evals.py +334 -0
  328. package/python/tests/test_domain_import.py +72 -0
  329. package/python/tests/test_domain_reasoners.py +134 -0
  330. package/python/tests/test_e2e.py +161 -0
  331. package/python/tests/test_emb_cache.py +97 -0
  332. package/python/tests/test_embedding_singleton.py +87 -0
  333. package/python/tests/test_envfile.py +83 -0
  334. package/python/tests/test_evals_smoke.py +90 -0
  335. package/python/tests/test_event_spine.py +174 -0
  336. package/python/tests/test_evidence_semantic.py +88 -0
  337. package/python/tests/test_evidence_sse.py +94 -0
  338. package/python/tests/test_file_ingest.py +135 -0
  339. package/python/tests/test_finance.py +144 -0
  340. package/python/tests/test_finance_ledger.py +245 -0
  341. package/python/tests/test_finance_persist.py +174 -0
  342. package/python/tests/test_gateway.py +125 -0
  343. package/python/tests/test_governance.py +421 -0
  344. package/python/tests/test_governance_audit.py +324 -0
  345. package/python/tests/test_governance_middleware.py +137 -0
  346. package/python/tests/test_hkc.py +268 -0
  347. package/python/tests/test_hkc_index_build_manager.py +306 -0
  348. package/python/tests/test_hkc_manifest_reuse.py +294 -0
  349. package/python/tests/test_hkc_search_event_sync.py +482 -0
  350. package/python/tests/test_import_path_guard.py +155 -0
  351. package/python/tests/test_injection_guard.py +193 -0
  352. package/python/tests/test_integration.py +117 -0
  353. package/python/tests/test_itinerary.py +100 -0
  354. package/python/tests/test_kee_semantic.py +84 -0
  355. package/python/tests/test_kernel.py +212 -0
  356. package/python/tests/test_learning.py +101 -0
  357. package/python/tests/test_llm_admin.py +145 -0
  358. package/python/tests/test_llm_planner.py +344 -0
  359. package/python/tests/test_markdown_skills.py +62 -0
  360. package/python/tests/test_markdown_skills_openclaw.py +66 -0
  361. package/python/tests/test_mcp_connectors.py +97 -0
  362. package/python/tests/test_mcp_guardian_discovery.py +87 -0
  363. package/python/tests/test_mcp_no_memory.py +43 -0
  364. package/python/tests/test_mcp_policy_gate.py +76 -0
  365. package/python/tests/test_mcp_server.py +92 -0
  366. package/python/tests/test_memory_recall.py +92 -0
  367. package/python/tests/test_metrics.py +58 -0
  368. package/python/tests/test_multi_chain.py +90 -0
  369. package/python/tests/test_multimodal.py +122 -0
  370. package/python/tests/test_negotiation.py +136 -0
  371. package/python/tests/test_orchestration.py +109 -0
  372. package/python/tests/test_orchestration_decoupled.py +103 -0
  373. package/python/tests/test_p3_p5_hardening.py +324 -0
  374. package/python/tests/test_parallel_dispatch.py +207 -0
  375. package/python/tests/test_pipeline.py +492 -0
  376. package/python/tests/test_plan_visible.py +86 -0
  377. package/python/tests/test_planner.py +88 -0
  378. package/python/tests/test_policy_gate.py +108 -0
  379. package/python/tests/test_profile.py +67 -0
  380. package/python/tests/test_readme_claims.py +163 -0
  381. package/python/tests/test_reasoner.py +170 -0
  382. package/python/tests/test_release_check.py +252 -0
  383. package/python/tests/test_resume_checkpoint.py +148 -0
  384. package/python/tests/test_routing.py +85 -0
  385. package/python/tests/test_scheduler.py +138 -0
  386. package/python/tests/test_search.py +194 -0
  387. package/python/tests/test_secrets.py +153 -0
  388. package/python/tests/test_semantic.py +69 -0
  389. package/python/tests/test_sessions.py +90 -0
  390. package/python/tests/test_settings.py +265 -0
  391. package/python/tests/test_shadow_learning.py +290 -0
  392. package/python/tests/test_shared_write.py +200 -0
  393. package/python/tests/test_skill_taint_gate.py +145 -0
  394. package/python/tests/test_skills.py +117 -0
  395. package/python/tests/test_skills_fetch.py +282 -0
  396. package/python/tests/test_stats_and_events.py +162 -0
  397. package/python/tests/test_streaming.py +55 -0
  398. package/python/tests/test_supervisor_pipeline.py +176 -0
  399. package/python/tests/test_supervisor_triage.py +102 -0
  400. package/python/tests/test_taint.py +112 -0
  401. package/python/tests/test_task_stream.py +74 -0
  402. package/python/tests/test_thinking_sse.py +66 -0
  403. package/python/tests/test_thinking_stream.py +76 -0
  404. package/python/tests/test_tool_center.py +113 -0
  405. package/python/tests/test_tool_parallel.py +113 -0
  406. package/python/tests/test_tool_params.py +99 -0
  407. package/python/tests/test_tool_use.py +229 -0
  408. package/python/tests/test_toplevel_concurrency.py +116 -0
  409. package/python/tests/test_tracing.py +256 -0
  410. package/python/tests/test_truth_crystallizer.py +125 -0
  411. package/python/tests/test_truth_flow.py +88 -0
  412. package/python/tests/test_update_check.py +95 -0
  413. package/python/tests/test_url_policy.py +53 -0
  414. package/python/tests/test_verify_evidence.py +212 -0
  415. package/python/tests/test_version_sync.py +100 -0
  416. package/python/tests/test_web_trigger.py +82 -0
  417. package/python/tools/check_faiss_type.py +30 -0
  418. package/python/tools/check_log.ps1 +38 -0
  419. package/python/tools/ivf_status.py +72 -0
  420. package/python/tools/mypy_baseline.txt +13 -0
  421. package/python/tools/mypy_ratchet.py +143 -0
  422. package/python/tools/pack.py +173 -0
  423. package/python/tools/release_check.py +349 -0
  424. package/python/tools/restart_foxclaw.ps1 +71 -0
  425. package/python/tools/shared_write_audit.py +190 -0
  426. package/python/tools/size_baseline.txt +28 -0
  427. package/python/tools/size_check.py +102 -0
  428. package/python/tools/strip_comments.py +228 -0
  429. package/python/tools/verify_env.bat +2 -0
  430. package/python/tools/verify_env.py +25 -0
  431. package/python/vendor/HMR_LICENSE +37 -0
  432. package/python/vendor/HMR_README.md +65 -0
  433. package/python/vendor/hkc/LICENSE +37 -0
  434. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  435. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  436. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  437. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  438. package/python/vendor/hkc/hkc_api/container.py +416 -0
  439. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  440. package/python/vendor/hkc/hkc_api/main.py +157 -0
  441. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  442. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  443. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  444. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  445. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  446. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  447. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  448. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  449. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  450. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  451. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  452. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  453. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  454. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  455. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  456. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  457. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  458. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  459. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  460. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  461. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  462. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  463. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  464. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  465. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  466. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  467. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  468. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  469. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  470. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  471. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  472. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  473. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  474. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  475. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  476. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  477. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  478. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  479. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  480. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  481. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  482. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  483. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  484. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  485. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  486. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  487. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  488. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  489. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  490. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  491. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  492. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  493. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  494. package/python/vendor/hmr/__init__.py +0 -0
  495. package/python/vendor/hmr/core/__init__.py +0 -0
  496. package/python/vendor/hmr/core/hmr.py +684 -0
  497. package/python/vendor/hmr/core/models.py +310 -0
  498. package/python/vendor/hmr/engines/__init__.py +0 -0
  499. package/python/vendor/hmr/engines/evolution.py +710 -0
  500. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  501. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  502. package/python/vendor/hmr/engines/policy.py +625 -0
  503. package/python/vendor/hmr/engines/recall.py +260 -0
  504. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  505. package/python/vendor/hmr/engines/scheduler.py +323 -0
  506. package/python/vendor/hmr/engines/semantic.py +150 -0
  507. package/python/vendor/hmr/engines/temporal.py +261 -0
  508. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  509. package/python/vendor/hmr/graph/__init__.py +0 -0
  510. package/python/vendor/hmr/graph/cwg.py +338 -0
  511. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  512. package/python/vendor/hmr/storage/__init__.py +0 -0
  513. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  514. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,329 @@
1
+ """
2
+ hkc-kde / packager.py
3
+ KU 打包器:把 ExtractionResult 转成正式 KU,写入 GraphStore,触发事件。
4
+
5
+ 职责(遵循 HKC Principle 07: Single Knowledge Authority):
6
+ 1. 分配 KU ID
7
+ 2. 同批 Evidence 关联 Claim(SUPPORTS,仅批内组装,非知识身份判断)
8
+ 3. 批量写入 GraphStore
9
+ 4. 发布 knowledge.created 事件
10
+
11
+ KDE = Producer。**不做**知识身份判断 / 合并 / 冲突 / 置信度演化 ——
12
+ 这些是 KEE 的专属职责。知识级去重/合并/置信度演化全部由 KEE 的
13
+ KnowledgeDeduplicator(订阅 knowledge.created)接管;KDE 只负责产出 KU 并发事件。
14
+ """
15
+ from __future__ import annotations
16
+ import logging
17
+ import threading
18
+
19
+ from .models import ExtractionResult
20
+ from .relation_disc import RelationshipDiscovery
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # 合集(文件夹)Evidence 的标记 tag,与普通文件 Evidence 区分
25
+ COLLECTION_TAG = "_collection"
26
+ # 文件 Evidence 记录所属合集 id 的 tag 前缀
27
+ INCOL_TAG_PREFIX = "_incol:"
28
+
29
+
30
+ class KUPackager:
31
+
32
+ def __init__(self, graph_store, event_bus, id_gen):
33
+ self.store = graph_store
34
+ self.bus = event_bus
35
+ self.id_gen = id_gen
36
+ self.rel_disc = RelationshipDiscovery()
37
+ # 合集 Evidence 的 find-or-create 必须原子(并发摄入同一文件夹的多个文件时,
38
+ # 否则会建出多个同名合集节点)。锁序:本组件锁 → graph_store 锁,全系统一致。
39
+ self._collection_lock = threading.Lock()
40
+
41
+ def package(
42
+ self,
43
+ result: ExtractionResult,
44
+ source_title: str = "",
45
+ source_year: int = 0,
46
+ source_id: str = "",
47
+ collection: str = "",
48
+ content_hash: str = "",
49
+ ) -> list:
50
+ """
51
+ 主入口:把 ExtractionResult 转成 KU 列表并写入系统。
52
+ 返回写入的 KU 列表。
53
+
54
+ collection: 文件夹名(批量摄入时传)。非空时:
55
+ - 确保存在唯一的"合集 Evidence"(name=文件夹名,一级来源节点);
56
+ - 本文档的文件 Evidence(二级,name=文件名)建 PART_OF 关系挂到合集,
57
+ 并打 _incol:<合集id> tag,供 KEE 按合集(而非文件)判同源。
58
+ """
59
+ from hkc_core.models.ku import (
60
+ EntityKU, ConceptKU, FactKU, ClaimKU, EvidenceKU, Relation
61
+ )
62
+ from hkc_core.models.enums import (
63
+ EntityType, ClaimStatus
64
+ )
65
+
66
+ new_kus = []
67
+ # 需要在 Evidence 就绪后回连来源的知识 KU(③ KU←Evidence 链闭环)
68
+ # 分两类:entity 用 MENTIONS 关系;concept/fact 填入 sources 字段。
69
+ entities_to_link = [] # EntityKU 列表 → Evidence --MENTIONS--> Entity
70
+ sourced_to_link = [] # Concept/Fact KU 列表 → sources += evd_ids
71
+
72
+ # ── 1. Entity ────────────────────────────────────────
73
+ # 知识身份判断/去重/别名合并由 KEE 负责(Principle 07)。KDE 一律产出新 KU;
74
+ # 若与已有 Entity 同一身份,KEE 会合并并 redirect_relations,MENTIONS 不丢。
75
+ for item in result.all_entities():
76
+ try:
77
+ etype = EntityType(item.get("type", "Person"))
78
+ except ValueError:
79
+ etype = EntityType.PERSON
80
+
81
+ ku = EntityKU(
82
+ ku_id = self.id_gen.next("Entity"),
83
+ name = item["name"],
84
+ domain = "",
85
+ entity_type = etype,
86
+ aliases = item.get("aliases", []),
87
+ source_text = item.get("_src", ""), # 原文段落
88
+ )
89
+ new_kus.append(ku)
90
+ entities_to_link.append(ku)
91
+
92
+ # ── 2. Concept ───────────────────────────────────────
93
+ # 去重由 KEE 负责;重复 Concept 会被 KEE 合并(含 sources 并入)后软删除。
94
+ for item in result.all_concepts():
95
+ ku = ConceptKU(
96
+ ku_id = self.id_gen.next("Concept"),
97
+ name = item["name"],
98
+ domain = item.get("domain", ""),
99
+ definition = item.get("definition", ""),
100
+ summary = item.get("definition", ""),
101
+ source_text = item.get("_src", ""), # 原文段落
102
+ )
103
+ new_kus.append(ku)
104
+ sourced_to_link.append(ku)
105
+
106
+ # ── 3. Fact ──────────────────────────────────────────
107
+ for item in result.all_facts():
108
+ stmt = item.get("statement", "").strip()
109
+ if not stmt:
110
+ continue
111
+ # 去重由 KEE 负责(同 stmt 的 Fact 会被 KEE 合并后软删除)。
112
+ ku = FactKU(
113
+ ku_id = self.id_gen.next("Fact"),
114
+ name = stmt[:80], # 截断作为 name
115
+ statement = stmt,
116
+ source_ref = "",
117
+ summary = stmt,
118
+ source_text = item.get("_src", ""), # 原文段落
119
+ )
120
+ new_kus.append(ku)
121
+ sourced_to_link.append(ku)
122
+
123
+ # ── 4. Evidence(整份文档=一个来源=一个 Evidence)──
124
+ # 之前"每个 chunk 一个 Evidence"会让一本书切出成百上千个同名 Evidence,
125
+ # 依赖 KEE 去重兜底,一旦漏兜(跨摄入/并发/重启)就爆:满屏同名标签 + 线条爆炸。
126
+ # 改为整份文档只建一个 Evidence 作为该书的来源锚点,所有 KU 都连它。
127
+ # 方向二:按内容哈希 find-or-create —— 相同内容复用已有 Evidence,
128
+ # 作为入口哈希查重(KDE.ingest_file)在写入层的兜底(如 ingest_text 等未拦截路径)。
129
+ evd_kus = []
130
+ evd_reused = False
131
+ if any(any([raw.facts, raw.claims, raw.entities, raw.concepts]) for raw in result.items):
132
+ src = source_id or source_title or result.source
133
+ ku = self._find_evidence_by_hash(content_hash) if content_hash else None
134
+ if ku is not None:
135
+ evd_reused = True
136
+ else:
137
+ ku = EvidenceKU(
138
+ ku_id = self.id_gen.next("Evidence"),
139
+ name = source_title or source_id or result.source,
140
+ source = src, # 精确来源标识(原始文件名等)
141
+ source_type = self._guess_source_type(src),
142
+ year = source_year,
143
+ summary = f"来自 {source_title or source_id or result.source}",
144
+ domain = "",
145
+ )
146
+ if content_hash:
147
+ ku.extra["content_hash"] = content_hash
148
+ evd_kus.append(ku)
149
+ if not evd_reused:
150
+ new_kus.append(ku)
151
+
152
+ # 合集(文件夹)归属:确保合集节点存在,给文件 Evidence 打 _incol tag(须在写入前)
153
+ collection_ku = None
154
+ if collection and evd_kus:
155
+ collection_ku = self._ensure_collection_evidence(collection)
156
+ for e in evd_kus:
157
+ tag = INCOL_TAG_PREFIX + collection_ku.ku_id
158
+ if tag not in e.tags:
159
+ e.tags.append(tag)
160
+
161
+ # evd_kus 必须先写入,Claim.supports 才不会悬空
162
+ if evd_kus:
163
+ self.store.batch_write(evd_kus)
164
+
165
+ # 文件 Evidence --PART_OF--> 合集(写入后建关系,供星球/wiki 下钻)
166
+ if collection_ku is not None:
167
+ for e in evd_kus:
168
+ try:
169
+ self.store.add_relation(Relation(
170
+ rel_id = self.id_gen.next("Relation"),
171
+ from_ku = e.ku_id,
172
+ to_ku = collection_ku.ku_id,
173
+ rel_type = "PART_OF",
174
+ ))
175
+ except Exception as ex:
176
+ logger.warning("PART_OF 关系写入失败: %s", ex)
177
+
178
+ # ③ KU←Evidence 链闭环:把本批 Evidence 回连到 Entity/Concept/Fact。
179
+ # 这是 KDE 作为 Producer 记录"知识产地"的本职(非知识身份判断,不违反 Principle 07)。
180
+ evd_ids_all = [e.ku_id for e in evd_kus]
181
+ if evd_ids_all:
182
+ # Concept/Fact:把 Evidence id 填入 sources 字段(EVD_xxx),随第 6 步写入持久化
183
+ for ku in sourced_to_link:
184
+ for eid in evd_ids_all:
185
+ if eid not in ku.sources:
186
+ ku.sources.append(eid)
187
+ # Entity 的 MENTIONS 关系在第 6 步 KU 写入后建立(见下方)
188
+
189
+ # ── 5. Claim ─────────────────────────────────────────
190
+ claim_kus = []
191
+ for item in result.all_claims():
192
+ stmt = item.get("statement", "").strip()
193
+ if not stmt:
194
+ continue
195
+ # 同一 Claim 的 supports 合并 + 置信度演化属于 KEE 的知识演化职责
196
+ # (Principle 07)。KDE 只产出带本批 Evidence 的新 Claim;若与已有 Claim
197
+ # 同一身份,KEE 的 KnowledgeDeduplicator 会并入 supports、演化置信度后软删重复。
198
+ ku = ClaimKU(
199
+ ku_id = self.id_gen.next("Claim"),
200
+ name = stmt[:80],
201
+ statement = stmt,
202
+ confidence = item.get("confidence", 0.5),
203
+ domain = item.get("domain", ""),
204
+ claim_status = ClaimStatus.PENDING,
205
+ supports = [e.ku_id for e in evd_kus],
206
+ summary = stmt,
207
+ source_text = item.get("_src", ""), # 原文段落
208
+ )
209
+ claim_kus.append(ku)
210
+ new_kus.append(ku)
211
+
212
+ if not new_kus:
213
+ logger.info("ExtractionResult 为空,无 KU 写入")
214
+ return []
215
+
216
+ # ── 6. 批量写入 GraphStore(evd_kus 已提前写入,此处排除)────
217
+ evd_ids = {e.ku_id for e in evd_kus}
218
+ kus_to_write = [k for k in new_kus if k.ku_id not in evd_ids]
219
+ if kus_to_write:
220
+ self.store.batch_write(kus_to_write)
221
+ logger.info("写入 %d 个 KU (doc=%s)", len(new_kus), result.doc_id)
222
+
223
+ # ③ Entity 的来源链:Evidence --MENTIONS--> Entity(在两端 KU 都写入后建立)
224
+ if evd_ids_all and entities_to_link:
225
+ from hkc_core.models.ku import Relation
226
+ for ent in entities_to_link:
227
+ # 已有的 MENTIONS 来源(去重,避免重复来源重复建关系)
228
+ existing_srcs = {
229
+ r.from_ku for r in self.store.get_relations(ent.ku_id, direction="in")
230
+ if r.rel_type == "MENTIONS"
231
+ }
232
+ for evd in evd_kus:
233
+ if evd.ku_id in existing_srcs:
234
+ continue
235
+ try:
236
+ self.store.add_relation(Relation(
237
+ rel_id = self.id_gen.next("Relation"),
238
+ from_ku = evd.ku_id,
239
+ to_ku = ent.ku_id,
240
+ rel_type = "MENTIONS",
241
+ source = evd.ku_id,
242
+ ))
243
+ except Exception as e:
244
+ logger.warning("Entity 来源关系写入失败: %s", e)
245
+
246
+ # ── 7. 关系发现 ───────────────────────────────────────
247
+ existing_kus = self._query_all_kus(limit=1000)
248
+ rels = self.rel_disc.discover(new_kus, existing_kus, self.id_gen)
249
+ # 批量单次落盘(逐条 add_relation 各自 commit 会在大文档时 fsync 风暴致卡死)。
250
+ # 批量失败(如个别关系外键违约)会整批回滚 → 回退逐条,只跳过坏的那条,
251
+ # 保住其余关系(恢复旧实现的逐条容错,同时保留正常路径的批量快速)。
252
+ try:
253
+ self.store.batch_add_relations(rels)
254
+ except Exception as e:
255
+ logger.warning("批量关系写入失败,回退逐条: %s", e)
256
+ for rel in rels:
257
+ try:
258
+ self.store.add_relation(rel)
259
+ except Exception as e2:
260
+ logger.warning("关系写入失败(跳过): %s", e2)
261
+
262
+ # ── 8. 发布事件 ───────────────────────────────────────
263
+ from hkc_kep.event_bus import KEPEvents
264
+ self.bus.publish({
265
+ "event": KEPEvents.KNOWLEDGE_CREATED,
266
+ "source": "KDE",
267
+ "payload": {
268
+ "doc_id": result.doc_id,
269
+ "ku_ids": [ku.ku_id for ku in new_kus],
270
+ "counts": {
271
+ "entities": len([k for k in new_kus if k.ku_type.value == "Entity"]),
272
+ "concepts": len([k for k in new_kus if k.ku_type.value == "Concept"]),
273
+ "facts": len([k for k in new_kus if k.ku_type.value == "Fact"]),
274
+ "claims": len([k for k in new_kus if k.ku_type.value == "Claim"]),
275
+ "evidence": len([k for k in new_kus if k.ku_type.value == "Evidence"]),
276
+ },
277
+ }
278
+ })
279
+
280
+ return new_kus
281
+
282
+ def _query_all_kus(self, limit: int = 1000) -> list:
283
+ """全量查询所有非删除 KU,供关系发现使用。"""
284
+ return self.store.query_all(limit=limit)
285
+
286
+ def _find_evidence_by_hash(self, content_hash: str):
287
+ """按内容 SHA-256 指纹查找已入库的文件 Evidence(find-or-create 的「find」)。"""
288
+ if not content_hash:
289
+ return None
290
+ from hkc_core.models.enums import KUType
291
+ for ku in self.store.query_by_type(
292
+ KUType.EVIDENCE, status="active", limit=10_000_000
293
+ ):
294
+ if (getattr(ku, "extra", None) or {}).get("content_hash") == content_hash:
295
+ return ku
296
+ return None
297
+
298
+ def _ensure_collection_evidence(self, collection: str):
299
+ """找到或新建"合集 Evidence"(文件夹一级节点,name=文件夹名)。按名去重、
300
+ 每文件夹一个;并发摄入同文件夹多文件时用锁保证原子(否则会建出多个同名合集)。"""
301
+ from hkc_core.models.ku import EvidenceKU
302
+ with self._collection_lock:
303
+ for ku in self.store.search_by_name(collection, fuzzy=False):
304
+ if (getattr(getattr(ku, "ku_type", None), "value", "") == "Evidence"
305
+ and COLLECTION_TAG in (getattr(ku, "tags", None) or [])):
306
+ return ku # 已有该文件夹的合集节点,复用
307
+ ku = EvidenceKU(
308
+ ku_id = self.id_gen.next("Evidence"),
309
+ name = collection,
310
+ source = "collection:" + collection,
311
+ source_type = self._guess_source_type("collection:" + collection),
312
+ summary = f"合集 · {collection}",
313
+ domain = "",
314
+ )
315
+ ku.tags = [COLLECTION_TAG]
316
+ self.store.batch_write([ku])
317
+ logger.info("新建合集节点: %s (%s)", collection, ku.ku_id)
318
+ return ku
319
+
320
+ def _guess_source_type(self, source: str) -> "SourceType":
321
+ from hkc_core.models.enums import SourceType
322
+ s = source.lower()
323
+ if s.startswith("http"):
324
+ return SourceType.WEB
325
+ if s.endswith(".pdf"):
326
+ return SourceType.BOOK
327
+ if s.endswith((".md", ".txt", ".docx")):
328
+ return SourceType.ARTICLE
329
+ return SourceType.BOOK
@@ -0,0 +1,151 @@
1
+ """
2
+ hkc-kde / relation_disc.py
3
+ 关系发现器:分析 KU 列表,自动建立 Relation。
4
+
5
+ v1 规则(确定性,不依赖 LLM):
6
+ 1. Entity → Concept : CREATED_BY(名字出现在 Concept.definition 里)
7
+ 2. Evidence → Claim : SUPPORTS(来自同一 Chunk 的 Claim)
8
+ 3. Concept → Concept : DERIVED_FROM(名字包含关系,如"量化投资"包含"投资")
9
+ 4. 跨文档去重 : 由 KEE 的 KnowledgeDeduplicator 负责(Principle 07),不在此处理
10
+ """
11
+ from __future__ import annotations
12
+ import logging
13
+ from typing import TYPE_CHECKING
14
+
15
+ if TYPE_CHECKING:
16
+ from hkc_core.models.ku import KU, Relation
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class RelationshipDiscovery:
22
+
23
+ def discover(
24
+ self,
25
+ new_kus: list["KU"],
26
+ existing_kus: list["KU"],
27
+ id_gen,
28
+ ) -> list["Relation"]:
29
+ """
30
+ 分析新写入的 KU,建立与已有 KU 和内部 KU 的关系。
31
+ 返回 Relation 列表(未写入 GraphStore,由 Packager 批量写入)。
32
+ """
33
+
34
+ all_kus = existing_kus + new_kus
35
+ rels: list[Relation] = []
36
+
37
+ # 1. Entity → Concept (CREATED_BY)
38
+ rels += self._entity_concept(new_kus, all_kus, id_gen)
39
+
40
+ # 2. Evidence → Claim (SUPPORTS):来自同一文档的 Evidence 和 Claim
41
+ rels += self._evidence_claim(new_kus, id_gen)
42
+
43
+ # 3. Concept → Concept (DERIVED_FROM)
44
+ rels += self._concept_hierarchy(new_kus, all_kus, id_gen)
45
+
46
+ # 去重(同一对 from/to/type 只保留一条)
47
+ seen = set()
48
+ dedup = []
49
+ for r in rels:
50
+ key = (r.from_ku, r.to_ku, r.rel_type)
51
+ if key not in seen:
52
+ seen.add(key)
53
+ dedup.append(r)
54
+
55
+ # 去重后一次性批量预留 rel_id(单事务),子方法里先留空 rel_id。
56
+ # 逐条 id_gen.next() 每次 commit 会在大文档时造成 fsync 风暴(摄入卡死)。
57
+ ids = id_gen.next_batch("Relation", len(dedup))
58
+ for r, rid in zip(dedup, ids):
59
+ r.rel_id = rid
60
+
61
+ return dedup
62
+
63
+ # ── 规则 1:Entity → Concept ─────────────────────────────
64
+
65
+ def _entity_concept(
66
+ self,
67
+ new_kus: list["KU"],
68
+ all_kus: list["KU"],
69
+ id_gen,
70
+ ) -> list["Relation"]:
71
+ from hkc_core.models.ku import Relation
72
+
73
+ entities = [ku for ku in new_kus if ku.ku_type.value == "Entity"]
74
+ concepts = [ku for ku in all_kus if ku.ku_type.value == "Concept"]
75
+ rels = []
76
+
77
+ for entity in entities:
78
+ name_lower = entity.name.lower()
79
+ for concept in concepts:
80
+ defn = getattr(concept, 'definition', '').lower()
81
+ c_sum = concept.summary.lower()
82
+ # 实体名出现在概念定义或摘要里 → CREATED_BY
83
+ if name_lower and (name_lower in defn or name_lower in c_sum):
84
+ rels.append(Relation(
85
+ rel_id = "", # 由 discover 去重后批量分配
86
+ from_ku = entity.ku_id,
87
+ to_ku = concept.ku_id,
88
+ rel_type = "CREATED_BY",
89
+ weight = 0.8,
90
+ ))
91
+ return rels
92
+
93
+ # ── 规则 2:Evidence → Claim (SUPPORTS) ──────────────────
94
+
95
+ def _evidence_claim(
96
+ self,
97
+ new_kus: list["KU"],
98
+ id_gen,
99
+ ) -> list["Relation"]:
100
+ from hkc_core.models.ku import Relation
101
+
102
+ evidences = [ku for ku in new_kus if ku.ku_type.value == "Evidence"]
103
+ claims = [ku for ku in new_kus if ku.ku_type.value == "Claim"]
104
+ rels = []
105
+
106
+ # 同批次(同文档)的 Evidence 默认支持同批次的 Claim
107
+ # 权重 0.6(比人工标注 1.0 低,后期可通过 KEE 调整)
108
+ for evd in evidences:
109
+ for clm in claims:
110
+ rels.append(Relation(
111
+ rel_id = "", # 由 discover 去重后批量分配
112
+ from_ku = evd.ku_id,
113
+ to_ku = clm.ku_id,
114
+ rel_type = "SUPPORTS",
115
+ weight = 0.6,
116
+ source = evd.ku_id,
117
+ ))
118
+ return rels
119
+
120
+ # ── 规则 3:Concept → Concept (DERIVED_FROM) ─────────────
121
+
122
+ def _concept_hierarchy(
123
+ self,
124
+ new_kus: list["KU"],
125
+ all_kus: list["KU"],
126
+ id_gen,
127
+ ) -> list["Relation"]:
128
+ from hkc_core.models.ku import Relation
129
+
130
+ new_concepts = [ku for ku in new_kus if ku.ku_type.value == "Concept"]
131
+ all_concepts = [ku for ku in all_kus if ku.ku_type.value == "Concept"]
132
+ rels = []
133
+
134
+ for new_c in new_concepts:
135
+ new_name = new_c.name.lower()
136
+ for old_c in all_concepts:
137
+ if old_c.ku_id == new_c.ku_id:
138
+ continue
139
+ old_name = old_c.name.lower()
140
+ # 新概念名包含旧概念名(且旧名 >= 2 字)→ DERIVED_FROM
141
+ if (len(old_name) >= 2
142
+ and old_name in new_name
143
+ and old_name != new_name):
144
+ rels.append(Relation(
145
+ rel_id = "", # 由 discover 去重后批量分配
146
+ from_ku = new_c.ku_id,
147
+ to_ku = old_c.ku_id,
148
+ rel_type = "DERIVED_FROM",
149
+ weight = 0.7,
150
+ ))
151
+ return rels
File without changes