foxclaw 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +11 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +165 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +122 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/Dockerfile +64 -0
  11. package/python/LICENSE +21 -0
  12. package/python/MANIFEST.in +14 -0
  13. package/python/README.md +192 -0
  14. package/python/README_CN.md +324 -0
  15. package/python/bench/bm25_audit.py +199 -0
  16. package/python/bench/bm25_regression.py +178 -0
  17. package/python/bench/bm25_stress.py +159 -0
  18. package/python/bench/cd_audit.py +238 -0
  19. package/python/bench/chaos.py +142 -0
  20. package/python/bench/concurrency.py +103 -0
  21. package/python/bench/e2e_real_models.py +95 -0
  22. package/python/bench/llm_retry_test.py +248 -0
  23. package/python/bench/load_test.py +196 -0
  24. package/python/bench/vector_ivf_upgrade.py +134 -0
  25. package/python/docs/CHANNELS.md +195 -0
  26. package/python/docs/GOVERNANCE.md +164 -0
  27. package/python/docs/RELEASE.md +109 -0
  28. package/python/docs/RUN.md +75 -0
  29. package/python/docs/RUN_CN.md +121 -0
  30. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  31. package/python/evals/__init__.py +0 -0
  32. package/python/evals/governance/README.md +61 -0
  33. package/python/evals/governance/__init__.py +0 -0
  34. package/python/evals/governance/cases.py +152 -0
  35. package/python/evals/governance/compare_embeddings.py +94 -0
  36. package/python/evals/governance/convergence.svg +11 -0
  37. package/python/evals/governance/report.json +371 -0
  38. package/python/evals/governance/run_evals.py +400 -0
  39. package/python/examples/governance_middleware_demo.py +77 -0
  40. package/python/examples/langgraph_governance_demo.py +108 -0
  41. package/python/examples/sample_itinerary.csv +6 -0
  42. package/python/examples/sample_knowledge.csv +9 -0
  43. package/python/examples/sample_transactions.csv +14 -0
  44. package/python/foxclaw/__init__.py +526 -0
  45. package/python/foxclaw/agents/__init__.py +12 -0
  46. package/python/foxclaw/agents/agents.py +35 -0
  47. package/python/foxclaw/agents/base.py +865 -0
  48. package/python/foxclaw/agents/impl/__init__.py +1 -0
  49. package/python/foxclaw/agents/impl/_helpers.py +78 -0
  50. package/python/foxclaw/agents/impl/finance.py +248 -0
  51. package/python/foxclaw/agents/impl/office.py +367 -0
  52. package/python/foxclaw/agents/impl/supervisor.py +659 -0
  53. package/python/foxclaw/agents/mixins/_context.py +151 -0
  54. package/python/foxclaw/agents/mixins/augment.py +149 -0
  55. package/python/foxclaw/agents/mixins/llm.py +66 -0
  56. package/python/foxclaw/agents/mixins/memory.py +35 -0
  57. package/python/foxclaw/agents/mixins/preference.py +81 -0
  58. package/python/foxclaw/agents/mixins/prompt.py +77 -0
  59. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  60. package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
  61. package/python/foxclaw/api/__init__.py +283 -0
  62. package/python/foxclaw/api/app.py +123 -0
  63. package/python/foxclaw/api/middleware.py +317 -0
  64. package/python/foxclaw/api/routes/ahs.py +417 -0
  65. package/python/foxclaw/api/routes/attachments.py +117 -0
  66. package/python/foxclaw/api/routes/browser.py +39 -0
  67. package/python/foxclaw/api/routes/domain.py +314 -0
  68. package/python/foxclaw/api/routes/knowledge.py +163 -0
  69. package/python/foxclaw/api/routes/llm_admin.py +461 -0
  70. package/python/foxclaw/api/routes/memory.py +89 -0
  71. package/python/foxclaw/api/routes/orchestration.py +41 -0
  72. package/python/foxclaw/api/routes/recovery.py +43 -0
  73. package/python/foxclaw/api/routes/review.py +275 -0
  74. package/python/foxclaw/api/routes/search.py +37 -0
  75. package/python/foxclaw/api/routes/system.py +327 -0
  76. package/python/foxclaw/api/routes/task.py +281 -0
  77. package/python/foxclaw/api/routes/ws.py +68 -0
  78. package/python/foxclaw/api/spine.py +55 -0
  79. package/python/foxclaw/browser/__init__.py +13 -0
  80. package/python/foxclaw/browser/action/__init__.py +1 -0
  81. package/python/foxclaw/browser/action/action_executor.py +54 -0
  82. package/python/foxclaw/browser/action/action_types.py +45 -0
  83. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  84. package/python/foxclaw/browser/action/url_policy.py +147 -0
  85. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  86. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  87. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  88. package/python/foxclaw/browser/driver/__init__.py +1 -0
  89. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  90. package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
  91. package/python/foxclaw/browser/perception/__init__.py +1 -0
  92. package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
  93. package/python/foxclaw/browser/perception/element_indexer.py +57 -0
  94. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  95. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  96. package/python/foxclaw/browser/service.py +148 -0
  97. package/python/foxclaw/browser/session/__init__.py +1 -0
  98. package/python/foxclaw/browser/session/session_manager.py +57 -0
  99. package/python/foxclaw/capability/__init__.py +12 -0
  100. package/python/foxclaw/capability/embedding_service.py +67 -0
  101. package/python/foxclaw/channels/__init__.py +140 -0
  102. package/python/foxclaw/channels/discord.py +184 -0
  103. package/python/foxclaw/channels/dispatcher.py +204 -0
  104. package/python/foxclaw/channels/feishu.py +225 -0
  105. package/python/foxclaw/channels/openclaw_bridge.py +156 -0
  106. package/python/foxclaw/channels/signing.py +187 -0
  107. package/python/foxclaw/channels/telegram.py +121 -0
  108. package/python/foxclaw/channels/weixin.py +225 -0
  109. package/python/foxclaw/cli.py +154 -0
  110. package/python/foxclaw/cognitive/__init__.py +4 -0
  111. package/python/foxclaw/cognitive/reasoner.py +82 -0
  112. package/python/foxclaw/cognitive/state.py +147 -0
  113. package/python/foxclaw/core/__init__.py +9 -0
  114. package/python/foxclaw/core/audit_chain.py +133 -0
  115. package/python/foxclaw/core/audit_sink.py +100 -0
  116. package/python/foxclaw/core/compaction.py +283 -0
  117. package/python/foxclaw/core/config.py +65 -0
  118. package/python/foxclaw/core/confirm_nonce.py +157 -0
  119. package/python/foxclaw/core/dispatch.py +27 -0
  120. package/python/foxclaw/core/envfile.py +48 -0
  121. package/python/foxclaw/core/failure_policy.py +151 -0
  122. package/python/foxclaw/core/gateway.py +85 -0
  123. package/python/foxclaw/core/governance_audit.py +175 -0
  124. package/python/foxclaw/core/idempotency.py +289 -0
  125. package/python/foxclaw/core/injection_guard.py +221 -0
  126. package/python/foxclaw/core/memory_adapter.py +516 -0
  127. package/python/foxclaw/core/metrics.py +89 -0
  128. package/python/foxclaw/core/model_router.py +1097 -0
  129. package/python/foxclaw/core/paths.py +103 -0
  130. package/python/foxclaw/core/recovery.py +362 -0
  131. package/python/foxclaw/core/scheduler.py +144 -0
  132. package/python/foxclaw/core/secrets.py +134 -0
  133. package/python/foxclaw/core/sessions.py +157 -0
  134. package/python/foxclaw/core/settings.py +430 -0
  135. package/python/foxclaw/core/shared_write.py +198 -0
  136. package/python/foxclaw/core/steering_queue.py +153 -0
  137. package/python/foxclaw/core/system_bus.py +84 -0
  138. package/python/foxclaw/core/thought_chain.py +318 -0
  139. package/python/foxclaw/core/token_meter.py +235 -0
  140. package/python/foxclaw/core/tools.py +172 -0
  141. package/python/foxclaw/core/tracing.py +390 -0
  142. package/python/foxclaw/core/truth_pipeline.py +114 -0
  143. package/python/foxclaw/core/vec_recall_adapter.py +503 -0
  144. package/python/foxclaw/core/verify.py +181 -0
  145. package/python/foxclaw/core/web_search.py +242 -0
  146. package/python/foxclaw/finance/__init__.py +15 -0
  147. package/python/foxclaw/finance/evidence.py +153 -0
  148. package/python/foxclaw/finance/fx.py +100 -0
  149. package/python/foxclaw/finance/graph.py +188 -0
  150. package/python/foxclaw/finance/models.py +150 -0
  151. package/python/foxclaw/finance/nlparse.py +575 -0
  152. package/python/foxclaw/finance/policy.py +329 -0
  153. package/python/foxclaw/finance/reasoner.py +129 -0
  154. package/python/foxclaw/finance/reasoning.py +340 -0
  155. package/python/foxclaw/finance/reconcile.py +180 -0
  156. package/python/foxclaw/finance/runtime.py +527 -0
  157. package/python/foxclaw/governance/__init__.py +24 -0
  158. package/python/foxclaw/governance/middleware.py +184 -0
  159. package/python/foxclaw/harness/__init__.py +17 -0
  160. package/python/foxclaw/harness/_base.py +72 -0
  161. package/python/foxclaw/harness/_policy.py +359 -0
  162. package/python/foxclaw/harness/drift.py +89 -0
  163. package/python/foxclaw/harness/event_bus.py +31 -0
  164. package/python/foxclaw/harness/harness.py +446 -0
  165. package/python/foxclaw/harness/loop_detection.py +152 -0
  166. package/python/foxclaw/itinerary/__init__.py +10 -0
  167. package/python/foxclaw/itinerary/amap_client.py +117 -0
  168. package/python/foxclaw/itinerary/models.py +80 -0
  169. package/python/foxclaw/itinerary/reasoner.py +101 -0
  170. package/python/foxclaw/itinerary/reasoning.py +174 -0
  171. package/python/foxclaw/itinerary/runtime.py +148 -0
  172. package/python/foxclaw/kernel/__init__.py +13 -0
  173. package/python/foxclaw/kernel/capabilities.py +73 -0
  174. package/python/foxclaw/kernel/policy_gate.py +114 -0
  175. package/python/foxclaw/kernel/process_manager.py +205 -0
  176. package/python/foxclaw/kernel/scheduler.py +131 -0
  177. package/python/foxclaw/kernel/sentinel.py +453 -0
  178. package/python/foxclaw/kernel/sentinel_client.py +246 -0
  179. package/python/foxclaw/kernel/spine.py +97 -0
  180. package/python/foxclaw/knowledge/__init__.py +376 -0
  181. package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
  182. package/python/foxclaw/learning/__init__.py +12 -0
  183. package/python/foxclaw/learning/knowledge_graph.py +149 -0
  184. package/python/foxclaw/learning/models.py +93 -0
  185. package/python/foxclaw/learning/reasoner.py +70 -0
  186. package/python/foxclaw/learning/runtime.py +363 -0
  187. package/python/foxclaw/learning/spaced_repetition.py +96 -0
  188. package/python/foxclaw/mcp_server.py +189 -0
  189. package/python/foxclaw/orchestration/__init__.py +184 -0
  190. package/python/foxclaw/pipeline/__init__.py +23 -0
  191. package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
  192. package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
  193. package/python/foxclaw/planning/__init__.py +17 -0
  194. package/python/foxclaw/planning/llm_planner.py +232 -0
  195. package/python/foxclaw/planning/planner.py +99 -0
  196. package/python/foxclaw/search/__init__.py +24 -0
  197. package/python/foxclaw/search/agent_port.py +194 -0
  198. package/python/foxclaw/search/evidence.py +113 -0
  199. package/python/foxclaw/search/external_search.py +50 -0
  200. package/python/foxclaw/search/hkc_read_port.py +60 -0
  201. package/python/foxclaw/search/internal_search.py +73 -0
  202. package/python/foxclaw/search/models.py +129 -0
  203. package/python/foxclaw/search/planner.py +44 -0
  204. package/python/foxclaw/search/retriever.py +39 -0
  205. package/python/foxclaw/search/runtime.py +134 -0
  206. package/python/foxclaw/search/strategy.py +44 -0
  207. package/python/foxclaw/skills/__init__.py +613 -0
  208. package/python/foxclaw/skills/hooks_spec.py +73 -0
  209. package/python/foxclaw/skills/lifecycle.py +278 -0
  210. package/python/foxclaw/skills/markdown_skill.py +174 -0
  211. package/python/foxclaw/skills/markdown_store.py +39 -0
  212. package/python/foxclaw/skills/mcp_client.py +190 -0
  213. package/python/foxclaw/skills/mcp_store.py +43 -0
  214. package/python/foxclaw/skills/permission.py +180 -0
  215. package/python/foxclaw/skills/pipeline.py +386 -0
  216. package/python/foxclaw/skills/policy.py +80 -0
  217. package/python/foxclaw/skills/review.py +72 -0
  218. package/python/foxclaw/skills/store.py +57 -0
  219. package/python/frontend/assets/core_bg.jpg +0 -0
  220. package/python/frontend/deck/auth.js +32 -0
  221. package/python/frontend/deck/chat_model_picker.js +537 -0
  222. package/python/frontend/deck/domain.js +296 -0
  223. package/python/frontend/deck/main.js +1950 -0
  224. package/python/frontend/deck/markdown.js +48 -0
  225. package/python/frontend/deck/settings.js +324 -0
  226. package/python/frontend/deck/state.js +6 -0
  227. package/python/frontend/deck/util.js +6 -0
  228. package/python/frontend/deck/visuals.js +212 -0
  229. package/python/frontend/deck.css +855 -0
  230. package/python/frontend/deck.html +686 -0
  231. package/python/frontend/deck_v2.html +341 -0
  232. package/python/frontend/planet/index.html +266 -0
  233. package/python/frontend/planet/planet.css +579 -0
  234. package/python/frontend/planet/planet.js +1637 -0
  235. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  236. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  237. package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
  238. package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
  239. package/python/frontend/v3/index.html +15 -0
  240. package/python/frontend/widget_runtime.js +295 -0
  241. package/python/frontend/widgets.js +696 -0
  242. package/python/frontend_src/index.html +14 -0
  243. package/python/frontend_src/package-lock.json +1376 -0
  244. package/python/frontend_src/package.json +19 -0
  245. package/python/frontend_src/src/App.vue +62 -0
  246. package/python/frontend_src/src/api.js +89 -0
  247. package/python/frontend_src/src/components/ChatDock.vue +377 -0
  248. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  249. package/python/frontend_src/src/components/DocViewer.vue +171 -0
  250. package/python/frontend_src/src/components/Inspector.vue +241 -0
  251. package/python/frontend_src/src/components/LeftRail.vue +394 -0
  252. package/python/frontend_src/src/components/MenuPopover.vue +130 -0
  253. package/python/frontend_src/src/components/MessageStream.vue +401 -0
  254. package/python/frontend_src/src/components/ModelPicker.vue +355 -0
  255. package/python/frontend_src/src/components/QuickIngest.vue +118 -0
  256. package/python/frontend_src/src/components/Stage.vue +226 -0
  257. package/python/frontend_src/src/components/ToolMenu.vue +144 -0
  258. package/python/frontend_src/src/components/TopBar.vue +90 -0
  259. package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
  260. package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
  261. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
  262. package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
  263. package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
  264. package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
  265. package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
  266. package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
  267. package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
  268. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  269. package/python/frontend_src/src/lib/fmt.js +48 -0
  270. package/python/frontend_src/src/lib/md.js +204 -0
  271. package/python/frontend_src/src/main.js +7 -0
  272. package/python/frontend_src/src/stores/app.js +106 -0
  273. package/python/frontend_src/src/stores/chat.js +484 -0
  274. package/python/frontend_src/src/stores/model.js +104 -0
  275. package/python/frontend_src/src/stores/panels.js +330 -0
  276. package/python/frontend_src/src/styles/base.css +138 -0
  277. package/python/frontend_src/src/styles/panel.css +117 -0
  278. package/python/frontend_src/src/styles/tokens.css +78 -0
  279. package/python/frontend_src/vite.config.js +46 -0
  280. package/python/pyproject.toml +265 -0
  281. package/python/skills/README.md +123 -0
  282. package/python/skills/execution-contract/SKILL.md +30 -0
  283. package/python/skills/general-assistant/SKILL.md +34 -0
  284. package/python/skills/planner/SKILL.md +33 -0
  285. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  286. package/python/skills/skill-composition/SKILL.md +31 -0
  287. package/python/skills/skill-guard/SKILL.md +67 -0
  288. package/python/skills/skill-memory/SKILL.md +50 -0
  289. package/python/skills/skill-router/SKILL.md +35 -0
  290. package/python/tests/conftest.py +76 -0
  291. package/python/tests/run_all.py +263 -0
  292. package/python/tests/test_agent_lifecycle.py +360 -0
  293. package/python/tests/test_ahs_p2.py +414 -0
  294. package/python/tests/test_anthropic_tools.py +206 -0
  295. package/python/tests/test_api.py +87 -0
  296. package/python/tests/test_api_auth.py +83 -0
  297. package/python/tests/test_api_host_guard.py +69 -0
  298. package/python/tests/test_attachments.py +167 -0
  299. package/python/tests/test_audit_chain.py +121 -0
  300. package/python/tests/test_audit_sink.py +136 -0
  301. package/python/tests/test_browser.py +153 -0
  302. package/python/tests/test_browser_request_guard.py +184 -0
  303. package/python/tests/test_capabilities.py +158 -0
  304. package/python/tests/test_channel_signing.py +192 -0
  305. package/python/tests/test_channel_trust.py +109 -0
  306. package/python/tests/test_channels.py +184 -0
  307. package/python/tests/test_cli.py +46 -0
  308. package/python/tests/test_compaction_toolpair.py +119 -0
  309. package/python/tests/test_config_layering.py +65 -0
  310. package/python/tests/test_confirm_nonce.py +197 -0
  311. package/python/tests/test_context_compaction_wiring.py +130 -0
  312. package/python/tests/test_dispatch.py +95 -0
  313. package/python/tests/test_domain_evals.py +403 -0
  314. package/python/tests/test_domain_import.py +78 -0
  315. package/python/tests/test_domain_reasoners.py +148 -0
  316. package/python/tests/test_e2e.py +178 -0
  317. package/python/tests/test_emb_cache.py +107 -0
  318. package/python/tests/test_embedding_singleton.py +89 -0
  319. package/python/tests/test_envfile.py +83 -0
  320. package/python/tests/test_evals_smoke.py +114 -0
  321. package/python/tests/test_event_spine.py +182 -0
  322. package/python/tests/test_evidence_semantic.py +101 -0
  323. package/python/tests/test_evidence_sse.py +97 -0
  324. package/python/tests/test_file_ingest.py +146 -0
  325. package/python/tests/test_finance.py +153 -0
  326. package/python/tests/test_finance_ledger.py +252 -0
  327. package/python/tests/test_finance_persist.py +178 -0
  328. package/python/tests/test_gateway.py +144 -0
  329. package/python/tests/test_governance.py +468 -0
  330. package/python/tests/test_governance_audit.py +359 -0
  331. package/python/tests/test_governance_middleware.py +143 -0
  332. package/python/tests/test_hkc.py +286 -0
  333. package/python/tests/test_hkc_index_build_manager.py +339 -0
  334. package/python/tests/test_hkc_manifest_reuse.py +317 -0
  335. package/python/tests/test_hkc_search_event_sync.py +533 -0
  336. package/python/tests/test_import_path_guard.py +161 -0
  337. package/python/tests/test_injection_guard.py +211 -0
  338. package/python/tests/test_integration.py +141 -0
  339. package/python/tests/test_itinerary.py +103 -0
  340. package/python/tests/test_kee_semantic.py +89 -0
  341. package/python/tests/test_kernel.py +222 -0
  342. package/python/tests/test_learning.py +110 -0
  343. package/python/tests/test_llm_admin.py +160 -0
  344. package/python/tests/test_llm_planner.py +366 -0
  345. package/python/tests/test_markdown_skills.py +73 -0
  346. package/python/tests/test_markdown_skills_openclaw.py +71 -0
  347. package/python/tests/test_mcp_connectors.py +109 -0
  348. package/python/tests/test_mcp_guardian_discovery.py +91 -0
  349. package/python/tests/test_mcp_no_memory.py +43 -0
  350. package/python/tests/test_mcp_policy_gate.py +77 -0
  351. package/python/tests/test_mcp_server.py +95 -0
  352. package/python/tests/test_memory_recall.py +95 -0
  353. package/python/tests/test_metrics.py +66 -0
  354. package/python/tests/test_multi_chain.py +102 -0
  355. package/python/tests/test_multimodal.py +135 -0
  356. package/python/tests/test_negotiation.py +146 -0
  357. package/python/tests/test_orchestration.py +119 -0
  358. package/python/tests/test_orchestration_decoupled.py +109 -0
  359. package/python/tests/test_p3_p5_hardening.py +338 -0
  360. package/python/tests/test_parallel_dispatch.py +215 -0
  361. package/python/tests/test_pipeline.py +533 -0
  362. package/python/tests/test_plan_visible.py +86 -0
  363. package/python/tests/test_planner.py +95 -0
  364. package/python/tests/test_policy_gate.py +109 -0
  365. package/python/tests/test_reasoner.py +185 -0
  366. package/python/tests/test_release_check.py +263 -0
  367. package/python/tests/test_resume_checkpoint.py +156 -0
  368. package/python/tests/test_routing.py +95 -0
  369. package/python/tests/test_scheduler.py +141 -0
  370. package/python/tests/test_search.py +204 -0
  371. package/python/tests/test_secrets.py +159 -0
  372. package/python/tests/test_semantic.py +78 -0
  373. package/python/tests/test_sessions.py +91 -0
  374. package/python/tests/test_settings.py +281 -0
  375. package/python/tests/test_shadow_learning.py +330 -0
  376. package/python/tests/test_shared_write.py +207 -0
  377. package/python/tests/test_skill_taint_gate.py +154 -0
  378. package/python/tests/test_skills.py +133 -0
  379. package/python/tests/test_stats_and_events.py +165 -0
  380. package/python/tests/test_streaming.py +60 -0
  381. package/python/tests/test_supervisor_pipeline.py +177 -0
  382. package/python/tests/test_supervisor_triage.py +108 -0
  383. package/python/tests/test_taint.py +117 -0
  384. package/python/tests/test_task_stream.py +85 -0
  385. package/python/tests/test_thinking_sse.py +67 -0
  386. package/python/tests/test_thinking_stream.py +76 -0
  387. package/python/tests/test_tool_center.py +129 -0
  388. package/python/tests/test_tool_parallel.py +113 -0
  389. package/python/tests/test_tool_params.py +103 -0
  390. package/python/tests/test_tool_use.py +242 -0
  391. package/python/tests/test_toplevel_concurrency.py +117 -0
  392. package/python/tests/test_tracing.py +290 -0
  393. package/python/tests/test_truth_crystallizer.py +127 -0
  394. package/python/tests/test_truth_flow.py +93 -0
  395. package/python/tests/test_url_policy.py +53 -0
  396. package/python/tests/test_verify_evidence.py +225 -0
  397. package/python/tests/test_version_sync.py +71 -0
  398. package/python/tests/test_web_trigger.py +99 -0
  399. package/python/tools/check_faiss_type.py +31 -0
  400. package/python/tools/check_log.ps1 +46 -0
  401. package/python/tools/ivf_status.py +72 -0
  402. package/python/tools/mypy_baseline.txt +5 -0
  403. package/python/tools/mypy_ratchet.py +97 -0
  404. package/python/tools/pack.py +178 -0
  405. package/python/tools/release_check.py +349 -0
  406. package/python/tools/restart_foxclaw.ps1 +74 -0
  407. package/python/tools/shared_write_audit.py +204 -0
  408. package/python/tools/size_baseline.txt +28 -0
  409. package/python/tools/size_check.py +102 -0
  410. package/python/tools/verify_env.bat +2 -0
  411. package/python/tools/verify_env.py +25 -0
  412. package/python/vendor/HMR_LICENSE +37 -0
  413. package/python/vendor/HMR_README.md +65 -0
  414. package/python/vendor/hkc/LICENSE +37 -0
  415. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  416. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  417. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  418. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  419. package/python/vendor/hkc/hkc_api/container.py +416 -0
  420. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  421. package/python/vendor/hkc/hkc_api/main.py +157 -0
  422. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  423. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  424. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  425. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  426. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  427. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  428. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  429. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  430. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  431. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  432. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  433. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  434. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  435. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  436. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  437. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  438. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  439. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  440. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  441. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  442. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  443. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  444. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  445. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  446. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  447. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  448. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  449. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  450. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  451. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  452. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  453. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  454. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  455. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  456. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  457. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  458. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  459. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  460. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  461. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  462. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  463. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  464. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  465. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  466. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  467. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  468. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  469. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  470. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  471. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  472. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  473. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  474. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  475. package/python/vendor/hmr/__init__.py +0 -0
  476. package/python/vendor/hmr/core/__init__.py +0 -0
  477. package/python/vendor/hmr/core/hmr.py +684 -0
  478. package/python/vendor/hmr/core/models.py +310 -0
  479. package/python/vendor/hmr/engines/__init__.py +0 -0
  480. package/python/vendor/hmr/engines/evolution.py +710 -0
  481. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  482. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  483. package/python/vendor/hmr/engines/policy.py +625 -0
  484. package/python/vendor/hmr/engines/recall.py +260 -0
  485. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  486. package/python/vendor/hmr/engines/scheduler.py +323 -0
  487. package/python/vendor/hmr/engines/semantic.py +150 -0
  488. package/python/vendor/hmr/engines/temporal.py +261 -0
  489. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  490. package/python/vendor/hmr/graph/__init__.py +0 -0
  491. package/python/vendor/hmr/graph/cwg.py +338 -0
  492. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  493. package/python/vendor/hmr/storage/__init__.py +0 -0
  494. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  495. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,156 @@
