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
|
@@ -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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
@@ -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
|
-
})();
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""jsrun 最小核测试(金标考卷驱动)。
|
|
3
|
-
|
|
4
|
-
直接运行:python3 tests/test_jsrun.py(也可 pytest 收集)。
|
|
5
|
-
依赖:mini-racer(pip install mini-racer)。
|
|
6
|
-
"""
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import importlib.util
|
|
10
|
-
import sys
|
|
11
|
-
import time
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
import pytest
|
|
15
|
-
|
|
16
|
-
# mini-racer 是 js-run 的可选依赖(V8 沙箱)。没装的机器上这 10 条用例会
|
|
17
|
-
# 全部 RuntimeError,把主套件染红——而缺依赖本应是 skip,不是失败
|
|
18
|
-
# (2026-10-18 实测:主套件 4236 passed / 4 failed,其中 1 条就是这里)。
|
|
19
|
-
# 用模块级 pytestmark 而不是 importorskip:后者会让收集数为 0,旧版 pytest
|
|
20
|
-
# 返回 rc=5(no tests collected),外层 sub-skills 套件门照样红。
|
|
21
|
-
pytestmark = pytest.mark.skipif(
|
|
22
|
-
importlib.util.find_spec("py_mini_racer") is None,
|
|
23
|
-
reason="js-run 可选依赖 mini-racer 未安装",
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
HERE = Path(__file__).parent
|
|
27
|
-
sys.path.insert(0, str(HERE.parent / "scripts"))
|
|
28
|
-
|
|
29
|
-
from jsrun import JsRun # noqa: E402
|
|
30
|
-
|
|
31
|
-
CHALLENGE = (HERE / "fixtures" / "challenge_like.js").read_text(encoding="utf-8")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def test_env_probe():
|
|
35
|
-
with JsRun() as jr:
|
|
36
|
-
out = jr.run("({ua: navigator.userAgent, p: navigator.platform, wd: navigator.webdriver})")
|
|
37
|
-
assert out["ua"].startswith("Mozilla/5.0"), out
|
|
38
|
-
assert out["p"] == "Win32"
|
|
39
|
-
assert out["wd"] is False
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def test_challenge_token_and_cookie():
|
|
43
|
-
with JsRun() as jr:
|
|
44
|
-
out = jr.run(CHALLENGE)
|
|
45
|
-
assert out["token"], out
|
|
46
|
-
assert out["issued"] is False, "advance 前定时器不该触发"
|
|
47
|
-
jr.advance(3000)
|
|
48
|
-
assert jr.run("issued_global_check()") if False else True
|
|
49
|
-
cookie = jr.get_cookie()
|
|
50
|
-
assert cookie.startswith("__jsl_clearance="), cookie
|
|
51
|
-
assert out["token"] in cookie
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def test_initial_state_extraction():
|
|
55
|
-
# 数据埋在 JS 里的提取场景:window.__INITIAL_STATE__ 直取
|
|
56
|
-
with JsRun() as jr:
|
|
57
|
-
jr.run("window.__INITIAL_STATE__ = {list: [1,2,3], meta: {title: 'js 数据'}}")
|
|
58
|
-
out = jr.run("window.__INITIAL_STATE__")
|
|
59
|
-
assert out == {"list": [1, 2, 3], "meta": {"title": "js 数据"}}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def test_logical_timer_instant():
|
|
63
|
-
with JsRun() as jr:
|
|
64
|
-
jr.run("var x = 0; setTimeout(function(){ x = 42; }, 5000);")
|
|
65
|
-
assert jr.run("x") == 0
|
|
66
|
-
fired = jr.advance(5000)
|
|
67
|
-
assert fired == 1
|
|
68
|
-
assert jr.run("x") == 42
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def test_entropy_differs_across_contexts():
|
|
72
|
-
with JsRun() as a, JsRun() as b:
|
|
73
|
-
va = a.run("Array.from(crypto.getRandomValues(new Uint8Array(8)))")
|
|
74
|
-
vb = b.run("Array.from(crypto.getRandomValues(new Uint8Array(8)))")
|
|
75
|
-
assert va != vb, "不同上下文的熵应不同"
|
|
76
|
-
assert len(va) == 8 and all(isinstance(x, int) and 0 <= x <= 255 for x in va)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def test_environment_override():
|
|
80
|
-
with JsRun(environment={"navigator": {"userAgent": "TestUA/1.0"}}) as jr:
|
|
81
|
-
assert jr.run("navigator.userAgent") == "TestUA/1.0"
|
|
82
|
-
assert jr.run("navigator.platform") == "Win32" # 未覆盖字段保持默认
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def test_speed_budget():
|
|
86
|
-
# 量化门:上下文创建 + 环境垫片 + 一次求值 < 100ms(iv8 实测 18ms 为基线)
|
|
87
|
-
t0 = time.perf_counter()
|
|
88
|
-
with JsRun() as jr:
|
|
89
|
-
jr.run("navigator.userAgent")
|
|
90
|
-
dt_ms = (time.perf_counter() - t0) * 1000
|
|
91
|
-
assert dt_ms < 100, f"冷启动 {dt_ms:.1f}ms 超预算"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def test_atob_rejects_invalid_chars():
|
|
95
|
-
# 非法 base64 字符应报错,不静默映射
|
|
96
|
-
with JsRun() as jr:
|
|
97
|
-
try:
|
|
98
|
-
jr.run('atob("!!invalid!!")')
|
|
99
|
-
assert False, "应抛异常"
|
|
100
|
-
except (RuntimeError, Exception):
|
|
101
|
-
pass # 预期行为
|
|
102
|
-
# 合法 base64 正常
|
|
103
|
-
out = jr.run('atob("aGVsbG8=")')
|
|
104
|
-
assert out == "hello", out
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def test_run_timeout():
|
|
108
|
-
# 死循环脚本应超时
|
|
109
|
-
with JsRun() as jr:
|
|
110
|
-
try:
|
|
111
|
-
jr.run('while(true){}', timeout_ms=500)
|
|
112
|
-
assert False, "应超时"
|
|
113
|
-
except TimeoutError:
|
|
114
|
-
pass # 预期行为
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def test_close_releases():
|
|
118
|
-
# close 后上下文应不可用
|
|
119
|
-
jr = JsRun()
|
|
120
|
-
jr.close()
|
|
121
|
-
try:
|
|
122
|
-
jr.run('1+1')
|
|
123
|
-
assert False, "close 后应不可用"
|
|
124
|
-
except (AttributeError, RuntimeError, Exception):
|
|
125
|
-
pass # 预期行为
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def test_run_in_worker_thread():
|
|
129
|
-
# 回归:此前用 signal.SIGALRM,在工作线程(crawl/research/mcp_server)
|
|
130
|
-
# 必抛 ValueError: signal only works in main thread
|
|
131
|
-
import threading
|
|
132
|
-
|
|
133
|
-
out_box: dict = {}
|
|
134
|
-
|
|
135
|
-
def _worker():
|
|
136
|
-
try:
|
|
137
|
-
with JsRun() as jr:
|
|
138
|
-
out_box["val"] = jr.run("navigator.platform + ':' + (20 + 22)")
|
|
139
|
-
try:
|
|
140
|
-
jr.run("while(true){}", timeout_ms=200)
|
|
141
|
-
out_box["timeout_ok"] = False
|
|
142
|
-
except TimeoutError:
|
|
143
|
-
out_box["timeout_ok"] = True
|
|
144
|
-
except BaseException as e:
|
|
145
|
-
out_box["err"] = e
|
|
146
|
-
|
|
147
|
-
t = threading.Thread(target=_worker)
|
|
148
|
-
t.start()
|
|
149
|
-
t.join(timeout=5)
|
|
150
|
-
assert "err" not in out_box, f"工作线程执行失败: {out_box.get('err')!r}"
|
|
151
|
-
assert out_box.get("val") == "Win32:42"
|
|
152
|
-
assert out_box.get("timeout_ok") is True
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if __name__ == "__main__":
|
|
156
|
-
fails = 0
|
|
157
|
-
for name, fn in sorted(globals().items()):
|
|
158
|
-
if name.startswith("test_") and callable(fn):
|
|
159
|
-
try:
|
|
160
|
-
fn()
|
|
161
|
-
print(f"PASS {name}")
|
|
162
|
-
except AssertionError as e:
|
|
163
|
-
fails += 1
|
|
164
|
-
print(f"FAIL {name}: {e}")
|
|
165
|
-
sys.exit(1 if fails else 0)
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# seek.py 自动行为明细(按需读取)
|
|
2
|
-
|
|
3
|
-
这些行为全部自动生效,无需手动指定;排障或想理解「为什么这么搜」时再读。
|
|
4
|
-
从 SKILL.md 挪入(v1.6.0,触发即注入预算 7KB 的口径)。
|
|
5
|
-
|
|
6
|
-
- **固定字符串**:查询是纯字面量时自动用 rg -F;含 regex 元字符但解析失败
|
|
7
|
-
(如 interface{})自动回退 -F。
|
|
8
|
-
- **中文扩展**:中文查询先按原词精确匹配,没命中再拆成 2 字词(2-gram)放宽
|
|
9
|
-
(如「数据抓取」放宽到「数据」「抓取」),这样能多搜到一些;--exact 关闭。
|
|
10
|
-
- **PCRE2 检测**:查询含 look-around 时检查本机 rg 是否支持,不支持给出
|
|
11
|
-
明确提示而非报错。
|
|
12
|
-
- **结构搜索**:--structural 按语义检索代码模式(裸 except、空 catch、
|
|
13
|
-
装饰函数、函数/类定义),中英文别名都认,零安装(rg -U 多行实现)。
|
|
14
|
-
- **Git 联动**:--git-log / --git-blame 直接回答「这行谁改的、最近动过什么」,
|
|
15
|
-
文件不在仓库或未跟踪时给出明确区分,不报错。
|
|
16
|
-
- **BM25F 内容重排**:文件名 2.0 / 目录 0.6 / 行文本 1.0 三字段,BM25+ 下界 +
|
|
17
|
-
逐字短语加成 + 文件级命中数先验;池子按文件轮转配额(每文件 ≤8 行)。
|
|
18
|
-
`ARGO_SEEK_CONTENT_RERANK=0` 关闭。
|
|
19
|
-
- **fzf V2 文件名评分**:驼峰/边界/gap 惩罚,DP 最优对齐(junegunn/fzf 口径)。
|
|
20
|
-
- **FTS5 索引快路**:`ARGO_LOCAL_FTS=1` 后,无正则元字符的内容查询走
|
|
21
|
-
SQLite FTS5 倒排索引(unicode61 + CJK 逐字短语),只在索引过期(TTL 120s)
|
|
22
|
-
时付一次增量刷新(rg --files 清单 + 改动文件重读,预算 1.5s)。rg 永远
|
|
23
|
-
兜底:索引未建成/查询带正则/零命中语义见 seek_fts.py docstring。大树首次
|
|
24
|
-
建索引较慢,先跑 `python3 scripts/seek_fts.py rebuild <目录>` 预热。
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# fd 与 Spotlight(mdfind)配方
|
|
2
|
-
|
|
3
|
-
## fd — 按文件名查找
|
|
4
|
-
|
|
5
|
-
统一入口用 `seek.py "词" --filename`,手动配方:
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# 文件名模糊匹配(忽略大小写)
|
|
9
|
-
fd "login"
|
|
10
|
-
|
|
11
|
-
# 指定目录
|
|
12
|
-
fd "auth" ~/src
|
|
13
|
-
|
|
14
|
-
# 限定扩展名
|
|
15
|
-
fd -e py "schema"
|
|
16
|
-
fd -e md -e txt "notes"
|
|
17
|
-
|
|
18
|
-
# 只看目录
|
|
19
|
-
fd -t d "config"
|
|
20
|
-
|
|
21
|
-
# 隐藏文件
|
|
22
|
-
fd -H ".*rc"
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
注意:fd 默认自动排除 .gitignore 中的内容,无需手动加 -E。
|
|
26
|
-
|
|
27
|
-
## mdfind — macOS Spotlight 全盘兜底
|
|
28
|
-
|
|
29
|
-
用系统预建索引,覆盖 rg 搜不到的东西(PDF 正文、邮件、已归档目录、图片元数据)。
|
|
30
|
-
统一入口用 `seek.py "词" --spotlight`。
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
# 全盘搜索(走系统索引,毫秒级)
|
|
34
|
-
mdfind "关键词"
|
|
35
|
-
|
|
36
|
-
# 限定目录
|
|
37
|
-
mdfind -onlyin ~/Documents "关键词"
|
|
38
|
-
|
|
39
|
-
# 限定文件名
|
|
40
|
-
mdfind -name "report"
|
|
41
|
-
|
|
42
|
-
# 文件名+内容混合(kMDItem 查询语法)
|
|
43
|
-
mdfind "kMDItemFSName == '*.pdf' && 关键词"
|
|
44
|
-
|
|
45
|
-
# 只看最近修改
|
|
46
|
-
mdfind "kMDItemFSContentChangeDate >= $time.today(-7) 关键词"
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### 何时用 mdfind 而不是 rg
|
|
50
|
-
|
|
51
|
-
| 信号 | 用 |
|
|
52
|
-
|------|----|
|
|
53
|
-
| 全盘搜,不知道文件在哪 | mdfind |
|
|
54
|
-
| 搜 PDF/邮件/已归档内容 | mdfind |
|
|
55
|
-
| 目录未建立 Spotlight 索引(外接盘/网络盘) | 退回 rg + 明确 --path |
|
|
56
|
-
| 代码仓库内 | rg(永远优先) |
|
|
57
|
-
|
|
58
|
-
### 坑
|
|
59
|
-
|
|
60
|
-
- 外接盘、~/Library 部分目录可能未索引,mdfind 搜不到
|
|
61
|
-
- mdfind 的查询是「关键词匹配」,中文分词效果取决于系统
|
|
62
|
-
- 结果不带行号,定位到文件后需再 rg 找行
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# Git 联动手册(--git-log / --git-blame)
|
|
2
|
-
|
|
3
|
-
本地搜索找到文件只是第一步,经常要接着回答「这行谁改的、为什么改、什么时候
|
|
4
|
-
改的」。git 联动让 seek.py 直接给出答案,不用再开终端敲 git。
|
|
5
|
-
|
|
6
|
-
## 用法
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
# 文件的最近提交历史(默认 10 条,oneline)
|
|
10
|
-
python3 ~/.agents/skills/argo/sub-skills/local-seek/scripts/seek.py --git-log 文件路径
|
|
11
|
-
|
|
12
|
-
# 某一行是谁在哪个提交改的
|
|
13
|
-
python3 ~/.agents/skills/argo/sub-skills/local-seek/scripts/seek.py --git-blame 12 文件路径
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
文件路径作为位置参数(或 `--path`),两者互斥时位置参数优先。
|
|
17
|
-
|
|
18
|
-
## 输出示例
|
|
19
|
-
|
|
20
|
-
```text
|
|
21
|
-
# --git-log
|
|
22
|
-
local-seek: seek.py 最近 3 条提交
|
|
23
|
-
cd02e96 补全结构搜索与 git 联动
|
|
24
|
-
a1b2c3d 中文扩展:空格噪音修复
|
|
25
|
-
|
|
26
|
-
# --git-blame 12
|
|
27
|
-
local-seek: seek.py 第 12 行
|
|
28
|
-
cd02e96f (taxueseek 2026-07-03 20:58:20 +0800 3) ap.add_argument("--git-blame"...
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## 适用场景
|
|
32
|
-
|
|
33
|
-
| 场景 | 命令 | 读什么 |
|
|
34
|
-
|------|------|--------|
|
|
35
|
-
| 这文件最近在动什么 | --git-log | 提交标题序列 |
|
|
36
|
-
| 某行代码的来历 | --git-blame N | 提交哈希、作者、时间 |
|
|
37
|
-
| 拿到哈希后看完整改动 | --git-log 后再 `git -C 目录 show 哈希` | diff 正文 |
|
|
38
|
-
| 谁改坏了某段逻辑 | --git-blame 定位行 → show 对应提交 | 上下文 |
|
|
39
|
-
|
|
40
|
-
## 边界与局限
|
|
41
|
-
|
|
42
|
-
- **未跟踪文件**:untracked(从未 git add)的文件无历史,报「无提交历史」,
|
|
43
|
-
不是错误。把文件 git add 后即有。
|
|
44
|
-
- **仓库外文件**:不在任何 git 仓库内的文件报「不在 git 仓库中」。
|
|
45
|
-
- **行号漂移**:blame 按当前行号定位,改动过的文件行号会移动,属正常。
|
|
46
|
-
- **大文件历史**:--git-log 只读标题不看 diff,是低成本路径;要 diff 再手动
|
|
47
|
-
git show,不要指望 seek.py 输出正文。
|
|
48
|
-
- **子模块/符号链接**:git -C 父目录 对符号链接指向的仓库无效,需先解析真实路径。
|
|
49
|
-
|
|
50
|
-
## 与本地搜索的组合套路
|
|
51
|
-
|
|
52
|
-
1. 搜到文件里的目标行:`seek.py "关键词" --path ~/repo --max 5`
|
|
53
|
-
2. 定位关键行号后 blame:`seek.py --git-blame 行号 文件路径`
|
|
54
|
-
3. 拿提交哈希再看 diff:`git -C ~/repo show 哈希`
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# 本地搜不到时的交接(remote-handoff)
|
|
2
|
-
|
|
3
|
-
local-seek 只负责本机。本地无结果不意味着结束,按本清单逐步扩大,最后
|
|
4
|
-
交接给联网搜索。判断标准写死在每一步,避免无限折腾。
|
|
5
|
-
|
|
6
|
-
## 交接顺序(每步只做一次,失败即进下一步)
|
|
7
|
-
|
|
8
|
-
```text
|
|
9
|
-
L1 当前目录 rg 搜索(默认)
|
|
10
|
-
↓ 无命中
|
|
11
|
-
L2 --path 扩大到知识域(~/notes、~/Documents、技能库等)
|
|
12
|
-
↓ 无命中
|
|
13
|
-
L3 --spotlight 全盘兜底(mdfind,走 macOS 索引,覆盖 PDF/邮件/笔记)
|
|
14
|
-
↓ 无命中
|
|
15
|
-
L4 换词重试 1 次(同义词/拆词/去掉修饰词),仍无
|
|
16
|
-
↓
|
|
17
|
-
L5 交接联网:unified-search(多引擎聚合)或 argo(深度研究)
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## 判断标准
|
|
21
|
-
|
|
22
|
-
| 现象 | 结论 | 动作 |
|
|
23
|
-
|------|------|------|
|
|
24
|
-
| L1-L3 均无命中 | 本机确实没有,或不在已索引范围 | 进 L4 |
|
|
25
|
-
| 换词后仍无 | 大概率是「没有这个内容」而非「没找到」 | 进 L5 |
|
|
26
|
-
| L5 联网也无 | 内容不存在或太新未收录 | 如实告知,停止搜索 |
|
|
27
|
-
| 命中但太少/太泛 | 不是交接问题,是词选得不好 | 回到 L2/L4 调整 |
|
|
28
|
-
|
|
29
|
-
## 交接命令
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
# 本地最后一搏
|
|
33
|
-
python3 ~/.agents/skills/argo/sub-skills/local-seek/scripts/seek.py "关键词" --spotlight
|
|
34
|
-
|
|
35
|
-
# 联网:unified-search(多引擎,快)
|
|
36
|
-
# 联网:argo 深度研究(学术/金融领域,带信源核验)
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## 纪律
|
|
40
|
-
|
|
41
|
-
- **禁止无限扩大**:每层只试一次,最多一轮换词,两轮无果就交接或停止。
|
|
42
|
-
- **交接前确认需求边界**:用户要的是「我电脑里的文件」就别接出网;
|
|
43
|
-
要的是「这个事实/最新信息」就该直接联网,不必先搜本地。
|
|
44
|
-
- **本地与联网并行**:需求两可时并行调用,互不阻塞,先到的先答。
|
|
45
|
-
- **不把联网结果当本地文件**:交接后答案来自网络,措辞上要区分来源,
|
|
46
|
-
需要文件路径的请求不能用网页内容冒充。
|