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
@@ -13,14 +13,14 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
13
13
  )
14
14
  def test_mcp_server():
15
15
  os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
16
- os.environ["FOXCLAW_SKIP_DOTENV"] = "1" # 用进程环境变量自洽测试,不加载开发机 .env
16
+ os.environ["FOXCLAW_SKIP_DOTENV"] = "1"
17
17
  os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_mcpsrv_")
18
- os.environ["FOXCLAW_API_KEY"] = "mcp-test-key" # ★ 开鉴权,测真实暴露
18
+ os.environ["FOXCLAW_API_KEY"] = "mcp-test-key"
19
19
  os.environ["FOXCLAW_MCP_SERVER"] = "1"
20
20
  os.environ["FOXCLAW_MCP_AGENTS"] = "1"
21
- os.environ["FOXCLAW_MCP_RATE_AGENT"] = "1" # ask_foxclaw 限 1/桶,测限流
21
+ os.environ["FOXCLAW_MCP_RATE_AGENT"] = "1"
22
22
  os.environ.setdefault("FOXCLAW_EMBEDDING_MODEL", "foxclaw-nonexistent-fast")
23
- os.environ["HF_HUB_OFFLINE"] = "1" # 无网环境:跳过 huggingface 联网重试(否则模型加载拖 2 分钟,服务器赶不上就绪窗口)
23
+ os.environ["HF_HUB_OFFLINE"] = "1"
24
24
  os.environ["TRANSFORMERS_OFFLINE"] = "1"
25
25
  import uvicorn
26
26
  import httpx
@@ -35,13 +35,12 @@ def test_mcp_server():
35
35
  daemon=True).start()
36
36
  for _ in range(60):
37
37
  try:
38
- urllib.request.urlopen(f"http://127.0.0.1:{PORT}/health", timeout=2); break # /health 开放
38
+ urllib.request.urlopen(f"http://127.0.0.1:{PORT}/health", timeout=2); break
39
39
  except urllib.error.HTTPError:
40
40
  break
41
41
  except Exception:
42
42
  time.sleep(1)
43
43
 
44
- # [0] 防后门:设了 key,不带/错 key 访问 /api/mcp/ 必须 401(未被鉴权绕过)
45
44
  with httpx.Client(timeout=8) as c:
46
45
  body = {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}
47
46
  hdr = {"Accept": "application/json, text/event-stream"}
@@ -55,7 +54,6 @@ def test_mcp_server():
55
54
  async def probe():
56
55
  from mcp.client.streamable_http import streamablehttp_client
57
56
  from mcp import ClientSession
58
- # 带正确 key 连接
59
57
  async with streamablehttp_client(f"http://127.0.0.1:{PORT}/api/mcp/",
60
58
  headers={"X-API-Key": KEY}) as (r, w, _):
61
59
  async with ClientSession(r, w) as s:
@@ -64,7 +62,7 @@ def test_mcp_server():
64
62
  assert tools == {"knowledge_search", "knowledge_get", "memory_recall", "ask_foxclaw"}, tools
65
63
  print("[1] 带key initialize + list_tools(3 只读 + ask_foxclaw) OK")
66
64
 
67
- res = await s.call_tool("knowledge_search", {"query": "anything", "top_k": 999999}) # 试超大 top_k(应被钳制不崩)
65
+ res = await s.call_tool("knowledge_search", {"query": "anything", "top_k": 999999})
68
66
  assert res.content is not None
69
67
  print("[2] knowledge_search 可调 + top_k 钳制不崩 OK")
70
68
 
@@ -82,7 +80,6 @@ def test_mcp_server():
82
80
  assert "dispatched_to" in txt or "reply" in txt or "error" in txt, txt
83
81
  print("[5] ask_foxclaw Agent 调度可调(返回 reply/dispatched_to) OK")
84
82
 
85
- # [6] 限流:agent 桶设 1,上面已消耗 → 再调应被限流
86
83
  res = await s.call_tool("ask_foxclaw", {"task": "再来一次"})
87
84
  txt = res.content[0].text if res.content else ""
88
85
  assert "rate limited" in txt, f"限流未生效: {txt}"
@@ -18,7 +18,7 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
18
18
  from foxclaw.core.memory_adapter import create_memory_backend
19
19
 
20
20
  QUERY = "quarterly revenue growth and budget forecast analysis"
21
- N_FINANCE = 55 # > 老路径 fetch_k(50),< 压缩阈值(同类型 80)
21
+ N_FINANCE = 55
22
22
 
23
23
 
24
24
  def check(cond, msg):
@@ -36,16 +36,13 @@ def main():
36
36
  print(" · HMR 不可用(降级 Mock),跳过 M4 测试")
37
37
  print("结果:跳过"); return
38
38
 
39
- # office:一条与 query 相关的关键记忆 + 一条无关
40
39
  hmr.ingest("office quarterly revenue growth and budget forecast for planning",
41
40
  type="concept", agent_id="office", title="office-key")
42
41
  hmr.ingest("office meeting room booking schedule for next week",
43
42
  type="concept", agent_id="office", title="office-other")
44
- # finance:大量与 query 强相关记忆(会在全局排序里淹没 office 的记忆)
45
43
  for i in range(N_FINANCE):
46
44
  hmr.ingest(f"finance quarterly revenue growth and budget forecast record {i}",
47
45
  type="concept", agent_id="finance", title=f"fin-{i}")
