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,145 @@
1
+ """
2
+ hkc_crystallizer / candidate.py
3
+ 知识候选体 (KnowledgeCandidate) 与边界协议。
4
+
5
+ 这是 Memory World → Knowledge World 的边界数据结构与契约定义。
6
+
7
+ 核心理念:
8
+ Crystallizer 处理的不是"记忆",而是"任何可沉淀认知"——
9
+ 来源可以是 memory.matured、meeting.finished、research.finished、
10
+ document.ingested、agent.reflection.completed …
11
+
12
+ 因此边界数据结构叫 KnowledgeCandidate(知识候选体),不叫 Memory。
13
+ 这样 HKC 永远不会被 HMR 绑定死。
14
+
15
+ 职责边界(架构铁律):
16
+ Crystallizer is a knowledge candidate generator, not a knowledge identity resolver.
17
+ KEE is the sole authority for knowledge deduplication and evolution.
18
+
19
+ → Candidate 携带 light_fingerprint(结构哈希,仅作标签),
20
+ 但**不**参与"是否已存在、是否合并"的决策。
21
+ 那个决策(canonical fingerprint + KU 查重 + Evidence 合并)是 KEE 的天职。
22
+ """
23
+ from __future__ import annotations
24
+ import hashlib
25
+ import re
26
+ from dataclasses import dataclass, field
27
+ from datetime import datetime, timezone
28
+ from typing import Protocol, Callable, Any, Optional, runtime_checkable
29
+
30
+
31
+ # ── 结构化 Evidence ──────────────────────────────────────────
32
+
33
+ @dataclass
34
+ class CandidateEvidence:
35
+ """
36
+ 候选体携带的结构化来源证据。
37
+
38
+ 取代旧版的 source="hmr_memory:agent:finance:mem_xxx" 字符串拼接 ——
39
+ 结构化后可进图谱,形成 KU ← Evidence ← (Memory/Meeting/...) 的可追溯链。
40
+
41
+ 未来查询"为什么系统认为用户长期关注 Agent 架构",可顺着这条链回溯。
42
+ """
43
+ evidence_type: str # memory | meeting | research | document | reflection ...
44
+ source_id: str # 原始单元 ID(memory_id / meeting_id / ...)
45
+ agent: Optional[str] = None # 产出该认知的 Agent
46
+ confidence: float = 1.0
47
+ timestamp: str = "" # ISO8601
48
+ extra: dict = field(default_factory=dict)
49
+
50
+ def __post_init__(self):
51
+ if not self.timestamp:
52
+ self.timestamp = datetime.now(timezone.utc).isoformat()
53
+
54
+ def to_dict(self) -> dict:
55
+ return {
56
+ "evidence_type": self.evidence_type,
57
+ "source_id": self.source_id,
58
+ "agent": self.agent,
59
+ "confidence": self.confidence,
60
+ "timestamp": self.timestamp,
61
+ "extra": dict(self.extra),
62
+ }
63
+
64
+
65
+ # ── 知识候选体 ───────────────────────────────────────────────
66
+
67
+ @dataclass
68
+ class KnowledgeCandidate:
69
+ """
70
+ 进入 HKC 前的知识候选体 —— 边界转换的产物。
71
+
72
+ 由 Crystallizer 构造,经 KnowledgeIngress 送入 HKC。
73
+ 它是"事件视角"的产物;到了 KEE 才转为"知识语义视角"。
74
+ """
75
+ content: str # 待结晶的文本
76
+ title: str = ""
77
+ evidence: list[CandidateEvidence] = field(default_factory=list)
78
+ event_refs: list[str] = field(default_factory=list) # 触发事件的引用(event_id / memory_id)
79
+ domain_hint: str = "" # 领域提示(非强制)
80
+ light_fingerprint: str = "" # 结构哈希(仅标签,KEE 才算 canonical)
81
+
82
+ def __post_init__(self):
83
+ if not self.light_fingerprint:
84
+ self.light_fingerprint = self.compute_light_fingerprint(self.content)
85
+
86
+ @staticmethod
87
+ def compute_light_fingerprint(content: str) -> str:
88
+ """
89
+ 轻量结构指纹:归一化(去空白/小写)后取 sha256 前 16 位。
90
+
91
+ 这是"事件视角"的粗去重标签 —— 只能识别"字面几乎相同"。
92
+ 语义层面的同义归并(canonical fingerprint)是 KEE 的职责,不在这里做。
93
+ """
94
+ norm = re.sub(r"\s+", " ", (content or "").strip().lower())
95
+ return hashlib.sha256(norm.encode("utf-8")).hexdigest()[:16]
96
+
97
+ @property
98
+ def text_for_ingest(self) -> str:
99
+ if self.title and self.content:
100
+ return f"{self.title}\n\n{self.content}"
101
+ return self.content or self.title
102
+
103
+ def to_dict(self) -> dict:
104
+ return {
105
+ "content": self.content,
106
+ "title": self.title,
107
+ "evidence": [e.to_dict() for e in self.evidence],
108
+ "event_refs": list(self.event_refs),
109
+ "domain_hint": self.domain_hint,
110
+ "light_fingerprint": self.light_fingerprint,
111
+ }
112
+
113
+
114
+ # ── 协议:知识事件来源 ───────────────────────────────────────
115
+
116
+ @runtime_checkable
117
+ class KnowledgeEventSource(Protocol):
118
+ """
119
+ 知识事件来源协议。
120
+
121
+ 不绑定"记忆"——任何能产出"可沉淀认知"事件的系统都可实现它:
122
+ HMR(memory.matured)、会议系统(meeting.finished)、
123
+ 研究 Agent(research.finished)、文档系统(document.ingested)…
124
+
125
+ 按 event_name 订阅,handler 收到该事件的 payload(dict)。
126
+ """
127
+ def subscribe(self, event_name: str, handler: Callable[[dict], None]) -> None:
128
+ ...
129
+
130
+
131
+ # ── 协议:知识入口 ───────────────────────────────────────────
132
+
133
+ @runtime_checkable
134
+ class KnowledgeIngress(Protocol):
135
+ """
136
+ HKC 对外的知识入口契约。
137
+
138
+ Crystallizer 只认这个协议,不知道 KDE/KEE/ACE 的存在。
139
+ 具体实现(HKCIngress)内部再去调 kde.ingest_text 等 ——
140
+ 这样 KDE 重构不会波及 Crystallizer。
141
+
142
+ 返回:本次摄入产出的 KU 数量(或可转为数量的结果)。
143
+ """
144
+ def ingest(self, candidate: KnowledgeCandidate) -> Any:
145
+ ...
@@ -0,0 +1,179 @@
1
+ """
2
+ hkc_crystallizer / crystallizer.py
3
+ 知识结晶器 (KnowledgeCrystallizer) —— 边界转换器 / Knowledge Candidate Generator
4
+
5
+ 定位:Memory World → Knowledge World 之间的边界转换器(Boundary Translator)。
6
+ 属于 HKC Integrations Layer,不属于 HKC Core。
7
+
8
+ 架构铁律:
9
+ Crystallizer is a knowledge candidate generator, not a knowledge identity resolver.
10
+ KEE is the sole authority for knowledge deduplication and evolution.
11
+
12
+ 职责严格锁死为四件:
13
+ 1. 接收事件 (通过 KnowledgeEventSource 订阅)
14
+ 2. 事件筛选 (合法性 / 类型判断 / 可选二次复核)
15
+ 3. 事件级去重 (event_id / source_id,避免重复消费同一事件)
16
+ 4. 构造候选体 (KnowledgeCandidate,含结构化 Evidence + light fingerprint)
17
+
18
+ 绝不负责(交给 HKC Core):
19
+ - 知识抽取 → KDE
20
+ - 知识身份判断/去重 → KEE(canonical fingerprint + KU 查重 + Evidence 合并)
21
+ - 图谱更新 → KEE
22
+ - Ability 生成 → ACE
23
+
24
+ 特别地:Crystallizer **永不查询 KU 是否存在**,**永不做合并/更新决策**。
25
+ 它计算的 light_fingerprint 只是候选体的结构标签,不参与任何知识决策。
26
+ """
27
+ from __future__ import annotations
28
+ import logging
29
+ from dataclasses import dataclass
30
+ from typing import Callable, Optional
31
+
32
+ from .candidate import (
33
+ KnowledgeCandidate, KnowledgeEventSource, KnowledgeIngress,
34
+ )
35
+ from .adapters import DEFAULT_TRANSLATORS, TranslatorFn
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ @dataclass
41
+ class CrystallizeStats:
42
+ received: int = 0
43
+ crystallized: int = 0
44
+ skipped_dup: int = 0
45
+ skipped_filter: int = 0
46
+ skipped_empty: int = 0
47
+ failed: int = 0
48
+
49
+ def as_dict(self) -> dict:
50
+ return {
51
+ "received": self.received, "crystallized": self.crystallized,
52
+ "skipped_dup": self.skipped_dup, "skipped_filter": self.skipped_filter,
53
+ "skipped_empty": self.skipped_empty, "failed": self.failed,
54
+ }
55
+
56
+
57
+ class KnowledgeCrystallizer:
58
+ """
59
+ 参数:
60
+ ingress: KnowledgeIngress,HKC 对外入口(如 HKCIngress)。
61
+ Crystallizer 只认这个协议,不认 KDE/KEE/ACE。
62
+ source: KnowledgeEventSource,事件来源(如 SystemBusEventSource)。
63
+ events: 要订阅的事件名列表(默认只订阅 memory.matured)。
64
+ translators: 事件名 → 翻译函数。默认用 DEFAULT_TRANSLATORS。
65
+ candidate_filter: 可选 (KnowledgeCandidate)->bool,二次筛选(防御性复核)。
66
+ auto_start: 构造时立即订阅(默认 True)。
67
+ """
68
+
69
+ def __init__(
70
+ self,
71
+ ingress: KnowledgeIngress,
72
+ source: KnowledgeEventSource,
73
+ *,
74
+ events: Optional[list] = None,
75
+ translators: Optional[dict] = None,
76
+ candidate_filter: Optional[Callable[[KnowledgeCandidate], bool]] = None,
77
+ auto_start: bool = True,
78
+ ):
79
+ self._ingress = ingress
80
+ self._source = source
81
+ self._events = events or ["memory.matured"]
82
+ self._translators: dict[str, TranslatorFn] = dict(DEFAULT_TRANSLATORS)
83
+ if translators:
84
+ self._translators.update(translators)
85
+ self._filter = candidate_filter
86
+ self.stats = CrystallizeStats()
87
+ self._seen_refs = set()
88
+ self._started = False
89
+ if auto_start:
90
+ self.start()
91
+
92
+ def start(self) -> None:
93
+ """订阅所有配置的事件。幂等。"""
94
+ if self._started:
95
+ return
96
+ for ev in self._events:
97
+ self._source.subscribe(ev, self._make_handler(ev))
98
+ self._started = True
99
+ logger.info("KnowledgeCrystallizer 已启动,订阅事件: %s", self._events)
100
+
101
+ def _make_handler(self, event_name: str):
102
+ def _handler(payload: dict):
103
+ self._on_event(event_name, payload)
104
+ return _handler
105
+
106
+ # ── 核心:收到一个事件 ──────────────────────────────────
107
+ def _on_event(self, event_name: str, payload: dict) -> None:
108
+ self.stats.received += 1
109
+
110
+ # (2) 筛选:有无对应翻译器
111
+ translator = self._translators.get(event_name)
112
+ if translator is None:
113
+ logger.debug("无翻译器,跳过事件: %s", event_name)
114
+ self.stats.skipped_filter += 1
115
+ return
116
+
117
+ # 翻译 event → candidate
118
+ try:
119
+ candidate = translator(payload)
120
+ except Exception as e:
121
+ logger.warning("事件翻译失败 (%s): %s", event_name, e)
122
+ self.stats.skipped_filter += 1
123
+ return
124
+ if candidate is None:
125
+ self.stats.skipped_empty += 1
126
+ return
127
+
128
+ # (3) 事件级去重(基于 event_refs)
129
+ ref = self._dedup_key(candidate)
130
+ if ref and ref in self._seen_refs:
131
+ self.stats.skipped_dup += 1
132
+ return
133
+
134
+ # (2') 二次筛选(防御性复核)
135
+ if self._filter is not None:
136
+ try:
137
+ if not self._filter(candidate):
138
+ self.stats.skipped_filter += 1
139
+ return
140
+ except Exception as e:
141
+ logger.warning("二次筛选异常,保守跳过: %s", e)
142
+ self.stats.skipped_filter += 1
143
+ return
144
+
145
+ # 内容非空
146
+ if not candidate.text_for_ingest.strip():
147
+ self.stats.skipped_empty += 1
148
+ return
149
+
150
+ # (4) candidate 已构造好(含结构化 Evidence + light fingerprint)
151
+ # → 交给 KnowledgeIngress 送入 HKC。到此为止,不做知识身份判断。
152
+ try:
153
+ self._ingress.ingest(candidate)
154
+ except Exception as e:
155
+ self.stats.failed += 1
156
+ logger.error("候选体送入 HKC 失败 (refs=%s): %s",
157
+ candidate.event_refs, e)
158
+ return
159
+
160
+ # 成功后才记事件级去重(失败可重试)
161
+ if ref:
162
+ self._seen_refs.add(ref)
163
+ self.stats.crystallized += 1
164
+ logger.info("候选体已送入 HKC: refs=%s fp=%s",
165
+ candidate.event_refs, candidate.light_fingerprint)
166
+
167
+ @staticmethod
168
+ def _dedup_key(candidate: KnowledgeCandidate) -> str:
169
+ """事件级去重键:优先 event_refs,退化用 light_fingerprint。"""
170
+ if candidate.event_refs:
171
+ return "|".join(candidate.event_refs)
172
+ return candidate.light_fingerprint
173
+
174
+ # ── 运维 ──────────────────────────────────────────────
175
+ def get_stats(self) -> dict:
176
+ return self.stats.as_dict()
177
+
178
+ def reset_dedup(self) -> None:
179
+ self._seen_refs.clear()
@@ -0,0 +1,90 @@
1
+ """
2
+ hkc_crystallizer / ingress.py
3
+ HKC 知识入口适配器。
4
+
5
+ 实现 KnowledgeIngress 协议,是 Crystallizer 与 HKC 内部(KDE/KEE/ACE)
6
+ 之间的唯一耦合点。Crystallizer 只认 KnowledgeIngress,不认 KDE。
7
+
8
+ 设计意图:
9
+ KDE 的接口(ingest_text 签名)是 KDE 的内部契约,不是 HKC 的稳定对外契约。
10
+ 若 Crystallizer 直接调 ingest_text,KDE 重构就会波及 Crystallizer。
11
+ HKCIngress 把这层耦合收敛到一处:KDE 变了,只改这里。
12
+
13
+ candidate 的结构化 Evidence 在这里翻译成 HKC 能理解的来源信息。
14
+ 注:第二层"知识级去重 + Evidence 合并"由 KEE 负责,不在此处 ——
15
+ 本适配器只负责"把候选体送进 HKC",不做任何知识身份判断。
16
+ """
17
+ from __future__ import annotations
18
+ import logging
19
+ from typing import Any
20
+
21
+ from .candidate import KnowledgeCandidate, KnowledgeIngress
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class HKCIngress:
27
+ """
28
+ 把 KnowledgeCandidate 送入 HKC 的适配器。
29
+
30
+ hkc_ingest_fn: 实际的 HKC 摄入入口。可为:
31
+ - HKC SDK DirectClient / HTTPClient(有 ingest_text 方法)
32
+ - KDE 实例(有 ingest_text 方法)
33
+ - 自定义 callable(text, source=, source_title=, domain=) -> result
34
+ """
35
+
36
+ def __init__(self, hkc_ingest_fn: Any):
37
+ self._ingest = self._resolve(hkc_ingest_fn)
38
+
39
+ @staticmethod
40
+ def _resolve(fn: Any):
41
+ if callable(fn) and not hasattr(fn, "ingest_text"):
42
+ return fn
43
+ if hasattr(fn, "ingest_text"):
44
+ return fn.ingest_text
45
+ raise TypeError(
46
+ "hkc_ingest_fn 必须可调用,或带 ingest_text 方法"
47
+ "(HKC SDK 客户端 / KDE 实例)"
48
+ )
49
+
50
+ def ingest(self, candidate: KnowledgeCandidate) -> Any:
51
+ """实现 KnowledgeIngress.ingest。"""
52
+ text = candidate.text_for_ingest.strip()
53
+ if not text:
54
+ return None
55
+
56
+ # 结构化 Evidence → 来源串(供 KDE 当前接口使用)。
57
+ # 注:完整结构化 Evidence 进图谱由后续 KEE 演化步骤处理,
58
+ # 这里先把主来源信息传给 KDE,保证可追溯不丢。
59
+ source = self._primary_source(candidate)
60
+ source_title = candidate.title or self._fallback_title(candidate)
61
+
62
+ return self._ingest(
63
+ text,
64
+ source=source,
65
+ source_title=source_title,
66
+ domain=candidate.domain_hint or "",
67
+ )
68
+
69
+ @staticmethod
70
+ def _primary_source(candidate: KnowledgeCandidate) -> str:
71
+ """从首条 Evidence 构造来源标识(保持可追溯)。"""
72
+ if not candidate.evidence:
73
+ return "knowledge_candidate"
74
+ e = candidate.evidence[0]
75
+ parts = [e.evidence_type or "candidate"]
76
+ if e.agent:
77
+ parts.append(f"agent:{e.agent}")
78
+ if e.source_id:
79
+ parts.append(e.source_id)
80
+ return ":".join(parts)
81
+
82
+ @staticmethod
83
+ def _fallback_title(candidate: KnowledgeCandidate) -> str:
84
+ if candidate.event_refs:
85
+ return f"候选 {candidate.event_refs[0]}"
86
+ return "知识候选"
87
+
88
+
89
+ # 协议一致性断言(显式声明 HKCIngress 实现 KnowledgeIngress)
90
+ _: KnowledgeIngress = HKCIngress.__new__(HKCIngress)
File without changes
@@ -0,0 +1,197 @@
1
+ """
2
+ hkc-kde / chunker.py
3
+ 分块器:把 ParsedDocument 切成适合 LLM 提取的 Chunk。
4
+
5
+ 规则(写死,不允许各 loader 自定义):
6
+ - 优先在章节边界切割
7
+ - 单个 Section 超过 MAX_TOKENS → 滑动窗口切割
8
+ - 单个 Section 不足 MIN_TOKENS → 与下一个 Section 合并
9
+ - 相邻 Chunk 之间保留 OVERLAP_TOKENS 重叠,保持上下文连续
10
+ """
11
+ from __future__ import annotations
12
+ from dataclasses import dataclass
13
+
14
+ from .models import ParsedDocument, Section
15
+
16
+
17
+ # ── 配置(统一常量,禁止在其他地方覆盖)────────────────────
18
+
19
+ MAX_TOKENS = 512 # 单个 Chunk 最大 token 数
20
+ MIN_TOKENS = 128 # 低于此长度不单独成块,与下一个合并
21
+ OVERLAP_TOKENS = 64 # 相邻 Chunk 重叠 token 数
22
+
23
+ # 粗略估算:1 token ≈ 4 个英文字符 / 1.5 个中文字符
24
+ # 用字符数近似,避免引入 tokenizer 依赖
25
+ CHARS_PER_TOKEN = 3.5
26
+
27
+
28
+ def _char_limit(tokens: int) -> int:
29
+ return int(tokens * CHARS_PER_TOKEN)
30
+
31
+
32
+ MAX_CHARS = _char_limit(MAX_TOKENS)
33
+ MIN_CHARS = _char_limit(MIN_TOKENS)
34
+ OVERLAP_CHARS = _char_limit(OVERLAP_TOKENS)
35
+
36
+
37
+ @dataclass
38
+ class Chunk:
39
+ chunk_id: str
40
+ content: str
41
+ source_hint: str # 章节标题,用于 Evidence 溯源
42
+ doc_id: str
43
+ char_count: int = 0
44
+
45
+ def __post_init__(self):
46
+ self.char_count = len(self.content)
47
+
48
+
49
+ class Chunker:
50
+
51
+ def chunk(self, doc: ParsedDocument) -> list[Chunk]:
52
+ """
53
+ 主入口:把 ParsedDocument 切成 Chunk 列表。
54
+ 章节边界优先,章节内再按 MAX_CHARS 切割。
55
+ """
56
+ chunks: list[Chunk] = []
57
+ chunk_idx: int = 0
58
+
59
+ for chapter in doc.chapters:
60
+ chapter_chunks = self._chunk_chapter(
61
+ chapter_title = chapter.title,
62
+ sections = chapter.sections,
63
+ doc_id = doc.doc_id,
64
+ start_idx = chunk_idx,
65
+ )
66
+ chunks.extend(chapter_chunks)
67
+ chunk_idx += len(chapter_chunks)
68
+
69
+ return chunks
70
+
71
+ def _chunk_chapter(
72
+ self,
73
+ chapter_title: str,
74
+ sections: list[Section],
75
+ doc_id: str,
76
+ start_idx: int,
77
+ ) -> list[Chunk]:
78
+ """
79
+ 章节内部的分块逻辑:
80
+ 1. 先把 sections 合并成连续文本
81
+ 2. 如果总长 <= MAX_CHARS → 一个 Chunk
82
+ 3. 否则滑动窗口切割,相邻 Chunk 保留 OVERLAP_CHARS 重叠
83
+ """
84
+ # 合并 sections 文本(短 section 先合并)
85
+ merged_text = self._merge_sections(sections)
86
+ if not merged_text.strip():
87
+ return []
88
+
89
+ if len(merged_text) <= MAX_CHARS:
90
+ # 整章一个 Chunk
91
+ return [Chunk(
92
+ chunk_id = f"{doc_id}_C{start_idx:04d}",
93
+ content = merged_text,
94
+ source_hint = chapter_title,
95
+ doc_id = doc_id,
96
+ )]
97
+
98
+ # 滑动窗口切割
99
+ return self._sliding_window(
100
+ text = merged_text,
101
+ source_hint = chapter_title,
102
+ doc_id = doc_id,
103
+ start_idx = start_idx,
104
+ )
105
+
106
+ def _merge_sections(self, sections: list[Section]) -> str:
107
+ """
108
+ 把 sections 合并成文本。
109
+ 短 Section(< MIN_CHARS)不单独成块,与相邻合并。
110
+ """
111
+ if not sections:
112
+ return ""
113
+
114
+ parts: list[str] = []
115
+ buffer: str = ""
116
+
117
+ for sec in sections:
118
+ text = sec.content.strip()
119
+ if not text:
120
+ continue
121
+ if len(buffer) + len(text) < MIN_CHARS:
122
+ # buffer 太短,继续累积
123
+ buffer = (buffer + "\n\n" + text).strip()
124
+ else:
125
+ if buffer:
126
+ parts.append(buffer)
127
+ buffer = text
128
+
129
+ if buffer:
130
+ parts.append(buffer)
131
+
132
+ return "\n\n".join(parts)
133
+
134
+ def _sliding_window(
135
+ self,
136
+ text: str,
137
+ source_hint: str,
138
+ doc_id: str,
139
+ start_idx: int,
140
+ ) -> list[Chunk]:
141
+ """
142
+ 固定步长滑动窗口,步长 = MAX_CHARS - OVERLAP_CHARS。
143
+ 在句子边界(。!?\n)对齐,避免在句中切断。
144
+ """
145
+ step = MAX_CHARS - OVERLAP_CHARS
146
+ chunks: list[Chunk] = []
147
+ pos = 0
148
+
149
+ while pos < len(text):
150
+ end = pos + MAX_CHARS
151
+ if end < len(text):
152
+ # 向后找最近的句子边界
153
+ boundary = self._find_boundary(text, end)
154
+ end = boundary if boundary > pos else end
155
+
156
+ fragment = text[pos:end].strip()
157
+ if fragment:
158
+ chunks.append(Chunk(
159
+ chunk_id = f"{doc_id}_C{start_idx + len(chunks):04d}",
160
+ content = fragment,
161
+ source_hint = source_hint,
162
+ doc_id = doc_id,
163
+ ))
164
+
165
+ # 下一个窗口起点 = 当前终点 - 重叠
166
+ pos = max(pos + step, end - OVERLAP_CHARS)
167
+ if pos >= len(text):
168
+ break
169
+
170
+ return chunks
171
+
172
+ def _find_boundary(self, text: str, pos: int) -> int:
173
+ """
174
+ 从 pos 向前扫描,找最近的句子边界字符。
175
+ 优先级:\n\n > 。!?\n > 。!? > 空格
176
+ 最多向前扫描 100 字符。
177
+ """
178
+ search_start = max(0, pos - 100)
179
+ segment = text[search_start:pos]
180
+
181
+ # 优先双换行(段落边界)
182
+ idx = segment.rfind('\n\n')
183
+ if idx != -1:
184
+ return search_start + idx + 2
185
+
186
+ # 中文句号等
187
+ for ch in ('。', '!', '?', '!', '?', '\n'):
188
+ idx = segment.rfind(ch)
189
+ if idx != -1:
190
+ return search_start + idx + 1
191
+
192
+ # 空格
193
+ idx = segment.rfind(' ')
194
+ if idx != -1:
195
+ return search_start + idx + 1
196
+
197
+ return pos