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.
- package/README.en.md +73 -150
- package/README.es.md +73 -150
- package/README.ja.md +79 -156
- package/README.ko.md +78 -155
- package/README.md +80 -159
- package/SKILL.md +3 -3
- package/assets/readme/hero.svg +2 -2
- package/backends/domain_profiles.json +48 -0
- package/backends/engine_registry.yaml +118 -4
- package/backends/quota_profiles.json +92 -12
- package/backends/source_types_cn.json +27 -1
- package/bin/argo +78 -5
- package/bin/argo.js +33 -2
- package/config.yaml +269 -63
- package/docs/ARGO_INTRO.md +5 -3
- package/docs/ENGINE_CATALOG.md +50 -39
- package/docs/MCP_SETUP.md +3 -1
- package/docs/RELEASE_NOTES_v2.9.8.md +73 -0
- package/docs/RELEASE_NOTES_v2.9.9.md +78 -0
- package/engines/specs/cfets.yaml +41 -0
- package/engines/specs/defillama.yaml +38 -0
- package/engines/specs/firecrawl.yaml +1 -1
- package/engines/specs/fxtwitter.yaml +5 -0
- package/engines/specs/gleif.yaml +55 -0
- package/engines/specs/hkex.yaml +40 -0
- package/engines/specs/keenable_free.yaml +36 -0
- package/engines/specs/shfe.yaml +39 -0
- package/engines/specs/stackexchange.yaml +2 -2
- package/engines/specs/tavily_free.yaml +56 -0
- package/engines/specs/yuanbao.yaml +93 -0
- package/mcp/clients.yaml +32 -0
- package/package.json +7 -2
- package/packages/dsh-plugin/README.md +25 -6
- package/packages/dsh-plugin/dsh/client.js +402 -47
- package/packages/dsh-plugin/dsh/diagnostics.js +97 -0
- package/packages/dsh-plugin/dsh/index.js +135 -1876
- package/packages/dsh-plugin/dsh/native-tools.mjs +7 -1
- 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 +3 -3
- package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
- package/packages/dsh-plugin/test/client.test.js +177 -2
- package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
- package/packages/dsh-plugin/test/index.test.js +44 -0
- package/scripts/adaptive.py +126 -43
- package/scripts/article_meta.py +131 -0
- package/scripts/bench_tail_ab.py +281 -0
- package/scripts/bounded_run.py +91 -49
- package/scripts/budget.py +247 -0
- package/scripts/budget_cli.py +93 -0
- package/scripts/build_release_assets.sh +65 -0
- package/scripts/cache.py +60 -72
- package/scripts/ci_drift_gate.py +90 -0
- package/scripts/clock_bridge.py +22 -0
- package/scripts/content_boundary.py +46 -0
- package/scripts/content_security.py +119 -4
- package/scripts/content_signals.py +26 -5
- package/scripts/cross_lingual_trace.py +293 -0
- package/scripts/dispatch_result.py +105 -0
- package/scripts/engine_add.py +602 -0
- package/scripts/engine_dispatch.py +58 -86
- package/scripts/engine_env.py +5 -0
- package/scripts/engine_families.py +6 -0
- package/scripts/engine_policy.py +7 -7
- package/scripts/engines.py +21 -0
- package/scripts/engines_base.py +3 -3
- package/scripts/engines_builders.py +12 -0
- package/scripts/engines_builders_batch10.py +48 -3
- package/scripts/engines_builders_batch14.py +656 -0
- package/scripts/engines_builders_batch15.py +249 -0
- package/scripts/engines_builders_keyless.py +111 -0
- package/scripts/engines_builders_tech.py +0 -1
- package/scripts/evidence.py +31 -3
- package/scripts/evidence_loop.py +58 -22
- package/scripts/fetch_quality.py +13 -0
- package/scripts/fetch_v3.py +28 -3
- package/scripts/gen_routing_snapshot.py +11 -0
- package/scripts/http_client.py +24 -38
- package/scripts/install.ps1 +18 -0
- package/scripts/install.sh +16 -1
- package/scripts/job.py +3 -3
- package/scripts/local_seek.py +41 -5
- package/scripts/macro_countries.py +15 -0
- package/scripts/matrix_search_eval.py +192 -0
- package/scripts/mcp_compact.py +234 -0
- package/scripts/mcp_handlers.py +60 -642
- package/scripts/mcp_launch.sh +10 -8
- package/scripts/mcp_local.py +241 -0
- package/scripts/mcp_server.py +19 -14
- package/scripts/mcp_setup.py +33 -9
- package/scripts/mcp_tools.py +1 -0
- package/scripts/mcp_transport.py +1 -1
- package/scripts/mcp_warm.py +127 -0
- package/scripts/net_proxy.py +27 -0
- package/scripts/output_transparency.py +200 -0
- package/scripts/paid_policy.py +98 -0
- package/scripts/quota.py +158 -13
- package/scripts/recovery.py +7 -3
- package/scripts/recovery_runner.py +150 -0
- package/scripts/replay_eval.py +18 -0
- package/scripts/research.py +37 -21
- package/scripts/research_expand.py +24 -1
- package/scripts/retry_policy.py +76 -0
- package/scripts/route.py +6 -6
- package/scripts/route_cache.py +18 -0
- package/scripts/route_combo.py +26 -5
- package/scripts/route_domains.py +170 -31
- package/scripts/safe_conn.py +52 -0
- package/scripts/search.py +15 -27
- package/scripts/search_attribution.py +100 -3
- package/scripts/search_cli.py +24 -11
- package/scripts/search_entry.py +8 -1
- package/scripts/search_output.py +99 -15
- package/scripts/search_pipeline.py +62 -195
- package/scripts/search_rank.py +6 -0
- package/scripts/search_types.py +107 -9
- package/scripts/semantic_evidence.py +179 -3
- package/scripts/serp_guard.py +138 -7
- package/scripts/serp_kill_log.py +25 -2
- package/scripts/stats_cli.py +13 -1
- package/scripts/tail_completion.py +212 -0
- package/scripts/url_safety.py +126 -3
- package/scripts/usage_log.py +45 -0
- package/scripts/wide_research.py +45 -0
- package/scripts/zhihu_quota.py +139 -0
- package/sub-skills/local-search/bm25.py +27 -3
- package/sub-skills/local-search/cli_runner.py +331 -0
- package/sub-skills/local-search/parsers.py +328 -0
- package/sub-skills/local-search/search_v3.py +31 -674
- package/sub-skills/local-seek/scripts/seek_rank.py +7 -3
- package/assets/readme/settings-keys.png +0 -0
- package/assets/readme/settings-tools.png +0 -0
- package/sub-skills/ego-search/references/browser-runtime.md +0 -43
- package/sub-skills/ego-search/references/install.md +0 -67
- package/sub-skills/ego-search/references/original-ego-upgrade.md +0 -51
- package/sub-skills/ego-search/tests/test_query_normalize.py +0 -68
- package/sub-skills/js-run/tests/fixtures/challenge_like.js +0 -27
- package/sub-skills/js-run/tests/test_jsrun.py +0 -165
- package/sub-skills/local-seek/references/auto-behaviors.md +0 -24
- package/sub-skills/local-seek/references/fd-mdfind-recipes.md +0 -62
- package/sub-skills/local-seek/references/git-integration.md +0 -54
- package/sub-skills/local-seek/references/remote-handoff.md +0 -46
- package/sub-skills/local-seek/references/rg-recipes.md +0 -96
- package/sub-skills/local-seek/references/strategies.md +0 -52
- package/sub-skills/local-seek/references/structural-search.md +0 -59
- package/sub-skills/local-seek/tests/test_content_rank.py +0 -222
- package/sub-skills/local-seek/tests/test_determinism_20261006.py +0 -145
- package/sub-skills/local-seek/tests/test_noise_tier.py +0 -101
- package/sub-skills/local-seek/tests/test_pcre2_probe.py +0 -136
- package/sub-skills/local-seek/tests/test_rank_pinyin.py +0 -77
- package/sub-skills/local-seek/tests/test_regression_20261003.py +0 -169
- package/sub-skills/local-seek/tests/test_regression_20261006.py +0 -146
- package/sub-skills/local-seek/tests/test_seek_degradation.py +0 -111
- 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
|