48
- # 一条共享记忆
49
46
  hmr.ingest("shared team quarterly planning memo", type="concept",
50
47
  agent_id="finance", shared=True, title="shared-memo")
51
48
 
@@ -6,8 +6,6 @@ import pytest
6
6
 
7
7
 
8
8
  def test_metrics():
9
- # ISOLATION_GUARD:本用例依赖 import 时固化的模块级单例(网关/限流器/路由配置按 env 在 import 时定型),单进程全量跑会被前序用例污染 →
10
- # 假红。全量请用 tests/run_all.py(每文件独立子进程)。
11
9
  if "foxclaw.api" in sys.modules:
12
10
  pytest.skip("需独立进程运行(模块级单例已固化),请用 tests/run_all.py")
13
11
  os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
@@ -21,11 +19,9 @@ def test_metrics():
21
19
  print("=" * 60); print("ops 监控验证"); print("=" * 60)
22
20
 
23
21
  with TestClient(app) as c:
24
- # 产生一些请求 + 一次任务(触发 LLM 埋点,stub)
25
22
  c.get("/api/status"); c.get("/api/ahs/skills"); c.get("/health")
26
23
  c.post("/api/task", json={"task": "测试", "agent_id": "supervisor"})
27
24
 
28
- # [1] 深度健康:逐子系统(开放路径,探针无需鉴权;不泄露 provider 名)
29
25
  hd = c.get("/health/ready")
30
26
  assert hd.status_code == 200, hd.status_code
31
27
  j = hd.json()
@@ -33,7 +29,6 @@ def test_metrics():
33
29
  assert j["components"]["router"] in ("ok", "stub", "down"), "router 不应泄露 provider 名"
34
30
  print(f"[1] /health/ready 深度健康({j['status']},{len(j['components'])} 组件,router 不泄露) OK")
35
31
 
36
- # [2] JSON metrics:端点延迟分位 + 错误率
37
32
  m = c.get("/api/metrics").json()
38
33
  assert "uptime_s" in m and m["endpoints"], m
39
34
  ep = next(k for k in m["endpoints"] if "status" in k)
@@ -41,16 +36,13 @@ def test_metrics():
41
36
  assert st["count"] >= 1 and "p95_ms" in st and "error_rate" in st and "avg_ms" in st, st
42
37
  print(f"[2] /api/metrics 端点(p50={st['p50_ms']} p95={st['p95_ms']} err={st['error_rate']}) OK")
43
38
 
44
- # [3] LLM 埋点(stub 也计入调用量)
45
39
  assert m["llm"], f"LLM 指标应非空: {m['llm']}"
46
40
  print(f"[3] LLM 埋点({list(m['llm'])}) OK")
47
41
 
48
- # [4] Prometheus 文本格式
49
42
  p = c.get("/metrics")
50
43
  assert p.status_code == 200 and "foxclaw_requests_total" in p.text and "foxclaw_llm_calls_total" in p.text
51
44
  print("[4] /metrics Prometheus 文本 OK")
52
45
 
53
- # [5] 未匹配路由(404)不撑爆 key:20 个不同 404 路径 → 只 1 个 "unmatched" key
54
46
  for i in range(20):
55
47
  c.get(f"/api/does-not-exist/random-{i}")
56
48
  m2 = c.get("/api/metrics").json()
@@ -16,32 +16,25 @@ def test_multi_chain():
16
16
 
17
17
  print("=" * 60); print("多步跨 Agent 链式协作验证"); print("=" * 60)
18
18
 
19
- # [1] 无前序 → 原样返回(第 1 步不该有前言)
20
19
  assert _with_prior("查餐饮支出", []) == "查餐饮支出"
21
20
  print("[1] 首步无前序上下文(原样)OK")
22
21
 
23
- # [2] 有前序 → 并进任务文本
24
22
  t = _with_prior("据此定预算", [{"step": 1, "agent": "finance", "output": "餐饮 1200 元"}])
25
23
  assert "据此定预算" in t and "餐饮 1200 元" in t and "第1步(finance)" in t, t
26
24
  print("[2] 前序结果并进下一步任务文本 OK")
27
25
 
28
- # [3] 步数封顶:只带最近 N 步(防长链把提示词滚爆)
29
26
  many = [{"step": i, "agent": "a", "output": f"out{i}"} for i in range(1, 9)]
30
27
  t3 = _with_prior("x", many)
31
28
  assert "out8" in t3 and "out6" in t3 and "out5" not in t3, t3
32
29
  assert t3.count("第") - 1 == PRIOR_MAX_STEPS or t3.count("步(") == PRIOR_MAX_STEPS, t3
33
30
  print(f"[3] 只带最近 {PRIOR_MAX_STEPS} 步(更早的不注入)OK")
34
31
 
35
- # [4] 单步字符封顶
36
32
  t4 = _with_prior("x", [{"step": 1, "agent": "a", "output": "y" * 900}])
37
33
  assert "y" * PRIOR_MAX_CHARS in t4 and "y" * (PRIOR_MAX_CHARS + 1) not in t4
38
34
  print(f"[4] 单步输出截断到 {PRIOR_MAX_CHARS} 字符 OK")
39
35
 
40
- # ── 集成:直接驱动 _dispatch_multi,看子 Agent 真实收到了什么 ──
41
36
  from foxclaw import Foxclaw
42
37
 
