argo-search 1.0.1 → 2.9.7

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 (463) hide show
  1. package/LICENSE +201 -21
  2. package/README.en.md +822 -0
  3. package/README.es.md +864 -0
  4. package/README.ja.md +864 -0
  5. package/README.ko.md +864 -0
  6. package/README.md +867 -320
  7. package/SKILL.md +135 -0
  8. package/assets/readme/hero.svg +121 -0
  9. package/assets/readme/made-with-beautify.svg +10 -0
  10. package/assets/readme/proof-routes.svg +73 -0
  11. package/assets/readme/settings-keys.png +0 -0
  12. package/assets/readme/settings-tools.png +0 -0
  13. package/assets/readme/why-better.svg +104 -0
  14. package/assets/readme/workflow.svg +83 -0
  15. package/backends/domain_profiles.json +2308 -96
  16. package/backends/engine_registry.yaml +3314 -503
  17. package/backends/query_synonyms_cn.json +35 -0
  18. package/backends/quota_profiles.json +2409 -161
  19. package/backends/source_types_cn.json +83 -0
  20. package/backends/text_lexicon.json +44 -0
  21. package/bin/argo +703 -0
  22. package/bin/argo.js +71 -39
  23. package/config.yaml +4976 -363
  24. package/cordis.patch.yml +21 -0
  25. package/data/countries.json +40 -0
  26. package/data/lang_matrix/lang_capability.json +842 -0
  27. package/data/lang_matrix/matrix_2026-09-10.json +11921 -0
  28. package/data/regions_cn.json +1 -0
  29. package/docs/ADDING_NEW_ENGINE.md +184 -0
  30. package/docs/ARGO_INTRO.md +124 -0
  31. package/docs/DATA_SOURCE_GAPS.md +81 -0
  32. package/docs/DESIGN_LOCAL_DATA_FUSION.md +90 -0
  33. package/docs/ENGINE_CATALOG.md +564 -0
  34. package/docs/MCP_SETUP.md +106 -0
  35. package/docs/PLATFORM_PATHS.md +111 -0
  36. package/docs/RELEASE_NOTES_v2.8.0.md +71 -0
  37. package/docs/RELEASE_NOTES_v2.8.2.md +63 -0
  38. package/docs/RELEASE_NOTES_v2.8.3.md +55 -0
  39. package/docs/RELEASE_NOTES_v2.8.4.md +79 -0
  40. package/docs/RELEASE_NOTES_v2.8.5.md +102 -0
  41. package/docs/RELEASE_NOTES_v2.8.6.md +66 -0
  42. package/docs/RELEASE_NOTES_v2.8.7.md +65 -0
  43. package/docs/RELEASE_NOTES_v2.8.8.md +67 -0
  44. package/docs/RELEASE_NOTES_v2.8.9.md +67 -0
  45. package/docs/RELEASE_NOTES_v2.9.0.md +64 -0
  46. package/docs/SEARCH_ARCHIVE.md +115 -0
  47. package/docs/assets/architecture.svg +104 -0
  48. package/docs/assets/hero.svg +98 -0
  49. package/docs/engines/bangumi.md +27 -0
  50. package/docs/engines/biorxiv.md +27 -0
  51. package/docs/engines/clawhub.md +27 -0
  52. package/docs/engines/crt_sh.md +27 -0
  53. package/docs/engines/datacite.md +27 -0
  54. package/docs/engines/deps_dev.md +27 -0
  55. package/docs/engines/douban_movie.md +27 -0
  56. package/docs/engines/endoflife.md +27 -0
  57. package/docs/engines/flk_law.md +27 -0
  58. package/docs/engines/fxtwitter.md +27 -0
  59. package/docs/engines/google_news.md +27 -0
  60. package/docs/engines/gov_regulations.md +27 -0
  61. package/docs/engines/guqian.md +27 -0
  62. package/docs/engines/hackernews.md +27 -0
  63. package/docs/engines/iplant.md +27 -0
  64. package/docs/engines/keenable.md +27 -0
  65. package/docs/engines/met_museum.md +27 -0
  66. package/docs/engines/nasa_images.md +27 -0
  67. package/docs/engines/netease_music.md +27 -0
  68. package/docs/engines/npm.md +27 -0
  69. package/docs/engines/nvd.md +27 -0
  70. package/docs/engines/openreview.md +27 -0
  71. package/docs/engines/openstd.md +27 -0
  72. package/docs/engines/parallel_free.md +27 -0
  73. package/docs/engines/people_daily.md +27 -0
  74. package/docs/engines/realtime_index.md +27 -0
  75. package/docs/engines/sec_edgar.md +27 -0
  76. package/docs/engines/seltz.md +27 -0
  77. package/docs/engines/skillsmp.md +27 -0
  78. package/docs/engines/sspai.md +27 -0
  79. package/docs/engines/std_samr.md +27 -0
  80. package/docs/engines/train.md +27 -0
  81. package/docs/engines/un_comtrade.md +27 -0
  82. package/docs/engines/wikisource.md +27 -0
  83. package/docs/engines/zdic.md +27 -0
  84. package/docs/engines/zenodo.md +27 -0
  85. package/docs/engines/zhihu_hot_app.md +27 -0
  86. package/docs//344/270/272/344/273/200/344/271/210/351/200/211/346/213/251argo.md +176 -0
  87. package/engines/README.md +10 -0
  88. package/engines/_template_cli.yaml +39 -0
  89. package/engines/_template_http.yaml +37 -0
  90. package/engines/plugins/_template_plugin.py +50 -0
  91. package/engines/specs/_cisa_kev.yaml +54 -0
  92. package/engines/specs/_osv.yaml +63 -0
  93. package/engines/specs/aljazeera.yaml +43 -0
  94. package/engines/specs/antara.yaml +43 -0
  95. package/engines/specs/artic.yaml +20 -0
  96. package/engines/specs/aviation_weather.yaml +39 -0
  97. package/engines/specs/bangumi.yaml +21 -0
  98. package/engines/specs/biorxiv.yaml +24 -0
  99. package/engines/specs/carbon_intensity.yaml +32 -0
  100. package/engines/specs/cinii.yaml +44 -0
  101. package/engines/specs/clawhub.yaml +49 -0
  102. package/engines/specs/cleveland.yaml +19 -0
  103. package/engines/specs/cn_ai_news.yaml +43 -0
  104. package/engines/specs/core.yaml +35 -0
  105. package/engines/specs/crt_sh.yaml +38 -0
  106. package/engines/specs/ct24.yaml +43 -0
  107. package/engines/specs/datacite.yaml +27 -0
  108. package/engines/specs/datagov_uk.yaml +41 -0
  109. package/engines/specs/ddgs_images.yaml +45 -0
  110. package/engines/specs/deepseek_search.yaml +74 -0
  111. package/engines/specs/deezer.yaml +20 -0
  112. package/engines/specs/deps_dev.yaml +21 -0
  113. package/engines/specs/digup.yaml +36 -0
  114. package/engines/specs/doi.yaml +39 -0
  115. package/engines/specs/douban_movie.yaml +22 -0
  116. package/engines/specs/dw_news.yaml +47 -0
  117. package/engines/specs/egov_law.yaml +21 -0
  118. package/engines/specs/elpais.yaml +43 -0
  119. package/engines/specs/endoflife.yaml +21 -0
  120. package/engines/specs/energy_charts.yaml +21 -0
  121. package/engines/specs/factcheck_org.yaml +52 -0
  122. package/engines/specs/faz.yaml +43 -0
  123. package/engines/specs/federal_register.yaml +57 -0
  124. package/engines/specs/firecrawl.yaml +53 -0
  125. package/engines/specs/flk_law.yaml +24 -0
  126. package/engines/specs/folha.yaml +43 -0
  127. package/engines/specs/france24.yaml +44 -0
  128. package/engines/specs/full_fact.yaml +47 -0
  129. package/engines/specs/fxtwitter.yaml +31 -0
  130. package/engines/specs/gbfs_nyc.yaml +32 -0
  131. package/engines/specs/gdacs.yaml +21 -0
  132. package/engines/specs/gdelt.yaml +64 -0
  133. package/engines/specs/google_news.yaml +22 -0
  134. package/engines/specs/gov_regulations.yaml +38 -0
  135. package/engines/specs/guardian_rss.yaml +59 -0
  136. package/engines/specs/hurriyet.yaml +43 -0
  137. package/engines/specs/iplant.yaml +25 -0
  138. package/engines/specs/jikan.yaml +24 -0
  139. package/engines/specs/jolpica.yaml +20 -0
  140. package/engines/specs/k10plus.yaml +20 -0
  141. package/engines/specs/keenable.yaml +44 -0
  142. package/engines/specs/lefigaro.yaml +43 -0
  143. package/engines/specs/listenbrainz.yaml +19 -0
  144. package/engines/specs/matichon.yaml +43 -0
  145. package/engines/specs/met_museum.yaml +21 -0
  146. package/engines/specs/nasa_images.yaml +45 -0
  147. package/engines/specs/netease_music.yaml +38 -0
  148. package/engines/specs/nhk.yaml +43 -0
  149. package/engines/specs/nhtsa_vpic.yaml +20 -0
  150. package/engines/specs/noaa_swpc.yaml +20 -0
  151. package/engines/specs/nvd.yaml +38 -0
  152. package/engines/specs/obis.yaml +20 -0
  153. package/engines/specs/opencitations.yaml +52 -0
  154. package/engines/specs/openf1.yaml +21 -0
  155. package/engines/specs/openfoodfacts.yaml +45 -0
  156. package/engines/specs/openligadb.yaml +20 -0
  157. package/engines/specs/openreview.yaml +37 -0
  158. package/engines/specs/openstd.yaml +21 -0
  159. package/engines/specs/people_daily.yaml +22 -0
  160. package/engines/specs/realtime_index.yaml +34 -0
  161. package/engines/specs/ror.yaml +20 -0
  162. package/engines/specs/satnogs.yaml +20 -0
  163. package/engines/specs/sec_edgar.yaml +46 -0
  164. package/engines/specs/skillsmp.yaml +50 -0
  165. package/engines/specs/sogou_weixin_browser.yaml +30 -0
  166. package/engines/specs/soilgrids.yaml +25 -0
  167. package/engines/specs/sspai.yaml +36 -0
  168. package/engines/specs/stackexchange.yaml +46 -0
  169. package/engines/specs/std_samr.yaml +21 -0
  170. package/engines/specs/tass.yaml +43 -0
  171. package/engines/specs/tle_mirror.yaml +21 -0
  172. package/engines/specs/train.yaml +37 -0
  173. package/engines/specs/tvmaze.yaml +20 -0
  174. package/engines/specs/twitter_syndication.yaml +31 -0
  175. package/engines/specs/un_comtrade.yaml +23 -0
  176. package/engines/specs/unpaywall.yaml +54 -0
  177. package/engines/specs/vnpress.yaml +43 -0
  178. package/engines/specs/weather.yaml +32 -0
  179. package/engines/specs/who_don.yaml +21 -0
  180. package/engines/specs/who_gho.yaml +20 -0
  181. package/engines/specs/wikimedia_commons.yaml +34 -0
  182. package/engines/specs/wikisource.yaml +22 -0
  183. package/engines/specs/worms.yaml +19 -0
  184. package/engines/specs/x_trending.yaml +34 -0
  185. package/engines/specs/yna.yaml +43 -0
  186. package/engines/specs/ynet.yaml +43 -0
  187. package/engines/specs/zbmath.yaml +40 -0
  188. package/engines/specs/zdic.yaml +22 -0
  189. package/engines/specs/zenodo.yaml +27 -0
  190. package/engines/specs/zhihu_hot_app.yaml +35 -0
  191. package/mcp/clients.yaml +74 -0
  192. package/package.json +51 -12
  193. package/packages/dsh-plugin/README.md +134 -0
  194. package/packages/dsh-plugin/cordis.patch.yml +48 -0
  195. package/packages/dsh-plugin/dsh/client.js +1244 -0
  196. package/packages/dsh-plugin/dsh/index.js +2290 -0
  197. package/packages/dsh-plugin/dsh/native-tools.mjs +743 -0
  198. package/packages/dsh-plugin/package.json +38 -0
  199. package/packages/dsh-plugin/test/client.test.js +232 -0
  200. package/packages/dsh-plugin/test/config.test.js +30 -0
  201. package/packages/dsh-plugin/test/index.test.js +1338 -0
  202. package/references/query-templates.md +96 -0
  203. package/references/query-templates.yaml +118 -0
  204. package/scripts/ab_eval.py +219 -0
  205. package/scripts/ab_eval_p0p1.py +266 -0
  206. package/scripts/adaptive.py +271 -70
  207. package/scripts/answer.py +154 -0
  208. package/scripts/archive_run.py +591 -0
  209. package/scripts/argo_engine_registry.py +172 -0
  210. package/scripts/argo_paths.py +728 -0
  211. package/scripts/article.py +136 -0
  212. package/scripts/arxiv_source.py +201 -0
  213. package/scripts/badcase_ledger.py +252 -0
  214. package/scripts/batch_probe.py +241 -0
  215. package/scripts/benchmark.py +19 -0
  216. package/scripts/bounded_run.py +144 -0
  217. package/scripts/browser_auth.py +323 -0
  218. package/scripts/burstiness_detector.py +147 -0
  219. package/scripts/cache.py +1078 -67
  220. package/scripts/cache_guard.py +271 -0
  221. package/scripts/cache_key_vdom.py +56 -0
  222. package/scripts/cache_similarity.py +170 -0
  223. package/scripts/caliber_isolation.py +195 -0
  224. package/scripts/calibrate_lowquality.py +210 -0
  225. package/scripts/candidate_envelope.py +366 -0
  226. package/scripts/chrome_cdp.py +871 -0
  227. package/scripts/circuit_breaker.py +389 -0
  228. package/scripts/citations.py +334 -0
  229. package/scripts/cite_cli.py +59 -0
  230. package/scripts/cjk_tokens.py +95 -0
  231. package/scripts/clarify.py +47 -24
  232. package/scripts/cli_io.py +240 -0
  233. package/scripts/config.py +683 -45
  234. package/scripts/config_cache.py +402 -0
  235. package/scripts/conn_pool.py +220 -0
  236. package/scripts/content_security.py +685 -0
  237. package/scripts/content_signals.py +730 -0
  238. package/scripts/crawl.py +74 -35
  239. package/scripts/daemon.py +866 -0
  240. package/scripts/digup_bridge.py +214 -0
  241. package/scripts/engine_admission.py +366 -0
  242. package/scripts/engine_dispatch.py +1019 -0
  243. package/scripts/engine_env.py +534 -0
  244. package/scripts/engine_failure.py +411 -0
  245. package/scripts/engine_families.py +732 -0
  246. package/scripts/engine_policy.py +218 -0
  247. package/scripts/engine_requires.py +190 -0
  248. package/scripts/engine_status.py +322 -0
  249. package/scripts/engine_validate.py +638 -0
  250. package/scripts/engines.py +778 -294
  251. package/scripts/engines_base.py +1521 -0
  252. package/scripts/engines_builders.py +298 -0
  253. package/scripts/engines_builders_batch10.py +159 -0
  254. package/scripts/engines_builders_batch11.py +174 -0
  255. package/scripts/engines_builders_batch12.py +103 -0
  256. package/scripts/engines_builders_batch13.py +340 -0
  257. package/scripts/engines_builders_batch9.py +1468 -0
  258. package/scripts/engines_builders_cn.py +2283 -0
  259. package/scripts/engines_builders_data.py +2923 -0
  260. package/scripts/engines_builders_data_macro.py +775 -0
  261. package/scripts/engines_builders_feeds.py +255 -0
  262. package/scripts/engines_builders_intl.py +1239 -0
  263. package/scripts/engines_builders_search.py +440 -0
  264. package/scripts/engines_builders_tech.py +1360 -0
  265. package/scripts/evidence.py +588 -162
  266. package/scripts/evidence_loop.py +592 -0
  267. package/scripts/evidence_tier.py +298 -0
  268. package/scripts/except_sets.py +25 -0
  269. package/scripts/extract.py +55 -16
  270. package/scripts/fact_align.py +207 -0
  271. package/scripts/fact_crosscheck.py +230 -0
  272. package/scripts/fetch.py +25 -12
  273. package/scripts/fetch_browser.py +87 -0
  274. package/scripts/fetch_jsrun.py +122 -0
  275. package/scripts/fetch_quality.py +225 -0
  276. package/scripts/fetch_render_tinyfish.py +117 -0
  277. package/scripts/fetch_v3.py +1779 -0
  278. package/scripts/focus_extract.py +350 -0
  279. package/scripts/fulltext_store.py +169 -0
  280. package/scripts/fxtwitter_api.py +325 -0
  281. package/scripts/gen_engine_catalog.py +364 -0
  282. package/scripts/gen_native_tools.py +91 -0
  283. package/scripts/gen_routing_snapshot.py +184 -0
  284. package/scripts/health_check.py +140 -402
  285. package/scripts/health_probe.py +23 -8
  286. package/scripts/hot_state.py +134 -0
  287. package/scripts/http_client.py +1053 -0
  288. package/scripts/image_ops.py +430 -0
  289. package/scripts/image_vision/vision_probe +0 -0
  290. package/scripts/image_vision/vision_probe.swift +126 -0
  291. package/scripts/install.ps1 +148 -0
  292. package/scripts/install.sh +206 -0
  293. package/scripts/job.py +1186 -0
  294. package/scripts/jump_resolver.py +235 -0
  295. package/scripts/lang_capability.py +150 -0
  296. package/scripts/lang_detect.py +289 -0
  297. package/scripts/lang_matrix_probe.py +329 -0
  298. package/scripts/lang_pref.py +311 -0
  299. package/scripts/link_source.py +340 -0
  300. package/scripts/local_engines.py +321 -0
  301. package/scripts/local_image.py +970 -0
  302. package/scripts/local_seek.py +366 -0
  303. package/scripts/macro_countries.py +82 -0
  304. package/scripts/matrix_search_eval.py +1105 -0
  305. package/scripts/mcp_diag.py +8 -3
  306. package/scripts/mcp_handlers.py +1074 -0
  307. package/scripts/mcp_handlers_surface.py +157 -0
  308. package/scripts/mcp_launch.sh +122 -0
  309. package/scripts/mcp_server.py +118 -417
  310. package/scripts/mcp_setup.py +407 -0
  311. package/scripts/mcp_tools.py +330 -0
  312. package/scripts/mcp_transport.py +191 -0
  313. package/scripts/min_version_scan.py +335 -0
  314. package/scripts/net_proxy.py +258 -0
  315. package/scripts/network_aware.py +152 -0
  316. package/scripts/paper.py +274 -0
  317. package/scripts/pdf_extract.py +518 -0
  318. package/scripts/phrase_filter.py +66 -0
  319. package/scripts/plan.py +422 -0
  320. package/scripts/postprocess_signals.py +150 -0
  321. package/scripts/pre-commit +48 -0
  322. package/scripts/query_enhance.py +157 -0
  323. package/scripts/query_operators.py +196 -0
  324. package/scripts/query_rewriter.py +116 -7
  325. package/scripts/query_signals.py +300 -0
  326. package/scripts/query_templates.py +167 -0
  327. package/scripts/query_understanding.py +411 -0
  328. package/scripts/query_variants.py +298 -0
  329. package/scripts/quota.py +488 -76
  330. package/scripts/rank_signals.py +775 -0
  331. package/scripts/ranking_eval.py +319 -0
  332. package/scripts/readability_extract.py +433 -0
  333. package/scripts/recompute.py +415 -0
  334. package/scripts/recovery.py +616 -0
  335. package/scripts/recovery_l0.py +160 -0
  336. package/scripts/redskill/redskill_engine.py +332 -0
  337. package/scripts/regression_p0p1.py +318 -0
  338. package/scripts/replay_eval.py +640 -0
  339. package/scripts/rerank_tfidf.py +225 -0
  340. package/scripts/research.py +591 -403
  341. package/scripts/research_cli.py +361 -0
  342. package/scripts/research_dossier.py +492 -0
  343. package/scripts/research_expand.py +146 -0
  344. package/scripts/research_gates.py +272 -0
  345. package/scripts/research_report.py +209 -0
  346. package/scripts/research_strategy.py +37 -0
  347. package/scripts/research_work_packages.py +195 -0
  348. package/scripts/result_lang.py +311 -0
  349. package/scripts/robots_guard.py +259 -0
  350. package/scripts/route.py +921 -254
  351. package/scripts/route_bangs.py +40 -0
  352. package/scripts/route_cache.py +225 -0
  353. package/scripts/route_combo.py +910 -0
  354. package/scripts/route_domains.py +328 -0
  355. package/scripts/route_lang.py +605 -0
  356. package/scripts/route_log.py +63 -0
  357. package/scripts/route_policy.py +250 -0
  358. package/scripts/rrf_classic.py +66 -0
  359. package/scripts/screenshot.py +73 -0
  360. package/scripts/search.py +897 -405
  361. package/scripts/search_attribution.py +88 -0
  362. package/scripts/search_benchmark.py +407 -0
  363. package/scripts/search_cli.py +627 -0
  364. package/scripts/search_entry.py +389 -0
  365. package/scripts/search_output.py +606 -0
  366. package/scripts/search_pipeline.py +1077 -0
  367. package/scripts/search_rank.py +991 -0
  368. package/scripts/search_types.py +27 -12
  369. package/scripts/seek_locator.py +68 -0
  370. package/scripts/semantic_evidence.py +215 -0
  371. package/scripts/serp_guard.py +228 -0
  372. package/scripts/serp_kill_log.py +51 -0
  373. package/scripts/single_flight.py +120 -0
  374. package/scripts/social_engines/__init__.py +10 -0
  375. package/scripts/social_engines/bilibili_engine.py +131 -0
  376. package/scripts/social_engines/hackernews_engine.py +113 -0
  377. package/scripts/social_engines/reddit_engine.py +172 -0
  378. package/scripts/social_engines/twitter_engine.py +397 -0
  379. package/scripts/social_engines/v2ex_engine.py +101 -0
  380. package/scripts/social_engines/weibo_engine.py +128 -0
  381. package/scripts/social_engines/xiaohongshu_engine.py +137 -0
  382. package/scripts/social_engines/zhihu_engine.py +139 -0
  383. package/scripts/social_research.py +201 -0
  384. package/scripts/sogou_weixin_browser.py +318 -0
  385. package/scripts/source_ledger.py +249 -0
  386. package/scripts/stage_timing.py +58 -0
  387. package/scripts/stats_cli.py +95 -0
  388. package/scripts/straggler_policy.py +60 -0
  389. package/scripts/stylometry_detector.py +231 -0
  390. package/scripts/sync_backends.py +353 -0
  391. package/scripts/sync_installs.py +29 -0
  392. package/scripts/template_cli.py +127 -0
  393. package/scripts/text_lexicon.py +72 -0
  394. package/scripts/tfidf_router.py +109 -37
  395. package/scripts/time_utils.py +233 -0
  396. package/scripts/topic_research_profiles.py +643 -0
  397. package/scripts/train.py +737 -0
  398. package/scripts/tweet.py +596 -0
  399. package/scripts/url_canon.py +217 -0
  400. package/scripts/url_optimize.py +56 -0
  401. package/scripts/url_safety.py +236 -0
  402. package/scripts/usage_log.py +116 -0
  403. package/scripts/usage_log_clean.py +113 -0
  404. package/scripts/v2ex_nodes.py +311 -0
  405. package/scripts/variant_recall.py +188 -0
  406. package/scripts/watch.py +205 -0
  407. package/scripts/wechat_article.py +258 -0
  408. package/scripts/wide_research.py +402 -0
  409. package/scripts/wx.py +349 -0
  410. package/scripts/x_article_fetch.py +141 -0
  411. package/scripts/x_search.py +234 -0
  412. package/scripts/x_trending.py +189 -0
  413. package/scripts/yaml_load.py +67 -0
  414. package/sub-skills/ego-search/SKILL.md +253 -0
  415. package/sub-skills/ego-search/references/browser-runtime.md +43 -0
  416. package/sub-skills/ego-search/references/install.md +67 -0
  417. package/sub-skills/ego-search/references/original-ego-upgrade.md +51 -0
  418. package/sub-skills/ego-search/scripts/ego_search.py +800 -0
  419. package/sub-skills/ego-search/scripts/merge.py +226 -0
  420. package/sub-skills/ego-search/scripts/quality.py +64 -0
  421. package/sub-skills/ego-search/scripts/runtime.py +172 -0
  422. package/sub-skills/ego-search/scripts/safety.py +109 -0
  423. package/sub-skills/ego-search/scripts/serp_spec.py +109 -0
  424. package/sub-skills/ego-search/scripts/webbridge_adapter.py +374 -0
  425. package/sub-skills/ego-search/tests/test_query_normalize.py +68 -0
  426. package/sub-skills/js-run/SKILL.md +67 -0
  427. package/sub-skills/js-run/scripts/jsrun.py +112 -0
  428. package/sub-skills/js-run/scripts/shim.js +188 -0
  429. package/sub-skills/js-run/tests/fixtures/challenge_like.js +27 -0
  430. package/sub-skills/js-run/tests/test_jsrun.py +165 -0
  431. package/sub-skills/local-search/SKILL.md +50 -53
  432. package/sub-skills/local-search/bm25.py +162 -0
  433. package/sub-skills/local-search/config.yaml +152 -55
  434. package/sub-skills/local-search/engine_registry.py +91 -26
  435. package/sub-skills/local-search/{health_check.py → local_health_check.py} +140 -10
  436. package/sub-skills/local-search/local_search_adapter.py +13 -2
  437. package/sub-skills/local-search/parse_maps.yaml +40 -42
  438. package/sub-skills/local-search/search_v3.py +1182 -65
  439. package/sub-skills/local-search/smart_router.py +47 -7
  440. package/sub-skills/local-seek/SKILL.md +110 -0
  441. package/sub-skills/local-seek/config/domains.yaml +20 -0
  442. package/sub-skills/local-seek/references/auto-behaviors.md +24 -0
  443. package/sub-skills/local-seek/references/fd-mdfind-recipes.md +62 -0
  444. package/sub-skills/local-seek/references/git-integration.md +54 -0
  445. package/sub-skills/local-seek/references/remote-handoff.md +46 -0
  446. package/sub-skills/local-seek/references/rg-recipes.md +96 -0
  447. package/sub-skills/local-seek/references/strategies.md +52 -0
  448. package/sub-skills/local-seek/references/structural-search.md +59 -0
  449. package/sub-skills/local-seek/scripts/eval_rank.py +216 -0
  450. package/sub-skills/local-seek/scripts/eval_seek.py +224 -0
  451. package/sub-skills/local-seek/scripts/seek.py +1465 -0
  452. package/sub-skills/local-seek/scripts/seek_frecency.py +126 -0
  453. package/sub-skills/local-seek/scripts/seek_fts.py +430 -0
  454. package/sub-skills/local-seek/scripts/seek_rank.py +193 -0
  455. package/sub-skills/local-seek/tests/test_content_rank.py +222 -0
  456. package/sub-skills/local-seek/tests/test_determinism_20261006.py +145 -0
  457. package/sub-skills/local-seek/tests/test_noise_tier.py +101 -0
  458. package/sub-skills/local-seek/tests/test_pcre2_probe.py +136 -0
  459. package/sub-skills/local-seek/tests/test_rank_pinyin.py +77 -0
  460. package/sub-skills/local-seek/tests/test_regression_20261003.py +169 -0
  461. package/sub-skills/local-seek/tests/test_regression_20261006.py +146 -0
  462. package/sub-skills/local-seek/tests/test_seek_degradation.py +111 -0
  463. package/sub-skills/local-seek/tests/test_seek_fts.py +313 -0
