127 Commits

Author SHA1 Message Date
fullex
9b9570116a refactor(db): replace libsql with better-sqlite3 + sqlite-vec (#16626) 2026-07-02 13:21:13 +08:00
亢奋猫
eb2622e9af refactor(ui): remove antd dependency (#16336)
Co-authored-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: kangfenmao <kangfenmao@qq.com>
Signed-off-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
2026-06-26 15:49:22 +08:00
LiuVaayne
efecdd5007 refactor(binary-manager): unify CLI binary acquisition behind mise-backed BinaryManager (#15184)
### What this PR does

**Before this PR**, Cherry Studio managed external CLI binaries through
five uncoordinated mechanisms — each requiring its own download script,
IPC channel, and on-disk layout:

| Mechanism | Where it lived | How it worked |
|---|---|---|
| rtk extraction | `src/main/utils/rtk.ts` + `AgentBootstrapService` |
Copies bundled binary to `~/.cherrystudio/bin/` |
| OpenClaw installer | `resources/scripts/install-openclaw.js` |
Downloads from GitHub/mirror with custom extraction |
| CodeCliService | `src/main/services/CodeCliService.ts` | `bun install
-g` to `~/.cherrystudio/install/global/` |
| ripgrep | `node_modules/@anthropic-ai/claude-agent-sdk/vendor/` |
Vendored, hardcoded path in `FileStorage` |
| (old) MiseService | `src/main/services/MiseService.ts` | Bundled mise
+ `mise use -g` |

**After this PR**, a single `BinaryManager` lifecycle service owns all
third-party CLI binary acquisition. It wraps
[mise](https://mise.jdx.dev) as the only acquisition backend (no custom
`BinaryBackend` interface — mise's polyglot grammar already covers
`npm:`, `pipx:`, `github:`, registry entries). Tools install into an
isolated environment under `~/.cherrystudio/mise/` so user-level mise
installs are never touched. `uv`, `bun`, `rg`, and mise itself ship
bundled at build time for instant first-run availability; everything
else flows through mise on demand.

<img width=\"1304\" height=\"714\" alt=\"BinaryManager settings UI\"
src=\"https://github.com/user-attachments/assets/7a4b78ab-5aa2-4e97-9ab7-134b20a4d78d\"
/>
<img width=\"1165\" height=\"748\" alt=\"Three-state managed vs bundled
vs not-installed\"
src=\"https://github.com/user-attachments/assets/a0dcfb7d-8bc3-4acd-b563-0fc04d99e252\"
/>
<img width=\"523\" height=\"328\" alt=\"Custom tool dialog\"
src=\"https://github.com/user-attachments/assets/90c3ee95-7f2a-4daf-a334-f20de6ff5ca2\"
/>

Fixes #15183. Addresses #15370.

### Why we need it and why it was done in this way

Adding a new managed CLI tool should be a one-line preset entry — not 4+
files of bespoke download/extract/IPC code. mise is a mature polyglot
tool manager that already speaks the backends Cherry needs.

**Tradeoffs made:**
- **mise bundled at build time** (~15 MB per platform) rather than
downloaded at first run — faster first-run UX, no chicken-and-egg on a
fresh install.
- **Fully isolated mise environment** (\`HOME\`/\`XDG_*\`/\`MISE_*\` all
relocated under \`feature.binaries.data\`) — Cherry never reads or
writes the user's own \`~/.config/mise/\` or \`~/.local/share/mise/\`.
- **No custom \`BinaryBackend\` interface.** mise's grammar (\`npm:\`,
\`pipx:\`, \`github:\`, registry) is already polyglot; wrapping it would
be a shallow seam that re-implements what mise owns. Removing this
abstraction makes consumers simpler (deletion test passes).
- **Auth-token policy: opt-in only.** Ambient \`GITHUB_TOKEN\` /
\`GH_TOKEN\` are not forwarded into mise's process env. Users who hit
GitHub's unauthenticated 60 req/hr API limit can set
\`CHERRY_GITHUB_TOKEN\` to raise it to 5000 req/hr without consenting to
share their general shell token.
- **China mirror auto-detection.** \`isUserInChina()\` toggles
\`NPM_CONFIG_REGISTRY=registry.npmmirror.com\` +
\`PIP_INDEX_URL=pypi.tuna.tsinghua.edu.cn\` for every npm/pipx backend
transparently.

**Alternatives considered:**
- *Keep per-tool install scripts.* Doesn't scale — each new tool is 4+
files of duplicated logic.
- *Use mise from user's \`PATH\`.* Would depend on user having mise
installed and could conflict with their config.
- *Custom \`BinaryBackend\` abstraction.* Shallow wrapper over mise's
grammar; no second backend in sight; deletion test passes.

**Scope (what's in / what's out):**
- **In:** uv, bun, ripgrep, claude-code, openclaw, gh, opencode,
gemini-cli, lark, kimi-cli, qwen-code, iflow-cli, github-copilot-cli —
anything mise can install as a single relocatable binary.
- **Out:** \`OvmsManager\` (multi-file server provisioning, hardware
detection, generated config); Tesseract OCR data (not a binary; lives
with \`OvOcrService\`).

### Breaking changes

None at the user-facing layer. v2 data is throwaway per CLAUDE.md, so
the preference-key rename (\`feature.mise.*\` → \`feature.binaries.*\`)
intentionally ships without a migrator.

### Special notes for your reviewer

**Architecture & docs**
- \`docs/references/binary-manager/README.md\` — scope criterion,
persisted/contract surface, bundled-vs-mise state contract, China mirror
behavior, \`CHERRY_GITHUB_TOKEN\` opt-in, adding a new managed binary.
- \`CLAUDE.md\` adds a \`**MUST READ**\` link next to Lifecycle / Window
Manager / Data / Paths.
- The mise-shim-wins-over-\`cherry.bin\` precedence rule is documented
in the README's "State contract" section.

**Code orientation**
- \`src/main/services/BinaryManager.ts\` — the lifecycle service. Wraps
mise via \`runMise()\`; isolated env in \`buildIsolatedEnv()\`; bundled
extraction with atomic tmp+rename; per-tool try/catch so a single
failure can't abort init; \`isManagedBinaryReady()\` verifies the
resolved file is executable (not just that mise thinks it's installed).
- \`packages/shared/data/presets/binary-tools.ts\` —
\`PREDEFINED_BINARY_TOOLS\` registry; \`tool\` field is a mise spec.
-
\`src/renderer/src/pages/settings/McpSettings/EnvironmentDependencies.tsx\`
— three-state UI (\`managed\` / \`bundled\` / \`not-installed\`) backed
by \`Binary_GetState\` + \`Binary_ProbeBundled\`.
- \`scripts/download-binaries.js\` — build-time downloader for mise / uv
/ bun / rg (HTTPS + sha256-verified, archive-aware extraction).

**Migration**
- \`OpenClawService.install()\` is now two lines delegating to
\`BinaryManager\` — \`install-openclaw.js\` is gone.
- \`CodeCliService\` no longer uses \`bun install -g\`; the
\`BUN_INSTALL\` / \`~/.cherrystudio/install/global/\` path is removed.
- \`FileStorage.getRipgrepBinaryPath()\` now resolves via
\`getBinaryPath('rg')\`; the vendored SDK rg is no longer used.
- \`extractRtkBinaries\` + the \`AgentBootstrapService\` call are
deleted. \`rtkRewrite()\` degrades gracefully when rtk is absent;
\`rtkAvailable\` caches with a 60s TTL so install-via-mise takes effect
without restart.

**Multi-round review**
Two adversarial review rounds against this branch (Bug Hunter / Security
/ Architecture / Correctness) ran during development; both rounds'
High-severity findings are addressed in commits \`70afde6af\` and
\`1d864439d\`. R3 known follow-ups (architecture duplications in
\`CodeCliService\`'s switch statements, etc.) are tracked as Medium and
intentionally deferred.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: Write code that humans can understand and Keep it simple
- [x] Refactor: Leaves binary acquisition meaningfully cleaner than
before (five mechanisms → one)
- [x] Upgrade: v2 data is throwaway; no migrator needed and the absence
is intentional
- [x] Documentation: \`docs/references/binary-manager/README.md\` +
\`CLAUDE.md\` Architecture section
- [x] Self-review: Two multi-perspective review rounds against the
branch; all Highs addressed

### Release note

\`\`\`release-note
NONE
\`\`\`

---------

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
Signed-off-by: Vaayne Liu <vaayne@macos.shared>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-06-24 15:15:25 +08:00
fullex
d06302db48 refactor(shared): rename preset & codeLanguages files to camelCase per naming-conventions §3.2
`.ts` files under `src/shared` must use camelCase (naming-conventions §3.2);
kebab-case is only sanctioned under `packages/ui/` and `src/renderer/routes/`.
The `presets/` kebab naming came from best-practice-layered-preset-pattern.md,
which predated and conflicted with the authoritative spec.

- Rename presets/{code-cli,default-assistant,file-processing,mini-apps,
  translate-languages,web-search-providers}.ts and utils/code-languages.ts
  (plus the two matching __tests__ files) to camelCase, and update all importers
- Fix the upstream generator scripts/update-languages.ts to emit
  codeLanguages.ts; otherwise `pnpm update:languages` would recreate the
  kebab-named file
- Correct best-practice-layered-preset-pattern.md (kebab -> camelCase) and link
  it to naming-conventions §3.2 so it cannot drift again
- Fix two stale `types/file` path references in file/architecture.md
2026-06-19 22:08:49 -07:00
fullex
ad3b6a7e3d fix(update-languages): emit codeLanguages export to match consumers
The generator template emitted `export const languages`, but the data file and its three consumers expect `codeLanguages` (diverged at #12803 when the linguist data file was forked and renamed without updating the generator). Emit the matching name so `pnpm update:languages` is usable again.

Also refresh stale "languages.ts" references in comments and logs to the current "code-languages.ts" filename.
2026-06-19 21:08:31 -07:00
fullex
1c6ff30a18 refactor(shared): dissolve config/ and move logic out of types/ into utils/
Dissolve the by-kind @shared/config junk drawer per shared-layer governance: route each member by shape and actual consumer process — cross-process slices into types//utils//ai/, single-process code back into main/renderer (Invariant 1.1). Confirm each item's real consumer process rather than trusting the directional plan (API_SERVER_DEFAULTS is renderer-only, MIN_WINDOW_* is cross-process, providers.ts is renderer-only), and drop dead consts (ZOOM_LEVELS/ZOOM_OPTIONS, bookExts, thirdPartyApplicationExts).

Purge runtime logic from types/ so the bucket holds only declarations: move serializeError + AI-SDK error guards to utils/error.ts, the FileHandle factories/guards to utils/file/handle.ts, isSerializable + SerializableSchema to utils/serializable.ts, and the tab-instance guard/normalizer to utils/tabInstanceMetadata.ts. Tests follow the logic to utils/__tests__; the type-level ipc contract test is retired (its invariants kept as a breadcrumb for the future IpcApi Zod schema). types/ is now logic-free and test-free.

Also: remove the dead @shared mock from the packages/ui code-editor test so packages/ui no longer references production code; fix the data-classify preference generator prompts import and the update-languages output path to the relocated modules.

Update shared-layer-architecture (3.1 type/util test rule, 5/6 config dissolution) and renderer-architecture cross-references.
2026-06-19 20:41:18 -07:00
fullex
ff6de394f6 refactor(shared): dissolve command/file/shortcuts/externalApp into types/ and utils/ by shape
Move the four ad-hoc top-level @shared dirs into the closed top-level set, routed by shape: pure logic + class blueprints to utils/, type/contract declarations to types/.

command -> utils/command + types/command; file -> utils/file + types/file; shortcuts -> utils/shortcut + types/shortcut; externalApp -> utils/externalApp + types/externalApp.

Replace the exported menuRegistry singleton with a pure resolveMenu over a frozen contribution set (Invariant 1.2: no exported instance); keep MenuRegistry as a per-process blueprint sharing the same resolve algorithm.

Rewrite all consumer imports per symbol origin and align shared-layer-architecture and renderer-architecture docs.
2026-06-19 18:52:21 -07:00
亢奋猫
b7b2a27a02 chore(scripts): update legacy css variable checks (#16025)
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: kangfenmao <kangfenmao@qq.com>
2026-06-13 13:04:06 +08:00
SuYao
de580c2fb1 feat(api-gateway): port the universal API gateway onto v2 (ElysiaJS rewrite) (#15705)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-06-07 18:04:26 +08:00
SuYao
b8f1feb4f8 feat(command): introduce command system, consolidating shortcut & app-menu handling (#15699)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-06-05 23:24:34 +08:00
亢奋猫
75cf5cc8bf feat(i18n): add unused key cleaner (#14858)
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: kangfenmao <kangfenmao@qq.com>
2026-06-05 00:14:24 +08:00
SuYao
5706307451 refactor(ai-service): consolidate AI runtime to main process (#14911)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-06-05 00:06:51 +08:00
fullex
ad922067d4 refactor(mcp): rename MCP* identifiers to Mcp* per naming conventions
Apply the naming-conventions §6.1 acronym-casing rule (MCP -> Mcp) to the
MCP* PascalCase identifier family across the codebase (McpServer, McpTool,
McpToolResponse, BuiltinMcpServerNames, McpService, etc.) plus the local
identifiers boundMcp/enableMcp/disableMcp and the didiMcp registry key.
Regenerate the OpenAPI spec from the renamed schemas.

Deliberately left unchanged (not naming-convention identifiers): persisted
field keys read by migrators (enabledMCPs), v1 Redux selectors (selectMCP),
string values (ExaMCP, logger labels), and UPPER_SNAKE constants (MCP_*).

Also fix naming issues in the data reference docs that prompted this:
- JSONStreamReader -> JsonStreamReader (match the real class name)
- rowToMCPServer -> rowToMcpServer (match the real function name)
- replace the TopicService getInstance() skeleton with a direct singleton
- sync stale MCPServer/MCPTool/McpService references in affected docs
2026-06-03 04:39:14 -07:00
亢奋猫
26508591f8 refactor(paintings): migrate to v2 data layer and UI (#15154)
Co-authored-by: jidan745le <420511176@qq.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: SuYao <sy20010504@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: jidan745le <420511176@qq.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-06-02 15:18:53 +08:00
fullex
53a3577389 refactor(renderer): flatten src/renderer/src to src/renderer
Move all renderer source from src/renderer/src/* up one level to
src/renderer/*, removing the redundant nested src directory.

- Update path aliases (@renderer, @types, @logger, @data) and TanStack
  Router paths in electron.vite.config.ts; update tsconfig.{json,web,node}
  path mappings and include globs.
- Fix Vite root-relative script paths in the 8 renderer HTML entries.
- Update cross-process relative imports in main/preload (language,
  apiServer models, preload index) to drop the /src segment.
- Switch renderer test imports of the logger mock to the @test-mocks alias.
- Update hardcoded renderer paths in scripts and their fixtures, lint
  configs (eslint/oxlint/biome), CODEOWNERS, docs, and the data-classify tool.
- Convert deep (../../+) relative imports within the renderer to the
  @renderer alias (69 files, 108 imports); keep single-level relatives.
- Fix doc links broken by the move and correct one pre-existing broken
  link in naming-conventions.md.
2026-05-28 21:40:20 -07:00
fullex
c514dcc049 refactor(shared): move packages/shared to src/shared
packages/shared was never a real pnpm workspace package (no package.json); it was referenced only through the @shared TypeScript path alias. Relocate it under src/ via git mv (143 files, detected as pure renames).

Repoint the @shared alias and include globs to src/shared across electron.vite.config.ts, tsconfig.{json,node,web}.json and vitest.config.ts; update scripts/check-custom-exts.ts, scripts/update-languages.ts, the eslint.config.mjs generated-file globs, the data-classify generator output targets, .github/CODEOWNERS path rules, and CLAUDE.md/docs/source-comment references.

The @shared alias name is unchanged, so all 1403 @shared/* import sites resolve without modification. Verified with typecheck:node, typecheck:web and the full test suite (700 files, 9739 tests passing).
2026-05-28 21:02:49 -07:00
fullex
cc51ba36ad chore(app-identity): lowercase bundle id to com.cherryai.cherrystudio
Normalise the bundle id casing from com.cherryai.CherryStudio to
com.cherryai.cherrystudio so it follows the dominant lowercase
convention used by modern AI tools (Anthropic Claude, OpenAI Codex,
Raycast, Docker) and the reverse-DNS norm. macOS LaunchServices is
case-insensitive, so this is a no-op on existing installs - bundle-id
bound state stays as it was after 4439d3b28, and no new breaking-changes
entry is needed.

Touches the same five definition points as the original rebrand
(electron-builder appId, notarize appBundleId, AppUserModelID, selection
self-detection, preview workflow replaceAll/appId) plus the
breaking-changes doc body. The doc's historical commit-subject reference
for 4439d3b28 deliberately retains its original PascalCase to remain a
faithful quote of that commit's subject.
2026-05-27 21:43:06 -07:00
fullex
4439d3b283 chore(app-identity): rebrand bundle id to com.cherryai.CherryStudio
Rename the app bundle id from com.kangfenmao.CherryStudio to
com.cherryai.CherryStudio at every definition point: electron-builder
appId (packaging source of truth), macOS notarization appBundleId,
Windows AppUserModelID, and the selection self-detection allowlist.

Daily preview builds now use com.cherryai.CherryStudio.preview (a distinct
channel id rather than a case-only variant that macOS would treat as the
same app); the workflow replaceAll search strings are updated to the new
id so the preview identity patch keeps matching.

Incidental cleanups bundled in:
- Drop the unused MAIN_VITE_BUNDLE_ID env override; env.d.ts now declares
  the actually-used MAIN_VITE_CHERRYAI_CLIENT_SECRET instead of leaning on
  vite/client's any index signature.
- Remove the stale @kangfenmao/keyv-storage from pnpm.onlyBuiltDependencies.
- SearchService: use @main/core/platform isDev over electron-toolkit is.dev.
2026-05-27 08:31:22 -07:00
fullex
7b556cf786 Merge branch 'main' of github.com:CherryHQ/cherry-studio into v2 2026-05-15 05:51:51 -07:00
zhibisora
b0c77f6415 fix(ci): improve GitCode sync reliability (#15063)
<!-- Template from
https://github.com/kubevirt/kubevirt/blob/main/.github/PULL_REQUEST_TEMPLATE.md?-->
<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Consider creating this PR as draft:
https://github.com/CherryHQ/cherry-studio/blob/main/CONTRIBUTING.md
-->

<!--

🚨 Branch Strategy Change (Effective April 3, 2026) 🚨

The `main` branch is now under CODE FREEZE.

- main branch: Only accepts critical bug fixes via `hotfix/*` branches.
Fix PRs must be minimal in scope and must not include any refactoring
code.
- v2 branch: All new features, refactoring, and optimizations should be
submitted to the `v2` branch.

If you are submitting a bug fix to main, please ensure your PR is from a
`hotfix/*` branch.

-->

### What this PR does

Before this PR:

GitCode release sync builds signed Windows artifacts and uploads them to
GitCode in one self-hosted Windows signing job. If the signing runner
has unreliable outbound network connectivity, the GitCode release
creation or asset upload can fail after the signed artifacts were
already built. The workflow also has no dry-run mode for validating a
manual release sync.

After this PR:

The workflow builds signed Windows artifacts on the Windows signing
runner, uploads them as a short-lived GitHub Actions artifact, then
performs GitCode release creation and asset upload from `ubuntu-latest`.
Manual dispatch supports a `dry_run` mode that previews the release
payload and upload file list without creating the GitCode release.
Windows code signing also retries timestamping across multiple timestamp
servers before failing.

<!-- (optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)`
format, will close the issue(s) when PR gets merged)*: -->

Fixes # None

### Why we need it and why it was done in this way

The following tradeoffs were made:

The release sync now uses an intermediate GitHub Actions artifact to
pass signed Windows files from the signing runner to the Ubuntu sync
job. This adds one artifact upload/download step, but keeps certificate
access constrained to the signing runner while moving GitCode API
traffic to a more reliable hosted runner.

The following alternatives were considered:

Keeping GitCode sync on the signing runner was simpler, but it leaves
release sync vulnerable to transient network failures on that runner.
Retrying only the GitCode upload would not address timestamp-server
flakiness during Windows signing, so this PR also adds timestamp server
fallback and retry support in `scripts/win-sign.js`.

Links to places where the discussion took place: N/A

### Breaking changes

None.

### Special notes for your reviewer

Validation performed:

- `pnpm format`
- `pnpm lint` (passed with one pre-existing unrelated React hook warning
in
`src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx`)
- `pnpm test`
- Parsed `.github/workflows/sync-to-gitcode.yml` with the repository
`yaml` package

`actionlint` was attempted, but the npm package named `actionlint` does
not expose a binary and this environment does not have Go installed to
run the upstream Go tool directly.

### Checklist

This checklist is not enforcing, but it's a reminder of items that could
be relevant to every PR.
Approvers are expected to review this list.

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [ ] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR
requires additional action from users switching to the new release,
include the string "action required".
2. If no release note is required, just write "NONE".
3. Only include user-facing changes (new features, bug fixes visible to
users, UI changes, behavior changes). For CI, maintenance, internal
refactoring, build tooling, or other non-user-facing work, write "NONE".
-->

```release-note
NONE
```

Signed-off-by: zhibisora <73344387+zhibisora@users.noreply.github.com>
2026-05-14 10:06:56 +08:00
亢奋猫
29a3750ac3 chore(style-reminders): add Tailwind canonical checks (#14862)
### What this PR does

Before this PR:

Tailwind canonical class suggestions such as `w-[420px] -> w-105` had to
be fixed manually, and the PR style reminder workflow only reported
newly introduced legacy renderer CSS variables.

After this PR:

Adds `pnpm styles:canonical <path>` to rewrite static Tailwind class
strings to their canonical Tailwind v4 forms. The PR style reminders
workflow now comments on both newly introduced legacy CSS variables and
Tailwind canonical class suggestions.

<!-- (optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)`
format, will close the issue(s) when PR gets merged)*: -->

Fixes #

### Why we need it and why it was done in this way

The following tradeoffs were made:

The canonical class fixer is conservative: it only rewrites static JSX
`class` / `className` strings and static `cn(...)` string inputs,
leaving dynamic template literals untouched. It uses Tailwind's own
design system canonicalization instead of maintaining a manual mapping
table.

The following alternatives were considered:

A regex-only implementation was avoided because Tailwind
canonicalization depends on Tailwind v4 parsing and theme behavior. A
separate PR workflow comment was also avoided so the style reminders
comment remains the single bot comment.

Links to places where the discussion took place: N/A

### Breaking changes

None.

### Special notes for your reviewer

Compatibility aliases and legacy marker/env fallback were removed; the
PR workflow now uses the `style-reminders` script, marker, and output
naming.

Validation performed:

- `pnpm test:scripts --
scripts/__tests__/check-pr-style-reminders.test.ts
scripts/__tests__/fix-tailwind-canonical-classes.test.ts`
- `pnpm build:check`
- `git diff --check`

### Checklist

This checklist is not enforcing, but it's a reminder of items that could
be relevant to every PR.
Approvers are expected to review this list.

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR
requires additional action from users switching to the new release,
include the string "action required".
2. If no release note is required, just write "NONE".
3. Only include user-facing changes (new features, bug fixes visible to
users, UI changes, behavior changes). For CI, maintenance, internal
refactoring, build tooling, or other non-user-facing work, write "NONE".
-->

```release-note
NONE
```

---------

Signed-off-by: kangfenmao <kangfenmao@qq.com>
2026-05-11 14:48:55 +08:00
亢奋猫
dee3bb0928 feat(settings): refactor settings UI and add settings window (#14567)
Signed-off-by: kangfenmao <kangfenmao@qq.com>
Signed-off-by: jdzhang <625013594@qq.com>
Co-authored-by: jdzhang <625013594@qq.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-05-08 18:09:26 +08:00
fullex
9f4026f5ec Merge branch 'main' of github.com:CherryHQ/cherry-studio into v2 2026-04-20 01:39:00 -07:00
亢奋猫
c88f6aa787 refactor: stabilize ui package boundaries and theme contracts (#14328)
Co-authored-by: MyPrototypeWhat <daoquqiexing@gmail.com>
Fixes #14331
2026-04-20 16:12:02 +08:00
Kennyzheng
6b5cb0b76b feat(i18n): add Vietnamese (vi-VN) language support (#14279)
### What this PR does

Before this PR:
Cherry Studio supported 11 languages but did not include Vietnamese.

After this PR:
Vietnamese (vi-VN / Tiếng Việt) is fully supported with 4016 translated
keys across both renderer and main processes. Ant Design components
correctly display Vietnamese locale. All 106 previously leaked
non-Vietnamese strings have been re-translated.

### Why we need it and why it was done in this way

Vietnamese users represent a growing segment of Cherry Studio's user
base. Adding vi-VN follows the same pattern as all existing
machine-translated locales (de-DE, es-ES, fr-FR, etc.):

1. Register `vi-VN` in the `LanguageVarious` TypeScript union type
2. Add locale entries to all `Record<LanguageVarious, ...>` maps (i18n,
dayjs, EmojiPicker)
3. Add `vi-VN` to main process `locales.ts` for menu/dialog translations
4. Add `vi-VN` case to `AntdProvider.tsx` for Ant Design component
localization
5. Add language selector option in GeneralSettings
6. Add `'vi-vn': 'Vietnamese'` to the auto-translate script's
`languageMap`
7. Generate the initial `vi-vn.json` translation file and fix 106
wrong-language entries

The following tradeoffs were made:
- EmojiPicker falls back to English data/i18n for Vietnamese since
`emoji-picker-element` doesn't ship Vietnamese locale data. This is
consistent with how Romanian and Greek are handled.

The following alternatives were considered:
- Manual translation: rejected in favor of machine translation for
consistency with other non-CJK locales, and because the CI pipeline
auto-maintains translations going forward.

### Breaking changes

None.

### Special notes for your reviewer

- The `vi-vn.json` file (~6000 lines) is machine-translated. 106 entries
that originally leaked from other locales (German, Spanish, French,
Italian, Thai, Japanese, Korean, etc.) were detected and re-translated.
- Compared to the previous PR (#14277), this version additionally
updates `src/main/utils/locales.ts` (main process translations) and
`src/renderer/src/context/AntdProvider.tsx` (Ant Design locale), which
were missing before.
- No changes to the CI workflow (`auto-i18n.yml`) were needed — it
auto-discovers all JSON files in the `translate/` directory.
- All checks pass: `pnpm lint`, `pnpm test` (4107 tests), `pnpm
typecheck`, `pnpm i18n:check`.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: Write code that humans can understand and Keep it simple
- [x] Refactor: You have left the code cleaner than you found it (Boy
Scout Rule)
- [ ] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [x] Documentation: A user-guide update was considered and is present
(link) or not required. Check this only when the PR introduces or
changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code before requesting review
from others

### Release note

```release-note
Added Vietnamese (Tiếng Việt) language support with complete UI translations and automatic translation maintenance via CI.
```

### Screenshots

| Agent Page | Assistant Page | Settings Page |
|:---:|:---:|:---:|
|
![Agent](https://pub-a9416c5573a34388b8d9465d8bef4257.r2.dev/pr-assets/14279/vi-agent.png)
|
![Assistant](https://pub-a9416c5573a34388b8d9465d8bef4257.r2.dev/pr-assets/14279/vi-assistant.png)
|
![Settings](https://pub-a9416c5573a34388b8d9465d8bef4257.r2.dev/pr-assets/14279/vi-settings.png)
|

---------

Signed-off-by: zhengke090@gmail.com <zhengke090@126.com>
Co-authored-by: zhengke090@gmail.com <zhengke090@126.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: SuYao <sy20010504@gmail.com>
Co-authored-by: George·Dong <98630204+GeorgeDong32@users.noreply.github.com>
2026-04-16 23:40:39 +08:00
亢奋猫
a83f98fd24 docs: consolidate bilingual docs, add link checker and architecture overview (#14138)
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-04-09 16:01:40 +08:00
beyondkmp
ea828787db fix(agents): route Claude Code child process through configured proxies (#13895)
### What this PR does

fix #13833

Before this PR:

Spawned Claude Code child processes did not reliably use the app's
configured proxy settings. HTTP proxy mode could work, but SOCKS proxy
mode could hang before the SDK returned any initial stream events.

After this PR:

Spawned Claude Code child processes inherit a dedicated Node-only proxy
bootstrap that applies the app's configured proxy settings for
fetch/undici, http/https, and axios. SOCKS proxy mode now avoids
exporting incompatible HTTP proxy env vars and emits clearer diagnostics
when proxy injection is active.

Fixes # None

### Why we need it and why it was done in this way

Claude Code runs as a standalone spawned `cli.js` process, so
main-process proxy patching and Electron session proxy configuration do
not automatically apply to it. This change builds a separate proxy
bootstrap, injects it only when proxy settings are configured, and keeps
the child-process proxy behavior aligned with the app's proxy settings
without changing the Claude SDK package itself.

The following tradeoffs were made:

- Added a separate build artifact for the Claude Code child-process
proxy bootstrap.
- Added child-process-specific proxy diagnostics to improve debugging
when proxy routing fails.
- Split SOCKS proxy environment handling from HTTP proxy handling to
avoid incompatible env combinations.

The following alternatives were considered:

- Relying only on inherited shell proxy environment variables.
- Relying on Electron session proxy configuration from the main process.
- Patching the Claude SDK package directly instead of injecting a local
bootstrap.

Links to places where the discussion took place: None

### Breaking changes

None.

If this PR introduces breaking changes, please describe the changes and
the impact on users.

No breaking changes.

### Special notes for your reviewer

- `out/proxy/index.js` is built as a standalone child-process bootstrap
and unpacked for packaged app usage.
- SOCKS proxy mode now exports `ALL_PROXY` / `SOCKS_PROXY` for the
Claude child process instead of forcing `HTTP_PROXY` / `HTTPS_PROXY` to
a SOCKS URL.
- Added tests covering HTTP vs SOCKS child-process proxy environment
generation.

### Checklist

This checklist is not enforcing, but it's a reminder of items that could
be relevant to every PR.
Approvers are expected to review this list.

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR
requires additional action from users switching to the new release,
include the string "action required".
2. If no release note is required, just write "NONE".
3. Only include user-facing changes (new features, bug fixes visible to
users, UI changes, behavior changes). For CI, maintenance, internal
refactoring, build tooling, or other non-user-facing work, write "NONE".
-->

```release-note
Fixed Claude Code agent sessions so spawned child processes respect configured HTTP and SOCKS proxy settings.
```

---------

Signed-off-by: beyondkmp <beyondkmp@gmail.com>
Signed-off-by: Payne Fu <payne@Paynes-MacBook-Air.local>
Co-authored-by: Payne Fu <payne@Paynes-MacBook-Air.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-04-03 14:18:02 +08:00
suyao
4dc1aac287 Merge remote-tracking branch 'origin/main' into DeJeune/merge-main-to-v2
Signed-off-by: suyao <sy20010504@gmail.com>

# Conflicts:
#	packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts
#	pnpm-lock.yaml
#	src/main/ipc.ts
#	src/main/services/CodeCliService.ts
#	src/main/services/ProxyManager.ts
#	src/main/services/WebviewService.ts
#	src/main/services/WindowService.ts
#	src/main/services/__tests__/ProxyManager.test.ts
#	src/main/services/agents/services/claudecode/index.ts
#	src/preload/index.ts
#	src/renderer/src/aiCore/index_new.ts
#	src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts
#	src/renderer/src/aiCore/legacy/clients/__tests__/index.clientCompatibilityTypes.test.ts
#	src/renderer/src/aiCore/legacy/clients/ovms/OVMSClient.ts
#	src/renderer/src/aiCore/legacy/clients/types.ts
#	src/renderer/src/aiCore/legacy/clients/zhipu/ZhipuAPIClient.ts
#	src/renderer/src/aiCore/legacy/middleware/feat/ImageGenerationMiddleware.ts
#	src/renderer/src/aiCore/plugins/PluginBuilder.ts
#	src/renderer/src/pages/code/CodeCliPage.tsx
#	src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx
#	src/renderer/src/pages/paintings/ZhipuPage.tsx
#	src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelListItem.tsx
#	src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx
#	src/renderer/src/services/AssistantService.ts
#	src/renderer/src/services/__tests__/ApiService.test.ts
#	tsconfig.web.json
2026-04-02 18:03:03 +08:00
beyondkmp
285ff0f3a3 fix(agents): route Claude Code child process through configured proxies (#13895)
### What this PR does

fix #13833

Before this PR:

Spawned Claude Code child processes did not reliably use the app's
configured proxy settings. HTTP proxy mode could work, but SOCKS proxy
mode could hang before the SDK returned any initial stream events.

After this PR:

Spawned Claude Code child processes inherit a dedicated Node-only proxy
bootstrap that applies the app's configured proxy settings for
fetch/undici, http/https, and axios. SOCKS proxy mode now avoids
exporting incompatible HTTP proxy env vars and emits clearer diagnostics
when proxy injection is active.

Fixes # None

### Why we need it and why it was done in this way

Claude Code runs as a standalone spawned `cli.js` process, so
main-process proxy patching and Electron session proxy configuration do
not automatically apply to it. This change builds a separate proxy
bootstrap, injects it only when proxy settings are configured, and keeps
the child-process proxy behavior aligned with the app's proxy settings
without changing the Claude SDK package itself.

The following tradeoffs were made:

- Added a separate build artifact for the Claude Code child-process
proxy bootstrap.
- Added child-process-specific proxy diagnostics to improve debugging
when proxy routing fails.
- Split SOCKS proxy environment handling from HTTP proxy handling to
avoid incompatible env combinations.

The following alternatives were considered:

- Relying only on inherited shell proxy environment variables.
- Relying on Electron session proxy configuration from the main process.
- Patching the Claude SDK package directly instead of injecting a local
bootstrap.

Links to places where the discussion took place: None

### Breaking changes

None.

If this PR introduces breaking changes, please describe the changes and
the impact on users.

No breaking changes.

### Special notes for your reviewer

- `out/proxy/index.js` is built as a standalone child-process bootstrap
and unpacked for packaged app usage.
- SOCKS proxy mode now exports `ALL_PROXY` / `SOCKS_PROXY` for the
Claude child process instead of forcing `HTTP_PROXY` / `HTTPS_PROXY` to
a SOCKS URL.
- Added tests covering HTTP vs SOCKS child-process proxy environment
generation.

### Checklist

This checklist is not enforcing, but it's a reminder of items that could
be relevant to every PR.
Approvers are expected to review this list.

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR
requires additional action from users switching to the new release,
include the string "action required".
2. If no release note is required, just write "NONE".
3. Only include user-facing changes (new features, bug fixes visible to
users, UI changes, behavior changes). For CI, maintenance, internal
refactoring, build tooling, or other non-user-facing work, write "NONE".
-->

```release-note
Fixed Claude Code agent sessions so spawned child processes respect configured HTTP and SOCKS proxy settings.
```

---------

Signed-off-by: beyondkmp <beyondkmp@gmail.com>
Signed-off-by: Payne Fu <payne@Paynes-MacBook-Air.local>
Co-authored-by: Payne Fu <payne@Paynes-MacBook-Air.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-04-02 17:04:00 +08:00
fullex
536025c8cf Merge branch 'main' of github.com:CherryHQ/cherry-studio into v2 2026-03-29 23:35:21 -07:00
LiuVaayne
4ef98318af feat: integrate rtk for reducing LLM token consumption on agent shell commands (#13615)
### What this PR does

Before this PR:
Agent Bash tool calls output raw, verbose shell command results that
consume excessive LLM tokens.

After this PR:
Bash commands are transparently rewritten via
[rtk](https://github.com/rtk-ai/rtk) to produce concise, LLM-friendly
output — reducing token consumption by 60-90% on common shell commands
(`cat`, `grep`, `find`, `ls`, etc.).

<img width="647" height="578" alt="image"
src="https://github.com/user-attachments/assets/438de126-c79d-4b69-bf3b-65a220671900"
/>



Fixes #13600

### Why we need it and why it was done in this way

rtk is a single Rust binary (MIT licensed, zero dependencies) that
rewrites shell commands into optimized versions. When a command has no
rtk equivalent, it passes through unchanged — zero risk of breaking
existing workflows.

The integration follows three layers:
1. **Build time**: `scripts/download-rtk-binaries.js` downloads
platform-specific rtk and jq binaries into `resources/binaries/`
(bundled via existing `asarUnpack: resources/**`)
2. **First run**: `extractRtkBinaries()` copies binaries from app
resources to `~/.cherrystudio/bin/` (follows existing binary
distribution pattern used by bun, uv, openclaw)
3. **Runtime**: A `PreToolUse` hook in the Claude Code service
intercepts Bash tool calls, runs `rtk rewrite "<command>"`, and
substitutes the optimized command if available

The following tradeoffs were made:
- Binaries are bundled in the app package (increases app size ~5MB)
rather than downloaded on demand — ensures rtk is always available
without network dependency
- jq is bundled alongside rtk for potential external hook script usage,
even though the TypeScript hook doesn't need it
- `rtkRewrite()` is async (non-blocking) to avoid stalling the main
process event loop

The following alternatives were considered:
- On-demand download (like bun/uv install scripts) — rejected because
rtk should "always be on" per requirements
- System PATH detection only — rejected because it requires users to
install rtk manually

### Breaking changes

None. The rtk rewrite is transparent and falls back gracefully when rtk
is unavailable or a command has no optimized version.

### Special notes for your reviewer

- The download script is non-fatal: if binary download fails during
build, the build continues without rtk
- The hook runs before the existing `preToolUseHook` (permission
handling), so commands are rewritten before permission checks
- Version guard requires rtk >= 0.23.0 (when `rtk rewrite` subcommand
was introduced)

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: Write code that humans can understand and Keep it simple
- [x] Refactor: You have left the code cleaner than you found it (Boy
Scout Rule)
- [ ] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A user-guide update was considered and is present
(link) or not required
- [x] Self-review: I have reviewed my own code before requesting review
from others

### Release note

```release-note
Integrate rtk to automatically optimize agent shell commands for 60-90% token savings
```

---------

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
2026-03-30 11:13:35 +08:00
beyondkmp
1161141d00 refactor(agents): replace postinstall patch with SDK's spawnClaudeCodeProcess option (#13886)
### What this PR does

Before this PR:
The Claude Agent SDK's minified `sdk.mjs` was patched at `postinstall`
time via regex-based replacements (`scripts/patch-claude-agent-sdk.ts`)
to convert `spawn` to `fork` with IPC stdio. This broke on every SDK
version bump when variable names changed.

After this PR:
Uses the SDK's built-in `spawnClaudeCodeProcess` option (available since
v0.2.81) to provide a custom `fork()`-based process spawner, eliminating
the need for any postinstall patching.

### Why we need it and why it was done in this way

The following tradeoffs were made:
- We handle stderr ourselves in the custom spawner because the SDK only
reads stderr inside its internal `spawnLocalProcess()`, not when
`spawnClaudeCodeProcess` is provided.

The following alternatives were considered:
- Keeping the postinstall patch: rejected because it's fragile and
breaks on SDK updates.
- Using `spawn` with explicit `node` path: rejected because `fork()`
automatically uses `process.execPath` which works reliably in both dev
and packaged Electron (where `node` may not be on PATH).

### Breaking changes

None.

### Special notes for your reviewer

- `fork()` with `ELECTRON_RUN_AS_NODE=1` makes the Electron binary act
as Node.js, which is the same behavior the postinstall patch achieved.
- The IPC channel (`'ipc'` in stdio) is added for safety/compatibility
but isn't actively used by the SDK protocol (confirmed by searching the
minified source for `.send()` and `on('message')` on the process object
— zero hits).
- Net deletion of ~600 lines.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: Write code that humans can understand and Keep it simple
- [x] Refactor: You have left the code cleaner than you found it (Boy
Scout Rule)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: Not required — internal refactoring with no
user-facing change
- [x] Self-review: I have reviewed my own code before requesting review
from others

### Release note

```release-note
NONE
```

Signed-off-by: beyondkmp <beyondkmp@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 10:49:48 +08:00
fullex
3b2b4d74d5 Merge branch 'main' of github.com:CherryHQ/cherry-studio into v2 2026-03-24 07:37:41 -07:00
亢奋猫
f9f122a635 fix(deps): add @napi-rs/canvas platform binaries to fix startup crash (#13759)
### What this PR does

Before this PR:
The app crashes on startup with `ReferenceError: DOMMatrix is not
defined` because `@napi-rs/canvas` platform-specific binaries were
missing from `optionalDependencies`.

<img width="620" height="587" alt="image"
src="https://github.com/user-attachments/assets/20269613-cb89-460b-8854-2140ecac289c"
/>

After this PR:
Platform-specific binaries for `@napi-rs/canvas` are properly declared
in `optionalDependencies` and handled in `before-pack.js`, fixing the
startup crash.

### Why we need it and why it was done in this way

The following tradeoffs were made:
- Only mainstream platforms are included (Linux x64/arm64 glibc/musl,
macOS x64/arm64, Windows x64/arm64)
- Excluded rarely-used platforms: linux-arm-gnueabihf (32-bit ARM),
linux-riscv64-gnu (RISC-V), android-arm64 (Android)

The following alternatives were considered:
None - this follows the existing pattern used for `@img/sharp`,
`@libsql`, and `@napi-rs/system-ocr` packages.

### Breaking changes

None.

### Special notes for your reviewer

This change follows the existing pattern in `before-pack.js` for
handling platform-specific native dependencies.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

```release-note
fix: resolve app startup crash caused by missing @napi-rs/canvas platform binaries (DOMMatrix is not defined)
```

Signed-off-by: kangfenmao <kangfenmao@qq.com>
2026-03-24 17:23:48 +08:00
Phantom
0a7a2b9381 refactor: resolve all no-floating-promises lint violations (#13743)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:00:03 +08:00
fullex
cc697894fc Merge branch 'main' of github.com:CherryHQ/cherry-studio into v2
Signed-off-by: fullex <0xfullex@gmail.com>

# Conflicts:
#	.agents/skills/.gitignore
#	.agents/skills/public-skills.txt
#	.claude/skills/.gitignore
#	.github/CODEOWNERS
#	.oxlintrc.json
#	CLAUDE.md
#	biome.jsonc
#	package.json
#	src/main/services/AnalyticsService.ts
#	src/main/services/AppUpdater.ts
#	src/main/services/agents/services/SessionService.ts
#	src/renderer/src/Router.tsx
#	src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts
#	src/renderer/src/components/Icons/SVGIcon.tsx
#	src/renderer/src/components/Popups/UpdateDialogPopup.tsx
#	src/renderer/src/components/app/Sidebar.tsx
#	src/renderer/src/config/minapps.ts
#	src/renderer/src/config/providers.ts
#	src/renderer/src/config/sidebar.ts
#	src/renderer/src/hooks/agents/useActiveAgent.ts
#	src/renderer/src/hooks/agents/useAgentSessionInitializer.ts
#	src/renderer/src/hooks/agents/useAgents.ts
#	src/renderer/src/hooks/agents/useCreateDefaultSession.ts
#	src/renderer/src/hooks/useApiServer.ts
#	src/renderer/src/hooks/useAppUpdate.ts
#	src/renderer/src/pages/agents/AgentSettingsTab.tsx
#	src/renderer/src/pages/agents/components/AgentSessionInputbar.tsx
#	src/renderer/src/pages/agents/components/Sessions.tsx
#	src/renderer/src/pages/home/Chat.tsx
#	src/renderer/src/pages/home/HomePage.tsx
#	src/renderer/src/pages/home/Inputbar/tools/components/WebSearchQuickPanelManager.tsx
#	src/renderer/src/pages/home/Messages/MessageHeader.tsx
#	src/renderer/src/pages/home/Navbar.tsx
#	src/renderer/src/pages/home/Tabs/AssistantsTab.tsx
#	src/renderer/src/pages/home/Tabs/SessionsTab.tsx
#	src/renderer/src/pages/home/Tabs/TopicsTab.tsx
#	src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx
#	src/renderer/src/pages/home/Tabs/components/AssistantTagGroups.tsx
#	src/renderer/src/pages/home/Tabs/components/Topics.tsx
#	src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx
#	src/renderer/src/pages/home/Tabs/components/UnifiedList.tsx
#	src/renderer/src/pages/home/Tabs/hooks/useActiveAgent.ts
#	src/renderer/src/pages/home/Tabs/index.tsx
#	src/renderer/src/pages/home/components/ChatNavBar/ChatNavbarContent/index.tsx
#	src/renderer/src/pages/home/components/ChatNavBar/Tools/SettingsButton.tsx
#	src/renderer/src/pages/home/components/ChatNavBar/Tools/SettingsTab/AssistantSettingsTab.tsx
#	src/renderer/src/pages/settings/AboutSettings.tsx
#	src/renderer/src/pages/settings/AgentSettings/components/ModelSetting.tsx
#	src/renderer/src/pages/settings/AssistantSettings/AssistantModelSettings.tsx
#	src/renderer/src/pages/settings/WebSearchSettings/BasicSettings.tsx
#	src/renderer/src/pages/settings/WebSearchSettings/WebSearchProviderSetting.tsx
#	src/renderer/src/pages/settings/WebSearchSettings/index.tsx
#	src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts
#	src/renderer/src/store/runtime.ts
#	src/renderer/src/store/thunk/__tests__/streamCallback.integration.test.ts
#	src/renderer/src/types/index.ts
#	tsconfig.node.json
#	tsconfig.web.json
2026-03-17 06:17:41 -07:00
Phantom
fe0678a206 chore: migrate .claude/skills to directory symlinks (#13486)
### What this PR does

Before this PR:

`.claude/skills/<name>/SKILL.md` files were **copied** from
`.agents/skills/<name>/SKILL.md` via `pnpm skills:sync`. A dedicated
`skills-check-windows` CI job ran on `windows-latest` to verify
cross-platform file-copy compatibility.

After this PR:

`.claude/skills/<name>` entries are **directory symlinks** pointing to
`../../.agents/skills/<name>`, following the Single Source of Truth
(SSoT) principle. The Windows-specific CI job is removed; Windows
developers are expected to enable symlink support.

### Why we need it and why it was done in this way

The following tradeoffs were made:

- Windows developers must now manually enable symlink support (Developer
Mode + `git config --global core.symlinks true`). This is acceptable
because:
1. The existing `AGENTS.md` is already a symlink, so Windows
compatibility was never fully enforced.
2. Symlinks eliminate the need for file-copy synchronization, reducing
maintenance complexity.
3. Contributors are expected to have sufficient technical capability to
configure their environments.

The following alternatives were considered:

- Keeping file-copy sync: rejected because it duplicates content and
requires extra CI to verify consistency.

### Breaking changes

Windows developers who clone without symlink support enabled will get
plain text files instead of symlinks. They must:
1. Enable Developer Mode or grant `SeCreateSymbolicLinkPrivilege`
2. Run `git config --global core.symlinks true`
3. Re-clone or run `pnpm skills:sync`

### Special notes for your reviewer

- The `.github/workflows/ci.yml` diff includes minor quote-style changes
(`'` → `"`) from the YAML formatter — these are cosmetic only.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [x] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [ ] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

```release-note
NONE
```

Signed-off-by: icarus <eurfelux@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 08:46:46 +08:00
fullex
2104e234af Merge 'main' into v2 2026-03-12 02:20:13 -07:00
SuYao
18ec986157 revert: remove Feishu at-mention from issue notification card (#13201)
### What this PR does

Before this PR:
Attempted to add Feishu @mention notifications in issue cards using `<at
id=...></at>` tags with various ID formats (app ID, open_id).

After this PR:
Reverted all mention-related changes. The Feishu card content is
restored to its original state without @mention tags.

### Why we need it and why it was done in this way

Testing confirmed that while the `<at>` tag with open_id displays
correctly in the card, Feishu does not provide a message receive API for
bots — so bot users cannot actually receive @mention notifications. The
feature is not feasible with the current Feishu API capabilities.

### Breaking changes

None

### Special notes for your reviewer

This PR reverts the mention feature added in #13199 and all subsequent
test commits. Net diff against `main` should only contain the revert of
#13199.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: Write code that humans can understand and Keep it simple
- [x] Refactor: You have left the code cleaner than you found it (Boy
Scout Rule)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: N/A - no user-facing change
- [x] Self-review: I have reviewed my own code before requesting review
from others

### Release note

```release-note
NONE
```

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:43:59 +08:00
Phantom
7f24cd0a01 fix: move OpenAPI spec generation to build time (#13207)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:27:08 +08:00
SuYao
1e4445962e feat: mention Feishu user in issue notification card (#13199)
### What this PR does

Before this PR:
Feishu issue notification cards did not @mention any specific user, so
notifications could be easily missed.

After this PR:
The issue notification card now includes an `<at>` element that
@mentions the Feishu user `cli_a92d6e7ba3f85ced` in the card body,
ensuring they receive a direct notification for every new GitHub issue.

### Why we need it and why it was done in this way

The Feishu user needs to be actively notified when new GitHub issues are
created. By embedding the `<at id=cli_a92d6e7ba3f85ced></at>` tag
directly in the card's lark_md content, the user gets a native Feishu
@mention notification without requiring changes to the workflow or CLI
interface.

The following tradeoffs were made:
- The Feishu user ID is hardcoded in the card template rather than
passed as a CLI option. This is simpler and sufficient for the current
use case.

The following alternatives were considered:
- Adding a `--mention` CLI option: More flexible but unnecessary
complexity for a single user.
- Adding the @mention via the workflow prompt: Less reliable since it
depends on Claude's output.

### Breaking changes

None.

### Special notes for your reviewer

The TypeScript diagnostics shown for `feishu-notify.ts` (missing
`process`, `Buffer`, etc.) are pre-existing and unrelated to this change
— the script runs via `npx tsx` and is outside the project's tsconfig
scope.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

```release-note
NONE
```

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:01:23 +08:00
SuYao
9c9739c6b1 fix(build): switch Windows code signing timestamp server to DigiCert (#13189)
### What this PR does

Before this PR: Windows code signing uses `timestamp.comodoca.com` as
the timestamp server, which is unreliable and frequently times out.

After this PR: Switches to `timestamp.digicert.com`, a more stable and
widely-used timestamp server.

### Why we need it and why it was done in this way

The Comodo timestamp server (`timestamp.comodoca.com`) has been
experiencing frequent connectivity issues, causing code signing failures
during Windows builds. DigiCert's timestamp server is industry-standard
and known for better reliability.

The following tradeoffs were made: N/A

The following alternatives were considered: N/A

### Breaking changes

None

### Special notes for your reviewer

Single-line URL change in the signing script.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

```release-note
NONE
```

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:26:32 +08:00
SuYao
e72bde30eb refactor: replace static pnpm patch with postinstall script for claude-agent-sdk (#13139)
### What this PR does

Before this PR:
`claude-agent-sdk` is patched via a static pnpm `.patch` file that
replaces entire minified lines. This breaks every time the SDK is
upgraded because obfuscated variable names change with each
minification.

After this PR:
A Node.js postinstall script (`scripts/patch-claude-agent-sdk.mjs`) uses
semantic regex patterns to apply the same 3 patches. Since it matches
structural patterns (not variable names), it survives SDK version bumps
as long as the code structure remains the same.

### Why we need it and why it was done in this way

The following tradeoffs were made:
- Regex-based patching is slightly less precise than a static `.patch`
file, but far more resilient to minified code changes.
- The script validates all 3 patches applied and exits with error if
patterns don't match, so SDK structure changes are caught immediately.

The following alternatives were considered:
- Keeping the pnpm patch approach — rejected because it requires manual
regeneration on every SDK upgrade.
- Using AST-based patching — rejected as overkill for 3 targeted
replacements in minified code.

### Breaking changes

None. The same 3 modifications are applied (spawn→fork, remove command
destructuring, IPC stdio), just via a different mechanism.

### Special notes for your reviewer

The 3 patches applied by the script:
1. `import{spawn as X}` → `import{fork as X}` — enables IPC channel
2. Remove `command:VAR,` from `spawnLocalProcess` destructuring
3. Rewrite spawn call to `fork(args[0], args.slice(1), ...)` with IPC
stdio, removing `windowsHide:!0`

43 unit tests cover: variable name variations, idempotency, partial
matches, and no-match detection.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: Write code that humans can understand and Keep it simple
- [x] Refactor: You have left the code cleaner than you found it (Boy
Scout Rule)
- [ ] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: Not required — internal build tooling change
- [x] Self-review: I have reviewed my own code before requesting review
from others

### Release note

```release-note
NONE
```

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:23:49 +08:00
fullex
f668fce4f2 merge main into v2 2026-02-28 10:11:18 +08:00
Phantom
e61e1bb672 feat: Optimize PR workflow with on-demand skill loading and project-level skills management (#12943)
### What this PR does

Before this PR:
- CLAUDE.md contained detailed PR workflow instructions that were loaded
in every agent session, consuming unnecessary tokens
- No unified project-level skills management mechanism; adding public
skills lacked standardization
- No automated checks to prevent non-compliant skills from being merged
- Team members had no convenient way to share skills with each other

After this PR:
- Simplified PR instructions in CLAUDE.md, now loaded on-demand via the
`gh-create-pr` skill
- Introduced project-level skills management (`.agents/skills/`
directory + `public-skills.txt` whitelist)
- Added `scripts/skills-sync.ts` and `scripts/skills-check.ts` for
automated management
- Integrated skills validation into CI to prevent non-whitelisted skills
from being merged
- **Teams can now easily share skills through the project-level
mechanism**, with `skills-sync.ts` automatically syncing skills to all
team members' local environments, streamlining onboarding and avoiding
duplicated configuration efforts
- **Optimized PR creation workflow**: `gh-create-pr` skill enforces
English PR body writing and displays the draft to users for review
before creation, ensuring quality and compliance

Fixes #

### Why we need it and why it was done in this way

The following tradeoffs were made:
- Moved PR workflow from CLAUDE.md to a skill, sacrificing immediate
visibility for token efficiency
- **Introduced whitelist mechanism (`public-skills.txt`) instead of
auto-scanning all files**: Allows developers to freely use private
project-level skills in the `.agents/skills/` directory (e.g.,
team-internal skills, personal customizations). Only skills added to the
whitelist are tracked by git and submitted. This ensures standardization
for shared skills while preserving development flexibility
- Skills exist in both `.agents/skills/` (project-level, shareable) and
`.claude/skills/` (local, private)
- **Symlink only SKILL.md files instead of entire directories**: On some
Windows/restricted filesystems, symlinks may fail or be treated as
regular files. If an entire directory is symlinked, failure results in a
regular file instead of a directory, causing complete skill failure
that's hard to diagnose. Symlinking only SKILL.md allows quick detection
when symlinks fail (file content displays directly or errors), reducing
troubleshooting costs

The following alternatives were considered:
- Keeping PR instructions in CLAUDE.md with collapsible blocks, but this
still consumes context tokens
- Using git hooks for pre-commit checks, but CI checks are more reliable
and don't block local development

Links to places where the discussion took place: N/A

### Breaking changes

None

### Special notes for your reviewer

- `gh-create-pr` skill fully implements the project's PR workflow
requirements (read template → display body → confirm → create)
- `skills-check.ts` validates: 1) tracked skills are in the whitelist;
2) whitelist skills have corresponding files
- Process for adding new public skills: 1) create skill files; 2) add to
`public-skills.txt`; 3) CI auto-validation
- `.claude/skills/` added to `.gitignore` for private skills

### Checklist

This checklist is not enforcing, but it's a reminder of items that could
be relevant to every PR.
Approvers are expected to review this list.

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
is present (link) or not required. You want a user-guide update if it's
a user facing feature.
- [ ] Documentation: A user-guide update was considered and is present
(link) or not required. You want a user-guide update if it's a user
facing feature.

### Release note

```release-note
Optimize PR workflow by moving instructions to on-demand skill; introduce project-level skills management with automated validation
```
2026-02-17 00:37:32 +08:00
SuYao
26e53c9a4b fix(openclaw): fix Node.js detection for nvm/mise/fnm-managed installations (#12902)
### What this PR does

Before this PR:
- On **Windows**, `getLoginShellEnvironment()` runs `cmd.exe /c set`
which just inherits the parent (Electron) process's env — it does NOT
re-read the Windows registry. If Node.js is installed via MSI after the
app launches, the captured PATH is stale. This causes npm preinstall
scripts to fail: npm itself runs (found via `commonPaths` filesystem
fallback), but `cmd.exe /d /s /c node ./engine-requirements.js` can't
find `node` in the stale PATH. On Unix this isn't an issue because `zsh
-ilc env` sources profile files and picks up nvm/mise/fnm PATH changes.
- On **Unix**, OpenClaw fails to start/install when Node.js is installed
via nvm, mise, or fnm after Cherry Studio has launched, because the
cached shell environment is stale.
- `findExecutableInEnv` has a hidden side effect of refreshing the shell
env cache, making it unpredictable.
- `startGateway` uses stale env because `findOpenClawBinary` refreshes
the cache but the gateway spawn uses a different (stale) env.
- Install process mutates the shared cached env object, polluting all
future callers.
- Inconsistent spawn strategy: install/uninstall use `spawn()` + `shell:
true` while gateway operations use `spawnWithEnv()`.

After this PR:
- **Windows**: skip the useless `cmd.exe /c set` entirely. Instead, copy
`process.env` as the base (same result, but faster), then read the
**current** system + user PATH from the Windows registry via `reg
query`, expand `%VAR%` references, and replace the stale PATH. This
ensures newly installed tools (e.g. Node.js MSI) are found immediately.
- **Unix**: unchanged — `zsh -ilc env` still sources profile files
correctly.
- Shell env cache follows CQS (Command-Query Separation):
`getShellEnv()` is a pure query, `refreshShellEnv()` is an explicit
command.
- `findExecutableInEnv` no longer refreshes the cache — callers
explicitly call `refreshShellEnv()` when they need fresh env.
- `startGateway` refreshes env first, then passes it to both
`findOpenClawBinary` and `crossPlatformSpawn`.
- Install process clones the cached env before modifying PATH (`{
...await getShellEnv() }`).
- All spawn calls unified to `crossPlatformSpawn` (handles Windows
`.cmd` files via `cmd.exe /c`).
- OpenClaw UI now checks Node.js version (≥18) and git availability
before install, with download URL hints and automatic polling for newly
installed tools.
- Unit tests added for the new Windows registry PATH resolution logic
(10 test cases).

<img width="1019" height="765" alt="image"
src="https://github.com/user-attachments/assets/5f6a4d27-6d3e-4033-8309-0c98bf8cba4c"
/>


### Why we need it and why it was done in this way

**Why registry reads instead of `cmd.exe /c set`?**

On Windows, `cmd.exe /c set` inherits the parent process env unchanged.
Unlike Unix shells that source `~/.bashrc`/`.zshrc` on launch, `cmd.exe`
does not re-read the registry. When a user installs Node.js (via MSI,
Scoop, etc.) after Cherry Studio is already running, the new PATH
entries only exist in the registry — not in the Electron process's
inherited env. Reading `HKLM\...\Environment` (system PATH) and
`HKCU\Environment` (user PATH) directly gives us the ground-truth PATH
at the time of the call.

**Why `execFileSync` instead of `crossPlatformSpawn`/`executeCommand`?**

1. **Circular dependency**: `executeCommand` internally calls
`getShellEnv()` to obtain env. Since `queryRegValue` is called *by*
`getShellEnv` → `getLoginShellEnvironment` → `getWindowsEnvironment`,
using `executeCommand` would create an infinite recursion.
2. **Synchronous is appropriate**: `reg query` completes in
milliseconds. Keeping it synchronous allows `getWindowsEnvironment()` to
return directly via `Promise.resolve()`, simplifying the control flow.
3. **No `.cmd` shim handling needed**: `reg.exe` is a native executable
— it doesn't need the `cmd.exe /c` wrapping that `crossPlatformSpawn`
provides.
4. **Security**: `execFileSync` executes the binary directly without
shell interpolation, avoiding command injection risk.

**Why expand `%VAR%` manually?**

Windows registry stores PATH as `REG_EXPAND_SZ` with embedded references
like `%SystemRoot%\system32`. The `reg query` output returns the raw
string without expansion. We expand these references against the current
`process.env` using case-insensitive lookup to match Windows behavior.

The following tradeoffs were made:
- CQS over convenience: callers must now explicitly call
`refreshShellEnv()` before `findExecutableInEnv()` when they need fresh
env. This adds a line of code at call sites but makes the caching
behavior predictable and eliminates hidden side effects.
- Clone-on-modify over freeze: we spread-clone the env object in
`install()` rather than `Object.freeze()` the cache, because freeze
would break callers that legitimately need to add env vars (e.g.,
`OPENCLAW_CONFIG_PATH`).

The following alternatives were considered:
- Making `getShellEnv()` always return a frozen copy — rejected because
it would require all callers to spread, even those that only read.
- Extracting a shared function for install/uninstall — rejected (Rule of
Three: only 2 instances, with semantic differences in error handling and
sudo retry).
- Using PowerShell `[Environment]::GetEnvironmentVariable` instead of
`reg query` — rejected because it has a much higher startup cost (~200ms
vs ~5ms) and requires detecting PowerShell availability.

Links to places where the discussion took place: N/A

### Breaking changes

None. All changes are internal to the main process. No Redux/IndexedDB
schema changes.

### Special notes for your reviewer

- **New file**: `src/main/utils/__tests__/shell-env.test.ts` — 10 test
cases covering registry PATH resolution (stale replacement, system+user
combination, `%VAR%` expansion, REG_SZ vs REG_EXPAND_SZ, fallback
behavior, cherry bin append, no cmd.exe spawn).
- **New helpers in `shell-env.ts`**: `queryRegValue()`,
`expandWindowsEnvVars()`, `readWindowsRegistryPath()`,
`getWindowsEnvironment()` — all private, tested through the public
`refreshShellEnv()` API.
- Function renames: `spawnWithEnv` → `crossPlatformSpawn`,
`executeInEnv` → `executeCommand` — names now reflect actual
responsibility (Windows `.cmd` adaptation, not "env injection").
- `checkNodeVersion` returns a discriminated union `{ status:
'not_found' } | { status: 'version_low'; version; path } | { status:
'ok'; version; path }` instead of the previous `checkNpmAvailable`
boolean.
- i18n keys renamed from `openclaw.node_required.*` →
`openclaw.node_missing.*` / `openclaw.node_version_low.*` with
translations for all supported locales.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780096809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. You want a
user-guide update if it's a user facing feature.

### Release note

```release-note
fix(shell-env): on Windows, read PATH from registry instead of inheriting stale Electron process env; fix Node.js detection for nvm/mise/fnm-managed installations; add version check (≥18) and git availability check with download hints in OpenClaw setup UI
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dev <dev@cherry-ai.com>
Co-authored-by: kangfenmao <kangfenmao@qq.com>
2026-02-14 13:13:20 +08:00
fullex
072dd67a87 merge main into v2 2026-02-09 13:02:41 +08:00
SuYao
6b9daa749e feat: add plugin package installation from ZIP,directory, remote (#12426)
* feat: add plugin install

* fix: some bug

* fix: i18n

* refactor: rename 'active-directory' to 'resource'

* feat(plugin): support remote fetch plugin

* refactor: improve error display

* feat: add cache protocol

* fix(plugin): address code review issues for PR #12426

- Fix command injection vulnerability by adding -- separator before
  positional arguments in git clone and ls-remote commands
- Extract duplicate file helpers (directoryExists, fileExists, pathExists)
  to shared @main/utils/file utility
- Add depth limit (MAX_PLUGIN_ROOT_DEPTH=10) to findPluginRoots to
  prevent infinite recursion from symlink cycles
- Rename InstallFromZipResult to InstallFromSourceResult with type alias
  for backward compatibility
- Extract duplicate loadFirstPage logic in useMarketplaceBrowser hook
- Add documentation for intentionally disabled readContent and
  invalidateCache methods explaining API compatibility reasons

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs(plugin): add documentation for marketplace API and telemetry

- Document MARKETPLACE_API_BASE_URL with API endpoints and usage
- Add TODO for China mainland accessibility testing
- Document reportSkillInstall telemetry behavior and data transmitted

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(plugin): fix skill installation and improve UI

- Fix skill installation using v2 resolve API endpoint
- Extract base repo URL from GitHub tree/blob URLs
- Fix skill card type label (skill -> skills key mapping)
- Split plugin settings into "Available Plugins" and "Installed Plugins" tabs
- Fix refresh button styling with aspect-square
- Add card vertical spacing with pb-4
- Add unit tests for extractBaseRepoUrl and extractResolvedSkill

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(plugin): use useTimer for search debounce

Replace manual setTimeout/clearTimeout with useTimer hook for better
timer management and automatic cleanup on component unmount.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* style(plugin): use explicit unknown type in catch blocks

Change all catch blocks from `catch (error)` to `catch (error: unknown)`
for better type safety and code clarity.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(plugin): only show content section for installed plugins

- Skip content fetching for marketplace plugins since readContent
  always fails for remote plugins
- Only display the Content section when viewing installed plugins
- Change catch (error) to catch (error: unknown) for type safety

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(plugin): use Zod schemas for marketplace API responses

- Add PluginResolveResponseSchema for plugin resolution API
- Add ResolvedSkillSchema and SkillsResolveResponseSchema for v2 skills API
- Refactor extractRepositoryUrl to use Zod safeParse instead of manual type checking
- Refactor resolveSkillV2 to validate response with Zod and return typed array
- Update extractResolvedSkill to accept typed array directly
- Update tests to match new function signatures

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(plugin): simplify code and reduce duplication

- Merge runCommand and captureCommand into executeCommand in PluginService
- Combine URL regex patterns and use constant map for plugin directories
- Create generic response parser factory in MarketplaceService
- Extract buildSkillSourceKey helper in useMarketplaceBrowser
- Remove unused displayedEntries variable in PluginBrowser
- Consolidate category config into single object in useResourcePanel
- Extract ensureCacheData helper method in PluginCacheStore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(plugin): remove dead readContent and invalidateCache methods

Remove methods that were non-functional (always throwing or no-op):
- Remove invalidateCache() no-op method from PluginService
- Remove readContent() method that always throws
- Remove corresponding IPC handlers and preload bindings
- Remove IpcChannel enum entries for both methods
- Remove content section from PluginDetailModal (relied on readContent)
- Remove agentId prop from PluginBrowser (no longer needed)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(plugin): update plugin settings and UI components

- Change default page size in useMarketplaceBrowser from 100 to 40 for improved performance.
- Add titles to the installed plugins section in multiple language files for better clarity.
- Refactor AgentSettings components to improve structure and readability, including the introduction of a new PluginsSettings component.
- Update modal widths in AgentSettingsPopup and SessionSettingsPopup for better UI consistency.
- Integrate Scrollbar component in SettingsContainer for enhanced scrolling experience.

* refactor(settings): improve Scrollbar import and enhance type safety

- Change Scrollbar import to use type-only import for ScrollbarProps.
- Update handleVirtualChange function to handle null scrollOffset for better type safety.

* fix(plugin): install only selected plugin and fix uninstall lookup

Bug fixes:
- Install only the specific requested plugin from marketplace repo
  instead of installing all plugins found in the repository
- Use actual installed filename for uninstall instead of marketplace
  metadata filename (which differs due to sanitization)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(plugin): support skill upload via drag-and-drop

Extended installFromSourceDir to detect and install skills:
- Check for plugin roots first (with .claude-plugin/plugin.json)
- If none found, search for skill directories (with SKILL.md)
- Install whichever type is detected

Added new methods:
- installSkillRoots: Install multiple skill directories
- installSkillFromDirectory: Install a single skill folder

Now users can drag-and-drop skill folders or ZIPs containing
skills to install them, not just plugin packages.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(browser): add beforeEach to reset mock state

Add vi.clearAllMocks() in beforeEach to prevent state leakage
between tests, which could cause flaky test failures in CI.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: kangfenmao <kangfenmao@qq.com>
2026-02-04 14:26:37 +08:00
SuYao
cb865b0faf fix: hardcoded ui string (#12655) 2026-01-30 17:49:21 +08:00
SuYao
5c013cdb63 feat(i18n): add hardcoded string detection script and CI check (#12547)
* feat(i18n): clean hardcoded ui string add ci

* fix(i18n): update snap

* fix: test

* fix(i18n): update plurals

* chore: revert other branch change

* refactor: use ast detect hardcoded string

* chore: typo error

* feat(i18n): add webview app

* chore(i18n): update i18n

* fix: pr comment

* chore: distinguish label and labelKey

* refactor: align v2 desgin
2026-01-24 18:19:38 +08:00