157 Commits

Author SHA1 Message Date
程序员阿江(Relakkes)
82c454e4dc Merge commit '5c5255b4926b0c5539f585950ffe9a8a1181544a' 2026-05-05 20:33:33 +08:00
程序员阿江(Relakkes)
5c5255b492 fix: make macOS notification delivery verifiable
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.
2026-05-05 20:33:12 +08:00
程序员阿江(Relakkes)
71f851d60a fix: clear DingTalk bot credentials through explicit unbind
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
2026-05-05 19:30:43 +08:00
程序员阿江(Relakkes)
432a58d725 Merge project terminal workflow into local main
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
2026-05-05 19:05:53 +08:00
程序员阿江(Relakkes)
35f9f0d0f8 feat: make project terminals open where users work
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
2026-05-05 19:05:36 +08:00
程序员阿江(Relakkes)
e4f9825bc1 fix: avoid misleading context loading on empty desktop sessions
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
2026-05-05 18:25:53 +08:00
程序员阿江(Relakkes)
2459488703 Harden provider auth, adapter paths, and notification retries
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
2026-05-04 21:37:30 +08:00
程序员阿江(Relakkes)
0a82efdcd9 fix: keep desktop permission prompts visible
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
2026-05-04 19:05:04 +08:00
程序员阿江(Relakkes)
2ca5228171 feat: make IM adapter behavior consistent
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
2026-05-03 21:08:12 +08:00
程序员阿江(Relakkes)
6d9aa98022 Support DingTalk IM without blocking on project history
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.
2026-05-03 17:56:22 +08:00
程序员阿江(Relakkes)
99b343d6a1 Merge branch 'feat/wechat-im' 2026-05-03 17:42:31 +08:00
程序员阿江(Relakkes)
eb00c0d501 Merge provider auth strategy into main
Bring the provider context-window, auth-secret masking, and explicit auth-strategy worktree commits onto main after local verification. The merge keeps main's notification and docs history intact while preserving the provider fixes as separate commits.

Constraint: main has diverged with local desktop notification and docs commits, so this cannot be fast-forwarded safely.

Confidence: high

Scope-risk: moderate

Directive: Keep provider auth behavior configured through preset authStrategy rather than implicit env-name guesses.

Tested: ALLOW_CLI_CORE_CHANGE=1 bun run quality:pr

Tested: bun run quality:gate --mode baseline --allow-live --provider-model minimax:main:minimax-main

# Conflicts:
#	desktop/src/pages/Settings.tsx
2026-05-03 17:40:18 +08:00
程序员阿江(Relakkes)
a627bb19f2 feat: support WeChat as a first-class IM channel
WeChat needs a QR-paired path instead of bot-token setup, so the adapter layer now includes the iLink protocol calls, desktop pairing UI, server-side bind/unbind APIs, and shared IM command behavior. Empty project history falls back to the user's default work directory so mobile /new works without pre-opening a desktop project.

Constraint: Tencent iLink login returns a URL that the desktop UI must render as a QR image locally
Constraint: IM adapters should keep /new, /projects, status, permission, and default workdir behavior consistent across WeChat, Feishu, and Telegram
Rejected: Require users to paste absolute project paths for first WeChat sessions | mobile onboarding should work from the default user working directory
Confidence: high
Scope-risk: moderate
Directive: Do not change WeChat polling back to overlapping intervals; getupdates is a long-poll endpoint and must remain serialized
Tested: Real WeChat QR scan, inbound /status, outbound reply, and unbind E2E
Tested: bun run check:adapters
Tested: bun run quality:pr
Not-tested: Re-scan live WeChat after the default-workdir fallback tweak; covered by adapter config tests and PR gate
2026-05-03 17:38:08 +08:00
程序员阿江(Relakkes)
a43460f28d fix: honor provider auth env strategy
Provider presets now carry an explicit auth strategy so Anthropic-compatible services can choose the right Claude Code environment shape instead of relying on one global variable. The desktop editor exposes the same strategy, keeps settings.json previews in sync, and applies the strategy to connection tests as well as runtime env sync.

Constraint: Third-party Anthropic-compatible providers do not agree on ANTHROPIC_API_KEY versus ANTHROPIC_AUTH_TOKEN.

Rejected: Force all third-party providers onto one env var | breaks providers that require bearer auth, dual variables, or dummy local auth.

Confidence: high

Scope-risk: moderate

Directive: Add new provider presets with explicit authStrategy; do not infer auth behavior from model names alone.

Tested: bun test src/server/__tests__/providers.test.ts src/server/__tests__/providers-real.test.ts src/server/__tests__/provider-presets.test.ts src/server/__tests__/conversation-service.test.ts

Tested: cd desktop && bun run lint

Tested: ALLOW_CLI_CORE_CHANGE=1 bun run quality:pr

Tested: bun run quality:gate --mode baseline --allow-live --provider-model minimax:main:minimax-main

Tested: agent-browser provider edit flow on http://127.0.0.1:19811

