Skip to content

refactor(chat): simplify branching to block number display + AI query routing improvements#120

Merged
hrygo merged 16 commits into
mainfrom
feat/119-chat-branching-simplify-ai-routing
Feb 7, 2026
Merged

refactor(chat): simplify branching to block number display + AI query routing improvements#120
hrygo merged 16 commits into
mainfrom
feat/119-chat-branching-simplify-ai-routing

Conversation

@hrygo

@hrygo hrygo commented Feb 7, 2026

Copy link
Copy Markdown
Owner

概述

Resolves #119
本 PR 包含两个主要改进:

  1. 前端 UI 简化:将复杂的分支路径显示简化为顺序块编号
  2. AI 查询路由增强:新增智能意图检测和多种检索策略

前端重构

分支路径 → 块编号

改进前

  • 复杂的分支路径显示:0/1/2, A.1.2, main/feature
  • 用户难以理解分支的含义

改进后

  • 简洁的块编号:#1, #2, #3
  • 直观显示对话中的第几个交互轮次

组件变更

组件 变更
BranchIndicator 重构为 BlockNumberIndicator,简化实现
BlockSelector 移除(未使用)
BlockEditDialog 简化接口,移除 blockIdconversationId props
useBlockQueries 移除分支树相关代码,简化逻辑

Bug 修复

  • 块创建闪烁:移除乐观更新,直接显示创建后的状态
  • ErrorSection 图标对齐:统一时间线图标位置
  • 中文正则:使用 Unicode 属性类 \p{Han} 替代 [一-龥]

AI 查询路由增强

新增文件

  • ai/agent/tools/memo_query_intent.go - 智能意图分类器

新增检索策略

策略 用途 性能
memo_list_only 纯 SQL 列表查询("列出所有笔记") ⚡ 最快 (~10ms)
memo_filter_only SQL 过滤查询(带时间范围/标签) ⚡ 快 (~20ms)
memo_bm25_only BM25 关键词搜索 🟡 中等 (~50ms)
memo_semantic_only 向量语义搜索 🟢 慢 (~200ms)

路由逻辑

用户查询
    ↓
意图分类
    ↓
┌───────────┬────────────┬───────────────┬───────────────┐
│ 列表查询   │ 过滤查询    │ 关键词查询      │ 语义查询       │
│ "列出笔记" │ "今天创建" │ "笔记 关键词"   │ "关于...的内容" │
└──────┬────┴─────┬──────┴───────┬───────┴───────┬───────┘
       │           │              │               │
       ↓           ↓              ↓               ↓
  list_only   filter_only    bm25_only      semantic_only

代码质量改进

测试覆盖

文件 新增测试
BranchIndicator.test.tsx ✅ 新建,153 行
BlockHeader.test.tsx ✅ 新增边缘情况测试

Lint/构建

  • golangci-lint 0 issues
  • pnpm lint 通过
  • pnpm build 通过
  • ✅ 所有测试通过 (232 tests)

提交清单

提交 描述
ff09aec5 refactor(chat): simplify branching to block number display
357994d9 fix(hooks): remove invalid --silent flag from pnpm lint:fix
6cc0fad0 fix(chat): remove optimistic update to eliminate block creation flicker
270807ba fix(chat): align ErrorSection timeline icon position
168c4a70 fix(agent): handle empty query in memo_search for "list all" requests
f048eb1e fix(chat): fix PR review issues
1ed9cd10 feat(ai): add intelligent memo query routing (SQL vs Vector)
a141619b fix(tools): use unicode property class for Chinese characters in regex

文件变更统计

 ai/agent/prompts.go                                |  13 +-
 ai/agent/tools/memo_query_intent.go                | 281 +++++++++++++++++++
 ai/agent/tools/memo_search.go                      |  45 ++-
 ai/core/retrieval/adaptive_retrieval.go            | 176 ++++++++++++
 scripts/pre-commit                                 |   2 +-
 web/src/components/AIChat/BlockEditDialog.tsx      |  52 ++--
 web/src/components/AIChat/BranchIndicator.test.tsx | 153 +++++++++++
 web/src/components/AIChat/BranchIndicator.tsx      | 306 ++++++++++++---------
 web/src/components/AIChat/ChatMessages.tsx         |  62 ++---
 web/src/components/AIChat/UnifiedMessageBlock.test.tsx |  21 +-
 web/src/components/AIChat/UnifiedMessageBlock.tsx  |  48 ++--
 .../UnifiedMessageBlock/components/BlockHeader.tsx |  57 ++--
 .../components/__tests__/BlockHeader.test.tsx      |  79 +++---
 web/src/hooks/useBlockQueries.ts                   |  75 +----
 web/src/locales/en.json                            |   1 +
 web/src/locales/zh-Hans.json                       |   2 +
 web/src/locales/zh-Hant.json                       |   3 +-
 17 files changed, 990 insertions(+), 386 deletions(-)

Resolves

#119


Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