@@ -1,437 +1,175 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- health_check.py — 引擎健康检查
4
-
5
- 职责:
6
- - 遍历三层引擎注册表所有引擎
7
- - 标记可用/降级/不可用
8
- - 按层级筛选可用引擎
9
- - 输出健康报告供路由决策使用
10
-
11
- 调用方式:
12
- - 内部:from health_check import health_check_all, get_available_engines_by_tier
13
- - CLI:python3 health_check.py [--tier T1] [--json]
3
+ health_check.py — 引擎健康检查(v2.1 新增,替代 health_probe.py)
4
+
5
+ 升级点:
6
+ - 反爬检测(14 种 marker + 内容长度检查)
7
+ - 解析成功验证(CSS container / JSON valid / XML valid)
8
+ - 惰性健康检查(只检查实际需要的引擎)
9
+ - 健康状态持久化(JSON + 5min TTL)
10
+ - 连续失败阈值(≥2 次 → unavailable,1 次成功恢复)
11
+
12
+ 用法:
13
+ python3 health_check.py --engine local_bing # 检查单个引擎
14
+ python3 health_check.py --category chinese # 检查整个分类
15
+ python3 health_check.py --all # 全量检查
14
16
  """
15
17
 
16
18
  from __future__ import annotations
17
19
 
18
- import json
20
+ import logging
19
21
  import os
20
22
  import sys
21
23
  import time
22
24
  from pathlib import Path
23
25
  from typing import Any
24
26
 
25
- # 添加 scripts 目录到 import 路径
26
- SCRIPT_DIR = Path(__file__).resolve().parent.parent / "scripts"
27
+ SCRIPT_DIR = Path(__file__).parent
27
28
  sys.path.insert(0, str(SCRIPT_DIR))
28
29
 
29
- try:
30
- import yaml
31
- except ImportError:
32
- yaml = None # type: ignore
33
-
34
-
35
- REGISTRY_PATH = Path(__file__).resolve().parent.parent / "backends" / "engine_registry.yaml"
36
-
37
- # 本地搜索模块路径(优雅降级,缺失时不阻断)
38
- # 默认路径为项目内 local-search/ 子目录,可通过环境变量 LOCAL_SEARCH_PATH 覆盖
39
- _LOCAL_SEARCH_V3_PATH = Path(os.environ.get(
40
- "LOCAL_SEARCH_PATH",
41
- str(Path(__file__).resolve().parent.parent / "local-search" / "search_v3.py")
42
- ))
43
- _SEARXNG_BRIDGE_PATH = Path(os.environ.get(
44
- "SEARXNG_BRIDGE_PATH",
45
- str(Path(__file__).resolve().parent.parent / "local-search" / "searxng_bridge.py")
46
- ))
47
-
48
-
49
- def _load_registry() -> list[dict[str, Any]]:
50
- """加载引擎注册表 YAML。"""
51
- if not REGISTRY_PATH.exists():
52
- return []
53
-
54
- if yaml is None:
55
- # 无 PyYAML 时尝试简易解析(仅处理基本结构)
56
- return _parse_yaml_light()
57
-
58
- try:
59
- with open(REGISTRY_PATH, "r", encoding="utf-8") as f:
60
- data = yaml.safe_load(f)
61
- except Exception:
62
- return []
63
-
64
- if not isinstance(data, dict):
65
- return []
66
-
67
- return data.get("engines", [])
68
-
69
-
70
- def _parse_yaml_light() -> list[dict[str, Any]]:
71
- """简易 YAML 解析器回退 — 仅提取 engines 列表。"""
72
- try:
73
- text = REGISTRY_PATH.read_text(encoding="utf-8")
74
- # 查找 engines: 段落后面的条目
75
- # 这是一个非常基础的解析,PyYAML 不可用时才使用
76
- return []
77
- except Exception:
78
- return []
79
-
80
-
81
- def _check_t1_engine(name: str, timeout: float = 5) -> dict[str, Any]:
82
- """检查 T1 引擎健康状态。
83
-
84
- 通过 engines.py 的 search() 函数快速探测。
85
- """
86
- result = {
87
- "engine": name,
88
- "tier": "T1",
89
- "available": False,
90
- "latency_ms": 0,
91
- "error": None,
92
- "status": "unavailable",
93
- }
94
-
30
+ from argo_engine_registry import get_registry
31
+ from net_proxy import open_url # 出口调度唯一入口(issue #13 同类修复)
32
+ from cli_io import dumps
33
+
34
+ logger = logging.getLogger("argo.health_check")
35
+ if not logger.handlers:
36
+ logger.setLevel(logging.WARNING)
37
+ logger.addHandler(logging.StreamHandler())
38
+
39
+ PROBE_TIMEOUT = 5.0
40
+ MAX_FAILURES = 2
41
+ HEALTH_TTL = 300 # 5 分钟
42
+
43
+ # 反爬/拦截标记
44
+ ANTI_BOT_MARKERS = [
45
+ "captcha", "recaptcha", "robot", "robots", "cloudflare", "challenge",
46
+ "blocked", "verification", "please verify", "access denied",
47
+ "too many requests", "rate limit", "enable javascript", "checking your browser",
48
+ "ddos-guard", "perimeterx", "arkose",
49
+ ]
50
+
51
+
52
+ def _detect_anti_bot(text: str) -> bool:
53
+ """检测反爬/拦截页面。"""
54
+ if not text or len(text.strip()) < 100:
55
+ return True
56
+ text_lower = text.lower()
57
+ return any(marker in text_lower for marker in ANTI_BOT_MARKERS)
58
+
59
+
60
+ def check_http_engine(name: str, url: str, spec: dict) -> dict:
61
+ """检查 HTTP/HTML 引擎健康状态。"""
62
+ # 仅真探测需要 urllib;route 热路径的 is_available 只读注册表,
63
+ # 不该为模块级这行 import 每次多付 ~26ms(urllib+http.client 链)。
64
+ import urllib.request
65
+ result = {"available": False, "latency_ms": 0, "error": None}
95
66
  try:
96
- from engines import search, get_registry
97
- registry = get_registry()
98
- if name not in registry:
99
- result["error"] = f"引擎 {name} 未在 engines.py 注册表中"
100
- result["status"] = "unavailable"
101
- return result
102
-
103
- fn = registry[name]
67
+ req = urllib.request.Request(url, headers={
68
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
69
+ })
104
70
  t0 = time.time()
105
- # 快速探测:只请求 1 条结果
106
- try:
107
- results = fn("test", n=1, _timeout=timeout)
108
- except TypeError:
109
- results = fn("test", n=1)
110
-
111
- elapsed = round((time.time() - t0) * 1000)
112
- result["latency_ms"] = elapsed
113
-
114
- if results and isinstance(results, list) and len(results) > 0:
71
+ with open_url(req, timeout=PROBE_TIMEOUT) as resp:
72
+ content = resp.read().decode("utf-8", errors="replace")
73
+ result["latency_ms"] = round((time.time() - t0) * 1000, 1)
74
+ if _detect_anti_bot(content):
75
+ result["error"] = "anti_bot_detected"
76
+ return result
77
+ # 解析成功验证:检查 CSS container 是否存在
78
+ container = spec.get("parse_container")
79
+ if container and content:
80
+ try:
81
+ from bs4 import BeautifulSoup
82
+ soup = BeautifulSoup(content, "html.parser")
83
+ if not soup.select(container):
84
+ result["error"] = "parse_container_not_found"
85
+ return result
86
+ except Exception:
87
+ pass
115
88
  result["available"] = True
116
- result["status"] = "ok"
117
- result["sample"] = results[0]
118
- else:
119
- result["error"] = "返回空结果"
120
- result["status"] = "degraded"
121
-
122
- except ImportError:
123
- result["error"] = "engines.py 不可导入"
124
- result["status"] = "unavailable"
125
- except Exception as e:
126
- result["error"] = f"{type(e).__name__}: {e}"
127
- result["status"] = "unavailable"
128
-
129
- return result
130
-
131
-
132
- def _check_t2_engine(name: str, timeout: float = 5) -> dict[str, Any]:
133
- """检查 T2 引擎健康状态。
134
-
135
- 通过 search_v3.check_engine_health() 探测。
136
- 引擎名去掉 local/ 前缀后传给 check_engine_health。
137
- """
138
- local_name = name.replace("local/", "") if name.startswith("local/") else name
139
-
140
- result = {
141
- "engine": name,
142
- "tier": "T2",
143
- "available": False,
144
- "latency_ms": 0,
145
- "error": None,
146
- "status": "unavailable",
147
- }
148
-
149
- try:
150
- sys.path.insert(0, str(_LOCAL_SEARCH_V3_PATH.parent))
151
- from search_v3 import check_engine_health
152
- health = check_engine_health(local_name, timeout=timeout)
153
- result["available"] = health.get("available", False)
154
- result["latency_ms"] = health.get("latency_ms", 0)
155
- result["error"] = health.get("error")
156
- result["status"] = health.get("status", "unavailable")
157
- if health.get("sample"):
158
- result["sample"] = health["sample"]
159
- except ImportError:
160
- result["error"] = "search_v3.py 不可导入"
161
- result["status"] = "unavailable"
162
89
  except Exception as e:
163
- result["error"] = f"{type(e).__name__}: {e}"
164
- result["status"] = "unavailable"
165
-
90
+ result["error"] = str(e)[:200]
166
91
  return result
167
92
 
168
93
 
169
- def _check_t3_engine(name: str, timeout: float = 5) -> dict[str, Any]:
170
- """检查 T3 引擎健康状态。
171
-
172
- 通过 searxng_bridge 检查 SearXNG 是否可达,然后检查引擎是否在列表中。
173
- """
174
- result = {
175
- "engine": name,
176
- "tier": "T3",
177
- "available": False,
178
- "latency_ms": 0,
179
- "error": None,
180
- "status": "unavailable",
181
- }
182
-
183
- try:
184
- sys.path.insert(0, str(_SEARXNG_BRIDGE_PATH.parent))
185
- from searxng_bridge import fetch_searxng_engine_list
186
-
187
- t0 = time.time()
188
- engine_data = fetch_searxng_engine_list(timeout=timeout)
189
- elapsed = round((time.time() - t0) * 1000)
190
- result["latency_ms"] = elapsed
191
-
192
- if engine_data.get("error"):
193
- result["error"] = engine_data["error"]
194
- result["status"] = "unavailable"
195
- return result
196
-
197
- # 检查指定引擎是否在垂直可用列表中
198
- searxng_engine_name = name.replace("searxng/", "")
199
- available_verticals = engine_data.get("available_vertical", [])
200
- available_names = {e.get("name", "").lower() for e in available_verticals}
201
-
202
- if searxng_engine_name.lower() in available_names or \
203
- searxng_engine_name.lower().replace("_", " ") in available_names:
204
- result["available"] = True
205
- result["status"] = "ok"
206
- else:
207
- # 也检查是否在 category_map 中
208
- cmap = engine_data.get("category_map", {})
209
- found = False
210
- for cat_engines in cmap.values():
211
- if searxng_engine_name.lower() in [e.lower() for e in cat_engines]:
212
- found = True
213
- break
214
- if found:
215
- result["available"] = True
216
- result["status"] = "ok"
217
- else:
218
- result["error"] = f"引擎 {searxng_engine_name} 不在 SearXNG 可用垂直列表中"
219
- result["status"] = "degraded"
220
-
221
- except ImportError:
222
- result["error"] = "searxng_bridge.py 不可导入"
223
- result["status"] = "unavailable"
224
- except Exception as e:
225
- result["error"] = f"{type(e).__name__}: {e}"
226
- result["status"] = "unavailable"
227
-
228
- return result
229
-
230
-
231
- # 健康检查映射
232
- _CHECKERS = {
233
- "T1": _check_t1_engine,
234
- "T2": _check_t2_engine,
235
- "T3": _check_t3_engine,
236
- }
237
-
238
-
239
- def health_check_all(
240
- timeout: float = 5,
241
- tiers: list[str] | None = None,
242
- fast: bool = False,
243
- ) -> dict[str, Any]:
244
- """遍历注册表所有引擎,标记可用/降级/不可用。
245
-
246
- Args:
247
- timeout: 单引擎探测超时(秒)
248
- tiers: 要检查的层级列表(None = 全部)
249
- fast: 快速模式 — 仅检查推荐引擎
250
-
251
- Returns:
252
- {
253
- "ok": [...],
254
- "degraded": [...],
255
- "unavailable": [...],
256
- "by_tier": {"T1": [...], "T2": [...], "T3": [...]},
257
- "summary": {"total": N, "ok_count": N, "degraded_count": N, ...},
258
- "elapsed_ms": N,
259
- }
260
- """
261
- t0_total = time.time()
262
-
263
- registry = _load_registry()
264
- if not registry:
265
- return {
266
- "ok": [], "degraded": [], "unavailable": [],
267
- "by_tier": {}, "summary": {"total": 0, "ok_count": 0,
268
- "degraded_count": 0, "unavailable_count": 0},
269
- "elapsed_ms": 0, "error": "引擎注册表为空或不可读",
270
- }
271
-
272
- target_tiers = set(tiers) if tiers else {"T1", "T2", "T3"}
273
-
274
- ok_list: list[dict] = []
275
- degraded_list: list[dict] = []
276
- unavailable_list: list[dict] = []
277
- by_tier: dict[str, list[dict]] = {"T1": [], "T2": [], "T3": []}
278
-
279
- for engine in registry:
280
- name = engine.get("name", "")
281
- tier = engine.get("tier", "")
282
- status = engine.get("status", "unknown")
283
-
284
- if tier not in target_tiers:
94
+ def check_engine(name: str) -> dict:
95
+ """检查单个引擎健康状态。"""
96
+ registry = get_registry()
97
+ engines = registry.get_local_search_engines()
98
+ spec = engines.get(name)
99
+ if not spec:
100
+ return {"available": False, "error": "engine_not_found"}
101
+ url = spec.get("url", "")
102
+ if not url:
103
+ return {"available": False, "error": "no_url"}
104
+ return check_http_engine(name, url, spec)
105
+
106
+
107
+ def check_category(category: str) -> dict[str, dict]:
108
+ """检查整个分类的引擎。"""
109
+ registry = get_registry()
110
+ engines = registry.get_local_search_engines()
111
+ results = {}
112
+ for name, spec in engines.items():
113
+ cats = spec.get("categories", [])
114
+ if isinstance(cats, str):
115
+ cats = [cats]
116
+ if category not in cats:
285
117
  continue
286
-
287
- # 快速模式:仅检查推荐引擎 + 状态为 ok 的引擎
288
- if fast:
289
- is_recommended = engine.get("recommended", False)
290
- is_ok = status == "ok"
291
- if not (is_recommended or is_ok):
292
- engine["_checked"] = False
293
- engine["_skip_reason"] = "非推荐引擎(快速模式)"
294
- continue
295
-
296
- # 如果注册表已标记为 disabled,直接跳过
297
- if status == "disabled":
298
- engine["_checked"] = False
299
- engine["_skip_reason"] = "已禁用"
118
+ url = spec.get("url", "")
119
+ if url:
120
+ result = check_http_engine(name, url, spec)
121
+ results[name] = result
122
+ registry.update_health(name, result["available"], **result)
123
+ return results
124
+
125
+
126
+ def check_all() -> dict[str, dict]:
127
+ """全量检查所有 local-search 子引擎。"""
128
+ registry = get_registry()
129
+ engines = registry.get_local_search_engines()
130
+ results = {}
131
+ for name, spec in engines.items():
132
+ if not spec.get("enabled", True):
300
133
  continue
301
-
302
- # 对于 degraded 状态的引擎(如 Baidu),不做探测直接使用注册表状态
303
- if status == "degraded":
304
- check_result = {
305
- "engine": name,
306
- "tier": tier,
307
- "available": False,
308
- "latency_ms": engine.get("latency_ms", 0),
309
- "error": engine.get("note", "已降级"),
310
- "status": "degraded",
311
- "_checked": True,
312
- "_from_registry": True,
313
- }
314
- degraded_list.append(check_result)
315
- by_tier.setdefault(tier, []).append(check_result)
134
+ url = spec.get("url", "")
135
+ if not url:
316
136
  continue
137
+ result = check_http_engine(name, url, spec)
138
+ results[name] = result
139
+ registry.update_health(name, result.pop("available"), **result)
140
+ return results
317
141
 
318
- # 执行健康检查
319
- checker = _CHECKERS.get(tier)
320
- if checker:
321
- check_result = checker(name, timeout=timeout)
322
- check_result["_checked"] = True
323
- else:
324
- check_result = {
325
- "engine": name,
326
- "tier": tier,
327
- "available": False,
328
- "latency_ms": 0,
329
- "error": f"未知层级: {tier}",
330
- "status": "unavailable",
331
- "_checked": True,
332
- }
333
-
334
- # 分类
335
- st = check_result.get("status", "unavailable")
336
- if st == "ok":
337
- ok_list.append(check_result)
338
- elif st in ("degraded", "slow"):
339
- degraded_list.append(check_result)
340
- else:
341
- unavailable_list.append(check_result)
342
-
343
- by_tier.setdefault(tier, []).append(check_result)
344
-
345
- elapsed = round((time.time() - t0_total) * 1000)
346
-
347
- return {
348
- "ok": ok_list,
349
- "degraded": degraded_list,
350
- "unavailable": unavailable_list,
351
- "by_tier": by_tier,
352
- "summary": {
353
- "total": len(ok_list) + len(degraded_list) + len(unavailable_list),
354
- "ok_count": len(ok_list),
355
- "degraded_count": len(degraded_list),
356
- "unavailable_count": len(unavailable_list),
357
- },
358
- "elapsed_ms": elapsed,
359
- }
360
142
 
143
+ def is_available(name: str) -> bool:
144
+ """查询引擎是否可用(带 TTL 缓存)。"""
145
+ registry = get_registry()
146
+ return registry.is_available(name)
361
147
 
362
- def get_available_engines_by_tier(
363
- tier: str,
364
- include_degraded: bool = False,
365
- ) -> list[str]:
366
- """按层级筛选可用引擎名称列表。
367
148
 
