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
@@ -23,36 +23,30 @@ def check(cond, msg):
23
23
  def test_loop_detection():
24
24
  print("\n【1】工具循环检测")
25
25
  from foxclaw.harness.loop_detection import ToolLoopDetector
26
- # repeat
27
26
  d = ToolLoopDetector(warning=3, critical=5, circuit_break=8)
28
27
  for _ in range(8):
29
28
  v = d.record("web_search", {"q": "天气"})
30
29
  check(v.stuck and v.detector == "repeat", "重复调用检测为 repeat")
31
- # ping_pong
32
30
  d2 = ToolLoopDetector(warning=4, critical=6, circuit_break=10)
33
31
  for i in range(6):
34
32
  v2 = d2.record("search" if i % 2 == 0 else "fetch", {"x": i % 2})
35
33
  check(v2.stuck and v2.detector == "ping_pong", "交替调用检测为 ping_pong")
36
- # no_progress
37
34
  d3 = ToolLoopDetector(warning=3, critical=5, circuit_break=8)
38
35
  for _ in range(5):
39
36
  v3 = d3.record("poll", {"id": "j"}, result={"status": "pending"})
40
37
  check(v3.stuck and v3.detector == "no_progress", "轮询无变化检测为 no_progress")
41
- # 正常不误报
42
38
  d4 = ToolLoopDetector(warning=3)
43
39
  for q in ["a", "b", "c", "d", "e"]:
44
40
  v4 = d4.record("search", {"q": q})
45
41
  check(not v4.stuck, "多样调用不误报")
46
- # 熔断
47
42
  check(d.should_abort(), "达阈值 should_abort")
48
43
 
49
44
 
50
45
  def test_loop_per_session():
51
46
  print("\n【1b】循环检测 per-session 隔离(阶段四·步骤13,消除跨会话污染)")
52
47
  from foxclaw.skills import SkillAdapter, set_loop_session, reset_loop_session, _loop_session
53
- adp = SkillAdapter(None) # _detector_for 不依赖 registry,构造最小实例即可
48
+ adp = SkillAdapter(None)
54
49
 
55
- # 会话 A 连续同工具 8 次(达熔断)
56
50
  tok = set_loop_session("sess_A")
57
51
  try:
58
52
  for _ in range(8):
@@ -61,7 +55,6 @@ def test_loop_per_session():
61
55
  reset_loop_session(tok)
62
56
  check(va.stuck, "会话 A 连续同工具达熔断")
63
57
 
64
- # 切到会话 B:历史应从零,同工具仅 1 次不应被 A 污染成 stuck
65
58
  tok = set_loop_session("sess_B")
66
59
  try:
67
60
  vb = adp._detector_for(_loop_session.get()).record("web_search", {"q": "天气"})
@@ -70,7 +63,6 @@ def test_loop_per_session():
70
63
  check(not vb.stuck, "会话 B 不受会话 A 污染(独立历史)")
71
64
  check(not adp._detector_for("sess_B").should_abort(), "会话 B 未达熔断")
72
65
 
73
- # 不同会话持有独立检测器实例;无会话时用共享默认槽(向后兼容)
74
66
  check(adp._detector_for("sess_A") is not adp._detector_for("sess_B"), "不同会话独立检测器实例")
75
67
  check(adp._detector_for(None) is adp._detector_for(None), "无会话共用默认槽(稳定)")
76
68
  check(_loop_session.get() is None, "退出后 contextvar 已复位(无残留)")
@@ -79,12 +71,9 @@ def test_loop_per_session():
79
71
  def test_compaction():
80
72
  print("\n【2】上下文压缩")
81
73
  from foxclaw.core.compaction import Compactor, extract_identifiers, estimate_messages_tokens
82
- # token 估算(压缩触发的判据)
83
74
  check(estimate_messages_tokens([{"role": "user", "content": "你好世界"}]) > 0, "token 估算非零")
84
- # 标识符提取(含 ¥金额)
85
75
  ids = extract_identifiers([{"role": "user", "content": "订单 txn_abc12345 金额 ¥1,200 流水 202606210001"}])
86
76
  check("txn_abc12345" in ids and any("¥" in i for i in ids), "关键标识符提取(含金额)")
87
- # 截断兜底(无 router)
88
77
  msgs = [{"role": "system", "content": "你是助手"}]
89
78
  for i in range(10):
90
79
  msgs.append({"role": "user", "content": f"长消息{i}" * 20})
@@ -93,7 +82,6 @@ def test_compaction():
93
82
  r = c.compact(msgs)
94
83
  check(r.method == "truncate" and r.compacted_tokens < r.original_tokens, "无 router 截断兜底有效")
95
84
  check(r.messages[-1] == msgs[-1], "最近消息保留")
