foxclaw 0.1.2 → 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 (344) hide show
  1. package/index.js +1 -8
  2. package/package.json +1 -1
  3. package/python/.env.example +8 -0
  4. package/python/.github/workflows/ci.yml +47 -2
  5. package/python/CHANGELOG.md +57 -0
  6. package/python/CHANGELOG_CN.md +57 -0
  7. package/python/README.md +11 -2
  8. package/python/README_CN.md +32 -20
  9. package/python/bench/bm25_audit.py +1 -14
  10. package/python/bench/bm25_regression.py +0 -1
  11. package/python/bench/bm25_stress.py +0 -4
  12. package/python/bench/cd_audit.py +0 -23
  13. package/python/bench/chaos.py +6 -8
  14. package/python/bench/e2e_real_models.py +0 -1
  15. package/python/bench/llm_retry_test.py +2 -9
  16. package/python/bench/load_test.py +5 -8
  17. package/python/bench/vector_ivf_upgrade.py +1 -7
  18. package/python/docs/RELEASE.md +4 -3
  19. package/python/docs/RUN_CN.md +2 -2
  20. package/python/evals/governance/cases.py +0 -37
  21. package/python/evals/governance/run_evals.py +5 -37
  22. package/python/examples/governance_middleware_demo.py +1 -6
  23. package/python/examples/langgraph_governance_demo.py +7 -11
  24. package/python/foxclaw/__init__.py +34 -152
  25. package/python/foxclaw/agents/base.py +65 -136
  26. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  27. package/python/foxclaw/agents/impl/finance.py +4 -21
  28. package/python/foxclaw/agents/impl/office.py +7 -24
  29. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  30. package/python/foxclaw/agents/mixins/_context.py +0 -23
  31. package/python/foxclaw/agents/mixins/augment.py +10 -16
  32. package/python/foxclaw/agents/mixins/llm.py +7 -6
  33. package/python/foxclaw/agents/mixins/memory.py +7 -1
  34. package/python/foxclaw/agents/mixins/preference.py +58 -8
  35. package/python/foxclaw/agents/mixins/prompt.py +0 -6
  36. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  37. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  38. package/python/foxclaw/api/__init__.py +18 -77
  39. package/python/foxclaw/api/_state.py +43 -0
  40. package/python/foxclaw/api/{app.py → bootstrap.py} +5 -32
  41. package/python/foxclaw/api/middleware.py +5 -28
  42. package/python/foxclaw/api/routes/ahs.py +60 -73
  43. package/python/foxclaw/api/routes/attachments.py +5 -6
  44. package/python/foxclaw/api/routes/browser.py +7 -11
  45. package/python/foxclaw/api/routes/domain.py +18 -26
  46. package/python/foxclaw/api/routes/knowledge.py +3 -21
  47. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  48. package/python/foxclaw/api/routes/memory.py +28 -12
  49. package/python/foxclaw/api/routes/orchestration.py +3 -5
  50. package/python/foxclaw/api/routes/recovery.py +4 -3
  51. package/python/foxclaw/api/routes/review.py +2 -11
  52. package/python/foxclaw/api/routes/search.py +3 -7
  53. package/python/foxclaw/api/routes/system.py +33 -49
  54. package/python/foxclaw/api/routes/task.py +31 -49
  55. package/python/foxclaw/api/routes/ws.py +2 -6
  56. package/python/foxclaw/api/spine.py +1 -4
  57. package/python/foxclaw/browser/action/action_executor.py +1 -5
  58. package/python/foxclaw/browser/action/action_types.py +0 -1
  59. package/python/foxclaw/browser/action/url_policy.py +1 -5
  60. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  61. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  62. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  63. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  64. package/python/foxclaw/browser/service.py +7 -11
  65. package/python/foxclaw/capability/embedding_service.py +4 -6
  66. package/python/foxclaw/channels/__init__.py +9 -11
  67. package/python/foxclaw/channels/discord.py +4 -13
  68. package/python/foxclaw/channels/dispatcher.py +2 -27
  69. package/python/foxclaw/channels/feishu.py +10 -20
  70. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  71. package/python/foxclaw/channels/signing.py +9 -9
  72. package/python/foxclaw/channels/telegram.py +3 -4
  73. package/python/foxclaw/channels/weixin.py +7 -17
  74. package/python/foxclaw/cli.py +42 -13
  75. package/python/foxclaw/cognitive/reasoner.py +9 -10
  76. package/python/foxclaw/cognitive/state.py +18 -24
  77. package/python/foxclaw/core/audit_chain.py +1 -3
  78. package/python/foxclaw/core/audit_sink.py +3 -3
  79. package/python/foxclaw/core/compaction.py +8 -26
  80. package/python/foxclaw/core/config.py +2 -2
  81. package/python/foxclaw/core/confirm_nonce.py +6 -12
  82. package/python/foxclaw/core/envfile.py +2 -2
  83. package/python/foxclaw/core/failure_policy.py +17 -26
  84. package/python/foxclaw/core/gateway.py +6 -10
  85. package/python/foxclaw/core/governance_audit.py +3 -7
  86. package/python/foxclaw/core/idempotency.py +15 -22
  87. package/python/foxclaw/core/injection_guard.py +10 -29
  88. package/python/foxclaw/core/memory_adapter.py +30 -57
  89. package/python/foxclaw/core/metrics.py +4 -5
  90. package/python/foxclaw/core/model_router.py +36 -148
  91. package/python/foxclaw/core/paths.py +1 -1
  92. package/python/foxclaw/core/recovery.py +21 -34
  93. package/python/foxclaw/core/scheduler.py +23 -26
  94. package/python/foxclaw/core/secrets.py +0 -3
  95. package/python/foxclaw/core/sessions.py +4 -6
  96. package/python/foxclaw/core/settings.py +16 -32
  97. package/python/foxclaw/core/shared_write.py +11 -23
  98. package/python/foxclaw/core/steering_queue.py +12 -11
  99. package/python/foxclaw/core/system_bus.py +1 -1
  100. package/python/foxclaw/core/thought_chain.py +30 -32
  101. package/python/foxclaw/core/token_meter.py +9 -13
  102. package/python/foxclaw/core/tools.py +4 -12
  103. package/python/foxclaw/core/tracing.py +25 -40
  104. package/python/foxclaw/core/truth_pipeline.py +5 -9
  105. package/python/foxclaw/core/update_check.py +149 -0
  106. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  107. package/python/foxclaw/core/verify.py +6 -14
  108. package/python/foxclaw/core/web_search.py +4 -28
  109. package/python/foxclaw/finance/evidence.py +14 -18
  110. package/python/foxclaw/finance/fx.py +1 -3
  111. package/python/foxclaw/finance/graph.py +9 -18
  112. package/python/foxclaw/finance/models.py +20 -24
  113. package/python/foxclaw/finance/nlparse.py +12 -72
  114. package/python/foxclaw/finance/policy.py +8 -36
  115. package/python/foxclaw/finance/reasoner.py +3 -11
  116. package/python/foxclaw/finance/reasoning.py +13 -34
  117. package/python/foxclaw/finance/reconcile.py +5 -10
  118. package/python/foxclaw/finance/runtime.py +19 -46
  119. package/python/foxclaw/governance/middleware.py +13 -21
  120. package/python/foxclaw/harness/_base.py +2 -8
  121. package/python/foxclaw/harness/_policy.py +19 -38
  122. package/python/foxclaw/harness/drift.py +1 -13
  123. package/python/foxclaw/harness/event_bus.py +2 -5
  124. package/python/foxclaw/harness/harness.py +9 -97
  125. package/python/foxclaw/harness/loop_detection.py +11 -18
  126. package/python/foxclaw/itinerary/amap_client.py +2 -5
  127. package/python/foxclaw/itinerary/models.py +7 -7
  128. package/python/foxclaw/itinerary/reasoner.py +1 -6
  129. package/python/foxclaw/itinerary/reasoning.py +15 -21
  130. package/python/foxclaw/itinerary/runtime.py +8 -12
  131. package/python/foxclaw/kernel/capabilities.py +5 -6
  132. package/python/foxclaw/kernel/policy_gate.py +4 -10
  133. package/python/foxclaw/kernel/process_manager.py +17 -36
  134. package/python/foxclaw/kernel/scheduler.py +3 -9
  135. package/python/foxclaw/kernel/sentinel.py +13 -23
  136. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  137. package/python/foxclaw/kernel/spine.py +9 -9
  138. package/python/foxclaw/knowledge/__init__.py +4 -20
  139. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  140. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  141. package/python/foxclaw/learning/models.py +13 -13
  142. package/python/foxclaw/learning/reasoner.py +1 -4
  143. package/python/foxclaw/learning/runtime.py +19 -31
  144. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  145. package/python/foxclaw/mcp_server.py +2 -3
  146. package/python/foxclaw/orchestration/__init__.py +16 -33
  147. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  148. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  149. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  150. package/python/foxclaw/planning/llm_planner.py +3 -3
  151. package/python/foxclaw/planning/planner.py +6 -11
  152. package/python/foxclaw/search/agent_port.py +7 -11
  153. package/python/foxclaw/search/evidence.py +0 -4
  154. package/python/foxclaw/search/internal_search.py +3 -5
  155. package/python/foxclaw/search/models.py +11 -13
  156. package/python/foxclaw/search/planner.py +3 -3
  157. package/python/foxclaw/search/retriever.py +1 -1
  158. package/python/foxclaw/search/runtime.py +1 -8
  159. package/python/foxclaw/skills/__init__.py +46 -100
  160. package/python/foxclaw/skills/archive.py +111 -0
  161. package/python/foxclaw/skills/fetch.py +226 -0
  162. package/python/foxclaw/skills/hooks_spec.py +21 -25
  163. package/python/foxclaw/skills/install_cmd.py +216 -0
  164. package/python/foxclaw/skills/lifecycle.py +16 -42
  165. package/python/foxclaw/skills/markdown_skill.py +10 -9
  166. package/python/foxclaw/skills/mcp_client.py +5 -11
  167. package/python/foxclaw/skills/mcp_store.py +0 -3
  168. package/python/foxclaw/skills/permission.py +13 -48
  169. package/python/foxclaw/skills/pipeline.py +21 -53
  170. package/python/foxclaw/skills/policy.py +4 -10
  171. package/python/foxclaw/skills/review.py +3 -3
  172. package/python/frontend/deck/auth.js +0 -7
  173. package/python/frontend/deck/chat_model_picker.js +0 -54
  174. package/python/frontend/deck/domain.js +0 -20
  175. package/python/frontend/deck/main.js +0 -144
  176. package/python/frontend/deck/markdown.js +0 -4
  177. package/python/frontend/deck/settings.js +1 -18
  178. package/python/frontend/deck/state.js +0 -1
  179. package/python/frontend/deck/util.js +0 -2
  180. package/python/frontend/deck/visuals.js +0 -22
  181. package/python/frontend/planet/planet.js +0 -201
  182. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  183. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  184. package/python/frontend/v3/index.html +3 -3
  185. package/python/frontend/widget_runtime.js +0 -43
  186. package/python/frontend/widgets.js +0 -57
  187. package/python/frontend_src/index.html +1 -1
  188. package/python/frontend_src/package-lock.json +2 -2
  189. package/python/frontend_src/package.json +1 -1
  190. package/python/frontend_src/src/App.vue +3 -7
  191. package/python/frontend_src/src/api.js +0 -7
  192. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  193. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  194. package/python/frontend_src/src/components/Inspector.vue +0 -6
  195. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  196. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  197. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  198. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  199. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  200. package/python/frontend_src/src/components/Stage.vue +71 -25
  201. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  202. package/python/frontend_src/src/components/TopBar.vue +38 -10
  203. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  204. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  205. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  206. package/python/frontend_src/src/components/panels/MemoryPanel.vue +0 -1
  207. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  208. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  209. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  210. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  211. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  212. package/python/frontend_src/src/lib/fmt.js +0 -4
  213. package/python/frontend_src/src/lib/i18n.js +53 -0
  214. package/python/frontend_src/src/lib/locales/en.js +173 -0
  215. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  216. package/python/frontend_src/src/lib/md.js +0 -14
  217. package/python/frontend_src/src/main.js +2 -0
  218. package/python/frontend_src/src/stores/app.js +17 -32
  219. package/python/frontend_src/src/stores/chat.js +0 -34
  220. package/python/frontend_src/src/stores/model.js +0 -8
  221. package/python/frontend_src/src/stores/panels.js +0 -41
  222. package/python/frontend_src/src/stores/profile.js +31 -0
  223. package/python/frontend_src/src/stores/system.js +25 -0
  224. package/python/frontend_src/vite.config.js +0 -7
  225. package/python/pyproject.toml +6 -3
  226. package/python/requirements.txt +311 -0
  227. package/python/tests/conftest.py +0 -22
  228. package/python/tests/run_all.py +3 -66
  229. package/python/tests/test_agent_lifecycle.py +4 -38
  230. package/python/tests/test_ahs_p2.py +4 -25
  231. package/python/tests/test_anthropic_tools.py +1 -15
  232. package/python/tests/test_api.py +19 -9
  233. package/python/tests/test_api_auth.py +1 -5
  234. package/python/tests/test_attachments.py +9 -25
  235. package/python/tests/test_audit_chain.py +2 -7
  236. package/python/tests/test_audit_sink.py +0 -6
  237. package/python/tests/test_browser.py +2 -6
  238. package/python/tests/test_browser_request_guard.py +0 -4
  239. package/python/tests/test_capabilities.py +0 -6
  240. package/python/tests/test_channel_signing.py +2 -9
  241. package/python/tests/test_channels.py +1 -11
  242. package/python/tests/test_config_layering.py +0 -4
  243. package/python/tests/test_confirm_nonce.py +1 -12
  244. package/python/tests/test_context_compaction_wiring.py +1 -7
  245. package/python/tests/test_dispatch.py +0 -6
  246. package/python/tests/test_domain_evals.py +11 -80
  247. package/python/tests/test_domain_import.py +0 -6
  248. package/python/tests/test_domain_reasoners.py +0 -14
  249. package/python/tests/test_e2e.py +0 -17
  250. package/python/tests/test_emb_cache.py +7 -17
  251. package/python/tests/test_embedding_singleton.py +1 -3
  252. package/python/tests/test_evals_smoke.py +0 -24
  253. package/python/tests/test_event_spine.py +10 -18
  254. package/python/tests/test_evidence_semantic.py +1 -14
  255. package/python/tests/test_evidence_sse.py +3 -6
  256. package/python/tests/test_file_ingest.py +0 -11
  257. package/python/tests/test_finance.py +0 -9
  258. package/python/tests/test_finance_ledger.py +5 -12
  259. package/python/tests/test_finance_persist.py +8 -12
  260. package/python/tests/test_gateway.py +3 -22
  261. package/python/tests/test_governance.py +5 -52
  262. package/python/tests/test_governance_audit.py +4 -39
  263. package/python/tests/test_governance_middleware.py +1 -7
  264. package/python/tests/test_hkc.py +6 -24
  265. package/python/tests/test_hkc_index_build_manager.py +0 -33
  266. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  267. package/python/tests/test_hkc_search_event_sync.py +3 -54
  268. package/python/tests/test_import_path_guard.py +0 -6
  269. package/python/tests/test_injection_guard.py +18 -36
  270. package/python/tests/test_integration.py +5 -29
  271. package/python/tests/test_itinerary.py +0 -3
  272. package/python/tests/test_kee_semantic.py +0 -5
  273. package/python/tests/test_kernel.py +1 -11
  274. package/python/tests/test_learning.py +3 -12
  275. package/python/tests/test_llm_admin.py +4 -19
  276. package/python/tests/test_llm_planner.py +1 -23
  277. package/python/tests/test_markdown_skills.py +3 -14
  278. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  279. package/python/tests/test_mcp_connectors.py +3 -15
  280. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  281. package/python/tests/test_mcp_no_memory.py +3 -3
  282. package/python/tests/test_mcp_policy_gate.py +0 -1
  283. package/python/tests/test_mcp_server.py +6 -9
  284. package/python/tests/test_memory_recall.py +1 -4
  285. package/python/tests/test_metrics.py +0 -8
  286. package/python/tests/test_multi_chain.py +1 -13
  287. package/python/tests/test_multimodal.py +5 -18
  288. package/python/tests/test_negotiation.py +3 -13
  289. package/python/tests/test_orchestration.py +1 -11
  290. package/python/tests/test_orchestration_decoupled.py +0 -6
  291. package/python/tests/test_p3_p5_hardening.py +3 -17
  292. package/python/tests/test_parallel_dispatch.py +4 -12
  293. package/python/tests/test_pipeline.py +17 -58
  294. package/python/tests/test_plan_visible.py +4 -4
  295. package/python/tests/test_planner.py +0 -7
  296. package/python/tests/test_policy_gate.py +2 -3
  297. package/python/tests/test_profile.py +67 -0
  298. package/python/tests/test_readme_claims.py +163 -0
  299. package/python/tests/test_reasoner.py +2 -17
  300. package/python/tests/test_release_check.py +2 -13
  301. package/python/tests/test_resume_checkpoint.py +7 -15
  302. package/python/tests/test_routing.py +2 -12
  303. package/python/tests/test_scheduler.py +7 -10
  304. package/python/tests/test_search.py +1 -11
  305. package/python/tests/test_secrets.py +4 -10
  306. package/python/tests/test_semantic.py +1 -10
  307. package/python/tests/test_sessions.py +3 -4
  308. package/python/tests/test_settings.py +2 -18
  309. package/python/tests/test_shadow_learning.py +3 -43
  310. package/python/tests/test_shared_write.py +0 -7
  311. package/python/tests/test_skill_taint_gate.py +1 -10
  312. package/python/tests/test_skills.py +2 -18
  313. package/python/tests/test_skills_fetch.py +282 -0
  314. package/python/tests/test_stats_and_events.py +2 -5
  315. package/python/tests/test_streaming.py +2 -7
  316. package/python/tests/test_supervisor_pipeline.py +4 -5
  317. package/python/tests/test_supervisor_triage.py +4 -10
  318. package/python/tests/test_taint.py +2 -7
  319. package/python/tests/test_task_stream.py +1 -12
  320. package/python/tests/test_thinking_sse.py +0 -1
  321. package/python/tests/test_tool_center.py +1 -17
  322. package/python/tests/test_tool_parallel.py +2 -2
  323. package/python/tests/test_tool_params.py +1 -5
  324. package/python/tests/test_tool_use.py +0 -13
  325. package/python/tests/test_toplevel_concurrency.py +3 -4
  326. package/python/tests/test_tracing.py +7 -41
  327. package/python/tests/test_truth_crystallizer.py +4 -6
  328. package/python/tests/test_truth_flow.py +2 -7
  329. package/python/tests/test_update_check.py +95 -0
  330. package/python/tests/test_verify_evidence.py +8 -21
  331. package/python/tests/test_version_sync.py +35 -6
  332. package/python/tests/test_web_trigger.py +0 -17
  333. package/python/tools/check_faiss_type.py +0 -1
  334. package/python/tools/check_log.ps1 +0 -8
  335. package/python/tools/mypy_baseline.txt +12 -4
  336. package/python/tools/mypy_ratchet.py +52 -6
  337. package/python/tools/pack.py +0 -5
  338. package/python/tools/release_check.py +7 -7
  339. package/python/tools/restart_foxclaw.ps1 +5 -8
  340. package/python/tools/shared_write_audit.py +1 -15
  341. package/python/tools/size_check.py +1 -1
  342. package/python/tools/strip_comments.py +228 -0
  343. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  344. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -20,6 +20,7 @@ import threading
