argo-search 2.9.7 → 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.
Files changed (140) hide show
  1. package/README.en.md +58 -145
  2. package/README.es.md +58 -145
  3. package/README.ja.md +64 -151
  4. package/README.ko.md +63 -150
  5. package/README.md +65 -154
  6. package/SKILL.md +3 -3
  7. package/assets/readme/hero.svg +2 -2
  8. package/backends/domain_profiles.json +48 -0
  9. package/backends/engine_registry.yaml +118 -4
  10. package/backends/quota_profiles.json +92 -12
  11. package/backends/source_types_cn.json +27 -1
  12. package/bin/argo +74 -5
  13. package/bin/argo.js +33 -2
  14. package/config.yaml +219 -60
  15. package/docs/ENGINE_CATALOG.md +50 -39
  16. package/docs/MCP_SETUP.md +3 -1
  17. package/docs/RELEASE_NOTES_v2.9.8.md +73 -0
  18. package/engines/specs/cfets.yaml +41 -0
  19. package/engines/specs/defillama.yaml +38 -0
  20. package/engines/specs/firecrawl.yaml +1 -1
  21. package/engines/specs/fxtwitter.yaml +5 -0
  22. package/engines/specs/gleif.yaml +55 -0
  23. package/engines/specs/hkex.yaml +40 -0
  24. package/engines/specs/keenable_free.yaml +36 -0
  25. package/engines/specs/shfe.yaml +39 -0
  26. package/engines/specs/stackexchange.yaml +2 -2
  27. package/engines/specs/tavily_free.yaml +56 -0
  28. package/engines/specs/yuanbao.yaml +93 -0
  29. package/mcp/clients.yaml +32 -0
  30. package/package.json +6 -2
  31. package/packages/dsh-plugin/README.md +23 -5
  32. package/packages/dsh-plugin/dsh/client.js +304 -15
  33. package/packages/dsh-plugin/dsh/diagnostics.js +54 -0
  34. package/packages/dsh-plugin/dsh/index.js +112 -1875
  35. package/packages/dsh-plugin/dsh/native-tools.mjs +7 -1
  36. package/packages/dsh-plugin/dsh/runner.js +666 -0
  37. package/packages/dsh-plugin/dsh/wide-research.js +878 -0
  38. package/packages/dsh-plugin/package.json +2 -2
  39. package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
  40. package/packages/dsh-plugin/test/client.test.js +175 -1
  41. package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
  42. package/packages/dsh-plugin/test/index.test.js +44 -0
  43. package/scripts/adaptive.py +24 -1
  44. package/scripts/article_meta.py +131 -0
  45. package/scripts/budget.py +247 -0
  46. package/scripts/budget_cli.py +93 -0
  47. package/scripts/build_release_assets.sh +65 -0
  48. package/scripts/cache.py +60 -72
  49. package/scripts/ci_drift_gate.py +90 -0
  50. package/scripts/content_boundary.py +46 -0
  51. package/scripts/content_signals.py +26 -5
  52. package/scripts/cross_lingual_trace.py +293 -0
  53. package/scripts/dispatch_result.py +98 -0
  54. package/scripts/engine_dispatch.py +49 -86
  55. package/scripts/engine_env.py +5 -0
  56. package/scripts/engine_families.py +6 -0
  57. package/scripts/engine_policy.py +7 -7
  58. package/scripts/engines.py +21 -0
  59. package/scripts/engines_base.py +3 -3
  60. package/scripts/engines_builders.py +12 -0
  61. package/scripts/engines_builders_batch10.py +48 -3
  62. package/scripts/engines_builders_batch14.py +656 -0
  63. package/scripts/engines_builders_batch15.py +249 -0
  64. package/scripts/engines_builders_keyless.py +111 -0
  65. package/scripts/engines_builders_tech.py +0 -1
  66. package/scripts/evidence.py +31 -3
  67. package/scripts/evidence_loop.py +54 -22
  68. package/scripts/fetch_v3.py +25 -0
  69. package/scripts/gen_routing_snapshot.py +11 -0
  70. package/scripts/install.ps1 +18 -0
  71. package/scripts/install.sh +16 -1
  72. package/scripts/job.py +3 -3
  73. package/scripts/local_seek.py +41 -5
  74. package/scripts/macro_countries.py +15 -0
  75. package/scripts/matrix_search_eval.py +82 -0
  76. package/scripts/mcp_compact.py +234 -0
  77. package/scripts/mcp_handlers.py +60 -642
  78. package/scripts/mcp_launch.sh +10 -8
  79. package/scripts/mcp_local.py +241 -0
  80. package/scripts/mcp_server.py +19 -14
  81. package/scripts/mcp_setup.py +33 -9
  82. package/scripts/mcp_tools.py +1 -0
  83. package/scripts/mcp_transport.py +1 -1
  84. package/scripts/mcp_warm.py +127 -0
  85. package/scripts/output_transparency.py +200 -0
  86. package/scripts/paid_policy.py +98 -0
  87. package/scripts/quota.py +158 -13
  88. package/scripts/recovery_runner.py +150 -0
  89. package/scripts/replay_eval.py +18 -0
  90. package/scripts/research.py +37 -21
  91. package/scripts/research_expand.py +24 -1
  92. package/scripts/retry_policy.py +76 -0
  93. package/scripts/route.py +6 -6
  94. package/scripts/route_cache.py +18 -0
  95. package/scripts/route_combo.py +7 -0
  96. package/scripts/route_domains.py +32 -19
  97. package/scripts/search.py +15 -27
  98. package/scripts/search_attribution.py +100 -3
  99. package/scripts/search_cli.py +24 -11
  100. package/scripts/search_entry.py +7 -1
  101. package/scripts/search_output.py +99 -15
  102. package/scripts/search_pipeline.py +42 -193
  103. package/scripts/search_types.py +107 -9
  104. package/scripts/semantic_evidence.py +179 -3
  105. package/scripts/serp_guard.py +115 -7
  106. package/scripts/serp_kill_log.py +25 -2
  107. package/scripts/stats_cli.py +13 -1
  108. package/scripts/wide_research.py +45 -0
  109. package/scripts/zhihu_quota.py +139 -0
  110. package/sub-skills/local-search/bm25.py +27 -3
  111. package/sub-skills/local-search/cli_runner.py +331 -0
  112. package/sub-skills/local-search/parsers.py +328 -0
  113. package/sub-skills/local-search/search_v3.py +31 -674
  114. package/sub-skills/local-seek/scripts/seek_rank.py +7 -3
  115. package/assets/readme/settings-keys.png +0 -0
  116. package/assets/readme/settings-tools.png +0 -0
  117. package/scripts/image_vision/vision_probe +0 -0
  118. package/scripts/local_engines.py +0 -321
  119. package/sub-skills/ego-search/references/browser-runtime.md +0 -43
  120. package/sub-skills/ego-search/references/install.md +0 -67
  121. package/sub-skills/ego-search/references/original-ego-upgrade.md +0 -51
  122. package/sub-skills/ego-search/tests/test_query_normalize.py +0 -68
  123. package/sub-skills/js-run/tests/fixtures/challenge_like.js +0 -27
  124. package/sub-skills/js-run/tests/test_jsrun.py +0 -165
  125. package/sub-skills/local-seek/references/auto-behaviors.md +0 -24
  126. package/sub-skills/local-seek/references/fd-mdfind-recipes.md +0 -62
  127. package/sub-skills/local-seek/references/git-integration.md +0 -54
  128. package/sub-skills/local-seek/references/remote-handoff.md +0 -46
  129. package/sub-skills/local-seek/references/rg-recipes.md +0 -96
  130. package/sub-skills/local-seek/references/strategies.md +0 -52
  131. package/sub-skills/local-seek/references/structural-search.md +0 -59
  132. package/sub-skills/local-seek/tests/test_content_rank.py +0 -222
  133. package/sub-skills/local-seek/tests/test_determinism_20261006.py +0 -145
  134. package/sub-skills/local-seek/tests/test_noise_tier.py +0 -101
  135. package/sub-skills/local-seek/tests/test_pcre2_probe.py +0 -136
  136. package/sub-skills/local-seek/tests/test_rank_pinyin.py +0 -77
  137. package/sub-skills/local-seek/tests/test_regression_20261003.py +0 -169
  138. package/sub-skills/local-seek/tests/test_regression_20261006.py +0 -146
  139. package/sub-skills/local-seek/tests/test_seek_degradation.py +0 -111
  140. package/sub-skills/local-seek/tests/test_seek_fts.py +0 -313
