Commit Graph
40 Commits
Author SHA1 Message Date
7418andClaude Opus 4.6 4e01902368 fix: Volcengine model list pollution + missing by-cwd route
- provider-catalog: getDefaultModelsForProvider() now returns preset's
  defaultModels even when empty, instead of falling through to
  ANTHROPIC_DEFAULT_MODELS. Fixes Volcengine (defaultModels: []) incorrectly
  showing Sonnet/Opus/Haiku.
- providers/models route: remove DEFAULT_MODELS fallback when both DB and
  catalog are empty — providers like Volcengine that require user-specified
  model names should show only those models.
- Add missing src/app/api/chat/sessions/by-cwd/route.ts that was untracked;
  without it worktree switching would 404 in release builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:47:03 +08:00
7418andClaude Opus 4.6 e2a0f49843 fix: CAS lock for assistant trigger, LIKE escape, provider options race
Assistant trigger CAS (P1):
- hook-triggered route: added expectedOwner param for compare-and-swap —
  rejects if current owner doesn't match, closing the race where two tabs
  both clear then both claim the lock
- useAssistantTrigger: clear sends expectedOwner (stale session), set sends
  expectedOwner: null; non-2xx responses now bail instead of falling through
- Server-side completion (chat/route.ts): only clears lock if owner matches
  current sessionId, preventing completed sessions from wiping active locks
- Compensation branch: clear now passes expectedOwner to avoid bypassing CAS

Image-gen LIKE escape (P2):
- db.ts: escape backslashes before % and _ since ESCAPE '\' makes \ the
  escape char — prevents Windows paths / escaped JSON from corrupting the
  LIKE pattern and silently falling back to prompt-prefix matching

Provider options fetch race (P2):
- page.tsx + ChatView.tsx: provider options fetch now uses AbortController
  with cleanup on dependency change — prevents stale slow responses from
  overwriting the current provider's thinking/context_1m state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 21:29:10 +08:00
7418andClaude Opus 4.6 2a0451329b fix: cross-session assistant trigger guard — heartbeat liveness detection
- Replace fixed-timeout stale detection with localStorage heartbeat mechanism
- Heartbeat writes {sid, ts} every 10s; isOwnerAlive checks sid matches AND freshness
- Stop heartbeat when triggered stream completes (useEffect on !isStreaming)
- removeHeartbeatIfOwned: only delete key if it still belongs to this session
  (prevents new owner's heartbeat from being wiped by old session's cleanup)
- Export clearOnboardingHeartbeat with optional sessionId parameter
- hook-triggered route sets/clears hookTriggeredAt alongside hookTriggeredSessionId
- MiniMax model auto-correction: reset model when not in current provider's list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:27:00 +08:00
7418andClaude Opus 4.6 8cce9889eb fix: Design Agent session isolation, image write-back robustness, and new-session flow
Image reference store session-scoped:
- Rewrite image-ref-store.ts: all keys/functions now scoped by sessionId
- Thread sessionId through MessageList → StreamingMessage/MessageItem → ImageGenConfirmation
- ChatView loads last-generated images for current session on mount

Image write-back robustness:
- Remove localStorage caching from ImageGenConfirmation (root cause of stale results)
- Add rawRequestBlock for exact content matching in DB fallback query
- updateMessageBySessionAndHint prefers exact block match, falls back to prompt hint
- PUT route passes raw_request_block to DB function
- Add retry-after-3s when first PUT fails and no messageId available
- Exclude already-completed messages (NOT LIKE image-gen-result) from update query

New session page (/chat) fix:
- Add createdSessionId state, set after session creation, pass to MessageList
- Prevents first-message image-gen-request from inheriting stale panel sessionId

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:26:34 +08:00
7418andClaude Opus 4.6 16edf9f59c feat: CLI Tools — curated tool catalog, detection, install, chat awareness
New sidebar page for managing system CLI tools (ffmpeg, jq, ripgrep, yt-dlp, pandoc).

Core modules:
- cli-tools-catalog.ts: static catalog of 6 curated tools + EXTRA_WELL_KNOWN_BINS
- cli-tools-detect.ts: system detection via which/where + --version (2min cache)
- cli-tools-context.ts: builds <available_cli_tools> block for chat system prompt

API routes:
- GET /api/cli-tools/catalog, /installed, /[id]/status, /[id]/detail
- POST /api/cli-tools/[id]/install (SSE streaming), /[id]/describe (AI gen)

UI components:
- CliToolsManager: installed + recommended sections with grid layout
- CliToolCard: two variants (installed/recommended) with status badges
- CliToolDetailDialog: intro, use cases, guide steps, example prompts
- CliToolInstallDialog: SSE-based install progress with live terminal log

Chat integration:
- Chat input toolbar: Terminal icon opens CLI tool picker popover
- System prompt injection: buildCliToolsContext() in /api/chat
- MessageInput: CLI popover with search, keyboard nav, locale-aware prefill
- NavRail + ChatListPanel: frosted glass (bg-sidebar/80 backdrop-blur-xl)
- SlashCommandButton: Lightning icon, unified 16px toolbar icons
- input-group: shadow-md → shadow-sm
- Model selector: searchable popover with provider grouping

Docs:
- docs/handover/cli-tools.md + apps/site/content/docs/{en,zh}/cli-tools.mdx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:56:29 +08:00
7418andClaude Opus 4.6 281e7f9502 fix: skills/MCP/completion system overhaul — separate skill kinds, fix MCP remote servers, deduplicate completion processing
Skills system:
- Replace `CommandBadge.isSkill: boolean` with `kind: SkillKind` (agent_skill | slash_command | sdk_command | codepilot_command)
- handleSubmit dispatches by kind: agent_skill retains skill name in prompt, slash/sdk commands pass through to SDK, codepilot commands expand via COMMAND_PROMPTS
- Popover groups commands into Commands / Slash Commands / Agent Skills
- API `/api/skills` returns `kind` field; `displayOverride` persisted through full chain (frontend → API → DB)
- Forward SDK `system:init` slash_commands/skills metadata to frontend via SSE

