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,416 @@
1
+ """
2
+ hkc-api / container.py
3
+ 应用容器:按依赖顺序装配 HKC 所有组件。
4
+
5
+ 装配顺序(依赖从下到上):
6
+ 1. IDGenerator ← 无依赖
7
+ 2. GraphStore ← 无依赖
8
+ 3. EventBus ← 无依赖
9
+ 4. KEE ← GraphStore + EventBus + IDGen(订阅 knowledge.created)
10
+ 5. ACE ← GraphStore + EventBus + IDGen
11
+ 6. KDE ← GraphStore + EventBus + IDGen
12
+ 7. HybridSearch ← GraphStore(+ 可选向量后端)
13
+
14
+ 容器是单例,整个 API 进程共享一套组件。
15
+ """
16
+ # TODO: Replace with proper package install before v1 release
17
+
18
+ import logging
19
+ import threading
20
+ from collections import deque
21
+ from pathlib import Path
22
+ from typing import Optional
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class HKCContainer:
28
+ """HKC 组件容器。一次装配,全程共享。"""
29
+
30
+ def __init__(
31
+ self,
32
+ data_dir: str = "./hkc_data",
33
+ embedding_kind: str = "stub", # stub | local | tei
34
+ embedding_url: str = "http://localhost:8080",
35
+ embedding_model: Optional[str] = None, # local/auto 后端的模型名(如 BAAI/bge-m3)
36
+ llm_api_key: Optional[str] = None,
37
+ llm_model: Optional[str] = None,
38
+ llm_provider: str = "anthropic",
39
+ llm_base_url: Optional[str] = None,
40
+ ):
41
+ self.data_dir = Path(data_dir)
42
+ self.data_dir.mkdir(parents=True, exist_ok=True)
43
+
44
+ self._embedding_kind = embedding_kind
45
+ self._embedding_url = embedding_url
46
+ self._embedding_model = embedding_model
47
+ self._llm_api_key = llm_api_key
48
+ self._llm_model = llm_model
49
+ self._llm_provider = llm_provider
50
+ self._llm_base_url = llm_base_url
51
+
52
+ # 组件占位
53
+ self.id_gen = None
54
+ self.graph_store = None
55
+ self.event_bus = None
56
+ self.kee = None
57
+ self.ace = None
58
+ self.kde = None
59
+ self.search = None
60
+ self.ingest_jobs = None # 异步摄入任务注册表(方案A)
61
+ self.synthesis = None # 综合页 LLM 综述(派生只读视图,不进知识库)
62
+ self.crystallizer = None # Crystallizer 摄入适配器(集成层入口:外部推知识候选)
63
+ self.index_build_manager = None # 阶段 C:后台冷构建管理器(无磁盘索引时跑)
64
+
65
+ # B3:IndexBuildManager 跑时(building)_sync_ku 入队,跑完(ready)flush。
66
+ # maxlen 防 OOM(超出从头丢,降级而非阻断,摄入仍走同步路径)
67
+ self._pending_ops: deque = deque(maxlen=10000)
68
+ self._pending_lock = threading.Lock()
69
+
70
+ self._assembled = False
71
+
72
+ def assemble(self) -> "HKCContainer":
73
+ """按依赖顺序装配所有组件。幂等。"""
74
+ if self._assembled:
75
+ return self
76
+
77
+ from hkc_core.utils.id_gen import IDGenerator
78
+ from hkc_core.graph.sqlite_store import SQLiteGraphStore
79
+ from hkc_kep.event_bus import EventBus
80
+ from hkc_kee.kee import KnowledgeEvolutionEngine
81
+ from hkc_ace.ace import AbilityCompilerEngine
82
+ from hkc_kde.kde import KnowledgeDigestEngine
83
+ from hkc_search.hybrid import HybridSearch
84
+ from hkc_search.vector_search import VectorIndex
85
+ from hkc_search.embedding_backends import make_backend
86
+
87
+ # 1. ID 生成器
88
+ self.id_gen = IDGenerator(str(self.data_dir / "id_counters.db"))
89
+
90
+ # 2. GraphStore
91
+ self.graph_store = SQLiteGraphStore(str(self.data_dir / "hkc.db"))
92
+
93
+ # 3. EventBus
94
+ self.event_bus = EventBus(str(self.data_dir / "events.db"))
95
+
96
+ # 4. KEE(构造时自动订阅 knowledge.created)
97
+ self.kee = KnowledgeEvolutionEngine(
98
+ self.graph_store, self.event_bus, self.id_gen
99
+ )
100
+
101
+ # 5. ACE
102
+ self.ace = AbilityCompilerEngine(
103
+ self.graph_store, self.event_bus, self.id_gen,
104
+ output_dir=str(self.data_dir / "abilities"),
105
+ )
106
+
107
+ # 6. KDE
108
+ self.kde = KnowledgeDigestEngine(
109
+ self.graph_store, self.event_bus, self.id_gen,
110
+ api_key=self._llm_api_key, model=self._llm_model,
111
+ provider=self._llm_provider, base_url=self._llm_base_url,
112
+ )
113
+
114
+ # 7. HybridSearch(向量后端可配置)
115
+ backend_kwargs = {}
116
+ if self._embedding_kind == "tei":
117
+ backend_kwargs["base_url"] = self._embedding_url
118
+ # local/auto 后端可指定模型名(如 BAAI/bge-m3,1024 维,维度由模型自动校正);
119
+ # 不传则用 LocalSTBackend 默认模型,行为与此前一致。tei 不接受 model_name,不传。
120
+ if self._embedding_model and self._embedding_kind in ("local", "auto"):
121
+ backend_kwargs["model_name"] = self._embedding_model
122
+ backend = make_backend(self._embedding_kind, **backend_kwargs)
123
+ vector_index = VectorIndex(backend=backend)
124
+ self.search = HybridSearch(self.graph_store, vector_index=vector_index)
125
+
126
+ # 启动时加载持久化索引 + 准备 BM25。阶段 C 后,**全量 build 由 IndexBuildManager
127
+ # 后台线程承担**,assemble() 内部只做「加载已存在」+「BM25 重建」,保证 Lifespan
128
+ # 启动不被 30-60 分钟的冷编码卡住。IndexBuildManager 见下段。
129
+ self._load_persisted_index_and_bm25()
130
+
131
+ # 阶段 C:起后台冷构建 manager。它会自动:
132
+ # · vector 已覆盖库 → 立即返,标 ready
133
+ # · vector 部分覆盖 / 为空 → 启 daemon 线程,扫描 + encode + append_bulk
134
+ # 全部完成后,manager 内部 save() 落盘,下次启动 assemble() 直接加载。
135
+ from hkc_search.index_build_manager import IndexBuildManager
136
+ import os as _os
137
+ self.index_build_manager = IndexBuildManager(
138
+ data_dir=str(self.data_dir),
139
+ hybrid_search=self.search,
140
+ graph_store=self.graph_store,
141
+ batch_size=int(_os.environ.get("HKC_BUILD_BATCH_SIZE", "64")),
142
+ max_failures=int(_os.environ.get("HKC_BUILD_MAX_FAILURES", "100")),
143
+ checkpoint_every_batches=int(_os.environ.get("HKC_BUILD_CHECKPOINT_BATCHES", "5")),
144
+ on_ready=lambda: self.flush_pending_ops(), # B3:ready 时 flush pending 队列
145
+ )
146
+ self.index_build_manager.start()
147
+
148
+ # 新知识写入后自动更新搜索索引
149
+ self._wire_search_index_updates()
150
+
151
+ # 8. 异步摄入任务注册表(后台线程池执行文件摄入)
152
+ from hkc_api.ingest_jobs import IngestJobRegistry
153
+ self.ingest_jobs = IngestJobRegistry(max_workers=4)
154
+
155
+ # 9. 综合页 LLM 综述服务(派生只读视图;缓存独立 synthesis.db,不进知识库,守 Principle 07)
156
+ from hkc_api.synthesis import SynthesisService
157
+ self.synthesis = SynthesisService(
158
+ self.graph_store, self.search, self.kde,
159
+ str(self.data_dir / "synthesis.db"),
160
+ )
161
+
162
+ # 10. Crystallizer 摄入适配器(集成层入口):外部系统经 POST /knowledge/crystallize
163
+ # 推知识候选,走 HKCIngress → KDE → KEE。守边界:只产候选,不做知识身份判定。
164
+ from hkc_crystallizer.ingress import HKCIngress
165
+ self.crystallizer = HKCIngress(self.kde)
166
+
167
+ self._embedding_name = backend.name # 实际生效的向量后端名(供 /stats 展示)
168
+ self._assembled = True
169
+ logger.info("HKC 容器装配完成: data_dir=%s embedding=%s",
170
+ self.data_dir, backend.name)
171
+ return self
172
+
173
+ def _load_persisted_index_and_bm25(self):
174
+ """阶段 C:启动期**只**加载磁盘索引 + 重建 BM25,**不**做全量 encode,
175
+ **不**做增量补算 —— 这两件事交给 IndexBuildManager 后台线程。
176
+ - vector.load:从 search_vector.faiss / .meta 加载(阶段 A 8 步校验失败 → _reset)
177
+ - bm25.build:从 store 拉 active KU 重建(快,毫秒级,不会卡启动)
178
+ 设计目标:assemble() 整体在秒级返回(在 1.6 万 KU 库上,< 5s),
179
+ 不再因为 30-60 分钟的冷编码被 Lifespan 启动超时打断。
180
+ """
181
+ vec_path = str(self.data_dir / "search_vector")
182
+ try:
183
+ kus = self.graph_store.query_all(limit=99999999) or []
184
+ except Exception:
185
+ kus = []
186
+ loaded_n = 0
187
+ if kus:
188
+ try:
189
+ if self.search.vector.load(vec_path):
190
+ loaded_n = self.search.vector.size()
191
+ logger.info(
192
+ "向量索引已从磁盘加载: %d 条 (库内共 %d 条,余下交 manager 后台)",
193
+ loaded_n, len(kus))
194
+ except Exception as e:
195
+ logger.warning("加载持久化向量索引失败,manager 会兜底: %s", e)
196
+ # BM25 必须重建一次(它不持久化;且重建开销极小,毫秒级)
197
+ if kus:
198
+ try:
199
+ self.search.bm25.build(kus)
200
+ except Exception as e:
201
+ logger.warning("BM25 重建失败(非致命,search 会回退 bm25=None 保护): %s", e)
202
+ if not kus:
203
+ logger.info("库为空,无需构建索引")
204
+
205
+ def _wire_search_index_updates(self):
206
+ """订阅 KU 事件,统一走 _sync_ku 五分支。
207
+
208
+ 阶段 B 修正:
209
+ ① CREATED 处理器必须**查 status** 后再走 _sync_ku(ku_id) —— 重复 KU 已被
210
+ KEE 软删,append_ku 会把同 id 推进 vector,导致检索返回「已删」内容。
211
+ ② UPDATED 处理器也要查 status —— KEE 状态机把 Claim 从 active 改成
212
+ superseded/rejected,vector 必须同步 remove,否则检索召回已废弃 KU。
213
+ ③ DELETED 处理器不查 store(被删了 get=None),直接 vector.remove_ku。
214
+
215
+ _sync_ku 五分支:
216
+ 1) store.get(ku_id) is None → vector.remove(兜底)
217
+ 2) status == 'deleted' → vector.remove
218
+ 3) status not in {active} → vector.remove (superseded/rejected/disputed)
219
+ 4) active + 不在 vector → vector.append_ku
220
+ 5) active + 在 vector + text_hash 变 → vector.update_ku(remove+append)
221
+ 5b) active + 在 vector + text_hash 同 → noop (同 hash 跳过 encode)
222
+
223
+ 五分支都包在 try/except:单个 KU 同步失败不阻断其它 KU(继续下一条)。
224
+ """
225
+ from hkc_kep.event_bus import KEPEvents
226
+
227
+ def _on_ku_event(event_name: str, event: dict):
228
+ ku_ids = event.get("payload", {}).get("ku_ids", [])
229
+ if not ku_ids and "ku_id" in event.get("payload", {}):
230
+ ku_ids = [event["payload"]["ku_id"]]
231
+ for ku_id in ku_ids:
232
+ self._sync_ku(ku_id)
233
+
234
+ self.event_bus.subscribe(KEPEvents.KNOWLEDGE_CREATED,
235
+ lambda e: _on_ku_event("CREATED", e))
236
+ self.event_bus.subscribe(KEPEvents.KNOWLEDGE_UPDATED,
237
+ lambda e: _on_ku_event("UPDATED", e))
238
+ self.event_bus.subscribe(KEPEvents.KNOWLEDGE_DELETED,
239
+ lambda e: _on_ku_event("DELETED", e))
240
+
241
+ def _sync_ku(self, ku_id: str) -> None:
242
+ """B3 入口:IndexBuildManager building 时入队(微秒返回,不持任何锁);
243
+ 其他状态走 _do_sync_ku(原五分支逻辑)。
244
+ 单一 KU 失败不抛,只 log warning —— 摄入任务里 50 条 KU 失败 1 条不应拖累其余。
245
+
246
+ B3 设计要点(2026-10-16 修正):
247
+ - **不**在 _sync_ku 入口同步 flush(早期版本有 bug:每次入队都
248
+ 同步调 flush_pending_ops(),IndexBuildManager 跑时持 hybrid._write_lock
249
+ 3-5s/batch(内含 encode),user 上传线程 acquire 同一锁等 3-5s
250
+ → 整个 ingest pipeline 卡死 30-50s)。
251
+ - 依赖 on_ready 回调集中 flush(IndexBuildManager._mark_ready_locked
252
+ 触发,state=ready 时 hybrid._write_lock 不再被持,flush 立即完成)。
253
+ - 兜底:on_ready 漏掉的极端情况,user 后续上传走 _do_sync_ku
254
+ 同步路径(state 已变 ready,走 else 分支)。
255
+ """
256
+ # B3:building 期间走队列,绕开 _sync_ku 内部的 graph_store.get + hybrid.* 锁
257
+ if (self.index_build_manager is not None
258
+ and self.index_build_manager.state == "building"):
259
+ with self._pending_lock:
260
+ self._pending_ops.append(("sync", ku_id))
261
+ # 不在入口同步 flush —— 由 on_ready 回调集中处理
262
+ return
263
+ self._do_sync_ku(ku_id)
264
+
265
+ def _do_sync_ku(self, ku_id: str) -> None:
266
+ """五分支同步单条 KU 到 vector(原 _sync_ku 逻辑,B3 入口和 flush 共用)。
267
+ 单一 KU 失败不抛,只 log warning。
268
+ """
269
+ try:
270
+ ku = self.graph_store.get(ku_id)
271
+ except Exception as e:
272
+ logger.warning("_sync_ku 读 store 失败(%s): %s", ku_id, e)
273
+ return
274
+ if ku is None or getattr(ku, "status", "") == "deleted":
275
+ # 1) / 2) — KU 不存在或已删,删 vector
276
+ try:
277
+ self.search.remove_ku(ku_id)
278
+ except Exception as e:
279
+ logger.warning("vector.remove_ku(%s) 失败: %s", ku_id, e)
280
+ return
281
+ if getattr(ku, "status", "active") not in ("active",):
282
+ # 3) — superseded / rejected / disputed(非 active 状态)也不该被检索召回
283
+ try:
284
+ self.search.remove_ku(ku_id)
285
+ except Exception as e:
286
+ logger.warning("vector.remove_ku(%s) 失败: %s", ku_id, e)
287
+ return
288
+ # Claim 还要看 claim_status(可能 active 但 claim_status 是 superseded)
289
+ if getattr(ku, "ku_type", None) and getattr(ku.ku_type, "value", "") == "Claim":
290
+ extra = getattr(ku, "to_dict", lambda: {} )().get("extra", {}) or {}
291
+ cs = extra.get("claim_status", "active")
292
+ if cs in ("superseded", "rejected", "disputed"):
293
+ try:
294
+ self.search.remove_ku(ku_id)
295
+ except Exception as e:
296
+ logger.warning("vector.remove_ku(%s) 失败: %s", ku_id, e)
297
+ return
298
+ # 4) / 5) / 5b)
299
+ in_vector = ku_id in (self.search.vector._ku_ids or [])
300
+ if not in_vector:
301
+ try:
302
+ self.search.append_ku(ku)
303
+ except Exception as e:
304
+ logger.warning("vector.append_ku(%s) 失败: %s", ku_id, e)
305
+ else:
306
+ try:
307
+ # update_ku 内部用 text_hash 检测:同 hash 跳过 encode,变才重编
308
+ self.search.update_ku(ku)
309
+ except Exception as e:
310
+ logger.warning("vector.update_ku(%s) 失败: %s", ku_id, e)
311
+
312
+ def flush_pending_ops(self) -> int:
313
+ """B3:flush 队列里 _sync_ku 攒下的待处理 op。
314
+ - atomic swap:持 pending_lock 取队列副本 + 清空(几微秒)
315
+ - 释放 lock 后批量执行(slow 操作在锁外,调 _do_sync_ku 走原五分支)
316
+ - 返回 flush 数量(供 on_ready 回调 / 调试)
317
+ 设计:IndexBuildManager ready 时调一次;每次 _sync_ku 入队也自检
318
+ flush(避免 ready 事件错过)。风险:deque maxlen=10000 超出从头丢;
319
+ 但 _sync_ku 入队时自检 state,ready 后立即 flush,正常不积压。"""
320
+ with self._pending_lock:
321
+ if not self._pending_ops:
322
+ return 0
323
+ pending = list(self._pending_ops)
324
+ self._pending_ops.clear()
325
+ flushed = 0
326
+ for op in pending:
327
+ try:
328
+ op_type, ku_id = op[0], op[1]
329
+ if op_type == "sync":
330
+ # 调 _do_sync_ku(跳过 B3 检测,强制同步执行,避免再次入队死循环)
331
+ self._do_sync_ku(ku_id)
332
+ flushed += 1
333
+ except Exception as e:
334
+ logger.warning("flush_pending_ops 失败[%s/%s]: %s",
335
+ op[0], op[1], e)
336
+ return flushed
337
+
338
+ def stats(self) -> dict:
339
+ """返回系统整体统计。"""
340
+ if not self._assembled:
341
+ return {"assembled": False}
342
+ s = self.graph_store.stats()
343
+ s["assembled"] = True
344
+ s["data_dir"] = str(self.data_dir.resolve()) # 绝对路径,前端显示 + 排查"连错目录"
345
+ s["embedding"] = getattr(self, "_embedding_name", "") # 生效的向量后端(stub 无语义)
346
+ s["bm25_size"] = self.search.bm25.size()
347
+ s["vector_size"] = self.search.vector.size()
348
+ # 阶段 C:把后台构建状态也吐出,前端可做「正在编码 N/M」提示
349
+ if self.index_build_manager is not None:
350
+ s["index_build"] = self.index_build_manager.stats()
351
+ else:
352
+ s["index_build"] = {"index_state": "idle"}
353
+ s["abilities"] = self.ace.list_available()
354
+ # B3 监控:IndexBuildManager building 期间 _sync_ku 入队的 KU 数
355
+ # (= 待 flush 积压)。ready 后被 on_ready 回调一次性清零
356
+ s["pending_ops"] = len(self._pending_ops)
357
+ return s
358
+
359
+ def close(self):
360
+ """关闭资源。每次 reset_container(测试 tearDown / 服务热重载)都会调,
361
+ 必须把线程池和额外的 SQLite 连接一并释放,否则会累积线程/文件描述符泄漏。"""
362
+ # 阶段 C:先停后台构建线程(它持有 self.search / self.graph_store 引用),
363
+ # 再释放 SQLite。否则若线程在 close 期间还在跑,SQLite 已被 close 句柄释放,
364
+ # 线程内 append_bulk 会爆「database is closed」。
365
+ if getattr(self, "index_build_manager", None):
366
+ try:
367
+ self.index_build_manager.cancel()
368
+ # 限时 join:不阻塞 close 太久(2s 内退出是合理预期,batch_size=64 bge-m3
369
+ # 约 3-5s,但 cancel 标志会让它在 batch 边界停)。
370
+ self.index_build_manager.join(timeout=2.0)
371
+ except Exception:
372
+ pass
373
+ if self.ingest_jobs:
374
+ try: self.ingest_jobs.shutdown()
375
+ except Exception: pass
376
+ if self.synthesis:
377
+ try: self.synthesis.cache.close()
378
+ except Exception: pass
379
+ # 4 个 SQLite 连接必须全部释放,否则 Windows 下数据目录删不掉(WinError 32)、
380
+ # 长期运行累积 fd 泄漏。此前只关了 graph_store,漏了 event_bus/id_gen(融合时暴露)。
381
+ if getattr(self, "event_bus", None):
382
+ try: self.event_bus.close()
383
+ except Exception: pass
384
+ if getattr(self, "id_gen", None):
385
+ try: self.id_gen.close()
386
+ except Exception: pass
387
+ if self.graph_store:
388
+ self.graph_store.close()
389
+
390
+
391
+ # ── 全局单例 ─────────────────────────────────────────────────
392
+
393
+ _container: Optional[HKCContainer] = None
394
+
395
+
396
+ def get_container() -> HKCContainer:
397
+ """获取全局容器(FastAPI 依赖注入用)。"""
398
+ global _container
399
+ if _container is None:
400
+ raise RuntimeError("容器未初始化,请先调用 init_container()")
401
+ return _container
402
+
403
+
404
+ def init_container(**kwargs) -> HKCContainer:
405
+ """初始化全局容器。"""
406
+ global _container
407
+ _container = HKCContainer(**kwargs).assemble()
408
+ return _container
409
+
410
+
411
+ def reset_container():
412
+ """重置全局容器(测试用)。"""
413
+ global _container
414
+ if _container:
415
+ _container.close()
416
+ _container = None
@@ -0,0 +1,103 @@
1
+ """
2
+ hkc-api / ingest_jobs.py
3
+ 异步摄入任务注册表(方案A)。
4
+
5
+ 上传文件后立即返回 job_id,真正的摄入(解析 + 逐 chunk 调 LLM,耗时数分钟)
6
+ 在后台线程池里跑,前端轮询 job 状态拿真进度,期间可继续上传下一个。
7
+
8
+ - 内存态(崩溃后 job 丢失可接受,重传即可);线程安全。
9
+ - 进度来自 Extractor 逐 chunk 回调 progress_cb(processed, total)。
10
+ """
11
+ from __future__ import annotations
12
+ import threading, time, uuid, logging
13
+ from concurrent.futures import ThreadPoolExecutor
14
+ from dataclasses import dataclass, field, asdict
15
+ from typing import Callable, Optional
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class IngestJob:
22
+ job_id: str
23
+ filename: str
24
+ status: str = "queued" # queued | running | done | error
25
+ processed: int = 0 # 已抽取 chunk 数
26
+ total: int = 0 # chunk 总数(截断后的实际处理数)
27
+ ku_count: int = 0 # 产出 KU 数
28
+ error: str = ""
29
+ created_at: float = field(default_factory=time.time)
30
+ updated_at: float = field(default_factory=time.time)
31
+
32
+
33
+ class IngestJobRegistry:
34
+ """维护 job_id → IngestJob,并用线程池在后台执行摄入。"""
35
+
36
+ def __init__(self, max_workers: int = 4, max_jobs: int = 200):
37
+ self._jobs: dict[str, IngestJob] = {}
38
+ self._lock = threading.RLock()
39
+ self._max_jobs = max_jobs # 内存里最多保留多少条 job 记录
40
+ self._executor = ThreadPoolExecutor(
41
+ max_workers=max_workers, thread_name_prefix="ingest"
42
+ )
43
+
44
+ def create(self, filename: str) -> IngestJob:
45
+ job = IngestJob(job_id="job_" + uuid.uuid4().hex[:12], filename=filename)
46
+ with self._lock:
47
+ self._jobs[job.job_id] = job
48
+ self._evict_locked()
49
+ return job
50
+
51
+ def _evict_locked(self):
52
+ """超过上限时,淘汰最旧的【已结束】job(done/error)。绝不淘汰进行中的
53
+ (queued/running)——前端只轮询进行中的,最旧的已完成 job 早已无人查看。
54
+ 若全是进行中的,宁可暂时超限也不丢活跃 job。须在持锁下调用。"""
55
+ over = len(self._jobs) - self._max_jobs
56
+ if over <= 0:
57
+ return
58
+ finished = sorted(
59
+ (j for j in self._jobs.values() if j.status in ("done", "error")),
60
+ key=lambda j: j.created_at,
61
+ )
62
+ for j in finished[:over]:
63
+ self._jobs.pop(j.job_id, None)
64
+
65
+ def get(self, job_id: str) -> Optional[dict]:
66
+ """返回 job 状态快照(dict)。不存在返回 None。"""
67
+ with self._lock:
68
+ job = self._jobs.get(job_id)
69
+ return asdict(job) if job else None
70
+
71
+ def submit(self, job: IngestJob, work: Callable[[Callable[[int, int], None]], list]) -> None:
72
+ """
73
+ 提交后台执行。work 接收一个 progress(processed,total) 回调,
74
+ 返回写入的 KU 列表(用于 ku_count)。异常会被捕获记入 job.error。
75
+ """
76
+ self._executor.submit(self._run, job, work)
77
+
78
+ def _run(self, job: IngestJob, work):
79
+ with self._lock:
80
+ job.status = "running"; job.updated_at = time.time()
81
+
82
+ def progress(processed: int, total: int):
83
+ with self._lock:
84
+ job.processed = processed; job.total = total
85
+ job.updated_at = time.time()
86
+
87
+ try:
88
+ kus = work(progress)
89
+ with self._lock:
90
+ job.status = "done"
91
+ job.ku_count = len(kus) if kus else 0
92
+ if job.total == 0: # 空文档等:没触发过进度回调
93
+ job.total = job.processed
94
+ job.updated_at = time.time()
95
+ logger.info("摄入 job 完成: %s (%s), %d KU", job.job_id, job.filename, job.ku_count)
96
+ except Exception as e:
97
+ with self._lock:
98
+ job.status = "error"; job.error = str(e)[:200]
99
+ job.updated_at = time.time()
100
+ logger.exception("摄入 job 失败: %s (%s)", job.job_id, job.filename)
101
+
102
+ def shutdown(self):
103
+ self._executor.shutdown(wait=False)
@@ -0,0 +1,157 @@
1
+ """
2
+ hkc-api / main.py
3
+ HKC REST API 主应用。
4
+
5
+ 启动(激活 venv 后统一用 python -m uvicorn):
6
+ python -m uvicorn hkc_api.main:app --host 127.0.0.1 --port 8000
7
+
8
+ 环境变量:
9
+ HKC_DATA_DIR 数据目录(默认 ./hkc_data)
10
+ HKC_EMBEDDING stub | local | tei(默认 stub)
11
+ HKC_EMBEDDING_URL TEI 服务地址(embedding=tei 时用)
12
+ HKC_EMBEDDING_MODEL local/auto 后端的模型名(如 BAAI/bge-m3;留空用内置默认)
13
+ HKC_LLM_PROVIDER anthropic | deepseek | openai | openai-compatible(默认 anthropic)
14
+ HKC_LLM_API_KEY 通用 key;未设则按 provider 回退读 DEEPSEEK_API_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY
15
+ HKC_LLM_MODEL / HKC_LLM_BASE_URL 模型名 / 自定义 API 地址(本地模型时)
16
+ HKC_API_KEY 设了则请求需带 X-API-Key(可选鉴权)
17
+ """
18
+ # TODO: Replace with proper package install before v1 release
19
+ import os
20
+
21
+ import logging
22
+ from contextlib import asynccontextmanager
23
+
24
+ from fastapi import FastAPI
25
+
26
+ from .container import init_container, get_container, reset_container
27
+ from .schemas import StatsResponse
28
+ from .routes import knowledge, search, ability, config
29
+
30
+ logging.basicConfig(
31
+ level=logging.INFO,
32
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
33
+ )
34
+ logger = logging.getLogger("hkc_api")
35
+
36
+
37
+ @asynccontextmanager
38
+ async def lifespan(app: FastAPI):
39
+ """启动时装配容器,关闭时清理。"""
40
+ # 【P2·单 LLM 入口】默认从 LLM_* 读,保留 HKC_LLM_* 显式覆盖向后兼容
41
+ _provider = (os.environ.get("HKC_LLM_PROVIDER")
42
+ or os.environ.get("LLM_PROVIDER")
43
+ or "anthropic").strip().lower()
44
+ # API key:优先 HKC_LLM_API_KEY,再 LLM_API_KEY,再各 provider 旧 env 兜底
45
+ _llm_key = (os.environ.get("HKC_LLM_API_KEY")
46
+ or os.environ.get("LLM_API_KEY")
47
+ or os.environ.get("DEEPSEEK_API_KEY")
48
+ or os.environ.get("OPENAI_API_KEY")
49
+ or os.environ.get("ANTHROPIC_API_KEY"))
50
+ # base_url 同源(单入口)
51
+ _llm_base = (os.environ.get("HKC_LLM_BASE_URL")
52
+ or os.environ.get("LLM_BASE_URL")) or None
53
+ # 模型名同源
54
+ _llm_model = (os.environ.get("HKC_LLM_MODEL")
55
+ or os.environ.get("LLM_MODEL")) or None
56
+ # embedding 模型:优先 EMBEDDING_MODEL(系统唯一本地入口),再 HKC_EMBEDDING_MODEL
57
+ _emb_model = (os.environ.get("EMBEDDING_MODEL")
58
+ or os.environ.get("HKC_EMBEDDING_MODEL")) or None
59
+ init_container(
60
+ data_dir = os.environ.get("HKC_DATA_DIR", "./hkc_data"),
61
+ embedding_kind = os.environ.get("HKC_EMBEDDING", "stub"),
62
+ embedding_url = os.environ.get("HKC_EMBEDDING_URL", "http://localhost:8080"),
63
+ embedding_model = _emb_model, # 空串→None,用默认模型
64
+ llm_api_key = _llm_key,
65
+ llm_model = _llm_model, # None → 按 provider 默认
66
+ llm_provider = _provider,
67
+ llm_base_url = _llm_base, # None → provider 默认
68
+ )
69
+ logger.info("HKC API 启动完成")
70
+ yield
71
+ reset_container()
72
+ logger.info("HKC API 已关闭")
73
+
74
+
75
+ app = FastAPI(
76
+ title="Foxclaw Knowledge Core API",
77
+ description="知识从存储进化成能力。KDE 摄入 → KEE 演化 → ACE 编译 → Search 检索。",
78
+ version="1.0.0",
79
+ lifespan=lifespan,
80
+ )
81
+
82
+ # 可选 API 鉴权:设了环境变量 HKC_API_KEY 才启用;未设=开放(本地默认,行为不变)。
83
+ # 在 CORS 之前注册 → CORS 处于更外层,即便鉴权返回 401 也带上 CORS 头,浏览器能读到。
84
+ _api_key = os.environ.get("HKC_API_KEY", "").strip()
85
+ if _api_key:
86
+ from starlette.requests import Request
87
+ from starlette.responses import JSONResponse
88
+ _OPEN_PATHS = {"/", "/health", "/docs", "/openapi.json", "/redoc"}
89
+
90
+ @app.middleware("http")
91
+ async def _require_api_key(request: Request, call_next):
92
+ if request.method == "OPTIONS" or request.url.path in _OPEN_PATHS:
93
+ return await call_next(request)
94
+ if request.headers.get("x-api-key", "") != _api_key:
95
+ return JSONResponse(
96
+ status_code=401,
97
+ content={"detail": "缺少或错误的 API Key(请求头需带 X-API-Key)"},
98
+ )
99
+ return await call_next(request)
100
+
101
+ # CORS:允许前端(hkc-ui)从浏览器调用。HKC 是本地知识工具,默认放开所有来源;
102
+ # 部署到受控网络时可用 HKC_CORS_ORIGINS(逗号分隔)收紧。
103
+ from fastapi.middleware.cors import CORSMiddleware
104
+ _origins = os.environ.get("HKC_CORS_ORIGINS", "*")
105
+ app.add_middleware(
106
+ CORSMiddleware,
107
+ allow_origins=[o.strip() for o in _origins.split(",")] if _origins != "*" else ["*"],
108
+ allow_methods=["*"],
109
+ allow_headers=["*"],
110
+ )
111
+
112
+ # 注册路由
113
+ app.include_router(knowledge.router)
114
+ app.include_router(search.router)
115
+ app.include_router(ability.router)
116
+ app.include_router(config.router)
117
+
118
+
119
+ @app.get("/")
120
+ def root():
121
+ return {
122
+ "name": "Foxclaw Knowledge Core",
123
+ "version": "1.0.0",
124
+ "docs": "/docs",
125
+ "modules": ["KDE", "KEE", "ACE", "Search"],
126
+ }
127
+
128
+
129
+ @app.get("/health")
130
+ def health():
131
+ return {"status": "ok"}
132
+
133
+
134
+ @app.get("/stats", response_model=StatsResponse, tags=["system"])
135
+ def stats():
136
+ c = get_container()
137
+ return StatsResponse(**c.stats())
138
+
139
+
140
+ @app.get("/events")
141
+ def recent_events(limit: int = 20):
142
+ """查看最近的 KEP 事件(调试用)。"""
143
+ c = get_container()
144
+ return {"events": c.event_bus.recent_events(limit=limit)}
145
+
146
+
147
+ def main():
148
+ import uvicorn
149
+ uvicorn.run(
150
+ app,
151
+ host=os.environ.get("HKC_HOST", "127.0.0.1"),
152
+ port=int(os.environ.get("HKC_PORT", "8000")),
153
+ )
154
+
155
+
156
+ if __name__ == "__main__":
157
+ main()
File without changes