hotplex-ai and others added 16 commits February 8, 2026 00:21
- Replace branch path (A, B, C...) with sequential block numbers (1, 2, 3...)
- Update BranchIndicator to show BlockNumberBadge with Hash icon
- Remove branch creation UI from BlockEditDialog
- Remove all branch-related props from BlockHeader and UnifiedMessageBlock
- Keep database fields (parent_block_id, branch_path) reserved but unused

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The lint:fix script doesn't accept --silent parameter, causing
pre-commit hook to fail even when lint passes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The optimistic update in useCreateBlock was causing a visual flicker
where an empty pending block would momentarily appear before being
replaced by the actual block from the server.

Changed to wait for server response before updating the cache, which
provides a smoother UX without the intermediate empty state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed inconsistent ErrorSection UI where the error icon was not
properly positioned on the timeline. Now uses the same
`absolute -left-[2rem] top-0` positioning as other sections
(Thinking, ToolCall, Answer).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When user asks "有什么笔记" (what notes do you have), the LLM
generates an empty query parameter, causing tool execution failure.

Changes:
1. memo_search tool: Treat empty query as "search all" (using "*" wildcard)
2. MemoParrot prompt: Add explicit examples for searching all notes
   - query: "*" for listing all memos
   - query: "Python" for keyword search

This allows natural "list all" queries to work without errors.

Fixes: ai.agent.event error "query cannot be empty"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add missing i18n keys for block number display
- Fix handleEditConfirm to log instead of silently discarding edits
- Add localStorage error logging in CompactToolCall
- Remove unused props (blockId, conversationId) from BlockEditDialog
- Update useBlockEditDialog hook signature
- Add comprehensive test coverage for BranchIndicator
- Add edge case tests for blockNumber (0, negative, large)
- Fix test selectors and remove unused imports

Refs #3 (PR review fixes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This optimization eliminates unnecessary vector search for list/filter queries.

**Problem**:
All memo queries were routed through semantic vector search,
including simple list queries like "有什么笔记" which should use SQL.

**Solution**:
Added a 4-layer query intent classifier:
- Layer 1: List intent (0ms) → "有什么笔记" → SQL list
- Layer 2: Filter intent (0ms) → "今天的笔记" → SQL filter
- Layer 3: Keyword intent (0ms) → "Python" → BM25
- Layer 4: Semantic default → "如何部署" → Vector

**New retrieval strategies**:
- `memo_list_only`: Pure SQL list (fastest, ~10ms)
- `memo_filter_only`: SQL with time filter
- `memo_bm25_only`: BM25 keyword search (no embedding)
- `memo_semantic_only`: Vector search (existing)

**Performance impact**:
| Query | Before | After |
|-------|--------|-------|
| "有什么笔记" | ~500ms (Embedding) | ~10ms (SQL) |

Refs #8

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace `\u4e00-\u9fff` with `\p{Han}` for proper Unicode
character class matching in Go regexp.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use predicted roundNumber (existing blocks count + 1) for optimistic block
- Remove refetchBlocks() call after streaming completion
- The streaming handler already updates block via optimistic updates

This eliminates the flicker caused by:
1. Optimistic block with roundNumber=0 appearing briefly
2. Then being replaced by backend data with correct roundNumber
3. Then refetchBlocks() triggering another re-render

Now the optimistic block appears with the correct roundNumber immediately,
and no additional refetch is needed since all data is already streamed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove fade-in, slide-in, and pulse animations that were causing
visual "flicker" effects during streaming block creation.

Changes:
- Remove animate-in fade-in slide-in-from-top-1 from tool details expansion
- Remove animate-in fade-in from thinking content expansion
- Remove animate-pulse from running tool timeline node
- Remove animate-pulse from streaming answer timeline node
- Remove animate-in fade-in from pending state initialization
- Remove animate-block-pulse from streaming block container

Retain animate-spin for Loader2 components (spinner icons).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove animate-block-pulse from streaming block container to prevent
visual flicker/breathing effect during block creation.

Other animations (fade-in, slide-in, pulse for timeline nodes) are
preserved as they provide meaningful UI feedback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement intelligent query routing to use SQL for list/filter queries
instead of forcing all queries through vector search.

Changes:
- Add MemoQueryClassifier with 4-layer intent classification:
  * IntentList: List all memos ("有什么笔记", "*", etc.)
  * IntentFilter: Time/tag filters ("今天的笔记", "#work")
  * IntentKeyword: Simple keywords (BM25 search)
  * IntentSemantic: Complex semantic queries (vector search)

- Add 3 new retrieval strategies in AdaptiveRetriever:
  * memo_list_only: Pure SQL list (fastest)
  * memo_filter_only: SQL with time filter using CEL
  * memo_bm25_only: BM25 keyword search without embedding

- Fix semantic query classification by reordering checks:
  * Check semantic patterns before short query pattern
  * Prevents "如何部署服务" from being classified as keyword

- Add comprehensive unit tests for query classifier

- Remove duplicate time range extraction code
- Add timeout error handling with DeadlineExceeded check

Refs #119

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix the issue where thinking placeholder text like "正在思考..."
was displayed as actual content in the Block's thinking section.

Root cause: Backend was sending hardcoded Chinese text as thinking
event content, which frontend couldn't distinguish from real content.