43
- # 注意:Foxclaw 的 data_dir 是**构造参数**(默认 ./foxclaw_data),不读 FOXCLAW_DATA_DIR
44
- # ——env 是 api.py 读了再传进来的。测试必须显式传临时目录,否则会写进真实用户记忆库。
45
38
  os_ = Foxclaw(data_dir=os.environ["FOXCLAW_DATA_DIR"])
46
39
  sup = os_.supervisor
47
40
 
@@ -58,35 +51,30 @@ def test_multi_chain():
58
51
  adm = StubAgent("admin", "已按 1200 元安排下周预算")
59
52
  subs = {"finance": fin, "admin": adm}
60
53
  sched = sup._schedule_for("t", subs) if hasattr(sup, "_schedule_for") else None
61
- if sched is None: # 兼容:直接拿一个 schedule 对象
54
+ if sched is None:
62
55
  from foxclaw.core.scheduler import SystemContext
63
56
  sched = sup._scheduler.schedule("t", SystemContext(active_agents=0, queue_depth=0, system_load=0.1))
64
57
 
65
58
  routes = [("查本月餐饮支出", "finance"), ("据此安排下周预算", "admin")]
66
59
  out = asyncio.run(sup._dispatch_multi("查餐饮并定预算", routes, sched, "sess1", subs, {}, []))
67
60
 
68
- # [5] 第 1 步不带前序;第 2 步**收到了**第 1 步的输出 → 链真的接上了
69
61
  s1, s2 = fin.seen[0]["task"], adm.seen[0]["task"]
70
62
  assert "前序步骤结果" not in s1, f"首步不该有前序前言: {s1}"
71
63
  assert "本月餐饮 1200 元" in s2, f"第2步应看到第1步结果,实际: {s2}"
72
64
  assert "据此安排下周预算" in s2
73
65
  print("[5] 第2步真实收到第1步输出(链式成立)OK")
74
66
 
75
- # [6] context 里也有结构化 prior_steps(两手准备)
76
67
  ps = adm.seen[0]["context"].get("prior_steps")
77
68
  assert ps and ps[0]["agent"] == "finance" and "1200" in ps[0]["output"], ps
78
69
  assert fin.seen[0]["context"].get("prior_steps") == [], "首步 prior 应为空"
79
70
  print("[6] context.prior_steps 结构化传递 OK")
80
71
 
81
- # [7] 展示用 subtask 保留原始文本(不被前序前言污染 UI)
82
72
  steps = out["result"]["multi_steps"]
83
73
  assert steps[1]["subtask"] == "据此安排下周预算", steps[1]["subtask"]
84
74
  assert "前序步骤结果" not in steps[1]["subtask"]
85
75
  assert out["result"]["dispatched_to"] == "multi" and len(steps) == 2
86
76
  print("[7] UI 展示的 subtask 仍是原始文本(未被污染)OK")
87
77
 
88
- # [8] 【长期运行】入链的输出必须截断:prior 会被复制进**每个**后续步骤的 context,
89
- # 不截断则长链 + 大输出会滚成可观的内存/拷贝开销(渲染本就只用 300 字符)。
90
78
  big = StubAgent("finance", "X" * 5000)
91
79
  subs2 = {"finance": big, "admin": StubAgent("admin", "ok")}