Not-tested: Live successful calls for every third-party provider with real credentials.
2026-05-03 17:34:39 +08:00
程序员阿江(Relakkes)
fa5fda24d0 feat: notify users when desktop attention is needed
Add native system notifications as a desktop-wide attention channel for permission prompts and scheduled task completion. The implementation keeps notification presentation owned by the OS, adds a user-facing enable switch with permission handling, and lets scheduled tasks choose desktop notifications without routing that channel through IM adapters.

Constraint: Notifications must use OS-native APIs without custom sound playback.
Constraint: Desktop channel is local-only and must not be sent through IM adapter delivery.
Rejected: Browser Notification API | not reliable inside the packaged Tauri desktop runtime.
Rejected: Treat desktop as an IM channel | would leak a local-only channel into server-side adapter sending.
Confidence: high
Scope-risk: moderate
Directive: Keep notification styling at the OS layer; business code should only provide title, body, dedupe, and routing decisions.
Tested: bun run check:desktop
Tested: bun run quality:pr
Tested: Computer Use macOS debug app verification for settings toggle, permission prompt, scheduled task desktop channel, and task-run polling dedupe
Not-tested: Windows and Linux native runtime smoke tests on physical hosts
2026-05-03 16:45:32 +08:00
Relakkes Yang
9eb7da5377 fix: preserve historical workspace changes on windows 2026-05-02 17:53:24 +08:00
程序员阿江(Relakkes)
071975e0cc Improve desktop failure diagnosis with exportable logs
Desktop failures were previously hard to debug from issue reports because server-side and CLI startup details were only partially visible in the UI. This adds a dedicated cc-haha diagnostics store with sanitized structured events, runtime error summaries, a Settings diagnostics view, and an exportable bundle that users can attach to reports.

Constraint: Diagnostic exports must not include chat content, file contents, full environment variables, API keys, bearer tokens, cookies, or OAuth tokens.
Rejected: Export raw server logs | easier to debug but too likely to leak secrets and private workspace data.
Rejected: Keep diagnostics only in transient UI errors | still leaves maintainers unable to diagnose later GitHub issues.
Confidence: high
Scope-risk: moderate
Directive: Do not add raw transcript, prompt, attachment, or environment dumps to diagnostics without a separate privacy review.
Tested: ANTHROPIC_API_KEY=test-key bun test src/server/__tests__/diagnostics-service.test.ts
Tested: ANTHROPIC_API_KEY=test-key bun test src/server/__tests__/conversation-service.test.ts
Tested: ANTHROPIC_API_KEY=test-key bun test src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run test src/__tests__/diagnosticsSettings.test.tsx --run
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: agent-browser E2E on local server/dev UI for CLI startup failure, provider test failure, diagnostics tab, copy summary, export bundle, and tar/secret scan
Not-tested: Destructive clear-logs button in browser E2E; local deletion was intentionally not clicked.
2026-05-02 12:41:31 +08:00
Relakkes Yang
465e043563 fix: hide blank assistant message bubbles 2026-05-01 22:09:55 +08:00
程序员阿江(Relakkes)
f5cf5932b4 fix: keep workspace changes and session checkpoints reliable
The desktop workspace panel and chat transcript were mixing project-level file state with per-turn session history, which made rewind, file attachment, and changed-file previews brittle across multi-turn and historical sessions. This keeps turn checkpoints durable in the transcript, makes workspace refreshes happen at the right lifecycle points, and hardens long file previews without blocking the UI.

Constraint: Right-side workspace changes are project working-tree state, while chat turn cards are session checkpoint state.
Rejected: Treat every changed-file panel entry as session-local | new sessions must still reveal existing dirty project files.
Confidence: high
Scope-risk: moderate
Directive: Do not couple global workspace status to session checkpoint cards without preserving both product meanings.
Tested: cd desktop && bun run test src/components/workspace/WorkspacePanel.test.tsx
Tested: cd desktop && bun run lint
Tested: git diff --check
Tested: cd desktop && bun run test
Not-tested: Packaged Tauri runtime smoke test
2026-05-01 20:53:04 +08:00
程序员阿江(Relakkes)
10b974dac3 Merge commit 'df52bea0d0cb5889f9c5e324c54f2bdce9e30378' 2026-05-01 17:19:31 +08:00
程序员阿江(Relakkes)
3a6bad0e03 fix: keep WebSearch usable across third-party models
Native Anthropic web search is only reliable for Claude-family model names,
while many configured providers either do not implement the server tool schema
or reject it through proxy layers. This routes WebSearch through a single
resolver, adds Tavily and Brave fallbacks, and exposes provider key setup in
the desktop settings page.