Changes:
- MemoParrot: Use "ai.states.thinking" key instead of "正在思考..."
- AmazingParrot: Use "ai.states.thinking" key instead of "正在分析您的需求..."
- UnifiedMessageBlock: Dynamically build placeholder filter list
  from i18n translations to catch all translated placeholders

This ensures placeholder texts are filtered out from display,
showing only genuine thinking content.

Ref: Frontend already had translateThinkingSteps() to handle i18n keys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix the issue where thinking section was not displayed when the only
thinking content was placeholder text (e.g., "ai.states.thinking").

The problem was that the display condition checked `allThinkingContent.length > 0`,
but `allThinkingContent` is filtered to exclude placeholders, causing the section
to be hidden even when `thinkingSteps` existed.

Changes:
- Show thinking section when `thinkingSteps.length > 0` OR `streamingPhase === "thinking"`
- Only allow expand/collapse toggle when there is real content
- Only show expanded content view when there is real content

This ensures the thinking section is always visible when thinking events exist,
while the expandable content area only shows non-placeholder content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the edit button and related edit functionality from the
AI chat Block footer to simplify the UI.

Changes:
- BlockFooter.tsx: Remove edit button, Pencil icon, and onEdit prop
- UnifiedMessageBlock.tsx: Remove onEdit from props and component
- ChatMessages.tsx: Remove edit dialog, handleEdit, and handleEditConfirm
- Update test file to remove onEdit prop

This simplifies the footer UX by removing the unimplemented
edit feature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement comprehensive performance optimizations for block streaming UI
to achieve smooth, ChatGPT-like typing animation and real-time progress display.

P0 optimizations:
- Replace O(n) array traversal with O(1) Map lookup in useAIQueries.ts
- Switch from StreamingText to StreamingMarkdown for sentence-level rendering
- Leverage React 18 automatic batching instead of dual cache updates

P1 optimizations:
- Merge 6 useEffect hooks into 2 unified scroll handlers in ChatMessages.tsx
- Add smart caching for messageBlocks conversion to avoid re-processing history
- Reduce scroll trigger threshold from 50 to 15 characters for better responsiveness

Type safety improvements:
- Fix BlockBodyProps.parrotId type from string to ParrotAgentType enum
- Use enum constants (ParrotAgentType.GEEK/EVOLUTION) instead of string literals
- Fix BlockEvent to use 'meta' instead of 'metadata' field

i18n fixes:
- Add missing 'block_number' translation key to en.json and zh-Hant.json
- Remove duplicate 'block_number' key in zh-Hant.json

Refs #119

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hrygo hrygo force-pushed the feat/119-chat-branching-simplify-ai-routing branch from 835cd87 to de46286 Compare February 7, 2026 16:22
@hrygo hrygo merged commit e05269e into main Feb 7, 2026
10 checks passed
@hrygo hrygo deleted the feat/119-chat-branching-simplify-ai-routing branch February 7, 2026 16:24
hrygo pushed a commit that referenced this pull request Feb 8, 2026
- Remove unused Database import from ProgressIndicator
- Fix Navigation i18n key path to ai.knowledge-graph.title
- Merge duplicate ai.progress keys into first ai key (en.json, zh-Hans.json)
- Apply auto-formatting fixes

Refs #120

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
hrygo pushed a commit that referenced this pull request Feb 8, 2026
- Remove unused Database import from ProgressIndicator
- Fix Navigation i18n key path to ai.knowledge-graph.title
- Merge duplicate ai.progress keys into first ai key (en.json, zh-Hans.json)
- Apply auto-formatting fixes

Refs #120

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
hrygo added a commit that referenced this pull request Feb 8, 2026
* feat(ai): add router optimization + progress feedback + fix i18n

Backend changes:
- Add four-layer intent routing (Cache → Rule → History → LLM)
- Add RuleMatcher for fast zero-latency classification
- Add router feedback collection with PostgreSQL storage
- Add Prometheus metrics and tracing support
- Add context delta builder for efficient context updates
- Add preload analyzer and scheduler for optimization
- Add sensitive input filter for security
- Add migration for router_feedback table

Frontend changes:
- Add ProgressIndicator component with phase animations
- Add QuickReplies component for suggested actions
- Add shimmer keyframe animation to default.css
- Update UnifiedMessageBlock with progress display
- Update AIChat page to support new features

i18n fixes:
- Fix zh-Hant.json: add 335 missing translation keys
- Translate ai.progress.phases to Traditional Chinese
- Ensure all locale files are synchronized

Refs #97 (Progressive Progress Feedback)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(claude): add Critical Context + Code Change Boundaries sections

- Add 🔑 Critical Context section with project structure table
- Document key configuration (PostgreSQL container name, ports)
- Add i18n completeness trap warning (check-i18n doesn't check zh-Hant.json)
- Add Language-Specific Notes for TypeScript/React and Go
- Add 🚫 Code Change Boundaries section with 4 workflow patterns
- Update freshness status to v0.93.2 (2026-02-08)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(ai): align struct field initialization across modules

- Standardize field alignment in deltaStats, DeltaStats, TracingContext structs
- Improve i18n validation script with complete zh-Hant.json check
- Add cross-reference to DEBUG_LESSONS.md for migration notes
- Refine terminology: GraphQL/Proto → Proto/Schema
- Update code-reviewer agent description format

All tests pass, build successful.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(i18n): resolve duplicate ai keys and lint issues

- Remove unused Database import from ProgressIndicator
- Fix Navigation i18n key path to ai.knowledge-graph.title
- Merge duplicate ai.progress keys into first ai key (en.json, zh-Hans.json)
- Apply auto-formatting fixes

Refs #120

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ai): resolve golangci-lint issues