92
80
  asyncio.run(sup._dispatch_multi("t", [("查", "finance"), ("用", "admin")],
@@ -7,8 +7,6 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
7
7
  import pytest
8
8
 
9
9
  def test_multimodal():
10
- # ISOLATION_GUARD:本用例依赖 import 时固化的模块级单例(网关/限流器/路由配置按 env 在 import 时定型),单进程全量跑会被前序用例污染 →
11
- # 假红。全量请用 tests/run_all.py(每文件独立子进程)。
12
10
  if "foxclaw.core.model_router" in sys.modules:
13
11
  pytest.skip("需独立进程运行(模块级单例已固化),请用 tests/run_all.py")
14
12
  for k in ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "DEEPSEEK_API_KEY"):
@@ -24,7 +22,6 @@ def test_multimodal():
24
22
  IMG = [{"media_type": "image/png", "data": "AAAABBBB"}]
25
23
  BASE = [{"role": "user", "content": "这是什么?"}]
26
24
 
27
- # [1] anthropic 组装:image/base64 block
28
25
  a = ModelRouter._attach_images("anthropic", BASE, IMG)
29
26
  blocks = a[-1]["content"]
30
27
  assert isinstance(blocks, list) and blocks[0] == {"type": "text", "text": "这是什么?"}, blocks
@@ -33,26 +30,22 @@ def test_multimodal():
33
30
  assert BASE[-1]["content"] == "这是什么?", "不得改动原 messages(tier/stub/meter 仍需文本)"
34
31
  print("[1] anthropic 多模态 block 组装 OK")
35
32
 
36
- # [2] openai 系组装:image_url data URI
37
33
  for prov in ("openai", "deepseek", "qwen"):
38
34
  o = ModelRouter._attach_images(prov, BASE, IMG)
39
35
  blk = o[-1]["content"]
40
36
  assert blk[1]["type"] == "image_url" and blk[1]["image_url"]["url"] == "data:image/png;base64,AAAABBBB", (prov, blk)
41
37
  print("[2] openai/deepseek/qwen 多模态 image_url 组装 OK")
42
38
 
43
- # [3] google/未知 provider:无视觉通道 → 原样(纯文本降级)
44
39
  assert ModelRouter._attach_images("google", BASE, IMG) == BASE
45
40
  assert ModelRouter._attach_images("whatever", BASE, IMG) == BASE
46
41
  print("[3] google/未知 provider 纯文本降级 OK")
47
42
 
48
- # [4] 无 user 消息 / 已是多模态 → 不重复组装
49
43
  assert ModelRouter._attach_images("anthropic", [{"role": "assistant", "content": "x"}], IMG)[0]["content"] == "x"
50
44
  already = [{"role": "user", "content": [{"type": "text", "text": "hi"}]}]
51
45
  assert ModelRouter._attach_images("anthropic", already, IMG) == already
52
46
  assert ModelRouter._attach_images("anthropic", BASE, None) == BASE
53
47
  print("[4] 无 user / 已多模态 / 无图 边界 OK")
54
48
 
55
- # [5] _sanitize_images 护栏(数量/大小/类型/非列表)
56
49
  from foxclaw.api import _sanitize_images, _MAX_IMAGES, _MAX_IMG_B64
57
50
  big = "x" * (_MAX_IMG_B64 + 1)
58
51
  many = [{"media_type": "image/png", "data": "a"}] * (_MAX_IMAGES + 3)
@@ -62,14 +55,12 @@ def test_multimodal():
62
55
  assert _sanitize_images([{"media_type": "image/jpeg", "data": ""}]) == [], "空数据未拒"
63
56
  assert _sanitize_images("notalist") == [] and _sanitize_images([1, "x"]) == []
64
57
  ok = _sanitize_images([{"media_type": "image/JPEG", "data": "abc"}])
65
- assert ok == [{"media_type": "image/jpeg", "data": "abc"}], ok # 类型小写归一
66
- # 累计总量上限:每张 8MB,第 3 张会使总量 24MB>20MB → 只留前 2 张
58
+ assert ok == [{"media_type": "image/jpeg", "data": "abc"}], ok
67
59
  half = "x" * (8 * 1024 * 1024)
68
60
  three = [{"media_type": "image/png", "data": half}] * 3
69
61
  assert len(_sanitize_images(three)) == 2, "未按累计总量封顶"
70
62
  print("[5] _sanitize_images 护栏(数量/单图/累计总量/类型/归一)OK")
71
63
 
72
- # [6] _call_provider 把图片真正带进发给 provider 的 messages(openai 假 client 捕获)
73
64
  cap = {}
74
65
  def _make_openai():
75
66
  def create(model, max_tokens, messages, **kw):
@@ -82,11 +73,10 @@ def test_multimodal():
82
73
  r = ModelRouter()
83
74
  out, _ = r._call_provider("openai", _make_openai(), "gpt-4o", "sys", BASE, 100, images=IMG)
84
75
  assert out == "ok"
85
- sent = cap["messages"][-1]["content"] # 最后一条(user)应为多模态列表
76
+ sent = cap["messages"][-1]["content"]
86
77
  assert isinstance(sent, list) and any(b.get("type") == "image_url" for b in sent), sent
87
78
  print("[6] _call_provider 把图片带进 provider 请求 OK")
88
79
 
89
- # [7] router.stream(openai) 流式路径也带图(假流式 client 捕获)
90
80
  capS = {}
91
81
  def _make_openai_stream():
92
82
  def create(model, max_tokens, messages, stream=False, **kw):
@@ -97,7 +87,7 @@ def test_multimodal():
97
87
  return types.SimpleNamespace(chat=types.SimpleNamespace(
98
88
  completions=types.SimpleNamespace(create=create)))
99
89
  r2 = ModelRouter()
100
- r2.provider = "openai"; r2._client = _make_openai_stream() # 非 None → is_stub 自动为 False
90
+ r2.provider = "openai"; r2._client = _make_openai_stream()
101
91
  r2._locked_model = "gpt-4o"
102
92
  got = "".join(r2.stream("sys", BASE, images=IMG))
103
93
  assert got == "hi", got
@@ -105,7 +95,6 @@ def test_multimodal():
105
95
  assert isinstance(sSent, list) and any(b.get("type") == "image_url" for b in sSent), sSent
106
96
  print("[7] router.stream 流式多模态带图 OK")
107
97
 
108
- # [8] stub 模式:带图不崩,忽略图片(无视觉时优雅降级)
109
98
  r3 = ModelRouter(provider="stub")
110
99
  assert r3.is_stub
111
100
  sc = r3.complete("sys", BASE, images=IMG)
@@ -114,18 +103,16 @@ def test_multimodal():
114
103
  assert isinstance(ss, str) and ss, "stub stream 带图应正常返回文本"
115
104
  print("[8] stub 带图优雅降级(忽略图片,不崩)OK")
116
105
 
117
- # [9] /api/chat/stream 端到端:带图 SSE 正常产出(stub),超大图被 sanitize 掉不崩
118
106
  from fastapi.testclient import TestClient
119
107
  from foxclaw.api import app
120
108
  with TestClient(app) as c:
121
109
  body = {"task": "看图说话", "agent_id": "supervisor",
122
110
  "images": [{"media_type": "image/png", "data": "iVBORw0KGgo="},
123
- {"media_type": "image/tiff", "data": "bad"}, # 类型不合法→被剔
124
- {"media_type": "image/png", "data": "y" * (_MAX_IMG_B64 + 1)}]} # 超大→被剔
111
+ {"media_type": "image/tiff", "data": "bad"},
112
+ {"media_type": "image/png", "data": "y" * (_MAX_IMG_B64 + 1)}]}
125
113
  r = c.post("/api/chat/stream", json=body)
