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,282 @@
1
+ """`foxclaw skills install` 的来源解析与落地。
2
+
3
+ 全部离线:远端用 monkeypatch 替掉 `_http_get`,不打真网 —— 否则 CI 会因 ClawHub
4
+ 抖动而红,这类测试一旦不稳就会被跳过,等于没测。
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import io
9
+ import json
10
+ import tarfile
11
+ import zipfile
12
+
13
+ import pytest
14
+
15
+ from foxclaw.skills import fetch as fetch_mod
16
+ from foxclaw.skills.archive import PackageError, has_code, read_package
17
+
18
+ MD = "---\nname: Demo Skill\ndescription: 演示用技能\nversion: 1.2.3\n---\n\n正文指示"
19
+
20
+
21
+ def _zip(files: dict) -> bytes:
22
+ buf = io.BytesIO()
23
+ with zipfile.ZipFile(buf, "w") as z:
24
+ for name, body in files.items():
25
+ z.writestr(name, body)
26
+ return buf.getvalue()
27
+
28
+
29
+ def _tgz(files: dict) -> bytes:
30
+ buf = io.BytesIO()
31
+ with tarfile.open(fileobj=buf, mode="w:gz") as t:
32
+ for name, text in files.items():
33
+ raw = text.encode("utf-8")
34
+ info = tarfile.TarInfo(name)
35
+ info.size = len(raw)
36
+ t.addfile(info, io.BytesIO(raw))
37
+ return buf.getvalue()
38
+
39
+
40
+ def _mock_http(monkeypatch, routes: dict) -> None:
41
+ def fake(url: str, timeout: float = 25.0, max_bytes: int = 10 * 1024 * 1024):
42
+ for key, val in routes.items():
43
+ if key in url:
44
+ return val
45
+ raise PackageError(f"测试未配置这条路由: {url}")
46
+
47
+ monkeypatch.setattr(fetch_mod, "_http_get", fake)
48
+
49
+
50
+ def test_read_zip_root_skill_md():
51
+ pkg = read_package(_zip({"SKILL.md": MD, "_meta.json": '{"slug": "demo"}'}))
52
+ assert pkg["skill_md"] == MD
53
+ assert pkg["meta"]["slug"] == "demo"
54
+
55
+
56
+ def test_read_targz_picks_shallowest():
57
+ pkg = read_package(_tgz({
58
+ "repo-abc/SKILL.md": MD,
59
+ "repo-abc/docs/SKILL.md": "更深的同名副本应被忽略",
60
+ }))
61
+ assert pkg["skill_md"] == MD
62
+
63
+
64
+ def test_read_package_rejects_traversal():
65
+ with pytest.raises(PackageError):
66
+ read_package(_zip({"../evil/SKILL.md": MD}))
67
+
68
+
69
+ def test_read_package_without_skill_md():
70
+ with pytest.raises(PackageError, match="未找到 SKILL.md"):
71
+ read_package(_zip({"README.md": "nothing here"}))
72
+
73
+
74
+ def test_subpath_narrows_search():
75
+ with pytest.raises(PackageError):
76
+ read_package(_tgz({"repo/a/SKILL.md": MD}), subpath="other")
77
+
78
+
79
+ def test_has_code_detects_scripts():
80
+ assert has_code(["SKILL.md", "scripts/run.py"])
81
+ assert has_code(["SKILL.md", "tool.sh"])
82
+ assert not has_code(["SKILL.md", "skill-card.md"])
83
+
84
+
85
+ def test_ref_routes_scoped_name_to_clawhub(monkeypatch):
86
+ seen: dict = {}
87
+
88
+ def fake(owner, slug, timeout):
89
+ seen["id"] = (owner, slug)
90
+ return fetch_mod.SkillPackage(skill_md=MD, source="clawhub", origin="x")
91
+
92
+ monkeypatch.setattr(fetch_mod, "_from_clawhub", fake)
93
+ assert fetch_mod.fetch("@snowfox/demo").origin == "x"
94
+ assert seen["id"] == ("snowfox", "demo")
95
+ fetch_mod.fetch("snowfox/demo")
96
+ assert seen["id"] == ("snowfox", "demo")
97
+
98
+
99
+ def test_unknown_ref_rejected():
100
+ with pytest.raises(PackageError, match="无法识别"):
101
+ fetch_mod.fetch("no-such-thing-xyz")
102
+
103
+
104
+ def test_local_dir_reads_skill_md(tmp_path):
105
+ (tmp_path / "SKILL.md").write_text(MD, encoding="utf-8")
106
+ pkg = fetch_mod.fetch(str(tmp_path))
107
+ assert pkg.source == "local"
108
+ assert "演示用技能" in pkg.skill_md
109
+
110
+
111
+ def test_local_dir_without_skill_md(tmp_path):
112
+ with pytest.raises(PackageError):
113
+ fetch_mod.fetch(str(tmp_path))
114
+
115
+
116
+ def test_clawhub_blocks_malware(monkeypatch):
117
+ _mock_http(monkeypatch, {
118
+ "/api/v1/skills/": (json.dumps({
119
+ "moderation": {"isMalwareBlocked": True, "summary": "窃取密钥"},
120
+ "latestVersion": {"version": "1.0.0"},
121
+ }).encode(), "application/json"),
122
+ })
123
+ with pytest.raises(fetch_mod.MalwareBlocked):
124
+ fetch_mod.fetch("@bad/skill")
125
+
126
+
127
+ def test_clawhub_github_handoff(monkeypatch):
128
+ """download 返回 JSON 交接(GitHub 托底)时要改下 archiveUrl,而不是当 zip 解。"""
129
+ _mock_http(monkeypatch, {
130
+ "/api/v1/skills/": (json.dumps({
131
+ "moderation": {"isSuspicious": True},
132
+ "latestVersion": {"version": "2.0.0"},
133
+ }).encode(), "application/json"),
134
+ "/api/v1/download": (json.dumps({
135
+ "archiveUrl": "https://codeload.example/archive.tar.gz",
136
+ }).encode(), "application/json"),
137
+ "archive.tar.gz": (_tgz({"repo-main/SKILL.md": MD}), "application/gzip"),
138
+ })
139
+ pkg = fetch_mod.fetch("@snowfox/demo")
140
+ assert pkg.version == "2.0.0"
141
+ assert pkg.suspicious is True
142
+ assert "repo-main/SKILL.md" in pkg.files
143
+
144
+
145
+ def test_clawhub_not_found(monkeypatch):
146
+ def boom(url, timeout=25.0, max_bytes=10 * 1024 * 1024):
147
+ raise fetch_mod.HttpError(404, url, "Not found")
148
+
149
+ monkeypatch.setattr(fetch_mod, "_http_get", boom)
150
+ with pytest.raises(PackageError, match="找不到"):
151
+ fetch_mod.fetch("@ghost/missing")
152
+
153
+
154
+ def test_github_subpath(monkeypatch):
155
+ _mock_http(monkeypatch, {
156
+ "codeload.github.com": (_tgz({
157
+ "repo-main/skills/demo/SKILL.md": MD,
158
+ "repo-main/skills/other/SKILL.md": "x",
159
+ }), "application/gzip"),
160
+ })
161
+ pkg = fetch_mod.fetch("github:snowfox/repo/skills/demo@main")
162
+ assert pkg.source == "github"
163
+ assert pkg.skill_md == MD
164
+ assert pkg.origin == "github:snowfox/repo/skills/demo@main"
165
+
166
+
167
+ def _src(tmp_path):
168
+ src = tmp_path / "src"
169
+ src.mkdir()
170
+ (src / "SKILL.md").write_text(MD, encoding="utf-8")
171
+ return src
172
+
173
+
174
+ def test_cli_install_writes_file_when_service_down(tmp_path, monkeypatch):
175
+ from foxclaw import cli
176
+ from foxclaw.skills import install_cmd
177
+
178
+ data = tmp_path / "data"
179
+ monkeypatch.setenv("FOXCLAW_DATA_DIR", str(data))
180
+ monkeypatch.setattr(install_cmd, "_api_alive", lambda base: False)
181
+
182
+ assert cli.main(["skills", "install", str(_src(tmp_path)), "--yes"]) == 0
183
+ saved = json.loads((data / "markdown_skills.json").read_text(encoding="utf-8"))
184
+ assert [s["id"] for s in saved["skills"]] == ["demo_skill"]
185
+ assert saved["skills"][0]["version"] == "1.2.3"
186
+
187
+
188
+ def test_cli_install_respects_explicit_id(tmp_path, monkeypatch):
189
+ from foxclaw import cli
190
+ from foxclaw.skills import install_cmd
191
+
192
+ data = tmp_path / "data"
193
+ monkeypatch.setenv("FOXCLAW_DATA_DIR", str(data))
194
+ monkeypatch.setattr(install_cmd, "_api_alive", lambda base: False)
195
+
196
+ assert cli.main(["skills", "install", str(_src(tmp_path)), "--yes",
197
+ "--id", "my_demo"]) == 0
198
+ saved = json.loads((data / "markdown_skills.json").read_text(encoding="utf-8"))
199
+ assert saved["skills"][0]["id"] == "my_demo"
200
+
201
+
202
+ def test_cli_install_refuses_without_confirmation(tmp_path, monkeypatch):
203
+ from foxclaw import cli
204
+ from foxclaw.skills import install_cmd
205
+
206
+ monkeypatch.setenv("FOXCLAW_DATA_DIR", str(tmp_path / "data"))
207
+ monkeypatch.setattr(install_cmd, "_api_alive", lambda base: False)
208
+ monkeypatch.setattr(install_cmd, "_confirm", lambda: False)
209
+
210
+ assert cli.main(["skills", "install", str(_src(tmp_path))]) == 1
211
+ assert not (tmp_path / "data" / "markdown_skills.json").exists()
212
+
213
+
214
+ def test_cli_list_reads_file_when_service_down(tmp_path, monkeypatch, capsys):
215
+ from foxclaw import cli
216
+ from foxclaw.skills import install_cmd
217
+
218
+ data = tmp_path / "data"
219
+ monkeypatch.setenv("FOXCLAW_DATA_DIR", str(data))
220
+ monkeypatch.setattr(install_cmd, "_api_alive", lambda base: False)
221
+ cli.main(["skills", "install", str(_src(tmp_path)), "--yes"])
222
+ capsys.readouterr()
223
+
224
+ assert cli.main(["skills", "list", "--json"]) == 0
225
+ got = json.loads(capsys.readouterr().out)
226
+ assert got["source"] == "file"
227
+ assert got["skills"][0]["id"] == "demo_skill"
228
+
229
+
230
+ def test_cli_remove_deletes_skill(tmp_path, monkeypatch):
231
+ from foxclaw import cli
232
+ from foxclaw.skills import install_cmd
233
+
234
+ data = tmp_path / "data"
235
+ monkeypatch.setenv("FOXCLAW_DATA_DIR", str(data))
236
+ monkeypatch.setattr(install_cmd, "_api_alive", lambda base: False)
237
+ cli.main(["skills", "install", str(_src(tmp_path)), "--yes"])
238
+
239
+ assert cli.main(["skills", "remove", "demo_skill"]) == 0
240
+ saved = json.loads((data / "markdown_skills.json").read_text(encoding="utf-8"))
241
+ assert saved["skills"] == []
242
+
243
+
244
+ def test_cli_remove_unknown_id_fails(tmp_path, monkeypatch):
245
+ from foxclaw import cli
246
+ from foxclaw.skills import install_cmd
247
+
248
+ monkeypatch.setenv("FOXCLAW_DATA_DIR", str(tmp_path / "data"))
249
+ monkeypatch.setattr(install_cmd, "_api_alive", lambda base: False)
250
+ assert cli.main(["skills", "remove", "nope"]) == 1
251
+
252
+
253
+ def test_cli_install_uses_api_when_service_alive(tmp_path, monkeypatch):
254
+ """服务在跑时必须打 API:直接改 json 会被运行中注册表的下次 save 整份覆盖。"""
255
+ from foxclaw import cli
256
+ from foxclaw.skills import install_cmd
257
+
258
+ monkeypatch.setenv("FOXCLAW_DATA_DIR", str(tmp_path / "data"))
259
+ monkeypatch.setattr(install_cmd, "_api_alive", lambda base: True)
260
+ calls: list = []
261
+
262
+ def fake_json(base, method, path, payload=None):
263
+ calls.append((method, path, payload))
264
+ return {"created": True}
265
+
266
+ monkeypatch.setattr(install_cmd, "_api_json", fake_json)
267
+
268
+ assert cli.main(["skills", "install", str(_src(tmp_path)), "--yes"]) == 0
269
+ method, path, payload = calls[0]
270
+ assert (method, path) == ("POST", "/api/ahs/md-skills")
271
+ assert payload["acknowledged"] is True
272
+ assert payload["id"] == "demo_skill"
273
+ assert "正文指示" in payload["content"]
274
+ assert not (tmp_path / "data" / "markdown_skills.json").exists()
275
+
276
+
277
+ def test_local_zip_package(tmp_path):
278
+ p = tmp_path / "demo.zip"
279
+ p.write_bytes(_zip({"SKILL.md": MD, "_meta.json": '{"slug": "pack"}'}))
280
+ pkg = fetch_mod.fetch(str(p))
281
+ assert pkg.source == "local"
282
+ assert pkg.meta["slug"] == "pack"
@@ -0,0 +1,162 @@
1
+ """
2
+ 记忆统计 + 活动事件 回归测试(全离线)。
3
+
4
+ 覆盖两个实锤 bug 的修复:
5
+ 1. hmr.stats() 从 vector_store 探测 count/size/memories 取 total —— 但真实记忆在 memory_fs,
6
+ vector_store 只存嵌入且无这些属性 → total 恒为 0(而 relations 却有值,自相矛盾)。
7
+ 修复:改为数 memory_fs.list_memories();by_agent 从 tags 里的 "agent:<id>" 解析。
8
+ 2. 对话路径(stream_reply)此前不发任何事件 → /bus/history 恒空、前端"活动"页永远没内容。
9
+ 修复:对话完成后 publish 一条 chat.completed。
10
+ """
11
+ import os, sys, types, threading
12
+ from pathlib import Path
13
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
14
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
15
+ os.environ["FOXCLAW_EMBEDDING_MODEL"] = "foxclaw-nonexistent-fast"
16
+ sys.path.insert(0, str(Path(__file__).parent.parent))
17
+
18
+ from foxclaw.core.memory_adapter import HMRAdapter
19
+ from foxclaw.agents.base import CognitiveAgent
20
+
21
+
22
+ def _mem(tags):
23
+ return types.SimpleNamespace(tags=list(tags))
24
+
25
+
26
+ def _adapter_with(hmr_obj):
27
+ """构造最小 self 调用 HMRAdapter.stats(不加载真实 HMR)。"""
28
+ s = types.SimpleNamespace()
29
+ s._lock = threading.RLock()
30
+ s._hmr = hmr_obj
31
+ s.version = "2.0.0"
32
+ s.backend = "HMR"
33
+ return s
34
+
35
+
36
+ def test_stats_counts_from_memory_fs_not_vector_store():
37
+ """核心回归:记忆在 memory_fs;vector_store 没有 count/size/memories 也不能让 total 归零。"""
38
+ mems = [_mem(["agent:supervisor"]), _mem(["agent:supervisor"]),
39
+ _mem(["agent:finance", "shared"]), _mem(["agent:office"])]
40
+ hmr = types.SimpleNamespace(
41
+ memory_fs=types.SimpleNamespace(list_memories=lambda: mems),
42
+ vector_store=types.SimpleNamespace(),
43
+ memory_graph=None)
44
+ out = HMRAdapter.stats(_adapter_with(hmr))
45
+ assert out["total_memories"] == 4, "必须从 memory_fs 数出真实记忆数"
46
+ assert out["by_agent"] == {"supervisor": 2, "finance": 1, "office": 1}
47
+ assert out["shared"] == 1 and out["private"] == 3
48
+
49
+
50
+ def test_stats_falls_back_to_vector_store_vectors():
51
+ """memory_fs 不可用时,兜底数 vector_store.vectors(dict)。"""
52
+ hmr = types.SimpleNamespace(
53
+ memory_fs=None,
54
+ vector_store=types.SimpleNamespace(vectors={"a": 1, "b": 2, "c": 3}),
55
+ memory_graph=None)
56
+ out = HMRAdapter.stats(_adapter_with(hmr))
57
+ assert out["total_memories"] == 3
58
+
59
+
60
+ def test_stats_relations_from_graph_unchanged():
61
+ """relations 仍来自 memory_graph.edges(这条原本就是对的,不能改坏)。"""
62
+ hmr = types.SimpleNamespace(
63
+ memory_fs=types.SimpleNamespace(list_memories=lambda: []),
64
+ vector_store=None,
65
+ memory_graph=types.SimpleNamespace(edges=[1, 2, 3, 4, 5]))
66
+ out = HMRAdapter.stats(_adapter_with(hmr))
67
+ assert out["relations"] == 5
68
+
69
+
70
+ def test_stats_survives_broken_hmr():
71
+ """HMR 结构异常时不抛,返回零值(fail-safe,前端不白屏)。"""
72
+ hmr = types.SimpleNamespace(memory_fs=None, vector_store=None, memory_graph=None)
73
+ out = HMRAdapter.stats(_adapter_with(hmr))
74
+ assert out["total_memories"] == 0 and out["relations"] == 0
75
+
76
+
77
+ def test_derive_title_from_content():
78
+ d = HMRAdapter._derive_title
79
+ assert d("对话:你好 → 你好呀!周五中午,正是一周里…") == "你好"
80
+ assert d("用户偏好:我喜欢简洁的回答") == "我喜欢简洁的回答"
81
+ assert d("把这份报告存入HMR → 好的,我现在") == "把这份报告存入HMR"
82
+ assert d("") == ""
83
+ assert len(d("很长" * 50)) <= 24
84
+
85
+
86
+ def test_recent_replaces_timestamp_titles():
87
+ """HMR 默认标题是 "Concept 03:11"(纯时间戳)→ 展示时必须现推可读标题(老数据也要好看)。"""
88
+ m = types.SimpleNamespace(id="m1", type="concept", title="Concept 03:11",
89
+ semantic_summary="", content="对话:今天是几号 → 今天是 7 月 24 日",
90
+ tags=["agent:supervisor"], created_at=None)
91
+ s = HMRAdapter.__new__(HMRAdapter)
92
+ s._lock = threading.RLock(); s.version = "2.0.0"; s.backend = "HMR"
93
+ s._hmr = types.SimpleNamespace(memory_fs=types.SimpleNamespace(list_memories=lambda: [m]))
94
+ out = s.recent(5)
95
+ assert out[0]["title"] == "今天是几号", "时间戳标题必须被替换成可读标题"
96
+ assert out[0]["agent"] == "supervisor"
97
+
98
+
99
+ def test_recent_keeps_meaningful_title():
100
+ """已有可读标题的不许被覆盖。"""
101
+ m = types.SimpleNamespace(id="m2", type="concept", title="季度财务复盘",
102
+ semantic_summary="", content="随便什么内容",
103
+ tags=[], created_at=None)
104
+ s = HMRAdapter.__new__(HMRAdapter)
105
+ s._lock = threading.RLock(); s.version = "2.0.0"; s.backend = "HMR"
106
+ s._hmr = types.SimpleNamespace(memory_fs=types.SimpleNamespace(list_memories=lambda: [m]))
107
+ assert s.recent(5)[0]["title"] == "季度财务复盘"
108
+
109
+
110
+ def test_chat_publishes_activity_event(monkeypatch):
111
+ """核心回归:对话完成后必须往事件脊柱发 chat.completed,否则"活动"页永远空。"""
112
+ monkeypatch.setenv("FOXCLAW_TOOL_USE", "1")
113
+ published = []
114
+ spine = types.SimpleNamespace(
115
+ publish=lambda topic, payload, source="": published.append((topic, payload, source)))
116
+
117
+ fake = types.SimpleNamespace()
118
+ fake._caps = {"event_spine": spine}
119
+ fake._sanitize_history = lambda h, max_turns=12: []
120
+ fake.router = types.SimpleNamespace(supports_tools=lambda: True)
121
+ fake.AGENT_ID = "assistant"
122
+ fake._ability_augment = lambda t, m: m
123
+ fake._skill_augment = lambda t, m: m
124
+ fake._pref_augment = lambda m: m
125
+ fake._reply_with_tools = lambda task, messages, stop_event=None: "这是回复内容。"
126
+ fake._think = lambda **k: None
127
+ fake._store_memory = lambda *a, **k: None
128
+ fake._maybe_store_preference = lambda t: None
129
+
130
+ out = "".join(CognitiveAgent.stream_reply(fake, "帮我看看这个"))
131
+ assert "回复内容" in out
132
+ topics = [t for t, _, _ in published]
133
+ assert "chat.completed" in topics, "对话路径必须发 chat.completed 事件"
134
+ payload = [p for t, p, _ in published if t == "chat.completed"][0]
135
+ assert payload["agent_id"] == "assistant"
136
+ assert payload["reply_chars"] > 0
137
+ assert payload["task"].startswith("帮我看看")
138
+
139
+
140
+ def test_chat_event_absent_without_spine():
141
+ """无事件脊柱(standalone/治理场景)时静默跳过,不得抛错。"""
142
+ fake = types.SimpleNamespace()
143
+ fake._caps = None
144
+ fake._sanitize_history = lambda h, max_turns=12: []
145
+ fake.router = types.SimpleNamespace(supports_tools=lambda: False)
146
+ fake.AGENT_ID = "assistant"
147
+ fake._web_augment = lambda t, m: m
148
+ fake._ability_augment = lambda t, m: m
149
+ fake._skill_augment = lambda t, m: m
150
+ fake._pref_augment = lambda m: m
151
+ fake.system_prompt = "sys"
152
+ fake.router.stream = lambda *a, **k: iter(["好", "的"])
153
+ fake._think = lambda **k: None
154
+ fake._store_memory = lambda *a, **k: None
155
+ fake._maybe_store_preference = lambda t: None
156
+ out = "".join(CognitiveAgent.stream_reply(fake, "你好"))
157
+ assert out == "好的"
158
+
159
+
160
+ if __name__ == "__main__":
161
+ import pytest
162
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,55 @@
1
+ """LLM 流式输出(方案A)验证:router.stream 分块 + /api/chat/stream SSE 逐字返回。"""
2
+ import os, sys, tempfile, json
3
+ from pathlib import Path
4
+ sys.path.insert(0, str(Path(__file__).parent.parent))
5
+ import pytest
6
+
7
+
8
+ def test_streaming():
9
+ if "foxclaw.api" in sys.modules:
10
+ pytest.skip("需独立进程运行(模块级单例已固化),请用 tests/run_all.py")
11
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
12
+ os.environ.setdefault("FOXCLAW_EMBEDDING_MODEL", "foxclaw-nonexistent-fast")
13
+ os.environ["FOXCLAW_STREAM_RATE"] = "1"
14
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_stream_")
15
+
16
+ print("=" * 60); print("LLM 流式输出验证"); print("=" * 60)
17
+
18
+ from foxclaw.core.model_router import ModelRouter
19
+ r = ModelRouter()
20
+ chunks = list(r.stream("sys", [{"role": "user", "content": "你好世界这是一段用于测试流式分块的文本"}]))
21
+ assert len(chunks) > 1, f"应多块: {len(chunks)}"
22
+ assert "".join(chunks), "拼接应非空"
23
+ print(f"[1] router.stream stub 分块(chunk={len(chunks)}) OK")
24
+
25
+ from fastapi.testclient import TestClient
26
+ from foxclaw.api import app
27
+ with TestClient(app) as c:
28
+ with c.stream("POST", "/api/chat/stream",
29
+ json={"task": "简单介绍一下你自己", "agent_id": "supervisor"}) as resp:
30
+ assert resp.status_code == 200, resp.status_code
31
+ deltas, done = [], False
32
+ for line in resp.iter_lines():
33
+ if not line or not line.startswith("data: "):
34
+ continue
35
+ ev = json.loads(line[6:])
36
+ if "delta" in ev:
37
+ deltas.append(ev["delta"])
38
+ if ev.get("done"):
39
+ done = True
40
+ assert len(deltas) > 0, "应有 delta 事件"
41
+ assert done, "应以 done 收尾"
42
+ assert "".join(deltas), "拼接应非空"
43
+ print(f"[2] /api/chat/stream SSE(delta={len(deltas)}, done) OK")
44
+
45
+ with c.stream("POST", "/api/chat/stream",
46
+ json={"task": "再来一次", "agent_id": "supervisor"}) as resp:
47
+ txt = "".join(resp.iter_text())
48
+ assert "rate limited" in txt, f"限流未生效: {txt[:120]}"
49
+ print("[3] /api/chat/stream 令牌桶限流 OK")
50
+
51
+ print("\nLLM 流式输出验证全部通过")
52
+
53
+
54
+ if __name__ == "__main__":
55
+ test_streaming()
@@ -0,0 +1,176 @@
1
+ """P1-1 · 主管 pipeline 化回归。
2
+
3
+ 主管不再重写 run_task,路由分发拆成 8 个 pipeline stage
4
+ (recall→meta_check→schedule→route→plan→dispatch→replan→aggregate),走 base 统一模板
5
+ `_run_task_core`。本文件锁定:
6
+
7
+ [1] 结构:SupervisorAgent.run_task 即基类模板;主管 pipeline 是 SUPERVISOR_STAGES;
8
+ [2] 元问题短路:meta_check 自答,后续 stage 跳过,内部标记不外泄;
9
+ [3] 单 Agent 分发:返回体顶层键与旧实现逐字一致(result/schedule/plan/process_id);
10
+ [4] stub 兜底:无关键词命中 + stub → 兜底派发 assistant(不经自答);
11
+ [5] 统一 execution.completed:多步并行任务也只发**一条**任务级事件、
12
+ agent_id=supervisor(文稿 Step 4:删手动 bus.publish);blocked 子任务 →
13
+ 顶层 status=blocked 且执行事件 blocked=True(未完成不沉淀真值);
14
+ [6] 流式:run_task_stream 逐 stage 出 8 个进度事件;子 Agent 的 act token 随
15
+ contextvar 传播流出(delta 非空);stop_event 预置 → interrupted;
16
+ [7] 恢复记账:经 base 生命周期钩子,started/finished 配对、异常路径记 failed。
17
+
18
+ 全离线:ws.needs_search 关掉联网;router 强制 stub(共享实例,五个 Agent 同源)。
19
+ """
20
+ import asyncio
21
+ import sys
22
+ import tempfile
23
+ import threading
24
+ import types
25
+ from pathlib import Path
26
+ sys.path.insert(0, str(Path(__file__).parent.parent))
27
+
28
+ import foxclaw.core.web_search as ws
29
+ ws.needs_search = lambda q: False
30
+
31
+ from foxclaw import Foxclaw
32
+ from foxclaw.agents.base import CognitiveAgent, HarnessBlockedError
33
+ from foxclaw.agents.agents import SupervisorAgent
34
+ from foxclaw.pipeline import SUPERVISOR_STAGES
35
+
36
+ EXPECTED_STAGES = ["recall", "meta_check", "schedule", "route", "plan", "dispatch", "replan", "aggregate"]
37
+
38
+
39
+ def check(cond, msg):
40
+ if not cond:
41
+ raise AssertionError(f"✗ {msg}")
42
+ print(f" ✓ {msg}")
43
+
44
+
45
+ def _names():
46
+ return [getattr(s, "stage_name", getattr(s, "__name__", "?")) for s in SUPERVISOR_STAGES]
47
+
48
+
49
+ def main():
50
+ print("=" * 60)
51
+ print("P1-1 · 主管 pipeline 化回归")
52
+ print("=" * 60)
53
+
54
+ print("\n【1】结构:主管走统一模板,不再重写 run_task")
55
+ check(SupervisorAgent.run_task is CognitiveAgent.run_task,
56
+ "SupervisorAgent.run_task 即基类模板(P1-1 达成)")
57
+ check(SupervisorAgent._cognitive_pipeline is not CognitiveAgent._cognitive_pipeline,
58
+ "主管覆盖 _cognitive_pipeline(路由分发走 SUPERVISOR_STAGES)")
59
+ check(_names() == EXPECTED_STAGES, f"主管 stage 序列={EXPECTED_STAGES}(实为 {_names()})")
60
+
61
+ os_ = Foxclaw(data_dir=tempfile.mkdtemp(prefix="foxclaw_svpipe_"))
62
+ os_.router.provider = "stub"
63
+ os_.router._client = None
64
+ sup = os_.supervisor
65
+
66
+ print("\n【2】元问题短路:meta_check 自答,内部标记不外泄")
67
+ r = asyncio.run(sup.run_task("你是谁,能做什么"))
68
+ res = r["result"]
69
+ check(res.get("dispatched_to") == "supervisor" and res.get("self_answered") is True,
70
+ "元问题 → 主管自答(dispatched_to=supervisor, self_answered=True)")
71
+ check("_meta_answered" not in r and "_self_answer" not in r,
72
+ "内部短路标记不外泄到返回体")
73
+ check(r.get("agent") == "supervisor" and "session_id" in r and "thought_summary" in r,
74
+ "返回体由 base 模板统一组装(agent/session_id/thought_summary)")
75
+
76
+ print("\n【3】单 Agent 分发:返回体顶层键与旧实现逐字一致")
77
+ r = asyncio.run(sup.run_task("帮我做个预算分析"))
78
+ res = r["result"]
79
+ check(res.get("dispatched_to") == "finance", f"关键词命中 → finance(实为 {res.get('dispatched_to')})")
80
+ for k in ("schedule", "plan", "process_id"):
81
+ check(k in r, f"顶层键 {k} 保留(与旧主管返回体一致)")
82
+ check(r["plan"].get("multi_step") is False, "单步任务 plan.multi_step=False")
83
+ check(res.get("blocked") is False, "正常分发 blocked=False")
84
+
85
+ print("\n【4】stub 兜底:无关键词命中 + stub → 主管自处理(助手已下放为技能)")
86
+ r = asyncio.run(sup.run_task("解释一下什么是航天器"))
87
+ res = r["result"]
88
+ check(res.get("dispatched_to") == "supervisor" and not res.get("self_answered"),
89
+ f"stub 下未命中 → 主管自身处理(实为 {res.get('dispatched_to')})")
90
+
91
+ print("\n【5】统一 execution.completed(文稿 Step 4)")
92
+ spine = os_.event_spine
93
+ before = len(spine.history("execution.completed"))
94
+ asyncio.run(sup.run_task("同时查今天的日程、分析本月理财、总结这篇论文"))
95
+ evts = spine.history("execution.completed")
96
+ new_evts = evts[before:]
97
+ check(len(new_evts) == 1,
98
+ f"并行 3 子任务只发 1 条任务级 execution.completed(实为 {len(new_evts)} 条)"
99
+ "——逐子任务手动 publish 已删")
100
+ check(new_evts and new_evts[0]["payload"].get("agent_id") == "supervisor",
101
+ "任务级事件归因 supervisor(子 Agent 带 _no_emit 不双发)")
102
+
103
+ print("\n【5b】blocked 子任务 → 顶层 status=blocked + 事件 blocked=True(不沉淀真值)")
104
+ async def _boom(self, task, context, session_id, memories):
105
+ raise HarnessBlockedError(types.SimpleNamespace(reason="模拟越界"), None)
106
+ os_.finance._execute = types.MethodType(_boom, os_.finance)
107
+ before = len(spine.history("execution.completed"))
108
+ r = asyncio.run(sup.run_task("帮我做个预算分析"))
109
+ check(r["result"].get("blocked") is True, "子 Agent 被拦 → result.blocked=True")
110
+ check(r.get("status") == "blocked",
111
+ "被拦提升为顶层 status=blocked(与 base 模板 skill_status 机制一致)")
112
+ new_evts = spine.history("execution.completed")[before:]
113
+ check(len(new_evts) == 1 and new_evts[0]["payload"].get("blocked") is True,
114
+ "被拦任务的执行事件 blocked=True → 真值链不沉淀")
115
+ del os_.finance._execute
116
+
117
+ print("\n【6】流式:run_task_stream 八 stage + 子 Agent token 传播 + 中断")
118
+ events = list(sup.run_task_stream("帮我做个预算分析"))
119
+ stages = [e["stage"] for e in events if "stage" in e]
120
+ deltas = [e["delta"] for e in events if "delta" in e]
121
+ results = [e["result"] for e in events if "result" in e]
122
+ check(stages == EXPECTED_STAGES, f"流式逐 stage 进度=主管八 stage(实为 {stages})")
123
+ check(len(results) == 1 and results[0].get("agent") == "supervisor",
124
+ "恰一个最终 result,agent=supervisor")
125
+ check(results[0]["result"].get("dispatched_to") == "finance", "流式与非流式路由结论一致")
126
+ check(len(deltas) > 0,
127
+ "delta 非空:子 Agent 的 act token 随 _act_stream contextvar 传播流出(文稿坑5实测反转)")
128
+ check("".join(deltas) == str(results[0]["result"].get("sub_result")),
129
+ "流式 token 拼接 == 子 Agent 最终结果(透传忠实)")
130
+
131
+ ev = threading.Event(); ev.set()
132
+ ev_events = list(sup.run_task_stream("这个任务会被中断", stop_event=ev))
133
+ ev_results = [e["result"] for e in ev_events if "result" in e]
134
+ check(ev_results and ev_results[0].get("interrupted") is True,
135
+ "stop_event 预置 → result.interrupted=True(stage 边界优雅停)")
136
+ check(not [e for e in ev_events if "stage" in e], "起步即停:无 stage 进度事件")
137
+
138
+ print("\n【7】恢复记账:base 生命周期钩子,started/finished 配对")
139
+ calls = []
140
+
141
+ class _Spy:
142
+ def task_started(self, task_id, description, session_id, channel=""):
143
+ calls.append(("started", task_id))
144
+ def task_finished(self, sid, status="done"):
145
+ calls.append(("finished", sid, status))
146
+
147
+ sup._recovery = _Spy()
148
+ asyncio.run(sup.run_task("帮我做个预算分析", context={"channel": "test"}))
149
+ check(calls[0][0] == "started" and calls[1][0] == "finished" and calls[1][2] == "done",
150
+ f"正常任务:started→finished(done) 配对(实为 {calls})")
151
+ check(calls[0][1] == calls[1][1], "started/finished 同一 task_id(可配对)")
152
+
153
+ calls.clear()
154
+ async def _raise(self, task, context=None, **kw):
155
+ raise RuntimeError("子 Agent 崩溃")
156
+ sup._subagents["finance"].run_task = _raise
157
+ try:
158
+ asyncio.run(sup.run_task("帮我做个预算分析"))
159
+ raised = False
160
+ except RuntimeError:
161
+ raised = True
162
+ check(raised, "子 Agent 未兜底异常向上传播(不吞)")
163
+ check(calls[0][0] == "started" and calls[1] == ("finished", calls[0][1], "failed"),
164
+ f"异常任务记 failed(区别于正常完成)(实为 {calls})")
165
+
166
+ print("\n" + "=" * 60)
167
+ print("P1-1 主管 pipeline 化回归通过")
168
+ print("=" * 60)
169
+
170
+
171
+ def test_supervisor_pipeline():
172
+ main()
173
+
174
+
175
+ if __name__ == "__main__":
176
+ main()