foxclaw 0.1.0 → 0.1.3

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 (514) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +4 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +173 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +167 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/CHANGELOG.md +57 -0
  11. package/python/CHANGELOG_CN.md +57 -0
  12. package/python/Dockerfile +64 -0
  13. package/python/LICENSE +21 -0
  14. package/python/MANIFEST.in +14 -0
  15. package/python/README.md +201 -0
  16. package/python/README_CN.md +336 -0
  17. package/python/bench/bm25_audit.py +186 -0
  18. package/python/bench/bm25_regression.py +177 -0
  19. package/python/bench/bm25_stress.py +155 -0
  20. package/python/bench/cd_audit.py +215 -0
  21. package/python/bench/chaos.py +140 -0
  22. package/python/bench/concurrency.py +103 -0
  23. package/python/bench/e2e_real_models.py +94 -0
  24. package/python/bench/llm_retry_test.py +241 -0
  25. package/python/bench/load_test.py +193 -0
  26. package/python/bench/vector_ivf_upgrade.py +128 -0
  27. package/python/docs/CHANNELS.md +195 -0
  28. package/python/docs/GOVERNANCE.md +164 -0
  29. package/python/docs/RELEASE.md +110 -0
  30. package/python/docs/RUN.md +75 -0
  31. package/python/docs/RUN_CN.md +121 -0
  32. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  33. package/python/evals/__init__.py +0 -0
  34. package/python/evals/governance/README.md +61 -0
  35. package/python/evals/governance/__init__.py +0 -0
  36. package/python/evals/governance/cases.py +115 -0
  37. package/python/evals/governance/compare_embeddings.py +94 -0
  38. package/python/evals/governance/convergence.svg +11 -0
  39. package/python/evals/governance/report.json +371 -0
  40. package/python/evals/governance/run_evals.py +368 -0
  41. package/python/examples/governance_middleware_demo.py +72 -0
  42. package/python/examples/langgraph_governance_demo.py +104 -0
  43. package/python/examples/sample_itinerary.csv +6 -0
  44. package/python/examples/sample_knowledge.csv +9 -0
  45. package/python/examples/sample_transactions.csv +14 -0
  46. package/python/foxclaw/__init__.py +408 -0
  47. package/python/foxclaw/agents/__init__.py +12 -0
  48. package/python/foxclaw/agents/agents.py +35 -0
  49. package/python/foxclaw/agents/base.py +794 -0
  50. package/python/foxclaw/agents/impl/__init__.py +1 -0
  51. package/python/foxclaw/agents/impl/_helpers.py +70 -0
  52. package/python/foxclaw/agents/impl/finance.py +231 -0
  53. package/python/foxclaw/agents/impl/office.py +350 -0
  54. package/python/foxclaw/agents/impl/supervisor.py +592 -0
  55. package/python/foxclaw/agents/mixins/_context.py +128 -0
  56. package/python/foxclaw/agents/mixins/augment.py +143 -0
  57. package/python/foxclaw/agents/mixins/llm.py +67 -0
  58. package/python/foxclaw/agents/mixins/memory.py +41 -0
  59. package/python/foxclaw/agents/mixins/preference.py +131 -0
  60. package/python/foxclaw/agents/mixins/prompt.py +71 -0
  61. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  62. package/python/foxclaw/agents/mixins/tool_exec.py +417 -0
  63. package/python/foxclaw/api/__init__.py +224 -0
  64. package/python/foxclaw/api/_state.py +43 -0
  65. package/python/foxclaw/api/bootstrap.py +96 -0
  66. package/python/foxclaw/api/middleware.py +294 -0
  67. package/python/foxclaw/api/routes/ahs.py +404 -0
  68. package/python/foxclaw/api/routes/attachments.py +116 -0
  69. package/python/foxclaw/api/routes/browser.py +35 -0
  70. package/python/foxclaw/api/routes/domain.py +306 -0
  71. package/python/foxclaw/api/routes/knowledge.py +145 -0
  72. package/python/foxclaw/api/routes/llm_admin.py +449 -0
  73. package/python/foxclaw/api/routes/memory.py +105 -0
  74. package/python/foxclaw/api/routes/orchestration.py +39 -0
  75. package/python/foxclaw/api/routes/recovery.py +44 -0
  76. package/python/foxclaw/api/routes/review.py +266 -0
  77. package/python/foxclaw/api/routes/search.py +33 -0
  78. package/python/foxclaw/api/routes/system.py +311 -0
  79. package/python/foxclaw/api/routes/task.py +263 -0
  80. package/python/foxclaw/api/routes/ws.py +64 -0
  81. package/python/foxclaw/api/spine.py +52 -0
  82. package/python/foxclaw/browser/__init__.py +13 -0
  83. package/python/foxclaw/browser/action/__init__.py +1 -0
  84. package/python/foxclaw/browser/action/action_executor.py +50 -0
  85. package/python/foxclaw/browser/action/action_types.py +44 -0
  86. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  87. package/python/foxclaw/browser/action/url_policy.py +143 -0
  88. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  89. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  90. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  91. package/python/foxclaw/browser/driver/__init__.py +1 -0
  92. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  93. package/python/foxclaw/browser/driver/playwright_driver.py +170 -0
  94. package/python/foxclaw/browser/perception/__init__.py +1 -0
  95. package/python/foxclaw/browser/perception/dom_extractor.py +42 -0
  96. package/python/foxclaw/browser/perception/element_indexer.py +56 -0
  97. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  98. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  99. package/python/foxclaw/browser/service.py +144 -0
  100. package/python/foxclaw/browser/session/__init__.py +1 -0
  101. package/python/foxclaw/browser/session/session_manager.py +57 -0
  102. package/python/foxclaw/capability/__init__.py +12 -0
  103. package/python/foxclaw/capability/embedding_service.py +65 -0
  104. package/python/foxclaw/channels/__init__.py +138 -0
  105. package/python/foxclaw/channels/discord.py +175 -0
  106. package/python/foxclaw/channels/dispatcher.py +179 -0
  107. package/python/foxclaw/channels/feishu.py +215 -0
  108. package/python/foxclaw/channels/openclaw_bridge.py +151 -0
  109. package/python/foxclaw/channels/signing.py +187 -0
  110. package/python/foxclaw/channels/telegram.py +120 -0
  111. package/python/foxclaw/channels/weixin.py +215 -0
  112. package/python/foxclaw/cli.py +183 -0
  113. package/python/foxclaw/cognitive/__init__.py +4 -0
  114. package/python/foxclaw/cognitive/reasoner.py +81 -0
  115. package/python/foxclaw/cognitive/state.py +141 -0
  116. package/python/foxclaw/core/__init__.py +9 -0
  117. package/python/foxclaw/core/audit_chain.py +131 -0
  118. package/python/foxclaw/core/audit_sink.py +100 -0
  119. package/python/foxclaw/core/compaction.py +265 -0
  120. package/python/foxclaw/core/config.py +65 -0
  121. package/python/foxclaw/core/confirm_nonce.py +151 -0
  122. package/python/foxclaw/core/dispatch.py +27 -0
  123. package/python/foxclaw/core/envfile.py +48 -0
  124. package/python/foxclaw/core/failure_policy.py +142 -0
  125. package/python/foxclaw/core/gateway.py +81 -0
  126. package/python/foxclaw/core/governance_audit.py +171 -0
  127. package/python/foxclaw/core/idempotency.py +282 -0
  128. package/python/foxclaw/core/injection_guard.py +202 -0
  129. package/python/foxclaw/core/memory_adapter.py +489 -0
  130. package/python/foxclaw/core/metrics.py +88 -0
  131. package/python/foxclaw/core/model_router.py +985 -0
  132. package/python/foxclaw/core/paths.py +103 -0
  133. package/python/foxclaw/core/recovery.py +349 -0
  134. package/python/foxclaw/core/scheduler.py +141 -0
  135. package/python/foxclaw/core/secrets.py +131 -0
  136. package/python/foxclaw/core/sessions.py +155 -0
  137. package/python/foxclaw/core/settings.py +414 -0
  138. package/python/foxclaw/core/shared_write.py +186 -0
  139. package/python/foxclaw/core/steering_queue.py +154 -0
  140. package/python/foxclaw/core/system_bus.py +84 -0
  141. package/python/foxclaw/core/thought_chain.py +316 -0
  142. package/python/foxclaw/core/token_meter.py +231 -0
  143. package/python/foxclaw/core/tools.py +164 -0
  144. package/python/foxclaw/core/tracing.py +375 -0
  145. package/python/foxclaw/core/truth_pipeline.py +110 -0
  146. package/python/foxclaw/core/update_check.py +149 -0
  147. package/python/foxclaw/core/vec_recall_adapter.py +477 -0
  148. package/python/foxclaw/core/verify.py +173 -0
  149. package/python/foxclaw/core/web_search.py +218 -0
  150. package/python/foxclaw/finance/__init__.py +15 -0
  151. package/python/foxclaw/finance/evidence.py +149 -0
  152. package/python/foxclaw/finance/fx.py +98 -0
  153. package/python/foxclaw/finance/graph.py +179 -0
  154. package/python/foxclaw/finance/models.py +146 -0
  155. package/python/foxclaw/finance/nlparse.py +515 -0
  156. package/python/foxclaw/finance/policy.py +301 -0
  157. package/python/foxclaw/finance/reasoner.py +121 -0
  158. package/python/foxclaw/finance/reasoning.py +319 -0
  159. package/python/foxclaw/finance/reconcile.py +175 -0
  160. package/python/foxclaw/finance/runtime.py +500 -0
  161. package/python/foxclaw/governance/__init__.py +24 -0
  162. package/python/foxclaw/governance/middleware.py +176 -0
  163. package/python/foxclaw/harness/__init__.py +17 -0
  164. package/python/foxclaw/harness/_base.py +66 -0
  165. package/python/foxclaw/harness/_policy.py +340 -0
  166. package/python/foxclaw/harness/drift.py +77 -0
  167. package/python/foxclaw/harness/event_bus.py +28 -0
  168. package/python/foxclaw/harness/harness.py +358 -0
  169. package/python/foxclaw/harness/loop_detection.py +145 -0
  170. package/python/foxclaw/itinerary/__init__.py +10 -0
  171. package/python/foxclaw/itinerary/amap_client.py +114 -0
  172. package/python/foxclaw/itinerary/models.py +80 -0
  173. package/python/foxclaw/itinerary/reasoner.py +96 -0
  174. package/python/foxclaw/itinerary/reasoning.py +168 -0
  175. package/python/foxclaw/itinerary/runtime.py +144 -0
  176. package/python/foxclaw/kernel/__init__.py +13 -0
  177. package/python/foxclaw/kernel/capabilities.py +72 -0
  178. package/python/foxclaw/kernel/policy_gate.py +108 -0
  179. package/python/foxclaw/kernel/process_manager.py +186 -0
  180. package/python/foxclaw/kernel/scheduler.py +125 -0
  181. package/python/foxclaw/kernel/sentinel.py +443 -0
  182. package/python/foxclaw/kernel/sentinel_client.py +243 -0
  183. package/python/foxclaw/kernel/spine.py +97 -0
  184. package/python/foxclaw/knowledge/__init__.py +360 -0
  185. package/python/foxclaw/knowledge/truth_crystallizer.py +89 -0
  186. package/python/foxclaw/learning/__init__.py +12 -0
  187. package/python/foxclaw/learning/knowledge_graph.py +144 -0
  188. package/python/foxclaw/learning/models.py +93 -0
  189. package/python/foxclaw/learning/reasoner.py +67 -0
  190. package/python/foxclaw/learning/runtime.py +351 -0
  191. package/python/foxclaw/learning/spaced_repetition.py +91 -0
  192. package/python/foxclaw/mcp_server.py +188 -0
  193. package/python/foxclaw/orchestration/__init__.py +167 -0
  194. package/python/foxclaw/pipeline/__init__.py +23 -0
  195. package/python/foxclaw/pipeline/cognitive_pipeline.py +429 -0
  196. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  197. package/python/foxclaw/pipeline/supervisor_stages.py +201 -0
  198. package/python/foxclaw/planning/__init__.py +17 -0
  199. package/python/foxclaw/planning/llm_planner.py +232 -0
  200. package/python/foxclaw/planning/planner.py +94 -0
  201. package/python/foxclaw/search/__init__.py +24 -0
  202. package/python/foxclaw/search/agent_port.py +190 -0
  203. package/python/foxclaw/search/evidence.py +109 -0
  204. package/python/foxclaw/search/external_search.py +50 -0
  205. package/python/foxclaw/search/hkc_read_port.py +60 -0
  206. package/python/foxclaw/search/internal_search.py +71 -0
  207. package/python/foxclaw/search/models.py +127 -0
  208. package/python/foxclaw/search/planner.py +44 -0
  209. package/python/foxclaw/search/retriever.py +39 -0
  210. package/python/foxclaw/search/runtime.py +127 -0
  211. package/python/foxclaw/search/strategy.py +44 -0
  212. package/python/foxclaw/skills/__init__.py +559 -0
  213. package/python/foxclaw/skills/archive.py +111 -0
  214. package/python/foxclaw/skills/fetch.py +226 -0
  215. package/python/foxclaw/skills/hooks_spec.py +69 -0
  216. package/python/foxclaw/skills/install_cmd.py +216 -0
  217. package/python/foxclaw/skills/lifecycle.py +252 -0
  218. package/python/foxclaw/skills/markdown_skill.py +175 -0
  219. package/python/foxclaw/skills/markdown_store.py +39 -0
  220. package/python/foxclaw/skills/mcp_client.py +184 -0
  221. package/python/foxclaw/skills/mcp_store.py +40 -0
  222. package/python/foxclaw/skills/permission.py +145 -0
  223. package/python/foxclaw/skills/pipeline.py +354 -0
  224. package/python/foxclaw/skills/policy.py +74 -0
  225. package/python/foxclaw/skills/review.py +72 -0
  226. package/python/foxclaw/skills/store.py +57 -0
  227. package/python/frontend/assets/core_bg.jpg +0 -0
  228. package/python/frontend/deck/auth.js +25 -0
  229. package/python/frontend/deck/chat_model_picker.js +483 -0
  230. package/python/frontend/deck/domain.js +276 -0
  231. package/python/frontend/deck/main.js +1806 -0
  232. package/python/frontend/deck/markdown.js +44 -0
  233. package/python/frontend/deck/settings.js +307 -0
  234. package/python/frontend/deck/state.js +5 -0
  235. package/python/frontend/deck/util.js +4 -0
  236. package/python/frontend/deck/visuals.js +190 -0
  237. package/python/frontend/deck.css +855 -0
  238. package/python/frontend/deck.html +686 -0
  239. package/python/frontend/deck_v2.html +341 -0
  240. package/python/frontend/planet/index.html +266 -0
  241. package/python/frontend/planet/planet.css +579 -0
  242. package/python/frontend/planet/planet.js +1436 -0
  243. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  244. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  245. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  246. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  247. package/python/frontend/v3/index.html +15 -0
  248. package/python/frontend/widget_runtime.js +252 -0
  249. package/python/frontend/widgets.js +639 -0
  250. package/python/frontend_src/index.html +14 -0
  251. package/python/frontend_src/package-lock.json +1376 -0
  252. package/python/frontend_src/package.json +19 -0
  253. package/python/frontend_src/src/App.vue +58 -0
  254. package/python/frontend_src/src/api.js +82 -0
  255. package/python/frontend_src/src/components/ChatDock.vue +365 -0
  256. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  257. package/python/frontend_src/src/components/DocViewer.vue +162 -0
  258. package/python/frontend_src/src/components/Inspector.vue +235 -0
  259. package/python/frontend_src/src/components/LeftRail.vue +415 -0
  260. package/python/frontend_src/src/components/MenuPopover.vue +128 -0
  261. package/python/frontend_src/src/components/MessageStream.vue +389 -0
  262. package/python/frontend_src/src/components/ModelPicker.vue +350 -0
  263. package/python/frontend_src/src/components/QuickIngest.vue +112 -0
  264. package/python/frontend_src/src/components/Stage.vue +272 -0
  265. package/python/frontend_src/src/components/ToolMenu.vue +141 -0
  266. package/python/frontend_src/src/components/TopBar.vue +118 -0
  267. package/python/frontend_src/src/components/panels/ActivityPanel.vue +82 -0
  268. package/python/frontend_src/src/components/panels/AgentsPanel.vue +83 -0
  269. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +425 -0
  270. package/python/frontend_src/src/components/panels/MemoryPanel.vue +117 -0
  271. package/python/frontend_src/src/components/panels/PanelBox.vue +50 -0
  272. package/python/frontend_src/src/components/panels/ReviewPanel.vue +292 -0
  273. package/python/frontend_src/src/components/panels/SettingsPanel.vue +358 -0
  274. package/python/frontend_src/src/components/panels/TasksPanel.vue +73 -0
  275. package/python/frontend_src/src/components/panels/ToolsPanel.vue +746 -0
  276. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  277. package/python/frontend_src/src/lib/fmt.js +44 -0
  278. package/python/frontend_src/src/lib/i18n.js +53 -0
  279. package/python/frontend_src/src/lib/locales/en.js +173 -0
  280. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  281. package/python/frontend_src/src/lib/md.js +190 -0
  282. package/python/frontend_src/src/main.js +9 -0
  283. package/python/frontend_src/src/stores/app.js +91 -0
  284. package/python/frontend_src/src/stores/chat.js +450 -0
  285. package/python/frontend_src/src/stores/model.js +96 -0
  286. package/python/frontend_src/src/stores/panels.js +289 -0
  287. package/python/frontend_src/src/stores/profile.js +31 -0
  288. package/python/frontend_src/src/stores/system.js +25 -0
  289. package/python/frontend_src/src/styles/base.css +138 -0
  290. package/python/frontend_src/src/styles/panel.css +117 -0
  291. package/python/frontend_src/src/styles/tokens.css +78 -0
  292. package/python/frontend_src/vite.config.js +39 -0
  293. package/python/pyproject.toml +268 -0
  294. package/python/requirements.txt +311 -0
  295. package/python/skills/README.md +123 -0
  296. package/python/skills/execution-contract/SKILL.md +30 -0
  297. package/python/skills/general-assistant/SKILL.md +34 -0
  298. package/python/skills/planner/SKILL.md +33 -0
  299. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  300. package/python/skills/skill-composition/SKILL.md +31 -0
  301. package/python/skills/skill-guard/SKILL.md +67 -0
  302. package/python/skills/skill-memory/SKILL.md +50 -0
  303. package/python/skills/skill-router/SKILL.md +35 -0
  304. package/python/tests/conftest.py +54 -0
  305. package/python/tests/run_all.py +200 -0
  306. package/python/tests/test_agent_lifecycle.py +326 -0
  307. package/python/tests/test_ahs_p2.py +393 -0
  308. package/python/tests/test_anthropic_tools.py +192 -0
  309. package/python/tests/test_api.py +97 -0
  310. package/python/tests/test_api_auth.py +79 -0
  311. package/python/tests/test_api_host_guard.py +69 -0
  312. package/python/tests/test_attachments.py +151 -0
  313. package/python/tests/test_audit_chain.py +116 -0
  314. package/python/tests/test_audit_sink.py +130 -0
  315. package/python/tests/test_browser.py +149 -0
  316. package/python/tests/test_browser_request_guard.py +180 -0
  317. package/python/tests/test_capabilities.py +152 -0
  318. package/python/tests/test_channel_signing.py +185 -0
  319. package/python/tests/test_channel_trust.py +109 -0
  320. package/python/tests/test_channels.py +174 -0
  321. package/python/tests/test_cli.py +46 -0
  322. package/python/tests/test_compaction_toolpair.py +119 -0
  323. package/python/tests/test_config_layering.py +61 -0
  324. package/python/tests/test_confirm_nonce.py +186 -0
  325. package/python/tests/test_context_compaction_wiring.py +124 -0
  326. package/python/tests/test_dispatch.py +89 -0
  327. package/python/tests/test_domain_evals.py +334 -0
  328. package/python/tests/test_domain_import.py +72 -0
  329. package/python/tests/test_domain_reasoners.py +134 -0
  330. package/python/tests/test_e2e.py +161 -0
  331. package/python/tests/test_emb_cache.py +97 -0
  332. package/python/tests/test_embedding_singleton.py +87 -0
  333. package/python/tests/test_envfile.py +83 -0
  334. package/python/tests/test_evals_smoke.py +90 -0
  335. package/python/tests/test_event_spine.py +174 -0
  336. package/python/tests/test_evidence_semantic.py +88 -0
  337. package/python/tests/test_evidence_sse.py +94 -0
  338. package/python/tests/test_file_ingest.py +135 -0
  339. package/python/tests/test_finance.py +144 -0
  340. package/python/tests/test_finance_ledger.py +245 -0
  341. package/python/tests/test_finance_persist.py +174 -0
  342. package/python/tests/test_gateway.py +125 -0
  343. package/python/tests/test_governance.py +421 -0
  344. package/python/tests/test_governance_audit.py +324 -0
  345. package/python/tests/test_governance_middleware.py +137 -0
  346. package/python/tests/test_hkc.py +268 -0
  347. package/python/tests/test_hkc_index_build_manager.py +306 -0
  348. package/python/tests/test_hkc_manifest_reuse.py +294 -0
  349. package/python/tests/test_hkc_search_event_sync.py +482 -0
  350. package/python/tests/test_import_path_guard.py +155 -0
  351. package/python/tests/test_injection_guard.py +193 -0
  352. package/python/tests/test_integration.py +117 -0
  353. package/python/tests/test_itinerary.py +100 -0
  354. package/python/tests/test_kee_semantic.py +84 -0
  355. package/python/tests/test_kernel.py +212 -0
  356. package/python/tests/test_learning.py +101 -0
  357. package/python/tests/test_llm_admin.py +145 -0
  358. package/python/tests/test_llm_planner.py +344 -0
  359. package/python/tests/test_markdown_skills.py +62 -0
  360. package/python/tests/test_markdown_skills_openclaw.py +66 -0
  361. package/python/tests/test_mcp_connectors.py +97 -0
  362. package/python/tests/test_mcp_guardian_discovery.py +87 -0
  363. package/python/tests/test_mcp_no_memory.py +43 -0
  364. package/python/tests/test_mcp_policy_gate.py +76 -0
  365. package/python/tests/test_mcp_server.py +92 -0
  366. package/python/tests/test_memory_recall.py +92 -0
  367. package/python/tests/test_metrics.py +58 -0
  368. package/python/tests/test_multi_chain.py +90 -0
  369. package/python/tests/test_multimodal.py +122 -0
  370. package/python/tests/test_negotiation.py +136 -0
  371. package/python/tests/test_orchestration.py +109 -0
  372. package/python/tests/test_orchestration_decoupled.py +103 -0
  373. package/python/tests/test_p3_p5_hardening.py +324 -0
  374. package/python/tests/test_parallel_dispatch.py +207 -0
  375. package/python/tests/test_pipeline.py +492 -0
  376. package/python/tests/test_plan_visible.py +86 -0
  377. package/python/tests/test_planner.py +88 -0
  378. package/python/tests/test_policy_gate.py +108 -0
  379. package/python/tests/test_profile.py +67 -0
  380. package/python/tests/test_readme_claims.py +163 -0
  381. package/python/tests/test_reasoner.py +170 -0
  382. package/python/tests/test_release_check.py +252 -0
  383. package/python/tests/test_resume_checkpoint.py +148 -0
  384. package/python/tests/test_routing.py +85 -0
  385. package/python/tests/test_scheduler.py +138 -0
  386. package/python/tests/test_search.py +194 -0
  387. package/python/tests/test_secrets.py +153 -0
  388. package/python/tests/test_semantic.py +69 -0
  389. package/python/tests/test_sessions.py +90 -0
  390. package/python/tests/test_settings.py +265 -0
  391. package/python/tests/test_shadow_learning.py +290 -0
  392. package/python/tests/test_shared_write.py +200 -0
  393. package/python/tests/test_skill_taint_gate.py +145 -0
  394. package/python/tests/test_skills.py +117 -0
  395. package/python/tests/test_skills_fetch.py +282 -0
  396. package/python/tests/test_stats_and_events.py +162 -0
  397. package/python/tests/test_streaming.py +55 -0
  398. package/python/tests/test_supervisor_pipeline.py +176 -0
  399. package/python/tests/test_supervisor_triage.py +102 -0
  400. package/python/tests/test_taint.py +112 -0
  401. package/python/tests/test_task_stream.py +74 -0
  402. package/python/tests/test_thinking_sse.py +66 -0
  403. package/python/tests/test_thinking_stream.py +76 -0
  404. package/python/tests/test_tool_center.py +113 -0
  405. package/python/tests/test_tool_parallel.py +113 -0
  406. package/python/tests/test_tool_params.py +99 -0
  407. package/python/tests/test_tool_use.py +229 -0
  408. package/python/tests/test_toplevel_concurrency.py +116 -0
  409. package/python/tests/test_tracing.py +256 -0
  410. package/python/tests/test_truth_crystallizer.py +125 -0
  411. package/python/tests/test_truth_flow.py +88 -0
  412. package/python/tests/test_update_check.py +95 -0
  413. package/python/tests/test_url_policy.py +53 -0
  414. package/python/tests/test_verify_evidence.py +212 -0
  415. package/python/tests/test_version_sync.py +100 -0
  416. package/python/tests/test_web_trigger.py +82 -0
  417. package/python/tools/check_faiss_type.py +30 -0
  418. package/python/tools/check_log.ps1 +38 -0
  419. package/python/tools/ivf_status.py +72 -0
  420. package/python/tools/mypy_baseline.txt +13 -0
  421. package/python/tools/mypy_ratchet.py +143 -0
  422. package/python/tools/pack.py +173 -0
  423. package/python/tools/release_check.py +349 -0
  424. package/python/tools/restart_foxclaw.ps1 +71 -0
  425. package/python/tools/shared_write_audit.py +190 -0
  426. package/python/tools/size_baseline.txt +28 -0
  427. package/python/tools/size_check.py +102 -0
  428. package/python/tools/strip_comments.py +228 -0
  429. package/python/tools/verify_env.bat +2 -0
  430. package/python/tools/verify_env.py +25 -0
  431. package/python/vendor/HMR_LICENSE +37 -0
  432. package/python/vendor/HMR_README.md +65 -0
  433. package/python/vendor/hkc/LICENSE +37 -0
  434. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  435. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  436. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  437. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  438. package/python/vendor/hkc/hkc_api/container.py +416 -0
  439. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  440. package/python/vendor/hkc/hkc_api/main.py +157 -0
  441. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  442. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  443. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  444. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  445. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  446. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  447. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  448. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  449. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  450. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  451. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  452. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  453. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  454. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  455. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  456. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  457. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  458. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  459. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  460. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  461. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  462. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  463. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  464. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  465. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  466. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  467. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  468. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  469. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  470. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  471. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  472. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  473. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  474. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  475. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  476. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  477. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  478. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  479. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  480. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  481. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  482. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  483. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  484. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  485. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  486. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  487. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  488. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  489. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  490. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  491. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  492. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  493. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  494. package/python/vendor/hmr/__init__.py +0 -0
  495. package/python/vendor/hmr/core/__init__.py +0 -0
  496. package/python/vendor/hmr/core/hmr.py +684 -0
  497. package/python/vendor/hmr/core/models.py +310 -0
  498. package/python/vendor/hmr/engines/__init__.py +0 -0
  499. package/python/vendor/hmr/engines/evolution.py +710 -0
  500. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  501. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  502. package/python/vendor/hmr/engines/policy.py +625 -0
  503. package/python/vendor/hmr/engines/recall.py +260 -0
  504. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  505. package/python/vendor/hmr/engines/scheduler.py +323 -0
  506. package/python/vendor/hmr/engines/semantic.py +150 -0
  507. package/python/vendor/hmr/engines/temporal.py +261 -0
  508. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  509. package/python/vendor/hmr/graph/__init__.py +0 -0
  510. package/python/vendor/hmr/graph/cwg.py +338 -0
  511. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  512. package/python/vendor/hmr/storage/__init__.py +0 -0
  513. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  514. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,136 @@
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))
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
+ gate = _Gate(limit=5000); r = _orch({"finance": gate, "admin": _Requester()})
66
+ req = r.negotiate(_req(8000))
67
+ assert req.status == "approved", (req.status, req.verdict)
68
+ assert req.payload["amount"] <= 5000, req.payload
69
+ assert len(req.negotiation) >= 2, "应发生过至少一轮让步"
70
+ assert gate.seen[0] == 8000 and gate.seen[-1] <= 5000, gate.seen
71
+ print(f"[1] 被拒→让步→通过({' → '.join(str(int(a)) for a in gate.seen)})OK")
72
+
73
+ n = req.negotiation
74
+ assert n[0]["status"] == "rejected" and "conceded_to" in n[0], n[0]
75
+ assert n[-1]["status"] == "approved", n[-1]
76
+ print(f"[2] 协商日志可追溯({len(n)} 轮,含每轮裁决与让步)OK")
77
+
78
+ gate2 = _Gate(limit=100); r2 = _orch({"finance": gate2, "admin": _Requester()})
79
+ req2 = r2.negotiate(_req(8000))
80
+ assert req2.status == "rejected", f"协商失败必须维持 rejected,不得降级: {req2.status}"
81
+ print("[3] 让无可让 → 维持 rejected(不给点穿守门人的口子)OK")
82
+
83
+ gate3 = _Gate(limit=1); r3 = _orch({"finance": gate3, "admin": _Requester(floor=0.0)})
84
+ req3 = r3.negotiate(_req(8000), max_rounds=3)
85
+ assert len(gate3.seen) <= 3, f"轮数应有界: {gate3.seen}"
86
+ assert req3.status == "rejected"
87
+ print(f"[4] 有界协商(裁决 {len(gate3.seen)} 次 ≤ max_rounds=3)OK")
88
+
89
+ class _NoOp:
90
+ def concede(self, req, verdict): return dict(req.payload)
91
+ gate4 = _Gate(limit=1); r4 = _orch({"finance": gate4, "admin": _NoOp()})
92
+ r4.negotiate(_req(8000), max_rounds=5)
93
+ assert len(gate4.seen) == 1, f"无实质让步应只裁决一次: {gate4.seen}"
94
+ print("[5] 假让步(payload 无变化)→ 立即停,不重复轰炸守门人 OK")
95
+
96
+ class _Silent:
97
+ def concede(self, req, verdict): return None
98
+ gate5 = _Gate(limit=1); r5 = _orch({"finance": gate5, "admin": _Silent()})
99
+ req5 = r5.negotiate(_req(8000))
100
+ assert len(gate5.seen) == 1 and req5.status == "rejected"
101
+ print("[6] 未实现让步的 Agent → 只跑一轮(零行为变更)OK")
102
+
103
+ class _Boom:
104
+ def concede(self, req, verdict): raise RuntimeError("concede 崩了")
105
+ gate6 = _Gate(limit=1); r6 = _orch({"finance": gate6, "admin": _Boom()})
106
+ req6 = r6.negotiate(_req(8000))
107
+ assert req6.status == "rejected", "发起方 concede 崩溃不得影响裁决"
108
+ assert any("concede_error" in x for x in req6.negotiation)
109
+ print("[7] 让步逻辑崩溃 → 维持 rejected + 记录错误 OK")
110
+
111
+ from foxclaw.agents.agents import OfficeAgent
112
+ adm = OfficeAgent.__new__(OfficeAgent)
113
+
114
+ p = adm.concede(_req(1000), "预算不足")
115
+ assert p and p["amount"] == 750 and "降标准" in p["_concession"], p
116
+ seq, cur = [1000], _req(1000).payload
117
+ while True:
118
+ nxt = adm.concede(_req(0).__class__(from_agent="admin", to_agent="finance",
119
+ request_type="budget_request", payload=dict(cur)), "拒")
120
+ if nxt is None:
121
+ break
122
+ assert nxt["amount"] < cur["amount"], "让步必须单调下降"
123
+ cur = nxt; seq.append(cur["amount"])
124
+ assert len(seq) < 20, "让步链必须终止"
125
+ assert seq[-1] == 600, f"应走到下限 600(原始 1000 的 60%),实际 {seq}"
126
+ print(f"[8] OfficeAgent 让步链 {seq} → 到下限认输(单调下降·必然终止)OK")
127
+
128
+ other = CrossAgentRequest(from_agent="admin", to_agent="office",
129
+ request_type="knowledge_request", payload={"q": "x"})
130
+ assert adm.concede(other, "拒") is None
131
+ print("[9] 非 budget_request → 不插手(回退默认)OK")
132
+
133
+ print("\n跨域协商闭环验证全部通过")
134
+
135
+ if __name__ == "__main__":
136
+ test_negotiation()
@@ -0,0 +1,109 @@
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
+ fin = os_.finance_runtime
30
+ fin.set_budget("travel", 3000)
31
+ fin.set_balance("default", 5000)
32
+ fin.import_csv(str(Path(__file__).parent.parent / "examples" / "sample_transactions.csv"))
33
+
34
+ learn = os_.learning_runtime
35
+ learn.import_csv(str(Path(__file__).parent.parent / "examples" / "sample_knowledge.csv"))
36
+
37
+ itin = os_.itinerary_runtime
38
+ itin.import_csv(str(Path(__file__).parent.parent / "examples" / "sample_itinerary.csv"))
39
+
40
+ print("\n【1】主管全局态势(驾驶舱视图)")
41
+ sit = os_.supervisor.global_situation()
42
+ print(f" 财务: {sit['finance']}")
43
+ print(f" 学习: {sit['learning']}")
44
+ print(f" 行程: {sit['itinerary']}")
45
+ print(f" 跨域告警 {len(sit['alerts'])} 条:")
46
+ for a in sit["alerts"][:5]:
47
+ print(f" [{a['severity']}] ({a['domain']}) {a['msg']}")
48
+ check(sit["finance"]["transactions"] == 13, f"财务态势反映 13 笔导入交易(实为 {sit['finance']['transactions']})")
49
+ check(sit["learning"]["total_items"] == 8, f"学习态势反映 8 个知识点(实为 {sit['learning']['total_items']})")
50
+ check(sit["itinerary"]["waypoints"] == 5, f"行程态势反映 5 个行程点(实为 {sit['itinerary']['waypoints']})")
51
+ sev = {a["severity"] for a in sit["alerts"]}
52
+ check(len(sit["alerts"]) >= 2 and "critical" in sev,
53
+ f"跨域告警被汇总且含 critical(实为 {sorted(sev)})")
54
+
55
+ print("\n【2】跨 Agent 编排:行程→财务 差旅预算申请(小额)")
56
+ r1 = os_.supervisor.cross_request(
57
+ from_agent="admin", to_agent="finance", request_type="budget_request",
58
+ payload={"amount": 150, "category": "travel", "entity": "高铁票", "account": "default"},
59
+ reason="出差北京高铁票")
60
+ print(f" 申请: 差旅 150 元 → 状态【{r1['status']}】")
61
+ print(f" 财务裁决: {r1['verdict']}")
62
+ check(r1["status"] == "approved",
63
+ f"小额(150)且在预算内 → 直接批准(实为 {r1['status']})")
64
+
65
+ print("\n【3】跨 Agent 编排:大额差旅(触发守门人)")
66
+ r2 = os_.supervisor.cross_request(
67
+ from_agent="admin", to_agent="finance", request_type="budget_request",
68
+ payload={"amount": 680, "category": "travel", "entity": "商务酒店", "account": "default"},
69
+ reason="出差住宿3晚")
70
+ print(f" 申请: 差旅 680 元 → 状态【{r2['status']}】")
71
+ print(f" 财务裁决: {r2['verdict']}")
72
+ check(r2["status"] == "needs_confirmation",
73
+ f"大额(680>500) → 转人工确认(实为 {r2['status']})")
74
+ check(r1["status"] != r2["status"], "金额不同 → 裁决结论确实不同(150 批准 / 680 待确认)")
75
+
76
+ print("\n【4】跨 Agent 编排:日程占用申请→行政检查冲突")
77
+ r3 = os_.supervisor.cross_request(
78
+ from_agent="assistant", to_agent="admin", request_type="schedule_request",
79
+ payload={"start_time": "2026-06-10T10:30:00", "title": "临时电话会"},
80
+ reason="客户临时约电话会")
81
+ print(f" 申请: 10:30 电话会 → 状态【{r3['status']}】")
82
+ print(f" 行政裁决: {r3['verdict']}")
83
+ check(r3["status"] == "needs_confirmation",
84
+ f"与既有行程时段冲突 → 转人工确认(实为 {r3['status']})")
85
+
86
+ print("\n【5】目标 Agent 不存在 → 必须转人工(不得默认放行)")
87
+ r4 = os_.supervisor.cross_request(
88
+ from_agent="admin", to_agent="不存在的Agent", request_type="budget_request",
89
+ payload={"amount": 1, "category": "travel", "entity": "x", "account": "default"},
90
+ reason="路由到不存在的目标")
91
+ print(f" 申请 → 状态【{r4['status']}】")
92
+ print(f" 裁决: {r4['verdict']}")
93
+ check(r4["status"] == "needs_confirmation",
94
+ f"目标不存在 → needs_confirmation 而非默认放行(实为 {r4['status']})")
95
+
96
+ import shutil
97
+ shutil.rmtree(_data, ignore_errors=True)
98
+
99
+ print("\n" + "=" * 60)
100
+ print("✓ 主管编排验证完成:全局态势 + 跨 Agent 守门人协调成立")
101
+ print("=" * 60)
102
+
103
+
104
+ def test_orchestration():
105
+ asyncio.run(main())
106
+
107
+
108
+ if __name__ == "__main__":
109
+ asyncio.run(main())
@@ -0,0 +1,103 @@
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
+ sit = os_.supervisor.global_situation()
59
+ assert set(sit["finance"]) >= {"transactions"} and "total_items" in sit["learning"]
60
+ assert "waypoints" in sit["itinerary"]
61
+ print(f" ✓ 态势聚合: fin={sit['finance']}, learn.items={sit['learning']['total_items']}, "
62
+ f"itin.wp={sit['itinerary']['waypoints']}, alerts={len(sit['alerts'])}")
63
+
64
+ r_small = os_.supervisor.cross_request("admin", "finance", "budget_request",
65
+ {"amount": 150, "category": "travel", "entity": "高铁", "account": "default"}, "小额")
66
+ r_big = os_.supervisor.cross_request("admin", "finance", "budget_request",
67
+ {"amount": 680, "category": "travel", "entity": "酒店", "account": "default"}, "大额")
68
+ r_sched = os_.supervisor.cross_request("assistant", "admin", "schedule_request",
69
+ {"start_time": "2026-06-10T10:30:00", "title": "电话会"}, "占日程")
70
+ assert r_small["status"] == "approved", r_small
71
+ assert r_big["status"] in ("needs_confirmation", "rejected"), r_big
72
+ assert r_sched["status"] in ("needs_confirmation", "approved"), r_sched
73
+ print(f" ✓ 小额={r_small['status']} 大额={r_big['status']} 日程={r_sched['status']}(委托目标守门人)")
74
+
75
+ r_unknown = os_.supervisor.cross_request("x", "nonexistent", "budget_request", {}, "未知目标")
76
+ assert r_unknown["status"] == "needs_confirmation", r_unknown
77
+ assert "不存在" in r_unknown["verdict"]
78
+ print(f" ✓ 路由到不存在 Agent → {r_unknown['status']}(不再 fail-open 放行,仍不崩)")
79
+
80
+ r_bad = os_.supervisor.cross_request("x", "admin", "schedule_request",
81
+ {"start_time": "不是合法时间"}, "畸形时间")
82
+ assert r_bad["status"] == "needs_confirmation", f"裁决崩溃应 fail-safe,实得 {r_bad}"
83
+ assert "转人工" in r_bad["verdict"]
84
+ print(f" ✓ 畸形 payload 让守门人裁决崩溃 → {r_bad['status']}(fail-safe,不 fail-open 放行)")
85
+ asyncio.run(os_.aclose())
86
+ finally:
87
+ shutil.rmtree(d, ignore_errors=True)
88
+
89
+
90
+ def main():
91
+ print("=" * 60)
92
+ print("编排层去领域硬编码 · 改委托(P3)")
93
+ print("=" * 60)
94
+ test_structural_decoupling()
95
+ test_domain_logic_in_agents()
96
+ test_behavior_and_generic_routing()
97
+ print("\n" + "=" * 60)
98
+ print("✓ P3 验证完成:编排层通用聚合/路由,领域知识归属领域 Agent")
99
+ print("=" * 60)
100
+
101
+
102
+ if __name__ == "__main__":
103
+ main()
@@ -0,0 +1,324 @@
1
+ """批次 7 · P3 收尾 + P4/P5 小项测试
2
+
3
+ [1] P3-1:_constant_time_eq 行为(等/不等/空/None 兜底)
4
+ [2] P3-8:_is_guarded_path 行为
5
+ [3] P3-1+P3-8 接线防退化:两个中间件 + WS 端点统一走辅助函数(源码级)
6
+ [4] P3-7:knowledge_ability 异常分类——isinstance 精确 / 真 HKC 异常证据 gate / 回退鸭子判断
7
+ [5] P3-5:blocked 任务只留审计 trace + truth.skipped,不发 truth.appended、不沉淀(含正常对照)
8
+ [6] P3-0:真值沉淀 agent_id="system" + origin tag 追溯
9
+ [7] P3-6/P4-1:SystemBus exact 订阅/前缀默认/history exact + TruthPipeline 不被前缀事件干扰
10
+ [8] P5-3:ProcessManager._transition 终态守卫(幂等忽略不重复 -1;非终态非法跳转仍抛)
11
+ [9] P5-1:PermissionDecision.reason_code 五码分类 + 非 ask 留空 + to_dict 携带
12
+ """
13
+ import asyncio
14
+ import inspect
15
+ import os
16
+ import sys
17
+ import tempfile
18
+ from pathlib import Path
19
+
20
+ sys.path.insert(0, str(Path(__file__).parent.parent))
21
+
22
+ FAILS = []
23
+
24
+
25
+ def check(cond, label):
26
+ if cond:
27
+ print(f" ✓ {label}")
28
+ else:
29
+ FAILS.append(label)
30
+ print(f" ✗ {label}")
31
+
32
+
33
+ os.environ["FOXCLAW_SKIP_DOTENV"] = "1"
34
+ os.environ.setdefault("FOXCLAW_EMBEDDING_MODEL", "foxclaw-nonexistent-fast")
35
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_b7_")
36
+ for _k in ("DEEPSEEK_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY",
37
+ "GOOGLE_API_KEY", "DASHSCOPE_API_KEY", "FOXCLAW_API_KEY"):
38
+ os.environ[_k] = ""
39
+
40
+
41
+ def test_helpers():
42
+ print("\n【1】P3-1:_constant_time_eq 常量时间比较")
43
+ from foxclaw.api import _constant_time_eq, _is_guarded_path
44
+ check(_constant_time_eq("abc", "abc") is True, "相同字符串相等")
45
+ check(_constant_time_eq("abc", "abd") is False, "末位不同不等")
46
+ check(_constant_time_eq("abc", "abcd") is False, "长度不同不等")
47
+ check(_constant_time_eq("", "") is True, "双空相等")
48
+ check(_constant_time_eq("", "abc") is False, "空与非空不等")
49
+ check(_constant_time_eq(None, "abc") is False, "None 由 or '' 兜底(不崩)")
50
+ check(_constant_time_eq("密钥含中文", "密钥含中文") is True,
51
+ "非 ASCII(utf-8 编码后比较)可用")
52
+
53
+ print("\n【2】P3-8:_is_guarded_path 受保护路径判断")
54
+ check(_is_guarded_path("/api/status") is True, "/api/* 受保护")
55
+ check(_is_guarded_path("/knowledge/graph") is True, "/knowledge/* 受保护")
56
+ check(_is_guarded_path("/health") is False, "/health 探针不受保护")
57
+ check(_is_guarded_path("/") is False, "/ 前端不受保护")
58
+ check(_is_guarded_path("/docs") is False, "/docs 不受保护")
59
+
60
+
61
+ def test_wiring_source():
62
+ print("\n【3】P3-1+P3-8 接线防退化:中间件/WS 统一走辅助函数(源码级断言)")
63
+ import foxclaw.api as api_mod
64
+
65
+ src_guard = inspect.getsource(api_mod._api_key_guard)
66
+ check("_is_guarded_path(path)" in src_guard, "_api_key_guard 用 _is_guarded_path")
67
+ check("_constant_time_eq(" in src_guard, "_api_key_guard 用 _constant_time_eq")
68
+ check('startswith("/api")' not in src_guard, "_api_key_guard 无残留 startswith 直写")
69
+
70
+ src_gw = inspect.getsource(api_mod._gateway_mw)
71
+ check("_is_guarded_path(path)" in src_gw, "_gateway_mw 用 _is_guarded_path")
72
+ check("_constant_time_eq(hdr, valid)" in src_gw, "_gateway_mw 客户识别用常量时间比较")
73
+ check("hdr == valid" not in src_gw, "_gateway_mw 无残留普通 ==")
74
+
75
+ src_trace = inspect.getsource(api_mod._trace_mw)
76
+ check("_is_guarded_path(path)" in src_trace, "_trace_mw 用 _is_guarded_path")
77
+
78
+ src_ws = inspect.getsource(api_mod.harness_websocket)
79
+ check("_constant_time_eq(" in src_ws, "WS 端点鉴权用 _constant_time_eq")
80
+ check("provided != api_key" not in src_ws, "WS 端点无残留 !=")
81
+
82
+
83
+ def _run_ability_with(exc):
84
+ """用抛指定异常的假 HKC 驱动 knowledge_ability 端点,返回 (status, body)。"""
85
+ import foxclaw.api as api_mod
86
+ from types import SimpleNamespace
87
+
88
+ class _Client:
89
+ def compile_ability(self, key):
90
+ raise exc
91
+
92
+ fake_hkc = SimpleNamespace(available=True, client=_Client())
93
+ old_os = api_mod._state.current_os()
94
+ api_mod._state.set_os_instance(SimpleNamespace(hkc=fake_hkc))
95
+ try:
96
+ try:
97
+ body = asyncio.run(api_mod.knowledge_ability("any_key"))
98
+ return 200, body
99
+ except Exception as e:
100
+ from fastapi import HTTPException
101
+ if isinstance(e, HTTPException):
102
+ return e.status_code, {"detail": e.detail}
103
+ raise
104
+ finally:
105
+ api_mod._state.set_os_instance(old_os)
106
+
107
+
108
+ def test_ability_isinstance():
109
+ print("\n【4】P3-7:knowledge_ability 异常分类(isinstance 精确 + 回退)")
110
+ import foxclaw.api as api_mod
111
+
112
+ check(api_mod._HKCInsuffCov is not None, "vendor 下 hkc_sdk.exceptions 可 import")
113
+
114
+ from hkc_sdk.exceptions import HKCInsufficientCoverage
115
+ try:
116
+ raise HKCInsufficientCoverage("覆盖不足测试")
117
+ except HKCInsufficientCoverage as e:
118
+ if not hasattr(e, "missing_skills"):
119
+ e.missing_skills = ["s1"]
120
+ if not hasattr(e, "coverage"):
121
+ e.coverage = {"a": 0.5}
122
+ status, body = _run_ability_with(e)
123
+ check(status == 200 and body.get("sufficient") is False,
124
+ f"真 HKC 覆盖不足 → 证据 gate(实为 {status}: {str(body)[:80]})")
125
+
126
+ class _FakeInsuff(Exception):
127
+ def __init__(self):
128
+ super().__init__("鸭子异常")
129
+ self.missing_skills = ["s1"]
130
+ self.coverage = {}
131
+
132
+ status, body = _run_ability_with(_FakeInsuff())
133
+ check(status == 404,
134
+ f"isinstance 模式:鸭子异常不再误判为覆盖不足(实为 {status})")
135
+
136
+ old = api_mod._HKCInsuffCov
137
+ api_mod._HKCInsuffCov = None
138
+ try:
139
+ status, body = _run_ability_with(_FakeInsuff())
140
+ check(status == 200 and body.get("sufficient") is False,
141
+ f"import 失败回退 hasattr → 证据 gate(实为 {status})")
142
+ status2, _ = _run_ability_with(RuntimeError("普通错误"))
143
+ check(status2 == 404, "回退模式下普通异常仍 404")
144
+ finally:
145
+ api_mod._HKCInsuffCov = old
146
+
147
+
148
+ class _MockHMR:
149
+ def __init__(self):
150
+ self.calls = []
151
+
152
+ def ingest(self, content, **kw):
153
+ self.calls.append((content, kw))
154
+
155
+
156
+ def _make_pipeline():
157
+ from foxclaw.core.system_bus import SystemBus
158
+ from foxclaw.core.truth_pipeline import TruthPipeline
159
+ bus = SystemBus()
160
+ events = []
161
+ for ch in ("trace.recorded", "truth.appended", "truth.skipped", "memory.updated"):
162
+ bus.subscribe(ch, lambda m, _c=ch: events.append(_c))
163
+ hmr = _MockHMR()
164
+ TruthPipeline(bus, hmr=hmr)
165
+ return bus, events, hmr
166
+
167
+
168
+ def test_truth_blocked_skipped():
169
+ print("\n【5】P3-5:blocked 任务不进真值链")
170
+ bus, events, hmr = _make_pipeline()
171
+ bus.publish("execution.completed", {"agent_id": "finance", "task": "危险操作",
172
+ "result": "x", "blocked": True})
173
+ check("trace.recorded" in events, "blocked 仍发 trace.recorded(审计留痕)")
174
+ check("truth.skipped" in events, "blocked 发 truth.skipped(可观测)")
175
+ check("truth.appended" not in events, "blocked 不发 truth.appended(不进真值链)")
176
+ check("memory.updated" not in events, "blocked 不发 memory.updated")
177
+ check(hmr.calls == [], "blocked 不沉淀 HMR")
178
+
179
+ print(" —— 正常任务对照 ——")
180
+ bus2, events2, hmr2 = _make_pipeline()
181
+ bus2.publish("execution.completed", {"agent_id": "finance", "task": "正常任务",
182
+ "result": "ok", "blocked": False})
183
+ check("truth.appended" in events2, "正常任务发 truth.appended")
184
+ check("truth.skipped" not in events2, "正常任务不发 truth.skipped")
185
+ check(len(hmr2.calls) == 1, "正常任务沉淀 HMR ×1")
186
+
187
+
188
+ def test_truth_system_attribution():
189
+ print("\n【6】P3-0:真值沉淀系统级归因")
190
+ bus, events, hmr = _make_pipeline()
191
+ bus.publish("execution.completed", {"agent_id": "finance", "task": "预算分析",
192
+ "result": "正常", "blocked": False})
193
+ check(len(hmr.calls) == 1, "真值沉淀 1 次")
194
+ kw = hmr.calls[0][1]
195
+ check(kw.get("agent_id") == "system",
196
+ f"写共享层 agent_id=system(实为 {kw.get('agent_id')!r})")
197
+ check("origin:finance" in (kw.get("tags") or []),
198
+ "来源 Agent 保留在 origin tag")
199
+ check("truth_pipeline" in (kw.get("tags") or []),
200
+ "truth_pipeline facet 保留(策略豁免通道)")
201
+ check(kw.get("shared") is True, "仍写共享层")
202
+
203
+
204
+ def test_system_bus_exact():
205
+ print("\n【7】P3-6/P4-1:SystemBus exact 参数")
206
+ from foxclaw.core.system_bus import SystemBus
207
+
208
+ bus = SystemBus()
209
+ received = []
210
+ bus.subscribe("execution.completed", lambda m: received.append(m.topic), exact=True)
211
+ bus.publish("execution.completed", {})
212
+ bus.publish("execution.completed.extra", {})
213
+ bus.publish("execution.failed", {})
214
+ check(received == ["execution.completed"],
215
+ f"exact=True 只收精确 topic(实为 {received})")
216
+
217
+ bus2 = SystemBus()
218
+ received2 = []
219
+ bus2.subscribe("execution", lambda m: received2.append(m.topic))
220
+ bus2.publish("execution.completed", {})
221
+ bus2.publish("execution.failed", {})
222
+ check(set(received2) == {"execution.completed", "execution.failed"},
223
+ "默认前缀匹配(行为不变)")
224
+
225
+ bus3 = SystemBus()
226
+ bus3.publish("execution.completed", {})
227
+ bus3.publish("execution.completed.extra", {})
228
+ bus3.publish("execution.failed", {})
229
+ h = bus3.history("execution.completed", exact=True)
230
+ topics = [(e.get("topic") or e.get("channel")) for e in h]
231
+ check(topics == ["execution.completed"],
232
+ f"history exact=True 只回精确项(实为 {topics})")
233
+ h2 = bus3.history("execution.completed")
234
+ check(len(h2) == 2, "history 默认前缀匹配(含 .extra)")
235
+
236
+ print(" —— TruthPipeline 抗前缀干扰 ——")
237
+ bus4, events4, hmr4 = _make_pipeline()
238
+ bus4.publish("execution.completed.extra", {"task": "x", "result": "y"})
239
+ bus4.publish("execution.completed", {"agent_id": "t", "task": "t",
240
+ "result": "ok", "blocked": False})
241
+ check(len(hmr4.calls) == 1,
242
+ f"前缀事件 execution.completed.extra 不触发真值链(沉淀 {len(hmr4.calls)} 次)")
243
+
244
+
245
+ def test_transition_terminal_guard():
246
+ print("\n【8】P5-3:ProcessManager._transition 终态守卫")
247
+ from foxclaw.kernel.process_manager import ProcessManager
248
+ pm = ProcessManager()
249
+ rec = pm.create("t1", agent_id="test")
250
+ pm.mark_running(rec.process_id)
251
+ pm.mark_completed(rec.process_id)
252
+ n = pm.active_count()
253
+ pm._transition(rec, "failed")
254
+ check(rec.state == "completed", "终态再流转 → 状态不变")
255
+ check(pm.active_count() == n, "终态再流转 → 活跃计数不重复 -1")
256
+ check(rec.state_history.count("completed") == 1, "终态再流转 → 历史不追加")
257
+ rec2 = pm.create("t2")
258
+ rec2.state = "created"
259
+ try:
260
+ pm._transition(rec2, "completed")
261
+ check(False, "非终态非法跳转应仍抛 ValueError")
262
+ except ValueError:
263
+ check(True, "非终态非法跳转(created→completed)仍抛 ValueError")
264
+
265
+
266
+ def test_reason_code():
267
+ print("\n【9】P5-1:PermissionDecision.reason_code 分类")
268
+ from foxclaw.skills import SkillSpec
269
+ from foxclaw.skills.permission import (
270
+ evaluate, PermissionRules, MISSING_PARAMS, DESTRUCTIVE_CONFIRM,
271
+ SIDE_EFFECT_CONFIRM, ASK_RULE, EXTERNAL_CONFIRM)
272
+
273
+ def _mk(**kw):
274
+ base = dict(id="s1", name="s", description="d", kind="local",
275
+ side_effect="read", handler=lambda: {})
276
+ base.update(kw)
277
+ return SkillSpec(**base)
278
+
279
+ d = evaluate(_mk(params={"q": {"required": True}}), {})
280
+ check(d.decision == "ask" and d.reason_code == MISSING_PARAMS,
281
+ f"缺必填参数 → ask + MISSING_PARAMS(实为 {d.reason_code!r})")
282
+
283
+ d = evaluate(_mk(destructive=True), {})
284
+ check(d.decision == "ask" and d.reason_code == DESTRUCTIVE_CONFIRM,
285
+ f"destructive → DESTRUCTIVE_CONFIRM(实为 {d.reason_code!r})")
286
+
287
+ d = evaluate(_mk(side_effect="write"), {})
288
+ check(d.decision == "ask" and d.reason_code == SIDE_EFFECT_CONFIRM,
289
+ f"write 副作用 → SIDE_EFFECT_CONFIRM(实为 {d.reason_code!r})")
290
+
291
+ d = evaluate(_mk(), {}, rules=PermissionRules(ask=["s1"]))
292
+ check(d.decision == "ask" and d.reason_code == ASK_RULE,
293
+ f"命中 ask 规则 → ASK_RULE(实为 {d.reason_code!r})")
294
+
295
+ d = evaluate(_mk(external=True), {})
296
+ check(d.decision == "ask" and d.reason_code == EXTERNAL_CONFIRM,
297
+ f"外部调用 → EXTERNAL_CONFIRM(实为 {d.reason_code!r})")
298
+
299
+ d = evaluate(_mk(), {})
300
+ check(d.decision == "allow" and d.reason_code == "",
301
+ "allow 侧 reason_code 留空(文稿未定义,不扩大枚举面)")
302
+ d = evaluate(_mk(), {}, rules=PermissionRules(deny=["s1"]))
303
+ check(d.decision == "deny" and d.reason_code == "",
304
+ "deny 侧 reason_code 留空(deny 语义已由 decision+stage 表达)")
305
+ d = evaluate(_mk(params={"q": {"required": True}}), {})
306
+ check(d.to_dict().get("reason_code") == MISSING_PARAMS, "to_dict 携带 reason_code")
307
+
308
+
309
+ if __name__ == "__main__":
310
+ test_helpers()
311
+ test_wiring_source()
312
+ test_ability_isinstance()
313
+ test_truth_blocked_skipped()
314
+ test_truth_system_attribution()
315
+ test_system_bus_exact()
316
+ test_transition_terminal_guard()
317
+ test_reason_code()
318
+ print("\n" + "=" * 60)
319
+ if FAILS:
320
+ print(f"✗ 批次7 共 {len(FAILS)} 项失败:")
321
+ for f in FAILS:
322
+ print(f" - {f}")
323
+ sys.exit(1)
324
+ print("✓ 批次7 P3 收尾 + P4/P5 小项全部通过")