96
- # 巨大系统消息 → 放弃无效压缩
97
85
  huge = [{"role": "system", "content": "x" * 8000}]
98
86
  for i in range(6):
99
87
  huge.append({"role": "user", "content": f"m{i}"})
@@ -108,20 +96,17 @@ def test_hooks():
108
96
  from foxclaw.skills.hooks_spec import Hooks, normalize_hook, is_valid_hook
109
97
  check(normalize_hook("pre_skill") == Hooks.BEFORE_TOOL_CALL, "旧名规整为标准名")
110
98
  check(is_valid_hook("on_loop"), "on_loop 是合法钩子")
111
- # 向后兼容互通
112
99
  hm = HookManager()
113
100
  fired = []
114
101
  hm.register("pre_skill", lambda ctx: fired.append("x") or {})
115
102
  hm.fire("before_tool_call", "calc", {})
116
103
  check("x" in fired, "旧名注册 + 标准名触发 互通")
117
- # 阻断权
118
104
  hm2 = HookManager()
119
105
  hm2.register("before_tool_call", lambda ctx: {"block": True, "reason": "test"})
120
106
  check(hm2.fire("before_tool_call", "calc", {}).blocked, "before_tool_call 可阻断")
121
107
  hm3 = HookManager()
122
108
  hm3.register("after_tool_call", lambda ctx: {"block": True})
123
109
  check(not hm3.fire("after_tool_call", "calc", {}).blocked, "after_tool_call 无阻断权")
124
- # 非法名警告
125
110
  hm4 = HookManager()
126
111
  hm4.register("typo_hook", lambda ctx: {})
127
112
  check(any("未知钩子" in a.get("error", "") for a in hm4.audit_log), "拼错钩子名有警告")
@@ -135,7 +120,6 @@ def test_idempotency():
135
120
  check(k1 == k2, "相同输入得相同 key")
136
121
  g = IdempotencyGuard(ttl_seconds=3600)
137
122
  check(g.check_and_mark(k1) and not g.check_and_mark(k1), "原子查重:首次True重复False")
138
- # 并发只1个成功
139
123
  g2 = IdempotencyGuard(ttl_seconds=3600)
140
124
  results, lock = [], threading.Lock()
141
125
  def worker():
@@ -145,11 +129,9 @@ def test_idempotency():
145
129
  for t in ts: t.start()
146
130
  for t in ts: t.join()
147
131
  check(sum(results) == 1, "50 线程抢同 key 恰好 1 个成功")
148
- # TTL
149
132
  g3 = IdempotencyGuard(ttl_seconds=0.3)
150
133
  g3.check_and_mark("tmp"); time.sleep(0.4)
151
134
  check(g3.check_and_mark("tmp"), "TTL 过期后可重新执行")
152
- # 跨重启
153
135
  p = tempfile.mktemp(suffix=".json")
154
136
  ga = IdempotencyGuard(persist_path=p); ga.check_and_mark("pk")
155
137
  gb = IdempotencyGuard(persist_path=p)
@@ -161,22 +143,16 @@ def test_recovery():
161
143
  print("\n【5】重启恢复")
162
144
  from foxclaw.core.recovery import RestartRecovery, _pid_alive
163
145
  log = tempfile.mktemp(suffix=".jsonl")
164
- # boot_id 防 pid 复用
165
146
  rA = RestartRecovery(log_path=log, boot_id="bootA")
166
147
  rA.task_started("t1", "中断任务")
167
- # 即使 pid 是存活的(复用),boot_id 不同也应识别中断
168
148
  rB = RestartRecovery(log_path=log, boot_id="bootB", current_pid=os.getpid())
169
149
  interrupted = rB.scan_interrupted()
170
150
  check(len(interrupted) == 1 and interrupted[0].task_id == "t1", "boot_id 识别中断(防 pid 复用)")
171
- # 本次启动自己的任务不误判
172
151
  rB.task_started("t2", "本次任务")
173
152
  check(not any(t.task_id == "t2" for t in rB.scan_interrupted()), "不误判本次启动的任务")
174
- # 完成的不算中断
175
153
  rB.task_finished("t2")
176
154
  check(not any(t.task_id == "t2" for t in rB.scan_interrupted()), "已完成任务不算中断")
177
155
  check(_pid_alive(os.getpid()) and not _pid_alive(999999), "pid 存活检测准确")
178
- # 危险路径回归:探测【外部活进程】必须 True 且【绝不杀死】它
179
- # (旧实现在 Windows 上 os.kill(pid,0) 会 TerminateProcess 误杀,本用例专抓此坑)
180
156
  import subprocess
181
157
  proc = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(10)"])
182
158
  try:
@@ -192,13 +168,11 @@ def test_recovery():
192
168
  def test_steering_queue():
193
169
  print("\n【6】干预队列")
