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>
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>
Root cause: `new Date().toISOString().slice(0, 10)` returns UTC date,
causing date-based features to be off by up to ±1 day for non-UTC users.
Check-in / assistant workspace:
- Add `getLocalDateString()` utility, replace all UTC date comparisons
- Schema v2→v3 migration: normalize lastCheckInDate only when it matches
UTC today (ambiguous window); leave past dates untouched so check-in
triggers correctly after upgrade
- Runtime compat in `needsDailyCheckIn`: accept UTC today as fallback
for edge cases the migration cannot catch
- `needsDailyCheckIn(state, now?)` accepts optional time for testability
Usage stats:
- Replace single-offset SQL bucketing (`DATE(datetime(..., '+N minutes'))`)
with JS-side per-row aggregation using `getLocalDateString(new Date(utc))`
- Date methods account for historical DST offset at each message's timestamp
- `getTokenUsageStats(days, now?)` accepts optional time to pin window start
- Window boundary via `localDayStartAsUTC()` is already DST-aware
Tests (new file: timezone-boundaries.test.ts, 22 tests):
- getLocalDateString at UTC+9/UTC-5/UTC boundaries with fixed timestamps
- needsDailyCheckIn: compat suppression, clearly-past trigger, local match
- v2→v3 migration: targeted normalization, past-date preservation, null safe
- localDayStartAsUTC: UTC+8/UTC-5 midnight boundaries
- Usage stats bucketing: UTC+9 cross-day, summary window, DST spring-forward
- All usage stats tests use pinned `now` — no time-bomb from fixed test data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
QQ Bridge Adapter (new):
- qq-api.ts: AppAccessToken, WebSocket Gateway, heartbeat, passive reply,
msg_seq auto-increment — pure QQ protocol layer
- qq-adapter.ts: BaseChannelAdapter impl with WebSocket lifecycle,
C2C_MESSAGE_CREATE handling, image download (base64), dedup, reconnect
- Settings UI (QqBridgeSection.tsx) + API routes (/api/settings/qq)
- i18n keys for en/zh, PLATFORM_LIMITS qq: 2000
replyToMessageId threading:
- bridge-manager.ts: thread msg.messageId through all deliver/command/
permission paths — required for QQ passive reply (msg_id mandatory)
- permission-broker.ts: accept and forward replyToMessageId
SDK crash resilience (bug fix):
- bridge-manager.ts: check hasError BEFORE saving sdkSessionId — the SDK
may emit a session_id before crashing, and saving that broken ID caused
ALL subsequent messages to fail by repeatedly resuming a corrupted session
- claude-client.ts: always clear sdk_session_id on crash, even for fresh
sessions (SDK can persist a new ID via status event before dying)
- conversation-engine.ts: attach filePath to file objects after disk write
so streamClaude() reuses on-disk copies (prevents duplicate writes,
matches desktop route behavior)
QQ permission handling (P1 fix):
- permission-broker.ts: for channels without inline button support (QQ),
render text-based /perm allow|deny|allow_session commands instead of
invisible inlineButtons that would deadlock the stream
QQ passive reply budget (P2 fix):
- bridge-manager.ts: QQ-specific delivery limits chunks to 3 max,
truncates with notice to avoid exhausting passive reply quota
- delivery-layer.ts: export chunkText for reuse
Error message improvement:
- claude-client.ts: when exit code 1 with image files, hint "Provider may
not support image/vision input" instead of generic auth/network message
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
Skills.sh Marketplace:
- Add marketplace browser with search, detail panel, and install/uninstall
- Search API proxying skills.sh with lock file integration for installed state
- Install/remove APIs using `npx skills` CLI with SSE streaming progress
- SKILL.md content fetching via GitHub Git Trees API with in-memory caching
- MarketplaceSkillCard, MarketplaceSkillDetail, MarketplaceBrowser components
- InstallProgressDialog with real-time SSE log output
- SkillsManager gains "My Skills" / "Marketplace" segmented control
- Type definitions: MarketplaceSkill, SkillLockFile, SkillLockEntry
Navigation restructure:
- Split Extensions page into standalone /skills and /mcp top-level pages
- NavRail: replace single Extensions icon with separate Skills (ZapIcon) and MCP (Plug01Icon) entries
- /extensions page now redirects to /skills or /mcp based on ?tab param
- /plugins redirects updated to point to new routes
- Remove redundant source badges from SkillListItem (section headers suffice)
Other:
- Add WeChat user group QR code to README.md and README_CN.md
- i18n: add 19 marketplace-related keys in en.ts and zh.ts
- Bump version to 0.18.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
Gallery UI overhaul:
- GalleryGrid: replace equal-size grid with CSS columns masonry layout (6 columns),
pure image cards without text/date info, hover gray ring highlight,
favorited items show red heart badge
- GalleryDetail: left-right layout dialog (70% image / 30% info),
nearly full-screen size with minimal margins, black background for image area,
absolute-positioned image container for correct sizing across all aspect ratios,
favorite toggle button replaces tag manager, removed outer border
- Gallery page: added "favorites only" filter button, replaced load-more button
with IntersectionObserver infinite scroll, removed tag system references
Favorites system:
- db.ts: added `favorited` column to media_generations with migration
- New API: PUT /api/media/[id]/favorite for toggle
- gallery/route.ts: support favoritesOnly=1 filter, return favorited field
- i18n: added favorites-related translations (en/zh)
Other:
- Default image model changed from Nano Banana Pro to Nano Banana 2
- providers/models/route.ts: skip media provider types in chat model selector
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: 生图)
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>
- New API route: src/app/api/skills/search/route.ts
- POST endpoint receives query + skill list + model alias
- resolveApiConfig() resolves credentials: active provider → settings → env
- Supports Anthropic direct, OpenRouter (Bearer auth), custom base URLs
- Bedrock/Vertex silently return empty results (unsupported)
- 5s hard timeout, all errors return { suggestions: [] }
- LLM returns JSON array of matching skill names, validated against input
- Modified: src/components/chat/MessageInput.tsx
- Extend substring matching to search both label and description fields
- Add debounced AI search (500ms) when skill mode has < 2 non-built-in
substring matches and filter length >= 2 characters
- AbortController cancellation on new input or popover close
- "AI Suggested" section with BrainIcon and spinner in popover UI
- Unified keyboard navigation across filteredItems + aiSuggestions
using allDisplayedItems for ArrowUp/Down, Enter/Tab, and visibility
- Modified: src/i18n/en.ts, src/i18n/zh.ts
- Added 'messageInput.aiSuggested' translation key (EN/ZH)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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)
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
FileTree component was not passing baseDir to the file API, causing
it to fall back to restricting access to the home directory. On
Windows, projects on a different drive (e.g., D:\) were blocked
with 403.
Changes:
- FileTree now passes workingDirectory as baseDir parameter
- File API routes no longer require baseDir to be under homeDir
(allows cross-drive access on Windows)
- Still prevents using filesystem root as baseDir for safety
- Add isRootPath() utility for root path detection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
- Remove homedir-only restriction from /api/files/raw (desktop app
doesn't need this sandbox, projects can be anywhere)
- Add console.warn on fetch failure instead of silent swallow
- Fix filename extraction for Windows backslash paths
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace all readFileSync/existsSync/readdirSync with fs.promises
equivalents to unblock the Node event loop under concurrency.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Claude CLI stores user-scoped MCP servers in ~/.claude.json, while
CodePilot was only reading from ~/.claude/settings.json. Now both
the MCP list API and streamClaude merge configs from both sources.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
Add automatic update detection (checks GitHub Releases every 8h with
dialog prompt and blue dot on Settings icon) and a skip-permissions
toggle in Settings that uses the SDK's native bypassPermissions mode
to auto-approve all tool actions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>