foxclaw 0.1.0 → 0.1.2

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 (495) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +11 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +165 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +122 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/Dockerfile +64 -0
  11. package/python/LICENSE +21 -0
  12. package/python/MANIFEST.in +14 -0
  13. package/python/README.md +192 -0
  14. package/python/README_CN.md +324 -0
  15. package/python/bench/bm25_audit.py +199 -0
  16. package/python/bench/bm25_regression.py +178 -0
  17. package/python/bench/bm25_stress.py +159 -0
  18. package/python/bench/cd_audit.py +238 -0
  19. package/python/bench/chaos.py +142 -0
  20. package/python/bench/concurrency.py +103 -0
  21. package/python/bench/e2e_real_models.py +95 -0
  22. package/python/bench/llm_retry_test.py +248 -0
  23. package/python/bench/load_test.py +196 -0
  24. package/python/bench/vector_ivf_upgrade.py +134 -0
  25. package/python/docs/CHANNELS.md +195 -0
  26. package/python/docs/GOVERNANCE.md +164 -0
  27. package/python/docs/RELEASE.md +109 -0
  28. package/python/docs/RUN.md +75 -0
  29. package/python/docs/RUN_CN.md +121 -0
  30. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  31. package/python/evals/__init__.py +0 -0
  32. package/python/evals/governance/README.md +61 -0
  33. package/python/evals/governance/__init__.py +0 -0
  34. package/python/evals/governance/cases.py +152 -0
  35. package/python/evals/governance/compare_embeddings.py +94 -0
  36. package/python/evals/governance/convergence.svg +11 -0
  37. package/python/evals/governance/report.json +371 -0
  38. package/python/evals/governance/run_evals.py +400 -0
  39. package/python/examples/governance_middleware_demo.py +77 -0
  40. package/python/examples/langgraph_governance_demo.py +108 -0
  41. package/python/examples/sample_itinerary.csv +6 -0
  42. package/python/examples/sample_knowledge.csv +9 -0
  43. package/python/examples/sample_transactions.csv +14 -0
  44. package/python/foxclaw/__init__.py +526 -0
  45. package/python/foxclaw/agents/__init__.py +12 -0
  46. package/python/foxclaw/agents/agents.py +35 -0
  47. package/python/foxclaw/agents/base.py +865 -0
  48. package/python/foxclaw/agents/impl/__init__.py +1 -0
  49. package/python/foxclaw/agents/impl/_helpers.py +78 -0
  50. package/python/foxclaw/agents/impl/finance.py +248 -0
  51. package/python/foxclaw/agents/impl/office.py +367 -0
  52. package/python/foxclaw/agents/impl/supervisor.py +659 -0
  53. package/python/foxclaw/agents/mixins/_context.py +151 -0
  54. package/python/foxclaw/agents/mixins/augment.py +149 -0
  55. package/python/foxclaw/agents/mixins/llm.py +66 -0
  56. package/python/foxclaw/agents/mixins/memory.py +35 -0
  57. package/python/foxclaw/agents/mixins/preference.py +81 -0
  58. package/python/foxclaw/agents/mixins/prompt.py +77 -0
  59. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  60. package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
  61. package/python/foxclaw/api/__init__.py +283 -0
  62. package/python/foxclaw/api/app.py +123 -0
  63. package/python/foxclaw/api/middleware.py +317 -0
  64. package/python/foxclaw/api/routes/ahs.py +417 -0
  65. package/python/foxclaw/api/routes/attachments.py +117 -0
  66. package/python/foxclaw/api/routes/browser.py +39 -0
  67. package/python/foxclaw/api/routes/domain.py +314 -0
  68. package/python/foxclaw/api/routes/knowledge.py +163 -0
  69. package/python/foxclaw/api/routes/llm_admin.py +461 -0
  70. package/python/foxclaw/api/routes/memory.py +89 -0
  71. package/python/foxclaw/api/routes/orchestration.py +41 -0
  72. package/python/foxclaw/api/routes/recovery.py +43 -0
  73. package/python/foxclaw/api/routes/review.py +275 -0
  74. package/python/foxclaw/api/routes/search.py +37 -0
  75. package/python/foxclaw/api/routes/system.py +327 -0
  76. package/python/foxclaw/api/routes/task.py +281 -0
  77. package/python/foxclaw/api/routes/ws.py +68 -0
  78. package/python/foxclaw/api/spine.py +55 -0
  79. package/python/foxclaw/browser/__init__.py +13 -0
  80. package/python/foxclaw/browser/action/__init__.py +1 -0
  81. package/python/foxclaw/browser/action/action_executor.py +54 -0
  82. package/python/foxclaw/browser/action/action_types.py +45 -0
  83. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  84. package/python/foxclaw/browser/action/url_policy.py +147 -0
  85. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  86. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  87. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  88. package/python/foxclaw/browser/driver/__init__.py +1 -0
  89. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  90. package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
  91. package/python/foxclaw/browser/perception/__init__.py +1 -0
  92. package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
  93. package/python/foxclaw/browser/perception/element_indexer.py +57 -0
  94. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  95. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  96. package/python/foxclaw/browser/service.py +148 -0
  97. package/python/foxclaw/browser/session/__init__.py +1 -0
  98. package/python/foxclaw/browser/session/session_manager.py +57 -0
  99. package/python/foxclaw/capability/__init__.py +12 -0
  100. package/python/foxclaw/capability/embedding_service.py +67 -0
  101. package/python/foxclaw/channels/__init__.py +140 -0
  102. package/python/foxclaw/channels/discord.py +184 -0
  103. package/python/foxclaw/channels/dispatcher.py +204 -0
  104. package/python/foxclaw/channels/feishu.py +225 -0
  105. package/python/foxclaw/channels/openclaw_bridge.py +156 -0
  106. package/python/foxclaw/channels/signing.py +187 -0
  107. package/python/foxclaw/channels/telegram.py +121 -0
  108. package/python/foxclaw/channels/weixin.py +225 -0
  109. package/python/foxclaw/cli.py +154 -0
  110. package/python/foxclaw/cognitive/__init__.py +4 -0
  111. package/python/foxclaw/cognitive/reasoner.py +82 -0
  112. package/python/foxclaw/cognitive/state.py +147 -0
  113. package/python/foxclaw/core/__init__.py +9 -0
  114. package/python/foxclaw/core/audit_chain.py +133 -0
  115. package/python/foxclaw/core/audit_sink.py +100 -0
  116. package/python/foxclaw/core/compaction.py +283 -0
  117. package/python/foxclaw/core/config.py +65 -0
  118. package/python/foxclaw/core/confirm_nonce.py +157 -0
  119. package/python/foxclaw/core/dispatch.py +27 -0
  120. package/python/foxclaw/core/envfile.py +48 -0
  121. package/python/foxclaw/core/failure_policy.py +151 -0
  122. package/python/foxclaw/core/gateway.py +85 -0
  123. package/python/foxclaw/core/governance_audit.py +175 -0
  124. package/python/foxclaw/core/idempotency.py +289 -0
  125. package/python/foxclaw/core/injection_guard.py +221 -0
  126. package/python/foxclaw/core/memory_adapter.py +516 -0
  127. package/python/foxclaw/core/metrics.py +89 -0
  128. package/python/foxclaw/core/model_router.py +1097 -0
  129. package/python/foxclaw/core/paths.py +103 -0
  130. package/python/foxclaw/core/recovery.py +362 -0
  131. package/python/foxclaw/core/scheduler.py +144 -0
  132. package/python/foxclaw/core/secrets.py +134 -0
  133. package/python/foxclaw/core/sessions.py +157 -0
  134. package/python/foxclaw/core/settings.py +430 -0
  135. package/python/foxclaw/core/shared_write.py +198 -0
  136. package/python/foxclaw/core/steering_queue.py +153 -0
  137. package/python/foxclaw/core/system_bus.py +84 -0
  138. package/python/foxclaw/core/thought_chain.py +318 -0
  139. package/python/foxclaw/core/token_meter.py +235 -0
  140. package/python/foxclaw/core/tools.py +172 -0
  141. package/python/foxclaw/core/tracing.py +390 -0
  142. package/python/foxclaw/core/truth_pipeline.py +114 -0
  143. package/python/foxclaw/core/vec_recall_adapter.py +503 -0
  144. package/python/foxclaw/core/verify.py +181 -0
  145. package/python/foxclaw/core/web_search.py +242 -0
  146. package/python/foxclaw/finance/__init__.py +15 -0
  147. package/python/foxclaw/finance/evidence.py +153 -0
  148. package/python/foxclaw/finance/fx.py +100 -0
  149. package/python/foxclaw/finance/graph.py +188 -0
  150. package/python/foxclaw/finance/models.py +150 -0
  151. package/python/foxclaw/finance/nlparse.py +575 -0
  152. package/python/foxclaw/finance/policy.py +329 -0
  153. package/python/foxclaw/finance/reasoner.py +129 -0
  154. package/python/foxclaw/finance/reasoning.py +340 -0
  155. package/python/foxclaw/finance/reconcile.py +180 -0
  156. package/python/foxclaw/finance/runtime.py +527 -0
  157. package/python/foxclaw/governance/__init__.py +24 -0
  158. package/python/foxclaw/governance/middleware.py +184 -0
  159. package/python/foxclaw/harness/__init__.py +17 -0
  160. package/python/foxclaw/harness/_base.py +72 -0
  161. package/python/foxclaw/harness/_policy.py +359 -0
  162. package/python/foxclaw/harness/drift.py +89 -0
  163. package/python/foxclaw/harness/event_bus.py +31 -0
  164. package/python/foxclaw/harness/harness.py +446 -0
  165. package/python/foxclaw/harness/loop_detection.py +152 -0
  166. package/python/foxclaw/itinerary/__init__.py +10 -0
  167. package/python/foxclaw/itinerary/amap_client.py +117 -0
  168. package/python/foxclaw/itinerary/models.py +80 -0
  169. package/python/foxclaw/itinerary/reasoner.py +101 -0
  170. package/python/foxclaw/itinerary/reasoning.py +174 -0
  171. package/python/foxclaw/itinerary/runtime.py +148 -0
  172. package/python/foxclaw/kernel/__init__.py +13 -0
  173. package/python/foxclaw/kernel/capabilities.py +73 -0
  174. package/python/foxclaw/kernel/policy_gate.py +114 -0
  175. package/python/foxclaw/kernel/process_manager.py +205 -0
  176. package/python/foxclaw/kernel/scheduler.py +131 -0
  177. package/python/foxclaw/kernel/sentinel.py +453 -0
  178. package/python/foxclaw/kernel/sentinel_client.py +246 -0
  179. package/python/foxclaw/kernel/spine.py +97 -0
  180. package/python/foxclaw/knowledge/__init__.py +376 -0
  181. package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
  182. package/python/foxclaw/learning/__init__.py +12 -0
  183. package/python/foxclaw/learning/knowledge_graph.py +149 -0
  184. package/python/foxclaw/learning/models.py +93 -0
  185. package/python/foxclaw/learning/reasoner.py +70 -0
  186. package/python/foxclaw/learning/runtime.py +363 -0
  187. package/python/foxclaw/learning/spaced_repetition.py +96 -0
  188. package/python/foxclaw/mcp_server.py +189 -0
  189. package/python/foxclaw/orchestration/__init__.py +184 -0
  190. package/python/foxclaw/pipeline/__init__.py +23 -0
  191. package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
  192. package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
  193. package/python/foxclaw/planning/__init__.py +17 -0
  194. package/python/foxclaw/planning/llm_planner.py +232 -0
  195. package/python/foxclaw/planning/planner.py +99 -0
  196. package/python/foxclaw/search/__init__.py +24 -0
  197. package/python/foxclaw/search/agent_port.py +194 -0
  198. package/python/foxclaw/search/evidence.py +113 -0
  199. package/python/foxclaw/search/external_search.py +50 -0
  200. package/python/foxclaw/search/hkc_read_port.py +60 -0
  201. package/python/foxclaw/search/internal_search.py +73 -0
  202. package/python/foxclaw/search/models.py +129 -0
  203. package/python/foxclaw/search/planner.py +44 -0
  204. package/python/foxclaw/search/retriever.py +39 -0
  205. package/python/foxclaw/search/runtime.py +134 -0
  206. package/python/foxclaw/search/strategy.py +44 -0
  207. package/python/foxclaw/skills/__init__.py +613 -0
  208. package/python/foxclaw/skills/hooks_spec.py +73 -0
  209. package/python/foxclaw/skills/lifecycle.py +278 -0
  210. package/python/foxclaw/skills/markdown_skill.py +174 -0
  211. package/python/foxclaw/skills/markdown_store.py +39 -0
  212. package/python/foxclaw/skills/mcp_client.py +190 -0
  213. package/python/foxclaw/skills/mcp_store.py +43 -0
  214. package/python/foxclaw/skills/permission.py +180 -0
  215. package/python/foxclaw/skills/pipeline.py +386 -0
  216. package/python/foxclaw/skills/policy.py +80 -0
  217. package/python/foxclaw/skills/review.py +72 -0
  218. package/python/foxclaw/skills/store.py +57 -0
  219. package/python/frontend/assets/core_bg.jpg +0 -0
  220. package/python/frontend/deck/auth.js +32 -0
  221. package/python/frontend/deck/chat_model_picker.js +537 -0
  222. package/python/frontend/deck/domain.js +296 -0
  223. package/python/frontend/deck/main.js +1950 -0
  224. package/python/frontend/deck/markdown.js +48 -0
  225. package/python/frontend/deck/settings.js +324 -0
  226. package/python/frontend/deck/state.js +6 -0
  227. package/python/frontend/deck/util.js +6 -0
  228. package/python/frontend/deck/visuals.js +212 -0
  229. package/python/frontend/deck.css +855 -0
  230. package/python/frontend/deck.html +686 -0
  231. package/python/frontend/deck_v2.html +341 -0
  232. package/python/frontend/planet/index.html +266 -0
  233. package/python/frontend/planet/planet.css +579 -0
  234. package/python/frontend/planet/planet.js +1637 -0
  235. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  236. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  237. package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
  238. package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
  239. package/python/frontend/v3/index.html +15 -0
  240. package/python/frontend/widget_runtime.js +295 -0
  241. package/python/frontend/widgets.js +696 -0
  242. package/python/frontend_src/index.html +14 -0
  243. package/python/frontend_src/package-lock.json +1376 -0
  244. package/python/frontend_src/package.json +19 -0
  245. package/python/frontend_src/src/App.vue +62 -0
  246. package/python/frontend_src/src/api.js +89 -0
  247. package/python/frontend_src/src/components/ChatDock.vue +377 -0
  248. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  249. package/python/frontend_src/src/components/DocViewer.vue +171 -0
  250. package/python/frontend_src/src/components/Inspector.vue +241 -0
  251. package/python/frontend_src/src/components/LeftRail.vue +394 -0
  252. package/python/frontend_src/src/components/MenuPopover.vue +130 -0
  253. package/python/frontend_src/src/components/MessageStream.vue +401 -0
  254. package/python/frontend_src/src/components/ModelPicker.vue +355 -0
  255. package/python/frontend_src/src/components/QuickIngest.vue +118 -0
  256. package/python/frontend_src/src/components/Stage.vue +226 -0
  257. package/python/frontend_src/src/components/ToolMenu.vue +144 -0
  258. package/python/frontend_src/src/components/TopBar.vue +90 -0
  259. package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
  260. package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
  261. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
  262. package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
  263. package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
  264. package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
  265. package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
  266. package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
  267. package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
  268. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  269. package/python/frontend_src/src/lib/fmt.js +48 -0
  270. package/python/frontend_src/src/lib/md.js +204 -0
  271. package/python/frontend_src/src/main.js +7 -0
  272. package/python/frontend_src/src/stores/app.js +106 -0
  273. package/python/frontend_src/src/stores/chat.js +484 -0
  274. package/python/frontend_src/src/stores/model.js +104 -0
  275. package/python/frontend_src/src/stores/panels.js +330 -0
  276. package/python/frontend_src/src/styles/base.css +138 -0
  277. package/python/frontend_src/src/styles/panel.css +117 -0
  278. package/python/frontend_src/src/styles/tokens.css +78 -0
  279. package/python/frontend_src/vite.config.js +46 -0
  280. package/python/pyproject.toml +265 -0
  281. package/python/skills/README.md +123 -0
  282. package/python/skills/execution-contract/SKILL.md +30 -0
  283. package/python/skills/general-assistant/SKILL.md +34 -0
  284. package/python/skills/planner/SKILL.md +33 -0
  285. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  286. package/python/skills/skill-composition/SKILL.md +31 -0
  287. package/python/skills/skill-guard/SKILL.md +67 -0
  288. package/python/skills/skill-memory/SKILL.md +50 -0
  289. package/python/skills/skill-router/SKILL.md +35 -0
  290. package/python/tests/conftest.py +76 -0
  291. package/python/tests/run_all.py +263 -0
  292. package/python/tests/test_agent_lifecycle.py +360 -0
  293. package/python/tests/test_ahs_p2.py +414 -0
  294. package/python/tests/test_anthropic_tools.py +206 -0
  295. package/python/tests/test_api.py +87 -0
  296. package/python/tests/test_api_auth.py +83 -0
  297. package/python/tests/test_api_host_guard.py +69 -0
  298. package/python/tests/test_attachments.py +167 -0
  299. package/python/tests/test_audit_chain.py +121 -0
  300. package/python/tests/test_audit_sink.py +136 -0
  301. package/python/tests/test_browser.py +153 -0
  302. package/python/tests/test_browser_request_guard.py +184 -0
  303. package/python/tests/test_capabilities.py +158 -0
  304. package/python/tests/test_channel_signing.py +192 -0
  305. package/python/tests/test_channel_trust.py +109 -0
  306. package/python/tests/test_channels.py +184 -0
  307. package/python/tests/test_cli.py +46 -0
  308. package/python/tests/test_compaction_toolpair.py +119 -0
  309. package/python/tests/test_config_layering.py +65 -0
  310. package/python/tests/test_confirm_nonce.py +197 -0
  311. package/python/tests/test_context_compaction_wiring.py +130 -0
  312. package/python/tests/test_dispatch.py +95 -0
  313. package/python/tests/test_domain_evals.py +403 -0
  314. package/python/tests/test_domain_import.py +78 -0
  315. package/python/tests/test_domain_reasoners.py +148 -0
  316. package/python/tests/test_e2e.py +178 -0
  317. package/python/tests/test_emb_cache.py +107 -0
  318. package/python/tests/test_embedding_singleton.py +89 -0
  319. package/python/tests/test_envfile.py +83 -0
  320. package/python/tests/test_evals_smoke.py +114 -0
  321. package/python/tests/test_event_spine.py +182 -0
  322. package/python/tests/test_evidence_semantic.py +101 -0
  323. package/python/tests/test_evidence_sse.py +97 -0
  324. package/python/tests/test_file_ingest.py +146 -0
  325. package/python/tests/test_finance.py +153 -0
  326. package/python/tests/test_finance_ledger.py +252 -0
  327. package/python/tests/test_finance_persist.py +178 -0
  328. package/python/tests/test_gateway.py +144 -0
  329. package/python/tests/test_governance.py +468 -0
  330. package/python/tests/test_governance_audit.py +359 -0
  331. package/python/tests/test_governance_middleware.py +143 -0
  332. package/python/tests/test_hkc.py +286 -0
  333. package/python/tests/test_hkc_index_build_manager.py +339 -0
  334. package/python/tests/test_hkc_manifest_reuse.py +317 -0
  335. package/python/tests/test_hkc_search_event_sync.py +533 -0
  336. package/python/tests/test_import_path_guard.py +161 -0
  337. package/python/tests/test_injection_guard.py +211 -0
  338. package/python/tests/test_integration.py +141 -0
  339. package/python/tests/test_itinerary.py +103 -0
  340. package/python/tests/test_kee_semantic.py +89 -0
  341. package/python/tests/test_kernel.py +222 -0
  342. package/python/tests/test_learning.py +110 -0
  343. package/python/tests/test_llm_admin.py +160 -0
  344. package/python/tests/test_llm_planner.py +366 -0
  345. package/python/tests/test_markdown_skills.py +73 -0
  346. package/python/tests/test_markdown_skills_openclaw.py +71 -0
  347. package/python/tests/test_mcp_connectors.py +109 -0
  348. package/python/tests/test_mcp_guardian_discovery.py +91 -0
  349. package/python/tests/test_mcp_no_memory.py +43 -0
  350. package/python/tests/test_mcp_policy_gate.py +77 -0
  351. package/python/tests/test_mcp_server.py +95 -0
  352. package/python/tests/test_memory_recall.py +95 -0
  353. package/python/tests/test_metrics.py +66 -0
  354. package/python/tests/test_multi_chain.py +102 -0
  355. package/python/tests/test_multimodal.py +135 -0
  356. package/python/tests/test_negotiation.py +146 -0
  357. package/python/tests/test_orchestration.py +119 -0
  358. package/python/tests/test_orchestration_decoupled.py +109 -0
  359. package/python/tests/test_p3_p5_hardening.py +338 -0
  360. package/python/tests/test_parallel_dispatch.py +215 -0
  361. package/python/tests/test_pipeline.py +533 -0
  362. package/python/tests/test_plan_visible.py +86 -0
  363. package/python/tests/test_planner.py +95 -0
  364. package/python/tests/test_policy_gate.py +109 -0
  365. package/python/tests/test_reasoner.py +185 -0
  366. package/python/tests/test_release_check.py +263 -0
  367. package/python/tests/test_resume_checkpoint.py +156 -0
  368. package/python/tests/test_routing.py +95 -0
  369. package/python/tests/test_scheduler.py +141 -0
  370. package/python/tests/test_search.py +204 -0
  371. package/python/tests/test_secrets.py +159 -0
  372. package/python/tests/test_semantic.py +78 -0
  373. package/python/tests/test_sessions.py +91 -0
  374. package/python/tests/test_settings.py +281 -0
  375. package/python/tests/test_shadow_learning.py +330 -0
  376. package/python/tests/test_shared_write.py +207 -0
  377. package/python/tests/test_skill_taint_gate.py +154 -0
  378. package/python/tests/test_skills.py +133 -0
  379. package/python/tests/test_stats_and_events.py +165 -0
  380. package/python/tests/test_streaming.py +60 -0
  381. package/python/tests/test_supervisor_pipeline.py +177 -0
  382. package/python/tests/test_supervisor_triage.py +108 -0
  383. package/python/tests/test_taint.py +117 -0
  384. package/python/tests/test_task_stream.py +85 -0
  385. package/python/tests/test_thinking_sse.py +67 -0
  386. package/python/tests/test_thinking_stream.py +76 -0
  387. package/python/tests/test_tool_center.py +129 -0
  388. package/python/tests/test_tool_parallel.py +113 -0
  389. package/python/tests/test_tool_params.py +103 -0
  390. package/python/tests/test_tool_use.py +242 -0
  391. package/python/tests/test_toplevel_concurrency.py +117 -0
  392. package/python/tests/test_tracing.py +290 -0
  393. package/python/tests/test_truth_crystallizer.py +127 -0
  394. package/python/tests/test_truth_flow.py +93 -0
  395. package/python/tests/test_url_policy.py +53 -0
  396. package/python/tests/test_verify_evidence.py +225 -0
  397. package/python/tests/test_version_sync.py +71 -0
  398. package/python/tests/test_web_trigger.py +99 -0
  399. package/python/tools/check_faiss_type.py +31 -0
  400. package/python/tools/check_log.ps1 +46 -0
  401. package/python/tools/ivf_status.py +72 -0
  402. package/python/tools/mypy_baseline.txt +5 -0
  403. package/python/tools/mypy_ratchet.py +97 -0
  404. package/python/tools/pack.py +178 -0
  405. package/python/tools/release_check.py +349 -0
  406. package/python/tools/restart_foxclaw.ps1 +74 -0
  407. package/python/tools/shared_write_audit.py +204 -0
  408. package/python/tools/size_baseline.txt +28 -0
  409. package/python/tools/size_check.py +102 -0
  410. package/python/tools/verify_env.bat +2 -0
  411. package/python/tools/verify_env.py +25 -0
  412. package/python/vendor/HMR_LICENSE +37 -0
  413. package/python/vendor/HMR_README.md +65 -0
  414. package/python/vendor/hkc/LICENSE +37 -0
  415. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  416. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  417. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  418. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  419. package/python/vendor/hkc/hkc_api/container.py +416 -0
  420. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  421. package/python/vendor/hkc/hkc_api/main.py +157 -0
  422. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  423. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  424. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  425. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  426. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  427. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  428. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  429. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  430. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  431. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  432. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  433. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  434. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  435. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  436. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  437. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  438. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  439. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  440. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  441. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  442. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  443. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  444. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  445. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  446. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  447. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  448. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  449. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  450. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  451. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  452. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  453. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  454. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  455. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  456. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  457. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  458. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  459. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  460. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  461. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  462. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  463. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  464. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  465. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  466. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  467. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  468. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  469. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  470. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  471. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  472. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  473. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  474. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  475. package/python/vendor/hmr/__init__.py +0 -0
  476. package/python/vendor/hmr/core/__init__.py +0 -0
  477. package/python/vendor/hmr/core/hmr.py +684 -0
  478. package/python/vendor/hmr/core/models.py +310 -0
  479. package/python/vendor/hmr/engines/__init__.py +0 -0
  480. package/python/vendor/hmr/engines/evolution.py +710 -0
  481. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  482. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  483. package/python/vendor/hmr/engines/policy.py +625 -0
  484. package/python/vendor/hmr/engines/recall.py +260 -0
  485. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  486. package/python/vendor/hmr/engines/scheduler.py +323 -0
  487. package/python/vendor/hmr/engines/semantic.py +150 -0
  488. package/python/vendor/hmr/engines/temporal.py +261 -0
  489. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  490. package/python/vendor/hmr/graph/__init__.py +0 -0
  491. package/python/vendor/hmr/graph/cwg.py +338 -0
  492. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  493. package/python/vendor/hmr/storage/__init__.py +0 -0
  494. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  495. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,359 @@