194
170
  from foxclaw.core.steering_queue import SteeringQueue
195
- # 基本流
196
171
  q = SteeringQueue(lease_seconds=30)
197
172
  q.publish("m1", "finance", {"r": "A"})
198
173
  leased = q.lease("m1")
199
174
  check(len(leased) == 1, "publish→lease")
200
175
  check(q.ack(leased[0].item_id, leased[0].lease_id), "ack 成功移除")
201
- # 并发不重复租用
202
176
  q2 = SteeringQueue(lease_seconds=30)
203
177
  for i in range(20):
204
178
  q2.publish("m2", "a", {"i": i})
@@ -210,11 +184,9 @@ def test_steering_queue():
210
184
  for t in ts: t.start()
211
185
  for t in ts: t.join()
212
186
  check(len(got) == len(set(got)) == 20, "并发租取不重复")
213
- # 租约过期重入队
214
187
  q3 = SteeringQueue(lease_seconds=0.3)
215
188
  q3.publish("m3", "a", {}); q3.lease("m3"); time.sleep(0.4)
216
189
  check(q3.pending_count() == 1, "租约过期自动重入队(不丢)")
217
- # drain 尊重租约
218
190
  q4 = SteeringQueue(lease_seconds=30)
219
191
  q4.publish("m4", "a", {"r": 1}); q4.publish("m4", "a", {"r": 2})
220
192
  q4.lease("m4", max_items=1)
@@ -224,27 +196,21 @@ def test_steering_queue():
224
196
  def test_token_meter():
225
197
  print("\n【7】Token 计量器")
226
198
  from foxclaw.core.token_meter import TokenMeter, estimate_tokens
227
- # 估算非零
228
199
  check(estimate_tokens("你好世界hello") > 0, "token 估算非零")
229
200
  check(estimate_tokens("") == 0, "空文本 0 token")
230
201
  m = TokenMeter()
231
- # 真实 usage 优先(estimated=False)
232
202
  r1 = m.measure_call(system="s", messages=[{"role": "user", "content": "q"}],
233
203
  output="a", provider="deepseek", model="deepseek-chat",
234
204
  agent_id="finance", usage={"input_tokens": 100, "output_tokens": 200})
235
205
  check(not r1.estimated and r1.total_tokens == 300, "真实 usage 优先,total 正确")
236
- # 无 usage 走估算(estimated=True)
237
206
  r2 = m.measure_call(system="你是主管", messages=[{"role": "user", "content": "分析理财预算"}],
238
207
  output="已完成分析", provider="deepseek", model="deepseek-chat",
239
208
  agent_id="supervisor", usage=None)
240
209
  check(r2.estimated and r2.total_tokens > 0, "无 usage 走估算")
241
- # 按 agent 聚合(agent_id 正确)
242
210
  agg = m.by_agent()
243
211
  check("finance" in agg and "supervisor" in agg, "按 Agent 聚合(非 unknown)")
244
212
  check(agg["finance"]["total_tokens"] == 300, "Agent 聚合数值正确")
245
- # 报告不崩
246
213
  check(len(m.report()) > 0, "文本报告生成")
247
- # 成本估算
248
214
  cost = m.estimate_cost(price_per_1k_input=0.001, price_per_1k_output=0.002)
249
215
  check(cost["estimated_cost"] >= 0, "成本估算")
250
216
 
@@ -253,11 +219,9 @@ def test_thoughtchain_concurrency():
253
219
  print("\n【8】ThoughtChain 并发写(共享审计存储)")
254
220
  from foxclaw.core.thought_chain import ThoughtChain, ThoughtNode
255
221
  tc = ThoughtChain(db_path=tempfile.mktemp(suffix=".db"))
256
- # WAL 已启用?(并发加固的核心)
257
222
  with tc._conn() as c:
258
223
  mode = c.execute("PRAGMA journal_mode").fetchone()[0]
259
224
  check(mode.lower() == "wal", f"WAL 日志模式已启用(实为 {mode})")
260
- # N 线程 × M 节点并发写:默认 busy_timeout=0 时这会 database is locked
261
225
  N, M, errors = 8, 25, []
262
226
  def writer(aid):
263
227
  try:
@@ -288,7 +252,7 @@ def test_harness_action_wiring():
288
252
  policy_path=tempfile.mktemp(suffix=".yaml"), shadow_rounds=0)
289
253
  h.register_goal("分析财务预算", allowed_tool_categories={"read", "finance"},
290
254
  baseline_entities=baseline_entities)
291
- h._shadow_mode = False # 关观测期,直接进入实判
255
+ h._shadow_mode = False
292
256
  h._block_threshold, h._warn_threshold = 0.25, 0.18
293
257
  return h
294
258
 
@@ -299,11 +263,9 @@ def test_harness_action_wiring():
299
263
  tc.add_node(n)