- Fix matchPool.Get() type assertion in sensitive.go
- Add slog error handling in analyzer.go async save
- Fix defer close error handling in exporter.go
- Fix http.NewRequest nil to http.NoBody in prometheus_test.go
- Rename testErr to errTest for staticcheck compliance
- Remove unused matchPool.Put to avoid SA6002
- Simplify regex pattern using \S* instead of [^\s]*
- Remove unused fields and imports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(claude): add golangci-lint common pitfalls to Go coding standards

Add "Go Lint 常见陷阱" section with:
- Type assertion must use comma-ok pattern
- Defer error checking requirements
- Error comparison with errors.Is
- HTTP nil body should use http.NoBody
- error variable naming convention
- Regex simplification patterns
- sync.Pool usage restrictions

This section captures lessons learned from fixing 18 golangci-lint issues
in commit 14900ad, preventing future repetitions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(web): set HTML lang attribute dynamically to prevent translation prompts

- Update document.documentElement.lang in applyLocaleEarly()
- Update document.documentElement.lang in loadLocale()
- Map zh-Hans → zh-CN, zh-Hant → zh-TW for BCP 47 compliance

This prevents Chrome/Google from offering to translate the page when
the user's browser language matches the content language.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(web): fix streaming event display and remove redundant FIFO queue

This commit fixes two issues with AI chat streaming:

1. **Tool call metadata fix**: tool_use events were incorrectly using
   response.eventData (JSON parameters) as toolName instead of
   eventMeta.toolName. Now correctly displays tool names like
   "schedule_query" instead of JSON strings.

2. **Optimistic block recreation fix**: Added blockAlreadyExists check
   to prevent recreating blocks on every SSE event, which was
   clearing the accumulated eventStream.

3. **Simplification**: Removed FIFO queue infrastructure (~250 lines)
   as SSE events are serial by nature - no concurrent updates occur.

Root causes:
- tool_use handler: toolName: response.eventData → toolMeta?.toolName
- Every SSE event triggered block recreation, resetting eventStream
- FIFO queue was over-engineering for serial SSE stream

Refs streaming UI debugging session

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(debug): add lessons learned from streaming UI debugging

Adds two new debug lessons:
1. Tool call metadata error - using eventData instead of eventMeta.toolName
2. Over-engineering: FIFO queue - SSE is serial, queue was always empty

Key takeaways:
- Diagnose before treating - add logs to confirm assumptions
- SSE doesn't need queuing - events are inherently serial
- Simplify over complexity - React Query has internal batching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(web): remove redundant debug logs from streaming handlers

- Remove console.log from eventTransformers.ts (extractToolCalls)
- Remove console.log from ChatMessages.tsx (eventStream debugging)
- Keep critical error logging in useAIQueries.ts

These debug logs were added during the streaming UI fix investigation
and are no longer needed now that the issue is resolved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(specs): add streaming tool calls technical specification

Add comprehensive technical specification for implementing full streaming
tool calls (Solution A) in AI chat system.

Key decisions:
- Adopt complete streaming mode (DeepSeek native support)
- Progressive enhancement strategy with backward compatibility
- New ChatStreamWithTools interface

Expected improvements:
- First tool call latency: ~800ms → ~200ms (75% reduction)
- Perceived latency: 3-5s → <1s (60% reduction)

Refs #125

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: 黄飞虹 <aaronwong1989@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
yangxb2010000 pushed a commit that referenced this pull request Feb 9, 2026
* feat(ccrunner): add session stats extraction and cost tracking

- Add EventTypeSessionStats and SessionStatsData struct for statistics
- Extend StreamMessage to parse CLI result messages (duration_ms, total_cost_usd, usage)
- Add handleResultMessage() to extract and send session_stats event
- Add total_cost_usd field to SessionSummary proto
- Handle session_stats event in handler.go to set TotalCostUsd
- Silence "unknown message type" warnings for system/control messages

Frontend can now access session cost and statistics via SessionSummary.total_cost_usd.

Refs cc-runner-message-handling-research.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs(ccrunner): update architecture spec to v1.3 with session stats

- Add session_stats event type documentation
- Document complete CLI event type mapping table (system, thinking, status, tool_use, tool_result, assistant, user, answer, error, result, session_stats)
- Add message flow transformation diagram with system/result handling
- Add SessionStatistics section with data structures
- Update version history to v1.3

Related to research in docs/research/cc-runner-message-handling-research.md

Refs #63

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(ccrunner): add session stats persistence and cost tracking

Phase 1 implementation of CC Runner optimization plan:

- Add agent_session_stats table for full session tracking
  - Token usage (input/output/cache)
  - Cost tracking (total_cost_usd)
  - Duration breakdown (thinking/tool/generation)
  - Tool usage and file operations

