foxclaw 0.1.2 → 0.1.4

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 (368) 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/.github/workflows/release.yml +5 -0
  6. package/python/CHANGELOG.md +100 -0
  7. package/python/CHANGELOG_CN.md +100 -0
  8. package/python/README.md +17 -2
  9. package/python/README_CN.md +38 -20
  10. package/python/bench/bm25_audit.py +1 -14
  11. package/python/bench/bm25_regression.py +0 -1
  12. package/python/bench/bm25_stress.py +0 -4
  13. package/python/bench/cd_audit.py +0 -23
  14. package/python/bench/chaos.py +6 -8
  15. package/python/bench/e2e_real_models.py +0 -1
  16. package/python/bench/llm_retry_test.py +2 -9
  17. package/python/bench/load_test.py +5 -8
  18. package/python/bench/vector_ivf_upgrade.py +1 -7
  19. package/python/docs/CONTENT_GUARD.md +189 -0
  20. package/python/docs/CONTENT_GUARD_CN.md +189 -0
  21. package/python/docs/RELEASE.md +78 -37
  22. package/python/docs/RUN_CN.md +2 -2
  23. package/python/evals/governance/cases.py +0 -37
  24. package/python/evals/governance/run_evals.py +5 -37
  25. package/python/examples/governance_middleware_demo.py +1 -6
  26. package/python/examples/langgraph_governance_demo.py +7 -11
  27. package/python/foxclaw/__init__.py +74 -152
  28. package/python/foxclaw/agents/base.py +82 -136
  29. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  30. package/python/foxclaw/agents/impl/finance.py +4 -21
  31. package/python/foxclaw/agents/impl/office.py +7 -24
  32. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  33. package/python/foxclaw/agents/mixins/_context.py +0 -23
  34. package/python/foxclaw/agents/mixins/augment.py +10 -16
  35. package/python/foxclaw/agents/mixins/llm.py +7 -6
  36. package/python/foxclaw/agents/mixins/memory.py +23 -3
  37. package/python/foxclaw/agents/mixins/preference.py +58 -8
  38. package/python/foxclaw/agents/mixins/prompt.py +59 -12
  39. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  40. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  41. package/python/foxclaw/agents/rules/finance/RULES.md +131 -0
  42. package/python/foxclaw/api/__init__.py +18 -77
  43. package/python/foxclaw/api/_state.py +43 -0
  44. package/python/foxclaw/api/bootstrap.py +114 -0
  45. package/python/foxclaw/api/middleware.py +5 -28
  46. package/python/foxclaw/api/routes/ahs.py +60 -73
  47. package/python/foxclaw/api/routes/attachments.py +5 -6
  48. package/python/foxclaw/api/routes/browser.py +7 -11
  49. package/python/foxclaw/api/routes/domain.py +18 -26
  50. package/python/foxclaw/api/routes/knowledge.py +3 -21
  51. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  52. package/python/foxclaw/api/routes/memory.py +50 -12
  53. package/python/foxclaw/api/routes/orchestration.py +3 -5
  54. package/python/foxclaw/api/routes/recovery.py +4 -3
  55. package/python/foxclaw/api/routes/review.py +2 -11
  56. package/python/foxclaw/api/routes/search.py +3 -7
  57. package/python/foxclaw/api/routes/system.py +33 -49
  58. package/python/foxclaw/api/routes/task.py +31 -49
  59. package/python/foxclaw/api/routes/ws.py +2 -6
  60. package/python/foxclaw/api/spine.py +1 -4
  61. package/python/foxclaw/behavior/__init__.py +20 -0
  62. package/python/foxclaw/behavior/behavior.yaml +62 -0
  63. package/python/foxclaw/behavior/spec.py +287 -0
  64. package/python/foxclaw/browser/action/action_executor.py +1 -5
  65. package/python/foxclaw/browser/action/action_types.py +0 -1
  66. package/python/foxclaw/browser/action/url_policy.py +1 -5
  67. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  68. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  69. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  70. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  71. package/python/foxclaw/browser/service.py +7 -11
  72. package/python/foxclaw/capability/embedding_service.py +4 -6
  73. package/python/foxclaw/channels/__init__.py +9 -11
  74. package/python/foxclaw/channels/discord.py +4 -13
  75. package/python/foxclaw/channels/dispatcher.py +2 -27
  76. package/python/foxclaw/channels/feishu.py +10 -20
  77. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  78. package/python/foxclaw/channels/signing.py +9 -9
  79. package/python/foxclaw/channels/telegram.py +3 -4
  80. package/python/foxclaw/channels/weixin.py +7 -17
  81. package/python/foxclaw/cli.py +42 -13
  82. package/python/foxclaw/cognitive/reasoner.py +9 -10
  83. package/python/foxclaw/cognitive/state.py +18 -24
  84. package/python/foxclaw/core/audit_chain.py +1 -3
  85. package/python/foxclaw/core/audit_sink.py +3 -3
  86. package/python/foxclaw/core/compaction.py +8 -26
  87. package/python/foxclaw/core/config.py +2 -2
  88. package/python/foxclaw/core/confirm_nonce.py +6 -12
  89. package/python/foxclaw/core/content_guard.py +341 -0
  90. package/python/foxclaw/core/envfile.py +2 -2
  91. package/python/foxclaw/core/failure_policy.py +17 -26
  92. package/python/foxclaw/core/gateway.py +6 -10
  93. package/python/foxclaw/core/governance_audit.py +3 -7
  94. package/python/foxclaw/core/idempotency.py +15 -22
  95. package/python/foxclaw/core/injection_guard.py +10 -29
  96. package/python/foxclaw/core/memory_adapter.py +53 -60
  97. package/python/foxclaw/core/metrics.py +4 -5
  98. package/python/foxclaw/core/model_router.py +74 -150
  99. package/python/foxclaw/core/paths.py +1 -1
  100. package/python/foxclaw/core/recovery.py +21 -34
  101. package/python/foxclaw/core/scheduler.py +23 -26
  102. package/python/foxclaw/core/secrets.py +0 -3
  103. package/python/foxclaw/core/sensitive_write.py +283 -0
  104. package/python/foxclaw/core/sessions.py +4 -6
  105. package/python/foxclaw/core/settings.py +16 -32
  106. package/python/foxclaw/core/shared_write.py +11 -23
  107. package/python/foxclaw/core/steering_queue.py +12 -11
  108. package/python/foxclaw/core/system_bus.py +1 -1
  109. package/python/foxclaw/core/thought_chain.py +30 -32
  110. package/python/foxclaw/core/token_meter.py +9 -13
  111. package/python/foxclaw/core/tools.py +4 -12
  112. package/python/foxclaw/core/tracing.py +25 -40
  113. package/python/foxclaw/core/truth_pipeline.py +5 -9
  114. package/python/foxclaw/core/update_check.py +149 -0
  115. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  116. package/python/foxclaw/core/verify.py +6 -14
  117. package/python/foxclaw/core/web_search.py +4 -28
  118. package/python/foxclaw/finance/evidence.py +14 -18
  119. package/python/foxclaw/finance/fx.py +1 -3
  120. package/python/foxclaw/finance/graph.py +9 -18
  121. package/python/foxclaw/finance/models.py +20 -24
  122. package/python/foxclaw/finance/nlparse.py +12 -72
  123. package/python/foxclaw/finance/policy.py +8 -36
  124. package/python/foxclaw/finance/reasoner.py +3 -11
  125. package/python/foxclaw/finance/reasoning.py +13 -34
  126. package/python/foxclaw/finance/reconcile.py +5 -10
  127. package/python/foxclaw/finance/runtime.py +19 -46
  128. package/python/foxclaw/governance/middleware.py +13 -21
  129. package/python/foxclaw/harness/_base.py +2 -8
  130. package/python/foxclaw/harness/_policy.py +19 -38
  131. package/python/foxclaw/harness/drift.py +1 -13
  132. package/python/foxclaw/harness/event_bus.py +2 -5
  133. package/python/foxclaw/harness/harness.py +9 -97
  134. package/python/foxclaw/harness/loop_detection.py +11 -18
  135. package/python/foxclaw/itinerary/amap_client.py +2 -5
  136. package/python/foxclaw/itinerary/models.py +7 -7
  137. package/python/foxclaw/itinerary/reasoner.py +1 -6
  138. package/python/foxclaw/itinerary/reasoning.py +15 -21
  139. package/python/foxclaw/itinerary/runtime.py +8 -12
  140. package/python/foxclaw/kernel/capabilities.py +5 -6
  141. package/python/foxclaw/kernel/policy_gate.py +4 -10
  142. package/python/foxclaw/kernel/process_manager.py +17 -36
  143. package/python/foxclaw/kernel/scheduler.py +3 -9
  144. package/python/foxclaw/kernel/sentinel.py +13 -23
  145. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  146. package/python/foxclaw/kernel/spine.py +9 -9
  147. package/python/foxclaw/knowledge/__init__.py +4 -20
  148. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  149. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  150. package/python/foxclaw/learning/models.py +13 -13
  151. package/python/foxclaw/learning/reasoner.py +1 -4
  152. package/python/foxclaw/learning/runtime.py +19 -31
  153. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  154. package/python/foxclaw/mcp_server.py +2 -3
  155. package/python/foxclaw/orchestration/__init__.py +16 -33
  156. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  157. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  158. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  159. package/python/foxclaw/planning/llm_planner.py +3 -3
  160. package/python/foxclaw/planning/planner.py +6 -11
  161. package/python/foxclaw/search/agent_port.py +7 -11
  162. package/python/foxclaw/search/evidence.py +0 -4
  163. package/python/foxclaw/search/internal_search.py +3 -5
  164. package/python/foxclaw/search/models.py +11 -13
  165. package/python/foxclaw/search/planner.py +3 -3
  166. package/python/foxclaw/search/retriever.py +1 -1
  167. package/python/foxclaw/search/runtime.py +1 -8
  168. package/python/foxclaw/skills/__init__.py +46 -100
  169. package/python/foxclaw/skills/archive.py +111 -0
  170. package/python/foxclaw/skills/fetch.py +226 -0
  171. package/python/foxclaw/skills/hooks_spec.py +21 -25
  172. package/python/foxclaw/skills/install_cmd.py +216 -0
  173. package/python/foxclaw/skills/lifecycle.py +16 -42
  174. package/python/foxclaw/skills/markdown_skill.py +10 -9
  175. package/python/foxclaw/skills/mcp_client.py +5 -11
  176. package/python/foxclaw/skills/mcp_store.py +0 -3
  177. package/python/foxclaw/skills/permission.py +13 -48
  178. package/python/foxclaw/skills/pipeline.py +21 -53
  179. package/python/foxclaw/skills/policy.py +4 -10
  180. package/python/foxclaw/skills/review.py +3 -3
  181. package/python/frontend/deck/auth.js +0 -7
  182. package/python/frontend/deck/chat_model_picker.js +0 -54
  183. package/python/frontend/deck/domain.js +0 -20
  184. package/python/frontend/deck/main.js +0 -144
  185. package/python/frontend/deck/markdown.js +0 -4
  186. package/python/frontend/deck/settings.js +1 -18
  187. package/python/frontend/deck/state.js +0 -1
  188. package/python/frontend/deck/util.js +0 -2
  189. package/python/frontend/deck/visuals.js +0 -22
  190. package/python/frontend/planet/planet.js +18 -201
  191. package/python/frontend/v3/assets/index-JKfHlb-a.css +1 -0
  192. package/python/frontend/v3/assets/index-tN90dpUH.js +34 -0
  193. package/python/frontend/v3/index.html +3 -3
  194. package/python/frontend/widget_runtime.js +0 -43
  195. package/python/frontend/widgets.js +0 -57
  196. package/python/frontend_src/index.html +1 -1
  197. package/python/frontend_src/package-lock.json +2 -2
  198. package/python/frontend_src/package.json +1 -1
  199. package/python/frontend_src/src/App.vue +3 -7
  200. package/python/frontend_src/src/api.js +0 -7
  201. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  202. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  203. package/python/frontend_src/src/components/Inspector.vue +0 -6
  204. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  205. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  206. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  207. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  208. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  209. package/python/frontend_src/src/components/Stage.vue +71 -25
  210. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  211. package/python/frontend_src/src/components/TopBar.vue +38 -10
  212. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  213. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  214. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  215. package/python/frontend_src/src/components/panels/MemoryPanel.vue +42 -3
  216. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  217. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  218. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  219. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  220. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  221. package/python/frontend_src/src/lib/fmt.js +0 -4
  222. package/python/frontend_src/src/lib/i18n.js +53 -0
  223. package/python/frontend_src/src/lib/locales/en.js +173 -0
  224. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  225. package/python/frontend_src/src/lib/md.js +0 -14
  226. package/python/frontend_src/src/main.js +2 -0
  227. package/python/frontend_src/src/stores/app.js +17 -32
  228. package/python/frontend_src/src/stores/chat.js +0 -34
  229. package/python/frontend_src/src/stores/model.js +0 -8
  230. package/python/frontend_src/src/stores/panels.js +16 -44
  231. package/python/frontend_src/src/stores/profile.js +31 -0
  232. package/python/frontend_src/src/stores/system.js +25 -0
  233. package/python/frontend_src/vite.config.js +0 -7
  234. package/python/pyproject.toml +17 -3
  235. package/python/requirements.txt +311 -0
  236. package/python/tests/conftest.py +0 -22
  237. package/python/tests/run_all.py +4 -66
  238. package/python/tests/test_agent_lifecycle.py +4 -38
  239. package/python/tests/test_agent_rules.py +81 -0
  240. package/python/tests/test_ahs_p2.py +4 -25
  241. package/python/tests/test_anthropic_tools.py +1 -15
  242. package/python/tests/test_api.py +19 -9
  243. package/python/tests/test_api_auth.py +1 -5
  244. package/python/tests/test_attachments.py +9 -25
  245. package/python/tests/test_audit_chain.py +2 -7
  246. package/python/tests/test_audit_sink.py +0 -6
  247. package/python/tests/test_behavior_spec.py +198 -0
  248. package/python/tests/test_browser.py +2 -6
  249. package/python/tests/test_browser_request_guard.py +0 -4
  250. package/python/tests/test_capabilities.py +0 -6
  251. package/python/tests/test_channel_signing.py +2 -9
  252. package/python/tests/test_channels.py +1 -11
  253. package/python/tests/test_config_layering.py +0 -4
  254. package/python/tests/test_confirm_nonce.py +1 -12
  255. package/python/tests/test_conflict_dismiss.py +143 -0
  256. package/python/tests/test_content_guard.py +177 -0
  257. package/python/tests/test_context_compaction_wiring.py +1 -7
  258. package/python/tests/test_dispatch.py +0 -6
  259. package/python/tests/test_domain_evals.py +11 -80
  260. package/python/tests/test_domain_import.py +0 -6
  261. package/python/tests/test_domain_reasoners.py +0 -14
  262. package/python/tests/test_e2e.py +0 -17
  263. package/python/tests/test_emb_cache.py +7 -17
  264. package/python/tests/test_embedding_singleton.py +1 -3
  265. package/python/tests/test_evals_smoke.py +0 -24
  266. package/python/tests/test_event_spine.py +10 -18
  267. package/python/tests/test_evidence_semantic.py +1 -14
  268. package/python/tests/test_evidence_sse.py +3 -6
  269. package/python/tests/test_file_ingest.py +0 -11
  270. package/python/tests/test_finance.py +0 -9
  271. package/python/tests/test_finance_ledger.py +5 -12
  272. package/python/tests/test_finance_persist.py +8 -12
  273. package/python/tests/test_gateway.py +3 -22
  274. package/python/tests/test_governance.py +5 -52
  275. package/python/tests/test_governance_audit.py +4 -39
  276. package/python/tests/test_governance_middleware.py +1 -7
  277. package/python/tests/test_hkc.py +6 -24
  278. package/python/tests/test_hkc_index_build_manager.py +0 -33
  279. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  280. package/python/tests/test_hkc_search_event_sync.py +3 -54
  281. package/python/tests/test_import_path_guard.py +0 -6
  282. package/python/tests/test_injection_guard.py +18 -36
  283. package/python/tests/test_integration.py +5 -29
  284. package/python/tests/test_itinerary.py +0 -3
  285. package/python/tests/test_kee_semantic.py +0 -5
  286. package/python/tests/test_kernel.py +1 -11
  287. package/python/tests/test_learning.py +3 -12
  288. package/python/tests/test_llm_admin.py +4 -19
  289. package/python/tests/test_llm_planner.py +1 -23
  290. package/python/tests/test_markdown_skills.py +3 -14
  291. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  292. package/python/tests/test_mcp_connectors.py +3 -15
  293. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  294. package/python/tests/test_mcp_no_memory.py +3 -3
  295. package/python/tests/test_mcp_policy_gate.py +0 -1
  296. package/python/tests/test_mcp_server.py +6 -9
  297. package/python/tests/test_memory_recall.py +1 -4
  298. package/python/tests/test_metrics.py +0 -8
  299. package/python/tests/test_multi_chain.py +1 -13
  300. package/python/tests/test_multimodal.py +5 -18
  301. package/python/tests/test_negotiation.py +3 -13
  302. package/python/tests/test_orchestration.py +1 -11
  303. package/python/tests/test_orchestration_decoupled.py +0 -6
  304. package/python/tests/test_p3_p5_hardening.py +3 -17
  305. package/python/tests/test_parallel_dispatch.py +4 -12
  306. package/python/tests/test_pipeline.py +17 -58
  307. package/python/tests/test_plan_visible.py +4 -4
  308. package/python/tests/test_planner.py +0 -7
  309. package/python/tests/test_policy_gate.py +2 -3
  310. package/python/tests/test_profile.py +67 -0
  311. package/python/tests/test_readme_claims.py +163 -0
  312. package/python/tests/test_reasoner.py +2 -17
  313. package/python/tests/test_release_check.py +2 -13
  314. package/python/tests/test_resume_checkpoint.py +7 -15
  315. package/python/tests/test_routing.py +2 -12
  316. package/python/tests/test_scheduler.py +7 -10
  317. package/python/tests/test_search.py +1 -11
  318. package/python/tests/test_secrets.py +4 -10
  319. package/python/tests/test_semantic.py +1 -10
  320. package/python/tests/test_sensitive_write.py +181 -0
  321. package/python/tests/test_sessions.py +3 -4
  322. package/python/tests/test_settings.py +2 -18
  323. package/python/tests/test_shadow_learning.py +3 -43
  324. package/python/tests/test_shared_write.py +0 -7
  325. package/python/tests/test_skill_taint_gate.py +1 -10
  326. package/python/tests/test_skills.py +2 -18
  327. package/python/tests/test_skills_fetch.py +282 -0
  328. package/python/tests/test_stats_and_events.py +2 -5
  329. package/python/tests/test_streaming.py +2 -7
  330. package/python/tests/test_supervisor_pipeline.py +4 -5
  331. package/python/tests/test_supervisor_triage.py +4 -10
  332. package/python/tests/test_taint.py +2 -7
  333. package/python/tests/test_task_stream.py +1 -12
  334. package/python/tests/test_thinking_sse.py +0 -1
  335. package/python/tests/test_tool_center.py +1 -17
  336. package/python/tests/test_tool_parallel.py +2 -2
  337. package/python/tests/test_tool_params.py +1 -5
  338. package/python/tests/test_tool_use.py +0 -13
  339. package/python/tests/test_toplevel_concurrency.py +3 -4
  340. package/python/tests/test_tracing.py +7 -41
  341. package/python/tests/test_truth_crystallizer.py +4 -6
  342. package/python/tests/test_truth_flow.py +2 -7
  343. package/python/tests/test_update_check.py +95 -0
  344. package/python/tests/test_verify_evidence.py +8 -21
  345. package/python/tests/test_version_sync.py +35 -6
  346. package/python/tests/test_web_trigger.py +0 -17
  347. package/python/tools/build_content_lexicon.py +218 -0
  348. package/python/tools/bundle_web.py +98 -0
  349. package/python/tools/check_faiss_type.py +0 -1
  350. package/python/tools/check_log.ps1 +0 -8
  351. package/python/tools/mypy_baseline.txt +12 -4
  352. package/python/tools/mypy_ratchet.py +52 -6
  353. package/python/tools/pack.py +0 -5
  354. package/python/tools/recheck_conflicts.py +156 -0
  355. package/python/tools/release_check.py +24 -15
  356. package/python/tools/restart_foxclaw.ps1 +5 -8
  357. package/python/tools/shared_write_audit.py +1 -15
  358. package/python/tools/size_check.py +1 -1
  359. package/python/tools/strip_comments.py +228 -0
  360. package/python/vendor/hkc/hkc_api/routes/ability.py +31 -0
  361. package/python/vendor/hkc/hkc_api/schemas.py +11 -0
  362. package/python/vendor/hkc/hkc_kee/kee.py +124 -3
  363. package/python/vendor/hkc/hkc_sdk/base.py +22 -0
  364. package/python/vendor/hkc/hkc_sdk/direct_client.py +10 -0
  365. package/python/vendor/hkc/hkc_sdk/http_client.py +19 -0
  366. package/python/foxclaw/api/app.py +0 -123
  367. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  368. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -45,8 +45,6 @@ from functools import lru_cache
