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,232 @@
1
+ """
2
+ LLM 驱动规划器(P0-1)—— 把主管的多步任务拆解从「规则拆分」升级为 LLM function-calling 规划。
3
+
4
+ 病根(与 docs/internal/TOOL_USE_PLAN.md 同源):planning/planner.py 的拆解靠
5
+ 标点/连接词/触发词("然后""同时""接着"),只能做表面切分,无法理解任务的语义结构;
6
+ LLM 从没机会决定「这个任务该拆成几步、每步派给哪个 Agent」。于是「帮我查最近一周支出,
7
+ 再据此定下月预算,最后整理成邮件」这类真实任务,规则拆解经常切错边界、派错 Agent。
8
+
9
+ 本模块给出单一真源:把「规划」声明成一个 function-calling 工具(submit_plan),交给
10
+ LLM 自主拆解,返回结构化计划(每步带目标 Agent 与依赖),由 SupervisorAgent 转成
11
+ Plan 兼容对象。
12
+
13
+ 后半段「反思重规划」:多步执行出现失败/未完成时,再调一次 submit_plan,把上一轮
14
+ 各步结果作为反馈喂给 LLM,让它决定「换 Agent / 换表述 / 拆更细 / 补前置 / 放弃」,
15
+ 产出修正计划并重新执行(llm_replan)。这就是 plan → 执行 → 反思 → 重规划的闭环。
16
+
17
+ opt-in + fail-safe(与 FOXCLAW_TOOL_USE 同哲学,默认关、可逆):
18
+ · FOXCLAW_LLM_PLAN=1 才开启初次规划;
19
+ · FOXCLAW_LLM_REPLAN=1 才开启反思重规划;
20
+ · provider 不支持 tools / 调用失败 / 模型未产出有效计划 → 返回 None,
21
+ 上层自动回退(规划回退规则拆解;重规划回退「保留原结果」)。
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import os
26
+
27
+
28
+ def _env_flag(name: str) -> bool:
29
+ return os.environ.get(name, "0").strip() in ("1", "true", "True", "yes", "on")
30
+
31
+
32
+ def llm_plan_enabled() -> bool:
33
+ """是否启用 LLM 规划。默认关(behavior-preserving / 可逆)。"""
34
+ return _env_flag("FOXCLAW_LLM_PLAN")
35
+
36
+
37
+ def replan_enabled() -> bool:
38
+ """是否启用反思重规划。默认关(behavior-preserving / 可逆)。"""
39
+ return _env_flag("FOXCLAW_LLM_REPLAN")
40
+
41
+
42
+ _PLAN_SYSTEM = (
43
+ "你是 Foxclaw 的任务规划器。唯一职责:把用户任务拆解成可执行计划,"
44
+ "并为每个子任务指派最合适的 Agent。\n"
45
+ "规则:\n"
46
+ "1. 只拆解真正需要多步或跨领域协作的任务;简单单步任务就返回单个 step。\n"
47
+ "2. 每个子任务独立可执行、边界清晰、动词开头、不含模糊表述。\n"
48
+ "3. 子任务有先后依赖(后一步要用前一步的结果)→ flow=sequential;"
49
+ "彼此独立可同时做 → flow=parallel。\n"
50
+ "4. 必须通过 submit_plan 工具提交计划,不要输出计划之外的任何文本。\n\n"
51
+ "可派发的 Agent 及其职责:\n{agent_desc}"
52
+ )
53
+
54
+ _REPLAN_SYSTEM = (
55
+ "你是 Foxclaw 的反思规划器。上一轮多步计划的执行结果已经给出,其中存在失败或未完成的步骤。"
56
+ "你的唯一职责:分析失败原因,产出修正后的计划,让任务最终完成。\n"
57
+ "规则:\n"
58
+ "1. 只重排**失败/未完成**的步骤,已经成功的步骤不要重复执行。\n"
59
+ "2. 可采取的修正手段:换 Agent、换表述、拆得更细、补前置步骤、合并步骤。\n"
60
+ "3. 若确认任务无法完成(超出能力 / 信息不足 / 反复失败),返回空 steps 数组表示放弃。\n"
61
+ "4. 必须通过 submit_plan 工具提交计划,不要输出计划之外的任何文本。\n\n"
62
+ "可派发的 Agent 及其职责:\n{agent_desc}"
63
+ )
64
+
65
+
66
+ def build_plan_tool(agent_catalog: dict) -> dict:
67
+ """构造 submit_plan 的 function schema(OpenAI tools 通用格式)。
68
+
69
+ agent_catalog: {agent_id: 职责描述},用于约束 agent 字段的取值枚举,
70
+ 让 LLM 只能在真实存在的 Agent 里选,避免幻觉出不存在的能力。
71
+
72
+ 工具描述保持中性(planning / replanning 共用同一工具),具体行为由
73
+ system prompt(_PLAN_SYSTEM 或 _REPLAN_SYSTEM)引导。
74
+ """
75
+ agent_ids = list(agent_catalog.keys())
76
+ return {
77
+ "type": "function",
78
+ "function": {
79
+ "name": "submit_plan",
80
+ "description": (
81
+ "提交任务执行计划(planning/replanning 共用)。steps 为子任务列表,"
82
+ "每步含 name(一句话、动词开头) / agent(负责的 agent id) / depends_on"
83
+ "(依赖的前序步骤序号,0-based)。flow 为执行模式。若无需拆解或确认"
84
+ "无法完成,返回空 steps。"
85
+ ),
86
+ "parameters": {
87
+ "type": "object",
88
+ "properties": {
89
+ "flow": {
90
+ "type": "string",
91
+ "enum": ["single", "sequential", "parallel"],
92
+ "description": "single=单步;sequential=步骤有先后依赖;"
93
+ "parallel=步骤彼此独立可并行",
94
+ },
95
+ "steps": {
96
+ "type": "array",
97
+ "description": "子任务列表(flow=single 时仅 1 项)",
98
+ "items": {
99
+ "type": "object",
100
+ "properties": {
101
+ "name": {
102
+ "type": "string",
103
+ "description": "子任务的一句话描述,动词开头",
104
+ },
105
+ "agent": {
106
+ "type": "string",
107
+ "enum": agent_ids,
108
+ "description": "负责该子任务的 agent id",
109
+ },
110
+ "depends_on": {
111
+ "type": "array",
112
+ "items": {"type": "integer"},
113
+ "description": "依赖的前序步骤序号(0-based);"
114
+ "无依赖传空数组",
115
+ },
116
+ },
117
+ "required": ["name", "agent"],
118
+ },
119
+ },
120
+ },
121
+ "required": ["flow", "steps"],
122
+ },
123
+ },
124
+ }
125
+
126
+
127
+ def _agent_desc(agent_catalog: dict) -> str:
128
+ return "\n".join(f"- {a}:{d}" for a, d in agent_catalog.items())
129
+
130
+
131
+ def _invoke_plan_tool(router, system: str, user_content: str, agent_catalog: dict,
132
+ max_tokens: int):
133
+ """调用 submit_plan 工具并解析为 {intent, flow, steps}。
134
+
135
+ 返回 None = 不可用 / 不支持 tools / 调用失败 / 参数解析失败。
136
+ 成功时 steps 可能为空列表(模型判定「无需拆解」或「放弃」),由上层决定含义。
137
+ """
138
+ if not agent_catalog or not user_content or not str(user_content).strip():
139
+ return None
140
+ try:
141
+ tool = build_plan_tool(agent_catalog)
142
+ res = router.call_tools(
143
+ system,
144
+ [{"role": "user", "content": str(user_content).strip()}],
145
+ [tool],
146
+ tool_choice="auto",
147
+ max_tokens=max_tokens,
148
+ )
149
+ except Exception:
150
+ return None
151
+ if not res.get("supported"):
152
+ return None
153
+ calls = res.get("tool_calls") or []
154
+ if not calls:
155
+ return None
156
+ if calls[0].get("args_parse_failed"):
157
+ return None
158
+ args = calls[0].get("args") or {}
159
+ steps = args.get("steps")
160
+ if not isinstance(steps, list):
161
+ return None
162
+ norm = []
163
+ for s in steps:
164
+ if not isinstance(s, dict):
165
+ continue
166
+ name = str(s.get("name") or "").strip()
167
+ if not name:
168
+ continue
169
+ agent = str(s.get("agent") or "office").strip()
170
+ if agent not in agent_catalog:
171
+ agent = "office"
172
+ dep = s.get("depends_on") or []
173
+ if not isinstance(dep, list):
174
+ dep = []
175
+ norm.append({
176
+ "name": name,
177
+ "agent": agent,
178
+ "depends_on": [int(d) for d in dep if isinstance(d, (int, float))],
179
+ })
180
+ flow = str(args.get("flow") or "sequential").strip()
181
+ if flow not in ("single", "sequential", "parallel"):
182
+ flow = "sequential"
183
+ if len(norm) == 1:
184
+ flow = "single"
185
+ return {"intent": str(user_content).strip(), "flow": flow, "steps": norm}
186
+
187
+
188
+ def llm_plan(router, task: str, agent_catalog: dict, max_tokens: int = 1024):
189
+ """初次规划:用 function-calling 让 LLM 产出结构化计划。
190
+
191
+ 返回 {intent, flow, steps: [{name, agent, depends_on}]};
192
+ 返回 None = 不可用 / 不支持 tools / 调用失败 / 模型未产出有效计划(上层回退规则拆解)。
193
+ """
194
+ out = _invoke_plan_tool(
195
+ router,
196
+ _PLAN_SYSTEM.format(agent_desc=_agent_desc(agent_catalog)),
197
+ task, agent_catalog, max_tokens)
198
+ if not out or not out["steps"]:
199
+ return None
200
+ return out
201
+
202
+
203
+ def _format_prior_results(prior_results) -> str:
204
+ """把上一轮各步结果序列化为 LLM 可读的反馈文本。"""
205
+ if not prior_results:
206
+ return "(无执行结果)"
207
+ lines = []
208
+ for i, s in enumerate(prior_results, 1):
209
+ status = "失败" if (s.get("blocked") or s.get("failed")) else "成功"
210
+ lines.append(
211
+ f"第{i}步[{s.get('agent', '?')}]({status}):{s.get('subtask', '')}\n"
212
+ f" 输出:{s.get('output', '')}")
213
+ return "\n".join(lines)
214
+
215
+
216
+ def llm_replan(router, task: str, prior_results, agent_catalog: dict,
217
+ max_tokens: int = 1024):
218
+ """反思重规划:把上一轮执行结果作为反馈,让 LLM 产出修正计划。
219
+
220
+ prior_results: 上一轮各步结果列表,形如 [{subtask, agent, output, blocked, failed}, ...]
221
+ 返回同 llm_plan 的结构;steps 为空 = 模型判定「放弃/无需重试」;
222
+ 返回 None = 不可用 / 不支持 / 失败 / 解析失败(上层保留原结果)。
223
+ """
224
+ feedback = _format_prior_results(prior_results)
225
+ user_content = (
226
+ f"原始任务:{task}\n\n"
227
+ f"上一轮执行结果:\n{feedback}\n\n"
228
+ f"请反思失败原因,产出修正后的计划(若确认无法完成则返回空 steps)。")
229
+ return _invoke_plan_tool(
230
+ router,
231
+ _REPLAN_SYSTEM.format(agent_desc=_agent_desc(agent_catalog)),
232
+ user_content, agent_catalog, max_tokens)
@@ -0,0 +1,94 @@
1
+ """
2
+ 统一规划原语(P1-2:三个 Planner 合并为一个引擎)。
3
+
4
+ 审计结论:主管路由(→子Agent)、AHS(→skill)、search QueryPlanner(→数据源)三处
5
+ 各自重复实现了同一套机制——否定词检测、标点拆分、marker 常量、触发词打分。
6
+ 本模块把这些机制收敛为**单一真源**,三处保留各自的公共入口与候选空间,内部委托到这里。
7
+
8
+ 不假设三者路由到同一目标:它们的候选集不同(agent/skill/source),但"怎么从文本里
9
+ 匹配、拆分、打分选候选"是同一套逻辑,这一套只应有一处定义。
10
+ """
11
+ from __future__ import annotations
12
+ import re
13
+ from dataclasses import dataclass, field
14
+ from typing import Optional
15
+
16
+ NEGATIONS = ["不", "别", "无需", "不要", "取消", "甭", "don't", "no need", "without"]
17
+ SPLIT_MARKERS = ["然后", "接着", "之后", "再", "最后", ";", ";"]
18
+ PARALLEL_TRIGGERS = ["同时", "分别", "并行", "一并"]
19
+ LIST_SEPS = ["、", ",", ",", ";", ";"]
20
+
21
+
22
+ def is_negated(task: str, trigger: str, window: int = 4) -> bool:
23
+ """触发词在任务中是否被否定词紧邻(前 window 字)修饰。"""
24
+ idx = (task or "").find(trigger)
25
+ if idx < 0:
26
+ return False
27
+ prefix = task[max(0, idx - window):idx]
28
+ return any(neg in prefix for neg in NEGATIONS)
29
+
30
+
31
+ def split_on(task: str, markers: list) -> list:
32
+ """按 markers 把文本切成非空片段(去空白)。"""
33
+ pat = "|".join(map(re.escape, markers))
34
+ return [p.strip() for p in re.split(pat, task or "") if p.strip()]
35
+
36
+
37
+ def keyword_hits(task: str, keywords: list, negation: bool = False) -> int:
38
+ """命中的关键词数(negation=True 时排除被否定词修饰的命中)。
39
+ 主管路由用 negation=False(保持原计数行为);AHS 触发打分用 negation=True。"""
40
+ task = task or ""
41
+ return sum(1 for kw in keywords
42
+ if kw in task and not (negation and is_negated(task, kw)))
43
+
44
+
45
+ @dataclass
46
+ class ScoredCandidate:
47
+ """打分候选:id + 触发词 + 描述(领域匹配)。三处把各自候选适配成它。"""
48
+ id: str
49
+ triggers: list = field(default_factory=list)
50
+ description: str = ""
51
+
52
+
53
+ @dataclass
54
+ class TriggerRoute:
55
+ primary: Optional[str] = None
56
+ confidence: float = 0.0
57
+ candidates: list = field(default_factory=list)
58
+ fallback: Optional[str] = None
59
+ needs_composition: bool = False
60
+ reason: str = ""
61
+
62
+
63
+ def trigger_route(task: str, candidates: list, fallback_id: str | None = None,
64
+ min_conf: float = 0.15, near_gap: float = 0.1) -> TriggerRoute:
65
+ """触发词路由三步打分:否定过滤后的触发命中(0.8)+ 描述领域匹配(0.2),确定性排序。
66
+ 这是 AHS 原 route 的算法,现作为三处共用的通用引擎。"""
67
+ if not candidates:
68
+ return TriggerRoute(reason="候选为空")
69
+
70
+ scored = []
71
+ for c in candidates:
72
+ triggers = c.triggers or []
73
+ if not triggers:
74
+ scored.append((c.id, 0.0)); continue
75
+ hits = keyword_hits(task, triggers, negation=True)
76
+ hit_score = 0.0 if hits == 0 else min(0.6 + 0.15 * (hits - 1), 1.0)
77
+ desc_words = [w for w in re.split(r"[\s,,、]", c.description or "") if len(w) >= 2]
78
+ domain_hit = sum(1 for w in desc_words if w in task)
79
+ domain_score = min(domain_hit / max(len(desc_words), 1), 1.0)
80
+ score = 0.8 * hit_score + 0.2 * domain_score
81
+ scored.append((c.id, round(score, 3)))
82
+
83
+ scored.sort(key=lambda x: (-x[1], x[0]))
84
+ top_id, top_score = scored[0]
85
+ cands = [(i, s) for i, s in scored if s > 0]
86
+
87
+ fb = fallback_id or candidates[-1].id
88
+ if top_score < min_conf:
89
+ return TriggerRoute(primary=fb, confidence=top_score, candidates=cands,
90
+ fallback=fb, reason="无显著匹配,使用兜底")
91
+ near = [c for c in cands if abs(c[1] - top_score) < near_gap]
92
+ return TriggerRoute(primary=top_id, confidence=top_score, candidates=cands,
93
+ fallback=fb, needs_composition=len(near) > 1,
94
+ reason=f"主候选 {top_id}(分 {top_score})")
@@ -0,0 +1,24 @@
1
+ """Foxclaw — Search: 认知搜索内核 (HSR)
2
+ 统一内部记忆(HMR)与外部联网检索,供五个 Agent 共享调用。"""
3
+ from .models import (
4
+ RouteType, SourceType, SubQuery, SearchPlan,
5
+ RawEvidence, EvidenceNode, EvidenceEdge, EvidenceGraph,
6
+ StrategyDecision, SearchResult,
7
+ )
8
+ from .planner import QueryPlanner
9
+ from .internal_search import InternalSearchEngine
10
+ from .external_search import BaiduWebSearchPort
11
+ from .hkc_read_port import HKCReadPort
12
+ from .agent_port import AgentSourcePort
13
+ from .retriever import Retriever
14
+ from .evidence import EvidenceFusion
15
+ from .strategy import SearchStrategy
16
+ from .runtime import SearchRuntime
17
+
18
+ __all__ = [
19
+ "RouteType", "SourceType", "SubQuery", "SearchPlan",
20
+ "RawEvidence", "EvidenceNode", "EvidenceEdge", "EvidenceGraph",
21
+ "StrategyDecision", "SearchResult",
22
+ "QueryPlanner", "InternalSearchEngine", "BaiduWebSearchPort", "HKCReadPort",
23
+ "AgentSourcePort", "Retriever", "EvidenceFusion", "SearchStrategy", "SearchRuntime",
24
+ ]
@@ -0,0 +1,190 @@
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):
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
+ def _from_finance(self, text, top_k):
73
+ out: list[tuple[float, RawEvidence]] = []
74
+ rt = self.finance
75
+ if rt is None:
76
+ return out
77
+ for i, txn in enumerate(getattr(rt, "transactions", []) or []):
78
+ desc = f"交易 {getattr(txn,'entity','')} {getattr(txn,'amount','')} " \
79
+ f"{getattr(txn,'category','')} {' '.join(getattr(txn,'tags',[]) or [])}"
80
+ s = _score(text, desc)
81
+ if s:
82
+ out.append((s, RawEvidence(id=f"agent:finance:txn:{i}", fact=desc.strip(),
83
+ source=SourceType.AGENT, origin="agent:finance:transaction",
84
+ confidence=_conf(s))))
85
+ for i, b in enumerate(getattr(rt, "budgets", []) or []):
86
+ desc = f"预算 {getattr(b,'category','')} 上限 {getattr(b,'monthly_limit','')}"
87
+ s = _score(text, desc)
88
+ if s:
89
+ out.append((s, RawEvidence(id=f"agent:finance:budget:{i}", fact=desc.strip(),
90
+ source=SourceType.AGENT, origin="agent:finance:budget",
91
+ confidence=_conf(s))))
92
+ for name, ent in (getattr(rt, "entities", {}) or {}).items():
93
+ desc = f"财务实体 {name} 累计 {getattr(ent,'total_amount','')} " \
94
+ f"次数 {getattr(ent,'txn_count','')}"
95
+ s = _score(text, desc)
96
+ if s:
97
+ out.append((s, RawEvidence(id=f"agent:finance:entity:{name}", fact=desc.strip(),
98
+ source=SourceType.AGENT, origin="agent:finance:entity",
99
+ confidence=_conf(s))))
100
+ return out
101
+
102
+ def _from_learning(self, text, top_k):
103
+ out: list[tuple[float, RawEvidence]] = []
104
+ rt = self.learning
105
+ if rt is None:
106
+ return out
107
+ for iid, item in (getattr(rt, "items", {}) or {}).items():
108
+ desc = f"知识 {getattr(item,'content','')} 主题 {getattr(item,'topic','')} " \
109
+ f"{' '.join(getattr(item,'tags',[]) or [])}"
110
+ s = _score(text, desc)
111
+ if s:
112
+ out.append((s, RawEvidence(id=f"agent:learning:{iid}", fact=desc.strip(),
113
+ source=SourceType.AGENT, origin=f"agent:learning:{iid}",
114
+ confidence=_conf(s))))
115
+ return out
116
+
117
+ def _from_itinerary(self, text, top_k):
118
+ out: list[tuple[float, RawEvidence]] = []
119
+ rt = self.itinerary
120
+ if rt is None:
121
+ return out
122
+ for i, wp in enumerate(getattr(rt, "waypoints", []) or []):
123
+ desc = f"行程点 {getattr(wp,'name','')} {getattr(wp,'address','')} " \
124
+ f"{getattr(wp,'city','')} {getattr(wp,'start_time','') or ''}"
125
+ s = _score(text, desc)
126
+ if s:
127
+ out.append((s, RawEvidence(id=f"agent:itinerary:{i}", fact=desc.strip(),
128
+ source=SourceType.AGENT, origin="agent:itinerary:waypoint",
129
+ confidence=_conf(s))))
130
+ return out
131
+
132
+ def _from_thoughtchain(self, text, top_k):
133
+ out: list[tuple[float, RawEvidence]] = []
134
+ tc = self.thought_chain
135
+ if tc is None or not hasattr(tc, "search_nodes"):
136
+ return out
137
+ for n in tc.search_nodes(text, limit=top_k * 2):
138
+ content = getattr(n, "content", "")
139
+ s = _score(text, content)
140
+ out.append((max(s, 1), RawEvidence(
141
+ id=f"agent:think:{getattr(n,'id','')}", fact=f"推理[{getattr(n,'agent_id','')}] {content}",
142
+ source=SourceType.AGENT, origin=f"agent:thoughtchain:{getattr(n,'agent_id','')}",
143
+ confidence=_conf(max(s, 1)))))
144
+ return out
145
+
146
+ def _from_memories(self, text, top_k):
147
+ out: list[tuple[float, RawEvidence]] = []
148
+ if self.hmr is None:
149
+ return out
150
+ for aid in (self.agents.keys() if self.agents else []):
151
+ try:
152
+ results = self.hmr.recall(query=text, agent_id=aid, top_k=top_k)
153
+ except Exception:
154
+ continue
155
+ for r in results or []:
156
+ content = r.get("content", "") or r.get("title", "")
157
+ if not content:
158
+ continue
159
+ s = _score(text, content) or 1
160
+ out.append((s, RawEvidence(
161
+ id=f"agent:mem:{aid}:{r.get('id','')}",
162
+ fact=f"[{aid}记忆] {content}", source=SourceType.AGENT,
163
+ origin=f"agent:{aid}:hmr:{r.get('id','')}",
164
+ confidence=_conf(s))))
165
+ return out
166
+
167
+ def _from_capabilities(self, text, top_k):
168
+ out = []
169
+ reg = self.skill_registry
170
+ if reg is not None and hasattr(reg, "catalog"):
171
+ for sk in reg.catalog():
172
+ desc = f"技能 {sk.get('name','')} 触发 {','.join(sk.get('triggers',[]) or [])} " \
173
+ f"{sk.get('kind','')}/{sk.get('side_effect','')}"
174
+ s = _score(text, desc)
175
+ if s:
176
+ out.append((s, RawEvidence(id=f"agent:skill:{sk.get('name','')}",
177
+ fact=desc.strip(), source=SourceType.AGENT,
178
+ origin="agent:capability:skill", confidence=_conf(s))))
179
+ for aid, agent in (self.agents or {}).items():
180
+ try:
181
+ st = agent.get_status()
182
+ except Exception:
183
+ st = {}
184
+ desc = f"Agent {aid} 角色 {st.get('role', st.get('name',''))} 状态 {st.get('status','')}"
185
+ s = _score(text, desc)
186
+ if s:
187
+ out.append((s, RawEvidence(id=f"agent:status:{aid}", fact=desc.strip(),
188
+ source=SourceType.AGENT, origin=f"agent:{aid}:status",
189
+ confidence=_conf(s))))
190
+ return out
@@ -0,0 +1,109 @@
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
+ _NEG_ASCII = ("not", "no longer", "never", "cannot", "isn't", "aren't", "won't")
28
+ _NEG_CJK = ("不是", "并非", "并不", "没有", "无法", "不会", "未能")
29
+ _NEG_ASCII_RE = re.compile(
30
+ r"\b(" + "|".join(re.escape(n) for n in sorted(_NEG_ASCII, key=len, reverse=True)) + r")\b")
31
+
32
+
33
+ def _has_neg(text: str) -> bool:
34
+ t = (text or "").lower()
35
+ return bool(_NEG_ASCII_RE.search(t)) or any(n in t for n in _NEG_CJK)
36
+
37
+
38
+ def _sem_model_name() -> str:
39
+ return (os.environ.get("FOXCLAW_EVIDENCE_MODEL")
40
+ or os.environ.get("EMBEDDING_MODEL")
41
+ or os.environ.get("HKC_EMBEDDING_MODEL")
42
+ or os.environ.get("HKC_KEE_SEMANTIC_MODEL")
43
+ or "paraphrase-multilingual-MiniLM-L12-v2")
44
+
45
+
46
+ def _semantic_sim(a: str, b: str):
47
+ """ST 余弦 ∈ [0,1](负相关截到 0)。取不到模型 → None(调用方回退字符级)。
48
+ 走 HKC 共享向量缓存 → 同文本全局只编码一次(与检索/KEE/去重同一份)。"""
49
+ try:
50
+ from hkc_search.embedding_backends import get_st_model, encode_cached
51
+ name = _sem_model_name()
52
+ model = get_st_model(name)
53
+ if model is None:
54
+ return None
55
+ import numpy as np
56
+ va, vb = encode_cached(model, name, [a or "", b or ""])
57
+ return max(0.0, min(1.0, float(np.dot(va, vb))))
58
+ except Exception:
59
+ return None
60
+
61
+
62
+ class EvidenceFusion:
63
+ def build(self, raw_list: list[RawEvidence]) -> EvidenceGraph:
64
+ graph = EvidenceGraph()
65
+
66
+ for e in self._dedup(raw_list):
67
+ graph.add_node(EvidenceNode(
68
+ id=e.id, fact=e.fact, source=e.source,
69
+ origin=e.origin, confidence=e.confidence,
70
+ grounded_by=e.grounded_by,
71
+ ))
72
+
73
+ nodes = list(graph.nodes.values())
74
+ for i, a in enumerate(nodes):
75
+ for b in nodes[i + 1:]:
76
+ sim = self._similarity(a.fact, b.fact)
77
+ if sim >= _SIMILARITY_SUPPORT:
78
+ graph.add_edge(EvidenceEdge(a.id, b.id, "supports"))
79
+ elif self._contradicts(a.fact, b.fact):
80
+ graph.add_edge(EvidenceEdge(a.id, b.id, "contradicts"))
81
+
82
+ for node in nodes:
83
+ if node.grounded_by and node.grounded_by in graph.nodes:
84
+ graph.add_edge(EvidenceEdge(node.id, node.grounded_by, "grounded_by"))
85
+
86
+ return graph
87
+
88
+ def confidence(self, graph: EvidenceGraph) -> float:
89
+ if not graph.nodes:
90
+ return 0.0
91
+ base = sum(n.confidence for n in graph.nodes.values()) / len(graph.nodes)
92
+ penalty = 0.1 * len(graph.contradictions())
93
+ return max(0.0, min(1.0, base - penalty))
94
+
95
+ def _dedup(self, raw_list: list[RawEvidence]) -> list[RawEvidence]:
96
+ kept: list[RawEvidence] = []
97
+ for e in raw_list:
98
+ if not any(self._similarity(e.fact, k.fact) >= _SIMILARITY_DUP for k in kept):
99
+ kept.append(e)
100
+ return kept
101
+
102
+ def _similarity(self, a: str, b: str) -> float:
103
+ """语义余弦优先;无 ST/加载失败 → 回退字符级 SequenceMatcher(降级不崩)。"""
104
+ v = _semantic_sim(a, b)
105
+ return v if v is not None else SequenceMatcher(None, a, b).ratio()
106
+
107
+ def _contradicts(self, a: str, b: str) -> bool:
108
+ """主题相近但**否定方向相反** → 矛盾。否定词按词边界判,不再被 another/notebook 误命中。"""
109
+ return _has_neg(a) != _has_neg(b) and self._similarity(a, b) >= 0.4