fix: address tool_result dedup, SDK task ID collision, and doc accuracy

P1: tool_result dedup changed from "first wins" to "last wins"
- claude-client.ts: remove emittedToolResultIds Set, both PostToolUse and
  user message handler emit freely. PostToolUse provides immediate UI feedback;
  user handler's potentially more complete result arrives second.
- route.ts: collectStreamResponse replaces existing contentBlock entry when
  duplicate tool_use_id arrives (last wins for DB persistence).
- ChatView.tsx: onToolResult replaces existing state entry by tool_use_id
  instead of skipping, ensuring most complete result is displayed.

P2: SDK task ID collision risk eliminated
- db.ts: syncSdkTasks now uses full sessionId in task ID
  (`sdk-{fullSessionId}-{index}`) instead of truncated 8-char prefix,
  eliminating cross-session primary key collisions.

P2: Todo ordering stabilized
- db.ts: add sort_order column migration, syncSdkTasks writes array index
  as sort_order. getTasksBySession sorts by sort_order ASC, created_at ASC
  so tasks with identical timestamps maintain insertion order.

P3: Documentation field mapping corrected
- agent-tooling-todo-bridge.md: fix field mapping table — actual implementation
  maps activeForm (not priority) to description column. Updated dedup strategy
  docs from "three-layer skip" to "two-layer last-wins replace".

Lint: fix prefer-const for finalPrompt in claude-client.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-02-27 13:22:10 +08:00
1 parent c13ff54638
commit 0c379e5607
5 files changed
+64 -54

No files matched your search

+18 -16
View File
@@ -16,12 +16,13 @@ SDK TodoWrite tool_use
## TodoWrite Field Mapping
| SDK TodoWrite field | Local tasks table column | Notes |
|---------------------|-------------------------|-------|
| `id` | `id` (prefixed `sdk-`) | Prefixed to avoid collision with user tasks |
| `content` | `title` | Main display text |
| `status` | `status` | Mapped via `mapStatus()` |
| `priority` | `description` | Optional context |
| 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
@@ -40,19 +41,20 @@ SDK TodoWrite tool_use
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 Three-Layer Dedup
## tool_result Two-Layer Last-Wins Dedup
Both `PostToolUse` hook and `user` message handler in the SDK can emit the same `tool_result`. Three layers prevent duplicates:
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: claude-client.ts (SSE emission)
- `emittedToolResultIds` Set — checks before enqueueing any `tool_result` SSE event
- Both PostToolUse and user message handler check this Set
### Layer 1: route.ts (DB persistence)
- `seenToolResultIds` Set in `collectStreamResponse()` — when a duplicate `tool_use_id`
arrives, the existing `contentBlock` entry is **replaced** with the newer result.
### Layer 2: route.ts (DB persistence)
- `seenToolResultIds` Set in `collectStreamResponse()` — skips duplicate `tool_use_id` before writing to `contentBlocks`
### Layer 3: ChatView.tsx (UI state)
- `onToolResult` checks `prev.some(r => r.tool_use_id === id)` before adding to state
### Layer 2: ChatView.tsx (UI state)
- `onToolResult` finds existing entry by `tool_use_id` and **replaces** it instead of
appending a duplicate.
## SSE Event Types