MCP management:
- Fix remote server (SSE/HTTP) creation: validate `url` instead of `command`
- Fix dual-config read/write asymmetry: PUT routes servers by `_source` to correct file (~/.claude.json vs ~/.claude/settings.json)
- DELETE checks both config files; POST checks both for name collision
- McpManager preserves `_source` tags on edit/rename; JSON editor only manages settings.json servers

Completion processing:
- Extract core logic into `onboarding-processor.ts` and `checkin-processor.ts` (shared by route handlers and server-side detection)
- Remove frontend `detectAssistantCompletion` from ChatView — server-side path in `finally` block is now the single source of truth
- Replace hard-coded `fetch("http://localhost:PORT/...")` with direct function imports
- Both processors are internally idempotent (check state before processing)

Other:
- `settingSources` always includes 'user' so SDK loads ~/.claude/skills/
- Add 24 unit tests for SkillKind dispatch logic
- Add onboarding-completion extraction tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 12:49:43 +08:00
7418andClaude Opus 4.6 f76e4747cd feat: unified provider resolver with protocol-based dispatch
Extract provider resolution logic into two new modules:
- provider-catalog.ts: vendor presets, default model catalogs, protocol definitions
- provider-resolver.ts: unified resolution for all consumers (Claude SDK, AI SDK, model listing)

DB migration:
- Add protocol, headers_json, env_overrides_json, role_models_json columns to api_providers
- Create provider_models table for per-provider model catalog

Consumer refactoring:
- claude-client.ts: replace inline env-building with toClaudeCodeEnv()
- text-generator.ts: replace inline config with toAiSdkConfig()
- conversation-engine.ts, onboarding, checkin, plan routes: use shared resolveProvider()
- /api/providers/models: read from catalog + provider_models table

UI improvements:
- Merge confusing "Custom API (Anthropic-compatible)" into "Anthropic Third-party API"
- Add 3-field model name mapping (sonnet/opus/haiku) in advanced options
- ProviderForm.tsx: support new fields (protocol, env_overrides, role_models)

Bug fixes:
- Protect auth credentials from being deleted by legacy extra_env empty-string overrides
- Fix env var names: ANTHROPIC_DEFAULT_SONNET → ANTHROPIC_DEFAULT_SONNET_MODEL (SDK convention)

Tests: 229/229 passed. Verified with aiberm (no mapping) and pipellm (with mapping).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 20:30:35 +08:00
7418andClaude Opus 4.6 c18776f4ca feat: integrate Claude Agent SDK capabilities — model/command discovery, effort/thinking, MCP runtime, file rewind, structured output
Systematically integrate high-value SDK capabilities while preserving the
multi-provider architecture:

SDK capability discovery (per-provider cache):
- agent-sdk-capabilities.ts: cache models, commands, account info, MCP status
  per provider via Map<providerId, cache> to prevent cross-provider pollution
- claude-client.ts: fire-and-forget captureCapabilities() after query() init
- providers/models: merge SDK models with static defaults
- skills: merge SDK slash commands with filesystem skills

Query options passthrough:
- thinking (adaptive/enabled/disabled), effort (low/medium/high/max),
  outputFormat, agents, enableFileCheckpointing
- /chat page.tsx: first-message path now includes effort/thinking (was missing)
- stream-session-manager: passes effort/thinking in POST body

Graceful interrupt:
- /api/chat/interrupt: conversation.interrupt() before abort fallback
- stream-session-manager: tries interrupt first, aborts after 2s timeout

MCP runtime management:
- /api/plugins/mcp/status: live status from active Query, auto-resolves
  providerId from session DB record (prevents wrong-provider cache writes)
- /api/plugins/mcp/reconnect + toggle: reconnect/enable/disable servers
- McpManager + McpServerList: runtime status indicators with action buttons

File checkpointing & rewind:
- enableFileCheckpointing=true for code mode by default
- rewind_point SSE: only emitted for prompt-level user messages
  (parent_tool_use_id===null) and skips autoTrigger turns
- /api/chat/rewind: dry-run preview + actual rewind via rewindFiles()
- MessageList: RewindButton with dry-run preview

Structured output:
- /api/chat/structured: reads SDKResultSuccess.structured_output as primary
  path, text fallback only when absent

Account info & thinking config:
- /api/sdk/account: cached account info display
- Settings: thinking_mode in ALLOWED_KEYS, thinking mode selector in UI
- Effort selector in MessageInput with lifted state pattern

New files: 11 route handlers + 2 lib modules + 1 test file
Modified files: 18 (types, client, manager, UI components, i18n)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:22:05 +08:00
7418andClaude Opus 4.6 6c02c135fe fix: composer UI polish and permission selector improvements
Slash command:
- Fix first-click bug: set textarea.value and cursor position before
  calling handleInputChange so selectionStart reads correctly
- Move slash button to right of + (attach) button
- Simplify icon from CommandLineIcon to plain "/" text

Composer action bar:
- Move Agent toggle and permission selector to left side (was centered)
- Adjust spacing: reduce input padding, increase bottom margin

Permission selector:
- Add dropdown arrow indicator (ArrowDown01Icon)
- Change full_access color from orange to red (icon + text + background)
- Check res.ok before updating UI state (prevent false success)
- Make sessionId optional to support /chat new session page
- Log warnings on PATCH failure instead of silent catch