Constraint: Third-party Anthropic-compatible endpoints may reject web_search_20250305 even when the model name is Claude-like
Constraint: Non-Claude models still need a usable WebSearch path when users configure an external search provider
Rejected: Gate native WebSearch by base URL | third-party Claude proxies can support it and official-looking URLs are not the real capability boundary
Rejected: Always expose Anthropic native WebSearch | unsupported providers loop on schema or tool errors
Confidence: high
Scope-risk: moderate
Directive: Keep WebSearch capability resolution centralized in WebSearchTool/backend.ts before adding more search providers
Tested: bun test src/tools/WebSearchTool/backend.test.ts
Tested: cd desktop && bun run test -- generalSettings.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: agent-browser Settings E2E for Tavily/Brave links and settings persistence
Tested: live Tavily and Brave fallback searches returned results using transient keys
Not-tested: automatic LLM decision to invoke WebSearch end-to-end against a paid model session
2026-05-01 10:14:02 +08:00
程序员阿江(Relakkes)
9ecc178c7a fix: prevent third-party thinking mode mismatches
Desktop users can now disable thinking for new sessions, and Anthropic-compatible providers that opt in receive explicit disabled-thinking requests across main turns, side queries, and AI title generation.

Constraint: DeepSeek/Kimi/GLM Anthropic-compatible endpoints need a non-thinking path without scattering provider-specific logic through the CLI.
Rejected: Per-model if/else branches | centralized provider preset env keeps the native CLI surface smaller and easier to audit.
Confidence: high
Scope-risk: moderate
Tested: bun test src/utils/__tests__/thinking.test.ts src/server/__tests__/title-service.test.ts src/server/__tests__/provider-presets.test.ts src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run test -- generalSettings.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: agent-browser DeepSeek desktop E2E with transparent proxy captured main and title requests with thinking.type=disabled
2026-05-01 10:13:10 +08:00
程序员阿江(Relakkes)
80aca8c176 feat: 让工作区文件引用成为可回滚的聊天上下文
桌面端的工作区文件面板现在可以把文件或局部评论加入聊天,发送给 CLI 的内容保持为原生 @文件路径引用,聊天界面则保留用户可读的文件 chip。为了让本轮变更卡片和 checkpoint 回滚继续可靠,UI 消息会同时保存用于展示的内容和实际发给模型的 modelContent;历史转录里的 @文件路径也会在重载时还原成附件 chip。

Constraint: CLI 侧最稳定的文件上下文入口仍是原生 @文件路径
Constraint: 聊天 UI 不能把绝对路径裸露成用户消息正文
Rejected: 只发送隐藏提示文本 | CLI 对 @文件路径已有稳定读取行为
Rejected: 只依赖前端展示内容做回滚校验 | 会和真实转录内容不一致
Confidence: high
Scope-risk: moderate
Directive: 修改附件展示或回滚校验时必须同时验证 modelContent 与 UI content 的分离
Tested: cd desktop && bun run test -- src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx src/stores/workspaceChatContextStore.test.ts src/components/workspace/WorkspacePanel.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: SKIP_INSTALL=1 ./desktop/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.1.8_aarch64.dmg
Tested: Computer Use macOS run with /private/tmp/cc-haha-computer-use-e2e verified file chip, @path model input, model sentinel response, workspace preview interactions
Not-tested: Deep unloaded-folder fuzzy search in all-files view
2026-05-01 00:00:32 +08:00
程序员阿江(Relakkes)
ba8c6b8828 fix: default empty workspace changes to file tree
The workspace panel now treats an empty changed-files status as a browsing case, so opening the panel shows the project tree instead of an empty changed-files view. The panel header controls were also reduced to keep the right workspace rail visually proportional in compact layouts.

Constraint: Users still need to manually inspect the empty changed-files view when they explicitly choose it.
Rejected: Always render the empty changed-files state | it makes the opened workspace feel broken when no files changed.
Confidence: high
Scope-risk: narrow
Directive: Do not auto-switch the workspace view after a user has explicitly selected a workspace view.
Tested: cd desktop && bun run test -- src/components/workspace/WorkspacePanel.test.tsx src/stores/workspacePanelStore.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Not-tested: Browser E2E visual screenshot pass was not rerun for this small header sizing change.
2026-04-30 19:27:22 +08:00
程序员阿江(Relakkes)
aaf25159af fix: keep workspace review surfaces usable across sessions
Workspace inspection now shares the same diff renderer between chat change cards and panel previews, supports scoped preview-tab closing, confirms checkpoint undo, resolves rewind checkpoints from the prompt cwd, and removes light-only workspace chrome so dark theme remains coherent.

Constraint: Workspace review must keep working for existing transcript sessions and non-git session-derived changes.
Rejected: Keep a separate chat diff preview | it drifted from panel rendering and dark theme behavior.
Confidence: high
Scope-risk: moderate
Directive: Keep chat and workspace diff rendering shared so theme and truncation behavior do not diverge again.
Tested: cd desktop && bun run test -- src/components/workspace/WorkspacePanel.test.tsx src/components/chat/MessageList.test.tsx src/stores/workspacePanelStore.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: Browser dark workspace preview at http://127.0.0.1:59468/ with src/App.jsx diff
Not-tested: macOS packaged app build
2026-04-30 14:30:20 +08:00
程序员阿江(Relakkes)
b811ffb8f1 feat: make session workspace changes inspectable
Expose a workspace inspector for desktop sessions so users can browse the active project, preview files, inspect session-derived changes, and review diffs even when the work directory is not a git repository.

