argo-search 2.9.7 → 2.9.9

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 (154) hide show
  1. package/README.en.md +73 -150
  2. package/README.es.md +73 -150
  3. package/README.ja.md +79 -156
  4. package/README.ko.md +78 -155
  5. package/README.md +80 -159
  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 +78 -5
  13. package/bin/argo.js +33 -2
  14. package/config.yaml +269 -63
  15. package/docs/ARGO_INTRO.md +5 -3
  16. package/docs/ENGINE_CATALOG.md +50 -39
  17. package/docs/MCP_SETUP.md +3 -1
  18. package/docs/RELEASE_NOTES_v2.9.8.md +73 -0
  19. package/docs/RELEASE_NOTES_v2.9.9.md +78 -0
  20. package/engines/specs/cfets.yaml +41 -0
  21. package/engines/specs/defillama.yaml +38 -0
  22. package/engines/specs/firecrawl.yaml +1 -1
  23. package/engines/specs/fxtwitter.yaml +5 -0
  24. package/engines/specs/gleif.yaml +55 -0
  25. package/engines/specs/hkex.yaml +40 -0
  26. package/engines/specs/keenable_free.yaml +36 -0
  27. package/engines/specs/shfe.yaml +39 -0
  28. package/engines/specs/stackexchange.yaml +2 -2
  29. package/engines/specs/tavily_free.yaml +56 -0
  30. package/engines/specs/yuanbao.yaml +93 -0
  31. package/mcp/clients.yaml +32 -0
  32. package/package.json +7 -2
  33. package/packages/dsh-plugin/README.md +25 -6
  34. package/packages/dsh-plugin/dsh/client.js +402 -47
  35. package/packages/dsh-plugin/dsh/diagnostics.js +97 -0
  36. package/packages/dsh-plugin/dsh/index.js +135 -1876
  37. package/packages/dsh-plugin/dsh/native-tools.mjs +7 -1
  38. package/packages/dsh-plugin/dsh/runner.js +666 -0
  39. package/packages/dsh-plugin/dsh/wide-research.js +878 -0
  40. package/packages/dsh-plugin/package.json +3 -3
  41. package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
  42. package/packages/dsh-plugin/test/client.test.js +177 -2
  43. package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
  44. package/packages/dsh-plugin/test/index.test.js +44 -0
  45. package/scripts/adaptive.py +126 -43
  46. package/scripts/article_meta.py +131 -0
  47. package/scripts/bench_tail_ab.py +281 -0
  48. package/scripts/bounded_run.py +91 -49
  49. package/scripts/budget.py +247 -0
  50. package/scripts/budget_cli.py +93 -0
  51. package/scripts/build_release_assets.sh +65 -0
  52. package/scripts/cache.py +60 -72
  53. package/scripts/ci_drift_gate.py +90 -0
  54. package/scripts/clock_bridge.py +22 -0
  55. package/scripts/content_boundary.py +46 -0
  56. package/scripts/content_security.py +119 -4
  57. package/scripts/content_signals.py +26 -5
  58. package/scripts/cross_lingual_trace.py +293 -0
  59. package/scripts/dispatch_result.py +105 -0
  60. package/scripts/engine_add.py +602 -0
  61. package/scripts/engine_dispatch.py +58 -86
  62. package/scripts/engine_env.py +5 -0
  63. package/scripts/engine_families.py +6 -0
  64. package/scripts/engine_policy.py +7 -7
  65. package/scripts/engines.py +21 -0
  66. package/scripts/engines_base.py +3 -3
  67. package/scripts/engines_builders.py +12 -0
  68. package/scripts/engines_builders_batch10.py +48 -3
  69. package/scripts/engines_builders_batch14.py +656 -0
  70. package/scripts/engines_builders_batch15.py +249 -0
  71. package/scripts/engines_builders_keyless.py +111 -0
  72. package/scripts/engines_builders_tech.py +0 -1
  73. package/scripts/evidence.py +31 -3
  74. package/scripts/evidence_loop.py +58 -22
  75. package/scripts/fetch_quality.py +13 -0
  76. package/scripts/fetch_v3.py +28 -3
  77. package/scripts/gen_routing_snapshot.py +11 -0
  78. package/scripts/http_client.py +24 -38
  79. package/scripts/install.ps1 +18 -0
  80. package/scripts/install.sh +16 -1
  81. package/scripts/job.py +3 -3
  82. package/scripts/local_seek.py +41 -5
  83. package/scripts/macro_countries.py +15 -0
  84. package/scripts/matrix_search_eval.py +192 -0
  85. package/scripts/mcp_compact.py +234 -0
  86. package/scripts/mcp_handlers.py +60 -642
  87. package/scripts/mcp_launch.sh +10 -8
  88. package/scripts/mcp_local.py +241 -0
  89. package/scripts/mcp_server.py +19 -14
  90. package/scripts/mcp_setup.py +33 -9
  91. package/scripts/mcp_tools.py +1 -0
  92. package/scripts/mcp_transport.py +1 -1
  93. package/scripts/mcp_warm.py +127 -0
  94. package/scripts/net_proxy.py +27 -0
  95. package/scripts/output_transparency.py +200 -0
  96. package/scripts/paid_policy.py +98 -0
  97. package/scripts/quota.py +158 -13
  98. package/scripts/recovery.py +7 -3
  99. package/scripts/recovery_runner.py +150 -0
  100. package/scripts/replay_eval.py +18 -0
  101. package/scripts/research.py +37 -21
  102. package/scripts/research_expand.py +24 -1
  103. package/scripts/retry_policy.py +76 -0
  104. package/scripts/route.py +6 -6
  105. package/scripts/route_cache.py +18 -0
  106. package/scripts/route_combo.py +26 -5
  107. package/scripts/route_domains.py +170 -31
  108. package/scripts/safe_conn.py +52 -0
  109. package/scripts/search.py +15 -27
  110. package/scripts/search_attribution.py +100 -3
  111. package/scripts/search_cli.py +24 -11
  112. package/scripts/search_entry.py +8 -1
  113. package/scripts/search_output.py +99 -15
  114. package/scripts/search_pipeline.py +62 -195
  115. package/scripts/search_rank.py +6 -0
  116. package/scripts/search_types.py +107 -9
  117. package/scripts/semantic_evidence.py +179 -3
  118. package/scripts/serp_guard.py +138 -7
  119. package/scripts/serp_kill_log.py +25 -2
  120. package/scripts/stats_cli.py +13 -1
  121. package/scripts/tail_completion.py +212 -0
  122. package/scripts/url_safety.py +126 -3
  123. package/scripts/usage_log.py +45 -0
  124. package/scripts/wide_research.py +45 -0
  125. package/scripts/zhihu_quota.py +139 -0
  126. package/sub-skills/local-search/bm25.py +27 -3
  127. package/sub-skills/local-search/cli_runner.py +331 -0
  128. package/sub-skills/local-search/parsers.py +328 -0
  129. package/sub-skills/local-search/search_v3.py +31 -674
  130. package/sub-skills/local-seek/scripts/seek_rank.py +7 -3
  131. package/assets/readme/settings-keys.png +0 -0
  132. package/assets/readme/settings-tools.png +0 -0
  133. package/sub-skills/ego-search/references/browser-runtime.md +0 -43
  134. package/sub-skills/ego-search/references/install.md +0 -67
  135. package/sub-skills/ego-search/references/original-ego-upgrade.md +0 -51
  136. package/sub-skills/ego-search/tests/test_query_normalize.py +0 -68
  137. package/sub-skills/js-run/tests/fixtures/challenge_like.js +0 -27
  138. package/sub-skills/js-run/tests/test_jsrun.py +0 -165
  139. package/sub-skills/local-seek/references/auto-behaviors.md +0 -24
  140. package/sub-skills/local-seek/references/fd-mdfind-recipes.md +0 -62
  141. package/sub-skills/local-seek/references/git-integration.md +0 -54
  142. package/sub-skills/local-seek/references/remote-handoff.md +0 -46
  143. package/sub-skills/local-seek/references/rg-recipes.md +0 -96
  144. package/sub-skills/local-seek/references/strategies.md +0 -52
  145. package/sub-skills/local-seek/references/structural-search.md +0 -59
  146. package/sub-skills/local-seek/tests/test_content_rank.py +0 -222
  147. package/sub-skills/local-seek/tests/test_determinism_20261006.py +0 -145
  148. package/sub-skills/local-seek/tests/test_noise_tier.py +0 -101
  149. package/sub-skills/local-seek/tests/test_pcre2_probe.py +0 -136
  150. package/sub-skills/local-seek/tests/test_rank_pinyin.py +0 -77
  151. package/sub-skills/local-seek/tests/test_regression_20261003.py +0 -169
  152. package/sub-skills/local-seek/tests/test_regression_20261006.py +0 -146
  153. package/sub-skills/local-seek/tests/test_seek_degradation.py +0 -111
  154. package/sub-skills/local-seek/tests/test_seek_fts.py +0 -313
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env python3
2
+ """mcp_compact.py — MCP 结果载荷裁剪与紧凑压缩协议。
3
+
4
+ 拆分自 mcp_handlers.py:纯数据处理与结构级截断,无网络 IO 与外部状态。
5
+ 供 MCP 工具执行层(mcp_handlers)、CLI 与外部测试独立复用。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from typing import Any
12
+
13
+
14
+ def _trim_snippet(text: Any, n: int = 120) -> str | None:
15
+ """截断摘要文本,防止超长字符膨胀。"""
16
+ if not text:
17
+ return None
18
+ s = str(text)
19
+ return s if len(s) <= n else s[:n]
20
+
21
+
22
+ def _compact_search_result(result: dict[str, Any], summary: bool = False) -> dict[str, Any]:
23
+ """去掉 MCP 热路径用不到的重字段,缩小首包。"""
24
+ snip_n = 80 if summary else 160
25
+ out: dict[str, Any] = {
26
+ "query": result.get("query"),
27
+ "engine": result.get("engine"),
28
+ "engines_used": result.get("engines_used") or result.get("engines"),
29
+ "count": result.get("count"),
30
+ "elapsed_ms": result.get("elapsed_ms"),
31
+ "cached": result.get("cached"),
32
+ "mode": result.get("mode"),
33
+ "depth": result.get("depth"),
34
+ "execution_tier": result.get("execution_tier"),
35
+ "input_kind": result.get("input_kind"),
36
+ "login_hint": result.get("login_hint"),
37
+ }
38
+ results = []
39
+ for r in result.get("results") or []:
40
+ if not isinstance(r, dict):
41
+ continue
42
+ item = {
43
+ "title": r.get("title"),
44
+ "url": r.get("url"),
45
+ "snippet": _trim_snippet(r.get("snippet"), snip_n),
46
+ "source": r.get("source"),
47
+ "score": r.get("score"),
48
+ }
49
+ # 保留发布时间维度(仅引擎输出该字段时,避免空字段噪音)
50
+ if r.get("published_at"):
51
+ item["published_at"] = r["published_at"]
52
+ # 顶包标记:recovery 替换了引擎原结果时透传,供上层识别结果已被代答
53
+ if r.get("_recovered"):
54
+ item["recovered"] = r["_recovered"]
55
+ # 保留快评,丢掉大块 meta
56
+ for k in ("selection", "absorption", "credibility_fast", "evidence_flags"):
57
+ if k in r:
58
+ item[k] = r[k]
59
+ # 证据完整链路 P0:保留核验信号(仅存在时,避免空字段噪音)
60
+ for k in ("fetch_suggested", "has_fetched_evidence", "post_fetch_absorption"):
61
+ if r.get(k) is not None:
62
+ item[k] = r[k]
63
+ results.append(item)
64
+ out["results"] = results
65
+ # 证据完整链路 P0:顶层门控信号(轻量 4 字段,供 Agent 判断「能否下结论」)
66
+ if result.get("fetch_required") is not None:
67
+ out["fetch_required"] = result["fetch_required"]
68
+ el = result.get("evidence_loop")
69
+ if isinstance(el, dict):
70
+ out["evidence_loop"] = {
71
+ "suggested": el.get("suggested") or [],
72
+ "verified_count": el.get("verified_count") or 0,
73
+ "pending_count": el.get("pending_count") or 0,
74
+ }
75
+ # 引擎可观测性:精简版 engine_outcomes(engine/status/latency,去掉 detail)
76
+ # 让 Agent 能看到「哪个引擎失败、为什么类别」,不占大体积
77
+ outcomes = result.get("engine_outcomes") or []
78
+ if outcomes:
79
+ out["engine_outcomes"] = [
80
+ {
81
+ "engine": o.get("engine"),
82
+ "status": o.get("status"),
83
+ "latency_ms": o.get("latency_ms"),
84
+ "results_count": o.get("results_count"),
85
+ }
86
+ for o in outcomes
87
+ if isinstance(o, dict)
88
+ ][:12]
89
+ wasted = result.get("wasted_engine_ms")
90
+ if wasted:
91
+ out["wasted_engine_ms"] = wasted
92
+ if result.get("early_stopped"):
93
+ out["early_stopped"] = True
94
+ if result.get("minhash_removed"):
95
+ out["minhash_removed"] = result.get("minhash_removed")
96
+ from search_output import decorate_compact_payload as _decorate
97
+ _decorate(out, result)
98
+ if result.get("domain_filter"):
99
+ out["domain_filter"] = result["domain_filter"]
100
+ if result.get("rank_method"):
101
+ out["rank_method"] = result.get("rank_method")
102
+ if result.get("reranker"):
103
+ out["reranker"] = result.get("reranker")
104
+ lp = result.get("lang_pref")
105
+ if isinstance(lp, dict):
106
+ out["lang_pref"] = {
107
+ "query_lang": lp.get("query_lang"),
108
+ "system_lang": lp.get("system_lang"),
109
+ "habit_lang": lp.get("habit_lang"),
110
+ "prefer_langs": lp.get("prefer_langs"),
111
+ "engine_lang": lp.get("engine_lang"),
112
+ }
113
+ rec = result.get("recovery")
114
+ if isinstance(rec, dict) and rec.get("triggered"):
115
+ out["recovery"] = {
116
+ "recovered": rec.get("recovered"),
117
+ "level_used": rec.get("level_used"),
118
+ "strategy_used": rec.get("strategy_used"),
119
+ "note": rec.get("note"),
120
+ }
121
+ return out
122
+
123
+
124
+ def _compact_research_result(report: dict[str, Any], summary: bool = False) -> dict[str, Any]:
125
+ """研究包:只保留 Agent 写作需要的字段,砍掉子查询原始 dumps。"""
126
+ snip_n = 100 if summary else 160
127
+ keys = (
128
+ "query", "query_original", "execution_tier", "topic_profile", "topic_profile_key",
129
+ "discipline", "quality_gates", "report_sections", "source_grades", "disclaimer",
130
+ "academic_discipline", "engines_used", "source_distribution", "elapsed_ms",
131
+ "sub_query_count", "total_sources", "gaps", "mode", "sub_queries",
132
+ "engines_priority", "vertical_engines", "fetch_required",
133
+ "kind", "conclusion_cap", "quality_gate_results", "work_packages",
134
+ "work_package_stages", "protocol", "cross_lingual_trace",
135
+ )
136
+ out: dict[str, Any] = {k: report[k] for k in keys if k in report and report[k] is not None}
137
+
138
+ # 证据完整链路 P0:研究包门控(pending_fetch 截断到 5 条,保留可编程核验信号)
139
+ el = report.get("evidence_loop")
140
+ if isinstance(el, dict):
141
+ out["evidence_loop"] = {
142
+ "high_consequence_domain": el.get("high_consequence_domain"),
143
+ "pending_fetch": (el.get("pending_fetch") or [])[:5],
144
+ "verified_count": el.get("verified_count") or 0,
145
+ "pending_count": el.get("pending_count") or 0,
146
+ }
147
+ if isinstance(report.get("verify"), dict):
148
+ v = report["verify"]
149
+ out["verify"] = {
150
+ "revision_summary": v.get("revision_summary") or {},
151
+ "pending": (v.get("pending") or [])[:5],
152
+ }
153
+
154
+ findings = []
155
+ for kf in report.get("key_findings") or []:
156
+ if not isinstance(kf, dict):
157
+ continue
158
+ top = dict(kf.get("top_result") or {})
159
+ if top.get("snippet"):
160
+ top["snippet"] = _trim_snippet(top.get("snippet"), snip_n)
161
+ findings.append({
162
+ "aspect": kf.get("aspect"),
163
+ "strategy": kf.get("strategy"),
164
+ "top_result": top,
165
+ "result_count": kf.get("result_count"),
166
+ "citation_refs": kf.get("citation_refs"),
167
+ })
168
+ out["key_findings"] = findings
169
+
170
+ sources = []
171
+ for s in report.get("sources") or report.get("citations") or []:
172
+ if not isinstance(s, dict) or not s.get("url"):
173
+ continue
174
+ sources.append({
175
+ "ref": s.get("ref") or s.get("id"),
176
+ "title": s.get("title"),
177
+ "url": s.get("url"),
178
+ "engine": s.get("engine") or s.get("source"),
179
+ "snippet": _trim_snippet(s.get("snippet"), snip_n),
180
+ })
181
+ if summary and len(sources) >= 10:
182
+ break
183
+ out["sources"] = sources
184
+ if not summary:
185
+ out["citations"] = report.get("citations") or sources
186
+ # 交叉引用截断
187
+ crs = report.get("cross_references") or []
188
+ out["cross_references"] = crs[:5]
189
+ # 覆盖度 / 验证记录 / 盲区(科研方法论增强)
190
+ if report.get("coverage_map"):
191
+ out["coverage_map"] = report["coverage_map"]
192
+ if report.get("verification_records"):
193
+ out["verification_records"] = report["verification_records"][:12]
194
+ if report.get("blind_spots"):
195
+ out["blind_spots"] = report["blind_spots"][:8]
196
+ return out
197
+
198
+
199
+ def _cap_extract_output(output: dict[str, Any]) -> dict[str, Any]:
200
+ """argo_fetch mode=extract 的输出上限(防大页表格撑爆上下文)。
201
+
202
+ 结构级裁剪保持 JSON 可解析:表格数量/单元格、metadata 值、jsonld
203
+ 条目体积分别设上限,超限截断并打标记。
204
+ """
205
+ tables = output.get("tables")
206
+ if isinstance(tables, list):
207
+ if len(tables) > 12:
208
+ output["tables"] = tables[:12]
209
+ output["tables_truncated"] = True
210
+ for t in output["tables"]:
211
+ if isinstance(t, list):
212
+ for i, row in enumerate(t):
213
+ if isinstance(row, list):
214
+ t[i] = [str(c)[:300] if c is not None else "" for c in row]
215
+ meta = output.get("metadata")
216
+ if isinstance(meta, dict):
217
+ output["metadata"] = {k: str(v)[:500] for k, v in meta.items()}
218
+ jsonld = output.get("jsonld")
219
+ if isinstance(jsonld, list):
220
+ capped = []
221
+ for entry in jsonld:
222
+ try:
223
+ text = json.dumps(entry, ensure_ascii=False)
224
+ except (TypeError, ValueError):
225
+ continue
226
+ if len(text) > 4000:
227
+ capped.append({"_clipped": True, "preview": text[:4000]})
228
+ else:
229
+ capped.append(entry)
230
+ if len(capped) > 8:
231
+ capped = capped[:8]
232
+ output["jsonld_truncated"] = True
233
+ output["jsonld"] = capped
234
+ return output