Adds IPC schemas/handlers for Code CLI actions, moves launch flow to the new request API, and extends support to Gemini CLI, Qwen Code, Kimi Code, Qoder CLI, and GitHub Copilot CLI. Also updates native config injection/cleanup, provider ordering, and the code tools UI to support providerless launches and the new tool metadata.
Main-process half of the code-cli refactor (split from renderer changes).
- Persist provider configs to native CLI config files (claude-code/codex/opencode/hermes/openclaw) instead of launch env vars
- Multi-config per CLI tool (named configs in preference)
- Migrate OpenClaw command IPC to IpcApi (openclaw.*), drop legacy IpcChannel.OpenClaw_* handlers and install-progress broadcast
- Keep OpenClawService syncConfig/resolveSyncConfig logic + install dedup from #16105; only the IPC surface is migrated
- Add per-CLI config writers (claudeCodeConfig/codexConfig/openCodeConfig/hermesConfig/openclawConfig) with unit tests
- Remove v1 CodeCliTransforms migrator + tests
- Add smol-toml/json5/js-yaml deps
Part 1 of 2. Renderer changes land in the stacked follow-up PR.
Signed-off-by: Pleasurecruise <3196812536@qq.com>
### 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>
Rename PowerMonitorService -> PowerService and relocate it to
src/main/core/power/, turning the shutdown-only service into a complete,
self-contained power hub:
- Typed power events (suspend/resume/lock/unlock/power-source) with
suspend/resume + power-source de-duplication; lock/unlock pass-through.
- Bounded, cross-platform shutdown barrier: preventDefault on macOS/Linux,
blockShutdown via a minimal hidden window on Windows; handlers run under a
5s hard timeout, then the app quits through the normal quit flow.
- Best-effort, ref-counted sleep prevention: preventSleep(reason) returns a
Disposable and never throws (the provider degrades internally). The OS
blocker engages only while a hold is held AND the user enabled
app.power.prevent_sleep_when_busy. JobManager is the first registrant,
acquiring a hold per execution attempt.
Add the app.power.prevent_sleep_when_busy preference (v2-only, no v1 source)
and a Settings -> General toggle. Service phase moved Background -> WhenReady.
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.
### What this PR does
Before this PR:
Web Search settings still depended on legacy renderer-side settings
wiring and provider-specific UI structure. Provider selection, API key
editing, blacklist handling, and compression settings were split across
legacy hooks/routes and were not aligned with the v2 preference/provider
configuration model.
After this PR:
Web Search settings are migrated to the v2 data and architecture path:
- Adds `useWebSearch` as the renderer settings hook backed by v2
preference APIs.
- Rebuilds the Web Search settings page around smaller components,
hooks, and utilities for provider metadata, API keys, defaults,
blacklist, and provider checks.
- Uses v2 provider presets and resolved provider capability data for
keyword search and URL fetch defaults.
- Updates preference mappings/classification for removed or normalized
Web Search settings.
- Adds focused tests for the new settings hooks, provider metadata, API
key handling, blacklist parsing, and provider check behavior.
- Documents user-visible v2 Web Search setting changes in the breaking
changes log.
<!-- (optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)`
format, will close the issue(s) when PR gets merged)*: -->
Fixes # N/A
### Why we need it and why it was done in this way
The v2 refactor is moving settings and provider configuration away from
legacy renderer-local state. Web Search settings need to read and write
through the same v2 preference source of truth as the main-side Web
Search service so the UI, migration layer, and runtime configuration use
one provider model.
The following tradeoffs were made:
- The PR keeps the work scoped to Web Search settings and the
preference/provider metadata needed by that settings surface.
- Deprecated settings are removed instead of preserved through
compatibility UI, because v1 Web Search settings are throwaway during
the v2 refactor.
- The settings page is split into local hooks/components instead of
introducing a broader settings framework abstraction.
- Runtime service adjustments are limited to what is required by the
settings schema and preference changes.
The following alternatives were considered:
- Keeping the old `useWebSearchProviders` path as a compatibility layer,
but that would continue dual settings ownership during the v2 migration.
- Preserving the removed subscription-source and cutoff-unit UI, but
those settings do not map cleanly to the v2 Web Search configuration
model.
- Combining this with runtime Web Search tool behavior changes, but that
would make the PR too broad and harder to review.
Links to places where the discussion took place: N/A
### Breaking changes
This PR removes deprecated Web Search settings from the v2 settings
surface:
- Web Search subscription-source management is removed.
- Compression cutoff is normalized to a token-based cutoff limit; the
cutoff unit selector is removed.
Impact: users will configure Web Search through v2 provider defaults,
API keys, capability hosts, blacklist, and compression settings. No
manual migration action is expected.
### Special notes for your reviewer
Scope of this PR:
- Web Search settings UI migration to v2 data/preferences.
- Renderer settings hooks/components/utilities for Web Search provider
configuration.
- Preference classification/mapping changes required by the settings
migration.
- Tests for the settings-side behavior introduced here.
Explicitly out of scope:
- Web Search runtime orchestration in chat responses.
- Built-in/external Web Search tool execution behavior.
- Message rendering for Web Search tool results or citations.
- Model switching behavior and assistant-level `enableWebSearch`
semantics outside the settings migration.
- Large-scale UI design-system replacement beyond the files touched for
this settings migration.
- New provider integrations or provider search algorithm changes.
Review scope note: this PR is based on `v2`, but the intended review
scope is limited to the Web Search settings migration described above.
Web Search runtime/service/tool changes that are already part of the
prerequisite v2 Web Search work are not part of this PR's review scope.
Validation:
- Not run in this PR creation pass.
### 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
- [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.
Present in `v2-refactor-temp/docs/breaking-changes/`.
- [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
Web Search settings now use the v2 preference/provider configuration path. Deprecated subscription-source settings were removed, and compression cutoff is now token-based.
```
---------
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
### What this PR does
Before this PR:
The renderer-side translate module (`TranslatePage`, `TranslateHistory`,
`TranslateSettings`, `ActionTranslate`, etc.) relied on Redux store and
Dexie (IndexedDB) for state management, language data, and history
persistence. A monolithic `useTranslate` hook bundled all
translate-related logic together.
After this PR:
- The monolithic `useTranslate` hook is replaced with **9 granular
hooks** (`useAddHistory`, `useDeleteHistory`, `useClearHistory`,
`useAddLanguage`, `useDeleteLanguage`, `useDetectLang`, `useLanguages`,
`useUpdateHistory`, `useUpdateLanguage`) backed by `usePreference`,
`useCache`, and `DataApi`.
- `TranslatePage`, `TranslateHistory`, `TranslateSettings`, and
`ActionTranslate` are migrated to the v2 Cache/Preference/DataApi
architecture.
- `TranslateService` is refactored to remove direct Redux/Dexie
dependencies and use `dataApiService` for language lookups.
- `TranslateHistory` component is split into
`TranslateHistory/index.tsx` and `TranslateHistoryItem.tsx` for better
separation of concerns.
- `CustomLanguageSettings` is renamed to `TranslateLanguageSettings`,
and `CustomLanguageModal` to `TranslateLanguagesModal`.
- Preference schemas and migration mappings are updated accordingly.
### Why we need it and why it was done in this way
This is part of the v2 data layer migration effort to remove Redux and
Dexie from the renderer process. The translate module is migrated to use
the new unified data architecture (Cache for transient UI state,
Preference for user settings, DataApi for persistent business data).
The following tradeoffs were made:
- Granular hooks increase the number of files but improve tree-shaking,
testability, and make each hook's responsibility clear.
- The `ActionTranslate` (selection window) component was significantly
refactored to use the new hooks while preserving the smart language
detection and bidirectional translation UX.
The following alternatives were considered:
- Keeping a single `useTranslate` hook wrapper was considered but
rejected in favor of composability and alignment with the v2 hook
patterns used elsewhere.
### Breaking changes
None. This is an internal refactoring with no user-facing behavior
changes.
### Special notes for your reviewer
- `target-key-definitions.json` has new preference keys added for
translate feature settings.
### 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)
- [ ] 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.
- [ ] 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>
Signed-off-by: jdzhang <625013594@qq.com>
Signed-off-by: kangfenmao <kangfenmao@qq.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: jdzhang <625013594@qq.com>
Co-authored-by: kangfenmao <kangfenmao@qq.com>
<!-- 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:
OpenClaw page state still depended on the legacy Redux slice for gateway
status, selected model, health checks, and port values, and the
LaunchPad OpenClaw entry navigated to a route that returned 404.
After this PR:
OpenClaw user preferences are stored in Preference, runtime gateway
snapshots are stored in Shared Cache, legacy selected model JSON is
migrated to `provider::model`, the page uses `@cherrystudio/ui`/Tailwind
instead of Antd, and LaunchPad opens `/app/openclaw`.
<!-- (optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)`
format, will close the issue(s) when PR gets merged)*: -->
Fixes #
N/A
### Why we need it and why it was done in this way
The following tradeoffs were made:
The Redux slice itself is left untouched; only the OpenClaw page
boundary and v2 migration mapping move data into Preference/Shared
Cache.
The following alternatives were considered:
A generated simple mapping was avoided because the selected model
requires JSON-to-UniqueModelId conversion, and runtime compatibility
reads from old Redux were avoided to keep the v2 migration one-way.
Links to places where the discussion took place: N/A
### Breaking changes
<!-- optional -->
If this PR introduces breaking changes, please describe the changes and
the impact on users.
None.
### Special notes for your reviewer
<!-- optional -->
Validation run locally: `pnpm format`, `pnpm lint`, `pnpm test`, and
`pnpm build: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
- [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.
- [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
OpenClaw now uses v2 Preference/Shared Cache storage and the LaunchPad OpenClaw shortcut opens the correct page.
```
---------
Signed-off-by: kangfenmao <kangfenmao@qq.com>
Co-authored-by: SuYao <sy20010504@gmail.com>
### What this PR does
Before this PR:
File processing on the `v2` branch was still described and wired around
split OCR / markdown APIs, legacy feature names, and feature-first
provider structure. OCR-like image text extraction and
document-to-markdown conversion did not share one task contract, and
provider task ids / polling details were harder to keep behind the
Main-process boundary.
After this PR:
`v2` file processing follows
`v2-refactor-temp/docs/fileProcessing/file-processing-service.md` as the
design baseline:
- exposes one Main-side task API: `startTask`, `getTask`, and
`cancelTask`
- replaces split file-processing IPC with `file-processing:start-task`,
`file-processing:get-task`, and `file-processing:cancel-task`
- renames features and preference keys to `image_to_text` and
`document_to_markdown`
- adds `FileProcessingTaskService` as the in-memory source of truth for
task ids, task state, progress, cancellation, TTL pruning, remote-poll
dedupe, and task change events
- keeps provider task ids, remote context, query context, abort
controllers, and in-flight polling inside Main-process task records
- maps completed results to artifacts: inline `text/plain` for
`image_to_text`, and persisted markdown file artifacts for
`document_to_markdown`
- reorganizes providers into processor-first handlers under
`src/main/services/fileProcessing/processors`
- moves Tesseract worker ownership under `processors/tesseract/runtime`
- removes the new file-processing module's old `ocr/` and `markdown/`
split directories after migrating their logic
- updates shared schemas, presets, preference generation, migration
mappings, and tests for the renamed feature model
The public file-processing contract is now:
```ts
await window.api.fileProcessing.startTask({
feature: 'image_to_text',
file,
processorId: 'tesseract'
})
await window.api.fileProcessing.getTask({ taskId })
await window.api.fileProcessing.cancelTask({ taskId })
```
Architecture overview:
```text
Renderer / upper-layer caller
|
| startTask / getTask / cancelTask
v
FileProcessingOrchestrationService
|
| Zod validation + delegation
v
FileProcessingTaskService
|
| taskId, task store, TTL, cancellation,
| background execution, remote polling, artifacts
v
processorRegistry[processorId].capabilities[feature]
|
+--> image-to-text handlers
| -> text/plain artifact
|
+--> document-to-markdown handlers
-> feature.files.data/fileId/file-processing/taskId/output.md
```
Notes:
- The new file-processing API does not keep facades for
`file-processing:extract-text`,
`file-processing:start-markdown-conversion-task`, or
`file-processing:get-markdown-conversion-task-result`.
- `FileProcessingOrchestrationService` is intentionally only the IPC
validation and delegation layer.
- Task state is Main-process runtime coordination state, not DataApi or
Cache state.
- Renderer task subscriptions, a global UI task center, and full
renderer business-flow migration are intentionally out of scope for this
PR.
- The legacy standalone OCR path outside the new file-processing module
can coexist during the v2 transition, but the new file-processing
interface is not polluted by those split-API types.
Fixes #N/A
### Why we need it and why it was done in this way
This PR makes OCR-style image text extraction and document-to-markdown
conversion use the same Main-process task model before renderer-side
adoption. The unified contract gives upper layers one way to start work,
query progress, handle failure, cancel work, and consume completed
artifacts without learning provider-specific polling details.
The following tradeoffs were made:
- Fast OCR now also goes through a task API, so callers need start/query
behavior instead of a direct `extractText -> text` call.
- Task state remains session-scoped in memory; completed artifacts are
persisted, but task snapshots are not restored after app restart.
- Remote-provider cancellation is best-effort: local polling and state
transition stop immediately, but third-party provider-side cancellation
is not guaranteed.
- Renderer integration is intentionally compile-safe and minimal in this
PR; full UX migration should happen in follow-up changes.
- Tesseract keeps a processor-owned runtime service, while other
processors stay as handlers/utilities until they need lifecycle-managed
resources.
The following alternatives were considered:
- Keeping separate OCR and markdown conversion APIs, which would
preserve the current split but continue duplicating task, progress,
cancellation, and result semantics.
- Adding a DataApi task table or Cache mirror for file-processing task
state, which would create a second source of truth for runtime
coordination state.
- Adding renderer push subscriptions in this PR, which would expand the
scope beyond the Main-side task contract.
- Introducing a generic process manager for all processors, which is
premature while only Tesseract currently owns reusable lifecycle
resources.
Links to places where the discussion took place:
`v2-refactor-temp/docs/fileProcessing/file-processing-service.md`
### Breaking changes
None for released user-facing behavior.
If this PR introduces breaking changes, please describe the changes and
the impact on users.
For the in-progress `v2` file-processing integration, this replaces the
split file-processing IPC/preload shape with the unified
`startTask/getTask/cancelTask` contract. It also renames file-processing
feature and preference keys from the old `text_extraction` /
`markdown_conversion` model to `image_to_text` / `document_to_markdown`.
### Special notes for your reviewer
- This PR targets `v2`, not `main`.
- Review this against
`v2-refactor-temp/docs/fileProcessing/file-processing-service.md`; that
document is the source of truth for the module boundary.
- Main review points: unified task API, artifact model, cancellation
semantics, processor-first registry/handlers, hidden provider runtime
state, and no DataApi/Cache task storage.
- `document_to_markdown` artifacts are persisted under
`application.getPath('feature.files.data')/fileId/file-processing/taskId/output.md`.
- `image_to_text` artifacts are returned inline as plain text artifacts
and are not persisted.
- Current local verification status:
- `pnpm format`: passed
- `pnpm build:check`: passed
- Vitest inside `build:check`: `432` test files passed, `7171` tests
passed, `72` skipped
### 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
```release-note
NONE
```
---------
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
### What this PR does
Migrates the MiniApp feature from v1 (Redux + sidecar
`custom-minapps.json`) to the v2 data architecture (DataApi + Preference
+ Cache), and integrates it into the v2 AppShell tab system.
**Before this PR**
- App lists lived in three Redux arrays (`enabled` / `disabled` /
`pinned`); custom-app logos were stripped before persistence and
recovered at runtime from `{userData}/Data/Files/custom-minapps.json`.
- Settings (`region`, `max_keep_alive`, `open_link_external`,
`show_opened_in_sidebar`) lived in legacy redux/electron-store.
- Runtime keep-alive used a module-level `lru-cache` singleton, mirrored
into v2 cache via `onInsert` / `disposeAfter` (two sources of truth —
already a known race).
- Routes were `/app/minapp/*`; sidebar icon literal was `'minapp'`.
- Sidebar mode used the legacy popup container; top-navbar mode was
non-functional.
**After this PR**
- A single `mini_app` SQLite table owns every row (preset + custom).
Preset rows are seeded by `MiniAppSeeder` from `PRESETS_MINI_APPS` on
every boot; custom rows come in via `POST /mini-apps`. The seeder uses
`setWhere isNotNull(presetMiniappId)` so refreshing preset display
fields can never overwrite a custom row whose `appId` happens to collide
with a preset.
- `MiniAppMigrator` imports v1 Redux state and reads
`custom-minapps.json` (path resolved through
`MigrationPaths.customMiniAppsFile`) to recover stripped logos.
- Settings live under typed Preference keys
(`feature.mini_app.{region,max_keep_alive,open_link_external}`); sidebar
icon literal renamed `'minapp'` → `'mini_app'` with a complex preference
transform that rewrites existing user arrays in-place.
- API: `GET/POST/PATCH/DELETE /mini-apps` + `POST
/mini-apps/order:batch`, Zod-validated, fractional-indexing ordering
scoped by `status` (cross-status batches are rejected with
`VALIDATION_ERROR` per the data-ordering-guide contract). Status
transitions reassign `orderKey` to the tail of the target partition
inside a transaction.
- Renderer hook `useMiniApps` exposes **command-style** writes only:
`updateAppStatus(id, status)` and `setAppStatusBulk([{id, status}])`.
The legacy declarative `updateMiniApps(list)` /
`updateDisabledMiniApps(list)` / `updatePinnedMiniApps(list)` are gone —
they took region-filtered subsets and silently disabled rows the caller
never saw.
- Keep-alive list is stored solely in
`useCache('mini_app.opened_keep_alive')`. Cap eviction respects AppShell
pin status: `useMiniAppPopup` reads pinned mini-app routes from
`useTabs` and skips them in eviction. `MiniAppTabsPool` renders webviews
in a stable `appId`-sorted order so LRU reorders never move `<webview>`
DOM nodes (Electron `<webview>` loses its guest WebContents on
detach/reattach).
- **Unified launch path**: clicking any miniapp (from the launcher grid
or a top tab bar entry) calls `openTab('/app/mini-app/<id>', { title,
icon: app.logo })`. A globally-mounted `<MiniAppTabsPool>` in `AppShell`
keeps a `<webview>` alive per opened app, regardless of sidebar vs
top-navbar layout.
- Settings UI rewritten as a `PageSidePanel` drawer composed of
`MiniAppListPair` (visible / hidden columns with drag-drop) and
`MiniAppDisplaySettings` (region / cache slider). New custom-app form is
a separate `NewMiniAppPanel` drawer.
- Sidebar's running-mini-apps strip removed — opened apps live
exclusively in the top tab bar (per #3198804265). Companion preference
`feature.mini_app.show_opened_in_sidebar` deleted from the schema.
### Why we need it and why it was done in this way
Part of the broader v2 data-layer migration (Redux/Dexie/ElectronStore →
DataApi + Preference + Cache).
**Architecture**
- DataApi for entity rows (preserves user content); Preference for
atomic settings; Cache (Memory tier) for runtime ephemera.
- Layered preset pattern (`best-practice-layered-preset-pattern.md`):
preset and custom rows share the same table, discriminated by
`presetMiniappId`. Seeder refreshes preset display fields on re-run;
custom rows are immutable to the seeder.
- Region filtering is a **view-only** concern (read path); the write
path is command-style and never references region. This eliminated a
class of bugs where editing the visible (filtered) list caused
region-hidden rows to drift.
- AppShell tab pinning is the canonical "keep this loaded" signal. The
keep-alive cap respects it; pinned mini-app tabs never get evicted
regardless of cap. Render-order independence in `MiniAppTabsPool`
ensures LRU touches don't move `<webview>` nodes around.
- Per-app icon resolution: `app.logo` is a `CompoundIcon` id (e.g.
`"Moonshot"`) for presets and a URL for custom apps. UI consumers (tab
bar, sidebar entry, settings list) call `getMiniAppsLogo` to resolve the
id to a `CompoundIcon` before rendering, with `<img>` fallback for URL
strings.
- Per-entity tab icons are cleared on internal navigation, sidebar
reuse, and the top-bar settings button — three call sites that all flip
the active tab's URL now consistently reset `icon: undefined` so a
mini-app logo never sticks onto an unrelated route.
**Tradeoffs**
- `useMiniApps` still exposes `miniapps` (region-filtered
enabled+pinned) and `disabled` (region-filtered). These are display-only
views. Renamed/typed wrappers were considered but deferred — the
refactor to command-style writes already eliminated the bug class that
motivated the rename.
- The `applyReorderedList` integration test for
`reorderMiniAppsByStatus` was dropped — `MockUseDataApiUtils` doesn't
fill the SWR cache that `useReorder.readCurrent` reads. Splice logic is
straightforward and the server-side `applyScopedMoves` test covers the
contract.
- Sidebar primitives in `@cherrystudio/ui`-adjacent layout still accept
`miniAppTabs` / `onMiniAppTabClick` props (defensive defaults — render
nothing without a producer). Removing these from the primitive's API is
a separate refactor not in scope.
### Breaking changes
User-visible changes are auto-migrated by the v2 migration framework —
no manual user action required:
- Sidebar icon literal `'minapp'` → `'mini_app'` (rewritten by the
`sidebar_icons_rename` complex preference transform)
- Preference key rename `feature.minapp.*` → `feature.mini_app.*`
(auto-migrated via `classification.json`)
- Custom-app logos stripped from v1 Redux are recovered from
`custom-minapps.json` during migration
One product-shape change is documented under
`v2-refactor-temp/docs/breaking-changes/`:
- `2026-05-07-miniapp-sidebar-running-list-removed.md` — the sidebar no
longer surfaces opened mini-apps under the mini-app entry. Open apps are
accessed exclusively via the top tab bar; pin a tab to keep its state
across switches.
The legacy v1 preference `showOpenedMinappsInSidebar` is reclassified as
`status: deleted` in the migration pipeline; v1 values are dropped
during v1→v2 migration with no v2 destination.
### Special notes for your reviewer
**Verified end-to-end on a real dev profile**: v1 Redux state +
`custom-minapps.json` → v2 SQLite, including pinned-app cross-group
dedup (a v1 pinned app appears in both `pinned` and `enabled` Redux
arrays; the migrator counts duplicates as skipped so the engine's
`targetCount >= sourceCount - skippedCount` invariant holds — without
this, any user with pinned miniapps was blocked from migrating).
**Drizzle migrations** are throwaway in dev per `CLAUDE.md`.
`migrations/sqlite-drizzle/0020_even_hulk.sql` is the single regenerated
migration; it will be wiped to a clean initial migration before release.
**Review history**: 28 line-comments across multiple formal review
rounds. All resolved. The most consequential fixes:
- `applyScopedMoves` in `MiniAppService.reorder` — rejects cross-status
batches with `VALIDATION_ERROR` instead of silently splitting them.
- `update()` reassigns `orderKey` to a fresh tail in the target
partition on status change.
- Empty-string substitution in migrator mappings is now caught by the
post-transform validity check; bad rows are skipped + warned, never
inserted.
- Migrator validation switched from `limit(5)` sample to full `count(*)
WHERE empty-fields` — bad rows can no longer pass validation by virtue
of being beyond the sample window.
- Keep-alive cap exempts pinned tabs (#3198809321 + the kangfenmao
keepalive review); render order in `MiniAppTabsPool` is `appId`-stable
so LRU touches don't move `<webview>` nodes (this was the root cause of
"switching tabs reloads the webview").
**Out of scope**:
- The remaining `@renderer/store/tabs` import in
`PaintingsRoutePage.tsx` is pre-existing v1 residual (not introduced or
touched by this PR).
### Checklist
- [x] PR: description rewritten to reflect the final architecture +
integration with the AppShell tab system
- [x] Code: command-style writes (`updateAppStatus` /
`setAppStatusBulk`); see `useMiniApps`, `MiniAppService`,
`MiniAppMigrator`, `MiniAppTabsPool`, `useMiniAppPopup` for the main
entry points
- [x] Refactor: ~1500 lines of dead/legacy code removed
(`Tab/TabContainer`, `TabsService`, `MiniAppPopupContainer`,
`TopViewMiniAppContainer`, legacy LRU singleton, `PinnedMiniApps`, dead
`userOverrides` / `MiniAppRegistryService`, unused `Signal.ts`)
- [x] Upgrade: v1 → v2 migration verified end-to-end on a real dev
instance
- [x] Documentation: architecture covered by `docs/references/data/`;
one user-visible behavior change documented in
`v2-refactor-temp/docs/breaking-changes/`
- [x] Self-review: multi-agent review via `/gh-pr-review` (twice); all
28 review comments resolved
### Release note
```release-note
NONE - Internal v2 data refactor. User-facing renames (route, sidebar icon, preference keys) are auto-migrated. The sidebar no longer shows a running-mini-apps strip; opened apps live in the top tab bar.
```
---------
Signed-off-by: suyao <sy20010504@gmail.com>
Signed-off-by: chengcheng84 <hello_world0000@outlook.com>
Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
### What this PR does
Before this PR:
The v2 migration system only supports ElectronStore and Redux as data
sources for preference migration. Dexie IndexedDB `settings` table (a
simple KV store used by the renderer for translate settings, pinned
models, etc.) has no migration path.
After this PR:
- Adds `DexieSettingsReader` that pre-loads the Dexie `settings` table
into memory for synchronous KV access, consistent with
`ElectronStoreReader` and `ReduxStateReader`
- Extends `MigrationContext` with a `dexieSettings` data source (makes
`createMigrationContext` async to pre-load the table)
- `PreferencesMigrator` now reads from `'dexie-settings'` source for
both simple and complex mappings
- `DEXIE_SETTINGS_MAPPINGS` is auto-generated into
`PreferencesMappings.ts` from `classification.json`'s new
`dexieSettings` section
- All generator scripts (`generate-migration.js`,
`generate-preferences.js`, `classificationUtils.js`) support
`dexieSettings` as a data source with priority-based deduplication
### Why we need it and why it was done in this way
The following tradeoffs were made:
- `DEXIE_SETTINGS_MAPPINGS` is integrated into `PreferencesMappings.ts`
(not a separate file) to keep all preference mapping exports in one
auto-generated file and simplify imports.
- `dexieSettings` priority is set to 2 (same as `localStorage`, below
`redux` at 3). If the same `targetKey` exists in both `redux` and
`dexieSettings`, redux wins — this is intentional since redux data is
typically more up-to-date.
- Dynamic keys (`image://`, `mcp:provider:*`) are explicitly excluded
from this path. They will be handled by dedicated migrators in future
PRs.
The following alternatives were considered:
- Generating a separate `DexieSettingsMappings.ts` file — rejected in
favor of consolidating all mappings into one file for simpler imports
and unified statistics.
Links to places where the discussion took place:
https://github.com/CherryHQ/cherry-studio/issues/10162
### Breaking changes
None.
### Special notes for your reviewer
- The `classification.json` `dexieSettings.settings` array is currently
empty (skeleton only). Actual key classifications will be added in
follow-up PRs.
- The architecture supports dynamic keys via
`DexieSettingsReader.keys()` prefix filtering, but dynamic key migrators
are out of scope for this PR.
- Source identifier in `PreferencesMigrator` uses `'dexie-settings'`
(with hyphen) to distinguish from the Dexie export reader
(`dexieExport`) used by `ChatMigrator`.
### 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)
- [ ] 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.
- [ ] 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>
- Introduced 'app.use_system_title_bar' preference in preferenceSchemas and DefaultPreferences.
- Updated PreferenceMappings to include mapping for 'useSystemTitleBar'.
- Adjusted classification.json and inventory.json to reflect new preference and metadata changes.
- Regenerated auto-generated files with updated timestamps and statistics.
Add infrastructure for complex preference migrations that support:
- Object splitting (1→N): One source object splits into multiple keys
- Multi-source merging (N→1): Multiple sources merge into targets
- Value calculation/transformation with pure functions
- Conditional mapping based on source values
New files:
- ComplexPreferenceMappings.ts: Mapping definitions and interfaces
- PreferenceTransformers.ts: Utility functions for transformations
- target-key-definitions.json: Target key override definitions
- Tests for both modules (43 tests total)
Modified:
- PreferencesMigrator.ts: Integrated complex mapping processing
with strict conflict detection
- generate-preferences.js: Merge classification.json with
target-key-definitions.json
- README.md: Updated documentation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Removed the config module and replaced its usage with preferenceService across the API server implementation.
- Updated the auth middleware to retrieve the API key from preferenceService instead of the config.
- Adjusted the ApiServerService to ensure a valid API key is generated and stored in preferences.
- Refactored the useApiServer hook to utilize the new preference system for API server configuration.
- Updated related tests to mock preferenceService instead of config.
- Cleaned up unused imports and adjusted related components to align with the new configuration approach.
- Add data-classify tools for data inventory extraction and code generation
- Include consolidated Chinese documentation (README.md)
- Update generated file path references
This temporary directory will be removed after V2 refactor is complete.