file-finder skill
Overview
把用户口语化的文件查找诉求("上周微信收到的那个报价单""关于双色球助手的 ppt")翻译为系统级搜索,返回打分后的候选文件列表。
采用两级检索:L1(快速扫描)秒级返回初筛结果,用户确认后可升级到 L2(语义精筛)做深度匹配。cowork 场景下 L1 有命中时会挂起等待用户确认是否升级 L2;L1 零命中则自动直接走 L2,不再询问。
通过 Bash 调用 scripts/find.py,stdout 返回 JSON envelope。调用方法详见「使用方法」。
何时调用
用户意图是定位本地文件在哪,且 query 中包含以下任意信号时调用:
- 文件名或路径片段 — "那个叫请假单的 word"、"Downloads 里的 markdown"
- 主题/内容描述 — "关于双色球助手设计的 ppt"、"提到环比增长15%的文档"、"那张有白板的照片"、"拍了投屏电视机的视频"
- 时间线索 — "上周编辑过的 excel"、"两三个月前输出的表格"
- 大小/类型约束 — "大于 1G 的视频文件"、"最占地方的几个文件"
- 来源应用 — "微信里收到的那个 pdf"、"钉钉群里发我的报价单"
- 集合列举 — "娟娟的学习资料都列一下"、"我电脑上那几个代码项目都有啥"
- 多信号叠加 — "上周编辑过的、大于10MB的Word"
不适用:
- 文件操作类(移动/重命名/删除/上传/整理归档)— 这些任务的"先找到文件"步骤可调本 skill,但主任务不归这里
使用方法
> $SKILL_DIR = Base directory for this skill(运行时自动注入的绝对路径)。直接用 python3 "$SKILL_DIR/scripts/find.py" 调用,无需 cd。
> Bash timeout 强制 1200000ms(20 分钟):本 skill 的每次 Bash 调用都必须设置 timeout: 1200000。L2 冷启动需全量建索引,耗时可达 5 分钟,默认 120s 必超时。
# ★ 标准调用(必须带 --gate=ask;--trace 可选,生成 HTML 排错报告)
python3 "$SKILL_DIR/scripts/find.py" --query "我那本最大的英汉词典放哪了" --gate=ask [--trace]
# 用户通过 interrupt 选择"再深入找找"后的第二轮调用
python3 "$SKILL_DIR/scripts/find.py" --query "<原 query>" --mode=l2_probe [--trace]
# 继续扩大搜索范围(须把上一轮 envelope.search_id 原样透传回来)
python3 "$SKILL_DIR/scripts/find.py" --query "<原 query>" --mode=l2_probe --expand --search-id "<上一轮 envelope.search_id>" [--trace]
参数:
| 参数 | 必填 | 说明 |
|---|---|---|
--query |
是 | 用户的自然语言查询 |
--gate=ask |
是(cowork) | L1 有命中时停下返回 interrupt 让用户决定是否升级 L2;L1 零命中则自动直接走 L2,不返回 interrupt |
--mode |
否 | l2_probe:深入查找,可逐步扩大搜索范围(第二轮升级用,见下);默认 full |
--expand |
否 | 仅 --mode=l2_probe 生效:不带则在最可能的位置查找一次;带则把搜索范围再扩大一次重新查找。可反复调用,每次扩大一点,直到 answer.scope_exhausted=true |
--search-id |
配合 --expand |
上一轮 envelope 的 search_id 值,原样传回即可(不必解析或改写)。规则:发 --expand 时必须带;不带 --expand 时不要传。 |
> 实时进度:搜索过程中,file-finder 会逐阶段向宿主推送中文进度提示(如「正在分析查询意图...」「正在按条件检索文件...」等),宿主自动实时显示给用户,缓解长时间搜索时的"无声"体感。Agent 既不需要解析这些提示,也不要在回复里引用它们 —— 最终结果仍以 stdout envelope 为准。
输出协议
stdout 输出一个 JSON envelope:
{
"schema_version": "1.0",
"search_id": "20260707213045A1B2C3D4",
"query": "我那本最大的英汉词典放哪了",
"answer": {
"status": "found | not_found | error",
"reply_text": "在 ~/Alice/books/ 找到 1 本字典:...",
"scope_exhausted": false
},
"hits": [
{"path": "/Users/x/Alice/books/朗文当代英汉.pdf",
"score": 0.92, "passed": true,
"evidence": "size=236MB matches '最大的'; filename contains '英汉'"}
],
"diagnostics": {"warnings": [], "error": null},
"interrupt": { /* 两种挂出:gate=ask 且 L1 有命中(l2_upgrade);l2_probe 命中但 scope 未穷尽(scope_continue)。无中断时 key 缺失 */ }
}
> answer.scope_exhausted 字段在深入查找结果里出现(--mode=l2_probe,以及首次 full 调用零命中时系统自动进行的深入查找)。见此字段时按「深入查找」一节的决策流处理:false=还有更大范围,可 --mode=l2_probe --expand 继续;true=已搜尽,停止扩大、如实告知用户「已搜遍所有可能位置仍未找到」。
Agent 处理逻辑(两层路由):
- 先看 interrupt:
"interrupt" in envelope→ 走中断态处理(下一节),不管 status 是什么。 - 无 interrupt 时按 status 行动:
| status | Agent 动作 |
|---|---|
found |
读取 hits 内所有字段理解命中结果,自行生成自然语言回答;reply_text 可作参考但非必须照搬 |
not_found |
若 answer 带 scope_exhausted 字段,优先按「深入查找」决策流处理(可能还能扩大范围继续找);否则告知用户"搜索完成,未找到匹配文件",可建议换个描述重试 |
error |
读 diagnostics.error.code 理解原因,用自然语言告知用户(如"搜索遇到问题,环境变量未配置") |
回复生成规则:
- Agent 可读取:envelope 全部字段(用于理解结果、判断逻辑)
- 用户可见:文件路径、文件名、Agent 自行组织的自然语言描述
- 禁止透出:score、passed、evidence、status、diagnostics、schema_version、search_id 等内部字段不得原样出现在面向用户的回复中
中断态处理(AskUserQuestion 流程)
cowork 场景下每次调用都带 --gate=ask。L1 有命中时会返回 interrupt 让用户决定是否升级 L2;L1 零命中时流水线自动跑完 L2,直接按 status(found/not_found)返回、不含 interrupt。因此务必用第 1 步的 "interrupt" in envelope 判断,不要假设一定有中断。
处理流程:
- 检查
"interrupt" in envelope(key 缺失=无中断)。 - 先回复用户:基于
hits(passed=true 的条目)和reply_text生成自然语言回复,把找到的文件展示给用户看。 - 再调 AskUserQuestion(必做,不可跳过):将
interrupt.questions里的question/header/options一字不改原样传给AskUserQuestion(不要改写、缩写或"润色"问句,options 只含 label/description),然后停下等待用户真实作答。 - 用户回答前禁止推进:
interrupt只是"需要问用户"的信号,不是用户的答复。绝不能因为看到interrupt(尤其是scope_continue_offer)就自行假设/脑补用户已选expand/run_l2并直接发下一条命令。没有拿到AskUserQuestion真实返回的 label 之前,不得执行第 5 步的任何分发动作。 - 用户选择后,用选中的 label 查
interrupt.action_map[label]得到路由标识,按下表分发:
| action | 下一步 |
|---|---|
accept |
用户已满意,结束(文件列表已在第 2 步展示过,无需重复) |
run_l2 |
跑 python3 "$SKILL_DIR/scripts/find.py" --query "<原 query>" --mode=l2_probe(Bash timeout 同样 1200000ms) |
cancel |
告诉用户已取消进一步搜索 |
expand |
用户想扩大范围继续找(l2_probe 命中但 scope 未穷尽时出现):发 python3 "$SKILL_DIR/scripts/find.py" --query "<原 query>" --mode=l2_probe --expand --search-id "<envelope.search_id>" 再扩一层;--search-id 从本轮 envelope 原样透传(Bash timeout 1200000ms) |
> 兜底:用户选择了"其他"自由输入(如"终止""算了""不找了"等)或选中的 label 在 action_map 中查不到时,一律按 cancel 处理——告知用户已结束搜索,不要报错或抛异常。
L2 超时或无 stdout 时的兜底:
- 若 L2 调用超时或 stdout 无有效 JSON envelope:回退到 L1 已有的
hits作为最终结果,告诉用户"深入搜索超时,以下是初步搜索结果"。
深入查找(--mode=l2_probe)
何时用:常规搜索(默认 full)没找到、需要进一步定位时的深入查找——先在最可能的位置查找,找不到再按需逐步扩大范围。两种进入方式:
- L1 有命中、用户在 interrupt 里选"再深入找找" → 按
action_map[run_l2]发--mode=l2_probe。 - 首次 full 调用即零命中时,系统会自动做首轮深入查找,并在 envelope 的
answer里带上scope_exhausted字段——此时无需再发升级命令,直接按下面决策流处理即可。
决策流(answer 带 scope_exhausted 字段即适用):
读 answer.status 与 answer.scope_exhausted:
found且带interrupt(scope_continue_offer,即命中但scope_exhausted=false)→ 先按「中断态处理」展示命中,再用interrupt.questions问用户「就用这些 / 扩大范围找找」,按action_map分发(accept=结束,expand=带--expand --search-id再扩一层)。found且无 interrupt(已穷尽或非续探场景)→ 展示命中,结束。not_found且scope_exhausted=false→ 还有更大范围没搜;要不要继续,由你自行判断,继续就发--mode=l2_probe --expand --search-id "<envelope.search_id>"再扩一层(可反复,每次扩大一点;--search-id从上一轮 envelope 直接读取原样透传即可)。not_found且scope_exhausted=true→ 已无更大范围可搜,如实告知未找到,不再--expand。
排错(开发者 debug 用)
遇到异常结果时,加 --trace 重跑生成 HTML trace 报告:
python3 "$SKILL_DIR/scripts/find.py" --query "..." --gate=ask --trace
常见排查方向:
diagnostics.error.code = bailian_api_key_missing:环境变量未配置(QueryCoT 复用BAILIAN_TOOL)status=not_found:查看 trace 中 L1 plan 和 FTS 命中情况,确认是否过严- 命中过多/嘈杂:检查 enrich 阶段的关键词质量,或缩小搜索范围重跑