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,212 @@
1
+ """三个薄点:失败策略表 / 执行后核验 / 执行证据。
2
+
3
+ 锁住的行为(对应审核报告里「静默假成功」那一类):
4
+ ① 失败文本能归类成明确类型,而不是"模型自己看着办";
5
+ ② 只有**只读**工具会自动重试一次 —— 有副作用的动作重试等于重复扣款/重复发消息;
6
+ ③ 每步都有核验结论:有凭据=verified,没后置条件=unverified,失败=failed,**空结果不算成功**;
7
+ ④ 证据只从真实返回里抽 URL / 磁盘上存在的文件,认不出的路径不冒充证据;
8
+ ⑤ 这些结论挂到会话槽并由 `_run_tool_calls` 记账,不上屏到用户正文;
9
+ ⑥ 成功结果的回注文本一个字都不改(否则会污染既有工具轮行为)。
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import types
15
+
16
+ import pytest
17
+
18
+ from foxclaw.agents.mixins import tool_exec
19
+ from foxclaw.core import failure_policy as policy
20
+ from foxclaw.core import tools as core_tools
21
+ from foxclaw.core import verify as vf
22
+
23
+ TIMEOUT_TEXT = "(工具 web_search 超过 45s 未返回,已跳过以免拖住整轮)"
24
+
25
+
26
+ class _Rec:
27
+ def __init__(self):
28
+ self.steps: list = []
29
+
30
+ def record_step(self, tid, step):
31
+ self.steps.append((tid, step))
32
+
33
+
34
+ def _agent():
35
+ a = types.SimpleNamespace()
36
+ a._recovery = _Rec()
37
+ a._session_id = "sess_test"
38
+ return a
39
+
40
+
41
+ @pytest.mark.parametrize("text,kind", [
42
+ (TIMEOUT_TEXT, policy.TIMEOUT),
43
+ ("(工具 shell 已被策略禁用)", policy.DENIED),
44
+ ("(联网搜索未配置,已降级)", policy.NOT_CONFIGURED),
45
+ ("(入参不合法:缺少必填 query)", policy.INVALID_ARGS),
46
+ ("(联网搜索未返回结果)", policy.EMPTY),
47
+ ("(工具 web_search 执行异常:boom)", policy.EXCEPTION),
48
+ ("中国未来十年的产业趋势如何", policy.OK),
49
+ ("", policy.UNKNOWN),
50
+ ("一切正常", policy.OK),
51
+ ])
52
+ def test_classify(text, kind) -> None:
53
+ assert policy.classify(text) == kind
54
+
55
+
56
+ def test_retry_only_for_read_only() -> None:
57
+ read = policy.decide(TIMEOUT_TEXT, capability={"read_only": True}, attempt=1)
58
+ assert read.action == policy.RETRY
59
+ assert policy.decide(TIMEOUT_TEXT, capability={"read_only": True}, attempt=2).action \
60
+ == policy.HAND_TO_HUMAN
61
+ write = policy.decide(TIMEOUT_TEXT, capability={"read_only": False}, attempt=1)
62
+ assert write.action == policy.HAND_TO_HUMAN
63
+ assert "重复执行" in write.hint
64
+
65
+
66
+ def test_decide_never_optimistic() -> None:
67
+ assert policy.decide("(联网搜索未配置)").action == policy.DEGRADE
68
+ assert policy.decide("(工具 x 已被策略禁用)").action == policy.STOP
69
+ assert policy.decide("(看不懂的回注").kind == policy.UNKNOWN
70
+ assert policy.decide("(看不懂的回注").action == policy.HAND_TO_HUMAN
71
+
72
+
73
+ def test_annotate_leaves_success_untouched() -> None:
74
+ d = policy.decide("搜索到的正文", capability={"read_only": True})
75
+ assert policy.annotate("搜索到的正文", d) == "搜索到的正文"
76
+ out = policy.annotate(TIMEOUT_TEXT, policy.decide(TIMEOUT_TEXT, capability={"read_only": True},
77
+ attempt=2))
78
+ assert out.startswith(TIMEOUT_TEXT)
79
+ assert "[执行策略]" in out
80
+
81
+
82
+ def test_failure_is_failed_not_verified() -> None:
83
+ v = vf.verify("web_search", {}, TIMEOUT_TEXT, {"read_only": True})
84
+ assert v.status == vf.FAILED
85
+ assert v.reason.startswith("失败类型")
86
+
87
+
88
+ def test_empty_result_is_not_success() -> None:
89
+ v = vf.verify("web_search", {}, "", {"read_only": True})
90
+ assert v.status == vf.FAILED
91
+
92
+
93
+ def test_read_tool_with_payload_is_verified(tmp_path) -> None:
94
+ p = tmp_path / "report.md"
95
+ p.write_text("x", encoding="utf-8")
96
+ v = vf.verify("read_file", {}, f"见 {p}", {"read_only": True})
97
+ assert v.status == vf.VERIFIED
98
+ assert [e.kind for e in v.evidence] == ["file"]
99
+ assert v.evidence[0].ref == str(p)
100
+
101
+
102
+ def test_side_effect_without_postcondition_is_unverified() -> None:
103
+ v = vf.verify("send_mail", {}, "已发送", {"read_only": False})
104
+ assert v.status == vf.UNVERIFIED
105
+ assert "后置条件" in v.reason
106
+ assert vf.verify("recall_memory", {}, "【召回的相关记忆】\n- a", {"read_only": True}).status \
107
+ == vf.VERIFIED
108
+
109
+
110
+ def test_web_search_without_source_is_unverified() -> None:
111
+ v = vf.verify("web_search", {}, "【联网搜索结果】据行业报告……", {"read_only": True})
112
+ assert v.status == vf.UNVERIFIED
113
+ assert "来源" in v.reason
114
+ v2 = vf.verify("web_search", {}, "【联网搜索结果】见 https://example.com/a", {"read_only": True})
115
+ assert v2.status == vf.VERIFIED
116
+ assert v2.evidence[0].label == "example.com"
117
+
118
+
119
+ def test_evidence_only_counts_real_paths(tmp_path) -> None:
120
+ ghost = "C:/definitely/not/here/x.md"
121
+ assert vf.extract_evidence("read_file", f"读了 {ghost}") == []
122
+ real = tmp_path / "a.md"
123
+ real.write_text("x", encoding="utf-8")
124
+ ev = vf.extract_evidence("read_file", f"读了 {real}")
125
+ assert [e.ref for e in ev] == [str(real)]
126
+
127
+
128
+ def test_evidence_dedup_and_cap() -> None:
129
+ text = " ".join([f"https://example.com/{i}" for i in range(20)]) + " https://example.com/1"
130
+ ev = vf.extract_evidence("web_search", text)
131
+ assert len(ev) == vf.MAX_EVIDENCE
132
+ assert len({e.ref for e in ev}) == vf.MAX_EVIDENCE
133
+
134
+
135
+ def test_note_for_model() -> None:
136
+ ok = vf.Verification(vf.VERIFIED)
137
+ assert vf.note_for_model(ok) == ""
138
+ un_read = vf.Verification(vf.UNVERIFIED, "无来源")
139
+ assert vf.note_for_model(un_read, read_only=True) == ""
140
+ assert "不要断言" in vf.note_for_model(un_read, read_only=False)
141
+ bad = vf.Verification(vf.FAILED, "失败类型:timeout")
142
+ assert "不要向用户声称" in vf.note_for_model(bad)
143
+
144
+
145
+ def test_retries_read_only_once_then_gives_up(monkeypatch) -> None:
146
+ agent = _agent()
147
+ batches: list = []
148
+
149
+ def fake_batch(a, calls):
150
+ batches.append([c["name"] for c in calls])
151
+ return [TIMEOUT_TEXT] * len(calls)
152
+
153
+ monkeypatch.setattr(tool_exec, "_run_tools_batch", fake_batch)
154
+ monkeypatch.setattr(core_tools, "tool_capability", lambda n: {"read_only": True})
155
+ out = tool_exec._run_tool_calls(agent, [{"name": "web_search", "args": {"q": "x"}}])
156
+
157
+ assert len(batches) == 2
158
+ assert "[执行策略]" in out[0] and "不要" in out[0]
159
+ assert agent._recovery.steps == []
160
+ ev = agent._evidence_list
161
+ assert ev and ev[0]["status"] == vf.FAILED
162
+
163
+
164
+ def test_side_effect_failure_is_not_retried(monkeypatch) -> None:
165
+ agent = _agent()
166
+ batches: list = []
167
+ monkeypatch.setattr(tool_exec, "_run_tools_batch",
168
+ lambda a, calls: (batches.append(len(calls)), [TIMEOUT_TEXT] * len(calls))[1])
169
+ monkeypatch.setattr(core_tools, "tool_capability", lambda n: {"read_only": False})
170
+ out = tool_exec._run_tool_calls(agent, [{"name": "send_mail", "args": {}}])
171
+ assert batches == [1]
172
+ assert "重复执行" in out[0]
173
+
174
+
175
+ def test_success_text_untouched_and_evidence_recorded(monkeypatch) -> None:
176
+ agent = _agent()
177
+ monkeypatch.setattr(tool_exec, "_run_tools_batch",
178
+ lambda a, calls: ["【联网搜索结果】结论 https://example.com/a"] * len(calls))
179
+ monkeypatch.setattr(core_tools, "tool_capability", lambda n: {"read_only": True})
180
+ out = tool_exec._run_tool_calls(agent, [{"name": "web_search", "args": {"q": "x"}}])
181
+
182
+ assert out[0] == "【联网搜索结果】结论 https://example.com/a"
183
+ assert len(agent._recovery.steps) == 1
184
+ ev = agent._evidence_list[0]
185
+ assert ev["status"] == vf.VERIFIED
186
+ assert ev["evidence"][0]["ref"] == "https://example.com/a"
187
+
188
+
189
+ def test_unverified_side_effect_tells_model_not_to_claim_done(monkeypatch) -> None:
190
+ agent = _agent()
191
+ monkeypatch.setattr(tool_exec, "_run_tools_batch", lambda a, calls: ["已发送"] * len(calls))
192
+ monkeypatch.setattr(core_tools, "tool_capability", lambda n: {"read_only": False})
193
+ out = tool_exec._run_tool_calls(agent, [{"name": "send_mail", "args": {}}])
194
+ assert out[0].startswith("已发送")
195
+ assert "不要断言" in out[0]
196
+ assert agent._evidence_list[0]["status"] == vf.UNVERIFIED
197
+
198
+
199
+ def test_evidence_is_per_agent() -> None:
200
+ from foxclaw.agents.mixins import _context
201
+
202
+ a, b = _agent(), _agent()
203
+ _context._evidence_reset(a)
204
+ _context._evidence_reset(b)
205
+ _context._evidence_add(a, {"tool": "x", "status": "verified"})
206
+ assert _context._evidence_take(a) and not _context._evidence_take(b)
207
+ assert _context._evidence_take(a) == []
208
+
209
+
210
+ def test_verify_never_raises_on_weird_input() -> None:
211
+ assert vf.verify("web_search", {}, "(((", None).status == vf.FAILED
212
+ assert vf.verify("unknown_tool", None, "ok", None).status == vf.UNVERIFIED
@@ -0,0 +1,100 @@
1
+ """版本号单一来源校验。
2
+
3
+ 版本号目前硬编码在五个地方,任何一处漏改都会造成「本地是 0.2.0、npm 上
4
+ 还是 0.1.0」这类对外不一致(`npm/index.js` 的 version 是发布物里用户
5
+ `import` 直接读到的值,最容易漏):
6
+
7
+ - ``pyproject.toml`` 的 ``project.version`` —— PyPI 包版本
8
+ - ``foxclaw/__init__.py`` 的 ``__version__`` —— 运行时 ``foxclaw --version``
9
+ - ``npm/package.json`` 的 ``version`` —— npm 包版本
10
+ - ``npm/index.js`` 的 ``export const version`` —— npm 包对外导出的版本
11
+ - ``frontend_src/package.json`` 的 ``version`` —— 前端构建工程版本
12
+
13
+ 本测试只做「五处必须相等」,不校验具体数值,发版时改哪一处都不用动它。
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import re
20
+ import tomllib
21
+ from pathlib import Path
22
+
23
+ from foxclaw import __version__
24
+
25
+ REPO_ROOT = Path(__file__).resolve().parents[1]
26
+ NPM_DIR = REPO_ROOT / "npm"
27
+
28
+
29
+ def _project_version() -> str:
30
+ with (REPO_ROOT / "pyproject.toml").open("rb") as fh:
31
+ return tomllib.load(fh)["project"]["version"]
32
+
33
+
34
+ def _project_name() -> str:
35
+ with (REPO_ROOT / "pyproject.toml").open("rb") as fh:
36
+ return tomllib.load(fh)["project"]["name"]
37
+
38
+
39
+ def _npm_package() -> dict:
40
+ return json.loads((NPM_DIR / "package.json").read_text(encoding="utf-8"))
41
+
42
+
43
+ def test_python_versions_agree() -> None:
44
+ """pyproject 与运行时 __version__ 一致。"""
45
+ assert __version__ == _project_version()
46
+
47
+
48
+ def test_npm_package_version_agrees() -> None:
49
+ """npm/package.json 与 PyPI 侧版本一致。"""
50
+ assert _npm_package()["version"] == _project_version()
51
+
52
+
53
+ def test_npm_index_export_version_agrees() -> None:
54
+ """npm/index.js 导出值(发布物对外可见)与 package.json 一致。"""
55
+ source = (NPM_DIR / "index.js").read_text(encoding="utf-8")
56
+ match = re.search(r'export\s+const\s+version\s*=\s*["\']([^"\']+)["\']', source)
57
+ assert match, "npm/index.js 里找不到 `export const version = \"...\"`"
58
+ assert match.group(1) == _npm_package()["version"]
59
+
60
+
61
+ def test_frontend_package_version_agrees() -> None:
62
+ """frontend_src/package.json 与 PyPI 侧版本一致。
63
+
64
+ 这个工程是 ``private`` 的构建工程,不发布,版本号曾长期停在 3.0.0
65
+ (Vue3 渲染层代号),与后端 0.1.2 对不上,容易误读成产品版本。
66
+ """
67
+ data = json.loads(
68
+ (REPO_ROOT / "frontend_src" / "package.json").read_text(encoding="utf-8"))
69
+ assert data["version"] == _project_version()
70
+
71
+
72
+ def test_package_names_are_foxclaw() -> None:
73
+ """PyPI 与 npm 两侧包名必须同名,且为 foxclaw。"""
74
+ assert _project_name() == "foxclaw"
75
+ assert _npm_package()["name"] == _project_name()
76
+
77
+
78
+ def test_npm_index_export_name_agrees() -> None:
79
+ """npm/index.js 导出的 name 与 package.json 一致。"""
80
+ source = (NPM_DIR / "index.js").read_text(encoding="utf-8")
81
+ match = re.search(r'export\s+const\s+name\s*=\s*["\']([^"\']+)["\']', source)
82
+ assert match, "npm/index.js 里找不到 `export const name = \"...\"`"
83
+ assert match.group(1) == _npm_package()["name"]
84
+
85
+
86
+ def test_frontend_does_not_hardcode_version() -> None:
87
+ """顶部栏版本号必须来自后端,不能在 Vue 里写死。
88
+
89
+ 2026-11-10:左上角硬编码 ``v3.0``,而后端已发到 0.1.2,用户看到的版本
90
+ 与仓库/PyPI 对不上。取值链路是 ``stores/system.js`` 请求
91
+ ``/api/system/info``,TopBar 只读 ``system.version``。
92
+ """
93
+ topbar = (REPO_ROOT / "frontend_src" / "src" / "components" / "TopBar.vue").read_text(
94
+ encoding="utf-8")
95
+ assert not re.search(r"\bv\d+\.\d+", topbar), "TopBar.vue 里出现了硬编码版本号"
96
+ assert "system.version" in topbar, "TopBar.vue 应从 system store 读 version"
97
+
98
+ store = (REPO_ROOT / "frontend_src" / "src" / "stores" / "system.js").read_text(
99
+ encoding="utf-8")
100
+ assert "/system/info" in store, "system store 应从 /api/system/info 取 version"
@@ -0,0 +1,82 @@
1
+ """联网触发判定:个人数据问题绝不联网,外部世界问题正常联网。
2
+
3
+ 背景(链路追踪抓出来的真 bug):原先纯时间词(今天/本月/最近…)也算触发,而 Foxclaw 的
4
+ 领域(个人财务/行程/学习)天生满是时间词 → 「分析一下本月支出」这类**问自己账本**的任务
5
+ 被误判为需联网,白跑 ~22s 的付费深度搜索。错搜代价远大于漏搜,故改为:
6
+ 个人数据否决 > 外部话题触发 > 纯时间词不触发。
7
+ """
8
+ import sys
9
+ from pathlib import Path
10
+ sys.path.insert(0, str(Path(__file__).parent.parent))
11
+ import pytest
12
+
13
+ def test_web_trigger():
14
+ if "foxclaw.core.web_search" in sys.modules:
15
+ pytest.skip("需独立进程运行(模块级单例已固化),请用 tests/run_all.py")
16
+ from foxclaw.core.web_search import (needs_search, TOPIC_TRIGGERS,
17
+ TIME_WORDS)
18
+
19
+ print("=" * 60); print("联网触发判定验证"); print("=" * 60)
20
+
21
+ personal = ["分析一下本月支出", "本月餐饮花了多少", "今天的行程安排",
22
+ "本周要复习什么知识点", "账户余额还剩多少", "这个月消费记录"]
23
+ for q in personal:
24
+ assert needs_search(q) is False, f"个人数据不该联网: {q}"
25
+ print(f"[1] 个人数据不联网({len(personal)} 例,含「本月支出」这类原 bug 用例)OK")
26
+
27
+ assert needs_search("本月支出多少钱") is False, "个人数据否决应压过话题触发"
28
+ assert needs_search("我的预算还剩多少钱") is False
29
+ print("[2] 个人数据否决压过话题触发(「本月支出多少钱」不联网)OK")
30
+
31
+ external = ["今天天气怎么样", "最新的AI新闻", "iPhone现在多少钱",
32
+ "特斯拉股价", "python最新版本", "美元汇率"]
33
+ for q in external:
34
+ assert needs_search(q) is True, f"外部话题应联网: {q}"
35
+ print(f"[3] 外部话题正常联网({len(external)} 例)OK")
36
+
37
+ assert needs_search("我的股票现在多少钱") is True, "「我的」过宽会误杀真实联网需求"
38
+ print("[4] 「我的股票现在多少钱」仍联网(个人标记不能放「我的」这类过宽词)OK")
39
+
40
+ for q in ["现在几点", "最近怎么样", "今天", "本月"]:
41
+ assert needs_search(q) is False, f"纯时间词不该单独触发: {q}"
42
+ assert not (set(TIME_WORDS) & set(TOPIC_TRIGGERS)), "时间词不得混入话题触发表(否则 bug 复发)"
43
+ print(f"[5] 纯时间词不单独触发({len(TIME_WORDS)} 个时间词与话题表零交集)OK")
44
+
45
+ for q in ["你好", "帮我写个函数", ""]:
46
+ assert needs_search(q) is False, q
47
+ print("[6] 闲聊/编码/空串 不联网 OK")
48
+
49
+ assert needs_search("knowledge base") is False, "now 不该子串命中 knowledge"
50
+ assert needs_search("concurrent tasks") is False, "current 不该子串命中 concurrent"
51
+ assert needs_search("latest news") is True
52
+ print("[7] 英文词边界不退化(knowledge/concurrent 不误命中)OK")
53
+
54
+ from foxclaw.core.web_search import is_personal_query
55
+ from foxclaw.pipeline import stage_route
56
+ from foxclaw.pipeline.cognitive_pipeline import PipelineContext
57
+ from foxclaw.skills import build_default_registry
58
+
59
+ assert is_personal_query("查一下最新的本月支出") is True
60
+ assert is_personal_query("查一下最新新闻") is False
61
+
62
+ _reg = build_default_registry()
63
+ class _A: pass
64
+ def _route_target(task):
65
+ a = _A(); a.skill_registry = _reg
66
+ ctx = PipelineContext(agent=a, task=task)
67
+ stage_route(ctx)
68
+ return ctx.route["target"], ctx.route.get("skill_id")
69
+
70
+ for q in ["查一下最新的本月支出", "查一下最新的预算结余", "今天的行程 最新 查一下"]:
71
+ t, sid = _route_target(q)
72
+ assert t == "self", f"私人数据不得自动发给外部技能: {q} → {t}/{sid}"
73
+ t, sid = _route_target("查一下最新新闻")
74
+ assert (t, sid) == ("skill", "web_search"), (t, sid)
75
+ t, sid = _route_target("帮我计算 (12+8)*5")
76
+ assert (t, sid) == ("skill", "calculator"), (t, sid)
77
+ print("[8] 跨路径防复发:技能路由也遵守个人数据保护(不外泄)+ 不误伤真实外部需求 OK")
78
+
79
+ print("\n联网触发判定验证全部通过")
80
+
81
+ if __name__ == "__main__":
82
+ test_web_trigger()
@@ -0,0 +1,30 @@
1
+ """检查磁盘 faiss 索引的类型和 ntotal(不依赖服务进程)"""
2
+ import sys
3
+ import os
4
+ from pathlib import Path
5
+
6
+ faiss_path = str(Path(__file__).resolve().parents[1] / "foxclaw_data" / "hkc" / "search_vector.faiss")
7
+ if len(sys.argv) > 1:
8
+ faiss_path = sys.argv[1]
9
+
10
+ if not os.path.exists(faiss_path):
11
+ print(f"NOT_FOUND: {faiss_path}")
12
+ sys.exit(1)
13
+
14
+ try:
15
+ import faiss
16
+ except ImportError:
17
+ print("faiss-cpu not installed (try: pip install faiss-cpu)")
18
+ sys.exit(1)
19
+
20
+ print(f"path: {faiss_path}")
21
+ print(f"size: {os.path.getsize(faiss_path)} bytes")
22
+
23
+ idx = faiss.read_index(faiss_path)
24
+ print(f"type : {type(idx).__name__}")
25
+ print(f"ntotal: {idx.ntotal}")
26
+ print(f"dim : {idx.d}")
27
+ if hasattr(idx, "nlist"):
28
+ print(f"nlist: {idx.nlist}")
29
+ if hasattr(idx, "metric_type"):
30
+ print(f"metric: {idx.metric_type}")
@@ -0,0 +1,38 @@
1
+ param(
2
+ [ValidateSet("stdout","err")] $which = "stdout",
3
+ [switch] $grep
4
+ )
5
+ $ErrorActionPreference = "Continue"
6
+
7
+ $logDir = Join-Path (Split-Path -Parent $PSScriptRoot) "foxclaw_data"
8
+ $log = if ($which -eq "err") { "$logDir\uvicorn.err.log" } else { "$logDir\uvicorn.log" }
9
+
10
+ if (-not (Test-Path $log)) {
11
+ Write-Host "[NOT FOUND] $log"
12
+ exit 1
13
+ }
14
+
15
+ $fs = [System.IO.File]::Open($log, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
16
+ $reader = New-Object System.IO.StreamReader($fs, [System.Text.Encoding]::UTF8)
17
+ $content = $reader.ReadToEnd()
18
+ $reader.Close()
19
+ $fs.Close()
20
+
21
+ if ($grep) {
22
+ foreach ($line in ($content -split "`n")) {
23
+ if ($line -match "VectorIndex 升级|VectorIndex 升级失败") {
24
+ Write-Host $line
25
+ }
26
+ }
27
+ return
28
+ }
29
+
30
+ Write-Host "log file: $log (size=$($content.Length))"
31
+ Write-Host ("has_VectorIndex=" + $content.Contains("VectorIndex"))
32
+ Write-Host ("has_IVF=" + $content.Contains("IVF"))
33
+ Write-Host ("has_upgrade_zh=" + $content.Contains("升级"))
34
+ Write-Host ("has_IndexFlat=" + $content.Contains("IndexFlat"))
35
+ Write-Host ("has_IndexIVF=" + $content.Contains("IndexIVF"))
36
+ Write-Host "--- last 1500 chars ---"
37
+ $start = [Math]::Max(0, $content.Length - 1500)
38
+ Write-Host $content.Substring($start)
@@ -0,0 +1,72 @@
1
+ """检查 vector 索引状态 + 距离自动升级还差多少。
2
+
3
+ 用法:
4
+ python tools/ivf_status.py
5
+
6
+ 输出:
7
+ vector_size, bm25_size, index_state, pending_ops
8
+ 距 HKC_VECTOR_IVF_THRESHOLD 阈值还差多少 KU
9
+ 下次升级触发时日志关键词
10
+
11
+ 可调环境变量:
12
+ HKC_VECTOR_IVF_THRESHOLD 默认 10000(对应 .env 里的设定)
13
+ HKC_STATS_URL 默认 http://127.0.0.1:8000/stats
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import os
19
+ import sys
20
+ import urllib.request
21
+
22
+ THRESHOLD = int(os.environ.get("HKC_VECTOR_IVF_THRESHOLD", "10000"))
23
+ STATS_URL = os.environ.get("HKC_STATS_URL", "http://127.0.0.1:8000/stats")
24
+ UPGRADE_KEYWORD = "VectorIndex 升级: Flat → IVF"
25
+
26
+
27
+ def fetch_stats(url: str) -> dict:
28
+ with urllib.request.urlopen(url, timeout=5) as r:
29
+ return json.loads(r.read())
30
+
31
+
32
+ def main() -> int:
33
+ try:
34
+ s = fetch_stats(STATS_URL)
35
+ except Exception as e:
36
+ print(f"[ERROR] 拉 {STATS_URL} 失败: {e}")
37
+ return 1
38
+
39
+ vs = s.get("vector_size", 0)
40
+ bs = s.get("bm25_size", 0)
41
+ ib = s.get("index_build", {}) or {}
42
+ state = ib.get("index_state", "?")
43
+ pending = s.get("pending_ops", 0)
44
+ task_id = ib.get("task_id", "-")
45
+
46
+ remaining = max(0, THRESHOLD - vs)
47
+ pct = min(100.0, vs / THRESHOLD * 100) if THRESHOLD > 0 else 100.0
48
+
49
+ print("=" * 50)
50
+ print(f"vector_size : {vs}")
51
+ print(f"bm25_size : {bs}")
52
+ print(f"index_state : {state} task_id={task_id}")
53
+ print(f"pending_ops : {pending}")
54
+ print(f"threshold : {THRESHOLD}")
55
+ print(f"progress : {pct:5.1f}% (距升级还差 {remaining} KU)")
56
+ print("=" * 50)
57
+
58
+ if THRESHOLD <= 0:
59
+ print("阶段: Flat (升级禁用,env HKC_VECTOR_IVF_THRESHOLD=0)")
60
+ elif vs < THRESHOLD:
61
+ print(f"阶段: Flat (IndexFlatIP, O(N) 暴力精确)")
62
+ print(f"下次 vector_size 跨过 {THRESHOLD} 时 append_ku 会触发升级")
63
+ print(f"触发后看日志: grep '{UPGRADE_KEYWORD}' <foxclaw.log>")
64
+ else:
65
+ print(f"阶段: IVF (IndexIVFFlat, O(√N) 倒排)")
66
+ print(f"升级日志关键词: '{UPGRADE_KEYWORD}'")
67
+
68
+ return 0
69
+
70
+
71
+ if __name__ == "__main__":
72
+ sys.exit(main())
@@ -0,0 +1,13 @@
1
+ # mypy 类型错误棘轮基线。只允许下降。
2
+ # 2026-11-10 复核:mypy 2.4.1 / Python 3.12(.venv,依赖按 requirements.txt)→ 0 errors。
3
+ # 历史:673(2.4.0 / 3.14 旧依赖)→ 655(cli.py 不再用 **dict 展开调 uvicorn.run)
4
+ # → 645(playwright_driver 收进 _require_page;_policy 用 np.float64)
5
+ # → 408(api/app.py 改名 bootstrap.py,解掉 101 条「子模块名遮蔽 app 变量」;
6
+ # 路由层经 os_instance() 取共享实例,消掉 104 条 Optional 属性访问;
7
+ # PolicyMixin 声明宿主注入状态;-33;model_router 补 Any/Optional 注解)。
8
+ # → 152(基准校准;此后按批真修:httpx kwargs 类型化 / named_stage 装修饰器、
9
+ # None 默认字段 Optional 化、tracing+守门人句柄窄化、Literal 收窄、
10
+ # max/min key 改 lambda、坐标与脏输入窄化)。
11
+ # → 0(2026-11-10:余量清空。修法一律是真修——Optional 注解、局部变量窄化、
12
+ # cast(Literal)、诚实返回类型;未新增 type: ignore,未放宽 mypy 配置)
13
+ 0