Constraint: The workspace view must work for temporary and non-git project directories.
Rejected: Rely only on git status | non-git sessions would lose the changed-files surface.
Confidence: medium
Scope-risk: moderate
Directive: Keep undo semantics separate from workspace browsing; checkpoint-backed rewind should remain the source of truth for rollback.
Tested: bun test src/server/__tests__/sessions.test.ts src/server/__tests__/workspace-service.test.ts
Tested: cd desktop && bun run test -- src/components/workspace/WorkspacePanel.test.tsx src/stores/workspacePanelStore.test.ts
Tested: cd desktop && bun run lint
Tested: git diff --check
Not-tested: Full desktop packaged app build after this commit
2026-04-29 17:28:53 +08:00
程序员阿江(Relakkes)
3fc7200eb2 feat: help users configure sponsored and local providers without guesswork
Provider setup now carries the metadata needed for sponsored and local Anthropic-compatible providers, while keeping provider URLs and API keys editable in the desktop form. The desktop UI also exposes API-key links, optional sponsor copy, full key visibility, and local no-key presets that can be activated into runtime settings.

Constraint: Local LM Studio and Ollama integrations require Anthropic-compatible root URLs rather than OpenAI /v1 URLs
Rejected: Keep local providers at the top of the preset list | user requested them immediately before Custom
Confidence: high
Scope-risk: moderate
Directive: Do not change local provider base URLs to /v1 without rechecking Anthropic compatibility docs
Tested: bun test src/server/__tests__/provider-presets.test.ts src/server/__tests__/providers.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test src/__tests__/generalSettings.test.tsx --run
Tested: git diff --cached --check
Tested: Web UI provider creation plus real sessions for LM Studio, Ollama, JiekouAI, and Shengsuanyun
Not-tested: Production packaged Tauri build
2026-04-28 16:38:16 +08:00
程序员阿江(Relakkes)
3d3dfdad76 feat: Keep terminal workflows inside desktop tabs
The desktop terminal already supported independent PTY sessions, but it only lived inside settings. This change promotes it to a first-class tab workflow so users can open multiple host terminals without leaving the chat-oriented desktop layout.

Constraint: Tauri terminal sessions are process-backed and must stay mounted while switching tabs.
Rejected: Reuse the settings terminal as a navigated page only | it cannot support multiple independent terminal tabs.
Rejected: Hide inactive xterm panes with display none | xterm lost visible output after tab switches during E2E.
Confidence: high
Scope-risk: moderate
Directive: Keep inactive terminal panes mounted and avoid display none unless xterm repaint behavior is reverified.
Tested: bun run test src/components/layout/ContentRouter.test.tsx src/components/layout/Sidebar.test.tsx src/components/layout/TabBar.test.tsx src/pages/TerminalSettings.test.tsx
Tested: bun run lint
Tested: bun run build
Tested: ./scripts/build-macos-arm64.sh
Tested: Computer Use E2E against build-artifacts/macos-arm64 app for multiple terminals, command output retention, tab switching, and terminal cleanup
Not-tested: Intel macOS package
2026-04-28 16:38:16 +08:00
程序员阿江(Relakkes)
bc93493ed1 Prevent desktop rewind from trimming the wrong turn
Desktop rewind previously used the visible user-message index as the primary selector. That can drift from the persisted active chain when hidden or non-rendered user messages exist, so the API now prefers a stable user message id and checks the selected prompt text before mutating transcript or files.

Constraint: Desktop UI can hide transcript entries that still exist in the persisted session chain
Rejected: Keep index-only rewind | can target an earlier user message after UI/server chain drift
Confidence: high
Scope-risk: moderate
Directive: Do not remove targetUserMessageId or expectedContent guards without reproducing shifted visible-index sessions
Tested: bun test src/server/__tests__/sessions.test.ts --timeout 20000
Tested: cd desktop && bun run test MessageList.test.tsx chatStore.test.ts -- --run
Tested: desktop/scripts/e2e-rewind-agent-browser.sh
Tested: desktop/scripts/e2e-rewind-complex-agent-browser.sh
Not-tested: Full desktop Vitest suite still has unrelated locale-sensitive failures
2026-04-28 16:38:16 +08:00
程序员阿江(Relakkes)
1631c449f6 fix: tighten desktop slash command edge cases
Desktop slash command handling needed a few follow-up guards after adding
CLI parity. Bare /clear remains a local desktop reset, but /clear with
arguments is rejected instead of silently discarding user text. Clear also
resets cached slash commands, compact boundaries preserve CLI-provided text,
and the help panel now discloses when the More section is truncated.