- Add user_cost_settings table for budget management
  - Daily budget limits
  - Per-session cost thresholds
  - Alert preferences

- Add agent_security_audit table for security logging
  - Risk level tracking
  - Command pattern matching
  - Action taken logging

- Implement AgentStatsStore interface
  - PostgreSQL driver implementation
  - Async persister service with queue

- Add cost alerting service
  - Threshold-based alerts
  - Daily budget warnings

- Add SessionStatsData.ConversationID for database relationship
- Add conversion method ToAgentSessionStats()

Tests: 4/4 passing in ai/stats package

Refs: docs/specs/cc-runner-optimization-plan.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): address security and error handling issues from quality review

Phase 3 post-implementation fixes:

Security fixes (CRITICAL):
- Fix SQL injection in getDailyCostBreakdown - use parameterized query
  Changed from fmt.Sprintf to INTERVAL '1 day' * $2 syntax

Error handling fixes (CRITICAL):
- drainQueue: track and log data loss during shutdown
- Heartbeat: return early on send failure
- Add rows.Err() checks after all QueryContext iterations
- Fix nilerr: distinguish sql.ErrNoRows from actual errors

Code quality:
- Use UTC for timezone consistency in validateDateRange
- Add protoTime() helper for safe timestamp conversion
- Remove unused protoTimePtr function

All golangci-lint checks pass.

Refs: docs/specs/cc-runner-optimization-plan.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(ccrunner): complete backend integration for session stats (Phase 3)

Backend gRPC handlers and store integration:

RPC handlers (ai_service_stats.go):
- GetSessionStats: retrieve single session by session_id
- ListSessionStats: list sessions with pagination (limit/offset)
- GetCostStats: aggregated N-day cost statistics with daily breakdown
- GetUserCostSettings: user budget and alert preferences
- SetUserCostSettings: update cost control settings

Store layer:
- AgentStatsStore interface with full CRUD operations
- PostgreSQL driver implementation
- SQLite stubs returning appropriate errors
- persister shutdown coordination in v1.go

Connect RPC wrappers:
- Wrap all 5 stats methods for Connect HTTP/gRPC transcoding

Frontend:
- CostTrendChart component for visualizing cost trends
- SessionSummaryPanel integration with stats display
- i18n keys for stats UI

All handlers include:
- User authentication via auth.GetUserID()
- User ownership verification
- Proto-to-Store type conversion
- Structured error logging

Refs: docs/specs/cc-runner-optimization-plan.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(ccrunner): add comprehensive event handling tests

Add cc_event_test.go with 69 test cases covering:
- StreamMessageParsing: all CLI message types (10 tests)
- GetContentBlocks: direct/nested content extraction (4 tests)
- HandleResultMessage: Result message stats extraction (3 tests)
- DispatchCallbackCoverage: event dispatch coverage (8 tests)
- ConversationIDToSessionID: UUID v5 deterministic mapping (5 tests)
- SessionStats: stats collection (6 tests)
- StreamMessageEdgeCases: edge case handling (7 tests)
- ContentBlockTypes: content block parsing (5 tests)
- NestedMessageStructure: nested message handling (3 tests)
- SessionStatsDataStructure: stats serialization (1 test)
- EventMetaStructure: metadata structure (1 test)
- UnknownMessageTypeHandling: unknown type tolerance (1 test)
- SessionStatsConcurrency: concurrent safety (1 test)
- CCRunnerConfigDefaults: config defaults (1 test)
- ResultMessageVariations: Result message variants (4 tests)
- SummarizeInput: input summary generation (5 tests)
- BuildSystemPromptCoverage: system prompt building (3 tests)

Add cc_event_tester.sh for quick test execution.

Refs: docs/research/cc-runner-message-handling-research.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs(release): prepare v0.81.0 release notes

Update documentation for CC Runner Session Stats & Cost Tracking:

CHANGELOG.md:
- Add v0.81.0 entry with full feature breakdown
- Database schema (3 new tables)
- 5 new gRPC handlers
- Frontend components (CostTrendChart, SessionSummaryPanel)
- Async persister architecture
- 69 test cases for event handling
- Security fixes from quality review

README.md:
- Add "成本追踪" to features table
- Add CC Runner optimization plan link to docs

Version 0.81.0 marks the completion of the 3-phase CC Runner
optimization plan with full session statistics and cost tracking.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): address code review findings from Loki Mode review

Critical fixes:
- Add AIService.Close() method for proper persister shutdown
  Prevents goroutine leaks and data loss on server shutdown

High priority fixes:
- Add maxOffset limit (10000) to prevent unbounded pagination
- Use defer close(heartbeatDone) to ensure cleanup on panic

Code review improvements:
- Add sync.RWMutex for concurrent Close() calls
- Proper error aggregation in Close()

All golangci-lint checks pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): fix all remaining code review issues

Performance optimizations:
- parseStringArray: use strings.Builder for O(n) instead of O(n²)
- Add partial index idx_session_stats_user_success for is_error=false queries