300
264
  return n
301
265
 
302
- # —— penalize_tool:get_balance 本在 finance 允许集,基线判 0.0 ——
303
266
  h1 = fresh({"预算", "收支"})
304
267
  check(h1.check(node(tool="get_balance")).scores["tool"] == 0.0,
305
268
  "基线:允许的工具 get_balance 工具分 = 0.0")
306
- # 注入一条 penalize_tool 学习规则(模拟之前 BLOCK 学到的)→ 应立即改变判定
307
269
  h1._policy.setdefault("rules", []).append({
308
270
  "id": "r1", "source": "harness_learned",
309
271
  "action": {"type": "penalize_tool", "tool_pattern": "get_balance"}})
@@ -312,7 +274,6 @@ def test_harness_action_wiring():
312
274
  check(r1.scores["tool"] == 1.0 and r1.is_blocked,
313
275
  "penalize_tool 生效:同一工具被抬到 1.0 并 BLOCK")
314
276
 
315
- # —— restrict_scope:大基线下加 2 个新实体,基线分低(0.2),规则应抬到 1.0 ——
316
277
  big = {f"e{i}" for i in range(10)}
317
278
  h2 = fresh(big)
318
279
  base_scope = h2.check(node(entities=["e0", "x1", "x2"])).scores["scope"]
@@ -324,16 +285,14 @@ def test_harness_action_wiring():
324
285
  r2 = h2.check(node(entities=["e0", "x1", "x2"]))
325
286
  check(r2.scores["scope"] == 1.0, "restrict_scope 生效:超上限 scope 抬到 1.0")
326
287
 
327
- # —— 端到端:一次真实 BLOCK → 【P0-1 新行为】规则进**影子**(不生效),不再立即重建索引 ——
328
288
  h3 = fresh({"预算"})
329
- h3.check(node(tool="delete_everything")) # delete 类越权 → BLOCK → 影子候选
289
+ h3.check(node(tool="delete_everything"))
330
290
  check(not h3._is_penalized_tool("delete_everything"),
331
291
  "P0-1:BLOCK 后规则进影子,**不立即生效**(未重建索引)")
332
292
  shadow = h3._policy.get("shadow_rules", [])
333
293
  check(len(shadow) == 1 and shadow[0]["action"]["type"] == "penalize_tool"
334
294
  and shadow[0]["shadow"]["status"] == "observing",
335
295
  "P0-1:影子规则已入库且处于 observing")
336
- # 手工 promote 后应立即生效(升迁路径与自动升迁同构)
337
296
  h3._promote_rule(shadow[0])
338
297
  check(h3._is_penalized_tool("delete_everything"),
339
298
  "P0-1:promote 后规则生效(索引已重建)")
@@ -350,7 +309,6 @@ def test_harness_thread_safety():
350
309
 
351
310
  def worker(k):
352
311
  try:
353
- # 每线程在自己的 context 里注册基线(contextvar 线程独立),再并发 check
354
312
  h.register_goal(f"目标{k}", allowed_tool_categories={"read"},
355
313
  baseline_entities={"e"})
356
314
  for i in range(M):
@@ -366,7 +324,6 @@ def test_harness_thread_safety():
366
324
  for t in ts: t.join()
367
325
 
368
326
  check(not errors, f"{N} 线程并发 check 无异常(否则: {errors[:2]})")
369
- # 锁保护下 _run_count 无丢增量(无锁时 += 竞态会丢)
370
327
  check(h._run_count == N * M, f"_run_count 无丢增量({h._run_count}=={N*M})")
371
328
  check(len(h._delta_history) == N * M, f"_delta_history 无丢({len(h._delta_history)}=={N*M})")
372
329
 
@@ -380,11 +337,10 @@ def test_harness_delta_history_bounded():
380
337
  h = Harness(agent_id="t2", thought_chain=tc,
381
338
  policy_path=tempfile.mktemp(suffix=".yaml"), shadow_rounds=rounds)
382
339
  h.register_goal("目标", allowed_tool_categories={"read"}, baseline_entities={"e"})
383
- for i in range(rounds * 20): # 远超 shadow_rounds
340
+ for i in range(rounds * 20):
384
341
  node = ThoughtNode.create(session_id="s", agent_id="t2",
385
342
  type="reasoning", content=f"n{i}", conf=0.9)
386
343
  tc.add_node(node); h.check(node)
387
- # 仅 shadow 期(前 rounds 次)append,校准后不再 append → 上限=rounds,不随 run_count 无界增长
388
344
  check(len(h._delta_history) <= rounds,
389
345
  f"_delta_history 有界(≤{rounds}),实得 {len(h._delta_history)}")
390
346
  check(h._run_count == rounds * 20, "run_count 仍完整计数(每次 check 都记)")
