# cc-linker 验收指南 本文档从实际使用场景出发,详细说明如何验收 cc-linker 的各项功能。 ## 一、环境准备 ### 1.1 前置条件 | 依赖 | 版本要求 | 检查命令 | 说明 | |------|---------|---------|------| | Bun | >= 1.0 | `bun --version` | 运行时和包管理器 | | Claude Code | 最新版 | `claude --version` | CLI 工具 | | @anthropic-ai/claude-agent-sdk | 最新版 | `bun pm ls` | SDK 模式依赖(Phase 2) | ### 1.2 目录结构 验收前确认以下目录存在: ```bash # Claude Code 会话目录 ls ~/.claude/projects/ # cc-linker 数据目录(初始化后创建) ls ~/.cc-linker/ # ├── registry.json # 会话注册表 # ├── user-mapping.json # 飞书用户映射 # ├── spool/ # 消息队列 # ├── backups/ # 备份目录 # └── config.toml # 配置文件(可选) ``` --- ## 二、安装验收 ### 2.1 从源码安装 ```bash # 步骤 1:克隆仓库 git clone https://github.com/xxx/cc-linker.git cd cc-linker # 步骤 2:安装依赖 bun install # 预期:无报错,node_modules 目录创建成功 # 步骤 3:类型检查 bun run typecheck # 预期:无类型错误 # 步骤 4:运行测试 bun test # 预期:所有测试通过 # 步骤 5:编译 bun run build # 预期:dist/cc-linker 文件生成 # 步骤 6:验证编译产物 ./dist/cc-linker --help # 预期:显示帮助信息,包含所有命令列表 # 步骤 7:加入 PATH export PATH="$(pwd)/dist:$PATH" cc-linker --help # 预期:同上 ``` **验收标准**: - [ ] `bun install` 无报错 - [ ] `bun run typecheck` 无类型错误 - [ ] `bun test` 所有测试通过 - [ ] `bun run build` 生成 `dist/cc-linker` - [ ] `cc-linker --help` 显示完整帮助信息 --- ## 三、初始化验收 ### 3.1 首次初始化 ```bash # 步骤 1:运行初始化 cc-linker init # 预期输出: # ✅ Created ~/.cc-linker/registry.json # 🔍 Scanning for existing sessions... # Found Y Claude Code sessions # ✅ Registered Y sessions total # # Next steps: # 1. Run 'cc-linker hook install' to install Claude Code hook # 2. Run 'cc-linker list' to view all sessions # 3. Run 'cc-linker resume' to resume a session # 步骤 2:验证目录结构 ls -la ~/.cc-linker/ # 预期:包含以下文件/目录 # - registry.json # 会话注册表 # - backups/ # 备份目录 # - scan_cache.json # 扫描缓存 # 步骤 3:验证 registry 内容 cat ~/.cc-linker/registry.json | jq . # 预期:JSON 格式,包含 version、updated_at、sessions 字段 ``` **验收标准**: - [ ] `~/.cc-linker/registry.json` 文件创建成功 - [ ] `~/.cc-linker/backups/` 目录创建成功 - [ ] `~/.cc-linker/scan_cache.json` 文件创建成功 - [ ] registry.json 格式正确,包含已扫描的会话 ### 3.2 重复初始化 ```bash # 步骤 1:再次运行初始化 cc-linker init # 预期输出: # 📁 Registry exists at ~/.cc-linker/registry.json, will refresh # 🔍 Scanning for existing sessions... # ... # 步骤 2:验证数据完整性 cc-linker list # 预期:显示会话列表,数据未丢失 ``` **验收标准**: - [ ] 重复初始化不会丢失已有数据 - [ ] 会话数量保持一致或增加(如有新会话) --- ## 四、Hook 安装验收 ### 4.1 安装 Hook ```bash # 步骤 1:安装 Hook cc-linker hook install # 预期输出: # Hook 安装成功 # 已添加到 ~/.claude/settings.json: # "hooks": { "SessionStart": "cc-linker hook session-start" } # 步骤 2:验证 settings.json cat ~/.claude/settings.json | jq .hooks # 预期: # { # "SessionStart": "cc-linker hook session-start" # } # 步骤 3:检查 Hook 状态 cc-linker hook status # 预期: # Hook 状态: 已安装 ``` **验收标准**: - [ ] `~/.claude/settings.json` 中包含 Hook 配置 - [ ] `cc-linker hook status` 显示"已安装" ### 4.2 Hook 功能验证 ```bash # 步骤 1:启动新的 Claude Code 会话 cd /tmp && claude # 步骤 2:在会话中做一些操作 # (输入一些内容,等待响应) # 步骤 3:退出会话 /exit # 步骤 4:检查 registry 是否自动注册 cc-linker list # 预期:新会话出现在列表中 # 步骤 5:查看 Hook 日志 cat ~/.cc-linker/hook.log # 预期:包含 "已注册会话 xxx (origin: cli, cwd: /tmp)" 类似日志 ``` **验收标准**: - [ ] 新启动的 Claude Code 会话自动注册到 registry - [ ] Hook 日志记录注册成功 - [ ] 会话的 origin 字段正确标识为 "cli" ### 4.3 卸载 Hook ```bash # 步骤 1:卸载 Hook cc-linker hook uninstall # 预期输出: # Hook 已卸载 # 步骤 2:验证 settings.json cat ~/.claude/settings.json | jq .hooks # 预期:不包含 SessionStart 或 SessionStart 为空 # 步骤 3:检查 Hook 状态 cc-linker hook status # 预期: # Hook 状态: 未安装 ``` **验收标准**: - [ ] `~/.claude/settings.json` 中 Hook 配置已移除 - [ ] `cc-linker hook status` 显示"未安装" --- ## 五、基本命令验收 ### 5.1 `cc-linker list` - 列出会话 ```bash # 场景 1:默认列表 cc-linker list # 预期:表格形式显示会话,包含 Ref、标题、来源、项目、消息数、最后活跃时间 # 场景 2:按来源过滤 cc-linker list --origin cli # 预期:只显示终端发起的会话 cc-linker list --origin cc-connect # 预期:只显示 cc-connect 发起的会话 # 场景 3:按项目过滤 cc-linker list --project cc-linker # 预期:只显示项目名包含 "cc-linker" 的会话 # 场景 4:显示最近活跃 cc-linker list --active # 预期:只显示最近 2 小时内活跃的会话 # 场景 5:限制数量 cc-linker list --limit 5 # 预期:最多显示 5 个会话 # 场景 6:JSON 格式输出 cc-linker list --format json # 预期:输出 JSON 格式的会话列表 # 场景 7:CSV 格式输出 cc-linker list --format csv # 预期:输出 CSV 格式的会话列表 # 场景 8:显示已归档会话 cc-linker list --archived # 预期:显示 status 为 archived 或 corrupted 的会话 ``` **验收标准**: - [ ] 默认表格格式正确显示 - [ ] 各过滤选项(--origin、--project、--active)正常工作 - [ ] --limit 限制数量正确 - [ ] --format json/csv 输出格式正确 - [ ] --archived 显示归档会话 ### 5.2 `cc-linker resume` - 恢复会话 ```bash # 场景 1:通过 UUID 前缀恢复 cc-linker resume b21d6d04 # 预期: # - 提示恢复会话标题 # - 切换到会话的工作目录 # - 启动 claude --resume # 场景 2:交互式选择 cc-linker resume # 预期: # - 显示会话列表供选择 # - 选择后恢复该会话 # 场景 3:搜索后恢复 cc-linker resume --search "认证" # 预期: # - 搜索标题包含 "认证" 的会话 # - 如果只有一个匹配,直接恢复 # - 如果多个匹配,显示选择列表 # 场景 4:恢复最近会话 cc-linker resume --latest # 预期:恢复最近活跃的会话 # 场景 5:Dry Run 模式 cc-linker resume b21d6d04 --dry-run # 预期:只显示将执行的命令,不实际执行 # 输出:将执行: cd /path/to/project && claude --resume # 场景 6:指定工作目录 cc-linker resume b21d6d04 --cwd /new/path # 预期:在指定目录下恢复会话 # 场景 7:跳过确认 cc-linker resume b21d6d04 --no-confirm # 预期:不提示确认,直接恢复 ``` **验收标准**: - [ ] UUID 前缀匹配正确 - [ ] 交互式选择列表正常显示 - [ ] --search 搜索功能正常 - [ ] --latest 恢复最近会话 - [ ] --dry-run 只显示命令不执行 - [ ] --cwd 指定目录正确 - [ ] 工作目录切换正确 ### 5.3 `cc-linker show` - 查看详情 ```bash # 场景 1:查看会话详情 cc-linker show b21d6d04 # 预期输出: # 会话详情 # ──────────────────────────────────── # UUID: b21d6d04-xxxx-xxxx-xxxx-xxxxxxxxxxxx # 标题: API 认证模块设计 # 来源: 飞书 (cc-connect) # 项目: cc-linker # 工作目录: /Users/xxx/Git/cc-linker # 状态: active # 创建时间: 2026/5/3 10:30:00 # 最后活跃: 3 分钟前 # 消息数: 28 # # JSONL 文件: /Users/xxx/.claude/projects/-Users-xxx-Git-cc-linker/b21d6d04-xxxx.jsonl # # 操作: # cc-linker resume b21d6d04 恢复此会话 ``` **验收标准**: - [ ] 显示完整的会话信息 - [ ] UUID、标题、来源、项目、工作目录、状态、时间、消息数正确 - [ ] JSONL 文件路径正确 - [ ] 提供恢复命令提示 ### 5.4 `cc-linker sync` - 同步会话 ```bash # 场景 1:手动同步 cc-linker sync # 预期输出: # 🔄 Syncing sessions... # Scanning cc-connect sessions... # Found X cc-connect sessions, Y Claude Code sessions # New sessions registered: Z # Sessions updated: W # ✅ Sync complete. Total registered: N # 场景 2:Dry Run(只扫描不写入) cc-linker sync --scan # 预期输出: # 🔄 Syncing sessions... # Found X cc-connect sessions, Y Claude Code sessions # ✅ Scan complete. Total registered: N # 场景 3:强制刷新 cc-linker sync --force # 预期:清除缓存,全量扫描所有文件 # 场景 4:清理无效记录 cc-linker sync --clean # 预期输出: # 🔄 Syncing sessions... # Cleaned N invalid sessions # ... ``` **验收标准**: - [ ] 同步后会话数量正确 - [ ] --scan 不写入 registry - [ ] --force 强制全量扫描 - [ ] --clean 清理无效记录 ### 5.5 `cc-linker search` - 搜索会话 ```bash # 场景 1:搜索标题 cc-linker search "认证" # 预期:显示标题包含 "认证" 的会话列表 # 场景 2:只搜索标题 cc-linker search "认证" --in-title # 预期:只在标题中搜索 # 场景 3:搜索内容(较慢) cc-linker search "JWT" --in-content # 预期: # - 搜索 JSONL 文件内容 # - 显示搜索进度 # - 返回包含 "JWT" 的会话 # 场景 4:无匹配结果 cc-linker search "不存在的关键词" # 预期: # 未找到包含 "不存在的关键词" 的会话 ``` **验收标准**: - [ ] 默认搜索标题和最后消息预览 - [ ] --in-title 只搜索标题 - [ ] --in-content 搜索 JSONL 内容 - [ ] 无匹配时显示友好提示 ### 5.6 `cc-linker export` - 导出会话 ```bash # 场景 1:导出为 Markdown(默认) cc-linker export b21d6d04 # 预期: # - 生成 export-b21d6d04.md 文件 # - 包含会话头信息(UUID、来源、创建时间、消息数) # - 包含对话内容(User/Assistant 角色、时间戳、内容) # 场景 2:导出为 JSON cc-linker export b21d6d04 --format json # 预期: # - 生成 export-b21d6d04.json 文件 # - 包含完整的会话元数据和消息列表 # 场景 3:导出为纯文本 cc-linker export b21d6d04 --format text # 预期: # - 生成 export-b21d6d04.txt 文件 # - 纯文本格式,易于阅读 # 场景 4:指定输出文件 cc-linker export b21d6d04 --output ~/Desktop/session.md # 预期:导出到指定路径 # 场景 5:限制消息数 cc-linker export b21d6d04 --max-messages 100 # 预期:只导出前 100 条消息 # 场景 6:包含 thinking block cc-linker export b21d6d04 --include-thinking # 预期:导出内容包含 [thinking] 块 # 场景 7:包含工具调用 cc-linker export b21d6d04 --include-tools # 预期:导出内容包含 [tool_use] 和 [tool_result] 块 ``` **验收标准**: - [ ] 默认导出 Markdown 格式正确 - [ ] --format json/text 格式正确 - [ ] --output 指定路径正确 - [ ] --max-messages 限制数量正确 - [ ] --include-thinking 包含 thinking 块 - [ ] --include-tools 包含工具调用 ### 5.7 `cc-linker clean` - 清理会话 ```bash # 场景 1:预览清理 cc-linker clean --dry-run # 预期输出: # 将清理 N 个会话: # - b21d6d04 API 认证模块设计 # - a3f8c1d2 数据库迁移方案 # # (dry run,未实际删除) # 场景 2:清理无效记录 cc-linker clean # 预期: # - 清理 JSONL 文件不存在的会话 # - 输出清理的会话数量 # 场景 3:清理旧会话 cc-linker clean --older-than 30 # 预期: # - 清理 30 天前的会话 # - 输出清理的会话数量 # 场景 4:无需清理 cc-linker clean # 预期输出: # 没有需要清理的会话 ``` **验收标准**: - [ ] --dry-run 只预览不删除 - [ ] 清理无效记录(JSONL 不存在) - [ ] --older-than 按时间清理 - [ ] 无清理时显示友好提示 ### 5.8 `cc-linker status` - 查看状态 ```bash cc-linker status # 预期输出: # cc-linker Status # ──────────────────────────────────── # Registry: ~/.cc-linker/registry.json # Last modified: 3 分钟前 # Total sessions: 15 # From CLI: 8 # From cc-connect: 7 # Active: 12 # Archived: 2 # Corrupted: 1 # # Scanners: # cc-connect scanner: enabled # Claude Code hook: installed # # Commands: # cc-linker list List all sessions # cc-linker resume Resume a session # cc-linker sync Sync sessions # cc-linker hook Manage hooks ``` **验收标准**: - [ ] 显示 registry 路径和最后修改时间 - [ ] 显示会话统计(总数、来源、状态) - [ ] 显示 scanner 状态(cc-connect、hook) - [ ] 显示常用命令列表 --- ## 六、飞书集成验收 ### 6.1 配置 cc-connect ```bash # 步骤 1:编辑 cc-connect config.toml # 位置:~/.cc-connect/config.toml # 添加以下配置: # [[commands]] # name = "bridge" # description = "跨平台会话管理" # exec = "cc-linker feishu-cmd --caller {{user}} {{args}}" # 步骤 2:重启 cc-connect cc-connect daemon restart # 步骤 3:验证命令注册 cc-connect commands list # 预期:包含 bridge 命令 ``` **验收标准**: - [ ] config.toml 配置正确 - [ ] cc-connect 重启成功 - [ ] bridge 命令注册成功 ### 6.2 `/bridge list` - 飞书列出会话 ```bash # 在飞书中发送: /bridge list # 预期输出(飞书消息): # 📋 我的会话(共 3 个) # # `b21d6d04` API 认证模块设计 # 💬 28 条消息 | 🕒 3 分钟前 # 📂 Home | 🟢 飞书 # 最后: "好的,我来实现 JWT 认证中间件..." # # `a3f8c1d2` 数据库迁移方案 # 💬 15 条消息 | 🕒 1 小时前 # 📂 backend | 💻 终端 # 最后: "migration 脚本已经写好了..." # # 回复 /bridge switch 切换到此会话 # 回复 /bridge resume 在终端恢复此会话 ``` **验收标准**: - [ ] 显示当前用户的会话列表 - [ ] 终端发起的会话(origin=cli)对所有用户可见 - [ ] 飞书发起的会话(origin=cc-connect)只对 owner 可见 - [ ] 显示会话详情(消息数、最后活跃时间、项目、来源) ### 6.3 `/bridge switch` - 飞书切换会话 ```bash # 场景 1:切换到已有映射的会话 /bridge switch b21d6d04 # 预期输出: # ✅ 已切换到「API 认证模块设计」(28 条消息) # ⚡ 无需重启,已即时生效 # 场景 2:切换到 CLI 会话(首次映射) /bridge switch a3f8c1d2 # 预期输出(第一次): # ⚠️ 首次切换此 CLI 会话需要创建 cc-connect 映射,并重启/重载 cc-connect。 # 当前对话会短暂中断,但会话历史会保留。 # 如确认继续,请重发: /bridge switch a3f8c1d2 --confirm # 预期输出(带 --confirm): # ✅ 已切换到「数据库迁移方案」(15 条消息) # 💻 此会话来自终端,已创建 cc-connect 映射 # ⚠️ cc-connect 已重启,正在进行的对话可能短暂中断,但历史已保留 ``` **验收标准**: - [ ] 已有映射的会话即时切换,无需重启 - [ ] CLI 会话首次映射需要确认 - [ ] 首次映射后自动重启 cc-connect - [ ] 切换后飞书对话继续该会话 ### 6.4 `/bridge resume` - 飞书获取恢复命令 ```bash # 在飞书中发送: /bridge resume b21d6d04 # 预期输出: # 📱 请在终端执行以下命令恢复此会话: # # cc-linker resume b21d6d04 # # 或直接运行: # claude --resume b21d6d04-xxxx-xxxx-xxxx-xxxxxxxxxxxx ``` **验收标准**: - [ ] 显示终端恢复命令 - [ ] 显示完整的 UUID ### 6.5 `/bridge status` - 飞书查看状态 ```bash # 在飞书中发送: /bridge status # 预期输出: # 🔗 cc-linker 状态 # 注册会话: 15 # 来源: 8 个来自终端,7 个来自飞书 # 活跃: 12 ``` **验收标准**: - [ ] 显示会话统计 - [ ] 显示来源分布 ### 6.6 `/bridge model` - 模型切换 > 前置条件:Bot 已启动且连接正常。模型配置来源:`~/.claude/providers/*.json`(手动配置)或 `~/.cc-switch/cc-switch.db`(CC Switch 自动同步)。 #### 准备测试 Provider ```bash # 方式 1:手动创建 provider 配置 mkdir -p ~/.claude/providers cat > ~/.claude/providers/opus.json << 'EOF' { "name": "Claude Opus", "model": "opus", "env": { "ANTHROPIC_MODEL": "claude-opus-4-7-20251001" } } EOF cat > ~/.claude/providers/sonnet.json << 'EOF' { "name": "Claude Sonnet", "model": "sonnet", "env": { "ANTHROPIC_MODEL": "claude-sonnet-4-6-20251001" } } EOF # 方式 2:使用 CC Switch(如已安装) # CC Switch 会自动同步到 cc-linker,无需手动创建 ``` #### 场景 1:查看可用模型列表(有 providers) ```text 飞书发送:/bridge model 预期输出: 当前默认模型: 未设置(跟随 Claude 全局配置) 可用模型: 1. Claude Opus (opus) 2. Claude Sonnet (sonnet) 用法: /bridge model <序号|别名> 设置默认模型 /bridge model --clear 清除默认设置 /bridge new /path --model <别名> 创建会话时指定模型 ``` #### 场景 2:查看可用模型列表(无 providers) ```text # 先移除所有 provider 配置 rm -rf ~/.claude/providers/* # 或确保 ~/.cc-switch/cc-switch.db 不存在 飞书发送:/bridge model 预期输出: 当前默认模型: 未设置(跟随 Claude 全局配置) 未检测到可切换模型。 请安装 CC Switch 或手动创建 ~/.claude/providers/*.json 用法: /bridge model <序号|别名> 设置默认模型 /bridge model --clear 清除默认设置 /bridge new /path --model <别名> 创建会话时指定模型 ``` #### 场景 3:通过别名设置默认模型 ```text 飞书发送:/bridge model opus 预期输出: ✅ 默认模型已设置为 Claude Opus (opus) 验证: 飞书发送:/bridge model 预期输出中 "当前默认模型:" 应显示 "Claude Opus (opus)",且 opus 行前显示 "●" ``` #### 场景 4:通过序号设置默认模型 ```text 飞书发送:/bridge model 2 预期输出: ✅ 默认模型已设置为 Claude Sonnet (sonnet) 验证: 飞书发送:/bridge model 预期输出中 "当前默认模型:" 应显示 "Claude Sonnet (sonnet)" ``` #### 场景 5:清除默认设置 ```text 飞书发送:/bridge model --clear 预期输出: ✅ 已清除默认模型设置 验证: 飞书发送:/bridge model 预期输出中 "当前默认模型:" 应显示 "未设置(跟随 Claude 全局配置)" ``` #### 场景 6:设置不存在的模型 ```text 飞书发送:/bridge model gpt-5 预期输出: 未知模型: "gpt-5" 请使用 /bridge model 查看可用列表 ``` #### 场景 7:模型持久化验证 ```text # 步骤 1:设置默认模型为 opus 飞书发送:/bridge model opus # 预期:设置成功 # 步骤 2:创建或切换到某个会话,发送几条消息 飞书发送:/bridge new ~/Git/cc-linker -- 你好 # 预期:新会话创建成功 # 步骤 3:切换到另一个会话 飞书发送:/bridge switch <另一个会话> # 预期:切换成功 # 步骤 4:再切换回来 飞书发送:/bridge switch <原会话> # 步骤 5:检查默认模型是否仍然保留 飞书发送:/bridge model # 预期:当前默认模型仍显示 "Claude Opus (opus)" ``` #### 场景 8:Provider 配置文件缺失后的回退 ```text # 步骤 1:设置默认模型 飞书发送:/bridge model opus # 预期:设置成功 # 步骤 2:在服务器上删除 provider 配置文件 rm ~/.claude/providers/opus.json # 步骤 3:发送消息触发会话 飞书发送:你好 预期: - 消息处理成功,不报错 - Claude CLI 不使用 --settings 参数(回退到全局配置) - Bot 日志中出现警告:Provider settings file not found: ... ``` **验收标准**: - [ ] 无参数时正确显示当前默认模型和可用列表 - [ ] 无 providers 时显示友好提示 - [ ] 通过别名设置成功 - [ ] 通过序号设置成功 - [ ] `--clear` 正确清除设置 - [ ] 设置不存在的模型时返回错误提示 - [ ] 切换会话后默认模型持久保留 - [ ] Provider 配置文件缺失时回退到全局配置,不崩溃 ### 6.7 `/bridge new --model` - 创建会话时指定模型 #### 场景 1:创建会话时指定模型 ```text 飞书发送:/bridge new ~/Git/cc-linker --model sonnet -- 帮我检查代码风格 预期: 1. 如果 sonnet 不存在,返回:未知模型: "sonnet"\n请使用 /bridge model 查看可用列表 2. 如果 sonnet 存在: - 默认模型被设置为 sonnet(写入 user-mapping) - 新会话创建成功 - 该会话使用 sonnet 模型回复 ``` #### 场景 2:创建会话时使用默认模型 ```text # 前提:已通过 /bridge model opus 设置默认模型 飞书发送:/bridge new ~/Git/cc-linker -- 帮我检查代码风格 预期: - 不指定 --model 时,自动使用用户默认模型(opus) - 新会话创建成功 ``` #### 场景 3:--model 参数位置灵活 ```text # 以下三种形式应等效: /bridge new ~/Git/cc-linker --model sonnet -- hello /bridge new --model sonnet ~/Git/cc-linker -- hello /bridge new ~/Git/cc-linker -- hello(前提是默认模型已设置) ``` **验收标准**: - [ ] `--model <别名>` 正确指定模型 - [ ] 不指定 `--model` 时使用用户默认模型 - [ ] 指定不存在的模型时返回错误,不创建会话 - [ ] `--model` 参数在命令中的位置灵活 --- ## 七、配置验收 ### 7.1 配置文件 ```bash # 步骤 1:创建配置文件 cat > ~/.cc-linker/config.toml << 'EOF' [general] log_level = "debug" log_path = "~/.cc-linker/cc-linker.log" [bridge] api_url = "http://localhost:9810" token = "my-secret-token" [scanner] max_file_size = 52428800 # 50MB EOF # 步骤 2:验证配置生效 cc-linker status # 预期:使用配置文件中的设置 # 步骤 3:查看日志 cat ~/.cc-linker/cc-linker.log # 预期:包含 debug 级别日志 ``` **验收标准**: - [ ] 配置文件正确加载 - [ ] 日志级别生效 - [ ] 日志文件路径生效 ### 7.2 环境变量覆盖 ```bash # 步骤 1:设置环境变量 export CC_LINKER_LOG_LEVEL=warn export CC_LINKER_API_URL=http://localhost:9811 # 步骤 2:验证环境变量生效 cc-linker status # 预期:使用环境变量的值 # 步骤 3:清理环境变量 unset CC_LINKER_LOG_LEVEL unset CC_LINKER_API_URL ``` **验收标准**: - [ ] 环境变量覆盖配置文件 - [ ] 环境变量优先级正确 --- ## 八、错误处理验收 ### 8.1 会话不存在 ```bash # 场景:恢复不存在的会话 cc-linker resume 00000000 # 预期输出: # 错误 [E002]: 未找到匹配 "00000000" 的会话 # 场景:UUID 前缀匹配多个 cc-linker resume b21 # 预期输出: # 错误 [E006]: 前缀 "b21" 匹配到 N 个会话,请输入更长的前缀 ``` **验收标准**: - [ ] 不存在的会话显示友好错误 - [ ] 多个匹配时提示输入更长前缀 ### 8.2 JSONL 文件不存在 ```bash # 场景:会话的 JSONL 文件被删除 cc-linker resume # 预期: # - 尝试查找 JSONL 文件 # - 如果找不到,标记为 corrupted # - 显示错误:JSONL 文件不存在,会话可能已被清理(已标记 status=corrupted) ``` **验收标准**: - [ ] 自动标记 corrupted 状态 - [ ] 显示友好的错误提示 ### 8.3 工作目录不存在 ```bash # 场景:会话的工作目录被删除 cc-linker resume # 预期输出: # 错误 [E008]: 工作目录不存在: /path/to/deleted/dir,使用 --cwd 指定替代目录 ``` **验收标准**: - [ ] 提示使用 --cwd 指定替代目录 ### 8.4 文件锁冲突 ```bash # 场景:多个进程同时操作 registry # (需要手动模拟,如同时运行两个 cc-linker sync) # 预期: # - 等待锁释放 # - 或显示错误:注册表被锁 ``` **验收标准**: - [ ] 文件锁机制正常工作 - [ ] 锁冲突时显示友好提示 --- ## 九、边界情况验收 ### 9.1 大文件处理 ```bash # 场景:JSONL 文件超过 100MB # (需要手动创建大文件测试) # 预期: # - 跳过解析过大的文件 # - 显示警告:跳过过大的 JSONL 文件 (xxx.xMB) ``` **验收标准**: - [ ] 大文件自动跳过 - [ ] 显示警告信息 ### 9.2 损坏的 JSONL 文件 ```bash # 场景:JSONL 文件格式错误 # (需要手动创建损坏的文件测试) # 预期: # - 跳过解析失败的行 # - 继续处理其他行 # - 显示警告:解析 JSONL 文件失败 ``` **验收标准**: - [ ] 损坏的行被跳过 - [ ] 其他行正常解析 ### 9.3 并发安全 ```bash # 场景:多个终端同时运行 cc-linker # (需要手动测试) # 预期: # - 使用文件锁保证并发安全 # - 数据不会丢失或损坏 ``` **验收标准**: - [ ] 并发操作不会导致数据损坏 - [ ] 文件锁机制正常工作 ### 9.4 会话状态管理 ```bash # 场景 1:会话标记为 corrupted cc-linker list --archived # 预期:显示 corrupted 状态的会话 # 场景 2:corrupted 会话恢复 # (重新创建 JSONL 文件后) cc-linker sync cc-linker list # 预期:corrupted 会话自动恢复为 active ``` **验收标准**: - [ ] corrupted 状态正确显示 - [ ] JSONL 恢复后状态自动更新 --- ## 十、性能验收 ### 10.1 扫描性能 ```bash # 场景:大量会话(100+) time cc-linker sync # 预期: # - 首次扫描:< 5 秒(取决于 JSONL 文件数量) # - 增量扫描:< 1 秒(使用缓存) ``` **验收标准**: - [ ] 首次扫描在合理时间内完成 - [ ] 增量扫描明显快于首次扫描 ### 10.2 列表性能 ```bash # 场景:大量会话 time cc-linker list # 预期:< 1 秒 ``` **验收标准**: - [ ] 列表命令响应时间 < 1 秒 ### 10.3 搜索性能 ```bash # 场景 1:标题搜索 time cc-linker search "关键词" # 预期:< 1 秒 # 场景 2:内容搜索 time cc-linker search "关键词" --in-content # 预期:取决于 JSONL 文件大小,但应在合理时间内完成 ``` **验收标准**: - [ ] 标题搜索 < 1 秒 - [ ] 内容搜索在合理时间内完成 --- ## 十一、流式响应验收(Phase 2) > 前置条件:`stream.enabled = true`(默认开启),飞书 Bot 已启动且连接正常。 > 本节验收在飞书私聊中进行,所有操作均通过 `/bridge` 命令和普通文本消息触发。 ### 11.1 流式配置验收 ```bash # 场景 1:验证默认配置 cc-linker status # 预期:配置中 stream.enabled = true # 场景 2:环境变量覆盖 CC_LINKER_STREAM_ENABLED=false cc-linker start # 预期:Bot 启动成功,但使用非流式路径(普通文本回复) # 场景 3:关闭流式后发消息 # 飞书发送普通文本消息 # 预期:不走卡片路径,直接返回普通文本消息(Phase 1 行为) ``` **验收标准**: - [ ] `stream.enabled = true` 时进入流式路径 - [ ] `stream.enabled = false` 时回退到非流式路径 - [ ] 环境变量 `CC_LINKER_STREAM_ENABLED` 覆盖生效 - [ ] `stream.throttle_ms`、`stream.max_card_bytes`、`stream.show_thinking` 均可配置 ### 11.2 卡片状态生命周期验收 **状态流转**:`processing` → `streaming` → `complete` / `error` ```text 场景 1:完整流程(短回复) 飞书发送:帮我用一句话介绍 cc-linker 是什么 预期卡片变化: 1. ⏳ 正在处理...(蓝色 header,立即出现) 内容:"Claude 正在处理你的请求,预计 2-10 秒..." 2. 💭 处理中(蓝色 header,首次 assistant chunk 到达) 内容:思考过程(> 引用格式)+ 回复内容逐步出现 底部:⏱ 已用时 Xs(数字随时间递增) 3. ✅ 处理完成(绿色 header,type=result 到达) 内容:完整回复文本 底部:💰 费用: $X.XX | ⏱ 耗时: Xs | 📊 轮数: N ``` **验收标准**: - [ ] processing 卡片在 Claude 进程启动后立即出现 - [ ] 首次 assistant chunk 到达时卡片切换为 streaming 状态(蓝色 → 💭 处理中) - [ ] streaming 状态中 thinking 用 `> ` 引用格式展示 - [ ] streaming 状态底部显示已用时,数字递增 - [ ] result 到达时卡片切换为 complete 状态(绿色 header) - [ ] complete 卡片底部显示费用、耗时、轮数 ```text 场景 2:处理失败(error 状态) # 模拟方式:将 claude_bin 配置为不存在的路径 飞书发送:测试消息 预期: 1. processing 卡片出现(或不出现,取决于启动失败时机) 2. 卡片变为 ❌ 处理失败(红色 header) 内容:错误原因 + 请检查 Claude CLI 是否可用 ``` **验收标准**: - [ ] 进程启动失败时卡片显示 error 状态(红色 header) - [ ] 错误信息包含具体原因(如"命令不存在") ```text 场景 3:执行过程中进程崩溃 # 模拟方式:在 Claude 处理中途 kill 进程 预期: 1. processing → streaming(已有部分内容) 2. 卡片变为 ❌ 处理失败 3. 飞书用户看到错误提示,而非卡住不动 ``` **验收标准**: - [ ] 进程异常退出时卡片正确切换到 error 状态 - [ ] 不会出现卡片永远停在 processing/streaming 的情况 ### 11.3 节流策略验收 ```text 场景:高频 stream-json 输出下的 patch 频率 飞书发送:请详细分析当前项目的目录结构,列出所有文件和子目录 预期: 1. stream-json 可能每秒输出 10-20 行 2. 实际飞书 patch 调用频率 ≈ 1 次/1.5 秒(默认 throttle_ms) 3. 卡片内容平滑更新,不会出现闪烁或丢失 4. 最后一次节流后的 pending 内容最终会发送 ``` **验收标准**: - [ ] 节流间隔内不会触发多余 patch 调用 - [ ] 节流结束后,pending 内容会被补发 - [ ] 卡片最终展示完整内容 - [ ] 飞书 API 5 QPS 限流未被触发 ### 11.4 内容大小管理验收 ```text 场景 1:正常大小回复(< 25KB) 飞书发送:请用 500 字介绍 TypeScript 的类型系统 预期: 1. 完整回复出现在 complete 卡片中 2. 不触发 fallback-to-text 降级 ``` ```text 场景 2:超长回复(> 25KB) 飞书发送:请列出本项目中所有文件的完整内容 预期: 1. 卡片内容被截断至 ~25KB 2. complete 卡片展示截断后的内容 3. 超出部分以普通文本消息分片发送 4. 每个文本分片独立可阅读 ``` **验收标准**: - [ ] 正常回复完整显示在卡片中 - [ ] 超过 max_card_bytes 时触发 fallback-to-text - [ ] 超长部分分片发送,不丢失内容 - [ ] 截断发生在字符边界,不乱码 ### 11.5 Thinking 内容展示验收 ```text 场景 1:show_thinking = true(默认) 飞书发送:帮我设计一个排序算法 预期: 1. streaming 卡片中包含 "思考过程:" 部分 2. thinking 内容用 `> ` 引用格式展示 3. thinking 和 回复 同时展示 ``` ```text 场景 2:show_thinking = false # 修改 config.toml: stream.show_thinking = false 飞书发送:帮我设计一个排序算法 预期: 1. streaming 卡片中不展示 thinking 内容 2. 只展示 "回复:" 部分 ``` **验收标准**: - [ ] show_thinking = true 时 thinking 正常展示 - [ ] show_thinking = false 时 thinking 被过滤 - [ ] thinking 内容被限制在 2000 字节以内 ### 11.6 Stream Parser 验收 ```text 场景 1:system 行过滤 # Claude stream-json 输出中包含大量 type=system 的 hook 行 预期: 1. 系统行被完全过滤,不展示在卡片中 2. 不影响 thinking/text/result 的正常解析 ``` ```text 场景 2:多 block 消息 # 一条 assistant 消息可能同时包含 thinking + text block 预期: 1. thinking block 正确提取为 thinking chunk 2. text block 正确提取为 text chunk 3. 两种内容分别累积,最终在卡片中正确展示 ``` **验收标准**: - [ ] system 行(hook 噪声)被过滤 - [ ] thinking 和 text 分别正确提取 - [ ] result chunk 正确解析费用/耗时/session_id - [ ] 无效 JSON 行不崩溃,静默跳过 - [ ] 空行不崩溃,静默跳过 ### 11.7 新会话流式创建验收 ```text 场景 1:/bridge new 带 prompt + 流式 飞书发送:/bridge new ~/Git/cc-linker -- 帮我检查 README 是否完整 预期: 1. processing 卡片出现 2. streaming 卡片更新(思考 + 回复逐步出现) 3. complete 卡片展示最终回复 4. 新会话正确创建,session_id 写入 Registry/UserMapping 5. JSONL 文件正常创建 ``` ```text 场景 2:/bridge new 不带 prompt + 后续消息流式 飞书发送:/bridge new ~/Git/cc-linker # 返回"已设置新会话目录" 飞书发送:你好,帮我看看这个项目 # 触发 pending_new_session → claim → 创建新会话 预期: 1. 首条普通文本正确触发 claim 机制 2. 流式卡片完整展示处理过程 3. 新会话创建成功 ``` **验收标准**: - [ ] 新会话创建路径走流式逻辑 - [ ] session_id 正确从 result chunk 提取 - [ ] Registry 以 provisioning 状态写入 - [ ] UserMapping 正确切换到 type=session - [ ] JSONL 路径补齐 ### 11.8 Delivery Receipt 验收(流式路径) ```text 场景 1:正常完成 飞书发送:你好 预期: 1. processing 卡片发送成功后记录 delivery status=sending 2. complete 卡片 patch 成功后更新 delivery status=sent 3. 记录 feishuMessageId = 卡片 message_id ``` ```text 场景 2:崩溃恢复 # 模拟:complete 卡片发送前进程崩溃 重启 cc-linker start 后: 1. startupReconcile 扫描 spool/replied + deliveries 2. 已发送的卡片不应重复发送 3. 未完成的消息应重新处理 ``` **验收标准**: - [ ] delivery receipt 在流式路径中正确记录 - [ ] 卡片 message_id 正确记录到 delivery 中 - [ ] 崩溃恢复后不重复回复 ### 11.9 错误场景验收 | 错误场景 | 预期行为 | |---------|---------| | Claude 进程启动失败 | 不发送 processing 卡片或立即变为 error,直接回复错误文本消息 | | 进程中途崩溃 | patch 卡片为 error 状态,spool 标记 failed | | 进程 5 分钟无输出 | 终止进程,patch 卡片为 error "超时" | | 进程 30 分钟硬上限 | 终止进程,patch 卡片为 error "超时" | | patch 请求失败(网络) | 指数退避重试,最终降级为普通文本 | | patch 请求 429(限流) | 延长节流间隔,等待后重试 | | stream-json 解析失败 | 忽略异常行,继续解析后续行 | | `--resume` 无效 UUID | patch 卡片为 error "会话已不存在,请 /bridge switch" | | 费用超预算 | patch 卡片为 error "费用超限" | **验收标准**: - [ ] 每种错误场景都返回用户友好的错误提示 - [ ] 不出现卡片永远卡住的情况 - [ ] 错误场景下 spool 状态正确(failed 而非 done) ### 11.10 流式性能验收 ```bash # 场景 1:首字节时间(TTFB) 飞书发送:你好 # 预期:processing 卡片在 3-5 秒内出现(含进程启动时间) # 场景 2:卡片更新延迟 # 预期:从 Claude 输出到飞书卡片更新,延迟 ≤ 2 秒(节流间隔内) # 场景 3:完整响应时间 # 预期:与直接使用 claude CLI 的耗时相当(仅增加卡片 patch 开销) ``` **验收标准**: - [ ] processing 卡片在 5 秒内出现 - [ ] 卡片更新延迟 ≤ 2 秒 - [ ] 不显著增加总体响应时间 --- ## 十二、SDK 模式与权限交互验收(Phase 2) > 前置条件:`sdk.enabled = true`,飞书 Bot 已启动且连接正常。 > SDK 模式使用 `@anthropic-ai/claude-agent-sdk` 的 `query()` 函数调用 Claude Code, > 而非 spawn 子进程。核心能力:Claude 在执行工具前暂停,飞书中弹出权限卡片供用户允许/拒绝。 > 本节验收在飞书私聊中进行。 ### 12.1 SDK 配置验收 ```bash # 场景 1:验证 SDK 开关 cc-linker status # 预期:配置中 sdk.enabled 状态可见 # 场景 2:环境变量覆盖 CC_LINKER_SDK_ENABLED=true cc-linker start # 预期:Bot 启动成功,进入 SDK 路径 # 场景 3:关闭 SDK 后发消息 # 飞书发送普通文本消息 # 预期:不走 SDK 路径,回退到 stream-json 或非流式路径(Phase 1 行为) ``` **验收标准**: - [ ] `sdk.enabled = true` 时进入 SDK 路径(`handleChatSDK` / `createSessionFromPromptSDK`) - [ ] `sdk.enabled = false` 且 `stream.enabled = true` 时回退到 stream-json 路径 - [ ] 两者都为 `false` 时回退到 JSON 单次调用路径 - [ ] 环境变量 `CC_LINKER_SDK_ENABLED` 覆盖生效 - [ ] `sdk.timeout_ms`、`sdk.permission_mode`、`sdk.claude_executable` 均可配置 - [ ] `claude.allowed_tools`、`claude.disallowed_tools` 均可配置 ### 12.2 权限卡片生命周期验收 **状态流转**:`processing` → `permission_card` → `approved/denied` → `complete` / `error` ```text 场景 1:完整权限交互流程(Claude 需要执行 Bash 命令) 飞书发送:帮我列出当前目录下的所有文件 预期卡片变化: 1. ⏳ 正在处理...(蓝色 header,立即出现) 内容:"Claude 正在处理你的请求,预计 2-10 秒..." 2. 💭 处理中(蓝色 header,Claude 开始思考) 内容:思考过程逐步出现(如"我需要使用 Bash 执行 ls 命令") 3. 🔐 需要权限确认(橙色 header,Claude 准备执行 Bash) 内容: Claude 想要执行以下操作: **Bash 命令:** ``` ls -la ``` [✅ 允许] [❌ 拒绝] 4. 用户点击"允许"后: 卡片变为 ✅ 已允许(绿色 header) 内容:操作已被允许,Claude 将继续执行。 Claude 继续执行,最终回到处理完成状态 5. ✅ 处理完成(绿色 header) 内容:完整回复(ls 输出结果) 底部:🪙 X tokens | ⏱ 耗时: Xs | 📊 轮数: N ``` **验收标准**: - [ ] processing 卡片在 SDK query() 启动后立即出现 - [ ] Claude 需要执行工具时,权限卡片正确弹出 - [ ] 权限卡片展示正确的工具名称和操作详情 - [ ] Bash 命令显示在代码块中 - [ ] Edit/Write 操作显示文件路径 - [ ] WebFetch 操作显示 URL - [ ] 用户点击"允许"后卡片变为"已允许"(绿色) - [ ] Claude 继续执行后续操作 - [ ] 最终回复完整展示在 complete 卡片中 ```text 场景 2:用户拒绝权限 # 前置:确保 Bash 不在 allowed_tools 中 飞书发送:帮我列出当前目录下的所有文件 当权限卡片出现时,点击"拒绝": 1. 卡片变为 ❌ 已拒绝(红色 header) 内容:操作已被拒绝,Claude 将尝试其他方式。 2. Claude 收到拒绝反馈,尝试其他方式完成任务 或返回无法完成该操作的提示 3. 最终卡片展示完成状态或错误状态 ``` **验收标准**: - [ ] 用户点击"拒绝"后卡片变为"已拒绝"(红色) - [ ] Claude 收到拒绝反馈,不会执行被拒绝的工具 - [ ] Claude 尝试其他方式完成任务或友好提示无法完成 ```text 场景 3:权限超时自动拒绝 # 前置:设置 sdk.timeout_ms = 60000(1 分钟,便于测试) 飞书发送:帮我执行一个需要权限的操作 预期: 1. 权限卡片出现 2. 等待超过 timeout_ms 时间(1 分钟)不操作 3. 卡片自动变为 ❌ 已拒绝(或日志中出现"权限确认超时") 4. Claude 收到超时拒绝反馈,继续执行或报错 ``` **验收标准**: - [ ] 超时后自动拒绝该工具调用 - [ ] 日志中出现超时警告:`Permission prompt #N (toolName) timed out after Xms, auto-denying` - [ ] Claude 不会永远挂起等待用户响应 ```text 场景 4:处理过程中进程异常/超时 # 模拟:SDK hard timeout 触发 预期: 1. processing/streaming 卡片变为 ❌ 处理失败(红色 header) 2. 内容:错误原因 + 请检查 Claude CLI 是否可用 3. spool 状态正确标记为 failed ``` **验收标准**: - [ ] SDK hard timeout 触发后卡片正确切换到 error 状态 - [ ] AbortController 正确中止 query() 执行 - [ ] 不会出现卡片永远卡住的情况 ### 12.3 自动审批/拒绝规则验收 ```text 场景 1:AskUserQuestion 自动放行 飞书发送:请帮我澄清一个问题,这个项目的目标用户是谁 预期: - 不弹出权限卡片 - AskUserQuestion 工具自动放行 - Claude 直接提出问题或直接回答 ``` **验收标准**: - [ ] AskUserQuestion 始终自动放行,不弹卡片 ```text 场景 2:白名单工具自动放行 # 前置:配置 claude.allowed_tools = ["Read", "Grep", "Glob"] 飞书发送:帮我查看 README.md 的内容 预期: - Read 工具自动放行,不弹卡片 - Claude 直接读取并回复文件内容 ``` **验收标准**: - [ ] 白名单中的工具自动放行 - [ ] 不弹出权限卡片 ```text 场景 3:黑名单工具始终拒绝 # 前置:配置 claude.disallowed_tools = ["Bash", "mcp_*"] 飞书发送:帮我运行 ls 命令 预期: - Bash 工具被自动拒绝 - 不弹出权限卡片 - Claude 收到拒绝反馈,尝试其他方式或提示无法执行 ``` **验收标准**: - [ ] 黑名单中的工具始终拒绝 - [ ] 不弹出权限卡片 ```text 场景 4:白名单 + 黑名单冲突 # 前置:claude.allowed_tools = ["Bash"],claude.disallowed_tools = ["Bash"] 飞书发送:帮我运行 ls 命令 预期: - 黑名单优先于白名单 - Bash 被拒绝 ``` **验收标准**: - [ ] 黑名单优先于白名单 - [ ] 行为可预测且一致 ### 12.4 SDK 模式新会话创建验收 ```text 场景 1:/new 带 prompt + SDK 模式 飞书发送:/new ~/Git/cc-linker -- 帮我检查 README 是否完整 预期: 1. processing 卡片出现 2. 如果 Claude 需要工具,权限卡片正常弹出 3. complete 卡片展示最终回复 4. 新会话正确创建,session_id 写入 Registry/UserMapping 5. JSONL 文件正常创建 6. 所有权限交互正常工作 ``` **验收标准**: - [ ] 新会话创建路径走 SDK 逻辑(`createSessionFromPromptSDK`) - [ ] session_id 正确从 result 提取 - [ ] Registry 写入(active/provisioning/degraded) - [ ] UserMapping 正确切换到 type=session - [ ] JSONL 路径补齐 ```text 场景 2:/new 不带 prompt + SDK 模式后续消息 飞书发送:/new ~/Git/cc-linker # 返回"已设置新会话目录" 飞书发送:你好,帮我看看这个项目 # 触发 pending_new_session → claim → SDK 创建新会话 预期: 1. 首条普通文本正确触发 claim 机制 2. SDK 路径完整执行(含权限交互) 3. 新会话创建成功 ``` **验收标准**: - [ ] claim 机制与 SDK 路径兼容 - [ ] 新会话创建流程完整 ### 12.5 权限卡片回调处理验收 ```text 场景 1:正常回调处理 # 前提:权限卡片已弹出 操作:点击"允许"按钮 预期: 1. handler.resolveUserDecision(index, true) 被调用 2. PermissionHandler 返回 { behavior: 'allow' } 3. Claude 继续执行该工具 4. 权限卡片更新为"已允许"(绿色) ``` **验收标准**: - [ ] 回调正确路由到 handlePermissionCardAction - [ ] index 正确匹配当前待处理的权限提示 - [ ] 决策正确传递到 SDK ```text 场景 2:无活跃 handler 的回调 # 模拟:权限已超时自动拒绝,handler 已被清理 操作:点击已过期权限卡片的按钮 预期: 1. 返回"权限确认已过期,请重试" 2. 不报错、不崩溃 ``` **验收标准**: - [ ] 过期回调友好处理 - [ ] 不影响其他正在进行的会话 ```text 场景 3:权限卡片创建失败 # 模拟:飞书 API 创建卡片失败 预期: 1. 日志中出现"SDK Stream: 权限卡片创建失败" 2. 该工具自动拒绝(auto-deny) 3. Claude 收到拒绝反馈,继续执行 ``` **验收标准**: - [ ] 卡片创建失败不阻塞主流程 - [ ] 自动拒绝该工具调用 ### 12.6 Handler 生命周期与并发安全验收 ```text 场景 1:多个权限提示的并发处理 # 前提:Claude 需要连续执行多个需要权限的工具 预期: 1. 每个权限提示独立 index 2. 用户可以按顺序逐个审批 3. handler 保留在 activePermissionHandlers Map 中 4. 所有权限提示解决后,handler 从 Map 中移除 ``` **验收标准**: - [ ] 多个权限提示独立管理 - [ ] handler 不会过早被清理 - [ ] 所有权限解决后正确清理 ```text 场景 2:会话取消/中断 # 模拟:用户发送 /switch 切换到其他会话 预期: 1. 当前 SDK query() 被 AbortController 中止 2. 所有待处理的权限提示自动拒绝 3. handler 从 activePermissionHandlers 中移除 ``` **验收标准**: - [ ] 会话中止后无残留 handler - [ ] 不会发生 ghost permission 回调 ### 12.7 Token 使用统计验收 ```text 场景 1:SDK 模式费用统计 飞书发送:请简要介绍这个项目 预期: - complete 卡片底部显示 token 统计(🪙 X tokens) - 费用、耗时、轮数正确显示 ``` **验收标准**: - [ ] SDK 模式正确提取 input_tokens / output_tokens - [ ] token 统计显示在卡片中 ### 12.8 Delivery Receipt 验收(SDK 路径) ```text 场景 1:正常完成 飞书发送:你好 预期: 1. processing 卡片发送成功后记录 delivery status=sending 2. complete 卡片 patch 成功后更新 delivery status=sent 3. 记录 feishuMessageId = 卡片 message_id ``` ```text 场景 2:崩溃恢复 # 模拟:complete 卡片发送前进程崩溃 重启 cc-linker start 后: 1. startupReconcile 扫描 spool/replied + deliveries 2. 已发送的卡片不应重复发送 3. 未完成的消息应重新处理 ``` **验收标准**: - [ ] SDK 路径 delivery receipt 记录正确 - [ ] 崩溃恢复后不重复回复 ### 12.9 错误场景验收(SDK 模式) | 错误场景 | 预期行为 | |---------|---------| | SDK query() 启动失败 | 不发送 processing 卡片,直接回复错误文本消息 | | query() 中途抛出异常 | patch 卡片为 error 状态,spool 标记 failed | | query() 硬超时(30 分钟) | abort 中止 query,patch 卡片为 error "超时" | | 权限卡片创建失败 | 自动拒绝该工具,不阻塞主流程 | | 回调无活跃 handler | 返回"权限已过期",不报错 | | patch 请求失败(网络) | 指数退避重试,最终降级为普通文本 | | patch 请求 429(限流) | 延长节流间隔,等待后重试 | | 费用超预算 | patch 卡片为 error "费用超限" | **验收标准**: - [ ] 每种错误场景都返回用户友好的错误提示 - [ ] 不出现卡片永远卡住的情况 - [ ] 错误场景下 spool 状态正确(failed 而非 done) ### 12.10 SDK 性能验收 ```bash # 场景 1:首字节时间(TTFB) 飞书发送:你好 # 预期:processing 卡片在 2-5 秒内出现(SDK 启动通常比 spawn 快) # 场景 2:权限响应延迟 # 预期:用户点击允许后,Claude 立即继续执行(≤ 1 秒) # 场景 3:卡片更新延迟 # 预期:从 Claude 输出到飞书卡片更新,延迟 ≤ 2 秒(节流间隔内) ``` **验收标准**: - [ ] processing 卡片在 5 秒内出现 - [ ] 权限响应延迟 ≤ 1 秒 - [ ] 卡片更新延迟 ≤ 2 秒 - [ ] SDK 模式总体响应时间不劣于 spawn 模式 --- ## 十三、验收清单汇总 ### 基础功能 - [ ] 安装(从源码、npm、bun) - [ ] 初始化(首次、重复) - [ ] Hook 安装/卸载 - [ ] Hook 自动注册新会话 ### 核心命令 - [ ] list(默认、过滤、格式) - [ ] resume(前缀、交互、搜索、latest) - [ ] show(查看详情) - [ ] sync(手动、dry-run、force、clean) - [ ] search(标题、内容) - [ ] export(markdown、json、text) - [ ] clean(dry-run、older-than) - [ ] status(查看状态) ### 飞书集成 - [ ] 配置 cc-connect - [ ] /bridge list - [ ] /bridge switch(已有映射、首次映射) - [ ] /bridge resume - [ ] /bridge status - [ ] /bridge model(查看列表、设置、清除) - [ ] /bridge new --model(创建会话时指定模型) - [ ] 模型持久化(切换会话后默认模型保留) - [ ] Provider 缺失回退(配置文件删除后回退全局配置) ### SDK 模式与权限交互(Phase 2) - [ ] SDK 配置(enabled 开关、环境变量覆盖) - [ ] 权限卡片生命周期(processing → permission → approved/denied → complete/error) - [ ] 自动审批规则(AskUserQuestion 始终放行、白名单工具放行) - [ ] 自动拒绝规则(黑名单工具、超时自动拒绝) - [ ] 白名单 + 黑名单冲突(黑名单优先) - [ ] SDK 模式新会话创建(/new + prompt) - [ ] 权限卡片回调处理(允许/拒绝按钮、过期 handler) - [ ] 权限卡片创建失败自动拒绝 - [ ] Handler 生命周期与并发安全 - [ ] Token 使用统计 - [ ] Delivery Receipt(SDK 路径投递记录) - [ ] 错误场景(query 失败/超时/卡片创建失败/限流) - [ ] SDK 性能(TTFB ≤ 5s、权限响应 ≤ 1s、更新延迟 ≤ 2s) ### 流式响应(Phase 2) - [ ] 流式配置(enabled 开关、环境变量) - [ ] 卡片状态流转(processing → streaming → complete / error) - [ ] 节流策略(1500ms 间隔、pending 补发) - [ ] 内容大小管理(25KB 限制、fallback-to-text) - [ ] Thinking 内容展示(show_thinking 开关) - [ ] Stream Parser(system 过滤、thinking/text 提取) - [ ] 新会话流式创建(/bridge new + prompt) - [ ] Delivery Receipt(流式路径投递记录) - [ ] 错误场景(进程崩溃/超时/解析失败/限流) - [ ] 流式性能(TTFB ≤ 5s、更新延迟 ≤ 2s) ### 配置管理 - [ ] 配置文件 - [ ] 环境变量覆盖 ### 错误处理 - [ ] 会话不存在 - [ ] JSONL 文件不存在 - [ ] 工作目录不存在 - [ ] 文件锁冲突 ### 边界情况 - [ ] 大文件处理 - [ ] 损坏的 JSONL - [ ] 并发安全 - [ ] 会话状态管理 ### 性能 - [ ] 扫描性能 - [ ] 列表性能 - [ ] 搜索性能 --- ## 十四、常见问题 ### Q1:为什么 `cc-linker list` 没有显示任何会话? **可能原因**: 1. 未运行 `cc-linker init` 2. cc-connect 和 Claude Code 都未创建过会话 3. 会话目录路径不正确 **解决方法**: ```bash # 1. 运行初始化 cc-linker init # 2. 检查会话目录 ls ~/.cc-connect/sessions/ ls ~/.claude/projects/ # 3. 手动同步 cc-linker sync --force ``` ### Q2:为什么 Hook 安装后新会话没有自动注册? **可能原因**: 1. Hook 未正确安装 2. Claude Code 版本不支持 Hook 3. Hook 执行失败 **解决方法**: ```bash # 1. 检查 Hook 状态 cc-linker hook status # 2. 查看 Hook 日志 cat ~/.cc-linker/hook.log # 3. 重新安装 Hook cc-linker hook uninstall cc-linker hook install ``` ### Q3:为什么 `cc-linker resume` 提示 JSONL 文件不存在? **可能原因**: 1. 会话的 JSONL 文件被删除 2. 会话目录路径变化 **解决方法**: ```bash # 1. 检查会话状态 cc-linker show # 2. 标记为 corrupted cc-linker clean # 3. 如果文件确实不存在,无法恢复 ``` ### Q4:为什么 `/bridge switch` 需要重启 cc-connect? **原因**: cc-connect 当前仅在启动时加载 session JSON,运行中不监听文件变化。首次映射 CLI 会话需要写入 session JSON 并重启 cc-connect 使其生效。 **解决方法**: 1. 使用 `--confirm` 参数确认重启 2. 或等待 cc-connect 支持热加载(未来版本) ### Q6:为什么 `/bridge model` 显示"未检测到可切换模型"? **可能原因**: 1. `~/.claude/providers/` 目录不存在或为空 2. `~/.cc-switch/cc-switch.db` 不存在(且未安装 CC Switch) 3. Provider 配置文件格式错误(非 JSON 或缺少必要字段) **解决方法**: ```bash # 1. 检查 providers 目录 ls ~/.claude/providers/ # 2. 手动创建测试 provider cat > ~/.claude/providers/test.json << 'EOF' { "name": "Test Provider", "model": "sonnet", "env": { "ANTHROPIC_MODEL": "claude-sonnet-4-6-20251001" } } EOF # 3. 检查 CC Switch 是否安装(可选) cc-switch --version ``` ### Q7:设置了默认模型后,为什么 Claude 还是使用全局模型? **可能原因**: 1. Provider 配置文件中的 `env.ANTHROPIC_MODEL` 值不正确 2. Provider 配置文件被删除或移动,导致 `--settings` 参数指向不存在的文件 3. Claude CLI 版本不支持 `--settings` 参数 **排查方法**: ```bash # 1. 检查 provider 配置文件内容 cat ~/.claude/providers/.json # 2. 检查 cc-linker 日志中是否有警告 grep "Provider settings file not found" ~/.cc-linker/cc-linker.log # 3. 手动测试 Claude CLI 是否支持 --settings claude -p "hello" --settings ~/.claude/providers/.json ``` ### Q8:模型切换和 CC Switch 是什么关系? **关系说明**: - **CC Switch** 是独立的模型管理工具,管理 `~/.cc-switch/cc-switch.db` 中的 provider 列表 - **cc-linker 的模型切换** 在运行时读取 CC Switch 的数据库(如果存在),自动生成临时 provider 配置到 `~/.cc-linker/auto-providers/` - 两者数据单向同步:CC Switch → cc-linker,cc-linker 不会修改 CC Switch 的数据 **优先级**: 1. `~/.claude/providers/*.json`(手动配置,最高优先级) 2. `~/.cc-switch/cc-switch.db`(自动同步,次优先级) 3. 无 provider 时跟随 Claude 全局配置 ### Q5:如何备份和恢复 registry? **备份**: ```bash # 自动备份在 ~/.cc-linker/backups/ 目录 ls ~/.cc-linker/backups/ # 手动备份 cp ~/.cc-linker/registry.json ~/registry-backup.json ``` **恢复**: ```bash # 从备份恢复 cp ~/.cc-linker/backups/registry.20260503_103000.json ~/.cc-linker/registry.json # 或重新初始化 cc-linker init ``` ### Q9:SDK 模式和 spawn 子进程模式有什么区别? **区别对比**: | 对比项 | spawn 子进程模式 | SDK 模式 | |--------|-----------------|---------| | 调用方式 | `Bun.spawn(['claude', ...])` | `query()` 函数 | | 启动延迟 | ~2-5 秒 | 通常更快 | | 权限交互 | ❌ 不支持 | ✅ 支持(飞书卡片) | | 流式输出 | ✅ stream-json | ✅ stream_event | | 进程管理 | 需要手动管理进程树 | SDK 内部管理 | | 超时策略 | 活跃检测 + 硬上限 | 仅硬上限(权限等待是正常行为) | | 依赖 | 仅 Claude CLI | `@anthropic-ai/claude-agent-sdk` | **如何切换**: ```toml # config.toml [sdk] enabled = true # SDK 模式 # enabled = false # spawn 子进程模式 ``` ### Q10:权限卡片弹出了,但是用户一直没点击会怎样? **行为**: - 权限提示会等待 `sdk.timeout_ms` 毫秒(默认 10 分钟) - 超时后自动拒绝该工具调用 - 日志中记录:`Permission prompt #N (toolName) timed out after Xms, auto-denying` - Claude 收到拒绝反馈,尝试其他方式或返回无法完成 **如果设置了很短的超时(如 1 分钟)**: - 适合测试验证,但不建议生产使用 - 复杂操作可能需要用户先查看详情再决定 ### Q11:为什么有些工具执行时没有弹出权限卡片? **可能原因**: 1. 该工具在 `claude.allowed_tools` 白名单中(自动放行) 2. 该工具是 `AskUserQuestion`(始终自动放行) 3. 该工具在 `claude.disallowed_tools` 黑名单中(自动拒绝,不弹卡片) 4. SDK 模式未启用(`sdk.enabled = false`),走的是 spawn 子进程路径 **排查方法**: ```bash # 检查配置 cat ~/.cc-linker/config.toml | grep -A5 '\[sdk\]' cat ~/.cc-linker/config.toml | grep -A5 '\[claude\]' # 检查日志 grep 'SDK\|permission\|PermissionHandler' ~/.cc-linker/cc-linker.log ``` ### Q12:SDK 模式下如何调试 Claude 执行过程? **方法 1**:设置 `log_level = "debug"` ```toml [general] log_level = "debug" ``` 日志中会记录每次工具调用和权限决策。 **方法 2**:观察飞书卡片 - processing → streaming → permission → complete 状态变化反映执行进度 - 卡片中的思考过程展示 Claude 的推理步骤 **方法 3**:检查 spool 目录 ```bash ls ~/.cc-linker/spool/pending/ # 待处理消息 ls ~/.cc-linker/spool/processing/ # 正在处理 ls ~/.cc-linker/spool/done/ # 已完成 ```