1
+ """批次 3 · 治理审计链:P1-2 统一审计 / P1-3 编排审计 / P2-0 Skill 审计 / P3-4 decision_id。
2
+
3
+ 验收标准(执行计划):governance.* 事件 WS 可见 + decision_id 跨层关联。
4
+ 覆盖:
5
+ 1. 审计器单元层:事件/落盘/开关/fail-safe
6
+ 2. PolicyGate(P1-2/P3-4):decision_id 生成、非 allow 记审计、向后兼容
7
+ 3. Orchestrator(P1-3):三种终态裁决有审计、协商链 correlation_id 关联
8
+ 4. SkillAdapter(P2-0/P3-4):执行终态审计、裁决→执行 decision_id join(核心)
9
+ 5. WS 可见性 + 真实装配端到端
10
+ """
11
+ import os
12
+ import sys
13
+ import tempfile
14
+ import shutil
15
+ from pathlib import Path
16
+
17
+ sys.path.insert(0, str(Path(__file__).parent.parent))
18
+
19
+ # 隔离项目 .env(本测试不依赖任何真实配置;与批次 2 的隔离策略一致)
20
+ os.environ.setdefault("FOXCLAW_SKIP_DOTENV", "1")
21
+ os.environ.setdefault("HKC_EMBEDDING", "stub")
22
+
23
+ CHECKS = []
24
+
25
+
26
+ def check(cond, msg):
27
+ CHECKS.append((bool(cond), msg))
28
+ print(f" {'✓' if cond else '✗'} {msg}")
29
+ if not cond:
30
+ raise AssertionError(f"断言失败: {msg}")
31
+
32
+
33
+ from foxclaw.core.governance_audit import (
34
+ GovernanceAuditor, attach, new_decision_id, TOPIC, ENV_SWITCH)
35
+ from foxclaw.kernel.spine import EventSpine
36
+
37
+
38
+ def _fresh_auditor(test_dir, with_spine=True):
39
+ spine = EventSpine() if with_spine else None
40
+ aud = GovernanceAuditor(spine=spine,
41
+ sink_path=Path(test_dir) / "audit" / "governance.jsonl")
42
+ return aud, spine
43
+
44
+
45
+ def _gov_events(spine):
46
+ return [e for e in spine.history("governance.", 500)]
47
+
48
+
49
+ # ── 1. 审计器单元层 ─────────────────────────────────────────
50
+
51
+ def test_auditor_unit(test_dir):
52
+ print("\n【1】审计器单元:decision_id / 事件 / 落盘 / fail-safe")
53
+ d1, d2 = new_decision_id(), new_decision_id()
54
+ check(d1.startswith("dec_") and len(d1) == 16, f"decision_id 格式 dec_xxxxxxxxxxxx: {d1}")
55
+ check(d1 != d2, "两次生成不相同(唯一性)")
56
+
57
+ aud, spine = _fresh_auditor(test_dir)
58
+ did = aud.record(layer="permission", actor="assistant", action="action:send_mail",
59
+ verdict="deny", reason="命中 deny 规则",
60
+ detail={"stage": 2}, correlation_id="corr_test_1")
61
+ evs = _gov_events(spine)
62
+ check(len(evs) == 1, f"脊柱有 1 条 governance.decision(实际 {len(evs)})")
63
+ p = evs[0]["payload"] # history() 返回 Event.to_dict()
64
+ check(evs[0]["topic"] == TOPIC, "事件主题 governance.decision")
65
+ check(p["decision_id"] == did and p["layer"] == "permission"
66
+ and p["verdict"] == "deny" and p["reason"] == "命中 deny 规则",
67
+ "事件 payload 字段完整")
68
+ check(evs[0]["correlation_id"] == "corr_test_1", "correlation_id 透传到事件")
69
+
70
+ # 显式 decision_id 被尊重(P3-4:PolicyGate 生成后交给审计器,不能再换一个)
71
+ did2 = aud.record(layer="skill", actor="", action="invoke:x", verdict="success",
72
+ decision_id="dec_explicit01")
73
+ check(did2 == "dec_explicit01", "显式 decision_id 原样返回")
74
+
75
+ # 落盘与事件一致
76
+ recs = aud.read()
77
+ check(len(recs) == 2, f"落盘 2 条(实际 {len(recs)})")
78
+ check(recs[0]["decision_id"] == "dec_explicit01", "read() 新→旧排序")
79
+ check(recs[1]["correlation_id"] == "corr_test_1", "落盘记录含 correlation_id")
80
+
81
+ # fail-safe:spine 爆炸不影响 record
82
+ class BoomSpine:
83
+ def publish(self, *a, **k):
84
+ raise RuntimeError("spine boom")
85
+ aud2 = GovernanceAuditor(spine=BoomSpine(), sink_path=None)
86
+ did3 = aud2.record(layer="harness", actor="finance", action="reasoning:plan",
87
+ verdict="warn", reason="语义漂移")
88
+ check(did3.startswith("dec_"), "spine 异常时 record 仍返回 decision_id(fail-safe)")
89
+
90
+ # 开关
91
+ os.environ[ENV_SWITCH] = "0"
92
+ try:
93
+ check(attach(spine, test_dir) is None, f"{ENV_SWITCH}=0 时 attach 返回 None")
94
+ finally:
95
+ del os.environ[ENV_SWITCH]
96
+ check(attach(spine, test_dir) is not None, "默认(未设开关)attach 正常返回")
97
+
98
+
99
+ # ── 2. PolicyGate(P1-2/P3-4)─────────────────────────────────
100
+
101
+ def _make_gate(test_dir, spine=None):
102
+ from foxclaw.kernel.policy_gate import PolicyGate
103
+ from foxclaw.skills.permission import evaluate
104
+ aud, spine = _fresh_auditor(test_dir, with_spine=True)
105
+ gate = PolicyGate(spine=spine, action_evaluator=evaluate, auditor=aud)
106
+ return gate, aud, spine
107
+
108
+
109
+ def _make_skill(sid="demo.skill", side_effect="read", destructive=False):
110
+ from foxclaw.skills import SkillSpec
111
+ return SkillSpec(id=sid, name=sid, description="测试",
112
+ side_effect=side_effect, destructive=destructive,
113
+ handler=lambda **k: {"ok": True})
114
+
115
+
116
+ def test_policy_gate(test_dir):
117
+ print("\n【2】PolicyGate:decision_id(P3-4)+ 非 allow 审计(P1-2)")
118
+ gate, aud, spine = _make_gate(test_dir)
119
+
120
+ # allow:有 decision_id,但**不**记审计(高频常态不淹没审计流)
121
+ v_allow = gate.check_action(_make_skill(), {})
122
+ check(v_allow.decision_id.startswith("dec_"), "allow 裁决也带 decision_id(供执行审计 join)")
123
+ check(len(_gov_events(spine)) == 0, "allow 不记 governance 事件")
124
+
125
+ # deny:记审计,layer=permission,decision_id 一致
126
+ from foxclaw.skills.permission import PermissionRules
127
+ rules = PermissionRules(deny=["demo.*"])
128
+ v_deny = gate.check_action(_make_skill(), {}, rules=rules)
129
+ check(v_deny.level == "deny" and not v_deny.allowed, "deny 裁决正确(行为不变)")
130
+ evs = _gov_events(spine)
131
+ check(len(evs) == 1, f"deny 记 1 条审计(实际 {len(evs)})")
132
+ p = evs[0]["payload"]
133
+ check(p["layer"] == "permission" and p["verdict"] == "deny",
134
+ "审计 layer=permission verdict=deny")
135
+ check(p["decision_id"] == v_deny.decision_id, "审计 decision_id 与 Verdict 一致(P3-4)")
136
+ check(p["detail"]["stage"] == 2, "detail 含四阶段信息")
137
+
138
+ # ask → confirm
139
+ v_ask = gate.check_action(_make_skill("x.pay", side_effect="pay"), {})
140
+ check(v_ask.level == "confirm", "ask 归一为 confirm(行为不变)")
141
+ evs = _gov_events(spine)
142
+ check(len(evs) == 2 and evs[1]["payload"]["verdict"] == "confirm", "confirm 也记审计")
143
+
144
+ # 推理准入:BLOCK 记,PASS 不记
145
+ from foxclaw.harness.harness import HarnessResult
146
+ class FakeHarness:
147
+ def __init__(self, status): self._s = status
148
+ def check(self, node):
149
+ return HarnessResult(status=self._s, delta=0.5, dominant_dim="semantic",
150
+ scores={}, reason="测试漂移")
151
+ class FakeNode:
152
+ id, type, agent_id = "n1", "plan", "finance"
153
+ v_blk = gate.check_reasoning(FakeHarness("BLOCK"), FakeNode())
154
+ check(v_blk.decision_id and v_blk.level == "deny", "BLOCK → deny + decision_id")
155
+ evs = _gov_events(spine)
156
+ check(len(evs) == 3 and evs[2]["payload"]["layer"] == "harness"
157
+ and evs[2]["payload"]["actor"] == "finance",
158
+ "BLOCK 记 layer=harness 审计(actor 取自 node)")
159
+ gate.check_reasoning(FakeHarness("PASS"), FakeNode())
160
+ check(len(_gov_events(spine)) == 3, "PASS 不记审计")
161
+
162
+ # 向后兼容:旧式位置参数构造(无 decision_id)仍可用
163
+ from foxclaw.kernel.policy_gate import Verdict
164
+ v_old = Verdict(True, "allow", "无动作评估器(降级放行)", "permission", None)
165
+ check(v_old.decision_id == "" and v_old.allowed, "Verdict 位置参数构造向后兼容")
166
+
167
+
168
+ # ── 3. Orchestrator(P1-3)───────────────────────────────────
169
+
170
+ class _FakeCaps:
171
+ def __init__(self, agents): self._agents = agents
172
+ def get(self, name): return self._agents if name == "agents" else None
173
+
174
+
175
+ class _FakeAgent:
176
+ """按预设结果裁决的假守门人。"""
177
+ def __init__(self, results): self._results = list(results)
178
+ def handle_cross_request(self, req):
179
+ st, verdict = self._results.pop(0) if self._results else ("approved", "批了")
180
+ req.status, req.verdict = st, verdict
181
+
182
+
183
+ def test_orchestrator(test_dir):
184
+ print("\n【3】Orchestrator:裁决审计 + 协商链 correlation_id(P1-3)")
185
+ from foxclaw.orchestration import Orchestrator, CrossAgentRequest
186
+ aud, spine = _fresh_auditor(test_dir)
187
+
188
+ # 三种终态 + 目标不存在(P1-0 路径)
189
+ agents = {"finance": _FakeAgent([("approved", "预算充足"),
190
+ ("rejected", "超出月度预算")])}
191
+ orch = Orchestrator(_FakeCaps(agents), auditor=aud)
192
+ r1 = orch.route_request(CrossAgentRequest(
193
+ from_agent="admin", to_agent="finance", request_type="budget_request",
194
+ payload={"amount": 100}))
195
+ check(r1.status == "approved", "approved 路由(行为不变)")
196
+ orch.route_request(CrossAgentRequest(
197
+ from_agent="admin", to_agent="finance", request_type="budget_request",
198
+ payload={"amount": 99999}))
199
+ r3 = orch.route_request(CrossAgentRequest(
200
+ from_agent="admin", to_agent="ghost", request_type="info_request",
201
+ payload={}))
202
+ check(r3.status == "needs_confirmation", "目标不存在 → needs_confirmation(P1-0 行为不变)")
203
+
204
+ evs = _gov_events(spine)
205
+ check(len(evs) == 3, f"三次裁决全部记审计(实际 {len(evs)})")
206
+ vs = [e["payload"]["verdict"] for e in evs]
207
+ check(vs == ["allow", "deny", "confirm"], f"终态映射 approved/rejected/needs_confirmation → allow/deny/confirm: {vs}")
208
+ check(all(e["payload"]["layer"] == "orchestration" for e in evs), "layer=orchestration")
209
+ check(all(e["payload"]["correlation_id"] == "" or True for e in evs), "correlation_id 字段存在")
210
+ check(evs[0]["payload"]["correlation_id"] == r1.id
211
+ and evs[2]["payload"]["correlation_id"] == r3.id,
212
+ "correlation_id == req.id(协商链 join 键)")
213
+
214
+ # 协商链:拒绝 → 让步 → 批准,两轮裁决同一 correlation_id
215
+ agents2 = {"finance": _FakeAgent([("rejected", "太贵"),
216
+ ("approved", "这个价可以")])}
217
+ requester = _FakeAgent([])
218
+ requester.concede = lambda req, verdict: {"amount": 50} # 让步:降价
219
+ orch2 = Orchestrator(_FakeCaps({"finance": agents2["finance"],
220
+ "admin": requester}), auditor=aud)
221
+ req = CrossAgentRequest(from_agent="admin", to_agent="finance",
222
+ request_type="budget_request", payload={"amount": 500})
223
+ final = orch2.negotiate(req)
224
+ check(final.status == "approved", "协商后获批(行为不变)")
225
+ evs2 = _gov_events(spine)[3:] # 前面 3 条之后
226
+ check(len(evs2) == 2, f"协商两轮裁决各记一条(实际 {len(evs2)})")
227
+ check(all(e["payload"]["correlation_id"] == req.id for e in evs2),
228
+ "多轮裁决共用 req.id——协商链可 join(P3-4 跨层关联)")
229
+ check(evs2[0]["payload"]["verdict"] == "deny" and evs2[1]["payload"]["verdict"] == "allow",
230
+ "协商链 deny→allow 轨迹完整")
231
+
232
+ # 无 auditor:standalone 不崩、不记
233
+ orch3 = Orchestrator(_FakeCaps({}), auditor=None)
234
+ orch3.route_request(CrossAgentRequest(from_agent="a", to_agent="b",
235
+ request_type="info_request", payload={}))
236
+ check(len(_gov_events(spine)) == 5, "auditor=None 时不记审计(standalone 兼容)")
237
+
238
+
239
+ # ── 4. SkillAdapter(P2-0/P3-4 核心:裁决→执行 join)──────────
240
+
241
+ def test_skill_adapter(test_dir):
242
+ print("\n【4】SkillAdapter:执行终态审计 + 裁决↔执行 decision_id join(P2-0/P3-4)")
243
+ from foxclaw.skills import SkillRegistry, SkillAdapter, SkillSpec
244
+ from foxclaw.skills.permission import PermissionRules
245
+ gate, aud, spine = _make_gate(test_dir)
246
+ reg = SkillRegistry()
247
+ adapter = SkillAdapter(reg, policy_gate=gate)
248
+
249
+ # ① 只读 skill 成功执行 → skill success 审计,correlation_id == 权限裁决 decision_id
250
+ ok_skill = SkillSpec(id="demo.read", name="读", description="只读",
251
+ side_effect="read", handler=lambda **k: {"value": 42})
252
+ res = adapter.invoke(ok_skill, {})
253
+ check(res.success, "只读 skill 执行成功(行为不变)")
254
+ evs = _gov_events(spine)
255
+ check(len(evs) == 1, "allow 的权限裁决不记审计,只有执行审计")
256
+ p = evs[0]["payload"]
257
+ check(p["layer"] == "skill" and p["verdict"] == "success", "执行成功记 success")
258
+ corr = p["correlation_id"]
259
+ check(corr.startswith("dec_"), f"执行审计带裁决 decision_id 作 correlation_id: {corr}")
260
+
261
+ # ② 该 correlation_id 能 join 到真实裁决:再触发一次**非 allow** 裁决,
262
+ # 验证 permission 记录的 decision_id 与后续执行审计的 correlation_id 同链
263
+ pay_skill = SkillSpec(id="demo.pay", name="付", description="支付",
264
+ side_effect="pay", handler=lambda **k: {"done": True})
265
+ res2 = adapter.invoke(pay_skill, {}, confirmed=True) # 已确认 → allow → 执行
266
+ check(res2.success, "已确认的支付 skill 执行成功")
267
+ # 手工复演同参裁决,验证 decision_id 链:invoke 内部那次裁决的 id == 执行审计 correlation_id
268
+ exec_recs = [e["payload"] for e in _gov_events(spine) if e["payload"]["layer"] == "skill"]
269
+ check(len(exec_recs) == 2, "两次成功执行各记一条")
270
+ check(all(r["correlation_id"].startswith("dec_") for r in exec_recs),
271
+ "每条执行审计都挂在某个裁决 decision_id 上")
272
+
273
+ # ③ deny:permission 记裁决,skill 层**不重复**记执行
274
+ adapter_deny = SkillAdapter(reg, policy_gate=gate,
275
+ rules=PermissionRules(deny=["evil.*"]))
276
+ evil = SkillSpec(id="evil.wipe", name="清", description="危险",
277
+ side_effect="write", destructive=True, handler=lambda **k: {})
278
+ res3 = adapter_deny.invoke(evil, {})
279
+ check(res3.status == "blocked" and "权限拒绝" in res3.message, "deny 拦截(行为不变)")
280
+ evs3 = [e["payload"] for e in _gov_events(spine)]
281
+ perm_recs = [r for r in evs3 if r["layer"] == "permission"]
282
+ skill_recs_after = [r for r in evs3 if r["layer"] == "skill"]
283
+ check(len(perm_recs) == 1 and perm_recs[0]["verdict"] == "deny", "deny 裁决被记录")
284
+ check(len(skill_recs_after) == 2, "deny 后 skill 层不重复记录(仍只有前两条执行审计)")
285
+
286
+ # ④ 断路器出口:权限过了但断路器开 → skill deny 审计,correlation_id 挂裁决
287
+ from foxclaw.skills.lifecycle import CircuitBreaker
288
+ br = CircuitBreaker()
289
+ for _ in range(10):
290
+ br.record_failure("demo.flaky")
291
+ adapter2 = SkillAdapter(reg, policy_gate=gate, breaker=br)
292
+ flaky = SkillSpec(id="demo.flaky", name="抖", description="老失败",
293
+ side_effect="read", handler=lambda **k: {"v": 1})
294
+ res4 = adapter2.invoke(flaky, {})
295
+ check(res4.status == "circuit_open", "断路器开启(行为不变)")
296
+ last = _gov_events(spine)[-1]["payload"]
297
+ check(last["layer"] == "skill" and last["verdict"] == "deny"
298
+ and "断路器" in last["reason"], "断路器出口记 skill deny 审计")
299
+ check(last["correlation_id"].startswith("dec_"), "断路器审计也挂在裁决 decision_id 上")
300
+
301
+
302
+ # ── 5. WS 可见性 + 真实装配端到端 ───────────────────────────
303
+
304
+ def test_ws_visibility_and_wiring(test_dir):
305
+ print("\n【5】WS 可见性 + 真实装配端到端")
306
+ # governance.decision 不能被 WS 静音逻辑吞掉(验收:governance.* 事件 WS 可见)。
307
+ # _WS_MUTED 是 api 投影闭包的局部变量,无法 import —— 改为源码级断言:静音规则只有
308
+ # 「agent.cognition.* 前缀」与「_WS_MUTED 元组」两条,二者都不得命中 governance.*。
309
+ # 这同时是一道防退化闸:将来若有人把 governance.* 加进静音列表,本测试会红。
310
+ import inspect, re as _re
311
+ import foxclaw.api as _api_mod
312
+ src = inspect.getsource(_api_mod)
313
+ m = _re.search(r"_WS_MUTED\s*=\s*\(([^)]*)\)", src)
314
+ muted_members = m.group(1) if m else ""
315
+ check("governance" not in muted_members, "governance.* 不在 _WS_MUTED 静音元组")
316
+ check(not "governance.decision".startswith("agent.cognition."),
317
+ "governance.decision 不命中 agent.cognition.* 静音前缀")
318
+
319
+ # 真实装配:三处同一 auditor;一次真共享写入之外的治理动作落盘
320
+ from foxclaw import Foxclaw
321
+ d = tempfile.mkdtemp(prefix="foxclaw_gov_e2e_", dir=test_dir)
322
+ o = Foxclaw(data_dir=d, shadow_rounds=2)
323
+ check(o.governance_auditor is not None, "装配:governance_auditor 存在")
324
+ check(o.policy_gate.auditor is o.governance_auditor, "PolicyGate 持同一 auditor")
325
+ check(o.orchestrator._auditor is o.governance_auditor, "Orchestrator 持同一 auditor")
326
+
327
+ # 端到端裁决:目标不存在的跨 Agent 请求 → 审计事件 + 落盘
328
+ from foxclaw.orchestration import CrossAgentRequest
329
+ o.orchestrator.route_request(CrossAgentRequest(
330
+ from_agent="admin", to_agent="ghost", request_type="info_request", payload={}))
331
+ evs = o.event_spine.history("governance.", 50)
332
+ check(len(evs) >= 1 and evs[-1]["payload"]["layer"] == "orchestration",
333
+ "真实装配:裁决进脊柱事件流")
334
+ recs = o.governance_auditor.read()
335
+ check(len(recs) >= 1 and recs[0]["verdict"] == "confirm",
336
+ "真实装配:裁决落盘 governance.jsonl 且可读")
337
+ shutil.rmtree(d, ignore_errors=True)
338
+
339
+
340
+ def main():
341
+ print("=" * 60)
342
+ print("批次 3 · 治理审计链(P1-2/P1-3/P2-0/P3-4)")
343
+ print("=" * 60)
344
+ test_dir = tempfile.mkdtemp(prefix="foxclaw_govtest_")
345
+ try:
346
+ test_auditor_unit(test_dir)
347
+ test_policy_gate(test_dir)
348
+ test_orchestrator(test_dir)
349
+ test_skill_adapter(test_dir)
350
+ test_ws_visibility_and_wiring(test_dir)
351
+ finally:
352
+ shutil.rmtree(test_dir, ignore_errors=True)
353
+ print("\n" + "=" * 60)
354
+ print(f"✓ 全部 {len(CHECKS)} 项断言通过")
355
+ print("=" * 60)
356
+
357
+
358
+ if __name__ == "__main__":
359
+ main()
@@ -0,0 +1,143 @@
1
+ """
2
+ M3 · 治理中间件回归测试
3
+
4
+ 验证框架无关的 GovernanceSession / governed:
5
+ 1. 解耦:全程不构造 Foxclaw、不碰 agents/skills/channels/HMR,即可治理 + 审计;
6
+ 2. 工具越权确定性拦截(guard 抛 GovernanceBlocked);
7
+ 3. @governed 装饰器(同步 + 异步)在越权时阻止函数执行;
8
+ 4. 正常动作放行;审计链可取回且含 BLOCK 节点;
9
+ 5. 工具死循环检测。
10
+
11
+ 用英文目标/内容做确定性断言(工具漂移本就确定性;语义维度对中文偏弱见 M1 评测)。
12
+ """
13
+ import asyncio
14
+ import os
15
+ import subprocess
16
+ import sys
17
+ from pathlib import Path
18
+ sys.path.insert(0, str(Path(__file__).parent.parent))
19
+
20
+ from foxclaw.governance import GovernanceSession, governed, GovernanceBlocked
21
+
22
+
23
+ def check(cond, msg):
24
+ if not cond:
25
+ raise AssertionError(f"✗ {msg}")
26
+ print(f" ✓ {msg}")
27
+
28
+
29
+ def _sess():
30
+ return GovernanceSession(
31
+ goal="read the account balance and transactions only",
32
+ allowed_tools={"read"}, entities={"balance", "account"})
33
+
34
+
35
+ def test_standalone_block():
36
+ print("\n【1】独立可用 + 工具越权拦截")
37
+ gov = _sess()
38
+ # 正常只读:不抛(可能 PASS/WARN,但绝不 BLOCK)
39
+ r_ok = gov.observe("call get_balance to read balance", tool="get_balance")
40
+ check(not r_ok.blocked, "只读工具 get_balance 不被拦")
41
+ # 越权:delete 不在 read 允许集 → 硬拦
42
+ r_bad = gov.observe("call delete_account", tool="delete_account")
43
+ check(r_bad.blocked and r_bad.dimension == "tool", "delete_account 被 BLOCK(维度=tool)")
44
+ # guard 风格:越权抛异常
45
+ raised = False
46
+ try:
47
+ gov.guard("call drop_table", tool="drop_table")
48
+ except GovernanceBlocked as e:
49
+ raised = e.result.blocked
50
+ check(raised, "guard 对越权抛 GovernanceBlocked")
51
+
52
+
53
+ def test_decorator_sync_async():
54
+ print("\n【2】@governed 装饰器(同步 + 异步)")
55
+ gov = _sess()
56
+
57
+ @governed(gov)
58
+ def delete_row(x):
59
+ return f"deleted {x}" # 不该被执行
60
+
61
+ executed = {"sync": False}
62
+ try:
63
+ delete_row("r1")
64
+ executed["sync"] = True
65
+ except GovernanceBlocked:
66
+ pass
67
+ check(not executed["sync"], "同步:越权工具函数被拦,未执行")
68
+
69
+ @governed(gov, tool_name="delete_async")
70
+ async def delete_async(x):
71
+ return f"deleted {x}"
72
+
73
+ async def run():
74
+ try:
75
+ await delete_async("r2")
76
+ return True
77
+ except GovernanceBlocked:
78
+ return False
79
+ check(not asyncio.run(run()), "异步:越权协程被拦,未执行")
80
+
81
+ # 正常只读函数正常执行
82
+ @governed(gov, tool_name="get_balance")
83
+ def read_balance():
84
+ return 100
85
+ check(read_balance() == 100, "只读工具函数正常放行执行")
86
+
87
+
88
+ def test_audit_and_loop():
89
+ print("\n【3】审计链 + 循环检测")
90
+ gov = _sess()
91
+ gov.observe("read balance", tool="get_balance")
92
+ gov.observe("call delete_account", tool="delete_account")
93
+ audit = gov.audit()
94
+ check(len(audit) == 2, "审计链取回全部步骤")
95
+ check(any(n["harness_status"] == "BLOCK" and n["tool_name"] == "delete_account"
96
+ for n in audit), "越权步骤在审计链里标为 BLOCK")
97
+ check(gov.summary()["blocked_this_session"] == 1, "摘要记录本会话拦截数")
98
+
99
+ # 循环检测:反复同一工具 → loop 告警
100
+ gov2 = _sess()
101
+ loop_hit = None
102
+ for _ in range(9):
103
+ r = gov2.observe("poll status", tool="get_balance")
104
+ if r.loop:
105
+ loop_hit = r.loop
106
+ check(loop_hit is not None, "反复同一工具触发循环检测告警")
107
+
108
+
109
+ def test_import_decoupling():
110
+ print("\n【4】物理解耦:导入治理中间件不连带拉入 Agent 框架")
111
+ # 必须在**全新子进程**里验(本进程里其它测试早已 import 了 agents,sys.modules 被污染)
112
+ code = (
113
+ "import sys, foxclaw.governance;"
114
+ "bad=[m for m in ('foxclaw.agents.agents','foxclaw.skills',"
115
+ "'foxclaw.channels','foxclaw.core.memory_adapter','foxclaw.orchestration')"
116
+ " if m in sys.modules];"
117
+ "assert not bad, ('leaked:', bad);"
118
+ "assert 'foxclaw.harness.harness' in sys.modules;"
119
+ "assert 'foxclaw.core.thought_chain' in sys.modules;"
120
+ "print('DECOUPLED_OK')"
121
+ )
122
+ env = dict(os.environ, PYTHONUTF8="1", HF_HUB_OFFLINE="1", TRANSFORMERS_OFFLINE="1")
123
+ r = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True,
124
+ cwd=str(Path(__file__).parent.parent), env=env,
125
+ encoding="utf-8", errors="replace")
126
+ check("DECOUPLED_OK" in r.stdout and r.returncode == 0,
127
+ f"import foxclaw.governance 不拉入 agents/skills/channels/HMR({r.stdout.strip()[:60]}{r.stderr.strip()[-80:]})")
128
+
129
+
130
+ def main():
131
+ print("=" * 56)
132
+ print("M3 · 治理中间件回归")
133
+ print("=" * 56)
134
+ for t in (test_standalone_block, test_decorator_sync_async, test_audit_and_loop,
135
+ test_import_decoupling):
136
+ t()
137
+ print("\n" + "=" * 56)
138
+ print("治理中间件全部通过")
139
+ print("=" * 56)
140
+
141
+
142
+ if __name__ == "__main__":
143
+ main()