Constraint: Preserve the desktop /clear fast path without accepting accidental arguments
Rejected: Let /clear arguments fall through to the CLI | the CLI command still clears context and would keep the footgun
Confidence: high
Scope-risk: narrow
Directive: Keep desktop-local slash command guards in sync with CLI command argument semantics
Tested: cd desktop && bun run test src/stores/chatStore.test.ts src/__tests__/pages.test.tsx --run -t "clears local desktop chat state|ActiveSession routes /help"
Tested: bun test src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run lint
Tested: git diff --check
Not-tested: Full pages.test.tsx because unrelated locale-sensitive assertions are already present in that file
2026-04-28 16:38:16 +08:00
程序员阿江(Relakkes)
caa164b62c fix: make desktop slash commands behave like the CLI
Desktop had no reliable handling for common local slash commands, so
commands like /clear, /help, /context, /cost, and /compact either looked
unresponsive or lost their CLI-specific side effects. This routes desktop
commands by behavior: local panels stay local, stateful clear resets the
session transcript, and CLI-local outputs are rendered in chat.

Constraint: Preserve existing CLI semantics for prompt, local, local-jsx, and compact slash commands
Rejected: Send every slash command through the normal chat path | local commands need desktop UI or session state side effects
Confidence: high
Scope-risk: moderate
Directive: Keep hidden aliases such as /plugins out of the visible command list unless they become canonical CLI commands
Tested: cd desktop && bun run test src/components/chat/composerUtils.test.ts src/stores/chatStore.test.ts --run
Tested: cd desktop && bun run test src/__tests__/pages.test.tsx --run -t "ActiveSession routes /plugin|ActiveSession routes /help|EmptySession slash picker includes dynamic skills"
Tested: bun test src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run lint
Tested: agent-browser E2E for /help, /plugin, /plugins, /cost, /clear, /context, /compact, /mcp, and normal text input
Not-tested: Root tsc project because it currently includes generated Tauri target assets and extracted native files
2026-04-28 16:38:16 +08:00
Relakkes Yang
55c5958b55 fix(desktop): stop sidecars before updater install 2026-04-25 13:21:31 +08:00
Relakkes Yang
928be2a4ff fix(desktop): remove install center 2026-04-24 23:05:45 +08:00
程序员阿江(Relakkes)
761031c036 fix: Make desktop MCP tools ready before first turn
Desktop sessions could show MCP configuration in the UI while the CLI SDK child process still started cold on the first user message. Start eligible desktop chat sessions as soon as their websocket is connected, keep init metadata cached but muted, and wait briefly for MCP startup in SDK print mode so turn one sees the tools.

Constraint: Desktop wraps the existing CLI SDK bridge rather than owning MCP startup directly.
Rejected: Prewarm every restored tab | synthetic tabs such as settings would start unnecessary CLI subprocesses.
Confidence: high
Scope-risk: moderate
Directive: Do not remove the synthetic-tab guard without validating restored settings and scheduled-task tabs in the browser.
Tested: bun test src/server/__tests__/conversations.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/mcp.test.ts
Tested: cd desktop && bun run test -- chatStore.test.ts mcpSettings.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: agent-browser E2E new session prewarm plus first message reuse with mock CLI
Not-tested: Full desktop Vitest suite still has pre-existing English-vs-Chinese copy assertion failures.
2026-04-24 17:11:03 +08:00
程序员阿江(Relakkes)
6d40b2befa fix: Keep desktop chats on selected provider runtimes
Desktop sessions can switch provider and model while a CLI subprocess is already alive, so the server now serializes runtime restarts and marks provider-managed launches to prevent stale settings env from overriding the selected provider. Provider settings also write API key env consistently and clear stale managed keys before syncing.

This includes the related desktop/docs brand asset refresh and keeps the desktop locale default in Chinese, with tests updated to match the current provider semantics.

Constraint: Session-scoped model selection must win over cc-haha/settings.json and inherited ANTHROPIC_* values.
Rejected: Store the selected model as a global provider activeModel | chat runtime selection is per session.
Confidence: high
Scope-risk: moderate
Directive: Do not remove CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST without validating Desktop provider switching against stale settings env.
Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/conversations.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/providers-real.test.ts
Tested: cd desktop && bun run test src/stores/settingsStore.test.ts
Tested: cd desktop && bun run lint
Tested: git diff --check
Not-tested: Full desktop production package/signing.
2026-04-24 13:24:31 +08:00
程序员阿江(Relakkes)
f911b16ada Merge host terminal support into post-release test branch
The detached worktree implementation adds a Settings terminal for command-based setup flows. The merge keeps the target branch's current adapter/runtime dependencies while adding the new portable-pty backend, xterm frontend, UTF-8 output handling, and host-shell environment propagation.

