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,102 @@
1
+ """主管分诊与自答策略回归。
2
+
3
+ 覆盖主管在「是否命中领域关键词」×「模型是否可用」下的派发决策:
4
+ · 关键词命中 → 直接路由到领域 Agent(快、确定),不额外调用一次 LLM 分诊;
5
+ · 未命中 + 模型可用 → 主管**直接自答**(dispatched_to=supervisor, self_answered=True);
6
+ · 未命中 + stub → 走派发兜底(assistant),保持离线确定性。
7
+
8
+ ⚠ 历史说明(改名前为 test_llm_routing.py):本文件曾断言「关键词未命中 → LLM 分诊精修到
9
+ finance」。那次精修实现在 `agents.py:_llm_classify`,但因每次要多一次约 5s 的串行 LLM
10
+ 往返,产品上被「主管直接自答」取代而退化为**死代码**(全仓库零调用点),已随本轮删除。
11
+ 原断言随之永远无法满足,长期为红,却被归因为「环境问题」——这是**误判**:它不是环境问题,
12
+ 是**测试过期**。现按真实行为重写,并把三种组合都锁住。
13
+
14
+ 用"假 router"模拟真机(is_stub=False),避免依赖真实 API key,全离线。
15
+ """
16
+ import asyncio
17
+ import sys
18
+ import tempfile
19
+ from pathlib import Path
20
+ sys.path.insert(0, str(Path(__file__).parent.parent))
21
+
22
+ import foxclaw.core.web_search as ws
23
+ ws.needs_search = lambda q: False
24
+
25
+ from foxclaw import Foxclaw
26
+
27
+
28
+ def check(cond, msg):
29
+ if not cond:
30
+ raise AssertionError(f"✗ {msg}")
31
+ print(f" ✓ {msg}")
32
+
33
+
34
+ CLASSIFY_MARK = "只输出一个词"
35
+
36
+
37
+ def main():
38
+ print("=" * 56)
39
+ print("主管分诊与自答策略回归")
40
+ print("=" * 56)
41
+ os_ = Foxclaw(data_dir=tempfile.mkdtemp())
42
+
43
+ calls = []
44
+
45
+ def fake(system, messages, max_tokens=2048, agent_id=""):
46
+ content = str(messages[-1]["content"]) if messages else ""
47
+ calls.append(content)
48
+ if CLASSIFY_MARK in content:
49
+ return "finance"
50
+ return "已处理"
51
+
52
+ os_.router.provider = "deepseek"
53
+ os_.router._client = object()
54
+ os_.router.complete = fake
55
+ check(os_.router.is_stub is False, "router 处于'可用'状态(非 stub)")
56
+
57
+ print("\n【1】关键词未命中 + 模型可用 → 主管直接自答(不再二次分诊)")
58
+ no_kw = "讲个笑话听听"
59
+ check(os_.supervisor._has_keyword_hit(no_kw) is False, f"「{no_kw[:12]}…」确无关键词命中")
60
+ r = asyncio.run(os_.supervisor.run_task(no_kw))
61
+ d1 = r["result"].get("dispatched_to")
62
+ check(d1 == "supervisor", f"未命中 → 主管自答(dispatched_to={d1},预期 supervisor)")
63
+ check(r["result"].get("self_answered") is True, "标记 self_answered=True(确为自答而非派发)")
64
+ check(bool(str(r["result"].get("sub_result") or "").strip()), "自答产出非空答复")
65
+ check(not any(CLASSIFY_MARK in c for c in calls), "全程未发出分诊 prompt(不再多花一次 LLM 往返)")
66
+
67
+ print("\n【1b】零星花费报备(无财务关键词)→ 认作财务命中,派给财务")
68
+ for line in ("今天买了一件衣服花了105块", "后天花了100洗车"):
69
+ check(os_.supervisor._has_keyword_hit(line), f"「{line[:12]}…」算作领域命中")
70
+ check(os_.supervisor._route(line) == "finance", f"「{line[:12]}…」→ finance")
71
+ check(os_.supervisor._route("每月房租3000占收入10000这样合理吗") == "finance",
72
+ "房租占比咨询 → finance")
73
+
74
+ print("\n【2】关键词命中 → 直接路由,不问 LLM 分诊(省成本)")
75
+ calls.clear()
76
+ r2 = asyncio.run(os_.supervisor.run_task("帮我做个预算分析"))
77
+ d2 = r2["result"].get("dispatched_to")
78
+ check(d2 == "finance", f"命中'预算' → 直接 finance(实为 {d2})")
79
+ check(not any(CLASSIFY_MARK in c for c in calls), "关键词命中时不发出分诊 prompt")
80
+
81
+ print("\n【3】stub + 关键词未命中 → 主管自处理(离线确定性)")
82
+ os_.router.provider = "stub"
83
+ os_.router._client = None
84
+ calls.clear()
85
+ r3 = asyncio.run(os_.supervisor.run_task("解释一下什么是航天器"))
86
+ check(os_.router.is_stub is True, "router 回到 stub")
87
+ d3 = r3["result"].get("dispatched_to")
88
+ check(d3 == "supervisor", f"stub 下未命中 → 主管自身处理(实为 {d3})")
89
+ check(not r3["result"].get("self_answered"), "stub 下走派发路径,而非主管自答")
90
+ check(not any(CLASSIFY_MARK in c for c in calls), "stub 下不发出分诊 prompt")
91
+
92
+ print("\n" + "=" * 56)
93
+ print("主管分诊与自答策略回归通过")
94
+ print("=" * 56)
95
+
96
+
97
+ def test_supervisor_triage():
98
+ main()
99
+
100
+
101
+ if __name__ == "__main__":
102
+ main()
@@ -0,0 +1,112 @@
1
+ """
2
+ 数据污点追踪 + 动态审批门槛测试 —— 全离线。
3
+
4
+ 对齐 Meta Muse「eBPF 污点追踪」的软件版:会话一旦取回用户/敏感数据就打污点,
5
+ 之后外发/副作用动作的审批门槛动态提高。覆盖:
6
+ 1. injection_guard.taint_level:来源 → 敏感度分级。
7
+ 2. permission.evaluate 污点门槛:默认关闭(行为不变)/ 达标+外发 → TAINT_CONFIRM /
8
+ 达标+只读 → 不触发 / 未达标 → 不触发。
9
+ 3. base 污点传播:_dispatch_tool 取回 memory/web_search 后会话污点单调上升;
10
+ 会话开始 _taint_reset 清零。
11
+ """
12
+ import os, sys, types
13
+ from pathlib import Path
14
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
15
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
16
+ os.environ["FOXCLAW_EMBEDDING_MODEL"] = "foxclaw-nonexistent-fast"
17
+ sys.path.insert(0, str(Path(__file__).parent.parent))
18
+
19
+ from foxclaw.core import injection_guard as IG
20
+ from foxclaw.skills import permission as PERM
21
+ from foxclaw.agents.base import (
22
+ CognitiveAgent, _taint_get, _taint_raise, _taint_reset,
23
+ )
24
+
25
+
26
+ def _skill(side_effect="read", read_only=True, external=False, destructive=False):
27
+ """构造一个 permission.evaluate 可用的最小 skill-spec(duck-typed)。"""
28
+ return types.SimpleNamespace(
29
+ id=f"tool:test_{side_effect}",
30
+ params={},
31
+ side_effect=side_effect,
32
+ read_only=read_only,
33
+ external=external,
34
+ destructive=destructive,
35
+ trust_level=lambda: "high",
36
+ )
37
+
38
+
39
+ def test_taint_level_grading():
40
+ assert IG.taint_level("web_search") == IG.TAINT_EXTERNAL == 1
41
+ assert IG.taint_level("knowledge") == IG.TAINT_PERSONAL == 2
42
+ assert IG.taint_level("file") == IG.TAINT_PERSONAL == 2
43
+ assert IG.taint_level("memory") == IG.TAINT_SENSITIVE == 3
44
+ assert IG.taint_level("unknown_source") == IG.TAINT_EXTERNAL
45
+
46
+
47
+ def test_permission_taint_default_off_behavior_unchanged():
48
+ s = _skill(side_effect="send", read_only=False)
49
+ d = PERM.evaluate(s, {})
50
+ assert d.decision == "ask" and d.reason_code == PERM.SIDE_EFFECT_CONFIRM
51
+
52
+
53
+ def test_permission_taint_blocks_send_when_sensitive():
54
+ s = _skill(side_effect="send", read_only=False)
55
+ d = PERM.evaluate(s, {}, taint_level=IG.TAINT_SENSITIVE,
56
+ taint_threshold=IG.TAINT_PERSONAL)
57
+ assert d.decision == "ask" and d.reason_code == PERM.TAINT_CONFIRM
58
+ assert d.requires_confirmation
59
+
60
+
61
+ def test_permission_taint_blocks_external_when_personal():
62
+ s = _skill(side_effect="read", read_only=False, external=True)
63
+ d = PERM.evaluate(s, {}, taint_level=IG.TAINT_PERSONAL,
64
+ taint_threshold=IG.TAINT_PERSONAL)
65
+ assert d.decision == "ask" and d.reason_code == PERM.TAINT_CONFIRM
66
+
67
+
68
+ def test_permission_taint_not_trigger_on_readonly():
69
+ s = _skill(side_effect="read", read_only=True)
70
+ d = PERM.evaluate(s, {}, taint_level=IG.TAINT_SENSITIVE,
71
+ taint_threshold=IG.TAINT_PERSONAL)
72
+ assert d.decision == "allow"
73
+
74
+
75
+ def test_permission_taint_not_trigger_below_threshold():
76
+ s = _skill(side_effect="send", read_only=False)
77
+ d = PERM.evaluate(s, {}, taint_level=IG.TAINT_EXTERNAL,
78
+ taint_threshold=IG.TAINT_PERSONAL)
79
+ assert d.reason_code == PERM.SIDE_EFFECT_CONFIRM
80
+
81
+
82
+ def test_taint_helpers_on_fake_agent():
83
+ a = types.SimpleNamespace(_think=lambda **k: None)
84
+ assert _taint_get(a) == 0
85
+ _taint_raise(a, IG.TAINT_EXTERNAL)
86
+ _taint_raise(a, IG.TAINT_SENSITIVE)
87
+ assert _taint_get(a) == IG.TAINT_SENSITIVE
88
+ _taint_reset(a)
89
+ assert _taint_get(a) == 0
90
+
91
+
92
+ def test_dispatch_recall_memory_raises_taint():
93
+ a = types.SimpleNamespace(
94
+ _think=lambda **k: None,
95
+ _recall_memory=lambda q, top_k=5: [{"content": "用户偏好:喜欢简洁"}])
96
+ out = CognitiveAgent._dispatch_tool(a, "recall_memory", {"query": "偏好"})
97
+ assert "喜欢简洁" in out
98
+ assert _taint_get(a) == IG.TAINT_SENSITIVE
99
+
100
+
101
+ def test_dispatch_web_search_raises_taint(monkeypatch):
102
+ from foxclaw.core import web_search as WS
103
+ monkeypatch.setattr(WS, "search_answer", lambda q: {"answer": "答案ABC", "references": []})
104
+ a = types.SimpleNamespace(_think=lambda **k: None)
105
+ out = CognitiveAgent._dispatch_tool(a, "web_search", {"query": "问题"})
106
+ assert "答案ABC" in out
107
+ assert _taint_get(a) == IG.TAINT_EXTERNAL
108
+
109
+
110
+ if __name__ == "__main__":
111
+ import pytest
112
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,74 @@
1
+ """D1:/api/task/stream SSE —— 任务执行流式化(逐 stage 进度 + act 逐 token + 最终 result)。
2
+ 与 test_streaming.py 的 /api/chat/stream 端点测试对称:那条测对话直答流式,这条测**走完整认知
3
+ 管线**的任务流式。核心逻辑(run_task_stream)在 test_agent_lifecycle 已直测,这里补端点级 SSE 打通。"""
4
+ import os, sys, tempfile, json
5
+ from pathlib import Path
6
+ sys.path.insert(0, str(Path(__file__).parent.parent))
7
+
8
+
9
+ def test_task_stream():
10
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
11
+ os.environ.setdefault("FOXCLAW_EMBEDDING_MODEL", "foxclaw-nonexistent-fast")
12
+ os.environ["FOXCLAW_STREAM_RATE"] = "60"
13
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_taskstream_")
14
+ os.environ["FOXCLAW_SKIP_DOTENV"] = "1"
15
+ for _k in ("DEEPSEEK_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY",
16
+ "GOOGLE_API_KEY", "DASHSCOPE_API_KEY", "FOXCLAW_API_KEY"):
17
+ os.environ[_k] = ""
18
+
19
+ print("=" * 60); print("D1 · /api/task/stream 任务流式化验证"); print("=" * 60)
20
+
21
+ from fastapi.testclient import TestClient
22
+ import foxclaw.api as _api
23
+ from foxclaw.api import app
24
+ _api._STREAM_LIMITER.capacity = 60
25
+ _api._STREAM_LIMITER.rate = 1.0
26
+ _api._STREAM_LIMITER.tokens = 60.0
27
+
28
+ with TestClient(app) as c:
29
+ with c.stream("POST", "/api/task/stream",
30
+ json={"task": "帮我分析一下本月开销", "agent_id": "office"}) as resp:
31
+ assert resp.status_code == 200, resp.status_code
32
+ stages, deltas, results, errors = [], [], [], []
33
+ for line in resp.iter_lines():
34
+ if not line or not line.startswith("data: "):
35
+ continue
36
+ ev = json.loads(line[6:])
37
+ if "stage" in ev: stages.append(ev["stage"])
38
+ if "delta" in ev: deltas.append(ev["delta"])
39
+ if "result" in ev: results.append(ev["result"])
40
+ if "error" in ev: errors.append(ev["error"])
41
+ assert not errors, f"不应有 error 事件: {errors}"
42
+ assert "act" in stages, f"应有 stage 进度事件(含 act): {stages}"
43
+ assert len(deltas) > 0, "应有 act 逐 token delta 事件(证明流式透传经端点打通)"
44
+ assert len(results) == 1, f"应恰有一个最终 result 事件,实得 {len(results)}"
45
+ assert results[0].get("agent") == "office" and "result" in results[0], results[0]
46
+ print(f"[1] /api/task/stream SSE(stage={len(stages)} delta={len(deltas)} result=1) OK")
47
+
48
+ with c.stream("POST", "/api/task/stream",
49
+ json={"task": "随便问问", "agent_id": "supervisor"}) as resp:
50
+ assert resp.status_code == 200, resp.status_code
51
+ stages, results, errors = [], [], []
52
+ for line in resp.iter_lines():
53
+ if not line or not line.startswith("data: "):
54
+ continue
55
+ ev = json.loads(line[6:])
56
+ if "stage" in ev: stages.append(ev["stage"])
57
+ if "result" in ev: results.append(ev["result"])
58
+ if "error" in ev: errors.append(ev["error"])
59
+ assert not errors, f"不应有 error 事件: {errors}"
60
+ assert len(results) == 1, f"应恰有一个最终 result 事件,实得 {len(results)}"
61
+ r0 = results[0]
62
+ assert r0.get("agent") == "supervisor", \
63
+ f"P3-3:主管走自身 pipeline,不再回落 assistant(实为 {r0.get('agent')})"
64
+ assert "dispatch" in stages and "route" in stages, \
65
+ f"应见主管管线 stage(route/dispatch): {stages}"
66
+ assert r0["result"].get("dispatched_to") == "supervisor", \
67
+ f"stub 无关键词 → 主管自身处理(实为 {r0['result'].get('dispatched_to')})"
68
+ print("[2] agent_id=supervisor → 主管自身流式管线(P3-3,不再回落) OK")
69
+
70
+ print("\nD1 /api/task/stream 验证通过")
71
+
72
+
73
+ if __name__ == "__main__":
74
+ test_task_stream()
@@ -0,0 +1,66 @@
1
+ """/api/chat/stream 的命名 SSE 事件:`event: thinking` 与正文 delta 分离。
2
+
3
+ 前端按事件名折叠展示推理;正文 delta 里不得出现推理文本(否则复制/落库会被污染)。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import os
10
+ import sys
11
+ import tempfile
12
+ from pathlib import Path
13
+
14
+ sys.path.insert(0, str(Path(__file__).parent.parent))
15
+
16
+
17
+ def test_chat_stream_emits_named_thinking_event():
18
+ if "foxclaw.api" in sys.modules:
19
+ import pytest
20
+ pytest.skip("需独立进程运行(模块级单例已固化),请用 tests/run_all.py")
21
+ os.environ.setdefault("FOXCLAW_DATA_DIR", tempfile.mkdtemp(prefix="foxclaw_think_"))
22
+
23
+ from fastapi.testclient import TestClient
24
+ from foxclaw.agents.base import CognitiveAgent
25
+ from foxclaw.api import app
26
+
27
+ def _fake_stream(self, task, stop_event=None, images=None, history=None,
28
+ attachments=None, on_thinking=None, on_evidence=None):
29
+ if on_thinking is not None:
30
+ on_thinking("先分析一下用户意图")
31
+ yield "好的"
32
+
33
+ original = CognitiveAgent.stream_reply
34
+ CognitiveAgent.stream_reply = _fake_stream
35
+ try:
36
+ with TestClient(app) as c:
37
+ resp = c.post("/api/chat/stream", json={"task": "hi", "agent_id": "supervisor"})
38
+ assert resp.status_code == 200, resp.status_code
39
+ body = resp.text
40
+ finally:
41
+ CognitiveAgent.stream_reply = original
42
+
43
+ assert "event: thinking" in body, "推理必须以命名事件送出"
44
+ blocks = [b for b in body.split("\n\n") if b.strip()]
45
+ think_payloads, delta_payloads = [], []
46
+ for b in blocks:
47
+ name = "message"
48
+ data = None
49
+ for line in b.splitlines():
50
+ if line.startswith("event: "):
51
+ name = line[7:].strip()
52
+ elif line.startswith("data: "):
53
+ data = line[6:].strip()
54
+ if data is None:
55
+ continue
56
+ ev = json.loads(data)
57
+ if name == "thinking":
58
+ think_payloads.append(ev)
59
+ elif "delta" in ev:
60
+ delta_payloads.append(ev["delta"])
61
+
62
+ assert think_payloads, "应有 thinking 事件块"
63
+ assert "先分析一下用户意图" == "".join(p.get("thinking", "") for p in think_payloads)
64
+ joined = "".join(delta_payloads)
65
+ assert joined == "好的"
66
+ assert "先分析一下" not in joined, "推理不得混入正文 delta"
@@ -0,0 +1,76 @@
1
+ """推理过程可见:`_ThinkStripper` 不再丢弃思考,原生 reasoning 走独立 thinking 通道。
2
+
3
+ 背景:`core/model_router.py` 的 `_ThinkStripper.feed` 只返回可见正文,think 块内容被整段
4
+ 淘汰;openai-compatible 的 `delta.reasoning_content`(deepseek-reasoner 等)更是完全没读,
5
+ 用户只看到结论,看不到推理。现在两者都转成 `on_thinking` 回调 → SSE `event: thinking`。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from types import SimpleNamespace
11
+
12
+ from foxclaw.core.model_router import ModelRouter, _ThinkStripper
13
+
14
+
15
+ class _FakeCompletions:
16
+ def __init__(self, chunks):
17
+ self.chunks = chunks
18
+ self.kwargs: dict = {}
19
+
20
+ def create(self, **kwargs):
21
+ self.kwargs = kwargs
22
+ return iter(self.chunks)
23
+
24
+
25
+ class _FakeClient:
26
+ def __init__(self, chunks):
27
+ self.chat = SimpleNamespace(completions=_FakeCompletions(chunks))
28
+
29
+
30
+ def _chunk(content=None, reasoning=None):
31
+ delta = SimpleNamespace(content=content, reasoning_content=reasoning)
32
+ return SimpleNamespace(choices=[SimpleNamespace(delta=delta)])
33
+
34
+
35
+ def _router(chunks) -> ModelRouter:
36
+ r = ModelRouter(provider="openai", model="gpt-4o-mini")
37
+ r._client = _FakeClient(chunks)
38
+ return r
39
+
40
+
41
+ def test_stripper_returns_think_text_separately() -> None:
42
+ s = _ThinkStripper()
43
+ vis1, th1 = s.feed_events("答案前<thi")
44
+ assert (vis1, th1) == ("答案前", ""), "半个标签不能当正文吐出"
45
+ vis2, th2 = s.feed_events("nk>内部推理</think>答案")
46
+ assert vis2 == "答案"
47
+ assert th2 == "内部推理"
48
+
49
+
50
+ def test_stripper_feed_still_visible_only() -> None:
51
+ s = _ThinkStripper()
52
+ assert s.feed("a<think>b</think>c") == "ac"
53
+
54
+
55
+ def test_reasoning_content_goes_to_thinking_only() -> None:
56
+ r = _router([_chunk(None, "先想一下"), _chunk("你好"), _chunk("世界")])
57
+ think: list[str] = []
58
+ text = "".join(r.stream("sys", [{"role": "user", "content": "hi"}],
59
+ on_thinking=think.append))
60
+ assert "".join(think) == "先想一下", "原生 reasoning_content 必须进推理通道"
61
+ assert text == "你好世界", "正文不得混入推理"
62
+
63
+
64
+ def test_embedded_think_routed_to_callback() -> None:
65
+ r = _router([_chunk("<think>内嵌思考</think>答案是 42<think>补一句")])
66
+ think: list[str] = []
67
+ text = "".join(r.stream("sys", [{"role": "user", "content": "hi"}],
68
+ on_thinking=think.append))
69
+ assert text == "答案是 42"
70
+ assert "内嵌思考" in "".join(think)
71
+ assert "补一句" not in text, "未闭合的 think 尾巴不得漏进正文"
72
+
73
+
74
+ def test_no_callback_keeps_old_behavior() -> None:
75
+ r = _router([_chunk("<think>x</think>ok")])
76
+ assert "".join(r.stream("sys", [{"role": "user", "content": "hi"}])) == "ok"
@@ -0,0 +1,113 @@
1
+ """工具中心第1期 · 技能台真实化 + 安装审核门 + 治理 端到端验证。
2
+ 判定:assert 失败→Traceback→run_all 记失败;正常退出=通过。"""
3
+ import sys, os, tempfile
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent))
6
+
7
+ def test_tool_center():
8
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
9
+ _DATA = tempfile.mkdtemp(prefix="foxclaw_toolc_")
10
+ os.environ["FOXCLAW_DATA_DIR"] = _DATA
11
+
12
+ from fastapi.testclient import TestClient
13
+ from foxclaw.api import app
14
+
15
+ WEBAPI = {"id": "wx_weather", "name": "天气", "description": "查天气", "kind": "web_api",
16
+ "triggers": ["天气"], "side_effect": "read",
17
+ "endpoint": "https://example.com/api/weather", "http_method": "GET"}
18
+
19
+ with TestClient(app) as c:
20
+ print("=" * 60); print("工具中心 · 技能台 + 审核门 + 治理 验证"); print("=" * 60)
21
+
22
+ r = c.post("/api/ahs/skills", json=WEBAPI)
23
+ assert r.status_code == 200, r.text
24
+ assert r.json()["created"] is True
25
+ print("[1] 安装 web_api 技能 OK")
26
+
27
+ r = c.get("/api/ahs/skills"); assert r.status_code == 200
28
+ ids = {s["id"]: s for s in r.json()["skills"]}
29
+ assert "wx_weather" in ids and ids["wx_weather"]["source"] == "user"
30
+ print("[2] 列表含新技能且 source=user OK")
31
+
32
+ r = c.post("/api/ahs/skills", json={**WEBAPI, "id": "x_local", "kind": "local"})
33
+ assert r.status_code == 400
34
+ print("[3] 拒绝 local kind(400) OK")
35
+
36
+ r = c.post("/api/ahs/skills", json={**WEBAPI, "id": "x_bad", "endpoint": "ftp://x"})
37
+ assert r.status_code == 400
38
+ print("[4] 拒非法 endpoint(400) OK")
39
+
40
+ r = c.post("/api/ahs/skills", json={"id": "x_mcp", "name": "m", "kind": "mcp", "side_effect": "read"})
41
+ assert r.status_code == 400
42
+ print("[5] mcp 缺 server/tool(400) OK")
43
+
44
+ r = c.patch("/api/ahs/skills/wx_weather", json={"enabled": False})
45
+ assert r.status_code == 200 and r.json()["skill"]["enabled"] is False
46
+ r = c.patch("/api/ahs/skills/wx_weather", json={"enabled": True})
47
+ assert r.json()["skill"]["enabled"] is True
48
+ print("[6] 启停切换 OK")
49
+
50
+ r = c.get("/api/ahs/skills")
51
+ builtin = next((s["id"] for s in r.json()["skills"] if s["source"] == "builtin"), None)
52
+ if builtin:
53
+ rd = c.delete(f"/api/ahs/skills/{builtin}")
54
+ assert rd.status_code == 409, rd.text
55
+ print("[7] 内置技能不可删(409) OK")
56
+ rc = c.post("/api/ahs/skills", json={**WEBAPI, "id": builtin})
57
+ assert rc.status_code == 409, rc.text
58
+ print("[7b] 用内置 id 安装被拒(409) OK")
59
+ else:
60
+ print("[7] 跳过(无内置技能)")
61
+
62
+ r = c.post("/api/ahs/skills/wx_weather/test", json={"args": {}})
63
+ assert r.status_code == 200 and "success" in r.json()
64
+ print("[8] 测试调用返回 SkillResult OK")
65
+
66
+ r = c.post("/api/ahs/skills", json={"id": "ssrf_t", "name": "s", "description": "", "kind": "web_api",
67
+ "triggers": ["x"], "side_effect": "read", "endpoint": "http://127.0.0.1:8000/api/status"})
68
+ assert r.status_code == 403, r.text
69
+ assert r.json()["detail"]["error"] == "install_denied"
70
+ print("[9] 安装审核门 deny 本地地址(403) OK")
71
+
72
+ _send = {"id": "send_x", "name": "发信", "description": "", "kind": "web_api",
73
+ "triggers": ["发"], "side_effect": "send", "endpoint": "https://example.com/send"}
74
+ r = c.post("/api/ahs/skills", json=_send)
75
+ assert r.status_code == 409 and r.json()["detail"]["needs_ack"] is True, r.text
76
+ r = c.post("/api/ahs/skills", json={**_send, "acknowledged": True})
77
+ assert r.status_code == 200, r.text
78
+ print("[9b] 副作用技能需确认(409→200) OK")
79
+
80
+ assert c.get("/api/ahs/breaker").status_code == 200
81
+ assert c.get("/api/ahs/hooks/audit").status_code == 200
82
+ print("[10] 治理端点(breaker/audit) OK")
83
+
84
+ sk = {s["id"]: s for s in c.get("/api/ahs/skills").json()["skills"]}
85
+ assert sk["calculator"]["trust"] == "high", sk["calculator"]["trust"]
86
+ assert sk["gmail_send"]["trust"] == "low", sk["gmail_send"]["trust"]
87
+ assert sk["wx_weather"]["trust"] == "mid"
88
+ print("[10b] 信任分级 high/mid/low OK")
89
+
90
+ c.post("/api/ahs/policy/deny", json={"id": "wx_weather"})
91
+ r = c.post("/api/ahs/skills/wx_weather/test", json={"args": {}, "confirmed": True}).json()
92
+ assert r["status"] == "blocked" and "封杀" in r["message"], r
93
+ assert next(s for s in c.get("/api/ahs/skills").json()["skills"] if s["id"] == "wx_weather")["blocked"] is True
94
+ assert "wx_weather" in c.get("/api/ahs/policy").json()["deny"]
95
+ assert c.delete("/api/ahs/policy/deny/wx_weather").status_code == 200
96
+ assert "wx_weather" not in c.get("/api/ahs/policy").json()["deny"]
97
+ assert next(s for s in c.get("/api/ahs/skills").json()["skills"] if s["id"] == "wx_weather")["blocked"] is False
98
+ print("[10c] deny 封杀/解封 kill-switch OK")
99
+
100
+ assert c.delete("/api/ahs/skills/wx_weather").status_code == 200
101
+ r = c.get("/api/ahs/skills"); assert "wx_weather" not in {s["id"] for s in r.json()["skills"]}
102
+ print("[11] 删除用户技能 OK")
103
+
104
+ from foxclaw import Foxclaw
105
+ os2 = Foxclaw(data_dir=_DATA)
106
+ assert os2.skill_registry.get("send_x") is not None, "重启后用户技能丢失"
107
+ assert os2.skill_registry.get("send_x").source == "user"
108
+ print("[12] 重启后用户技能仍在(持久化 round-trip) OK")
109
+
110
+ print("\n工具中心第1期验证全部通过")
111
+
112
+ if __name__ == "__main__":
113
+ test_tool_center()
@@ -0,0 +1,113 @@
1
+ """工具调度:只读并发 / 写与未声明串行 / 单工具超时 / 污点回主线程合并。
2
+
3
+ 背景:`agents/mixins/tool_exec.py` 原本逐个 `_dispatch_tool` 串行,一个慢工具阻塞整轮,
4
+ 且完全无单工具超时——模型一次发两个只读工具(联网+召回)要等两倍时间,网络卡死则整轮挂住。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import threading
10
+ import time
11
+
12
+ from foxclaw.agents.mixins import tool_exec as _te
13
+ from foxclaw.agents.mixins.tool_exec import ToolExecMixin
14
+
15
+
16
+ class _Fake(ToolExecMixin):
17
+ """只保留调度层:闸门走真实 registry + permission,执行被 monkeypatch 替换。"""
18
+
19
+ def __init__(self):
20
+ self.calls: list[str] = []
21
+ self._taint_level = 0
22
+
23
+ def _think(self, **kwargs):
24
+ return None, None
25
+
26
+
27
+ def _make(monkeypatch, behavior) -> _Fake:
28
+ fake = _Fake()
29
+
30
+ def _invoke(agent, name, args, taint_sink=None):
31
+ agent.calls.append(name)
32
+ return behavior(name, args, taint_sink)
33
+
34
+ monkeypatch.setattr(_te, "_invoke_tool", _invoke)
35
+ return fake
36
+
37
+
38
+ def _calls(*pairs):
39
+ return [{"name": n, "args": a} for n, a in pairs]
40
+
41
+
42
+ def test_readonly_tools_run_concurrently(monkeypatch) -> None:
43
+ barrier = threading.Barrier(2, timeout=3)
44
+
45
+ def behave(name, args, sink):
46
+ barrier.wait()
47
+ return f"{name}:ok"
48
+
49
+ fake = _make(monkeypatch, behave)
50
+ t0 = time.monotonic()
51
+ out = fake._run_tools(_calls(("web_search", {"query": "python 3.14"}),
52
+ ("recall_memory", {"query": "项目约定"})))
53
+ elapsed = time.monotonic() - t0
54
+ assert out == ["web_search:ok", "recall_memory:ok"]
55
+ assert elapsed < 2.0, f"两个只读工具应并发执行,实际耗时 {elapsed:.2f}s"
56
+
57
+
58
+ def test_slow_tool_is_cut_off(monkeypatch) -> None:
59
+ monkeypatch.setenv("FOXCLAW_TOOL_TIMEOUT", "0.3")
60
+
61
+ def behave(name, args, sink):
62
+ time.sleep(2)
63
+ return "late"
64
+
65
+ fake = _make(monkeypatch, behave)
66
+ t0 = time.monotonic()
67
+ out = fake._dispatch_tool("web_search", {"query": "x"})
68
+ assert "超过" in out and "未返回" in out
69
+ assert "不要编造" in out
70
+ assert time.monotonic() - t0 < 1.5, "超时后必须立刻返回,不能等工具自己结束"
71
+
72
+
73
+ def test_taint_collected_in_worker_and_merged_in_main(monkeypatch) -> None:
74
+ def behave(name, args, sink):
75
+ sink.append(3 if name == "web_search" else 2)
76
+ return "ok"
77
+
78
+ fake = _make(monkeypatch, behave)
79
+ fake._run_tools(_calls(("web_search", {"query": "x"}),
80
+ ("recall_memory", {"query": "y"})))
81
+ assert fake._taint_level == 3, "并发执行后污点应取最大值合并回会话"
82
+
83
+
84
+ def test_single_readonly_call_still_merged(monkeypatch) -> None:
85
+ def behave(name, args, sink):
86
+ sink.append(2)
87
+ return "ok"
88
+
89
+ fake = _make(monkeypatch, behave)
90
+ assert fake._dispatch_tool("recall_memory", {"query": "y"}) == "ok"
91
+ assert fake._taint_level == 2
92
+
93
+
94
+ def test_undeclared_tool_never_executed_and_batch_serialized(monkeypatch) -> None:
95
+ def behave(name, args, sink):
96
+ return "ok"
97
+
98
+ fake = _make(monkeypatch, behave)
99
+ out = fake._run_tools(_calls(("web_search", {"query": "x"}),
100
+ ("write_file", {"path": "a.md"})))
101
+ assert "未知工具 write_file" in out[1]
102
+ assert fake.calls == ["web_search"], "未声明能力的工具必须被闸门挡住,不进入执行"
103
+
104
+
105
+ def test_missing_required_arg_reports_instead_of_executing(monkeypatch) -> None:
106
+ def behave(name, args, sink): # pragma: no cover - 不应被调用
107
+ raise AssertionError("入参校验失败时不该执行工具")
108
+
109
+ fake = _make(monkeypatch, behave)
110
+ out = fake._run_tools([{"name": "web_search", "args": {}}])
111
+ assert "入参不合法" in out[0]
112
+ assert "query" in out[0]
113
+ assert fake.calls == []