45
45
  from pathlib import Path
46
46
  from typing import Any, Iterable, Mapping, Sequence
47
47
 
48
- # provider → 需要的环境变量(与 core/model_router.PROVIDER_ENV 保持一致;
49
- # 这里重复一份是为了让 settings 不依赖 model_router —— 那会拖入 LLM SDK 的导入)。
50
48
  PROVIDER_KEY_ENV: dict[str, str] = {
51
49
  "anthropic": "ANTHROPIC_API_KEY",
52
50
  "openai": "OPENAI_API_KEY",
@@ -58,8 +56,6 @@ PROVIDER_KEY_ENV: dict[str, str] = {
58
56
  "generic": "LLM_API_KEY",
59
57
  }
60
58
 
61
- # 只检查这些前缀下的"未知键":它们属于本项目或本项目专用的厂商约定,
62
- # 出现没人读的名字几乎必然是拼错。像 `PATH` / `HOME` 这类系统变量不在此列。
63
59
  OWNED_PREFIXES = (
64
60
  "FOXCLAW_", "BAIDU_", "AMAP_", "LLM_", "GENERIC_LLM_",
65
61
  "MINIMAX_", "LOCAL_LLM_", "HKC_", "HMR_",
@@ -75,10 +71,10 @@ class Opt:
75
71
  """一个环境变量的声明。kind 只描述**解析方式**,不代表业务语义。"""
76
72
 
77
73
  name: str
78
- kind: str = "str" # str | int | float | csv | choice
74
+ kind: str = "str"
79
75
  default: Any = ""
80
76
  group: str = "其他"
81
- hint: str = "" # 期望格式 / 示例(拼进出错信息)
77
+ hint: str = ""
82
78
  choices: Sequence[str] = ()
83
79
  secret: bool = False
84
80
  lo: float | None = None
@@ -86,10 +82,6 @@ class Opt:
86
82
 
87
83
 
88
84
  SPECS: tuple[Opt, ...] = (
89
- # lo/hi 只给**调用点自己不兜底**的项。像 TRACE_RETAIN 已有 max(100, ...)、
90
- # BAIDU_SEARCH_TIMEOUT 已有 max(5, ...):在那里再报一次范围错,等于把"clamp"
91
- # 变成"整条配置作废退默认",反而更糟。
92
- # ── 模型 ────────────────────────────────────────────────────────────────
93
85
  Opt("LLM_PROVIDER", "choice", "", "模型",
94
86
  "anthropic / openai / deepseek / google / qwen / local / minimax / generic",
95
87
  tuple(PROVIDER_KEY_ENV)),
@@ -109,9 +101,10 @@ SPECS: tuple[Opt, ...] = (
109
101
  Opt("LLM_API_KEY", "str", "", "模型", "generic(通用 openai 兼容)的 key", secret=True),
110
102
  Opt("LLM_BASE_URL", "str", "", "模型", "generic 的 base_url,如 https://host/v1"),
111
103
  Opt("LOCAL_LLM_BASE", "str", "", "模型", "本地推理服务的 base_url"),
112
- # ── 服务 ────────────────────────────────────────────────────────────────
113
104
  Opt("FOXCLAW_PORT", "int", 8000, "服务", "HTTP 监听端口", lo=1, hi=65535),
114
105
  Opt("FOXCLAW_DATA_DIR", "str", "", "服务", "数据目录(默认 <仓库>/foxclaw_data)"),
106
+ Opt("FOXCLAW_CLAWHUB_REGISTRY", "str", "", "服务",
107
+ "ClawHub 地址(默认 https://clawhub.ai;内网镜像或自建 registry 填这里)"),
115
108
  Opt("FOXCLAW_IMPORT_ROOTS", "csv", "", "服务",
116
109
  "额外允许的导入目录(逗号分隔);不配则只有数据目录内的文件可被导入"),
117
110
  Opt("FOXCLAW_API_KEY", "str", "", "服务",
@@ -127,7 +120,6 @@ SPECS: tuple[Opt, ...] = (
127
120
  Opt("FOXCLAW_STREAM_RATE", "int", 60, "服务", "流式端点每分钟上限", lo=0),
128
121
  Opt("FOXCLAW_STREAM_CONCURRENCY", "int", 4, "服务", "流式并发上限", lo=1),
129
122
  Opt("FOXCLAW_SHADOW_ROUNDS", "int", 20, "服务", "影子推演轮数", lo=1),
130
- # ── 工具 / 追踪 ─────────────────────────────────────────────────────────
131
123
  Opt("FOXCLAW_TOOL_TIMEOUT", "float", 45.0, "工具",
132
124
  "单个工具调用硬超时(秒);可小于 1(测试用),调用点会兜 >0"),
133
125
  Opt("FOXCLAW_TRACE_SLOW_MS", "float", 1000.0, "追踪", "超过多少毫秒算慢请求", lo=0),
@@ -135,12 +127,10 @@ SPECS: tuple[Opt, ...] = (
135
127
  "非慢请求的采样率 0~1;超出范围由调用点 clamp,不在这里报错"),
136
128
  Opt("FOXCLAW_TRACE_RETAIN", "int", 5000, "追踪",
137
129
  "内存里保留的 trace 条数;调用点已有 max(100, ...) 兜底"),
138
- # ── 搜索 ────────────────────────────────────────────────────────────────
139
130
  Opt("BAIDU_SEARCH_KEY", "str", "", "搜索", "百度 AI 搜索的 key", secret=True),
140
131
  Opt("BAIDU_SEARCH_MODEL", "str", "", "搜索", "百度搜索用的模型名"),
141
132
  Opt("BAIDU_SEARCH_BASE", "str", "", "搜索", "百度搜索的 base_url"),
142
133
  Opt("BAIDU_SEARCH_TIMEOUT", "int", 45, "搜索", "秒;调用点已有 max(5, ...) 兜底"),
143
- # ── 渠道 ────────────────────────────────────────────────────────────────
144
134
  Opt("FOXCLAW_CHANNEL_TRUSTED_SENDERS", "csv", "", "渠道",
145
135
  "逗号分隔的可信发送者:<id> / <channel>:<id> / <channel>:*"),
146
136
  Opt("FOXCLAW_CHANNEL_TRUST_ALL", "str", "", "渠道",
@@ -148,27 +138,24 @@ SPECS: tuple[Opt, ...] = (
148
138
  Opt("FOXCLAW_CHANNEL_SIGNING_SECRET", "str", "", "渠道",
149
139
  "出站 HMAC 密钥;留空 = 不签", secret=True),
150
140
  Opt("FOXCLAW_CHANNEL_SIGNING_KEY_ID", "str", "", "渠道", "密钥轮换标识"),
151
- # ── 浏览器 ──────────────────────────────────────────────────────────────
152
141
  Opt("FOXCLAW_BROWSER_ALLOW_PRIVATE", "str", "", "浏览器",
153
142
  "1/true/yes 才允许访问内网地址"),
154
143
  )
155
144
 
156
145
  SPEC_BY_NAME: dict[str, Opt] = {o.name: o for o in SPECS}
157
146
 
158
- # 会在启动/运行早期被读、且类型写错就会崩的少数几个 —— 供 serve 预检快速拦截。
159
147
  FATAL_CODES = ("type", "choice", "range")
160
148
 
161
149
 
162
150
  @dataclass
163
151
  class Issue:
164
- level: str # "error" | "warn"
165
- code: str # type / choice / range / provider_key / unknown_key ...
152
+ level: str
153
+ code: str
166
154
  name: str
167
155
  message: str
168
- hint: str = "" # 修复建议(可为空)
156
+ hint: str = ""
169
157
 
170
158
  def __str__(self) -> str:
171
- # 只用 ASCII 记号:Windows 控制台默认 GBK,✓/✗/⚠ 会抛 UnicodeEncodeError
172
159
  mark = "ERROR" if self.level == "error" else " WARN"
173
160
  head = f"[{mark}] {self.name}: {self.message}" if self.name else f"[{mark}] {self.message}"
174
161
  return f"{head}\n → {self.hint}" if self.hint else head
@@ -178,7 +165,7 @@ def _caller() -> str:
178
165
  """当前 `get_*` 的调用点(文件:行)。只在出错路径上求值,不进热路径。"""
179
166
  try:
180
167
  f = inspect.currentframe()
181
- for _ in range(3): # 跳过 get_* → _parse → _caller 自身
168
+ for _ in range(3):
182
169
  if f is None:
183
170
  return ""
184
171
  f = f.f_back
@@ -210,16 +197,16 @@ def _parse(opt: Opt, raw: str, where: str = "") -> Any:
210
197
  return value
211
198
 
212
199
  if opt.kind == "choice":
213
- value = text.lower()
214
- if value not in opt.choices:
200
+ choice = text.lower()
201
+ if choice not in opt.choices:
215
202
  raise ConfigError(
216
203
  f'值 "{raw}" 不在允许范围内。可选:{" / ".join(opt.choices)}{suffix}')
217
- return value
204
+ return choice
218
205
 
219
206
  if opt.kind == "csv":
220
207
  return [s.strip() for s in text.split(",") if s.strip()]
221
208
 
222
- return raw # str:保留原样(尾部空格可能有意义,如口令)
209
+ return raw
223
210
 
224
211
 
225
212
  def _opt(name: str) -> Opt:
@@ -251,7 +238,7 @@ _warned: set[str] = set()
251
238
  _log = logging.getLogger("foxclaw.config")
252
239
 
253
240
 
254
- def get_safe(name: str, default: Any = None) -> Any:
241
+ def get_safe(name: str, default: Any | None = None) -> Any:
255
242
  """可降级的读取:值非法时**退默认值并要求日志警告一次**,不抛异常。
256
243
 
257
244
  给"配置错不该拖垮主流程"的地方用(trace 采样、工具超时、搜索超时):这些点
@@ -262,12 +249,12 @@ def get_safe(name: str, default: Any = None) -> Any:
262
249
  fallback = opt.default if default is None else default
263
250
  raw = os.environ.get(name)
264
251
  if raw is None or not raw.strip():
265
- return fallback # 未设置/空串:用调用方给的兜底
252
+ return fallback
266
253
  try:
267
254
  return _parse(opt, raw, _caller())
268
255
  except ConfigError as e:
269
256
  if name not in _warned:
270
- _warned.add(name) # 只喊一次:tracing 是每请求调用
257
+ _warned.add(name)
271
258
  _log.warning("[config] %s", e)
272
259
  return fallback
273
260
 
@@ -297,8 +284,6 @@ def known_keys() -> frozenset[str]:
297
284
  只在 `check()` / `show()` 这类诊断路径调用,不进请求路径;结果缓存在本进程内。
298
285
  """
299
286
  pkg = Path(__file__).resolve().parent.parent
300
- # vendor/(HKC/HMR)在仓库根,不在包内:它们自己读 HMR_*/HKC_* 变量,不一起扫就会把
301
- # 正常配置报成"拼错的键"。wheel 里没有 vendor/ → 扫描自然为空,不影响安装后的行为。
302
287
  roots = [pkg] + ([pkg.parent / "vendor"] if (pkg.parent / "vendor").is_dir() else [])
303
288
  found: set[str] = set(SPEC_BY_NAME)
304
289
  files: list[Path] = []
@@ -308,7 +293,7 @@ def known_keys() -> frozenset[str]:
308
293
  for path in files:
309
294
  try:
310
295
  tree = ast.parse(path.read_text(encoding="utf-8", errors="replace"))
311
- except SyntaxError: # 语法坏掉的文件留着给 CI 报,这里跳过
296
+ except SyntaxError:
312
297
  continue
313
298
  for node in ast.walk(tree):
314
299
  if isinstance(node, ast.Call):
@@ -401,7 +386,6 @@ def check(env: Mapping[str, str] | None = None) -> list[Issue]:
401
386
  except ConfigError as e:
402
387
  code = "choice" if opt.kind == "choice" else (
403
388
  "range" if ("下限" in str(e) or "上限" in str(e)) else "type")
404
- # 密钥不回显原值,免得把半截口令打进日志
405
389
  msg = str(e)
406
390
  if is_secret(opt.name):
407
391
  msg = f"值 {mask(raw)} 不合法(原始值已脱敏)"
@@ -42,21 +42,12 @@ from typing import Iterable, Optional
42
42
  MODE_ENV = "FOXCLAW_SHARED_WRITE_MODE"
43
43
  VALID_MODES = ("audit", "warn", "enforce")
44
44
 
45
- # 与写入方无关、任何 Agent 都可写的 facet(设计上就该跨 Agent 共享的东西):
46
- # preference —— 用户偏好:base.py 明确以 shared=True 写入,意在跨会话跨 Agent 生效
47
- # truth_pipeline —— 真值管线沉淀(truth_pipeline.py),写入方是 agent_id="system"
48
45
  GLOBAL_ALLOWED_FACETS = frozenset({"preference", "truth_pipeline"})
49
46
 
50
- # 各 Agent 的「职责域」允许 facet。facet 取自一次写入的 {type:<记忆类型>} ∪ tags,
51
- # 支持 fnmatch 通配(如 finance.* 可匹配 finance.budget)。
52
- #
53
- # 注意:这是**起始假设**,不是已验证的结论 —— 默认 audit 模式的目的正是用真实流量验证它。
54
47
  DEFAULT_SHARED_WRITE_ALLOWED = {
55
- "supervisor": {"*"}, # 主管:分发/协调记录,语义上允许任意
56
- "system": {"*"}, # 系统(真值管线、后台任务)
48
+ "supervisor": {"*"},
49
+ "system": {"*"},
57
50
  "finance": {"finance.*"},
58
- # 行政并入办公后,办公学习 Agent 同时拥有学习与行程两个写域;
59
- # 助手已下放为技能(不再有独立 agent_id),不再单列 ACL 条目。
60
51
  "office": {"learning.*", "itinerary.*", "schedule.*"},
61
52
  }
62
53
 
@@ -90,8 +81,8 @@ class SharedWritePolicy:
90
81
  无状态、无锁、纯函数式判定 —— 并发安全直接成立,也便于单独测试。
91
82
  """
92
83
 
93
- def __init__(self, allowed: dict = None, mode: str = None,
94
- global_facets: Iterable[str] = None):
84
+ def __init__(self, allowed: dict | None = None, mode: str | None = None,
85
+ global_facets: Iterable[str] | None = None):
95
86
  self.allowed = {k: set(v) for k, v in
96
87
  (allowed or DEFAULT_SHARED_WRITE_ALLOWED).items()}
97
88
  self.global_facets = set(global_facets if global_facets is not None
@@ -99,13 +90,11 @@ class SharedWritePolicy:
99
90
  self.mode = self._resolve_mode(mode)
100
91
 
101
92
  @staticmethod
102
- def _resolve_mode(mode: str = None) -> str:
93
+ def _resolve_mode(mode: str | None = None) -> str:
103
94
  """显式入参 > 环境变量 > 默认 audit。非法值一律回落到 audit(保守:不拦截)。"""
104
95
  m = (mode or os.environ.get(MODE_ENV, "") or "audit").strip().lower()
105
96
  return m if m in VALID_MODES else "audit"
106
97
 
107
- # 纯标记类 tag:描述"这条记忆的归属/去向",不描述"内容是什么"。
108
- # 让它们参与内容域匹配只会稀释策略表 —— 尤其 "shared" 在本闸门语境下恒为真,零信息量。
109
98
  _MARKER_TAGS = ("agent:", "shared")
110
99
 
111
100
  @staticmethod
@@ -126,9 +115,9 @@ class SharedWritePolicy:
126
115
  out.append(t)
127
116
  return tuple(dict.fromkeys(out))
128
117
 
129
- def evaluate(self, agent_id: str = None, memory_type: str = "concept",
130
- tags: Iterable[str] = None) -> Decision:
131
- facets = self.facets_of(memory_type, tags)
118
+ def evaluate(self, agent_id: str | None = None, memory_type: str = "concept",
119
+ tags: Iterable[str] | None = None) -> Decision:
120
+ facets = self.facets_of(memory_type, tags or ())
132
121
  who = agent_id or "unknown"
133
122
 
134
123
  if any(fnmatch.fnmatchcase(f, p) for f in facets for p in self.global_facets):
@@ -142,9 +131,9 @@ class SharedWritePolicy:
142
131
  return Decision(False, f"{who} 的职责域不含 {list(facets)}", self.mode, facets)
143
132
 
144
133
 
145
- def guard_shared_write(backend, agent_id: str, content: str,
134
+ def guard_shared_write(backend, agent_id: str | None, content: str,
146
135
  memory_type: str = "concept",
147
- tags: Iterable[str] = None) -> Optional[Decision]:
136
+ tags: Iterable[str] | None = None) -> Optional[Decision]:
148
137
  """共享层写入的统一闸门 —— 必须在**真正落库之前**调用。
149
138
 
150
139
  行为分三档:
@@ -170,7 +159,6 @@ def guard_shared_write(backend, agent_id: str, content: str,
170
159
  "allowed": decision.allowed,
171
160
  "reason": decision.reason,
172
161
  "mode": decision.mode,
173
- # 只留摘要哈希 + 短预览,不把共享层正文原样灌进事件流
174
162
  "content_sha": hashlib.sha256(
175
163
  str(content or "").encode("utf-8")).hexdigest()[:16],
176
164
  "content_preview": str(content or "")[:60],
@@ -187,7 +175,7 @@ def guard_shared_write(backend, agent_id: str, content: str,
187
175
  return decision
188
176
 
189
177
 
190
- def attach(backend, policy: SharedWritePolicy = None, spine=None) -> SharedWritePolicy:
178
+ def attach(backend, policy: SharedWritePolicy | None = None, spine=None) -> SharedWritePolicy:
191
179
  """把策略挂到记忆后端(HMRAdapter / MockHMR 皆可)。返回生效的策略实例。
192
180
 
193
181
  用挂载而非构造函数注入,是因为装配顺序:memory_adapter 在 event_spine **之前**创建,
@@ -21,17 +21,18 @@ import threading
21
21
  import time
22
22
  import uuid
23
23
  from dataclasses import dataclass, field
24
+ from typing import Any
24
25
 
25
26
 
26
27
  @dataclass
27
28
  class SteeringItem:
28
29
  item_id: str
29
- requester: str # 谁在等这个结果(主任务/会话标识)
30
- source_agent: str # 哪个子 Agent 产出的
31
- payload: dict # 结果内容
30
+ requester: str
31
+ source_agent: str
32
+ payload: dict
32
33
  created: float = field(default_factory=time.time)
33
- leased_until: float = 0.0 # 0 = 未租出;>0 = 租约到期时间戳
34
- lease_id: str = "" # 当前租约持有者标识
34
+ leased_until: float = 0.0
35
+ lease_id: str = ""
35
36
 
36
37
  @property
37
38
  def is_leased(self) -> bool:
@@ -60,7 +61,7 @@ class SteeringQueue:
60
61
  source_agent=source_agent, payload=payload)
61
62
  return item_id
62
63
 
63
- def lease(self, requester: str = None, max_items: int = 10) -> list[SteeringItem]:
64
+ def lease(self, requester: str | None = None, max_items: int = 10) -> list[SteeringItem]:
64
65
  """租走一批待处理结果(可按 requester 过滤)。
65
66
 
66
67
  原子地:挑出未被租用(或租约已过期)的项,标记为 leased,返回。
@@ -69,7 +70,7 @@ class SteeringQueue:
69
70
  with self._lock:
70
71
  self._reclaim_expired()
71
72
  now = time.time()
72
- leased = []
73
+ leased: list[Any] = []
73
74
  lease_id = uuid.uuid4().hex[:12]
74
75
  for item in self._items.values():
75
76
  if len(leased) >= max_items:
@@ -83,7 +84,7 @@ class SteeringQueue:
83
84
  leased.append(item)
84
85
  return leased
85
86
 
86
- def ack(self, item_id: str, lease_id: str = None) -> bool:
87
+ def ack(self, item_id: str, lease_id: str | None = None) -> bool:
87
88
  """消费者确认处理完 → 移除该项。
88
89
 
89
90
  校验 lease_id:只有持有当前租约的消费者能 ack(防止过期租约误删
@@ -94,7 +95,7 @@ class SteeringQueue:
94
95
  if item is None:
95
96
  return False
96
97
  if lease_id is not None and item.lease_id != lease_id:
97
- return False # 租约不匹配(可能已过期被别人租走)
98
+ return False
98
99
  del self._items[item_id]
99
100
  return True
100
101
 
@@ -114,7 +115,7 @@ class SteeringQueue:
114
115
  item.leased_until = 0.0
115
116
  item.lease_id = ""
116
117
 
117
- def pending_count(self, requester: str = None) -> int:
118
+ def pending_count(self, requester: str | None = None) -> int:
118
119
  """待处理(未被有效租用)的项数。"""
119
120
  with self._lock:
120
121
  self._reclaim_expired()
@@ -145,7 +146,7 @@ class SteeringQueue:
145
146
  if item.requester != requester:
146
147
  continue
147
148
  if item.is_leased:
148
- continue # 正被租用 → 留给租约消费者,drain 不碰
149
+ continue
149
150
  out.append(item.payload)
150
151
  to_del.append(item_id)
151
152
  for item_id in to_del:
@@ -73,7 +73,7 @@ class SystemBus:
73
73
  def publish(self, channel: str, payload: dict):
74
74
  return self.spine.publish(channel, payload, source="system_bus")
75
75
 
76
- def history(self, channel: str = None, limit: int = 50,
76
+ def history(self, channel: str | None = None, limit: int = 50,
77
77
  exact: bool = False) -> list:
78
78
  """历史事件(dict 列表)。exact=True 时只返回 topic 完全匹配的事件
79
79
  (默认前缀匹配,与 EventSpine 原生一致)。"""
@@ -16,16 +16,16 @@ from pathlib import Path
16
16
 
17
17
 
18
18
  NodeType = Literal[
19
- "goal", # 目标基线节点(根)
20
- "reasoning", # 推理步骤
21
- "memory_retrieval", # 记忆检索
22
- "tool_intent", # 工具意图
23
- "tool_use", # 工具实际调用(augment.py 已在用;此前漏登记,类型面是欠账)
24
- "plan", # 显式计划(steps/flow 落在 metadata,供回显与审计)
25
- "decision", # 行动决策
26
- "policy_update", # Policy 候选更新
27
- "consolidation", # 记忆巩固写回
28
- "reflection", # 执行后反思
19
+ "goal",
20
+ "reasoning",
21
+ "memory_retrieval",
22
+ "tool_intent",
23
+ "tool_use",
24
+ "plan",
25
+ "decision",
26
+ "policy_update",
27
+ "consolidation",
28
+ "reflection",
29
29
  ]
30
30
 
31
31
  HarnessStatus = Literal["BASELINE", "PASS", "WARN", "BLOCK", "PENDING", "REGRESSION"]
@@ -39,15 +39,15 @@ class ThoughtNode:
39
39
  agent_id: str
40
40
  type: NodeType
41
41
  content: str
42
- conf: float # 置信度 0.0-1.0
43
- mem_refs: list[str] # 引用的 HMR 记忆 ID
44
- parent_ids: list[str] # 父节点 ID(因果依赖)
42
+ conf: float
43
+ mem_refs: list[str]
44
+ parent_ids: list[str]
45
45
  harness_status: HarnessStatus
46
- harness_reason: Optional[str] # BLOCK/WARN 时的原因
47
- harness_delta: Optional[float] # 目标漂移量
48
- dominant_dim: Optional[str] # 主导漂移维度
49
- tool_name: Optional[str] # 如果是 tool_intent 节点
50
- entities: list[str] # 涉及的实体集合
46
+ harness_reason: Optional[str]
47
+ harness_delta: Optional[float]
48
+ dominant_dim: Optional[str]
49
+ tool_name: Optional[str]
50
+ entities: list[str]
51
51
  timestamp: str
52
52
  metadata: dict
53
53
 
@@ -59,11 +59,11 @@ class ThoughtNode:
59
59
  type: NodeType,
60
60
  content: str,
61
61
  conf: float = 1.0,
62
- mem_refs: list[str] = None,
63
- parent_ids: list[str] = None,
64
- tool_name: str = None,
65
- entities: list[str] = None,
66
- metadata: dict = None,
62
+ mem_refs: list[str] | None = None,
63
+ parent_ids: list[str] | None = None,
64
+ tool_name: str | None = None,
65
+ entities: list[str] | None = None,
66
+ metadata: dict | None = None,
67
67
  ) -> "ThoughtNode":
68
68
  return cls(
69
69
  id=f"tn_{uuid.uuid4().hex[:12]}",
@@ -150,7 +150,7 @@ class ThoughtChain:
150
150
  try:
151
151
  conn.execute("PRAGMA journal_mode=WAL")
152
152
  except sqlite3.OperationalError:
153
- pass # 个别文件系统不支持 WAL(如网络盘)→ 退回默认日志模式,timeout 仍生效
153
+ pass
154
154
  try:
155
155
  yield conn
156
156
  conn.commit()
@@ -183,9 +183,9 @@ class ThoughtChain:
183
183
  self,
184
184
  node_id: str,
185
185
  status: HarnessStatus,
186
- reason: str = None,
187
- delta: float = None,
188
- dominant_dim: str = None,
186
+ reason: str | None = None,
187
+ delta: float | None = None,
188
+ dominant_dim: str | None = None,
189
189
  ):
190
190
  """Harness 校验完成后更新节点标注"""
191
191
  with self._conn() as conn:
@@ -202,7 +202,7 @@ class ThoughtChain:
202
202
  ).fetchone()
203
203
  return self._row_to_node(row) if row else None
204
204
 
205
- def get_chain(self, session_id: str, agent_id: str = None) -> list[ThoughtNode]:
205
+ def get_chain(self, session_id: str, agent_id: str | None = None) -> list[ThoughtNode]:
206
206
  """获取一个会话的完整推理链,按时间排序"""
207
207
  with self._conn() as conn:
208
208
  if agent_id:
@@ -217,7 +217,7 @@ class ThoughtChain:
217
217
  ).fetchall()
218
218
  return [self._row_to_node(r) for r in rows]
219
219
 
220
- def get_blocked_nodes(self, agent_id: str = None, limit: int = 50) -> list[ThoughtNode]:
220
+ def get_blocked_nodes(self, agent_id: str | None = None, limit: int = 50) -> list[ThoughtNode]:
221
221
  """获取被 Harness 截断的节点,用于审计"""
222
222
  with self._conn() as conn:
223
223
  if agent_id:
@@ -232,7 +232,7 @@ class ThoughtChain:
232
232
  ).fetchall()
233
233
  return [self._row_to_node(r) for r in rows]
234
234
 
235
- def search_nodes(self, keyword: str, agent_id: str = None,
235
+ def search_nodes(self, keyword: str, agent_id: str | None = None,
236
236
  limit: int = 20) -> list[ThoughtNode]:
237
237
  """按关键词在推理节点内容里检索(LIKE 子串匹配)。供 HSR 的 AgentSourcePort
238
238
  把「各 Agent 想过/判过什么」连通进内部搜索。只读。"""
@@ -307,8 +307,6 @@ class ThoughtChain:
307
307
  "timestamp", "metadata"
308
308
  ]
309
309
  d = dict(zip(cols, row))
310
- # JSON 字段容错:单行某字段损坏时降级为默认值,而非让整个审计链读取崩溃。
311
- # 审计系统的可靠性要求"一条坏数据不拖垮全部"。
312
310
  for field, default in (("mem_refs", []), ("parent_ids", []),
313
311
  ("entities", []), ("metadata", {})):
314
312
  try:
@@ -36,7 +36,6 @@ def estimate_tokens(text: str, model: str = "") -> int:
36
36
  enc = tiktoken.get_encoding("cl100k_base")
37
37
  return len(enc.encode(text))
38
38
  except Exception:
39
- # 字符估算:中文(非ASCII)按 1.1 字符/token,英文(ASCII)按 4 字符/token。
40
39
  ascii_chars = sum(1 for c in text if ord(c) < 128)
41
40
  cjk_chars = len(text) - ascii_chars
42
41
  return int(ascii_chars / 4 + cjk_chars * 1.1) + 1
@@ -50,8 +49,8 @@ class CallRecord:
50
49
  agent_id: str
51
50
  input_tokens: int
52
51
  output_tokens: int
53
- estimated: bool # True=估算,False=API真实usage
54
- task_kind: str = "" # 可选:single/multi/...
52
+ estimated: bool
53
+ task_kind: str = ""
55
54
 
56
55
  @property
57
56
  def total_tokens(self) -> int:
@@ -66,14 +65,14 @@ class CallRecord:
66
65
  class TokenMeter:
67
66
  """Token 计量器。线程安全,可聚合,可落盘。"""
68
67
 
69
- def __init__(self, log_path: str = None, max_records: int = 10000,
68
+ def __init__(self, log_path: str | None = None, max_records: int = 10000,
70
69
  max_log_mb: float = 10.0):
71
70
  self.log_path = log_path
72
71
  self.max_records = max_records
73
72
  self.max_log_bytes = int(max_log_mb * 1024 * 1024)
74
73
  self._records: list[CallRecord] = []
75
74
  self._lock = threading.RLock()
76
- self._append_count = 0 # 触发轮转检查的计数器
75
+ self._append_count = 0
77
76
  if log_path:
78
77
  os.makedirs(os.path.dirname(log_path) or ".", exist_ok=True)
79
78
 
@@ -94,7 +93,7 @@ class TokenMeter:
94
93
 
95
94
  def measure_call(self, *, system: str, messages: list, output: str,
96
95
  provider: str, model: str, agent_id: str = "",
97
- usage: dict = None, task_kind: str = "") -> CallRecord:
96
+ usage: dict | None = None, task_kind: str = "") -> CallRecord:
98
97
  """计量一次调用。优先用 API 返回的 usage,否则估算。
99
98
 
100
99
  usage 形如 {"input_tokens": N, "output_tokens": M}(已由 router 归一化)。
@@ -104,7 +103,6 @@ class TokenMeter:
104
103
  usage.get("input_tokens", 0),
105
104
  usage.get("output_tokens", 0),
106
105
  estimated=False, task_kind=task_kind)
107
- # 估算:输入 = system + 所有 message content,输出 = output
108
106
  input_text = (system or "") + "\n".join(
109
107
  str(m.get("content", "")) for m in (messages or []))
110
108
  in_tok = estimate_tokens(input_text, model)
@@ -112,7 +110,6 @@ class TokenMeter:
112
110
  return self.record(provider, model, agent_id, in_tok, out_tok,
113
111
  estimated=True, task_kind=task_kind)
114
112
 
115
- # ── 聚合查询 ──────────────────────────────────────────
116
113
  def summary(self) -> dict:
117
114
  """总体统计。"""
118
115
  with self._lock:
@@ -211,8 +208,7 @@ class TokenMeter:
211
208
  with open(self.log_path, "a", encoding="utf-8") as f:
212
209
  f.write(json.dumps(rec.to_dict(), ensure_ascii=False) + "\n")
213
210
  except Exception:
214
- pass # 计量落盘失败不影响主流程
215
- # 每 100 次追加检查一次文件大小,超限则轮转(防磁盘无限膨胀)
211
+ pass
216
212
  self._append_count += 1
217
213
  if self._append_count % 100 == 0:
218
214
  self._rotate_if_needed()
@@ -226,10 +222,10 @@ class TokenMeter:
226
222
  return
227
223
  with open(self.log_path, encoding="utf-8") as f:
228
224
  lines = f.readlines()
229
- keep = lines[len(lines) // 2:] # 保留较新的一半
225
+ keep = lines[len(lines) // 2:]
230
226
  tmp = self.log_path + ".tmp"
231
227
  with open(tmp, "w", encoding="utf-8") as f:
232
228
  f.writelines(keep)
233
- os.replace(tmp, self.log_path) # 原子替换
229
+ os.replace(tmp, self.log_path)
234
230
  except Exception:
235
- pass # 轮转失败不影响主流程
231
+ pass
@@ -15,6 +15,7 @@ Foxclaw — 工具注册表(ToolRegistry / P2 function-calling)
15
15
  """
16
16
  from __future__ import annotations
17
17
  import os
18
+ from typing import Any
18
19
 
19
20
 
20
21
  def _tool(name: str, description: str, properties: dict, required: list) -> dict:
@@ -33,7 +34,6 @@ def _tool(name: str, description: str, properties: dict, required: list) -> dict
33
34
  }
34
35
 
35
36
 
36
- # ── 首批工具声明(高价值、正好替掉现有关键词坑)──────────────────────────────
37
37
  WEB_SEARCH = _tool(
38
38
  "web_search",
39
39
  "联网搜索实时/时效性的**外部**信息:新闻、产品的版本与型号、发布动态、价格、汇率、"
@@ -113,15 +113,15 @@ class ToolRegistry:
113
113
  for k in required:
114
114
  if k not in args or args[k] in (None, ""):
115
115
  errs.append(f"缺少必填参数 {k}")
116
- _tmap = {"string": str, "integer": int, "number": (int, float),
116
+ _tmap: dict[str, Any] = {"string": str, "integer": int, "number": (int, float),
117
117
  "boolean": bool, "array": list, "object": dict}
118
118
  for k, v in args.items():
119
119
  spec = props.get(k)
120
120
  if not isinstance(spec, dict):
121
121
  continue
122
- want = _tmap.get(spec.get("type"))
122
+ stype = spec.get("type")
123
+ want = _tmap.get(stype) if isinstance(stype, str) else None
123
124
  if not want or not isinstance(v, want):
124
- # bool 是 int 的子类,须排除,否则 True 会被当成合法 integer
125
125
  if not (want is not bool and isinstance(v, bool)):
126
126
  errs.append(f"参数 {k} 类型应为 {spec.get('type')},"
127
127
  f"实为 {type(v).__name__}")
@@ -143,20 +143,12 @@ def default_registry() -> ToolRegistry:
143
143
  return ToolRegistry().register(WEB_SEARCH).register(RECALL_MEMORY)
144
144
 
145
145
 
146
- # ── 工具能力分级(供 permission.evaluate 四阶段管线判定)────────────────────────
147
- # 病根提醒:prompt 级隔离挡不住弱模型被块内强指令诱导(实测 flash ~2/3 被劫持),真正的
148
- # 安全阀在**动作级**——工具执行前过权限管线,写/副作用/不可逆动作走人在回路(HITL)。
149
- # 字段对齐 permission.evaluate:side_effect(read/write/send/pay)/ read_only / external / destructive。
150
- # parallel_safe 不在权限语义里,只用于**同轮多工具是否可并发**:置 True 表示"纯读、可与其他
151
- # parallel_safe 工具同时跑"(见 agents/mixins/tool_exec.py::_run_tools)。未声明 = 串行。
152
- # **新增任何工具都应在此声明能力;未声明者 fail-closed 当作有副作用 → 强制 HITL(见 _DEFAULT_CAP)。**
153
146
  TOOL_CAPABILITIES: dict[str, dict] = {
154
147
  "web_search": {"side_effect": "read", "read_only": True, "external": False,
155
148
  "destructive": False, "parallel_safe": True},
156
149
  "recall_memory": {"side_effect": "read", "read_only": True, "external": False,
157
150
  "destructive": False, "parallel_safe": True},
158
151
  }
159
- # 未声明工具的兜底:当有副作用 + 外部 → permission 管线判 ask → 强制人在回路。宁误拦不漏放。
160
152
  _DEFAULT_CAP = {"side_effect": "write", "read_only": False, "external": True, "destructive": False}
161
153
 
162
154