Database fixes:
- conversation_id: INTEGER (matching ai_conversation.id type) instead of BIGINT
- Remove redundant index on user_cost_settings.user_id (UNIQUE provides index)
- Standardize constraint name: chk_agent_session_stats_type

Error handling:
- GetMostExpensiveSession: log error instead of silently ignoring
- Add slog import for structured logging

All golangci-lint checks pass.

Refs: docs/specs/cc-runner-optimization-plan.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs(release): update CHANGELOG with all code review fixes

Add performance optimizations and database improvements to v0.81.0 release notes:
- parseStringArray O(n) performance fix
- Partial index for is_error filtering
- conversation_id type fix (BIGINT → INTEGER)
- Constraint name standardization
- Redundant index removal
- GetMostExpensiveSession error logging

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore(release): bump version to v0.91.0

- Update version.go to 0.91.0
- Update CHANGELOG.md header version

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(ccrunner): fix lint issues in cc_event_test.go

- Fix unused write to err (use _ = instead)
- Remove unused fields in TestCCRunnerConfigDefaults
- Remove empty if branches
- Add actual verification for generated SessionID
- Add uuid import for UUID validation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(ccrunner): fix TestBuildSystemPromptCoverage assertion

Update expected strings to match actual markdown format with **.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs(release): add v0.9.2 release notes

- CC Runner Session Stats & Cost Tracking
- Database schema (3 new tables)
- Backend API (5 RPCs)
- Frontend components (CostTrendChart, SessionSummaryPanel)
- 69 test cases, all passing
- Security and performance fixes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs(release): translate v0.9.2 release notes to Chinese

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore(release): bump version to 0.92.0

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(stats): use pq.Array for string slice serialization

Fixes "sql: converting argument $18 type: unsupported type []string" error.
PostgreSQL requires pq.Array to convert Go []string to SQL array.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(stats): properly handle JSONB for tools_used, TEXT[] for file_paths

- tools_used (JSONB): use json.Marshal()
- file_paths (TEXT[]): use pq.Array()

Previous fix incorrectly used pq.Array for both.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(stats): remove conversation_id foreign key constraint

Geek/Evolution modes operate independently and don't require
ai_conversation entries. The FK constraint was causing save failures
when conversation doesn't exist yet.

- Remove fk_session_stats_conv constraint
- Keep conversation_id as reference field for analytics

Resolves: pq: insert or update on table "agent_session_stats"
violates foreign key constraint "fk_session_stats_conv"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(stats): always pass valid JSON/arrays for empty collections

- tools_used: pass "[]" instead of nil for empty JSONB
- file_paths: pass pq.Array([]string{}) instead of nil for empty TEXT[]

Fixes: pq: invalid input syntax for type json

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(stats): comprehensive fixes from code review

Multiple fixes based on thorough code review:

1. cc_runner.go:
   - Add tools and file paths deduplication (using map/set)
   - Add cost calculation fallback when CLI reports $0
   - Fix TotalTokens to only count billed tokens (input+output)
   - Set ModelUsed to "claude-code" (constant, CLI doesn't report model)

2. persister.go:
   - Add idempotency protection with 5-second duplicate window
   - Use sync.Map for concurrent-safe session tracking
   - Add dedupEnabled flag for future control

3. agent_stats.go:
   - parseStringArray now always returns empty slice, not nil
   - Ensures consistency between read/write operations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(stats): safely handle type assertion in dedup check

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* debug(ai): add logging to trace stream completion

Add logging to track:
- Backend: when Done: true message is sent with session summary
- Frontend: stream loop progress, done signal reception, fallback usage

This will help diagnose why Geek Mode sessions appear to hang
instead of completing properly.

Refs #65

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* debug(ai): add more logging to trace executeAgent flow

Add logs at key points:
- After ExecuteWithCallback completes
- When preparing session summary
- When checking if agent is SessionStatsProvider

This will help identify where the code is getting stuck
after session stats are received.

Refs #65

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): resolve deadlock in streamOutput when stdout completes

Root cause: After stdout goroutine completes, the stderr goroutine
was still waiting on its select for either scanDone or streamCtx.Done().
But streamCtx.Done() wouldn't be signaled until stopStreams() is called,
which is deferred and only runs when the function returns. This created
a deadlock because the function couldn't return until both goroutines
completed, but stderr couldn't complete until streamCtx was canceled.

Fix: Call stopStreams() immediately after stdout scan completes,
which signals streamCtx.Done() and causes the stderr goroutine to exit
its select statement and complete.

This resolves the issue where Geek Mode sessions would hang after
receiving the result message from Claude Code CLI.

Refs #65

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(web): remove ES module syntax from sw.js

Service Workers don't support ES module syntax like 'export default'.
Changed 'export default null;' to a comment to fix syntax error
causing Service Worker registration to fail.

Fixes browser console error:
  Uncaught SyntaxError: Unexpected token 'export' (at sw.js:155:1)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(ai): enhance geek mode UI with tool results and session summary

Frontend:
- Add ExpandedSessionSummary component with detailed metrics
  - Duration breakdown with visual progress bar
  - Token usage (input/output/cache)
  - Cost analysis
  - Tool calls summary
  - Files modified (Evolution Mode)