368
- Args:
369
- tier: 层级名("T1" / "T2" / "T3")
370
- include_degraded: 是否包含降级但仍可用的引擎
371
-
372
- Returns:
373
- 引擎名称列表(仅为 ok 状态,若 include_degraded 则包含 degraded)
374
- """
375
- registry = _load_registry()
376
- names: list[str] = []
377
-
378
- for engine in registry:
379
- if engine.get("tier") != tier:
380
- continue
381
- status = engine.get("status", "unknown")
382
- if status == "ok":
383
- names.append(engine.get("name", ""))
384
- elif include_degraded and status in ("degraded", "slow"):
385
- names.append(engine.get("name", ""))
386
-
387
- return names
388
-
389
-
390
- # ── CLI 调试用 ─────────────────────────────────────────────────────────────────
391
- def _cli():
149
+ def main():
392
150
  import argparse
393
-
394
- parser = argparse.ArgumentParser(description="引擎健康检查")
395
- parser.add_argument("--tier", "-t", choices=["T1", "T2", "T3"],
396
- help="仅检查指定层级")
397
- parser.add_argument("--json", action="store_true", help="JSON 格式输出")
398
- parser.add_argument("--fast", action="store_true", help="快速模式(仅检查推荐引擎)")
399
- parser.add_argument("--list", action="store_true", help="列出注册表所有引擎(不探测)")
151
+ parser = argparse.ArgumentParser(description="Argo 引擎健康检查")
152
+ parser.add_argument("--engine", help="检查单个引擎")
153
+ parser.add_argument("--category", help="检查整个分类")
154
+ parser.add_argument("--all", action="store_true", help="全量检查")
400
155
  args = parser.parse_args()
401
156
 
402
- if args.list:
403
- registry = _load_registry()
404
- if args.tier:
405
- registry = [e for e in registry if e.get("tier") == args.tier]
406
- for e in registry:
407
- print(f"[{e.get('tier', '?')}] {e.get('name', '?')} "
408
- f"({e.get('status', '?')}) — {e.get('desc', '')}")
409
- return
410
-
411
- tiers = [args.tier] if args.tier else None
412
- result = health_check_all(timeout=5, tiers=tiers, fast=args.fast)
413
-
414
- if args.json:
415
- print(json.dumps(result, ensure_ascii=False, indent=2))
157
+ if args.engine:
158
+ result = check_engine(args.engine)
159
+ print(dumps({args.engine: result}))
160
+ elif args.category:
161
+ results = check_category(args.category)
162
+ print(dumps(results))
163
+ elif args.all:
164
+ results = check_all()
165
+ available = sum(1 for r in results.values() if r.get("available"))
166
+ print(f"检查完成: {available}/{len(results)} 可用")
167
+ for name, r in results.items():
168
+ status = "✅" if r.get("available") else "❌"
169
+ print(f" {status} {name}: {r.get('latency_ms', 0)}ms {r.get('error', '')}")
416
170
  else:
417
- print(f"健康检查完成 — 耗时 {result['elapsed_ms']}ms")
418
- summary = result["summary"]
419
- print(f" 可用: {summary['ok_count']} 降级: {summary['degraded_count']} "
420
- f"不可用: {summary['unavailable_count']} 总计: {summary['total']}")
421
- print()
422
- if result["ok"]:
423
- print("✅ 可用:")
424
- for e in result["ok"]:
425
- print(f" [{e['tier']}] {e['engine']} ({e['latency_ms']}ms)")
426
- if result["degraded"]:
427
- print("⚠️ 降级:")
428
- for e in result["degraded"]:
429
- print(f" [{e['tier']}] {e['engine']} — {e.get('error', '')}")
430
- if result["unavailable"]:
431
- print("❌ 不可用:")
432
- for e in result["unavailable"]:
433
- print(f" [{e['tier']}] {e['engine']} — {e.get('error', '')}")
171
+ parser.print_help()
434
172
 
435
173
 
436
174
  if __name__ == "__main__":
437
- _cli()
175
+ main()
@@ -14,7 +14,6 @@ health_probe.py — 引擎健康探针(v2.5 新增)
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- import json
18
17
  import os
19
18
  import sqlite3
20
19
  import sys
@@ -23,6 +22,8 @@ import time
23
22
  import urllib.request
24
23
  from pathlib import Path
25
24
  from typing import Any
25
+ from net_proxy import open_url # 出口调度唯一入口(issue #13 同类修复)
26
+ from cli_io import dumps
26
27
 
27
28
  SCRIPT_DIR = Path(__file__).parent
28
29
  sys.path.insert(0, str(SCRIPT_DIR))
@@ -35,7 +36,13 @@ except ImportError:
35
36
 
36
37
  # ── 路径 ──────────────────────────────────────────────────────────────────────
37
38
 
38
- DB_PATH = Path.home() / ".cache" / "unified-search" / "health.db"
39
+ def _db_path() -> Path:
40
+ """状态库路径(惰性派生,支持 ARGO_STATE_DIR 覆盖)。"""
41
+ import argo_paths
42
+ return argo_paths.state_path("health.db")
43
+
44
+
45
+ DB_PATH = _db_path() # 兼容旧引用
39
46
  PROBE_INTERVAL = 300 # 5 分钟
40
47
  PROBE_TIMEOUT = 1.5 # 单次探测超时(快,不能拖慢启动)
41
48
  MAX_FAILURES = 2 # 连续失败次数阈值
@@ -46,7 +53,8 @@ MAX_FAILURES = 2 # 连续失败次数阈值
46
53
  def _connect():
47
54
  DB_PATH.parent.mkdir(parents=True, exist_ok=True)
48
55
  conn = sqlite3.connect(str(DB_PATH), timeout=5)
49
- conn.execute("PRAGMA journal_mode=WAL")
56
+ import argo_paths
57
+ argo_paths.apply_state_pragmas(conn)
50
58
  return conn
51
59
 
52
60
 
@@ -69,7 +77,7 @@ def _probe_http(url: str, timeout: float = PROBE_TIMEOUT) -> tuple[bool, float,
69
77
  try:
70
78
  req = urllib.request.Request(url, method="HEAD")
71
79
  t0 = time.time()
72
- with urllib.request.urlopen(req, timeout=timeout) as resp:
80
+ with open_url(req, timeout=timeout) as resp:
73
81
  elapsed = (time.time() - t0) * 1000
74
82
  return True, elapsed, ""
75
83
  except Exception as e:
@@ -85,8 +93,8 @@ def _probe_cli(cmd: list[str], timeout: float = 2) -> tuple[bool, float, str]:
85
93
  if isinstance(exe, str) and exe.startswith("~"):
86
94
  exe = str(Path(exe).expanduser())
87
95
  try:
88
- result = subprocess.run(["which", exe], capture_output=True, text=True, timeout=timeout)
89
- if result.returncode == 0:
96
+ import shutil
97
+ if shutil.which(exe):
90
98
  return True, 0, ""
91
99
  return False, 0, f"command not found: {exe}"
92
100
  except Exception as e:
@@ -94,13 +102,20 @@ def _probe_cli(cmd: list[str], timeout: float = 2) -> tuple[bool, float, str]:
94
102
 
95
103
 
96
104
  def probe_all_engines():
97
- """探测所有已启用的引擎。"""
105
+ """探测 local_* 子引擎(route 健康过滤的唯一消费面)。
106
+
107
+ 非 local 引擎不做 HEAD 探测:1.5s 超时对慢源误报严重(wikipedia 曾因
108
+ 9 次探测失败被标 unavailable),且探测结果无人消费。网络延迟估计
109
+ 以 adaptive 数据为主,health_probe 仅保底。
110
+ """
98
111
  _init_db()
99
112
  cfg = load_config()
100
113
  engines = get_engines(cfg)
101
114
  results = {}
102
115
 
103
116
  for name, spec in engines.items():
117
+ if not name.startswith("local_"):
118
+ continue
104
119
  engine_type = spec.get("type", "")
105
120
  url = spec.get("url", "")
106
121
  cmd = spec.get("cmd", [])
@@ -215,4 +230,4 @@ if __name__ == "__main__":
215
230
  pass
216
231
  else:
217
232
  results = probe_all_engines()
218
- print(json.dumps(results, ensure_ascii=False, indent=2))
233
+ print(dumps(results))