- docs/handover/ — 交接文档(架构、设计决策) - docs/research/ — 调研文档(技术方案、可行性分析) - 各目录添加 README.md 索引,要求 AI 增删文件时更新索引 - CLAUDE.md 更新文档路径引用 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.1 KiB
4.1 KiB
Agent Tooling & TodoWrite Bridge
Architecture Overview
SDK TodoWrite tool_use
→ PostToolUse hook (claude-client.ts)
→ Emits tool_result SSE (deduped)
→ Emits task_update SSE { session_id, todos[] }
→ collectStreamResponse (route.ts)
→ syncSdkTasks() persists to DB
→ Frontend SSE consumer (useSSEStream.ts)
→ onTaskUpdate dispatches 'tasks-updated' event
→ TaskList re-fetches from /api/tasks
TodoWrite Field Mapping
| SDK TodoWriteInput field | Local tasks table column | Notes |
|---|---|---|
| (array index) | id (prefixed sdk-{sessionId}-{index}) |
Full sessionId avoids collision |
content |
title |
Main display text |
status |
status |
Mapped via mapStatus() |
activeForm |
description |
Present-continuous form label |
| (array index) | sort_order |
Preserves original order |
Status Mapping
| SDK status | Local TaskStatus |
|---|---|
pending |
pending |
in_progress |
in_progress |
completed |
completed |
| Other | pending |
Idempotency
syncSdkTasks() uses a replace-all strategy within a transaction:
DELETE FROM tasks WHERE session_id = ? AND source = 'sdk'INSERTall todos from the latest TodoWrite payload
This is naturally idempotent — calling it multiple times with the same data produces identical results. User-created tasks (source = 'user') are never affected.
tool_result Two-Layer Last-Wins Dedup
Both PostToolUse hook and user message handler in the SDK can emit the same tool_result.
PostToolUse fires first (for immediate UI feedback); the user handler's result may be more
complete/canonical. Strategy: both sources emit freely; consumption layers use "last wins"
(replace, not skip) so the most complete result is always kept.
Layer 1: route.ts (DB persistence)
seenToolResultIdsSet incollectStreamResponse()— when a duplicatetool_use_idarrives, the existingcontentBlockentry is replaced with the newer result.
Layer 2: ChatView.tsx (UI state)
onToolResultfinds existing entry bytool_use_idand replaces it instead of appending a duplicate.
SSE Event Types
| Event | Source | Purpose |
|---|---|---|
text |
stream_event | Streaming text delta |
tool_use |
assistant message | Tool invocation |
tool_result |
PostToolUse / user | Tool execution result (deduped) |
tool_output |
stderr callback | Raw tool output |
tool_timeout |
tool_progress | Tool execution timeout |
task_update |
PostToolUse | TodoWrite sync trigger |
status |
system message | Session init, notifications |
result |
result message | Final result with usage stats |
permission_request |
canUseTool | Permission approval needed |
mode_changed |
system status | SDK mode change |
error |
catch block | Error occurred |
done |
stream end | Stream complete |
Troubleshooting
Tasks not appearing after TodoWrite
- Check server console for
[claude-client]logs — ensure PostToolUse fires - Verify
task_updateSSE event in browser DevTools Network tab - Check
syncSdkTasks()runs incollectStreamResponse(server-side tee'd stream) - Verify
tasks-updatedCustomEvent fires in browser console
Duplicate tool results
- Check
emittedToolResultIdsSet size in server logs - Compare SSE event count before/after (should be halved for tools)
- Query
messagestable:SELECT content FROM messages WHERE content LIKE '%tool_result%'— eachtool_use_idshould appear once
SDK upgrade issues
- Current: v0.2.62 (upgraded from v0.2.33)
- Rollback:
npm install @anthropic-ai/claude-agent-sdk@0.2.33 - Key integration points:
canUseTool,PostToolUse,Notification,tool_progress,system,result