- Add tool results display in terminal-style cards
- Fix type definitions for toolCallsRef and toolResults metadata

Backend:
- Fix scanner loop blocking issue in cc_runner (break vs return)
- Add detailed logging for debugging stream completion
- Simplify stderr handling to io.Copy

Refs #120

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ui): complete session summary display with mode indicator

- Add mode field to SessionSummary proto (geek/evolution/normal)
- Update ExpandedSessionSummary to show mode badge with icon
- Display mode prominently in header (Geek Mode / Evolution Mode)
- Show all session stats: duration breakdown, tokens, cost, tools, files
- Map agent_type to mode in frontend summary handling

This resolves the incomplete session display issue where mode was missing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(chat): restore beautiful markdown rendering in ChatMessages

- Restore early version with精美 table/list styling
- Tables: border, shadow-sm, bg-muted/50 header, hover effects
- Lists: proper spacing (mb-2, space-y-1, pl-1)
- Blockquotes: border-l-4, bg-muted/30, rounded-r-lg
- Inline code: px-1.5 py-0.5 rounded-md bg-muted

Reverts simplified styling that made markdown look ugly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(chat): restore Tool Results Display with精美 styling

- Add ToolResult type with name, outputSummary, duration, isError
- Display tool results in terminal-style cards (slate-950 bg)
- Show tool icon (Terminal for bash/run, File for others)
- Add duration badge and error indicator
- Keep beautiful markdown rendering from previous commit

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(chat): use ExpandedSessionSummary with精美的完整面板

- Replace SessionSummaryPanel with ExpandedSessionSummary
- Shows full session stats with expandable card:
  - Mode badge (Geek/Evolution/Normal) with icon
  - Status indicator with color
  - Duration breakdown bar chart (thinking/tools/generation)
  - Token grid (input/output/cache)
  - Cost display with per-1K-token rate
  - Tool calls count with average time
  - Files modified with path list

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(summary): show full session ID instead of truncated

- Remove .slice(0, 8) truncation
- Add break-all for proper wrapping of long IDs
- Keep select-all for easy copying

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(chat): message bubbles default to expanded

- Change isFolded initial state from true to false
- Users can still manually fold long messages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(summary): ExpandedSessionSummary defaults to collapsed

- Change isExpanded initial state from true to false
- Users can click to expand and see full stats

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): track file modifications in SessionStats

- Add RecordFileModification method to SessionStats
- Extract file path from Write/Edit tool input
- Update FilesModified count and FilePaths list
- Handle both direct tool_use and nested tool_use in assistant messages

Fixes:
- FilesModified always showing 0
- FilePaths never populated

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* debug(ccrunner): add logging for SessionStats data flow

- Log values returned by GetSessionStats()
- Log when handleResultMessage updates TotalDurationMs
- Log token usage updates from CLI

This will help diagnose why SessionSummary shows incorrect values.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): preserve CLI-reported TotalDurationMs

Problem: After handleResultMessage correctly set TotalDurationMs to
CLI's reported value (12175ms), it was being overwritten by
time.Since(StartTime) (28444ms) which includes backend overhead.

Solution: Only override TotalDurationMs if CLI didn't provide it.
The CLI's duration_ms is the actual execution time and should be used.

This fixes:
- TotalDurationMs now shows CLI's 12175ms instead of 28444ms
- Frontend SessionSummary displays accurate duration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* debug(ccrunner): add cache_read_tokens to GetSessionStats logging

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): isolate CLI config to reduce token usage

- Add CLAUDE_CONFIG_DIR environment variable for Geek Mode sessions
- Prevents loading main ~/.claude/history.jsonl (3891 lines, 1.3MB)
- Creates isolated config at ~/.divinesense/claude-geek/user_{id}/
- Significantly reduces input_tokens for simple queries

Before: input_tokens=84391 (loading entire main CLI history)
After: input_tokens will only include Geek Mode context

This resolves the issue where a simple message consumed 84K tokens
due to cached history from main Claude Code CLI sessions.

Technical details:
- CLAUDE_CONFIG_DIR is set per-user to isolate sessions
- Each user gets their own CLI history and config
- Cache read tokens will now reflect only relevant Geek Mode context

Refs #96

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): add detailed token logging to debug cache_read > input

Add cache_write_tokens and cache_read_tokens to the log output,
plus a flag indicating when cache_read exceeds input.

This will help debug why Claude Code CLI sometimes reports
cache_read_tokens > input_tokens, which appears to be
an artifact of CLI's cumulative token accounting.

Refs #96

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): address all code review findings

P1 - High Priority:
- Fix TotalDurationMs conditional logic: use <= 1 instead of == 0
  to better handle edge cases where CLI reports minimal values

P2 - Medium Priority:
- Optimize RecordFileModification: O(1) map lookup instead of O(n) scan
  Added filePathsSet field to SessionStats for efficient deduplication
- Add stderr sampling (10% rate) to preserve debug info without log flooding
  Changed from complete discard to sampled logging

P3 - Low Priority:
- Extract magic numbers to package-level constants:
  deepSeekInputCostPerMillion = 0.27
  deepSeekOutputCostPerMillion = 2.25
- Unify log levels: changed key lifecycle events to Info level

