19 Commits

Author SHA1 Message Date
程序员阿江(Relakkes)
67660ab4e5 fix(desktop): surface non-streaming fallback in chat status (#766)
api_retry heartbeats already reach the desktop status bar, but the
streaming-to-non-streaming fallback had no signal at all: the CLI only
flipped an internal flag, and the one-shot fallback response can take
minutes with zero incremental output, so the UI showed a bare spinner
the whole time.

- CLI: yield a {type:'system', subtype:'streaming_fallback', cause}
  message at both fallback sites (stream error/watchdog and 404 stream
  creation), mirroring the existing api_error -> api_retry path through
  query.ts passthrough, QueryEngine SDK output, and the SDK schema.
- Server: translate it to a streaming_fallback ServerMessage;
  unrecognized causes normalize to 'unknown' instead of dropping the
  event.
- Desktop: track it as active-turn state (cleared at the same 12 sites
  as apiRetry; a fallback notice supersedes the stale retry banner) and
  render a neutral pill with the turn timer - expected state, not an
  error, so no amber styling and nothing in the diagnostics panel.
- Retry banner now shows "retrying now" once the countdown elapses
  instead of sticking at "waiting 0s".

With 62241a31 disabling the non-streaming fallback for desktop CLI
sessions, this notice mainly covers the 404 gateway path (which skips
the disable check), callers that re-enable fallback via env, and
non-desktop SDK consumers.

Constraint: Retries and fallbacks are expected states per the
diagnostics severity standard - lightweight active-turn UI only, no
error-panel entries, no transcript persistence.
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: bun run check:server
Tested: cd desktop && bun run test -- --run
Tested: cd desktop && bun run lint
Not-tested: live provider outage reproduction; verified via unit
coverage of the translation, store lifecycle, and indicator rendering.
2026-06-12 18:32:28 +08:00
程序员阿江(Relakkes)
5cf8c1bb67 fix(desktop): queue active-turn prompts (#755)
Add pending user-message queue controls while a desktop turn is running, replay queued prompts into the transcript when the CLI accepts them, and flush remaining prompts after turn completion.

Tested: cd desktop && bun run test -- src/components/chat/ChatInput.test.tsx
Tested: cd desktop && bun run test -- src/stores/chatStore.test.ts
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: bun run check:desktop
Not-tested: bun run check:server remains blocked by unrelated E2E CORS preflight expectation, expected 403 but got 204.
Confidence: medium
Scope-risk: moderate
2026-06-12 16:44:52 +08:00
程序员阿江(Relakkes)
a94e5b641a fix: unify token usage display across desktop and CLI (#757)
Token counts were rendered with five inconsistent formats across the
chat UI (header "181,117 t", in-progress bare "↓ 2514", background
agents "12,345 tokens", compact summary "1.5k", trace "1.2k"), and the
in-progress count was actually stale: the server never set the status
event's tokens field, so the indicator showed the previous turn's value
(hence "first message shows nothing", "sometimes appears, never moves").

- Add shared desktop lib/formatTokenCount; route header, streaming
  indicator, background agents, compact summary, and trace through it.
- Header shows compact "124.3k tokens" with the exact count on hover.
- Add common.tokens i18n key (en/zh/zh-TW/jp/kr).
- Estimate the in-progress count from streamed chars (÷4, mirroring the
  CLI spinner) via a new streamingResponseChars per-session field, reset
  on each send; drop the dead status.tokens/elapsed fields.
- CLI spinner rows use formatTokens to drop the "1.0k" artifact.
2026-06-12 15:25:56 +08:00
程序员阿江(Relakkes)
d7b8fb032c fix(desktop): preview exit plan approvals (#793)
Show ExitPlanMode approvals as a rendered plan preview in desktop chat,
forward plan feedback and requested prompt permissions through the desktop
WebSocket permission response, and keep permission-mode restoration owned by
the CLI runtime.

Tested: bun run verify
Confidence: high
Scope-risk: moderate
2026-06-12 11:16:31 +08:00
程序员阿江(Relakkes)
892f5e193a fix(desktop): restore permission mode after exiting plan mode (#623)
桌面端用 bypassPermissions 进入计划模式、退出后,权限选择器停留在"计划模式",
导致下一个工具调用又弹权限申请。两层根因都修:

- 回传链路断裂:服务端 WS handler 把 CLI 广播的权限模式变化(status:null +
  permissionMode)当成 thinking 丢弃,桌面端协议也没有承载权限模式的入站消息,
  CLI 恢复后的权限永远同步不到 UI。新增 permission_mode_changed 回传通道,桌面端
  据此校正选择器(只更新本地、不回发避免回环;未知模式忽略)。

- 重启抹掉 prePlanMode:bypass→plan 因策略"切换涉及 bypass 就重启"而重启 CLI,
  新进程直接以 plan 启动、prePlanMode 为空,ExitPlanMode 只能恢复成 default 而非
  bypassPermissions。收窄 needsRestart 为只在"进入 bypass"时重启;从 bypass 切出
  保持进程不变、走进程内 transition,CLI 才会栈存 prePlanMode 并在退出 plan 时
  正确恢复 bypass,与 TUI 行为一致。

测试:服务端 ws-memory-events 新增权限回传 + 重启策略用例;桌面端 chatStore 新增
回传校正 + 防回环 + 未知模式忽略用例。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:29:01 +08:00
程序员阿江(Relakkes)
998e85efab fix: localize synthetic business errors
Synthetic API errors were carrying only English display text, so desktop chat could not distinguish our compatibility guidance from raw upstream failures and recovery logic depended on those English strings.

Add stable business error codes for media, PDF, request-size, prompt-length, and auto-mode errors. Desktop chat now resolves those codes through locale keys and suppresses the stale English fallback for known business errors, while normalizeMessagesForAPI still supports legacy text matching for old transcripts.

Constraint: Raw upstream provider details must remain available for diagnostics but should not drive localized business UX.

Rejected: Localize src/services/api/errors.ts strings directly | that would make persisted transcript text and media recovery depend on the active UI language.

Confidence: high

Scope-risk: moderate

Directive: Add new user-facing synthetic API errors with businessErrorCode values before localizing their display text.

Tested: bun test src/services/api/errors.test.ts tests/mediaRecoveryAndEstimation.test.ts src/server/__tests__/ws-memory-events.test.ts

Tested: cd desktop && bun run test -- run src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx

Tested: bun run check:server

Tested: bun run check:desktop

Not-tested: Manual desktop screenshot smoke for the rendered Chinese error card
2026-06-01 00:36:29 +08:00
程序员阿江(Relakkes)
1f9af7e578 feat: scope desktop reasoning effort to sessions
Desktop reasoning effort now follows the selected session runtime instead of the General settings surface. Session metadata records provider, model, and effort so reconnects and prewarm restarts preserve the per-session runtime, while the default effort is max for new sessions.

Constraint: Existing transcript and localStorage runtime data must remain readable without a required migration.
Rejected: Keep effort as a General setting | it made one session change leak into every other session.
Confidence: high
Scope-risk: moderate
Directive: Do not reintroduce global desktop effort controls without proving multi-session isolation.
Tested: bun run check:server
Tested: bun run check:desktop
Tested: bun run check:native
Tested: bun run check:policy
Tested: bun run check:coverage
Tested: cd desktop && bun run test -- src/components/controls/ModelSelector.test.tsx src/stores/chatStore.test.ts
Tested: bun test src/server/__tests__/settings.test.ts --test-name-pattern "effort"
Tested: bun test src/server/__tests__/conversations.test.ts --test-name-pattern "runtime"
Not-tested: Live provider smoke for domestic providers without local credentials.
2026-05-31 17:42:30 +08:00
程序员阿江(Relakkes)
e437199b81 fix: Surface API retry progress in desktop chat
CLI retry events already include retry attempts, delay, and upstream status, but
the desktop WebSocket translation dropped api_retry system messages. The desktop
chat now keeps the current retry state in session state and renders a compact
active-turn indicator with attempt count, HTTP status, and countdown.

Constraint: Desktop users need visible feedback during provider retry waits after auth or network failures.
Rejected: Add retry rows to the transcript | retry heartbeats would clutter chat history instead of describing the active turn.
Confidence: high
Scope-risk: moderate
Directive: Keep api_retry as active-turn state; do not persist retry heartbeats into transcript history without a product decision.
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: cd desktop && bun run test src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:server
Tested: Browser smoke with temp CLAUDE_CONFIG_DIR and mock OpenAI-compatible 503 endpoint, screenshot /tmp/cc-haha-retry-ui.png
Not-tested: Live ChatGPT logout retry path against OpenAI production auth.
2026-05-20 18:48:36 +08:00
程序员阿江(Relakkes)
a4aba9ec5a Show context compaction as transcript state
Desktop users need to see automatic context compaction as a transient transcript state instead of a stray stdout bubble or large card. The UI now collapses prior visible content into a compact timeline divider, shows the compacting phase, then updates the same divider with the completed summary affordance.

Constraint: CLI compaction emits both status and synthetic transcript artifacts that must be normalized before rendering

Rejected: Keep the existing small pill marker | it did not show the in-progress state and looked like chat content

Confidence: high

Scope-risk: moderate

Directive: Do not render local compact stdout as a user-visible chat message

Tested: cd desktop && bun run test -- src/components/chat/MessageList.test.tsx src/stores/chatStore.test.ts

Tested: bun test src/server/__tests__/ws-memory-events.test.ts

Tested: bun run check:desktop

Not-tested: full root bun run verify after the final UI iteration
2026-05-19 23:13:01 +08:00
程序员阿江(Relakkes)
f41e786748 Align desktop file mentions with CLI search
Desktop @ file search was recursively walking the filesystem with a local skip list, which let Python and Node generated directories leak into results and diverged from CLI behavior. Route candidate discovery through the same git-first model: tracked files, untracked files with exclude-standard, and ripgrep fallback for non-git folders, then derive selectable directories from those candidates.

Constraint: Desktop picker must select both files and directories without surfacing ignored project artifacts.
Constraint: No new dependencies; reuse the existing git, ripgrep, settings, and ignore utilities.
Rejected: Maintain a hardcoded directory denylist | it would drift from CLI and miss project-specific ignore rules.
Rejected: Full recursive readdir scanning | it ignores git index semantics and makes large dependency trees visible.
Confidence: high
Scope-risk: moderate
Directive: Keep desktop @ file candidate discovery aligned with src/hooks/fileSuggestions.ts before changing ranking or ignore behavior.
Tested: bun test src/server/__tests__/filesystem.test.ts
Tested: cd desktop && bun run test -- FileSearchMenu.test.tsx ChatInput.test.tsx
Tested: cd desktop && bun run lint && bun run test -- --run && bun run build
Tested: bun run check:server
Tested: bun run check:coverage
Tested: bun run verify
Not-tested: Native Windows/Linux manual UI smoke; path handling relies on cross-platform git/ripgrep wrappers and normalized relative paths.
2026-05-13 10:38:13 +08:00
程序员阿江(Relakkes)
761031c036 fix: Make desktop MCP tools ready before first turn
Desktop sessions could show MCP configuration in the UI while the CLI SDK child process still started cold on the first user message. Start eligible desktop chat sessions as soon as their websocket is connected, keep init metadata cached but muted, and wait briefly for MCP startup in SDK print mode so turn one sees the tools.

Constraint: Desktop wraps the existing CLI SDK bridge rather than owning MCP startup directly.
Rejected: Prewarm every restored tab | synthetic tabs such as settings would start unnecessary CLI subprocesses.
Confidence: high
Scope-risk: moderate
Directive: Do not remove the synthetic-tab guard without validating restored settings and scheduled-task tabs in the browser.
Tested: bun test src/server/__tests__/conversations.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/mcp.test.ts
Tested: cd desktop && bun run test -- chatStore.test.ts mcpSettings.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: agent-browser E2E new session prewarm plus first message reuse with mock CLI
Not-tested: Full desktop Vitest suite still has pre-existing English-vs-Chinese copy assertion failures.
2026-04-24 17:11:03 +08:00
程序员阿江(Relakkes)
773a8a703f Keep desktop chats on the selected provider/model
The desktop model picker now stores a session-scoped provider/model selection instead of relying on the global active provider. That selection is replayed on connect, passed into the CLI startup path, and preserved across turns until the user changes it again.

To make that true end-to-end, the server now restarts the session process when runtime selection changes, injects provider-scoped env for third-party providers, and routes proxy traffic by provider id. The selector UI was also tightened so provider grouping stays visible while the actual model choice remains readable.

Constraint: Different providers can expose the same model id, so chat runtime selection cannot be derived from model id alone
Constraint: A desktop session reuses one CLI subprocess across turns, so runtime changes must restart that process to take effect
Rejected: Keep using Settings active provider as the chat selector | conflates defaults with live session state and breaks overlapping models
Rejected: UI-only runtime switching without server restart | later turns would continue using the old CLI subprocess configuration
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep provider defaults and session runtime overrides separate, and preserve provider-scoped proxy routing when extending model selection surfaces
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test src/stores/chatStore.test.ts
Tested: cd desktop && bun run test src/__tests__/generalSettings.test.tsx
Tested: bun test src/server/__tests__/conversation-service.test.ts
Tested: bun test src/server/__tests__/conversations.test.ts
Tested: bun -e "await import('./src/server/services/titleService.ts'); await import('./src/server/ws/handler.ts')"
Not-tested: Real third-party provider round-trip from the desktop UI against a live upstream account
2026-04-23 13:41:27 +08:00
程序员阿江(Relakkes)
0d3933c2c9 Keep desktop AskUserQuestion flows from stalling in plan mode
Desktop chat was rendering AskUserQuestion twice: once as an inline question card and again as a generic permission request. That split also dropped the structured answers on the floor because the websocket permission_response shape only carried allow/deny state.

This change keeps AskUserQuestion on the permission pipeline end-to-end. The desktop websocket contract now carries toolUseId and updatedInput, AskUserQuestion submits answers through permission_response, and the generic permission card is suppressed for that tool so the user sees a single question flow.

Constraint: AskUserQuestion answers must round-trip through updatedInput.answers for the CLI tool contract to complete
Rejected: Leave AskUserQuestion as a plain chat reply in desktop | the tool never receives structured answers and the pending approval UI remains stuck
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep AskUserQuestion bound to the permission-response path unless the desktop protocol grows a separate structured elicitation channel
Tested: desktop lint; vitest src/components/chat/AskUserQuestion.test.tsx src/stores/chatStore.test.ts; bun test src/server/__tests__/conversations.test.ts
Not-tested: Manual desktop click-through against a live plan-mode session after bundling
2026-04-20 23:07:15 +08:00
程序员阿江(Relakkes)
5cd6b5d07b Prevent desktop Computer Use from stalling on missing approvals and unstable text input
Desktop sessions were missing a visible request_access approval path and could
mis-detect their own app window as an unapproved frontmost target, which caused
Computer Use clicks to fail even after opening the intended app. On macOS, text
entry was also split across inconsistent clipboard and keystroke paths, making
Electron inputs unreliable for Chinese and short strings.

This change adds a desktop approval bridge over the existing session websocket,
renders a dedicated desktop approval modal, threads the real desktop bundle id
into the Computer Use executor, and switches macOS clipboard typing onto the
native pasteboard plus system paste shortcut path. It also makes tool error
results expandable in the desktop chat UI so frontmost-gate failures are fully
visible during debugging.

Constraint: Desktop sessions run the CLI over the SDK websocket path, so Ink tool JSX dialogs are not visible there
Constraint: macOS IME and Electron text inputs are unreliable with pyautogui.write and generic hotkey synthesis
Rejected: Reuse CLI setToolJSX dialogs in desktop mode | no transport for mid-call Ink UI over the SDK bridge
Rejected: Keep shell pbcopy/pbpaste for clipboard typing | inconsistent with NSPasteboard path and less reliable for Chinese text
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep desktop Computer Use approvals and macOS text-entry behavior on a single bridge/path; avoid reintroducing separate CLI-only and desktop-only codepaths for the same action
Tested: python3 -m unittest runtime/test_helpers.py
Tested: bun test src/utils/computerUse/permissions.test.ts src/server/__tests__/conversation-service.test.ts
Tested: cd desktop && bun run test ComputerUsePermissionModal chatStore
Tested: cd desktop && bun run test chatBlocks
Tested: cd desktop && bun run lint
Not-tested: End-to-end manual Computer Use interaction against a live Electron target app on macOS
2026-04-20 12:12:02 +08:00
程序员阿江(Relakkes)
91b4d16dc5 Restore sub-agent context in the desktop chat transcript
The desktop chat view flattened Agent tool activity, which made sub-agent work
hard to follow and separated key evidence from the main conversation. This
change threads parent tool linkage through the server bridge and desktop store,
renders dispatched sub-agents as grouped cards with nested tool activity, and
moves long final outputs into a markdown preview dialog so the main transcript
stays readable on narrow layouts.

Constraint: Existing sessions and live websocket events both needed to preserve parent-child relationships
Rejected: Add brand-new subagent websocket event types | unnecessary protocol expansion when parent linkage already existed upstream
Rejected: Inline full sub-agent markdown in the card body | too cramped for narrow desktop chat layouts
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep Agent card summaries compact; route long-form sub-agent output through the preview dialog unless the main chat layout is widened substantially
Tested: cd desktop && bun run test -- MessageList.test.tsx chatStore.test.ts
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/sessions.test.ts -t should\ reconstruct\ parent\ agent\ tool\ linkage\ from\ parentUuid\ chains
Not-tested: Full end-to-end visual verification against live CLI sessions with sub-agent text/thinking nested inline
2026-04-09 00:04:40 +08:00
程序员阿江(Relakkes)
12cc872150 feat: auto-generate session titles using AI (Haiku model)
After the first assistant response, derive a quick placeholder title from
the user message, then asynchronously call the provider's Haiku model to
generate a polished 3-7 word title. Titles update again at message 3 with
fuller conversation context. Updates push to frontend in real-time via
WebSocket `session_title_updated` event.

- extractTitle now reads `ai-title` JSONL entries (priority: custom > ai > first message)
- New titleService with deriveTitle + generateTitle using active provider config
- Handler tracks per-session message count and triggers generation on result
- Frontend sessionStore receives live title updates for sidebar + header

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:48:48 +08:00
程序员阿江(Relakkes)
993b96cd39 Stabilize the desktop transcript so long agent sessions stay readable
The desktop app now keeps the composer stable while turns are active,
reduces low-signal tool noise in the transcript, restores project context
under the composer after session creation, and relies on the CLI's own
permission requests instead of injecting broader desktop-side Bash asks.

This also brings in the supporting desktop app source tree and the server
routes/session metadata needed for git info, filesystem browsing, session
resume, slash commands, and SDK-backed permission bridging so the UI can
operate as a coherent feature instead of a partial patch.

Constraint: Desktop transcript needs to stay usable during long multi-tool sessions without hiding file-change diffs
Constraint: Permission prompts must mirror CLI behavior closely enough that read-only commands do not get desktop-only prompts
Rejected: Keep rendering Read/Bash bodies inline | too noisy and unlike the intended transcript model
Rejected: Commit only the touched desktop files | would leave the newly introduced desktop app incomplete in git history
Confidence: medium
Scope-risk: broad
Reversibility: messy
Directive: Treat non-writing tools as summary-first transcript events; do not re-expand them by default without validating the UX against long sessions
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- --run
Tested: bun test src/server/__tests__/conversations.test.ts
Not-tested: Manual visual regression against the exact screenshots in a live desktop session
Not-tested: Full root TypeScript check (repository still has unrelated extracted-native parse failures)
2026-04-06 20:37:44 +08:00
程序员阿江(Relakkes)
ce0908a4e0 feat: complete server-side gaps — tool visibility, cron execution engine, team real-time push
- Fix translateCliMessage to return ServerMessage[] with full tool lifecycle
  (tool_result, tool_use_complete, toolInput, thinking, stream_event)
- Add CronScheduler that actually executes scheduled tasks via CLI subprocess,
  with execution log persistence and GET /api/scheduled-tasks/:id/runs API
- Add TeamWatcher polling ~/.claude/teams/ every 3s, broadcasting team_update/
  team_created/team_deleted to all WebSocket clients
- 280 tests pass (51 new), real LLM integration verified via MiniMax API

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:42:32 +08:00
程序员阿江(Relakkes)
0c6d2754b4 feat: add Desktop UI backend server with full REST API and WebSocket support
Add complete server-side implementation for the Claude Code Desktop App UI:

- REST API: sessions, conversations, settings, models, scheduled tasks,
  search, agents, and status endpoints (9 modules, 30+ endpoints)
- WebSocket: real-time chat streaming with state transitions, ping/pong,
  permission request forwarding, and stop generation support
- Services: sessionService (JSONL read/write, CLI-compatible),
  settingsService (atomic writes), cronService, searchService (ripgrep),
  agentService (YAML management)
- Middleware: CORS (localhost-only), auth, unified error handling
- Tests: 180 tests (unit + E2E + business flow), all passing
- Docs: PRD, UI design spec, server architecture design

Non-invasive: all new code under src/server/, no changes to existing CLI code.
CLI/UI data interop: reads/writes the same JSONL/JSON files as the CLI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:23:44 +08:00