New chat page (/chat):
- Add ChatPermissionSelector with local state before session exists
- Pass permission_profile in session creation payload
- Backend createSession() now accepts and writes permission_profile

Context usage indicator:
- Reduce circle size from 22px to 16px
- Change used-portion color to dark gray (zinc-600/400)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 16:03:19 +08:00
7418andClaude Opus 4.6 111fa8c92e feat: refactor composer to two-layer layout, add session permission & context indicator
Composer restructure:
- MessageInput internal footer now only contains: file button, model
  selector, slash command button, and send button
- Removed mode toggle (Code/Plan) from composer area (logic preserved)
- New ChatComposerActionBar below input with three slots: left (Design
  Agent), center (permission selector), right (context indicator)

New components:
- SlashCommandButton: inserts `/` at cursor, reuses existing slash
  command detection and popover logic
- ChatPermissionSelector: session-level permission toggle (default /
  full_access) with danger confirmation dialog
- ContextUsageIndicator: SVG donut chart showing context usage ratio
  with HoverCard displaying model, tokens, cache stats
- ChatComposerActionBar: slot-based container for action bar items

Session permission (permission_profile):
- DB: new column on chat_sessions, default 'default'
- API: PATCH sessions supports updating permission_profile
- Chat API: bypassPermissions = global setting OR session full_access
- PermissionPrompt: auto-approves and hides when full_access
- Claude client: combines global and per-session bypass flags

Bridge integration:
- permission-broker: auto-approves permissions for full_access sessions
  without sending IM notifications
- conversation-engine: passes bypassPermissions to SDK for full_access
- PATCH route: auto-approves pending bridge permissions on switch to
  full_access

