Skip to content

feat(ccrunner): session stats and cost tracking#65

Merged
hrygo merged 54 commits into
mainfrom
feat/cc-runner-session-stats
Feb 3, 2026
Merged

feat(ccrunner): session stats and cost tracking#65
hrygo merged 54 commits into
mainfrom
feat/cc-runner-session-stats

Conversation

@hrygo

@hrygo hrygo commented Feb 3, 2026

Copy link
Copy Markdown
Owner

概述

完善 CCRunner 事件类型系统,添加前端会话统计展示、消息时间戳显示、优化 UI 主题。

变更内容

后端

  • 修复 goroutine 泄漏和竞态条件
  • 优化日志输出,移除不必要的日志
  • 修复 SessionID 显示(使用真实 UUID 而非 conv_N 格式)
  • 完善会话统计和成本追踪

前端

  • 消息时间戳:每条消息显示相对时间(刚刚、5分钟前)
  • 时间戳样式:用户消息气泡使用高对比度,字体 8px
  • 6 个鹦鹉主题:MEMO、SCHEDULE、AMAZING、GEEK、EVOLUTION
    • GEEK: violet 色系(极客模式)
    • EVOLUTION: rose 色系(进化模式)
  • 会话摘要面板:完整展示 token 使用、耗时、成本
  • 工具调用卡片:精美样式展示

构建

  • Vite 生产构建自动移除 console.log
  • 添加 rollup-plugin-visualizer 用于包分析

关联 Issue

Resolves #63

测试计划

  • 本地测试通过
  • 后端测试通过
  • 前端 lint + build 通过
  • 6 个主题颜色正确显示
  • 时间戳在用户/AI 消息上都可见

截图/演示

  • 消息时间戳显示在气泡下方
  • 用户消息时间戳为白色(深色气泡上)
  • AI 消息时间戳为灰色
  • Geek/Evolution 模式使用独立配色

检查清单

  • 代码遵循项目规范
  • 自我审查代码
  • 无合并冲突

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

hotplex-ai and others added 15 commits February 3, 2026 23:14
- 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>
- 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>
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>
…y 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>
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>
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>
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>
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>
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>
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>
- Update version.go to 0.91.0
- Update CHANGELOG.md header version

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
Update expected strings to match actual markdown format with **.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@hrygo hrygo force-pushed the feat/cc-runner-session-stats branch from ee3c020 to 080f06d Compare February 3, 2026 15:16
hotplex-ai and others added 14 commits February 3, 2026 23:18
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
- 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>
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>
- 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>
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>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
- 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>
- 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>
hotplex-ai and others added 22 commits February 4, 2026 00:28
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
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>
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>
- 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>
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>
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 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>
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>
Only show the Files Modified card when files are actually modified.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
…tion

- 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>
- 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>
- 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>
@hrygo hrygo changed the title feat(ccrunner): session stats and cost tracking v0.91.0 feat(ccrunner): session stats and cost tracking Feb 3, 2026
hotplex-ai and others added 3 commits February 4, 2026 02:02
- 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>
- 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>
- 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>
@hrygo hrygo merged commit 77b9f66 into main Feb 3, 2026
11 checks passed
hrygo added a commit that referenced this pull request Feb 5, 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>
hrygo pushed a commit that referenced this pull request Feb 7, 2026
The test was checking for PascalCase field names (DurationMs, ToolName)
but the struct uses snake_case JSON tags (duration_ms, tool_name).

This is a pre-existing bug in PR #65 that caused CI to fail.

Refs #89

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
hrygo added a commit that referenced this pull request Feb 7, 2026
* fix(frontend): optimize Vite config for React deps stability

Add React and related dependencies to optimizeDeps.include to prevent
runtime issues where React hooks may be unavailable due to module
loading order problems.

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

* fix(ai): add debug log for user message event tracking

Add ai.chat.user_message log to help diagnose issue where
messages are silently swallowed after previous block is done.

Related to #4

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

* fix(ai): resolve message hang when needs_direct_answer=true

Root cause: When AmazingParrot's plan sets needs_direct_answer=true
(with no memo_search/schedule_query/etc), the retrievalResults map
is empty. This caused buildSynthesisPrompt to generate a synthesis
prompt with an empty context placeholder, which could cause the LLM
to hang or produce unexpected behavior.

Changes:
- Add fallback casual chat prompt when no retrieval results exist
- Add extensive debug logging to track synthesis flow
- Add chunk counting and channel close logging for better visibility

This fixes the bug where messages are "swallowed" after a block
completes with status=done.

Related: Task #4

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

* feat(ai): add progress events for chat response feedback

Add immediate feedback events to eliminate 5-10 second "dead silence"
after user sends message. Implements 200ms psychological threshold
principle for user experience.

Backend changes:
- Add "received" event sent immediately after message receipt
- Add "routing_start" event before intent classification
- Add "routing_end" event with agent info and duration
- Apply events consistently across Normal/Geek/Evolution modes

Refs #89

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

* fix(tests): correct JSON field names in EventMeta test

The test was checking for PascalCase field names (DurationMs, ToolName)
but the struct uses snake_case JSON tags (duration_ms, tool_name).

This is a pre-existing bug in PR #65 that caused CI to fail.

Refs #89

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

* style: gofmt format alignment fixes

Fix gofmt alignment issues detected by CI golangci-lint.

Refs #90

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.

feat(ccrunner): 调研并完善 CCRunner 事件类型系统

2 participants