[
 {
  "id": "T01",
  "target": "ai_session_search_archive",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不能直接用那份旧 txt，而且缓存里有向量也照样会漏结果。\n\n原因：在语义搜索里，`--file-list` 不只是\"要索引哪些文件\"的清单，它同时是**结果 allowlist**。即使某个文件的向量已经在 `.knowledge_cache` 里，只要它不在这次传入的 file list 中，就不会被返回。所以用几周前的 txt 会静默漏掉之后新增的会话，而且你从结果上看不出漏了。\n\n正确做法是每次查询时按当前 source scope 现场生成文件列表：\n\n```bash\nFILELIST=\"$(mktemp)\"\ntrap 'rm -f \"$FILELIST\"' EXIT\nrg --files contexts/ai_sessions/{opencode,claude_code,codex,antigravity,second_mind}/ \\\n  -g '*.md' > \"$FILELIST\"\n\nsemantic-search query \\\n  --file-list \"$FILELIST\" \\\n  --cache-dir .knowledge_cache \\\n  --query '你记得的那个概念' \\\n  --top-k 10 \\\n  --no-refresh\n```\n\n几点补充：\n\n1. **范围按需收窄**：如果你确定是某个来源（比如 claude_code），只把那个目录写进 `rg --files`，别照抄全部五个。\n2. **`--no-refresh` 保持只读**：索引刷新是单独的维护动作，一次查询不该顺手重建大缓存。但这也意味着新文件可能还没向量——如果现场生成的列表里有文件不在缓存中，先单独跑一次索引刷新，再查询。\n3. **先做词法搜索**：如果你还记得产品名、人名、项目名、标题或 session id，先用 `rg -i -n --glob '*.md' '变体1|变体2'` 搜一遍，比语义搜索更准。注意 glob 匹配的是文件名不是内容，结果被截断也不代表没有。\n4. **新鲜度检查**：如果目标会话比最近一次成功导出还新，归档里根本没有它，得先做一次临时导出，查完删掉。\n5. **结果整理**：按 source + session id 归组，一条会话只出现一次；展示标题、日期、来源、项目短名和一段原文摘录供你核对，不要用 AI 摘要替代证据，也不要显示 embedding 分数。",
  "error": null,
  "n_skills": 10,
  "target_pos": 9,
  "prompt_tokens": 15114,
  "completion_tokens": 495,
  "sec": 4.685781955718994,
  "cost": 0.0025641,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T02",
  "target": "compressor",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "`-format json` 必须紧跟在 `-monitor` 后面，不能放在命令末尾。\n\n正确写法：\n\n```bash\n\"/Applications/Compressor.app/Contents/MacOS/Compressor\" \\\n  -monitor \\\n  -format json \\\n  -batchid \"<batch_id>\" \\\n  -once\n```\n\n放在末尾（例如 `-monitor -batchid \"<batch_id>\" -once -format json`）会报 `Invalid parameter: -format`。\n\n补充两点：\n\n- 需要持续监控时，去掉 `-once`，改用 `-query <seconds>` 和 `-timeout <seconds>`，`-format json` 仍然紧跟 `-monitor`。\n- 拿到 `batchID` 只说明批次已入队，不代表真的在编码。用 `-monitor` 确认 `percentComplete` 和 `timeElapsedSeconds` 在真实增长，并对照 `~/Library/Logs/Compressor/servicecontroller:com.apple.stomp.transcoder.stomp.log` 里的 `starting _processRequest` / `Done _processRequest` 记录。",
  "error": null,
  "n_skills": 10,
  "target_pos": 4,
  "prompt_tokens": 16722,
  "completion_tokens": 235,
  "sec": 4.282392978668213,
  "cost": 0.0026493000000000003,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T03",
  "target": "deployment_github_actions_koyeb",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "在 `koyeb/action-git-deploy@v1` 那一步里加：\n\n```yaml\ngit-builder: \"docker\"\n```\n\n同时把 Dockerfile 路径也显式写上（否则可能找不到或走默认）：\n\n```yaml\ngit-docker-dockerfile: \"Dockerfile\"\n```\n\n完整那一步长这样：\n\n```yaml\n      - name: Deploy\n        uses: koyeb/action-git-deploy@v1\n        with:\n          app-name: \"<app-name>\"\n          service-name: \"<service-name>\"\n          git-branch: \"master\"\n          git-builder: \"docker\"                  # ← 强制容器构建\n          git-docker-dockerfile: \"Dockerfile\"    # ← Dockerfile 相对仓库根目录的路径\n          service-regions: \"na\"\n          service-ports: \"8000:http\"\n          service-routes: \"/:8000\"\n```\n\n判断依据：如果部署日志里出现 `Installing Python 3.x`（或 Node 之类）而不是 Docker 构建步骤，就是没指定 `git-builder`，Koyeb 检测到仓库里的 Python/Node 文件后回退到了 buildpack。\n\n另外两个容易一起踩的点，顺手确认一下：\n\n- Koyeb 控制台里该服务的 **Autodeploy 要关掉**（Settings → Source → Autodeploy），否则部署时机不受 Actions 控制。\n- Dockerfile 里要有 `CMD`，并且监听 `${PORT:-8000}`、绑定 `0.0.0.0`，否则会出现 `no command to run your application` 或 502。",
  "error": null,
  "n_skills": 10,
  "target_pos": 4,
  "prompt_tokens": 15500,
  "completion_tokens": 351,
  "sec": 4.703473806381226,
  "cost": 0.0025356,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T04",
  "target": "mac_universal_clipboard",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "可以。下面这条命令就是完整的一行版本，包含偏好项路径和三个需要重启的 daemon：\n\n```bash\ndefaults delete ~/Library/Preferences/com.apple.coreservices.useractivityd.plist ClipboardSharingEnabled 2>/dev/null; \\\ndefaults write ~/Library/Preferences/com.apple.coreservices.useractivityd.plist ClipboardSharingEnabled 1; \\\nkillall useractivityd 2>/dev/null; \\\nkillall sharingd 2>/dev/null; \\\nkillall pboard 2>/dev/null; \\\nsleep 2; \\\npgrep -fl 'useractivityd|sharingd|pboard'\n```\n\n拆开说明：\n\n- **偏好项位置**：`~/Library/Preferences/com.apple.coreservices.useractivityd.plist`，键名 `ClipboardSharingEnabled`。先 `delete` 再 `write ... 1`，是为了清掉可能存在的过期/损坏值，再显式置为开启。\n- **需要重启的进程**（三个）：\n  - `useractivityd` — 管 Handoff / Continuity activity 状态，包括 Universal Clipboard 偏好和共享剪贴板 blob\n  - `sharingd` — 管附近设备发现和 sharing transport\n  - `pboard` — 管本地 pasteboard 状态\n- **最后一条 `pgrep`** 是验收：这三个进程会被 launchd 自动拉起并重新出现在列表里。如果没出现，说明没被拉起，需要进一步排查。\n\n执行后请分别测试两个方向：iPhone/iPad → Mac 复制粘贴，以及 Mac → iPhone/iPad。\n\n几点补充：\n\n1. 你提到\"重启也不行\"，这其实符合这个故障的特征——问题出在 Mac 端 Continuity daemon 持有的过期状态，重启电脑不一定清掉，而重写这个偏好项并重启这三个 daemon 成本更低。\n2. 如果这条快速修复无效，再按顺序升级：先在 Mac 的 System Settings → General → AirDrop & Handoff 和 iOS 的 Settings → General → AirPlay & Handoff 里关开一次 Handoff；仍无效则让 Mac 进一次 Safe Mode 再正常重启；再不行才考虑删除 Keychain 里的 `handoff-own-encryption-key`，把退出 iCloud 再登录留作最后手段。\n3. 如果你的 macOS 是 beta 版本，快速修复失败后优先按系统回归判断，不要反复叠加更重的本地改动。",
  "error": null,
  "n_skills": 10,
  "target_pos": 3,
  "prompt_tokens": 15020,
  "completion_tokens": 527,
  "sec": 5.443295001983643,
  "cost": 0.0025691999999999998,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T05",
  "target": "openreview",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "你描述的症状（连续查询后全部 403、账号没变）最符合 **OpenReview API** 的登录 rate limit：每个时间窗口最多 3 次登录，CLI 靠 token 缓存避免重复登录，token 过期后就会连续 403。\n\n**立刻恢复的做法：**\n\n```bash\nrm tmp/openreview_token.json\n```\n\n然后重试一次原命令即可（CLI 会重新登录一次并写回新 token）。注意只重试一次——如果又失败，不要连续重试，否则会继续消耗登录窗口。\n\n**几点补充判断：**\n\n- 这个缓存路径是相对你运行 CLI 的工作目录的（skill 里写的是 `tmp/openreview_token.json`）。如果你在别的目录跑，先确认 token 文件实际落在哪，删对文件才有用。\n- 删缓存只解决\"token 过期/失效导致的 403\"。如果删掉重登后仍然 403，那就不是本地缓存问题，而是账号侧或窗口限流还没恢复，此时应等待窗口过去，而不是反复删缓存重试。\n- 如果你说的 403 其实来自别的服务（比如 Maven 的 CDP 会话、Firewalla、Cursor CLI），那对应的是另一套机制，不是这个 token 文件。请确认一下报 403 的具体是哪个工具/哪条命令，我可以给出对应的恢复步骤。",
  "error": null,
  "n_skills": 10,
  "target_pos": 5,
  "prompt_tokens": 15544,
  "completion_tokens": 292,
  "sec": 4.815154790878296,
  "cost": 0.0025068,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T06",
  "target": "workflow_bilibili_whisper_transcription",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "按工作流文档的规定：**单线程下载，每次间隔 2–3 秒**。\n\n具体做法：\n\n1. **不要并发**。下载阶段固定单线程，逐个下载音频，这是避免触发 B 站反爬的核心决策（转录阶段才用多进程 4–8）。\n2. **每个视频之间 sleep 2–3 秒**，不要连续无间隔请求。\n3. **带上 User-Agent / Referer headers**。文档里 352 错误（请求被 B 站拦截）的解决方案就是这三条组合：加 headers、增加延迟、或手动获取视频 ID。\n4. **只下载音频**，减少请求量和体积。\n5. **记录失败 ID**，遇到 352/404 时跳过并记下来，不要让脚本中断，之后单独重试。\n\n如果 2–3 秒仍然被拦，按文档的踩坑表处理：进一步加大延迟、确认 headers 生效，或改为手动获取视频 ID 列表后再逐个下载。不要靠提高并发来\"抢时间\"，那正是触发风控的原因。\n\n另外提醒一点：下载和转录的中间产物放在 `tmp/<task_name>/`，最终只把纯文本 transcript 落到 `adhoc_jobs/videos_transcribe/transcripts/`，音频和字幕文件在落盘后清理。",
  "error": null,
  "n_skills": 10,
  "target_pos": 4,
  "prompt_tokens": 15594,
  "completion_tokens": 280,
  "sec": 5.228631019592285,
  "cost": 0.0025071,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T07",
  "target": "workflow_watchdog",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "默认约 **30 分钟（1800s）** 设一次巡检 wake-up。\n\n在 Claude Code harness 里用 **`ScheduleWakeup`** 这个调度动作来设（其他 harness 用对应的定时机制，比如 Process Launcher 的延时执行）。\n\n醒来后自己判断两种情况，不用问用户：\n\n1. **真实在忙**（有新输出、agent 在持续产出）→ 不管它，继续等，或再设一次 wake-up。\n2. **鬼打墙卡住**（长时间无进展、同一步骤反复重试）→ 用 `TaskStop` 把它 kill 掉，用已有的部分结果推进，或换方法重做。\n\n巡检和 kill 属于技术编排决策，自己推到底即可。",
  "error": null,
  "n_skills": 10,
  "target_pos": 0,
  "prompt_tokens": 14423,
  "completion_tokens": 159,
  "sec": 3.6179182529449463,
  "cost": 0.0022588499999999997,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T08",
  "target": "workflow_parallel_subagents",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不行。`subagent_type` 必须是**已注册的 agent 名**，不是模型名。`provider/model` 只是底层模型入口，直接写 `deepseek/deepseek-v4-pro` 会报 `Unknown agent type`，因为 OpenCode 执行的是 `agent.get(subagent_type)`，找不到同名 agent 就失败。\n\n## 正确做法\n\n**1. 把模型包成 agent**\n\n在 `~/.config/opencode/opencode.json`、项目 `opencode.json`，或 `.opencode/agent(s)/*.md` 里注册一个 agent，让它指向该模型。例如：\n\n```\nwriter_deepseek -> deepseek/deepseek-v4-pro\n```\n\n**2. 用注册后的 agent 名调用**\n\n```json\n{\n  \"tool_uses\": [\n    {\n      \"recipient_name\": \"functions.task\",\n      \"parameters\": {\n        \"description\": \"中文写作\",\n        \"subagent_type\": \"writer_deepseek\",\n        \"prompt\": \"……\",\n        \"task_id\": \"\",\n        \"command\": \"\"\n      }\n    }\n  ]\n}\n```\n\n## 现成可用的替代\n\n如果只是想要 DeepSeek V4 Pro 的能力，当前环境里已经有注册好的 agent，不必自己配：\n\n| subagent_type | 说明 |\n|---|---|\n| `writer_deepseek` | DeepSeek Pro 路线，中文写作与改稿；内容不敏感时用 |\n| `ollama_deepseek_pro` | Ollama Cloud DeepSeek V4 Pro，zero-data-retention，较贵，适合隐私姿态要求高且需要更强 DeepSeek Pro 的任务 |\n\n## 几个容易踩的点\n\n- **不要用旧路由名**：`category=\"deep\"`、`category=\"writing\"`、`librarian`、`ultrabrain`、`glm51` 这类写法在当前工具里不被接受，除非它们已经被注册成同名 agent。\n- **`general` 不是便宜模型**：未配置模型时它继承主会话模型，别拿它当低成本选项。\n- **并行要真的并行**：所有 `functions.task` 必须放在**同一条 assistant 消息**里，用 `multi_tool_use.parallel` 包裹。逐个连续调用即使嘴上说并行，实际也是串行。\n- **不要用 `run_in_background`**：当前工具没有这个参数，也没有 `background_output` 取结果流程；`multi_tool_use.parallel` 会在同一轮返回所有结果。\n- **语言要显式声明**：在 prompt 里写明 `LANGUAGE: 用中文思考与输出`，否则 subagent 常回落到英文。\n\n一句话：模型名不能当 `subagent_type`，先在配置里注册成 agent，再用 agent 名调用；或者直接用已有的 `writer_deepseek` / `ollama_deepseek_pro`。",
  "error": null,
  "n_skills": 10,
  "target_pos": 8,
  "prompt_tokens": 18682,
  "completion_tokens": 616,
  "sec": 5.9820311069488525,
  "cost": 0.0031719,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T09",
  "target": "codex_cli",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "在 Codex CLI 里关掉 `web_search`，写法是**一个裸的顶层键**，值是**字符串** `\"disabled\"`：\n\n```toml\nweb_search = \"disabled\"\n```\n\n三个要点：\n\n**1. 值必须是字符串 `\"disabled\"`，不能用布尔值。**\n在 0.156.1 上只有 `\"disabled\"` 能解析通过；`false`、`on`、`off`、`enabled` 都会被拒绝，报错形如 `expected string only in 'web_search'`。所以 `web_search = false` 是无效的。\n\n**2. 必须放在顶层，且在所有 `[section]` 表头之前。**\nTOML 会把裸键绑定到它前面最近的那个 table。如果你把它写在 `[profile.xxx]` 之类的 section 下面，它就会变成那个 section 的成员，而不是顶层键，起不到作用。所以放在文件最上面、任何 `[section]` 之前。\n\n**3. 放哪个文件。**\n可以放在基础配置文件（`$CODEX_HOME/config.toml`）里，也可以放在这次运行实际加载的 profile 文件里——profile 文件是叠加在基础配置之上的。二选一即可，不必两处都写。\n\n**为什么需要这一行：** 当前 Codex 构建每一轮都会声明一个 `web_search` 工具定义。OpenAI 兼容后端如果不实现它，整个 run 会以 HTTP 400 `the web_search tool is not supported` 失败。关掉这个声明后，后端就不会再收到它。\n\n**验证方式：** 改完后跑一次 `codex exec --help` 之外的实际调用（比如一个便宜的 `codex exec` 一次性任务），确认不再出现 400。注意升级 Codex 后要重新确认——这个 CLI 的 flag 和配置键在小版本之间会变（0.144.6 → 0.156.1 就删掉了 `--full-auto` 和 `--ask-for-approval`），所以别把这次的写法当成永久契约。",
  "error": null,
  "n_skills": 10,
  "target_pos": 1,
  "prompt_tokens": 16072,
  "completion_tokens": 449,
  "sec": 5.245563983917236,
  "cost": 0.0026801999999999998,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T10",
  "target": "ai_session_profanity_rate",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不能，而且这正是文档明确禁止的做法。\n\n## 为什么不能混\n\n`ai_session_profanity_rate` 的 workflow contract 里写得很直接：\n\n> Do not combine an archive with its corresponding native stores in one run; that counts the same messages twice.\n\nMarkdown 归档（AI Session Export 的产物）就是从本机原始会话库导出的。同一次 run 里同时喂 `--source archive` 和 native stores，同一批消息会被计两遍——消息条数、incidence rate、per-100-messages 单位数全部虚高，而且虚高比例取决于你导出窗口和统计窗口的重叠程度，不是个固定系数，事后没法校正。\n\n## 该选哪个\n\n**优先用 native stores**，理由是文档给的：\n\n> Prefer native stores when available because they preserve exact timestamps and stronger per-message model attribution.\n\n具体差异：\n\n- **时间戳**：native 有精确时间戳；archive 只存 session 日期 + 本地 `HH:MM`，跨午夜要靠 turn 顺序推断。\n- **模型归属**：native 有 per-turn model metadata；archive 只有新归档才带 index 对齐的 `turn_models` 数组，旧归档是 `archive_unavailable` / `Unknown`，而且文档明确说 **never guess from session-level `models_used`**。模型构成图会因此变糊。\n\n所以只要本机原始库还在、还能读，就走 native，别用 archive。\n\n## 什么时候才用 archive\n\n只有一种情况：**可移植性**。比如换机器、原始库不在手边、或者要复现别人机器上的统计。这时先跑 AI Session Export，然后只选 archive 输入：\n\n```bash\nai-session-profanity-rate prepare \\\n  --source archive \\\n  --archive-dir ~/.local/share/ai-session-export \\\n  --timezone <timezone-used-by-exporting-machine> \\\n  --classifier-profile <approved-classifier-profile>\n```\n\n注意 `--timezone` 要填**导出那台机器**用的时区，不是你现在这台。\n\n## 如果你确实想覆盖两个来源\n\n正确做法是**分两次 run**，各自独立出 `results.json` 和 `summary.json`，然后自己合并——但合并前必须先确认两个窗口的消息集合没有交集，否则还是重复计数。实际上更省事的判断是：archive 覆盖的窗口如果是 native 窗口的子集，那 archive 这次 run 就是纯冗余，直接跳过。\n\n## 顺带两个容易踩的点\n\n1. **`--output` 必须落在 workspace 内的 `tmp/` 下**，比如 `tmp/profanity_runs/<stamp>-<days>d`。默认的 `--home/runs/` 在 workspace 树外，会触发用户审批弹窗卡住流程。持久 SQLite cache 留在默认 `--home` 位置不动。\n2. **`--assume-source-model` 只在用户明确确认整个窗口只用一个模型时才用**，它只填缺失值并记 `configured_source_default` 归属，不能拿来推断或静默套用。\n\n另外无论走哪条路，验收时都要确认 `unresolved_count` 为零、summary 计数等于 `results.json` 的直接聚合，并且同 profile 重跑应该产生零个新 request batch（除非源文本变了）——这条正好也能帮你验证有没有重复喂。",
  "error": null,
  "n_skills": 10,
  "target_pos": 6,
  "prompt_tokens": 15450,
  "completion_tokens": 729,
  "sec": 7.14920711517334,
  "cost": 0.0027549000000000002,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T11",
  "target": "apple_photos",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不能一次性发起，也不能复用授权或重试不确定的批次。\n\n**分批上限：每批最多 50 对。**\n\n按 Apple Photos skill 的硬边界：\n\n- **Delete Batch Bound**：每个 delete manifest 限制为 50 对。更大的证据集必须拆成独立的 plan，每一份都要重新走一遍 review 和授权，不能沿用上一批的 token。\n- 你这次约 120 对，至少要拆成 3 批（例如 50 + 50 + 20），每批各自生成独立的 manifest、各自重新审阅、各自重新授权。\n\n**授权不能复用：**\n\n- 删除 token 是**短时效、单次使用、绑定 manifest** 的 HMAC token，校验项包括 action、manifest、snapshot、count、expiry、nonce 和 CLI major version。\n- nonce 在 helper 执行前就被消费掉，所以同一个 token 无法用于第二批，也无法重放。\n- 每批都需要新的、独立的显式人工授权。AI agent 必须在 `delete authorize` 提示处停下，通过提问工具或对话拿到**最新且明确**的人类确认后才能输入确认短语；不得用管道或 PTY 注入短语。\n\n**结果不确定的批次不能重试：**\n\n- `partial` 或 `outcome_unknown` 一律视为事务失败。处理方式是**只读对账**（reconcile read-only），**绝不自动重试**。\n- 删除派发刻意不设客户端超时，因为 `performChangesAndWait` 可能在 macOS 弹出删除确认窗口时阻塞，进程会一直持有 library 锁直到操作者应答。所以执行时人要守在电脑前。\n- 如果进程被外部中断或崩溃，结果就是 unknown：不要重试，而是通过全新的 PhotoKit 和 library 读取，对每一个冻结的 candidate identifier 逐一核对。\n\n**另外几条会影响你这次操作的约束：**\n\n- 删除只把通过像素校验的候选移到「最近删除」（Recently Deleted），不做永久擦除，也不得绕过 macOS 的删除确认弹窗。\n- 只有「字节不同、单资源、静态图像」的配对才符合删除资格。Live Photo、RAW+JPEG 或已编辑的复合资产、不可用资源、非照片资源都要拒绝。要求尺寸相同、RGB MAE ≤ 1%、luma MAE ≤ 1%、RGB P99 ≤ 5%。字节完全相同的重复项不支持删除，请用 Photos 自带的重复项处理功能。\n- 每批执行前都要有确定性的、带完整性校验和的冻结 JSON manifest，并且执行的操作要与已审阅的 library snapshot digest 一致。\n- 这是 live-unverified alpha，不要在正式的生产图库上跑 mutation 命令。\n\n**建议的落地顺序：**\n\n1. 先把 120 对按 50/50/20 拆成 3 份独立 plan。\n2. 对每份 plan 单独跑 `evidence compare-images`，确认全部通过且字节不同。\n3. 逐份生成冻结 manifest，逐份审阅。\n4. 逐份执行：每份都重新拿一次显式人工授权，人在电脑前应答 macOS 确认框。\n5. 每份结束后只读读回，确认目标资产已进入「最近删除」且状态为终态；出现 `partial`/`outcome_unknown` 就停下对账，不要重试。",
  "error": null,
  "n_skills": 10,
  "target_pos": 8,
  "prompt_tokens": 16057,
  "completion_tokens": 724,
  "sec": 5.450033903121948,
  "cost": 0.00284295,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T12",
  "target": "asr",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不能直接用。2 小时 10 分钟 = 7800 秒，超过 7200 秒上限。\n\n**在什么阶段被拒绝**：在**加载模型之前**就被拒绝——单个文件超过 7200 秒会在任何模型加载前直接报错，不会浪费一次 diarization/ASR 推理。\n\n**切成多段能解决吗**：能绕过长度限制，但有两个必须接受的后果：\n\n1. **说话人字母不跨切片延续**。切出来的每一段是独立文件，diarization 各自编号，所以同一人在第 1 段是 `A`、在第 2 段可能变成 `B`。跨段的说话人身份需要你自己在编辑阶段用 `name_map` 重新映射统一。\n2. **注意\"总时长\"不算数**。限制是针对**单个文件**的：一天里多个文件加起来很长没问题，只有单个文件超 7200 秒才被拒。\n\n**具体做法**：\n\n- 用 ffmpeg 切成若干段，每段留出安全余量（比如每段 30–60 分钟，远低于 7200 秒），切点尽量落在静音处，减少把一句话劈成两半。\n- 每段单独跑 `asr-skill run`（或 `diarize` + `align`），各自产出 `transcript.raw.csv` / `transcript.timed.csv`。\n- 对每段分别执行 `prepare-edit --rich ... --task-dir ...`，在 `edited.json` 里把该段的声学标签映射到真实姓名（`name_map` 中 `status: mapped` + 非空 `name`），跨段保持同一人用同一个名字。\n- 最后用 `finalize-edit` 逐段导出，再按时间顺序拼接。\n\n另外提醒：`--csv-format` 默认是 `timed`（`start,end,speaker,content`），拼接时按这个格式对齐时间轴即可；如果某段是空语音，会被记为 `empty_speech=true` 的完成态，不会无限重跑，这是正常结果。",
  "error": null,
  "n_skills": 10,
  "target_pos": 6,
  "prompt_tokens": 16097,
  "completion_tokens": 458,
  "sec": 4.269449949264526,
  "cost": 0.00268935,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T13",
  "target": "ct_education",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不行，绝对路径会被拒绝。\n\n`serve` 的 `--video-file` / `--rendered-video-file` 只接受**已完成 workspace 内部**的、已存在的普通 `.mp4` 文件的**相对路径**。绝对路径、路径穿越（`..`）、符号链接、文件不存在、非 `.mp4` 后缀都会在启动时直接失败并报 `E_VIDEO_FILE`。HTTP 请求也无法在运行时改选别的文件。\n\n正确做法：\n\n1. 把 MP4 放进那个已完成的 workspace 目录里（例如 `/path/to/external/workspace/demo.mp4`）。\n2. 用相对 workspace 的路径传参：\n\n```bash\nct-edu serve --workspace /path/to/external/workspace --video-file demo.mp4\n```\n\n补充几点：\n\n- 视频默认是关闭的，必须显式传 flag 才会启用播放/下载。\n- 两个 flag 可以单独用：`--video-file` 是 demo（默认优先播放），`--rendered-video-file` 是渲染影片；只给后者时回退到它。\n- 下载名是固定的：demo 为 `ct-education-tour.mp4`，影片为 `ct-education-film.mp4`。\n- 如果你要放的是 `render-cinematic` 的成片，注意 cinematic 的输出目录与 viewer workspace 是**不相交**的，把最终 MP4 拷进 workspace 需要**显式批准**；生成本身不构成自动暴露授权。而且只应拷贝最终成片，不要暴露中间产物或整个 run 目录。\n- 另外，`/tmp` 下的文件通常也不满足\"外部私有 workspace\"的路径约束（仓库、输入、输出必须两两不相交），所以别指望直接把 `/tmp/xxx.mp4` 指过去。",
  "error": null,
  "n_skills": 10,
  "target_pos": 0,
  "prompt_tokens": 20737,
  "completion_tokens": 396,
  "sec": 4.349023342132568,
  "cost": 0.00334815,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T14",
  "target": "dataforseo",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "这是 DataForSEO 账号**已认证但未完成验证**的典型症状，不是 CLI 或 sandbox 的问题。\n\n## 判断依据\n\n`40104` 的含义是账号通过了 Basic Auth 认证，但账号本身还没在 DataForSEO 面板里完成验证。所以：\n\n- `balance` 能跑通 —— 账号/余额类端点对未验证账号仍然开放\n- 所有关键词数据端点（`keyword-suggestions`、`related-keywords`、`keywords-for-site`）被拦 —— 包括 sandbox 端点，因为 sandbox 也走同一套账号验证门\n\n这也解释了为什么切到 `--base-url https://sandbox.dataforseo.com` 没用：sandbox 只改变数据来源，不绕过账号验证状态。\n\n## 解决办法\n\n**唯一有效的动作是让账号持有人去 DataForSEO 面板完成验证**，这一步 Agent 做不了，也不需要改任何 CLI 参数或环境变量。\n\n具体做法：\n\n1. 登录 DataForSEO 控制台（app.dataforseo.com）。\n2. 找到账号验证 / verification 相关入口，按提示提交所需信息（通常是身份或公司信息、以及付款方式确认）。\n3. 等验证状态变为已通过。\n\n## 验证是否修好\n\n验证通过后，用最小请求确认，不要一上来就跑大批量：\n\n```bash\ncd adhoc_jobs/dataforseo_skill/\nsource .venv/bin/activate\n\n# 先确认余额仍正常\n.venv/bin/dataforseo-skill balance\n\n# 再用 limit=3 的小请求探一下关键词端点\n.venv/bin/dataforseo-skill keyword-suggestions \"agent harness\" --limit 3 --location-code 2840 --language-code en\n```\n\n如果这条返回了关键词行而不是 `40104`，就说明验证已生效，可以继续正常使用。\n\n## 不要做的事\n\n- 不要反复重试或换 sandbox 地址 —— 状态没变，结果不会变，只会浪费调用。\n- 不要怀疑 `DATAFORSEO_AUTH_BASE64` 配错了。认证失败和未验证是两种不同的错误；`balance` 能通就说明凭据本身是对的。\n- 不要为了绕过而新建账号或换凭据 —— 新账号同样需要验证。\n\n一句话：**去面板完成账号验证，然后用小 limit 请求读回确认。**",
  "error": null,
  "n_skills": 10,
  "target_pos": 9,
  "prompt_tokens": 14867,
  "completion_tokens": 516,
  "sec": 4.399955987930298,
  "cost": 0.00253965,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T15",
  "target": "eink_diary",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不需要自己改时间窗，直接用 `--full-day` 就行。\n\n```bash\neink-diary run --full-day\n```\n\n具体取哪段时间：本地时间 00:00–02:00 之间，`--full-day` 会自动解释为**昨天完整一天**，即 `昨天 00:00 .. 今天 00:00`。你现在是凌晨 1:30，落在 00:00–02:00 这个区间里，所以取的是昨天一整天（昨天 00:00 到 今天 00:00）。\n\n几点提醒：\n\n- 不要手算 `--minutes` 去凑全天，skill 明确说用户说\"今天全天/全天版本\"时优先用 `--full-day`。\n- 其他时间（非 00:00–02:00）跑 `--full-day` 才是\"今天 00:00 到现在\"。\n- 如果只是想回放某个历史窗口，才用 `--end`，例如 `eink-diary run --end 2026-06-06T20:00`。\n- 只想出图不推屏，加 `--no-push`。",
  "error": null,
  "n_skills": 10,
  "target_pos": 0,
  "prompt_tokens": 17388,
  "completion_tokens": 248,
  "sec": 2.7138431072235107,
  "cost": 0.0027570000000000003,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T16",
  "target": "skill_google_maps_routing",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "根据 Google Maps Routing skill 文档，直接回答：\n\n## 参数怎么填\n\n**是的，`--departure-time` 和 `--arrival-time` 必须且只能提供其中一个**（\"Exactly one of `--departure-time` or `--arrival-time` is required\"）。两个都给或都不给都不行。\n\n计算\"现在出发\"的驾驶时间，用：\n\n```bash\nscripts/google_maps_routing route \\\n  --origin \"起点地址\" \\\n  --destination \"终点地址\" \\\n  --departure-time now \\\n  --format json\n```\n\n## 关于接近当前时刻的 ISO 时间戳\n\n**会被拒绝。** 文档明确说明：\n\n> Use `--departure-time now` for current traffic. The CLI avoids sending a fragile explicit near-current timestamp because Routes API may reject it as not sufficiently in the future.\n\n也就是说，CLI 内部刻意不发送一个接近当前时刻的显式时间戳，因为 Routes API 可能以\"时间不够靠后（not sufficiently in the future）\"为由拒绝它。对应的报错是：\n\n> `Timestamp must be set to a future time`\n\n## 结论\n\n- 想算\"现在出发\"：**用 `--departure-time now`**，不要自己拼一个当前时刻的 ISO 时间戳。\n- 如果确实要用 ISO 时间戳，就选一个**明显在未来**的、带时区的时间，例如 `--arrival-time 2026-04-05T13:00:00-07:00`。\n- 另外，`route` 直接传地址字符串即可，**不需要先 geocode**（除非用户明确要坐标）。\n\n返回结果里关注 `route.duration_seconds`（含实时路况）和 `route.static_duration_seconds`（无路况基准），以及 `leave_by_time`。",
  "error": null,
  "n_skills": 10,
  "target_pos": 4,
  "prompt_tokens": 15319,
  "completion_tokens": 395,
  "sec": 4.0759501457214355,
  "cost": 0.0025348499999999995,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T17",
  "target": "hybrid_ai_video",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不能直接重发。按 `hybrid_ai_video` 的规则，POST 超时或任务 ID 缺失属于**歧义提交（ambiguous submission）**，必须保留本次尝试记录和预算预留，**阻塞自动重发**，等 owner/服务方对账后再决定。\n\n具体做法：\n\n1. **不要发新的 POST**。重发可能造成重复计费或重复生成，而且你无法判断第一次是否已经建成。\n2. **保留现场**：把这次尝试的请求体、时间、预算预留写进私有 resume state（单一 ledger writer 或串行预留，避免多进程抢同一份预算）。\n3. **先查，不先建**：如果你在超时前已经拿到 `request_id`，就只用 `GET https://api.x.ai/v1/videos/{id}` 轮询恢复，**绝不重新 POST**。轮询要有界等待和截止时间。\n4. **如果连 request_id 都没有**：这就是典型的歧义状态，只能停下来，把情况报告给 owner，由 owner 或服务方对账（查账户任务列表/用量）确认是否已建成，再决定下一步。不要用\"换个 key\"\"换个模型\"绕过。\n5. **凭证相关**：如果超时同时伴随 401 或临近过期，暂停并交回凭证 owner 处理刷新，不要自己抢着刷新轮换 token。\n\n一句话：**有 ID 就 GET 轮询，没 ID 就冻结并上报对账，任何情况下都不要盲目重发 POST。**",
  "error": null,
  "n_skills": 10,
  "target_pos": 8,
  "prompt_tokens": 18973,
  "completion_tokens": 327,
  "sec": 3.7078468799591064,
  "cost": 0.00304215,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T18",
  "target": "ecg_plot",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "直接拒绝出图，不会裁剪。\n\n按 `ecg_plot` 的核对规则，全部样本必须落在 0 到 30 秒内。超出 6 条 5 秒窗口的点不会被裁掉，而是拒绝作图。\n\n具体行为是：\n\n- 核对失败时**仍然写完整性 JSON**（`--integrity-output` 指定的那个文件），但**不写 PNG**。这样避免把缺样、截断或坏点画成一条看起来完整的曲线。\n- 命令会以失败状态结束，标准输出里只有状态、比例提示和路径，不会把电压样本打到终端。\n\n所以你的处理路径是：先看完整性 JSON 里是哪一项没过（超范围的点、时间间隔、`truncated`、`voltage_status` 等），确认数据本身是不是导出时就被截断或采样频率不对，再决定是重新导出还是只取前 30 秒的合法窗口另存一份 JSON 再画。\n\n另外提醒两点：\n\n1. 输出文件名必须是新路径。如果图或完整性报告已经存在，命令会拒绝覆盖——重跑前先换个文件名或删掉旧的。\n2. 输入输出只能放在 `data/` 下且已被 gitignore 的路径（如 `data/exports/`、`data/raw/`）。仓库外的输出路径不受本项目 `.gitignore` 保护，用真实波形前要自己确认落点私有。\n\n命令形式（在 `adhoc_jobs/health_quantification/` 下）：\n\n```bash\n.venv/bin/python -m health_quantification.ecg_plot \\\n  --input data/exports/your_ecg.json \\\n  --output data/exports/your_ecg.png \\\n  --integrity-output data/exports/your_ecg_integrity.json\n```\n\n如果 matplotlib 没装，先 `uv pip install --python .venv/bin/python -e '.[plot]'`。",
  "error": null,
  "n_skills": 10,
  "target_pos": 3,
  "prompt_tokens": 15958,
  "completion_tokens": 416,
  "sec": 3.5777699947357178,
  "cost": 0.0026433,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T19",
  "target": "koyeb",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不行。`--tail` 是布尔开关，不是行数参数，`--tail 100` 会被当成多余的位置参数而报错或行为不符预期。\n\n按 Koyeb skill 的写法，实时跟随当前日志应该这样：\n\n```bash\npython3 scripts/koyeb_env.py -- services logs example-service --app example-app --tail\n```\n\n要点：\n\n1. **必须走 launcher**：所有 Koyeb CLI 命令都通过 `scripts/koyeb_env.py --` 转发，避免 token 出现在 argv 或 shell history 里。不要直接调 `koyeb` 二进制。\n2. **`--tail` 只表示\"跟随\"**，不接受行数。想限制行数/时间范围，用有界查询而不是 `--tail N`：\n\n```bash\npython3 scripts/koyeb_env.py -- services logs example-service --app example-app --type runtime \\\n  --start-time 2026-01-01T00:00:00Z --end-time 2026-01-01T00:10:00Z\n```\n\n3. **跟随模式要自己控制超时**：`--tail` 是流式的，不会自己结束。有界调用方（脚本、agent）必须自己设 streaming timeout 并主动终止，否则会一直挂着。\n4. **日志类型**：需要运行日志时显式加 `--type runtime`；不指定时注意区分 build/runtime/platform 日志。\n5. **别把 404 当服务挂了**：日志查询返回 root HTTP 404 不代表服务失败，要去看 container 状态和应用的 route 路径。\n\n所以结论：`--tail 100` 不行；实时跟随用 `--tail`（无参数），要限定范围就用 `--start-time` / `--end-time` 做有界查询。",
  "error": null,
  "n_skills": 10,
  "target_pos": 9,
  "prompt_tokens": 16624,
  "completion_tokens": 391,
  "sec": 3.131844997406006,
  "cost": 0.0027282,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T20",
  "target": "narwal",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "## 原因\n\n`NOT_READY` 是机器人**主动拒绝**了命令，不是工具或连接故障。`start`（全屋清扫）走的是 `clean/start_clean` 主题，这个命令**只在机器人处于充电座上（docked）时才生效**。你在客厅直接让它开始，机器人当前不在座上，所以回了 `NOT_READY`（退出码 20）。\n\n这是机器人的正常回答，不是 bug。**不要循环重试**——重试不会改变结果。\n\n## 怎么做它才会接受\n\n1. **先确认当前状态**，别靠猜：\n\n   ```bash\n   narwal-local snapshot\n   ```\n\n   看 `base_query.mode` 和 `task`：`standby` 且 task 字段为 null 是空闲；如果已经是某个 cleaning 模式且 `area_m2` / `elapsed_seconds` 有值，说明它正在清扫，那也不该再发 start。\n\n   注意：`is_docked` 和 `charging_state` **永远是 null**，不能用来判断是否在座上。要判断是否在充电，看 `watch` 窗口内或两次 snapshot 之间的**电量趋势**（上升即充电中）。\n\n2. **让它回到充电座**（如果不在座上）：\n\n   ```bash\n   narwal-local dock --yes\n   ```\n\n   建议先 `--dry-run` 预览一下要发的主题和 payload，确认无误再真发。`dock` 走 `supply/recall`，是慢操作，等它物理回座完成。\n\n3. **在座上后再发起清扫**：\n\n   ```bash\n   narwal-local start --yes\n   ```\n\n   想只扫指定房间就用：\n\n   ```bash\n   narwal-local clean --rooms 3,5 --yes\n   ```\n\n   房间 id 和 `map_id` 必须来自**当前**的 `get_map`（先跑 `narwal-local map` 拿），工具不会把房间 id 映射成你家的房间名。\n\n## 几个容易踩的点\n\n- **一次只开一个连接**：每个源 IP 只允许一个连接，别并行跑命令。\n- **`--yes` 是必须的**：控制命令缺 `--yes` 会直接以退出码 2 报用法错误，根本不会连机器人。\n- **`dock` 之后不要再叠一个 `stop`**：回座本身会结束任务。\n- **如果 `dock` 也被拒**（同样可能回 `NOT_READY` / `CONFLICT`），说明机器人当前状态不允许回座，这时用 `watch --duration 20` 看一段实时遥测，或直接看 App 的任务历史——这个工具没有历史，单次 snapshot 回答不了\"它之前为什么停\"。\n\n一句话：**先 `snapshot` 确认状态 → 不在座上就 `dock --yes` 回座 → 在座上再 `start --yes`**。",
  "error": null,
  "n_skills": 10,
  "target_pos": 2,
  "prompt_tokens": 18428,
  "completion_tokens": 632,
  "sec": 4.624017238616943,
  "cost": 0.0031434,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T21",
  "target": "skill_open_router_data_scraper",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "应该传 **canonical_slug**，也就是末尾带 8 位日期的那一版 slug，而不是短标识。\n\n具体做法：\n\n1. **优先让 CLI 自动解析**。`ords fetch <slug>` / `ords archive` 内部会自动把短 slug 解析成 canonical_slug，所以直接用短名走 CLI 是没问题的。\n\n2. **手动调接口时必须自己用 canonical_slug**。短 slug 不被 `model-activity` 接受，服务端会返回空数组（不是报错），所以看起来像\"没数据\"。\n\n3. **怎么拿到 canonical_slug**：先跑 `ords discover --top 20` 拿到 Top-N 模型列表，里面给出的就是带日期后缀的 canonical 形式；或者从 `rankings/models?view=day` 的返回里取。\n\n4. **顺带两个容易一起踩的坑**：\n   - 用 `view=day`，不要用 `view=month` / `view=week`——后两者返回的是累积值，数字会比实际大很多（约 12 倍）。\n   - `rankings/models` 批量层返回的 `reasoning` / `cached` 字段是 0，那是缺失 telemetry，不是真实的 0；需要靠补全层（Top-20 逐个 `model-activity`）在主键冲突时刷新成完整值。\n\n所以：**短 slug → 空数组；canonical_slug（末尾 8 位日期）→ 正常返回**。",
  "error": null,
  "n_skills": 10,
  "target_pos": 2,
  "prompt_tokens": 15324,
  "completion_tokens": 325,
  "sec": 3.0153632164001465,
  "cost": 0.0024936,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T22",
  "target": "skill_opencode_data",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "默认会展开子会话，想只动选中的会话本身要加 `--no-expand`。\n\n具体说明：\n\n**默认行为**：CLI 的 selector 默认会沿 session 的 `parent_id` 链向下展开，把根会话和它的 subagent 子会话一起纳入。所以按 `--selector title --prefix batch-` 选中的会话，其子会话也会被算进去。\n\n**只选本身**：加 `--no-expand`，此时只取 selector 的原始结果，不展开后代。\n\n**建议的操作顺序**（这是破坏性维护，先看再动）：\n\n```bash\n# 1. 先确认数据库存在\n.venv/bin/python -m opencode_skill stats --json\n\n# 2. 用 --no-expand 出计划，看解析出的 session/message/part 数量和 selector 描述\n.venv/bin/python -m opencode_skill plan --selector title --prefix batch- --no-expand\n\n# 3. 确认无误后再执行；想先复制验证就加 --no-delete\n.venv/bin/python -m opencode_skill apply --selector title --prefix batch- --no-expand \\\n  --dest ~/.local/share/opencode/opencode_archive.db --confirm\n```\n\n两点提醒：\n\n- `plan` 输出的 sample titles 可能含隐私内容，不要贴到公开日志里。\n- 如果 OpenCode 可能正在写源数据库，先让用户停掉写入方，再跑 `apply`。",
  "error": null,
  "n_skills": 10,
  "target_pos": 2,
  "prompt_tokens": 15433,
  "completion_tokens": 326,
  "sec": 2.9837779998779297,
  "cost": 0.0025105500000000003,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T23",
  "target": "video-scoring-audio",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "是的，测法有问题。**不要在测峰值前用 `-ac 1` 下混。**\n\n## 为什么错\n\n对相关立体声（比如单声道混音同时写进左右两个声道，旁白混音通常就是这种），`-ac 1` 会把两个声道相加，约 **+3 dB**。一个真实峰值 -1.5 dBFS 的混音，下混后测出来会变成约 1.19，于是被误报为削波。\n\n## 正确做法\n\n**峰值必须在所有声道上测，绝不在下混之后测。**\n\n```python\nimport subprocess, numpy as np\n\ndef load(path, rate=48000):\n    ch = int(subprocess.run([\"ffprobe\",\"-v\",\"error\",\"-select_streams\",\"a:0\",\n                             \"-show_entries\",\"stream=channels\",\"-of\",\"csv=p=0\",path],\n                            capture_output=True, text=True).stdout.split()[0])\n    raw = subprocess.run([\"ffmpeg\",\"-v\",\"quiet\",\"-i\",path,\"-f\",\"f32le\",\"-ar\",str(rate),\"-\"],\n                         check=True, stdout=subprocess.PIPE).stdout\n    x = np.frombuffer(raw, dtype=\"<f4\")\n    return x[: len(x) // ch * ch].reshape(-1, ch), rate   # 保留声道\n\nxc, sr = load(\"out.wav\")\nprint(float(np.max(np.abs(xc))))     # 峰值：对 xc（多声道）取，不是对下混后的 mono\n```\n\n下混只用于**包络（RMS envelope）和频谱质心**这类分析，不用于峰值：\n\n```python\nx = xc.mean(axis=1)   # 只有包络/质心才用 mono\n```\n\n## 交叉验证\n\n用 ffmpeg 的 `ebur128` 独立复核整体响度和真峰值：\n\n```bash\nffmpeg -i out.wav -af ebur128=peak=true -f null -\n```\n\n它会给出 integrated loudness（LUFS）和 true peak（dBTP）。真峰值应低于 0 dBTP（例如 -1.5 dBFS 的混音对应约 -1.5 dBTP）。如果这个数字和下混测出来的\"削波\"对不上，就说明问题出在下混那一步，而不是原始混音。\n\n## 一句话总结\n\n- 峰值/削波判定：在**原始多声道**上测，或直接用 `ebur128=peak=true`。\n- 包络、质心：可以下混成 mono 再算。\n- 报告削波前先确认自己没有在测量链里引入 +3 dB。",
  "error": null,
  "n_skills": 10,
  "target_pos": 4,
  "prompt_tokens": 19331,
  "completion_tokens": 579,
  "sec": 4.693845987319946,
  "cost": 0.00324705,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T24",
  "target": "speaker_notes",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "有硬性上限，而且分两层。\n\n**硬性门槛（Hard gate）**\n- 普通口播句子**不得超过 22 个词**。这是硬性上限，不是建议值。\n\n**常规区间**\n- 普通句子尽量控制在 **12–20 个词**之间。\n- 判断标准是\"一口气能说完\"：每个句子都要容易一次念完。\n\n**另外一条容易忽略的约束**\n- 词数达标不等于合格。一个句子最多承载**一个主信息 + 一个从句**。如果一句话里同时塞了条件、例外和后果，即使没超 22 词，也要拆开——按逻辑转折点拆，让条件、动作、后果各自成句。\n\n**压缩时的注意点**\n- 压缩篇幅时优先删整块内容（重复点、旁支轶事、已经写在幻灯片上的细节），不要靠删连接词来省词。删连接词省不了几个词，却会破坏口播语气。\n- 每次压缩后要重读一遍，把被顺手删掉的反问句、连接词和讲者指定的措辞补回来。\n\n**配套的机械检查**\n- 定稿后要跑一遍机械门槛：逐句查词数、统计总口播词数、核对每页备注与幻灯片是否对齐。不要只凭\"读起来顺\"就放过。\n\n顺带一个时间预算的换算：按大约 **135 词/分钟**规划，每段再留 **10–15%** 给演示、点击、视频和停顿。",
  "error": null,
  "n_skills": 10,
  "target_pos": 8,
  "prompt_tokens": 17023,
  "completion_tokens": 325,
  "sec": 4.4440529346466064,
  "cost": 0.00274845,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T25",
  "target": "skill_tavily",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不可以，`--stdout` 在 `search` 上是**未识别的参数**，会直接报用法错误。\n\n具体行为（按 skill 文档）：\n\n- **退出码是 2**（usage error），不是 0 也不是 1。\n- **不会发起任何网络请求**——参数解析阶段就失败了，所以不会消耗 Tavily 额度。\n- stdout 可能是空的，所以**不要**先 `json.loads(proc.stdout)`，要先检查 `proc.returncode`，失败时读 `proc.stderr` 并中止。\n\n## 为什么没有这个选项\n\n`search` 和 `extract` 的设计就是**完整结果永远不内联到 stdout**：完整 payload 写到本地 JSON 文件，stdout 只返回一个轻量 status 对象（含 `command`、`status`、`output_mode`、`output_path`、`payload_bytes`、`summary`、`payload_schema`）。这是刻意的，避免把大 payload 灌进 LLM 上下文。\n\n注意 `usage` 子命令**有** `--stdout`（`python -m tavily_skill usage --stdout`），别把两者搞混。\n\n## 想拿到完整结果，正确做法\n\n1. 正常跑 search，从 stdout 的 status JSON 里取 `output_path`：\n\n```bash\npython -m tavily_skill search \"latest AI news\"\n```\n\n2. 在代码里打开**这个精确返回的路径**（不要 glob、不要按 mtime 猜最新文件），只挑需要的字段：\n\n```python\nimport json, subprocess, sys\nfrom pathlib import Path\n\nproc = subprocess.run(\n    [sys.executable, \"-m\", \"tavily_skill\", \"search\", \"latest AI news\"],\n    capture_output=True, text=True,\n)\nif proc.returncode != 0:                      # 先查退出码，再解析\n    print(f\"Error (exit {proc.returncode}): {proc.stderr.strip()}\", file=sys.stderr)\n    sys.exit(proc.returncode)\n\nstatus = json.loads(proc.stdout)\nif (status.get(\"command\") != \"search\" or status.get(\"status\") != \"ok\"\n        or status.get(\"output_mode\") != \"file\" or not status.get(\"output_path\")):\n    print(\"Unexpected search status envelope\", file=sys.stderr)\n    sys.exit(1)\n\npayload = json.loads(Path(status[\"output_path\"]).read_text(encoding=\"utf-8\"))\nfor item in payload[\"data\"][\"results\"][:3]:\n    print((item.get(\"title\") or \"\")[:80], (item.get(\"url\") or \"\")[:300])\n```\n\n3. 如果你确实想指定落点，用 `--output`（stdout 仍然只是 status schema，不是完整结果）：\n\n```bash\npython -m tavily_skill search \"AI coding tools\" --output /tmp/tavily_search.json\n```\n\n4. 如果只是想**人肉看一眼**完整 JSON，直接 `cat` 那个 `output_path` 文件即可——但别把它整段贴进对话上下文。\n\n## 顺带两个坑\n\n- 运行目录：从 skill 仓库根目录用项目内解释器跑 `./.venv/bin/python -m tavily_skill ...`，否则 `.env` 里的 `TAVILY_API_KEY` / `ONEPASSWORD_TAVILY_REFERENCE` 可能加载不到。\n- 批量模式（多个 `--query`）下 `--output` 会被拒绝，每个 query 各写一个自动命名文件，stdout 只返回一个 batch envelope。",
  "error": null,
  "n_skills": 10,
  "target_pos": 0,
  "prompt_tokens": 19256,
  "completion_tokens": 782,
  "sec": 5.845440149307251,
  "cost": 0.0033575999999999996,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T26",
  "target": "zun_music",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不能随便改。规则很明确：\n\n**上限：每四小节最多一处**，也就是 8 小节里最多 2 处。而且通常这 2 处里只有最后终止式那一处是真正会用的。\n\n**合规判定方式**（`check()` 自动执行，`python scripts/render_example.py <tune> --midi-only` 会跑，失败则非零退出）：\n\n1. **强拍对齐**：4/4 里强拍是第 1 拍和第 3 拍。在每个强拍上，你的编曲的音级（pitch class）必须等于**未经改动的原谱**（`SOURCE`）在同一位置的音级。\n2. **例外必须登记**：每一处故意偏离都要写进 `skeleton_allow`，并且**每四小节最多一条**，正常情况只有最后终止式那一条。\n3. **小节完整性**：旋律、原谱、和弦的每一小节都要凑满拍数，只有最后一小节可以短。\n\n所以做法是：\n\n- 先把原谱原样存成 `SOURCE`，不要动它。\n- 编曲的 `MELODY` 在强拍上照抄 `SOURCE` 的音级。\n- 想加 ZUN 味道，把改动放在**弱拍和强拍之间**：3-3-2 切分、长音提前半拍、长音结尾加 16 分音符回旋（如 C–D–C–B–C）、乐句间用上行 16 分音符跑动连接。这些都不触发 skeleton 检查。\n- 唯一想改强拍的地方，留给终止式：和声小调 V7 下、旋律第 4 级音悬留出 ♭9 张力，最后落在小调主音上——把这一处写进 `skeleton_allow`。\n\n一个 8 小节乐句里，`skeleton_allow` 里超过 2 条就会失败；即使只有 2 条，也要确认其中一条确实是终止式，否则听感上会变成\"和原曲完全无关\"（这是参考会话里实际踩过的坑：改成平行小调、加密集装饰、乐句中途转调，被听者评价为\"完全不像原曲\"）。",
  "error": null,
  "n_skills": 10,
  "target_pos": 2,
  "prompt_tokens": 16732,
  "completion_tokens": 491,
  "sec": 5.325813055038452,
  "cost": 0.0028044,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T27",
  "target": "skill_image_generation",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不会生效，这是预期行为，不是你的用法错了。\n\n按 image generation skill 的说明：\n\n> `--quality low|medium|high|xhigh|max|auto` applies to GPT Image models. Gemini accepts the flag but ignores it because quality tiers are provider-specific.\n\n也就是说：\n\n- `--quality` 只对 GPT Image 系列（`gpt-image-2`、`gpt-image-2.5-sunburst`、`gpt-image-2.5-flare`）有意义。\n- Gemini（`gemini-flash` / `gemini-pro`）会**接受**这个参数、不报错，但**直接忽略**它，因为质量档位是各家 provider 自己的概念，Gemini 没有对应的映射。\n- 所以你加不加 `--quality high`，Gemini 出图结果都一样。\n\n想真正控制 Gemini 的输出，改用这些手段：\n\n1. **换模型档位**：`--model gemini-flash`（默认）vs `--model gemini-pro`。这是 Gemini 侧唯一实质性的质量/能力切换。\n2. **改 prompt**：Gemini 的质量差异主要来自提示词的具体程度（构图、材质、光线、镜头等描述）。\n3. **改尺寸/比例**：`--size 4K --aspect-ratio 16:9` 这类参数对 Gemini 是生效的。\n4. **要精细控制质量档位就换 GPT Image**，例如：\n\n```bash\ngenerate-image -p \"...\" -o output/x.png --model gpt-image-2.5-sunburst --quality high\n```\n\n补充两点实操提醒：\n\n- GPT Image 2.5 的档位比 2 更细：2.5 的 `high` 成本约为 GPT Image 2 `high` 的四分之一，2.5 的 `max` 才相当于旧的 `high`。所以从 Gemini 换过去时别直接照搬 `high` 的预期。\n- 如果只是想要快速草稿，用 `gpt-image-2.5-flare --quality low`；要最终成品用 `gpt-image-2.5-sunburst`。\n\n一句话：Gemini 下 `--quality` 是空操作，别在它上面调这个参数。",
  "error": null,
  "n_skills": 10,
  "target_pos": 1,
  "prompt_tokens": 15952,
  "completion_tokens": 484,
  "sec": 4.605816125869751,
  "cost": 0.0026832,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T28",
  "target": "skill_process_launcher",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "这是 Process Launcher 的**精确时刻冲突保护**（exact-time collision guard）。\n\n## 冲突是怎么触发的\n\n当你用 `delay_seconds` 或 `run_at` 提交定时任务，解析出的 `run_at` 与某个已存在的 pending 或 running 任务**完全相等**时，`POST /run` 返回 HTTP 409，body 里：\n\n- `detail.error = \"schedule_conflict\"`\n- `detail.message` 列出每个冲突任务的 id、label、command、status、run_at\n- `detail.conflicts` 给出完整的冲突任务对象\n\n**409 时不会创建任何东西**，所以你的两条任务里只有先到的那条存在。\n\n注意：这个守卫只匹配**完全相同的时刻**。两条相隔几秒、延迟相近的任务不会碰撞。\n\n## 方案一：两条都保留\n\n用同一个请求重发一次，加上 `\"confirm_conflict\": true`：\n\n```bash\ncurl -sf -X POST http://127.0.0.1:7997/run \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"command\": [\"python\", \"job_b.py\"],\n    \"label\": \"job_b\",\n    \"run_at\": \"2026-06-10T09:00:00-07:00\",\n    \"confirm_conflict\": true\n  }'\n```\n\n`confirm_conflict` 默认是 `false`；显式设为 `true` 表示你确认这个时间重叠是有意的，允许它与同一 `run_at` 上已 pending 的任务并存。\n\n## 方案二：用新的替换旧的\n\n先取消旧任务，再提交新任务（此时不再冲突，不需要 `confirm_conflict`）：\n\n```bash\n# 1. 找到旧任务 id\ncurl -sf 'http://127.0.0.1:7997/scheduled?status=pending&label=job_a'\n\n# 2. 取消它\ncurl -sf -X POST http://127.0.0.1:7997/scheduled/{job_id}/cancel\n\n# 3. 提交新任务\ncurl -sf -X POST http://127.0.0.1:7997/run \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"command\": [\"python\", \"job_new.py\"], \"label\": \"job_new\", \"run_at\": \"2026-06-10T09:00:00-07:00\"}'\n```\n\n## 相关细节\n\n- **改时间也会触发守卫**：`PATCH /scheduled/{job_id}` 把 `run_at` 移到冲突时刻时，同样返回 409，同样用 `confirm_conflict: true` 放行。\n- **只有 pending 能改**：`PATCH` 只对 pending 任务有效；running、completed、failed、cancelled、missed 的任务不能更新，只能取消 pending 的。\n- **`GET /scheduled` 默认只列非终态任务**（pending + running）。要查已结束的加 `?include_terminated=true`，或用 `?status=all` 关掉过滤。\n- **提交成功后的响应可能显示 `pid: 0`、`output_file: null`**，这是正常的——延迟任务还没启动子进程。用响应里的 `scheduled_job_id` 作为后续取消或审计的句柄。",
  "error": null,
  "n_skills": 10,
  "target_pos": 7,
  "prompt_tokens": 18049,
  "completion_tokens": 729,
  "sec": 5.031691074371338,
  "cost": 0.00314475,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T29",
  "target": "skill_resend_email",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "不行。`received list` 和 `received export-all-md` 的 `--after` / `--before` 是 Resend 的分页游标（cursor），不是时间过滤器。传 ISO 时间戳会被 API 当成 UUID 校验，直接返回 validation error。\n\n正确的按日期窗口筛选流程：\n\n1. **先验证配置**（可选但推荐）：\n   ```bash\n   op run --env-file=.env -- .venv/bin/python -m resend_email_skill.cli doctor config --format json\n   ```\n\n2. **导出足够大的一页到 Markdown**，不要用 `--after/--before` 传时间：\n   ```bash\n   op run --env-file=.env -- .venv/bin/python -m resend_email_skill.cli received export-all-md \\\n     --limit 100 \\\n     --output-dir data/received/markdown \\\n     --format json\n   ```\n\n3. **在本地按 `created_at` frontmatter 过滤**，筛出最近两天的文件。日期窗口分析完全在本地做，不依赖 API 参数。\n\n4. **先看元数据再读正文**：从元数据构建抽样清单，覆盖可操作邮件、财务/收据、安全验证码、服务告警、订阅续费、社区回复、newsletter、本地生活物流、明显营销噪音等类别。不要只读最新的几封——最新优先的样本会过度代表重复通知和促销。\n\n5. **隐私**：导出的 Markdown 和附件属于私有邮箱数据。不要提交 `data/received/`、下载的附件、原始 MIME 或暴露个人邮件内容的派生报告。头脑风暴时只引用最少必要事实，优先用类别级摘要而不是完整正文。",
  "error": null,
  "n_skills": 10,
  "target_pos": 6,
  "prompt_tokens": 16161,
  "completion_tokens": 372,
  "sec": 4.165566921234131,
  "cost": 0.0026473499999999993,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 },
 {
  "id": "T30",
  "target": "skill_typefully_twitter",
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "answer": "**默认用 long post 模式。**\n\n按 `skill_typefully_twitter` 的规则：文章分发帖（来自内容平台的）默认走 Typefully long post。\"只发一条帖子\"指的是一个 Typefully post，不是一条 280 字符的普通 X 推文——不要自己套上 280 字限制。\n\n具体做法：\n\n1. 先确认账号能力。设置阶段问用户：你的 Typefully/X 账号能否发 long post？\n   - 能 → 在本地 `.env` 里设 `TYPEFULLY_TWITTER_DEFAULT_LONG_POST=true`，之后 `post count`、`post draft`、`post create` 默认就按 long post 校验。\n   - 不能 → 不设或设为 false。\n\n2. 如果环境默认没开，就在命令上显式加 `--long-post`：\n   ```bash\n   typefully-twitter post count --thread-file thread.md --long-post\n   typefully-twitter post draft --thread-file thread.md --long-post\n   ```\n\n3. 如果 `post count` 返回 `TOO_LONG`，对分析性长文不要为了塞进限制而删内容——这通常说明内容本来就该用 long post 模式。\n\n**用户明确要短推时**：加 `--no-long-post`，切回普通 X 长度限制（280 字规则）。\n\n```bash\ntypefully-twitter post count --text \"...\" --no-long-post\ntypefully-twitter post draft --text \"...\" --no-long-post\n```\n\n触发 `--no-long-post` 的明确信号包括用户说\"普通推文\"、\"280 字内\"、\"short tweet\"，或直接指定该参数。只有这些明确要求才切普通模式。\n\n另外提醒两点：发之前用 `post count` 或 `post get` 检查实际发出的 `text`，第一行必须是给读者看的正文（不能是 `# Twitter copy`、`T 导流帖` 这类内部标签）；`--thread-file` 里的 Markdown 标题、frontmatter、注释都会被当成正文原样发出，需要的话另建一份干净的发布文件。",
  "error": null,
  "n_skills": 10,
  "target_pos": 7,
  "prompt_tokens": 16478,
  "completion_tokens": 461,
  "sec": 3.7185299396514893,
  "cost": 0.0027483,
  "web_tools": [],
  "n_calls": 1,
  "finish_reason": "stop"
 }
]