@@ -400,7 +356,7 @@ def test_orchestration_request_log_bounded():
400
356
  try:
401
357
  os_ = Foxclaw(data_dir=d, shadow_rounds=50)
402
358
  os_.finance_runtime.set_balance("default", 100000)
403
- for _ in range(1200): # 远超 maxlen=1000
359
+ for _ in range(1200):
404
360
  os_.orchestrator.route_request(CrossAgentRequest(
405
361
  from_agent="admin", to_agent="finance", request_type="budget_request",
406
362
  payload={"amount": 10, "category": "travel", "account": "default"},
@@ -419,8 +375,6 @@ def test_hmr_adapter_stats_lock():
419
375
  hmr = create_memory_backend(storage_path=tempfile.mkdtemp(prefix="hmrlock_"))
420
376
  if not hasattr(hmr, "_lock"):
421
377
  print(" ~ MockHMR(无 vendor/hmr)无 _lock,跳过"); return
422
- # 白盒:主线程持锁 → 另一线程的 stats() 必须等锁释放才完成(证明 stats 已进锁)。
423
- # 补锁前 stats 不持锁 → 会在持锁期间直接完成,order 顺序相反。
424
378
  order = []
425
379
  hmr._lock.acquire()
426
380
  def call_stats():
@@ -430,7 +384,6 @@ def test_hmr_adapter_stats_lock():
430
384
  t.join(timeout=5)
431
385
  check(order == ["released", "stats_done"],
432
386
  f"stats() 应等 _lock 释放才完成(证明补锁生效),实得 {order}")
433
- # get_system_status 同样补了锁
434
387
  hmr._lock.acquire()
435
388
  order2 = []
436
389
  def call_status():
@@ -16,7 +16,6 @@ from pathlib import Path
16
16
 
17
17
  sys.path.insert(0, str(Path(__file__).parent.parent))
18
18
 
19
- # 隔离项目 .env(本测试不依赖任何真实配置;与批次 2 的隔离策略一致)
20
19
  os.environ.setdefault("FOXCLAW_SKIP_DOTENV", "1")
21
20
  os.environ.setdefault("HKC_EMBEDDING", "stub")
22
21
 
@@ -46,8 +45,6 @@ def _gov_events(spine):
46
45
  return [e for e in spine.history("governance.", 500)]
47
46
 
48
47
 
49
- # ── 1. 审计器单元层 ─────────────────────────────────────────
50
-
51
48
  def test_auditor_unit(test_dir):
52
49
  print("\n【1】审计器单元:decision_id / 事件 / 落盘 / fail-safe")
53
50
  d1, d2 = new_decision_id(), new_decision_id()
@@ -60,25 +57,22 @@ def test_auditor_unit(test_dir):
60
57
  detail={"stage": 2}, correlation_id="corr_test_1")
61
58
  evs = _gov_events(spine)
62
59
  check(len(evs) == 1, f"脊柱有 1 条 governance.decision(实际 {len(evs)})")
63
- p = evs[0]["payload"] # history() 返回 Event.to_dict()
60
+ p = evs[0]["payload"]
64
61
  check(evs[0]["topic"] == TOPIC, "事件主题 governance.decision")
65
62
  check(p["decision_id"] == did and p["layer"] == "permission"
66
63
  and p["verdict"] == "deny" and p["reason"] == "命中 deny 规则",
67
64
  "事件 payload 字段完整")
68
65
  check(evs[0]["correlation_id"] == "corr_test_1", "correlation_id 透传到事件")
69
66
 
70
- # 显式 decision_id 被尊重(P3-4:PolicyGate 生成后交给审计器,不能再换一个)
71
67
  did2 = aud.record(layer="skill", actor="", action="invoke:x", verdict="success",
72
68
  decision_id="dec_explicit01")
73
69
  check(did2 == "dec_explicit01", "显式 decision_id 原样返回")
74
70
 
75
- # 落盘与事件一致
76
71
  recs = aud.read()
77
72
  check(len(recs) == 2, f"落盘 2 条(实际 {len(recs)})")
78
73
  check(recs[0]["decision_id"] == "dec_explicit01", "read() 新→旧排序")
79
74
  check(recs[1]["correlation_id"] == "corr_test_1", "落盘记录含 correlation_id")
80
75
 
81
- # fail-safe:spine 爆炸不影响 record
82
76
  class BoomSpine:
83
77
  def publish(self, *a, **k):
84
78
  raise RuntimeError("spine boom")
@@ -87,7 +81,6 @@ def test_auditor_unit(test_dir):
87
81
  verdict="warn", reason="语义漂移")
88
82
  check(did3.startswith("dec_"), "spine 异常时 record 仍返回 decision_id(fail-safe)")
89
83
 
90
- # 开关
91
84
  os.environ[ENV_SWITCH] = "0"
