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,344 @@
1
+ """
2
+ LLM 驱动规划器(P0-1)测试 —— 全离线(用 fake router,不触发真实网络)。
3
+
4
+ 覆盖:
5
+ 1. 开关:FOXCLAW_LLM_PLAN / FOXCLAW_LLM_REPLAN 默认关 / 显式开
6
+ 2. build_plan_tool:submit_plan 的 schema 含 agent 枚举 + flow 枚举
7
+ 3. llm_plan 的 fail-safe:不支持 tools / 模型不产出 / args 解析失败 / 空输入 / 抛异常 → None
8
+ 4. llm_plan 正常解析:flow + steps + 依赖 + 非法 agent 兜底 + 单步强制 single
9
+ 5. SupervisorAgent._llm_plan:未开启/不支持/单步 → None;多步 → (Plan, step_routes)
10
+ 6. llm_replan:正常产出修正计划 / 空 steps=放弃 / fail-safe → None
11
+ 7. _dispatch_failed:blocked/failed 失败判定
12
+ 8. _replan_and_redo:失败 → 反思 → 重执行 → 成功(含放弃短路)
13
+ 9. sv_replan stage:无失败/未开启 短路保留原结果
14
+ """
15
+ import os
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
20
+ sys.path.insert(0, str(Path(__file__).parent.parent))
21
+
22
+ from foxclaw.planning.llm_planner import (
23
+ llm_plan,
24
+ llm_plan_enabled,
25
+ llm_replan,
26
+ replan_enabled,
27
+ build_plan_tool,
28
+ )
29
+
30
+
31
+ class _FakeRouter:
32
+ """fake ModelRouter:只暴露 llm_plan 依赖的接口。"""
33
+
34
+ def __init__(self, is_stub=False, supports=True, result=None):
35
+ self.is_stub = is_stub
36
+ self._supports = supports
37
+ self._result = result
38
+ self.last_call = None
39
+
40
+ def supports_tools(self):
41
+ return self._supports and not self.is_stub
42
+
43
+ def call_tools(self, system, messages, tools, tool_choice="auto",
44
+ max_tokens=1024, agent_id=""):
45
+ self.last_call = {"system": system, "messages": messages,
46
+ "tools": tools, "tool_choice": tool_choice}
47
+ return self._result
48
+
49
+
50
+ def _catalog():
51
+ return {"finance": "财务", "office": "办公", "admin": "行政", "assistant": "助手"}
52
+
53
+
54
+ def _plan_args(flow="sequential", steps=()):
55
+ return {"id": "c1", "name": "submit_plan",
56
+ "args": {"flow": flow, "steps": list(steps)}}
57
+
58
+
59
+ def test_enabled_switch():
60
+ print("\n【1】开关默认关 / 显式开")
61
+ os.environ.pop("FOXCLAW_LLM_PLAN", None)
62
+ assert llm_plan_enabled() is False, "默认必须关(可逆/不改变现有行为)"
63
+ os.environ["FOXCLAW_LLM_PLAN"] = "1"
64
+ assert llm_plan_enabled() is True
65
+ os.environ["FOXCLAW_LLM_PLAN"] = "0"
66
+ assert llm_plan_enabled() is False
67
+ os.environ.pop("FOXCLAW_LLM_PLAN", None)
68
+ print(" ✓ 默认关 / '1'开 / '0'关")
69
+
70
+
71
+ def test_build_plan_tool_schema():
72
+ print("\n【2】submit_plan schema:agent 枚举约束")
73
+ tool = build_plan_tool(_catalog())
74
+ fn = tool["function"]
75
+ assert tool["type"] == "function"
76
+ assert fn["name"] == "submit_plan"
77
+ agent_enum = fn["parameters"]["properties"]["steps"]["items"]["properties"]["agent"]["enum"]
78
+ assert set(agent_enum) == {"finance", "office", "admin", "assistant"}, agent_enum
79
+ flow_enum = fn["parameters"]["properties"]["flow"]["enum"]
80
+ assert set(flow_enum) == {"single", "sequential", "parallel"}
81
+ print(" ✓ schema 含 agent 枚举 + flow 枚举")
82
+
83
+
84
+ def test_llm_plan_failsafe():
85
+ print("\n【3】fail-safe:不支持/不产出/解析失败/空输入/抛异常 → None")
86
+ r = _FakeRouter(result={"supported": False})
87
+ assert llm_plan(r, "任务", _catalog()) is None
88
+ r2 = _FakeRouter(result={"supported": True, "tool_calls": []})
89
+ assert llm_plan(r2, "任务", _catalog()) is None
90
+ r3 = _FakeRouter(result={"supported": True, "tool_calls": [
91
+ {"id": "c1", "name": "submit_plan", "args": {}, "args_parse_failed": True}]})
92
+ assert llm_plan(r3, "任务", _catalog()) is None
93
+ assert llm_plan(r2, "任务", {}) is None
94
+ assert llm_plan(r2, "", _catalog()) is None
95
+
96
+ class _Boom:
97
+ def call_tools(self, *a, **k):
98
+ raise RuntimeError("boom")
99
+
100
+ assert llm_plan(_Boom(), "任务", _catalog()) is None
101
+ print(" ✓ 6 条 fail-safe 路径全部回退 None")
102
+
103
+
104
+ def test_llm_plan_parse():
105
+ print("\n【4】正常解析:flow + steps + 依赖 + 非法 agent 兜底")
106
+ r = _FakeRouter(result={"supported": True, "tool_calls": [
107
+ _plan_args("sequential", [
108
+ {"name": "查支出", "agent": "finance", "depends_on": []},
109
+ {"name": "定预算", "agent": "finance", "depends_on": [0]},
110
+ {"name": "发邮件", "agent": "office", "depends_on": [1]},
111
+ ])]})
112
+ out = llm_plan(r, "查支出再定预算再发邮件", _catalog())
113
+ assert out["flow"] == "sequential"
114
+ assert [s["name"] for s in out["steps"]] == ["查支出", "定预算", "发邮件"]
115
+ assert [s["agent"] for s in out["steps"]] == ["finance", "finance", "office"]
116
+ assert out["steps"][1]["depends_on"] == [0]
117
+
118
+ r2 = _FakeRouter(result={"supported": True, "tool_calls": [
119
+ _plan_args("parallel", [{"name": "查天气", "agent": "office"}])]})
120
+ out2 = llm_plan(r2, "查天气", _catalog())
121
+ assert out2["flow"] == "single" and len(out2["steps"]) == 1
122
+
123
+ r3 = _FakeRouter(result={"supported": True, "tool_calls": [
124
+ _plan_args("single", [{"name": "x", "agent": "不存在的Agent"}])]})
125
+ out3 = llm_plan(r3, "x", _catalog())
126
+ assert out3["steps"][0]["agent"] == "office"
127
+ print(" ✓ 解析 / 单步single / 非法agent兜底 正确")
128
+
129
+
130
+ def test_supervisor_llm_plan():
131
+ print("\n【5】SupervisorAgent._llm_plan:开关 + 路由链路")
132
+ from foxclaw.agents.agents import SupervisorAgent
133
+ D = type("D", (), {"AGENT_NAME": "dummy"})
134
+ sub = {"finance": D(), "office": D()}
135
+
136
+ os.environ.pop("FOXCLAW_LLM_PLAN", None)
137
+ a1 = SupervisorAgent.__new__(SupervisorAgent)
138
+ a1.router = _FakeRouter(result={"supported": True})
139
+ a1._subagents = sub
140
+ assert a1._llm_plan("任务") is None
141
+
142
+ os.environ["FOXCLAW_LLM_PLAN"] = "1"
143
+ a2 = SupervisorAgent.__new__(SupervisorAgent)
144
+ a2.router = _FakeRouter(result={"supported": True, "tool_calls": [
145
+ _plan_args("sequential", [
146
+ {"name": "查支出", "agent": "finance"},
147
+ {"name": "发邮件", "agent": "office"},
148
+ ])]})
149
+ a2._subagents = sub
150
+ plan, routes = a2._llm_plan("查支出再发邮件")
151
+ assert plan is not None and plan.flow_type == "sequential"
152
+ assert len(plan.tasks) == 2
153
+ assert routes == [("查支出", "finance"), ("发邮件", "office")]
154
+
155
+ a3 = SupervisorAgent.__new__(SupervisorAgent)
156
+ a3.router = _FakeRouter(result={"supported": True, "tool_calls": [
157
+ _plan_args("single", [{"name": "查天气", "agent": "office"}])]})
158
+ a3._subagents = sub
159
+ assert a3._llm_plan("查天气") is None
160
+
161
+ a4 = SupervisorAgent.__new__(SupervisorAgent)
162
+ a4.router = _FakeRouter(is_stub=True)
163
+ a4._subagents = sub
164
+ assert a4._llm_plan("任务") is None
165
+
166
+ os.environ.pop("FOXCLAW_LLM_PLAN", None)
167
+ print(" ✓ 开关 / 多步链路 / 单步回退 / 不支持回退 正确")
168
+
169
+
170
+ def test_replan_enabled_switch():
171
+ print("\n【6】反思重规划开关")
172
+ os.environ.pop("FOXCLAW_LLM_REPLAN", None)
173
+ assert replan_enabled() is False, "默认必须关(可逆)"
174
+ os.environ["FOXCLAW_LLM_REPLAN"] = "1"
175
+ assert replan_enabled() is True
176
+ os.environ.pop("FOXCLAW_LLM_REPLAN", None)
177
+ print(" ✓ 默认关 / '1'开")
178
+
179
+
180
+ def test_llm_replan_parse():
181
+ print("\n【7】llm_replan:修正计划 + 空 steps 放弃 + fail-safe")
182
+ prior = [{"subtask": "查支出", "agent": "finance", "output": "超时", "blocked": True},
183
+ {"subtask": "发邮件", "agent": "office", "output": "未执行", "blocked": False}]
184
+ r = _FakeRouter(result={"supported": True, "tool_calls": [
185
+ _plan_args("sequential", [
186
+ {"name": "查支出(重试)", "agent": "finance"},
187
+ {"name": "发邮件", "agent": "office"},
188
+ ])]})
189
+ out = llm_replan(r, "查支出再发邮件", prior, _catalog())
190
+ assert out is not None and [s["agent"] for s in out["steps"]] == ["finance", "office"]
191
+ r2 = _FakeRouter(result={"supported": True, "tool_calls": [
192
+ _plan_args("sequential", [])]})
193
+ out2 = llm_replan(r2, "任务", prior, _catalog())
194
+ assert out2 is not None and out2["steps"] == []
195
+ r3 = _FakeRouter(result={"supported": False})
196
+ assert llm_replan(r3, "任务", prior, _catalog()) is None
197
+ print(" ✓ 修正计划 / 空steps放弃 / 不支持回退 正确")
198
+
199
+
200
+ def test_dispatch_failed():
201
+ print("\n【8】_dispatch_failed:失败判定")
202
+ from foxclaw.agents.agents import _dispatch_failed
203
+ assert _dispatch_failed({"result": {"dispatched_to": "multi", "blocked": True}}) is True
204
+ assert _dispatch_failed({"result": {"dispatched_to": "parallel",
205
+ "parallel_steps": [{"failed": True}]}}) is True
206
+ assert _dispatch_failed({"result": {"dispatched_to": "finance"}}) is False
207
+ assert _dispatch_failed({"result": {"dispatched_to": "multi", "blocked": False,
208
+ "multi_steps": [{"blocked": False}]}}) is False
209
+ assert _dispatch_failed(None) is False
210
+ print(" ✓ blocked/failed/单Agent/成功/None 判定正确")
211
+
212
+
213
+ def test_replan_and_redo():
214
+ print("\n【9】_replan_and_redo:失败 → 反思 → 重执行 → 成功")
215
+ import asyncio
216
+ from foxclaw.agents.agents import SupervisorAgent
217
+ os.environ["FOXCLAW_LLM_REPLAN"] = "1"
218
+ D = type("D", (), {"AGENT_NAME": "dummy"})
219
+ sub = {"finance": D(), "office": D()}
220
+
221
+ router = _FakeRouter(result={"supported": True, "tool_calls": [
222
+ _plan_args("sequential", [
223
+ {"name": "查支出(重试)", "agent": "finance"},
224
+ {"name": "发邮件", "agent": "office"},
225
+ ])]})
226
+ a = SupervisorAgent.__new__(SupervisorAgent)
227
+ a.router = router
228
+ a._subagents = sub
229
+ a._think = lambda **kw: None
230
+
231
+ calls = {"n": 0}
232
+
233
+ async def fake_dispatch_multi(task, step_routes, *a2, **kw):
234
+ calls["n"] += 1
235
+ if calls["n"] == 1:
236
+ return {"result": {"dispatched_to": "multi", "blocked": True,
237
+ "multi_steps": [{"step": 1, "subtask": "查支出",
238
+ "agent": "finance", "output": "超时",
239
+ "blocked": True}]}}
240
+ return {"result": {"dispatched_to": "multi", "blocked": False,
241
+ "multi_steps": [{"step": 1, "subtask": "查支出(重试)",
242
+ "agent": "finance", "output": "支出=1000",
243
+ "blocked": False},
244
+ {"step": 2, "subtask": "发邮件",
245
+ "agent": "assistant", "output": "已发送",
246
+ "blocked": False}]}}
247
+ a._dispatch_multi = fake_dispatch_multi
248
+
249
+ failed_dr = {"result": {"dispatched_to": "multi", "blocked": True,
250
+ "multi_steps": [{"step": 1, "subtask": "查支出",
251
+ "agent": "finance", "output": "超时",
252
+ "blocked": True}]}}
253
+ out = asyncio.run(a._replan_and_redo("查支出再发邮件", None, "sess", sub, {}, [], failed_dr))
254
+ assert calls["n"] == 2, f"应重执行 1 次,实际 {calls['n']}"
255
+ assert out["result"]["blocked"] is False
256
+ os.environ.pop("FOXCLAW_LLM_REPLAN", None)
257
+ print(" ✓ 失败触发反思 → 重执行 1 次 → 成功")
258
+
259
+
260
+ def test_replan_gives_up():
261
+ print("\n【10】_replan_and_redo:模型放弃 → 不重执行,保留原结果")
262
+ import asyncio
263
+ from foxclaw.agents.agents import SupervisorAgent
264
+ os.environ["FOXCLAW_LLM_REPLAN"] = "1"
265
+ D = type("D", (), {"AGENT_NAME": "dummy"})
266
+ sub = {"finance": D(), "assistant": D()}
267
+
268
+ router = _FakeRouter(result={"supported": True, "tool_calls": [
269
+ _plan_args("sequential", [])]})
270
+ a = SupervisorAgent.__new__(SupervisorAgent)
271
+ a.router = router
272
+ a._subagents = sub
273
+ a._think = lambda **kw: None
274
+ called = {"n": 0}
275
+
276
+ async def fake_dispatch_multi(task, step_routes, *a2, **kw):
277
+ called["n"] += 1
278
+ return {"result": {"dispatched_to": "multi", "blocked": False}}
279
+
280
+ a._dispatch_multi = fake_dispatch_multi
281
+ failed_dr = {"result": {"dispatched_to": "multi", "blocked": True,
282
+ "multi_steps": [{"step": 1, "subtask": "查支出",
283
+ "agent": "finance", "output": "超时",
284
+ "blocked": True}]}}
285
+ out = asyncio.run(a._replan_and_redo("查支出", None, "s", sub, {}, [], failed_dr))
286
+ assert called["n"] == 0, "放弃时不应重执行"
287
+ assert out is failed_dr, "放弃时保留原结果"
288
+ os.environ.pop("FOXCLAW_LLM_REPLAN", None)
289
+ print(" ✓ 放弃短路,保留原结果")
290
+
291
+
292
+ def test_sv_replan_stage():
293
+ print("\n【11】sv_replan stage:短路 + 触发")
294
+ import asyncio
295
+ from types import SimpleNamespace
296
+ from foxclaw.pipeline.supervisor_stages import sv_replan
297
+
298
+ a1 = SimpleNamespace(_replan_enabled=lambda: True)
299
+ ctx1 = SimpleNamespace(extra={}, agent=a1,
300
+ dispatch_result={"result": {"dispatched_to": "multi", "blocked": False}})
301
+ asyncio.run(sv_replan(ctx1))
302
+ assert ctx1.dispatch_result["result"]["blocked"] is False
303
+
304
+ a2 = SimpleNamespace(_replan_enabled=lambda: False)
305
+ ctx2 = SimpleNamespace(extra={}, agent=a2,
306
+ dispatch_result={"result": {"dispatched_to": "multi", "blocked": True}})
307
+ asyncio.run(sv_replan(ctx2))
308
+ assert ctx2.dispatch_result["result"]["blocked"] is True
309
+
310
+ async def fake_replan(task, sched, session_id, subagents, context, memories, dr, max_rounds=2):
311
+ return {"result": {"dispatched_to": "multi", "blocked": False}}
312
+
313
+ a3 = SimpleNamespace(_replan_enabled=lambda: True, _replan_and_redo=fake_replan)
314
+ ctx3 = SimpleNamespace(extra={}, agent=a3,
315
+ dispatch_result={"result": {"dispatched_to": "multi", "blocked": True}},
316
+ task="t", sched=None, session_id="s", context={}, memories=[])
317
+ asyncio.run(sv_replan(ctx3))
318
+ assert ctx3.dispatch_result["result"]["blocked"] is False
319
+
320
+ print(" ✓ 无失败/未开启短路,触发路径更新结果")
321
+
322
+
323
+ def main():
324
+ print("=" * 60)
325
+ print("LLM 驱动规划器(P0-1)")
326
+ print("=" * 60)
327
+ test_enabled_switch()
328
+ test_build_plan_tool_schema()
329
+ test_llm_plan_failsafe()
330
+ test_llm_plan_parse()
331
+ test_supervisor_llm_plan()
332
+ test_replan_enabled_switch()
333
+ test_llm_replan_parse()
334
+ test_dispatch_failed()
335
+ test_replan_and_redo()
336
+ test_replan_gives_up()
337
+ test_sv_replan_stage()
338
+ print("\n" + "=" * 60)
339
+ print("✓ P0-1 验证完成:LLM 规划 + 反思重规划,opt-in + fail-safe,规则拆解 fallback 保留")
340
+ print("=" * 60)
341
+
342
+
343
+ if __name__ == "__main__":
344
+ main()
@@ -0,0 +1,62 @@
1
+ """工具中心第3期 · SKILL.md 指令技能 端到端验证。"""
2
+ import sys, os, tempfile
3
+ from pathlib import Path
4
+ sys.path.insert(0, str(Path(__file__).parent.parent))
5
+
6
+ def test_markdown_skills():
7
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
8
+ _DATA = tempfile.mkdtemp(prefix="foxclaw_md_")
9
+ os.environ["FOXCLAW_DATA_DIR"] = _DATA
10
+
11
+ from fastapi.testclient import TestClient
12
+ from foxclaw.api import app
13
+
14
+ SKILL = "---\nname: 周报助手\ndescription: 教怎么写周报\ntriggers: 周报, 汇报\n---\n先列本周成果,再列风险,最后列下周计划。"
15
+
16
+ with TestClient(app) as c:
17
+ print("=" * 60); print("工具中心 · SKILL.md 指令技能 验证"); print("=" * 60)
18
+
19
+ r = c.post("/api/ahs/md-skills", json={"content": SKILL})
20
+ assert r.status_code == 409 and r.json()["detail"]["needs_ack"] is True
21
+ print("[1] 未确认 → 409 needs_ack OK")
22
+
23
+ r = c.post("/api/ahs/md-skills", json={"content": SKILL, "acknowledged": True})
24
+ assert r.status_code == 200, r.text
25
+ sid = r.json()["skill"]["id"]
26
+ metas = c.get("/api/ahs/md-skills").json()["skills"]
27
+ assert any(s["id"] == sid for s in metas)
28
+ assert all("body" not in s for s in metas)
29
+ print("[2] 上传(ack)+列表(不含body) OK")
30
+
31
+ r = c.get(f"/api/ahs/md-skills/{sid}")
32
+ assert r.status_code == 200 and "成果" in r.json()["body"]
33
+ print("[3] 查看含 body OK")
34
+
35
+ r = c.post("/api/ahs/md-skills", json={"content": "---\ndescription: x\n---\nbody", "acknowledged": True})
36
+ assert r.status_code == 400
37
+ print("[4] 缺 name → 400 OK")
38
+
39
+ assert c.patch(f"/api/ahs/md-skills/{sid}", json={"enabled": False}).json()["skill"]["enabled"] is False
40
+ assert c.patch(f"/api/ahs/md-skills/{sid}", json={"enabled": True}).status_code == 200
41
+ print("[5] 启停 OK")
42
+
43
+ from foxclaw import Foxclaw
44
+ os2 = Foxclaw(data_dir=_DATA)
45
+ assert os2.md_skills.get(sid) is not None, "重启后指令技能丢失(持久化)"
46
+ ag = os2.supervisor
47
+ out = ag._skill_augment("帮我写周报", [{"role": "user", "content": "帮我写周报"}])
48
+ assert "先列本周成果" in out[-1]["content"] and "帮我写周报" in out[-1]["content"]
49
+ assert sum(1 for i in range(len(out) - 1) if out[i]["role"] == out[i + 1]["role"] == "user") == 0
50
+ out2 = ag._skill_augment("订机票", [{"role": "user", "content": "订机票"}])
51
+ assert out2[0]["content"] == "订机票"
52
+ print("[6] 注入命中/未命中 + 持久化 round-trip OK")
53
+
54
+ with TestClient(app) as c:
55
+ assert c.delete(f"/api/ahs/md-skills/{sid}").status_code == 200
56
+ assert not any(s["id"] == sid for s in c.get("/api/ahs/md-skills").json()["skills"])
57
+ print("[7] 删除 OK")
58
+
59
+ print("\nSKILL.md 指令技能验证全部通过")
60
+
61
+ if __name__ == "__main__":
62
+ test_markdown_skills()
@@ -0,0 +1,66 @@
1
+ """工具中心第3期 · SKILL.md 指令技能 · OpenClaw 兼容段(原 test_markdown_skills [8]-[12])。
2
+
3
+ 独立成文件的原因:原文件单进程内 3×TestClient lifespan + 4×Foxclaw 实例 +
4
+ 两个不同 ST 模型共存,第三次 lifespan 预热阶段 torch/FAISS 必现 0xC0000005
5
+ 进程崩溃(git stash 基线对照实锤,与业务改动无关——原生库析构/重入问题)。
6
+ 进程隔离后两段各自全绿。本段自包含:不依赖前半段的 sid,自带 _DATA。
7
+ """
8
+ import sys, os, tempfile
9
+ from pathlib import Path
10
+ sys.path.insert(0, str(Path(__file__).parent.parent))
11
+
12
+ def test_markdown_skills_openclaw():
13
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
14
+ _DATA = tempfile.mkdtemp(prefix="foxclaw_oc_")
15
+ os.environ["FOXCLAW_DATA_DIR"] = _DATA
16
+
17
+ from fastapi.testclient import TestClient
18
+ from foxclaw.api import app
19
+
20
+ NO_TRIG = "---\nname: skill-vetter\nversion: 1.0.0\ndescription: Security-first skill vetting. Use before installing any skill from ClawdHub or GitHub.\n---\nCheck red flags before install."
21
+ with TestClient(app) as c:
22
+ r = c.post("/api/ahs/md-skills", json={"content": NO_TRIG, "id": "skill_vetter", "acknowledged": True})
23
+ assert r.status_code == 200, r.text
24
+ assert r.json()["skill"]["version"] == "1.0.0"
25
+ print("[8] 无 triggers 的 OpenClaw 技能(单.md)安装 OK")
26
+
27
+ import io as _io, zipfile as _zip, json as _json, base64 as _b64
28
+ _buf = _io.BytesIO()
29
+ with _zip.ZipFile(_buf, "w") as z:
30
+ z.writestr("SKILL.md", NO_TRIG)
31
+ z.writestr("_meta.json", _json.dumps({"slug": "skill-vetter-zip", "version": "1.0.0"}))
32
+ _pkg = _b64.b64encode(_buf.getvalue()).decode()
33
+ with TestClient(app) as c:
34
+ r = c.post("/api/ahs/md-skills", json={"package_b64": _pkg, "acknowledged": True})
35
+ assert r.status_code == 200, r.text
36
+ assert r.json()["skill"]["id"] == "skill-vetter-zip" and r.json()["skill"]["version"] == "1.0.0"
37
+ print("[9] OpenClaw zip 包上传(slug→id,version) OK")
38
+ _b2 = _io.BytesIO()
39
+ with _zip.ZipFile(_b2, "w") as z: z.writestr("readme.txt", "hi")
40
+ r = c.post("/api/ahs/md-skills", json={"package_b64": _b64.b64encode(_b2.getvalue()).decode(), "acknowledged": True})
41
+ assert r.status_code == 400
42
+ print("[10] zip 无 SKILL.md → 400 OK")
43
+
44
+ from foxclaw import Foxclaw
45
+ os3 = Foxclaw(data_dir=_DATA)
46
+ assert os3.md_skills.get("skill-vetter-zip") is not None
47
+ ag3 = os3.supervisor
48
+ hit = ag3._skill_augment("please vet this skill before install", [{"role": "user", "content": "please vet this skill before install"}])
49
+ assert "Check red flags" in hit[-1]["content"]
50
+ miss = ag3._skill_augment("订一张机票", [{"role": "user", "content": "订一张机票"}])
51
+ assert miss[0]["content"] == "订一张机票"
52
+ print("[11] 无 triggers 描述相关注入 + 持久化 OK")
53
+
54
+ with TestClient(app) as c:
55
+ r = c.patch("/api/ahs/md-skills/skill-vetter-zip", json={"triggers": ["审核", "搜索"]})
56
+ assert r.status_code == 200 and r.json()["skill"]["triggers"] == ["审核", "搜索"], r.text
57
+ os4 = Foxclaw(data_dir=_DATA)
58
+ ag4 = os4.supervisor
59
+ hit = ag4._skill_augment("帮我审核这个技能", [{"role": "user", "content": "帮我审核这个技能"}])
60
+ assert "Check red flags" in hit[-1]["content"]
61
+ print("[12] 编辑触发词 → 中文任务触发 OK")
62
+
63
+ print("\nOpenClaw 兼容验证全部通过")
64
+
65
+ if __name__ == "__main__":
66
+ test_markdown_skills_openclaw()
@@ -0,0 +1,97 @@
1
+ """工具中心第2期 · MCP 连接器 端到端验证(monkeypatch async 助手,不联网)。"""
2
+ import sys, os, tempfile
3
+ from pathlib import Path
4
+ sys.path.insert(0, str(Path(__file__).parent.parent))
5
+
6
+ def test_mcp_connectors():
7
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
8
+ os.environ["FOXCLAW_SKIP_DOTENV"] = "1"
9
+ os.environ["HF_HUB_OFFLINE"] = "1"
10
+ os.environ["TRANSFORMERS_OFFLINE"] = "1"
11
+ _DATA = tempfile.mkdtemp(prefix="foxclaw_mcp_")
12
+ os.environ["FOXCLAW_DATA_DIR"] = _DATA
13
+
14
+ from fastapi.testclient import TestClient
15
+ from foxclaw.api import app
16
+ import foxclaw.skills.mcp_client as mc
17
+ from types import SimpleNamespace as NS
18
+
19
+ import asyncio as _aio
20
+ _blocked = False
21
+ try:
22
+ _aio.run(mc._amcp_list_tools("http://127.0.0.1:9/mcp", {}, "streamable_http"))
23
+ except BaseException as _e:
24
+ _s = str(_e) + repr(getattr(_e, "exceptions", ""))
25
+ _blocked = ("内网" in _s) or ("127.0.0.1" in _s) or ("拒" in _s)
26
+ assert _blocked, "客户端 SSRF 钩子未拦截内网连接"
27
+ print("[SSRF] 客户端连内网被请求钩子拦截 OK")
28
+
29
+ FAKE_TOOLS = [
30
+ NS(name="get_time", description="读时间", inputSchema={}, annotations=NS(readOnlyHint=True, destructiveHint=None)),
31
+ NS(name="do_thing", description="干活", inputSchema={}, annotations=None),
32
+ ]
33
+ async def fake_list(url, headers, transport): return list(FAKE_TOOLS)
34
+ async def fake_call(url, headers, transport, name, args): return {"isError": False, "content": "ok:" + name, "structured": None}
35
+ mc._amcp_list_tools = fake_list
36
+ mc._amcp_call_tool = fake_call
37
+
38
+ SRV = {"id": "demo_srv", "name": "演示", "url": "https://mcp.example.com/mcp", "acknowledged": True}
39
+
40
+ with TestClient(app) as c:
41
+ print("=" * 60); print("工具中心 · MCP 连接器 验证"); print("=" * 60)
42
+
43
+ r = c.post("/api/ahs/mcp/servers", json=SRV)
44
+ assert r.status_code == 200, r.text
45
+ assert r.json()["tools_registered"] == 2
46
+ print("[1] 加服务器 + 注册 2 工具 OK")
47
+
48
+ skills = {s["id"]: s for s in c.get("/api/ahs/skills").json()["skills"]}
49
+ assert skills["mcp__demo_srv__get_time"]["side_effect"] == "read"
50
+ assert skills["mcp__demo_srv__do_thing"]["side_effect"] == "write"
51
+ print("[2] 工具成技能 + fail-closed(read/write) OK")
52
+
53
+ srvs = c.get("/api/ahs/mcp/servers").json()["servers"]
54
+ assert srvs and srvs[0]["tool_count"] == 2
55
+ print("[3] 服务器列表 tool_count=2 OK")
56
+
57
+ r = c.post("/api/ahs/mcp/servers", json={"id": "bad", "url": "http://127.0.0.1/mcp"})
58
+ assert r.status_code == 403, r.text
59
+ print("[4] 审核门 deny 内网(403) OK")
60
+
61
+ r = c.post("/api/ahs/mcp/servers", json={"id": "p2", "url": "https://mcp2.example.com/mcp"})
62
+ assert r.status_code == 409 and r.json()["detail"]["needs_ack"] is True
63
+ print("[5] 外部低信任需确认(409) OK")
64
+
65
+ r = c.post("/api/ahs/skills/mcp__demo_srv__get_time/test", json={"args": {}})
66
+ assert r.json()["status"] == "needs_confirmation", r.text
67
+ r = c.post("/api/ahs/skills/mcp__demo_srv__get_time/test", json={"args": {}, "confirmed": True})
68
+ j = r.json()
69
+ assert r.status_code == 200 and j["success"] is True, r.text
70
+ out = j["output"]
71
+ assert out.get("_untrusted") is True, out
72
+ assert "不可信" in out.get("data", {}).get("content", ""), out
73
+ print("[6] 测试调用 mcp 技能 + 双层 untrusted 标记(防提示注入) OK")
74
+
75
+ async def boom(url, headers, transport): raise RuntimeError("connrefused")
76
+ mc._amcp_list_tools = boom
77
+ r = c.post("/api/ahs/mcp/servers", json={"id": "err_srv", "url": "https://mcp3.example.com/mcp", "acknowledged": True})
78
+ assert r.status_code == 502, r.text
79
+ mc._amcp_list_tools = fake_list
80
+ print("[7] 连接失败 502 OK")
81
+
82
+ assert c.delete("/api/ahs/mcp/servers/demo_srv").status_code == 200
83
+ assert "mcp__demo_srv__get_time" not in {s["id"] for s in c.get("/api/ahs/skills").json()["skills"]}
84
+ print("[8] 移除服务器 + 技能消失 OK")
85
+
86
+ with TestClient(app) as c:
87
+ c.post("/api/ahs/mcp/servers", json=SRV)
88
+ from foxclaw import Foxclaw
89
+ os2 = Foxclaw(data_dir=_DATA)
90
+ assert any(s["id"] == "demo_srv" for s in os2.mcp_manager.servers()), "server 未持久化"
91
+ assert os2.skill_registry.get("mcp__demo_srv__get_time") is not None, "mcp 技能未持久化"
92
+ print("[9] 重启后 server + mcp 技能仍在(持久化 round-trip) OK")
93
+
94
+ print("\nMCP 连接器验证全部通过")
95
+
96
+ if __name__ == "__main__":
97
+ test_mcp_connectors()
@@ -0,0 +1,87 @@
1
+ """第4步 · MCP 发现阶段进守门人:主进程(内存+磁盘)不碰 headers 凭证。"""
2
+ import sys, os, tempfile
3
+ from pathlib import Path
4
+ from types import SimpleNamespace as NS
5
+ sys.path.insert(0, str(Path(__file__).parent.parent))
6
+
7
+ def test_mcp_guardian_discovery():
8
+ os.environ["FOXCLAW_SKIP_DOTENV"] = "1"
9
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_mcp_guardian_")
10
+
11
+ from foxclaw.skills.mcp_client import tool_to_spec
12
+ spec = tool_to_spec("srv", {"name": "get_time", "description": "读时间", "inputSchema": {},
13
+ "annotations": {"readOnlyHint": True, "destructiveHint": None}})
14
+ assert spec.id == "mcp__srv__get_time" and spec.side_effect == "read" and not spec.destructive
15
+ spec2 = tool_to_spec("srv", {"name": "do_thing", "description": "", "inputSchema": {},
16
+ "annotations": None})
17
+ assert spec2.side_effect == "write"
18
+ print("[1] tool_to_spec 兼容 dict + fail-closed OK")
19
+
20
+ import foxclaw.kernel.sentinel as sm
21
+ servers = {}
22
+ sm._handle_mcp_register({"servers": [
23
+ {"id": "a", "name": "a", "url": "https://a", "headers": {"Authorization": "Bearer SECRET"},
24
+ "transport": "streamable_http"}]}, servers)
25
+ assert servers["a"]["headers"]["Authorization"] == "Bearer SECRET"
26
+ sm._handle_mcp_register({"servers": [
27
+ {"id": "a", "name": "a", "url": "https://a", "transport": "streamable_http"}]}, servers)
28
+ assert servers["a"]["headers"]["Authorization"] == "Bearer SECRET", "空 headers 不应覆盖守门人已有凭证"
29
+ print("[2] mcp_register 空 headers 保留旧凭证 OK")
30
+
31
+ import foxclaw.skills.mcp_client as mc
32
+ _seen = {}
33
+ async def fake_list(url, headers, transport):
34
+ _seen["headers"] = headers
35
+ return [NS(name="t1", description="d", inputSchema={},
36
+ annotations=NS(readOnlyHint=True, destructiveHint=None))]
37
+ mc._amcp_list_tools = fake_list
38
+ out = sm._handle_mcp_list_tools({"server_id": "a"}, servers)
39
+ assert out["tools"][0]["name"] == "t1"
40
+ assert out["tools"][0]["annotations"]["readOnlyHint"] is True
41
+ assert _seen["headers"].get("Authorization") == "Bearer SECRET"
42
+ print("[3] mcp_list_tools 守门人代发现 + 序列化 + 凭证不出守门人 OK")
43
+
44
+ class FakeSentinel:
45
+ def __init__(self):
46
+ self.registered = None
47
+ def mcp_register(self, servers):
48
+ self.registered = servers
49
+ def mcp_list_tools(self, sid):
50
+ return [{"name": "t1", "description": "", "inputSchema": {}, "annotations": None}]
51
+
52
+ class FakeMgr:
53
+ def __init__(self):
54
+ self._servers = {}
55
+ def servers(self):
56
+ return [dict(s) for s in self._servers.values()]
57
+ def restore(self, cfg):
58
+ self._servers[cfg["id"]] = cfg
59
+
60
+ class FakeStore:
61
+ def __init__(self):
62
+ self.saved = None
63
+ def save(self, servers):
64
+ self.saved = servers
65
+
66
+ class FakeHost:
67
+ pass
68
+
69
+ host = FakeHost()
70
+ host.sentinel = FakeSentinel()
71
+ host.mcp_manager = FakeMgr()
72
+ host.mcp_store = FakeStore()
73
+
74
+ from foxclaw import Foxclaw
75
+ Foxclaw.mcp_add_server_guardian(host, {
76
+ "id": "srv", "name": "srv", "url": "https://srv",
77
+ "headers": {"X-Key": "SECRET"}, "transport": "streamable_http"})
78
+
79
+ assert "headers" not in host.mcp_manager.servers()[0], "主进程 McpManager 不应存 headers"
80
+ assert "headers" not in host.mcp_store.saved[0], "主进程磁盘不应存 headers"
81
+ assert host.sentinel.registered[-1]["headers"]["X-Key"] == "SECRET", "守门人应收到 headers"
82
+ print("[4] mcp_add_server_guardian:主进程内存/磁盘无 headers,守门人持 headers OK")
83
+
84
+ print("\n第4步 · MCP 发现进守门人 验证全部通过")
85
+
86
+ if __name__ == "__main__":
87
+ test_mcp_guardian_discovery()