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
@@ -0,0 +1,440 @@
1
+ #!/usr/bin/env python3
2
+ """专用构建器:通用搜索 API(parallel.ai / you.com)——按官方文档保持一致
3
+
4
+ parallel(docs.parallel.ai/search/search-quickstart):
5
+ - objective(自然语言目标)+ search_queries(数组)为官方推荐组合
6
+ - mode: turbo(~200ms, $1/千次, 仅英日文) / basic(~1s, $5/千次) / advanced(~3s, $5/千次)
7
+ argo 默认 basic,deep 模式 advanced
8
+ - advanced_settings: excerpt_settings.max_chars_per_result 与 argo snippet 截断保持一致
9
+
10
+ you.com(docs.you.com):
11
+ - 官方环境变量名 YDC_API_KEY
12
+ - freshness(day/week/month/year)按查询时效敏感度动态化(同 bocha 逻辑)
13
+ - language 按查询主语言下推(zh/en)
14
+ - page_age 为 ISO 8601 时间戳 → 提取为 published_at
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import os
21
+ import re
22
+ import sys
23
+ import urllib.request
24
+ from typing import Any
25
+
26
+ from engines_base import safe_search, http_open, mcp_error_of as _mcp_error_of
27
+ from engine_env import get_env
28
+
29
+
30
+ class _LazyLogger:
31
+ """延迟创建 logger,避免 import logging 的重量级 import 链(traceback→dataclasses→inspect)。"""
32
+ _logger = None
33
+
34
+ def __getattr__(self, name: str):
35
+ if _LazyLogger._logger is None:
36
+ import logging
37
+ _LazyLogger._logger = logging.getLogger("unified_search.engines")
38
+ if not _LazyLogger._logger.handlers:
39
+ _LazyLogger._logger.setLevel(logging.WARNING)
40
+ _LazyLogger._logger.addHandler(logging.StreamHandler(sys.stderr))
41
+ return getattr(_LazyLogger._logger, name)
42
+
43
+
44
+ logger = _LazyLogger()
45
+
46
+ # ── Parallel 搜索(api.parallel.ai)────────────────────────────────────────────
47
+
48
+ _PARALLEL_URL = "https://api.parallel.ai/v1/search"
49
+
50
+
51
+ def _parallel_key() -> str:
52
+ # get_env 候选链(2026-09-29,issue #12 同类):文档推 ARGO_ 前缀名、
53
+ # 老集成留裸名,两套都必须认。此前的裸 os.environ.get 只认裸名——
54
+ # 用户按文档配好 ARGO_PARALLEL_API_KEY 后状态层报 ready、执行层取到
55
+ # 空串,恒 0 结果且失败被 L2 负缓存回放(见 cache_guard 失败态守卫)。
56
+ return get_env(["ARGO_PARALLEL_API_KEY", "PARALLEL_API_KEY"])
57
+
58
+
59
+ def _parallel_mode(query: str, depth: str) -> str:
60
+ """mode 选择:deep → advanced;中文查询不可用 turbo(官方仅英日文)→ basic;英文 fast/auto → basic。"""
61
+ if depth == "deep":
62
+ return "advanced"
63
+ return "basic"
64
+
65
+
66
+ def _build_parallel_engine(spec: dict[str, Any]) -> Any:
67
+ """Parallel Search:objective + search_queries + mode(官方推荐组合)。"""
68
+ timeout = spec.get("timeout", 15)
69
+
70
+ @safe_search
71
+ def _engine(query: str, n: int = 5, _timeout: float | None = None,
72
+ depth: str = "fast", **kwargs) -> list[dict[str, Any]]:
73
+ to = _timeout or timeout
74
+ key = _parallel_key()
75
+ if not key:
76
+ return [{"error": "PARALLEL_API_KEY 未设置", "source": "parallel"}]
77
+ limit = max(1, int(n or 5))
78
+ # 多路召回:原查询 + 无 LLM 变体(问句化/概念扩展等),官方最佳实践 2-3 个
79
+ try:
80
+ from query_variants import generate_query_variations
81
+ queries = generate_query_variations(query)[:3] or [query]
82
+ except Exception:
83
+ queries = [query]
84
+ body = {
85
+ "objective": f"Find the latest, most relevant information about: {query}",
86
+ "search_queries": queries,
87
+ "mode": _parallel_mode(query, depth),
88
+ "advanced_settings": {
89
+ "max_results": min(limit, 20),
90
+ "excerpt_settings": {"max_chars_per_result": 300},
91
+ },
92
+ }
93
+ # argo 时间窗 --since → after_date(官方 --after-date)
94
+ since = kwargs.get("since")
95
+ if since:
96
+ date_str = str(since)[:10]
97
+ if re.fullmatch(r"\d{4}-\d{2}-\d{2}", date_str):
98
+ body["advanced_settings"]["source_policy"] = {"after_date": date_str}
99
+ req = urllib.request.Request(
100
+ _PARALLEL_URL,
101
+ data=json.dumps(body).encode("utf-8"),
102
+ headers={"X-Api-Key": key, "Content-Type": "application/json"},
103
+ )
104
+ try:
105
+ with http_open(req, timeout=to, engine=spec.get("_name", "")) as resp:
106
+ data = json.loads(resp.read().decode("utf-8"))
107
+ except Exception as e:
108
+ logger.warning(f"parallel 请求失败: {e}")
109
+ return []
110
+ results: list[dict[str, Any]] = []
111
+ for item in (data.get("results") or [])[:limit]:
112
+ title = str(item.get("title") or "")[:200]
113
+ url = str(item.get("url") or "")
114
+ if not (title or url):
115
+ continue
116
+ excerpts = item.get("excerpts") or []
117
+ snippet = str(excerpts[0])[:300] if excerpts else ""
118
+ results.append({
119
+ "title": title,
120
+ "url": url,
121
+ "snippet": snippet,
122
+ "source": "parallel",
123
+ "published_at": str(item.get("publish_date") or "")[:64],
124
+ })
125
+ return results
126
+ return _engine
127
+
128
+
129
+ # ── Parallel 免费通道(search.parallel.ai MCP,keyless)───────────────────────
130
+
131
+ _PARALLEL_FREE_MCP_URL = "https://search.parallel.ai/mcp"
132
+
133
+
134
+ def _build_parallel_free_engine(spec: dict[str, Any]) -> Any:
135
+ """Parallel 免费搜索:官方免费 MCP 端点的 web_search 工具,恒走免 key 通道。
136
+
137
+ 与 parallel(REST + PARALLEL_API_KEY,按量计费)同上游、不同经济模型,
138
+ 故注册为独立引擎而非共用 cost_tier——「计费源不当免费」检查的语义
139
+ 对两条通道各自成立。本引擎**不看 key**:key 的有无/有效与否是 parallel
140
+ 的事;免费端点不收 key、按 key 计费,带了反而可能混淆计费归属。
141
+ 路由上以 daily_support 低优先级做补位:每家族 max_per_family=2 挡住
142
+ 与 parallel 的日常同查冗余,计费通道缺位(额度耗尽/失败/未配 key)时
143
+ 由本通道接住。
144
+ 2026-09-14 实测:无状态直调成立(免 initialize/session),响应
145
+ content[0].text 内嵌 JSON,字段与 REST 响应同构(url/title/publish_date/
146
+ excerpts);响应 _meta.parallel.usage 自带上游计量(sku_search,免费
147
+ 通道 cost_usd 仅记账)。
148
+ """
149
+ timeout = spec.get("timeout", 15)
150
+
151
+ @safe_search
152
+ def _engine(query: str, n: int = 5, _timeout: float | None = None,
153
+ **kwargs) -> list[dict[str, Any]]:
154
+ to = _timeout or timeout
155
+ limit = max(1, int(n or 5))
156
+ # 多路召回:同 REST 通道,官方最佳实践 2-3 个关键词探针
157
+ try:
158
+ from query_variants import generate_query_variations
159
+ queries = generate_query_variations(query)[:3] or [query]
160
+ except Exception:
161
+ queries = [query]
162
+ body = {
163
+ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
164
+ "params": {"name": "web_search", "arguments": {
165
+ "objective": f"Find the latest, most relevant information about: {query}",
166
+ "search_queries": queries,
167
+ }},
168
+ }
169
+ try:
170
+ from http_client import HttpClient
171
+ # max_retries=0:引擎内不做连接级重试(同 anysearch,防与调度层重试叠乘)
172
+ client = HttpClient(timeout=to, max_retries=0, jitter=False)
173
+ resp = client.post(_PARALLEL_FREE_MCP_URL, body=body,
174
+ extra_headers={"Content-Type": "application/json"})
175
+ except ImportError:
176
+ return []
177
+ if resp.get("status", 0) >= 400 or not resp.get("text"):
178
+ return []
179
+ try:
180
+ data = json.loads(resp["text"])
181
+ except (ValueError, TypeError):
182
+ return []
183
+ mcp_err = _mcp_error_of(data, source="parallel_free")
184
+ if mcp_err:
185
+ logger.warning(f"parallel_free 上游错误: {mcp_err}")
186
+ return [{"error": mcp_err, "source": "parallel_free"}]
187
+ inner: dict[str, Any] = {}
188
+ for block in (data.get("result", {}).get("content") or []):
189
+ text = block.get("text", "") if isinstance(block, dict) else str(block)
190
+ try:
191
+ parsed = json.loads(text)
192
+ except (ValueError, TypeError):
193
+ continue
194
+ if isinstance(parsed, dict) and parsed.get("results"):
195
+ inner = parsed
196
+ break
197
+ results: list[dict[str, Any]] = []
198
+ for item in (inner.get("results") or [])[:limit]:
199
+ title = str(item.get("title") or "")[:200]
200
+ url = str(item.get("url") or "")
201
+ if not (title or url):
202
+ continue
203
+ excerpts = item.get("excerpts") or []
204
+ snippet = str(excerpts[0])[:300] if excerpts else ""
205
+ results.append({
206
+ "title": title,
207
+ "url": url,
208
+ "snippet": snippet,
209
+ "source": "parallel_free",
210
+ "published_at": str(item.get("publish_date") or "")[:64],
211
+ })
212
+ return results
213
+ return _engine
214
+
215
+
216
+ # ── Seltz 搜索(api.seltz.ai,2026 新兴 agent 搜索)───────────────────────────
217
+
218
+ _SELTZ_URL = "https://api.seltz.ai/v1/search"
219
+
220
+
221
+ def _seltz_key() -> str:
222
+ # get_env 候选链(2026-09-29,issue #12 同类,同 _parallel_key)
223
+ return get_env(["ARGO_SELTZ_API_KEY", "SELTZ_API_KEY"])
224
+
225
+
226
+ _SELTZ_SCOPES = ("news", "wikipedia", "people", "companies")
227
+
228
+
229
+ def _build_seltz_engine(spec: dict[str, Any]) -> Any:
230
+ """Seltz Search:POST {query, max_results, scope},响应 {documents:[...]}。
231
+
232
+ 官方文档 docs.seltz.ai:x-api-key 头鉴权。响应无 title 字段(content
233
+ 即摘录正文,首行截作标题);无 score/confidence 字段,排序即上游相关度。
234
+ 注册赠 20000 次搜索额度(一次性),故配额按 month/20000 保守看管。
235
+
236
+ **scope 是语料选择,不是可选项**:上游只支持 news / wikipedia / people /
237
+ companies 四个语料,不传一律落 news。2026-09-16 实测:不传 scope 时技术
238
+ 查询(「RRF 融合算法」)拿到的是 news 语料里的时政与播客条目,看上去像
239
+ 「引用跑偏」,实为语料选错而非上游坏了。四个语料实测:companies 查公司
240
+ 概况最好;news 在真新闻查询上可用但覆盖窄;wikipedia 干净;people 不可用
241
+ (10 条全 linkedin.com,人名查不到)。故本引擎按 spec.scope 声明语料,
242
+ config 里把 coverage 收到对应域,不冒充通用源。
243
+ """
244
+ timeout = spec.get("timeout", 15)
245
+ scope = str(spec.get("scope") or "").strip().lower()
246
+ if scope and scope not in _SELTZ_SCOPES:
247
+ logger.warning(f"seltz: 未知 scope {scope!r}(上游会返回 404),本轮不传 scope")
248
+ scope = ""
249
+
250
+ @safe_search
251
+ def _engine(query: str, n: int = 5, _timeout: float | None = None,
252
+ **kwargs) -> list[dict[str, Any]]:
253
+ key = _seltz_key()
254
+ if not key:
255
+ return [{"error": "SELTZ_API_KEY 未设置", "source": "seltz"}]
256
+ to = _timeout or timeout
257
+ limit = max(1, int(n or 5))
258
+ body: dict[str, Any] = {"query": query, "max_results": min(limit, 10)}
259
+ if scope:
260
+ body["scope"] = scope
261
+ req = urllib.request.Request(
262
+ _SELTZ_URL,
263
+ data=json.dumps(body).encode("utf-8"),
264
+ headers={"x-api-key": key, "Content-Type": "application/json"},
265
+ )
266
+ try:
267
+ with http_open(req, timeout=to, engine=spec.get("_name", "")) as resp:
268
+ data = json.loads(resp.read().decode("utf-8"))
269
+ except Exception as e:
270
+ logger.warning(f"seltz 请求失败: {e}")
271
+ return []
272
+ results: list[dict[str, Any]] = []
273
+ for item in (data.get("documents") or [])[:limit]:
274
+ url = str(item.get("url") or "")
275
+ content = str(item.get("content") or "")
276
+ # 官方响应无 title 字段;content 是 markdown,首行截标题时剥掉
277
+ # 井号标题标记(实测冒烟首行形如 "# Building a Rust HTTP/1.1 server...")
278
+ title = str(item.get("title") or "") or content.split("\n")[0].lstrip("# ").strip()[:120]
279
+ if not (title or url):
280
+ continue
281
+ results.append({
282
+ "title": title[:200],
283
+ "url": url,
284
+ "snippet": content[:300],
285
+ "source": "seltz",
286
+ "published_at": str(item.get("published_date") or "")[:64],
287
+ })
288
+ return results
289
+ return _engine
290
+
291
+
292
+ # ── You.com 搜索(ydc-index.io)───────────────────────────────────────────────
293
+ _YOU_URL = "https://ydc-index.io/v1/search"
294
+
295
+
296
+ def _you_key() -> str:
297
+ # get_env 候选链(2026-09-29,issue #12 同类,同 _parallel_key)
298
+ return get_env(["ARGO_YDC_API_KEY", "YDC_API_KEY"])
299
+
300
+
301
+ def _you_freshness(query: str) -> str:
302
+ """freshness 动态化:时效敏感查询(复用缓存层检测)→ day;否则省略(全量)。"""
303
+ try:
304
+ from cache import is_freshness_sensitive_query
305
+ if is_freshness_sensitive_query(query or ""):
306
+ return "day"
307
+ except Exception:
308
+ pass
309
+ return ""
310
+
311
+
312
+ def _you_language(query: str) -> str:
313
+ """language 下推:含中文 → zh,其余 en。"""
314
+ try:
315
+ from lang_detect import detect_language
316
+ lang = detect_language(query or "")
317
+ if lang:
318
+ return {"zh": "zh", "ja": "ja"}.get(lang, "en")
319
+ except Exception:
320
+ pass
321
+ return "zh" if any("\u4e00" <= c <= "\u9fff" for c in (query or "")) else "en"
322
+
323
+
324
+ def _build_you_engine(spec: dict[str, Any]) -> Any:
325
+ """You.com Web Search:web+news 合并,freshness/language 动态化,page_age → published_at。"""
326
+ timeout = spec.get("timeout", 12)
327
+
328
+ @safe_search
329
+ def _engine(query: str, n: int = 5, _timeout: float | None = None, **kwargs) -> list[dict[str, Any]]:
330
+ to = _timeout or timeout
331
+ key = _you_key()
332
+ if not key:
333
+ return [{"error": "YDC_API_KEY 未设置", "source": "you"}]
334
+ body: dict[str, Any] = {"query": query, "count": max(1, int(n or 5))}
335
+ freshness = _you_freshness(query)
336
+ if freshness:
337
+ body["freshness"] = freshness
338
+ body["language"] = _you_language(query)
339
+ req = urllib.request.Request(
340
+ _YOU_URL,
341
+ data=json.dumps(body).encode("utf-8"),
342
+ headers={"X-API-Key": key, "Content-Type": "application/json"},
343
+ )
344
+ try:
345
+ with http_open(req, timeout=to, engine=spec.get("_name", "")) as resp:
346
+ data = json.loads(resp.read().decode("utf-8"))
347
+ except Exception as e:
348
+ logger.warning(f"you 请求失败: {e}")
349
+ return []
350
+ results: list[dict[str, Any]] = []
351
+ for kind in ("news", "web"):
352
+ for item in (data.get("results", {}).get(kind) or [])[: max(1, int(n or 5))]:
353
+ title = str(item.get("title") or "")[:200]
354
+ url = str(item.get("url") or "")
355
+ if not (title or url):
356
+ continue
357
+ results.append({
358
+ "title": title,
359
+ "url": url,
360
+ "snippet": str(item.get("description") or "")[:300],
361
+ "source": f"you_{kind}",
362
+ # page_age 是 ISO 8601 时间戳(如 2026-08-11T16:24:41)→ 取日期
363
+ "published_at": str(item.get("page_age") or "")[:10],
364
+ })
365
+ return results
366
+ return _engine
367
+
368
+
369
+ # ── Qwant(欧盟 GDPR 合规)──────────────────────────────────────────────────
370
+
371
+ def _build_qwant_engine(spec: dict[str, Any]) -> Any:
372
+ """Qwant 搜索(qwant.com/?q=,HTML 解析,欧盟 GDPR 合规)"""
373
+ timeout = spec.get("timeout", 10)
374
+
375
+ @safe_search
376
+ def _engine(query: str, n: int = 5, _timeout: float | None = None, **kwargs) -> list[dict[str, Any]]:
377
+ import urllib.parse as up
378
+ to = _timeout or timeout
379
+ url = "https://www.qwant.com/?q=" + up.quote(query)
380
+ headers = {"User-Agent": "Mozilla/5.0", "Referer": "https://www.qwant.com/"}
381
+ req = urllib.request.Request(url, headers=headers)
382
+ try:
383
+ with http_open(req, timeout=to, engine=spec.get("_name", "")) as resp:
384
+ page = resp.read().decode("utf-8", "replace")
385
+ results = []
386
+ for m in re.finditer(r'<a[^>]*href="(https?://[^"]+)"[^>]*>(.*?)</a>', page, re.S):
387
+ url_m, title_m = m.group(1), re.sub(r'<[^>]+>', '', m.group(2)).strip()
388
+ if not title_m or not url_m or "qwant" in url_m or len(title_m) < 5:
389
+ continue
390
+ results.append({
391
+ "title": title_m[:80],
392
+ "url": url_m,
393
+ "snippet": "",
394
+ "source": "qwant",
395
+ "score": max(1.0 - len(results) * 0.1, 0.1),
396
+ })
397
+ if len(results) >= n:
398
+ break
399
+ return results
400
+ except Exception as e:
401
+ logger.warning(f"Qwant 搜索失败: {e}")
402
+ return []
403
+ return _engine
404
+
405
+
406
+ # ── Ecosia(环保隐私引擎)───────────────────────────────────────────────────
407
+
408
+ def _build_ecosia_engine(spec: dict[str, Any]) -> Any:
409
+ """Ecosia 搜索(ecosia.org/search?q=,HTML 解析,环保隐私引擎)"""
410
+ timeout = spec.get("timeout", 10)
411
+
412
+ @safe_search
413
+ def _engine(query: str, n: int = 5, _timeout: float | None = None, **kwargs) -> list[dict[str, Any]]:
414
+ import urllib.parse as up
415
+ to = _timeout or timeout
416
+ url = "https://www.ecosia.org/search?q=" + up.quote(query)
417
+ headers = {"User-Agent": "Mozilla/5.0", "Referer": "https://www.ecosia.org/"}
418
+ req = urllib.request.Request(url, headers=headers)
419
+ try:
420
+ with http_open(req, timeout=to, engine=spec.get("_name", "")) as resp:
421
+ page = resp.read().decode("utf-8", "replace")
422
+ results = []
423
+ for m in re.finditer(r'<a[^>]*href="(https?://[^"]+)"[^>]*>(.*?)</a>', page, re.S):
424
+ url_m, title_m = m.group(1), re.sub(r'<[^>]+>', '', m.group(2)).strip()
425
+ if not title_m or not url_m or "ecosia" in url_m or len(title_m) < 5:
426
+ continue
427
+ results.append({
428
+ "title": title_m[:80],
429
+ "url": url_m,
430
+ "snippet": "",
431
+ "source": "ecosia",
432
+ "score": max(1.0 - len(results) * 0.1, 0.1),
433
+ })
434
+ if len(results) >= n:
435
+ break
436
+ return results
437
+ except Exception as e:
438
+ logger.warning(f"Ecosia 搜索失败: {e}")
439
+ return []
440
+ return _engine