92
85
  try:
93
86
  check(attach(spine, test_dir) is None, f"{ENV_SWITCH}=0 时 attach 返回 None")
@@ -96,8 +89,6 @@ def test_auditor_unit(test_dir):
96
89
  check(attach(spine, test_dir) is not None, "默认(未设开关)attach 正常返回")
97
90
 
98
91
 
99
- # ── 2. PolicyGate(P1-2/P3-4)─────────────────────────────────
100
-
101
92
  def _make_gate(test_dir, spine=None):
102
93
  from foxclaw.kernel.policy_gate import PolicyGate
103
94
  from foxclaw.skills.permission import evaluate
@@ -117,12 +108,10 @@ def test_policy_gate(test_dir):
117
108
  print("\n【2】PolicyGate:decision_id(P3-4)+ 非 allow 审计(P1-2)")
118
109
  gate, aud, spine = _make_gate(test_dir)
119
110
 
120
- # allow:有 decision_id,但**不**记审计(高频常态不淹没审计流)
121
111
  v_allow = gate.check_action(_make_skill(), {})
122
112
  check(v_allow.decision_id.startswith("dec_"), "allow 裁决也带 decision_id(供执行审计 join)")
123
113
  check(len(_gov_events(spine)) == 0, "allow 不记 governance 事件")
124
114
 
125
- # deny:记审计,layer=permission,decision_id 一致
126
115
  from foxclaw.skills.permission import PermissionRules
127
116
  rules = PermissionRules(deny=["demo.*"])
128
117
  v_deny = gate.check_action(_make_skill(), {}, rules=rules)
@@ -135,13 +124,11 @@ def test_policy_gate(test_dir):
135
124
  check(p["decision_id"] == v_deny.decision_id, "审计 decision_id 与 Verdict 一致(P3-4)")
136
125
  check(p["detail"]["stage"] == 2, "detail 含四阶段信息")
137
126
 
138
- # ask → confirm
139
127
  v_ask = gate.check_action(_make_skill("x.pay", side_effect="pay"), {})
140
128
  check(v_ask.level == "confirm", "ask 归一为 confirm(行为不变)")
141
129
  evs = _gov_events(spine)
142
130
  check(len(evs) == 2 and evs[1]["payload"]["verdict"] == "confirm", "confirm 也记审计")
143
131
 
144
- # 推理准入:BLOCK 记,PASS 不记
145
132
  from foxclaw.harness.harness import HarnessResult
146
133
  class FakeHarness:
147
134
  def __init__(self, status): self._s = status
@@ -159,14 +146,11 @@ def test_policy_gate(test_dir):
159
146
  gate.check_reasoning(FakeHarness("PASS"), FakeNode())
160
147
  check(len(_gov_events(spine)) == 3, "PASS 不记审计")
161
148
 
162
- # 向后兼容:旧式位置参数构造(无 decision_id)仍可用
163
149
  from foxclaw.kernel.policy_gate import Verdict
164
150
  v_old = Verdict(True, "allow", "无动作评估器(降级放行)", "permission", None)
165
151
  check(v_old.decision_id == "" and v_old.allowed, "Verdict 位置参数构造向后兼容")
166
152
 
167
153
 
168
- # ── 3. Orchestrator(P1-3)───────────────────────────────────
169
-
170
154
  class _FakeCaps:
171
155
  def __init__(self, agents): self._agents = agents
172
156
  def get(self, name): return self._agents if name == "agents" else None
@@ -185,7 +169,6 @@ def test_orchestrator(test_dir):
185
169
  from foxclaw.orchestration import Orchestrator, CrossAgentRequest
186
170
  aud, spine = _fresh_auditor(test_dir)
187
171
 
188
- # 三种终态 + 目标不存在(P1-0 路径)
189
172
  agents = {"finance": _FakeAgent([("approved", "预算充足"),
190
173
  ("rejected", "超出月度预算")])}
191
174
  orch = Orchestrator(_FakeCaps(agents), auditor=aud)
@@ -211,33 +194,29 @@ def test_orchestrator(test_dir):
211
194
  and evs[2]["payload"]["correlation_id"] == r3.id,
212
195
  "correlation_id == req.id(协商链 join 键)")
213
196
 
214
- # 协商链:拒绝 → 让步 → 批准,两轮裁决同一 correlation_id
215
197
  agents2 = {"finance": _FakeAgent([("rejected", "太贵"),
216
198
  ("approved", "这个价可以")])}
217
199
  requester = _FakeAgent([])
218
- requester.concede = lambda req, verdict: {"amount": 50} # 让步:降价
200
+ requester.concede = lambda req, verdict: {"amount": 50}
219
201
  orch2 = Orchestrator(_FakeCaps({"finance": agents2["finance"],
220
202
  "admin": requester}), auditor=aud)
