Commit Graph
120 Commits
Author SHA1 Message Date
7418andClaude Opus 4.6 27bf396cd3 refactor: migrate all Lucide icons to Phosphor + remove lucide-react (PR4)
Unified icon library:
- Create src/components/ui/icon.tsx as centralized icon re-export + size constants
- Migrate all 20+ files from lucide-react to @phosphor-icons/react
- Remove lucide-react from package.json dependencies
- Upgrade ESLint lucide-react rule from warn → error

Icon mappings applied:
  CheckIcon → Check, ChevronDown → CaretDown, ChevronUp → CaretUp,
  ChevronRight → CaretRight, X → X, Search → MagnifyingGlass,
  Loader2 → SpinnerGap, CornerDownLeft → ArrowElbowDownLeft,
  Image → Image, Brain → Brain, Terminal → Terminal, Book → Book,
  Dot → DotOutline, Circle → Circle

Files migrated: ui/ (6), ai-elements/ (11), layout/ (3)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:51:38 +08:00
7418andClaude Opus 4.6 d9f2e6d614 refactor: replace inline layout patterns with SettingsCard/FieldRow/StatusBanner (PR3)
Settings pages:
- GeneralSection: 3 inline cards → SettingsCard, 3 field rows → FieldRow,
  warning banner → StatusBanner
- AppearanceSection: card → SettingsCard, mode/family rows → FieldRow,
  native <button> in ThemeModePills → Button, UIPreview buttons → Button

Bridge pages:
- BridgeSection: 5 inline cards → SettingsCard, master toggle/autostart → FieldRow,
  active hint → StatusBanner
- TelegramBridgeSection: 3 cards → SettingsCard, verify result → StatusBanner
- DiscordBridgeSection: 4 cards → SettingsCard, require mention/stream → FieldRow,
  verify result → StatusBanner
- FeishuBridgeSection: 4 cards → SettingsCard, require mention → FieldRow,
  verify result → StatusBanner
- QqBridgeSection: 4 cards → SettingsCard, image toggle → FieldRow,
  verify result → StatusBanner

Pure UI replacement — no data logic changes. All files retain identical
behavior and visual output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:50:20 +08:00
7418andClaude Opus 4.6 6ea323b92f feat: add reusable pattern components (PR2)
Create src/components/patterns/ with 6 pure-presentation components
that abstract repeated layout patterns from settings/bridge pages:

- SettingsCard: rounded card with optional title/description
- FieldRow: label+description left, control right, optional separator
- SectionPage: max-width page container with consistent spacing
- StatusBanner: success/warning/error/info inline status messages
- EmptyState: centered empty state with icon, title, description, action
- IconAction: ghost icon button with optional tooltip wrapper

All components are zero-side-effect, only import from React and @/lib/utils
(plus ui/button and ui/tooltip for IconAction).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:45:32 +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 4e603323c4 fix: user bubble color not rendering — use Tailwind v4 variable syntax
The bg-[var(--user-bubble)] arbitrary value syntax wasn't working with
Tailwind v4's Lightning CSS pipeline. Switch to bg-(--user-bubble) which
is Tailwind v4's native CSS variable reference syntax.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:49:51 +08:00
7418andClaude Opus 4.6 18a77dc915 fix: file tree attachment on Electron + model selection follows session
File tree "+" button (Bug 1):
- Replace fragile fetch/blob attachment bridge with @filepath text insertion
- Works identically on web and Electron (pure text, no /api/files/raw fetch)
- Remove unused mimeFromFilename helper

Model selection follows session (Bug 2):
- page.tsx: include model/provider_id in session creation request
- [id]/page.tsx: add sessionInfoLoaded gate — ChatView only mounts after
  session metadata is fetched, preventing stale localStorage fallback