20
20
  from pathlib import Path
21
21
 
22
22
  from .shared_write import guard_shared_write
23
+ from typing import Any
23
24
 
24
25
 
25
26
  def _ensure_hmr_importable():
@@ -41,19 +42,18 @@ class HMRAdapter:
41
42
  若 HMR 不可用,调用方应回退到 MockHMR。
42
43
  """
43
44
 
44
- def __init__(self, storage_path: str = "./hmr_data", llm_api_key: str = None,
45
- embedding_model: str = None):
45
+ def __init__(self, storage_path: str = "./hmr_data", llm_api_key: str | None = None,
46
+ embedding_model: str | None = None):
46
47
  """【P1·share-bge-m3】新增 embedding_model 参数。
47
48
  上层(Foxclaw)通常在 HKC 已装配后再调本构造,bge-m3 已入 HKC 缓存;
48
49
  传具体模型名(如 HKC_EMBEDDING_MODEL)使 HMR 与 HKC 共享同一 SentenceTransformer 实例。
49
50
  不传则走 vendor/hmr/storage/vector_store.py 的 env 解析链(HKC_EMBEDDING_MODEL>bge-m3)。"""
50
51
  _ensure_hmr_importable()
51
- from hmr.core.hmr import HMR # 内嵌包
52
+ from hmr.core.hmr import HMR
52
53
 
53
- # 不传 None 就用 .env / HKC 的配置;只有上层显式 override 才用 _embedding_model
54
54
  import os as _os
55
55
  resolved_model = (embedding_model
56
- or _os.environ.get("EMBEDDING_MODEL") # 【P2·单 embedding 入口】最高优先级
56
+ or _os.environ.get("EMBEDDING_MODEL")
57
57
  or _os.environ.get("HKC_EMBEDDING_MODEL")
58
58
  or _os.environ.get("FOXCLAW_EMBEDDING_MODEL")
59
59
  or "all-MiniLM-L6-v2")
@@ -61,13 +61,8 @@ class HMRAdapter:
61
61
  embedding_model=resolved_model)
62
62
  self.backend = "HMR"
63
63
  self.version = getattr(self._hmr, "VERSION", "2.0.0")
64
- # 并行子 Agent 会并发 ingest/recall,底层 HMR 的向量库/记忆图并非线程安全
65
- # (向量索引并发 add+search 是未定义行为)。串行化对 HMR 的访问。叶子组件:
66
- # 锁内不回调 pm/bus,故不参与全局锁序,无死锁风险。
67
64
  self._lock = threading.RLock()
68
65
 
69
- # HMR 未给 title 时会退化成 "Concept 03:11" 这类纯时间戳(见 hmr.py 的
70
- # `title or f"{type.title()} {HH:MM}"`),在前端"记忆信息条"里一眼看不出是什么。
71
66
  _AUTO_TITLE = re.compile(r"^[A-Za-z_]+\s+\d{1,2}:\d{2}$")
72
67
 
73
68
  @staticmethod
@@ -80,23 +75,22 @@ class HMRAdapter:
80
75
  if s.startswith(pfx):
81
76
  s = s[len(pfx):].strip()
82
77
  break
83
- for sep in ("→", "->"): # 对话记忆是"问 → 答",标题取问
78
+ for sep in ("→", "->"):
84
79
  if sep in s:
85
80
  s = s.split(sep, 1)[0].strip()
86
81
  break
87
82
  s = re.split(r"[。!?!?\n\r]", s, 1)[0].strip()
88
83
  return " ".join(s.split())[:24]
89
84
 
90
- # ── 记忆摄入 ──────────────────────────────────────────
91
- def ingest(self, content: str, type: str = "concept", agent_id: str = None,
92
- title: str = None, tags: list = None, shared: bool = False, **kwargs):
85
+ def ingest(self, content: str, type: str = "concept", agent_id: str | None = None,
86
+ title: str | None = None, tags: list | None = None, shared: bool = False, **kwargs):
93
87
  """统一摄入接口。agent_id 写入 tags 实现隔离;shared=True 则写入共享层。