126
114
  assert r.status_code == 200, r.status_code
127
115
  assert "data:" in r.text, r.text[:200]
128
- # 无 error 事件(sanitize 把坏图剔掉,剩 1 张合法图走 stub)
129
116
  assert '"error"' not in r.text, r.text[:300]
130
117
  print("[9] /api/chat/stream 带图端到端(含超大/非法图被剔)OK")
131
118
 
@@ -54,7 +54,7 @@ def test_negotiation():
54
54
 
55
55
 
56
56
  def _orch(agents):
57
- return Orchestrator(_Caps(agents)) # 只测协议,不拉起整个 OS
57
+ return Orchestrator(_Caps(agents))
58
58
 
59
59
 
60
60
  def _req(amount=8000):
@@ -62,7 +62,6 @@ def test_negotiation():
62
62
  request_type="budget_request", payload={"amount": amount})
63
63
 
64
64
 
65
- # [1] 核心场景:被拒 → 让步 → 通过(原先到 rejected 就结束了)
66
65
  gate = _Gate(limit=5000); r = _orch({"finance": gate, "admin": _Requester()})
67
66
  req = r.negotiate(_req(8000))
68
67
  assert req.status == "approved", (req.status, req.verdict)
@@ -71,34 +70,29 @@ def test_negotiation():
71
70
  assert gate.seen[0] == 8000 and gate.seen[-1] <= 5000, gate.seen
72
71
  print(f"[1] 被拒→让步→通过({' → '.join(str(int(a)) for a in gate.seen)})OK")
73
72
 
74
- # [2] 让步记录可追溯(每轮裁决 + 让到多少)
75
73
  n = req.negotiation
76
74
  assert n[0]["status"] == "rejected" and "conceded_to" in n[0], n[0]
77
75
  assert n[-1]["status"] == "approved", n[-1]
78
76
  print(f"[2] 协商日志可追溯({len(n)} 轮,含每轮裁决与让步)OK")
79
77
 
80
- # [3] 安全:让无可让 → **维持 rejected**,绝不降级成 needs_confirmation
81
78
  gate2 = _Gate(limit=100); r2 = _orch({"finance": gate2, "admin": _Requester()})
82
79
  req2 = r2.negotiate(_req(8000))
83
80
  assert req2.status == "rejected", f"协商失败必须维持 rejected,不得降级: {req2.status}"
84
81
  print("[3] 让无可让 → 维持 rejected(不给点穿守门人的口子)OK")
85
82
 
86
- # [4] 有界:轮数不超过 max_rounds(防死循环)
87
83
  gate3 = _Gate(limit=1); r3 = _orch({"finance": gate3, "admin": _Requester(floor=0.0)})
88
84
  req3 = r3.negotiate(_req(8000), max_rounds=3)
89
85
  assert len(gate3.seen) <= 3, f"轮数应有界: {gate3.seen}"
90
86
  assert req3.status == "rejected"
91
87
  print(f"[4] 有界协商(裁决 {len(gate3.seen)} 次 ≤ max_rounds=3)OK")
92
88
 
93
- # [5] 必须真让步:concede 返回原样 payload → 立即停(否则无限重复同一请求)
94
89
  class _NoOp:
95
- def concede(self, req, verdict): return dict(req.payload) # 假让步
90
+ def concede(self, req, verdict): return dict(req.payload)
96
91
  gate4 = _Gate(limit=1); r4 = _orch({"finance": gate4, "admin": _NoOp()})
97
92
  r4.negotiate(_req(8000), max_rounds=5)
98
93
  assert len(gate4.seen) == 1, f"无实质让步应只裁决一次: {gate4.seen}"
99
94
  print("[5] 假让步(payload 无变化)→ 立即停,不重复轰炸守门人 OK")
100
95
 
101
- # [6] 不让步的 Agent(默认 concede→None)→ 与协商前逐字等价:只跑一轮
102
96
  class _Silent:
103
97
  def concede(self, req, verdict): return None
104
98
  gate5 = _Gate(limit=1); r5 = _orch({"finance": gate5, "admin": _Silent()})
@@ -106,7 +100,6 @@ def test_negotiation():
106
100
  assert len(gate5.seen) == 1 and req5.status == "rejected"
107
101
  print("[6] 未实现让步的 Agent → 只跑一轮(零行为变更)OK")
108
102
 
109
- # [7] 让步逻辑抛异常 → 停 + 维持原裁决(不因发起方 bug 放行)
110
103
  class _Boom:
111
104
  def concede(self, req, verdict): raise RuntimeError("concede 崩了")
112
105
  gate6 = _Gate(limit=1); r6 = _orch({"finance": gate6, "admin": _Boom()})
@@ -115,15 +108,13 @@ def test_negotiation():
115
108
  assert any("concede_error" in x for x in req6.negotiation)
116
109
  print("[7] 让步逻辑崩溃 → 维持 rejected + 记录错误 OK")
117
110
 
118
- # ── 真 OfficeAgent 的让步策略(行政并入办公,领域逻辑仍归领域 Agent)──
119
111
  from foxclaw.agents.agents import OfficeAgent
