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,135 @@
1
+ """多模态对话:图片随问题发给视觉模型(model_router 多模态通道 + /chat/stream images)。
2
+ 全离线:用假 client 捕获实际发出的 messages,验证按 provider 正确组装多模态 content。
3
+ 模型无关:图片附到任意配置的模型,非视觉模型由 router 报错降级(不在此断言真实视觉)。"""
4
+ import os, sys, types, tempfile
5
+ from pathlib import Path
6
+ sys.path.insert(0, str(Path(__file__).parent.parent))
7
+ import pytest
8
+
9
+ def test_multimodal():
10
+ # ISOLATION_GUARD:本用例依赖 import 时固化的模块级单例(网关/限流器/路由配置按 env 在 import 时定型),单进程全量跑会被前序用例污染 →
11
+ # 假红。全量请用 tests/run_all.py(每文件独立子进程)。
12
+ if "foxclaw.core.model_router" in sys.modules:
13
+ pytest.skip("需独立进程运行(模块级单例已固化),请用 tests/run_all.py")
14
+ for k in ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "DEEPSEEK_API_KEY"):
15
+ os.environ.pop(k, None)
16
+ os.environ["HKC_EMBEDDING"] = "stub"
17
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
18
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_mm_")
19
+
20
+ from foxclaw.core.model_router import ModelRouter
21
+
22
+ print("=" * 60); print("多模态对话通道验证"); print("=" * 60)
23
+
24
+ IMG = [{"media_type": "image/png", "data": "AAAABBBB"}]
25
+ BASE = [{"role": "user", "content": "这是什么?"}]
26
+
27
+ # [1] anthropic 组装:image/base64 block
28
+ a = ModelRouter._attach_images("anthropic", BASE, IMG)
29
+ blocks = a[-1]["content"]
30
+ assert isinstance(blocks, list) and blocks[0] == {"type": "text", "text": "这是什么?"}, blocks
31
+ assert blocks[1]["type"] == "image" and blocks[1]["source"]["type"] == "base64" \
32
+ and blocks[1]["source"]["media_type"] == "image/png" and blocks[1]["source"]["data"] == "AAAABBBB", blocks
33
+ assert BASE[-1]["content"] == "这是什么?", "不得改动原 messages(tier/stub/meter 仍需文本)"
34
+ print("[1] anthropic 多模态 block 组装 OK")
35
+
36
+ # [2] openai 系组装:image_url data URI
37
+ for prov in ("openai", "deepseek", "qwen"):
38
+ o = ModelRouter._attach_images(prov, BASE, IMG)
39
+ blk = o[-1]["content"]
40
+ assert blk[1]["type"] == "image_url" and blk[1]["image_url"]["url"] == "data:image/png;base64,AAAABBBB", (prov, blk)
41
+ print("[2] openai/deepseek/qwen 多模态 image_url 组装 OK")
42
+
43
+ # [3] google/未知 provider:无视觉通道 → 原样(纯文本降级)
44
+ assert ModelRouter._attach_images("google", BASE, IMG) == BASE
45
+ assert ModelRouter._attach_images("whatever", BASE, IMG) == BASE
46
+ print("[3] google/未知 provider 纯文本降级 OK")
47
+
48
+ # [4] 无 user 消息 / 已是多模态 → 不重复组装
49
+ assert ModelRouter._attach_images("anthropic", [{"role": "assistant", "content": "x"}], IMG)[0]["content"] == "x"
50
+ already = [{"role": "user", "content": [{"type": "text", "text": "hi"}]}]
51
+ assert ModelRouter._attach_images("anthropic", already, IMG) == already
52
+ assert ModelRouter._attach_images("anthropic", BASE, None) == BASE
53
+ print("[4] 无 user / 已多模态 / 无图 边界 OK")
54
+
55
+ # [5] _sanitize_images 护栏(数量/大小/类型/非列表)
56
+ from foxclaw.api import _sanitize_images, _MAX_IMAGES, _MAX_IMG_B64
57
+ big = "x" * (_MAX_IMG_B64 + 1)
58
+ many = [{"media_type": "image/png", "data": "a"}] * (_MAX_IMAGES + 3)
59
+ assert len(_sanitize_images(many)) == _MAX_IMAGES, "未限数量"
60
+ assert _sanitize_images([{"media_type": "image/png", "data": big}]) == [], "未限大小"
61
+ assert _sanitize_images([{"media_type": "image/tiff", "data": "a"}]) == [], "未按类型白名单"
62
+ assert _sanitize_images([{"media_type": "image/jpeg", "data": ""}]) == [], "空数据未拒"
63
+ assert _sanitize_images("notalist") == [] and _sanitize_images([1, "x"]) == []
64
+ 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 张
67
+ half = "x" * (8 * 1024 * 1024)
68
+ three = [{"media_type": "image/png", "data": half}] * 3
69
+ assert len(_sanitize_images(three)) == 2, "未按累计总量封顶"
70
+ print("[5] _sanitize_images 护栏(数量/单图/累计总量/类型/归一)OK")
71
+
72
+ # [6] _call_provider 把图片真正带进发给 provider 的 messages(openai 假 client 捕获)
73
+ cap = {}
74
+ def _make_openai():
75
+ def create(model, max_tokens, messages, **kw):
76
+ cap["messages"] = messages
77
+ return types.SimpleNamespace(
78
+ choices=[types.SimpleNamespace(message=types.SimpleNamespace(content="ok"))],
79
+ usage=None)
80
+ return types.SimpleNamespace(chat=types.SimpleNamespace(
81
+ completions=types.SimpleNamespace(create=create)))
82
+ r = ModelRouter()
83
+ out, _ = r._call_provider("openai", _make_openai(), "gpt-4o", "sys", BASE, 100, images=IMG)
84
+ assert out == "ok"
85
+ sent = cap["messages"][-1]["content"] # 最后一条(user)应为多模态列表
86
+ assert isinstance(sent, list) and any(b.get("type") == "image_url" for b in sent), sent
87
+ print("[6] _call_provider 把图片带进 provider 请求 OK")
88
+
89
+ # [7] router.stream(openai) 流式路径也带图(假流式 client 捕获)
90
+ capS = {}
91
+ def _make_openai_stream():
92
+ def create(model, max_tokens, messages, stream=False, **kw):
93
+ capS["messages"] = messages
94
+ chunk = types.SimpleNamespace(choices=[types.SimpleNamespace(
95
+ delta=types.SimpleNamespace(content="hi"))])
96
+ return iter([chunk])
97
+ return types.SimpleNamespace(chat=types.SimpleNamespace(
98
+ completions=types.SimpleNamespace(create=create)))
99
+ r2 = ModelRouter()
100
+ r2.provider = "openai"; r2._client = _make_openai_stream() # 非 None → is_stub 自动为 False
101
+ r2._locked_model = "gpt-4o"
102
+ got = "".join(r2.stream("sys", BASE, images=IMG))
103
+ assert got == "hi", got
104
+ sSent = capS["messages"][-1]["content"]
105
+ assert isinstance(sSent, list) and any(b.get("type") == "image_url" for b in sSent), sSent
106
+ print("[7] router.stream 流式多模态带图 OK")
107
+
108
+ # [8] stub 模式:带图不崩,忽略图片(无视觉时优雅降级)
109
+ r3 = ModelRouter(provider="stub")
110
+ assert r3.is_stub
111
+ sc = r3.complete("sys", BASE, images=IMG)
112
+ assert isinstance(sc, str) and sc, "stub complete 带图应正常返回文本"
113
+ ss = "".join(r3.stream("sys", BASE, images=IMG))
114
+ assert isinstance(ss, str) and ss, "stub stream 带图应正常返回文本"
115
+ print("[8] stub 带图优雅降级(忽略图片,不崩)OK")
116
+
117
+ # [9] /api/chat/stream 端到端:带图 SSE 正常产出(stub),超大图被 sanitize 掉不崩
118
+ from fastapi.testclient import TestClient
119
+ from foxclaw.api import app
120
+ with TestClient(app) as c:
121
+ body = {"task": "看图说话", "agent_id": "supervisor",
122
+ "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)}]} # 超大→被剔
125
+ r = c.post("/api/chat/stream", json=body)
126
+ assert r.status_code == 200, r.status_code
127
+ assert "data:" in r.text, r.text[:200]
128
+ # 无 error 事件(sanitize 把坏图剔掉,剩 1 张合法图走 stub)
129
+ assert '"error"' not in r.text, r.text[:300]
130
+ print("[9] /api/chat/stream 带图端到端(含超大/非法图被剔)OK")
131
+
132
+ print("\n多模态对话通道验证全部通过")
133
+
134
+ if __name__ == "__main__":
135
+ test_multimodal()
@@ -0,0 +1,146 @@
1
+ """智能体层深化·跨域协商闭环:被守门人拒 → 发起方降标准重报 → 再裁决。
2
+
3
+ 原缺口:cross_request 是**一次性**的(路由→拿裁决→return)。「下周去上海出差」→ 行程排程
4
+ → 财务守门人预算不足 → rejected → **到此为止**,用户只拿到一句"被拒绝"。
5
+
6
+ 安全不变量(比功能更重要):
7
+ · 裁决权仍在目标 Agent 的守门人,协商**不得成为绕过手段**
8
+ · 协商失败**维持 rejected**,绝不降级成 needs_confirmation(否则等于开了个点穿守门人的口子)
9
+ · 有界 + 必须真让步(否则无限贴红线试探)
10
+ """
11
+ import os, sys
12
+ from pathlib import Path
13
+ sys.path.insert(0, str(Path(__file__).parent.parent))
14
+
15
+ def test_negotiation():
16
+ os.environ["HKC_EMBEDDING"] = "stub"
17
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
18
+
19
+ from foxclaw.orchestration import CrossAgentRequest, Orchestrator
20
+
21
+ print("=" * 60); print("跨域协商闭环验证"); print("=" * 60)
22
+
23
+
24
+ class _Gate:
25
+ """假目标 Agent:预算 <= limit 才批(模拟财务守门人)。"""
26
+ def __init__(self, limit): self.limit = limit; self.seen = []
27
+ def handle_cross_request(self, req):
28
+ amt = float(req.payload.get("amount", 0))
29
+ self.seen.append(amt)
30
+ if amt <= self.limit:
31
+ req.status = "approved"; req.verdict = f"预算 {amt:.0f} 在额度内"
32
+ else:
33
+ req.status = "rejected"; req.verdict = f"预算不足:申请 {amt:.0f} > 可用 {self.limit:.0f}"
34
+
35
+
36
+ class _Requester:
37
+ """假发起方:每轮降 25%,夹到原始的 floor 倍;到底则认输(与 AdminAgent.concede 同款)。"""
38
+ def __init__(self, floor=0.6): self.floor = floor; self.calls = 0
39
+ def concede(self, req, verdict):
40
+ self.calls += 1
41
+ amt = float(req.payload.get("amount", 0))
42
+ orig = float(req.payload.get("_original_amount") or amt)
43
+ new = max(round(amt * 0.75), round(orig * self.floor))
44
+ if new <= 0 or new >= amt:
45
+ return None
46
+ p = dict(req.payload); p["amount"] = new; p["_original_amount"] = orig
47
+ return p
48
+
49
+
50
+ class _Caps:
51
+ """假能力注册中枢:Orchestrator 经它惰性解析 agents(P1-3 打断对象环)。"""
52
+ def __init__(self, agents): self._a = agents
53
+ def get(self, name): return self._a if name == "agents" else None
54
+
55
+
56
+ def _orch(agents):
57
+ return Orchestrator(_Caps(agents)) # 只测协议,不拉起整个 OS
58
+
59
+
60
+ def _req(amount=8000):
61
+ return CrossAgentRequest(from_agent="admin", to_agent="finance",
62
+ request_type="budget_request", payload={"amount": amount})
63
+
64
+
65
+ # [1] 核心场景:被拒 → 让步 → 通过(原先到 rejected 就结束了)
66
+ gate = _Gate(limit=5000); r = _orch({"finance": gate, "admin": _Requester()})
67
+ req = r.negotiate(_req(8000))
68
+ assert req.status == "approved", (req.status, req.verdict)
69
+ assert req.payload["amount"] <= 5000, req.payload
70
+ assert len(req.negotiation) >= 2, "应发生过至少一轮让步"
71
+ assert gate.seen[0] == 8000 and gate.seen[-1] <= 5000, gate.seen
72
+ print(f"[1] 被拒→让步→通过({' → '.join(str(int(a)) for a in gate.seen)})OK")
73
+
74
+ # [2] 让步记录可追溯(每轮裁决 + 让到多少)
75
+ n = req.negotiation
76
+ assert n[0]["status"] == "rejected" and "conceded_to" in n[0], n[0]
77
+ assert n[-1]["status"] == "approved", n[-1]
78
+ print(f"[2] 协商日志可追溯({len(n)} 轮,含每轮裁决与让步)OK")
79
+
80
+ # [3] 安全:让无可让 → **维持 rejected**,绝不降级成 needs_confirmation
81
+ gate2 = _Gate(limit=100); r2 = _orch({"finance": gate2, "admin": _Requester()})
82
+ req2 = r2.negotiate(_req(8000))
83
+ assert req2.status == "rejected", f"协商失败必须维持 rejected,不得降级: {req2.status}"
84
+ print("[3] 让无可让 → 维持 rejected(不给点穿守门人的口子)OK")
85
+
86
+ # [4] 有界:轮数不超过 max_rounds(防死循环)
87
+ gate3 = _Gate(limit=1); r3 = _orch({"finance": gate3, "admin": _Requester(floor=0.0)})
88
+ req3 = r3.negotiate(_req(8000), max_rounds=3)
89
+ assert len(gate3.seen) <= 3, f"轮数应有界: {gate3.seen}"
90
+ assert req3.status == "rejected"
91
+ print(f"[4] 有界协商(裁决 {len(gate3.seen)} 次 ≤ max_rounds=3)OK")
92
+
93
+ # [5] 必须真让步:concede 返回原样 payload → 立即停(否则无限重复同一请求)
94
+ class _NoOp:
95
+ def concede(self, req, verdict): return dict(req.payload) # 假让步
96
+ gate4 = _Gate(limit=1); r4 = _orch({"finance": gate4, "admin": _NoOp()})
97
+ r4.negotiate(_req(8000), max_rounds=5)
98
+ assert len(gate4.seen) == 1, f"无实质让步应只裁决一次: {gate4.seen}"
99
+ print("[5] 假让步(payload 无变化)→ 立即停,不重复轰炸守门人 OK")
100
+
101
+ # [6] 不让步的 Agent(默认 concede→None)→ 与协商前逐字等价:只跑一轮
102
+ class _Silent:
103
+ def concede(self, req, verdict): return None
104
+ gate5 = _Gate(limit=1); r5 = _orch({"finance": gate5, "admin": _Silent()})
105
+ req5 = r5.negotiate(_req(8000))
106
+ assert len(gate5.seen) == 1 and req5.status == "rejected"
107
+ print("[6] 未实现让步的 Agent → 只跑一轮(零行为变更)OK")
108
+
109
+ # [7] 让步逻辑抛异常 → 停 + 维持原裁决(不因发起方 bug 放行)
110
+ class _Boom:
111
+ def concede(self, req, verdict): raise RuntimeError("concede 崩了")
112
+ gate6 = _Gate(limit=1); r6 = _orch({"finance": gate6, "admin": _Boom()})
113
+ req6 = r6.negotiate(_req(8000))
114
+ assert req6.status == "rejected", "发起方 concede 崩溃不得影响裁决"
115
+ assert any("concede_error" in x for x in req6.negotiation)
116
+ print("[7] 让步逻辑崩溃 → 维持 rejected + 记录错误 OK")
117
+
118
+ # ── 真 OfficeAgent 的让步策略(行政并入办公,领域逻辑仍归领域 Agent)──
119
+ from foxclaw.agents.agents import OfficeAgent
120
+ adm = OfficeAgent.__new__(OfficeAgent)
121
+
122
+ # [8] OfficeAgent(原 AdminAgent):逐轮降档 → 夹到下限(600)→ 到底认输。全程单调下降、必然终止
123
+ p = adm.concede(_req(1000), "预算不足")
124
+ assert p and p["amount"] == 750 and "降标准" in p["_concession"], p
125
+ seq, cur = [1000], _req(1000).payload
126
+ while True: # 走完整条让步链,证明单调且会终止
127
+ nxt = adm.concede(_req(0).__class__(from_agent="admin", to_agent="finance",
128
+ request_type="budget_request", payload=dict(cur)), "拒")
129
+ if nxt is None:
130
+ break
131
+ assert nxt["amount"] < cur["amount"], "让步必须单调下降"
132
+ cur = nxt; seq.append(cur["amount"])
133
+ assert len(seq) < 20, "让步链必须终止"
134
+ assert seq[-1] == 600, f"应走到下限 600(原始 1000 的 60%),实际 {seq}"
135
+ print(f"[8] OfficeAgent 让步链 {seq} → 到下限认输(单调下降·必然终止)OK")
136
+
137
+ # [9] OfficeAgent:非预算请求不插手(回退默认不让步)
138
+ other = CrossAgentRequest(from_agent="admin", to_agent="office",
139
+ request_type="knowledge_request", payload={"q": "x"})
140
+ assert adm.concede(other, "拒") is None
141
+ print("[9] 非 budget_request → 不插手(回退默认)OK")
142
+
143
+ print("\n跨域协商闭环验证全部通过")
144
+
145
+ if __name__ == "__main__":
146
+ test_negotiation()
@@ -0,0 +1,119 @@
1
+ """
2
+ 主管编排测试 — 全局态势 + 跨 Agent 编排
3
+ 核心场景:行程 Agent 规划出差 → 主管发起差旅预算申请 →
4
+ 财务守门人裁决(够不够/要不要确认/否决)。
5
+ 这把三个独立内核真正串成一个有机系统。
6
+ """
7
+ import sys, os, tempfile
8
+ from pathlib import Path
9
+ sys.path.insert(0, str(Path(__file__).parent.parent))
10
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "50"
11
+
12
+ import asyncio
13
+ from foxclaw import Foxclaw
14
+
15
+
16
+ def check(cond, msg):
17
+ if not cond:
18
+ raise AssertionError(f"✗ {msg}")
19
+ print(f" ✓ {msg}")
20
+
21
+
22
+ async def main():
23
+ print("=" * 60)
24
+ print("主管编排 · 全局态势 + 跨 Agent 协调")
25
+ print("=" * 60)
26
+ _data = tempfile.mkdtemp(prefix="foxclaw_orch_") # 系统临时目录,不污染仓库根
27
+ os_ = Foxclaw(data_dir=_data, shadow_rounds=50)
28
+
29
+ # 准备各内核的数据
30
+ fin = os_.finance_runtime
31
+ fin.set_budget("travel", 3000)
32
+ fin.set_balance("default", 5000)
33
+ fin.import_csv(str(Path(__file__).parent.parent / "examples" / "sample_transactions.csv"))
34
+
35
+ learn = os_.learning_runtime
36
+ learn.import_csv(str(Path(__file__).parent.parent / "examples" / "sample_knowledge.csv"))
37
+
38
+ itin = os_.itinerary_runtime
39
+ itin.import_csv(str(Path(__file__).parent.parent / "examples" / "sample_itinerary.csv"))
40
+
41
+ # 1. 全局态势(驾驶舱视图)
42
+ print("\n【1】主管全局态势(驾驶舱视图)")
43
+ sit = os_.supervisor.global_situation()
44
+ print(f" 财务: {sit['finance']}")
45
+ print(f" 学习: {sit['learning']}")
46
+ print(f" 行程: {sit['itinerary']}")
47
+ print(f" 跨域告警 {len(sit['alerts'])} 条:")
48
+ for a in sit["alerts"][:5]:
49
+ print(f" [{a['severity']}] ({a['domain']}) {a['msg']}")
50
+ # "驾驶舱"的意义在于态势**来自各内核的真实数据**,不是空壳。故断言三域计数与导入一致。
51
+ check(sit["finance"]["transactions"] == 13, f"财务态势反映 13 笔导入交易(实为 {sit['finance']['transactions']})")
52
+ check(sit["learning"]["total_items"] == 8, f"学习态势反映 8 个知识点(实为 {sit['learning']['total_items']})")
53
+ check(sit["itinerary"]["waypoints"] == 5, f"行程态势反映 5 个行程点(实为 {sit['itinerary']['waypoints']})")
54
+ sev = {a["severity"] for a in sit["alerts"]}
55
+ check(len(sit["alerts"]) >= 2 and "critical" in sev,
56
+ f"跨域告警被汇总且含 critical(实为 {sorted(sev)})")
57
+
58
+ # 2. 跨 Agent 编排:差旅预算申请(小额,应批准)
59
+ print("\n【2】跨 Agent 编排:行程→财务 差旅预算申请(小额)")
60
+ r1 = os_.supervisor.cross_request(
61
+ from_agent="admin", to_agent="finance", request_type="budget_request",
62
+ payload={"amount": 150, "category": "travel", "entity": "高铁票", "account": "default"},
63
+ reason="出差北京高铁票")
64
+ print(f" 申请: 差旅 150 元 → 状态【{r1['status']}】")
65
+ print(f" 财务裁决: {r1['verdict']}")
66
+ check(r1["status"] == "approved",
67
+ f"小额(150)且在预算内 → 直接批准(实为 {r1['status']})")
68
+
69
+ # 3. 跨 Agent 编排:大额差旅(触发守门人确认)
70
+ print("\n【3】跨 Agent 编排:大额差旅(触发守门人)")
71
+ r2 = os_.supervisor.cross_request(
72
+ from_agent="admin", to_agent="finance", request_type="budget_request",
73
+ payload={"amount": 680, "category": "travel", "entity": "商务酒店", "account": "default"},
74
+ reason="出差住宿3晚")
75
+ print(f" 申请: 差旅 680 元 → 状态【{r2['status']}】")
76
+ print(f" 财务裁决: {r2['verdict']}")
77
+ check(r2["status"] == "needs_confirmation",
78
+ f"大额(680>500) → 转人工确认(实为 {r2['status']})")
79
+ # 金额确实改变结论(不是两条路都走同一个分支)
80
+ check(r1["status"] != r2["status"], "金额不同 → 裁决结论确实不同(150 批准 / 680 待确认)")
81
+
82
+ # 4. 跨 Agent 编排:日程占用申请
83
+ print("\n【4】跨 Agent 编排:日程占用申请→行政检查冲突")
84
+ r3 = os_.supervisor.cross_request(
85
+ from_agent="assistant", to_agent="admin", request_type="schedule_request",
86
+ payload={"start_time": "2026-06-10T10:30:00", "title": "临时电话会"},
87
+ reason="客户临时约电话会")
88
+ print(f" 申请: 10:30 电话会 → 状态【{r3['status']}】")
89
+ print(f" 行政裁决: {r3['verdict']}")
90
+ check(r3["status"] == "needs_confirmation",
91
+ f"与既有行程时段冲突 → 转人工确认(实为 {r3['status']})")
92
+
93
+ # 5. 【P1-0 守护】目标 Agent 不存在时必须 fail-closed
94
+ # 这是回归守卫:此前该分支返回 "approved"(默认放行)= fail-open —— 发起方会拿到
95
+ # "已批准",但**没有任何 Agent 真的处理过这个请求**。若哪天有人把它改回去,这里必须红。
96
+ print("\n【5】目标 Agent 不存在 → 必须转人工(不得默认放行)")
97
+ r4 = os_.supervisor.cross_request(
98
+ from_agent="admin", to_agent="不存在的Agent", request_type="budget_request",
99
+ payload={"amount": 1, "category": "travel", "entity": "x", "account": "default"},
100
+ reason="路由到不存在的目标")
101
+ print(f" 申请 → 状态【{r4['status']}】")
102
+ print(f" 裁决: {r4['verdict']}")
103
+ check(r4["status"] == "needs_confirmation",
104
+ f"目标不存在 → needs_confirmation 而非默认放行(实为 {r4['status']})")
105
+
106
+ import shutil
107
+ shutil.rmtree(_data, ignore_errors=True)
108
+
109
+ print("\n" + "=" * 60)
110
+ print("✓ 主管编排验证完成:全局态势 + 跨 Agent 守门人协调成立")
111
+ print("=" * 60)
112
+
113
+
114
+ def test_orchestration():
115
+ asyncio.run(main())
116
+
117
+
118
+ if __name__ == "__main__":
119
+ asyncio.run(main())
@@ -0,0 +1,109 @@
1
+ """
2
+ 编排层去领域硬编码(P3)测试 —— 全离线。
3
+
4
+ 审计:Orchestrator 的 _handle_budget_request/_handle_schedule_request + gather_situation
5
+ 硬编码了 finance/learning/itinerary 内核内部 API(SRP 违反,编排层懂领域)。
6
+ 修复:领域知识下沉到各领域 Agent(situation()/handle_cross_request()),编排层只做
7
+ 通用聚合(逐个 agent.situation())+ 通用路由(dispatch 到 target.handle_cross_request())。
8
+
9
+ 覆盖:
10
+ 1. 结构去耦:Orchestrator 删除 _handle_* 领域方法;编排层源码零领域内核 API 引用
11
+ 2. 领域逻辑归属 Agent:finance/admin 有 handle_cross_request,base 默认放行
12
+ 3. 行为保持:态势聚合 + 小额批准/大额确认/日程冲突确认(裁决与原一致)
13
+ 4. 通用性:路由到不存在的 Agent → 转人工确认(P1-0 修 fail-open,仍不崩)
14
+ """
15
+ import os, sys, asyncio, tempfile, shutil
16
+ from pathlib import Path
17
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
18
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
19
+ os.environ["FOXCLAW_EMBEDDING_MODEL"] = "foxclaw-nonexistent-fast"
20
+ sys.path.insert(0, str(Path(__file__).parent.parent))
21
+
22
+
23
+ def test_structural_decoupling():
24
+ print("\n【1】结构去耦:编排层无领域方法/无领域内核 API 引用")
25
+ from foxclaw.orchestration import Orchestrator
26
+ assert not hasattr(Orchestrator, "_handle_budget_request"), "编排层不应再有 _handle_budget_request"
27
+ assert not hasattr(Orchestrator, "_handle_schedule_request"), "编排层不应再有 _handle_schedule_request"
28
+ src = Path("foxclaw/orchestration/__init__.py").read_text(encoding="utf-8")
29
+ for banned in ("budget_status", "detect_conflicts", "due_items", "Transaction",
30
+ ".waypoints", ".policy", "review_states"):
31
+ assert banned not in src, f"编排层仍引用领域内核 API: {banned}"
32
+ print(" ✓ 删除 _handle_*;源码零领域内核 API 引用")
33
+
34
+
35
+ def test_domain_logic_in_agents():
36
+ print("\n【2】领域逻辑归属 Agent(situation / handle_cross_request)")
37
+ from foxclaw.agents.agents import FinanceAgent, OfficeAgent
38
+ from foxclaw.agents.base import CognitiveAgent
39
+ assert FinanceAgent.handle_cross_request is not CognitiveAgent.handle_cross_request
40
+ assert OfficeAgent.handle_cross_request is not CognitiveAgent.handle_cross_request
41
+ assert FinanceAgent.situation is not CognitiveAgent.situation
42
+ assert OfficeAgent.situation is not CognitiveAgent.situation
43
+ print(" ✓ finance/admin 覆盖 handle_cross_request;finance/admin/office 覆盖 situation")
44
+
45
+
46
+ def test_behavior_and_generic_routing():
47
+ print("\n【3】行为保持 + 通用路由")
48
+ d = tempfile.mkdtemp(prefix="p3_")
49
+ try:
50
+ from foxclaw import Foxclaw
51
+ os_ = Foxclaw(data_dir=d, shadow_rounds=50)
52
+ fin = os_.finance_runtime
53
+ fin.set_budget("travel", 3000); fin.set_balance("default", 5000)
54
+ fin.import_csv(str(Path("examples/sample_transactions.csv")))
55
+ os_.learning_runtime.import_csv(str(Path("examples/sample_knowledge.csv")))
56
+ os_.itinerary_runtime.import_csv(str(Path("examples/sample_itinerary.csv")))
57
+
58
+ # 态势通用聚合
59
+ sit = os_.supervisor.global_situation()
60
+ assert set(sit["finance"]) >= {"transactions"} and "total_items" in sit["learning"]
61
+ assert "waypoints" in sit["itinerary"]
62
+ print(f" ✓ 态势聚合: fin={sit['finance']}, learn.items={sit['learning']['total_items']}, "
63
+ f"itin.wp={sit['itinerary']['waypoints']}, alerts={len(sit['alerts'])}")
64
+
65
+ # 裁决行为保持(由目标 Agent 守门人裁决)
66
+ r_small = os_.supervisor.cross_request("admin", "finance", "budget_request",
67
+ {"amount": 150, "category": "travel", "entity": "高铁", "account": "default"}, "小额")
68
+ r_big = os_.supervisor.cross_request("admin", "finance", "budget_request",
69
+ {"amount": 680, "category": "travel", "entity": "酒店", "account": "default"}, "大额")
70
+ r_sched = os_.supervisor.cross_request("assistant", "admin", "schedule_request",
71
+ {"start_time": "2026-06-10T10:30:00", "title": "电话会"}, "占日程")
72
+ assert r_small["status"] == "approved", r_small
73
+ assert r_big["status"] in ("needs_confirmation", "rejected"), r_big
74
+ assert r_sched["status"] in ("needs_confirmation", "approved"), r_sched
75
+ print(f" ✓ 小额={r_small['status']} 大额={r_big['status']} 日程={r_sched['status']}(委托目标守门人)")
76
+
77
+ # 通用性:路由到不存在的 Agent → 转人工确认,不崩(P1-0)
78
+ # 原断言锁的是 status == "approved" —— 那等于"没有任何 Agent 处理过,却回报批准",
79
+ # 是 fail-open:发起方会据此继续推进(如拿到"预算已批"去下单)。
80
+ r_unknown = os_.supervisor.cross_request("x", "nonexistent", "budget_request", {}, "未知目标")
81
+ assert r_unknown["status"] == "needs_confirmation", r_unknown
82
+ assert "不存在" in r_unknown["verdict"]
83
+ print(f" ✓ 路由到不存在 Agent → {r_unknown['status']}(不再 fail-open 放行,仍不崩)")
84
+
85
+ # 深审修复:守门人裁决崩溃(畸形 payload)必须 fail-safe → 转人工确认,绝不自动放行
86
+ r_bad = os_.supervisor.cross_request("x", "admin", "schedule_request",
87
+ {"start_time": "不是合法时间"}, "畸形时间")
88
+ assert r_bad["status"] == "needs_confirmation", f"裁决崩溃应 fail-safe,实得 {r_bad}"
89
+ assert "转人工" in r_bad["verdict"]
90
+ print(f" ✓ 畸形 payload 让守门人裁决崩溃 → {r_bad['status']}(fail-safe,不 fail-open 放行)")
91
+ asyncio.run(os_.aclose())
92
+ finally:
93
+ shutil.rmtree(d, ignore_errors=True)
94
+
95
+
96
+ def main():
97
+ print("=" * 60)
98
+ print("编排层去领域硬编码 · 改委托(P3)")
99
+ print("=" * 60)
100
+ test_structural_decoupling()
101
+ test_domain_logic_in_agents()
102
+ test_behavior_and_generic_routing()
103
+ print("\n" + "=" * 60)
104
+ print("✓ P3 验证完成:编排层通用聚合/路由,领域知识归属领域 Agent")
105
+ print("=" * 60)
106
+
107
+
108
+ if __name__ == "__main__":
109
+ main()