argo-search 1.0.1 → 2.9.8
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.
- package/LICENSE +201 -21
- package/README.en.md +735 -0
- package/README.es.md +777 -0
- package/README.ja.md +777 -0
- package/README.ko.md +777 -0
- package/README.md +778 -320
- package/SKILL.md +135 -0
- package/assets/readme/hero.svg +121 -0
- package/assets/readme/made-with-beautify.svg +10 -0
- package/assets/readme/proof-routes.svg +73 -0
- package/assets/readme/why-better.svg +104 -0
- package/assets/readme/workflow.svg +83 -0
- package/backends/domain_profiles.json +2356 -96
- package/backends/engine_registry.yaml +3428 -503
- package/backends/query_synonyms_cn.json +35 -0
- package/backends/quota_profiles.json +2488 -160
- package/backends/source_types_cn.json +109 -0
- package/backends/text_lexicon.json +44 -0
- package/bin/argo +772 -0
- package/bin/argo.js +102 -39
- package/config.yaml +5139 -367
- package/cordis.patch.yml +21 -0
- package/data/countries.json +40 -0
- package/data/lang_matrix/lang_capability.json +842 -0
- package/data/lang_matrix/matrix_2026-09-10.json +11921 -0
- package/data/regions_cn.json +1 -0
- package/docs/ADDING_NEW_ENGINE.md +184 -0
- package/docs/ARGO_INTRO.md +124 -0
- package/docs/DATA_SOURCE_GAPS.md +81 -0
- package/docs/DESIGN_LOCAL_DATA_FUSION.md +90 -0
- package/docs/ENGINE_CATALOG.md +575 -0
- package/docs/MCP_SETUP.md +108 -0
- package/docs/PLATFORM_PATHS.md +111 -0
- package/docs/RELEASE_NOTES_v2.8.0.md +71 -0
- package/docs/RELEASE_NOTES_v2.8.2.md +63 -0
- package/docs/RELEASE_NOTES_v2.8.3.md +55 -0
- package/docs/RELEASE_NOTES_v2.8.4.md +79 -0
- package/docs/RELEASE_NOTES_v2.8.5.md +102 -0
- package/docs/RELEASE_NOTES_v2.8.6.md +66 -0
- package/docs/RELEASE_NOTES_v2.8.7.md +65 -0
- package/docs/RELEASE_NOTES_v2.8.8.md +67 -0
- package/docs/RELEASE_NOTES_v2.8.9.md +67 -0
- package/docs/RELEASE_NOTES_v2.9.0.md +64 -0
- package/docs/RELEASE_NOTES_v2.9.8.md +73 -0
- package/docs/SEARCH_ARCHIVE.md +115 -0
- package/docs/assets/architecture.svg +104 -0
- package/docs/assets/hero.svg +98 -0
- package/docs/engines/bangumi.md +27 -0
- package/docs/engines/biorxiv.md +27 -0
- package/docs/engines/clawhub.md +27 -0
- package/docs/engines/crt_sh.md +27 -0
- package/docs/engines/datacite.md +27 -0
- package/docs/engines/deps_dev.md +27 -0
- package/docs/engines/douban_movie.md +27 -0
- package/docs/engines/endoflife.md +27 -0
- package/docs/engines/flk_law.md +27 -0
- package/docs/engines/fxtwitter.md +27 -0
- package/docs/engines/google_news.md +27 -0
- package/docs/engines/gov_regulations.md +27 -0
- package/docs/engines/guqian.md +27 -0
- package/docs/engines/hackernews.md +27 -0
- package/docs/engines/iplant.md +27 -0
- package/docs/engines/keenable.md +27 -0
- package/docs/engines/met_museum.md +27 -0
- package/docs/engines/nasa_images.md +27 -0
- package/docs/engines/netease_music.md +27 -0
- package/docs/engines/npm.md +27 -0
- package/docs/engines/nvd.md +27 -0
- package/docs/engines/openreview.md +27 -0
- package/docs/engines/openstd.md +27 -0
- package/docs/engines/parallel_free.md +27 -0
- package/docs/engines/people_daily.md +27 -0
- package/docs/engines/realtime_index.md +27 -0
- package/docs/engines/sec_edgar.md +27 -0
- package/docs/engines/seltz.md +27 -0
- package/docs/engines/skillsmp.md +27 -0
- package/docs/engines/sspai.md +27 -0
- package/docs/engines/std_samr.md +27 -0
- package/docs/engines/train.md +27 -0
- package/docs/engines/un_comtrade.md +27 -0
- package/docs/engines/wikisource.md +27 -0
- package/docs/engines/zdic.md +27 -0
- package/docs/engines/zenodo.md +27 -0
- package/docs/engines/zhihu_hot_app.md +27 -0
- package/docs//344/270/272/344/273/200/344/271/210/351/200/211/346/213/251argo.md +176 -0
- package/engines/README.md +10 -0
- package/engines/_template_cli.yaml +39 -0
- package/engines/_template_http.yaml +37 -0
- package/engines/plugins/_template_plugin.py +50 -0
- package/engines/specs/_cisa_kev.yaml +54 -0
- package/engines/specs/_osv.yaml +63 -0
- package/engines/specs/aljazeera.yaml +43 -0
- package/engines/specs/antara.yaml +43 -0
- package/engines/specs/artic.yaml +20 -0
- package/engines/specs/aviation_weather.yaml +39 -0
- package/engines/specs/bangumi.yaml +21 -0
- package/engines/specs/biorxiv.yaml +24 -0
- package/engines/specs/carbon_intensity.yaml +32 -0
- package/engines/specs/cfets.yaml +41 -0
- package/engines/specs/cinii.yaml +44 -0
- package/engines/specs/clawhub.yaml +49 -0
- package/engines/specs/cleveland.yaml +19 -0
- package/engines/specs/cn_ai_news.yaml +43 -0
- package/engines/specs/core.yaml +35 -0
- package/engines/specs/crt_sh.yaml +38 -0
- package/engines/specs/ct24.yaml +43 -0
- package/engines/specs/datacite.yaml +27 -0
- package/engines/specs/datagov_uk.yaml +41 -0
- package/engines/specs/ddgs_images.yaml +45 -0
- package/engines/specs/deepseek_search.yaml +74 -0
- package/engines/specs/deezer.yaml +20 -0
- package/engines/specs/defillama.yaml +38 -0
- package/engines/specs/deps_dev.yaml +21 -0
- package/engines/specs/digup.yaml +36 -0
- package/engines/specs/doi.yaml +39 -0
- package/engines/specs/douban_movie.yaml +22 -0
- package/engines/specs/dw_news.yaml +47 -0
- package/engines/specs/egov_law.yaml +21 -0
- package/engines/specs/elpais.yaml +43 -0
- package/engines/specs/endoflife.yaml +21 -0
- package/engines/specs/energy_charts.yaml +21 -0
- package/engines/specs/factcheck_org.yaml +52 -0
- package/engines/specs/faz.yaml +43 -0
- package/engines/specs/federal_register.yaml +57 -0
- package/engines/specs/firecrawl.yaml +53 -0
- package/engines/specs/flk_law.yaml +24 -0
- package/engines/specs/folha.yaml +43 -0
- package/engines/specs/france24.yaml +44 -0
- package/engines/specs/full_fact.yaml +47 -0
- package/engines/specs/fxtwitter.yaml +36 -0
- package/engines/specs/gbfs_nyc.yaml +32 -0
- package/engines/specs/gdacs.yaml +21 -0
- package/engines/specs/gdelt.yaml +64 -0
- package/engines/specs/gleif.yaml +55 -0
- package/engines/specs/google_news.yaml +22 -0
- package/engines/specs/gov_regulations.yaml +38 -0
- package/engines/specs/guardian_rss.yaml +59 -0
- package/engines/specs/hkex.yaml +40 -0
- package/engines/specs/hurriyet.yaml +43 -0
- package/engines/specs/iplant.yaml +25 -0
- package/engines/specs/jikan.yaml +24 -0
- package/engines/specs/jolpica.yaml +20 -0
- package/engines/specs/k10plus.yaml +20 -0
- package/engines/specs/keenable.yaml +44 -0
- package/engines/specs/keenable_free.yaml +36 -0
- package/engines/specs/lefigaro.yaml +43 -0
- package/engines/specs/listenbrainz.yaml +19 -0
- package/engines/specs/matichon.yaml +43 -0
- package/engines/specs/met_museum.yaml +21 -0
- package/engines/specs/nasa_images.yaml +45 -0
- package/engines/specs/netease_music.yaml +38 -0
- package/engines/specs/nhk.yaml +43 -0
- package/engines/specs/nhtsa_vpic.yaml +20 -0
- package/engines/specs/noaa_swpc.yaml +20 -0
- package/engines/specs/nvd.yaml +38 -0
- package/engines/specs/obis.yaml +20 -0
- package/engines/specs/opencitations.yaml +52 -0
- package/engines/specs/openf1.yaml +21 -0
- package/engines/specs/openfoodfacts.yaml +45 -0
- package/engines/specs/openligadb.yaml +20 -0
- package/engines/specs/openreview.yaml +37 -0
- package/engines/specs/openstd.yaml +21 -0
- package/engines/specs/people_daily.yaml +22 -0
- package/engines/specs/realtime_index.yaml +34 -0
- package/engines/specs/ror.yaml +20 -0
- package/engines/specs/satnogs.yaml +20 -0
- package/engines/specs/sec_edgar.yaml +46 -0
- package/engines/specs/shfe.yaml +39 -0
- package/engines/specs/skillsmp.yaml +50 -0
- package/engines/specs/sogou_weixin_browser.yaml +30 -0
- package/engines/specs/soilgrids.yaml +25 -0
- package/engines/specs/sspai.yaml +36 -0
- package/engines/specs/stackexchange.yaml +46 -0
- package/engines/specs/std_samr.yaml +21 -0
- package/engines/specs/tass.yaml +43 -0
- package/engines/specs/tavily_free.yaml +56 -0
- package/engines/specs/tle_mirror.yaml +21 -0
- package/engines/specs/train.yaml +37 -0
- package/engines/specs/tvmaze.yaml +20 -0
- package/engines/specs/twitter_syndication.yaml +31 -0
- package/engines/specs/un_comtrade.yaml +23 -0
- package/engines/specs/unpaywall.yaml +54 -0
- package/engines/specs/vnpress.yaml +43 -0
- package/engines/specs/weather.yaml +32 -0
- package/engines/specs/who_don.yaml +21 -0
- package/engines/specs/who_gho.yaml +20 -0
- package/engines/specs/wikimedia_commons.yaml +34 -0
- package/engines/specs/wikisource.yaml +22 -0
- package/engines/specs/worms.yaml +19 -0
- package/engines/specs/x_trending.yaml +34 -0
- package/engines/specs/yna.yaml +43 -0
- package/engines/specs/ynet.yaml +43 -0
- package/engines/specs/yuanbao.yaml +93 -0
- package/engines/specs/zbmath.yaml +40 -0
- package/engines/specs/zdic.yaml +22 -0
- package/engines/specs/zenodo.yaml +27 -0
- package/engines/specs/zhihu_hot_app.yaml +35 -0
- package/mcp/clients.yaml +106 -0
- package/package.json +55 -12
- package/packages/dsh-plugin/README.md +152 -0
- package/packages/dsh-plugin/cordis.patch.yml +48 -0
- package/packages/dsh-plugin/dsh/client.js +1533 -0
- package/packages/dsh-plugin/dsh/diagnostics.js +54 -0
- package/packages/dsh-plugin/dsh/index.js +527 -0
- package/packages/dsh-plugin/dsh/native-tools.mjs +749 -0
- package/packages/dsh-plugin/dsh/runner.js +666 -0
- package/packages/dsh-plugin/dsh/wide-research.js +878 -0
- package/packages/dsh-plugin/package.json +38 -0
- package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
- package/packages/dsh-plugin/test/client.test.js +406 -0
- package/packages/dsh-plugin/test/config.test.js +30 -0
- package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
- package/packages/dsh-plugin/test/index.test.js +1382 -0
- package/references/query-templates.md +96 -0
- package/references/query-templates.yaml +118 -0
- package/scripts/ab_eval.py +219 -0
- package/scripts/ab_eval_p0p1.py +266 -0
- package/scripts/adaptive.py +294 -70
- package/scripts/answer.py +154 -0
- package/scripts/archive_run.py +591 -0
- package/scripts/argo_engine_registry.py +172 -0
- package/scripts/argo_paths.py +728 -0
- package/scripts/article.py +136 -0
- package/scripts/article_meta.py +131 -0
- package/scripts/arxiv_source.py +201 -0
- package/scripts/badcase_ledger.py +252 -0
- package/scripts/batch_probe.py +241 -0
- package/scripts/benchmark.py +19 -0
- package/scripts/bounded_run.py +144 -0
- package/scripts/browser_auth.py +323 -0
- package/scripts/budget.py +247 -0
- package/scripts/budget_cli.py +93 -0
- package/scripts/build_release_assets.sh +65 -0
- package/scripts/burstiness_detector.py +147 -0
- package/scripts/cache.py +1066 -67
- package/scripts/cache_guard.py +271 -0
- package/scripts/cache_key_vdom.py +56 -0
- package/scripts/cache_similarity.py +170 -0
- package/scripts/caliber_isolation.py +195 -0
- package/scripts/calibrate_lowquality.py +210 -0
- package/scripts/candidate_envelope.py +366 -0
- package/scripts/chrome_cdp.py +871 -0
- package/scripts/ci_drift_gate.py +90 -0
- package/scripts/circuit_breaker.py +389 -0
- package/scripts/citations.py +334 -0
- package/scripts/cite_cli.py +59 -0
- package/scripts/cjk_tokens.py +95 -0
- package/scripts/clarify.py +47 -24
- package/scripts/cli_io.py +240 -0
- package/scripts/config.py +683 -45
- package/scripts/config_cache.py +402 -0
- package/scripts/conn_pool.py +220 -0
- package/scripts/content_boundary.py +46 -0
- package/scripts/content_security.py +685 -0
- package/scripts/content_signals.py +751 -0
- package/scripts/crawl.py +74 -35
- package/scripts/cross_lingual_trace.py +293 -0
- package/scripts/daemon.py +866 -0
- package/scripts/digup_bridge.py +214 -0
- package/scripts/dispatch_result.py +98 -0
- package/scripts/engine_admission.py +366 -0
- package/scripts/engine_dispatch.py +982 -0
- package/scripts/engine_env.py +539 -0
- package/scripts/engine_failure.py +411 -0
- package/scripts/engine_families.py +738 -0
- package/scripts/engine_policy.py +218 -0
- package/scripts/engine_requires.py +190 -0
- package/scripts/engine_status.py +322 -0
- package/scripts/engine_validate.py +638 -0
- package/scripts/engines.py +799 -294
- package/scripts/engines_base.py +1521 -0
- package/scripts/engines_builders.py +310 -0
- package/scripts/engines_builders_batch10.py +204 -0
- package/scripts/engines_builders_batch11.py +174 -0
- package/scripts/engines_builders_batch12.py +103 -0
- package/scripts/engines_builders_batch13.py +340 -0
- package/scripts/engines_builders_batch14.py +656 -0
- package/scripts/engines_builders_batch15.py +249 -0
- package/scripts/engines_builders_batch9.py +1468 -0
- package/scripts/engines_builders_cn.py +2283 -0
- package/scripts/engines_builders_data.py +2923 -0
- package/scripts/engines_builders_data_macro.py +775 -0
- package/scripts/engines_builders_feeds.py +255 -0
- package/scripts/engines_builders_intl.py +1239 -0
- package/scripts/engines_builders_keyless.py +111 -0
- package/scripts/engines_builders_search.py +440 -0
- package/scripts/engines_builders_tech.py +1359 -0
- package/scripts/evidence.py +620 -166
- package/scripts/evidence_loop.py +624 -0
- package/scripts/evidence_tier.py +298 -0
- package/scripts/except_sets.py +25 -0
- package/scripts/extract.py +55 -16
- package/scripts/fact_align.py +207 -0
- package/scripts/fact_crosscheck.py +230 -0
- package/scripts/fetch.py +25 -12
- package/scripts/fetch_browser.py +87 -0
- package/scripts/fetch_jsrun.py +122 -0
- package/scripts/fetch_quality.py +225 -0
- package/scripts/fetch_render_tinyfish.py +117 -0
- package/scripts/fetch_v3.py +1804 -0
- package/scripts/focus_extract.py +350 -0
- package/scripts/fulltext_store.py +169 -0
- package/scripts/fxtwitter_api.py +325 -0
- package/scripts/gen_engine_catalog.py +364 -0
- package/scripts/gen_native_tools.py +91 -0
- package/scripts/gen_routing_snapshot.py +195 -0
- package/scripts/health_check.py +140 -402
- package/scripts/health_probe.py +23 -8
- package/scripts/hot_state.py +134 -0
- package/scripts/http_client.py +1053 -0
- package/scripts/image_ops.py +430 -0
- package/scripts/image_vision/vision_probe.swift +126 -0
- package/scripts/install.ps1 +166 -0
- package/scripts/install.sh +221 -0
- package/scripts/job.py +1186 -0
- package/scripts/jump_resolver.py +235 -0
- package/scripts/lang_capability.py +150 -0
- package/scripts/lang_detect.py +289 -0
- package/scripts/lang_matrix_probe.py +329 -0
- package/scripts/lang_pref.py +311 -0
- package/scripts/link_source.py +340 -0
- package/scripts/local_image.py +970 -0
- package/scripts/local_seek.py +402 -0
- package/scripts/macro_countries.py +97 -0
- package/scripts/matrix_search_eval.py +1187 -0
- package/scripts/mcp_compact.py +234 -0
- package/scripts/mcp_diag.py +8 -3
- package/scripts/mcp_handlers.py +492 -0
- package/scripts/mcp_handlers_surface.py +157 -0
- package/scripts/mcp_launch.sh +124 -0
- package/scripts/mcp_local.py +241 -0
- package/scripts/mcp_server.py +125 -419
- package/scripts/mcp_setup.py +431 -0
- package/scripts/mcp_tools.py +331 -0
- package/scripts/mcp_transport.py +191 -0
- package/scripts/mcp_warm.py +127 -0
- package/scripts/min_version_scan.py +335 -0
- package/scripts/net_proxy.py +258 -0
- package/scripts/network_aware.py +152 -0
- package/scripts/output_transparency.py +200 -0
- package/scripts/paid_policy.py +98 -0
- package/scripts/paper.py +274 -0
- package/scripts/pdf_extract.py +518 -0
- package/scripts/phrase_filter.py +66 -0
- package/scripts/plan.py +422 -0
- package/scripts/postprocess_signals.py +150 -0
- package/scripts/pre-commit +48 -0
- package/scripts/query_enhance.py +157 -0
- package/scripts/query_operators.py +196 -0
- package/scripts/query_rewriter.py +116 -7
- package/scripts/query_signals.py +300 -0
- package/scripts/query_templates.py +167 -0
- package/scripts/query_understanding.py +411 -0
- package/scripts/query_variants.py +298 -0
- package/scripts/quota.py +633 -76
- package/scripts/rank_signals.py +775 -0
- package/scripts/ranking_eval.py +319 -0
- package/scripts/readability_extract.py +433 -0
- package/scripts/recompute.py +415 -0
- package/scripts/recovery.py +616 -0
- package/scripts/recovery_l0.py +160 -0
- package/scripts/recovery_runner.py +150 -0
- package/scripts/redskill/redskill_engine.py +332 -0
- package/scripts/regression_p0p1.py +318 -0
- package/scripts/replay_eval.py +658 -0
- package/scripts/rerank_tfidf.py +225 -0
- package/scripts/research.py +607 -403
- package/scripts/research_cli.py +361 -0
- package/scripts/research_dossier.py +492 -0
- package/scripts/research_expand.py +169 -0
- package/scripts/research_gates.py +272 -0
- package/scripts/research_report.py +209 -0
- package/scripts/research_strategy.py +37 -0
- package/scripts/research_work_packages.py +195 -0
- package/scripts/result_lang.py +311 -0
- package/scripts/retry_policy.py +76 -0
- package/scripts/robots_guard.py +259 -0
- package/scripts/route.py +921 -254
- package/scripts/route_bangs.py +40 -0
- package/scripts/route_cache.py +243 -0
- package/scripts/route_combo.py +917 -0
- package/scripts/route_domains.py +341 -0
- package/scripts/route_lang.py +605 -0
- package/scripts/route_log.py +63 -0
- package/scripts/route_policy.py +250 -0
- package/scripts/rrf_classic.py +66 -0
- package/scripts/screenshot.py +73 -0
- package/scripts/search.py +886 -406
- package/scripts/search_attribution.py +185 -0
- package/scripts/search_benchmark.py +407 -0
- package/scripts/search_cli.py +640 -0
- package/scripts/search_entry.py +395 -0
- package/scripts/search_output.py +690 -0
- package/scripts/search_pipeline.py +926 -0
- package/scripts/search_rank.py +991 -0
- package/scripts/search_types.py +129 -16
- package/scripts/seek_locator.py +68 -0
- package/scripts/semantic_evidence.py +391 -0
- package/scripts/serp_guard.py +336 -0
- package/scripts/serp_kill_log.py +74 -0
- package/scripts/single_flight.py +120 -0
- package/scripts/social_engines/__init__.py +10 -0
- package/scripts/social_engines/bilibili_engine.py +131 -0
- package/scripts/social_engines/hackernews_engine.py +113 -0
- package/scripts/social_engines/reddit_engine.py +172 -0
- package/scripts/social_engines/twitter_engine.py +397 -0
- package/scripts/social_engines/v2ex_engine.py +101 -0
- package/scripts/social_engines/weibo_engine.py +128 -0
- package/scripts/social_engines/xiaohongshu_engine.py +137 -0
- package/scripts/social_engines/zhihu_engine.py +139 -0
- package/scripts/social_research.py +201 -0
- package/scripts/sogou_weixin_browser.py +318 -0
- package/scripts/source_ledger.py +249 -0
- package/scripts/stage_timing.py +58 -0
- package/scripts/stats_cli.py +107 -0
- package/scripts/straggler_policy.py +60 -0
- package/scripts/stylometry_detector.py +231 -0
- package/scripts/sync_backends.py +353 -0
- package/scripts/sync_installs.py +29 -0
- package/scripts/template_cli.py +127 -0
- package/scripts/text_lexicon.py +72 -0
- package/scripts/tfidf_router.py +109 -37
- package/scripts/time_utils.py +233 -0
- package/scripts/topic_research_profiles.py +643 -0
- package/scripts/train.py +737 -0
- package/scripts/tweet.py +596 -0
- package/scripts/url_canon.py +217 -0
- package/scripts/url_optimize.py +56 -0
- package/scripts/url_safety.py +236 -0
- package/scripts/usage_log.py +116 -0
- package/scripts/usage_log_clean.py +113 -0
- package/scripts/v2ex_nodes.py +311 -0
- package/scripts/variant_recall.py +188 -0
- package/scripts/watch.py +205 -0
- package/scripts/wechat_article.py +258 -0
- package/scripts/wide_research.py +447 -0
- package/scripts/wx.py +349 -0
- package/scripts/x_article_fetch.py +141 -0
- package/scripts/x_search.py +234 -0
- package/scripts/x_trending.py +189 -0
- package/scripts/yaml_load.py +67 -0
- package/scripts/zhihu_quota.py +139 -0
- package/sub-skills/ego-search/SKILL.md +253 -0
- package/sub-skills/ego-search/scripts/ego_search.py +800 -0
- package/sub-skills/ego-search/scripts/merge.py +226 -0
- package/sub-skills/ego-search/scripts/quality.py +64 -0
- package/sub-skills/ego-search/scripts/runtime.py +172 -0
- package/sub-skills/ego-search/scripts/safety.py +109 -0
- package/sub-skills/ego-search/scripts/serp_spec.py +109 -0
- package/sub-skills/ego-search/scripts/webbridge_adapter.py +374 -0
- package/sub-skills/js-run/SKILL.md +67 -0
- package/sub-skills/js-run/scripts/jsrun.py +112 -0
- package/sub-skills/js-run/scripts/shim.js +188 -0
- package/sub-skills/local-search/SKILL.md +50 -53
- package/sub-skills/local-search/bm25.py +186 -0
- package/sub-skills/local-search/cli_runner.py +331 -0
- package/sub-skills/local-search/config.yaml +152 -55
- package/sub-skills/local-search/engine_registry.py +91 -26
- package/sub-skills/local-search/{health_check.py → local_health_check.py} +140 -10
- package/sub-skills/local-search/local_search_adapter.py +13 -2
- package/sub-skills/local-search/parse_maps.yaml +40 -42
- package/sub-skills/local-search/parsers.py +328 -0
- package/sub-skills/local-search/search_v3.py +716 -242
- package/sub-skills/local-search/smart_router.py +47 -7
- package/sub-skills/local-seek/SKILL.md +110 -0
- package/sub-skills/local-seek/config/domains.yaml +20 -0
- package/sub-skills/local-seek/scripts/eval_rank.py +216 -0
- package/sub-skills/local-seek/scripts/eval_seek.py +224 -0
- package/sub-skills/local-seek/scripts/seek.py +1465 -0
- package/sub-skills/local-seek/scripts/seek_frecency.py +126 -0
- package/sub-skills/local-seek/scripts/seek_fts.py +430 -0
- package/sub-skills/local-seek/scripts/seek_rank.py +197 -0
|
@@ -0,0 +1,1533 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @taxueseek/argo-dsh — 浏览器端设置页(无需构建步骤)
|
|
3
|
+
*
|
|
4
|
+
* 由 DSH ModuleLoader 以 lazy-CJS 方式加载:工厂函数里只 require('react'),
|
|
5
|
+
* 其余能力(slots / configForms / remote.credentials / locale)全部由宿主
|
|
6
|
+
* 通过 cordis 服务注入。设置值走 configForms(settings namespace =
|
|
7
|
+
* 'wide-research'),密钥走 credentials 域——两者都不经过插件配置文件,
|
|
8
|
+
* 密钥更不会回显明文。
|
|
9
|
+
*
|
|
10
|
+
* 注册两个位置:
|
|
11
|
+
* settings.section —— 设置弹窗左侧一级菜单(主入口)
|
|
12
|
+
* plugins.item —— 设置 > 插件 页里的卡片(摘要 + 同一面板)
|
|
13
|
+
*/
|
|
14
|
+
window.__ModuleLoader__.load({
|
|
15
|
+
id: '@taxueseek/argo-dsh',
|
|
16
|
+
factory: (require) => {
|
|
17
|
+
var module = { exports: {} };
|
|
18
|
+
var exports = module.exports;
|
|
19
|
+
|
|
20
|
+
const react = require('react');
|
|
21
|
+
const h = react.createElement;
|
|
22
|
+
const useState = react.useState;
|
|
23
|
+
const useEffect = react.useEffect;
|
|
24
|
+
const useRef = react.useRef;
|
|
25
|
+
|
|
26
|
+
const NS = 'wide-research';
|
|
27
|
+
const SECTION_ID = 'argo';
|
|
28
|
+
|
|
29
|
+
const DICT = {
|
|
30
|
+
zh: {
|
|
31
|
+
title: 'Argo 搜索配置',
|
|
32
|
+
intro: '接入方式、原生工具、API 密钥与研究编排。保存后即时生效;工具名与 provider 属结构性字段,仍需在 profile patch 中修改。',
|
|
33
|
+
loading: '配置加载中…',
|
|
34
|
+
unavailable: '本插件的配置服务不可用(未挂载或只读)。',
|
|
35
|
+
readonly: '当前配置只读。',
|
|
36
|
+
saved: '已保存',
|
|
37
|
+
refused: '宿主拒绝写入(可能只读或字段不可热更新)',
|
|
38
|
+
save: '保存',
|
|
39
|
+
saving: '保存中…',
|
|
40
|
+
on: '开',
|
|
41
|
+
off: '关',
|
|
42
|
+
showMore: '显示更多',
|
|
43
|
+
showLess: '收起',
|
|
44
|
+
tabAccess: '接入',
|
|
45
|
+
tabTools: '工具',
|
|
46
|
+
tabKeys: '密钥',
|
|
47
|
+
tabResearch: '研究',
|
|
48
|
+
tabAdvanced: '高级',
|
|
49
|
+
tabDashboard: '看板',
|
|
50
|
+
dashboardTitle: '运行监控与健康态看板',
|
|
51
|
+
dashboardHint: '实时监控常驻 MCP 连接池、上游搜索源拓扑与额度状态,提供一键预设与快速调优。',
|
|
52
|
+
poolTitle: '常驻 MCP 连接池监控',
|
|
53
|
+
poolStatusHealthy: '就绪(正常)',
|
|
54
|
+
presetTitle: '工作流快速预设',
|
|
55
|
+
presetHint: '一键对齐常用工作场景的并发与引擎策略,免去逐项手动调优。',
|
|
56
|
+
presetFast: '极速日常',
|
|
57
|
+
presetFastDesc: '免密免费源优先,关闭 TF-IDF 重排与数值交叉比对,超低延迟',
|
|
58
|
+
presetStandard: '平衡生产(推荐)',
|
|
59
|
+
presetStandardDesc: '开启 TF-IDF 重排、数值交叉核验、主流付费源,标准超时',
|
|
60
|
+
presetDeep: '深度调研',
|
|
61
|
+
presetDeepDesc: '激活更多取证工具、对冲自适应宽限、语义证据层、更大并发',
|
|
62
|
+
presetApplied: '已应用预设:',
|
|
63
|
+
matrixTitle: '核心引擎拓扑与覆盖全景',
|
|
64
|
+
matrixHint: '当前可用引擎的接入层级与角色分工。带免密标识的引擎无需密钥即可直接使用。',
|
|
65
|
+
matrixConfigured: '密钥已就绪',
|
|
66
|
+
matrixKeyless: '免密层可用',
|
|
67
|
+
matrixPending: '待配密钥',
|
|
68
|
+
searchFilterPlaceholder: '输入关键词快速筛选...',
|
|
69
|
+
summary: 'Argo · web_search {search} · web_fetch {fetch} · 原生工具 {tools} 个 · 研究 {workers}/{max}',
|
|
70
|
+
accessTitle: '接入方式',
|
|
71
|
+
searchSeam: '宿主 web_search 走 argo',
|
|
72
|
+
searchSeamHint: '关闭后 web_search 回到宿主默认搜索后端(web 行 searchProvider)。',
|
|
73
|
+
fetchSeam: '宿主 web_fetch 走 argo',
|
|
74
|
+
fetchSeamHint: '开启前必须在 web 配置行加 fetchProvider: argo;否则两个 provider 同时可用会报 WEB_PROVIDER_AMBIGUOUS。',
|
|
75
|
+
entryTitle: '引擎入口',
|
|
76
|
+
entryHint: 'npx 模式填 npx,参数 -y / github:taxueseek/argo;本机模式填 python3,参数填 mcp_server.py 的绝对路径。',
|
|
77
|
+
command: '命令',
|
|
78
|
+
args: '参数(一行一个)',
|
|
79
|
+
searchTimeout: '搜索超时 (ms)',
|
|
80
|
+
fetchTimeout: '抓取超时 (ms)',
|
|
81
|
+
fetchChars: '抓取正文上限(字符)',
|
|
82
|
+
idle: '连接空闲回收 (ms)',
|
|
83
|
+
concurrency: '并发连接数',
|
|
84
|
+
toolsTitle: '原生工具',
|
|
85
|
+
toolsHint: '勾选即注册为原生一等工具(CLI 单发,不依赖 MCP)。长尾工具只在需要时开,避免每轮工具面膨胀。',
|
|
86
|
+
coreTools: '高频路径',
|
|
87
|
+
longTools: '长尾取证',
|
|
88
|
+
nativeTimeout: '单次调用超时 (ms)',
|
|
89
|
+
nativeChars: '返回文本上限(字符)',
|
|
90
|
+
keysTitle: 'API 密钥',
|
|
91
|
+
keysHint: '密钥写入 DSH 凭证库(与 Models 页同一处),不会写进插件配置、不回显明文。只有勾选「转发」的引用才会作为环境变量传给 argo 子进程。「清除」只删凭证库里的值,环境变量或 .env 提供的 key 仍会生效。',
|
|
92
|
+
commonKeys: '常用引擎',
|
|
93
|
+
moreKeys: '更多引擎',
|
|
94
|
+
configured: '已配置',
|
|
95
|
+
notConfigured: '未配置',
|
|
96
|
+
forward: '转发',
|
|
97
|
+
keyPlaceholderSet: '输入新值(留空保留现有密钥)',
|
|
98
|
+
keyPlaceholderEmpty: '粘贴 API Key',
|
|
99
|
+
saveKey: '保存',
|
|
100
|
+
clearKey: '清除',
|
|
101
|
+
keySaved: '密钥已保存;下一次新建 argo 连接生效(池内旧连接最长 60s 回收)。',
|
|
102
|
+
keyCleared: '密钥已清除。',
|
|
103
|
+
keyUnavailable: '凭证服务不可用。',
|
|
104
|
+
researchTitle: '研究编排',
|
|
105
|
+
workers: '默认 worker 数',
|
|
106
|
+
maxWorkers: 'worker 上限',
|
|
107
|
+
tracks: '轨道上限',
|
|
108
|
+
sources: '每轨信源上限',
|
|
109
|
+
workerTokens: 'worker token 上限',
|
|
110
|
+
synthTokens: '综合 token 上限',
|
|
111
|
+
reportDir: '报告落盘目录',
|
|
112
|
+
advancedTitle: '高级',
|
|
113
|
+
allow: 'worker 工具白名单(一行一个)',
|
|
114
|
+
allowHint: '留空表示使用内置默认(MCP 与原生工具双写)。wide_research 自身与 argo_research 会被硬排除。',
|
|
115
|
+
deny: 'worker 工具拒绝名单(一行一个)',
|
|
116
|
+
keyRefs: '凭证转发名单(一行一个)',
|
|
117
|
+
keyRefsHint: '这些引用名会经 DSH credentials 解析后作为环境变量传给 argo 子进程;没有对应凭证的项会被跳过。',
|
|
118
|
+
structural: '结构性字段',
|
|
119
|
+
structuralHint: 'toolName / provider / searchProviderId / fetchProviderId 只能改 profile patch,改后重启 DSH 生效。',
|
|
120
|
+
mcpHint: 'MCP 全量工具面(mcp__argo__*)是 profile 里的独立 mcp-argo 行,不在本页开关;需要时在 patch 中取消注释并重启。',
|
|
121
|
+
noKey: '(未配置)',
|
|
122
|
+
tabEngines: '引擎',
|
|
123
|
+
enginesTitle: 'argo 引擎开关',
|
|
124
|
+
enginesHint: '这些开关原先是 argo 的环境变量,只能改 ~/.config/argo/env 或启动环境;现在写进插件配置,随 argo 子进程生效。改动对下一次新建连接生效(池内旧连接按「连接空闲回收」时长退出,默认 60s)。密钥不在这里配,请用「密钥」页;本页也不收授权类开关(可复算执行、内网 URL)。',
|
|
125
|
+
groupCache: '缓存与排序',
|
|
126
|
+
groupFetch: '抓取层',
|
|
127
|
+
groupSemantic: '语义与自适应',
|
|
128
|
+
groupEngines: '引擎启停',
|
|
129
|
+
groupNet: '网络出口',
|
|
130
|
+
groupLocal: '本机检索',
|
|
131
|
+
groupMisc: '其他',
|
|
132
|
+
groupPaid: '额度与预算',
|
|
133
|
+
groupPaidHint: '免费与付费源统一管理:金额计费的源设月度预算,按次数限额的源设日/月次数上限;0=不限制。额度或预算用尽后该源自动跳过并回落免费源。改动对下一次新建连接生效。',
|
|
134
|
+
envDefault: '默认',
|
|
135
|
+
envRange: '需在 {min}–{max} 之间',
|
|
136
|
+
keySourceFile: '凭证库',
|
|
137
|
+
keySourceEnv: '启动环境',
|
|
138
|
+
keySourceProjectEnv: '项目 .env',
|
|
139
|
+
keySourceUserEnv: '家目录 .env',
|
|
140
|
+
keySourceOther: '其他层',
|
|
141
|
+
keyReadonly: '只读',
|
|
142
|
+
keyOverrideHint: '保存后将覆盖当前层',
|
|
143
|
+
deepseekShared: '与宿主 web_search 官方后端共用同一把 key,改动同时影响两边',
|
|
144
|
+
},
|
|
145
|
+
en: {
|
|
146
|
+
title: 'Argo Search Settings',
|
|
147
|
+
intro: 'Access mode, native tools, API keys and research orchestration. Saves apply immediately; tool name and provider remain structural profile settings.',
|
|
148
|
+
loading: 'Loading configuration…',
|
|
149
|
+
unavailable: 'Configuration service unavailable (not mounted or read-only).',
|
|
150
|
+
readonly: 'Configuration is read-only.',
|
|
151
|
+
saved: 'Saved',
|
|
152
|
+
refused: 'Host refused the write (read-only or not live-editable)',
|
|
153
|
+
save: 'Save',
|
|
154
|
+
saving: 'Saving…',
|
|
155
|
+
on: 'on',
|
|
156
|
+
off: 'off',
|
|
157
|
+
showMore: 'Show more',
|
|
158
|
+
showLess: 'Show less',
|
|
159
|
+
tabAccess: 'Access',
|
|
160
|
+
tabTools: 'Tools',
|
|
161
|
+
tabKeys: 'Keys',
|
|
162
|
+
tabResearch: 'Research',
|
|
163
|
+
tabAdvanced: 'Advanced',
|
|
164
|
+
tabDashboard: 'Dashboard',
|
|
165
|
+
dashboardTitle: 'Runtime Monitoring & Health Dashboard',
|
|
166
|
+
dashboardHint: 'Real-time overview of the resident MCP connection pool, upstream search engine matrix, and quick workflow presets.',
|
|
167
|
+
poolTitle: 'Resident MCP Pool Monitoring',
|
|
168
|
+
poolStatusHealthy: 'Ready (Healthy)',
|
|
169
|
+
presetTitle: 'Workflow Presets',
|
|
170
|
+
presetHint: 'One-click presets tailored for common research and search workflows.',
|
|
171
|
+
presetFast: 'Fast & Free',
|
|
172
|
+
presetFastDesc: 'Keyless & free sources, disable rerank & crosscheck for lowest latency',
|
|
173
|
+
presetStandard: 'Balanced (Recommended)',
|
|
174
|
+
presetStandardDesc: 'TF-IDF rerank, numeric crosscheck, mainstream paid sources, standard timeouts',
|
|
175
|
+
presetDeep: 'Deep Research',
|
|
176
|
+
presetDeepDesc: 'Full forensic tools, adaptive hedge grace, semantic evidence layer, max concurrency',
|
|
177
|
+
presetApplied: 'Preset applied:',
|
|
178
|
+
matrixTitle: 'Core Engine Topology & Coverage',
|
|
179
|
+
matrixHint: 'Access tiers and roles of available engines. Keyless engines work without an API key.',
|
|
180
|
+
matrixConfigured: 'Key Ready',
|
|
181
|
+
matrixKeyless: 'Keyless Ready',
|
|
182
|
+
matrixPending: 'Key Required',
|
|
183
|
+
searchFilterPlaceholder: 'Filter by keyword or engine...',
|
|
184
|
+
summary: 'Argo · web_search {search} · web_fetch {fetch} · {tools} native tools · research {workers}/{max}',
|
|
185
|
+
accessTitle: 'Access',
|
|
186
|
+
searchSeam: 'Route host web_search to argo',
|
|
187
|
+
searchSeamHint: 'When off, web_search falls back to the host provider configured on the web row.',
|
|
188
|
+
fetchSeam: 'Route host web_fetch to argo',
|
|
189
|
+
fetchSeamHint: 'Requires fetchProvider: argo on the web row; otherwise two available providers trigger WEB_PROVIDER_AMBIGUOUS.',
|
|
190
|
+
entryTitle: 'Engine entry',
|
|
191
|
+
entryHint: 'npx mode: command npx, args -y / github:taxueseek/argo. Local mode: python3 plus the absolute mcp_server.py path.',
|
|
192
|
+
command: 'Command',
|
|
193
|
+
args: 'Args (one per line)',
|
|
194
|
+
searchTimeout: 'Search timeout (ms)',
|
|
195
|
+
fetchTimeout: 'Fetch timeout (ms)',
|
|
196
|
+
fetchChars: 'Fetch body cap (chars)',
|
|
197
|
+
idle: 'Idle recycle (ms)',
|
|
198
|
+
concurrency: 'Concurrent connections',
|
|
199
|
+
toolsTitle: 'Native tools',
|
|
200
|
+
toolsHint: 'Checked tools register as native first-class tools (single-shot CLI, no MCP). Enable long-tail tools only when needed.',
|
|
201
|
+
coreTools: 'Hot path',
|
|
202
|
+
longTools: 'Long tail',
|
|
203
|
+
nativeTimeout: 'Per-call timeout (ms)',
|
|
204
|
+
nativeChars: 'Returned text cap (chars)',
|
|
205
|
+
keysTitle: 'API keys',
|
|
206
|
+
keysHint: 'Keys are stored in the DSH credential store (same place as the Models page), never in plugin config and never echoed. Only refs marked Forward reach the argo child process. Clear removes the stored value only; a key supplied by the environment or .env still applies.',
|
|
207
|
+
commonKeys: 'Common engines',
|
|
208
|
+
moreKeys: 'More engines',
|
|
209
|
+
configured: 'Configured',
|
|
210
|
+
notConfigured: 'Not configured',
|
|
211
|
+
forward: 'Forward',
|
|
212
|
+
keyPlaceholderSet: 'Enter a new value (leave blank to keep the current key)',
|
|
213
|
+
keyPlaceholderEmpty: 'Paste API key',
|
|
214
|
+
saveKey: 'Save',
|
|
215
|
+
clearKey: 'Clear',
|
|
216
|
+
keySaved: 'Key saved; effective on the next new argo connection (old pooled connections recycle within ~60s).',
|
|
217
|
+
keyCleared: 'Key cleared.',
|
|
218
|
+
keyUnavailable: 'Credential service unavailable.',
|
|
219
|
+
researchTitle: 'Research',
|
|
220
|
+
workers: 'Default workers',
|
|
221
|
+
maxWorkers: 'Worker cap',
|
|
222
|
+
tracks: 'Track cap',
|
|
223
|
+
sources: 'Sources per track',
|
|
224
|
+
workerTokens: 'Worker token cap',
|
|
225
|
+
synthTokens: 'Synthesis token cap',
|
|
226
|
+
reportDir: 'Report directory',
|
|
227
|
+
advancedTitle: 'Advanced',
|
|
228
|
+
allow: 'Worker tool allowlist (one per line)',
|
|
229
|
+
allowHint: 'Empty uses the built-in default (MCP + native tools). wide_research itself and argo_research are always excluded.',
|
|
230
|
+
deny: 'Worker tool denylist (one per line)',
|
|
231
|
+
keyRefs: 'Credential forwarding refs (one per line)',
|
|
232
|
+
keyRefsHint: 'These refs resolve through DSH credentials and reach the argo child process as environment variables. Missing refs are skipped.',
|
|
233
|
+
structural: 'Structural fields',
|
|
234
|
+
structuralHint: 'toolName / provider / searchProviderId / fetchProviderId are profile-patch only and need a DSH restart.',
|
|
235
|
+
mcpHint: 'The full MCP tool face (mcp__argo__*) is a separate mcp-argo entry in the profile patch; enable it there and restart.',
|
|
236
|
+
noKey: '(not configured)',
|
|
237
|
+
tabEngines: 'Engines',
|
|
238
|
+
enginesTitle: 'argo engine switches',
|
|
239
|
+
enginesHint: 'These switches used to be argo environment variables, editable only in ~/.config/argo/env or the launch environment. They now live in plugin config and reach the argo child process. Changes apply to the next new connection (pooled connections retire after the idle recycle window, 60s by default). Keys do not belong here; use the Keys tab. Authorization switches (recompute execution, private URLs) are deliberately not exposed.',
|
|
240
|
+
groupCache: 'Cache & ranking',
|
|
241
|
+
groupFetch: 'Fetch layer',
|
|
242
|
+
groupSemantic: 'Semantics & adaptivity',
|
|
243
|
+
groupEngines: 'Engine on-off',
|
|
244
|
+
groupNet: 'Network',
|
|
245
|
+
groupLocal: 'Local search',
|
|
246
|
+
groupMisc: 'Misc',
|
|
247
|
+
groupPaid: 'Quotas & budgets',
|
|
248
|
+
groupPaidHint: 'Free and paid sources share one manager: money-metered sources take a monthly budget, count-capped sources take daily/monthly call caps; 0 = unlimited. When a budget or cap is exhausted, that source is skipped and free fallbacks take over. Changes apply to the next new connection.',
|
|
249
|
+
envDefault: 'default',
|
|
250
|
+
envRange: 'must be between {min} and {max}',
|
|
251
|
+
keySourceFile: 'credential store',
|
|
252
|
+
keySourceEnv: 'launch environment',
|
|
253
|
+
keySourceProjectEnv: 'project .env',
|
|
254
|
+
keySourceUserEnv: 'home .env',
|
|
255
|
+
keySourceOther: 'other layer',
|
|
256
|
+
keyReadonly: 'read-only',
|
|
257
|
+
keyOverrideHint: 'saving overrides the current layer',
|
|
258
|
+
deepseekShared: 'Shared with the host web_search backend; changing it affects both',
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const TOOLS = [
|
|
263
|
+
{ id: 'argo_search', group: 'core', zh: '统一搜索', en: 'Unified search' },
|
|
264
|
+
{ id: 'argo_fetch', group: 'core', zh: '智能抓取', en: 'Smart fetch' },
|
|
265
|
+
{ id: 'argo_evidence', group: 'long', zh: '来源可信度评估', en: 'Source credibility' },
|
|
266
|
+
{ id: 'argo_local_search', group: 'long', zh: '本机文件搜索', en: 'Local file search' },
|
|
267
|
+
{ id: 'argo_local_read', group: 'long', zh: '本机文件读取', en: 'Local file read' },
|
|
268
|
+
{ id: 'argo_recompute', group: 'long', zh: '可复算执行器', en: 'Recompute' },
|
|
269
|
+
{ id: 'argo_clarify', group: 'long', zh: '意图消歧', en: 'Intent clarification' },
|
|
270
|
+
{ id: 'argo_crawl', group: 'long', zh: '站点级爬取', en: 'Site crawl' },
|
|
271
|
+
{ id: 'argo_social_search', group: 'long', zh: '社交平台搜索', en: 'Social search' },
|
|
272
|
+
{ id: 'argo_article', group: 'long', zh: '公众号全文', en: 'WeChat article' },
|
|
273
|
+
{ id: 'argo_job', group: 'long', zh: '招聘聚合', en: 'Job aggregation' },
|
|
274
|
+
{ id: 'argo_extract', group: 'long', zh: '结构化提取', en: 'Structured extract' },
|
|
275
|
+
{ id: 'argo_preflight', group: 'long', zh: '批量 URL 预检', en: 'URL preflight' },
|
|
276
|
+
{ id: 'argo_pdf', group: 'long', zh: 'PDF 提取', en: 'PDF extract' },
|
|
277
|
+
{ id: 'argo_screenshot', group: 'long', zh: '网页截图', en: 'Screenshot' },
|
|
278
|
+
{ id: 'argo_answer', group: 'long', zh: '带引用直答', en: 'Cited answer' },
|
|
279
|
+
{ id: 'argo_watch', group: 'long', zh: '网页变化监控', en: 'Page watch' },
|
|
280
|
+
{ id: 'argo_cite', group: 'long', zh: 'DOI 引用生成', en: 'DOI citation' },
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
const KEYS = [
|
|
284
|
+
{ ref: 'DEEPSEEK_API_KEY', common: true, zh: 'DeepSeek 官方搜索', en: 'DeepSeek official search' },
|
|
285
|
+
{ ref: 'ARGO_YUANBAO_API_KEY', common: true, zh: '腾讯元宝搜索', en: 'Tencent Yuanbao' },
|
|
286
|
+
{ ref: 'TAVILY_API_KEY', common: true, zh: 'Tavily', en: 'Tavily' },
|
|
287
|
+
{ ref: 'EXA_API_KEY', common: true, zh: 'Exa', en: 'Exa' },
|
|
288
|
+
{ ref: 'BOCHA_API_KEY', common: true, zh: '博查', en: 'Bocha' },
|
|
289
|
+
{ ref: 'BRAVE_API_KEY', common: true, zh: 'Brave Search', en: 'Brave Search' },
|
|
290
|
+
{ ref: 'TINYFISH_API_KEY', common: true, zh: 'TinyFish(抓取渲染)', en: 'TinyFish (fetch render)' },
|
|
291
|
+
{ ref: 'WEB_SEARCH_API_KEY', zh: '火山 / ByteDance', en: 'ByteDance search' },
|
|
292
|
+
{ ref: 'OCTEN_API_KEY', zh: 'Octen', en: 'Octen' },
|
|
293
|
+
{ ref: 'FELO_API_KEY', zh: 'Felo', en: 'Felo' },
|
|
294
|
+
{ ref: 'METASO_API_KEY', zh: '秘塔 Metaso', en: 'Metaso' },
|
|
295
|
+
{ ref: 'ANYSEARCH_API_KEY', zh: 'AnySearch(可选)', en: 'AnySearch (optional)' },
|
|
296
|
+
{ ref: 'FIRECRAWL_API_KEY', zh: 'Firecrawl(可选,有免费层)', en: 'Firecrawl (optional, free tier)' },
|
|
297
|
+
{ ref: 'WEREAD_API_KEY', zh: '微信读书', en: 'WeRead' },
|
|
298
|
+
{ ref: 'GITHUB_TOKEN', zh: 'GitHub(仅提高限频)', en: 'GitHub (rate limit)' },
|
|
299
|
+
{ ref: 'ZHIHU_ACCESS_SECRET', zh: '知乎', en: 'Zhihu' },
|
|
300
|
+
{ ref: 'PARALLEL_API_KEY', zh: 'Parallel', en: 'Parallel' },
|
|
301
|
+
{ ref: 'SELTZ_API_KEY', zh: 'Seltz', en: 'Seltz' },
|
|
302
|
+
{ ref: 'YDC_API_KEY', zh: 'You.com', en: 'You.com' },
|
|
303
|
+
{ ref: 'EASTMONEY_APIKEY', zh: '东财妙想', en: 'Eastmoney Miaoxiang' },
|
|
304
|
+
{ ref: 'WOLFRAM_APPID', zh: 'Wolfram Alpha', en: 'Wolfram Alpha' },
|
|
305
|
+
{ ref: 'QWEATHER_KEY', zh: '和风天气', en: 'QWeather' },
|
|
306
|
+
{ ref: 'UNPAYWALL_EMAIL', zh: 'Unpaywall 邮箱(非密钥)', en: 'Unpaywall email (not secret)' },
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
const CORE_ENGINES = [
|
|
310
|
+
{ id: 'deepseek', nameZh: 'DeepSeek 搜索', nameEn: 'DeepSeek Search', roleZh: '通用搜索 / 官方对齐', roleEn: 'General / Official Seam', ref: 'DEEPSEEK_API_KEY', keyless: false },
|
|
311
|
+
{ id: 'yuanbao', nameZh: '腾讯元宝', nameEn: 'Tencent Yuanbao', roleZh: '中文对冲主力', roleEn: 'Chinese General Primary', ref: 'ARGO_YUANBAO_API_KEY', keyless: false },
|
|
312
|
+
{ id: 'tavily', nameZh: 'Tavily Search', nameEn: 'Tavily Search', roleZh: 'AI 知识增强检索', roleEn: 'AI Knowledge Retrieval', ref: 'TAVILY_API_KEY', keyless: false },
|
|
313
|
+
{ id: 'exa', nameZh: 'Exa Neural', nameEn: 'Exa Neural', roleZh: '神经语义精准搜索', roleEn: 'Neural Semantic Search', ref: 'EXA_API_KEY', keyless: false },
|
|
314
|
+
{ id: 'brave', nameZh: 'Brave Search', nameEn: 'Brave Search', roleZh: '独立全球 Web 索引', roleEn: 'Independent Global Index', ref: 'BRAVE_API_KEY', keyless: true },
|
|
315
|
+
{ id: 'bocha', nameZh: '博查 Bocha', nameEn: 'Bocha', roleZh: '垂直多源聚合', roleEn: 'Vertical Multi-source', ref: 'BOCHA_API_KEY', keyless: false },
|
|
316
|
+
{ id: 'jina', nameZh: 'Jina Reader', nameEn: 'Jina Reader', roleZh: '正文高保真提取', roleEn: 'Article Text Extraction', ref: '', keyless: true },
|
|
317
|
+
{ id: 'tinyfish', nameZh: 'TinyFish', nameEn: 'TinyFish', roleZh: '无头渲染抓取兜底', roleEn: 'Headless Render Fallback', ref: 'TINYFISH_API_KEY', keyless: true },
|
|
318
|
+
{ id: 'zhihu', nameZh: '知乎 Zhihu', nameEn: 'Zhihu', roleZh: '中文专业问答舆情', roleEn: 'Professional Q&A / Social', ref: 'ZHIHU_ACCESS_SECRET', keyless: true },
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
// argo 引擎开关(argoEnv 的 KEY=VALUE 行)。与 index.js 的 ARGO_ENV_SPECS
|
|
322
|
+
// 同一份名单,由 test/client.test.js 的漂移门禁核对;kind 决定控件:
|
|
323
|
+
// bool=开关、num=数字、str=文本、list=名单。def 仅作「未配置时显示什么」,
|
|
324
|
+
// 缺省即不写该行。开关文案写正向行为或明确的关闭动作,不再用 invert 反转
|
|
325
|
+
// (历史 bug 见 index.js 注释)。
|
|
326
|
+
const ENGINE_TOGGLES = [
|
|
327
|
+
{ name: 'ARGO_NO_CACHE', kind: 'bool', group: 'cache', def: false, zh: '测试模式(关闭结果缓存)', en: 'Test mode (disable result cache)', hintZh: '日常保持关闭:结果缓存 1 小时。改进 Skill/插件、需要看实时结果时再打开', hintEn: 'Off by default: results are cached for 1h. Enable only when developing the skill/plugin' },
|
|
328
|
+
{ name: 'ARGO_TFIDF_RERANK', kind: 'bool', group: 'cache', def: true, zh: 'TF-IDF 重排', en: 'TF-IDF rerank', hintZh: '多引擎结果融合前的相关性重排,默认开', hintEn: 'Relevance rerank before fusion, on by default' },
|
|
329
|
+
{ name: 'ARGO_NUMBER_CROSSCHECK', kind: 'bool', group: 'cache', def: true, zh: '数值交叉校验', en: 'Number cross-check', hintZh: '对查询里的数值主张做跨源核对,默认开', hintEn: 'Cross-check numeric claims across sources, on by default' },
|
|
330
|
+
{ name: 'ARGO_MINHASH_DEDUPE', kind: 'bool', group: 'cache', def: true, zh: 'MinHash 去重', en: 'MinHash dedupe', hintZh: '近重复结果合并,默认开', hintEn: 'Merge near-duplicate results, on by default' },
|
|
331
|
+
{ name: 'ARGO_MINHASH_THRESHOLD', kind: 'num', group: 'cache', def: 0.85, min: 0.5, max: 1, step: 0.05, zh: '去重阈值', en: 'Dedupe threshold', hintZh: '越高越严(越多判为不同),默认 0.85', hintEn: 'Higher = stricter, default 0.85' },
|
|
332
|
+
{ name: 'ARGO_SERP_GUARD', kind: 'bool', group: 'cache', def: true, zh: 'SERP 守卫', en: 'SERP guard', hintZh: '拦截壳页/风控页混入结果,默认开;误杀时先关它复现', hintEn: 'Keep shell/blocked pages out of results, on by default' },
|
|
333
|
+
{ name: 'ARGO_FETCH_JINA', kind: 'bool', group: 'fetch', def: true, zh: 'Jina 阅读器抓取', en: 'Jina reader fetch', hintZh: 'r.jina.ai 正文提取,默认开', hintEn: 'r.jina.ai text extraction, on by default' },
|
|
334
|
+
{ name: 'ARGO_FETCH_MOBILE', kind: 'bool', group: 'fetch', def: true, zh: '移动端 UA 抓取', en: 'Mobile UA fetch', hintZh: '按 host 记忆移动端身份,默认开', hintEn: 'Per-host mobile identity, on by default' },
|
|
335
|
+
{ name: 'ARGO_FETCH_PARALLEL', kind: 'bool', group: 'fetch', def: true, zh: 'Parallel 免费渲染抓取', en: 'Parallel free render fetch', hintZh: 'keyless 云端渲染兜底,默认开', hintEn: 'Keyless cloud render fallback, on by default' },
|
|
336
|
+
{ name: 'ARGO_FETCH_DEADLINE_S', kind: 'num', group: 'fetch', def: 60, min: 5, max: 300, step: 5, zh: '抓取总截止(秒)', en: 'Fetch deadline (s)', hintZh: '单次抓取全链路墙钟上限,默认 60', hintEn: 'Wall-clock cap per fetch, default 60' },
|
|
337
|
+
{ name: 'ARGO_SEMANTIC_EVIDENCE', kind: 'bool', group: 'semantic', def: false, zh: '语义证据层', en: 'Semantic evidence', hintZh: '显式核验链路的零样本判定,默认关;开启后多一次外呼', hintEn: 'Zero-shot support check on the verify path, off by default' },
|
|
338
|
+
{ name: 'ARGO_ADAPTIVE_GRACE', kind: 'bool', group: 'semantic', def: true, zh: '对冲宽限自适应', en: 'Adaptive hedge grace', hintZh: '按引擎历史 p75 收窄对冲等待窗,默认开', hintEn: 'Narrow hedge window by engine p75, on by default' },
|
|
339
|
+
{ name: 'ARGO_ADAPTIVE_TAIL', kind: 'bool', group: 'semantic', def: false, zh: '分层尾宽', en: 'Layered straggler tail', hintZh: '尾宽按未回来引擎分层取值,默认关', hintEn: 'Per-engine layered tail width, off by default' },
|
|
340
|
+
{ name: 'ARGO_STRAGGLER_GRACE_S', kind: 'num', group: 'semantic', def: 1.5, min: 0, max: 30, step: 0.5, zh: '掉队宽限(秒)', en: 'Straggler grace (s)', hintZh: '已有引擎交付后还等多久,默认 1.5;0 关闭', hintEn: 'Wait after first delivery, default 1.5; 0 disables' },
|
|
341
|
+
{ name: 'ARGO_SERIAL_STAGGER_S', kind: 'num', group: 'semantic', def: 0.8, min: 0, max: 30, step: 0.5, zh: '串行起步间隔(秒)', en: 'Serial stagger (s)', hintZh: '首引擎超时前补发备选源的提前量,默认 0.8;0 最激进', hintEn: 'Lead before firing a backup source, default 0.8; 0 = fire together' },
|
|
342
|
+
{ name: 'ARGO_ENABLE_ENGINES', kind: 'list', group: 'engines', zh: '额外启用引擎', en: 'Extra enabled engines', hintZh: '逗号分隔;叠加在 config.yaml 启用集之上', hintEn: 'Comma-separated; adds to the config.yaml enabled set' },
|
|
343
|
+
{ name: 'ARGO_DISABLE_ENGINES', kind: 'list', group: 'engines', zh: '停用引擎', en: 'Disabled engines', hintZh: '逗号分隔;优先级高于启用集,调试单引擎时用', hintEn: 'Comma-separated; wins over the enabled set' },
|
|
344
|
+
{ name: 'ARGO_PROXY', kind: 'str', group: 'net', zh: 'argo 全局代理', en: 'argo global proxy', hintZh: 'http:// 代理 URL,或 direct 强制直连;留空走环境变量', hintEn: 'http:// proxy URL, or direct; empty falls back to env' },
|
|
345
|
+
{ name: 'ARGO_GDELT_PROXY', kind: 'str', group: 'net', zh: 'GDELT 代理', en: 'GDELT proxy', hintZh: 'GDELT 按出口 IP 限流,经本地代理更稳;留空自动直连', hintEn: 'GDELT rate-limits by egress IP; empty = direct' },
|
|
346
|
+
{ name: 'ARGO_LOCAL_SEEK_ROOTS', kind: 'list', group: 'local', zh: '本机检索根目录', en: 'Local seek roots', hintZh: '路径分隔符分隔;seek.py 的候选安装根,默认用打包位置', hintEn: 'Path-separated roots for seek.py; default uses the bundled copy' },
|
|
347
|
+
{ name: 'ARGO_LOCAL_SEEK_PATH', kind: 'str', group: 'local', zh: 'seek.py 绝对路径', en: 'seek.py path', hintZh: '显式指定 seek.py,优先于根目录候选', hintEn: 'Explicit seek.py, wins over root candidates' },
|
|
348
|
+
{ name: 'ARGO_LOCAL_READ_DIRS', kind: 'list', group: 'local', zh: '本机读取白名单', en: 'Local read allowlist', hintZh: '逗号分隔;不配置则 local_read 一律不放行', hintEn: 'Comma-separated; empty means local_read allows nothing' },
|
|
349
|
+
{ name: 'ARGO_TEXT_LEXICON', kind: 'str', group: 'local', zh: '词表 JSON 路径', en: 'Lexicon JSON path', hintZh: '文本处理词表覆盖,默认用打包词表', hintEn: 'Text lexicon override; default uses the bundled one' },
|
|
350
|
+
{ name: 'ARGO_PAID_SEARCH', kind: 'str', group: 'paid', def: 'auto', zh: '付费源策略', en: 'Paid source policy', hintZh: 'auto=优势域自动使用付费源;off=只用免费源。额度见下方', hintEn: 'auto = use paid sources in proven domains; off = free sources only. See quotas below' },
|
|
351
|
+
{ name: 'ARGO_BUDGET_DEEPSEEK_SEARCH', kind: 'num', group: 'paid', def: 5, min: 0, max: 1000, step: 1, zh: 'DeepSeek 月度预算(元)', en: 'DeepSeek monthly budget (CNY)', hintZh: '0=关闭;¥5≈1000次成功调用(¥0.005/次)', hintEn: '0 = off; CNY 5 ≈ 1000 successful calls (CNY 0.005 each)' },
|
|
352
|
+
{ name: 'ARGO_BUDGET_EXA', kind: 'num', group: 'paid', def: 10, min: 0, max: 10000, step: 1, zh: 'Exa 每月预算(美元)', en: 'Exa monthly budget (USD)', hintZh: '默认 $10/月(约 1000 次);0=关闭自动使用。按成功调用计费', hintEn: 'Default $10/month (~1000 calls); 0 = off. Billed per successful call' },
|
|
353
|
+
{ name: 'ARGO_QUOTA_ANYSEARCH_LIMIT', kind: 'num', group: 'paid', def: 1000, min: 0, max: 1000000, step: 1, zh: 'AnySearch 每日次数上限', en: 'AnySearch daily call cap', hintZh: '默认 1000/日;0=不限制。改动对下一次新建连接生效', hintEn: 'Default 1000/day; 0 = unlimited. Applies to new connections' },
|
|
354
|
+
{ name: 'ARGO_QUOTA_ZHIHU_LIMIT', kind: 'num', group: 'paid', def: 5000, min: 0, max: 1000000, step: 1, zh: '知乎 每日次数上限', en: 'Zhihu daily call cap', hintZh: '默认 5000/日;0=不限制。改动对下一次新建连接生效', hintEn: 'Default 5000/day; 0 = unlimited. Applies to new connections' },
|
|
355
|
+
{ name: 'ARGO_QUOTA_YUANBAO_LIMIT', kind: 'num', group: 'paid', def: 2000, min: 0, max: 1000000, step: 1, zh: '元宝 每月次数上限', en: 'Yuanbao monthly call cap', hintZh: '默认 2000/月;0=不限制。改动对下一次新建连接生效', hintEn: 'Default 2000/month; 0 = unlimited. Applies to new connections' },
|
|
356
|
+
{ name: 'ARGO_QUOTA_TAVILY_LIMIT', kind: 'num', group: 'paid', def: 1000, min: 0, max: 1000000, step: 1, zh: 'Tavily 每月次数上限', en: 'Tavily monthly call cap', hintZh: '默认 1000/月;0=不限制。改动对下一次新建连接生效', hintEn: 'Default 1000/month; 0 = unlimited. Applies to new connections' },
|
|
357
|
+
{ name: 'ARGO_NO_PREWARM', kind: 'bool', group: 'misc', def: false, zh: '关闭连接预热', en: 'Disable prewarm', hintZh: 'MCP 握手后预热引擎注册表,默认开;开启本项即关闭预热', hintEn: 'Prewarm the engine registry after handshake, on by default; enable this to turn it off' },
|
|
358
|
+
{ name: 'ARGO_USAGE_LOG', kind: 'bool', group: 'misc', def: true, zh: '使用日志', en: 'Usage log', hintZh: 'argo 侧 JSONL 使用账本,默认开', hintEn: 'argo JSONL usage ledger, on by default' },
|
|
359
|
+
{ name: 'ARGO_ENGINE_HTTP_CLIENT', kind: 'bool', group: 'misc', def: true, zh: 'HttpClient 连接池', en: 'HttpClient pool', hintZh: '引擎 HTTP 走连接池,默认开;=0 回退 urllib(诊断用)', hintEn: 'Pooled engine HTTP, on by default; =0 falls back to urllib' },
|
|
360
|
+
{ name: 'ARGO_ADMISSION_TTL_S', kind: 'num', group: 'misc', def: 1, min: 0, max: 60, step: 0.5, zh: '准入记忆 TTL(秒)', en: 'Admission memo TTL (s)', hintZh: '引擎准入状态记忆时长,默认 1;0 每次现查', hintEn: 'Admission memo window, default 1; 0 = always fresh' },
|
|
361
|
+
{ name: 'ARGO_CONFIG_STAMP_TTL_S', kind: 'num', group: 'misc', def: 1, min: 0, max: 60, step: 0.5, zh: '配置戳记忆 TTL(秒)', en: 'Config stamp memo TTL (s)', hintZh: '外部引擎扫描记忆时长,默认 1;0 每次现扫', hintEn: 'External engine scan memo window, default 1; 0 = always rescan' },
|
|
362
|
+
];
|
|
363
|
+
const ENV_NAMES = new Set(ENGINE_TOGGLES.map((spec) => spec.name));
|
|
364
|
+
|
|
365
|
+
const DEFAULTS = {
|
|
366
|
+
searchCommand: 'npx',
|
|
367
|
+
searchArgs: ['-y', 'github:taxueseek/argo'],
|
|
368
|
+
searchTimeoutMs: 30000,
|
|
369
|
+
fetchTimeoutMs: 25000,
|
|
370
|
+
fetchMaxChars: 50000,
|
|
371
|
+
searchIdleMs: 60000,
|
|
372
|
+
searchConcurrency: 3,
|
|
373
|
+
nativeTimeoutMs: 60000,
|
|
374
|
+
nativeMaxChars: 24000,
|
|
375
|
+
defaultWorkers: 6,
|
|
376
|
+
maxWorkers: 9,
|
|
377
|
+
maxTracks: 9,
|
|
378
|
+
maxSourcesPerTrack: 5,
|
|
379
|
+
workerMaxTokens: 5000,
|
|
380
|
+
synthesisMaxTokens: 7000,
|
|
381
|
+
reportDir: '',
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const S = {
|
|
385
|
+
wrap: {
|
|
386
|
+
display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 800,
|
|
387
|
+
color: 'var(--dsw-alias-label-primary, #1f2328)',
|
|
388
|
+
},
|
|
389
|
+
title: { margin: 0, fontSize: 18, fontWeight: 600, lineHeight: '26px' },
|
|
390
|
+
hint: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary, #7b818b)' },
|
|
391
|
+
notice: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-business-primary, #3b6ef6)' },
|
|
392
|
+
error: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary, #d1242f)' },
|
|
393
|
+
tabs: {
|
|
394
|
+
display: 'flex', gap: 2, flexWrap: 'wrap',
|
|
395
|
+
borderBottom: '.5px solid var(--dsw-alias-border-l2, #e5e7eb)',
|
|
396
|
+
},
|
|
397
|
+
tab: (active) => ({
|
|
398
|
+
padding: '7px 12px 9px', border: 0, background: 'transparent', cursor: 'pointer',
|
|
399
|
+
font: 'inherit', fontSize: 13, lineHeight: '20px', color: active
|
|
400
|
+
? 'var(--dsw-alias-label-primary, #1f2328)'
|
|
401
|
+
: 'var(--dsw-alias-label-tertiary, #7b818b)',
|
|
402
|
+
borderBottom: active ? '2px solid var(--dsw-alias-state-business-primary, #3b6ef6)' : '2px solid transparent',
|
|
403
|
+
marginBottom: -1,
|
|
404
|
+
}),
|
|
405
|
+
card: {
|
|
406
|
+
display: 'flex', flexDirection: 'column', gap: 10, padding: '14px 16px',
|
|
407
|
+
borderRadius: 12, border: '.5px solid var(--dsw-alias-border-l3, #e5e7eb)',
|
|
408
|
+
background: 'var(--dsw-alias-settings-card-fill, #fff)',
|
|
409
|
+
},
|
|
410
|
+
cardTitle: { margin: 0, fontSize: 14, fontWeight: 600, lineHeight: '20px' },
|
|
411
|
+
row: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 },
|
|
412
|
+
field: { display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 },
|
|
413
|
+
label: { fontSize: 13, fontWeight: 500, lineHeight: '18px' },
|
|
414
|
+
sub: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary, #7b818b)' },
|
|
415
|
+
input: {
|
|
416
|
+
width: '100%', boxSizing: 'border-box', font: 'inherit', fontSize: 13, lineHeight: '20px',
|
|
417
|
+
padding: '7px 9px', borderRadius: 8,
|
|
418
|
+
border: '.5px solid var(--dsw-alias-border-l4, #d0d7de)',
|
|
419
|
+
background: 'var(--dsw-alias-bg-layer-1, #fff)', color: 'inherit',
|
|
420
|
+
},
|
|
421
|
+
mono: { fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', fontSize: 12 },
|
|
422
|
+
textarea: { minHeight: 64, resize: 'vertical' },
|
|
423
|
+
grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 10 },
|
|
424
|
+
toolGrid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 6 },
|
|
425
|
+
check: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' },
|
|
426
|
+
btn: {
|
|
427
|
+
padding: '6px 12px', borderRadius: 8, border: '.5px solid var(--dsw-alias-border-l4, #d0d7de)',
|
|
428
|
+
background: 'var(--dsw-alias-bg-layer-1, #fff)', color: 'inherit', font: 'inherit',
|
|
429
|
+
fontSize: 12, lineHeight: '18px', cursor: 'pointer',
|
|
430
|
+
},
|
|
431
|
+
btnPrimary: {
|
|
432
|
+
padding: '6px 14px', borderRadius: 8, border: '.5px solid var(--dsw-alias-state-business-primary, #3b6ef6)',
|
|
433
|
+
background: 'var(--dsw-alias-state-business-primary, #3b6ef6)', color: '#fff', font: 'inherit',
|
|
434
|
+
fontSize: 12, lineHeight: '18px', cursor: 'pointer',
|
|
435
|
+
},
|
|
436
|
+
chip: (on) => ({
|
|
437
|
+
display: 'inline-flex', alignItems: 'center', gap: 6, padding: '3px 9px',
|
|
438
|
+
borderRadius: 999, fontSize: 11, lineHeight: '16px',
|
|
439
|
+
border: '.5px solid ' + (on ? 'var(--dsw-alias-state-business-primary, #3b6ef6)' : 'var(--dsw-alias-border-l3, #e5e7eb)'),
|
|
440
|
+
color: on ? 'var(--dsw-alias-state-business-primary, #3b6ef6)' : 'var(--dsw-alias-label-tertiary, #7b818b)',
|
|
441
|
+
background: 'transparent',
|
|
442
|
+
}),
|
|
443
|
+
status: { margin: 0, fontSize: 12, minHeight: 18, lineHeight: '18px' },
|
|
444
|
+
keyRow: {
|
|
445
|
+
display: 'grid', gridTemplateColumns: 'minmax(150px, 1fr) minmax(0, 1.5fr)', gap: 10,
|
|
446
|
+
alignItems: 'start', padding: '10px 0',
|
|
447
|
+
borderTop: '.5px solid var(--dsw-alias-border-l2, #eee)',
|
|
448
|
+
},
|
|
449
|
+
keyHead: { display: 'flex', flexDirection: 'column', gap: 2, minWidth: 0 },
|
|
450
|
+
code: { fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', fontSize: 10, color: 'var(--dsw-alias-label-tertiary, #7b818b)' },
|
|
451
|
+
dot: (on) => ({
|
|
452
|
+
display: 'inline-block', width: 6, height: 6, borderRadius: 999,
|
|
453
|
+
background: on ? '#2da44e' : 'var(--dsw-alias-border-l4, #d0d7de)',
|
|
454
|
+
marginRight: 5,
|
|
455
|
+
}),
|
|
456
|
+
actions: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },
|
|
457
|
+
switchWrap: { display: 'flex', alignItems: 'center', gap: 8 },
|
|
458
|
+
divider: { height: '.5px', background: 'var(--dsw-alias-border-l2, #eee)', margin: '2px 0' },
|
|
459
|
+
metricGrid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(170px, 1fr))', gap: 10 },
|
|
460
|
+
metricCard: {
|
|
461
|
+
display: 'flex', flexDirection: 'column', gap: 6, padding: '12px 14px',
|
|
462
|
+
borderRadius: 10, border: '.5px solid var(--dsw-alias-border-l3, #e5e7eb)',
|
|
463
|
+
background: 'var(--dsw-alias-bg-layer-2, #f8fafc)',
|
|
464
|
+
},
|
|
465
|
+
metricLabel: { fontSize: 11, fontWeight: 500, color: 'var(--dsw-alias-label-tertiary, #7b818b)', textTransform: 'uppercase', letterSpacing: '0.04em' },
|
|
466
|
+
metricValue: { fontSize: 18, fontWeight: 600, lineHeight: '22px', color: 'var(--dsw-alias-label-primary, #1f2328)' },
|
|
467
|
+
metricSub: { fontSize: 11, lineHeight: '16px', color: 'var(--dsw-alias-label-secondary, #4b5563)' },
|
|
468
|
+
presetGrid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 },
|
|
469
|
+
presetCard: (active) => ({
|
|
470
|
+
display: 'flex', flexDirection: 'column', gap: 6, padding: '12px 14px',
|
|
471
|
+
borderRadius: 10, cursor: 'pointer', textAlign: 'left',
|
|
472
|
+
border: active ? '1.5px solid var(--dsw-alias-state-business-primary, #3b6ef6)' : '.5px solid var(--dsw-alias-border-l3, #e5e7eb)',
|
|
473
|
+
background: active ? 'var(--dsw-alias-bg-layer-1, #fff)' : 'var(--dsw-alias-settings-card-fill, #fff)',
|
|
474
|
+
boxShadow: active ? '0 1px 3px rgba(59, 110, 246, 0.12)' : 'none',
|
|
475
|
+
}),
|
|
476
|
+
matrixGrid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8 },
|
|
477
|
+
matrixCard: {
|
|
478
|
+
display: 'flex', flexDirection: 'column', gap: 4, padding: '10px 12px',
|
|
479
|
+
borderRadius: 8, border: '.5px solid var(--dsw-alias-border-l3, #e5e7eb)',
|
|
480
|
+
background: 'var(--dsw-alias-bg-layer-1, #fff)',
|
|
481
|
+
},
|
|
482
|
+
badge: (color) => ({
|
|
483
|
+
display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 8px',
|
|
484
|
+
borderRadius: 999, fontSize: 10, fontWeight: 500, lineHeight: '14px',
|
|
485
|
+
background: color === 'green' ? 'rgba(46, 160, 67, 0.12)' : (color === 'blue' ? 'rgba(59, 110, 246, 0.12)' : 'rgba(100, 116, 139, 0.12)'),
|
|
486
|
+
color: color === 'green' ? '#2da44e' : (color === 'blue' ? '#3b6ef6' : '#64748b'),
|
|
487
|
+
border: '.5px solid ' + (color === 'green' ? '#2da44e' : (color === 'blue' ? '#3b6ef6' : '#64748b')),
|
|
488
|
+
}),
|
|
489
|
+
filterInput: {
|
|
490
|
+
width: '100%', boxSizing: 'border-box', font: 'inherit', fontSize: 12, lineHeight: '18px',
|
|
491
|
+
padding: '6px 10px', borderRadius: 8,
|
|
492
|
+
border: '.5px solid var(--dsw-alias-border-l4, #d0d7de)',
|
|
493
|
+
background: 'var(--dsw-alias-bg-layer-1, #fff)', color: 'inherit',
|
|
494
|
+
marginBottom: 8,
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
function asString(value, fallback) {
|
|
499
|
+
return typeof value === 'string' ? value : (fallback ?? '');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function asNumber(value, fallback) {
|
|
503
|
+
const n = typeof value === 'number' ? value : Number.parseInt(String(value ?? ''), 10);
|
|
504
|
+
return Number.isFinite(n) ? n : fallback;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function asArray(value) {
|
|
508
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function lines(text) {
|
|
512
|
+
return String(text ?? '').split('\n').map((line) => line.trim()).filter(Boolean);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function toDraft(value) {
|
|
516
|
+
return {
|
|
517
|
+
command: asString(value.searchCommand, DEFAULTS.searchCommand),
|
|
518
|
+
argsText: asArray(value.searchArgs).join('\n'),
|
|
519
|
+
searchTimeoutMs: String(asNumber(value.searchTimeoutMs, DEFAULTS.searchTimeoutMs)),
|
|
520
|
+
fetchTimeoutMs: String(asNumber(value.fetchTimeoutMs, DEFAULTS.fetchTimeoutMs)),
|
|
521
|
+
fetchMaxChars: String(asNumber(value.fetchMaxChars, DEFAULTS.fetchMaxChars)),
|
|
522
|
+
searchIdleMs: String(asNumber(value.searchIdleMs, DEFAULTS.searchIdleMs)),
|
|
523
|
+
searchConcurrency: String(asNumber(value.searchConcurrency, DEFAULTS.searchConcurrency)),
|
|
524
|
+
nativeTimeoutMs: String(asNumber(value.nativeTimeoutMs, DEFAULTS.nativeTimeoutMs)),
|
|
525
|
+
nativeMaxChars: String(asNumber(value.nativeMaxChars, DEFAULTS.nativeMaxChars)),
|
|
526
|
+
defaultWorkers: String(asNumber(value.defaultWorkers, DEFAULTS.defaultWorkers)),
|
|
527
|
+
maxWorkers: String(asNumber(value.maxWorkers, DEFAULTS.maxWorkers)),
|
|
528
|
+
maxTracks: String(asNumber(value.maxTracks, DEFAULTS.maxTracks)),
|
|
529
|
+
maxSourcesPerTrack: String(asNumber(value.maxSourcesPerTrack, DEFAULTS.maxSourcesPerTrack)),
|
|
530
|
+
workerMaxTokens: String(asNumber(value.workerMaxTokens, DEFAULTS.workerMaxTokens)),
|
|
531
|
+
synthesisMaxTokens: String(asNumber(value.synthesisMaxTokens, DEFAULTS.synthesisMaxTokens)),
|
|
532
|
+
reportDir: asString(value.reportDir, DEFAULTS.reportDir),
|
|
533
|
+
allowText: asArray(value.childToolAllow).join('\n'),
|
|
534
|
+
denyText: asArray(value.childToolDeny).join('\n'),
|
|
535
|
+
keyRefsText: asArray(value.keyRefs).join('\n'),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function Card(props) {
|
|
540
|
+
return h('section', { style: S.card }, [
|
|
541
|
+
props.title ? h('h3', { key: 't', style: S.cardTitle }, props.title) : null,
|
|
542
|
+
props.hint ? h('p', { key: 'h', style: S.hint }, props.hint) : null,
|
|
543
|
+
props.children,
|
|
544
|
+
]);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function Field(props) {
|
|
548
|
+
return h('label', { style: S.field }, [
|
|
549
|
+
h('span', { key: 'l', style: S.label }, props.label),
|
|
550
|
+
props.children,
|
|
551
|
+
props.hint ? h('span', { key: 'h', style: S.sub }, props.hint) : null,
|
|
552
|
+
]);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function Switch(props) {
|
|
556
|
+
return h('div', { style: S.row }, [
|
|
557
|
+
h('div', { key: 'txt', style: { minWidth: 0 } }, [
|
|
558
|
+
h('div', { key: 'l', style: S.label }, props.label),
|
|
559
|
+
props.hint ? h('div', { key: 'h', style: S.sub }, props.hint) : null,
|
|
560
|
+
]),
|
|
561
|
+
h('input', {
|
|
562
|
+
key: 'i',
|
|
563
|
+
type: 'checkbox',
|
|
564
|
+
checked: props.checked === true,
|
|
565
|
+
disabled: props.disabled === true,
|
|
566
|
+
onChange: (event) => props.onChange(event.target.checked),
|
|
567
|
+
'aria-label': props.label,
|
|
568
|
+
}),
|
|
569
|
+
]);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function NumberField(props) {
|
|
573
|
+
return h(Field, { label: props.label, hint: props.hint }, h('input', {
|
|
574
|
+
type: 'number',
|
|
575
|
+
style: S.input,
|
|
576
|
+
value: props.value,
|
|
577
|
+
min: props.min,
|
|
578
|
+
step: props.step ?? 1,
|
|
579
|
+
disabled: props.disabled === true,
|
|
580
|
+
onChange: (event) => props.onChange(event.target.value),
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function PrimaryButton(props) {
|
|
585
|
+
return h('button', {
|
|
586
|
+
type: 'button',
|
|
587
|
+
style: props.primary ? S.btnPrimary : S.btn,
|
|
588
|
+
disabled: props.disabled === true,
|
|
589
|
+
onClick: props.onClick,
|
|
590
|
+
}, props.children);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function ArgoPanel(props) {
|
|
594
|
+
const state = props.useArgoForm((snapshot) => snapshot);
|
|
595
|
+
const value = state && state.value ? state.value : {};
|
|
596
|
+
const ready = state && state.status === 'ready';
|
|
597
|
+
const writable = ready && state.writable !== false;
|
|
598
|
+
const disabled = !writable;
|
|
599
|
+
|
|
600
|
+
const locale = props.locale;
|
|
601
|
+
const initialLang = locale && locale.getSnapshot && locale.getSnapshot().active === 'en' ? 'en' : 'zh';
|
|
602
|
+
const [lang, setLang] = useState(initialLang);
|
|
603
|
+
|
|
604
|
+
const [tab, setTab] = useState('dashboard');
|
|
605
|
+
const [draft, setDraft] = useState(() => toDraft(value));
|
|
606
|
+
const [busy, setBusy] = useState('');
|
|
607
|
+
const [message, setMessage] = useState('');
|
|
608
|
+
const [error, setError] = useState(false);
|
|
609
|
+
|
|
610
|
+
const [credentials, setCredentials] = useState({});
|
|
611
|
+
const [keyDraft, setKeyDraft] = useState({});
|
|
612
|
+
const [keyBusy, setKeyBusy] = useState('');
|
|
613
|
+
const [showAllKeys, setShowAllKeys] = useState(false);
|
|
614
|
+
|
|
615
|
+
// argoEnv 引擎开关:bool 类即时写;num/str/list 类走草稿 + 分组保存。
|
|
616
|
+
const [envDraft, setEnvDraft] = useState({});
|
|
617
|
+
const [envBusy, setEnvBusy] = useState('');
|
|
618
|
+
const [engineFilter, setEngineFilter] = useState('');
|
|
619
|
+
const [keyFilter, setKeyFilter] = useState('');
|
|
620
|
+
|
|
621
|
+
const dirty = useRef(new Set());
|
|
622
|
+
// 读-改-写共享配置字段(keyRefs / nativeTools / argoEnv)时读实时快照,
|
|
623
|
+
// 不读渲染时快照:保存是 await 之后的异步写,连续两次操作都从渲染值出发
|
|
624
|
+
// 会把前一次的结果挤掉(丢一个转发勾选/工具勾选)。
|
|
625
|
+
const liveValue = () => {
|
|
626
|
+
const snapshot = typeof props.useArgoForm?.getSnapshot === 'function'
|
|
627
|
+
? props.useArgoForm.getSnapshot()
|
|
628
|
+
: undefined;
|
|
629
|
+
return snapshot && snapshot.value ? snapshot.value : value;
|
|
630
|
+
};
|
|
631
|
+
// describe 往返乱序防护:只认最新一次请求的响应(同官方 settings 页口径)。
|
|
632
|
+
const credSeq = useRef(0);
|
|
633
|
+
|
|
634
|
+
const dict = lang === 'en' ? DICT.en : DICT.zh;
|
|
635
|
+
const t = (key) => {
|
|
636
|
+
const text = dict[key] !== undefined ? dict[key] : DICT.zh[key];
|
|
637
|
+
return text !== undefined ? text : key;
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
useEffect(() => {
|
|
641
|
+
const off = locale && locale.subscribe
|
|
642
|
+
? locale.subscribe(() => {
|
|
643
|
+
const next = locale.getSnapshot && locale.getSnapshot().active === 'en' ? 'en' : 'zh';
|
|
644
|
+
setLang(next);
|
|
645
|
+
})
|
|
646
|
+
: undefined;
|
|
647
|
+
return typeof off === 'function' ? off : undefined;
|
|
648
|
+
}, [locale]);
|
|
649
|
+
|
|
650
|
+
useEffect(() => {
|
|
651
|
+
setDraft((previous) => {
|
|
652
|
+
const next = toDraft(value);
|
|
653
|
+
for (const key of dirty.current) {
|
|
654
|
+
if (Object.prototype.hasOwnProperty.call(previous, key)) next[key] = previous[key];
|
|
655
|
+
}
|
|
656
|
+
return next;
|
|
657
|
+
});
|
|
658
|
+
}, [state]);
|
|
659
|
+
|
|
660
|
+
const refreshCredentials = async () => {
|
|
661
|
+
if (!props.credentials || typeof props.credentials.describe !== 'function') return;
|
|
662
|
+
const seq = ++credSeq.current;
|
|
663
|
+
try {
|
|
664
|
+
const response = await props.credentials.describe(KEYS.map((entry) => entry.ref));
|
|
665
|
+
if (seq !== credSeq.current) return;
|
|
666
|
+
if (response && response.ok) setCredentials(response.value || {});
|
|
667
|
+
} catch {
|
|
668
|
+
/* 凭证服务暂不可用:保留上一次状态 */
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
useEffect(() => {
|
|
673
|
+
if (props.view === 'summary') return undefined;
|
|
674
|
+
void refreshCredentials();
|
|
675
|
+
const off = props.remote && typeof props.remote.$on === 'function'
|
|
676
|
+
? props.remote.$on('credentials/reference-updated', () => { void refreshCredentials(); })
|
|
677
|
+
: undefined;
|
|
678
|
+
return typeof off === 'function' ? off : undefined;
|
|
679
|
+
}, [state]);
|
|
680
|
+
|
|
681
|
+
const edit = (key, text) => {
|
|
682
|
+
dirty.current.add(key);
|
|
683
|
+
setDraft((previous) => ({ ...previous, [key]: text }));
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const report = (ok, text) => {
|
|
687
|
+
setError(!ok);
|
|
688
|
+
setMessage(text);
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const write = async (ops) => {
|
|
692
|
+
if (disabled) return false;
|
|
693
|
+
setBusy('save');
|
|
694
|
+
try {
|
|
695
|
+
const ok = await props.mutate(ops);
|
|
696
|
+
report(ok, ok ? t('saved') : t('refused'));
|
|
697
|
+
return ok;
|
|
698
|
+
} catch (cause) {
|
|
699
|
+
report(false, cause && cause.message ? cause.message : String(cause));
|
|
700
|
+
return false;
|
|
701
|
+
} finally {
|
|
702
|
+
setBusy('');
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
const setField = async (field, next) => {
|
|
707
|
+
if (disabled) return;
|
|
708
|
+
setBusy(field);
|
|
709
|
+
try {
|
|
710
|
+
const ok = await props.set(field, next);
|
|
711
|
+
report(ok, ok ? t('saved') : t('refused'));
|
|
712
|
+
} catch (cause) {
|
|
713
|
+
report(false, cause && cause.message ? cause.message : String(cause));
|
|
714
|
+
} finally {
|
|
715
|
+
setBusy('');
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
const atLeast = (text, fallback, min) => Math.max(min, asNumber(text, fallback));
|
|
720
|
+
const clearDirty = (keys) => {
|
|
721
|
+
for (const key of keys) dirty.current.delete(key);
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const saveAccess = async () => {
|
|
725
|
+
const args = lines(draft.argsText);
|
|
726
|
+
const ok = await write([
|
|
727
|
+
{ op: 'set', path: ['searchCommand'], value: draft.command.trim() || DEFAULTS.searchCommand },
|
|
728
|
+
{ op: 'set', path: ['searchArgs'], value: args.length ? args : DEFAULTS.searchArgs },
|
|
729
|
+
{ op: 'set', path: ['searchTimeoutMs'], value: atLeast(draft.searchTimeoutMs, DEFAULTS.searchTimeoutMs, 1000) },
|
|
730
|
+
{ op: 'set', path: ['fetchTimeoutMs'], value: atLeast(draft.fetchTimeoutMs, DEFAULTS.fetchTimeoutMs, 1000) },
|
|
731
|
+
// argo_fetch 工具 schema 上限 50000(native-tools.mjs 由 mcp_tools.py
|
|
732
|
+
// 生成):配更大会被 argo clamp 回 50000,面板显示值与实效不符,这里就地截断。
|
|
733
|
+
{ op: 'set', path: ['fetchMaxChars'], value: Math.min(50_000, atLeast(draft.fetchMaxChars, DEFAULTS.fetchMaxChars, 500)) },
|
|
734
|
+
{ op: 'set', path: ['searchIdleMs'], value: atLeast(draft.searchIdleMs, DEFAULTS.searchIdleMs, 0) },
|
|
735
|
+
{ op: 'set', path: ['searchConcurrency'], value: atLeast(draft.searchConcurrency, DEFAULTS.searchConcurrency, 1) },
|
|
736
|
+
]);
|
|
737
|
+
if (ok) clearDirty(['command', 'argsText', 'searchTimeoutMs', 'fetchTimeoutMs', 'fetchMaxChars', 'searchIdleMs', 'searchConcurrency']);
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
const saveTools = async () => {
|
|
741
|
+
const ok = await write([
|
|
742
|
+
{ op: 'set', path: ['nativeTimeoutMs'], value: atLeast(draft.nativeTimeoutMs, DEFAULTS.nativeTimeoutMs, 1000) },
|
|
743
|
+
{ op: 'set', path: ['nativeMaxChars'], value: atLeast(draft.nativeMaxChars, DEFAULTS.nativeMaxChars, 500) },
|
|
744
|
+
]);
|
|
745
|
+
if (ok) clearDirty(['nativeTimeoutMs', 'nativeMaxChars']);
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
const saveResearch = async () => {
|
|
749
|
+
const workers = atLeast(draft.defaultWorkers, DEFAULTS.defaultWorkers, 1);
|
|
750
|
+
const maxWorkers = Math.max(workers, atLeast(draft.maxWorkers, DEFAULTS.maxWorkers, 1));
|
|
751
|
+
const ok = await write([
|
|
752
|
+
{ op: 'set', path: ['defaultWorkers'], value: workers },
|
|
753
|
+
{ op: 'set', path: ['maxWorkers'], value: maxWorkers },
|
|
754
|
+
{ op: 'set', path: ['maxTracks'], value: atLeast(draft.maxTracks, DEFAULTS.maxTracks, 2) },
|
|
755
|
+
{ op: 'set', path: ['maxSourcesPerTrack'], value: atLeast(draft.maxSourcesPerTrack, DEFAULTS.maxSourcesPerTrack, 1) },
|
|
756
|
+
{ op: 'set', path: ['workerMaxTokens'], value: atLeast(draft.workerMaxTokens, DEFAULTS.workerMaxTokens, 512) },
|
|
757
|
+
{ op: 'set', path: ['synthesisMaxTokens'], value: atLeast(draft.synthesisMaxTokens, DEFAULTS.synthesisMaxTokens, 512) },
|
|
758
|
+
{ op: 'set', path: ['reportDir'], value: draft.reportDir.trim() },
|
|
759
|
+
]);
|
|
760
|
+
if (ok) clearDirty(['defaultWorkers', 'maxWorkers', 'maxTracks', 'maxSourcesPerTrack', 'workerMaxTokens', 'synthesisMaxTokens', 'reportDir']);
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
const saveAdvanced = async () => {
|
|
764
|
+
const ok = await write([
|
|
765
|
+
{ op: 'set', path: ['childToolAllow'], value: lines(draft.allowText) },
|
|
766
|
+
{ op: 'set', path: ['childToolDeny'], value: lines(draft.denyText) },
|
|
767
|
+
{ op: 'set', path: ['keyRefs'], value: lines(draft.keyRefsText) },
|
|
768
|
+
]);
|
|
769
|
+
if (ok) clearDirty(['allowText', 'denyText', 'keyRefsText']);
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
const toggleTool = (id) => {
|
|
773
|
+
const current = new Set(asArray(liveValue().nativeTools));
|
|
774
|
+
if (current.has(id)) current.delete(id);
|
|
775
|
+
else current.add(id);
|
|
776
|
+
const ordered = TOOLS.map((tool) => tool.id).filter((toolId) => current.has(toolId));
|
|
777
|
+
for (const extra of current) if (!ordered.includes(extra)) ordered.push(extra);
|
|
778
|
+
void setField('nativeTools', ordered);
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
const saveKey = async (ref) => {
|
|
782
|
+
const key = asString(keyDraft[ref]).trim();
|
|
783
|
+
if (!key || !props.credentials) return;
|
|
784
|
+
setKeyBusy(ref);
|
|
785
|
+
try {
|
|
786
|
+
const response = await props.credentials.set(ref, key);
|
|
787
|
+
if (!response || !response.ok) {
|
|
788
|
+
throw new Error(response && response.error ? response.error.message : t('keyUnavailable'));
|
|
789
|
+
}
|
|
790
|
+
setKeyDraft((previous) => ({ ...previous, [ref]: '' }));
|
|
791
|
+
const refs = asArray(liveValue().keyRefs);
|
|
792
|
+
if (!refs.includes(ref)) await props.set('keyRefs', [...refs, ref]);
|
|
793
|
+
report(true, t('keySaved'));
|
|
794
|
+
await refreshCredentials();
|
|
795
|
+
} catch (cause) {
|
|
796
|
+
report(false, cause && cause.message ? cause.message : String(cause));
|
|
797
|
+
} finally {
|
|
798
|
+
setKeyBusy('');
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
const clearKey = async (ref) => {
|
|
803
|
+
if (!props.credentials) return;
|
|
804
|
+
setKeyBusy(ref);
|
|
805
|
+
try {
|
|
806
|
+
const response = await props.credentials.unset(ref);
|
|
807
|
+
if (!response || !response.ok) {
|
|
808
|
+
throw new Error(response && response.error ? response.error.message : t('keyUnavailable'));
|
|
809
|
+
}
|
|
810
|
+
report(true, t('keyCleared'));
|
|
811
|
+
await refreshCredentials();
|
|
812
|
+
} catch (cause) {
|
|
813
|
+
report(false, cause && cause.message ? cause.message : String(cause));
|
|
814
|
+
} finally {
|
|
815
|
+
setKeyBusy('');
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
const setForward = (ref, on) => {
|
|
820
|
+
const refs = new Set(asArray(liveValue().keyRefs));
|
|
821
|
+
if (on) refs.add(ref);
|
|
822
|
+
else refs.delete(ref);
|
|
823
|
+
void setField('keyRefs', [...refs]);
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
// ── argoEnv 引擎开关:KEY=VALUE 行 ↔ 控件 ──
|
|
827
|
+
// argoEnv 行集合是单一真源:每次写入都基于实时快照读-改-写,只碰白名单
|
|
828
|
+
// 内的名字。白名单外的行**被清掉**,不是原样保留——apply 期对白名单外
|
|
829
|
+
// 名字是 loud fail(整个插件加载失败),直接写 profile patch 写错的开关
|
|
830
|
+
// 会让人卡在「改不回来」;这里任何一次面板写入都顺手清毒,是最容易碰到
|
|
831
|
+
// 的恢复入口。
|
|
832
|
+
const envMap = () => {
|
|
833
|
+
const map = {};
|
|
834
|
+
for (const entry of asArray(liveValue().argoEnv)) {
|
|
835
|
+
const eq = entry.indexOf('=');
|
|
836
|
+
if (eq > 0) map[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
837
|
+
}
|
|
838
|
+
return map;
|
|
839
|
+
};
|
|
840
|
+
// 与 argo 的 env_flag 判定对齐:空串/0/false/no/off/n/disable/disabled/none 算关。
|
|
841
|
+
const ENV_FALSY = ['', '0', 'false', 'no', 'off', 'n', 'disable', 'disabled', 'none'];
|
|
842
|
+
const envBoolState = (spec, map) => {
|
|
843
|
+
const raw = map[spec.name];
|
|
844
|
+
return raw === undefined
|
|
845
|
+
? spec.def === true
|
|
846
|
+
: !ENV_FALSY.includes(String(raw).trim().toLowerCase());
|
|
847
|
+
};
|
|
848
|
+
const writeEnvLines = async (updates) => {
|
|
849
|
+
const map = envMap();
|
|
850
|
+
for (const name of Object.keys(updates)) {
|
|
851
|
+
const value = updates[name];
|
|
852
|
+
if (value === null || value === undefined || value === '') delete map[name];
|
|
853
|
+
else map[name] = value;
|
|
854
|
+
}
|
|
855
|
+
const lines = Object.keys(map)
|
|
856
|
+
.filter((name) => ENV_NAMES.has(name))
|
|
857
|
+
.map((name) => `${name}=${map[name]}`);
|
|
858
|
+
return props.set('argoEnv', lines);
|
|
859
|
+
};
|
|
860
|
+
const toggleEnv = (spec, on) => {
|
|
861
|
+
if (disabled) return;
|
|
862
|
+
// 开关状态直接落 0/1:文案与 env 同向(开=1,关=0),不做反转。
|
|
863
|
+
const value = on ? '1' : '0';
|
|
864
|
+
void writeEnvLines({ [spec.name]: value })
|
|
865
|
+
.then((ok) => report(ok, ok ? t('saved') : t('refused')))
|
|
866
|
+
.catch((cause) => report(false, cause && cause.message ? cause.message : String(cause)));
|
|
867
|
+
};
|
|
868
|
+
const saveEnvGroup = async (group) => {
|
|
869
|
+
if (disabled) return;
|
|
870
|
+
const updates = {};
|
|
871
|
+
for (const spec of ENGINE_TOGGLES) {
|
|
872
|
+
if (spec.group !== group || spec.kind === 'bool') continue;
|
|
873
|
+
const raw = envDraft[spec.name];
|
|
874
|
+
if (raw === undefined) continue;
|
|
875
|
+
const text = String(raw).trim();
|
|
876
|
+
if (text === '') {
|
|
877
|
+
updates[spec.name] = null;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (spec.kind === 'num') {
|
|
881
|
+
const n = Number(text);
|
|
882
|
+
if (!Number.isFinite(n) || n < spec.min || n > spec.max) {
|
|
883
|
+
report(false, `${lang === 'en' ? spec.en : spec.zh}: ${t('envRange').replace('{min}', String(spec.min)).replace('{max}', String(spec.max))}`);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
updates[spec.name] = text;
|
|
888
|
+
}
|
|
889
|
+
if (Object.keys(updates).length === 0) return;
|
|
890
|
+
setEnvBusy(group);
|
|
891
|
+
try {
|
|
892
|
+
const ok = await writeEnvLines(updates);
|
|
893
|
+
report(ok, ok ? t('saved') : t('refused'));
|
|
894
|
+
if (ok) setEnvDraft({});
|
|
895
|
+
} catch (cause) {
|
|
896
|
+
report(false, cause && cause.message ? cause.message : String(cause));
|
|
897
|
+
} finally {
|
|
898
|
+
setEnvBusy('');
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
const applyPreset = async (type) => {
|
|
903
|
+
if (disabled) return;
|
|
904
|
+
setBusy('preset');
|
|
905
|
+
try {
|
|
906
|
+
const map = envMap();
|
|
907
|
+
const ops = [];
|
|
908
|
+
if (type === 'fast') {
|
|
909
|
+
map.ARGO_TFIDF_RERANK = '0';
|
|
910
|
+
map.ARGO_NUMBER_CROSSCHECK = '0';
|
|
911
|
+
map.ARGO_PAID_SEARCH = 'off';
|
|
912
|
+
map.ARGO_ADAPTIVE_GRACE = '1';
|
|
913
|
+
map.ARGO_SERIAL_STAGGER_S = '0.5';
|
|
914
|
+
ops.push({ op: 'set', path: ['searchTimeoutMs'], value: 15000 });
|
|
915
|
+
ops.push({ op: 'set', path: ['fetchTimeoutMs'], value: 15000 });
|
|
916
|
+
} else if (type === 'standard') {
|
|
917
|
+
map.ARGO_TFIDF_RERANK = '1';
|
|
918
|
+
map.ARGO_NUMBER_CROSSCHECK = '1';
|
|
919
|
+
map.ARGO_MINHASH_DEDUPE = '1';
|
|
920
|
+
map.ARGO_PAID_SEARCH = 'auto';
|
|
921
|
+
map.ARGO_ADAPTIVE_GRACE = '1';
|
|
922
|
+
map.ARGO_SERIAL_STAGGER_S = '0.8';
|
|
923
|
+
ops.push({ op: 'set', path: ['searchTimeoutMs'], value: 30000 });
|
|
924
|
+
ops.push({ op: 'set', path: ['fetchTimeoutMs'], value: 25000 });
|
|
925
|
+
} else if (type === 'deep') {
|
|
926
|
+
map.ARGO_TFIDF_RERANK = '1';
|
|
927
|
+
map.ARGO_NUMBER_CROSSCHECK = '1';
|
|
928
|
+
map.ARGO_MINHASH_DEDUPE = '1';
|
|
929
|
+
map.ARGO_SEMANTIC_EVIDENCE = '1';
|
|
930
|
+
map.ARGO_ADAPTIVE_GRACE = '1';
|
|
931
|
+
map.ARGO_PAID_SEARCH = 'auto';
|
|
932
|
+
ops.push({ op: 'set', path: ['searchTimeoutMs'], value: 45000 });
|
|
933
|
+
ops.push({ op: 'set', path: ['fetchTimeoutMs'], value: 35000 });
|
|
934
|
+
ops.push({ op: 'set', path: ['nativeTimeoutMs'], value: 60000 });
|
|
935
|
+
}
|
|
936
|
+
const envLines = Object.keys(map)
|
|
937
|
+
.filter((name) => ENV_NAMES.has(name))
|
|
938
|
+
.map((name) => `${name}=${map[name]}`);
|
|
939
|
+
ops.push({ op: 'set', path: ['argoEnv'], value: envLines });
|
|
940
|
+
const ok = await props.mutate(ops);
|
|
941
|
+
const presetName = type === 'fast' ? t('presetFast') : (type === 'standard' ? t('presetStandard') : t('presetDeep'));
|
|
942
|
+
report(ok, ok ? `${t('presetApplied')} ${presetName}` : t('refused'));
|
|
943
|
+
} catch (cause) {
|
|
944
|
+
report(false, cause && cause.message ? cause.message : String(cause));
|
|
945
|
+
} finally {
|
|
946
|
+
setBusy('');
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
if (props.view === 'summary') {
|
|
951
|
+
const summary = t('summary')
|
|
952
|
+
.replace('{search}', value.searchProviderEnabled !== false ? t('on') : t('off'))
|
|
953
|
+
.replace('{fetch}', value.fetchProviderEnabled !== false ? t('on') : t('off'))
|
|
954
|
+
.replace('{tools}', String(asArray(value.nativeTools).length))
|
|
955
|
+
.replace('{workers}', String(asNumber(value.defaultWorkers, DEFAULTS.defaultWorkers)))
|
|
956
|
+
.replace('{max}', String(asNumber(value.maxWorkers, DEFAULTS.maxWorkers)));
|
|
957
|
+
return summary;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (!ready) {
|
|
961
|
+
return h('div', { style: S.wrap }, [
|
|
962
|
+
h('h2', { key: 't', style: S.title }, t('title')),
|
|
963
|
+
h('p', { key: 'p', style: S.hint }, state && state.status === 'unavailable' ? t('unavailable') : t('loading')),
|
|
964
|
+
]);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
const tabButton = (id, label) => h('button', {
|
|
968
|
+
key: id,
|
|
969
|
+
type: 'button',
|
|
970
|
+
style: S.tab(tab === id),
|
|
971
|
+
onClick: () => setTab(id),
|
|
972
|
+
}, label);
|
|
973
|
+
|
|
974
|
+
const statusLine = message
|
|
975
|
+
? h('p', { style: error ? S.error : S.notice, role: 'status' }, message)
|
|
976
|
+
: h('p', { style: S.status }, disabled ? t('readonly') : '');
|
|
977
|
+
|
|
978
|
+
const coreTools = TOOLS.filter((tool) => tool.group === 'core');
|
|
979
|
+
const longTools = TOOLS.filter((tool) => tool.group === 'long');
|
|
980
|
+
const toolCheckbox = (tool) => h('label', { key: tool.id, style: S.check }, [
|
|
981
|
+
h('input', {
|
|
982
|
+
key: 'i',
|
|
983
|
+
type: 'checkbox',
|
|
984
|
+
checked: asArray(value.nativeTools).includes(tool.id),
|
|
985
|
+
disabled,
|
|
986
|
+
onChange: () => toggleTool(tool.id),
|
|
987
|
+
}),
|
|
988
|
+
h('span', { key: 't' }, [
|
|
989
|
+
h('span', { key: 'n', style: S.mono }, tool.id),
|
|
990
|
+
h('span', { key: 'd', style: S.sub }, ' ' + (lang === 'en' ? tool.en : tool.zh)),
|
|
991
|
+
]),
|
|
992
|
+
]);
|
|
993
|
+
|
|
994
|
+
const keyRow = (entry) => {
|
|
995
|
+
const info = credentials[entry.ref];
|
|
996
|
+
const configured = info && info.configured === true;
|
|
997
|
+
const writable = !info || info.writable !== false;
|
|
998
|
+
const forwarded = asArray(value.keyRefs).includes(entry.ref);
|
|
999
|
+
// 来源层:回答「这把 key 从哪来」。保存写凭证库文件层,优先级高于
|
|
1000
|
+
// 启动环境与 .env,所以来源不是 file 时提示保存后会覆盖当前层。
|
|
1001
|
+
const sourceLabel = configured && typeof info.source === 'string'
|
|
1002
|
+
? t('keySource' + info.source.split('-').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(''))
|
|
1003
|
+
: '';
|
|
1004
|
+
const statusParts = [configured ? t('configured') : t('notConfigured')];
|
|
1005
|
+
if (sourceLabel && sourceLabel !== 'keySource' + info.source) statusParts.push(sourceLabel);
|
|
1006
|
+
if (configured && !writable) statusParts.push(t('keyReadonly'));
|
|
1007
|
+
if (configured && writable && info.source !== undefined && info.source !== 'file') statusParts.push(t('keyOverrideHint'));
|
|
1008
|
+
return h('div', { key: entry.ref, style: S.keyRow }, [
|
|
1009
|
+
h('div', { key: 'head', style: S.keyHead }, [
|
|
1010
|
+
h('span', { key: 'n', style: S.label }, [
|
|
1011
|
+
h('span', { key: 'dot', style: S.dot(configured) }),
|
|
1012
|
+
lang === 'en' ? entry.en : entry.zh,
|
|
1013
|
+
]),
|
|
1014
|
+
h('span', { key: 'ref', style: S.code }, entry.ref),
|
|
1015
|
+
h('span', { key: 's', style: S.sub }, statusParts.join(' · ')),
|
|
1016
|
+
entry.ref === 'DEEPSEEK_API_KEY'
|
|
1017
|
+
? h('span', { key: 'shared', style: S.sub }, t('deepseekShared'))
|
|
1018
|
+
: null,
|
|
1019
|
+
]),
|
|
1020
|
+
h('div', { key: 'body', style: { display: 'flex', flexDirection: 'column', gap: 6 } }, [
|
|
1021
|
+
h('input', {
|
|
1022
|
+
key: 'i',
|
|
1023
|
+
type: 'password',
|
|
1024
|
+
autoComplete: 'off',
|
|
1025
|
+
spellCheck: false,
|
|
1026
|
+
style: S.input,
|
|
1027
|
+
value: asString(keyDraft[entry.ref]),
|
|
1028
|
+
placeholder: configured ? t('keyPlaceholderSet') : t('keyPlaceholderEmpty'),
|
|
1029
|
+
disabled: disabled || !props.credentials || !writable,
|
|
1030
|
+
onChange: (event) => setKeyDraft((previous) => ({ ...previous, [entry.ref]: event.target.value })),
|
|
1031
|
+
}),
|
|
1032
|
+
h('div', { key: 'a', style: S.actions }, [
|
|
1033
|
+
h('label', { key: 'f', style: S.check }, [
|
|
1034
|
+
h('input', {
|
|
1035
|
+
key: 'i',
|
|
1036
|
+
type: 'checkbox',
|
|
1037
|
+
checked: forwarded,
|
|
1038
|
+
disabled,
|
|
1039
|
+
onChange: (event) => setForward(entry.ref, event.target.checked),
|
|
1040
|
+
}),
|
|
1041
|
+
h('span', { key: 't' }, t('forward')),
|
|
1042
|
+
]),
|
|
1043
|
+
h('span', { key: 'sp', style: { flex: 1 } }),
|
|
1044
|
+
h('button', {
|
|
1045
|
+
key: 's',
|
|
1046
|
+
type: 'button',
|
|
1047
|
+
style: S.btn,
|
|
1048
|
+
disabled: disabled || !writable || keyBusy === entry.ref || asString(keyDraft[entry.ref]).trim() === '',
|
|
1049
|
+
onClick: () => { void saveKey(entry.ref); },
|
|
1050
|
+
}, keyBusy === entry.ref ? t('saving') : t('saveKey')),
|
|
1051
|
+
configured ? h('button', {
|
|
1052
|
+
key: 'c',
|
|
1053
|
+
type: 'button',
|
|
1054
|
+
style: S.btn,
|
|
1055
|
+
disabled: disabled || !writable || keyBusy === entry.ref,
|
|
1056
|
+
onClick: () => { void clearKey(entry.ref); },
|
|
1057
|
+
}, t('clearKey')) : null,
|
|
1058
|
+
]),
|
|
1059
|
+
]),
|
|
1060
|
+
]);
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
const commonKeys = KEYS.filter((entry) => entry.common);
|
|
1064
|
+
const visibleKeys = showAllKeys ? KEYS : commonKeys;
|
|
1065
|
+
|
|
1066
|
+
const panel = [];
|
|
1067
|
+
|
|
1068
|
+
if (tab === 'dashboard') {
|
|
1069
|
+
const configuredKeyCount = Object.values(credentials).filter((c) => c && c.configured).length;
|
|
1070
|
+
const keylessEngineCount = CORE_ENGINES.filter((e) => e.keyless).length;
|
|
1071
|
+
const seamSearchOn = value.searchProviderEnabled !== false;
|
|
1072
|
+
const seamFetchOn = value.fetchProviderEnabled !== false;
|
|
1073
|
+
const activeToolCount = asArray(value.nativeTools).length;
|
|
1074
|
+
const poolConcurrency = asNumber(value.searchConcurrency, DEFAULTS.searchConcurrency);
|
|
1075
|
+
const poolIdleMs = asNumber(value.searchIdleMs, DEFAULTS.searchIdleMs);
|
|
1076
|
+
|
|
1077
|
+
panel.push(h('div', { key: 'metrics', style: S.metricGrid }, [
|
|
1078
|
+
h('div', { key: 'pool', style: S.metricCard }, [
|
|
1079
|
+
h('span', { key: 'l', style: S.metricLabel }, t('poolTitle')),
|
|
1080
|
+
h('div', { key: 'v', style: { display: 'flex', alignItems: 'center', gap: 6 } }, [
|
|
1081
|
+
h('span', { key: 'dot', style: S.dot(true) }),
|
|
1082
|
+
h('span', { key: 'val', style: S.metricValue }, `${poolConcurrency} 并发`),
|
|
1083
|
+
]),
|
|
1084
|
+
h('span', { key: 's', style: S.metricSub }, `${t('poolStatusHealthy')} · 保活 ${poolIdleMs / 1000}s`),
|
|
1085
|
+
]),
|
|
1086
|
+
h('div', { key: 'seam', style: S.metricCard }, [
|
|
1087
|
+
h('span', { key: 'l', style: S.metricLabel }, t('accessTitle')),
|
|
1088
|
+
h('span', { key: 'val', style: S.metricValue }, seamSearchOn && seamFetchOn ? '双路接管' : (seamSearchOn || seamFetchOn ? '单路接管' : '未接管')),
|
|
1089
|
+
h('span', { key: 's', style: S.metricSub }, `Search: ${seamSearchOn ? 'ON' : 'OFF'} · Fetch: ${seamFetchOn ? 'ON' : 'OFF'}`),
|
|
1090
|
+
]),
|
|
1091
|
+
h('div', { key: 'tools', style: S.metricCard }, [
|
|
1092
|
+
h('span', { key: 'l', style: S.metricLabel }, t('toolsTitle')),
|
|
1093
|
+
h('span', { key: 'val', style: S.metricValue }, `${activeToolCount} / ${TOOLS.length}`),
|
|
1094
|
+
h('span', { key: 's', style: S.metricSub }, '已就绪原生 MCP 工具'),
|
|
1095
|
+
]),
|
|
1096
|
+
h('div', { key: 'keys', style: S.metricCard }, [
|
|
1097
|
+
h('span', { key: 'l', style: S.metricLabel }, t('keysTitle')),
|
|
1098
|
+
h('span', { key: 'val', style: S.metricValue }, `${configuredKeyCount} 配置 · ${keylessEngineCount} 免密`),
|
|
1099
|
+
h('span', { key: 's', style: S.metricSub }, '覆盖全局多源引擎'),
|
|
1100
|
+
]),
|
|
1101
|
+
]));
|
|
1102
|
+
|
|
1103
|
+
panel.push(h(Card, { key: 'presets', title: t('presetTitle'), hint: t('presetHint') }, [
|
|
1104
|
+
h('div', { key: 'grid', style: S.presetGrid }, [
|
|
1105
|
+
h('div', {
|
|
1106
|
+
key: 'fast',
|
|
1107
|
+
style: S.presetCard(false),
|
|
1108
|
+
onClick: () => { void applyPreset('fast'); },
|
|
1109
|
+
}, [
|
|
1110
|
+
h('div', { key: 't', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' } }, [
|
|
1111
|
+
h('span', { key: 'title', style: S.label }, t('presetFast')),
|
|
1112
|
+
h('span', { key: 'badge', style: S.badge('blue') }, '15s 超时'),
|
|
1113
|
+
]),
|
|
1114
|
+
h('span', { key: 'd', style: S.sub }, t('presetFastDesc')),
|
|
1115
|
+
]),
|
|
1116
|
+
h('div', {
|
|
1117
|
+
key: 'standard',
|
|
1118
|
+
style: S.presetCard(true),
|
|
1119
|
+
onClick: () => { void applyPreset('standard'); },
|
|
1120
|
+
}, [
|
|
1121
|
+
h('div', { key: 't', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' } }, [
|
|
1122
|
+
h('span', { key: 'title', style: S.label }, t('presetStandard')),
|
|
1123
|
+
h('span', { key: 'badge', style: S.badge('green') }, '标准生产'),
|
|
1124
|
+
]),
|
|
1125
|
+
h('span', { key: 'd', style: S.sub }, t('presetStandardDesc')),
|
|
1126
|
+
]),
|
|
1127
|
+
h('div', {
|
|
1128
|
+
key: 'deep',
|
|
1129
|
+
style: S.presetCard(false),
|
|
1130
|
+
onClick: () => { void applyPreset('deep'); },
|
|
1131
|
+
}, [
|
|
1132
|
+
h('div', { key: 't', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' } }, [
|
|
1133
|
+
h('span', { key: 'title', style: S.label }, t('presetDeep')),
|
|
1134
|
+
h('span', { key: 'badge', style: S.badge('blue') }, '深度核验'),
|
|
1135
|
+
]),
|
|
1136
|
+
h('span', { key: 'd', style: S.sub }, t('presetDeepDesc')),
|
|
1137
|
+
]),
|
|
1138
|
+
]),
|
|
1139
|
+
]));
|
|
1140
|
+
|
|
1141
|
+
panel.push(h(Card, { key: 'matrix', title: t('matrixTitle'), hint: t('matrixHint') }, [
|
|
1142
|
+
h('div', { key: 'grid', style: S.matrixGrid }, CORE_ENGINES.map((engine) => {
|
|
1143
|
+
const hasKey = engine.ref && credentials[engine.ref] && credentials[engine.ref].configured;
|
|
1144
|
+
const badgeType = hasKey ? 'green' : (engine.keyless ? 'blue' : 'gray');
|
|
1145
|
+
const badgeText = hasKey ? t('matrixConfigured') : (engine.keyless ? t('matrixKeyless') : t('matrixPending'));
|
|
1146
|
+
return h('div', { key: engine.id, style: S.matrixCard }, [
|
|
1147
|
+
h('div', { key: 'h', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 6 } }, [
|
|
1148
|
+
h('span', { key: 'n', style: S.label }, lang === 'en' ? engine.nameEn : engine.nameZh),
|
|
1149
|
+
h('span', { key: 'b', style: S.badge(badgeType) }, badgeText),
|
|
1150
|
+
]),
|
|
1151
|
+
h('span', { key: 'r', style: S.sub }, lang === 'en' ? engine.roleEn : engine.roleZh),
|
|
1152
|
+
]);
|
|
1153
|
+
})),
|
|
1154
|
+
]));
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
if (tab === 'access') {
|
|
1158
|
+
panel.push(h(Card, { key: 'seam', title: t('accessTitle') }, [
|
|
1159
|
+
h(Switch, {
|
|
1160
|
+
key: 's',
|
|
1161
|
+
checked: value.searchProviderEnabled !== false,
|
|
1162
|
+
disabled,
|
|
1163
|
+
label: t('searchSeam'),
|
|
1164
|
+
hint: t('searchSeamHint'),
|
|
1165
|
+
onChange: (next) => { void setField('searchProviderEnabled', next); },
|
|
1166
|
+
}),
|
|
1167
|
+
h(Switch, {
|
|
1168
|
+
key: 'f',
|
|
1169
|
+
checked: value.fetchProviderEnabled !== false,
|
|
1170
|
+
disabled,
|
|
1171
|
+
label: t('fetchSeam'),
|
|
1172
|
+
hint: t('fetchSeamHint'),
|
|
1173
|
+
onChange: (next) => { void setField('fetchProviderEnabled', next); },
|
|
1174
|
+
}),
|
|
1175
|
+
]));
|
|
1176
|
+
panel.push(h(Card, { key: 'entry', title: t('entryTitle'), hint: t('entryHint') }, [
|
|
1177
|
+
h(Field, { key: 'c', label: t('command') }, h('input', {
|
|
1178
|
+
type: 'text',
|
|
1179
|
+
style: { ...S.input, ...S.mono },
|
|
1180
|
+
value: draft.command,
|
|
1181
|
+
disabled,
|
|
1182
|
+
spellCheck: false,
|
|
1183
|
+
onChange: (event) => edit('command', event.target.value),
|
|
1184
|
+
})),
|
|
1185
|
+
h(Field, { key: 'a', label: t('args') }, h('textarea', {
|
|
1186
|
+
style: { ...S.input, ...S.textarea, ...S.mono },
|
|
1187
|
+
value: draft.argsText,
|
|
1188
|
+
disabled,
|
|
1189
|
+
spellCheck: false,
|
|
1190
|
+
onChange: (event) => edit('argsText', event.target.value),
|
|
1191
|
+
})),
|
|
1192
|
+
h('div', { key: 'g', style: S.grid }, [
|
|
1193
|
+
h(NumberField, {
|
|
1194
|
+
key: 'st', label: t('searchTimeout'), min: 1000, value: draft.searchTimeoutMs, disabled,
|
|
1195
|
+
onChange: (next) => edit('searchTimeoutMs', next),
|
|
1196
|
+
}),
|
|
1197
|
+
h(NumberField, {
|
|
1198
|
+
key: 'ft', label: t('fetchTimeout'), min: 1000, value: draft.fetchTimeoutMs, disabled,
|
|
1199
|
+
onChange: (next) => edit('fetchTimeoutMs', next),
|
|
1200
|
+
}),
|
|
1201
|
+
h(NumberField, {
|
|
1202
|
+
key: 'fc', label: t('fetchChars'), min: 500, step: 500, value: draft.fetchMaxChars, disabled,
|
|
1203
|
+
onChange: (next) => edit('fetchMaxChars', next),
|
|
1204
|
+
}),
|
|
1205
|
+
h(NumberField, {
|
|
1206
|
+
key: 'id', label: t('idle'), min: 0, step: 1000, value: draft.searchIdleMs, disabled,
|
|
1207
|
+
onChange: (next) => edit('searchIdleMs', next),
|
|
1208
|
+
}),
|
|
1209
|
+
h(NumberField, {
|
|
1210
|
+
key: 'cc', label: t('concurrency'), min: 1, value: draft.searchConcurrency, disabled,
|
|
1211
|
+
onChange: (next) => edit('searchConcurrency', next),
|
|
1212
|
+
}),
|
|
1213
|
+
]),
|
|
1214
|
+
h('div', { key: 'a2', style: S.actions }, [
|
|
1215
|
+
h(PrimaryButton, { primary: true, disabled: disabled || busy === 'save', onClick: () => { void saveAccess(); } },
|
|
1216
|
+
busy === 'save' ? t('saving') : t('save')),
|
|
1217
|
+
]),
|
|
1218
|
+
]));
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
if (tab === 'tools') {
|
|
1222
|
+
panel.push(h(Card, { key: 'tools', title: t('toolsTitle'), hint: t('toolsHint') }, [
|
|
1223
|
+
h('div', { key: 'core', style: { display: 'flex', flexDirection: 'column', gap: 4 } }, [
|
|
1224
|
+
h('span', { key: 'l', style: S.label }, t('coreTools')),
|
|
1225
|
+
h('div', { key: 'g', style: S.toolGrid }, coreTools.map(toolCheckbox)),
|
|
1226
|
+
]),
|
|
1227
|
+
h('div', { key: 'div', style: S.divider }),
|
|
1228
|
+
h('div', { key: 'long', style: { display: 'flex', flexDirection: 'column', gap: 4 } }, [
|
|
1229
|
+
h('span', { key: 'l', style: S.label }, t('longTools')),
|
|
1230
|
+
h('div', { key: 'g', style: S.toolGrid }, longTools.map(toolCheckbox)),
|
|
1231
|
+
]),
|
|
1232
|
+
h('div', { key: 'g2', style: S.grid }, [
|
|
1233
|
+
h(NumberField, {
|
|
1234
|
+
key: 'nt', label: t('nativeTimeout'), min: 1000, value: draft.nativeTimeoutMs, disabled,
|
|
1235
|
+
onChange: (next) => edit('nativeTimeoutMs', next),
|
|
1236
|
+
}),
|
|
1237
|
+
h(NumberField, {
|
|
1238
|
+
key: 'nc', label: t('nativeChars'), min: 500, step: 500, value: draft.nativeMaxChars, disabled,
|
|
1239
|
+
onChange: (next) => edit('nativeMaxChars', next),
|
|
1240
|
+
}),
|
|
1241
|
+
]),
|
|
1242
|
+
h('div', { key: 'a', style: S.actions }, [
|
|
1243
|
+
h(PrimaryButton, { primary: true, disabled: disabled || busy === 'save', onClick: () => { void saveTools(); } },
|
|
1244
|
+
busy === 'save' ? t('saving') : t('save')),
|
|
1245
|
+
]),
|
|
1246
|
+
]));
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (tab === 'engines') {
|
|
1250
|
+
const map = envMap();
|
|
1251
|
+
// 渲染顺序即面板顺序。**不是**从 ENGINE_TOGGLES 去重推导——那样分组
|
|
1252
|
+
// 顺序会随白名单增删而漂移,用户每次升级都看到重排的面板。
|
|
1253
|
+
//
|
|
1254
|
+
// 历史 bug(2026-10-21 定位):`paid` 一度不在这个清单里,于是
|
|
1255
|
+
// ARGO_PAID_SEARCH 与 ARGO_BUDGET_DEEPSEEK_SEARCH 两条开关在
|
|
1256
|
+
// index.js 白名单、client.js ENGINE_TOGGLES、双语文案三处都齐全,
|
|
1257
|
+
// 却在面板上完全不显示——用户配好了预算却找不到入口,还以为是插件
|
|
1258
|
+
// 没装。漂移门禁只核对「两边名单同集合、字段一致」,核对不到「这个
|
|
1259
|
+
// 分组到底渲不渲染」;已由 test/client.test.js 的「engines tab renders
|
|
1260
|
+
// a control for every ENGINE_TOGGLES switch」补上(见该测试注释)。
|
|
1261
|
+
const groups = ['cache', 'fetch', 'semantic', 'engines', 'net', 'local', 'paid', 'misc'];
|
|
1262
|
+
const groupTitle = (group) => t('group' + group.charAt(0).toUpperCase() + group.slice(1));
|
|
1263
|
+
panel.push(h('p', { key: 'hint', style: S.hint }, t('enginesHint')));
|
|
1264
|
+
panel.push(h('input', {
|
|
1265
|
+
key: 'filter',
|
|
1266
|
+
type: 'text',
|
|
1267
|
+
style: S.filterInput,
|
|
1268
|
+
placeholder: t('searchFilterPlaceholder'),
|
|
1269
|
+
value: engineFilter,
|
|
1270
|
+
onChange: (event) => setEngineFilter(event.target.value),
|
|
1271
|
+
}));
|
|
1272
|
+
const query = engineFilter.trim().toLowerCase();
|
|
1273
|
+
for (const group of groups) {
|
|
1274
|
+
let specs = ENGINE_TOGGLES.filter((spec) => spec.group === group);
|
|
1275
|
+
if (query) {
|
|
1276
|
+
const groupNameMatch = group.toLowerCase().includes(query) || groupTitle(group).toLowerCase().includes(query);
|
|
1277
|
+
if (!groupNameMatch) {
|
|
1278
|
+
specs = specs.filter((spec) => {
|
|
1279
|
+
const name = spec.name.toLowerCase();
|
|
1280
|
+
const zh = (spec.zh || '').toLowerCase();
|
|
1281
|
+
const en = (spec.en || '').toLowerCase();
|
|
1282
|
+
const hintZh = (spec.hintZh || '').toLowerCase();
|
|
1283
|
+
const hintEn = (spec.hintEn || '').toLowerCase();
|
|
1284
|
+
return name.includes(query) || zh.includes(query) || en.includes(query) || hintZh.includes(query) || hintEn.includes(query);
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (specs.length === 0) continue;
|
|
1289
|
+
const bools = specs.filter((spec) => spec.kind === 'bool');
|
|
1290
|
+
const fields = specs.filter((spec) => spec.kind !== 'bool');
|
|
1291
|
+
const children = [];
|
|
1292
|
+
if (group === 'paid') {
|
|
1293
|
+
children.push(h('p', { key: 'groupHint', style: S.hint }, t('groupPaidHint')));
|
|
1294
|
+
}
|
|
1295
|
+
for (const spec of bools) {
|
|
1296
|
+
// 未配置时显示 argo 默认态并标注「默认」,配置过的显示自定义值,
|
|
1297
|
+
// 让「这一行到底生不生效」在面板上一眼可辨。
|
|
1298
|
+
const custom = map[spec.name] !== undefined;
|
|
1299
|
+
const suffix = custom ? '' : (lang === 'en' ? ` (${t('envDefault')})` : `(${t('envDefault')})`);
|
|
1300
|
+
children.push(h(Switch, {
|
|
1301
|
+
key: spec.name,
|
|
1302
|
+
checked: envBoolState(spec, map),
|
|
1303
|
+
disabled,
|
|
1304
|
+
label: (lang === 'en' ? spec.en : spec.zh) + suffix,
|
|
1305
|
+
hint: lang === 'en' ? spec.hintEn : spec.hintZh,
|
|
1306
|
+
onChange: (next) => toggleEnv(spec, next),
|
|
1307
|
+
}));
|
|
1308
|
+
}
|
|
1309
|
+
if (fields.length > 0) {
|
|
1310
|
+
const fieldNodes = fields.map((spec) => {
|
|
1311
|
+
const drafted = envDraft[spec.name];
|
|
1312
|
+
const current = drafted !== undefined
|
|
1313
|
+
? drafted
|
|
1314
|
+
: (map[spec.name] !== undefined ? map[spec.name] : (spec.kind === 'num' ? String(spec.def) : ''));
|
|
1315
|
+
const control = spec.kind === 'num'
|
|
1316
|
+
? h('input', {
|
|
1317
|
+
key: 'i',
|
|
1318
|
+
type: 'number',
|
|
1319
|
+
style: S.input,
|
|
1320
|
+
value: current,
|
|
1321
|
+
min: spec.min,
|
|
1322
|
+
max: spec.max,
|
|
1323
|
+
step: spec.step ?? 1,
|
|
1324
|
+
disabled,
|
|
1325
|
+
onChange: (event) => setEnvDraft((previous) => ({ ...previous, [spec.name]: event.target.value })),
|
|
1326
|
+
})
|
|
1327
|
+
: h('input', {
|
|
1328
|
+
key: 'i',
|
|
1329
|
+
type: 'text',
|
|
1330
|
+
style: { ...S.input, ...S.mono },
|
|
1331
|
+
value: current,
|
|
1332
|
+
disabled,
|
|
1333
|
+
spellCheck: false,
|
|
1334
|
+
onChange: (event) => setEnvDraft((previous) => ({ ...previous, [spec.name]: event.target.value })),
|
|
1335
|
+
});
|
|
1336
|
+
return h(Field, {
|
|
1337
|
+
key: spec.name,
|
|
1338
|
+
label: lang === 'en' ? spec.en : spec.zh,
|
|
1339
|
+
hint: lang === 'en' ? spec.hintEn : spec.hintZh,
|
|
1340
|
+
}, control);
|
|
1341
|
+
});
|
|
1342
|
+
children.push(h('div', { key: 'fields', style: S.grid }, fieldNodes));
|
|
1343
|
+
children.push(h('div', { key: 'save', style: S.actions }, [
|
|
1344
|
+
h(PrimaryButton, {
|
|
1345
|
+
key: 'b',
|
|
1346
|
+
primary: true,
|
|
1347
|
+
disabled: disabled || envBusy === group,
|
|
1348
|
+
onClick: () => { void saveEnvGroup(group); },
|
|
1349
|
+
}, envBusy === group ? t('saving') : t('save')),
|
|
1350
|
+
]));
|
|
1351
|
+
}
|
|
1352
|
+
panel.push(h(Card, { key: group, title: groupTitle(group) }, children));
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
if (tab === 'keys') {
|
|
1357
|
+
const query = keyFilter.trim().toLowerCase();
|
|
1358
|
+
const displayKeys = query
|
|
1359
|
+
? KEYS.filter((entry) => entry.ref.toLowerCase().includes(query) || (entry.zh && entry.zh.toLowerCase().includes(query)) || (entry.en && entry.en.toLowerCase().includes(query)))
|
|
1360
|
+
: visibleKeys;
|
|
1361
|
+
panel.push(h(Card, { key: 'keys', title: t('keysTitle'), hint: t('keysHint') }, [
|
|
1362
|
+
h('input', {
|
|
1363
|
+
key: 'filter',
|
|
1364
|
+
type: 'text',
|
|
1365
|
+
style: S.filterInput,
|
|
1366
|
+
placeholder: t('searchFilterPlaceholder'),
|
|
1367
|
+
value: keyFilter,
|
|
1368
|
+
onChange: (event) => setKeyFilter(event.target.value),
|
|
1369
|
+
}),
|
|
1370
|
+
h('span', { key: 'l', style: S.label }, query ? t('keysTitle') : t('commonKeys')),
|
|
1371
|
+
...displayKeys.map(keyRow),
|
|
1372
|
+
query ? null : h('div', { key: 'a', style: S.actions }, [
|
|
1373
|
+
h('button', {
|
|
1374
|
+
key: 'more',
|
|
1375
|
+
type: 'button',
|
|
1376
|
+
style: S.btn,
|
|
1377
|
+
onClick: () => setShowAllKeys((previous) => !previous),
|
|
1378
|
+
}, showAllKeys ? t('showLess') : t('showMore')),
|
|
1379
|
+
props.credentials ? null : h('span', { key: 'u', style: S.sub }, t('keyUnavailable')),
|
|
1380
|
+
]),
|
|
1381
|
+
]));
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
if (tab === 'research') {
|
|
1385
|
+
panel.push(h(Card, { key: 'research', title: t('researchTitle') }, [
|
|
1386
|
+
h('div', { key: 'g', style: S.grid }, [
|
|
1387
|
+
h(NumberField, {
|
|
1388
|
+
key: 'dw', label: t('workers'), min: 1, value: draft.defaultWorkers, disabled,
|
|
1389
|
+
onChange: (next) => edit('defaultWorkers', next),
|
|
1390
|
+
}),
|
|
1391
|
+
h(NumberField, {
|
|
1392
|
+
key: 'mw', label: t('maxWorkers'), min: 1, value: draft.maxWorkers, disabled,
|
|
1393
|
+
onChange: (next) => edit('maxWorkers', next),
|
|
1394
|
+
}),
|
|
1395
|
+
h(NumberField, {
|
|
1396
|
+
key: 'mt', label: t('tracks'), min: 2, value: draft.maxTracks, disabled,
|
|
1397
|
+
onChange: (next) => edit('maxTracks', next),
|
|
1398
|
+
}),
|
|
1399
|
+
h(NumberField, {
|
|
1400
|
+
key: 'ms', label: t('sources'), min: 1, value: draft.maxSourcesPerTrack, disabled,
|
|
1401
|
+
onChange: (next) => edit('maxSourcesPerTrack', next),
|
|
1402
|
+
}),
|
|
1403
|
+
h(NumberField, {
|
|
1404
|
+
key: 'wt', label: t('workerTokens'), min: 512, step: 512, value: draft.workerMaxTokens, disabled,
|
|
1405
|
+
onChange: (next) => edit('workerMaxTokens', next),
|
|
1406
|
+
}),
|
|
1407
|
+
h(NumberField, {
|
|
1408
|
+
key: 'st', label: t('synthTokens'), min: 512, step: 512, value: draft.synthesisMaxTokens, disabled,
|
|
1409
|
+
onChange: (next) => edit('synthesisMaxTokens', next),
|
|
1410
|
+
}),
|
|
1411
|
+
]),
|
|
1412
|
+
h(Field, { key: 'd', label: t('reportDir') }, h('input', {
|
|
1413
|
+
type: 'text',
|
|
1414
|
+
style: { ...S.input, ...S.mono },
|
|
1415
|
+
value: draft.reportDir,
|
|
1416
|
+
disabled,
|
|
1417
|
+
spellCheck: false,
|
|
1418
|
+
onChange: (event) => edit('reportDir', event.target.value),
|
|
1419
|
+
})),
|
|
1420
|
+
h('div', { key: 'a', style: S.actions }, [
|
|
1421
|
+
h(PrimaryButton, { primary: true, disabled: disabled || busy === 'save', onClick: () => { void saveResearch(); } },
|
|
1422
|
+
busy === 'save' ? t('saving') : t('save')),
|
|
1423
|
+
]),
|
|
1424
|
+
]));
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
if (tab === 'advanced') {
|
|
1428
|
+
panel.push(h(Card, { key: 'adv', title: t('advancedTitle') }, [
|
|
1429
|
+
h(Field, { key: 'allow', label: t('allow'), hint: t('allowHint') }, h('textarea', {
|
|
1430
|
+
style: { ...S.input, ...S.textarea, ...S.mono },
|
|
1431
|
+
value: draft.allowText,
|
|
1432
|
+
disabled,
|
|
1433
|
+
spellCheck: false,
|
|
1434
|
+
onChange: (event) => edit('allowText', event.target.value),
|
|
1435
|
+
})),
|
|
1436
|
+
h(Field, { key: 'deny', label: t('deny') }, h('textarea', {
|
|
1437
|
+
style: { ...S.input, ...S.textarea, ...S.mono },
|
|
1438
|
+
value: draft.denyText,
|
|
1439
|
+
disabled,
|
|
1440
|
+
spellCheck: false,
|
|
1441
|
+
onChange: (event) => edit('denyText', event.target.value),
|
|
1442
|
+
})),
|
|
1443
|
+
h(Field, { key: 'refs', label: t('keyRefs'), hint: t('keyRefsHint') }, h('textarea', {
|
|
1444
|
+
style: { ...S.input, ...S.textarea, ...S.mono },
|
|
1445
|
+
value: draft.keyRefsText,
|
|
1446
|
+
disabled,
|
|
1447
|
+
spellCheck: false,
|
|
1448
|
+
onChange: (event) => edit('keyRefsText', event.target.value),
|
|
1449
|
+
})),
|
|
1450
|
+
h('div', { key: 'a', style: S.actions }, [
|
|
1451
|
+
h(PrimaryButton, { primary: true, disabled: disabled || busy === 'save', onClick: () => { void saveAdvanced(); } },
|
|
1452
|
+
busy === 'save' ? t('saving') : t('save')),
|
|
1453
|
+
]),
|
|
1454
|
+
h('div', { key: 'div', style: S.divider }),
|
|
1455
|
+
h('div', { key: 'struct', style: { display: 'flex', flexDirection: 'column', gap: 4 } }, [
|
|
1456
|
+
h('span', { key: 'l', style: S.label }, t('structural')),
|
|
1457
|
+
h('span', { key: 'h', style: S.sub }, t('structuralHint')),
|
|
1458
|
+
h('span', { key: 'm', style: S.sub }, t('mcpHint')),
|
|
1459
|
+
]),
|
|
1460
|
+
]));
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
return h('div', { style: S.wrap }, [
|
|
1464
|
+
h('div', { key: 'head', style: { display: 'flex', flexDirection: 'column', gap: 4 } }, [
|
|
1465
|
+
h('h2', { key: 't', style: S.title }, t('title')),
|
|
1466
|
+
h('p', { key: 'i', style: S.hint }, t('intro')),
|
|
1467
|
+
]),
|
|
1468
|
+
h('div', { key: 'tabs', style: S.tabs }, [
|
|
1469
|
+
tabButton('dashboard', t('tabDashboard')),
|
|
1470
|
+
tabButton('access', t('tabAccess')),
|
|
1471
|
+
tabButton('tools', t('tabTools')),
|
|
1472
|
+
tabButton('engines', t('tabEngines')),
|
|
1473
|
+
tabButton('keys', t('tabKeys')),
|
|
1474
|
+
tabButton('research', t('tabResearch')),
|
|
1475
|
+
tabButton('advanced', t('tabAdvanced')),
|
|
1476
|
+
]),
|
|
1477
|
+
statusLine,
|
|
1478
|
+
...panel,
|
|
1479
|
+
]);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const inject = ['slots', 'locale', 'configForms', 'remote', 'remote.credentials'];
|
|
1483
|
+
|
|
1484
|
+
function apply(ctx) {
|
|
1485
|
+
const form = ctx.configForms.get(NS);
|
|
1486
|
+
const locale = ctx.locale ?? (typeof ctx.get === 'function' ? ctx.get('locale') : undefined);
|
|
1487
|
+
const credentials = ctx.remote ? ctx.remote.credentials : undefined;
|
|
1488
|
+
const label = () => 'Argo 搜索';
|
|
1489
|
+
|
|
1490
|
+
const injected = () => ({
|
|
1491
|
+
hooks: { argoForm: form },
|
|
1492
|
+
set: (field, next) => form.set(field, next),
|
|
1493
|
+
mutate: (ops) => form.mutate(ops),
|
|
1494
|
+
credentials,
|
|
1495
|
+
remote: ctx.remote,
|
|
1496
|
+
locale,
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
ctx.effect(() => ctx.configForms.whileServed([NS], () => {
|
|
1500
|
+
const offs = [
|
|
1501
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
1502
|
+
name: 'settings.section',
|
|
1503
|
+
id: SECTION_ID,
|
|
1504
|
+
order: 26,
|
|
1505
|
+
label,
|
|
1506
|
+
inject: injected,
|
|
1507
|
+
}, ArgoPanel)),
|
|
1508
|
+
ctx.slots.inject('plugins.item', () => ctx.slots.register({
|
|
1509
|
+
name: 'plugins.item',
|
|
1510
|
+
id: SECTION_ID,
|
|
1511
|
+
order: 50,
|
|
1512
|
+
label,
|
|
1513
|
+
inject: injected,
|
|
1514
|
+
}, ArgoPanel)),
|
|
1515
|
+
];
|
|
1516
|
+
return () => {
|
|
1517
|
+
for (const off of offs) if (typeof off === 'function') off();
|
|
1518
|
+
};
|
|
1519
|
+
}), 'argo-dsh: settings page');
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
exports.apply = apply;
|
|
1523
|
+
exports.inject = inject;
|
|
1524
|
+
exports.ArgoPanel = ArgoPanel;
|
|
1525
|
+
// 静态数据导出供漂移门禁核对:TOOLS 必须与 native-tools.mjs(mcp_tools.py
|
|
1526
|
+
// 生成产物)同名同集合,ENGINE_TOGGLES 必须与 index.js 的 ARGO_ENV_SPECS
|
|
1527
|
+
// 同名同集合——两边任何一边增删而另一边没跟上,测试即红。
|
|
1528
|
+
exports.TOOLS = TOOLS;
|
|
1529
|
+
exports.ENGINE_TOGGLES = ENGINE_TOGGLES;
|
|
1530
|
+
|
|
1531
|
+
return module.exports;
|
|
1532
|
+
}
|
|
1533
|
+
});
|