94
88
  未给 title 时从内容提炼一个可读标题(否则 HMR 会生成纯时间戳标题)。"""
95
89
  meta_tags = list(tags or [])
96
90
  if agent_id:
97
91
  meta_tags.append(f"agent:{agent_id}")
98
92
  if shared:
99
- meta_tags.append("shared") # 共享记忆:所有 Agent 可召回
93
+ meta_tags.append("shared")
100
94
  metadata = {"tags": meta_tags}
101
95
  if "confidence" in kwargs:
102
96
  metadata["confidence"] = kwargs["confidence"]
@@ -105,15 +99,12 @@ class HMRAdapter:
105
99
  mtype = type if type in valid else "concept"
106
100
  if not title:
107
101
  title = self._derive_title(content) or None
108
- # 【P0-3】共享层写入闸门:必须在**落库之前**判定(见 core/shared_write.py)。
109
- # 未挂策略时返回 None 直接放行;audit 只记事件;enforce 才抛 SharedWriteDenied。
110
102
  if shared:
111
103
  guard_shared_write(self, agent_id, content, mtype, meta_tags)
112
104
  with self._lock:
113
105
  return self._hmr.ingest(content, memory_type=mtype, title=title, metadata=metadata)
114
106
 
115
- # ── 记忆召回 ──────────────────────────────────────────
116
- def recall(self, query: str = "", agent_id: str = None, top_k: int = 5,
107
+ def recall(self, query: str = "", agent_id: str | None = None, top_k: int = 5,
117
108
  include_shared: bool = True, **kwargs):
118
109
  """统一召回接口。返回 list[dict]。