@@ -46,6 +46,26 @@ window.__ModuleLoader__.load({
46
46
  tabKeys: '密钥',
47
47
  tabResearch: '研究',
48
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: '输入关键词快速筛选...',
49
69
  summary: 'Argo · web_search {search} · web_fetch {fetch} · 原生工具 {tools} 个 · 研究 {workers}/{max}',
50
70
  accessTitle: '接入方式',
51
71
  searchSeam: '宿主 web_search 走 argo',
@@ -109,6 +129,8 @@ window.__ModuleLoader__.load({
109
129
  groupNet: '网络出口',
110
130
  groupLocal: '本机检索',
111
131
  groupMisc: '其他',
132
+ groupPaid: '额度与预算',
133
+ groupPaidHint: '免费与付费源统一管理:金额计费的源设月度预算,按次数限额的源设日/月次数上限;0=不限制。额度或预算用尽后该源自动跳过并回落免费源。改动对下一次新建连接生效。',
112
134
  envDefault: '默认',
113
135
  envRange: '需在 {min}–{max} 之间',
114
136
  keySourceFile: '凭证库',
@@ -139,6 +161,26 @@ window.__ModuleLoader__.load({
139
161
  tabKeys: 'Keys',
140
162
  tabResearch: 'Research',
141
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...',
142
184
  summary: 'Argo · web_search {search} · web_fetch {fetch} · {tools} native tools · research {workers}/{max}',
143
185
  accessTitle: 'Access',
144
186
  searchSeam: 'Route host web_search to argo',
@@ -202,6 +244,8 @@ window.__ModuleLoader__.load({
202
244
  groupNet: 'Network',
203
245
  groupLocal: 'Local search',
204
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.',
205
249
  envDefault: 'default',
206
250
  envRange: 'must be between {min} and {max}',
207
251
  keySourceFile: 'credential store',
@@ -238,6 +282,7 @@ window.__ModuleLoader__.load({
238
282
 
239
283
  const KEYS = [
240
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' },
241
286
  { ref: 'TAVILY_API_KEY', common: true, zh: 'Tavily', en: 'Tavily' },
242
287
  { ref: 'EXA_API_KEY', common: true, zh: 'Exa', en: 'Exa' },
243
288
  { ref: 'BOCHA_API_KEY', common: true, zh: '博查', en: 'Bocha' },
@@ -261,12 +306,25 @@ window.__ModuleLoader__.load({
261
306
  { ref: 'UNPAYWALL_EMAIL', zh: 'Unpaywall 邮箱(非密钥)', en: 'Unpaywall email (not secret)' },
262
307
  ];
263
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
+
264
321
  // argo 引擎开关(argoEnv 的 KEY=VALUE 行)。与 index.js 的 ARGO_ENV_SPECS
265
322
  // 同一份名单,由 test/client.test.js 的漂移门禁核对;kind 决定控件:
266
- // bool=开关(invert 语义反转,如 NO_CACHE 是「关缓存」)、num=数字、
267
- // str=文本、list=名单。def 仅作「未配置时显示什么」,缺省即不写该行。
323
+ // bool=开关、num=数字、str=文本、list=名单。def 仅作「未配置时显示什么」,
324
+ // 缺省即不写该行。开关文案写正向行为或明确的关闭动作,不再用 invert 反转
325
+ // (历史 bug 见 index.js 注释)。
268
326
  const ENGINE_TOGGLES = [
269
- { name: 'ARGO_NO_CACHE', kind: 'bool', invert: true, group: 'cache', def: false, zh: '关闭搜索结果缓存', en: 'Disable result cache', hintZh: 'argo 默认缓存 1 小时;调试期关掉缓存看实时结果', hintEn: 'argo caches results for 1h by default; turn off to see live results' },
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' },
270
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' },
271
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' },
272
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' },
@@ -289,7 +347,14 @@ window.__ModuleLoader__.load({
289
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' },
290
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' },
291
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' },
292
- { name: 'ARGO_NO_PREWARM', kind: 'bool', invert: true, group: 'misc', def: false, zh: '关闭连接预热', en: 'Disable prewarm', hintZh: 'MCP 握手后预热引擎注册表,默认开', hintEn: 'Prewarm the engine registry after handshake, on by default' },
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' },
293
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' },
294
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' },
295
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' },
@@ -391,6 +456,43 @@ window.__ModuleLoader__.load({
391
456
  actions: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },
392
457
  switchWrap: { display: 'flex', alignItems: 'center', gap: 8 },
393
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
+ },
394
496
  };
395
497
 
396
498
  function asString(value, fallback) {
@@ -499,7 +601,7 @@ window.__ModuleLoader__.load({
499
601
  const initialLang = locale && locale.getSnapshot && locale.getSnapshot().active === 'en' ? 'en' : 'zh';
500
602
  const [lang, setLang] = useState(initialLang);
501
603
 
502
- const [tab, setTab] = useState('access');
604
+ const [tab, setTab] = useState('dashboard');
503
605
  const [draft, setDraft] = useState(() => toDraft(value));
504
606
  const [busy, setBusy] = useState('');
505
607
  const [message, setMessage] = useState('');
@@ -513,6 +615,8 @@ window.__ModuleLoader__.load({
513
615
  // argoEnv 引擎开关:bool 类即时写;num/str/list 类走草稿 + 分组保存。
514
616
  const [envDraft, setEnvDraft] = useState({});
515
617
  const [envBusy, setEnvBusy] = useState('');
618
+ const [engineFilter, setEngineFilter] = useState('');
619
+ const [keyFilter, setKeyFilter] = useState('');
516
620
 
517
621
  const dirty = useRef(new Set());
518
622
  // 读-改-写共享配置字段(keyRefs / nativeTools / argoEnv)时读实时快照,
@@ -737,8 +841,9 @@ window.__ModuleLoader__.load({
737
841
  const ENV_FALSY = ['', '0', 'false', 'no', 'off', 'n', 'disable', 'disabled', 'none'];
738
842
  const envBoolState = (spec, map) => {
739
843
  const raw = map[spec.name];
740
- const on = raw === undefined ? spec.def === true : !ENV_FALSY.includes(String(raw).trim().toLowerCase());
741
- return spec.invert ? !on : on;
844
+ return raw === undefined
845
+ ? spec.def === true
846
+ : !ENV_FALSY.includes(String(raw).trim().toLowerCase());
742
847
  };
743
848
  const writeEnvLines = async (updates) => {
744
849
  const map = envMap();
@@ -754,9 +859,8 @@ window.__ModuleLoader__.load({
754
859
  };
755
860
  const toggleEnv = (spec, on) => {
756
861
  if (disabled) return;
757
- // invert 语义反转:UI「开」对 invert 开关意味着 env 取 falsy
758
- // (缓存开 → ARGO_NO_CACHE=0;关 → =1)。
759
- const value = on === (spec.invert === true) ? '0' : '1';
862
+ // 开关状态直接落 0/1:文案与 env 同向(开=1,关=0),不做反转。
863
+ const value = on ? '1' : '0';
760
864
  void writeEnvLines({ [spec.name]: value })
761
865
  .then((ok) => report(ok, ok ? t('saved') : t('refused')))
762
866
  .catch((cause) => report(false, cause && cause.message ? cause.message : String(cause)));
@@ -795,6 +899,54 @@ window.__ModuleLoader__.load({
795
899
  }
796
900
  };
797
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
+
798
950
  if (props.view === 'summary') {
799
951
  const summary = t('summary')
800
952
  .replace('{search}', value.searchProviderEnabled !== false ? t('on') : t('off'))
@@ -913,6 +1065,95 @@ window.__ModuleLoader__.load({
913
1065
 
914
1066
  const panel = [];
915
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
+
916
1157
  if (tab === 'access') {
917
1158
  panel.push(h(Card, { key: 'seam', title: t('accessTitle') }, [
918
1159
  h(Switch, {
@@ -1007,15 +1248,50 @@ window.__ModuleLoader__.load({
1007
1248
 
1008
1249
  if (tab === 'engines') {
1009
1250
  const map = envMap();
1010
- const groups = ['cache', 'fetch', 'semantic', 'engines', 'net', 'local', 'misc'];
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'];
1011
1262
  const groupTitle = (group) => t('group' + group.charAt(0).toUpperCase() + group.slice(1));
1012
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();
1013
1273
  for (const group of groups) {
1014
- const specs = ENGINE_TOGGLES.filter((spec) => spec.group === group);
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
+ }
1015
1288
  if (specs.length === 0) continue;
1016
1289
  const bools = specs.filter((spec) => spec.kind === 'bool');
1017
1290
  const fields = specs.filter((spec) => spec.kind !== 'bool');
1018
1291
  const children = [];
1292
+ if (group === 'paid') {
1293
+ children.push(h('p', { key: 'groupHint', style: S.hint }, t('groupPaidHint')));
1294
+ }
1019
1295
  for (const spec of bools) {
1020
1296
  // 未配置时显示 argo 默认态并标注「默认」,配置过的显示自定义值,
1021
1297
  // 让「这一行到底生不生效」在面板上一眼可辨。
@@ -1078,10 +1354,22 @@ window.__ModuleLoader__.load({
1078
1354
  }
1079
1355
 
1080
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;
1081
1361
  panel.push(h(Card, { key: 'keys', title: t('keysTitle'), hint: t('keysHint') }, [
1082
- h('span', { key: 'l', style: S.label }, t('commonKeys')),
1083
- ...visibleKeys.map(keyRow),
1084
- h('div', { key: 'a', style: S.actions }, [
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 }, [
1085
1373
  h('button', {
1086
1374
  key: 'more',
1087
1375
  type: 'button',
@@ -1178,6 +1466,7 @@ window.__ModuleLoader__.load({
1178
1466
  h('p', { key: 'i', style: S.hint }, t('intro')),
1179
1467
  ]),
1180
1468
  h('div', { key: 'tabs', style: S.tabs }, [
1469
+ tabButton('dashboard', t('tabDashboard')),
1181
1470
  tabButton('access', t('tabAccess')),
1182
1471
  tabButton('tools', t('tabTools')),
1183
1472
  tabButton('engines', t('tabEngines')),
@@ -0,0 +1,54 @@
1
+ /*
2
+ * diagnostics.js — argo-dsh 运行时监控与连接池只读诊断
3
+ *
4
+ * 为设置面板、外部插件或排查脚本提供只读状态自省能力,
5
+ * 避免状态探查逻辑与核心检索逻辑耦合。
6
+ */
7
+
8
+ /**
9
+ * 获取当前常驻 MCP 连接池的运行时指标。
10
+ *
11
+ * @param {Array} pool - MCP 连接池实例数组
12
+ * @returns {Object} 包含连接数、当前负载以及各连接健康状态的快照
13
+ */
14
+ export function getMcpPoolStats(pool = []) {
15
+ const connections = pool.map((conn, index) => ({
16
+ index,
17
+ load: typeof conn.load === 'function' ? conn.load() : 0,
18
+ hasProcess: Boolean(conn.proc ?? true),
19
+ }))
20
+
21
+ const totalLoad = connections.reduce((sum, item) => sum + item.load, 0)
22
+
23
+ return {
24
+ poolSize: pool.length,
25
+ activeConnections: pool.filter(conn => typeof conn.load === 'function' && conn.load() > 0).length,
26
+ totalLoad,
27
+ connections,
28
+ timestamp: Date.now(),
29
+ }
30
+ }
31
+
32
+ /**
33
+ * 诊断并重置 MCP 连接池。
34
+ *
35
+ * @param {Array} pool - MCP 连接池实例数组
36
+ * @param {Function} disposeFn - 释放函数
37
+ * @returns {Object} 重置结果摘要
38
+ */
39
+ export function resetMcpPool(pool = [], disposeFn) {
40
+ const beforeCount = pool.length
41
+ if (typeof disposeFn === 'function') {
42
+ disposeFn()
43
+ } else {
44
+ for (const conn of [...pool]) {
45
+ try { conn.close?.() } catch { /* ignore */ }
46
+ }
47
+ pool.length = 0
48
+ }
49
+ return {
50
+ evictedCount: beforeCount,
51
+ poolSizeNow: pool.length,
52
+ success: true,
53
+ }
54
+ }