221
203
  req = CrossAgentRequest(from_agent="admin", to_agent="finance",
222
204
  request_type="budget_request", payload={"amount": 500})
223
205
  final = orch2.negotiate(req)
224
206
  check(final.status == "approved", "协商后获批(行为不变)")
225
- evs2 = _gov_events(spine)[3:] # 前面 3 条之后
207
+ evs2 = _gov_events(spine)[3:]
226
208
  check(len(evs2) == 2, f"协商两轮裁决各记一条(实际 {len(evs2)})")
227
209
  check(all(e["payload"]["correlation_id"] == req.id for e in evs2),
228
210
  "多轮裁决共用 req.id——协商链可 join(P3-4 跨层关联)")
229
211
  check(evs2[0]["payload"]["verdict"] == "deny" and evs2[1]["payload"]["verdict"] == "allow",
230
212
  "协商链 deny→allow 轨迹完整")
231
213
 
232
- # 无 auditor:standalone 不崩、不记
233
214
  orch3 = Orchestrator(_FakeCaps({}), auditor=None)
234
215
  orch3.route_request(CrossAgentRequest(from_agent="a", to_agent="b",
235
216
  request_type="info_request", payload={}))
236
217
  check(len(_gov_events(spine)) == 5, "auditor=None 时不记审计(standalone 兼容)")
237
218
 
238
219
 
239
- # ── 4. SkillAdapter(P2-0/P3-4 核心:裁决→执行 join)──────────
240
-
241
220
  def test_skill_adapter(test_dir):
242
221
  print("\n【4】SkillAdapter:执行终态审计 + 裁决↔执行 decision_id join(P2-0/P3-4)")
243
222
  from foxclaw.skills import SkillRegistry, SkillAdapter, SkillSpec
@@ -246,7 +225,6 @@ def test_skill_adapter(test_dir):
246
225
  reg = SkillRegistry()
247
226
  adapter = SkillAdapter(reg, policy_gate=gate)
248
227
 
249
- # ① 只读 skill 成功执行 → skill success 审计,correlation_id == 权限裁决 decision_id
250
228
  ok_skill = SkillSpec(id="demo.read", name="读", description="只读",
251
229
  side_effect="read", handler=lambda **k: {"value": 42})
252
230
  res = adapter.invoke(ok_skill, {})
@@ -258,19 +236,15 @@ def test_skill_adapter(test_dir):
258
236
  corr = p["correlation_id"]
259
237
  check(corr.startswith("dec_"), f"执行审计带裁决 decision_id 作 correlation_id: {corr}")
260
238
 
261
- # ② 该 correlation_id 能 join 到真实裁决:再触发一次**非 allow** 裁决,
262
- # 验证 permission 记录的 decision_id 与后续执行审计的 correlation_id 同链
263
239
  pay_skill = SkillSpec(id="demo.pay", name="付", description="支付",
264
240
  side_effect="pay", handler=lambda **k: {"done": True})
265
- res2 = adapter.invoke(pay_skill, {}, confirmed=True) # 已确认 → allow → 执行
241
+ res2 = adapter.invoke(pay_skill, {}, confirmed=True)
266
242
  check(res2.success, "已确认的支付 skill 执行成功")
267
- # 手工复演同参裁决,验证 decision_id 链:invoke 内部那次裁决的 id == 执行审计 correlation_id
268
243
  exec_recs = [e["payload"] for e in _gov_events(spine) if e["payload"]["layer"] == "skill"]
269
244
  check(len(exec_recs) == 2, "两次成功执行各记一条")
270
245
  check(all(r["correlation_id"].startswith("dec_") for r in exec_recs),
271
246
  "每条执行审计都挂在某个裁决 decision_id 上")
272
247
 
273
- # ③ deny:permission 记裁决,skill 层**不重复**记执行
274
248
  adapter_deny = SkillAdapter(reg, policy_gate=gate,
275
249
  rules=PermissionRules(deny=["evil.*"]))
