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
@@ -82,6 +82,7 @@ _TOPIC_PRIOR_WEIGHT = 1.2
82
82
  # 量级与一个中频词元命中相当,够翻二元组的歧义、翻不了多词全覆盖行。
83
83
  _PHRASE_BONUS = 3.0
84
84
  _PHRASE_BONUS_CAP = 6.0
85
+ _NAME_PHRASE_BONUS = 4.0 # 文件名直接命中查询短语时的定位加成
85
86
 
86
87
 
87
88
  def _split_camel(run: str) -> list[str]:
@@ -178,9 +179,12 @@ def rank_rows(rows: list[tuple[str, int, str]], raw_query: str,
178
179
  total += w * qtf * (sat + _DELTA) * idf
179
180
  total += _TOPIC_PRIOR_WEIGHT * math.log1p(file_hits[rows[i][0]])
180
181
  if phrases:
181
- low = (rows[i][2] or "").lower()
182
- hits = sum(1 for ph in phrases if ph in low)
183
- total += min(hits, 2) * _PHRASE_BONUS
182
+ low_line = (rows[i][2] or "").lower()
183
+ fp_low = (rows[i][0] or "").lower()
184
+ name_low = fp_low.rpartition("/")[2] or fp_low
185
+ name_hits = sum(1 for ph in phrases if ph in name_low)
186
+ line_hits = sum(1 for ph in phrases if ph in low_line)
187
+ total += min(name_hits, 2) * _NAME_PHRASE_BONUS + min(line_hits, 2) * _PHRASE_BONUS
184
188
  if boost_fn is not None:
185
189
  try:
186
190
  total += boost_fn(rows[i][0])
Binary file
Binary file
Binary file
@@ -1,321 +0,0 @@
1
- #!/usr/bin/env python3
2
- """本地个性化引擎插件 — 不随公开仓库提交。
3
-
4
- 包含漏洞查询类引擎(OSV.dev + CISA KEV),因启发式杀毒检测
5
- (HEUR:HackTool/VulnScan)从公开构建器中移出。
6
- 本地文件,不提交 git;删除后对应引擎自动消失,不影响其他功能。
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import json
12
- import re
13
- import time
14
- import urllib.request
15
- from typing import Any
16
-
17
- from engines_base import safe_search, rank_score, http_open, note_failure
18
-
19
-
20
- # ── OSV.dev 开源漏洞库(免 key,必须 POST)─────────────────────────────────
21
-
22
- _OSV_ECOSYSTEM_PREFIX: dict[str, str] = {
23
- "pypi": "PyPI", "npm": "npm", "go": "Go", "crates": "crates.io",
24
- "cargo": "crates.io", "maven": "Maven", "nuget": "NuGet",
25
- "packagist": "Packagist", "composer": "Packagist",
26
- "rubygems": "RubyGems", "gem": "RubyGems", "hex": "Hex",
27
- }
28
-
29
- _OSV_NPM_HINTS: frozenset[str] = frozenset({
30
- "lodash", "react", "vue", "express", "axios", "next", "webpack",
31
- "typescript", "eslint", "vite", "jest", "moment", "jquery", "angular",
32
- "svelte", "nuxt", "rollup", "babel", "prettier", "cheerio", "socket.io",
33
- })
34
-
35
- _OSV_GO_HINTS: frozenset[str] = frozenset({
36
- "golang.org/x/net", "golang.org/x/crypto", "golang.org/x/text",
37
- "github.com/gin-gonic/gin", "github.com/gorilla/websocket",
38
- })
39
-
40
-
41
- def _osv_resolve_ecosystem(query: str) -> tuple[str, str]:
42
- q = (query or "").strip()
43
- if ":" in q:
44
- head, _, tail = q.partition(":")
45
- eco = _OSV_ECOSYSTEM_PREFIX.get(head.strip().lower())
46
- if eco and tail.strip():
47
- return tail.strip(), eco
48
- name = q
49
- if name.startswith("@") or "/" in name:
50
- if name.startswith("@"):
51
- return name, "npm"
52
- if name.startswith(("github.com/", "gitlab.com/", "golang.org/")):
53
- return name, "Go"
54
- return name, "npm"
55
- if name.lower() in _OSV_NPM_HINTS:
56
- return name, "npm"
57
- if name.lower() in _OSV_GO_HINTS:
58
- return name, "Go"
59
- return name, "PyPI"
60
-
61
-
62
- def _build_osv_engine(spec: dict[str, Any]) -> Any:
63
- """OSV.dev 漏洞查询(按包名,POST /v1/query;免 key、免限次)。"""
64
- timeout = spec.get("timeout", 15)
65
-
66
- @safe_search
67
- def _engine(query: str, n: int = 5, _timeout: float | None = None,
68
- **kwargs) -> list[dict[str, Any]]:
69
- to = _timeout or timeout
70
- q = (query or "").strip()
71
- if not q:
72
- return []
73
- if re.match(r"^CVE-\d{4}-\d{4,}$", q, re.I):
74
- return []
75
- pkg, eco = _osv_resolve_ecosystem(q)
76
- body = json.dumps({"package": {"name": pkg, "ecosystem": eco}}).encode("utf-8")
77
- req = urllib.request.Request(
78
- "https://api.osv.dev/v1/query", data=body,
79
- headers={"Content-Type": "application/json"})
80
- eng_name = spec.get("_name") or "osv"
81
- try:
82
- with http_open(req, timeout=to, engine=eng_name) as resp:
83
- data = json.loads(resp.read().decode("utf-8"))
84
- except Exception as e:
85
- if isinstance(e, (json.JSONDecodeError, UnicodeDecodeError)):
86
- note_failure(eng_name, "parse", "json_decode", str(e))
87
- import logging
88
- logging.getLogger("unified_search.engines").warning(f"OSV 失败: {e}")
89
- return []
90
- results = []
91
- for i, v in enumerate((data.get("vulns") or [])[:n]):
92
- vid = str(v.get("id") or "").strip()
93
- if not vid:
94
- continue
95
- summary = str(v.get("summary") or "").strip()
96
- affected_bits = []
97
- for aff in (v.get("affected") or [])[:2]:
98
- if not isinstance(aff, dict):
99
- continue
100
- for rng in (aff.get("ranges") or [])[:2]:
101
- if not isinstance(rng, dict):
102
- continue
103
- events = rng.get("events") or []
104
- introduced = next((e.get("introduced") for e in events
105
- if isinstance(e, dict) and e.get("introduced")), "")
106
- fixed = next((e.get("fixed") for e in events
107
- if isinstance(e, dict) and e.get("fixed")), "")
108
- if introduced or fixed:
109
- affected_bits.append(
110
- f"{introduced or '?'} ≤ 受影响 < {fixed or '未修复'}")
111
- aliases = [a for a in (v.get("aliases") or []) if a][:3]
112
- snippet = " · ".join(p for p in (
113
- summary, f"生态 {eco}",
114
- affected_bits[0] if affected_bits else "",
115
- " ".join(aliases),
116
- ) if p)[:300]
117
- results.append({
118
- "title": f"{vid} [{eco}] {summary}"[:500] or vid,
119
- "url": f"https://osv.dev/vulnerability/{vid}",
120
- "snippet": snippet,
121
- "source": "osv",
122
- "published_at": str(v.get("published") or "")[:10],
123
- "score": rank_score(0.85, i),
124
- })
125
- return results
126
- return _engine
127
-
128
-
129
- # ── CISA KEV 已知在野利用漏洞(免 key,全量文件 + 本地过滤)──────────────
130
-
131
- _KEV_CACHE: dict[str, Any] = {"at": 0.0, "items": None}
132
- _KEV_TTL_S = 6 * 3600
133
-
134
-
135
- def _kev_disk_path():
136
- try:
137
- from argo_paths import state_path
138
- return state_path("cisa_kev_cache.json")
139
- except Exception:
140
- return None
141
-
142
-
143
- def _kev_load(timeout: float) -> list[dict[str, Any]]:
144
- now = time.time()
145
- cached = _KEV_CACHE.get("items")
146
- if cached and (now - float(_KEV_CACHE.get("at") or 0)) < _KEV_TTL_S:
147
- return cached
148
- disk_p = _kev_disk_path()
149
- if disk_p is not None and disk_p.exists():
150
- try:
151
- st = disk_p.stat()
152
- if (now - st.st_mtime) < _KEV_TTL_S:
153
- disk_items = json.loads(disk_p.read_text(encoding="utf-8"))
154
- if isinstance(disk_items, list) and disk_items:
155
- _KEV_CACHE["items"] = disk_items
156
- _KEV_CACHE["at"] = st.st_mtime
157
- return disk_items
158
- except Exception:
159
- pass
160
- req = urllib.request.Request(
161
- "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
162
- headers={"User-Agent": "argo-search/1.0 (+cisa-kev)", "Accept": "application/json"})
163
- try:
164
- with http_open(req, timeout=timeout, engine="cisa_kev") as resp:
165
- data = json.loads(resp.read().decode("utf-8", "replace"))
166
- except Exception as e:
167
- if isinstance(e, (json.JSONDecodeError, UnicodeDecodeError)):
168
- note_failure("cisa_kev", "parse", "json_decode", str(e))
169
- import logging
170
- logging.getLogger("unified_search.engines").warning(f"CISA KEV 失败: {e}")
171
- return cached or []
172
- items = data.get("vulnerabilities") or []
173
- if not isinstance(items, list):
174
- return cached or []
175
- _KEV_CACHE["items"] = items
176
- _KEV_CACHE["at"] = now
177
- if disk_p is not None and items:
178
- try:
179
- from argo_paths import atomic_write_json
180
- atomic_write_json(disk_p, items)
181
- except Exception:
182
- pass
183
- return items
184
-
185
-
186
- def _build_cisa_kev_engine(spec: dict[str, Any]) -> Any:
187
- """CISA KEV 在野利用漏洞目录(本地过滤全量目录)。"""
188
- timeout = spec.get("timeout", 30)
189
-
190
- @safe_search
191
- def _engine(query: str, n: int = 5, _timeout: float | None = None,
192
- **kwargs) -> list[dict[str, Any]]:
193
- to = _timeout or timeout
194
- q = (query or "").strip()
195
- if not q:
196
- return []
197
- items = _kev_load(to)
198
- if not items:
199
- return []
200
- cve_q = q.upper() if re.match(r"^CVE-\d{4}-\d{4,}$", q.strip(), re.I) else ""
201
- matched = []
202
- for v in items:
203
- if not isinstance(v, dict):
204
- continue
205
- if cve_q:
206
- if str(v.get("cveID") or "").strip().upper() == cve_q:
207
- matched.append(v)
208
- continue
209
- terms = [t for t in re.split(r"[\s,]+", q.lower()) if t]
210
- hay = " ".join(str(v.get(k) or "") for k in (
211
- "cveID", "vendorProject", "product", "vulnerabilityName",
212
- "shortDescription", "requiredAction", "cwes")).lower()
213
- if all(t in hay for t in terms):
214
- matched.append(v)
215
- matched.sort(key=lambda x: str(x.get("dateAdded") or ""), reverse=True)
216
- results = []
217
- for i, v in enumerate(matched[:n]):
218
- cve = str(v.get("cveID") or "").strip()
219
- if not cve:
220
- continue
221
- vendor = str(v.get("vendorProject") or "").strip()
222
- product = str(v.get("product") or "").strip()
223
- name = str(v.get("vulnerabilityName") or "").strip()
224
- desc = str(v.get("shortDescription") or "").strip()
225
- due = str(v.get("dueDate") or "").strip()
226
- ransom = str(v.get("knownRansomwareCampaignUse") or "").strip()
227
- bits = [b for b in (
228
- f"{vendor} {product}".strip(),
229
- f"修复期限 {due}" if due else "",
230
- "已知勒索软件利用" if ransom.lower() == "known" else "",
231
- desc,
232
- ) if b]
233
- results.append({
234
- "title": f"{cve} — {name or product or vendor}"[:500],
235
- "url": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog",
236
- "snippet": " · ".join(bits)[:300],
237
- "source": "cisa_kev",
238
- "published_at": str(v.get("dateAdded") or "")[:10],
239
- "score": rank_score(0.9, i),
240
- })
241
- return results
242
- return _engine
243
-
244
-
245
- # ── 引擎声明(本地个性化配置,不提交公开仓库)────────────────────────────────
246
- #
247
- # 字段与原 engines/specs/osv.yaml、engines/specs/cisa_kev.yaml 逐项对齐——
248
- # 那两个声明已加 _ 前缀改名停用(公开仓每次启动刷两行「未知引擎类型」warning),
249
- # 元数据随之并入本文件,本机路由行为(explicit_only / family / priority / qps
250
- # 等)与改名前完全一致。
251
-
252
- _LOCAL_ENGINE_SPECS: dict[str, dict[str, Any]] = {
253
- "osv": {
254
- "type": "osv",
255
- "enabled": True,
256
- "explicit_only": True,
257
- "cost_tier": "free",
258
- "label": "OSV 开源漏洞库",
259
- "family": "security",
260
- "priority": 44,
261
- "qps": 2,
262
- "period": "second",
263
- "cost_per_call": 0.0,
264
- "cost_unit": "free",
265
- "method": "POST",
266
- "url": "https://api.osv.dev/v1/query",
267
- "timeout": 15,
268
- "canary_query": "requests",
269
- "desc": "OSV.dev 开源软件漏洞库(按包查已知漏洞与影响版本区间,免 key;生态 PyPI/npm/Go/crates 等)",
270
- "description": "OSV.dev 开源漏洞查询(按包名)",
271
- "coverage": ["security", "package"],
272
- "recommended": False,
273
- "latency_ms": 1500,
274
- "headers": {"Content-Type": "application/json"},
275
- "quality_queries": [
276
- {"query": "requests"},
277
- {"query": "lodash"},
278
- {"query": "log4j-core"},
279
- ],
280
- },
281
- "cisa_kev": {
282
- "type": "cisa_kev",
283
- "enabled": True,
284
- "explicit_only": True,
285
- "cost_tier": "free",
286
- "label": "CISA KEV 在野利用漏洞",
287
- "family": "security",
288
- "priority": 47,
289
- "qps": 1,
290
- "period": "second",
291
- "cost_per_call": 0.0,
292
- "cost_unit": "free",
293
- "timeout": 30,
294
- "canary_query": "chrome",
295
- "desc": "CISA 已知被在野利用漏洞目录(官方确认已被实际攻击利用的 CVE;全量 1.35MB 本地过滤)",
296
- "description": "CISA KEV 在野利用漏洞目录",
297
- "coverage": ["security"],
298
- "recommended": False,
299
- "latency_ms": 4000,
300
- "quality_queries": [
301
- {"query": "chrome"},
302
- {"query": "CVE-2021-44228"},
303
- ],
304
- },
305
- }
306
-
307
-
308
- def register(builders: dict[str, Any], engine_specs: dict[str, Any]) -> None:
309
- """将本地引擎注册到全局 builders 和 engine_specs 中。
310
-
311
- 在 engines.py 的 _load_registry() 中、构建 registry 之前调用。
312
- 文件不存在时 engines.py 跳过 import,不影响公开仓库。
313
- 外置 spec(engines/specs/*.yaml)已有的字段保留,不抹除 explicit_only/family 等元数据。
314
- """
315
- builders["osv"] = _build_osv_engine
316
- builders["cisa_kev"] = _build_cisa_kev_engine
317
- for name, spec in _LOCAL_ENGINE_SPECS.items():
318
- base = dict(engine_specs.get(name) or {})
319
- for k, v in spec.items():
320
- base.setdefault(k, v)
321
- engine_specs[name] = base
@@ -1,43 +0,0 @@
1
- # ego-search 浏览器运行时参考(低频:复杂交互才需要)
2
-
3
-
4
- ## 直接浏览器操作
5
-
6
- 复杂交互路径用浏览器运行时命令 `ego-browser nodejs` heredoc 直接编排(本子技能继承完整
7
- 运行时)。核心纪律:
8
-
9
- ```bash
10
- ego-browser nodejs <<'EOF'
11
- // 同名任务空间跨轮次复用;同一用户目标不新建空间
12
- const task = await useOrCreateTaskSpace('竞品调研')
13
- await openOrReuseTab('https://example.com', { wait: true, timeout: 20 })
14
- cliLog(await snapshotText()) // 语义快照,带 [ref=N, loc=..., url=...]
15
- EOF
16
- ```
17
-
18
- **Helpers 速查**:
19
-
20
- - 任务空间:`useOrCreateTaskSpace` / `listTaskSpaces` / `claimTaskSpace` / `handOffTaskSpace` / `takeOverTaskSpace` / `completeTaskSpace`
21
- - 导航:`openOrReuseTab` / `gotoAndWait` / `pageInfo` / `listTabs` / `switchTab`
22
- - 观察:`snapshotText` / `captureScreenshot` / `drainEvents`
23
- - 动作:`click` / `fillInput` / `typeText` / `pressKey` / `scrollToBottomUntil` / `hover` / `uploadFile`
24
- - 等待:`wait` / `waitForElement` / `waitForNetworkIdle`
25
- - 提取:`js`(页面内 JS)/ `cdp`(浏览器协议)
26
- - 输出:`cliLog`(唯一输出通道;`-e` 模式下输出到 stderr)
27
-
28
- **关键纪律**:
29
-
30
- 1. **任务空间隔离 + 登录态继承**:Agent 在独立空间操作,不抢用户标签页;登录态默认继承,可访问已登录站点。
31
- 2. **跨轮次复用**:Node 运行时每次 heredoc 退出即释放,后续轮次用 `useOrCreateTaskSpace(同名)` 或
32
- `takeOverTaskSpace`(用户确认继续后)恢复。
33
- 3. **归属权**:用户接管空间时("user is controlling")是硬停——问用户并等待,不重试不抢回;
34
- `handOffTaskSpace` 交还用户后,只有用户明确确认(Ask 的 Continue)才 `takeOverTaskSpace`。
35
- 4. **收尾**:任务完成必须 `completeTaskSpace(name, { keep: false })` 关掉空间;
36
- 仅当用户明确要求保留页面/需人工操作/结果无法用 URL 交付时才 `{ keep: true }`。
37
- 5. **`js()` 用法**:页面内逻辑包成单个 IIFE 一次返回;`js()` 返回求值结果而非 JSON 字符串,
38
- 不要包 `JSON.parse`;模板字符串里正则反斜杠要双写或 `String.raw`。
39
- 6. **输出通道**:heredoc 模式 `cliLog` 输出到 stdout;`ego-browser nodejs -e "..."` 模式输出到 **stderr**。
40
-
41
- **三种工作流**:普通 DOM 页用语义流(`snapshotText` + `@N`/`loc=` 引用);canvas/富编辑器
42
- (Google Docs/Notion/Figma 等)用视觉流(截图 + 坐标 + 键盘);需要浏览器态/紧凑数据提取用
43
- 直接 DOM/CDP 流(`js`/`cdp`)。
@@ -1,67 +0,0 @@
1
- # Install ego lite
2
-
3
- Read this file only when ego lite isn't installed yet, or when the user asks to install ego lite. For day-to-day browser work, go back to `SKILL.md`.
4
-
5
- The ego-browser skill depends on the ego lite browser: the `ego-browser` command is provided by the ego lite app. Once ego lite is installed and you've gone through onboarding once, the environment is ready and there are no further environment issues.
6
-
7
- ego lite website: https://lite.ego.app/
8
-
9
- ## Install steps (macOS only)
10
-
11
- The install script lives at `scripts/install.sh` in this skill and supports macOS only. It will:
12
-
13
- - Download the ego lite installer (a DMG) for your CPU architecture (arm64 / x64).
14
- - Install `ego lite.app` to `/Applications` (falling back to `~/Applications` when needed).
15
- - Strip the quarantine attribute to keep Gatekeeper from blocking the first launch.
16
- - After installing, launch the `ego lite` app.
17
-
18
- Run the script (use the script's actual path under this skill's directory):
19
-
20
- ```bash
21
- sh skills/ego-browser/scripts/install.sh
22
- ```
23
-
24
- After installing, the script opens the ego lite app directly. If ego lite is already installed, the script skips the download and opens the app directly.
25
-
26
- After the script opens the ego lite app, the user completes the first-run onboarding in the app:
27
-
28
- - Choose to import data from Chrome or another browser as needed.
29
- - Onboarding registers the `ego-browser` command on the PATH (usually under `~/.local/bin`).
30
-
31
- Onboarding is a step the user completes in the GUI. After the script opens ego lite, wait for the user to confirm they've finished onboarding before continuing.
32
-
33
- ## After installing: confirm `ego-browser` is available
34
-
35
- Once the user has finished onboarding, confirm the command is ready:
36
-
37
- ```bash
38
- command -v ego-browser
39
- ```
40
-
41
- If it reports that the command isn't found, `~/.local/bin` is most likely not on the current PATH. Fix it temporarily and retry:
42
-
43
- ```bash
44
- export PATH="$HOME/.local/bin:$PATH"
45
- command -v ego-browser
46
- ```
47
-
48
- Once the command exists, verify the runtime with a minimal heredoc:
49
-
50
- ```bash
51
- ego-browser nodejs <<'EOF'
52
- cliLog('ego-browser ready')
53
- EOF
54
- ```
55
-
56
- Printing `ego-browser ready` means the environment is ready.
57
-
58
- ## After that, return to the original task
59
-
60
- Once the environment is ready, return to the user's original task and continue with the task space flow in `SKILL.md` — start from `useOrCreateTaskSpace(name)` and proceed as usual.
61
-
62
- ## Troubleshooting
63
-
64
- - **Not macOS**: the script supports macOS only (`uname -s` is `Darwin`). On other platforms, have the user download and install from the ego lite website at https://lite.ego.app/.
65
- - **Download failed**: the script retries 3 times automatically; if it still fails, it's usually a network issue — have the user check their network and retry.
66
- - **Gatekeeper still blocks it**: the script already tries to strip quarantine; if the first launch is still blocked, have the user allow ego lite manually under System Settings → Privacy & Security.
67
- - **Command still unavailable after onboarding**: confirm `~/.local/bin` is on the PATH (see above); or have the user reopen ego lite, finish onboarding, and retry.
@@ -1,51 +0,0 @@
1
- # 原版 ego 技能升级要点(与本子技能配套)
2
-
3
- 原版 `ego-browser` skill 仍是**通用浏览器自动化**真源;本文件只补「与登录态专业搜索完全态」对齐的用法,不替换原版 SKILL 正文。
4
-
5
- ## 与 ego-search 分工
6
-
7
- | 场景 | 用谁 |
8
- |------|------|
9
- | 搜 SERP / 抓正文 / 已知 API / 登录墙取证 | **ego-search**(双运行时 auto) |
10
- | 复杂点选、填表、多步、截图工作流 | **原版 ego-browser** heredoc |
11
- | 无 ego App、只有用户 Chrome | **ego-search --runtime webbridge** |
12
-
13
- ## 建议启用但常被忽略的能力
14
-
15
- 在 `ego-browser nodejs` 中(打开目标站后):
16
-
17
- ```js
18
- // 站点经验包(若该站有 learnings)
19
- const ctx = await learnContext()
20
- cliLog(JSON.stringify(ctx))
21
- // 例如 google 搜索抽取(以实际 manifest 为准)
22
- // await runSiteTool('google', 'search_and_extract', { query: '...' })
23
- ```
24
-
25
- 站点包通常在应用内 `learnings/`(google / x-com / github 等)。
26
-
27
- ## 输出建议(与隔离/融合对齐)
28
-
29
- 原版 heredoc 若产出可并入分析的结果,请在 `cliLog` 的 JSON 中尽量带:
30
-
31
- ```json
32
- {
33
- "login_state_used": true,
34
- "auth_partition": "login",
35
- "cache_eligible": false,
36
- "search_partition": "login",
37
- "merge_with_public_ok": true,
38
- "runtime": "ego",
39
- "source": "ego-browser"
40
- }
41
- ```
42
-
43
- 勿把登录态 body 写入 argo 公共 `SearchCache`。
44
-
45
- ## 探测
46
-
47
- ```bash
48
- python3 sub-skills/ego-search/scripts/ego_search.py status
49
- ```
50
-
51
- `runtimes.preferred` / `login_search_ready` 表示登录态专业搜索是否可跑。
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env python3
2
- """ego-search query 归一化前处理单测(保持一致 argo 核心 query_enhance)
3
-
4
- 设计约束:
5
- - 只做词形规范化(全角→半角、拆斜杠、压空格)
6
- - 必须保留平台结构化语法(from:/site:/filter: …)——ego 是站内通道,剥语法会丢能力
7
- - 核心 query_enhance 不可用时降级为原 query(不破坏)
8
-
9
- 运行:
10
- cd ~/.agents/skills/argo
11
- python3 -m pytest sub-skills/ego-search/tests/test_query_normalize.py -q
12
- """
13
-
14
- from __future__ import annotations
15
-
16
- import sys
17
- import unittest
18
- from pathlib import Path
19
-
20
- SCRIPTS = Path(__file__).resolve().parent.parent / "scripts"
21
- sys.path.insert(0, str(SCRIPTS))
22
-
23
- import ego_search as es # noqa: E402
24
-
25
-
26
- class TestQueryNormalize(unittest.TestCase):
27
- def test_full_width_to_half(self):
28
- self.assertEqual(es._normalized_query("“GPT-5”,价格"), '"GPT-5",价格')
29
-
30
- def test_slash_split(self):
31
- self.assertEqual(es._normalized_query("LongCat-2.0/1.6 万亿"), "LongCat-2.0 1.6 万亿")
32
-
33
- def test_keep_platform_syntax(self):
34
- self.assertEqual(
35
- es._normalized_query('from:OpenAI "GPT-5" filter:images'),
36
- 'from:OpenAI "GPT-5" filter:images',
37
- )
38
-
39
- def test_plain_unchanged(self):
40
- self.assertEqual(es._normalized_query("openai api"), "openai api")
41
-
42
- def test_plain_slash_not_split(self):
43
- # 核心规则:只拆「点号版本」斜杠(LongCat-2.0/1.6),普通斜杠可能是
44
- # 路径/日期/分数,必须原样保留。本测试曾检查 "a/b c" → "a b c",
45
- # 是旧实现(全斜杠拆分)的遗留,与核心 normalize_query 矛盾,
46
- # 且子技能测试不在主检查收集范围,红了一年没人看见。
47
- self.assertEqual(es._normalized_query("a/b c"), "a/b c")
48
-
49
- def test_import_fallback(self):
50
- # normalize_query 缺失时应回退原 query(真正模拟核心不可用)
51
- orig = es.normalize_query
52
- try:
53
- es.normalize_query = None
54
- self.assertEqual(es._normalized_query("a/b c"), "a/b c")
55
- self.assertEqual(es._normalized_query("LongCat-2.0/1.6"), "LongCat-2.0/1.6")
56
- finally:
57
- es.normalize_query = orig
58
-
59
- def test_core_scripts_path_is_argo_root(self):
60
- # 回归锚:相对路径必须指向 argo 根/scripts(parents[3])。
61
- # 曾用 parents[2] 指向 sub-skills/scripts(不存在)→ normalize_query
62
- # 静默降级为 None,子技能归一化整条失效。
63
- self.assertTrue(es._CORE_SCRIPTS.exists(), es._CORE_SCRIPTS)
64
- self.assertEqual(es._CORE_SCRIPTS.name, "scripts")
65
-
66
-
67
- if __name__ == "__main__":
68
- unittest.main()
@@ -1,27 +0,0 @@
1
- // challenge_like.js — 金标考卷 #1:挑战页形态的 JS(离线可复跑)
2
- // 形态参照真实反爬挑战脚本的三段式:环境探测 → 字符串运算 → 发通行证。
3
- // 只用 v0 垫片保证过的面:navigator/screen/btoa/document.cookie/定时器。
4
- (function () {
5
- var probe = [
6
- navigator.userAgent.length,
7
- navigator.platform,
8
- navigator.language,
9
- screen.width * screen.height,
10
- navigator.hardwareConcurrency,
11
- Number(!navigator.webdriver),
12
- ].join("|");
13
-
14
- // 字符串运算(模拟混淆层的算子链)
15
- var salt = "";
16
- for (var i = 0; i < probe.length; i += 7) salt += String.fromCharCode(probe.charCodeAt(i) ^ 0x1f);
17
- var token = btoa(salt + "|" + probe.length);
18
-
19
- // 定时器:真浏览器里等 3 秒才发 cookie;逻辑时间下瞬间完成
20
- var issued = false;
21
- setTimeout(function () {
22
- document.cookie = "__jsl_clearance=" + token + "; path=/";
23
- issued = true;
24
- }, 3000);
25
-
26
- return { probe: probe, token: token, issued: issued };
27
- })();