Settings refreshes can complete after the user changes Computer Use authorization state. The page now ignores stale config reads once a local mutation has happened, so enablement and grant controls no longer visually roll back and require a second operation.
Constraint: Computer Use config is shared between the desktop settings page and runtime MCP exposure.
Rejected: Refetch after every save | still allows older in-flight reads to overwrite newer local intent.
Confidence: high
Scope-risk: narrow
Directive: Do not apply async settings snapshots after a newer local authorization mutation without a freshness check.
Tested: cd desktop && bun run test -- src/pages/ComputerUseSettings.test.tsx
Tested: bun run check:desktop
Tested: bun run check:coverage
Not-tested: bun run verify is blocked by existing path-aware policy labels for broader dirty scope.
The detached worktree contains the session composer repository controls work: preserving launch context across empty chat tabs, exposing branch and worktree selection under the composer, tightening long-label truncation, and validating the launch paths with desktop tests. Main had moved forward independently, so this records the integration as a merge commit instead of rewriting either side.
Constraint: Merge into local main only; do not push or alter remote state.
Rejected: Rebase the detached worktree onto main | it would rewrite the already reviewed local commit chain and was unnecessary after a clean merge.
Confidence: high
Scope-risk: moderate
Directive: Keep repository context controls under the composer as a single-line rail; long repo or branch labels must truncate inside their own controls.
Tested: bun run check:desktop
Not-tested: Native Tauri packaged window visual check
Long branch names should be clipped inside the branch selector instead of forcing the repository context rail onto a second row. The rail now stays nowrap, the branch selector owns truncation, and the worktree mode selector keeps its readable fixed footprint.
Constraint: Preserve the existing composer and repository context layout shape.
Rejected: Let the rail wrap on long branch names | it creates the broken two-line state shown in the desktop composer.
Confidence: high
Scope-risk: narrow
Directive: Long repository or branch labels must truncate inside their own controls rather than changing the rail height.
Tested: cd desktop && bun run test -- --run src/pages/EmptySession.test.tsx src/components/chat/ChatInput.test.tsx src/components/shared/DirectoryPicker.test.tsx
Tested: bun run check:desktop
Tested: agent-browser selected a long branch on http://localhost:4790 and verified rail height 53px, branch width 260px, no horizontal overflow; screenshot /tmp/cc-haha-launch-rail-long-branch-nowrap.png
Not-tested: Native Tauri packaged window visual check
The worktree control is a mode choice, so it should behave like the branch selector instead of a hidden toggle. This adds a compact dropdown with current and isolated worktree options while preserving the existing launch layout and isolation guard.
Constraint: Keep the composer context rail layout unchanged.
Rejected: Keep the single-click toggle | the interaction hid the available modes and made the state harder to reason about.
Confidence: high
Scope-risk: narrow
Directive: Worktree mode must stay an explicit selection surface, not a hidden toggle.
Tested: cd desktop && bun run test -- --run src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx src/components/shared/DirectoryPicker.test.tsx
Tested: bun run check:desktop
Tested: agent-browser worktree dropdown desktop and mobile screenshots at /tmp/cc-haha-worktree-dropdown-menu.png, /tmp/cc-haha-worktree-dropdown-selected.png, /tmp/cc-haha-worktree-dropdown-mobile-menu.png, /tmp/cc-haha-worktree-dropdown-mobile-selected.png
Not-tested: Native Tauri packaged window visual check
Runtime and permission changes can require a CLI restart, but the desktop UI should not interpret that maintenance restart as an active assistant turn. The restart path now stays out of the running chat state while preserving the stop/start behavior that applies model and bypass-permission changes.
Constraint: bypassPermissions changes require a CLI restart so startup args are applied.
Rejected: Hide the stop button in the frontend | the backend would still conflate configuration restarts with agent turns and quick-send races would remain untested.
Confidence: high
Scope-risk: narrow
Directive: Keep runtime and permission restart transitions serialized before user_message handling.
Tested: bun test src/server/__tests__/conversations.test.ts -t "in-flight runtime restart|in-flight permission restart"
Tested: bun run check:server
Not-tested: Browser screenshot; this change is the WebSocket state contract, not layout rendering.
The composer context rail should read as one compact group, not three stretched controls. This change removes flex growth from the repository picker, caps branch width by content, softens the active worktree state, and removes hover borders that made secondary state controls look primary.
Constraint: Keep the existing composer and session layout unchanged.
Rejected: Use a prominent active outline for worktree | it made the state look like the primary action.
Confidence: high
Scope-risk: narrow
Directive: Keep this rail content-sized; long names should truncate inside chips instead of spreading across the bar.
Tested: cd desktop && bun run test -- --run src/components/shared/DirectoryPicker.test.tsx src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx
Tested: bun run check:desktop
Tested: agent-browser desktop screenshot /tmp/cc-haha-repo-controls-compact-worktree-active-final.png and mobile overflow check /tmp/cc-haha-repo-controls-compact-mobile-final.png
Not-tested: Native Tauri packaged window visual check
Desktop chat sessions can stream and reconnect concurrently, so shared in-process buffers must not be keyed only by process lifetime. This change scopes streaming delta buffers, task-tool refresh bookkeeping, and CLI task mutations to the session that produced the event. It also ignores stale client socket closes after a newer socket has replaced the active connection for the same session.
Constraint: Desktop users can keep multiple sessions and tabs active at the same time.
Rejected: Serialize all desktop streaming through one active session | would hide the race instead of preserving multi-session behavior.
Confidence: high
Scope-risk: moderate
Directive: Do not reintroduce process-global chat/task pending state without session keys and multi-session regression tests.
Tested: cd desktop && bun run test -- src/stores/chatStore.test.ts src/stores/cliTaskStore.test.ts
Tested: bun test src/server/__tests__/websocket-handler.test.ts
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: bun run check:server
Tested: bun run check:coverage | changed-lines 100%, fails only existing agent-utils baseline
Tested: bun run verify | 8 passed, 1 failed on existing agent-utils coverage baseline, 2 skipped
Not-tested: Real desktop dual-window manual smoke.
Related: https://github.com/NanmiCoder/cc-haha/issues/302
Related: https://github.com/NanmiCoder/cc-haha/issues/303
The launch controls live directly under the composer, so they need to read as a compact context rail rather than a second card. The styling now uses a lighter surface, shorter controls, bounded flex sizing, and explicit hover titles for long repository and branch names. Git repo status is passed into the picker so a freshly opened Git project does not briefly look like a plain folder.
Constraint: Keep the existing session/composer layout untouched; only refine the context bar under the input.
Rejected: Redesign the empty session layout | user explicitly scoped this to the lower context bar.
Confidence: high
Scope-risk: narrow
Directive: Do not grow this rail vertically without browser-checking long repo and branch names.
Tested: cd desktop && bun run test -- --run src/components/shared/DirectoryPicker.test.tsx src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx
Tested: bun run check:desktop
Tested: agent-browser long repo and long branch screenshots at /tmp/cc-haha-repo-controls-polished-git-long-branch-selected.png and /tmp/cc-haha-repo-controls-polished-mobile-long.png
Not-tested: Native Tauri packaged window visual check
Scheduled task Run Now was still constructing a source checkout Bun command, so packaged desktop builds could try to execute src/entrypoints/cli.tsx from a missing or wrong root. Resolve the same launcher used by the desktop runtime first, and keep the source checkout fallback only for development.
Constraint: Desktop Release tasks must run through the configured sidecar and app root instead of assuming source files exist on disk
Rejected: Retain import.meta.dir path math with broader fallback | still bypasses packaged desktop launcher and repeats the issue root cause
Confidence: high
Scope-risk: narrow
Directive: Do not change scheduled task execution back to direct source CLI invocation without repeating desktop Run Now E2E
Tested: bun test src/server/__tests__/cron-scheduler-launcher.test.ts src/server/__tests__/cron-scheduler.test.ts
Tested: bun run check:server
Tested: bun run check:native
Tested: Desktop UI E2E via scheduled task page create task and Run Now with fake sidecar
Not-tested: Full packaged macOS .app click test
Empty ActiveSession launch controls now use both loaded chat messages and persisted session messageCount, so historical sessions do not briefly fall back into new-session launch mode while message history is still loading. Regression coverage also locks the first-message branch launch flows for direct checkout and isolated worktree sessions.
Constraint: Historical session lists already know messageCount before chat history finishes loading
Rejected: Wait for history fetch before rendering composer controls | it keeps the wrong new-session affordance visible during reopen
Confidence: high
Scope-risk: narrow
Directive: Branch/worktree launch controls are only for messageCount-zero sessions; reopened history must render repository context chips
Tested: bun run test -- --run src/components/chat/ChatInput.test.tsx src/pages/ActiveSession.test.tsx src/__tests__/pages.test.tsx
Tested: bun run check:desktop
Tested: agent-browser /tmp repo direct branch launch on /private/tmp/cc-haha-repo-e2e-IP9mkb produced session 9089ae96-5dd6-4ea6-b88e-376ab081ca24 on workDir /private/tmp/cc-haha-repo-e2e-IP9mkb
Tested: agent-browser /tmp repo isolated worktree launch produced session 9ec657bd-e503-48b3-b52f-36e210fc5d64 on workDir /private/tmp/cc-haha-repo-e2e-IP9mkb/.claude/worktrees/desktop-main-9ec657bd
Not-tested: root bun run verify; desktop surface was covered with check:desktop and live browser smoke
Empty active session tabs were still using the old directory-only picker, so sessions created from the sidebar lost the branch and worktree controls even when their workDir was a Git repository. Reuse the launch controls below the existing composer and replace only the empty session when the user changes repository launch options.
Constraint: Sidebar-created sessions land in ActiveSession, not the EmptySession landing view
Rejected: Reshape the whole session layout | the intended surface is only the controls below the composer
Confidence: high
Scope-risk: narrow
Directive: Keep repository launch controls shared between EmptySession and empty ActiveSession composer flows
Tested: bun run test -- --run src/__tests__/pages.test.tsx src/components/chat/ChatInput.test.tsx
Tested: bun run check:desktop
Tested: agent-browser sidebar new-session smoke at http://127.0.0.1:4790/ with screenshot /tmp/cc-haha-after-sidebar-new-session-controls.png
Not-tested: root bun run verify remains blocked by pre-existing root coverage gate outside desktop
The repository launch controls should extend the existing empty-session composer, not replace the session layout. This restores the previous mascot, title, composer sizing, textarea, and run button while keeping the project, branch, and worktree controls attached directly beneath the input.
Constraint: User requested the original session layout stay intact except for adding repository controls under the chat input
Rejected: Keep the Codex-style centered launcher rewrite | it changed too much of the existing session surface
Confidence: high
Scope-risk: narrow
Directive: Future changes to repository launch controls should treat EmptySession layout as fixed unless explicitly requested otherwise
Tested: cd desktop && bun run test -- --run src/components/shared/DirectoryPicker.test.tsx src/pages/EmptySession.test.tsx src/__tests__/pages.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: agent-browser screenshot /tmp/cc-haha-layout-restored-selected.png on localhost:4790
Not-tested: root bun run verify not rerun for this narrow layout correction
The empty-session launch surface needed to behave like a real coding-agent entry point: project, branch, and worktree choices are now visible together, and branch choices that cannot safely run in-place force an isolated worktree before session creation.
Constraint: Desktop launch flows must preserve repository safety when a target branch is dirty or checked out elsewhere
Rejected: Keep branch and worktree controls as detached chips | the selection state was easy to miss and visually inconsistent with the launcher
Confidence: high
Scope-risk: moderate
Directive: Do not remove the automatic worktree requirement without rechecking dirty and checked-out branch launches through the desktop UI
Tested: cd desktop && bun run test -- --run src/components/shared/DirectoryPicker.test.tsx src/pages/EmptySession.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: browser-driven desktop smoke on ports 4789/4790
Not-tested: bun run verify still has the pre-existing agent-utils coverage ratchet failure outside desktop
Diagnostics should capture fatal server failures without replacing the terminal stderr signal that developers rely on during local startup. Fatal uncaught exceptions now print their stack to stderr, persist the diagnostic event, and exit non-zero so repeated server starts expose port conflicts immediately.
Constraint: Startup diagnostics must still write sanitized runtime events for export bundles.
Rejected: Only document checking runtime-errors.log | developers should not lose the standard stderr failure path.
Confidence: high
Scope-risk: narrow
Directive: Do not install process-level exception handlers that swallow fatal startup errors without preserving stderr and non-zero exit behavior.
Tested: bun test src/server/__tests__/diagnostics-service.test.ts
Tested: bun run check:server
Tested: SERVER_PORT=3456 bun run src/server/index.ts returned exit=1 and printed the port-conflict stack while diagnostics recorded the event
Open session tabs can outlive the title value they were created with, while the sidebar and session detail view read from the refreshed session list. Refresh existing tab titles when a session tab is reopened and after session list refreshes so the top tab bar cannot keep a stale generated title.
Constraint: Sidebar filtering and session refresh should not leave already-open tabs with stale labels.
Rejected: Only rely on WebSocket title update events | restored tabs and already-open tabs can miss later session-list corrections.
Confidence: high
Scope-risk: narrow
Directive: Keep session tab titles derived from current session list data when available.
Tested: cd desktop && bun run test -- --run src/stores/tabStore.test.ts src/stores/sessionStore.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- --run
Tested: agent-browser smoke on http://127.0.0.1:1420/ confirmed top tabs match sidebar and session detail titles.
Notification clicks and repeated launches can enter the native app through OS-level activation paths, so the desktop runtime now registers Tauri's single-instance guard before sidecars or other plugins start. A second launch restores and focuses the existing main window instead of creating another app process.
Constraint: macOS and Windows launch paths must reuse the running desktop instance.
Rejected: Handle only notification click callbacks | does not cover Dock, shortcut, installer, or Windows relaunch paths.
Confidence: high
Scope-risk: narrow
Directive: Keep the single-instance plugin before sidecar startup so duplicate launches cannot create extra runtime processes.
Tested: SKIP_INSTALL=1 ./desktop/scripts/build-macos-arm64.sh
Tested: open -n built app twice; desktop_count stayed 1 with pid 69821
Tested: bun run check:native
Tested: cargo test --manifest-path desktop/src-tauri/Cargo.toml
Tested: cd desktop && bun run test -- desktopNotifications.test.ts
Tested: cd desktop && bun run lint
Session titles need to appear immediately, but the async AI title pass can return fenced or escaped JSON fragments. Keep the first-message title path fast, preserve optimistic client titles during background refresh, and only accept structured title responses that parse cleanly.
Constraint: Desktop sessions should stop showing Untitled/New Session after the first user turn.
Rejected: Accept short raw model output as a fallback | malformed JSON fragments are short enough to leak into the UI.
Confidence: high
Scope-risk: moderate
Directive: Do not loosen generated title parsing without regression coverage for fenced, escaped, and truncated JSON responses.
Tested: bun test src/server/__tests__/title-service.test.ts
Tested: bun run check:server
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts src/stores/sessionStore.test.ts src/pages/EmptySession.test.tsx
Tested: agent-browser smoke on http://127.0.0.1:1420/ confirmed sidebar and tab titles update without JSON fragments.
The desktop test package was able to reuse stale Tauri target output even after the frontend and sidecar were rebuilt, which made UI changes look missing after a build. The local macOS build script now clears that cache by default while retaining an opt-in incremental path.
The project selector also no longer repeats the active branch because branch and worktree are now first-class launch controls beside it.
Constraint: Local macOS test packages must prioritize freshness over incremental build speed
Rejected: Keep branch text in the project chip | duplicated the dedicated branch selector and made the launch row harder to scan
Confidence: high
Scope-risk: narrow
Directive: Keep branch selection behavior in RepositoryLaunchControls; DirectoryPicker should only identify the project
Tested: cd desktop && bun run test -- DirectoryPicker.test.tsx
Tested: cd desktop && bun run lint
Tested: SKIP_INSTALL=1 ./desktop/scripts/build-macos-arm64.sh
Tested: Opened built app and confirmed sidecar /health returned ok
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
Contributors and coding agents need one local command that both reports and enforces the quality contract. This change turns the PR gate into the shared verification entrypoint, adds path-selected local lanes, tightens coverage accounting around changed lines, and documents the repair loop in contributor and agent-facing guidance.
Constraint: Ordinary PR verification must stay non-live and runnable without provider credentials
Constraint: Coverage policy updates in this commit require maintainer approval before push/merge
Rejected: Keep quality guidance only in docs | agents need executable scripts and AGENTS.md instructions to follow the loop consistently
Confidence: high
Scope-risk: broad
Directive: Do not bypass `bun run verify` for production changes; fix failed lanes and coverage reports instead of lowering thresholds
Tested: bun run check:policy
Tested: ALLOW_CLI_CORE_CHANGE=1 ALLOW_COVERAGE_BASELINE_CHANGE=1 bun run verify
Not-tested: live provider baseline; no provider credentials were required for this non-live PR gate
The repository now has a measurable PR quality path instead of a loose set of
manual checks. Coverage, quarantine governance, provider smoke, desktop smoke,
and workflow wiring all produce durable reports that contributors and maintainers
can inspect without reconstructing terminal output.
This also fixes the desktop smoke current-runtime path so browser-driven smoke
runs use the desktop default active provider instead of forcing the official
current model, and records that runtime decision as an artifact.
Constraint: Default PR gates must remain non-live and contributor-safe while live model checks stay explicit.
Constraint: Release packaging is still GitHub Actions based, so release preflight must run before the build matrix.
Rejected: Make live provider or desktop smoke mandatory on every PR | secrets, quotas, and model availability are maintainer-controlled.
Rejected: Let PRs lower coverage baselines in the same change | base-branch ratchet comparison must remain authoritative.
Confidence: high
Scope-risk: moderate
Directive: Do not relax coverage or quarantine policy without a maintainer approval label and a fresh quality report.
Tested: ALLOW_CLI_CORE_CHANGE=1 ALLOW_COVERAGE_BASELINE_CHANGE=1 bun run quality:gate --mode pr
Tested: bun run quality:gate --mode baseline --allow-live --only provider-smoke:* --provider-model nvidia-custom:main:nvidia-custom-main --artifacts-dir /tmp/quality-gate-live-smoke
Tested: bun run quality:gate --mode baseline --allow-live --only desktop-smoke:* --provider-model current:current:current-runtime --artifacts-dir /tmp/quality-gate-desktop-smoke-fixed
Tested: git diff --check
Not-tested: Full live release mode with multiple providers in hosted CI; provider credentials and quota remain maintainer-controlled.
The branch label can render before the parent composer receives the launch-ready callback, especially after the main branch notification UI changes added more render work. The test now waits for the same enabled Run state a user can interact with before submitting.
Constraint: The product intentionally disables Run while repository context is still resolving
Rejected: Loosen the create-session assertion | the assertion is still the behavior this flow must guarantee
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/pages/EmptySession.test.tsx --run
This brings the desktop branch/worktree launch flow into the local main checkout after validation in the feature worktree. The merge keeps the feature commit intact while preserving the newer main history.
Constraint: Local main already contains newer notification routing work
Confidence: high
Scope-risk: moderate
Tested: ALLOW_CLI_CORE_CHANGE=1 bun run quality:pr in the feature worktree before merge
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
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
Desktop notifications now carry a narrow target payload so clicking a permission, completion, or scheduled-task notification can reopen the matching tab and reconnect the session. macOS uses the native notification bridge for tap callbacks, while the shared desktop notification layer also accepts plugin action payloads where the platform exposes them.
Constraint: Notification clicks need to activate existing desktop tabs without adding a new navigation state channel.
Rejected: Store only notification ids and infer the active session later | stale notifications would point at the wrong session after tab changes.
Confidence: high
Scope-risk: moderate
Directive: Keep notification targets serializable and versioned through desktopNotifications before adding new target types.
Tested: bun run quality:pr with ALLOW_CLI_CORE_CHANGE=1; desktop unit tests; server tests; native sidecar build and cargo check; docs build.
Not-tested: Packaged Windows toast activation runtime; current fallback depends on Tauri notification action delivery.
External builds were inheriting internal feature-gate defaults, so Explore,
Plan, and verification disappeared from the active agent registry. Keep the
internal ant experiment behavior gated, but make public builds default to the
full workflow agent set while preserving the SDK opt-out path.
Constraint: Internal ant builds still rely on GrowthBook rollout gates.
Rejected: Force-enable all users unconditionally | would bypass internal experiment controls.
Confidence: high
Scope-risk: narrow
Directive: Do not move public built-in agents behind internal-only feature flags without verifying desktop /api/agents and real Agent subagent orchestration.
Tested: bun test src/tools/AgentTool/builtInAgents.test.ts
Tested: git diff --check
Not-tested: Full quality gate in this commit step; earlier quality:pr is blocked by cli-core approval policy.
Custom providers can point at user-defined model IDs that the CLI cannot
classify by built-in model tables. Declare the existing tier capability
overrides for custom provider roles so explicit thinking and effort settings
survive request construction.
Constraint: Issue #285 reports local proxy logs dropping user-defined model thinking and effort as passthrough with no config.
Rejected: Disable model capability checks globally | would relax behavior for built-in presets and non-custom providers.
Confidence: high
Scope-risk: narrow
Directive: Keep this scoped to custom providers unless each preset has explicit capability semantics.
Tested: bun test src/server/__tests__/providers.test.ts src/server/__tests__/provider-presets.test.ts src/utils/__tests__/thinking.test.ts
Tested: bun run check:server
Tested: bun run quality:pr
Tested: bun run quality:gate --mode baseline --allow-live --provider-model custom:main:custom-main
Tested: agent-browser custom provider UI smoke with request log showing thinking adaptive and effort max
Related: https://github.com/NanmiCoder/cc-haha/issues/285
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
Local OpenAI-compatible proxies can return function arguments as parsed objects instead of JSON strings. Preserve object-shaped arguments directly and serialize them for streaming deltas so Write, Bash, and Edit tool calls keep the fields required by Anthropic tool validation.
Constraint: Local proxy implementations such as OneAPI/NewAPI may not preserve OpenAI's string-only arguments shape.
Rejected: Normalize every upstream response through JSON.stringify before parsing | loses already-valid object identity and leaves streaming object deltas vulnerable to [object Object].
Confidence: high
Scope-risk: narrow
Directive: Do not assume function.arguments is always a string on provider responses or stream deltas.
Tested: bun test src/server/__tests__/proxy-streaming.test.ts src/server/__tests__/proxy-transform.test.ts
Tested: bun run check:server
Tested: bun run check:native
Tested: bun run quality:pr
Tested: agent-browser with real Ollama qwen3:4b provider for Write/Bash/Edit and streaming Write
Not-tested: Real Windows OneAPI/NewAPI instance from the issue reporters was not available locally.
Linux release builds use MenuBuilder for the shared tray menu, while MenuItemBuilder and SubmenuBuilder are only used in the macOS native menu bar. Gate only the macOS-only imports so Linux builds no longer warn about them and still compile the shared tray path.
Constraint: Linux builds compile the shared tray setup path and require MenuBuilder
Constraint: MenuItemBuilder and SubmenuBuilder are only referenced inside cfg(target_os = "macos")
Rejected: Gate the whole tauri::menu import on macOS | Linux still needs MenuBuilder for tray menu construction
Confidence: high
Scope-risk: narrow
Directive: Do not gate MenuBuilder by platform unless setup_system_tray is also made platform-specific
Tested: cargo fmt --check
Tested: git diff --check
Not-tested: Local Linux cargo check beyond system GTK/WebKit pkg-config setup; GitHub release Linux job is the verification surface
Release logs showed two warning classes after the packaging fixes: Linux builds warned about macOS-only menu imports, and tauri-action reported an unknown releaseAssetNamePattern input. Gate the native menu imports to macOS and use the action's supported assetNamePattern input so release logs stay clean and asset naming is actually applied.
Constraint: Linux release builds compile the shared Tauri lib without macOS-only menu usage
Constraint: tauri-action v0 accepts assetNamePattern, not releaseAssetNamePattern
Rejected: Ignore warning-only output | release candidates should not hide real signal behind avoidable warnings
Confidence: high
Scope-risk: narrow
Directive: Keep workflow input names aligned with tauri-action's published action schema
Tested: cargo fmt --check
Tested: cargo check --target aarch64-apple-darwin
Tested: git diff --check
Not-tested: Full GitHub release workflow after retag before pushing
The release workflow shells out through tauri-action but did not pass Tauri's --ci flag, while the desktop development workflow already uses it for the same app,dmg packaging path. Align release builds with the non-interactive CI mode so macOS DMG packaging does not depend on interactive defaults during tag builds.
Constraint: Release assets are produced remotely by .github/workflows/release-desktop.yml on tag push
Constraint: The dev desktop workflow already validates macOS app,dmg packaging through tauri build --ci
Rejected: Drop macOS DMG from release assets | would reduce the expected user-facing installer surface for 0.2.1
Confidence: medium
Scope-risk: narrow
Directive: Keep release and dev desktop Tauri build flags aligned when changing bundle behavior
Tested: bunx tauri build --target aarch64-apple-darwin --bundles app,dmg --ci --config /tmp/tauri-no-updater.json
Tested: git diff --check
Not-tested: Full GitHub release workflow after retag before pushing
The macOS notification bridge is compiled by build.rs outside Cargo's Rust target handling. On Apple Silicon, clang defaulted macos_notifications.o to arm64 even when Cargo was building x86_64, so the x64 release job ignored the object and missed the exported notification symbols.
Constraint: The ObjC bridge is linked manually through cargo:rustc-link-arg
Constraint: Release workflow builds both aarch64-apple-darwin and x86_64-apple-darwin from macOS runners
Rejected: Rely on clang default architecture | Apple Silicon defaults arm64 and breaks x86_64 linking
Confidence: high
Scope-risk: narrow
Directive: Keep native ObjC bridge compilation aligned with Cargo TARGET when adding platform bridge files
Tested: cargo fmt --check
Tested: git diff --check
Tested: cargo check --target aarch64-apple-darwin
Tested: cargo build --target x86_64-apple-darwin --release
Tested: file target/x86_64-apple-darwin/release/.../macos_notifications.o reports Mach-O 64-bit object x86_64
Not-tested: Full GitHub release workflow after retag before pushing
This records the user-facing 0.2.1 release notes and aligns the desktop package, Tauri config, Cargo manifest, and lockfile package version so the tag-triggered release workflow can publish the intended build.
Constraint: GitHub Release body is sourced from release-notes/v0.2.1.md in the tagged commit
Constraint: Keep provider and OpenAI login details weak in the release narrative because this release is centered on IM, notifications, context usage, and desktop stability
Rejected: Run scripts/release.ts directly | it creates a generic commit message and tag before review of the release narrative
Confidence: high
Scope-risk: narrow
Directive: Do not tag v0.2.1 from a commit that lacks release-notes/v0.2.1.md or mismatched desktop version files
Tested: bun run scripts/release.ts 0.2.1 --dry
Tested: cargo check
Tested: git diff --check
Not-tested: Live release gate with real provider credentials in this commit step
The README entry points still described IM support as Telegram and Feishu only, while the product and docs now cover WeChat and DingTalk as first-class adapters. Update the Chinese and English READMEs so the first screen, desktop preview caption, feature list, and documentation table point readers to the current IM guide.
Constraint: English IM docs do not have a separate docs/en/im tree yet, so both README variants link to the existing docs/im guide.
Rejected: Keep the old Channel System links | they point readers at historical architecture instead of the current setup docs.
Confidence: high
Scope-risk: narrow
Tested: bun run check:docs
Not-tested: Live rendered README preview on GitHub
Telegram polling stopped consuming updates when all IM adapters were loaded into the same desktop sidecar process. Launch each IM platform as its own sidecar process so Telegram keeps an isolated long-polling consumer while Feishu, WeChat, and DingTalk manage their own SDK loops independently.
Constraint: Telegram Bot API long polling needs one healthy consumer per token and should not share lifecycle with other IM SDKs
Rejected: Keep one combined adapter process | reproduced pending Telegram updates until Telegram was launched alone
Confidence: high
Scope-risk: moderate
Directive: Keep IM adapter process ownership isolated unless cross-platform lifecycle tests prove a merged process is safe
Tested: cargo fmt --check
Tested: cargo check
Tested: bun run check:native
Tested: ./desktop/scripts/build-macos-arm64.sh
Tested: Computer Use launched built app and sent Telegram smoke message with pending_update_count=0
Not-tested: Automated Feishu, WeChat, or DingTalk live message delivery after process split
Telegram long polling can be consumed or disrupted by orphaned adapter sidecar processes left behind after desktop restarts or IM settings refreshes. Kill stale Unix claude-sidecar adapters processes before launching a new managed adapter sidecar so only one Telegram consumer remains active.
Constraint: Telegram Bot API long polling should have a single active consumer for a token
Rejected: Only restart the current tracked child | orphaned sidecars with PPID 1 are outside the tracked Tauri child handle
Confidence: high
Scope-risk: narrow
Directive: Keep adapter sidecar startup single-instance across platforms when adding new IM adapters
Tested: cargo fmt --check
Tested: cargo check
Tested: bun run check:native
Tested: git diff --check
Not-tested: Packaged macOS app relaunch with a real stale sidecar during automated test
DingTalk keeps AI card placement tied to the original card delivery time, so a card created before a permission request can later be updated with the final answer above the permission card. Finish and drop the active stream when permission interrupts a turn so authorized output creates a new card below the prompt.
Constraint: DingTalk does not reorder an existing delivered AI card when its streaming content is updated
Rejected: Reuse the pre-permission AI card after approval | final answers can remain visually above the permission request
Confidence: high
Scope-risk: narrow
Directive: Keep permission_request as a lifecycle boundary for DingTalk AI card streams
Tested: cd adapters && bun test dingtalk/__tests__/stream-state.test.ts dingtalk/__tests__/ai-card.test.ts
Tested: cd adapters && bunx tsc --noEmit
Tested: bun run check:adapters
Tested: git diff --check
Not-tested: Live DingTalk account end-to-end permission approval
The WeChat and DingTalk IM docs were behind the actual desktop adapter flow, especially around QR binding versus user pairing and how users discover slash commands when no platform menu exists. This records the setup path with screenshots and nudges paired users toward /help from the bot itself.
Constraint: DingTalk does not expose the same menu setup path documented for Feishu, so command discovery must work through chat text.
Rejected: Document commands only in VitePress | users may be on mobile when they need the commands.
Confidence: high
Scope-risk: narrow
Directive: Keep IM command docs aligned with adapters/common/format.ts when adding commands.
Tested: cd adapters && bun test common/ dingtalk/ wechat/
Tested: cd adapters && bunx tsc --noEmit
Tested: bun run check:docs
Tested: bun run check:adapters
Not-tested: Live WeChat or DingTalk message delivery with real platform accounts
The worktree adds short mobile replies for IM permission prompts while preserving existing requestId-based authorization semantics. Main already had a separate desktop reply visibility fix, so this merge brings the adapter authorization work across without flattening either line of history.
Constraint: Main worktree had unrelated local documentation and adapter edits, so they were stashed before merging
Confidence: high
Scope-risk: moderate
Directive: Keep requestId validation as the source of truth for all IM permission responses
Tested: cd adapters && bun test common/ telegram/ feishu/ dingtalk/ wechat/
Tested: cd adapters && bunx tsc --noEmit
Tested: bun run check:adapters
Not-tested: Post-merge full PR quality gate; earlier gate is blocked by repository policy without allow-cli-core-change approval
Text-only IM channels forced users to copy long permission request IDs from mobile chat, which made approval slow and error-prone. This keeps the requestId-based authorization protocol intact while adding short replies for the single-pending-request case and preserving full command fallbacks for ambiguous cases.
Constraint: IM authorization must still resolve through the existing requestId permission_response path
Rejected: Replace request IDs with global numeric IDs | concurrent permission prompts would make numeric IDs ambiguous across chats
Confidence: high
Scope-risk: moderate
Directive: Do not allow short numeric replies when more than one permission request is pending in the chat
Tested: cd adapters && bun test common/ telegram/ feishu/ dingtalk/ wechat/
Tested: cd adapters && bunx tsc --noEmit
Tested: bun run check:adapters
Not-tested: Real WeChat or Telegram account end-to-end message delivery
Desktop users already receive native notifications for permission prompts, but long-running turns can finish with only a final Markdown reply while the app is out of focus. The completion path now treats a non-empty assistant reply as a notifiable terminal event and reuses the existing desktop notification settings, dedupe, and cooldown behavior.
Constraint: Notification permission must still be controlled by the existing desktopNotificationsEnabled setting and native OS authorization.
Rejected: Send from status idle events | idle is also used by prewarm, clear, stop, and other non-reply transitions.
Rejected: Notify on every message_complete | empty completions and post-stop tail completions would create noisy false positives.
Confidence: high
Scope-risk: narrow
Directive: Keep completion notifications tied to finalized assistant text, not generic idle/status transitions.
Tested: bun run test -- src/stores/chatStore.test.ts
Tested: bun run check:desktop
Not-tested: Manual macOS notification center click-through behavior
Desktop notifications were enabled by default and depended on the Tauri notification plugin for both permission state and delivery. On macOS the plugin reports desktop permission as granted and can hide foreground delivery failures, so authorization prompts could appear in the app without a real system notification.
This moves macOS permission and delivery through a native UserNotifications bridge, keeps notifications opt-in by default, and sends a test notification after successful authorization.
Constraint: Tauri notification plugin desktop permission state is always granted.
Constraint: macOS foreground delivery needs explicit native presentation handling.
Rejected: Keep using the plugin with retries | it still cannot report real macOS permission or delivery failures.
Confidence: high
Scope-risk: moderate
Directive: Do not remove the macOS bridge without verifying foreground banners from an installed app bundle.
Tested: cd desktop && bun run test --run src/lib/desktopNotifications.test.ts src/stores/settingsStore.test.ts src/__tests__/generalSettings.test.tsx src/stores/chatStore.test.ts
Tested: bun run check:desktop
Tested: bun run check:native
Tested: SKIP_INSTALL=1 ./scripts/build-macos-arm64.sh
Tested: codesign --verify --deep --strict --verbose=2 desktop/build-artifacts/macos-arm64/Claude Code Haha.app
Tested: hdiutil verify desktop/build-artifacts/macos-arm64/Claude Code Haha_0.2.0_aarch64.dmg
Tested: Computer Use macOS Settings and Bash authorization notifications, confirmed usernoted displayed banners.
Not-tested: Windows toast delivery on a real installed Windows build.
Desktop context inspection could send get_context_usage while a prewarmed CLI process existed but before the SDK socket was connected. That first control request sat in the pending outbound queue and could time out, leaving the composer spinner or a misleading empty-state context display. The server now waits for the SDK socket before sending control requests, and the UI renders the actual initial context snapshot instead of masking empty active-session data.
Constraint: CLI /context and desktop get_context_usage must stay on the shared collectContextData/analyzeContextUsage path
Rejected: Hide zero-token active snapshots in the component | that masks backend timing bugs and prevents showing real initial system/tool context
Confidence: high
Scope-risk: narrow
Directive: Do not queue control requests before the SDK socket is connected; let user messages keep the pending outbound behavior
Tested: bun test src/server/__tests__/conversations.test.ts --test-name-pattern "not queue control|context-only|prewarmed empty session|structured session inspection"
Tested: cd desktop && bun run test -- pages.test.tsx --test-name-pattern "EmptySession shows draft context|first-paint spinner|empty live session|live context usage|runtime model"
Tested: bun run check:server
Tested: cd desktop && bun run lint
Tested: real dev API prewarmed empty session returned 31,190 / 262,144 (12%) on first context-only inspection and 16ms on repeat
Tested: browser at http://127.0.0.1:5174/ showed 上下文用量 12% for an empty active session