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
WeChat development turns were reaching the local CLI, but the adapter treated iLink business failures as successful sends and only emitted a short-lived typing indicator. The adapter now validates sendmessage/sendtyping ret codes, retries text replies without stale context tokens, keeps typing alive during long-running tool work, and surfaces queue failures back to the chat.
Constraint: WeChat uses separate sendmessage and sendtyping APIs with business-level ret codes inside HTTP 200 responses.
Rejected: Only fixing the typing indicator | the transcript showed the agent completed the task, so the reply delivery path also needed hardening.
Confidence: high
Scope-risk: narrow
Directive: Do not treat WeChat HTTP 200 responses as successful until the iLink ret/errcode body has been checked.
Tested: bun test adapters/wechat/__tests__/protocol.test.ts adapters/wechat/__tests__/typing.test.ts
Tested: cd adapters && bunx tsc --noEmit
Tested: bun run check:adapters
Tested: real bound WeChat getconfig, typing, text send, cancel typing smoke
Not-tested: Full inbound WeChat user-message loop without a fresh user-triggered message
The live desktop context path already routes through the CLI get_context_usage control request and the shared analyzeContextUsage implementation. The offline transcript fallback was still using its own prompt-token-only estimate, so disconnected sessions could report a lower context total than the live /context path. The fallback now reuses the same current-context total helper and includes output tokens as next-turn context.
Constraint: Desktop must still show context estimates when the CLI process is not running
Rejected: Keep transcript fallback prompt-only | it diverges from the live CLI /context total and can drop after responses
Confidence: high
Scope-risk: narrow
Directive: Transcript context fallback must stay aligned with calculateCurrentContextTokenTotal
Tested: bun test src/server/__tests__/conversations.test.ts --test-name-pattern "context-only|structured session inspection|reconstruct Sonnet 4.6 transcript usage"
Tested: bun test src/utils/__tests__/context.test.ts
Tested: bun run check:server
Context inspection was switching from the local context estimate to the latest
provider usage total once a response completed. That provider total omitted the
assistant output tokens, even though those tokens become part of the next turn's
context, so the desktop meter could fall from 11% to 10% after work finished.
The context total now includes output tokens and keeps the local estimate as a
lower bound.
Constraint: Provider usage and local context estimates use different token accounting paths
Rejected: Trust provider input tokens alone | it omits the latest assistant output from the next-turn context
Confidence: high
Scope-risk: narrow
Directive: Context usage totals must represent the next-turn conversation context, not billing-only input tokens
Tested: bun test src/utils/__tests__/context.test.ts
Tested: bun run check:server
A newly opened desktop session can return a live context snapshot before any
prompt content has been counted. When that snapshot is all zeroes, rendering it
as 0% makes the first real context update look like an unexplained jump. The
composer now keeps that empty live snapshot in the pending state while still
using its model metadata.
Constraint: Live context snapshots can be initialized before any user-visible context exists
Rejected: Render 0% for zero-token live snapshots | it repeats the misleading empty-session state
Confidence: high
Scope-risk: narrow
Directive: Do not show numeric context usage for empty zero-token sessions
Tested: cd desktop && bun run test -- pages.test.tsx
Tested: cd desktop && bun run lint
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
Bring the detached worktree terminal improvements into the local main line so desktop sessions can open a bottom terminal in the current project directory and still promote it to a full tab when needed.
Constraint: Local main already contains additional desktop fixes after the worktree base
Confidence: high
Scope-risk: moderate
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: bun run quality:pr is blocked by existing branch policy requiring allow-cli-core-change approval
Desktop terminal access should behave like an IDE: active project sessions open a bottom panel in the session working directory while keeping a full terminal tab available for dedicated use. The panel has constrained resizing and cleanup so session tab state remains isolated, and terminal guidance points users to the bundled claude-haha command for extension setup.
Constraint: Desktop bundles the user-facing CLI as claude-haha while claude-sidecar remains internal
Rejected: Always opening a standalone terminal tab | loses the current project context and diverges from common IDE behavior
Rejected: Exposing claude-sidecar in terminal guidance | it is an internal launcher, not the supportable user command
Confidence: high
Scope-risk: moderate
Directive: Keep bottom terminals keyed by session id and pass session workDir/projectPath into spawned terminals
Tested: bun run check:desktop
Tested: git diff --check
Tested: Computer Use E2E against built macOS app during implementation
Not-tested: bun run quality:pr is blocked by existing branch policy requiring allow-cli-core-change approval
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
Bring the runtime diagnostics worktree back onto local main while preserving the existing local main commits for provider context windows and IM localization.
Constraint: Preserve both independent local main commits and the diagnostics worktree commit.
Confidence: high
Scope-risk: moderate
Directive: Keep diagnostics capture non-blocking and sanitized when modifying this area again.
Tested: merged cleanly on local main after prior check:server, check:desktop, and agent-browser diagnostics export verification
Diagnostics exports previously preserved only compact summaries, which made user-uploaded issue bundles hard to debug. This records richer sanitized details from server, CLI, SDK, browser, React, and desktop API failure paths while keeping request bodies and secrets out of client-side reports.
Constraint: Diagnostic bundles must be useful for GitHub issues without including chat contents, file contents, full environments, or API keys.
Rejected: Only expand the Settings UI summary | the exported bundle would still miss hidden runtime failures.
Confidence: high
Scope-risk: moderate
Directive: Keep diagnostics write paths best-effort and non-blocking; do not add request-body capture without a redaction review.
Tested: bun test src/server/__tests__/diagnostics-service.test.ts src/server/__tests__/conversation-service.test.ts
Tested: cd desktop && bun run test src/api/client.test.ts src/__tests__/diagnosticsSettings.test.tsx --run
Tested: bun run check:server
Tested: bun run check:desktop
Tested: agent-browser E2E on ports 37652/41752 captured client_unhandled_rejection and exported a redacted tar.gz bundle
The featured Anthropic-compatible provider presets now advertise their Sonnet models as one-million-token windows so the desktop context form and runtime env match the requested provider limits. Regression assertions cover both preset display and runtime env generation.
Constraint: Only the two featured provider presets requested by the desktop settings flow should change.
Rejected: Change global Sonnet built-in default | would alter unrelated providers and custom models.
Confidence: high
Scope-risk: narrow
Directive: Do not lower these preset windows without checking provider-specific limits and the desktop settings preview.
Tested: bun test src/server/__tests__/provider-presets.test.ts src/server/__tests__/providers.test.ts
Tested: bun run check:server
Tested: agent-browser UI verification on http://127.0.0.1:49174 with backend http://127.0.0.1:49173
Not-tested: Live API requests against 接口AI or 胜算云.
The Chinese settings UI should use localized platform names directly instead of appending English names in parentheses, which made the tabs look visually awkward.
Constraint: Only the Chinese locale needed this presentation adjustment
Confidence: high
Scope-risk: narrow
Reversibility: clean
Tested: cd desktop && bunx vitest run src/i18n/index.test.tsx
Not-tested: Full desktop quality gate, because this is a two-string locale-only change
This captures the pending worktree fixes before applying them to the
current local main. The changes tighten IM adapter path and credential
handling, preserve retry behavior for failed desktop notifications, and
make Azure/OpenAI provider auth and stop reasons reflect actual runtime
state.
Constraint: Worktree was detached from an older local main with pending uncommitted fixes
Rejected: Merge the detached HEAD directly | would also replay unrelated stale history
Rejected: Leave notification dedupe as fire-and-forget | failed sends consumed retry keys
Confidence: high
Scope-risk: broad
Directive: Keep adapter absolute-path matching constrained to configured work roots
Tested: git diff --check
Not-tested: full quality gate before local main integration
Remote main already contains PR #217. Local main had provider context-window and auth-strategy work, so this merge keeps both context resolvers and reconciles the desktop Bun lockfile with the merged desktop package manifest. The accidental remote .nvimlog artifact is excluded from the merge result.
Constraint: Local main was 15 commits ahead and 8 commits behind origin/main after PR #217 was merged remotely
Rejected: Fast-forward pull | local main had unpushed commits
Rejected: Keep remote desktop lockfile verbatim | failed bun install --frozen-lockfile against the merged package.json
Confidence: high
Scope-risk: broad
Directive: Keep provider-configured context windows ahead of OpenAI OAuth model windows so explicit user/provider settings win
Tested: bun install --frozen-lockfile; cd desktop && bun install --frozen-lockfile; git diff --check; bun run check:server; bun run check:desktop; ALLOW_CLI_CORE_CHANGE=1 bun run quality:pr
Not-tested: live provider OAuth login with a real OpenAI account
PR 217 combined OpenAI OAuth model options with env-configured provider models, but the picker path returned before appending third-party model aliases. The desktop workspace test also exercised Prism highlighting for every expanded line, which was costly enough to time out on CI runners. The docs job did not need the root React Vite plugin; the desktop package owns that dependency.\n\nConstraint: PR Quality must pass server, desktop, and docs jobs before merge.\nRejected: Increase Vitest timeout | keeps the expensive render path and hides the CI regression.\nRejected: Add root Vite 8 peer dependencies | root only builds VitePress docs; React Vite plugin belongs in desktop/.\nConfidence: high\nScope-risk: narrow\nTested: npm ci --ignore-scripts; npm run docs:build; bun test src/server/__tests__/settings.test.ts src/server/__tests__/conversation-service.test.ts; bun run check:server; bun run check:desktop\nNot-tested: live provider OAuth login
Provider model context windows now feed the composer-level context indicator, so users can see current usage without opening slash panels. The indicator refreshes when the session runtime model changes, keeps a stable loading placeholder, and marks estimate fallback data during reconnect or compaction windows.
Constraint: The desktop UI must use the real session inspection API and session-scoped runtime selection instead of mocked or static context values.
Rejected: Keep context details only in /context or /status | the user explicitly wanted an always-visible composer indicator.
Rejected: Reuse stale context after model switches | different provider windows make identical percentages misleading.
Confidence: high
Scope-risk: moderate
Directive: Do not remove the runtimeSelectionKey refresh path without real multi-provider browser verification.
Tested: cd desktop && bun run test -- pages.test.tsx
Tested: bun run check:desktop
Tested: KEEP_ARTIFACTS=1 desktop/scripts/e2e-context-usage-live-agent-browser.sh
Not-tested: Live browser E2E with every configured provider in the committed script; separate agent-browser sweep verified four providers locally.
Desktop notification permission can appear granted while macOS still suppresses a foreground banner, so blocking permission prompts now also request OS-level window attention. The notification sender keeps the native notification path, but prompt callers can ask the window manager for attention when silence would block user progress.
The desktop package also declares the existing qrcode runtime dependency used by AdapterSettings so production desktop builds can resolve that import.
Constraint: Tauri window attention requires an explicit capability grant in packaged builds.
Constraint: macOS may suppress foreground notification banners even when notification APIs report success.
Rejected: Replace native notifications with a custom in-app sound or overlay | the notification feature should stay OS-owned and already has an in-app permission card.
Confidence: high
Scope-risk: narrow
Directive: Keep requestAttention limited to blocking prompts unless another flow truly needs persistent OS attention.
Tested: bun run test -- src/lib/desktopNotifications.test.ts src/stores/chatStore.test.ts
Tested: bun run check:desktop
Tested: bun run check:native
Tested: git diff --check
Not-tested: bun run quality:pr | blocked by existing branch-wide CLI core policy requiring allow-cli-core-change approval
DingTalk can receive interactive card callbacks, but the card UI still depends on a published template, so the adapter now supports a template-backed card path with text commands as the reliable fallback. Telegram and WeChat use the same allow-once, allow-always, and deny semantics so manual authorization behaves the same across platforms.
Constraint: DingTalk button rendering requires an operator-provided interactive card template id
Rejected: Treat the existing AI streaming card template as a permission card | it cannot guarantee visible action buttons
Confidence: high
Scope-risk: moderate
Directive: Do not remove the text approval fallback unless DingTalk card template provisioning is guaranteed
Tested: bun test adapters/common/__tests__/permission.test.ts adapters/dingtalk/__tests__/permission-card.test.ts adapters/telegram/__tests__/telegram.test.ts adapters/common/__tests__/config.test.ts src/server/__tests__/adapters.test.ts
Tested: bunx tsc -p adapters/tsconfig.json --noEmit
Tested: bun run check:adapters
Tested: cd desktop && bun run build
Tested: bun run check:server
Tested: bun run check:docs
Tested: git diff --check
Not-tested: bun run quality:pr is blocked by existing CLI core approval policy; see artifacts/quality-runs/2026-05-03T13-19-56-232Z/report.md
WeChat and DingTalk were using different pairing, attachment, and response
state paths, which made the new IM channels behave differently from Feishu
and Telegram. Align the shared pairing model, wire inbound media into the
existing attachment bridge, and map platform response capabilities to their
real APIs: WeChat block streaming plus typing, DingTalk AI Card streaming.
Constraint: WeChat iLink exposes typing and block streaming, but no editable message/card streaming API
Constraint: DingTalk streaming depends on the AI Card create/deliver/stream/finalize lifecycle
Rejected: Fake DingTalk typing with standalone markdown | it would add chat noise instead of platform state
Rejected: Auto-pair WeChat after QR login | it bypasses the shared IM pairing model
Confidence: high
Scope-risk: moderate
Directive: Keep WeChat streaming as block-send unless iLink adds editable messages; keep DingTalk streaming on AI Card APIs
Tested: bun run check:adapters; bun run check:server; cd desktop && bun run test src/stores/adapterStore.test.ts; cd desktop && bun run build; bunx tsc -p adapters/tsconfig.json --noEmit; git diff --check
Not-tested: Live WeChat and DingTalk platform smoke with real production credentials
DingTalk uses QR registration to store client credentials, then reuses the existing IM pairing and session bridge. The merged main implementation keeps the existing WeChat QR binding path intact while adding DingTalk as a peer IM platform. The default IM workdir now falls back to the local user working directory so a newly bound chat can start immediately even when recent-project history is empty.
Constraint: Local main already carries WeChat IM, so the merge keeps WeChat config, QR binding, sidecar args, and unbind behavior intact while adding DingTalk.
Rejected: Keep empty defaultProjectDir as project-picker-only | newly bound IM users can hit a dead end with no recent projects.
Confidence: high
Scope-risk: moderate
Directive: Keep IM platform unions synchronized across config, pairing, sidecar args, desktop settings, and docs.
Tested: bun test common/ dingtalk/ wechat/; bun test src/server/__tests__/adapters.test.ts; cd adapters && bunx tsc --noEmit; bun run check:policy; bun run check:server; bun run check:desktop; bun run check:adapters; bun run check:native; bun run check:docs
Not-tested: quality:pr full gate was blocked by existing local main CLI-core diff requiring allow-cli-core-change maintainer approval; live post-fix DingTalk second-message delivery was not repeated after the merge.