1
+ """断点续跑(P1):步骤级 checkpoint + 续跑复用已完成步骤。
2
+
3
+ 锁住四件事:
4
+ ① `RestartRecovery.record_step/steps_of` 能按 task 回放步骤,`compact_log` 不会把断点压实掉;
5
+ ② 工具步指纹稳定(同名同参 = 同一步,参数字典序无关),失败/需确认的回注文本不入账;
6
+ ③ `_run_tool_calls` 命中续跑缓存时**不再执行**该步,只执行未完成的步并各自记账;
7
+ ④ 生命周期钩子下移基类后,任何装着 `_recovery` 的 Agent 都会落 start/finish(领域 Agent 不再漏记)。
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import types
13
+
14
+ import pytest
15
+
16
+ from foxclaw.agents.base import CognitiveAgent
17
+ from foxclaw.agents.mixins import _context, tool_exec
18
+ from foxclaw.core.recovery import RestartRecovery
19
+
20
+
21
+ class _Rec:
22
+ """鸭子类型替身:只记调用,不做 IO。"""
23
+
24
+ def __init__(self, rows=None):
25
+ self.steps: list = []
26
+ self.started: list = []
27
+ self.finished: list = []
28
+ self._rows = rows or []
29
+ self.compacted = False
30
+
31
+ def record_step(self, tid, step):
32
+ self.steps.append((tid, step))
33
+
34
+ def steps_of(self, tid, limit=200):
35
+ return list(self._rows)
36
+
37
+ def task_started(self, **kw):
38
+ self.started.append(kw)
39
+
40
+ def task_finished(self, tid, status="done"):
41
+ self.finished.append((tid, status))
42
+
43
+
44
+ # ── ① 日志层 ───────────────────────────────────────────────
45
+
46
+ def test_record_and_replay_steps(tmp_path) -> None:
47
+ rec = RestartRecovery(str(tmp_path / "recovery.jsonl"), fsync=False)
48
+ rec.record_step("t1", {"kind": "stage", "name": "perceive"})
49
+ rec.record_step("t2", {"kind": "stage", "name": "recall"})
50
+ rec.record_step("t1", {"kind": "tool", "name": "web_search", "key": "k", "result": "r"})
51
+ steps = rec.steps_of("t1")
52
+ assert [s["name"] for s in steps] == ["perceive", "web_search"]
53
+ assert all("event" not in s and "task_id" not in s for s in steps)
54
+ assert rec.steps_of("nope") == []
55
+
56
+
57
+ def test_compact_log_keeps_steps(tmp_path) -> None:
58
+ rec = RestartRecovery(str(tmp_path / "recovery.jsonl"), fsync=False)
59
+ rec.task_started("t1", "任务一")
60
+ rec.record_step("t1", {"kind": "tool", "name": "web_search", "key": "k", "result": "r"})
61
+ rec.task_finished("t1", status="done")
62
+ rec.compact_log(keep_recent_days=7) # 保留 → 步骤必须还在
63
+ assert len(rec.steps_of("t1")) == 1
64
+ rec.compact_log(keep_recent_days=0) # 过期 → 记录与步骤一起丢
65
+ assert rec.steps_of("t1") == []
66
+
67
+
68
+ # ── ② 指纹与成败判定 ────────────────────────────────────────
69
+
70
+ def test_step_key_is_argument_order_insensitive() -> None:
71
+ a = tool_exec._step_key("web_search", {"q": "x", "n": 3})
72
+ b = tool_exec._step_key("web_search", {"n": 3, "q": "x"})
73
+ assert a == b
74
+ assert a != tool_exec._step_key("web_search", {"q": "y", "n": 3})
75
+ assert a != tool_exec._step_key("recall_memory", {"q": "x", "n": 3})
76
+
77
+
78
+ @pytest.mark.parametrize("text,ok", [
79
+ ("【联网搜索结果】...", True),
80
+ ("【召回的相关记忆】...", True),
81
+ ("(工具 web_search 未返回:45s 超时)", False),
82
+ ("(该步需要你确认后才能执行)", False),
83
+ ("(未知工具 foo)", False),
84
+ ("", False),
85
+ ])
86
+ def test_step_ok_rejects_failure_texts(text, ok) -> None:
87
+ assert tool_exec._step_ok(text) is ok
88
+
89
+
90
+ # ── ③ 续跑复用 ──────────────────────────────────────────────
91
+
92
+ def test_run_tool_calls_reuses_completed_steps(monkeypatch) -> None:
93
+ executed: list = []
94
+
95
+ def _fake_batch(agent, calls):
96
+ executed.extend(calls)
97
+ return [f"新结果:{c['name']}" for c in calls]
98
+
99
+ monkeypatch.setattr(tool_exec, "_run_tools_batch", _fake_batch)
100
+ rec = _Rec()
101
+ agent = types.SimpleNamespace(_recovery=rec, _session_id="t1")
102
+ done = {"name": "web_search", "args": {"q": "天气"}}
103
+ todo = {"name": "recall_memory", "args": {"q": "偏好"}}
104
+ cache = {tool_exec._step_key("web_search", {"q": "天气"}):
105
+ {"name": "web_search", "result": "【联网搜索结果】晴"}}
106
+ tok = _context._resume_steps.set(cache)
107
+ try:
108
+ out = tool_exec._run_tool_calls(agent, [done, todo])
109
+ finally:
110
+ _context._resume_steps.reset(tok)
111
+
112
+ assert executed == [todo], "已完成的步必须复用结果、绝不重复执行"
113
+ assert "断点续跑" in out[0] and "晴" in out[0]
114
+ assert out[1] == "新结果:recall_memory"
115
+ assert [s[1]["key"] for s in rec.steps] == [tool_exec._step_key("recall_memory", {"q": "偏好"})]
116
+
117
+
118
+ def test_run_tool_calls_skips_recording_failures(monkeypatch) -> None:
119
+ monkeypatch.setattr(tool_exec, "_run_tools_batch",
120
+ lambda agent, calls: ["(工具 web_search 未返回:45s 超时)"])
121
+ rec = _Rec()
122
+ agent = types.SimpleNamespace(_recovery=rec, _session_id="t1")
123
+ tool_exec._run_tool_calls(agent, [{"name": "web_search", "args": {}}])
124
+ assert rec.steps == [], "失败/超时步不能进 checkpoint(否则续跑会把失败当完成)"
125
+
126
+
127
+ # ── ④ 装载与生命周期钩子 ────────────────────────────────────
128
+
129
+ def test_load_resume_cache_filters_bad_steps() -> None:
130
+ rows = [
131
+ {"kind": "stage", "name": "act"}, # 非工具步 → 跳过
132
+ {"kind": "tool", "name": "web_search", "key": "k1", "result": "ok"},
133
+ {"kind": "tool", "name": "web_search", "key": "k2", "result": "x",
134
+ "truncated": True}, # 截断 → 不复用
135
+ {"kind": "tool", "name": "web_search", "key": "k3", "result": " "}, # 空 → 不复用
136
+ {"kind": "tool", "name": "web_search", "result": "no-key"}, # 无指纹 → 跳过
137
+ ]
138
+ fake = types.SimpleNamespace(_recovery=_Rec(rows))
139
+ assert CognitiveAgent._load_resume_cache(fake, "t1") == {"k1": {"name": "web_search",
140
+ "result": "ok"}}
141
+ assert CognitiveAgent._load_resume_cache(fake, "") == {}
142
+ assert CognitiveAgent._load_resume_cache(types.SimpleNamespace(), "t1") == {}
143
+
144
+
145
+ def test_lifecycle_hooks_record_for_any_agent() -> None:
146
+ rec = _Rec()
147
+ agent = types.SimpleNamespace(_recovery=rec)
148
+ CognitiveAgent._on_session_started(agent, "t1", "跑个任务", {"channel": "cli"})
149
+ CognitiveAgent._on_task_settled(agent, "t1", "done")
150
+ assert rec.started and rec.started[0]["task_id"] == "t1"
151
+ assert rec.started[0]["channel"] == "cli"
152
+ assert rec.finished == [("t1", "done")]
153
+
154
+ CognitiveAgent._on_session_started(types.SimpleNamespace(), "t1", "x", {}) # 无 recovery
155
+ CognitiveAgent._on_task_settled(types.SimpleNamespace(), "t1", "done")
156
+ CognitiveAgent._record_stage(types.SimpleNamespace(), "t1", "recall")
@@ -0,0 +1,95 @@
1
+ """任务感知模型路由 + fallback 链验证(全离线,monkeypatch 不联网)。"""
2
+ import os, sys
3
+ import pytest
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent))
6
+
7
+
8
+ @pytest.mark.xfail(
9
+ reason="pre-existing 失败:本地 stub 无外部 API 时,fallback 链全模型降级后断言 '全部模型失败' 不一致;需真实 provider 配置(fast/smart 多档)才能过。",
10
+ strict=False,
11
+ )
12
+ def test_routing():
13
+ for k in ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "DEEPSEEK_API_KEY", "GOOGLE_API_KEY",
14
+ "DASHSCOPE_API_KEY", "FOXCLAW_MODEL", "FOXCLAW_MODEL_FAST", "FOXCLAW_MODEL_SMART",
15
+ "FOXCLAW_PROVIDER"]:
16
+ os.environ.pop(k, None)
17
+
18
+ from foxclaw.core.model_router import ModelRouter, _route_tier, _tier_model
19
+
20
+ print("=" * 60); print("任务感知模型路由 + fallback 链验证"); print("=" * 60)
21
+
22
+ # [1] 任务→档
23
+ assert _route_tier("supervisor", [{"role": "user", "content": "x"}]) == "fast"
24
+ assert _route_tier("finance", [{"role": "user", "content": "帮我做深度财务分析报告"}]) == "smart"
25
+ assert _route_tier("assistant", [{"role": "user", "content": "你好"}]) == "fast"
26
+ assert _tier_model("deepseek", "fast") == "deepseek-chat"
27
+ assert _tier_model("deepseek", "smart") == "deepseek-reasoner"
28
+ print("[1] 任务→档 + 档→模型 OK")
29
+
30
+ # [2] 按任务/Agent 选档
31
+ os.environ["FOXCLAW_PROVIDER"] = "deepseek"; os.environ["DEEPSEEK_API_KEY"] = "x"
32
+ r = ModelRouter()
33
+ assert r.provider == "deepseek"
34
+ assert r._model_for("deepseek", "supervisor", [{"role": "user", "content": "x"}]) == "deepseek-chat"
35
+ assert r._model_for("deepseek", "finance", [{"role": "user", "content": "深度分析报告方案评估"}]) == "deepseek-reasoner"
36
+ print("[2] 按任务/Agent 选档(supervisor→chat,复杂→reasoner) OK")
37
+
38
+ # [3] fallback 链:主 provider 失败 → 试下一个已配 provider
39
+ os.environ["OPENAI_API_KEY"] = "y"
40
+ r2 = ModelRouter(); calls = []
41
+ def _fake(p, c, m, s, msgs, mt):
42
+ calls.append(p)
43
+ if p == "deepseek":
44
+ raise RuntimeError("deepseek down")
45
+ return f"OK-{p}", None
46
+ r2._call_provider = _fake
47
+ out = r2.complete("sys", [{"role": "user", "content": "hi"}], agent_id="assistant")
48
+ assert "OK-openai" in out and calls == ["deepseek", "openai"], (out, calls)
49
+ assert "回退到 openai" in r2.fallback_reason
50
+ print("[3] fallback 链(deepseek 失败→openai) OK")
51
+
52
+ # [4] 锁定模型(显式 model=)不分档
53
+ r3 = ModelRouter(provider="deepseek", model="fixed-x")
54
+ assert r3._model_for("deepseek", "supervisor", [{"role": "user", "content": "x"}]) == "fixed-x"
55
+ print("[4] 锁定模型不分档(向后兼容) OK")
56
+
57
+ # [5] 全部 provider 失败 → 显式失败(不再降 stub 伪装成功)
58
+ def _allfail(*a):
59
+ raise RuntimeError("boom")
60
+ r2._call_provider = _allfail
61
+ out = r2.complete("sys", [{"role": "user", "content": "hi"}])
62
+ assert "STUB" in out or "全部模型失败" in out, out
63
+ # 失败语义(A 类修复):全部 provider 挂掉属**故障**,不是"未配置模型"的离线态。
64
+ # 旧实现会拼接 stub 文案「已完成处理:…」—— 那是把故障伪装成成功,且该文本会
65
+ # 经 base._store_memory 沉入长期记忆造成持续复读。此处锁死该行为不得回归。
66
+ assert "已完成处理" not in out, f"故障被伪装成成功: {out}"
67
+ assert r2.is_degraded() is True, "全部失败必须置 degraded 标志(上游据此禁写长期记忆)"
68
+ print("[5] 全部失败 → 显式失败(无假成功文案) + degraded OK")
69
+
70
+ # [6] switch 尊重显式模型(修回归:UI 切模型不能被 tiering 覆盖);只切 provider 清锁恢复分档
71
+ r4 = ModelRouter()
72
+ r4.switch("deepseek", "deepseek-reasoner")
73
+ assert r4._model_for("deepseek", "supervisor", [{"role": "user", "content": "简单"}]) == "deepseek-reasoner"
74
+ r4.switch("deepseek") # 无 model → 清锁
75
+ assert r4._model_for("deepseek", "supervisor", [{"role": "user", "content": "简单"}]) == "deepseek-chat"
76
+ print("[6] switch 尊重显式模型 + 清锁恢复分档 OK")
77
+
78
+ # [7] 断路器:主 provider 持续宕机,第二次调用直接跳过它(免每次等超时)
79
+ r5 = ModelRouter(); seq = []
80
+ def _fk(p, c, m, s, msgs, mt):
81
+ seq.append(p)
82
+ if p == "deepseek":
83
+ raise RuntimeError("down")
84
+ return f"OK-{p}", None
85
+ r5._call_provider = _fk
86
+ r5.complete("s", [{"role": "user", "content": "hi"}]); seq.clear()
87
+ r5.complete("s", [{"role": "user", "content": "hi"}]) # 第二次:deepseek 冷却中
88
+ assert seq == ["openai"], f"断路器失效: {seq}"
89
+ print("[7] 断路器跳过宕机 provider OK")
90
+
91
+ print("\n任务感知模型路由验证全部通过")
92
+
93
+
94
+ if __name__ == "__main__":
95
+ test_routing()
@@ -0,0 +1,141 @@
1
+ """
2
+ 微内核调度器(v2 ① Step1:Scheduler v0)测试 —— 全离线,线程级。
3
+
4
+ 覆盖:
5
+ 1. 无上限(默认):准入直通,行为不变;并发不受限(Barrier 确定性达峰值 N)
6
+ 2. 有上限:并发配额生效(peak_running 不超过 max_concurrency = 背压)
7
+ 3. 生命周期事件:每任务 emit task.lifecycle.{scheduled,running,completed};失败 → failed
8
+ (【P5-2】回退路径原用 started,与 ProcessManager 的 running 不一致,已统一为 running)
9
+ 4. 记账 + 结果透传 + 异常透传
10
+ """
11
+ import sys, threading, time
12
+ from pathlib import Path
13
+ sys.path.insert(0, str(Path(__file__).parent.parent))
14
+
15
+ from foxclaw.kernel import Scheduler
16
+
17
+
18
+ class _CapSpine:
19
+ def __init__(self): self.events = []; self._lk = threading.Lock()
20
+ def publish(self, topic, payload=None, source="", **kw):
21
+ with self._lk: self.events.append(topic)
22
+
23
+
24
+ def _run_n_concurrent(sched, n, work):
25
+ threads = [threading.Thread(target=lambda: sched.run(work, task="t")) for _ in range(n)]
26
+ for t in threads: t.start()
27
+ for t in threads: t.join()
28
+
29
+
30
+ def test_unlimited_reaches_peak():
31
+ print("\n【1】无上限(默认):并发不受限,行为直通")
32
+ sched = Scheduler() # max_concurrency=None
33
+ N = 6
34
+ barrier = threading.Barrier(N) # 让 N 个任务确定性地同时在跑
35
+ def work():
36
+ barrier.wait(timeout=5) # 全部到齐才继续 → 峰值=N
37
+ time.sleep(0.01)
38
+ return "ok"
39
+ _run_n_concurrent(sched, N, work)
40
+ st = sched.stats()
41
+ assert st["max_concurrency"] is None
42
+ assert st["peak_running"] == N, f"无上限应达峰值 {N},实得 {st['peak_running']}"
43
+ assert st["completed"] == N and st["submitted"] == N
44
+ print(f" ✓ 无上限:peak={st['peak_running']}=N,completed={st['completed']}")
45
+
46
+
47
+ def test_concurrency_cap():
48
+ print("\n【2】有上限:并发配额生效(背压)")
49
+ sched = Scheduler(max_concurrency=2)
50
+ def work():
51
+ time.sleep(0.02)
52
+ return "ok"
53
+ _run_n_concurrent(sched, 8, work)
54
+ st = sched.stats()
55
+ assert st["peak_running"] <= 2, f"并发峰值不得超上限 2,实得 {st['peak_running']}"
56
+ assert st["peak_running"] == 2, "8 任务/上限 2 应打满到 2"
57
+ assert st["completed"] == 8
58
+ print(f" ✓ 上限 2:peak={st['peak_running']}(不超上限),completed={st['completed']}")
59
+
60
+
61
+ def test_lifecycle_events_and_accounting():
62
+ print("\n【3】生命周期事件 + 记账 + 失败传播")
63
+ spine = _CapSpine()
64
+ sched = Scheduler(spine=spine)
65
+ # 成功任务
66
+ r = sched.run(lambda: 42, agent_id="finance", task="算账")
67
+ assert r == 42
68
+ assert spine.events == ["task.lifecycle.scheduled", "task.lifecycle.running",
69
+ "task.lifecycle.completed"], spine.events
70
+ # 失败任务:异常透传 + failed 事件 + 记账
71
+ spine.events.clear()
72
+ raised = False
73
+ try:
74
+ sched.run(lambda: (_ for _ in ()).throw(RuntimeError("boom")), task="崩")
75
+ except RuntimeError:
76
+ raised = True
77
+ assert raised, "异常应透传"
78
+ assert spine.events == ["task.lifecycle.scheduled", "task.lifecycle.running",
79
+ "task.lifecycle.failed"], spine.events
80
+ st = sched.stats()
81
+ assert st["submitted"] == 2 and st["completed"] == 1 and st["failed"] == 1
82
+ assert st["running"] == 0, "跑完 running 归零"
83
+ print(f" ✓ 成功→completed事件;失败→failed事件+异常透传;记账 {st}")
84
+
85
+
86
+ def test_pm_delegation_unified_lifecycle():
87
+ print("\n【4】Step3:有 ProcessManager → 委托它发唯一 task.lifecycle.<state>(不双套词汇)")
88
+ from foxclaw.kernel import EventSpine, ProcessManager
89
+ from foxclaw.core.system_bus import SystemBus
90
+ spine = EventSpine()
91
+ seen = []
92
+ spine.project(lambda e: seen.append(e.topic))
93
+ pm = ProcessManager(bus=SystemBus(spine=spine)) # pm 经总线发事件到脊柱
94
+ sched = Scheduler(spine=spine, process_manager=pm) # 调度器委托 pm
95
+ r = sched.run(lambda: 7, agent_id="finance", task="t")
96
+ assert r == 7
97
+ life = [t for t in seen if t.startswith("task.lifecycle")]
98
+ assert life == ["task.lifecycle.scheduled", "task.lifecycle.running",
99
+ "task.lifecycle.completed"], f"应由 pm 发唯一词汇,实得 {life}"
100
+ # pm 里确有该进程(顶层任务也成了进程),调度器记账仍在
101
+ assert pm.stats()["by_state"].get("completed") == 1
102
+ assert sched.stats()["completed"] == 1
103
+ print(" ✓ pm 发 task.lifecycle.{scheduled,running,completed};顶层任务纳入进程;记账双在")
104
+
105
+
106
+ def test_priority_admission():
107
+ print("\n【5】Step5:满载后释放 → 高优先排队者先获空位(不抢占运行中)")
108
+ sched = Scheduler(max_concurrency=1)
109
+ order = []
110
+ running = threading.Event(); release = threading.Event()
111
+ def blocker():
112
+ sched.run(lambda: (running.set(), release.wait(5))[-1], priority=0, task="blocker")
113
+ tb = threading.Thread(target=blocker); tb.start()
114
+ assert running.wait(5), "blocker 应占住唯一槽"
115
+ def worker(p, name):
116
+ sched.run(lambda: order.append(name), priority=p, task=name)
117
+ tlo = threading.Thread(target=worker, args=(0, "lo"))
118
+ thi = threading.Thread(target=worker, args=(10, "hi"))
119
+ tlo.start(); time.sleep(0.05); thi.start(); time.sleep(0.15) # 两者都已在准入队列里等
120
+ release.set() # 释放 blocker 的槽
121
+ for t in (tb, tlo, thi): t.join(5)
122
+ assert order == ["hi", "lo"], f"高优先应先获空位,实得 {order}"
123
+ print(f" ✓ 释放后:高优先先获空位 → {order}(准入优先级,非抢占运行中)")
124
+
125
+
126
+ def main():
127
+ print("=" * 60)
128
+ print("微内核调度器 · Scheduler(v2 ① Step1/3/5)")
129
+ print("=" * 60)
130
+ test_unlimited_reaches_peak()
131
+ test_concurrency_cap()
132
+ test_lifecycle_events_and_accounting()
133
+ test_pm_delegation_unified_lifecycle()
134
+ test_priority_admission()
135
+ print("\n" + "=" * 60)
136
+ print("✓ Step1 验证完成:准入配额(背压)+ 生命周期事件 + 记账 + 透传")
137
+ print("=" * 60)
138
+
139
+
140
+ if __name__ == "__main__":
141
+ main()
@@ -0,0 +1,204 @@
1
+ """
2
+ 认知搜索内核(HSR 融入)测试 —— 全离线,不联网(external_ports=[])。
3
+
4
+ 覆盖:
5
+ 1. 空源检索:无 HMR / 无知识 / 无外部端口 → 返回 SearchResult,迭代有上限,不死循环
6
+ 2. 内部记忆证据:假 HMR recall → 证据图含 MEMORY 来源节点
7
+ 3. 知识端口边界:HKCReadPort(client=None) 永远返回 [](降级不报错)
8
+ 4. 经 Foxclaw 惰性 search_runtime + aclose teardown
9
+
10
+ 联网(百度)与真实 HKC 检索不在离线回归内;这里验证 HSR 的编排/融合/迭代/降级契约。
11
+ """
12
+ import sys, tempfile, shutil, asyncio
13
+ from pathlib import Path
14
+ sys.path.insert(0, str(Path(__file__).parent.parent))
15
+
16
+ from foxclaw.search import SearchRuntime, HKCReadPort, SourceType
17
+
18
+
19
+ class FakeHMR:
20
+ """假 HMR:recall 返回两条记忆,验证内部记忆证据能进证据图。"""
21
+ def recall(self, query, agent_id=None, top_k=5):
22
+ return [
23
+ {"id": "m1", "content": f"关于「{query}」的历史记忆一", "confidence": 0.7},
24
+ {"id": "m2", "content": f"关于「{query}」的历史记忆二", "confidence": 0.6},
25
+ ]
26
+
27
+
28
+ def test_empty_sources_terminates():
29
+ print("\n【1】空源检索:不死循环 + 迭代有上限")
30
+ rt = SearchRuntime(hmr=None, knowledge_port=None, external_ports=[])
31
+ r = rt.search("任意查询", agent_id="finance")
32
+ assert r.iterations <= 5, f"迭代应有上限,实际 {r.iterations}"
33
+ assert isinstance(r.trace, list) and r.trace
34
+ assert 0.0 <= r.confidence <= 1.0
35
+ print(f" ✓ 返回 SearchResult: iterations={r.iterations} confidence={r.confidence:.2f} "
36
+ f"trace={len(r.trace)}条")
37
+
38
+
39
+ def test_memory_evidence():
40
+ print("\n【2】内部记忆证据进证据图(MEMORY 来源)")
41
+ rt = SearchRuntime(hmr=FakeHMR(), knowledge_port=None, external_ports=[])
42
+ # "分析" 是 HYBRID 触发词 → 会查内部记忆(见 planner._HYBRID_HINTS)
43
+ r = rt.search("分析活字印刷的历史", agent_id="office")
44
+ nodes = list(r.graph.nodes.values())
45
+ mem = [n for n in nodes if n.source == SourceType.MEMORY]
46
+ assert len(mem) >= 1, "假 HMR 的记忆应作为证据进图"
47
+ print(f" ✓ 证据图 {len(nodes)} 节点,其中 MEMORY 来源 {len(mem)} 个")
48
+ print(f" 样例: {mem[0].fact[:30]!r} conf={mem[0].confidence:.2f}")
49
+
50
+
51
+ def test_hkc_port_degrades():
52
+ print("\n【3】知识端口降级:HKCReadPort(client=None) 返回 []")
53
+ port = HKCReadPort(client=None)
54
+ assert port.available is False
55
+ assert port.query("任意", top_k=5) == []
56
+ print(" ✓ client=None 时 available=False 且 query 返回 [](不报错)")
57
+
58
+
59
+ def test_via_foxclawos_lazy():
60
+ print("\n【4】经 Foxclaw 惰性 search_runtime + aclose")
61
+ d = tempfile.mkdtemp(prefix="hsr_os_")
62
+ try:
63
+ from foxclaw import Foxclaw
64
+ os_ = Foxclaw(data_dir=d, shadow_rounds=50)
65
+ # 【P1-3】search_runtime 由能力中枢惰性构造:未访问前未缓存
66
+ assert "search_runtime" not in os_.capabilities._caps, "未访问前不该构造"
67
+ rt = os_.search_runtime # 触发构造(顺带惰性装配 HKC 作知识端口)
68
+ assert rt is not None and os_.search_runtime is rt, "应缓存同一实例"
69
+ # 不联网:替换外部端口为空,跑一次
70
+ rt.external_ports = []
71
+ r = rt.search("测试查询", agent_id="supervisor")
72
+ assert r.confidence is not None
73
+ print(f" ✓ 惰性构造成功,search 返回 confidence={r.confidence:.2f}")
74
+ asyncio.run(os_.aclose())
75
+ print(" ✓ aclose() 释放(含惰性装配过的 HKC)")
76
+ finally:
77
+ shutil.rmtree(d, ignore_errors=True)
78
+
79
+
80
+ class _NS:
81
+ """简易命名空间对象(带属性)。"""
82
+ def __init__(self, **kw): self.__dict__.update(kw)
83
+
84
+
85
+ class FakeThoughtChain:
86
+ def search_nodes(self, keyword, agent_id=None, limit=20):
87
+ if "预算" in keyword or "风险" in keyword:
88
+ return [_NS(id="t1", agent_id="finance", content="评估本月预算风险偏高")]
89
+ return []
90
+
91
+
92
+ class FakeRegistry:
93
+ def catalog(self):
94
+ return [{"name": "gmail_send", "triggers": ["发邮件", "email"],
95
+ "kind": "mcp", "side_effect": "external"}]
96
+
97
+
98
+ def test_agent_port_all_subsources():
99
+ print("\n【5】AgentSourcePort:四类子源全连通")
100
+ from foxclaw.search import AgentSourcePort
101
+ finance = _NS(
102
+ transactions=[_NS(entity="星巴克咖啡", amount=35, category="餐饮", tags=["coffee"])],
103
+ budgets=[_NS(category="餐饮", monthly_limit=800)], entities={})
104
+ learning = _NS(items={"k1": _NS(content="活字印刷术由毕昇发明", topic="历史", tags=[])})
105
+ itinerary = _NS(waypoints=[_NS(name="首都机场", address="北京朝阳", city="北京", start_time="09:00")])
106
+ agents = {"finance": _NS(get_status=lambda: {"role": "理财风控", "status": "idle"})}
107
+
108
+ class HMRStub:
109
+ def recall(self, query, agent_id=None, top_k=5):
110
+ return [{"id": "m1", "content": f"{agent_id}记得关于{query}的事"}]
111
+
112
+ port = AgentSourcePort(agents=agents, finance_runtime=finance, learning_runtime=learning,
113
+ itinerary_runtime=itinerary, thought_chain=FakeThoughtChain(),
114
+ skill_registry=FakeRegistry(), hmr=HMRStub())
115
+ assert port.available
116
+ from foxclaw.search import SourceType
117
+
118
+ def origins(q):
119
+ return {e.origin for e in port.query(q, top_k=8)}
120
+
121
+ assert any("finance:transaction" in o for o in origins("星巴克")), "财务交易应连通"
122
+ assert any("learning" in o for o in origins("活字印刷")), "学习知识应连通"
123
+ assert any("itinerary" in o for o in origins("首都机场")), "行程点应连通"
124
+ assert any("thoughtchain" in o for o in origins("预算风险")), "推理链应连通"
125
+ assert any("capability:skill" in o for o in origins("发邮件")), "能力目录应连通"
126
+ assert any(":hmr:" in o for o in origins("任意")), "各Agent记忆应连通"
127
+ # 全部证据都标记为 AGENT 源
128
+ evs = port.query("星巴克", top_k=8)
129
+ assert all(e.source == SourceType.AGENT for e in evs)
130
+ print(" ✓ 财务/学习/行程/推理链/能力/各Agent记忆 六路均连通,来源标记 AGENT")
131
+
132
+
133
+ def test_bidirectional_and_internal_only():
134
+ print("\n【6】双向:Agent 调用内部搜索(internal_only,不联网)")
135
+ d = tempfile.mkdtemp(prefix="hsr_bi_")
136
+ try:
137
+ from foxclaw import Foxclaw
138
+ from foxclaw.search import SourceType
139
+ os_ = Foxclaw(data_dir=d, shadow_rounds=50)
140
+ # 往财务内核放一笔交易
141
+ from foxclaw.finance.models import Transaction
142
+ os_.finance_runtime.add_transaction(
143
+ Transaction(entity="蔚来汽车", amount=-260000, category="购车",
144
+ account="alipay", timestamp="2026-05-01T10:00:00"),
145
+ run_gatekeeper=False)
146
+ # 触发 search_runtime 装配(会把自身注入各 Agent)
147
+ _ = os_.search_runtime
148
+ # 财务 Agent 反向调用内部搜索
149
+ res = os_.finance.search_internal("蔚来汽车")
150
+ assert res is not None, "search_internal 应返回 SearchResult"
151
+ agent_ev = [n for n in res.graph.nodes.values() if n.source == SourceType.AGENT]
152
+ assert any("蔚来" in n.fact for n in agent_ev), "应从财务内核检索到该交易"
153
+ # internal_only:轨迹里不应出现 web 证据来源
154
+ assert all(n.source != SourceType.WEB for n in res.graph.nodes.values())
155
+ print(f" ✓ os_.finance.search_internal('蔚来汽车') → AGENT证据 {len(agent_ev)} 条,无联网")
156
+ asyncio.run(os_.aclose())
157
+ finally:
158
+ shutil.rmtree(d, ignore_errors=True)
159
+
160
+
161
+ def test_internal_source_isolation():
162
+ print("\n【7】深审修复:内部单源抛异常被隔离(不掀翻整个 search)")
163
+ class _BoomPort:
164
+ def query(self, text, top_k=5): raise RuntimeError("内部源炸了")
165
+ class _OKKnow:
166
+ def query(self, text, top_k=5):
167
+ from foxclaw.search import SourceType
168
+ from foxclaw.search.models import RawEvidence
169
+ return [RawEvidence(id="k1", fact=f"知识:{text}", source=SourceType.KNOWLEDGE,
170
+ origin="hkc:k1", confidence=0.7)]
171
+ # agent_port 抛异常 + memory(FakeHMR)正常 + knowledge 正常 → search 不崩,仍返回其它源证据
172
+ rt = SearchRuntime(hmr=FakeHMR(), knowledge_port=_OKKnow(),
173
+ agent_port=_BoomPort(), external_ports=[])
174
+ r = rt.search("测试查询", agent_id="finance", internal_only=True)
175
+ facts = [n.fact for n in r.graph.nodes.values()]
176
+ assert any("知识" in f for f in facts), "知识源应仍返回(不受 agent 源崩溃影响)"
177
+ assert any("记忆" in f for f in facts), "记忆源应仍返回"
178
+ print(f" ✓ agent 源抛异常被隔离,其它源正常:{len(r.graph.nodes)} 节点")
179
+ # 全部内部源都抛异常 → search 仍不崩,返回空图(优雅降级)
180
+ rt2 = SearchRuntime(hmr=None, knowledge_port=_BoomPort(),
181
+ agent_port=_BoomPort(), external_ports=[])
182
+ r2 = rt2.search("测试", internal_only=True)
183
+ assert len(r2.graph.nodes) == 0 and 0.0 <= r2.confidence <= 1.0
184
+ print(" ✓ 全源崩溃 → search 不崩、返回空图(与 external 隔离对称)")
185
+
186
+
187
+ def main():
188
+ print("=" * 60)
189
+ print("认知搜索内核 · HSR 融入(全离线,不联网)")
190
+ print("=" * 60)
191
+ test_empty_sources_terminates()
192
+ test_memory_evidence()
193
+ test_hkc_port_degrades()
194
+ test_via_foxclawos_lazy()
195
+ test_agent_port_all_subsources()
196
+ test_bidirectional_and_internal_only()
197
+ test_internal_source_isolation()
198
+ print("\n" + "=" * 60)
199
+ print("✓ HSR 融入验证完成:编排/证据融合/迭代上限/端口降级/惰性teardown")
200
+ print("=" * 60)
201
+
202
+
203
+ if __name__ == "__main__":
204
+ main()