- SplitColumn.tsx: same sessionInfoLoaded gate for split-pane mode
- ChatView.tsx: useEffect sync only overrides model/provider when props
  are non-empty; empty = genuinely no model saved (old/imported sessions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:46:34 +08:00
7418andClaude Opus 4.6 e5a48ca6af feat: migrate icon library from Hugeicons to Phosphor + UI polish
Icon library migration:
- Replace @hugeicons/react with @phosphor-icons/react across 60+ components
  (Phosphor supports 6 weight variants: thin/light/regular/bold/fill/duotone)
- Update apps/site (marketing) to use Phosphor icons
- Remove leftover svg.hugeicons CSS rules from globals.css

UI improvements:
- NavRail: selected tab icons use filled weight, unselected use regular
- User chat bubble: dark bg + white text in light mode, light bg + dark text
  in dark mode (new --user-bubble / --user-bubble-foreground CSS variables)
- Fix expand/collapse icons: use CaretRight (triangle) instead of ArrowRight
  in file-tree.tsx and tool-actions-group.tsx

Lint fixes:
- Fix eslint react-hooks/static-components in CodeBlock, ToolCallBlock,
  tool-actions-group — use createElement() instead of PascalCase variable
- Fix react-hooks/rules-of-hooks in MessageItem — move early return after hooks
- Fix react-hooks/set-state-in-effect in GalleryDetail — use ref pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:12:12 +08:00
7418andClaude Opus 4.6 5e18acb63d feat: provider auth style selector & edit UX improvements
- Anthropic Third-party API: add API Key / Auth Token selector in connect dialog
  with proper extra_env injection and auth style detection
- Edit mode: reuse PresetConnectDialog for matched providers instead of generic
  ProviderForm, showing only relevant fields consistent with connect experience
- Edit mode: show masked key (dots) instead of empty field; switching auth style
  clears key and requires re-entry; switching back restores masked value
- Edit mode: expose headers_json, env_overrides_json, notes in advanced section
  so all fields remain visible and editable
- Edit mode: role_models_json merge logic preserves unseen roles (reasoning, small)
  while allowing clearing of visible mapping fields
- Dialog: add max-h-[calc(100vh-4rem)] + overflow-y-auto to DialogContent base;
  remove overflow-hidden from preset dialog so scrolling actually works
- Connected provider badge: detect ANTHROPIC_AUTH_TOKEN in extra_env to show
  correct "Auth Token" vs "API Key" label
- i18n: add provider.authToken, provider.authStyle keys (en + zh)
- Bump version to 0.31.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 00:57:40 +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 789b453e92 fix: onboarding stability, invalid JSON from hooks, file tree race conditions
Issue 1 — Onboarding re-triggering after completion:
- ChatView.tsx: fence regex now tolerates CRLF, trailing whitespace, leading
  whitespace before closing fence; JSON.parse input trimmed
- ChatView.tsx: hookTriggeredSessionId only cleared when backend returns ok
- assistant-workspace.ts: saveState uses atomic write (tmp + rename)

Issue 2 — "CLI output was not valid JSON" during document reads:
- claude-client.ts: remove all SDK hooks (Notification + PostToolUse) to
  eliminate hook_callback control_request transport that corrupts stdout
- claude-client.ts: TodoWrite sync moved to message stream — tool_use records
  pending input, tool_result emits task_update only on success
- claude-client.ts: notifications derived from system/task_notification and
  result/is_error messages; Telegram forwarding preserved via notifyGeneric

Issue 3 — File tree not updating on project switch:
- FileTree.tsx: AbortController cancels in-flight requests on directory change;
  abort fires before empty-directory early return to prevent stale overwrites;
  error state distinguishes empty dir from load failure; loading always reset
- page.tsx: clear workingDirectory immediately on session switch
- SplitColumn.tsx: clear workingDirectory when active column has no directory

Tests: 9 new tests for fence parsing, atomic write, corrupted state fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:27:54 +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 94528edac9 fix: use local timezone for date-based features (check-in, usage stats)
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>
2026-03-09 11:55:29 +08:00
7418andClaude Opus 4.6 4da188bfe5 feat: theme family system with 12 color palettes + NavRail redesign
Theme system:
- Two-layer architecture: mode (light/dark/system via next-themes) + theme family (color palette via CSS variables)
- 12 theme families: Default, GitHub, Rosé Pine, Tokyo Night, Everforest, Nord, Kanagawa, Night Owl, Poimandres, Vesper, Horizon, SynthWave '84
- Per-family code block highlighting for Prism, HLJS, and Shiki (no more generic oneLight/oneDark fallback)
- Anti-FOUC: synchronous script in <head> reads localStorage and sets data-theme-family before paint
- Theme JSON files in themes/ directory, auto-discovered by server-side loader
- OKLCH color space for all values; CSS specificity html[data-theme-family] beats :root

UI changes:
- NavRail: moved Settings to bottom (replacing theme toggle), removed light/dark toggle button
- Settings: merged General controls into one card with dividers; Appearance section with live Shiki code preview
- Replaced hardcoded blue-500/blue-600 with primary/primary-foreground across 19 components
- Appearance settings: pill mode selector (light/dark/system), theme family dropdown, live code preview

New files:
- src/lib/theme/{types,loader,render-css,context,code-themes}.ts — core theme infrastructure
- src/components/layout/ThemeFamilyProvider.tsx — client context provider
- src/components/settings/AppearanceSection.tsx — appearance settings UI
- src/hooks/useAppTheme.ts — combined theme hook
- themes/*.json — 12 theme family definitions
- docs/handover/theme-system.md — handover documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:15:01 +08:00
7418andClaude Opus 4.6 1e0958f598 feat: QQ bridge channel + SDK crash resilience fixes
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>
2026-03-07 22:24:54 +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
7418andClaude Opus 4.6 c07a604271 fix: address code review findings — snapshot dedup, workspace hooks, permission params, Windows health check
ChatView.tsx (P2): snapshot consumption now calls detectAssistantCompletion()
so assistant workspace onboarding/check-in state updates even when the
stream completes while ChatView is unmounted.

ChatView.tsx (P2): replace local temp-message append with a DB re-fetch
on snapshot consumption to avoid duplicate messages (backend already
persists the reply in route.ts).

page.tsx (P3): add updatedInput and denyMessage parameters to
handlePermissionResponse so AskUserQuestion answers and ExitPlanMode
custom deny messages are forwarded to the permission API on the
first-message page.

electron/main.ts: fix Windows startup timeout by forcing IPv4 (family:4)
in health check HTTP request and improving error diagnostics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 14:24:19 +08:00
7418andClaude Opus 4.6 5eedb5bacc fix: resolve 7 bugs — idle output, permission scroll, file selector, drag area, input reset, markdown tables, panel collapse
- ChatView: consume unconsumed stream snapshots on mount so output
  appears after idle without needing a conversation switch
- PermissionPrompt: extract permission UI from StreamingMessage into a
  fixed-position component at the bottom of ChatView, preventing
  permission popups from scrolling off-screen during long responses
- StreamingMessage/MessageList: remove ~409 lines of permission UI code
  now handled by PermissionPrompt
- AssistantWorkspaceSection: fix macOS Electron file selector by using
  the correct IPC API (window.electronAPI.dialog.openFolder)
- AppShell: increase title bar drag region from h-5 to h-10 (#152)
- ChatView: add key={sessionId} on MessageInput to reset input state
  when switching conversations (#176)
- UpdateDialog: add remark-gfm plugin and table component mappings for
  proper GFM markdown rendering in release notes (#117)
- RightPanel: add border and top margin to collapsed panel toggle for
  visibility (#116)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 14:24:19 +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
jasonjaclyn2017andClaude Opus 4.6 a1bde18cd1 feat: add View Plan modal and feedback textbox to plan approval UI (#164)
* feat: add "View Plan" button with fullscreen modal to ExitPlanMode UI

When Claude writes a plan in plan mode, the plan content is written to a
file on disk but the ExitPlanMode UI only shows Approve/Reject buttons —
the user never sees the actual plan without manually finding the file.

Add a "View Plan" button that opens a fullscreen Dialog modal showing
the plan content rendered as markdown. The plan content is extracted from
the tool uses array (the Write tool's input for the plan file), so no
new API endpoints are needed.

Changes in StreamingMessage.tsx:
- extractPlanContent(): walks toolUses backwards to find the last Write
  to a plans/*.md file and returns its content
- ExitPlanModeUI: accepts toolUses prop, renders "View Plan" button
  (hidden if no plan content found), opens Radix Dialog modal with
  MessageResponse markdown renderer
- Modal is 80vh tall, max-w-4xl, scrollable, with Close button

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

* feat: add "Do this instead" feedback textbox to plan approval UI

When rejecting a plan, users can now type feedback and click "Do this
instead" (or press Enter) to send a custom deny message to the SDK.
This matches the Claude Code CLI behavior where users can provide
alternative instructions when rejecting a plan.

Previously, the deny message was hardcoded to "User denied permission"
— the API already supported custom messages but the client never sent
them.

Changes:
- StreamingMessage.tsx: add feedback textbox + "Do this instead" button
  to ExitPlanModeUI, wire onDenyWithMessage callback
- ChatView.tsx: pass denyMessage through handlePermissionResponse
- MessageList.tsx: update onPermissionResponse type signature
- stream-session-manager.ts: add denyMessage param to
  respondToPermission(), use it in the deny decision body

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

* fix: fetch plan content from disk so View Plan always shows latest version

Previously extractPlanContent read the Write tool's input.content, which
showed stale content when Claude later edited the plan via the Edit tool.

Now extractPlanFilePath finds the plan file path (matching both Write and
Edit tools, with Windows backslash support), and the View Plan button
fetches the file from disk on each click via /api/files/preview.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:00:02 +08:00
7418andClaude Opus 4.6 113e15e2f4 feat: Discord bridge adapter — 完整实现 Discord 作为第三个桥接渠道
新增文件:
- discord-adapter.ts: 基于 discord.js v14 的适配器,支持 DM/频道消息、
  按钮交互、流式预览(edit-based)、@提及过滤(兼容角色 mention)、
  图片附件下载、!→/ 命令归一化、消息去重、typing indicator
- markdown/discord.ts: Discord 2000 字符分段,代码块围栏自动平衡
- API routes: settings CRUD(Token 掩码)+ verify(Discord REST 验证)
- DiscordBridgeSection.tsx: 设置 UI(凭据、授权、服务器策略、配置指南含 ID 获取方法)
- discord-bridge.test.ts: 22 项单元测试(chunking/auth/command/HTML 转换)

修改文件:
- bridge-manager.ts: Discord 分段投递 + channel-aware stream config
  (Discord 默认 1500ms/40chars/1900max vs Telegram 700ms/20/3900)
- BridgeLayout/BridgeSection: Discord 渠道开关和侧栏入口
- bridge/settings/route.ts: 新增 14 个 Discord 设置键
- next.config.ts: serverExternalPackages 添加 discord.js/zlib-sync
- i18n: 中英文完整翻译(30+ keys)
- docs/handover/bridge-system.md: Discord 架构文档

依赖:discord.js ^14.18.0, zlib-sync ^0.1.9

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:32:46 +08:00
7418andClaude Opus 4.6 243724a765 feat: 飞书(Feishu/Lark)Bridge 适配器 + 发布 v0.25.0
新增飞书 IM 通道,通过 @larksuiteoapi/node-sdk WSClient 接收消息、REST Client 发送回复。

适配器核心 (feishu-adapter.ts):
- WSClient 长连接接收 im.message.receive_v1 事件
- 消息去重 (message_id LRU 1000)、授权校验、群聊策略 (open/allowlist/disabled)
- @提及检测 (bot open_id/user_id/union_id 匹配)
- 图片/文件/音频/视频/富文本(post) 下载 → base64 FileAttachment
- Typing 指示器 (Openclaw 方案: emoji reaction)
- /perm 文本命令处理权限审批

渲染策略 (markdown/feishu.ts):
- 代码块/表格 → schema 2.0 interactive card (markdown 元素)
- 纯文本 → post (md tag), 支持粗体/斜体/行内代码/链接
- 权限卡片 → schema 2.0 card + /perm 文本命令 (无按钮, 见下方决策)
- 每层降级: card → post → text

关键决策 — 权限交互无按钮:
- 飞书 card.action.trigger 回调需 HTTP webhook, WSClient 不支持
- schema 2.0 不支持 action 标签 (错误码 200861)
- schema 1.0 按钮可渲染但点击报 200340 (无 webhook 端点)
- Openclaw 用 http.createServer + Lark.adaptDefault 解决, CodePilot 桌面端无公网端点
- 最终方案: 格式化卡片 + /perm 文本命令, 用户复制发送即可审批

其他变更:
- permission-broker.ts: permissionRequestId 去重 (30s TTL)
- telegram-bot.ts: bridge 模式互斥 (globalThis 标志)
- bridge-manager.ts: 飞书渲染分发 (deliverResponse feishu 分支)
- UI: FeishuBridgeSection 设置页 + 侧边栏入口 + 通道开关
- i18n: 中英文飞书配置文案
- 交接文档: bridge-system.md 更新飞书架构、数据流、6 项设计决策

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:03:13 +08:00
7418 6dfe23a9a9 fix: 修复百炼 API key 配置并发布 v0.23.1 2026-03-03 16:36:10 +08:00
7418andClaude Sonnet 4.6 0e38e4460e feat: macOS Developer ID 签名 + notarization + 原生热更新
签名 & 公证:
- build/entitlements.mac.plist, build/entitlements.mac.inherit.plist — Electron hardened runtime 所需权限(JIT、unsigned memory、disable library validation)
- electron-builder.yml — 启用 hardenedRuntime、entitlements、notarize: true
- scripts/after-sign.js — 环境变量 + codesign -d 双重检测,有 Developer ID 时跳过 ad-hoc;无证书时 fallback ad-hoc
- .github/workflows/build.yml — 映射签名 secrets(MAC_CERT_P12_BASE64 等),验证步骤断言 Developer ID Authority + TeamIdentifier,遍历双架构 .app

原生热更新:
- electron/updater.ts — 从 no-op 重写为完整 electron-updater 实现(autoDownload:false, IPC handlers, 启动 10s 延迟检查)
- electron/preload.ts — 恢复 updater IPC bridge
- src/components/layout/AppShell.tsx — 运行时检测 native updater,useEffect 监听 updater:status 事件,native 优先 + browser fallback

其他:
- README_CN.md — 更新自动更新功能描述
- package.json — 版本号升至 0.23.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 00:46:51 +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 7e45448860 fix: 滚动条不在底部时发送消息看不到 AI 输出
use-stick-to-bottom 库仅在 isAtBottom=true 时跟随新内容自动滚动。
用户滚动上去后发送消息,流式内容渲染在视口下方不可见。

新增 ScrollOnStream 组件,监听 isStreaming 从 false→true 的变化,
强制调用 scrollToBottom(),确保流式输出开始时自动跳到底部。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:49:35 +08:00
7418andClaude Opus 4.6 65fdeb6dac fix: bridge 创建的聊天不立即显示在 ChatList
Bridge 在服务端通过 createSession() 创建会话,无法触发浏览器端的
session-created 事件,导致 ChatListPanel 不知道有新聊天。

添加 5 秒轮询刷新,查本地 SQLite 开销可忽略,bridge 新建的会话
最多 5 秒即可出现在聊天列表。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 00:49:30 +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 f7735e0b9c fix: 修复关闭最后一个分屏时 setState-during-render 报错
- removeFromSplit 中 setSplitSessions updater 内直接调用 router.replace
  导致在渲染 AppShell 时触发 Router 组件状态更新
- 改为将导航目标存入 useRef,通过 useEffect 延迟执行 router.replace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:30:20 +08:00
7418andClaude Opus 4.6 7696cbf5c5 refactor: 右侧面板 Tasks/Files 合并为单视图 + 简化 TaskList
- RightPanel: 移除 Files/Tasks 双 Tab 切换,改为单视图布局
  - 顶部标题改为 TASKS,下方直接渲染 TaskList
  - 中间用 mx-4 非贯通分隔线 + border-border/40 降低视觉强度
  - 分隔线下方增加 FILES 小标题,再接 FileTree
- TaskList: 大幅简化
  - 移除筛选 Tab(全部/进行中/已完成)
  - 移除新增任务输入框
  - 移除 TaskCard 组件引用,改为 checkbox + 标题单行展示
  - 点击切换 completed/pending 状态
  - 勾选框使用 foreground 色(黑/白)替代蓝色
  - 空状态显示"暂无任务"
- bump version to 0.20.1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:27:30 +08:00
7418andClaude Opus 4.6 b174527719 fix: ChatList 布局优化 + 多语言适配
- ChatList 字体从 12px 放大到 13px,图标同步放大,改善点击体验
- ConnectionStatus 胶囊移到顶部 header 区域,与下方按钮左对齐
- "Threads" 改为会话列表上方的小标题,充当分割作用
- ConnectionStatus 文字(Connected/Disconnected/Checking)多语言适配
- "从 Claude CLI 导入" 改为 "从 Claude Code 导入"
- 新增 i18n key: chatList.threads, connection.connected/disconnected/checking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:13:30 +08:00
7418andClaude Opus 4.6 68bb6b60bb feat: 新增服务商 + 文件上传优化 + AskQuestion 改进
新增功能:
- 新增字节火山方舟 (Volcengine Ark) 快速配置预设
  - 支持用户输入模型名称,映射到聊天输入框模型选择器
  - 模型名称存入 extra_env 的 ANTHROPIC_MODEL
- 新增阿里云百炼 (Aliyun Bailian) 快速配置预设
  - 内置 7 个模型供用户直接选择(Qwen 3.5 Plus、Kimi K2.5、GLM-5 等)
  - 无需额外配置模型名称
- 两个服务商均使用 @lobehub/icons 品牌图标 (Volcengine, Bailian)
- ProviderManager 图标识别规则新增 volces.com/dashscope 等 URL 匹配
- PresetConnectDialog 新增 model_names 字段类型,渲染模型名称输入框
- models route 动态读取 extra_env 中的 ANTHROPIC_MODEL 展示到模型选择器

修复 & 优化:
- 移除文件上传的类型限制 (ACCEPTED_FILE_TYPES) 和大小限制 (MAX_FILE_SIZE)
  现在支持所有文件类型和任意大小,由 API 端处理限制
- AskQuestion 按钮样式对齐权限确认按钮 (h-8 px-3 text-sm font-medium)
- AskQuestion 组件渲染位置从工具调用区域移到流式文本下方
  解决长文本输出后用户找不到选择组件的问题
- Anthropic Third-party API 预设的 provider_type 从 "custom" 改为 "anthropic"
  修复第三方 Anthropic 兼容 API 无法正确生效的问题

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:44:07 +08:00
7418andClaude Opus 4.6 ad34fe387b chore: 禁用 native auto-updater,改为引导用户从 GitHub 下载
macOS 上 ad-hoc 签名无法通过 ShipIt 的 kSecCSStrictValidate 校验,
导致热更新始终失败。在获取 Apple 开发者证书之前,暂时禁用 native
auto-updater,改为 browser-mode 检查 GitHub Releases API:

- electron/updater.ts: 注释掉 electron-updater 逻辑,initAutoUpdater
  和 setUpdaterWindow 改为 no-op
- electron/preload.ts: 注释掉 updater IPC bridge,前端不再暴露
  window.electronAPI.updater
- AppShell.tsx: isNativeUpdater 硬编码为 false,移除 native IPC 订阅
  和 native check/download/quitAndInstall 逻辑;保留 browser-mode
  的 GitHub API 检查(mount 时 + 每 8 小时),发现新版本时弹窗引导
  用户跳转 GitHub Release 页面下载

设置页 UpdateCard 保持不变:「检查更新」按钮通过 /api/app/updates
查询 GitHub,有新版本时显示「查看发布」按钮跳转下载。

TODO: 获取 Apple 开发者证书后,取消注释恢复 native auto-updater。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 11:45:36 +08:00
7418andClaude Opus 4.6 4f4ba5f8eb feat: 聊天分屏功能 — 支持多会话并排查看
新增聊天分屏功能,允许同时查看和操作多个聊天会话:

新增文件:
- src/hooks/useSplit.ts: SplitContext 和 useSplit hook,管理分屏会话列表、
  活跃列、添加/移除/退出分屏等操作,状态通过 localStorage 持久化
- src/components/layout/SplitColumn.tsx: 单个分屏列组件,独立加载会话元数据
  和消息,渲染紧凑标题栏 + ChatView,活跃列蓝色描边并同步 PanelContext
- src/components/layout/SplitChatContainer.tsx: 分屏容器,并排渲染多个
  SplitColumn,列间以 1px 分隔线分隔

修改文件:
- AppShell.tsx: 新增分屏状态管理(splitSessions/activeColumnId),包裹
  SplitContext.Provider,分屏激活时渲染 SplitChatContainer 替代路由页面,
  URL 与活跃列同步,离开聊天路由自动退出分屏
- ChatListPanel.tsx: 聊天卡片 hover 交互重构 — 移除模式徽章(Code/Plan),
  左侧 indent 区域 hover 显示分屏图标(点击将该会话加入分屏),右侧时间区域
  hover 切换为删除图标;分屏激活时列表顶部显示灰色描边分屏组卡片,分屏中的
  会话从常规列表过滤掉;所有元素始终存在于 DOM 中通过 opacity 切换,避免
  hover 时布局跳动
- en.ts / zh.ts: 新增 split.splitScreen、split.closeSplit、
  split.splitGroup、chatList.splitScreen 翻译

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:13:19 +08:00
7418andClaude Opus 4.6 c4d03c46e6 feat: Skills.sh marketplace integration + navigation restructure
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>
2026-02-27 21:43:46 +08:00
7418andClaude Opus 4.6 b1e6b0bfac fix: 修复预览面板拖拽分隔线快速移动时宽度不跟手的问题
根因:ResizeHandle 使用 document.addEventListener('mousemove') 监听拖拽,
当鼠标快速移到 iframe(HTML 预览)上方时,iframe 捕获了鼠标事件,
document 上的 mousemove 回调不再触发,导致宽度停止更新。

修复:将 mouse events 替换为 pointer events + setPointerCapture。
- onMouseDown → onPointerDown + setPointerCapture
- 在元素上直接监听 onPointerMove/onPointerUp(不再挂 document 事件)
- pointer capture 确保所有后续事件锁定到拖拽手柄,即使鼠标移到 iframe 上方
- 添加 touch-none CSS 防止触屏浏览器默认行为干扰拖拽
- 代码更简洁:不再需要手动 addEventListener/removeEventListener

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:49:01 +08:00
7418andClaude Opus 4.6 d13a0396f9 fix: replace tarball fallback with Homebrew install guide for macOS without brew
- electron/main.ts: check-prerequisites now returns hasHomebrew + platform
  so the frontend can distinguish macOS-without-Homebrew from other scenarios
- electron/main.ts: remove untested tarball fallback (hardcoded Node version,
  custom ~/.codepilot/node/bin PATH), replace with clean Homebrew-only path
  that returns a clear error message when Homebrew is absent
- electron/main.ts: remove ~/.codepilot/node/bin from getExpandedShellPath()
  since the tarball install target no longer exists
- InstallWizard.tsx: when Node.js is missing AND Homebrew is absent on macOS,
  show a guided UI with the Homebrew install command, copy button, and
  step-by-step instructions instead of a broken "Install" button
- InstallWizard.tsx: footer shows "Recheck" button (re-runs checkPrereqs)
  instead of "Install" in the no-Homebrew case
- en.ts/zh.ts: add 8 new i18n keys for the Homebrew guidance UI

Root cause: the previous tarball fallback was untested, pinned to a specific
Node version (v22.12.0), and required maintaining a custom PATH entry. Guiding
users to install Homebrew first is more reliable and maintainable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:30:32 +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
7418 c14fdb07ae fix: resolve mac updater no-response by shipping zip and surfacing errors 2026-02-27 14:35:22 +08:00
7418andClaude Opus 4.6 54515e1cec feat: stream session manager + always show Claude Code built-in models, bump v0.17.4
Stream Session Manager — 切换会话不中断流:

- src/lib/stream-session-manager.ts(新建): 全局单例管理所有 SSE 流,
  使用 globalThis 模式(同 conversation-registry.ts)保证 HMR 不丢状态。
  管理 AbortController、idle timeout (330s)、tool timeout 自动重试、
  5 分钟 GC,所有 SSE 回调写入 snapshot 并 emit 事件给订阅者。
  导出 startStream / stopStream / subscribe / getSnapshot /
  respondToPermission / clearSnapshot 等 API。

- src/types/index.ts: 新增 ToolUseInfo、ToolResultInfo(从 ChatView 提取)、
  StreamPhase、SessionStreamSnapshot、StreamEvent、StreamEventListener 类型,
  作为管理器与 ChatView 之间的数据契约。

- src/components/chat/ChatView.tsx(重构): 删除 abortControllerRef 及
  unmount abort effect(此前切换会话的根因)、整个 fetch/SSE 逻辑约 280 行、
  accumulatedRef/toolUsesRef/toolResultsRef/beforeunload handler。
  改为 subscribe() + getSnapshot() 订阅模式,unmount 仅 unsubscribe
  不 abort,mount 时通过 getSnapshot 恢复进行中的流状态。
  sendMessage / stopStreaming / handlePermissionResponse 全部委托管理器。

- src/hooks/usePanel.ts: PanelContextValue 新增
  activeStreamingSessions: Set<string> 和
  pendingApprovalSessionIds: Set<string>,支持多会话并发流指示。

- src/components/layout/AppShell.tsx: 监听 stream-session-event 窗口事件
  更新 activeStreamingSessions / pendingApprovalSessionIds Set 状态;
  将 beforeunload handler 从 ChatView 移至此处(全局感知所有流)。

- src/components/layout/ChatListPanel.tsx: 侧栏流指示器改用
  activeStreamingSessions.has() / pendingApprovalSessionIds.has(),
  支持同时显示多个会话的流/审批状态。

模型选择器始终显示 Claude Code 内置模型:

- src/app/api/providers/models/route.ts: 移除 hasEnvKey 环境变量检测条件,
  始终显示 "Claude Code" provider 组(Sonnet 4.6 / Opus 4.6 / Haiku 4.5)。
  根因:Claude Code CLI 通过 `claude login` 存储凭证在 ~/.claude/ 下,
  SDK 子进程可以读取,但 Next.js 进程的 process.env 中没有对应变量,
  导致之前的 hasEnvKey 检测失败时模型选择器不显示内置模型。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:58:30 +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 09908cfcb7 fix: resolve macOS build race condition and update download button unresponsive
CI 修复 (electron-builder.yml):
- 移除 mac target 的 arch: [arm64, x64],改由 CI matrix 的 --arch flag 控制
- 根因:两个 macOS job 各自构建两个架构的 DMG,产生两个 draft release,
  导致 release job 只发布了没有 DMG 的那个,macOS 产物丢失

下载按钮无响应修复:
- electron/updater.ts: downloadUpdate IPC handler 加 try-catch,
  失败时通过 sendStatus 发送 error 事件给前端,避免静默失败
- AppShell.tsx: downloadUpdate 回调点击后立即设置 downloadProgress=0
  提供即时 UI 反馈;加 await + catch 处理下载失败并重置状态
- AppShell.tsx: error 事件处理中重置 downloadProgress 为 null,
  使下载按钮在失败后重新出现
- UpdateDialog.tsx / GeneralSection.tsx: isDownloading 判断移除 > 0 条件,
  downloadProgress === 0 时也显示下载中状态,消除点击后无反馈的间隙

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 12:11:20 +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
7418andClaude Opus 4.6 fd03d179e3 fix: improve auto-update UX and proxy support, bump v0.17.1
Auto-update flow rework:
- updater.ts: disable autoDownload, add manual `updater:download` IPC handler
  so user explicitly triggers download via "Download & Install" button
- updater.ts: resolve system proxy via session.defaultSession.resolveProxy()
  and inject into HTTPS_PROXY env var, fixing zero download speed behind VPN/proxy
- preload.ts + electron.d.ts: expose `downloadUpdate` method to renderer

UI improvements:
- UpdateDialog: add "Download & Install" button when update available but not
  yet downloading; show progress bar during download; "Restart to Update" when ready
- GeneralSection UpdateCard: show "Download & Install" / "Restart to Update" button
  next to "Check for Updates"; show download progress bar inline; add "View Details"
  link to reopen update dialog
- AppShell: dismissUpdate no longer permanently hides update for that version
  (removed localStorage persistence), just closes the dialog
- useUpdate: add downloadUpdate to context

i18n:
- Added `update.installUpdate` key (en: "Download & Install", zh: "下载并安装")

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:19:48 +08:00
7418andClaude Opus 4.6 a5e2cedc12 feat: gallery masonry layout, favorites system, left-right detail dialog, bump v0.17.0
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>
2026-02-27 11:00:58 +08:00