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,194 @@
1
+ """
2
+ AgentSourcePort —— 内部搜索的「Agent」源。
3
+
4
+ 把五个 Agent 持有的**全部内部数据**连通进 HSR 的内部检索,四类子源:
5
+ 1. 领域内核结构化数据:财务(交易/预算/实体) + 学习(知识条目) + 行程(行程点)
6
+ 2. ThoughtChain 推理链:各 Agent 想过/判过什么(按内容关键词检索)
7
+ 3. 各 Agent 的 HMR 记忆:逐 agent_id 召回(区别于共享 memory_port 的当前-agent 召回)
8
+ 4. 能力/状态目录:skill 目录 + 各 Agent 角色/状态("哪个 Agent 能做这事")
9
+
10
+ 设计边界(与 HSR 一致):
11
+ · 只读——绝不写回任何持有方状态。
12
+ · 解耦——只依赖各持有方稳定的公开读接口 + 领域对象文本化,不侵入其内部逻辑。
13
+ · 降级——任一子源缺失/报错都 try/except 跳过,不影响其它子源与主流程。
14
+ · 只依赖显式传入的引用(不 import Foxclaw),保持 search 内核与本体的低耦合。
15
+ """
16
+ from __future__ import annotations
17
+ import re
18
+ from .models import RawEvidence, SourceType
19
+
20
+
21
+ def _score(query: str, text: str) -> int:
22
+ """轻量关键词打分:词级(空格/标点分隔,len>=2 子串命中)+ 中文 2-gram 重叠。
23
+ 纯确定性、可解释,不引入模型——语义匹配由 HKC/HMR 端负责,这里是结构化态势查找。"""
24
+ q = (query or "").strip()
25
+ t = text or ""
26
+ if not q or not t:
27
+ return 0
28
+ score = 0
29
+ for term in re.split(r"[\s,,、;;::/]+", q):
30
+ if len(term) >= 2 and term in t:
31
+ score += 2
32
+ for i in range(len(q) - 1): # 中文/无分隔场景的 2-gram 重叠
33
+ if q[i:i + 2] in t:
34
+ score += 1
35
+ return score
36
+
37
+
38
+ def _conf(score: int) -> float:
39
+ return round(min(0.7, 0.4 + 0.04 * score), 3)
40
+
41
+
42
+ class AgentSourcePort:
43
+ """内部搜索的 Agent 源。query(text, top_k) -> list[RawEvidence(source=AGENT)]。"""
44
+
45
+ def __init__(self, agents=None, finance_runtime=None, learning_runtime=None,
46
+ itinerary_runtime=None, thought_chain=None, skill_registry=None,
47
+ hmr=None):
48
+ self.agents = agents or {}
49
+ self.finance = finance_runtime
50
+ self.learning = learning_runtime
51
+ self.itinerary = itinerary_runtime
52
+ self.thought_chain = thought_chain
53
+ self.skill_registry = skill_registry
54
+ self.hmr = hmr
55
+
56
+ @property
57
+ def available(self) -> bool:
58
+ return any([self.agents, self.finance, self.learning, self.itinerary,
59
+ self.thought_chain, self.skill_registry, self.hmr])
60
+
61
+ def query(self, text: str, top_k: int = 5) -> list[RawEvidence]:
62
+ scored: list[tuple[int, RawEvidence]] = []
63
+ for gather in (self._from_finance, self._from_learning, self._from_itinerary,
64
+ self._from_thoughtchain, self._from_memories, self._from_capabilities):
65
+ try:
66
+ scored += gather(text, top_k)
67
+ except Exception:
68
+ continue # 单一子源失败不影响其它子源
69
+ scored.sort(key=lambda x: x[0], reverse=True)
70
+ return [ev for _, ev in scored[:max(top_k * 2, top_k)]]
71
+
72
+ # ── 1. 领域内核 ──────────────────────────────────────────
73
+ def _from_finance(self, text, top_k):
74
+ out = []
75
+ rt = self.finance
76
+ if rt is None:
77
+ return out
78
+ for i, txn in enumerate(getattr(rt, "transactions", []) or []):
79
+ desc = f"交易 {getattr(txn,'entity','')} {getattr(txn,'amount','')} " \
80
+ f"{getattr(txn,'category','')} {' '.join(getattr(txn,'tags',[]) or [])}"
81
+ s = _score(text, desc)
82
+ if s:
83
+ out.append((s, RawEvidence(id=f"agent:finance:txn:{i}", fact=desc.strip(),
84
+ source=SourceType.AGENT, origin="agent:finance:transaction",
85
+ confidence=_conf(s))))
86
+ for i, b in enumerate(getattr(rt, "budgets", []) or []):
87
+ desc = f"预算 {getattr(b,'category','')} 上限 {getattr(b,'monthly_limit','')}"
88
+ s = _score(text, desc)
89
+ if s:
90
+ out.append((s, RawEvidence(id=f"agent:finance:budget:{i}", fact=desc.strip(),
91
+ source=SourceType.AGENT, origin="agent:finance:budget",
92
+ confidence=_conf(s))))
93
+ for name, ent in (getattr(rt, "entities", {}) or {}).items():
94
+ desc = f"财务实体 {name} 累计 {getattr(ent,'total_amount','')} " \
95
+ f"次数 {getattr(ent,'txn_count','')}"
96
+ s = _score(text, desc)
97
+ if s:
98
+ out.append((s, RawEvidence(id=f"agent:finance:entity:{name}", fact=desc.strip(),
99
+ source=SourceType.AGENT, origin="agent:finance:entity",
100
+ confidence=_conf(s))))
101
+ return out
102
+
103
+ def _from_learning(self, text, top_k):
104
+ out = []
105
+ rt = self.learning
106
+ if rt is None:
107
+ return out
108
+ for iid, item in (getattr(rt, "items", {}) or {}).items():
109
+ desc = f"知识 {getattr(item,'content','')} 主题 {getattr(item,'topic','')} " \
110
+ f"{' '.join(getattr(item,'tags',[]) or [])}"
111
+ s = _score(text, desc)
112
+ if s:
113
+ out.append((s, RawEvidence(id=f"agent:learning:{iid}", fact=desc.strip(),
114
+ source=SourceType.AGENT, origin=f"agent:learning:{iid}",
115
+ confidence=_conf(s))))
116
+ return out
117
+
118
+ def _from_itinerary(self, text, top_k):
119
+ out = []
120
+ rt = self.itinerary
121
+ if rt is None:
122
+ return out
123
+ for i, wp in enumerate(getattr(rt, "waypoints", []) or []):
124
+ desc = f"行程点 {getattr(wp,'name','')} {getattr(wp,'address','')} " \
125
+ f"{getattr(wp,'city','')} {getattr(wp,'start_time','') or ''}"
126
+ s = _score(text, desc)
127
+ if s:
128
+ out.append((s, RawEvidence(id=f"agent:itinerary:{i}", fact=desc.strip(),
129
+ source=SourceType.AGENT, origin="agent:itinerary:waypoint",
130
+ confidence=_conf(s))))
131
+ return out
132
+
133
+ # ── 2. ThoughtChain 推理链 ───────────────────────────────
134
+ def _from_thoughtchain(self, text, top_k):
135
+ out = []
136
+ tc = self.thought_chain
137
+ if tc is None or not hasattr(tc, "search_nodes"):
138
+ return out
139
+ for n in tc.search_nodes(text, limit=top_k * 2):
140
+ content = getattr(n, "content", "")
141
+ s = _score(text, content)
142
+ out.append((max(s, 1), RawEvidence(
143
+ id=f"agent:think:{getattr(n,'id','')}", fact=f"推理[{getattr(n,'agent_id','')}] {content}",
144
+ source=SourceType.AGENT, origin=f"agent:thoughtchain:{getattr(n,'agent_id','')}",
145
+ confidence=_conf(max(s, 1)))))
146
+ return out
147
+
148
+ # ── 3. 各 Agent 的 HMR 记忆 ─────────────────────────────
149
+ def _from_memories(self, text, top_k):
150
+ out = []
151
+ if self.hmr is None:
152
+ return out
153
+ for aid in (self.agents.keys() if self.agents else []):
154
+ try:
155
+ results = self.hmr.recall(query=text, agent_id=aid, top_k=top_k)
156
+ except Exception:
157
+ continue
158
+ for r in results or []:
159
+ content = r.get("content", "") or r.get("title", "")
160
+ if not content:
161
+ continue
162
+ s = _score(text, content) or 1
163
+ out.append((s, RawEvidence(
164
+ id=f"agent:mem:{aid}:{r.get('id','')}",
165
+ fact=f"[{aid}记忆] {content}", source=SourceType.AGENT,
166
+ origin=f"agent:{aid}:hmr:{r.get('id','')}",
167
+ confidence=_conf(s))))
168
+ return out
169
+
170
+ # ── 4. 能力/状态目录 ─────────────────────────────────────
171
+ def _from_capabilities(self, text, top_k):
172
+ out = []
173
+ reg = self.skill_registry
174
+ if reg is not None and hasattr(reg, "catalog"):
175
+ for sk in reg.catalog():
176
+ desc = f"技能 {sk.get('name','')} 触发 {','.join(sk.get('triggers',[]) or [])} " \
177
+ f"{sk.get('kind','')}/{sk.get('side_effect','')}"
178
+ s = _score(text, desc)
179
+ if s:
180
+ out.append((s, RawEvidence(id=f"agent:skill:{sk.get('name','')}",
181
+ fact=desc.strip(), source=SourceType.AGENT,
182
+ origin="agent:capability:skill", confidence=_conf(s))))
183
+ for aid, agent in (self.agents or {}).items():
184
+ try:
185
+ st = agent.get_status()
186
+ except Exception:
187
+ st = {}
188
+ desc = f"Agent {aid} 角色 {st.get('role', st.get('name',''))} 状态 {st.get('status','')}"
189
+ s = _score(text, desc)
190
+ if s:
191
+ out.append((s, RawEvidence(id=f"agent:status:{aid}", fact=desc.strip(),
192
+ source=SourceType.AGENT, origin=f"agent:{aid}:status",
193
+ confidence=_conf(s))))
194
+ return out
@@ -0,0 +1,113 @@
1
+ """
2
+ Evidence Fusion —— 把离散、可能重复/冲突的原始证据,
3
+ 构建成一张有 support/contradict/grounded_by 关系的证据图,并算出整体置信度。
4
+
5
+ 相似度/矛盾判断已**语义化**(2026-07-16),与 KEE 冲突检测同款、共用同一份 ST 模型与向量缓存:
6
+ · 相似度:sentence-transformers 余弦;无 ST/模型未缓存 → 回退 SequenceMatcher(降级不崩)
7
+ · 矛盾:否定词方向 —— ASCII 按**词边界**、中文按子串
8
+ 对外接口(build / confidence)保持不变。
9
+
10
+ 修掉的两个真问题(原为"规则占位"):
11
+ ① 否定词子串误命中:原 ("不是","并非","no longer","not") 是子串匹配 →
12
+ "another"/"notebook" 里的 "not" 被命中 → 无关证据被判**矛盾** → 而 confidence()
13
+ 按矛盾数扣分(0.1/条)→ 搜索置信被**幻影矛盾**无端拉低。
14
+ (与 hkc_kee/kee.py 早已修过的 not⊂nothing / no⊂know 是同一 bug 家族,此处漏同步。)
15
+ ② 字符级相似度非语义:「机器学习很有用」vs「ML 非常有价值」=0.13、
16
+ 「今天下雨」vs「今日降雨」=0.50(<0.6)→ 语义等价的证据**识别不出互相支持**。
17
+ """
18
+ from __future__ import annotations
19
+ import os
20
+ import re
21
+ from difflib import SequenceMatcher
22
+ from .models import RawEvidence, EvidenceGraph, EvidenceNode, EvidenceEdge
23
+
24
+ _SIMILARITY_SUPPORT = 0.6
25
+ _SIMILARITY_DUP = 0.92
26
+
27
+ # 否定词:ASCII 按词边界(防 not⊂another/notebook、no⊂know);中文按子串(中文无词边界)
28
+ _NEG_ASCII = ("not", "no longer", "never", "cannot", "isn't", "aren't", "won't")
29
+ _NEG_CJK = ("不是", "并非", "并不", "没有", "无法", "不会", "未能")
30
+ _NEG_ASCII_RE = re.compile(
31
+ r"\b(" + "|".join(re.escape(n) for n in sorted(_NEG_ASCII, key=len, reverse=True)) + r")\b")
32
+
33
+
34
+ def _has_neg(text: str) -> bool:
35
+ t = (text or "").lower()
36
+ return bool(_NEG_ASCII_RE.search(t)) or any(n in t for n in _NEG_CJK)
37
+
38
+
39
+ def _sem_model_name() -> str:
40
+ # 【P2·单 embedding 入口】解析链:与 harness.py 一致 ——
41
+ # 显式传参 > EMBEDDING_MODEL(单入口) > HKC_EMBEDDING_MODEL > FOXCLAW_EVIDENCE_MODEL > HKC_KEE_SEMANTIC_MODEL > MiniLM 兜底。
42
+ # 与 KEE 默认同名 → get_st_model 共享同一实例;可经环境变量换任意 ST 模型(模型无关)
43
+ return (os.environ.get("FOXCLAW_EVIDENCE_MODEL")
44
+ or os.environ.get("EMBEDDING_MODEL")
45
+ or os.environ.get("HKC_EMBEDDING_MODEL")
46
+ or os.environ.get("HKC_KEE_SEMANTIC_MODEL")
47
+ or "paraphrase-multilingual-MiniLM-L12-v2")
48
+
49
+
50
+ def _semantic_sim(a: str, b: str):
51
+ """ST 余弦 ∈ [0,1](负相关截到 0)。取不到模型 → None(调用方回退字符级)。
52
+ 走 HKC 共享向量缓存 → 同文本全局只编码一次(与检索/KEE/去重同一份)。"""
53
+ try:
54
+ from hkc_search.embedding_backends import get_st_model, encode_cached
55
+ name = _sem_model_name()
56
+ model = get_st_model(name)
57
+ if model is None:
58
+ return None
59
+ import numpy as np
60
+ va, vb = encode_cached(model, name, [a or "", b or ""])
61
+ return max(0.0, min(1.0, float(np.dot(va, vb))))
62
+ except Exception:
63
+ return None
64
+
65
+
66
+ class EvidenceFusion:
67
+ def build(self, raw_list: list[RawEvidence]) -> EvidenceGraph:
68
+ graph = EvidenceGraph()
69
+
70
+ for e in self._dedup(raw_list):
71
+ graph.add_node(EvidenceNode(
72
+ id=e.id, fact=e.fact, source=e.source,
73
+ origin=e.origin, confidence=e.confidence,
74
+ grounded_by=e.grounded_by,
75
+ ))
76
+
77
+ nodes = list(graph.nodes.values())
78
+ for i, a in enumerate(nodes):
79
+ for b in nodes[i + 1:]:
80
+ sim = self._similarity(a.fact, b.fact)
81
+ if sim >= _SIMILARITY_SUPPORT:
82
+ graph.add_edge(EvidenceEdge(a.id, b.id, "supports"))
83
+ elif self._contradicts(a.fact, b.fact):
84
+ graph.add_edge(EvidenceEdge(a.id, b.id, "contradicts"))
85
+
86
+ for node in nodes:
87
+ if node.grounded_by and node.grounded_by in graph.nodes:
88
+ graph.add_edge(EvidenceEdge(node.id, node.grounded_by, "grounded_by"))
89
+
90
+ return graph
91
+
92
+ def confidence(self, graph: EvidenceGraph) -> float:
93
+ if not graph.nodes:
94
+ return 0.0
95
+ base = sum(n.confidence for n in graph.nodes.values()) / len(graph.nodes)
96
+ penalty = 0.1 * len(graph.contradictions())
97
+ return max(0.0, min(1.0, base - penalty))
98
+
99
+ def _dedup(self, raw_list: list[RawEvidence]) -> list[RawEvidence]:
100
+ kept: list[RawEvidence] = []
101
+ for e in raw_list:
102
+ if not any(self._similarity(e.fact, k.fact) >= _SIMILARITY_DUP for k in kept):
103
+ kept.append(e)
104
+ return kept
105
+
106
+ def _similarity(self, a: str, b: str) -> float:
107
+ """语义余弦优先;无 ST/加载失败 → 回退字符级 SequenceMatcher(降级不崩)。"""
108
+ v = _semantic_sim(a, b)
109
+ return v if v is not None else SequenceMatcher(None, a, b).ratio()
110
+
111
+ def _contradicts(self, a: str, b: str) -> bool:
112
+ """主题相近但**否定方向相反** → 矛盾。否定词按词边界判,不再被 another/notebook 误命中。"""
113
+ return _has_neg(a) != _has_neg(b) and self._similarity(a, b) >= 0.4
@@ -0,0 +1,50 @@
1
+ """
2
+ BaiduWebSearchPort —— 把已有的 foxclaw.core.web_search(百度 AI 搜索)
3
+ 包装成 HSR 的外部检索端口(ExternalSearchPort),接入 Retriever。
4
+
5
+ 没有另起一套联网搜索实现:Foxclaw 已经在用百度 AI 搜索
6
+ (foxclaw/core/web_search.py,Agent 的 `_web_augment` 也在用它),
7
+ 这里只是把它的返回结果(answer + references)转成 RawEvidence,
8
+ 让 HSR 的证据融合/策略循环可以复用它,而不是绕开它另建一套。
9
+
10
+ 未配置 BAIDU_SEARCH_KEY 或网络不通时,底层 web_search.search_answer()
11
+ 本身就会静默返回空 dict,这里顺势返回空列表,不影响 HSR 主流程。
12
+ """
13
+ from __future__ import annotations
14
+ from ..core import web_search as _ws
15
+ from .models import RawEvidence, SourceType
16
+
17
+
18
+ class BaiduWebSearchPort:
19
+ name = "baidu_web_search"
20
+
21
+ def search(self, text: str, top_k: int = 5) -> list[RawEvidence]:
22
+ payload = _ws.search_answer(text)
23
+ if not payload:
24
+ return []
25
+
26
+ out: list[RawEvidence] = []
27
+
28
+ answer = payload.get("answer", "")
29
+ if answer:
30
+ out.append(RawEvidence(
31
+ id="baidu:answer",
32
+ fact=answer,
33
+ source=SourceType.WEB,
34
+ origin="baidu_ai_search",
35
+ confidence=0.65,
36
+ ))
37
+
38
+ for i, ref in enumerate(payload.get("references", [])[:top_k]):
39
+ snippet = ref.get("content", "") or ref.get("abstract", "")
40
+ if not snippet:
41
+ continue
42
+ out.append(RawEvidence(
43
+ id=f"baidu:ref:{i}",
44
+ fact=snippet,
45
+ source=SourceType.WEB,
46
+ origin=ref.get("url", "") or ref.get("web_anchor", ""),
47
+ confidence=0.55,
48
+ ))
49
+
50
+ return out
@@ -0,0 +1,60 @@
1
+ """
2
+ HKCReadPort —— 把 foxclaw-hkc 的检索能力接入 HSR 的 knowledge_port。
3
+
4
+ 融合之后:HKC 是 Foxclaw 内部的一个模块,不是外挂的独立系统。
5
+ Foxclaw 启动时会在同一个 Python 进程里直接装配 HKCContainer
6
+ (`foxclaw/__init__.py` 里的 `_init_hkc()`),这里只是拿 hkc_sdk 的
7
+ DirectClient 包一层,调用的是同进程里的对象方法,不发 HTTP 请求、
8
+ 不需要单独起服务、不存在"服务没启动"这种降级场景。
9
+
10
+ 这一层只暴露 query(),没有 ingest_text()——写不写知识回 HKC
11
+ 不是 HSR 的职责范围,那是 foxclaw-hkc 自己的 KDE/KEE 摄入管线,
12
+ 或者未来 Runtime 层的决定。
13
+
14
+ 如果确实需要把 foxclaw-hkc 单独部署成另一个服务(比如给其它系统复用),
15
+ client 换成 hkc_sdk.connect(base_url=...) 返回的 HTTPClient 依然能用——
16
+ DirectClient/HTTPClient 方法签名完全一致,这里的代码不用改一行。
17
+ """
18
+ from __future__ import annotations
19
+ import logging
20
+
21
+ from .models import RawEvidence, SourceType
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class HKCReadPort:
27
+ """只读查询 foxclaw-hkc。需要一个已经连好的 hkc_sdk 客户端
28
+ (DirectClient 或 HTTPClient,两者接口一致);没有就永远返回空列表,
29
+ 不影响 HSR 主流程——和整个项目的降级哲学一致(没配就跳过,不报错)。"""
30
+
31
+ def __init__(self, client=None):
32
+ self._client = client
33
+
34
+ @property
35
+ def available(self) -> bool:
36
+ return self._client is not None
37
+
38
+ def query(self, text: str, top_k: int = 5) -> list[RawEvidence]:
39
+ if self._client is None:
40
+ return []
41
+
42
+ try:
43
+ hits = self._client.search(text, mode="hybrid", top_k=top_k)
44
+ except Exception as e:
45
+ logger.info(f"[HKC] 搜索「{text}」失败({e})")
46
+ return []
47
+
48
+ out: list[RawEvidence] = []
49
+ for h in hits:
50
+ fact = (h.summary or h.name or "").strip()
51
+ if not fact:
52
+ continue
53
+ out.append(RawEvidence(
54
+ id=h.ku_id,
55
+ fact=fact,
56
+ source=SourceType.KNOWLEDGE,
57
+ origin=f"hkc:{h.ku_id}",
58
+ confidence=float(h.score) if h.score else 0.6,
59
+ ))
60
+ return out
@@ -0,0 +1,73 @@
1
+ """
2
+ Internal Search Engine —— HSR-I 路由的内部检索器
3
+
4
+ 串联 HMR(记忆)与 HKC(知识,在同一进程内直连,Foxclaw 启动时装配好后
5
+ 通过 knowledge_port 传进来):
6
+
7
+ 1. 若配置了 knowledge_port:先查它拿权威知识候选
8
+ 2. 查 HMR 拿原始记忆,补充上下文 + 给①做溯源材料
9
+ 3. 交叉关联:knowledge 候选如果能在 HMR 里找到对应记忆,
10
+ 标记 grounded_by;找不到就做置信度惩罚(孤证)
11
+ 4. 没配置 knowledge_port / 没查到候选时,自然退化为纯 HMR 检索
12
+
13
+ knowledge_port 只需要实现 query(text, top_k) -> list[RawEvidence],
14
+ 这里不关心它具体是 foxclaw-hkc、还是别的什么知识源。
15
+ """
16
+ from __future__ import annotations
17
+ from difflib import SequenceMatcher
18
+ from .models import RawEvidence
19
+
20
+ _GROUNDING_SIMILARITY = 0.5
21
+ _UNGROUNDED_PENALTY = 0.15
22
+
23
+
24
+ class InternalSearchEngine:
25
+ def __init__(self, memory_port=None, knowledge_port=None, agent_port=None):
26
+ self.memory_port = memory_port # 只读查询 HMR:query(text, top_k) -> list[RawEvidence]
27
+ self.knowledge_port = knowledge_port # HKC 只读查询接口(同进程 DirectClient)
28
+ self.agent_port = agent_port # Agent 源:领域内核 + 推理链 + 各Agent记忆 + 能力/状态
29
+
30
+ @staticmethod
31
+ def _safe_query(port, text: str, top_k: int) -> list[RawEvidence]:
32
+ """【深审修复·内部源隔离】逐个内部源 try/except → [],与 external 端口的隔离对称:
33
+ 任一内部源(知识/记忆/Agent)失败都只让该源返回空,**不掀翻整个 search**。
34
+ 现有三端口虽各自兜异常,但此处防御式隔离让"接入不兜异常的自定义 port"也安全。"""
35
+ if port is None:
36
+ return []
37
+ try:
38
+ return port.query(text, top_k) or []
39
+ except Exception:
40
+ return []
41
+
42
+ def search(self, text: str, top_k: int = 5) -> list[RawEvidence]:
43
+ from foxclaw.core.tracing import span as _span
44
+ with _span("search.internal", top_k=top_k):
45
+ knowledge_hits = self._safe_query(self.knowledge_port, text, top_k)
46
+ memory_hits = self._safe_query(self.memory_port, text, top_k)
47
+ agent_hits = self._safe_query(self.agent_port, text, top_k)
48
+
49
+ # 知识候选用「内部一切可溯源证据」(记忆 + Agent 态势)交叉验证:找得到就 grounded,
50
+ # 找不到就做孤证惩罚。这样 HKC 权威知识与 Agent 实际持有的数据互为印证。
51
+ if knowledge_hits:
52
+ knowledge_hits = self._cross_link(knowledge_hits, memory_hits + agent_hits)
53
+
54
+ return knowledge_hits + memory_hits + agent_hits
55
+
56
+ def _cross_link(self, knowledge_hits, memory_hits) -> list[RawEvidence]:
57
+ linked = []
58
+ for k in knowledge_hits:
59
+ trace = self._find_trace(k, memory_hits)
60
+ if trace:
61
+ k.grounded_by = trace.id
62
+ else:
63
+ k.confidence = max(0.0, k.confidence - _UNGROUNDED_PENALTY)
64
+ linked.append(k)
65
+ return linked
66
+
67
+ def _find_trace(self, knowledge: RawEvidence, memory_hits: list[RawEvidence]):
68
+ best, best_sim = None, 0.0
69
+ for m in memory_hits:
70
+ sim = SequenceMatcher(None, knowledge.fact, m.fact).ratio()
71
+ if sim >= _GROUNDING_SIMILARITY and sim > best_sim:
72
+ best, best_sim = m, sim
73
+ return best
@@ -0,0 +1,129 @@
1
+ """
2
+ Foxclaw — Search 内核数据模型 (HSR)
3
+
4
+ 设计原则:这一层只产出"证据",不做任何持久化决策。
5
+ 是否把搜索结果沉淀回 HMR、是否提交知识库(未来的 foxclaw-hkc),
6
+ 由调用方(Agent / Runtime)决定,这里的数据结构都是只读产物。
7
+ """
8
+ from __future__ import annotations
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum
11
+ from typing import Any, Optional
12
+
13
+
14
+ class RouteType(str, Enum):
15
+ INTERNAL = "HSR-I" # 查内部(HMR 记忆 / 未来的 HKC 知识)
16
+ EXTERNAL = "HSR-E" # 查外部世界(联网搜索)
17
+ HYBRID = "HYBRID" # 内外混合 + 交叉验证
18
+
19
+
20
+ class SourceType(str, Enum):
21
+ MEMORY = "memory" # 来自 HMR(只读查询)
22
+ KNOWLEDGE = "knowledge" # 来自 HKC(通过 foxclaw-hkc 的 hkc_sdk 只读查询)
23
+ AGENT = "agent" # 来自五个 Agent 持有的内部数据:领域内核(财务/学习/行程)
24
+ # + ThoughtChain 推理链 + 各 Agent HMR 记忆 + 能力/状态目录
25
+ WEB = "web" # 来自联网搜索(当前接的是百度 AI 搜索)
26
+
27
+
28
+ @dataclass
29
+ class SubQuery:
30
+ id: str
31
+ text: str
32
+ source_hint: Optional[SourceType] = None
33
+ depends_on: list[str] = field(default_factory=list)
34
+
35
+
36
+ @dataclass
37
+ class SearchPlan:
38
+ route: RouteType
39
+ steps: list[SubQuery]
40
+ reasoning: str = ""
41
+
42
+
43
+ @dataclass
44
+ class RawEvidence:
45
+ id: str
46
+ fact: str
47
+ source: SourceType
48
+ origin: str # 具体来源:hmr 记忆 id / 网页 url
49
+ confidence: float = 0.5
50
+ raw: Any = None
51
+ grounded_by: Optional[str] = None # 指向另一条证据的 id:
52
+ # "这条知识由这条 HMR 记忆溯源支撑"
53
+
54
+
55
+ @dataclass
56
+ class EvidenceNode:
57
+ id: str
58
+ fact: str
59
+ source: SourceType
60
+ origin: str
61
+ confidence: float
62
+ grounded_by: Optional[str] = None
63
+
64
+
65
+ @dataclass
66
+ class EvidenceEdge:
67
+ src: str
68
+ dst: str
69
+ relation: str # "supports" | "contradicts" | "grounded_by"
70
+
71
+
72
+ @dataclass
73
+ class EvidenceGraph:
74
+ nodes: dict[str, EvidenceNode] = field(default_factory=dict)
75
+ edges: list[EvidenceEdge] = field(default_factory=list)
76
+
77
+ def add_node(self, node: EvidenceNode) -> None:
78
+ self.nodes[node.id] = node
79
+
80
+ def add_edge(self, edge: EvidenceEdge) -> None:
81
+ self.edges.append(edge)
82
+
83
+ def contradictions(self) -> list[EvidenceEdge]:
84
+ return [e for e in self.edges if e.relation == "contradicts"]
85
+
86
+ def to_dict(self) -> dict:
87
+ """给 API / ThoughtChain 用的精简序列化。"""
88
+ return {
89
+ "nodes": [
90
+ {
91
+ "id": n.id, "fact": n.fact, "source": n.source.value,
92
+ "origin": n.origin, "confidence": round(n.confidence, 2),
93
+ "grounded_by": n.grounded_by,
94
+ }
95
+ for n in self.nodes.values()
96
+ ],
97
+ "edges": [
98
+ {"src": e.src, "dst": e.dst, "relation": e.relation}
99
+ for e in self.edges
100
+ ],
101
+ }
102
+
103
+
104
+ @dataclass
105
+ class StrategyDecision:
106
+ action: str # "continue" | "stop" | "switch_query" | "switch_source"
107
+ reason: str
108
+ next_steps: list[SubQuery] = field(default_factory=list)
109
+
110
+
111
+ @dataclass
112
+ class SearchResult:
113
+ """search() 的唯一返回值。到这里为止,Search 内核不再做任何事。"""
114
+ query: str
115
+ route: RouteType
116
+ graph: EvidenceGraph
117
+ iterations: int
118
+ confidence: float
119
+ trace: list[str] = field(default_factory=list)
120
+
121
+ def to_dict(self) -> dict:
122
+ return {
123
+ "query": self.query,
124
+ "route": self.route.value,
125
+ "confidence": round(self.confidence, 2),
126
+ "iterations": self.iterations,
127
+ "graph": self.graph.to_dict(),
128
+ "trace": self.trace,
129
+ }
@@ -0,0 +1,44 @@
1
+ """
2
+ Query Planner —— 决定路由(HSR-I / HSR-E / HYBRID),并把任务拆成多跳检索步骤。
3
+
4
+ 当前是规则实现(触发词 + 标点拆句),接口(SearchPlan)保持稳定,
5
+ 未来要换成 LLM function-calling 结构化拆解时,只需替换这个文件内部实现,
6
+ 不影响 Retriever / EvidenceFusion / SearchStrategy。
7
+ """
8
+ from __future__ import annotations
9
+ from .models import SearchPlan, SubQuery, RouteType
10
+ from ..planning import planner as P # 统一拆分原语(P1-2)
11
+
12
+ _MEMORY_HINTS = ("我之前", "我们讨论过", "上次", "记得", "history", "之前说")
13
+ _HYBRID_HINTS = ("为什么", "对比", "分析", "综合", "评估")
14
+
15
+
16
+ class QueryPlanner:
17
+ def route(self, query: str, context: dict | None = None) -> RouteType:
18
+ if any(h in query for h in _MEMORY_HINTS):
19
+ return RouteType.INTERNAL
20
+ if any(h in query for h in _HYBRID_HINTS):
21
+ return RouteType.HYBRID
22
+ return RouteType.EXTERNAL
23
+
24
+ def plan(self, query: str, context: dict | None = None) -> SearchPlan:
25
+ route = self.route(query, context)
26
+
27
+ if route == RouteType.INTERNAL:
28
+ steps = [SubQuery(id="s1", text=query)]
29
+ elif route == RouteType.EXTERNAL:
30
+ steps = self._decompose(query)
31
+ else: # HYBRID:先查内部,再拆外部子问题
32
+ steps = [SubQuery(id="s1", text=query), *self._decompose(query, offset=2)]
33
+
34
+ return SearchPlan(
35
+ route=route,
36
+ steps=steps,
37
+ reasoning=f"route={route.value}, steps={len(steps)}",
38
+ )
39
+
40
+ def _decompose(self, query: str, offset: int = 1) -> list[SubQuery]:
41
+ parts = P.split_on(query, P.LIST_SEPS) # 统一拆分原语(等价于原 [,,、;;])
42
+ if len(parts) <= 1:
43
+ parts = [query]
44
+ return [SubQuery(id=f"s{offset + i}", text=p) for i, p in enumerate(parts)]