120
112
  adm = OfficeAgent.__new__(OfficeAgent)
121
113
 
122
- # [8] OfficeAgent(原 AdminAgent):逐轮降档 → 夹到下限(600)→ 到底认输。全程单调下降、必然终止
123
114
  p = adm.concede(_req(1000), "预算不足")
124
115
  assert p and p["amount"] == 750 and "降标准" in p["_concession"], p
125
116
  seq, cur = [1000], _req(1000).payload
126
- while True: # 走完整条让步链,证明单调且会终止
117
+ while True:
127
118
  nxt = adm.concede(_req(0).__class__(from_agent="admin", to_agent="finance",
128
119
  request_type="budget_request", payload=dict(cur)), "拒")
129
120
  if nxt is None:
@@ -134,7 +125,6 @@ def test_negotiation():
134
125
  assert seq[-1] == 600, f"应走到下限 600(原始 1000 的 60%),实际 {seq}"
135
126
  print(f"[8] OfficeAgent 让步链 {seq} → 到下限认输(单调下降·必然终止)OK")
136
127
 
137
- # [9] OfficeAgent:非预算请求不插手(回退默认不让步)
138
128
  other = CrossAgentRequest(from_agent="admin", to_agent="office",
139
129
  request_type="knowledge_request", payload={"q": "x"})
140
130
  assert adm.concede(other, "拒") is None
@@ -23,10 +23,9 @@ async def main():
23
23
  print("=" * 60)
24
24
  print("主管编排 · 全局态势 + 跨 Agent 协调")
25
25
  print("=" * 60)
26
- _data = tempfile.mkdtemp(prefix="foxclaw_orch_") # 系统临时目录,不污染仓库根
26
+ _data = tempfile.mkdtemp(prefix="foxclaw_orch_")
27
27
  os_ = Foxclaw(data_dir=_data, shadow_rounds=50)
28
28
 
29
- # 准备各内核的数据
30
29
  fin = os_.finance_runtime
31
30
  fin.set_budget("travel", 3000)
32
31
  fin.set_balance("default", 5000)
@@ -38,7 +37,6 @@ async def main():
38
37
  itin = os_.itinerary_runtime
39
38
  itin.import_csv(str(Path(__file__).parent.parent / "examples" / "sample_itinerary.csv"))
40
39
 
41
- # 1. 全局态势(驾驶舱视图)
42
40
  print("\n【1】主管全局态势(驾驶舱视图)")
43
41
  sit = os_.supervisor.global_situation()
44
42
  print(f" 财务: {sit['finance']}")
@@ -47,7 +45,6 @@ async def main():
47
45
  print(f" 跨域告警 {len(sit['alerts'])} 条:")
48
46
  for a in sit["alerts"][:5]:
49
47
  print(f" [{a['severity']}] ({a['domain']}) {a['msg']}")
50
- # "驾驶舱"的意义在于态势**来自各内核的真实数据**,不是空壳。故断言三域计数与导入一致。
51
48
  check(sit["finance"]["transactions"] == 13, f"财务态势反映 13 笔导入交易(实为 {sit['finance']['transactions']})")
52
49
  check(sit["learning"]["total_items"] == 8, f"学习态势反映 8 个知识点(实为 {sit['learning']['total_items']})")
53
50
  check(sit["itinerary"]["waypoints"] == 5, f"行程态势反映 5 个行程点(实为 {sit['itinerary']['waypoints']})")
@@ -55,7 +52,6 @@ async def main():
55
52
  check(len(sit["alerts"]) >= 2 and "critical" in sev,
56
53
  f"跨域告警被汇总且含 critical(实为 {sorted(sev)})")
57
54
 
58
- # 2. 跨 Agent 编排:差旅预算申请(小额,应批准)
59
55
  print("\n【2】跨 Agent 编排:行程→财务 差旅预算申请(小额)")