Other:
- ImageGenToggle relabeled to "Design Agent" / "设计 Agent"
- New model-context.ts with MODEL_CONTEXT_WINDOWS lookup
- Provider models API returns optional contextWindow field
- New useContextUsage hook for token usage calculation
- i18n: added composer.*, permission.*, context.* keys (en + zh)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 14:55:38 +08:00
jasonjaclyn2017 a53c60531f fix: prevent stale SDK session and tool-marker parroting after idle timeout (#184)
fix: prevent stale SDK session and tool-marker parroting after idle timeout
2026-03-07 10:26:05 +08:00
7418andClaude Opus 4.6 94fdd36d27 feat: assistant workspace V2 — indexing, retrieval, security hardening & workspace switching
Assistant Workspace V2:
- Markdown indexing engine (incremental manifest + chunks JSONL)
- Keyword retrieval with CJK bigram support and hotset boosting
- Directory taxonomy inference and evolution suggestions
- Workspace config management (.assistant/config.json)
- Organize API: capture, classify, move, archive, suggest-evolution

Workspace switching:
- Inspect API (GET /api/workspace/inspect) for pre-switch validation
- Two-phase switching with confirmation dialogs (5 workspace statuses)
- Atomic save: init/reset runs before setSetting(), rollback on failure
- Auto-navigate to new/reused session after switch
- Mismatch banner in ChatView (only for former assistant sessions)
- Debounced path validation with visual indicators

Security hardening:
- Completion fence (onboarding-complete/checkin-complete) only processes
  when session.workingDirectory === assistant_workspace_path (frontend)
  AND session.working_directory === workspacePath (backend double-check)
- assertContained() in workspace-organizer: rejects absolute paths, ~/,
  ../ traversal, and symlink escapes for captureNote/moveFile
- organize API validates all path params at entry (validateRelativePath)
- captureDefault sanitized to relative path on onboarding write
- R_OK + W_OK validation on GET settings, session creation, and PUT

Bug fixes:
- hookTriggeredSessionId now read before auto-trigger; allows retry if
  session has no messages (previous trigger failed silently)
- workspace-switched event listener matches oldPath (not just != newPath)
  so normal project chats never show the mismatch banner
- Daily check-in card hidden when onboarding not complete
- Refresh docs now calls generateRootDocs() + generateDirectoryDocs()
- Banner "Open" button reuses latest session (checkin mode) instead of
  always creating new onboarding session

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:37:02 +08:00
7418andClaude Opus 4.6 19aba25c8f feat: assistant workspace — persistent AI personality & memory via Markdown files (#165)
Add "Assistant Workspace" feature that lets users designate a directory
(e.g. Obsidian vault) containing 4 Markdown files (soul.md, user.md,
memory.md, claude.md) to define AI personality, user profile, long-term
memory, and execution rules. These files are automatically injected into
the system prompt for assistant project sessions.

Core library (src/lib/assistant-workspace.ts):
- Workspace validation, initialization with template files
- File loading with 8000-char truncation, XML prompt assembly (40k limit)
- State management via .assistant/state.json
- Directory docs generation (README.ai.md + PATH.ai.md)
- Case-insensitive file name fallback (soul.md → SOUL.md)

System prompt injection (src/app/api/chat/route.ts):
- Scoped to sessions where working_directory === workspace path
- Workspace prompt prepended before session-level system prompt
- Onboarding/check-in instructions injected as <assistant-project-task>
- autoTrigger flag skips user message save and title update

Chat-based onboarding (10 questions) & daily check-in (3 questions):
- AI conducts conversation, outputs structured completion code blocks
- Frontend detects onboarding-complete/checkin-complete blocks
- Backend calls Claude API to generate file content from answers
- Fallback to raw answers if API call fails

Auto-trigger pipeline:
- Mount trigger (500ms delay) + focus fallback in MessageInput
- assistantTriggerFiredRef prevents duplicate triggers per mount
- hookTriggeredSessionId in state.json prevents cross-reload duplicates
- Daily check-in only triggers in the most recent workspace session
- hookTriggeredSessionId cleared via __clear__ sentinel after completion

API endpoints:
- GET/PUT /api/settings/workspace — path CRUD + file status
- POST /api/workspace/session — create (onboarding) or reuse (check-in)
- POST /api/workspace/onboarding — AI-generate soul.md + user.md
- POST /api/workspace/checkin — AI-update memory.md + user.md
- POST /api/workspace/hook-triggered — set/clear trigger guard
- GET /api/workspace/latest-session — latest session lookup
- POST /api/workspace/docs — refresh directory documentation

UI:
- Settings "Assistant" tab with workspace path, file status, controls
- Violet badge on workspace project group in ChatListPanel
- useAssistantWorkspace hook for state queries

Also includes:
- 102 passing tests (16 new regression tests for workspace logic)
- i18n keys for en.ts and zh.ts
- Handover documentation and execution plan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:31:42 +08:00
7418andClaude Opus 4.6 c9f1b62809 fix: 模型切换串台 + 发送消息后滚动不到底部
1. page.tsx — loadSession 加 cancelled 竞态守卫,快速切换会话时
   丢弃过期的 fetch 结果,防止旧会话的 model/provider 覆盖新会话
2. ChatView.tsx — 去掉 if(modelName)/if(providerId) 守卫,改为
   无条件同步 props,空值走 localStorage → default fallback 链
3. ChatListPanel.tsx + types + route.ts + db.ts — 三个创建会话入口
   (handleFolderSelect/handleNewChat/handleCreateSessionInProject)
   统一从 localStorage 读取并传递 model + provider_id,DB 层写入
4. MessageList.tsx — ScrollOnStream 增加 messageCount prop,
   消息数增加时立即 scrollToBottom,覆盖乐观插入和 assistant 落地

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 12:01:10 +08:00
7418andClaude Opus 4.6 d9c4ad2c95 fix: 切换聊天后模型选择不保持
两个问题:
1. 用户选择模型只写 localStorage,不写 DB。切换聊天回来后从 DB
   读到空值,模型回退到默认值
2. ChatView 的 useState 只用初始值,后续 modelName prop 异步更新
   不会同步到 currentModel state

修复:
- handleProviderModelChange 选择模型时立即 PATCH 到 DB
- PATCH /api/chat/sessions/:id 新增 model + provider_id 字段支持
- ChatView 添加 useEffect 同步 modelName/providerId prop 变化

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:55:36 +08:00
7418andClaude Opus 4.6 6ad821d3af feat: 多 IM 远程桥接系统 + Bridge 一级导航 + bug 修复
**Bridge 系统核心(src/lib/bridge/)**
- bridge-manager: 生命周期编排、adapter 事件循环、会话级并发控制
- channel-adapter: 抽象基类 + 注册式 adapter 工厂
- channel-router: IM 地址 → CodePilot session 自动映射
- conversation-engine: 服务端消费 streamClaude() SSE 流
- permission-broker: 权限请求转发到 IM 内联按钮
- delivery-layer: 出站分片、限流、重试退避、HTML 降级
- telegram-adapter: Telegram 长轮询 + offset 安全水位 + 幂等去重
- security/: 路径校验、危险输入检测、滑动窗口限流

**Telegram 通知 Bot(src/lib/telegram-bot.ts)**
- 独立通知模式,与 bridge 模式互斥
- bridgeModeActive 使用 globalThis 存储,避免 HMR 重置导致轮询冲突

**API 路由**
- /api/bridge: GET 状态查询 + POST start/stop/auto-start
- /api/bridge/channels: 活跃通道列表
- /api/bridge/settings: bridge 设置读写
- /api/settings/telegram: 凭据管理(含 allowed_users 白名单)
- /api/settings/telegram/verify: 连接验证 + chat ID 自动检测

**UI 重构:Bridge 提升为一级导航**
- NavRail 新增 Bridge 图标导航项
- /bridge 页面:BridgeLayout hash 路由,Bridge + Telegram 两个子 tab
- BridgeSection: 总开关、状态、启停、通道开关、适配器状态、默认设置
- TelegramBridgeSection: 凭据配置 + 白名单 + Setup Guide(6 步)
- Settings 移除 Telegram 和 Bridge tab,只保留 4 个核心 tab

**Bug 修复**
- bridge-manager: runAdapterLoop 在 state.running=true 之前被调用,
  while 条件同步求值为 false 导致消费者循环不启动。修复为先设 running
  再启动 loop
- telegram-bot: bridgeModeActive 改用 globalThis 防止 HMR 丢失状态
- settings/telegram route: TELEGRAM_KEYS 补充 telegram_bridge_allowed_users

**Electron**
- 窗口关闭时 bridge 活跃则最小化到系统托盘而非退出
- 启动时通过 auto-start API 恢复桥接
- preload 暴露 bridge 相关 IPC

**文档**
- docs/bridge-system.md: 完整架构文档
- docs/chat-sdk-integration-feasibility.md: Chat SDK 集成调研
- CLAUDE.md 添加 bridge 文档链接

chore: bump v0.21.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 00:18:19 +08:00
7418andClaude Opus 4.6 b5233d0257 fix: Image Agent 模式下图片不回显、不调用生成工具的问题
三个连锁 Bug 的根因修复:

1. route.ts — 图片 base64 数据保留
   - 之前:文件存盘后一律清空 base64 data(包括图片)
   - 现在:只对非图片文件清空 data,图片始终保留 base64
   - 确保 SDK 收到有效的 image content block 用于 vision

2. types/index.ts — ClaudeStreamOptions 增加 imageAgentMode 标记
   - 新增 imageAgentMode?: boolean 字段
   - 由 route.ts 根据 systemPromptAppend 是否存在来设置

3. claude-client.ts — buildFinalPrompt 在 imageAgentMode 下跳过文件路径引用
   - imageAgentMode=true 时不添加 [User attached image: /path] 文本
   - 防止 Claude 看到路径后调用 analyze_image 等内置工具
   - Claude 仅通过 vision(base64 content blocks)看到图片
   - 遵循 IMAGE_AGENT_SYSTEM_PROMPT 输出 image-gen-request 块
   - 普通模式行为不变,仍保留路径引用供 skill/工具使用

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:45:49 +08:00
7418andClaude Opus 4.6 132fdeb255 perf: P0 performance optimizations — memo, base64 cleanup, startup speed, bump v0.17.5
Six independent fixes targeting startup lag, streaming jank, and excessive payloads:

Step 0 — MessageItem React.memo + useMemo (MessageItem.tsx)
- Wrap MessageItem with React.memo so historical messages skip re-render
  when `message` ref is stable during streaming
- Add useMemo for parseToolBlocks + pairTools (deps: message.content),
  parseMessageFiles (deps: text), token_usage JSON.parse (deps: message.token_usage)
- Extract assistant content rendering into separate memo'd AssistantContent component
  to cache parseBatchPlan / parseImageGen chain (deps: displayText, messageId)
- Root cause: 50 msgs × 10 SSE events/s = 500 full parses/s causing UI jank

Step 1 — Strip base64 from new messages (ChatView.tsx, route.ts)
- ChatView: remove `data: f.data` from fileMeta in display content HTML comment
- route.ts: skip base64 data in fileAttachments when filePath exists (file already on disk)
- Root cause: base64 image data inflated message content to MB-level

Step 2 — Sanitize old messages in API (messages/route.ts)
- Add stripFileData() to strip base64 `data` fields from <!--files:...--> comments
  before returning messages from GET /api/chat/sessions/:id/messages
- Handles legacy data already stored with base64

Step 3 — Reduce initial fetch limit 100 → 30 (page.tsx, messages/route.ts)
- Default limit changed from 100 to 30 on both frontend and backend
- "Load more" functionality unchanged

Step 4 — ChatListPanel dedup requests (ChatListPanel.tsx)
- Remove duplicate pathname-dependent useEffect (was causing 2 fetches per navigation)
- Add AbortController to cancel in-flight requests on new fetch
- Add 300ms debounce for event-driven fetches (session-created/session-updated)

Step 5 — Electron startup: show window before server ready (electron/main.ts)
- createWindow() now accepts optional URL, defaults to inline loading HTML
- Loading page: dark theme, centered spinner, "Starting CodePilot..." text
- Startup: startServer() → createWindow(loading) → waitForServer() → loadURL(real)
- Also updated activate handler for same early-window pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:00:03 +08:00
7418andClaude Opus 4.6 0c379e5607 fix: address tool_result dedup, SDK task ID collision, and doc accuracy
P1: tool_result dedup changed from "first wins" to "last wins"
- claude-client.ts: remove emittedToolResultIds Set, both PostToolUse and
  user message handler emit freely. PostToolUse provides immediate UI feedback;
  user handler's potentially more complete result arrives second.
- route.ts: collectStreamResponse replaces existing contentBlock entry when
  duplicate tool_use_id arrives (last wins for DB persistence).
- ChatView.tsx: onToolResult replaces existing state entry by tool_use_id
  instead of skipping, ensuring most complete result is displayed.

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

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

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

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:22:10 +08:00
7418andClaude Opus 4.6 c13ff54638 feat: fix tool call chain and Tasks panel integration
Phase 1: RightPanel Files/Tasks tab toggle
- RightPanel.tsx: add Files/Tasks toggle buttons, conditionally render
  FileTree vs TaskList based on panelContent state
- en.ts/zh.ts: add panel.tasks translation

Phase 2: TodoWrite SDK mapping
- types/index.ts: add source field to TaskItem, add task_update SSE event type
- db.ts: add source column migration, syncSdkTasks() replace-all function
  that syncs SDK TodoWrite output to local tasks table (user tasks untouched)
- claude-client.ts: PostToolUse hook detects TodoWrite tool, emits task_update
  SSE with mapped todo items
- route.ts: collectStreamResponse handles task_update, calls syncSdkTasks()
- useSSEStream.ts: add onTaskUpdate callback and task_update case
- ChatView.tsx: onTaskUpdate dispatches tasks-updated CustomEvent
- TaskList.tsx: listens for tasks-updated event to auto-refresh
- tasks/route.ts: add PUT bulk sync endpoint for testing

Phase 3: tool_result three-layer deduplication
- claude-client.ts: emittedToolResultIds Set prevents PostToolUse and user
  message handler from emitting duplicate tool_result for same tool_use_id
- route.ts: seenToolResultIds Set in collectStreamResponse skips duplicate
  DB writes
- ChatView.tsx: onToolResult checks existing tool_use_id before state update

Phase 4: Agent SDK upgrade
- Upgrade @anthropic-ai/claude-agent-sdk from v0.2.33 to v0.2.62
  (29 versions, zero TypeScript errors, all integration points compatible)

Phase 5: Documentation
- docs/agent-tooling-todo-bridge.md: architecture overview, TodoWrite field
  mapping, status mapping, idempotency strategy, three-layer dedup, SSE event
  types, troubleshooting guide
- CLAUDE.md: add Architecture Docs section with reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:12:59 +08:00
7418andClaude Opus 4.6 2a7dbd45c2 feat: add context sharing and storage consistency (P0)
Session runtime mutex:
- Add session_runtime_locks table with TTL-based locking via SQLite
  single-writer guarantee (transaction: DELETE expired + INSERT, PK
  conflict = already locked → 409 SESSION_BUSY)
- POST /api/chat acquires lock before processing, releases in
  collectStreamResponse finally block and error catch
- Add runtime_status/runtime_updated_at/runtime_error columns to
  chat_sessions for tracking session state

Permission request persistence:
- Add permission_requests table with full lifecycle tracking
  (pending → allow/deny/timeout/aborted)
- Dual-write in permission-registry: in-memory Map for real-time
  resolution + DB for audit and recovery
- claude-client canUseTool now persists to DB before SSE event
- permission/route.ts validates against DB before resolving:
  404 (not found), 409 (already resolved), 410 (waiter gone)

Immutable sdk_cwd:
- Add sdk_cwd column, snapshot from working_directory at session
  creation time, never changes afterward
- route.ts uses sdk_cwd for SDK cwd to prevent resume breakage
  when user modifies working_directory
- Backfill existing sessions via migration

Startup recovery (migrateDb):
- Reset running/waiting_permission sessions to idle with error msg
- Clear all runtime locks (stale from previous process)
- Abort pending permission requests (no in-memory waiter exists)

All DB writes are try/catch guarded — failure degrades gracefully
without breaking the in-memory hot path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 12:20:01 +08:00
7418andClaude Opus 4.6 40a78ea0fc fix: image gen result persistence with fallback for temp message IDs, bump v0.17.3
Root cause: assistant messages use temporary IDs (temp-assistant-{timestamp})
in the frontend, which don't match real DB message IDs. The PUT update by
message_id always found 0 rows, so results were never persisted.

Fix — fallback search by session + prompt:
- db.ts: updateMessageContent now returns changes count; new
  updateMessageBySessionAndHint() finds the latest assistant message
  containing image-gen-request with matching prompt text and updates it
- /api/chat/messages PUT: tries message_id first, falls back to
  session_id + prompt_hint search; returns 404 if neither finds a match;
  response includes updated_message_id and fallback_used for debugging
- ImageGenConfirmation: always sends session_id and prompt_hint in PUT
  request, regardless of whether messageId is a real or temp ID

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:51:31 +08:00
7418andClaude Opus 4.6 20a5934cfc fix: persist image generation results to DB for reload survival, bump v0.17.2
Root cause: after image generation, the message content in DB stayed as
`image-gen-request` (the options form). On app restart, MessageItem re-parsed
this as a generation form instead of showing the completed result. The result
only lived in React state and localStorage.

Fix:
- db.ts: add updateMessageContent() to update a message's content by ID
- /api/chat/messages: add PUT handler for message content updates
- ImageGenConfirmation: accept messageId prop; on successful generation,
  replace message content from image-gen-request to image-gen-result format
  (with localPath references) via PUT API call
- MessageItem: pass message.id to ImageGenConfirmation

Now on restart, parseImageGenResult() matches the persisted image-gen-result
block and renders ImageGenCard directly with the saved image paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:32:00 +08:00
7418 00d3fef5a4 feat: refactor image generation to bypass model dependency, fix UI issues
Image Generation Refactor:
- MessageInput: add onImageGenerate callback, remove skill fetch dependency
- ChatView: implement handleImageGenerate that creates image-gen-request
  message for ImageGenConfirmation UI, then user selects params and generates
- New API route POST /api/chat/messages for DB-only message persistence
- image-gen-completed event now only persists notice to DB, no longer triggers model
- Reference images passed via module-level Map (imageGenRefStore) to avoid
  DB bloat from base64 data; user message DB content stores metadata only

Bug Fixes:
- Fix useCallback dependency missing imageGen causing stale closure reads
- Fix React Hooks order violation in MessageItem (early return before hooks)
- Fix localStorage cache collision across sessions (key now includes sessionId)
- Fix empty src attribute in FileAttachmentDisplay for DB-restored messages
- Add JSON parse fallback for malformed image-gen-request blocks

Gallery Reference Images:
- /api/media/generate saves reference images to disk, stores paths in metadata
- /api/media/gallery extracts referenceImages from metadata in mapRow
- GalleryDetail displays reference image thumbnails
- ImageGenCard supports referenceImages prop for chat display

UI Changes:
- ImageGenToggle: replace paint brush icon with text label (en: Image, zh: 生图)
- Model selector button: remove provider name display to save space
- ImageGenConfirmation: support referenceImagesData prop for base64 preview

i18n:
- Add imageGen.toggleLabel key (en: Image, zh: 生图)
2026-02-26 21:07:48 +08:00
7418andClaude Opus 4.6 448738d4f3 feat: provider/model system refactor — switch provider from chat input
Refactors the provider/model selection system so users can switch providers
directly from the chat input model selector, instead of having to go to
Settings to activate/deactivate providers.

types/index.ts:
- Add ProviderModelGroup interface for grouped provider+model data
- Extend SendMessageRequest, ClaudeStreamOptions, ChatSession with provider_id

db.ts:
- Migration: add provider_id column to chat_sessions
- Migration: convert is_active=1 provider to default_provider_id setting
- New helpers: updateSessionProviderId, getDefaultProviderId, setDefaultProviderId

providers/models/route.ts:
- Rewrite to return { groups, default_provider_id } instead of flat model list
- Each configured provider becomes a group; env vars become "Environment" group
- Deduplicate models within a group (e.g. GLM sonnet/opus both → "GLM-4.7" once)

chat/route.ts:
- Accept provider_id from request body
- Resolution chain: request → session → default_provider_id → env vars
- provider_id='env' is sentinel for environment variable fallback
- Pass resolved provider object to streamClaude()

claude-client.ts:
- Use options.provider ?? getActiveProvider() so explicit provider takes priority

ChatView.tsx:
- Track currentProviderId state, pass provider_id in fetch body
- New handleProviderModelChange callback

MessageInput.tsx:
- New props: providerId, onProviderModelChange
- Replace dynamicModels/activeProviderName with providerGroups/defaultProviderId
- Model selector UI: grouped by provider with section headers, ✓ for active item
- Persist both model and provider ID to localStorage

chat/page.tsx, chat/[id]/page.tsx:
- Track and pass provider_id through the component chain

ProviderManager.tsx:
- Remove handleActivate/handleDeactivate, activatingId, Apply/Disable buttons
- Remove Active badge and green border styling
- Simplify env variable banner
- Add provider-changed event dispatch to save/delete handlers

providers/route.ts:
- GET response now includes default_provider_id field

i18n (en.ts, zh.ts):
- Add provider.default, provider.setDefault, provider.environment translations

Backward compatible: old sessions without provider_id default to '' which
triggers the fallback chain. is_active column preserved but no longer written.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 00:22:18 +08:00
7418andClaude Opus 4.6 52f5bb5f86 feat: add token usage statistics page in Settings
New "Usage" tab in Settings showing token consumption analytics:

- **Data cards**: Total Tokens, Total Cost, Sessions, Cache Hit Rate
- **Bar chart**: Daily token usage with model/provider color grouping
  using recharts v3, with custom tooltip and continuous date x-axis
- **Provider tracking**: Each session now records the active provider
  name (e.g. "Kimi Coding Plan") so the chart distinguishes usage
  across different providers, not just model names

Backend changes (db.ts):
- Added `provider_name` column to chat_sessions with auto-migration
- Added `getTokenUsageStats(days)` with summary + daily aggregation
- SQL uses `json_valid()` guard to prevent malformed JSON crashes
- Fixed off-by-one: 7D now shows exactly 7 calendar days, not 8
- Provider-qualified grouping: shows provider name when set,
  plain model name when using direct Anthropic

API changes (route.ts):
- Persists effectiveModel and activeProvider name on every message
- Captures model from SDK init event for accurate tracking
- New GET /api/usage/stats endpoint

Frontend fixes:
- SettingsLayout: replaced useEffect+setState with useSyncExternalStore
  to fix react-hooks/set-state-in-effect lint error
- UsageStatsSection: AbortController prevents request race on rapid
  range switching; cache hit rate formula corrected to
  cache_read/(cache_read+input); date gaps filled with zeros for
  continuous x-axis
- Full i18n support (en + zh)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 00:07:41 +08:00
7418 9e7ccfefaa feat: v0.12.0 — fix streaming tool UI persistence, file tree UX improvements, unify icon library
Streaming tool UI fix (ChatView.tsx):
- Add toolUsesRef/toolResultsRef to reliably track tool data across closures
- Serialize tool_use/tool_result into JSON content-blocks array when building
  assistant message after stream ends, matching backend storage format
- MessageItem.parseToolBlocks() now renders tool UI from history messages
- Clean up refs in finally block and timeout auto-retry path

File tree UX (FileTree.tsx, file-tree.tsx):
- Remove folder path header from right panel, merge search + refresh into one row
- Remove file icon spacer (size-5) so file icons align flush with search box
- Change folder icon color from blue to muted-foreground

Chat list panel (ChatListPanel.tsx):
- Folder header: expanded uses FolderOpenIcon, collapsed uses Folder01Icon
- Add tooltip showing full working directory path on folder hover
- Double-click folder header opens in system file manager

Title bar folder name (chat/[id]/page.tsx):
- Add tooltip with full path on project name hover
- Click opens folder in Finder/Explorer via Electron IPC or API fallback

Open folder support:
- electron/main.ts: add shell:open-path IPC handler via shell.openPath
- electron/preload.ts: expose electronAPI.shell.openPath to renderer
- api/files/open/route.ts: fallback API route using open/explorer/xdg-open

Unify icon library — migrate app-level components from Lucide to Hugeicons:
- file-tree.tsx: folder, file, chevron, plus icons
- tool-actions-group.tsx: chevron expand icon
- MessageItem.tsx: copy, check, expand/collapse icons
- MessageInput.tsx: stop button (SquareIcon → StopIcon)
- FileCard.tsx: file icon
- ImageLightbox.tsx: left/right navigation arrows
- InstallWizard.tsx: status icons (check, x, minus, loader, circle, copy, download)
- Keep Lucide in ui/ and ai-elements/ base layers (shadcn standard)
2026-02-24 12:32:03 +08:00
7418 9c13f043d0 feat: v0.11.0 — SDK settingSources, nested session fix, dynamic models, UX improvements
Core fixes:
- Add settingSources to SDK options so CodePilot loads user/project settings
  like the CLI (fixes WebFetch timeouts, tool permission issues)
- Delete CLAUDECODE env var to prevent nested session detection when
  CodePilot runs alongside Claude Code CLI (fixes 'exited with code 1')
- Remove manual MCP config reading (SDK handles it via settingSources)
- Remove hardcoded timeout env vars (SDK loads from user settings)

UX improvements:
- Remember last selected model across sessions (localStorage)
- Same-project new chat skips directory picker
- File tree auto-refreshes on project switch and after each tool completes
- Left sidebar projects default collapsed (only most recent expanded)
- Right-side file tree defaults collapsed
- Fix file tree icon alignment (spacer size-4 → size-5)
- Dynamic model list from /api/providers/models endpoint
- Provider change dispatches event to refresh model selector
- Improved error messages with provider name and troubleshooting hints
- Fix context button file type matching for extension patterns
- Default tool timeout 120s → 300s
2026-02-24 03:08:17 +08:00
7418andClaude Opus 4.6 6aca8c3870 fix: remove process.cwd() fallback for working directory in chat route
The chat route and claude-client both fell back to process.cwd() when
working_directory was empty, causing Claude to operate in the server's
directory instead of the user's project. Since session creation now
requires a valid working_directory, these fallbacks are unnecessary
and misleading. Fall back to os.homedir() as a safer last resort.

Also update CLAUDE.md: require explicit user confirmation before
publishing releases (no auto push+tag).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 17:28:53 +08:00
7418andClaude Opus 4.6 916b8889b7 fix: require valid project directory for session creation
- Backend: POST /api/chat/sessions now validates working_directory
  exists (400 MISSING_DIRECTORY / INVALID_DIRECTORY)
- db.ts: remove process.cwd() fallback in createSession
- ChatListPanel: validate last-working-directory before use,
  open FolderPicker when invalid; add SSR guard for localStorage
- NewChatPage: block sending without a project directory selected

Bump version to 0.10.5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 15:37:51 +08:00
7418andClaude Opus 4.6 af2830db9b feat: add cursor-based message pagination (P0-3)
Backend returns paginated messages with hasMore flag. Frontend shows
"Load earlier messages" button with scroll anchor preservation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 01:24:27 +08:00
7418andClaude Opus 4.6 f3c49a9e49 fix: deduplicate attachment file writes (P0-1)
Upload route now passes persisted filePath to claude-client, which
reuses it instead of writing the file again.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 01:24:12 +08:00
7418andClaude Opus 4.6 4fa26032a4 feat: tool timeout auto-retry, file tree attachment, force stop button
- Add configurable tool timeout with auto-retry: when a tool exceeds the
  timeout threshold, abort and send a follow-up message asking Claude to
  try a different approach
- Add "+" button on file tree items to attach files directly to chat
  input via new /api/files/raw endpoint and custom event bridge
- Add force stop button in streaming status bar when tool runs >90s,
  with warning indicators at 60s
- Improve doc preview markdown rendering (padding, list indentation)
- Bump version to 0.10.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 00:33:21 +08:00
7418andClaude Opus 4.6 e4bbe8578a fix: persist file attachments so images/documents survive page reload
Previously uploaded files were only held in memory (optimistic message)
and lost when navigating away. Now:
- API route saves all uploaded files to .codepilot-uploads/ on disk
- File metadata (with filePath, not base64) is embedded in message content
- New /api/uploads endpoint serves persisted files
- FileAttachmentDisplay handles both base64 (in-memory) and filePath (DB)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 21:02:34 +08:00
7418andClaude Opus 4.6 164f730da1 feat: add file upload and preview support (images and documents)
- Support image (JPEG/PNG/GIF/WebP, max 5MB) and document uploads
- Images sent as multimodal content blocks to Claude SDK
- Documents saved to temp dir for Claude's Read tool
- Emerald capsule badges in input for attached files
- Drag-and-drop, paste, and file picker support via PromptInput
- Chat display: image thumbnails with lightbox, file cards
- Lightbox uses Radix Dialog with prev/next navigation
- File metadata embedded in message content as HTML comments
- Compatible with existing slash command badge system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 21:35:45 +08:00
歸藏 5d62e8ce5d Merge pull request #18 from liuxiaopai-ai/fix/persist-structured-messages
Code review approved: high quality bug fix, backward compatible design, comprehensive tests (10 cases).
2026-02-08 20:27:27 +08:00
7418andClaude Opus 4.6 4e81eaa60b feat: fix slash commands, add badge UI, multi-provider management
Slash commands:
- Fix skills not appearing (removed non-existent `enabled` filter)
- Add badge/capsule UI for prompt-type commands (/compact, /review, etc.)
- SDK-native commands sent directly; CLI-only commands expanded to prompts
- /clear now resets SDK session and clears database messages
- /cost shows actual aggregated token usage statistics
- Keyboard: Backspace/Escape to remove badge, arrow keys auto-scroll popover
- Add /memory command for editing CLAUDE.md

Multi-provider management:
- CRUD API for API providers with SQLite storage
- ProviderManager UI with quick presets (GLM, Kimi, MiniMax, etc.)
- Dynamic model labels based on active provider
- Environment variable injection with conflict resolution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 20:03:18 +08:00
liuxiaopai-ai 960d5906b8 fix: persist structured tool_use/tool_result blocks in messages
The collectStreamResponse() function only collected text events,
discarding tool_use and tool_result events. When a conversation was
resumed, all tool call context was lost — the user only saw the
text portions of Claude's responses.

Changes:
- Collect tool_use and tool_result SSE events as MessageContentBlock
  entries alongside text blocks
- Flush accumulated text into a text block before each tool_use to
  preserve the interleaved ordering (text → tool_use → tool_result →
  text → ...)
- Store messages with tool calls as JSON arrays of MessageContentBlock,
  using the existing type definitions in types/index.ts
- Text-only messages remain as plain strings for backward compatibility
  with existing message rendering (parseMessageContent handles both)
- Add 10 unit tests covering parsing, serialization, round-tripping,
  edge cases, and backward compatibility (all passing)

This enables future improvements like:
- Rendering tool call history in resumed conversations
- Showing which files were read/written in previous turns
- Exporting full conversation logs with tool context
2026-02-08 15:59:59 +08:00
7418andClaude Opus 4.6 454c7b0e6c fix: show detailed error messages for session creation failures
Return full error stack traces from the sessions API and display
the actual server error in the frontend, instead of a generic
"Failed to create session" message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 22:29:44 +08:00
7418andClaude Opus 4.6 ff1fb38735 feat: CodePilot - desktop GUI for Claude Code
Full-featured desktop application built with Next.js + Electron:
- Streaming chat with Claude Code via Agent SDK
- Session management with SQLite persistence
- Project file tree and file preview
- MCP server and custom skills management
- Permission controls with Code/Plan/Ask modes
- Dark/light theme, token tracking
- macOS DMG packaging with electron-builder

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:04:05 +08:00