feat: 管道状态持久化 + 交互式卡片按钮 (Phase C) - #22
Conversation
- 新增 PipelineStore (SQLite) 持久化管道状态,支持崩溃恢复 - 新增确认卡片:/dev 命令后先确认再执行,避免误触昂贵管道 - 新增中止/取消/重试按钮,用户可全程控制管道生命周期 - 新增 Pipeline Runner 统一管理管道生命周期(创建→确认→执行→完成) - Orchestrator 支持 abort(),可中止正在运行的管道 - WebSocket 模式注册卡片交互 HTTP 端点 - 服务启动时恢复被中断的管道并通知用户 - 会话锁延迟到确认时获取,避免确认卡片阻塞会话 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Claude finished @lishuceo's task in 2m 16s —— View job PR Review: Pipeline State Persistence + Interactive Card Buttons (Phase C)
|
| Severity | Issue | File | Confidence |
|---|---|---|---|
| 🔴 Critical | No user authorization on card actions — any user in the chat can confirm/cancel/abort/retry another user's pipeline | src/feishu/event-handler.ts:88 |
85 |
| 🟡 Warning | Abort status overwrite — abortPipeline() sets status to 'aborted', but startPipeline()'s completion handler overwrites it to 'failed' |
src/pipeline/runner.ts:271-285 |
80 |
| 🟡 Warning | TOCTOU between get() and tryStart() CAS — redundant status check in handlePipelineConfirm could become inconsistent |
src/pipeline/runner.ts:108-119 |
82 |
| 🟡 Warning | Pipeline ID collision risk — 2 bytes of randomness (65K range) with ms-precision timestamp | src/pipeline/store.ts:229-231 |
78 |
| 🔴 Info | Module-level SQLite singleton — pipelineStore opens DB at import time; fragile for tests and startup error handling |
src/pipeline/store.ts:233 |
90 |
What looks good
- CAS double-click prevention via
tryStart()— correct SQLite-level atomicity - Graceful shutdown in
src/index.ts— marks running pipelines as interrupted and closes the DB - Crash recovery —
recoverInterruptedPipelines()correctly marks running→interrupted on startup and notifies users - Session lock deferred to confirm — avoids blocking chat while waiting for user confirmation
- Comprehensive test suite — store tests cover CRUD, CAS, expiration; orchestrator tests cover happy path, retry logic, cost accumulation, abort, and callback resilience
- Card builder functions are well-organized with proper phase state rendering
- 卡片交互处理器验证操作者 open_id 与管道创建者一致,防止越权操作 - abortPipeline 不再提前写入 aborted 状态,改由 startPipeline 完成 处理器检查 orchestrator.isAborted() 决定最终状态 - generatePipelineId 随机部分从 2 字节增加到 4 字节,降低碰撞概率 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Claude finished @lishuceo's task in 2m 37s —— View job PR Review: Pipeline State Persistence + Interactive Card Buttons (Phase C) — Follow-up
Previous Issues: 3 of 5 FixedThe fix commit
All 3 fixed threads have been resolved. The test ( New Review: No New Issues FoundThe fix commit is clean. The ✅ ApprovedWell-structured PR with thorough test coverage (store CRUD, CAS atomicity, orchestrator abort/retry/cost tracking, callback resilience). All previously identified issues are resolved or acknowledged. |
- 卡片操作鉴权改为 fail closed:operatorId 缺失时拒绝操作 - 将 CAS tryStart 移至 handlePipelineConfirm 同步执行,避免 CAS 失败时返回卡住的进度卡片 - PipelineStore 使用独立 DB 文件(pipelines.db),避免与 SessionDatabase 共用同一文件导致 SQLITE_BUSY - catch 块中 feishuClient.updateCard 增加内层 try-catch - 提取 ensureThread 为共享模块,消除 runner.ts 与 event-handler.ts 的代码重复 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Claude finished @lishuceo's task in 2m 32s —— View job PR Review: Pipeline State Persistence + Interactive Card Buttons (Phase C) — Re-review
Latest Commit:
|
| Change | Assessment |
|---|---|
Separate pipeline DB — pipelineStore now uses config.db.pipelineDbPath instead of sharing sessionDbPath |
✅ Correct. Prevents table namespace collisions between sessions and pipelines |
ensureThread extraction — Deduplicated thread creation into src/feishu/thread-utils.ts, shared by event-handler.ts and runner.ts |
✅ Clean refactor. Eliminates the duplicated ensureThreadForPipeline |
Auth check hardened — Changed from permissive (skip if no operatorId) to fail-closed (reject if no operatorId) |
✅ Correct security posture. Lines 94-98 of event-handler.ts |
CAS moved to handlePipelineConfirm — tryStart() now runs synchronously before returning the progress card, with startPipeline doing a defensive fallback check |
✅ Correct. Prevents users from seeing a stuck progress card on CAS failure |
Error card update wrapped in try-catch — runner.ts:227-236 |
✅ Prevents cascading failures when Feishu API is unreachable |
Test mocks updated — Config mocks include pipelineDbPath, thread-utils.js mock added |
✅ Consistent with code changes |
Previous Issues Status
All 5 issues from the original review are now resolved or acknowledged across the two fix commits:
| Issue | Final Status |
|---|---|
| Card action authorization | ✅ Fixed (db6bd75) + hardened to fail-closed (f85c4f9) |
| Abort status overwrite | ✅ Fixed (db6bd75) |
| Pipeline ID collision risk | ✅ Fixed (db6bd75) |
| TOCTOU race condition | ✅ Acknowledged — CAS is sole source of truth; improved in f85c4f9 by moving CAS earlier |
| Module-level singleton | ✅ Acknowledged — follows project pattern; now uses separate DB path |
New Issues Found: None
The latest commit is clean. I traced through the CAS flow (handlePipelineConfirm → startPipeline), abort status propagation (orchestrator.isAborted() → 'aborted' status in store), and error handling paths — all are correct. The ensureThread extraction is a clean refactor with no behavioral changes.
Minor style note (not blocking): In runner.ts:37, the import { ensureThread } is placed after the interface definition rather than with the other imports at the top of the file. This doesn't affect functionality.
✅ Approved
Well-structured PR with comprehensive fixes across 3 commits. The pipeline persistence, interactive card buttons, crash recovery, and auth controls are all solid. Test coverage is thorough (store CRUD, CAS atomicity, orchestrator abort/retry/cost tracking, callback resilience).
* feat: 管道状态持久化 + 交互式卡片按钮 (Phase C) - 新增 PipelineStore (SQLite) 持久化管道状态,支持崩溃恢复 - 新增确认卡片:/dev 命令后先确认再执行,避免误触昂贵管道 - 新增中止/取消/重试按钮,用户可全程控制管道生命周期 - 新增 Pipeline Runner 统一管理管道生命周期(创建→确认→执行→完成) - Orchestrator 支持 abort(),可中止正在运行的管道 - WebSocket 模式注册卡片交互 HTTP 端点 - 服务启动时恢复被中断的管道并通知用户 - 会话锁延迟到确认时获取,避免确认卡片阻塞会话 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 修复 PR review 反馈 — 卡片操作鉴权 + abort 状态覆盖 + ID 碰撞风险 - 卡片交互处理器验证操作者 open_id 与管道创建者一致,防止越权操作 - abortPipeline 不再提前写入 aborted 状态,改由 startPipeline 完成 处理器检查 orchestrator.isAborted() 决定最终状态 - generatePipelineId 随机部分从 2 字节增加到 4 字节,降低碰撞概率 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 修复 deep review 发现的关键问题 - 卡片操作鉴权改为 fail closed:operatorId 缺失时拒绝操作 - 将 CAS tryStart 移至 handlePipelineConfirm 同步执行,避免 CAS 失败时返回卡住的进度卡片 - PipelineStore 使用独立 DB 文件(pipelines.db),避免与 SessionDatabase 共用同一文件导致 SQLITE_BUSY - catch 块中 feishuClient.updateCard 增加内层 try-catch - 提取 ensureThread 为共享模块,消除 runner.ts 与 event-handler.ts 的代码重复 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
PipelineStore(SQLite) 持久化管道状态,支持服务崩溃后恢复并通知用户/dev命令改为先发送确认卡片(含成本预估),用户点击「确认执行」后才开始管道,新增「中止」「取消」「重试」交互按钮Pipeline Runner模块统一管理管道生命周期,会话锁延迟到确认时获取,避免阻塞聊天Test plan
npm test— 200 tests 全部通过(含新增 store/runner/orchestrator abort 测试)npm run typecheck— TypeScript 类型检查通过/dev <简单任务>→ 看到确认卡片(含「确认执行」和「取消」按钮)/dev <任务>→ 确认 → 看到管道进度卡片(含「中止」按钮)🤖 Generated with Claude Code