Constraint: Target branch had newer desktop dependency changes, including anyhow in the Tauri crate.
Constraint: Existing uncommitted work in the main worktree had to stay out of this merge commit.
Rejected: Fast-forward the checked-out branch under a dirty worktree | would risk mixing unrelated local changes into the branch state.
Confidence: high
Scope-risk: moderate
Directive: Treat the terminal as host-shell integration; keep PATH and UTF-8 locale handling together when changing startup behavior.
Tested: cargo fmt --check
Tested: cargo test --lib
Tested: cargo check
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test src/pages/TerminalSettings.test.tsx
Tested: git diff --check
Not-tested: Rebuilt macOS DMG from the target branch after merge.
2026-04-24 09:38:02 +08:00
程序员阿江(Relakkes)
0c58a9f36f Give desktop users a real terminal for command-based setup
Some setup flows end in a shell command instead of a natural-language install path, so Settings now exposes a host PTY terminal backed by portable-pty and xterm. The terminal inherits the user's login-shell environment, forces a UTF-8 locale when needed, and preserves split UTF-8 output so Chinese paths render correctly.

Constraint: Desktop GUI apps do not inherit the user's interactive shell PATH on macOS.
Constraint: Command output may split UTF-8 characters across PTY reads.
Rejected: Use Tauri shell commands only | users need an interactive PTY for copy-pasted install flows.
Rejected: Ask users to edit shell profiles | terminal setup should work out of the box.
Confidence: high
Scope-risk: moderate
Directive: Keep terminal startup tied to host environment checks; do not bundle runtimes to solve PATH issues.
Tested: cargo fmt --check; cargo test --lib; cargo check; cd desktop && bun run lint; cd desktop && bun run test src/pages/TerminalSettings.test.tsx; cd desktop && bun run build; cd desktop && bun run build:macos-arm64
Tested: Computer Use verified npm is available in the built macOS app terminal and Chinese output renders after UTF-8 decoding fix.
Not-tested: Native Windows/Linux package runtime validation.
2026-04-24 09:36:00 +08:00
程序员阿江(Relakkes)
773a8a703f Keep desktop chats on the selected provider/model
The desktop model picker now stores a session-scoped provider/model selection instead of relying on the global active provider. That selection is replayed on connect, passed into the CLI startup path, and preserved across turns until the user changes it again.

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

Constraint: Different providers can expose the same model id, so chat runtime selection cannot be derived from model id alone
Constraint: A desktop session reuses one CLI subprocess across turns, so runtime changes must restart that process to take effect
Rejected: Keep using Settings active provider as the chat selector | conflates defaults with live session state and breaks overlapping models
Rejected: UI-only runtime switching without server restart | later turns would continue using the old CLI subprocess configuration
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep provider defaults and session runtime overrides separate, and preserve provider-scoped proxy routing when extending model selection surfaces
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test src/stores/chatStore.test.ts
Tested: cd desktop && bun run test src/__tests__/generalSettings.test.tsx
Tested: bun test src/server/__tests__/conversation-service.test.ts
Tested: bun test src/server/__tests__/conversations.test.ts
Tested: bun -e "await import('./src/server/services/titleService.ts'); await import('./src/server/ws/handler.ts')"
Not-tested: Real third-party provider round-trip from the desktop UI against a live upstream account
2026-04-23 13:41:27 +08:00
程序员阿江(Relakkes)
90c3db1790 Prevent task-progress updates from corrupting streamed reply formatting
Desktop chat currently reuses one status channel for assistant streaming and
background task progress. When a task update arrived mid-turn, the store
flushed the in-flight markdown into a completed message, which caused the
remaining deltas to render as a second bubble and broke markdown styling.

Keep the reply in streaming state until the turn actually becomes idle, and
lock the behavior with a regression test that injects task progress between
text deltas.

Constraint: Background task progress currently shares the same status channel as assistant streaming
Rejected: Retune markdown renderer styles | would hide the symptom without preserving message integrity
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not flush streaming assistant text on non-idle status transitions unless the turn is actually ending
Tested: bun run test src/stores/chatStore.test.ts; bun run lint
Not-tested: Manual desktop UI verification with a live agent session
2026-04-23 12:11:30 +08:00
程序员阿江(Relakkes)
e0142b286b Keep built-in provider presets aligned with vendor-supported model IDs
The provider picker had two separate preset definitions and some defaults had
started to drift from the model IDs vendors document for Claude Code /
Anthropic-compatible usage. This change moves built-in presets to one
server-side JSON source, serves that source through the existing presets API,
and makes the desktop settings page consume the API instead of a duplicated
frontend constant. The preset defaults were then corrected against vendor docs
so the built-in values match documented model IDs and casing.

Constraint: Built-in provider defaults must match official vendor Claude Code or Anthropic docs
Rejected: Keep duplicated frontend and backend preset lists | values and casing drifted independently
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Update src/server/config/providerPresets.json and rerun provider-presets tests before changing built-in provider defaults again
Tested: bun test src/server/__tests__/provider-presets.test.ts; cd desktop && bun run lint
Not-tested: Manual desktop settings UI interaction after fetching presets from the API
2026-04-22 17:06:35 +08:00
程序员阿江(Relakkes)
49e1367659 fix: avoid blocking MCP list on live health checks
Desktop MCP settings was synchronously probing every configured server from /api/mcp, which made the list page scale with connection latency and surface brittle behavior as installs accumulated more MCPs. Return lightweight snapshot rows from the list API, keep explicit status checks separate, and let the desktop UI refresh status in a constrained background lane while preserving project-aware server identity.