276
250
  evil = SkillSpec(id="evil.wipe", name="清", description="危险",
@@ -283,7 +257,6 @@ def test_skill_adapter(test_dir):
283
257
  check(len(perm_recs) == 1 and perm_recs[0]["verdict"] == "deny", "deny 裁决被记录")
284
258
  check(len(skill_recs_after) == 2, "deny 后 skill 层不重复记录(仍只有前两条执行审计)")
285
259
 
286
- # ④ 断路器出口:权限过了但断路器开 → skill deny 审计,correlation_id 挂裁决
287
260
  from foxclaw.skills.lifecycle import CircuitBreaker
288
261
  br = CircuitBreaker()
289
262
  for _ in range(10):
@@ -299,14 +272,8 @@ def test_skill_adapter(test_dir):
299
272
  check(last["correlation_id"].startswith("dec_"), "断路器审计也挂在裁决 decision_id 上")
300
273
 
301
274
 
302
- # ── 5. WS 可见性 + 真实装配端到端 ───────────────────────────
303
-
304
275
  def test_ws_visibility_and_wiring(test_dir):
305
276
  print("\n【5】WS 可见性 + 真实装配端到端")
306
- # governance.decision 不能被 WS 静音逻辑吞掉(验收:governance.* 事件 WS 可见)。
307
- # _WS_MUTED 是 api 投影闭包的局部变量,无法 import —— 改为源码级断言:静音规则只有
308
- # 「agent.cognition.* 前缀」与「_WS_MUTED 元组」两条,二者都不得命中 governance.*。
309
- # 这同时是一道防退化闸:将来若有人把 governance.* 加进静音列表,本测试会红。
310
277
  import inspect, re as _re
311
278
  import foxclaw.api as _api_mod
312
279
  src = inspect.getsource(_api_mod)
@@ -316,7 +283,6 @@ def test_ws_visibility_and_wiring(test_dir):
316
283
  check(not "governance.decision".startswith("agent.cognition."),
317
284
  "governance.decision 不命中 agent.cognition.* 静音前缀")
318
285
 
319
- # 真实装配:三处同一 auditor;一次真共享写入之外的治理动作落盘
320
286
  from foxclaw import Foxclaw
321
287
  d = tempfile.mkdtemp(prefix="foxclaw_gov_e2e_", dir=test_dir)
322
288
  o = Foxclaw(data_dir=d, shadow_rounds=2)
@@ -324,7 +290,6 @@ def test_ws_visibility_and_wiring(test_dir):
324
290
  check(o.policy_gate.auditor is o.governance_auditor, "PolicyGate 持同一 auditor")
325
291
  check(o.orchestrator._auditor is o.governance_auditor, "Orchestrator 持同一 auditor")
326
292
 
327
- # 端到端裁决:目标不存在的跨 Agent 请求 → 审计事件 + 落盘
328
293
  from foxclaw.orchestration import CrossAgentRequest
329
294
  o.orchestrator.route_request(CrossAgentRequest(
330
295
  from_agent="admin", to_agent="ghost", request_type="info_request", payload={}))
@@ -35,13 +35,10 @@ def _sess():
35
35
  def test_standalone_block():
36
36
  print("\n【1】独立可用 + 工具越权拦截")
37
37
  gov = _sess()
38
- # 正常只读:不抛(可能 PASS/WARN,但绝不 BLOCK)
39
38
  r_ok = gov.observe("call get_balance to read balance", tool="get_balance")
40
39
  check(not r_ok.blocked, "只读工具 get_balance 不被拦")
41
- # 越权:delete 不在 read 允许集 → 硬拦
42
40
  r_bad = gov.observe("call delete_account", tool="delete_account")
43
41
  check(r_bad.blocked and r_bad.dimension == "tool", "delete_account 被 BLOCK(维度=tool)")
44
- # guard 风格:越权抛异常
45
42
  raised = False
46
43
  try:
47
44
  gov.guard("call drop_table", tool="drop_table")
@@ -56,7 +53,7 @@ def test_decorator_sync_async():
56
53
 
57
54
  @governed(gov)
58
55
  def delete_row(x):
59
- return f"deleted {x}" # 不该被执行
56
+ return f"deleted {x}"
60
57
 
61
58
  executed = {"sync": False}
62
59
  try:
@@ -78,7 +75,6 @@ def test_decorator_sync_async():
78
75
  return False
79
76
  check(not asyncio.run(run()), "异步:越权协程被拦,未执行")
80
77
 
81
- # 正常只读函数正常执行
82
78
  @governed(gov, tool_name="get_balance")
83
79
  def read_balance():
84
80
  return 100
@@ -96,7 +92,6 @@ def test_audit_and_loop():
96
92
  for n in audit), "越权步骤在审计链里标为 BLOCK")
97
93
  check(gov.summary()["blocked_this_session"] == 1, "摘要记录本会话拦截数")
98
94
 
99
- # 循环检测:反复同一工具 → loop 告警
100
95
  gov2 = _sess()
101
96
  loop_hit = None
102
97
  for _ in range(9):
@@ -108,7 +103,6 @@ def test_audit_and_loop():
108
103
 
109
104
  def test_import_decoupling():
110
105
  print("\n【4】物理解耦:导入治理中间件不连带拉入 Agent 框架")
111
- # 必须在**全新子进程**里验(本进程里其它测试早已 import 了 agents,sys.modules 被污染)
112
106
  code = (
113
107
  "import sys, foxclaw.governance;"
114
108
  "bad=[m for m in ('foxclaw.agents.agents','foxclaw.skills',"