These changes improve:
- Data accuracy: TotalDurationMs now handles edge cases correctly
- Performance: file path deduplication is now O(1) instead of O(n)
- Observability: stderr errors are now sampled for debugging
- Maintainability: pricing constants are centralized

Refs #96

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): improve error reporting with stderr context

- Add stderrBuffer type to capture last 100 stderr lines
- Include stderr content in error messages when CLI fails
- This helps debug "exit status 1" errors by showing CLI error output

Example error output before:
  "command exited with code 1: exit status 1"

Example error output after:
  "command exited with code 1: exit status 1 (stderr: Error: ...)"

Changes:
- Add stderrBuffer struct with thread-safe line capture
- Pass stderrBuffer to streamOutput as parameter
- Modify executeWithSession to include stderr in error messages
- Refactor stderr goroutine to use stderrBuffer.addLine()

This will help identify the root cause of CLI failures like the
"GeekMode execution failed" error reported by users.

Refs #96

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): use main CLI config for authentication

Issue: Geek Mode CLI failed with "Could not resolve authentication method"
Root cause: CLAUDE_CONFIG_DIR pointed to isolated config without API credentials

Trade-off: CLI will now load main CLI's history.jsonl (~1.3MB, 3891 lines)
which increases initial token usage. This is acceptable since authentication
is required for CLI to function.

Alternative considered: Copy API credentials to isolated config
- Rejected: Credentials stored in encrypted format, copying is complex
- Rejected: CLI manages credentials internally, manual copying error-prone

After authentication is working, we can optimize by:
1. Using CLI's --session-config-dir to point to existing session
2. Or implementing credential sync between main and isolated config

Refs #96

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(ui): improve token display with clear cache labeling

Changes to ExpandedSessionSummary Token panel:
- Split Input into "New Input" (billed) and "Cache Read (free)"
- Add visual indicators (colored dots) to distinguish billed vs free tokens
- Use green color for Cache Read to indicate it's free
- Rename "Total" to "Billed Total" for clarity
- Calculate billed total as: new input + output (excluding cache read)

This helps users understand:
- What they're actually paying for (new input + output)
- What's free (cache read)
- The value of caching (green = saved money)

Refs #96

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ui): improve session summary display clarity

- Fix token calculation: input_tokens already represents "new input" per Anthropic API docs, no need to subtract cache_read_tokens
- Remove hardcoded pricing estimates (use actual backend cost instead)
- Add Cache Hit Rate indicator to show cache effectiveness
- Only show token rows with values > 0 (hides confusing "0" entries)
- Fix division by zero risk in tool calls average calculation
- Improve cost display: show actual cost + cost per 1K billed tokens
- Keep Files Modified card visible even when count is 0

Refs #96cb56e4

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): suppress gosec G404 for logging sampler

Math/rand is acceptable for non-security sampling (logging rate limit).
Add nolint comment to clarify intent.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ui): hide Files Modified card when count is zero

Only show the Files Modified card when files are actually modified.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ccrunner): resolve goroutine leaks and race conditions

- Fix streamCtx to derive from parent ctx for proper cancellation propagation
- Fix Session.WriteInput() timer race condition with proper lock handling
- Consolidate duplicate cmd.Wait() error handling
- Remove inefficient map re-allocation in handleResultMessage
- Add panic recovery to scanner goroutine to prevent deadlock

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ui): use real SessionID in summary; remove console logs in production

- Fix SessionID display: use real UUID from detailedStats instead of "conv_N"
- Remove 18 unnecessary logs from cc_runner.go (raw line, received message, etc.)
- Add terser drop_console for production builds
- Add rollup-plugin-visualizer for bundle analysis

Refs #9

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(chat): add timestamp display to chat messages

- Add formatMessageTime() function with relative time display
- Show message timestamp below each message bubble
- User messages: right-aligned time
- AI messages: left-aligned time
- Use existing i18n keys from ConversationItem

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(chat): increase timestamp contrast for user messages

- User messages: text-white/70 (light mode) / text-slate-900/60 (dark mode)
- AI messages: keep subtle low-contrast style
- Improves readability on dark user message bubbles

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(chat): reduce timestamp font size and add 6 parrot themes

- Reduce timestamp font from 10px to 8px for subtle display
- Add GEEK (violet) and EVOLUTION (rose) themes
- Add sound effects, catchphrases, behaviors for new themes
- Extend ParrotAgentType enum with GEEK and EVOLUTION

Refs #63

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(chat): apply WeChat-style timestamp display (outside bubble)

- Move timestamp outside message bubble (WeChat style)
- User messages: timestamp on right side
- AI messages: timestamp on left side
- Gray color (text-muted-foreground/60), 10px font
- Vertically centered with bubble

Refs #63

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(chat): implement WeChat-style centered timestamp

- Timestamp displayed as centered row between messages
- Only shows when gap from previous message > 3 minutes
- Gray badge style (bg-muted/50, text-muted-foreground/60)
- First message always shows timestamp
- Shows after context-separator

Refs #63

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: 黄飞虹 <aaronwong1989@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(chat): simplify branching to block number display + AI query routing improvements

2 participants