119
110
  默认召回:本 Agent 的记忆 + 共享层(shared)记忆;include_shared=False 只看自己。
@@ -153,8 +144,7 @@ class HMRAdapter:
153
144
  "tags": tags,
154
145
  "_ts": getattr(m, "created_at", None) or getattr(m, "timestamp", None) or "",
155
146
  })
156
- out.sort(key=lambda x: str(x["_ts"]), reverse=True) # 新近优先
157
- # 按内容去重(保留最新):否则同一偏好反复表达存成多条,会挤满 limit、把别的偏好挤出召回
147
+ out.sort(key=lambda x: str(x["_ts"]), reverse=True)
158
148
  deduped, seen_c = [], set()
159
149
  for x in out:
160
150
  if x["content"] in seen_c:
@@ -195,7 +185,7 @@ class HMRAdapter:
195
185
  try:
196
186
  with self._lock:
197
187
  mems = fs.list_memories()
198
- cand = [] # tag 预过滤:本 Agent 私有 + 共享
188
+ cand = []
199
189
  for m in mems:
200
190
  tags = getattr(m, "tags", []) or []
201
191
  if agent_id:
@@ -210,7 +200,7 @@ class HMRAdapter:
210
200
  scored = []
211
201
  for m, tags in cand:
212
202
  vec = vs.vectors.get(getattr(m, "id", None))
