Desktop file mentions should follow the workspace the user opened, but
the filesystem browser only trusted the home and temp roots. Register
workspace roots after repository context, session creation, and session
git-info resolution so Windows projects on another drive can be searched
without turning the browse API into arbitrary disk access.
Constraint: Windows users can open repositories outside C:\\Users, such as D:\\workspace\\code\\cc-haha
Constraint: Filesystem browse must not become an unbounded local disk reader
Rejected: Add a global user-configurable filesystem whitelist | broader product and persistence surface than this bug needs
Rejected: Allow every requested browse path | would bypass the intended filesystem boundary
Confidence: high
Scope-risk: moderate
Directive: Register only workspace roots that the server has already resolved through session or repository flows
Tested: bun test src/server/__tests__/filesystem.test.ts
Tested: bun run check:server
Not-tested: Windows packaged desktop smoke
Desktop git-info preserved the launch branch before checking the active worktree cwd, so materialized isolated worktree sessions could keep showing the source branch instead of the branch Git had checked out in the worktree. The API now switches to the real cwd branch only once the session has actually entered its planned worktree, while direct branch launches still keep their stable launch branch.
Constraint: Direct branch launches still need stable launch metadata when the source checkout later changes.
Rejected: Always prefer git rev-parse output | would regress direct launch sessions that intentionally show the selected branch.
Confidence: high
Scope-risk: narrow
Tested: bun test src/server/__tests__/sessions.test.ts -t "git-info"
Tested: /tmp/cc-haha-issue-539-verify API reproduction returned the worktree branch
Tested: bun run check:server
OpenAI rejects Codex OAuth authorize URLs when the redirect URI uses the desktop API server's dynamic port. The desktop ChatGPT Official flow now starts a temporary Codex callback listener and generates the authorize URL with that callback port, while keeping token exchange and storage in the existing desktop OAuth service.
Constraint: OpenAI Codex OAuth client accepts the Codex-compatible localhost callback shape, not arbitrary desktop API ports.
Rejected: Keep routing the callback through the desktop server port | that fails before the browser can return an authorization code.
Confidence: high
Scope-risk: narrow
Directive: Do not put non-Codex query parameters or dynamic desktop ports into the OpenAI authorize URL without live authorization-page verification.
Tested: bun test src/server/__tests__/haha-openai-oauth-service.test.ts src/server/__tests__/haha-openai-oauth-api.test.ts
Tested: bun test src/services/openaiAuth/client.test.ts src/services/openaiAuth/fetch.test.ts src/services/openaiAuth/storage.test.ts
Tested: bun run check:server
Tested: bun run check:coverage
Tested: bun run verify
Not-tested: Completing a real account OAuth approval in the browser after the fix.
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
The ChatGPT Official OAuth start flow already generated the Codex /auth/callback redirect, but the desktop server only handled the older /callback/openai path. Real HTTP and UI E2E exposed the mismatch, so the server now routes the Codex callback path while retaining the legacy alias.
Constraint: OpenAI Codex OAuth client code owns OPENAI_CODEX_REDIRECT_PATH.
Rejected: Change the generated redirect back to /callback/openai | that would diverge from the shared OpenAI Codex OAuth client path.
Confidence: high
Scope-risk: narrow
Tested: bun test src/server/__tests__/providers.test.ts src/server/__tests__/settings.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/proxy-transform.test.ts src/utils/__tests__/providerManagedEnvCompat.test.ts src/services/api/client.test.ts src/services/openaiAuth/fetch.test.ts src/server/__tests__/haha-openai-oauth-service.test.ts src/server/__tests__/haha-openai-oauth-api.test.ts
Tested: HTTP E2E passed at /var/folders/sx/hl8zlmxd7gx9b58_1t2zhp5r0000gn/T/cc-haha-openai-oauth-e2e-4uWfok/result.json.
Tested: UI E2E passed at /var/folders/sx/hl8zlmxd7gx9b58_1t2zhp5r0000gn/T/cc-haha-openai-oauth-ui-e2e-ZyQ60q/result.json.
Local main added desktop branch-from-chat, attribution headers, sidecar packaging changes, and provider model normalization. The merge keeps those mainline changes while preserving ChatGPT Official provider metadata, OpenAI OAuth runtime env, provider-load guards, and model catalog behavior.
Constraint: Current worktree was detached; created feat/chatgpt-official-oauth-provider before merging to preserve the OAuth commit line.
Rejected: Rebase the OAuth line over main | the branch already contains many reviewed commits and a merge makes the integration point explicit.
Confidence: high
Scope-risk: moderate
Tested: bun test src/server/__tests__/providers.test.ts src/server/__tests__/settings.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/proxy-transform.test.ts src/utils/__tests__/providerManagedEnvCompat.test.ts src/services/api/client.test.ts src/services/openaiAuth/fetch.test.ts src/server/__tests__/haha-openai-oauth-service.test.ts src/server/__tests__/haha-openai-oauth-api.test.ts
Tested: cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx src/components/settings/ChatGPTOfficialLogin.test.tsx src/stores/providerStore.test.ts src/components/controls/ModelSelector.test.tsx --testNamePattern "ChatGPT|OpenAI OAuth|Providers tab|ChatGPTOfficialLogin|ModelSelector|providerStore"
Tested: git diff --cached --check
Bring the verified worktree implementation into local main while preserving the existing local main commits and unrelated dirty working tree changes.
Constraint: Local main already had unrelated uncommitted provider/runtime edits, so the merge only touches the desktop branch feature paths.
Confidence: high
Scope-risk: moderate
Tested: bun run verify before merge; merge completed without conflicts
Not-tested: Post-merge full quality gate on dirty local main
Desktop users need the same branch-from-here workflow that the CLI already exposed, so the branch creation logic now lives in a shared transcript utility and the desktop app routes completed message actions through the server API. The UI hydrates transcript ids after live completions so newly generated turns can be branched immediately without a refresh.
Constraint: Source sessions must remain unmodified while branch sessions inherit the active transcript chain and persistence metadata.
Rejected: Keep a desktop-only branch implementation | it would drift from CLI /branch semantics and duplicate transcript filtering rules
Confidence: high
Scope-risk: moderate
Directive: Do not remove the post-completion transcript hydration without a real-model desktop E2E for just-finished messages
Tested: bun run verify; Chrome Web UI E2E with real gpt-5.5 provider on ports 45678/45679
Not-tested: Provider-specific behavior beyond the configured Sub2API-ChatGPT route
Provider-managed environments should preserve Claude attribution only when the selected model is actually Claude-prefixed. Non-Claude provider models now disable the attribution header so third-party providers do not inherit Claude-specific prompt metadata.
Constraint: Claude model detection is intentionally model-name based to match the requested provider policy.
Rejected: Remove attribution headers globally | official Claude-prefixed models should keep existing behavior.
Confidence: high
Scope-risk: moderate
Directive: Do not broaden this policy without live provider request-shape verification.
Tested: Targeted server, provider, settings, cron, and managed-env tests in prior verification pass.
Not-tested: Full verify gate after this commit.
When ChatGPT Official is active, the model API now returns the OpenAI Codex catalog and reads or writes the selected GPT model from cc-haha managed settings. Desktop provider activation also recognizes the built-in provider id and resets the current model to the OpenAI default instead of looking for a saved provider record.
Constraint: ChatGPT Official is a built-in provider and is not present in providers.json providers[].
Rejected: Reuse the four-slot provider model list | it hides GPT-5.5 and misrepresents the OpenAI catalog as Anthropic slot names.
Confidence: high
Scope-risk: moderate
Tested: bun test src/server/__tests__/settings.test.ts
Tested: bun test src/server/__tests__/providers.test.ts --test-name-pattern "ChatGPT Official|getProviderForProxy"
Tested: bun test src/server/__tests__/conversation-service.test.ts --test-name-pattern "ChatGPT Official|session-scoped provider"
Tested: cd desktop && bun run test src/stores/providerStore.test.ts
Tested: git diff --check
Remote main carries portable-mode, legacy Windows workdir recovery, and Feishu path-safety fixes while local main carries terminal shell, update proxy, slash-command, prompt-draft, AskUserQuestion, background-work, and shell-env changes. This merge keeps both lines by layering portable Bash-path defaults underneath the desktop terminal shell preference and preserving both update-proxy and app-mode settings state.
Constraint: Local main and origin/main diverged after v0.2.7 and both lines contain release-relevant desktop/runtime fixes
Rejected: Prefer either side's terminal settings wholesale | would drop either Windows portable Bash support or explicit desktop startup-shell support
Confidence: medium
Scope-risk: moderate
Directive: Keep portable Bash path as the system-default terminal fallback; explicit desktop startup-shell settings should continue to override it
Tested: cd desktop && bun run test -- --run src/pages/TerminalSettings.test.tsx src/stores/settingsStore.test.ts
Tested: cd desktop/src-tauri && cargo test terminal -- --nocapture
Tested: bun test src/server/__tests__/sessions.test.ts -t stale worktree
Tested: bun run check:desktop
Tested: bun run check:server
Tested: bun run check:native
Not-tested: Manual Windows packaged-app terminal/portable smoke
The desktop command list could be replaced by a partial live CLI update after a turn, and the server fallback only knew about skills. Keep the client list stable while refreshing from the authoritative session endpoint, and include legacy .claude/commands entries in that endpoint.
Constraint: Claude Code custom slash commands still use .claude/commands/*.md alongside newer skill commands.
Rejected: Only union client-side updates | would still miss custom commands before CLI init and lose argument hints from the authoritative API.
Confidence: high
Scope-risk: moderate
Directive: Keep session slash command fallback aware of both skills and legacy command directories.
Tested: bun test src/server/__tests__/sessions.test.ts -t "slash-commands"
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts
Tested: bun run check:desktop
Tested: bun run check:server
Tested: bun run check:native
Not-tested: bun run verify remains red due unrelated/flaky coverage lane failures outside this change.
Related: https://github.com/NanmiCoder/cc-haha/issues/495
New desktop sessions populated slash suggestions from the session endpoint before the CLI had emitted init metadata. That endpoint only scanned user and project skill directories, while the plugin settings view and global skills API already saw enabled plugin skills such as superpowers. The session endpoint now reuses the global skill listing and merges it with any cached CLI slash commands, and both composer surfaces rank command-name matches before broad description matches so /su surfaces superpowers first.
Constraint: New sessions need plugin skills before the first real user turn starts the CLI.
Rejected: Start or restart a hidden CLI process on plugin enable | heavier than needed and still misses the REST slash-command fallback path.
Confidence: high
Scope-risk: moderate
Directive: Keep session slash commands and /api/skills on the same skill discovery path when changing plugin skill loading.
Tested: bun test src/server/__tests__/skills.test.ts src/server/__tests__/plugins.test.ts src/server/__tests__/sessions.test.ts
Tested: cd desktop && bun run test --run src/components/chat/composerUtils.test.ts src/pages/EmptySession.test.tsx src/components/chat/ChatInput.test.tsx
Tested: bun run check:server
Tested: cd desktop && bun run lint
Not-tested: Manual desktop click-through in the packaged app.
Plugin enablement already has a live CLI reload control path, so desktop now applies plugin changes by refreshing the active session instead of waiting for a future conversation startup. The server forwards reload_plugins to the active CLI session, refreshes session slash-command cache, and notifies the client. The desktop plugin store automatically reloads after mutating plugin state, and the empty-session composer refetches skills when plugin capabilities change.
Constraint: Existing CLI exposes reload_plugins as the supported hot-refresh mechanism for commands, agents, plugins, and MCP state.
Rejected: Start a hidden replacement CLI process | higher cost, extra process lifecycle risk, and less precise than the existing control channel.
Confidence: high
Scope-risk: moderate
Directive: Keep plugin refresh routed through reload_plugins unless the CLI control contract is removed or changed.
Tested: bun test src/server/__tests__/plugins.test.ts
Tested: cd desktop && bun run test --run src/stores/pluginStore.test.ts src/__tests__/pluginsSettings.test.tsx src/pages/EmptySession.test.tsx
Tested: bun run check:server
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Not-tested: bun run check:desktop is blocked by a pre-existing color-mix compatibility failure in desktop/src/theme/globals.css.
Desktop sessions must treat the global Thinking toggle as the source of truth across active CLI sessions, title generation, and provider-compatible request bodies. DeepSeek-style streams can start with reasoning blocks before text, so the WebSocket bridge now keeps the UI in thinking state until text content actually starts.
Constraint: DeepSeek and MiniMax use Anthropic-compatible surfaces but expose thinking behavior through provider-specific request and stream shapes
Rejected: Keep provider-specific disabled-thinking env defaults | stale defaults kept overriding the user's global Thinking setting
Rejected: Treat every non-tool stream block as text | reasoning-only startup blocks created a blank streaming gap before the visible thinking bubble
Confidence: high
Scope-risk: moderate
Directive: Do not change thinking stream state transitions without replaying reasoning-before-text provider events
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: bun run check:server
Tested: bun run verify
Not-tested: Live DeepSeek provider smoke with real quota
Sidebar project ordering, pinning, and hidden-project state must survive
browser access to the same local server, so the UI now stores these
preferences under the cc-haha config directory and keeps localStorage as
a migration/cache fallback.
Constraint: Browser and H5 localStorage is isolated from the Tauri WebView
Rejected: Keep sidebar project preferences only in localStorage | browser sessions would not share state
Rejected: Reuse cc-haha/settings.json | provider and H5 access settings should stay separate
Confidence: high
Scope-risk: moderate
Directive: Keep sidebar hide/remove semantics non-destructive; do not delete transcript files for project removal
Tested: cd desktop && bun run test -- src/components/layout/Sidebar.test.tsx --run
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/desktop-ui-preferences.test.ts
Tested: bun run check:persistence-upgrade
Tested: bun run check:server
The desktop sidebar needed a project-first navigation model that keeps worktree sessions attached to their source project instead of scattering them as separate paths. The UI now renders sessions under project groups with project-level actions and persisted ordering, while the server exposes a logical project root for worktree transcripts.
Constraint: Existing memory directory tree work must remain separate from the session sidebar behavior.
Rejected: Group by raw transcript projectPath | worktree paths fragment the same repository into multiple sidebar projects.
Confidence: high
Scope-risk: moderate
Directive: Keep future sidebar grouping keyed by projectRoot before projectPath so isolated worktrees stay under their source repository.
Tested: cd desktop && bun run test -- src/components/layout/Sidebar.test.tsx
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: git diff --check
Settings reported false negatives for resources the runtime could load, because the API paths used narrower discovery rules than the execution path. This keeps the Settings surfaces read-only where appropriate while matching runtime visibility for linked skills, version-constrained plugins, and merged MCP sources.
Constraint: Settings MCP list must not actively connect to servers while loading.
Rejected: Add UI-specific fallbacks | would preserve drift between Settings and runtime loaders
Confidence: high
Scope-risk: moderate
Directive: Keep Settings resource listing backed by the same shared loader semantics as runtime discovery.
Tested: bun test src/server/__tests__/skills.test.ts
Tested: bun test src/server/__tests__/plugins.test.ts
Tested: bun test src/server/__tests__/mcp.test.ts
Tested: bun run check:server
Tested: bun run check:coverage
Tested: bun run verify
Not-tested: Live external MCP connector account state
This brings the desktop memory worktree onto local main after main advanced with opener, IM, zoom, and autonomous goal work. The conflict resolution keeps both the selected-chat reference imports and memory event settings entrypoint in MessageList, and keeps both open-targets and memory API routes in the server router.
Constraint: main and feat/desktop-memory-surface diverged across desktop chat and server routing.
Constraint: desktop Vitest runs src-tauri tests under Vitest, so the Tauri config test cannot import bun:test.
Rejected: Fast-forward merge | main carried newer local commits not present on the memory branch.
Confidence: high
Scope-risk: moderate
Directive: Keep chat selection references, memory cards, open-target routes, and memory routes together when touching these files.
Tested: bun run check:desktop
Tested: bun run check:server
Some memory-only project directories have no session JSONL metadata, so the previous label recovery still fell back to reversing sanitizePath(). That reverse mapping is lossy for Chinese characters, spaces, and punctuation, leaving paths rendered with repeated slashes. The memory API now performs a bounded filesystem-guided recovery: it walks only roots whose sanitized prefix can still match the project id and only descends through candidate directories that preserve that prefix match.
Constraint: Do not change the existing ~/.claude/projects/<sanitized>/memory storage layout.
Rejected: Full-home recursive search | too expensive and unnecessary because sanitizePath prefix matching gives a tight traversal boundary.
Confidence: high
Scope-risk: narrow
Directive: Keep project ids as opaque storage keys; recover display labels from real metadata or existing directories first.
Tested: bun test src/server/__tests__/memory.test.ts
Tested: local server curl /api/memory/projects against the real PicTacticAgent legacy memory project
The first project opener UI used generic code glyphs for every IDE, which made the menu visually noisy and did not match the native desktop expectation. This changes the macOS path to expose transparent PNG icons generated from each detected local .app bundle and renders them directly in the toolbar/menu, falling back to the existing glyph only when an icon cannot be loaded.
Constraint: Do not redistribute third-party IDE trademark assets in the app bundle.
Constraint: Keep icon detection local to already detected open targets and cache only runtime PNG results.
Rejected: Bundling downloaded IDE logos | creates asset maintenance and trademark redistribution risk.
Rejected: Adding simple-icons as a dependency | it is not a faithful desktop app icon source and still requires brand permission checks.
Confidence: high
Scope-risk: narrow
Directive: Prefer local bundle icons on macOS; add curated official assets only as a cross-platform fallback layer.
Tested: bun test src/server/__tests__/open-target-service.test.ts src/server/__tests__/open-target-api.test.ts
Tested: cd desktop && bun run test -- src/stores/openTargetStore.test.ts src/components/layout/OpenProjectMenu.test.tsx src/components/layout/TabBar.test.tsx
Tested: cd desktop && bun run lint
Tested: bun -e openTargetService.getTargetIcon for vscode and finder returned PNG data
Tested: cd desktop && bun run build
The CLI stores project directories with sanitizePath(), which intentionally replaces every non-alphanumeric byte with '-' for cross-platform safety. That storage key is lossy, so the desktop memory page must not treat it as the only source of display truth. The memory API now prefers the current cwd or the cwd/workDir/projectPath recorded in recent session JSONL files, and only falls back to the lossy reverse mapping when no metadata exists.
Constraint: Keep the existing ~/.claude/projects/<sanitized>/memory layout unchanged for CLI compatibility.
Rejected: Change sanitizePath to preserve Unicode | would break existing transcript and memory directory lookup semantics.
Confidence: high
Scope-risk: narrow
Directive: Treat project ids as storage keys, not user-facing path labels.
Tested: bun test src/server/__tests__/memory.test.ts
Tested: cd desktop && bunx vitest run src/__tests__/memorySettings.test.tsx
Tested: local server curl /api/memory/projects with a Chinese-space project path
Not-tested: bun run check:server still has two unrelated cron scheduler launcher timeout failures in src/server/__tests__/cron-scheduler-launcher.test.ts
Desktop sessions need a fast local escape hatch that opens the same materialized cwd the agent is editing, without showing unavailable IDE choices or persisting detection state. This adds a local open-targets API with silent in-memory detection for common IDEs and platform file managers, then wires a compact Codex-style toolbar menu into the desktop TabBar for active session workdirs.
Constraint: The first version is local IDE/editor and Finder/Explorer/file-manager only, no terminal targets or IDE plugin integration.
Constraint: The opened path must come from the active session workDir so isolated worktrees open the actual agent editing surface.
Rejected: Persisting detected applications | detection is cheap and temporary state avoids stale app inventory.
Rejected: Rendering unavailable IDEs as disabled menu rows | the user asked to show only detected targets and fall back to Finder/Explorer when no IDE is available.
Confidence: high
Scope-risk: moderate
Directive: Keep this path session-workdir based; do not switch it to repository root without proving isolated worktree behavior.
Tested: bun test src/server/__tests__/open-target-service.test.ts src/server/__tests__/open-target-api.test.ts
Tested: cd desktop && bun run test -- src/stores/openTargetStore.test.ts src/components/layout/OpenProjectMenu.test.tsx src/components/layout/TabBar.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: bun test src/server/__tests__/h5-access-auth.test.ts -t 'allows local desktop H5 access settings under explicit server auth with a valid bearer'
Not-tested: bun run check:server full suite had one unrelated H5 auth integration timeout in the full concurrent run; the timed-out test passed when rerun alone.
The CLI already emits memory_saved events and stores Markdown memory files, but the desktop app had no usable surface for seeing or editing those writes. This adds a project memory API, a Settings memory editor, chat memory event cards, and routing from /memory or /context into the memory UI.
Constraint: Memory files live under Claude project storage and must remain plain Markdown editable by users.
Rejected: Only expose raw filesystem links | users need an in-app review and edit flow from chat.
Confidence: high
Scope-risk: moderate
Directive: Keep memory storage project-scoped and preserve unknown Markdown content when editing.
Tested: bun test src/server/__tests__/ws-memory-events.test.ts src/server/__tests__/memory.test.ts
Tested: bun run check:server
Tested: bun run check:desktop
Tested: agent-browser E2E for chat memory card, Open Memory navigation, and responsive Markdown editor layout
Not-tested: Live model auto-memory trigger rate with real provider credentials
Desktop search results were visually misleading when many directories shared the same basename, because the UI emphasized the basename and the server ranking allowed deep basename matches to outrank direct path-prefix matches. This makes search rows show the insertable relative path and ranks direct path-prefix matches before unrelated same-name directories.
Constraint: Desktop @ file suggestions should match the CLI mental model for path search.
Rejected: Keep basename plus parent-path rows | it hides the distinction between src/, src/hooks/, and desktop/src in the primary text.
Confidence: high
Scope-risk: narrow
Directive: Preserve path-prefix ranking ahead of basename-only matches for directory searches.
Tested: bun test src/server/__tests__/filesystem.test.ts
Tested: cd desktop && bun run test -- FileSearchMenu.test.tsx ChatInput.test.tsx
Tested: bun run check:server
Tested: bun run check:desktop
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.
The sidebar had only single-session deletion, which made stale or noisy
session lists expensive to maintain. This adds a batch-management lane in
the desktop UI and a server endpoint that deletes multiple sessions while
preserving per-session failure reporting and cleanup behavior.
Constraint: Session deletion must preserve existing transcript and adapter cleanup semantics
Constraint: Desktop UI should reuse the shared confirmation dialog instead of introducing a second modal surface
Rejected: Delete all selected sessions through repeated client DELETE calls | weaker partial-failure handling and duplicated cleanup orchestration
Rejected: Use a generic checkmark entry icon | it did not communicate batch deletion clearly enough
Confidence: high
Scope-risk: moderate
Directive: Keep batch deletion routed through the server batch endpoint so rollback and adapter cleanup stay centralized
Tested: bun test src/server/__tests__/sessions.test.ts -t batch-delete
Tested: bun run check:server
Tested: cd desktop && bun run check:desktop
Tested: browser UI smoke for group select, shift range select, delete confirmation, 7/30 day cleanup, Cmd+A filtered selection, and Escape exit
Not-tested: Live provider-backed session generation; this change covers session list management after sessions already exist
The previous fix compared the venv's pyvenv.cfg `home` field with the
custom interpreter's directory, but Python's venv module skips the
wrapping venv when the source interpreter is itself in a venv (conda /
pyenv / hand-built venvs — which is the common case). The recorded
`home` then points to the base Python install, not the user-provided
path, causing the comparison to permanently fail and the UI to keep
showing "venv not ready" even right after a successful rebuild.
Switch to a marker file (~/.claude/.runtime/venv-base-interpreter.txt)
that records the exact `config.pythonPath` used at venv creation time.
Status check compares it verbatim with the current config. Falls back
to "match if both empty" when the marker is missing, which preserves
behavior for legacy users who never set a custom path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The dependency check ignored the custom Python path: it only verified
the global venv's stamp file, so switching to a fresh custom interpreter
still showed deps as installed even though the new interpreter's
environment was empty. Setup also reused the old venv built from a
different base interpreter.
Verify the existing venv via pyvenv.cfg's home field, but only when a
custom path is configured — users without a custom path keep the
original code path untouched, protecting existing installations.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Integrate the Token usage settings page on top of the newer local main branch, preserving the existing H5 access router surface while adding the activity stats API route.
Constraint: Local main already contained H5 access router changes after the Token usage worktree base.
Rejected: Prefer either router branch during conflict resolution | both API surfaces are independent and must remain registered.
Confidence: high
Scope-risk: narrow
Directive: Keep h5-access and activity-stats routes registered together in src/server/router.ts.
Tested: Conflict marker scan for src/server/router.ts
Not-tested: Post-merge desktop/server checks pending
Expose local Claude Code CLI transcript usage in Settings so users can inspect recent token consumption and daily activity without leaving the desktop app.
The page uses server-side transcript aggregation for session, message, tool, model-token, and subagent token data. Daily token buckets use assistant message timestamps, and daily session counts use active parent sessions for the same date bucket so resumed sessions and cross-midnight work do not produce token-only days. Cache accounting is bumped to v5 to force recomputation under the corrected daily semantics.
Constraint: Usage data must come from local Claude Code CLI transcripts rather than mock/demo data.
Constraint: Desktop navigation keeps Token usage directly above Diagnostics.
Rejected: Bucket all token usage by session start date | hides resumed-session and cross-midnight consumption from the actual day it was spent.
Confidence: high
Scope-risk: moderate
Directive: Keep daily token and daily session counts on the same date-bucketing semantics.
Tested: bun run check:desktop
Tested: bun run check:server
Tested: Browser verification for Token usage in English and Chinese locale date labels
Not-tested: Full bun run verify quality gate
Persist H5 access state in cc-haha managed settings, expose a narrow server API
for enable/disable/regenerate/verify flows, and keep token responses sanitized
so the raw secret is only returned at generation time.
Constraint: H5 config must live in ~/.claude/cc-haha/settings.json and preserve unknown fields
Rejected: Store raw token for later display | violates hash-only persistence requirement
Confidence: high
Scope-risk: narrow
Directive: Do not move H5 settings into ~/.claude/settings.json or expose tokenHash through the API
Tested: bun test src/server/__tests__/h5-access-service.test.ts src/server/__tests__/h5-access-api.test.ts
Not-tested: Full repo typecheck via tsc is blocked locally by missing bun-types and a TS 6 baseUrl deprecation in the current config
Computer Use setup could fail on machines where PATH discovery misses a valid Python installation, especially Windows or conda-style environments. Store an optional interpreter path, prefer it during environment checks and venv creation, and expose a desktop settings control for selecting or clearing it.
Constraint: Python discovery is environment-specific and cannot always be inferred from PATH.
Rejected: Continue falling back to PATH after an invalid custom path | hides a user-selected broken interpreter and makes diagnosis ambiguous.
Confidence: high
Scope-risk: moderate
Directive: Preserve unknown Computer Use config fields and keep blank interpreter paths normalized to automatic detection.
Tested: bun test src/utils/computerUse/preauthorizedConfig.test.ts src/server/__tests__/computer-use-python.test.ts src/server/__tests__/computer-use-api.test.ts
Tested: cd desktop && bun run test src/pages/ComputerUseSettings.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: Computer Use browser smoke saved /opt/homebrew/bin/python3 and backend persisted then reset pythonPath to null
Not-tested: Full bun run check:server; existing cron-scheduler-launcher test expects CLAUDE_CODE_ENTRYPOINT=sdk-cli but received undefined.
Related: https://github.com/NanmiCoder/cc-haha/issues/331
CLI transcripts persist native worktree-state entries for resume, so the desktop git-info endpoint should treat that state as authoritative for worktree identity. Desktop launch metadata can be stale after placeholder cleanup or reopen, and should only fill gaps.
The displayed business branch still prefers the desktop launch branch because CLI originalBranch means the source checkout before worktree creation, not necessarily the selected base ref.
Constraint: CLI worktree-state carries runtime identity; Desktop repository metadata carries launch intent.
Rejected: Let desktop repository metadata override worktree-state | stale placeholder metadata can resurrect the wrong slug, path, or source cwd after reload.
Confidence: high
Scope-risk: narrow
Directive: Worktree identity fields should prefer CLI worktree-state; only branch display may prefer desktop launch intent.
Tested: bun test src/server/__tests__/sessions.test.ts --test-name-pattern worktree-state|worktree identity|git-info
Tested: cd desktop && bun run lint
Tested: git diff --check
Desktop sessions already persist repository launch metadata, but CLI worktree sessions also write native worktree-state entries to the JSONL transcript for resume. Relying only on desktop session-meta leaves reopened or older transcripts vulnerable to losing the footer worktree identity when in-memory state is gone.
This teaches SessionService to recover the latest CLI worktree-state entry and lets git-info use it as a fallback for worktree badge fields. Desktop repository metadata still wins when present because it carries the selected business branch, while the CLI state keeps reopened sessions stable.
Constraint: CLI records worktree state as transcript metadata, not only as cwd.
Rejected: Infer worktree identity only from .claude/worktrees path | path heuristics miss hook-based worktrees and do not carry original cwd or slug.
Confidence: high
Scope-risk: narrow
Directive: Prefer explicit transcript metadata over path guessing for worktree session identity.
Tested: bun test src/server/__tests__/sessions.test.ts --test-name-pattern CLI worktree state
Tested: bun test src/server/__tests__/sessions.test.ts --test-name-pattern worktree-state|worktree identity|git-info
Tested: cd desktop && bun run lint
Tested: git diff --check
The footer project chip previously kept showing only the source project and selected branch even after a session launched inside an isolated CLI worktree. That made an active worktree session look identical to a normal checkout, especially while agent tools were already operating under .claude/worktrees. The server now returns explicit worktree metadata from the session launch intent, and the desktop chip preserves the user-facing project and branch while appending the isolated worktree slug.
Constraint: CLI worktree branches are internal implementation details and should not replace the user-selected branch label.
Rejected: Show only the actual worktree git branch | it would expose worktree-desktop-* internals and make the business branch harder to read.
Confidence: high
Scope-risk: narrow
Directive: Keep normal checkout footer rendering unchanged; only add worktree identity when session metadata says isolation is enabled.
Tested: bun test src/server/__tests__/sessions.test.ts --test-name-pattern git-info
Tested: cd desktop && bun run test -- src/components/shared/ProjectContextChip.test.tsx
Tested: cd desktop && bun run lint
Tested: git diff --check
The chat footer previously read git-info from the persisted session file and refreshed only on tab changes. Repository launches can update the active CLI cwd after the composer has already rendered, so the footer could keep showing an older selected branch even while the running model was operating in the correct checkout.
Constraint: The active CLI process is the source of truth for a live session cwd
Rejected: Derive the footer branch only from persisted session metadata | metadata can lag behind startup and does not prove the current running cwd
Confidence: high
Scope-risk: narrow
Tested: bun test src/server/__tests__/sessions.test.ts --test-name-pattern "git-info should prefer"
Tested: cd desktop && bun run lint
Tested: git diff --check
The detached worktree fix has been verified on adapter and server lanes
and is now integrated into the local main branch without pushing.
Constraint: main already contains follow-up local commits beyond the worktree base
Confidence: high
Scope-risk: moderate
Tested: bun test common/__tests__/session-store.test.ts common/__tests__/ws-bridge.test.ts
Tested: bun test src/server/__tests__/websocket-handler.test.ts src/server/__tests__/sessions.test.ts --timeout 30000
Tested: cd adapters && bunx tsc --noEmit
Tested: bun run check:adapters
Tested: bun run check:server
Not-tested: bun run verify fails on pre-existing agent-utils coverage ratchet; changed-line coverage is 95.92% and affected lanes pass
Related: 1f62bcd
Deleting a desktop session removed the transcript but left IM adapter
chat mappings and live WebSocket state able to point at the old session.
The server now closes the active session socket and removes adapter
mappings after deletion, while adapters refresh the shared session store
before reads so a running process cannot reuse stale in-memory data.
Constraint: Adapter session mappings are shared through adapter-sessions.json across long-running IM processes
Rejected: Patch each adapter ensureSession path separately | shared store refresh fixes all current adapters and avoids drift
Confidence: high
Scope-risk: moderate
Directive: Do not cache adapter session mappings without invalidating after server-side session deletion
Tested: bun test common/__tests__/session-store.test.ts common/__tests__/ws-bridge.test.ts
Tested: bun test src/server/__tests__/websocket-handler.test.ts src/server/__tests__/sessions.test.ts --timeout 30000
Tested: cd adapters && bunx tsc --noEmit
Tested: bun run check:adapters
Tested: bun run check:server
Not-tested: bun run verify fails on pre-existing agent-utils coverage ratchet; changed-line coverage is 95.92% and affected lanes pass
Related: https://github.com/NanmiCoder/cc-haha/issues/305
Scheduled tasks used the model selector in model-only mode, so a task could store a third-party model id without the provider runtime needed to execute it. The desktop task editor now stores the selected provider with the model, and the scheduler injects the same provider-scoped runtime env that desktop sessions use before launching the sidecar.
Constraint: Scheduled tasks run outside an active desktop websocket session, so provider runtime must be serialized on the task itself.
Rejected: Reuse session runtime localStorage for tasks | scheduled executions can run long after the UI session is gone.
Confidence: high
Scope-risk: moderate
Directive: Keep task execution provider env isolated from inherited process env; stale parent ANTHROPIC_* values must not override task provider choices.
Tested: cd desktop && bun run test -- src/components/tasks/NewTaskModal.test.tsx src/components/controls/ModelSelector.test.tsx
Tested: bun test src/server/__tests__/scheduled-tasks.test.ts src/server/__tests__/cron-scheduler-launcher.test.ts
Tested: bun run check:desktop
Tested: bun run check:server
Tested: git diff --check
Not-tested: Live third-party provider API call; covered by sidecar launch integration with provider proxy env.
Online upgrades can strand users on stale desktop UI state or malformed local persistence. This adds a deny-by-default Doctor path that resets only regenerable desktop UI state, reports protected local files with redacted metadata, and keeps protected repair as a dry-run no-op until a reviewed backup-first flow exists.
Constraint: Chat transcripts, model/provider config, Skills, MCP, IM bindings, adapter sessions, OAuth tokens, plugins, and team/session records are user-owned protected state.
Rejected: Automatically rewrite malformed protected JSON | unsafe without schema-specific migrations and backups.
Rejected: Continue relying only on startup migrations | users need an explicit recovery action after a white screen.
Confidence: high
Scope-risk: moderate
Directive: Keep Doctor repair deny-by-default; do not mutate protected state without an explicit reviewed backup-first manual repair flow.
Tested: bun test src/server/__tests__/doctor-service.test.ts
Tested: cd desktop && bun run test src/components/ErrorBoundary.test.tsx src/lib/doctorRepair.test.ts src/__tests__/diagnosticsSettings.test.tsx src/components/layout/StartupErrorView.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:server
Tested: bun run verify (failed only existing agent-utils coverage baseline; changed-lines coverage 97.62%)
Not-tested: Packaged desktop manual Doctor click path.
Desktop users can carry provider indexes, managed settings, localStorage state, and native update state from builds that no longer match current readers. This adds startup migrations and recovery paths before server and React state are consumed, plus a persistence upgrade gate so future storage protocol changes ship with old-format fixtures.
Constraint: Existing installs may contain malformed or legacy JSON/localStorage that must not block startup.
Constraint: Local verify should evaluate the current worktree diff rather than unrelated detached-worktree history.
Rejected: Treat invalid persisted state as fatal | reproduces white-screen and startup failure behavior for existing users.
Rejected: Bypass PR policy locally | hides real gate behavior and does not fix detached-worktree false positives.
Confidence: high
Scope-risk: moderate
Directive: Any local JSON, localStorage, or app config shape change must add a migration fixture and keep `bun run check:persistence-upgrade` green.
Tested: bun run check:persistence-upgrade; bun run check:policy; bun run check:desktop; bun run check:server; bun run check:native; bun run verify (9 passed, 1 coverage baseline failure)
Not-tested: Live provider baseline; existing user configs beyond covered fixtures
Desktop sessions now resolve repository launch intent before creation, so users can pick a branch, choose whether to isolate it in a worktree, and get stable error messages instead of silent branch-switch failures. The server owns the Git safety checks and session metadata so the UI and real agent runtime agree on the actual working directory.
Constraint: Direct branch switching must not overwrite or hide uncommitted user changes
Constraint: Desktop worktree branches must stay out of normal branch selection and recent-project labels
Rejected: Let the UI call git directly | server-side checks keep session metadata and runtime launch paths consistent
Rejected: Auto-switch dirty checkouts | users need an explicit isolated-worktree choice to protect local edits
Confidence: high
Scope-risk: moderate
Directive: Do not relax dirty-worktree or checked-out-branch guards without adding equivalent business-flow tests
Tested: ALLOW_CLI_CORE_CHANGE=1 bun run quality:pr
Tested: /tmp business-flow script with dirty checkout, checked-out branch, isolated worktree, missing branch, non-git, missing directory, and real LLM session
Tested: agent-browser desktop UI flow across dirty, checked-out, non-git, branch search, recent-project, and Run scenarios
Computer Use is useful when explicitly needed, but exposing its MCP tools by default creates unnecessary desktop-control surface for users who want coding-only sessions. This adds a shared disable path for CLI flags, environment, and desktop settings while keeping preauthorized app state in one config file.
The same change also preserves Windows and WSL shell startup behavior by applying the MSYS argument-conversion guard only on WSL-bound launches.
Constraint: Computer Use MCP must not be exposed to the Coding Agent when disabled
Constraint: Desktop settings and CLI sessions need to read the same persisted Computer Use config
Rejected: Environment-only disable switch | desktop users need a persistent Settings control
Rejected: Remove Computer Use setup entirely | enabled sessions still need the existing built-in MCP path
Confidence: high
Scope-risk: moderate
Directive: Keep every new Computer Use entrypoint wired through loadStoredComputerUseConfig or the CLI disable flag before adding MCP tools
Tested: bun test src/utils/computerUse/gates.test.ts src/utils/computerUse/preauthorizedConfig.test.ts src/server/__tests__/computer-use-api.test.ts src/utils/shell/wslInterop.test.ts desktop/src/pages/ComputerUseSettings.test.tsx
Tested: bun run check:server; bun run check:desktop; bun run check:docs; bun run check:policy; bun run check:native; git diff --check
Tested: SKIP_INSTALL=1 ./desktop/scripts/build-macos-arm64.sh; codesign verify; hdiutil verify; built CLI Computer Use E2E exposure and disable checks
Not-tested: Full screenshot/control action after granting macOS Screen Recording permission on this machine
DingTalk bot unbind was routed through the generic adapter config update,
but undefined credential fields are dropped during JSON serialization. That
left stored Client ID and Secret values intact after clicking the unbind
button. Match the working WeChat path with a dedicated server endpoint and
frontend API call so credential removal is explicit.
Constraint: JSON request bodies cannot preserve undefined fields for generic config clearing
Rejected: Keep using updateConfig with undefined fields | serialized requests omit the fields before the server can clear them
Confidence: high
Scope-risk: narrow
Directive: Keep credential-clearing flows on explicit unbind endpoints rather than relying on partial config merges
Tested: bun test src/server/__tests__/adapters.test.ts
Tested: cd desktop && bun run test src/stores/adapterStore.test.ts
Tested: bun run check:desktop
Tested: bun run check:server
Not-tested: bun run quality:pr is blocked by repository policy requiring allow-cli-core-change approval for the broader branch impact
The desktop composer now treats draft and not-yet-running sessions as pending
until a real CLI context snapshot exists. Context inspection has a lightweight
context-only path so the composer and /context panel do not wait on unrelated
usage or MCP status work, and desktop boot no longer blocks the first shell
render on tab restoration.
Constraint: Empty composer state has no live CLI context to inspect until a session starts
Rejected: Display 0% before live context exists | it implied one user message consumed the fixed prompt/tool baseline
Confidence: high
Scope-risk: moderate
Directive: Do not show numeric context usage without a real context snapshot
Tested: cd desktop && bun run test -- pages.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: bun run check:server
Tested: Browser reload at http://127.0.0.1:5174 with cleared open-tabs showed pending context and no /inspection request