60
56
  r1 = os_.supervisor.cross_request(
61
57
  from_agent="admin", to_agent="finance", request_type="budget_request",
@@ -66,7 +62,6 @@ async def main():
66
62
  check(r1["status"] == "approved",
67
63
  f"小额(150)且在预算内 → 直接批准(实为 {r1['status']})")
68
64
 
69
- # 3. 跨 Agent 编排:大额差旅(触发守门人确认)
70
65
  print("\n【3】跨 Agent 编排:大额差旅(触发守门人)")
71
66
  r2 = os_.supervisor.cross_request(
72
67
  from_agent="admin", to_agent="finance", request_type="budget_request",
@@ -76,10 +71,8 @@ async def main():
76
71
  print(f" 财务裁决: {r2['verdict']}")
77
72
  check(r2["status"] == "needs_confirmation",
78
73
  f"大额(680>500) → 转人工确认(实为 {r2['status']})")
79
- # 金额确实改变结论(不是两条路都走同一个分支)
80
74
  check(r1["status"] != r2["status"], "金额不同 → 裁决结论确实不同(150 批准 / 680 待确认)")
81
75
 
82
- # 4. 跨 Agent 编排:日程占用申请
83
76
  print("\n【4】跨 Agent 编排:日程占用申请→行政检查冲突")
84
77
  r3 = os_.supervisor.cross_request(
85
78
  from_agent="assistant", to_agent="admin", request_type="schedule_request",
@@ -90,9 +83,6 @@ async def main():
90
83
  check(r3["status"] == "needs_confirmation",
91
84
  f"与既有行程时段冲突 → 转人工确认(实为 {r3['status']})")
92
85
 
93
- # 5. 【P1-0 守护】目标 Agent 不存在时必须 fail-closed
94
- # 这是回归守卫:此前该分支返回 "approved"(默认放行)= fail-open —— 发起方会拿到
95
- # "已批准",但**没有任何 Agent 真的处理过这个请求**。若哪天有人把它改回去,这里必须红。
96
86
  print("\n【5】目标 Agent 不存在 → 必须转人工(不得默认放行)")
97
87
  r4 = os_.supervisor.cross_request(
98
88
  from_agent="admin", to_agent="不存在的Agent", request_type="budget_request",
@@ -55,14 +55,12 @@ def test_behavior_and_generic_routing():
55
55
  os_.learning_runtime.import_csv(str(Path("examples/sample_knowledge.csv")))
56
56
  os_.itinerary_runtime.import_csv(str(Path("examples/sample_itinerary.csv")))
57
57
 
58
- # 态势通用聚合
59
58
  sit = os_.supervisor.global_situation()
60
59
  assert set(sit["finance"]) >= {"transactions"} and "total_items" in sit["learning"]
61
60
  assert "waypoints" in sit["itinerary"]
62
61
  print(f" ✓ 态势聚合: fin={sit['finance']}, learn.items={sit['learning']['total_items']}, "
63
62
  f"itin.wp={sit['itinerary']['waypoints']}, alerts={len(sit['alerts'])}")
64
63
 
65
- # 裁决行为保持(由目标 Agent 守门人裁决)
66
64
  r_small = os_.supervisor.cross_request("admin", "finance", "budget_request",
67
65
  {"amount": 150, "category": "travel", "entity": "高铁", "account": "default"}, "小额")
68
66
  r_big = os_.supervisor.cross_request("admin", "finance", "budget_request",
@@ -74,15 +72,11 @@ def test_behavior_and_generic_routing():
74
72
  assert r_sched["status"] in ("needs_confirmation", "approved"), r_sched
75
73
  print(f" ✓ 小额={r_small['status']} 大额={r_big['status']} 日程={r_sched['status']}(委托目标守门人)")
76
74
 
77
- # 通用性:路由到不存在的 Agent → 转人工确认,不崩(P1-0)
78
- # 原断言锁的是 status == "approved" —— 那等于"没有任何 Agent 处理过,却回报批准",
79
- # 是 fail-open:发起方会据此继续推进(如拿到"预算已批"去下单)。
80
75
  r_unknown = os_.supervisor.cross_request("x", "nonexistent", "budget_request", {}, "未知目标")
81
76
  assert r_unknown["status"] == "needs_confirmation", r_unknown
82
77
  assert "不存在" in r_unknown["verdict"]
83
78
  print(f" ✓ 路由到不存在 Agent → {r_unknown['status']}(不再 fail-open 放行,仍不崩)")
84
79
 
85
- # 深审修复:守门人裁决崩溃(畸形 payload)必须 fail-safe → 转人工确认,绝不自动放行
86
80
  r_bad = os_.supervisor.cross_request("x", "admin", "schedule_request",
87
81
  {"start_time": "不是合法时间"}, "畸形时间")
88
82
  assert r_bad["status"] == "needs_confirmation", f"裁决崩溃应 fail-safe,实得 {r_bad}"
@@ -30,7 +30,6 @@ def check(cond, label):
30
30
  print(f" ✗ {label}")
31
31
 
32
32
 
33
- # 测试环境自洽:不读 .env,不打外部 API,不写真实数据目录(在任何 foxclaw import 之前)
34
33
  os.environ["FOXCLAW_SKIP_DOTENV"] = "1"
35
34
  os.environ.setdefault("FOXCLAW_EMBEDDING_MODEL", "foxclaw-nonexistent-fast")
36
35
  os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_b7_")
@@ -39,7 +38,6 @@ for _k in ("DEEPSEEK_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY",
39
38
  os.environ[_k] = ""
40
39
 
41
40
 
42
- # ═══════════════ [1][2] P3-1 / P3-8 辅助函数行为 ═══════════════
43
41
  def test_helpers():
44
42
  print("\n【1】P3-1:_constant_time_eq 常量时间比较")
45
43
  from foxclaw.api import _constant_time_eq, _is_guarded_path
@@ -60,7 +58,6 @@ def test_helpers():
60
58
  check(_is_guarded_path("/docs") is False, "/docs 不受保护")
61
59
 
62
60
 
63
- # ═══════════════ [3] P3-1+P3-8 接线防退化(源码级) ═══════════════
64
61
  def test_wiring_source():
65
62
  print("\n【3】P3-1+P3-8 接线防退化:中间件/WS 统一走辅助函数(源码级断言)")
66
63
  import foxclaw.api as api_mod
@@ -83,7 +80,6 @@ def test_wiring_source():
83
80
  check("provided != api_key" not in src_ws, "WS 端点无残留 !=")
84
81
 
85
82
 
86
- # ═══════════════ [4] P3-7 isinstance 异常分类 ═══════════════
87
83
  def _run_ability_with(exc):
88
84
  """用抛指定异常的假 HKC 驱动 knowledge_ability 端点,返回 (status, body)。"""
89
85
  import foxclaw.api as api_mod
@@ -94,8 +90,8 @@ def _run_ability_with(exc):
94
90
  raise exc
95
91
 
96
92
  fake_hkc = SimpleNamespace(available=True, client=_Client())
97
- old_os = api_mod._os_instance
98
- api_mod._os_instance = SimpleNamespace(hkc=fake_hkc)
93
+ old_os = api_mod._state.current_os()
94
+ api_mod._state.set_os_instance(SimpleNamespace(hkc=fake_hkc))
99
95
  try:
100
96
  try:
101
97
  body = asyncio.run(api_mod.knowledge_ability("any_key"))
@@ -106,7 +102,7 @@ def _run_ability_with(exc):
106
102
  return e.status_code, {"detail": e.detail}
107
103
  raise
108
104
  finally:
109
- api_mod._os_instance = old_os
105
+ api_mod._state.set_os_instance(old_os)
110
106
 
111
107
 
112
108
  def test_ability_isinstance():
@@ -115,12 +111,10 @@ def test_ability_isinstance():
115
111
 
116
112
  check(api_mod._HKCInsuffCov is not None, "vendor 下 hkc_sdk.exceptions 可 import")
117
113
 
118
- # a) 真 HKCInsufficientCoverage → 证据 gate(sufficient=False,非 404)
119
114
  from hkc_sdk.exceptions import HKCInsufficientCoverage
120
115
  try:
121
116
  raise HKCInsufficientCoverage("覆盖不足测试")
122
117
  except HKCInsufficientCoverage as e:
123
- # 尽量补上端点要读的属性(不同版本构造签名可能不同)
124
118
  if not hasattr(e, "missing_skills"):
125
119
  e.missing_skills = ["s1"]
126
120
  if not hasattr(e, "coverage"):
@@ -129,7 +123,6 @@ def test_ability_isinstance():
129
123
  check(status == 200 and body.get("sufficient") is False,
130
124
  f"真 HKC 覆盖不足 → 证据 gate(实为 {status}: {str(body)[:80]})")
131
125
 
132
- # b) 【精确化核心价值】带 missing_skills 属性的**非 HKC** 异常 → 404,不被误吞
133
126
  class _FakeInsuff(Exception):
134
127
  def __init__(self):
135
128
  super().__init__("鸭子异常")
@@ -140,7 +133,6 @@ def test_ability_isinstance():
140
133
  check(status == 404,
141
134
  f"isinstance 模式:鸭子异常不再误判为覆盖不足(实为 {status})")
142
135
 
143
- # c) 回退:_HKCInsuffCov=None(模拟缺 vendor)时 hasattr 鸭子判断仍工作
144
136
  old = api_mod._HKCInsuffCov
145
137
  api_mod._HKCInsuffCov = None
146
138
  try:
@@ -153,7 +145,6 @@ def test_ability_isinstance():
153
145
  api_mod._HKCInsuffCov = old
154
146
 
155
147
 
156
- # ═══════════════ [5][6] P3-5 blocked 短路 + P3-0 system 归因 ═══════════════
157
148
  class _MockHMR:
158
149
  def __init__(self):
159
150
  self.calls = []
@@ -210,7 +201,6 @@ def test_truth_system_attribution():
210
201
  check(kw.get("shared") is True, "仍写共享层")
211
202
 
212
203
 
213
- # ═══════════════ [7] P3-6/P4-1 SystemBus exact ═══════════════
214
204
  def test_system_bus_exact():
215
205
  print("\n【7】P3-6/P4-1:SystemBus exact 参数")
216
206
  from foxclaw.core.system_bus import SystemBus
@@ -252,7 +242,6 @@ def test_system_bus_exact():
252
242
  f"前缀事件 execution.completed.extra 不触发真值链(沉淀 {len(hmr4.calls)} 次)")
253
243
 
254
244
 
255
- # ═══════════════ [8] P5-3 _transition 终态守卫 ═══════════════
256
245
  def test_transition_terminal_guard():
257
246
  print("\n【8】P5-3:ProcessManager._transition 终态守卫")
258
247
  from foxclaw.kernel.process_manager import ProcessManager
@@ -261,12 +250,10 @@ def test_transition_terminal_guard():
261
250
  pm.mark_running(rec.process_id)
262
251
  pm.mark_completed(rec.process_id)
263
252
  n = pm.active_count()
264
- # 终态再 _transition(模拟未来新增的直接调用点)→ 幂等忽略
265
253
  pm._transition(rec, "failed")
266
254
  check(rec.state == "completed", "终态再流转 → 状态不变")
267
255
  check(pm.active_count() == n, "终态再流转 → 活跃计数不重复 -1")
268
256
  check(rec.state_history.count("completed") == 1, "终态再流转 → 历史不追加")
269
- # 非终态的非法跳转仍 fail-fast(行为不变)
270
257
  rec2 = pm.create("t2")
271
258
  rec2.state = "created"
272
259
  try:
@@ -276,7 +263,6 @@ def test_transition_terminal_guard():
276
263
  check(True, "非终态非法跳转(created→completed)仍抛 ValueError")
277
264
 
278
265
 
279
- # ═══════════════ [9] P5-1 reason_code ═══════════════
280
266
  def test_reason_code():
281
267
  print("\n【9】P5-1:PermissionDecision.reason_code 分类")
282
268
  from foxclaw.skills import SkillSpec