213
- if vec is None: # 少见:该记忆尚无向量 → 现算
203
+ if vec is None:
214
204
  try:
215
205
  text = getattr(m, "semantic_summary", "") or (m.content or "")[:500]
216
206
  vec = vs.embedder.embed(text)
@@ -232,7 +222,7 @@ class HMRAdapter:
232
222
  })
233
223
  return out
234
224
  except Exception:
235
- return None # 任何异常 → 回退老路径,绝不因根治优化而丢召回能力
225
+ return None
236
226
 
237
227
  def _legacy_recall(self, query, agent_id, top_k, include_shared):
238
228
  """回退路径:全局召回再按 tag 过滤(HMR 结构不可访问时用)。有完整性局限,见 recall。"""
@@ -250,7 +240,7 @@ class HMRAdapter:
250
240
  own = f"agent:{agent_id}" in tags
251
241
  shared = include_shared and ("shared" in tags)
252
242
  if not (own or shared):
253
- continue # 既不是本 Agent 的,也不是共享的 → 跳过(隔离保证)
243
+ continue
254
244
  out.append({
255
245
  "id": getattr(m, "id", ""),
256
246
  "title": getattr(m, "title", ""),
@@ -264,11 +254,6 @@ class HMRAdapter:
264
254
  break
265
255
  return out
266
256
 
267
- # ── 工作区 ────────────────────────────────────────────
268
- # 【深审修复·补齐锁覆盖】适配层自述"串行化**所有** HMR 访问"(底层向量库/记忆图非线程安全),
269
- # 但此前只有 ingest/recall 加锁;下列同样触碰 self._hmr 的方法漏了锁——与并发 ingest/recall
270
- # (并行子 Agent / 并发请求)同时跑会读到 mutation 中的向量索引/字典(未定义行为/迭代崩溃)。
271
- # 统一补 self._lock(RLock 叶子锁,锁内不回调 pm/bus,无死锁)。
272
257
  def get_workspace(self, agent_id: str):
273
258
  with self._lock:
274
259
  return self._hmr.get_workspace(agent_id)
@@ -277,7 +262,6 @@ class HMRAdapter:
277
262
  with self._lock:
278
263
  return self._hmr.save_workspace(agent_id)
279
264
 
280
- # ── 运行时状态 ────────────────────────────────────────
281
265
  def save_runtime_state(self, **kwargs):
282
266
  with self._lock:
283
267
  return self._hmr.save_runtime_state(**kwargs)
@@ -286,7 +270,6 @@ class HMRAdapter:
286
270
  with self._lock:
287
271
  return self._hmr.restore_runtime_state()
288
272
 
289
- # ── 系统状态 ──────────────────────────────────────────
290
273
  def get_system_status(self):
291
274
  try:
292
275
  with self._lock:
@@ -307,8 +290,10 @@ class HMRAdapter:
307
290
 
308
291
  def _ts(m):
309
292
  v = getattr(m, "created_at", None)
293
+ if v is None:
294
+ return 0.0
310
295
  try:
311
- return v.timestamp() if hasattr(v, "timestamp") else float(v or 0)
296
+ return v.timestamp() if hasattr(v, "timestamp") else float(v)
312
297
  except Exception:
313
298
  return 0.0
314
299
 
@@ -325,11 +310,12 @@ class HMRAdapter:
325
310
  break
326
311
  created = getattr(m, "created_at", None)
327
312
  title = (getattr(m, "title", "") or "").strip()
328
- # 历史记忆多是 "Concept 03:11" 这类时间戳标题(修复前存的)→ 展示时现推一个可读的,
329
- # 让老数据也能看懂;推不出来才退回原标题。
330
313
  if not title or self._AUTO_TITLE.match(title):
331
314
  title = self._derive_title(
332
315
  getattr(m, "content", "") or getattr(m, "semantic_summary", "")) or title
316
+ created_at = str(created or "")
317
+ if created is not None and hasattr(created, "isoformat"):
318
+ created_at = created.isoformat()
333
319
  out.append({
334
320
  "id": getattr(m, "id", ""),
335
321
  "type": getattr(m, "type", ""),
@@ -337,7 +323,7 @@ class HMRAdapter:
337
323
  "summary": (getattr(m, "semantic_summary", "") or getattr(m, "content", "") or "")[:180],
338
324
  "agent": agent,
339
325
  "shared": "shared" in tags,
340
- "created_at": created.isoformat() if hasattr(created, "isoformat") else str(created or ""),
326
+ "created_at": created_at,
341
327
  })
342
328
  return out
343
329
 
@@ -346,17 +332,11 @@ class HMRAdapter:
346
332
  total = 0
347
333
  relations = 0
348
334
  shared_count = 0
349
- by_agent = {}
335
+ by_agent: dict[str, Any] = {}
350
336
  embedding = "TF-IDF n-gram"
351
337
  try:
352
- # 序列化对 HMR 内部结构(向量库/记忆图)的读,与 ingest/recall 的写互斥,
353
- # 避免并发迭代 store.memories("dict changed size")/读 mutation 中的 faiss 索引。
354
338
  with self._lock:
355
- # 权威来源:memory_fs 里的真实记忆对象。
356
- # 历史 bug:此前从 vector_store 探测 count/size/memories 取 total——但真实记忆存在
357
- # memory_fs,vector_store 只存嵌入且无这些属性 → total 恒为 0,而 relations 却有值(109),
358
- # 自相矛盾。改为直接数 memory_fs.list_memories()(与 get_statistics 同源)。
359
- mems = []
339
+ mems: list[Any] = []
360
340
  mfs = getattr(self._hmr, "memory_fs", None)
361
341
  if mfs is not None and hasattr(mfs, "list_memories"):
362
342
  try:
@@ -364,12 +344,11 @@ class HMRAdapter:
364
344
  except Exception:
365
345
  mems = []
366
346
  total = len(mems)
367
- if not total: # 兜底:memory_fs 不可用 → 数 vector_store.vectors(dict)
347
+ if not total:
368
348
  vs0 = getattr(self._hmr, "vector_store", None)
369
349
  vecs = getattr(vs0, "vectors", None) if vs0 is not None else None
370
350
  if isinstance(vecs, dict):
371
351
  total = len(vecs)
372
- # shared / 按 Agent 分布:agent_id 编码在 tags 里的 "agent:<id>"
373
352
  for m in mems:
374
353
  tags = getattr(m, "tags", None)
375
354
  if tags is None and isinstance(m, dict):
@@ -386,7 +365,6 @@ class HMRAdapter:
386
365
  edges = getattr(graph, "edges", None)
387
366
  if edges is not None:
388
367
  relations = len(edges) if hasattr(edges, "__len__") else 0
389
- # embedding 模型探测
390
368
  emb = getattr(self._hmr, "embedding", None) or getattr(self._hmr, "embedder", None)
391
369
  if emb is not None:
392
370
  name = type(emb).__name__
@@ -424,18 +402,13 @@ class MockHMR:
424
402
  pass
425
403
 
426
404
  def ingest(self, content, type="concept", agent_id=None, shared=False, **kwargs):
427
- # 【P0-3】与 HMRAdapter 同一道闸门:Mock 后端也过策略。否则在无 HMR 的环境里
428
- # 策略形同虚设 —— 而测试与降级场景恰好都落在这一支,那是最需要行为一致的地方。
429
405
  if shared:
430
406
  guard_shared_write(self, agent_id, content, type, kwargs.get("tags") or [])
431
407
  from types import SimpleNamespace
432
- # 注意:不能用内置 type() 构造对象——参数名 type 已覆盖内置 type。
433
408
  return SimpleNamespace(id="mock_mem", type=type)
434
409
 
435
- def recall(self, query: str = "", agent_id: str = None, top_k: int = 5, **kwargs):
410
+ def recall(self, query: str = "", agent_id: str | None = None, top_k: int = 5, **kwargs):
436
411
  from types import SimpleNamespace
437
- # 返回带 memory_objects 的对象,与真实 HMR 的 RecallResult 结构一致
438
- # (HMRAdapter.recall 会读 result.memory_objects)。
439
412
  return SimpleNamespace(memory_objects=[])
440
413
 
441
414
  def save_runtime_state(self, **kwargs):
@@ -484,7 +457,7 @@ def _memory_chain_enabled() -> bool:
484
457
  return raw.lower() in ("1", "true", "yes", "on")
485
458
 
486
459
 
487
- def create_memory_backend(storage_path: str = "./hmr_data", llm_api_key: str = None):
460
+ def create_memory_backend(storage_path: str = "./hmr_data", llm_api_key: str | None = None):
488
461
  """工厂:默认 HMR;`FOXCLAW_MEMORY_BACKEND=vecrecall` 时才切 vecRecall。
489
462
 
490
463
  顺序 vecRecall → HMR → MockHMR,任一层失败退到下一层,且**明确打印**实际用的是
@@ -508,9 +481,9 @@ def create_memory_backend(storage_path: str = "./hmr_data", llm_api_key: str = N
508
481
  print(f"[Foxclaw] ⚠ FOXCLAW_MEMORY_BACKEND={choice!r} 无法识别,按 HMR 处理")
509
482
 
510
483
  try:
511
- adapter = HMRAdapter(storage_path=storage_path, llm_api_key=llm_api_key)
512
- print(f"[Foxclaw] ✓ HMR v{adapter.version} 已接入 (backend={adapter.backend})")
513
- return adapter
484
+ hmr = HMRAdapter(storage_path=storage_path, llm_api_key=llm_api_key)
485
+ print(f"[Foxclaw] ✓ HMR v{hmr.version} 已接入 (backend={hmr.backend})")
486
+ return hmr
514
487
  except Exception as e:
515
488
  print(f"[Foxclaw] ⚠ HMR 不可用 ({e.__class__.__name__}: {e}),降级 MockHMR")
516
489
  return MockHMR()
@@ -5,7 +5,7 @@ import time
5
5
  import threading
6
6
  from collections import defaultdict, deque
7
7
 
8
- _MAX_SAMPLES = 500 # 每个 key 保留最近 N 次延迟(有界,防内存涨)
8
+ _MAX_SAMPLES = 500
9
9
 
10
10
 
11
11
  class _Stat:
@@ -14,7 +14,7 @@ class _Stat:
14
14
  def __init__(self):
15
15
  self.count = 0
16
16
  self.errors = 0
17
- self.samples = deque(maxlen=_MAX_SAMPLES)
17
+ self.samples: deque[float] = deque(maxlen=_MAX_SAMPLES)
18
18
 
19
19
 
20
20
  def _pct(sorted_vals, p: float) -> float:
@@ -29,8 +29,8 @@ class MetricsCollector:
29
29
 
30
30
  def __init__(self):
31
31
  self._lock = threading.RLock()
32
- self._ep: dict = defaultdict(_Stat) # endpoint(路由模板) -> _Stat
33
- self._llm: dict = defaultdict(_Stat) # provider -> _Stat
32
+ self._ep: dict = defaultdict(_Stat)
33
+ self._llm: dict = defaultdict(_Stat)
34
34
  self._start = time.time()
35
35
 
36
36
  def record_request(self, endpoint: str, status: int, ms: float):
@@ -85,5 +85,4 @@ class MetricsCollector:
85
85
  return "\n".join(out) + "\n"
86
86
 
87
87
 
88
- # 进程级单例:中间件 + router 都往这里写,端点从这里读。
89
88
  METRICS = MetricsCollector()