Constraint: MCP list must stay responsive even with many configured servers
Rejected: Probe all servers from the list view without limits | still fans out with server count and can overload slow installs
Rejected: Keep servers permanently unchecked until detail view | misses the desired loading feedback when the MCP page opens
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep /api/mcp as a configuration snapshot endpoint; do not reintroduce per-row live connect work on list load
Tested: bun test src/server/__tests__/mcp.test.ts; cd desktop && bun run test -- mcpSettings.test.tsx; cd desktop && bun x tsc --noEmit --ignoreDeprecations 5.0
Not-tested: Real desktop interaction against an environment with dozens of live MCP servers
2026-04-22 15:34:36 +08:00
Relakkes Yang
0f533efb8d fix: merge split assistant text blocks in desktop chat 2026-04-22 14:32:23 +08:00
程序员阿江(Relakkes)
de736c49f7 Unify plugin capabilities with the desktop management views
Desktop plugin details now route into the shared Skills, Agents, and MCP management surfaces instead of maintaining separate read-only drilldowns. This also extends the desktop/server skill aggregation so plugin-provided skills appear in the shared list, groups MCP entries by source, and preserves detail-view back navigation based on where the user entered the page.

The implementation keeps plugin detail as the high-level capability hub while pushing real inspection into the existing management pages. Disabled plugins no longer expose false navigation paths into shared views, and the agent-browser regression script was expanded to exercise the new end-to-end flows.

Constraint: Shared Agents data only includes enabled plugin agents, so disabled plugins cannot deep-link into agent detail
Rejected: Keep duplicating full Skills/Agents/MCP detail inside Plugin detail | creates divergent UI flows and stale data paths
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If a detail view can be opened from multiple entry points, keep the return target in store state rather than hardcoding a single back destination
Tested: desktop vitest for plugins/skills/agents/mcp; desktop tsc --noEmit; desktop vite build; server skills API test; agent-browser web regression on plugin->skill/mcp and plugin->agent back navigation
Not-tested: packaged desktop app regression after rebuilding the Tauri bundle
2026-04-22 12:31:57 +08:00
程序员阿江(Relakkes)
28f36da0fd Reduce extension setup friction inside desktop settings
The desktop app already had solid session streaming, permission, and tool-rendering flows, but extension setup still forced users into manual forms or external shell work. This change adds an Install Center in Settings that reuses the session chat pipeline for natural-language installs, adds installer-specific guidance for plugin and skill URLs, and includes an agent-browser E2E script for real UI validation.

Constraint: Must reuse the existing session/chat execution path instead of introducing a second install runtime
Constraint: Plugin installs need real CLI commands while skill installs may come from published install commands on third-party pages
Rejected: Separate terminal-only install surface | duplicates session UX and weakens permission/tool visibility
Rejected: Pure form-based installer expansion | too much friction for plugin, MCP, and skill onboarding
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep installer prompts aligned with the actual CLI install surfaces; do not let the installer fall back to slash-command syntax inside Bash
Tested: cd desktop && bun run lint
Tested: Real UI automation via agent-browser for Telegram plugin install flow through Settings > Install, verified Plugins page shows telegram enabled
Tested: Real UI automation via agent-browser for ui-ux-pro-max skill install flow through Settings > Install, verified ~/.claude/skills/ui-ux-pro-max and Skills page visibility
Not-tested: Full e2e-install-center-agent-browser.sh script as a single uninterrupted green run after the latest stability tweaks
2026-04-22 01:06:57 +08:00
程序员阿江(Relakkes)
6c02904e58 Keep slash-command entry surfaces aligned with MCP desktop management
Merged the desktop MCP management work into local main and retained
existing plugin settings affordances while resolving router and settings-tab
conflicts. The resulting flow keeps MCP settings global-only for speed and
uses slash-command cards to route users into concrete MCP or skill targets.

Constraint: local main already had plugin settings and API routes that had to remain available
Rejected: Favor the worktree version wholesale during merge | would have dropped local plugin tab and router support on main
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep slash-command entry flows and settings tabs additive during future merges; do not collapse MCP and plugin navigation into one another
Tested: Conflict resolution review during merge; source commit 66edded validated with bun test src/server/__tests__/mcp.test.ts; cd desktop && bun run test -- pages.test.tsx mcpSettings.test.tsx; cd desktop && bun x tsc --noEmit --ignoreDeprecations 5.0
Not-tested: Re-running full desktop/manual verification from the merged main worktree after merge completion
Related: 66edded
2026-04-21 23:29:11 +08:00
程序员阿江(Relakkes)
66edded2d7 Make desktop MCP management and slash entry points directly operable
Desktop MCP management now has a working server API, a global-only settings surface,
and slash-command entry points that surface MCP and skills from the composer before
routing users into the right settings view.

