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,477 @@
1
+ """
2
+ VecRecall 适配层 — 可选记忆后端,对外接口与 `HMRAdapter` 完全一致。
3
+
4
+ 为什么要这一层:业务代码只认 `HMRAdapter` 的 9 个方法(ingest / recall /
5
+ recall_by_tag / delete_memory / get_workspace / save_workspace /
6
+ save_runtime_state / restore_runtime_state / recent / stats)。
7
+ `VecRecall` 的 API 形状与之不同,直接替换会让调用方炸在一个与被调用能力无关的地方。
8
+
9
+ 几处**按源码**而非按文档写的映射(v2.1 实测):
10
+ Foxclaw VecRecall 2.1
11
+ ───────────────────────── ───────────────────────────────────────────
12
+ ingest(...) add(content, memory_type=, visibility=, owner=, ...)
13
+ recall(query, agent_id=) build_context(query, viewer=agent_id) 的 L2+L3
14
+ recall_by_tag("preference") query_by_topic_key("preference")
15
+ delete_memory(id) delete(id)
16
+ recent(limit) list_memories(sort="recent", full=True)
17
+ get_workspace(agent_id) 无对应概念 → 返回空壳(字段无人读,见 agents/base.py)
18
+
19
+ 注意两点与直觉相反的地方:
20
+ 1. `VecRecall.recall(memory_id)` **不是检索**,是"复习并增强记忆强度"。检索入口是
21
+ `build_context`(带 viewer 隔离) / `search`(不做任何隔离)。用错名字会得到
22
+ TypeError 或越权召回。
23
+ 2. 隔离走 `owner` + `visibility`,在**召回前**生效 —— 这正好修掉 HMR 路径
24
+ "全局召回 K 条再按 tag 筛"的完整性缺陷(别人的记忆挤满 top-k 时自己的召不回)。
25
+ """
26
+ import os
27
+ import re
28
+ import sys
29
+ import threading
30
+ from pathlib import Path
31
+ from types import SimpleNamespace
32
+
33
+ from .shared_write import guard_shared_write
34
+ from typing import Any
35
+
36
+ _FOXCLAW_TO_VR = {
37
+ "concept": "semantic",
38
+ "project": "semantic",
39
+ "task": "semantic",
40
+ "agent_memory": "semantic",
41
+ "reflection": "semantic",
42
+ "workflow": "procedural",
43
+ "decision": "episodic",
44
+ "runtime": "episodic",
45
+ "execution": "episodic",
46
+ }
47
+ _VR_TO_FOXCLAW = {
48
+ "semantic": "concept",
49
+ "procedural": "workflow",
50
+ "episodic": "concept",
51
+ }
52
+ _VALID_TYPES = set(_FOXCLAW_TO_VR)
53
+
54
+ _PREFERENCE_TAG = "preference"
55
+
56
+
57
+ def _ensure_vendor_importable():
58
+ """把 vendor/ 与 vendor/hkc 加入 sys.path,使内嵌 HKC 的共享 embedding 可用。"""
59
+ vendor = Path(__file__).resolve().parent.parent.parent / "vendor"
60
+ for p in (vendor, vendor / "hkc"):
61
+ if p.exists() and str(p) not in sys.path:
62
+ sys.path.insert(0, str(p))
63
+
64
+
65
+ class _HKCSharedEmbedding:
66
+ """与 HMR 共用同一个 SentenceTransformer 实例。
67
+
68
+ 两套后端跑同一个模型才能让影子对比有意义(维度、分数可比);若各自
69
+ `SentenceTransformer(...)` 一次,bge-m3 级别的模型会多占约 1GB 常驻内存。
70
+ HKC 的 `get_st_model` 已经是进程级缓存,这里只做一层薄包装。
71
+ """
72
+
73
+ backend = "hkc-shared"
74
+
75
+ def __init__(self, model_name: str):
76
+ self.model_name = model_name
77
+ self._model = None
78
+ self._dim: int | None = None
79
+
80
+ def _load(self):
81
+ if self._model is None:
82
+ _ensure_vendor_importable()
83
+ from hkc_search.embedding_backends import get_st_model
84
+ model = get_st_model(self.model_name)
85
+ if model is None:
86
+ raise RuntimeError(
87
+ f"embedding 模型不可用: {self.model_name}(HKC 共享缓存加载失败)")
88
+ self._model = model
89
+ return self._model
90
+
91
+ def embed(self, text: str):
92
+ vec = self._load().encode([text or ""], normalize_embeddings=True)[0]
93
+ return [float(x) for x in vec]
94
+
95
+ def embed_batch(self, texts):
96
+ arr = self._load().encode(list(texts), normalize_embeddings=True)
97
+ return [[float(x) for x in row] for row in arr]
98
+
99
+ @property
100
+ def dim(self):
101
+ if self._dim is None:
102
+ try:
103
+ model = self._load()
104
+ getter = (getattr(model, "get_embedding_dimension", None)
105
+ or getattr(model, "get_sentence_embedding_dimension", None))
106
+ self._dim = (int(getter() or 0) or None) if getter else None
107
+ except Exception:
108
+ self._dim = None
109
+ return self._dim
110
+
111
+
112
+ class VecRecallAdapter:
113
+ """把 vecRecall v2.1 包装成 Foxclaw 期望的记忆接口。"""
114
+
115
+ backend = "vecRecall"
116
+
117
+ def __init__(self, storage_path: str = "./vecrecall_data", wing: str = "foxclaw",
118
+ use_chain: bool = False, embedding_model: str | None = None,
119
+ dedup: bool = True, embedding_backend=None, vector_backend=None):
120
+ """`embedding_backend` / `vector_backend` 用于注入(测试传轻量实现)。
121
+
122
+ 默认为 None 时走生产配置:HKC 共享的 SentenceTransformer + vecRecall 默认
123
+ 的 sqlite 向量库。测试若不注入,会去加载 bge-m3 级别模型,命令长时间无输出
124
+ —— 看起来像死锁,实际是在下载/加载权重。
125
+ """
126
+ from vecrecall import VecRecall
127
+
128
+ self.wing = wing
129
+ self.storage_path = str(storage_path)
130
+ self.version = self._resolve_version()
131
+ self._lock = threading.RLock()
132
+
133
+ self._emb = embedding_backend or _HKCSharedEmbedding(
134
+ self._resolve_model(embedding_model))
135
+ self._assert_dim_matches()
136
+
137
+ extra = {"vector_backend": vector_backend} if vector_backend is not None else {}
138
+ self._vr = VecRecall(
139
+ base_dir=self.storage_path,
140
+ wing=wing,
141
+ embedding_backend=self._emb,
142
+ use_blockchain=use_chain,
143
+ dedup=dedup,
144
+ **extra,
145
+ )
146
+
147
+ @staticmethod
148
+ def _resolve_version() -> str:
149
+ try:
150
+ import vecrecall as _pkg
151
+ return str(getattr(_pkg, "__version__", "unknown"))
152
+ except Exception:
153
+ return "unknown"
154
+
155
+ @staticmethod
156
+ def _resolve_model(embedding_model: str | None = None) -> str:
157
+ """与 HMRAdapter 同一条解析链,保证两套后端用同一个模型(分数与维度可比)。"""
158
+ return (embedding_model
159
+ or os.environ.get("EMBEDDING_MODEL")
160
+ or os.environ.get("HKC_EMBEDDING_MODEL")
161
+ or os.environ.get("FOXCLAW_EMBEDDING_MODEL")
162
+ or "all-MiniLM-L6-v2")
163
+
164
+ def _assert_dim_matches(self) -> None:
165
+ """已有向量维度与当前嵌入维度不一致时明确失败。
166
+
167
+ vecRecall 的 SqliteVectorBackend 与 HMR 一样用 zip() 求内积:两侧维度不同时
168
+ 短的一侧被静默截断,分数照常返回、不抛错。症状是"换了 embedding 模型之后
169
+ 召回突然变成噪声",比直接崩更难定位。
170
+ """
171
+ db = Path(self.storage_path) / "vectors.db"
172
+ if not db.exists():
173
+ return
174
+ import sqlite3
175
+ try:
176
+ conn = sqlite3.connect(str(db))
177
+ rows = conn.execute("SELECT DISTINCT dim FROM vectors LIMIT 2").fetchall()
178
+ conn.close()
179
+ except Exception:
180
+ return
181
+ dims = {int(r[0]) for r in rows}
182
+ want = getattr(self._emb, "dim", None)
183
+ if dims and want and dims != {int(want)}:
184
+ raise RuntimeError(
185
+ f"已有向量维度 {sorted(dims)} 与当前嵌入维度 {want} 不一致。"
186
+ f"换 embedding 模型必须重建索引:删除 {self.storage_path} 后重启"
187
+ )
188
+
189
+ _AUTO_TITLE = re.compile(r"^[A-Za-z_]+\s+\d{1,2}:\d{2}$")
190
+
191
+ @staticmethod
192
+ def _derive_title(content: str) -> str:
193
+ s = str(content or "").strip()
194
+ if not s:
195
+ return ""
196
+ for pfx in ("对话:", "对话:", "用户偏好:", "用户偏好:", "任务:", "任务:"):
197
+ if s.startswith(pfx):
198
+ s = s[len(pfx):].strip()
199
+ break
200
+ for sep in ("→", "->"):
201
+ if sep in s:
202
+ s = s.split(sep, 1)[0].strip()
203
+ break
204
+ s = re.split(r"[。!?!?\n\r]", s, 1)[0].strip()
205
+ return " ".join(s.split())[:80]
206
+
207
+ def ingest(self, content: str, type: str = "concept", agent_id: str | None = None,
208
+ title: str | None = None, tags: list | None = None, shared: bool = False, **kwargs):
209
+ """统一摄入。签名与 `HMRAdapter.ingest` 一致。
210
+
211
+ 隔离承载:`agent_id` → owner,`shared=True` → visibility="shared"。
212
+ 不再依赖 tag 前缀做隔离,但 tag 仍然写进 metadata(前端展示与偏好查询要用)。
213
+ """
214
+ meta_tags = list(tags or [])
215
+ if agent_id:
216
+ meta_tags.append(f"agent:{agent_id}")
217
+ if shared:
218
+ meta_tags.append("shared")
219
+
220
+ mtype = type if type in _VALID_TYPES else "concept"
221
+ if not title:
222
+ title = self._derive_title(content) or ""
223
+
224
+ if shared:
225
+ guard_shared_write(self, agent_id, content, mtype, meta_tags)
226
+
227
+ metadata = {"tags": meta_tags, "foxclaw_type": mtype}
228
+ if "confidence" in kwargs:
229
+ metadata["confidence"] = kwargs["confidence"]
230
+
231
+ retention = "immutable" if mtype == "execution" else "decay"
232
+ topic_key = kwargs.get("topic_key") or (
233
+ _PREFERENCE_TAG if _PREFERENCE_TAG in meta_tags else "")
234
+
235
+ with self._lock:
236
+ return self._vr.add(
237
+ content=content,
238
+ wing=self.wing,
239
+ importance=kwargs.get("importance"),
240
+ ui_summary=(title or "")[:200],
241
+ metadata=metadata,
242
+ memory_type=_FOXCLAW_TO_VR.get(mtype, "semantic"),
243
+ visibility="shared" if shared else "private",
244
+ owner=(agent_id or "") if not shared else "",
245
+ retention_policy=retention,
246
+ topic_key=topic_key,
247
+ )
248
+
249
+ def _visible(self, mem, agent_id: str | None, include_shared: bool) -> bool:
250
+ """可见性判定,与 vecRecall `_visible_to` 规则保持同源。
251
+
252
+ - shared 记忆:仅当 include_shared 时可见
253
+ - private 记忆:owner 为空(老数据)视为公开;否则必须等于当前 agent
254
+ - agent_id 为空:不过滤(与 HMR 路径 `if agent_id:` 的行为一致)
255
+ """
256
+ vis = getattr(mem, "visibility", "private") or "private"
257
+ owner = getattr(mem, "owner", "") or ""
258
+ if not agent_id:
259
+ return True
260
+ if vis == "shared":
261
+ return include_shared
262
+ return owner == agent_id or not owner
263
+
264
+ def recall(self, query: str = "", agent_id: str | None = None, top_k: int = 5,
265
+ include_shared: bool = True, **kwargs):
266
+ """统一召回,返回 list[dict]。
267
+
268
+ 走 `build_context(viewer=...)` 而不是 `search()`:前者在 L1/L2/L3 全层按
269
+ visibility+owner 过滤,是**召回前**生效的隔离;`search()` 源码里明写
270
+ "不经过任何结构过滤",拿它做按 Agent 召回会越权。
271
+
272
+ 只取 L2+L3:L1 是"全库 importance top-15 + immutable 全额并入",与当前
273
+ query 无关,混进来会挤占 top_k。
274
+ """
275
+ fetch_k = min(max(top_k * 5, 20), 100) if agent_id else top_k
276
+ results = []
277
+ try:
278
+ with self._lock:
279
+ bundle = self._vr.build_context(
280
+ current_query=query or "",
281
+ load_l2=True,
282
+ load_l3=bool(query and query.strip()),
283
+ viewer=agent_id,
284
+ )
285
+ results = list(getattr(bundle, "l2_topic_context", []) or []) + \
286
+ list(getattr(bundle, "l3_deep_results", []) or [])
287
+ if not results and (query or "").strip():
288
+ results = list(self._vr.search(query, n=fetch_k) or [])
289
+ except Exception:
290
+ return []
291
+
292
+ out, seen = [], set()
293
+ for r in results:
294
+ mem = getattr(r, "memory", None)
295
+ if mem is None:
296
+ continue
297
+ mid = getattr(mem, "id", "")
298
+ if not mid or mid in seen:
299
+ continue
300
+ if not self._visible(mem, agent_id, include_shared):
301
+ continue
302
+ seen.add(mid)
303
+ row = self._memory_to_row(mem)
304
+ row["score"] = round(float(getattr(r, "score", 0.0) or 0.0), 4)
305
+ out.append(row)
306
+
307
+ out.sort(key=lambda x: x.get("score", 0.0), reverse=True)
308
+ return out[:top_k]
309
+
310
+ def _memory_to_row(self, mem) -> dict:
311
+ """Memory → Foxclaw 的召回结果结构。"""
312
+ meta = getattr(mem, "metadata", None) or {}
313
+ tags = [t for t in (meta.get("tags") or []) if isinstance(t, str)]
314
+ owner = getattr(mem, "owner", "") or ""
315
+ if owner and f"agent:{owner}" not in tags:
316
+ tags.append(f"agent:{owner}")
317
+ shared = (getattr(mem, "visibility", "") == "shared")
318
+ if shared and "shared" not in tags:
319
+ tags.append("shared")
320
+ ftype = (meta.get("foxclaw_type")
321
+ or _VR_TO_FOXCLAW.get(getattr(mem, "memory_type", ""), "concept"))
322
+ return {
323
+ "id": getattr(mem, "id", ""),
324
+ "title": (getattr(mem, "ui_summary", "") or "")[:120],
325
+ "content": getattr(mem, "content", "") or "",
326
+ "type": ftype,
327
+ "confidence": meta.get("confidence", 1.0),
328
+ "tags": tags,
329
+ "shared": shared,
330
+ }
331
+
332
+ def recall_by_tag(self, agent_id=None, tag="preference", limit=8, include_shared=True):
333
+ """按 tag 精确拉取(不做语义排序),用于"总是注入"的偏好类记忆。
334
+
335
+ vecRecall 的硬边界禁止用 wing/topic 结构过滤**检索**,但偏好本来就不该做
336
+ 语义模糊匹配 —— 它走 `query_by_topic_key`(精确、时间序),语义反而更准。
337
+ 非偏好 tag 退化为全量列表筛选,避免调用方拿到静默的空结果。
338
+ """
339
+ mems = []
340
+ try:
341
+ with self._lock:
342
+ if tag == _PREFERENCE_TAG:
343
+ mems = list(self._vr.query_by_topic_key(tag, wing=self.wing) or [])
344
+ if not mems:
345
+ listed = self._vr.list_memories(wing=self.wing, limit=500,
346
+ full=True, sort="recent")
347
+ for d in (listed.get("memories") or []):
348
+ t = (d.get("metadata") or {}).get("tags") or []
349
+ if tag in t:
350
+ mems.append(SimpleNamespace(
351
+ id=d.get("id", ""), content=d.get("content", ""),
352
+ metadata=d.get("metadata") or {},
353
+ visibility=d.get("visibility", "private"),
354
+ owner="", timestamp=d.get("timestamp", ""),
355
+ ui_summary=d.get("ui_summary", ""),
356
+ memory_type=d.get("memory_type", ""),
357
+ ))
358
+ except Exception:
359
+ return []
360
+
361
+ rows = []
362
+ for m in mems:
363
+ if not self._visible(m, agent_id, include_shared):
364
+ continue
365
+ row = self._memory_to_row(m)
366
+ row["_ts"] = getattr(m, "timestamp", "") or ""
367
+ rows.append(row)
368
+
369
+ rows.sort(key=lambda x: str(x.get("_ts")), reverse=True)
370
+ deduped, seen_c = [], set()
371
+ for x in rows:
372
+ if x["content"] in seen_c:
373
+ continue
374
+ seen_c.add(x["content"])
375
+ deduped.append(x)
376
+ return deduped[:limit]
377
+
378
+ def delete_memory(self, memory_id):
379
+ with self._lock:
380
+ try:
381
+ return bool(self._vr.delete(memory_id))
382
+ except Exception:
383
+ return False
384
+
385
+ def recent(self, limit: int = 20) -> list:
386
+ """最近的记忆条目(新→旧),供前端右栏"记忆信息条"。"""
387
+ rows = []
388
+ try:
389
+ with self._lock:
390
+ listed = self._vr.list_memories(wing=self.wing, limit=max(1, int(limit)),
391
+ full=True, sort="recent")
392
+ except Exception:
393
+ return []
394
+ for d in (listed.get("memories") or []):
395
+ meta = d.get("metadata") or {}
396
+ tags = [t for t in (meta.get("tags") or []) if isinstance(t, str)]
397
+ agent = ""
398
+ for t in tags:
399
+ if t.startswith("agent:"):
400
+ agent = t.split(":", 1)[1]
401
+ break
402
+ title = (d.get("ui_summary") or "").strip()
403
+ if not title or self._AUTO_TITLE.match(title):
404
+ title = self._derive_title(d.get("content") or "") or title
405
+ rows.append({
406
+ "id": d.get("id", ""),
407
+ "type": meta.get("foxclaw_type") or _VR_TO_FOXCLAW.get(
408
+ d.get("memory_type", ""), ""),
409
+ "title": title[:120],
410
+ "summary": (d.get("ui_summary") or d.get("content_preview")
411
+ or d.get("content") or "")[:180],
412
+ "agent": agent,
413
+ "shared": d.get("visibility") == "shared",
414
+ "created_at": str(d.get("timestamp") or ""),
415
+ })
416
+ return rows
417
+
418
+ def get_workspace(self, agent_id: str):
419
+ return SimpleNamespace(agent_id=agent_id, active_goal="", status="idle",
420
+ push_task=lambda _t: None)
421
+
422
+ def save_workspace(self, agent_id: str):
423
+ return None
424
+
425
+ def save_runtime_state(self, **kwargs):
426
+ return None
427
+
428
+ def restore_runtime_state(self):
429
+ return None
430
+
431
+ def get_system_status(self):
432
+ return {"backend": self.backend, "version": self.version, "wing": self.wing}
433
+
434
+ def stats(self) -> dict:
435
+ """记忆统计:前端记忆中心展示真实数字。字段与 HMRAdapter.stats 一致。"""
436
+ total = shared_count = relations = 0
437
+ by_agent: dict[str, Any] = {}
438
+ embedding = type(self._emb).__name__
439
+ try:
440
+ with self._lock:
441
+ listed = self._vr.list_memories(wing=self.wing, limit=5000,
442
+ full=True, sort="recent")
443
+ mems = listed.get("memories") or []
444
+ total = int(listed.get("total") or len(mems))
445
+ for d in mems:
446
+ if d.get("visibility") == "shared":
447
+ shared_count += 1
448
+ for t in ((d.get("metadata") or {}).get("tags") or []):
449
+ if isinstance(t, str) and t.startswith("agent:"):
450
+ aid = t.split(":", 1)[1] or "unknown"
451
+ by_agent[aid] = by_agent.get(aid, 0) + 1
452
+ g = ((self._vr.stats() or {}).get("graph") or {})
453
+ for k in ("edges", "total_edges", "relations"):
454
+ if isinstance(g.get(k), int):
455
+ relations = g[k]
456
+ break
457
+ except Exception:
458
+ pass
459
+ return {
460
+ "backend": self.backend,
461
+ "version": self.version,
462
+ "total_memories": int(total),
463
+ "relations": int(relations),
464
+ "shared": int(shared_count),
465
+ "private": int(max(total - shared_count, 0)),
466
+ "by_agent": by_agent,
467
+ "embedding": embedding,
468
+ }
469
+
470
+ def close(self):
471
+ with self._lock:
472
+ closer = getattr(self._vr, "close", None)
473
+ if closer is not None:
474
+ try:
475
+ closer()
476
+ except Exception:
477
+ pass
@@ -0,0 +1,173 @@
1
+ """执行后核验:这一步到底做成了没有,以及凭什么这么说。
2
+
3
+ 病根:工具返回一段文本,模型接着把它写成"已完成"。可这段文本**既不是成功证明,
4
+ 也不是失败证明**——它只是工具说了什么。于是出现了审核报告里记的那类静默假成功:
5
+ 发送没发出去、写入没落盘,回答里照样写"已搞定"。人只有事后翻记录才发现。
6
+
7
+ 这里给每个工具结果一个**三态结论** + 可展开的证据:
8
+ · ``verified`` —— 拿到了可核对的后置条件(如搜索结果里的来源链接、命中的本地文件);
9
+ · ``unverified`` —— 执行没报错,但**没有可观察的后置条件**(写/发送/支付类),
10
+ 此时模型不许断言"已完成",只能描述"我执行了什么";
11
+ · ``failed`` —— 明确失败/认不出来。
12
+
13
+ 刻意不做的事:
14
+ - **不猜**。没有后置条件的工具就是 ``unverified``,不会因为文本里有"成功"二字就转成
15
+ ``verified``——那句"成功"是工具自己写的,不是证据。
16
+ - **不解析自然语言断言**。不去从模型答案里抽取"已完成"再逐个核对(那要靠第二次 LLM
17
+ 调用,既慢又会编);证据只从**工具真实返回**里抽,绑在步骤上,由 UI 展示给人看。
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import os
22
+ import re
23
+ import time
24
+ from dataclasses import dataclass, field
25
+ from urllib.parse import urlparse
26
+
27
+ from . import failure_policy as _policy
28
+ from typing import Callable
29
+
30
+ VERIFIED = "verified"
31
+ UNVERIFIED = "unverified"
32
+ FAILED = "failed"
33
+
34
+ MAX_EVIDENCE = 8
35
+
36
+ _URL_RE = re.compile(r"https?://[^\s<>\"'))\]}]+")
37
+ _WIN_PATH_RE = re.compile(r"[A-Za-z]:[\\/][^\s\"'<>|*?,。;]+")
38
+ _POSIX_PATH_RE = re.compile(r"(?<![\w:])/(?:[\w.\-]+/)+[\w.\-]+")
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class Evidence:
43
+ """一条可核对的凭据:一个链接、一个真实存在的文件、或一次工具的原始命中。"""
44
+
45
+ kind: str
46
+ ref: str
47
+ label: str
48
+ source: str
49
+ at: float = field(default_factory=time.time)
50
+
51
+ def to_dict(self) -> dict:
52
+ return {"kind": self.kind, "ref": self.ref, "label": self.label,
53
+ "source": self.source, "at": self.at}
54
+
55
+
56
+ @dataclass
57
+ class Verification:
58
+ """一次工具调用的核验结论。"""
59
+
60
+ status: str
61
+ reason: str = ""
62
+ evidence: list[Evidence] = field(default_factory=list)
63
+
64
+ @property
65
+ def ok(self) -> bool:
66
+ return self.status == VERIFIED
67
+
68
+ def to_dict(self) -> dict:
69
+ return {"status": self.status, "reason": self.reason,
70
+ "evidence": [e.to_dict() for e in self.evidence]}
71
+
72
+
73
+ def _label_for_url(url: str) -> str:
74
+ try:
75
+ host = urlparse(url).netloc
76
+ return host[4:] if host.startswith("www.") else host or url[:40]
77
+ except Exception:
78
+ return url[:40]
79
+
80
+
81
+ def _paths_in(text: str) -> list[str]:
82
+ out: list[str] = []
83
+ for m in _WIN_PATH_RE.finditer(text or ""):
84
+ out.append(m.group(0).rstrip(".,;:,。;:"))
85
+ for m in _POSIX_PATH_RE.finditer(text or ""):
86
+ out.append(m.group(0).rstrip(".,;:,。;:"))
87
+ return out
88
+
89
+
90
+ def extract_evidence(source: str, text: str, limit: int = MAX_EVIDENCE) -> list[Evidence]:
91
+ """从工具返回文本里抽可核对的证据。
92
+
93
+ 只认两类硬凭据:URL(去重、截断到 8 条)与**磁盘上确实存在**的路径。
94
+ 正文摘要不算证据——那是工具的自述,不是可复核的事实。
95
+ """
96
+ seen: set[str] = set()
97
+ out: list[Evidence] = []
98
+ for m in _URL_RE.finditer(text or ""):
99
+ url = m.group(0).rstrip(".,;:!?,。;:!?")
100
+ key = url.split("#", 1)[0]
101
+ if key in seen:
102
+ continue
103
+ seen.add(key)
104
+ out.append(Evidence("url", url, _label_for_url(url), source))
105
+ if len(out) >= limit:
106
+ return out
107
+ for p in _paths_in(text):
108
+ if p in seen:
109
+ continue
110
+ try:
111
+ if not os.path.exists(p):
112
+ continue
113
+ except Exception:
114
+ continue
115
+ seen.add(p)
116
+ out.append(Evidence("file", p, os.path.basename(p), source))
117
+ if len(out) >= limit:
118
+ break
119
+ return out
120
+
121
+
122
+ _CHECKERS: dict[str, Callable[..., tuple[bool, str]]] = {
123
+ "web_search": lambda args, text: (
124
+ (True, "") if _URL_RE.search(text or "")
125
+ else (False, "搜索结果没有可核对的来源链接")),
126
+ "recall_memory": lambda args, text: (
127
+ (True, "") if (text or "").strip().startswith("【召回的相关记忆】")
128
+ else (False, "未返回记忆条目")),
129
+ }
130
+
131
+
132
+ def verify(name: str, args: dict | None, text: str,
133
+ capability: dict | None = None) -> Verification:
134
+ """给一次工具调用的结果下结论(不抛异常:核验本身失败时不拖垮工具轮)。"""
135
+ kind = _policy.classify(text)
136
+ if kind not in (_policy.OK, _policy.UNKNOWN):
137
+ return Verification(FAILED, reason=f"失败类型:{kind}")
138
+ if kind == _policy.UNKNOWN:
139
+ return Verification(FAILED, reason="认不出的返回(不当作成功)")
140
+
141
+ ev = extract_evidence(name, text)
142
+ checker = _CHECKERS.get(name)
143
+ if checker is not None:
144
+ try:
145
+ passed, why = checker(args or {}, text or "")
146
+ except Exception:
147
+ passed, why = False, "后置条件检查自身出错"
148
+ if not passed:
149
+ return Verification(UNVERIFIED, reason=why, evidence=ev)
150
+ return Verification(VERIFIED, evidence=ev)
151
+
152
+ read_only = bool((capability or {}).get("read_only"))
153
+ if read_only:
154
+ return Verification(VERIFIED, evidence=ev)
155
+ return Verification(UNVERIFIED, reason="该工具未声明可观察的后置条件", evidence=ev)
156
+
157
+
158
+ def note_for_model(v: Verification, read_only: bool = False) -> str:
159
+ """给模型的硬提示。**只对未核验/失败**追加;已验证的成功结果一个字都不改。
160
+
161
+ ``read_only`` 决定未核验时要不要训话:只读工具的"没核对到来源"照样记进证据区
162
+ (UI 显示未核验),但不往结果文本里塞提示 —— 否则每条搜索/记忆命中都会多一段
163
+ 劝诫,既挤占上下文又改变既有行为。**有副作用**的未核验必须训:那才是"没做成
164
+ 却可能被写成做成了"的地方。
165
+ """
166
+ if v.status == VERIFIED:
167
+ return ""
168
+ if v.status == FAILED:
169
+ return f"[核验] 该步骤未成功({v.reason}),不要向用户声称它已完成。"
170
+ if read_only:
171
+ return ""
172
+ return (f"[核验] 该步骤执行了但没有可核对的凭据({v.reason})。"
173
+ f"只描述你执行了什么,不要断言「已完成/已成功」。")