Constraint: Project-scoped MCP browsing in settings was too slow and noisy because it scanned multiple workdirs
Rejected: Keep project MCP aggregation on the settings homepage | duplicated entries and poor responsiveness
Rejected: Route /mcp directly on Enter without an intermediate card | removed the user's ability to choose a specific target first
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep settings focused on global MCP; add project-scoped MCP affordances in the chat-context slash surfaces instead of re-expanding the settings homepage
Tested: bun test src/server/__tests__/mcp.test.ts; cd desktop && bun run test -- pages.test.tsx mcpSettings.test.tsx; cd desktop && bun x tsc --noEmit --ignoreDeprecations 5.0
Not-tested: Manual IAB verification after this final commit/merge cycle
2026-04-21 23:28:06 +08:00
程序员阿江(Relakkes)
89911bd6bf Merge commit '375d587' 2026-04-21 23:21:25 +08:00
程序员阿江(Relakkes)
375d587ed1 Expose desktop plugin management through first-class APIs
The desktop app could read plugin-produced skills and agents, but it had no
plugin control plane of its own. This adds a dedicated Settings tab backed by
server-side plugin APIs so installed plugins can be inspected, enabled,
disabled, updated, reloaded, and uninstalled from the WebUI.

The implementation also teaches browser-based desktop dev sessions to honor a
custom backend URL, which made it possible to run isolated worktree ports for
real UI automation. During verification, the long-lived desktop server kept a
stale installed-plugin snapshot after external CLI mutations, so cache clearing
now resets that session-level plugin installation state as well.

Constraint: Desktop WebUI needed an isolated backend URL instead of the hard-coded 127.0.0.1:3456 fallback
Constraint: Reuse existing plugin operations and loaders instead of rebuilding plugin lifecycle logic in the desktop layer
Rejected: Fold plugin management into Skills or Adapters | mixed unrelated lifecycles and hid plugin-specific health/actions
Rejected: Expose only read-only plugin status in desktop | did not satisfy enable-disable-reload-uninstall verification needs
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep desktop plugin actions routed through the shared plugin operation layer and clear installed-plugin session caches when plugin state changes externally
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- src/__tests__/pluginsSettings.test.tsx
Tested: bun test src/server/__tests__/plugins.test.ts src/server/__tests__/skills.test.ts
Tested: Browser automation against isolated ports 15120/38456 covering discord plugin list/detail/disable/apply/enable/update/uninstall flows
Not-tested: Full desktop session runtime parity with CLI /reload-plugins AppState refresh beyond the new desktop API path
2026-04-21 23:21:16 +08:00
程序员阿江(Relakkes)
044e9b6cc8 Make desktop rewind restore both transcript state and file checkpoints
The desktop client already had a conversation-level rewind UI concept on the
CLI side, but the web/desktop surface lacked the protocol, session trimming,
and file checkpoint restore path needed to make rewind trustworthy. This change
adds a desktop-specific rewind API, wires the message-level UI affordance and
confirmation modal, enables SDK file checkpointing for desktop sessions, and
covers the restore path with service tests plus a real agent-browser workflow.

Constraint: Desktop sessions run the CLI in SDK/print mode, so file checkpointing had to be enabled explicitly for that path
Constraint: main branch is checked out in a separate worktree, so merge-back must happen from the primary worktree after commit
Rejected: UI-only rewind that only trims local state | would leave persisted transcript and disk state inconsistent after refresh
Rejected: Reuse getLastSessionLog as the sole snapshot source | active rewind must read file-history metadata directly from the session file
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep desktop rewind keyed to persisted user-message order unless the UI model starts carrying stable transcript UUIDs end-to-end
Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/sessions.test.ts; desktop MessageList vitest; desktop tsc no-emit; live agent-browser E2E on isolated ports with file edit then rewind
Not-tested: Browser E2E matrix for multi-file and second-edit scenarios is still covered at service-test level rather than full UI level
2026-04-21 21:34:41 +08:00
程序员阿江(Relakkes)
b7aefc3d01 Make desktop updates less noisy and more truthful
The desktop updater now renders release notes as markdown, avoids fake 0%
progress when the server omits Content-Length, and remembers when the user
has dismissed a specific release prompt so reopening the app does not nag
again for the same version.

Constraint: Existing 0.1.4 clients can receive updater events without total size metadata and users still need a manual update path in About
Rejected: Keep repeating the prompt on every launch | creates avoidable noise after an explicit later decision
Rejected: Global dismiss flag for all future releases | would hide newer versions that should prompt again
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep prompt suppression keyed to availableVersion only; About page visibility and manual update actions must remain available
Tested: bun run test src/stores/updateStore.test.ts src/components/shared/UpdateChecker.test.tsx src/__tests__/generalSettings.test.tsx; bun run lint; manual local updater validation from 0.1.4 to 0.1.5 on /Applications and an extracted v0.1.4 release bundle
Not-tested: Signed and notarized macOS distribution behavior outside this local machine
2026-04-21 01:50:29 +08:00